summaryrefslogtreecommitdiffstats
path: root/source3
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /source3
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'source3')
-rw-r--r--source3/.clang_complete19
-rw-r--r--source3/.dmallocrc2
-rw-r--r--source3/.indent.pro30
-rw-r--r--source3/Doxyfile192
-rw-r--r--source3/auth/auth.c639
-rw-r--r--source3/auth/auth_builtin.c184
-rw-r--r--source3/auth/auth_generic.c557
-rw-r--r--source3/auth/auth_ntlmssp.c326
-rw-r--r--source3/auth/auth_sam.c315
-rw-r--r--source3/auth/auth_samba4.c401
-rw-r--r--source3/auth/auth_unix.c107
-rw-r--r--source3/auth/auth_util.c2368
-rw-r--r--source3/auth/auth_winbind.c202
-rw-r--r--source3/auth/check_samsec.c641
-rw-r--r--source3/auth/pampass.c896
-rw-r--r--source3/auth/pass_check.c294
-rw-r--r--source3/auth/proto.h438
-rw-r--r--source3/auth/server_info.c789
-rw-r--r--source3/auth/server_info_sam.c124
-rw-r--r--source3/auth/token_util.c1338
-rw-r--r--source3/auth/user_info.c169
-rw-r--r--source3/auth/user_krb5.c263
-rw-r--r--source3/auth/user_util.c447
-rw-r--r--source3/auth/wscript_build62
-rw-r--r--source3/build/__init__.py0
-rw-r--r--source3/build/charset.py48
-rw-r--r--source3/client/README.smbspool17
-rw-r--r--source3/client/client.c6806
-rw-r--r--source3/client/client_proto.h53
-rw-r--r--source3/client/clitar.c1903
-rw-r--r--source3/client/clitar_proto.h34
-rw-r--r--source3/client/dnsbrowse.c216
-rw-r--r--source3/client/smbspool.c888
-rw-r--r--source3/client/smbspool_krb5_wrapper.c341
-rw-r--r--source3/exports/libaddns.syms5
-rw-r--r--source3/exports/modules-darwin.syms1
-rw-r--r--source3/groupdb/mapping.c893
-rw-r--r--source3/groupdb/mapping.h56
-rw-r--r--source3/groupdb/mapping_tdb.c1135
-rw-r--r--source3/groupdb/mapping_tdb.h30
-rw-r--r--source3/include/MacExtensions.h255
-rw-r--r--source3/include/ads.h72
-rw-r--r--source3/include/adt_tree.h46
-rw-r--r--source3/include/async_smb.h47
-rw-r--r--source3/include/auth.h149
-rw-r--r--source3/include/auth_generic.h53
-rw-r--r--source3/include/client.h131
-rw-r--r--source3/include/ctdb_srvids.h46
-rw-r--r--source3/include/ctdbd_conn.h194
-rw-r--r--source3/include/fake_file.h51
-rw-r--r--source3/include/g_lock.h134
-rw-r--r--source3/include/idmap.h74
-rw-r--r--source3/include/idmap_autorid_tdb.h214
-rw-r--r--source3/include/includes.h360
-rw-r--r--source3/include/intl.h23
-rw-r--r--source3/include/krb5_env.h26
-rw-r--r--source3/include/libsmb_internal.h588
-rw-r--r--source3/include/libsmbclient.h3270
-rw-r--r--source3/include/local.h202
-rw-r--r--source3/include/locking.h77
-rw-r--r--source3/include/lsa.h29
-rw-r--r--source3/include/mangle.h42
-rw-r--r--source3/include/messages.h128
-rw-r--r--source3/include/msdfs.h59
-rw-r--r--source3/include/nameserv.h629
-rw-r--r--source3/include/nss_info.h96
-rw-r--r--source3/include/nt_printing.h211
-rw-r--r--source3/include/ntdomain.h35
-rw-r--r--source3/include/ntioctl.h34
-rw-r--r--source3/include/ntquotas.h91
-rw-r--r--source3/include/passdb.h990
-rw-r--r--source3/include/printing.h262
-rw-r--r--source3/include/proto.h774
-rw-r--r--source3/include/registry.h133
-rw-r--r--source3/include/rpc_dce.h37
-rw-r--r--source3/include/rpc_misc.h35
-rw-r--r--source3/include/secrets.h185
-rw-r--r--source3/include/serverid.h31
-rw-r--r--source3/include/session.h48
-rw-r--r--source3/include/smb.h714
-rw-r--r--source3/include/smb_acls.h73
-rw-r--r--source3/include/smb_krb5.h2
-rw-r--r--source3/include/smb_ldap.h110
-rw-r--r--source3/include/smb_macros.h289
-rw-r--r--source3/include/smbldap.h121
-rw-r--r--source3/include/smbprofile.h623
-rw-r--r--source3/include/srvstr.h21
-rw-r--r--source3/include/stamp-h.in1
-rw-r--r--source3/include/sysquotas.h85
-rw-r--r--source3/include/tldap.h315
-rw-r--r--source3/include/tldap_util.h105
-rw-r--r--source3/include/trans2.h450
-rw-r--r--source3/include/transfer_file.h32
-rw-r--r--source3/include/util_event.h34
-rw-r--r--source3/include/util_sd.h37
-rw-r--r--source3/include/util_tdb.h65
-rw-r--r--source3/include/vfs.h2253
-rw-r--r--source3/include/vfs_macros.h592
-rw-r--r--source3/intl/linux-msg.sed99
-rw-r--r--source3/lib/ABI/smbldap-0.sigs28
-rw-r--r--source3/lib/ABI/smbldap-1.sigs31
-rw-r--r--source3/lib/ABI/smbldap-2.1.0.sigs33
-rw-r--r--source3/lib/ABI/smbldap-2.sigs32
-rw-r--r--source3/lib/addrchange.c314
-rw-r--r--source3/lib/addrchange.h45
-rw-r--r--source3/lib/adouble.c2856
-rw-r--r--source3/lib/adouble.h192
-rw-r--r--source3/lib/adt_tree.c448
-rw-r--r--source3/lib/audit.c149
-rw-r--r--source3/lib/avahi.c275
-rw-r--r--source3/lib/background.c256
-rw-r--r--source3/lib/background.h45
-rw-r--r--source3/lib/cbuf.c328
-rw-r--r--source3/lib/cbuf.h245
-rw-r--r--source3/lib/charcnv.c535
-rw-r--r--source3/lib/cleanupdb.c169
-rw-r--r--source3/lib/cleanupdb.h32
-rw-r--r--source3/lib/cluster_support.c72
-rw-r--r--source3/lib/cluster_support.h21
-rw-r--r--source3/lib/cmdline_contexts.c71
-rw-r--r--source3/lib/cmdline_contexts.h27
-rw-r--r--source3/lib/ctdb_dummy.c180
-rw-r--r--source3/lib/ctdbd_conn.c2341
-rw-r--r--source3/lib/dbwrap/dbwrap_ctdb.c1985
-rw-r--r--source3/lib/dbwrap/dbwrap_ctdb.h40
-rw-r--r--source3/lib/dbwrap/dbwrap_open.c197
-rw-r--r--source3/lib/dbwrap/dbwrap_open.h45
-rw-r--r--source3/lib/dbwrap/dbwrap_watch.c1285
-rw-r--r--source3/lib/dbwrap/dbwrap_watch.h45
-rw-r--r--source3/lib/dmallocmsg.c79
-rw-r--r--source3/lib/dumpcore.c343
-rw-r--r--source3/lib/errmap_unix.c158
-rw-r--r--source3/lib/eventlog/eventlog.c1066
-rw-r--r--source3/lib/eventlog/eventlog.h45
-rw-r--r--source3/lib/eventlog/proto.h62
-rw-r--r--source3/lib/file_id.c82
-rw-r--r--source3/lib/file_id.h44
-rw-r--r--source3/lib/filename_util.c406
-rw-r--r--source3/lib/fstring.c67
-rw-r--r--source3/lib/g_lock.c1984
-rw-r--r--source3/lib/gencache.c742
-rw-r--r--source3/lib/gencache.h60
-rw-r--r--source3/lib/global_contexts.c71
-rw-r--r--source3/lib/global_contexts.h31
-rw-r--r--source3/lib/id_cache.c116
-rw-r--r--source3/lib/id_cache.h40
-rw-r--r--source3/lib/idmap_cache.c442
-rw-r--r--source3/lib/idmap_cache.h40
-rw-r--r--source3/lib/interface.c721
-rw-r--r--source3/lib/interface.h46
-rw-r--r--source3/lib/ldap_debug_handler.c51
-rw-r--r--source3/lib/ldap_escape.c137
-rw-r--r--source3/lib/lsa.c73
-rw-r--r--source3/lib/messages.c1480
-rw-r--r--source3/lib/messages_ctdb.c306
-rw-r--r--source3/lib/messages_ctdb.h44
-rw-r--r--source3/lib/messages_ctdb_ref.c145
-rw-r--r--source3/lib/messages_ctdb_ref.h35
-rw-r--r--source3/lib/messages_util.c42
-rw-r--r--source3/lib/messages_util.h33
-rw-r--r--source3/lib/ms_fnmatch.c232
-rw-r--r--source3/lib/namearray.c39
-rw-r--r--source3/lib/namemap_cache.c333
-rw-r--r--source3/lib/namemap_cache.h49
-rw-r--r--source3/lib/netapi/Doxyfile1362
-rw-r--r--source3/lib/netapi/cm.c284
-rw-r--r--source3/lib/netapi/examples/common.c249
-rw-r--r--source3/lib/netapi/examples/common.h18
-rw-r--r--source3/lib/netapi/examples/dsgetdc/dsgetdc.c101
-rw-r--r--source3/lib/netapi/examples/file/file_close.c83
-rw-r--r--source3/lib/netapi/examples/file/file_enum.c146
-rw-r--r--source3/lib/netapi/examples/file/file_getinfo.c112
-rw-r--r--source3/lib/netapi/examples/getdc/getdc.c86
-rw-r--r--source3/lib/netapi/examples/group/group_add.c90
-rw-r--r--source3/lib/netapi/examples/group/group_adduser.c91
-rw-r--r--source3/lib/netapi/examples/group/group_del.c82
-rw-r--r--source3/lib/netapi/examples/group/group_deluser.c91
-rw-r--r--source3/lib/netapi/examples/group/group_enum.c153
-rw-r--r--source3/lib/netapi/examples/group/group_getinfo.c127
-rw-r--r--source3/lib/netapi/examples/group/group_getusers.c132
-rw-r--r--source3/lib/netapi/examples/group/group_setinfo.c142
-rw-r--r--source3/lib/netapi/examples/group/group_setusers.c142
-rw-r--r--source3/lib/netapi/examples/join/djoin.c166
-rw-r--r--source3/lib/netapi/examples/join/getjoinableous.c110
-rw-r--r--source3/lib/netapi/examples/join/getjoininformation.c105
-rw-r--r--source3/lib/netapi/examples/join/netdomjoin.c104
-rw-r--r--source3/lib/netapi/examples/join/provision_computer_account.c122
-rw-r--r--source3/lib/netapi/examples/join/rename_machine.c101
-rw-r--r--source3/lib/netapi/examples/join/request_offline_domain_join.c97
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_add.c106
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_addmembers.c141
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_del.c83
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_delmembers.c141
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_enum.c126
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_getinfo.c112
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_getmembers.c165
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_setinfo.c128
-rw-r--r--source3/lib/netapi/examples/localgroup/localgroup_setmembers.c146
-rw-r--r--source3/lib/netapi/examples/netdomjoin-gui/logo-small.pngbin0 -> 4485 bytes
-rw-r--r--source3/lib/netapi/examples/netdomjoin-gui/logo.pngbin0 -> 9329 bytes
-rw-r--r--source3/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c1888
-rwxr-xr-xsource3/lib/netapi/examples/netdomjoin-gui/samba.icobin0 -> 1406 bytes
-rw-r--r--source3/lib/netapi/examples/netlogon/netlogon_control.c143
-rw-r--r--source3/lib/netapi/examples/netlogon/netlogon_control2.c147
-rw-r--r--source3/lib/netapi/examples/netlogon/nltest.c677
-rw-r--r--source3/lib/netapi/examples/server/remote_tod.c83
-rw-r--r--source3/lib/netapi/examples/server/server_getinfo.c136
-rw-r--r--source3/lib/netapi/examples/share/share_add.c105
-rw-r--r--source3/lib/netapi/examples/share/share_del.c85
-rw-r--r--source3/lib/netapi/examples/share/share_enum.c142
-rw-r--r--source3/lib/netapi/examples/share/share_getinfo.c152
-rw-r--r--source3/lib/netapi/examples/share/share_setinfo.c105
-rw-r--r--source3/lib/netapi/examples/shutdown/shutdown_abort.c76
-rw-r--r--source3/lib/netapi/examples/shutdown/shutdown_init.c94
-rw-r--r--source3/lib/netapi/examples/user/user_add.c103
-rw-r--r--source3/lib/netapi/examples/user/user_chgpwd.c99
-rw-r--r--source3/lib/netapi/examples/user/user_del.c82
-rw-r--r--source3/lib/netapi/examples/user/user_dispinfo.c100
-rw-r--r--source3/lib/netapi/examples/user/user_enum.c157
-rw-r--r--source3/lib/netapi/examples/user/user_getgroups.c133
-rw-r--r--source3/lib/netapi/examples/user/user_getinfo.c293
-rw-r--r--source3/lib/netapi/examples/user/user_getlocalgroups.c122
-rw-r--r--source3/lib/netapi/examples/user/user_modalsget.c131
-rw-r--r--source3/lib/netapi/examples/user/user_modalsset.c132
-rw-r--r--source3/lib/netapi/examples/user/user_setgroups.c144
-rw-r--r--source3/lib/netapi/examples/user/user_setinfo.c200
-rw-r--r--source3/lib/netapi/examples/wscript_build72
-rw-r--r--source3/lib/netapi/file.c283
-rw-r--r--source3/lib/netapi/getdc.c212
-rw-r--r--source3/lib/netapi/group.c1848
-rw-r--r--source3/lib/netapi/joindomain.c1145
-rw-r--r--source3/lib/netapi/libnetapi.c3019
-rw-r--r--source3/lib/netapi/libnetapi.h523
-rw-r--r--source3/lib/netapi/localgroup.c1390
-rw-r--r--source3/lib/netapi/netapi.c523
-rw-r--r--source3/lib/netapi/netapi.h2778
-rw-r--r--source3/lib/netapi/netapi_net.h26
-rw-r--r--source3/lib/netapi/netapi_private.h114
-rw-r--r--source3/lib/netapi/netlogon.c248
-rw-r--r--source3/lib/netapi/samr.c346
-rw-r--r--source3/lib/netapi/serverinfo.c701
-rw-r--r--source3/lib/netapi/share.c573
-rw-r--r--source3/lib/netapi/shutdown.c110
-rw-r--r--source3/lib/netapi/sid.c71
-rw-r--r--source3/lib/netapi/tests/common.c116
-rw-r--r--source3/lib/netapi/tests/common.h67
-rw-r--r--source3/lib/netapi/tests/netapitest.c112
-rw-r--r--source3/lib/netapi/tests/netdisplay.c150
-rw-r--r--source3/lib/netapi/tests/netfile.c138
-rw-r--r--source3/lib/netapi/tests/netgroup.c503
-rw-r--r--source3/lib/netapi/tests/netlocalgroup.c226
-rw-r--r--source3/lib/netapi/tests/netserver.c61
-rw-r--r--source3/lib/netapi/tests/netshare.c250
-rw-r--r--source3/lib/netapi/tests/netuser.c462
-rw-r--r--source3/lib/netapi/tests/netwksta.c62
-rw-r--r--source3/lib/netapi/tests/wscript_build6
-rw-r--r--source3/lib/netapi/user.c3638
-rw-r--r--source3/lib/netapi/wkstainfo.c154
-rw-r--r--source3/lib/per_thread_cwd.c156
-rw-r--r--source3/lib/privileges.c514
-rw-r--r--source3/lib/privileges.h45
-rw-r--r--source3/lib/readdir_attr.h37
-rw-r--r--source3/lib/recvfile.c310
-rw-r--r--source3/lib/sendfile.c602
-rw-r--r--source3/lib/server_id_db_util.c99
-rw-r--r--source3/lib/server_id_db_util.h22
-rw-r--r--source3/lib/server_id_watch.c104
-rw-r--r--source3/lib/server_id_watch.h33
-rw-r--r--source3/lib/server_mutex.c104
-rw-r--r--source3/lib/serverid.c62
-rw-r--r--source3/lib/sessionid_tdb.c95
-rw-r--r--source3/lib/sharesec.c588
-rw-r--r--source3/lib/smbconf/pys3smbconf.c212
-rw-r--r--source3/lib/smbconf/smbconf_init.c96
-rw-r--r--source3/lib/smbconf/smbconf_init.h32
-rw-r--r--source3/lib/smbconf/smbconf_reg.c1246
-rw-r--r--source3/lib/smbconf/smbconf_reg.h38
-rw-r--r--source3/lib/smbconf/testsuite.c338
-rw-r--r--source3/lib/smbd_shim.c116
-rw-r--r--source3/lib/smbd_shim.h53
-rw-r--r--source3/lib/smbldap.c1941
-rw-r--r--source3/lib/smbrun.c354
-rw-r--r--source3/lib/srprs.c228
-rw-r--r--source3/lib/srprs.h189
-rw-r--r--source3/lib/string_replace.c192
-rw-r--r--source3/lib/string_replace.h35
-rw-r--r--source3/lib/substitute.c675
-rw-r--r--source3/lib/substitute.h65
-rw-r--r--source3/lib/substitute_generic.c113
-rw-r--r--source3/lib/sysacls.c518
-rw-r--r--source3/lib/sysquotas.c619
-rw-r--r--source3/lib/sysquotas_4A.c334
-rw-r--r--source3/lib/sysquotas_4B.c243
-rw-r--r--source3/lib/sysquotas_jfs2.c150
-rw-r--r--source3/lib/sysquotas_linux.c220
-rw-r--r--source3/lib/sysquotas_nfs.c303
-rw-r--r--source3/lib/sysquotas_xfs.c345
-rw-r--r--source3/lib/system.c1076
-rw-r--r--source3/lib/system_smbd.c253
-rw-r--r--source3/lib/tallocmsg.c85
-rw-r--r--source3/lib/tdb_validate.c538
-rw-r--r--source3/lib/tdb_validate.h79
-rw-r--r--source3/lib/test_adouble.c389
-rw-r--r--source3/lib/test_tldap.c63
-rw-r--r--source3/lib/tevent_barrier.c192
-rw-r--r--source3/lib/tevent_barrier.h37
-rw-r--r--source3/lib/tevent_glib_glue.c881
-rw-r--r--source3/lib/tevent_glib_glue.h68
-rw-r--r--source3/lib/tevent_glib_glue_tests.c397
-rw-r--r--source3/lib/time.c458
-rw-r--r--source3/lib/tldap.c2726
-rw-r--r--source3/lib/tldap_gensec_bind.c366
-rw-r--r--source3/lib/tldap_gensec_bind.h33
-rw-r--r--source3/lib/tldap_util.c843
-rw-r--r--source3/lib/username.c243
-rw-r--r--source3/lib/util.c2013
-rw-r--r--source3/lib/util_builtin.c158
-rw-r--r--source3/lib/util_cluster.c42
-rw-r--r--source3/lib/util_cluster.h27
-rw-r--r--source3/lib/util_ea.c126
-rw-r--r--source3/lib/util_ea.h36
-rw-r--r--source3/lib/util_event.c102
-rw-r--r--source3/lib/util_file.c187
-rw-r--r--source3/lib/util_file.h35
-rw-r--r--source3/lib/util_macstreams.c73
-rw-r--r--source3/lib/util_macstreams.h27
-rw-r--r--source3/lib/util_malloc.c122
-rw-r--r--source3/lib/util_matching.c391
-rw-r--r--source3/lib/util_matching.h40
-rw-r--r--source3/lib/util_names.c86
-rw-r--r--source3/lib/util_nscd.c45
-rw-r--r--source3/lib/util_nttoken.c103
-rw-r--r--source3/lib/util_path.c350
-rw-r--r--source3/lib/util_path.h55
-rw-r--r--source3/lib/util_procid.c69
-rw-r--r--source3/lib/util_procid.h37
-rw-r--r--source3/lib/util_sd.c626
-rw-r--r--source3/lib/util_sec.c598
-rw-r--r--source3/lib/util_sid.c206
-rw-r--r--source3/lib/util_sid_passdb.c131
-rw-r--r--source3/lib/util_sid_passdb.h36
-rw-r--r--source3/lib/util_sock.c1232
-rw-r--r--source3/lib/util_specialsids.c42
-rw-r--r--source3/lib/util_specialsids.h29
-rw-r--r--source3/lib/util_str.c762
-rw-r--r--source3/lib/util_tdb.c449
-rw-r--r--source3/lib/util_transfer_file.c119
-rw-r--r--source3/lib/util_tsock.c149
-rw-r--r--source3/lib/util_tsock.h38
-rw-r--r--source3/lib/util_unixsids.c80
-rw-r--r--source3/lib/util_unixsids.h36
-rw-r--r--source3/lib/util_wellknown.c192
-rw-r--r--source3/lib/version.c32
-rw-r--r--source3/lib/version_test.c26
-rw-r--r--source3/lib/winbind_util.c403
-rw-r--r--source3/lib/winbind_util.h56
-rw-r--r--source3/lib/wins_srv.c409
-rw-r--r--source3/lib/xattr_tdb.c468
-rw-r--r--source3/lib/xattr_tdb.h41
-rw-r--r--source3/libads/ads_ldap_protos.h142
-rw-r--r--source3/libads/ads_proto.h229
-rw-r--r--source3/libads/ads_status.c157
-rw-r--r--source3/libads/ads_status.h68
-rw-r--r--source3/libads/ads_struct.c239
-rw-r--r--source3/libads/authdata.c323
-rw-r--r--source3/libads/cldap.c453
-rw-r--r--source3/libads/cldap.h60
-rw-r--r--source3/libads/disp_sec.c254
-rw-r--r--source3/libads/kerberos.c908
-rw-r--r--source3/libads/kerberos_keytab.c1026
-rw-r--r--source3/libads/kerberos_proto.h104
-rw-r--r--source3/libads/kerberos_util.c80
-rw-r--r--source3/libads/krb5_setpw.c329
-rw-r--r--source3/libads/ldap.c4684
-rw-r--r--source3/libads/ldap_printer.c361
-rw-r--r--source3/libads/ldap_schema.c354
-rw-r--r--source3/libads/ldap_schema.h57
-rw-r--r--source3/libads/ldap_schema_oids.h49
-rw-r--r--source3/libads/ldap_user.c132
-rw-r--r--source3/libads/ldap_utils.c389
-rw-r--r--source3/libads/net_ads_setspn.c229
-rw-r--r--source3/libads/sasl.c855
-rw-r--r--source3/libads/sasl_wrapping.c351
-rw-r--r--source3/libads/sitename_cache.c132
-rw-r--r--source3/libads/sitename_cache.h28
-rw-r--r--source3/libads/util.c236
-rw-r--r--source3/libgpo/gpext/registry.c440
-rw-r--r--source3/libgpo/gpext/scripts.c487
-rw-r--r--source3/libgpo/gpext/security.c297
-rw-r--r--source3/libgpo/gpext/wscript_build23
-rw-r--r--source3/libnet/libnet_dssync.c724
-rw-r--r--source3/libnet/libnet_dssync.h73
-rw-r--r--source3/libnet/libnet_dssync_keytab.c609
-rw-r--r--source3/libnet/libnet_dssync_passdb.c1955
-rw-r--r--source3/libnet/libnet_join.c3289
-rw-r--r--source3/libnet/libnet_join.h40
-rw-r--r--source3/libnet/libnet_join_offline.c441
-rw-r--r--source3/libnet/libnet_join_offline.h26
-rw-r--r--source3/libnet/libnet_keytab.c457
-rw-r--r--source3/libnet/libnet_keytab.h61
-rw-r--r--source3/libnet/netapi.pc.in11
-rw-r--r--source3/librpc/crypto/gse.c1438
-rw-r--r--source3/librpc/crypto/gse.h26
-rw-r--r--source3/librpc/crypto/gse_krb5.c609
-rw-r--r--source3/librpc/crypto/gse_krb5.h30
-rw-r--r--source3/librpc/gen_ndr/README4
-rw-r--r--source3/librpc/idl/IDL_LICENSE.txt9
-rw-r--r--source3/librpc/idl/ads.idl130
-rw-r--r--source3/librpc/idl/leases_db.idl54
-rw-r--r--source3/librpc/idl/libnet_join.idl91
-rw-r--r--source3/librpc/idl/libnetapi.idl2070
-rw-r--r--source3/librpc/idl/open_files.idl133
-rw-r--r--source3/librpc/idl/perfcount.idl172
-rw-r--r--source3/librpc/idl/rpc_host.idl93
-rw-r--r--source3/librpc/idl/rpcd_witness.idl109
-rw-r--r--source3/librpc/idl/secrets.idl131
-rw-r--r--source3/librpc/idl/smbXsrv.idl549
-rw-r--r--source3/librpc/idl/wscript_build41
-rw-r--r--source3/librpc/ndr/ndr_ads.c32
-rw-r--r--source3/librpc/rpc/dcerpc.h80
-rw-r--r--source3/librpc/rpc/dcerpc_helpers.c520
-rw-r--r--source3/librpc/wscript_build53
-rw-r--r--source3/libsmb/ABI/smbclient-0.1.0.sigs172
-rw-r--r--source3/libsmb/ABI/smbclient-0.2.0.sigs172
-rw-r--r--source3/libsmb/ABI/smbclient-0.2.1.sigs174
-rw-r--r--source3/libsmb/ABI/smbclient-0.2.2.sigs176
-rw-r--r--source3/libsmb/ABI/smbclient-0.2.3.sigs179
-rw-r--r--source3/libsmb/ABI/smbclient-0.3.0.sigs179
-rw-r--r--source3/libsmb/ABI/smbclient-0.3.1.sigs180
-rw-r--r--source3/libsmb/ABI/smbclient-0.3.2.sigs181
-rw-r--r--source3/libsmb/ABI/smbclient-0.3.3.sigs184
-rw-r--r--source3/libsmb/ABI/smbclient-0.4.0.sigs184
-rw-r--r--source3/libsmb/ABI/smbclient-0.5.0.sigs185
-rw-r--r--source3/libsmb/ABI/smbclient-0.6.0.sigs188
-rw-r--r--source3/libsmb/ABI/smbclient-0.7.0.sigs188
-rw-r--r--source3/libsmb/ABI/smbclient-0.8.0.sigs190
-rw-r--r--source3/libsmb/async_smb.c269
-rw-r--r--source3/libsmb/auth_generic.c229
-rw-r--r--source3/libsmb/cli_smb2_fnum.c5040
-rw-r--r--source3/libsmb/cli_smb2_fnum.h324
-rw-r--r--source3/libsmb/cliconnect.c4026
-rw-r--r--source3/libsmb/clidfs.c1451
-rw-r--r--source3/libsmb/clidgram.c482
-rw-r--r--source3/libsmb/clidgram.h54
-rw-r--r--source3/libsmb/clientgen.c650
-rw-r--r--source3/libsmb/clierror.c119
-rw-r--r--source3/libsmb/clifile.c7486
-rw-r--r--source3/libsmb/clifsinfo.c815
-rw-r--r--source3/libsmb/clilist.c1255
-rw-r--r--source3/libsmb/climessage.c411
-rw-r--r--source3/libsmb/clioplock.c169
-rw-r--r--source3/libsmb/cliprint.c219
-rw-r--r--source3/libsmb/cliquota.c696
-rw-r--r--source3/libsmb/clirap.c1853
-rw-r--r--source3/libsmb/clirap.h206
-rw-r--r--source3/libsmb/clireadwrite.c1907
-rw-r--r--source3/libsmb/clisecdesc.c372
-rw-r--r--source3/libsmb/clisymlink.c650
-rw-r--r--source3/libsmb/clitrans.c224
-rw-r--r--source3/libsmb/conncache.c227
-rw-r--r--source3/libsmb/dsgetdcname.c1274
-rw-r--r--source3/libsmb/dsgetdcname.h43
-rw-r--r--source3/libsmb/errormap.c295
-rw-r--r--source3/libsmb/errormap_wbc.c62
-rw-r--r--source3/libsmb/errormap_wbc.h29
-rw-r--r--source3/libsmb/libsmb.h31
-rw-r--r--source3/libsmb/libsmb_cache.c242
-rw-r--r--source3/libsmb/libsmb_compat.c581
-rw-r--r--source3/libsmb/libsmb_context.c807
-rw-r--r--source3/libsmb/libsmb_dir.c2726
-rw-r--r--source3/libsmb/libsmb_file.c803
-rw-r--r--source3/libsmb/libsmb_misc.c46
-rw-r--r--source3/libsmb/libsmb_path.c416
-rw-r--r--source3/libsmb/libsmb_printjob.c335
-rw-r--r--source3/libsmb/libsmb_server.c910
-rw-r--r--source3/libsmb/libsmb_setget.c1155
-rw-r--r--source3/libsmb/libsmb_stat.c538
-rw-r--r--source3/libsmb/libsmb_thread_impl.c127
-rw-r--r--source3/libsmb/libsmb_thread_posix.c52
-rw-r--r--source3/libsmb/libsmb_xattr.c2406
-rw-r--r--source3/libsmb/namecache.c480
-rw-r--r--source3/libsmb/namequery.c3538
-rw-r--r--source3/libsmb/namequery.h116
-rw-r--r--source3/libsmb/namequery_dc.c262
-rw-r--r--source3/libsmb/nmblib.c1466
-rw-r--r--source3/libsmb/nmblib.h59
-rw-r--r--source3/libsmb/passchange.c323
-rw-r--r--source3/libsmb/proto.h1080
-rw-r--r--source3/libsmb/pylibsmb.c3030
-rw-r--r--source3/libsmb/samlogon_cache.c405
-rw-r--r--source3/libsmb/samlogon_cache.h46
-rw-r--r--source3/libsmb/smbclient.pc.in11
-rw-r--r--source3/libsmb/smberr.c224
-rw-r--r--source3/libsmb/smbsock_connect.c870
-rw-r--r--source3/libsmb/trusts_util.c630
-rw-r--r--source3/libsmb/unexpected.c751
-rw-r--r--source3/libsmb/unexpected.h49
-rw-r--r--source3/libsmb/wscript30
-rw-r--r--source3/locale/net/de.po8852
-rwxr-xr-xsource3/locale/net/genmsg61
-rw-r--r--source3/locale/pam_winbind/ar.po559
-rw-r--r--source3/locale/pam_winbind/cs.po559
-rw-r--r--source3/locale/pam_winbind/da.po561
-rw-r--r--source3/locale/pam_winbind/de.po562
-rw-r--r--source3/locale/pam_winbind/es.po559
-rw-r--r--source3/locale/pam_winbind/fi.po564
-rw-r--r--source3/locale/pam_winbind/fr.po547
-rwxr-xr-xsource3/locale/pam_winbind/genmsg27
-rw-r--r--source3/locale/pam_winbind/hu.po560
-rw-r--r--source3/locale/pam_winbind/it.po559
-rw-r--r--source3/locale/pam_winbind/ja.po552
-rw-r--r--source3/locale/pam_winbind/ko.po559
-rw-r--r--source3/locale/pam_winbind/nb.po566
-rw-r--r--source3/locale/pam_winbind/nl.po559
-rw-r--r--source3/locale/pam_winbind/pl.po559
-rw-r--r--source3/locale/pam_winbind/pt_BR.po559
-rw-r--r--source3/locale/pam_winbind/ru.po559
-rw-r--r--source3/locale/pam_winbind/sv.po559
-rw-r--r--source3/locale/pam_winbind/tr.po548
-rw-r--r--source3/locale/pam_winbind/zh_CN.po559
-rw-r--r--source3/locale/pam_winbind/zh_TW.po559
-rw-r--r--source3/locking/brlock.c1998
-rw-r--r--source3/locking/leases_db.c724
-rw-r--r--source3/locking/leases_db.h80
-rw-r--r--source3/locking/leases_util.c77
-rw-r--r--source3/locking/locking.c1359
-rw-r--r--source3/locking/posix.c1371
-rw-r--r--source3/locking/proto.h215
-rw-r--r--source3/locking/share_mode_lock.c3193
-rw-r--r--source3/locking/share_mode_lock.h202
-rw-r--r--source3/locking/share_mode_lock_private.h25
-rw-r--r--source3/mainpage.dox7
-rw-r--r--source3/modules/README-gpfs-acl.txt82
-rw-r--r--source3/modules/README.nfs4acls.txt89
-rw-r--r--source3/modules/The_New_VFS.org469
-rw-r--r--source3/modules/The_New_VFS.txt607
-rw-r--r--source3/modules/getdate.c2742
-rw-r--r--source3/modules/getdate.h45
-rw-r--r--source3/modules/getdate.y1118
-rw-r--r--source3/modules/hash_inode.c87
-rw-r--r--source3/modules/hash_inode.h25
-rw-r--r--source3/modules/lib_vxfs.c279
-rw-r--r--source3/modules/nfs41acl.x111
-rw-r--r--source3/modules/nfs4_acls.c1223
-rw-r--r--source3/modules/nfs4_acls.h184
-rw-r--r--source3/modules/nfs4acl_xattr.h40
-rw-r--r--source3/modules/nfs4acl_xattr_ndr.c300
-rw-r--r--source3/modules/nfs4acl_xattr_ndr.h44
-rw-r--r--source3/modules/nfs4acl_xattr_nfs.c894
-rw-r--r--source3/modules/nfs4acl_xattr_nfs.h36
-rw-r--r--source3/modules/nfs4acl_xattr_util.c73
-rw-r--r--source3/modules/nfs4acl_xattr_util.h25
-rw-r--r--source3/modules/nfs4acl_xattr_xdr.c401
-rw-r--r--source3/modules/nfs4acl_xattr_xdr.h34
-rw-r--r--source3/modules/non_posix_acls.c63
-rw-r--r--source3/modules/non_posix_acls.h24
-rw-r--r--source3/modules/offload_token.c348
-rw-r--r--source3/modules/offload_token.h44
-rw-r--r--source3/modules/posixacl_xattr.c423
-rw-r--r--source3/modules/posixacl_xattr.h36
-rw-r--r--source3/modules/test_nfs4_acls.c1898
-rw-r--r--source3/modules/test_vfs_full_audit.c49
-rw-r--r--source3/modules/test_vfs_gpfs.c101
-rw-r--r--source3/modules/test_vfs_posixacl.c171
-rw-r--r--source3/modules/util_reparse.c84
-rw-r--r--source3/modules/util_reparse.h40
-rw-r--r--source3/modules/vfs_acl_common.c1181
-rw-r--r--source3/modules/vfs_acl_common.h91
-rw-r--r--source3/modules/vfs_acl_tdb.c387
-rw-r--r--source3/modules/vfs_acl_xattr.c552
-rw-r--r--source3/modules/vfs_afsacl.c1085
-rw-r--r--source3/modules/vfs_aio_fork.c936
-rw-r--r--source3/modules/vfs_aio_pthread.c538
-rw-r--r--source3/modules/vfs_aixacl.c131
-rw-r--r--source3/modules/vfs_aixacl.h34
-rw-r--r--source3/modules/vfs_aixacl2.c480
-rw-r--r--source3/modules/vfs_aixacl_util.c288
-rw-r--r--source3/modules/vfs_aixacl_util.h22
-rw-r--r--source3/modules/vfs_audit.c355
-rw-r--r--source3/modules/vfs_btrfs.c887
-rw-r--r--source3/modules/vfs_cacheprime.c181
-rw-r--r--source3/modules/vfs_cap.c1004
-rw-r--r--source3/modules/vfs_catia.c1952
-rw-r--r--source3/modules/vfs_ceph.c1960
-rw-r--r--source3/modules/vfs_ceph_snapshots.c1478
-rw-r--r--source3/modules/vfs_commit.c400
-rw-r--r--source3/modules/vfs_crossrename.c194
-rw-r--r--source3/modules/vfs_default.c4098
-rw-r--r--source3/modules/vfs_default_quota.c236
-rw-r--r--source3/modules/vfs_delay_inject.c440
-rw-r--r--source3/modules/vfs_dfs_samba4.c162
-rw-r--r--source3/modules/vfs_dirsort.c267
-rw-r--r--source3/modules/vfs_error_inject.c219
-rw-r--r--source3/modules/vfs_expand_msdfs.c270
-rw-r--r--source3/modules/vfs_extd_audit.c418
-rw-r--r--source3/modules/vfs_fake_acls.c704
-rw-r--r--source3/modules/vfs_fake_dfq.c281
-rw-r--r--source3/modules/vfs_fake_perms.c108
-rw-r--r--source3/modules/vfs_fileid.c723
-rw-r--r--source3/modules/vfs_fruit.c5478
-rw-r--r--source3/modules/vfs_full_audit.c3035
-rw-r--r--source3/modules/vfs_glusterfs.c2673
-rw-r--r--source3/modules/vfs_glusterfs_fuse.c273
-rw-r--r--source3/modules/vfs_gpfs.c2546
-rw-r--r--source3/modules/vfs_hpuxacl.c1174
-rw-r--r--source3/modules/vfs_hpuxacl.h56
-rw-r--r--source3/modules/vfs_io_uring.c822
-rw-r--r--source3/modules/vfs_linux_xfs_sgid.c128
-rw-r--r--source3/modules/vfs_media_harmony.c1873
-rw-r--r--source3/modules/vfs_nfs4acl_xattr.c580
-rw-r--r--source3/modules/vfs_not_implemented.c1193
-rw-r--r--source3/modules/vfs_offline.c50
-rw-r--r--source3/modules/vfs_posix_eadb.c450
-rw-r--r--source3/modules/vfs_posixacl.c389
-rw-r--r--source3/modules/vfs_posixacl.h38
-rw-r--r--source3/modules/vfs_prealloc.c222
-rw-r--r--source3/modules/vfs_preopen.c757
-rw-r--r--source3/modules/vfs_readahead.c186
-rw-r--r--source3/modules/vfs_readonly.c113
-rw-r--r--source3/modules/vfs_recycle.c737
-rw-r--r--source3/modules/vfs_shadow_copy.c274
-rw-r--r--source3/modules/vfs_shadow_copy2.c3303
-rw-r--r--source3/modules/vfs_shell_snap.c201
-rw-r--r--source3/modules/vfs_snapper.c2647
-rw-r--r--source3/modules/vfs_solarisacl.c699
-rw-r--r--source3/modules/vfs_solarisacl.h44
-rw-r--r--source3/modules/vfs_streams_depot.c1218
-rw-r--r--source3/modules/vfs_streams_xattr.c1614
-rw-r--r--source3/modules/vfs_syncops.c418
-rw-r--r--source3/modules/vfs_time_audit.c2812
-rw-r--r--source3/modules/vfs_tsmsm.c573
-rw-r--r--source3/modules/vfs_unityed_media.c1544
-rw-r--r--source3/modules/vfs_virusfilter.c1677
-rw-r--r--source3/modules/vfs_virusfilter_clamav.c195
-rw-r--r--source3/modules/vfs_virusfilter_common.h154
-rw-r--r--source3/modules/vfs_virusfilter_dummy.c58
-rw-r--r--source3/modules/vfs_virusfilter_fsav.c451
-rw-r--r--source3/modules/vfs_virusfilter_sophos.c391
-rw-r--r--source3/modules/vfs_virusfilter_utils.c1036
-rw-r--r--source3/modules/vfs_virusfilter_utils.h177
-rw-r--r--source3/modules/vfs_vxfs.c736
-rw-r--r--source3/modules/vfs_vxfs.h36
-rw-r--r--source3/modules/vfs_widelinks.c418
-rw-r--r--source3/modules/vfs_worm.c359
-rw-r--r--source3/modules/vfs_xattr_tdb.c702
-rw-r--r--source3/modules/vfs_zfsacl.c507
-rw-r--r--source3/modules/wscript_build639
-rw-r--r--source3/nmbd/asyncdns.c346
-rw-r--r--source3/nmbd/nmbd.c1091
-rw-r--r--source3/nmbd/nmbd.h39
-rw-r--r--source3/nmbd/nmbd_become_dmb.c398
-rw-r--r--source3/nmbd/nmbd_become_lmb.c587
-rw-r--r--source3/nmbd/nmbd_browserdb.c180
-rw-r--r--source3/nmbd/nmbd_browsesync.c692
-rw-r--r--source3/nmbd/nmbd_elections.c395
-rw-r--r--source3/nmbd/nmbd_incomingdgrams.c841
-rw-r--r--source3/nmbd/nmbd_incomingrequests.c590
-rw-r--r--source3/nmbd/nmbd_lmhosts.c105
-rw-r--r--source3/nmbd/nmbd_logonnames.c172
-rw-r--r--source3/nmbd/nmbd_mynames.c323
-rw-r--r--source3/nmbd/nmbd_namelistdb.c689
-rw-r--r--source3/nmbd/nmbd_namequery.c276
-rw-r--r--source3/nmbd/nmbd_nameregister.c608
-rw-r--r--source3/nmbd/nmbd_namerelease.c222
-rw-r--r--source3/nmbd/nmbd_nodestatus.c92
-rw-r--r--source3/nmbd/nmbd_packets.c2262
-rw-r--r--source3/nmbd/nmbd_processlogon.c570
-rw-r--r--source3/nmbd/nmbd_proto.h385
-rw-r--r--source3/nmbd/nmbd_responserecordsdb.c244
-rw-r--r--source3/nmbd/nmbd_sendannounce.c602
-rw-r--r--source3/nmbd/nmbd_serverlistdb.c417
-rw-r--r--source3/nmbd/nmbd_subnetdb.c436
-rw-r--r--source3/nmbd/nmbd_synclists.c325
-rw-r--r--source3/nmbd/nmbd_winsproxy.c233
-rw-r--r--source3/nmbd/nmbd_winsserver.c2706
-rw-r--r--source3/nmbd/nmbd_workgroupdb.c327
-rw-r--r--source3/nmbd/wscript_build45
-rw-r--r--source3/param/loadparm.c4835
-rw-r--r--source3/param/loadparm.h204
-rw-r--r--source3/param/loadparm_ctx.c82
-rw-r--r--source3/param/pyparam.c92
-rw-r--r--source3/param/pyparam.h27
-rw-r--r--source3/param/pyparam_util.c76
-rw-r--r--source3/param/service.c335
-rw-r--r--source3/param/test_lp_load.c120
-rw-r--r--source3/param/util.c50
-rw-r--r--source3/param/wscript_build44
-rw-r--r--source3/passdb/ABI/pdb-0.1.0.sigs311
-rw-r--r--source3/passdb/ABI/pdb-0.1.1.sigs312
-rw-r--r--source3/passdb/ABI/pdb-0.1.2.sigs313
-rw-r--r--source3/passdb/ABI/pdb-0.sigs311
-rw-r--r--source3/passdb/ABI/samba-passdb-0.2.0.sigs312
-rw-r--r--source3/passdb/ABI/samba-passdb-0.24.1.sigs313
-rw-r--r--source3/passdb/ABI/samba-passdb-0.24.2.sigs313
-rw-r--r--source3/passdb/ABI/samba-passdb-0.25.0.sigs312
-rw-r--r--source3/passdb/ABI/samba-passdb-0.26.0.sigs310
-rw-r--r--source3/passdb/ABI/samba-passdb-0.27.0.sigs308
-rw-r--r--source3/passdb/ABI/samba-passdb-0.27.1.sigs309
-rw-r--r--source3/passdb/ABI/samba-passdb-0.27.2.sigs306
-rw-r--r--source3/passdb/ABI/samba-passdb-0.28.0.sigs306
-rw-r--r--source3/passdb/account_pol.c494
-rw-r--r--source3/passdb/login_cache.c202
-rw-r--r--source3/passdb/lookup_sid.c1731
-rw-r--r--source3/passdb/lookup_sid.h96
-rw-r--r--source3/passdb/machine_account_secrets.c2080
-rw-r--r--source3/passdb/machine_sid.c251
-rw-r--r--source3/passdb/machine_sid.h33
-rw-r--r--source3/passdb/passdb.c2755
-rw-r--r--source3/passdb/pdb_compat.c105
-rw-r--r--source3/passdb/pdb_get_set.c1163
-rw-r--r--source3/passdb/pdb_interface.c2713
-rw-r--r--source3/passdb/pdb_ldap.c6822
-rw-r--r--source3/passdb/pdb_ldap.h71
-rw-r--r--source3/passdb/pdb_ldap_schema.c191
-rw-r--r--source3/passdb/pdb_ldap_schema.h124
-rw-r--r--source3/passdb/pdb_ldap_util.c338
-rw-r--r--source3/passdb/pdb_ldap_util.h32
-rw-r--r--source3/passdb/pdb_nds.c908
-rw-r--r--source3/passdb/pdb_nds.h39
-rw-r--r--source3/passdb/pdb_samba_dsdb.c3895
-rw-r--r--source3/passdb/pdb_secrets.c172
-rw-r--r--source3/passdb/pdb_secrets.h30
-rw-r--r--source3/passdb/pdb_smbpasswd.c1750
-rw-r--r--source3/passdb/pdb_smbpasswd.h30
-rw-r--r--source3/passdb/pdb_tdb.c1369
-rw-r--r--source3/passdb/pdb_tdb.h32
-rw-r--r--source3/passdb/pdb_util.c245
-rw-r--r--source3/passdb/py_passdb.c4074
-rw-r--r--source3/passdb/secrets.c546
-rw-r--r--source3/passdb/secrets_lsa.c245
-rw-r--r--source3/passdb/wscript_build42
-rw-r--r--source3/printing/load.c109
-rw-r--r--source3/printing/load.h28
-rw-r--r--source3/printing/lpq_parse.c1164
-rw-r--r--source3/printing/notify.c693
-rw-r--r--source3/printing/notify.h87
-rw-r--r--source3/printing/nt_printing.c2419
-rw-r--r--source3/printing/nt_printing_ads.c908
-rw-r--r--source3/printing/nt_printing_migrate.c386
-rw-r--r--source3/printing/nt_printing_migrate.h47
-rw-r--r--source3/printing/nt_printing_migrate_internal.c272
-rw-r--r--source3/printing/nt_printing_migrate_internal.h26
-rw-r--r--source3/printing/nt_printing_os2.c167
-rw-r--r--source3/printing/nt_printing_os2.h22
-rw-r--r--source3/printing/nt_printing_tdb.c498
-rw-r--r--source3/printing/nt_printing_tdb.h28
-rw-r--r--source3/printing/pcap.c213
-rw-r--r--source3/printing/pcap.h69
-rw-r--r--source3/printing/print_aix.c140
-rw-r--r--source3/printing/print_cups.c1749
-rw-r--r--source3/printing/print_generic.c358
-rw-r--r--source3/printing/print_iprint.c1367
-rw-r--r--source3/printing/print_standard.c157
-rw-r--r--source3/printing/print_svid.c148
-rw-r--r--source3/printing/printer_list.c460
-rw-r--r--source3/printing/printer_list.h105
-rw-r--r--source3/printing/printing.c3266
-rw-r--r--source3/printing/printing_db.c228
-rw-r--r--source3/printing/printspoolss.c406
-rw-r--r--source3/printing/queue_process.c451
-rw-r--r--source3/printing/queue_process.h44
-rw-r--r--source3/printing/rap_jobid.c164
-rw-r--r--source3/printing/rap_jobid.h29
-rw-r--r--source3/printing/samba-bgqd.c359
-rw-r--r--source3/printing/tests/README.vlp19
-rw-r--r--source3/printing/tests/vlp.c445
-rw-r--r--source3/profile/profile.c332
-rw-r--r--source3/profile/profile_dummy.c31
-rw-r--r--source3/profile/profile_read.c202
-rw-r--r--source3/registry/reg_api.c1063
-rw-r--r--source3/registry/reg_api.h70
-rw-r--r--source3/registry/reg_api_util.c84
-rw-r--r--source3/registry/reg_api_util.h35
-rw-r--r--source3/registry/reg_backend_current_version.c78
-rw-r--r--source3/registry/reg_backend_db.c2270
-rw-r--r--source3/registry/reg_backend_db.h36
-rw-r--r--source3/registry/reg_backend_hkpt_params.c73
-rw-r--r--source3/registry/reg_backend_netlogon_params.c60
-rw-r--r--source3/registry/reg_backend_perflib.c110
-rw-r--r--source3/registry/reg_backend_printing.c281
-rw-r--r--source3/registry/reg_backend_prod_options.c68
-rw-r--r--source3/registry/reg_backend_shares.c165
-rw-r--r--source3/registry/reg_backend_smbconf.c110
-rw-r--r--source3/registry/reg_backend_tcpip_params.c54
-rw-r--r--source3/registry/reg_cachehook.c147
-rw-r--r--source3/registry/reg_cachehook.h29
-rw-r--r--source3/registry/reg_db.h37
-rw-r--r--source3/registry/reg_dispatcher.c264
-rw-r--r--source3/registry/reg_dispatcher.h44
-rw-r--r--source3/registry/reg_format.c830
-rw-r--r--source3/registry/reg_format.h219
-rw-r--r--source3/registry/reg_import.c314
-rw-r--r--source3/registry/reg_import.h199
-rw-r--r--source3/registry/reg_init_basic.c67
-rw-r--r--source3/registry/reg_init_basic.h26
-rw-r--r--source3/registry/reg_init_full.c102
-rw-r--r--source3/registry/reg_init_full.h27
-rw-r--r--source3/registry/reg_init_smbconf.c71
-rw-r--r--source3/registry/reg_init_smbconf.h27
-rw-r--r--source3/registry/reg_objects.c615
-rw-r--r--source3/registry/reg_objects.h74
-rw-r--r--source3/registry/reg_parse.c1090
-rw-r--r--source3/registry/reg_parse.h190
-rw-r--r--source3/registry/reg_parse_dox.cfg1562
-rw-r--r--source3/registry/reg_parse_internal.c392
-rw-r--r--source3/registry/reg_parse_internal.h123
-rw-r--r--source3/registry/reg_parse_prs.c453
-rw-r--r--source3/registry/reg_parse_prs.h80
-rw-r--r--source3/registry/reg_perfcount.c1463
-rw-r--r--source3/registry/reg_perfcount.h34
-rw-r--r--source3/registry/reg_util_internal.c149
-rw-r--r--source3/registry/reg_util_internal.h28
-rw-r--r--source3/registry/reg_util_token.c61
-rw-r--r--source3/registry/reg_util_token.h26
-rw-r--r--source3/registry/regfio.c1993
-rw-r--r--source3/registry/regfio.h233
-rw-r--r--source3/registry/tests/test_regfio.c186
-rw-r--r--source3/rpc_client/cli_lsarpc.c850
-rw-r--r--source3/rpc_client/cli_lsarpc.h278
-rw-r--r--source3/rpc_client/cli_mdssvc.c1214
-rw-r--r--source3/rpc_client/cli_mdssvc.h97
-rw-r--r--source3/rpc_client/cli_mdssvc_private.h74
-rw-r--r--source3/rpc_client/cli_mdssvc_util.c551
-rw-r--r--source3/rpc_client/cli_mdssvc_util.h45
-rw-r--r--source3/rpc_client/cli_netlogon.c860
-rw-r--r--source3/rpc_client/cli_netlogon.h111
-rw-r--r--source3/rpc_client/cli_pipe.c3677
-rw-r--r--source3/rpc_client/cli_pipe.h143
-rw-r--r--source3/rpc_client/cli_pipe_schannel.c120
-rw-r--r--source3/rpc_client/cli_samr.c654
-rw-r--r--source3/rpc_client/cli_samr.h229
-rw-r--r--source3/rpc_client/cli_spoolss.c1006
-rw-r--r--source3/rpc_client/cli_spoolss.h153
-rw-r--r--source3/rpc_client/cli_winreg.c975
-rw-r--r--source3/rpc_client/cli_winreg.h449
-rw-r--r--source3/rpc_client/cli_winreg_int.c323
-rw-r--r--source3/rpc_client/cli_winreg_int.h103
-rw-r--r--source3/rpc_client/cli_winreg_spoolss.c4729
-rw-r--r--source3/rpc_client/cli_winreg_spoolss.h720
-rw-r--r--source3/rpc_client/init_lsa.c60
-rw-r--r--source3/rpc_client/init_lsa.h35
-rw-r--r--source3/rpc_client/init_samr.c126
-rw-r--r--source3/rpc_client/init_samr.h54
-rw-r--r--source3/rpc_client/init_spoolss.c479
-rw-r--r--source3/rpc_client/init_spoolss.h55
-rw-r--r--source3/rpc_client/local_np.c852
-rw-r--r--source3/rpc_client/local_np.h56
-rw-r--r--source3/rpc_client/py_mdscli.c572
-rw-r--r--source3/rpc_client/rpc_client.h52
-rw-r--r--source3/rpc_client/rpc_transport.h104
-rw-r--r--source3/rpc_client/rpc_transport_np.c179
-rw-r--r--source3/rpc_client/rpc_transport_sock.c53
-rw-r--r--source3/rpc_client/rpc_transport_tstream.c564
-rw-r--r--source3/rpc_client/util_netlogon.c449
-rw-r--r--source3/rpc_client/util_netlogon.h54
-rw-r--r--source3/rpc_client/wsp_cli.c2221
-rw-r--r--source3/rpc_client/wsp_cli.h114
-rw-r--r--source3/rpc_server/dfs/srv_dfs_nt.c606
-rw-r--r--source3/rpc_server/dssetup/srv_dssetup_nt.c232
-rw-r--r--source3/rpc_server/echo/srv_echo_nt.c126
-rw-r--r--source3/rpc_server/epmapper/srv_epmapper.c1063
-rw-r--r--source3/rpc_server/eventlog/srv_eventlog_nt.c1047
-rw-r--r--source3/rpc_server/eventlog/srv_eventlog_reg.c267
-rw-r--r--source3/rpc_server/eventlog/srv_eventlog_reg.h29
-rw-r--r--source3/rpc_server/fss/srv_fss_agent.c1776
-rw-r--r--source3/rpc_server/fss/srv_fss_private.h92
-rw-r--r--source3/rpc_server/fss/srv_fss_state.c698
-rw-r--r--source3/rpc_server/initshutdown/srv_initshutdown_nt.c84
-rw-r--r--source3/rpc_server/lsa/srv_lsa_nt.c5235
-rw-r--r--source3/rpc_server/mdssvc/README14
-rw-r--r--source3/rpc_server/mdssvc/dalloc.c404
-rw-r--r--source3/rpc_server/mdssvc/dalloc.h165
-rw-r--r--source3/rpc_server/mdssvc/elasticsearch_mappings.json142
-rw-r--r--source3/rpc_server/mdssvc/es_lexer.l92
-rw-r--r--source3/rpc_server/mdssvc/es_mapping.c241
-rw-r--r--source3/rpc_server/mdssvc/es_mapping.h49
-rw-r--r--source3/rpc_server/mdssvc/es_parser.y686
-rw-r--r--source3/rpc_server/mdssvc/es_parser_test.c97
-rw-r--r--source3/rpc_server/mdssvc/marshalling.c1422
-rw-r--r--source3/rpc_server/mdssvc/marshalling.h63
-rw-r--r--source3/rpc_server/mdssvc/mdssvc.c1893
-rw-r--r--source3/rpc_server/mdssvc/mdssvc.h168
-rw-r--r--source3/rpc_server/mdssvc/mdssvc_es.c865
-rw-r--r--source3/rpc_server/mdssvc/mdssvc_es.h108
-rw-r--r--source3/rpc_server/mdssvc/mdssvc_noindex.c57
-rw-r--r--source3/rpc_server/mdssvc/mdssvc_noindex.h26
-rw-r--r--source3/rpc_server/mdssvc/mdssvc_tracker.c499
-rw-r--r--source3/rpc_server/mdssvc/mdssvc_tracker.h62
-rw-r--r--source3/rpc_server/mdssvc/sparql_lexer.l67
-rw-r--r--source3/rpc_server/mdssvc/sparql_mapping.c378
-rw-r--r--source3/rpc_server/mdssvc/sparql_mapping.h58
-rw-r--r--source3/rpc_server/mdssvc/sparql_parser.y483
-rw-r--r--source3/rpc_server/mdssvc/sparql_parser_test.c47
-rw-r--r--source3/rpc_server/mdssvc/srv_mdssvc_nt.c319
-rw-r--r--source3/rpc_server/mdssvc/srv_mdssvc_nt.h27
-rw-r--r--source3/rpc_server/mdssvc/test_mdsparser_es.c304
-rw-r--r--source3/rpc_server/netlogon/srv_netlog_nt.c2937
-rw-r--r--source3/rpc_server/ntsvcs/srv_ntsvcs_nt.c810
-rw-r--r--source3/rpc_server/rpc_config.c77
-rw-r--r--source3/rpc_server/rpc_config.h30
-rw-r--r--source3/rpc_server/rpc_handles.c233
-rw-r--r--source3/rpc_server/rpc_host.c2984
-rw-r--r--source3/rpc_server/rpc_ncacn_np.c217
-rw-r--r--source3/rpc_server/rpc_ncacn_np.h58
-rw-r--r--source3/rpc_server/rpc_pipes.h73
-rw-r--r--source3/rpc_server/rpc_server.c309
-rw-r--r--source3/rpc_server/rpc_server.h72
-rw-r--r--source3/rpc_server/rpc_sock_helper.c399
-rw-r--r--source3/rpc_server/rpc_sock_helper.h36
-rw-r--r--source3/rpc_server/rpc_worker.c1287
-rw-r--r--source3/rpc_server/rpc_worker.h40
-rw-r--r--source3/rpc_server/rpcd_classic.c149
-rw-r--r--source3/rpc_server/rpcd_epmapper.c109
-rw-r--r--source3/rpc_server/rpcd_fsrvp.c82
-rw-r--r--source3/rpc_server/rpcd_lsad.c141
-rw-r--r--source3/rpc_server/rpcd_mdssvc.c71
-rw-r--r--source3/rpc_server/rpcd_rpcecho.c89
-rw-r--r--source3/rpc_server/rpcd_spoolss.c91
-rw-r--r--source3/rpc_server/rpcd_winreg.c71
-rw-r--r--source3/rpc_server/rpcd_witness.c120
-rw-r--r--source3/rpc_server/samr/srv_samr_chgpasswd.c1421
-rw-r--r--source3/rpc_server/samr/srv_samr_nt.c7910
-rw-r--r--source3/rpc_server/samr/srv_samr_util.c756
-rw-r--r--source3/rpc_server/samr/srv_samr_util.h89
-rw-r--r--source3/rpc_server/spoolss/iremotewinspool_util.c156
-rw-r--r--source3/rpc_server/spoolss/iremotewinspool_util.h21
-rw-r--r--source3/rpc_server/spoolss/srv_iremotewinspool.c2382
-rw-r--r--source3/rpc_server/spoolss/srv_iremotewinspool_nt.c924
-rw-r--r--source3/rpc_server/spoolss/srv_spoolss_handle.h77
-rw-r--r--source3/rpc_server/spoolss/srv_spoolss_nt.c11622
-rw-r--r--source3/rpc_server/spoolss/srv_spoolss_nt.h40
-rw-r--r--source3/rpc_server/spoolss/srv_spoolss_util.c917
-rw-r--r--source3/rpc_server/spoolss/srv_spoolss_util.h190
-rw-r--r--source3/rpc_server/srv_access_check.c168
-rw-r--r--source3/rpc_server/srv_access_check.h44
-rw-r--r--source3/rpc_server/srv_pipe_hnd.c368
-rw-r--r--source3/rpc_server/srv_pipe_hnd.h50
-rw-r--r--source3/rpc_server/srvsvc/srv_srvsvc_nt.c3202
-rw-r--r--source3/rpc_server/svcctl/srv_svcctl_nt.c1497
-rw-r--r--source3/rpc_server/svcctl/srv_svcctl_nt.h33
-rw-r--r--source3/rpc_server/svcctl/srv_svcctl_reg.c678
-rw-r--r--source3/rpc_server/svcctl/srv_svcctl_reg.h29
-rw-r--r--source3/rpc_server/winreg/srv_winreg_nt.c1126
-rw-r--r--source3/rpc_server/witness/srv_witness_nt.c2465
-rw-r--r--source3/rpc_server/wkssvc/srv_wkssvc_nt.c964
-rw-r--r--source3/rpc_server/wscript_build308
-rw-r--r--source3/rpcclient/cmd_clusapi.c753
-rw-r--r--source3/rpcclient/cmd_dfs.c396
-rw-r--r--source3/rpcclient/cmd_drsuapi.c723
-rw-r--r--source3/rpcclient/cmd_dssetup.c84
-rw-r--r--source3/rpcclient/cmd_echo.c232
-rw-r--r--source3/rpcclient/cmd_epmapper.c270
-rw-r--r--source3/rpcclient/cmd_eventlog.c650
-rw-r--r--source3/rpcclient/cmd_fss.c759
-rw-r--r--source3/rpcclient/cmd_iremotewinspool.c195
-rw-r--r--source3/rpcclient/cmd_lsarpc.c3000
-rw-r--r--source3/rpcclient/cmd_netlogon.c1186
-rw-r--r--source3/rpcclient/cmd_ntsvcs.c361
-rw-r--r--source3/rpcclient/cmd_samr.c3941
-rw-r--r--source3/rpcclient/cmd_shutdown.c137
-rw-r--r--source3/rpcclient/cmd_spoolss.c4543
-rw-r--r--source3/rpcclient/cmd_spotlight.c420
-rw-r--r--source3/rpcclient/cmd_srvsvc.c1266
-rw-r--r--source3/rpcclient/cmd_unixinfo.c141
-rw-r--r--source3/rpcclient/cmd_winreg.c491
-rw-r--r--source3/rpcclient/cmd_witness.c623
-rw-r--r--source3/rpcclient/cmd_wkssvc.c274
-rw-r--r--source3/rpcclient/rpcclient.c1389
-rw-r--r--source3/rpcclient/rpcclient.h53
-rw-r--r--source3/rpcclient/wscript_build61
-rwxr-xr-xsource3/script/count_80_col.pl16
-rwxr-xr-xsource3/script/creategroup25
-rwxr-xr-xsource3/script/fix_bool.pl19
-rwxr-xr-xsource3/script/format_indent.sh12
-rw-r--r--source3/script/makeunicodecasemap.awk59
-rwxr-xr-xsource3/script/mknissmbpasswd.sh28
-rwxr-xr-xsource3/script/mknissmbpwdtbl.sh41
-rwxr-xr-xsource3/script/mksmbpasswd.sh6
-rw-r--r--source3/script/mksyms.awk76
-rwxr-xr-xsource3/script/mksyms.sh46
-rwxr-xr-xsource3/script/samba-log-parser382
-rwxr-xr-xsource3/script/scancvslog.pl112
-rwxr-xr-xsource3/script/smbaddshare32
-rwxr-xr-xsource3/script/smbchangeshare47
-rwxr-xr-xsource3/script/smbdeleteshare21
-rw-r--r--source3/script/smbtar181
-rwxr-xr-xsource3/script/strip_trail_ws.pl18
-rwxr-xr-xsource3/script/tests/dlopen.sh90
-rwxr-xr-xsource3/script/tests/fake_snap.pl85
-rwxr-xr-xsource3/script/tests/full_audit_segfault/run.sh25
-rw-r--r--source3/script/tests/full_audit_segfault/vfstest.cmd3
-rwxr-xr-xsource3/script/tests/getset_quota.py154
-rwxr-xr-xsource3/script/tests/printing/modprinter.pl140
-rwxr-xr-xsource3/script/tests/printing/printing_var_exp_lpr_cmd.sh9
-rw-r--r--source3/script/tests/smbspool_argv_wrapper.c72
-rwxr-xr-xsource3/script/tests/stream-depot/run.sh36
-rw-r--r--source3/script/tests/stream-depot/vfstest.cmd5
-rwxr-xr-xsource3/script/tests/test_acl_xattr.sh156
-rwxr-xr-xsource3/script/tests/test_aio_outstanding.sh99
-rwxr-xr-xsource3/script/tests/test_async_req.sh14
-rwxr-xr-xsource3/script/tests/test_bad_auditnames.sh29
-rwxr-xr-xsource3/script/tests/test_bug15435_widelink_dfs.sh28
-rwxr-xr-xsource3/script/tests/test_chdir_cache.sh122
-rwxr-xr-xsource3/script/tests/test_close_denied_share.sh78
-rwxr-xr-xsource3/script/tests/test_deadtime.sh67
-rwxr-xr-xsource3/script/tests/test_delete_stream.sh123
-rwxr-xr-xsource3/script/tests/test_delete_veto_files_only_rmdir.sh182
-rwxr-xr-xsource3/script/tests/test_dfree_command.sh69
-rwxr-xr-xsource3/script/tests/test_dfree_quota.sh296
-rwxr-xr-xsource3/script/tests/test_dropbox.sh88
-rwxr-xr-xsource3/script/tests/test_durable_handle_reconnect.sh36
-rwxr-xr-xsource3/script/tests/test_failure.sh34
-rwxr-xr-xsource3/script/tests/test_fakedircreatetimes.sh65
-rwxr-xr-xsource3/script/tests/test_fifo.sh83
-rwxr-xr-xsource3/script/tests/test_force_close_share.sh115
-rwxr-xr-xsource3/script/tests/test_force_create_mode.sh72
-rwxr-xr-xsource3/script/tests/test_force_group_change.sh73
-rwxr-xr-xsource3/script/tests/test_force_user_unlink.sh41
-rwxr-xr-xsource3/script/tests/test_forceuser_validusers.sh60
-rwxr-xr-xsource3/script/tests/test_fruit_resource_stream.sh41
-rwxr-xr-xsource3/script/tests/test_give_owner.sh147
-rwxr-xr-xsource3/script/tests/test_groupmap.sh217
-rwxr-xr-xsource3/script/tests/test_guest_auth.sh106
-rwxr-xr-xsource3/script/tests/test_homes.sh136
-rwxr-xr-xsource3/script/tests/test_inherit_owner.sh170
-rwxr-xr-xsource3/script/tests/test_large_acl.sh61
-rwxr-xr-xsource3/script/tests/test_libwbclient_threads.sh17
-rwxr-xr-xsource3/script/tests/test_list_nt4_trust.sh25
-rwxr-xr-xsource3/script/tests/test_local_s3.sh39
-rwxr-xr-xsource3/script/tests/test_net_cache_samlogon.sh43
-rwxr-xr-xsource3/script/tests/test_net_conf.sh1042
-rwxr-xr-xsource3/script/tests/test_net_cred_change.sh17
-rwxr-xr-xsource3/script/tests/test_net_cred_change_at.sh33
-rwxr-xr-xsource3/script/tests/test_net_dom_join_fail_dc.sh22
-rwxr-xr-xsource3/script/tests/test_net_lookup.sh54
-rwxr-xr-xsource3/script/tests/test_net_machine_account.sh34
-rwxr-xr-xsource3/script/tests/test_net_misc.sh80
-rwxr-xr-xsource3/script/tests/test_net_registry.sh411
-rwxr-xr-xsource3/script/tests/test_net_registry_check.sh145
-rwxr-xr-xsource3/script/tests/test_net_registry_import.sh192
-rwxr-xr-xsource3/script/tests/test_net_registry_roundtrip.sh158
-rwxr-xr-xsource3/script/tests/test_net_rpc_join.sh25
-rwxr-xr-xsource3/script/tests/test_net_rpc_join_creds.sh30
-rwxr-xr-xsource3/script/tests/test_net_rpc_oldjoin.sh49
-rwxr-xr-xsource3/script/tests/test_net_rpc_share_allowedusers.sh49
-rwxr-xr-xsource3/script/tests/test_net_tdb.sh104
-rwxr-xr-xsource3/script/tests/test_net_usershare.sh83
-rwxr-xr-xsource3/script/tests/test_netfileenum.sh84
-rwxr-xr-xsource3/script/tests/test_nt4_trust.sh31
-rwxr-xr-xsource3/script/tests/test_offline.sh33
-rwxr-xr-xsource3/script/tests/test_old_dirlisting.sh28
-rwxr-xr-xsource3/script/tests/test_open_eintr.sh77
-rwxr-xr-xsource3/script/tests/test_preserve_case.sh86
-rwxr-xr-xsource3/script/tests/test_printing_var_exp.sh93
-rwxr-xr-xsource3/script/tests/test_pthreadpool.sh20
-rwxr-xr-xsource3/script/tests/test_recycle.sh102
-rwxr-xr-xsource3/script/tests/test_registry_share.sh39
-rwxr-xr-xsource3/script/tests/test_registry_upgrade.sh192
-rwxr-xr-xsource3/script/tests/test_resolvconf.sh20
-rwxr-xr-xsource3/script/tests/test_rofs.sh34
-rwxr-xr-xsource3/script/tests/test_rpcclient.sh19
-rwxr-xr-xsource3/script/tests/test_rpcclient_dfs.sh45
-rwxr-xr-xsource3/script/tests/test_rpcclient_lookup.sh42
-rwxr-xr-xsource3/script/tests/test_rpcclient_netsessenum.sh55
-rwxr-xr-xsource3/script/tests/test_rpcclient_pw_nt_hash.sh27
-rwxr-xr-xsource3/script/tests/test_rpcclient_samlogon.sh32
-rwxr-xr-xsource3/script/tests/test_rpcclientsrvsvc.sh90
-rwxr-xr-xsource3/script/tests/test_sacl_set_get.sh45
-rwxr-xr-xsource3/script/tests/test_server_addresses.sh32
-rwxr-xr-xsource3/script/tests/test_shadow_copy.sh458
-rwxr-xr-xsource3/script/tests/test_shadow_copy_torture.sh227
-rwxr-xr-xsource3/script/tests/test_shareenum.sh31
-rwxr-xr-xsource3/script/tests/test_sharesec.sh140
-rwxr-xr-xsource3/script/tests/test_smb1_shadow_copy_torture.sh77
-rwxr-xr-xsource3/script/tests/test_smb1_system_security.sh44
-rwxr-xr-xsource3/script/tests/test_smb2_not_casesensitive.sh81
-rwxr-xr-xsource3/script/tests/test_smbXsrv_client_cross_node.sh92
-rwxr-xr-xsource3/script/tests/test_smbXsrv_client_ctdb_registered_ips.sh159
-rwxr-xr-xsource3/script/tests/test_smbXsrv_client_dead_rec.sh76
-rwxr-xr-xsource3/script/tests/test_smbclient_auth.sh47
-rwxr-xr-xsource3/script/tests/test_smbclient_basic.sh47
-rwxr-xr-xsource3/script/tests/test_smbclient_encryption.sh72
-rwxr-xr-xsource3/script/tests/test_smbclient_encryption_off.sh65
-rwxr-xr-xsource3/script/tests/test_smbclient_iconv.sh55
-rwxr-xr-xsource3/script/tests/test_smbclient_kerberos.sh80
-rwxr-xr-xsource3/script/tests/test_smbclient_krb5.sh21
-rwxr-xr-xsource3/script/tests/test_smbclient_large_file.sh62
-rwxr-xr-xsource3/script/tests/test_smbclient_list_servers.sh45
-rwxr-xr-xsource3/script/tests/test_smbclient_log_basename.sh36
-rwxr-xr-xsource3/script/tests/test_smbclient_machine_auth.sh44
-rwxr-xr-xsource3/script/tests/test_smbclient_mget.sh47
-rwxr-xr-xsource3/script/tests/test_smbclient_netbios_aliases.sh42
-rwxr-xr-xsource3/script/tests/test_smbclient_ntlm.sh80
-rwxr-xr-xsource3/script/tests/test_smbclient_s3.sh2364
-rwxr-xr-xsource3/script/tests/test_smbclient_tarmode.pl1723
-rwxr-xr-xsource3/script/tests/test_smbclient_tarmode.sh197
-rwxr-xr-xsource3/script/tests/test_smbcquota.py244
-rwxr-xr-xsource3/script/tests/test_smbcquota.sh45
-rwxr-xr-xsource3/script/tests/test_smbd_error.sh66
-rwxr-xr-xsource3/script/tests/test_smbd_no_krb5.sh46
-rwxr-xr-xsource3/script/tests/test_smbget.sh619
-rwxr-xr-xsource3/script/tests/test_smbpasswd.sh133
-rwxr-xr-xsource3/script/tests/test_smbspool.sh294
-rwxr-xr-xsource3/script/tests/test_smbspool_krb.sh90
-rwxr-xr-xsource3/script/tests/test_smbstatus.sh479
-rwxr-xr-xsource3/script/tests/test_smbtorture_nocrash_s3.sh38
-rwxr-xr-xsource3/script/tests/test_smbtorture_s3.sh26
-rwxr-xr-xsource3/script/tests/test_stream_dir_rename.sh72
-rwxr-xr-xsource3/script/tests/test_substitutions.sh79
-rwxr-xr-xsource3/script/tests/test_success.sh21
-rwxr-xr-xsource3/script/tests/test_symlink_dosmode.sh74
-rwxr-xr-xsource3/script/tests/test_symlink_rename_smb1_posix.sh185
-rwxr-xr-xsource3/script/tests/test_symlink_traversal_smb1.sh262
-rwxr-xr-xsource3/script/tests/test_symlink_traversal_smb1_posix.sh269
-rwxr-xr-xsource3/script/tests/test_symlink_traversal_smb2.sh382
-rwxr-xr-xsource3/script/tests/test_testparm_s3.sh150
-rwxr-xr-xsource3/script/tests/test_tevent_glib_glue.sh20
-rwxr-xr-xsource3/script/tests/test_timestamps.sh72
-rwxr-xr-xsource3/script/tests/test_user_in_sharelist.sh22
-rwxr-xr-xsource3/script/tests/test_usernamemap.sh28
-rwxr-xr-xsource3/script/tests/test_valid_users.sh70
-rwxr-xr-xsource3/script/tests/test_veto_files.sh279
-rwxr-xr-xsource3/script/tests/test_veto_rmdir.sh217
-rwxr-xr-xsource3/script/tests/test_virus_scanner.sh135
-rwxr-xr-xsource3/script/tests/test_volume_serial_number.sh37
-rwxr-xr-xsource3/script/tests/test_wbinfo_lookuprids_cache.sh31
-rwxr-xr-xsource3/script/tests/test_wbinfo_sids2xids.sh12
-rwxr-xr-xsource3/script/tests/test_wbinfo_sids2xids_int.py147
-rwxr-xr-xsource3/script/tests/test_wbinfo_u_large_ad.sh28
-rwxr-xr-xsource3/script/tests/test_winbind_call_depth_trace.sh113
-rwxr-xr-xsource3/script/tests/test_winbind_ignore_domains.sh106
-rwxr-xr-xsource3/script/tests/test_worm.sh121
-rwxr-xr-xsource3/script/tests/test_zero_data.sh54
-rwxr-xr-xsource3/script/tests/test_zero_readsize.sh101
-rw-r--r--source3/script/tests/timelimit.c102
-rwxr-xr-xsource3/script/tests/vfstest-acl/run.sh52
-rw-r--r--source3/script/tests/vfstest-acl/vfstest.cmd15
-rwxr-xr-xsource3/script/tests/vfstest-catia/run.sh102
-rw-r--r--source3/script/tests/vfstest-catia/vfstest.cmd2
-rw-r--r--source3/script/tests/vfstest-catia/vfstest1.cmd2
-rwxr-xr-xsource3/script/tests/wb_pad.sh83
-rwxr-xr-xsource3/script/tests/xattr-tdb-1/run.sh52
-rw-r--r--source3/script/tests/xattr-tdb-1/vfstest.cmd6
-rwxr-xr-xsource3/script/updatesmbpasswd.sh14
-rw-r--r--source3/script/wscript_build13
-rw-r--r--source3/selftest/ktest-krb5_ccache-2bin0 -> 11966 bytes
-rw-r--r--source3/selftest/ktest-krb5_ccache-2.txt1574
-rw-r--r--source3/selftest/ktest-krb5_ccache-3bin0 -> 6031 bytes
-rw-r--r--source3/selftest/ktest-krb5_ccache-3.txt832
-rw-r--r--source3/selftest/ktest-secrets.tdbbin0 -> 45056 bytes
-rwxr-xr-xsource3/selftest/tests.py1918
-rw-r--r--source3/services/services.h54
-rw-r--r--source3/services/svc_netlogon.c75
-rw-r--r--source3/services/svc_rcinit.c150
-rw-r--r--source3/services/svc_spoolss.c87
-rw-r--r--source3/services/svc_winreg.c64
-rw-r--r--source3/services/svc_winreg_glue.c370
-rw-r--r--source3/services/svc_winreg_glue.h60
-rw-r--r--source3/services/svc_wins.c72
-rw-r--r--source3/smbadduser.in79
-rw-r--r--source3/smbd/avahi_register.c300
-rw-r--r--source3/smbd/blocking.c763
-rw-r--r--source3/smbd/close.c1742
-rw-r--r--source3/smbd/conn.c267
-rw-r--r--source3/smbd/conn_idle.c277
-rw-r--r--source3/smbd/conn_msg.c148
-rw-r--r--source3/smbd/connection.c97
-rw-r--r--source3/smbd/dfree.c296
-rw-r--r--source3/smbd/dir.c1504
-rw-r--r--source3/smbd/dir.h89
-rw-r--r--source3/smbd/dmapi.c357
-rw-r--r--source3/smbd/dnsregister.c200
-rw-r--r--source3/smbd/dosmode.c1305
-rw-r--r--source3/smbd/durable.c938
-rw-r--r--source3/smbd/error.c175
-rw-r--r--source3/smbd/fake_file.c213
-rw-r--r--source3/smbd/fd_handle.c147
-rw-r--r--source3/smbd/fd_handle.h49
-rw-r--r--source3/smbd/file_access.c247
-rw-r--r--source3/smbd/fileio.c318
-rw-r--r--source3/smbd/filename.c1271
-rw-r--r--source3/smbd/files.c2651
-rw-r--r--source3/smbd/globals.c123
-rw-r--r--source3/smbd/globals.h912
-rw-r--r--source3/smbd/mangle.c153
-rw-r--r--source3/smbd/mangle_hash.c1132
-rw-r--r--source3/smbd/mangle_hash2.c875
-rw-r--r--source3/smbd/msdfs.c1774
-rw-r--r--source3/smbd/notify.c964
-rw-r--r--source3/smbd/notify_fam.c307
-rw-r--r--source3/smbd/notify_inotify.c457
-rw-r--r--source3/smbd/notify_msg.c247
-rw-r--r--source3/smbd/notifyd/fcn_wait.c270
-rw-r--r--source3/smbd/notifyd/fcn_wait.h38
-rw-r--r--source3/smbd/notifyd/notifyd.c1428
-rw-r--r--source3/smbd/notifyd/notifyd.h145
-rw-r--r--source3/smbd/notifyd/notifyd_db.c165
-rw-r--r--source3/smbd/notifyd/notifyd_db.h27
-rw-r--r--source3/smbd/notifyd/notifyd_entry.c42
-rw-r--r--source3/smbd/notifyd/notifyd_private.h49
-rw-r--r--source3/smbd/notifyd/notifydd.c98
-rw-r--r--source3/smbd/notifyd/test_notifyd.c347
-rw-r--r--source3/smbd/notifyd/tests.c118
-rw-r--r--source3/smbd/notifyd/wscript_build44
-rw-r--r--source3/smbd/ntquotas.c265
-rw-r--r--source3/smbd/open.c6676
-rw-r--r--source3/smbd/oplock_linux.c243
-rw-r--r--source3/smbd/password.c91
-rw-r--r--source3/smbd/posix_acls.c4862
-rw-r--r--source3/smbd/proto.h1224
-rw-r--r--source3/smbd/pysmbd.c1305
-rw-r--r--source3/smbd/quotas.c497
-rw-r--r--source3/smbd/scavenger.c731
-rw-r--r--source3/smbd/scavenger.h31
-rw-r--r--source3/smbd/seal.c313
-rw-r--r--source3/smbd/sec_ctx.c513
-rw-r--r--source3/smbd/server.c2136
-rw-r--r--source3/smbd/server_exit.c262
-rw-r--r--source3/smbd/server_reload.c176
-rw-r--r--source3/smbd/session.c218
-rw-r--r--source3/smbd/share_access.c291
-rw-r--r--source3/smbd/smb1_aio.c409
-rw-r--r--source3/smbd/smb1_aio.h29
-rw-r--r--source3/smbd/smb1_ipc.c949
-rw-r--r--source3/smbd/smb1_ipc.h33
-rw-r--r--source3/smbd/smb1_lanman.c5920
-rw-r--r--source3/smbd/smb1_lanman.h28
-rw-r--r--source3/smbd/smb1_message.c334
-rw-r--r--source3/smbd/smb1_message.h23
-rw-r--r--source3/smbd/smb1_negprot.c706
-rw-r--r--source3/smbd/smb1_negprot.h21
-rw-r--r--source3/smbd/smb1_nttrans.c2718
-rw-r--r--source3/smbd/smb1_nttrans.h25
-rw-r--r--source3/smbd/smb1_oplock.c72
-rw-r--r--source3/smbd/smb1_oplock.h26
-rw-r--r--source3/smbd/smb1_pipes.c447
-rw-r--r--source3/smbd/smb1_pipes.h26
-rw-r--r--source3/smbd/smb1_process.c2718
-rw-r--r--source3/smbd/smb1_process.h72
-rw-r--r--source3/smbd/smb1_reply.c7178
-rw-r--r--source3/smbd/smb1_reply.h80
-rw-r--r--source3/smbd/smb1_service.c241
-rw-r--r--source3/smbd/smb1_service.h24
-rw-r--r--source3/smbd/smb1_sesssetup.c1118
-rw-r--r--source3/smbd/smb1_sesssetup.h25
-rw-r--r--source3/smbd/smb1_signing.c290
-rw-r--r--source3/smbd/smb1_signing.h37
-rw-r--r--source3/smbd/smb1_trans2.c5703
-rw-r--r--source3/smbd/smb1_trans2.h27
-rw-r--r--source3/smbd/smb1_utils.c261
-rw-r--r--source3/smbd/smb1_utils.h46
-rw-r--r--source3/smbd/smb2_aio.c608
-rw-r--r--source3/smbd/smb2_break.c495
-rw-r--r--source3/smbd/smb2_close.c427
-rw-r--r--source3/smbd/smb2_create.c2113
-rw-r--r--source3/smbd/smb2_flush.c257
-rw-r--r--source3/smbd/smb2_getinfo.c694
-rw-r--r--source3/smbd/smb2_glue.c118
-rw-r--r--source3/smbd/smb2_ioctl.c505
-rw-r--r--source3/smbd/smb2_ioctl_dfs.c157
-rw-r--r--source3/smbd/smb2_ioctl_filesys.c830
-rw-r--r--source3/smbd/smb2_ioctl_named_pipe.c188
-rw-r--r--source3/smbd/smb2_ioctl_network_fs.c839
-rw-r--r--source3/smbd/smb2_ioctl_private.h60
-rw-r--r--source3/smbd/smb2_ioctl_smbtorture.c230
-rw-r--r--source3/smbd/smb2_ipc.c40
-rw-r--r--source3/smbd/smb2_keepalive.c50
-rw-r--r--source3/smbd/smb2_lock.c782
-rw-r--r--source3/smbd/smb2_negprot.c1229
-rw-r--r--source3/smbd/smb2_notify.c394
-rw-r--r--source3/smbd/smb2_nttrans.c911
-rw-r--r--source3/smbd/smb2_oplock.c1430
-rw-r--r--source3/smbd/smb2_pipes.c150
-rw-r--r--source3/smbd/smb2_posix.c73
-rw-r--r--source3/smbd/smb2_process.c2073
-rw-r--r--source3/smbd/smb2_query_directory.c1076
-rw-r--r--source3/smbd/smb2_read.c685
-rw-r--r--source3/smbd/smb2_reply.c2195
-rw-r--r--source3/smbd/smb2_server.c5235
-rw-r--r--source3/smbd/smb2_service.c951
-rw-r--r--source3/smbd/smb2_sesssetup.c1373
-rw-r--r--source3/smbd/smb2_setinfo.c628
-rw-r--r--source3/smbd/smb2_signing.c52
-rw-r--r--source3/smbd/smb2_tcon.c765
-rw-r--r--source3/smbd/smb2_trans2.c5243
-rw-r--r--source3/smbd/smb2_write.c457
-rw-r--r--source3/smbd/smbXsrv_client.c1458
-rw-r--r--source3/smbd/smbXsrv_open.c1526
-rw-r--r--source3/smbd/smbXsrv_open.h75
-rw-r--r--source3/smbd/smbXsrv_session.c2527
-rw-r--r--source3/smbd/smbXsrv_tcon.c1272
-rw-r--r--source3/smbd/smbXsrv_version.c265
-rw-r--r--source3/smbd/smbd.h102
-rw-r--r--source3/smbd/smbd_cleanupd.c182
-rw-r--r--source3/smbd/smbd_cleanupd.h33
-rw-r--r--source3/smbd/srvstr.c78
-rw-r--r--source3/smbd/statvfs.c182
-rw-r--r--source3/smbd/uid.c752
-rw-r--r--source3/smbd/utmp.c604
-rw-r--r--source3/smbd/vfs.c2661
-rw-r--r--source3/torture/bench_pthreadpool.c68
-rw-r--r--source3/torture/cmd_vfs.c2362
-rw-r--r--source3/torture/denytest.c1600
-rw-r--r--source3/torture/locktest2.c610
-rw-r--r--source3/torture/mangle_test.c223
-rw-r--r--source3/torture/msg_sink.c285
-rw-r--r--source3/torture/msg_source.c159
-rw-r--r--source3/torture/msgtest.c171
-rw-r--r--source3/torture/nbench.c504
-rw-r--r--source3/torture/nbio.c377
-rw-r--r--source3/torture/pdbtest.c736
-rw-r--r--source3/torture/proto.h192
-rw-r--r--source3/torture/scanner.c515
-rw-r--r--source3/torture/test_addrchange.c94
-rw-r--r--source3/torture/test_async_echo.c148
-rw-r--r--source3/torture/test_authinfo_structs.c218
-rw-r--r--source3/torture/test_buffersize.c55
-rw-r--r--source3/torture/test_case_insensitive.c80
-rw-r--r--source3/torture/test_chain3.c296
-rw-r--r--source3/torture/test_cleanup.c240
-rw-r--r--source3/torture/test_ctdbd_conn.c312
-rw-r--r--source3/torture/test_dbwrap_ctdb.c163
-rw-r--r--source3/torture/test_dbwrap_do_locked.c161
-rw-r--r--source3/torture/test_dbwrap_watch.c467
-rw-r--r--source3/torture/test_g_lock.c1403
-rw-r--r--source3/torture/test_hidenewfiles.c234
-rw-r--r--source3/torture/test_idmap_cache.c122
-rw-r--r--source3/torture/test_idmap_tdb_common.c1047
-rw-r--r--source3/torture/test_matching.c276
-rw-r--r--source3/torture/test_messaging_fd_passing.c397
-rw-r--r--source3/torture/test_messaging_read.c706
-rw-r--r--source3/torture/test_messaging_send_all.c279
-rw-r--r--source3/torture/test_namemap_cache.c270
-rw-r--r--source3/torture/test_notify.c731
-rw-r--r--source3/torture/test_notify_online.c293
-rw-r--r--source3/torture/test_nttrans_create.c108
-rw-r--r--source3/torture/test_nttrans_fsctl.c288
-rw-r--r--source3/torture/test_oplock_cancel.c168
-rw-r--r--source3/torture/test_posix.c1972
-rw-r--r--source3/torture/test_posix_append.c100
-rw-r--r--source3/torture/test_pthreadpool_tevent.c82
-rw-r--r--source3/torture/test_readdir_timestamp.c533
-rw-r--r--source3/torture/test_rpc_scale.c301
-rw-r--r--source3/torture/test_smb1_dfs.c4284
-rw-r--r--source3/torture/test_smb2.c5471
-rw-r--r--source3/torture/test_smbsock_any_connect.c47
-rw-r--r--source3/torture/test_tdb_validate.c68
-rw-r--r--source3/torture/torture.c16512
-rw-r--r--source3/torture/utable.c210
-rw-r--r--source3/torture/vfstest.c650
-rw-r--r--source3/torture/vfstest.h51
-rw-r--r--source3/torture/vfstest_chain.c342
-rw-r--r--source3/torture/wbc_async.c758
-rw-r--r--source3/torture/wbc_async.h171
-rw-r--r--source3/torture/wscript_build135
-rw-r--r--source3/utils/async-tracker.c315
-rw-r--r--source3/utils/clirap2.c2552
-rw-r--r--source3/utils/clirap2.h80
-rw-r--r--source3/utils/conn_tdb.c173
-rw-r--r--source3/utils/conn_tdb.h45
-rw-r--r--source3/utils/dbwrap_tool.c593
-rw-r--r--source3/utils/dbwrap_torture.c366
-rw-r--r--source3/utils/destroy_netlogon_creds_cli.c136
-rw-r--r--source3/utils/eventlogadm.c506
-rw-r--r--source3/utils/interact.c135
-rw-r--r--source3/utils/interact.h36
-rw-r--r--source3/utils/log2pcaphex.c408
-rw-r--r--source3/utils/mdsearch.c260
-rw-r--r--source3/utils/mvxattr.c227
-rw-r--r--source3/utils/net.c1450
-rw-r--r--source3/utils/net.h208
-rw-r--r--source3/utils/net_ads.c4184
-rw-r--r--source3/utils/net_ads_gpo.c428
-rw-r--r--source3/utils/net_ads_join_dns.c342
-rw-r--r--source3/utils/net_afs.c127
-rw-r--r--source3/utils/net_afs.h29
-rw-r--r--source3/utils/net_cache.c652
-rw-r--r--source3/utils/net_conf.c1305
-rw-r--r--source3/utils/net_conf_util.c69
-rw-r--r--source3/utils/net_conf_util.h33
-rw-r--r--source3/utils/net_dns.c224
-rw-r--r--source3/utils/net_dns.h44
-rw-r--r--source3/utils/net_dom.c385
-rw-r--r--source3/utils/net_eventlog.c275
-rw-r--r--source3/utils/net_file.c57
-rw-r--r--source3/utils/net_g_lock.c267
-rw-r--r--source3/utils/net_group.c68
-rw-r--r--source3/utils/net_groupmap.c1023
-rw-r--r--source3/utils/net_help.c69
-rw-r--r--source3/utils/net_help_common.c95
-rw-r--r--source3/utils/net_help_common.h49
-rw-r--r--source3/utils/net_idmap.c1413
-rw-r--r--source3/utils/net_idmap_check.c974
-rw-r--r--source3/utils/net_idmap_check.h48
-rw-r--r--source3/utils/net_join.c55
-rw-r--r--source3/utils/net_lookup.c542
-rw-r--r--source3/utils/net_notify.c199
-rw-r--r--source3/utils/net_offlinejoin.c600
-rw-r--r--source3/utils/net_printing.c592
-rw-r--r--source3/utils/net_proto.h488
-rw-r--r--source3/utils/net_rap.c1386
-rw-r--r--source3/utils/net_registry.c1732
-rw-r--r--source3/utils/net_registry_check.c1324
-rw-r--r--source3/utils/net_registry_check.h52
-rw-r--r--source3/utils/net_registry_util.c177
-rw-r--r--source3/utils/net_registry_util.h41
-rw-r--r--source3/utils/net_rpc.c8408
-rw-r--r--source3/utils/net_rpc_audit.c540
-rw-r--r--source3/utils/net_rpc_conf.c2483
-rw-r--r--source3/utils/net_rpc_printer.c2624
-rw-r--r--source3/utils/net_rpc_registry.c2126
-rw-r--r--source3/utils/net_rpc_rights.c798
-rw-r--r--source3/utils/net_rpc_samsync.c257
-rw-r--r--source3/utils/net_rpc_service.c1138
-rw-r--r--source3/utils/net_rpc_sh_acct.c489
-rw-r--r--source3/utils/net_rpc_shell.c306
-rw-r--r--source3/utils/net_rpc_trust.c735
-rw-r--r--source3/utils/net_sam.c2308
-rw-r--r--source3/utils/net_serverid.c703
-rw-r--r--source3/utils/net_share.c75
-rw-r--r--source3/utils/net_status.c246
-rw-r--r--source3/utils/net_tdb.c105
-rw-r--r--source3/utils/net_time.c262
-rw-r--r--source3/utils/net_user.c67
-rw-r--r--source3/utils/net_usershare.c1172
-rw-r--r--source3/utils/net_util.c614
-rw-r--r--source3/utils/net_vfs.c469
-rw-r--r--source3/utils/net_witness.c2361
-rw-r--r--source3/utils/netlookup.c218
-rw-r--r--source3/utils/nmblookup.c468
-rw-r--r--source3/utils/ntlm_auth.c2856
-rw-r--r--source3/utils/ntlm_auth.h26
-rw-r--r--source3/utils/ntlm_auth_diagnostics.c724
-rw-r--r--source3/utils/ntlm_auth_proto.h51
-rw-r--r--source3/utils/passwd_proto.h31
-rw-r--r--source3/utils/passwd_util.c80
-rw-r--r--source3/utils/pdbedit.c1414
-rw-r--r--source3/utils/profiles.c365
-rw-r--r--source3/utils/py_net.c369
-rw-r--r--source3/utils/py_net.h26
-rw-r--r--source3/utils/regedit.c835
-rw-r--r--source3/utils/regedit.h77
-rw-r--r--source3/utils/regedit_dialog.c2328
-rw-r--r--source3/utils/regedit_dialog.h240
-rw-r--r--source3/utils/regedit_hexedit.c563
-rw-r--r--source3/utils/regedit_hexedit.h49
-rw-r--r--source3/utils/regedit_list.c591
-rw-r--r--source3/utils/regedit_list.h82
-rw-r--r--source3/utils/regedit_samba3.c244
-rw-r--r--source3/utils/regedit_treeview.c705
-rw-r--r--source3/utils/regedit_treeview.h89
-rw-r--r--source3/utils/regedit_valuelist.c496
-rw-r--r--source3/utils/regedit_valuelist.h72
-rw-r--r--source3/utils/regedit_wrap.c143
-rw-r--r--source3/utils/sharesec.c613
-rw-r--r--source3/utils/smbcacls.c2614
-rw-r--r--source3/utils/smbcontrol.c1870
-rw-r--r--source3/utils/smbcquotas.c832
-rw-r--r--source3/utils/smbfilter.c356
-rw-r--r--source3/utils/smbget.c1073
-rw-r--r--source3/utils/smbpasswd.c678
-rw-r--r--source3/utils/smbtree.c295
-rw-r--r--source3/utils/status.c1241
-rw-r--r--source3/utils/status.h44
-rw-r--r--source3/utils/status_json.c1386
-rw-r--r--source3/utils/status_json.h77
-rw-r--r--source3/utils/status_json_dummy.c101
-rw-r--r--source3/utils/status_profile.c381
-rw-r--r--source3/utils/status_profile.h30
-rw-r--r--source3/utils/status_profile_dummy.c35
-rw-r--r--source3/utils/testparm.c1060
-rw-r--r--source3/utils/wscript_build364
-rw-r--r--source3/utils/wspsearch.c842
-rw-r--r--source3/web/swat.c58
-rw-r--r--source3/winbindd/idmap.c632
-rw-r--r--source3/winbindd/idmap_ad.c1243
-rw-r--r--source3/winbindd/idmap_ad_nss.c418
-rw-r--r--source3/winbindd/idmap_autorid.c945
-rw-r--r--source3/winbindd/idmap_autorid_tdb.c1269
-rw-r--r--source3/winbindd/idmap_hash/idmap_hash.c504
-rw-r--r--source3/winbindd/idmap_hash/idmap_hash.h60
-rw-r--r--source3/winbindd/idmap_hash/mapfile.c182
-rw-r--r--source3/winbindd/idmap_ldap.c1140
-rw-r--r--source3/winbindd/idmap_nss.c446
-rw-r--r--source3/winbindd/idmap_passdb.c87
-rw-r--r--source3/winbindd/idmap_proto.h69
-rw-r--r--source3/winbindd/idmap_rfc2307.c848
-rw-r--r--source3/winbindd/idmap_rid.c182
-rw-r--r--source3/winbindd/idmap_rw.c109
-rw-r--r--source3/winbindd/idmap_rw.h56
-rw-r--r--source3/winbindd/idmap_script.c650
-rw-r--r--source3/winbindd/idmap_tdb.c434
-rw-r--r--source3/winbindd/idmap_tdb2.c612
-rw-r--r--source3/winbindd/idmap_tdb_common.c664
-rw-r--r--source3/winbindd/idmap_tdb_common.h137
-rw-r--r--source3/winbindd/idmap_util.c137
-rw-r--r--source3/winbindd/nss_info.c377
-rw-r--r--source3/winbindd/nss_info_template.c80
-rw-r--r--source3/winbindd/wb_alias_members.c137
-rw-r--r--source3/winbindd/wb_dsgetdcname.c255
-rw-r--r--source3/winbindd/wb_getgrsid.c403
-rw-r--r--source3/winbindd/wb_getpwsid.c156
-rw-r--r--source3/winbindd/wb_gettoken.c290
-rw-r--r--source3/winbindd/wb_group_members.c489
-rw-r--r--source3/winbindd/wb_lookupname.c123
-rw-r--r--source3/winbindd/wb_lookupsid.c114
-rw-r--r--source3/winbindd/wb_lookupsids.c699
-rw-r--r--source3/winbindd/wb_lookupuseraliases.c108
-rw-r--r--source3/winbindd/wb_lookupusergroups.c120
-rw-r--r--source3/winbindd/wb_next_grent.c169
-rw-r--r--source3/winbindd/wb_next_pwent.c162
-rw-r--r--source3/winbindd/wb_query_group_list.c94
-rw-r--r--source3/winbindd/wb_query_user_list.c146
-rw-r--r--source3/winbindd/wb_queryuser.c480
-rw-r--r--source3/winbindd/wb_seqnum.c78
-rw-r--r--source3/winbindd/wb_seqnums.c153
-rw-r--r--source3/winbindd/wb_sids2xids.c786
-rw-r--r--source3/winbindd/wb_xids2sids.c422
-rw-r--r--source3/winbindd/winbindd.c1742
-rw-r--r--source3/winbindd/winbindd.h370
-rw-r--r--source3/winbindd/winbindd_ads.c1604
-rw-r--r--source3/winbindd/winbindd_ads.h34
-rw-r--r--source3/winbindd/winbindd_allocate_gid.c121
-rw-r--r--source3/winbindd/winbindd_allocate_uid.c122
-rw-r--r--source3/winbindd/winbindd_cache.c4930
-rw-r--r--source3/winbindd/winbindd_ccache_access.c397
-rw-r--r--source3/winbindd/winbindd_change_machine_acct.c99
-rw-r--r--source3/winbindd/winbindd_check_machine_acct.c96
-rw-r--r--source3/winbindd/winbindd_cm.c3434
-rw-r--r--source3/winbindd/winbindd_cred_cache.c1061
-rw-r--r--source3/winbindd/winbindd_creds.c147
-rw-r--r--source3/winbindd/winbindd_domain.c36
-rw-r--r--source3/winbindd/winbindd_domain_info.c141
-rw-r--r--source3/winbindd/winbindd_dsgetdcname.c200
-rw-r--r--source3/winbindd/winbindd_dual.c2093
-rw-r--r--source3/winbindd/winbindd_dual_ndr.c615
-rw-r--r--source3/winbindd/winbindd_dual_srv.c2127
-rw-r--r--source3/winbindd/winbindd_endgrent.c54
-rw-r--r--source3/winbindd/winbindd_endpwent.c55
-rw-r--r--source3/winbindd/winbindd_getdcname.c95
-rw-r--r--source3/winbindd/winbindd_getgrent.c213
-rw-r--r--source3/winbindd/winbindd_getgrgid.c156
-rw-r--r--source3/winbindd/winbindd_getgrnam.c213
-rw-r--r--source3/winbindd/winbindd_getgroups.c283
-rw-r--r--source3/winbindd/winbindd_getpwent.c162
-rw-r--r--source3/winbindd/winbindd_getpwnam.c163
-rw-r--r--source3/winbindd/winbindd_getpwsid.c109
-rw-r--r--source3/winbindd/winbindd_getpwuid.c137
-rw-r--r--source3/winbindd/winbindd_getsidaliases.c160
-rw-r--r--source3/winbindd/winbindd_getuserdomgroups.c123
-rw-r--r--source3/winbindd/winbindd_getusersids.c128
-rw-r--r--source3/winbindd/winbindd_gpupdate.c184
-rw-r--r--source3/winbindd/winbindd_group.c156
-rw-r--r--source3/winbindd/winbindd_idmap.c436
-rw-r--r--source3/winbindd/winbindd_irpc.c891
-rw-r--r--source3/winbindd/winbindd_list_groups.c233
-rw-r--r--source3/winbindd/winbindd_list_users.c216
-rw-r--r--source3/winbindd/winbindd_locator.c56
-rw-r--r--source3/winbindd/winbindd_lookupname.c130
-rw-r--r--source3/winbindd/winbindd_lookuprids.c200
-rw-r--r--source3/winbindd/winbindd_lookupsid.c104
-rw-r--r--source3/winbindd/winbindd_lookupsids.c144
-rw-r--r--source3/winbindd/winbindd_misc.c513
-rw-r--r--source3/winbindd/winbindd_msrpc.c1124
-rw-r--r--source3/winbindd/winbindd_ndr.c162
-rw-r--r--source3/winbindd/winbindd_pam.c3616
-rw-r--r--source3/winbindd/winbindd_pam_auth.c291
-rw-r--r--source3/winbindd/winbindd_pam_auth_crap.c285
-rw-r--r--source3/winbindd/winbindd_pam_chauthtok.c204
-rw-r--r--source3/winbindd/winbindd_pam_chng_pswd_auth_crap.c171
-rw-r--r--source3/winbindd/winbindd_pam_logoff.c183
-rw-r--r--source3/winbindd/winbindd_ping_dc.c140
-rw-r--r--source3/winbindd/winbindd_proto.h1059
-rw-r--r--source3/winbindd/winbindd_reconnect.c354
-rw-r--r--source3/winbindd/winbindd_reconnect_ads.c362
-rw-r--r--source3/winbindd/winbindd_rpc.c855
-rw-r--r--source3/winbindd/winbindd_rpc.h95
-rw-r--r--source3/winbindd/winbindd_samr.c1424
-rw-r--r--source3/winbindd/winbindd_setgrent.c67
-rw-r--r--source3/winbindd/winbindd_setpwent.c67
-rw-r--r--source3/winbindd/winbindd_show_sequence.c167
-rw-r--r--source3/winbindd/winbindd_sids_to_xids.c164
-rw-r--r--source3/winbindd/winbindd_traceid.c147
-rw-r--r--source3/winbindd/winbindd_traceid.h29
-rw-r--r--source3/winbindd/winbindd_util.c2243
-rw-r--r--source3/winbindd/winbindd_wins_byip.c142
-rw-r--r--source3/winbindd/winbindd_wins_byname.c154
-rw-r--r--source3/winbindd/winbindd_xids_to_sids.c142
-rw-r--r--source3/winbindd/wscript_build291
-rw-r--r--source3/wscript2109
-rw-r--r--source3/wscript_build1323
-rw-r--r--source3/wscript_configure_system_ncurses27
1592 files changed, 854724 insertions, 0 deletions
diff --git a/source3/.clang_complete b/source3/.clang_complete
new file mode 100644
index 0000000..7898373
--- /dev/null
+++ b/source3/.clang_complete
@@ -0,0 +1,19 @@
+-I.
+-I./..
+-I./../lib
+-I./../lib/replace
+-I./../lib/talloc
+-I./../lib/tevent
+-I./../lib/popt
+-I./../lib/tdb/include
+-I./include/autoconf
+-I./include
+-I./librpc
+-I./lib
+-DDEBUG_PASSWORD
+-DDEVELOPER
+-DHAVE_CONFIG_H
+-D_GNU_SOURCE
+-D_SAMBA_BUILD_=3
+-D_REENTRANT
+-DUSING_SMBCONTROL
diff --git a/source3/.dmallocrc b/source3/.dmallocrc
new file mode 100644
index 0000000..5e5c45e
--- /dev/null
+++ b/source3/.dmallocrc
@@ -0,0 +1,2 @@
+samba allow-free-null, log-stats, log-non-free, log-trans, \
+ check-fence, check-heap, check-lists, error-abort \ No newline at end of file
diff --git a/source3/.indent.pro b/source3/.indent.pro
new file mode 100644
index 0000000..05445f4
--- /dev/null
+++ b/source3/.indent.pro
@@ -0,0 +1,30 @@
+-bad
+-bap
+-bbb
+-br
+-ce
+-ut
+-ts8
+-i8
+-di1
+-brs
+-npsl
+-npcs
+-prs
+-bbo
+-hnl
+-bad
+-bap
+-bbb
+-br
+-ce
+-ut
+-ts8
+-i8
+-di1
+-brs
+-npsl
+-npcs
+-prs
+-bbo
+-hnl
diff --git a/source3/Doxyfile b/source3/Doxyfile
new file mode 100644
index 0000000..9ade25c
--- /dev/null
+++ b/source3/Doxyfile
@@ -0,0 +1,192 @@
+# Doxyfile 1.5.3
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+PROJECT_NAME = Samba
+PROJECT_NUMBER = HEAD
+
+# NOTE: By default, Doxygen writes into the dox/ subdirectory of the
+# invocation directory. If you want to put it somewhere else, for
+# example, to write straight into a webserver directory, then override
+# this variable in a configuration concatenated to this one: Doxygen
+# doesn't mind variables being redefined.
+
+OUTPUT_DIRECTORY = dox
+OUTPUT_LANGUAGE = English
+DOXYFILE_ENCODING = UTF-8
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ALWAYS_DETAILED_SEC = NO
+FULL_PATH_NAMES = YES
+STRIP_FROM_PATH = $(PWD)/
+SHORT_NAMES = NO
+JAVADOC_AUTOBRIEF = YES
+INHERIT_DOCS = YES
+TAB_SIZE = 8
+ALIASES =
+OPTIMIZE_OUTPUT_FOR_C = YES
+DISTRIBUTE_GROUP_DOC = NO
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL = YES
+EXTRACT_PRIVATE = YES
+EXTRACT_STATIC = YES
+EXTRACT_LOCAL_CLASSES = YES
+HIDE_UNDOC_MEMBERS = NO
+HIDE_UNDOC_CLASSES = NO
+INTERNAL_DOCS = YES
+CASE_SENSE_NAMES = YES
+HIDE_SCOPE_NAMES = YES
+SHOW_INCLUDE_FILES = YES
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = NO
+SORT_BRIEF_DOCS = NO
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = YES
+SHOW_DIRECTORIES = YES
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET = YES
+WARNINGS = NO
+WARN_IF_UNDOCUMENTED = NO
+WARN_IF_DOC_ERROR = NO
+WARN_NO_PARAMDOC = NO
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT = .
+INPUT_ENCODING = UTF-8
+FILE_PATTERNS = *.c \
+ *.h \
+ *.idl
+RECURSIVE = YES
+EXCLUDE = include/includes.h \
+ include/proto.h
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS =
+EXAMPLE_PATH =
+EXAMPLE_PATTERNS =
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH =
+INPUT_FILTER =
+FILTER_SOURCE_FILES = NO
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER = YES
+INLINE_SOURCES = YES
+STRIP_CODE_COMMENTS = NO
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION = YES
+REFERENCES_LINK_SOURCE = YES
+VERBATIM_HEADERS = YES
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX = YES
+COLS_IN_ALPHA_INDEX = 1
+IGNORE_PREFIX =
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML = YES
+HTML_OUTPUT = .
+HTML_FILE_EXTENSION = .html
+HTML_HEADER =
+HTML_FOOTER =
+HTML_STYLESHEET =
+HTML_ALIGN_MEMBERS = YES
+GENERATE_HTMLHELP = NO
+TOC_EXPAND = NO
+DISABLE_INDEX = NO
+ENUM_VALUES_PER_LINE = 3
+GENERATE_TREEVIEW = NO
+TREEVIEW_WIDTH = 250
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX = NO
+LATEX_OUTPUT = latex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4wide
+EXTRA_PACKAGES =
+LATEX_HEADER =
+PDF_HYPERLINKS = YES
+USE_PDFLATEX = YES
+LATEX_BATCHMODE = YES
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_LINKS = NO
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML = NO
+#---------------------------------------------------------------------------
+# configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING = NO
+MACRO_EXPANSION = NO
+EXPAND_ONLY_PREDEF = NO
+SEARCH_INCLUDES = YES
+INCLUDE_PATH =
+INCLUDE_FILE_PATTERNS =
+PREDEFINED =
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+#---------------------------------------------------------------------------
+# configuration::additions related to external references
+#---------------------------------------------------------------------------
+TAGFILES =
+GENERATE_TAGFILE =
+ALLEXTERNALS = NO
+PERL_PATH = /usr/bin/perl
+#---------------------------------------------------------------------------
+# configuration options related to the dot tool
+#---------------------------------------------------------------------------
+HAVE_DOT = NO
+CLASS_DIAGRAMS = YES
+HIDE_UNDOC_RELATIONS = NO
+CLASS_GRAPH = YES
+COLLABORATION_GRAPH = YES
+GROUP_GRAPHS = YES
+TEMPLATE_RELATIONS = YES
+INCLUDE_GRAPH = YES
+INCLUDED_BY_GRAPH = YES
+CALL_GRAPH = YES
+CALLER_GRAPH = YES
+GRAPHICAL_HIERARCHY = YES
+DIRECTORY_GRAPH = YES
+DOT_IMAGE_FORMAT = png
+DOT_PATH =
+DOTFILE_DIRS =
+DOT_GRAPH_MAX_NODES = 50
+MAX_DOT_GRAPH_DEPTH = 0
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
+#---------------------------------------------------------------------------
+# configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+SEARCHENGINE = NO
diff --git a/source3/auth/auth.c b/source3/auth/auth.c
new file mode 100644
index 0000000..b83e98f
--- /dev/null
+++ b/source3/auth/auth.c
@@ -0,0 +1,639 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Bartlett 2001-2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+
+#include "param/param.h"
+#include "../lib/messaging/messaging.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+static_decl_auth;
+
+static struct auth_init_function_entry *auth_backends = NULL;
+
+static struct auth_init_function_entry *auth_find_backend_entry(const char *name);
+
+NTSTATUS smb_register_auth(int version, const char *name, auth_init_function init)
+{
+ struct auth_init_function_entry *entry = NULL;
+
+ if (version != AUTH_INTERFACE_VERSION) {
+ DEBUG(0,("Can't register auth_method!\n"
+ "You tried to register an auth module with AUTH_INTERFACE_VERSION %d, while this version of samba uses %d\n",
+ version,AUTH_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !init) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(5,("Attempting to register auth backend %s\n", name));
+
+ if (auth_find_backend_entry(name)) {
+ DEBUG(0,("There already is an auth method registered with the name %s!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = SMB_XMALLOC_P(struct auth_init_function_entry);
+ entry->name = smb_xstrdup(name);
+ entry->init = init;
+
+ DLIST_ADD(auth_backends, entry);
+ DEBUG(5,("Successfully added auth method '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+static struct auth_init_function_entry *auth_find_backend_entry(const char *name)
+{
+ struct auth_init_function_entry *entry = auth_backends;
+
+ while(entry) {
+ if (strcmp(entry->name, name)==0) return entry;
+ entry = entry->next;
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Try to get a challenge out of the various authentication modules.
+ Returns a const char of length 8 bytes.
+****************************************************************************/
+
+NTSTATUS auth_get_ntlm_challenge(struct auth_context *auth_context,
+ uint8_t chal[8])
+{
+ if (auth_context->challenge.length) {
+ DBG_INFO("get_ntlm_challenge (auth subsystem): returning "
+ "previous challenge by module %s (normal)\n",
+ auth_context->challenge_set_by);
+ memcpy(chal, auth_context->challenge.data, 8);
+ return NT_STATUS_OK;
+ }
+
+ auth_context->challenge = data_blob_talloc(auth_context, NULL, 8);
+ if (auth_context->challenge.data == NULL) {
+ DBG_WARNING("data_blob_talloc failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+ generate_random_buffer(
+ auth_context->challenge.data, auth_context->challenge.length);
+
+ auth_context->challenge_set_by = "random";
+
+ memcpy(chal, auth_context->challenge.data, 8);
+ return NT_STATUS_OK;
+}
+
+
+/**
+ * Check user is in correct domain (if required)
+ *
+ * @param user Only used to fill in the debug message
+ *
+ * @param domain The domain to be verified
+ *
+ * @return True if the user can connect with that domain,
+ * False otherwise.
+**/
+
+static bool check_domain_match(const char *user, const char *domain)
+{
+ /*
+ * If we aren't serving to trusted domains, we must make sure that
+ * the validation request comes from an account in the same domain
+ * as the Samba server
+ */
+
+ if (!lp_allow_trusted_domains() &&
+ !(strequal("", domain) ||
+ strequal(lp_workgroup(), domain) ||
+ is_myname(domain))) {
+ DEBUG(1, ("check_domain_match: Attempt to connect as user %s from domain %s denied.\n", user, domain));
+ return False;
+ } else {
+ return True;
+ }
+}
+
+/**
+ * Check a user's Plaintext, LM or NTLM password.
+ *
+ * Check a user's password, as given in the user_info struct and return various
+ * interesting details in the server_info struct.
+ *
+ * This function does NOT need to be in a become_root()/unbecome_root() pair
+ * as it makes the calls itself when needed.
+ *
+ * The return value takes precedence over the contents of the server_info
+ * struct. When the return is other than NT_STATUS_OK the contents
+ * of that structure is undefined.
+ *
+ * @param user_info Contains the user supplied components, including the passwords.
+ * Must be created with make_user_info() or one of its wrappers.
+ *
+ * @param auth_context Supplies the challenges and some other data.
+ * Must be created with make_auth_context(), and the challenges should be
+ * filled in, either at creation or by calling the challenge generation
+ * function auth_get_challenge().
+ *
+ * @param pserver_info If successful, contains information about the authentication,
+ * including a struct samu struct describing the user.
+ *
+ * @param pauthoritative Indicates if the result should be treated as final
+ * result.
+ *
+ * @return An NTSTATUS with NT_STATUS_OK or an appropriate error.
+ *
+ **/
+NTSTATUS auth_check_ntlm_password(TALLOC_CTX *mem_ctx,
+ const struct auth_context *auth_context,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **pserver_info,
+ uint8_t *pauthoritative)
+{
+ TALLOC_CTX *frame;
+ const char *auth_method_name = "";
+ /* if all the modules say 'not for me' this is reasonable */
+ NTSTATUS nt_status = NT_STATUS_NOT_IMPLEMENTED;
+ const char *unix_username;
+ struct auth_methods *auth_method;
+ struct auth_serversupplied_info *server_info = NULL;
+ struct dom_sid sid = {0};
+ struct imessaging_context *msg_ctx = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+
+ if (user_info == NULL || auth_context == NULL || pserver_info == NULL) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ frame = talloc_stackframe();
+
+ if (lp_auth_event_notification()) {
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ msg_ctx = imessaging_client_init(
+ frame, lp_ctx, global_event_context());
+ }
+
+ *pauthoritative = 1;
+
+ DBG_NOTICE("check_ntlm_password: Checking password for unmapped user "
+ "[%s]\\[%s]@[%s] with the new password interface\n",
+ user_info->client.domain_name,
+ user_info->client.account_name,
+ user_info->workstation_name);
+
+ DBG_NOTICE("check_ntlm_password: mapped user is: [%s]\\[%s]@[%s]\n",
+ user_info->mapped.domain_name,
+ user_info->mapped.account_name,
+ user_info->workstation_name);
+
+ if (auth_context->challenge.length != 8) {
+ DEBUG(0, ("check_ntlm_password: Invalid challenge stored for this auth context - cannot continue\n"));
+ nt_status = NT_STATUS_LOGON_FAILURE;
+ goto fail;
+ }
+
+ if (auth_context->challenge_set_by)
+ DEBUG(10, ("check_ntlm_password: auth_context challenge created by %s\n",
+ auth_context->challenge_set_by));
+
+ DEBUG(10, ("challenge is: \n"));
+ dump_data(5, auth_context->challenge.data, auth_context->challenge.length);
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100, ("user_info has passwords of length %d and %d\n",
+ (int)user_info->password.response.lanman.length, (int)user_info->password.response.nt.length));
+ DEBUG(100, ("lm:\n"));
+ dump_data(100, user_info->password.response.lanman.data, user_info->password.response.lanman.length);
+ DEBUG(100, ("nt:\n"));
+ dump_data(100, user_info->password.response.nt.data, user_info->password.response.nt.length);
+#endif
+
+ /* This needs to be sorted: If it doesn't match, what should we do? */
+ if (!check_domain_match(user_info->client.account_name,
+ user_info->mapped.domain_name)) {
+ nt_status = NT_STATUS_LOGON_FAILURE;
+ goto fail;
+ }
+
+ for (auth_method = auth_context->auth_method_list;auth_method; auth_method = auth_method->next) {
+
+ auth_method_name = auth_method->name;
+
+ nt_status = auth_method->auth(auth_context,
+ auth_method->private_data,
+ talloc_tos(),
+ user_info,
+ &server_info);
+
+ if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_NOT_IMPLEMENTED)) {
+ break;
+ }
+
+ DBG_DEBUG("%s had nothing to say\n", auth_method->name);
+ }
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NOT_IMPLEMENTED)) {
+ *pauthoritative = 0;
+ nt_status = NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_INFO("%s authentication for user [%s] FAILED with "
+ "error %s, authoritative=%u\n",
+ auth_method_name,
+ user_info->client.account_name,
+ nt_errstr(nt_status),
+ *pauthoritative);
+ goto fail;
+ }
+
+ DBG_NOTICE("%s authentication for user [%s] succeeded\n",
+ auth_method_name, user_info->client.account_name);
+
+ unix_username = server_info->unix_name;
+
+ /* We skip doing this step if the caller asked us not to */
+ if (!(user_info->flags & USER_INFO_INFO3_AND_NO_AUTHZ)
+ && !(server_info->guest)) {
+ const char *rhost;
+
+ if (tsocket_address_is_inet(user_info->remote_host, "ip")) {
+ rhost = tsocket_address_inet_addr_string(
+ user_info->remote_host, talloc_tos());
+ if (rhost == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ } else {
+ rhost = "127.0.0.1";
+ }
+
+ /* We might not be root if we are an RPC call */
+ become_root();
+ nt_status = smb_pam_accountcheck(unix_username, rhost);
+ unbecome_root();
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(5, ("check_ntlm_password: PAM Account for user [%s] "
+ "succeeded\n", unix_username));
+ } else {
+ DEBUG(3, ("check_ntlm_password: PAM Account for user [%s] "
+ "FAILED with error %s\n",
+ unix_username, nt_errstr(nt_status)));
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto fail;
+ }
+
+ nt_status = get_user_sid_info3_and_extra(server_info->info3,
+ &server_info->extra,
+ &sid);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ sid = (struct dom_sid) {0};
+ }
+
+ log_authentication_event(msg_ctx,
+ lp_ctx,
+ &auth_context->start_time,
+ user_info,
+ nt_status,
+ server_info->info3->base.logon_domain.string,
+ server_info->info3->base.account_name.string,
+ &sid,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_info */);
+
+ DEBUG(server_info->guest ? 5 : 2,
+ ("check_ntlm_password: %sauthentication for user "
+ "[%s] -> [%s] -> [%s] succeeded\n",
+ server_info->guest ? "guest " : "",
+ user_info->client.account_name,
+ user_info->mapped.account_name,
+ unix_username));
+
+ *pserver_info = talloc_move(mem_ctx, &server_info);
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+
+fail:
+
+ /* failed authentication; check for guest lapping */
+
+ /*
+ * Please try not to change this string, it is probably in use
+ * in audit logging tools
+ */
+ DEBUG(2, ("check_ntlm_password: Authentication for user "
+ "[%s] -> [%s] FAILED with error %s, authoritative=%u\n",
+ user_info->client.account_name, user_info->mapped.account_name,
+ nt_errstr(nt_status), *pauthoritative));
+
+ log_authentication_event(msg_ctx,
+ lp_ctx,
+ &auth_context->start_time,
+ user_info,
+ nt_status,
+ NULL,
+ NULL,
+ NULL,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_info */);
+
+ ZERO_STRUCTP(pserver_info);
+
+ TALLOC_FREE(frame);
+
+ return nt_status;
+}
+
+/***************************************************************************
+ Clear out a auth_context, and destroy the attached TALLOC_CTX
+***************************************************************************/
+
+static int auth_context_destructor(void *ptr)
+{
+ struct auth_context *ctx = talloc_get_type(ptr, struct auth_context);
+ struct auth_methods *am;
+
+
+ /* Free private data of context's authentication methods */
+ for (am = ctx->auth_method_list; am; am = am->next) {
+ TALLOC_FREE(am->private_data);
+ }
+
+ return 0;
+}
+
+/***************************************************************************
+ Make a auth_info struct
+***************************************************************************/
+
+static NTSTATUS make_auth_context(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context)
+{
+ struct auth_context *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct auth_context);
+ if (!ctx) {
+ DEBUG(0,("make_auth_context: talloc failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ctx->start_time = timeval_current();
+
+ talloc_set_destructor((TALLOC_CTX *)ctx, auth_context_destructor);
+
+ *auth_context = ctx;
+ return NT_STATUS_OK;
+}
+
+static bool load_auth_module(
+ struct auth_context *auth_context,
+ const char *module,
+ struct auth_methods **ret)
+{
+ static bool initialised_static_modules = False;
+
+ struct auth_init_function_entry *entry;
+ char *module_name = smb_xstrdup(module);
+ char *module_params = NULL;
+ char *p;
+ bool good = False;
+
+ /* Initialise static modules if not done so yet */
+ if(!initialised_static_modules) {
+ static_init_auth(NULL);
+ initialised_static_modules = True;
+ }
+
+ DEBUG(5,("load_auth_module: Attempting to find an auth method to match %s\n",
+ module));
+
+ p = strchr(module_name, ':');
+ if (p) {
+ *p = 0;
+ module_params = p+1;
+ trim_char(module_params, ' ', ' ');
+ }
+
+ trim_char(module_name, ' ', ' ');
+
+ entry = auth_find_backend_entry(module_name);
+
+ if (entry == NULL) {
+ if (NT_STATUS_IS_OK(smb_probe_module("auth", module_name))) {
+ entry = auth_find_backend_entry(module_name);
+ }
+ }
+
+ if (entry != NULL) {
+ if (!NT_STATUS_IS_OK(entry->init(auth_context, module_params, ret))) {
+ DEBUG(0,("load_auth_module: auth method %s did not correctly init\n",
+ module_name));
+ } else {
+ DEBUG(5,("load_auth_module: auth method %s has a valid init\n",
+ module_name));
+ good = True;
+ }
+ } else {
+ DEBUG(0,("load_auth_module: can't find auth method %s!\n", module_name));
+ }
+
+ SAFE_FREE(module_name);
+ return good;
+}
+
+/***************************************************************************
+ Make a auth_info struct for the auth subsystem
+***************************************************************************/
+
+static NTSTATUS make_auth_context_text_list(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context,
+ char **text_list)
+{
+ struct auth_methods *list = NULL;
+ struct auth_methods *t, *method = NULL;
+ NTSTATUS nt_status;
+
+ if (!text_list) {
+ DEBUG(2,("make_auth_context_text_list: No auth method list!?\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ nt_status = make_auth_context(mem_ctx, auth_context);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ for (;*text_list; text_list++) {
+ if (load_auth_module(*auth_context, *text_list, &t)) {
+ DLIST_ADD_END(list, t);
+ }
+ }
+
+ (*auth_context)->auth_method_list = list;
+
+ /* Look for the first module to provide a prepare_gensec and
+ * make_auth4_context hook, and set that if provided */
+ for (method = (*auth_context)->auth_method_list; method; method = method->next) {
+ if (method->prepare_gensec && method->make_auth4_context) {
+ (*auth_context)->prepare_gensec = method->prepare_gensec;
+ (*auth_context)->make_auth4_context = method->make_auth4_context;
+ break;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS make_auth_context_specific(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context,
+ const char *methods)
+{
+ char **method_list;
+ NTSTATUS status;
+
+ method_list = str_list_make_v3(talloc_tos(), methods, NULL);
+ if (method_list == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = make_auth_context_text_list(
+ mem_ctx, auth_context, method_list);
+
+ TALLOC_FREE(method_list);
+
+ return status;
+}
+
+/***************************************************************************
+ Make a auth_context struct for the auth subsystem
+***************************************************************************/
+
+NTSTATUS make_auth3_context_for_ntlm(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context)
+{
+ const char *methods = NULL;
+ const char *role = NULL;
+
+ switch (lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ role = "'active directory domain controller'";
+ methods = "samba4";
+ break;
+ case ROLE_DOMAIN_MEMBER:
+ role = "'domain member'";
+ methods = "anonymous sam winbind sam_ignoredomain";
+ break;
+ case ROLE_DOMAIN_BDC:
+ case ROLE_DOMAIN_PDC:
+ case ROLE_IPA_DC:
+ role = "'DC'";
+ methods = "anonymous sam winbind sam_ignoredomain";
+ break;
+ case ROLE_STANDALONE:
+ if (lp_encrypt_passwords()) {
+ role = "'standalone server', encrypt passwords = yes";
+ methods = "anonymous sam_ignoredomain";
+ } else {
+ role = "'standalone server', encrypt passwords = no";
+ methods = "anonymous unix";
+ }
+ break;
+ default:
+ DEBUG(5,("Unknown auth method!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DBG_INFO("Making default auth method list for server role = %s\n",
+ role);
+
+ return make_auth_context_specific(mem_ctx, auth_context, methods);
+}
+
+NTSTATUS make_auth3_context_for_netlogon(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context)
+{
+ const char *methods = NULL;
+
+ switch (lp_server_role()) {
+ case ROLE_DOMAIN_BDC:
+ case ROLE_DOMAIN_PDC:
+ case ROLE_IPA_DC:
+ methods = "sam_netlogon3 winbind";
+ break;
+
+ default:
+ DBG_ERR("Invalid server role!\n");
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ return make_auth_context_specific(mem_ctx, auth_context, methods);
+}
+
+NTSTATUS make_auth3_context_for_winbind(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context)
+{
+ const char *methods = NULL;
+
+ switch (lp_server_role()) {
+ case ROLE_STANDALONE:
+ case ROLE_DOMAIN_MEMBER:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_DOMAIN_PDC:
+ case ROLE_IPA_DC:
+ methods = "sam";
+ break;
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ methods = "samba4:sam";
+ break;
+ default:
+ DEBUG(5,("Unknown auth method!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return make_auth_context_specific(mem_ctx, auth_context, methods);
+}
+
+bool auth3_context_set_challenge(
+ struct auth_context *ctx,
+ const uint8_t chal[8],
+ const char *challenge_set_by)
+{
+ ctx->challenge = data_blob_talloc(ctx, chal, 8);
+ if (ctx->challenge.data == NULL) {
+ return false;
+ }
+ ctx->challenge_set_by = talloc_strdup(ctx, challenge_set_by);
+ if (ctx->challenge_set_by == NULL) {
+ return false;
+ }
+ return true;
+}
diff --git a/source3/auth/auth_builtin.c b/source3/auth/auth_builtin.c
new file mode 100644
index 0000000..646fbea
--- /dev/null
+++ b/source3/auth/auth_builtin.c
@@ -0,0 +1,184 @@
+/*
+ Unix SMB/CIFS implementation.
+ Generic authentication types
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Jelmer Vernooij 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/**
+ * Return a guest logon for anonymous users (username = "")
+ *
+ * Typically used as the first module in the auth chain, this allows
+ * guest logons to be dealt with in one place. Non-guest logons 'fail'
+ * and pass onto the next module.
+ **/
+
+static NTSTATUS check_anonymous_security(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ DEBUG(10, ("Check auth for: [%s]\n", user_info->mapped.account_name));
+
+ if (user_info->mapped.account_name && *user_info->mapped.account_name) {
+ /* mark this as 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ switch (user_info->password_state) {
+ case AUTH_PASSWORD_PLAIN:
+ if (user_info->password.plaintext != NULL &&
+ strlen(user_info->password.plaintext) > 0)
+ {
+ /* mark this as 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ break;
+ case AUTH_PASSWORD_HASH:
+ if (user_info->password.hash.lanman != NULL) {
+ /* mark this as 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ if (user_info->password.hash.nt != NULL) {
+ /* mark this as 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ break;
+ case AUTH_PASSWORD_RESPONSE:
+ if (user_info->password.response.lanman.length == 1) {
+ if (user_info->password.response.lanman.data[0] != '\0') {
+ /* mark this as 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ } else if (user_info->password.response.lanman.length > 1) {
+ /* mark this as 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ if (user_info->password.response.nt.length > 0) {
+ /* mark this as 'not for me' */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ break;
+ }
+
+ return make_server_info_anonymous(NULL, server_info);
+}
+
+/* Guest modules initialisation */
+
+static NTSTATUS auth_init_anonymous(
+ struct auth_context *auth_context,
+ const char *options,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->auth = check_anonymous_security;
+ result->name = "anonymous";
+
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+#ifdef DEVELOPER
+/**
+ * Return an error based on username
+ *
+ * This function allows the testing of obscure errors, as well as the generation
+ * of NT_STATUS -> DOS error mapping tables.
+ *
+ * This module is of no value to end-users.
+ *
+ * The password is ignored.
+ *
+ * @return An NTSTATUS value based on the username
+ **/
+
+static NTSTATUS check_name_to_ntstatus_security(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ NTSTATUS nt_status;
+ fstring user;
+ long error_num;
+
+ DEBUG(10, ("Check auth for: [%s]\n", user_info->mapped.account_name));
+
+ fstrcpy(user, user_info->client.account_name);
+
+ if (strnequal("NT_STATUS", user, strlen("NT_STATUS"))) {
+ if (!strupper_m(user)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return nt_status_string_to_code(user);
+ }
+
+ if (!strlower_m(user)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ error_num = strtoul(user, NULL, 16);
+
+ DEBUG(5,("check_name_to_ntstatus_security: Error for user %s was %lx\n", user, error_num));
+
+ nt_status = NT_STATUS(error_num);
+
+ return nt_status;
+}
+
+/** Module initialisation function */
+
+static NTSTATUS auth_init_name_to_ntstatus(
+ struct auth_context *auth_context,
+ const char *param,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->auth = check_name_to_ntstatus_security;
+ result->name = "name_to_ntstatus";
+
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+#endif /* DEVELOPER */
+
+NTSTATUS auth_builtin_init(TALLOC_CTX *mem_ctx)
+{
+ smb_register_auth(AUTH_INTERFACE_VERSION, "anonymous", auth_init_anonymous);
+#ifdef DEVELOPER
+ smb_register_auth(AUTH_INTERFACE_VERSION, "name_to_ntstatus", auth_init_name_to_ntstatus);
+#endif
+ return NT_STATUS_OK;
+}
diff --git a/source3/auth/auth_generic.c b/source3/auth/auth_generic.c
new file mode 100644
index 0000000..f957045
--- /dev/null
+++ b/source3/auth/auth_generic.c
@@ -0,0 +1,557 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ handle GENSEC authentication, server side
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett 2001-2003,2011
+ Copyright (C) Simo Sorce 2010.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include <tevent.h>
+#include "../lib/util/tevent_ntstatus.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "auth/gensec/gensec.h"
+#include "lib/param/param.h"
+#ifdef HAVE_KRB5
+#include "auth/kerberos/pac_utils.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#endif
+#include "librpc/crypto/gse.h"
+#include "auth/credentials/credentials.h"
+#include "lib/param/loadparm.h"
+#include "librpc/gen_ndr/dcerpc.h"
+#include "source3/lib/substitute.h"
+
+static NTSTATUS auth3_generate_session_info_pac(struct auth4_context *auth_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ DATA_BLOB *pac_blob,
+ const char *princ_name,
+ const struct tsocket_address *remote_address,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info)
+{
+ enum server_role server_role = lp_server_role();
+ TALLOC_CTX *tmp_ctx;
+ bool is_mapped;
+ bool is_guest;
+ char *ntuser;
+ char *ntdomain;
+ char *username;
+ const char *rhost;
+ struct passwd *pw;
+ NTSTATUS status;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (tsocket_address_is_inet(remote_address, "ip")) {
+ rhost = tsocket_address_inet_addr_string(
+ remote_address, tmp_ctx);
+ if (rhost == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ } else {
+ rhost = "127.0.0.1";
+ }
+
+ if (server_role != ROLE_STANDALONE) {
+ struct wbcAuthUserParams params = { 0 };
+ struct wbcAuthUserInfo *info = NULL;
+ struct wbcAuthErrorInfo *err = NULL;
+ struct auth_serversupplied_info *server_info = NULL;
+ char *original_user_name = NULL;
+ char *p = NULL;
+ wbcErr wbc_err;
+
+ if (pac_blob == NULL) {
+ /*
+ * This should already be caught at the main
+ * gensec layer, but better check twice
+ */
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /*
+ * Let winbind decode the PAC.
+ * This will also store the user
+ * data in the netsamlogon cache.
+ *
+ * This used to be a cache prime
+ * optimization, but now we delegate
+ * all logic to winbindd, as we require
+ * winbindd as domain member anyway.
+ */
+ params.level = WBC_AUTH_USER_LEVEL_PAC;
+ params.password.pac.data = pac_blob->data;
+ params.password.pac.length = pac_blob->length;
+
+ /* we are contacting the privileged pipe */
+ become_root();
+ wbc_err = wbcAuthenticateUserEx(&params, &info, &err);
+ unbecome_root();
+
+ /*
+ * As this is merely a cache prime
+ * WBC_ERR_WINBIND_NOT_AVAILABLE
+ * is not a fatal error, treat it
+ * as success.
+ */
+
+ switch (wbc_err) {
+ case WBC_ERR_SUCCESS:
+ break;
+ case WBC_ERR_WINBIND_NOT_AVAILABLE:
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ DBG_ERR("winbindd not running - "
+ "but required as domain member: %s\n",
+ nt_errstr(status));
+ goto done;
+ case WBC_ERR_AUTH_ERROR:
+ status = NT_STATUS(err->nt_status);
+ wbcFreeMemory(err);
+ goto done;
+ case WBC_ERR_NO_MEMORY:
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ default:
+ status = NT_STATUS_LOGON_FAILURE;
+ goto done;
+ }
+
+ status = make_server_info_wbcAuthUserInfo(tmp_ctx,
+ info->account_name,
+ info->domain_name,
+ info, &server_info);
+ wbcFreeMemory(info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("make_server_info_wbcAuthUserInfo failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /* We skip doing this step if the caller asked us not to */
+ if (!(server_info->guest)) {
+ const char *unix_username = server_info->unix_name;
+
+ /* We might not be root if we are an RPC call */
+ become_root();
+ status = smb_pam_accountcheck(unix_username, rhost);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("check_ntlm_password: PAM Account for user [%s] "
+ "FAILED with error %s\n",
+ unix_username, nt_errstr(status)));
+ goto done;
+ }
+
+ DEBUG(5, ("check_ntlm_password: PAM Account for user [%s] "
+ "succeeded\n", unix_username));
+ }
+
+ DEBUG(3, ("Kerberos ticket principal name is [%s]\n", princ_name));
+
+ p = strchr_m(princ_name, '@');
+ if (!p) {
+ DEBUG(3, ("[%s] Doesn't look like a valid principal\n",
+ princ_name));
+ status = NT_STATUS_LOGON_FAILURE;
+ goto done;
+ }
+
+ original_user_name = talloc_strndup(tmp_ctx, princ_name, p - princ_name);
+ if (original_user_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = create_local_token(mem_ctx,
+ server_info,
+ NULL,
+ original_user_name,
+ session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("create_local_token failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ goto session_info_ready;
+ }
+
+ /* This is the standalone legacy code path */
+
+ if (pac_blob != NULL) {
+ /*
+ * In standalone mode we don't expect a PAC!
+ * we only support MIT realms
+ */
+ status = NT_STATUS_BAD_TOKEN_TYPE;
+ DBG_WARNING("Unexpected PAC for [%s] in standalone mode - %s\n",
+ princ_name, nt_errstr(status));
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ status = get_user_from_kerberos_info(tmp_ctx, rhost,
+ princ_name,
+ &is_mapped, &is_guest,
+ &ntuser, &ntdomain,
+ &username, &pw);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("Failed to map kerberos principal to system user "
+ "(%s)\n", nt_errstr(status));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ status = make_session_info_krb5(mem_ctx,
+ ntuser, ntdomain, username, pw,
+ is_guest, is_mapped,
+ session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to map kerberos pac to server info (%s)\n",
+ nt_errstr(status)));
+ status = nt_status_squash(status);
+ goto done;
+ }
+
+session_info_ready:
+
+ /* setup the string used by %U */
+ set_current_user_info((*session_info)->unix_info->sanitized_username,
+ (*session_info)->unix_info->unix_name,
+ (*session_info)->info->domain_name);
+
+ /* reload services so that the new %U is taken into account */
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ DEBUG(5, (__location__ "OK: user: %s domain: %s client: %s\n",
+ (*session_info)->info->account_name,
+ (*session_info)->info->domain_name,
+ rhost));
+
+ status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+static struct auth4_context *make_auth4_context_s3(TALLOC_CTX *mem_ctx, struct auth_context *auth_context)
+{
+ struct auth4_context *auth4_context = talloc_zero(mem_ctx, struct auth4_context);
+ if (auth4_context == NULL) {
+ DEBUG(10, ("failed to allocate auth4_context failed\n"));
+ return NULL;
+ }
+ auth4_context->generate_session_info_pac = auth3_generate_session_info_pac;
+ auth4_context->generate_session_info = auth3_generate_session_info;
+ auth4_context->get_ntlm_challenge = auth3_get_challenge;
+ auth4_context->set_ntlm_challenge = auth3_set_challenge;
+ auth4_context->check_ntlm_password_send = auth3_check_password_send;
+ auth4_context->check_ntlm_password_recv = auth3_check_password_recv;
+ auth4_context->private_data = talloc_steal(auth4_context, auth_context);
+ return auth4_context;
+}
+
+NTSTATUS make_auth4_context(TALLOC_CTX *mem_ctx, struct auth4_context **auth4_context_out)
+{
+ struct auth_context *auth_context;
+ NTSTATUS nt_status;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ nt_status = make_auth3_context_for_ntlm(tmp_ctx, &auth_context);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ if (auth_context->make_auth4_context) {
+ nt_status = auth_context->make_auth4_context(auth_context, mem_ctx, auth4_context_out);
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+
+ } else {
+ struct auth4_context *auth4_context = make_auth4_context_s3(tmp_ctx, auth_context);
+ if (auth4_context == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *auth4_context_out = talloc_steal(mem_ctx, auth4_context);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+}
+
+NTSTATUS auth_generic_prepare(TALLOC_CTX *mem_ctx,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ struct gensec_security **gensec_security_out)
+{
+ struct gensec_security *gensec_security;
+ struct auth_context *auth_context = NULL;
+ NTSTATUS nt_status;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ nt_status = make_auth3_context_for_ntlm(tmp_ctx, &auth_context);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ if (auth_context->prepare_gensec) {
+ nt_status = auth_context->prepare_gensec(auth_context, tmp_ctx,
+ &gensec_security);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+ } else {
+ const struct gensec_security_ops **backends = NULL;
+ struct gensec_settings *gensec_settings;
+ struct loadparm_context *lp_ctx;
+ size_t idx = 0;
+ struct cli_credentials *server_credentials;
+ const char *dns_name;
+ const char *dns_domain;
+ bool ok;
+ struct auth4_context *auth4_context = make_auth4_context_s3(tmp_ctx, auth_context);
+ if (auth4_context == NULL) {
+ goto nomem;
+ }
+
+ lp_ctx = loadparm_init_s3(tmp_ctx, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(10, ("loadparm_init_s3 failed\n"));
+ nt_status = NT_STATUS_INVALID_SERVER_STATE;
+ goto done;
+ }
+
+ gensec_settings = lpcfg_gensec_settings(tmp_ctx, lp_ctx);
+ if (lp_ctx == NULL) {
+ DEBUG(10, ("lpcfg_gensec_settings failed\n"));
+ goto nomem;
+ }
+
+ /*
+ * This should be a 'netbios domain -> DNS domain'
+ * mapping, and can currently validly return NULL on
+ * poorly configured systems.
+ *
+ * This is used for the NTLMSSP server
+ *
+ */
+ dns_name = get_mydnsfullname();
+ if (dns_name == NULL) {
+ dns_name = "";
+ }
+
+ dns_domain = get_mydnsdomname(tmp_ctx);
+ if (dns_domain == NULL) {
+ dns_domain = "";
+ }
+
+ gensec_settings->server_dns_name = strlower_talloc(gensec_settings, dns_name);
+ if (gensec_settings->server_dns_name == NULL) {
+ goto nomem;
+ }
+
+ gensec_settings->server_dns_domain = strlower_talloc(gensec_settings, dns_domain);
+ if (gensec_settings->server_dns_domain == NULL) {
+ goto nomem;
+ }
+
+ backends = talloc_zero_array(gensec_settings,
+ const struct gensec_security_ops *, 6);
+ if (backends == NULL) {
+ goto nomem;
+ }
+ gensec_settings->backends = backends;
+
+ gensec_init();
+
+ /* These need to be in priority order, krb5 before NTLMSSP */
+#if defined(HAVE_KRB5)
+ backends[idx++] = &gensec_gse_krb5_security_ops;
+#endif
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_NTLMSSP);
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_SPNEGO);
+
+ backends[idx++] = gensec_security_by_auth_type(NULL, DCERPC_AUTH_TYPE_SCHANNEL);
+
+ backends[idx++] = gensec_security_by_auth_type(NULL, DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM);
+
+ /*
+ * This is anonymous for now, because we just use it
+ * to set the kerberos state at the moment
+ */
+ server_credentials = cli_credentials_init_anon(tmp_ctx);
+ if (!server_credentials) {
+ DEBUG(0, ("auth_generic_prepare: Failed to init server credentials\n"));
+ goto nomem;
+ }
+
+ ok = cli_credentials_set_conf(server_credentials, lp_ctx);
+ if (!ok) {
+ DBG_ERR("Failed to set server credentials defaults "
+ "from smb.conf.\n");
+ goto nomem;
+ }
+
+ if (lp_security() == SEC_ADS || USE_KERBEROS_KEYTAB) {
+ cli_credentials_set_kerberos_state(server_credentials,
+ CRED_USE_KERBEROS_DESIRED,
+ CRED_SPECIFIED);
+ } else {
+ cli_credentials_set_kerberos_state(server_credentials,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+ }
+
+ nt_status = gensec_server_start(tmp_ctx, gensec_settings,
+ auth4_context, &gensec_security);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ nt_status = gensec_set_credentials(
+ gensec_security, server_credentials);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+ }
+
+ nt_status = gensec_set_remote_address(gensec_security,
+ remote_address);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ nt_status = gensec_set_local_address(gensec_security,
+ local_address);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ nt_status = gensec_set_target_service_description(gensec_security,
+ service_description);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ *gensec_security_out = talloc_move(mem_ctx, &gensec_security);
+ nt_status = NT_STATUS_OK;
+ goto done;
+nomem:
+ nt_status = NT_STATUS_NO_MEMORY;
+done:
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+}
+
+/*
+ * Check a username and password, and return the final session_info.
+ * We also log the authorization of the session here, just as
+ * gensec_session_info() does.
+ */
+NTSTATUS auth_check_password_session_info(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info *user_info,
+ struct auth_session_info **session_info)
+{
+ NTSTATUS nt_status;
+ void *server_info;
+ uint8_t authoritative = 1;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *subreq = NULL;
+ bool ok;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ subreq = auth_context->check_ntlm_password_send(ev, ev,
+ auth_context,
+ user_info);
+ if (subreq == NULL) {
+ TALLOC_FREE(ev);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ok = tevent_req_poll_ntstatus(subreq, ev, &nt_status);
+ if (!ok) {
+ TALLOC_FREE(ev);
+ return nt_status;
+ }
+ nt_status = auth_context->check_ntlm_password_recv(subreq,
+ talloc_tos(),
+ &authoritative,
+ &server_info,
+ NULL, NULL);
+ TALLOC_FREE(ev);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ nt_status = auth_context->generate_session_info(auth_context,
+ mem_ctx,
+ server_info,
+ user_info->client.account_name,
+ AUTH_SESSION_INFO_UNIX_TOKEN |
+ AUTH_SESSION_INFO_DEFAULT_GROUPS |
+ AUTH_SESSION_INFO_NTLM,
+ session_info);
+ TALLOC_FREE(server_info);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ /*
+ * This is rather redundant (the authentication has just been
+ * logged, with much the same details), but because we want to
+ * log all authorizations consistently (be they NLTM, NTLMSSP
+ * or krb5) we log this info again as an authorization.
+ */
+ log_successful_authz_event(auth_context->msg_ctx,
+ auth_context->lp_ctx,
+ user_info->remote_host,
+ user_info->local_host,
+ user_info->service_description,
+ user_info->auth_description,
+ AUTHZ_TRANSPORT_PROTECTION_SMB,
+ *session_info,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_info */);
+
+ return nt_status;
+}
diff --git a/source3/auth/auth_ntlmssp.c b/source3/auth/auth_ntlmssp.c
new file mode 100644
index 0000000..73938dc
--- /dev/null
+++ b/source3/auth/auth_ntlmssp.c
@@ -0,0 +1,326 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ handle NLTMSSP, server side
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett 2001-2005,2011
+ Copyright (C) Stefan Metzmacher 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "libcli/security/security.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "source3/lib/substitute.h"
+
+NTSTATUS auth3_generate_session_info(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ void *server_returned_info,
+ const char *original_user_name,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info)
+{
+ struct auth_user_info_dc *user_info = NULL;
+ struct auth_serversupplied_info *server_info = NULL;
+ NTSTATUS nt_status;
+
+ /*
+ * This is a hack, some callers...
+ *
+ * Some callers pass auth_user_info_dc, the SCHANNEL and
+ * NCALRPC_AS_SYSTEM gensec modules.
+ *
+ * While the rest passes auth3_check_password() returned.
+ */
+ user_info = talloc_get_type(server_returned_info,
+ struct auth_user_info_dc);
+ if (user_info != NULL) {
+ const struct dom_sid *sid;
+ int cmp;
+
+ /*
+ * This should only be called from SCHANNEL or NCALRPC_AS_SYSTEM
+ */
+ if (user_info->num_sids != 1) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ sid = &user_info->sids[PRIMARY_USER_SID_INDEX].sid;
+
+ cmp = dom_sid_compare(sid, &global_sid_System);
+ if (cmp == 0) {
+ return make_session_info_system(mem_ctx, session_info);
+ }
+
+ cmp = dom_sid_compare(sid, &global_sid_Anonymous);
+ if (cmp == 0) {
+ return make_session_info_anonymous(mem_ctx, session_info);
+ }
+
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ server_info = talloc_get_type_abort(server_returned_info,
+ struct auth_serversupplied_info);
+ nt_status = create_local_token(mem_ctx,
+ server_info,
+ NULL,
+ original_user_name,
+ session_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(10, ("create_local_token failed: %s\n",
+ nt_errstr(nt_status)));
+ return nt_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Return the challenge as determined by the authentication subsystem
+ * @return an 8 byte random challenge
+ */
+
+NTSTATUS auth3_get_challenge(struct auth4_context *auth4_context,
+ uint8_t chal[8])
+{
+ struct auth_context *auth_context = talloc_get_type_abort(auth4_context->private_data,
+ struct auth_context);
+ auth_get_ntlm_challenge(auth_context, chal);
+ return NT_STATUS_OK;
+}
+
+/**
+ * NTLM2 authentication modifies the effective challenge,
+ * @param challenge The new challenge value
+ */
+NTSTATUS auth3_set_challenge(struct auth4_context *auth4_context, const uint8_t *chal,
+ const char *challenge_set_by)
+{
+ struct auth_context *auth_context = talloc_get_type_abort(auth4_context->private_data,
+ struct auth_context);
+ bool ok;
+
+ ok = auth3_context_set_challenge(auth_context, chal, challenge_set_by);
+ if (!ok) {
+ /*
+ * This can only fail for ENOMEM
+ */
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5, ("auth_context challenge set by %s\n", auth_context->challenge_set_by));
+ DEBUG(5, ("challenge is: \n"));
+ dump_data(5, auth_context->challenge.data, auth_context->challenge.length);
+ return NT_STATUS_OK;
+}
+
+/**
+ * Check the password on an NTLMSSP login.
+ *
+ * Return the session keys used on the connection.
+ */
+
+struct auth3_check_password_state {
+ uint8_t authoritative;
+ void *server_info;
+ DATA_BLOB nt_session_key;
+ DATA_BLOB lm_session_key;
+};
+
+struct tevent_req *auth3_check_password_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth4_context *auth4_context,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct auth3_check_password_state *state = NULL;
+ struct auth_context *auth_context = talloc_get_type_abort(
+ auth4_context->private_data, struct auth_context);
+ struct auth_usersupplied_info *mapped_user_info = NULL;
+ struct auth_serversupplied_info *server_info = NULL;
+ char *sanitized_username = NULL;
+ NTSTATUS nt_status;
+ bool username_was_mapped;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct auth3_check_password_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Be authoritative by default.
+ */
+ state->authoritative = 1;
+
+ /* The client has given us its machine name (which we only get over NBT transport).
+ We need to possibly reload smb.conf if smb.conf includes depend on the machine name. */
+
+ set_remote_machine_name(user_info->workstation_name, True);
+
+ nt_status = make_user_info_map(talloc_tos(),
+ &mapped_user_info,
+ user_info->client.account_name,
+ user_info->client.domain_name,
+ user_info->workstation_name,
+ user_info->remote_host,
+ user_info->local_host,
+ user_info->service_description,
+ user_info->password.response.lanman.data ? &user_info->password.response.lanman : NULL,
+ user_info->password.response.nt.data ? &user_info->password.response.nt : NULL,
+ NULL, NULL, NULL,
+ AUTH_PASSWORD_RESPONSE);
+
+ if (tevent_req_nterror(req, nt_status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ mapped_user_info->logon_parameters = user_info->logon_parameters;
+
+ mapped_user_info->flags = user_info->flags;
+
+ sanitized_username = talloc_alpha_strcpy(
+ state,
+ user_info->client.account_name,
+ SAFE_NETBIOS_CHARS "$");
+ if (sanitized_username == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ nt_status = auth_check_ntlm_password(state,
+ auth_context,
+ mapped_user_info,
+ &server_info,
+ &state->authoritative);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_INFO("Checking NTLMSSP password for %s\\%s failed: "
+ "%s, authoritative=%"PRIu8"\n",
+ user_info->client.domain_name,
+ user_info->client.account_name,
+ nt_errstr(nt_status),
+ state->authoritative);
+ }
+
+ username_was_mapped = mapped_user_info->was_mapped;
+
+ TALLOC_FREE(mapped_user_info);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ nt_status = do_map_to_guest_server_info(
+ state,
+ nt_status,
+ user_info->client.account_name,
+ user_info->client.domain_name,
+ &server_info);
+ if (!tevent_req_nterror(req, nt_status)) {
+ state->authoritative = 1;
+
+ /* setup the string used by %U */
+ set_current_user_info(
+ sanitized_username,
+ server_info->unix_name,
+ server_info->info3->base.logon_domain.string);
+
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ tevent_req_done(req);
+ }
+ state->server_info = server_info;
+ return tevent_req_post(req, ev);
+ }
+
+ server_info->nss_token |= username_was_mapped;
+
+ /* setup the string used by %U */
+ set_current_user_info(sanitized_username,
+ server_info->unix_name,
+ server_info->info3->base.logon_domain.string);
+
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ /* Clear out the session keys, and pass them to the caller.
+ * They will not be used in this form again - instead the
+ * NTLMSSP code will decide on the final correct session key,
+ * and supply it to create_local_token() */
+
+ DBG_DEBUG("Got NT session key of length %zu\n",
+ server_info->session_key.length);
+ state->nt_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ state, &server_info->session_key.data),
+ .length = server_info->session_key.length,
+ };
+ server_info->session_key = data_blob_null;
+
+ DBG_DEBUG("Got LM session key of length %zu\n",
+ server_info->lm_session_key.length);
+ state->lm_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ state, &server_info->lm_session_key.data),
+ .length = server_info->lm_session_key.length,
+ };
+ server_info->lm_session_key = data_blob_null;
+
+ state->server_info = server_info;
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+NTSTATUS auth3_check_password_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *pauthoritative,
+ void **server_returned_info,
+ DATA_BLOB *nt_session_key,
+ DATA_BLOB *lm_session_key)
+{
+ struct auth3_check_password_state *state = tevent_req_data(
+ req, struct auth3_check_password_state);
+ NTSTATUS status;
+
+ if (pauthoritative != NULL) {
+ *pauthoritative = state->authoritative;
+ }
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (server_returned_info != NULL) {
+ *server_returned_info = talloc_move(
+ mem_ctx, &state->server_info);
+ }
+ if (nt_session_key != NULL) {
+ *nt_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ mem_ctx, &state->nt_session_key.data),
+ .length = state->nt_session_key.length,
+ };
+ }
+ if (lm_session_key != NULL) {
+ *lm_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ mem_ctx, &state->lm_session_key.data),
+ .length = state->lm_session_key.length,
+ };
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/auth/auth_sam.c b/source3/auth/auth_sam.c
new file mode 100644
index 0000000..a2ce101
--- /dev/null
+++ b/source3/auth/auth_sam.c
@@ -0,0 +1,315 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Luke Kenneth Casson Leighton 1996-2000
+ Copyright (C) Andrew Bartlett 2001-2003
+ Copyright (C) Gerald Carter 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "passdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+static NTSTATUS auth_sam_ignoredomain_auth(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ if (!user_info || !auth_context) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (user_info->mapped.account_name == NULL ||
+ user_info->mapped.account_name[0] == '\0')
+ {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ DBG_DEBUG("Check auth for: [%s]\\[%s]\n",
+ user_info->mapped.domain_name,
+ user_info->mapped.account_name);
+
+ return check_sam_security(&auth_context->challenge, mem_ctx,
+ user_info, server_info);
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_sam_ignoredomain(
+ struct auth_context *auth_context,
+ const char *param,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->auth = auth_sam_ignoredomain_auth;
+ result->name = "sam_ignoredomain";
+
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+
+/****************************************************************************
+Check SAM security (above) but with a few extra checks.
+****************************************************************************/
+
+static NTSTATUS auth_samstrict_auth(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ const char *effective_domain = NULL;
+ bool is_local_name, is_my_domain;
+
+ if (!user_info || !auth_context) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ effective_domain = user_info->mapped.domain_name;
+
+ if (user_info->mapped.account_name == NULL ||
+ user_info->mapped.account_name[0] == '\0')
+ {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (lp_server_role() == ROLE_DOMAIN_MEMBER) {
+ const char *p = NULL;
+
+ p = strchr_m(user_info->mapped.account_name, '@');
+ if (p != NULL) {
+ /*
+ * This needs to go to the DC,
+ * even if @ is the last character
+ */
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ }
+
+ if (effective_domain == NULL) {
+ effective_domain = "";
+ }
+
+ DBG_DEBUG("Check auth for: [%s]\\[%s]\n",
+ effective_domain,
+ user_info->mapped.account_name);
+
+
+ if (strequal(effective_domain, "") || strequal(effective_domain, ".")) {
+ /*
+ * An empty domain name or '.' should be handled
+ * as the local SAM name.
+ */
+ effective_domain = lp_netbios_name();
+ }
+
+ is_local_name = is_myname(effective_domain);
+ is_my_domain = strequal(effective_domain, lp_workgroup());
+
+ /* check whether or not we service this domain/workgroup name */
+
+ switch ( lp_server_role() ) {
+ case ROLE_STANDALONE:
+ case ROLE_DOMAIN_MEMBER:
+ if ( !is_local_name ) {
+ DEBUG(6,("check_samstrict_security: %s is not one of my local names (%s)\n",
+ effective_domain, (lp_server_role() == ROLE_DOMAIN_MEMBER
+ ? "ROLE_DOMAIN_MEMBER" : "ROLE_STANDALONE") ));
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ break;
+ case ROLE_DOMAIN_PDC:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_IPA_DC:
+ if (!is_local_name && !is_my_domain) {
+ /* If we are running on a DC that has PASSDB module with domain
+ * information, check if DNS forest name is matching the domain
+ * name. This is the case of IPA domain controller when
+ * trusted AD DCs attempt to authenticate IPA users using
+ * the forest root domain (which is the only domain in IPA).
+ */
+ struct pdb_domain_info *dom_info = NULL;
+
+ dom_info = pdb_get_domain_info(mem_ctx);
+ if ((dom_info != NULL) && (dom_info->dns_forest != NULL)) {
+ is_my_domain = strequal(user_info->mapped.domain_name,
+ dom_info->dns_forest);
+ }
+
+ TALLOC_FREE(dom_info);
+ if (!is_my_domain) {
+ DEBUG(6,("check_samstrict_security: %s is not one "
+ "of my local names or domain name (DC)\n",
+ effective_domain));
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+ }
+
+ break;
+ default: /* name is ok */
+ break;
+ }
+
+ return check_sam_security(&auth_context->challenge, mem_ctx,
+ user_info, server_info);
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_sam(
+ struct auth_context *auth_context,
+ const char *param,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC
+ && !lp_parm_bool(-1, "server role check", "inhibit", false)) {
+ DEBUG(0, ("server role = 'active directory domain controller' not compatible with running the auth_sam module. \n"));
+ DEBUGADD(0, ("You should not set 'auth methods' when running the AD DC.\n"));
+ exit(1);
+ }
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->auth = auth_samstrict_auth;
+ result->name = "sam";
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS auth_sam_netlogon3_auth(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ const char *effective_domain = NULL;
+ bool is_my_domain;
+
+ if (!user_info || !auth_context) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ effective_domain = user_info->mapped.domain_name;
+
+ if (user_info->mapped.account_name == NULL ||
+ user_info->mapped.account_name[0] == '\0')
+ {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (effective_domain == NULL) {
+ effective_domain = "";
+ }
+
+ DBG_DEBUG("Check auth for: [%s]\\[%s]\n",
+ effective_domain,
+ user_info->mapped.account_name);
+
+ /* check whether or not we service this domain/workgroup name */
+
+ switch (lp_server_role()) {
+ case ROLE_DOMAIN_PDC:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_IPA_DC:
+ break;
+ default:
+ DBG_ERR("Invalid server role\n");
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ if (strequal(effective_domain, "") || strequal(effective_domain, ".")) {
+ /*
+ * An empty domain name or '.' should be handled
+ * as the local SAM name.
+ */
+ effective_domain = lp_workgroup();
+ }
+
+ is_my_domain = strequal(user_info->mapped.domain_name, lp_workgroup());
+ if (!is_my_domain) {
+ /* If we are running on a DC that has PASSDB module with domain
+ * information, check if DNS forest name is matching the domain
+ * name. This is the case of IPA domain controller when
+ * trusted AD DCs attempt to authenticate IPA users using
+ * the forest root domain (which is the only domain in IPA).
+ */
+ struct pdb_domain_info *dom_info = NULL;
+ dom_info = pdb_get_domain_info(mem_ctx);
+
+ if ((dom_info != NULL) && (dom_info->dns_forest != NULL)) {
+ is_my_domain = strequal(user_info->mapped.domain_name,
+ dom_info->dns_forest);
+ }
+
+ TALLOC_FREE(dom_info);
+ }
+
+ if (!is_my_domain) {
+ DBG_INFO("%s is not our domain name (DC for %s)\n",
+ effective_domain, lp_workgroup());
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ return check_sam_security(&auth_context->challenge, mem_ctx,
+ user_info, server_info);
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_sam_netlogon3(
+ struct auth_context *auth_context,
+ const char *param,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC
+ && !lp_parm_bool(-1, "server role check", "inhibit", false)) {
+ DEBUG(0, ("server role = 'active directory domain controller' "
+ "not compatible with running the auth_sam module.\n"));
+ DEBUGADD(0, ("You should not set 'auth methods' when "
+ "running the AD DC.\n"));
+ exit(1);
+ }
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->auth = auth_sam_netlogon3_auth;
+ result->name = "sam_netlogon3";
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_sam_init(TALLOC_CTX *mem_ctx)
+{
+ smb_register_auth(AUTH_INTERFACE_VERSION, "sam", auth_init_sam);
+ smb_register_auth(AUTH_INTERFACE_VERSION, "sam_ignoredomain", auth_init_sam_ignoredomain);
+ smb_register_auth(AUTH_INTERFACE_VERSION, "sam_netlogon3", auth_init_sam_netlogon3);
+ return NT_STATUS_OK;
+}
diff --git a/source3/auth/auth_samba4.c b/source3/auth/auth_samba4.c
new file mode 100644
index 0000000..bae34e5
--- /dev/null
+++ b/source3/auth/auth_samba4.c
@@ -0,0 +1,401 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authenticate against Samba4's auth subsystem
+ Copyright (C) Volker Lendecke 2008
+ Copyright (C) Andrew Bartlett 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "source3/include/auth.h"
+#include "source3/include/messages.h"
+#include "source4/auth/auth.h"
+#include "auth/auth_sam_reply.h"
+#include "param/param.h"
+#include "source4/lib/events/events.h"
+#include "source4/lib/messaging/messaging.h"
+#include "auth/gensec/gensec.h"
+#include "auth/credentials/credentials.h"
+#include "lib/global_contexts.h"
+#include "lib/util/idtree.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+static NTSTATUS make_auth4_context_s4(const struct auth_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct auth4_context **auth4_context);
+
+static struct idr_context *task_id_tree;
+
+static int free_task_id(struct server_id *server_id)
+{
+ idr_remove(task_id_tree, server_id->task_id);
+ return 0;
+}
+
+/* Return a server_id with a unique task_id element. Free the
+ * returned pointer to de-allocate the task_id via a talloc destructor
+ * (ie, use talloc_free()) */
+static struct server_id *new_server_id_task(TALLOC_CTX *mem_ctx)
+{
+ struct messaging_context *msg_ctx;
+ struct server_id *server_id;
+ int task_id;
+ if (!task_id_tree) {
+ task_id_tree = idr_init(NULL);
+ if (!task_id_tree) {
+ return NULL;
+ }
+ }
+
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ return NULL;
+ }
+
+ server_id = talloc(mem_ctx, struct server_id);
+
+ if (!server_id) {
+ return NULL;
+ }
+ *server_id = messaging_server_id(msg_ctx);
+
+ /* 0 is the default server_id, so we need to start with 1 */
+ task_id = idr_get_new_above(task_id_tree, server_id, 1, INT32_MAX);
+
+ if (task_id == -1) {
+ talloc_free(server_id);
+ return NULL;
+ }
+
+ talloc_set_destructor(server_id, free_task_id);
+ server_id->task_id = task_id;
+ return server_id;
+}
+
+/*
+ * This module is not an ordinary authentication module. It is really
+ * a way to redirect the whole authentication and authorization stack
+ * to use the source4 auth code, not a way to just handle NTLM
+ * authentication.
+ *
+ * See the comments above each function for how that hook changes the
+ * behaviour.
+ */
+
+/*
+ * This hook is currently used by winbindd only, as all other NTLM
+ * logins go via the hooks provided by make_auth4_context_s4() below.
+ *
+ * This is only left in case we find a way that it might become useful
+ * in future. Importantly, this routine returns the information
+ * needed for a NETLOGON SamLogon, not what is needed to establish a
+ * session.
+ *
+ * We expect we may use this hook in the source3/ winbind when this
+ * services the AD DC. It is tested via pdbtest.
+ */
+
+static NTSTATUS check_samba4_security(
+ const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **pserver_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct netr_SamInfo3 *info3 = NULL;
+ NTSTATUS nt_status;
+ struct auth_user_info_dc *user_info_dc;
+ struct auth4_context *auth4_context;
+ uint8_t authoritative = 1;
+ struct auth_serversupplied_info *server_info = NULL;
+
+ nt_status = make_auth4_context_s4(auth_context, mem_ctx, &auth4_context);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(frame);
+ goto done;
+ }
+
+ nt_status = auth_context_set_challenge(auth4_context, auth_context->challenge.data, "auth_samba4");
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(auth4_context);
+ TALLOC_FREE(frame);
+ return nt_status;
+ }
+
+ nt_status = auth_check_password(auth4_context, auth4_context, user_info,
+ &user_info_dc, &authoritative);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER) &&
+ authoritative == 0)
+ {
+ nt_status = NT_STATUS_NOT_IMPLEMENTED;
+ }
+ TALLOC_FREE(auth4_context);
+ TALLOC_FREE(frame);
+ return nt_status;
+ }
+
+ nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx,
+ user_info_dc,
+ AUTH_INCLUDE_RESOURCE_GROUPS,
+ &info3,
+ NULL);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ /* We need the strings from the server_info to be valid as long as the info3 is around */
+ talloc_steal(info3, user_info_dc);
+ }
+ talloc_free(auth4_context);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ if (user_info->flags & USER_INFO_INFO3_AND_NO_AUTHZ) {
+ server_info = make_server_info(mem_ctx);
+ if (server_info == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ server_info->info3 = talloc_move(server_info, &info3);
+ } else {
+ nt_status = make_server_info_info3(
+ mem_ctx,
+ user_info->client.account_name,
+ user_info->mapped.domain_name,
+ &server_info,
+ info3);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(10, ("make_server_info_info3 failed: %s\n",
+ nt_errstr(nt_status)));
+ goto done;
+ }
+ }
+
+ *pserver_info = server_info;
+ nt_status = NT_STATUS_OK;
+
+ done:
+ TALLOC_FREE(frame);
+ return nt_status;
+}
+
+/*
+ * Hook to allow the source4 set of GENSEC modules to handle
+ * blob-based authentication mechanisms, without directly linking the
+ * mechanism code.
+ *
+ * This may eventually go away, when the GSSAPI acceptors are merged,
+ * when we will just rely on the make_auth4_context_s4 hook instead.
+ *
+ * Even for NTLMSSP, which has a common module, significant parts of
+ * the behaviour are overridden here, because it uses the source4 NTLM
+ * stack and the source4 mapping between the PAC/SamLogon response and
+ * the local token.
+ *
+ * It is important to override all this to ensure that the exact same
+ * token is generated and used in the SMB and LDAP servers, for NTLM
+ * and for Kerberos.
+ */
+static NTSTATUS prepare_gensec(const struct auth_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct gensec_security **gensec_context)
+{
+ NTSTATUS status;
+ struct loadparm_context *lp_ctx;
+ struct tevent_context *event_ctx;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct gensec_security *gensec_ctx;
+ struct imessaging_context *msg_ctx;
+ struct cli_credentials *server_credentials;
+ struct server_id *server_id;
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(1, ("loadparm_init_s3 failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+ event_ctx = s4_event_context_init(frame);
+ if (event_ctx == NULL) {
+ DEBUG(1, ("s4_event_context_init failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ server_id = new_server_id_task(frame);
+ if (server_id == NULL) {
+ DEBUG(1, ("new_server_id_task failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ msg_ctx = imessaging_init_discard_incoming(frame,
+ lp_ctx,
+ *server_id,
+ event_ctx);
+ if (msg_ctx == NULL) {
+ DEBUG(1, ("imessaging_init_discard_incoming failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ talloc_reparent(frame, msg_ctx, server_id);
+
+ server_credentials
+ = cli_credentials_init_server(frame, lp_ctx);
+ if (!server_credentials) {
+ DEBUG(1, ("Failed to init server credentials\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ status = samba_server_gensec_start(mem_ctx,
+ event_ctx, msg_ctx,
+ lp_ctx, server_credentials, "cifs",
+ &gensec_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to start GENSEC server code: %s\n", nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ talloc_reparent(frame, gensec_ctx, msg_ctx);
+ talloc_reparent(frame, gensec_ctx, event_ctx);
+ talloc_reparent(frame, gensec_ctx, lp_ctx);
+ talloc_reparent(frame, gensec_ctx, server_credentials);
+
+ gensec_want_feature(gensec_ctx, GENSEC_FEATURE_SESSION_KEY);
+ gensec_want_feature(gensec_ctx, GENSEC_FEATURE_UNIX_TOKEN);
+
+ *gensec_context = gensec_ctx;
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * Hook to allow handling of NTLM authentication for AD operation
+ * without directly linking the s4 auth stack
+ *
+ * This ensures we use the source4 authentication stack, as well as
+ * the authorization stack to create the user's token. This ensures
+ * consistency between NTLM logins and NTLMSSP logins, as NTLMSSP is
+ * handled by the hook above.
+ */
+static NTSTATUS make_auth4_context_s4(const struct auth_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct auth4_context **auth4_context)
+{
+ NTSTATUS status;
+ struct loadparm_context *lp_ctx;
+ struct tevent_context *event_ctx;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct imessaging_context *msg_ctx;
+ struct server_id *server_id;
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(1, ("loadparm_init_s3 failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+ event_ctx = s4_event_context_init(frame);
+ if (event_ctx == NULL) {
+ DEBUG(1, ("s4_event_context_init failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ server_id = new_server_id_task(frame);
+ if (server_id == NULL) {
+ DEBUG(1, ("new_server_id_task failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ msg_ctx = imessaging_init_discard_incoming(frame,
+ lp_ctx,
+ *server_id,
+ event_ctx);
+ if (msg_ctx == NULL) {
+ DEBUG(1, ("imessaging_init_discard_incoming failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+ talloc_reparent(frame, msg_ctx, server_id);
+
+ /* Allow forcing a specific auth4 module */
+ if (!auth_context->forced_samba4_methods) {
+ status = auth_context_create(mem_ctx,
+ event_ctx,
+ msg_ctx,
+ lp_ctx,
+ auth4_context);
+ } else {
+ const char * const *forced_auth_methods = (const char * const *)str_list_make(mem_ctx, auth_context->forced_samba4_methods, NULL);
+ status = auth_context_create_methods(mem_ctx, forced_auth_methods, event_ctx, msg_ctx, lp_ctx, NULL, auth4_context);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to start auth server code: %s\n", nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ talloc_reparent(frame, *auth4_context, msg_ctx);
+ talloc_reparent(frame, *auth4_context, event_ctx);
+ talloc_reparent(frame, *auth4_context, lp_ctx);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_samba4(struct auth_context *auth_context,
+ const char *param,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ gensec_init();
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->name = "samba4";
+ result->auth = check_samba4_security;
+ result->prepare_gensec = prepare_gensec;
+ result->make_auth4_context = make_auth4_context_s4;
+
+ if (param && *param) {
+ auth_context->forced_samba4_methods = talloc_strdup(result, param);
+ if (!auth_context->forced_samba4_methods) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_samba4_init(TALLOC_CTX *mem_ctx);
+NTSTATUS auth_samba4_init(TALLOC_CTX *mem_ctx)
+{
+ smb_register_auth(AUTH_INTERFACE_VERSION, "samba4",
+ auth_init_samba4);
+ return NT_STATUS_OK;
+}
diff --git a/source3/auth/auth_unix.c b/source3/auth/auth_unix.c
new file mode 100644
index 0000000..eaf344d
--- /dev/null
+++ b/source3/auth/auth_unix.c
@@ -0,0 +1,107 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Bartlett 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "system/passwd.h"
+#include "../lib/tsocket/tsocket.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/** Check a plaintext username/password
+ *
+ * Cannot deal with an encrypted password in any manner whatsoever,
+ * unless the account has a null password.
+ **/
+
+static NTSTATUS check_unix_security(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ NTSTATUS nt_status;
+ struct passwd *pass = NULL;
+ const char *rhost;
+
+ DEBUG(10, ("Check auth for: [%s]\n", user_info->mapped.account_name));
+
+ if (tsocket_address_is_inet(user_info->remote_host, "ip")) {
+ rhost = tsocket_address_inet_addr_string(user_info->remote_host,
+ talloc_tos());
+ if (rhost == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ rhost = "127.0.0.1";
+ }
+
+ become_root();
+ pass = Get_Pwnam_alloc(talloc_tos(), user_info->mapped.account_name);
+
+ /** @todo This call assumes a ASCII password, no charset transformation is
+ done. We may need to revisit this **/
+ nt_status = pass_check(pass,
+ pass ? pass->pw_name : user_info->mapped.account_name,
+ rhost,
+ user_info->password.plaintext,
+ true);
+
+ unbecome_root();
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ if (pass != NULL) {
+ nt_status = make_server_info_pw(mem_ctx,
+ pass->pw_name,
+ pass,
+ server_info);
+ } else {
+ /* we need to do something more useful here */
+ nt_status = NT_STATUS_NO_SUCH_USER;
+ }
+ }
+
+ TALLOC_FREE(pass);
+ return nt_status;
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_unix(
+ struct auth_context *auth_context,
+ const char* param,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->name = "unix";
+ result->auth = check_unix_security;
+
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_unix_init(TALLOC_CTX *mem_ctx)
+{
+ return smb_register_auth(AUTH_INTERFACE_VERSION, "unix", auth_init_unix);
+}
diff --git a/source3/auth/auth_util.c b/source3/auth/auth_util.c
new file mode 100644
index 0000000..abc5e95
--- /dev/null
+++ b/source3/auth/auth_util.c
@@ -0,0 +1,2368 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001-2011
+ Copyright (C) Jeremy Allison 2000-2001
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Volker Lendecke 2006-2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dom_sid.h"
+#include "includes.h"
+#include "auth.h"
+#include "lib/util_unixsids.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/util_pw.h"
+#include "lib/winbind_util.h"
+#include "passdb.h"
+#include "../librpc/gen_ndr/ndr_auth.h"
+#include "../auth/auth_sam_reply.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "lib/param/loadparm.h"
+#include "../lib/tsocket/tsocket.h"
+#include "rpc_client/util_netlogon.h"
+#include "source4/auth/auth.h"
+#include "auth/auth_util.h"
+#include "source3/lib/substitute.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/****************************************************************************
+ Create a UNIX user on demand.
+****************************************************************************/
+
+static int _smb_create_user(const char *domain, const char *unix_username, const char *homedir)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *add_script;
+ int ret;
+
+ add_script = lp_add_user_script(ctx, lp_sub);
+ if (!add_script || !*add_script) {
+ return -1;
+ }
+ add_script = talloc_all_string_sub(ctx,
+ add_script,
+ "%u",
+ unix_username);
+ if (!add_script) {
+ return -1;
+ }
+ if (domain) {
+ add_script = talloc_all_string_sub(ctx,
+ add_script,
+ "%D",
+ domain);
+ if (!add_script) {
+ return -1;
+ }
+ }
+ if (homedir) {
+ add_script = talloc_all_string_sub(ctx,
+ add_script,
+ "%H",
+ homedir);
+ if (!add_script) {
+ return -1;
+ }
+ }
+ ret = smbrun(add_script, NULL, NULL);
+ flush_pwnam_cache();
+ DEBUG(ret ? 0 : 3,
+ ("smb_create_user: Running the command `%s' gave %d\n",
+ add_script,ret));
+ return ret;
+}
+
+/****************************************************************************
+ Create an auth_usersupplied_data structure after appropriate mapping.
+****************************************************************************/
+
+NTSTATUS make_user_info_map(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ const DATA_BLOB *lm_pwd,
+ const DATA_BLOB *nt_pwd,
+ const struct samr_Password *lm_interactive_pwd,
+ const struct samr_Password *nt_interactive_pwd,
+ const char *plaintext,
+ enum auth_password_state password_state)
+{
+ const char *domain;
+ NTSTATUS result;
+ bool was_mapped;
+ char *internal_username = NULL;
+
+ was_mapped = map_username(talloc_tos(), smb_name, &internal_username);
+ if (!internal_username) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5, ("Mapping user [%s]\\[%s] from workstation [%s]\n",
+ client_domain, smb_name, workstation_name));
+
+ /*
+ * We let the auth stack canonicalize, username
+ * and domain.
+ */
+ domain = client_domain;
+
+ result = make_user_info(mem_ctx, user_info, smb_name, internal_username,
+ client_domain, domain, workstation_name,
+ remote_address, local_address,
+ service_description, lm_pwd, nt_pwd,
+ lm_interactive_pwd, nt_interactive_pwd,
+ plaintext, password_state);
+ if (NT_STATUS_IS_OK(result)) {
+ /* did we actually map the user to a different name? */
+ (*user_info)->was_mapped = was_mapped;
+ }
+ return result;
+}
+
+/****************************************************************************
+ Create an auth_usersupplied_data, making the DATA_BLOBs here.
+ Decrypt and encrypt the passwords.
+****************************************************************************/
+
+bool make_user_info_netlogon_network(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ uint32_t logon_parameters,
+ const uchar *lm_network_pwd,
+ int lm_pwd_len,
+ const uchar *nt_network_pwd,
+ int nt_pwd_len)
+{
+ bool ret;
+ NTSTATUS status;
+ DATA_BLOB lm_blob = data_blob(lm_network_pwd, lm_pwd_len);
+ DATA_BLOB nt_blob = data_blob(nt_network_pwd, nt_pwd_len);
+
+ status = make_user_info_map(mem_ctx, user_info,
+ smb_name, client_domain,
+ workstation_name,
+ remote_address,
+ local_address,
+ "SamLogon",
+ lm_pwd_len ? &lm_blob : NULL,
+ nt_pwd_len ? &nt_blob : NULL,
+ NULL, NULL, NULL,
+ AUTH_PASSWORD_RESPONSE);
+
+ if (NT_STATUS_IS_OK(status)) {
+ (*user_info)->logon_parameters = logon_parameters;
+ }
+ ret = NT_STATUS_IS_OK(status) ? true : false;
+
+ data_blob_free(&lm_blob);
+ data_blob_free(&nt_blob);
+ return ret;
+}
+
+/****************************************************************************
+ Create an auth_usersupplied_data, making the DATA_BLOBs here.
+ Decrypt and encrypt the passwords.
+****************************************************************************/
+
+bool make_user_info_netlogon_interactive(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ uint32_t logon_parameters,
+ const uchar chal[8],
+ const uchar lm_interactive_pwd[16],
+ const uchar nt_interactive_pwd[16])
+{
+ struct samr_Password lm_pwd;
+ struct samr_Password nt_pwd;
+ unsigned char local_lm_response[24];
+ unsigned char local_nt_response[24];
+ int rc;
+
+ if (lm_interactive_pwd)
+ memcpy(lm_pwd.hash, lm_interactive_pwd, sizeof(lm_pwd.hash));
+
+ if (nt_interactive_pwd)
+ memcpy(nt_pwd.hash, nt_interactive_pwd, sizeof(nt_pwd.hash));
+
+ if (lm_interactive_pwd) {
+ rc = SMBOWFencrypt(lm_pwd.hash, chal,
+ local_lm_response);
+ if (rc != 0) {
+ return false;
+ }
+ }
+
+ if (nt_interactive_pwd) {
+ rc = SMBOWFencrypt(nt_pwd.hash, chal,
+ local_nt_response);
+ if (rc != 0) {
+ return false;
+ }
+ }
+
+ {
+ bool ret;
+ NTSTATUS nt_status;
+ DATA_BLOB local_lm_blob = data_blob_null;
+ DATA_BLOB local_nt_blob = data_blob_null;
+
+ if (lm_interactive_pwd) {
+ local_lm_blob = data_blob(local_lm_response,
+ sizeof(local_lm_response));
+ }
+
+ if (nt_interactive_pwd) {
+ local_nt_blob = data_blob(local_nt_response,
+ sizeof(local_nt_response));
+ }
+
+ nt_status = make_user_info_map(
+ mem_ctx,
+ user_info,
+ smb_name, client_domain, workstation_name,
+ remote_address,
+ local_address,
+ "SamLogon",
+ lm_interactive_pwd ? &local_lm_blob : NULL,
+ nt_interactive_pwd ? &local_nt_blob : NULL,
+ lm_interactive_pwd ? &lm_pwd : NULL,
+ nt_interactive_pwd ? &nt_pwd : NULL,
+ NULL, AUTH_PASSWORD_HASH);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ (*user_info)->logon_parameters = logon_parameters;
+ (*user_info)->flags |= USER_INFO_INTERACTIVE_LOGON;
+ }
+
+ ret = NT_STATUS_IS_OK(nt_status) ? true : false;
+ data_blob_free(&local_lm_blob);
+ data_blob_free(&local_nt_blob);
+ return ret;
+ }
+}
+
+
+/****************************************************************************
+ Create an auth_usersupplied_data structure
+****************************************************************************/
+
+bool make_user_info_for_reply(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ const uint8_t chal[8],
+ DATA_BLOB plaintext_password)
+{
+
+ DATA_BLOB local_lm_blob;
+ DATA_BLOB local_nt_blob;
+ NTSTATUS ret;
+ char *plaintext_password_string;
+ /*
+ * Not encrypted - do so.
+ */
+
+ DEBUG(5,("make_user_info_for_reply: User passwords not in encrypted "
+ "format.\n"));
+ if (plaintext_password.data && plaintext_password.length) {
+ unsigned char local_lm_response[24];
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(10,("Unencrypted password (len %d):\n",
+ (int)plaintext_password.length));
+ dump_data(100, plaintext_password.data,
+ plaintext_password.length);
+#endif
+
+ SMBencrypt( (const char *)plaintext_password.data,
+ (const uchar*)chal, local_lm_response);
+ local_lm_blob = data_blob(local_lm_response, 24);
+
+ /* We can't do an NT hash here, as the password needs to be
+ case insensitive */
+ local_nt_blob = data_blob_null;
+ } else {
+ local_lm_blob = data_blob_null;
+ local_nt_blob = data_blob_null;
+ }
+
+ plaintext_password_string = talloc_strndup(talloc_tos(),
+ (const char *)plaintext_password.data,
+ plaintext_password.length);
+ if (!plaintext_password_string) {
+ return false;
+ }
+
+ ret = make_user_info(mem_ctx,
+ user_info, smb_name, smb_name, client_domain, client_domain,
+ get_remote_machine_name(),
+ remote_address,
+ local_address,
+ service_description,
+ local_lm_blob.data ? &local_lm_blob : NULL,
+ local_nt_blob.data ? &local_nt_blob : NULL,
+ NULL, NULL,
+ plaintext_password_string,
+ AUTH_PASSWORD_PLAIN);
+
+ if (plaintext_password_string) {
+ memset(plaintext_password_string, '\0', strlen(plaintext_password_string));
+ talloc_free(plaintext_password_string);
+ }
+
+ data_blob_free(&local_lm_blob);
+ return NT_STATUS_IS_OK(ret) ? true : false;
+}
+
+/****************************************************************************
+ Create an auth_usersupplied_data structure
+****************************************************************************/
+
+NTSTATUS make_user_info_for_reply_enc(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ DATA_BLOB lm_resp, DATA_BLOB nt_resp)
+{
+ bool allow_raw = lp_raw_ntlmv2_auth();
+
+ if (!allow_raw && nt_resp.length >= 48) {
+ /*
+ * NTLMv2_RESPONSE has at least 48 bytes
+ * and should only be supported via NTLMSSP.
+ */
+ DEBUG(2,("Rejecting raw NTLMv2 authentication with "
+ "user [%s\\%s] from[%s]\n",
+ client_domain, smb_name,
+ tsocket_address_string(remote_address, mem_ctx)));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return make_user_info(mem_ctx,
+ user_info, smb_name, smb_name,
+ client_domain, client_domain,
+ get_remote_machine_name(),
+ remote_address,
+ local_address,
+ service_description,
+ lm_resp.data && (lm_resp.length > 0) ? &lm_resp : NULL,
+ nt_resp.data && (nt_resp.length > 0) ? &nt_resp : NULL,
+ NULL, NULL, NULL,
+ AUTH_PASSWORD_RESPONSE);
+}
+
+/****************************************************************************
+ Create a guest user_info blob, for anonymous authentication.
+****************************************************************************/
+
+bool make_user_info_guest(TALLOC_CTX *mem_ctx,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ struct auth_usersupplied_info **user_info)
+{
+ NTSTATUS nt_status;
+
+ nt_status = make_user_info(mem_ctx,
+ user_info,
+ "","",
+ "","",
+ "",
+ remote_address,
+ local_address,
+ service_description,
+ NULL, NULL,
+ NULL, NULL,
+ NULL,
+ AUTH_PASSWORD_RESPONSE);
+
+ return NT_STATUS_IS_OK(nt_status) ? true : false;
+}
+
+static NTSTATUS log_nt_token(struct security_token *token)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *command;
+ char *group_sidstr;
+ struct dom_sid_buf buf;
+ size_t i;
+
+ if ((lp_log_nt_token_command(frame, lp_sub) == NULL) ||
+ (strlen(lp_log_nt_token_command(frame, lp_sub)) == 0)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ group_sidstr = talloc_strdup(frame, "");
+ for (i=1; i<token->num_sids; i++) {
+ group_sidstr = talloc_asprintf(
+ frame, "%s %s", group_sidstr,
+ dom_sid_str_buf(&token->sids[i], &buf));
+ }
+
+ command = talloc_string_sub(
+ frame, lp_log_nt_token_command(frame, lp_sub),
+ "%s", dom_sid_str_buf(&token->sids[0], &buf));
+ command = talloc_string_sub(frame, command, "%t", group_sidstr);
+
+ if (command == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(8, ("running command: [%s]\n", command));
+ if (smbrun(command, NULL, NULL) != 0) {
+ DEBUG(0, ("Could not log NT token\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Create the token to use from server_info->info3 and
+ * server_info->sids (the info3/sam groups). Find the unix gids.
+ */
+
+NTSTATUS create_local_token(TALLOC_CTX *mem_ctx,
+ const struct auth_serversupplied_info *server_info,
+ DATA_BLOB *session_key,
+ const char *smb_username, /* for ->sanitized_username, for %U subs */
+ struct auth_session_info **session_info_out)
+{
+ struct security_token *t;
+ NTSTATUS status;
+ size_t i;
+ struct dom_sid tmp_sid;
+ struct auth_session_info *session_info = NULL;
+ struct unixid *ids;
+ bool is_allowed = false;
+
+ /* Ensure we can't possible take a code path leading to a
+ * null deref. */
+ if (!server_info) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ if (is_allowed_domain(server_info->info3->base.logon_domain.string)) {
+ is_allowed = true;
+ }
+
+ /* Check if we have extra info about the user. */
+ if (dom_sid_in_domain(&global_sid_Unix_Users,
+ &server_info->extra.user_sid) ||
+ dom_sid_in_domain(&global_sid_Unix_Groups,
+ &server_info->extra.pgid_sid))
+ {
+ is_allowed = true;
+ }
+
+ if (!is_allowed) {
+ DBG_NOTICE("Authentication failed for user [%s] "
+ "from firewalled domain [%s]\n",
+ server_info->info3->base.account_name.string,
+ server_info->info3->base.logon_domain.string);
+ return NT_STATUS_AUTHENTICATION_FIREWALL_FAILED;
+ }
+
+ if (server_info->cached_session_info != NULL) {
+ session_info = copy_session_info(mem_ctx,
+ server_info->cached_session_info);
+ if (session_info == NULL) {
+ goto nomem;
+ }
+
+ /* This is a potentially untrusted username for use in %U */
+ session_info->unix_info->sanitized_username =
+ talloc_alpha_strcpy(session_info->unix_info,
+ smb_username,
+ SAFE_NETBIOS_CHARS "$");
+ if (session_info->unix_info->sanitized_username == NULL) {
+ goto nomem;
+ }
+
+ session_info->unique_session_token = GUID_random();
+
+ *session_info_out = session_info;
+ return NT_STATUS_OK;
+ }
+
+ session_info = talloc_zero(mem_ctx, struct auth_session_info);
+ if (!session_info) {
+ goto nomem;
+ }
+
+ session_info->unix_token = talloc_zero(session_info, struct security_unix_token);
+ if (!session_info->unix_token) {
+ goto nomem;
+ }
+
+ session_info->unix_token->uid = server_info->utok.uid;
+ session_info->unix_token->gid = server_info->utok.gid;
+
+ session_info->unix_info = talloc_zero(session_info, struct auth_user_info_unix);
+ if (!session_info->unix_info) {
+ goto nomem;
+ }
+
+ session_info->unix_info->unix_name = talloc_strdup(session_info, server_info->unix_name);
+ if (!session_info->unix_info->unix_name) {
+ goto nomem;
+ }
+
+ /* This is a potentially untrusted username for use in %U */
+ session_info->unix_info->sanitized_username =
+ talloc_alpha_strcpy(session_info->unix_info,
+ smb_username,
+ SAFE_NETBIOS_CHARS "$");
+ if (session_info->unix_info->sanitized_username == NULL) {
+ goto nomem;
+ }
+
+ if (session_key) {
+ data_blob_free(&session_info->session_key);
+ session_info->session_key = data_blob_talloc(session_info,
+ session_key->data,
+ session_key->length);
+ if (!session_info->session_key.data && session_key->length) {
+ goto nomem;
+ }
+ } else {
+ session_info->session_key = data_blob_talloc( session_info, server_info->session_key.data,
+ server_info->session_key.length);
+ }
+
+ /* We need to populate session_info->info with the information found in server_info->info3 */
+ status = make_user_info_SamBaseInfo(session_info, "", &server_info->info3->base,
+ server_info->guest == false,
+ &session_info->info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("conversion of info3 into auth_user_info failed!\n"));
+ goto fail;
+ }
+
+ /*
+ * If the user name was mapped to some local unix user,
+ * we can not make much use of the SIDs the
+ * domain controller provided us with.
+ */
+ if (server_info->nss_token) {
+ char *found_username = NULL;
+ status = create_token_from_username(session_info,
+ server_info->unix_name,
+ server_info->guest,
+ &session_info->unix_token->uid,
+ &session_info->unix_token->gid,
+ &found_username,
+ &session_info->security_token);
+ if (NT_STATUS_IS_OK(status)) {
+ session_info->unix_info->unix_name = found_username;
+ }
+ } else {
+ status = create_local_nt_token_from_info3(session_info,
+ server_info->guest,
+ server_info->info3,
+ &server_info->extra,
+ &session_info->security_token);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* Convert the SIDs to gids. */
+
+ session_info->unix_token->ngroups = 0;
+ session_info->unix_token->groups = NULL;
+
+ t = session_info->security_token;
+
+ ids = talloc_array(talloc_tos(), struct unixid,
+ t->num_sids);
+ if (ids == NULL) {
+ goto nomem;
+ }
+
+ if (!sids_to_unixids(t->sids, t->num_sids, ids)) {
+ goto nomem;
+ }
+
+ for (i=0; i<t->num_sids; i++) {
+
+ if (i == 0 && ids[i].type != ID_TYPE_BOTH) {
+ continue;
+ }
+
+ if (ids[i].type != ID_TYPE_GID &&
+ ids[i].type != ID_TYPE_BOTH) {
+ struct dom_sid_buf buf;
+ DEBUG(10, ("Could not convert SID %s to gid, "
+ "ignoring it\n",
+ dom_sid_str_buf(&t->sids[i], &buf)));
+ continue;
+ }
+ if (!add_gid_to_array_unique(session_info->unix_token,
+ ids[i].id,
+ &session_info->unix_token->groups,
+ &session_info->unix_token->ngroups)) {
+ goto nomem;
+ }
+ }
+
+ /*
+ * Add the "Unix Group" SID for each gid to catch mapped groups
+ * and their Unix equivalent. This is to solve the backwards
+ * compatibility problem of 'valid users = +ntadmin' where
+ * ntadmin has been paired with "Domain Admins" in the group
+ * mapping table. Otherwise smb.conf would need to be changed
+ * to 'valid user = "Domain Admins"'. --jerry
+ *
+ * For consistency we also add the "Unix User" SID,
+ * so that the complete unix token is represented within
+ * the nt token.
+ */
+
+ uid_to_unix_users_sid(session_info->unix_token->uid, &tmp_sid);
+ status = add_sid_to_array_unique(
+ session_info->security_token,
+ &tmp_sid,
+ &session_info->security_token->sids,
+ &session_info->security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ gid_to_unix_groups_sid(session_info->unix_token->gid, &tmp_sid);
+ status = add_sid_to_array_unique(
+ session_info->security_token,
+ &tmp_sid,
+ &session_info->security_token->sids,
+ &session_info->security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ for ( i=0; i<session_info->unix_token->ngroups; i++ ) {
+ gid_to_unix_groups_sid(session_info->unix_token->groups[i], &tmp_sid);
+ status = add_sid_to_array_unique(
+ session_info->security_token,
+ &tmp_sid,
+ &session_info->security_token->sids,
+ &session_info->security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ security_token_debug(DBGC_AUTH, 10, session_info->security_token);
+ debug_unix_user_token(DBGC_AUTH, 10,
+ session_info->unix_token->uid,
+ session_info->unix_token->gid,
+ session_info->unix_token->ngroups,
+ session_info->unix_token->groups);
+
+ status = log_nt_token(session_info->security_token);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ session_info->unique_session_token = GUID_random();
+
+ *session_info_out = session_info;
+ return NT_STATUS_OK;
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ TALLOC_FREE(session_info);
+ return status;
+}
+
+NTSTATUS auth3_user_info_dc_add_hints(struct auth_user_info_dc *user_info_dc,
+ uid_t uid,
+ gid_t gid,
+ uint32_t flags)
+{
+ uint32_t orig_num_sids = user_info_dc->num_sids;
+ struct dom_sid tmp_sid = { 0, };
+ NTSTATUS status;
+
+ /*
+ * We add S-5-88-1-X in order to pass the uid
+ * for the unix token.
+ */
+ sid_compose(&tmp_sid,
+ &global_sid_Unix_NFS_Users,
+ (uint32_t)uid);
+ status = add_sid_to_array_attrs_unique(user_info_dc->sids,
+ &tmp_sid,
+ SE_GROUP_DEFAULT_FLAGS,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("add_sid_to_array_unique failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ /*
+ * We add S-5-88-2-X in order to pass the gid
+ * for the unix token.
+ */
+ sid_compose(&tmp_sid,
+ &global_sid_Unix_NFS_Groups,
+ (uint32_t)gid);
+ status = add_sid_to_array_attrs_unique(user_info_dc->sids,
+ &tmp_sid,
+ SE_GROUP_DEFAULT_FLAGS,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("add_sid_to_array_unique failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ /*
+ * We add S-5-88-3-X in order to pass some flags
+ * (AUTH3_UNIX_HINT_*) to auth3_create_session_info().
+ */
+ sid_compose(&tmp_sid,
+ &global_sid_Unix_NFS_Mode,
+ flags);
+ status = add_sid_to_array_attrs_unique(user_info_dc->sids,
+ &tmp_sid,
+ SE_GROUP_DEFAULT_FLAGS,
+ &user_info_dc->sids,
+ &user_info_dc->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("add_sid_to_array_unique failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ return NT_STATUS_OK;
+
+fail:
+ user_info_dc->num_sids = orig_num_sids;
+ return status;
+}
+
+static NTSTATUS auth3_session_info_create(
+ TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ const char *original_user_name,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info_out)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct auth_session_info *session_info = NULL;
+ uid_t hint_uid = -1;
+ bool found_hint_uid = false;
+ uid_t hint_gid = -1;
+ bool found_hint_gid = false;
+ uint32_t hint_flags = 0;
+ bool found_hint_flags = false;
+ bool need_getpwuid = false;
+ struct unixid *ids = NULL;
+ uint32_t num_gids = 0;
+ gid_t *gids = NULL;
+ struct dom_sid tmp_sid = { 0, };
+ NTSTATUS status;
+ size_t i;
+ bool ok;
+
+ *session_info_out = NULL;
+
+ if (user_info_dc->num_sids == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ if (user_info_dc->info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ if (user_info_dc->info->account_name == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ session_info = talloc_zero(mem_ctx, struct auth_session_info);
+ if (session_info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ /* keep this under frame for easier cleanup */
+ talloc_reparent(mem_ctx, frame, session_info);
+
+ session_info->info = auth_user_info_copy(session_info,
+ user_info_dc->info);
+ if (session_info->info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ session_info->security_token = talloc_zero(session_info,
+ struct security_token);
+ if (session_info->security_token == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Avoid a lot of reallocations and allocate what we'll
+ * use in most cases.
+ */
+ session_info->security_token->sids = talloc_zero_array(
+ session_info->security_token,
+ struct dom_sid,
+ user_info_dc->num_sids);
+ if (session_info->security_token->sids == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = PRIMARY_USER_SID_INDEX; i < user_info_dc->num_sids; i++) {
+ struct security_token *nt_token = session_info->security_token;
+ int cmp;
+
+ /*
+ * S-1-5-88-X-Y sids are only used to give hints
+ * to the unix token construction.
+ *
+ * S-1-5-88-1-Y gives the uid=Y
+ * S-1-5-88-2-Y gives the gid=Y
+ * S-1-5-88-3-Y gives flags=Y: AUTH3_UNIX_HINT_*
+ */
+ cmp = dom_sid_compare_domain(&global_sid_Unix_NFS,
+ &user_info_dc->sids[i].sid);
+ if (cmp == 0) {
+ bool match;
+ uint32_t hint = 0;
+
+ match = sid_peek_rid(&user_info_dc->sids[i].sid, &hint);
+ if (!match) {
+ continue;
+ }
+
+ match = dom_sid_in_domain(&global_sid_Unix_NFS_Users,
+ &user_info_dc->sids[i].sid);
+ if (match) {
+ if (found_hint_uid) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+ found_hint_uid = true;
+ hint_uid = (uid_t)hint;
+ continue;
+ }
+
+ match = dom_sid_in_domain(&global_sid_Unix_NFS_Groups,
+ &user_info_dc->sids[i].sid);
+ if (match) {
+ if (found_hint_gid) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+ found_hint_gid = true;
+ hint_gid = (gid_t)hint;
+ continue;
+ }
+
+ match = dom_sid_in_domain(&global_sid_Unix_NFS_Mode,
+ &user_info_dc->sids[i].sid);
+ if (match) {
+ if (found_hint_flags) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+ found_hint_flags = true;
+ hint_flags = hint;
+ continue;
+ }
+
+ continue;
+ }
+
+ status = add_sid_to_array_unique(nt_token->sids,
+ &user_info_dc->sids[i].sid,
+ &nt_token->sids,
+ &nt_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ /*
+ * We need at least one usable SID
+ */
+ if (session_info->security_token->num_sids == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ /*
+ * We need all tree hints: uid, gid, flags
+ * or none of them.
+ */
+ if (found_hint_uid || found_hint_gid || found_hint_flags) {
+ if (!found_hint_uid) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ if (!found_hint_gid) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ if (!found_hint_flags) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+ }
+
+ if (!(user_info_dc->info->user_flags & NETLOGON_GUEST)) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ status = finalize_local_nt_token(session_info->security_token,
+ session_info_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * unless set otherwise, the session key is the user session
+ * key from the auth subsystem
+ */
+ if (user_info_dc->user_session_key.length != 0) {
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ user_info_dc->user_session_key);
+ if (session_info->session_key.data == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!(session_info_flags & AUTH_SESSION_INFO_UNIX_TOKEN)) {
+ goto done;
+ }
+
+ session_info->unix_token = talloc_zero(session_info, struct security_unix_token);
+ if (session_info->unix_token == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ session_info->unix_token->uid = -1;
+ session_info->unix_token->gid = -1;
+
+ session_info->unix_info = talloc_zero(session_info, struct auth_user_info_unix);
+ if (session_info->unix_info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Convert the SIDs to uid/gids. */
+
+ ids = talloc_zero_array(frame, struct unixid,
+ session_info->security_token->num_sids);
+ if (ids == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!(hint_flags & AUTH3_UNIX_HINT_DONT_TRANSLATE_FROM_SIDS)) {
+ ok = sids_to_unixids(session_info->security_token->sids,
+ session_info->security_token->num_sids,
+ ids);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (found_hint_uid) {
+ session_info->unix_token->uid = hint_uid;
+ } else if (ids[0].type == ID_TYPE_UID) {
+ /*
+ * The primary SID resolves to a UID only.
+ */
+ session_info->unix_token->uid = ids[0].id;
+ } else if (ids[0].type == ID_TYPE_BOTH) {
+ /*
+ * The primary SID resolves to a UID and GID,
+ * use it as uid and add it as first element
+ * to the groups array.
+ */
+ session_info->unix_token->uid = ids[0].id;
+
+ ok = add_gid_to_array_unique(session_info->unix_token,
+ session_info->unix_token->uid,
+ &session_info->unix_token->groups,
+ &session_info->unix_token->ngroups);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ /*
+ * It we can't get a uid, we can't imporsonate
+ * the user.
+ */
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ if (found_hint_gid) {
+ session_info->unix_token->gid = hint_gid;
+ } else {
+ need_getpwuid = true;
+ }
+
+ if (hint_flags & AUTH3_UNIX_HINT_QUALIFIED_NAME) {
+ session_info->unix_info->unix_name =
+ talloc_asprintf(session_info->unix_info,
+ "%s%c%s",
+ session_info->info->domain_name,
+ *lp_winbind_separator(),
+ session_info->info->account_name);
+ if (session_info->unix_info->unix_name == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else if (hint_flags & AUTH3_UNIX_HINT_ISLOLATED_NAME) {
+ session_info->unix_info->unix_name =
+ talloc_strdup(session_info->unix_info,
+ session_info->info->account_name);
+ if (session_info->unix_info->unix_name == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ need_getpwuid = true;
+ }
+
+ if (need_getpwuid) {
+ struct passwd *pwd = NULL;
+
+ /*
+ * Ask the system for the primary gid
+ * and the real unix name.
+ */
+ pwd = getpwuid_alloc(frame, session_info->unix_token->uid);
+ if (pwd == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+ if (!found_hint_gid) {
+ session_info->unix_token->gid = pwd->pw_gid;
+ }
+
+ session_info->unix_info->unix_name =
+ talloc_strdup(session_info->unix_info, pwd->pw_name);
+ if (session_info->unix_info->unix_name == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ TALLOC_FREE(pwd);
+ }
+
+ ok = add_gid_to_array_unique(session_info->unix_token,
+ session_info->unix_token->gid,
+ &session_info->unix_token->groups,
+ &session_info->unix_token->ngroups);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* This is a potentially untrusted username for use in %U */
+ session_info->unix_info->sanitized_username =
+ talloc_alpha_strcpy(session_info->unix_info,
+ original_user_name,
+ SAFE_NETBIOS_CHARS "$");
+ if (session_info->unix_info->sanitized_username == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i < session_info->security_token->num_sids; i++) {
+
+ if (ids[i].type != ID_TYPE_GID &&
+ ids[i].type != ID_TYPE_BOTH) {
+ struct security_token *nt_token =
+ session_info->security_token;
+ struct dom_sid_buf buf;
+
+ DEBUG(10, ("Could not convert SID %s to gid, "
+ "ignoring it\n",
+ dom_sid_str_buf(&nt_token->sids[i], &buf)));
+ continue;
+ }
+
+ ok = add_gid_to_array_unique(session_info->unix_token,
+ ids[i].id,
+ &session_info->unix_token->groups,
+ &session_info->unix_token->ngroups);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ TALLOC_FREE(ids);
+
+ /*
+ * Now we must get any groups this user has been
+ * added to in /etc/group and merge them in.
+ * This has to be done in every code path
+ * that creates an NT token, as remote users
+ * may have been added to the local /etc/group
+ * database. Tokens created merely from the
+ * info3 structs (via the DC or via the krb5 PAC)
+ * won't have these local groups. Note the
+ * groups added here will only be UNIX groups
+ * (S-1-22-2-XXXX groups) as getgroups_unix_user()
+ * turns off winbindd before calling getgroups().
+ *
+ * NB. This is duplicating work already
+ * done in the 'unix_user:' case of
+ * create_token_from_sid() but won't
+ * do anything other than be inefficient
+ * in that case.
+ */
+ if (!(hint_flags & AUTH3_UNIX_HINT_DONT_EXPAND_UNIX_GROUPS)) {
+ ok = getgroups_unix_user(frame,
+ session_info->unix_info->unix_name,
+ session_info->unix_token->gid,
+ &gids, &num_gids);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_TOKEN;
+ }
+ }
+
+ for (i=0; i < num_gids; i++) {
+
+ ok = add_gid_to_array_unique(session_info->unix_token,
+ gids[i],
+ &session_info->unix_token->groups,
+ &session_info->unix_token->ngroups);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ TALLOC_FREE(gids);
+
+ if (hint_flags & AUTH3_UNIX_HINT_DONT_TRANSLATE_TO_SIDS) {
+ /*
+ * We should not translate the unix token uid/gids
+ * to S-1-22-X-Y SIDs.
+ */
+ goto done;
+ }
+
+ /*
+ * Add the "Unix Group" SID for each gid to catch mapped groups
+ * and their Unix equivalent. This is to solve the backwards
+ * compatibility problem of 'valid users = +ntadmin' where
+ * ntadmin has been paired with "Domain Admins" in the group
+ * mapping table. Otherwise smb.conf would need to be changed
+ * to 'valid user = "Domain Admins"'. --jerry
+ *
+ * For consistency we also add the "Unix User" SID,
+ * so that the complete unix token is represented within
+ * the nt token.
+ */
+
+ uid_to_unix_users_sid(session_info->unix_token->uid, &tmp_sid);
+ status = add_sid_to_array_unique(session_info->security_token, &tmp_sid,
+ &session_info->security_token->sids,
+ &session_info->security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ gid_to_unix_groups_sid(session_info->unix_token->gid, &tmp_sid);
+ status = add_sid_to_array_unique(session_info->security_token, &tmp_sid,
+ &session_info->security_token->sids,
+ &session_info->security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ for (i=0; i < session_info->unix_token->ngroups; i++ ) {
+ struct security_token *nt_token = session_info->security_token;
+
+ gid_to_unix_groups_sid(session_info->unix_token->groups[i],
+ &tmp_sid);
+ status = add_sid_to_array_unique(nt_token->sids,
+ &tmp_sid,
+ &nt_token->sids,
+ &nt_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+done:
+ security_token_debug(DBGC_AUTH, 10, session_info->security_token);
+ if (session_info->unix_token != NULL) {
+ debug_unix_user_token(DBGC_AUTH, 10,
+ session_info->unix_token->uid,
+ session_info->unix_token->gid,
+ session_info->unix_token->ngroups,
+ session_info->unix_token->groups);
+ }
+
+ status = log_nt_token(session_info->security_token);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ session_info->unique_session_token = GUID_random();
+
+ *session_info_out = talloc_move(mem_ctx, &session_info);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ Make (and fill) a server_info struct from a 'struct passwd' by conversion
+ to a struct samu
+***************************************************************************/
+
+NTSTATUS make_server_info_pw(TALLOC_CTX *mem_ctx,
+ const char *unix_username,
+ const struct passwd *pwd,
+ struct auth_serversupplied_info **server_info)
+{
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct auth_serversupplied_info *result;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result = make_server_info(tmp_ctx);
+ if (result == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = passwd_to_SamInfo3(result,
+ unix_username,
+ pwd,
+ &result->info3,
+ &result->extra);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ result->unix_name = talloc_strdup(result, unix_username);
+ if (result->unix_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ result->utok.uid = pwd->pw_uid;
+ result->utok.gid = pwd->pw_gid;
+
+ *server_info = talloc_move(mem_ctx, &result);
+ status = NT_STATUS_OK;
+done:
+ talloc_free(tmp_ctx);
+
+ return status;
+}
+
+static NTSTATUS get_guest_info3(TALLOC_CTX *mem_ctx,
+ struct netr_SamInfo3 *info3)
+{
+ const char *guest_account = lp_guest_account();
+ struct dom_sid domain_sid;
+ struct passwd *pwd;
+ const char *tmp;
+
+ pwd = Get_Pwnam_alloc(mem_ctx, guest_account);
+ if (pwd == NULL) {
+ DEBUG(0,("SamInfo3_for_guest: Unable to locate guest "
+ "account [%s]!\n", guest_account));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /* Set account name */
+ tmp = talloc_strdup(mem_ctx, pwd->pw_name);
+ if (tmp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ init_lsa_String(&info3->base.account_name, tmp);
+
+ /* Set domain name */
+ tmp = talloc_strdup(mem_ctx, get_global_sam_name());
+ if (tmp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ init_lsa_StringLarge(&info3->base.logon_domain, tmp);
+
+ /* Domain sid */
+ sid_copy(&domain_sid, get_global_sam_sid());
+
+ info3->base.domain_sid = dom_sid_dup(mem_ctx, &domain_sid);
+ if (info3->base.domain_sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Guest rid */
+ info3->base.rid = DOMAIN_RID_GUEST;
+
+ /* Primary gid */
+ info3->base.primary_gid = DOMAIN_RID_GUESTS;
+
+ /* Set as guest */
+ info3->base.user_flags = NETLOGON_GUEST;
+
+ TALLOC_FREE(pwd);
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ Make (and fill) a user_info struct for a guest login.
+ This *must* succeed for smbd to start. If there is no mapping entry for
+ the guest gid, then create one.
+
+ The resulting structure is a 'session_info' because
+ create_local_token() has already been called on it. This is quite
+ nasty, as the auth subsystem isn't expect this, but the behavior is
+ left as-is for now.
+***************************************************************************/
+
+static NTSTATUS make_new_session_info_guest(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **_session_info,
+ struct auth_serversupplied_info **_server_info)
+{
+ struct auth_session_info *session_info = NULL;
+ struct auth_serversupplied_info *server_info = NULL;
+ const char *guest_account = lp_guest_account();
+ const char *domain = lp_netbios_name();
+ struct netr_SamInfo3 info3;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(info3);
+
+ status = get_guest_info3(tmp_ctx, &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("get_guest_info3 failed with %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = make_server_info_info3(tmp_ctx,
+ guest_account,
+ domain,
+ &server_info,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("make_server_info_info3 failed with %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ server_info->guest = true;
+
+ /* This should not be done here (we should produce a server
+ * info, and later construct a session info from it), but for
+ * now this does not change the previous behavior */
+ status = create_local_token(tmp_ctx, server_info, NULL,
+ server_info->info3->base.account_name.string,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("create_local_token failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /*
+ * It's ugly, but for now it's
+ * needed to force Builtin_Guests
+ * here, because memberships of
+ * Builtin_Guests might be incomplete.
+ */
+ status = add_sid_to_array_unique(session_info->security_token,
+ &global_sid_Builtin_Guests,
+ &session_info->security_token->sids,
+ &session_info->security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to force Builtin_Guests to nt token\n");
+ goto done;
+ }
+
+ /* annoying, but the Guest really does have a session key, and it is
+ all zeros! */
+ session_info->session_key = data_blob_talloc_zero(session_info, 16);
+
+ *_session_info = talloc_move(mem_ctx, &session_info);
+ *_server_info = talloc_move(mem_ctx, &server_info);
+
+ status = NT_STATUS_OK;
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/***************************************************************************
+ Make (and fill) a auth_session_info struct for a system user login.
+ This *must* succeed for smbd to start.
+***************************************************************************/
+
+static NTSTATUS make_new_session_info_system(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct auth_user_info_dc *user_info_dc = NULL;
+ uid_t uid = -1;
+ gid_t gid = -1;
+ uint32_t hint_flags = 0;
+ uint32_t session_info_flags = 0;
+ NTSTATUS status;
+
+ status = auth_system_user_info_dc(frame, lp_netbios_name(),
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("auth_system_user_info_dc failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /*
+ * Just get the initial uid/gid
+ * and don't expand the unix groups.
+ */
+ uid = sec_initial_uid();
+ gid = sec_initial_gid();
+ hint_flags |= AUTH3_UNIX_HINT_DONT_EXPAND_UNIX_GROUPS;
+
+ /*
+ * Also avoid sid mapping to gids,
+ * as well as adding the unix_token uid/gids as
+ * S-1-22-X-Y SIDs to the nt token.
+ */
+ hint_flags |= AUTH3_UNIX_HINT_DONT_TRANSLATE_FROM_SIDS;
+ hint_flags |= AUTH3_UNIX_HINT_DONT_TRANSLATE_TO_SIDS;
+
+ /*
+ * The unix name will be "NT AUTHORITY+SYSTEM",
+ * where '+' is the "winbind separator" character.
+ */
+ hint_flags |= AUTH3_UNIX_HINT_QUALIFIED_NAME;
+ status = auth3_user_info_dc_add_hints(user_info_dc,
+ uid,
+ gid,
+ hint_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("auth3_user_info_dc_add_hints failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES;
+ session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN;
+ status = auth3_session_info_create(mem_ctx, user_info_dc,
+ user_info_dc->info->account_name,
+ session_info_flags,
+ session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("auth3_session_info_create failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS make_new_session_info_anonymous(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *guest_account = lp_guest_account();
+ struct auth_user_info_dc *user_info_dc = NULL;
+ struct passwd *pwd = NULL;
+ uint32_t hint_flags = 0;
+ uint32_t session_info_flags = 0;
+ NTSTATUS status;
+
+ /*
+ * We use the guest account for the unix token
+ * while we use a true anonymous nt token.
+ *
+ * It's very important to have a separate
+ * nt token for anonymous.
+ */
+
+ pwd = Get_Pwnam_alloc(frame, guest_account);
+ if (pwd == NULL) {
+ DBG_ERR("Unable to locate guest account [%s]!\n",
+ guest_account);
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ status = auth_anonymous_user_info_dc(frame, lp_netbios_name(),
+ &user_info_dc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("auth_anonymous_user_info_dc failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /*
+ * Note we don't pass AUTH3_UNIX_HINT_QUALIFIED_NAME
+ * nor AUTH3_UNIX_HINT_ISOLATED_NAME here
+ * as we want the unix name be found by getpwuid_alloc().
+ */
+
+ status = auth3_user_info_dc_add_hints(user_info_dc,
+ pwd->pw_uid,
+ pwd->pw_gid,
+ hint_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("auth3_user_info_dc_add_hints failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /*
+ * In future we may want to remove
+ * AUTH_SESSION_INFO_DEFAULT_GROUPS.
+ *
+ * Similar to Windows with EveryoneIncludesAnonymous
+ * and RestrictAnonymous.
+ *
+ * We may introduce AUTH_SESSION_INFO_ANON_WORLD...
+ *
+ * But for this is required to keep the existing tests
+ * working.
+ */
+ session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS;
+ session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES;
+ session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN;
+ status = auth3_session_info_create(mem_ctx, user_info_dc,
+ "",
+ session_info_flags,
+ session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("auth3_session_info_create failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Fake a auth_session_info just from a username (as a
+ session_info structure, with create_local_token() already called on
+ it.
+****************************************************************************/
+
+NTSTATUS make_session_info_from_username(TALLOC_CTX *mem_ctx,
+ const char *username,
+ bool is_guest,
+ struct auth_session_info **session_info)
+{
+ struct passwd *pwd;
+ NTSTATUS status;
+ struct auth_serversupplied_info *result;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pwd = Get_Pwnam_alloc(tmp_ctx, username);
+ if (pwd == NULL) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ status = make_server_info_pw(tmp_ctx, pwd->pw_name, pwd, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ result->nss_token = true;
+ result->guest = is_guest;
+
+ /* Now turn the server_info into a session_info with the full token etc */
+ status = create_local_token(mem_ctx,
+ result,
+ NULL,
+ pwd->pw_name,
+ session_info);
+
+done:
+ talloc_free(tmp_ctx);
+
+ return status;
+}
+
+/* This function MUST only used to create the cached server_info for
+ * guest.
+ *
+ * This is a lossy conversion. Variables known to be lost so far
+ * include:
+ *
+ * - nss_token (not needed because the only read doesn't happen
+ * for the GUEST user, as this routine populates ->security_token
+ *
+ * - extra (not needed because the guest account must have valid RIDs per the output of get_guest_info3())
+ *
+ * - The 'server_info' parameter allows the missing 'info3' to be copied across.
+ */
+static struct auth_serversupplied_info *copy_session_info_serverinfo_guest(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *src,
+ struct auth_serversupplied_info *server_info)
+{
+ struct auth_serversupplied_info *dst;
+ NTSTATUS status;
+
+ dst = make_server_info(mem_ctx);
+ if (dst == NULL) {
+ return NULL;
+ }
+
+ /* This element must be provided to convert back to an auth_serversupplied_info */
+ SMB_ASSERT(src->unix_info);
+
+ dst->guest = true;
+
+ /* This element must be provided to convert back to an
+ * auth_serversupplied_info. This needs to be from the
+ * auth_session_info because the group values in particular
+ * may change during create_local_token() processing */
+ SMB_ASSERT(src->unix_token);
+ dst->utok.uid = src->unix_token->uid;
+ dst->utok.gid = src->unix_token->gid;
+ dst->utok.ngroups = src->unix_token->ngroups;
+ if (src->unix_token->ngroups != 0) {
+ dst->utok.groups = (gid_t *)talloc_memdup(
+ dst, src->unix_token->groups,
+ sizeof(gid_t)*dst->utok.ngroups);
+ } else {
+ dst->utok.groups = NULL;
+ }
+
+ /* We must have a security_token as otherwise the lossy
+ * conversion without nss_token would cause create_local_token
+ * to take the wrong path */
+ SMB_ASSERT(src->security_token);
+
+ dst->session_key = data_blob_talloc( dst, src->session_key.data,
+ src->session_key.length);
+
+ /* This is OK because this functions is only used for the
+ * GUEST account, which has all-zero keys for both values */
+ dst->lm_session_key = data_blob_talloc(dst, src->session_key.data,
+ src->session_key.length);
+
+ status = copy_netr_SamInfo3(dst,
+ server_info->info3,
+ &dst->info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(dst);
+ return NULL;
+ }
+
+ dst->unix_name = talloc_strdup(dst, src->unix_info->unix_name);
+ if (!dst->unix_name) {
+ TALLOC_FREE(dst);
+ return NULL;
+ }
+
+ dst->cached_session_info = src;
+ return dst;
+}
+
+/*
+ * Set a new session key. Used in the rpc server where we have to override the
+ * SMB level session key with SystemLibraryDTC
+ */
+
+bool session_info_set_session_key(struct auth_session_info *info,
+ DATA_BLOB session_key)
+{
+ TALLOC_FREE(info->session_key.data);
+
+ info->session_key = data_blob_talloc(
+ info, session_key.data, session_key.length);
+
+ return (info->session_key.data != NULL);
+}
+
+static struct auth_session_info *guest_info = NULL;
+static struct auth_session_info *anonymous_info = NULL;
+
+static struct auth_serversupplied_info *guest_server_info = NULL;
+
+bool init_guest_session_info(TALLOC_CTX *mem_ctx)
+{
+ NTSTATUS status;
+
+ if (guest_info != NULL)
+ return true;
+
+ status = make_new_session_info_guest(mem_ctx,
+ &guest_info,
+ &guest_server_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ status = make_new_session_info_anonymous(mem_ctx,
+ &anonymous_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool reinit_guest_session_info(TALLOC_CTX *mem_ctx)
+{
+ TALLOC_FREE(guest_info);
+ TALLOC_FREE(guest_server_info);
+ TALLOC_FREE(anonymous_info);
+
+ DBG_DEBUG("Reinitialing guest info\n");
+
+ return init_guest_session_info(mem_ctx);
+}
+
+NTSTATUS make_server_info_guest(TALLOC_CTX *mem_ctx,
+ struct auth_serversupplied_info **server_info)
+{
+ /* This is trickier than it would appear to need to be because
+ * we are trying to avoid certain costly operations when the
+ * structure is converted to a 'auth_session_info' again in
+ * create_local_token() */
+ *server_info = copy_session_info_serverinfo_guest(mem_ctx, guest_info, guest_server_info);
+ return (*server_info != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+}
+
+NTSTATUS make_session_info_guest(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info)
+{
+ *session_info = copy_session_info(mem_ctx, guest_info);
+ return (*session_info != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+}
+
+NTSTATUS make_server_info_anonymous(TALLOC_CTX *mem_ctx,
+ struct auth_serversupplied_info **server_info)
+{
+ if (anonymous_info == NULL) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /*
+ * This is trickier than it would appear to need to be because
+ * we are trying to avoid certain costly operations when the
+ * structure is converted to a 'auth_session_info' again in
+ * create_local_token()
+ *
+ * We use a guest server_info, but with the anonymous session info,
+ * which means create_local_token() will return a copy
+ * of the anonymous token.
+ *
+ * The server info is just used as legacy in order to
+ * keep existing code working. Maybe some debug messages
+ * will still refer to guest instead of anonymous.
+ */
+ *server_info = copy_session_info_serverinfo_guest(mem_ctx, anonymous_info,
+ guest_server_info);
+ if (*server_info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS make_session_info_anonymous(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info)
+{
+ if (anonymous_info == NULL) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *session_info = copy_session_info(mem_ctx, anonymous_info);
+ if (*session_info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static struct auth_session_info *system_info = NULL;
+
+NTSTATUS init_system_session_info(TALLOC_CTX *mem_ctx)
+{
+ if (system_info != NULL)
+ return NT_STATUS_OK;
+
+ return make_new_session_info_system(mem_ctx, &system_info);
+}
+
+NTSTATUS make_session_info_system(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info)
+{
+ if (system_info == NULL) return NT_STATUS_UNSUCCESSFUL;
+ *session_info = copy_session_info(mem_ctx, system_info);
+ return (*session_info != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+}
+
+const struct auth_session_info *get_session_info_system(void)
+{
+ return system_info;
+}
+
+/***************************************************************************
+ Purely internal function for make_server_info_info3
+***************************************************************************/
+
+static NTSTATUS check_account(TALLOC_CTX *mem_ctx, const char *domain,
+ const char *username,
+ const struct dom_sid *sid,
+ char **found_username,
+ struct passwd **pwd,
+ bool *username_was_mapped)
+{
+ char *orig_dom_user = NULL;
+ char *dom_user = NULL;
+ char *lower_username = NULL;
+ char *real_username = NULL;
+ struct passwd *passwd;
+
+ lower_username = talloc_strdup(mem_ctx, username);
+ if (!lower_username) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!strlower_m( lower_username )) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ orig_dom_user = talloc_asprintf(mem_ctx,
+ "%s%c%s",
+ domain,
+ *lp_winbind_separator(),
+ lower_username);
+ if (!orig_dom_user) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Get the passwd struct. Try to create the account if necessary. */
+
+ *username_was_mapped = map_username(mem_ctx, orig_dom_user, &dom_user);
+ if (!dom_user) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ passwd = smb_getpwnam(mem_ctx, dom_user, &real_username, false);
+ if (!passwd && !*username_was_mapped) {
+ struct dom_sid_buf buf;
+ uid_t uid;
+ bool ok;
+
+ DBG_DEBUG("Failed to find authenticated user %s via "
+ "getpwnam(), fallback to sid_to_uid(%s).\n",
+ dom_user, dom_sid_str_buf(sid, &buf));
+
+ ok = sid_to_uid(sid, &uid);
+ if (!ok) {
+ DBG_ERR("Failed to convert SID %s to a UID (dom_user[%s])\n",
+ dom_sid_str_buf(sid, &buf), dom_user);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ passwd = getpwuid_alloc(mem_ctx, uid);
+ if (!passwd) {
+ DBG_ERR("Failed to find local account with UID %lld for SID %s (dom_user[%s])\n",
+ (long long)uid,
+ dom_sid_str_buf(sid, &buf),
+ dom_user);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ real_username = talloc_strdup(mem_ctx, passwd->pw_name);
+ }
+ if (!passwd) {
+ DEBUG(3, ("Failed to find authenticated user %s via "
+ "getpwnam(), denying access.\n", dom_user));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (!real_username) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *pwd = passwd;
+
+ /* This is pointless -- there is no support for differing
+ unix and windows names. Make sure to always store the
+ one we actually looked up and succeeded. Have I mentioned
+ why I hate the 'winbind use default domain' parameter?
+ --jerry */
+
+ *found_username = talloc_strdup( mem_ctx, real_username );
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Wrapper to allow the getpwnam() call to strip the domain name and
+ try again in case a local UNIX user is already there. Also run through
+ the username if we fallback to the username only.
+ ****************************************************************************/
+
+struct passwd *smb_getpwnam( TALLOC_CTX *mem_ctx, const char *domuser,
+ char **p_save_username, bool create )
+{
+ struct passwd *pw = NULL;
+ char *p = NULL;
+ const char *username = NULL;
+
+ /* we only save a copy of the username it has been mangled
+ by winbindd use default domain */
+ *p_save_username = NULL;
+
+ /* don't call map_username() here since it has to be done higher
+ up the stack so we don't call it multiple times */
+
+ username = talloc_strdup(mem_ctx, domuser);
+ if (!username) {
+ return NULL;
+ }
+
+ p = strchr_m( username, *lp_winbind_separator() );
+
+ /* code for a DOMAIN\user string */
+
+ if ( p ) {
+ const char *domain = NULL;
+
+ /* split the domain and username into 2 strings */
+ *p = '\0';
+ domain = username;
+ p++;
+ username = p;
+
+ if (strequal(domain, get_global_sam_name())) {
+ /*
+ * This typically don't happen
+ * as check_sam_Security()
+ * don't call make_server_info_info3()
+ * and thus check_account().
+ *
+ * But we better keep this.
+ */
+ goto username_only;
+ }
+
+ pw = Get_Pwnam_alloc( mem_ctx, domuser );
+ if (pw == NULL) {
+ return NULL;
+ }
+ /* make sure we get the case of the username correct */
+ /* work around 'winbind use default domain = yes' */
+
+ if ( lp_winbind_use_default_domain() &&
+ !strchr_m( pw->pw_name, *lp_winbind_separator() ) ) {
+ *p_save_username = talloc_asprintf(mem_ctx,
+ "%s%c%s",
+ domain,
+ *lp_winbind_separator(),
+ pw->pw_name);
+ if (!*p_save_username) {
+ TALLOC_FREE(pw);
+ return NULL;
+ }
+ } else {
+ *p_save_username = talloc_strdup(mem_ctx, pw->pw_name);
+ }
+
+ /* whew -- done! */
+ return pw;
+
+ }
+
+ /* just lookup a plain username */
+username_only:
+ pw = Get_Pwnam_alloc(mem_ctx, username);
+
+ /* Create local user if requested but only if winbindd
+ is not running. We need to protect against cases
+ where winbindd is failing and then prematurely
+ creating users in /etc/passwd */
+
+ if ( !pw && create && !winbind_ping() ) {
+ /* Don't add a machine account. */
+ if (username[strlen(username)-1] == '$')
+ return NULL;
+
+ _smb_create_user(NULL, username, NULL);
+ pw = Get_Pwnam_alloc(mem_ctx, username);
+ }
+
+ /* one last check for a valid passwd struct */
+
+ if (pw) {
+ *p_save_username = talloc_strdup(mem_ctx, pw->pw_name);
+ }
+ return pw;
+}
+
+/***************************************************************************
+ Make a server_info struct from the info3 returned by a domain logon
+***************************************************************************/
+
+NTSTATUS make_server_info_info3(TALLOC_CTX *mem_ctx,
+ const char *sent_nt_username,
+ const char *domain,
+ struct auth_serversupplied_info **server_info,
+ const struct netr_SamInfo3 *info3)
+{
+ NTSTATUS nt_status;
+ char *found_username = NULL;
+ const char *nt_domain;
+ const char *nt_username;
+ struct dom_sid user_sid;
+ struct dom_sid group_sid;
+ bool username_was_mapped;
+ struct passwd *pwd;
+ struct auth_serversupplied_info *result;
+ struct dom_sid sid;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ /*
+ Here is where we should check the list of
+ trusted domains, and verify that the SID
+ matches.
+ */
+
+ if (!sid_compose(&user_sid, info3->base.domain_sid, info3->base.rid)) {
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (!sid_compose(&group_sid, info3->base.domain_sid,
+ info3->base.primary_gid)) {
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ nt_username = talloc_strdup(tmp_ctx, info3->base.account_name.string);
+ if (!nt_username) {
+ /* If the server didn't give us one, just use the one we sent
+ * them */
+ nt_username = sent_nt_username;
+ }
+
+ nt_domain = talloc_strdup(mem_ctx, info3->base.logon_domain.string);
+ if (!nt_domain) {
+ /* If the server didn't give us one, just use the one we sent
+ * them */
+ nt_domain = domain;
+ }
+
+ /* If getpwnam() fails try the add user script (2.2.x behavior).
+
+ We use the _unmapped_ username here in an attempt to provide
+ consistent username mapping behavior between kerberos and NTLM[SSP]
+ authentication in domain mode security. I.E. Username mapping
+ should be applied to the fully qualified username
+ (e.g. DOMAIN\user) and not just the login name. Yes this means we
+ called map_username() unnecessarily in make_user_info_map() but
+ that is how the current code is designed. Making the change here
+ is the least disruptive place. -- jerry */
+
+ /* this call will try to create the user if necessary */
+
+ sid_copy(&sid, info3->base.domain_sid);
+ sid_append_rid(&sid, info3->base.rid);
+
+ nt_status = check_account(tmp_ctx,
+ nt_domain,
+ nt_username,
+ &sid,
+ &found_username,
+ &pwd,
+ &username_was_mapped);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /* Handle 'map to guest = Bad Uid */
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER) &&
+ (lp_security() == SEC_ADS || lp_security() == SEC_DOMAIN) &&
+ lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_UID) {
+ DBG_NOTICE("Try to map %s to guest account\n",
+ nt_username);
+ nt_status = make_server_info_guest(tmp_ctx, &result);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ *server_info = talloc_move(mem_ctx, &result);
+ }
+ }
+ goto out;
+ } else if ((lp_security() == SEC_ADS || lp_security() == SEC_DOMAIN) &&
+ !is_myname(domain) && pwd->pw_uid < lp_min_domain_uid()) {
+ /*
+ * !is_myname(domain) because when smbd starts tries to setup
+ * the guest user info, calling this function with nobody
+ * username. Nobody is usually uid 65535 but it can be changed
+ * to a regular user with 'guest account' parameter
+ */
+ nt_status = NT_STATUS_INVALID_TOKEN;
+ DBG_NOTICE("Username '%s%s%s' is invalid on this system, "
+ "it does not meet 'min domain uid' "
+ "restriction (%u < %u): %s\n",
+ nt_domain, lp_winbind_separator(), nt_username,
+ pwd->pw_uid, lp_min_domain_uid(),
+ nt_errstr(nt_status));
+ goto out;
+ }
+
+ result = make_server_info(tmp_ctx);
+ if (result == NULL) {
+ DEBUG(4, ("make_server_info failed!\n"));
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ result->unix_name = talloc_strdup(result, found_username);
+
+ /* copy in the info3 */
+ nt_status = copy_netr_SamInfo3(result,
+ info3,
+ &result->info3);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto out;
+ }
+
+ /* Fill in the unix info we found on the way */
+
+ result->utok.uid = pwd->pw_uid;
+ result->utok.gid = pwd->pw_gid;
+
+ /* ensure we are never given NULL session keys */
+
+ if (all_zero(info3->base.key.key, sizeof(info3->base.key.key))) {
+ result->session_key = data_blob_null;
+ } else {
+ result->session_key = data_blob_talloc(
+ result, info3->base.key.key,
+ sizeof(info3->base.key.key));
+ }
+
+ if (all_zero(info3->base.LMSessKey.key,
+ sizeof(info3->base.LMSessKey.key))) {
+ result->lm_session_key = data_blob_null;
+ } else {
+ result->lm_session_key = data_blob_talloc(
+ result, info3->base.LMSessKey.key,
+ sizeof(info3->base.LMSessKey.key));
+ }
+
+ result->nss_token |= username_was_mapped;
+
+ result->guest = (info3->base.user_flags & NETLOGON_GUEST);
+
+ *server_info = talloc_move(mem_ctx, &result);
+
+ nt_status = NT_STATUS_OK;
+out:
+ talloc_free(tmp_ctx);
+
+ return nt_status;
+}
+
+/*****************************************************************************
+ Make a server_info struct from the wbcAuthUserInfo returned by a domain logon
+******************************************************************************/
+
+NTSTATUS make_server_info_wbcAuthUserInfo(TALLOC_CTX *mem_ctx,
+ const char *sent_nt_username,
+ const char *domain,
+ const struct wbcAuthUserInfo *info,
+ struct auth_serversupplied_info **server_info)
+{
+ struct netr_SamInfo3 info3;
+ struct netr_SamInfo6 *info6;
+
+ info6 = wbcAuthUserInfo_to_netr_SamInfo6(mem_ctx, info);
+ if (!info6) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info3.base = info6->base;
+ info3.sidcount = info6->sidcount;
+ info3.sids = info6->sids;
+
+ return make_server_info_info3(mem_ctx,
+ sent_nt_username, domain,
+ server_info, &info3);
+}
+
+/**
+ * Verify whether or not given domain is trusted.
+ *
+ * This should only be used on a DC.
+ *
+ * @param domain_name name of the domain to be verified
+ * @return true if domain is one of the trusted ones or
+ * false if otherwise
+ **/
+
+bool is_trusted_domain(const char* dom_name)
+{
+ bool ret;
+
+ if (!IS_DC) {
+ return false;
+ }
+
+ if (dom_name == NULL || dom_name[0] == '\0') {
+ return false;
+ }
+
+ if (strequal(dom_name, get_global_sam_name())) {
+ return false;
+ }
+
+ become_root();
+ DEBUG (5,("is_trusted_domain: Checking for domain trust with "
+ "[%s]\n", dom_name ));
+ ret = pdb_get_trusteddom_pw(dom_name, NULL, NULL, NULL);
+ unbecome_root();
+
+ return ret;
+}
+
+
+
+/*
+ on a logon error possibly map the error to success if "map to guest"
+ is set appropriately
+*/
+NTSTATUS do_map_to_guest_server_info(TALLOC_CTX *mem_ctx,
+ NTSTATUS status,
+ const char *user,
+ const char *domain,
+ struct auth_serversupplied_info **server_info)
+{
+ user = user ? user : "";
+ domain = domain ? domain : "";
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ if ((lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_USER) ||
+ (lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_PASSWORD)) {
+ DEBUG(3,("No such user %s [%s] - using guest account\n",
+ user, domain));
+ return make_server_info_guest(mem_ctx, server_info);
+ }
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) {
+ if (lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_PASSWORD) {
+ DEBUG(3,("Registered username %s for guest access\n",
+ user));
+ return make_server_info_guest(mem_ctx, server_info);
+ }
+ }
+
+ return status;
+}
+
+/*
+ Extract session key from a session info and return it in a blob
+ if intent is KEY_USE_16BYTES, truncate it to 16 bytes
+
+ See sections 3.2.4.15 and 3.3.4.2 of MS-SMB
+ Also see https://lists.samba.org/archive/cifs-protocol/2012-January/002265.html for details
+
+ Note that returned session_key is referencing the original key, it is supposed to be
+ short-lived. If original session_info->session_key is gone, the reference will be broken.
+*/
+NTSTATUS session_extract_session_key(const struct auth_session_info *session_info, DATA_BLOB *session_key, enum session_key_use_intent intent)
+{
+
+ if (session_key == NULL || session_info == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (session_info->session_key.length == 0) {
+ return NT_STATUS_NO_USER_SESSION_KEY;
+ }
+
+ *session_key = session_info->session_key;
+ if (intent == KEY_USE_16BYTES) {
+ session_key->length = MIN(session_info->session_key.length, 16);
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/auth/auth_winbind.c b/source3/auth/auth_winbind.c
new file mode 100644
index 0000000..46db4f2
--- /dev/null
+++ b/source3/auth/auth_winbind.c
@@ -0,0 +1,202 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind authentication mechanism
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Andrew Bartlett 2001 - 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "passdb.h"
+#include "nsswitch/libwbclient/wbclient.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/* Authenticate a user with a challenge/response */
+
+static NTSTATUS check_winbind_security(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ NTSTATUS nt_status;
+ wbcErr wbc_status;
+ struct wbcAuthUserParams params;
+ struct wbcAuthUserInfo *info = NULL;
+ struct wbcAuthErrorInfo *err = NULL;
+
+ ZERO_STRUCT(params);
+
+ if (!user_info) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10, ("Check auth for: [%s]\n", user_info->mapped.account_name));
+
+ if (!auth_context) {
+ DEBUG(3,("Password for user %s cannot be checked because we have no auth_info to get the challenge from.\n",
+ user_info->mapped.account_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (strequal(user_info->mapped.domain_name, get_global_sam_name())) {
+ DEBUG(3,("check_winbind_security: Not using winbind, requested domain [%s] was for this SAM.\n",
+ user_info->mapped.domain_name));
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /* Send off request */
+ params.account_name = user_info->client.account_name;
+ /*
+ * We need to send the domain name from the client to the DC. With
+ * NTLMv2 the domain name is part of the hashed second challenge,
+ * if we change the domain name, the DC will fail to verify the
+ * challenge cause we changed the domain name, this is like a
+ * man in the middle attack.
+ */
+ params.domain_name = user_info->client.domain_name;
+ params.workstation_name = user_info->workstation_name;
+
+ params.flags = 0;
+ params.parameter_control= user_info->logon_parameters;
+
+ params.level = WBC_AUTH_USER_LEVEL_RESPONSE;
+
+ memcpy(params.password.response.challenge,
+ auth_context->challenge.data,
+ sizeof(params.password.response.challenge));
+
+ if (user_info->password.response.nt.length != 0) {
+ params.password.response.nt_length =
+ user_info->password.response.nt.length;
+ params.password.response.nt_data =
+ user_info->password.response.nt.data;
+ }
+ if (user_info->password.response.lanman.length != 0) {
+ params.password.response.lm_length =
+ user_info->password.response.lanman.length;
+ params.password.response.lm_data =
+ user_info->password.response.lanman.data;
+ }
+
+ /* we are contacting the privileged pipe */
+ become_root();
+ wbc_status = wbcAuthenticateUserEx(&params, &info, &err);
+ unbecome_root();
+
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ DEBUG(10,("check_winbind_security: wbcAuthenticateUserEx failed: %s\n",
+ wbcErrorString(wbc_status)));
+ }
+
+ if (wbc_status == WBC_ERR_NO_MEMORY) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (wbc_status == WBC_ERR_WINBIND_NOT_AVAILABLE) {
+ struct pdb_trusted_domain **domains = NULL;
+ uint32_t num_domains = 0;
+ NTSTATUS status;
+
+ if (lp_server_role() == ROLE_DOMAIN_MEMBER) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ DBG_ERR("winbindd not running - "
+ "but required as domain member: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = pdb_enum_trusted_domains(talloc_tos(), &num_domains, &domains);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("pdb_enum_trusted_domains() failed - %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ TALLOC_FREE(domains);
+
+ if (num_domains == 0) {
+ DBG_DEBUG("winbindd not running - ignoring without "
+ "trusted domains\n");
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ DBG_ERR("winbindd not running - "
+ "but required as DC with trusts: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (wbc_status == WBC_ERR_AUTH_ERROR) {
+ nt_status = NT_STATUS(err->nt_status);
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_SUCH_USER) &&
+ (err->authoritative == 0)) {
+ /*
+ * Trigger a fallback to local SAM
+ */
+ nt_status = NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ wbcFreeMemory(err);
+ return nt_status;
+ }
+
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ nt_status = make_server_info_wbcAuthUserInfo(mem_ctx,
+ user_info->client.account_name,
+ user_info->mapped.domain_name,
+ info, server_info);
+ wbcFreeMemory(info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ (*server_info)->nss_token |= user_info->was_mapped;
+
+ return nt_status;
+}
+
+/* module initialisation */
+static NTSTATUS auth_init_winbind(
+ struct auth_context *auth_context,
+ const char *param,
+ struct auth_methods **auth_method)
+{
+ struct auth_methods *result;
+
+ result = talloc_zero(auth_context, struct auth_methods);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->name = "winbind";
+ result->auth = check_winbind_security;
+
+ *auth_method = result;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_winbind_init(TALLOC_CTX *mem_ctx)
+{
+ return smb_register_auth(AUTH_INTERFACE_VERSION, "winbind", auth_init_winbind);
+}
diff --git a/source3/auth/check_samsec.c b/source3/auth/check_samsec.c
new file mode 100644
index 0000000..1e55fed
--- /dev/null
+++ b/source3/auth/check_samsec.c
@@ -0,0 +1,641 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Luke Kenneth Casson Leighton 1996-2000
+ Copyright (C) Andrew Bartlett 2001-2003
+ Copyright (C) Gerald Carter 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "passdb.h"
+#include "lib/util/memcache.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/****************************************************************************
+ Do a specific test for an smb password being correct, given a smb_password and
+ the lanman and NT responses.
+****************************************************************************/
+
+static NTSTATUS sam_password_ok(TALLOC_CTX *mem_ctx,
+ const char *username,
+ uint32_t acct_ctrl,
+ const DATA_BLOB *challenge,
+ const uint8_t *lm_pw,
+ const uint8_t *nt_pw,
+ const struct auth_usersupplied_info *user_info,
+ DATA_BLOB *user_sess_key,
+ DATA_BLOB *lm_sess_key)
+{
+ NTSTATUS status;
+ struct samr_Password _lm_hash, _nt_hash;
+ struct samr_Password *lm_hash = NULL;
+ struct samr_Password *nt_hash = NULL;
+
+ *user_sess_key = data_blob_null;
+ *lm_sess_key = data_blob_null;
+
+ if (acct_ctrl & ACB_PWNOTREQ) {
+ if (lp_null_passwords()) {
+ DEBUG(3,("Account for user '%s' has no password and null passwords are allowed.\n", username));
+ return NT_STATUS_OK;
+ } else {
+ DEBUG(3,("Account for user '%s' has no password and null passwords are NOT allowed.\n", username));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ }
+
+ if (lm_pw) {
+ memcpy(_lm_hash.hash, lm_pw, sizeof(_lm_hash.hash));
+ lm_hash = &_lm_hash;
+ }
+ if (nt_pw) {
+ memcpy(_nt_hash.hash, nt_pw, sizeof(_nt_hash.hash));
+ nt_hash = &_nt_hash;
+ }
+ switch (user_info->password_state) {
+ case AUTH_PASSWORD_HASH:
+ status = hash_password_check(mem_ctx, lp_lanman_auth(),
+ lp_ntlm_auth(),
+ user_info->password.hash.lanman,
+ user_info->password.hash.nt,
+ username,
+ lm_hash,
+ nt_hash);
+ if (NT_STATUS_IS_OK(status)) {
+ if (nt_pw) {
+ *user_sess_key = data_blob_talloc(mem_ctx, NULL, 16);
+ if (!user_sess_key->data) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ SMBsesskeygen_ntv1(nt_pw, user_sess_key->data);
+ }
+ }
+ break;
+
+ /* Eventually we should test plaintext passwords in their own
+ * function, not assuming the caller has done a
+ * mapping */
+ case AUTH_PASSWORD_PLAIN:
+ case AUTH_PASSWORD_RESPONSE:
+ status = ntlm_password_check(mem_ctx, lp_lanman_auth(),
+ lp_ntlm_auth(),
+ user_info->logon_parameters,
+ challenge,
+ &user_info->password.response.lanman, &user_info->password.response.nt,
+ username,
+ user_info->client.account_name,
+ user_info->client.domain_name,
+ lm_hash,
+ nt_hash,
+ user_sess_key, lm_sess_key);
+ break;
+ default:
+ DEBUG(0,("user_info constructed for user '%s' was invalid - password_state=%u invalid.\n", username, user_info->password_state));
+ status = NT_STATUS_INTERNAL_ERROR;
+ }
+done:
+ ZERO_STRUCTP(lm_hash);
+ ZERO_STRUCTP(nt_hash);
+ return status;
+}
+
+/****************************************************************************
+ Check if a user is allowed to logon at this time. Note this is the
+ servers local time, as logon hours are just specified as a weekly
+ bitmask.
+****************************************************************************/
+
+static bool logon_hours_ok(struct samu *sampass)
+{
+ /* In logon hours first bit is Sunday from 12AM to 1AM */
+ const uint8_t *hours;
+ struct tm *utctime;
+ time_t lasttime;
+ const char *asct;
+ uint8_t bitmask, bitpos;
+
+ hours = pdb_get_hours(sampass);
+ if (!hours) {
+ DEBUG(5,("logon_hours_ok: No hours restrictions for user %s\n",pdb_get_username(sampass)));
+ return True;
+ }
+
+ lasttime = time(NULL);
+ utctime = gmtime(&lasttime);
+ if (!utctime) {
+ DEBUG(1, ("logon_hours_ok: failed to get gmtime. Failing logon for user %s\n",
+ pdb_get_username(sampass) ));
+ return False;
+ }
+
+ /* find the corresponding byte and bit */
+ bitpos = (utctime->tm_wday * 24 + utctime->tm_hour) % 168;
+ bitmask = 1 << (bitpos % 8);
+
+ if (! (hours[bitpos/8] & bitmask)) {
+ struct tm *t = localtime(&lasttime);
+ if (!t) {
+ asct = "INVALID TIME";
+ } else {
+ asct = asctime(t);
+ if (!asct) {
+ asct = "INVALID TIME";
+ }
+ }
+
+ DEBUG(1, ("logon_hours_ok: Account for user %s not allowed to "
+ "logon at this time (%s).\n",
+ pdb_get_username(sampass), asct ));
+ return False;
+ }
+
+ asct = asctime(utctime);
+ DEBUG(5,("logon_hours_ok: user %s allowed to logon at this time (%s)\n",
+ pdb_get_username(sampass), asct ? asct : "UNKNOWN TIME" ));
+
+ return True;
+}
+
+/****************************************************************************
+ Do a specific test for a struct samu being valid for this connection
+ (ie not disabled, expired and the like).
+****************************************************************************/
+
+static NTSTATUS sam_account_ok(TALLOC_CTX *mem_ctx,
+ struct samu *sampass,
+ const struct auth_usersupplied_info *user_info)
+{
+ uint32_t acct_ctrl = pdb_get_acct_ctrl(sampass);
+ char *workstation_list;
+ time_t kickoff_time;
+
+ DEBUG(4,("sam_account_ok: Checking SMB password for user %s\n",pdb_get_username(sampass)));
+
+ /* Quit if the account was disabled. */
+ if (acct_ctrl & ACB_DISABLED) {
+ DEBUG(1,("sam_account_ok: Account for user '%s' was disabled.\n", pdb_get_username(sampass)));
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+
+ /* Quit if the account was locked out. */
+ if (acct_ctrl & ACB_AUTOLOCK) {
+ DEBUG(1,("sam_account_ok: Account for user %s was locked out.\n", pdb_get_username(sampass)));
+ return NT_STATUS_ACCOUNT_LOCKED_OUT;
+ }
+
+ /* Quit if the account is not allowed to logon at this time. */
+ if (! logon_hours_ok(sampass)) {
+ return NT_STATUS_INVALID_LOGON_HOURS;
+ }
+
+ /* Test account expire time */
+
+ kickoff_time = pdb_get_kickoff_time(sampass);
+ if (kickoff_time != 0 && time(NULL) > kickoff_time) {
+ DEBUG(1,("sam_account_ok: Account for user '%s' has expired.\n", pdb_get_username(sampass)));
+ DEBUG(3,("sam_account_ok: Account expired at '%ld' unix time.\n", (long)kickoff_time));
+ return NT_STATUS_ACCOUNT_EXPIRED;
+ }
+
+ if (!(pdb_get_acct_ctrl(sampass) & ACB_PWNOEXP) && !(pdb_get_acct_ctrl(sampass) & ACB_PWNOTREQ)) {
+ time_t must_change_time = pdb_get_pass_must_change_time(sampass);
+ time_t last_set_time = pdb_get_pass_last_set_time(sampass);
+
+ /* check for immediate expiry "must change at next logon"
+ * for a user account. */
+ if (((acct_ctrl & (ACB_WSTRUST|ACB_SVRTRUST)) == 0) && (last_set_time == 0)) {
+ DEBUG(1,("sam_account_ok: Account for user '%s' password must change!\n", pdb_get_username(sampass)));
+ return NT_STATUS_PASSWORD_MUST_CHANGE;
+ }
+
+ /* check for expired password */
+ if (must_change_time < time(NULL) && must_change_time != 0) {
+ DEBUG(1,("sam_account_ok: Account for user '%s' password expired!\n", pdb_get_username(sampass)));
+ DEBUG(1,("sam_account_ok: Password expired at '%s' (%ld) unix time.\n", http_timestring(talloc_tos(), must_change_time), (long)must_change_time));
+ return NT_STATUS_PASSWORD_EXPIRED;
+ }
+ }
+
+ /* Test workstation. Workstation list is comma separated. */
+
+ workstation_list = talloc_strdup(mem_ctx, pdb_get_workstations(sampass));
+ if (!workstation_list)
+ return NT_STATUS_NO_MEMORY;
+
+ if (*workstation_list) {
+ bool invalid_ws = True;
+ char *tok = NULL;
+ const char *s = workstation_list;
+ char *machine_name = talloc_asprintf(mem_ctx, "%s$", user_info->workstation_name);
+
+ if (machine_name == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ while (next_token_talloc(mem_ctx, &s, &tok, ",")) {
+ DEBUG(10,("sam_account_ok: checking for workstation match %s and %s\n",
+ tok, user_info->workstation_name));
+ if(strequal(tok, user_info->workstation_name)) {
+ invalid_ws = False;
+ break;
+ }
+ if (tok[0] == '+') {
+ DEBUG(10,("sam_account_ok: checking for workstation %s in group: %s\n",
+ machine_name, tok + 1));
+ if (user_in_group(machine_name, tok + 1)) {
+ invalid_ws = False;
+ break;
+ }
+ }
+ TALLOC_FREE(tok);
+ }
+ TALLOC_FREE(tok);
+ TALLOC_FREE(machine_name);
+
+ if (invalid_ws)
+ return NT_STATUS_INVALID_WORKSTATION;
+ }
+
+ if (acct_ctrl & ACB_DOMTRUST) {
+ DEBUG(2,("sam_account_ok: Domain trust account %s denied by server\n", pdb_get_username(sampass)));
+ return NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
+ }
+
+ if (acct_ctrl & ACB_SVRTRUST) {
+ if (!(user_info->logon_parameters & MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT)) {
+ DEBUG(2,("sam_account_ok: Server trust account %s denied by server\n", pdb_get_username(sampass)));
+ return NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
+ }
+ }
+
+ if (acct_ctrl & ACB_WSTRUST) {
+ if (!(user_info->logon_parameters & MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT)) {
+ DEBUG(2,("sam_account_ok: Wksta trust account %s denied by server\n", pdb_get_username(sampass)));
+ return NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/**
+ * Check whether the given password is one of the last two
+ * password history entries. If so, the bad pwcount should
+ * not be incremented even though the actual password check
+ * failed.
+ */
+static bool need_to_increment_bad_pw_count(
+ const DATA_BLOB *challenge,
+ struct samu* sampass,
+ const struct auth_usersupplied_info *user_info)
+{
+ uint8_t i;
+ const uint8_t *pwhistory;
+ uint32_t pwhistory_len;
+ uint32_t policy_pwhistory_len;
+ uint32_t acct_ctrl;
+ const char *username;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ bool result = true;
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY,
+ &policy_pwhistory_len);
+ if (policy_pwhistory_len == 0) {
+ goto done;
+ }
+
+ pwhistory = pdb_get_pw_history(sampass, &pwhistory_len);
+ if (!pwhistory || pwhistory_len == 0) {
+ goto done;
+ }
+
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+ username = pdb_get_username(sampass);
+
+ for (i=1; i < MIN(MIN(3, policy_pwhistory_len), pwhistory_len); i++) {
+ const uint8_t *salt;
+ const uint8_t *nt_pw;
+ NTSTATUS status;
+ DATA_BLOB user_sess_key = data_blob_null;
+ DATA_BLOB lm_sess_key = data_blob_null;
+
+ salt = &pwhistory[i*PW_HISTORY_ENTRY_LEN];
+ nt_pw = salt + PW_HISTORY_SALT_LEN;
+
+ if (all_zero(nt_pw, NT_HASH_LEN)) {
+ /* skip zero password hash */
+ continue;
+ }
+
+ if (!all_zero(salt, PW_HISTORY_SALT_LEN)) {
+ /* skip nonzero salt (old format entry) */
+ continue;
+ }
+
+ status = sam_password_ok(mem_ctx,
+ username, acct_ctrl,
+ challenge,
+ NULL, nt_pw,
+ user_info, &user_sess_key, &lm_sess_key);
+ if (NT_STATUS_IS_OK(status)) {
+ result = false;
+ break;
+ }
+ }
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+/****************************************************************************
+check if a username/password is OK assuming the password is a 24 byte
+SMB hash supplied in the user_info structure
+return an NT_STATUS constant.
+****************************************************************************/
+
+NTSTATUS check_sam_security(const DATA_BLOB *challenge,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info)
+{
+ struct samu *sampass=NULL;
+ bool ret;
+ NTSTATUS nt_status;
+ NTSTATUS update_login_attempts_status;
+ DATA_BLOB user_sess_key = data_blob_null;
+ DATA_BLOB lm_sess_key = data_blob_null;
+ bool updated_badpw = False;
+ const char *username;
+ const uint8_t *nt_pw;
+ const uint8_t *lm_pw;
+ uint32_t acct_ctrl;
+ char *mutex_name_by_user = NULL;
+ struct named_mutex *mtx = NULL;
+
+ /* the returned struct gets kept on the server_info, by means
+ of a steal further down */
+
+ sampass = samu_new(mem_ctx);
+ if (sampass == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get the account information */
+
+ become_root();
+ ret = pdb_getsampwnam(sampass, user_info->mapped.account_name);
+ unbecome_root();
+
+ if (!ret) {
+ DEBUG(3,("check_sam_security: Couldn't find user '%s' in "
+ "passdb.\n", user_info->mapped.account_name));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+ username = pdb_get_username(sampass);
+ nt_pw = pdb_get_nt_passwd(sampass);
+ lm_pw = pdb_get_lanman_passwd(sampass);
+
+ /* Quit if the account was locked out. */
+ if (acct_ctrl & ACB_AUTOLOCK) {
+ DEBUG(3,("check_sam_security: Account for user %s was locked out.\n", username));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_ACCOUNT_LOCKED_OUT;
+ }
+
+ nt_status = sam_password_ok(mem_ctx,
+ username, acct_ctrl,
+ challenge, lm_pw, nt_pw,
+ user_info, &user_sess_key, &lm_sess_key);
+
+ /*
+ * We must re-load the sam account information under a mutex
+ * lock to ensure we don't miss any concurrent account lockout
+ * changes.
+ */
+
+ /* Clear out old sampass info. */
+ TALLOC_FREE(sampass);
+ acct_ctrl = 0;
+ username = NULL;
+ nt_pw = NULL;
+ lm_pw = NULL;
+
+ sampass = samu_new(mem_ctx);
+ if (sampass == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ mutex_name_by_user = talloc_asprintf(mem_ctx,
+ "check_sam_security_mutex_%s",
+ user_info->mapped.account_name);
+ if (mutex_name_by_user == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* Grab the named mutex under root with 30 second timeout. */
+ become_root();
+ mtx = grab_named_mutex(mem_ctx, mutex_name_by_user, 30);
+ if (mtx != NULL) {
+ /* Re-load the account information if we got the mutex. */
+ ret = pdb_getsampwnam(sampass, user_info->mapped.account_name);
+ }
+ unbecome_root();
+
+ /* Everything from here on until mtx is freed is done under the mutex.*/
+
+ if (mtx == NULL) {
+ DBG_ERR("Acquisition of mutex %s failed "
+ "for user %s\n",
+ mutex_name_by_user,
+ user_info->mapped.account_name);
+ nt_status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ if (!ret) {
+ /*
+ * Re-load of account failed. This could only happen if the
+ * user was deleted in the meantime.
+ */
+ DBG_NOTICE("reload of user '%s' in passdb failed.\n",
+ user_info->mapped.account_name);
+ nt_status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ /* Re-load the account control info. */
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+ username = pdb_get_username(sampass);
+
+ /*
+ * Check if the account is now locked out - now under the mutex.
+ * This can happen if the server is under
+ * a password guess attack and the ACB_AUTOLOCK is set by
+ * another process.
+ */
+ if (acct_ctrl & ACB_AUTOLOCK) {
+ DBG_NOTICE("Account for user %s was locked out.\n", username);
+ nt_status = NT_STATUS_ACCOUNT_LOCKED_OUT;
+ goto done;
+ }
+
+ /* Notify passdb backend of login success/failure. If not
+ NT_STATUS_OK the backend doesn't like the login */
+
+ update_login_attempts_status = pdb_update_login_attempts(sampass, NT_STATUS_IS_OK(nt_status));
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ bool increment_bad_pw_count = false;
+
+ if (NT_STATUS_EQUAL(nt_status,NT_STATUS_WRONG_PASSWORD) &&
+ (acct_ctrl & ACB_NORMAL) &&
+ NT_STATUS_IS_OK(update_login_attempts_status))
+ {
+ increment_bad_pw_count =
+ need_to_increment_bad_pw_count(
+ challenge, sampass, user_info);
+ }
+
+ if (increment_bad_pw_count) {
+ pdb_increment_bad_password_count(sampass);
+ updated_badpw = True;
+ } else {
+ pdb_update_bad_password_count(sampass,
+ &updated_badpw);
+ }
+ if (updated_badpw){
+ NTSTATUS status;
+
+ become_root();
+ status = pdb_update_sam_account(sampass);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to modify entry: %s\n",
+ nt_errstr(status)));
+ }
+ }
+ goto done;
+ }
+
+ /*
+ * We must only reset the bad password count if the login was
+ * successful, including checking account policies
+ */
+ nt_status = sam_account_ok(mem_ctx, sampass, user_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ if ((acct_ctrl & ACB_NORMAL) &&
+ (pdb_get_bad_password_count(sampass) > 0)){
+ NTSTATUS status;
+
+ pdb_set_bad_password_count(sampass, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(sampass, 0, PDB_CHANGED);
+
+ become_root();
+ status = pdb_update_sam_account(sampass);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to modify entry: %s\n",
+ nt_errstr(status)));
+ }
+ }
+
+ become_root();
+ nt_status = make_server_info_sam(mem_ctx, sampass, server_info);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("check_sam_security: make_server_info_sam() failed with '%s'\n", nt_errstr(nt_status)));
+ goto done;
+ }
+
+ (*server_info)->session_key =
+ data_blob_talloc(*server_info, user_sess_key.data,
+ user_sess_key.length);
+ data_blob_free(&user_sess_key);
+
+ (*server_info)->lm_session_key =
+ data_blob_talloc(*server_info, lm_sess_key.data,
+ lm_sess_key.length);
+ data_blob_free(&lm_sess_key);
+
+ (*server_info)->nss_token |= user_info->was_mapped;
+
+done:
+ /*
+ * Always flush the getpwsid cache or this will grow indefinitely for
+ * each NTLM auththentication.
+ */
+ memcache_flush(NULL, PDB_GETPWSID_CACHE);
+ TALLOC_FREE(sampass);
+ data_blob_free(&user_sess_key);
+ data_blob_free(&lm_sess_key);
+ TALLOC_FREE(mutex_name_by_user);
+ TALLOC_FREE(mtx);
+ return nt_status;
+}
+
+/* This helper function for winbindd returns a very similar value to
+ * what a NETLOGON call would give, without the indirection */
+NTSTATUS check_sam_security_info3(const DATA_BLOB *challenge,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct netr_SamInfo3 **pinfo3)
+{
+ struct auth_serversupplied_info *server_info = NULL;
+ struct netr_SamInfo3 *info3;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ status = check_sam_security(challenge, talloc_tos(), user_info,
+ &server_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("check_sam_security failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ info3 = talloc_zero(mem_ctx, struct netr_SamInfo3);
+ if (info3 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = serverinfo_to_SamInfo3(server_info, info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("serverinfo_to_SamInfo3 failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ *pinfo3 = info3;
+ status = NT_STATUS_OK;
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/auth/pampass.c b/source3/auth/pampass.c
new file mode 100644
index 0000000..d64a6e8
--- /dev/null
+++ b/source3/auth/pampass.c
@@ -0,0 +1,896 @@
+/*
+ Unix SMB/CIFS implementation.
+ PAM Password checking
+ Copyright (C) Andrew Tridgell 1992-2001
+ Copyright (C) John H Terpsta 1999-2001
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This module provides PAM based functions for validation of
+ * username/password pairs, account management, session and access control.
+ * Note: SMB password checking is done in smbpass.c
+ */
+
+#include "includes.h"
+#include "auth.h"
+#include "../libcli/auth/pam_errors.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#ifdef WITH_PAM
+
+/*******************************************************************
+ * Handle PAM authentication
+ * - Access, Authentication, Session, Password
+ * Note: See PAM Documentation and refer to local system PAM implementation
+ * which determines what actions/limitations/allowances become affected.
+ *********************************************************************/
+
+#if defined(HAVE_SECURITY_PAM_APPL_H)
+#include <security/pam_appl.h>
+#elif defined(HAVE_PAM_PAM_APPL_H)
+#include <pam/pam_appl.h>
+#endif
+
+/*
+ * Structure used to communicate between the conversation function
+ * and the server_login/change password functions.
+ */
+
+struct smb_pam_userdata {
+ const char *PAM_username;
+ const char *PAM_password;
+ const char *PAM_newpassword;
+};
+
+typedef int (*smb_pam_conv_fn)(int, const struct pam_message **, struct pam_response **, void *appdata_ptr);
+
+static char *smb_pam_copy_string(const char *s)
+{
+ if (s == NULL) {
+ return NULL;
+ }
+ return SMB_STRDUP(s);
+}
+
+static char *smb_pam_copy_fstring(const char *s)
+{
+ if (s[0] == '\0') {
+ return NULL;
+ }
+ return SMB_STRDUP(s);
+}
+
+/*******************************************************************
+ PAM error handler.
+ *********************************************************************/
+
+static bool smb_pam_error_handler(pam_handle_t *pamh, int pam_error, const char *msg, int dbglvl)
+{
+
+ if( pam_error != PAM_SUCCESS) {
+ DEBUG(dbglvl, ("smb_pam_error_handler: PAM: %s : %s\n",
+ msg, pam_strerror(pamh, pam_error)));
+ return False;
+ }
+ return True;
+}
+
+/*******************************************************************
+ This function is a sanity check, to make sure that we NEVER report
+ failure as success.
+*********************************************************************/
+
+static bool smb_pam_nt_status_error_handler(pam_handle_t *pamh, int pam_error,
+ const char *msg, int dbglvl,
+ NTSTATUS *nt_status)
+{
+ *nt_status = pam_to_nt_status(pam_error);
+
+ if (smb_pam_error_handler(pamh, pam_error, msg, dbglvl))
+ return True;
+
+ if (NT_STATUS_IS_OK(*nt_status)) {
+ /* Complain LOUDLY */
+ DEBUG(0, ("smb_pam_nt_status_error_handler: PAM: BUG: PAM and NT_STATUS \
+error MISMATCH, forcing to NT_STATUS_LOGON_FAILURE\n"));
+ *nt_status = NT_STATUS_LOGON_FAILURE;
+ }
+ return False;
+}
+
+/*
+ * PAM conversation function
+ * Here we assume (for now, at least) that echo on means login name, and
+ * echo off means password.
+ */
+
+static int smb_pam_conv(int num_msg,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *appdata_ptr)
+{
+ int replies = 0;
+ struct pam_response *reply = NULL;
+ struct smb_pam_userdata *udp = (struct smb_pam_userdata *)appdata_ptr;
+
+ *resp = NULL;
+
+ if (num_msg <= 0)
+ return PAM_CONV_ERR;
+
+ /*
+ * Apparently HPUX has a buggy PAM that doesn't support the
+ * appdata_ptr. Fail if this is the case. JRA.
+ */
+
+ if (udp == NULL) {
+ DEBUG(0,("smb_pam_conv: PAM on this system is broken - appdata_ptr == NULL !\n"));
+ return PAM_CONV_ERR;
+ }
+
+ reply = SMB_MALLOC_ARRAY(struct pam_response, num_msg);
+ if (!reply)
+ return PAM_CONV_ERR;
+
+ memset(reply, '\0', sizeof(struct pam_response) * num_msg);
+
+ for (replies = 0; replies < num_msg; replies++) {
+ switch (msg[replies]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ reply[replies].resp_retcode = PAM_SUCCESS;
+ reply[replies].resp = smb_pam_copy_string(
+ udp->PAM_username);
+ /* PAM frees resp */
+ break;
+
+ case PAM_PROMPT_ECHO_OFF:
+ reply[replies].resp_retcode = PAM_SUCCESS;
+ reply[replies].resp = smb_pam_copy_string(
+ udp->PAM_password);
+ /* PAM frees resp */
+ break;
+
+ case PAM_TEXT_INFO:
+ FALL_THROUGH;
+
+ case PAM_ERROR_MSG:
+ /* ignore it... */
+ reply[replies].resp_retcode = PAM_SUCCESS;
+ reply[replies].resp = NULL;
+ break;
+
+ default:
+ /* Must be an error of some sort... */
+ SAFE_FREE(reply);
+ return PAM_CONV_ERR;
+ }
+ }
+ if (reply)
+ *resp = reply;
+ return PAM_SUCCESS;
+}
+
+/*
+ * PAM password change conversation function
+ * Here we assume (for now, at least) that echo on means login name, and
+ * echo off means password.
+ */
+
+static void special_char_sub(char *buf)
+{
+ all_string_sub(buf, "\\n", "", 0);
+ all_string_sub(buf, "\\r", "", 0);
+ all_string_sub(buf, "\\s", " ", 0);
+ all_string_sub(buf, "\\t", "\t", 0);
+}
+
+static void pwd_sub(char *buf, const char *username, const char *oldpass, const char *newpass)
+{
+ string_sub(buf, "%u", username, sizeof(fstring));
+ all_string_sub(buf, "%o", oldpass, sizeof(fstring));
+ all_string_sub(buf, "%n", newpass, sizeof(fstring));
+}
+
+
+struct chat_struct {
+ struct chat_struct *next, *prev;
+ fstring prompt;
+ fstring reply;
+};
+
+/**************************************************************
+ Create a linked list containing chat data.
+***************************************************************/
+
+static struct chat_struct *make_pw_chat(const char *p)
+{
+ char *prompt;
+ char *reply;
+ struct chat_struct *list = NULL;
+ struct chat_struct *t;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ while (1) {
+ t = SMB_MALLOC_P(struct chat_struct);
+ if (!t) {
+ DEBUG(0,("make_pw_chat: malloc failed!\n"));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ ZERO_STRUCTP(t);
+
+ DLIST_ADD_END(list, t);
+
+ if (!next_token_talloc(frame, &p, &prompt, NULL)) {
+ break;
+ }
+
+ if (strequal(prompt,".")) {
+ fstrcpy(prompt,"*");
+ }
+
+ special_char_sub(prompt);
+ fstrcpy(t->prompt, prompt);
+ (void)strlower_m(t->prompt);
+ trim_char(t->prompt, ' ', ' ');
+
+ if (!next_token_talloc(frame, &p, &reply, NULL)) {
+ break;
+ }
+
+ if (strequal(reply,".")) {
+ fstrcpy(reply,"");
+ }
+
+ special_char_sub(reply);
+ fstrcpy(t->reply, reply);
+ (void)strlower_m(t->reply);
+ trim_char(t->reply, ' ', ' ');
+
+ }
+ TALLOC_FREE(frame);
+ return list;
+}
+
+static void free_pw_chat(struct chat_struct *list)
+{
+ while (list) {
+ struct chat_struct *old_head = list;
+ DLIST_REMOVE(list, list);
+ SAFE_FREE(old_head);
+ }
+}
+
+static int smb_pam_passchange_conv(int num_msg,
+ const struct pam_message **msg,
+ struct pam_response **resp,
+ void *appdata_ptr)
+{
+ int replies = 0;
+ struct pam_response *reply = NULL;
+ fstring current_prompt;
+ fstring current_reply;
+ struct smb_pam_userdata *udp = (struct smb_pam_userdata *)appdata_ptr;
+ struct chat_struct *pw_chat;
+ struct chat_struct *t;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ bool found;
+ *resp = NULL;
+
+ DEBUG(10,("smb_pam_passchange_conv: starting conversation for %d messages\n", num_msg));
+
+ if (num_msg <= 0)
+ return PAM_CONV_ERR;
+
+ if ((pw_chat = make_pw_chat(lp_passwd_chat(talloc_tos(), lp_sub))) == NULL)
+ return PAM_CONV_ERR;
+
+ /*
+ * Apparently HPUX has a buggy PAM that doesn't support the
+ * appdata_ptr. Fail if this is the case. JRA.
+ */
+
+ if (udp == NULL) {
+ DEBUG(0,("smb_pam_passchange_conv: PAM on this system is broken - appdata_ptr == NULL !\n"));
+ free_pw_chat(pw_chat);
+ return PAM_CONV_ERR;
+ }
+
+ reply = SMB_MALLOC_ARRAY(struct pam_response, num_msg);
+ if (!reply) {
+ DEBUG(0,("smb_pam_passchange_conv: malloc for reply failed!\n"));
+ free_pw_chat(pw_chat);
+ return PAM_CONV_ERR;
+ }
+
+ for (replies = 0; replies < num_msg; replies++) {
+ found = False;
+ DEBUG(10,("smb_pam_passchange_conv: Processing message %d\n", replies));
+ switch (msg[replies]->msg_style) {
+ case PAM_PROMPT_ECHO_ON:
+ DEBUG(10,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_ON: PAM said: %s\n", msg[replies]->msg));
+ fstrcpy(current_prompt, msg[replies]->msg);
+ trim_char(current_prompt, ' ', ' ');
+ for (t=pw_chat; t; t=t->next) {
+
+ DEBUG(10,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_ON: trying to match |%s| to |%s|\n",
+ t->prompt, current_prompt ));
+
+ if (unix_wild_match(t->prompt, current_prompt)) {
+ fstrcpy(current_reply, t->reply);
+ DEBUG(10,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_ON: We sent: %s\n", current_reply));
+ pwd_sub(current_reply, udp->PAM_username, udp->PAM_password, udp->PAM_newpassword);
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_ON: We actually sent: %s\n", current_reply));
+#endif
+ reply[replies].resp_retcode = PAM_SUCCESS;
+ reply[replies].resp = smb_pam_copy_fstring(
+ current_reply);
+ found = True;
+ break;
+ }
+ }
+ /* PAM frees resp */
+ if (!found) {
+ DEBUG(3,("smb_pam_passchange_conv: Could not find reply for PAM prompt: %s\n",msg[replies]->msg));
+ free_pw_chat(pw_chat);
+ SAFE_FREE(reply);
+ return PAM_CONV_ERR;
+ }
+ break;
+
+ case PAM_PROMPT_ECHO_OFF:
+ DEBUG(10,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_OFF: PAM said: %s\n", msg[replies]->msg));
+ fstrcpy(current_prompt, msg[replies]->msg);
+ trim_char(current_prompt, ' ', ' ');
+ for (t=pw_chat; t; t=t->next) {
+
+ DEBUG(10,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_OFF: trying to match |%s| to |%s|\n",
+ t->prompt, current_prompt ));
+
+ if (unix_wild_match(t->prompt, current_prompt)) {
+ fstrcpy(current_reply, t->reply);
+ DEBUG(10,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_OFF: We sent: %s\n", current_reply));
+ pwd_sub(current_reply, udp->PAM_username, udp->PAM_password, udp->PAM_newpassword);
+ reply[replies].resp_retcode = PAM_SUCCESS;
+ reply[replies].resp = smb_pam_copy_fstring(
+ current_reply);
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("smb_pam_passchange_conv: PAM_PROMPT_ECHO_OFF: We actually sent: %s\n", current_reply));
+#endif
+ found = True;
+ break;
+ }
+ }
+ /* PAM frees resp */
+
+ if (!found) {
+ DEBUG(3,("smb_pam_passchange_conv: Could not find reply for PAM prompt: %s\n",msg[replies]->msg));
+ free_pw_chat(pw_chat);
+ SAFE_FREE(reply);
+ return PAM_CONV_ERR;
+ }
+ break;
+
+ case PAM_TEXT_INFO:
+ FALL_THROUGH;
+
+ case PAM_ERROR_MSG:
+ /* ignore it... */
+ reply[replies].resp_retcode = PAM_SUCCESS;
+ reply[replies].resp = NULL;
+ break;
+
+ default:
+ /* Must be an error of some sort... */
+ free_pw_chat(pw_chat);
+ SAFE_FREE(reply);
+ return PAM_CONV_ERR;
+ }
+ }
+
+ free_pw_chat(pw_chat);
+ if (reply)
+ *resp = reply;
+ return PAM_SUCCESS;
+}
+
+/***************************************************************************
+ Free up a malloced pam_conv struct.
+****************************************************************************/
+
+static void smb_free_pam_conv(struct pam_conv *pconv)
+{
+ if (pconv)
+ SAFE_FREE(pconv->appdata_ptr);
+
+ SAFE_FREE(pconv);
+}
+
+/***************************************************************************
+ Allocate a pam_conv struct.
+****************************************************************************/
+
+static struct pam_conv *smb_setup_pam_conv(smb_pam_conv_fn smb_pam_conv_fnptr, const char *user,
+ const char *passwd, const char *newpass)
+{
+ struct pam_conv *pconv = SMB_MALLOC_P(struct pam_conv);
+ struct smb_pam_userdata *udp = SMB_MALLOC_P(struct smb_pam_userdata);
+
+ if (pconv == NULL || udp == NULL) {
+ SAFE_FREE(pconv);
+ SAFE_FREE(udp);
+ return NULL;
+ }
+
+ udp->PAM_username = user;
+ udp->PAM_password = passwd;
+ udp->PAM_newpassword = newpass;
+
+ pconv->conv = smb_pam_conv_fnptr;
+ pconv->appdata_ptr = (void *)udp;
+ return pconv;
+}
+
+/*
+ * PAM Closing out cleanup handler
+ */
+
+static bool smb_pam_end(pam_handle_t *pamh, struct pam_conv *smb_pam_conv_ptr)
+{
+ int pam_error;
+
+ smb_free_pam_conv(smb_pam_conv_ptr);
+
+ if( pamh != NULL ) {
+ pam_error = pam_end(pamh, 0);
+ if(smb_pam_error_handler(pamh, pam_error, "End Cleanup Failed", 2) == True) {
+ DEBUG(4, ("smb_pam_end: PAM: PAM_END OK.\n"));
+ return True;
+ }
+ }
+ DEBUG(2,("smb_pam_end: PAM: not initialised\n"));
+ return False;
+}
+
+/*
+ * Start PAM authentication for specified account
+ */
+
+static bool smb_pam_start(pam_handle_t **pamh, const char *user, const char *rhost, struct pam_conv *pconv)
+{
+ int pam_error;
+
+ *pamh = (pam_handle_t *)NULL;
+
+ DEBUG(4,("smb_pam_start: PAM: Init user: %s\n", user));
+
+ pam_error = pam_start("samba", user, pconv, pamh);
+ if( !smb_pam_error_handler(*pamh, pam_error, "Init Failed", 0)) {
+ *pamh = (pam_handle_t *)NULL;
+ return False;
+ }
+
+#ifdef HAVE_PAM_RHOST
+ DEBUG(4,("smb_pam_start: PAM: setting rhost to: %s\n", rhost));
+ pam_error = pam_set_item(*pamh, PAM_RHOST, rhost);
+ if(!smb_pam_error_handler(*pamh, pam_error, "set rhost failed", 0)) {
+ smb_pam_end(*pamh, pconv);
+ *pamh = (pam_handle_t *)NULL;
+ return False;
+ }
+#endif
+#ifdef HAVE_PAM_TTY
+ DEBUG(4,("smb_pam_start: PAM: setting tty\n"));
+ pam_error = pam_set_item(*pamh, PAM_TTY, "samba");
+ if (!smb_pam_error_handler(*pamh, pam_error, "set tty failed", 0)) {
+ smb_pam_end(*pamh, pconv);
+ *pamh = (pam_handle_t *)NULL;
+ return False;
+ }
+#endif
+ DEBUG(4,("smb_pam_start: PAM: Init passed for user: %s\n", user));
+ return True;
+}
+
+/*
+ * PAM Authentication Handler
+ */
+static NTSTATUS smb_pam_auth(pam_handle_t *pamh, const char *user)
+{
+ int pam_error;
+ NTSTATUS nt_status = NT_STATUS_LOGON_FAILURE;
+
+ /*
+ * To enable debugging set in /etc/pam.d/samba:
+ * auth required /lib/security/pam_pwdb.so nullok shadow audit
+ */
+
+ DEBUG(4,("smb_pam_auth: PAM: Authenticate User: %s\n", user));
+ pam_error = pam_authenticate(pamh, PAM_SILENT | (lp_null_passwords() ? 0 : PAM_DISALLOW_NULL_AUTHTOK));
+ switch( pam_error ){
+ case PAM_AUTH_ERR:
+ DEBUG(2, ("smb_pam_auth: PAM: Authentication Error for user %s\n", user));
+ break;
+ case PAM_CRED_INSUFFICIENT:
+ DEBUG(2, ("smb_pam_auth: PAM: Insufficient Credentials for user %s\n", user));
+ break;
+ case PAM_AUTHINFO_UNAVAIL:
+ DEBUG(2, ("smb_pam_auth: PAM: Authentication Information Unavailable for user %s\n", user));
+ break;
+ case PAM_USER_UNKNOWN:
+ DEBUG(2, ("smb_pam_auth: PAM: Username %s NOT known to Authentication system\n", user));
+ break;
+ case PAM_MAXTRIES:
+ DEBUG(2, ("smb_pam_auth: PAM: One or more authentication modules reports user limit for user %s exceeeded\n", user));
+ break;
+ case PAM_ABORT:
+ DEBUG(0, ("smb_pam_auth: PAM: One or more PAM modules failed to load for user %s\n", user));
+ break;
+ case PAM_SUCCESS:
+ DEBUG(4, ("smb_pam_auth: PAM: User %s Authenticated OK\n", user));
+ break;
+ default:
+ DEBUG(0, ("smb_pam_auth: PAM: UNKNOWN ERROR while authenticating user %s\n", user));
+ break;
+ }
+
+ smb_pam_nt_status_error_handler(pamh, pam_error, "Authentication Failure", 2, &nt_status);
+ return nt_status;
+}
+
+/*
+ * PAM Account Handler
+ */
+static NTSTATUS smb_pam_account(pam_handle_t *pamh, const char * user)
+{
+ int pam_error;
+ NTSTATUS nt_status = NT_STATUS_ACCOUNT_DISABLED;
+
+ DEBUG(4,("smb_pam_account: PAM: Account Management for User: %s\n", user));
+ pam_error = pam_acct_mgmt(pamh, PAM_SILENT); /* Is user account enabled? */
+ switch( pam_error ) {
+ case PAM_AUTHTOK_EXPIRED:
+ DEBUG(2, ("smb_pam_account: PAM: User %s is valid but password is expired\n", user));
+ break;
+ case PAM_ACCT_EXPIRED:
+ DEBUG(2, ("smb_pam_account: PAM: User %s no longer permitted to access system\n", user));
+ break;
+ case PAM_AUTH_ERR:
+ DEBUG(2, ("smb_pam_account: PAM: There was an authentication error for user %s\n", user));
+ break;
+ case PAM_PERM_DENIED:
+ DEBUG(0, ("smb_pam_account: PAM: User %s is NOT permitted to access system at this time\n", user));
+ break;
+ case PAM_USER_UNKNOWN:
+ DEBUG(0, ("smb_pam_account: PAM: User \"%s\" is NOT known to account management\n", user));
+ break;
+ case PAM_SUCCESS:
+ DEBUG(4, ("smb_pam_account: PAM: Account OK for User: %s\n", user));
+ break;
+ default:
+ DEBUG(0, ("smb_pam_account: PAM: UNKNOWN PAM ERROR (%d) during Account Management for User: %s\n", pam_error, user));
+ break;
+ }
+
+ smb_pam_nt_status_error_handler(pamh, pam_error, "Account Check Failed", 2, &nt_status);
+ return nt_status;
+}
+
+/*
+ * PAM Credential Setting
+ */
+
+static NTSTATUS smb_pam_setcred(pam_handle_t *pamh, const char * user)
+{
+ int pam_error;
+ NTSTATUS nt_status = NT_STATUS_NO_TOKEN;
+
+ /*
+ * This will allow samba to acquire a kerberos token. And, when
+ * exporting an AFS cell, be able to /write/ to this cell.
+ */
+
+ DEBUG(4,("PAM: Account Management SetCredentials for User: %s\n", user));
+ pam_error = pam_setcred(pamh, (PAM_ESTABLISH_CRED|PAM_SILENT));
+ switch( pam_error ) {
+ case PAM_CRED_UNAVAIL:
+ DEBUG(0, ("smb_pam_setcred: PAM: Credentials not found for user:%s\n", user ));
+ break;
+ case PAM_CRED_EXPIRED:
+ DEBUG(0, ("smb_pam_setcred: PAM: Credentials for user: \"%s\" EXPIRED!\n", user ));
+ break;
+ case PAM_USER_UNKNOWN:
+ DEBUG(0, ("smb_pam_setcred: PAM: User: \"%s\" is NOT known so can not set credentials!\n", user ));
+ break;
+ case PAM_CRED_ERR:
+ DEBUG(0, ("smb_pam_setcred: PAM: Unknown setcredentials error - unable to set credentials for %s\n", user ));
+ break;
+ case PAM_SUCCESS:
+ DEBUG(4, ("smb_pam_setcred: PAM: SetCredentials OK for User: %s\n", user));
+ break;
+ default:
+ DEBUG(0, ("smb_pam_setcred: PAM: UNKNOWN PAM ERROR (%d) during SetCredentials for User: %s\n", pam_error, user));
+ break;
+ }
+
+ smb_pam_nt_status_error_handler(pamh, pam_error, "Set Credential Failure", 2, &nt_status);
+ return nt_status;
+}
+
+/*
+ * PAM Internal Session Handler
+ */
+static bool smb_internal_pam_session(pam_handle_t *pamh, const char *user, const char *tty, bool flag)
+{
+ int pam_error;
+
+#ifdef HAVE_PAM_TTY
+ DEBUG(4,("smb_internal_pam_session: PAM: tty set to: %s\n", tty));
+ pam_error = pam_set_item(pamh, PAM_TTY, tty);
+ if (!smb_pam_error_handler(pamh, pam_error, "set tty failed", 0))
+ return False;
+#endif
+
+ if (flag) {
+ pam_error = pam_open_session(pamh, PAM_SILENT);
+ if (!smb_pam_error_handler(pamh, pam_error, "session setup failed", 0))
+ return False;
+ } else {
+ pam_setcred(pamh, (PAM_DELETE_CRED|PAM_SILENT)); /* We don't care if this fails */
+ pam_error = pam_close_session(pamh, PAM_SILENT); /* This will probably pick up the error anyway */
+ if (!smb_pam_error_handler(pamh, pam_error, "session close failed", 0))
+ return False;
+ }
+ return (True);
+}
+
+/*
+ * Internal PAM Password Changer.
+ */
+
+static bool smb_pam_chauthtok(pam_handle_t *pamh, const char * user)
+{
+ int pam_error;
+
+ DEBUG(4,("smb_pam_chauthtok: PAM: Password Change for User: %s\n", user));
+
+ pam_error = pam_chauthtok(pamh, PAM_SILENT); /* Change Password */
+
+ switch( pam_error ) {
+ case PAM_AUTHTOK_ERR:
+ DEBUG(2, ("PAM: unable to obtain the new authentication token - is password to weak?\n"));
+ break;
+
+ /* This doesn't seem to be defined on Solaris. JRA */
+#ifdef PAM_AUTHTOK_RECOVER_ERR
+ case PAM_AUTHTOK_RECOVER_ERR:
+ DEBUG(2, ("PAM: unable to obtain the old authentication token - was the old password wrong?.\n"));
+ break;
+#endif
+
+ case PAM_AUTHTOK_LOCK_BUSY:
+ DEBUG(2, ("PAM: unable to change the authentication token since it is currently locked.\n"));
+ break;
+ case PAM_AUTHTOK_DISABLE_AGING:
+ DEBUG(2, ("PAM: Authentication token aging has been disabled.\n"));
+ break;
+ case PAM_PERM_DENIED:
+ DEBUG(0, ("PAM: Permission denied.\n"));
+ break;
+ case PAM_TRY_AGAIN:
+ DEBUG(0, ("PAM: Could not update all authentication token(s). No authentication tokens were updated.\n"));
+ break;
+ case PAM_USER_UNKNOWN:
+ DEBUG(0, ("PAM: User not known to PAM\n"));
+ break;
+ case PAM_SUCCESS:
+ DEBUG(4, ("PAM: Account OK for User: %s\n", user));
+ break;
+ default:
+ DEBUG(0, ("PAM: UNKNOWN PAM ERROR (%d) for User: %s\n", pam_error, user));
+ }
+
+ if(!smb_pam_error_handler(pamh, pam_error, "Password Change Failed", 2)) {
+ return False;
+ }
+
+ /* If this point is reached, the password has changed. */
+ return True;
+}
+
+/*
+ * PAM Externally accessible Session handler
+ */
+
+bool smb_pam_claim_session(const char *user, const char *tty, const char *rhost)
+{
+ pam_handle_t *pamh = NULL;
+ struct pam_conv *pconv = NULL;
+
+ /* Ignore PAM if told to. */
+
+ if (!lp_obey_pam_restrictions())
+ return True;
+
+ if ((pconv = smb_setup_pam_conv(smb_pam_conv, user, NULL, NULL)) == NULL)
+ return False;
+
+ if (!smb_pam_start(&pamh, user, rhost, pconv))
+ return False;
+
+ if (!smb_internal_pam_session(pamh, user, tty, True)) {
+ smb_pam_end(pamh, pconv);
+ return False;
+ }
+
+ return smb_pam_end(pamh, pconv);
+}
+
+/*
+ * PAM Externally accessible Session handler
+ */
+
+bool smb_pam_close_session(const char *user, const char *tty, const char *rhost)
+{
+ pam_handle_t *pamh = NULL;
+ struct pam_conv *pconv = NULL;
+
+ /* Ignore PAM if told to. */
+
+ if (!lp_obey_pam_restrictions())
+ return True;
+
+ if ((pconv = smb_setup_pam_conv(smb_pam_conv, user, NULL, NULL)) == NULL)
+ return False;
+
+ if (!smb_pam_start(&pamh, user, rhost, pconv))
+ return False;
+
+ if (!smb_internal_pam_session(pamh, user, tty, False)) {
+ smb_pam_end(pamh, pconv);
+ return False;
+ }
+
+ return smb_pam_end(pamh, pconv);
+}
+
+/*
+ * PAM Externally accessible Account handler
+ */
+
+NTSTATUS smb_pam_accountcheck(const char *user, const char *rhost)
+{
+ NTSTATUS nt_status = NT_STATUS_ACCOUNT_DISABLED;
+ pam_handle_t *pamh = NULL;
+ struct pam_conv *pconv = NULL;
+
+ /* Ignore PAM if told to. */
+
+ if (!lp_obey_pam_restrictions())
+ return NT_STATUS_OK;
+
+ if ((pconv = smb_setup_pam_conv(smb_pam_conv, user, NULL, NULL)) == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ if (!smb_pam_start(&pamh, user, rhost, pconv))
+ return NT_STATUS_ACCOUNT_DISABLED;
+
+ if (!NT_STATUS_IS_OK(nt_status = smb_pam_account(pamh, user)))
+ DEBUG(0, ("smb_pam_accountcheck: PAM: Account Validation Failed - Rejecting User %s!\n", user));
+
+ smb_pam_end(pamh, pconv);
+ return nt_status;
+}
+
+/*
+ * PAM Password Validation Suite
+ */
+
+NTSTATUS smb_pam_passcheck(const char * user, const char * rhost,
+ const char * password)
+{
+ pam_handle_t *pamh = NULL;
+ NTSTATUS nt_status = NT_STATUS_LOGON_FAILURE;
+ struct pam_conv *pconv = NULL;
+
+ /*
+ * Note we can't ignore PAM here as this is the only
+ * way of doing auths on plaintext passwords when
+ * compiled --with-pam.
+ */
+
+ if ((pconv = smb_setup_pam_conv(smb_pam_conv, user, password, NULL)) == NULL)
+ return NT_STATUS_LOGON_FAILURE;
+
+ if (!smb_pam_start(&pamh, user, rhost, pconv))
+ return NT_STATUS_LOGON_FAILURE;
+
+ if (!NT_STATUS_IS_OK(nt_status = smb_pam_auth(pamh, user))) {
+ DEBUG(0, ("smb_pam_passcheck: PAM: smb_pam_auth failed - Rejecting User %s !\n", user));
+ smb_pam_end(pamh, pconv);
+ return nt_status;
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status = smb_pam_account(pamh, user))) {
+ DEBUG(0, ("smb_pam_passcheck: PAM: smb_pam_account failed - Rejecting User %s !\n", user));
+ smb_pam_end(pamh, pconv);
+ return nt_status;
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status = smb_pam_setcred(pamh, user))) {
+ DEBUG(0, ("smb_pam_passcheck: PAM: smb_pam_setcred failed - Rejecting User %s !\n", user));
+ smb_pam_end(pamh, pconv);
+ return nt_status;
+ }
+
+ smb_pam_end(pamh, pconv);
+ return nt_status;
+}
+
+/*
+ * PAM Password Change Suite
+ */
+
+bool smb_pam_passchange(const char *user, const char *rhost,
+ const char *oldpassword, const char *newpassword)
+{
+ /* Appropriate quantities of root should be obtained BEFORE calling this function */
+ struct pam_conv *pconv = NULL;
+ pam_handle_t *pamh = NULL;
+
+ if ((pconv = smb_setup_pam_conv(smb_pam_passchange_conv, user, oldpassword, newpassword)) == NULL)
+ return False;
+
+ if(!smb_pam_start(&pamh, user, rhost, pconv))
+ return False;
+
+ if (!smb_pam_chauthtok(pamh, user)) {
+ DEBUG(0, ("smb_pam_passchange: PAM: Password Change Failed for user %s!\n", user));
+ smb_pam_end(pamh, pconv);
+ return False;
+ }
+
+ return smb_pam_end(pamh, pconv);
+}
+
+#else
+
+/* If PAM not used, no PAM restrictions on accounts. */
+NTSTATUS smb_pam_accountcheck(const char *user, const char *rhost)
+{
+ return NT_STATUS_OK;
+}
+
+/* If PAM not used, also no PAM restrictions on sessions. */
+bool smb_pam_claim_session(const char *user, const char *tty, const char *rhost)
+{
+ return True;
+}
+
+/* If PAM not used, also no PAM restrictions on sessions. */
+bool smb_pam_close_session(const char *in_user, const char *tty, const char *rhost)
+{
+ return True;
+}
+#endif /* WITH_PAM */
diff --git a/source3/auth/pass_check.c b/source3/auth/pass_check.c
new file mode 100644
index 0000000..3b74c8f
--- /dev/null
+++ b/source3/auth/pass_check.c
@@ -0,0 +1,294 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password checking
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* this module is for checking a username/password against a system
+ password database. The SMB encrypted password support is elsewhere */
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "auth.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#if !defined(WITH_PAM)
+static char *ths_salt;
+/* This must be writable. */
+static char *get_this_salt(void)
+{
+ return ths_salt;
+}
+
+/* We may be setting a modified version of the same
+ * string, so don't free before use. */
+
+static const char *set_this_salt(const char *newsalt)
+{
+ char *orig_salt = ths_salt;
+ ths_salt = SMB_STRDUP(newsalt);
+ SAFE_FREE(orig_salt);
+ return ths_salt;
+}
+
+static char *ths_crypted;
+static const char *get_this_crypted(void)
+{
+ if (!ths_crypted) {
+ return "";
+ }
+ return ths_crypted;
+}
+
+static const char *set_this_crypted(const char *newcrypted)
+{
+ char *orig_crypted = ths_crypted;
+ ths_crypted = SMB_STRDUP(newcrypted);
+ SAFE_FREE(orig_crypted);
+ return ths_crypted;
+}
+#endif
+
+
+
+
+
+
+
+/****************************************************************************
+core of password checking routine
+****************************************************************************/
+static NTSTATUS password_check(const char *user, const char *password, const void *private_data)
+{
+#ifdef WITH_PAM
+ const char *rhost = (const char *)private_data;
+ return smb_pam_passcheck(user, rhost, password);
+#else
+
+ bool ret;
+
+
+
+
+#ifdef ULTRIX_AUTH
+ ret = (strcmp((char *)crypt16(password, get_this_salt()), get_this_crypted()) == 0);
+ if (ret) {
+ return NT_STATUS_OK;
+ } else {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+#endif /* ULTRIX_AUTH */
+
+
+
+#ifdef HAVE_BIGCRYPT
+ ret = (strcmp(bigcrypt(password, get_this_salt()), get_this_crypted()) == 0);
+ if (ret) {
+ return NT_STATUS_OK;
+ } else {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+#endif /* HAVE_BIGCRYPT */
+
+#ifndef HAVE_CRYPT
+ DEBUG(1, ("Warning - no crypt available\n"));
+ return NT_STATUS_LOGON_FAILURE;
+#else /* HAVE_CRYPT */
+ ret = (strcmp((char *)crypt(password, get_this_salt()), get_this_crypted()) == 0);
+ if (ret) {
+ return NT_STATUS_OK;
+ } else {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+#endif /* HAVE_CRYPT */
+#endif /* WITH_PAM */
+}
+
+
+
+/****************************************************************************
+CHECK if a username/password is OK
+the function pointer fn() points to a function to call when a successful
+match is found and is used to update the encrypted password file
+return NT_STATUS_OK on correct match, appropriate error otherwise
+****************************************************************************/
+
+NTSTATUS pass_check(const struct passwd *pass,
+ const char *user,
+ const char *rhost,
+ const char *password,
+ bool run_cracker)
+{
+ char *pass2 = NULL;
+
+ NTSTATUS nt_status;
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100, ("checking user=[%s] pass=[%s]\n", user, password));
+#endif
+
+ if (!password)
+ return NT_STATUS_LOGON_FAILURE;
+
+ if ((!*password) && !lp_null_passwords())
+ return NT_STATUS_LOGON_FAILURE;
+
+#if defined(WITH_PAM)
+
+ /*
+ * If we're using PAM we want to short-circuit all the
+ * checks below and dive straight into the PAM code.
+ */
+
+ DEBUG(4, ("pass_check: Checking (PAM) password for user %s\n", user));
+
+#else /* Not using PAM */
+
+ DEBUG(4, ("pass_check: Checking password for user %s\n", user));
+
+ if (!pass) {
+ DEBUG(3, ("Couldn't find user %s\n", user));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+
+ /* Copy into global for the convenience of looping code */
+ /* Also the place to keep the 'password' no matter what
+ crazy struct it started in... */
+ if (set_this_crypted(pass->pw_passwd) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (set_this_salt(pass->pw_passwd) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+#ifdef HAVE_GETSPNAM
+ {
+ struct spwd *spass;
+
+ /* many shadow systems require you to be root to get
+ the password, in most cases this should already be
+ the case when this function is called, except
+ perhaps for IPC password changing requests */
+
+ spass = getspnam(pass->pw_name);
+ if (spass && spass->sp_pwdp) {
+ if (set_this_crypted(spass->sp_pwdp) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (set_this_salt(spass->sp_pwdp) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+#elif defined(IA_UINFO)
+ {
+ /* Need to get password with SVR4.2's ia_ functions
+ instead of get{sp,pw}ent functions. Required by
+ UnixWare 2.x, tested on version
+ 2.1. (tangent@cyberport.com) */
+ uinfo_t uinfo;
+ if (ia_openinfo(pass->pw_name, &uinfo) != -1)
+ ia_get_logpwd(uinfo, &(pass->pw_passwd));
+ }
+#endif
+
+
+#ifdef HAVE_GETPWANAM
+ {
+ struct passwd_adjunct *pwret;
+ pwret = getpwanam(s);
+ if (pwret && pwret->pwa_passwd) {
+ if (set_this_crypted(pwret->pwa_passwd) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+#endif
+
+
+#ifdef ULTRIX_AUTH
+ {
+ AUTHORIZATION *ap = getauthuid(pass->pw_uid);
+ if (ap) {
+ if (set_this_crypted(ap->a_password) == NULL) {
+ endauthent();
+ return NT_STATUS_NO_MEMORY;
+ }
+ endauthent();
+ }
+ }
+#endif
+
+
+ if (!get_this_crypted() || !*get_this_crypted()) {
+ if (!lp_null_passwords()) {
+ DEBUG(2, ("Disallowing %s with null password\n",
+ user));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ if (!*password) {
+ DEBUG(3,
+ ("Allowing access to %s with null password\n",
+ user));
+ return NT_STATUS_OK;
+ }
+ }
+
+#endif /* defined(WITH_PAM) */
+
+ /* try it as it came to us */
+ nt_status = password_check(user, password, (const void *)rhost);
+ if NT_STATUS_IS_OK(nt_status) {
+ return (nt_status);
+ } else if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD)) {
+ /* No point continuing if its not the password that's to blame (ie PAM disabled). */
+ return (nt_status);
+ }
+
+ if (!run_cracker) {
+ return (nt_status);
+ }
+
+ /* if the password was given to us with mixed case then we don't
+ * need to proceed as we know it hasn't been case modified by the
+ * client */
+ if (strhasupper(password) && strhaslower(password)) {
+ return nt_status;
+ }
+
+ /* make a copy of it */
+ pass2 = talloc_strdup(talloc_tos(), password);
+ if (!pass2) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* try all lowercase if it's currently all uppercase */
+ if (strhasupper(pass2)) {
+ if (!strlower_m(pass2)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ nt_status = password_check(user, pass2, (const void *)rhost);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ return (nt_status);
+ }
+ }
+
+ return NT_STATUS_WRONG_PASSWORD;
+}
diff --git a/source3/auth/proto.h b/source3/auth/proto.h
new file mode 100644
index 0000000..d40422d
--- /dev/null
+++ b/source3/auth/proto.h
@@ -0,0 +1,438 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Password and authentication handling
+ *
+ * Copyright (C) Andrew Tridgell 1992-2001
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-2000
+ * Copyright (C) Jeremy Allison 1997-2001
+ * Copyright (C) John H Terpsta 1999-2001
+ * Copyright (C) Tim Potter 2000
+ * Copyright (C) Andrew Bartlett 2001-2003
+ * Copyright (C) Jelmer Vernooij 2002
+ * Copyright (C) Rafal Szczesniak 2002
+ * Copyright (C) Gerald Carter 2003
+ * Copyright (C) Volker Lendecke 2006,2010
+ * Copyright (C) Michael Adam 2007
+ * Copyright (C) Dan Sledz 2009
+ * Copyright (C) Simo Sorce 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _AUTH_PROTO_H_
+#define _AUTH_PROTO_H_
+
+/* The following definitions come from auth/auth.c */
+
+NTSTATUS smb_register_auth(int version, const char *name, auth_init_function init);
+NTSTATUS make_auth3_context_for_ntlm(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context);
+NTSTATUS make_auth3_context_for_netlogon(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context);
+NTSTATUS make_auth3_context_for_winbind(TALLOC_CTX *mem_ctx,
+ struct auth_context **auth_context);
+bool auth3_context_set_challenge(
+ struct auth_context *ctx,
+ const uint8_t chal[8],
+ const char *challenge_set_by);
+
+/****************************************************************************
+ Try to get a challenge out of the various authentication modules.
+ Returns a const char of length 8 bytes.
+****************************************************************************/
+
+NTSTATUS auth_get_ntlm_challenge(struct auth_context *auth_context,
+ uint8_t chal[8]);
+
+/**
+ * Check a user's Plaintext, LM or NTLM password.
+ *
+ * Check a user's password, as given in the user_info struct and return various
+ * interesting details in the server_info struct.
+ *
+ * This function does NOT need to be in a become_root()/unbecome_root() pair
+ * as it makes the calls itself when needed.
+ *
+ * The return value takes precedence over the contents of the server_info
+ * struct. When the return is other than NT_STATUS_OK the contents
+ * of that structure is undefined.
+ *
+ * @param mem_ctx The memory context to use to allocate server_info
+ *
+ * @param user_info Contains the user supplied components, including the passwords.
+ * Must be created with make_user_info() or one of its wrappers.
+ *
+ * @param auth_context Supplies the challenges and some other data.
+ * Must be created with make_auth_context(), and the challenges should be
+ * filled in, either at creation or by calling the challenge generation
+ * function auth_get_challenge().
+ *
+ * @param pserver_info If successful, contains information about the authentication,
+ * including a struct samu struct describing the user.
+ *
+ * @param pauthoritative Indicates if the result should be treated as final
+ * result.
+ *
+ * @return An NTSTATUS with NT_STATUS_OK or an appropriate error.
+ *
+ **/
+NTSTATUS auth_check_ntlm_password(TALLOC_CTX *mem_ctx,
+ const struct auth_context *auth_context,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **pserver_info,
+ uint8_t *pauthoritative);
+
+/* The following definitions come from auth/auth_builtin.c */
+
+NTSTATUS auth_builtin_init(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from auth/auth_generic.c */
+
+NTSTATUS make_auth4_context(TALLOC_CTX *mem_ctx, struct auth4_context **auth4_context_out);
+NTSTATUS auth_generic_prepare(TALLOC_CTX *mem_ctx,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ struct gensec_security **gensec_security_out);
+
+NTSTATUS auth_check_password_session_info(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info *user_info,
+ struct auth_session_info **session_info);
+
+/* The following definitions come from auth/auth_ntlmssp.c */
+
+NTSTATUS auth3_generate_session_info(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ void *server_returned_info,
+ const char *original_user_name,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info);
+
+NTSTATUS auth3_get_challenge(struct auth4_context *auth4_context,
+ uint8_t chal[8]);
+
+NTSTATUS auth3_set_challenge(struct auth4_context *auth4_context, const uint8_t *chal,
+ const char *challenge_set_by);
+
+struct tevent_req *auth3_check_password_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth4_context *auth4_context,
+ const struct auth_usersupplied_info *user_info);
+NTSTATUS auth3_check_password_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *pauthoritative,
+ void **server_returned_info,
+ DATA_BLOB *nt_session_key,
+ DATA_BLOB *lm_session_key);
+
+/* The following definitions come from auth/auth_sam.c */
+
+NTSTATUS check_sam_security(const DATA_BLOB *challenge,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info);
+NTSTATUS check_sam_security_info3(const DATA_BLOB *challenge,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct netr_SamInfo3 **pinfo3);
+NTSTATUS auth_sam_init(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from auth/auth_unix.c */
+
+NTSTATUS auth_unix_init(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from auth/auth_util.c */
+struct tsocket_address;
+
+NTSTATUS make_user_info_map(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ const DATA_BLOB *lm_pwd,
+ const DATA_BLOB *nt_pwd,
+ const struct samr_Password *lm_interactive_pwd,
+ const struct samr_Password *nt_interactive_pwd,
+ const char *plaintext,
+ enum auth_password_state password_state);
+bool make_user_info_netlogon_network(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ uint32_t logon_parameters,
+ const unsigned char *lm_network_pwd,
+ int lm_pwd_len,
+ const unsigned char *nt_network_pwd,
+ int nt_pwd_len);
+bool make_user_info_netlogon_interactive(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ uint32_t logon_parameters,
+ const unsigned char chal[8],
+ const unsigned char lm_interactive_pwd[16],
+ const unsigned char nt_interactive_pwd[16]);
+bool make_user_info_for_reply(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ const uint8_t chal[8],
+ DATA_BLOB plaintext_password);
+NTSTATUS make_user_info_for_reply_enc(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **user_info,
+ const char *smb_name,
+ const char *client_domain,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ DATA_BLOB lm_resp, DATA_BLOB nt_resp);
+bool make_user_info_guest(TALLOC_CTX *mem_ctx,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ struct auth_usersupplied_info **user_info);
+
+struct samu;
+NTSTATUS make_server_info_sam(TALLOC_CTX *mem_ctx,
+ struct samu *sampass,
+ struct auth_serversupplied_info **pserver_info);
+NTSTATUS create_local_token(TALLOC_CTX *mem_ctx,
+ const struct auth_serversupplied_info *server_info,
+ DATA_BLOB *session_key,
+ const char *smb_name,
+ struct auth_session_info **session_info_out);
+
+/*
+ * The unix name should be constructed as DOMAIN+ACCOUNT,
+ * while '+' will be the "winbind separator" character.
+ */
+#define AUTH3_UNIX_HINT_QUALIFIED_NAME 0x00000001
+/*
+ * The unix name will be just ACCOUNT
+ */
+#define AUTH3_UNIX_HINT_ISLOLATED_NAME 0x00000002
+/*
+ * Don't translate the nt token SIDS into uid/gids
+ */
+#define AUTH3_UNIX_HINT_DONT_TRANSLATE_FROM_SIDS 0x00000004
+/*
+ * Don't translate the unix token uid/gids to S-1-22-X-Y SIDS
+ */
+#define AUTH3_UNIX_HINT_DONT_TRANSLATE_TO_SIDS 0x00000008
+/*
+ * The unix token won't get expanded gid values
+ * from getgroups_unix_user()
+ */
+#define AUTH3_UNIX_HINT_DONT_EXPAND_UNIX_GROUPS 0x00000010
+NTSTATUS auth3_user_info_dc_add_hints(struct auth_user_info_dc *user_info_dc,
+ uid_t uid,
+ gid_t gid,
+ uint32_t flags);
+NTSTATUS create_token_from_username(TALLOC_CTX *mem_ctx, const char *username,
+ bool is_guest,
+ uid_t *uid, gid_t *gid,
+ char **found_username,
+ struct security_token **token);
+bool user_in_group_sid(const char *username, const struct dom_sid *group_sid);
+bool user_sid_in_group_sid(const struct dom_sid *sid, const struct dom_sid *group_sid);
+bool user_in_group(const char *username, const char *groupname);
+struct passwd;
+NTSTATUS make_server_info_pw(TALLOC_CTX *mem_ctx,
+ const char *unix_username,
+ const struct passwd *pwd,
+ struct auth_serversupplied_info **server_info);
+NTSTATUS make_session_info_from_username(TALLOC_CTX *mem_ctx,
+ const char *username,
+ bool is_guest,
+ struct auth_session_info **session_info);
+bool init_guest_session_info(TALLOC_CTX *mem_ctx);
+bool reinit_guest_session_info(TALLOC_CTX *mem_ctx);
+NTSTATUS init_system_session_info(TALLOC_CTX *mem_ctx);
+bool session_info_set_session_key(struct auth_session_info *info,
+ DATA_BLOB session_key);
+NTSTATUS make_server_info_guest(TALLOC_CTX *mem_ctx,
+ struct auth_serversupplied_info **server_info);
+NTSTATUS make_session_info_guest(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **server_info);
+NTSTATUS make_server_info_anonymous(TALLOC_CTX *mem_ctx,
+ struct auth_serversupplied_info **server_info);
+NTSTATUS make_session_info_anonymous(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **psession_info);
+NTSTATUS make_session_info_system(TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info);
+const struct auth_session_info *get_session_info_system(void);
+struct passwd *smb_getpwnam( TALLOC_CTX *mem_ctx, const char *domuser,
+ char **p_save_username, bool create );
+NTSTATUS make_server_info_info3(TALLOC_CTX *mem_ctx,
+ const char *sent_nt_username,
+ const char *domain,
+ struct auth_serversupplied_info **server_info,
+ const struct netr_SamInfo3 *info3);
+struct wbcAuthUserInfo;
+NTSTATUS make_server_info_wbcAuthUserInfo(TALLOC_CTX *mem_ctx,
+ const char *sent_nt_username,
+ const char *domain,
+ const struct wbcAuthUserInfo *info,
+ struct auth_serversupplied_info **server_info);
+bool is_trusted_domain(const char* dom_name);
+NTSTATUS session_extract_session_key(const struct auth_session_info *session_info, DATA_BLOB *session_key, enum session_key_use_intent intent);
+
+/* The following definitions come from auth/user_info.c */
+
+NTSTATUS make_user_info(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **ret_user_info,
+ const char *smb_name,
+ const char *internal_username,
+ const char *client_domain,
+ const char *domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ const DATA_BLOB *lm_pwd,
+ const DATA_BLOB *nt_pwd,
+ const struct samr_Password *lm_interactive_pwd,
+ const struct samr_Password *nt_interactive_pwd,
+ const char *plaintext_password,
+ enum auth_password_state password_state);
+
+NTSTATUS do_map_to_guest_server_info(TALLOC_CTX *mem_ctx,
+ NTSTATUS status,
+ const char *user,
+ const char *domain,
+ struct auth_serversupplied_info **server_info);
+
+/* The following definitions come from auth/auth_winbind.c */
+
+NTSTATUS auth_winbind_init(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from auth/server_info.c */
+
+struct netr_SamInfo2;
+struct netr_SamInfo3;
+struct netr_SamInfo6;
+
+struct auth_serversupplied_info *make_server_info(TALLOC_CTX *mem_ctx);
+NTSTATUS serverinfo_to_SamInfo2(struct auth_serversupplied_info *server_info,
+ struct netr_SamInfo2 *sam2);
+NTSTATUS serverinfo_to_SamInfo3(const struct auth_serversupplied_info *server_info,
+ struct netr_SamInfo3 *sam3);
+NTSTATUS serverinfo_to_SamInfo6(struct auth_serversupplied_info *server_info,
+ struct netr_SamInfo6 *sam6);
+NTSTATUS create_info3_from_pac_logon_info(TALLOC_CTX *mem_ctx,
+ const struct PAC_LOGON_INFO *logon_info,
+ struct netr_SamInfo3 **pp_info3);
+NTSTATUS create_info6_from_pac(TALLOC_CTX *mem_ctx,
+ const struct PAC_LOGON_INFO *logon_info,
+ const struct PAC_UPN_DNS_INFO *upn_dns_info,
+ struct netr_SamInfo6 **pp_info6);
+NTSTATUS samu_to_SamInfo3(TALLOC_CTX *mem_ctx,
+ struct samu *samu,
+ const char *login_server,
+ struct netr_SamInfo3 **_info3,
+ struct extra_auth_info *extra);
+NTSTATUS passwd_to_SamInfo3(TALLOC_CTX *mem_ctx,
+ const char *unix_username,
+ const struct passwd *pwd,
+ struct netr_SamInfo3 **pinfo3,
+ struct extra_auth_info *extra);
+
+/* The following definitions come from auth/pampass.c */
+
+bool smb_pam_claim_session(const char *user, const char *tty, const char *rhost);
+bool smb_pam_close_session(const char *user, const char *tty, const char *rhost);
+NTSTATUS smb_pam_accountcheck(const char *user, const char *rhost);
+NTSTATUS smb_pam_passcheck(const char * user, const char * rhost,
+ const char * password);
+bool smb_pam_passchange(const char *user, const char *rhost,
+ const char *oldpassword, const char *newpassword);
+
+/* The following definitions come from auth/pass_check.c */
+
+NTSTATUS pass_check(const struct passwd *pass,
+ const char *user,
+ const char *rhost,
+ const char *password,
+ bool run_cracker);
+
+/* The following definitions come from auth/token_util.c */
+
+bool nt_token_check_sid ( const struct dom_sid *sid, const struct security_token *token );
+bool nt_token_check_domain_rid( struct security_token *token, uint32_t rid );
+NTSTATUS get_root_nt_token( struct security_token **token );
+NTSTATUS add_aliases(const struct dom_sid *domain_sid,
+ struct security_token *token);
+NTSTATUS create_local_nt_token(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ bool is_guest,
+ int num_groupsids,
+ const struct dom_sid *groupsids,
+ struct security_token **token);
+NTSTATUS finalize_local_nt_token(struct security_token *result,
+ uint32_t session_info_flags);
+NTSTATUS get_user_sid_info3_and_extra(const struct netr_SamInfo3 *info3,
+ const struct extra_auth_info *extra,
+ struct dom_sid *sid);
+NTSTATUS create_local_nt_token_from_info3(TALLOC_CTX *mem_ctx,
+ bool is_guest,
+ const struct netr_SamInfo3 *info3,
+ const struct extra_auth_info *extra,
+ struct security_token **ntok);
+void debug_unix_user_token(int dbg_class, int dbg_lev, uid_t uid, gid_t gid,
+ int n_groups, gid_t *groups);
+
+/* The following definitions come from auth/user_util.c */
+
+bool map_username(TALLOC_CTX *ctx, const char *user_in, char **p_user_out);
+bool user_in_netgroup(TALLOC_CTX *ctx, const char *user, const char *ngname);
+bool user_in_list(TALLOC_CTX *ctx, const char *user, const char * const *list);
+
+/* The following definitions come from auth/user_krb5.c */
+struct PAC_LOGON_INFO;
+NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx,
+ const char *cli_name,
+ const char *princ_name,
+ bool *is_mapped,
+ bool *mapped_to_guest,
+ char **ntuser,
+ char **ntdomain,
+ char **username,
+ struct passwd **_pw);
+NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx,
+ char *ntuser,
+ char *ntdomain,
+ char *username,
+ struct passwd *pw,
+ bool mapped_to_guest, bool username_was_mapped,
+ struct auth_session_info **session_info);
+
+/* The following definitions come from auth/auth_samba4.c */
+
+NTSTATUS auth_samba4_init(TALLOC_CTX *mem_ctx);
+
+#endif /* _AUTH_PROTO_H_ */
diff --git a/source3/auth/server_info.c b/source3/auth/server_info.c
new file mode 100644
index 0000000..1eae636
--- /dev/null
+++ b/source3/auth/server_info.c
@@ -0,0 +1,789 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "lib/util_unixsids.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "rpc_client/util_netlogon.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "lib/winbind_util.h"
+#include "passdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+/***************************************************************************
+ Make a server_info struct. Free with TALLOC_FREE().
+***************************************************************************/
+
+struct auth_serversupplied_info *make_server_info(TALLOC_CTX *mem_ctx)
+{
+ struct auth_serversupplied_info *result;
+
+ result = talloc_zero(mem_ctx, struct auth_serversupplied_info);
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ /* Initialise the uid and gid values to something non-zero
+ which may save us from giving away root access if there
+ is a bug in allocating these fields. */
+
+ result->utok.uid = -1;
+ result->utok.gid = -1;
+
+ return result;
+}
+
+/****************************************************************************
+ inits a netr_SamInfo2 structure from an auth_serversupplied_info. sam2 must
+ already be initialized and is used as the talloc parent for its members.
+*****************************************************************************/
+
+NTSTATUS serverinfo_to_SamInfo2(struct auth_serversupplied_info *server_info,
+ struct netr_SamInfo2 *sam2)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ NTSTATUS status;
+
+ status = copy_netr_SamInfo3(sam2,
+ server_info->info3,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (server_info->session_key.length) {
+ memcpy(info3->base.key.key,
+ server_info->session_key.data,
+ MIN(sizeof(info3->base.key.key),
+ server_info->session_key.length));
+ }
+ if (server_info->lm_session_key.length) {
+ memcpy(info3->base.LMSessKey.key,
+ server_info->lm_session_key.data,
+ MIN(sizeof(info3->base.LMSessKey.key),
+ server_info->lm_session_key.length));
+ }
+
+ sam2->base = info3->base;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ inits a netr_SamInfo3 structure from an auth_serversupplied_info. sam3 must
+ already be initialized and is used as the talloc parent for its members.
+*****************************************************************************/
+
+NTSTATUS serverinfo_to_SamInfo3(const struct auth_serversupplied_info *server_info,
+ struct netr_SamInfo3 *sam3)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ NTSTATUS status;
+
+ status = copy_netr_SamInfo3(sam3,
+ server_info->info3,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (server_info->session_key.length) {
+ memcpy(info3->base.key.key,
+ server_info->session_key.data,
+ MIN(sizeof(info3->base.key.key),
+ server_info->session_key.length));
+ }
+ if (server_info->lm_session_key.length) {
+ memcpy(info3->base.LMSessKey.key,
+ server_info->lm_session_key.data,
+ MIN(sizeof(info3->base.LMSessKey.key),
+ server_info->lm_session_key.length));
+ }
+
+ sam3->base = info3->base;
+
+ sam3->sidcount = 0;
+ sam3->sids = NULL;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ inits a netr_SamInfo6 structure from an auth_serversupplied_info. sam6 must
+ already be initialized and is used as the talloc parent for its members.
+*****************************************************************************/
+
+NTSTATUS serverinfo_to_SamInfo6(struct auth_serversupplied_info *server_info,
+ struct netr_SamInfo6 *sam6)
+{
+ struct pdb_domain_info *dominfo;
+ struct netr_SamInfo3 *info3 = NULL;
+ NTSTATUS status;
+
+ if ((pdb_capabilities() & PDB_CAP_ADS) == 0) {
+ DEBUG(10,("Not adding validation info level 6 "
+ "without ADS passdb backend\n"));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ dominfo = pdb_get_domain_info(sam6);
+ if (dominfo == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = copy_netr_SamInfo3(sam6,
+ server_info->info3,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (server_info->session_key.length) {
+ memcpy(info3->base.key.key,
+ server_info->session_key.data,
+ MIN(sizeof(info3->base.key.key),
+ server_info->session_key.length));
+ }
+ if (server_info->lm_session_key.length) {
+ memcpy(info3->base.LMSessKey.key,
+ server_info->lm_session_key.data,
+ MIN(sizeof(info3->base.LMSessKey.key),
+ server_info->lm_session_key.length));
+ }
+
+ sam6->base = info3->base;
+
+ sam6->sidcount = 0;
+ sam6->sids = NULL;
+
+ sam6->dns_domainname.string = talloc_strdup(sam6, dominfo->dns_domain);
+ if (sam6->dns_domainname.string == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sam6->principal_name.string = talloc_asprintf(
+ sam6, "%s@%s", sam6->base.account_name.string,
+ sam6->dns_domainname.string);
+ if (sam6->principal_name.string == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS append_netr_SidAttr(TALLOC_CTX *mem_ctx,
+ struct netr_SidAttr **sids,
+ uint32_t *count,
+ const struct dom_sid2 *asid,
+ uint32_t attributes)
+{
+ uint32_t t = *count;
+
+ *sids = talloc_realloc(mem_ctx, *sids, struct netr_SidAttr, t + 1);
+ if (*sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ (*sids)[t].sid = dom_sid_dup(*sids, asid);
+ if ((*sids)[t].sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ (*sids)[t].attributes = attributes;
+ *count = t + 1;
+
+ return NT_STATUS_OK;
+}
+
+/* Fills the samr_RidWithAttributeArray with the provided sids.
+ * If it happens that we have additional groups that do not belong
+ * to the domain, add their sids as extra sids */
+static NTSTATUS group_sids_to_info3(struct netr_SamInfo3 *info3,
+ const struct dom_sid *sids,
+ size_t num_sids)
+{
+ uint32_t attributes = SE_GROUP_DEFAULT_FLAGS;
+ struct samr_RidWithAttributeArray *groups;
+ struct dom_sid *domain_sid;
+ unsigned int i;
+ NTSTATUS status;
+ uint32_t rid;
+ bool ok;
+
+ domain_sid = info3->base.domain_sid;
+ groups = &info3->base.groups;
+
+ groups->rids = talloc_array(info3,
+ struct samr_RidWithAttribute, num_sids);
+ if (!groups->rids) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_sids; i++) {
+ ok = sid_peek_check_rid(domain_sid, &sids[i], &rid);
+ if (ok) {
+ /* store domain group rid */
+ groups->rids[groups->count].rid = rid;
+ groups->rids[groups->count].attributes = attributes;
+ groups->count++;
+ continue;
+ }
+
+ /* if this wasn't a domain sid, add it as extra sid */
+ status = append_netr_SidAttr(info3, &info3->sids,
+ &info3->sidcount,
+ &sids[i], attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Merge resource SIDs, if any, into the passed in info3 structure.
+ */
+
+static NTSTATUS merge_resource_sids(const struct PAC_LOGON_INFO *logon_info,
+ struct netr_SamInfo3 *info3)
+{
+ uint32_t i = 0;
+ const struct PAC_DOMAIN_GROUP_MEMBERSHIP *rg = NULL;
+
+ if (logon_info->info3.base.user_flags & NETLOGON_RESOURCE_GROUPS) {
+ rg = &logon_info->resource_groups;
+ }
+
+ if (rg == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (rg->domain_sid == NULL) {
+ DEBUG(10, ("Missing Resource Group Domain SID\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* The IDL layer would be a better place to check this, but to
+ * guard the integer addition below, we double-check */
+ if (rg->groups.count > 65535) {
+ DEBUG(10, ("Too much Resource Group RIDs %u\n",
+ (unsigned)rg->groups.count));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * If there are any resource groups (SID Compression) add
+ * them to the extra sids portion of the info3 in the PAC.
+ *
+ * This makes the info3 look like it would if we got the info
+ * from the DC rather than the PAC.
+ */
+
+ /*
+ * Construct a SID for each RID in the list and then append it
+ * to the info3.
+ */
+ for (i = 0; i < rg->groups.count; i++) {
+ NTSTATUS status;
+ struct dom_sid new_sid;
+ uint32_t attributes = rg->groups.rids[i].attributes;
+ struct dom_sid_buf buf;
+
+ sid_compose(&new_sid,
+ rg->domain_sid,
+ rg->groups.rids[i].rid);
+
+ DEBUG(10, ("Adding SID %s to extra SIDS\n",
+ dom_sid_str_buf(&new_sid, &buf)));
+
+ status = append_netr_SidAttr(info3, &info3->sids,
+ &info3->sidcount,
+ &new_sid,
+ attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to append SID %s to extra SIDS: %s\n",
+ dom_sid_str_buf(&new_sid, &buf),
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Create a copy of an info3 struct from the struct PAC_LOGON_INFO,
+ * then merge resource SIDs, if any, into it. If successful return
+ * the created info3 struct.
+ */
+
+NTSTATUS create_info3_from_pac_logon_info(TALLOC_CTX *mem_ctx,
+ const struct PAC_LOGON_INFO *logon_info,
+ struct netr_SamInfo3 **pp_info3)
+{
+ NTSTATUS status;
+ struct netr_SamInfo3 *info3 = NULL;
+
+ status = copy_netr_SamInfo3(mem_ctx,
+ &logon_info->info3,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = merge_resource_sids(logon_info, info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info3);
+ return status;
+ }
+ *pp_info3 = info3;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Create a copy of an info6 struct from the PAC_UPN_DNS_INFO and PAC_LOGON_INFO
+ * then merge resource SIDs, if any, into it. If successful return the created
+ * info6 struct.
+ */
+NTSTATUS create_info6_from_pac(TALLOC_CTX *mem_ctx,
+ const struct PAC_LOGON_INFO *logon_info,
+ const struct PAC_UPN_DNS_INFO *upn_dns_info,
+ struct netr_SamInfo6 **pp_info6)
+{
+ NTSTATUS status;
+ struct netr_SamInfo6 *info6 = NULL;
+ struct netr_SamInfo3 *info3 = NULL;
+
+ info6 = talloc_zero(mem_ctx, struct netr_SamInfo6);
+ if (info6 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = copy_netr_SamInfo3(info6,
+ &logon_info->info3,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info6);
+ return status;
+ }
+
+ status = merge_resource_sids(logon_info, info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info6);
+ return status;
+ }
+
+ info6->base = info3->base;
+ info6->sids = info3->sids;
+ info6->sidcount = info3->sidcount;
+
+ if (upn_dns_info != NULL) {
+ info6->dns_domainname.string = talloc_strdup(info6,
+ upn_dns_info->dns_domain_name);
+ if (info6->dns_domainname.string == NULL) {
+ TALLOC_FREE(info6);
+ return NT_STATUS_NO_MEMORY;
+ }
+ info6->principal_name.string = talloc_strdup(info6,
+ upn_dns_info->upn_name);
+ if (info6->principal_name.string == NULL) {
+ TALLOC_FREE(info6);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *pp_info6 = info6;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Check if this is a "Unix Users" domain user, or a
+ * "Unix Groups" domain group, we need to handle it
+ * in a special way if that's the case.
+ */
+
+static NTSTATUS SamInfo3_handle_sids(const char *username,
+ const struct dom_sid *user_sid,
+ const struct dom_sid *group_sid,
+ struct netr_SamInfo3 *info3,
+ struct dom_sid *domain_sid,
+ struct extra_auth_info *extra)
+{
+ struct dom_sid_buf buf;
+
+ if (sid_check_is_in_unix_users(user_sid)) {
+ /* in info3 you can only set rids for the user and the
+ * primary group, and the domain sid must be that of
+ * the sam domain.
+ *
+ * Store a completely bogus value here.
+ * The real SID is stored in the extra sids.
+ * Other code will know to look there if (-1) is found
+ */
+ info3->base.rid = (uint32_t)(-1);
+ sid_copy(&extra->user_sid, user_sid);
+
+ DEBUG(10, ("Unix User found. Rid marked as "
+ "special and sid (%s) saved as extra sid\n",
+ dom_sid_str_buf(user_sid, &buf)));
+ } else {
+ sid_copy(domain_sid, user_sid);
+ sid_split_rid(domain_sid, &info3->base.rid);
+ }
+
+ if (is_null_sid(domain_sid)) {
+ sid_copy(domain_sid, get_global_sam_sid());
+ }
+
+ /* check if this is a "Unix Groups" domain group,
+ * if so we need special handling */
+ if (sid_check_is_in_unix_groups(group_sid)) {
+ /* in info3 you can only set rids for the user and the
+ * primary group, and the domain sid must be that of
+ * the sam domain.
+ *
+ * Store a completely bogus value here.
+ * The real SID is stored in the extra sids.
+ * Other code will know to look there if (-1) is found
+ */
+ info3->base.primary_gid = (uint32_t)(-1);
+ sid_copy(&extra->pgid_sid, group_sid);
+
+ DEBUG(10, ("Unix Group found. Rid marked as "
+ "special and sid (%s) saved as extra sid\n",
+ dom_sid_str_buf(group_sid, &buf)));
+ } else {
+ bool ok = sid_peek_check_rid(domain_sid, group_sid,
+ &info3->base.primary_gid);
+ if (!ok) {
+ struct dom_sid_buf buf2, buf3;
+ DEBUG(1, ("The primary group domain sid(%s) does not "
+ "match the domain sid(%s) for %s(%s)\n",
+ dom_sid_str_buf(group_sid, &buf),
+ dom_sid_str_buf(domain_sid, &buf2),
+ username,
+ dom_sid_str_buf(user_sid, &buf3)));
+ return NT_STATUS_INVALID_SID;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+#define RET_NOMEM(ptr) do { \
+ if (!ptr) { \
+ TALLOC_FREE(info3); \
+ return NT_STATUS_NO_MEMORY; \
+ } } while(0)
+
+NTSTATUS samu_to_SamInfo3(TALLOC_CTX *mem_ctx,
+ struct samu *samu,
+ const char *login_server,
+ struct netr_SamInfo3 **_info3,
+ struct extra_auth_info *extra)
+{
+ struct netr_SamInfo3 *info3;
+ const struct dom_sid *user_sid;
+ const struct dom_sid *group_sid;
+ struct dom_sid domain_sid = {0};
+ struct dom_sid *group_sids;
+ uint32_t num_group_sids = 0;
+ const char *tmp;
+ gid_t *gids;
+ NTSTATUS status;
+
+ user_sid = pdb_get_user_sid(samu);
+ group_sid = pdb_get_group_sid(samu);
+
+ if (!user_sid || !group_sid) {
+ DEBUG(1, ("Sam account is missing sids!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ info3 = talloc_zero(mem_ctx, struct netr_SamInfo3);
+ if (!info3) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = SamInfo3_handle_sids(pdb_get_username(samu),
+ user_sid,
+ group_sid,
+ info3,
+ &domain_sid,
+ extra);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info3);
+ return status;
+ }
+
+ unix_to_nt_time(&info3->base.logon_time, pdb_get_logon_time(samu));
+ unix_to_nt_time(&info3->base.logoff_time, get_time_t_max());
+ unix_to_nt_time(&info3->base.kickoff_time, get_time_t_max());
+ unix_to_nt_time(&info3->base.last_password_change,
+ pdb_get_pass_last_set_time(samu));
+ unix_to_nt_time(&info3->base.allow_password_change,
+ pdb_get_pass_can_change_time(samu));
+ unix_to_nt_time(&info3->base.force_password_change,
+ pdb_get_pass_must_change_time(samu));
+
+ tmp = pdb_get_username(samu);
+ if (tmp) {
+ info3->base.account_name.string = talloc_strdup(info3, tmp);
+ RET_NOMEM(info3->base.account_name.string);
+ }
+ tmp = pdb_get_fullname(samu);
+ if (tmp) {
+ info3->base.full_name.string = talloc_strdup(info3, tmp);
+ RET_NOMEM(info3->base.full_name.string);
+ }
+ tmp = pdb_get_logon_script(samu);
+ if (tmp) {
+ info3->base.logon_script.string = talloc_strdup(info3, tmp);
+ RET_NOMEM(info3->base.logon_script.string);
+ }
+ tmp = pdb_get_profile_path(samu);
+ if (tmp) {
+ info3->base.profile_path.string = talloc_strdup(info3, tmp);
+ RET_NOMEM(info3->base.profile_path.string);
+ }
+ tmp = pdb_get_homedir(samu);
+ if (tmp) {
+ info3->base.home_directory.string = talloc_strdup(info3, tmp);
+ RET_NOMEM(info3->base.home_directory.string);
+ }
+ tmp = pdb_get_dir_drive(samu);
+ if (tmp) {
+ info3->base.home_drive.string = talloc_strdup(info3, tmp);
+ RET_NOMEM(info3->base.home_drive.string);
+ }
+
+ info3->base.logon_count = pdb_get_logon_count(samu);
+ info3->base.bad_password_count = pdb_get_bad_password_count(samu);
+
+ info3->base.logon_domain.string = talloc_strdup(info3,
+ pdb_get_domain(samu));
+ RET_NOMEM(info3->base.logon_domain.string);
+
+ info3->base.domain_sid = dom_sid_dup(info3, &domain_sid);
+ RET_NOMEM(info3->base.domain_sid);
+
+ status = pdb_enum_group_memberships(mem_ctx, samu,
+ &group_sids, &gids,
+ &num_group_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to get groups from sam account.\n"));
+ TALLOC_FREE(info3);
+ return status;
+ }
+
+ if (num_group_sids) {
+ status = group_sids_to_info3(info3, group_sids, num_group_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info3);
+ return status;
+ }
+ }
+
+ /* We don't need sids and gids after the conversion */
+ TALLOC_FREE(group_sids);
+ TALLOC_FREE(gids);
+ num_group_sids = 0;
+
+ /* FIXME: should we add other flags ? */
+ info3->base.user_flags = NETLOGON_EXTRA_SIDS;
+
+ if (login_server) {
+ info3->base.logon_server.string = talloc_strdup(info3, login_server);
+ RET_NOMEM(info3->base.logon_server.string);
+ }
+
+ info3->base.acct_flags = pdb_get_acct_ctrl(samu);
+
+ *_info3 = info3;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS passwd_to_SamInfo3(TALLOC_CTX *mem_ctx,
+ const char *unix_username,
+ const struct passwd *pwd,
+ struct netr_SamInfo3 **pinfo3,
+ struct extra_auth_info *extra)
+{
+ struct netr_SamInfo3 *info3;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+ const char *domain_name = NULL;
+ const char *user_name = NULL;
+ struct dom_sid domain_sid;
+ struct dom_sid user_sid;
+ struct dom_sid group_sid;
+ enum lsa_SidType type;
+ uint32_t num_sids = 0;
+ struct dom_sid *user_sids = NULL;
+ bool is_null;
+ bool ok;
+
+ tmp_ctx = talloc_stackframe();
+
+ ok = lookup_name_smbconf(tmp_ctx,
+ unix_username,
+ LOOKUP_NAME_ALL,
+ &domain_name,
+ &user_name,
+ &user_sid,
+ &type);
+ if (!ok) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ if (type != SID_NAME_USER) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ ok = winbind_lookup_usersids(tmp_ctx,
+ &user_sid,
+ &num_sids,
+ &user_sids);
+ /* Check if winbind is running */
+ if (ok) {
+ /*
+ * Winbind is running and the first element of the user_sids
+ * is the primary group.
+ */
+ if (num_sids > 0) {
+ group_sid = user_sids[0];
+ }
+ } else {
+ /*
+ * Winbind is not running, try to create the group_sid from the
+ * passwd group id.
+ */
+
+ /*
+ * This can lead to a primary group of S-1-22-2-XX which
+ * will be rejected by other Samba code.
+ */
+ gid_to_sid(&group_sid, pwd->pw_gid);
+ }
+
+ /*
+ * If we are a unix group, or a wellknown/builtin alias,
+ * set the group_sid to the
+ * 'Domain Users' RID of 513 which will always resolve to a
+ * name.
+ */
+ if (sid_check_is_in_unix_groups(&group_sid) ||
+ sid_check_is_in_builtin(&group_sid) ||
+ sid_check_is_in_wellknown_domain(&group_sid)) {
+ if (sid_check_is_in_unix_users(&user_sid)) {
+ sid_compose(&group_sid,
+ get_global_sam_sid(),
+ DOMAIN_RID_USERS);
+ } else {
+ sid_copy(&domain_sid, &user_sid);
+ sid_split_rid(&domain_sid, NULL);
+ sid_compose(&group_sid,
+ &domain_sid,
+ DOMAIN_RID_USERS);
+ }
+ }
+
+ /* Make sure we have a valid group sid */
+ is_null = is_null_sid(&group_sid);
+ if (is_null) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ /* Construct a netr_SamInfo3 from the information we have */
+ info3 = talloc_zero(tmp_ctx, struct netr_SamInfo3);
+ if (!info3) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ info3->base.account_name.string = talloc_strdup(info3, unix_username);
+ if (info3->base.account_name.string == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ info3->base.logon_domain.string = talloc_strdup(info3, domain_name);
+ if (info3->base.logon_domain.string == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ZERO_STRUCT(domain_sid);
+
+ status = SamInfo3_handle_sids(unix_username,
+ &user_sid,
+ &group_sid,
+ info3,
+ &domain_sid,
+ extra);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ info3->base.domain_sid = dom_sid_dup(info3, &domain_sid);
+ if (info3->base.domain_sid == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ok = sid_peek_check_rid(&domain_sid, &group_sid,
+ &info3->base.primary_gid);
+ if (!ok) {
+ struct dom_sid_buf buf1, buf2, buf3;
+
+ DEBUG(1, ("The primary group domain sid(%s) does not "
+ "match the domain sid(%s) for %s(%s)\n",
+ dom_sid_str_buf(&group_sid, &buf1),
+ dom_sid_str_buf(&domain_sid, &buf2),
+ unix_username,
+ dom_sid_str_buf(&user_sid, &buf3)));
+ status = NT_STATUS_INVALID_SID;
+ goto done;
+ }
+
+ info3->base.acct_flags = ACB_NORMAL;
+
+ if (num_sids) {
+ status = group_sids_to_info3(info3, user_sids, num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ *pinfo3 = talloc_move(mem_ctx, &info3);
+
+ status = NT_STATUS_OK;
+done:
+ talloc_free(tmp_ctx);
+
+ return status;
+}
diff --git a/source3/auth/server_info_sam.c b/source3/auth/server_info_sam.c
new file mode 100644
index 0000000..71a52f8
--- /dev/null
+++ b/source3/auth/server_info_sam.c
@@ -0,0 +1,124 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 2000-2001
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Volker Lendecke 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "nsswitch/winbind_client.h"
+#include "passdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+
+/***************************************************************************
+ Is the incoming username our own machine account ?
+ If so, the connection is almost certainly from winbindd.
+***************************************************************************/
+
+static bool is_our_machine_account(const char *username)
+{
+ bool ret;
+ size_t ulen = strlen(username);
+ const char *nb_name = lp_netbios_name();
+ size_t nb_namelen = strlen(nb_name);
+
+ if (ulen == 0 || username[ulen-1] != '$') {
+ return false;
+ }
+ if (nb_namelen != ulen-1) {
+ return false;
+ }
+ ret = strnequal(username, nb_name, ulen-1);
+ return ret;
+}
+
+/***************************************************************************
+ Make (and fill) a user_info struct from a struct samu
+***************************************************************************/
+
+NTSTATUS make_server_info_sam(TALLOC_CTX *mem_ctx,
+ struct samu *sampass,
+ struct auth_serversupplied_info **pserver_info)
+{
+ struct passwd *pwd;
+ struct auth_serversupplied_info *server_info;
+ const char *username = pdb_get_username(sampass);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+
+ server_info = make_server_info(tmp_ctx);
+ if (server_info == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ pwd = Get_Pwnam_alloc(tmp_ctx, username);
+ if (pwd == NULL) {
+ DEBUG(1, ("User %s in passdb, but getpwnam() fails!\n",
+ pdb_get_username(sampass)));
+ status = NT_STATUS_NO_SUCH_USER;
+ goto out;
+ }
+
+ status = samu_to_SamInfo3(server_info,
+ sampass,
+ lp_netbios_name(),
+ &server_info->info3,
+ &server_info->extra);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ server_info->unix_name = talloc_move(server_info, &pwd->pw_name);
+
+ server_info->utok.gid = pwd->pw_gid;
+ server_info->utok.uid = pwd->pw_uid;
+
+ if (IS_DC && is_our_machine_account(username)) {
+ /*
+ * This is a hack of monstrous proportions.
+ * If we know it's winbindd talking to us,
+ * we know we must never recurse into it,
+ * so turn off contacting winbindd for this
+ * entire process. This will get fixed when
+ * winbindd doesn't need to talk to smbd on
+ * a PDC. JRA.
+ */
+
+ (void)winbind_off();
+
+ DEBUG(10, ("make_server_info_sam: our machine account %s "
+ "turning off winbindd requests.\n", username));
+ }
+
+ DEBUG(5,("make_server_info_sam: made server info for user %s -> %s\n",
+ pdb_get_username(sampass), server_info->unix_name));
+
+ *pserver_info = talloc_move(mem_ctx, &server_info);
+
+ status = NT_STATUS_OK;
+out:
+ talloc_free(tmp_ctx);
+
+ return status;
+}
diff --git a/source3/auth/token_util.c b/source3/auth/token_util.c
new file mode 100644
index 0000000..023ad7c
--- /dev/null
+++ b/source3/auth/token_util.c
@@ -0,0 +1,1338 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Authentication utility functions
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Jeremy Allison 2000-2001
+ * Copyright (C) Rafal Szczesniak 2002
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* functions moved from auth/auth_util.c to minimize linker deps */
+
+#include "includes.h"
+#include "lib/util_unixsids.h"
+#include "system/passwd.h"
+#include "auth.h"
+#include "secrets.h"
+#include "../lib/util/memcache.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/util_pw.h"
+#include "passdb.h"
+#include "lib/privileges.h"
+
+/****************************************************************************
+ Check for a SID in an struct security_token
+****************************************************************************/
+
+bool nt_token_check_sid ( const struct dom_sid *sid, const struct security_token *token )
+{
+ if ( !sid || !token )
+ return False;
+
+ return security_token_has_sid(token, sid);
+}
+
+bool nt_token_check_domain_rid( struct security_token *token, uint32_t rid )
+{
+ struct dom_sid domain_sid;
+
+ /* if we are a domain member, the get the domain SID, else for
+ a DC or standalone server, use our own SID */
+
+ if ( lp_server_role() == ROLE_DOMAIN_MEMBER ) {
+ if ( !secrets_fetch_domain_sid( lp_workgroup(),
+ &domain_sid ) ) {
+ DEBUG(1,("nt_token_check_domain_rid: Cannot lookup "
+ "SID for domain [%s]\n", lp_workgroup()));
+ return False;
+ }
+ }
+ else
+ sid_copy( &domain_sid, get_global_sam_sid() );
+
+ sid_append_rid( &domain_sid, rid );
+
+ return nt_token_check_sid( &domain_sid, token );\
+}
+
+/******************************************************************************
+ Create a token for the root user to be used internally by smbd.
+ This is similar to running under the context of the LOCAL_SYSTEM account
+ in Windows. This is a read-only token. Do not modify it or free() it.
+ Create a copy if you need to change it.
+******************************************************************************/
+
+NTSTATUS get_root_nt_token( struct security_token **token )
+{
+ struct security_token *for_cache;
+ struct dom_sid u_sid, g_sid;
+ struct passwd *pw;
+ void *cache_data;
+ NTSTATUS status = NT_STATUS_OK;
+
+ cache_data = memcache_lookup_talloc(
+ NULL, SINGLETON_CACHE_TALLOC,
+ data_blob_string_const_null("root_nt_token"));
+
+ if (cache_data != NULL) {
+ *token = talloc_get_type_abort(
+ cache_data, struct security_token);
+ return NT_STATUS_OK;
+ }
+
+ if ( !(pw = getpwuid(0)) ) {
+ if ( !(pw = getpwnam("root")) ) {
+ DBG_ERR("get_root_nt_token: both getpwuid(0) "
+ "and getpwnam(\"root\") failed!\n");
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ }
+
+ /* get the user and primary group SIDs; although the
+ BUILTIN\Administrators SId is really the one that matters here */
+
+ uid_to_sid(&u_sid, pw->pw_uid);
+ gid_to_sid(&g_sid, pw->pw_gid);
+
+ status = create_local_nt_token(talloc_tos(), &u_sid, False,
+ 1, &global_sid_Builtin_Administrators, token);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ security_token_set_privilege(*token, SEC_PRIV_DISK_OPERATOR);
+
+ for_cache = *token;
+
+ memcache_add_talloc(
+ NULL, SINGLETON_CACHE_TALLOC,
+ data_blob_string_const_null("root_nt_token"), &for_cache);
+
+ return status;
+}
+
+
+/*
+ * Add alias SIDs from memberships within the partially created token SID list
+ */
+
+NTSTATUS add_aliases(const struct dom_sid *domain_sid,
+ struct security_token *token)
+{
+ uint32_t *aliases;
+ size_t i, num_aliases;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ if (!(tmp_ctx = talloc_init("add_aliases"))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ aliases = NULL;
+ num_aliases = 0;
+
+ status = pdb_enum_alias_memberships(tmp_ctx, domain_sid,
+ token->sids,
+ token->num_sids,
+ &aliases, &num_aliases);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("pdb_enum_alias_memberships failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ for (i=0; i<num_aliases; i++) {
+ struct dom_sid alias_sid;
+ sid_compose(&alias_sid, domain_sid, aliases[i]);
+ status = add_sid_to_array_unique(token, &alias_sid,
+ &token->sids,
+ &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("add_sid_to_array failed\n"));
+ goto done;
+ }
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static NTSTATUS add_builtin_administrators(struct security_token *token,
+ const struct dom_sid *dom_sid)
+{
+ struct dom_sid domadm;
+ NTSTATUS status;
+
+ /* nothing to do if we aren't in a domain */
+
+ if ( !(IS_DC || lp_server_role()==ROLE_DOMAIN_MEMBER) ) {
+ return NT_STATUS_OK;
+ }
+
+ /* Find the Domain Admins SID */
+
+ if ( IS_DC ) {
+ sid_copy( &domadm, get_global_sam_sid() );
+ } else {
+ if (dom_sid == NULL) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+ sid_copy(&domadm, dom_sid);
+ }
+ sid_append_rid( &domadm, DOMAIN_RID_ADMINS );
+
+ /* Add Administrators if the user beloongs to Domain Admins */
+
+ if ( nt_token_check_sid( &domadm, token ) ) {
+ status = add_sid_to_array(token,
+ &global_sid_Builtin_Administrators,
+ &token->sids, &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS add_builtin_guests(struct security_token *token,
+ const struct dom_sid *dom_sid)
+{
+ struct dom_sid tmp_sid;
+ NTSTATUS status;
+
+ /*
+ * First check the local GUEST account.
+ */
+ sid_compose(&tmp_sid, get_global_sam_sid(), DOMAIN_RID_GUEST);
+
+ if (nt_token_check_sid(&tmp_sid, token)) {
+ status = add_sid_to_array_unique(token,
+ &global_sid_Builtin_Guests,
+ &token->sids, &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * First check the local GUESTS group.
+ */
+ sid_compose(&tmp_sid, get_global_sam_sid(), DOMAIN_RID_GUESTS);
+
+ if (nt_token_check_sid(&tmp_sid, token)) {
+ status = add_sid_to_array_unique(token,
+ &global_sid_Builtin_Guests,
+ &token->sids, &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+ }
+
+ if (lp_server_role() != ROLE_DOMAIN_MEMBER) {
+ return NT_STATUS_OK;
+ }
+
+ if (dom_sid == NULL) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ /*
+ * First check the domain GUESTS group.
+ */
+ sid_copy(&tmp_sid, dom_sid);
+ sid_append_rid(&tmp_sid, DOMAIN_RID_GUESTS);
+
+ if (nt_token_check_sid(&tmp_sid, token)) {
+ status = add_sid_to_array_unique(token,
+ &global_sid_Builtin_Guests,
+ &token->sids, &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS add_local_groups(struct security_token *result,
+ bool is_guest);
+
+NTSTATUS get_user_sid_info3_and_extra(const struct netr_SamInfo3 *info3,
+ const struct extra_auth_info *extra,
+ struct dom_sid *sid)
+{
+ /* USER SID */
+ if (info3->base.rid == (uint32_t)(-1)) {
+ /* this is a signal the user was fake and generated,
+ * the actual SID we want to use is stored in the extra
+ * sids */
+ if (is_null_sid(&extra->user_sid)) {
+ /* we couldn't find the user sid, bail out */
+ DEBUG(3, ("Invalid user SID\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ sid_copy(sid, &extra->user_sid);
+ } else {
+ sid_copy(sid, info3->base.domain_sid);
+ sid_append_rid(sid, info3->base.rid);
+ }
+ return NT_STATUS_OK;
+}
+
+static struct security_token *init_local_nt_token(TALLOC_CTX *mem_ctx)
+{
+ /*
+ * We do not have a method to populate the claims into this
+ * buffer in the source3/ stack. When that changes, we will
+ * instead make this optional based on lp_acl_claims_evaluation()
+ */
+
+ struct security_token *result
+ = security_token_initialise(mem_ctx,
+ CLAIMS_EVALUATION_NEVER);
+
+ if (result == NULL) {
+ DBG_ERR("talloc failed for security_token\n");
+ return NULL;
+ }
+
+ return result;
+}
+
+NTSTATUS create_local_nt_token_from_info3(TALLOC_CTX *mem_ctx,
+ bool is_guest,
+ const struct netr_SamInfo3 *info3,
+ const struct extra_auth_info *extra,
+ struct security_token **ntok)
+{
+ struct security_token *usrtok = NULL;
+ uint32_t session_info_flags = 0;
+ NTSTATUS status;
+ uint32_t i;
+
+ DEBUG(10, ("Create local NT token for %s\n",
+ info3->base.account_name.string));
+
+ usrtok = init_local_nt_token(mem_ctx);
+ if (!usrtok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Add the user and primary group sid FIRST */
+ /* check if the user rid is the special "Domain Guests" rid.
+ * If so pick the first sid for the extra sids instead as it
+ * is a local fake account */
+ usrtok->sids = talloc_array(usrtok, struct dom_sid, 2);
+ if (!usrtok->sids) {
+ TALLOC_FREE(usrtok);
+ return NT_STATUS_NO_MEMORY;
+ }
+ usrtok->num_sids = 2;
+
+ status = get_user_sid_info3_and_extra(info3, extra, &usrtok->sids[0]);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(usrtok);
+ return status;
+ }
+
+ /* GROUP SID */
+ if (info3->base.primary_gid == (uint32_t)(-1)) {
+ /* this is a signal the user was fake and generated,
+ * the actual SID we want to use is stored in the extra
+ * sids */
+ if (is_null_sid(&extra->pgid_sid)) {
+ /* we couldn't find the user sid, bail out */
+ DEBUG(3, ("Invalid group SID\n"));
+ TALLOC_FREE(usrtok);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ sid_copy(&usrtok->sids[1], &extra->pgid_sid);
+ } else {
+ sid_copy(&usrtok->sids[1], info3->base.domain_sid);
+ sid_append_rid(&usrtok->sids[1],
+ info3->base.primary_gid);
+ }
+
+ /* Now the SIDs we got from authentication. These are the ones from
+ * the info3 struct or from the pdb_enum_group_memberships, depending
+ * on who authenticated the user.
+ * Note that we start the for loop at "1" here, we already added the
+ * first group sid as primary above. */
+
+ for (i = 0; i < info3->base.groups.count; i++) {
+ struct dom_sid tmp_sid;
+
+ sid_copy(&tmp_sid, info3->base.domain_sid);
+ sid_append_rid(&tmp_sid, info3->base.groups.rids[i].rid);
+
+ status = add_sid_to_array_unique(usrtok, &tmp_sid,
+ &usrtok->sids,
+ &usrtok->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Failed to add SID to nt token\n"));
+ TALLOC_FREE(usrtok);
+ return status;
+ }
+ }
+
+ /* now also add extra sids if they are not the special user/group
+ * sids */
+ for (i = 0; i < info3->sidcount; i++) {
+ status = add_sid_to_array_unique(usrtok,
+ info3->sids[i].sid,
+ &usrtok->sids,
+ &usrtok->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Failed to add SID to nt token\n"));
+ TALLOC_FREE(usrtok);
+ return status;
+ }
+ }
+
+ status = add_local_groups(usrtok, is_guest);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Failed to add local groups\n"));
+ TALLOC_FREE(usrtok);
+ return status;
+ }
+
+ session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS;
+ if (!is_guest) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ status = finalize_local_nt_token(usrtok, session_info_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Failed to finalize nt token\n"));
+ TALLOC_FREE(usrtok);
+ return status;
+ }
+
+ *ntok = usrtok;
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Create a NT token for the user, expanding local aliases
+*******************************************************************/
+
+NTSTATUS create_local_nt_token(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ bool is_guest,
+ int num_groupsids,
+ const struct dom_sid *groupsids,
+ struct security_token **token)
+{
+ struct security_token *result = NULL;
+ int i;
+ NTSTATUS status;
+ uint32_t session_info_flags = 0;
+ struct dom_sid_buf buf;
+
+ DEBUG(10, ("Create local NT token for %s\n",
+ dom_sid_str_buf(user_sid, &buf)));
+
+ result = init_local_nt_token(mem_ctx);
+ if (result == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ /* Add the user and primary group sid */
+
+ status = add_sid_to_array(result, user_sid,
+ &result->sids, &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+
+ /* For guest, num_groupsids may be zero. */
+ if (num_groupsids) {
+ status = add_sid_to_array(result, &groupsids[0],
+ &result->sids,
+ &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ }
+
+ /* Now the SIDs we got from authentication. These are the ones from
+ * the info3 struct or from the pdb_enum_group_memberships, depending
+ * on who authenticated the user.
+ * Note that we start the for loop at "1" here, we already added the
+ * first group sid as primary above. */
+
+ for (i=1; i<num_groupsids; i++) {
+ status = add_sid_to_array_unique(result, &groupsids[i],
+ &result->sids,
+ &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ }
+
+ status = add_local_groups(result, is_guest);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+
+ session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS;
+ if (!is_guest) {
+ session_info_flags |= AUTH_SESSION_INFO_AUTHENTICATED;
+ }
+
+ status = finalize_local_nt_token(result, session_info_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+
+ if (is_guest) {
+ /*
+ * It's ugly, but for now it's
+ * needed to add Builtin_Guests
+ * here, the "local" token only
+ * consist of S-1-22-* SIDs
+ * and finalize_local_nt_token()
+ * doesn't have the chance to
+ * to detect it need to
+ * add Builtin_Guests via
+ * add_builtin_guests().
+ */
+ status = add_sid_to_array_unique(result,
+ &global_sid_Builtin_Guests,
+ &result->sids,
+ &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Failed to add SID to nt token\n"));
+ goto err;
+ }
+ }
+
+ *token = result;
+ return NT_STATUS_SUCCESS;
+
+err:
+ TALLOC_FREE(result);
+ return status;
+}
+
+/***************************************************
+ Merge in any groups from /etc/group.
+***************************************************/
+
+static NTSTATUS add_local_groups(struct security_token *result,
+ bool is_guest)
+{
+ gid_t *gids = NULL;
+ uint32_t getgroups_num_group_sids = 0;
+ struct passwd *pass = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ uint32_t i;
+
+ if (is_guest) {
+ /*
+ * Guest is a special case. It's always
+ * a user that can be looked up, but
+ * result->sids[0] is set to DOMAIN\Guest.
+ * Lookup by account name instead.
+ */
+ pass = Get_Pwnam_alloc(tmp_ctx, lp_guest_account());
+ } else {
+ uid_t uid;
+
+ /* For non-guest result->sids[0] is always the user sid. */
+ if (!sid_to_uid(&result->sids[0], &uid)) {
+ /*
+ * Non-mappable SID like SYSTEM.
+ * Can't be in any /etc/group groups.
+ */
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ pass = getpwuid_alloc(tmp_ctx, uid);
+ if (pass == NULL) {
+ struct dom_sid_buf buf;
+ DBG_ERR("SID %s -> getpwuid(%u) failed, is nsswitch configured?\n",
+ dom_sid_str_buf(&result->sids[0], &buf),
+ (unsigned int)uid);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ }
+
+ if (!pass) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /*
+ * Now we must get any groups this user has been
+ * added to in /etc/group and merge them in.
+ * This has to be done in every code path
+ * that creates an NT token, as remote users
+ * may have been added to the local /etc/group
+ * database. Tokens created merely from the
+ * info3 structs (via the DC or via the krb5 PAC)
+ * won't have these local groups. Note the
+ * groups added here will only be UNIX groups
+ * (S-1-22-2-XXXX groups) as getgroups_unix_user()
+ * turns off winbindd before calling getgroups().
+ *
+ * NB. This is duplicating work already
+ * done in the 'unix_user:' case of
+ * create_token_from_sid() but won't
+ * do anything other than be inefficient
+ * in that case.
+ */
+
+ if (!getgroups_unix_user(tmp_ctx, pass->pw_name, pass->pw_gid,
+ &gids, &getgroups_num_group_sids)) {
+ DEBUG(1, ("getgroups_unix_user for user %s failed\n",
+ pass->pw_name));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ for (i=0; i<getgroups_num_group_sids; i++) {
+ NTSTATUS status;
+ struct dom_sid grp_sid;
+ gid_to_sid(&grp_sid, gids[i]);
+
+ status = add_sid_to_array_unique(result,
+ &grp_sid,
+ &result->sids,
+ &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Failed to add UNIX SID to nt token\n"));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+ }
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS finalize_local_nt_token(struct security_token *result,
+ uint32_t session_info_flags)
+{
+ struct dom_sid _dom_sid = { 0, };
+ struct dom_sid *domain_sid = NULL;
+ NTSTATUS status;
+ struct acct_info *info;
+ bool ok;
+
+ result->privilege_mask = 0;
+ result->rights_mask = 0;
+
+ if (result->num_sids == 0) {
+ return NT_STATUS_INVALID_TOKEN;
+ }
+
+ if (session_info_flags & AUTH_SESSION_INFO_DEFAULT_GROUPS) {
+ status = add_sid_to_array(result, &global_sid_World,
+ &result->sids, &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = add_sid_to_array(result, &global_sid_Network,
+ &result->sids, &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /*
+ * Don't expand nested groups of system, anonymous etc
+ *
+ * Note that they still get SID_WORLD and SID_NETWORK
+ * for now in order let existing tests pass.
+ *
+ * But SYSTEM doesn't get AUTHENTICATED_USERS
+ * and ANONYMOUS doesn't get BUILTIN GUESTS anymore.
+ */
+ if (security_token_is_anonymous(result)) {
+ return NT_STATUS_OK;
+ }
+ if (security_token_is_system(result)) {
+ result->privilege_mask = ~0;
+ return NT_STATUS_OK;
+ }
+
+ if (session_info_flags & AUTH_SESSION_INFO_AUTHENTICATED) {
+ status = add_sid_to_array(result,
+ &global_sid_Authenticated_Users,
+ &result->sids,
+ &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* Add in BUILTIN sids */
+
+ become_root();
+ ok = secrets_fetch_domain_sid(lp_workgroup(), &_dom_sid);
+ if (ok) {
+ domain_sid = &_dom_sid;
+ } else {
+ DEBUG(3, ("Failed to fetch domain sid for %s\n",
+ lp_workgroup()));
+ }
+ unbecome_root();
+
+ info = talloc_zero(talloc_tos(), struct acct_info);
+ if (info == NULL) {
+ DEBUG(0, ("talloc failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Deal with the BUILTIN\Administrators group. If the SID can
+ be resolved then assume that the add_aliasmem( S-1-5-32 )
+ handled it. */
+
+ status = pdb_get_aliasinfo(&global_sid_Builtin_Administrators, info);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ become_root();
+ status = create_builtin_administrators(domain_sid);
+ unbecome_root();
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE)) {
+ /* Add BUILTIN\Administrators directly to token. */
+ status = add_builtin_administrators(result, domain_sid);
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(3, ("Failed to check for local "
+ "Administrators membership (%s)\n",
+ nt_errstr(status)));
+ }
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("WARNING: Failed to create "
+ "BUILTIN\\Administrators group! Can "
+ "Winbind allocate gids?\n"));
+ }
+ }
+
+ /* Deal with the BUILTIN\Users group. If the SID can
+ be resolved then assume that the add_aliasmem( S-1-5-32 )
+ handled it. */
+
+ status = pdb_get_aliasinfo(&global_sid_Builtin_Users, info);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ become_root();
+ status = create_builtin_users(domain_sid);
+ unbecome_root();
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE) &&
+ !NT_STATUS_IS_OK(status))
+ {
+ DEBUG(2, ("WARNING: Failed to create BUILTIN\\Users group! "
+ "Can Winbind allocate gids?\n"));
+ }
+ }
+
+ /*
+ * Deal with the BUILTIN\Guests group. If the SID can
+ * be resolved then assume that the add_aliasmem( S-1-5-32 )
+ * handled it.
+ */
+ status = pdb_get_aliasinfo(&global_sid_Builtin_Guests, info);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ become_root();
+ status = create_builtin_guests(domain_sid);
+ unbecome_root();
+
+ /*
+ * NT_STATUS_PROTOCOL_UNREACHABLE:
+ * => winbindd is not running.
+ *
+ * NT_STATUS_ACCESS_DENIED:
+ * => no idmap config at all
+ * and wbint_AllocateGid()/winbind_allocate_gid()
+ * failed.
+ *
+ * NT_STATUS_NO_SUCH_GROUP:
+ * => no idmap config at all and
+ * "tdbsam:map builtin = no" means
+ * wbint_Sids2UnixIDs() fails.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_GROUP)) {
+ /*
+ * Add BUILTIN\Guests directly to token.
+ * But only if the token already indicates
+ * real guest access by:
+ * - local GUEST account
+ * - local GUESTS group
+ * - domain GUESTS group
+ *
+ * Even if a user was authenticated, it
+ * can be member of a guest related group.
+ */
+ status = add_builtin_guests(result, domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Failed to check for local "
+ "Guests membership (%s)\n",
+ nt_errstr(status)));
+ /*
+ * This is a hard error.
+ */
+ return status;
+ }
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("Failed to create "
+ "BUILTIN\\Guests group %s! Can "
+ "Winbind allocate gids?\n",
+ nt_errstr(status)));
+ /*
+ * This is a hard error.
+ */
+ return status;
+ }
+ }
+
+ TALLOC_FREE(info);
+
+ /* Deal with local groups */
+
+ if (lp_winbind_nested_groups()) {
+
+ become_root();
+
+ /* Now add the aliases. First the one from our local SAM */
+
+ status = add_aliases(get_global_sam_sid(), result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ return status;
+ }
+
+ /* Finally the builtin ones */
+
+ status = add_aliases(&global_sid_Builtin, result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ return status;
+ }
+
+ unbecome_root();
+ }
+
+ if (session_info_flags & AUTH_SESSION_INFO_NTLM) {
+ struct dom_sid tmp_sid = { 0, };
+
+ ok = dom_sid_parse(SID_NT_NTLM_AUTHENTICATION, &tmp_sid);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = add_sid_to_array(result,
+ &tmp_sid,
+ &result->sids,
+ &result->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (session_info_flags & AUTH_SESSION_INFO_SIMPLE_PRIVILEGES) {
+ if (security_token_has_builtin_administrators(result)) {
+ result->privilege_mask = ~0;
+ }
+ } else {
+ /* Add privileges based on current user sids */
+ get_privileges_for_sids(&result->privilege_mask, result->sids,
+ result->num_sids);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ prints a UNIX 'token' to debug output.
+****************************************************************************/
+
+void debug_unix_user_token(int dbg_class, int dbg_lev, uid_t uid, gid_t gid,
+ int n_groups, gid_t *groups)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *s = NULL;
+ int i;
+
+ s = talloc_asprintf(frame,
+ "UNIX token of user %ld\n",
+ (long int)uid);
+
+ talloc_asprintf_addbuf(
+ &s,
+ "Primary group is %ld and contains %i supplementary "
+ "groups\n",
+ (long int)gid,
+ n_groups);
+ for (i = 0; i < n_groups; i++) {
+ talloc_asprintf_addbuf(&s,
+ "Group[%3i]: %ld\n",
+ i,
+ (long int)groups[i]);
+ }
+
+ DEBUGC(dbg_class, dbg_lev, ("%s", s ? s : "(NULL)"));
+ TALLOC_FREE(frame);
+}
+
+/*
+ * Create an artificial NT token given just a domain SID.
+ *
+ * We have 3 cases:
+ *
+ * unmapped unix users: Go directly to nss to find the user's group.
+ *
+ * A passdb user: The list of groups is provided by pdb_enum_group_memberships.
+ *
+ * If the user is provided by winbind, the primary gid is set to "domain
+ * users" of the user's domain. For an explanation why this is necessary, see
+ * the thread starting at
+ * http://lists.samba.org/archive/samba-technical/2006-January/044803.html.
+ */
+
+static NTSTATUS create_token_from_sid(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ bool is_guest,
+ uid_t *uid, gid_t *gid,
+ char **found_username,
+ struct security_token **token)
+{
+ NTSTATUS result = NT_STATUS_NO_SUCH_USER;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ gid_t *gids;
+ struct dom_sid *group_sids;
+ struct dom_sid tmp_sid;
+ uint32_t num_group_sids;
+ uint32_t num_gids;
+ uint32_t i;
+ uint32_t high, low;
+ bool range_ok;
+ struct dom_sid_buf buf;
+
+ if (sid_check_is_in_our_sam(user_sid)) {
+ bool ret;
+ uint32_t pdb_num_group_sids;
+ /* This is a passdb user, so ask passdb */
+
+ struct samu *sam_acct = NULL;
+
+ if ( !(sam_acct = samu_new( tmp_ctx )) ) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ become_root();
+ ret = pdb_getsampwsid(sam_acct, user_sid);
+ unbecome_root();
+
+ if (!ret) {
+ DEBUG(1, ("pdb_getsampwsid(%s) failed\n",
+ dom_sid_str_buf(user_sid, &buf)));
+ DEBUGADD(1, ("Fall back to unix user\n"));
+ goto unix_user;
+ }
+
+ result = pdb_enum_group_memberships(tmp_ctx, sam_acct,
+ &group_sids, &gids,
+ &pdb_num_group_sids);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("enum_group_memberships failed for %s: "
+ "%s\n",
+ dom_sid_str_buf(user_sid, &buf),
+ nt_errstr(result)));
+ DEBUGADD(1, ("Fall back to unix uid lookup\n"));
+ goto unix_user;
+ }
+ num_group_sids = pdb_num_group_sids;
+
+ /* see the smb_panic() in pdb_default_enum_group_memberships */
+ SMB_ASSERT(num_group_sids > 0);
+
+ /* Ensure we're returning the found_username on the right context. */
+ *found_username = talloc_strdup(mem_ctx,
+ pdb_get_username(sam_acct));
+
+ if (*found_username == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /*
+ * If the SID from lookup_name() was the guest sid, passdb knows
+ * about the mapping of guest sid to lp_guest_account()
+ * username and will return the unix_pw info for a guest
+ * user. Use it if it's there, else lookup the *uid details
+ * using Get_Pwnam_alloc(). See bug #6291 for details. JRA.
+ */
+
+ /* We must always assign the *uid. */
+ if (sam_acct->unix_pw == NULL) {
+ struct passwd *pwd = Get_Pwnam_alloc(sam_acct, *found_username );
+ if (!pwd) {
+ DEBUG(10, ("Get_Pwnam_alloc failed for %s\n",
+ *found_username));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+ result = samu_set_unix(sam_acct, pwd );
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("samu_set_unix failed for %s\n",
+ *found_username));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+ }
+ *uid = sam_acct->unix_pw->pw_uid;
+
+ } else if (sid_check_is_in_unix_users(user_sid)) {
+ uint32_t getgroups_num_group_sids;
+ /* This is a unix user not in passdb. We need to ask nss
+ * directly, without consulting passdb */
+
+ struct passwd *pass;
+
+ /*
+ * This goto target is used as a fallback for the passdb
+ * case. The concrete bug report is when passdb gave us an
+ * unmapped gid.
+ */
+
+ unix_user:
+
+ if (!sid_to_uid(user_sid, uid)) {
+ DEBUG(1, ("unix_user case, sid_to_uid for %s failed\n",
+ dom_sid_str_buf(user_sid, &buf)));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ uid_to_unix_users_sid(*uid, &tmp_sid);
+ user_sid = &tmp_sid;
+
+ pass = getpwuid_alloc(tmp_ctx, *uid);
+ if (pass == NULL) {
+ DEBUG(1, ("getpwuid(%u) failed\n",
+ (unsigned int)*uid));
+ goto done;
+ }
+
+ if (!getgroups_unix_user(tmp_ctx, pass->pw_name, pass->pw_gid,
+ &gids, &getgroups_num_group_sids)) {
+ DEBUG(1, ("getgroups_unix_user for user %s failed\n",
+ pass->pw_name));
+ goto done;
+ }
+ num_group_sids = getgroups_num_group_sids;
+
+ group_sids = talloc_array(tmp_ctx, struct dom_sid, num_group_sids);
+ if (group_sids == NULL) {
+ DEBUG(1, ("talloc_array failed\n"));
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i<num_group_sids; i++) {
+ gid_to_sid(&group_sids[i], gids[i]);
+ }
+
+ /* In getgroups_unix_user we always set the primary gid */
+ SMB_ASSERT(num_group_sids > 0);
+
+ /* Ensure we're returning the found_username on the right context. */
+ *found_username = talloc_strdup(mem_ctx, pass->pw_name);
+ if (*found_username == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ } else {
+
+ /* This user is from winbind, force the primary gid to the
+ * user's "domain users" group. Under certain circumstances
+ * (user comes from NT4), this might be a loss of
+ * information. But we can not rely on winbind getting the
+ * correct info. AD might prohibit winbind looking up that
+ * information. */
+
+ /* We must always assign the *uid. */
+ if (!sid_to_uid(user_sid, uid)) {
+ DEBUG(1, ("winbindd case, sid_to_uid for %s failed\n",
+ dom_sid_str_buf(user_sid, &buf)));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ num_group_sids = 1;
+ group_sids = talloc_array(tmp_ctx, struct dom_sid, num_group_sids);
+ if (group_sids == NULL) {
+ DEBUG(1, ("talloc_array failed\n"));
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ gids = talloc_array(tmp_ctx, gid_t, num_group_sids);
+ if (gids == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ sid_copy(&group_sids[0], user_sid);
+ sid_split_rid(&group_sids[0], NULL);
+ sid_append_rid(&group_sids[0], DOMAIN_RID_USERS);
+
+ if (!sid_to_gid(&group_sids[0], &gids[0])) {
+ DEBUG(1, ("sid_to_gid(%s) failed\n",
+ dom_sid_str_buf(&group_sids[0], &buf)));
+ goto done;
+ }
+
+ *found_username = NULL;
+ }
+
+ *gid = gids[0];
+
+ /* Add the "Unix Group" SID for each gid to catch mapped groups
+ and their Unix equivalent. This is to solve the backwards
+ compatibility problem of 'valid users = +ntadmin' where
+ ntadmin has been paired with "Domain Admins" in the group
+ mapping table. Otherwise smb.conf would need to be changed
+ to 'valid user = "Domain Admins"'. --jerry */
+
+ num_gids = num_group_sids;
+ range_ok = lp_idmap_default_range(&low, &high);
+ for ( i=0; i<num_gids; i++ ) {
+ struct dom_sid unix_group_sid;
+
+ /* don't pickup anything managed by Winbind */
+ if (range_ok && (gids[i] >= low) && (gids[i] <= high)) {
+ continue;
+ }
+
+ gid_to_unix_groups_sid(gids[i], &unix_group_sid);
+
+ result = add_sid_to_array_unique(tmp_ctx, &unix_group_sid,
+ &group_sids, &num_group_sids);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ /* Ensure we're creating the nt_token on the right context. */
+ result = create_local_nt_token(mem_ctx, user_sid,
+ is_guest, num_group_sids, group_sids, token);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ result = NT_STATUS_OK;
+ done:
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/*
+ * Create an artificial NT token given just a username. (Initially intended
+ * for force user)
+ *
+ * We go through lookup_name() to avoid problems we had with 'winbind use
+ * default domain'.
+ *
+ * We have 3 cases:
+ *
+ * unmapped unix users: Go directly to nss to find the user's group.
+ *
+ * A passdb user: The list of groups is provided by pdb_enum_group_memberships.
+ *
+ * If the user is provided by winbind, the primary gid is set to "domain
+ * users" of the user's domain. For an explanation why this is necessary, see
+ * the thread starting at
+ * http://lists.samba.org/archive/samba-technical/2006-January/044803.html.
+ */
+
+NTSTATUS create_token_from_username(TALLOC_CTX *mem_ctx, const char *username,
+ bool is_guest,
+ uid_t *uid, gid_t *gid,
+ char **found_username,
+ struct security_token **token)
+{
+ NTSTATUS result = NT_STATUS_NO_SUCH_USER;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct dom_sid user_sid;
+ enum lsa_SidType type;
+
+ if (!lookup_name_smbconf(tmp_ctx, username, LOOKUP_NAME_ALL,
+ NULL, NULL, &user_sid, &type)) {
+ DEBUG(1, ("lookup_name_smbconf for %s failed\n", username));
+ goto done;
+ }
+
+ if (type != SID_NAME_USER) {
+ DEBUG(1, ("%s is a %s, not a user\n", username,
+ sid_type_lookup(type)));
+ goto done;
+ }
+
+ result = create_token_from_sid(mem_ctx, &user_sid, is_guest, uid, gid, found_username, token);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ /*
+ * If result == NT_STATUS_OK then
+ * we know we have a valid token. Ensure
+ * we also have a valid username to match.
+ */
+
+ if (*found_username == NULL) {
+ *found_username = talloc_strdup(mem_ctx, username);
+ if (*found_username == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ }
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/***************************************************************************
+ Build upon create_token_from_sid:
+
+ Expensive helper function to figure out whether a user given its sid is
+ member of a particular group.
+***************************************************************************/
+
+bool user_sid_in_group_sid(const struct dom_sid *sid, const struct dom_sid *group_sid)
+{
+ NTSTATUS status;
+ uid_t uid;
+ gid_t gid;
+ char *found_username;
+ struct security_token *token;
+ bool result = false;
+ enum lsa_SidType type;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct dom_sid_buf buf;
+
+ if (!lookup_sid(mem_ctx, sid,
+ NULL, NULL, &type)) {
+ DEBUG(1, ("lookup_sid for %s failed\n",
+ dom_sid_str_buf(sid, &buf)));
+ goto done;
+ }
+
+ if (type != SID_NAME_USER) {
+ DEBUG(5, ("%s is a %s, not a user\n",
+ dom_sid_str_buf(sid, &buf),
+ sid_type_lookup(type)));
+ goto done;
+ }
+
+ status = create_token_from_sid(mem_ctx, sid, False,
+ &uid, &gid, &found_username,
+ &token);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("could not create token for %s\n",
+ dom_sid_str_buf(sid, &buf)));
+ goto done;
+ }
+
+ result = security_token_has_sid(token, group_sid);
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+/***************************************************************************
+ Build upon create_token_from_username:
+
+ Expensive helper function to figure out whether a user given its name is
+ member of a particular group.
+***************************************************************************/
+
+bool user_in_group_sid(const char *username, const struct dom_sid *group_sid)
+{
+ NTSTATUS status;
+ uid_t uid;
+ gid_t gid;
+ char *found_username;
+ struct security_token *token;
+ bool result;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ status = create_token_from_username(mem_ctx, username, False,
+ &uid, &gid, &found_username,
+ &token);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("could not create token for %s\n", username));
+ TALLOC_FREE(mem_ctx);
+ return False;
+ }
+
+ result = security_token_has_sid(token, group_sid);
+
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+bool user_in_group(const char *username, const char *groupname)
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct dom_sid group_sid;
+ bool ret;
+
+ ret = lookup_name(mem_ctx, groupname, LOOKUP_NAME_ALL,
+ NULL, NULL, &group_sid, NULL);
+ TALLOC_FREE(mem_ctx);
+
+ if (!ret) {
+ DEBUG(10, ("lookup_name for (%s) failed.\n", groupname));
+ return False;
+ }
+
+ return user_in_group_sid(username, &group_sid);
+}
+
+/* END */
diff --git a/source3/auth/user_info.c b/source3/auth/user_info.c
new file mode 100644
index 0000000..81192a5
--- /dev/null
+++ b/source3/auth/user_info.c
@@ -0,0 +1,169 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "librpc/gen_ndr/samr.h"
+#include "../lib/tsocket/tsocket.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+static int clear_samr_Password(struct samr_Password *password)
+{
+ memset(password->hash, '\0', sizeof(password->hash));
+ return 0;
+}
+
+static int clear_string(char *password)
+{
+ memset(password, '\0', strlen(password));
+ return 0;
+}
+
+/****************************************************************************
+ Create an auth_usersupplied_data structure
+****************************************************************************/
+
+NTSTATUS make_user_info(TALLOC_CTX *mem_ctx,
+ struct auth_usersupplied_info **ret_user_info,
+ const char *smb_name,
+ const char *internal_username,
+ const char *client_domain,
+ const char *domain,
+ const char *workstation_name,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const char *service_description,
+ const DATA_BLOB *lm_pwd,
+ const DATA_BLOB *nt_pwd,
+ const struct samr_Password *lm_interactive_pwd,
+ const struct samr_Password *nt_interactive_pwd,
+ const char *plaintext_password,
+ enum auth_password_state password_state)
+{
+ struct auth_usersupplied_info *user_info;
+ *ret_user_info = NULL;
+
+ DEBUG(5,("attempting to make a user_info for %s (%s)\n", internal_username, smb_name));
+
+ user_info = talloc_zero(mem_ctx, struct auth_usersupplied_info);
+ if (user_info == NULL) {
+ DEBUG(0,("talloc failed for user_info\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5,("making strings for %s's user_info struct\n", internal_username));
+
+ user_info->client.account_name = talloc_strdup(user_info, smb_name);
+ if (user_info->client.account_name == NULL) {
+ goto nomem;
+ }
+
+ user_info->mapped.account_name = talloc_strdup(user_info, internal_username);
+ if (user_info->mapped.account_name == NULL) {
+ goto nomem;
+ }
+
+ user_info->mapped.domain_name = talloc_strdup(user_info, domain);
+ if (user_info->mapped.domain_name == NULL) {
+ goto nomem;
+ }
+
+ user_info->client.domain_name = talloc_strdup(user_info, client_domain);
+ if (user_info->client.domain_name == NULL) {
+ goto nomem;
+ }
+
+ user_info->workstation_name = talloc_strdup(user_info, workstation_name);
+ if (user_info->workstation_name == NULL) {
+ goto nomem;
+ }
+
+ user_info->remote_host = tsocket_address_copy(remote_address, user_info);
+ if (user_info->remote_host == NULL) {
+ goto nomem;
+ }
+
+ if (local_address != NULL) {
+ user_info->local_host = tsocket_address_copy(local_address,
+ user_info);
+ if (user_info->local_host == NULL) {
+ goto nomem;
+ }
+ }
+
+ user_info->service_description = talloc_strdup(user_info, service_description);
+ if (user_info->service_description == NULL) {
+ goto nomem;
+ }
+
+ DEBUG(5,("making blobs for %s's user_info struct\n", internal_username));
+
+ if (lm_pwd && lm_pwd->data) {
+ user_info->password.response.lanman = data_blob_talloc(user_info, lm_pwd->data, lm_pwd->length);
+ if (user_info->password.response.lanman.data == NULL) {
+ goto nomem;
+ }
+ }
+ if (nt_pwd && nt_pwd->data) {
+ user_info->password.response.nt = data_blob_talloc(user_info, nt_pwd->data, nt_pwd->length);
+ if (user_info->password.response.nt.data == NULL) {
+ goto nomem;
+ }
+ }
+ if (lm_interactive_pwd) {
+ user_info->password.hash.lanman = talloc(user_info, struct samr_Password);
+ if (user_info->password.hash.lanman == NULL) {
+ goto nomem;
+ }
+ memcpy(user_info->password.hash.lanman->hash, lm_interactive_pwd->hash,
+ sizeof(user_info->password.hash.lanman->hash));
+ talloc_set_destructor(user_info->password.hash.lanman, clear_samr_Password);
+ }
+
+ if (nt_interactive_pwd) {
+ user_info->password.hash.nt = talloc(user_info, struct samr_Password);
+ if (user_info->password.hash.nt == NULL) {
+ goto nomem;
+ }
+ memcpy(user_info->password.hash.nt->hash, nt_interactive_pwd->hash,
+ sizeof(user_info->password.hash.nt->hash));
+ talloc_set_destructor(user_info->password.hash.nt, clear_samr_Password);
+ }
+
+ if (plaintext_password) {
+ user_info->password.plaintext = talloc_strdup(user_info, plaintext_password);
+ if (user_info->password.plaintext == NULL) {
+ goto nomem;
+ }
+ talloc_set_destructor(user_info->password.plaintext, clear_string);
+ }
+
+ user_info->password_state = password_state;
+
+ user_info->logon_parameters = 0;
+
+ DEBUG(10,("made a user_info for %s (%s)\n", internal_username, smb_name));
+ *ret_user_info = user_info;
+ return NT_STATUS_OK;
+nomem:
+ TALLOC_FREE(user_info);
+ return NT_STATUS_NO_MEMORY;
+}
diff --git a/source3/auth/user_krb5.c b/source3/auth/user_krb5.c
new file mode 100644
index 0000000..169bf56
--- /dev/null
+++ b/source3/auth/user_krb5.c
@@ -0,0 +1,263 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth.h"
+#include "librpc/gen_ndr/krb5pac.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "passdb.h"
+#include "lib/param/loadparm.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_AUTH
+
+#ifdef HAVE_KRB5
+NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx,
+ const char *cli_name,
+ const char *princ_name,
+ bool *is_mapped,
+ bool *mapped_to_guest,
+ char **ntuser,
+ char **ntdomain,
+ char **username,
+ struct passwd **_pw)
+{
+ NTSTATUS status;
+ const char *domain = NULL;
+ const char *realm = NULL;
+ char *user = NULL;
+ char *p;
+ char *fuser = NULL;
+ char *unixuser = NULL;
+ struct passwd *pw = NULL;
+ bool may_retry = false;
+
+ DEBUG(3, ("Kerberos ticket principal name is [%s]\n", princ_name));
+
+ p = strchr_m(princ_name, '@');
+ if (!p) {
+ DEBUG(3, ("[%s] Doesn't look like a valid principal\n",
+ princ_name));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ user = talloc_strndup(mem_ctx, princ_name, p - princ_name);
+ if (!user) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ realm = p + 1;
+
+ if (!strequal(realm, lp_realm())) {
+ DEBUG(3, ("Ticket for foreign realm %s@%s\n", user, realm));
+ if (!lp_allow_trusted_domains()) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ domain = realm;
+ } else {
+ domain = lp_workgroup();
+ may_retry = true;
+ }
+
+ fuser = talloc_asprintf(mem_ctx,
+ "%s%c%s",
+ domain,
+ *lp_winbind_separator(),
+ user);
+ if (!fuser) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *is_mapped = map_username(mem_ctx, fuser, &fuser);
+ if (!fuser) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *mapped_to_guest = false;
+
+ pw = smb_getpwnam(mem_ctx, fuser, &unixuser, true);
+ if (may_retry && pw == NULL && !*is_mapped) {
+ fuser = talloc_strdup(mem_ctx, user);
+ if (!fuser) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pw = smb_getpwnam(mem_ctx, fuser, &unixuser, true);
+ }
+ if (pw) {
+ if (!unixuser) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ /* if a real user check pam account restrictions */
+ /* only really performed if "obey pam restriction" is true */
+ /* do this before an eventual mapping to guest occurs */
+ status = smb_pam_accountcheck(pw->pw_name, cli_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("PAM account restrictions prevent user "
+ "[%s] login\n", unixuser));
+ return status;
+ }
+ }
+ if (!pw) {
+
+ /* this was originally the behavior of Samba 2.2, if a user
+ did not have a local uid but has been authenticated, then
+ map them to a guest account */
+
+ if (lp_map_to_guest() == MAP_TO_GUEST_ON_BAD_UID) {
+ *mapped_to_guest = true;
+ fuser = talloc_strdup(mem_ctx, lp_guest_account());
+ if (!fuser) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pw = smb_getpwnam(mem_ctx, fuser, &unixuser, false);
+ }
+
+ /* extra sanity check that the guest account is valid */
+ if (!pw) {
+ DBG_NOTICE("Username %s is invalid on this system\n",
+ fuser);
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ }
+
+ if (!unixuser) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *username = talloc_strdup(mem_ctx, unixuser);
+ if (!*username) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *ntuser = user;
+ *ntdomain = talloc_strdup(mem_ctx, domain);
+ if (*ntdomain == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *_pw = pw;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx,
+ char *ntuser,
+ char *ntdomain,
+ char *username,
+ struct passwd *pw,
+ bool mapped_to_guest, bool username_was_mapped,
+ struct auth_session_info **session_info)
+{
+ NTSTATUS status;
+ struct auth_serversupplied_info *server_info;
+
+ if (mapped_to_guest) {
+ status = make_server_info_guest(mem_ctx, &server_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("make_server_info_guest failed: %s!\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ } else {
+ /*
+ * We didn't get a PAC, we have to make up the user
+ * ourselves. Try to ask the pdb backend to provide
+ * SID consistency with ntlmssp session setup
+ */
+ struct samu *sampass;
+
+ sampass = samu_new(talloc_tos());
+ if (sampass == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (pdb_getsampwnam(sampass, username)) {
+ DEBUG(10, ("found user %s in passdb, calling "
+ "make_server_info_sam\n", username));
+ status = make_server_info_sam(mem_ctx,
+ sampass,
+ &server_info);
+ } else {
+ /*
+ * User not in passdb, make it up artificially
+ */
+ DEBUG(10, ("didn't find user %s in passdb, calling "
+ "make_server_info_pw\n", username));
+ status = make_server_info_pw(mem_ctx,
+ username,
+ pw,
+ &server_info);
+ }
+
+ TALLOC_FREE(sampass);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("make_server_info_[sam|pw] failed: %s!\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ /* make_server_info_pw does not set the domain. Without this
+ * we end up with the local netbios name in substitutions for
+ * %D. */
+
+ if (server_info->info3 != NULL) {
+ server_info->info3->base.logon_domain.string =
+ talloc_strdup(server_info->info3, ntdomain);
+ }
+ }
+
+ server_info->nss_token |= username_was_mapped;
+
+ status = create_local_token(mem_ctx, server_info, NULL, ntuser, session_info);
+ talloc_free(server_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("failed to create local token: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+#else /* HAVE_KRB5 */
+NTSTATUS get_user_from_kerberos_info(TALLOC_CTX *mem_ctx,
+ const char *cli_name,
+ const char *princ_name,
+ bool *is_mapped,
+ bool *mapped_to_guest,
+ char **ntuser,
+ char **ntdomain,
+ char **username,
+ struct passwd **_pw)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS make_session_info_krb5(TALLOC_CTX *mem_ctx,
+ char *ntuser,
+ char *ntdomain,
+ char *username,
+ struct passwd *pw,
+ bool mapped_to_guest, bool username_was_mapped,
+ struct auth_session_info **session_info)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+#endif /* HAVE_KRB5 */
diff --git a/source3/auth/user_util.c b/source3/auth/user_util.c
new file mode 100644
index 0000000..cd97d62
--- /dev/null
+++ b/source3/auth/user_util.c
@@ -0,0 +1,447 @@
+/*
+ Unix SMB/CIFS implementation.
+ Username handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1997-2001.
+ Copyright (C) Volker Lendecke 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "auth.h"
+#include "lib/gencache.h"
+
+/*******************************************************************
+ Map a username from a dos name to a unix name by looking in the username
+ map. Note that this modifies the name in place.
+ This is the main function that should be called *once* on
+ any incoming or new username - in order to canonicalize the name.
+ This is being done to de-couple the case conversions from the user mapping
+ function. Previously, the map_username was being called
+ every time Get_Pwnam_alloc was called.
+ Returns True if username was changed, false otherwise.
+********************************************************************/
+
+static char *last_from = NULL;
+static char *last_to = NULL;
+
+static const char *get_last_from(void)
+{
+ if (!last_from) {
+ return "";
+ }
+ return last_from;
+}
+
+static const char *get_last_to(void)
+{
+ if (!last_to) {
+ return "";
+ }
+ return last_to;
+}
+
+static bool set_last_from_to(const char *from, const char *to)
+{
+ char *orig_from = last_from;
+ char *orig_to = last_to;
+
+ last_from = SMB_STRDUP(from);
+ last_to = SMB_STRDUP(to);
+
+ SAFE_FREE(orig_from);
+ SAFE_FREE(orig_to);
+
+ if (!last_from || !last_to) {
+ SAFE_FREE(last_from);
+ SAFE_FREE(last_to);
+ return false;
+ }
+ return true;
+}
+
+static char *skip_space(char *s)
+{
+ while (isspace((int)(*s))) {
+ s += 1;
+ }
+ return s;
+}
+
+static bool fetch_map_from_gencache(TALLOC_CTX *ctx,
+ const char *user_in,
+ char **p_user_out)
+{
+ char *key, *value;
+ bool found;
+
+ if (lp_username_map_cache_time() == 0) {
+ return false;
+ }
+
+ key = talloc_asprintf_strupper_m(ctx, "USERNAME_MAP/%s",
+ user_in);
+ if (key == NULL) {
+ return false;
+ }
+ found = gencache_get(key, ctx, &value, NULL);
+ TALLOC_FREE(key);
+ if (!found) {
+ return false;
+ }
+ TALLOC_FREE(*p_user_out);
+ *p_user_out = value;
+ if (!*p_user_out) {
+ return false;
+ }
+ return true;
+}
+
+static void store_map_in_gencache(TALLOC_CTX *ctx, const char *from, const char *to)
+{
+ char *key;
+ int cache_time = lp_username_map_cache_time();
+
+ if (cache_time == 0) {
+ return;
+ }
+
+ key = talloc_asprintf_strupper_m(ctx, "USERNAME_MAP/%s",
+ from);
+ if (key == NULL) {
+ return;
+ }
+ gencache_set(key, to, cache_time + time(NULL));
+ TALLOC_FREE(key);
+}
+
+/****************************************************************************
+ Check if a user is in a netgroup user list. If at first we don't succeed,
+ try lower case.
+****************************************************************************/
+
+bool user_in_netgroup(TALLOC_CTX *ctx, const char *user, const char *ngname)
+{
+#if defined(HAVE_NETGROUP) && defined(HAVE_INNETGR)
+ char nis_domain_buf[256];
+ const char *nis_domain = NULL;
+ char *lowercase_user = NULL;
+
+ if (getdomainname(nis_domain_buf, sizeof(nis_domain_buf)) == 0) {
+ nis_domain = &nis_domain_buf[0];
+ } else {
+ DEBUG(5,("Unable to get default yp domain, "
+ "let's try without specifying it\n"));
+ nis_domain = NULL;
+ }
+
+ DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
+ user, nis_domain ? nis_domain : "(ANY)", ngname));
+
+ if (innetgr(ngname, NULL, user, nis_domain)) {
+ DEBUG(5,("user_in_netgroup: Found\n"));
+ return true;
+ }
+
+ /*
+ * Ok, innetgr is case sensitive. Try once more with lowercase
+ * just in case. Attempt to fix #703. JRA.
+ */
+ lowercase_user = talloc_strdup(ctx, user);
+ if (!lowercase_user) {
+ return false;
+ }
+ if (!strlower_m(lowercase_user)) {
+ TALLOC_FREE(lowercase_user);
+ return false;
+ }
+
+ if (strcmp(user,lowercase_user) == 0) {
+ /* user name was already lower case! */
+ TALLOC_FREE(lowercase_user);
+ return false;
+ }
+
+ DEBUG(5,("looking for user %s of domain %s in netgroup %s\n",
+ lowercase_user, nis_domain ? nis_domain : "(ANY)", ngname));
+
+ if (innetgr(ngname, NULL, lowercase_user, nis_domain)) {
+ DEBUG(5,("user_in_netgroup: Found\n"));
+ TALLOC_FREE(lowercase_user);
+ return true;
+ }
+#endif /* HAVE_NETGROUP and HAVE_INNETGR */
+ return false;
+}
+
+/****************************************************************************
+ Check if a user is in a user list - can check combinations of UNIX
+ and netgroup lists.
+****************************************************************************/
+
+bool user_in_list(TALLOC_CTX *ctx, const char *user, const char * const *list)
+{
+ if (!list || !*list)
+ return False;
+
+ DEBUG(10,("user_in_list: checking user %s in list\n", user));
+
+ while (*list) {
+
+ DEBUG(10,("user_in_list: checking user |%s| against |%s|\n",
+ user, *list));
+
+ /*
+ * Check raw username.
+ */
+ if (strequal(user, *list))
+ return(True);
+
+ /*
+ * Now check to see if any combination
+ * of UNIX and netgroups has been specified.
+ */
+
+ if(**list == '@') {
+ /*
+ * Old behaviour. Check netgroup list
+ * followed by UNIX list.
+ */
+ if(user_in_netgroup(ctx, user, *list +1))
+ return True;
+ if(user_in_group(user, *list +1))
+ return True;
+ } else if (**list == '+') {
+
+ if((*(*list +1)) == '&') {
+ /*
+ * Search UNIX list followed by netgroup.
+ */
+ if(user_in_group(user, *list +2))
+ return True;
+ if(user_in_netgroup(ctx, user, *list +2))
+ return True;
+
+ } else {
+
+ /*
+ * Just search UNIX list.
+ */
+
+ if(user_in_group(user, *list +1))
+ return True;
+ }
+
+ } else if (**list == '&') {
+
+ if(*(*list +1) == '+') {
+ /*
+ * Search netgroup list followed by UNIX list.
+ */
+ if(user_in_netgroup(ctx, user, *list +2))
+ return True;
+ if(user_in_group(user, *list +2))
+ return True;
+ } else {
+ /*
+ * Just search netgroup list.
+ */
+ if(user_in_netgroup(ctx, user, *list +1))
+ return True;
+ }
+ }
+
+ list++;
+ }
+ return(False);
+}
+
+bool map_username(TALLOC_CTX *ctx, const char *user_in, char **p_user_out)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ FILE *f;
+ char *mapfile = lp_username_map(talloc_tos(), lp_sub);
+ char *s;
+ char buf[512];
+ bool mapped_user = False;
+ char *cmd = lp_username_map_script(talloc_tos(), lp_sub);
+
+ *p_user_out = NULL;
+
+ if (!user_in)
+ return false;
+
+ /* Initially make a copy of the incoming name. */
+ *p_user_out = talloc_strdup(ctx, user_in);
+ if (!*p_user_out) {
+ return false;
+ }
+
+ if (strequal(user_in,get_last_to()))
+ return false;
+
+ if (strequal(user_in,get_last_from())) {
+ DEBUG(3,("Mapped user %s to %s\n",user_in,get_last_to()));
+ TALLOC_FREE(*p_user_out);
+ *p_user_out = talloc_strdup(ctx, get_last_to());
+ return true;
+ }
+
+ if (fetch_map_from_gencache(ctx, user_in, p_user_out)) {
+ return true;
+ }
+
+ /* first try the username map script */
+
+ if ( *cmd ) {
+ char **qlines;
+ char *command = NULL;
+ int numlines, ret, fd;
+
+ command = talloc_asprintf(ctx,
+ "%s \"%s\"",
+ cmd,
+ user_in);
+ if (!command) {
+ return false;
+ }
+
+ DEBUG(10,("Running [%s]\n", command));
+ ret = smbrun(command, &fd, NULL);
+ DEBUGADD(10,("returned [%d]\n", ret));
+
+ TALLOC_FREE(command);
+
+ if ( ret != 0 ) {
+ if (fd != -1)
+ close(fd);
+ return False;
+ }
+
+ numlines = 0;
+ qlines = fd_lines_load(fd, &numlines, 0, ctx);
+ DEBUGADD(10,("Lines returned = [%d]\n", numlines));
+ close(fd);
+
+ /* should be either no lines or a single line with the mapped username */
+
+ if (numlines && qlines) {
+ DEBUG(3,("Mapped user %s to %s\n", user_in, qlines[0] ));
+ set_last_from_to(user_in, qlines[0]);
+ store_map_in_gencache(ctx, user_in, qlines[0]);
+ TALLOC_FREE(*p_user_out);
+ *p_user_out = talloc_strdup(ctx, qlines[0]);
+ if (!*p_user_out) {
+ return false;
+ }
+ }
+
+ TALLOC_FREE(qlines);
+
+ return numlines != 0;
+ }
+
+ /* ok. let's try the mapfile */
+ if (!*mapfile)
+ return False;
+
+ f = fopen(mapfile, "r");
+ if (!f) {
+ DEBUG(0,("can't open username map %s. Error %s\n",mapfile, strerror(errno) ));
+ return False;
+ }
+
+ DEBUG(4,("Scanning username map %s\n",mapfile));
+
+ while((s=fgets_slash(NULL,buf,sizeof(buf),f))!=NULL) {
+ char *unixname = s;
+ char *dosname = strchr_m(unixname,'=');
+ char **dosuserlist;
+ bool return_if_mapped = False;
+
+ if (!dosname)
+ continue;
+
+ *dosname++ = 0;
+
+ unixname = skip_space(unixname);
+
+ if ('!' == *unixname) {
+ return_if_mapped = True;
+ unixname = skip_space(unixname+1);
+ }
+
+ if (!*unixname || strchr_m("#;",*unixname))
+ continue;
+
+ {
+ int l = strlen(unixname);
+ while (l && isspace((int)unixname[l-1])) {
+ unixname[l-1] = 0;
+ l--;
+ }
+ }
+
+ /* skip lines like 'user = ' */
+
+ dosuserlist = str_list_make_v3(ctx, dosname, NULL);
+ if (!dosuserlist) {
+ DEBUG(0,("Bad username map entry. Unable to build user list. Ignoring.\n"));
+ continue;
+ }
+
+ if (strchr_m(dosname,'*') ||
+ user_in_list(ctx, user_in, (const char * const *)dosuserlist)) {
+ DEBUG(3,("Mapped user %s to %s\n",user_in,unixname));
+ mapped_user = True;
+
+ set_last_from_to(user_in, unixname);
+ store_map_in_gencache(ctx, user_in, unixname);
+ TALLOC_FREE(*p_user_out);
+ *p_user_out = talloc_strdup(ctx, unixname);
+ if (!*p_user_out) {
+ TALLOC_FREE(dosuserlist);
+ fclose(f);
+ return false;
+ }
+
+ if ( return_if_mapped ) {
+ TALLOC_FREE(dosuserlist);
+ fclose(f);
+ return True;
+ }
+ }
+
+ TALLOC_FREE(dosuserlist);
+ }
+
+ fclose(f);
+
+ /*
+ * If we didn't successfully map a user in the loop above,
+ * setup the last_from and last_to as an optimization so
+ * that we don't scan the file again for the same user.
+ */
+ if (!mapped_user) {
+ DEBUG(8, ("The user '%s' has no mapping. "
+ "Skip it next time.\n", user_in));
+ set_last_from_to(user_in, user_in);
+ store_map_in_gencache(ctx, user_in, user_in);
+ }
+
+ return mapped_user;
+}
diff --git a/source3/auth/wscript_build b/source3/auth/wscript_build
new file mode 100644
index 0000000..97008fc
--- /dev/null
+++ b/source3/auth/wscript_build
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_SUBSYSTEM('TOKEN_UTIL',
+ source='token_util.c',
+ deps='samba-util pdb')
+
+bld.SAMBA3_SUBSYSTEM('USER_UTIL',
+ source='user_util.c',
+ deps='TOKEN_UTIL')
+
+bld.SAMBA3_SUBSYSTEM('AUTH_COMMON',
+ source='''auth_util.c
+ check_samsec.c
+ server_info.c
+ server_info_sam.c
+ user_info.c''',
+ deps='TOKEN_UTIL DCUTIL USER_UTIL common_auth')
+
+bld.SAMBA3_LIBRARY('auth',
+ source='''auth.c
+ user_krb5.c
+ auth_ntlmssp.c
+ auth_generic.c''',
+ deps='''PLAINTEXT_AUTH SLCACHE DCUTIL TOKEN_UTIL AUTH_COMMON libcli_netlogon3 samba-hostconfig MESSAGING''',
+ private_library=True)
+
+bld.SAMBA3_MODULE('auth_sam',
+ subsystem='auth',
+ source='auth_sam.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=True)
+
+bld.SAMBA3_MODULE('auth_unix',
+ subsystem='auth',
+ source='auth_unix.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('auth_unix'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('auth_unix'))
+
+bld.SAMBA3_MODULE('auth_winbind',
+ subsystem='auth',
+ source='auth_winbind.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=True)
+
+bld.SAMBA3_MODULE('auth_builtin',
+ subsystem='auth',
+ source='auth_builtin.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=True)
+
+bld.SAMBA3_MODULE('auth_samba4',
+ subsystem='auth',
+ source='auth_samba4.c',
+ init_function='',
+ deps='auth4 samba_server_gensec gensec',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('auth_samba4') and bld.AD_DC_BUILD_IS_ENABLED(),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('auth_samba4') and bld.AD_DC_BUILD_IS_ENABLED())
diff --git a/source3/build/__init__.py b/source3/build/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/source3/build/__init__.py
diff --git a/source3/build/charset.py b/source3/build/charset.py
new file mode 100644
index 0000000..6fc1575
--- /dev/null
+++ b/source3/build/charset.py
@@ -0,0 +1,48 @@
+# tests for charsets for Samba3
+
+from waflib.Configure import conf
+
+
+@conf
+def CHECK_SAMBA3_CHARSET(conf, crossbuild=False):
+ '''Check for default charsets for Samba3
+ '''
+ if conf.CHECK_ICONV(define='HAVE_NATIVE_ICONV'):
+ default_dos_charset = False
+ default_unix_charset = False
+
+ # check for default dos charset name
+ for charset in ['CP850', 'IBM850']:
+ if conf.CHECK_CHARSET_EXISTS(charset, headers='iconv.h'):
+ default_dos_charset = charset
+ break
+
+ # check for default unix charset name
+ for charset in ['UTF-8', 'UTF8']:
+ if conf.CHECK_CHARSET_EXISTS(charset, headers='iconv.h'):
+ default_unix_charset = charset
+ break
+
+ # At this point, we have a libiconv candidate. We know that
+ # we have the right headers and libraries, but we don't know
+ # whether it does the conversions we want. We can't test this
+ # because we are cross-compiling. This is not necessarily a big
+ # deal, since we can't guarantee that the results we get now will
+ # match the results we get at runtime anyway.
+ if crossbuild:
+ default_dos_charset = "CP850"
+ default_unix_charset = "UTF-8"
+ # TODO: this used to warn about the set charset on cross builds
+
+ if default_dos_charset is False or default_unix_charset is False:
+ # we found iconv, but it failed to convert anything (e.g. on AIX)
+ conf.undefine('HAVE_NATIVE_ICONV')
+ default_dos_charset = "ASCII"
+ default_unix_charset = "UTF-8"
+
+ conf.DEFINE('DEFAULT_DOS_CHARSET', default_dos_charset, quote=True)
+ conf.DEFINE('DEFAULT_UNIX_CHARSET', default_unix_charset, quote=True)
+
+ else:
+ conf.DEFINE('DEFAULT_DOS_CHARSET', "ASCII", quote=True)
+ conf.DEFINE('DEFAULT_UNIX_CHARSET', "UTF8", quote=True)
diff --git a/source3/client/README.smbspool b/source3/client/README.smbspool
new file mode 100644
index 0000000..f73167a
--- /dev/null
+++ b/source3/client/README.smbspool
@@ -0,0 +1,17 @@
+smbspool
+=========
+
+smbspool is a very small print spooling program that sends a print file to an
+SMB printer. The command-line arguments are position-dependent for
+compatibility with the CUPS.
+
+For printing support with Kerberos, CUPS 1.5+ needs a wrapper for the backend
+which sets the correct location of the Kerberos credential cache.
+
+smbspool_krb5_wrapper
+======================
+
+This tool can be used to print using Kerberos credentials. To get this working
+smbspool_krb5_wrapper needs to be the smb backend of CUPS. It needs to be owned
+by root and the permissions for the binary need to be 0700. Once
+smbspool_krb5_wrapper switched to the user trying to print it executes smbspool.
diff --git a/source3/client/client.c b/source3/client/client.c
new file mode 100644
index 0000000..267e3eb
--- /dev/null
+++ b/source3/client/client.c
@@ -0,0 +1,6806 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB client
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Simo Sorce 2001-2002
+ Copyright (C) Jelmer Vernooij 2003
+ Copyright (C) Gerald (Jerry) Carter 2004
+ Copyright (C) Jeremy Allison 1994-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "rpc_client/cli_pipe.h"
+#include "client/client_proto.h"
+#include "client/clitar_proto.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "../lib/util/select.h"
+#include "system/readline.h"
+#include "../libcli/smbreadline/smbreadline.h"
+#include "../libcli/security/security.h"
+#include "system/select.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "trans2.h"
+#include "libsmb/nmblib.h"
+#include "include/ntioctl.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/util/time_basic.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/cmdline/cmdline.h"
+#include "libcli/smb/reparse.h"
+#include "lib/param/param.h"
+
+#ifndef REGISTER
+#define REGISTER 0
+#endif
+
+extern int do_smb_browse(void); /* mDNS browsing */
+
+static int port = 0;
+static char *service;
+static char *desthost;
+static bool grepable = false;
+static bool quiet = false;
+static char *cmdstr = NULL;
+const char *cmd_ptr = NULL;
+
+static int io_bufsize = 0; /* we use the default size */
+static int io_timeout = (CLIENT_TIMEOUT/1000); /* Per operation timeout (in seconds). */
+
+static int name_type = 0x20;
+
+static int process_tok(char *tok);
+static int cmd_help(void);
+
+/* value for unused fid field in trans2 secondary request */
+#define FID_UNUSED (0xFFFF)
+
+time_t newer_than = 0;
+static int archive_level = 0;
+
+static bool translation = false;
+static bool have_ip;
+
+static bool prompt = true;
+
+static bool recurse = false;
+static bool showacls = false;
+bool lowercase = false;
+static bool backup_intent = false;
+
+static struct sockaddr_storage dest_ss;
+static char dest_ss_str[INET6_ADDRSTRLEN];
+
+#define SEPARATORS " \t\n\r"
+
+/* timing globals */
+uint64_t get_total_size = 0;
+unsigned int get_total_time_ms = 0;
+static uint64_t put_total_size = 0;
+static unsigned int put_total_time_ms = 0;
+
+/* totals globals */
+static double dir_total;
+
+/* root cli_state connection */
+
+struct cli_state *cli;
+
+static char CLI_DIRSEP_CHAR = '\\';
+static char CLI_DIRSEP_STR[] = { '\\', '\0' };
+
+/* Accessor functions for directory paths. */
+static char *fileselection;
+static const char *client_get_fileselection(void)
+{
+ if (fileselection) {
+ return fileselection;
+ }
+ return "";
+}
+
+static const char *client_set_fileselection(const char *new_fs)
+{
+ SAFE_FREE(fileselection);
+ if (new_fs) {
+ fileselection = SMB_STRDUP(new_fs);
+ }
+ return client_get_fileselection();
+}
+
+static char *cwd;
+static const char *client_get_cwd(void)
+{
+ if (cwd) {
+ return cwd;
+ }
+ return CLI_DIRSEP_STR;
+}
+
+static const char *client_set_cwd(const char *new_cwd)
+{
+ SAFE_FREE(cwd);
+ if (new_cwd) {
+ cwd = SMB_STRDUP(new_cwd);
+ }
+ return client_get_cwd();
+}
+
+static char *cur_dir;
+const char *client_get_cur_dir(void)
+{
+ if (cur_dir) {
+ return cur_dir;
+ }
+ return CLI_DIRSEP_STR;
+}
+
+const char *client_set_cur_dir(const char *newdir)
+{
+ SAFE_FREE(cur_dir);
+ if (newdir) {
+ cur_dir = SMB_STRDUP(newdir);
+ }
+ return client_get_cur_dir();
+}
+
+/****************************************************************************
+ Put up a yes/no prompt.
+****************************************************************************/
+
+static bool yesno(const char *p)
+{
+ char ans[20];
+ printf("%s",p);
+
+ if (!fgets(ans,sizeof(ans)-1,stdin))
+ return(False);
+
+ if (*ans == 'y' || *ans == 'Y')
+ return(True);
+
+ return(False);
+}
+
+/****************************************************************************
+ Write to a local file with CR/LF->LF translation if appropriate. Return the
+ number taken from the buffer. This may not equal the number written.
+****************************************************************************/
+
+static ssize_t writefile(int f, char *b, size_t n)
+{
+ size_t i = 0;
+
+ if (n == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!translation) {
+ return write(f,b,n);
+ }
+
+ do {
+ if (*b == '\r' && (i<(n-1)) && *(b+1) == '\n') {
+ b++;i++;
+ }
+ if (write(f, b, 1) != 1) {
+ break;
+ }
+ b++;
+ i++;
+ } while (i < n);
+
+ return (ssize_t)i;
+}
+
+/****************************************************************************
+ Read from a file with LF->CR/LF translation if appropriate. Return the
+ number read. read approx n bytes.
+****************************************************************************/
+
+static int readfile(uint8_t *b, int n, FILE *f)
+{
+ int i;
+ int c;
+
+ if (!translation)
+ return fread(b,1,n,f);
+
+ i = 0;
+ while (i < (n - 1)) {
+ if ((c = getc(f)) == EOF) {
+ break;
+ }
+
+ if (c == '\n') { /* change all LFs to CR/LF */
+ b[i++] = '\r';
+ }
+
+ b[i++] = c;
+ }
+
+ return(i);
+}
+
+struct push_state {
+ FILE *f;
+ off_t nread;
+};
+
+static size_t push_source(uint8_t *buf, size_t n, void *priv)
+{
+ struct push_state *state = (struct push_state *)priv;
+ int result;
+
+ if (feof(state->f)) {
+ return 0;
+ }
+
+ result = readfile(buf, n, state->f);
+ state->nread += result;
+ return result;
+}
+
+/****************************************************************************
+ Send a message.
+****************************************************************************/
+
+static void send_message(const char *username)
+{
+ char buf[1600];
+ NTSTATUS status;
+ size_t i;
+
+ d_printf("Type your message, ending it with a Control-D\n");
+
+ i = 0;
+ while (i<sizeof(buf)-2) {
+ int c = fgetc(stdin);
+ if (c == EOF) {
+ break;
+ }
+ if (c == '\n') {
+ buf[i++] = '\r';
+ }
+ buf[i++] = c;
+ }
+ buf[i] = '\0';
+
+ status = cli_message(cli, desthost, username, buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_message returned %s\n",
+ nt_errstr(status));
+ }
+}
+
+/****************************************************************************
+ Check the space on a device.
+****************************************************************************/
+
+static int do_dskattr(void)
+{
+ uint64_t total, bsize, avail;
+ struct cli_state *targetcli = NULL;
+ char *targetpath = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ status = cli_resolve_path(ctx,
+ "",
+ creds,
+ cli,
+ client_get_cur_dir(), &targetcli,
+ &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error in dskattr: %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_disk_size(targetcli, targetpath, &bsize, &total, &avail);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error in dskattr: %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("\n\t\t%" PRIu64
+ " blocks of size %" PRIu64
+ ". %" PRIu64 " blocks available\n",
+ total, bsize, avail);
+
+ return 0;
+}
+
+/****************************************************************************
+ Show cd/pwd.
+****************************************************************************/
+
+static int cmd_pwd(void)
+{
+ d_printf("Current directory is %s",service);
+ d_printf("%s\n",client_get_cur_dir());
+ return 0;
+}
+
+/****************************************************************************
+ Ensure name has correct directory separators.
+****************************************************************************/
+
+static void normalize_name(char *newdir)
+{
+ if (!(cli->requested_posix_capabilities & CIFS_UNIX_POSIX_PATHNAMES_CAP)) {
+ string_replace(newdir,'/','\\');
+ }
+}
+
+/****************************************************************************
+ Local name cleanup before sending to server. SMB1 allows relative pathnames,
+ but SMB2 does not, so we need to resolve them locally.
+****************************************************************************/
+
+char *client_clean_name(TALLOC_CTX *ctx, const char *name)
+{
+ char *newname = NULL;
+ if (name == NULL) {
+ return NULL;
+ }
+
+ /* First ensure any path separators are correct. */
+ newname = talloc_strdup(ctx, name);
+ if (newname == NULL) {
+ return NULL;
+ }
+ normalize_name(newname);
+
+ /* Now remove any relative (..) path components. */
+ if (cli->requested_posix_capabilities & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
+ newname = unix_clean_name(ctx, newname);
+ } else {
+ newname = clean_name(ctx, newname);
+ }
+ if (newname == NULL) {
+ return NULL;
+ }
+ return newname;
+}
+
+/****************************************************************************
+ Change directory - inner section.
+****************************************************************************/
+
+static int do_cd(const char *new_dir)
+{
+ char *newdir = NULL;
+ char *saved_dir = NULL;
+ char *new_cd = NULL;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ int ret = 1;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ newdir = talloc_strdup(ctx, new_dir);
+ if (!newdir) {
+ TALLOC_FREE(ctx);
+ return 1;
+ }
+
+ normalize_name(newdir);
+
+ /* Save the current directory in case the new directory is invalid */
+
+ saved_dir = talloc_strdup(ctx, client_get_cur_dir());
+ if (!saved_dir) {
+ TALLOC_FREE(ctx);
+ return 1;
+ }
+
+ if (*newdir == CLI_DIRSEP_CHAR) {
+ client_set_cur_dir(newdir);
+ new_cd = newdir;
+ } else {
+ new_cd = talloc_asprintf(ctx, "%s%s",
+ client_get_cur_dir(),
+ newdir);
+ if (!new_cd) {
+ goto out;
+ }
+ }
+
+ /* Ensure cur_dir ends in a DIRSEP */
+ if ((new_cd[0] != '\0') && (*(new_cd+strlen(new_cd)-1) != CLI_DIRSEP_CHAR)) {
+ new_cd = talloc_asprintf_append(new_cd, "%s", CLI_DIRSEP_STR);
+ if (!new_cd) {
+ goto out;
+ }
+ }
+ client_set_cur_dir(new_cd);
+
+ new_cd = client_clean_name(ctx, new_cd);
+ client_set_cur_dir(new_cd);
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, new_cd, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cd %s: %s\n", new_cd, nt_errstr(status));
+ client_set_cur_dir(saved_dir);
+ goto out;
+ }
+
+ if (strequal(targetpath,CLI_DIRSEP_STR )) {
+ TALLOC_FREE(ctx);
+ return 0;
+ }
+
+
+ targetpath = talloc_asprintf(
+ ctx, "%s%s", targetpath, CLI_DIRSEP_STR);
+ if (!targetpath) {
+ client_set_cur_dir(saved_dir);
+ goto out;
+ }
+ targetpath = client_clean_name(ctx, targetpath);
+ if (!targetpath) {
+ client_set_cur_dir(saved_dir);
+ goto out;
+ }
+
+ status = cli_chkpath(targetcli, targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cd %s: %s\n", new_cd, nt_errstr(status));
+ client_set_cur_dir(saved_dir);
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Change directory.
+****************************************************************************/
+
+static int cmd_cd(void)
+{
+ char *buf = NULL;
+ int rc = 0;
+
+ if (next_token_talloc(talloc_tos(), &cmd_ptr, &buf,NULL)) {
+ rc = do_cd(buf);
+ } else {
+ d_printf("Current directory is %s\n",client_get_cur_dir());
+ }
+
+ return rc;
+}
+
+/****************************************************************************
+ Change directory.
+****************************************************************************/
+
+static int cmd_cd_oneup(void)
+{
+ return do_cd("..");
+}
+
+/*******************************************************************
+ Decide if a file should be operated on.
+********************************************************************/
+
+static bool do_this_one(struct file_info *finfo)
+{
+ if (!finfo->name) {
+ return false;
+ }
+
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ return true;
+ }
+
+ if (*client_get_fileselection() &&
+ !mask_match(finfo->name,client_get_fileselection(),false)) {
+ DEBUG(3,("mask_match %s failed\n", finfo->name));
+ return false;
+ }
+
+ if (newer_than && finfo->mtime_ts.tv_sec < newer_than) {
+ DEBUG(3,("newer_than %s failed\n", finfo->name));
+ return false;
+ }
+
+ if ((archive_level==1 || archive_level==2) && !(finfo->attr & FILE_ATTRIBUTE_ARCHIVE)) {
+ DEBUG(3,("archive %s failed\n", finfo->name));
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Display info about a file.
+****************************************************************************/
+
+static NTSTATUS display_finfo(struct cli_state *cli_state, struct file_info *finfo,
+ const char *dir)
+{
+ time_t t;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (!do_this_one(finfo)) {
+ return NT_STATUS_OK;
+ }
+
+ t = finfo->mtime_ts.tv_sec; /* the time is assumed to be passed as GMT */
+ if (!showacls) {
+ d_printf(" %-30s%7.7s %8.0f %s",
+ finfo->name,
+ attrib_string(talloc_tos(), finfo->attr),
+ (double)finfo->size,
+ time_to_asc(t));
+ dir_total += finfo->size;
+ } else {
+ struct cli_state *targetcli = NULL;
+ char *targetpath = NULL;
+ char *afname = NULL;
+ uint16_t fnum;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
+ return NT_STATUS_OK;
+ }
+ /* create absolute filename for cli_ntcreate() FIXME */
+ afname = talloc_asprintf(ctx,
+ "%s%s%s",
+ dir,
+ CLI_DIRSEP_STR,
+ finfo->name);
+ if (!afname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ /* print file meta date header */
+ d_printf( "FILENAME:%s\n", finfo->name);
+ d_printf( "MODE:%s\n", attrib_string(talloc_tos(), finfo->attr));
+ d_printf( "SIZE:%.0f\n", (double)finfo->size);
+ d_printf( "MTIME:%s", time_to_asc(t));
+
+ status = cli_resolve_path(
+ ctx,
+ "",
+ creds,
+ cli_state,
+ afname,
+ &targetcli,
+ &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("display_finfo() Failed to resolve "
+ "%s: %s\n",
+ afname, nt_errstr(status));
+ return status;
+ }
+
+ status = cli_ntcreate(
+ targetcli, /* cli */
+ targetpath, /* fname */
+ 0, /* CreatFlags */
+ READ_CONTROL_ACCESS, /* DesiredAccess */
+ 0, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0x0, /* CreateOptions */
+ 0x0, /* SecurityFlags */
+ &fnum, /* pfid */
+ NULL); /* cr */
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG( 0, ("display_finfo() Failed to open %s: %s\n",
+ afname, nt_errstr(status)));
+ } else {
+ struct security_descriptor *sd = NULL;
+ status = cli_query_secdesc(targetcli, fnum,
+ ctx, &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG( 0, ("display_finfo() failed to "
+ "get security descriptor: %s\n",
+ nt_errstr(status)));
+ } else {
+ display_sec_desc(sd);
+ }
+ TALLOC_FREE(sd);
+ cli_close(targetcli, fnum);
+ }
+ TALLOC_FREE(afname);
+ }
+ return status;
+}
+
+/****************************************************************************
+ Accumulate size of a file.
+****************************************************************************/
+
+static NTSTATUS do_du(struct cli_state *cli_state, struct file_info *finfo,
+ const char *dir)
+{
+ if (do_this_one(finfo)) {
+ dir_total += finfo->size;
+ }
+ return NT_STATUS_OK;
+}
+
+struct do_list_queue_entry {
+ struct do_list_queue_entry *prev, *next;
+ char name[];
+};
+
+struct do_list_queue {
+ struct do_list_queue_entry *list;
+};
+
+static bool do_list_recurse;
+static bool do_list_dirs;
+static struct do_list_queue *queue = NULL;
+static NTSTATUS (*do_list_fn)(struct cli_state *cli_state, struct file_info *,
+ const char *dir);
+
+/****************************************************************************
+ Functions for do_list_queue.
+****************************************************************************/
+
+static void reset_do_list_queue(void)
+{
+ TALLOC_FREE(queue);
+}
+
+static void init_do_list_queue(void)
+{
+ TALLOC_FREE(queue);
+ queue = talloc_zero(NULL, struct do_list_queue);
+}
+
+static void add_to_do_list_queue(const char *entry)
+{
+ struct do_list_queue_entry *e = NULL;
+ size_t entry_str_len = strlen(entry)+1;
+ size_t entry_len = offsetof(struct do_list_queue_entry, name);
+
+ entry_len += entry_str_len;
+ SMB_ASSERT(entry_len >= entry_str_len);
+
+ e = talloc_size(queue, entry_len);
+ if (e == NULL) {
+ d_printf("talloc failed for entry %s\n", entry);
+ return;
+ }
+ talloc_set_name_const(e, "struct do_list_queue_entry");
+
+ memcpy(e->name, entry, entry_str_len);
+ DLIST_ADD_END(queue->list, e);
+}
+
+static char *do_list_queue_head(void)
+{
+ return queue->list->name;
+}
+
+static void remove_do_list_queue_head(void)
+{
+ struct do_list_queue_entry *e = queue->list;
+ DLIST_REMOVE(queue->list, e);
+ TALLOC_FREE(e);
+}
+
+static int do_list_queue_empty(void)
+{
+ return (queue == NULL) || (queue->list == NULL);
+}
+
+/****************************************************************************
+ A helper for do_list.
+****************************************************************************/
+
+struct do_list_helper_state {
+ const char *mask;
+ struct cli_state *cli;
+};
+
+static NTSTATUS do_list_helper(
+ struct file_info *f,
+ const char *_mask,
+ void *private_data)
+{
+ struct do_list_helper_state *state = private_data;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *dir = NULL;
+ char *dir_end = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ char *mask2 = NULL;
+
+ /* Work out the directory. */
+ dir = talloc_strdup(ctx, state->mask);
+ if (!dir) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if ((dir_end = strrchr(dir, CLI_DIRSEP_CHAR)) != NULL) {
+ *dir_end = '\0';
+ }
+
+ if (!(f->attr & FILE_ATTRIBUTE_DIRECTORY)) {
+ if (do_this_one(f)) {
+ status = do_list_fn(state->cli, f, dir);
+ }
+ TALLOC_FREE(dir);
+ return status;
+ }
+
+ if (do_list_dirs && do_this_one(f)) {
+ status = do_list_fn(state->cli, f, dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!do_list_recurse ||
+ (f->name == NULL) ||
+ ISDOT(f->name) ||
+ ISDOTDOT(f->name)) {
+ return NT_STATUS_OK;
+ }
+
+ if (!f->name[0]) {
+ d_printf("Empty dir name returned. Possible server misconfiguration.\n");
+ TALLOC_FREE(dir);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ mask2 = talloc_asprintf(ctx,
+ "%s%c%s%c*",
+ dir,
+ CLI_DIRSEP_CHAR,
+ f->name,
+ CLI_DIRSEP_CHAR);
+ if (!mask2) {
+ TALLOC_FREE(dir);
+ return NT_STATUS_NO_MEMORY;
+ }
+ add_to_do_list_queue(mask2);
+ TALLOC_FREE(mask2);
+
+ TALLOC_FREE(dir);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ A wrapper around cli_list that adds recursion.
+****************************************************************************/
+
+NTSTATUS do_list(const char *mask,
+ uint32_t attribute,
+ NTSTATUS (*fn)(struct cli_state *cli_state, struct file_info *,
+ const char *dir),
+ bool rec,
+ bool dirs)
+{
+ struct do_list_helper_state state = { .cli = cli, };
+ static int in_do_list = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS ret_status = NT_STATUS_OK;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (in_do_list && rec) {
+ fprintf(stderr, "INTERNAL ERROR: do_list called recursively when the recursive flag is true\n");
+ exit(1);
+ }
+
+ in_do_list = 1;
+
+ do_list_recurse = rec;
+ do_list_dirs = dirs;
+ do_list_fn = fn;
+
+ init_do_list_queue();
+ add_to_do_list_queue(mask);
+
+ while (!do_list_queue_empty()) {
+ struct cli_state *targetcli = NULL;
+ char *targetpath = NULL;
+
+ state.mask = do_list_queue_head();
+
+ /* check for dfs */
+
+ status = cli_resolve_path(
+ ctx,
+ "",
+ creds,
+ cli,
+ state.mask,
+ &targetcli,
+ &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("do_list: [%s] %s\n", state.mask,
+ nt_errstr(status));
+ remove_do_list_queue_head();
+ continue;
+ }
+
+ status = cli_list(
+ targetcli,
+ targetpath,
+ attribute,
+ do_list_helper,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s listing %s\n",
+ nt_errstr(status), targetpath);
+ ret_status = status;
+ }
+ remove_do_list_queue_head();
+ if ((! do_list_queue_empty()) && (fn == display_finfo)) {
+ char *next_file = do_list_queue_head();
+ char *save_ch = 0;
+ if ((strlen(next_file) >= 2) &&
+ (next_file[strlen(next_file) - 1] == '*') &&
+ (next_file[strlen(next_file) - 2] == CLI_DIRSEP_CHAR)) {
+ save_ch = next_file +
+ strlen(next_file) - 2;
+ *save_ch = '\0';
+ if (showacls) {
+ /* cwd is only used if showacls is on */
+ client_set_cwd(next_file);
+ }
+ }
+ if (!showacls) /* don't disturbe the showacls output */
+ d_printf("\n%s\n",next_file);
+ if (save_ch) {
+ *save_ch = CLI_DIRSEP_CHAR;
+ }
+ }
+ TALLOC_FREE(targetpath);
+ }
+
+ in_do_list = 0;
+ reset_do_list_queue();
+ return ret_status;
+}
+
+/****************************************************************************
+ Get a directory listing.
+****************************************************************************/
+
+static int cmd_dir(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ uint32_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+ char *mask = NULL;
+ char *buf = NULL;
+ int rc = 1;
+ NTSTATUS status;
+
+ dir_total = 0;
+ mask = talloc_strdup(ctx, client_get_cur_dir());
+ if (!mask) {
+ return 1;
+ }
+
+ if (next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ normalize_name(buf);
+ if (*buf == CLI_DIRSEP_CHAR) {
+ mask = talloc_strdup(ctx, buf);
+ } else {
+ mask = talloc_asprintf_append(mask, "%s", buf);
+ }
+ } else {
+ mask = talloc_asprintf_append(mask, "*");
+ }
+ if (!mask) {
+ return 1;
+ }
+
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ if (showacls) {
+ /* cwd is only used if showacls is on */
+ client_set_cwd(client_get_cur_dir());
+ }
+
+ status = do_list(mask, attribute, display_finfo, recurse, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+
+ rc = do_dskattr();
+
+ DEBUG(3, ("Total bytes listed: %.0f\n", dir_total));
+
+ return rc;
+}
+
+/****************************************************************************
+ Get a directory listing.
+****************************************************************************/
+
+static int cmd_du(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ uint32_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+ char *mask = NULL;
+ char *buf = NULL;
+ NTSTATUS status;
+ int rc = 1;
+
+ dir_total = 0;
+ mask = talloc_strdup(ctx, client_get_cur_dir());
+ if (!mask) {
+ return 1;
+ }
+ if ((mask[0] != '\0') && (mask[strlen(mask)-1]!=CLI_DIRSEP_CHAR)) {
+ mask = talloc_asprintf_append(mask, "%s", CLI_DIRSEP_STR);
+ if (!mask) {
+ return 1;
+ }
+ }
+
+ if (next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ normalize_name(buf);
+ if (*buf == CLI_DIRSEP_CHAR) {
+ mask = talloc_strdup(ctx, buf);
+ } else {
+ mask = talloc_asprintf_append(mask, "%s", buf);
+ }
+ } else {
+ mask = talloc_strdup(ctx, "*");
+ }
+ if (!mask) {
+ return 1;
+ }
+
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ status = do_list(mask, attribute, do_du, recurse, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+
+ rc = do_dskattr();
+
+ d_printf("Total number of bytes: %.0f\n", dir_total);
+
+ return rc;
+}
+
+static int cmd_echo(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *num;
+ char *data;
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &num, NULL)
+ || !next_token_talloc(ctx, &cmd_ptr, &data, NULL)) {
+ d_printf("echo <num> <data>\n");
+ return 1;
+ }
+
+ status = cli_echo(cli, atoi(num), data_blob_const(data, strlen(data)));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("echo failed: %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Get a file from rname to lname
+****************************************************************************/
+
+static NTSTATUS writefile_sink(char *buf, size_t n, void *priv)
+{
+ int *pfd = (int *)priv;
+ ssize_t rc;
+
+ rc = writefile(*pfd, buf, n);
+ if (rc == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ return NT_STATUS_OK;
+}
+
+static int do_get(const char *rname, const char *lname_in, bool reget)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ int handle = 0;
+ uint16_t fnum;
+ bool newhandle = false;
+ struct timespec tp_start;
+ uint32_t attr;
+ off_t size;
+ off_t start = 0;
+ off_t nread = 0;
+ int rc = 0;
+ struct cli_state *targetcli = NULL;
+ char *targetname = NULL;
+ char *lname = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ lname = talloc_strdup(ctx, lname_in);
+ if (!lname) {
+ return 1;
+ }
+
+ if (lowercase) {
+ if (!strlower_m(lname)) {
+ d_printf("strlower_m %s failed\n", lname);
+ return 1;
+ }
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, rname, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to open %s: %s\n", rname, nt_errstr(status));
+ return 1;
+ }
+
+ clock_gettime_mono(&tp_start);
+
+ status = cli_open(targetcli, targetname, O_RDONLY, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s opening remote file %s\n", nt_errstr(status),
+ rname);
+ return 1;
+ }
+
+ if(!strcmp(lname,"-")) {
+ handle = fileno(stdout);
+ } else {
+ if (reget) {
+ handle = open(lname, O_WRONLY|O_CREAT, 0644);
+ if (handle >= 0) {
+ start = lseek(handle, 0, SEEK_END);
+ if (start == -1) {
+ d_printf("Error seeking local file\n");
+ close(handle);
+ return 1;
+ }
+ }
+ } else {
+ handle = open(lname, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ }
+ newhandle = true;
+ }
+ if (handle < 0) {
+ d_printf("Error opening local file %s\n",lname);
+ return 1;
+ }
+
+
+ status = cli_qfileinfo_basic(targetcli, fnum, &attr, &size, NULL, NULL,
+ NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("getattrib: %s\n", nt_errstr(status));
+ if (newhandle) {
+ close(handle);
+ }
+ return 1;
+ }
+
+ DEBUG(1,("getting file %s of size %.0f as %s ",
+ rname, (double)size, lname));
+
+ status = cli_pull(targetcli, fnum, start, size, io_bufsize,
+ writefile_sink, (void *)&handle, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "parallel_read returned %s\n",
+ nt_errstr(status));
+ if (newhandle) {
+ close(handle);
+ }
+ cli_close(targetcli, fnum);
+ return 1;
+ }
+
+ status = cli_close(targetcli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error %s closing remote file\n", nt_errstr(status));
+ rc = 1;
+ }
+
+ if (newhandle) {
+ close(handle);
+ }
+
+ if (archive_level >= 2 && (attr & FILE_ATTRIBUTE_ARCHIVE)) {
+ cli_setatr(cli, rname, attr & ~(uint32_t)FILE_ATTRIBUTE_ARCHIVE, 0);
+ }
+
+ {
+ struct timespec tp_end;
+ int this_time;
+
+ clock_gettime_mono(&tp_end);
+ this_time = nsec_time_diff(&tp_end,&tp_start)/1000000;
+ get_total_time_ms += this_time;
+ get_total_size += nread;
+
+ DEBUG(1,("(%3.1f KiloBytes/sec) (average %3.1f KiloBytes/sec)\n",
+ nread / (1.024*this_time + 1.0e-4),
+ get_total_size / (1.024*get_total_time_ms)));
+ }
+
+ TALLOC_FREE(targetname);
+ return rc;
+}
+
+/****************************************************************************
+ Get a file.
+****************************************************************************/
+
+static int cmd_get(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *lname = NULL;
+ char *rname = NULL;
+ char *fname = NULL;
+
+ rname = talloc_strdup(ctx, client_get_cur_dir());
+ if (!rname) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&fname,NULL)) {
+ d_printf("get <filename> [localname]\n");
+ return 1;
+ }
+ rname = talloc_asprintf_append(rname, "%s", fname);
+ if (!rname) {
+ return 1;
+ }
+ rname = client_clean_name(ctx, rname);
+ if (!rname) {
+ return 1;
+ }
+
+ next_token_talloc(ctx, &cmd_ptr,&lname,NULL);
+ if (!lname) {
+ lname = fname;
+ }
+
+ return do_get(rname, lname, false);
+}
+
+/****************************************************************************
+ Do an mget operation on one file.
+****************************************************************************/
+
+static NTSTATUS do_mget(struct cli_state *cli_state, struct file_info *finfo,
+ const char *dir)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ const char *client_cwd = NULL;
+ size_t client_cwd_len;
+ char *path = NULL;
+ char *local_path = NULL;
+
+ if (!finfo->name) {
+ return NT_STATUS_OK;
+ }
+
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
+ return NT_STATUS_OK;
+ }
+
+ if ((finfo->attr & FILE_ATTRIBUTE_DIRECTORY) && !recurse) {
+ return NT_STATUS_OK;
+ }
+
+ if (prompt) {
+ const char *object = (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) ?
+ "directory" : "file";
+ char *quest = NULL;
+ bool ok;
+
+ quest = talloc_asprintf(
+ ctx, "Get %s %s? ", object, finfo->name);
+ if (quest == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = yesno(quest);
+ TALLOC_FREE(quest);
+ if (!ok) {
+ return NT_STATUS_OK;
+ }
+ }
+
+ path = talloc_asprintf(
+ ctx, "%s%c%s", dir, CLI_DIRSEP_CHAR, finfo->name);
+ if (path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ path = client_clean_name(ctx, path);
+ if (path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Skip the path prefix if we've done a remote "cd" when
+ * creating the local path
+ */
+ client_cwd = client_get_cur_dir();
+ client_cwd_len = strlen(client_cwd);
+
+ local_path = talloc_strdup(ctx, path + client_cwd_len);
+ if (local_path == NULL) {
+ TALLOC_FREE(path);
+ return NT_STATUS_NO_MEMORY;
+ }
+ string_replace(local_path, CLI_DIRSEP_CHAR, '/');
+
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ int ret = mkdir(local_path, 0777);
+
+ if ((ret == -1) && (errno != EEXIST)) {
+ return map_nt_error_from_unix(errno);
+ }
+ } else {
+ do_get(path, local_path, false);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ View the file using the pager.
+****************************************************************************/
+
+static int cmd_more(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *rname = NULL;
+ char *fname = NULL;
+ char *lname = NULL;
+ char *pager_cmd = NULL;
+ const char *pager;
+ int fd;
+ int rc = 0;
+ mode_t mask;
+
+ rname = talloc_strdup(ctx, client_get_cur_dir());
+ if (!rname) {
+ return 1;
+ }
+
+ lname = talloc_asprintf(ctx, "%s/smbmore.XXXXXX",tmpdir());
+ if (!lname) {
+ return 1;
+ }
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(lname);
+ umask(mask);
+ if (fd == -1) {
+ d_printf("failed to create temporary file for more\n");
+ return 1;
+ }
+ close(fd);
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&fname,NULL)) {
+ d_printf("more <filename>\n");
+ unlink(lname);
+ return 1;
+ }
+ rname = talloc_asprintf_append(rname, "%s", fname);
+ if (!rname) {
+ return 1;
+ }
+ rname = client_clean_name(ctx,rname);
+ if (!rname) {
+ return 1;
+ }
+
+ rc = do_get(rname, lname, false);
+
+ pager=getenv("PAGER");
+
+ pager_cmd = talloc_asprintf(ctx,
+ "%s %s",
+ (pager? pager:PAGER),
+ lname);
+ if (!pager_cmd) {
+ return 1;
+ }
+ if (system(pager_cmd) == -1) {
+ d_printf("system command '%s' returned -1\n",
+ pager_cmd);
+ }
+ unlink(lname);
+
+ return rc;
+}
+
+/****************************************************************************
+ Do a mget command.
+****************************************************************************/
+
+static int cmd_mget(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ uint32_t attribute = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+ char *mget_mask = NULL;
+ char *buf = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (recurse) {
+ attribute |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+
+ while (next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+
+ mget_mask = talloc_strdup(ctx, client_get_cur_dir());
+ if (!mget_mask) {
+ return 1;
+ }
+ if (*buf == CLI_DIRSEP_CHAR) {
+ mget_mask = talloc_strdup(ctx, buf);
+ } else {
+ mget_mask = talloc_asprintf_append(mget_mask,
+ "%s", buf);
+ }
+ if (!mget_mask) {
+ return 1;
+ }
+ mget_mask = client_clean_name(ctx, mget_mask);
+ if (mget_mask == NULL) {
+ return 1;
+ }
+ status = do_list(mget_mask, attribute, do_mget, recurse, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+ }
+
+ if (mget_mask == NULL) {
+ d_printf("nothing to mget\n");
+ return 0;
+ }
+
+ if (!*mget_mask) {
+ mget_mask = talloc_asprintf(ctx,
+ "%s*",
+ client_get_cur_dir());
+ if (!mget_mask) {
+ return 1;
+ }
+ mget_mask = client_clean_name(ctx, mget_mask);
+ if (mget_mask == NULL) {
+ return 1;
+ }
+ status = do_list(mget_mask, attribute, do_mget, recurse, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Make a directory of name "name".
+****************************************************************************/
+
+static bool do_mkdir(const char *name)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ struct cli_state *targetcli;
+ char *targetname = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, name, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("mkdir %s: %s\n", name, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_mkdir(targetcli, targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s making remote directory %s\n",
+ nt_errstr(status),name);
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Show 8.3 name of a file.
+****************************************************************************/
+
+static bool do_altname(const char *name)
+{
+ fstring altname;
+ NTSTATUS status;
+
+ status = cli_qpathinfo_alt_name(cli, name, altname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s getting alt name for %s\n",
+ nt_errstr(status),name);
+ return false;
+ }
+ d_printf("%s\n", altname);
+
+ return true;
+}
+
+/****************************************************************************
+ Exit client.
+****************************************************************************/
+
+static int cmd_quit(void)
+{
+ cli_shutdown(cli);
+ exit(0);
+ /* NOTREACHED */
+ return 0;
+}
+
+/****************************************************************************
+ Make a directory.
+****************************************************************************/
+
+static int cmd_mkdir(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ mask = talloc_strdup(ctx, client_get_cur_dir());
+ if (!mask) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ if (!recurse) {
+ d_printf("mkdir <dirname>\n");
+ }
+ return 1;
+ }
+ mask = talloc_asprintf_append(mask, "%s", buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ if (recurse) {
+ char *ddir = NULL;
+ char *ddir2 = NULL;
+ struct cli_state *targetcli;
+ char *targetname = NULL;
+ char *p = NULL;
+ char *saveptr;
+
+ ddir2 = talloc_strdup(ctx, "");
+ if (!ddir2) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask,
+ &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+
+ ddir = talloc_strdup(ctx, targetname);
+ if (!ddir) {
+ return 1;
+ }
+ trim_char(ddir,'.','\0');
+ p = strtok_r(ddir, "/\\", &saveptr);
+ while (p) {
+ ddir2 = talloc_asprintf_append(ddir2, "%s", p);
+ if (!ddir2) {
+ return 1;
+ }
+ if (!NT_STATUS_IS_OK(cli_chkpath(targetcli, ddir2))) {
+ do_mkdir(ddir2);
+ }
+ ddir2 = talloc_asprintf_append(ddir2, "%s", CLI_DIRSEP_STR);
+ if (!ddir2) {
+ return 1;
+ }
+ p = strtok_r(NULL, "/\\", &saveptr);
+ }
+ } else {
+ do_mkdir(mask);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Show alt name.
+****************************************************************************/
+
+static int cmd_altname(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *name;
+ char *buf;
+
+ name = talloc_strdup(ctx, client_get_cur_dir());
+ if (!name) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ d_printf("altname <file>\n");
+ return 1;
+ }
+ name = talloc_asprintf_append(name, "%s", buf);
+ if (!name) {
+ return 1;
+ }
+ name = client_clean_name(ctx, name);
+ if (name == NULL) {
+ return 1;
+ }
+ do_altname(name);
+ return 0;
+}
+
+static char *attr_str(TALLOC_CTX *mem_ctx, uint32_t attr)
+{
+ char *attrs = talloc_zero_array(mem_ctx, char, 17);
+ int i = 0;
+
+ if (!(attr & FILE_ATTRIBUTE_NORMAL)) {
+ if (attr & FILE_ATTRIBUTE_ENCRYPTED) {
+ attrs[i++] = 'E';
+ }
+ if (attr & FILE_ATTRIBUTE_NONINDEXED) {
+ attrs[i++] = 'N';
+ }
+ if (attr & FILE_ATTRIBUTE_OFFLINE) {
+ attrs[i++] = 'O';
+ }
+ if (attr & FILE_ATTRIBUTE_COMPRESSED) {
+ attrs[i++] = 'C';
+ }
+ if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
+ attrs[i++] = 'r';
+ }
+ if (attr & FILE_ATTRIBUTE_SPARSE) {
+ attrs[i++] = 's';
+ }
+ if (attr & FILE_ATTRIBUTE_TEMPORARY) {
+ attrs[i++] = 'T';
+ }
+ if (attr & FILE_ATTRIBUTE_NORMAL) {
+ attrs[i++] = 'N';
+ }
+ if (attr & FILE_ATTRIBUTE_READONLY) {
+ attrs[i++] = 'R';
+ }
+ if (attr & FILE_ATTRIBUTE_HIDDEN) {
+ attrs[i++] = 'H';
+ }
+ if (attr & FILE_ATTRIBUTE_SYSTEM) {
+ attrs[i++] = 'S';
+ }
+ if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+ attrs[i++] = 'D';
+ }
+ if (attr & FILE_ATTRIBUTE_ARCHIVE) {
+ attrs[i++] = 'A';
+ }
+ }
+ return attrs;
+}
+
+/****************************************************************************
+ Show all info we can get
+****************************************************************************/
+
+static int do_allinfo(const char *name)
+{
+ fstring altname;
+ struct timespec b_time, a_time, m_time, c_time;
+ off_t size;
+ uint32_t attr;
+ NTTIME tmp;
+ uint16_t fnum;
+ unsigned int num_streams;
+ struct stream_struct *streams;
+ int j, num_snapshots;
+ char **snapshots = NULL;
+ unsigned int i;
+ NTSTATUS status;
+
+ status = cli_qpathinfo_alt_name(cli, name, altname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s getting alt name for %s\n", nt_errstr(status),
+ name);
+ /*
+ * Ignore not supported or not implemented, it does not
+ * hurt if we can't list alternate names.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ altname[0] = '\0';
+ } else {
+ return false;
+ }
+ }
+ d_printf("altname: %s\n", altname);
+
+ status = cli_qpathinfo3(cli, name, &b_time, &a_time, &m_time, &c_time,
+ &size, &attr, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s getting pathinfo for %s\n", nt_errstr(status),
+ name);
+ return false;
+ }
+
+ tmp = full_timespec_to_nt_time(&b_time);
+ d_printf("create_time: %s\n", nt_time_string(talloc_tos(), tmp));
+
+ tmp = full_timespec_to_nt_time(&a_time);
+ d_printf("access_time: %s\n", nt_time_string(talloc_tos(), tmp));
+
+ tmp = full_timespec_to_nt_time(&m_time);
+ d_printf("write_time: %s\n", nt_time_string(talloc_tos(), tmp));
+
+ tmp = full_timespec_to_nt_time(&c_time);
+ d_printf("change_time: %s\n", nt_time_string(talloc_tos(), tmp));
+
+ d_printf("attributes: %s (%x)\n", attr_str(talloc_tos(), attr), attr);
+
+ status = cli_qpathinfo_streams(cli, name, talloc_tos(), &num_streams,
+ &streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "%s getting streams for %s\n",
+ nt_errstr(status),
+ name);
+ num_streams = 0;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ d_printf("stream: [%s], %lld bytes\n", streams[i].name,
+ (unsigned long long)streams[i].size);
+ }
+
+ if (attr & FILE_ATTRIBUTE_REPARSE_POINT) {
+ struct reparse_data_buffer *rep = NULL;
+ uint8_t *data = NULL;
+ uint32_t datalen;
+ char *s = NULL;
+
+ rep = talloc_zero(talloc_tos(), struct reparse_data_buffer);
+ if (rep == NULL) {
+ d_printf("talloc_zero() failed\n");
+ return false;
+ }
+
+ status = cli_get_reparse_data(cli, name, rep, &data, &datalen);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_get_reparse_data() failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(rep);
+ return false;
+ }
+
+ status = reparse_data_buffer_parse(rep, rep, data, datalen);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "reparse_data_buffer_parse() failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(rep);
+ return false;
+ }
+
+ s = reparse_data_buffer_str(rep, rep);
+ d_printf("%s", s);
+ TALLOC_FREE(rep);
+ }
+
+ status = cli_ntcreate(cli, name, 0,
+ SEC_FILE_READ_DATA | SEC_FILE_READ_ATTRIBUTE |
+ SEC_STD_SYNCHRONIZE, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE
+ |FILE_SHARE_DELETE,
+ FILE_OPEN, 0x0, 0x0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Ignore failure, it does not hurt if we can't list
+ * snapshots
+ */
+ return 0;
+ }
+ /*
+ * In order to get shadow copy data over SMB1 we
+ * must call twice, once with 'get_names = false'
+ * to get the size, then again with 'get_names = true'
+ * to get the data or a Windows server fails to return
+ * valid info. Samba doesn't have this bug. JRA.
+ */
+
+ status = cli_shadow_copy_data(talloc_tos(), cli, fnum,
+ false, &snapshots, &num_snapshots);
+ if (NT_STATUS_IS_OK(status)) {
+ status = cli_shadow_copy_data(talloc_tos(),
+ cli,
+ fnum,
+ true,
+ &snapshots,
+ &num_snapshots);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "%s getting shadow copy data for %s\n",
+ nt_errstr(status),
+ name);
+ num_snapshots = 0;
+ }
+
+ for (j=0; j<num_snapshots; j++) {
+ char *snap_name;
+
+ d_printf("%s\n", snapshots[j]);
+ snap_name = talloc_asprintf(talloc_tos(), "%s%s",
+ snapshots[j], name);
+ status = cli_qpathinfo3(cli, snap_name, &b_time, &a_time,
+ &m_time, &c_time, &size,
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "pathinfo(%s) failed: %s\n",
+ snap_name, nt_errstr(status));
+ TALLOC_FREE(snap_name);
+ continue;
+ }
+ tmp = unix_timespec_to_nt_time(b_time);
+ d_printf("create_time: %s\n", nt_time_string(talloc_tos(), tmp));
+ tmp = unix_timespec_to_nt_time(a_time);
+ d_printf("access_time: %s\n", nt_time_string(talloc_tos(), tmp));
+ tmp =unix_timespec_to_nt_time(m_time);
+ d_printf("write_time: %s\n", nt_time_string(talloc_tos(), tmp));
+ tmp = unix_timespec_to_nt_time(c_time);
+ d_printf("change_time: %s\n", nt_time_string(talloc_tos(), tmp));
+ d_printf("size: %d\n", (int)size);
+ }
+
+ TALLOC_FREE(snapshots);
+ cli_close(cli, fnum);
+
+ return 0;
+}
+
+/****************************************************************************
+ Show all info we can get
+****************************************************************************/
+
+static int cmd_allinfo(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *name;
+ char *buf;
+
+ name = talloc_strdup(ctx, client_get_cur_dir());
+ if (!name) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ d_printf("allinfo <file>\n");
+ return 1;
+ }
+ name = talloc_asprintf_append(name, "%s", buf);
+ if (!name) {
+ return 1;
+ }
+ name = client_clean_name(ctx, name);
+ if (name == NULL) {
+ return 1;
+ }
+ do_allinfo(name);
+
+ return 0;
+}
+
+/****************************************************************************
+ Put a single file.
+****************************************************************************/
+
+static int do_put(const char *rname, const char *lname, bool reput)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ uint16_t fnum;
+ FILE *f;
+ off_t start = 0;
+ int rc = 0;
+ struct timespec tp_start;
+ struct cli_state *targetcli;
+ char *targetname = NULL;
+ struct push_state state;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, rname, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to open %s: %s\n", rname, nt_errstr(status));
+ return 1;
+ }
+
+ clock_gettime_mono(&tp_start);
+
+ if (reput) {
+ status = cli_open(targetcli, targetname, O_RDWR|O_CREAT, DENY_NONE, &fnum);
+ if (NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_IS_OK(status = cli_qfileinfo_basic(
+ targetcli, fnum, NULL,
+ &start, NULL, NULL,
+ NULL, NULL, NULL))) {
+ d_printf("getattrib: %s\n", nt_errstr(status));
+ return 1;
+ }
+ }
+ } else {
+ status = cli_open(targetcli, targetname, O_RDWR|O_CREAT|O_TRUNC, DENY_NONE, &fnum);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s opening remote file %s\n", nt_errstr(status),
+ rname);
+ return 1;
+ }
+
+ /* allow files to be piped into smbclient
+ jdblair 24.jun.98
+
+ Note that in this case this function will exit(0) rather
+ than returning. */
+ if (!strcmp(lname, "-")) {
+ f = stdin;
+ /* size of file is not known */
+ } else {
+ f = fopen(lname, "r");
+ if (f && reput) {
+ if (fseek(f, start, SEEK_SET) == -1) {
+ d_printf("Error seeking local file\n");
+ fclose(f);
+ return 1;
+ }
+ }
+ }
+
+ if (!f) {
+ d_printf("Error opening local file %s\n",lname);
+ return 1;
+ }
+
+ DEBUG(1,("putting file %s as %s ",lname,
+ rname));
+
+ setvbuf(f, NULL, _IOFBF, io_bufsize);
+
+ state.f = f;
+ state.nread = 0;
+
+ status = cli_push(targetcli, fnum, 0, 0, io_bufsize, push_source,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_push returned %s\n", nt_errstr(status));
+ rc = 1;
+ }
+
+ status = cli_close(targetcli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s closing remote file %s\n", nt_errstr(status),
+ rname);
+ if (f != stdin) {
+ fclose(f);
+ }
+ return 1;
+ }
+
+ if (f != stdin) {
+ fclose(f);
+ }
+
+ {
+ struct timespec tp_end;
+ int this_time;
+
+ clock_gettime_mono(&tp_end);
+ this_time = nsec_time_diff(&tp_end,&tp_start)/1000000;
+ put_total_time_ms += this_time;
+ put_total_size += state.nread;
+
+ DEBUG(1,("(%3.1f kb/s) (average %3.1f kb/s)\n",
+ state.nread / (1.024*this_time + 1.0e-4),
+ put_total_size / (1.024*put_total_time_ms)));
+ }
+
+ if (f == stdin) {
+ cli_shutdown(cli);
+ exit(rc);
+ }
+
+ return rc;
+}
+
+/****************************************************************************
+ Put a file.
+****************************************************************************/
+
+static int cmd_put(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *lname;
+ char *rname;
+ char *buf;
+
+ rname = talloc_strdup(ctx, client_get_cur_dir());
+ if (!rname) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&lname,NULL)) {
+ d_printf("put <filename>\n");
+ return 1;
+ }
+
+ if (next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ rname = talloc_asprintf_append(rname, "%s", buf);
+ } else {
+ rname = talloc_asprintf_append(rname, "%s", lname);
+ }
+ if (!rname) {
+ return 1;
+ }
+
+ rname = client_clean_name(ctx, rname);
+ if (!rname) {
+ return 1;
+ }
+
+ {
+ SMB_STRUCT_STAT st;
+ /* allow '-' to represent stdin
+ jdblair, 24.jun.98 */
+ if (!file_exist_stat(lname, &st, false) &&
+ (strcmp(lname,"-"))) {
+ d_printf("%s does not exist\n",lname);
+ return 1;
+ }
+ }
+
+ return do_put(rname, lname, false);
+}
+
+/*************************************
+ File list structure.
+*************************************/
+
+static struct file_list {
+ struct file_list *prev, *next;
+ char *file_path;
+ bool isdir;
+} *file_list;
+
+/****************************************************************************
+ Free a file_list structure.
+****************************************************************************/
+
+static void free_file_list (struct file_list *l_head)
+{
+ struct file_list *list, *next;
+
+ for (list = l_head; list; list = next) {
+ next = list->next;
+ DLIST_REMOVE(l_head, list);
+ TALLOC_FREE(list);
+ }
+}
+
+/****************************************************************************
+ Seek in a directory/file list until you get something that doesn't start with
+ the specified name.
+****************************************************************************/
+
+static bool seek_list(struct file_list *list, char *name)
+{
+ while (list) {
+ trim_string(list->file_path,"./","\n");
+ if (strncmp(list->file_path, name, strlen(name)) != 0) {
+ return true;
+ }
+ list = list->next;
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ Set the file selection mask.
+****************************************************************************/
+
+static int cmd_select(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *new_fs = NULL;
+ next_token_talloc(ctx, &cmd_ptr,&new_fs,NULL)
+ ;
+ if (new_fs) {
+ client_set_fileselection(new_fs);
+ } else {
+ client_set_fileselection("");
+ }
+ return 0;
+}
+
+/****************************************************************************
+ Recursive file matching function act as find
+ match must be always set to true when calling this function
+****************************************************************************/
+
+static int file_find(TALLOC_CTX *ctx,
+ struct file_list **list,
+ const char *directory,
+ const char *expression,
+ bool match)
+{
+ DIR *dir;
+ struct file_list *entry;
+ struct stat statbuf;
+ int ret;
+ char *path;
+ bool isdir;
+ const char *dname;
+
+ dir = opendir(directory);
+ if (!dir)
+ return -1;
+
+ while ((dname = readdirname(dir))) {
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+
+ path = talloc_asprintf(ctx, "%s/%s", directory, dname);
+ if (path == NULL) {
+ continue;
+ }
+
+ isdir = false;
+ if (!match || !gen_fnmatch(expression, dname)) {
+ if (recurse) {
+ ret = stat(path, &statbuf);
+ if (ret == 0) {
+ if (S_ISDIR(statbuf.st_mode)) {
+ isdir = true;
+ ret = file_find(ctx,
+ list,
+ path,
+ expression,
+ false);
+ }
+ } else {
+ d_printf("file_find: cannot stat file %s\n", path);
+ }
+
+ if (ret == -1) {
+ TALLOC_FREE(path);
+ closedir(dir);
+ return -1;
+ }
+ }
+ entry = talloc_zero(ctx, struct file_list);
+ if (!entry) {
+ d_printf("Out of memory in file_find\n");
+ closedir(dir);
+ return -1;
+ }
+ entry->file_path = talloc_move(entry, &path);
+ entry->isdir = isdir;
+ DLIST_ADD(*list, entry);
+ } else {
+ TALLOC_FREE(path);
+ }
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+/****************************************************************************
+ mput some files.
+****************************************************************************/
+
+static int cmd_mput(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *p = NULL;
+
+ while (next_token_talloc(ctx, &cmd_ptr,&p,NULL)) {
+ int ret;
+ struct file_list *temp_list;
+ char *quest, *lname, *rname;
+
+ file_list = NULL;
+
+ ret = file_find(ctx, &file_list, ".", p, true);
+ if (ret) {
+ free_file_list(file_list);
+ continue;
+ }
+
+ quest = NULL;
+ lname = NULL;
+ rname = NULL;
+
+ for (temp_list = file_list; temp_list;
+ temp_list = temp_list->next) {
+
+ SAFE_FREE(lname);
+ if (asprintf(&lname, "%s/", temp_list->file_path) <= 0) {
+ continue;
+ }
+ trim_string(lname, "./", "/");
+
+ /* check if it's a directory */
+ if (temp_list->isdir) {
+ /* if (!recurse) continue; */
+
+ SAFE_FREE(quest);
+ if (asprintf(&quest, "Put directory %s? ", lname) < 0) {
+ break;
+ }
+ if (prompt && !yesno(quest)) { /* No */
+ /* Skip the directory */
+ lname[strlen(lname)-1] = '/';
+ if (!seek_list(temp_list, lname))
+ break;
+ } else { /* Yes */
+ SAFE_FREE(rname);
+ if(asprintf(&rname, "%s%s", client_get_cur_dir(), lname) < 0) {
+ break;
+ }
+ normalize_name(rname);
+ {
+ char *tmp_rname =
+ client_clean_name(ctx, rname);
+ if (tmp_rname == NULL) {
+ break;
+ }
+ SAFE_FREE(rname);
+ rname = smb_xstrdup(tmp_rname);
+ TALLOC_FREE(tmp_rname);
+ if (rname == NULL) {
+ break;
+ }
+ }
+ if (!NT_STATUS_IS_OK(cli_chkpath(cli, rname)) &&
+ !do_mkdir(rname)) {
+ DEBUG (0, ("Unable to make dir, skipping...\n"));
+ /* Skip the directory */
+ lname[strlen(lname)-1] = '/';
+ if (!seek_list(temp_list, lname)) {
+ break;
+ }
+ }
+ }
+ continue;
+ } else {
+ SAFE_FREE(quest);
+ if (asprintf(&quest,"Put file %s? ", lname) < 0) {
+ break;
+ }
+ if (prompt && !yesno(quest)) {
+ /* No */
+ continue;
+ }
+
+ /* Yes */
+ SAFE_FREE(rname);
+ if (asprintf(&rname, "%s%s", client_get_cur_dir(), lname) < 0) {
+ break;
+ }
+ }
+
+ normalize_name(rname);
+
+ {
+ char *tmp_rname = client_clean_name(ctx, rname);
+ if (tmp_rname == NULL) {
+ break;
+ }
+ SAFE_FREE(rname);
+ rname = smb_xstrdup(tmp_rname);
+ TALLOC_FREE(tmp_rname);
+ if (rname == NULL) {
+ break;
+ }
+ }
+ do_put(rname, lname, false);
+ }
+ free_file_list(file_list);
+ SAFE_FREE(quest);
+ SAFE_FREE(lname);
+ SAFE_FREE(rname);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Cancel a print job.
+****************************************************************************/
+
+static int do_cancel(int job)
+{
+ NTSTATUS status = cli_printjob_del(cli, job);
+
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("Job %d cancelled\n",job);
+ return 0;
+ } else {
+ d_printf("Error cancelling job %d : %s\n",
+ job, nt_errstr(status));
+ return 1;
+ }
+}
+
+/****************************************************************************
+ Cancel a print job.
+****************************************************************************/
+
+static int cmd_cancel(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+ int job;
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &buf,NULL)) {
+ d_printf("cancel <jobid> ...\n");
+ return 1;
+ }
+ do {
+ job = atoi(buf);
+ do_cancel(job);
+ } while (next_token_talloc(ctx, &cmd_ptr,&buf,NULL));
+
+ return 0;
+}
+
+/****************************************************************************
+ Print a file.
+****************************************************************************/
+
+static int cmd_print(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *lname = NULL;
+ char *rname = NULL;
+ char *p = NULL;
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &lname,NULL)) {
+ d_printf("print <filename>\n");
+ return 1;
+ }
+
+ rname = talloc_strdup(ctx, lname);
+ if (!rname) {
+ return 1;
+ }
+ p = strrchr_m(rname,'/');
+ if (p) {
+ rname = talloc_asprintf(ctx,
+ "%s-%d",
+ p+1,
+ (int)getpid());
+ }
+ if (strequal(lname,"-")) {
+ rname = talloc_asprintf(ctx,
+ "stdin-%d",
+ (int)getpid());
+ }
+ if (!rname) {
+ return 1;
+ }
+
+ return do_put(rname, lname, false);
+}
+
+/****************************************************************************
+ Show a print queue entry.
+****************************************************************************/
+
+static void queue_fn(struct print_job_info *p)
+{
+ d_printf("%-6d %-9d %s\n", (int)p->id, (int)p->size, p->name);
+}
+
+/****************************************************************************
+ Show a print queue.
+****************************************************************************/
+
+static int cmd_queue(void)
+{
+ cli_print_queue(cli, queue_fn);
+ return 0;
+}
+
+/****************************************************************************
+ Delete some files.
+****************************************************************************/
+
+static NTSTATUS do_del(struct cli_state *cli_state, struct file_info *finfo,
+ const char *dir)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ struct cli_state *targetcli = NULL;
+ char *targetname = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ mask = talloc_asprintf(ctx,
+ "%s%c%s",
+ dir,
+ CLI_DIRSEP_CHAR,
+ finfo->name);
+ if (!mask) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ TALLOC_FREE(mask);
+ return NT_STATUS_OK;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = cli_unlink(targetcli, targetname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+out:
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s deleting remote file %s\n",
+ nt_errstr(status), mask);
+ }
+ TALLOC_FREE(mask);
+ return status;
+}
+
+/****************************************************************************
+ Delete some files.
+****************************************************************************/
+
+static int cmd_del(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ uint32_t attribute = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+
+ if (recurse) {
+ attribute |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+
+ mask = talloc_strdup(ctx, client_get_cur_dir());
+ if (!mask) {
+ return 1;
+ }
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("del <filename>\n");
+ return 1;
+ }
+ mask = talloc_asprintf_append(mask, "%s", buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ status = do_list(mask,attribute,do_del,false,false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+ return 0;
+}
+
+/****************************************************************************
+ Delete some files.
+****************************************************************************/
+
+static NTSTATUS delete_remote_files_list(struct cli_state *cli_state,
+ struct file_list *flist)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct file_list *deltree_list_iter = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ TALLOC_CTX *ctx = talloc_tos();
+
+ for (deltree_list_iter = flist;
+ deltree_list_iter != NULL;
+ deltree_list_iter = deltree_list_iter->next) {
+ status = cli_resolve_path(ctx,
+ "",
+ creds,
+ cli_state,
+ deltree_list_iter->file_path,
+ &targetcli,
+ &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("delete_remote_files %s: %s\n",
+ deltree_list_iter->file_path,
+ nt_errstr(status));
+ return status;
+ }
+ if (CLI_DIRSEP_CHAR == '/') {
+ /* POSIX. */
+ status = cli_posix_unlink(targetcli,
+ targetname);
+ } else if (deltree_list_iter->isdir) {
+ status = cli_rmdir(targetcli,
+ targetname);
+ } else {
+ status = cli_unlink(targetcli,
+ targetname,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s deleting remote %s %s\n",
+ nt_errstr(status),
+ deltree_list_iter->isdir ?
+ "directory" : "file",
+ deltree_list_iter->file_path);
+ return status;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Save a list of files to delete.
+****************************************************************************/
+
+static struct file_list *deltree_list_head;
+
+static NTSTATUS do_deltree_list(struct cli_state *cli_state,
+ struct file_info *finfo,
+ const char *dir)
+{
+ struct file_list **file_list_head_pp = &deltree_list_head;
+ struct file_list *dt = NULL;
+
+ if (!do_this_one(finfo)) {
+ return NT_STATUS_OK;
+ }
+
+ /* skip if this is . or .. */
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
+ return NT_STATUS_OK;
+ }
+
+ dt = talloc_zero(NULL, struct file_list);
+ if (dt == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* create absolute filename for cli_ntcreate() */
+ dt->file_path = talloc_asprintf(dt,
+ "%s%s%s",
+ dir,
+ CLI_DIRSEP_STR,
+ finfo->name);
+ if (dt->file_path == NULL) {
+ TALLOC_FREE(dt);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ dt->isdir = true;
+ }
+
+ DLIST_ADD(*file_list_head_pp, dt);
+ return NT_STATUS_OK;
+}
+
+static int cmd_deltree(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct file_list *deltree_list_norecurse = NULL;
+ struct file_list *deltree_list_iter = NULL;
+ uint32_t attribute = FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_DIRECTORY;
+ bool ok;
+ char *mask = talloc_strdup(ctx, client_get_cur_dir());
+ if (mask == NULL) {
+ return 1;
+ }
+ ok = next_token_talloc(ctx, &cmd_ptr, &buf, NULL);
+ if (!ok) {
+ d_printf("deltree <filename>\n");
+ return 1;
+ }
+ mask = talloc_asprintf_append(mask, "%s", buf);
+ if (mask == NULL) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ deltree_list_head = NULL;
+
+ /*
+ * Get the list of directories to
+ * delete (in case mask has a wildcard).
+ */
+ status = do_list(mask, attribute, do_deltree_list, false, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ deltree_list_norecurse = deltree_list_head;
+ deltree_list_head = NULL;
+
+ for (deltree_list_iter = deltree_list_norecurse;
+ deltree_list_iter != NULL;
+ deltree_list_iter = deltree_list_iter->next) {
+
+ if (deltree_list_iter->isdir == false) {
+ char *targetname = NULL;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ status = cli_resolve_path(ctx,
+ "",
+ creds,
+ cli,
+ deltree_list_iter->file_path,
+ &targetcli,
+ &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ /* Just a regular file. */
+ if (CLI_DIRSEP_CHAR == '/') {
+ /* POSIX. */
+ status = cli_posix_unlink(targetcli,
+ targetname);
+ } else {
+ status = cli_unlink(targetcli,
+ targetname,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ continue;
+ }
+
+ /*
+ * Get the list of files or directories to
+ * delete in depth order.
+ */
+ status = do_list(deltree_list_iter->file_path,
+ attribute,
+ do_deltree_list,
+ true,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ status = delete_remote_files_list(cli, deltree_list_head);
+ free_file_list(deltree_list_head);
+ deltree_list_head = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+ }
+
+ free_file_list(deltree_list_norecurse);
+ free_file_list(deltree_list_head);
+ return 0;
+
+ err:
+
+ free_file_list(deltree_list_norecurse);
+ free_file_list(deltree_list_head);
+ deltree_list_head = NULL;
+ return 1;
+}
+
+
+/****************************************************************************
+ Wildcard delete some files.
+****************************************************************************/
+
+static int cmd_wdel(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ uint32_t attribute;
+ struct cli_state *targetcli;
+ char *targetname = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("wdel 0x<attrib> <wcard>\n");
+ return 1;
+ }
+
+ attribute = (uint32_t)strtol(buf, (char **)NULL, 16);
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("wdel 0x<attrib> <wcard>\n");
+ return 1;
+ }
+
+ mask = talloc_asprintf(ctx, "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cmd_wdel %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_unlink(targetcli, targetname, attribute);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s deleting remote files %s\n", nt_errstr(status),
+ targetname);
+ }
+ return 0;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static int cmd_open(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ uint16_t fnum = (uint16_t)-1;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("open <filename>\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!mask) {
+ return 1;
+ }
+
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_ntcreate(targetcli, targetname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN,
+ 0x0, 0x0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = cli_ntcreate(targetcli, targetname, 0,
+ FILE_READ_DATA, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN,
+ 0x0, 0x0, &fnum, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("open file %s: for read/write fnum %d\n", targetname, fnum);
+ } else {
+ d_printf("Failed to open file %s. %s\n",
+ targetname, nt_errstr(status));
+ }
+ } else {
+ d_printf("open file %s: for read/write fnum %d\n", targetname, fnum);
+ }
+ return 0;
+}
+
+static int cmd_posix_encrypt(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ char *domain = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ struct cli_credentials *creds = NULL;
+ struct cli_credentials *lcreds = NULL;
+
+ if (next_token_talloc(ctx, &cmd_ptr, &domain, NULL)) {
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &user, NULL)) {
+ d_printf("posix_encrypt domain user password\n");
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &password, NULL)) {
+ d_printf("posix_encrypt domain user password\n");
+ return 1;
+ }
+
+ lcreds = cli_session_creds_init(ctx,
+ user,
+ domain,
+ NULL, /* realm */
+ password,
+ false, /* use_kerberos */
+ false, /* fallback_after_kerberos */
+ false, /* use_ccache */
+ false); /* password_is_nt_hash */
+ if (lcreds == NULL) {
+ d_printf("cli_session_creds_init() failed.\n");
+ return -1;
+ }
+ creds = lcreds;
+ } else {
+ bool auth_requested = false;
+
+ creds = samba_cmdline_get_creds();
+
+ auth_requested = cli_credentials_authentication_requested(creds);
+ if (!auth_requested) {
+ d_printf("posix_encrypt domain user password\n");
+ return 1;
+ }
+ }
+
+ status = cli_smb1_setup_encryption(cli, creds);
+ /* gensec currently references the creds so we can't free them here */
+ talloc_unlink(ctx, lcreds);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("posix_encrypt failed with error %s\n", nt_errstr(status));
+ } else {
+ bool ok;
+
+ d_printf("encryption on\n");
+ ok = cli_credentials_set_smb_encryption(creds,
+ SMB_ENCRYPTION_REQUIRED,
+ CRED_SPECIFIED);
+ SMB_ASSERT(ok);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static int cmd_posix_open(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ mode_t mode;
+ uint16_t fnum;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("posix_open <filename> 0<mode>\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("posix_open <filename> 0<mode>\n");
+ return 1;
+ }
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"posix_open\" command can be used.\n");
+ return 1;
+ }
+ mode = (mode_t)strtol(buf, (char **)NULL, 8);
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("posix_open %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_posix_open(targetcli, targetname, O_CREAT|O_RDWR, mode,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = cli_posix_open(targetcli, targetname,
+ O_CREAT|O_RDONLY, mode, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to open file %s. %s\n", targetname,
+ nt_errstr(status));
+ } else {
+ d_printf("posix_open file %s: for readonly fnum %d\n",
+ targetname, fnum);
+ }
+ } else {
+ d_printf("posix_open file %s: for read/write fnum %d\n",
+ targetname, fnum);
+ }
+
+ return 0;
+}
+
+static int cmd_posix_mkdir(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ mode_t mode;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("posix_mkdir <filename> 0<mode>\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("posix_mkdir <filename> 0<mode>\n");
+ return 1;
+ }
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"posix_mkdir\" command can be used.\n");
+ return 1;
+ }
+ mode = (mode_t)strtol(buf, (char **)NULL, 8);
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("posix_mkdir %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_posix_mkdir(targetcli, targetname, mode);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to open file %s. %s\n",
+ targetname, nt_errstr(status));
+ } else {
+ d_printf("posix_mkdir created directory %s\n", targetname);
+ }
+ return 0;
+}
+
+static int cmd_posix_unlink(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("posix_unlink <filename>\n");
+ return 1;
+ }
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"posix_unlink\" command can be used.\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("posix_unlink %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_posix_unlink(targetcli, targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to unlink file %s. %s\n",
+ targetname, nt_errstr(status));
+ } else {
+ d_printf("posix_unlink deleted file %s\n", targetname);
+ }
+
+ return 0;
+}
+
+static int cmd_posix_rmdir(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("posix_rmdir <filename>\n");
+ return 1;
+ }
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"posix_rmdir\" command can be used.\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("posix_rmdir %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_posix_rmdir(targetcli, targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to unlink directory %s. %s\n",
+ targetname, nt_errstr(status));
+ } else {
+ d_printf("posix_rmdir deleted directory %s\n", targetname);
+ }
+
+ return 0;
+}
+
+static int cmd_mkfifo(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ mode_t mode;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ d_printf("mkfifo <filename> 0<mode>\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx, "%s%s", client_get_cur_dir(), buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ d_printf("mkfifo <filename> 0<mode>\n");
+ return 1;
+ }
+
+ mode = (mode_t)strtol(buf, (char **)NULL, 8);
+ if ((mode & ~(S_IRWXU | S_IRWXG | S_IRWXO)) != 0) {
+ d_printf("mode %o can only contain permission bits\n", mode);
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx,
+ "",
+ creds,
+ cli,
+ mask,
+ &targetcli,
+ &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("mkfifo %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_mknod(targetcli, targetname, mode | S_IFIFO, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to open file %s. %s\n",
+ targetname,
+ nt_errstr(status));
+ } else {
+ d_printf("mkfifo created %s\n", targetname);
+ }
+ return 0;
+}
+
+static int cmd_close(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+ int fnum;
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("close <fnum>\n");
+ return 1;
+ }
+
+ fnum = atoi(buf);
+ /* We really should use the targetcli here.... */
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("close %d: %s\n", fnum, nt_errstr(status));
+ return 1;
+ }
+ return 0;
+}
+
+static int cmd_posix(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ char *caps;
+ NTSTATUS status;
+
+ if (!smbXcli_conn_have_posix(cli->conn)) {
+ d_printf("Server doesn't support UNIX CIFS extensions.\n");
+ return 1;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB3_11) {
+ cli->smb2.client_smb311_posix = true;
+ return 0;
+ }
+
+ status = cli_unix_extensions_version(cli, &major, &minor, &caplow,
+ &caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Can't get UNIX CIFS extensions version from "
+ "server: %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("Server supports CIFS extensions %u.%u\n", (unsigned int)major, (unsigned int)minor);
+
+ caps = talloc_strdup(ctx, "");
+ if (caplow & CIFS_UNIX_FCNTL_LOCKS_CAP) {
+ talloc_asprintf_addbuf(&caps, "locks ");
+ }
+ if (caplow & CIFS_UNIX_POSIX_ACLS_CAP) {
+ talloc_asprintf_addbuf(&caps, "acls ");
+ }
+ if (caplow & CIFS_UNIX_XATTTR_CAP) {
+ talloc_asprintf_addbuf(&caps, "eas ");
+ }
+ if (caplow & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
+ talloc_asprintf_addbuf(&caps, "pathnames ");
+ }
+ if (caplow & CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP) {
+ talloc_asprintf_addbuf(&caps, "posix_path_operations ");
+ }
+ if (caplow & CIFS_UNIX_LARGE_READ_CAP) {
+ talloc_asprintf_addbuf(&caps, "large_read ");
+ }
+ if (caplow & CIFS_UNIX_LARGE_WRITE_CAP) {
+ talloc_asprintf_addbuf(&caps, "large_write ");
+ }
+ if (caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP) {
+ talloc_asprintf_addbuf(&caps, "posix_encrypt ");
+ }
+ if (caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP) {
+ talloc_asprintf_addbuf(&caps, "mandatory_posix_encrypt ");
+ }
+
+ if (caps == NULL) {
+ return 1;
+ }
+
+ if (*caps && caps[strlen(caps)-1] == ' ') {
+ caps[strlen(caps)-1] = '\0';
+ }
+
+ d_printf("Server supports CIFS capabilities %s\n", caps);
+
+ status = cli_set_unix_extensions_capabilities(cli, major, minor,
+ caplow, caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Can't set UNIX CIFS extensions capabilities. %s.\n",
+ nt_errstr(status));
+ return 1;
+ }
+
+ if (caplow & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
+ CLI_DIRSEP_CHAR = '/';
+ *CLI_DIRSEP_STR = '/';
+ client_set_cur_dir(CLI_DIRSEP_STR);
+ }
+
+ return 0;
+}
+
+static int cmd_lock(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+ uint64_t start, len;
+ enum brl_type lock_type;
+ int fnum;
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("lock <fnum> [r|w] <hex-start> <hex-len>\n");
+ return 1;
+ }
+ fnum = atoi(buf);
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("lock <fnum> [r|w] <hex-start> <hex-len>\n");
+ return 1;
+ }
+
+ if (*buf == 'r' || *buf == 'R') {
+ lock_type = READ_LOCK;
+ } else if (*buf == 'w' || *buf == 'W') {
+ lock_type = WRITE_LOCK;
+ } else {
+ d_printf("lock <fnum> [r|w] <hex-start> <hex-len>\n");
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("lock <fnum> [r|w] <hex-start> <hex-len>\n");
+ return 1;
+ }
+
+ start = (uint64_t)strtol(buf, (char **)NULL, 16);
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("lock <fnum> [r|w] <hex-start> <hex-len>\n");
+ return 1;
+ }
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"lock\" command can be used.\n");
+ return 1;
+ }
+
+ len = (uint64_t)strtol(buf, (char **)NULL, 16);
+
+ status = cli_posix_lock(cli, fnum, start, len, true, lock_type);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("lock failed %d: %s\n", fnum, nt_errstr(status));
+ }
+
+ return 0;
+}
+
+static int cmd_unlock(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf = NULL;
+ uint64_t start, len;
+ int fnum;
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("unlock <fnum> <hex-start> <hex-len>\n");
+ return 1;
+ }
+ fnum = atoi(buf);
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("unlock <fnum> <hex-start> <hex-len>\n");
+ return 1;
+ }
+
+ start = (uint64_t)strtol(buf, (char **)NULL, 16);
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("unlock <fnum> <hex-start> <hex-len>\n");
+ return 1;
+ }
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"unlock\" command can be used.\n");
+ return 1;
+ }
+
+ len = (uint64_t)strtol(buf, (char **)NULL, 16);
+
+ status = cli_posix_unlock(cli, fnum, start, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("unlock failed %d: %s\n", fnum, nt_errstr(status));
+ }
+
+ return 0;
+}
+
+static int cmd_posix_whoami(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ uint64_t uid = 0;
+ uint64_t gid = 0;
+ uint32_t num_gids = 0;
+ uint32_t num_sids = 0;
+ uint64_t *gids = NULL;
+ struct dom_sid *sids = NULL;
+ bool guest = false;
+ uint32_t i;
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"posix_whoami\" command can be used.\n");
+ return 1;
+ }
+
+ status = cli_posix_whoami(cli,
+ ctx,
+ &uid,
+ &gid,
+ &num_gids,
+ &gids,
+ &num_sids,
+ &sids,
+ &guest);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("posix_whoami failed with error %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("GUEST:%s\n", guest ? "True" : "False");
+ d_printf("UID:%" PRIu64 "\n", uid);
+ d_printf("GID:%" PRIu64 "\n", gid);
+ d_printf("NUM_GIDS:%" PRIu32 "\n", num_gids);
+ for (i = 0; i < num_gids; i++) {
+ d_printf("GIDS[%" PRIu32 "]:%" PRIu64 "\n", i, gids[i]);
+ }
+ d_printf("NUM_SIDS:%" PRIu32 "\n", num_sids);
+ for (i = 0; i < num_sids; i++) {
+ struct dom_sid_buf buf;
+ d_printf("SIDS[%" PRIu32 "]:%s\n",
+ i,
+ dom_sid_str_buf(&sids[i], &buf));
+ }
+ return 0;
+}
+
+
+/****************************************************************************
+ Remove a directory.
+****************************************************************************/
+
+static int cmd_rmdir(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *mask = NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("rmdir <dirname>\n");
+ return 1;
+ }
+ mask = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!mask) {
+ return 1;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, mask, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("rmdir %s: %s\n", mask, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_rmdir(targetcli, targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s removing remote directory file %s\n",
+ nt_errstr(status), mask);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ UNIX hardlink.
+****************************************************************************/
+
+static int cmd_link(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *oldname = NULL;
+ char *newname = NULL;
+ char *buf = NULL;
+ char *buf2 = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf2,NULL)) {
+ d_printf("link <oldname> <newname>\n");
+ return 1;
+ }
+ oldname = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!oldname) {
+ return 1;
+ }
+ oldname = client_clean_name(ctx, oldname);
+ if (oldname == NULL) {
+ return 1;
+ }
+ newname = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf2);
+ if (!newname) {
+ return 1;
+ }
+ newname = client_clean_name(ctx, newname);
+ if (newname == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, oldname, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("link %s: %s\n", oldname, nt_errstr(status));
+ return 1;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(targetcli)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"link\" command can be used.\n");
+ return 1;
+ }
+
+ status = cli_posix_hardlink(targetcli, targetname, newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s linking files (%s -> %s)\n",
+ nt_errstr(status), newname, oldname);
+ return 1;
+ }
+ return 0;
+}
+
+/****************************************************************************
+ UNIX readlink.
+****************************************************************************/
+
+static int cmd_readlink(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *name= NULL;
+ char *buf = NULL;
+ char *targetname = NULL;
+ char *linkname = NULL;
+ char *printname = NULL;
+ uint32_t flags;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("readlink <name>\n");
+ return 1;
+ }
+ name = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!name) {
+ return 1;
+ }
+ name = client_clean_name(ctx, name);
+ if (name == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, name, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("readlink %s: %s\n", name, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_readlink(
+ cli, name, talloc_tos(), &linkname, &printname, &flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s readlink on file %s\n",
+ nt_errstr(status), name);
+ return 1;
+ }
+
+ d_printf("%s -> %s\n", name, linkname);
+
+ TALLOC_FREE(linkname);
+
+ return 0;
+}
+
+
+/****************************************************************************
+ UNIX symlink.
+****************************************************************************/
+
+static int cmd_symlink(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *link_target = NULL;
+ char *newname = NULL;
+ char *buf = NULL;
+ char *buf2 = NULL;
+ struct cli_state *newcli;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf2,NULL)) {
+ d_printf("symlink <link_target> <newname>\n");
+ return 1;
+ }
+ /* Oldname (link target) must be an untouched blob. */
+ link_target = buf;
+
+ if (SERVER_HAS_UNIX_CIFS(cli)) {
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"symlink\" command can be used.\n");
+ return 1;
+ }
+ newname = talloc_asprintf(ctx, "%s%s", client_get_cur_dir(),
+ buf2);
+ if (!newname) {
+ return 1;
+ }
+ newname = client_clean_name(ctx, newname);
+ if (newname == NULL) {
+ return 1;
+ }
+ /* New name must be present in share namespace. */
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, newname,
+ &newcli, &newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("link %s: %s\n", newname,
+ nt_errstr(status));
+ return 1;
+ }
+ status = cli_posix_symlink(newcli, link_target, newname);
+ } else {
+ status = cli_symlink(
+ cli, link_target, buf2,
+ buf2[0] == '\\' ? 0 : SYMLINK_FLAG_RELATIVE);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s symlinking files (%s -> %s)\n",
+ nt_errstr(status), newname, link_target);
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ UNIX chmod.
+****************************************************************************/
+
+static int cmd_chmod(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src = NULL;
+ char *buf = NULL;
+ char *buf2 = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ mode_t mode;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf2,NULL)) {
+ d_printf("chmod mode file\n");
+ return 1;
+ }
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf2);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ mode = (mode_t)strtol(buf, NULL, 8);
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("chmod %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(targetcli)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"chmod\" command can be used.\n");
+ return 1;
+ }
+
+ status = cli_posix_chmod(targetcli, targetname, mode);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s chmod file %s 0%o\n",
+ nt_errstr(status), src, (unsigned int)mode);
+ return 1;
+ }
+
+ return 0;
+}
+
+static const char *filetype_to_str(mode_t mode)
+{
+ if (S_ISREG(mode)) {
+ return "regular file";
+ } else if (S_ISDIR(mode)) {
+ return "directory";
+ } else
+#ifdef S_ISCHR
+ if (S_ISCHR(mode)) {
+ return "character device";
+ } else
+#endif
+#ifdef S_ISBLK
+ if (S_ISBLK(mode)) {
+ return "block device";
+ } else
+#endif
+#ifdef S_ISFIFO
+ if (S_ISFIFO(mode)) {
+ return "fifo";
+ } else
+#endif
+#ifdef S_ISLNK
+ if (S_ISLNK(mode)) {
+ return "symbolic link";
+ } else
+#endif
+#ifdef S_ISSOCK
+ if (S_ISSOCK(mode)) {
+ return "socket";
+ } else
+#endif
+ return "";
+}
+
+static char rwx_to_str(mode_t m, mode_t bt, char ret)
+{
+ if (m & bt) {
+ return ret;
+ } else {
+ return '-';
+ }
+}
+
+static char *unix_mode_to_str(char *s, mode_t m)
+{
+ char *p = s;
+ const char *str = filetype_to_str(m);
+
+ switch(str[0]) {
+ case 'd':
+ *p++ = 'd';
+ break;
+ case 'c':
+ *p++ = 'c';
+ break;
+ case 'b':
+ *p++ = 'b';
+ break;
+ case 'f':
+ *p++ = 'p';
+ break;
+ case 's':
+ *p++ = str[1] == 'y' ? 'l' : 's';
+ break;
+ case 'r':
+ default:
+ *p++ = '-';
+ break;
+ }
+ *p++ = rwx_to_str(m, S_IRUSR, 'r');
+ *p++ = rwx_to_str(m, S_IWUSR, 'w');
+ *p++ = rwx_to_str(m, S_IXUSR, 'x');
+ *p++ = rwx_to_str(m, S_IRGRP, 'r');
+ *p++ = rwx_to_str(m, S_IWGRP, 'w');
+ *p++ = rwx_to_str(m, S_IXGRP, 'x');
+ *p++ = rwx_to_str(m, S_IROTH, 'r');
+ *p++ = rwx_to_str(m, S_IWOTH, 'w');
+ *p++ = rwx_to_str(m, S_IXOTH, 'x');
+ *p++ = '\0';
+ return s;
+}
+
+/****************************************************************************
+ Utility function for UNIX getfacl.
+****************************************************************************/
+
+static char *perms_to_string(fstring permstr, unsigned char perms)
+{
+ fstrcpy(permstr, "---");
+ if (perms & SMB_POSIX_ACL_READ) {
+ permstr[0] = 'r';
+ }
+ if (perms & SMB_POSIX_ACL_WRITE) {
+ permstr[1] = 'w';
+ }
+ if (perms & SMB_POSIX_ACL_EXECUTE) {
+ permstr[2] = 'x';
+ }
+ return permstr;
+}
+
+/****************************************************************************
+ UNIX getfacl.
+****************************************************************************/
+
+static int cmd_getfacl(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src = NULL;
+ char *name = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ char *retbuf = NULL;
+ size_t rb_size = 0;
+ SMB_STRUCT_STAT sbuf;
+ size_t num_file_acls = 0;
+ size_t num_dir_acls = 0;
+ size_t expected_buflen;
+ uint16_t i;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&name,NULL)) {
+ d_printf("getfacl filename\n");
+ return 1;
+ }
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ name);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("stat %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(targetcli)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"getfacl\" command can be used.\n");
+ return 1;
+ }
+
+ status = cli_unix_extensions_version(targetcli, &major, &minor,
+ &caplow, &caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Can't get UNIX CIFS version from server: %s.\n",
+ nt_errstr(status));
+ return 1;
+ }
+
+ if (!(caplow & CIFS_UNIX_POSIX_ACLS_CAP)) {
+ d_printf("This server supports UNIX extensions "
+ "but doesn't support POSIX ACLs.\n");
+ return 1;
+ }
+
+ status = cli_posix_stat(targetcli, targetname, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s getfacl doing a stat on file %s\n",
+ nt_errstr(status), src);
+ return 1;
+ }
+
+ status = cli_posix_getacl(targetcli, targetname, ctx, &rb_size, &retbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s getfacl file %s\n",
+ nt_errstr(status), src);
+ return 1;
+ }
+
+ /* ToDo : Print out the ACL values. */
+ if (rb_size < 6 || SVAL(retbuf,0) != SMB_POSIX_ACL_VERSION) {
+ d_printf("getfacl file %s, unknown POSIX acl version %u.\n",
+ src, (unsigned int)CVAL(retbuf,0) );
+ return 1;
+ }
+
+ num_file_acls = SVAL(retbuf,2);
+ num_dir_acls = SVAL(retbuf,4);
+
+ /*
+ * No overflow check, num_*_acls comes from a 16-bit value,
+ * and we expect expected_buflen (size_t) to be of at least 32
+ * bit.
+ */
+ expected_buflen = SMB_POSIX_ACL_HEADER_SIZE +
+ SMB_POSIX_ACL_ENTRY_SIZE*(num_file_acls+num_dir_acls);
+
+ if (rb_size != expected_buflen) {
+ d_printf("getfacl file %s, incorrect POSIX acl buffer size "
+ "(should be %zu, was %zu).\n",
+ src,
+ expected_buflen,
+ rb_size);
+ return 1;
+ }
+
+ d_printf("# file: %s\n", src);
+ d_printf("# owner: %u\n# group: %u\n", (unsigned int)sbuf.st_ex_uid, (unsigned int)sbuf.st_ex_gid);
+
+ if (num_file_acls == 0 && num_dir_acls == 0) {
+ d_printf("No acls found.\n");
+ }
+
+ for (i = 0; i < num_file_acls; i++) {
+ uint32_t uorg;
+ fstring permstring;
+ unsigned char tagtype = CVAL(retbuf, SMB_POSIX_ACL_HEADER_SIZE+(i*SMB_POSIX_ACL_ENTRY_SIZE));
+ unsigned char perms = CVAL(retbuf, SMB_POSIX_ACL_HEADER_SIZE+(i*SMB_POSIX_ACL_ENTRY_SIZE)+1);
+
+ switch(tagtype) {
+ case SMB_POSIX_ACL_USER_OBJ:
+ d_printf("user::");
+ break;
+ case SMB_POSIX_ACL_USER:
+ uorg = IVAL(retbuf,SMB_POSIX_ACL_HEADER_SIZE+(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ d_printf("user:%u:", uorg);
+ break;
+ case SMB_POSIX_ACL_GROUP_OBJ:
+ d_printf("group::");
+ break;
+ case SMB_POSIX_ACL_GROUP:
+ uorg = IVAL(retbuf,SMB_POSIX_ACL_HEADER_SIZE+(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ d_printf("group:%u:", uorg);
+ break;
+ case SMB_POSIX_ACL_MASK:
+ d_printf("mask::");
+ break;
+ case SMB_POSIX_ACL_OTHER:
+ d_printf("other::");
+ break;
+ default:
+ d_printf("getfacl file %s, incorrect POSIX acl tagtype (%u).\n",
+ src, (unsigned int)tagtype );
+ SAFE_FREE(retbuf);
+ return 1;
+ }
+
+ d_printf("%s\n", perms_to_string(permstring, perms));
+ }
+
+ for (i = 0; i < num_dir_acls; i++) {
+ uint32_t uorg;
+ fstring permstring;
+ unsigned char tagtype = CVAL(retbuf, SMB_POSIX_ACL_HEADER_SIZE+((i+num_file_acls)*SMB_POSIX_ACL_ENTRY_SIZE));
+ unsigned char perms = CVAL(retbuf, SMB_POSIX_ACL_HEADER_SIZE+((i+num_file_acls)*SMB_POSIX_ACL_ENTRY_SIZE)+1);
+
+ switch(tagtype) {
+ case SMB_POSIX_ACL_USER_OBJ:
+ d_printf("default:user::");
+ break;
+ case SMB_POSIX_ACL_USER:
+ uorg = IVAL(retbuf,SMB_POSIX_ACL_HEADER_SIZE+((i+num_file_acls)*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ d_printf("default:user:%u:", uorg);
+ break;
+ case SMB_POSIX_ACL_GROUP_OBJ:
+ d_printf("default:group::");
+ break;
+ case SMB_POSIX_ACL_GROUP:
+ uorg = IVAL(retbuf,SMB_POSIX_ACL_HEADER_SIZE+((i+num_file_acls)*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ d_printf("default:group:%u:", uorg);
+ break;
+ case SMB_POSIX_ACL_MASK:
+ d_printf("default:mask::");
+ break;
+ case SMB_POSIX_ACL_OTHER:
+ d_printf("default:other::");
+ break;
+ default:
+ d_printf("getfacl file %s, incorrect POSIX acl tagtype (%u).\n",
+ src, (unsigned int)tagtype );
+ SAFE_FREE(retbuf);
+ return 1;
+ }
+
+ d_printf("%s\n", perms_to_string(permstring, perms));
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Get the EA list of a file
+****************************************************************************/
+
+static int cmd_geteas(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src = NULL;
+ char *name = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ NTSTATUS status;
+ size_t i, num_eas;
+ struct ea_struct *eas;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&name,NULL)) {
+ d_printf("geteas filename\n");
+ return 1;
+ }
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ name);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("stat %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_get_ea_list_path(targetcli, targetname, talloc_tos(),
+ &num_eas, &eas);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_get_ea_list_path: %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ for (i=0; i<num_eas; i++) {
+ d_printf("%s (%d) =\n", eas[i].name, (int)eas[i].flags);
+ dump_data_file(eas[i].value.data, eas[i].value.length, false,
+ stdout);
+ d_printf("\n");
+ }
+
+ TALLOC_FREE(eas);
+
+ return 0;
+}
+
+/****************************************************************************
+ Set an EA of a file
+****************************************************************************/
+
+static int cmd_setea(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src = NULL;
+ char *name = NULL;
+ char *eaname = NULL;
+ char *eavalue = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &name, NULL)
+ || !next_token_talloc(ctx, &cmd_ptr, &eaname, NULL)) {
+ d_printf("setea filename eaname value\n");
+ return 1;
+ }
+ if (!next_token_talloc(ctx, &cmd_ptr, &eavalue, NULL)) {
+ eavalue = talloc_strdup(ctx, "");
+ }
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ name);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("stat %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_set_ea_path(targetcli, targetname, eaname, eavalue,
+ strlen(eavalue));
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("set_ea %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ UNIX stat.
+****************************************************************************/
+
+static int cmd_stat(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src = NULL;
+ char *name = NULL;
+ char *targetname = NULL;
+ struct cli_state *targetcli;
+ fstring mode_str;
+ SMB_STRUCT_STAT sbuf;
+ struct tm *lt;
+ time_t tmp_time;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&name,NULL)) {
+ d_printf("stat file\n");
+ return 1;
+ }
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ name);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("stat %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(targetcli)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"stat\" command can be used.\n");
+ return 1;
+ }
+
+ status = cli_posix_stat(targetcli, targetname, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s stat file %s\n",
+ nt_errstr(status), src);
+ return 1;
+ }
+
+ /* Print out the stat values. */
+ d_printf("File: %s\n", src);
+ d_printf("Size: %-12.0f\tBlocks: %u\t%s\n",
+ (double)sbuf.st_ex_size,
+ (unsigned int)sbuf.st_ex_blocks,
+ filetype_to_str(sbuf.st_ex_mode));
+
+#if defined(S_ISCHR) && defined(S_ISBLK)
+ if (S_ISCHR(sbuf.st_ex_mode) || S_ISBLK(sbuf.st_ex_mode)) {
+ d_printf("Inode: %.0f\tLinks: %u\tDevice type: %u,%u\n",
+ (double)sbuf.st_ex_ino,
+ (unsigned int)sbuf.st_ex_nlink,
+ unix_dev_major(sbuf.st_ex_rdev),
+ unix_dev_minor(sbuf.st_ex_rdev));
+ } else
+#endif
+ d_printf("Inode: %.0f\tLinks: %u\n",
+ (double)sbuf.st_ex_ino,
+ (unsigned int)sbuf.st_ex_nlink);
+
+ d_printf("Access: (0%03o/%s)\tUid: %u\tGid: %u\n",
+ ((int)sbuf.st_ex_mode & 0777),
+ unix_mode_to_str(mode_str, sbuf.st_ex_mode),
+ (unsigned int)sbuf.st_ex_uid,
+ (unsigned int)sbuf.st_ex_gid);
+
+ tmp_time = convert_timespec_to_time_t(sbuf.st_ex_atime);
+ lt = localtime(&tmp_time);
+ if (lt) {
+ strftime(mode_str, sizeof(mode_str), "%Y-%m-%d %T %z", lt);
+ } else {
+ fstrcpy(mode_str, "unknown");
+ }
+ d_printf("Access: %s\n", mode_str);
+
+ tmp_time = convert_timespec_to_time_t(sbuf.st_ex_mtime);
+ lt = localtime(&tmp_time);
+ if (lt) {
+ strftime(mode_str, sizeof(mode_str), "%Y-%m-%d %T %z", lt);
+ } else {
+ fstrcpy(mode_str, "unknown");
+ }
+ d_printf("Modify: %s\n", mode_str);
+
+ tmp_time = convert_timespec_to_time_t(sbuf.st_ex_ctime);
+ lt = localtime(&tmp_time);
+ if (lt) {
+ strftime(mode_str, sizeof(mode_str), "%Y-%m-%d %T %z", lt);
+ } else {
+ fstrcpy(mode_str, "unknown");
+ }
+ d_printf("Change: %s\n", mode_str);
+
+ return 0;
+}
+
+
+/****************************************************************************
+ UNIX chown.
+****************************************************************************/
+
+static int cmd_chown(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src = NULL;
+ uid_t uid;
+ gid_t gid;
+ char *buf, *buf2, *buf3;
+ struct cli_state *targetcli;
+ char *targetname = NULL;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf2,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf3,NULL)) {
+ d_printf("chown uid gid file\n");
+ return 1;
+ }
+
+ uid = (uid_t)atoi(buf);
+ gid = (gid_t)atoi(buf2);
+
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf3);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("chown %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(targetcli)) {
+ d_printf("Server doesn't support UNIX CIFS calls.\n");
+ return 1;
+ }
+
+ if (CLI_DIRSEP_CHAR != '/') {
+ d_printf("Command \"posix\" must be issued before "
+ "the \"chown\" command can be used.\n");
+ return 1;
+ }
+
+ status = cli_posix_chown(targetcli, targetname, uid, gid);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s chown file %s uid=%d, gid=%d\n",
+ nt_errstr(status), src, (int)uid, (int)gid);
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Rename some file.
+****************************************************************************/
+
+static int cmd_rename(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src, *dest;
+ char *buf, *buf2;
+ struct cli_state *targetcli;
+ char *targetsrc;
+ char *targetdest;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+ bool replace = false;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf2,NULL)) {
+ d_printf("rename <src> <dest> [-f]\n");
+ return 1;
+ }
+
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ dest = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf2);
+ if (!dest) {
+ return 1;
+ }
+ dest = client_clean_name(ctx, dest);
+ if (dest == NULL) {
+ return 1;
+ }
+
+ if (next_token_talloc(ctx, &cmd_ptr, &buf, NULL) &&
+ strcsequal(buf, "-f")) {
+ replace = true;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetsrc);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("rename %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, dest, &targetcli, &targetdest);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("rename %s: %s\n", dest, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_rename(targetcli, targetsrc, targetdest, replace);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s renaming files %s -> %s \n",
+ nt_errstr(status),
+ targetsrc,
+ targetdest);
+ return 1;
+ }
+
+ return 0;
+}
+
+struct scopy_timing {
+ struct timespec tp_start;
+};
+
+static int scopy_status(off_t written, void *priv)
+{
+ struct timespec tp_end;
+ unsigned int scopy_total_time_ms;
+ struct scopy_timing *st = priv;
+
+ clock_gettime_mono(&tp_end);
+ scopy_total_time_ms = nsec_time_diff(&tp_end,&st->tp_start)/1000000;
+
+ DEBUG(5,("Copied %jd bytes at an average %3.1f kb/s\n",
+ (intmax_t)written, written / (1.024*scopy_total_time_ms)));
+
+ return true;
+}
+
+/****************************************************************************
+ Server-Side copy some file.
+****************************************************************************/
+
+static int cmd_scopy(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src, *dest;
+ char *buf, *buf2;
+ struct cli_state *targetcli;
+ char *targetsrc;
+ char *targetdest;
+ uint32_t DesiredAccess, ShareAccess, CreateDisposition, CreateOptions;
+ struct smb_create_returns cr;
+ uint16_t destfnum = (uint16_t)-1;
+ uint16_t srcfnum = (uint16_t)-1;
+ off_t written = 0;
+ struct scopy_timing st;
+ int rc = 0;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf2,NULL)) {
+ d_printf("scopy <src> <dest>\n");
+ return 1;
+ }
+
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ dest = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf2);
+ if (!dest) {
+ return 1;
+ }
+ dest = client_clean_name(ctx, dest);
+ if (dest == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetsrc);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("scopy %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, dest, &targetcli, &targetdest);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("scopy %s: %s\n", dest, nt_errstr(status));
+ return 1;
+ }
+
+
+ DesiredAccess = (FILE_READ_DATA|FILE_READ_EA|FILE_READ_ATTRIBUTES|
+ READ_CONTROL_ACCESS|SYNCHRONIZE_ACCESS);
+ ShareAccess = FILE_SHARE_READ|FILE_SHARE_DELETE;
+ CreateDisposition = FILE_OPEN;
+ CreateOptions = (FILE_SEQUENTIAL_ONLY|FILE_NON_DIRECTORY_FILE|
+ FILE_OPEN_REPARSE_POINT);
+ status = cli_ntcreate(targetcli, targetsrc, 0, DesiredAccess, 0,
+ ShareAccess, CreateDisposition, CreateOptions, 0x0,
+ &srcfnum, &cr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to open file %s. %s\n",
+ targetsrc, nt_errstr(status));
+ return 1;
+ }
+
+ DesiredAccess = (FILE_READ_DATA|FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_READ_EA|
+ FILE_WRITE_EA|FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES|
+ DELETE_ACCESS|READ_CONTROL_ACCESS|WRITE_DAC_ACCESS|SYNCHRONIZE_ACCESS);
+ ShareAccess = FILE_SHARE_NONE;
+ CreateDisposition = FILE_CREATE;
+ CreateOptions = FILE_SEQUENTIAL_ONLY|FILE_NON_DIRECTORY_FILE;
+ status = cli_ntcreate(targetcli, targetdest, 0, DesiredAccess,
+ FILE_ATTRIBUTE_ARCHIVE, ShareAccess, CreateDisposition,
+ CreateOptions, 0x0, &destfnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to create file %s. %s\n",
+ targetdest, nt_errstr(status));
+ cli_close(targetcli, srcfnum);
+ return 1;
+ }
+
+ clock_gettime_mono(&st.tp_start);
+ status = cli_splice(targetcli, targetcli, srcfnum, destfnum,
+ cr.end_of_file, 0, 0, &written, scopy_status, &st);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s copying file %s -> %s \n",
+ nt_errstr(status),
+ targetsrc,
+ targetdest);
+ rc = 1;
+ }
+
+ status = cli_close(targetcli, srcfnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error %s closing remote source file\n", nt_errstr(status));
+ rc = 1;
+ }
+ status = cli_close(targetcli, destfnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error %s closing remote dest file\n", nt_errstr(status));
+ rc = 1;
+ }
+
+ return rc;
+}
+
+/****************************************************************************
+ Print the volume name.
+****************************************************************************/
+
+static int cmd_volume(void)
+{
+ char *volname;
+ uint32_t serial_num;
+ time_t create_date;
+ NTSTATUS status;
+
+ status = cli_get_fs_volume_info(cli, talloc_tos(),
+ &volname, &serial_num,
+ &create_date);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error %s getting volume info\n", nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("Volume: |%s| serial number 0x%x\n",
+ volname, (unsigned int)serial_num);
+ return 0;
+}
+
+/****************************************************************************
+ Hard link files using the NT call.
+****************************************************************************/
+
+static int cmd_hardlink(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *src, *dest;
+ char *buf, *buf2;
+ struct cli_state *targetcli;
+ char *targetname;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL) ||
+ !next_token_talloc(ctx, &cmd_ptr,&buf2,NULL)) {
+ d_printf("hardlink <src> <dest>\n");
+ return 1;
+ }
+
+ src = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (!src) {
+ return 1;
+ }
+ src = client_clean_name(ctx, src);
+ if (src == NULL) {
+ return 1;
+ }
+
+ dest = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf2);
+ if (!dest) {
+ return 1;
+ }
+ dest = client_clean_name(ctx, dest);
+ if (dest == NULL) {
+ return 1;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, src, &targetcli, &targetname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("hardlink %s: %s\n", src, nt_errstr(status));
+ return 1;
+ }
+
+ status = cli_hardlink(targetcli, targetname, dest);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s doing an NT hard link of files\n",
+ nt_errstr(status));
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Toggle the prompt flag.
+****************************************************************************/
+
+static int cmd_prompt(void)
+{
+ prompt = !prompt;
+ DEBUG(2,("prompting is now %s\n",prompt?"on":"off"));
+ return 1;
+}
+
+/****************************************************************************
+ Set the newer than time.
+****************************************************************************/
+
+static int cmd_newer(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf;
+ bool ok;
+ SMB_STRUCT_STAT sbuf;
+
+ ok = next_token_talloc(ctx, &cmd_ptr,&buf,NULL);
+ if (ok && (sys_stat(buf, &sbuf, false) == 0)) {
+ newer_than = convert_timespec_to_time_t(sbuf.st_ex_mtime);
+ DEBUG(1,("Getting files newer than %s",
+ time_to_asc(newer_than)));
+ } else {
+ newer_than = 0;
+ }
+
+ if (ok && newer_than == 0) {
+ d_printf("Error setting newer-than time\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Watch directory changes
+****************************************************************************/
+
+static int cmd_notify(void)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *name, *buf;
+ NTSTATUS status;
+ uint16_t fnum;
+
+ name = talloc_strdup(talloc_tos(), client_get_cur_dir());
+ if (name == NULL) {
+ goto fail;
+ }
+ if (!next_token_talloc(talloc_tos(), &cmd_ptr, &buf, NULL)) {
+ goto usage;
+ }
+ name = talloc_asprintf_append(name, "%s", buf);
+ if (name == NULL) {
+ goto fail;
+ }
+ name = client_clean_name(talloc_tos(), name);
+ if (name == NULL) {
+ return 1;
+ }
+ status = cli_ntcreate(
+ cli, name, 0, FILE_READ_DATA, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not open file: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ while (1) {
+ uint32_t i;
+ uint32_t num_changes = 0;
+ struct notify_change *changes = NULL;
+
+ status = cli_notify(cli, fnum, 1000, FILE_NOTIFY_CHANGE_ALL,
+ true,
+ talloc_tos(), &num_changes, &changes);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOTIFY_ENUM_DIR)) {
+ printf("NOTIFY_ENUM_DIR\n");
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("notify returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ for (i=0; i<num_changes; i++) {
+ printf("%4.4x %s\n", changes[i].action,
+ changes[i].name);
+ }
+ TALLOC_FREE(changes);
+ }
+usage:
+ d_printf("notify <dir name>\n");
+fail:
+ TALLOC_FREE(frame);
+ return 1;
+}
+
+/****************************************************************************
+ Set the archive level.
+****************************************************************************/
+
+static int cmd_archive(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf;
+
+ if (next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ archive_level = atoi(buf);
+ } else {
+ d_printf("Archive level is %d\n",archive_level);
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Toggle the backup_intent state.
+****************************************************************************/
+
+static int cmd_backup(void)
+{
+ backup_intent = !backup_intent;
+ cli_set_backup_intent(cli, backup_intent);
+ DEBUG(2,("backup intent is now %s\n",backup_intent?"on":"off"));
+ return 1;
+}
+
+/****************************************************************************
+ Toggle the lowercaseflag.
+****************************************************************************/
+
+static int cmd_lowercase(void)
+{
+ lowercase = !lowercase;
+ DEBUG(2,("filename lowercasing is now %s\n",lowercase?"on":"off"));
+ return 0;
+}
+
+/****************************************************************************
+ Toggle the case sensitive flag.
+****************************************************************************/
+
+static int cmd_setcase(void)
+{
+ bool orig_case_sensitive = cli_set_case_sensitive(cli, false);
+
+ cli_set_case_sensitive(cli, !orig_case_sensitive);
+ DEBUG(2,("filename case sensitivity is now %s\n",!orig_case_sensitive ?
+ "on":"off"));
+ return 0;
+}
+
+/****************************************************************************
+ Toggle the showacls flag.
+****************************************************************************/
+
+static int cmd_showacls(void)
+{
+ showacls = !showacls;
+ DEBUG(2,("showacls is now %s\n",showacls?"on":"off"));
+ return 0;
+}
+
+
+/****************************************************************************
+ Toggle the recurse flag.
+****************************************************************************/
+
+static int cmd_recurse(void)
+{
+ recurse = !recurse;
+ DEBUG(2,("directory recursion is now %s\n",recurse?"on":"off"));
+ return 0;
+}
+
+/****************************************************************************
+ Toggle the translate flag.
+****************************************************************************/
+
+static int cmd_translate(void)
+{
+ translation = !translation;
+ DEBUG(2,("CR/LF<->LF and print text translation now %s\n",
+ translation?"on":"off"));
+ return 0;
+}
+
+/****************************************************************************
+ Do the lcd command.
+ ****************************************************************************/
+
+static int cmd_lcd(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf;
+ char *d;
+
+ if (next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ if (chdir(buf) == -1) {
+ d_printf("chdir to %s failed (%s)\n",
+ buf, strerror(errno));
+ }
+ }
+ d = sys_getwd();
+ if (!d) {
+ return 1;
+ }
+ DEBUG(2,("the local directory is now %s\n",d));
+ SAFE_FREE(d);
+ return 0;
+}
+
+/****************************************************************************
+ Get a file restarting at end of local file.
+ ****************************************************************************/
+
+static int cmd_reget(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *local_name = NULL;
+ char *remote_name = NULL;
+ char *fname = NULL;
+ char *p = NULL;
+
+ remote_name = talloc_strdup(ctx, client_get_cur_dir());
+ if (!remote_name) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &fname, NULL)) {
+ d_printf("reget <filename>\n");
+ return 1;
+ }
+ remote_name = talloc_asprintf_append(remote_name, "%s", fname);
+ if (!remote_name) {
+ return 1;
+ }
+ remote_name = client_clean_name(ctx,remote_name);
+ if (!remote_name) {
+ return 1;
+ }
+
+ local_name = fname;
+ next_token_talloc(ctx, &cmd_ptr, &p, NULL);
+ if (p) {
+ local_name = p;
+ }
+
+ return do_get(remote_name, local_name, true);
+}
+
+/****************************************************************************
+ Put a file restarting at end of local file.
+ ****************************************************************************/
+
+static int cmd_reput(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *local_name = NULL;
+ char *remote_name = NULL;
+ char *buf;
+ SMB_STRUCT_STAT st;
+
+ remote_name = talloc_strdup(ctx, client_get_cur_dir());
+ if (!remote_name) {
+ return 1;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &local_name, NULL)) {
+ d_printf("reput <filename>\n");
+ return 1;
+ }
+
+ if (!file_exist_stat(local_name, &st, false)) {
+ d_printf("%s does not exist\n", local_name);
+ return 1;
+ }
+
+ if (next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ remote_name = talloc_asprintf_append(remote_name,
+ "%s", buf);
+ } else {
+ remote_name = talloc_asprintf_append(remote_name,
+ "%s", local_name);
+ }
+ if (!remote_name) {
+ return 1;
+ }
+
+ remote_name = client_clean_name(ctx, remote_name);
+ if (!remote_name) {
+ return 1;
+ }
+
+ return do_put(remote_name, local_name, true);
+}
+
+/****************************************************************************
+ List a share name.
+ ****************************************************************************/
+
+static void browse_fn(const char *name, uint32_t m,
+ const char *comment, void *state)
+{
+ const char *typestr = "";
+
+ switch (m & 7) {
+ case STYPE_DISKTREE:
+ typestr = "Disk";
+ break;
+ case STYPE_PRINTQ:
+ typestr = "Printer";
+ break;
+ case STYPE_DEVICE:
+ typestr = "Device";
+ break;
+ case STYPE_IPC:
+ typestr = "IPC";
+ break;
+ }
+ /* FIXME: If the remote machine returns non-ascii characters
+ in any of these fields, they can corrupt the output. We
+ should remove them. */
+ if (!grepable) {
+ d_printf("\t%-15s %-10.10s%s\n",
+ name,typestr,comment);
+ } else {
+ d_printf ("%s|%s|%s\n",typestr,name,comment);
+ }
+}
+
+static bool browse_host_rpc(bool sort)
+{
+ NTSTATUS status;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ WERROR werr;
+ struct srvsvc_NetShareInfoCtr info_ctr;
+ struct srvsvc_NetShareCtr1 ctr1;
+ uint32_t resume_handle = 0;
+ uint32_t total_entries = 0;
+ uint32_t i;
+ struct dcerpc_binding_handle *b;
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_srvsvc,
+ &pipe_hnd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Could not connect to srvsvc pipe: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(info_ctr);
+ ZERO_STRUCT(ctr1);
+
+ info_ctr.level = 1;
+ info_ctr.ctr.ctr1 = &ctr1;
+
+ status = dcerpc_srvsvc_NetShareEnumAll(b, frame,
+ pipe_hnd->desthost,
+ &info_ctr,
+ 0xffffffff,
+ &total_entries,
+ &resume_handle,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status) || !W_ERROR_IS_OK(werr)) {
+ TALLOC_FREE(pipe_hnd);
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ for (i=0; i < info_ctr.ctr.ctr1->count; i++) {
+ struct srvsvc_NetShareInfo1 info = info_ctr.ctr.ctr1->array[i];
+ browse_fn(info.name, info.type, info.comment, NULL);
+ }
+
+ TALLOC_FREE(pipe_hnd);
+ TALLOC_FREE(frame);
+ return true;
+}
+
+/****************************************************************************
+ Try and browse available connections on a host.
+****************************************************************************/
+
+static bool browse_host(bool sort)
+{
+ int ret;
+
+ if (!grepable) {
+ d_printf("\n\tSharename Type Comment\n");
+ d_printf("\t--------- ---- -------\n");
+ }
+
+ if (browse_host_rpc(sort)) {
+ return true;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) > PROTOCOL_NT1) {
+ return false;
+ }
+
+ ret = cli_RNetShareEnum(cli, browse_fn, NULL);
+ if (ret == -1) {
+ NTSTATUS status = cli_nt_error(cli);
+ d_printf("Error returning browse list: %s\n",
+ nt_errstr(status));
+ }
+
+ return (ret != -1);
+}
+
+/****************************************************************************
+ List a server name.
+****************************************************************************/
+
+static void server_fn(const char *name, uint32_t m,
+ const char *comment, void *state)
+{
+
+ if (!grepable){
+ d_printf("\t%-16s %s\n", name, comment);
+ } else {
+ d_printf("%s|%s|%s\n",(char *)state, name, comment);
+ }
+}
+
+/****************************************************************************
+ Try and browse available connections on a host.
+****************************************************************************/
+
+static bool list_servers(const char *wk_grp)
+{
+ fstring state;
+
+ if (!cli->server_domain)
+ return false;
+
+ if (!grepable) {
+ d_printf("\n\tServer Comment\n");
+ d_printf("\t--------- -------\n");
+ };
+ fstrcpy( state, "Server" );
+ cli_NetServerEnum(cli, cli->server_domain, SV_TYPE_ALL, server_fn,
+ state);
+
+ if (!grepable) {
+ d_printf("\n\tWorkgroup Master\n");
+ d_printf("\t--------- -------\n");
+ };
+
+ fstrcpy( state, "Workgroup" );
+ cli_NetServerEnum(cli, cli->server_domain, SV_TYPE_DOMAIN_ENUM,
+ server_fn, state);
+ return true;
+}
+
+/****************************************************************************
+ Print or set current VUID
+****************************************************************************/
+
+static int cmd_vuid(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ d_printf("Current VUID is %d\n",
+ cli_state_get_uid(cli));
+ return 0;
+ }
+
+ cli_state_set_uid(cli, atoi(buf));
+ return 0;
+}
+
+/****************************************************************************
+ Setup a new VUID, by issuing a session setup
+****************************************************************************/
+
+static int cmd_logon(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *l_username, *l_password;
+ struct cli_credentials *creds = NULL;
+ NTSTATUS nt_status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&l_username,NULL)) {
+ d_printf("logon <username> [<password>]\n");
+ return 0;
+ }
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&l_password,NULL)) {
+ char pwd[256] = {0};
+ int rc;
+
+ rc = samba_getpass("Password: ", pwd, sizeof(pwd), false, false);
+ if (rc == 0) {
+ l_password = talloc_strdup(ctx, pwd);
+ }
+ }
+ if (!l_password) {
+ return 1;
+ }
+
+ creds = cli_session_creds_init(ctx,
+ l_username,
+ lp_workgroup(),
+ NULL, /* realm */
+ l_password,
+ false, /* use_kerberos */
+ false, /* fallback_after_kerberos */
+ false, /* use_ccache */
+ false); /* password_is_nt_hash */
+ if (creds == NULL) {
+ d_printf("cli_session_creds_init() failed.\n");
+ return -1;
+ }
+ nt_status = cli_session_setup_creds(cli, creds);
+ TALLOC_FREE(creds);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_printf("session setup failed: %s\n", nt_errstr(nt_status));
+ return -1;
+ }
+
+ d_printf("Current VUID is %d\n", cli_state_get_uid(cli));
+ return 0;
+}
+
+/**
+ * close the session
+ */
+
+static int cmd_logoff(void)
+{
+ NTSTATUS status;
+
+ status = cli_ulogoff(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("logoff failed: %s\n", nt_errstr(status));
+ return -1;
+ }
+
+ d_printf("logoff successful\n");
+ return 0;
+}
+
+
+/**
+ * tree connect (connect to a share)
+ */
+
+static int cmd_tcon(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *sharename;
+ NTSTATUS status;
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &sharename, NULL)) {
+ d_printf("tcon <sharename>\n");
+ return 0;
+ }
+
+ if (!sharename) {
+ return 1;
+ }
+
+ status = cli_tree_connect(cli, sharename, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("tcon failed: %s\n", nt_errstr(status));
+ return -1;
+ }
+
+ d_printf("tcon to %s successful, tid: %u\n", sharename,
+ cli_state_get_tid(cli));
+
+ talloc_free(sharename);
+
+ return 0;
+}
+
+/**
+ * tree disconnect (disconnect from a share)
+ */
+
+static int cmd_tdis(void)
+{
+ NTSTATUS status;
+
+ status = cli_tdis(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("tdis failed: %s\n", nt_errstr(status));
+ return -1;
+ }
+
+ d_printf("tdis successful\n");
+ return 0;
+}
+
+
+/**
+ * get or set tid
+ */
+
+static int cmd_tid(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *tid_str;
+
+ if (!next_token_talloc(ctx, &cmd_ptr, &tid_str, NULL)) {
+ if (cli_state_has_tcon(cli)) {
+ d_printf("current tid is %d\n", cli_state_get_tid(cli));
+ } else {
+ d_printf("no tcon currently\n");
+ }
+ } else {
+ uint32_t tid = atoi(tid_str);
+ if (!cli_state_has_tcon(cli)) {
+ d_printf("no tcon currently\n");
+ }
+ cli_state_set_tid(cli, tid);
+ }
+
+ return 0;
+}
+
+
+/****************************************************************************
+ list active connections
+****************************************************************************/
+
+static int cmd_list_connect(void)
+{
+ cli_cm_display(cli);
+ return 0;
+}
+
+/****************************************************************************
+ display the current active client connection
+****************************************************************************/
+
+static int cmd_show_connect( void )
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ struct cli_state *targetcli;
+ char *targetpath;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli,
+ client_get_cur_dir(), &targetcli,
+ &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("showconnect %s: %s\n", cur_dir, nt_errstr(status));
+ return 1;
+ }
+
+ d_printf("//%s/%s\n", smbXcli_conn_remote_name(targetcli->conn), targetcli->share);
+ return 0;
+}
+
+/**
+ * cmd_utimes - interactive command to set the four times
+ *
+ * Read a filename and four times from the client command line and update
+ * the file times. A value of -1 for a time means don't change.
+ */
+static int cmd_utimes(void)
+{
+ char *buf;
+ char *fname = NULL;
+ struct timespec times[4] = {{0}};
+ struct timeval_buf tbuf[4];
+ int time_count = 0;
+ int err = 0;
+ bool ok;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ NTSTATUS status;
+
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ ok = next_token_talloc(ctx, &cmd_ptr, &buf, NULL);
+ if (!ok) {
+ d_printf("utimes <filename> <create-time> <access-time> "
+ "<write-time> <change-time>\n");
+ d_printf("Dates should be in YY:MM:DD-HH:MM:SS format "
+ "or -1 for no change\n");
+ err = 1;
+ goto out;
+ }
+
+ fname = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (fname == NULL) {
+ err = 1;
+ goto out;
+ }
+ fname = client_clean_name(ctx, fname);
+ if (fname == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ while (next_token_talloc(ctx, &cmd_ptr, &buf, NULL) &&
+ time_count < 4) {
+ const char *s = buf;
+ struct tm tm = {0,};
+ time_t t;
+ char *ret;
+
+ if (strlen(s) == 2 && strcmp(s, "-1") == 0) {
+ times[time_count] = make_omit_timespec();
+ time_count++;
+ continue;
+ }
+
+ ret = strptime(s, "%y:%m:%d-%H:%M:%S", &tm);
+
+ if (ret == NULL) {
+ ret = strptime(s, "%Y:%m:%d-%H:%M:%S", &tm);
+ }
+
+ /* We could not match all the chars, so print error */
+ if (ret == NULL || *ret != 0) {
+ d_printf("Invalid date format: %s\n", s);
+ d_printf("utimes <filename> <create-time> "
+ "<access-time> <write-time> <change-time>\n");
+ d_printf("Dates should be in [YY]YY:MM:DD-HH:MM:SS "
+ "format or -1 for no change\n");
+ err = 1;
+ goto out;
+ }
+
+ /* Convert tm to a time_t */
+ t = mktime(&tm);
+ times[time_count] = (struct timespec){.tv_sec = t};
+ time_count++;
+ }
+
+ if (time_count < 4) {
+ d_printf("Insufficient dates: %d\n", time_count);
+ d_printf("utimes <filename> <create-time> <access-time> "
+ "<write-time> <change-time>\n");
+ d_printf("Dates should be in YY:MM:DD-HH:MM:SS format "
+ "or -1 for no change\n");
+ err = 1;
+ goto out;
+ }
+
+ DEBUG(10, ("times\nCreate: %sAccess: %s Write: %sChange: %s\n",
+ timespec_string_buf(&times[0], false, &tbuf[0]),
+ timespec_string_buf(&times[1], false, &tbuf[1]),
+ timespec_string_buf(&times[2], false, &tbuf[2]),
+ timespec_string_buf(&times[3], false, &tbuf[3])));
+
+ status = cli_setpathinfo_ext(
+ cli, fname, times[0], times[1], times[2], times[3],
+ (uint32_t)-1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_setpathinfo_ext failed: %s\n",
+ nt_errstr(status));
+ err = 1;
+ goto out;
+ }
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * set_remote_attr - set DOS attributes of a remote file
+ * @filename: path to the file name
+ * @new_attr: attribute bit mask to use
+ * @mode: one of ATTR_SET or ATTR_UNSET
+ *
+ * Update the file attributes with the one provided.
+ */
+int set_remote_attr(const char *filename, uint32_t new_attr, int mode)
+{
+ extern struct cli_state *cli;
+ uint32_t old_attr;
+ NTSTATUS status;
+
+ status = cli_getatr(cli, filename, &old_attr, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_getatr failed: %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ if (mode == ATTR_SET) {
+ new_attr |= old_attr;
+ } else {
+ new_attr = old_attr & ~new_attr;
+ }
+
+ status = cli_setatr(cli, filename, new_attr, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_setatr failed: %s\n", nt_errstr(status));
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * cmd_setmode - interactive command to set DOS attributes
+ *
+ * Read a filename and mode from the client command line and update
+ * the file DOS attributes.
+ */
+int cmd_setmode(void)
+{
+ char *buf;
+ char *fname = NULL;
+ uint32_t attr[2] = {0};
+ int mode = ATTR_SET;
+ int err = 0;
+ bool ok;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ ok = next_token_talloc(ctx, &cmd_ptr, &buf, NULL);
+ if (!ok) {
+ d_printf("setmode <filename> <[+|-]rsha>\n");
+ err = 1;
+ goto out;
+ }
+
+ fname = talloc_asprintf(ctx,
+ "%s%s",
+ client_get_cur_dir(),
+ buf);
+ if (fname == NULL) {
+ err = 1;
+ goto out;
+ }
+ fname = client_clean_name(ctx, fname);
+ if (fname == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ while (next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ const char *s = buf;
+
+ while (*s) {
+ switch (*s++) {
+ case '+':
+ mode = ATTR_SET;
+ break;
+ case '-':
+ mode = ATTR_UNSET;
+ break;
+ case 'r':
+ attr[mode] |= FILE_ATTRIBUTE_READONLY;
+ break;
+ case 'h':
+ attr[mode] |= FILE_ATTRIBUTE_HIDDEN;
+ break;
+ case 's':
+ attr[mode] |= FILE_ATTRIBUTE_SYSTEM;
+ break;
+ case 'a':
+ attr[mode] |= FILE_ATTRIBUTE_ARCHIVE;
+ break;
+ default:
+ d_printf("setmode <filename> <perm=[+|-]rsha>\n");
+ err = 1;
+ goto out;
+ }
+ }
+ }
+
+ if (attr[ATTR_SET] == 0 && attr[ATTR_UNSET] == 0) {
+ d_printf("setmode <filename> <[+|-]rsha>\n");
+ err = 1;
+ goto out;
+ }
+
+ DEBUG(2, ("perm set %d %d\n", attr[ATTR_SET], attr[ATTR_UNSET]));
+
+ /* ignore return value: server might not store DOS attributes */
+ set_remote_attr(fname, attr[ATTR_SET], ATTR_SET);
+ set_remote_attr(fname, attr[ATTR_UNSET], ATTR_UNSET);
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+/****************************************************************************
+ iosize command
+***************************************************************************/
+
+int cmd_iosize(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf;
+ int iosize;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ bool smb_encrypt =
+ (cli_credentials_get_smb_encryption(creds) ==
+ SMB_ENCRYPTION_REQUIRED);
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
+ if (!smb_encrypt) {
+ d_printf("iosize <n> or iosize 0x<n>. "
+ "Minimum is 0 (default), "
+ "max is 16776960 (0xFFFF00)\n");
+ } else {
+ d_printf("iosize <n> or iosize 0x<n>. "
+ "(Encrypted connection) ,"
+ "Minimum is 0 (default), "
+ "max is 130048 (0x1FC00)\n");
+ }
+ } else {
+ d_printf("iosize <n> or iosize 0x<n>.\n");
+ }
+ return 1;
+ }
+
+ iosize = strtol(buf,NULL,0);
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
+ if (smb_encrypt && (iosize < 0 || iosize > 0xFC00)) {
+ d_printf("iosize out of range for encrypted "
+ "connection (min = 0 (default), "
+ "max = 130048 (0x1FC00)\n");
+ return 1;
+ } else if (!smb_encrypt && (iosize < 0 || iosize > 0xFFFF00)) {
+ d_printf("iosize out of range (min = 0 (default), "
+ "max = 16776960 (0xFFFF00)\n");
+ return 1;
+ }
+ }
+
+ io_bufsize = iosize;
+ d_printf("iosize is now %d\n", io_bufsize);
+ return 0;
+}
+
+/****************************************************************************
+ timeout command
+***************************************************************************/
+
+static int cmd_timeout(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *buf;
+
+ if (!next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ unsigned int old_timeout = cli_set_timeout(cli, 0);
+ cli_set_timeout(cli, old_timeout);
+ d_printf("timeout <n> (per-operation timeout "
+ "in seconds - currently %u).\n",
+ old_timeout/1000);
+ return 1;
+ }
+
+ io_timeout = strtol(buf,NULL,0);
+ cli_set_timeout(cli, io_timeout*1000);
+ d_printf("io_timeout per operation is now %d\n", io_timeout);
+ return 0;
+}
+
+
+/****************************************************************************
+history
+****************************************************************************/
+static int cmd_history(void)
+{
+#if defined(HAVE_LIBREADLINE) && defined(HAVE_HISTORY_LIST)
+ HIST_ENTRY **hlist;
+ int i;
+
+ hlist = history_list();
+
+ for (i = 0; hlist && hlist[i]; i++) {
+ DEBUG(0, ("%d: %s\n", i, hlist[i]->line));
+ }
+#else
+ DEBUG(0,("no history without readline support\n"));
+#endif
+
+ return 0;
+}
+
+/* Some constants for completing filename arguments */
+
+#define COMPL_NONE 0 /* No completions */
+#define COMPL_REMOTE 1 /* Complete remote filename */
+#define COMPL_LOCAL 2 /* Complete local filename */
+
+/* This defines the commands supported by this client.
+ * NOTE: The "!" must be the last one in the list because it's fn pointer
+ * field is NULL, and NULL in that field is used in process_tok()
+ * (below) to indicate the end of the list. crh
+ */
+static struct {
+ const char *name;
+ int (*fn)(void);
+ const char *description;
+ char compl_args[2]; /* Completion argument info */
+} commands[] = {
+ {"?",cmd_help,"[command] give help on a command",{COMPL_NONE,COMPL_NONE}},
+ {"allinfo",cmd_allinfo,"<file> show all available info",
+ {COMPL_REMOTE,COMPL_NONE}},
+ {"altname",cmd_altname,"<file> show alt name",{COMPL_REMOTE,COMPL_NONE}},
+ {"archive",cmd_archive,"<level>\n0=ignore archive bit\n1=only get archive files\n2=only get archive files and reset archive bit\n3=get all files and reset archive bit",{COMPL_NONE,COMPL_NONE}},
+ {"backup",cmd_backup,"toggle backup intent state",{COMPL_NONE,COMPL_NONE}},
+ {"blocksize",cmd_block,"blocksize <number> (default 20)",{COMPL_NONE,COMPL_NONE}},
+ {"cancel",cmd_cancel,"<jobid> cancel a print queue entry",{COMPL_NONE,COMPL_NONE}},
+ {"case_sensitive",cmd_setcase,"toggle the case sensitive flag to server",{COMPL_NONE,COMPL_NONE}},
+ {"cd",cmd_cd,"[directory] change/report the remote directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"chmod",cmd_chmod,"<src> <mode> chmod a file using UNIX permission",{COMPL_REMOTE,COMPL_NONE}},
+ {"chown",cmd_chown,"<src> <uid> <gid> chown a file using UNIX uids and gids",{COMPL_REMOTE,COMPL_NONE}},
+ {"close",cmd_close,"<fid> close a file given a fid",{COMPL_REMOTE,COMPL_NONE}},
+ {"del",cmd_del,"<mask> delete all matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"deltree",cmd_deltree,"<mask> recursively delete all matching files and directories",{COMPL_REMOTE,COMPL_NONE}},
+ {"dir",cmd_dir,"<mask> list the contents of the current directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"du",cmd_du,"<mask> computes the total size of the current directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"echo",cmd_echo,"ping the server",{COMPL_NONE,COMPL_NONE}},
+ {"exit",cmd_quit,"logoff the server",{COMPL_NONE,COMPL_NONE}},
+ {"get",cmd_get,"<remote name> [local name] get a file",{COMPL_REMOTE,COMPL_LOCAL}},
+ {"getfacl",cmd_getfacl,"<file name> get the POSIX ACL on a file (UNIX extensions only)",{COMPL_REMOTE,COMPL_NONE}},
+ {"geteas", cmd_geteas, "<file name> get the EA list of a file",
+ {COMPL_REMOTE, COMPL_NONE}},
+ {"hardlink",cmd_hardlink,"<src> <dest> create a Windows hard link",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"help",cmd_help,"[command] give help on a command",{COMPL_NONE,COMPL_NONE}},
+ {"history",cmd_history,"displays the command history",{COMPL_NONE,COMPL_NONE}},
+ {"iosize",cmd_iosize,"iosize <number> (default 64512)",{COMPL_NONE,COMPL_NONE}},
+ {"lcd",cmd_lcd,"[directory] change/report the local current working directory",{COMPL_LOCAL,COMPL_NONE}},
+ {"link",cmd_link,"<oldname> <newname> create a UNIX hard link",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"lock",cmd_lock,"lock <fnum> [r|w] <hex-start> <hex-len> : set a POSIX lock",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"lowercase",cmd_lowercase,"toggle lowercasing of filenames for get",{COMPL_NONE,COMPL_NONE}},
+ {"ls",cmd_dir,"<mask> list the contents of the current directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"l",cmd_dir,"<mask> list the contents of the current directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"mask",cmd_select,"<mask> mask all filenames against this",{COMPL_REMOTE,COMPL_NONE}},
+ {"md",cmd_mkdir,"<directory> make a directory",{COMPL_NONE,COMPL_NONE}},
+ {"mget",cmd_mget,"<mask> get all the matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"mkdir",cmd_mkdir,"<directory> make a directory",{COMPL_NONE,COMPL_NONE}},
+ {"mkfifo",cmd_mkfifo,"<file mode> make a fifo",{COMPL_NONE,COMPL_NONE}},
+ {"more",cmd_more,"<remote name> view a remote file with your pager",{COMPL_REMOTE,COMPL_NONE}},
+ {"mput",cmd_mput,"<mask> put all matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"newer",cmd_newer,"<file> only mget files newer than the specified local file",{COMPL_LOCAL,COMPL_NONE}},
+ {"notify",cmd_notify,"<file>Get notified of dir changes",{COMPL_REMOTE,COMPL_NONE}},
+ {"open",cmd_open,"<mask> open a file",{COMPL_REMOTE,COMPL_NONE}},
+ {"posix", cmd_posix, "turn on all POSIX capabilities", {COMPL_REMOTE,COMPL_NONE}},
+ {"posix_encrypt",cmd_posix_encrypt,"<domain> <user> <password> start up transport encryption",{COMPL_REMOTE,COMPL_NONE}},
+ {"posix_open",cmd_posix_open,"<name> 0<mode> open_flags mode open a file using POSIX interface",{COMPL_REMOTE,COMPL_NONE}},
+ {"posix_mkdir",cmd_posix_mkdir,"<name> 0<mode> creates a directory using POSIX interface",{COMPL_REMOTE,COMPL_NONE}},
+ {"posix_rmdir",cmd_posix_rmdir,"<name> removes a directory using POSIX interface",{COMPL_REMOTE,COMPL_NONE}},
+ {"posix_unlink",cmd_posix_unlink,"<name> removes a file using POSIX interface",{COMPL_REMOTE,COMPL_NONE}},
+ {"posix_whoami",cmd_posix_whoami,"return logged on user information "
+ "using POSIX interface",{COMPL_REMOTE,COMPL_NONE}},
+ {"print",cmd_print,"<file name> print a file",{COMPL_NONE,COMPL_NONE}},
+ {"prompt",cmd_prompt,"toggle prompting for filenames for mget and mput",{COMPL_NONE,COMPL_NONE}},
+ {"put",cmd_put,"<local name> [remote name] put a file",{COMPL_LOCAL,COMPL_REMOTE}},
+ {"pwd",cmd_pwd,"show current remote directory (same as 'cd' with no args)",{COMPL_NONE,COMPL_NONE}},
+ {"q",cmd_quit,"logoff the server",{COMPL_NONE,COMPL_NONE}},
+ {"queue",cmd_queue,"show the print queue",{COMPL_NONE,COMPL_NONE}},
+ {"quit",cmd_quit,"logoff the server",{COMPL_NONE,COMPL_NONE}},
+ {"readlink",cmd_readlink,"filename Do a UNIX extensions readlink call on a symlink",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"rd",cmd_rmdir,"<directory> remove a directory",{COMPL_NONE,COMPL_NONE}},
+ {"recurse",cmd_recurse,"toggle directory recursion for mget and mput",{COMPL_NONE,COMPL_NONE}},
+ {"reget",cmd_reget,"<remote name> [local name] get a file restarting at end of local file",{COMPL_REMOTE,COMPL_LOCAL}},
+ {"rename",cmd_rename,"<src> <dest> rename some files",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"reput",cmd_reput,"<local name> [remote name] put a file restarting at end of remote file",{COMPL_LOCAL,COMPL_REMOTE}},
+ {"rm",cmd_del,"<mask> delete all matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"rmdir",cmd_rmdir,"<directory> remove a directory",{COMPL_REMOTE,COMPL_NONE}},
+ {"showacls",cmd_showacls,"toggle if ACLs are shown or not",{COMPL_NONE,COMPL_NONE}},
+ {"setea", cmd_setea, "<file name> <eaname> <eaval> Set an EA of a file",
+ {COMPL_REMOTE, COMPL_LOCAL}},
+ {"setmode",cmd_setmode,"<file name> <setmode string> change modes of file",{COMPL_REMOTE,COMPL_NONE}},
+ {"scopy",cmd_scopy,"<src> <dest> server-side copy file",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"stat",cmd_stat,"<file name> Do a UNIX extensions stat call on a file",{COMPL_REMOTE,COMPL_NONE}},
+ {"symlink",cmd_symlink,"<oldname> <newname> create a UNIX symlink",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"tar",cmd_tar,"tar <c|x>[IXFvbgNan] current directory to/from <file name>",{COMPL_NONE,COMPL_NONE}},
+ {"tarmode",cmd_tarmode,"<full|inc|reset|noreset> tar's behaviour towards archive bits",{COMPL_NONE,COMPL_NONE}},
+ {"timeout",cmd_timeout,"timeout <number> - set the per-operation timeout in seconds (default 20)",{COMPL_NONE,COMPL_NONE}},
+ {"translate",cmd_translate,"toggle text translation for printing",{COMPL_NONE,COMPL_NONE}},
+ {"unlock",cmd_unlock,"unlock <fnum> <hex-start> <hex-len> : remove a POSIX lock",{COMPL_REMOTE,COMPL_REMOTE}},
+ {"volume",cmd_volume,"print the volume name",{COMPL_NONE,COMPL_NONE}},
+ {"vuid",cmd_vuid,"change current vuid",{COMPL_NONE,COMPL_NONE}},
+ {"wdel",cmd_wdel,"<attrib> <mask> wildcard delete all matching files",{COMPL_REMOTE,COMPL_NONE}},
+ {"logon",cmd_logon,"establish new logon",{COMPL_NONE,COMPL_NONE}},
+ {"listconnect",cmd_list_connect,"list open connections",{COMPL_NONE,COMPL_NONE}},
+ {"showconnect",cmd_show_connect,"display the current active connection",{COMPL_NONE,COMPL_NONE}},
+ {"tcon",cmd_tcon,"connect to a share" ,{COMPL_NONE,COMPL_NONE}},
+ {"tdis",cmd_tdis,"disconnect from a share",{COMPL_NONE,COMPL_NONE}},
+ {"tid",cmd_tid,"show or set the current tid (tree-id)",{COMPL_NONE,COMPL_NONE}},
+ {"utimes", cmd_utimes,"<file name> <create_time> <access_time> <mod_time> "
+ "<ctime> set times", {COMPL_REMOTE,COMPL_NONE}},
+ {"logoff",cmd_logoff,"log off (close the session)",{COMPL_NONE,COMPL_NONE}},
+ {"..",cmd_cd_oneup,"change the remote directory (up one level)",{COMPL_REMOTE,COMPL_NONE}},
+
+ /* Yes, this must be here, see crh's comment above. */
+ {"!",NULL,"run a shell command on the local system",{COMPL_NONE,COMPL_NONE}},
+ {NULL,NULL,NULL,{COMPL_NONE,COMPL_NONE}}
+};
+
+/*******************************************************************
+ Lookup a command string in the list of commands, including
+ abbreviations.
+******************************************************************/
+
+static int process_tok(char *tok)
+{
+ size_t i = 0, matches = 0;
+ size_t cmd=0;
+ size_t tok_len = strlen(tok);
+
+ while (commands[i].fn != NULL) {
+ if (strequal(commands[i].name,tok)) {
+ matches = 1;
+ cmd = i;
+ break;
+ } else if (strnequal(commands[i].name, tok, tok_len)) {
+ matches++;
+ cmd = i;
+ }
+ i++;
+ }
+
+ if (matches == 0)
+ return(-1);
+ else if (matches == 1)
+ return(cmd);
+ else
+ return(-2);
+}
+
+/****************************************************************************
+ Help.
+****************************************************************************/
+
+static int cmd_help(void)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ int i=0,j;
+ char *buf;
+
+ if (next_token_talloc(ctx, &cmd_ptr,&buf,NULL)) {
+ if ((i = process_tok(buf)) >= 0)
+ d_printf("HELP %s:\n\t%s\n\n",
+ commands[i].name,commands[i].description);
+ } else {
+ while (commands[i].description) {
+ for (j=0; commands[i].description && (j<5); j++) {
+ d_printf("%-15s",commands[i].name);
+ i++;
+ }
+ d_printf("\n");
+ }
+ }
+ return 0;
+}
+
+/****************************************************************************
+ Process a -c command string.
+****************************************************************************/
+
+static int process_command_string(const char *cmd_in)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *cmd = talloc_strdup(ctx, cmd_in);
+ int rc = 0;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ if (!cmd) {
+ return 1;
+ }
+ /* establish the connection if not already */
+
+ if (!cli) {
+ NTSTATUS status;
+
+ status = cli_cm_open(talloc_tos(), NULL,
+ desthost,
+ service,
+ creds,
+ have_ip ? &dest_ss : NULL, port,
+ name_type,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+ cli_set_timeout(cli, io_timeout*1000);
+ }
+
+ while (cmd[0] != '\0') {
+ char *line;
+ char *p;
+ char *tok;
+ int i;
+
+ if ((p = strchr_m(cmd, ';')) == 0) {
+ line = cmd;
+ cmd += strlen(cmd);
+ } else {
+ *p = '\0';
+ line = cmd;
+ cmd = p + 1;
+ }
+
+ /* and get the first part of the command */
+ cmd_ptr = line;
+ if (!next_token_talloc(ctx, &cmd_ptr,&tok,NULL)) {
+ continue;
+ }
+
+ if ((i = process_tok(tok)) >= 0) {
+ rc = commands[i].fn();
+ } else if (i == -2) {
+ d_printf("%s: command abbreviation ambiguous\n",tok);
+ } else {
+ d_printf("%s: command not found\n",tok);
+ }
+ }
+
+ return rc;
+}
+
+#define MAX_COMPLETIONS 100
+
+struct completion_remote {
+ char *dirmask;
+ char **matches;
+ int count, samelen;
+ const char *text;
+ int len;
+};
+
+static NTSTATUS completion_remote_filter(struct file_info *f,
+ const char *mask,
+ void *state)
+{
+ struct completion_remote *info = (struct completion_remote *)state;
+
+ if (info->count >= MAX_COMPLETIONS - 1) {
+ return NT_STATUS_OK;
+ }
+ if (strncmp(info->text, f->name, info->len) != 0) {
+ return NT_STATUS_OK;
+ }
+ if (ISDOT(f->name) || ISDOTDOT(f->name)) {
+ return NT_STATUS_OK;
+ }
+
+ if ((info->dirmask[0] == 0) && !(f->attr & FILE_ATTRIBUTE_DIRECTORY))
+ info->matches[info->count] = SMB_STRDUP(f->name);
+ else {
+ TALLOC_CTX *ctx = talloc_stackframe();
+ char *tmp;
+
+ tmp = talloc_strdup(ctx,info->dirmask);
+ if (!tmp) {
+ TALLOC_FREE(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tmp = talloc_asprintf_append(tmp, "%s", f->name);
+ if (!tmp) {
+ TALLOC_FREE(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (f->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ tmp = talloc_asprintf_append(tmp, "%s",
+ CLI_DIRSEP_STR);
+ }
+ if (!tmp) {
+ TALLOC_FREE(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ info->matches[info->count] = SMB_STRDUP(tmp);
+ TALLOC_FREE(ctx);
+ }
+ if (info->matches[info->count] == NULL) {
+ return NT_STATUS_OK;
+ }
+ if (f->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ smb_readline_ca_char(0);
+ }
+ if (info->count == 1) {
+ info->samelen = strlen(info->matches[info->count]);
+ } else {
+ while (strncmp(info->matches[info->count],
+ info->matches[info->count-1],
+ info->samelen) != 0) {
+ info->samelen--;
+ }
+ }
+ info->count++;
+ return NT_STATUS_OK;
+}
+
+static char **remote_completion(const char *text, int len)
+{
+ TALLOC_CTX *ctx = talloc_stackframe();
+ char *dirmask = NULL;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ int i;
+ struct completion_remote info = { NULL, NULL, 1, 0, NULL, 0 };
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NTSTATUS status;
+
+ /* can't have non-static initialisation on Sun CC, so do it
+ at run time here */
+ info.samelen = len;
+ info.text = text;
+ info.len = len;
+
+ info.matches = SMB_MALLOC_ARRAY(char *,MAX_COMPLETIONS);
+ if (!info.matches) {
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+
+ /*
+ * We're leaving matches[0] free to fill it later with the text to
+ * display: Either the one single match or the longest common subset
+ * of the matches.
+ */
+ info.matches[0] = NULL;
+ info.count = 1;
+
+ for (i = len-1; i >= 0; i--) {
+ if ((text[i] == '/') || (text[i] == CLI_DIRSEP_CHAR)) {
+ break;
+ }
+ }
+
+ info.text = text+i+1;
+ info.samelen = info.len = len-i-1;
+
+ if (i > 0) {
+ info.dirmask = SMB_MALLOC_ARRAY(char, i+2);
+ if (!info.dirmask) {
+ goto cleanup;
+ }
+ strncpy(info.dirmask, text, i+1);
+ info.dirmask[i+1] = 0;
+ dirmask = talloc_asprintf(ctx,
+ "%s%*s*",
+ client_get_cur_dir(),
+ i-1,
+ text);
+ } else {
+ info.dirmask = SMB_STRDUP("");
+ if (!info.dirmask) {
+ goto cleanup;
+ }
+ dirmask = talloc_asprintf(ctx,
+ "%s*",
+ client_get_cur_dir());
+ }
+ if (!dirmask) {
+ goto cleanup;
+ }
+ dirmask = client_clean_name(ctx, dirmask);
+ if (dirmask == NULL) {
+ goto cleanup;
+ }
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, dirmask, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup;
+ }
+ status = cli_list(targetcli, targetpath, FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
+ completion_remote_filter, (void *)&info);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup;
+ }
+
+ if (info.count == 1) {
+ /*
+ * No matches at all, NULL indicates there is nothing
+ */
+ SAFE_FREE(info.matches[0]);
+ SAFE_FREE(info.matches);
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+
+ if (info.count == 2) {
+ /*
+ * Exactly one match in matches[1], indicate this is the one
+ * in matches[0].
+ */
+ info.matches[0] = info.matches[1];
+ info.matches[1] = NULL;
+ info.count -= 1;
+ TALLOC_FREE(ctx);
+ return info.matches;
+ }
+
+ /*
+ * We got more than one possible match, set the result to the maximum
+ * common subset
+ */
+
+ info.matches[0] = SMB_STRNDUP(info.matches[1], info.samelen);
+ info.matches[info.count] = NULL;
+ TALLOC_FREE(ctx);
+ return info.matches;
+
+cleanup:
+ for (i = 0; i < info.count; i++) {
+ SAFE_FREE(info.matches[i]);
+ }
+ SAFE_FREE(info.matches);
+ SAFE_FREE(info.dirmask);
+ TALLOC_FREE(ctx);
+ return NULL;
+}
+
+static char **completion_fn(const char *text, int start, int end)
+{
+ smb_readline_ca_char(' ');
+
+ if (start) {
+ const char *buf, *sp;
+ int i;
+ char compl_type;
+
+ buf = smb_readline_get_line_buffer();
+ if (buf == NULL)
+ return NULL;
+
+ sp = strchr(buf, ' ');
+ if (sp == NULL)
+ return NULL;
+
+ for (i = 0; commands[i].name; i++) {
+ if ((strncmp(commands[i].name, buf, sp - buf) == 0) &&
+ (commands[i].name[sp - buf] == 0)) {
+ break;
+ }
+ }
+ if (commands[i].name == NULL)
+ return NULL;
+
+ while (*sp == ' ')
+ sp++;
+
+ if (sp == (buf + start))
+ compl_type = commands[i].compl_args[0];
+ else
+ compl_type = commands[i].compl_args[1];
+
+ if (compl_type == COMPL_REMOTE)
+ return remote_completion(text, end - start);
+ else /* fall back to local filename completion */
+ return NULL;
+ } else {
+ char **matches;
+ size_t i, len, samelen = 0, count=1;
+
+ matches = SMB_MALLOC_ARRAY(char *, MAX_COMPLETIONS);
+ if (!matches) {
+ return NULL;
+ }
+ matches[0] = NULL;
+
+ len = strlen(text);
+ for (i=0;commands[i].fn && count < MAX_COMPLETIONS-1;i++) {
+ if (strncmp(text, commands[i].name, len) == 0) {
+ matches[count] = SMB_STRDUP(commands[i].name);
+ if (!matches[count])
+ goto cleanup;
+ if (count == 1)
+ samelen = strlen(matches[count]);
+ else
+ while (strncmp(matches[count], matches[count-1], samelen) != 0)
+ samelen--;
+ count++;
+ }
+ }
+
+ switch (count) {
+ case 0: /* should never happen */
+ case 1:
+ goto cleanup;
+ case 2:
+ matches[0] = SMB_STRDUP(matches[1]);
+ break;
+ default:
+ matches[0] = (char *)SMB_MALLOC(samelen+1);
+ if (!matches[0])
+ goto cleanup;
+ strncpy(matches[0], matches[1], samelen);
+ matches[0][samelen] = 0;
+ }
+ matches[count] = NULL;
+ return matches;
+
+cleanup:
+ for (i = 0; i < count; i++)
+ free(matches[i]);
+
+ free(matches);
+ return NULL;
+ }
+}
+
+static bool finished;
+
+/****************************************************************************
+ Make sure we swallow keepalives during idle time.
+****************************************************************************/
+
+static void readline_callback(void)
+{
+ static time_t last_t;
+ struct timespec now;
+ time_t t;
+ NTSTATUS status;
+ unsigned char garbage[16];
+
+ clock_gettime_mono(&now);
+ t = now.tv_sec;
+
+ if (t - last_t < 5)
+ return;
+
+ last_t = t;
+
+ /* Ping the server to keep the connection alive using SMBecho. */
+ memset(garbage, 0xf0, sizeof(garbage));
+ status = cli_echo(cli, 1, data_blob_const(garbage, sizeof(garbage)));
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ /*
+ * Even if server returns NT_STATUS_INVALID_PARAMETER
+ * it still responded.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13007
+ */
+ return;
+ }
+
+ if (!cli_state_is_connected(cli)) {
+ DEBUG(0,("SMBecho failed (%s). The connection is "
+ "disconnected now\n", nt_errstr(status)));
+ finished = true;
+ smb_readline_done();
+ }
+}
+
+/****************************************************************************
+ Process commands on stdin.
+****************************************************************************/
+
+static int process_stdin(void)
+{
+ int rc = 0;
+
+ if (!quiet) {
+ d_printf("Try \"help\" to get a list of possible commands.\n");
+ }
+
+ while (!finished) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *tok = NULL;
+ char *the_prompt = NULL;
+ char *line = NULL;
+ int i;
+
+ /* display a prompt */
+ the_prompt = talloc_asprintf(frame,
+ "smb: %s> ",
+ client_get_cur_dir());
+ if (the_prompt == NULL) {
+ TALLOC_FREE(frame);
+ break;
+ }
+ line = smb_readline(the_prompt, readline_callback, completion_fn);
+ if (!line) {
+ TALLOC_FREE(frame);
+ break;
+ }
+
+ /* special case - first char is ! */
+ if (*line == '!') {
+ if (system(line + 1) == -1) {
+ d_printf("system() command %s failed.\n",
+ line+1);
+ }
+ SAFE_FREE(line);
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ /* and get the first part of the command */
+ cmd_ptr = line;
+ if (!next_token_talloc(frame, &cmd_ptr,&tok,NULL)) {
+ TALLOC_FREE(frame);
+ SAFE_FREE(line);
+ continue;
+ }
+
+ if ((i = process_tok(tok)) >= 0) {
+ rc = commands[i].fn();
+ } else if (i == -2) {
+ d_printf("%s: command abbreviation ambiguous\n",tok);
+ } else {
+ d_printf("%s: command not found\n",tok);
+ }
+ SAFE_FREE(line);
+ TALLOC_FREE(frame);
+ }
+ return rc;
+}
+
+/****************************************************************************
+ Process commands from the client.
+****************************************************************************/
+
+static int process(const char *base_directory)
+{
+ int rc = 0;
+ NTSTATUS status;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ status = cli_cm_open(talloc_tos(), NULL,
+ desthost,
+ service,
+ creds,
+ have_ip ? &dest_ss : NULL, port,
+ name_type, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+
+ cli_set_timeout(cli, io_timeout*1000);
+
+ if (base_directory && *base_directory) {
+ rc = do_cd(base_directory);
+ if (rc) {
+ cli_shutdown(cli);
+ return rc;
+ }
+ }
+
+ if (cmdstr) {
+ rc = process_command_string(cmdstr);
+ } else {
+ process_stdin();
+ }
+
+ cli_shutdown(cli);
+ return rc;
+}
+
+/****************************************************************************
+ Handle a -L query.
+****************************************************************************/
+
+static int do_host_query(struct loadparm_context *lp_ctx,
+ const char *query_host)
+{
+ NTSTATUS status;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ status = cli_cm_open(talloc_tos(), NULL,
+ query_host,
+ "IPC$",
+ creds,
+ have_ip ? &dest_ss : NULL, port,
+ name_type, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+
+ cli_set_timeout(cli, io_timeout*1000);
+ browse_host(true);
+
+ /* Ensure that the host can do IPv4 */
+
+ if (!interpret_addr(query_host)) {
+ struct sockaddr_storage ss;
+ if (interpret_string_addr(&ss, query_host, 0) &&
+ (ss.ss_family != AF_INET)) {
+ d_printf("%s is an IPv6 address -- no workgroup available\n",
+ query_host);
+ return 1;
+ }
+ }
+
+ if (lp_client_min_protocol() > PROTOCOL_NT1) {
+ d_printf("SMB1 disabled -- no workgroup available\n");
+ goto out;
+ }
+
+ if (lp_disable_netbios()) {
+ d_printf("NetBIOS over TCP disabled -- no workgroup available\n");
+ goto out;
+ }
+
+ if (port != NBT_SMB_PORT ||
+ smbXcli_conn_protocol(cli->conn) > PROTOCOL_NT1)
+ {
+ /*
+ * Workgroups simply don't make sense over anything
+ * else but port 139 and SMB1.
+ */
+
+ cli_shutdown(cli);
+ d_printf("Reconnecting with SMB1 for workgroup listing.\n");
+ lpcfg_set_cmdline(lp_ctx, "client max protocol", "NT1");
+ status = cli_cm_open(talloc_tos(), NULL,
+ query_host,
+ "IPC$",
+ creds,
+ have_ip ? &dest_ss : NULL, NBT_SMB_PORT,
+ name_type, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Unable to connect with SMB1 "
+ "-- no workgroup available\n");
+ return 0;
+ }
+ }
+
+ cli_set_timeout(cli, io_timeout*1000);
+ list_servers(lp_workgroup());
+out:
+ cli_shutdown(cli);
+
+ return(0);
+}
+
+/****************************************************************************
+ Handle a tar operation.
+****************************************************************************/
+
+static int do_tar_op(const char *base_directory)
+{
+ struct tar *tar_ctx = tar_get_ctx();
+ int ret = 0;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ /* do we already have a connection? */
+ if (!cli) {
+ NTSTATUS status;
+
+ status = cli_cm_open(talloc_tos(), NULL,
+ desthost,
+ service,
+ creds,
+ have_ip ? &dest_ss : NULL, port,
+ name_type, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = 1;
+ goto out;
+ }
+ cli_set_timeout(cli, io_timeout*1000);
+ }
+
+ recurse = true;
+
+ if (base_directory && *base_directory) {
+ ret = do_cd(base_directory);
+ if (ret) {
+ goto out_cli;
+ }
+ }
+
+ ret = tar_process(tar_ctx);
+
+ out_cli:
+ cli_shutdown(cli);
+ out:
+ return ret;
+}
+
+/****************************************************************************
+ Handle a message operation.
+****************************************************************************/
+
+static int do_message_op(struct cli_credentials *creds)
+{
+ NTSTATUS status;
+
+ if (lp_disable_netbios()) {
+ d_printf("NetBIOS over TCP disabled.\n");
+ return 1;
+ }
+
+ status = cli_connect_nb(desthost, have_ip ? &dest_ss : NULL,
+ port ? port : NBT_SMB_PORT, name_type,
+ lp_netbios_name(),
+ SMB_SIGNING_OFF,
+ 0,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Connection to %s failed. Error %s\n", desthost, nt_errstr(status));
+ return 1;
+ }
+
+ cli_set_timeout(cli, io_timeout*1000);
+ send_message(cli_credentials_get_username(creds));
+ cli_shutdown(cli);
+
+ return 0;
+}
+
+/****************************************************************************
+ main program
+****************************************************************************/
+
+int main(int argc,char *argv[])
+{
+ const char **const_argv = discard_const_p(const char *, argv);
+ char *base_directory = NULL;
+ int opt;
+ char *query_host = NULL;
+ bool message = false;
+ static const char *new_name_resolve_order = NULL;
+ poptContext pc;
+ char *p;
+ int rc = 0;
+ bool tar_opt = false;
+ bool service_opt = false;
+ struct tar *tar_ctx = tar_get_ctx();
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+
+ {
+ .longName = "message",
+ .shortName = 'M',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'M',
+ .descrip = "Send message",
+ .argDescrip = "HOST",
+ },
+ {
+ .longName = "ip-address",
+ .shortName = 'I',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'I',
+ .descrip = "Use this IP to connect to",
+ .argDescrip = "IP",
+ },
+ {
+ .longName = "stderr",
+ .shortName = 'E',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'E',
+ .descrip = "Write messages to stderr instead of stdout",
+ },
+ {
+ .longName = "list",
+ .shortName = 'L',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'L',
+ .descrip = "Get a list of shares available on a host",
+ .argDescrip = "HOST",
+ },
+ {
+ .longName = "tar",
+ .shortName = 'T',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'T',
+ .descrip = "Command line tar",
+ .argDescrip = "<c|x>IXFvgbNan",
+ },
+ {
+ .longName = "directory",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'D',
+ .descrip = "Start from directory",
+ .argDescrip = "DIR",
+ },
+ {
+ .longName = "command",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &cmdstr,
+ .val = 'c',
+ .descrip = "Execute semicolon separated commands",
+ },
+ {
+ .longName = "send-buffer",
+ .shortName = 'b',
+ .argInfo = POPT_ARG_INT,
+ .arg = &io_bufsize,
+ .val = 'b',
+ .descrip = "Changes the transmit/send buffer",
+ .argDescrip = "BYTES",
+ },
+ {
+ .longName = "timeout",
+ .shortName = 't',
+ .argInfo = POPT_ARG_INT,
+ .arg = &io_timeout,
+ .val = 'b',
+ .descrip = "Changes the per-operation timeout",
+ .argDescrip = "SECONDS",
+ },
+ {
+ .longName = "port",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_INT,
+ .arg = &port,
+ .val = 'p',
+ .descrip = "Port to connect to",
+ .argDescrip = "PORT",
+ },
+ {
+ .longName = "grepable",
+ .shortName = 'g',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'g',
+ .descrip = "Produce grepable output",
+ },
+ {
+ .longName = "quiet",
+ .shortName = 'q',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'q',
+ .descrip = "Suppress help message",
+ },
+ {
+ .longName = "browse",
+ .shortName = 'B',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'B',
+ .descrip = "Browse SMB servers using DNS",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S3
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cli_credentials *creds = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+
+ if (!client_set_cur_dir("\\")) {
+ exit(ENOMEM);
+ }
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ exit(ENOMEM);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "1");
+
+ /* skip argv(0) */
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ const_argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "[OPTIONS] service <password>");
+
+ creds = samba_cmdline_get_creds();
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+
+ /*
+ * if the tar option has been called previously, now
+ * we need to eat out the leftovers
+ */
+ /* I see no other way to keep things sane --SSS */
+ if (tar_opt == true) {
+ while (poptPeekArg(pc)) {
+ poptGetArg(pc);
+ }
+ tar_opt = false;
+ }
+
+ /* if the service has not yet been specified lets see if it is available in the popt stack */
+ if (!service_opt && poptPeekArg(pc)) {
+ service = talloc_strdup(frame, poptGetArg(pc));
+ if (!service) {
+ exit(ENOMEM);
+ }
+ service_opt = true;
+ }
+
+ /* if the service has already been retrieved then check if we have also a password */
+ if (service_opt &&
+ cli_credentials_get_password(creds) == NULL &&
+ poptPeekArg(pc)) {
+ cli_credentials_set_password(creds,
+ poptGetArg(pc),
+ CRED_SPECIFIED);
+ }
+
+
+ switch (opt) {
+ case 'M':
+ /* Messages are sent to NetBIOS name type 0x3
+ * (Messenger Service). Make sure we default
+ * to port 139 instead of port 445. srl,crh
+ */
+ name_type = 0x03;
+ desthost = talloc_strdup(frame,poptGetOptArg(pc));
+ if (!desthost) {
+ exit(ENOMEM);
+ }
+ if( !port )
+ port = NBT_SMB_PORT;
+ message = true;
+ break;
+ case 'I':
+ {
+ if (!interpret_string_addr(&dest_ss, poptGetOptArg(pc), 0)) {
+ exit(1);
+ }
+ have_ip = true;
+ print_sockaddr(dest_ss_str, sizeof(dest_ss_str), &dest_ss);
+ }
+ break;
+ case 'E':
+ setup_logging("smbclient", DEBUG_STDERR );
+ display_set_stderr();
+ break;
+
+ case 'L':
+ query_host = talloc_strdup(frame, poptGetOptArg(pc));
+ if (!query_host) {
+ exit(ENOMEM);
+ }
+ break;
+ case 'T':
+ /* We must use old option processing for this. Find the
+ * position of the -T option in the raw argv[]. */
+ {
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ if (strncmp("-T", argv[i],2)==0)
+ break;
+ }
+ i++;
+ if (tar_parse_args(tar_ctx, poptGetOptArg(pc),
+ const_argv + i, argc - i)) {
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+ /* this must be the last option, mark we have parsed it so that we know we have */
+ tar_opt = true;
+ break;
+ case 'D':
+ base_directory = talloc_strdup(frame, poptGetOptArg(pc));
+ if (!base_directory) {
+ exit(ENOMEM);
+ }
+ break;
+ case 'g':
+ grepable=true;
+ break;
+ case 'q':
+ quiet=true;
+ break;
+ case 'B':
+ return(do_smb_browse());
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ /* We may still have some leftovers after the last popt option has been called */
+ if (tar_opt == true) {
+ while (poptPeekArg(pc)) {
+ poptGetArg(pc);
+ }
+ tar_opt = false;
+ }
+
+ /* if the service has not yet been specified lets see if it is available in the popt stack */
+ if (!service_opt && poptPeekArg(pc)) {
+ service = talloc_strdup(frame,poptGetArg(pc));
+ if (!service) {
+ exit(ENOMEM);
+ }
+ service_opt = true;
+ }
+
+ /* if the service has already been retrieved then check if we have also a password */
+ if (service_opt &&
+ cli_credentials_get_password(creds) == NULL &&
+ poptPeekArg(pc)) {
+ cli_credentials_set_password(creds,
+ poptGetArg(pc),
+ CRED_SPECIFIED);
+ }
+
+ if (service_opt && service) {
+ size_t len;
+
+ /* Convert any '/' characters in the service name to '\' characters */
+ string_replace(service, '/','\\');
+ if (count_chars(service,'\\') < 3) {
+ d_printf("\n%s: Not enough '\\' characters in service\n",service);
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ /* Remove trailing slashes */
+ len = strlen(service);
+ while(len > 0 && service[len - 1] == '\\') {
+ --len;
+ service[len] = '\0';
+ }
+ }
+
+ if(new_name_resolve_order)
+ lpcfg_set_cmdline(lp_ctx,
+ "name resolve order",
+ new_name_resolve_order);
+
+ if (!tar_to_process(tar_ctx) && !query_host && !service && !message) {
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+
+ poptFreeContext(pc);
+ samba_cmdline_burn(argc, argv);
+
+ DEBUG(3,("Client started (version %s).\n", samba_version_string()));
+
+ if (tar_to_process(tar_ctx)) {
+ if (cmdstr)
+ process_command_string(cmdstr);
+ rc = do_tar_op(base_directory);
+ } else if (query_host && *query_host) {
+ char *qhost = query_host;
+ char *slash;
+
+ while (*qhost == '\\' || *qhost == '/')
+ qhost++;
+
+ if ((slash = strchr_m(qhost, '/'))
+ || (slash = strchr_m(qhost, '\\'))) {
+ *slash = 0;
+ }
+
+ if ((p=strchr_m(qhost, '#'))) {
+ *p = 0;
+ p++;
+ sscanf(p, "%x", &name_type);
+ }
+
+ rc = do_host_query(lp_ctx, qhost);
+ } else if (message) {
+ rc = do_message_op(creds);
+ } else if (process(base_directory)) {
+ rc = 1;
+ }
+
+ gfree_all();
+
+ TALLOC_FREE(frame);
+ return rc;
+}
diff --git a/source3/client/client_proto.h b/source3/client/client_proto.h
new file mode 100644
index 0000000..0c83ba9
--- /dev/null
+++ b/source3/client/client_proto.h
@@ -0,0 +1,53 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * collected prototypes header
+ *
+ * frozen from "make proto" in May 2008
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CLIENT_PROTO_H_
+#define _CLIENT_PROTO_H_
+
+struct cli_state;
+struct file_info;
+
+enum {
+ ATTR_UNSET,
+ ATTR_SET,
+};
+
+/* The following definitions come from client/client.c */
+
+const char *client_get_cur_dir(void);
+const char *client_set_cur_dir(const char *newdir);
+char *client_clean_name(TALLOC_CTX *ctx, const char *name);
+NTSTATUS do_list(const char *mask,
+ uint32_t attribute,
+ NTSTATUS (*fn)(struct cli_state *cli_state, struct file_info *,
+ const char *dir),
+ bool rec,
+ bool dirs);
+int set_remote_attr(const char *filename, uint32_t new_attr, int mode);
+int cmd_iosize(void);
+
+/* The following definitions come from client/dnsbrowse.c */
+
+int do_smb_browse(void);
+int do_smb_browse(void);
+
+#endif /* _CLIENT_PROTO_H_ */
diff --git a/source3/client/clitar.c b/source3/client/clitar.c
new file mode 100644
index 0000000..14b9811
--- /dev/null
+++ b/source3/client/clitar.c
@@ -0,0 +1,1903 @@
+/*
+ Unix SMB/CIFS implementation.
+ Tar backup command extension
+ Copyright (C) Aurélien Aptel 2013
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * # General overview of the tar extension
+ *
+ * All tar_xxx() functions work on a `struct tar` which store most of
+ * the context of the backup process.
+ *
+ * The current tar context can be accessed via the global variable
+ * `tar_ctx`. It's publicly exported as an opaque handle via
+ * tar_get_ctx().
+ *
+ * A tar context is first configured through tar_parse_args() which
+ * can be called from either the CLI (in client.c) or the interactive
+ * session (via the cmd_tar() callback).
+ *
+ * Once the configuration is done (successfully), the context is ready
+ * for processing and tar_to_process() returns true.
+ *
+ * The next step is to call tar_process() which dispatch the
+ * processing to either tar_create() or tar_extract(), depending on
+ * the context.
+ *
+ * ## Archive creation
+ *
+ * tar_create() creates an archive using the libarchive API then
+ *
+ * - iterates on the requested paths if the context is in inclusion
+ * mode with tar_create_from_list()
+ *
+ * - or iterates on the whole share (starting from the current dir) if
+ * in exclusion mode or if no specific path were requested
+ *
+ * The do_list() function from client.c is used to list recursively
+ * the share. In particular it takes a DOS path mask (eg. \mydir\*)
+ * and a callback function which will be called with each file name
+ * and attributes. The tar callback function is get_file_callback().
+ *
+ * The callback function checks whether the file should be skipped
+ * according the the configuration via tar_create_skip_path(). If it's
+ * not skipped it's downloaded and written to the archive in
+ * tar_get_file().
+ *
+ * ## Archive extraction
+ *
+ * tar_extract() opens the archive and iterates on each file in
+ * it. For each file tar_extract_skip_path() checks whether it should
+ * be skipped according to the config. If it's not skipped it's
+ * uploaded on the server in tar_send_file().
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "client/client_proto.h"
+#include "client/clitar_proto.h"
+#include "libsmb/libsmb.h"
+
+#ifdef HAVE_LIBARCHIVE
+
+#include <archive.h>
+#include <archive_entry.h>
+
+/* prepend module name and line number to debug messages */
+#define DBG(a, b) (DEBUG(a, ("tar:%-4d ", __LINE__)), DEBUG(a, b))
+
+/* preprocessor magic to stringify __LINE__ (int) */
+#define STR1(x) #x
+#define STR2(x) STR1(x)
+
+/**
+ * Number of byte in a block unit.
+ */
+#define TAR_BLOCK_UNIT 512
+
+/**
+ * Default tar block size in TAR_BLOCK_UNIT.
+ */
+#define TAR_DEFAULT_BLOCK_SIZE 20
+
+/**
+ * Maximum value for the blocksize field
+ */
+#define TAR_MAX_BLOCK_SIZE 0xffff
+
+/**
+ * Size of the buffer used when downloading a file
+ */
+#define TAR_CLI_READ_SIZE 0xff00
+
+#define TAR_DO_LIST_ATTR (FILE_ATTRIBUTE_DIRECTORY \
+ | FILE_ATTRIBUTE_SYSTEM \
+ | FILE_ATTRIBUTE_HIDDEN)
+
+
+enum tar_operation {
+ TAR_NO_OPERATION,
+ TAR_CREATE, /* c flag */
+ TAR_EXTRACT, /* x flag */
+};
+
+enum tar_selection {
+ TAR_NO_SELECTION,
+ TAR_INCLUDE, /* I and F flag, default */
+ TAR_EXCLUDE, /* X flag */
+};
+
+struct tar {
+ TALLOC_CTX *talloc_ctx;
+
+ /* in state that needs/can be processed? */
+ bool to_process;
+
+ /* flags */
+ struct tar_mode {
+ enum tar_operation operation; /* create, extract */
+ enum tar_selection selection; /* include, exclude */
+ int blocksize; /* size in TAR_BLOCK_UNIT of a tar file block */
+ bool hidden; /* backup hidden file? */
+ bool system; /* backup system file? */
+ bool incremental; /* backup _only_ archived file? */
+ bool reset; /* unset archive bit? */
+ bool dry; /* don't write tar file? */
+ bool regex; /* XXX: never actually using regex... */
+ bool verbose; /* XXX: ignored */
+ } mode;
+
+ /* nb of bytes received */
+ uint64_t total_size;
+
+ /* path to tar archive name */
+ char *tar_path;
+
+ /* list of path to include or exclude */
+ char **path_list;
+ int path_list_size;
+
+ /* archive handle */
+ struct archive *archive;
+
+ /* counters */
+ uint64_t numdir;
+ uint64_t numfile;
+};
+
+/**
+ * Global context imported in client.c when needed.
+ *
+ * Default options.
+ */
+struct tar tar_ctx = {
+ .mode.selection = TAR_INCLUDE,
+ .mode.blocksize = TAR_DEFAULT_BLOCK_SIZE,
+ .mode.hidden = true,
+ .mode.system = true,
+ .mode.incremental = false,
+ .mode.reset = false,
+ .mode.dry = false,
+ .mode.regex = false,
+ .mode.verbose = false,
+};
+
+/* tar, local function */
+static int tar_create(struct tar* t);
+static int tar_create_from_list(struct tar *t);
+static int tar_extract(struct tar *t);
+static int tar_read_inclusion_file(struct tar *t, const char* filename);
+static int tar_send_file(struct tar *t, struct archive_entry *entry);
+static int tar_set_blocksize(struct tar *t, int size);
+static int tar_set_newer_than(struct tar *t, const char *filename);
+static NTSTATUS tar_add_selection_path(struct tar *t, const char *path);
+static void tar_dump(struct tar *t);
+static NTSTATUS tar_extract_skip_path(struct tar *t,
+ struct archive_entry *entry,
+ bool *_skip);
+static TALLOC_CTX *tar_reset_mem_context(struct tar *t);
+static void tar_free_mem_context(struct tar *t);
+static NTSTATUS tar_create_skip_path(struct tar *t,
+ const char *fullpath,
+ const struct file_info *finfo,
+ bool *_skip);
+
+static NTSTATUS tar_path_in_list(struct tar *t, const char *path,
+ bool reverse, bool *_is_in_list);
+
+static int tar_get_file(struct tar *t,
+ const char *full_dos_path,
+ struct file_info *finfo);
+
+static NTSTATUS get_file_callback(struct cli_state *cli,
+ struct file_info *finfo,
+ const char *dir);
+
+/* utilities */
+static char *fix_unix_path(char *path, bool removeprefix);
+static NTSTATUS path_base_name(TALLOC_CTX *ctx, const char *path, char **_base);
+static const char* skip_useless_char_in_path(const char *p);
+static int make_remote_path(const char *full_path);
+static int max_token (const char *str);
+static NTSTATUS is_subpath(const char *sub, const char *full,
+ bool *_subpath_match);
+ /*
+ * tar_get_ctx - retrieve global tar context handle
+ */
+struct tar *tar_get_ctx(void)
+{
+ return &tar_ctx;
+}
+
+/**
+ * cmd_block - interactive command to change tar blocksize
+ *
+ * Read a size from the client command line and update the current
+ * blocksize.
+ */
+int cmd_block(void)
+{
+ /* XXX: from client.c */
+ const extern char *cmd_ptr;
+ char *buf;
+ int err = 0;
+ bool ok;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ ok = next_token_talloc(ctx, &cmd_ptr, &buf, NULL);
+ if (!ok) {
+ DBG(0, ("blocksize <n>\n"));
+ err = 1;
+ goto out;
+ }
+
+ ok = tar_set_blocksize(&tar_ctx, atoi(buf));
+ if (ok) {
+ DBG(0, ("invalid blocksize\n"));
+ err = 1;
+ goto out;
+ }
+
+ DBG(2, ("blocksize is now %d\n", tar_ctx.mode.blocksize));
+
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * cmd_tarmode - interactive command to change tar behaviour
+ *
+ * Read one or more modes from the client command line and update the
+ * current tar mode.
+ */
+int cmd_tarmode(void)
+{
+ const extern char *cmd_ptr;
+ char *buf;
+ TALLOC_CTX *ctx;
+
+ struct {
+ const char *cmd;
+ bool *p;
+ bool value;
+ } table[] = {
+ {"full", &tar_ctx.mode.incremental, false},
+ {"inc", &tar_ctx.mode.incremental, true },
+ {"reset", &tar_ctx.mode.reset, true },
+ {"noreset", &tar_ctx.mode.reset, false},
+ {"system", &tar_ctx.mode.system, true },
+ {"nosystem", &tar_ctx.mode.system, false},
+ {"hidden", &tar_ctx.mode.hidden, true },
+ {"nohidden", &tar_ctx.mode.hidden, false},
+ {"verbose", &tar_ctx.mode.verbose, false},
+ {"noverbose", &tar_ctx.mode.verbose, true },
+ };
+
+ ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ while (next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(table); i++) {
+ if (strequal(table[i].cmd, buf)) {
+ *table[i].p = table[i].value;
+ break;
+ }
+ }
+
+ if (i == ARRAY_SIZE(table))
+ d_printf("tarmode: unrecognised option %s\n", buf);
+ }
+
+ d_printf("tarmode is now %s, %s, %s, %s, %s\n",
+ tar_ctx.mode.incremental ? "incremental" : "full",
+ tar_ctx.mode.system ? "system" : "nosystem",
+ tar_ctx.mode.hidden ? "hidden" : "nohidden",
+ tar_ctx.mode.reset ? "reset" : "noreset",
+ tar_ctx.mode.verbose ? "verbose" : "noverbose");
+
+ talloc_free(ctx);
+ return 0;
+}
+
+/**
+ * cmd_tar - interactive command to start a tar backup/restoration
+ *
+ * Check presence of argument, parse them and handle the request.
+ */
+int cmd_tar(void)
+{
+ const extern char *cmd_ptr;
+ const char *flag;
+ const char **val;
+ char *buf;
+ int maxtok = max_token(cmd_ptr);
+ int i = 0;
+ int err = 0;
+ bool ok;
+ int rc;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ ok = next_token_talloc(ctx, &cmd_ptr, &buf, NULL);
+ if (!ok) {
+ d_printf("tar <c|x>[IXFbgvanN] [options] <tar file> [path list]\n");
+ err = 1;
+ goto out;
+ }
+
+ flag = buf;
+ val = talloc_array(ctx, const char *, maxtok);
+ if (val == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ while (next_token_talloc(ctx, &cmd_ptr, &buf, NULL)) {
+ val[i++] = buf;
+ }
+
+ rc = tar_parse_args(&tar_ctx, flag, val, i);
+ if (rc != 0) {
+ d_printf("parse_args failed\n");
+ err = 1;
+ goto out;
+ }
+
+ rc = tar_process(&tar_ctx);
+ if (rc != 0) {
+ d_printf("tar_process failed\n");
+ err = 1;
+ goto out;
+ }
+
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+
+/**
+ * tar_parse_args - parse and set tar command line arguments
+ * @flag: string pointing to tar options
+ * @val: number of tar arguments
+ * @valsize: table of arguments after the flags (number of element in val)
+ *
+ * tar arguments work in a weird way. For each flag f that takes a
+ * value v, the user is supposed to type:
+ *
+ * on the CLI:
+ * -Tf1f2f3 v1 v2 v3 TARFILE PATHS...
+ *
+ * in the interactive session:
+ * tar f1f2f3 v1 v2 v3 TARFILE PATHS...
+ *
+ * @flag has only flags (eg. "f1f2f3") and @val has the arguments
+ * (values) following them (eg. ["v1", "v2", "v3", "TARFILE", "PATH1",
+ * "PATH2"]).
+ *
+ * There are only 2 flags that take an arg: b and N. The other flags
+ * just change the semantic of PATH or TARFILE.
+ *
+ * PATH can be a list of included/excluded paths, the path to a file
+ * containing a list of included/excluded paths to use (F flag). If no
+ * PATH is provided, the whole share is used (/).
+ */
+int tar_parse_args(struct tar* t,
+ const char *flag,
+ const char **val,
+ int valsize)
+{
+ TALLOC_CTX *ctx;
+ bool do_read_list = false;
+ /* index of next value to use */
+ int ival = 0;
+ int rc;
+
+ if (t == NULL) {
+ DBG_WARNING("Invalid tar context\n");
+ return 1;
+ }
+
+ ctx = tar_reset_mem_context(t);
+ if (ctx == NULL) {
+ return 1;
+ }
+ /*
+ * Reset back some options - could be from interactive version
+ * all other modes are left as they are
+ */
+ t->mode.operation = TAR_NO_OPERATION;
+ t->mode.selection = TAR_NO_SELECTION;
+ t->mode.dry = false;
+ t->to_process = false;
+ t->total_size = 0;
+
+ while (flag[0] != '\0') {
+ switch(flag[0]) {
+ /* operation */
+ case 'c':
+ if (t->mode.operation != TAR_NO_OPERATION) {
+ d_printf("Tar must be followed by only one of c or x.\n");
+ return 1;
+ }
+ t->mode.operation = TAR_CREATE;
+ break;
+ case 'x':
+ if (t->mode.operation != TAR_NO_OPERATION) {
+ d_printf("Tar must be followed by only one of c or x.\n");
+ return 1;
+ }
+ t->mode.operation = TAR_EXTRACT;
+ break;
+
+ /* selection */
+ case 'I':
+ if (t->mode.selection != TAR_NO_SELECTION) {
+ d_printf("Only one of I,X,F must be specified\n");
+ return 1;
+ }
+ t->mode.selection = TAR_INCLUDE;
+ break;
+ case 'X':
+ if (t->mode.selection != TAR_NO_SELECTION) {
+ d_printf("Only one of I,X,F must be specified\n");
+ return 1;
+ }
+ t->mode.selection = TAR_EXCLUDE;
+ break;
+ case 'F':
+ if (t->mode.selection != TAR_NO_SELECTION) {
+ d_printf("Only one of I,X,F must be specified\n");
+ return 1;
+ }
+ t->mode.selection = TAR_INCLUDE;
+ do_read_list = true;
+ break;
+
+ /* blocksize */
+ case 'b':
+ if (ival >= valsize) {
+ d_printf("Option b must be followed by a blocksize\n");
+ return 1;
+ }
+
+ if (tar_set_blocksize(t, atoi(val[ival]))) {
+ d_printf("Option b must be followed by a valid blocksize\n");
+ return 1;
+ }
+
+ ival++;
+ break;
+
+ /* incremental mode */
+ case 'g':
+ t->mode.incremental = true;
+ break;
+
+ /* newer than */
+ case 'N':
+ if (ival >= valsize) {
+ d_printf("Option N must be followed by valid file name\n");
+ return 1;
+ }
+
+ if (tar_set_newer_than(t, val[ival])) {
+ d_printf("Error setting newer-than time\n");
+ return 1;
+ }
+
+ ival++;
+ break;
+
+ /* reset mode */
+ case 'a':
+ t->mode.reset = true;
+ break;
+
+ /* verbose */
+ case 'v':
+ t->mode.verbose = true;
+ break;
+
+ /* regex match */
+ case 'r':
+ t->mode.regex = true;
+ break;
+
+ /* dry run mode */
+ case 'n':
+ if (t->mode.operation != TAR_CREATE) {
+ d_printf("n is only meaningful when creating a tar-file\n");
+ return 1;
+ }
+
+ t->mode.dry = true;
+ d_printf("dry_run set\n");
+ break;
+
+ default:
+ d_printf("Unknown tar option\n");
+ return 1;
+ }
+
+ flag++;
+ }
+
+ /* no selection given? default selection is include */
+ if (t->mode.selection == TAR_NO_SELECTION) {
+ t->mode.selection = TAR_INCLUDE;
+ }
+
+ if (valsize - ival < 1) {
+ d_printf("No tar file given.\n");
+ return 1;
+ }
+
+ /* handle TARFILE */
+ t->tar_path = talloc_strdup(ctx, val[ival]);
+ if (t->tar_path == NULL) {
+ return 1;
+ }
+ ival++;
+
+ /*
+ * Make sure that dbf points to stderr if we are using stdout for
+ * tar output
+ */
+ if (t->mode.operation == TAR_CREATE && strequal(t->tar_path, "-")) {
+ setup_logging("smbclient", DEBUG_STDERR);
+ }
+
+ /* handle PATHs... */
+
+ /* flag F -> read file list */
+ if (do_read_list) {
+ if (valsize - ival != 1) {
+ d_printf("Option F must be followed by exactly one filename.\n");
+ return 1;
+ }
+
+ rc = tar_read_inclusion_file(t, val[ival]);
+ if (rc != 0) {
+ return 1;
+ }
+ ival++;
+ /* otherwise store all the PATHs on the command line */
+ } else {
+ int i;
+ for (i = ival; i < valsize; i++) {
+ NTSTATUS status;
+ status = tar_add_selection_path(t, val[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+ }
+ }
+
+ t->to_process = true;
+ tar_dump(t);
+ return 0;
+}
+
+/**
+ * tar_process - start processing archive
+ *
+ * The talloc context of the fields is freed at the end of the call.
+ */
+int tar_process(struct tar *t)
+{
+ int rc = 0;
+
+ if (t == NULL) {
+ DBG_WARNING("Invalid tar context\n");
+ return 1;
+ }
+
+ switch(t->mode.operation) {
+ case TAR_EXTRACT:
+ rc = tar_extract(t);
+ break;
+ case TAR_CREATE:
+ rc = tar_create(t);
+ break;
+ default:
+ DBG_WARNING("Invalid tar state\n");
+ rc = 1;
+ }
+
+ t->to_process = false;
+ tar_free_mem_context(t);
+ DBG(5, ("tar_process done, err = %d\n", rc));
+ return rc;
+}
+
+/**
+ * tar_create - create archive and fetch files
+ */
+static int tar_create(struct tar* t)
+{
+ int r;
+ int err = 0;
+ NTSTATUS status;
+ const char *mask;
+ struct timespec tp_start, tp_end;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ clock_gettime_mono(&tp_start);
+
+ t->numfile = 0;
+ t->numdir = 0;
+ t->archive = archive_write_new();
+
+ if (!t->mode.dry) {
+ const int bsize = t->mode.blocksize * TAR_BLOCK_UNIT;
+ r = archive_write_set_bytes_per_block(t->archive, bsize);
+ if (r != ARCHIVE_OK) {
+ d_printf("Can't use a block size of %d bytes", bsize);
+ err = 1;
+ goto out;
+ }
+
+ /*
+ * Use PAX restricted format which is not the most
+ * conservative choice but has useful extensions and is widely
+ * supported
+ */
+ r = archive_write_set_format_pax_restricted(t->archive);
+ if (r != ARCHIVE_OK) {
+ d_printf("Can't use pax restricted format: %s\n",
+ archive_error_string(t->archive));
+ err = 1;
+ goto out;
+ }
+
+ if (strequal(t->tar_path, "-")) {
+ r = archive_write_open_fd(t->archive, STDOUT_FILENO);
+ } else {
+ r = archive_write_open_filename(t->archive, t->tar_path);
+ }
+
+ if (r != ARCHIVE_OK) {
+ d_printf("Can't open %s: %s\n", t->tar_path,
+ archive_error_string(t->archive));
+ err = 1;
+ goto out_close;
+ }
+ }
+
+ /*
+ * In inclusion mode, iterate on the inclusion list
+ */
+ if (t->mode.selection == TAR_INCLUDE && t->path_list_size > 0) {
+ if (tar_create_from_list(t)) {
+ err = 1;
+ goto out_close;
+ }
+ } else {
+ mask = talloc_asprintf(ctx, "%s\\*", client_get_cur_dir());
+ if (mask == NULL) {
+ err = 1;
+ goto out_close;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ err = 1;
+ goto out_close;
+ }
+ DBG(5, ("tar_process do_list with mask: %s\n", mask));
+ status = do_list(mask, TAR_DO_LIST_ATTR, get_file_callback, true, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG(0, ("do_list fail %s\n", nt_errstr(status)));
+ err = 1;
+ goto out_close;
+ }
+ }
+
+ clock_gettime_mono(&tp_end);
+ d_printf("tar: dumped %"PRIu64" files and %"PRIu64" directories\n",
+ t->numfile, t->numdir);
+ d_printf("Total bytes written: %"PRIu64" (%.1f MiB/s)\n",
+ t->total_size,
+ t->total_size/timespec_elapsed2(&tp_start, &tp_end)/1024/1024);
+
+out_close:
+
+ if (!t->mode.dry) {
+ r = archive_write_close(t->archive);
+ if (r != ARCHIVE_OK) {
+ d_printf("Fatal: %s\n", archive_error_string(t->archive));
+ err = 1;
+ goto out;
+ }
+ }
+out:
+#ifdef HAVE_ARCHIVE_READ_FREE
+ archive_write_free(t->archive);
+#else
+ archive_write_finish(t->archive);
+#endif
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * tar_create_from_list - fetch from path list in include mode
+ */
+static int tar_create_from_list(struct tar *t)
+{
+ int err = 0;
+ NTSTATUS status;
+ char *base;
+ const char *path, *mask, *start_dir;
+ int i;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ start_dir = talloc_strdup(ctx, client_get_cur_dir());
+ if (start_dir == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ for (i = 0; i < t->path_list_size; i++) {
+ path = t->path_list[i];
+ base = NULL;
+ status = path_base_name(ctx, path, &base);
+ if (!NT_STATUS_IS_OK(status)) {
+ err = 1;
+ goto out;
+ }
+ mask = talloc_asprintf(ctx, "%s\\%s",
+ client_get_cur_dir(), path);
+ if (mask == NULL) {
+ err = 1;
+ goto out;
+ }
+ mask = client_clean_name(ctx, mask);
+ if (mask == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ DBG(5, ("incl. path='%s', base='%s', mask='%s'\n",
+ path, base ? base : "NULL", mask));
+
+ if (base != NULL) {
+ base = talloc_asprintf(ctx, "%s%s\\",
+ client_get_cur_dir(), base);
+ if (base == NULL) {
+ err = 1;
+ goto out;
+ }
+ base = client_clean_name(ctx, base);
+ if (base == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ DBG(5, ("cd '%s' before do_list\n", base));
+ client_set_cur_dir(base);
+ }
+ status = do_list(mask, TAR_DO_LIST_ATTR, get_file_callback, true, true);
+ if (base != NULL) {
+ client_set_cur_dir(start_dir);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG(0, ("do_list failed on %s (%s)\n", path, nt_errstr(status)));
+ err = 1;
+ goto out;
+ }
+ }
+
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * get_file_callback - do_list callback
+ *
+ * Callback for client.c do_list(). Called for each file found on the
+ * share matching do_list mask. Recursively call do_list() with itself
+ * as callback when the current file is a directory.
+ */
+static NTSTATUS get_file_callback(struct cli_state *cli,
+ struct file_info *finfo,
+ const char *dir)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ char *remote_name;
+ char *old_dir = NULL;
+ char *new_dir = NULL;
+ const char *initial_dir = dir;
+ bool skip = false;
+ bool isdir;
+ int rc;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ remote_name = talloc_asprintf(ctx, "%s\\%s", initial_dir, finfo->name);
+ if (remote_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ remote_name = client_clean_name(ctx, remote_name);
+ if (remote_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (strequal(finfo->name, "..") || strequal(finfo->name, ".")) {
+ goto out;
+ }
+
+ isdir = finfo->attr & FILE_ATTRIBUTE_DIRECTORY;
+ if (isdir) {
+ old_dir = talloc_strdup(ctx, initial_dir);
+ new_dir = talloc_asprintf(ctx, "%s\\", remote_name);
+ if ((old_dir == NULL) || (new_dir == NULL)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ status = tar_create_skip_path(&tar_ctx,
+ isdir ? new_dir : remote_name,
+ finfo, &skip);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (skip) {
+ DBG(5, ("--- %s\n", remote_name));
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ rc = tar_get_file(&tar_ctx, remote_name, finfo);
+ if (rc != 0) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+out:
+ talloc_free(ctx);
+ return status;
+}
+
+/**
+ * tar_get_file - fetch a remote file to the local archive
+ * @full_dos_path: path to the file to fetch
+ * @finfo: attributes of the file to fetch
+ */
+static int tar_get_file(struct tar *t,
+ const char *full_dos_path,
+ struct file_info *finfo)
+{
+ extern struct cli_state *cli;
+ NTSTATUS status;
+ struct archive_entry *entry;
+ char *full_unix_path;
+ char buf[TAR_CLI_READ_SIZE];
+ size_t len;
+ uint64_t off = 0;
+ uint16_t remote_fd = (uint16_t)-1;
+ int err = 0, r;
+ const bool isdir = finfo->attr & FILE_ATTRIBUTE_DIRECTORY;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ DBG(5, ("+++ %s\n", full_dos_path));
+
+ t->total_size += finfo->size;
+
+ if (t->mode.dry) {
+ goto out;
+ }
+
+ if (t->mode.reset) {
+ /* ignore return value: server might not store DOS attributes */
+ set_remote_attr(full_dos_path, FILE_ATTRIBUTE_ARCHIVE, ATTR_UNSET);
+ }
+
+ full_unix_path = talloc_asprintf(ctx, ".%s", full_dos_path);
+ if (full_unix_path == NULL) {
+ err = 1;
+ goto out;
+ }
+ string_replace(full_unix_path, '\\', '/');
+ entry = archive_entry_new();
+ archive_entry_copy_pathname(entry, full_unix_path);
+ archive_entry_set_filetype(entry, isdir ? AE_IFDIR : AE_IFREG);
+ archive_entry_set_atime(entry,
+ finfo->atime_ts.tv_sec,
+ finfo->atime_ts.tv_nsec);
+ archive_entry_set_mtime(entry,
+ finfo->mtime_ts.tv_sec,
+ finfo->mtime_ts.tv_nsec);
+ archive_entry_set_ctime(entry,
+ finfo->ctime_ts.tv_sec,
+ finfo->ctime_ts.tv_nsec);
+ archive_entry_set_perm(entry, isdir ? 0755 : 0644);
+ /*
+ * check if we can safely cast unsigned file size to libarchive
+ * signed size. Very unlikely problem (>9 exabyte file)
+ */
+ if (finfo->size > INT64_MAX) {
+ d_printf("Remote file %s too big\n", full_dos_path);
+ goto out_entry;
+ }
+
+ archive_entry_set_size(entry, (int64_t)finfo->size);
+
+ if (isdir) {
+ /* It's a directory just write a header */
+ r = archive_write_header(t->archive, entry);
+ if (r != ARCHIVE_OK) {
+ d_printf("Fatal: %s\n", archive_error_string(t->archive));
+ err = 1;
+ }
+ if (t->mode.verbose) {
+ d_printf("a %s\\\n", full_dos_path);
+ }
+ DBG(5, ("get_file skip dir %s\n", full_dos_path));
+ goto out_entry;
+ }
+
+ status = cli_open(cli, full_dos_path, O_RDONLY, DENY_NONE, &remote_fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s opening remote file %s\n",
+ nt_errstr(status), full_dos_path);
+ goto out_entry;
+ }
+
+ /* don't make tar file entry until after the file is open */
+ r = archive_write_header(t->archive, entry);
+ if (r != ARCHIVE_OK) {
+ d_printf("Fatal: %s\n", archive_error_string(t->archive));
+ err = 1;
+ goto out_entry;
+ }
+
+ if (t->mode.verbose) {
+ d_printf("a %s\n", full_dos_path);
+ }
+
+ do {
+ status = cli_read(cli, remote_fd, buf, off, sizeof(buf), &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error reading file %s : %s\n",
+ full_dos_path, nt_errstr(status));
+ err = 1;
+ goto out_close;
+ }
+
+ off += len;
+
+ r = archive_write_data(t->archive, buf, len);
+ if (r < 0) {
+ d_printf("Fatal: %s\n", archive_error_string(t->archive));
+ err = 1;
+ goto out_close;
+ }
+
+ } while (off < finfo->size);
+ t->numfile++;
+
+out_close:
+ cli_close(cli, remote_fd);
+
+out_entry:
+ archive_entry_free(entry);
+
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * tar_extract - open archive and send files.
+ */
+static int tar_extract(struct tar *t)
+{
+ int err = 0;
+ int r;
+ struct archive_entry *entry;
+ const size_t bsize = t->mode.blocksize * TAR_BLOCK_UNIT;
+ int rc;
+
+ t->archive = archive_read_new();
+ archive_read_support_format_all(t->archive);
+#ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL
+ archive_read_support_filter_all(t->archive);
+#endif
+
+ if (strequal(t->tar_path, "-")) {
+ r = archive_read_open_fd(t->archive, STDIN_FILENO, bsize);
+ } else {
+ r = archive_read_open_filename(t->archive, t->tar_path, bsize);
+ }
+
+ if (r != ARCHIVE_OK) {
+ d_printf("Can't open %s : %s\n", t->tar_path,
+ archive_error_string(t->archive));
+ err = 1;
+ goto out;
+ }
+
+ for (;;) {
+ NTSTATUS status;
+ bool skip = false;
+ r = archive_read_next_header(t->archive, &entry);
+ if (r == ARCHIVE_EOF) {
+ break;
+ }
+ if (r == ARCHIVE_WARN) {
+ d_printf("Warning: %s\n", archive_error_string(t->archive));
+ }
+ if (r == ARCHIVE_FATAL) {
+ d_printf("Fatal: %s\n", archive_error_string(t->archive));
+ err = 1;
+ goto out;
+ }
+
+ status = tar_extract_skip_path(t, entry, &skip);
+ if (!NT_STATUS_IS_OK(status)) {
+ err = 1;
+ goto out;
+ }
+ if (skip) {
+ DBG(5, ("--- %s\n", archive_entry_pathname(entry)));
+ continue;
+ }
+ DBG(5, ("+++ %s\n", archive_entry_pathname(entry)));
+
+ if (t->mode.verbose) {
+ d_printf("x %s\n", archive_entry_pathname(entry));
+ }
+
+ rc = tar_send_file(t, entry);
+ if (rc != 0) {
+ err = 1;
+ goto out;
+ }
+ }
+
+out:
+#ifdef HAVE_ARCHIVE_READ_FREE
+ r = archive_read_free(t->archive);
+#else
+ r = archive_read_finish(t->archive);
+#endif
+ if (r != ARCHIVE_OK) {
+ d_printf("Can't close %s : %s\n", t->tar_path,
+ archive_error_string(t->archive));
+ err = 1;
+ }
+ return err;
+}
+
+/**
+ * tar_send_file - send @entry to the remote server
+ * @entry: current archive entry
+ *
+ * Handle the creation of the parent directories and transfer the
+ * entry to a new remote file.
+ */
+static int tar_send_file(struct tar *t, struct archive_entry *entry)
+{
+ extern struct cli_state *cli;
+ char *dos_path;
+ char *full_path;
+ NTSTATUS status;
+ uint16_t remote_fd = (uint16_t) -1;
+ int err = 0;
+ int flags = O_RDWR | O_CREAT | O_TRUNC;
+ mode_t filetype = archive_entry_filetype(entry);
+ mode_t mode = archive_entry_mode(entry);
+ time_t mtime = archive_entry_mtime(entry);
+ int rc;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ dos_path = talloc_strdup(ctx, archive_entry_pathname(entry));
+ if (dos_path == NULL) {
+ err = 1;
+ goto out;
+ }
+ fix_unix_path(dos_path, true);
+
+ full_path = talloc_strdup(ctx, client_get_cur_dir());
+ if (full_path == NULL) {
+ err = 1;
+ goto out;
+ }
+ full_path = talloc_strdup_append(full_path, dos_path);
+ if (full_path == NULL) {
+ err = 1;
+ goto out;
+ }
+ full_path = client_clean_name(ctx, full_path);
+ if (full_path == NULL) {
+ err = 1;
+ goto out;
+ }
+
+ if (filetype != AE_IFREG && filetype != AE_IFDIR) {
+ d_printf("Skipping non-dir & non-regular file %s\n", full_path);
+ goto out;
+ }
+
+ rc = make_remote_path(full_path);
+ if (rc != 0) {
+ err = 1;
+ goto out;
+ }
+
+ if (filetype == AE_IFDIR) {
+ goto out;
+ }
+
+ status = cli_open(cli, full_path, flags, DENY_NONE, &remote_fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error opening remote file %s: %s\n",
+ full_path, nt_errstr(status));
+ err = 1;
+ goto out;
+ }
+
+ for (;;) {
+ const void *buf;
+ size_t len;
+ off_t off;
+ int r;
+
+ r = archive_read_data_block(t->archive, &buf, &len, &off);
+ if (r == ARCHIVE_EOF) {
+ break;
+ }
+ if (r == ARCHIVE_WARN) {
+ d_printf("Warning: %s\n", archive_error_string(t->archive));
+ }
+ if (r == ARCHIVE_FATAL) {
+ d_printf("Fatal: %s\n", archive_error_string(t->archive));
+ err = 1;
+ goto close_out;
+ }
+
+ status = cli_writeall(cli, remote_fd, 0, buf, off, len, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error writing remote file %s: %s\n",
+ full_path, nt_errstr(status));
+ err = 1;
+ goto close_out;
+ }
+ }
+
+close_out:
+ status = cli_close(cli, remote_fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error closing remote file %s: %s\n",
+ full_path, nt_errstr(status));
+ err = 1;
+ }
+
+ status = cli_setatr(cli, full_path, mode, mtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error setting attributes on remote file %s: %s\n",
+ full_path, nt_errstr(status));
+ err = 1;
+ }
+
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * tar_add_selection_path - add a path to the path list
+ * @path: path to add
+ */
+static NTSTATUS tar_add_selection_path(struct tar *t, const char *path)
+{
+ const char **list;
+ TALLOC_CTX *ctx = t->talloc_ctx;
+ if (!t->path_list) {
+ t->path_list = str_list_make_empty(ctx);
+ if (t->path_list == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ t->path_list_size = 0;
+ }
+
+ /* cast to silence gcc const-qual warning */
+ list = str_list_add((void *)t->path_list, path);
+ if (list == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ t->path_list = discard_const_p(char *, list);
+ t->path_list_size++;
+ fix_unix_path(t->path_list[t->path_list_size - 1], true);
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * tar_set_blocksize - set block size in TAR_BLOCK_UNIT
+ */
+static int tar_set_blocksize(struct tar *t, int size)
+{
+ if (size <= 0 || size > TAR_MAX_BLOCK_SIZE) {
+ return 1;
+ }
+
+ t->mode.blocksize = size;
+
+ return 0;
+}
+
+/**
+ * tar_set_newer_than - set date threshold of saved files
+ * @filename: local path to a file
+ *
+ * Only files newer than the modification time of @filename will be
+ * saved.
+ *
+ * Note: this function set the global variable newer_than from
+ * client.c. Thus the time is not a field of the tar structure. See
+ * cmd_newer() to change its value from an interactive session.
+ */
+static int tar_set_newer_than(struct tar *t, const char *filename)
+{
+ extern time_t newer_than;
+ SMB_STRUCT_STAT stbuf;
+ int rc;
+
+ rc = sys_stat(filename, &stbuf, false);
+ if (rc != 0) {
+ d_printf("Error setting newer-than time\n");
+ return 1;
+ }
+
+ newer_than = convert_timespec_to_time_t(stbuf.st_ex_mtime);
+ DBG(1, ("Getting files newer than %s", time_to_asc(newer_than)));
+ return 0;
+}
+
+/**
+ * tar_read_inclusion_file - set path list from file
+ * @filename: path to the list file
+ *
+ * Read and add each line of @filename to the path list.
+ */
+static int tar_read_inclusion_file(struct tar *t, const char* filename)
+{
+ char *line;
+ int err = 0;
+ int fd = -1;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ d_printf("Can't open inclusion file '%s': %s\n", filename, strerror(errno));
+ err = 1;
+ goto out;
+ }
+
+ for (line = afdgets(fd, ctx, 0);
+ line != NULL;
+ line = afdgets(fd, ctx, 0)) {
+ NTSTATUS status;
+ status = tar_add_selection_path(t, line);
+ if (!NT_STATUS_IS_OK(status)) {
+ err = 1;
+ goto out;
+ }
+ }
+
+out:
+ if (fd != -1) {
+ close(fd);
+ }
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * tar_path_in_list - check whether @path is in the path list
+ * @path: path to find
+ * @reverse: when true also try to find path list element in @path
+ * @_is_in_list: set if @path is in the path list
+ *
+ * Look at each path of the path list and set @_is_in_list if @path is a
+ * subpath of one of them.
+ *
+ * If you want /path to be in the path list (path/a/, path/b/) set
+ * @reverse to true to try to match the other way around.
+ */
+static NTSTATUS tar_path_in_list(struct tar *t, const char *path,
+ bool reverse, bool *_is_in_list)
+{
+ int i;
+ const char *p;
+ const char *pattern;
+
+ if (path == NULL || path[0] == '\0') {
+ *_is_in_list = false;
+ return NT_STATUS_OK;
+ }
+
+ p = skip_useless_char_in_path(path);
+
+ for (i = 0; i < t->path_list_size; i++) {
+ bool is_in_list;
+ NTSTATUS status;
+
+ pattern = skip_useless_char_in_path(t->path_list[i]);
+ status = is_subpath(p, pattern, &is_in_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (reverse && !is_in_list) {
+ status = is_subpath(pattern, p, &is_in_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ if (is_in_list) {
+ *_is_in_list = true;
+ return NT_STATUS_OK;
+ }
+ }
+
+ *_is_in_list = false;
+ return NT_STATUS_OK;
+}
+
+/**
+ * tar_extract_skip_path - check if @entry should be skipped
+ * @entry: current tar entry
+ * @_skip: set true if path should be skipped, otherwise false
+ *
+ * Skip predicate for tar extraction (archive to server) only.
+ */
+static NTSTATUS tar_extract_skip_path(struct tar *t,
+ struct archive_entry *entry,
+ bool *_skip)
+{
+ const char *fullpath = archive_entry_pathname(entry);
+ bool in = true;
+
+ if (t->path_list_size <= 0) {
+ *_skip = false;
+ return NT_STATUS_OK;
+ }
+
+ if (t->mode.regex) {
+ in = mask_match_list(fullpath, t->path_list, t->path_list_size, true);
+ } else {
+ NTSTATUS status = tar_path_in_list(t, fullpath, false, &in);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (t->mode.selection == TAR_EXCLUDE) {
+ *_skip = in;
+ } else {
+ *_skip = !in;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * tar_create_skip_path - check if @fullpath should be skipped
+ * @fullpath: full remote path of the current file
+ * @finfo: remote file attributes
+ * @_skip: returned skip not
+ *
+ * Skip predicate for tar creation (server to archive) only.
+ */
+static NTSTATUS tar_create_skip_path(struct tar *t,
+ const char *fullpath,
+ const struct file_info *finfo,
+ bool *_skip)
+{
+ /* syntaxic sugar */
+ const mode_t mode = finfo->attr;
+ const bool isdir = mode & FILE_ATTRIBUTE_DIRECTORY;
+ const bool exclude = t->mode.selection == TAR_EXCLUDE;
+ bool in = true;
+
+ if (!isdir) {
+
+ /* 1. if we don't want X and we have X, skip */
+ if (!t->mode.system && (mode & FILE_ATTRIBUTE_SYSTEM)) {
+ *_skip = true;
+ return NT_STATUS_OK;
+ }
+
+ if (!t->mode.hidden && (mode & FILE_ATTRIBUTE_HIDDEN)) {
+ *_skip = true;
+ return NT_STATUS_OK;
+ }
+
+ /* 2. if we only want archive and it's not, skip */
+
+ if (t->mode.incremental && !(mode & FILE_ATTRIBUTE_ARCHIVE)) {
+ *_skip = true;
+ return NT_STATUS_OK;
+ }
+ }
+
+ /* 3. is it in the selection list? */
+
+ /*
+ * tar_create_from_list() use the include list as a starting
+ * point, no need to check
+ */
+ if (!exclude) {
+ *_skip = false;
+ return NT_STATUS_OK;
+ }
+
+ /* we are now in exclude mode */
+
+ /* no matter the selection, no list => include everything */
+ if (t->path_list_size <= 0) {
+ *_skip = false;
+ return NT_STATUS_OK;
+ }
+
+ if (t->mode.regex) {
+ in = mask_match_list(fullpath, t->path_list, t->path_list_size, true);
+ } else {
+ bool reverse = isdir && !exclude;
+ NTSTATUS status = tar_path_in_list(t, fullpath, reverse, &in);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ *_skip = in;
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * tar_to_process - return true if @t is ready to be processed
+ *
+ * @t is ready if it properly parsed command line arguments.
+ */
+bool tar_to_process(struct tar *t)
+{
+ if (t == NULL) {
+ DBG_WARNING("Invalid tar context\n");
+ return false;
+ }
+ return t->to_process;
+}
+
+/**
+ * skip_useless_char_in_path - skip leading slashes/dots
+ *
+ * Skip leading slashes, backslashes and dot-slashes.
+ */
+static const char* skip_useless_char_in_path(const char *p)
+{
+ while (p) {
+ if (*p == '/' || *p == '\\') {
+ p++;
+ }
+ else if (p[0] == '.' && (p[1] == '/' || p[1] == '\\')) {
+ p += 2;
+ }
+ else
+ return p;
+ }
+ return p;
+}
+
+/**
+ * is_subpath - check if the path @sub is a subpath of @full.
+ * @sub: path to test
+ * @full: container path
+ * @_subpath_match: set true if @sub is a subpath of @full, otherwise false
+ *
+ * String comparison is case-insensitive.
+ */
+static NTSTATUS is_subpath(const char *sub, const char *full,
+ bool *_subpath_match)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int len = 0;
+ char *f, *s;
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ f = strlower_talloc(tmp_ctx, full);
+ if (f == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out_ctx_free;
+ }
+ string_replace(f, '\\', '/');
+ s = strlower_talloc(tmp_ctx, sub);
+ if (s == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out_ctx_free;
+ }
+ string_replace(s, '\\', '/');
+
+ /* find the point where sub and full diverge */
+ while ((*f != '\0') && (*s != '\0') && (*f == *s)) {
+ f++;
+ s++;
+ len++;
+ }
+
+ if ((*f == '\0') && (*s == '\0')) {
+ *_subpath_match = true; /* sub and full match */
+ goto out_ctx_free;
+ }
+
+ if ((*f == '\0') && (len > 0) && (*(f - 1) == '/')) {
+ /* sub diverges from full at path separator */
+ *_subpath_match = true;
+ goto out_ctx_free;
+ }
+
+ if ((*s == '\0') && (strcmp(f, "/") == 0)) {
+ /* full diverges from sub with trailing slash only */
+ *_subpath_match = true;
+ goto out_ctx_free;
+ }
+
+ if ((*s == '/') && (*f == '\0')) {
+ /* sub diverges from full with extra path component */
+ *_subpath_match = true;
+ goto out_ctx_free;
+ }
+ *_subpath_match = false;
+
+out_ctx_free:
+ talloc_free(tmp_ctx);
+out:
+ return status;
+}
+
+
+/**
+ * make_remote_path - recursively make remote dirs
+ * @full_path: full hierarchy to create
+ *
+ * Create @full_path and each parent directories as needed.
+ */
+static int make_remote_path(const char *full_path)
+{
+ extern struct cli_state *cli;
+ char *path;
+ char *subpath;
+ char *state;
+ char *last_backslash;
+ char *p;
+ int len;
+ NTSTATUS status;
+ int err = 0;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ return 1;
+ }
+
+ subpath = talloc_strdup(ctx, full_path);
+ if (subpath == NULL) {
+ err = 1;
+ goto out;
+ }
+ path = talloc_strdup(ctx, full_path);
+ if (path == NULL) {
+ err = 1;
+ goto out;
+ }
+ len = talloc_get_size(path) - 1;
+
+ last_backslash = strrchr_m(path, '\\');
+ if (last_backslash == NULL) {
+ goto out;
+ }
+
+ *last_backslash = 0;
+
+ subpath[0] = 0;
+ p = strtok_r(path, "\\", &state);
+
+ while (p != NULL) {
+ strlcat(subpath, p, len);
+ status = cli_chkpath(cli, subpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = cli_mkdir(cli, subpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Can't mkdir %s: %s\n", subpath, nt_errstr(status));
+ err = 1;
+ goto out;
+ }
+ DBG(3, ("mkdir %s\n", subpath));
+ }
+
+ strlcat(subpath, "\\", len);
+ p = strtok_r(NULL, "/\\", &state);
+
+ }
+
+out:
+ talloc_free(ctx);
+ return err;
+}
+
+/**
+ * tar_reset_mem_context - reset talloc context associated with @t
+ *
+ * At the start of the program the context is NULL so a new one is
+ * allocated. On the following runs (interactive session only), simply
+ * free the children.
+ */
+static TALLOC_CTX *tar_reset_mem_context(struct tar *t)
+{
+ tar_free_mem_context(t);
+ t->talloc_ctx = talloc_new(NULL);
+ return t->talloc_ctx;
+}
+
+/**
+ * tar_free_mem_context - free talloc context associated with @t
+ */
+static void tar_free_mem_context(struct tar *t)
+{
+ if (t->talloc_ctx) {
+ talloc_free(t->talloc_ctx);
+ t->talloc_ctx = NULL;
+ t->path_list_size = 0;
+ t->path_list = NULL;
+ t->tar_path = NULL;
+ }
+}
+
+#define XSET(v) [v] = #v
+#define XTABLE(v, t) DBG(2, ("DUMP:%-20.20s = %s\n", #v, t[v]))
+#define XBOOL(v) DBG(2, ("DUMP:%-20.20s = %d\n", #v, v ? 1 : 0))
+#define XSTR(v) DBG(2, ("DUMP:%-20.20s = %s\n", #v, v ? v : "NULL"))
+#define XINT(v) DBG(2, ("DUMP:%-20.20s = %d\n", #v, v))
+#define XUINT64(v) DBG(2, ("DUMP:%-20.20s = %" PRIu64 "\n", #v, v))
+
+/**
+ * tar_dump - dump tar structure on stdout
+ */
+static void tar_dump(struct tar *t)
+{
+ int i;
+ const char* op[] = {
+ XSET(TAR_NO_OPERATION),
+ XSET(TAR_CREATE),
+ XSET(TAR_EXTRACT),
+ };
+
+ const char* sel[] = {
+ XSET(TAR_NO_SELECTION),
+ XSET(TAR_INCLUDE),
+ XSET(TAR_EXCLUDE),
+ };
+
+ XBOOL(t->to_process);
+ XTABLE(t->mode.operation, op);
+ XTABLE(t->mode.selection, sel);
+ XINT(t->mode.blocksize);
+ XBOOL(t->mode.hidden);
+ XBOOL(t->mode.system);
+ XBOOL(t->mode.incremental);
+ XBOOL(t->mode.reset);
+ XBOOL(t->mode.dry);
+ XBOOL(t->mode.verbose);
+ XUINT64(t->total_size);
+ XSTR(t->tar_path);
+ XINT(t->path_list_size);
+
+ for (i = 0; t->path_list && t->path_list[i]; i++) {
+ DBG(2, ("DUMP: t->path_list[%2d] = %s\n", i, t->path_list[i]));
+ }
+
+ DBG(2, ("DUMP:t->path_list @ %p (%d elem)\n", t->path_list, i));
+}
+#undef XSET
+#undef XTABLE
+#undef XBOOL
+#undef XSTR
+#undef XINT
+
+/**
+ * max_token - return upper limit for the number of token in @str
+ *
+ * The result is not exact, the actual number of token might be less
+ * than what is returned.
+ */
+static int max_token(const char *str)
+{
+ const char *s;
+ int nb = 0;
+
+ if (str == NULL) {
+ return 0;
+ }
+
+ s = str;
+ while (s[0] != '\0') {
+ if (isspace((int)s[0])) {
+ nb++;
+ }
+ s++;
+ }
+
+ nb++;
+
+ return nb;
+}
+
+/**
+ * fix_unix_path - convert @path to a DOS path
+ * @path: path to convert
+ * @removeprefix: if true, remove leading ./ or /.
+ */
+static char *fix_unix_path(char *path, bool do_remove_prefix)
+{
+ char *from = path, *to = path;
+
+ if (path == NULL || path[0] == '\0') {
+ return path;
+ }
+
+ /* remove prefix:
+ * ./path => path
+ * /path => path
+ */
+ if (do_remove_prefix) {
+ /* /path */
+ if (path[0] == '/' || path[0] == '\\') {
+ from += 1;
+ }
+
+ /* ./path */
+ if (path[1] != '\0' && path[0] == '.' && (path[1] == '/' || path[1] == '\\')) {
+ from += 2;
+ }
+ }
+
+ /* replace / with \ */
+ while (from[0] != '\0') {
+ if (from[0] == '/') {
+ to[0] = '\\';
+ } else {
+ to[0] = from[0];
+ }
+
+ from++;
+ to++;
+ }
+ to[0] = '\0';
+
+ return path;
+}
+
+/**
+ * path_base_name - return @path basename
+ *
+ * If @path doesn't contain any directory separator return NULL.
+ */
+static NTSTATUS path_base_name(TALLOC_CTX *ctx, const char *path, char **_base)
+{
+ char *base = NULL;
+ int last = -1;
+ int i;
+
+ for (i = 0; path[i]; i++) {
+ if (path[i] == '\\' || path[i] == '/') {
+ last = i;
+ }
+ }
+
+ if (last >= 0) {
+ base = talloc_strdup(ctx, path);
+ if (base == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ base[last] = 0;
+ }
+
+ *_base = base;
+ return NT_STATUS_OK;
+}
+
+#else
+
+#define NOT_IMPLEMENTED DEBUG(0, ("tar mode not compiled. build used --without-libarchive\n"))
+
+int cmd_block(void)
+{
+ NOT_IMPLEMENTED;
+ return 1;
+}
+
+int cmd_tarmode(void)
+{
+ NOT_IMPLEMENTED;
+ return 1;
+}
+
+int cmd_tar(void)
+{
+ NOT_IMPLEMENTED;
+ return 1;
+}
+
+int tar_process(struct tar* tar)
+{
+ NOT_IMPLEMENTED;
+ return 1;
+}
+
+int tar_parse_args(struct tar *tar, const char *flag, const char **val, int valsize)
+{
+ NOT_IMPLEMENTED;
+ return 1;
+}
+
+bool tar_to_process(struct tar *tar)
+{
+ return false;
+}
+
+struct tar *tar_get_ctx()
+{
+ return NULL;
+}
+
+#endif
diff --git a/source3/client/clitar_proto.h b/source3/client/clitar_proto.h
new file mode 100644
index 0000000..6520476
--- /dev/null
+++ b/source3/client/clitar_proto.h
@@ -0,0 +1,34 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Tar backup command extension
+ * Copyright (C) Aurélien Aptel 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CLITAR_PROTO_H_
+#define _CLITAR_PROTO_H_
+
+struct tar;
+
+int cmd_block(void);
+int cmd_tarmode(void);
+int cmd_setmode(void);
+int cmd_tar(void);
+int tar_process(struct tar* tar);
+int tar_parse_args(struct tar *tar, const char *flag, const char **val, int valsize);
+bool tar_to_process(struct tar *tar);
+struct tar *tar_get_ctx(void);
+
+#endif /* _CLITAR_PROTO_H_ */
diff --git a/source3/client/dnsbrowse.c b/source3/client/dnsbrowse.c
new file mode 100644
index 0000000..be6eb88
--- /dev/null
+++ b/source3/client/dnsbrowse.c
@@ -0,0 +1,216 @@
+/*
+ Unix SMB/CIFS implementation.
+ DNS-SD browse client
+ Copyright (C) Rishi Srivatsavai 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "client/client_proto.h"
+
+#ifdef WITH_DNSSD_SUPPORT
+
+#include <dns_sd.h>
+#include "system/select.h"
+
+/* Holds service instances found during DNS browse */
+struct mdns_smbsrv_result
+{
+ char *serviceName;
+ char *regType;
+ char *domain;
+ uint32_t ifIndex;
+ struct mdns_smbsrv_result *nextResult;
+};
+
+/* Maintains state during DNS browse */
+struct mdns_browse_state
+{
+ struct mdns_smbsrv_result *listhead; /* Browse result list head */
+ int browseDone;
+
+};
+
+
+static void
+do_smb_resolve_reply (DNSServiceRef sdRef, DNSServiceFlags flags,
+ uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+ const char *fullname, const char *hosttarget, uint16_t port,
+ uint16_t txtLen, const unsigned char *txtRecord, void *context)
+{
+ printf("SMB service available on %s port %u\n",
+ hosttarget, ntohs(port));
+}
+
+
+static void do_smb_resolve(struct mdns_smbsrv_result *browsesrv)
+{
+ DNSServiceRef mdns_conn_sdref = NULL;
+ int mdnsfd;
+ int fdsetsz;
+ int ret;
+ struct timeval tv;
+ DNSServiceErrorType err;
+
+ TALLOC_CTX * ctx = talloc_tos();
+
+ err = DNSServiceResolve(&mdns_conn_sdref, 0 /* flags */,
+ browsesrv->ifIndex,
+ browsesrv->serviceName, browsesrv->regType, browsesrv->domain,
+ do_smb_resolve_reply, NULL);
+
+ if (err != kDNSServiceErr_NoError) {
+ return;
+ }
+
+ mdnsfd = DNSServiceRefSockFD(mdns_conn_sdref);
+ for (;;) {
+ int revents;
+
+ ret = poll_one_fd(mdnsfd, POLLIN|POLLHUP, 1000, &revents);
+ if (ret <= 0 && errno != EINTR) {
+ break;
+ }
+
+ if (revents & (POLLIN|POLLHUP|POLLERR)) {
+ /* Invoke callback function */
+ DNSServiceProcessResult(mdns_conn_sdref);
+ break;
+ }
+ }
+
+ TALLOC_FREE(fdset);
+ DNSServiceRefDeallocate(mdns_conn_sdref);
+}
+
+
+static void
+do_smb_browse_reply(DNSServiceRef sdRef, DNSServiceFlags flags,
+ uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+ const char *serviceName, const char *regtype,
+ const char *replyDomain, void *context)
+{
+ struct mdns_browse_state *bstatep = (struct mdns_browse_state *)context;
+ struct mdns_smbsrv_result *bresult;
+
+ if (bstatep == NULL) {
+ return;
+ }
+
+ if (errorCode != kDNSServiceErr_NoError) {
+ bstatep->browseDone = 1;
+ return;
+ }
+
+ if (flags & kDNSServiceFlagsMoreComing) {
+ bstatep->browseDone = 0;
+ } else {
+ bstatep->browseDone = 1;
+ }
+
+ if (!(flags & kDNSServiceFlagsAdd)) {
+ return;
+ }
+
+ bresult = talloc_array(talloc_tos(), struct mdns_smbsrv_result, 1);
+ if (bresult == NULL) {
+ return;
+ }
+
+ if (bstatep->listhead != NULL) {
+ bresult->nextResult = bstatep->listhead;
+ }
+
+ bresult->serviceName = talloc_strdup(talloc_tos(), serviceName);
+ bresult->regType = talloc_strdup(talloc_tos(), regtype);
+ bresult->domain = talloc_strdup(talloc_tos(), replyDomain);
+ bresult->ifIndex = interfaceIndex;
+ bstatep->listhead = bresult;
+}
+
+int do_smb_browse(void)
+{
+ int mdnsfd;
+ int fdsetsz;
+ int ret;
+ struct mdns_browse_state bstate;
+ struct mdns_smbsrv_result *resptr;
+ struct timeval tv;
+ DNSServiceRef mdns_conn_sdref = NULL;
+ DNSServiceErrorType err;
+
+ TALLOC_CTX * ctx = talloc_stackframe();
+
+ ZERO_STRUCT(bstate);
+
+ err = DNSServiceBrowse(&mdns_conn_sdref, 0, 0, "_smb._tcp", "",
+ do_smb_browse_reply, &bstate);
+
+ if (err != kDNSServiceErr_NoError) {
+ d_printf("Error connecting to the Multicast DNS daemon\n");
+ TALLOC_FREE(ctx);
+ return 1;
+ }
+
+ mdnsfd = DNSServiceRefSockFD(mdns_conn_sdref);
+ for (;;) {
+ int revents;
+
+ ret = poll_one_fd(mdnsfd, POLLIN|POLLHUP, 1000, &revents);
+ if (ret <= 0 && errno != EINTR) {
+ break;
+ }
+
+ if (revents & (POLLIN|POLLHUP|POLLERR)) {
+ /* Invoke callback function */
+ if (DNSServiceProcessResult(mdns_conn_sdref)) {
+ break;
+ }
+ if (bstate.browseDone) {
+ break;
+ }
+ }
+ }
+
+ DNSServiceRefDeallocate(mdns_conn_sdref);
+
+ if (bstate.listhead != NULL) {
+ resptr = bstate.listhead;
+ while (resptr != NULL) {
+ struct mdns_smbsrv_result *oldresptr;
+ oldresptr = resptr;
+
+ /* Resolve smb service instance */
+ do_smb_resolve(resptr);
+
+ resptr = resptr->nextResult;
+ }
+ }
+
+ TALLOC_FREE(ctx);
+ return 0;
+}
+
+#else /* WITH_DNSSD_SUPPORT */
+
+int do_smb_browse(void)
+{
+ d_printf("DNS-SD browsing is not supported on this platform\n");
+ return 1;
+}
+
+#endif /* WITH_DNSSD_SUPPORT */
+
+
diff --git a/source3/client/smbspool.c b/source3/client/smbspool.c
new file mode 100644
index 0000000..b3da875
--- /dev/null
+++ b/source3/client/smbspool.c
@@ -0,0 +1,888 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB backend for the Common UNIX Printing System ("CUPS")
+
+ Copyright (C) Michael R Sweet 1999
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Rodrigo Fernandez-Vizarra 2005
+ Copyright (C) James Peach 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/passwd.h"
+#include "system/kerberos.h"
+#include "libsmb/libsmb.h"
+#include "lib/param/param.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+
+/*
+ * Starting with CUPS 1.3, Kerberos support is provided by cupsd including
+ * the forwarding of user credentials via the authenticated session between
+ * user and server and the KRB5CCNAME environment variable which will point
+ * to a temporary file or an in-memory representation depending on the version
+ * of Kerberos you use. As a result, all of the ticket code that used to
+ * live here has been removed, and we depend on the user session (if you
+ * run smbspool by hand) or cupsd to provide the necessary Kerberos info.
+ *
+ * Also, the AUTH_USERNAME and AUTH_PASSWORD environment variables provide
+ * for per-job authentication for non-Kerberized printing. We use those
+ * if there is no username and password specified in the device URI.
+ *
+ * Finally, if we have an authentication failure we return exit code 2
+ * which tells CUPS to hold the job for authentication and bug the user
+ * to get the necessary credentials.
+ */
+
+#define MAX_RETRY_CONNECT 3
+
+
+/*
+ * Globals...
+ */
+
+
+
+/*
+ * Local functions...
+ */
+
+static int get_exit_code(NTSTATUS nt_status);
+static void list_devices(void);
+static NTSTATUS
+smb_connect(struct cli_state **output_cli,
+ const char *workgroup,
+ const char *server,
+ const int port,
+ const char *share,
+ const char *username,
+ const char *password,
+ const char *jobusername);
+static int smb_print(struct cli_state *, const char *, FILE *);
+static char *uri_unescape_alloc(const char *);
+#if 0
+static bool smb_encrypt;
+#endif
+
+static const char *auth_info_required;
+
+/*
+ * 'main()' - Main entry for SMB backend.
+ */
+
+int /* O - Exit status */
+main(int argc, /* I - Number of command-line arguments */
+ char *argv[])
+{ /* I - Command-line arguments */
+ int i; /* Looping var */
+ int copies; /* Number of copies */
+ int port; /* Port number */
+ char uri[1024], /* URI */
+ *sep, /* Pointer to separator */
+ *tmp, *tmp2; /* Temp pointers to do escaping */
+ const char *password = NULL; /* Password */
+ const char *username = NULL; /* Username */
+ char *server, /* Server name */
+ *printer;/* Printer name */
+ const char *workgroup; /* Workgroup */
+ FILE *fp; /* File to print */
+ int status = 1; /* Status of LPD job */
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct cli_state *cli = NULL; /* SMB interface */
+ int tries = 0;
+ const char *dev_uri = NULL;
+ const char *env = NULL;
+ const char *config_file = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *print_user = NULL;
+ const char *print_title = NULL;
+ const char *print_file = NULL;
+ const char *print_copies = NULL;
+ int cmp;
+ int len;
+
+ if (argc == 1) {
+ /*
+ * NEW! In CUPS 1.1 the backends are run with no arguments
+ * to list the available devices. These can be devices
+ * served by this backend or any other backends (i.e. you
+ * can have an SNMP backend that is only used to enumerate
+ * the available network printers... :)
+ */
+
+ list_devices();
+ status = 0;
+ goto done;
+ }
+
+ /*
+ * We need at least 5 options if the DEVICE_URI is passed via an env
+ * variable and printing data comes via stdin.
+ * We don't accept more than 7 options in total, including optional.
+ */
+ if (argc < 5 || argc > 8) {
+ fprintf(stderr,
+"Usage: %s [DEVICE_URI] job-id user title copies options [file]\n"
+" The DEVICE_URI environment variable can also contain the\n"
+" destination printer:\n"
+"\n"
+" smb://[username:password@][workgroup/]server[:port]/printer\n",
+ argv[0]);
+ goto done;
+ }
+
+ /*
+ * Find out if we have the device_uri in the command line.
+ *
+ * If we are started as a CUPS backend argv[0] is normally the
+ * device_uri!
+ */
+ if (argc == 8) {
+ /*
+ * smbspool <uri> <job> <user> <title> <copies> <options> <file>
+ * 0 1 2 3 4 5 6 7
+ */
+
+ dev_uri = argv[1];
+
+ print_user = argv[3];
+ print_title = argv[4];
+ print_copies = argv[5];
+ print_file = argv[7];
+ } else if (argc == 7) {
+ int cmp1;
+ int cmp2;
+
+ /*
+ * <uri> <job> <user> <title> <copies> <options> <file>
+ * smbspool <uri> <job> <user> <title> <copies> <options>
+ * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI
+ */
+ cmp1 = strncmp(argv[0], "smb://", 6);
+ cmp2 = strncmp(argv[1], "smb://", 6);
+
+ if (cmp1 == 0) {
+ /*
+ * <uri> <job> <user> <title> <copies> <options> <file>
+ * 0 1 2 3 4 5 6
+ */
+ dev_uri = argv[0];
+
+ print_user = argv[2];
+ print_title = argv[3];
+ print_copies = argv[4];
+ print_file = argv[6];
+ } else if (cmp2 == 0) {
+ /*
+ * smbspool <uri> <job> <user> <title> <copies> <options>
+ * 0 1 2 3 4 5 6
+ */
+ dev_uri = argv[1];
+
+ print_user = argv[3];
+ print_title = argv[4];
+ print_copies = argv[5];
+ print_file = NULL;
+ } else {
+ /*
+ * smbspool <job> <user> <title> <copies> <options> <file> | DEVICE_URI
+ * 0 1 2 3 4 5 6
+ */
+ print_user = argv[2];
+ print_title = argv[3];
+ print_copies = argv[4];
+ print_file = argv[6];
+ }
+ } else if (argc == 6) {
+ /*
+ * <uri> <job> <user> <title> <copies> <options>
+ * smbspool <job> <user> <title> <copies> <options> | DEVICE_URI
+ * 0 1 2 3 4 5
+ */
+ cmp = strncmp(argv[0], "smb://", 6);
+ if (cmp == 0) {
+ dev_uri = argv[0];
+ }
+
+ print_user = argv[2];
+ print_title = argv[3];
+ print_copies = argv[4];
+ }
+
+ if (print_file != NULL) {
+ char *endp;
+
+ fp = fopen(print_file, "rb");
+ if (fp == NULL) {
+ fprintf(stderr,
+ "ERROR: Unable to open print file: %s",
+ print_file);
+ goto done;
+ }
+
+ copies = strtol(print_copies, &endp, 10);
+ if (print_copies == endp) {
+ perror("ERROR: Unable to determine number of copies");
+ goto done;
+ }
+ } else {
+ fp = stdin;
+ copies = 1;
+ }
+
+ /*
+ * Find the URI ...
+ *
+ * The URI in argv[0] is sanitized to remove username/password, so
+ * use DEVICE_URI if available. Otherwise keep the URI already
+ * discovered in argv.
+ */
+ env = getenv("DEVICE_URI");
+ if (env != NULL && env[0] != '\0') {
+ dev_uri = env;
+ }
+
+ if (dev_uri == NULL) {
+ fprintf(stderr,
+ "ERROR: No valid device URI has been specified\n");
+ goto done;
+ }
+
+ cmp = strncmp(dev_uri, "smb://", 6);
+ if (cmp != 0) {
+ fprintf(stderr,
+ "ERROR: No valid device URI has been specified\n");
+ goto done;
+ }
+ len = snprintf(uri, sizeof(uri), "%s", dev_uri);
+ if (len >= sizeof(uri)) {
+ fprintf(stderr,
+ "ERROR: The URI is too long.\n");
+ goto done;
+ }
+
+ auth_info_required = getenv("AUTH_INFO_REQUIRED");
+ if (auth_info_required == NULL) {
+ auth_info_required = "samba";
+ }
+
+ /*
+ * Extract the destination from the URI...
+ */
+
+ if ((sep = strrchr_m(uri, '@')) != NULL) {
+ tmp = uri + 6;
+ *sep++ = '\0';
+
+ /* username is in tmp */
+
+ server = sep;
+
+ /*
+ * Extract password as needed...
+ */
+
+ if ((tmp2 = strchr_m(tmp, ':')) != NULL) {
+ *tmp2++ = '\0';
+ password = uri_unescape_alloc(tmp2);
+ }
+ username = uri_unescape_alloc(tmp);
+ } else {
+ env = getenv("AUTH_USERNAME");
+ if (env != NULL && strlen(env) > 0) {
+ username = env;
+ }
+
+ env = getenv("AUTH_PASSWORD");
+ if (env != NULL && strlen(env) > 0) {
+ password = env;
+ }
+
+ server = uri + 6;
+ }
+
+ if (password != NULL) {
+ auth_info_required = "username,password";
+ }
+
+ tmp = server;
+
+ if ((sep = strchr_m(tmp, '/')) == NULL) {
+ fputs("ERROR: Bad URI - need printer name!\n", stderr);
+ goto done;
+ }
+
+ *sep++ = '\0';
+ tmp2 = sep;
+
+ if ((sep = strchr_m(tmp2, '/')) != NULL) {
+ /*
+ * Convert to smb://[username:password@]workgroup/server/printer...
+ */
+
+ *sep++ = '\0';
+
+ workgroup = uri_unescape_alloc(tmp);
+ server = uri_unescape_alloc(tmp2);
+ printer = uri_unescape_alloc(sep);
+ } else {
+ workgroup = NULL;
+ server = uri_unescape_alloc(tmp);
+ printer = uri_unescape_alloc(tmp2);
+ }
+
+ if ((sep = strrchr_m(server, ':')) != NULL) {
+ *sep++ = '\0';
+
+ port = atoi(sep);
+ } else {
+ port = 0;
+ }
+
+ /*
+ * Setup the SAMBA server state...
+ */
+
+ setup_logging("smbspool", DEBUG_STDERR);
+
+ smb_init_locale();
+
+ config_file = lp_default_path();
+ if (!lp_load_client(config_file)) {
+ fprintf(stderr,
+ "ERROR: Can't load %s - run testparm to debug it\n",
+ config_file);
+ goto done;
+ }
+
+ if (workgroup == NULL) {
+ workgroup = lp_workgroup();
+ }
+
+ load_interfaces();
+
+ do {
+ nt_status = smb_connect(&cli,
+ workgroup,
+ server,
+ port,
+ printer,
+ username,
+ password,
+ print_user);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ status = get_exit_code(nt_status);
+ if (status == 2) {
+ fprintf(stderr,
+ "DEBUG: Unable to connect to CIFS "
+ "host: %s",
+ nt_errstr(nt_status));
+ goto done;
+ } else if (getenv("CLASS") == NULL) {
+ fprintf(stderr,
+ "ERROR: Unable to connect to CIFS "
+ "host: %s. Will retry in 60 "
+ "seconds...\n",
+ nt_errstr(nt_status));
+ sleep(60);
+ tries++;
+ } else {
+ fprintf(stderr,
+ "ERROR: Unable to connect to CIFS "
+ "host: %s. Trying next printer...\n",
+ nt_errstr(nt_status));
+ goto done;
+ }
+ }
+ } while (!NT_STATUS_IS_OK(nt_status) && (tries < MAX_RETRY_CONNECT));
+
+ if (cli == NULL) {
+ fprintf(stderr, "ERROR: Unable to connect to CIFS host after (tried %d times)\n", tries);
+ goto done;
+ }
+
+ /*
+ * Now that we are connected to the server, ignore SIGTERM so that we
+ * can finish out any page data the driver sends (e.g. to eject the
+ * current page... Only ignore SIGTERM if we are printing data from
+ * stdin (otherwise you can't cancel raw jobs...)
+ */
+
+ if (argc < 7) {
+ CatchSignal(SIGTERM, SIG_IGN);
+ }
+
+ /*
+ * Queue the job...
+ */
+
+ for (i = 0; i < copies; i++) {
+ status = smb_print(cli, print_title, fp);
+ if (status != 0) {
+ break;
+ }
+ }
+
+ cli_shutdown(cli);
+
+ /*
+ * Return the queue status...
+ */
+
+done:
+ gfree_all();
+ TALLOC_FREE(frame);
+ return (status);
+}
+
+
+/*
+ * 'get_exit_code()' - Get the backend exit code based on the current error.
+ */
+
+static int
+get_exit_code(NTSTATUS nt_status)
+{
+ size_t i;
+
+ /* List of NTSTATUS errors that are considered
+ * authentication errors
+ */
+ static const NTSTATUS auth_errors[] =
+ {
+ NT_STATUS_ACCESS_DENIED,
+ NT_STATUS_ACCESS_VIOLATION,
+ NT_STATUS_ACCOUNT_DISABLED,
+ NT_STATUS_ACCOUNT_LOCKED_OUT,
+ NT_STATUS_ACCOUNT_RESTRICTION,
+ NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND,
+ NT_STATUS_INVALID_ACCOUNT_NAME,
+ NT_STATUS_INVALID_COMPUTER_NAME,
+ NT_STATUS_INVALID_LOGON_HOURS,
+ NT_STATUS_INVALID_WORKSTATION,
+ NT_STATUS_LOGON_FAILURE,
+ NT_STATUS_NO_SUCH_USER,
+ NT_STATUS_NO_SUCH_DOMAIN,
+ NT_STATUS_NO_LOGON_SERVERS,
+ NT_STATUS_PASSWORD_EXPIRED,
+ NT_STATUS_PRIVILEGE_NOT_HELD,
+ NT_STATUS_SHARING_VIOLATION,
+ NT_STATUS_WRONG_PASSWORD,
+ };
+
+
+ fprintf(stderr,
+ "DEBUG: get_exit_code(nt_status=%s [%x])\n",
+ nt_errstr(nt_status), NT_STATUS_V(nt_status));
+
+ for (i = 0; i < ARRAY_SIZE(auth_errors); i++) {
+ if (!NT_STATUS_EQUAL(nt_status, auth_errors[i])) {
+ continue;
+ }
+
+ fprintf(stderr, "ATTR: auth-info-required=%s\n", auth_info_required);
+
+ /*
+ * 2 = authentication required...
+ */
+
+ return (2);
+
+ }
+
+ /*
+ * 1 = fail
+ */
+
+ return (1);
+}
+
+
+/*
+ * 'list_devices()' - List the available printers seen on the network...
+ */
+
+static void
+list_devices(void)
+{
+ /*
+ * Eventually, search the local workgroup for available hosts and printers.
+ */
+
+ puts("network smb \"Unknown\" \"Windows Printer via SAMBA\"");
+}
+
+
+static NTSTATUS
+smb_complete_connection(struct cli_state **output_cli,
+ const char *myname,
+ const char *server,
+ int port,
+ const char *username,
+ const char *password,
+ const char *workgroup,
+ const char *share,
+ bool use_kerberos,
+ bool fallback_after_kerberos)
+{
+ struct cli_state *cli; /* New connection */
+ NTSTATUS nt_status;
+ struct cli_credentials *creds = NULL;
+
+ /* Start the SMB connection */
+ nt_status = cli_start_connection(&cli, myname, server, NULL, port,
+ SMB_SIGNING_DEFAULT, 0);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ fprintf(stderr, "ERROR: Connection failed: %s\n", nt_errstr(nt_status));
+ return nt_status;
+ }
+
+ creds = cli_session_creds_init(cli,
+ username,
+ workgroup,
+ NULL, /* realm */
+ password,
+ use_kerberos,
+ fallback_after_kerberos,
+ false, /* use_ccache */
+ false); /* password_is_nt_hash */
+ if (creds == NULL) {
+ fprintf(stderr, "ERROR: cli_session_creds_init failed\n");
+ cli_shutdown(cli);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = cli_session_setup_creds(cli, creds);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ fprintf(stderr, "ERROR: Session setup failed: %s\n", nt_errstr(nt_status));
+
+ cli_shutdown(cli);
+
+ return nt_status;
+ }
+
+ nt_status = cli_tree_connect_creds(cli, share, "?????", creds);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ fprintf(stderr, "ERROR: Tree connect failed (%s)\n",
+ nt_errstr(nt_status));
+
+ cli_shutdown(cli);
+
+ return nt_status;
+ }
+
+ *output_cli = cli;
+ return NT_STATUS_OK;
+}
+
+static bool kerberos_ccache_is_valid(void) {
+ krb5_context ctx;
+ const char *ccache_name = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_error_code code;
+
+ code = smb_krb5_init_context_common(&ctx);
+ if (code != 0) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(code));
+ return false;
+ }
+
+ ccache_name = krb5_cc_default_name(ctx);
+ if (ccache_name == NULL) {
+ DBG_ERR("Failed to get default ccache name\n");
+ krb5_free_context(ctx);
+ return false;
+ }
+
+ code = krb5_cc_resolve(ctx, ccache_name, &ccache);
+ if (code != 0) {
+ DBG_ERR("Failed to resolve ccache name: %s\n",
+ ccache_name);
+ krb5_free_context(ctx);
+ return false;
+ } else {
+ krb5_principal default_princ = NULL;
+ char *princ_name = NULL;
+
+ code = krb5_cc_get_principal(ctx,
+ ccache,
+ &default_princ);
+ if (code != 0) {
+ DBG_ERR("Failed to get default principal from "
+ "ccache: %s\n",
+ ccache_name);
+ krb5_cc_close(ctx, ccache);
+ krb5_free_context(ctx);
+ return false;
+ }
+
+ code = krb5_unparse_name(ctx,
+ default_princ,
+ &princ_name);
+ if (code == 0) {
+ fprintf(stderr,
+ "DEBUG: Try to authenticate as %s\n",
+ princ_name);
+ krb5_free_unparsed_name(ctx, princ_name);
+ }
+ krb5_free_principal(ctx, default_princ);
+ }
+ krb5_cc_close(ctx, ccache);
+ krb5_free_context(ctx);
+
+ return true;
+}
+
+/*
+ * 'smb_connect()' - Return a connection to a server.
+ */
+
+static NTSTATUS
+smb_connect(struct cli_state **output_cli,
+ const char *workgroup, /* I - Workgroup */
+ const char *server, /* I - Server */
+ const int port, /* I - Port */
+ const char *share, /* I - Printer */
+ const char *username, /* I - Username */
+ const char *password, /* I - Password */
+ const char *jobusername) /* I - User who issued the print job */
+{
+ struct cli_state *cli = NULL; /* New connection */
+ char *myname = NULL; /* Client name */
+ struct passwd *pwd;
+ bool use_kerberos = false;
+ bool fallback_after_kerberos = false;
+ const char *user = username;
+ NTSTATUS nt_status;
+
+ /*
+ * Get the names and addresses of the client and server...
+ */
+ myname = get_myname(talloc_tos());
+ if (!myname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+
+ if (strcmp(auth_info_required, "negotiate") == 0) {
+ if (!kerberos_ccache_is_valid()) {
+ fprintf(stderr,
+ "ERROR: No valid Kerberos credential cache found! "
+ "Using smbspool_krb5_wrapper may help.\n");
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ user = jobusername;
+
+ use_kerberos = true;
+ fprintf(stderr,
+ "DEBUG: Try to connect using Kerberos ...\n");
+ } else if (strcmp(auth_info_required, "username,password") == 0) {
+ if (username == NULL) {
+ return NT_STATUS_INVALID_ACCOUNT_NAME;
+ }
+
+ /* Fallback to NTLM */
+ fallback_after_kerberos = true;
+
+ fprintf(stderr,
+ "DEBUG: Try to connect using username/password ...\n");
+ } else if (strcmp(auth_info_required, "none") == 0) {
+ goto anonymous;
+ } else if (strcmp(auth_info_required, "samba") == 0) {
+ if (username != NULL) {
+ fallback_after_kerberos = true;
+ } else if (kerberos_ccache_is_valid()) {
+ auth_info_required = "negotiate";
+
+ user = jobusername;
+ use_kerberos = true;
+ } else {
+ fprintf(stderr,
+ "DEBUG: This backend requires credentials!\n");
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ nt_status = smb_complete_connection(&cli,
+ myname,
+ server,
+ port,
+ user,
+ password,
+ workgroup,
+ share,
+ true, /* try kerberos */
+ fallback_after_kerberos);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ fprintf(stderr, "DEBUG: SMB connection established.\n");
+
+ *output_cli = cli;
+ return NT_STATUS_OK;
+ }
+
+ if (!use_kerberos) {
+ fprintf(stderr, "ERROR: SMB connection failed!\n");
+ return nt_status;
+ }
+
+ /* give a chance for a passwordless NTLMSSP session setup */
+ pwd = getpwuid(geteuid());
+ if (pwd == NULL) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ nt_status = smb_complete_connection(&cli,
+ myname,
+ server,
+ port,
+ pwd->pw_name,
+ "",
+ workgroup,
+ share,
+ false, false);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ fputs("DEBUG: Connected with NTLMSSP...\n", stderr);
+
+ *output_cli = cli;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * last try. Use anonymous authentication
+ */
+
+anonymous:
+ nt_status = smb_complete_connection(&cli,
+ myname,
+ server,
+ port,
+ "",
+ "",
+ workgroup,
+ share,
+ false, false);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ *output_cli = cli;
+ return NT_STATUS_OK;
+ }
+
+ return nt_status;
+}
+
+
+/*
+ * 'smb_print()' - Queue a job for printing using the SMB protocol.
+ */
+
+static int /* O - 0 = success, non-0 = failure */
+smb_print(struct cli_state * cli, /* I - SMB connection */
+ const char *print_title, /* I - Title/job name */
+ FILE * fp)
+{ /* I - File to print */
+ uint16_t fnum; /* File number */
+ int nbytes, /* Number of bytes read */
+ tbytes; /* Total bytes read */
+ char buffer[8192], /* Buffer for copy */
+ *ptr; /* Pointer into title */
+ char title[1024] = {0};
+ int len;
+ NTSTATUS nt_status;
+
+
+ /*
+ * Sanitize the title...
+ */
+ len = snprintf(title, sizeof(title), "%s", print_title);
+ if (len != strlen(print_title)) {
+ return 2;
+ }
+
+ for (ptr = title; *ptr; ptr++) {
+ if (!isalnum((int) *ptr) && !isspace((int) *ptr)) {
+ *ptr = '_';
+ }
+ }
+
+ /*
+ * Open the printer device...
+ */
+
+ nt_status = cli_open(cli, title, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE,
+ &fnum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ fprintf(stderr, "ERROR: %s opening remote spool %s\n",
+ nt_errstr(nt_status), title);
+ return get_exit_code(nt_status);
+ }
+
+ /*
+ * Copy the file to the printer...
+ */
+
+ if (fp != stdin)
+ rewind(fp);
+
+ tbytes = 0;
+
+ while ((nbytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
+ NTSTATUS status;
+
+ status = cli_writeall(cli, fnum, 0, (uint8_t *)buffer,
+ tbytes, nbytes, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ int ret = get_exit_code(status);
+ fprintf(stderr, "ERROR: Error writing spool: %s\n",
+ nt_errstr(status));
+ fprintf(stderr, "DEBUG: Returning status %d...\n",
+ ret);
+ cli_close(cli, fnum);
+
+ return (ret);
+ }
+ tbytes += nbytes;
+ }
+
+ nt_status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ fprintf(stderr, "ERROR: %s closing remote spool %s\n",
+ nt_errstr(nt_status), title);
+ return get_exit_code(nt_status);
+ } else {
+ return (0);
+ }
+}
+
+static char *
+uri_unescape_alloc(const char *uritok)
+{
+ char *ret;
+ char *end;
+ ret = (char *) SMB_STRDUP(uritok);
+ if (!ret) {
+ return NULL;
+ }
+
+ end = rfc1738_unescape(ret);
+ if (end == NULL) {
+ free(ret);
+ return NULL;
+ }
+ return ret;
+}
diff --git a/source3/client/smbspool_krb5_wrapper.c b/source3/client/smbspool_krb5_wrapper.c
new file mode 100644
index 0000000..34553d6
--- /dev/null
+++ b/source3/client/smbspool_krb5_wrapper.c
@@ -0,0 +1,341 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * CUPS printing backend helper to execute smbspool
+ *
+ * Copyright (C) 2010-2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/kerberos.h"
+#include "system/passwd.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cups/backend.h>
+
+#include "dynconfig/dynconfig.h"
+
+#undef calloc
+
+enum cups_smb_dbglvl_e {
+ CUPS_SMB_LOG_DEBUG = 0,
+ CUPS_SMB_LOG_ERROR,
+};
+static void cups_smb_debug(enum cups_smb_dbglvl_e lvl, const char *format, ...)
+ PRINTF_ATTRIBUTE(2, 3);
+
+#define CUPS_SMB_DEBUG(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
+#define CUPS_SMB_ERROR(...) cups_smb_debug(CUPS_SMB_LOG_DEBUG, __VA_ARGS__)
+
+static void cups_smb_debug(enum cups_smb_dbglvl_e lvl, const char *format, ...)
+{
+ const char *prefix = "DEBUG";
+ char buffer[1024];
+ va_list va;
+
+ va_start(va, format);
+ vsnprintf(buffer, sizeof(buffer), format, va);
+ va_end(va);
+
+ switch (lvl) {
+ case CUPS_SMB_LOG_DEBUG:
+ prefix = "DEBUG";
+ break;
+ case CUPS_SMB_LOG_ERROR:
+ prefix = "ERROR";
+ break;
+ }
+
+ fprintf(stderr,
+ "%s: SMBSPOOL_KRB5 - %s\n",
+ prefix,
+ buffer);
+}
+
+static bool kerberos_get_default_ccache(char *ccache_buf, size_t len)
+{
+ krb5_context ctx;
+ const char *ccache_name = NULL;
+ char *full_ccache_name = NULL;
+ krb5_ccache ccache = NULL;
+ krb5_error_code code;
+
+ code = krb5_init_context(&ctx);
+ if (code != 0) {
+ return false;
+ }
+
+ ccache_name = krb5_cc_default_name(ctx);
+ if (ccache_name == NULL) {
+ krb5_free_context(ctx);
+ return false;
+ }
+
+ code = krb5_cc_resolve(ctx, ccache_name, &ccache);
+ if (code != 0) {
+ krb5_free_context(ctx);
+ return false;
+ }
+
+ code = krb5_cc_get_full_name(ctx, ccache, &full_ccache_name);
+ krb5_cc_close(ctx, ccache);
+ if (code != 0) {
+ krb5_free_context(ctx);
+ return false;
+ }
+
+ snprintf(ccache_buf, len, "%s", full_ccache_name);
+
+#ifdef SAMBA4_USES_HEIMDAL
+ free(full_ccache_name);
+#else
+ krb5_free_string(ctx, full_ccache_name);
+#endif
+ krb5_free_context(ctx);
+
+ return true;
+}
+
+/*
+ * This is a helper binary to execute smbspool.
+ *
+ * It needs to be installed or symlinked as:
+ * /usr/lib/cups/backend/smb
+ *
+ * The permissions of the binary need to be set to 0700 so that it is executed
+ * as root. The binary switches to the user which is passed via the environment
+ * variable AUTH_UID, so we can access the kerberos ticket.
+ */
+int main(int argc, char *argv[])
+{
+ char smbspool_cmd[PATH_MAX] = {0};
+ struct passwd *pwd;
+ struct group *g = NULL;
+ char gen_cc[PATH_MAX] = {0};
+ char *env = NULL;
+ char auth_info_required[256] = {0};
+ char device_uri[4096] = {0};
+ uid_t uid = (uid_t)-1;
+ gid_t gid = (gid_t)-1;
+ gid_t groups[1] = { (gid_t)-1 };
+ unsigned long tmp;
+ bool ok;
+ int cmp;
+ int rc;
+
+ env = getenv("DEVICE_URI");
+ if (env != NULL && strlen(env) > 2) {
+ snprintf(device_uri, sizeof(device_uri), "%s", env);
+ }
+
+ /* We must handle the following values of AUTH_INFO_REQUIRED:
+ * none: Anonymous/guest printing
+ * username,password: A username (of the form "username" or "DOMAIN\username")
+ * and password are required
+ * negotiate: Kerberos authentication
+ * NULL (not set): will never happen when called from cupsd
+ * https://www.cups.org/doc/spec-ipp.html#auth-info-required
+ * https://github.com/apple/cups/issues/5674
+ */
+ env = getenv("AUTH_INFO_REQUIRED");
+
+ /* If not set, then just call smbspool. */
+ if (env == NULL || env[0] == 0) {
+ CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED is not set - "
+ "executing smbspool");
+ /* Pass this printing task to smbspool without Kerberos auth */
+ goto smbspool;
+ } else {
+ CUPS_SMB_DEBUG("AUTH_INFO_REQUIRED=%s", env);
+
+ /* First test the value of AUTH_INFO_REQUIRED
+ * against known possible values
+ */
+ cmp = strcmp(env, "none");
+ if (cmp == 0) {
+ CUPS_SMB_DEBUG("Authenticate using none (anonymous) - "
+ "executing smbspool");
+ goto smbspool;
+ }
+
+ cmp = strcmp(env, "username,password");
+ if (cmp == 0) {
+ CUPS_SMB_DEBUG("Authenticate using username/password - "
+ "executing smbspool");
+ goto smbspool;
+ }
+
+ /* Now, if 'goto smbspool' still has not happened,
+ * there are only two variants left:
+ * 1) AUTH_INFO_REQUIRED is "negotiate" and then
+ * we have to continue working
+ * 2) or it is something not known to us, then Kerberos
+ * authentication is not required, so just also pass
+ * this task to smbspool
+ */
+ cmp = strcmp(env, "negotiate");
+ if (cmp != 0) {
+ CUPS_SMB_DEBUG("Value of AUTH_INFO_REQUIRED is not known "
+ "to smbspool_krb5_wrapper, executing smbspool");
+ goto smbspool;
+ }
+
+ snprintf(auth_info_required,
+ sizeof(auth_info_required),
+ "%s",
+ env);
+ }
+
+ uid = getuid();
+
+ CUPS_SMB_DEBUG("Started with uid=%d\n", uid);
+ if (uid != 0) {
+ goto smbspool;
+ }
+
+ /*
+ * AUTH_UID gets only set if we have an incoming connection over the
+ * CUPS unix domain socket.
+ */
+ env = getenv("AUTH_UID");
+ if (env == NULL) {
+ CUPS_SMB_ERROR("AUTH_UID is not set");
+ fprintf(stderr, "ATTR: auth-info-required=negotiate\n");
+ return CUPS_BACKEND_AUTH_REQUIRED;
+ }
+
+ if (strlen(env) > 10) {
+ CUPS_SMB_ERROR("Invalid AUTH_UID");
+ return CUPS_BACKEND_FAILED;
+ }
+
+ errno = 0;
+ tmp = strtoul(env, NULL, 10);
+ if (errno != 0 || tmp >= UINT32_MAX) {
+ CUPS_SMB_ERROR("Failed to convert AUTH_UID=%s", env);
+ return CUPS_BACKEND_FAILED;
+ }
+ uid = (uid_t)tmp;
+
+ /* If we are printing as the root user, we're done here. */
+ if (uid == 0) {
+ goto smbspool;
+ }
+
+ pwd = getpwuid(uid);
+ if (pwd == NULL) {
+ CUPS_SMB_ERROR("Failed to find system user: %u - %s",
+ uid, strerror(errno));
+ return CUPS_BACKEND_FAILED;
+ }
+ gid = pwd->pw_gid;
+
+ rc = setgroups(0, NULL);
+ if (rc != 0) {
+ CUPS_SMB_ERROR("Failed to clear groups - %s",
+ strerror(errno));
+ return CUPS_BACKEND_FAILED;
+ }
+
+ /*
+ * We need the primary group of the 'lp' user. This is needed to access
+ * temporary files in /var/spool/cups/.
+ */
+ g = getgrnam("lp");
+ if (g == NULL) {
+ CUPS_SMB_ERROR("Failed to find user 'lp' - %s",
+ strerror(errno));
+ return CUPS_BACKEND_FAILED;
+ }
+
+ CUPS_SMB_DEBUG("Adding group 'lp' (%u)", g->gr_gid);
+ groups[0] = g->gr_gid;
+ rc = setgroups(ARRAY_SIZE(groups), groups);
+ if (rc != 0) {
+ CUPS_SMB_ERROR("Failed to set groups for 'lp' - %s",
+ strerror(errno));
+ return CUPS_BACKEND_FAILED;
+ }
+
+ CUPS_SMB_DEBUG("Switching to gid=%d", gid);
+ rc = setgid(gid);
+ if (rc != 0) {
+ CUPS_SMB_ERROR("Failed to switch to gid=%u - %s",
+ gid,
+ strerror(errno));
+ return CUPS_BACKEND_FAILED;
+ }
+
+ CUPS_SMB_DEBUG("Switching to uid=%u", uid);
+ rc = setuid(uid);
+ if (rc != 0) {
+ CUPS_SMB_ERROR("Failed to switch to uid=%u - %s",
+ uid,
+ strerror(errno));
+ return CUPS_BACKEND_FAILED;
+ }
+
+ env = getenv("KRB5CCNAME");
+ if (env != NULL && env[0] != 0) {
+ snprintf(gen_cc, sizeof(gen_cc), "%s", env);
+ CUPS_SMB_DEBUG("User already set KRB5CCNAME [%s] as ccache",
+ gen_cc);
+
+ goto create_env;
+ }
+
+ ok = kerberos_get_default_ccache(gen_cc, sizeof(gen_cc));
+ if (ok) {
+ CUPS_SMB_DEBUG("Use default KRB5CCNAME [%s]",
+ gen_cc);
+ goto create_env;
+ }
+
+ /* Fallback to a FILE ccache */
+ snprintf(gen_cc, sizeof(gen_cc), "FILE:/tmp/krb5cc_%u", uid);
+
+create_env:
+ /*
+ * Make sure we do not have LD_PRELOAD or other security relevant
+ * environment variables set.
+ */
+#ifdef HAVE_CLEARENV
+ clearenv();
+#else
+ environ = calloc(3, sizeof(*environ));
+#endif
+
+ CUPS_SMB_DEBUG("Setting KRB5CCNAME to '%s'", gen_cc);
+ setenv("KRB5CCNAME", gen_cc, 1);
+ if (device_uri[0] != '\0') {
+ setenv("DEVICE_URI", device_uri, 1);
+ }
+ if (auth_info_required[0] != '\0') {
+ setenv("AUTH_INFO_REQUIRED", auth_info_required, 1);
+ }
+
+smbspool:
+ snprintf(smbspool_cmd,
+ sizeof(smbspool_cmd),
+ "%s/smbspool",
+ get_dyn_BINDIR());
+
+ return execv(smbspool_cmd, argv);
+}
diff --git a/source3/exports/libaddns.syms b/source3/exports/libaddns.syms
new file mode 100644
index 0000000..3e88ba7
--- /dev/null
+++ b/source3/exports/libaddns.syms
@@ -0,0 +1,5 @@
+{
+ # no global exported symbols (yet) in libaddns ...
+
+ local: *;
+};
diff --git a/source3/exports/modules-darwin.syms b/source3/exports/modules-darwin.syms
new file mode 100644
index 0000000..276543b
--- /dev/null
+++ b/source3/exports/modules-darwin.syms
@@ -0,0 +1 @@
+_samba_init_module
diff --git a/source3/groupdb/mapping.c b/source3/groupdb/mapping.c
new file mode 100644
index 0000000..cb6a611
--- /dev/null
+++ b/source3/groupdb/mapping.c
@@ -0,0 +1,893 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2001.
+ * Copyright (C) Volker Lendecke 2006.
+ * Copyright (C) Gerald Carter 2006.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "passdb.h"
+#include "groupdb/mapping.h"
+#include "../libcli/security/security.h"
+#include "lib/winbind_util.h"
+#include <tdb.h>
+#include "groupdb/mapping_tdb.h"
+#include "lib/util/smb_strtox.h"
+
+static const struct mapping_backend *backend;
+
+/*
+ initialise a group mapping backend
+ */
+static bool init_group_mapping(void)
+{
+ if (backend != NULL) {
+ /* already initialised */
+ return True;
+ }
+
+ backend = groupdb_tdb_init();
+
+ return backend != NULL;
+}
+
+/****************************************************************************
+initialise first time the mapping list
+****************************************************************************/
+NTSTATUS add_initial_entry(gid_t gid, const char *sid, enum lsa_SidType sid_name_use, const char *nt_name, const char *comment)
+{
+ NTSTATUS status;
+ GROUP_MAP *map;
+
+ if(!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ map->gid=gid;
+ if (!string_to_sid(&map->sid, sid)) {
+ DEBUG(0, ("string_to_sid failed: %s\n", sid));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ map->sid_name_use=sid_name_use;
+ map->nt_name = talloc_strdup(map, nt_name);
+ if (!map->nt_name) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (comment) {
+ map->comment = talloc_strdup(map, comment);
+ } else {
+ map->comment = talloc_strdup(map, "");
+ }
+ if (!map->comment) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = pdb_add_group_mapping_entry(map);
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+static NTSTATUS alias_memberships(const struct dom_sid *members, size_t num_members,
+ struct dom_sid **sids, size_t *num)
+{
+ size_t i;
+
+ *num = 0;
+ *sids = NULL;
+
+ for (i=0; i<num_members; i++) {
+ NTSTATUS status = backend->one_alias_membership(&members[i], sids, num);
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+struct aliasmem_closure {
+ const struct dom_sid *alias;
+ struct dom_sid **sids;
+ size_t *num;
+};
+
+
+
+/*
+ *
+ * High level functions
+ * better to use them than the lower ones.
+ *
+ * we are checking if the group is in the mapping file
+ * and if the group is an existing unix group
+ *
+ */
+
+/* get a domain group from it's SID */
+
+bool get_domain_group_from_sid(struct dom_sid sid, GROUP_MAP *map)
+{
+ struct group *grp;
+ bool ret;
+
+ if(!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return(False);
+ }
+
+ DEBUG(10, ("get_domain_group_from_sid\n"));
+
+ /* if the group is NOT in the database, it CAN NOT be a domain group */
+
+ become_root();
+ ret = pdb_getgrsid(map, sid);
+ unbecome_root();
+
+ /* special case check for rid 513 */
+
+ if ( !ret ) {
+ uint32_t rid;
+
+ sid_peek_rid( &sid, &rid );
+
+ if ( rid == DOMAIN_RID_USERS ) {
+ map->nt_name = talloc_strdup(map, "None");
+ if (!map->nt_name) {
+ return false;
+ }
+ map->comment = talloc_strdup(map, "Ordinary Users");
+ if (!map->comment) {
+ return false;
+ }
+ sid_copy( &map->sid, &sid );
+ map->sid_name_use = SID_NAME_DOM_GRP;
+ map->gid = (gid_t)-1;
+ return True;
+ }
+ return False;
+ }
+
+ DEBUG(10, ("get_domain_group_from_sid: SID found in passdb\n"));
+
+ /* if it's not a domain group, continue */
+ if (map->sid_name_use!=SID_NAME_DOM_GRP) {
+ return False;
+ }
+
+ DEBUG(10, ("get_domain_group_from_sid: SID is a domain group\n"));
+
+ if (map->gid==-1) {
+ return False;
+ }
+
+ DEBUG(10, ("get_domain_group_from_sid: SID is mapped to gid:%lu\n",(unsigned long)map->gid));
+
+ grp = getgrgid(map->gid);
+ if ( !grp ) {
+ DEBUG(10, ("get_domain_group_from_sid: gid DOESN'T exist in UNIX security\n"));
+ return False;
+ }
+
+ DEBUG(10, ("get_domain_group_from_sid: gid exists in UNIX security\n"));
+
+ return True;
+}
+
+/****************************************************************************
+ Create a UNIX group on demand.
+****************************************************************************/
+
+int smb_create_group(const char *unix_group, gid_t *new_gid)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *add_script = NULL;
+ int ret = -1;
+ int fd = 0;
+ int error = 0;
+
+ *new_gid = 0;
+
+ /* defer to scripts */
+
+ if ( *lp_add_group_script(talloc_tos(), lp_sub) ) {
+ TALLOC_CTX *ctx = talloc_tos();
+
+ add_script = talloc_strdup(ctx,
+ lp_add_group_script(ctx, lp_sub));
+ if (!add_script) {
+ return -1;
+ }
+ add_script = talloc_string_sub(ctx,
+ add_script, "%g", unix_group);
+ if (!add_script) {
+ return -1;
+ }
+
+ ret = smbrun(add_script, &fd, NULL);
+ DEBUG(ret ? 0 : 3,("smb_create_group: Running the command `%s' gave %d\n",add_script,ret));
+ if (ret == 0) {
+ smb_nscd_flush_group_cache();
+ }
+ if (ret != 0)
+ return ret;
+
+ if (fd != 0) {
+ fstring output;
+ ssize_t nread;
+
+ *new_gid = 0;
+
+ nread = read(fd, output, sizeof(output)-1);
+ if (nread > 0) {
+ output[nread] = '\0';
+ *new_gid = (gid_t)smb_strtoul(output,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ *new_gid = 0;
+ close(fd);
+ return -1;
+ }
+ }
+
+ close(fd);
+ }
+
+ }
+
+ if (*new_gid == 0) {
+ struct group *grp = getgrnam(unix_group);
+
+ if (grp != NULL)
+ *new_gid = grp->gr_gid;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Delete a UNIX group on demand.
+****************************************************************************/
+
+int smb_delete_group(const char *unix_group)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *del_script = NULL;
+ int ret = -1;
+
+ /* defer to scripts */
+
+ if ( *lp_delete_group_script(talloc_tos(), lp_sub) ) {
+ TALLOC_CTX *ctx = talloc_tos();
+
+ del_script = talloc_strdup(ctx,
+ lp_delete_group_script(ctx, lp_sub));
+ if (!del_script) {
+ return -1;
+ }
+ del_script = talloc_string_sub(ctx,
+ del_script, "%g", unix_group);
+ if (!del_script) {
+ return -1;
+ }
+ ret = smbrun(del_script, NULL, NULL);
+ DEBUG(ret ? 0 : 3,("smb_delete_group: Running the command `%s' gave %d\n",del_script,ret));
+ if (ret == 0) {
+ smb_nscd_flush_group_cache();
+ }
+ return ret;
+ }
+
+ return -1;
+}
+
+/****************************************************************************
+ Set a user's primary UNIX group.
+****************************************************************************/
+
+int smb_set_primary_group(const char *unix_group, const char* unix_user)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *add_script = NULL;
+ int ret = -1;
+
+ /* defer to scripts */
+
+ if ( *lp_set_primary_group_script(talloc_tos(), lp_sub) ) {
+ TALLOC_CTX *ctx = talloc_tos();
+
+ add_script = talloc_strdup(ctx,
+ lp_set_primary_group_script(ctx, lp_sub));
+ if (!add_script) {
+ return -1;
+ }
+ add_script = talloc_all_string_sub(ctx,
+ add_script, "%g", unix_group);
+ if (!add_script) {
+ return -1;
+ }
+ add_script = talloc_string_sub(ctx,
+ add_script, "%u", unix_user);
+ if (!add_script) {
+ return -1;
+ }
+ ret = smbrun(add_script, NULL, NULL);
+ flush_pwnam_cache();
+ DEBUG(ret ? 0 : 3,("smb_set_primary_group: "
+ "Running the command `%s' gave %d\n",add_script,ret));
+ if (ret == 0) {
+ smb_nscd_flush_group_cache();
+ }
+ return ret;
+ }
+
+ return -1;
+}
+
+/****************************************************************************
+ Add a user to a UNIX group.
+****************************************************************************/
+
+int smb_add_user_group(const char *unix_group, const char *unix_user)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *add_script = NULL;
+ int ret = -1;
+
+ /* defer to scripts */
+
+ if ( *lp_add_user_to_group_script(talloc_tos(), lp_sub) ) {
+ TALLOC_CTX *ctx = talloc_tos();
+
+ add_script = talloc_strdup(ctx,
+ lp_add_user_to_group_script(ctx, lp_sub));
+ if (!add_script) {
+ return -1;
+ }
+ add_script = talloc_string_sub(ctx,
+ add_script, "%g", unix_group);
+ if (!add_script) {
+ return -1;
+ }
+ add_script = talloc_string_sub2(ctx,
+ add_script, "%u", unix_user, true, false, true);
+ if (!add_script) {
+ return -1;
+ }
+ ret = smbrun(add_script, NULL, NULL);
+ DEBUG(ret ? 0 : 3,("smb_add_user_group: Running the command `%s' gave %d\n",add_script,ret));
+ if (ret == 0) {
+ smb_nscd_flush_group_cache();
+ }
+ return ret;
+ }
+
+ return -1;
+}
+
+/****************************************************************************
+ Delete a user from a UNIX group
+****************************************************************************/
+
+int smb_delete_user_group(const char *unix_group, const char *unix_user)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *del_script = NULL;
+ int ret = -1;
+
+ /* defer to scripts */
+
+ if ( *lp_delete_user_from_group_script(talloc_tos(), lp_sub) ) {
+ TALLOC_CTX *ctx = talloc_tos();
+
+ del_script = talloc_strdup(ctx,
+ lp_delete_user_from_group_script(ctx, lp_sub));
+ if (!del_script) {
+ return -1;
+ }
+ del_script = talloc_string_sub(ctx,
+ del_script, "%g", unix_group);
+ if (!del_script) {
+ return -1;
+ }
+ del_script = talloc_string_sub2(ctx,
+ del_script, "%u", unix_user, true, false, true);
+ if (!del_script) {
+ return -1;
+ }
+ ret = smbrun(del_script, NULL, NULL);
+ DEBUG(ret ? 0 : 3,("smb_delete_user_group: Running the command `%s' gave %d\n",del_script,ret));
+ if (ret == 0) {
+ smb_nscd_flush_group_cache();
+ }
+ return ret;
+ }
+
+ return -1;
+}
+
+
+NTSTATUS pdb_default_getgrsid(struct pdb_methods *methods, GROUP_MAP *map,
+ struct dom_sid sid)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->get_group_map_from_sid(sid, map) ?
+ NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_default_getgrgid(struct pdb_methods *methods, GROUP_MAP *map,
+ gid_t gid)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->get_group_map_from_gid(gid, map) ?
+ NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_default_getgrnam(struct pdb_methods *methods, GROUP_MAP *map,
+ const char *name)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->get_group_map_from_ntname(name, map) ?
+ NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_default_add_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->add_mapping_entry(map, TDB_INSERT) ?
+ NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_default_update_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->add_mapping_entry(map, TDB_REPLACE) ?
+ NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_default_delete_group_mapping_entry(struct pdb_methods *methods,
+ struct dom_sid sid)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->group_map_remove(&sid) ?
+ NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_default_enum_group_mapping(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries,
+ bool unix_only)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->enum_group_mapping(sid, sid_name_use, pp_rmap, p_num_entries, unix_only) ?
+ NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_default_create_alias(struct pdb_methods *methods,
+ const char *name, uint32_t *rid)
+{
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ uint32_t new_rid;
+ gid_t gid;
+ bool exists;
+ GROUP_MAP *map;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+
+ DEBUG(10, ("Trying to create alias %s\n", name));
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ exists = lookup_name(mem_ctx, name, LOOKUP_NAME_LOCAL,
+ NULL, NULL, &sid, &type);
+
+ if (exists) {
+ status = NT_STATUS_ALIAS_EXISTS;
+ goto done;
+ }
+
+ if (!pdb_new_rid(&new_rid)) {
+ DEBUG(0, ("Could not allocate a RID.\n"));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ sid_compose(&sid, get_global_sam_sid(), new_rid);
+
+ if (!winbind_allocate_gid(&gid)) {
+ DEBUG(3, ("Could not get a gid out of winbind - "
+ "wasted a rid :-(\n"));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ DEBUG(10, ("Creating alias %s with gid %u and rid %u\n",
+ name, (unsigned int)gid, (unsigned int)new_rid));
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (!map) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ map->gid = gid;
+ sid_copy(&map->sid, &sid);
+ map->sid_name_use = SID_NAME_ALIAS;
+ map->nt_name = talloc_strdup(map, name);
+ if (!map->nt_name) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ map->comment = talloc_strdup(map, "");
+ if (!map->comment) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = pdb_add_group_mapping_entry(map);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not add group mapping entry for alias %s "
+ "(%s)\n", name, nt_errstr(status)));
+ goto done;
+ }
+
+ *rid = new_rid;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return status;
+}
+
+NTSTATUS pdb_default_delete_alias(struct pdb_methods *methods,
+ const struct dom_sid *sid)
+{
+ return pdb_delete_group_mapping_entry(*sid);
+}
+
+NTSTATUS pdb_default_get_aliasinfo(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct acct_info *info)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ GROUP_MAP *map;
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_getgrsid(map, *sid)) {
+ status = NT_STATUS_NO_SUCH_ALIAS;
+ goto done;
+ }
+
+ if ((map->sid_name_use != SID_NAME_ALIAS) &&
+ (map->sid_name_use != SID_NAME_WKN_GRP)) {
+ struct dom_sid_buf buf;
+ DEBUG(2, ("%s is a %s, expected an alias\n",
+ dom_sid_str_buf(sid, &buf),
+ sid_type_lookup(map->sid_name_use)));
+ status = NT_STATUS_NO_SUCH_ALIAS;
+ goto done;
+ }
+
+ info->acct_name = talloc_move(info, &map->nt_name);
+ if (!info->acct_name) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ info->acct_desc = talloc_move(info, &map->comment);
+ if (!info->acct_desc) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ sid_peek_rid(&map->sid, &info->rid);
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+NTSTATUS pdb_default_set_aliasinfo(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct acct_info *info)
+{
+ NTSTATUS status;
+ GROUP_MAP *map;
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_getgrsid(map, *sid)) {
+ status = NT_STATUS_NO_SUCH_ALIAS;
+ goto done;
+ }
+
+ map->nt_name = talloc_strdup(map, info->acct_name);
+ if (!map->nt_name) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ map->comment = talloc_strdup(map, info->acct_desc);
+ if (!map->comment) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = pdb_update_group_mapping_entry(map);
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+NTSTATUS pdb_default_add_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias, const struct dom_sid *member)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->add_aliasmem(alias, member);
+}
+
+NTSTATUS pdb_default_del_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias, const struct dom_sid *member)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->del_aliasmem(alias, member);
+}
+
+NTSTATUS pdb_default_enum_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias, TALLOC_CTX *mem_ctx,
+ struct dom_sid **pp_members, size_t *p_num_members)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return backend->enum_aliasmem(alias, mem_ctx, pp_members,
+ p_num_members);
+}
+
+NTSTATUS pdb_default_alias_memberships(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *members,
+ size_t num_members,
+ uint32_t **pp_alias_rids,
+ size_t *p_num_alias_rids)
+{
+ struct dom_sid *alias_sids;
+ size_t i, num_alias_sids;
+ NTSTATUS result;
+
+ if (!init_group_mapping()) {
+ DEBUG(0,("failed to initialize group mapping\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ alias_sids = NULL;
+ num_alias_sids = 0;
+
+ result = alias_memberships(members, num_members,
+ &alias_sids, &num_alias_sids);
+
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ *p_num_alias_rids = 0;
+
+ if (num_alias_sids == 0) {
+ TALLOC_FREE(alias_sids);
+ return NT_STATUS_OK;
+ }
+
+ *pp_alias_rids = talloc_array(mem_ctx, uint32_t, num_alias_sids);
+ if (*pp_alias_rids == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ for (i=0; i<num_alias_sids; i++) {
+ if (!sid_peek_check_rid(domain_sid, &alias_sids[i],
+ &(*pp_alias_rids)[*p_num_alias_rids]))
+ continue;
+ *p_num_alias_rids += 1;
+ }
+
+ TALLOC_FREE(alias_sids);
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ no ops for passdb backends that don't implement group mapping
+ *********************************************************************/
+
+NTSTATUS pdb_nop_getgrsid(struct pdb_methods *methods, GROUP_MAP *map,
+ struct dom_sid sid)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_nop_getgrgid(struct pdb_methods *methods, GROUP_MAP *map,
+ gid_t gid)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_nop_getgrnam(struct pdb_methods *methods, GROUP_MAP *map,
+ const char *name)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_nop_add_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_nop_update_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_nop_delete_group_mapping_entry(struct pdb_methods *methods,
+ struct dom_sid sid)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+NTSTATUS pdb_nop_enum_group_mapping(struct pdb_methods *methods,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP **rmap, size_t *num_entries,
+ bool unix_only)
+{
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+/**
+* @brief Add a new group mapping
+*
+* @param[in] gid gid to use to store the mapping. If gid is 0,
+* new gid will be allocated from winbind
+*
+* @return Normal NTSTATUS return
+*/
+NTSTATUS pdb_create_builtin_alias(uint32_t rid, gid_t gid)
+{
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ gid_t gidformap;
+ GROUP_MAP *map;
+ NTSTATUS status;
+ const char *name = NULL;
+
+ DEBUG(10, ("Trying to create builtin alias %d\n", rid));
+
+ if ( !sid_compose( &sid, &global_sid_Builtin, rid ) ) {
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ /* use map as overall temp mem context */
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!lookup_sid(map, &sid, NULL, &name, &type)) {
+ status = NT_STATUS_NO_SUCH_ALIAS;
+ goto done;
+ }
+
+ if (gid == 0) {
+ if (!winbind_allocate_gid(&gidformap)) {
+ DEBUG(3, ("pdb_create_builtin_alias: Could not get a "
+ "gid out of winbind\n"));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ } else {
+ gidformap = gid;
+ }
+
+ DEBUG(10, ("Creating alias %s with gid %u\n", name,
+ (unsigned) gidformap));
+
+ map->gid = gidformap;
+ sid_copy(&map->sid, &sid);
+ map->sid_name_use = SID_NAME_ALIAS;
+ map->nt_name = talloc_strdup(map, name);
+ if (!map->nt_name) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ map->comment = talloc_strdup(map, "");
+ if (!map->comment) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = pdb_add_group_mapping_entry(map);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("pdb_create_builtin_alias: Could not add group mapping entry for alias %d "
+ "(%s)\n", rid, nt_errstr(status)));
+ }
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+
diff --git a/source3/groupdb/mapping.h b/source3/groupdb/mapping.h
new file mode 100644
index 0000000..bb6cc02
--- /dev/null
+++ b/source3/groupdb/mapping.h
@@ -0,0 +1,56 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2001.
+ * Copyright (C) Volker Lendecke 2006.
+ * Copyright (C) Gerald Carter 2006.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define DATABASE_VERSION_V1 1 /* native byte format. */
+#define DATABASE_VERSION_V2 2 /* le format. */
+
+#define GROUP_PREFIX "UNIXGROUP/"
+#define GROUP_PREFIX_LEN 10
+
+/* Alias memberships are stored reverse, as memberships. The performance
+ * critical operation is to determine the aliases a SID is member of, not
+ * listing alias members. So we store a list of alias SIDs a SID is member of
+ * hanging of the member as key.
+ */
+#define MEMBEROF_PREFIX "MEMBEROF/"
+#define MEMBEROF_PREFIX_LEN 9
+
+/*
+ groupdb mapping backend abstraction
+ */
+struct mapping_backend {
+ bool (*init_group_mapping)(void);
+ bool (*add_mapping_entry)(GROUP_MAP *map, int flag);
+ bool (*get_group_map_from_sid)(struct dom_sid sid, GROUP_MAP *map);
+ bool (*get_group_map_from_gid)(gid_t gid, GROUP_MAP *map);
+ bool (*get_group_map_from_ntname)(const char *name, GROUP_MAP *map);
+ bool (*group_map_remove)(const struct dom_sid *sid);
+ bool (*enum_group_mapping)(const struct dom_sid *domsid, enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries, bool unix_only);
+ NTSTATUS (*one_alias_membership)(const struct dom_sid *member,
+ struct dom_sid **sids, size_t *num);
+ NTSTATUS (*add_aliasmem)(const struct dom_sid *alias, const struct dom_sid *member);
+ NTSTATUS (*del_aliasmem)(const struct dom_sid *alias, const struct dom_sid *member);
+ NTSTATUS (*enum_aliasmem)(const struct dom_sid *alias, TALLOC_CTX *mem_ctx,
+ struct dom_sid **sids, size_t *num);
+};
diff --git a/source3/groupdb/mapping_tdb.c b/source3/groupdb/mapping_tdb.c
new file mode 100644
index 0000000..a39b03d
--- /dev/null
+++ b/source3/groupdb/mapping_tdb.c
@@ -0,0 +1,1135 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2006,
+ * Copyright (C) Jean François Micouleau 1998-2001.
+ * Copyright (C) Volker Lendecke 2006.
+ * Copyright (C) Gerald Carter 2006.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "passdb.h"
+#include "groupdb/mapping.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "util_tdb.h"
+#include "../libcli/security/security.h"
+#include "groupdb/mapping_tdb.h"
+#include "lib/util/smb_strtox.h"
+
+static struct db_context *db; /* used for driver files */
+
+static bool enum_group_mapping(const struct dom_sid *domsid,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries,
+ bool unix_only);
+static bool group_map_remove(const struct dom_sid *sid);
+
+static bool mapping_switch(const char *ldb_path);
+
+/****************************************************************************
+ Open the group mapping tdb.
+****************************************************************************/
+static bool init_group_mapping(void)
+{
+ char *tdb_path;
+ char *ldb_path;
+
+ if (db != NULL) {
+ return true;
+ }
+
+ tdb_path = state_path(talloc_tos(), "group_mapping.tdb");
+ if (tdb_path == NULL) {
+ return false;
+ }
+ db = db_open(NULL, tdb_path, 0,
+ TDB_DEFAULT, O_RDWR|O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ DEBUG(0, ("Failed to open group mapping database: %s\n",
+ strerror(errno)));
+ talloc_free(tdb_path);
+ return false;
+ }
+
+ ldb_path = state_path(talloc_tos(), "group_mapping.ldb");
+ if (ldb_path == NULL) {
+ talloc_free(tdb_path);
+ return false;
+ }
+ if (file_exist(ldb_path) && !mapping_switch(ldb_path)) {
+ unlink(tdb_path);
+ talloc_free(tdb_path);
+ talloc_free(ldb_path);
+ return false;
+
+ } else {
+ /* handle upgrade from old versions of the database */
+#if 0 /* -- Needs conversion to dbwrap -- */
+ const char *vstring = "INFO/version";
+ int32 vers_id;
+ GROUP_MAP *map_table = NULL;
+ size_t num_entries = 0;
+
+ /* handle a Samba upgrade */
+ tdb_lock_bystring(tdb, vstring);
+
+ /* Cope with byte-reversed older versions of the db. */
+ vers_id = tdb_fetch_int32(tdb, vstring);
+ if ((vers_id == DATABASE_VERSION_V1)
+ || (IREV(vers_id) == DATABASE_VERSION_V1)) {
+ /*
+ * Written on a bigendian machine with old fetch_int
+ * code. Save as le.
+ */
+ tdb_store_int32(tdb, vstring, DATABASE_VERSION_V2);
+ vers_id = DATABASE_VERSION_V2;
+ }
+
+ /* if its an unknown version we remove everything in the db */
+
+ if (vers_id != DATABASE_VERSION_V2) {
+ tdb_wipe_all(tdb);
+ tdb_store_int32(tdb, vstring, DATABASE_VERSION_V2);
+ }
+
+ tdb_unlock_bystring(tdb, vstring);
+
+ /* cleanup any map entries with a gid == -1 */
+
+ if ( enum_group_mapping( NULL, SID_NAME_UNKNOWN, &map_table,
+ &num_entries, False ) ) {
+ int i;
+
+ for ( i=0; i<num_entries; i++ ) {
+ if ( map_table[i].gid == -1 ) {
+ group_map_remove( &map_table[i].sid );
+ }
+ }
+
+ SAFE_FREE( map_table );
+ }
+#endif
+ }
+ talloc_free(tdb_path);
+ talloc_free(ldb_path);
+ return true;
+}
+
+static char *group_mapping_key(TALLOC_CTX *mem_ctx, const struct dom_sid *sid)
+{
+ struct dom_sid_buf sidstr;
+
+ return talloc_asprintf(
+ mem_ctx, "%s%s", GROUP_PREFIX, dom_sid_str_buf(sid, &sidstr));
+}
+
+/****************************************************************************
+****************************************************************************/
+static bool add_mapping_entry(GROUP_MAP *map, int flag)
+{
+ char *key, *buf;
+ int len;
+ NTSTATUS status;
+
+ key = group_mapping_key(talloc_tos(), &map->sid);
+ if (key == NULL) {
+ return false;
+ }
+
+ len = tdb_pack(NULL, 0, "ddff",
+ map->gid, map->sid_name_use, map->nt_name, map->comment);
+
+ buf = talloc_array(key, char, len);
+ if (!buf) {
+ TALLOC_FREE(key);
+ return false;
+ }
+ len = tdb_pack((uint8_t *)buf, len, "ddff", map->gid,
+ map->sid_name_use, map->nt_name, map->comment);
+
+ status = dbwrap_trans_store(
+ db, string_term_tdb_data(key),
+ make_tdb_data((uint8_t *)buf, len), TDB_REPLACE);
+
+ TALLOC_FREE(key);
+
+ return NT_STATUS_IS_OK(status);
+}
+
+
+/****************************************************************************
+ Return the sid and the type of the unix group.
+****************************************************************************/
+
+static bool get_group_map_from_sid(struct dom_sid sid, GROUP_MAP *map)
+{
+ TDB_DATA dbuf;
+ char *key;
+ int ret = 0;
+ NTSTATUS status;
+ fstring nt_name;
+ fstring comment;
+
+ /* the key is the SID, retrieving is direct */
+
+ key = group_mapping_key(talloc_tos(), &sid);
+ if (key == NULL) {
+ return false;
+ }
+
+ status = dbwrap_fetch_bystring(db, key, key, &dbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(key);
+ return false;
+ }
+
+ ret = tdb_unpack(dbuf.dptr, dbuf.dsize, "ddff",
+ &map->gid, &map->sid_name_use,
+ &nt_name, &comment);
+
+ TALLOC_FREE(key);
+
+ if ( ret == -1 ) {
+ DEBUG(3,("get_group_map_from_sid: tdb_unpack failure\n"));
+ return false;
+ }
+
+ sid_copy(&map->sid, &sid);
+
+ map->nt_name = talloc_strdup(map, nt_name);
+ if (!map->nt_name) {
+ return false;
+ }
+ map->comment = talloc_strdup(map, comment);
+ if (!map->comment) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool dbrec2map(const struct db_record *rec, GROUP_MAP *map)
+{
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA value = dbwrap_record_get_value(rec);
+ int ret = 0;
+ fstring nt_name;
+ fstring comment;
+
+ if ((key.dsize < strlen(GROUP_PREFIX))
+ || (strncmp((char *)key.dptr, GROUP_PREFIX,
+ GROUP_PREFIX_LEN) != 0)) {
+ return False;
+ }
+
+ if (!string_to_sid(&map->sid, (const char *)key.dptr
+ + GROUP_PREFIX_LEN)) {
+ return False;
+ }
+
+ ret = tdb_unpack(value.dptr, value.dsize, "ddff",
+ &map->gid, &map->sid_name_use,
+ &nt_name, &comment);
+
+ if (ret == -1) {
+ DEBUG(3, ("dbrec2map: tdb_unpack failure\n"));
+ return false;
+ }
+
+ map->nt_name = talloc_strdup(map, nt_name);
+ if (!map->nt_name) {
+ return false;
+ }
+ map->comment = talloc_strdup(map, comment);
+ if (!map->comment) {
+ return false;
+ }
+
+ return true;
+}
+
+struct find_map_state {
+ bool found;
+ const char *name; /* If != NULL, look for name */
+ gid_t gid; /* valid iff name == NULL */
+ GROUP_MAP *map;
+};
+
+static int find_map(struct db_record *rec, void *private_data)
+{
+ struct find_map_state *state = (struct find_map_state *)private_data;
+
+ if (!dbrec2map(rec, state->map)) {
+ DEBUG(10, ("failed to unpack map\n"));
+ return 0;
+ }
+
+ if (state->name != NULL) {
+ if (strequal(state->name, state->map->nt_name)) {
+ state->found = true;
+ return 1;
+ }
+ }
+ else {
+ if (state->map->gid == state->gid) {
+ state->found = true;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Return the sid and the type of the unix group.
+****************************************************************************/
+
+static bool get_group_map_from_gid(gid_t gid, GROUP_MAP *map)
+{
+ struct find_map_state state;
+
+ state.found = false;
+ state.name = NULL; /* Indicate we're looking for gid */
+ state.gid = gid;
+ state.map = map;
+
+ dbwrap_traverse_read(db, find_map, (void *)&state, NULL);
+
+ return state.found;
+}
+
+/****************************************************************************
+ Return the sid and the type of the unix group.
+****************************************************************************/
+
+static bool get_group_map_from_ntname(const char *name, GROUP_MAP *map)
+{
+ struct find_map_state state;
+
+ state.found = false;
+ state.name = name;
+ state.map = map;
+
+ dbwrap_traverse_read(db, find_map, (void *)&state, NULL);
+
+ return state.found;
+}
+
+/****************************************************************************
+ Remove a group mapping entry.
+****************************************************************************/
+
+static bool group_map_remove(const struct dom_sid *sid)
+{
+ char *key;
+ NTSTATUS status;
+
+ key = group_mapping_key(talloc_tos(), sid);
+ if (key == NULL) {
+ return false;
+ }
+
+ status = dbwrap_trans_delete(db, string_term_tdb_data(key));
+
+ TALLOC_FREE(key);
+ return NT_STATUS_IS_OK(status);
+}
+
+/****************************************************************************
+ Enumerate the group mapping.
+****************************************************************************/
+
+struct enum_map_state {
+ const struct dom_sid *domsid;
+ enum lsa_SidType sid_name_use;
+ bool unix_only;
+
+ size_t num_maps;
+ GROUP_MAP **maps;
+};
+
+static int collect_map(struct db_record *rec, void *private_data)
+{
+ struct enum_map_state *state = (struct enum_map_state *)private_data;
+ GROUP_MAP *map;
+ GROUP_MAP **tmp;
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ DEBUG(0, ("Unable to allocate group map!\n"));
+ return 1;
+ }
+
+ if (!dbrec2map(rec, map)) {
+ TALLOC_FREE(map);
+ return 0;
+ }
+ /* list only the type or everything if UNKNOWN */
+ if (state->sid_name_use != SID_NAME_UNKNOWN
+ && state->sid_name_use != map->sid_name_use) {
+ DEBUG(11,("enum_group_mapping: group %s is not of the "
+ "requested type\n", map->nt_name));
+ TALLOC_FREE(map);
+ return 0;
+ }
+
+ if ((state->unix_only == ENUM_ONLY_MAPPED) && (map->gid == -1)) {
+ DEBUG(11,("enum_group_mapping: group %s is non mapped\n",
+ map->nt_name));
+ TALLOC_FREE(map);
+ return 0;
+ }
+
+ if ((state->domsid != NULL) &&
+ (dom_sid_compare_domain(state->domsid, &map->sid) != 0)) {
+ struct dom_sid_buf buf;
+ DEBUG(11,("enum_group_mapping: group %s is not in domain\n",
+ dom_sid_str_buf(&map->sid, &buf)));
+ TALLOC_FREE(map);
+ return 0;
+ }
+
+ tmp = talloc_realloc(NULL, state->maps, GROUP_MAP *,
+ state->num_maps + 1);
+ if (!tmp) {
+ DEBUG(0,("enum_group_mapping: Unable to enlarge group "
+ "map!\n"));
+ TALLOC_FREE(map);
+ return 1;
+ }
+
+ state->maps = tmp;
+ state->maps[state->num_maps] = talloc_move(state->maps, &map);
+ state->num_maps++;
+ return 0;
+}
+
+static bool enum_group_mapping(const struct dom_sid *domsid,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries, bool unix_only)
+{
+ struct enum_map_state state;
+ NTSTATUS status;
+
+ state.domsid = domsid;
+ state.sid_name_use = sid_name_use;
+ state.unix_only = unix_only;
+ state.num_maps = 0;
+ state.maps = NULL;
+
+ status = dbwrap_traverse_read(db, collect_map, (void *)&state, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(state.maps);
+ return false;
+ }
+
+ *pp_rmap = state.maps;
+ *p_num_entries = state.num_maps;
+
+ return true;
+}
+
+/* This operation happens on session setup, so it should better be fast. We
+ * store a list of aliases a SID is member of hanging off MEMBEROF/SID. */
+
+static NTSTATUS one_alias_membership(const struct dom_sid *member,
+ struct dom_sid **sids, size_t *num)
+{
+ struct dom_sid_buf tmp;
+ fstring key;
+ char *string_sid;
+ TDB_DATA dbuf;
+ const char *p;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ slprintf(key, sizeof(key), "%s%s", MEMBEROF_PREFIX,
+ dom_sid_str_buf(member, &tmp));
+
+ status = dbwrap_fetch_bystring(db, frame, key, &dbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ p = (const char *)dbuf.dptr;
+
+ while (next_token_talloc(frame, &p, &string_sid, " ")) {
+ struct dom_sid alias;
+ uint32_t num_sids;
+
+ if (!string_to_sid(&alias, string_sid))
+ continue;
+
+ num_sids = *num;
+ status= add_sid_to_array_unique(NULL, &alias, sids, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ *num = num_sids;
+ }
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS alias_memberships(const struct dom_sid *members, size_t num_members,
+ struct dom_sid **sids, size_t *num)
+{
+ size_t i;
+
+ *num = 0;
+ *sids = NULL;
+
+ for (i=0; i<num_members; i++) {
+ NTSTATUS status = one_alias_membership(&members[i], sids, num);
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+static bool is_aliasmem(const struct dom_sid *alias, const struct dom_sid *member)
+{
+ struct dom_sid *sids;
+ size_t i;
+ size_t num;
+
+ /* This feels the wrong way round, but the on-disk data structure
+ * dictates it this way. */
+ if (!NT_STATUS_IS_OK(alias_memberships(member, 1, &sids, &num)))
+ return False;
+
+ for (i=0; i<num; i++) {
+ if (dom_sid_compare(alias, &sids[i]) == 0) {
+ TALLOC_FREE(sids);
+ return True;
+ }
+ }
+ TALLOC_FREE(sids);
+ return False;
+}
+
+
+static NTSTATUS add_aliasmem(const struct dom_sid *alias, const struct dom_sid *member)
+{
+ GROUP_MAP *map;
+ char *key;
+ struct dom_sid_buf string_sid;
+ char *new_memberstring;
+ struct db_record *rec;
+ NTSTATUS status;
+ TDB_DATA value;
+
+ map = talloc_zero(talloc_tos(), GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!get_group_map_from_sid(*alias, map)) {
+ TALLOC_FREE(map);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ if ((map->sid_name_use != SID_NAME_ALIAS) &&
+ (map->sid_name_use != SID_NAME_WKN_GRP)) {
+ TALLOC_FREE(map);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ TALLOC_FREE(map);
+
+ if (is_aliasmem(alias, member))
+ return NT_STATUS_MEMBER_IN_ALIAS;
+
+ key = talloc_asprintf(talloc_tos(), "%s%s", MEMBEROF_PREFIX,
+ dom_sid_str_buf(member, &string_sid));
+ if (key == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (dbwrap_transaction_start(db) != 0) {
+ DEBUG(0, ("transaction_start failed\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ rec = dbwrap_fetch_locked(db, key, string_term_tdb_data(key));
+
+ if (rec == NULL) {
+ DEBUG(10, ("fetch_lock failed\n"));
+ TALLOC_FREE(key);
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto cancel;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ dom_sid_str_buf(alias, &string_sid);
+
+ if (value.dptr != NULL) {
+ new_memberstring = talloc_asprintf(
+ key, "%s %s", (char *)(value.dptr), string_sid.buf);
+ } else {
+ new_memberstring = talloc_strdup(key, string_sid.buf);
+ }
+
+ if (new_memberstring == NULL) {
+ TALLOC_FREE(key);
+ status = NT_STATUS_NO_MEMORY;
+ goto cancel;
+ }
+
+ status = dbwrap_record_store(rec, string_term_tdb_data(new_memberstring), 0);
+
+ TALLOC_FREE(key);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Could not store record: %s\n", nt_errstr(status)));
+ goto cancel;
+ }
+
+ if (dbwrap_transaction_commit(db) != 0) {
+ DEBUG(0, ("transaction_commit failed\n"));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return status;
+ }
+
+ return NT_STATUS_OK;
+
+ cancel:
+ if (dbwrap_transaction_cancel(db) != 0) {
+ smb_panic("transaction_cancel failed");
+ }
+
+ return status;
+}
+
+struct aliasmem_state {
+ TALLOC_CTX *mem_ctx;
+ const struct dom_sid *alias;
+ struct dom_sid **sids;
+ size_t *num;
+};
+
+static int collect_aliasmem(struct db_record *rec, void *priv)
+{
+ struct aliasmem_state *state = (struct aliasmem_state *)priv;
+ const char *p;
+ char *alias_string;
+ TALLOC_CTX *frame;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA value = dbwrap_record_get_value(rec);
+
+ if (strncmp((const char *)key.dptr, MEMBEROF_PREFIX,
+ MEMBEROF_PREFIX_LEN) != 0)
+ return 0;
+
+ p = (const char *)value.dptr;
+
+ frame = talloc_stackframe();
+
+ while (next_token_talloc(frame, &p, &alias_string, " ")) {
+ struct dom_sid alias, member;
+ const char *member_string;
+ uint32_t num_sids;
+
+ if (!string_to_sid(&alias, alias_string))
+ continue;
+
+ if (dom_sid_compare(state->alias, &alias) != 0)
+ continue;
+
+ /* Ok, we found the alias we're looking for in the membership
+ * list currently scanned. The key represents the alias
+ * member. Add that. */
+
+ member_string = strchr((const char *)key.dptr, '/');
+
+ /* Above we tested for MEMBEROF_PREFIX which includes the
+ * slash. */
+
+ SMB_ASSERT(member_string != NULL);
+ member_string += 1;
+
+ if (!string_to_sid(&member, member_string))
+ continue;
+
+ num_sids = *state->num;
+ if (!NT_STATUS_IS_OK(add_sid_to_array(state->mem_ctx, &member,
+ state->sids,
+ &num_sids)))
+ {
+ /* talloc fail. */
+ break;
+ }
+ *state->num = num_sids;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+static NTSTATUS enum_aliasmem(const struct dom_sid *alias, TALLOC_CTX *mem_ctx,
+ struct dom_sid **sids, size_t *num)
+{
+ GROUP_MAP *map;
+ struct aliasmem_state state;
+
+ map = talloc_zero(talloc_tos(), GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!get_group_map_from_sid(*alias, map)) {
+ TALLOC_FREE(map);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ if ((map->sid_name_use != SID_NAME_ALIAS) &&
+ (map->sid_name_use != SID_NAME_WKN_GRP)) {
+ TALLOC_FREE(map);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ TALLOC_FREE(map);
+
+ *sids = NULL;
+ *num = 0;
+
+ state.alias = alias;
+ state.sids = sids;
+ state.num = num;
+ state.mem_ctx = mem_ctx;
+
+ dbwrap_traverse_read(db, collect_aliasmem, &state, NULL);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS del_aliasmem(const struct dom_sid *alias, const struct dom_sid *member)
+{
+ NTSTATUS status;
+ struct dom_sid *sids;
+ size_t i, num;
+ bool found = False;
+ char *member_string;
+ char *key;
+ struct dom_sid_buf sid_string;
+
+ if (dbwrap_transaction_start(db) != 0) {
+ DEBUG(0, ("transaction_start failed\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = alias_memberships(member, 1, &sids, &num);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cancel;
+ }
+
+ for (i=0; i<num; i++) {
+ if (dom_sid_compare(&sids[i], alias) == 0) {
+ found = True;
+ break;
+ }
+ }
+
+ if (!found) {
+ TALLOC_FREE(sids);
+ status = NT_STATUS_MEMBER_NOT_IN_ALIAS;
+ goto cancel;
+ }
+
+ if (i < num)
+ sids[i] = sids[num-1];
+
+ num -= 1;
+
+ key = talloc_asprintf(
+ sids,
+ "%s%s",
+ MEMBEROF_PREFIX,
+ dom_sid_str_buf(member, &sid_string));
+ if (key == NULL) {
+ TALLOC_FREE(sids);
+ status = NT_STATUS_NO_MEMORY;
+ goto cancel;
+ }
+
+ if (num == 0) {
+ status = dbwrap_delete_bystring(db, key);
+ goto commit;
+ }
+
+ member_string = talloc_strdup(sids, "");
+ if (member_string == NULL) {
+ TALLOC_FREE(sids);
+ status = NT_STATUS_NO_MEMORY;
+ goto cancel;
+ }
+
+ for (i=0; i<num; i++) {
+
+ member_string = talloc_asprintf_append_buffer(
+ member_string,
+ " %s",
+ dom_sid_str_buf(&sids[i], &sid_string));
+
+ if (member_string == NULL) {
+ TALLOC_FREE(sids);
+ status = NT_STATUS_NO_MEMORY;
+ goto cancel;
+ }
+ }
+
+ status = dbwrap_store_bystring(
+ db, key, string_term_tdb_data(member_string), 0);
+ commit:
+ TALLOC_FREE(sids);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("dbwrap_store_bystring failed: %s\n",
+ nt_errstr(status)));
+ goto cancel;
+ }
+
+ if (dbwrap_transaction_commit(db) != 0) {
+ DEBUG(0, ("transaction_commit failed\n"));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return status;
+ }
+
+ return NT_STATUS_OK;
+
+ cancel:
+ if (dbwrap_transaction_cancel(db) != 0) {
+ smb_panic("transaction_cancel failed");
+ }
+ return status;
+}
+
+
+/* -- ldb->tdb switching code -------------------------------------------- */
+
+/* change this if the data format ever changes */
+#define LTDB_PACKING_FORMAT 0x26011967
+
+/* old packing formats (not supported for now,
+ * it was never used for group mapping AFAIK) */
+#define LTDB_PACKING_FORMAT_NODN 0x26011966
+
+static unsigned int pull_uint32(uint8_t *p, int ofs)
+{
+ p += ofs;
+ return p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24);
+}
+
+/*
+ unpack a ldb message from a linear buffer in TDB_DATA
+*/
+static int convert_ldb_record(TDB_CONTEXT *ltdb, TDB_DATA key,
+ TDB_DATA data, void *ptr)
+{
+ TALLOC_CTX *tmp_ctx = talloc_tos();
+ GROUP_MAP *map = NULL;
+ uint8_t *p;
+ uint32_t format;
+ uint32_t num_el;
+ unsigned int remaining;
+ unsigned int i, j;
+ size_t len;
+ char *name;
+ char *val;
+ uint32_t num_mem = 0;
+ struct dom_sid *members = NULL;
+ int error = 0;
+
+ p = (uint8_t *)data.dptr;
+ if (data.dsize < 8) {
+ errno = EIO;
+ goto failed;
+ }
+
+ format = pull_uint32(p, 0);
+ num_el = pull_uint32(p, 4);
+ p += 8;
+
+ remaining = data.dsize - 8;
+
+ switch (format) {
+ case LTDB_PACKING_FORMAT:
+ len = strnlen((char *)p, remaining);
+ if (len == remaining) {
+ errno = EIO;
+ goto failed;
+ }
+
+ if (*p == '@') {
+ /* ignore special LDB attributes */
+ return 0;
+ }
+
+ if (strncmp((char *)p, "rid=", 4)) {
+ /* unknown entry, ignore */
+ DEBUG(3, ("Found unknown entry in group mapping "
+ "database named [%s]\n", (char *)p));
+ return 0;
+ }
+
+ remaining -= len + 1;
+ p += len + 1;
+ break;
+
+ case LTDB_PACKING_FORMAT_NODN:
+ default:
+ errno = EIO;
+ goto failed;
+ }
+
+ if (num_el == 0) {
+ /* bad entry, ignore */
+ return 0;
+ }
+
+ if (num_el > remaining / 6) {
+ errno = EIO;
+ goto failed;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ errno = ENOMEM;
+ goto failed;
+ }
+
+ for (i = 0; i < num_el; i++) {
+ uint32_t num_vals;
+
+ if (remaining < 10) {
+ errno = EIO;
+ goto failed;
+ }
+ len = strnlen((char *)p, remaining - 6);
+ if (len == remaining - 6) {
+ errno = EIO;
+ goto failed;
+ }
+ name = talloc_strndup(tmp_ctx, (char *)p, len);
+ if (name == NULL) {
+ errno = ENOMEM;
+ goto failed;
+ }
+ remaining -= len + 1;
+ p += len + 1;
+
+ num_vals = pull_uint32(p, 0);
+ if (strcasecmp_m(name, "member") == 0) {
+ num_mem = num_vals;
+ members = talloc_array(tmp_ctx, struct dom_sid, num_mem);
+ if (members == NULL) {
+ errno = ENOMEM;
+ goto failed;
+ }
+ } else if (num_vals != 1) {
+ errno = EIO;
+ goto failed;
+ }
+
+ p += 4;
+ remaining -= 4;
+
+ for (j = 0; j < num_vals; j++) {
+ len = pull_uint32(p, 0);
+ if (len > remaining-5) {
+ errno = EIO;
+ goto failed;
+ }
+
+ val = talloc_strndup(tmp_ctx, (char *)(p + 4), len);
+ if (val == NULL) {
+ errno = ENOMEM;
+ goto failed;
+ }
+
+ remaining -= len+4+1;
+ p += len+4+1;
+
+ /* we ignore unknown or uninteresting attributes
+ * (objectclass, etc.) */
+ if (strcasecmp_m(name, "gidNumber") == 0) {
+ map->gid = smb_strtoul(val,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ errno = EIO;
+ goto failed;
+ }
+ } else if (strcasecmp_m(name, "sid") == 0) {
+ if (!string_to_sid(&map->sid, val)) {
+ errno = EIO;
+ goto failed;
+ }
+ } else if (strcasecmp_m(name, "sidNameUse") == 0) {
+ map->sid_name_use =
+ smb_strtoul(val,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ errno = EIO;
+ goto failed;
+ }
+ } else if (strcasecmp_m(name, "ntname") == 0) {
+ map->nt_name = talloc_strdup(map, val);
+ if (!map->nt_name) {
+ errno = ENOMEM;
+ goto failed;
+ }
+ } else if (strcasecmp_m(name, "comment") == 0) {
+ map->comment = talloc_strdup(map, val);
+ if (!map->comment) {
+ errno = ENOMEM;
+ goto failed;
+ }
+ } else if (strcasecmp_m(name, "member") == 0) {
+ if (!string_to_sid(&members[j], val)) {
+ errno = EIO;
+ goto failed;
+ }
+ }
+
+ TALLOC_FREE(val);
+ }
+
+ TALLOC_FREE(name);
+ }
+
+ if (map->nt_name == NULL) {
+ errno = EIO;
+ goto failed;
+ }
+
+ if (map->comment == NULL) {
+ map->comment = talloc_strdup(map, "");
+ }
+ if (map->comment == NULL) {
+ errno = ENOMEM;
+ goto failed;
+ }
+
+ if (!add_mapping_entry(map, 0)) {
+ errno = EIO;
+ goto failed;
+ }
+
+ if (num_mem) {
+ for (j = 0; j < num_mem; j++) {
+ NTSTATUS status;
+ status = add_aliasmem(&map->sid, &members[j]);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = EIO;
+ goto failed;
+ }
+ }
+ }
+
+ if (remaining != 0) {
+ DEBUG(0, ("Error: %d bytes unread in ltdb_unpack_data\n",
+ remaining));
+ }
+
+ TALLOC_FREE(map);
+ return 0;
+
+failed:
+ TALLOC_FREE(map);
+ return -1;
+}
+
+static bool mapping_switch(const char *ldb_path)
+{
+ TDB_CONTEXT *ltdb;
+ TALLOC_CTX *frame;
+ char *new_path;
+ int ret;
+
+ frame = talloc_stackframe();
+
+ ltdb = tdb_open_log(ldb_path, 0, TDB_DEFAULT, O_RDONLY, 0600);
+ if (ltdb == NULL) goto failed;
+
+ /* ldb is just a very fancy tdb, read out raw data and perform
+ * conversion */
+ ret = tdb_traverse(ltdb, convert_ldb_record, NULL);
+ if (ret < 0) goto failed;
+
+ if (ltdb) {
+ tdb_close(ltdb);
+ ltdb = NULL;
+ }
+
+ /* now rename the old db out of the way */
+ new_path = state_path(talloc_tos(), "group_mapping.ldb.replaced");
+ if (!new_path) {
+ goto failed;
+ }
+ if (rename(ldb_path, new_path) != 0) {
+ DEBUG(0,("Failed to rename old group mapping database\n"));
+ goto failed;
+ }
+ TALLOC_FREE(frame);
+ return True;
+
+failed:
+ DEBUG(0, ("Failed to switch to tdb group mapping database\n"));
+ if (ltdb) tdb_close(ltdb);
+ TALLOC_FREE(frame);
+ return False;
+}
+
+static const struct mapping_backend tdb_backend = {
+ .add_mapping_entry = add_mapping_entry,
+ .get_group_map_from_sid = get_group_map_from_sid,
+ .get_group_map_from_gid = get_group_map_from_gid,
+ .get_group_map_from_ntname = get_group_map_from_ntname,
+ .group_map_remove = group_map_remove,
+ .enum_group_mapping = enum_group_mapping,
+ .one_alias_membership = one_alias_membership,
+ .add_aliasmem = add_aliasmem,
+ .del_aliasmem = del_aliasmem,
+ .enum_aliasmem = enum_aliasmem
+};
+
+/*
+ initialise the tdb mapping backend
+ */
+const struct mapping_backend *groupdb_tdb_init(void)
+{
+ if (!init_group_mapping()) {
+ DEBUG(0,("Failed to initialise tdb mapping backend\n"));
+ return NULL;
+ }
+
+ return &tdb_backend;
+}
diff --git a/source3/groupdb/mapping_tdb.h b/source3/groupdb/mapping_tdb.h
new file mode 100644
index 0000000..86b0ecb
--- /dev/null
+++ b/source3/groupdb/mapping_tdb.h
@@ -0,0 +1,30 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2006,
+ * Copyright (C) Jean François Micouleau 1998-2001.
+ * Copyright (C) Volker Lendecke 2006.
+ * Copyright (C) Gerald Carter 2006.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GROUPDB_MAPPING_TDB_H_
+#define _GROUPDB_MAPPING_TDB_H_
+
+/* The following definitions come from groupdb/mapping_tdb.c */
+
+const struct mapping_backend *groupdb_tdb_init(void);
+
+#endif /* _GROUPDB_MAPPING_TDB_H_ */
diff --git a/source3/include/MacExtensions.h b/source3/include/MacExtensions.h
new file mode 100644
index 0000000..6175ef9
--- /dev/null
+++ b/source3/include/MacExtensions.h
@@ -0,0 +1,255 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) John H Terpstra 1996-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef _MAC_EXTENSIONS_H
+#define _MAC_EXTENSIONS_H
+
+/* Folder that holds the stream info */
+#define STREAM_FOLDER ".streams"
+#define STREAM_FOLDER_SLASH ".streams/"
+
+/* Common Streams Names*/
+#define DefaultStreamTestLen 6
+#define DefaultStreamTest ":$DATA"
+#define AFPDATA_STREAM "::$DATA"
+
+#define AFPINFO_STREAM_NAME ":AFP_AfpInfo"
+#define AFPRESOURCE_STREAM_NAME ":AFP_Resource"
+#define AFPCOMMENTS_STREAM_NAME ":Comments"
+#define AFPDESKTOP_STREAM_NAME ":AFP_DeskTop"
+#define AFPIDINDEX_STREAM_NAME ":AFP_IdIndex"
+
+#define AFPINFO_STREAM AFPINFO_STREAM_NAME ":$DATA"
+#define AFPRESOURCE_STREAM AFPRESOURCE_STREAM_NAME ":$DATA"
+#define AFPCOMMENTS_STREAM AFPCOMMENTS_STREAM_NAME ":$DATA"
+#define AFPDESKTOP_STREAM AFPDESKTOP_STREAM_NAME ":$DATA"
+#define AFPIDINDEX_STREAM AFPIDINDEX_STREAM_NAME ":$DATA"
+
+/*
+** NT's AFP_AfpInfo stream structure
+*/
+#define AFP_INFO_SIZE 0x3c
+#define AFP_Signature 0x41465000
+#define AFP_Version 0x00000100
+#define AFP_BackupTime 0x80000000
+#define AFP_FinderSize 32
+
+#define AFP_OFF_FinderInfo 16
+
+/*
+** Original AFP_AfpInfo stream used by NT
+** We needed a way to store the create date so SAMBA
+** AFP_AfpInfo adds for bytes to this structrure
+** and call's it _SambaAfpInfo
+*/
+typedef struct _AfpInfo
+{
+ uint32_t afpi_Signature; /* Must be *(PDWORD)"AFP" */
+ uint32_t afpi_Version; /* Must be 0x00010000 */
+ uint32_t afpi_Reserved1;
+ uint32_t afpi_BackupTime; /* Backup time for the file/dir */
+ unsigned char afpi_FinderInfo[AFP_FinderSize]; /* Finder Info (32 bytes) */
+ unsigned char afpi_ProDosInfo[6]; /* ProDos Info (6 bytes) # */
+ unsigned char afpi_Reserved2[6];
+} AfpInfo;
+
+typedef struct _SambaAfpInfo
+{
+ AfpInfo afp;
+ unsigned long createtime;
+} SambaAfpInfo;
+
+/*
+** On SAMBA this structure is followed by 4 bytes that store the create
+** date of the file or folder associated with it.
+*/
+
+/*
+** These extensions are only supported with the NT LM 0.12 Dialect. These extensions
+** will be process on a share by share bases.
+*/
+
+/*
+** Trans2_Query_FS_Information Call is used by the MacCIFS extensions for three reasons.
+** First to see if the remote server share supports the basic Macintosh CIFS extensions.
+** Second to return some basic need information about the share to the Macintosh.
+** Third to see if this share support any other Macintosh extensions.
+**
+** We will be using information levels that are between 0x300 and 0x399 for all Macintosh
+** extensions calls. The first of these will be the SMB_MAC_QUERY_FS_INFO level which
+** will allow the server to return the MacQueryFSInfo structure. All fields are Little
+** Endian unless otherwise specified.
+*/
+#define SMB_MAC_QUERY_FS_INFO 0x301
+
+
+
+/*
+** The server will return folder access control in the Trans2_Find_First2
+** and Trans2_Find_Next2 message described later in this document.
+*/
+#define SUPPORT_MAC_ACCESS_CNTRL 0x0010
+/*
+** The server supports setting/getting comments using the mechanism in this
+** document instead of using the NTFS format described in the Introduction.
+*/
+#define SUPPORT_MAC_GETSETCOMMENTS 0x0020
+/*
+** The Server supports setting and getting Macintosh desktop database information
+** using the mechanism in this document.
+*/
+#define SUPPORT_MAC_DESKTOPDB_CALLS 0x0040
+/*
+** The server will return a unique id for files and directories in the
+** Trans2_Find_First2 and Trans2_Find_Next2 message described later in this document.
+*/
+#define SUPPORT_MAC_UNIQUE_IDS 0x0080
+/*
+** The server will return this flag telling the client that the server does
+** not support streams or the Macintosh extensions. The rest of this message
+** will be ignored by the client.
+*/
+#define NO_STREAMS_OR_MAC_SUPPORT 0x0100
+
+/*
+** We will be adding a new info level to the Trans2_Find_First2 and Trans2_Find_Next2.
+** This info level will be SMB_MAC_FIND_BOTH_HFS_INFO and will support the server
+** return additional information need by the Macintosh. All fields are Little
+** Endian unless other wise specified.
+*/
+
+#define SMB_MAC_FIND_BOTH_HFS_INFO 0x302
+
+enum {
+ ownerRead = 0x0400,
+ ownerWrite = 0x0200,
+ ownerSearch = 0x0100,
+ groupRead = 0x0040,
+ groupWrite = 0x0020,
+ groupSearch = 0x0010,
+ otherRead = 0x0004,
+ otherWrite = 0x0002,
+ otherSearch = 0x0001,
+ Owner = 0x0800
+};
+
+
+/*
+** We will be adding a new info level to the Trans2_Set_Path_Information.
+** This info level will be SMB_MAC_SET_FINDER_INFO and will support the client
+** setting information on the server need by the Macintosh. All fields are Little
+** Endian unless other wise specified.
+*/
+
+#define SMB_MAC_SET_FINDER_INFO 0x303
+
+enum {
+ SetCreateDate = 0x01, /* If this is set then set the create date of the file/folder */
+ SetModDate = 0x02, /* If this is set then set the modify date of the file/folder */
+ SetFLAttrib = 0x04, /* If this is set then set the Macintosh lock bit of the file/folder */
+ FndrInfo1 = 0x08, /* If this is set then set the first 16 bytes of finder info */
+ FndrInfo2 = 0x10, /* If this is set then set the second 16 bytes of finder info */
+ SetHidden = 0x20 /* We are either setting or unsetting the hidden bit */
+};
+
+
+/*
+** We will be adding some new info level to the Trans2_Set_Path_Information and Trans2_Query_Path_Information.
+** These info levels will allow the client to add, get, and remove desktop information from the
+** server. How the server stores this information is up to them.
+*/
+
+/*
+** We need to be able to store an application name and its creator in a database. We send a
+** Trans2_Set_Path_Information call with the full path of the application in the path field.
+** We will send an info level that represents adding an application name and creator to the database.
+** We will pass the File Creator in the data message.
+**
+** The server should just respond with no error or an error.
+*/
+#define SMB_MAC_DT_ADD_APPL 0x304
+
+/*
+** We need to be able to remove an application name and its creator from a database. We send a
+** Trans2_Set_Path_Information call with the full path of the application in the path field.
+** We will send an info level that represents removing an application name and creator from the database.
+** We will pass the File Creator in the data message.
+**
+** The server should just respond with no error or an error.
+*/
+#define SMB_MAC_DT_REMOVE_APPL 0x305
+
+
+/*
+** We need to be able to get an application name and its creator from a database. We send a
+** Trans2_Query_Path_Information call in which the name field is just ignore.
+** We will send an info level that represents getting an application name with a structure that
+** contains the File Creator and index. Were index has the following meaning.
+** Index = 0; Get the application path from the database with the most current date.
+** Index > 0; Use the index to find the application path from the database.
+** e.g. index of 5 means get the fifth entry of this application name in the database.
+** if not entry return an error.
+**
+** The server returns with a structure that contains the full path to the application and
+** its creator's date.
+*/
+#define SMB_MAC_DT_GET_APPL 0x306
+
+
+/*
+** We need to be able to get an icon from a database. We send a Trans2_Query_Path_Information call in
+** which the path name is ignore. We will send an info level that represents getting an icon with a structure
+** that contains the Requested size of the icon, the Icon type, File Creator, and File Type.
+**
+** The server returns with a structure that contains the actual size of the icon
+** (must be less than requested length) and the icon bit map.
+*/
+#define SMB_MAC_DT_GET_ICON 0x307
+
+
+/*
+** We need to be able to get an icon from a database. We send a Trans2_Query_Path_Information call in
+** which the path name is ignore. We will send an info level that represents getting an icon with a structure
+** that contains the index and File Creator. The index allows the client to make repeated calls to the server
+** gathering all icon stored by this file creator.
+**
+**
+** The server returns with a structure that contains the actual size of the icon
+** (must be less than requested length) and the icon bit map, File Type, and Icon Type.
+*/
+#define SMB_MAC_DT_GET_ICON_INFO 0x308
+
+
+
+/*
+** We need to be able to add an icon to a database. We send a Trans2_Set_Path_Information call in
+** which the path name is ignore. We will send an info level that represents setting an icon with a structure
+** that contains the icon data, icon size, icon type, the file type, and file creator.
+**
+**
+** The server returns only that the call was successful or not.
+*/
+#define SMB_MAC_DT_ADD_ICON 0x309
+
+#endif /* _MAC_EXTENSIONS_H */
+
+/* _MAC_EXTENSIONS_H */
+
diff --git a/source3/include/ads.h b/source3/include/ads.h
new file mode 100644
index 0000000..6c9e57b
--- /dev/null
+++ b/source3/include/ads.h
@@ -0,0 +1,72 @@
+#ifndef _INCLUDE_ADS_H_
+#define _INCLUDE_ADS_H_
+/*
+ header for ads (active directory) library routines
+
+ basically this is a wrapper around ldap
+*/
+
+#include "libads/ads_status.h"
+#include "smb_ldap.h"
+#include "librpc/gen_ndr/ads.h"
+
+struct ads_saslwrap;
+
+struct ads_saslwrap_ops {
+ const char *name;
+ ADS_STATUS (*wrap)(struct ads_saslwrap *, uint8_t *buf, uint32_t len);
+ ADS_STATUS (*unwrap)(struct ads_saslwrap *);
+ void (*disconnect)(struct ads_saslwrap *);
+};
+
+typedef struct ads_struct ADS_STRUCT;
+
+#ifdef HAVE_ADS
+typedef LDAPMod **ADS_MODLIST;
+#else
+typedef void **ADS_MODLIST;
+#endif
+
+/* time between reconnect attempts */
+#define ADS_RECONNECT_TIME 5
+
+/* ldap control oids */
+#define ADS_PAGE_CTL_OID "1.2.840.113556.1.4.319"
+#define ADS_NO_REFERRALS_OID "1.2.840.113556.1.4.1339"
+#define ADS_SERVER_SORT_OID "1.2.840.113556.1.4.473"
+#define ADS_PERMIT_MODIFY_OID "1.2.840.113556.1.4.1413"
+#define ADS_ASQ_OID "1.2.840.113556.1.4.1504"
+#define ADS_EXTENDED_DN_OID "1.2.840.113556.1.4.529"
+#define ADS_SD_FLAGS_OID "1.2.840.113556.1.4.801"
+
+/* ldap bitwise searches */
+#define ADS_LDAP_MATCHING_RULE_BIT_AND "1.2.840.113556.1.4.803"
+#define ADS_LDAP_MATCHING_RULE_BIT_OR "1.2.840.113556.1.4.804"
+
+#define ADS_PINGS 0x0000FFFF /* Ping response */
+
+enum ads_extended_dn_flags {
+ ADS_EXTENDED_DN_HEX_STRING = 0,
+ ADS_EXTENDED_DN_STRING = 1 /* not supported on win2k */
+};
+
+/* this is probably not very well suited to pass other controls generically but
+ * is good enough for the extended dn control where it is only used for atm */
+
+typedef struct {
+ const char *control;
+ int val;
+ int critical;
+} ads_control;
+
+#include "libads/ads_proto.h"
+
+#ifdef HAVE_LDAP
+#include "libads/ads_ldap_protos.h"
+#endif
+
+#include "libads/kerberos_proto.h"
+
+#define ADS_TALLOC_CONST_FREE(PTR) do { talloc_free(discard_const(PTR)); PTR = NULL; } while (0);
+
+#endif /* _INCLUDE_ADS_H_ */
diff --git a/source3/include/adt_tree.h b/source3/include/adt_tree.h
new file mode 100644
index 0000000..b3d71e7
--- /dev/null
+++ b/source3/include/adt_tree.h
@@ -0,0 +1,46 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Generic Abstract Data Types
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ADT_TREE_H
+#define ADT_TREE_H
+
+struct sorted_tree;
+
+/*
+ * API
+ */
+
+/* create a new tree, talloc_free() to throw it away */
+
+struct sorted_tree *pathtree_init(void *data_p);
+
+/* add a new path component */
+
+bool pathtree_add(struct sorted_tree *tree, const char *path, void *data_p );
+
+/* search path */
+
+void *pathtree_find(struct sorted_tree *tree, char *key );
+
+/* debug (print) functions */
+
+void pathtree_print_keys(struct sorted_tree *tree, int debug );
+
+
+#endif
diff --git a/source3/include/async_smb.h b/source3/include/async_smb.h
new file mode 100644
index 0000000..3c64d08
--- /dev/null
+++ b/source3/include/async_smb.h
@@ -0,0 +1,47 @@
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for async SMB client requests
+ Copyright (C) Volker Lendecke 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __ASYNC_SMB_H__
+#define __ASYNC_SMB_H__
+
+struct cli_state;
+
+struct tevent_req *cli_smb_req_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint8_t smb_command,
+ uint8_t additional_flags,
+ uint16_t additional_flags2,
+ uint8_t wct, uint16_t *vwv,
+ int iov_count,
+ struct iovec *bytes_iov);
+struct tevent_req *cli_smb_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli,
+ uint8_t smb_command,
+ uint8_t additional_flags,
+ uint16_t additional_flags2,
+ uint8_t wct, uint16_t *vwv,
+ uint32_t num_bytes,
+ const uint8_t *bytes);
+NTSTATUS cli_smb_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, uint8_t **pinbuf,
+ uint8_t min_wct, uint8_t *pwct, uint16_t **pvwv,
+ uint32_t *pnum_bytes, uint8_t **pbytes);
+
+#endif
diff --git a/source3/include/auth.h b/source3/include/auth.h
new file mode 100644
index 0000000..69e53bb
--- /dev/null
+++ b/source3/include/auth.h
@@ -0,0 +1,149 @@
+#ifndef _SMBAUTH_H_
+#define _SMBAUTH_H_
+/*
+ Unix SMB/CIFS implementation.
+ Standardised Authentication types
+ Copyright (C) Andrew Bartlett 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "../auth/common_auth.h"
+
+struct gensec_security;
+
+struct extra_auth_info {
+ struct dom_sid user_sid;
+ struct dom_sid pgid_sid;
+};
+
+struct auth_serversupplied_info {
+ bool guest;
+
+ struct security_unix_token utok;
+
+ /*
+ * A complete auth_session_info
+ *
+ * This is not normally filled in, during the typical
+ * authentication process. If filled in, it has already been
+ * finalised by a nasty hack to support a cached guest/system
+ * session_info
+ */
+ const struct auth_session_info *cached_session_info;
+
+ /* These are the intermediate session keys, as provided by a
+ * NETLOGON server and used by NTLMSSP to negotiate key
+ * exchange etc (which will provide the session_key in the
+ * auth_session_info). It is usually the same as the keys in
+ * the info3, but is a variable length structure here to allow
+ * it to be omitted if the auth module does not know it.
+ */
+
+ DATA_BLOB session_key;
+ DATA_BLOB lm_session_key;
+
+ struct netr_SamInfo3 *info3;
+
+ /* this structure is filled *only* in pathological cases where the user
+ * sid or the primary group sid are not sids of the domain. Normally
+ * this happens only for unix accounts that have unix domain sids.
+ * This is checked only when info3.rid and/or info3.primary_gid are set
+ * to the special invalid value of 0xFFFFFFFF */
+ struct extra_auth_info extra;
+
+ /*
+ * This is a token from /etc/passwd and /etc/group
+ */
+ bool nss_token;
+
+ char *unix_name;
+};
+
+struct auth_context;
+
+typedef NTSTATUS (*prepare_gensec_fn)(const struct auth_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct gensec_security **gensec_context);
+
+typedef NTSTATUS (*make_auth4_context_fn)(const struct auth_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ struct auth4_context **auth4_context);
+
+struct auth_context {
+ DATA_BLOB challenge;
+
+ /* What time did this start */
+ struct timeval start_time;
+
+ /* Who set this up in the first place? */
+ const char *challenge_set_by;
+
+ /* What order are the various methods in? Try to stop it changing under us */
+ struct auth_methods *auth_method_list;
+
+ prepare_gensec_fn prepare_gensec;
+ make_auth4_context_fn make_auth4_context;
+ const char *forced_samba4_methods;
+};
+
+struct auth_methods
+{
+ struct auth_methods *prev, *next;
+ const char *name; /* What name got this module */
+
+ NTSTATUS (*auth)(const struct auth_context *auth_context,
+ void *my_private_data,
+ TALLOC_CTX *mem_ctx,
+ const struct auth_usersupplied_info *user_info,
+ struct auth_serversupplied_info **server_info);
+
+ /* Optional methods allowing this module to provide a way to get a gensec context and an auth4_context */
+ prepare_gensec_fn prepare_gensec;
+ make_auth4_context_fn make_auth4_context;
+ /* Used to keep tabs on things like the cli for SMB server authentication */
+ void *private_data;
+
+ uint32_t flags;
+
+};
+
+typedef NTSTATUS (*auth_init_function)(struct auth_context *, const char *, struct auth_methods **);
+
+struct auth_init_function_entry {
+ const char *name;
+ /* Function to create a member of the authmethods list */
+
+ auth_init_function init;
+
+ struct auth_init_function_entry *prev, *next;
+};
+
+extern const struct gensec_security_ops gensec_ntlmssp3_server_ops;
+
+/* Intent of use for session key. LSA and SAMR pipes use 16 bytes of session key when doing create/modify calls */
+enum session_key_use_intent {
+ KEY_USE_FULL = 0,
+ KEY_USE_16BYTES
+};
+
+/* Changed from 1 -> 2 to add the logon_parameters field. */
+/* Changed from 2 -> 3 when we reworked many auth structures to use IDL or be in common with Samba4 */
+/* Changed from 3 -> 4 when we reworked added the flags */
+/* Changed from 4 -> 5 as module init functions now take a TALLOC_CTX * */
+#define AUTH_INTERFACE_VERSION 5
+
+#include "auth/proto.h"
+
+#endif /* _SMBAUTH_H_ */
diff --git a/source3/include/auth_generic.h b/source3/include/auth_generic.h
new file mode 100644
index 0000000..0911182
--- /dev/null
+++ b/source3/include/auth_generic.h
@@ -0,0 +1,53 @@
+/*
+ NLTMSSP wrappers
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett 2001-2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _AUTH_GENERIC_
+#define _AUTH_GENERIC_
+
+struct gensec_security;
+
+struct auth_generic_state {
+ /* used only by the client implementation */
+ struct cli_credentials *credentials;
+
+ /* used by both */
+ struct gensec_security *gensec_security;
+};
+
+NTSTATUS auth_generic_set_username(struct auth_generic_state *ans,
+ const char *user);
+NTSTATUS auth_generic_set_domain(struct auth_generic_state *ans,
+ const char *domain);
+NTSTATUS auth_generic_set_password(struct auth_generic_state *ans,
+ const char *password);
+NTSTATUS auth_generic_set_creds(struct auth_generic_state *ans,
+ struct cli_credentials *creds);
+NTSTATUS auth_generic_client_prepare(TALLOC_CTX *mem_ctx,
+ struct auth_generic_state **_ans);
+NTSTATUS auth_generic_client_start(struct auth_generic_state *ans, const char *oid);
+NTSTATUS auth_generic_client_start_by_name(struct auth_generic_state *ans,
+ const char *name);
+NTSTATUS auth_generic_client_start_by_authtype(struct auth_generic_state *ans,
+ uint8_t auth_type,
+ uint8_t auth_level);
+NTSTATUS auth_generic_client_start_by_sasl(struct auth_generic_state *ans,
+ const char **sasl_list);
+
+#endif /* _AUTH_GENERIC_ */
diff --git a/source3/include/client.h b/source3/include/client.h
new file mode 100644
index 0000000..e1a394a
--- /dev/null
+++ b/source3/include/client.h
@@ -0,0 +1,131 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Jeremy Allison 1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _CLIENT_H
+#define _CLIENT_H
+
+#define CLI_BUFFER_SIZE SMB_BUFFER_SIZE_MAX
+
+/* default client timeout to 20 seconds on most commands */
+#define CLIENT_TIMEOUT (20 * 1000)
+
+/*
+ * These definitions depend on smb.h
+ */
+
+struct print_job_info {
+ uint16_t id;
+ uint16_t priority;
+ size_t size;
+ fstring user;
+ fstring name;
+ time_t t;
+};
+
+struct smbXcli_conn;
+struct smbXcli_session;
+
+struct cli_state {
+ /**
+ * A list of subsidiary connections for DFS.
+ */
+ struct cli_state *prev, *next;
+ int rap_error;
+ NTSTATUS raw_status; /* maybe via NT_STATUS_DOS() */
+ bool map_dos_errors;
+
+ /*
+ * The following strings are the
+ * ones returned by the server if
+ * the protocol > NT1.
+ */
+ char *server_type;
+ char *server_os;
+ char *server_domain;
+
+ char *share;
+ char *dev;
+
+ int timeout; /* in milliseconds. */
+ int initialised;
+ int win95;
+ /* What the server offered. */
+ uint32_t server_posix_capabilities;
+ /* What the client requested. */
+ uint32_t requested_posix_capabilities;
+ bool backup_intent;
+
+ /* The list of pipes currently open on this connection. */
+ struct rpc_pipe_client *pipe_list;
+
+ bool use_oplocks; /* should we use oplocks? */
+
+ struct smbXcli_conn *conn;
+
+ struct {
+ uint32_t pid;
+ uint16_t vc_num;
+ struct smbXcli_session *session;
+ struct smbXcli_tcon *tcon;
+ } smb1;
+
+ struct {
+ struct smbXcli_session *session;
+ struct smbXcli_tcon *tcon;
+ struct idr_context *open_handles;
+ bool client_smb311_posix;
+ } smb2;
+};
+
+struct file_info {
+ uint64_t size;
+ uint64_t allocated_size;
+ uint32_t attr;
+ uid_t uid;
+ gid_t gid;
+ uint64_t ino;
+ /* these times are normally kept in GMT */
+ struct timespec btime_ts; /* Birth-time if supported by system */
+ struct timespec mtime_ts;
+ struct timespec atime_ts;
+ struct timespec ctime_ts;
+ char *name;
+ char *short_name;
+ uint32_t reparse_tag;
+ dev_t st_ex_dev;
+ mode_t st_ex_mode;
+ nlink_t st_ex_nlink;
+ struct dom_sid owner_sid;
+ struct dom_sid group_sid;
+};
+
+#define CLI_FULL_CONNECTION_DONT_SPNEGO 0x0001
+#define CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK 0x0004
+#define CLI_FULL_CONNECTION_OPLOCKS 0x0010
+#define CLI_FULL_CONNECTION_LEVEL_II_OPLOCKS 0x0020
+#define CLI_FULL_CONNECTION_FORCE_DOS_ERRORS 0x0080
+#define CLI_FULL_CONNECTION_FORCE_ASCII 0x0100
+#define CLI_FULL_CONNECTION_FORCE_SMB1 0x0400
+#define CLI_FULL_CONNECTION_DISABLE_SMB1 0x0800
+#define CLI_FULL_CONNECTION_IPC 0x1000
+#define CLI_FULL_CONNECTION_REQUEST_POSIX 0x2000
+
+#endif /* _CLIENT_H */
diff --git a/source3/include/ctdb_srvids.h b/source3/include/ctdb_srvids.h
new file mode 100644
index 0000000..b51a458
--- /dev/null
+++ b/source3/include/ctdb_srvids.h
@@ -0,0 +1,46 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba3 ctdb srvid assignments
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * ctdb has reserved all srvids starting with 0xFE for Samba. Here we list our
+ * static assignments that are supposed to be globally unique.
+ */
+
+/*
+ * ctdb_protocol.h already has the following definition, used in the g_lock
+ * implementation. Waiters for a g_lock register this to receive notifications
+ * when g_lock holders die.
+ */
+
+#if 0
+#define CTDB_SRVID_SAMBA_NOTIFY 0xFE00000000000000LL
+#endif
+
+/*
+ * SRVID for notify_internal.c: On every node, one process registers this
+ * SRVID. It receives filechangenotify notifications and multicasts them
+ * locally according to the non-clustered local notify.tdb
+ */
+#define CTDB_SRVID_SAMBA_NOTIFY_PROXY 0xFE00000000000001LL
+
+/*
+ * SRVID for all processes that come from Samba. Used to be
+ * MSG_SRVID_SAMBA in the past. Now used for message_send_all.
+ */
+#define CTDB_SRVID_SAMBA_PROCESS 0xFE00000000000002LL
diff --git a/source3/include/ctdbd_conn.h b/source3/include/ctdbd_conn.h
new file mode 100644
index 0000000..0618d07
--- /dev/null
+++ b/source3/include/ctdbd_conn.h
@@ -0,0 +1,194 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba3 ctdb connection handling
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _CTDBD_CONN_H
+#define _CTDBD_CONN_H
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/network.h"
+#include "lib/dbwrap/dbwrap.h"
+#include <tdb.h>
+#include <tevent.h>
+
+struct ctdbd_connection;
+struct messaging_context;
+struct messaging_rec;
+
+int ctdbd_init_connection(TALLOC_CTX *mem_ctx,
+ const char *sockname, int timeout,
+ struct ctdbd_connection **pconn);
+int ctdbd_init_async_connection(
+ TALLOC_CTX *mem_ctx,
+ const char *sockname,
+ int timeout,
+ struct ctdbd_connection **pconn);
+int ctdbd_reinit_connection(TALLOC_CTX *mem_ctx,
+ const char *sockname, int timeout,
+ struct ctdbd_connection *conn);
+
+uint32_t ctdbd_vnn(const struct ctdbd_connection *conn);
+
+int ctdbd_conn_get_fd(struct ctdbd_connection *conn);
+void ctdbd_socket_readable(struct tevent_context *ev,
+ struct ctdbd_connection *conn);
+
+int ctdbd_messaging_send_iov(struct ctdbd_connection *conn,
+ uint32_t dst_vnn, uint64_t dst_srvid,
+ const struct iovec *iov, int iovlen);
+
+bool ctdbd_process_exists(struct ctdbd_connection *conn, uint32_t vnn,
+ pid_t pid, uint64_t unique_id);
+
+char *ctdbd_dbpath(struct ctdbd_connection *conn,
+ TALLOC_CTX *mem_ctx, uint32_t db_id);
+
+int ctdbd_db_attach(struct ctdbd_connection *conn, const char *name,
+ uint32_t *db_id, bool persistent);
+
+int ctdbd_migrate(struct ctdbd_connection *conn, uint32_t db_id, TDB_DATA key);
+
+int ctdbd_parse(struct ctdbd_connection *conn, uint32_t db_id,
+ TDB_DATA key, bool local_copy,
+ void (*parser)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data);
+
+int ctdbd_traverse(struct ctdbd_connection *master, uint32_t db_id,
+ void (*fn)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data);
+
+int ctdbd_register_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *server,
+ const struct sockaddr_storage *client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data),
+ void *private_data);
+void ctdbd_unregister_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data);
+void ctdbd_passed_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data);
+
+/*
+ * call @cb for each public IP. If @cb returns non-zero, then break the loop
+ * and propagate the return value upwards.
+ * @returns: 0 on success, where all @cb invocations also returned zero
+ * ENOMEM on memory allocation failure
+ * EIO on ctdbd connection failure
+ * @cb() return value if non-zero
+ */
+int ctdbd_public_ip_foreach(struct ctdbd_connection *conn,
+ int (*cb)(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ bool is_movable_ip,
+ void *private_data),
+ void *private_data);
+int ctdbd_all_ip_foreach(struct ctdbd_connection *conn,
+ bool include_node_ips,
+ bool include_public_ips,
+ int (*cb)(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ uint32_t pinned_pnn,
+ uint32_t current_pnn,
+ void *private_data),
+ void *private_data);
+
+int ctdbd_control_local(struct ctdbd_connection *conn, uint32_t opcode,
+ uint64_t srvid, uint32_t flags, TDB_DATA data,
+ TALLOC_CTX *mem_ctx, TDB_DATA *outdata,
+ int32_t *cstatus);
+int ctdb_watch_us(struct ctdbd_connection *conn);
+int ctdb_unwatch(struct ctdbd_connection *conn);
+
+struct ctdb_req_message_old;
+
+int register_with_ctdbd(struct ctdbd_connection *conn, uint64_t srvid,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data),
+ void *private_data);
+void deregister_from_ctdbd(struct ctdbd_connection *conn,
+ uint64_t srvid,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data);
+int ctdbd_probe(const char *sockname, int timeout);
+
+struct ctdb_req_header;
+void ctdbd_prep_hdr_next_reqid(
+ struct ctdbd_connection *conn, struct ctdb_req_header *hdr);
+
+/*
+ * Async ctdb_request. iov[0] must start with an initialized
+ * struct ctdb_req_header
+ */
+struct tevent_req *ctdbd_req_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ struct iovec *iov,
+ size_t num_iov);
+int ctdbd_req_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_header **reply);
+
+struct tevent_req *ctdbd_parse_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ uint32_t db_id,
+ TDB_DATA key,
+ bool local_copy,
+ void (*parser)(TDB_DATA key,
+ TDB_DATA data,
+ void *private_data),
+ void *private_data,
+ enum dbwrap_req_state *req_state);
+int ctdbd_parse_recv(struct tevent_req *req);
+
+#endif /* _CTDBD_CONN_H */
diff --git a/source3/include/fake_file.h b/source3/include/fake_file.h
new file mode 100644
index 0000000..f688ed2
--- /dev/null
+++ b/source3/include/fake_file.h
@@ -0,0 +1,51 @@
+/*
+ Unix SMB/CIFS implementation.
+ FAKE FILE support, for faking up special files windows want access to
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _FAKE_FILE_H
+#define _FAKE_FILE_H
+
+enum FAKE_FILE_TYPE {
+ FAKE_FILE_TYPE_NONE = 0,
+ FAKE_FILE_TYPE_QUOTA,
+ FAKE_FILE_TYPE_NAMED_PIPE_PROXY
+};
+
+/*
+we now get the unix name --metze
+*/
+#define FAKE_FILE_NAME_QUOTA_WIN32 "\\$Extend\\$Quota:$Q:$INDEX_ALLOCATION"
+#define FAKE_FILE_NAME_QUOTA_UNIX "$Extend/$Quota:$Q:$INDEX_ALLOCATION"
+
+struct fake_file_handle {
+ enum FAKE_FILE_TYPE type;
+ void *private_data;
+};
+
+enum FAKE_FILE_TYPE is_fake_file_path(const char *path);
+enum FAKE_FILE_TYPE is_fake_file(const struct smb_filename *smb_fname);
+NTSTATUS open_fake_file(struct smb_request *req, connection_struct *conn,
+ uint64_t current_vuid,
+ enum FAKE_FILE_TYPE fake_file_type,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ files_struct **result);
+NTSTATUS close_fake_file(struct smb_request *req, files_struct *fsp);
+uint32_t dosmode_from_fake_filehandle(const struct fake_file_handle *ffh);
+
+#endif /* _FAKE_FILE_H */
diff --git a/source3/include/g_lock.h b/source3/include/g_lock.h
new file mode 100644
index 0000000..d6c1521
--- /dev/null
+++ b/source3/include/g_lock.h
@@ -0,0 +1,134 @@
+/*
+ Unix SMB/CIFS implementation.
+ global locks based on dbwrap and messaging
+ Copyright (C) 2009 by Volker Lendecke
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _G_LOCK_H_
+#define _G_LOCK_H_
+
+#include "replace.h"
+#include "librpc/gen_ndr/server_id.h"
+#include "dbwrap/dbwrap.h"
+
+struct g_lock_ctx;
+struct g_lock_lock_cb_state;
+struct messaging_context;
+
+enum g_lock_type {
+ G_LOCK_READ,
+ G_LOCK_WRITE,
+ G_LOCK_UPGRADE,
+ G_LOCK_DOWNGRADE,
+};
+
+struct g_lock_ctx *g_lock_ctx_init_backend(
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct db_context **backend);
+void g_lock_set_lock_order(struct g_lock_ctx *ctx,
+ enum dbwrap_lock_order lock_order);
+struct g_lock_ctx *g_lock_ctx_init(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg);
+
+typedef void (*g_lock_lock_cb_fn_t)(struct g_lock_lock_cb_state *glck,
+ void *cb_private);
+
+NTSTATUS g_lock_lock_cb_dump(struct g_lock_lock_cb_state *glck,
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data),
+ void *private_data);
+NTSTATUS g_lock_lock_cb_writev(struct g_lock_lock_cb_state *glck,
+ const TDB_DATA *dbufs,
+ size_t num_dbufs);
+void g_lock_lock_cb_unlock(struct g_lock_lock_cb_state *glck);
+struct tevent_req *g_lock_lock_cb_watch_data_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_lock_cb_state *cb_state,
+ struct server_id blocker);
+NTSTATUS g_lock_lock_cb_watch_data_recv(
+ struct tevent_req *req,
+ bool *blockerdead,
+ struct server_id *blocker);
+void g_lock_lock_cb_wake_watchers(struct g_lock_lock_cb_state *cb_state);
+
+struct tevent_req *g_lock_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ enum g_lock_type type,
+ g_lock_lock_cb_fn_t cb_fn,
+ void *cb_private);
+NTSTATUS g_lock_lock_recv(struct tevent_req *req);
+NTSTATUS g_lock_lock(struct g_lock_ctx *ctx, TDB_DATA key,
+ enum g_lock_type lock_type, struct timeval timeout,
+ g_lock_lock_cb_fn_t cb_fn,
+ void *cb_private);
+NTSTATUS g_lock_unlock(struct g_lock_ctx *ctx, TDB_DATA key);
+
+NTSTATUS g_lock_writev_data(
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ const TDB_DATA *dbufs,
+ size_t num_dbufs);
+NTSTATUS g_lock_write_data(struct g_lock_ctx *ctx, TDB_DATA key,
+ const uint8_t *buf, size_t buflen);
+
+int g_lock_locks(struct g_lock_ctx *ctx,
+ int (*fn)(TDB_DATA key, void *private_data),
+ void *private_data);
+struct tevent_req *g_lock_dump_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data),
+ void *private_data);
+NTSTATUS g_lock_dump_recv(struct tevent_req *req);
+NTSTATUS g_lock_dump(struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data),
+ void *private_data);
+int g_lock_seqnum(struct g_lock_ctx *ctx);
+
+struct tevent_req *g_lock_watch_data_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ struct server_id blocker);
+NTSTATUS g_lock_watch_data_recv(
+ struct tevent_req *req,
+ bool *blockerdead,
+ struct server_id *blocker);
+void g_lock_wake_watchers(struct g_lock_ctx *ctx, TDB_DATA key);
+
+#endif
diff --git a/source3/include/idmap.h b/source3/include/idmap.h
new file mode 100644
index 0000000..647badd
--- /dev/null
+++ b/source3/include/idmap.h
@@ -0,0 +1,74 @@
+#ifndef _IDMAP_H_
+#define _IDMAP_H_
+/*
+ Unix SMB/CIFS implementation.
+
+ Idmap headers
+
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Simo Sorce 2003
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* The interface version specifier.
+ Updated to 3 for enum types by JRA. */
+
+/* Updated to 4, completely new interface, SSS */
+/* Updated to 5, simplified interface by Volker */
+/* Updated to 6, modules now take TALLOC_CTX * init parameter. */
+
+#define SMB_IDMAP_INTERFACE_VERSION 6
+
+#include "librpc/gen_ndr/idmap.h"
+
+struct wbint_userinfo;
+
+struct idmap_domain {
+ const char *name;
+ /*
+ * dom_sid is currently only initialized in the unixids_to_sids request,
+ * so don't rely on this being filled out everywhere!
+ */
+ struct dom_sid dom_sid;
+ const struct idmap_methods *methods;
+ NTSTATUS (*query_user)(struct idmap_domain *domain,
+ struct wbint_userinfo *info);
+ uint32_t low_id;
+ uint32_t high_id;
+ bool read_only;
+ void *private_data;
+};
+
+/* Filled out by IDMAP backends */
+struct idmap_methods {
+
+ /* Called when backend is first loaded */
+ NTSTATUS (*init)(struct idmap_domain *dom);
+
+ /* Map an array of uids/gids to SIDs. The caller specifies
+ the uid/gid and type. Gets back the SID. */
+ NTSTATUS (*unixids_to_sids)(struct idmap_domain *dom, struct id_map **ids);
+
+ /* Map an array of SIDs to uids/gids. The caller sets the SID
+ and type and gets back a uid or gid. */
+ NTSTATUS (*sids_to_unixids)(struct idmap_domain *dom, struct id_map **ids);
+
+ /* Allocate a Unix-ID. */
+ NTSTATUS (*allocate_id)(struct idmap_domain *dom, struct unixid *id);
+};
+
+#include "winbindd/idmap_proto.h"
+
+#endif /* _IDMAP_H_ */
diff --git a/source3/include/idmap_autorid_tdb.h b/source3/include/idmap_autorid_tdb.h
new file mode 100644
index 0000000..aa65222
--- /dev/null
+++ b/source3/include/idmap_autorid_tdb.h
@@ -0,0 +1,214 @@
+/*
+ * idmap_autorid: static map between Active Directory/NT RIDs
+ * and RFC 2307 accounts. This file contains common functions
+ * and structures used by idmap_autorid and net idmap autorid utilities
+ *
+ * Copyright (C) Christian Ambach, 2010-2012
+ * Copyright (C) Atul Kulkarni, 2013
+ * Copyright (C) Michael Adam, 2012-2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _IDMAP_AUTORID_H_
+#define _IDMAP_AUTORID_H_
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../lib/util/util_tdb.h"
+#include "winbindd/idmap_tdb_common.h"
+
+#define HWM "NEXT RANGE"
+#define ALLOC_HWM_UID "NEXT ALLOC UID"
+#define ALLOC_HWM_GID "NEXT ALLOC GID"
+#define ALLOC_RANGE "ALLOC"
+#define CONFIGKEY "CONFIG"
+
+struct autorid_global_config {
+ uint32_t minvalue;
+ uint32_t rangesize;
+ uint32_t maxranges;
+};
+
+struct autorid_range_config {
+ fstring domsid;
+ uint32_t rangenum;
+ uint32_t domain_range_index;
+ uint32_t low_id;
+ uint32_t high_id;
+};
+
+/**
+ * Get the range for a pair consisting of the domain sid
+ * and a domain range. If there is no stored range for
+ * this pair and read_only == false, a new range is
+ * acquired by incrementing that range HWM counter in the
+ * database.
+ */
+NTSTATUS idmap_autorid_get_domainrange(struct db_context *db,
+ struct autorid_range_config *range,
+ bool read_only);
+
+/**
+ * get the domain range and low_id for the domain
+ * identified by domsid and domain_range_index
+ */
+NTSTATUS idmap_autorid_getrange(struct db_context *db,
+ const char *domsid,
+ uint32_t domain_range_index,
+ uint32_t *rangenum,
+ uint32_t *low_id);
+
+/**
+ * Set a range for a domain#index pair to a given
+ * number. Fail if a different range was already stored.
+ */
+NTSTATUS idmap_autorid_setrange(struct db_context *db,
+ const char *domsid,
+ uint32_t domain_range_index,
+ uint32_t rangenum);
+
+NTSTATUS idmap_autorid_acquire_range(struct db_context *db,
+ struct autorid_range_config *range);
+
+/**
+ * Delete a domain#index <-> range mapping from the database.
+ * The mapping is specified by the sid and index.
+ * If force == true, invalid mapping records are deleted as far
+ * as possible, otherwise they are left untouched.
+ */
+NTSTATUS idmap_autorid_delete_range_by_sid(struct db_context *db,
+ const char *domsid,
+ uint32_t domain_range_index,
+ bool force);
+
+/**
+ * Delete a domain#index <-> range mapping from the database.
+ * The mapping is specified by the range number.
+ * If force == true, invalid mapping records are deleted as far
+ * as possible, otherwise they are left untouched.
+ */
+NTSTATUS idmap_autorid_delete_range_by_num(struct db_context *db,
+ uint32_t rangenum,
+ bool force);
+
+/**
+ * Initialize a specified HWM value to 0 if it is not
+ * yet present in the database.
+ */
+NTSTATUS idmap_autorid_init_hwm(struct db_context *db, const char *hwm);
+
+/**
+ * Open and possibly create the autorid database.
+ */
+NTSTATUS idmap_autorid_db_open(const char *path,
+ TALLOC_CTX *mem_ctx,
+ struct db_context **db);
+
+/**
+ * Initialize the high watermark records in the database.
+ */
+NTSTATUS idmap_autorid_init_hwms(struct db_context *db);
+
+/**
+ * Initialize an idmap_autorid database.
+ * After this function has successfully completed, the following are true:
+ * - the database exists
+ * - the required HWM keys exist (range, alloc-uid, alloc-gid)
+ */
+NTSTATUS idmap_autorid_db_init(const char *path,
+ TALLOC_CTX *mem_ctx,
+ struct db_context **db);
+
+/**
+ * Load the configuration stored in the autorid database.
+ */
+NTSTATUS idmap_autorid_loadconfig(struct db_context *db,
+ struct autorid_global_config *result);
+
+/**
+ * Save the global autorid configuration into the autorid database.
+ * The stored configuration consists of:
+ * - the low value of the idmap range
+ * - the rangesize
+ * - the maximum number of ranges
+ */
+NTSTATUS idmap_autorid_saveconfig(struct db_context *db,
+ struct autorid_global_config *cfg);
+
+/**
+ * get the range config string stored in the database
+ */
+NTSTATUS idmap_autorid_getconfigstr(struct db_context *db, TALLOC_CTX *mem_ctx,
+ char **result);
+
+/**
+ * parse the handed in config string and fill the provided config structure.
+ * return false if the string could not be parsed.
+ */
+bool idmap_autorid_parse_configstr(const char *configstr,
+ struct autorid_global_config *cfg);
+
+
+/**
+ * Save the global autorid configuration into the autorid database
+ * as provided in the config string.
+ * First parse the configstr and validate it.
+ */
+NTSTATUS idmap_autorid_saveconfigstr(struct db_context *db,
+ const char *configstr);
+
+
+/**
+ * idmap_autorid_iterate_domain_ranges:
+ * perform an action on all domain range mappings for a given domain
+ * specified by domain sid.
+ */
+NTSTATUS idmap_autorid_iterate_domain_ranges(struct db_context *db,
+ const char *domsid,
+ NTSTATUS (*fn)(struct db_context *db,
+ const char *domsid,
+ uint32_t index,
+ uint32_t rangenum,
+ void *private_data),
+ void *private_data,
+ int *count);
+
+/**
+ * idmap_autorid_iterate_domain_ranges_read:
+ * perform a read only action on all domain range mappings for a given domain
+ * specified by domain sid.
+ */
+NTSTATUS idmap_autorid_iterate_domain_ranges_read(struct db_context *db,
+ const char *domsid,
+ NTSTATUS (*fn)(struct db_context *db,
+ const char *domsid,
+ uint32_t index,
+ uint32_t rangenum,
+ void *private_data),
+ void *private_data,
+ int *count);
+
+/**
+ * delete all range mappings for a given domain
+ */
+NTSTATUS idmap_autorid_delete_domain_ranges(struct db_context *db,
+ const char *domsid,
+ bool force,
+ int *count);
+
+#endif /* _IDMAP_AUTORID_H_ */
diff --git a/source3/include/includes.h b/source3/include/includes.h
new file mode 100644
index 0000000..1e7b79b
--- /dev/null
+++ b/source3/include/includes.h
@@ -0,0 +1,360 @@
+#ifndef _INCLUDES_H
+#define _INCLUDES_H
+/*
+ Unix SMB/CIFS implementation.
+ Machine customisation and include handling
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) 2002 by Martin Pool <mbp@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "../replace/replace.h"
+
+/* make sure we have included the correct config.h */
+#ifndef NO_CONFIG_H /* for some tests */
+#ifndef CONFIG_H_IS_FROM_SAMBA
+#error "make sure you have removed all config.h files from standalone builds!"
+#error "the included config.h isn't from samba!"
+#endif
+#endif /* NO_CONFIG_H */
+
+/* only do the C++ reserved word check when we compile
+ to include --with-developer since too many systems
+ still have conflicts with their header files (e.g. IRIX 6.4) */
+
+#if !defined(__cplusplus) && defined(DEVELOPER) && defined(__linux__)
+#define class #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define private #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define public #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define protected #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define template #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define this #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define new #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define delete #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#define friend #error DONT_USE_CPLUSPLUS_RESERVED_NAMES
+#endif
+
+#include "local.h"
+
+#ifdef SUNOS4
+/* on SUNOS4 termios.h conflicts with sys/ioctl.h */
+#undef HAVE_TERMIOS_H
+#endif
+
+#ifdef RELIANTUNIX
+/*
+ * <unistd.h> has to be included before any other to get
+ * large file support on Reliant UNIX. Yes, it's broken :-).
+ */
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#endif /* RELIANTUNIX */
+
+#include "system/dir.h"
+#include "system/locale.h"
+#include "system/time.h"
+#include "system/wait.h"
+
+#ifndef HAVE_KRB5_H
+#undef HAVE_KRB5
+#endif
+
+#ifndef HAVE_LDAP_H
+#undef HAVE_LDAP
+#endif
+
+#ifdef HAVE_SYS_ATTRIBUTES_H
+#include <sys/attributes.h>
+#endif
+
+#ifndef ENOATTR
+#if defined(ENODATA)
+#define ENOATTR ENODATA
+#else
+#define ENOATTR ENOENT
+#endif
+#endif
+
+#ifdef HAVE_SYS_UIO_H
+#include <sys/uio.h>
+#endif
+
+#ifdef HAVE_LANGINFO_H
+#include <langinfo.h>
+#endif
+
+/* Special macros that are no-ops except when run under Valgrind on
+ * x86. They've moved a little bit from valgrind 1.0.4 to 1.9.4 */
+#ifdef HAVE_VALGRIND_MEMCHECK_H
+ /* memcheck.h includes valgrind.h */
+#include <valgrind/memcheck.h>
+#elif defined(HAVE_VALGRIND_H)
+#include <valgrind.h>
+#endif
+
+/* we support ADS if we want it and have krb5 and ldap libs */
+#if defined(WITH_ADS) && defined(HAVE_KRB5) && defined(HAVE_LDAP)
+#define HAVE_ADS
+#endif
+
+/*
+ * Define additional missing types
+ */
+#if defined(AIX)
+typedef sig_atomic_t SIG_ATOMIC_T;
+#else
+typedef sig_atomic_t volatile SIG_ATOMIC_T;
+#endif
+
+#ifndef uchar
+#define uchar unsigned char
+#endif
+
+/*
+ * Types for devices, inodes and offsets.
+ */
+
+#ifndef SMB_DEV_T
+# define SMB_DEV_T dev_t
+#endif
+
+#ifndef LARGE_SMB_DEV_T
+# if (defined(SIZEOF_DEV_T) && (SIZEOF_DEV_T == 8))
+# define LARGE_SMB_DEV_T 1
+# endif
+#endif
+
+#ifdef LARGE_SMB_DEV_T
+#define SDEV_T_VAL(p, ofs, v) (SIVAL((p),(ofs),(v)&0xFFFFFFFF), SIVAL((p),(ofs)+4,(v)>>32))
+#define DEV_T_VAL(p, ofs) ((SMB_DEV_T)(((uint64_t)(IVAL((p),(ofs))))| (((uint64_t)(IVAL((p),(ofs)+4))) << 32)))
+#else
+#define SDEV_T_VAL(p, ofs, v) (SIVAL((p),(ofs),v),SIVAL((p),(ofs)+4,0))
+#define DEV_T_VAL(p, ofs) ((SMB_DEV_T)(IVAL((p),(ofs))))
+#endif
+
+/*
+ * Setup the correctly sized inode type.
+ */
+
+#ifndef SMB_INO_T
+# define SMB_INO_T ino_t
+#endif
+
+#ifndef LARGE_SMB_INO_T
+# if (defined(SIZEOF_INO_T) && (SIZEOF_INO_T == 8))
+# define LARGE_SMB_INO_T 1
+# endif
+#endif
+
+#ifdef LARGE_SMB_INO_T
+#define SINO_T_VAL(p, ofs, v) SBVAL(p, ofs, v)
+#define INO_T_VAL(p, ofs) ((SMB_INO_T)BVAL(p, ofs))
+#else
+#define SINO_T_VAL(p, ofs, v) SBVAL(p, ofs, ((uint64_t)(v)) & UINT32_MAX)
+#define INO_T_VAL(p, ofs) ((SMB_INO_T)(IVAL((p),(ofs))))
+#endif
+
+/* TODO: remove this macros */
+#define SBIG_UINT(p, ofs, v) SBVAL(p, ofs, v)
+#define BIG_UINT(p, ofs) BVAL(p, ofs)
+#define IVAL2_TO_SMB_BIG_UINT(p, ofs) BVAL(p, ofs)
+
+/*
+ * Set the define that tells us if we can do 64 bit
+ * NT SMB calls.
+ */
+
+#define SOFF_T(p, ofs, v) (SIVAL(p,ofs,(v)&0xFFFFFFFF), SIVAL(p,(ofs)+4,(v)>>32))
+#define SOFF_T_R(p, ofs, v) (SIVAL(p,(ofs)+4,(v)&0xFFFFFFFF), SIVAL(p,ofs,(v)>>32))
+#define IVAL_TO_SMB_OFF_T(buf,off) ((off_t)(( ((uint64_t)(IVAL((buf),(off)))) & ((uint64_t)0xFFFFFFFF) )))
+
+/* Is birthtime real, or was it calculated ? */
+#define ST_EX_IFLAG_CALCULATED_BTIME (1 << 0)
+
+/*
+ * Type for stat structure.
+ */
+
+struct stat_ex {
+ dev_t st_ex_dev;
+ ino_t st_ex_ino;
+ mode_t st_ex_mode;
+ nlink_t st_ex_nlink;
+ uid_t st_ex_uid;
+ gid_t st_ex_gid;
+ dev_t st_ex_rdev;
+ off_t st_ex_size;
+ struct timespec st_ex_atime;
+ struct timespec st_ex_mtime;
+ struct timespec st_ex_ctime;
+ struct timespec st_ex_btime; /* birthtime */
+ uint32_t cached_dos_attributes;
+
+ blksize_t st_ex_blksize;
+ blkcnt_t st_ex_blocks;
+
+ uint32_t st_ex_flags;
+ uint32_t st_ex_iflags;
+};
+
+typedef struct stat_ex SMB_STRUCT_STAT;
+
+enum timestamp_set_resolution {
+ TIMESTAMP_SET_SECONDS = 0,
+ TIMESTAMP_SET_MSEC,
+ TIMESTAMP_SET_NT_OR_BETTER
+};
+
+/* Our own fstrings */
+
+/*
+ --------------
+ / \
+ / REST \
+ / IN \
+ / PEACE \
+ / \
+ | The infamous pstring |
+ | |
+ | |
+ | 7 December |
+ | |
+ | 2007 |
+ *| * * * | *
+ _________)/\\_//(\/(/\)/\//\/\///|_)_______
+*/
+
+#ifndef FSTRING_LEN
+#define FSTRING_LEN 256
+typedef char fstring[FSTRING_LEN];
+#endif
+
+/* debug.h need to be included before samba_util.h for the macro SMB_ASSERT */
+#include "../lib/util/debug.h"
+
+/* Lists, trees, caching, database... */
+#include "../lib/util/samba_util.h"
+#include "../lib/util/util_net.h"
+#include "../lib/util/attr.h"
+#include "../lib/util/tsort.h"
+#include "../lib/util/dlinklist.h"
+
+#include <talloc.h>
+#include <tevent.h>
+
+#include "../lib/util/data_blob.h"
+#include "../lib/util/time.h"
+#include "../lib/util/debug_s3.h"
+
+#include "../libcli/util/ntstatus.h"
+#include "../libcli/util/error.h"
+#include "../lib/util/charset/charset.h"
+#include "dynconfig/dynconfig.h"
+#include "locking.h"
+#include "smb.h"
+#include "../lib/util/byteorder.h"
+
+#include "../lib/util/samba_modules.h"
+#include "../lib/util/talloc_stack.h"
+
+/* samba_setXXid functions. */
+#include "../lib/util/setid.h"
+
+/***** prototypes *****/
+#ifndef NO_PROTO_H
+#include "proto.h"
+#endif
+
+#include "lib/param/loadparm.h"
+#include "source3/param/loadparm.h"
+/* Automatically generated by generate_param.py. */
+#include "source3/param/param_proto.h"
+
+/* String routines */
+
+#include "srvstr.h"
+#include "lib/util/safe_string.h"
+
+#ifndef SIGCLD
+#define SIGCLD SIGCHLD
+#endif
+
+#ifndef SIGRTMIN
+#define SIGRTMIN NSIG
+#endif
+
+
+#if defined(HAVE_CRYPT16) && defined(HAVE_GETAUTHUID)
+#define ULTRIX_AUTH 1
+#endif
+
+/* yuck, I'd like a better way of doing this */
+#define DIRP_SIZE (256 + 32)
+
+/* default socket options. Dave Miller thinks we should default to TCP_NODELAY
+ given the socket IO pattern that Samba uses */
+#ifdef TCP_NODELAY
+#define DEFAULT_SOCKET_OPTIONS "TCP_NODELAY"
+#else
+#define DEFAULT_SOCKET_OPTIONS ""
+#endif
+
+/* dmalloc -- free heap debugger (dmalloc.org). This should be near
+ * the *bottom* of include files so as not to conflict. */
+#ifdef ENABLE_DMALLOC
+# include <dmalloc.h>
+#endif
+
+
+#define MAX_SEC_CTX_DEPTH 8 /* Maximum number of security contexts */
+
+
+/* add varargs prototypes with printf checking */
+/*PRINTFLIKE1 */
+int d_printf(const char *, ...) PRINTF_ATTRIBUTE(1,2);
+/*PRINTFLIKE2 */
+int d_fprintf(FILE *f, const char *, ...) PRINTF_ATTRIBUTE(2,3);
+
+/* PRINTFLIKE2 */
+int fstr_sprintf(fstring s, const char *fmt, ...) PRINTF_ATTRIBUTE(2,3);
+
+char *talloc_asprintf_strupper_m(TALLOC_CTX *t, const char *fmt, ...) PRINTF_ATTRIBUTE(2,3);
+
+/*
+ * Veritas File System. Often in addition to native.
+ * Quotas different.
+ */
+#if defined(HAVE_SYS_FS_VX_QUOTA_H)
+#define VXFS_QUOTA
+#endif
+
+#ifdef TRUE
+#undef TRUE
+#endif
+#define TRUE __ERROR__XX__DONT_USE_TRUE
+
+#ifdef FALSE
+#undef FALSE
+#endif
+#define FALSE __ERROR__XX__DONT_USE_FALSE
+
+void dump_core(void) _NORETURN_;
+void exit_server(const char *const reason) _NORETURN_;
+void exit_server_cleanly(const char *const reason) _NORETURN_;
+
+#define BASE_RID (0x000003E8L)
+
+#endif /* _INCLUDES_H */
diff --git a/source3/include/intl.h b/source3/include/intl.h
new file mode 100644
index 0000000..cb0dc34
--- /dev/null
+++ b/source3/include/intl.h
@@ -0,0 +1,23 @@
+/*
+ Unix SMB/CIFS implementation.
+ internationalisation headers
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/* ideally we would have a static mapping, but that precludes
+ dynamic loading. This is a reasonable compromise */
+#define N_(x) (x)
diff --git a/source3/include/krb5_env.h b/source3/include/krb5_env.h
new file mode 100644
index 0000000..0022da8
--- /dev/null
+++ b/source3/include/krb5_env.h
@@ -0,0 +1,26 @@
+/*
+ Samba Unix/Linux SMB client library
+
+ Copyright (C) 2010 Günther Deschner
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _INCLUDE_KRB5_ENV_H_
+#define _INCLUDE_KRB5_ENV_H_
+
+/* Kerberos environment variable names */
+#define KRB5_ENV_CCNAME "KRB5CCNAME"
+
+#endif /* _INCLUDE_KRB5_ENV_H_ */
diff --git a/source3/include/libsmb_internal.h b/source3/include/libsmb_internal.h
new file mode 100644
index 0000000..1131115
--- /dev/null
+++ b/source3/include/libsmb_internal.h
@@ -0,0 +1,588 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+
+#ifndef _LIBSMB_INTERNAL_H_
+#define _LIBSMB_INTERNAL_H_
+
+#include "../include/libsmbclient.h"
+#include "libsmb/clirap.h"
+
+#define SMBC_MAX_NAME 1023
+
+/*
+ * DOS Attribute values (used internally)
+ */
+struct DOS_ATTR_DESC {
+ int mode;
+ off_t size;
+ time_t create_time;
+ time_t access_time;
+ time_t write_time;
+ time_t change_time;
+ SMB_INO_T inode;
+};
+
+/*
+ * Extension of libsmbclient.h's #defines
+ */
+#define SMB_CTX_FLAG_USE_NT_HASH (1 << 4)
+
+/*
+ * Internal flags for extended attributes
+ */
+
+/* internal mode values */
+#define SMBC_XATTR_MODE_ADD 1
+#define SMBC_XATTR_MODE_REMOVE 2
+#define SMBC_XATTR_MODE_REMOVE_ALL 3
+#define SMBC_XATTR_MODE_SET 4
+#define SMBC_XATTR_MODE_CHOWN 5
+#define SMBC_XATTR_MODE_CHGRP 6
+
+/*We should test for this in configure ... */
+#ifndef ENOTSUP
+#define ENOTSUP EOPNOTSUPP
+#endif
+
+
+struct _SMBCSRV {
+ struct cli_state *cli;
+ dev_t dev;
+ bool no_pathinfo;
+ bool no_pathinfo2;
+ bool no_pathinfo3;
+ bool no_nt_session;
+ struct policy_handle pol;
+ time_t last_echo_time;
+
+ struct _SMBCSRV *next, *prev;
+};
+
+/*
+ * Keep directory entries in a list
+ */
+struct smbc_dir_list {
+ struct smbc_dir_list *next;
+ struct smbc_dirent *dirent;
+};
+
+struct smbc_dirplus_list {
+ struct smbc_dirplus_list *next;
+ struct libsmb_file_info *smb_finfo;
+ uint64_t ino;
+};
+
+/*
+ * Structure for open file management
+ */
+struct _SMBCFILE {
+ int cli_fd;
+ /*
+ * cache of cli_state we opened cli_fd on.
+ * Due to DFS can be a subsidiary connection to srv->cli
+ */
+ struct cli_state *targetcli;
+ char *fname;
+ off_t offset;
+ struct _SMBCSRV *srv;
+ bool file;
+ struct smbc_dir_list *dir_list, *dir_end, *dir_next;
+ struct smbc_dirplus_list *dirplus_list, *dirplus_end, *dirplus_next;
+ int dir_type, dir_error;
+
+ struct _SMBCFILE *next, *prev;
+};
+
+
+/*
+ * Context structure
+ */
+struct SMBC_internal_data {
+
+ /* True when this handle is initialized */
+ bool initialized;
+
+ /* dirent pointer location */
+ struct smbc_dirent dirent;
+ /*
+ * Leave room for any urlencoded filename and the comment field.
+ *
+ * We use (NAME_MAX * 3) plus whatever the max length of a comment is,
+ * plus a couple of null terminators (one after the filename,
+ * one after the comment).
+ *
+ * According to <linux/limits.h>, NAME_MAX is 255. Is it longer
+ * anyplace else?
+ */
+ char _dirent_name[1024];
+
+ /*
+ * server connection list
+ */
+ SMBCSRV * servers;
+
+ /*
+ * open file/dir list
+ */
+ SMBCFILE * files;
+
+ /*
+ * Support "Create Time" in get/set with the *xattr() functions, if
+ * true. This replaces the dos attribute strings C_TIME, A_TIME and
+ * M_TIME with CHANGE_TIME, ACCESS_TIME and WRITE_TIME, and adds
+ * CREATE_TIME. Default is FALSE, i.e. to use the old-style shorter
+ * names and to not support CREATE time, for backward compatibility.
+ */
+ bool full_time_names;
+
+ /*
+ * Enable POSIX extensions before opening files/directories
+ * Will silently ignore if the server does not support the POSIX
+ * extensions
+ */
+
+ bool posix_extensions;
+
+ /*
+ * The share mode of a file being opened. To match POSIX semantics
+ * (and maintain backward compatibility), DENY_NONE is the default.
+ */
+ smbc_share_mode share_mode;
+
+ /*
+ * Authentication function which includes the context. This will be
+ * used if set; otherwise context->callbacks.auth_fn() will be used.
+ */
+ smbc_get_auth_data_with_context_fn auth_fn_with_context;
+
+ /*
+ * An opaque (to this library) user data handle which can be set
+ * and retrieved with smbc_option_set() and smbc_option_get().
+ */
+ void * user_data;
+
+ /*
+ * Should we attempt UNIX smb encryption ?
+ * Set to 0 if we should never attempt, set to 1 if
+ * encryption requested, set to 2 if encryption required.
+ */
+ smbc_smb_encrypt_level smb_encryption_level;
+
+ /*
+ * Should we request case sensitivity of file names?
+ */
+ bool case_sensitive;
+
+ /*
+ * Credentials needed for DFS traversal.
+ */
+ struct cli_credentials *creds;
+
+ struct smbc_server_cache * server_cache;
+
+ /* POSIX emulation functions */
+ struct
+ {
+#if 0 /* Left in libsmbclient.h for backward compatibility */
+ smbc_open_fn open_fn;
+ smbc_creat_fn creat_fn;
+ smbc_read_fn read_fn;
+ smbc_write_fn write_fn;
+ smbc_unlink_fn unlink_fn;
+ smbc_rename_fn rename_fn;
+ smbc_lseek_fn lseek_fn;
+ smbc_stat_fn stat_fn;
+ smbc_fstat_fn fstat_fn;
+#endif
+ smbc_statvfs_fn statvfs_fn;
+ smbc_fstatvfs_fn fstatvfs_fn;
+ smbc_ftruncate_fn ftruncate_fn;
+#if 0 /* Left in libsmbclient.h for backward compatibility */
+ smbc_close_fn close_fn;
+ smbc_opendir_fn opendir_fn;
+ smbc_closedir_fn closedir_fn;
+ smbc_readdir_fn readdir_fn;
+ smbc_getdents_fn getdents_fn;
+ smbc_mkdir_fn mkdir_fn;
+ smbc_rmdir_fn rmdir_fn;
+ smbc_telldir_fn telldir_fn;
+ smbc_lseekdir_fn lseekdir_fn;
+ smbc_fstatdir_fn fstatdir_fn;
+ smbc_chmod_fn chmod_fn;
+ smbc_utimes_fn utimes_fn;
+ smbc_setxattr_fn setxattr_fn;
+ smbc_getxattr_fn getxattr_fn;
+ smbc_removexattr_fn removexattr_fn;
+ smbc_listxattr_fn listxattr_fn;
+#endif
+ } posix_emu;
+
+#if 0 /* Left in libsmbclient.h for backward compatibility */
+ /* Printing-related functions */
+ struct
+ {
+ smbc_print_file_fn print_file_fn;
+ smbc_open_print_job_fn open_print_job_fn;
+ smbc_list_print_jobs_fn list_print_jobs_fn;
+ smbc_unlink_print_job_fn unlink_print_job_fn;
+ } printing;
+#endif
+
+ /* SMB high-level functions */
+ struct
+ {
+ smbc_splice_fn splice_fn;
+ smbc_notify_fn notify_fn;
+ } smb;
+
+ uint16_t port;
+
+ struct loadparm_context *lp_ctx;
+};
+
+/* Functions in libsmb_cache.c */
+int
+SMBC_add_cached_server(SMBCCTX * context,
+ SMBCSRV * newsrv,
+ const char * server,
+ const char * share,
+ const char * workgroup,
+ const char * username);
+
+SMBCSRV *
+SMBC_get_cached_server(SMBCCTX * context,
+ const char * server,
+ const char * share,
+ const char * workgroup,
+ const char * user);
+
+int
+SMBC_remove_cached_server(SMBCCTX * context,
+ SMBCSRV * server);
+
+int
+SMBC_purge_cached_servers(SMBCCTX * context);
+
+
+/* Functions in libsmb_dir.c */
+int
+SMBC_check_options(char *server,
+ char *share,
+ char *path,
+ char *options);
+
+SMBCFILE *
+SMBC_opendir_ctx(SMBCCTX *context,
+ const char *fname);
+
+int
+SMBC_closedir_ctx(SMBCCTX *context,
+ SMBCFILE *dir);
+
+struct smbc_dirent *
+SMBC_readdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir);
+
+const struct libsmb_file_info *
+SMBC_readdirplus_ctx(SMBCCTX *context,
+ SMBCFILE *dir);
+
+const struct libsmb_file_info *
+SMBC_readdirplus2_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct stat *st);
+
+int
+SMBC_getdents_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct smbc_dirent *dirp,
+ int count);
+
+int
+SMBC_mkdir_ctx(SMBCCTX *context,
+ const char *fname,
+ mode_t mode);
+
+int
+SMBC_rmdir_ctx(SMBCCTX *context,
+ const char *fname);
+
+off_t
+SMBC_telldir_ctx(SMBCCTX *context,
+ SMBCFILE *dir);
+
+int
+SMBC_lseekdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ off_t offset);
+
+int
+SMBC_fstatdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct stat *st);
+
+int
+SMBC_chmod_ctx(SMBCCTX *context,
+ const char *fname,
+ mode_t newmode);
+
+int
+SMBC_utimes_ctx(SMBCCTX *context,
+ const char *fname,
+ struct timeval *tbuf);
+
+int
+SMBC_unlink_ctx(SMBCCTX *context,
+ const char *fname);
+
+int
+SMBC_rename_ctx(SMBCCTX *ocontext,
+ const char *oname,
+ SMBCCTX *ncontext,
+ const char *nname);
+
+int
+SMBC_notify_ctx(SMBCCTX *c, SMBCFILE *dir, smbc_bool recursive,
+ uint32_t completion_filter, unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data);
+
+
+
+/* Functions in libsmb_file.c */
+SMBCFILE *
+SMBC_open_ctx(SMBCCTX *context,
+ const char *fname,
+ int flags,
+ mode_t mode);
+
+SMBCFILE *
+SMBC_creat_ctx(SMBCCTX *context,
+ const char *path,
+ mode_t mode);
+
+ssize_t
+SMBC_read_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ void *buf,
+ size_t count);
+
+ssize_t
+SMBC_write_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ const void *buf,
+ size_t count);
+
+off_t
+SMBC_splice_ctx(SMBCCTX *context,
+ SMBCFILE *srcfile,
+ SMBCFILE *dstfile,
+ off_t count,
+ int (*splice_cb)(off_t n, void *priv),
+ void *priv);
+
+int
+SMBC_close_ctx(SMBCCTX *context,
+ SMBCFILE *file);
+
+NTSTATUS
+SMBC_getatr(SMBCCTX * context,
+ SMBCSRV *srv,
+ const char *path,
+ struct stat *sbuf);
+
+bool
+SMBC_setatr(SMBCCTX * context, SMBCSRV *srv, char *path,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint16_t mode);
+
+off_t
+SMBC_lseek_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ off_t offset,
+ int whence);
+
+int
+SMBC_ftruncate_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ off_t length);
+
+
+/* Functions in libsmb_misc.c */
+bool SMBC_dlist_contains(SMBCFILE * list, SMBCFILE *p);
+
+/* Functions in libsmb_path.c */
+int
+SMBC_parse_path(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *fname,
+ char **pp_workgroup,
+ char **pp_server,
+ uint16_t *p_port,
+ char **pp_share,
+ char **pp_path,
+ char **pp_user,
+ char **pp_password,
+ char **pp_options);
+
+
+/* Functions in libsmb_printjob.c */
+SMBCFILE *
+SMBC_open_print_job_ctx(SMBCCTX *context,
+ const char *fname);
+
+int
+SMBC_print_file_ctx(SMBCCTX *c_file,
+ const char *fname,
+ SMBCCTX *c_print,
+ const char *printq);
+
+int
+SMBC_list_print_jobs_ctx(SMBCCTX *context,
+ const char *fname,
+ smbc_list_print_job_fn fn);
+
+int
+SMBC_unlink_print_job_ctx(SMBCCTX *context,
+ const char *fname,
+ int id);
+
+
+/* Functions in libsmb_server.c */
+int
+SMBC_check_server(SMBCCTX * context,
+ SMBCSRV * server);
+
+int
+SMBC_remove_unused_server(SMBCCTX * context,
+ SMBCSRV * srv);
+
+void
+SMBC_get_auth_data(const char *server, const char *share,
+ char *workgroup_buf, int workgroup_buf_len,
+ char *username_buf, int username_buf_len,
+ char *password_buf, int password_buf_len);
+
+SMBCSRV *
+SMBC_find_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password);
+
+SMBCSRV *
+SMBC_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ bool connect_if_not_found,
+ const char *server,
+ uint16_t port,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password);
+
+SMBCSRV *
+SMBC_attr_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ uint16_t port,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password);
+
+
+/* Functions in libsmb_stat.c */
+void setup_stat(struct stat *st,
+ const char *fname,
+ off_t size,
+ int mode,
+ ino_t ino,
+ dev_t dev,
+ struct timespec access_time_ts,
+ struct timespec change_time_ts,
+ struct timespec write_time_ts);
+void setup_stat_from_stat_ex(const struct stat_ex *stex,
+ const char *fname,
+ struct stat *st);
+
+int
+SMBC_stat_ctx(SMBCCTX *context,
+ const char *fname,
+ struct stat *st);
+
+int
+SMBC_fstat_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ struct stat *st);
+
+
+int
+SMBC_statvfs_ctx(SMBCCTX *context,
+ char *path,
+ struct statvfs *st);
+
+
+int
+SMBC_fstatvfs_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ struct statvfs *st);
+
+
+/* Functions in libsmb_xattr.c */
+int
+SMBC_setxattr_ctx(SMBCCTX *context,
+ const char *fname,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags);
+
+int
+SMBC_getxattr_ctx(SMBCCTX *context,
+ const char *fname,
+ const char *name,
+ const void *value,
+ size_t size);
+
+int
+SMBC_removexattr_ctx(SMBCCTX *context,
+ const char *fname,
+ const char *name);
+
+int
+SMBC_listxattr_ctx(SMBCCTX *context,
+ const char *fname,
+ char *list,
+ size_t size);
+
+
+#endif
diff --git a/source3/include/libsmbclient.h b/source3/include/libsmbclient.h
new file mode 100644
index 0000000..619feab
--- /dev/null
+++ b/source3/include/libsmbclient.h
@@ -0,0 +1,3270 @@
+/*=====================================================================
+ Unix SMB/Netbios implementation.
+ SMB client library API definitions
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000
+ Copyright (C) John Terpsra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+ =====================================================================*/
+
+#ifndef SMBCLIENT_H_INCLUDED
+#define SMBCLIENT_H_INCLUDED
+
+#undef DEPRECATED_SMBC_INTERFACE
+#if ! defined(__LIBSMBCLIENT_INTERNAL__) && defined(__GNUC__)
+# define DEPRECATED_SMBC_INTERFACE __attribute__ ((deprecated))
+#else
+# define DEPRECATED_SMBC_INTERFACE
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*-------------------------------------------------------------------*/
+/* The following are special comments to instruct DOXYGEN (automated
+ * documentation tool:
+*/
+/** \defgroup libsmbclient
+*/
+/** \defgroup structure Data Structures Type and Constants
+* \ingroup libsmbclient
+* Data structures, types, and constants
+*/
+/** \defgroup callback Callback function types
+* \ingroup libsmbclient
+* Callback functions
+*/
+/** \defgroup file File Functions
+* \ingroup libsmbclient
+* Functions used to access individual file contents
+*/
+/** \defgroup directory Directory Functions
+* \ingroup libsmbclient
+* Functions used to access directory entries
+*/
+/** \defgroup attribute Attributes Functions
+* \ingroup libsmbclient
+* Functions used to view or change file and directory attributes
+*/
+/** \defgroup print Print Functions
+* \ingroup libsmbclient
+* Functions used to access printing functionality
+*/
+/** \defgroup misc Miscellaneous Functions
+* \ingroup libsmbclient
+* Functions that don't fit in to other categories
+*/
+/*-------------------------------------------------------------------*/
+
+/* Make sure we have the following includes for now ... */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <time.h>
+#include <utime.h>
+
+#define SMBC_BASE_FD 10000 /* smallest file descriptor returned */
+
+#define SMBC_WORKGROUP 1
+#define SMBC_SERVER 2
+#define SMBC_FILE_SHARE 3
+#define SMBC_PRINTER_SHARE 4
+#define SMBC_COMMS_SHARE 5
+#define SMBC_IPC_SHARE 6
+#define SMBC_DIR 7
+#define SMBC_FILE 8
+#define SMBC_LINK 9
+
+/**@ingroup structure
+ * Structure that represents a directory entry.
+ *
+ */
+struct smbc_dirent
+{
+ /** Type of entity.
+ SMBC_WORKGROUP=1,
+ SMBC_SERVER=2,
+ SMBC_FILE_SHARE=3,
+ SMBC_PRINTER_SHARE=4,
+ SMBC_COMMS_SHARE=5,
+ SMBC_IPC_SHARE=6,
+ SMBC_DIR=7,
+ SMBC_FILE=8,
+ SMBC_LINK=9,*/
+ unsigned int smbc_type;
+
+ /** Length of this smbc_dirent in bytes
+ */
+ unsigned int dirlen;
+ /** The length of the comment string in bytes (does not include
+ * null terminator)
+ */
+ unsigned int commentlen;
+ /** Points to the null terminated comment string
+ */
+ char *comment;
+ /** The length of the name string in bytes (does not include
+ * null terminator)
+ */
+ unsigned int namelen;
+ /** Points to the null terminated name string
+ */
+ char name[1];
+};
+
+/**@ingroup structure
+ * Structure that represents all attributes of a directory entry.
+ *
+ */
+struct libsmb_file_info
+{
+ /**
+ * Size of file
+ */
+ uint64_t size;
+ /**
+ * DOS attributes of file
+ */
+ uint16_t attrs;
+ /**
+ * User ID of file
+ */
+ uid_t uid;
+ /**
+ * Group ID of file
+ */
+ gid_t gid;
+ /**
+ * Birth/Create time of file (if supported by system)
+ * Otherwise the value will be 0
+ */
+ struct timespec btime_ts;
+ /**
+ * Modified time for the file
+ */
+ struct timespec mtime_ts;
+ /**
+ * Access time for the file
+ */
+ struct timespec atime_ts;
+ /**
+ * Change time for the file
+ */
+ struct timespec ctime_ts;
+ /**
+ * Name of file
+ */
+ char *name;
+ /**
+ * Short name of file
+ */
+ char *short_name;
+};
+
+/*
+ * Logging callback function
+ */
+typedef void (*smbc_debug_callback_fn)(void *private_ptr, int level, const char *msg);
+
+/*
+ * Flags for smbc_setxattr()
+ * Specify a bitwise OR of these, or 0 to add or replace as necessary
+ */
+#define SMBC_XATTR_FLAG_CREATE 0x1 /* fail if attr already exists */
+#define SMBC_XATTR_FLAG_REPLACE 0x2 /* fail if attr does not exist */
+
+
+/*
+ * Mappings of the DOS mode bits, as returned by smbc_getxattr() when the
+ * attribute name "system.dos_attr.mode" (or "system.dos_attr.*" or
+ * "system.*") is specified.
+ */
+#define SMBC_DOS_MODE_READONLY 0x01
+#define SMBC_DOS_MODE_HIDDEN 0x02
+#define SMBC_DOS_MODE_SYSTEM 0x04
+#define SMBC_DOS_MODE_VOLUME_ID 0x08
+#define SMBC_DOS_MODE_DIRECTORY 0x10
+#define SMBC_DOS_MODE_ARCHIVE 0x20
+
+/*
+ * Valid values for the option "open_share_mode", when calling
+ * smbc_setOptionOpenShareMode()
+ */
+typedef enum smbc_share_mode
+{
+ SMBC_SHAREMODE_DENY_DOS = 0,
+ SMBC_SHAREMODE_DENY_ALL = 1,
+ SMBC_SHAREMODE_DENY_WRITE = 2,
+ SMBC_SHAREMODE_DENY_READ = 3,
+ SMBC_SHAREMODE_DENY_NONE = 4,
+ SMBC_SHAREMODE_DENY_FCB = 7
+} smbc_share_mode;
+
+
+/**
+ * Values for option SMB Encryption Level, as set and retrieved with
+ * smbc_setOptionSmbEncryptionLevel() and smbc_getOptionSmbEncryptionLevel()
+ */
+typedef enum smbc_smb_encrypt_level
+{
+ SMBC_ENCRYPTLEVEL_DEFAULT = -1,
+ SMBC_ENCRYPTLEVEL_NONE = 0,
+ SMBC_ENCRYPTLEVEL_REQUEST = 1,
+ SMBC_ENCRYPTLEVEL_REQUIRE = 2
+} smbc_smb_encrypt_level;
+
+
+/**
+ * Capabilities set in the f_flag field of struct statvfs, from
+ * smbc_statvfs(). These may be OR-ed together to reflect a full set of
+ * available capabilities.
+ */
+typedef enum smbc_vfs_feature
+{
+ /* Defined by POSIX or in Linux include files (low-order bits) */
+ SMBC_VFS_FEATURE_RDONLY = (1 << 0),
+
+ /* Specific to libsmbclient (high-order bits) */
+ SMBC_VFS_FEATURE_DFS = (1 << 28),
+ SMBC_VFS_FEATURE_CASE_INSENSITIVE = (1 << 29),
+ SMBC_VFS_FEATURE_NO_UNIXCIFS = (1 << 30)
+} smbc_vfs_feature;
+
+typedef int smbc_bool;
+
+
+#ifndef ENOATTR
+# define ENOATTR ENOENT /* No such attribute */
+#endif
+
+
+
+
+/**@ingroup structure
+ * Structure that represents a print job.
+ *
+ */
+#ifndef _CLIENT_H
+struct print_job_info
+{
+ /** numeric ID of the print job
+ */
+ unsigned short id;
+
+ /** represents print job priority (lower numbers mean higher priority)
+ */
+ unsigned short priority;
+
+ /** Size of the print job
+ */
+ size_t size;
+
+ /** Name of the user that owns the print job
+ */
+ char user[128];
+
+ /** Name of the print job. This will have no name if an anonymous print
+ * file was opened. Ie smb://server/printer
+ */
+ char name[128];
+
+ /** Time the print job was spooled
+ */
+ time_t t;
+};
+#endif /* _CLIENT_H */
+
+
+/**@ingroup structure
+ * Server handle
+ */
+typedef struct _SMBCSRV SMBCSRV;
+
+/**@ingroup structure
+ * File or directory handle
+ */
+typedef struct _SMBCFILE SMBCFILE;
+
+/**@ingroup structure
+ * File or directory handle
+ */
+typedef struct _SMBCCTX SMBCCTX;
+
+
+/*
+ * Flags for SMBCCTX->flags
+ *
+ * NEW CODE SHOULD NOT DIRECTLY MANIPULATE THE CONTEXT STRUCTURE.
+ * Instead, use:
+ * smbc_setOptionUseKerberos()
+ * smbc_getOptionUseKerberos()
+ * smbc_setOptionFallbackAfterKerberos()
+ * smbc_getOptionFallbackAFterKerberos()
+ * smbc_setOptionNoAutoAnonymousLogin()
+ * smbc_getOptionNoAutoAnonymousLogin()
+ * smbc_setOptionUseCCache()
+ * smbc_getOptionUseCCache()
+ */
+# define SMB_CTX_FLAG_USE_KERBEROS (1 << 0)
+# define SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS (1 << 1)
+# define SMBCCTX_FLAG_NO_AUTO_ANONYMOUS_LOGON (1 << 2)
+# define SMB_CTX_FLAG_USE_CCACHE (1 << 3)
+
+
+
+/**@ingroup callback
+ * Authentication callback function type (traditional method)
+ *
+ * Type for the the authentication function called by the library to
+ * obtain authentication credentials
+ *
+ * For kerberos support the function should just be called without
+ * prompting the user for credentials. Which means a simple 'return'
+ * should work. Take a look at examples/libsmbclient/get_auth_data_fn.h
+ * and examples/libsmbclient/testbrowse.c.
+ *
+ * @param srv Server being authenticated to
+ *
+ * @param shr Share being authenticated to
+ *
+ * @param wg Pointer to buffer containing a "hint" for the
+ * workgroup to be authenticated. Should be filled in
+ * with the correct workgroup if the hint is wrong.
+ *
+ * @param wglen The size of the workgroup buffer in bytes
+ *
+ * @param un Pointer to buffer containing a "hint" for the
+ * user name to be use for authentication. Should be
+ * filled in with the correct workgroup if the hint is
+ * wrong.
+ *
+ * @param unlen The size of the username buffer in bytes
+ *
+ * @param pw Pointer to buffer containing to which password
+ * copied
+ *
+ * @param pwlen The size of the password buffer in bytes
+ *
+ */
+typedef void (*smbc_get_auth_data_fn)(const char *srv,
+ const char *shr,
+ char *wg, int wglen,
+ char *un, int unlen,
+ char *pw, int pwlen);
+/**@ingroup callback
+ * Authentication callback function type (method that includes context)
+ *
+ * Type for the the authentication function called by the library to
+ * obtain authentication credentials
+ *
+ * For kerberos support the function should just be called without
+ * prompting the user for credentials. Which means a simple 'return'
+ * should work. Take a look at examples/libsmbclient/get_auth_data_fn.h
+ * and examples/libsmbclient/testbrowse.c.
+ *
+ * @param c Pointer to the smb context
+ *
+ * @param srv Server being authenticated to
+ *
+ * @param shr Share being authenticated to
+ *
+ * @param wg Pointer to buffer containing a "hint" for the
+ * workgroup to be authenticated. Should be filled in
+ * with the correct workgroup if the hint is wrong.
+ *
+ * @param wglen The size of the workgroup buffer in bytes
+ *
+ * @param un Pointer to buffer containing a "hint" for the
+ * user name to be use for authentication. Should be
+ * filled in with the correct workgroup if the hint is
+ * wrong.
+ *
+ * @param unlen The size of the username buffer in bytes
+ *
+ * @param pw Pointer to buffer containing to which password
+ * copied
+ *
+ * @param pwlen The size of the password buffer in bytes
+ *
+ */
+typedef void (*smbc_get_auth_data_with_context_fn)(SMBCCTX *c,
+ const char *srv,
+ const char *shr,
+ char *wg, int wglen,
+ char *un, int unlen,
+ char *pw, int pwlen);
+
+
+/**@ingroup callback
+ * Print job info callback function type.
+ *
+ * @param i pointer to print job information structure
+ *
+ */
+typedef void (*smbc_list_print_job_fn)(struct print_job_info *i);
+
+
+/**@ingroup callback
+ * Check if a server is still good
+ *
+ * @param c pointer to smb context
+ *
+ * @param srv pointer to server to check
+ *
+ * @return 0 when connection is good. 1 on error.
+ *
+ */
+typedef int (*smbc_check_server_fn)(SMBCCTX * c, SMBCSRV *srv);
+
+/**@ingroup callback
+ * Remove a server if unused
+ *
+ * @param c pointer to smb context
+ *
+ * @param srv pointer to server to remove
+ *
+ * @return 0 on success. 1 on failure.
+ *
+ */
+typedef int (*smbc_remove_unused_server_fn)(SMBCCTX * c, SMBCSRV *srv);
+
+
+/**@ingroup callback
+ * Add a server to the cache system
+ *
+ * @param c pointer to smb context
+ *
+ * @param srv pointer to server to add
+ *
+ * @param server server name
+ *
+ * @param share share name
+ *
+ * @param workgroup workgroup used to connect
+ *
+ * @param username username used to connect
+ *
+ * @return 0 on success. 1 on failure.
+ *
+ */
+typedef int (*smbc_add_cached_srv_fn) (SMBCCTX * c, SMBCSRV *srv,
+ const char * server, const char * share,
+ const char * workgroup, const char * username);
+
+/**@ingroup callback
+ * Look up a server in the cache system
+ *
+ * @param c pointer to smb context
+ *
+ * @param server server name to match
+ *
+ * @param share share name to match
+ *
+ * @param workgroup workgroup to match
+ *
+ * @param username username to match
+ *
+ * @return pointer to SMBCSRV on success. NULL on failure.
+ *
+ */
+typedef SMBCSRV * (*smbc_get_cached_srv_fn) (SMBCCTX * c, const char * server,
+ const char * share, const char * workgroup,
+ const char * username);
+
+/**@ingroup callback
+ * Remove a cached server
+ *
+ * @param c pointer to smb context
+ *
+ * @param srv pointer to server to remove
+ *
+ * @return 0 when found and removed. 1 on failure.
+ *
+ */
+typedef int (*smbc_remove_cached_srv_fn)(SMBCCTX * c, SMBCSRV *srv);
+
+
+/**@ingroup callback
+ * Try to remove all servers from the cache system and disconnect
+ *
+ * @param c pointer to smb context
+ *
+ * @return 0 when found and removed. 1 on failure.
+ *
+ */
+typedef int (*smbc_purge_cached_fn) (SMBCCTX * c);
+
+
+
+/*****************************************
+ * Getters and setters for CONFIGURATION *
+ *****************************************/
+
+/** Get the debug level */
+int
+smbc_getDebug(SMBCCTX *c);
+
+/** Set the debug level */
+void
+smbc_setDebug(SMBCCTX *c, int debug);
+
+/**
+ * set log callback function to capture logs from libsmbclient, this
+ * is applied at global level
+ */
+void
+smbc_setLogCallback(SMBCCTX *c, void *private_ptr,
+ smbc_debug_callback_fn fn);
+
+/** set configuration file, this is applied at global level */
+int
+smbc_setConfiguration(SMBCCTX *c, const char *file);
+
+/** Get the netbios name used for making connections */
+const char *
+smbc_getNetbiosName(SMBCCTX *c);
+
+/** Set the netbios name used for making connections */
+void
+smbc_setNetbiosName(SMBCCTX *c, const char *netbios_name);
+
+/** Get the workgroup used for making connections */
+const char *
+smbc_getWorkgroup(SMBCCTX *c);
+
+/** Set the workgroup used for making connections */
+void smbc_setWorkgroup(SMBCCTX *c, const char *workgroup);
+
+/** Get the username used for making connections */
+const char *
+smbc_getUser(SMBCCTX *c);
+
+/** Set the username used for making connections */
+void
+smbc_setUser(SMBCCTX *c, const char *user);
+
+/**
+ * Get the timeout used for waiting on connections and response data
+ * (in milliseconds)
+ */
+int
+smbc_getTimeout(SMBCCTX *c);
+
+/**
+ * Set the timeout used for waiting on connections and response data
+ * (in milliseconds)
+ */
+void
+smbc_setTimeout(SMBCCTX *c, int timeout);
+
+
+/**
+ * Get the TCP port used to connect (0 means default).
+ */
+uint16_t
+smbc_getPort(SMBCCTX *c);
+
+/**
+ * Set the TCP port used to connect (0 means default).
+ */
+void
+smbc_setPort(SMBCCTX *c, uint16_t port);
+
+/** Get whether to enable POSIX extensions if available */
+smbc_bool
+smbc_getOptionPosixExtensions(SMBCCTX *c);
+
+/** Set whether to enable POSIX extensions if available */
+void
+smbc_setOptionPosixExtensions(SMBCCTX *c, smbc_bool b);
+
+/***********************************
+ * Getters and setters for OPTIONS *
+ ***********************************/
+
+/** Get whether to log to standard error instead of standard output */
+smbc_bool
+smbc_getOptionDebugToStderr(SMBCCTX *c);
+
+/** Set whether to log to standard error instead of standard output */
+void
+smbc_setOptionDebugToStderr(SMBCCTX *c, smbc_bool b);
+
+/**
+ * Get whether to use new-style time attribute names, e.g. WRITE_TIME rather
+ * than the old-style names such as M_TIME. This allows also setting/getting
+ * CREATE_TIME which was previously unimplemented. (Note that the old C_TIME
+ * was supposed to be CHANGE_TIME but was confused and sometimes referred to
+ * CREATE_TIME.)
+ */
+smbc_bool
+smbc_getOptionFullTimeNames(SMBCCTX *c);
+
+/**
+ * Set whether to use new-style time attribute names, e.g. WRITE_TIME rather
+ * than the old-style names such as M_TIME. This allows also setting/getting
+ * CREATE_TIME which was previously unimplemented. (Note that the old C_TIME
+ * was supposed to be CHANGE_TIME but was confused and sometimes referred to
+ * CREATE_TIME.)
+ */
+void
+smbc_setOptionFullTimeNames(SMBCCTX *c, smbc_bool b);
+
+/**
+ * Get the share mode to use for files opened with SMBC_open_ctx(). The
+ * default is SMBC_SHAREMODE_DENY_NONE.
+ */
+smbc_share_mode
+smbc_getOptionOpenShareMode(SMBCCTX *c);
+
+/**
+ * Set the share mode to use for files opened with SMBC_open_ctx(). The
+ * default is SMBC_SHAREMODE_DENY_NONE.
+ */
+void
+smbc_setOptionOpenShareMode(SMBCCTX *c, smbc_share_mode share_mode);
+
+/** Retrieve a previously saved user data handle */
+void *
+smbc_getOptionUserData(SMBCCTX *c);
+
+/** Save a user data handle */
+void
+smbc_setOptionUserData(SMBCCTX *c, void *user_data);
+
+/** Get the encoded value for encryption level. */
+smbc_smb_encrypt_level
+smbc_getOptionSmbEncryptionLevel(SMBCCTX *c);
+
+/** Set the encoded value for encryption level. */
+void
+smbc_setOptionSmbEncryptionLevel(SMBCCTX *c, smbc_smb_encrypt_level level);
+
+/**
+ * Get whether to treat file names as case-sensitive if we can't determine
+ * when connecting to the remote share whether the file system is case
+ * sensitive. This defaults to FALSE since it's most likely that if we can't
+ * retrieve the file system attributes, it's a very old file system that does
+ * not support case sensitivity.
+ */
+smbc_bool
+smbc_getOptionCaseSensitive(SMBCCTX *c);
+
+/**
+ * Set whether to treat file names as case-sensitive if we can't determine
+ * when connecting to the remote share whether the file system is case
+ * sensitive. This defaults to FALSE since it's most likely that if we can't
+ * retrieve the file system attributes, it's a very old file system that does
+ * not support case sensitivity.
+ */
+void
+smbc_setOptionCaseSensitive(SMBCCTX *c, smbc_bool b);
+
+
+/**
+ * Get from how many local master browsers should the list of workgroups be
+ * retrieved. It can take up to 12 minutes or longer after a server becomes a
+ * local master browser, for it to have the entire browse list (the list of
+ * workgroups/domains) from an entire network. Since a client never knows
+ * which local master browser will be found first, the one which is found
+ * first and used to retrieve a browse list may have an incomplete or empty
+ * browse list. By requesting the browse list from multiple local master
+ * browsers, a more complete list can be generated. For small networks (few
+ * workgroups), it is recommended that this value be set to 0, causing the
+ * browse lists from all found local master browsers to be retrieved and
+ * merged. For networks with many workgroups, a suitable value for this
+ * variable is probably somewhere around 3. (Default: 3).
+ */
+int
+smbc_getOptionBrowseMaxLmbCount(SMBCCTX *c);
+
+/**
+ * Set from how many local master browsers should the list of workgroups be
+ * retrieved. It can take up to 12 minutes or longer after a server becomes a
+ * local master browser, for it to have the entire browse list (the list of
+ * workgroups/domains) from an entire network. Since a client never knows
+ * which local master browser will be found first, the one which is found
+ * first and used to retrieve a browse list may have an incomplete or empty
+ * browse list. By requesting the browse list from multiple local master
+ * browsers, a more complete list can be generated. For small networks (few
+ * workgroups), it is recommended that this value be set to 0, causing the
+ * browse lists from all found local master browsers to be retrieved and
+ * merged. For networks with many workgroups, a suitable value for this
+ * variable is probably somewhere around 3. (Default: 3).
+ */
+void
+smbc_setOptionBrowseMaxLmbCount(SMBCCTX *c, int count);
+
+/**
+ * Get whether to url-encode readdir entries.
+ *
+ * There is a difference in the desired return strings from
+ * smbc_readdir() depending upon whether the filenames are to
+ * be displayed to the user, or whether they are to be
+ * appended to the path name passed to smbc_opendir() to call
+ * a further smbc_ function (e.g. open the file with
+ * smbc_open()). In the former case, the filename should be
+ * in "human readable" form. In the latter case, the smbc_
+ * functions expect a URL which must be url-encoded. Those
+ * functions decode the URL. If, for example, smbc_readdir()
+ * returned a file name of "abc%20def.txt", passing a path
+ * with this file name attached to smbc_open() would cause
+ * smbc_open to attempt to open the file "abc def.txt" since
+ * the %20 is decoded into a space.
+ *
+ * Set this option to True if the names returned by
+ * smbc_readdir() should be url-encoded such that they can be
+ * passed back to another smbc_ call. Set it to False if the
+ * names returned by smbc_readdir() are to be presented to the
+ * user.
+ *
+ * For backwards compatibility, this option defaults to False.
+ */
+smbc_bool
+smbc_getOptionUrlEncodeReaddirEntries(SMBCCTX *c);
+
+/**
+ * Set whether to url-encode readdir entries.
+ *
+ * There is a difference in the desired return strings from
+ * smbc_readdir() depending upon whether the filenames are to
+ * be displayed to the user, or whether they are to be
+ * appended to the path name passed to smbc_opendir() to call
+ * a further smbc_ function (e.g. open the file with
+ * smbc_open()). In the former case, the filename should be
+ * in "human readable" form. In the latter case, the smbc_
+ * functions expect a URL which must be url-encoded. Those
+ * functions decode the URL. If, for example, smbc_readdir()
+ * returned a file name of "abc%20def.txt", passing a path
+ * with this file name attached to smbc_open() would cause
+ * smbc_open to attempt to open the file "abc def.txt" since
+ * the %20 is decoded into a space.
+ *
+ * Set this option to True if the names returned by
+ * smbc_readdir() should be url-encoded such that they can be
+ * passed back to another smbc_ call. Set it to False if the
+ * names returned by smbc_readdir() are to be presented to the
+ * user.
+ *
+ * For backwards compatibility, this option defaults to False.
+ */
+void
+smbc_setOptionUrlEncodeReaddirEntries(SMBCCTX *c, smbc_bool b);
+
+/**
+ * Get whether to use the same connection for all shares on a server.
+ *
+ * Some Windows versions appear to have a limit to the number
+ * of concurrent SESSIONs and/or TREE CONNECTions. In
+ * one-shot programs (i.e. the program runs and then quickly
+ * ends, thereby shutting down all connections), it is
+ * probably reasonable to establish a new connection for each
+ * share. In long-running applications, the limitation can be
+ * avoided by using only a single connection to each server,
+ * and issuing a new TREE CONNECT when the share is accessed.
+ */
+smbc_bool
+smbc_getOptionOneSharePerServer(SMBCCTX *c);
+
+/**
+ * Set whether to use the same connection for all shares on a server.
+ *
+ * Some Windows versions appear to have a limit to the number
+ * of concurrent SESSIONs and/or TREE CONNECTions. In
+ * one-shot programs (i.e. the program runs and then quickly
+ * ends, thereby shutting down all connections), it is
+ * probably reasonable to establish a new connection for each
+ * share. In long-running applications, the limitation can be
+ * avoided by using only a single connection to each server,
+ * and issuing a new TREE CONNECT when the share is accessed.
+ */
+void
+smbc_setOptionOneSharePerServer(SMBCCTX *c, smbc_bool b);
+
+/** Get whether to enable use of kerberos */
+smbc_bool
+smbc_getOptionUseKerberos(SMBCCTX *c);
+
+/** Set whether to enable use of kerberos */
+void
+smbc_setOptionUseKerberos(SMBCCTX *c, smbc_bool b);
+
+/** Get whether to fallback after kerberos */
+smbc_bool
+smbc_getOptionFallbackAfterKerberos(SMBCCTX *c);
+
+/** Set whether to fallback after kerberos */
+void
+smbc_setOptionFallbackAfterKerberos(SMBCCTX *c, smbc_bool b);
+
+/** Get whether to automatically select anonymous login */
+smbc_bool
+smbc_getOptionNoAutoAnonymousLogin(SMBCCTX *c);
+
+/** Set whether to automatically select anonymous login */
+void
+smbc_setOptionNoAutoAnonymousLogin(SMBCCTX *c, smbc_bool b);
+
+/** Get whether to enable use of the winbind ccache */
+smbc_bool
+smbc_getOptionUseCCache(SMBCCTX *c);
+
+/** Set whether to enable use of the winbind ccache */
+void
+smbc_setOptionUseCCache(SMBCCTX *c, smbc_bool b);
+
+/** Get indication that the password supplied is the NT hash */
+smbc_bool
+smbc_getOptionUseNTHash(SMBCCTX *c);
+
+/** Set indication that the password supplied is the NT hash */
+void
+smbc_setOptionUseNTHash(SMBCCTX *c, smbc_bool b);
+
+/**
+ * @brief Set the 'client min protocol' and the 'client max protocol'.
+ *
+ * IMPORTANT: This overrides the values 'client min protocol' and 'client max
+ * protocol' set in the smb.conf file!
+ *
+ * @param[in] c The smbc context to use.
+ *
+ * @param[in] min_proto The minimal protocol to use or NULL for leaving it
+ * untouched.
+ *
+ * @param[in] max_proto The maximum protocol to use or NULL for leaving it
+ * untouched.
+ *
+ * @returns true for success, false otherwise
+ */
+smbc_bool
+smbc_setOptionProtocols(SMBCCTX *c, const char *min_proto, const char *max_proto);
+
+/*************************************
+ * Getters and setters for FUNCTIONS *
+ *************************************/
+
+/** Get the function for obtaining authentication data */
+smbc_get_auth_data_fn smbc_getFunctionAuthData(SMBCCTX *c);
+
+/** Set the function for obtaining authentication data */
+void smbc_setFunctionAuthData(SMBCCTX *c, smbc_get_auth_data_fn fn);
+
+/** Get the new-style authentication function which includes the context. */
+smbc_get_auth_data_with_context_fn
+smbc_getFunctionAuthDataWithContext(SMBCCTX *c);
+
+/** Set the new-style authentication function which includes the context. */
+void
+smbc_setFunctionAuthDataWithContext(SMBCCTX *c,
+ smbc_get_auth_data_with_context_fn fn);
+
+/** Get the function for checking if a server is still good */
+smbc_check_server_fn smbc_getFunctionCheckServer(SMBCCTX *c);
+
+/** Set the function for checking if a server is still good */
+void smbc_setFunctionCheckServer(SMBCCTX *c, smbc_check_server_fn fn);
+
+/** Get the function for removing a server if unused */
+smbc_remove_unused_server_fn smbc_getFunctionRemoveUnusedServer(SMBCCTX *c);
+
+/** Set the function for removing a server if unused */
+void smbc_setFunctionRemoveUnusedServer(SMBCCTX *c,
+ smbc_remove_unused_server_fn fn);
+
+/** Get the function for adding a cached server */
+smbc_add_cached_srv_fn smbc_getFunctionAddCachedServer(SMBCCTX *c);
+
+/** Set the function for adding a cached server */
+void smbc_setFunctionAddCachedServer(SMBCCTX *c, smbc_add_cached_srv_fn fn);
+
+/** Get the function for server cache lookup */
+smbc_get_cached_srv_fn smbc_getFunctionGetCachedServer(SMBCCTX *c);
+
+/** Set the function for server cache lookup */
+void smbc_setFunctionGetCachedServer(SMBCCTX *c, smbc_get_cached_srv_fn fn);
+
+/** Get the function for server cache removal */
+smbc_remove_cached_srv_fn smbc_getFunctionRemoveCachedServer(SMBCCTX *c);
+
+/** Set the function for server cache removal */
+void smbc_setFunctionRemoveCachedServer(SMBCCTX *c,
+ smbc_remove_cached_srv_fn fn);
+
+/**
+ * Get the function for server cache purging. This function tries to
+ * remove all cached servers (e.g. on disconnect)
+ */
+smbc_purge_cached_fn smbc_getFunctionPurgeCachedServers(SMBCCTX *c);
+
+/**
+ * Set the function for server cache purging. This function tries to
+ * remove all cached servers (e.g. on disconnect)
+ */
+void smbc_setFunctionPurgeCachedServers(SMBCCTX *c,
+ smbc_purge_cached_fn fn);
+
+/** Get the function to store private data of the server cache */
+struct smbc_server_cache * smbc_getServerCacheData(SMBCCTX *c);
+
+/** Set the function to store private data of the server cache */
+void smbc_setServerCacheData(SMBCCTX *c, struct smbc_server_cache * cache);
+
+
+
+/*****************************************************************
+ * Callable functions for files. *
+ * Each callable has a function signature typedef, a declaration *
+ * for the getter, and a declaration for the setter. *
+ *****************************************************************/
+
+typedef SMBCFILE * (*smbc_open_fn)(SMBCCTX *c,
+ const char *fname,
+ int flags,
+ mode_t mode);
+smbc_open_fn smbc_getFunctionOpen(SMBCCTX *c);
+void smbc_setFunctionOpen(SMBCCTX *c, smbc_open_fn fn);
+
+typedef SMBCFILE * (*smbc_creat_fn)(SMBCCTX *c,
+ const char *path,
+ mode_t mode);
+smbc_creat_fn smbc_getFunctionCreat(SMBCCTX *c);
+void smbc_setFunctionCreat(SMBCCTX *c, smbc_creat_fn);
+
+typedef ssize_t (*smbc_read_fn)(SMBCCTX *c,
+ SMBCFILE *file,
+ void *buf,
+ size_t count);
+smbc_read_fn smbc_getFunctionRead(SMBCCTX *c);
+void smbc_setFunctionRead(SMBCCTX *c, smbc_read_fn fn);
+
+typedef ssize_t (*smbc_write_fn)(SMBCCTX *c,
+ SMBCFILE *file,
+ const void *buf,
+ size_t count);
+smbc_write_fn smbc_getFunctionWrite(SMBCCTX *c);
+void smbc_setFunctionWrite(SMBCCTX *c, smbc_write_fn fn);
+
+typedef off_t (*smbc_splice_fn)(SMBCCTX *c,
+ SMBCFILE *srcfile,
+ SMBCFILE *dstfile,
+ off_t count,
+ int (*splice_cb)(off_t n, void *priv),
+ void *priv);
+smbc_splice_fn smbc_getFunctionSplice(SMBCCTX *c);
+void smbc_setFunctionSplice(SMBCCTX *c, smbc_splice_fn fn);
+
+typedef int (*smbc_unlink_fn)(SMBCCTX *c,
+ const char *fname);
+smbc_unlink_fn smbc_getFunctionUnlink(SMBCCTX *c);
+void smbc_setFunctionUnlink(SMBCCTX *c, smbc_unlink_fn fn);
+
+typedef int (*smbc_rename_fn)(SMBCCTX *ocontext,
+ const char *oname,
+ SMBCCTX *ncontext,
+ const char *nname);
+smbc_rename_fn smbc_getFunctionRename(SMBCCTX *c);
+void smbc_setFunctionRename(SMBCCTX *c, smbc_rename_fn fn);
+
+typedef off_t (*smbc_lseek_fn)(SMBCCTX *c,
+ SMBCFILE * file,
+ off_t offset,
+ int whence);
+smbc_lseek_fn smbc_getFunctionLseek(SMBCCTX *c);
+void smbc_setFunctionLseek(SMBCCTX *c, smbc_lseek_fn fn);
+
+typedef int (*smbc_stat_fn)(SMBCCTX *c,
+ const char *fname,
+ struct stat *st);
+smbc_stat_fn smbc_getFunctionStat(SMBCCTX *c);
+void smbc_setFunctionStat(SMBCCTX *c, smbc_stat_fn fn);
+
+typedef int (*smbc_fstat_fn)(SMBCCTX *c,
+ SMBCFILE *file,
+ struct stat *st);
+smbc_fstat_fn smbc_getFunctionFstat(SMBCCTX *c);
+void smbc_setFunctionFstat(SMBCCTX *c, smbc_fstat_fn fn);
+
+typedef int (*smbc_statvfs_fn)(SMBCCTX *c,
+ char *path,
+ struct statvfs *st);
+smbc_statvfs_fn smbc_getFunctionStatVFS(SMBCCTX *c);
+void smbc_setFunctionStatVFS(SMBCCTX *c, smbc_statvfs_fn fn);
+
+typedef int (*smbc_fstatvfs_fn)(SMBCCTX *c,
+ SMBCFILE *file,
+ struct statvfs *st);
+smbc_fstatvfs_fn smbc_getFunctionFstatVFS(SMBCCTX *c);
+void smbc_setFunctionFstatVFS(SMBCCTX *c, smbc_fstatvfs_fn fn);
+
+typedef int (*smbc_ftruncate_fn)(SMBCCTX *c,
+ SMBCFILE *f,
+ off_t size);
+smbc_ftruncate_fn smbc_getFunctionFtruncate(SMBCCTX *c);
+void smbc_setFunctionFtruncate(SMBCCTX *c, smbc_ftruncate_fn fn);
+
+typedef int (*smbc_close_fn)(SMBCCTX *c,
+ SMBCFILE *file);
+smbc_close_fn smbc_getFunctionClose(SMBCCTX *c);
+void smbc_setFunctionClose(SMBCCTX *c, smbc_close_fn fn);
+
+
+
+/*****************************************************************
+ * Callable functions for directories. *
+ * Each callable has a function signature typedef, a declaration *
+ * for the getter, and a declaration for the setter. *
+ *****************************************************************/
+
+typedef SMBCFILE * (*smbc_opendir_fn)(SMBCCTX *c,
+ const char *fname);
+smbc_opendir_fn smbc_getFunctionOpendir(SMBCCTX *c);
+void smbc_setFunctionOpendir(SMBCCTX *c, smbc_opendir_fn fn);
+
+typedef int (*smbc_closedir_fn)(SMBCCTX *c,
+ SMBCFILE *dir);
+smbc_closedir_fn smbc_getFunctionClosedir(SMBCCTX *c);
+void smbc_setFunctionClosedir(SMBCCTX *c, smbc_closedir_fn fn);
+
+typedef struct smbc_dirent * (*smbc_readdir_fn)(SMBCCTX *c,
+ SMBCFILE *dir);
+smbc_readdir_fn smbc_getFunctionReaddir(SMBCCTX *c);
+void smbc_setFunctionReaddir(SMBCCTX *c, smbc_readdir_fn fn);
+
+typedef const struct libsmb_file_info * (*smbc_readdirplus_fn)(SMBCCTX *c,
+ SMBCFILE *dir);
+smbc_readdirplus_fn smbc_getFunctionReaddirPlus(SMBCCTX *c);
+void smbc_setFunctionReaddirPlus(SMBCCTX *c, smbc_readdirplus_fn fn);
+
+typedef const struct libsmb_file_info * (*smbc_readdirplus2_fn)(SMBCCTX *c,
+ SMBCFILE *dir,
+ struct stat *st);
+smbc_readdirplus2_fn smbc_getFunctionReaddirPlus2(SMBCCTX *c);
+void smbc_setFunctionReaddirPlus2(SMBCCTX *c, smbc_readdirplus2_fn fn);
+
+typedef int (*smbc_getdents_fn)(SMBCCTX *c,
+ SMBCFILE *dir,
+ struct smbc_dirent *dirp,
+ int count);
+smbc_getdents_fn smbc_getFunctionGetdents(SMBCCTX *c);
+void smbc_setFunctionGetdents(SMBCCTX *c, smbc_getdents_fn fn);
+
+typedef int (*smbc_mkdir_fn)(SMBCCTX *c,
+ const char *fname,
+ mode_t mode);
+smbc_mkdir_fn smbc_getFunctionMkdir(SMBCCTX *c);
+void smbc_setFunctionMkdir(SMBCCTX *c, smbc_mkdir_fn fn);
+
+typedef int (*smbc_rmdir_fn)(SMBCCTX *c,
+ const char *fname);
+smbc_rmdir_fn smbc_getFunctionRmdir(SMBCCTX *c);
+void smbc_setFunctionRmdir(SMBCCTX *c, smbc_rmdir_fn fn);
+
+typedef off_t (*smbc_telldir_fn)(SMBCCTX *c,
+ SMBCFILE *dir);
+smbc_telldir_fn smbc_getFunctionTelldir(SMBCCTX *c);
+void smbc_setFunctionTelldir(SMBCCTX *c, smbc_telldir_fn fn);
+
+typedef int (*smbc_lseekdir_fn)(SMBCCTX *c,
+ SMBCFILE *dir,
+ off_t offset);
+smbc_lseekdir_fn smbc_getFunctionLseekdir(SMBCCTX *c);
+void smbc_setFunctionLseekdir(SMBCCTX *c, smbc_lseekdir_fn fn);
+
+typedef int (*smbc_fstatdir_fn)(SMBCCTX *c,
+ SMBCFILE *dir,
+ struct stat *st);
+smbc_fstatdir_fn smbc_getFunctionFstatdir(SMBCCTX *c);
+void smbc_setFunctionFstatdir(SMBCCTX *c, smbc_fstatdir_fn fn);
+
+#define SMBC_NOTIFY_ACTION_ADDED 1
+#define SMBC_NOTIFY_ACTION_REMOVED 2
+#define SMBC_NOTIFY_ACTION_MODIFIED 3
+#define SMBC_NOTIFY_ACTION_OLD_NAME 4
+#define SMBC_NOTIFY_ACTION_NEW_NAME 5
+#define SMBC_NOTIFY_ACTION_ADDED_STREAM 6
+#define SMBC_NOTIFY_ACTION_REMOVED_STREAM 7
+#define SMBC_NOTIFY_ACTION_MODIFIED_STREAM 8
+
+struct smbc_notify_callback_action {
+ uint32_t action;
+ const char *filename;
+};
+
+typedef int (*smbc_notify_callback_fn)(
+ const struct smbc_notify_callback_action *actions,
+ size_t num_actions, void *private_data);
+
+typedef int (*smbc_notify_fn)(SMBCCTX *c, SMBCFILE *dir, smbc_bool recursive,
+ uint32_t completion_filter,
+ unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data);
+smbc_notify_fn smbc_getFunctionNotify(SMBCCTX *c);
+void smbc_setFunctionNotify(SMBCCTX *c, smbc_notify_fn fn);
+
+
+/*****************************************************************
+ * Callable functions applicable to both files and directories. *
+ * Each callable has a function signature typedef, a declaration *
+ * for the getter, and a declaration for the setter. *
+ *****************************************************************/
+
+typedef int (*smbc_chmod_fn)(SMBCCTX *c,
+ const char *fname,
+ mode_t mode);
+smbc_chmod_fn smbc_getFunctionChmod(SMBCCTX *c);
+void smbc_setFunctionChmod(SMBCCTX *c, smbc_chmod_fn fn);
+
+typedef int (*smbc_utimes_fn)(SMBCCTX *c,
+ const char *fname,
+ struct timeval *tbuf);
+smbc_utimes_fn smbc_getFunctionUtimes(SMBCCTX *c);
+void smbc_setFunctionUtimes(SMBCCTX *c, smbc_utimes_fn fn);
+
+typedef int (*smbc_setxattr_fn)(SMBCCTX *context,
+ const char *fname,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags);
+smbc_setxattr_fn smbc_getFunctionSetxattr(SMBCCTX *c);
+void smbc_setFunctionSetxattr(SMBCCTX *c, smbc_setxattr_fn fn);
+
+typedef int (*smbc_getxattr_fn)(SMBCCTX *context,
+ const char *fname,
+ const char *name,
+ const void *value,
+ size_t size);
+smbc_getxattr_fn smbc_getFunctionGetxattr(SMBCCTX *c);
+void smbc_setFunctionGetxattr(SMBCCTX *c, smbc_getxattr_fn fn);
+
+typedef int (*smbc_removexattr_fn)(SMBCCTX *context,
+ const char *fname,
+ const char *name);
+smbc_removexattr_fn smbc_getFunctionRemovexattr(SMBCCTX *c);
+void smbc_setFunctionRemovexattr(SMBCCTX *c, smbc_removexattr_fn fn);
+
+typedef int (*smbc_listxattr_fn)(SMBCCTX *context,
+ const char *fname,
+ char *list,
+ size_t size);
+smbc_listxattr_fn smbc_getFunctionListxattr(SMBCCTX *c);
+void smbc_setFunctionListxattr(SMBCCTX *c, smbc_listxattr_fn fn);
+
+
+
+/*****************************************************************
+ * Callable functions for printing. *
+ * Each callable has a function signature typedef, a declaration *
+ * for the getter, and a declaration for the setter. *
+ *****************************************************************/
+
+typedef int (*smbc_print_file_fn)(SMBCCTX *c_file,
+ const char *fname,
+ SMBCCTX *c_print,
+ const char *printq);
+smbc_print_file_fn smbc_getFunctionPrintFile(SMBCCTX *c);
+void smbc_setFunctionPrintFile(SMBCCTX *c, smbc_print_file_fn fn);
+
+typedef SMBCFILE * (*smbc_open_print_job_fn)(SMBCCTX *c,
+ const char *fname);
+smbc_open_print_job_fn smbc_getFunctionOpenPrintJob(SMBCCTX *c);
+void smbc_setFunctionOpenPrintJob(SMBCCTX *c,
+ smbc_open_print_job_fn fn);
+
+typedef int (*smbc_list_print_jobs_fn)(SMBCCTX *c,
+ const char *fname,
+ smbc_list_print_job_fn fn);
+smbc_list_print_jobs_fn smbc_getFunctionListPrintJobs(SMBCCTX *c);
+void smbc_setFunctionListPrintJobs(SMBCCTX *c,
+ smbc_list_print_jobs_fn fn);
+
+typedef int (*smbc_unlink_print_job_fn)(SMBCCTX *c,
+ const char *fname,
+ int id);
+smbc_unlink_print_job_fn smbc_getFunctionUnlinkPrintJob(SMBCCTX *c);
+void smbc_setFunctionUnlinkPrintJob(SMBCCTX *c,
+ smbc_unlink_print_job_fn fn);
+
+
+/**@ingroup misc
+ * Create a new SBMCCTX (a context).
+ *
+ * Must be called before the context is passed to smbc_context_init()
+ *
+ * @return The given SMBCCTX pointer on success, NULL on error with errno set:
+ * - ENOMEM Out of memory
+ *
+ * @see smbc_free_context(), smbc_init_context()
+ *
+ * @note Do not forget to smbc_init_context() the returned SMBCCTX pointer !
+ */
+SMBCCTX * smbc_new_context(void);
+
+/**@ingroup misc
+ * Delete a SBMCCTX (a context) acquired from smbc_new_context().
+ *
+ * The context will be deleted if possible.
+ *
+ * @param context A pointer to a SMBCCTX obtained from smbc_new_context()
+ *
+ * @param shutdown_ctx If 1, all connections and files will be closed even if they are busy.
+ *
+ *
+ * @return Returns 0 on success. Returns 1 on failure with errno set:
+ * - EBUSY Server connections are still used, Files are open or cache
+ * could not be purged
+ * - EBADF context == NULL
+ *
+ * @see smbc_new_context()
+ *
+ * @note It is advised to clean up all the contexts with shutdown_ctx set to 1
+ * just before exit()'ing. When shutdown_ctx is 0, this function can be
+ * use in periodical cleanup functions for example.
+ */
+int smbc_free_context(SMBCCTX * context, int shutdown_ctx);
+
+
+/**@ingroup misc
+ *
+ * @deprecated. Use smbc_setOption*() functions instead.
+ */
+void
+smbc_option_set(SMBCCTX *context,
+ char *option_name,
+ ... /* option_value */);
+
+/*
+ * @deprecated. Use smbc_getOption*() functions instead.
+ */
+void *
+smbc_option_get(SMBCCTX *context,
+ char *option_name);
+
+/**@ingroup misc
+ * Initialize a SBMCCTX (a context).
+ *
+ * Must be called before using any SMBCCTX API function
+ *
+ * @param context A pointer to a SMBCCTX obtained from smbc_new_context()
+ *
+ * @return A pointer to the given SMBCCTX on success,
+ * NULL on error with errno set:
+ * - EBADF NULL context given
+ * - ENOMEM Out of memory
+ * - ENOENT The smb.conf file would not load
+ *
+ * @see smbc_new_context()
+ *
+ * @note my_context = smbc_init_context(smbc_new_context())
+ * is perfectly safe, but it might leak memory on
+ * smbc_context_init() failure. Avoid this.
+ * You'll have to call smbc_free_context() yourself
+ * on failure.
+ */
+
+SMBCCTX * smbc_init_context(SMBCCTX * context);
+
+/**@ingroup misc
+ * Initialize the samba client library.
+ *
+ * @deprecated use smbc_init_context()
+ * @see smbc_init_context()
+ */
+DEPRECATED_SMBC_INTERFACE
+int smbc_init(smbc_get_auth_data_fn fn, int debug);
+
+/**@ingroup misc
+ * Set or retrieve the compatibility library's context pointer
+ *
+ * @param context New context to use, or NULL. If a new context is provided,
+ * it must have allocated with smbc_new_context() and
+ * initialized with smbc_init_context(), followed, optionally,
+ * by some manual changes to some of the non-internal fields.
+ *
+ * @return The old context.
+ *
+ * @see smbc_new_context(), smbc_init_context(), smbc_init()
+ *
+ * @note This function may be called prior to smbc_init() to force
+ * use of the next context without any internal calls to
+ * smbc_new_context() or smbc_init_context(). It may also
+ * be called after smbc_init() has already called those two
+ * functions, to replace the existing context with a new one.
+ * Care should be taken, in this latter case, to ensure that
+ * the server cache and any data allocated by the
+ * authentication functions have been freed, if necessary.
+ */
+
+SMBCCTX * smbc_set_context(SMBCCTX * new_context);
+
+/**@ingroup file
+ * Open a file on an SMB server.
+ *
+ * @param furl The smb url of the file to be opened.
+ *
+ * @param flags Is one of O_RDONLY, O_WRONLY or O_RDWR which
+ * request opening the file read-only,write-only
+ * or read/write. flags may also be bitwise-or'd with
+ * one or more of the following:
+ * O_CREAT - If the file does not exist it will be
+ * created.
+ * O_EXCL - When used with O_CREAT, if the file
+ * already exists it is an error and the open will
+ * fail.
+ * O_TRUNC - If the file already exists it will be
+ * truncated.
+ * O_APPEND The file is opened in append mode
+ *
+ * @param mode mode specifies the permissions to use if a new
+ * file is created. It is modified by the
+ * process's umask in the usual way: the permissions
+ * of the created file are (mode & ~umask)
+ *
+ * Not currently use, but there for future use.
+ * We will map this to SYSTEM, HIDDEN, etc bits
+ * that reverses the mapping that smbc_fstat does.
+ *
+ * @return Valid file handle, < 0 on error with errno set:
+ * - ENOMEM Out of memory
+ * - EINVAL if an invalid parameter passed, like no
+ * file, or smbc_init not called.
+ * - EEXIST pathname already exists and O_CREAT and
+ * O_EXCL were used.
+ * - EISDIR pathname refers to a directory and
+ * the access requested involved writing.
+ * - EACCES The requested access to the file is not
+ * allowed
+ * - ENODEV The requested share does not exist
+ * - ENOTDIR A file on the path is not a directory
+ * - ENOENT A directory component in pathname does
+ * not exist.
+ *
+ * @see smbc_creat()
+ *
+ * @note This call uses an underlying routine that may create
+ * a new connection to the server specified in the URL.
+ * If the credentials supplied in the URL, or via the
+ * auth_fn in the smbc_init call, fail, this call will
+ * try again with an empty username and password. This
+ * often gets mapped to the guest account on some machines.
+ */
+
+int smbc_open(const char *furl, int flags, mode_t mode);
+
+/**@ingroup file
+ * Create a file on an SMB server.
+ *
+ * Same as calling smbc_open() with flags = O_CREAT|O_WRONLY|O_TRUNC
+ *
+ * @param furl The smb url of the file to be created
+ *
+ * @param mode mode specifies the permissions to use if a new
+ * file is created. It is modified by the
+ * process's umask in the usual way: the permissions
+ * of the created file are (mode & ~umask)
+ *
+ * NOTE, the above is not true. We are dealing with
+ * an SMB server, which has no concept of a umask!
+ *
+ * @return Valid file handle, < 0 on error with errno set:
+ * - ENOMEM Out of memory
+ * - EINVAL if an invalid parameter passed, like no
+ * file, or smbc_init not called.
+ * - EEXIST pathname already exists and O_CREAT and
+ * O_EXCL were used.
+ * - EISDIR pathname refers to a directory and
+ * the access requested involved writing.
+ * - EACCES The requested access to the file is not
+ * allowed
+ * - ENOENT A directory component in pathname does
+ * not exist.
+ * - ENODEV The requested share does not exist.
+ * @see smbc_open()
+ *
+ */
+
+int smbc_creat(const char *furl, mode_t mode);
+
+/**@ingroup file
+ * Read from a file using an opened file handle.
+ *
+ * @param fd Open file handle from smbc_open() or smbc_creat()
+ *
+ * @param buf Pointer to buffer to receive read data
+ *
+ * @param bufsize Size of buf in bytes
+ *
+ * @return Number of bytes read;
+ * 0 upon EOF;
+ * < 0 on error, with errno set:
+ * - EISDIR fd refers to a directory
+ * - EBADF fd is not a valid file descriptor or
+ * is not open for reading.
+ * - EINVAL fd is attached to an object which is
+ * unsuitable for reading, or no buffer passed or
+ * smbc_init not called.
+ *
+ * @see smbc_open(), smbc_write()
+ *
+ */
+ssize_t smbc_read(int fd, void *buf, size_t bufsize);
+
+
+/**@ingroup file
+ * Write to a file using an opened file handle.
+ *
+ * @param fd Open file handle from smbc_open() or smbc_creat()
+ *
+ * @param buf Pointer to buffer to receive read data
+ *
+ * @param bufsize Size of buf in bytes
+ *
+ * @return Number of bytes written, < 0 on error with errno set:
+ * - EISDIR fd refers to a directory.
+ * - EBADF fd is not a valid file descriptor or
+ * is not open for reading.
+ * - EINVAL fd is attached to an object which is
+ * unsuitable for reading, or no buffer passed or
+ * smbc_init not called.
+ *
+ * @see smbc_open(), smbc_read()
+ *
+ */
+ssize_t smbc_write(int fd, const void *buf, size_t bufsize);
+
+
+/**@ingroup file
+ * Seek to a specific location in a file.
+ *
+ * @param fd Open file handle from smbc_open() or smbc_creat()
+ *
+ * @param offset Offset in bytes from whence
+ *
+ * @param whence A location in the file:
+ * - SEEK_SET The offset is set to offset bytes from
+ * the beginning of the file
+ * - SEEK_CUR The offset is set to current location
+ * plus offset bytes.
+ * - SEEK_END The offset is set to the size of the
+ * file plus offset bytes.
+ *
+ * @return Upon successful completion, lseek returns the
+ * resulting offset location as measured in bytes
+ * from the beginning of the file. Otherwise, a value
+ * of (off_t)-1 is returned and errno is set to
+ * indicate the error:
+ * - EBADF Fildes is not an open file descriptor.
+ * - EINVAL Whence is not a proper value or smbc_init
+ * not called.
+ *
+ * @todo Are all the whence values really supported?
+ *
+ * @todo Are errno values complete and correct?
+ */
+off_t smbc_lseek(int fd, off_t offset, int whence);
+
+
+/**@ingroup file
+ * Close an open file handle.
+ *
+ * @param fd The file handle to close
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EBADF fd isn't a valid open file descriptor
+ * - EINVAL smbc_init() failed or has not been called
+ *
+ * @see smbc_open(), smbc_creat()
+ */
+int smbc_close(int fd);
+
+
+/**@ingroup directory
+ * Unlink (delete) a file or directory.
+ *
+ * @param furl The smb url of the file to delete
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EACCES or EPERM Write access to the directory
+ * containing pathname is not allowed or one
+ * of the directories in pathname did not allow
+ * search (execute) permission
+ * - ENOENT A directory component in pathname does
+ * not exist
+ * - EINVAL NULL was passed in the file param or
+ * smbc_init not called.
+ * - EACCES You do not have access to the file
+ * - ENOMEM Insufficient kernel memory was available
+ *
+ * @see smbc_rmdir()s
+ *
+ * @todo Are errno values complete and correct?
+ */
+int smbc_unlink(const char *furl);
+
+
+/**@ingroup directory
+ * Rename or move a file or directory.
+ *
+ * @param ourl The original smb url (source url) of file or
+ * directory to be moved
+ *
+ * @param nurl The new smb url (destination url) of the file
+ * or directory after the move. Currently nurl must
+ * be on the same share as ourl.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EISDIR nurl is an existing directory, but ourl is
+ * not a directory.
+ * - EEXIST nurl is a non-empty directory,
+ * i.e., contains entries other than "." and ".."
+ * - EINVAL The new url contained a path prefix
+ * of the old, or, more generally, an attempt was
+ * made to make a directory a subdirectory of itself
+ * or smbc_init not called.
+ * - ENOTDIR A component used as a directory in ourl
+ * or nurl path is not, in fact, a directory. Or,
+ * ourl is a directory, and newpath exists but is not
+ * a directory.
+ * - EACCES or EPERM Write access to the directory
+ * containing ourl or nurl is not allowed for the
+ * process's effective uid, or one of the
+ * directories in ourl or nurl did not allow search
+ * (execute) permission, or ourl was a directory
+ * and did not allow write permission.
+ * - ENOENT A directory component in ourl or nurl
+ * does not exist.
+ * - EXDEV Rename across shares not supported.
+ * - ENOMEM Insufficient kernel memory was available.
+ * - EEXIST The target file, nurl, already exists.
+ *
+ *
+ * @todo Are we going to support copying when urls are not on the same
+ * share? I say no... NOTE. I agree for the moment.
+ *
+ */
+int smbc_rename(const char *ourl, const char *nurl);
+
+
+/**@ingroup directory
+ * Open a directory used to obtain directory entries.
+ *
+ * @param durl The smb url of the directory to open
+ *
+ * @return Valid directory handle. < 0 on error with errno set:
+ * - EACCES Permission denied.
+ * - EINVAL A NULL file/URL was passed, or the URL would
+ * not parse, or was of incorrect form or smbc_init not
+ * called.
+ * - ENOENT durl does not exist, or name is an
+ * - ENOMEM Insufficient memory to complete the
+ * operation.
+ * - ENOTDIR name is not a directory.
+ * - EPERM the workgroup could not be found.
+ * - ENODEV the workgroup or server could not be found.
+ *
+ * @see smbc_getdents(), smbc_readdir(), smbc_closedir()
+ *
+ */
+int smbc_opendir(const char *durl);
+
+
+/**@ingroup directory
+ * Close a directory handle opened by smbc_opendir().
+ *
+ * @param dh Directory handle to close
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EBADF dh is an invalid directory handle
+ *
+ * @see smbc_opendir()
+ */
+int smbc_closedir(int dh);
+
+
+/**@ingroup directory
+ * Get multiple directory entries.
+ *
+ * smbc_getdents() reads as many dirent structures from the an open
+ * directory handle into a specified memory area as will fit.
+ *
+ * @param dh Valid directory as returned by smbc_opendir()
+ *
+ * @param dirp pointer to buffer that will receive the directory
+ * entries.
+ *
+ * @param count The size of the dirp buffer in bytes
+ *
+ * @returns If any dirents returned, return will indicate the
+ * total size. If there were no more dirents available,
+ * 0 is returned. < 0 indicates an error.
+ * - EBADF Invalid directory handle
+ * - EINVAL Result buffer is too small or smbc_init
+ * not called.
+ * - ENOENT No such directory.
+ * @see , smbc_dirent, smbc_readdir(), smbc_open()
+ *
+ * @todo Are errno values complete and correct?
+ *
+ * @todo Add example code so people know how to parse buffers.
+ */
+int smbc_getdents(unsigned int dh, struct smbc_dirent *dirp, int count);
+
+
+/**@ingroup directory
+ * Get a single directory entry.
+ *
+ * @param dh Valid directory as returned by smbc_opendir()
+ *
+ * @return A pointer to a smbc_dirent structure, or NULL if an
+ * error occurs or end-of-directory is reached:
+ * - EBADF Invalid directory handle
+ * - EINVAL smbc_init() failed or has not been called
+ *
+ * @see smbc_dirent, smbc_getdents(), smbc_open()
+ */
+struct smbc_dirent* smbc_readdir(unsigned int dh);
+
+/**@ingroup directory
+ * Works similar as smbc_readdir() but returns more information about file.
+ *
+ * @param dh Valid directory as returned by smbc_opendir()
+ *
+ * @return A const pointer to a libsmb_file_info structure,
+ * or NULL if an error occurs or end-of-directory is reached:
+ * - EBADF Invalid directory handle
+ * - EINVAL smbc_init() failed or has not been called
+ *
+ * @see smbc_open(), smbc_readdir()
+ */
+const struct libsmb_file_info *smbc_readdirplus(unsigned int dh);
+
+/**@ingroup directory
+ * Works similar as smbc_readdirplus() as well as fills up stat structure if
+ * provided.
+ *
+ * @param dh Valid directory as returned by smbc_opendir()
+ *
+ * @param stat Pointer to stat structure which will receive the
+ * information. If this pointer is null the call
+ * is identical to smbc_readdirplus.
+ *
+ * @return A const pointer to a libsmb_file_info structure,
+ * or NULL if an error occurs or end-of-directory is reached:
+ * - EBADF Invalid directory handle
+ * - EINVAL smbc_init() failed or has not been called
+ *
+ * @see smbc_open(), smbc_readdir(), smbc_readdirplus2()
+ */
+const struct libsmb_file_info *smbc_readdirplus2(unsigned int dh,
+ struct stat *st);
+
+/**@ingroup directory
+ * Get the current directory offset.
+ *
+ * smbc_telldir() may be used in conjunction with smbc_readdir() and
+ * smbc_lseekdir().
+ *
+ * @param dh Valid directory as returned by smbc_opendir()
+ *
+ * @return The current location in the directory stream or -1
+ * if an error occur. The current location is not
+ * an offset. Because of the implementation, it is a
+ * handle that allows the library to find the entry
+ * later.
+ * - EBADF dh is not a valid directory handle
+ * - EINVAL smbc_init() failed or has not been called
+ * - ENOTDIR if dh is not a directory
+ *
+ * @see smbc_readdir()
+ *
+ */
+off_t smbc_telldir(int dh);
+
+
+/**@ingroup directory
+ * lseek on directories.
+ *
+ * smbc_lseekdir() may be used in conjunction with smbc_readdir() and
+ * smbc_telldir(). (rewind by smbc_lseekdir(fd, NULL))
+ *
+ * @param fd Valid directory as returned by smbc_opendir()
+ *
+ * @param offset The offset (as returned by smbc_telldir). Can be
+ * NULL, in which case we will rewind
+ *
+ * @return 0 on success, -1 on failure
+ * - EBADF dh is not a valid directory handle
+ * - ENOTDIR if dh is not a directory
+ * - EINVAL offset did not refer to a valid dirent or
+ * smbc_init not called.
+ *
+ * @see smbc_telldir()
+ *
+ *
+ * @todo In what does the return and errno values mean?
+ */
+int smbc_lseekdir(int fd, off_t offset);
+
+/**@ingroup directory
+ * Create a directory.
+ *
+ * @param durl The url of the directory to create
+ *
+ * @param mode Specifies the permissions to use. It is modified
+ * by the process's umask in the usual way: the
+ * permissions of the created file are (mode & ~umask).
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EEXIST directory url already exists
+ * - EACCES The parent directory does not allow write
+ * permission to the process, or one of the directories
+ * - ENOENT A directory component in pathname does not
+ * exist.
+ * - EINVAL NULL durl passed or smbc_init not called.
+ * - ENOMEM Insufficient memory was available.
+ *
+ * @see smbc_rmdir()
+ *
+ */
+int smbc_mkdir(const char *durl, mode_t mode);
+
+
+/**@ingroup directory
+ * Remove a directory.
+ *
+ * @param durl The smb url of the directory to remove
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EACCES or EPERM Write access to the directory
+ * containing pathname was not allowed.
+ * - EINVAL durl is NULL or smbc_init not called.
+ * - ENOENT A directory component in pathname does not
+ * exist.
+ * - ENOTEMPTY directory contains entries.
+ * - ENOMEM Insufficient kernel memory was available.
+ *
+ * @see smbc_mkdir(), smbc_unlink()
+ *
+ * @todo Are errno values complete and correct?
+ */
+int smbc_rmdir(const char *durl);
+
+/**@ingroup directory
+ * Request directory notifications
+ *
+ * @param dh Valid directory as returned by smbc_opendir()
+ *
+ * @param recursive Are changes in subdirectories wanted?
+ *
+ * @param completion_filter Bitwise-or of the SMBC_NOTIFY_CHANGE_*
+ * events that are interesting
+ *
+ * @param callback_timeout_ms If set to non-zero, interval in milliseconds
+ * that "cb" will be called with 0 actions.
+ * This gives "cb" the chance to cancel the
+ * smbc_notify call.
+ *
+ * @param cb Callback functions taking events. If "cb"
+ * returns nonzero, smbc_notify will return.
+ *
+ * @param private_data Pointer given to "cb"
+ *
+ * @return 0 on success, -1 on error with errno set
+ *
+ * @see smbc_opendir(), smbc_closedir()
+ */
+
+#define SMBC_NOTIFY_CHANGE_FILE_NAME 0x001
+#define SMBC_NOTIFY_CHANGE_DIR_NAME 0x002
+#define SMBC_NOTIFY_CHANGE_ATTRIBUTES 0x004
+#define SMBC_NOTIFY_CHANGE_SIZE 0x008
+#define SMBC_NOTIFY_CHANGE_LAST_WRITE 0x010
+#define SMBC_NOTIFY_CHANGE_LAST_ACCESS 0x020
+#define SMBC_NOTIFY_CHANGE_CREATION 0x040
+#define SMBC_NOTIFY_CHANGE_EA 0x080
+#define SMBC_NOTIFY_CHANGE_SECURITY 0x100
+#define SMBC_NOTIFY_CHANGE_STREAM_NAME 0x200
+#define SMBC_NOTIFY_CHANGE_STREAM_SIZE 0x400
+#define SMBC_NOTIFY_CHANGE_STREAM_WRITE 0x800
+
+int smbc_notify(int dh, smbc_bool recursive, uint32_t completion_filter,
+ unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data);
+
+/**@ingroup attribute
+ * Get information about a file or directory.
+ *
+ * @param url The smb url to get information for
+ *
+ * @param st pointer to a buffer that will be filled with
+ * standard Unix struct stat information.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - ENOENT A component of the path file_name does not
+ * exist.
+ * - EINVAL a NULL url was passed or smbc_init not called.
+ * - EACCES Permission denied.
+ * - ENOMEM Out of memory
+ * - ENOTDIR The target dir, url, is not a directory.
+ *
+ * @see Unix stat()
+ *
+ */
+int smbc_stat(const char *url, struct stat *st);
+
+
+/**@ingroup attribute
+ * Get file information via an file descriptor.
+ *
+ * @param fd Open file handle from smbc_open() or smbc_creat()
+ *
+ * @param st pointer to a buffer that will be filled with
+ * standard Unix struct stat information.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EBADF filedes is bad.
+ * - EACCES Permission denied.
+ * - EBADF fd is not a valid file descriptor
+ * - EINVAL Problems occurred in the underlying routines
+ * or smbc_init not called.
+ * - ENOMEM Out of memory
+ *
+ * @see smbc_stat(), Unix stat()
+ *
+ */
+int smbc_fstat(int fd, struct stat *st);
+
+
+/**@ingroup attribute
+ * Get file system information for a specified path.
+ *
+ * @param url The smb url to get information for
+ *
+ * @param st pointer to a buffer that will be filled with
+ * standard Unix struct statvfs information.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EBADF filedes is bad.
+ * - EACCES Permission denied.
+ * - EBADF fd is not a valid file descriptor
+ * - EINVAL Problems occurred in the underlying routines
+ * or smbc_init not called.
+ * - ENOMEM Out of memory
+ *
+ * @see Unix fstatvfs()
+ *
+ */
+int
+smbc_statvfs(char *url,
+ struct statvfs *st);
+
+/**@ingroup attribute
+ * Get file system information via an file descriptor.
+ *
+ * @param fd Open file handle from smbc_open(), smbc_creat(),
+ * or smbc_opendir()
+ *
+ * @param st pointer to a buffer that will be filled with
+ * standard Unix struct statvfs information.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EBADF filedes is bad.
+ * - EACCES Permission denied.
+ * - EBADF fd is not a valid file descriptor
+ * - EINVAL Problems occurred in the underlying routines
+ * or smbc_init not called.
+ * - ENOMEM Out of memory
+ *
+ * @see Unix fstatvfs()
+ *
+ */
+int
+smbc_fstatvfs(int fd,
+ struct statvfs *st);
+
+
+/**@ingroup attribute
+ * Truncate a file given a file descriptor
+ *
+ * @param fd Open file handle from smbc_open() or smbc_creat()
+ *
+ * @param size size to truncate the file to
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EBADF filedes is bad.
+ * - EACCES Permission denied.
+ * - EBADF fd is not a valid file descriptor
+ * - EINVAL Problems occurred in the underlying routines
+ * or smbc_init not called.
+ * - ENOMEM Out of memory
+ *
+ * @see , Unix ftruncate()
+ *
+ */
+int smbc_ftruncate(int fd, off_t size);
+
+
+/**@ingroup attribute
+ * Change the permissions of a file.
+ *
+ * @param url The smb url of the file or directory to change
+ * permissions of
+ *
+ * @param mode The permissions to set:
+ * - Put good explanation of permissions here!
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EPERM The effective UID does not match the owner
+ * of the file, and is not zero
+ * - ENOENT The file does not exist.
+ * - ENOMEM Insufficient was available.
+ * - ENOENT file or directory does not exist
+ *
+ * @todo Actually implement this function?
+ *
+ * @todo Are errno values complete and correct?
+ */
+int smbc_chmod(const char *url, mode_t mode);
+
+/**
+ * @ingroup attribute
+ * Change the last modification time on a file
+ *
+ * @param url The smb url of the file or directory to change
+ * the modification time of
+ *
+ * @param tbuf An array of two timeval structures which contains,
+ * respectively, the desired access and modification times.
+ * NOTE: Only the tv_sec field off each timeval structure is
+ * used. The tv_usec (microseconds) portion is ignored.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - EPERM Permission was denied.
+ *
+ */
+int smbc_utimes(const char *url, struct timeval *tbuf);
+
+#ifdef HAVE_UTIME_H
+/**
+ * @ingroup attribute
+ * Change the last modification time on a file
+ *
+ * @param url The smb url of the file or directory to change
+ * the modification time of
+ *
+ * @param utbuf A pointer to a utimebuf structure which contains the
+ * desired access and modification times.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - ENOMEM No memory was available for internal needs
+ * - EPERM Permission was denied.
+ *
+ */
+int smbc_utime(const char *fname, struct utimbuf *utbuf);
+#endif
+
+/**@ingroup attribute
+ * Set extended attributes for a file. This is used for modifying a file's
+ * security descriptor (i.e. owner, group, and access control list)
+ *
+ * @param url The smb url of the file or directory to set extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be changed. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter should contain a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @param value The value to be assigned to the specified attribute name.
+ * This buffer should contain only the attribute value if the
+ * name was of the "system.nt_sec_desc.<attribute_name>"
+ * form. If the name was of the "system.nt_sec_desc.*" form
+ * then a complete security descriptor, with name:value pairs
+ * separated by tabs, commas, or newlines (not spaces!),
+ * should be provided in this value buffer. A complete
+ * security descriptor will contain one or more entries
+ * selected from the following:
+ *
+ * REVISION:<revision number>
+ * OWNER:<sid or name>
+ * GROUP:<sid or name>
+ * ACL:<sid or name>:<type>/<flags>/<mask>
+ *
+ * The revision of the ACL specifies the internal Windows NT
+ * ACL revision for the security descriptor. If not specified
+ * it defaults to 1. Using values other than 1 may cause
+ * strange behaviour.
+ *
+ * The owner and group specify the owner and group sids for
+ * the object. If the attribute name (either '*+' with a
+ * complete security descriptor, or individual 'owner+' or
+ * 'group+' attribute names) ended with a plus sign, the
+ * specified name is resolved to a SID value, using the
+ * server on which the file or directory resides. Otherwise,
+ * the value should be provided in SID-printable format as
+ * S-1-x-y-z, and is used directly. The <sid or name>
+ * associated with the ACL: attribute should be provided
+ * similarly.
+ *
+ * @param size The number of the bytes of data in the value buffer
+ *
+ * @param flags A bit-wise OR of zero or more of the following:
+ * SMBC_XATTR_FLAG_CREATE -
+ * fail if the named attribute already exists
+ * SMBC_XATTR_FLAG_REPLACE -
+ * fail if the attribute does not already exist
+ *
+ * If neither flag is specified, the specified attributes
+ * will be added or replace existing attributes of the same
+ * name, as necessary.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * or one of the parameters is not of a correct
+ * form
+ * - ENOMEM No memory was available for internal needs
+ * - EEXIST If the attribute already exists and the flag
+ * SMBC_XATTR_FLAG_CREAT was specified
+ * - ENOATTR If the attribute does not exist and the flag
+ * SMBC_XATTR_FLAG_REPLACE was specified
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ * @note Attribute names are compared in a case-insensitive
+ * fashion. All of the following are equivalent, although
+ * the all-lower-case name is the preferred format:
+ * system.nt_sec_desc.owner
+ * SYSTEM.NT_SEC_DESC.OWNER
+ * sYsTeM.nt_sEc_desc.owNER
+ *
+ */
+int smbc_setxattr(const char *url,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags);
+
+
+/**@ingroup attribute
+ * Set extended attributes for a file. This is used for modifying a file's
+ * security descriptor (i.e. owner, group, and access control list). The
+ * POSIX function which this maps to would act on a symbolic link rather than
+ * acting on what the symbolic link points to, but with no symbolic links in
+ * SMB file systems, this function is functionally identical to
+ * smbc_setxattr().
+ *
+ * @param url The smb url of the file or directory to set extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be changed. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter should contain a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @param value The value to be assigned to the specified attribute name.
+ * This buffer should contain only the attribute value if the
+ * name was of the "system.nt_sec_desc.<attribute_name>"
+ * form. If the name was of the "system.nt_sec_desc.*" form
+ * then a complete security descriptor, with name:value pairs
+ * separated by tabs, commas, or newlines (not spaces!),
+ * should be provided in this value buffer. A complete
+ * security descriptor will contain one or more entries
+ * selected from the following:
+ *
+ * REVISION:<revision number>
+ * OWNER:<sid or name>
+ * GROUP:<sid or name>
+ * ACL:<sid or name>:<type>/<flags>/<mask>
+ *
+ * The revision of the ACL specifies the internal Windows NT
+ * ACL revision for the security descriptor. If not specified
+ * it defaults to 1. Using values other than 1 may cause
+ * strange behaviour.
+ *
+ * The owner and group specify the owner and group sids for
+ * the object. If the attribute name (either '*+' with a
+ * complete security descriptor, or individual 'owner+' or
+ * 'group+' attribute names) ended with a plus sign, the
+ * specified name is resolved to a SID value, using the
+ * server on which the file or directory resides. Otherwise,
+ * the value should be provided in SID-printable format as
+ * S-1-x-y-z, and is used directly. The <sid or name>
+ * associated with the ACL: attribute should be provided
+ * similarly.
+ *
+ * @param size The number of the bytes of data in the value buffer
+ *
+ * @param flags A bit-wise OR of zero or more of the following:
+ * SMBC_XATTR_FLAG_CREATE -
+ * fail if the named attribute already exists
+ * SMBC_XATTR_FLAG_REPLACE -
+ * fail if the attribute does not already exist
+ *
+ * If neither flag is specified, the specified attributes
+ * will be added or replace existing attributes of the same
+ * name, as necessary.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * or one of the parameters is not of a correct
+ * form
+ * - ENOMEM No memory was available for internal needs
+ * - EEXIST If the attribute already exists and the flag
+ * SMBC_XATTR_FLAG_CREAT was specified
+ * - ENOATTR If the attribute does not exist and the flag
+ * SMBC_XATTR_FLAG_REPLACE was specified
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ * @note Attribute names are compared in a case-insensitive
+ * fashion. All of the following are equivalent, although
+ * the all-lower-case name is the preferred format:
+ * system.nt_sec_desc.owner
+ * SYSTEM.NT_SEC_DESC.OWNER
+ * sYsTeM.nt_sEc_desc.owNER
+ *
+ */
+int smbc_lsetxattr(const char *url,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags);
+
+
+/**@ingroup attribute
+ * Set extended attributes for a file. This is used for modifying a file's
+ * security descriptor (i.e. owner, group, and access control list)
+ *
+ * @param fd A file descriptor associated with an open file (as
+ * previously returned by smbc_open(), to get extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be changed. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter should contain a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @param value The value to be assigned to the specified attribute name.
+ * This buffer should contain only the attribute value if the
+ * name was of the "system.nt_sec_desc.<attribute_name>"
+ * form. If the name was of the "system.nt_sec_desc.*" form
+ * then a complete security descriptor, with name:value pairs
+ * separated by tabs, commas, or newlines (not spaces!),
+ * should be provided in this value buffer. A complete
+ * security descriptor will contain one or more entries
+ * selected from the following:
+ *
+ * REVISION:<revision number>
+ * OWNER:<sid or name>
+ * GROUP:<sid or name>
+ * ACL:<sid or name>:<type>/<flags>/<mask>
+ *
+ * The revision of the ACL specifies the internal Windows NT
+ * ACL revision for the security descriptor. If not specified
+ * it defaults to 1. Using values other than 1 may cause
+ * strange behaviour.
+ *
+ * The owner and group specify the owner and group sids for
+ * the object. If the attribute name (either '*+' with a
+ * complete security descriptor, or individual 'owner+' or
+ * 'group+' attribute names) ended with a plus sign, the
+ * specified name is resolved to a SID value, using the
+ * server on which the file or directory resides. Otherwise,
+ * the value should be provided in SID-printable format as
+ * S-1-x-y-z, and is used directly. The <sid or name>
+ * associated with the ACL: attribute should be provided
+ * similarly.
+ *
+ * @param size The number of the bytes of data in the value buffer
+ *
+ * @param flags A bit-wise OR of zero or more of the following:
+ * SMBC_XATTR_FLAG_CREATE -
+ * fail if the named attribute already exists
+ * SMBC_XATTR_FLAG_REPLACE -
+ * fail if the attribute does not already exist
+ *
+ * If neither flag is specified, the specified attributes
+ * will be added or replace existing attributes of the same
+ * name, as necessary.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * or one of the parameters is not of a correct
+ * form
+ * - ENOMEM No memory was available for internal needs
+ * - EEXIST If the attribute already exists and the flag
+ * SMBC_XATTR_FLAG_CREAT was specified
+ * - ENOATTR If the attribute does not exist and the flag
+ * SMBC_XATTR_FLAG_REPLACE was specified
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ * @note Attribute names are compared in a case-insensitive
+ * fashion. All of the following are equivalent, although
+ * the all-lower-case name is the preferred format:
+ * system.nt_sec_desc.owner
+ * SYSTEM.NT_SEC_DESC.OWNER
+ * sYsTeM.nt_sEc_desc.owNER
+ *
+ */
+int smbc_fsetxattr(int fd,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags);
+
+
+/**@ingroup attribute
+ * Get extended attributes for a file.
+ *
+ * @param url The smb url of the file or directory to get extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be retrieved. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter will return a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @param value A pointer to a buffer in which the value of the specified
+ * attribute will be placed (unless size is zero).
+ *
+ * @param size The size of the buffer pointed to by value. This parameter
+ * may also be zero, in which case the size of the buffer
+ * required to hold the attribute value will be returned,
+ * but nothing will be placed into the value buffer.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * or one of the parameters is not of a correct
+ * form
+ * - ENOMEM No memory was available for internal needs
+ * - EEXIST If the attribute already exists and the flag
+ * SMBC_XATTR_FLAG_CREAT was specified
+ * - ENOATTR If the attribute does not exist and the flag
+ * SMBC_XATTR_FLAG_REPLACE was specified
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ */
+int smbc_getxattr(const char *url,
+ const char *name,
+ const void *value,
+ size_t size);
+
+
+/**@ingroup attribute
+ * Get extended attributes for a file. The POSIX function which this maps to
+ * would act on a symbolic link rather than acting on what the symbolic link
+ * points to, but with no symbolic links in SMB file systems, this function
+ * is functionally identical to smbc_getxattr().
+ *
+ * @param url The smb url of the file or directory to get extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be retrieved. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter will return a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @param value A pointer to a buffer in which the value of the specified
+ * attribute will be placed (unless size is zero).
+ *
+ * @param size The size of the buffer pointed to by value. This parameter
+ * may also be zero, in which case the size of the buffer
+ * required to hold the attribute value will be returned,
+ * but nothing will be placed into the value buffer.
+ *
+ * @return size on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * or one of the parameters is not of a correct
+ * form
+ * - ENOMEM No memory was available for internal needs
+ * - EEXIST If the attribute already exists and the flag
+ * SMBC_XATTR_FLAG_CREAT was specified
+ * - ENOATTR If the attribute does not exist and the flag
+ * SMBC_XATTR_FLAG_REPLACE was specified
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ */
+int smbc_lgetxattr(const char *url,
+ const char *name,
+ const void *value,
+ size_t size);
+
+
+/**@ingroup attribute
+ * Get extended attributes for a file.
+ *
+ * @param fd A file descriptor associated with an open file (as
+ * previously returned by smbc_open(), to get extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be retrieved. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter will return a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @param value A pointer to a buffer in which the value of the specified
+ * attribute will be placed (unless size is zero).
+ *
+ * @param size The size of the buffer pointed to by value. This parameter
+ * may also be zero, in which case the size of the buffer
+ * required to hold the attribute value will be returned,
+ * but nothing will be placed into the value buffer.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * or one of the parameters is not of a correct
+ * form
+ * - ENOMEM No memory was available for internal needs
+ * - EEXIST If the attribute already exists and the flag
+ * SMBC_XATTR_FLAG_CREAT was specified
+ * - ENOATTR If the attribute does not exist and the flag
+ * SMBC_XATTR_FLAG_REPLACE was specified
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ */
+int smbc_fgetxattr(int fd,
+ const char *name,
+ const void *value,
+ size_t size);
+
+
+/**@ingroup attribute
+ * Remove extended attributes for a file. This is used for modifying a file's
+ * security descriptor (i.e. owner, group, and access control list)
+ *
+ * @param url The smb url of the file or directory to remove the extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be removed. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter will return a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - ENOMEM No memory was available for internal needs
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ */
+int smbc_removexattr(const char *url,
+ const char *name);
+
+
+/**@ingroup attribute
+ * Remove extended attributes for a file. This is used for modifying a file's
+ * security descriptor (i.e. owner, group, and access control list) The POSIX
+ * function which this maps to would act on a symbolic link rather than acting
+ * on what the symbolic link points to, but with no symbolic links in SMB file
+ * systems, this function is functionally identical to smbc_removexattr().
+ *
+ * @param url The smb url of the file or directory to remove the extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be removed. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter will return a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - ENOMEM No memory was available for internal needs
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ */
+int smbc_lremovexattr(const char *url,
+ const char *name);
+
+
+/**@ingroup attribute
+ * Remove extended attributes for a file. This is used for modifying a file's
+ * security descriptor (i.e. owner, group, and access control list)
+ *
+ * @param fd A file descriptor associated with an open file (as
+ * previously returned by smbc_open(), to get extended
+ * attributes for.
+ *
+ * @param name The name of an attribute to be removed. Names are of
+ * one of the following forms:
+ *
+ * system.nt_sec_desc.<attribute name>
+ * system.nt_sec_desc.*
+ * system.nt_sec_desc.*+
+ *
+ * where <attribute name> is one of:
+ *
+ * revision
+ * owner
+ * owner+
+ * group
+ * group+
+ * acl:<name or sid>
+ * acl+:<name or sid>
+ *
+ * In the forms "system.nt_sec_desc.*" and
+ * "system.nt_sec_desc.*+", the asterisk and plus signs are
+ * literal, i.e. the string is provided exactly as shown, and
+ * the value parameter will return a complete security
+ * descriptor with name:value pairs separated by tabs,
+ * commas, or newlines (not spaces!).
+ *
+ * The plus sign ('+') indicates that SIDs should be mapped
+ * to names. Without the plus sign, SIDs are not mapped;
+ * rather they are simply converted to a string format.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - ENOMEM No memory was available for internal needs
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ */
+int smbc_fremovexattr(int fd,
+ const char *name);
+
+
+/**@ingroup attribute
+ * List the supported extended attribute names associated with a file
+ *
+ * @param url The smb url of the file or directory to list the extended
+ * attributes for.
+ *
+ * @param list A pointer to a buffer in which the list of attributes for
+ * the specified file or directory will be placed (unless
+ * size is zero).
+ *
+ * @param size The size of the buffer pointed to by list. This parameter
+ * may also be zero, in which case the size of the buffer
+ * required to hold all of the attribute names will be
+ * returned, but nothing will be placed into the list buffer.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - ENOMEM No memory was available for internal needs
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ * @note This function always returns all attribute names supported
+ * by NT file systems, regardless of whether the referenced
+ * file system supports extended attributes (e.g. a Windows
+ * 2000 machine supports extended attributes if NTFS is used,
+ * but not if FAT is used, and Windows 98 doesn't support
+ * extended attributes at all. Whether this is a feature or
+ * a bug is yet to be decided.
+ */
+int smbc_listxattr(const char *url,
+ char *list,
+ size_t size);
+
+/**@ingroup attribute
+ * List the supported extended attribute names associated with a file The
+ * POSIX function which this maps to would act on a symbolic link rather than
+ * acting on what the symbolic link points to, but with no symbolic links in
+ * SMB file systems, this function is functionally identical to
+ * smbc_listxattr().
+ *
+ * @param url The smb url of the file or directory to list the extended
+ * attributes for.
+ *
+ * @param list A pointer to a buffer in which the list of attributes for
+ * the specified file or directory will be placed (unless
+ * size is zero).
+ *
+ * @param size The size of the buffer pointed to by list. This parameter
+ * may also be zero, in which case the size of the buffer
+ * required to hold all of the attribute names will be
+ * returned, but nothing will be placed into the list buffer.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - ENOMEM No memory was available for internal needs
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ * @note This function always returns all attribute names supported
+ * by NT file systems, regardless of whether the referenced
+ * file system supports extended attributes (e.g. a Windows
+ * 2000 machine supports extended attributes if NTFS is used,
+ * but not if FAT is used, and Windows 98 doesn't support
+ * extended attributes at all. Whether this is a feature or
+ * a bug is yet to be decided.
+ */
+int smbc_llistxattr(const char *url,
+ char *list,
+ size_t size);
+
+/**@ingroup attribute
+ * List the supported extended attribute names associated with a file
+ *
+ * @param fd A file descriptor associated with an open file (as
+ * previously returned by smbc_open(), to get extended
+ * attributes for.
+ *
+ * @param list A pointer to a buffer in which the list of attributes for
+ * the specified file or directory will be placed (unless
+ * size is zero).
+ *
+ * @param size The size of the buffer pointed to by list. This parameter
+ * may also be zero, in which case the size of the buffer
+ * required to hold all of the attribute names will be
+ * returned, but nothing will be placed into the list buffer.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL The client library is not properly initialized
+ * - ENOMEM No memory was available for internal needs
+ * - EPERM Permission was denied.
+ * - ENOTSUP The referenced file system does not support
+ * extended attributes
+ *
+ * @note This function always returns all attribute names supported
+ * by NT file systems, regardless of whether the referenced
+ * file system supports extended attributes (e.g. a Windows
+ * 2000 machine supports extended attributes if NTFS is used,
+ * but not if FAT is used, and Windows 98 doesn't support
+ * extended attributes at all. Whether this is a feature or
+ * a bug is yet to be decided.
+ */
+int smbc_flistxattr(int fd,
+ char *list,
+ size_t size);
+
+/**@ingroup print
+ * Print a file given the name in fname. It would be a URL ...
+ *
+ * @param fname The URL of a file on a remote SMB server that the
+ * caller wants printed
+ *
+ * @param printq The URL of the print share to print the file to.
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ *
+ * - EINVAL fname or printq was NULL or smbc_init not
+ * not called.
+ * and errors returned by smbc_open
+ *
+ */
+int smbc_print_file(const char *fname, const char *printq);
+
+/**@ingroup print
+ * Open a print file that can be written to by other calls. This simply
+ * does an smbc_open call after checking if there is a file name on the
+ * URI. If not, a temporary name is added ...
+ *
+ * @param fname The URL of the print share to print to?
+ *
+ * @returns A file handle for the print file if successful.
+ * Returns -1 if an error occurred and errno has the values
+ * - EINVAL fname was NULL or smbc_init not called.
+ * - all errors returned by smbc_open
+ *
+ */
+int smbc_open_print_job(const char *fname);
+
+/**@ingroup print
+ * List the print jobs on a print share, for the moment, pass a callback
+ *
+ * @param purl The url of the print share to list the jobs of
+ *
+ * @param fn Callback function the receives printjob info
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL fname was NULL or smbc_init not called
+ * - EACCES ???
+ */
+int smbc_list_print_jobs(const char *purl, smbc_list_print_job_fn fn);
+
+/**@ingroup print
+ * Delete a print job
+ *
+ * @param purl Url of the print share
+ *
+ * @param id The id of the job to delete
+ *
+ * @return 0 on success, < 0 on error with errno set:
+ * - EINVAL fname was NULL or smbc_init not called
+ *
+ * @todo what errno values are possible here?
+ */
+int smbc_unlink_print_job(const char *purl, int id);
+
+/**@ingroup callback
+ * Remove a server from the cached server list it's unused.
+ *
+ * @param context pointer to smb context
+ *
+ * @param srv pointer to server to remove
+ *
+ * @return On success, 0 is returned. 1 is returned if the server could not
+ * be removed. Also usable outside libsmbclient.
+ */
+int smbc_remove_unused_server(SMBCCTX * context, SMBCSRV * srv);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**@ingroup directory
+ * Convert strings of %xx to their single character equivalent.
+ *
+ * @param dest A pointer to a buffer in which the resulting decoded
+ * string should be placed. This may be a pointer to the
+ * same buffer as src_segment.
+ *
+ * @param src A pointer to the buffer containing the URL to be decoded.
+ * Any %xx sequences herein are converted to their single
+ * character equivalent. Each 'x' must be a valid hexadecimal
+ * digit, or that % sequence is left undecoded.
+ *
+ * @param max_dest_len
+ * The size of the buffer pointed to by dest_segment.
+ *
+ * @return The number of % sequences which could not be converted
+ * due to lack of two following hexadecimal digits.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+int
+smbc_urldecode(char *dest, char * src, size_t max_dest_len);
+#ifdef __cplusplus
+}
+#endif
+
+
+/*
+ * Convert any characters not specifically allowed in a URL into their %xx
+ * equivalent.
+ *
+ * @param dest A pointer to a buffer in which the resulting encoded
+ * string should be placed. Unlike smbc_urldecode(), this
+ * must be a buffer unique from src.
+ *
+ * @param src A pointer to the buffer containing the string to be encoded.
+ * Any character not specifically allowed in a URL is converted
+ * into its hexadecimal value and encoded as %xx.
+ *
+ * @param max_dest_len
+ * The size of the buffer pointed to by dest_segment.
+ *
+ * @returns The remaining buffer length.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+int
+smbc_urlencode(char * dest, char * src, int max_dest_len);
+#ifdef __cplusplus
+}
+#endif
+
+
+/**@ingroup directory
+ * Return the version of the linked Samba code, and thus the version of the
+ * libsmbclient code.
+ *
+ * @return The version string.
+ */
+#ifdef __cplusplus
+extern "C" {
+#endif
+const char *
+smbc_version(void);
+#ifdef __cplusplus
+}
+#endif
+
+/**@ingroup misc
+ * @deprecated This interface has been deprecated use
+ * smbc_set_credentials_with_fallback() instead.
+ *
+ * @see smbc_set_credentials_with_fallback()
+ */
+DEPRECATED_SMBC_INTERFACE
+void
+smbc_set_credentials(const char *workgroup,
+ const char *user,
+ const char *password,
+ smbc_bool use_kerberos,
+ const char *signing_state);
+
+/**@ingroup misc
+ *
+ * Set the users credentials globally so they can be used for DFS
+ * referrals. Probably best to use this function in the smbc_get_auth_data_fn
+ * callback.
+ *
+ * @param ctx The smb context.
+ *
+ * @param workgroup Workgroup of the user.
+ *
+ * @param user Username of user.
+ *
+ * @param password Password of user.
+ */
+void
+smbc_set_credentials_with_fallback(SMBCCTX *ctx,
+ const char *workgroup,
+ const char *user,
+ const char *password);
+
+
+/**
+ * @ingroup threads
+ *
+ * Initialize for threads using the Posix Threads (pthread)
+ * implementation. This is a built-in implementation, avoiding the need to
+ * implement the component functions of the thread interface. If this function
+ * is used, it is not necessary to call smbc_thread_impl().
+ *
+ * @return {void}
+ */
+void
+smbc_thread_posix(void);
+
+/**
+ * @ingroup threads
+ *
+ * Initialize for an arbitrary thread implementation. The caller should
+ * provide, as parameters, pointers to functions to implement the requisite
+ * low-level thread functionality. A function must be provided for each
+ * parameter; none may be null.
+ *
+ * If the thread implementation is POSIX Threads (pthreads), then the much
+ * simpler smbc_thread_pthread() function may be used instead of this one.
+ *
+ * @param create_mutex
+ * Create a mutex. This function should expect three parameters: lockname,
+ * pplock, and location. It should create a unique mutex for each unique
+ * lockname it is provided, and return the mutex identifier in *pplock. The
+ * location parameter can be used for debugging, as it contains the
+ * compiler-provided __location__ of the call.
+ *
+ * @param destroy_mutex
+ * Destroy a mutex. This function should expect two parameters: plock and
+ * location. It should destroy the mutex associated with the identifier
+ * plock. The location parameter can be used for debugging, as it contains
+ * the compiler-provided __location__ of the call.
+ *
+ * @param lock_mutex
+ * Lock a mutex. This function should expect three parameters: plock,
+ * lock_type, and location. The mutex associated with identifier plock
+ * should be locked if lock_type is 1, and unlocked if lock_type is 2. The
+ * location parameter can be used for debugging, as it contains the
+ * compiler-provided __location__ of the call.
+ *
+ * @param create_tls
+ * Create thread local storage. This function should expect three
+ * parameters: keyname, ppkey, and location. It should allocate an
+ * implementation-specific amount of memory and assign the pointer to that
+ * allocated memory to *ppkey. The location parameter can be used for
+ * debugging, as it contains the compiler-provided __location__ of the
+ * call. This function should return 0 upon success, non-zero upon failure.
+ *
+ * @param destroy_tls
+ * Destroy thread local storage. This function should expect two parameters:
+ * ppkey and location. The ppkey parameter points to a variable containing a
+ * thread local storage key previously provided by the create_tls
+ * function. The location parameter can be used for debugging, as it
+ * contains the compiler-provided __location__ of the call.
+ *
+ * @param set_tls
+ * Set a thread local storage variable's value. This function should expect
+ * three parameters: pkey, pval, and location. The pkey parameter is a
+ * thread local storage key previously provided by the create_tls
+ * function. The (void *) pval parameter contains the value to be placed in
+ * the thread local storage variable identified by pkey. The location
+ * parameter can be used for debugging, as it contains the compiler-provided
+ * __location__ of the call. This function should return 0 upon success;
+ * non-zero otherwise.
+ *
+ * @param get_tls
+ * Retrieve a thread local storage variable's value. This function should
+ * expect two parameters: pkey and location. The pkey parameter is a thread
+ * local storage key previously provided by the create_tls function, and
+ * which has previously been used in a call to the set_tls function to
+ * initialize a thread local storage variable. The location parameter can be
+ * used for debugging, as it contains the compiler-provided __location__ of
+ * the call. This function should return the (void *) value stored in the
+ * variable identified by pkey.
+ *
+ * @return {void}
+ */
+void
+smbc_thread_impl(
+ /* Mutex functions. */
+ int (*create_mutex)(const char *lockname,
+ void **pplock,
+ const char *location),
+ void (*destroy_mutex)(void *plock,
+ const char *location),
+ int (*lock_mutex)(void *plock,
+ int lock_type,
+ const char *location),
+
+ /* Thread local storage. */
+ int (*create_tls)(const char *keyname,
+ void **ppkey,
+ const char *location),
+ void (*destroy_tls)(void **ppkey,
+ const char *location),
+ int (*set_tls)(void *pkey,
+ const void *pval,
+ const char *location),
+ void *(*get_tls)(void *pkey,
+ const char *location)
+ );
+
+
+
+/**
+ * @ingroup structure
+ * Structure that contains a client context information
+ * This structure is known as SMBCCTX
+ *
+ * DO NOT DIRECTLY MANIPULATE THE CONTEXT STRUCTURE! The data in the context
+ * structure should all be considered private to the library. It remains here
+ * only for backward compatibility.
+ *
+ * See the comments herein for use of the setter and getter functions which
+ * should now be used for manipulating these values. New features, functions,
+ * etc., are not added here but rather in _internal where they are not
+ * directly visible to applications. This makes it much easier to maintain
+ * ABI compatibility.
+ */
+struct _SMBCCTX
+{
+ /**
+ * debug level
+ *
+ * DEPRECATED:
+ * Use smbc_getDebug() and smbc_setDebug()
+ */
+ int debug DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * netbios name used for making connections
+ *
+ * DEPRECATED:
+ * Use smbc_getNetbiosName() and smbc_setNetbiosName()
+ */
+ char * netbios_name DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * workgroup name used for making connections
+ *
+ * DEPRECATED:
+ * Use smbc_getWorkgroup() and smbc_setWorkgroup()
+ */
+ char * workgroup DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * username used for making connections
+ *
+ * DEPRECATED:
+ * Use smbc_getUser() and smbc_setUser()
+ */
+ char * user DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * timeout used for waiting on connections / response data (in
+ * milliseconds)
+ *
+ * DEPRECATED:
+ * Use smbc_getTimeout() and smbc_setTimeout()
+ */
+ int timeout DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * callable functions for files:
+ * For usage and return values see the SMBC_* functions
+ *
+ * DEPRECATED:
+ *
+ * Use smbc_getFunction*() and smbc_setFunction*(), e.g.
+ * smbc_getFunctionOpen(), smbc_setFunctionUnlink(), etc.
+ */
+ smbc_open_fn open DEPRECATED_SMBC_INTERFACE;
+ smbc_creat_fn creat DEPRECATED_SMBC_INTERFACE;
+ smbc_read_fn read DEPRECATED_SMBC_INTERFACE;
+ smbc_write_fn write DEPRECATED_SMBC_INTERFACE;
+ smbc_unlink_fn unlink DEPRECATED_SMBC_INTERFACE;
+ smbc_rename_fn rename DEPRECATED_SMBC_INTERFACE;
+ smbc_lseek_fn lseek DEPRECATED_SMBC_INTERFACE;
+ smbc_stat_fn stat DEPRECATED_SMBC_INTERFACE;
+ smbc_fstat_fn fstat DEPRECATED_SMBC_INTERFACE;
+#if 0 /* internal */
+ smbc_ftruncate_fn ftruncate_fn;
+#endif
+ smbc_close_fn close_fn DEPRECATED_SMBC_INTERFACE;
+ smbc_opendir_fn opendir DEPRECATED_SMBC_INTERFACE;
+ smbc_closedir_fn closedir DEPRECATED_SMBC_INTERFACE;
+ smbc_readdir_fn readdir DEPRECATED_SMBC_INTERFACE;
+ smbc_readdirplus_fn readdirplus DEPRECATED_SMBC_INTERFACE;
+ smbc_readdirplus2_fn readdirplus2 DEPRECATED_SMBC_INTERFACE;
+ smbc_getdents_fn getdents DEPRECATED_SMBC_INTERFACE;
+ smbc_mkdir_fn mkdir DEPRECATED_SMBC_INTERFACE;
+ smbc_rmdir_fn rmdir DEPRECATED_SMBC_INTERFACE;
+ smbc_telldir_fn telldir DEPRECATED_SMBC_INTERFACE;
+ smbc_lseekdir_fn lseekdir DEPRECATED_SMBC_INTERFACE;
+ smbc_fstatdir_fn fstatdir DEPRECATED_SMBC_INTERFACE;
+ smbc_chmod_fn chmod DEPRECATED_SMBC_INTERFACE;
+ smbc_utimes_fn utimes DEPRECATED_SMBC_INTERFACE;
+ smbc_setxattr_fn setxattr DEPRECATED_SMBC_INTERFACE;
+ smbc_getxattr_fn getxattr DEPRECATED_SMBC_INTERFACE;
+ smbc_removexattr_fn removexattr DEPRECATED_SMBC_INTERFACE;
+ smbc_listxattr_fn listxattr DEPRECATED_SMBC_INTERFACE;
+
+ /* Printing-related functions */
+ smbc_print_file_fn print_file DEPRECATED_SMBC_INTERFACE;
+ smbc_open_print_job_fn open_print_job DEPRECATED_SMBC_INTERFACE;
+ smbc_list_print_jobs_fn list_print_jobs DEPRECATED_SMBC_INTERFACE;
+ smbc_unlink_print_job_fn unlink_print_job DEPRECATED_SMBC_INTERFACE;
+
+ /*
+ ** Callbacks
+ *
+ * DEPRECATED:
+ *
+ * See the comment above each field, for the getter and setter
+ * functions that should now be used.
+ */
+ struct _smbc_callbacks
+ {
+ /**
+ * authentication function callback: called upon auth requests
+ *
+ * DEPRECATED:
+ * Use smbc_getFunctionAuthData(), smbc_setFunctionAuthData()
+ */
+ smbc_get_auth_data_fn auth_fn DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * check if a server is still good
+ *
+ * DEPRECATED:
+ * Use smbc_getFunctionCheckServer(),
+ * smbc_setFunctionCheckServer()
+ */
+ smbc_check_server_fn check_server_fn DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * remove a server if unused
+ *
+ * DEPRECATED:
+ * Use smbc_getFunctionRemoveUnusedServer(),
+ * smbc_setFunctionCheckServer()
+ */
+ smbc_remove_unused_server_fn remove_unused_server_fn DEPRECATED_SMBC_INTERFACE;
+
+ /** Cache subsystem
+ *
+ * For an example cache system see
+ * samba/source/libsmb/libsmb_cache.c
+ *
+ * Cache subsystem * functions follow.
+ */
+
+ /**
+ * server cache addition
+ *
+ * DEPRECATED:
+ * Use smbc_getFunctionAddCachedServer(),
+ * smbc_setFunctionAddCachedServer()
+ */
+ smbc_add_cached_srv_fn add_cached_srv_fn DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * server cache lookup
+ *
+ * DEPRECATED:
+ * Use smbc_getFunctionGetCachedServer(),
+ * smbc_setFunctionGetCachedServer()
+ */
+ smbc_get_cached_srv_fn get_cached_srv_fn DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * server cache removal
+ *
+ * DEPRECATED:
+ * Use smbc_getFunctionRemoveCachedServer(),
+ * smbc_setFunctionRemoveCachedServer()
+ */
+ smbc_remove_cached_srv_fn remove_cached_srv_fn DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * server cache purging, try to remove all cached servers
+ * (disconnect)
+ *
+ * DEPRECATED:
+ * Use smbc_getFunctionPurgeCachedServers(),
+ * smbc_setFunctionPurgeCachedServers()
+ */
+ smbc_purge_cached_fn purge_cached_fn DEPRECATED_SMBC_INTERFACE;
+ } callbacks;
+
+ /**
+ * Space where the private data of the server cache used to be
+ *
+ * DEPRECATED:
+ * Use smbc_getServerCacheData(), smbc_setServerCacheData()
+ */
+ void * reserved DEPRECATED_SMBC_INTERFACE;
+
+ /*
+ * Very old configuration options.
+ *
+ * DEPRECATED:
+ * Use one of the following functions instead:
+ * smbc_setOptionUseKerberos()
+ * smbc_getOptionUseKerberos()
+ * smbc_setOptionFallbackAfterKerberos()
+ * smbc_getOptionFallbackAfterKerberos()
+ * smbc_setOptionNoAutoAnonymousLogin()
+ * smbc_getOptionNoAutoAnonymousLogin()
+ */
+ int flags DEPRECATED_SMBC_INTERFACE;
+
+ /**
+ * user options selections that apply to this session
+ *
+ * NEW OPTIONS ARE NOT ADDED HERE!
+ *
+ * DEPRECATED:
+ * To set and retrieve options, use the smbc_setOption*() and
+ * smbc_getOption*() functions.
+ */
+ struct _smbc_options {
+ int browse_max_lmb_count DEPRECATED_SMBC_INTERFACE;
+ int urlencode_readdir_entries DEPRECATED_SMBC_INTERFACE;
+ int one_share_per_server DEPRECATED_SMBC_INTERFACE;
+ } options DEPRECATED_SMBC_INTERFACE;
+
+ /** INTERNAL DATA
+ * do _NOT_ touch this from your program !
+ */
+ struct SMBC_internal_data * internal;
+};
+
+
+#endif /* SMBCLIENT_H_INCLUDED */
diff --git a/source3/include/local.h b/source3/include/local.h
new file mode 100644
index 0000000..95be8ee
--- /dev/null
+++ b/source3/include/local.h
@@ -0,0 +1,202 @@
+/* Copyright (C) 1995-1998 Samba-Team */
+/* Copyright (C) 1998 John H Terpstra <jht@aquasoft.com.au> */
+
+/* local definitions for file server */
+#ifndef _LOCAL_H
+#define _LOCAL_H
+
+/* Yves Gaige <yvesg@hptnodur.grenoble.hp.com> requested this set this */
+/* to a maximum of 8 if old smb clients break because of long printer names. */
+#define MAXPRINTERLEN 15
+
+/* max number of SMB1 directory handles */
+/* As this now uses the bitmap code this can be
+ quite large. */
+#define MAX_DIRECTORY_HANDLES 2048
+
+/* maximum number of file caches per smbd */
+#define MAX_WRITE_CACHES 10
+
+/*
+ * Fudgefactor required for open tdb's, etc.
+ */
+
+#ifndef MAX_OPEN_FUDGEFACTOR
+#define MAX_OPEN_FUDGEFACTOR 40
+#endif
+
+/*
+ * Minimum number of open files needed for Windows7 to
+ * work correctly. A little conservative but better that
+ * than run out of fd's.
+ */
+
+#ifndef MIN_OPEN_FILES_WINDOWS
+#define MIN_OPEN_FILES_WINDOWS 16384
+#endif
+
+/*
+ * Default number of maximum open files per smbd. This is
+ * also limited by the maximum available file descriptors
+ * per process and can also be set in smb.conf as "max open files"
+ * in the [global] section.
+ */
+
+#ifndef MAX_OPEN_FILES
+#define MAX_OPEN_FILES (MIN_OPEN_FILES_WINDOWS + MAX_OPEN_FUDGEFACTOR)
+#endif
+
+#define WORDMAX 0xFFFF
+
+/* the maximum password length before we declare a likely attack */
+#define MAX_PASS_LEN 200
+
+/* separators for lists */
+#define LIST_SEP " \t,;\n\r"
+
+/* wchar separators for lists */
+#define LIST_SEP_W wchar_list_sep
+
+/* this is where browse lists are kept in the lock dir */
+#define SERVER_LIST "browse.dat"
+
+/* shall filenames with illegal chars in them get mangled in long
+ filename listings? */
+#define MANGLE_LONG_FILENAMES
+
+/* define this if you want to stop spoofing with .. and soft links
+ NOTE: This also slows down the server considerably */
+#define REDUCE_PATHS
+
+/* the size of the directory cache */
+#define DIRCACHESIZE 20
+
+/* what default type of filesystem do we want this to show up as in a
+ NT file manager window? */
+#define FSTYPE_STRING "NTFS"
+
+/* user to test password server with as invalid in security=server mode. */
+#ifndef INVALID_USER_PREFIX
+#define INVALID_USER_PREFIX "sambatest"
+#endif
+
+/* the default pager to use for the client "more" command. Users can
+ override this with the PAGER environment variable */
+#ifndef PAGER
+#define PAGER "more"
+#endif
+
+/* the size of the uid cache used to reduce valid user checks */
+#define VUID_CACHE_SIZE 32
+
+/* the following control timings of various actions. Don't change
+ them unless you know what you are doing. These are all in seconds */
+#define SMBD_RELOAD_CHECK (180)
+#define IDLE_CLOSED_TIMEOUT (60)
+#define SMBD_SELECT_TIMEOUT (60)
+#define NMBD_SELECT_LOOP (10)
+#define BROWSE_INTERVAL (60)
+#define REGISTRATION_INTERVAL (10*60)
+#define NMBD_INETD_TIMEOUT (120)
+#define NMBD_MAX_TTL (24*60*60)
+#define LPQ_LOCK_TIMEOUT (5)
+#define NMBD_INTERFACES_RELOAD (120)
+#define NMBD_UNEXPECTED_TIMEOUT (15)
+#define SMBD_HOUSEKEEPING_INTERVAL SMBD_SELECT_TIMEOUT
+
+/* the following are in milliseconds */
+#define LOCK_RETRY_TIMEOUT (100)
+
+/* do you want to dump core (carefully!) when an internal error is
+ encountered? Samba will be careful to make the core file only
+ accessible to root */
+#define DUMP_CORE 1
+
+/* shall we support browse requests via a FIFO to nmbd? */
+#define ENABLE_FIFO 1
+
+/* how long (in milliseconds) to wait for a socket connect to happen */
+#define LONG_CONNECT_TIMEOUT 30000
+#define SHORT_CONNECT_TIMEOUT 5000
+
+/* the default netbios keepalive timeout */
+#define DEFAULT_KEEPALIVE 300
+
+/* the directory to sit in when idle */
+/* #define IDLE_DIR "/" */
+
+/* Timeout (in seconds) to wait for an oplock break
+ message to return from the client. */
+
+#define OPLOCK_BREAK_TIMEOUT 35
+
+/* Timeout (in seconds) to add to the oplock break timeout
+ to wait for the smbd to smbd message to return. */
+
+#define OPLOCK_BREAK_TIMEOUT_FUDGEFACTOR 2
+
+/* the read preciction code has been disabled until some problems with
+ it are worked out */
+#define USE_READ_PREDICTION 0
+
+/* Minimum length of allowed password when changing UNIX password. */
+#define MINPASSWDLENGTH 5
+
+/* the maximum age in seconds of a password. Should be a lp_ parameter */
+#define MAX_PASSWORD_AGE (21*24*60*60)
+
+/* shall we deny oplocks to clients that get timeouts? */
+#define FASCIST_OPLOCK_BACKOFF 1
+
+/* this enables the "rabbit pellet" fix for SMBwritebraw */
+#define RABBIT_PELLET_FIX 1
+
+/* Max number of open RPC pipes. */
+#define MAX_OPEN_PIPES 2048
+
+/* Tuning for server auth mutex. */
+#define CLI_AUTH_TIMEOUT 5000 /* In milli-seconds. */
+#define NUM_CLI_AUTH_CONNECT_RETRIES 3
+/* Number in seconds to wait for the mutex. This must be less than 30 seconds. */
+#define SERVER_MUTEX_WAIT_TIME ( ((NUM_CLI_AUTH_CONNECT_RETRIES) * ((CLI_AUTH_TIMEOUT)/1000)) + 5)
+/* Number in seconds for winbindd to wait for the mutex. Make this 2 * smbd wait time. */
+#define WINBIND_SERVER_MUTEX_WAIT_TIME (( ((NUM_CLI_AUTH_CONNECT_RETRIES) * ((CLI_AUTH_TIMEOUT)/1000)) + 5)*2)
+
+/* size of listen() backlog in smbd */
+#define SMBD_LISTEN_BACKLOG 50
+
+/* Number of microseconds to wait before a sharing violation. */
+#define SHARING_VIOLATION_USEC_WAIT 950000
+
+/* Number of microseconds to wait before a updating the write time (2 secs). */
+#define WRITE_TIME_UPDATE_USEC_DELAY 2000000
+
+#define MAX_LDAP_REPLICATION_SLEEP_TIME 5000 /* In milliseconds. */
+
+/* tdb hash size for the databases having one entry per open file. */
+#define SMBD_VOLATILE_TDB_HASH_SIZE 10007
+
+/* tdb flags for the databases having one entry per open file. */
+#define SMBD_VOLATILE_TDB_FLAGS \
+ (TDB_DEFAULT|TDB_VOLATILE|TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH)
+
+/* Characters we disallow in sharenames. */
+#define INVALID_SHARENAME_CHARS "%<>*?|/\\+=;:\","
+
+/* Seconds between connection attempts to a remote server. */
+#define FAILED_CONNECTION_CACHE_TIMEOUT (LONG_CONNECT_TIMEOUT * 2 / 1000)
+
+/* Default hash size for the winbindd cache. */
+#define WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE 5000
+
+/* Windows minimum lock resolution timeout in ms */
+#define WINDOWS_MINIMUM_LOCK_TIMEOUT_MS 200
+
+/* Maximum size of RPC data we will accept for one call. */
+#define MAX_RPC_DATA_SIZE (15*1024*1024)
+
+/* A guestimate of how many domains winbindd will be contacting */
+#ifndef WINBIND_MAX_DOMAINS_HINT
+#define WINBIND_MAX_DOMAINS_HINT 10
+#endif
+#endif
diff --git a/source3/include/locking.h b/source3/include/locking.h
new file mode 100644
index 0000000..f9eb502
--- /dev/null
+++ b/source3/include/locking.h
@@ -0,0 +1,77 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup, plus a whole lot more.
+
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LOCKING_H
+#define _LOCKING_H
+
+/* passed to br lock code - the UNLOCK_LOCK should never be stored into the tdb
+ and is used in calculating POSIX unlock ranges only. We differentiate between
+ PENDING read and write locks to allow posix lock downgrades to trigger a lock
+ re-evaluation. */
+
+enum brl_type {READ_LOCK, WRITE_LOCK, UNLOCK_LOCK};
+enum brl_flavour {WINDOWS_LOCK = 0, POSIX_LOCK = 1};
+
+#include "librpc/gen_ndr/server_id.h"
+#include "librpc/gen_ndr/misc.h"
+
+/* This contains elements that differentiate locks. The smbpid is a
+ client supplied pid, and is essentially the locking context for
+ this client */
+
+struct lock_context {
+ uint64_t smblctx;
+ uint32_t tid;
+ struct server_id pid;
+};
+
+struct files_struct;
+
+#include "lib/file_id.h"
+
+struct byte_range_lock;
+typedef uint64_t br_off;
+
+/* Internal structure in brlock.tdb.
+ The data in brlock records is an unsorted linear array of these
+ records. It is unnecessary to store the count as tdb provides the
+ size of the record */
+
+struct lock_struct {
+ struct lock_context context;
+ br_off start;
+ br_off size;
+ uint64_t fnum;
+ enum brl_type lock_type;
+ enum brl_flavour lock_flav;
+};
+
+struct smbd_lock_element {
+ struct GUID req_guid;
+ uint64_t smblctx;
+ enum brl_type brltype;
+ enum brl_flavour lock_flav;
+ uint64_t offset;
+ uint64_t count;
+};
+
+struct share_mode_lock;
+
+#endif /* _LOCKING_H_ */
diff --git a/source3/include/lsa.h b/source3/include/lsa.h
new file mode 100644
index 0000000..c23e942
--- /dev/null
+++ b/source3/include/lsa.h
@@ -0,0 +1,29 @@
+/*
+ * Helper functions related to the LSA server
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef LSA_H
+#define LSA_H
+
+int init_lsa_ref_domain_list(TALLOC_CTX *mem_ctx,
+ struct lsa_RefDomainList *ref,
+ const char *dom_name,
+ struct dom_sid *dom_sid);
+
+#define NT_STATUS_LOOKUP_ERR(status) \
+ (!NT_STATUS_IS_OK(status) && \
+ !NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED) && \
+ !NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))
+#endif
diff --git a/source3/include/mangle.h b/source3/include/mangle.h
new file mode 100644
index 0000000..7c4602b
--- /dev/null
+++ b/source3/include/mangle.h
@@ -0,0 +1,42 @@
+/*
+ Unix SMB/CIFS implementation.
+ Name mangling interface
+ Copyright (C) Andrew Tridgell 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MANGLE_H_
+#define _MANGLE_H_
+/*
+ header for 8.3 name mangling interface
+*/
+
+struct mangle_fns {
+ void (*reset)(void);
+ bool (*is_mangled)(const char *s, const struct share_params *p);
+ bool (*must_mangle)(const char *s, const struct share_params *p);
+ bool (*is_8_3)(const char *fname, bool check_case, bool allow_wildcards,
+ const struct share_params *p);
+ bool (*lookup_name_from_8_3)(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p);
+ bool (*name_to_8_3)(const char *in,
+ char out[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p);
+};
+#endif /* _MANGLE_H_ */
diff --git a/source3/include/messages.h b/source3/include/messages.h
new file mode 100644
index 0000000..23c90f7
--- /dev/null
+++ b/source3/include/messages.h
@@ -0,0 +1,128 @@
+/*
+ Unix SMB/CIFS implementation.
+ messages.c header
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) 2001, 2002 by Martin Pool
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MESSAGES_H_
+#define _MESSAGES_H_
+
+#include "replace.h"
+#include <tevent.h>
+
+/* change the message version with any incompatible changes in the protocol */
+#define MESSAGE_VERSION 2
+
+/*
+ * Special flags passed to message_send. Allocated from the top, lets see when
+ * it collides with the message types in the lower 16 bits :-)
+ */
+
+/*
+ * Under high load, this message can be dropped. Use for notify-style
+ * messages that are not critical for correct operation.
+ */
+#define MSG_FLAG_LOWPRIORITY 0x80000000
+
+#include "librpc/gen_ndr/server_id.h"
+#include "lib/util/data_blob.h"
+#include "system/network.h"
+
+#define MSG_BROADCAST_PID_STR "0:0"
+
+struct messaging_context;
+struct messaging_rec;
+
+struct messaging_context *messaging_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev);
+
+struct server_id messaging_server_id(const struct messaging_context *msg_ctx);
+struct tevent_context *messaging_tevent_context(
+ struct messaging_context *msg_ctx);
+struct server_id_db *messaging_names_db(struct messaging_context *msg_ctx);
+
+/*
+ * re-init after a fork
+ */
+NTSTATUS messaging_reinit(struct messaging_context *msg_ctx);
+
+NTSTATUS messaging_register(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ void (*fn)(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data));
+void messaging_deregister(struct messaging_context *ctx, uint32_t msg_type,
+ void *private_data);
+
+/**
+ * CAVEAT:
+ *
+ * While the messaging_send*() functions are synchronous by API,
+ * they trigger a tevent-based loop upon sending bigger messages.
+ *
+ * Hence callers should not use these in purely synchronous code,
+ * but run a tevent_loop instead.
+ */
+NTSTATUS messaging_send(struct messaging_context *msg_ctx,
+ struct server_id server,
+ uint32_t msg_type, const DATA_BLOB *data);
+
+NTSTATUS messaging_send_buf(struct messaging_context *msg_ctx,
+ struct server_id server, uint32_t msg_type,
+ const uint8_t *buf, size_t len);
+int messaging_send_iov_from(struct messaging_context *msg_ctx,
+ struct server_id src, struct server_id dst,
+ uint32_t msg_type,
+ const struct iovec *iov, int iovlen,
+ const int *fds, size_t num_fds);
+NTSTATUS messaging_send_iov(struct messaging_context *msg_ctx,
+ struct server_id server, uint32_t msg_type,
+ const struct iovec *iov, int iovlen,
+ const int *fds, size_t num_fds);
+void messaging_send_all(struct messaging_context *msg_ctx,
+ int msg_type, const void *buf, size_t len);
+
+struct tevent_req *messaging_filtered_read_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ bool (*filter)(struct messaging_rec *rec, void *private_data),
+ void *private_data);
+int messaging_filtered_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct messaging_rec **presult);
+
+struct tevent_req *messaging_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ uint32_t msg_type);
+int messaging_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct messaging_rec **presult);
+
+int messaging_cleanup(struct messaging_context *msg_ctx, pid_t pid);
+
+bool messaging_parent_dgm_cleanup_init(struct messaging_context *msg);
+
+struct messaging_rec *messaging_rec_create(
+ TALLOC_CTX *mem_ctx, struct server_id src, struct server_id dst,
+ uint32_t msg_type, const struct iovec *iov, int iovlen,
+ const int *fds, size_t num_fds);
+
+#include "librpc/gen_ndr/ndr_messaging.h"
+
+#endif
diff --git a/source3/include/msdfs.h b/source3/include/msdfs.h
new file mode 100644
index 0000000..892343f
--- /dev/null
+++ b/source3/include/msdfs.h
@@ -0,0 +1,59 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ MSDfs services for Samba
+ Copyright (C) Shirish Kalele 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _MSDFS_H
+#define _MSDFS_H
+
+#define REFERRAL_TTL 600
+
+/* Flags used in trans2 Get Referral reply */
+#define DFSREF_REFERRAL_SERVER 0x1
+#define DFSREF_STORAGE_SERVER 0x2
+
+/* Referral sizes */
+#define VERSION2_REFERRAL_SIZE 0x16
+#define VERSION3_REFERRAL_SIZE 0x22
+#define REFERRAL_HEADER_SIZE 0x08
+
+/* Maximum number of referrals for each Dfs volume */
+#define MAX_REFERRAL_COUNT 256
+#define MAX_MSDFS_JUNCTIONS 256
+
+struct client_dfs_referral {
+ uint32_t proximity;
+ uint32_t ttl;
+ char *dfspath;
+};
+
+struct referral {
+ char *alternate_path; /* contains the path referred */
+ uint32_t proximity;
+ uint32_t ttl; /* how long should client cache referral */
+};
+
+struct junction_map {
+ char *service_name;
+ char *volume_name;
+ const char *comment;
+ size_t referral_count;
+ struct referral* referral_list;
+};
+#endif /* _MSDFS_H */
diff --git a/source3/include/nameserv.h b/source3/include/nameserv.h
new file mode 100644
index 0000000..8fbe5a3
--- /dev/null
+++ b/source3/include/nameserv.h
@@ -0,0 +1,629 @@
+#ifndef _NAMESERV_H_
+#define _NAMESERV_H_
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios header - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#define INFO_VERSION "INFO/version"
+#define INFO_COUNT "INFO/num_entries"
+#define INFO_ID_HIGH "INFO/id_high"
+#define INFO_ID_LOW "INFO/id_low"
+#define ENTRY_PREFIX "ENTRY/"
+
+#define PERMANENT_TTL 0
+
+/* NTAS uses 2, NT uses 1, WfWg uses 0 */
+#define MAINTAIN_LIST 2
+#define ELECTION_VERSION 1
+
+#define MAX_DGRAM_SIZE (576) /* tcp/ip datagram limit is 576 bytes */
+#define MIN_DGRAM_SIZE 12
+
+/*********************************************************
+ Types of reply packet.
+**********************************************************/
+
+enum netbios_reply_type_code { NMB_QUERY, NMB_STATUS, NMB_REG, NMB_REG_REFRESH,
+ NMB_REL, NMB_WAIT_ACK, NMB_MULTIHOMED_REG,
+ WINS_REG, WINS_QUERY };
+
+/* From rfc1002, 4.2.1.2 */
+/* Question types. */
+#define QUESTION_TYPE_NB_QUERY 0x20
+#define QUESTION_TYPE_NB_STATUS 0x21
+
+/* Question class */
+#define QUESTION_CLASS_IN 0x1
+
+/* Opcode definitions */
+#define NMB_NAME_QUERY_OPCODE 0x0
+#define NMB_NAME_REG_OPCODE 0x05 /* see rfc1002.txt 4.2.2,3,5,6,7,8 */
+#define NMB_NAME_RELEASE_OPCODE 0x06 /* see rfc1002.txt 4.2.9,10,11 */
+#define NMB_WACK_OPCODE 0x07 /* see rfc1002.txt 4.2.16 */
+/* Ambiguity in rfc1002 about which of these is correct. */
+/* WinNT uses 8 by default but can be made to use 9. */
+#define NMB_NAME_REFRESH_OPCODE_8 0x08 /* see rfc1002.txt 4.2.4 */
+#define NMB_NAME_REFRESH_OPCODE_9 0x09 /* see rfc1002.txt 4.2.4 */
+#define NMB_NAME_MULTIHOMED_REG_OPCODE 0x0F /* Invented by Microsoft. */
+
+/* XXXX what about all the other types?? 0x1, 0x2, 0x3, 0x4, 0x8? */
+
+/* Resource record types. rfc1002 4.2.1.3 */
+#define RR_TYPE_A 0x1
+#define RR_TYPE_NS 0x2
+#define RR_TYPE_NULL 0xA
+#define RR_TYPE_NB 0x20
+#define RR_TYPE_NBSTAT 0x21
+
+/* Resource record class. */
+#define RR_CLASS_IN 0x1
+
+/* NetBIOS flags */
+#define NB_GROUP 0x80
+#define NB_PERM 0x02
+#define NB_ACTIVE 0x04
+#define NB_CONFL 0x08
+#define NB_DEREG 0x10
+#define NB_BFLAG 0x00 /* Broadcast node type. */
+#define NB_PFLAG 0x20 /* Point-to-point node type. */
+#define NB_MFLAG 0x40 /* Mixed bcast & p-p node type. */
+#define NB_HFLAG 0x60 /* Microsoft 'hybrid' node type. */
+#define NB_NODETYPEMASK 0x60
+/* Mask applied to outgoing NetBIOS flags. */
+#define NB_FLGMSK 0xE0
+
+/* The wins flags. Looks like the nbflags ! */
+#define WINS_UNIQUE 0x00 /* Unique record */
+#define WINS_NGROUP 0x01 /* Normal Group eg: 1B */
+#define WINS_SGROUP 0x02 /* Special Group eg: 1C */
+#define WINS_MHOMED 0x03 /* MultiHomed */
+
+#define WINS_ACTIVE 0x00 /* active record */
+#define WINS_RELEASED 0x04 /* released record */
+#define WINS_TOMBSTONED 0x08 /* tombstoned record */
+#define WINS_DELETED 0x0C /* deleted record */
+
+#define WINS_STATE_MASK 0x0C
+
+#define WINS_LOCAL 0x00 /* local record */
+#define WINS_REMOTE 0x10 /* remote record */
+
+#define WINS_BNODE 0x00 /* Broadcast node */
+#define WINS_PNODE 0x20 /* PtP node */
+#define WINS_MNODE 0x40 /* Mixed node */
+#define WINS_HNODE 0x60 /* Hybrid node */
+
+#define WINS_NONSTATIC 0x00 /* dynamic record */
+#define WINS_STATIC 0x80 /* static record */
+
+#define WINS_STATE_ACTIVE(p) (((p)->data.wins_flags & WINS_STATE_MASK) == WINS_ACTIVE)
+
+
+/* NetBIOS flag identifier. */
+#define NAME_GROUP(p) ((p)->data.nb_flags & NB_GROUP)
+#define NAME_BFLAG(p) (((p)->data.nb_flags & NB_NODETYPEMASK) == NB_BFLAG)
+#define NAME_PFLAG(p) (((p)->data.nb_flags & NB_NODETYPEMASK) == NB_PFLAG)
+#define NAME_MFLAG(p) (((p)->data.nb_flags & NB_NODETYPEMASK) == NB_MFLAG)
+#define NAME_HFLAG(p) (((p)->data.nb_flags & NB_NODETYPEMASK) == NB_HFLAG)
+
+/* Samba name state for a name in a namelist. */
+#define NAME_IS_ACTIVE(p) ((p)->data.nb_flags & NB_ACTIVE)
+#define NAME_IN_CONFLICT(p) ((p)->data.nb_flags & NB_CONFL)
+#define NAME_IS_DEREGISTERING(p) ((p)->data.nb_flags & NB_DEREG)
+
+/* Error codes for NetBIOS requests. */
+#define FMT_ERR 0x1 /* Packet format error. */
+#define SRV_ERR 0x2 /* Internal server error. */
+#define NAM_ERR 0x3 /* Name does not exist. */
+#define IMP_ERR 0x4 /* Request not implemented. */
+#define RFS_ERR 0x5 /* Request refused. */
+#define ACT_ERR 0x6 /* Active error - name owned by another host. */
+#define CFT_ERR 0x7 /* Name in conflict error. */
+
+#define REFRESH_TIME (15*60)
+#define NAME_POLL_REFRESH_TIME (5*60)
+#define NAME_POLL_INTERVAL 15
+
+/* Workgroup state identifiers. */
+#define AM_POTENTIAL_MASTER_BROWSER(work) ((work)->mst_state == MST_POTENTIAL)
+#define AM_LOCAL_MASTER_BROWSER(work) ((work)->mst_state == MST_BROWSER)
+#define AM_DOMAIN_MASTER_BROWSER(work) ((work)->dom_state == DOMAIN_MST)
+#define AM_DOMAIN_MEMBER(work) ((work)->log_state == LOGON_SRV)
+
+/* Microsoft browser NetBIOS name. */
+#define MSBROWSE "\001\002__MSBROWSE__\002"
+
+/* Mail slots. */
+#define BROWSE_MAILSLOT "\\MAILSLOT\\BROWSE"
+#define NET_LOGON_MAILSLOT "\\MAILSLOT\\NET\\NETLOGON"
+#define NT_LOGON_MAILSLOT "\\MAILSLOT\\NET\\NTLOGON"
+#define LANMAN_MAILSLOT "\\MAILSLOT\\LANMAN"
+
+/* Samba definitions for find_name_on_subnet(). */
+#define FIND_ANY_NAME 0
+#define FIND_SELF_NAME 1
+
+/*
+ * The different name types that can be in namelists.
+ *
+ * SELF_NAME should only be on the broadcast and unicast subnets.
+ * LMHOSTS_NAME should only be in the remote_broadcast_subnet.
+ * REGISTER_NAME, DNS_NAME, DNSFAIL_NAME should only be in the wins_server_subnet.
+ * WINS_PROXY_NAME should only be on the broadcast subnets.
+ * PERMANENT_NAME can be on all subnets except remote_broadcast_subnet.
+ *
+ */
+
+enum name_source {LMHOSTS_NAME, REGISTER_NAME, SELF_NAME, DNS_NAME,
+ DNSFAIL_NAME, PERMANENT_NAME, WINS_PROXY_NAME};
+enum node_type {B_NODE=0, P_NODE=1, M_NODE=2, NBDD_NODE=3};
+enum packet_type {NMB_PACKET, DGRAM_PACKET};
+
+enum master_state {
+ MST_NONE,
+ MST_POTENTIAL,
+ MST_BACKUP,
+ MST_MSB,
+ MST_BROWSER,
+ MST_UNBECOMING_MASTER
+};
+
+enum domain_state {
+ DOMAIN_NONE,
+ DOMAIN_WAIT,
+ DOMAIN_MST
+};
+
+enum logon_state {
+ LOGON_NONE,
+ LOGON_WAIT,
+ LOGON_SRV
+};
+
+struct subnet_record;
+
+struct nmb_data {
+ uint16_t nb_flags; /* Netbios flags. */
+ int num_ips; /* Number of ip entries. */
+ struct in_addr *ip; /* The ip list for this name. */
+
+ enum name_source source; /* Where the name came from. */
+
+ time_t death_time; /* The time the record must be removed (do not remove if 0). */
+ time_t refresh_time; /* The time the record should be refreshed. */
+
+ uint64_t id; /* unique id */
+ struct in_addr wins_ip; /* the address of the wins server this record comes from */
+
+ int wins_flags; /* similar to the netbios flags but different ! */
+};
+
+/* This structure represents an entry in a local netbios name list. */
+struct name_record {
+ struct name_record *prev, *next;
+ struct subnet_record *subnet;
+ struct nmb_name name; /* The netbios name. */
+ struct nmb_data data; /* The netbios data. */
+};
+
+/* Browser cache for synchronising browse lists. */
+struct browse_cache_record {
+ struct browse_cache_record *prev, *next;
+ unstring lmb_name;
+ unstring work_group;
+ struct in_addr ip;
+ time_t sync_time;
+ time_t death_time; /* The time the record must be removed. */
+};
+
+/* used for server information: client, nameserv and ipc */
+struct server_info_struct {
+ fstring name;
+ uint32_t type;
+ fstring comment;
+ fstring domain; /* used ONLY in ipc.c NOT namework.c */
+ bool server_added; /* used ONLY in ipc.c NOT namework.c */
+};
+
+/* This is used to hold the list of servers in my domain, and is
+ contained within lists of domains. */
+
+struct server_record {
+ struct server_record *next;
+ struct server_record *prev;
+
+ struct subnet_record *subnet;
+
+ struct server_info_struct serv;
+ time_t death_time;
+};
+
+/* A workgroup structure. It contains a list of servers. */
+struct work_record {
+ struct work_record *next;
+ struct work_record *prev;
+
+ struct subnet_record *subnet;
+
+ struct server_record *serverlist;
+
+ /* Stage of development from non-local-master up to local-master browser. */
+ enum master_state mst_state;
+
+ /* Stage of development from non-domain-master to domain-master browser. */
+ enum domain_state dom_state;
+
+ /* Stage of development from non-logon-server to logon server. */
+ enum logon_state log_state;
+
+ /* Work group info. */
+ unstring work_group;
+ int token; /* Used when communicating with backup browsers. */
+ unstring local_master_browser_name; /* Current local master browser. */
+
+ /* Announce info. */
+ time_t lastannounce_time;
+ int announce_interval;
+ bool needannounce;
+
+ /* Timeout time for this workgroup. 0 means permanent. */
+ time_t death_time;
+
+ /* Election info */
+ bool RunningElection;
+ bool needelection;
+ int ElectionCount;
+ uint32_t ElectionCriterion;
+
+ /* Domain master browser info. Used for efficient syncs. */
+ struct nmb_name dmb_name;
+ struct in_addr dmb_addr;
+};
+
+/* typedefs needed to define copy & free functions for userdata. */
+struct userdata_struct;
+
+typedef struct userdata_struct * (*userdata_copy_fn)(struct userdata_struct *);
+typedef void (*userdata_free_fn)(struct userdata_struct *);
+
+/* Structure to define any userdata passed around. */
+
+struct userdata_struct {
+ userdata_copy_fn copy_fn;
+ userdata_free_fn free_fn;
+ unsigned int userdata_len;
+ char data[16]; /* 16 is to ensure alignment/padding on all systems */
+};
+
+struct response_record;
+struct packet_struct;
+struct res_rec;
+
+/* typedef to define the function called when this response packet comes in. */
+typedef void (*response_function)(struct subnet_record *, struct response_record *,
+ struct packet_struct *);
+
+/* typedef to define the function called when this response record times out. */
+typedef void (*timeout_response_function)(struct subnet_record *,
+ struct response_record *);
+
+/* typedef to define the function called when the request that caused this
+ response record to be created is successful. */
+typedef void (*success_function)(struct subnet_record *, struct userdata_struct *, ...);
+
+/* typedef to define the function called when the request that caused this
+ response record to be created is unsuccessful. */
+typedef void (*fail_function)(struct subnet_record *, struct response_record *, ...);
+
+/* List of typedefs for success and fail functions of the different query
+ types. Used to catch any compile time prototype errors. */
+
+typedef void (*register_name_success_function)( struct subnet_record *,
+ struct userdata_struct *,
+ struct nmb_name *,
+ uint16_t,
+ int,
+ struct in_addr);
+typedef void (*register_name_fail_function)( struct subnet_record *,
+ struct response_record *,
+ struct nmb_name *);
+
+typedef void (*release_name_success_function)( struct subnet_record *,
+ struct userdata_struct *,
+ struct nmb_name *,
+ struct in_addr);
+typedef void (*release_name_fail_function)( struct subnet_record *,
+ struct response_record *,
+ struct nmb_name *);
+
+typedef void (*refresh_name_success_function)( struct subnet_record *,
+ struct userdata_struct *,
+ struct nmb_name *,
+ uint16_t,
+ int,
+ struct in_addr);
+typedef void (*refresh_name_fail_function)( struct subnet_record *,
+ struct response_record *,
+ struct nmb_name *);
+
+typedef void (*query_name_success_function)( struct subnet_record *,
+ struct userdata_struct *,
+ struct nmb_name *,
+ struct in_addr,
+ struct res_rec *answers);
+
+typedef void (*query_name_fail_function)( struct subnet_record *,
+ struct response_record *,
+ struct nmb_name *,
+ int);
+
+typedef void (*node_status_success_function)( struct subnet_record *,
+ struct userdata_struct *,
+ struct res_rec *,
+ struct in_addr);
+typedef void (*node_status_fail_function)( struct subnet_record *,
+ struct response_record *);
+
+/* Initiated name queries are recorded in this list to track any responses. */
+
+struct response_record {
+ struct response_record *next;
+ struct response_record *prev;
+
+ uint16_t response_id;
+
+ /* Callbacks for packets received or not. */
+ response_function resp_fn;
+ timeout_response_function timeout_fn;
+
+ /* Callbacks for the request succeeding or not. */
+ success_function success_fn;
+ fail_function fail_fn;
+
+ struct packet_struct *packet;
+
+ struct userdata_struct *userdata;
+
+ int num_msgs;
+
+ time_t repeat_time;
+ time_t repeat_interval;
+ int repeat_count;
+
+ /* Recursion protection. */
+ bool in_expiration_processing;
+};
+
+/* A subnet structure. It contains a list of workgroups and netbios names. */
+
+/*
+ B nodes will have their own, totally separate subnet record, with their
+ own netbios name set. These do NOT interact with other subnet records'
+ netbios names.
+*/
+
+enum subnet_type {
+ NORMAL_SUBNET = 0, /* Subnet listed in interfaces list. */
+ UNICAST_SUBNET = 1, /* Subnet for unicast packets. */
+ REMOTE_BROADCAST_SUBNET = 2, /* Subnet for remote broadcasts. */
+ WINS_SERVER_SUBNET = 3 /* Only created if we are a WINS server. */
+};
+
+struct subnet_record {
+ struct subnet_record *next;
+ struct subnet_record *prev;
+
+ char *subnet_name; /* For Debug identification. */
+ enum subnet_type type; /* To catagorize the subnet. */
+
+ struct work_record *workgrouplist; /* List of workgroups. */
+ struct name_record *namelist; /* List of netbios names. */
+ struct response_record *responselist; /* List of responses expected. */
+
+ bool namelist_changed;
+ bool work_changed;
+
+ struct in_addr bcast_ip;
+ struct in_addr mask_ip;
+ struct in_addr myip;
+ int nmb_sock; /* socket to listen for unicast 137. */
+ int nmb_bcast; /* socket to listen for broadcast 137. */
+ int dgram_sock; /* socket to listen for unicast 138. */
+ int dgram_bcast; /* socket to listen for broadcast 138. */
+};
+
+/* A resource record. */
+struct res_rec {
+ struct nmb_name rr_name;
+ int rr_type;
+ int rr_class;
+ int ttl;
+ int rdlength;
+ char rdata[MAX_DGRAM_SIZE];
+};
+
+/* Define these so we can pass info back to caller of name_query */
+#define NM_FLAGS_RS 0x80 /* Response. Cheat */
+#define NM_FLAGS_AA 0x40 /* Authoritative */
+#define NM_FLAGS_TC 0x20 /* Truncated */
+#define NM_FLAGS_RD 0x10 /* Recursion Desired */
+#define NM_FLAGS_RA 0x08 /* Recursion Available */
+#define NM_FLAGS_B 0x01 /* Broadcast */
+
+/* An nmb packet. */
+struct nmb_packet {
+ struct {
+ int name_trn_id;
+ int opcode;
+ bool response;
+ struct {
+ bool bcast;
+ bool recursion_available;
+ bool recursion_desired;
+ bool trunc;
+ bool authoritative;
+ } nm_flags;
+ int rcode;
+ int qdcount;
+ int ancount;
+ int nscount;
+ int arcount;
+ } header;
+
+ struct {
+ struct nmb_name question_name;
+ int question_type;
+ int question_class;
+ } question;
+
+ struct res_rec *answers;
+ struct res_rec *nsrecs;
+ struct res_rec *additional;
+};
+
+/* msg_type field options - from rfc1002. */
+
+#define DGRAM_UNIQUE 0x10
+#define DGRAM_GROUP 0x11
+#define DGRAM_BROADCAST 0x12
+/* defined in IDL
+#define DGRAM_ERROR 0x13
+*/
+#define DGRAM_QUERY_REQUEST 0x14
+#define DGRAM_POSITIVE_QUERY_RESPONSE 0x15
+#define DGRAM_NEGATIVE_QUERT_RESPONSE 0x16
+
+/* A datagram - this normally contains SMB data in the data[] array. */
+
+struct dgram_packet {
+ struct {
+ int msg_type;
+ struct {
+ enum node_type node_type;
+ bool first;
+ bool more;
+ } flags;
+ int dgm_id;
+ struct in_addr source_ip;
+ int source_port;
+ int dgm_length;
+ int packet_offset;
+ } header;
+ struct nmb_name source_name;
+ struct nmb_name dest_name;
+ int datasize;
+ char data[MAX_DGRAM_SIZE];
+};
+
+/* Define a structure used to queue packets. This will be a linked
+ list of nmb packets. */
+
+struct packet_struct
+{
+ struct packet_struct *next;
+ struct packet_struct *prev;
+ bool locked;
+ struct in_addr ip;
+ int port;
+ int recv_fd;
+ int send_fd;
+ time_t timestamp;
+ enum packet_type packet_type;
+ union {
+ struct nmb_packet nmb;
+ struct dgram_packet dgram;
+ } packet;
+};
+
+/* Ids for netbios packet types. */
+
+#define ANN_HostAnnouncement 1
+#define ANN_AnnouncementRequest 2
+#define ANN_Election 8
+#define ANN_GetBackupListReq 9
+#define ANN_GetBackupListResp 10
+#define ANN_BecomeBackup 11
+#define ANN_DomainAnnouncement 12
+#define ANN_MasterAnnouncement 13
+#define ANN_ResetBrowserState 14
+#define ANN_LocalMasterAnnouncement 15
+
+
+/* Broadcast packet announcement intervals, in minutes. */
+
+/* Attempt to add domain logon and domain master names. */
+#define CHECK_TIME_ADD_DOM_NAMES 5
+
+/* Search for master browsers of workgroups samba knows about,
+ except default. */
+#define CHECK_TIME_MST_BROWSE 5
+
+/* Request backup browser announcements from other servers. */
+#define CHECK_TIME_ANNOUNCE_BACKUP 15
+
+/* Request host announcements from other servers: min and max of interval. */
+#define CHECK_TIME_MIN_HOST_ANNCE 3
+#define CHECK_TIME_MAX_HOST_ANNCE 12
+
+/* Announce as master to WINS server and any Primary Domain Controllers. */
+#define CHECK_TIME_MST_ANNOUNCE 15
+
+/* Time between syncs from domain master browser to local master browsers. */
+#define CHECK_TIME_DMB_TO_LMB_SYNC 15
+
+/* Do all remote announcements this often. */
+#define REMOTE_ANNOUNCE_INTERVAL 180
+
+/* what is the maximum period between name refreshes. Note that this only
+ affects non-permanent self names (in seconds) */
+#define MAX_REFRESH_TIME (60*20)
+
+/* The Extinction interval: 4 days, time a node will stay in released state */
+#define EXTINCTION_INTERVAL (4*24*60*60)
+
+/* The Extinction time-out: 1 day, time a node will stay in deleted state */
+#define EXTINCTION_TIMEOUT (24*60*60)
+
+/* Macro's to enumerate subnets either with or without
+ the UNICAST subnet. */
+
+extern struct subnet_record *subnetlist;
+extern struct subnet_record *unicast_subnet;
+extern struct subnet_record *wins_server_subnet;
+extern struct subnet_record *remote_broadcast_subnet;
+
+#define FIRST_SUBNET subnetlist
+#define NEXT_SUBNET_EXCLUDING_UNICAST(x) ((x)->next)
+#define NEXT_SUBNET_INCLUDING_UNICAST(x) (get_next_subnet_maybe_unicast((x)))
+
+/* wins replication record used between nmbd and wrepld */
+typedef struct _WINS_RECORD {
+ char name[17];
+ char type;
+ int nb_flags;
+ int wins_flags;
+ uint64_t id;
+ int num_ips;
+ struct in_addr ip[25];
+ struct in_addr wins_ip;
+} WINS_RECORD;
+
+/* To be removed. */
+enum state_type { TEST };
+#endif /* _NAMESERV_H_ */
diff --git a/source3/include/nss_info.h b/source3/include/nss_info.h
new file mode 100644
index 0000000..94df56e
--- /dev/null
+++ b/source3/include/nss_info.h
@@ -0,0 +1,96 @@
+/*
+ Unix SMB/CIFS implementation.
+ Idmap NSS headers
+
+ Copyright (C) Gerald Carter 2006
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _IDMAP_NSS_H
+#define _IDMAP_NSS_H
+
+#ifndef HAVE_LDAP
+# ifndef LDAPMessage
+# define LDAPMessage void
+# endif
+#endif
+
+/* The interface version specifier */
+
+#define SMB_NSS_INFO_INTERFACE_VERSION 1
+
+/* List of available backends. All backends must
+ register themselves */
+
+struct nss_function_entry {
+ struct nss_function_entry *prev, *next;
+
+ const char *name;
+ const struct nss_info_methods *methods;
+};
+
+/* List of configured domains. Each domain points
+ back to its configured backend. */
+
+struct nss_domain_entry {
+ struct nss_domain_entry *prev, *next;
+
+ const char *domain;
+
+ NTSTATUS init_status;
+ const struct nss_function_entry *backend;
+
+ /* hold state on a per domain basis */
+
+ void *state;
+};
+
+/* API */
+
+struct nss_info_methods {
+ NTSTATUS (*init)( struct nss_domain_entry *e );
+ NTSTATUS (*map_to_alias)(TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *name, char **alias);
+ NTSTATUS (*map_from_alias)(TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *alias, char **name);
+ NTSTATUS (*close_fn)( void );
+};
+
+
+/* The following definitions come from nsswitch/nss_info.c */
+
+NTSTATUS smb_register_idmap_nss(int version,
+ const char *name,
+ const struct nss_info_methods *methods);
+
+NTSTATUS nss_map_to_alias( TALLOC_CTX *mem_ctx, const char *domain,
+ const char *name, char **alias );
+
+NTSTATUS nss_map_from_alias( TALLOC_CTX *mem_ctx, const char *domain,
+ const char *alias, char **name );
+
+NTSTATUS nss_close( const char *parameters );
+
+/* The following definitions come from winbindd/nss_info.c */
+
+
+/* The following definitions come from winbindd/nss_info_template.c */
+
+NTSTATUS nss_info_template_init(TALLOC_CTX *mem_ctx);
+
+#endif /* _IDMAP_NSS_H_ */
+
diff --git a/source3/include/nt_printing.h b/source3/include/nt_printing.h
new file mode 100644
index 0000000..b89c3bd
--- /dev/null
+++ b/source3/include/nt_printing.h
@@ -0,0 +1,211 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-2000,
+ Copyright (C) Jean Francois Micouleau 1998-2000.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef NT_PRINTING_H_
+#define NT_PRINTING_H_
+
+#include "../librpc/gen_ndr/spoolss.h"
+
+#ifndef SAMBA_PRINTER_PORT_NAME
+#define SAMBA_PRINTER_PORT_NAME "Samba Printer Port"
+#endif
+
+/* DOS header format */
+#define DOS_HEADER_SIZE 64
+#define DOS_HEADER_MAGIC_OFFSET 0
+#define DOS_HEADER_MAGIC 0x5A4D
+#define DOS_HEADER_LFANEW_OFFSET 60
+
+/* New Executable format (Win or OS/2 1.x segmented) */
+#define NE_HEADER_SIZE 64
+#define NE_HEADER_SIGNATURE_OFFSET 0
+#define NE_HEADER_SIGNATURE 0x454E
+#define NE_HEADER_TARGET_OS_OFFSET 54
+#define NE_HEADER_TARGOS_WIN 0x02
+#define NE_HEADER_MINOR_VER_OFFSET 62
+#define NE_HEADER_MAJOR_VER_OFFSET 63
+
+/* Portable Executable format */
+#define PE_HEADER_SIZE 24
+#define PE_HEADER_SIGNATURE_OFFSET 0
+#define PE_HEADER_SIGNATURE 0x00004550
+#define PE_HEADER_MACHINE_OFFSET 4
+#define PE_HEADER_MACHINE_I386 0x14c
+#define PE_HEADER_NUMBER_OF_SECTIONS 6
+#define PE_HEADER_OPTIONAL_HEADER_SIZE 20
+#define PE_HEADER_SECT_HEADER_SIZE 40
+#define PE_HEADER_SECT_NAME_OFFSET 0
+#define PE_HEADER_SECT_SIZE_DATA_OFFSET 16
+#define PE_HEADER_SECT_PTR_DATA_OFFSET 20
+
+/* Microsoft file version format */
+#define VS_SIGNATURE "VS_VERSION_INFO"
+#define VS_MAGIC_VALUE 0xfeef04bd
+#define VS_MAJOR_OFFSET 8
+#define VS_MINOR_OFFSET 12
+#define VS_VERSION_INFO_UNICODE_SIZE (sizeof(VS_SIGNATURE)*2+4+VS_MINOR_OFFSET+4) /* not true size! */
+#define VS_VERSION_INFO_SIZE (sizeof(VS_SIGNATURE)+4+VS_MINOR_OFFSET+4) /* not true size! */
+#define VS_NE_BUF_SIZE 4096 /* Must be > 2*VS_VERSION_INFO_SIZE */
+
+/* Notify spoolss clients that something has changed. The
+ notification data is either stored in two uint32_t values or a
+ variable length array. */
+
+#define SPOOLSS_NOTIFY_MSG_UNIX_JOBID 0x0001 /* Job id is unix */
+
+typedef struct spoolss_notify_msg {
+ fstring printer; /* Name of printer notified */
+ uint32_t type; /* Printer or job notify */
+ uint32_t field; /* Notify field changed */
+ uint32_t id; /* Job id */
+ uint32_t len; /* Length of data, 0 for two uint32_t value */
+ uint32_t flags;
+ union {
+ uint32_t value[2];
+ char *data;
+ } notify;
+} SPOOLSS_NOTIFY_MSG;
+
+typedef struct {
+ fstring printername;
+ uint32_t num_msgs;
+ SPOOLSS_NOTIFY_MSG *msgs;
+} SPOOLSS_NOTIFY_MSG_GROUP;
+
+typedef struct {
+ TALLOC_CTX *ctx;
+ uint32_t num_groups;
+ SPOOLSS_NOTIFY_MSG_GROUP *msg_groups;
+} SPOOLSS_NOTIFY_MSG_CTR;
+
+/*
+ * The printer attributes.
+ * I #defined all of them (grabbed form MSDN)
+ * I'm only using:
+ * ( SHARED | NETWORK | RAW_ONLY )
+ * RAW_ONLY _MUST_ be present otherwise NT will send an EMF file
+ */
+
+#define PRINTER_ATTRIBUTE_SAMBA (PRINTER_ATTRIBUTE_RAW_ONLY|\
+ PRINTER_ATTRIBUTE_SHARED|\
+ PRINTER_ATTRIBUTE_LOCAL)
+#define PRINTER_ATTRIBUTE_NOT_SAMBA (PRINTER_ATTRIBUTE_NETWORK)
+
+#define DRIVER_ANY_VERSION 0xffffffff
+#define DRIVER_MAX_VERSION 4
+
+struct print_architecture_table_node {
+ const char *long_archi;
+ const char *short_archi;
+ int version;
+};
+
+bool nt_printing_init(struct messaging_context *msg_ctx);
+
+const char *get_short_archi(const char *long_archi);
+
+WERROR print_access_check(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum,
+ int access_type);
+
+WERROR nt_printer_guid_retrieve(TALLOC_CTX *mem_ctx, const char *printer,
+ struct GUID *pguid);
+
+WERROR nt_printer_guid_store(struct messaging_context *msg_ctx,
+ const char *printer, struct GUID guid);
+
+WERROR nt_printer_guid_get(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer, struct GUID *guid);
+
+WERROR nt_printer_publish(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ int action);
+
+bool is_printer_published(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **info2);
+
+WERROR check_published_printers(struct messaging_context *msg_ctx);
+
+struct dcerpc_binding_handle;
+
+bool printer_driver_in_use(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const struct spoolss_DriverInfo8 *r);
+bool printer_driver_files_in_use(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct spoolss_DriverInfo8 *r);
+bool delete_driver_files(const struct auth_session_info *server_info,
+ const struct spoolss_DriverInfo8 *r);
+
+WERROR move_driver_to_download_area(const struct auth_session_info *session_info,
+ const struct spoolss_AddDriverInfoCtr *r,
+ const char *driver_directory);
+
+WERROR clean_up_driver_struct(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ const struct spoolss_AddDriverInfoCtr *r,
+ uint32_t flags,
+ const char **driver_directory);
+
+void map_printer_permissions(struct security_descriptor *sd);
+
+void map_job_permissions(struct security_descriptor *sd);
+
+bool print_time_access_check(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *servicename);
+
+void nt_printer_remove(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *printer);
+void nt_printer_add(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *printer);
+
+/* The version int is used by getdrivers. Note that
+ all architecture strings that support multiple
+ versions must be grouped together since enumdrivers
+ uses this property to prevent issuing multiple
+ enumdriver calls for the same arch */
+static const struct print_architecture_table_node archi_table[]= {
+ {SPOOLSS_ARCHITECTURE_4_0, SPL_ARCH_WIN40, 0 },
+ {SPOOLSS_ARCHITECTURE_NT_X86, SPL_ARCH_W32X86, 2 },
+ {SPOOLSS_ARCHITECTURE_NT_X86, SPL_ARCH_W32X86, 3 },
+ {SPOOLSS_ARCHITECTURE_W32MIPS, SPL_ARCH_W32MIPS, 2 },
+ {SPOOLSS_ARCHITECTURE_W32ALPHA, SPL_ARCH_W32ALPHA, 2 },
+ {SPOOLSS_ARCHITECTURE_W32PPC, SPL_ARCH_W32PPC, 2 },
+ {SPOOLSS_ARCHITECTURE_IA_64, SPL_ARCH_IA64, 3 },
+ {SPOOLSS_ARCHITECTURE_x64, SPL_ARCH_X64, 3 },
+ {SPOOLSS_ARCHITECTURE_ARM64, SPL_ARCH_ARM64, 3 },
+ {NULL, "", -1 }
+};
+
+#endif /* NT_PRINTING_H_ */
diff --git a/source3/include/ntdomain.h b/source3/include/ntdomain.h
new file mode 100644
index 0000000..9076c11
--- /dev/null
+++ b/source3/include/ntdomain.h
@@ -0,0 +1,35 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1997
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1997
+ Copyright (C) Paul Ashton 1997
+ Copyright (C) Jeremy Allison 2000-2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _NT_DOMAIN_H /* _NT_DOMAIN_H */
+#define _NT_DOMAIN_H
+
+/*
+ * A bunch of stuff that was put into smb.h
+ * in the NTDOM branch - it didn't belong there.
+ */
+
+struct gse_context;
+
+#include "rpc_server/rpc_pipes.h"
+
+#endif /* _NT_DOMAIN_H */
diff --git a/source3/include/ntioctl.h b/source3/include/ntioctl.h
new file mode 100644
index 0000000..18959b0
--- /dev/null
+++ b/source3/include/ntioctl.h
@@ -0,0 +1,34 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT ioctl code constants
+ Copyright (C) Andrew Tridgell 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _NTIOCTL_H
+#define _NTIOCTL_H
+
+/* For FSCTL_GET_SHADOW_COPY_DATA ...*/
+typedef char SHADOW_COPY_LABEL[25]; /* sizeof("@GMT-2004.02.18-15.44.00") + 1 */
+
+struct shadow_copy_data {
+ /* Total number of shadow volumes currently mounted */
+ uint32_t num_volumes;
+ /* Concatenated list of labels */
+ SHADOW_COPY_LABEL *labels;
+};
+
+
+#endif /* _NTIOCTL_H */
diff --git a/source3/include/ntquotas.h b/source3/include/ntquotas.h
new file mode 100644
index 0000000..6fbbbb9
--- /dev/null
+++ b/source3/include/ntquotas.h
@@ -0,0 +1,91 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT QUOTA code constants
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _NTQUOTAS_H
+#define _NTQUOTAS_H
+
+/*
+ * details for Quota Flags:
+ *
+ * 0x20 Log Limit: log if the user exceeds his Hard Quota
+ * 0x10 Log Warn: log if the user exceeds his Soft Quota
+ * 0x02 Deny Disk: deny disk access when the user exceeds his Hard Quota
+ * 0x01 Enable Quotas: enable quota for this fs
+ *
+ */
+
+#define QUOTAS_ENABLED 0x0001
+#define QUOTAS_DENY_DISK 0x0002
+#define QUOTAS_LOG_VIOLATIONS 0x0004
+#define CONTENT_INDEX_DISABLED 0x0008
+#define QUOTAS_LOG_THRESHOLD 0x0010
+#define QUOTAS_LOG_LIMIT 0x0020
+#define LOG_VOLUME_THRESHOLD 0x0040
+#define LOG_VOLUME_LIMIT 0x0080
+#define QUOTAS_INCOMPLETE 0x0100
+#define QUOTAS_REBUILDING 0x0200
+#define QUOTAS_0400 0x0400
+#define QUOTAS_0800 0x0800
+#define QUOTAS_1000 0x1000
+#define QUOTAS_2000 0x2000
+#define QUOTAS_4000 0x4000
+#define QUOTAS_8000 0x8000
+
+#define SMB_NTQUOTAS_NO_LIMIT ((uint64_t)(-1))
+#define SMB_NTQUOTAS_NO_ENTRY ((uint64_t)(-2))
+#define SMB_NTQUOTAS_NO_SPACE ((uint64_t)(0))
+#define SMB_NTQUOTAS_1_B (uint64_t)0x0000000000000001
+#define SMB_NTQUOTAS_1KB (uint64_t)0x0000000000000400
+#define SMB_NTQUOTAS_1MB (uint64_t)0x0000000000100000
+#define SMB_NTQUOTAS_1GB (uint64_t)0x0000000040000000
+#define SMB_NTQUOTAS_1TB (uint64_t)0x0000010000000000
+#define SMB_NTQUOTAS_1PB (uint64_t)0x0004000000000000
+#define SMB_NTQUOTAS_1EB (uint64_t)0x1000000000000000
+
+enum SMB_QUOTA_TYPE {
+ SMB_INVALID_QUOTA_TYPE = -1,
+ SMB_USER_FS_QUOTA_TYPE = 1,
+ SMB_USER_QUOTA_TYPE = 2,
+ SMB_GROUP_FS_QUOTA_TYPE = 3,/* not used yet */
+ SMB_GROUP_QUOTA_TYPE = 4 /* used by disk_free queries */
+};
+
+typedef struct _SMB_NTQUOTA_STRUCT {
+ enum SMB_QUOTA_TYPE qtype;
+ uint64_t usedspace;
+ uint64_t softlim;
+ uint64_t hardlim;
+ uint32_t qflags;
+ struct dom_sid sid;
+} SMB_NTQUOTA_STRUCT;
+
+typedef struct _SMB_NTQUOTA_LIST {
+ struct _SMB_NTQUOTA_LIST *prev,*next;
+ TALLOC_CTX *mem_ctx;
+ uid_t uid;
+ SMB_NTQUOTA_STRUCT *quotas;
+} SMB_NTQUOTA_LIST;
+
+typedef struct _SMB_NTQUOTA_HANDLE {
+ bool valid;
+ SMB_NTQUOTA_LIST *quota_list;
+ SMB_NTQUOTA_LIST *tmp_list;
+} SMB_NTQUOTA_HANDLE;
+
+#endif /*_NTQUOTAS_H */
diff --git a/source3/include/passdb.h b/source3/include/passdb.h
new file mode 100644
index 0000000..5ee60d3
--- /dev/null
+++ b/source3/include/passdb.h
@@ -0,0 +1,990 @@
+/*
+ Unix SMB/CIFS implementation.
+ passdb structures and parameters
+ Copyright (C) Gerald Carter 2001
+ Copyright (C) Luke Kenneth Casson Leighton 1998 - 2000
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Simo Sorce 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PASSDB_H
+#define _PASSDB_H
+
+#ifndef NT_HASH_LEN
+#define NT_HASH_LEN 16
+#endif
+
+#ifndef LM_HASH_LEN
+#define LM_HASH_LEN 16
+#endif
+
+#include "../librpc/gen_ndr/lsa.h"
+#include <tevent.h>
+struct unixid;
+struct cli_credentials;
+
+/* group mapping headers */
+
+#define ENUM_ONLY_MAPPED True
+#define ENUM_ALL_MAPPED False
+
+typedef struct _GROUP_MAP {
+ struct pdb_methods *methods;
+ gid_t gid;
+ struct dom_sid sid;
+ enum lsa_SidType sid_name_use;
+ char *nt_name;
+ char *comment;
+} GROUP_MAP;
+
+struct acct_info {
+ char *acct_name; /* account name */
+ char *acct_desc; /* account name */
+ uint32_t rid; /* domain-relative RID */
+};
+
+/* The following definitions come from groupdb/mapping.c */
+
+NTSTATUS add_initial_entry(gid_t gid, const char *sid, enum lsa_SidType sid_name_use, const char *nt_name, const char *comment);
+bool get_domain_group_from_sid(struct dom_sid sid, GROUP_MAP *map);
+int smb_create_group(const char *unix_group, gid_t *new_gid);
+int smb_delete_group(const char *unix_group);
+int smb_set_primary_group(const char *unix_group, const char* unix_user);
+int smb_add_user_group(const char *unix_group, const char *unix_user);
+int smb_delete_user_group(const char *unix_group, const char *unix_user);
+NTSTATUS pdb_default_getgrsid(struct pdb_methods *methods, GROUP_MAP *map,
+ struct dom_sid sid);
+NTSTATUS pdb_default_getgrgid(struct pdb_methods *methods, GROUP_MAP *map,
+ gid_t gid);
+NTSTATUS pdb_default_getgrnam(struct pdb_methods *methods, GROUP_MAP *map,
+ const char *name);
+NTSTATUS pdb_default_add_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map);
+NTSTATUS pdb_default_update_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map);
+NTSTATUS pdb_default_delete_group_mapping_entry(struct pdb_methods *methods,
+ struct dom_sid sid);
+NTSTATUS pdb_default_enum_group_mapping(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries,
+ bool unix_only);
+NTSTATUS pdb_default_create_alias(struct pdb_methods *methods,
+ const char *name, uint32_t *rid);
+NTSTATUS pdb_default_delete_alias(struct pdb_methods *methods,
+ const struct dom_sid *sid);
+NTSTATUS pdb_default_get_aliasinfo(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct acct_info *info);
+NTSTATUS pdb_default_set_aliasinfo(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct acct_info *info);
+NTSTATUS pdb_default_add_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias, const struct dom_sid *member);
+NTSTATUS pdb_default_del_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias, const struct dom_sid *member);
+NTSTATUS pdb_default_enum_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias, TALLOC_CTX *mem_ctx,
+ struct dom_sid **pp_members,
+ size_t *p_num_members);
+NTSTATUS pdb_default_alias_memberships(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *members,
+ size_t num_members,
+ uint32_t **pp_alias_rids,
+ size_t *p_num_alias_rids);
+NTSTATUS pdb_nop_getgrsid(struct pdb_methods *methods, GROUP_MAP *map,
+ struct dom_sid sid);
+NTSTATUS pdb_nop_getgrgid(struct pdb_methods *methods, GROUP_MAP *map,
+ gid_t gid);
+NTSTATUS pdb_nop_getgrnam(struct pdb_methods *methods, GROUP_MAP *map,
+ const char *name);
+NTSTATUS pdb_nop_add_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map);
+NTSTATUS pdb_nop_update_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map);
+NTSTATUS pdb_nop_delete_group_mapping_entry(struct pdb_methods *methods,
+ struct dom_sid sid);
+NTSTATUS pdb_nop_enum_group_mapping(struct pdb_methods *methods,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP **rmap, size_t *num_entries,
+ bool unix_only);
+NTSTATUS pdb_create_builtin_alias(uint32_t rid, gid_t gid);
+
+
+/* passdb headers */
+
+/**********************************************************************
+ * Masks for mappings between unix uid and gid types and
+ * NT RIDS.
+ **********************************************************************/
+
+/* Take the bottom bit. */
+#define RID_TYPE_MASK 1
+#define RID_MULTIPLIER 2
+
+/* The two common types. */
+#define USER_RID_TYPE 0
+#define GROUP_RID_TYPE 1
+
+/*
+ * Flags for local user manipulation.
+ */
+
+#define LOCAL_ADD_USER 0x1
+#define LOCAL_DELETE_USER 0x2
+#define LOCAL_DISABLE_USER 0x4
+#define LOCAL_ENABLE_USER 0x8
+#define LOCAL_TRUST_ACCOUNT 0x10
+#define LOCAL_SET_NO_PASSWORD 0x20
+#define LOCAL_SET_PASSWORD 0x40
+#define LOCAL_SET_LDAP_ADMIN_PW 0x80
+#define LOCAL_INTERDOM_ACCOUNT 0x100
+#define LOCAL_AM_ROOT 0x200 /* Act as root */
+
+/*
+ * Size of new password account encoding string. This is enough space to
+ * hold 11 ACB characters, plus the surrounding [] and a terminating null.
+ * Do not change unless you are adding new ACB bits!
+ */
+
+#define NEW_PW_FORMAT_SPACE_PADDED_LEN 14
+
+/* Password history constants. */
+#define PW_HISTORY_SALT_LEN 16
+#define SALTED_MD5_HASH_LEN 16
+#define PW_HISTORY_ENTRY_LEN (PW_HISTORY_SALT_LEN+SALTED_MD5_HASH_LEN)
+#define MAX_PW_HISTORY_LEN 24
+
+/*
+ * bit flags representing initialized fields in struct samu
+ */
+enum pdb_elements {
+ PDB_UNINIT,
+ PDB_SMBHOME,
+ PDB_PROFILE,
+ PDB_DRIVE,
+ PDB_LOGONSCRIPT,
+ PDB_LOGONTIME,
+ PDB_LOGOFFTIME,
+ PDB_KICKOFFTIME,
+ PDB_BAD_PASSWORD_TIME,
+ PDB_CANCHANGETIME,
+ PDB_PLAINTEXT_PW,
+ PDB_USERNAME,
+ PDB_FULLNAME,
+ PDB_DOMAIN,
+ PDB_NTUSERNAME,
+ PDB_HOURSLEN,
+ PDB_LOGONDIVS,
+ PDB_USERSID,
+ PDB_GROUPSID,
+ PDB_ACCTCTRL,
+ PDB_PASSLASTSET,
+ PDB_ACCTDESC,
+ PDB_WORKSTATIONS,
+ PDB_COMMENT,
+ PDB_MUNGEDDIAL,
+ PDB_HOURS,
+ PDB_FIELDS_PRESENT,
+ PDB_BAD_PASSWORD_COUNT,
+ PDB_LOGON_COUNT,
+ PDB_COUNTRY_CODE,
+ PDB_CODE_PAGE,
+ PDB_UNKNOWN6,
+ PDB_LMPASSWD,
+ PDB_NTPASSWD,
+ PDB_PWHISTORY,
+ PDB_BACKEND_PRIVATE_DATA,
+
+ /* this must be the last element */
+ PDB_COUNT
+};
+
+enum pdb_group_elements {
+ PDB_GROUP_NAME,
+ PDB_GROUP_SID,
+ PDB_GROUP_SID_NAME_USE,
+ PDB_GROUP_MEMBERS,
+
+ /* this must be the last element */
+ PDB_GROUP_COUNT
+};
+
+
+enum pdb_value_state {
+ PDB_DEFAULT=0,
+ PDB_SET,
+ PDB_CHANGED
+};
+
+#define IS_SAM_SET(x, flag) (pdb_get_init_flags(x, flag) == PDB_SET)
+#define IS_SAM_CHANGED(x, flag) (pdb_get_init_flags(x, flag) == PDB_CHANGED)
+#define IS_SAM_DEFAULT(x, flag) (pdb_get_init_flags(x, flag) == PDB_DEFAULT)
+
+/* cache for bad password lockout data, to be used on replicated SAMs */
+struct login_cache {
+ time_t entry_timestamp;
+ uint32_t acct_ctrl;
+ uint16_t bad_password_count;
+ time_t bad_password_time;
+};
+
+#define SAMU_BUFFER_V0 0
+#define SAMU_BUFFER_V1 1
+#define SAMU_BUFFER_V2 2
+#define SAMU_BUFFER_V3 3
+/* nothing changed from V3 to V4 */
+#define SAMU_BUFFER_V4 4
+#define SAMU_BUFFER_LATEST SAMU_BUFFER_V4
+
+#define MAX_HOURS_LEN 32
+
+struct samu {
+ struct pdb_methods *methods;
+
+ /* initialization flags */
+ struct bitmap *change_flags;
+ struct bitmap *set_flags;
+
+ time_t logon_time; /* logon time */
+ time_t logoff_time; /* logoff time */
+ time_t kickoff_time; /* kickoff time */
+ time_t bad_password_time; /* last bad password entered */
+ time_t pass_last_set_time; /* password last set time */
+ time_t pass_can_change_time; /* password can change time */
+
+ const char *username; /* UNIX username string */
+ const char *domain; /* Windows Domain name */
+ const char *nt_username; /* Windows username string */
+ const char *full_name; /* user's full name string */
+ const char *home_dir; /* home directory string */
+ const char *dir_drive; /* home directory drive string */
+ const char *logon_script; /* logon script string */
+ const char *profile_path; /* profile path string */
+ const char *acct_desc; /* user description string */
+ const char *workstations; /* login from workstations string */
+ const char *comment;
+ const char *munged_dial; /* munged path name and dial-back tel number */
+
+ struct dom_sid user_sid;
+ struct dom_sid *group_sid;
+
+ DATA_BLOB lm_pw; /* .data is Null if no password */
+ DATA_BLOB nt_pw; /* .data is Null if no password */
+ DATA_BLOB nt_pw_his; /* nt hashed password history .data is Null if not available */
+ char* plaintext_pw; /* is Null if not available */
+
+ uint32_t acct_ctrl; /* account info (ACB_xxxx bit-mask) */
+ uint32_t fields_present; /* 0x00ff ffff */
+
+ uint16_t logon_divs; /* 168 - number of hours in a week */
+ uint32_t hours_len; /* normally 21 bytes */
+ uint8_t hours[MAX_HOURS_LEN];
+
+ /* Was unknown_5. */
+ uint16_t bad_password_count;
+ uint16_t logon_count;
+
+ uint16_t country_code;
+ uint16_t code_page;
+
+ uint32_t unknown_6; /* 0x0000 04ec */
+
+ /* a tag for who added the private methods */
+
+ const struct pdb_methods *backend_private_methods;
+ void *backend_private_data;
+ void (*backend_private_data_free_fn)(void **);
+
+ /* maintain a copy of the user's struct passwd */
+
+ struct passwd *unix_pw;
+};
+
+struct samr_displayentry {
+ uint32_t idx;
+ uint32_t rid;
+ uint32_t acct_flags;
+ const char *account_name;
+ const char *fullname;
+ const char *description;
+};
+
+enum pdb_search_type {
+ PDB_USER_SEARCH,
+ PDB_GROUP_SEARCH,
+ PDB_ALIAS_SEARCH
+};
+
+struct pdb_search {
+ enum pdb_search_type type;
+ struct samr_displayentry *cache;
+ uint32_t num_entries;
+ ssize_t cache_size;
+ bool search_ended;
+ void *private_data;
+ bool (*next_entry)(struct pdb_search *search,
+ struct samr_displayentry *entry);
+ void (*search_end)(struct pdb_search *search);
+};
+
+struct pdb_domain_info {
+ char *name;
+ char *dns_domain;
+ char *dns_forest;
+ struct dom_sid sid;
+ struct GUID guid;
+};
+
+struct pdb_trusted_domain {
+ char *domain_name;
+ char *netbios_name;
+ struct dom_sid security_identifier;
+ DATA_BLOB trust_auth_incoming;
+ DATA_BLOB trust_auth_outgoing;
+ uint32_t trust_direction;
+ uint32_t trust_type;
+ uint32_t trust_attributes;
+ uint32_t *trust_posix_offset;
+ uint32_t *supported_enc_type;
+ DATA_BLOB trust_forest_trust_info;
+};
+
+/*
+ * trusted domain entry/entries returned by secrets_get_trusted_domains
+ * (used in _lsa_enum_trust_dom call)
+ */
+struct trustdom_info {
+ char *name;
+ struct dom_sid sid;
+};
+
+/*
+ * Types of account policy.
+ */
+enum pdb_policy_type {
+ PDB_POLICY_MIN_PASSWORD_LEN = 1,
+ PDB_POLICY_PASSWORD_HISTORY = 2,
+ PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS = 3,
+ PDB_POLICY_MAX_PASSWORD_AGE = 4,
+ PDB_POLICY_MIN_PASSWORD_AGE = 5,
+ PDB_POLICY_LOCK_ACCOUNT_DURATION = 6,
+ PDB_POLICY_RESET_COUNT_TIME = 7,
+ PDB_POLICY_BAD_ATTEMPT_LOCKOUT = 8,
+ PDB_POLICY_TIME_TO_LOGOUT = 9,
+ PDB_POLICY_REFUSE_MACHINE_PW_CHANGE = 10
+};
+
+#define PDB_CAP_STORE_RIDS 0x0001
+#define PDB_CAP_ADS 0x0002
+#define PDB_CAP_TRUSTED_DOMAINS_EX 0x0004
+
+/*****************************************************************
+ Functions to be implemented by the new (v2) passdb API
+****************************************************************/
+
+/*
+ * This next constant specifies the version number of the PASSDB interface
+ * this SAMBA will load. Increment this if *ANY* changes are made to the interface.
+ * Changed interface to fix int -> size_t problems. JRA.
+ * There's no point in allocating arrays in
+ * samr_lookup_rids twice. It was done in the srv_samr_nt.c code as well as in
+ * the pdb module. Remove the latter, this might happen more often. VL.
+ * changed to version 14 to move lookup_rids and lookup_names to return
+ * enum lsa_SidType rather than uint32_t.
+ * Changed to 16 for access to the trusted domain passwords (obnox).
+ * Changed to 17, the sampwent interface is gone.
+ * Changed to 18, pdb_rid_algorithm -> pdb_capabilities
+ * Changed to 19, removed uid_to_rid
+ * Changed to 20, pdb_secret calls
+ * Changed to 21, set/enum_upn_suffixes. AB.
+ * Changed to 22, idmap control functions
+ * Changed to 23, new idmap control functions
+ * Changed to 24, removed uid_to_sid and gid_to_sid, replaced with id_to_sid
+ * Leave at 24, add optional get_trusteddom_creds()
+ * Change to 25, loadable modules now have a TALLOC_CTX * parameter in init.
+ */
+
+#define PASSDB_INTERFACE_VERSION 25
+
+struct pdb_methods
+{
+ const char *name; /* What name got this module */
+
+ struct pdb_domain_info *(*get_domain_info)(struct pdb_methods *,
+ TALLOC_CTX *mem_ctx);
+
+ NTSTATUS (*getsampwnam)(struct pdb_methods *, struct samu *sam_acct, const char *username);
+
+ NTSTATUS (*getsampwsid)(struct pdb_methods *, struct samu *sam_acct, const struct dom_sid *sid);
+
+ NTSTATUS (*create_user)(struct pdb_methods *, TALLOC_CTX *tmp_ctx,
+ const char *name, uint32_t acct_flags,
+ uint32_t *rid);
+
+ NTSTATUS (*delete_user)(struct pdb_methods *, TALLOC_CTX *tmp_ctx,
+ struct samu *sam_acct);
+
+ NTSTATUS (*add_sam_account)(struct pdb_methods *, struct samu *sampass);
+
+ NTSTATUS (*update_sam_account)(struct pdb_methods *, struct samu *sampass);
+
+ NTSTATUS (*delete_sam_account)(struct pdb_methods *, struct samu *username);
+
+ NTSTATUS (*rename_sam_account)(struct pdb_methods *, struct samu *oldname, const char *newname);
+
+ NTSTATUS (*update_login_attempts)(struct pdb_methods *methods, struct samu *sam_acct, bool success);
+
+ NTSTATUS (*getgrsid)(struct pdb_methods *methods, GROUP_MAP *map, struct dom_sid sid);
+
+ NTSTATUS (*getgrgid)(struct pdb_methods *methods, GROUP_MAP *map, gid_t gid);
+
+ NTSTATUS (*getgrnam)(struct pdb_methods *methods, GROUP_MAP *map, const char *name);
+
+ NTSTATUS (*create_dom_group)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx, const char *name,
+ uint32_t *rid);
+
+ NTSTATUS (*delete_dom_group)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx, uint32_t rid);
+
+ NTSTATUS (*add_group_mapping_entry)(struct pdb_methods *methods,
+ GROUP_MAP *map);
+
+ NTSTATUS (*update_group_mapping_entry)(struct pdb_methods *methods,
+ GROUP_MAP *map);
+
+ NTSTATUS (*delete_group_mapping_entry)(struct pdb_methods *methods,
+ struct dom_sid sid);
+
+ NTSTATUS (*enum_group_mapping)(struct pdb_methods *methods,
+ const struct dom_sid *sid, enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap, size_t *p_num_entries,
+ bool unix_only);
+
+ NTSTATUS (*enum_group_members)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group,
+ uint32_t **pp_member_rids,
+ size_t *p_num_members);
+
+ NTSTATUS (*enum_group_memberships)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user,
+ struct dom_sid **pp_sids, gid_t **pp_gids,
+ uint32_t *p_num_groups);
+
+ NTSTATUS (*set_unix_primary_group)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user);
+
+ NTSTATUS (*add_groupmem)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t group_rid, uint32_t member_rid);
+
+ NTSTATUS (*del_groupmem)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t group_rid, uint32_t member_rid);
+
+ NTSTATUS (*create_alias)(struct pdb_methods *methods,
+ const char *name, uint32_t *rid);
+
+ NTSTATUS (*delete_alias)(struct pdb_methods *methods,
+ const struct dom_sid *sid);
+
+ NTSTATUS (*get_aliasinfo)(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct acct_info *info);
+
+ NTSTATUS (*set_aliasinfo)(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct acct_info *info);
+
+ NTSTATUS (*add_aliasmem)(struct pdb_methods *methods,
+ const struct dom_sid *alias, const struct dom_sid *member);
+ NTSTATUS (*del_aliasmem)(struct pdb_methods *methods,
+ const struct dom_sid *alias, const struct dom_sid *member);
+ NTSTATUS (*enum_aliasmem)(struct pdb_methods *methods,
+ const struct dom_sid *alias, TALLOC_CTX *mem_ctx,
+ struct dom_sid **members, size_t *p_num_members);
+ NTSTATUS (*enum_alias_memberships)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *members,
+ size_t num_members,
+ uint32_t **pp_alias_rids,
+ size_t *p_num_alias_rids);
+
+ NTSTATUS (*lookup_rids)(struct pdb_methods *methods,
+ const struct dom_sid *domain_sid,
+ int num_rids,
+ uint32_t *rids,
+ const char **pp_names,
+ enum lsa_SidType *attrs);
+
+ NTSTATUS (*lookup_names)(struct pdb_methods *methods,
+ const struct dom_sid *domain_sid,
+ int num_names,
+ const char **pp_names,
+ uint32_t *rids,
+ enum lsa_SidType *attrs);
+
+ NTSTATUS (*get_account_policy)(struct pdb_methods *methods,
+ enum pdb_policy_type type,
+ uint32_t *value);
+
+ NTSTATUS (*set_account_policy)(struct pdb_methods *methods,
+ enum pdb_policy_type type,
+ uint32_t value);
+
+ NTSTATUS (*get_seq_num)(struct pdb_methods *methods, time_t *seq_num);
+
+ bool (*search_users)(struct pdb_methods *methods,
+ struct pdb_search *search,
+ uint32_t acct_flags);
+ bool (*search_groups)(struct pdb_methods *methods,
+ struct pdb_search *search);
+ bool (*search_aliases)(struct pdb_methods *methods,
+ struct pdb_search *search,
+ const struct dom_sid *sid);
+
+ /*
+ * Instead of passing down a gid or uid, this function sends down a pointer
+ * to a unixid.
+ *
+ * This acts as an in-out variable so that the idmap functions can correctly
+ * receive ID_TYPE_BOTH, filling in cache details correctly rather than forcing
+ * the cache to store ID_TYPE_UID or ID_TYPE_GID.
+ */
+ bool (*id_to_sid)(struct pdb_methods *methods, struct unixid *id,
+ struct dom_sid *sid);
+ bool (*sid_to_id)(struct pdb_methods *methods, const struct dom_sid *sid,
+ struct unixid *id);
+
+ uint32_t (*capabilities)(struct pdb_methods *methods);
+ bool (*new_rid)(struct pdb_methods *methods, uint32_t *rid);
+
+
+ bool (*get_trusteddom_pw)(struct pdb_methods *methods,
+ const char *domain, char** pwd,
+ struct dom_sid *sid, time_t *pass_last_set_time);
+ NTSTATUS (*get_trusteddom_creds)(struct pdb_methods *methods,
+ const char *domain,
+ TALLOC_CTX *mem_ctx,
+ struct cli_credentials **creds);
+ bool (*set_trusteddom_pw)(struct pdb_methods *methods,
+ const char* domain, const char* pwd,
+ const struct dom_sid *sid);
+ bool (*del_trusteddom_pw)(struct pdb_methods *methods,
+ const char *domain);
+ NTSTATUS (*enum_trusteddoms)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx, uint32_t *num_domains,
+ struct trustdom_info ***domains);
+
+ NTSTATUS (*get_trusted_domain)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const char *domain,
+ struct pdb_trusted_domain **td);
+ NTSTATUS (*get_trusted_domain_by_sid)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid *sid,
+ struct pdb_trusted_domain **td);
+ NTSTATUS (*set_trusted_domain)(struct pdb_methods *methods,
+ const char* domain,
+ const struct pdb_trusted_domain *td);
+ NTSTATUS (*del_trusted_domain)(struct pdb_methods *methods,
+ const char *domain);
+ NTSTATUS (*enum_trusted_domains)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_domains,
+ struct pdb_trusted_domain ***domains);
+
+ NTSTATUS (*get_secret)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ NTTIME *secret_current_lastchange,
+ DATA_BLOB *secret_old,
+ NTTIME *secret_old_lastchange,
+ struct security_descriptor **sd);
+ NTSTATUS (*set_secret)(struct pdb_methods *methods,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ DATA_BLOB *secret_old,
+ struct security_descriptor *sd);
+ NTSTATUS (*delete_secret)(struct pdb_methods *methods,
+ const char *secret_name);
+
+ NTSTATUS (*enum_upn_suffixes)(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_suffixes,
+ char ***suffixes);
+
+ NTSTATUS (*set_upn_suffixes)(struct pdb_methods *methods,
+ uint32_t num_suffixes,
+ const char **suffixes);
+
+ bool (*is_responsible_for_our_sam)(struct pdb_methods *methods);
+ bool (*is_responsible_for_builtin)(struct pdb_methods *methods);
+ bool (*is_responsible_for_wellknown)(struct pdb_methods *methods);
+ bool (*is_responsible_for_unix_users)(struct pdb_methods *methods);
+ bool (*is_responsible_for_unix_groups)(struct pdb_methods *methods);
+ bool (*is_responsible_for_everything_else)(struct pdb_methods *methods);
+
+ void *private_data; /* Private data of some kind */
+
+ void (*free_private_data)(void **);
+};
+
+typedef NTSTATUS (*pdb_init_function)(struct pdb_methods **, const char *);
+
+struct pdb_init_function_entry {
+ const char *name;
+
+ /* Function to create a member of the pdb_methods list */
+ pdb_init_function init;
+
+ struct pdb_init_function_entry *prev, *next;
+};
+
+/* The following definitions come from passdb/account_pol.c */
+
+void account_policy_names_list(TALLOC_CTX *mem_ctx, const char ***names, int *num_names);
+const char *decode_account_policy_name(enum pdb_policy_type type);
+const char *get_account_policy_attr(enum pdb_policy_type type);
+const char *account_policy_get_desc(enum pdb_policy_type type);
+enum pdb_policy_type account_policy_name_to_typenum(const char *name);
+bool account_policy_get_default(enum pdb_policy_type type, uint32_t *val);
+bool init_account_policy(void);
+bool account_policy_get(enum pdb_policy_type type, uint32_t *value);
+bool account_policy_set(enum pdb_policy_type type, uint32_t value);
+bool cache_account_policy_set(enum pdb_policy_type type, uint32_t value);
+bool cache_account_policy_get(enum pdb_policy_type type, uint32_t *value);
+struct db_context *get_account_pol_db( void );
+
+/* The following definitions come from passdb/login_cache.c */
+
+bool login_cache_init(void);
+bool login_cache_shutdown(void);
+bool login_cache_read(struct samu *sampass, struct login_cache *entry);
+bool login_cache_write(const struct samu *sampass,
+ const struct login_cache *entry);
+bool login_cache_delentry(const struct samu *sampass);
+
+/* The following definitions come from passdb/passdb.c */
+
+struct samu *samu_new( TALLOC_CTX *ctx );
+NTSTATUS samu_set_unix(struct samu *user, const struct passwd *pwd);
+NTSTATUS samu_alloc_rid_unix(struct pdb_methods *methods,
+ struct samu *user, const struct passwd *pwd);
+char *pdb_encode_acct_ctrl(uint32_t acct_ctrl, size_t length);
+uint32_t pdb_decode_acct_ctrl(const char *p);
+void pdb_sethexpwd(char p[33], const unsigned char *pwd, uint32_t acct_ctrl);
+bool pdb_gethexpwd(const char *p, unsigned char *pwd);
+void pdb_sethexhours(char *p, const unsigned char *hours);
+bool pdb_gethexhours(const char *p, unsigned char *hours);
+int algorithmic_rid_base(void);
+uid_t algorithmic_pdb_user_rid_to_uid(uint32_t user_rid);
+uid_t max_algorithmic_uid(void);
+uint32_t algorithmic_pdb_uid_to_user_rid(uid_t uid);
+gid_t pdb_group_rid_to_gid(uint32_t group_rid);
+gid_t max_algorithmic_gid(void);
+uint32_t algorithmic_pdb_gid_to_group_rid(gid_t gid);
+bool algorithmic_pdb_rid_is_user(uint32_t rid);
+bool lookup_global_sam_name(const char *name, int flags, uint32_t *rid,
+ enum lsa_SidType *type);
+NTSTATUS local_password_change(const char *user_name,
+ int local_flags,
+ const char *new_passwd,
+ char **pp_err_str,
+ char **pp_msg_str);
+bool init_samu_from_buffer(struct samu *sampass, uint32_t level,
+ uint8_t *buf, uint32_t buflen);
+uint32_t init_buffer_from_samu (uint8_t **buf, struct samu *sampass, bool size_only);
+bool pdb_copy_sam_account(struct samu *dst, struct samu *src );
+bool pdb_update_bad_password_count(struct samu *sampass, bool *updated);
+bool pdb_update_autolock_flag(struct samu *sampass, bool *updated);
+bool pdb_increment_bad_password_count(struct samu *sampass);
+bool is_dc_trusted_domain_situation(const char *domain_name);
+bool get_trust_pw_clear(const char *domain, char **ret_pwd,
+ const char **account_name,
+ enum netr_SchannelType *channel);
+bool get_trust_pw_hash(const char *domain, uint8_t ret_pwd[16],
+ const char **account_name,
+ enum netr_SchannelType *channel);
+struct cli_credentials;
+NTSTATUS pdb_get_trust_credentials(const char *netbios_domain,
+ const char *dns_domain, /* optional */
+ TALLOC_CTX *mem_ctx,
+ struct cli_credentials **_creds);
+
+/* The following definitions come from passdb/pdb_compat.c */
+
+uint32_t pdb_get_user_rid (const struct samu *sampass);
+uint32_t pdb_get_group_rid (struct samu *sampass);
+bool pdb_set_user_sid_from_rid (struct samu *sampass, uint32_t rid, enum pdb_value_state flag);
+bool pdb_set_group_sid_from_rid (struct samu *sampass, uint32_t grid, enum pdb_value_state flag);
+
+/* The following definitions come from passdb/pdb_get_set.c */
+
+bool pdb_is_password_change_time_max(time_t test_time);
+uint32_t pdb_get_acct_ctrl(const struct samu *sampass);
+time_t pdb_get_logon_time(const struct samu *sampass);
+time_t pdb_get_logoff_time(const struct samu *sampass);
+time_t pdb_get_kickoff_time(const struct samu *sampass);
+time_t pdb_get_bad_password_time(const struct samu *sampass);
+time_t pdb_get_pass_last_set_time(const struct samu *sampass);
+time_t pdb_get_pass_can_change_time(const struct samu *sampass);
+time_t pdb_get_pass_can_change_time_noncalc(const struct samu *sampass);
+time_t pdb_get_pass_must_change_time(const struct samu *sampass);
+bool pdb_get_pass_can_change(const struct samu *sampass);
+uint16_t pdb_get_logon_divs(const struct samu *sampass);
+uint32_t pdb_get_hours_len(const struct samu *sampass);
+const uint8_t *pdb_get_hours(const struct samu *sampass);
+const uint8_t *pdb_get_nt_passwd(const struct samu *sampass);
+const uint8_t *pdb_get_lanman_passwd(const struct samu *sampass);
+const uint8_t *pdb_get_pw_history(const struct samu *sampass, uint32_t *current_hist_len);
+const char *pdb_get_plaintext_passwd(const struct samu *sampass);
+const struct dom_sid *pdb_get_user_sid(const struct samu *sampass);
+const struct dom_sid *pdb_get_group_sid(struct samu *sampass);
+enum pdb_value_state pdb_get_init_flags(const struct samu *sampass, enum pdb_elements element);
+const char *pdb_get_username(const struct samu *sampass);
+const char *pdb_get_domain(const struct samu *sampass);
+const char *pdb_get_nt_username(const struct samu *sampass);
+const char *pdb_get_fullname(const struct samu *sampass);
+const char *pdb_get_homedir(const struct samu *sampass);
+const char *pdb_get_dir_drive(const struct samu *sampass);
+const char *pdb_get_logon_script(const struct samu *sampass);
+const char *pdb_get_profile_path(const struct samu *sampass);
+const char *pdb_get_acct_desc(const struct samu *sampass);
+const char *pdb_get_workstations(const struct samu *sampass);
+const char *pdb_get_comment(const struct samu *sampass);
+const char *pdb_get_munged_dial(const struct samu *sampass);
+uint16_t pdb_get_bad_password_count(const struct samu *sampass);
+uint16_t pdb_get_logon_count(const struct samu *sampass);
+uint16_t pdb_get_country_code(const struct samu *sampass);
+uint16_t pdb_get_code_page(const struct samu *sampass);
+uint32_t pdb_get_unknown_6(const struct samu *sampass);
+void *pdb_get_backend_private_data(const struct samu *sampass, const struct pdb_methods *my_methods);
+bool pdb_set_acct_ctrl(struct samu *sampass, uint32_t acct_ctrl, enum pdb_value_state flag);
+bool pdb_set_logon_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag);
+bool pdb_set_logoff_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag);
+bool pdb_set_kickoff_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag);
+bool pdb_set_bad_password_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag);
+bool pdb_set_pass_can_change_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag);
+bool pdb_set_pass_last_set_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag);
+bool pdb_set_hours_len(struct samu *sampass, uint32_t len, enum pdb_value_state flag);
+bool pdb_set_logon_divs(struct samu *sampass, uint16_t hours, enum pdb_value_state flag);
+bool pdb_set_init_flags(struct samu *sampass, enum pdb_elements element, enum pdb_value_state value_flag);
+bool pdb_set_user_sid(struct samu *sampass, const struct dom_sid *u_sid, enum pdb_value_state flag);
+bool pdb_set_user_sid_from_string(struct samu *sampass, const char *u_sid, enum pdb_value_state flag);
+bool pdb_set_group_sid(struct samu *sampass, const struct dom_sid *g_sid, enum pdb_value_state flag);
+bool pdb_set_username(struct samu *sampass, const char *username, enum pdb_value_state flag);
+bool pdb_set_domain(struct samu *sampass, const char *domain, enum pdb_value_state flag);
+bool pdb_set_nt_username(struct samu *sampass, const char *nt_username, enum pdb_value_state flag);
+bool pdb_set_fullname(struct samu *sampass, const char *full_name, enum pdb_value_state flag);
+bool pdb_set_logon_script(struct samu *sampass, const char *logon_script, enum pdb_value_state flag);
+bool pdb_set_profile_path(struct samu *sampass, const char *profile_path, enum pdb_value_state flag);
+bool pdb_set_dir_drive(struct samu *sampass, const char *dir_drive, enum pdb_value_state flag);
+bool pdb_set_homedir(struct samu *sampass, const char *home_dir, enum pdb_value_state flag);
+bool pdb_set_acct_desc(struct samu *sampass, const char *acct_desc, enum pdb_value_state flag);
+bool pdb_set_workstations(struct samu *sampass, const char *workstations, enum pdb_value_state flag);
+bool pdb_set_comment(struct samu *sampass, const char *comment, enum pdb_value_state flag);
+bool pdb_set_munged_dial(struct samu *sampass, const char *munged_dial, enum pdb_value_state flag);
+bool pdb_set_nt_passwd(struct samu *sampass, const uint8_t pwd[NT_HASH_LEN], enum pdb_value_state flag);
+bool pdb_set_lanman_passwd(struct samu *sampass, const uint8_t pwd[LM_HASH_LEN], enum pdb_value_state flag);
+bool pdb_set_pw_history(struct samu *sampass, const uint8_t *pwd, uint32_t historyLen, enum pdb_value_state flag);
+bool pdb_set_plaintext_pw_only(struct samu *sampass, const char *password, enum pdb_value_state flag);
+bool pdb_update_history(struct samu *sampass, const uint8_t new_nt[NT_HASH_LEN]);
+bool pdb_set_bad_password_count(struct samu *sampass, uint16_t bad_password_count, enum pdb_value_state flag);
+bool pdb_set_logon_count(struct samu *sampass, uint16_t logon_count, enum pdb_value_state flag);
+bool pdb_set_country_code(struct samu *sampass, uint16_t country_code,
+ enum pdb_value_state flag);
+bool pdb_set_code_page(struct samu *sampass, uint16_t code_page,
+ enum pdb_value_state flag);
+bool pdb_set_unknown_6(struct samu *sampass, uint32_t unkn, enum pdb_value_state flag);
+bool pdb_set_hours(struct samu *sampass, const uint8_t *hours, int hours_len,
+ enum pdb_value_state flag);
+bool pdb_set_backend_private_data(struct samu *sampass, void *private_data,
+ void (*free_fn)(void **),
+ const struct pdb_methods *my_methods,
+ enum pdb_value_state flag);
+bool pdb_set_pass_can_change(struct samu *sampass, bool canchange);
+bool pdb_set_plaintext_passwd(struct samu *sampass, const char *plaintext);
+uint32_t pdb_build_fields_present(struct samu *sampass);
+bool pdb_element_is_changed(const struct samu *sampass,
+ enum pdb_elements element);
+bool pdb_element_is_set_or_changed(const struct samu *sampass,
+ enum pdb_elements element);
+
+/* The following definitions come from passdb/pdb_interface.c */
+
+NTSTATUS smb_register_passdb(int version, const char *name, pdb_init_function init) ;
+struct pdb_init_function_entry *pdb_find_backend_entry(const char *name);
+const struct pdb_init_function_entry *pdb_get_backends(void);
+struct tevent_context *pdb_get_tevent_context(void);
+NTSTATUS make_pdb_method_name(struct pdb_methods **methods, const char *selected);
+struct pdb_domain_info *pdb_get_domain_info(TALLOC_CTX *mem_ctx);
+bool pdb_getsampwnam(struct samu *sam_acct, const char *username) ;
+bool pdb_getsampwsid(struct samu *sam_acct, const struct dom_sid *sid) ;
+NTSTATUS pdb_create_user(TALLOC_CTX *mem_ctx, const char *name, uint32_t flags,
+ uint32_t *rid);
+NTSTATUS pdb_delete_user(TALLOC_CTX *mem_ctx, struct samu *sam_acct);
+NTSTATUS pdb_add_sam_account(struct samu *sam_acct) ;
+NTSTATUS pdb_update_sam_account(struct samu *sam_acct) ;
+NTSTATUS pdb_delete_sam_account(struct samu *sam_acct) ;
+NTSTATUS pdb_rename_sam_account(struct samu *oldname, const char *newname);
+NTSTATUS pdb_update_login_attempts(struct samu *sam_acct, bool success);
+bool pdb_getgrsid(GROUP_MAP *map, struct dom_sid sid);
+bool pdb_getgrgid(GROUP_MAP *map, gid_t gid);
+bool pdb_getgrnam(GROUP_MAP *map, const char *name);
+NTSTATUS pdb_create_dom_group(TALLOC_CTX *mem_ctx, const char *name,
+ uint32_t *rid);
+NTSTATUS pdb_delete_dom_group(TALLOC_CTX *mem_ctx, uint32_t rid);
+NTSTATUS pdb_add_group_mapping_entry(GROUP_MAP *map);
+NTSTATUS pdb_update_group_mapping_entry(GROUP_MAP *map);
+NTSTATUS pdb_delete_group_mapping_entry(struct dom_sid sid);
+bool pdb_enum_group_mapping(const struct dom_sid *sid,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries,
+ bool unix_only);
+NTSTATUS pdb_enum_group_members(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ uint32_t **pp_member_rids,
+ size_t *p_num_members);
+NTSTATUS pdb_enum_group_memberships(TALLOC_CTX *mem_ctx, struct samu *user,
+ struct dom_sid **pp_sids, gid_t **pp_gids,
+ uint32_t *p_num_groups);
+NTSTATUS pdb_set_unix_primary_group(TALLOC_CTX *mem_ctx, struct samu *user);
+NTSTATUS pdb_add_groupmem(TALLOC_CTX *mem_ctx, uint32_t group_rid,
+ uint32_t member_rid);
+NTSTATUS pdb_del_groupmem(TALLOC_CTX *mem_ctx, uint32_t group_rid,
+ uint32_t member_rid);
+NTSTATUS pdb_create_alias(const char *name, uint32_t *rid);
+NTSTATUS pdb_delete_alias(const struct dom_sid *sid);
+NTSTATUS pdb_get_aliasinfo(const struct dom_sid *sid, struct acct_info *info);
+NTSTATUS pdb_set_aliasinfo(const struct dom_sid *sid, struct acct_info *info);
+NTSTATUS pdb_add_aliasmem(const struct dom_sid *alias, const struct dom_sid *member);
+NTSTATUS pdb_del_aliasmem(const struct dom_sid *alias, const struct dom_sid *member);
+NTSTATUS pdb_enum_aliasmem(const struct dom_sid *alias, TALLOC_CTX *mem_ctx,
+ struct dom_sid **pp_members, size_t *p_num_members);
+NTSTATUS pdb_enum_alias_memberships(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *members, size_t num_members,
+ uint32_t **pp_alias_rids,
+ size_t *p_num_alias_rids);
+NTSTATUS pdb_lookup_rids(const struct dom_sid *domain_sid,
+ int num_rids,
+ uint32_t *rids,
+ const char **names,
+ enum lsa_SidType *attrs);
+bool pdb_get_account_policy(enum pdb_policy_type type, uint32_t *value);
+bool pdb_set_account_policy(enum pdb_policy_type type, uint32_t value);
+bool pdb_get_seq_num(time_t *seq_num);
+/*
+ * Instead of passing down a gid or uid, this function sends down a pointer
+ * to a unixid.
+ *
+ * This acts as an in-out variable so that the idmap functions can correctly
+ * receive ID_TYPE_BOTH, filling in cache details correctly rather than forcing
+ * the cache to store ID_TYPE_UID or ID_TYPE_GID.
+ */
+bool pdb_id_to_sid(struct unixid *id, struct dom_sid *sid);
+bool pdb_sid_to_id(const struct dom_sid *sid, struct unixid *id);
+uint32_t pdb_capabilities(void);
+bool pdb_new_rid(uint32_t *rid);
+bool initialize_password_db(bool reload, struct tevent_context *tevent_ctx);
+struct pdb_search *pdb_search_init(TALLOC_CTX *mem_ctx,
+ enum pdb_search_type type);
+struct pdb_search *pdb_search_users(TALLOC_CTX *mem_ctx, uint32_t acct_flags);
+struct pdb_search *pdb_search_groups(TALLOC_CTX *mem_ctx);
+struct pdb_search *pdb_search_aliases(TALLOC_CTX *mem_ctx, const struct dom_sid *sid);
+uint32_t pdb_search_entries(struct pdb_search *search,
+ uint32_t start_idx, uint32_t max_entries,
+ struct samr_displayentry **result);
+bool pdb_get_trusteddom_pw(const char *domain, char** pwd, struct dom_sid *sid,
+ time_t *pass_last_set_time);
+NTSTATUS pdb_get_trusteddom_creds(const char *domain, TALLOC_CTX *mem_ctx,
+ struct cli_credentials **creds);
+bool pdb_set_trusteddom_pw(const char* domain, const char* pwd,
+ const struct dom_sid *sid);
+bool pdb_del_trusteddom_pw(const char *domain);
+NTSTATUS pdb_enum_trusteddoms(TALLOC_CTX *mem_ctx, uint32_t *num_domains,
+ struct trustdom_info ***domains);
+NTSTATUS pdb_get_trusted_domain(TALLOC_CTX *mem_ctx, const char *domain,
+ struct pdb_trusted_domain **td);
+NTSTATUS pdb_get_trusted_domain_by_sid(TALLOC_CTX *mem_ctx, struct dom_sid *sid,
+ struct pdb_trusted_domain **td);
+NTSTATUS pdb_set_trusted_domain(const char* domain,
+ const struct pdb_trusted_domain *td);
+NTSTATUS pdb_del_trusted_domain(const char *domain);
+NTSTATUS pdb_enum_trusted_domains(TALLOC_CTX *mem_ctx, uint32_t *num_domains,
+ struct pdb_trusted_domain ***domains);
+NTSTATUS make_pdb_method( struct pdb_methods **methods ) ;
+NTSTATUS pdb_get_secret(TALLOC_CTX *mem_ctx,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ NTTIME *secret_current_lastchange,
+ DATA_BLOB *secret_old,
+ NTTIME *secret_old_lastchange,
+ struct security_descriptor **sd);
+NTSTATUS pdb_set_secret(const char *secret_name,
+ DATA_BLOB *secret_current,
+ DATA_BLOB *secret_old,
+ struct security_descriptor *sd);
+NTSTATUS pdb_delete_secret(const char *secret_name);
+bool pdb_sid_to_id_unix_users_and_groups(const struct dom_sid *sid,
+ struct unixid *id);
+
+NTSTATUS pdb_enum_upn_suffixes(TALLOC_CTX *mem_ctx,
+ uint32_t *num_suffixes,
+ char ***suffixes);
+
+NTSTATUS pdb_set_upn_suffixes(uint32_t num_suffixes,
+ const char **suffixes);
+bool pdb_is_responsible_for_our_sam(void);
+bool pdb_is_responsible_for_builtin(void);
+bool pdb_is_responsible_for_wellknown(void);
+bool pdb_is_responsible_for_unix_users(void);
+bool pdb_is_responsible_for_unix_groups(void);
+bool pdb_is_responsible_for_everything_else(void);
+
+/* The following definitions come from passdb/pdb_util.c */
+
+NTSTATUS pdb_create_builtin(uint32_t rid);
+NTSTATUS create_builtin_users(const struct dom_sid *sid);
+NTSTATUS create_builtin_administrators(const struct dom_sid *sid);
+NTSTATUS create_builtin_guests(const struct dom_sid *dom_sid);
+
+#include "passdb/machine_sid.h"
+#include "passdb/lookup_sid.h"
+
+/* The following definitions come from passdb/pdb_secrets.c
+ * and should be used by PDB modules if they need to store
+ * sid/guid information for the domain in secrets database
+ */
+bool PDB_secrets_mark_domain_protected(const char *domain);
+bool PDB_secrets_clear_domain_protection(const char *domain);
+bool PDB_secrets_store_domain_sid(const char *domain, const struct dom_sid *sid);
+bool PDB_secrets_fetch_domain_sid(const char *domain, struct dom_sid *sid);
+bool PDB_secrets_store_domain_guid(const char *domain, struct GUID *guid);
+bool PDB_secrets_fetch_domain_guid(const char *domain, struct GUID *guid);
+
+#endif /* _PASSDB_H */
diff --git a/source3/include/printing.h b/source3/include/printing.h
new file mode 100644
index 0000000..20a2eb3
--- /dev/null
+++ b/source3/include/printing.h
@@ -0,0 +1,262 @@
+/*
+ Unix SMB/CIFS implementation.
+ printing definitions
+ Copyright (C) Andrew Tridgell 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ This file defines the low-level printing system interfaces used by the
+ SAMBA printing subsystem.
+*/
+#ifndef PRINTING_H_
+#define PRINTING_H_
+
+#include <tdb.h>
+#include "lib/param/loadparm.h"
+
+/* Extra fields above "LPQ_PRINTING" are used to map extra NT status codes. */
+
+enum {
+ LPQ_QUEUED = 0,
+ LPQ_PAUSED,
+ LPQ_SPOOLING,
+ LPQ_PRINTING,
+ LPQ_ERROR,
+ LPQ_DELETING,
+ LPQ_OFFLINE,
+ LPQ_PAPEROUT,
+ LPQ_PRINTED,
+ LPQ_DELETED,
+ LPQ_BLOCKED,
+ LPQ_USER_INTERVENTION,
+
+ /* smbd is dooing the file spooling before passing control to spoolss */
+ PJOB_SMBD_SPOOLING
+};
+
+typedef struct _print_queue_struct {
+ int sysjob; /* normally the UNIX jobid -- see note in
+ printing.c:traverse_fn_delete() */
+ int size;
+ int page_count;
+ int status;
+ int priority;
+ time_t time;
+ fstring fs_user;
+ fstring fs_file;
+} print_queue_struct;
+
+enum {LPSTAT_OK, LPSTAT_STOPPED, LPSTAT_ERROR};
+
+typedef struct {
+ fstring message;
+ size_t qcount;
+ int status;
+} print_status_struct;
+
+/* Information for print jobs */
+struct printjob {
+ pid_t pid; /* which process launched the job */
+ uint32_t jobid; /* the spoolss print job identifier */
+ int sysjob; /* the system (lp) job number */
+ int fd; /* file descriptor of open file if open */
+ time_t starttime; /* when the job started spooling */
+ int status; /* the status of this job */
+ size_t size; /* the size of the job so far */
+ int page_count; /* then number of pages so far */
+ bool spooled; /* has it been sent to the spooler yet? */
+ bool smbjob; /* set if the job is a SMB job */
+ fstring filename; /* the filename used to spool the file */
+ fstring jobname; /* the job name given to us by the client */
+ fstring user; /* the user who started the job */
+ fstring clientmachine; /* The client machine which started this job */
+ fstring queuename; /* service number of printer for this job */
+ struct spoolss_DeviceMode *devmode;
+};
+
+/* Information for print interfaces */
+struct printif
+{
+ /* value of the 'printing' option for this service */
+ enum printing_types type;
+
+ int (*queue_get)(const char *printer_name,
+ enum printing_types printing_type,
+ char *lpq_command,
+ print_queue_struct **q,
+ print_status_struct *status);
+ int (*queue_pause)(int snum);
+ int (*queue_resume)(int snum);
+ int (*job_delete)(const char *sharename, const char *lprm_command, struct printjob *pjob);
+ int (*job_pause)(int snum, struct printjob *pjob);
+ int (*job_resume)(int snum, struct printjob *pjob);
+ int (*job_submit)(int snum, struct printjob *pjob,
+ enum printing_types printing_type,
+ char *lpq_command);
+};
+
+extern struct printif generic_printif;
+
+#ifdef HAVE_CUPS
+extern struct printif cups_printif;
+#endif /* HAVE_CUPS */
+
+#ifdef HAVE_IPRINT
+extern struct printif iprint_printif;
+#endif /* HAVE_IPRINT */
+
+/* PRINT_MAX_JOBID is now defined in local.h */
+#define UNIX_JOB_START PRINT_MAX_JOBID
+#define NEXT_JOBID(j) ((j+1) % PRINT_MAX_JOBID > 0 ? (j+1) % PRINT_MAX_JOBID : 1)
+
+#define MAX_CACHE_VALID_TIME 3600
+#define CUPS_DEFAULT_CONNECTION_TIMEOUT 30
+
+#ifndef PRINT_SPOOL_PREFIX
+#define PRINT_SPOOL_PREFIX "smbprn."
+#endif
+#define PRINT_DATABASE_VERSION 8
+
+#ifdef AIX
+#define DEFAULT_PRINTING PRINT_AIX
+#define PRINTCAP_NAME "/etc/qconfig"
+#endif
+
+#ifdef HPUX
+#define DEFAULT_PRINTING PRINT_HPUX
+#endif
+
+#ifdef QNX
+#define DEFAULT_PRINTING PRINT_QNX
+#endif
+
+#ifndef DEFAULT_PRINTING
+#ifdef HAVE_CUPS
+#define DEFAULT_PRINTING PRINT_CUPS
+#define PRINTCAP_NAME "cups"
+#elif defined(SYSV)
+#define DEFAULT_PRINTING PRINT_SYSV
+#define PRINTCAP_NAME "lpstat"
+#else
+#define DEFAULT_PRINTING PRINT_BSD
+#define PRINTCAP_NAME "/etc/printcap"
+#endif
+#endif
+
+#ifndef PRINTCAP_NAME
+#define PRINTCAP_NAME "/etc/printcap"
+#endif
+
+/* There can be this many printing tdb's open, plus any locked ones. */
+#define MAX_PRINT_DBS_OPEN 1
+
+struct tdb_print_db {
+ struct tdb_print_db *next, *prev;
+ struct tdb_context *tdb;
+ int ref_count;
+ fstring printer_name;
+};
+
+/*
+ * Used for print notify
+ */
+
+#define NOTIFY_PID_LIST_KEY "NOTIFY_PID_LIST"
+
+/* The following definitions come from printing/printspoolss.c */
+
+NTSTATUS print_spool_open(files_struct *fsp,
+ const char *fname,
+ uint64_t current_vuid);
+
+int print_spool_write(files_struct *fsp, const char *data, uint32_t size,
+ off_t offset, uint32_t *written);
+
+void print_spool_end(files_struct *fsp, enum file_close_type close_type);
+
+void print_spool_terminate(struct connection_struct *conn,
+ struct print_file_data *print_file);
+uint16_t print_spool_rap_jobid(struct print_file_data *print_file);
+
+/* The following definitions come from printing/printing.c */
+
+uint32_t sysjob_to_jobid_pdb(struct tdb_print_db *pdb, int sysjob);
+uint32_t sysjob_to_jobid(int unix_jobid);
+int jobid_to_sysjob_pdb(struct tdb_print_db *pdb, uint32_t jobid);
+bool print_notify_register_pid(int snum);
+bool print_notify_deregister_pid(int snum);
+bool print_job_exists(const char* sharename, uint32_t jobid);
+struct spoolss_DeviceMode *print_job_devmode(TALLOC_CTX *mem_ctx,
+ const char *sharename,
+ uint32_t jobid);
+bool print_job_set_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, const char *name);
+bool print_job_get_name(TALLOC_CTX *mem_ctx, const char *sharename, uint32_t jobid, char **name);
+WERROR print_job_delete(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid);
+WERROR print_job_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid);
+WERROR print_job_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid);
+ssize_t print_job_write(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid, const char *buf, size_t size);
+int print_queue_length(struct messaging_context *msg_ctx, int snum,
+ print_status_struct *pstatus);
+WERROR print_job_start(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *clientmachine,
+ int snum, const char *docname, const char *filename,
+ struct spoolss_DeviceMode *devmode, uint32_t *_jobid);
+void print_job_endpage(struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid);
+NTSTATUS print_job_end(struct messaging_context *msg_ctx, int snum,
+ uint32_t jobid, enum file_close_type close_type);
+int print_queue_status(struct messaging_context *msg_ctx, int snum,
+ print_queue_struct **ppqueue,
+ print_status_struct *status);
+WERROR print_queue_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum);
+WERROR print_queue_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum);
+WERROR print_queue_purge(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum);
+bool print_backend_init(struct messaging_context *msg_ctx);
+void printing_end(void);
+
+/* The following definitions come from printing/lpq_parse.c */
+
+bool parse_lpq_entry(enum printing_types printing_type,char *line,
+ print_queue_struct *buf,
+ print_status_struct *status,bool first);
+
+/* The following definitions come from printing/printing_db.c */
+
+struct tdb_print_db *get_print_db_byname(const char *printername);
+void release_print_db( struct tdb_print_db *pdb);
+void close_all_print_db(void);
+TDB_DATA get_printer_notify_pid_list(struct tdb_context *tdb, const char *printer_name, bool cleanlist);
+
+void print_queue_receive(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+#endif /* PRINTING_H_ */
diff --git a/source3/include/proto.h b/source3/include/proto.h
new file mode 100644
index 0000000..e9e13f6
--- /dev/null
+++ b/source3/include/proto.h
@@ -0,0 +1,774 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * collected prototypes header
+ *
+ * frozen from "make proto" in May 2008
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PROTO_H_
+#define _PROTO_H_
+
+#include <sys/types.h>
+#include <regex.h>
+
+#include "lib/util/access.h"
+
+/* The following definitions come from lib/adt_tree.c */
+
+/* The following definitions come from lib/audit.c */
+
+const char *audit_category_str(uint32_t category);
+const char *audit_param_str(uint32_t category);
+const char *audit_description_str(uint32_t category);
+bool get_audit_category_from_param(const char *param, uint32_t *audit_category);
+const char *audit_policy_str(TALLOC_CTX *mem_ctx, uint32_t policy);
+
+/* The following definitions come from lib/charcnv.c */
+
+void gfree_charcnv(void);
+size_t push_ascii(void *dest, const char *src, size_t dest_len, int flags);
+size_t push_ascii_fstring(void *dest, const char *src);
+size_t push_ascii_nstring(void *dest, const char *src);
+size_t pull_ascii(char *dest, const void *src, size_t dest_len, size_t src_len, int flags);
+size_t pull_ascii_fstring(char *dest, const void *src);
+size_t pull_ascii_nstring(char *dest, size_t dest_len, const void *src);
+size_t push_string_check_fn(void *dest, const char *src,
+ size_t dest_len, int flags);
+size_t push_string_base(const char *base, uint16_t flags2,
+ void *dest, const char *src,
+ size_t dest_len, int flags);
+size_t pull_string_talloc(TALLOC_CTX *ctx,
+ const void *base_ptr,
+ uint16_t smb_flags2,
+ char **ppdest,
+ const void *src,
+ size_t src_len,
+ int flags);
+size_t dos_PutUniCode(char *dst,const char *src, size_t len, bool null_terminate);
+int rpcstr_push_talloc(TALLOC_CTX *ctx, smb_ucs2_t **dest, const char *src);
+
+/* The following definitions come from lib/dmallocmsg.c */
+
+void register_dmalloc_msgs(struct messaging_context *msg_ctx);
+
+/* The following definitions come from lib/dprintf.c */
+
+void display_set_stderr(void);
+
+/* The following definitions come from lib/errmap_unix.c */
+
+NTSTATUS map_nt_error_from_unix(int unix_error);
+
+/* The following definitions come from lib/file_id.c */
+
+struct file_id vfs_file_id_from_sbuf(connection_struct *conn, const SMB_STRUCT_STAT *sbuf);
+
+NTSTATUS vfs_at_fspcwd(TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ struct files_struct **_fsp);
+
+#include "source3/lib/interface.h"
+
+/* The following definitions come from lib/ldap_debug_handler.c */
+
+void init_ldap_debugging(void);
+
+/* The following definitions come from lib/ldap_escape.c */
+
+char *escape_ldap_string(TALLOC_CTX *mem_ctx, const char *s);
+char *escape_rdn_val_string_alloc(const char *s);
+
+/* The following definitions come from lib/ms_fnmatch.c */
+
+int ms_fnmatch(const char *pattern, const char *string, bool translate_pattern,
+ bool is_case_sensitive);
+
+/* The following definitions come from lib/recvfile.c */
+
+ssize_t sys_recvfile(int fromfd,
+ int tofd,
+ off_t offset,
+ size_t count);
+ssize_t sys_recvfile(int fromfd,
+ int tofd,
+ off_t offset,
+ size_t count);
+ssize_t drain_socket(int sockfd, size_t count);
+
+/* The following definitions come from lib/sendfile.c */
+
+ssize_t sys_sendfile(int tofd, int fromfd, const DATA_BLOB *header, off_t offset, size_t count);
+
+/* The following definitions come from lib/server_mutex.c */
+
+struct named_mutex *grab_named_mutex(TALLOC_CTX *mem_ctx, const char *name,
+ int timeout);
+
+/* The following definitions come from lib/sharesec.c */
+
+NTSTATUS share_info_db_init(void);
+struct security_descriptor *get_share_security( TALLOC_CTX *ctx, const char *servicename,
+ size_t *psize);
+NTSTATUS set_share_security(const char *share_name,
+ struct security_descriptor *psd);
+NTSTATUS delete_share_security(const char *servicename);
+bool share_access_check(const struct security_token *token,
+ const char *sharename,
+ uint32_t desired_access,
+ uint32_t *pgranted);
+bool parse_usershare_acl(TALLOC_CTX *ctx, const char *acl_str, struct security_descriptor **ppsd);
+
+/* The following definitions come from lib/smbrun.c */
+
+int smbrun_no_sanitize(const char *cmd, int *outfd, char * const *env);
+int smbrun(const char *cmd, int *outfd, char * const *env);
+int smbrunsecret(const char *cmd, const char *secret);
+
+/* The following definitions come from lib/sysquotas.c */
+
+int sys_get_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+int sys_set_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+
+/* The following definitions come from lib/sysquotas_*.c */
+
+int sys_get_vfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+int sys_set_vfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+
+int sys_get_xfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+int sys_set_xfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+
+int sys_get_jfs2_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+int sys_set_jfs2_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+
+int sys_get_nfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id, SMB_DISK_QUOTA *dp);
+int sys_set_nfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id, SMB_DISK_QUOTA *dp);
+
+/* The following definitions come from lib/system.c */
+
+ssize_t sys_send(int s, const void *msg, size_t len, int flags);
+ssize_t sys_recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
+int sys_fcntl_ptr(int fd, int cmd, void *arg);
+int sys_fcntl_long(int fd, int cmd, long arg);
+int sys_fcntl_int(int fd, int cmd, int arg);
+void update_stat_ex_mtime(struct stat_ex *dst, struct timespec write_ts);
+void update_stat_ex_create_time(struct stat_ex *dst, struct timespec create_time);
+void update_stat_ex_from_saved_stat(struct stat_ex *dst,
+ const struct stat_ex *src);
+int sys_stat(const char *fname, SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times);
+int sys_fstat(int fd, SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times);
+int sys_lstat(const char *fname,SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times);
+int sys_fstatat(int fd,
+ const char *pathname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags,
+ bool fake_dir_create_times);
+int sys_posix_fallocate(int fd, off_t offset, off_t len);
+int sys_fallocate(int fd, uint32_t mode, off_t offset, off_t len);
+DIR *sys_fdopendir(int fd);
+int sys_mknod(const char *path, mode_t mode, SMB_DEV_T dev);
+int sys_mknodat(int dirfd, const char *path, mode_t mode, SMB_DEV_T dev);
+char *sys_getwd(void);
+void set_effective_capability(enum smbd_capability capability);
+void drop_effective_capability(enum smbd_capability capability);
+long sys_random(void);
+void sys_srandom(unsigned int seed);
+int getgroups_max(void);
+int setgroups_max(void);
+int sys_getgroups(int setlen, gid_t *gidset);
+int sys_setgroups(gid_t UNUSED(primary_gid), int setlen, gid_t *gidset);
+uint32_t unix_dev_major(SMB_DEV_T dev);
+uint32_t unix_dev_minor(SMB_DEV_T dev);
+char *sys_realpath(const char *path);
+#if 0
+int sys_get_number_of_cores(void);
+#endif
+
+struct sys_proc_fd_path_buf {
+ char buf[35]; /* "/proc/self/fd/" + strlen(2^64) + 0-terminator */
+};
+bool sys_have_proc_fds(void);
+char *sys_proc_fd_path(int fd, struct sys_proc_fd_path_buf *buf);
+
+struct stat;
+void init_stat_ex_from_stat (struct stat_ex *dst,
+ const struct stat *src,
+ bool fake_dir_create_times);
+
+/* The following definitions come from lib/system_smbd.c */
+
+bool getgroups_unix_user(TALLOC_CTX *mem_ctx, const char *user,
+ gid_t primary_gid,
+ gid_t **ret_groups, uint32_t *p_ngroups);
+
+/* The following definitions come from lib/tallocmsg.c */
+
+void register_msg_pool_usage(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx);
+
+/* The following definitions come from lib/time.c */
+
+uint32_t convert_time_t_to_uint32_t(time_t t);
+time_t convert_uint32_t_to_time_t(uint32_t u);
+bool nt_time_is_zero(const NTTIME *nt);
+time_t generalized_to_unix_time(const char *str);
+int get_server_zone_offset(void);
+int set_server_zone_offset(time_t t);
+char *timeval_string(TALLOC_CTX *ctx, const struct timeval *tp, bool hires);
+char *current_timestring(TALLOC_CTX *ctx, bool hires);
+void srv_put_dos_date(char *buf,int offset,time_t unixdate);
+void srv_put_dos_date2_ts(char *buf, int offset, struct timespec unix_ts);
+void srv_put_dos_date3(char *buf,int offset,time_t unixdate);
+void round_timespec(enum timestamp_set_resolution res, struct timespec *ts);
+void put_long_date_timespec(enum timestamp_set_resolution res, char *p, struct timespec ts);
+void put_long_date_full_timespec(enum timestamp_set_resolution res,
+ char *p,
+ const struct timespec *ts);
+struct timespec pull_long_date_full_timespec(const char *p);
+void put_long_date(char *p, time_t t);
+void dos_filetime_timespec(struct timespec *tsp);
+time_t make_unix_date(const void *date_ptr, int zone_offset);
+time_t make_unix_date2(const void *date_ptr, int zone_offset);
+time_t make_unix_date3(const void *date_ptr, int zone_offset);
+time_t srv_make_unix_date(const void *date_ptr);
+time_t srv_make_unix_date2(const void *date_ptr);
+time_t srv_make_unix_date3(const void *date_ptr);
+struct timespec interpret_long_date(NTTIME nt);
+void TimeInit(void);
+void get_process_uptime(struct timeval *ret_time);
+void get_startup_time(struct timeval *ret_time);
+time_t nt_time_to_unix_abs(const NTTIME *nt);
+void unix_to_nt_time_abs(NTTIME *nt, time_t t);
+const char *time_to_asc(const time_t t);
+const char *display_time(NTTIME nttime);
+bool nt_time_is_set(const NTTIME *nt);
+
+/* The following definitions come from lib/username.c */
+
+void flush_pwnam_cache(void);
+char *get_user_home_dir(TALLOC_CTX *mem_ctx, const char *user);
+struct passwd *Get_Pwnam_alloc(TALLOC_CTX *mem_ctx, const char *user);
+
+/* The following definitions come from lib/util_names.c */
+const char *get_global_sam_name(void);
+const char *my_sam_name(void);
+bool is_allowed_domain(const char *domain_name);
+
+/* The following definitions come from lib/util.c */
+
+enum protocol_types get_Protocol(void);
+void set_Protocol(enum protocol_types p);
+void gfree_all( void );
+bool file_exist_stat(const char *fname,SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times);
+bool socket_exist(const char *fname);
+uint64_t get_file_size_stat(const SMB_STRUCT_STAT *sbuf);
+bool check_same_dev_ino(const SMB_STRUCT_STAT *sbuf1,
+ const SMB_STRUCT_STAT *sbuf2);
+bool check_same_stat(const SMB_STRUCT_STAT *sbuf1,
+ const SMB_STRUCT_STAT *sbuf2);
+void show_msg(const char *buf);
+int set_message_bcc(char *buf,int num_bytes);
+ssize_t message_push_blob(uint8_t **outbuf, DATA_BLOB blob);
+char *unix_clean_name(TALLOC_CTX *ctx, const char *s);
+char *clean_name(TALLOC_CTX *ctx, const char *s);
+ssize_t write_data_at_offset(int fd, const char *buffer, size_t N, off_t pos);
+NTSTATUS init_before_fork(void);
+int parent_watch_fd(void);
+NTSTATUS reinit_after_fork(struct messaging_context *msg_ctx,
+ struct tevent_context *ev_ctx,
+ bool parent_longlived);
+NTSTATUS smbd_reinit_after_fork(struct messaging_context *msg_ctx,
+ struct tevent_context *ev_ctx,
+ bool parent_longlived);
+void *malloc_(size_t size);
+void *Realloc(void *p, size_t size, bool free_old_on_error);
+void add_to_large_array(TALLOC_CTX *mem_ctx, size_t element_size,
+ void *element, void *_array, uint32_t *num_elements,
+ ssize_t *array_size);
+char *get_myname(TALLOC_CTX *ctx);
+char *get_mydnsdomname(TALLOC_CTX *ctx);
+char *automount_lookup(TALLOC_CTX *ctx, const char *user_name);
+char *automount_lookup(TALLOC_CTX *ctx, const char *user_name);
+bool process_exists(const struct server_id pid);
+const char *uidtoname(uid_t uid);
+char *gidtoname(gid_t gid);
+uid_t nametouid(const char *name);
+gid_t nametogid(const char *name);
+void smb_panic_s3(const char *why);
+void log_panic_action(const char *msg);
+const char *readdirname(DIR *p);
+bool is_in_path(const char *name, name_compare_entry *namelist, bool case_sensitive);
+void set_namearray(name_compare_entry **ppname_array, const char *namelist);
+void free_namearray(name_compare_entry *name_array);
+bool fcntl_lock(int fd, int op, off_t offset, off_t count, int type);
+bool fcntl_getlock(int fd, int op, off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid);
+int map_process_lock_to_ofd_lock(int op);
+bool is_myname(const char *s);
+void ra_lanman_string( const char *native_lanman );
+const char *get_remote_arch_str(void);
+enum remote_arch_types get_remote_arch_from_str(const char *remote_arch_string);
+void set_remote_arch(enum remote_arch_types type);
+enum remote_arch_types get_remote_arch(void);
+bool remote_arch_cache_update(const struct GUID *client_guid);
+bool remote_arch_cache_delete(const struct GUID *client_guid);
+int str_checksum(const char *s);
+void zero_free(void *p, size_t size);
+int set_maxfiles(int requested_max);
+void *smb_xmalloc_array(size_t size, unsigned int count);
+char *myhostname(void);
+char *myhostname_upper(void);
+#include "lib/util_path.h"
+bool parent_dirname(TALLOC_CTX *mem_ctx, const char *dir, char **parent,
+ const char **name);
+bool ms_has_wild(const char *s);
+bool ms_has_wild_w(const smb_ucs2_t *s);
+bool mask_match(const char *string, const char *pattern, bool is_case_sensitive);
+bool mask_match_list(const char *string, char **list, int listLen, bool is_case_sensitive);
+#include "lib/util/unix_match.h"
+bool name_to_fqdn(fstring fqdn, const char *name);
+
+#include "lib/util_procid.h"
+
+struct server_id interpret_pid(const char *pid_string);
+bool is_offset_safe(const char *buf_base, size_t buf_len, char *ptr, size_t off);
+char *get_safe_str_ptr(const char *buf_base, size_t buf_len, char *ptr, size_t off);
+bool split_domain_user(TALLOC_CTX *mem_ctx,
+ const char *full_name,
+ char **domain,
+ char **user);
+const char *strip_hostname(const char *s);
+bool any_nt_status_not_ok(NTSTATUS err1, NTSTATUS err2, NTSTATUS *result);
+int timeval_to_msec(struct timeval t);
+char *valid_share_pathname(TALLOC_CTX *ctx, const char *dos_pathname);
+bool is_executable(const char *fname);
+bool map_open_params_to_ntcreate(const char *smb_base_fname,
+ int deny_mode, int open_func,
+ uint32_t *paccess_mask,
+ uint32_t *pshare_mode,
+ uint32_t *pcreate_disposition,
+ uint32_t *pcreate_options,
+ uint32_t *pprivate_flags);
+struct security_unix_token *copy_unix_token(TALLOC_CTX *ctx, const struct security_unix_token *tok);
+struct security_unix_token *root_unix_token(TALLOC_CTX *mem_ctx);
+char *utok_string(TALLOC_CTX *mem_ctx, const struct security_unix_token *tok);
+bool dir_check_ftype(uint32_t mode, uint32_t dirtype);
+
+/* The following definitions come from lib/util_builtin.c */
+
+bool lookup_builtin_rid(TALLOC_CTX *mem_ctx, uint32_t rid, const char **name);
+bool lookup_builtin_name(const char *name, uint32_t *rid);
+const char *builtin_domain_name(void);
+bool sid_check_is_builtin(const struct dom_sid *sid);
+bool sid_check_is_in_builtin(const struct dom_sid *sid);
+bool sid_check_is_wellknown_builtin(const struct dom_sid *sid);
+
+/* The following definitions come from lib/util_nscd.c */
+
+void smb_nscd_flush_user_cache(void);
+void smb_nscd_flush_group_cache(void);
+
+/* The following definitions come from lib/util_nttoken.c */
+
+NTSTATUS merge_with_system_token(TALLOC_CTX *mem_ctx,
+ const struct security_token *token_1,
+ struct security_token **token_out);
+bool token_sid_in_ace(const struct security_token *token, const struct security_ace *ace);
+
+/* The following definitions come from lib/util_sec.c */
+
+void sec_init(void);
+uid_t sec_initial_uid(void);
+gid_t sec_initial_gid(void);
+bool root_mode(void);
+bool non_root_mode(void);
+void gain_root_privilege(void);
+void gain_root_group_privilege(void);
+void set_effective_uid(uid_t uid);
+void set_effective_gid(gid_t gid);
+void save_re_uid(void);
+void restore_re_uid_fromroot(void);
+void restore_re_uid(void);
+void save_re_gid(void);
+void restore_re_gid(void);
+int set_re_uid(void);
+void become_user_permanently(uid_t uid, gid_t gid);
+int set_thread_credentials(uid_t uid,
+ gid_t gid,
+ size_t setlen,
+ const gid_t *gidset);
+bool is_setuid_root(void) ;
+
+/* The following definitions come from lib/util_sid.c */
+
+char *sid_to_fstring(fstring sidstr_out, const struct dom_sid *sid);
+bool sid_linearize(uint8_t *outbuf, size_t len, const struct dom_sid *sid);
+bool non_mappable_sid(struct dom_sid *sid);
+char *sid_binstring_hex_talloc(TALLOC_CTX *mem_ctx, const struct dom_sid *sid);
+struct netr_SamInfo3;
+NTSTATUS sid_array_from_info3(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo3 *info3,
+ struct dom_sid **user_sids,
+ uint32_t *num_user_sids,
+ bool include_user_group_rid);
+bool security_token_find_npa_flags(const struct security_token *token,
+ uint32_t *_flags);
+void security_token_del_npa_flags(struct security_token *token);
+
+/* The following definitions come from lib/util_sock.c */
+
+bool is_broadcast_addr(const struct sockaddr *pss);
+bool is_loopback_ip_v4(struct in_addr ip);
+bool is_loopback_addr(const struct sockaddr *pss);
+bool is_zero_addr(const struct sockaddr_storage *pss);
+void zero_ip_v4(struct in_addr *ip);
+void in_addr_to_sockaddr_storage(struct sockaddr_storage *ss,
+ struct in_addr ip);
+bool same_net(const struct sockaddr *ip1,
+ const struct sockaddr *ip2,
+ const struct sockaddr *mask);
+bool sockaddr_equal(const struct sockaddr *ip1,
+ const struct sockaddr *ip2);
+bool is_address_any(const struct sockaddr *psa);
+uint16_t get_sockaddr_port(const struct sockaddr_storage *pss);
+char *print_sockaddr(char *dest,
+ size_t destlen,
+ const struct sockaddr_storage *psa);
+char *print_canonical_sockaddr(TALLOC_CTX *ctx,
+ const struct sockaddr_storage *pss);
+bool is_a_socket(int fd);
+void set_socket_options(int fd, const char *options);
+NTSTATUS read_fd_with_timeout(int fd, char *buf,
+ size_t mincnt, size_t maxcnt,
+ unsigned int time_out,
+ size_t *size_ret);
+NTSTATUS read_data_ntstatus(int fd, char *buffer, size_t N);
+
+NTSTATUS read_smb_length_return_keepalive(int fd, char *inbuf,
+ unsigned int timeout,
+ size_t *len);
+NTSTATUS receive_smb_raw(int fd,
+ char *buffer,
+ size_t buflen,
+ unsigned int timeout,
+ size_t maxlen,
+ size_t *p_len);
+int open_socket_in(
+ int type,
+ const struct sockaddr_storage *paddr,
+ uint16_t port,
+ bool rebind);
+NTSTATUS open_socket_out(const struct sockaddr_storage *pss, uint16_t port,
+ int timeout, int *pfd);
+struct tevent_req *open_socket_out_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct sockaddr_storage *pss,
+ uint16_t port,
+ int timeout);
+NTSTATUS open_socket_out_recv(struct tevent_req *req, int *pfd);
+struct tevent_req *open_socket_out_defer_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct timeval wait_time,
+ const struct sockaddr_storage *pss,
+ uint16_t port,
+ int timeout);
+NTSTATUS open_socket_out_defer_recv(struct tevent_req *req, int *pfd);
+const char *get_peer_addr(int fd, char *addr, size_t addr_len);
+
+struct tsocket_address;
+
+int get_remote_hostname(const struct tsocket_address *remote_address,
+ char **name,
+ TALLOC_CTX *mem_ctx);
+
+int create_pipe_sock(const char *socket_dir,
+ const char *socket_name,
+ mode_t dir_perms);
+const char *get_mydnsfullname(void);
+bool is_myname_or_ipaddr(const char *s);
+int poll_one_fd(int fd, int events, int timeout, int *revents);
+int poll_intr_one_fd(int fd, int events, int timeout, int *revents);
+
+/* The following definitions come from lib/util_str.c */
+
+bool next_token(const char **ptr, char *buff, const char *sep, size_t bufsize);
+bool strnequal(const char *s1,const char *s2,size_t n);
+bool strcsequal(const char *s1,const char *s2);
+char *skip_string(const char *base, size_t len, char *buf);
+size_t str_charnum(const char *s);
+bool trim_char(char *s,char cfront,char cback);
+bool strhasupper(const char *s);
+bool strhaslower(const char *s);
+bool in_list(const char *s, const char *list, bool casesensitive);
+char *realloc_string_sub2(char *string,
+ const char *pattern,
+ const char *insert,
+ bool remove_unsafe_characters,
+ bool allow_trailing_dollar);
+char *realloc_string_sub(char *string,
+ const char *pattern,
+ const char *insert);
+void all_string_sub(char *s,const char *pattern,const char *insert, size_t len);
+char *string_truncate(char *s, unsigned int length);
+char *strchr_m(const char *src, char c);
+char *strrchr_m(const char *s, char c);
+char *strnrchr_m(const char *s, char c, unsigned int n);
+char *strstr_m(const char *src, const char *findstr);
+bool strlower_m(char *s);
+bool strupper_m(char *s);
+size_t strlen_m(const char *s);
+size_t strlen_m_term(const char *s);
+size_t strlen_m_term_null(const char *s);
+int fstr_sprintf(fstring s, const char *fmt, ...);
+
+uint64_t STR_TO_SMB_BIG_UINT(const char *nptr, const char **entptr);
+uint64_t conv_str_size(const char * str);
+char *talloc_asprintf_strupper_m(TALLOC_CTX *t, const char *fmt, ...)
+ PRINTF_ATTRIBUTE(2,3);
+char *talloc_asprintf_strlower_m(TALLOC_CTX *t, const char *fmt, ...)
+ PRINTF_ATTRIBUTE(2,3);
+bool validate_net_name( const char *name,
+ const char *invalid_chars,
+ int max_len);
+char *escape_shell_string(const char *src);
+ssize_t full_path_tos(const char *dir, const char *name,
+ char *tmpbuf, size_t tmpbuf_len,
+ char **pdst, char **to_free);
+
+/* The following definitions come from lib/version.c */
+
+const char *samba_version_string(void);
+const char *samba_copyright_string(void);
+
+/* The following definitions come from lib/wins_srv.c */
+
+bool wins_srv_is_dead(struct in_addr wins_ip, struct in_addr src_ip);
+void wins_srv_alive(struct in_addr wins_ip, struct in_addr src_ip);
+void wins_srv_died(struct in_addr wins_ip, struct in_addr src_ip);
+unsigned wins_srv_count(void);
+char **wins_srv_tags(void);
+void wins_srv_tags_free(char **list);
+struct in_addr wins_srv_ip_tag(const char *tag, struct in_addr src_ip);
+bool wins_server_tag_ips(const char *tag, TALLOC_CTX *mem_ctx,
+ struct in_addr **pservers, size_t *pnum_servers);
+unsigned wins_srv_count_tag(const char *tag);
+
+/* The following definitions come from libsmb/conncache.c */
+
+NTSTATUS check_negative_conn_cache( const char *domain, const char *server);
+void add_failed_connection_entry(const char *domain, const char *server, NTSTATUS result) ;
+void flush_negative_conn_cache_for_domain(const char *domain);
+
+/* The following definitions come from libsmb/errormap.c */
+
+NTSTATUS dos_to_ntstatus(uint8_t eclass, uint32_t ecode);
+
+/* The following definitions come from libsmb/namecache.c */
+
+bool namecache_store(const char *name,
+ int name_type,
+ size_t num_names,
+ struct samba_sockaddr *sa_list);
+bool namecache_fetch(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type,
+ struct samba_sockaddr **sa_list,
+ size_t *num_names);
+bool namecache_delete(const char *name, int name_type);
+void namecache_flush(void);
+bool namecache_status_store(const char *keyname, int keyname_type,
+ int name_type, const struct sockaddr_storage *keyip,
+ const char *srvname);
+bool namecache_status_fetch(const char *keyname,
+ int keyname_type,
+ int name_type,
+ const struct sockaddr_storage *keyip,
+ char *srvname_out);
+
+/* The following definitions come from libsmb/namequery_dc.c */
+
+bool get_dc_name(const char *domain,
+ const char *realm,
+ fstring srv_name,
+ struct sockaddr_storage *ss_out);
+
+/* The following definitions come from libsmb/smberr.c */
+
+const char *smb_dos_err_name(uint8_t e_class, uint16_t num);
+const char *get_dos_error_msg(WERROR result);
+const char *smb_dos_err_class(uint8_t e_class);
+WERROR map_werror_from_unix(int error);
+
+/* The following definitions come from libsmb/trusts_util.c */
+
+struct netlogon_creds_cli_context;
+struct messaging_context;
+struct dcerpc_binding_handle;
+char *trust_pw_new_value(TALLOC_CTX *mem_ctx,
+ enum netr_SchannelType sec_channel_type,
+ int security);
+NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *domain,
+ const char *dcname,
+ bool force);
+
+/* The following definitions come from lib/sessionid_tdb.c */
+struct sessionid;
+NTSTATUS sessionid_traverse_read(int (*fn)(const char *key,
+ struct sessionid *session,
+ void *private_data),
+ void *private_data);
+
+/* The following definitions come from lib/avahi.c */
+
+struct AvahiPoll *tevent_avahi_poll(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev);
+
+/* The following definitions come from libsmb/smbsock_connect.c */
+
+struct tevent_req *smbsock_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct sockaddr_storage *addr,
+ uint16_t port,
+ const char *called_name,
+ int called_type,
+ const char *calling_name,
+ int calling_type);
+NTSTATUS smbsock_connect_recv(struct tevent_req *req, int *sock,
+ uint16_t *ret_port);
+NTSTATUS smbsock_connect(const struct sockaddr_storage *addr, uint16_t port,
+ const char *called_name, int called_type,
+ const char *calling_name, int calling_type,
+ int *pfd, uint16_t *ret_port, int sec_timeout);
+
+struct tevent_req *smbsock_any_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct sockaddr_storage *addrs,
+ const char **called_names,
+ int *called_types,
+ const char **calling_names,
+ int *calling_types,
+ size_t num_addrs, uint16_t port);
+NTSTATUS smbsock_any_connect_recv(struct tevent_req *req, int *pfd,
+ size_t *chosen_index, uint16_t *chosen_port);
+NTSTATUS smbsock_any_connect(const struct sockaddr_storage *addrs,
+ const char **called_names,
+ int *called_types,
+ const char **calling_names,
+ int *calling_types,
+ size_t num_addrs,
+ uint16_t port,
+ int sec_timeout,
+ int *pfd, size_t *chosen_index,
+ uint16_t *chosen_port);
+
+/* The following definitions come from lib/util_wellknown.c */
+
+bool sid_check_is_wellknown_domain(const struct dom_sid *sid, const char **name);
+bool sid_check_is_in_wellknown_domain(const struct dom_sid *sid);
+bool lookup_wellknown_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ const char **domain, const char **name);
+bool lookup_wellknown_name(TALLOC_CTX *mem_ctx, const char *name,
+ struct dom_sid *sid, const char **domain);
+
+/* The following definitions come from lib/filename_util.c */
+
+NTSTATUS get_full_smb_filename(TALLOC_CTX *ctx, const struct smb_filename *smb_fname,
+ char **full_name);
+struct smb_filename *synthetic_smb_fname(TALLOC_CTX *mem_ctx,
+ const char *base_name,
+ const char *stream_name,
+ const SMB_STRUCT_STAT *psbuf,
+ NTTIME twrp,
+ uint32_t flags);
+NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
+ const char *connectpath,
+ const char *dir,
+ const char *target,
+ size_t unparsed,
+ char **_relative);
+NTSTATUS filename_convert_dirfsp(
+ TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *name_in,
+ uint32_t ucf_flags,
+ NTTIME twrp,
+ struct files_struct **pdirfsp,
+ struct smb_filename **psmb_name_rel);
+char *full_path_from_dirfsp_at_basename(TALLOC_CTX *mem_ctx,
+ const struct files_struct *dirfsp,
+ const char *at_base_name);
+struct smb_filename *full_path_from_dirfsp_atname(
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *atname);
+struct smb_filename *synthetic_smb_fname_split(TALLOC_CTX *ctx,
+ const char *fname,
+ bool posix_path);
+const char *smb_fname_str_dbg(const struct smb_filename *smb_fname);
+const char *fsp_str_dbg(const struct files_struct *fsp);
+const char *fsp_fnum_dbg(const struct files_struct *fsp);
+struct smb_filename *cp_smb_filename(TALLOC_CTX *mem_ctx,
+ const struct smb_filename *in);
+struct smb_filename *cp_smb_filename_nostream(TALLOC_CTX *mem_ctx,
+ const struct smb_filename *in);
+bool is_ntfs_stream_smb_fname(const struct smb_filename *smb_fname);
+bool is_ntfs_default_stream_smb_fname(const struct smb_filename *smb_fname);
+bool is_named_stream(const struct smb_filename *smb_fname);
+bool is_invalid_windows_ea_name(const char *name);
+bool ea_list_has_invalid_name(struct ea_list *ea_list);
+bool split_stream_filename(TALLOC_CTX *ctx,
+ const char *filename_in,
+ char **filename_out,
+ char **streamname_out);
+
+/* The following definitions come from lib/dummyroot.c */
+
+void become_root(void);
+void unbecome_root(void);
+
+/* The following definitions come from lib/smbd_shim.c */
+
+int find_service(TALLOC_CTX *ctx, const char *service_in, char **p_service_out);
+bool lp_allow_local_address(
+ int snum, const struct tsocket_address *local_address);
+bool change_to_root_user(void);
+bool become_authenticated_pipe_user(struct auth_session_info *session_info);
+bool unbecome_authenticated_pipe_user(void);
+
+void contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type);
+void contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type);
+
+/* The following definitions come from lib/per_thread_cwd.c */
+
+void per_thread_cwd_check(void);
+bool per_thread_cwd_supported(void);
+void per_thread_cwd_disable(void);
+void per_thread_cwd_activate(void);
+
+#endif /* _PROTO_H_ */
diff --git a/source3/include/registry.h b/source3/include/registry.h
new file mode 100644
index 0000000..7d2aad9
--- /dev/null
+++ b/source3/include/registry.h
@@ -0,0 +1,133 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ *
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2006-2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REGISTRY_H
+#define _REGISTRY_H
+
+#include "../librpc/gen_ndr/winreg.h"
+
+struct registry_value {
+ enum winreg_Type type;
+ DATA_BLOB data;
+};
+
+/* forward declarations. definitions in reg_objects.c */
+struct regval_ctr;
+struct regsubkey_ctr;
+
+/*
+ * container for function pointers to enumeration routines
+ * for virtual registry view
+ */
+
+struct registry_ops {
+ /* functions for enumerating subkeys and values */
+ int (*fetch_subkeys)( const char *key, struct regsubkey_ctr *subkeys);
+ int (*fetch_values) ( const char *key, struct regval_ctr *val );
+ bool (*store_subkeys)( const char *key, struct regsubkey_ctr *subkeys );
+ WERROR (*create_subkey)(const char *key, const char *subkey);
+ WERROR (*delete_subkey)(const char *key, const char *subkey, bool lazy);
+ bool (*store_values)( const char *key, struct regval_ctr *val );
+ bool (*reg_access_check)( const char *keyname, uint32_t requested,
+ uint32_t *granted,
+ const struct security_token *token );
+ WERROR (*get_secdesc)(TALLOC_CTX *mem_ctx, const char *key,
+ struct security_descriptor **psecdesc);
+ WERROR (*set_secdesc)(const char *key,
+ struct security_descriptor *sec_desc);
+ bool (*subkeys_need_update)(struct regsubkey_ctr *subkeys);
+ bool (*values_need_update)(struct regval_ctr *values);
+};
+
+/* structure to store the registry handles */
+
+struct registry_key_handle {
+ uint32_t type;
+ char *name; /* full name of registry key */
+ uint32_t access_granted;
+ struct registry_ops *ops;
+};
+
+struct registry_key {
+ struct registry_key_handle *key;
+ struct regsubkey_ctr *subkeys;
+ struct regval_ctr *values;
+ struct security_token *token;
+};
+
+
+/*
+ *
+ * Macros that used to reside in rpc_reg.h
+ *
+ */
+
+#define HKEY_CLASSES_ROOT 0x80000000
+#define HKEY_CURRENT_USER 0x80000001
+#define HKEY_LOCAL_MACHINE 0x80000002
+#define HKEY_USERS 0x80000003
+#define HKEY_PERFORMANCE_DATA 0x80000004
+
+#define KEY_HKLM "HKLM"
+#define KEY_HKU "HKU"
+#define KEY_HKCC "HKCC"
+#define KEY_HKCR "HKCR"
+#define KEY_HKPD "HKPD"
+#define KEY_HKPT "HKPT"
+#define KEY_HKPN "HKPN"
+#define KEY_HKCU "HKCU"
+#define KEY_HKDD "HKDD"
+#define KEY_SERVICES "HKLM\\SYSTEM\\CurrentControlSet\\Services"
+#define KEY_EVENTLOG "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Eventlog"
+#define KEY_SHARES "HKLM\\SYSTEM\\CurrentControlSet\\Services\\LanmanServer\\Shares"
+#define KEY_NETLOGON_PARAMS "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters"
+#define KEY_TCPIP_PARAMS "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters"
+#define KEY_PROD_OPTIONS "HKLM\\SYSTEM\\CurrentControlSet\\Control\\ProductOptions"
+#define KEY_PRINTING "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Print"
+#define KEY_PCC "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\PackageInstallation"
+#define KEY_PRINTING_2K "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print\\Printers"
+#define KEY_PRINTING_PORTS "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports"
+#define KEY_CURRENT_VERSION "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
+#define KEY_PERFLIB "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib"
+#define KEY_PERFLIB_009 "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009"
+#define KEY_GROUP_POLICY "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Group Policy"
+#define KEY_WINLOGON "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"
+#define KEY_SMBCONF "HKLM\\SOFTWARE\\Samba\\smbconf"
+#define KEY_SAMBA_GROUP_POLICY "HKLM\\SOFTWARE\\Samba\\Group Policy"
+#define KEY_TREE_ROOT ""
+
+#define KEY_GP_MACHINE_POLICY "HKLM\\Software\\Policies"
+#define KEY_GP_MACHINE_WIN_POLICY "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies"
+#define KEY_GP_USER_POLICY "HKCU\\Software\\Policies"
+#define KEY_GP_USER_WIN_POLICY "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies"
+
+/*
+ * Registry key types
+ * Most keys are going to be GENERIC -- may need a better name?
+ * HKPD and HKPT are used by reg_perfcount.c
+ * they are special keys that contain performance data
+ */
+#define REG_KEY_GENERIC 0
+#define REG_KEY_HKPD 1
+#define REG_KEY_HKPT 2
+
+#endif /* _REGISTRY_H */
diff --git a/source3/include/rpc_dce.h b/source3/include/rpc_dce.h
new file mode 100644
index 0000000..2cc770e
--- /dev/null
+++ b/source3/include/rpc_dce.h
@@ -0,0 +1,37 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1997
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1997
+ Copyright (C) Paul Ashton 1997
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _DCE_RPC_H /* _DCE_RPC_H */
+#define _DCE_RPC_H
+
+/* Maximum size of the signing data in a fragment. */
+#define RPC_MAX_SIGN_SIZE 0x38 /* 56 */
+
+/* Maximum PDU fragment size. */
+/* #define MAX_PDU_FRAG_LEN 0x1630 this is what wnt sets */
+#define RPC_MAX_PDU_FRAG_LEN 0x10b8 /* this is what w2k sets */
+
+#define RPC_HEADER_LEN 16
+
+#define RPC_BIG_ENDIAN 1
+#define RPC_LITTLE_ENDIAN 0
+
+#endif /* _DCE_RPC_H */
diff --git a/source3/include/rpc_misc.h b/source3/include/rpc_misc.h
new file mode 100644
index 0000000..06d4b49
--- /dev/null
+++ b/source3/include/rpc_misc.h
@@ -0,0 +1,35 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 1992-1997
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1997
+ Copyright (C) Paul Ashton 1997
+ Copyright (C) Gerald (Jerry) Carter 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _RPC_MISC_H /* _RPC_MISC_H */
+#define _RPC_MISC_H
+
+/**********************************************************************
+ * RPC policy handle used pretty much everywhere
+ **********************************************************************/
+
+#define OUR_HANDLE(hnd) (((hnd)==NULL) ? "NULL" :\
+ ( IVAL((hnd)->uuid.node,2) == (uint32_t)getpid() ? "OURS" : \
+ "OTHER")), ((unsigned int)IVAL((hnd)->uuid.node,2)),\
+ ((unsigned int)getpid() )
+
+#endif /* _RPC_MISC_H */
diff --git a/source3/include/secrets.h b/source3/include/secrets.h
new file mode 100644
index 0000000..47cf404
--- /dev/null
+++ b/source3/include/secrets.h
@@ -0,0 +1,185 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * secrets.tdb file format info
+ * Copyright (C) Andrew Tridgell 2000
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SECRETS_H
+#define _SECRETS_H
+
+#include "replace.h"
+#include "librpc/gen_ndr/security.h"
+
+/* the first one is for the hashed password (NT4 style) the latter
+ for plaintext (ADS)
+*/
+#define SECRETS_MACHINE_ACCT_PASS "SECRETS/$MACHINE.ACC"
+#define SECRETS_MACHINE_PASSWORD "SECRETS/MACHINE_PASSWORD"
+#define SECRETS_MACHINE_PASSWORD_PREV "SECRETS/MACHINE_PASSWORD.PREV"
+#define SECRETS_MACHINE_LAST_CHANGE_TIME "SECRETS/MACHINE_LAST_CHANGE_TIME"
+#define SECRETS_MACHINE_SEC_CHANNEL_TYPE "SECRETS/MACHINE_SEC_CHANNEL_TYPE"
+#define SECRETS_MACHINE_TRUST_ACCOUNT_NAME "SECRETS/SECRETS_MACHINE_TRUST_ACCOUNT_NAME"
+#define SECRETS_MACHINE_DOMAIN_INFO "SECRETS/MACHINE_DOMAIN_INFO"
+/* this one is for storing trusted domain account password */
+#define SECRETS_DOMTRUST_ACCT_PASS "SECRETS/$DOMTRUST.ACC"
+
+/* Store the principal name used for Kerberos DES key salt under this key name. */
+#define SECRETS_SALTING_PRINCIPAL "SECRETS/SALTING_PRINCIPAL"
+
+/* The domain sid and our sid are stored here even though they aren't
+ really secret. */
+#define SECRETS_DOMAIN_SID "SECRETS/SID"
+#define SECRETS_SAM_SID "SAM/SID"
+#define SECRETS_PROTECT_IDS "SECRETS/PROTECT/IDS"
+
+/* The domain GUID and server GUID (NOT the same) are also not secret */
+#define SECRETS_DOMAIN_GUID "SECRETS/DOMGUID"
+#define SECRETS_SERVER_GUID "SECRETS/GUID"
+
+#define SECRETS_LDAP_BIND_PW "SECRETS/LDAP_BIND_PW"
+
+#define SECRETS_LOCAL_SCHANNEL_KEY "SECRETS/LOCAL_SCHANNEL_KEY"
+
+/* Authenticated user info is stored in secrets.tdb under these keys */
+
+#define SECRETS_AUTH_USER "SECRETS/AUTH_USER"
+#define SECRETS_AUTH_DOMAIN "SECRETS/AUTH_DOMAIN"
+#define SECRETS_AUTH_PASSWORD "SECRETS/AUTH_PASSWORD"
+
+struct cli_credentials;
+
+/* structure for storing machine account password
+ (ie. when samba server is member of a domain */
+struct machine_acct_pass {
+ uint8_t hash[16];
+ time_t mod_time;
+};
+
+/*
+ * Format of an OpenAFS keyfile
+ */
+
+#define SECRETS_AFS_MAXKEYS 8
+
+struct afs_key {
+ uint32_t kvno;
+ char key[8];
+};
+
+struct afs_keyfile {
+ uint32_t nkeys;
+ struct afs_key entry[SECRETS_AFS_MAXKEYS];
+};
+
+#define SECRETS_AFS_KEYFILE "SECRETS/AFS_KEYFILE"
+
+/* The following definitions come from passdb/secrets.c */
+
+bool secrets_init_path(const char *private_dir);
+bool secrets_init(void);
+struct db_context *secrets_db_ctx(void);
+void secrets_shutdown(void);
+void *secrets_fetch(const char *key, size_t *size);
+bool secrets_store(const char *key, const void *data, size_t size);
+bool secrets_store_creds(struct cli_credentials *creds);
+bool secrets_delete_entry(const char *key);
+bool secrets_delete(const char *key);
+
+/* The following definitions come from passdb/machine_account_secrets.c */
+bool secrets_mark_domain_protected(const char *domain);
+bool secrets_clear_domain_protection(const char *domain);
+bool secrets_store_domain_sid(const char *domain, const struct dom_sid *sid);
+bool secrets_fetch_domain_sid(const char *domain, struct dom_sid *sid);
+bool secrets_store_domain_guid(const char *domain, const struct GUID *guid);
+bool secrets_fetch_domain_guid(const char *domain, struct GUID *guid);
+enum netr_SchannelType get_default_sec_channel(void);
+bool secrets_fetch_trust_account_password_legacy(const char *domain,
+ uint8_t ret_pwd[16],
+ time_t *pass_last_set_time,
+ enum netr_SchannelType *channel);
+bool secrets_fetch_trusted_domain_password(const char *domain, char** pwd,
+ struct dom_sid *sid, time_t *pass_last_set_time);
+bool secrets_store_trusted_domain_password(const char* domain, const char* pwd,
+ const struct dom_sid *sid);
+struct libnet_JoinCtx;
+NTSTATUS secrets_store_JoinCtx(const struct libnet_JoinCtx *r);
+struct secrets_domain_info1;
+struct secrets_domain_info1_change;
+void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info,
+ const char *name);
+char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
+ const char *name, bool include_secrets);
+NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
+ TALLOC_CTX *mem_ctx,
+ struct secrets_domain_info1 **pinfo);
+NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
+ const char *cleartext_unix,
+ TALLOC_CTX *mem_ctx,
+ struct secrets_domain_info1 **pinfo,
+ struct secrets_domain_info1_change **pprev);
+NTSTATUS secrets_failed_password_change(const char *change_server,
+ NTSTATUS local_status,
+ NTSTATUS remote_status,
+ const struct secrets_domain_info1 *info);
+NTSTATUS secrets_defer_password_change(const char *change_server,
+ NTSTATUS local_status,
+ NTSTATUS remote_status,
+ const struct secrets_domain_info1 *info);
+NTSTATUS secrets_finish_password_change(const char *change_server,
+ NTTIME change_time,
+ const struct secrets_domain_info1 *info);
+bool secrets_delete_machine_password_ex(const char *domain, const char *realm);
+bool secrets_delete_domain_sid(const char *domain);
+char *secrets_fetch_prev_machine_password(const char *domain);
+time_t secrets_fetch_pass_last_set_time(const char *domain);
+char *secrets_fetch_machine_password(const char *domain,
+ time_t *pass_last_set_time,
+ enum netr_SchannelType *channel);
+bool trusted_domain_password_delete(const char *domain);
+bool secrets_store_ldap_pw(const char* dn, char* pw);
+bool fetch_ldap_pw(char **dn, char** pw);
+bool secrets_store_afs_keyfile(const char *cell, const struct afs_keyfile *keyfile);
+bool secrets_fetch_afs_key(const char *cell, struct afs_key *result);
+void secrets_fetch_ipc_userpass(char **username, char **domain, char **password);
+bool secrets_store_generic(const char *owner, const char *key, const char *secret);
+char *secrets_fetch_generic(const char *owner, const char *key);
+
+bool secrets_store_machine_pw_sync(const char *pass, const char *oldpass, const char *domain,
+ const char *realm,
+ const char *salting_principal, uint32_t supported_enc_types,
+ const struct dom_sid *domain_sid, uint32_t last_change_time,
+ uint32_t secure_channel,
+ bool delete_join);
+
+char* kerberos_standard_des_salt( void );
+bool kerberos_secrets_store_des_salt( const char* salt );
+char *kerberos_secrets_fetch_salt_princ(void);
+
+/* The following definitions come from passdb/secrets_lsa.c */
+NTSTATUS lsa_secret_get(TALLOC_CTX *mem_ctx,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ NTTIME *secret_current_lastchange,
+ DATA_BLOB *secret_old,
+ NTTIME *secret_old_lastchange,
+ struct security_descriptor **sd);
+NTSTATUS lsa_secret_set(const char *secret_name,
+ DATA_BLOB *secret_current,
+ DATA_BLOB *secret_old,
+ struct security_descriptor *sd);
+NTSTATUS lsa_secret_delete(const char *secret_name);
+
+#endif /* _SECRETS_H */
diff --git a/source3/include/serverid.h b/source3/include/serverid.h
new file mode 100644
index 0000000..89487cf
--- /dev/null
+++ b/source3/include/serverid.h
@@ -0,0 +1,31 @@
+/*
+ Unix SMB/CIFS implementation.
+ Implementation of a reliable server_exists()
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SERVERID_H__
+#define __SERVERID_H__
+
+#include "replace.h"
+#include "librpc/gen_ndr/server_id.h"
+
+/*
+ * Check existence of a server id
+ */
+bool serverid_exists(const struct server_id *id);
+
+#endif
diff --git a/source3/include/session.h b/source3/include/session.h
new file mode 100644
index 0000000..40c25e5
--- /dev/null
+++ b/source3/include/session.h
@@ -0,0 +1,48 @@
+/*
+ Unix SMB/CIFS implementation.
+ session handling for recording currently vailid vuids
+
+ Copyright (C) tridge@samba.org 2001
+ Copyright (C) Andew Bartlett <abartlet@samba.org> 2001
+ Copyright (C) Gerald (Jerry) Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* a "session" is claimed when we do a SessionSetupX operation
+ and is yielded when the corresponding vuid is destroyed.
+
+ sessions are used to populate utmp and PAM session structures
+*/
+
+struct sessionid {
+ uid_t uid;
+ gid_t gid;
+ fstring username;
+ fstring hostname;
+ fstring netbios_name;
+ fstring remote_machine;
+ fstring id_str;
+ uint32_t id_num;
+ struct server_id pid;
+ fstring ip_addr_str;
+ time_t connect_start;
+ uint16_t connection_dialect;
+ uint8_t encryption_flags;
+ uint16_t cipher;
+ uint16_t signing;
+ uint8_t signing_flags;
+ const struct smbXsrv_session_global0 *global;
+};
+
diff --git a/source3/include/smb.h b/source3/include/smb.h
new file mode 100644
index 0000000..81d761d
--- /dev/null
+++ b/source3/include/smb.h
@@ -0,0 +1,714 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup, plus a whole lot more.
+
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) John H Terpstra 1996-2002
+ Copyright (C) Luke Kenneth Casson Leighton 1996-2000
+ Copyright (C) Paul Ashton 1998-2000
+ Copyright (C) Simo Sorce 2001-2002
+ Copyright (C) Martin Pool 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SMB_H
+#define _SMB_H
+
+#include "libcli/smb/smb_common.h"
+#include "libds/common/roles.h"
+
+#define LARGE_WRITEX_HDR_SIZE 65
+#define LARGE_WRITEX_BUFFER_SIZE (128*1024)
+
+#define NMB_PORT 137
+#define DGRAM_PORT 138
+#define NBT_SMB_PORT 139 /* Port for SMB over NBT transport (IETF STD#19). */
+#define TCP_SMB_PORT 445 /* Port for SMB over naked TCP transport. */
+#define SMB_PORTS "445 139"
+
+#define Undefined (-1)
+#define False false
+#define True true
+#define Auto (2)
+#define Required (3)
+
+#define SIZEOFWORD 2
+
+/* how long to wait for secondary SMB packets (milli-seconds) */
+#define SMB_SECONDARY_WAIT (60*1000)
+
+#define DIR_STRUCT_SIZE 43
+
+/* deny modes */
+#define DENY_DOS 0
+#define DENY_ALL 1
+#define DENY_WRITE 2
+#define DENY_READ 3
+#define DENY_NONE 4
+#define DENY_FCB 7
+
+/* open modes */
+#define DOS_OPEN_RDONLY 0
+#define DOS_OPEN_WRONLY 1
+#define DOS_OPEN_RDWR 2
+#define DOS_OPEN_EXEC 3
+#define DOS_OPEN_FCB 0xF
+
+/* define shifts and masks for share and open modes. */
+#define OPENX_MODE_MASK 0xF
+#define DENY_MODE_SHIFT 4
+#define DENY_MODE_MASK 0x7
+#define GET_OPENX_MODE(x) ((x) & OPENX_MODE_MASK)
+#define SET_OPENX_MODE(x) ((x) & OPENX_MODE_MASK)
+#define GET_DENY_MODE(x) (((x)>>DENY_MODE_SHIFT) & DENY_MODE_MASK)
+#define SET_DENY_MODE(x) (((x) & DENY_MODE_MASK) <<DENY_MODE_SHIFT)
+
+/* Sync on open file (not sure if used anymore... ?) */
+#define FILE_SYNC_OPENMODE (1<<14)
+#define GET_FILE_SYNC_OPENMODE(x) (((x) & FILE_SYNC_OPENMODE) ? True : False)
+
+/* open disposition values */
+#define OPENX_FILE_EXISTS_FAIL 0
+#define OPENX_FILE_EXISTS_OPEN 1
+#define OPENX_FILE_EXISTS_TRUNCATE 2
+
+/* mask for open disposition. */
+#define OPENX_FILE_OPEN_MASK 0x3
+
+#define GET_FILE_OPENX_DISPOSITION(x) ((x) & FILE_OPEN_MASK)
+#define SET_FILE_OPENX_DISPOSITION(x) ((x) & FILE_OPEN_MASK)
+
+/* The above can be OR'ed with... */
+#define OPENX_FILE_CREATE_IF_NOT_EXIST 0x10
+#define OPENX_FILE_FAIL_IF_NOT_EXIST 0
+
+/* pipe string names */
+
+#ifndef MAXSUBAUTHS
+#define MAXSUBAUTHS 15 /* max sub authorities in a SID */
+#endif
+
+#define SID_MAX_SIZE ((size_t)(8+(MAXSUBAUTHS*4)))
+
+#include "librpc/gen_ndr/security.h"
+
+struct share_mode_entry;
+struct uuid;
+struct named_mutex;
+struct wb_context;
+struct rpc_cli_smbd_conn;
+
+/* the basic packet size, assuming no words or bytes */
+#define smb_size 39
+
+struct notify_change {
+ uint32_t action;
+ const char *name;
+};
+
+struct notify_mid_map;
+struct notify_db_entry;
+struct notify_event;
+struct notify_change_request;
+struct sys_notify_context {
+ struct tevent_context *ev;
+ void *private_data; /* For use by the system backend */
+};
+
+#include "ntquotas.h"
+#include "sysquotas.h"
+
+/* Include VFS stuff */
+
+#include "smb_acls.h"
+#include "lib/readdir_attr.h"
+#include "vfs.h"
+
+struct current_user {
+ struct connection_struct *conn;
+ uint64_t vuid; /* SMB2 compat */
+ struct security_unix_token ut;
+ struct security_token *nt_user_token;
+};
+
+/* Defines for the sent_oplock_break field above. */
+#define NO_BREAK_SENT 0
+#define BREAK_TO_NONE_SENT 1
+#define LEVEL_II_BREAK_SENT 2
+
+typedef struct {
+ fstring smb_name; /* user name from the client */
+ fstring unix_name; /* unix user name of a validated user */
+ fstring domain; /* domain that the client specified */
+} userdom_struct;
+
+/* used for network interfaces */
+struct interface {
+ struct interface *next, *prev;
+ char *name;
+ int flags;
+ struct sockaddr_storage ip;
+ struct sockaddr_storage netmask;
+ struct sockaddr_storage bcast;
+ uint32_t if_index;
+ uint64_t linkspeed;
+ uint32_t capability;
+};
+
+#define SHARE_MODE_FLAG_POSIX_OPEN 0x1
+
+#include "librpc/gen_ndr/server_id.h"
+
+/* oplock break message definition - linearization of share_mode_entry.
+
+Offset Data length.
+0 struct server_id pid 4
+4 uint16_t op_mid 8
+12 uint16_t op_type 2
+14 uint32_t access_mask 4
+18 uint32_t share_access 4
+22 uint32_t private_options 4
+26 uint32_t time sec 4
+30 uint32_t time usec 4
+34 uint64_t dev 8 bytes
+42 uint64_t inode 8 bytes
+50 uint64_t extid 8 bytes
+58 unsigned long file_id 4 bytes
+62 uint32_t uid 4 bytes
+66 uint16_t flags 2 bytes
+68 uint32_t name_hash 4 bytes
+72
+
+*/
+
+#define OP_BREAK_MSG_PID_OFFSET 0
+#define OP_BREAK_MSG_MID_OFFSET 4
+#define OP_BREAK_MSG_OP_TYPE_OFFSET 12
+#define OP_BREAK_MSG_ACCESS_MASK_OFFSET 14
+#define OP_BREAK_MSG_SHARE_ACCESS_OFFSET 18
+#define OP_BREAK_MSG_PRIV_OFFSET 22
+#define OP_BREAK_MSG_TIME_SEC_OFFSET 26
+#define OP_BREAK_MSG_TIME_USEC_OFFSET 30
+#define OP_BREAK_MSG_DEV_OFFSET 34
+#define OP_BREAK_MSG_INO_OFFSET 42
+#define OP_BREAK_MSG_EXTID_OFFSET 50
+#define OP_BREAK_MSG_FILE_ID_OFFSET 58
+#define OP_BREAK_MSG_UID_OFFSET 62
+#define OP_BREAK_MSG_FLAGS_OFFSET 66
+#define OP_BREAK_MSG_NAME_HASH_OFFSET 68
+
+#define OP_BREAK_MSG_VNN_OFFSET 72
+#define MSG_SMB_SHARE_MODE_ENTRY_SIZE 76
+
+#define NT_HASH_LEN 16
+#define LM_HASH_LEN 16
+
+/* offsets into message for common items */
+#define smb_com (NBT_HDR_SIZE+HDR_COM)
+#define smb_rcls (NBT_HDR_SIZE+HDR_RCLS)
+#define smb_reh (NBT_HDR_SIZE+HDR_REH)
+#define smb_err (NBT_HDR_SIZE+HDR_ERR)
+#define smb_flg (NBT_HDR_SIZE+HDR_FLG)
+#define smb_flg2 (NBT_HDR_SIZE+HDR_FLG2)
+#define smb_pidhigh (NBT_HDR_SIZE+HDR_PIDHIGH)
+#define smb_ss_field (NBT_HDR_SIZE+HDR_SS_FIELD)
+#define smb_tid (NBT_HDR_SIZE+HDR_TID)
+#define smb_pid (NBT_HDR_SIZE+HDR_PID)
+#define smb_uid (NBT_HDR_SIZE+HDR_UID)
+#define smb_mid (NBT_HDR_SIZE+HDR_MID)
+#define smb_wct (NBT_HDR_SIZE+HDR_WCT)
+#define smb_vwv (NBT_HDR_SIZE+HDR_VWV)
+#define smb_vwv0 (smb_vwv+( 0*2))
+#define smb_vwv1 (smb_vwv+( 1*2))
+#define smb_vwv2 (smb_vwv+( 2*2))
+#define smb_vwv3 (smb_vwv+( 3*2))
+#define smb_vwv4 (smb_vwv+( 4*2))
+#define smb_vwv5 (smb_vwv+( 5*2))
+#define smb_vwv6 (smb_vwv+( 6*2))
+#define smb_vwv7 (smb_vwv+( 7*2))
+#define smb_vwv8 (smb_vwv+( 8*2))
+#define smb_vwv9 (smb_vwv+( 9*2))
+#define smb_vwv10 (smb_vwv+(10*2))
+#define smb_vwv11 (smb_vwv+(11*2))
+#define smb_vwv12 (smb_vwv+(12*2))
+#define smb_vwv13 (smb_vwv+(13*2))
+#define smb_vwv14 (smb_vwv+(14*2))
+#define smb_vwv15 (smb_vwv+(15*2))
+#define smb_vwv16 (smb_vwv+(16*2))
+#define smb_vwv17 (smb_vwv+(17*2))
+
+/* These are the NT transact_get_user_quota sub commands */
+#define TRANSACT_GET_USER_QUOTA_LIST_CONTINUE 0x0000
+#define TRANSACT_GET_USER_QUOTA_LIST_START 0x0100
+#define TRANSACT_GET_USER_QUOTA_FOR_SID 0x0101
+
+/* Relevant IOCTL codes */
+#define IOCTL_QUERY_JOB_INFO 0x530060
+
+/* these are the trans2 sub fields for primary requests */
+#define smb_tpscnt smb_vwv0
+#define smb_tdscnt smb_vwv1
+#define smb_mprcnt smb_vwv2
+#define smb_mdrcnt smb_vwv3
+#define smb_msrcnt smb_vwv4
+#define smb_flags smb_vwv5
+#define smb_timeout smb_vwv6
+#define smb_pscnt smb_vwv9
+#define smb_psoff smb_vwv10
+#define smb_dscnt smb_vwv11
+#define smb_dsoff smb_vwv12
+#define smb_suwcnt smb_vwv13
+#define smb_setup smb_vwv14
+#define smb_setup0 smb_setup
+#define smb_setup1 (smb_setup+2)
+#define smb_setup2 (smb_setup+4)
+
+/* these are for the secondary requests */
+#define smb_spscnt smb_vwv2
+#define smb_spsoff smb_vwv3
+#define smb_spsdisp smb_vwv4
+#define smb_sdscnt smb_vwv5
+#define smb_sdsoff smb_vwv6
+#define smb_sdsdisp smb_vwv7
+#define smb_sfid smb_vwv8
+
+/* and these for responses */
+#define smb_tprcnt smb_vwv0
+#define smb_tdrcnt smb_vwv1
+#define smb_prcnt smb_vwv3
+#define smb_proff smb_vwv4
+#define smb_prdisp smb_vwv5
+#define smb_drcnt smb_vwv6
+#define smb_droff smb_vwv7
+#define smb_drdisp smb_vwv8
+
+/* these are for the NT trans primary request. */
+#define smb_nt_MaxSetupCount smb_vwv0
+#define smb_nt_Flags (smb_vwv0 + 1)
+#define smb_nt_TotalParameterCount (smb_vwv0 + 3)
+#define smb_nt_TotalDataCount (smb_vwv0 + 7)
+#define smb_nt_MaxParameterCount (smb_vwv0 + 11)
+#define smb_nt_MaxDataCount (smb_vwv0 + 15)
+#define smb_nt_ParameterCount (smb_vwv0 + 19)
+#define smb_nt_ParameterOffset (smb_vwv0 + 23)
+#define smb_nt_DataCount (smb_vwv0 + 27)
+#define smb_nt_DataOffset (smb_vwv0 + 31)
+#define smb_nt_SetupCount (smb_vwv0 + 35)
+#define smb_nt_Function (smb_vwv0 + 36)
+#define smb_nt_SetupStart (smb_vwv0 + 38)
+
+/* these are for the NT trans secondary request. */
+#define smb_nts_TotalParameterCount (smb_vwv0 + 3)
+#define smb_nts_TotalDataCount (smb_vwv0 + 7)
+#define smb_nts_ParameterCount (smb_vwv0 + 11)
+#define smb_nts_ParameterOffset (smb_vwv0 + 15)
+#define smb_nts_ParameterDisplacement (smb_vwv0 + 19)
+#define smb_nts_DataCount (smb_vwv0 + 23)
+#define smb_nts_DataOffset (smb_vwv0 + 27)
+#define smb_nts_DataDisplacement (smb_vwv0 + 31)
+
+/* these are for the NT trans reply. */
+#define smb_ntr_TotalParameterCount (smb_vwv0 + 3)
+#define smb_ntr_TotalDataCount (smb_vwv0 + 7)
+#define smb_ntr_ParameterCount (smb_vwv0 + 11)
+#define smb_ntr_ParameterOffset (smb_vwv0 + 15)
+#define smb_ntr_ParameterDisplacement (smb_vwv0 + 19)
+#define smb_ntr_DataCount (smb_vwv0 + 23)
+#define smb_ntr_DataOffset (smb_vwv0 + 27)
+#define smb_ntr_DataDisplacement (smb_vwv0 + 31)
+
+/* these are for the NT create_and_X */
+#define smb_ntcreate_NameLength (smb_vwv0 + 5)
+#define smb_ntcreate_Flags (smb_vwv0 + 7)
+#define smb_ntcreate_RootDirectoryFid (smb_vwv0 + 11)
+#define smb_ntcreate_DesiredAccess (smb_vwv0 + 15)
+#define smb_ntcreate_AllocationSize (smb_vwv0 + 19)
+#define smb_ntcreate_FileAttributes (smb_vwv0 + 27)
+#define smb_ntcreate_ShareAccess (smb_vwv0 + 31)
+#define smb_ntcreate_CreateDisposition (smb_vwv0 + 35)
+#define smb_ntcreate_CreateOptions (smb_vwv0 + 39)
+#define smb_ntcreate_ImpersonationLevel (smb_vwv0 + 43)
+#define smb_ntcreate_SecurityFlags (smb_vwv0 + 47)
+
+/* Named pipe write mode flags. Used in writeX calls. */
+#define PIPE_RAW_MODE 0x4
+#define PIPE_START_MESSAGE 0x8
+
+/* the desired access to use when opening a pipe */
+#define DESIRED_ACCESS_PIPE 0x2019f
+
+/* Mapping of access rights to UNIX perms. */
+#define UNIX_ACCESS_RWX FILE_GENERIC_ALL
+#define UNIX_ACCESS_R FILE_GENERIC_READ
+#define UNIX_ACCESS_W FILE_GENERIC_WRITE
+#define UNIX_ACCESS_X FILE_GENERIC_EXECUTE
+
+/* Mapping of access rights to UNIX perms. for a UNIX directory. */
+#define UNIX_DIRECTORY_ACCESS_RWX FILE_GENERIC_ALL
+#define UNIX_DIRECTORY_ACCESS_R FILE_GENERIC_READ
+#define UNIX_DIRECTORY_ACCESS_W (FILE_GENERIC_WRITE|FILE_DELETE_CHILD)
+#define UNIX_DIRECTORY_ACCESS_X FILE_GENERIC_EXECUTE
+
+#if 0
+/*
+ * This is the old mapping we used to use. To get W2KSP2 profiles
+ * working we need to map to the canonical file perms.
+ */
+#define UNIX_ACCESS_RWX (UNIX_ACCESS_R|UNIX_ACCESS_W|UNIX_ACCESS_X)
+#define UNIX_ACCESS_R (READ_CONTROL_ACCESS|SYNCHRONIZE_ACCESS|\
+ FILE_READ_ATTRIBUTES|FILE_READ_EA|FILE_READ_DATA)
+#define UNIX_ACCESS_W (READ_CONTROL_ACCESS|SYNCHRONIZE_ACCESS|\
+ FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA|\
+ FILE_APPEND_DATA|FILE_WRITE_DATA)
+#define UNIX_ACCESS_X (READ_CONTROL_ACCESS|SYNCHRONIZE_ACCESS|\
+ FILE_EXECUTE|FILE_READ_ATTRIBUTES)
+#endif
+
+#define UNIX_ACCESS_NONE (WRITE_OWNER_ACCESS)
+
+/* Flags field. */
+#define REQUEST_OPLOCK 2
+#define REQUEST_BATCH_OPLOCK 4
+#define OPEN_DIRECTORY 8
+#define EXTENDED_RESPONSE_REQUIRED 0x10
+
+#define NTCREATEX_OPTIONS_MUST_IGNORE_MASK (0x008F0480)
+
+#define NTCREATEX_OPTIONS_INVALID_PARAM_MASK (0xFF100030)
+
+/*
+ * Private flags used by the ntcreatex processing
+ * code. Passed in the private_flags argument.
+ */
+#define NTCREATEX_FLAG_DENY_DOS 0x0001
+#define NTCREATEX_FLAG_DENY_FCB 0x0002
+
+/* Private flag for streams support */
+#define NTCREATEX_FLAG_STREAM_BASEOPEN 0x0010
+
+/* Flag for NT transact rename call. */
+#define RENAME_REPLACE_IF_EXISTS 1
+
+/* flags for SMBntrename call (from Samba4) */
+#define RENAME_FLAG_MOVE_CLUSTER_INFORMATION 0x102 /* ???? */
+#define RENAME_FLAG_HARD_LINK 0x103
+#define RENAME_FLAG_RENAME 0x104
+#define RENAME_FLAG_COPY 0x105
+
+/* ChangeNotify flags. */
+#define FILE_NOTIFY_CHANGE_FILE_NAME 0x001
+#define FILE_NOTIFY_CHANGE_DIR_NAME 0x002
+#define FILE_NOTIFY_CHANGE_ATTRIBUTES 0x004
+#define FILE_NOTIFY_CHANGE_SIZE 0x008
+#define FILE_NOTIFY_CHANGE_LAST_WRITE 0x010
+#define FILE_NOTIFY_CHANGE_LAST_ACCESS 0x020
+#define FILE_NOTIFY_CHANGE_CREATION 0x040
+#define FILE_NOTIFY_CHANGE_EA 0x080
+#define FILE_NOTIFY_CHANGE_SECURITY 0x100
+#define FILE_NOTIFY_CHANGE_STREAM_NAME 0x00000200
+#define FILE_NOTIFY_CHANGE_STREAM_SIZE 0x00000400
+#define FILE_NOTIFY_CHANGE_STREAM_WRITE 0x00000800
+
+#define FILE_NOTIFY_CHANGE_NAME \
+ (FILE_NOTIFY_CHANGE_FILE_NAME|FILE_NOTIFY_CHANGE_DIR_NAME)
+
+#define FILE_NOTIFY_CHANGE_ALL \
+ (FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | \
+ FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | \
+ FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS | \
+ FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_EA | \
+ FILE_NOTIFY_CHANGE_SECURITY | FILE_NOTIFY_CHANGE_STREAM_NAME | \
+ FILE_NOTIFY_CHANGE_STREAM_SIZE | FILE_NOTIFY_CHANGE_STREAM_WRITE)
+
+/* change notify action results */
+#define NOTIFY_ACTION_ADDED 1
+#define NOTIFY_ACTION_REMOVED 2
+#define NOTIFY_ACTION_MODIFIED 3
+#define NOTIFY_ACTION_OLD_NAME 4
+#define NOTIFY_ACTION_NEW_NAME 5
+#define NOTIFY_ACTION_ADDED_STREAM 6
+#define NOTIFY_ACTION_REMOVED_STREAM 7
+#define NOTIFY_ACTION_MODIFIED_STREAM 8
+
+/* where to find the base of the SMB packet proper */
+#define smb_base(buf) (((const char *)(buf))+4)
+
+/* we don't allow server strings to be longer than 48 characters as
+ otherwise NT will not honour the announce packets */
+#define MAX_SERVER_STRING_LENGTH 48
+
+#ifdef NOSTRDUP
+char *strdup(char *s);
+#endif
+
+#ifndef SELECT_CAST
+#define SELECT_CAST
+#endif
+
+/* This was set by JHT in liaison with Jeremy Allison early 1997
+ * History:
+ * Version 4.0 - never made public
+ * Version 4.10 - New to 1.9.16p2, lost in space 1.9.16p3 to 1.9.16p9
+ * - Reappeared in 1.9.16p11 with fixed smbd services
+ * Version 4.20 - To indicate that nmbd and browsing now works better
+ * Version 4.50 - Set at release of samba-2.2.0 by JHT
+ *
+ * Note: In the presence of NT4.X do not set above 4.9
+ * Setting this above 4.9 can have undesired side-effects.
+ * This may change again in Samba-3.0 after further testing. JHT
+ *
+ * Version 6.1 - For older smb server versions, MMC doesn't let offline
+ * settings to be configured during share creation. Changing
+ * it to 6.1 to mimic Win2K8R2.
+ *
+ */
+
+#define SAMBA_MAJOR_NBT_ANNOUNCE_VERSION 0x06
+#define SAMBA_MINOR_NBT_ANNOUNCE_VERSION 0x01
+
+/* Browser Election Values */
+#define BROWSER_ELECTION_VERSION 0x010f
+#define BROWSER_CONSTANT 0xaa55
+
+/* File Status Flags. See:
+
+http://msdn.microsoft.com/en-us/library/cc246334(PROT.13).aspx
+*/
+
+#define NO_EAS 0x1
+#define NO_SUBSTREAMS 0x2
+#define NO_REPARSETAG 0x4
+
+/* Remote architectures we know about, keep in sync with remote_arch_strings */
+enum remote_arch_types {RA_UNKNOWN, RA_WFWG, RA_OS2, RA_WIN95, RA_WINNT,
+ RA_WIN2K, RA_WINXP, RA_WIN2K3, RA_VISTA,
+ RA_SAMBA, RA_CIFSFS, RA_WINXP64, RA_OSX};
+
+/*
+ * Global value meaning that the smb_uid field should be
+ * ignored (in share level security and protocol level == CORE)
+ */
+
+#define UID_FIELD_INVALID 0
+#define VUID_OFFSET 100 /* Amount to bias returned vuid numbers */
+
+#define TID_FIELD_INVALID 0
+
+#define FNUM_FIELD_INVALID 0
+
+/*
+ * Map the Core and Extended Oplock request bits down
+ * to common bits (EXCLUSIVE_OPLOCK & BATCH_OPLOCK).
+ */
+
+/*
+ * Core protocol.
+ */
+#define CORE_OPLOCK_REQUEST(inbuf) \
+ ((CVAL(inbuf,smb_flg)&(FLAG_REQUEST_OPLOCK|FLAG_REQUEST_BATCH_OPLOCK))>>5)
+
+/*
+ * Extended protocol.
+ */
+#define EXTENDED_OPLOCK_REQUEST(inbuf) ((SVAL(inbuf,smb_vwv2)&((1<<1)|(1<<2)))>>1)
+
+/*
+ * Bits we test with.
+ * Note these must fit into 16-bits.
+ */
+
+#define NO_OPLOCK OPLOCK_NONE
+#define EXCLUSIVE_OPLOCK OPLOCK_EXCLUSIVE
+#define BATCH_OPLOCK OPLOCK_BATCH
+#define LEVEL_II_OPLOCK OPLOCK_LEVEL_II
+#define LEASE_OPLOCK 0x100
+
+/* The following are Samba-private. */
+#define INTERNAL_OPEN_ONLY 0x8
+/* #define FAKE_LEVEL_II_OPLOCK 0x10 */ /* Not used anymore */
+ /* Client requested no_oplock, but we have to
+ * inform potential level2 holders on
+ * write. */
+/* #define DEFERRED_OPEN_ENTRY 0x20 */ /* Not used anymore */
+/* #define UNUSED_SHARE_MODE_ENTRY 0x40 */ /* Not used anymore */
+/* #define FORCE_OPLOCK_BREAK_TO_NONE 0x80 */ /* Not used anymore */
+
+/* None of the following should ever appear in fsp->oplock_request. */
+#define SAMBA_PRIVATE_OPLOCK_MASK (INTERNAL_OPEN_ONLY)
+
+#define EXCLUSIVE_OPLOCK_TYPE(lck) ((lck) & ((unsigned int)EXCLUSIVE_OPLOCK|(unsigned int)BATCH_OPLOCK))
+#define BATCH_OPLOCK_TYPE(lck) ((lck) & (unsigned int)BATCH_OPLOCK)
+#define LEVEL_II_OPLOCK_TYPE(lck) ((lck) & (unsigned int)LEVEL_II_OPLOCK)
+
+/* kernel_oplock_message definition.
+
+struct kernel_oplock_message {
+ uint64_t dev;
+ uint64_t inode;
+ unit64_t extid;
+ unsigned long file_id;
+};
+
+Offset Data length.
+0 uint64_t dev 8 bytes
+8 uint64_t inode 8 bytes
+16 uint64_t extid 8 bytes
+24 unsigned long file_id 4 bytes
+28
+
+*/
+#define MSG_SMB_KERNEL_BREAK_SIZE 28
+
+/*
+ * On the wire return values for oplock types.
+ */
+
+#define CORE_OPLOCK_GRANTED (1<<5)
+#define EXTENDED_OPLOCK_GRANTED (1<<15)
+
+#define NO_OPLOCK_RETURN 0
+#define EXCLUSIVE_OPLOCK_RETURN 1
+#define BATCH_OPLOCK_RETURN 2
+#define LEVEL_II_OPLOCK_RETURN 3
+
+/* Oplock levels */
+#define OPLOCKLEVEL_NONE 0
+#define OPLOCKLEVEL_II 1
+
+/*
+ * Capabilities abstracted for different systems.
+ */
+
+enum smbd_capability {
+ KERNEL_OPLOCK_CAPABILITY,
+ DMAPI_ACCESS_CAPABILITY,
+ LEASE_CAPABILITY,
+ DAC_OVERRIDE_CAPABILITY
+};
+
+struct kernel_oplocks_ops;
+struct kernel_oplocks {
+ const struct kernel_oplocks_ops *ops;
+ void *private_data;
+};
+
+enum level2_contention_type {
+ LEVEL2_CONTEND_ALLOC_SHRINK,
+ LEVEL2_CONTEND_ALLOC_GROW,
+ LEVEL2_CONTEND_SET_FILE_LEN,
+ LEVEL2_CONTEND_FILL_SPARSE,
+ LEVEL2_CONTEND_WRITE,
+ LEVEL2_CONTEND_WINDOWS_BRL,
+ LEVEL2_CONTEND_POSIX_BRL
+};
+
+/* if a kernel does support oplocks then a structure of the following
+ typee is used to describe how to interact with the kernel */
+struct kernel_oplocks_ops {
+ bool (*set_oplock)(struct kernel_oplocks *ctx,
+ files_struct *fsp, int oplock_type);
+ void (*release_oplock)(struct kernel_oplocks *ctx,
+ files_struct *fsp, int oplock_type);
+};
+
+#include "smb_macros.h"
+
+#define MAX_NETBIOSNAME_LEN 16
+/* DOS character, NetBIOS namestring. Type used on the wire. */
+typedef char nstring[MAX_NETBIOSNAME_LEN];
+/* Unix character, NetBIOS namestring. Type used to manipulate name in nmbd. */
+typedef char unstring[MAX_NETBIOSNAME_LEN*4];
+
+/* A netbios name structure. */
+struct nmb_name {
+ nstring name;
+ char scope[64];
+ unsigned int name_type;
+};
+
+/* A netbios node status array element. */
+struct node_status {
+ nstring name;
+ unsigned char type;
+ unsigned char flags;
+};
+
+/* The extra info from a NetBIOS node status query */
+struct node_status_extra {
+ unsigned char mac_addr[6];
+ /* There really is more here ... */
+};
+
+#define SAFE_NETBIOS_CHARS ". -_"
+
+#define PORT_NONE 0
+#ifndef LDAP_PORT
+#define LDAP_PORT 389
+#endif
+#define LDAP_GC_PORT 3268
+
+struct ea_struct {
+ uint8_t flags;
+ char *name;
+ DATA_BLOB value;
+};
+
+struct ea_list {
+ struct ea_list *next, *prev;
+ struct ea_struct ea;
+};
+
+/* EA names used internally in Samba. KEEP UP TO DATE with prohibited_ea_names in trans2.c !. */
+#define SAMBA_POSIX_INHERITANCE_EA_NAME "user.SAMBA_PAI"
+/* EA to use for DOS attributes */
+#define SAMBA_XATTR_DOS_ATTRIB "user.DOSATTRIB"
+/* Prefix for DosStreams in the vfs_streams_xattr module */
+#define SAMBA_XATTR_DOSSTREAM_PREFIX "user.DosStream."
+/* Prefix for xattrs storing streams. */
+#define SAMBA_XATTR_MARKER "user.SAMBA_STREAMS"
+
+/* usershare error codes. */
+enum usershare_err {
+ USERSHARE_OK=0,
+ USERSHARE_MALFORMED_FILE,
+ USERSHARE_BAD_VERSION,
+ USERSHARE_MALFORMED_PATH,
+ USERSHARE_MALFORMED_COMMENT_DEF,
+ USERSHARE_MALFORMED_ACL_DEF,
+ USERSHARE_ACL_ERR,
+ USERSHARE_PATH_NOT_ABSOLUTE,
+ USERSHARE_PATH_IS_DENIED,
+ USERSHARE_PATH_NOT_ALLOWED,
+ USERSHARE_PATH_NOT_DIRECTORY,
+ USERSHARE_POSIX_ERR,
+ USERSHARE_MALFORMED_SHARENAME_DEF,
+ USERSHARE_BAD_SHARENAME
+};
+
+/* Different reasons for closing a file. */
+enum file_close_type {NORMAL_CLOSE=0,SHUTDOWN_CLOSE,ERROR_CLOSE};
+
+/* Used in SMB_FS_OBJECTID_INFORMATION requests. Must be exactly 48 bytes. */
+#define SAMBA_EXTENDED_INFO_MAGIC 0x536d4261 /* "SmBa" */
+#define SAMBA_EXTENDED_INFO_VERSION_STRING_LENGTH 28
+struct smb_extended_info {
+ uint32_t samba_magic; /* Always SAMBA_EXTRA_INFO_MAGIC */
+ uint32_t samba_version; /* Major/Minor/Release/Revision */
+ uint32_t samba_subversion; /* Prerelease/RC/Vendor patch */
+ NTTIME samba_gitcommitdate;
+ char samba_version_string[SAMBA_EXTENDED_INFO_VERSION_STRING_LENGTH];
+};
+
+#endif /* _SMB_H */
diff --git a/source3/include/smb_acls.h b/source3/include/smb_acls.h
new file mode 100644
index 0000000..7203dd2
--- /dev/null
+++ b/source3/include/smb_acls.h
@@ -0,0 +1,73 @@
+/*
+ Unix SMB/CIFS implementation.
+ Portable SMB ACL interface
+ Copyright (C) Jeremy Allison 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SMB_ACLS_H
+#define _SMB_ACLS_H
+
+#include "librpc/gen_ndr/smb_acl.h"
+
+struct vfs_handle_struct;
+struct files_struct;
+struct smb_filename;
+
+typedef int SMB_ACL_TYPE_T;
+/*
+ * struct smb_acl_entry is defined in IDL as
+ * using mode_t values, pidl always converts these
+ * to uint32_t. Ensure the external type definitions
+ * match.
+ */
+typedef uint32_t *SMB_ACL_PERMSET_T;
+typedef uint32_t SMB_ACL_PERM_T;
+
+typedef enum smb_acl_tag_t SMB_ACL_TAG_T;
+typedef struct smb_acl_t *SMB_ACL_T;
+
+typedef struct smb_acl_entry *SMB_ACL_ENTRY_T;
+
+/* The following definitions come from lib/sysacls.c */
+
+int sys_acl_get_entry(SMB_ACL_T acl_d, int entry_id, SMB_ACL_ENTRY_T *entry_p);
+int sys_acl_get_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *type_p);
+int sys_acl_get_permset(SMB_ACL_ENTRY_T entry_d, SMB_ACL_PERMSET_T *permset_p);
+void *sys_acl_get_qualifier(SMB_ACL_ENTRY_T entry_d);
+int sys_acl_clear_perms(SMB_ACL_PERMSET_T permset_d);
+int sys_acl_add_perm(SMB_ACL_PERMSET_T permset_d, SMB_ACL_PERM_T perm);
+int sys_acl_get_perm(SMB_ACL_PERMSET_T permset_d, SMB_ACL_PERM_T perm);
+char *sys_acl_to_text(const struct smb_acl_t *acl_d, ssize_t *len_p);
+SMB_ACL_T sys_acl_init(TALLOC_CTX *mem_ctx);
+int sys_acl_create_entry(SMB_ACL_T *acl_p, SMB_ACL_ENTRY_T *entry_p);
+int sys_acl_set_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T tag_type);
+int sys_acl_set_qualifier(SMB_ACL_ENTRY_T entry_d, void *qual_p);
+int sys_acl_set_permset(SMB_ACL_ENTRY_T entry_d, SMB_ACL_PERMSET_T permset_d);
+int sys_acl_free_text(char *text);
+int sys_acl_valid(SMB_ACL_T acl_d);
+SMB_ACL_T sys_acl_get_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+int sys_acl_set_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T acl_d);
+int sys_acl_delete_def_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp);
+int no_acl_syscall_error(int err);
+
+#endif /* _SMB_ACLS_H */
diff --git a/source3/include/smb_krb5.h b/source3/include/smb_krb5.h
new file mode 100644
index 0000000..743b67f
--- /dev/null
+++ b/source3/include/smb_krb5.h
@@ -0,0 +1,2 @@
+#include "lib/krb5_wrap/krb5_samba.h"
+#include "lib/krb5_wrap/gss_samba.h"
diff --git a/source3/include/smb_ldap.h b/source3/include/smb_ldap.h
new file mode 100644
index 0000000..5786638
--- /dev/null
+++ b/source3/include/smb_ldap.h
@@ -0,0 +1,110 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Guenther Deschner 2005
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SMB_LDAP_H
+#define _SMB_LDAP_H
+
+#ifdef HAVE_LBER_H
+#include <lber.h>
+#if defined(HPUX) && !defined(_LBER_TYPES_H)
+/* Define ber_tag_t and ber_int_t for using
+ * HP LDAP-UX Integration products' LDAP libraries.
+*/
+#ifndef ber_tag_t
+typedef unsigned long ber_tag_t;
+typedef int ber_int_t;
+#endif
+#endif /* defined(HPUX) && !defined(_LBER_TYPES_H) */
+#ifndef LBER_USE_DER
+#define LBER_USE_DER 0x01
+#endif
+#endif /* HAVE_LBER_H */
+
+#ifdef HAVE_LDAP_H
+#include <ldap.h>
+#ifndef LDAP_CONST
+#define LDAP_CONST const
+#endif
+
+#ifdef HAVE_LDAP_PVT_H
+#include <ldap_pvt.h>
+#endif /* HAVE_LDAP_PVT_H */
+
+/* Solaris 8 and maybe other LDAP implementations spell this "..._INPROGRESS": */
+#if defined(LDAP_SASL_BIND_INPROGRESS) && !defined(LDAP_SASL_BIND_IN_PROGRESS)
+#define LDAP_SASL_BIND_IN_PROGRESS LDAP_SASL_BIND_INPROGRESS
+#endif
+/* Solaris 8 defines SSL_LDAP_PORT, not LDAPS_PORT and it only does so if
+ LDAP_SSL is defined - but SSL is not working. We just want the
+ port number! Let's just define LDAPS_PORT correct. */
+#if !defined(LDAPS_PORT)
+#define LDAPS_PORT 636
+#endif
+
+#endif /* HAVE_LDAP_H */
+
+#ifndef HAVE_LDAP
+#define LDAP void
+#define LDAPMessage void
+#define LDAPMod void
+#define LDAP_CONST const
+#define LDAPControl void
+struct berval;
+struct ldapsam_privates;
+#endif /* HAVE_LDAP */
+
+#ifndef LDAP_OPT_SUCCESS
+#define LDAP_OPT_SUCCESS 0
+#endif
+
+#define LDAP_DEFAULT_TIMEOUT 15
+#define LDAP_CONNECTION_DEFAULT_TIMEOUT 2
+#define LDAP_PAGE_SIZE 1000
+
+#define ADS_PAGE_CTL_OID "1.2.840.113556.1.4.319"
+
+/*
+ * Work around versions of the LDAP client libs that don't have the OIDs
+ * defined, or have them defined under the old name.
+ * This functionality is really a factor of the server, not the client
+ *
+ */
+
+#if defined(LDAP_EXOP_X_MODIFY_PASSWD) && !defined(LDAP_EXOP_MODIFY_PASSWD)
+#define LDAP_EXOP_MODIFY_PASSWD LDAP_EXOP_X_MODIFY_PASSWD
+#elif !defined(LDAP_EXOP_MODIFY_PASSWD)
+#define LDAP_EXOP_MODIFY_PASSWD "1.3.6.1.4.1.4203.1.11.1"
+#endif
+
+#if defined(LDAP_EXOP_X_MODIFY_PASSWD_ID) && !defined(LDAP_EXOP_MODIFY_PASSWD_ID)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID LDAP_EXOP_X_MODIFY_PASSWD_ID
+#elif !defined(LDAP_EXOP_MODIFY_PASSWD_ID)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_ID ((ber_tag_t) 0x80U)
+#endif
+
+#if defined(LDAP_EXOP_X_MODIFY_PASSWD_NEW) && !defined(LDAP_EXOP_MODIFY_PASSWD_NEW)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW LDAP_EXOP_X_MODIFY_PASSWD_NEW
+#elif !defined(LDAP_EXOP_MODIFY_PASSWD_NEW)
+#define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U)
+#endif
+
+#endif /* _SMB_LDAP_H */
diff --git a/source3/include/smb_macros.h b/source3/include/smb_macros.h
new file mode 100644
index 0000000..4b3989d
--- /dev/null
+++ b/source3/include/smb_macros.h
@@ -0,0 +1,289 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1999
+ Copyright (C) John H Terpstra 1996-1999
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1999
+ Copyright (C) Paul Ashton 1998 - 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SMB_MACROS_H
+#define _SMB_MACROS_H
+
+/* Misc bit macros */
+#define BOOLSTR(b) ((b) ? "Yes" : "No")
+#define BITSETW(ptr,bit) ((SVAL(ptr,0) & (1<<(bit)))!=0)
+
+/* these are useful macros for checking validity of handles */
+#define IS_IPC(conn) ((conn) && (conn)->ipc)
+#define IS_PRINT(conn) ((conn) && (conn)->printer)
+
+#define CHECK_READ(fsp,req) \
+ ((!(fsp)->fsp_flags.is_pathref) && \
+ (fsp_get_io_fd(fsp) != -1) && \
+ (((fsp)->fsp_flags.can_read) || \
+ ((req->flags2 & FLAGS2_READ_PERMIT_EXECUTE) && \
+ (fsp->access_mask & FILE_EXECUTE))))
+
+/*
+ * This is not documented in revision 49 of [MS-SMB2] but should be added in a
+ * later revision (and torture test smb2.read.access as well as
+ * smb2.ioctl_copy_chunk_bad_access against Server 2012R2 confirms this)
+ *
+ * If FILE_EXECUTE is granted to a handle then the SMB2 server acts as if
+ * FILE_READ_DATA has also been granted. We must still keep the original granted
+ * mask, because with ioctl requests, access checks are made on the file handle,
+ * "below" the SMB2 server, and the object store below the SMB layer is not
+ * aware of this arrangement (see smb2.ioctl.copy_chunk_bad_access torture
+ * test).
+ */
+#define CHECK_READ_SMB2(fsp) \
+ ((!(fsp)->fsp_flags.is_pathref) && \
+ (fsp_get_io_fd(fsp) != -1) && \
+ (((fsp)->fsp_flags.can_read) || \
+ (fsp->access_mask & FILE_EXECUTE)))
+
+/* An IOCTL readability check (validating read access
+ * when the IOCTL code requires it)
+ * http://social.technet.microsoft.com/wiki/contents/articles/24653.decoding-io-control-codes-ioctl-fsctl-and-deviceiocodes-with-table-of-known-values.aspx
+ * ). On Windows servers, this is done by the IO manager, which is unaware of
+ * the "if execute is granted then also grant read" arrangement.
+ */
+#define CHECK_READ_IOCTL(fsp) \
+ ((!(fsp)->fsp_flags.is_pathref) && \
+ (fsp_get_io_fd(fsp) != -1) && \
+ (((fsp)->fsp_flags.can_read)))
+
+#define ERROR_WAS_LOCK_DENIED(status) (NT_STATUS_EQUAL((status), NT_STATUS_LOCK_NOT_GRANTED) || \
+ NT_STATUS_EQUAL((status), NT_STATUS_FILE_LOCK_CONFLICT) )
+
+/* the service number for the [globals] defaults */
+#define GLOBAL_SECTION_SNUM (-1)
+/* translates a connection number into a service number */
+#define SNUM(conn) ((conn)?(conn)->params->service:GLOBAL_SECTION_SNUM)
+
+
+/* access various service details */
+#define CAN_WRITE(conn) (!conn->read_only)
+#define VALID_SNUM(snum) (lp_snum_ok(snum))
+#define GUEST_OK(snum) (VALID_SNUM(snum) && lp_guest_ok(snum))
+#define GUEST_ONLY(snum) (VALID_SNUM(snum) && lp_guest_only(snum))
+#define CAN_PRINT(conn) ((conn) && lp_printable(SNUM(conn)))
+#define MAP_HIDDEN(conn) ((conn) && lp_map_hidden(SNUM(conn)))
+#define MAP_SYSTEM(conn) ((conn) && lp_map_system(SNUM(conn)))
+#define MAP_ARCHIVE(conn) ((conn) && lp_map_archive(SNUM(conn)))
+#define IS_HIDDEN_PATH(conn,path) ((conn) && is_in_path((path),(conn)->hide_list,(conn)->case_sensitive))
+#define IS_VETO_PATH(conn,path) ((conn) && is_in_path((path),(conn)->veto_list,(conn)->case_sensitive))
+#define IS_VETO_OPLOCK_PATH(conn,path) ((conn) && is_in_path((path),(conn)->veto_oplock_list,(conn)->case_sensitive))
+
+/*
+ * Used by the stat cache code to check if a returned
+ * stat structure is valid.
+ */
+
+#define VALID_STAT(st) ((st).st_ex_nlink != 0)
+#define VALID_STAT_OF_DIR(st) (VALID_STAT(st) && S_ISDIR((st).st_ex_mode))
+#define SET_STAT_INVALID(st) { \
+ (st).st_ex_nlink = 0; \
+ (st).cached_dos_attributes = FILE_ATTRIBUTE_INVALID; \
+};
+
+/* Macros to get at offsets within smb_lkrng and smb_unlkrng
+ structures. We cannot define these as actual structures
+ due to possible differences in structure packing
+ on different machines/compilers. */
+
+#define SMB_LPID_OFFSET(indx) (10 * (indx))
+#define SMB_LKOFF_OFFSET(indx) ( 2 + (10 * (indx)))
+#define SMB_LKLEN_OFFSET(indx) ( 6 + (10 * (indx)))
+#define SMB_LARGE_LPID_OFFSET(indx) (20 * (indx))
+#define SMB_LARGE_LKOFF_OFFSET_HIGH(indx) (4 + (20 * (indx)))
+#define SMB_LARGE_LKOFF_OFFSET_LOW(indx) (8 + (20 * (indx)))
+#define SMB_LARGE_LKLEN_OFFSET_HIGH(indx) (12 + (20 * (indx)))
+#define SMB_LARGE_LKLEN_OFFSET_LOW(indx) (16 + (20 * (indx)))
+
+#define ERROR_NT(status) error_packet(outbuf,0,0,status,__LINE__,__FILE__)
+#define ERROR_BOTH(status,class,code) error_packet(outbuf,class,code,status,__LINE__,__FILE__)
+
+#define reply_nterror(req,status) reply_nt_error(req,status,__LINE__,__FILE__)
+#define reply_force_doserror(req,eclass,ecode) reply_force_dos_error(req,eclass,ecode,__LINE__,__FILE__)
+#define reply_botherror(req,status,eclass,ecode) reply_both_error(req,eclass,ecode,status,__LINE__,__FILE__)
+
+#if 0
+/* defined in IDL */
+/* these are the datagram types */
+#define DGRAM_DIRECT_UNIQUE 0x10
+#endif
+
+#define SMB_ROUNDUP(x,r) ( ((x)%(r)) ? ( (((x)+(r))/(r))*(r) ) : (x))
+
+/* Extra macros added by Ying Chen at IBM - speed increase by inlining. */
+#define smb_buf(buf) (((char *)(buf)) + smb_size + CVAL(buf,smb_wct)*2)
+#define smb_buf_const(buf) (((const char *)(buf)) + smb_size + CVAL(buf,smb_wct)*2)
+#define smb_buflen(buf) (SVAL(buf,smb_vwv0 + (int)CVAL(buf, smb_wct)*2))
+
+/* the remaining number of bytes in smb buffer 'buf' from pointer 'p'. */
+#define smbreq_bufrem(req, p) ((req)->buflen < PTR_DIFF((p), (req)->buf) ? 0 : (req)->buflen - PTR_DIFF((p), (req)->buf))
+
+
+/* Note that chain_size must be available as an extern int to this macro. */
+#define smb_offset(p,buf) (PTR_DIFF(p,buf+4))
+
+#define smb_len(buf) smb_len_nbt(buf)
+#define _smb_setlen(buf, len) _smb_setlen_nbt(buf, len)
+#define smb_setlen(buf, len) smb_setlen_nbt(buf, len)
+
+#define smb_len_large(buf) smb_len_tcp(buf)
+#define _smb_setlen_large(buf, len) _smb_setlen_tcp(buf, len)
+
+#define ENCRYPTION_REQUIRED(conn) ((conn) ? ((conn)->encrypt_level == SMB_SIGNING_REQUIRED) : false)
+#define IS_CONN_ENCRYPTED(conn) ((conn) ? (conn)->encrypted_tid : false)
+
+/****************************************************************************
+ Return True if the offset is at zero.
+****************************************************************************/
+
+#define dptr_zero(buf) (IVAL(buf,1) == 0)
+
+/*******************************************************************
+copy an IP address from one buffer to another
+********************************************************************/
+
+#define putip(dest,src) memcpy(dest,src,4)
+
+/*******************************************************************
+ Return True if a server has CIFS UNIX capabilities.
+********************************************************************/
+
+#define SERVER_HAS_UNIX_CIFS(c) (smb1cli_conn_capabilities(c->conn) & CAP_UNIX)
+
+/****************************************************************************
+ Make a filename into unix format.
+****************************************************************************/
+
+#define IS_DIRECTORY_SEP(c) ((c) == '\\' || (c) == '/')
+#define unix_format(fname) string_replace(fname,'\\','/')
+
+/****************************************************************************
+ Make a file into DOS format.
+****************************************************************************/
+
+#define dos_format(fname) string_replace(fname,'/','\\')
+
+/*****************************************************************************
+ Check to see if we are a DC for this domain
+*****************************************************************************/
+
+#define IS_DC (lp_server_role()==ROLE_DOMAIN_PDC || lp_server_role()==ROLE_DOMAIN_BDC || lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC || lp_server_role() == ROLE_IPA_DC)
+#define IS_AD_DC (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC)
+
+/*
+ * If you add any entries to KERBEROS_VERIFY defines, please modify the below expressions
+ * so they remain accurate.
+ */
+#define USE_KERBEROS_KEYTAB (KERBEROS_VERIFY_SECRETS != lp_kerberos_method())
+#define USE_SYSTEM_KEYTAB \
+ ((KERBEROS_VERIFY_SECRETS_AND_KEYTAB == lp_kerberos_method()) || \
+ (KERBEROS_VERIFY_SYSTEM_KEYTAB == lp_kerberos_method()))
+
+/*****************************************************************************
+ Safe allocation macros.
+*****************************************************************************/
+
+#define SMB_MALLOC_ARRAY(type,count) (type *)malloc_array(sizeof(type),(count))
+#define SMB_MEMALIGN_ARRAY(type,align,count) (type *)memalign_array(sizeof(type),align,(count))
+#define SMB_REALLOC(p,s) Realloc((p),(s),True) /* Always frees p on error or s == 0 */
+#define SMB_REALLOC_ARRAY(p,type,count) (type *)realloc_array((p),sizeof(type),(count),True) /* Always frees p on error or s == 0 */
+#define SMB_CALLOC_ARRAY(type,count) (type *)calloc_array(sizeof(type),(count))
+#define SMB_XMALLOC_P(type) (type *)smb_xmalloc_array(sizeof(type),1)
+#define SMB_XMALLOC_ARRAY(type,count) (type *)smb_xmalloc_array(sizeof(type),(count))
+
+#define TALLOC(ctx, size) talloc_named_const(ctx, size, __location__)
+#define TALLOC_SIZE(ctx, size) talloc_named_const(ctx, size, __location__)
+#define TALLOC_REALLOC(ctx, ptr, count) _talloc_realloc(ctx, ptr, count, __location__)
+#define talloc_destroy(ctx) talloc_free(ctx)
+#ifndef TALLOC_FREE
+#define TALLOC_FREE(ctx) do { talloc_free(ctx); ctx=NULL; } while(0)
+#endif
+
+/* only define PARANOID_MALLOC_CHECKER with --enable-developer */
+
+#if defined(DEVELOPER)
+# define PARANOID_MALLOC_CHECKER 1
+#endif
+
+#if defined(PARANOID_MALLOC_CHECKER)
+
+/* Get medieval on our ass about malloc.... */
+
+/* Restrictions on malloc/realloc/calloc. */
+#ifdef malloc
+#undef malloc
+#endif
+#define malloc(s) __ERROR_DONT_USE_MALLOC_DIRECTLY
+
+#ifdef realloc
+#undef realloc
+#endif
+#define realloc(p,s) __ERROR_DONT_USE_REALLOC_DIRECTLY
+
+#ifdef calloc
+#undef calloc
+#endif
+#define calloc(n,s) __ERROR_DONT_USE_CALLOC_DIRECTLY
+
+#ifdef strndup
+#undef strndup
+#endif
+#define strndup(s,n) __ERROR_DONT_USE_STRNDUP_DIRECTLY
+
+#ifdef strdup
+#undef strdup
+#endif
+#define strdup(s) __ERROR_DONT_USE_STRDUP_DIRECTLY
+
+#define SMB_MALLOC(s) malloc_(s)
+#define SMB_MALLOC_P(type) (type *)malloc_(sizeof(type))
+
+#define SMB_STRDUP(s) smb_xstrdup(s)
+#define SMB_STRNDUP(s,n) smb_xstrndup(s,n)
+
+#else
+
+/* Regular malloc code. */
+
+#define SMB_MALLOC(s) malloc(s)
+#define SMB_MALLOC_P(type) (type *)malloc(sizeof(type))
+
+#define SMB_STRDUP(s) strdup(s)
+#define SMB_STRNDUP(s,n) strndup(s,n)
+
+#endif
+
+#define ADD_TO_ARRAY(mem_ctx, type, elem, array, num) \
+do { \
+ *(array) = ((mem_ctx) != NULL) ? \
+ talloc_realloc(mem_ctx, (*(array)), type, (*(num))+1) : \
+ SMB_REALLOC_ARRAY((*(array)), type, (*(num))+1); \
+ SMB_ASSERT((*(array)) != NULL); \
+ (*(array))[*(num)] = (elem); \
+ (*(num)) += 1; \
+} while (0)
+
+#define ADD_TO_LARGE_ARRAY(mem_ctx, type, elem, array, num, size) \
+ add_to_large_array((mem_ctx), sizeof(type), &(elem), (void *)(array), (num), (size));
+
+#endif /* _SMB_MACROS_H */
diff --git a/source3/include/smbldap.h b/source3/include/smbldap.h
new file mode 100644
index 0000000..8820d14
--- /dev/null
+++ b/source3/include/smbldap.h
@@ -0,0 +1,121 @@
+/*
+ Unix SMB/CIFS Implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Gerald Carter 2001-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _SMBLDAP_H
+#define _SMBLDAP_H
+
+#include "include/smb_ldap.h"
+
+#ifdef HAVE_LDAP
+
+#include <talloc.h>
+#include <tevent.h>
+
+/**
+ * Struct to keep the state for all the ldap stuff
+ *
+ */
+
+struct smbldap_state;
+typedef int (*smbldap_bind_callback_fn)(LDAP *ldap_struct,
+ struct smbldap_state *ldap_state,
+ void *data);
+
+/* The following definitions come from lib/smbldap.c */
+
+NTSTATUS smbldap_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *tevent_ctx,
+ const char *location,
+ bool anon,
+ const char *bind_dn,
+ const char *bind_secret,
+ struct smbldap_state **smbldap_state);
+
+LDAP *smbldap_get_ldap(struct smbldap_state *state);
+bool smbldap_get_paged_results(struct smbldap_state *state);
+void smbldap_set_paged_results(struct smbldap_state *state,
+ bool paged_results);
+
+void smbldap_set_bind_callback(struct smbldap_state *state,
+ smbldap_bind_callback_fn callback,
+ void *callback_data);
+
+void smbldap_set_mod (LDAPMod *** modlist, int modop, const char *attribute, const char *value);
+void smbldap_set_mod_blob(LDAPMod *** modlist, int modop, const char *attribute, const DATA_BLOB *newblob);
+void smbldap_make_mod(LDAP *ldap_struct, LDAPMessage *existing,
+ LDAPMod ***mods,
+ const char *attribute, const char *newval);
+void smbldap_make_mod_blob(LDAP *ldap_struct, LDAPMessage *existing,
+ LDAPMod ***mods,
+ const char *attribute, const DATA_BLOB *newblob);
+bool smbldap_get_single_attribute (LDAP * ldap_struct, LDAPMessage * entry,
+ const char *attribute, char *value,
+ int max_len);
+int smbldap_modify(struct smbldap_state *ldap_state,
+ const char *dn,
+ LDAPMod *attrs[]);
+int smbldap_start_tls(LDAP *ldap_struct, int version);
+int smbldap_start_tls_start(LDAP *ldap_struct, int version);
+int smbldap_setup_full_conn(LDAP **ldap_struct, const char *uri);
+int smbldap_search(struct smbldap_state *ldap_state,
+ const char *base, int scope, const char *filter,
+ const char *attrs[], int attrsonly,
+ LDAPMessage **res);
+int smbldap_search_paged(struct smbldap_state *ldap_state,
+ const char *base, int scope, const char *filter,
+ const char **attrs, int attrsonly, int pagesize,
+ LDAPMessage **res, void **cookie);
+int smbldap_modify(struct smbldap_state *ldap_state, const char *dn, LDAPMod *attrs[]);
+int smbldap_add(struct smbldap_state *ldap_state, const char *dn, LDAPMod *attrs[]);
+int smbldap_delete(struct smbldap_state *ldap_state, const char *dn);
+int smbldap_extended_operation(struct smbldap_state *ldap_state,
+ LDAP_CONST char *reqoid, struct berval *reqdata,
+ LDAPControl **serverctrls, LDAPControl **clientctrls,
+ char **retoidp, struct berval **retdatap);
+int smbldap_search_suffix (struct smbldap_state *ldap_state,
+ const char *filter, const char **search_attr,
+ LDAPMessage ** result);
+void smbldap_free_struct(struct smbldap_state **ldap_state) ;
+bool smbldap_has_control(LDAP *ld, const char *control);
+bool smbldap_has_extension(LDAP *ld, const char *extension);
+bool smbldap_has_naming_context(LDAP *ld, const char *naming_context);
+bool smbldap_set_creds(struct smbldap_state *ldap_state, bool anon, const char *dn, const char *secret);
+char * smbldap_talloc_single_attribute(LDAP *ldap_struct, LDAPMessage *entry,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx);
+char * smbldap_talloc_first_attribute(LDAP *ldap_struct, LDAPMessage *entry,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx);
+char * smbldap_talloc_smallest_attribute(LDAP *ldap_struct, LDAPMessage *entry,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx);
+bool smbldap_talloc_single_blob(TALLOC_CTX *mem_ctx, LDAP *ld,
+ LDAPMessage *msg, const char *attrib,
+ DATA_BLOB *blob);
+bool smbldap_pull_sid(LDAP *ld, LDAPMessage *msg, const char *attrib,
+ struct dom_sid *sid);
+void smbldap_talloc_autofree_ldapmsg(TALLOC_CTX *mem_ctx, LDAPMessage *result);
+void smbldap_talloc_autofree_ldapmod(TALLOC_CTX *mem_ctx, LDAPMod **mod);
+char *smbldap_talloc_dn(TALLOC_CTX *mem_ctx, LDAP *ld,
+ LDAPMessage *entry);
+
+#endif /* HAVE_LDAP */
+
+#endif /* _SMBLDAP_H */
diff --git a/source3/include/smbprofile.h b/source3/include/smbprofile.h
new file mode 100644
index 0000000..91af220
--- /dev/null
+++ b/source3/include/smbprofile.h
@@ -0,0 +1,623 @@
+#ifndef _PROFILE_H_
+#define _PROFILE_H_
+/*
+ Unix SMB/CIFS implementation.
+ store smbd profiling information in shared memory
+ Copyright (C) Andrew Tridgell 1999
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "replace.h"
+#include <tdb.h>
+#include "lib/util/time.h"
+
+struct tevent_context;
+
+#ifdef WITH_PROFILE
+
+#define SMBPROFILE_STATS_ALL_SECTIONS \
+ SMBPROFILE_STATS_START \
+ \
+ SMBPROFILE_STATS_SECTION_START(global, "SMBD loop") \
+ SMBPROFILE_STATS_COUNT(connect) \
+ SMBPROFILE_STATS_COUNT(disconnect) \
+ SMBPROFILE_STATS_BASIC(idle) \
+ SMBPROFILE_STATS_TIME(cpu_user) \
+ SMBPROFILE_STATS_TIME(cpu_system) \
+ SMBPROFILE_STATS_COUNT(request) \
+ SMBPROFILE_STATS_BASIC(push_sec_ctx) \
+ SMBPROFILE_STATS_BASIC(set_sec_ctx) \
+ SMBPROFILE_STATS_BASIC(set_root_sec_ctx) \
+ SMBPROFILE_STATS_BASIC(pop_sec_ctx) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_SECTION_START(syscall, "System Calls") \
+ SMBPROFILE_STATS_BASIC(syscall_opendir) \
+ SMBPROFILE_STATS_BASIC(syscall_fdopendir) \
+ SMBPROFILE_STATS_BASIC(syscall_readdir) \
+ SMBPROFILE_STATS_BASIC(syscall_rewinddir) \
+ SMBPROFILE_STATS_BASIC(syscall_mkdirat) \
+ SMBPROFILE_STATS_BASIC(syscall_closedir) \
+ SMBPROFILE_STATS_BASIC(syscall_open) \
+ SMBPROFILE_STATS_BASIC(syscall_openat) \
+ SMBPROFILE_STATS_BASIC(syscall_createfile) \
+ SMBPROFILE_STATS_BASIC(syscall_close) \
+ SMBPROFILE_STATS_BYTES(syscall_pread) \
+ SMBPROFILE_STATS_BYTES(syscall_asys_pread) \
+ SMBPROFILE_STATS_BYTES(syscall_pwrite) \
+ SMBPROFILE_STATS_BYTES(syscall_asys_pwrite) \
+ SMBPROFILE_STATS_BASIC(syscall_lseek) \
+ SMBPROFILE_STATS_BYTES(syscall_sendfile) \
+ SMBPROFILE_STATS_BYTES(syscall_recvfile) \
+ SMBPROFILE_STATS_BASIC(syscall_renameat) \
+ SMBPROFILE_STATS_BYTES(syscall_asys_fsync) \
+ SMBPROFILE_STATS_BASIC(syscall_stat) \
+ SMBPROFILE_STATS_BASIC(syscall_fstat) \
+ SMBPROFILE_STATS_BASIC(syscall_lstat) \
+ SMBPROFILE_STATS_BASIC(syscall_fstatat) \
+ SMBPROFILE_STATS_BASIC(syscall_get_alloc_size) \
+ SMBPROFILE_STATS_BASIC(syscall_unlinkat) \
+ SMBPROFILE_STATS_BASIC(syscall_chmod) \
+ SMBPROFILE_STATS_BASIC(syscall_fchmod) \
+ SMBPROFILE_STATS_BASIC(syscall_fchown) \
+ SMBPROFILE_STATS_BASIC(syscall_lchown) \
+ SMBPROFILE_STATS_BASIC(syscall_chdir) \
+ SMBPROFILE_STATS_BASIC(syscall_getwd) \
+ SMBPROFILE_STATS_BASIC(syscall_fntimes) \
+ SMBPROFILE_STATS_BASIC(syscall_ftruncate) \
+ SMBPROFILE_STATS_BASIC(syscall_fallocate) \
+ SMBPROFILE_STATS_BASIC(syscall_fcntl_lock) \
+ SMBPROFILE_STATS_BASIC(syscall_fcntl) \
+ SMBPROFILE_STATS_BASIC(syscall_linux_setlease) \
+ SMBPROFILE_STATS_BASIC(syscall_fcntl_getlock) \
+ SMBPROFILE_STATS_BASIC(syscall_readlinkat) \
+ SMBPROFILE_STATS_BASIC(syscall_symlinkat) \
+ SMBPROFILE_STATS_BASIC(syscall_linkat) \
+ SMBPROFILE_STATS_BASIC(syscall_mknodat) \
+ SMBPROFILE_STATS_BASIC(syscall_realpath) \
+ SMBPROFILE_STATS_BASIC(syscall_get_quota) \
+ SMBPROFILE_STATS_BASIC(syscall_set_quota) \
+ SMBPROFILE_STATS_BASIC(syscall_get_sd) \
+ SMBPROFILE_STATS_BASIC(syscall_set_sd) \
+ SMBPROFILE_STATS_BASIC(syscall_brl_lock) \
+ SMBPROFILE_STATS_BASIC(syscall_brl_unlock) \
+ SMBPROFILE_STATS_BASIC(syscall_brl_cancel) \
+ SMBPROFILE_STATS_BYTES(syscall_asys_getxattrat) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_SECTION_START(acl, "ACL Calls") \
+ SMBPROFILE_STATS_BASIC(get_nt_acl) \
+ SMBPROFILE_STATS_BASIC(get_nt_acl_at) \
+ SMBPROFILE_STATS_BASIC(fget_nt_acl) \
+ SMBPROFILE_STATS_BASIC(fset_nt_acl) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_SECTION_START(statcache, "Stat Cache") \
+ SMBPROFILE_STATS_COUNT(statcache_lookups) \
+ SMBPROFILE_STATS_COUNT(statcache_misses) \
+ SMBPROFILE_STATS_COUNT(statcache_hits) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_SECTION_START(SMB, "SMB Calls") \
+ SMBPROFILE_STATS_BASIC(SMBmkdir) \
+ SMBPROFILE_STATS_BASIC(SMBrmdir) \
+ SMBPROFILE_STATS_BASIC(SMBopen) \
+ SMBPROFILE_STATS_BASIC(SMBcreate) \
+ SMBPROFILE_STATS_BASIC(SMBclose) \
+ SMBPROFILE_STATS_BASIC(SMBflush) \
+ SMBPROFILE_STATS_BASIC(SMBunlink) \
+ SMBPROFILE_STATS_BASIC(SMBmv) \
+ SMBPROFILE_STATS_BASIC(SMBgetatr) \
+ SMBPROFILE_STATS_BASIC(SMBsetatr) \
+ SMBPROFILE_STATS_BASIC(SMBread) \
+ SMBPROFILE_STATS_BASIC(SMBwrite) \
+ SMBPROFILE_STATS_BASIC(SMBlock) \
+ SMBPROFILE_STATS_BASIC(SMBunlock) \
+ SMBPROFILE_STATS_BASIC(SMBctemp) \
+ SMBPROFILE_STATS_BASIC(SMBmknew) \
+ SMBPROFILE_STATS_BASIC(SMBcheckpath) \
+ SMBPROFILE_STATS_BASIC(SMBexit) \
+ SMBPROFILE_STATS_BASIC(SMBlseek) \
+ SMBPROFILE_STATS_BASIC(SMBlockread) \
+ SMBPROFILE_STATS_BASIC(SMBwriteunlock) \
+ SMBPROFILE_STATS_BASIC(SMBreadbraw) \
+ SMBPROFILE_STATS_BASIC(SMBreadBmpx) \
+ SMBPROFILE_STATS_BASIC(SMBreadBs) \
+ SMBPROFILE_STATS_BASIC(SMBwritebraw) \
+ SMBPROFILE_STATS_BASIC(SMBwriteBmpx) \
+ SMBPROFILE_STATS_BASIC(SMBwriteBs) \
+ SMBPROFILE_STATS_BASIC(SMBwritec) \
+ SMBPROFILE_STATS_BASIC(SMBsetattrE) \
+ SMBPROFILE_STATS_BASIC(SMBgetattrE) \
+ SMBPROFILE_STATS_BASIC(SMBlockingX) \
+ SMBPROFILE_STATS_BASIC(SMBtrans) \
+ SMBPROFILE_STATS_BASIC(SMBtranss) \
+ SMBPROFILE_STATS_BASIC(SMBioctl) \
+ SMBPROFILE_STATS_BASIC(SMBioctls) \
+ SMBPROFILE_STATS_BASIC(SMBcopy) \
+ SMBPROFILE_STATS_BASIC(SMBmove) \
+ SMBPROFILE_STATS_BASIC(SMBecho) \
+ SMBPROFILE_STATS_BASIC(SMBwriteclose) \
+ SMBPROFILE_STATS_BASIC(SMBopenX) \
+ SMBPROFILE_STATS_BASIC(SMBreadX) \
+ SMBPROFILE_STATS_BASIC(SMBwriteX) \
+ SMBPROFILE_STATS_BASIC(SMBtrans2) \
+ SMBPROFILE_STATS_BASIC(SMBtranss2) \
+ SMBPROFILE_STATS_BASIC(SMBfindclose) \
+ SMBPROFILE_STATS_BASIC(SMBfindnclose) \
+ SMBPROFILE_STATS_BASIC(SMBtcon) \
+ SMBPROFILE_STATS_BASIC(SMBtdis) \
+ SMBPROFILE_STATS_BASIC(SMBnegprot) \
+ SMBPROFILE_STATS_BASIC(SMBsesssetupX) \
+ SMBPROFILE_STATS_BASIC(SMBulogoffX) \
+ SMBPROFILE_STATS_BASIC(SMBtconX) \
+ SMBPROFILE_STATS_BASIC(SMBdskattr) \
+ SMBPROFILE_STATS_BASIC(SMBsearch) \
+ SMBPROFILE_STATS_BASIC(SMBffirst) \
+ SMBPROFILE_STATS_BASIC(SMBfunique) \
+ SMBPROFILE_STATS_BASIC(SMBfclose) \
+ SMBPROFILE_STATS_BASIC(SMBnttrans) \
+ SMBPROFILE_STATS_BASIC(SMBnttranss) \
+ SMBPROFILE_STATS_BASIC(SMBntcreateX) \
+ SMBPROFILE_STATS_BASIC(SMBntcancel) \
+ SMBPROFILE_STATS_BASIC(SMBntrename) \
+ SMBPROFILE_STATS_BASIC(SMBsplopen) \
+ SMBPROFILE_STATS_BASIC(SMBsplwr) \
+ SMBPROFILE_STATS_BASIC(SMBsplclose) \
+ SMBPROFILE_STATS_BASIC(SMBsplretq) \
+ SMBPROFILE_STATS_BASIC(SMBsends) \
+ SMBPROFILE_STATS_BASIC(SMBsendb) \
+ SMBPROFILE_STATS_BASIC(SMBfwdname) \
+ SMBPROFILE_STATS_BASIC(SMBcancelf) \
+ SMBPROFILE_STATS_BASIC(SMBgetmac) \
+ SMBPROFILE_STATS_BASIC(SMBsendstrt) \
+ SMBPROFILE_STATS_BASIC(SMBsendend) \
+ SMBPROFILE_STATS_BASIC(SMBsendtxt) \
+ SMBPROFILE_STATS_BASIC(SMBinvalid) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_SECTION_START(Trans2, "Trans2 Calls") \
+ SMBPROFILE_STATS_BASIC(Trans2_open) \
+ SMBPROFILE_STATS_BASIC(Trans2_findfirst) \
+ SMBPROFILE_STATS_BASIC(Trans2_findnext) \
+ SMBPROFILE_STATS_BASIC(Trans2_qfsinfo) \
+ SMBPROFILE_STATS_BASIC(Trans2_setfsinfo) \
+ SMBPROFILE_STATS_BASIC(Trans2_qpathinfo) \
+ SMBPROFILE_STATS_BASIC(Trans2_setpathinfo) \
+ SMBPROFILE_STATS_BASIC(Trans2_qfileinfo) \
+ SMBPROFILE_STATS_BASIC(Trans2_setfileinfo) \
+ SMBPROFILE_STATS_BASIC(Trans2_fsctl) \
+ SMBPROFILE_STATS_BASIC(Trans2_ioctl) \
+ SMBPROFILE_STATS_BASIC(Trans2_findnotifyfirst) \
+ SMBPROFILE_STATS_BASIC(Trans2_findnotifynext) \
+ SMBPROFILE_STATS_BASIC(Trans2_mkdir) \
+ SMBPROFILE_STATS_BASIC(Trans2_session_setup) \
+ SMBPROFILE_STATS_BASIC(Trans2_get_dfs_referral) \
+ SMBPROFILE_STATS_BASIC(Trans2_report_dfs_inconsistancy) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_SECTION_START(NT_transact, "NT Transact Calls") \
+ SMBPROFILE_STATS_BASIC(NT_transact_create) \
+ SMBPROFILE_STATS_BASIC(NT_transact_ioctl) \
+ SMBPROFILE_STATS_BASIC(NT_transact_set_security_desc) \
+ SMBPROFILE_STATS_BASIC(NT_transact_notify_change) \
+ SMBPROFILE_STATS_BASIC(NT_transact_rename) \
+ SMBPROFILE_STATS_BASIC(NT_transact_query_security_desc) \
+ SMBPROFILE_STATS_BASIC(NT_transact_get_user_quota) \
+ SMBPROFILE_STATS_BASIC(NT_transact_set_user_quota) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_SECTION_START(smb2, "SMB2 Calls") \
+ SMBPROFILE_STATS_IOBYTES(smb2_negprot) \
+ SMBPROFILE_STATS_IOBYTES(smb2_sesssetup) \
+ SMBPROFILE_STATS_IOBYTES(smb2_logoff) \
+ SMBPROFILE_STATS_IOBYTES(smb2_tcon) \
+ SMBPROFILE_STATS_IOBYTES(smb2_tdis) \
+ SMBPROFILE_STATS_IOBYTES(smb2_create) \
+ SMBPROFILE_STATS_IOBYTES(smb2_close) \
+ SMBPROFILE_STATS_IOBYTES(smb2_flush) \
+ SMBPROFILE_STATS_IOBYTES(smb2_read) \
+ SMBPROFILE_STATS_IOBYTES(smb2_write) \
+ SMBPROFILE_STATS_IOBYTES(smb2_lock) \
+ SMBPROFILE_STATS_IOBYTES(smb2_ioctl) \
+ SMBPROFILE_STATS_IOBYTES(smb2_cancel) \
+ SMBPROFILE_STATS_IOBYTES(smb2_keepalive) \
+ SMBPROFILE_STATS_IOBYTES(smb2_find) \
+ SMBPROFILE_STATS_IOBYTES(smb2_notify) \
+ SMBPROFILE_STATS_IOBYTES(smb2_getinfo) \
+ SMBPROFILE_STATS_IOBYTES(smb2_setinfo) \
+ SMBPROFILE_STATS_IOBYTES(smb2_break) \
+ SMBPROFILE_STATS_SECTION_END \
+ \
+ SMBPROFILE_STATS_END
+
+/* this file defines the profile structure in the profile shared
+ memory area */
+
+/* time values in the following structure are in microseconds */
+
+struct smbprofile_stats_count {
+ uint64_t count; /* number of events */
+};
+
+struct smbprofile_stats_time {
+ uint64_t time; /* microseconds */
+};
+
+struct smbprofile_stats_time_async {
+ uint64_t start;
+ struct smbprofile_stats_time *stats;
+};
+
+struct smbprofile_stats_basic {
+ uint64_t count; /* number of events */
+ uint64_t time; /* microseconds */
+};
+
+struct smbprofile_stats_basic_async {
+ uint64_t start;
+ struct smbprofile_stats_basic *stats;
+};
+
+struct smbprofile_stats_bytes {
+ uint64_t count; /* number of events */
+ uint64_t time; /* microseconds */
+ uint64_t idle; /* idle time compared to 'time' microseconds */
+ uint64_t bytes; /* bytes */
+};
+
+struct smbprofile_stats_bytes_async {
+ uint64_t start;
+ uint64_t idle_start;
+ uint64_t idle_time;
+ struct smbprofile_stats_bytes *stats;
+};
+
+struct smbprofile_stats_iobytes {
+ uint64_t count; /* number of events */
+ uint64_t time; /* microseconds */
+ uint64_t idle; /* idle time compared to 'time' microseconds */
+ uint64_t inbytes; /* bytes read */
+ uint64_t outbytes; /* bytes written */
+};
+
+struct smbprofile_stats_iobytes_async {
+ uint64_t start;
+ uint64_t idle_start;
+ uint64_t idle_time;
+ struct smbprofile_stats_iobytes *stats;
+};
+
+struct profile_stats {
+ uint64_t magic;
+ struct {
+#define SMBPROFILE_STATS_START
+#define SMBPROFILE_STATS_SECTION_START(name, display)
+#define SMBPROFILE_STATS_COUNT(name) \
+ struct smbprofile_stats_count name##_stats;
+#define SMBPROFILE_STATS_TIME(name) \
+ struct smbprofile_stats_time name##_stats;
+#define SMBPROFILE_STATS_BASIC(name) \
+ struct smbprofile_stats_basic name##_stats;
+#define SMBPROFILE_STATS_BYTES(name) \
+ struct smbprofile_stats_bytes name##_stats;
+#define SMBPROFILE_STATS_IOBYTES(name) \
+ struct smbprofile_stats_iobytes name##_stats;
+#define SMBPROFILE_STATS_SECTION_END
+#define SMBPROFILE_STATS_END
+ SMBPROFILE_STATS_ALL_SECTIONS
+#undef SMBPROFILE_STATS_START
+#undef SMBPROFILE_STATS_SECTION_START
+#undef SMBPROFILE_STATS_COUNT
+#undef SMBPROFILE_STATS_TIME
+#undef SMBPROFILE_STATS_BASIC
+#undef SMBPROFILE_STATS_BYTES
+#undef SMBPROFILE_STATS_IOBYTES
+#undef SMBPROFILE_STATS_SECTION_END
+#undef SMBPROFILE_STATS_END
+ } values;
+};
+
+#define _SMBPROFILE_COUNT_INCREMENT(_stats, _area, _v) do { \
+ if (smbprofile_state.config.do_count) { \
+ (_area)->values._stats.count += (_v); \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+#define SMBPROFILE_COUNT_INCREMENT(_name, _area, _v) \
+ _SMBPROFILE_COUNT_INCREMENT(_name##_stats, _area, _v)
+
+#define SMBPROFILE_TIME_ASYNC_STATE(_async_name) \
+ struct smbprofile_stats_time_async _async_name;
+#define _SMBPROFILE_TIME_ASYNC_START(_stats, _area, _async) do { \
+ (_async) = (struct smbprofile_stats_time_async) {}; \
+ if (smbprofile_state.config.do_times) { \
+ (_async).stats = &((_area)->values._stats), \
+ (_async).start = profile_timestamp(); \
+ } \
+} while(0)
+#define SMBPROFILE_TIME_ASYNC_START(_name, _area, _async) \
+ _SMBPROFILE_TIME_ASYNC_START(_name##_stats, _area, _async)
+#define SMBPROFILE_TIME_ASYNC_END(_async) do { \
+ if ((_async).start != 0) { \
+ (_async).stats->time += profile_timestamp() - (_async).start; \
+ (_async) = (struct smbprofile_stats_basic_async) {}; \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+
+#define SMBPROFILE_BASIC_ASYNC_STATE(_async_name) \
+ struct smbprofile_stats_basic_async _async_name;
+#define _SMBPROFILE_BASIC_ASYNC_START(_stats, _area, _async) do { \
+ (_async) = (struct smbprofile_stats_basic_async) {}; \
+ if (smbprofile_state.config.do_count) { \
+ if (smbprofile_state.config.do_times) { \
+ (_async).start = profile_timestamp(); \
+ (_async).stats = &((_area)->values._stats); \
+ } \
+ (_area)->values._stats.count += 1; \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+#define SMBPROFILE_BASIC_ASYNC_START(_name, _area, _async) \
+ _SMBPROFILE_BASIC_ASYNC_START(_name##_stats, _area, _async)
+#define SMBPROFILE_BASIC_ASYNC_END(_async) do { \
+ if ((_async).start != 0) { \
+ (_async).stats->time += profile_timestamp() - (_async).start; \
+ (_async) = (struct smbprofile_stats_basic_async) {}; \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+
+#define _SMBPROFILE_TIMER_ASYNC_START(_stats, _area, _async) do { \
+ (_async).stats = &((_area)->values._stats); \
+ if (smbprofile_state.config.do_times) { \
+ (_async).start = profile_timestamp(); \
+ } \
+} while(0)
+#define _SMBPROFILE_TIMER_ASYNC_SET_IDLE(_async) do { \
+ if ((_async).start != 0) { \
+ if ((_async).idle_start == 0) { \
+ (_async).idle_start = profile_timestamp(); \
+ } \
+ } \
+} while(0)
+#define _SMBPROFILE_TIMER_ASYNC_SET_BUSY(_async) do { \
+ if ((_async).idle_start != 0) { \
+ (_async).idle_time += \
+ profile_timestamp() - (_async).idle_start; \
+ (_async).idle_start = 0; \
+ } \
+} while(0)
+#define _SMBPROFILE_TIMER_ASYNC_END(_async) do { \
+ if ((_async).start != 0) { \
+ _SMBPROFILE_TIMER_ASYNC_SET_BUSY(_async); \
+ (_async).stats->time += profile_timestamp() - (_async).start; \
+ (_async).stats->idle += (_async).idle_time; \
+ } \
+} while(0)
+
+#define SMBPROFILE_BYTES_ASYNC_STATE(_async_name) \
+ struct smbprofile_stats_bytes_async _async_name;
+#define _SMBPROFILE_BYTES_ASYNC_START(_stats, _area, _async, _bytes) do { \
+ (_async) = (struct smbprofile_stats_bytes_async) {}; \
+ if (smbprofile_state.config.do_count) { \
+ _SMBPROFILE_TIMER_ASYNC_START(_stats, _area, _async); \
+ (_area)->values._stats.count += 1; \
+ (_area)->values._stats.bytes += (_bytes); \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+#define SMBPROFILE_BYTES_ASYNC_START(_name, _area, _async, _bytes) \
+ _SMBPROFILE_BYTES_ASYNC_START(_name##_stats, _area, _async, _bytes)
+#define SMBPROFILE_BYTES_ASYNC_SET_IDLE(_async) \
+ _SMBPROFILE_TIMER_ASYNC_SET_IDLE(_async)
+#define SMBPROFILE_BYTES_ASYNC_SET_BUSY(_async) \
+ _SMBPROFILE_TIMER_ASYNC_SET_BUSY(_async)
+#define SMBPROFILE_BYTES_ASYNC_END(_async) do { \
+ if ((_async).stats != NULL) { \
+ _SMBPROFILE_TIMER_ASYNC_END(_async); \
+ (_async) = (struct smbprofile_stats_bytes_async) {}; \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+
+#define SMBPROFILE_IOBYTES_ASYNC_STATE(_async_name) \
+ struct smbprofile_stats_iobytes_async _async_name;
+#define _SMBPROFILE_IOBYTES_ASYNC_START(_stats, _area, _async, _inbytes) do { \
+ (_async) = (struct smbprofile_stats_iobytes_async) {}; \
+ if (smbprofile_state.config.do_count) { \
+ _SMBPROFILE_TIMER_ASYNC_START(_stats, _area, _async); \
+ (_area)->values._stats.count += 1; \
+ (_area)->values._stats.inbytes += (_inbytes); \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+#define SMBPROFILE_IOBYTES_ASYNC_START(_name, _area, _async, _inbytes) \
+ _SMBPROFILE_IOBYTES_ASYNC_START(_name##_stats, _area, _async, _inbytes)
+#define SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(_async) \
+ _SMBPROFILE_TIMER_ASYNC_SET_IDLE(_async)
+#define SMBPROFILE_IOBYTES_ASYNC_SET_BUSY(_async) \
+ _SMBPROFILE_TIMER_ASYNC_SET_BUSY(_async)
+#define SMBPROFILE_IOBYTES_ASYNC_END(_async, _outbytes) do { \
+ if ((_async).stats != NULL) { \
+ (_async).stats->outbytes += (_outbytes); \
+ _SMBPROFILE_TIMER_ASYNC_END(_async); \
+ (_async) = (struct smbprofile_stats_iobytes_async) {}; \
+ smbprofile_dump_schedule(); \
+ } \
+} while(0)
+
+extern struct profile_stats *profile_p;
+
+struct smbprofile_global_state {
+ struct {
+ struct tdb_wrap *db;
+ struct tevent_context *ev;
+ struct tevent_timer *te;
+ } internal;
+
+ struct {
+ bool do_count;
+ bool do_times;
+ } config;
+
+ struct {
+ struct profile_stats global;
+ } stats;
+};
+
+extern struct smbprofile_global_state smbprofile_state;
+
+void smbprofile_dump_schedule_timer(void);
+void smbprofile_dump_setup(struct tevent_context *ev);
+
+static inline void smbprofile_dump_schedule(void)
+{
+ if (likely(smbprofile_state.internal.te != NULL)) {
+ return;
+ }
+
+ if (unlikely(smbprofile_state.internal.ev == NULL)) {
+ return;
+ }
+
+ smbprofile_dump_schedule_timer();
+}
+
+static inline bool smbprofile_active(void)
+{
+ return smbprofile_state.config.do_count;
+}
+
+static inline bool smbprofile_dump_pending(void)
+{
+ if (smbprofile_state.internal.te == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+void smbprofile_dump(void);
+
+void smbprofile_cleanup(pid_t pid, pid_t dst);
+void smbprofile_stats_accumulate(struct profile_stats *acc,
+ const struct profile_stats *add);
+int smbprofile_magic(const struct profile_stats *stats, uint64_t *_magic);
+void smbprofile_collect_tdb(struct tdb_context *tdb,
+ uint64_t magic,
+ struct profile_stats *stats);
+void smbprofile_collect(struct profile_stats *stats);
+
+static inline uint64_t profile_timestamp(void)
+{
+ struct timespec ts;
+
+ /* we might prefer to use the _COARSE clock variant of CLOCK_MONOTONIC
+ that one is faster but cached and "just" tick-wise precise */
+ clock_gettime_mono(&ts);
+ return (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000); /* usec */
+}
+
+#define DO_PROFILE_INC(x) \
+ _SMBPROFILE_COUNT_INCREMENT(x##_stats, profile_p, 1); \
+
+#define START_PROFILE(x) \
+ struct smbprofile_stats_basic_async __profasync_##x = {}; \
+ _SMBPROFILE_BASIC_ASYNC_START(x##_stats, profile_p, __profasync_##x);
+
+#define START_PROFILE_BYTES(x,n) \
+ struct smbprofile_stats_bytes_async __profasync_##x = {}; \
+ _SMBPROFILE_BYTES_ASYNC_START(x##_stats, profile_p, __profasync_##x, n);
+
+#define END_PROFILE(x) \
+ SMBPROFILE_BASIC_ASYNC_END(__profasync_##x)
+
+#define END_PROFILE_BYTES(x) \
+ SMBPROFILE_BYTES_ASYNC_END(__profasync_##x)
+
+#define PROFILE_TIMESTAMP(x) clock_gettime_mono(x)
+
+#else /* WITH_PROFILE */
+
+#define SMBPROFILE_COUNT_INCREMENT(_name, _area, _v)
+
+#define SMBPROFILE_TIME_ASYNC_STATE(_async_name)
+#define SMBPROFILE_TIME_ASYNC_START(_name, _area, _async)
+#define SMBPROFILE_TIME_ASYNC_END(_async)
+
+#define SMBPROFILE_BASIC_ASYNC_STATE(_async_name)
+#define SMBPROFILE_BASIC_ASYNC_START(_name, _area, _async)
+#define SMBPROFILE_BASIC_ASYNC_END(_async)
+
+#define SMBPROFILE_BYTES_ASYNC_STATE(_async_name)
+#define SMBPROFILE_BYTES_ASYNC_START(_name, _area, _async, _inbytes)
+#define SMBPROFILE_BYTES_ASYNC_SET_IDLE(_async)
+#define SMBPROFILE_BYTES_ASYNC_SET_BUSY(_async)
+#define SMBPROFILE_BYTES_ASYNC_END(_async)
+
+#define SMBPROFILE_IOBYTES_ASYNC_STATE(_async_name)
+#define SMBPROFILE_IOBYTES_ASYNC_START(_name, _area, _async, _inbytes)
+#define SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(_async)
+#define SMBPROFILE_IOBYTES_ASYNC_SET_BUSY(_async)
+#define SMBPROFILE_IOBYTES_ASYNC_END(_async, _outbytes)
+
+#define DO_PROFILE_INC(x)
+#define START_PROFILE(x)
+#define START_PROFILE_BYTES(x,n)
+#define END_PROFILE(x)
+#define END_PROFILE_BYTES(x)
+
+#define PROFILE_TIMESTAMP(x) (*(x)=(struct timespec){0})
+
+static inline bool smbprofile_active(void)
+{
+ return false;
+}
+
+static inline bool smbprofile_dump_pending(void)
+{
+ return false;
+}
+
+static inline void smbprofile_dump_setup(struct tevent_context *ev)
+{
+ return;
+}
+
+static inline void smbprofile_dump(void)
+{
+ return;
+}
+
+static inline void smbprofile_cleanup(pid_t pid, pid_t dst)
+{
+ return;
+}
+
+#endif /* WITH_PROFILE */
+
+/* The following definitions come from profile/profile.c */
+struct server_id;
+
+void set_profile_level(int level, const struct server_id *src);
+
+struct messaging_context;
+bool profile_setup(struct messaging_context *msg_ctx, bool rdonly);
+
+#endif
diff --git a/source3/include/srvstr.h b/source3/include/srvstr.h
new file mode 100644
index 0000000..2c6e7ef
--- /dev/null
+++ b/source3/include/srvstr.h
@@ -0,0 +1,21 @@
+/*
+ Unix SMB/CIFS implementation.
+ server specific string routines
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define srvstr_pull_talloc(ctx, base_ptr, smb_flags2, dest, src, src_len, flags) \
+ pull_string_talloc(ctx, base_ptr, smb_flags2, dest, src, src_len, flags)
diff --git a/source3/include/stamp-h.in b/source3/include/stamp-h.in
new file mode 100644
index 0000000..c9061b3
--- /dev/null
+++ b/source3/include/stamp-h.in
@@ -0,0 +1 @@
+Sun Jul 18 20:32:29 UTC 1999
diff --git a/source3/include/sysquotas.h b/source3/include/sysquotas.h
new file mode 100644
index 0000000..7ba631e
--- /dev/null
+++ b/source3/include/sysquotas.h
@@ -0,0 +1,85 @@
+/*
+ Unix SMB/CIFS implementation.
+ SYS QUOTA code constants
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SYSQUOTAS_H
+#define _SYSQUOTAS_H
+
+#ifdef HAVE_SYS_QUOTAS
+
+#if defined(HAVE_MNTENT_H)&&defined(HAVE_SETMNTENT)&&defined(HAVE_GETMNTENT)&&defined(HAVE_ENDMNTENT)
+#include <mntent.h>
+#define HAVE_MNTENT 1
+/*#endif defined(HAVE_MNTENT_H)&&defined(HAVE_SETMNTENT)&&defined(HAVE_GETMNTENT)&&defined(HAVE_ENDMNTENT) */
+#elif defined(HAVE_DEVNM_H)&&defined(HAVE_DEVNM)
+#include <devnm.h>
+#endif /* defined(HAVE_DEVNM_H)&&defined(HAVE_DEVNM) */
+
+#endif /* HAVE_SYS_QUOTAS */
+
+
+/**************************************************
+ Some stuff for the sys_quota api.
+ **************************************************/
+
+#define SMB_QUOTAS_NO_LIMIT ((uint64_t)(0))
+#define SMB_QUOTAS_NO_SPACE ((uint64_t)(1))
+
+#define SMB_QUOTAS_SET_NO_LIMIT(dp) \
+{\
+ (dp)->softlimit = SMB_QUOTAS_NO_LIMIT;\
+ (dp)->hardlimit = SMB_QUOTAS_NO_LIMIT;\
+ (dp)->isoftlimit = SMB_QUOTAS_NO_LIMIT;\
+ (dp)->ihardlimit = SMB_QUOTAS_NO_LIMIT;\
+}
+
+#define SMB_QUOTAS_SET_NO_SPACE(dp) \
+{\
+ (dp)->softlimit = SMB_QUOTAS_NO_SPACE;\
+ (dp)->hardlimit = SMB_QUOTAS_NO_SPACE;\
+ (dp)->isoftlimit = SMB_QUOTAS_NO_SPACE;\
+ (dp)->ihardlimit = SMB_QUOTAS_NO_SPACE;\
+}
+
+typedef struct _SMB_DISK_QUOTA {
+ enum SMB_QUOTA_TYPE qtype;
+ uint64_t bsize;
+ uint64_t hardlimit; /* In bsize units. */
+ uint64_t softlimit; /* In bsize units. */
+ uint64_t curblocks; /* In bsize units. */
+ uint64_t ihardlimit; /* inode hard limit. */
+ uint64_t isoftlimit; /* inode soft limit. */
+ uint64_t curinodes; /* Current used inodes. */
+ uint32_t qflags;
+} SMB_DISK_QUOTA;
+
+#ifndef QUOTABLOCK_SIZE
+#if defined(DQBSIZE) /* AIX */
+#define QUOTABLOCK_SIZE DQBSIZE
+#elif defined(QIF_DQBLKSIZE) /* Linux */
+#define QUOTABLOCK_SIZE QIF_DQBLKSIZE
+#elif defined(HAVE_STRUCT_DQBLK_DQB_CURBYTES) /*Darwin */
+#define QUOTABLOCK_SIZE 1
+#elif defined(HAVE_UFS_UFS_QUOTA_H) /* BSDs */
+#define QUOTABLOCK_SIZE 512
+#else
+#define QUOTABLOCK_SIZE 1024
+#endif
+#endif /* QUOTABLOCK_SIZE */
+
+#endif /*_SYSQUOTAS_H */
diff --git a/source3/include/tldap.h b/source3/include/tldap.h
new file mode 100644
index 0000000..23e3f1b
--- /dev/null
+++ b/source3/include/tldap.h
@@ -0,0 +1,315 @@
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for async ldap client requests
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TLDAP_H__
+#define __TLDAP_H__
+
+#include "replace.h"
+#include <talloc.h>
+#include <tevent.h>
+#include "lib/util/data_blob.h"
+
+struct tldap_context;
+struct tldap_message;
+
+struct tldap_control {
+ const char *oid;
+ DATA_BLOB value;
+ bool critical;
+};
+
+struct tldap_attribute {
+ char *name;
+ int num_values;
+ DATA_BLOB *values;
+};
+
+struct tldap_mod {
+ int mod_op;
+ char *attribute;
+ int num_values;
+ DATA_BLOB *values;
+};
+
+#if defined(HAVE_IMMEDIATE_STRUCTURES)
+typedef struct { uint8_t rc; } TLDAPRC;
+#define TLDAP_RC(x) ((TLDAPRC){.rc = x})
+#define TLDAP_RC_V(x) ((x).rc)
+#else
+typedef uint8_t TLDAPRC;
+#define TLDAP_RC(x) (x)
+#define TLDAP_RC_V(x) (x)
+#endif
+
+#define TLDAP_RC_EQUAL(x,y) (TLDAP_RC_V(x)==TLDAP_RC_V(y))
+#define TLDAP_RC_IS_SUCCESS(x) TLDAP_RC_EQUAL(x,TLDAP_SUCCESS)
+
+#define TLDAP_SUCCESS TLDAP_RC(0x00)
+#define TLDAP_OPERATIONS_ERROR TLDAP_RC(0x01)
+#define TLDAP_PROTOCOL_ERROR TLDAP_RC(0x02)
+#define TLDAP_TIMELIMIT_EXCEEDED TLDAP_RC(0x03)
+#define TLDAP_SIZELIMIT_EXCEEDED TLDAP_RC(0x04)
+#define TLDAP_COMPARE_FALSE TLDAP_RC(0x05)
+#define TLDAP_COMPARE_TRUE TLDAP_RC(0x06)
+#define TLDAP_STRONG_AUTH_NOT_SUPPORTED TLDAP_RC(0x07)
+#define TLDAP_STRONG_AUTH_REQUIRED TLDAP_RC(0x08)
+#define TLDAP_REFERRAL TLDAP_RC(0x0a)
+#define TLDAP_ADMINLIMIT_EXCEEDED TLDAP_RC(0x0b)
+#define TLDAP_UNAVAILABLE_CRITICAL_EXTENSION TLDAP_RC(0x0c)
+#define TLDAP_CONFIDENTIALITY_REQUIRED TLDAP_RC(0x0d)
+#define TLDAP_SASL_BIND_IN_PROGRESS TLDAP_RC(0x0e)
+#define TLDAP_NO_SUCH_ATTRIBUTE TLDAP_RC(0x10)
+#define TLDAP_UNDEFINED_TYPE TLDAP_RC(0x11)
+#define TLDAP_INAPPROPRIATE_MATCHING TLDAP_RC(0x12)
+#define TLDAP_CONSTRAINT_VIOLATION TLDAP_RC(0x13)
+#define TLDAP_TYPE_OR_VALUE_EXISTS TLDAP_RC(0x14)
+#define TLDAP_INVALID_SYNTAX TLDAP_RC(0x15)
+#define TLDAP_NO_SUCH_OBJECT TLDAP_RC(0x20)
+#define TLDAP_ALIAS_PROBLEM TLDAP_RC(0x21)
+#define TLDAP_INVALID_DN_SYNTAX TLDAP_RC(0x22)
+#define TLDAP_IS_LEAF TLDAP_RC(0x23)
+#define TLDAP_ALIAS_DEREF_PROBLEM TLDAP_RC(0x24)
+#define TLDAP_INAPPROPRIATE_AUTH TLDAP_RC(0x30)
+#define TLDAP_INVALID_CREDENTIALS TLDAP_RC(0x31)
+#define TLDAP_INSUFFICIENT_ACCESS TLDAP_RC(0x32)
+#define TLDAP_BUSY TLDAP_RC(0x33)
+#define TLDAP_UNAVAILABLE TLDAP_RC(0x34)
+#define TLDAP_UNWILLING_TO_PERFORM TLDAP_RC(0x35)
+#define TLDAP_LOOP_DETECT TLDAP_RC(0x36)
+#define TLDAP_NAMING_VIOLATION TLDAP_RC(0x40)
+#define TLDAP_OBJECT_CLASS_VIOLATION TLDAP_RC(0x41)
+#define TLDAP_NOT_ALLOWED_ON_NONLEAF TLDAP_RC(0x42)
+#define TLDAP_NOT_ALLOWED_ON_RDN TLDAP_RC(0x43)
+#define TLDAP_ALREADY_EXISTS TLDAP_RC(0x44)
+#define TLDAP_NO_OBJECT_CLASS_MODS TLDAP_RC(0x45)
+#define TLDAP_RESULTS_TOO_LARGE TLDAP_RC(0x46)
+#define TLDAP_AFFECTS_MULTIPLE_DSAS TLDAP_RC(0x47)
+#define TLDAP_OTHER TLDAP_RC(0x50)
+#define TLDAP_SERVER_DOWN TLDAP_RC(0x51)
+#define TLDAP_LOCAL_ERROR TLDAP_RC(0x52)
+#define TLDAP_ENCODING_ERROR TLDAP_RC(0x53)
+#define TLDAP_DECODING_ERROR TLDAP_RC(0x54)
+#define TLDAP_TIMEOUT TLDAP_RC(0x55)
+#define TLDAP_AUTH_UNKNOWN TLDAP_RC(0x56)
+#define TLDAP_FILTER_ERROR TLDAP_RC(0x57)
+#define TLDAP_USER_CANCELLED TLDAP_RC(0x58)
+#define TLDAP_PARAM_ERROR TLDAP_RC(0x59)
+#define TLDAP_NO_MEMORY TLDAP_RC(0x5a)
+#define TLDAP_CONNECT_ERROR TLDAP_RC(0x5b)
+#define TLDAP_NOT_SUPPORTED TLDAP_RC(0x5c)
+#define TLDAP_CONTROL_NOT_FOUND TLDAP_RC(0x5d)
+#define TLDAP_NO_RESULTS_RETURNED TLDAP_RC(0x5e)
+#define TLDAP_MORE_RESULTS_TO_RETURN TLDAP_RC(0x5f)
+#define TLDAP_CLIENT_LOOP TLDAP_RC(0x60)
+#define TLDAP_REFERRAL_LIMIT_EXCEEDED TLDAP_RC(0x61)
+
+bool tevent_req_ldap_error(struct tevent_req *req, TLDAPRC rc);
+bool tevent_req_is_ldap_error(struct tevent_req *req, TLDAPRC *perr);
+
+struct tldap_context *tldap_context_create(TALLOC_CTX *mem_ctx, int fd);
+struct tstream_context *tldap_get_tstream(struct tldap_context *ld);
+void tldap_set_tstream(struct tldap_context *ld,
+ struct tstream_context *stream);
+
+bool tldap_connection_ok(struct tldap_context *ld);
+bool tldap_context_setattr(struct tldap_context *ld,
+ const char *name, const void *pptr);
+void *tldap_context_getattr(struct tldap_context *ld, const char *name);
+
+struct tevent_req *tldap_sasl_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ const char *mechanism,
+ DATA_BLOB *creds,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls);
+TLDAPRC tldap_sasl_bind_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ DATA_BLOB *serverSaslCreds);
+TLDAPRC tldap_sasl_bind(struct tldap_context *ldap,
+ const char *dn,
+ const char *mechanism,
+ DATA_BLOB *creds,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *serverSaslCreds);
+
+struct tevent_req *tldap_simple_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ldap,
+ const char *dn,
+ const char *passwd);
+TLDAPRC tldap_simple_bind_recv(struct tevent_req *req);
+TLDAPRC tldap_simple_bind(struct tldap_context *ldap, const char *dn,
+ const char *passwd);
+
+struct tevent_req *tldap_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *base, int scope,
+ const char *filter,
+ const char **attrs,
+ int num_attrs,
+ int attrsonly,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls,
+ int timelimit,
+ int sizelimit,
+ int deref);
+TLDAPRC tldap_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct tldap_message **pmsg);
+
+struct tevent_req *tldap_search_all_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct tldap_context *ld, const char *base, int scope,
+ const char *filter, const char **attrs, int num_attrs, int attrsonly,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls,
+ int timelimit, int sizelimit, int deref);
+TLDAPRC tldap_search_all_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct tldap_message ***msgs,
+ struct tldap_message **result);
+
+TLDAPRC tldap_search(struct tldap_context *ld,
+ const char *base, int scope, const char *filter,
+ const char **attrs, int num_attrs, int attrsonly,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls,
+ int timelimit, int sizelimit, int deref,
+ TALLOC_CTX *mem_ctx, struct tldap_message ***pmsgs);
+
+bool tldap_entry_dn(struct tldap_message *msg, char **dn);
+bool tldap_entry_attributes(struct tldap_message *msg,
+ struct tldap_attribute **attributes,
+ int *num_attributes);
+
+struct tevent_req *tldap_add_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ struct tldap_mod *attributes,
+ int num_attributes,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls);
+TLDAPRC tldap_add_recv(struct tevent_req *req);
+TLDAPRC tldap_add(struct tldap_context *ld, const char *dn,
+ struct tldap_mod *attributes, int num_attributes,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls);
+
+struct tevent_req *tldap_modify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ struct tldap_mod *mods, int num_mods,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls);
+TLDAPRC tldap_modify_recv(struct tevent_req *req);
+TLDAPRC tldap_modify(struct tldap_context *ld, const char *dn,
+ struct tldap_mod *mods, int num_mods,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls);
+
+struct tevent_req *tldap_delete_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls);
+TLDAPRC tldap_delete_recv(struct tevent_req *req);
+TLDAPRC tldap_delete(struct tldap_context *ld, const char *dn,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls);
+
+int tldap_msg_id(const struct tldap_message *msg);
+int tldap_msg_type(const struct tldap_message *msg);
+const char *tldap_msg_matcheddn(struct tldap_message *msg);
+const char *tldap_msg_diagnosticmessage(struct tldap_message *msg);
+const char *tldap_msg_referral(struct tldap_message *msg);
+void tldap_msg_sctrls(struct tldap_message *msg, int *num_sctrls,
+ struct tldap_control **sctrls);
+struct tldap_message *tldap_ctx_lastmsg(struct tldap_context *ld);
+const char *tldap_rc2string(TLDAPRC rc);
+
+/* DEBUG */
+enum tldap_debug_level {
+ TLDAP_DEBUG_FATAL,
+ TLDAP_DEBUG_ERROR,
+ TLDAP_DEBUG_WARNING,
+ TLDAP_DEBUG_TRACE
+};
+
+void tldap_set_debug(struct tldap_context *ld,
+ void (*log_fn)(void *log_private,
+ enum tldap_debug_level level,
+ const char *fmt,
+ va_list ap) PRINTF_ATTRIBUTE(3,0),
+ void *log_private);
+
+/*
+ * "+ 0x60" is from ASN1_APPLICATION
+ */
+#define TLDAP_REQ_BIND (0 + 0x60)
+#define TLDAP_RES_BIND (1 + 0x60)
+#define TLDAP_REQ_UNBIND (2 + 0x60)
+#define TLDAP_REQ_SEARCH (3 + 0x60)
+#define TLDAP_RES_SEARCH_ENTRY (4 + 0x60)
+#define TLDAP_RES_SEARCH_RESULT (5 + 0x60)
+#define TLDAP_REQ_MODIFY (6 + 0x60)
+#define TLDAP_RES_MODIFY (7 + 0x60)
+#define TLDAP_REQ_ADD (8 + 0x60)
+#define TLDAP_RES_ADD (9 + 0x60)
+/* ASN1_APPLICATION_SIMPLE instead of ASN1_APPLICATION */
+#define TLDAP_REQ_DELETE (10 + 0x40)
+#define TLDAP_RES_DELETE (11 + 0x60)
+#define TLDAP_REQ_MODDN (12 + 0x60)
+#define TLDAP_RES_MODDN (13 + 0x60)
+#define TLDAP_REQ_COMPARE (14 + 0x60)
+#define TLDAP_RES_COMPARE (15 + 0x60)
+/* ASN1_APPLICATION_SIMPLE instead of ASN1_APPLICATION */
+#define TLDAP_REQ_ABANDON (16 + 0x40)
+#define TLDAP_RES_SEARCH_REFERENCE (19 + 0x60)
+#define TLDAP_REQ_EXTENDED (23 + 0x60)
+#define TLDAP_RES_EXTENDED (24 + 0x60)
+#define TLDAP_RES_INTERMEDIATE (25 + 0x60)
+
+#define TLDAP_MOD_ADD (0)
+#define TLDAP_MOD_DELETE (1)
+#define TLDAP_MOD_REPLACE (2)
+
+#define TLDAP_SCOPE_BASE (0)
+#define TLDAP_SCOPE_ONE (1)
+#define TLDAP_SCOPE_SUB (2)
+
+#define TLDAP_CONTROL_PAGEDRESULTS "1.2.840.113556.1.4.319"
+
+#endif
diff --git a/source3/include/tldap_util.h b/source3/include/tldap_util.h
new file mode 100644
index 0000000..5da0c94
--- /dev/null
+++ b/source3/include/tldap_util.h
@@ -0,0 +1,105 @@
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for async ldap client requests
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TLDAP_UTIL_H__
+#define __TLDAP_UTIL_H__
+
+#include "includes.h"
+
+bool tldap_entry_values(struct tldap_message *msg, const char *attribute,
+ DATA_BLOB **values, int *num_values);
+bool tldap_get_single_valueblob(struct tldap_message *msg,
+ const char *attribute, DATA_BLOB *blob);
+char *tldap_talloc_single_attribute(struct tldap_message *msg,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx);
+bool tldap_pull_binsid(struct tldap_message *msg, const char *attribute,
+ struct dom_sid *sid);
+bool tldap_pull_guid(struct tldap_message *msg, const char *attribute,
+ struct GUID *guid);
+bool tldap_add_mod_blobs(TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ int mod_op, const char *attrib,
+ DATA_BLOB *newvals, int num_newvals);
+bool tldap_add_mod_str(TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ int mod_op, const char *attrib, const char *str);
+bool tldap_make_mod_blob(struct tldap_message *existing, TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ const char *attrib, DATA_BLOB newval);
+bool tldap_make_mod_fmt(struct tldap_message *existing, TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ const char *attrib, const char *fmt, ...)
+ PRINTF_ATTRIBUTE(6,7);
+
+const char *tldap_errstr(TALLOC_CTX *mem_ctx, struct tldap_context *ld,
+ TLDAPRC rc);
+TLDAPRC tldap_search_va(struct tldap_context *ld, const char *base, int scope,
+ const char *attrs[], int num_attrs, int attrsonly,
+ TALLOC_CTX *mem_ctx, struct tldap_message ***res,
+ const char *fmt, va_list ap) PRINTF_ATTRIBUTE(9,0);
+TLDAPRC tldap_search_fmt(struct tldap_context *ld, const char *base, int scope,
+ const char *attrs[], int num_attrs, int attrsonly,
+ TALLOC_CTX *mem_ctx, struct tldap_message ***res,
+ const char *fmt, ...) PRINTF_ATTRIBUTE(9,10);
+bool tldap_pull_uint64(struct tldap_message *msg, const char *attr,
+ uint64_t *presult);
+bool tldap_pull_uint32(struct tldap_message *msg, const char *attr,
+ uint32_t *presult);
+
+struct tevent_req *tldap_fetch_rootdse_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld);
+TLDAPRC tldap_fetch_rootdse_recv(struct tevent_req *req);
+TLDAPRC tldap_fetch_rootdse(struct tldap_context *ld);
+struct tldap_message *tldap_rootdse(struct tldap_context *ld);
+
+bool tldap_entry_has_attrvalue(struct tldap_message *msg,
+ const char *attribute,
+ const DATA_BLOB blob);
+bool tldap_supports_control(struct tldap_context *ld, const char *oid);
+
+struct tldap_control *tldap_add_control(TALLOC_CTX *mem_ctx,
+ struct tldap_control *ctrls,
+ int num_ctrls,
+ struct tldap_control *ctrl);
+struct tldap_control *tldap_msg_findcontrol(struct tldap_message *msg,
+ const char *oid);
+
+struct tevent_req *tldap_search_paged_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *base, int scope,
+ const char *filter,
+ const char **attrs,
+ int num_attrs,
+ int attrsonly,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls,
+ int timelimit,
+ int sizelimit,
+ int deref,
+ int page_size);
+TLDAPRC tldap_search_paged_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct tldap_message **pmsg);
+
+
+#endif
diff --git a/source3/include/trans2.h b/source3/include/trans2.h
new file mode 100644
index 0000000..3ed42a3
--- /dev/null
+++ b/source3/include/trans2.h
@@ -0,0 +1,450 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+
+ Copyright (C) James Peach 2007
+ Copyright (C) Jeremy Allison 1994-2002.
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TRANS2_H_
+#define _TRANS2_H_
+
+/* Define the structures needed for the trans2 calls. */
+
+/*******************************************************
+ For DosFindFirst/DosFindNext - level 1
+
+MAXFILENAMELEN = 255;
+FDATE == uint16
+FTIME == uint16
+ULONG == uint32
+USHORT == uint16
+
+typedef struct _FILEFINDBUF {
+Byte offset Type name description
+-------------+-------+-------------------+--------------
+0 FDATE fdateCreation;
+2 FTIME ftimeCreation;
+4 FDATE fdateLastAccess;
+6 FTIME ftimeLastAccess;
+8 FDATE fdateLastWrite;
+10 FTIME ftimeLastWrite;
+12 ULONG cbFile file length in bytes
+16 ULONG cbFileAlloc size of file allocation unit
+20 USHORT attrFile
+22 UCHAR cchName length of name to follow (not including zero)
+23 UCHAR achName[MAXFILENAMELEN]; Null terminated name
+} FILEFINDBUF;
+*********************************************************/
+
+#define l1_fdateCreation 0
+#define l1_fdateLastAccess 4
+#define l1_fdateLastWrite 8
+#define l1_cbFile 12
+#define l1_cbFileAlloc 16
+#define l1_attrFile 20
+#define l1_cchName 22
+#define l1_achName 23
+
+/**********************************************************
+For DosFindFirst/DosFindNext - level 2
+
+typedef struct _FILEFINDBUF2 {
+Byte offset Type name description
+-------------+-------+-------------------+--------------
+0 FDATE fdateCreation;
+2 FTIME ftimeCreation;
+4 FDATE fdateLastAccess;
+6 FTIME ftimeLastAccess;
+8 FDATE fdateLastWrite;
+10 FTIME ftimeLastWrite;
+12 ULONG cbFile file length in bytes
+16 ULONG cbFileAlloc size of file allocation unit
+20 USHORT attrFile
+22 ULONG cbList Extended attribute list (always 0)
+26 UCHAR cchName length of name to follow (not including zero)
+27 UCHAR achName[MAXFILENAMELEN]; Null terminated name
+} FILEFINDBUF2;
+*************************************************************/
+
+#define l2_fdateCreation 0
+#define l2_fdateLastAccess 4
+#define l2_fdateLastWrite 8
+#define l2_cbFile 12
+#define l2_cbFileAlloc 16
+#define l2_attrFile 20
+#define l2_cbList 22
+#define l2_cchName 26
+#define l2_achName 27
+
+
+/**********************************************************
+For DosFindFirst/DosFindNext - level 260
+
+typedef struct _FILEFINDBUF260 {
+Byte offset Type name description
+-------------+-------+-------------------+--------------
+0 ULONG NextEntryOffset;
+4 ULONG FileIndex;
+8 LARGE_INTEGER CreationTime;
+16 LARGE_INTEGER LastAccessTime;
+24 LARGE_INTEGER LastWriteTime;
+32 LARGE_INTEGER ChangeTime;
+40 LARGE_INTEGER EndOfFile;
+48 LARGE_INTEGER AllocationSize;
+56 ULONG FileAttributes;
+60 ULONG FileNameLength;
+64 ULONG EaSize;
+68 CHAR ShortNameLength;
+70 UNICODE ShortName[12];
+94 UNICODE FileName[];
+*************************************************************/
+
+#define l260_achName 94
+
+
+/**********************************************************
+For DosQueryPathInfo/DosQueryFileInfo/DosSetPathInfo/
+DosSetFileInfo - level 1
+
+typedef struct _FILESTATUS {
+Byte offset Type name description
+-------------+-------+-------------------+--------------
+0 FDATE fdateCreation;
+2 FTIME ftimeCreation;
+4 FDATE fdateLastAccess;
+6 FTIME ftimeLastAccess;
+8 FDATE fdateLastWrite;
+10 FTIME ftimeLastWrite;
+12 ULONG cbFile file length in bytes
+16 ULONG cbFileAlloc size of file allocation unit
+20 USHORT attrFile
+} FILESTATUS;
+*************************************************************/
+
+/* Use the l1_ defines from DosFindFirst */
+
+/**********************************************************
+For DosQueryPathInfo/DosQueryFileInfo/DosSetPathInfo/
+DosSetFileInfo - level 2
+
+typedef struct _FILESTATUS2 {
+Byte offset Type name description
+-------------+-------+-------------------+--------------
+0 FDATE fdateCreation;
+2 FTIME ftimeCreation;
+4 FDATE fdateLastAccess;
+6 FTIME ftimeLastAccess;
+8 FDATE fdateLastWrite;
+10 FTIME ftimeLastWrite;
+12 ULONG cbFile file length in bytes
+16 ULONG cbFileAlloc size of file allocation unit
+20 USHORT attrFile
+22 ULONG cbList Length of EA's (0)
+} FILESTATUS2;
+*************************************************************/
+
+/* Use the l2_ #defines from DosFindFirst */
+
+/**********************************************************
+For DosQFSInfo/DosSetFSInfo - level 1
+
+typedef struct _FSALLOCATE {
+Byte offset Type name description
+-------------+-------+-------------------+--------------
+0 ULONG idFileSystem id of file system
+4 ULONG cSectorUnit number of sectors per allocation unit
+8 ULONG cUnit number of allocation units
+12 ULONG cUnitAvail Available allocation units
+16 USHORT cbSector bytes per sector
+} FSALLOCATE;
+*************************************************************/
+
+#define l1_idFileSystem 0
+#define l1_cSectorUnit 4
+#define l1_cUnit 8
+#define l1_cUnitAvail 12
+#define l1_cbSector 16
+
+/**********************************************************
+For DosQFSInfo/DosSetFSInfo - level 2
+
+typedef struct _FSINFO {
+Byte offset Type name description
+-------------+-------+-------------------+--------------
+0 FDATE vol_fdateCreation
+2 FTIME vol_ftimeCreation
+4 UCHAR vol_cch length of volume name (excluding NULL)
+5 UCHAR vol_szVolLabel[12] volume name
+} FSINFO;
+*************************************************************/
+
+#define SMB_INFO_STANDARD 1 /* FILESTATUS3 struct */
+#define SMB_INFO_SET_EA 2 /* EAOP2 struct, only valid on set not query */
+#define SMB_INFO_QUERY_EA_SIZE 2 /* FILESTATUS4 struct, only valid on query not set */
+#define SMB_INFO_QUERY_EAS_FROM_LIST 3 /* only valid on query not set */
+#define SMB_INFO_QUERY_ALL_EAS 4 /* only valid on query not set */
+#define SMB_INFO_IS_NAME_VALID 6
+#define SMB_INFO_STANDARD_LONG 11 /* similar to level 1, ie struct FileStatus3 */
+#define SMB_QUERY_EA_SIZE_LONG 12 /* similar to level 2, ie struct FileStatus4 */
+#define SMB_QUERY_FS_LABEL_INFO 0x101
+#define SMB_QUERY_FS_VOLUME_INFO 0x102
+#define SMB_QUERY_FS_SIZE_INFO 0x103
+#define SMB_QUERY_FS_DEVICE_INFO 0x104
+#define SMB_QUERY_FS_ATTRIBUTE_INFO 0x105
+#if 0
+#define SMB_QUERY_FS_QUOTA_INFO
+#endif
+
+#define l2_vol_fdateCreation 0
+#define l2_vol_cch 4
+#define l2_vol_szVolLabel 5
+
+
+#define SMB_QUERY_FILE_BASIC_INFO 0x101
+#define SMB_QUERY_FILE_STANDARD_INFO 0x102
+#define SMB_QUERY_FILE_EA_INFO 0x103
+#define SMB_QUERY_FILE_NAME_INFO 0x104
+#define SMB_QUERY_FILE_ALLOCATION_INFO 0x105
+#define SMB_QUERY_FILE_END_OF_FILEINFO 0x106
+#define SMB_QUERY_FILE_ALL_INFO 0x107
+#define SMB_QUERY_FILE_ALT_NAME_INFO 0x108
+#define SMB_QUERY_FILE_STREAM_INFO 0x109
+#define SMB_QUERY_COMPRESSION_INFO 0x10b
+
+#define SMB_FIND_INFO_STANDARD 1
+#define SMB_FIND_EA_SIZE 2
+#define SMB_FIND_EA_LIST 3
+#define SMB_FIND_FILE_DIRECTORY_INFO 0x101
+#define SMB_FIND_FILE_FULL_DIRECTORY_INFO 0x102
+#define SMB_FIND_FILE_NAMES_INFO 0x103
+#define SMB_FIND_FILE_BOTH_DIRECTORY_INFO 0x104
+#define SMB_FIND_ID_FULL_DIRECTORY_INFO 0x105
+#define SMB_FIND_ID_BOTH_DIRECTORY_INFO 0x106
+
+#define SMB_SET_FILE_BASIC_INFO 0x101
+#define SMB_SET_FILE_DISPOSITION_INFO 0x102
+#define SMB_SET_FILE_ALLOCATION_INFO 0x103
+#define SMB_SET_FILE_END_OF_FILE_INFO 0x104
+
+/* Query FS info. */
+#define SMB_INFO_ALLOCATION 1
+#define SMB_INFO_VOLUME 2
+
+/*
+ * Thursby MAC extensions....
+ */
+
+/*
+ * MAC CIFS Extensions have the range 0x300 - 0x2FF reserved.
+ * Supposedly Microsoft have agreed to this.
+ */
+
+#define MIN_MAC_INFO_LEVEL 0x300
+#define MAX_MAC_INFO_LEVEL 0x3FF
+
+#define SMB_MAC_QUERY_FS_INFO 0x301
+
+#define DIRLEN_GUESS (45+MAX(l1_achName,l2_achName))
+
+/*
+ * DeviceType and Characteristics returned in a
+ * SMB_QUERY_FS_DEVICE_INFO call.
+ */
+
+#define DEVICETYPE_CD_ROM 0x2
+#define DEVICETYPE_CD_ROM_FILE_SYSTEM 0x3
+#define DEVICETYPE_DISK 0x7
+#define DEVICETYPE_DISK_FILE_SYSTEM 0x8
+#define DEVICETYPE_FILE_SYSTEM 0x9
+
+/* Characteristics. */
+#define TYPE_REMOVABLE_MEDIA 0x1
+#define TYPE_READ_ONLY_DEVICE 0x2
+#define TYPE_FLOPPY 0x4
+#define TYPE_WORM 0x8
+#define TYPE_REMOTE 0x10
+#define TYPE_MOUNTED 0x20
+#define TYPE_VIRTUAL 0x40
+
+/* SMB_FS_SECTOR_SIZE_INFORMATION values */
+#define SSINFO_FLAGS_ALIGNED_DEVICE 0x00000001
+#define SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE 0x00000002
+#define SSINFO_FLAGS_NO_SEEK_PENALTY 0x00000004
+#define SSINFO_FLAGS_TRIM_ENABLED 0x00000008
+
+#define SSINFO_OFFSET_UNKNOWN 0xffffffff
+
+/* MS-FSCC 2.4 File Information Classes */
+
+#define FSCC_FILE_DIRECTORY_INFORMATION 1
+#define FSCC_FILE_FULL_DIRECTORY_INFORMATION 2
+#define FSCC_FILE_BOTH_DIRECTORY_INFORMATION 3
+#define FSCC_FILE_BASIC_INFORMATION 4
+#define FSCC_FILE_STANDARD_INFORMATION 5
+#define FSCC_FILE_INTERNAL_INFORMATION 6
+#define FSCC_FILE_EA_INFORMATION 7
+#define FSCC_FILE_ACCESS_INFORMATION 8
+#define FSCC_FILE_NAME_INFORMATION 9
+#define FSCC_FILE_RENAME_INFORMATION 10
+#define FSCC_FILE_LINK_INFORMATION 11
+#define FSCC_FILE_NAMES_INFORMATION 12
+#define FSCC_FILE_DISPOSITION_INFORMATION 13
+#define FSCC_FILE_POSITION_INFORMATION 14
+#define FSCC_FILE_FULL_EA_INFORMATION 15
+#define FSCC_FILE_MODE_INFORMATION 16
+#define FSCC_FILE_ALIGNMENT_INFORMATION 17
+#define FSCC_FILE_ALL_INFORMATION 18
+#define FSCC_FILE_ALLOCATION_INFORMATION 19
+#define FSCC_FILE_END_OF_FILE_INFORMATION 20
+#define FSCC_FILE_ALTERNATE_NAME_INFORMATION 21
+#define FSCC_FILE_STREAM_INFORMATION 22
+#define FSCC_FILE_PIPE_INFORMATION 23
+#define FSCC_FILE_PIPE_LOCAL_INFORMATION 24
+#define FSCC_FILE_PIPE_REMOTE_INFORMATION 25
+#define FSCC_FILE_MAILSLOT_QUERY_INFORMATION 26
+#define FSCC_FILE_MAILSLOT_SET_INFORMATION 27
+#define FSCC_FILE_COMPRESSION_INFORMATION 28
+#define FSCC_FILE_OBJECTID_INFORMATION 29
+#define FSCC_FILE_COMPLETION_INFORMATION 30
+#define FSCC_FILE_MOVE_CLUSTER_INFORMATION 31
+#define FSCC_FILE_QUOTA_INFORMATION 32
+#define FSCC_FILE_REPARSEPOINT_INFORMATION 33
+#define FSCC_FILE_NETWORK_OPEN_INFORMATION 34
+#define FSCC_FILE_ATTRIBUTE_TAG_INFORMATION 35
+#define FSCC_FILE_TRACKING_INFORMATION 36
+#define FSCC_FILE_ID_BOTH_DIRECTORY_INFORMATION 37
+#define FSCC_FILE_ID_FULL_DIRECTORY_INFORMATION 38
+#define FSCC_FILE_VALID_DATA_LENGTH_INFORMATION 39
+#define FSCC_FILE_SHORT_NAME_INFORMATION 40
+#define FSCC_FILE_SFIO_RESERVE_INFORMATION 44
+#define FSCC_FILE_SFIO_VOLUME_INFORMATION 45
+#define FSCC_FILE_HARD_LINK_INFORMATION 46
+#define FSCC_FILE_NORMALIZED_NAME_INFORMATION 48
+#define FSCC_FILE_ID_GLOBAL_TX_DIRECTORY_INFORMATION 50
+#define FSCC_FILE_STANDARD_LINK_INFORMATION 54
+#define FSCC_FILE_MAXIMUM_INFORMATION 55
+
+/* As yet undefined FSCC_ code for POSIX info level. */
+#define SMB2_FILE_POSIX_INFORMATION 100
+
+/* MS-FSCC 2.4 File System Information Classes */
+
+#define FSCC_FS_VOLUME_INFORMATION 1
+#define FSCC_FS_LABEL_INFORMATION 2
+#define FSCC_FS_SIZE_INFORMATION 3
+#define FSCC_FS_DEVICE_INFORMATION 4
+#define FSCC_FS_ATTRIBUTE_INFORMATION 5
+#define FSCC_FS_QUOTA_INFORMATION 6
+#define FSCC_FS_FULL_SIZE_INFORMATION 7
+#define FSCC_FS_OBJECTID_INFORMATION 8
+#define FSCC_FS_SECTOR_SIZE_INFORMATION 11
+
+/* NT passthrough levels... */
+
+#define NT_PASSTHROUGH_OFFSET 1000
+#define SMB2_INFO_SPECIAL 0xFF00
+
+#define SMB_FILE_DIRECTORY_INFORMATION (FSCC_FILE_DIRECTORY_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_FULL_DIRECTORY_INFORMATION (FSCC_FILE_FULL_DIRECTORY_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_BOTH_DIRECTORY_INFORMATION (FSCC_FILE_BOTH_DIRECTORY_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_BASIC_INFORMATION (FSCC_FILE_BASIC_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_STANDARD_INFORMATION (FSCC_FILE_STANDARD_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_INTERNAL_INFORMATION (FSCC_FILE_INTERNAL_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_EA_INFORMATION (FSCC_FILE_EA_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ACCESS_INFORMATION (FSCC_FILE_ACCESS_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_NAME_INFORMATION (FSCC_FILE_NAME_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_RENAME_INFORMATION (FSCC_FILE_RENAME_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_LINK_INFORMATION (FSCC_FILE_LINK_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_NAMES_INFORMATION (FSCC_FILE_NAMES_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_DISPOSITION_INFORMATION (FSCC_FILE_DISPOSITION_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_POSITION_INFORMATION (FSCC_FILE_POSITION_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_FULL_EA_INFORMATION (FSCC_FILE_FULL_EA_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_MODE_INFORMATION (FSCC_FILE_MODE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ALIGNMENT_INFORMATION (FSCC_FILE_ALIGNMENT_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ALL_INFORMATION (FSCC_FILE_ALL_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ALLOCATION_INFORMATION (FSCC_FILE_ALLOCATION_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_END_OF_FILE_INFORMATION (FSCC_FILE_END_OF_FILE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ALTERNATE_NAME_INFORMATION (FSCC_FILE_ALTERNATE_NAME_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_STREAM_INFORMATION (FSCC_FILE_STREAM_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_PIPE_INFORMATION (FSCC_FILE_PIPE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_PIPE_LOCAL_INFORMATION (FSCC_FILE_PIPE_LOCAL_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_PIPE_REMOTE_INFORMATION (FSCC_FILE_PIPE_REMOTE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_MAILSLOT_QUERY_INFORMATION (FSCC_FILE_MAILSLOT_QUERY_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_MAILSLOT_SET_INFORMATION (FSCC_FILE_MAILSLOT_SET_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_COMPRESSION_INFORMATION (FSCC_FILE_COMPRESSION_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_OBJECTID_INFORMATION (FSCC_FILE_OBJECTID_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_COMPLETION_INFORMATION (FSCC_FILE_COMPLETION_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_MOVE_CLUSTER_INFORMATION (FSCC_FILE_MOVE_CLUSTER_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_QUOTA_INFORMATION (FSCC_FILE_QUOTA_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_REPARSEPOINT_INFORMATION (FSCC_FILE_REPARSEPOINT_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_NETWORK_OPEN_INFORMATION (FSCC_FILE_NETWORK_OPEN_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ATTRIBUTE_TAG_INFORMATION (FSCC_FILE_ATTRIBUTE_TAG_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_TRACKING_INFORMATION (FSCC_FILE_TRACKING_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ID_BOTH_DIRECTORY_INFORMATION (FSCC_FILE_ID_BOTH_DIRECTORY_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ID_FULL_DIRECTORY_INFORMATION (FSCC_FILE_ID_FULL_DIRECTORY_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_VALID_DATA_LENGTH_INFORMATION (FSCC_FILE_VALID_DATA_LENGTH_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_SHORT_NAME_INFORMATION (FSCC_FILE_SHORT_NAME_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_SFIO_RESERVE_INFORMATION (FSCC_FILE_SFIO_RESERVE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_SFIO_VOLUME_INFORMATION (FSCC_FILE_SFIO_VOLUME_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_HARD_LINK_INFORMATION (FSCC_FILE_HARD_LINK_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_NORMALIZED_NAME_INFORMATION (FSCC_FILE_NORMALIZED_NAME_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_ID_GLOBAL_TX_DIRECTORY_INFORMATION (FSCC_FILE_ID_GLOBAL_TX_DIRECTORY_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_STANDARD_LINK_INFORMATION (FSCC_FILE_STANDARD_LINK_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FILE_MAXIMUM_INFORMATION (FSCC_FILE_MAXIMUM_INFORMATION + NT_PASSTHROUGH_OFFSET)
+/* Internal mapped versions. */
+#define SMB2_FILE_RENAME_INFORMATION_INTERNAL (FSCC_FILE_RENAME_INFORMATION + SMB2_INFO_SPECIAL)
+#define SMB2_FILE_FULL_EA_INFORMATION (FSCC_FILE_FULL_EA_INFORMATION + SMB2_INFO_SPECIAL)
+#define SMB2_FILE_ALL_INFORMATION (FSCC_FILE_ALL_INFORMATION + SMB2_INFO_SPECIAL)
+#define SMB2_FILE_POSIX_INFORMATION_INTERNAL (SMB2_FILE_POSIX_INFORMATION + SMB2_INFO_SPECIAL)
+#define SMB2_FS_POSIX_INFORMATION_INTERNAL 1100
+
+/* NT passthrough levels for qfsinfo. */
+
+#define SMB_FS_VOLUME_INFORMATION (FSCC_FS_VOLUME_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_LABEL_INFORMATION (FSCC_FS_LABEL_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_SIZE_INFORMATION (FSCC_FS_SIZE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_DEVICE_INFORMATION (FSCC_FS_DEVICE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_ATTRIBUTE_INFORMATION (FSCC_FS_ATTRIBUTE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_QUOTA_INFORMATION (FSCC_FS_QUOTA_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_FULL_SIZE_INFORMATION (FSCC_FS_FULL_SIZE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_OBJECTID_INFORMATION (FSCC_FS_OBJECTID_INFORMATION + NT_PASSTHROUGH_OFFSET)
+#define SMB_FS_SECTOR_SIZE_INFORMATION (FSCC_FS_SECTOR_SIZE_INFORMATION + NT_PASSTHROUGH_OFFSET)
+
+/* SMB_FS_DEVICE_INFORMATION device types. */
+#define FILE_DEVICE_CD_ROM 0x2
+#define FILE_DEVICE_DISK 0x7
+
+/* SMB_FS_DEVICE_INFORMATION characteristics. */
+#define FILE_REMOVABLE_MEDIA 0x001
+#define FILE_READ_ONLY_DEVICE 0x002
+#define FILE_FLOPPY_DISKETTE 0x004
+#define FILE_WRITE_ONCE_MEDIA 0x008
+#define FILE_REMOTE_DEVICE 0x010
+#define FILE_DEVICE_IS_MOUNTED 0x020
+#define FILE_VIRTUAL_VOLUME 0x040
+#define FILE_DEVICE_SECURE_OPEN 0x100
+
+/* flags on trans2 findfirst/findnext that control search */
+#define FLAG_TRANS2_FIND_CLOSE 0x1
+#define FLAG_TRANS2_FIND_CLOSE_IF_END 0x2
+#define FLAG_TRANS2_FIND_REQUIRE_RESUME 0x4
+#define FLAG_TRANS2_FIND_CONTINUE 0x8
+#define FLAG_TRANS2_FIND_BACKUP_INTENT 0x10
+
+#endif
diff --git a/source3/include/transfer_file.h b/source3/include/transfer_file.h
new file mode 100644
index 0000000..2f1bff4
--- /dev/null
+++ b/source3/include/transfer_file.h
@@ -0,0 +1,32 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utility functions to transfer files.
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TRANSFER_FILE_H__
+#define __TRANSFER_FILE_H__
+
+ssize_t transfer_file_internal(void *in_file,
+ void *out_file,
+ size_t n,
+ ssize_t (*pread_fn)(void *, void *, size_t, off_t),
+ ssize_t (*pwrite_fn)(void *, const void *, size_t, off_t));
+
+off_t transfer_file(int infd, int outfd, off_t n);
+
+#endif /* __TRANSFER_FILE_H__ */
diff --git a/source3/include/util_event.h b/source3/include/util_event.h
new file mode 100644
index 0000000..d19678e
--- /dev/null
+++ b/source3/include/util_event.h
@@ -0,0 +1,34 @@
+/*
+ Unix SMB/CIFS implementation.
+ event handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* The following definitions come from lib/util_event.c */
+
+#include "replace.h"
+#include <tevent.h>
+
+struct idle_event;
+
+struct idle_event *event_add_idle(struct tevent_context *event_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct timeval interval,
+ const char *name,
+ bool (*handler)(const struct timeval *now,
+ void *private_data),
+ void *private_data);
diff --git a/source3/include/util_sd.h b/source3/include/util_sd.h
new file mode 100644
index 0000000..7f82969
--- /dev/null
+++ b/source3/include/util_sd.h
@@ -0,0 +1,37 @@
+/*
+ Unix SMB/CIFS implementation.
+ Security Descriptor (SD) helper functions
+
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jeremy Allison 2000
+ Copyright (C) Jelmer Vernooij 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __UTIL_SD_H__
+#define __UTIL_SD_H__
+
+void SidToString(struct cli_state *cli, fstring str, const struct dom_sid *sid,
+ bool numeric);
+bool StringToSid(struct cli_state *cli, struct dom_sid *sid, const char *str);
+void print_ace(struct cli_state *cli, FILE *f, struct security_ace *ace,
+ bool numeric);
+bool parse_ace(struct cli_state *cli, struct security_ace *ace,
+ const char *orig_str);
+void sec_desc_print(struct cli_state *cli, FILE *f,
+ struct security_descriptor *sd, bool numeric);
+
+#endif
diff --git a/source3/include/util_tdb.h b/source3/include/util_tdb.h
new file mode 100644
index 0000000..ff50918
--- /dev/null
+++ b/source3/include/util_tdb.h
@@ -0,0 +1,65 @@
+/*
+ Unix SMB/CIFS implementation.
+ tdb utility functions
+ Copyright (C) Andrew Tridgell 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TDBUTIL_H__
+#define __TDBUTIL_H__
+
+#include <tdb.h>
+
+#include <talloc.h> /* for tdb_wrap_open() */
+#include "../libcli/util/ntstatus.h" /* for map_nt_error_from_tdb() */
+#include "../../lib/util/util_tdb.h"
+
+/*
+ * The tdb_unpack() and tdb_pack[_append]() helpers are deprecated. Consider
+ * using idl/ndr for marshalling of complex data types instead.
+ */
+int tdb_unpack(const uint8_t *buf, int bufsize, const char *fmt, ...);
+size_t tdb_pack(uint8_t *buf, int bufsize, const char *fmt, ...);
+
+struct tdb_context *tdb_open_log(const char *name, int hash_size,
+ int tdb_flags, int open_flags, mode_t mode);
+
+NTSTATUS map_nt_error_from_tdb(enum TDB_ERROR err);
+
+int tdb_data_cmp(TDB_DATA t1, TDB_DATA t2);
+
+char *tdb_data_string(TALLOC_CTX *mem_ctx, TDB_DATA d);
+char *tdb_data_dbg(TDB_DATA d);
+
+/****************************************************************************
+ Lock a chain, with timeout.
+****************************************************************************/
+int tdb_chainlock_with_timeout( struct tdb_context *tdb, TDB_DATA key,
+ unsigned int timeout);
+
+/****************************************************************************
+ Lock a chain by string, with timeout Return non-zero if lock failed.
+****************************************************************************/
+int tdb_lock_bystring_with_timeout(struct tdb_context *tdb, const char *keyval,
+ int timeout);
+
+/****************************************************************************
+ Readlock a chain by string, with timeout Return non-zero if lock failed.
+****************************************************************************/
+int tdb_read_lock_bystring_with_timeout(TDB_CONTEXT *tdb, const char *keyval,
+ unsigned int timeout);
+
+
+#endif /* __TDBUTIL_H__ */
diff --git a/source3/include/vfs.h b/source3/include/vfs.h
new file mode 100644
index 0000000..1e2e88d
--- /dev/null
+++ b/source3/include/vfs.h
@@ -0,0 +1,2253 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS structures and parameters
+ Copyright (C) Jeremy Allison 1999-2005
+ Copyright (C) Tim Potter 1999
+ Copyright (C) Alexander Bokovoy 2002-2005
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ This work was sponsored by Optifacio Software Services, Inc.
+*/
+
+#ifndef _VFS_H
+#define _VFS_H
+
+/* Avoid conflict with an AIX include file */
+
+#ifdef vfs_ops
+#undef vfs_ops
+#endif
+
+/*
+ * As we're now (thanks Andrew ! :-) using file_structs and connection
+ * structs in the vfs - then anyone writing a vfs must include includes.h...
+ */
+
+/*
+ * This next constant specifies the version number of the VFS interface
+ * this smbd will load. Increment this if *ANY* changes are made to the
+ * vfs_ops below. JRA.
+ *
+ * If you change anything here, please also update modules/vfs_full_audit.c.
+ * VL.
+ */
+
+/*
+ * Changed to version 2 for CIFS UNIX extensions (mknod and link added). JRA.
+ * Changed to version 3 for POSIX acl extensions. JRA.
+ * Changed to version 4 for cascaded VFS interface. Alexander Bokovoy.
+ * Changed to version 5 for sendfile addition. JRA.
+ * Changed to version 6 for the new module system, fixed cascading and quota functions. --metze
+ * Changed to version 7 to include the get_nt_acl info parameter. JRA.
+ * Changed to version 8 includes EA calls. JRA.
+ * Changed to version 9 to include the get_shadow_data call. --metze
+ * Changed to version 10 to include pread pwrite calls.
+ * Changed to version 11 to include seekdir telldir rewinddir calls. JRA
+ * Changed to version 12 to add mask and attributes to opendir(). JRA
+ * Also include aio calls. JRA.
+ * Changed to version 13 as the internal structure of files_struct has changed. JRA
+ * Changed to version 14 as we had to change DIR to SMB_STRUCT_DIR. JRA
+ * Changed to version 15 as we added the statvfs call. JRA
+ * Changed to version 16 as we added the getlock call. JRA
+ * Changed to version 17 as we removed redundant connection_struct parameters. --jpeach
+ * Changed to version 18 to add fsp parameter to the open call -- jpeach
+ * Also include kernel_flock call - jmcd
+ * Changed to version 19, kernel change notify has been merged
+ * Also included linux setlease call - jmcd
+ * Changed to version 20, use ntimes call instead of utime (greater
+ * timestamp resolition. JRA.
+ * Changed to version21 to add chflags operation -- jpeach
+ * Changed to version22 to add lchown operation -- jra
+ * Leave at 22 - not yet released. But change set_nt_acl to return an NTSTATUS. jra.
+ * Leave at 22 - not yet released. Add file_id_create operation. --metze
+ * Leave at 22 - not yet released. Change all BOOL parameters (int) to bool. jra.
+ * Leave at 22 - not yet released. Added recvfile.
+ * Leave at 22 - not yet released. Change get_nt_acl to return NTSTATUS - vl
+ * Leave at 22 - not yet released. Change get_nt_acl to *not* take a
+ * files_struct. - obnox.
+ * Leave at 22 - not yet released. Remove parameter fd from fget_nt_acl. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from gset_nt_acl. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from pread. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from pwrite. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from lseek. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fsync. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fstat. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fchmod. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fchown. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from ftruncate. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from lock. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from kernel_flock. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from linux_setlease. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from getlock. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from sys_acl_get_fd. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fchmod_acl. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from sys_acl_set_fd. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fgetxattr. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from flistxattr. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fremovexattr. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from fsetxattr. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from aio_cancel. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from read. - obnox
+ * Leave at 22 - not yet released. Remove parameter fd from write. - obnox
+ * Leave at 22 - not yet released. Remove parameter fromfd from sendfile. - obnox
+ * Leave at 22 - not yet released. Remove parameter fromfd from recvfile. - obnox
+ * Leave at 22 - not yet released. Additional change: add operations for offline files -- ab
+ * Leave at 22 - not yet released. Add the streaminfo call. -- jpeach, vl
+ * Leave at 22 - not yet released. Remove parameter fd from close_fn. - obnox
+ * Changed to version 23 - remove set_nt_acl call. This can only be done via an
+ * open handle. JRA.
+ * Changed to version 24 - make security descriptor const in fset_nt_acl. JRA.
+ * Changed to version 25 - Jelmer's change from SMB_BIG_UINT to uint64_t.
+ * Leave at 25 - not yet released. Add create_file call. -- tprouty.
+ * Leave at 25 - not yet released. Add create time to ntimes. -- tstecher.
+ * Leave at 25 - not yet released. Add get_alloc_size call. -- tprouty.
+ * Leave at 25 - not yet released. Add SMB_STRUCT_STAT to readdir. - sdann
+ * Leave at 25 - not yet released. Add init_search_op call. - sdann
+ * Leave at 25 - not yet released. Add locking calls. -- zkirsch.
+ * Leave at 25 - not yet released. Add strict locking calls. -- drichards.
+ * Changed to version 26 - Plumb struct smb_filename to SMB_VFS_CREATE_FILE,
+ * SMB_VFS_OPEN, SMB_VFS_STAT, SMB_VFS_LSTAT,
+ * SMB_VFS_RENAME, SMB_VFS_UNLINK, SMB_VFS_NTIMES.
+ * Changed to version 27 - not yet released. Added enum timestamp_set_resolution
+ * return to fs_capabilities call. JRA.
+ * Leave at 27 - not yet released. Add translate_name VFS call to convert
+ * UNIX names to Windows supported names -- asrinivasan.
+ * Changed to version 28 - Add private_flags uint32_t to CREATE call.
+ * Leave at 28 - not yet released. Change realpath to assume NULL and return a
+ * malloc'ed path. JRA.
+ * Leave at 28 - not yet released. Move posix_fallocate into the VFS
+ * where it belongs. JRA.
+ * Leave at 28 - not yet released. Rename posix_fallocate to fallocate
+ * to split out the two possible uses. JRA.
+ * Leave at 28 - not yet released. Add fdopendir. JRA.
+ * Leave at 28 - not yet released. Rename open function to open_fn. - gd
+ * Leave at 28 - not yet released. Make getwd function always return malloced memory. JRA.
+ * Bump to version 29 - Samba 3.6.0 will ship with interface version 28.
+ * Leave at 29 - not yet releases. Add fsctl. Richard Sharpe
+ * Leave at 29 - not yet released. add SMB_VFS_GET_DFS_REFERRAL() - metze
+ * Leave at 29 - not yet released. Remove l{list,get,set,remove}xattr - abartlet
+ * Leave at 29 - not yet released. move to plain off_t - abartlet
+ * Leave at 29 - not yet released. Remove sys_acl functions other than set and get - abartlet
+ * Leave at 29 - not yet released. Added backup_intent bool to files_struct - JRA
+ * Leave at 29 - not yet released. Add durable handle functions - metze obnox
+ * Leave at 29 - not yet released. Added sys_acl_blob_get_file and sys_acl_blob_get_fd
+ * Bump to version 30 - Samba 4.0.0 will ship with interface version 30
+ * Leave at 30 - not yet released. Added conn->cwd to save vfs_GetWd() calls.
+ * Leave at 30 - not yet released. Changed sys_acl_blob_get_file interface to remove type
+ * Bump to version 31 - Samba 4.1.0 will ship with interface version 31
+ * Leave at 31 - not yet released. Make struct vuid_cache_entry in
+ * connection_struct a pointer.
+ * Leave at 31 - not yet released. Add share_access to vuid_cache_entry.
+ * Leave at 31 - not yet released. add SMB_VFS_COPY_CHUNK()
+ * Leave at 31 - not yet released. Remove the unused
+ * fsp->pending_break_messages array
+ * Leave at 31 - not yet released. add SMB_VFS_[GET SET]_COMPRESSION()
+ *
+ * Bump to version 32 - Samba 4.2 will ship with that.
+ * Version 32 - Add "lease" to CREATE_FILE operation
+ * Version 32 - Add "lease" to struct files_struct
+ * Version 32 - Add SMB_VFS_READDIR_ATTR()
+ * Version 32 - Add in and out create context blobs to create_file
+ * Version 32 - Remove unnecessary SMB_VFS_DISK_FREE() small_query parameter
+ * Bump to version 33 - Samba 4.3 will ship with that.
+ * Version 33 - change fallocate mode flags param from enum->uint32_t
+ * Version 33 - Add snapshot create delete calls
+ * Version 33 - Add OS X SMB2 AAPL copyfile extension flag to fsp
+ * Version 33 - Remove notify_watch_fn
+ * Bump to version 34 - Samba 4.4 will ship with that
+ * Version 34 - Remove bool posix_open, add uint64_t posix_flags
+ * Version 34 - Added bool posix_pathnames to struct smb_request
+ * Bump to version 35 - Samba 4.5 will ship with that
+ * Version 35 - Change get_nt_acl_fn from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Change mkdir from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Change rmdir from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Change opendir from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Wrap aio async functions args in a struct vfs_aio_state
+ * Version 35 - Change chmod from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Change chmod_acl from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Change chown from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Change lchown from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Change streaminfo from const char *, to
+ * const struct smb_filename *
+ * Version 35 - Add uint32_t flags to struct smb_filename
+ * Version 35 - Add get set fget fset dos attribute functions.
+ * Version 35 - Add bool use_ofd_locks to struct files_struct
+ * Bump to version 36 - Samba 4.6 will ship with that
+ * Version 36 - Remove is_offline and set_offline
+ * Version 37 - Module init functions now take a TALLOC_CTX * parameter.
+ * Version 37 - Add vfs_copy_chunk_flags for DUP_EXTENTS_TO_FILE
+ * Version 37 - Change sys_acl_delete_def_file from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change sys_acl_get_file from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change sys_acl_blob_get_file from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change sys_acl_set_file from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change listxattr from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change removexattr from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change setxattr from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change getxattr from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change mknod from const char * to const struct smb_filename *
+ * Version 37 - Change chflags from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change disk_free from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change get_quota from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change link from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change statvfs from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change readlink from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change symlink from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change chdir from const char *
+ * to const struct smb_filename *
+ * Version 37 - Change getwd from char *
+ * to const struct smb_filename *
+ * Version 37 - Change conn->cwd from char *
+ * to struct smb_filename *
+ * Version 37 - Change realpath from char *
+ * to struct smb_filename *
+ * Version 37 - Change connectpath from char *
+ * to struct smb_filename *
+ * Version 37 - Add SMB_VFS_OFFLOAD_READ_SEND RECV
+ * Version 37 - Rename SMB_VFS_COPY_CHUNK_SEND RECV to
+ * SMB_VFS_OFFLOAD_READ_SEND RECV
+ * Version 37 - Remove SMB_VFS_STRICT_UNLOCK
+ * Version 37 - Rename SMB_VFS_STRICT_LOCK to
+ * SMB_VFS_STRICT_LOCK_CHECK
+ * Version 38 - Remove SMB_VFS_INIT_SEARCH_OP
+ * Bump to version 39, Samba 4.9 will ship with that
+ * Version 39 - Remove SMB_VFS_FSYNC
+ * Only implement async versions.
+ * Version 39 - Remove SMB_VFS_READ
+ * All users are now pread or async versions.
+ * Version 39 - Remove SMB_VFS_WRITE
+ * All users are now pwrite or async versions.
+ * Version 39 - Remove SMB_VFS_CHMOD_ACL - no longer used.
+ * Version 39 - Remove SMB_VFS_FCHMOD_ACL - no longer used.
+ * Version 39 - Remove struct dfree_cached_info pointer from
+ * connection struct
+ * Bump to version 40, Samba 4.10 will ship with that
+ * Version 40 - Add SMB_VFS_GETXATTRAT_SEND RECV
+ * Version 40 - Add SMB_VFS_GET_DOS_ATTRIBUTES_SEND RECV
+ * Bump to version 41, Samba 4.11 will ship with that
+ * Version 41 - Remove SMB_VFS_BRL_CANCEL_WINDOWS
+ * Version 41 - Remove unused st_ex_mask from struct stat_ex
+ * Version 41 - convert struct stat_ex.st_ex_calculated_birthtime to flags
+ * Version 41 - add st_ex_itime to struct stat_ex
+ * Version 41 - add st_ex_file_id to struct stat_ex
+ * Version 41 - add SMB_VFS_FS_FILE_ID
+ * Version 41 - Remove "blocking_lock" parameter from
+ * SMB_VFS_BRL_LOCK_WINDOWS
+ * Version 41 - Remove "msg_ctx" parameter from SMB_VFS_BRL_UNLOCK_WINDOWS
+ * Bump to version 42, Samba 4.12 will ship with that
+ * Version 42 - Remove share_access member from struct files_struct
+ * Version 42 - Make "lease" a const* in create_file_fn
+ * Version 42 - Move SMB_VFS_RENAME -> SMB_VFS_RENAMEAT
+ * Version 42 - Move SMB_VFS_LINK -> SMB_VFS_LINKAT.
+ * Version 42 - Move SMB_VFS_MKNOD -> SMB_VFS_MKDNODAT.
+ * Version 42 - Move SMB_VFS_READLINK -> SMB_VFS_READLINKAT.
+ * Version 42 - Move SMB_VFS_SYMLINK -> SMB_VFS_SYMLINKAT.
+ * Version 42 - Move SMB_VFS_MKDIR -> SMB_VFS_MKDIRAT.
+ * Version 42 - Move change_to_user() -> change_to_user_and_service()
+ * Version 42 - Move change_to_user_by_fsp() -> change_to_user_and_service_by_fsp()
+ * Version 42 - Move [un]become_user*() -> [un]become_user_without_service*()
+ * Version 42 - Move SMB_VFS_UNLINK -> SMB_VFS_UNLINKAT.
+ * Version 42 - Add SMB_VFS_FCNTL
+ * Version 42 - Remove SMB_VFS_RMDIR.
+ * Use SMB_VFS_UNLINKAT(.., AT_REMOVEDIR) instead.
+ * Version 42 - Remove SMB_VFS_CHOWN
+ * Version 42 - Remove struct write_cache *wcp from files_struct
+ * Version 42 - SMB_VFS_NTIMES() receives null times based on UTIMES_OMIT
+ * Version 42 - Add SMB_VFS_CREATE_DFS_PATHAT()
+ * Version 42 - Add SMB_VFS_READ_DFS_PATHAT()
+ * Change to Version 43 - will ship with 4.13.
+ * Version 43 - Remove deferred_close from struct files_struct
+ * Version 43 - Remove SMB_VFS_OPENDIR()
+ * Version 43 - Remove original_lcomp from struct smb_filename
+ * Version 43 - files_struct flags:
+ * bool kernel_share_modes_taken
+ * bool update_write_time_triggered
+ * bool update_write_time_on_close
+ * bool write_time_forced
+ * bool can_lock
+ * bool can_read
+ * bool can_write
+ * bool modified
+ * bool is_directory
+ * bool aio_write_behind
+ * bool initial_delete_on_close
+ * bool delete_on_close
+ * bool is_sparse
+ * bool backup_intent
+ * bool use_ofd_locks
+ * bool closing
+ * bool lock_failure_seen
+ * changed to bitfields.
+ * Version 43 - convert SMB_VFS_GET_REAL_FILENAME() arg path
+ * to be a struct smb_filename
+ * Version 43 - convert link_contents arg of SMB_VFS_SYMLINKAT()
+ * to struct smb_filename
+ * Version 43 - Move SMB_VFS_GET_NT_ACL() -> SMB_VFS_GET_NT_ACL_AT().
+ * Version 43 - Remove root_dir_fid from SMB_VFS_CREATE_FILE().
+ * Version 43 - Add dirfsp to struct files_struct
+ * Version 43 - Add dirfsp args to SMB_VFS_CREATE_FILE()
+ * Version 43 - Add SMB_VFS_OPENAT()
+ * Version 43 - Remove SMB_VFS_OPEN()
+ * Version 43 - SMB_VFS_READ_DFS_PATHAT() should take a non-const name.
+ There's no easy way to return stat info for a DFS link
+ otherwise.
+ * Change to Version 44 - will ship with 4.14.
+ * Version 44 - Remove dirfsp arg from struct files_struct
+ * Version 44 - Remove dirfsp arg to SMB_VFS_CREATE_FILE()
+ * Version 44 - Make dirfsp arg to SMB_VFS_READLINKAT() const
+ * Version 44 - Add a flag 'encryption_required' to files_struct that that
+ * prevents that encrypted connections can be downgraded.
+ * Version 44 - Add a flag 'is_pathref' to struct files_struct.
+ * Version 44 - Add 'is_fsa' flag to struct files_struct.
+ * Version 44 - Add 'have_proc_fds' flag to struct connection_struct.
+ * Version 44 - Add 'have_proc_fds' flag to struct files_struct.
+ * Version 44 - Add dirfsp arg to SMB_VFS_READDIR()
+ * Version 44 - Replace SMB_VFS_GET_COMPRESSION() with SMB_VFS_FGET_COMPRESSION()
+ * Version 44 - Add type argument to SMB_VFS_SYS_ACL_SET_FD()
+ * Version 44 - Remove SMB_VFS_SYS_ACL_SET_FILE()
+ * Change to Version 45 - will ship with 4.15
+ * Version 45 - Remove SMB_VFS_LISTXATTR
+ * Version 45 - Remove SMB_VFS_SETXATTR
+ * Version 45 - Remove SMB_VFS_REMOVEXATTR
+ * Version 45 - Remove SMB_VFS_GET_DOS_ATTRIBUTES()
+ * Version 45 - Remove SMB_VFS_CHMOD
+ * Version 45 - Add SMB_VFS_FNTIMES
+ * Version 45 - Remove SMB_VFS_NTIMES
+ * Version 45 - ADD SMB_VFS_FSTREAMINFO
+ * Version 45 - Add SMB_VFS_FREADDIR_ATTR
+ * Version 45 - Remove SMB_VFS_READDIR_ATTR
+ * Version 45 - Add SMB_VFS_SYS_ACL_DELETE_DEF_FD
+ * Version 45 - Remove SMB_VFS_SYS_ACL_DELETE_DEF_FILE
+ * Version 45 - Add SMB_VFS_PARENT_PATHNAME
+ * Version 45 - Remove SMB_VFS_GET_NT_ACL_AT
+ * Version 45 - Remove SYS_ACL_GET_FILE
+ * Version 45 - Remove SYS_ACL_BLOB_GET_FILE
+ * Version 45 - Add SMB_VFS_FCHFLAGS
+ * Version 45 - Remove SMB_VFS_GETXATTR
+ * Change to Version 46 - will ship with 4.16
+ * Version 46 - Rename SMB_VFS_KERNEL_FLOCK to SMB_VFS_FILESYSTEM_SHAREMODE
+ * Version 46 - Add flags and xferlen args to SMB_VFS_OFFLOAD_READ_RECV
+ * Change to Version 47 - will ship with 4.17
+ * Version 47 - Add SMB_VFS_FSTATAT
+ * Version 47 - Change SMB_VFS_GET_REAL_FILENAME to return NTSTATUS
+ * Version 47 - remove st_ex_itime from struct stat_ex
+ * Version 47 - remove (unused) struct lock_struct last_lock_failure
+ from files_struct.
+ * Version 47 - Add SMB_VFS_GET_REAL_FILENAME_AT
+ * Version 47 - Replace SMB_VFS_GET_REAL_FILENAME with SMB_VFS_GET_REAL_FILENAME_AT
+ * Version 47 - Re-add dirfsp to CREATE_FILE
+ * Version 47 - Add fsp flag fstat_before_close
+ * Version 47 - Change SMB_VFS_OPENAT() to match the Linux openat2 prototype, add vfs_open_how
+ * Version 47 - Add VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS for SMB_VFS_OPENAT()
+ * Change to Version 48 - will ship with 4.18
+ * Version 48 - Add cached_dos_attributes to struct stat_ex
+ * Version 48 - Add dirfsp to connectpath_fn()
+ * Change to Version 49 - will ship with 4.19
+ * Version 49 - remove seekdir and telldir
+ * Version 49 - remove "sbuf" argument from readdir_fn()
+ */
+
+#define SMB_VFS_INTERFACE_VERSION 49
+
+/*
+ All intercepted VFS operations must be declared as static functions inside module source
+ in order to keep smbd namespace unpolluted. See source of audit, extd_audit, fake_perms and recycle
+ example VFS modules for more details.
+*/
+
+/* VFS operations structure */
+
+struct vfs_handle_struct;
+struct connection_struct;
+struct files_struct;
+struct security_descriptor;
+struct vfs_statvfs_struct;
+struct smb_request;
+struct ea_list;
+struct smb_file_time;
+struct smb_filename;
+struct dfs_GetDFSReferral;
+
+typedef union unid_t {
+ uid_t uid;
+ gid_t gid;
+} unid_t;
+
+struct fd_handle;
+
+struct fsp_lease {
+ size_t ref_count;
+ struct smbd_server_connection *sconn;
+ struct tevent_timer *timeout;
+ struct smb2_lease lease;
+};
+
+typedef struct files_struct {
+ struct files_struct *next, *prev;
+ uint64_t fnum;
+ struct smbXsrv_open *op;
+ struct connection_struct *conn;
+ struct fd_handle *fh;
+ unsigned int num_smb_operations;
+ struct file_id file_id;
+ uint64_t initial_allocation_size; /* Faked up initial allocation on disk. */
+ uint16_t file_pid;
+ uint64_t vuid; /* SMB2 compat */
+ struct timeval open_time;
+ uint32_t access_mask; /* NTCreateX access bits (FILE_READ_DATA etc.) */
+ struct {
+ bool is_pathref : 1; /* See below */
+ bool is_fsa : 1; /* See below */
+ bool have_proc_fds : 1;
+ bool kernel_share_modes_taken : 1;
+ bool update_write_time_triggered : 1;
+ bool update_write_time_on_close : 1;
+ bool write_time_forced : 1;
+ bool can_lock : 1;
+ bool can_read : 1;
+ bool can_write : 1;
+ bool modified : 1;
+ bool is_directory : 1;
+ bool is_dirfsp : 1;
+ bool aio_write_behind : 1;
+ bool initial_delete_on_close : 1;
+ bool delete_on_close : 1;
+ bool is_sparse : 1;
+ bool backup_intent : 1;
+ bool use_ofd_locks : 1;
+ bool closing : 1;
+ bool lock_failure_seen : 1;
+ bool encryption_required : 1;
+ bool fstat_before_close : 1;
+ } fsp_flags;
+
+ struct tevent_timer *update_write_time_event;
+ struct timespec close_write_time;
+
+ int oplock_type;
+
+ /*
+ * Cache of our lease_type, stored as "current_state" in
+ * leases.tdb
+ */
+ int leases_db_seqnum;
+ uint32_t lease_type;
+
+ struct fsp_lease *lease;
+ int sent_oplock_break;
+ struct tevent_timer *oplock_timeout;
+ int current_lock_count; /* Count the number of outstanding locks and pending locks. */
+
+ uint64_t posix_flags;
+ struct smb_filename *fsp_name;
+ uint32_t name_hash; /* Jenkins hash of full pathname. */
+ uint64_t mid; /* Mid of the operation that created us. */
+
+ struct vfs_fsp_data *vfs_extension;
+ struct fake_file_handle *fake_file_handle;
+
+ struct notify_change_buf *notify;
+
+ struct files_struct *base_fsp; /* placeholder for delete on close */
+ struct files_struct *stream_fsp; /* backlink from base_fsp */
+
+ /*
+ * Cache of share_mode_data->flags
+ */
+ int share_mode_flags_seqnum;
+ uint16_t share_mode_flags;
+
+ /*
+ * Read-only cached brlock record, thrown away when the
+ * brlock.tdb seqnum changes. This avoids fetching data from
+ * the brlock.tdb on every read/write call.
+ */
+ int brlock_seqnum;
+ struct byte_range_lock *brlock_rec;
+
+ struct dptr_struct *dptr;
+
+ /* if not NULL, means this is a print file */
+ struct print_file_data *print_file;
+
+ /*
+ * Optimize the aio_requests array for high performance: Never
+ * shrink it, maintain num_aio_requests separately
+ */
+ unsigned num_aio_requests;
+ struct tevent_req **aio_requests;
+
+ /*
+ * Requests waiting for smb1 byte range locks. They are
+ * generated by smbd_smb1_do_locks_send and are required here,
+ * because lock cancel operations index through reply_lockingX
+ * not based on mid but on the lock type and range.
+ */
+ struct tevent_req **blocked_smb1_lock_reqs;
+
+ /*
+ * SMB1 remembers lock failures and delays repeated blocking
+ * lock attempts on the same offset.
+ */
+ uint64_t lock_failure_offset;
+} files_struct;
+
+/*
+ * The fsp flags "is_pathref" and "is_fsa"
+ * =======================================
+ *
+ * Summary
+ * -------
+ *
+ * The flag "is_pathref" is a property of the low-level VFS-layer file
+ * handle. If "is_pathref" is true, only a subset of VFS calls are allowed
+ * on the handle and on systems that support it, the low-level fd is open
+ * with O_PATH. If "is_pathref" is false, the low-level fd is a "normal"
+ * file descriptor that can be used with all VFS calls.
+ *
+ * The flag "is_fsa" is a property of the FSA layer in Samba. The term FSA
+ * layer refers to the parts of smbd that implement Windows NTFS semantics
+ * on-top of a POSIX filesystem. If "is_fsa" is true, the fsp was
+ * processed by the SMB_VFS_CREATE_FILE() VFS call, otherwise the fsp was
+ * created by openat_pathref_fsp() which only connected the low-level
+ * handle by calling into VFS SMB_VFS_OPENAT(), but the whole FSA layer
+ * logic is skipped.
+ *
+ * Note that only three possible combinations of "is_pathref" and "is_fsa"
+ * are possible:
+ *
+ * | is_fsa \ is_pathref | + | - |
+ * |---------------------+---+---|
+ * | + | + | + |
+ * | - | + | - |
+ *
+ * So a fsp can't be a full low-level fd (is_pathref=false) and not be
+ * processed by the FSA layer.
+ *
+ * Details
+ * -------
+ *
+ * On Linux the O_PATH flag to open() can be used to open a filehandle on
+ * a file or directory with interesting properties:
+ *
+ * - the file-handle indicates a location in the filesystem tree,
+ * - no permission checks are done by the kernel and
+ * - only operations that act purely at the file descriptor level are
+ * allowed.
+ *
+ * The file itself is not opened, and other file operations (e.g.,
+ * read(2), write(2), fchmod(2), fchown(2), fgetxattr(2), ioctl(2),
+ * mmap(2)) fail with the error EBADF.
+ *
+ * The following subset of operations that is relevant to Samba is allowed:
+ *
+ * - close(2),
+ * - fchdir(2), if the file descriptor refers to a directory,
+ * - fstat(2),
+ * - fstatfs(2) and
+ * - passing the file descriptor as the dirfd argument of openat() and the
+ * other "*at()" system calls. This includes linkat(2) with
+ * AT_EMPTY_PATH (or via procfs using AT_SYMLINK_FOLLOW) even if the
+ * file is not a directory.
+ *
+ * Opening a file or directory with the O_PATH flag requires no
+ * permissions on the object itself (but does require execute permission
+ * on the directories in the path prefix). By contrast, obtaining a
+ * reference to a filesystem object by opening it with the O_RDONLY flag
+ * requires that the caller have read permission on the object, even when
+ * the subsequent operation (e.g., fchdir(2), fstat(2)) does not require
+ * read permission on the object. [1]
+ *
+ * If for example Samba receives an SMB request to open a file requesting
+ * SEC_FILE_READ_ATTRIBUTE access rights because the client wants to read
+ * the file's metadata from the handle, Samba will have to call POSIX
+ * open() with at least O_RDONLY access rights.
+ *
+ * Usecase for O_PATH in Samba
+ * ---------------------------
+ *
+ * By leveraging this Linux specific flags we can avoid permission
+ * mismatches as described above. Additionally O_PATH allows basing all
+ * filesystem accesses done by the fileserver on handle based syscalls by
+ * opening all client pathnames with O_PATH and consistently using for
+ * example fstat() instead of stat() throughout the codebase.
+ *
+ * Subsequently we will refer to Samba file-handles (fsp's) opened with
+ * O_PATH "path referencing fsp's" or "pathref" fsp's for short.
+ *
+ * Currently Samba bases the decision whether to call POSIX open() on a
+ * client pathname or whether to leave the low-level handle at -1, what we
+ * call a stat-open, in the function open_file() and it is based on the
+ * client requested SMB access mask.
+ *
+ * The set of rights that trigger an open() include READ_CONTROL_ACCESS,
+ * resulting in a call to open() with at least O_RDONLY. If the filesystem
+ * supports NT style ACLs natively (like GPFS or ZFS), the filesystem may
+ * grant the user requested right READ_CONTROL_ACCESS, but it may not
+ * grant READ_DATA (O_RDONLY), resulting in a permission denied error.
+ *
+ * Historically the set of access rights that triggered opening a file was:
+ *
+ * FILE_READ_DATA
+ * FILE_WRITE_DATA
+ * FILE_APPEND_DATA
+ * FILE_EXECUTE
+ * WRITE_DAC_ACCESS
+ * WRITE_OWNER_ACCESS
+ * SEC_FLAG_SYSTEM_SECURITY
+ * READ_CONTROL_ACCESS
+ *
+ * By using O_PATH this can be trimmed down to
+ *
+ * FILE_READ_DATA
+ * FILE_WRITE_DATA
+ * FILE_APPEND_DATA
+ * FILE_EXECUTE
+ *
+ * Fallback on systems without O_PATH support
+ * ------------------------------------------
+ *
+ * A fallback is needed that allows opening a file-handle with the same
+ * higher level semantics even if the system doesn't support O_PATH. This
+ * is implemented by impersonating the root user for the open()
+ * syscall. To avoid bypassing restrictive permissions on intermediate
+ * directories components of a path, the root user is only impersonated
+ * after changing directory to the parent directory of the client
+ * requested pathname.
+ *
+ * In order to avoid privilege escalation security issues with these root
+ * opened file-handles we must carefully control their usage throughout
+ * the codebase. Therefore we
+ *
+ * - tag the pathref fsp's with the flag "is_pathref" and
+ *
+ * - control access to the file-handle by making the structure private and only
+ * allowing access with accessor functions.
+ *
+ * Two functions are used to fetch the low-level system file-handle from an fsp
+ *
+ * - fsp_get_io_fd(fsp): enforces fsp is NOT a pathref file-handle and
+ *
+ * - fsp_get_pathref_fd(fsp): allows fsp to be either a pathref file-handle or a
+ * traditional POSIX file-handle opened with O_RDONLY or any other POSIX open
+ * flag.
+ *
+ * The general guideline when to use which function is:
+ *
+ * - if you do something like fstat(fd), use fsp_get_pathref_fd(fsp),
+ * - if you do something like *at(dirfd, ...), use fsp_get_pathref_fd(fsp),
+ * - if you want to print the fd for example in DEBUG messages, use
+ * fsp_get_pathref_fd(fsp),
+ * - if you want to call close(fd), use fsp_get_pathref_fd(fsp),
+ * - if you're doing a logical comparison of fd values, use
+ * fsp_get_pathref_fd().
+ *
+ * In any other case use fsp_get_io_fd().
+ */
+
+#define FSP_POSIX_FLAGS_OPEN 0x01
+#define FSP_POSIX_FLAGS_RENAME 0x02
+#define FSP_POSIX_FLAGS_PATHNAMES 0x04
+
+#define FSP_POSIX_FLAGS_ALL \
+ (FSP_POSIX_FLAGS_OPEN | \
+ FSP_POSIX_FLAGS_PATHNAMES | \
+ FSP_POSIX_FLAGS_RENAME)
+
+struct vuid_cache_entry {
+ struct auth_session_info *session_info;
+ uint64_t vuid; /* SMB2 compat */
+ bool read_only;
+ uint32_t share_access;
+};
+
+struct vuid_cache {
+ unsigned int next_entry;
+ struct vuid_cache_entry array[VUID_CACHE_SIZE];
+};
+
+typedef struct {
+ char *name;
+ bool is_wild;
+} name_compare_entry;
+
+struct share_params {
+ int service;
+};
+
+typedef struct connection_struct {
+ struct connection_struct *next, *prev;
+ struct smbd_server_connection *sconn; /* can be NULL */
+ struct smbXsrv_tcon *tcon; /* can be NULL */
+ uint32_t cnum; /* an index passed over the wire */
+ struct share_params *params;
+ bool force_user;
+ struct vuid_cache *vuid_cache;
+ bool printer;
+ bool ipc;
+ bool read_only; /* Attributes for the current user of the share. */
+ bool have_proc_fds;
+ uint64_t open_how_resolve; /* supported vfs_open_how.resolve features */
+ uint32_t share_access;
+ /* Does this filesystem honor
+ sub second timestamps on files
+ and directories when setting time ? */
+ enum timestamp_set_resolution ts_res;
+ char *connectpath;
+ struct files_struct *cwd_fsp; /* Working directory. */
+ bool tcon_done;
+
+ struct vfs_handle_struct *vfs_handles; /* for the new plugins */
+
+ /*
+ * This represents the user information on this connection. Depending
+ * on the vuid using this tid, this might change per SMB request.
+ */
+ struct auth_session_info *session_info;
+
+ /*
+ * If the "force group" parameter is set, this is the primary gid that
+ * may be used in the users token, depending on the vuid using this tid.
+ */
+ gid_t force_group_gid;
+
+ uint64_t vuid; /* vuid of user who *opened* this connection, or UID_FIELD_INVALID */
+
+ time_t lastused;
+ time_t lastused_count;
+ int num_files_open;
+ unsigned int num_smb_operations; /* Count of smb operations on this tree. */
+ int encrypt_level;
+ bool encrypted_tid;
+
+ /* Semantics requested by the client or forced by the server config. */
+ bool case_sensitive;
+ bool case_preserve;
+ bool short_case_preserve;
+
+ /* Semantics provided by the underlying filesystem. */
+ int fs_capabilities;
+ /* Device number of the directory of the share mount.
+ Used to ensure unique FileIndex returns. */
+ SMB_DEV_T base_share_dev;
+
+ name_compare_entry *hide_list; /* Per-share list of files to return as hidden. */
+ name_compare_entry *veto_list; /* Per-share list of files to veto (never show). */
+ name_compare_entry *veto_oplock_list; /* Per-share list of files to refuse oplocks on. */
+ name_compare_entry *aio_write_behind_list; /* Per-share list of files to use aio write behind on. */
+ struct trans_state *pending_trans;
+
+ struct rpc_pipe_client *spoolss_pipe;
+
+} connection_struct;
+
+struct smbd_smb2_request;
+struct referral;
+
+struct smb_request {
+ uint8_t cmd;
+ uint16_t flags2;
+ uint16_t smbpid;
+ uint64_t mid; /* For compatibility with SMB2. */
+ uint32_t seqnum;
+ uint64_t vuid; /* For compatibility with SMB2. */
+ uint32_t tid;
+ uint8_t wct;
+ const uint16_t *vwv;
+ uint16_t buflen;
+ const uint8_t *buf;
+ const uint8_t *inbuf;
+
+ /*
+ * Async handling in the main smb processing loop is directed by
+ * outbuf: reply_xxx routines indicate sync behaviour by putting their
+ * reply into "outbuf". If they leave it as NULL, they take care of it
+ * themselves, possibly later.
+ *
+ * If async handling is wanted, the reply_xxx routine must make sure
+ * that it talloc_move()s the smb_req somewhere else.
+ */
+ uint8_t *outbuf;
+
+ size_t unread_bytes;
+ bool encrypted;
+ connection_struct *conn;
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+
+ /*
+ * Pointer to session, can be NULL,
+ * eg during negprot and session setup.
+ */
+ struct smbXsrv_session *session;
+
+ /*
+ * Chained request handling
+ */
+ struct files_struct *chain_fsp;
+
+ /*
+ * state information for async smb handling
+ */
+ void *async_priv;
+
+ /*
+ * Back pointer to smb2 request.
+ */
+ struct smbd_smb2_request *smb2req;
+
+ /*
+ * Request list for chained requests, we're part of it.
+ */
+ struct smb_request **chain;
+
+ struct timeval request_time;
+
+ bool posix_pathnames;
+};
+
+/*
+ * Info about an alternate data stream
+ */
+
+struct stream_struct {
+ off_t size;
+ off_t alloc_size;
+ char *name;
+};
+
+/* time info */
+struct smb_file_time {
+ struct timespec mtime;
+ struct timespec atime;
+ struct timespec ctime;
+ struct timespec create_time;
+};
+
+/*
+ * smb_filename
+ */
+struct smb_filename {
+ char *base_name;
+ char *stream_name;
+ uint32_t flags;
+ SMB_STRUCT_STAT st;
+ NTTIME twrp;
+
+ /*
+ * Internal file handle, O_PATH based if available,
+ * otherwise O_RDONLY as root.
+ */
+ struct files_struct *fsp;
+
+ /*
+ * Link between the struct smb_filename and the above
+ * fsp. fsp_link is a talloc child of the fsp. Ensures a possible
+ * talloc_free(fsp) resets the smb_fname->fsp pointer to NULL in
+ * the is fsp_link talloc destructor.
+ */
+ struct fsp_smb_fname_link *fsp_link;
+};
+
+/*
+ * smb_filename flags. Define in terms of the FSP_POSIX_FLAGS_XX
+ * to keep the numeric values consistent.
+ */
+
+#define SMB_FILENAME_POSIX_PATH FSP_POSIX_FLAGS_PATHNAMES
+
+enum vfs_translate_direction {
+ vfs_translate_to_unix = 0,
+ vfs_translate_to_windows
+};
+
+enum vfs_fallocate_flags {
+ VFS_FALLOCATE_FL_KEEP_SIZE = 0x0001,
+ VFS_FALLOCATE_FL_PUNCH_HOLE = 0x0002,
+};
+
+struct vfs_aio_state {
+ int error;
+ uint64_t duration;
+};
+
+#define VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS 1
+#define VFS_OPEN_HOW_WITH_BACKUP_INTENT 2
+
+struct vfs_open_how {
+ int flags;
+ mode_t mode;
+ uint64_t resolve;
+};
+
+/*
+ Available VFS operations. These values must be in sync with vfs_ops struct
+ (struct vfs_fn_pointers and struct vfs_handle_pointers inside of struct vfs_ops).
+ In particular, if new operations are added to vfs_ops, appropriate constants
+ should be added to vfs_op_type so that order of them kept same as in vfs_ops.
+*/
+struct shadow_copy_data;
+
+struct vfs_fn_pointers {
+ /* Disk operations */
+
+ int (*connect_fn)(struct vfs_handle_struct *handle, const char *service, const char *user);
+ void (*disconnect_fn)(struct vfs_handle_struct *handle);
+ uint64_t (*disk_free_fn)(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize);
+ int (*get_quota_fn)(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt);
+ int (*set_quota_fn)(struct vfs_handle_struct *handle, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *qt);
+ int (*get_shadow_copy_data_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, struct shadow_copy_data *shadow_copy_data, bool labels);
+ int (*statvfs_fn)(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf);
+ uint32_t (*fs_capabilities_fn)(struct vfs_handle_struct *handle, enum timestamp_set_resolution *p_ts_res);
+
+ /*
+ * Note: that "struct dfs_GetDFSReferral *r"
+ * needs to be a valid TALLOC_CTX
+ */
+ NTSTATUS (*get_dfs_referrals_fn)(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r);
+ NTSTATUS (*create_dfs_pathat_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count);
+ NTSTATUS (*read_dfs_pathat_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count);
+
+ /* Directory operations */
+
+ DIR *(*fdopendir_fn)(struct vfs_handle_struct *handle, files_struct *fsp, const char *mask, uint32_t attributes);
+ struct dirent *(*readdir_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp);
+ void (*rewind_dir_fn)(struct vfs_handle_struct *handle, DIR *dirp);
+ int (*mkdirat_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode);
+ int (*closedir_fn)(struct vfs_handle_struct *handle, DIR *dir);
+
+ /* File operations */
+
+ int (*openat_fn)(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how);
+ NTSTATUS (*create_file_fn)(struct vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs);
+ int (*close_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp);
+ ssize_t (*pread_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, void *data, size_t n, off_t offset);
+ struct tevent_req *(*pread_send_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset);
+ ssize_t (*pread_recv_fn)(struct tevent_req *req, struct vfs_aio_state *state);
+ ssize_t (*pwrite_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, const void *data, size_t n, off_t offset);
+ struct tevent_req *(*pwrite_send_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset);
+ ssize_t (*pwrite_recv_fn)(struct tevent_req *req, struct vfs_aio_state *state);
+ off_t (*lseek_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, off_t offset, int whence);
+ ssize_t (*sendfile_fn)(struct vfs_handle_struct *handle, int tofd, files_struct *fromfsp, const DATA_BLOB *header, off_t offset, size_t count);
+ ssize_t (*recvfile_fn)(struct vfs_handle_struct *handle, int fromfd, files_struct *tofsp, off_t offset, size_t count);
+ int (*renameat_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *srcdir_fsp,
+ const struct smb_filename *smb_fname_src,
+ struct files_struct *dstdir_fsp,
+ const struct smb_filename *smb_fname_dst);
+ struct tevent_req *(*fsync_send_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp);
+ int (*fsync_recv_fn)(struct tevent_req *req, struct vfs_aio_state *state);
+ int (*stat_fn)(struct vfs_handle_struct *handle, struct smb_filename *smb_fname);
+ int (*fstat_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, SMB_STRUCT_STAT *sbuf);
+ int (*lstat_fn)(struct vfs_handle_struct *handle, struct smb_filename *smb_filename);
+ int (*fstatat_fn)(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags);
+ uint64_t (*get_alloc_size_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, const SMB_STRUCT_STAT *sbuf);
+ int (*unlinkat_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *srcdir_fsp,
+ const struct smb_filename *smb_fname,
+ int flags);
+ int (*fchmod_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, mode_t mode);
+ int (*fchown_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, uid_t uid, gid_t gid);
+ int (*lchown_fn)(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid);
+ int (*chdir_fn)(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname);
+ struct smb_filename *(*getwd_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx);
+ int (*fntimes_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct smb_file_time *ft);
+ int (*ftruncate_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, off_t offset);
+ int (*fallocate_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len);
+ bool (*lock_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, int op, off_t offset, off_t count, int type);
+ int (*filesystem_sharemode_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_access, uint32_t
+ access_mask);
+ int (*fcntl_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int cmd, va_list cmd_arg);
+ int (*linux_setlease_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, int leasetype);
+ bool (*getlock_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid);
+ int (*symlinkat_fn)(struct vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname);
+ int (*readlinkat_fn)(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz);
+ int (*linkat_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ struct files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags);
+ int (*mknodat_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev);
+ struct smb_filename *(*realpath_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname);
+ int (*fchflags_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags);
+ struct file_id (*file_id_create_fn)(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf);
+ uint64_t (*fs_file_id_fn)(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf);
+ struct tevent_req *(*offload_read_send_fn)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy);
+ NTSTATUS (*offload_read_recv_fn)(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token_blob);
+ struct tevent_req *(*offload_write_send_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t to_copy);
+ NTSTATUS (*offload_write_recv_fn)(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied);
+ NTSTATUS (*fget_compression_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt);
+ NTSTATUS (*set_compression_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt);
+ NTSTATUS (*snap_check_path_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume);
+ NTSTATUS (*snap_create_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path);
+ NTSTATUS (*snap_delete_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path);
+
+ NTSTATUS (*fstreaminfo_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams);
+
+ NTSTATUS (*get_real_filename_at_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+
+ const char *(*connectpath_fn)(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname);
+
+ NTSTATUS (*brl_lock_windows_fn)(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock);
+
+ bool (*brl_unlock_windows_fn)(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock);
+
+ bool (*strict_lock_check_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock);
+
+ NTSTATUS (*translate_name_fn)(struct vfs_handle_struct *handle,
+ const char *name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name);
+
+ NTSTATUS (*parent_pathname_fn)(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out);
+
+ NTSTATUS (*fsctl_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags,
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len);
+
+ NTSTATUS (*fget_dos_attributes_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode);
+
+ NTSTATUS (*fset_dos_attributes_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode);
+
+ struct tevent_req *(*get_dos_attributes_send_fn)(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname);
+
+ NTSTATUS (*get_dos_attributes_recv_fn)(
+ struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode);
+
+ /* NT ACL operations. */
+
+ NTSTATUS (*fget_nt_acl_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+ NTSTATUS (*fset_nt_acl_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd);
+
+ NTSTATUS (*audit_file_fn)(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied);
+
+ /* POSIX ACL operations. */
+
+ SMB_ACL_T (*sys_acl_get_fd_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+ int (*sys_acl_blob_get_fd_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx, char **blob_description,
+ DATA_BLOB *blob);
+ int (*sys_acl_set_fd_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl);
+ int (*sys_acl_delete_def_fd_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp);
+
+ /* EA operations. */
+ struct tevent_req *(*getxattrat_send_fn)(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint);
+ ssize_t (*getxattrat_recv_fn)(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value);
+ ssize_t (*fgetxattr_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, void *value, size_t size);
+ ssize_t (*flistxattr_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, char *list, size_t size);
+ int (*fremovexattr_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name);
+ int (*fsetxattr_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, const void *value, size_t size, int flags);
+
+ /* aio operations */
+ bool (*aio_force_fn)(struct vfs_handle_struct *handle, struct files_struct *fsp);
+
+ /* durable handle operations */
+ NTSTATUS (*durable_cookie_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie);
+ NTSTATUS (*durable_disconnect_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie);
+ NTSTATUS (*durable_reconnect_fn)(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie);
+
+ NTSTATUS (*freaddir_attr_fn)(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **attr_data);
+};
+
+/*
+ VFS operation description. Each VFS module registers an array of vfs_op_tuple to VFS subsystem,
+ which describes all operations this module is willing to intercept.
+ VFS subsystem initializes then the conn->vfs_ops and conn->vfs_opaque_ops structs
+ using this information.
+*/
+
+typedef struct vfs_handle_struct {
+ struct vfs_handle_struct *next, *prev;
+ const char *param;
+ struct connection_struct *conn;
+ const struct vfs_fn_pointers *fns;
+ void *data;
+ void (*free_data)(void **data);
+} vfs_handle_struct;
+
+
+struct vfs_statvfs_struct {
+ /* For undefined recommended transfer size return -1 in that field */
+ uint32_t OptimalTransferSize; /* bsize on some os, iosize on other os */
+ uint32_t BlockSize;
+
+ /*
+ The next three fields are in terms of the block size.
+ (above). If block size is unknown, 4096 would be a
+ reasonable block size for a server to report.
+ Note that returning the blocks/blocksavail removes need
+ to make a second call (to QFSInfo level 0x103 to get this info.
+ UserBlockAvail is typically less than or equal to BlocksAvail,
+ if no distinction is made return the same value in each.
+ */
+
+ uint64_t TotalBlocks;
+ uint64_t BlocksAvail; /* bfree */
+ uint64_t UserBlocksAvail; /* bavail */
+
+ /* For undefined Node fields or FSID return -1 */
+ uint64_t TotalFileNodes;
+ uint64_t FreeFileNodes;
+ uint64_t FsIdentifier; /* fsid */
+ /* NB Namelen comes from FILE_SYSTEM_ATTRIBUTE_INFO call */
+ /* NB flags can come from FILE_SYSTEM_DEVICE_INFO call */
+
+ int FsCapabilities;
+};
+
+/* Add a new FSP extension of the given type. Returns a pointer to the
+ * extension data.
+ */
+#define VFS_ADD_FSP_EXTENSION(handle, fsp, type, destroy_fn) \
+ (type *)vfs_add_fsp_extension_notype(handle, (fsp), sizeof(type), (destroy_fn))
+
+/* Return a pointer to the existing FSP extension data. */
+#define VFS_FETCH_FSP_EXTENSION(handle, fsp) \
+ vfs_fetch_fsp_extension(handle, (fsp))
+
+/* Return the talloc context associated with an FSP extension. */
+#define VFS_MEMCTX_FSP_EXTENSION(handle, fsp) \
+ vfs_memctx_fsp_extension(handle, (fsp))
+
+/* Remove and destroy an FSP extension. */
+#define VFS_REMOVE_FSP_EXTENSION(handle, fsp) \
+ vfs_remove_fsp_extension((handle), (fsp))
+
+#define SMB_VFS_HANDLE_GET_DATA(handle, datap, type, ret) { \
+ if (!(handle)||((datap=(type *)(handle)->data)==NULL)) { \
+ DEBUG(0,("%s() failed to get vfs_handle->data!\n",__FUNCTION__)); \
+ ret; \
+ } \
+}
+
+#define SMB_VFS_HANDLE_SET_DATA(handle, datap, free_fn, type, ret) { \
+ if (!(handle)) { \
+ DEBUG(0,("%s() failed to set handle->data!\n",__FUNCTION__)); \
+ ret; \
+ } else { \
+ if ((handle)->free_data) { \
+ (handle)->free_data(&(handle)->data); \
+ } \
+ (handle)->data = (void *)datap; \
+ (handle)->free_data = free_fn; \
+ } \
+}
+
+#define SMB_VFS_HANDLE_FREE_DATA(handle) { \
+ if ((handle) && (handle)->free_data) { \
+ (handle)->free_data(&(handle)->data); \
+ } \
+}
+
+/* Check whether module-specific data handle was already allocated or not */
+#define SMB_VFS_HANDLE_TEST_DATA(handle) ( !(handle) || !(handle)->data ? False : True )
+
+#define SMB_VFS_OP(x) ((void *) x)
+
+#define DEFAULT_VFS_MODULE_NAME "/[Default VFS]/"
+
+#include "vfs_macros.h"
+
+struct smb_vfs_deny_state {
+ struct smb_vfs_deny_state *parent;
+ const char *location;
+};
+
+void smb_vfs_assert_allowed(void);
+
+void _smb_vfs_deny_push(struct smb_vfs_deny_state *state, const char *location);
+#define smb_vfs_deny_push(__state) _smb_vfs_deny_push(__state, __location__);
+void _smb_vfs_deny_pop(struct smb_vfs_deny_state *state, const char *location);
+#define smb_vfs_deny_pop(__state) _smb_vfs_deny_pop(__state, __location__);
+
+int smb_vfs_call_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user);
+void smb_vfs_call_disconnect(struct vfs_handle_struct *handle);
+uint64_t smb_vfs_call_disk_free(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_filename,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize);
+int smb_vfs_call_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_filename,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt);
+int smb_vfs_call_set_quota(struct vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype, unid_t id,
+ SMB_DISK_QUOTA *qt);
+int smb_vfs_call_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels);
+int smb_vfs_call_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf);
+uint32_t smb_vfs_call_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res);
+/*
+ * Note: that "struct dfs_GetDFSReferral *r" needs to be a valid TALLOC_CTX
+ */
+NTSTATUS smb_vfs_call_get_dfs_referrals(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r);
+NTSTATUS smb_vfs_call_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count);
+NTSTATUS smb_vfs_call_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count);
+DIR *smb_vfs_call_fdopendir(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *mask,
+ uint32_t attributes);
+struct dirent *smb_vfs_call_readdir(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp);
+void smb_vfs_call_rewind_dir(struct vfs_handle_struct *handle,
+ DIR *dirp);
+int smb_vfs_call_mkdirat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode);
+int smb_vfs_call_closedir(struct vfs_handle_struct *handle,
+ DIR *dir);
+int smb_vfs_call_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how);
+NTSTATUS smb_vfs_call_create_file(struct vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs);
+int smb_vfs_call_close(struct vfs_handle_struct *handle,
+ struct files_struct *fsp);
+ssize_t smb_vfs_call_pread(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, void *data, size_t n,
+ off_t offset);
+struct tevent_req *smb_vfs_call_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset);
+ssize_t SMB_VFS_PREAD_RECV(struct tevent_req *req, struct vfs_aio_state *state);
+
+ssize_t smb_vfs_call_pwrite(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const void *data,
+ size_t n, off_t offset);
+struct tevent_req *smb_vfs_call_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset);
+ssize_t SMB_VFS_PWRITE_RECV(struct tevent_req *req, struct vfs_aio_state *state);
+
+off_t smb_vfs_call_lseek(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t offset,
+ int whence);
+ssize_t smb_vfs_call_sendfile(struct vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp, const DATA_BLOB *header,
+ off_t offset, size_t count);
+ssize_t smb_vfs_call_recvfile(struct vfs_handle_struct *handle, int fromfd,
+ files_struct *tofsp, off_t offset,
+ size_t count);
+int smb_vfs_call_renameat(struct vfs_handle_struct *handle,
+ struct files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ struct files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst);
+
+struct tevent_req *smb_vfs_call_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp);
+int SMB_VFS_FSYNC_RECV(struct tevent_req *req, struct vfs_aio_state *state);
+
+int smb_vfs_fsync_sync(files_struct *fsp);
+int smb_vfs_call_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname);
+int smb_vfs_call_fstat(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, SMB_STRUCT_STAT *sbuf);
+int smb_vfs_call_lstat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_filename);
+int smb_vfs_call_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags);
+uint64_t smb_vfs_call_get_alloc_size(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf);
+int smb_vfs_call_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags);
+int smb_vfs_call_fchmod(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, mode_t mode);
+int smb_vfs_call_fchown(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, uid_t uid, gid_t gid);
+int smb_vfs_call_lchown(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid);
+int smb_vfs_call_chdir(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname);
+struct smb_filename *smb_vfs_call_getwd(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx);
+int smb_vfs_call_ntimes(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct smb_file_time *ft);
+int smb_vfs_call_fntimes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct smb_file_time *ft);
+int smb_vfs_call_ftruncate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t offset);
+int smb_vfs_call_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len);
+bool smb_vfs_call_lock(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int op, off_t offset,
+ off_t count, int type);
+int smb_vfs_call_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask);
+int smb_vfs_call_fcntl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int cmd, ...);
+int smb_vfs_call_linux_setlease(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int leasetype);
+bool smb_vfs_call_getlock(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t *poffset,
+ off_t *pcount, int *ptype, pid_t *ppid);
+int smb_vfs_call_symlinkat(struct vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname);
+int smb_vfs_call_readlinkat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz);
+int smb_vfs_call_linkat(struct vfs_handle_struct *handle,
+ struct files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ struct files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags);
+int smb_vfs_call_mknodat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev);
+struct smb_filename *smb_vfs_call_realpath(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname);
+int smb_vfs_call_chflags(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ unsigned int flags);
+int smb_vfs_call_fchflags(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags);
+struct file_id smb_vfs_call_file_id_create(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf);
+uint64_t smb_vfs_call_fs_file_id(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf);
+NTSTATUS smb_vfs_call_fstreaminfo(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams);
+NTSTATUS smb_vfs_call_get_real_filename_at(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+const char *smb_vfs_call_connectpath(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname);
+NTSTATUS smb_vfs_call_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock);
+bool smb_vfs_call_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock);
+bool smb_vfs_call_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock);
+NTSTATUS smb_vfs_call_translate_name(struct vfs_handle_struct *handle,
+ const char *name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name);
+NTSTATUS smb_vfs_call_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out);
+NTSTATUS smb_vfs_call_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags,
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len);
+NTSTATUS smb_vfs_call_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode);
+NTSTATUS smb_vfs_call_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode);
+struct tevent_req *smb_vfs_call_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname);
+NTSTATUS smb_vfs_call_get_dos_attributes_recv(
+ struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode);
+struct tevent_req *smb_vfs_call_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy);
+NTSTATUS smb_vfs_call_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token_blob);
+struct tevent_req *smb_vfs_call_offload_write_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num);
+NTSTATUS smb_vfs_call_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied);
+NTSTATUS smb_vfs_call_fget_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt);
+NTSTATUS smb_vfs_call_set_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt);
+NTSTATUS smb_vfs_call_snap_check_path(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume);
+NTSTATUS smb_vfs_call_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path);
+NTSTATUS smb_vfs_call_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path);
+NTSTATUS smb_vfs_call_fget_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+NTSTATUS smb_vfs_call_get_nt_acl_at(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+NTSTATUS smb_vfs_call_fset_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd);
+NTSTATUS smb_vfs_call_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied);
+int smb_vfs_call_chmod_acl(struct vfs_handle_struct *handle,
+ const struct smb_filename *file,
+ mode_t mode);
+SMB_ACL_T smb_vfs_call_sys_acl_get_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+int smb_vfs_call_sys_acl_blob_get_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob);
+int smb_vfs_call_sys_acl_set_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl);
+int smb_vfs_call_sys_acl_delete_def_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp);
+struct tevent_req *smb_vfs_call_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint);
+ssize_t smb_vfs_call_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value);
+ssize_t smb_vfs_call_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ void *value, size_t size);
+ssize_t smb_vfs_call_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size);
+int smb_vfs_call_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name);
+int smb_vfs_call_lsetxattr(struct vfs_handle_struct *handle, const char *path,
+ const char *name, const void *value, size_t size,
+ int flags);
+int smb_vfs_call_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ const void *value, size_t size, int flags);
+bool smb_vfs_call_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp);
+NTSTATUS smb_vfs_call_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie);
+NTSTATUS smb_vfs_call_durable_disconnect(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie);
+NTSTATUS smb_vfs_call_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie);
+NTSTATUS smb_vfs_call_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **attr_data);
+
+NTSTATUS smb_register_vfs(int version, const char *name,
+ const struct vfs_fn_pointers *fns);
+void *vfs_add_fsp_extension_notype(vfs_handle_struct *handle,
+ files_struct *fsp, size_t ext_size,
+ void (*destroy_fn)(void *p_data));
+void vfs_remove_fsp_extension(vfs_handle_struct *handle, files_struct *fsp);
+void vfs_remove_all_fsp_extensions(struct files_struct *fsp);
+void *vfs_memctx_fsp_extension(vfs_handle_struct *handle,
+ const struct files_struct *fsp);
+void *vfs_fetch_fsp_extension(vfs_handle_struct *handle, const struct files_struct *fsp);
+
+void smb_vfs_assert_all_fns(const struct vfs_fn_pointers* fns,
+ const char *module);
+
+/*
+ * Helper functions from source3/modules/vfs_not_implemented.c
+ */
+int vfs_not_implemented_connect(
+ vfs_handle_struct *handle,
+ const char *service,
+ const char *user);
+void vfs_not_implemented_disconnect(vfs_handle_struct *handle);
+uint64_t vfs_not_implemented_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize);
+int vfs_not_implemented_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq);
+int vfs_not_implemented_set_quota(vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id, SMB_DISK_QUOTA *dq);
+int vfs_not_implemented_get_shadow_copy_data(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels);
+int vfs_not_implemented_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf);
+uint32_t vfs_not_implemented_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res);
+NTSTATUS vfs_not_implemented_get_dfs_referrals(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r);
+NTSTATUS vfs_not_implemented_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count);
+NTSTATUS vfs_not_implemented_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count);
+NTSTATUS vfs_not_implemented_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume);
+NTSTATUS vfs_not_implemented_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path);
+NTSTATUS vfs_not_implemented_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path);
+DIR *vfs_not_implemented_fdopendir(vfs_handle_struct *handle, files_struct *fsp,
+ const char *mask, uint32_t attr);
+struct dirent *vfs_not_implemented_readdir(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp);
+void vfs_not_implemented_rewind_dir(vfs_handle_struct *handle, DIR *dirp);
+int vfs_not_implemented_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode);
+int vfs_not_implemented_closedir(vfs_handle_struct *handle, DIR *dir);
+int vfs_not_implemented_open(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ files_struct *fsp, int flags, mode_t mode);
+int vfs_not_implemented_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how);
+NTSTATUS vfs_not_implemented_create_file(struct vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result, int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs);
+int vfs_not_implemented_close_fn(vfs_handle_struct *handle, files_struct *fsp);
+ssize_t vfs_not_implemented_pread(vfs_handle_struct *handle, files_struct *fsp,
+ void *data, size_t n, off_t offset);
+struct tevent_req *vfs_not_implemented_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data, size_t n, off_t offset);
+ssize_t vfs_not_implemented_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state);
+ssize_t vfs_not_implemented_pwrite(vfs_handle_struct *handle, files_struct *fsp,
+ const void *data, size_t n, off_t offset);
+struct tevent_req *vfs_not_implemented_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset);
+ssize_t vfs_not_implemented_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state);
+off_t vfs_not_implemented_lseek(vfs_handle_struct *handle, files_struct *fsp,
+ off_t offset, int whence);
+ssize_t vfs_not_implemented_sendfile(vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp, const DATA_BLOB *hdr,
+ off_t offset, size_t n);
+ssize_t vfs_not_implemented_recvfile(vfs_handle_struct *handle, int fromfd,
+ files_struct *tofsp, off_t offset, size_t n);
+int vfs_not_implemented_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst);
+struct tevent_req *vfs_not_implemented_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp);
+int vfs_not_implemented_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state);
+int vfs_not_implemented_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname);
+int vfs_not_implemented_fstat(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf);
+int vfs_not_implemented_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname);
+int vfs_not_implemented_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags);
+uint64_t vfs_not_implemented_get_alloc_size(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf);
+int vfs_not_implemented_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags);
+int vfs_not_implemented_fchmod(vfs_handle_struct *handle, files_struct *fsp,
+ mode_t mode);
+int vfs_not_implemented_fchown(vfs_handle_struct *handle, files_struct *fsp,
+ uid_t uid, gid_t gid);
+int vfs_not_implemented_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid);
+int vfs_not_implemented_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname);
+struct smb_filename *vfs_not_implemented_getwd(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx);
+int vfs_not_implemented_ntimes(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct smb_file_time *ft);
+int vfs_not_implemented_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft);
+int vfs_not_implemented_ftruncate(vfs_handle_struct *handle, files_struct *fsp,
+ off_t offset);
+int vfs_not_implemented_fallocate(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t mode, off_t offset, off_t len);
+bool vfs_not_implemented_lock(vfs_handle_struct *handle, files_struct *fsp, int op,
+ off_t offset, off_t count, int type);
+int vfs_not_implemented_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask);
+int vfs_not_implemented_fcntl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int cmd, va_list cmd_arg);
+int vfs_not_implemented_linux_setlease(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int leasetype);
+bool vfs_not_implemented_getlock(vfs_handle_struct *handle, files_struct *fsp,
+ off_t *poffset, off_t *pcount, int *ptype,
+ pid_t *ppid);
+int vfs_not_implemented_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname);
+int vfs_not_implemented_vfs_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz);
+int vfs_not_implemented_linkat(vfs_handle_struct *handle,
+ struct files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ struct files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags);
+int vfs_not_implemented_mknodat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev);
+struct smb_filename *vfs_not_implemented_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname);
+int vfs_not_implemented_chflags(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint flags);
+int vfs_not_implemented_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fst,
+ uint flags);
+struct file_id vfs_not_implemented_file_id_create(vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf);
+uint64_t vfs_not_implemented_fs_file_id(vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf);
+struct tevent_req *vfs_not_implemented_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy);
+NTSTATUS vfs_not_implemented_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *_token_blob);
+struct tevent_req *vfs_not_implemented_offload_write_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num);
+NTSTATUS vfs_not_implemented_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied);
+NTSTATUS vfs_not_implemented_fget_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt);
+NTSTATUS vfs_not_implemented_set_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt);
+NTSTATUS vfs_not_implemented_fstreaminfo(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams);
+NTSTATUS vfs_not_implemented_get_real_filename(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *path,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+NTSTATUS vfs_not_implemented_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+const char *vfs_not_implemented_connectpath(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname);
+NTSTATUS vfs_not_implemented_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock);
+bool vfs_not_implemented_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock);
+bool vfs_not_implemented_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock);
+NTSTATUS vfs_not_implemented_translate_name(struct vfs_handle_struct *handle,
+ const char *mapped_name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx, char **pmapped_name);
+NTSTATUS vfs_not_implemented_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out);
+NTSTATUS vfs_not_implemented_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags, /* Needed for UNICODE ... */
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len, uint32_t *out_len);
+NTSTATUS vfs_not_implemented_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **pattr_data);
+struct tevent_req *vfs_not_implemented_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname);
+NTSTATUS vfs_not_implemented_get_dos_attributes_recv(
+ struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode);
+NTSTATUS vfs_not_implemented_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode);
+NTSTATUS vfs_not_implemented_set_dos_attributes(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint32_t dosmode);
+NTSTATUS vfs_not_implemented_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode);
+NTSTATUS vfs_not_implemented_fget_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+NTSTATUS vfs_not_implemented_get_nt_acl_at(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+NTSTATUS vfs_not_implemented_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd);
+SMB_ACL_T vfs_not_implemented_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+int vfs_not_implemented_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp, TALLOC_CTX *mem_ctx,
+ char **blob_description, DATA_BLOB *blob);
+int vfs_not_implemented_sys_acl_set_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl);
+int vfs_not_implemented_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp);
+struct tevent_req *vfs_not_implemented_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint);
+ssize_t vfs_not_implemented_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value);
+ssize_t vfs_not_implemented_fgetxattr(vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ void *value, size_t size);
+ssize_t vfs_not_implemented_listxattr(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ char *list,
+ size_t size);
+ssize_t vfs_not_implemented_flistxattr(vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size);
+int vfs_not_implemented_fremovexattr(vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name);
+int vfs_not_implemented_setxattr(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags);
+int vfs_not_implemented_fsetxattr(vfs_handle_struct *handle, struct files_struct *fsp,
+ const char *name, const void *value, size_t size,
+ int flags);
+bool vfs_not_implemented_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp);
+NTSTATUS vfs_not_implemented_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied);
+NTSTATUS vfs_not_implemented_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie);
+NTSTATUS vfs_not_implemented_durable_disconnect(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie);
+NTSTATUS vfs_not_implemented_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie);
+#endif /* _VFS_H */
diff --git a/source3/include/vfs_macros.h b/source3/include/vfs_macros.h
new file mode 100644
index 0000000..9196f6e
--- /dev/null
+++ b/source3/include/vfs_macros.h
@@ -0,0 +1,592 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS wrapper macros
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VFS_MACROS_H
+#define _VFS_MACROS_H
+
+/*
+ * These macros SMB_VFS_<FOO> (and SMB_VFS_NEXT_<FOO>) are our
+ * interface for the VFS.
+ *
+ * Don't access conn->vfs_handles[->next]->fns->* directly!
+ */
+
+/* Disk operations */
+#define SMB_VFS_CONNECT(conn, service, user) \
+ smb_vfs_call_connect((conn)->vfs_handles, (service), (user))
+#define SMB_VFS_NEXT_CONNECT(handle, service, user) \
+ smb_vfs_call_connect((handle)->next, (service), (user))
+
+#define SMB_VFS_DISCONNECT(conn) \
+ smb_vfs_call_disconnect((conn)->vfs_handles)
+#define SMB_VFS_NEXT_DISCONNECT(handle) \
+ smb_vfs_call_disconnect((handle)->next)
+
+#define SMB_VFS_DISK_FREE(conn, smb_fname, bsize, dfree ,dsize) \
+ smb_vfs_call_disk_free((conn)->vfs_handles, (smb_fname), (bsize), (dfree), (dsize))
+#define SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, bsize, dfree ,dsize)\
+ smb_vfs_call_disk_free((handle)->next, (smb_fname), (bsize), (dfree), (dsize))
+
+#define SMB_VFS_GET_QUOTA(conn, smb_fname, qtype, id, qt) \
+ smb_vfs_call_get_quota((conn)->vfs_handles, (smb_fname), (qtype), (id), (qt))
+#define SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, qt) \
+ smb_vfs_call_get_quota((handle)->next, (smb_fname), (qtype), (id), (qt))
+
+#define SMB_VFS_SET_QUOTA(conn, qtype, id, qt) \
+ smb_vfs_call_set_quota((conn)->vfs_handles, (qtype), (id), (qt))
+#define SMB_VFS_NEXT_SET_QUOTA(handle, qtype, id, qt) \
+ smb_vfs_call_set_quota((handle)->next, (qtype), (id), (qt))
+
+#define SMB_VFS_GET_SHADOW_COPY_DATA(fsp,shadow_copy_data,labels) \
+ smb_vfs_call_get_shadow_copy_data((fsp)->conn->vfs_handles, (fsp), (shadow_copy_data), (labels))
+#define SMB_VFS_NEXT_GET_SHADOW_COPY_DATA(handle, fsp, shadow_copy_data ,labels) \
+ smb_vfs_call_get_shadow_copy_data((handle)->next, (fsp), (shadow_copy_data), (labels))
+
+#define SMB_VFS_STATVFS(conn, smb_fname, statbuf) \
+ smb_vfs_call_statvfs((conn)->vfs_handles, (smb_fname), (statbuf))
+#define SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf) \
+ smb_vfs_call_statvfs((handle)->next, (smb_fname), (statbuf))
+
+#define SMB_VFS_FS_CAPABILITIES(conn, p_ts_res) \
+ smb_vfs_call_fs_capabilities((conn)->vfs_handles, (p_ts_res))
+#define SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) \
+ smb_vfs_call_fs_capabilities((handle)->next, (p_ts_res))
+
+/*
+ * Note: that "struct dfs_GetDFSReferral *r"
+ * needs to be a valid TALLOC_CTX
+ */
+#define SMB_VFS_GET_DFS_REFERRALS(conn, r) \
+ smb_vfs_call_get_dfs_referrals((conn)->vfs_handles, (r))
+#define SMB_VFS_NEXT_GET_DFS_REFERRALS(handle, r) \
+ smb_vfs_call_get_dfs_referrals((handle)->next, (r))
+
+#define SMB_VFS_CREATE_DFS_PATHAT(conn, dirfsp, smb_fname, reflist, count) \
+ smb_vfs_call_create_dfs_pathat((conn)->vfs_handles, \
+ (dirfsp), \
+ (smb_fname), \
+ (reflist), \
+ (count))
+#define SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle, dirfsp, smb_fname, reflist, count) \
+ smb_vfs_call_create_dfs_pathat((handle)->next, \
+ (dirfsp), \
+ (smb_fname), \
+ (reflist), \
+ (count))
+#define SMB_VFS_READ_DFS_PATHAT(conn, mem_ctx, dirfsp, smb_fname, ppreflist, pcount) \
+ smb_vfs_call_read_dfs_pathat((conn)->vfs_handles, \
+ (mem_ctx), \
+ (dirfsp), \
+ (smb_fname), \
+ (ppreflist), \
+ (pcount))
+#define SMB_VFS_NEXT_READ_DFS_PATHAT(handle, mem_ctx, dirfsp, smb_fname, ppreflist, pcount) \
+ smb_vfs_call_read_dfs_pathat((handle)->next, \
+ (mem_ctx), \
+ (dirfsp), \
+ (smb_fname), \
+ (ppreflist), \
+ (pcount))
+
+/* Directory operations */
+#define SMB_VFS_FDOPENDIR(fsp, mask, attr) \
+ smb_vfs_call_fdopendir((fsp)->conn->vfs_handles, (fsp), (mask), (attr))
+#define SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr) \
+ smb_vfs_call_fdopendir((handle)->next, (fsp), (mask), (attr))
+
+#define SMB_VFS_READDIR(conn, dirfsp, dirp) \
+ smb_vfs_call_readdir((conn)->vfs_handles, (dirfsp), (dirp))
+#define SMB_VFS_NEXT_READDIR(handle, dirfsp, dirp) \
+ smb_vfs_call_readdir((handle)->next, (dirfsp), (dirp))
+
+#define SMB_VFS_REWINDDIR(conn, dirp) \
+ smb_vfs_call_rewind_dir((conn)->vfs_handles, (dirp))
+#define SMB_VFS_NEXT_REWINDDIR(handle, dirp) \
+ smb_vfs_call_rewind_dir((handle)->next, (dirp))
+
+#define SMB_VFS_MKDIRAT(conn, dirfsp, smb_fname, mode) \
+ smb_vfs_call_mkdirat((conn)->vfs_handles,(dirfsp), (smb_fname), (mode))
+#define SMB_VFS_NEXT_MKDIRAT(handle, dirfsp, smb_fname, mode) \
+ smb_vfs_call_mkdirat((handle)->next,(dirfsp), (smb_fname), (mode))
+
+#define SMB_VFS_CLOSEDIR(conn, dir) \
+ smb_vfs_call_closedir((conn)->vfs_handles, dir)
+#define SMB_VFS_NEXT_CLOSEDIR(handle, dir) \
+ smb_vfs_call_closedir((handle)->next, (dir))
+
+/* File operations */
+#define SMB_VFS_OPENAT(conn, dirfsp, smb_fname, fsp, how) \
+ smb_vfs_call_openat( \
+ (conn)->vfs_handles, (dirfsp), (smb_fname), (fsp), (how))
+#define SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how) \
+ smb_vfs_call_openat( \
+ (handle)->next, (dirfsp), (smb_fname), (fsp), (how))
+
+#define SMB_VFS_CREATE_FILE(conn, req, dirfsp, smb_fname, access_mask, share_access, create_disposition, \
+ create_options, file_attributes, oplock_request, lease, allocation_size, private_flags, sd, ea_list, result, pinfo, in_context_blobs, out_context_blobs) \
+ smb_vfs_call_create_file((conn)->vfs_handles, (req), (dirfsp), (smb_fname), (access_mask), (share_access), (create_disposition), \
+ (create_options), (file_attributes), (oplock_request), (lease), (allocation_size), (private_flags), (sd), (ea_list), (result), (pinfo), \
+ (in_context_blobs), (out_context_blobs))
+#define SMB_VFS_NEXT_CREATE_FILE(handle, req, dirfsp, smb_fname, access_mask, share_access, create_disposition, \
+ create_options, file_attributes, oplock_request, lease, allocation_size, private_flags, sd, ea_list, result, pinfo, in_context_blobs, out_context_blobs) \
+ smb_vfs_call_create_file((handle)->next, (req), (dirfsp), (smb_fname), (access_mask), (share_access), (create_disposition), \
+ (create_options), (file_attributes), (oplock_request), (lease), (allocation_size), (private_flags), (sd), (ea_list), (result), (pinfo), \
+ (in_context_blobs), (out_context_blobs))
+
+#define SMB_VFS_CLOSE(fsp) \
+ smb_vfs_call_close((fsp)->conn->vfs_handles, (fsp))
+#define SMB_VFS_NEXT_CLOSE(handle, fsp) \
+ smb_vfs_call_close((handle)->next, (fsp))
+
+#define SMB_VFS_PREAD(fsp, data, n, off) \
+ smb_vfs_call_pread((fsp)->conn->vfs_handles, (fsp), (data), (n), (off))
+#define SMB_VFS_NEXT_PREAD(handle, fsp, data, n, off) \
+ smb_vfs_call_pread((handle)->next, (fsp), (data), (n), (off))
+
+#define SMB_VFS_PREAD_SEND(mem_ctx, ev, fsp, data, n, off) \
+ smb_vfs_call_pread_send((fsp)->conn->vfs_handles, (mem_ctx), (ev), \
+ (fsp), (data), (n), (off))
+#define SMB_VFS_NEXT_PREAD_SEND(mem_ctx, ev, handle, fsp, data, n, off) \
+ smb_vfs_call_pread_send((handle)->next, (mem_ctx), (ev), (fsp), \
+ (data), (n), (off))
+
+#define SMB_VFS_PWRITE(fsp, data, n, off) \
+ smb_vfs_call_pwrite((fsp)->conn->vfs_handles, (fsp), (data), (n), (off))
+#define SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, off) \
+ smb_vfs_call_pwrite((handle)->next, (fsp), (data), (n), (off))
+
+#define SMB_VFS_PWRITE_SEND(mem_ctx, ev, fsp, data, n, off) \
+ smb_vfs_call_pwrite_send((fsp)->conn->vfs_handles, (mem_ctx), (ev), \
+ (fsp), (data), (n), (off))
+#define SMB_VFS_NEXT_PWRITE_SEND(mem_ctx, ev, handle, fsp, data, n, off) \
+ smb_vfs_call_pwrite_send((handle)->next, (mem_ctx), (ev), (fsp), \
+ (data), (n), (off))
+
+#define SMB_VFS_LSEEK(fsp, offset, whence) \
+ smb_vfs_call_lseek((fsp)->conn->vfs_handles, (fsp), (offset), (whence))
+#define SMB_VFS_NEXT_LSEEK(handle, fsp, offset, whence) \
+ smb_vfs_call_lseek((handle)->next, (fsp), (offset), (whence))
+
+#define SMB_VFS_SENDFILE(tofd, fromfsp, header, offset, count) \
+ smb_vfs_call_sendfile((fromfsp)->conn->vfs_handles, (tofd), (fromfsp), (header), (offset), (count))
+#define SMB_VFS_NEXT_SENDFILE(handle, tofd, fromfsp, header, offset, count) \
+ smb_vfs_call_sendfile((handle)->next, (tofd), (fromfsp), (header), (offset), (count))
+
+#define SMB_VFS_RECVFILE(fromfd, tofsp, offset, count) \
+ smb_vfs_call_recvfile((tofsp)->conn->vfs_handles, (fromfd), (tofsp), (offset), (count))
+#define SMB_VFS_NEXT_RECVFILE(handle, fromfd, tofsp, offset, count) \
+ smb_vfs_call_recvfile((handle)->next, (fromfd), (tofsp), (offset), (count))
+
+#define SMB_VFS_RENAMEAT(conn, oldfsp, old, newfsp, new) \
+ smb_vfs_call_renameat((conn)->vfs_handles, (oldfsp), (old), (newfsp), (new))
+#define SMB_VFS_NEXT_RENAMEAT(handle, oldfsp, old, newfsp, new) \
+ smb_vfs_call_renameat((handle)->next, (oldfsp), (old), (newfsp), (new))
+
+#define SMB_VFS_FSYNC_SEND(mem_ctx, ev, fsp) \
+ smb_vfs_call_fsync_send((fsp)->conn->vfs_handles, (mem_ctx), (ev), \
+ (fsp))
+#define SMB_VFS_NEXT_FSYNC_SEND(mem_ctx, ev, handle, fsp) \
+ smb_vfs_call_fsync_send((handle)->next, (mem_ctx), (ev), (fsp))
+
+#define SMB_VFS_STAT(conn, smb_fname) \
+ smb_vfs_call_stat((conn)->vfs_handles, (smb_fname))
+#define SMB_VFS_NEXT_STAT(handle, smb_fname) \
+ smb_vfs_call_stat((handle)->next, (smb_fname))
+
+#define SMB_VFS_FSTAT(fsp, sbuf) \
+ smb_vfs_call_fstat((fsp)->conn->vfs_handles, (fsp), (sbuf))
+#define SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf) \
+ smb_vfs_call_fstat((handle)->next, (fsp), (sbuf))
+
+#define SMB_VFS_LSTAT(conn, smb_fname) \
+ smb_vfs_call_lstat((conn)->vfs_handles, (smb_fname))
+#define SMB_VFS_NEXT_LSTAT(handle, smb_fname) \
+ smb_vfs_call_lstat((handle)->next, (smb_fname))
+
+#define SMB_VFS_FSTATAT(conn, dirfsp, smb_fname, sbuf, flags) \
+ smb_vfs_call_fstatat((conn)->vfs_handles, (dirfsp), (smb_fname), \
+ (sbuf), (flags))
+#define SMB_VFS_NEXT_FSTATAT(conn, dirfsp, smb_fname, sbuf, flags) \
+ smb_vfs_call_fstatat((handle)->next, (dirfsp), (smb_fname), \
+ (sbuf), (flags))
+
+#define SMB_VFS_GET_ALLOC_SIZE(conn, fsp, sbuf) \
+ smb_vfs_call_get_alloc_size((conn)->vfs_handles, (fsp), (sbuf))
+#define SMB_VFS_NEXT_GET_ALLOC_SIZE(conn, fsp, sbuf) \
+ smb_vfs_call_get_alloc_size((conn)->next, (fsp), (sbuf))
+
+#define SMB_VFS_UNLINKAT(conn, dirfsp, path, flags) \
+ smb_vfs_call_unlinkat((conn)->vfs_handles, (dirfsp), (path), (flags))
+#define SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, path, flags) \
+ smb_vfs_call_unlinkat((handle)->next, (dirfsp), (path), (flags))
+
+#define SMB_VFS_FCHMOD(fsp, mode) \
+ smb_vfs_call_fchmod((fsp)->conn->vfs_handles, (fsp), (mode))
+#define SMB_VFS_NEXT_FCHMOD(handle, fsp, mode) \
+ smb_vfs_call_fchmod((handle)->next, (fsp), (mode))
+
+#define SMB_VFS_FCHOWN(fsp, uid, gid) \
+ smb_vfs_call_fchown((fsp)->conn->vfs_handles, (fsp), (uid), (gid))
+#define SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid) \
+ smb_vfs_call_fchown((handle)->next, (fsp), (uid), (gid))
+
+#define SMB_VFS_LCHOWN(conn, smb_fname, uid, gid) \
+ smb_vfs_call_lchown((conn)->vfs_handles, (smb_fname), (uid), (gid))
+#define SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid) \
+ smb_vfs_call_lchown((handle)->next, (smb_fname), (uid), (gid))
+
+#define SMB_VFS_CHDIR(conn, smb_fname) \
+ smb_vfs_call_chdir((conn)->vfs_handles, (smb_fname))
+#define SMB_VFS_NEXT_CHDIR(handle, smb_fname) \
+ smb_vfs_call_chdir((handle)->next, (smb_fname))
+
+#define SMB_VFS_GETWD(conn, ctx) \
+ smb_vfs_call_getwd((conn)->vfs_handles, (ctx))
+#define SMB_VFS_NEXT_GETWD(handle, ctx) \
+ smb_vfs_call_getwd((handle)->next, (ctx))
+
+#define SMB_VFS_FNTIMES(fsp, ts) \
+ smb_vfs_call_fntimes((fsp)->conn->vfs_handles, (fsp), (ts))
+#define SMB_VFS_NEXT_FNTIMES(handle, fsp, ts) \
+ smb_vfs_call_fntimes((handle)->next, (fsp), (ts))
+
+#define SMB_VFS_FTRUNCATE(fsp, offset) \
+ smb_vfs_call_ftruncate((fsp)->conn->vfs_handles, (fsp), (offset))
+#define SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset) \
+ smb_vfs_call_ftruncate((handle)->next, (fsp), (offset))
+
+#define SMB_VFS_FALLOCATE(fsp, mode, offset, len) \
+ smb_vfs_call_fallocate((fsp)->conn->vfs_handles, (fsp), (mode), (offset), (len))
+#define SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len) \
+ smb_vfs_call_fallocate((handle)->next, (fsp), (mode), (offset), (len))
+
+#define SMB_VFS_LOCK(fsp, op, offset, count, type) \
+ smb_vfs_call_lock((fsp)->conn->vfs_handles, (fsp), (op), (offset), (count), (type))
+#define SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type) \
+ smb_vfs_call_lock((handle)->next, (fsp), (op), (offset), (count), (type))
+
+#define SMB_VFS_FILESYSTEM_SHAREMODE(fsp, share_access, access_mask) \
+ smb_vfs_call_filesystem_sharemode((fsp)->conn->vfs_handles, \
+ (fsp), \
+ (share_access), \
+ (access_mask))
+#define SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle, fsp, share_access, \
+ access_mask) \
+ smb_vfs_call_filesystem_sharemode((handle)->next, \
+ (fsp), \
+ (share_access), \
+ (access_mask))
+
+#define SMB_VFS_FCNTL(fsp, cmd, ...) \
+ smb_vfs_call_fcntl((fsp)->conn->vfs_handles, (fsp), (cmd), (__VA_ARGS__))
+#define SMB_VFS_NEXT_FCNTL(handle, fsp, cmd, ...) \
+ smb_vfs_call_fcntl((handle)->next, (fsp), (cmd), (__VA_ARGS__))
+
+#define SMB_VFS_LINUX_SETLEASE(fsp, leasetype) \
+ smb_vfs_call_linux_setlease((fsp)->conn->vfs_handles, (fsp), (leasetype))
+#define SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype) \
+ smb_vfs_call_linux_setlease((handle)->next, (fsp), (leasetype))
+
+#define SMB_VFS_GETLOCK(fsp, poffset, pcount, ptype, ppid) \
+ smb_vfs_call_getlock((fsp)->conn->vfs_handles, (fsp), (poffset), (pcount), (ptype), (ppid))
+#define SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, pcount, ptype, ppid) \
+ smb_vfs_call_getlock((handle)->next, (fsp), (poffset), (pcount), (ptype), (ppid))
+
+#define SMB_VFS_SYMLINKAT(conn, oldpath, dirfsp, newpath) \
+ smb_vfs_call_symlinkat((conn)->vfs_handles, (oldpath), (dirfsp), (newpath))
+#define SMB_VFS_NEXT_SYMLINKAT(handle, oldpath, dirfsp, newpath) \
+ smb_vfs_call_symlinkat((handle)->next, (oldpath), (dirfsp), (newpath))
+
+#define SMB_VFS_READLINKAT(conn, dirfsp, smb_fname, buf, bufsiz) \
+ smb_vfs_call_readlinkat((conn)->vfs_handles, (dirfsp), (smb_fname), (buf), (bufsiz))
+#define SMB_VFS_NEXT_READLINKAT(handle, dirfsp, smb_fname, buf, bufsiz) \
+ smb_vfs_call_readlinkat((handle)->next, (dirfsp), (smb_fname), (buf), (bufsiz))
+
+#define SMB_VFS_LINKAT(conn, srcfsp, oldpath, dstfsp, newpath, flags) \
+ smb_vfs_call_linkat((conn)->vfs_handles, (srcfsp), (oldpath), (dstfsp), (newpath), (flags))
+#define SMB_VFS_NEXT_LINKAT(handle, srcfsp, oldpath, dstfsp, newpath, flags) \
+ smb_vfs_call_linkat((handle)->next, (srcfsp), (oldpath), (dstfsp), (newpath), (flags))
+
+#define SMB_VFS_MKNODAT(conn, dirfsp, smb_fname, mode, dev) \
+ smb_vfs_call_mknodat((conn)->vfs_handles, (dirfsp), (smb_fname), (mode), (dev))
+#define SMB_VFS_NEXT_MKNODAT(handle, dirfsp, smb_fname, mode, dev) \
+ smb_vfs_call_mknodat((handle)->next, (dirfsp), (smb_fname), (mode), (dev))
+
+#define SMB_VFS_REALPATH(conn, ctx, smb_fname) \
+ smb_vfs_call_realpath((conn)->vfs_handles, (ctx), (smb_fname))
+#define SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname) \
+ smb_vfs_call_realpath((handle)->next, (ctx), (smb_fname))
+
+#define SMB_VFS_FCHFLAGS(fsp, flags) \
+ smb_vfs_call_fchflags((fsp)->conn->vfs_handles, (fsp), (flags))
+#define SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags) \
+ smb_vfs_call_fchflags((handle)->next, (fsp), (flags))
+
+#define SMB_VFS_FILE_ID_CREATE(conn, sbuf) \
+ smb_vfs_call_file_id_create((conn)->vfs_handles, (sbuf))
+#define SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf) \
+ smb_vfs_call_file_id_create((handle)->next, (sbuf))
+
+#define SMB_VFS_FS_FILE_ID(conn, sbuf) \
+ smb_vfs_call_fs_file_id((conn)->vfs_handles, (sbuf))
+#define SMB_VFS_NEXT_FS_FILE_ID(handle, sbuf) \
+ smb_vfs_call_fs_file_id((handle)->next, (sbuf))
+
+#define SMB_VFS_STREAMINFO(conn, fsp, smb_fname, mem_ctx, num_streams, streams) \
+ smb_vfs_call_streaminfo((conn)->vfs_handles, (fsp), (smb_fname), (mem_ctx), (num_streams), (streams))
+#define SMB_VFS_NEXT_STREAMINFO(handle, fsp, smb_fname, mem_ctx, num_streams, streams) \
+ smb_vfs_call_streaminfo((handle)->next, (fsp), (smb_fname), (mem_ctx), (num_streams), (streams))
+
+#define SMB_VFS_FSTREAMINFO(fsp, mem_ctx, num_streams, streams) \
+ smb_vfs_call_fstreaminfo((fsp)->conn->vfs_handles, (fsp), (mem_ctx), (num_streams), (streams))
+#define SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx, num_streams, streams) \
+ smb_vfs_call_fstreaminfo(handle->next, (fsp), (mem_ctx), (num_streams), (streams))
+
+#define SMB_VFS_GET_REAL_FILENAME_AT(conn, dirfsp, name, mem_ctx, found_name) \
+ smb_vfs_call_get_real_filename_at( \
+ (conn)->vfs_handles, \
+ (dirfsp), \
+ (name), \
+ (mem_ctx), \
+ (found_name))
+#define SMB_VFS_NEXT_GET_REAL_FILENAME_AT( \
+ handle, dirfsp, name, mem_ctx, found_name) \
+ smb_vfs_call_get_real_filename_at( \
+ (handle)->next, \
+ (dirfsp), \
+ (name), \
+ (mem_ctx), \
+ (found_name))
+
+#define SMB_VFS_CONNECTPATH(conn, dirfsp, smb_fname) \
+ smb_vfs_call_connectpath((conn)->vfs_handles, (dirfsp), (smb_fname))
+#define SMB_VFS_NEXT_CONNECTPATH(conn, dirfsp, smb_fname) \
+ smb_vfs_call_connectpath((conn)->next, (dirfsp), (smb_fname))
+
+#define SMB_VFS_BRL_LOCK_WINDOWS(conn, br_lck, plock) \
+ smb_vfs_call_brl_lock_windows((conn)->vfs_handles, (br_lck), (plock))
+#define SMB_VFS_NEXT_BRL_LOCK_WINDOWS(handle, br_lck, plock) \
+ smb_vfs_call_brl_lock_windows((handle)->next, (br_lck), (plock))
+
+#define SMB_VFS_BRL_UNLOCK_WINDOWS(conn, br_lck, plock) \
+ smb_vfs_call_brl_unlock_windows((conn)->vfs_handles, (br_lck), (plock))
+#define SMB_VFS_NEXT_BRL_UNLOCK_WINDOWS(handle, br_lck, plock) \
+ smb_vfs_call_brl_unlock_windows((handle)->next, (br_lck), (plock))
+
+#define SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, plock) \
+ smb_vfs_call_strict_lock_check((conn)->vfs_handles, (fsp), (plock))
+#define SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock) \
+ smb_vfs_call_strict_lock_check((handle)->next, (fsp), (plock))
+
+#define SMB_VFS_TRANSLATE_NAME(conn, name, direction, mem_ctx, mapped_name) \
+ smb_vfs_call_translate_name((conn)->vfs_handles, (name), (direction), (mem_ctx), (mapped_name))
+#define SMB_VFS_NEXT_TRANSLATE_NAME(handle, name, direction, mem_ctx, mapped_name) \
+ smb_vfs_call_translate_name((handle)->next, (name), (direction), (mem_ctx), (mapped_name))
+
+#define SMB_VFS_PARENT_PATHNAME(conn, mem_ctx, smb_fname_in, parent_dir_out, atname_out) \
+ smb_vfs_call_parent_pathname((conn)->vfs_handles, (mem_ctx), (smb_fname_in), (parent_dir_out), (atname_out))
+#define SMB_VFS_NEXT_PARENT_PATHNAME(handle, mem_ctx, smb_fname_in, parent_dir_out, atname_out) \
+ smb_vfs_call_parent_pathname((handle)->next, (mem_ctx), (smb_fname_in), (parent_dir_out), (atname_out))
+
+#define SMB_VFS_FSCTL(fsp, ctx, function, req_flags, in_data, in_len, out_data, max_out_len, out_len) \
+ smb_vfs_call_fsctl((fsp)->conn->vfs_handles, (fsp), (ctx), (function), (req_flags), (in_data), (in_len), (out_data), (max_out_len), (out_len))
+
+#define SMB_VFS_NEXT_FSCTL(handle, fsp, ctx, function, req_flags, in_data, in_len, out_data, max_out_len, out_len) \
+ smb_vfs_call_fsctl((handle)->next, (fsp), (ctx), (function), (req_flags), (in_data), (in_len), (out_data), (max_out_len), (out_len))
+
+#define SMB_VFS_FGET_DOS_ATTRIBUTES(conn, fsp, attributes) \
+ smb_vfs_call_fget_dos_attributes((conn)->vfs_handles, (fsp), (attributes))
+#define SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, attributes) \
+ smb_vfs_call_fget_dos_attributes((handle)->next, (fsp), (attributes))
+
+#define SMB_VFS_GET_DOS_ATTRIBUTES_SEND(mem_ctx, evg, dir_fsp, smb_fname) \
+ smb_vfs_call_get_dos_attributes_send((mem_ctx), (evg), \
+ (dir_fsp)->conn->vfs_handles, \
+ (dir_fsp), (smb_fname))
+#define SMB_VFS_GET_DOS_ATTRIBUTES_RECV(req, aio_state, dosmode) \
+ smb_vfs_call_get_dos_attributes_recv((req), (aio_state), (dosmode))
+
+#define SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_SEND(mem_ctx, evg, handle, dir_fsp, \
+ smb_fname) \
+ smb_vfs_call_get_dos_attributes_send((mem_ctx), (evg), \
+ (handle)->next, \
+ (dir_fsp), (smb_fname))
+#define SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_RECV(req, aio_state, dosmode) \
+ smb_vfs_call_get_dos_attributes_recv((req), (aio_state), (dosmode))
+
+#define SMB_VFS_FSET_DOS_ATTRIBUTES(conn, fsp, attributes) \
+ smb_vfs_call_fset_dos_attributes((conn)->vfs_handles, (fsp), (attributes))
+#define SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, attributes) \
+ smb_vfs_call_fset_dos_attributes((handle)->next, (fsp), (attributes))
+
+#define SMB_VFS_OFFLOAD_READ_SEND(mem_ctx, ev, fsp, fsctl, ttl, offset, to_copy) \
+ smb_vfs_call_offload_read_send((mem_ctx), (ev), (fsp)->conn->vfs_handles, fsp, (fsctl), (ttl), (offset), (to_copy))
+#define SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp, fsctl, ttl, offset, to_copy) \
+ smb_vfs_call_offload_read_send((mem_ctx), (ev), (handle)->next, (fsp), (fsctl), (ttl), (offset), (to_copy))
+
+#define SMB_VFS_OFFLOAD_READ_RECV(req, conn, mem_ctx, flags, xferlen, token_blob) \
+ smb_vfs_call_offload_read_recv((req), (conn)->vfs_handles, (mem_ctx), (flags), (xferlen), (token_blob))
+#define SMB_VFS_NEXT_OFFLOAD_READ_RECV(req, handle, mem_ctx, flags, xferlen, token_blob) \
+ smb_vfs_call_offload_read_recv((req), (handle)->next, (mem_ctx), flags, xferlen, (token_blob))
+
+#define SMB_VFS_OFFLOAD_WRITE_SEND(conn, mem_ctx, ev, fsctl, token, transfer_offset, dest_fsp, dest_off, num) \
+ smb_vfs_call_offload_write_send((conn)->vfs_handles, (mem_ctx), (ev), (fsctl), (token), (transfer_offset), (dest_fsp), (dest_off), (num))
+#define SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, mem_ctx, ev, fsctl, token, transfer_offset, dest_fsp, dest_off, num) \
+ smb_vfs_call_offload_write_send((handle)->next, (mem_ctx), (ev), (fsctl), (token), (transfer_offset), (dest_fsp), (dest_off), (num))
+
+#define SMB_VFS_OFFLOAD_WRITE_RECV(conn, req, copied) \
+ smb_vfs_call_offload_write_recv((conn)->vfs_handles, (req), (copied))
+#define SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(handle, req, copied) \
+ smb_vfs_call_offload_write_recv((handle)->next, (req), (copied))
+
+#define SMB_VFS_FGET_COMPRESSION(conn, mem_ctx, fsp, _compression_fmt) \
+ smb_vfs_call_fget_compression((conn)->vfs_handles, (mem_ctx), (fsp), (_compression_fmt))
+#define SMB_VFS_NEXT_FGET_COMPRESSION(handle, mem_ctx, fsp, _compression_fmt) \
+ smb_vfs_call_fget_compression((handle)->next, (mem_ctx), (fsp), (_compression_fmt))
+
+#define SMB_VFS_SET_COMPRESSION(conn, mem_ctx, fsp, compression_fmt) \
+ smb_vfs_call_set_compression((conn)->vfs_handles, (mem_ctx), (fsp), (compression_fmt))
+#define SMB_VFS_NEXT_SET_COMPRESSION(handle, mem_ctx, fsp, compression_fmt) \
+ smb_vfs_call_set_compression((handle)->next, (mem_ctx), (fsp), (compression_fmt))
+
+#define SMB_VFS_SNAP_CHECK_PATH(conn, mem_ctx, service_path, base_volume) \
+ smb_vfs_call_snap_check_path((conn)->vfs_handles, (mem_ctx), (service_path), (base_volume))
+#define SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx, service_path, base_volume) \
+ smb_vfs_call_snap_check_path((handle)->next, (mem_ctx), (service_path), (base_volume))
+
+#define SMB_VFS_SNAP_CREATE(conn, mem_ctx, base_volume, tstamp, rw, base_path, snap_path) \
+ smb_vfs_call_snap_create((conn)->vfs_handles, (mem_ctx), (base_volume), (tstamp), (rw), (base_path), (snap_path))
+#define SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume, tstamp, rw, base_path, snap_path) \
+ smb_vfs_call_snap_create((handle)->next, (mem_ctx), (base_volume), (tstamp), (rw), (base_path), (snap_path))
+
+#define SMB_VFS_SNAP_DELETE(conn, mem_ctx, base_path, snap_path) \
+ smb_vfs_call_snap_delete((conn)->vfs_handles, (mem_ctx), (base_path), (snap_path))
+#define SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx, base_path, snap_path) \
+ smb_vfs_call_snap_delete((handle)->next, (mem_ctx), (base_path), (snap_path))
+
+#define SMB_VFS_FGET_NT_ACL(fsp, security_info, mem_ctx, ppdesc) \
+ smb_vfs_call_fget_nt_acl((fsp)->conn->vfs_handles, (fsp), (security_info), (mem_ctx), (ppdesc))
+#define SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info, mem_ctx, ppdesc) \
+ smb_vfs_call_fget_nt_acl((handle)->next, (fsp), (security_info), (mem_ctx), (ppdesc))
+
+#define SMB_VFS_AUDIT_FILE(conn, name, sacl, access_requested, access_denied) \
+ smb_vfs_call_audit_file((conn)->vfs_handles, (name), (sacl), (access_requested), (access_denied))
+#define SMB_VFS_NEXT_AUDIT_FILE(handle, name, sacl, access_requested, access_denied) \
+ smb_vfs_call_audit_file((handle)->next, (name), (sacl), (access_requested), (access_denied))
+
+#define SMB_VFS_FSET_NT_ACL(fsp, security_info_sent, psd) \
+ smb_vfs_call_fset_nt_acl((fsp)->conn->vfs_handles, (fsp), (security_info_sent), (psd))
+#define SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd) \
+ smb_vfs_call_fset_nt_acl((handle)->next, (fsp), (security_info_sent), (psd))
+
+#define SMB_VFS_SYS_ACL_GET_FD(fsp, type, mem_ctx) \
+ smb_vfs_call_sys_acl_get_fd((fsp)->conn->vfs_handles, (fsp), (type), (mem_ctx))
+#define SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, fsp, type, mem_ctx) \
+ smb_vfs_call_sys_acl_get_fd((handle)->next, (fsp), (type), (mem_ctx))
+
+#define SMB_VFS_SYS_ACL_BLOB_GET_FD(fsp, mem_ctx, blob_description, blob) \
+ smb_vfs_call_sys_acl_blob_get_fd((fsp)->conn->vfs_handles, (fsp), (mem_ctx), (blob_description), (blob))
+#define SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx, blob_description, blob) \
+ smb_vfs_call_sys_acl_blob_get_fd((handle)->next, (fsp), mem_ctx, (blob_description), (blob))
+
+#define SMB_VFS_SYS_ACL_SET_FD(fsp, type, theacl) \
+ smb_vfs_call_sys_acl_set_fd((fsp)->conn->vfs_handles, (fsp), (type), (theacl))
+#define SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl) \
+ smb_vfs_call_sys_acl_set_fd((handle)->next, (fsp), (type), (theacl))
+
+#define SMB_VFS_SYS_ACL_DELETE_DEF_FD(fsp) \
+ smb_vfs_call_sys_acl_delete_def_fd((fsp)->conn->vfs_handles, (fsp))
+#define SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp) \
+ smb_vfs_call_sys_acl_delete_def_fd((handle)->next, (fsp))
+
+#define SMB_VFS_GETXATTRAT_SEND(mem_ctx,ev,dir_fsp,smb_fname, \
+ xattr_name, alloc_hint) \
+ smb_vfs_call_getxattrat_send((mem_ctx),(ev), \
+ (dir_fsp)->conn->vfs_handles, \
+ (dir_fsp),(smb_fname),(xattr_name), \
+ (alloc_hint))
+#define SMB_VFS_GETXATTRAT_RECV(req, aio_state, mem_ctx, xattr_value) \
+ smb_vfs_call_getxattrat_recv((req),(aio_state),(mem_ctx),(xattr_value))
+
+#define SMB_VFS_NEXT_GETXATTRAT_SEND(mem_ctx,ev,handle,dir_fsp,smb_fname, \
+ xattr_name,alloc_hint) \
+ smb_vfs_call_getxattrat_send((mem_ctx),(ev), \
+ (handle)->next, \
+ (dir_fsp), (smb_fname),(xattr_name), \
+ (alloc_hint))
+#define SMB_VFS_NEXT_GETXATTRAT_RECV(req, aio_state, mem_ctx, xattr_value) \
+ smb_vfs_call_getxattrat_recv((req),(aio_state),(mem_ctx),(xattr_value))
+
+#define SMB_VFS_FGETXATTR(fsp,name,value,size) \
+ smb_vfs_call_fgetxattr((fsp)->conn->vfs_handles, (fsp), (name),(value),(size))
+#define SMB_VFS_NEXT_FGETXATTR(handle,fsp,name,value,size) \
+ smb_vfs_call_fgetxattr((handle)->next,(fsp),(name),(value),(size))
+
+#define SMB_VFS_FLISTXATTR(fsp,list,size) \
+ smb_vfs_call_flistxattr((fsp)->conn->vfs_handles, (fsp), (list),(size))
+#define SMB_VFS_NEXT_FLISTXATTR(handle,fsp,list,size) \
+ smb_vfs_call_flistxattr((handle)->next,(fsp),(list),(size))
+
+#define SMB_VFS_FREMOVEXATTR(fsp,name) \
+ smb_vfs_call_fremovexattr((fsp)->conn->vfs_handles, (fsp), (name))
+#define SMB_VFS_NEXT_FREMOVEXATTR(handle,fsp,name) \
+ smb_vfs_call_fremovexattr((handle)->next,(fsp),(name))
+
+#define SMB_VFS_FSETXATTR(fsp,name,value,size,flags) \
+ smb_vfs_call_fsetxattr((fsp)->conn->vfs_handles, (fsp), (name),(value),(size),(flags))
+#define SMB_VFS_NEXT_FSETXATTR(handle,fsp,name,value,size,flags) \
+ smb_vfs_call_fsetxattr((handle)->next,(fsp),(name),(value),(size),(flags))
+
+#define SMB_VFS_AIO_FORCE(fsp) \
+ smb_vfs_call_aio_force((fsp)->conn->vfs_handles, (fsp))
+#define SMB_VFS_NEXT_AIO_FORCE(handle,fsp) \
+ smb_vfs_call_aio_force((handle)->next,(fsp))
+
+/* durable handle operations */
+
+#define SMB_VFS_DURABLE_COOKIE(fsp, mem_ctx, cookie) \
+ smb_vfs_call_durable_cookie((fsp)->conn->vfs_handles, \
+ (fsp), (mem_ctx), (cookie))
+#define SMB_VFS_NEXT_DURABLE_COOKIE(handle, fsp, mem_ctx, cookie) \
+ smb_vfs_call_durable_cookie((handle)->next, \
+ (fsp), (mem_ctx), (cookie))
+
+#define SMB_VFS_DURABLE_DISCONNECT(fsp, old_cookie, mem_ctx, new_cookie) \
+ smb_vfs_call_durable_disconnect((fsp)->conn->vfs_handles, \
+ (fsp), (old_cookie), (mem_ctx), (new_cookie))
+#define SMB_VFS_NEXT_DURABLE_DISCONNECT(handle, fsp, old_cookie, mem_ctx, new_cookie) \
+ smb_vfs_call_durable_disconnect((handle)->next, \
+ (fsp), (old_cookie), (mem_ctx), (new_cookie))
+
+#define SMB_VFS_DURABLE_RECONNECT(conn, smb1req, op, old_cookie, mem_ctx, fsp, new_cookie) \
+ smb_vfs_call_durable_reconnect((conn)->vfs_handles, \
+ (smb1req), (op), (old_cookie), \
+ (mem_ctx), (fsp), (new_cookie))
+#define SMB_VFS_NEXT_DURABLE_RECONNECT(handle, smb1req, op, old_cookie, mem_ctx, fsp, new_cookie) \
+ smb_vfs_call_durable_reconnect((handle)->next, \
+ (smb1req), (op), (old_cookie), \
+ (mem_ctx), (fsp), (new_cookie))
+
+#define SMB_VFS_FREADDIR_ATTR(fsp, mem_ctx, attr_data) \
+ smb_vfs_call_freaddir_attr((fsp)->conn->vfs_handles, (fsp), (mem_ctx), (attr_data))
+#define SMB_VFS_NEXT_FREADDIR_ATTR(handle, fsp, mem_ctx, attr_data) \
+ smb_vfs_call_freaddir_attr((handle)->next, (fsp), (mem_ctx), (attr_data))
+
+#endif /* _VFS_MACROS_H */
diff --git a/source3/intl/linux-msg.sed b/source3/intl/linux-msg.sed
new file mode 100644
index 0000000..19505b8
--- /dev/null
+++ b/source3/intl/linux-msg.sed
@@ -0,0 +1,99 @@
+# po2msg.sed - Convert Uniforum style .po file to Linux style .msg file
+# Copyright (C) 1995 Free Software Foundation, Inc.
+# Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+#
+# The first directive in the .msg should be the definition of the
+# message set number. We use always set number 1.
+#
+1 {
+ i\
+$set 1 # Automatically created by po2msg.sed
+ h
+ s/.*/0/
+ x
+}
+#
+# Mitch's old catalog format does not allow comments.
+#
+# We copy the original message as a comment into the .msg file.
+#
+/^msgid/ {
+ s/msgid[ ]*"//
+#
+# This does not work now with the new format.
+# /"$/! {
+# s/\\$//
+# s/$/ ... (more lines following)"/
+# }
+ x
+# The following nice solution is by
+# Bruno <Haible@ma2s2.mathematik.uni-karlsruhe.de>
+ td
+# Increment a decimal number in pattern space.
+# First hide trailing `9' digits.
+ :d
+ s/9\(_*\)$/_\1/
+ td
+# Assure at least one digit is available.
+ s/^\(_*\)$/0\1/
+# Increment the last digit.
+ s/8\(_*\)$/9\1/
+ s/7\(_*\)$/8\1/
+ s/6\(_*\)$/7\1/
+ s/5\(_*\)$/6\1/
+ s/4\(_*\)$/5\1/
+ s/3\(_*\)$/4\1/
+ s/2\(_*\)$/3\1/
+ s/1\(_*\)$/2\1/
+ s/0\(_*\)$/1\1/
+# Convert the hidden `9' digits to `0's.
+ s/_/0/g
+ x
+ G
+ s/\(.*\)"\n\([0-9]*\)/$ #\2 Original Message:(\1)/p
+}
+#
+# The .msg file contains, other then the .po file, only the translations
+# but each given a unique ID. Starting from 1 and incrementing by 1 for
+# each message we assign them to the messages.
+# It is important that the .po file used to generate the cat-id-tbl.c file
+# (with po-to-tbl) is the same as the one used here. (At least the order
+# of declarations must not be changed.)
+#
+/^msgstr/ {
+ s/msgstr[ ]*"\(.*\)"/# \1/
+# Clear substitution flag.
+ tb
+# Append the next line.
+ :b
+ N
+# Look whether second part is continuation line.
+ s/\(.*\n\)"\(.*\)"/\1\2/
+# Yes, then branch.
+ ta
+ P
+ D
+# Note that D includes a jump to the start!!
+# We found a continuation line. But before printing insert '\'.
+ :a
+ s/\(.*\)\(\n.*\)/\1\\\2/
+ P
+# We cannot use D here.
+ s/.*\n\(.*\)/\1/
+ tb
+}
+d
diff --git a/source3/lib/ABI/smbldap-0.sigs b/source3/lib/ABI/smbldap-0.sigs
new file mode 100644
index 0000000..d0e1d2f
--- /dev/null
+++ b/source3/lib/ABI/smbldap-0.sigs
@@ -0,0 +1,28 @@
+smbldap_add: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_delete: int (struct smbldap_state *, const char *)
+smbldap_extended_operation: int (struct smbldap_state *, const char *, struct berval *, LDAPControl **, LDAPControl **, char **, struct berval **)
+smbldap_free_struct: void (struct smbldap_state **)
+smbldap_get_single_attribute: bool (LDAP *, LDAPMessage *, const char *, char *, int)
+smbldap_has_control: bool (LDAP *, const char *)
+smbldap_has_extension: bool (LDAP *, const char *)
+smbldap_has_naming_context: bool (LDAP *, const char *)
+smbldap_init: NTSTATUS (TALLOC_CTX *, struct tevent_context *, const char *, bool, const char *, const char *, struct smbldap_state **)
+smbldap_make_mod: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const char *)
+smbldap_make_mod_blob: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const DATA_BLOB *)
+smbldap_modify: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_pull_sid: bool (LDAP *, LDAPMessage *, const char *, struct dom_sid *)
+smbldap_search: int (struct smbldap_state *, const char *, int, const char *, const char **, int, LDAPMessage **)
+smbldap_search_paged: int (struct smbldap_state *, const char *, int, const char *, const char **, int, int, LDAPMessage **, void **)
+smbldap_search_suffix: int (struct smbldap_state *, const char *, const char **, LDAPMessage **)
+smbldap_set_creds: bool (struct smbldap_state *, bool, const char *, const char *)
+smbldap_set_mod: void (LDAPMod ***, int, const char *, const char *)
+smbldap_set_mod_blob: void (LDAPMod ***, int, const char *, const DATA_BLOB *)
+smbldap_setup_full_conn: int (LDAP **, const char *)
+smbldap_start_tls: int (LDAP *, int)
+smbldap_talloc_autofree_ldapmod: void (TALLOC_CTX *, LDAPMod **)
+smbldap_talloc_autofree_ldapmsg: void (TALLOC_CTX *, LDAPMessage *)
+smbldap_talloc_dn: char *(TALLOC_CTX *, LDAP *, LDAPMessage *)
+smbldap_talloc_first_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_blob: bool (TALLOC_CTX *, LDAP *, LDAPMessage *, const char *, DATA_BLOB *)
+smbldap_talloc_smallest_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
diff --git a/source3/lib/ABI/smbldap-1.sigs b/source3/lib/ABI/smbldap-1.sigs
new file mode 100644
index 0000000..cacc56d
--- /dev/null
+++ b/source3/lib/ABI/smbldap-1.sigs
@@ -0,0 +1,31 @@
+smbldap_add: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_delete: int (struct smbldap_state *, const char *)
+smbldap_extended_operation: int (struct smbldap_state *, const char *, struct berval *, LDAPControl **, LDAPControl **, char **, struct berval **)
+smbldap_free_struct: void (struct smbldap_state **)
+smbldap_get_ldap: LDAP *(struct smbldap_state *)
+smbldap_get_paged_results: bool (struct smbldap_state *)
+smbldap_get_single_attribute: bool (LDAP *, LDAPMessage *, const char *, char *, int)
+smbldap_has_control: bool (LDAP *, const char *)
+smbldap_has_extension: bool (LDAP *, const char *)
+smbldap_has_naming_context: bool (LDAP *, const char *)
+smbldap_init: NTSTATUS (TALLOC_CTX *, struct tevent_context *, const char *, bool, const char *, const char *, struct smbldap_state **)
+smbldap_make_mod: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const char *)
+smbldap_make_mod_blob: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const DATA_BLOB *)
+smbldap_modify: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_pull_sid: bool (LDAP *, LDAPMessage *, const char *, struct dom_sid *)
+smbldap_search: int (struct smbldap_state *, const char *, int, const char *, const char **, int, LDAPMessage **)
+smbldap_search_paged: int (struct smbldap_state *, const char *, int, const char *, const char **, int, int, LDAPMessage **, void **)
+smbldap_search_suffix: int (struct smbldap_state *, const char *, const char **, LDAPMessage **)
+smbldap_set_creds: bool (struct smbldap_state *, bool, const char *, const char *)
+smbldap_set_mod: void (LDAPMod ***, int, const char *, const char *)
+smbldap_set_mod_blob: void (LDAPMod ***, int, const char *, const DATA_BLOB *)
+smbldap_set_paged_results: void (struct smbldap_state *, bool)
+smbldap_setup_full_conn: int (LDAP **, const char *)
+smbldap_start_tls: int (LDAP *, int)
+smbldap_talloc_autofree_ldapmod: void (TALLOC_CTX *, LDAPMod **)
+smbldap_talloc_autofree_ldapmsg: void (TALLOC_CTX *, LDAPMessage *)
+smbldap_talloc_dn: char *(TALLOC_CTX *, LDAP *, LDAPMessage *)
+smbldap_talloc_first_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_blob: bool (TALLOC_CTX *, LDAP *, LDAPMessage *, const char *, DATA_BLOB *)
+smbldap_talloc_smallest_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
diff --git a/source3/lib/ABI/smbldap-2.1.0.sigs b/source3/lib/ABI/smbldap-2.1.0.sigs
new file mode 100644
index 0000000..67dcc9a
--- /dev/null
+++ b/source3/lib/ABI/smbldap-2.1.0.sigs
@@ -0,0 +1,33 @@
+smbldap_add: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_delete: int (struct smbldap_state *, const char *)
+smbldap_extended_operation: int (struct smbldap_state *, const char *, struct berval *, LDAPControl **, LDAPControl **, char **, struct berval **)
+smbldap_free_struct: void (struct smbldap_state **)
+smbldap_get_ldap: LDAP *(struct smbldap_state *)
+smbldap_get_paged_results: bool (struct smbldap_state *)
+smbldap_get_single_attribute: bool (LDAP *, LDAPMessage *, const char *, char *, int)
+smbldap_has_control: bool (LDAP *, const char *)
+smbldap_has_extension: bool (LDAP *, const char *)
+smbldap_has_naming_context: bool (LDAP *, const char *)
+smbldap_init: NTSTATUS (TALLOC_CTX *, struct tevent_context *, const char *, bool, const char *, const char *, struct smbldap_state **)
+smbldap_make_mod: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const char *)
+smbldap_make_mod_blob: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const DATA_BLOB *)
+smbldap_modify: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_pull_sid: bool (LDAP *, LDAPMessage *, const char *, struct dom_sid *)
+smbldap_search: int (struct smbldap_state *, const char *, int, const char *, const char **, int, LDAPMessage **)
+smbldap_search_paged: int (struct smbldap_state *, const char *, int, const char *, const char **, int, int, LDAPMessage **, void **)
+smbldap_search_suffix: int (struct smbldap_state *, const char *, const char **, LDAPMessage **)
+smbldap_set_bind_callback: void (struct smbldap_state *, smbldap_bind_callback_fn, void *)
+smbldap_set_creds: bool (struct smbldap_state *, bool, const char *, const char *)
+smbldap_set_mod: void (LDAPMod ***, int, const char *, const char *)
+smbldap_set_mod_blob: void (LDAPMod ***, int, const char *, const DATA_BLOB *)
+smbldap_set_paged_results: void (struct smbldap_state *, bool)
+smbldap_setup_full_conn: int (LDAP **, const char *)
+smbldap_start_tls: int (LDAP *, int)
+smbldap_start_tls_start: int (LDAP *, int)
+smbldap_talloc_autofree_ldapmod: void (TALLOC_CTX *, LDAPMod **)
+smbldap_talloc_autofree_ldapmsg: void (TALLOC_CTX *, LDAPMessage *)
+smbldap_talloc_dn: char *(TALLOC_CTX *, LDAP *, LDAPMessage *)
+smbldap_talloc_first_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_blob: bool (TALLOC_CTX *, LDAP *, LDAPMessage *, const char *, DATA_BLOB *)
+smbldap_talloc_smallest_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
diff --git a/source3/lib/ABI/smbldap-2.sigs b/source3/lib/ABI/smbldap-2.sigs
new file mode 100644
index 0000000..8abefec
--- /dev/null
+++ b/source3/lib/ABI/smbldap-2.sigs
@@ -0,0 +1,32 @@
+smbldap_add: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_delete: int (struct smbldap_state *, const char *)
+smbldap_extended_operation: int (struct smbldap_state *, const char *, struct berval *, LDAPControl **, LDAPControl **, char **, struct berval **)
+smbldap_free_struct: void (struct smbldap_state **)
+smbldap_get_ldap: LDAP *(struct smbldap_state *)
+smbldap_get_paged_results: bool (struct smbldap_state *)
+smbldap_get_single_attribute: bool (LDAP *, LDAPMessage *, const char *, char *, int)
+smbldap_has_control: bool (LDAP *, const char *)
+smbldap_has_extension: bool (LDAP *, const char *)
+smbldap_has_naming_context: bool (LDAP *, const char *)
+smbldap_init: NTSTATUS (TALLOC_CTX *, struct tevent_context *, const char *, bool, const char *, const char *, struct smbldap_state **)
+smbldap_make_mod: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const char *)
+smbldap_make_mod_blob: void (LDAP *, LDAPMessage *, LDAPMod ***, const char *, const DATA_BLOB *)
+smbldap_modify: int (struct smbldap_state *, const char *, LDAPMod **)
+smbldap_pull_sid: bool (LDAP *, LDAPMessage *, const char *, struct dom_sid *)
+smbldap_search: int (struct smbldap_state *, const char *, int, const char *, const char **, int, LDAPMessage **)
+smbldap_search_paged: int (struct smbldap_state *, const char *, int, const char *, const char **, int, int, LDAPMessage **, void **)
+smbldap_search_suffix: int (struct smbldap_state *, const char *, const char **, LDAPMessage **)
+smbldap_set_bind_callback: void (struct smbldap_state *, smbldap_bind_callback_fn, void *)
+smbldap_set_creds: bool (struct smbldap_state *, bool, const char *, const char *)
+smbldap_set_mod: void (LDAPMod ***, int, const char *, const char *)
+smbldap_set_mod_blob: void (LDAPMod ***, int, const char *, const DATA_BLOB *)
+smbldap_set_paged_results: void (struct smbldap_state *, bool)
+smbldap_setup_full_conn: int (LDAP **, const char *)
+smbldap_start_tls: int (LDAP *, int)
+smbldap_talloc_autofree_ldapmod: void (TALLOC_CTX *, LDAPMod **)
+smbldap_talloc_autofree_ldapmsg: void (TALLOC_CTX *, LDAPMessage *)
+smbldap_talloc_dn: char *(TALLOC_CTX *, LDAP *, LDAPMessage *)
+smbldap_talloc_first_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
+smbldap_talloc_single_blob: bool (TALLOC_CTX *, LDAP *, LDAPMessage *, const char *, DATA_BLOB *)
+smbldap_talloc_smallest_attribute: char *(LDAP *, LDAPMessage *, const char *, TALLOC_CTX *)
diff --git a/source3/lib/addrchange.c b/source3/lib/addrchange.c
new file mode 100644
index 0000000..9a64e8d
--- /dev/null
+++ b/source3/lib/addrchange.c
@@ -0,0 +1,314 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Copyright (C) Volker Lendecke 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/addrchange.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+#ifdef HAVE_LINUX_RTNETLINK_H
+
+#include "asm/types.h"
+#include "linux/netlink.h"
+#include "linux/rtnetlink.h"
+#include "lib/tsocket/tsocket.h"
+
+struct addrchange_context {
+ struct tdgram_context *sock;
+};
+
+NTSTATUS addrchange_context_create(TALLOC_CTX *mem_ctx,
+ struct addrchange_context **pctx)
+{
+ struct addrchange_context *ctx;
+ struct sockaddr_nl addr;
+ NTSTATUS status;
+ int sock = -1;
+ int res;
+ bool ok;
+
+ ctx = talloc(mem_ctx, struct addrchange_context);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (sock == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ ok = smb_set_close_on_exec(sock);
+ if (!ok) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ res = set_blocking(sock, false);
+ if (res == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ /*
+ * We're interested in address changes
+ */
+ ZERO_STRUCT(addr);
+ addr.nl_family = AF_NETLINK;
+ addr.nl_groups = RTMGRP_IPV6_IFADDR | RTMGRP_IPV4_IFADDR;
+
+ res = bind(sock, (struct sockaddr *)(void *)&addr, sizeof(addr));
+ if (res == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ res = tdgram_bsd_existing_socket(ctx, sock, &ctx->sock);
+ if (res == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ *pctx = ctx;
+ return NT_STATUS_OK;
+fail:
+ if (sock != -1) {
+ close(sock);
+ }
+ TALLOC_FREE(ctx);
+ return status;
+}
+
+struct addrchange_state {
+ struct tevent_context *ev;
+ struct addrchange_context *ctx;
+ uint8_t *buf;
+ struct tsocket_address *fromaddr;
+
+ enum addrchange_type type;
+ struct sockaddr_storage addr;
+};
+
+static void addrchange_done(struct tevent_req *subreq);
+
+struct tevent_req *addrchange_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct addrchange_context *ctx)
+{
+ struct tevent_req *req, *subreq;
+ struct addrchange_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct addrchange_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctx = ctx;
+
+ subreq = tdgram_recvfrom_send(state, state->ev, state->ctx->sock);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+ tevent_req_set_callback(subreq, addrchange_done, req);
+ return req;
+}
+
+static void addrchange_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct addrchange_state *state = tevent_req_data(
+ req, struct addrchange_state);
+ union {
+ struct sockaddr sa;
+ struct sockaddr_nl nl;
+ struct sockaddr_storage ss;
+ } fromaddr;
+ struct nlmsghdr *h;
+ struct ifaddrmsg *ifa;
+ struct rtattr *rta;
+ ssize_t received;
+ int len;
+ int err;
+ bool found;
+
+ received = tdgram_recvfrom_recv(subreq, &err, state,
+ &state->buf,
+ &state->fromaddr);
+ TALLOC_FREE(subreq);
+ if (received == -1) {
+ DEBUG(10, ("tdgram_recvfrom_recv returned %s\n", strerror(err)));
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ len = tsocket_address_bsd_sockaddr(state->fromaddr,
+ &fromaddr.sa,
+ sizeof(fromaddr));
+
+ if ((len != sizeof(fromaddr.nl) ||
+ fromaddr.sa.sa_family != AF_NETLINK))
+ {
+ DEBUG(10, ("Got message from wrong addr\n"));
+ goto retry;
+ }
+
+ if (fromaddr.nl.nl_pid != 0) {
+ DEBUG(10, ("Got msg from pid %d, not from the kernel\n",
+ (int)fromaddr.nl.nl_pid));
+ goto retry;
+ }
+
+ if (received < sizeof(struct nlmsghdr)) {
+ DEBUG(10, ("received %d, expected at least %d\n",
+ (int)received, (int)sizeof(struct nlmsghdr)));
+ goto retry;
+ }
+
+ h = (struct nlmsghdr *)state->buf;
+ if (h->nlmsg_len < sizeof(struct nlmsghdr)) {
+ DEBUG(10, ("nlmsg_len=%d, expected at least %d\n",
+ (int)h->nlmsg_len, (int)sizeof(struct nlmsghdr)));
+ goto retry;
+ }
+ if (h->nlmsg_len > received) {
+ DEBUG(10, ("nlmsg_len=%d, expected at most %d\n",
+ (int)h->nlmsg_len, (int)received));
+ goto retry;
+ }
+ switch (h->nlmsg_type) {
+ case RTM_NEWADDR:
+ state->type = ADDRCHANGE_ADD;
+ break;
+ case RTM_DELADDR:
+ state->type = ADDRCHANGE_DEL;
+ break;
+ default:
+ DEBUG(10, ("Got unexpected type %d - ignoring\n", h->nlmsg_type));
+ goto retry;
+ }
+
+ if (h->nlmsg_len < sizeof(struct nlmsghdr)+sizeof(struct ifaddrmsg)) {
+ DEBUG(10, ("nlmsg_len=%d, expected at least %d\n",
+ (int)h->nlmsg_len,
+ (int)(sizeof(struct nlmsghdr)
+ +sizeof(struct ifaddrmsg))));
+ tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR);
+ return;
+ }
+
+ ifa = (struct ifaddrmsg *)NLMSG_DATA(h);
+
+ state->addr.ss_family = ifa->ifa_family;
+
+ len = h->nlmsg_len - sizeof(struct nlmsghdr) + sizeof(struct ifaddrmsg);
+
+ found = false;
+
+ for (rta = IFA_RTA(ifa); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
+
+ if ((rta->rta_type != IFA_LOCAL)
+ && (rta->rta_type != IFA_ADDRESS)) {
+ continue;
+ }
+
+ switch (ifa->ifa_family) {
+ case AF_INET: {
+ struct sockaddr_in *v4_addr;
+ v4_addr = (struct sockaddr_in *)(void *)&state->addr;
+
+ if (RTA_PAYLOAD(rta) != sizeof(uint32_t)) {
+ continue;
+ }
+ v4_addr->sin_addr.s_addr = *(uint32_t *)RTA_DATA(rta);
+ found = true;
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *v6_addr;
+ v6_addr = (struct sockaddr_in6 *)(void *)&state->addr;
+
+ if (RTA_PAYLOAD(rta) !=
+ sizeof(v6_addr->sin6_addr.s6_addr)) {
+ continue;
+ }
+ memcpy(v6_addr->sin6_addr.s6_addr, RTA_DATA(rta),
+ sizeof(v6_addr->sin6_addr.s6_addr));
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_ADDRESS);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+
+retry:
+ TALLOC_FREE(state->buf);
+ TALLOC_FREE(state->fromaddr);
+
+ subreq = tdgram_recvfrom_send(state, state->ev, state->ctx->sock);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, addrchange_done, req);
+}
+
+NTSTATUS addrchange_recv(struct tevent_req *req, enum addrchange_type *type,
+ struct sockaddr_storage *addr)
+{
+ struct addrchange_state *state = tevent_req_data(
+ req, struct addrchange_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *type = state->type;
+ *addr = state->addr;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+#else
+
+NTSTATUS addrchange_context_create(TALLOC_CTX *mem_ctx,
+ struct addrchange_context **pctx)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+struct tevent_req *addrchange_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct addrchange_context *ctx)
+{
+ return NULL;
+}
+
+NTSTATUS addrchange_recv(struct tevent_req *req, enum addrchange_type *type,
+ struct sockaddr_storage *addr)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+#endif
diff --git a/source3/lib/addrchange.h b/source3/lib/addrchange.h
new file mode 100644
index 0000000..1106380
--- /dev/null
+++ b/source3/lib/addrchange.h
@@ -0,0 +1,45 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Copyright (C) Volker Lendecke 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __ADDRCHANGE_H__
+#define __ADDRCHANGE_H__
+
+#include "replace.h"
+#include "system/network.h"
+#include <talloc.h>
+#include <tevent.h>
+#include "libcli/util/ntstatus.h"
+
+struct addrchange_context;
+
+NTSTATUS addrchange_context_create(TALLOC_CTX *mem_ctx,
+ struct addrchange_context **pctx);
+
+struct tevent_req *addrchange_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct addrchange_context *ctx);
+
+enum addrchange_type {
+ ADDRCHANGE_ADD,
+ ADDRCHANGE_DEL
+};
+
+NTSTATUS addrchange_recv(struct tevent_req *req, enum addrchange_type *type,
+ struct sockaddr_storage *addr);
+
+#endif
diff --git a/source3/lib/adouble.c b/source3/lib/adouble.c
new file mode 100644
index 0000000..1e8dbc4
--- /dev/null
+++ b/source3/lib/adouble.c
@@ -0,0 +1,2856 @@
+/*
+ * Samba AppleDouble helpers
+ *
+ * Copyright (C) Ralph Boehme, 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "adouble.h"
+#include "MacExtensions.h"
+#include "string_replace.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "libcli/security/security.h"
+#include "lib/util_macstreams.h"
+#include "auth.h"
+
+/*
+ "._" AppleDouble Header File Layout:
+
+ MAGIC 0x00051607
+ VERSION 0x00020000
+ FILLER 0
+ COUNT 2
+ .-- AD ENTRY[0] Finder Info Entry (must be first)
+ .--+-- AD ENTRY[1] Resource Fork Entry (must be last)
+ | | /////////////
+ | '-> FINDER INFO Fixed Size Data (32 Bytes)
+ | ~~~~~~~~~~~~~ 2 Bytes Padding
+ | EXT ATTR HDR Fixed Size Data (36 Bytes)
+ | /////////////
+ | ATTR ENTRY[0] --.
+ | ATTR ENTRY[1] --+--.
+ | ATTR ENTRY[2] --+--+--.
+ | ... | | |
+ | ATTR ENTRY[N] --+--+--+--.
+ | ATTR DATA 0 <-' | | |
+ | //////////// | | |
+ | ATTR DATA 1 <----' | |
+ | ///////////// | |
+ | ATTR DATA 2 <-------' |
+ | ///////////// |
+ | ... |
+ | ATTR DATA N <----------'
+ | /////////////
+ | ... Attribute Free Space
+ |
+ '----> RESOURCE FORK
+ ... Variable Sized Data
+ ...
+*/
+
+/* Number of actually used entries */
+#define ADEID_NUM_XATTR 8
+#define ADEID_NUM_DOT_UND 2
+#define ADEID_NUM_RSRC_XATTR 1
+
+/* Sizes of relevant entry bits */
+#define ADEDLEN_MAGIC 4
+#define ADEDLEN_VERSION 4
+#define ADEDLEN_FILLER 16
+#define AD_FILLER_TAG "Netatalk " /* should be 16 bytes */
+#define AD_FILLER_TAG_OSX "Mac OS X " /* should be 16 bytes */
+#define ADEDLEN_NENTRIES 2
+#define AD_HEADER_LEN (ADEDLEN_MAGIC + ADEDLEN_VERSION + \
+ ADEDLEN_FILLER + ADEDLEN_NENTRIES) /* 26 */
+#define AD_ENTRY_LEN_EID 4
+#define AD_ENTRY_LEN_OFF 4
+#define AD_ENTRY_LEN_LEN 4
+#define AD_ENTRY_LEN (AD_ENTRY_LEN_EID + AD_ENTRY_LEN_OFF + AD_ENTRY_LEN_LEN)
+
+/* Offsets */
+#define ADEDOFF_MAGIC 0
+#define ADEDOFF_VERSION (ADEDOFF_MAGIC + ADEDLEN_MAGIC)
+#define ADEDOFF_FILLER (ADEDOFF_VERSION + ADEDLEN_VERSION)
+#define ADEDOFF_NENTRIES (ADEDOFF_FILLER + ADEDLEN_FILLER)
+
+#define ADEDOFF_FINDERI_XATTR (AD_HEADER_LEN + \
+ (ADEID_NUM_XATTR * AD_ENTRY_LEN))
+#define ADEDOFF_COMMENT_XATTR (ADEDOFF_FINDERI_XATTR + ADEDLEN_FINDERI)
+#define ADEDOFF_FILEDATESI_XATTR (ADEDOFF_COMMENT_XATTR + ADEDLEN_COMMENT)
+#define ADEDOFF_AFPFILEI_XATTR (ADEDOFF_FILEDATESI_XATTR + \
+ ADEDLEN_FILEDATESI)
+#define ADEDOFF_PRIVDEV_XATTR (ADEDOFF_AFPFILEI_XATTR + ADEDLEN_AFPFILEI)
+#define ADEDOFF_PRIVINO_XATTR (ADEDOFF_PRIVDEV_XATTR + ADEDLEN_PRIVDEV)
+#define ADEDOFF_PRIVSYN_XATTR (ADEDOFF_PRIVINO_XATTR + ADEDLEN_PRIVINO)
+#define ADEDOFF_PRIVID_XATTR (ADEDOFF_PRIVSYN_XATTR + ADEDLEN_PRIVSYN)
+
+#define ADEDOFF_FINDERI_DOT_UND (AD_HEADER_LEN + \
+ (ADEID_NUM_DOT_UND * AD_ENTRY_LEN))
+#define ADEDOFF_RFORK_DOT_UND (ADEDOFF_FINDERI_DOT_UND + ADEDLEN_FINDERI)
+
+#define AD_DATASZ_XATTR (AD_HEADER_LEN + \
+ (ADEID_NUM_XATTR * AD_ENTRY_LEN) + \
+ ADEDLEN_FINDERI + ADEDLEN_COMMENT + \
+ ADEDLEN_FILEDATESI + ADEDLEN_AFPFILEI + \
+ ADEDLEN_PRIVDEV + ADEDLEN_PRIVINO + \
+ ADEDLEN_PRIVSYN + ADEDLEN_PRIVID)
+
+#if AD_DATASZ_XATTR != 402
+#error bad size for AD_DATASZ_XATTR
+#endif
+
+#define AD_DATASZ_DOT_UND (AD_HEADER_LEN + \
+ (ADEID_NUM_DOT_UND * AD_ENTRY_LEN) + \
+ ADEDLEN_FINDERI)
+#if AD_DATASZ_DOT_UND != 82
+#error bad size for AD_DATASZ_DOT_UND
+#endif
+
+#define AD_XATTR_HDR_MAGIC 0x41545452 /* 'ATTR' */
+#define AD_XATTR_MAX_ENTRIES 1024 /* Some arbitrarily enforced limit */
+#define AD_XATTR_HDR_SIZE 36
+#define AD_XATTR_MAX_HDR_SIZE 65536
+#define ADX_ENTRY_FIXED_SIZE (4+4+2+1)
+
+/*
+ * Both struct ad_xattr_header and struct ad_xattr_entry describe the in memory
+ * representation as well as the on-disk format.
+ *
+ * The ad_xattr_header follows the FinderInfo data in the FinderInfo entry if
+ * the length of the FinderInfo entry is larger then 32 bytes. It is then
+ * preceded with 2 bytes padding.
+ *
+ * Cf: https://opensource.apple.com/source/xnu/xnu-4570.1.46/bsd/vfs/vfs_xattr.c
+ */
+
+struct ad_xattr_header {
+ uint32_t adx_magic; /* ATTR_HDR_MAGIC */
+ uint32_t adx_debug_tag; /* for debugging == file id of owning file */
+ uint32_t adx_total_size; /* file offset of end of attribute header + entries + data */
+ uint32_t adx_data_start; /* file offset to attribute data area */
+ uint32_t adx_data_length; /* length of attribute data area */
+ uint32_t adx_reserved[3];
+ uint16_t adx_flags;
+ uint16_t adx_num_attrs;
+};
+
+/* On-disk entries are aligned on 4 byte boundaries */
+struct ad_xattr_entry {
+ uint32_t adx_offset; /* file offset to data */
+ uint32_t adx_length; /* size of attribute data */
+ uint16_t adx_flags;
+ uint8_t adx_namelen; /* included the NULL terminator */
+ char *adx_name; /* NULL-terminated UTF-8 name */
+};
+
+struct ad_entry {
+ size_t ade_off;
+ size_t ade_len;
+};
+
+struct adouble {
+ files_struct *ad_fsp;
+ bool ad_opened;
+ adouble_type_t ad_type;
+ uint32_t ad_magic;
+ uint32_t ad_version;
+ uint8_t ad_filler[ADEDLEN_FILLER];
+ struct ad_entry ad_eid[ADEID_MAX];
+ char *ad_data;
+ char *ad_rsrc_data;
+ struct ad_xattr_header adx_header;
+ struct ad_xattr_entry *adx_entries;
+ char *adx_data;
+};
+
+struct ad_entry_order {
+ uint32_t id, offset, len;
+};
+
+/* Netatalk AppleDouble metadata xattr */
+static const
+struct ad_entry_order entry_order_meta_xattr[ADEID_NUM_XATTR + 1] = {
+ {ADEID_FINDERI, ADEDOFF_FINDERI_XATTR, ADEDLEN_FINDERI},
+ {ADEID_COMMENT, ADEDOFF_COMMENT_XATTR, 0},
+ {ADEID_FILEDATESI, ADEDOFF_FILEDATESI_XATTR, ADEDLEN_FILEDATESI},
+ {ADEID_AFPFILEI, ADEDOFF_AFPFILEI_XATTR, ADEDLEN_AFPFILEI},
+ {ADEID_PRIVDEV, ADEDOFF_PRIVDEV_XATTR, 0},
+ {ADEID_PRIVINO, ADEDOFF_PRIVINO_XATTR, 0},
+ {ADEID_PRIVSYN, ADEDOFF_PRIVSYN_XATTR, 0},
+ {ADEID_PRIVID, ADEDOFF_PRIVID_XATTR, 0},
+ {0, 0, 0}
+};
+
+/* AppleDouble resource fork file (the ones prefixed by "._") */
+static const
+struct ad_entry_order entry_order_dot_und[ADEID_NUM_DOT_UND + 1] = {
+ {ADEID_FINDERI, ADEDOFF_FINDERI_DOT_UND, ADEDLEN_FINDERI},
+ {ADEID_RFORK, ADEDOFF_RFORK_DOT_UND, 0},
+ {0, 0, 0}
+};
+
+/* Conversion from enumerated id to on-disk AppleDouble id */
+#define AD_EID_DISK(a) (set_eid[a])
+static const uint32_t set_eid[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ AD_DEV, AD_INO, AD_SYN, AD_ID
+};
+
+static char empty_resourcefork[] = {
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E,
+ 0x54, 0x68, 0x69, 0x73, 0x20, 0x72, 0x65, 0x73,
+ 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x66, 0x6F,
+ 0x72, 0x6B, 0x20, 0x69, 0x6E, 0x74, 0x65, 0x6E,
+ 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x6C, 0x79,
+ 0x20, 0x6C, 0x65, 0x66, 0x74, 0x20, 0x62, 0x6C,
+ 0x61, 0x6E, 0x6B, 0x20, 0x20, 0x20, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x1C, 0x00, 0x1E, 0xFF, 0xFF
+};
+
+size_t ad_getentrylen(const struct adouble *ad, int eid)
+{
+ return ad->ad_eid[eid].ade_len;
+}
+
+size_t ad_getentryoff(const struct adouble *ad, int eid)
+{
+ return ad->ad_eid[eid].ade_off;
+}
+
+size_t ad_setentrylen(struct adouble *ad, int eid, size_t len)
+{
+ return ad->ad_eid[eid].ade_len = len;
+}
+
+size_t ad_setentryoff(struct adouble *ad, int eid, size_t off)
+{
+ return ad->ad_eid[eid].ade_off = off;
+}
+
+/*
+ * All entries besides FinderInfo and resource fork must fit into the
+ * buffer. FinderInfo is special as it may be larger then the default 32 bytes
+ * if it contains marshalled xattrs, which we will fixup that in
+ * ad_convert(). The first 32 bytes however must also be part of the buffer.
+ *
+ * The resource fork is never accessed directly by the ad_data buf.
+ */
+static bool ad_entry_check_size(uint32_t eid,
+ size_t bufsize,
+ uint32_t off,
+ uint32_t got_len)
+{
+ struct {
+ off_t expected_len;
+ bool fixed_size;
+ bool minimum_size;
+ } ad_checks[] = {
+ [ADEID_DFORK] = {-1, false, false}, /* not applicable */
+ [ADEID_RFORK] = {-1, false, false}, /* no limit */
+ [ADEID_NAME] = {ADEDLEN_NAME, false, false},
+ [ADEID_COMMENT] = {ADEDLEN_COMMENT, false, false},
+ [ADEID_ICONBW] = {ADEDLEN_ICONBW, true, false},
+ [ADEID_ICONCOL] = {ADEDLEN_ICONCOL, false, false},
+ [ADEID_FILEI] = {ADEDLEN_FILEI, true, false},
+ [ADEID_FILEDATESI] = {ADEDLEN_FILEDATESI, true, false},
+ [ADEID_FINDERI] = {ADEDLEN_FINDERI, false, true},
+ [ADEID_MACFILEI] = {ADEDLEN_MACFILEI, true, false},
+ [ADEID_PRODOSFILEI] = {ADEDLEN_PRODOSFILEI, true, false},
+ [ADEID_MSDOSFILEI] = {ADEDLEN_MSDOSFILEI, true, false},
+ [ADEID_SHORTNAME] = {ADEDLEN_SHORTNAME, false, false},
+ [ADEID_AFPFILEI] = {ADEDLEN_AFPFILEI, true, false},
+ [ADEID_DID] = {ADEDLEN_DID, true, false},
+ [ADEID_PRIVDEV] = {ADEDLEN_PRIVDEV, true, false},
+ [ADEID_PRIVINO] = {ADEDLEN_PRIVINO, true, false},
+ [ADEID_PRIVSYN] = {ADEDLEN_PRIVSYN, true, false},
+ [ADEID_PRIVID] = {ADEDLEN_PRIVID, true, false},
+ };
+
+ if (eid >= ADEID_MAX) {
+ return false;
+ }
+ if (got_len == 0) {
+ /* Entry present, but empty, allow */
+ return true;
+ }
+ if (ad_checks[eid].expected_len == 0) {
+ /*
+ * Shouldn't happen: implicitly initialized to zero because
+ * explicit initializer missing.
+ */
+ return false;
+ }
+ if (ad_checks[eid].expected_len == -1) {
+ /* Unused or no limit */
+ return true;
+ }
+ if (ad_checks[eid].fixed_size) {
+ if (ad_checks[eid].expected_len != got_len) {
+ /* Wrong size for fixed size entry. */
+ return false;
+ }
+ } else {
+ if (ad_checks[eid].minimum_size) {
+ if (got_len < ad_checks[eid].expected_len) {
+ /*
+ * Too small for variable sized entry with
+ * minimum size.
+ */
+ return false;
+ }
+ } else {
+ if (got_len > ad_checks[eid].expected_len) {
+ /* Too big for variable sized entry. */
+ return false;
+ }
+ }
+ }
+ if (off + got_len < off) {
+ /* wrap around */
+ return false;
+ }
+ if (off + got_len > bufsize) {
+ /* overflow */
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Return a pointer to an AppleDouble entry
+ *
+ * Returns NULL if the entry is not present
+ **/
+char *ad_get_entry(const struct adouble *ad, int eid)
+{
+ size_t bufsize = talloc_get_size(ad->ad_data);
+ off_t off = ad_getentryoff(ad, eid);
+ size_t len = ad_getentrylen(ad, eid);
+ bool valid;
+
+ valid = ad_entry_check_size(eid, bufsize, off, len);
+ if (!valid) {
+ return NULL;
+ }
+
+ if (off == 0 || len == 0) {
+ return NULL;
+ }
+
+ return ad->ad_data + off;
+}
+
+/**
+ * Get a date
+ **/
+int ad_getdate(const struct adouble *ad, unsigned int dateoff, uint32_t *date)
+{
+ bool xlate = (dateoff & AD_DATE_UNIX);
+ char *p = NULL;
+
+ dateoff &= AD_DATE_MASK;
+ p = ad_get_entry(ad, ADEID_FILEDATESI);
+ if (p == NULL) {
+ return -1;
+ }
+
+ if (dateoff > AD_DATE_ACCESS) {
+ return -1;
+ }
+
+ memcpy(date, p + dateoff, sizeof(uint32_t));
+
+ if (xlate) {
+ *date = AD_DATE_TO_UNIX(*date);
+ }
+ return 0;
+}
+
+/**
+ * Set a date
+ **/
+int ad_setdate(struct adouble *ad, unsigned int dateoff, uint32_t date)
+{
+ bool xlate = (dateoff & AD_DATE_UNIX);
+ char *p = NULL;
+
+ p = ad_get_entry(ad, ADEID_FILEDATESI);
+ if (p == NULL) {
+ return -1;
+ }
+
+ dateoff &= AD_DATE_MASK;
+ if (xlate) {
+ date = AD_DATE_FROM_UNIX(date);
+ }
+
+ if (dateoff > AD_DATE_ACCESS) {
+ return -1;
+ }
+
+ memcpy(p + dateoff, &date, sizeof(date));
+
+ return 0;
+}
+
+
+/**
+ * Map on-disk AppleDouble id to enumerated id
+ **/
+static uint32_t get_eid(uint32_t eid)
+{
+ if (eid <= 15) {
+ return eid;
+ }
+
+ switch (eid) {
+ case AD_DEV:
+ return ADEID_PRIVDEV;
+ case AD_INO:
+ return ADEID_PRIVINO;
+ case AD_SYN:
+ return ADEID_PRIVSYN;
+ case AD_ID:
+ return ADEID_PRIVID;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Move resourcefork data in an AppleDouble file
+ *
+ * This is supposed to make room in an AppleDouble file by moving the
+ * resourcefork data behind the space required for packing additional xattr data
+ * in the extended FinderInfo entry.
+ *
+ * When we're called we're expecting an AppleDouble file with just two entries
+ * (FinderInfo an Resourcefork) and the resourcefork is expected at a fixed
+ * offset of ADEDOFF_RFORK_DOT_UND.
+ */
+static bool ad_pack_move_reso(struct vfs_handle_struct *handle,
+ struct adouble *ad,
+ files_struct *fsp)
+{
+ size_t reso_len;
+ size_t reso_off;
+ size_t n;
+ bool ok;
+
+ reso_len = ad_getentrylen(ad, ADEID_RFORK);
+ reso_off = ad_getentryoff(ad, ADEID_RFORK);
+
+ if (reso_len == 0) {
+ return true;
+ }
+
+ if (ad->ad_rsrc_data == NULL) {
+ /*
+ * This buffer is already set when converting a resourcefork
+ * stream from vfs_streams_depot backend via ad_unconvert(). It
+ * is NULL with vfs_streams_xattr where the resourcefork stream
+ * is stored in an AppleDouble sidecar file vy vfs_fruit.
+ */
+ ad->ad_rsrc_data = talloc_size(ad, reso_len);
+ if (ad->ad_rsrc_data == NULL) {
+ return false;
+ }
+
+ n = SMB_VFS_NEXT_PREAD(handle,
+ fsp,
+ ad->ad_rsrc_data,
+ reso_len,
+ ADEDOFF_RFORK_DOT_UND);
+ if (n != reso_len) {
+ DBG_ERR("Read on [%s] failed\n",
+ fsp_str_dbg(fsp));
+ ok = false;
+ goto out;
+ }
+ }
+
+ n = SMB_VFS_NEXT_PWRITE(handle,
+ fsp,
+ ad->ad_rsrc_data,
+ reso_len,
+ reso_off);
+ if (n != reso_len) {
+ DBG_ERR("Write on [%s] failed\n",
+ fsp_str_dbg(fsp));
+ ok = false;
+ goto out;
+ }
+
+ ok = true;
+out:
+ return ok;
+}
+
+static bool ad_pack_xattrs(struct vfs_handle_struct *handle,
+ struct adouble *ad,
+ files_struct *fsp)
+{
+ struct ad_xattr_header *h = &ad->adx_header;
+ size_t oldsize;
+ uint32_t off;
+ uint32_t data_off;
+ uint16_t i;
+ bool ok;
+
+ if (ad->adx_entries == NULL) {
+ /* No xattrs, nothing to pack */
+ return true;
+ }
+
+ if (fsp == NULL) {
+ DBG_ERR("fsp unexpectedly NULL\n");
+ return false;
+ }
+
+ oldsize = talloc_get_size(ad->ad_data);
+ if (oldsize < AD_XATTR_MAX_HDR_SIZE) {
+ ad->ad_data = talloc_realloc(ad,
+ ad->ad_data,
+ char,
+ AD_XATTR_MAX_HDR_SIZE);
+ if (ad->ad_data == NULL) {
+ return false;
+ }
+ memset(ad->ad_data + oldsize,
+ 0,
+ AD_XATTR_MAX_HDR_SIZE - oldsize);
+ }
+
+ /*
+ * First, let's calculate the start of the xattr data area which will be
+ * after the xattr header + header entries.
+ */
+
+ data_off = ad_getentryoff(ad, ADEID_FINDERI);
+ data_off += ADEDLEN_FINDERI + AD_XATTR_HDR_SIZE;
+ /* 2 bytes padding */
+ data_off += 2;
+
+ for (i = 0; i < h->adx_num_attrs; i++) {
+ struct ad_xattr_entry *e = &ad->adx_entries[i];
+
+ /* Align on 4 byte boundary */
+ data_off = (data_off + 3) & ~3;
+
+ data_off += e->adx_namelen + ADX_ENTRY_FIXED_SIZE;
+ if (data_off >= AD_XATTR_MAX_HDR_SIZE) {
+ return false;
+ }
+ }
+
+ off = ad_getentryoff(ad, ADEID_FINDERI);
+ off += ADEDLEN_FINDERI + AD_XATTR_HDR_SIZE;
+ /* 2 bytes padding */
+ off += 2;
+
+ for (i = 0; i < h->adx_num_attrs; i++) {
+ struct ad_xattr_entry *e = &ad->adx_entries[i];
+
+ /* Align on 4 byte boundary */
+ off = (off + 3) & ~3;
+
+ e->adx_offset = data_off;
+ data_off += e->adx_length;
+
+ DBG_DEBUG("%zu(%s){%zu}: off [%zu] adx_length [%zu] "
+ "adx_data_off [%zu]\n",
+ (size_t)i,
+ e->adx_name,
+ (size_t)e->adx_namelen,
+ (size_t)off,
+ (size_t)e->adx_length,
+ (size_t)e->adx_offset);
+
+ if (off + 4 >= AD_XATTR_MAX_HDR_SIZE) {
+ return false;
+ }
+ RSIVAL(ad->ad_data, off, e->adx_offset);
+ off += 4;
+
+ if (off + 4 >= AD_XATTR_MAX_HDR_SIZE) {
+ return false;
+ }
+ RSIVAL(ad->ad_data, off, e->adx_length);
+ off += 4;
+
+ if (off + 2 >= AD_XATTR_MAX_HDR_SIZE) {
+ return false;
+ }
+ RSSVAL(ad->ad_data, off, e->adx_flags);
+ off += 2;
+
+ if (off + 1 >= AD_XATTR_MAX_HDR_SIZE) {
+ return false;
+ }
+ SCVAL(ad->ad_data, off, e->adx_namelen);
+ off += 1;
+
+ if (off + e->adx_namelen >= AD_XATTR_MAX_HDR_SIZE) {
+ return false;
+ }
+ memcpy(ad->ad_data + off, e->adx_name, e->adx_namelen);
+ off += e->adx_namelen;
+ }
+
+ h->adx_data_start = off;
+ h->adx_data_length = talloc_get_size(ad->adx_data);
+ h->adx_total_size = h->adx_data_start + h->adx_data_length;
+
+ if (talloc_get_size(ad->ad_data) < h->adx_total_size) {
+ ad->ad_data = talloc_realloc(ad,
+ ad->ad_data,
+ char,
+ h->adx_total_size);
+ if (ad->ad_data == NULL) {
+ return false;
+ }
+ }
+
+ memcpy(ad->ad_data + h->adx_data_start,
+ ad->adx_data,
+ h->adx_data_length);
+
+ ad_setentrylen(ad,
+ ADEID_FINDERI,
+ h->adx_total_size - ad_getentryoff(ad, ADEID_FINDERI));
+
+ ad_setentryoff(ad,
+ ADEID_RFORK,
+ ad_getentryoff(ad, ADEID_FINDERI) +
+ ad_getentrylen(ad, ADEID_FINDERI));
+
+ memcpy(ad->ad_data + ADEDOFF_FILLER, AD_FILLER_TAG_OSX, ADEDLEN_FILLER);
+
+ /*
+ * Rewind, then update the header fields.
+ */
+
+ off = ad_getentryoff(ad, ADEID_FINDERI) + ADEDLEN_FINDERI;
+ /* 2 bytes padding */
+ off += 2;
+
+ RSIVAL(ad->ad_data, off, AD_XATTR_HDR_MAGIC);
+ off += 4;
+ RSIVAL(ad->ad_data, off, 0);
+ off += 4;
+ RSIVAL(ad->ad_data, off, h->adx_total_size);
+ off += 4;
+ RSIVAL(ad->ad_data, off, h->adx_data_start);
+ off += 4;
+ RSIVAL(ad->ad_data, off, h->adx_data_length);
+ off += 4;
+
+ /* adx_reserved and adx_flags */
+ memset(ad->ad_data + off, 0, 3 * 4 + 2);
+ off += 3 * 4 + 2;
+
+ RSSVAL(ad->ad_data, off, h->adx_num_attrs);
+ off += 2;
+
+ ok = ad_pack_move_reso(handle, ad, fsp);
+ if (!ok) {
+ DBG_ERR("Moving resourcefork of [%s] failed\n",
+ fsp_str_dbg(fsp));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Pack AppleDouble structure into data buffer
+ **/
+static bool ad_pack(struct vfs_handle_struct *handle,
+ struct adouble *ad,
+ files_struct *fsp)
+{
+ uint32_t eid;
+ uint16_t nent;
+ uint32_t bufsize;
+ uint32_t offset = 0;
+ bool ok;
+
+ bufsize = talloc_get_size(ad->ad_data);
+ if (bufsize < AD_DATASZ_DOT_UND) {
+ DBG_ERR("bad buffer size [0x%" PRIx32 "]\n", bufsize);
+ return false;
+ }
+
+ if (offset + ADEDLEN_MAGIC < offset ||
+ offset + ADEDLEN_MAGIC >= bufsize) {
+ return false;
+ }
+ RSIVAL(ad->ad_data, offset, ad->ad_magic);
+ offset += ADEDLEN_MAGIC;
+
+ if (offset + ADEDLEN_VERSION < offset ||
+ offset + ADEDLEN_VERSION >= bufsize) {
+ return false;
+ }
+ RSIVAL(ad->ad_data, offset, ad->ad_version);
+ offset += ADEDLEN_VERSION;
+
+ if (offset + ADEDLEN_FILLER < offset ||
+ offset + ADEDLEN_FILLER >= bufsize) {
+ return false;
+ }
+ if (ad->ad_type == ADOUBLE_RSRC) {
+ memcpy(ad->ad_data + offset, AD_FILLER_TAG, ADEDLEN_FILLER);
+ }
+ offset += ADEDLEN_FILLER;
+
+ if (offset + ADEDLEN_NENTRIES < offset ||
+ offset + ADEDLEN_NENTRIES >= bufsize) {
+ return false;
+ }
+ offset += ADEDLEN_NENTRIES;
+
+ ok = ad_pack_xattrs(handle, ad, fsp);
+ if (!ok) {
+ return false;
+ }
+
+ for (eid = 0, nent = 0; eid < ADEID_MAX; eid++) {
+ if (ad->ad_eid[eid].ade_off == 0) {
+ /*
+ * ade_off is also used as indicator whether a
+ * specific entry is used or not
+ */
+ continue;
+ }
+
+ if (offset + AD_ENTRY_LEN_EID < offset ||
+ offset + AD_ENTRY_LEN_EID >= bufsize) {
+ return false;
+ }
+ RSIVAL(ad->ad_data, offset, AD_EID_DISK(eid));
+ offset += AD_ENTRY_LEN_EID;
+
+ if (offset + AD_ENTRY_LEN_OFF < offset ||
+ offset + AD_ENTRY_LEN_OFF >= bufsize) {
+ return false;
+ }
+ RSIVAL(ad->ad_data, offset, ad->ad_eid[eid].ade_off);
+ offset += AD_ENTRY_LEN_OFF;
+
+ if (offset + AD_ENTRY_LEN_LEN < offset ||
+ offset + AD_ENTRY_LEN_LEN >= bufsize) {
+ return false;
+ }
+ RSIVAL(ad->ad_data, offset, ad->ad_eid[eid].ade_len);
+ offset += AD_ENTRY_LEN_LEN;
+
+ nent++;
+ }
+
+ if (ADEDOFF_NENTRIES + 2 >= bufsize) {
+ return false;
+ }
+ RSSVAL(ad->ad_data, ADEDOFF_NENTRIES, nent);
+
+ return true;
+}
+
+static bool ad_unpack_xattrs(struct adouble *ad)
+{
+ struct ad_xattr_header *h = &ad->adx_header;
+ size_t bufsize = talloc_get_size(ad->ad_data);
+ const char *p = ad->ad_data;
+ uint32_t hoff;
+ uint32_t i;
+
+ if (ad->ad_type != ADOUBLE_RSRC) {
+ return false;
+ }
+
+ if (ad_getentrylen(ad, ADEID_FINDERI) <= ADEDLEN_FINDERI) {
+ return true;
+ }
+
+ /*
+ * Ensure the buffer ad->ad_data was allocated by ad_alloc() for an
+ * ADOUBLE_RSRC type (._ AppleDouble file on-disk).
+ */
+ if (bufsize != AD_XATTR_MAX_HDR_SIZE) {
+ return false;
+ }
+
+ /* 2 bytes padding */
+ hoff = ad_getentryoff(ad, ADEID_FINDERI) + ADEDLEN_FINDERI + 2;
+
+ h->adx_magic = RIVAL(p, hoff + 0);
+ h->adx_debug_tag = RIVAL(p, hoff + 4); /* Not used -> not checked */
+ h->adx_total_size = RIVAL(p, hoff + 8);
+ h->adx_data_start = RIVAL(p, hoff + 12);
+ h->adx_data_length = RIVAL(p, hoff + 16);
+ h->adx_flags = RSVAL(p, hoff + 32); /* Not used -> not checked */
+ h->adx_num_attrs = RSVAL(p, hoff + 34);
+
+ if (h->adx_magic != AD_XATTR_HDR_MAGIC) {
+ DBG_ERR("Bad magic: 0x%" PRIx32 "\n", h->adx_magic);
+ return false;
+ }
+
+ if (h->adx_total_size > ad_getentryoff(ad, ADEID_RFORK)) {
+ DBG_ERR("Bad total size: 0x%" PRIx32 "\n", h->adx_total_size);
+ return false;
+ }
+ if (h->adx_total_size > AD_XATTR_MAX_HDR_SIZE) {
+ DBG_ERR("Bad total size: 0x%" PRIx32 "\n", h->adx_total_size);
+ return false;
+ }
+
+ if (h->adx_data_start < (hoff + AD_XATTR_HDR_SIZE)) {
+ DBG_ERR("Bad start: 0x%" PRIx32 "\n", h->adx_data_start);
+ return false;
+ }
+
+ if ((h->adx_data_start + h->adx_data_length) < h->adx_data_start) {
+ DBG_ERR("Bad length: %" PRIu32 "\n", h->adx_data_length);
+ return false;
+ }
+ if ((h->adx_data_start + h->adx_data_length) >
+ ad->adx_header.adx_total_size)
+ {
+ DBG_ERR("Bad length: %" PRIu32 "\n", h->adx_data_length);
+ return false;
+ }
+
+ if (h->adx_num_attrs > AD_XATTR_MAX_ENTRIES) {
+ DBG_ERR("Bad num xattrs: %" PRIu16 "\n", h->adx_num_attrs);
+ return false;
+ }
+
+ if (h->adx_num_attrs == 0) {
+ return true;
+ }
+
+ ad->adx_entries = talloc_zero_array(
+ ad, struct ad_xattr_entry, h->adx_num_attrs);
+ if (ad->adx_entries == NULL) {
+ return false;
+ }
+
+ hoff += AD_XATTR_HDR_SIZE;
+
+ for (i = 0; i < h->adx_num_attrs; i++) {
+ struct ad_xattr_entry *e = &ad->adx_entries[i];
+
+ hoff = (hoff + 3) & ~3;
+
+ e->adx_offset = RIVAL(p, hoff + 0);
+ e->adx_length = RIVAL(p, hoff + 4);
+ e->adx_flags = RSVAL(p, hoff + 8);
+ e->adx_namelen = *(p + hoff + 10);
+
+ if (e->adx_offset >= ad->adx_header.adx_total_size) {
+ DBG_ERR("Bad adx_offset: %" PRIx32 "\n",
+ e->adx_offset);
+ return false;
+ }
+
+ if ((e->adx_offset + e->adx_length) < e->adx_offset) {
+ DBG_ERR("Bad adx_length: %" PRIx32 "\n",
+ e->adx_length);
+ return false;
+ }
+
+ if ((e->adx_offset + e->adx_length) >
+ ad->adx_header.adx_total_size)
+ {
+ DBG_ERR("Bad adx_length: %" PRIx32 "\n",
+ e->adx_length);
+ return false;
+ }
+
+ if (e->adx_namelen == 0) {
+ DBG_ERR("Bad adx_namelen: %" PRIx32 "\n",
+ e->adx_namelen);
+ return false;
+ }
+ if ((hoff + 11 + e->adx_namelen) < hoff + 11) {
+ DBG_ERR("Bad adx_namelen: %" PRIx32 "\n",
+ e->adx_namelen);
+ return false;
+ }
+ if ((hoff + 11 + e->adx_namelen) >
+ ad->adx_header.adx_data_start)
+ {
+ DBG_ERR("Bad adx_namelen: %" PRIx32 "\n",
+ e->adx_namelen);
+ return false;
+ }
+
+ e->adx_name = talloc_strndup(ad->adx_entries,
+ p + hoff + 11,
+ e->adx_namelen);
+ if (e->adx_name == NULL) {
+ return false;
+ }
+
+ DBG_DEBUG("xattr [%s] offset [0x%x] size [0x%x]\n",
+ e->adx_name, e->adx_offset, e->adx_length);
+ dump_data(10, (uint8_t *)(ad->ad_data + e->adx_offset),
+ e->adx_length);
+
+ hoff += 11 + e->adx_namelen;
+ }
+
+ return true;
+}
+
+/**
+ * Unpack an AppleDouble blob into a struct adoble
+ **/
+static bool ad_unpack(struct adouble *ad, const size_t nentries,
+ size_t filesize)
+{
+ size_t bufsize = talloc_get_size(ad->ad_data);
+ size_t adentries, i;
+ uint32_t eid, len, off;
+ bool ok;
+
+ /*
+ * The size of the buffer ad->ad_data is checked when read, so
+ * we wouldn't have to check our own offsets, a few extra
+ * checks won't hurt though. We have to check the offsets we
+ * read from the buffer anyway.
+ */
+
+ if (bufsize < (AD_HEADER_LEN + (AD_ENTRY_LEN * nentries))) {
+ DBG_NOTICE("Bad size\n");
+ return false;
+ }
+
+ ad->ad_magic = RIVAL(ad->ad_data, 0);
+ ad->ad_version = RIVAL(ad->ad_data, ADEDOFF_VERSION);
+ if ((ad->ad_magic != AD_MAGIC) || (ad->ad_version != AD_VERSION)) {
+ DBG_NOTICE("Wrong magic or version\n");
+ return false;
+ }
+
+ memcpy(ad->ad_filler, ad->ad_data + ADEDOFF_FILLER, ADEDLEN_FILLER);
+
+ adentries = RSVAL(ad->ad_data, ADEDOFF_NENTRIES);
+ if (adentries != nentries) {
+ DBG_NOTICE("Invalid number of entries: %zu\n", adentries);
+ return false;
+ }
+
+ /* now, read in the entry bits */
+ for (i = 0; i < adentries; i++) {
+ eid = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN));
+ eid = get_eid(eid);
+ off = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN) + 4);
+ len = RIVAL(ad->ad_data, AD_HEADER_LEN + (i * AD_ENTRY_LEN) + 8);
+
+ if (!eid || eid >= ADEID_MAX) {
+ DBG_NOTICE("Bogus eid %d\n", eid);
+ return false;
+ }
+
+ /*
+ * All entries other than the resource fork are
+ * expected to be read into the ad_data buffer, so
+ * ensure the specified offset is within that bound
+ */
+ if ((off > bufsize) && (eid != ADEID_RFORK)) {
+ DBG_NOTICE("Bogus eid %d: off: %" PRIu32
+ ", len: %" PRIu32 "\n",
+ eid,
+ off,
+ len);
+ return false;
+ }
+
+ ok = ad_entry_check_size(eid, bufsize, off, len);
+ if (!ok) {
+ DBG_NOTICE("Bogus eid [%" PRIu32 "] bufsize [%zu] "
+ "off [%" PRIu32 "] len [%" PRIu32 "]\n",
+ eid,
+ bufsize,
+ off,
+ len);
+ return false;
+ }
+
+ /*
+ * That would be obviously broken
+ */
+ if (off > filesize) {
+ DBG_NOTICE("Bogus eid %d: off: %" PRIu32
+ ", len: %" PRIu32 "\n",
+ eid,
+ off,
+ len);
+ return false;
+ }
+
+ /*
+ * Check for any entry that has its end beyond the
+ * filesize.
+ */
+ if (off + len < off) {
+ DBG_NOTICE("offset wrap in eid %d: off: %" PRIu32
+ ", len: %" PRIu32 "\n",
+ eid,
+ off,
+ len);
+ return false;
+
+ }
+ if (off + len > filesize) {
+ /*
+ * If this is the resource fork entry, we fix
+ * up the length, for any other entry we bail
+ * out.
+ */
+ if (eid != ADEID_RFORK) {
+ DBG_NOTICE("Bogus eid %d: off: %" PRIu32
+ ", len: %" PRIu32 "\n",
+ eid,
+ off,
+ len);
+ return false;
+ }
+
+ /*
+ * Fixup the resource fork entry by limiting
+ * the size to entryoffset - filesize.
+ */
+ len = filesize - off;
+ DBG_NOTICE("Limiting ADEID_RFORK: off: %" PRIu32
+ ", len: %" PRIu32 "\n",
+ off,
+ len);
+ }
+
+ ad->ad_eid[eid].ade_off = off;
+ ad->ad_eid[eid].ade_len = len;
+ }
+
+ if (ad->ad_type == ADOUBLE_RSRC) {
+ ok = ad_unpack_xattrs(ad);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool ad_convert_move_reso(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname)
+{
+ char *buf = NULL;
+ size_t rforklen;
+ size_t rforkoff;
+ ssize_t n;
+ int ret;
+
+ rforklen = ad_getentrylen(ad, ADEID_RFORK);
+ if (rforklen == 0) {
+ return true;
+ }
+
+ buf = talloc_size(ad, rforklen);
+ if (buf == NULL) {
+ /*
+ * This allocates a buffer for reading the resource fork data in
+ * one big swoop. Resource forks won't be larger then, say, 64
+ * MB, I swear, so just doing the allocation with the talloc
+ * limit as safeguard seems safe.
+ */
+ DBG_ERR("Failed to allocate %zu bytes for rfork\n",
+ rforklen);
+ return false;
+ }
+
+ rforkoff = ad_getentryoff(ad, ADEID_RFORK);
+
+ n = SMB_VFS_PREAD(ad->ad_fsp, buf, rforklen, rforkoff);
+ if (n != rforklen) {
+ DBG_ERR("Reading %zu bytes from rfork [%s] failed: %s\n",
+ rforklen, fsp_str_dbg(ad->ad_fsp), strerror(errno));
+ return false;
+ }
+
+ rforkoff = ADEDOFF_RFORK_DOT_UND;
+
+ n = SMB_VFS_PWRITE(ad->ad_fsp, buf, rforklen, rforkoff);
+ if (n != rforklen) {
+ DBG_ERR("Writing %zu bytes to rfork [%s] failed: %s\n",
+ rforklen, fsp_str_dbg(ad->ad_fsp), strerror(errno));
+ return false;
+ }
+
+ ad_setentryoff(ad, ADEID_RFORK, ADEDOFF_RFORK_DOT_UND);
+
+ ret = ad_fset(handle, ad, ad->ad_fsp);
+ if (ret != 0) {
+ DBG_ERR("ad_fset on [%s] failed\n", fsp_str_dbg(ad->ad_fsp));
+ return false;
+ }
+
+ return true;
+}
+
+static bool ad_convert_xattr(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname,
+ const char *catia_mappings,
+ bool *converted_xattr)
+{
+ static struct char_mappings **string_replace_cmaps = NULL;
+ uint16_t i;
+ int saved_errno = 0;
+ NTSTATUS status;
+ int rc;
+ bool ok;
+
+ *converted_xattr = false;
+
+ if (ad_getentrylen(ad, ADEID_FINDERI) == ADEDLEN_FINDERI) {
+ return true;
+ }
+
+ if (string_replace_cmaps == NULL) {
+ const char **mappings = NULL;
+
+ mappings = str_list_make_v3_const(
+ talloc_tos(), catia_mappings, NULL);
+ if (mappings == NULL) {
+ return false;
+ }
+ string_replace_cmaps = string_replace_init_map(
+ handle->conn->sconn, mappings);
+ TALLOC_FREE(mappings);
+ }
+
+ for (i = 0; i < ad->adx_header.adx_num_attrs; i++) {
+ struct ad_xattr_entry *e = &ad->adx_entries[i];
+ char *mapped_name = NULL;
+ char *tmp = NULL;
+ struct smb_filename *stream_name = NULL;
+ files_struct *fsp = NULL;
+ ssize_t nwritten;
+
+ status = string_replace_allocate(handle->conn,
+ e->adx_name,
+ string_replace_cmaps,
+ talloc_tos(),
+ &mapped_name,
+ vfs_translate_to_windows);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))
+ {
+ DBG_ERR("string_replace_allocate failed\n");
+ ok = false;
+ goto fail;
+ }
+
+ tmp = mapped_name;
+ mapped_name = talloc_asprintf(talloc_tos(), ":%s", tmp);
+ TALLOC_FREE(tmp);
+ if (mapped_name == NULL) {
+ ok = false;
+ goto fail;
+ }
+
+ stream_name = synthetic_smb_fname(talloc_tos(),
+ smb_fname->base_name,
+ mapped_name,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ TALLOC_FREE(mapped_name);
+ if (stream_name == NULL) {
+ DBG_ERR("synthetic_smb_fname failed\n");
+ ok = false;
+ goto fail;
+ }
+
+ DBG_DEBUG("stream_name: %s\n", smb_fname_str_dbg(stream_name));
+
+ status = openat_pathref_fsp(handle->conn->cwd_fsp, stream_name);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ ok = false;
+ goto fail;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ handle->conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ stream_name, /* fname */
+ FILE_GENERIC_WRITE, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN_IF, /* create_disposition */
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* psbuf */
+ NULL, NULL); /* create context */
+ TALLOC_FREE(stream_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("SMB_VFS_CREATE_FILE failed\n");
+ ok = false;
+ goto fail;
+ }
+
+ nwritten = SMB_VFS_PWRITE(fsp,
+ ad->ad_data + e->adx_offset,
+ e->adx_length,
+ 0);
+ if (nwritten == -1) {
+ DBG_ERR("SMB_VFS_PWRITE failed\n");
+ saved_errno = errno;
+ close_file_free(NULL, &fsp, ERROR_CLOSE);
+ errno = saved_errno;
+ ok = false;
+ goto fail;
+ }
+
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ ok = false;
+ goto fail;
+ }
+ fsp = NULL;
+ }
+
+ ad->adx_header.adx_num_attrs = 0;
+ TALLOC_FREE(ad->adx_entries);
+
+ ad_setentrylen(ad, ADEID_FINDERI, ADEDLEN_FINDERI);
+
+ rc = ad_fset(handle, ad, ad->ad_fsp);
+ if (rc != 0) {
+ DBG_ERR("ad_fset on [%s] failed: %s\n",
+ fsp_str_dbg(ad->ad_fsp), strerror(errno));
+ ok = false;
+ goto fail;
+ }
+
+ ok = ad_convert_move_reso(handle, ad, smb_fname);
+ if (!ok) {
+ goto fail;
+ }
+
+ *converted_xattr = true;
+ ok = true;
+
+fail:
+ return ok;
+}
+
+static bool ad_convert_finderinfo(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname)
+{
+ char *p_ad = NULL;
+ AfpInfo *ai = NULL;
+ DATA_BLOB aiblob;
+ struct smb_filename *stream_name = NULL;
+ files_struct *fsp = NULL;
+ size_t size;
+ ssize_t nwritten;
+ NTSTATUS status;
+ int saved_errno = 0;
+ int cmp;
+
+ cmp = memcmp(ad->ad_filler, AD_FILLER_TAG_OSX, ADEDLEN_FILLER);
+ if (cmp != 0) {
+ return true;
+ }
+
+ p_ad = ad_get_entry(ad, ADEID_FINDERI);
+ if (p_ad == NULL) {
+ return false;
+ }
+
+ ai = afpinfo_new(talloc_tos());
+ if (ai == NULL) {
+ return false;
+ }
+
+ memcpy(ai->afpi_FinderInfo, p_ad, ADEDLEN_FINDERI);
+
+ aiblob = data_blob_talloc(talloc_tos(), NULL, AFP_INFO_SIZE);
+ if (aiblob.data == NULL) {
+ TALLOC_FREE(ai);
+ return false;
+ }
+
+ size = afpinfo_pack(ai, (char *)aiblob.data);
+ TALLOC_FREE(ai);
+ if (size != AFP_INFO_SIZE) {
+ return false;
+ }
+
+ stream_name = synthetic_smb_fname(talloc_tos(),
+ smb_fname->base_name,
+ AFPINFO_STREAM,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (stream_name == NULL) {
+ data_blob_free(&aiblob);
+ DBG_ERR("synthetic_smb_fname failed\n");
+ return false;
+ }
+
+ DBG_DEBUG("stream_name: %s\n", smb_fname_str_dbg(stream_name));
+
+ status = openat_pathref_fsp(handle->conn->cwd_fsp, stream_name);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ return false;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ handle->conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ stream_name, /* fname */
+ FILE_GENERIC_WRITE, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN_IF, /* create_disposition */
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* psbuf */
+ NULL, NULL); /* create context */
+ TALLOC_FREE(stream_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("SMB_VFS_CREATE_FILE failed\n");
+ return false;
+ }
+
+ nwritten = SMB_VFS_PWRITE(fsp,
+ aiblob.data,
+ aiblob.length,
+ 0);
+ if (nwritten == -1) {
+ DBG_ERR("SMB_VFS_PWRITE failed\n");
+ saved_errno = errno;
+ close_file_free(NULL, &fsp, ERROR_CLOSE);
+ errno = saved_errno;
+ return false;
+ }
+
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ fsp = NULL;
+
+ return true;
+}
+
+static bool ad_convert_truncate(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname)
+{
+ int rc;
+ off_t newlen;
+
+ newlen = ADEDOFF_RFORK_DOT_UND + ad_getentrylen(ad, ADEID_RFORK);
+
+ rc = SMB_VFS_FTRUNCATE(ad->ad_fsp, newlen);
+ if (rc != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool ad_convert_blank_rfork(vfs_handle_struct *handle,
+ struct adouble *ad,
+ uint32_t flags,
+ bool *blank)
+{
+ size_t rforklen = sizeof(empty_resourcefork);
+ char buf[rforklen];
+ ssize_t nread;
+ int cmp;
+ int rc;
+
+ *blank = false;
+
+ if (!(flags & AD_CONV_WIPE_BLANK)) {
+ return true;
+ }
+
+ if (ad_getentrylen(ad, ADEID_RFORK) != rforklen) {
+ return true;
+ }
+
+ nread = SMB_VFS_PREAD(ad->ad_fsp, buf, rforklen, ADEDOFF_RFORK_DOT_UND);
+ if (nread != rforklen) {
+ DBG_ERR("Reading %zu bytes from rfork [%s] failed: %s\n",
+ rforklen, fsp_str_dbg(ad->ad_fsp), strerror(errno));
+ return false;
+ }
+
+ cmp = memcmp(buf, empty_resourcefork, rforklen);
+ if (cmp != 0) {
+ return true;
+ }
+
+ ad_setentrylen(ad, ADEID_RFORK, 0);
+
+ rc = ad_fset(handle, ad, ad->ad_fsp);
+ if (rc != 0) {
+ DBG_ERR("ad_fset on [%s] failed\n", fsp_str_dbg(ad->ad_fsp));
+ return false;
+ }
+
+ *blank = true;
+ return true;
+}
+
+static bool ad_convert_delete_adfile(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname,
+ uint32_t flags)
+{
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ struct smb_filename *ad_name = NULL;
+ NTSTATUS status;
+ int rc;
+
+ if (ad_getentrylen(ad, ADEID_RFORK) > 0) {
+ return true;
+ }
+
+ if (!(flags & AD_CONV_DELETE)) {
+ return true;
+ }
+
+ rc = adouble_path(talloc_tos(), smb_fname, &ad_name);
+ if (rc != 0) {
+ return false;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ ad_name,
+ &parent_fname,
+ &at_fname);
+ TALLOC_FREE(ad_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ parent_fname->fsp,
+ at_fname,
+ 0);
+ if (rc != 0) {
+ DBG_ERR("Unlinking [%s/%s] failed: %s\n",
+ smb_fname_str_dbg(parent_fname),
+ smb_fname_str_dbg(at_fname), strerror(errno));
+ TALLOC_FREE(parent_fname);
+ return false;
+ }
+
+ DBG_WARNING("Unlinked [%s/%s] after conversion\n",
+ smb_fname_str_dbg(parent_fname),
+ smb_fname_str_dbg(at_fname));
+ TALLOC_FREE(parent_fname);
+
+ return true;
+}
+
+/**
+ * Convert from Apple's ._ file to Netatalk
+ *
+ * Apple's AppleDouble may contain a FinderInfo entry longer then 32
+ * bytes containing packed xattrs.
+ *
+ * @return -1 in case an error occurred, 0 if no conversion was done, 1
+ * otherwise
+ **/
+int ad_convert(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ const char *catia_mappings,
+ uint32_t flags)
+{
+ struct adouble *ad = NULL;
+ bool ok;
+ bool converted_xattr = false;
+ bool blank;
+ int ret;
+
+ ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ return 0;
+ }
+
+ ok = ad_convert_xattr(handle,
+ ad,
+ smb_fname,
+ catia_mappings,
+ &converted_xattr);
+ if (!ok) {
+ ret = -1;
+ goto done;
+ }
+
+ ok = ad_convert_blank_rfork(handle, ad, flags, &blank);
+ if (!ok) {
+ ret = -1;
+ goto done;
+ }
+
+ if (converted_xattr || blank) {
+ ok = ad_convert_truncate(handle, ad, smb_fname);
+ if (!ok) {
+ ret = -1;
+ goto done;
+ }
+ }
+
+ ok = ad_convert_finderinfo(handle, ad, smb_fname);
+ if (!ok) {
+ DBG_ERR("Failed to convert [%s]\n",
+ smb_fname_str_dbg(smb_fname));
+ ret = -1;
+ goto done;
+ }
+
+ ok = ad_convert_delete_adfile(handle,
+ ad,
+ smb_fname,
+ flags);
+ if (!ok) {
+ ret = -1;
+ goto done;
+ }
+
+ ret = 0;
+done:
+ TALLOC_FREE(ad);
+ return ret;
+}
+
+static bool ad_unconvert_open_ad(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ struct smb_filename *adpath,
+ files_struct **_fsp)
+{
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ int ret;
+
+ status = openat_pathref_fsp(handle->conn->cwd_fsp, adpath);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ return false;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ handle->conn,
+ NULL, /* req */
+ NULL, /* dirfsp */
+ adpath,
+ FILE_READ_DATA|FILE_WRITE_DATA,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN_IF,
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY,
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp,
+ NULL, /* info */
+ NULL, NULL); /* create context */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("SMB_VFS_CREATE_FILE [%s] failed: %s\n",
+ smb_fname_str_dbg(adpath), nt_errstr(status));
+ return false;
+ }
+
+ if (fsp->fsp_name->st.st_ex_uid != smb_fname->st.st_ex_uid ||
+ fsp->fsp_name->st.st_ex_gid != smb_fname->st.st_ex_gid)
+ {
+ ret = SMB_VFS_FCHOWN(fsp,
+ smb_fname->st.st_ex_uid,
+ smb_fname->st.st_ex_gid);
+ if (ret != 0) {
+ DBG_ERR("SMB_VFS_FCHOWN [%s] failed: %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ return false;
+ }
+ }
+
+ *_fsp = fsp;
+ return true;
+}
+
+static bool ad_unconvert_get_streams(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams)
+{
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ status = openat_pathref_fsp(handle->conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ handle->conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_READ_ATTRIBUTES, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Opening [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ return false;
+ }
+
+ status = vfs_fstreaminfo(fsp,
+ mem_ctx,
+ num_streams,
+ streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ DBG_ERR("streaminfo on [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ return false;
+ }
+
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("close_file [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+struct ad_collect_state {
+ bool have_adfile;
+ size_t adx_data_off;
+ char *rsrc_data_buf;
+};
+
+static bool ad_collect_one_stream(struct vfs_handle_struct *handle,
+ struct char_mappings **cmaps,
+ struct smb_filename *smb_fname,
+ const struct stream_struct *stream,
+ struct adouble *ad,
+ struct ad_collect_state *state)
+{
+ struct smb_filename *sname = NULL;
+ files_struct *fsp = NULL;
+ struct ad_xattr_entry *e = NULL;
+ char *mapped_name = NULL;
+ char *p = NULL;
+ size_t needed_size;
+ ssize_t nread;
+ NTSTATUS status;
+ bool ok;
+
+ sname = synthetic_smb_fname(ad,
+ smb_fname->base_name,
+ stream->name,
+ NULL,
+ smb_fname->twrp,
+ 0);
+ if (sname == NULL) {
+ return false;
+ }
+
+ if (is_ntfs_default_stream_smb_fname(sname)) {
+ TALLOC_FREE(sname);
+ return true;
+ }
+
+ DBG_DEBUG("Collecting stream [%s]\n", smb_fname_str_dbg(sname));
+
+ status = openat_pathref_fsp(handle->conn->cwd_fsp, sname);
+ if (!NT_STATUS_IS_OK(status)) {
+ ok = false;
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ handle->conn,
+ NULL, /* req */
+ NULL, /* dirfsp */
+ sname,
+ FILE_READ_DATA|DELETE_ACCESS,
+ FILE_SHARE_READ,
+ FILE_OPEN,
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp,
+ NULL, /* info */
+ NULL, NULL); /* create context */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("SMB_VFS_CREATE_FILE [%s] failed\n",
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+
+ if (is_afpinfo_stream(stream->name)) {
+ char buf[AFP_INFO_SIZE];
+
+ if (stream->size != AFP_INFO_SIZE) {
+ DBG_ERR("Bad size [%zd] on [%s]\n",
+ (ssize_t)stream->size,
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+
+ nread = SMB_VFS_PREAD(fsp, buf, stream->size, 0);
+ if (nread != AFP_INFO_SIZE) {
+ DBG_ERR("Bad size [%zd] on [%s]\n",
+ (ssize_t)stream->size,
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+
+ memcpy(ad->ad_data + ADEDOFF_FINDERI_DOT_UND,
+ buf + AFP_OFF_FinderInfo,
+ AFP_FinderSize);
+
+ ok = set_delete_on_close(fsp,
+ true,
+ fsp->conn->session_info->security_token,
+ fsp->conn->session_info->unix_token);
+ if (!ok) {
+ DBG_ERR("Deleting [%s] failed\n",
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+ ok = true;
+ goto out;
+ }
+
+ if (is_afpresource_stream(stream->name)) {
+ ad->ad_rsrc_data = talloc_size(ad, stream->size);
+ if (ad->ad_rsrc_data == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ nread = SMB_VFS_PREAD(fsp,
+ ad->ad_rsrc_data,
+ stream->size,
+ 0);
+ if (nread != stream->size) {
+ DBG_ERR("Bad size [%zd] on [%s]\n",
+ (ssize_t)stream->size,
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+
+ ad_setentrylen(ad, ADEID_RFORK, stream->size);
+
+ if (!state->have_adfile) {
+ /*
+ * We have a resource *stream* but no AppleDouble
+ * sidecar file, this means the share is configured with
+ * fruit:resource=stream. So we should delete the
+ * resource stream.
+ */
+ ok = set_delete_on_close(
+ fsp,
+ true,
+ fsp->conn->session_info->security_token,
+ fsp->conn->session_info->unix_token);
+ if (!ok) {
+ DBG_ERR("Deleting [%s] failed\n",
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+ }
+ ok = true;
+ goto out;
+ }
+
+ ad->adx_entries = talloc_realloc(ad,
+ ad->adx_entries,
+ struct ad_xattr_entry,
+ ad->adx_header.adx_num_attrs + 1);
+ if (ad->adx_entries == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ e = &ad->adx_entries[ad->adx_header.adx_num_attrs];
+ *e = (struct ad_xattr_entry) {
+ .adx_length = stream->size,
+ };
+ e->adx_name = talloc_strdup(ad, stream->name + 1);
+ if (e->adx_name == NULL) {
+ ok = false;
+ goto out;
+ }
+ p = strchr(e->adx_name, ':');
+ if (p != NULL) {
+ *p = '\0';
+ }
+
+ status = string_replace_allocate(handle->conn,
+ e->adx_name,
+ cmaps,
+ ad,
+ &mapped_name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))
+ {
+ DBG_ERR("string_replace_allocate failed\n");
+ ok = false;
+ goto out;
+ }
+
+ e->adx_name = mapped_name;
+ e->adx_namelen = strlen(e->adx_name) + 1,
+
+ DBG_DEBUG("%u: name (%s) size (%zu)\n",
+ ad->adx_header.adx_num_attrs,
+ e->adx_name,
+ (size_t)e->adx_length);
+
+ ad->adx_header.adx_num_attrs++;
+
+ needed_size = state->adx_data_off + stream->size;
+ if (needed_size > talloc_get_size(ad->adx_data)) {
+ ad->adx_data = talloc_realloc(ad,
+ ad->adx_data,
+ char,
+ needed_size);
+ if (ad->adx_data == NULL) {
+ ok = false;
+ goto out;
+ }
+ }
+
+ nread = SMB_VFS_PREAD(fsp,
+ ad->adx_data + state->adx_data_off,
+ stream->size,
+ 0);
+ if (nread != stream->size) {
+ DBG_ERR("Bad size [%zd] on [%s]\n",
+ (ssize_t)stream->size,
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+ state->adx_data_off += nread;
+
+ ok = set_delete_on_close(fsp,
+ true,
+ fsp->conn->session_info->security_token,
+ fsp->conn->session_info->unix_token);
+ if (!ok) {
+ DBG_ERR("Deleting [%s] failed\n",
+ smb_fname_str_dbg(sname));
+ ok = false;
+ goto out;
+ }
+
+out:
+ TALLOC_FREE(sname);
+ if (fsp != NULL) {
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("close_file [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ ok = false;
+ }
+ }
+
+ return ok;
+}
+
+/**
+ * Convert filesystem metadata to AppleDouble file
+ **/
+bool ad_unconvert(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const char *catia_mappings,
+ struct smb_filename *smb_fname,
+ bool *converted)
+{
+ static struct char_mappings **cmaps = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct ad_collect_state state;
+ struct stream_struct *streams = NULL;
+ struct smb_filename *adpath = NULL;
+ struct adouble *ad = NULL;
+ unsigned int num_streams = 0;
+ size_t to_convert = 0;
+ bool have_rsrc = false;
+ files_struct *fsp = NULL;
+ size_t i;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ *converted = false;
+
+ if (cmaps == NULL) {
+ const char **mappings = NULL;
+
+ mappings = str_list_make_v3_const(
+ frame, catia_mappings, NULL);
+ if (mappings == NULL) {
+ ok = false;
+ goto out;
+ }
+ cmaps = string_replace_init_map(mem_ctx, mappings);
+ TALLOC_FREE(mappings);
+ }
+
+ ok = ad_unconvert_get_streams(handle,
+ smb_fname,
+ frame,
+ &num_streams,
+ &streams);
+ if (!ok) {
+ goto out;
+ }
+
+ for (i = 0; i < num_streams; i++) {
+ if (strcasecmp_m(streams[i].name, "::$DATA") == 0) {
+ continue;
+ }
+ to_convert++;
+ if (is_afpresource_stream(streams[i].name)) {
+ have_rsrc = true;
+ }
+ }
+
+ if (to_convert == 0) {
+ ok = true;
+ goto out;
+ }
+
+ state = (struct ad_collect_state) {
+ .adx_data_off = 0,
+ };
+
+ ret = adouble_path(frame, smb_fname, &adpath);
+ if (ret != 0) {
+ ok = false;
+ goto out;
+ }
+
+ ret = SMB_VFS_STAT(handle->conn, adpath);
+ if (ret == 0) {
+ state.have_adfile = true;
+ } else {
+ if (errno != ENOENT) {
+ ok = false;
+ goto out;
+ }
+ state.have_adfile = false;
+ }
+
+ if (to_convert == 1 && have_rsrc && state.have_adfile) {
+ /*
+ * So we have just a single stream, the resource fork stream
+ * from an AppleDouble file. Fine, that means there's nothing to
+ * convert.
+ */
+ ok = true;
+ goto out;
+ }
+
+ ad = ad_init(frame, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ for (i = 0; i < num_streams; i++) {
+ ok = ad_collect_one_stream(handle,
+ cmaps,
+ smb_fname,
+ &streams[i],
+ ad,
+ &state);
+ if (!ok) {
+ goto out;
+ }
+ }
+
+ ok = ad_unconvert_open_ad(frame, handle, smb_fname, adpath, &fsp);
+ if (!ok) {
+ DBG_ERR("Failed to open adfile [%s]\n",
+ smb_fname_str_dbg(smb_fname));
+ goto out;
+ }
+
+ ret = ad_fset(handle, ad, fsp);
+ if (ret != 0) {
+ ok = false;
+ goto out;
+ }
+
+ *converted = true;
+ ok = true;
+
+out:
+ if (fsp != NULL) {
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("close_file_free() [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ ok = false;
+ }
+ }
+ TALLOC_FREE(frame);
+ return ok;
+}
+
+/**
+ * Read and parse Netatalk AppleDouble metadata xattr
+ **/
+static ssize_t ad_read_meta(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname)
+{
+ int rc = 0;
+ ssize_t ealen;
+ bool ok;
+ struct files_struct *fsp = smb_fname->fsp;
+
+ DEBUG(10, ("reading meta xattr for %s\n", smb_fname->base_name));
+
+ fsp = metadata_fsp(fsp);
+
+ ealen = SMB_VFS_FGETXATTR(fsp,
+ AFPINFO_EA_NETATALK,
+ ad->ad_data,
+ AD_DATASZ_XATTR);
+
+ if (ealen == -1) {
+ switch (errno) {
+ case ENOATTR:
+ case ENOENT:
+ if (errno == ENOATTR) {
+ errno = ENOENT;
+ }
+ rc = -1;
+ goto exit;
+ default:
+ DEBUG(2, ("error reading meta xattr: %s\n",
+ strerror(errno)));
+ rc = -1;
+ goto exit;
+ }
+ }
+ if (ealen != AD_DATASZ_XATTR) {
+ DEBUG(2, ("bad size %zd\n", ealen));
+ errno = EINVAL;
+ rc = -1;
+ goto exit;
+ }
+
+ /* Now parse entries */
+ ok = ad_unpack(ad, ADEID_NUM_XATTR, AD_DATASZ_XATTR);
+ if (!ok) {
+ DBG_WARNING(
+ "Invalid AppleDouble xattr metadata (%s) in file: %s - "
+ "Consider deleting the corrupted file.\n",
+ smb_fname->base_name,
+ ad->ad_fsp->fsp_name->base_name);
+ errno = EINVAL;
+ rc = -1;
+ goto exit;
+ }
+
+ if (!ad_getentryoff(ad, ADEID_FINDERI)
+ || !ad_getentryoff(ad, ADEID_COMMENT)
+ || !ad_getentryoff(ad, ADEID_FILEDATESI)
+ || !ad_getentryoff(ad, ADEID_AFPFILEI)
+ || !ad_getentryoff(ad, ADEID_PRIVDEV)
+ || !ad_getentryoff(ad, ADEID_PRIVINO)
+ || !ad_getentryoff(ad, ADEID_PRIVSYN)
+ || !ad_getentryoff(ad, ADEID_PRIVID)) {
+ DEBUG(2, ("invalid AppleDouble metadata xattr\n"));
+ errno = EINVAL;
+ rc = -1;
+ goto exit;
+ }
+
+exit:
+ DEBUG(10, ("reading meta xattr for %s, rc: %d\n",
+ smb_fname->base_name, rc));
+
+ if (rc != 0) {
+ ealen = -1;
+ if (errno == EINVAL) {
+ become_root();
+ (void)SMB_VFS_FREMOVEXATTR(fsp,
+ AFPINFO_EA_NETATALK);
+ unbecome_root();
+ errno = ENOENT;
+ }
+ }
+ return ealen;
+}
+
+static NTSTATUS adouble_open_rsrc_fsp(const struct files_struct *dirfsp,
+ const struct smb_filename *smb_base_fname,
+ int in_flags,
+ mode_t mode,
+ struct files_struct **_ad_fsp)
+{
+ int rc = 0;
+ struct adouble *ad = NULL;
+ struct smb_filename *adp_smb_fname = NULL;
+ struct files_struct *ad_fsp = NULL;
+ NTSTATUS status;
+ struct vfs_open_how how = { .flags = in_flags, .mode = mode, };
+
+ rc = adouble_path(talloc_tos(),
+ smb_base_fname,
+ &adp_smb_fname);
+ if (rc != 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = create_internal_fsp(dirfsp->conn,
+ adp_smb_fname,
+ &ad_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+#ifdef O_PATH
+ how.flags &= ~(O_PATH);
+#endif
+ if (how.flags & (O_CREAT | O_TRUNC | O_WRONLY)) {
+ /* We always need read/write access for the metadata header too */
+ how.flags &= ~(O_WRONLY);
+ how.flags |= O_RDWR;
+ }
+
+ status = fd_openat(dirfsp,
+ adp_smb_fname,
+ ad_fsp,
+ &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(NULL, ad_fsp);
+ return status;
+ }
+
+ if (how.flags & (O_CREAT | O_TRUNC)) {
+ ad = ad_init(talloc_tos(), ADOUBLE_RSRC);
+ if (ad == NULL) {
+ file_free(NULL, ad_fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rc = ad_fset(ad_fsp->conn->vfs_handles, ad, ad_fsp);
+ if (rc != 0) {
+ file_free(NULL, ad_fsp);
+ return NT_STATUS_IO_DEVICE_ERROR;
+ }
+ TALLOC_FREE(ad);
+ }
+
+ *_ad_fsp = ad_fsp;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS adouble_open_from_base_fsp(const struct files_struct *dirfsp,
+ struct files_struct *base_fsp,
+ adouble_type_t type,
+ int flags,
+ mode_t mode,
+ struct files_struct **_ad_fsp)
+{
+ *_ad_fsp = NULL;
+
+ SMB_ASSERT(base_fsp != NULL);
+ SMB_ASSERT(!fsp_is_alternate_stream(base_fsp));
+
+ switch (type) {
+ case ADOUBLE_META:
+ return NT_STATUS_INTERNAL_ERROR;
+ case ADOUBLE_RSRC:
+ return adouble_open_rsrc_fsp(dirfsp,
+ base_fsp->fsp_name,
+ flags,
+ mode,
+ _ad_fsp);
+ }
+
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+/*
+ * Here's the deal: for ADOUBLE_META we can do without an fd as we can issue
+ * path based xattr calls. For ADOUBLE_RSRC however we need a full-fledged fd
+ * for file IO on the ._ file.
+ */
+static int ad_open(vfs_handle_struct *handle,
+ struct adouble *ad,
+ files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ int flags,
+ mode_t mode)
+{
+ NTSTATUS status;
+
+ DBG_DEBUG("Path [%s] type [%s]\n", smb_fname->base_name,
+ ad->ad_type == ADOUBLE_META ? "meta" : "rsrc");
+
+ if (ad->ad_type == ADOUBLE_META) {
+ return 0;
+ }
+
+ if (fsp != NULL) {
+ ad->ad_fsp = fsp;
+ ad->ad_opened = false;
+ return 0;
+ }
+
+ status = adouble_open_rsrc_fsp(handle->conn->cwd_fsp,
+ smb_fname,
+ flags,
+ mode,
+ &ad->ad_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+ ad->ad_opened = true;
+
+ DBG_DEBUG("Path [%s] type [%s]\n",
+ smb_fname->base_name,
+ ad->ad_type == ADOUBLE_META ? "meta" : "rsrc");
+
+ return 0;
+}
+
+static ssize_t ad_read_rsrc_adouble(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname)
+{
+ size_t to_read;
+ ssize_t len;
+ int ret;
+ bool ok;
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, ad->ad_fsp, &ad->ad_fsp->fsp_name->st);
+ if (ret != 0) {
+ DBG_ERR("fstat [%s] failed: %s\n",
+ fsp_str_dbg(ad->ad_fsp), strerror(errno));
+ return -1;
+ }
+
+ to_read = ad->ad_fsp->fsp_name->st.st_ex_size;
+ if (to_read > AD_XATTR_MAX_HDR_SIZE) {
+ to_read = AD_XATTR_MAX_HDR_SIZE;
+ }
+
+ len = SMB_VFS_NEXT_PREAD(handle,
+ ad->ad_fsp,
+ ad->ad_data,
+ to_read,
+ 0);
+ if (len != to_read) {
+ DBG_NOTICE("%s %s: bad size: %zd\n",
+ smb_fname->base_name, strerror(errno), len);
+ return -1;
+ }
+
+ /* Now parse entries */
+ ok = ad_unpack(ad,
+ ADEID_NUM_DOT_UND,
+ ad->ad_fsp->fsp_name->st.st_ex_size);
+ if (!ok) {
+ DBG_WARNING("Invalid AppleDouble resource (%s) in file: %s - "
+ "Consider deleting the corrupted file.\n",
+ smb_fname->base_name,
+ ad->ad_fsp->fsp_name->base_name);
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((ad_getentryoff(ad, ADEID_FINDERI) != ADEDOFF_FINDERI_DOT_UND)
+ || (ad_getentrylen(ad, ADEID_FINDERI) < ADEDLEN_FINDERI)
+ || (ad_getentryoff(ad, ADEID_RFORK) < ADEDOFF_RFORK_DOT_UND))
+ {
+ DBG_WARNING("Invalid AppleDouble resource (%s) in file: %s - "
+ "Consider deleting the corrupted file.\n",
+ smb_fname->base_name,
+ ad->ad_fsp->fsp_name->base_name);
+ errno = EINVAL;
+ return -1;
+ }
+
+ return len;
+}
+
+/**
+ * Read and parse resource fork, either ._ AppleDouble file or xattr
+ **/
+static ssize_t ad_read_rsrc(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname)
+{
+ return ad_read_rsrc_adouble(handle, ad, smb_fname);
+}
+
+/**
+ * Read and unpack an AppleDouble metadata xattr or resource
+ **/
+static ssize_t ad_read(vfs_handle_struct *handle,
+ struct adouble *ad,
+ const struct smb_filename *smb_fname)
+{
+ switch (ad->ad_type) {
+ case ADOUBLE_META:
+ return ad_read_meta(handle, ad, smb_fname);
+ case ADOUBLE_RSRC:
+ return ad_read_rsrc(handle, ad, smb_fname);
+ default:
+ return -1;
+ }
+}
+
+static int adouble_destructor(struct adouble *ad)
+{
+ NTSTATUS status;
+
+ if (!ad->ad_opened) {
+ return 0;
+ }
+
+ SMB_ASSERT(ad->ad_fsp != NULL);
+
+ status = fd_close(ad->ad_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Closing [%s] failed: %s\n",
+ fsp_str_dbg(ad->ad_fsp), nt_errstr(status));
+ }
+ file_free(NULL, ad->ad_fsp);
+ ad->ad_fsp = NULL;
+ ad->ad_opened = false;
+
+ return 0;
+}
+
+/**
+ * Allocate a struct adouble without initialiing it
+ *
+ * The struct is either hang of the fsp extension context or if fsp is
+ * NULL from ctx.
+ *
+ * @param[in] ctx talloc context
+ * @param[in] handle vfs handle
+ * @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
+ *
+ * @return adouble handle
+ **/
+static struct adouble *ad_alloc(TALLOC_CTX *ctx,
+ adouble_type_t type)
+{
+ int rc = 0;
+ size_t adsize = 0;
+ struct adouble *ad;
+
+ switch (type) {
+ case ADOUBLE_META:
+ adsize = AD_DATASZ_XATTR;
+ break;
+ case ADOUBLE_RSRC:
+ /*
+ * AppleDouble ._ file case, optimize for fewer (but larger)
+ * IOs. Two cases:
+ *
+ * - without xattrs size of the header is exactly
+ * AD_DATASZ_DOT_UND (82) bytes
+ *
+ * - with embedded xattrs it can be larger, up to
+ * AD_XATTR_MAX_HDR_SIZE
+ *
+ * Larger headers are not supported, but this is a reasonable
+ * limit that is also employed by the macOS client.
+ *
+ * We used the largest possible size to be able to read the full
+ * header with one IO.
+ */
+ adsize = AD_XATTR_MAX_HDR_SIZE;
+ break;
+ default:
+ return NULL;
+ }
+
+ ad = talloc_zero(ctx, struct adouble);
+ if (ad == NULL) {
+ rc = -1;
+ goto exit;
+ }
+
+ if (adsize) {
+ ad->ad_data = talloc_zero_array(ad, char, adsize);
+ if (ad->ad_data == NULL) {
+ rc = -1;
+ goto exit;
+ }
+ }
+
+ ad->ad_type = type;
+ ad->ad_magic = AD_MAGIC;
+ ad->ad_version = AD_VERSION;
+
+ talloc_set_destructor(ad, adouble_destructor);
+
+exit:
+ if (rc != 0) {
+ TALLOC_FREE(ad);
+ }
+ return ad;
+}
+
+/**
+ * Allocate and initialize a new struct adouble
+ *
+ * @param[in] ctx talloc context
+ * @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
+ *
+ * @return adouble handle, initialized
+ **/
+struct adouble *ad_init(TALLOC_CTX *ctx, adouble_type_t type)
+{
+ int rc = 0;
+ const struct ad_entry_order *eid;
+ struct adouble *ad = NULL;
+ time_t t = time(NULL);
+
+ switch (type) {
+ case ADOUBLE_META:
+ eid = entry_order_meta_xattr;
+ break;
+ case ADOUBLE_RSRC:
+ eid = entry_order_dot_und;
+ break;
+ default:
+ return NULL;
+ }
+
+ ad = ad_alloc(ctx, type);
+ if (ad == NULL) {
+ return NULL;
+ }
+
+ while (eid->id) {
+ ad->ad_eid[eid->id].ade_off = eid->offset;
+ ad->ad_eid[eid->id].ade_len = eid->len;
+ eid++;
+ }
+
+ /* put something sane in the date fields */
+ ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX, t);
+ ad_setdate(ad, AD_DATE_MODIFY | AD_DATE_UNIX, t);
+ ad_setdate(ad, AD_DATE_ACCESS | AD_DATE_UNIX, t);
+ ad_setdate(ad, AD_DATE_BACKUP, htonl(AD_DATE_START));
+
+ if (rc != 0) {
+ TALLOC_FREE(ad);
+ }
+ return ad;
+}
+
+static struct adouble *ad_get_internal(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ adouble_type_t type)
+{
+ int rc = 0;
+ ssize_t len;
+ struct adouble *ad = NULL;
+ int mode;
+
+ if (fsp != NULL) {
+ if (fsp_is_alternate_stream(fsp)) {
+ smb_fname = fsp->base_fsp->fsp_name;
+ } else {
+ smb_fname = fsp->fsp_name;
+ }
+ }
+
+ DEBUG(10, ("ad_get(%s) called for %s\n",
+ type == ADOUBLE_META ? "meta" : "rsrc",
+ smb_fname != NULL ? smb_fname->base_name : "???"));
+
+ ad = ad_alloc(ctx, type);
+ if (ad == NULL) {
+ rc = -1;
+ goto exit;
+ }
+
+ /* Try rw first so we can use the fd in ad_convert() */
+ mode = O_RDWR;
+
+ rc = ad_open(handle, ad, fsp, smb_fname, mode, 0);
+ if (rc == -1 && ((errno == EROFS) || (errno == EACCES))) {
+ mode = O_RDONLY;
+ rc = ad_open(handle, ad, fsp, smb_fname, mode, 0);
+ }
+ if (rc == -1) {
+ DBG_DEBUG("ad_open [%s] error [%s]\n",
+ smb_fname->base_name, strerror(errno));
+ goto exit;
+
+ }
+
+ len = ad_read(handle, ad, smb_fname);
+ if (len == -1) {
+ DEBUG(10, ("error reading AppleDouble for %s\n",
+ smb_fname->base_name));
+ rc = -1;
+ goto exit;
+ }
+
+exit:
+ DEBUG(10, ("ad_get(%s) for %s returning %d\n",
+ type == ADOUBLE_META ? "meta" : "rsrc",
+ smb_fname->base_name, rc));
+
+ if (rc != 0) {
+ TALLOC_FREE(ad);
+ }
+ return ad;
+}
+
+/**
+ * Return AppleDouble data for a file
+ *
+ * @param[in] ctx talloc context
+ * @param[in] handle vfs handle
+ * @param[in] smb_fname pathname to file or directory
+ * @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
+ *
+ * @return talloced struct adouble or NULL on error
+ **/
+struct adouble *ad_get(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ adouble_type_t type)
+{
+ return ad_get_internal(ctx, handle, NULL, smb_fname, type);
+}
+
+/**
+ * Return AppleDouble data for a file
+ *
+ * @param[in] ctx talloc context
+ * @param[in] handle vfs handle
+ * @param[in] fsp fsp to use for IO
+ * @param[in] type type of AppleDouble, ADOUBLE_META or ADOUBLE_RSRC
+ *
+ * @return talloced struct adouble or NULL on error
+ **/
+struct adouble *ad_fget(TALLOC_CTX *ctx, vfs_handle_struct *handle,
+ files_struct *fsp, adouble_type_t type)
+{
+ return ad_get_internal(ctx, handle, fsp, NULL, type);
+}
+
+/**
+ * Set AppleDouble metadata on a file or directory
+ *
+ * @param[in] ad adouble handle
+ * @param[in] fsp file handle
+ *
+ * @return status code, 0 means success
+ **/
+int ad_fset(struct vfs_handle_struct *handle,
+ struct adouble *ad,
+ files_struct *fsp)
+{
+ int rc = -1;
+ ssize_t len;
+ bool ok;
+
+ DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp));
+
+ ok = ad_pack(handle, ad, fsp);
+ if (!ok) {
+ return -1;
+ }
+
+ switch (ad->ad_type) {
+ case ADOUBLE_META:
+ rc = SMB_VFS_NEXT_FSETXATTR(handle,
+ fsp->base_fsp ? fsp->base_fsp : fsp,
+ AFPINFO_EA_NETATALK,
+ ad->ad_data,
+ AD_DATASZ_XATTR, 0);
+ break;
+ case ADOUBLE_RSRC:
+ len = SMB_VFS_NEXT_PWRITE(handle,
+ fsp,
+ ad->ad_data,
+ ad_getentryoff(ad, ADEID_RFORK),
+ 0);
+ if (len != ad_getentryoff(ad, ADEID_RFORK)) {
+ DBG_ERR("short write on %s: %zd\n", fsp_str_dbg(fsp), len);
+ return -1;
+ }
+ rc = 0;
+ break;
+
+ default:
+ return -1;
+ }
+
+ DBG_DEBUG("Path [%s] rc [%d]\n", fsp_str_dbg(fsp), rc);
+
+ return rc;
+}
+
+bool is_adouble_file(const char *path)
+{
+ const char *p = NULL;
+ int match;
+
+ p = strrchr(path, '/');
+ if (p == NULL) {
+ p = path;
+ } else {
+ p++;
+ }
+
+ match = strncmp(p,
+ ADOUBLE_NAME_PREFIX,
+ strlen(ADOUBLE_NAME_PREFIX));
+ if (match != 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Prepend "._" to a basename
+ * Return a new struct smb_filename with stream_name == NULL.
+ **/
+int adouble_path(TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **pp_smb_fname_out)
+{
+ char *parent;
+ const char *base;
+ struct smb_filename *smb_fname = NULL;
+
+ smb_fname = cp_smb_filename_nostream(ctx, smb_fname_in);
+ if (smb_fname == NULL) {
+ return -1;
+ }
+
+ /* We're replacing base_name. */
+ TALLOC_FREE(smb_fname->base_name);
+
+ SET_STAT_INVALID(smb_fname->st);
+
+ if (!parent_dirname(smb_fname, smb_fname_in->base_name,
+ &parent, &base)) {
+ TALLOC_FREE(smb_fname);
+ return -1;
+ }
+
+ if (ISDOT(parent)) {
+ smb_fname->base_name = talloc_asprintf(smb_fname,
+ "._%s", base);
+ } else {
+ smb_fname->base_name = talloc_asprintf(smb_fname,
+ "%s/._%s", parent, base);
+ }
+ if (smb_fname->base_name == NULL) {
+ TALLOC_FREE(smb_fname);
+ return -1;
+ }
+
+ *pp_smb_fname_out = smb_fname;
+
+ return 0;
+}
+
+/**
+ * Allocate and initialize an AfpInfo struct
+ **/
+AfpInfo *afpinfo_new(TALLOC_CTX *ctx)
+{
+ AfpInfo *ai = talloc_zero(ctx, AfpInfo);
+ if (ai == NULL) {
+ return NULL;
+ }
+ ai->afpi_Signature = AFP_Signature;
+ ai->afpi_Version = AFP_Version;
+ ai->afpi_BackupTime = AD_DATE_START;
+ return ai;
+}
+
+/**
+ * Pack an AfpInfo struct into a buffer
+ *
+ * Buffer size must be at least AFP_INFO_SIZE
+ * Returns size of packed buffer
+ **/
+ssize_t afpinfo_pack(const AfpInfo *ai, char *buf)
+{
+ memset(buf, 0, AFP_INFO_SIZE);
+
+ RSIVAL(buf, 0, ai->afpi_Signature);
+ RSIVAL(buf, 4, ai->afpi_Version);
+ RSIVAL(buf, 12, ai->afpi_BackupTime);
+ memcpy(buf + 16, ai->afpi_FinderInfo, sizeof(ai->afpi_FinderInfo));
+
+ return AFP_INFO_SIZE;
+}
+
+/**
+ * Unpack a buffer into a AfpInfo structure
+ *
+ * Buffer size must be at least AFP_INFO_SIZE
+ * Returns allocated AfpInfo struct
+ **/
+AfpInfo *afpinfo_unpack(TALLOC_CTX *ctx, const void *data, bool validate)
+{
+ AfpInfo *ai = talloc_zero(ctx, AfpInfo);
+ if (ai == NULL) {
+ return NULL;
+ }
+
+ ai->afpi_Signature = RIVAL(data, 0);
+ ai->afpi_Version = RIVAL(data, 4);
+ ai->afpi_BackupTime = RIVAL(data, 12);
+ memcpy(ai->afpi_FinderInfo, (const char *)data + 16,
+ sizeof(ai->afpi_FinderInfo));
+
+ if (validate) {
+ if (ai->afpi_Signature != AFP_Signature
+ || ai->afpi_Version != AFP_Version)
+ {
+ DEBUG(1, ("Bad AfpInfo signature or version\n"));
+ TALLOC_FREE(ai);
+ }
+ } else {
+ ai->afpi_Signature = AFP_Signature;
+ ai->afpi_Version = AFP_Version;
+ }
+
+ return ai;
+}
diff --git a/source3/lib/adouble.h b/source3/lib/adouble.h
new file mode 100644
index 0000000..d2c7293
--- /dev/null
+++ b/source3/lib/adouble.h
@@ -0,0 +1,192 @@
+/*
+ * Samba AppleDouble helpers
+ *
+ * Copyright (C) Ralph Boehme, 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _ADOUBLE_H_
+#define _ADOUBLE_H_
+
+#include "MacExtensions.h"
+
+#define ADOUBLE_NAME_PREFIX "._"
+
+#define NETATALK_META_XATTR "org.netatalk.Metadata"
+#define NETATALK_RSRC_XATTR "org.netatalk.ResourceFork"
+
+#if defined(HAVE_ATTROPEN)
+#define AFPINFO_EA_NETATALK NETATALK_META_XATTR
+#define AFPRESOURCE_EA_NETATALK NETATALK_RSRC_XATTR
+#else
+#define AFPINFO_EA_NETATALK "user." NETATALK_META_XATTR
+#define AFPRESOURCE_EA_NETATALK "user." NETATALK_RSRC_XATTR
+#endif
+
+/*
+ * There are two AppleDouble blobs we deal with:
+ *
+ * - ADOUBLE_META - AppleDouble blob used by Netatalk for storing
+ * metadata in an xattr
+ *
+ * - ADOUBLE_RSRC - AppleDouble blob used by OS X and Netatalk in
+ * ._ files
+ */
+typedef enum {ADOUBLE_META, ADOUBLE_RSRC} adouble_type_t;
+
+/* Version info */
+#define AD_VERSION2 0x00020000
+#define AD_VERSION AD_VERSION2
+
+/*
+ * AppleDouble entry IDs.
+ */
+#define ADEID_DFORK 1
+#define ADEID_RFORK 2
+#define ADEID_NAME 3
+#define ADEID_COMMENT 4
+#define ADEID_ICONBW 5
+#define ADEID_ICONCOL 6
+#define ADEID_FILEI 7
+#define ADEID_FILEDATESI 8
+#define ADEID_FINDERI 9
+#define ADEID_MACFILEI 10
+#define ADEID_PRODOSFILEI 11
+#define ADEID_MSDOSFILEI 12
+#define ADEID_SHORTNAME 13
+#define ADEID_AFPFILEI 14
+#define ADEID_DID 15
+
+/* Private Netatalk entries */
+#define ADEID_PRIVDEV 16
+#define ADEID_PRIVINO 17
+#define ADEID_PRIVSYN 18
+#define ADEID_PRIVID 19
+#define ADEID_MAX (ADEID_PRIVID + 1)
+
+/*
+ * These are the real ids for the private entries,
+ * as stored in the adouble file
+ */
+#define AD_DEV 0x80444556
+#define AD_INO 0x80494E4F
+#define AD_SYN 0x8053594E
+#define AD_ID 0x8053567E
+
+/* AppleDouble magic */
+#define AD_APPLESINGLE_MAGIC 0x00051600
+#define AD_APPLEDOUBLE_MAGIC 0x00051607
+#define AD_MAGIC AD_APPLEDOUBLE_MAGIC
+
+/* Field widths */
+#define ADEDLEN_NAME 255
+#define ADEDLEN_COMMENT 200
+#define ADEDLEN_FILEI 16
+#define ADEDLEN_FINDERI 32
+#define ADEDLEN_FILEDATESI 16
+#define ADEDLEN_SHORTNAME 12 /* length up to 8.3 */
+#define ADEDLEN_AFPFILEI 4
+#define ADEDLEN_MACFILEI 4
+#define ADEDLEN_PRODOSFILEI 8
+#define ADEDLEN_MSDOSFILEI 2
+#define ADEDLEN_ICONBW 128
+#define ADEDLEN_ICONCOL 1024
+#define ADEDLEN_DID 4
+#define ADEDLEN_PRIVDEV 8
+#define ADEDLEN_PRIVINO 8
+#define ADEDLEN_PRIVSYN 8
+#define ADEDLEN_PRIVID 4
+
+/*
+ * Sharemode locks fcntl() offsets
+ */
+#if _FILE_OFFSET_BITS == 64 || defined(HAVE_LARGEFILE)
+#define AD_FILELOCK_BASE (UINT64_C(0x7FFFFFFFFFFFFFFF) - 9)
+#else
+#define AD_FILELOCK_BASE (UINT32_C(0x7FFFFFFF) - 9)
+#endif
+#define BYTELOCK_MAX (AD_FILELOCK_BASE - 1)
+
+#define AD_FILELOCK_OPEN_WR (AD_FILELOCK_BASE + 0)
+#define AD_FILELOCK_OPEN_RD (AD_FILELOCK_BASE + 1)
+#define AD_FILELOCK_RSRC_OPEN_WR (AD_FILELOCK_BASE + 2)
+#define AD_FILELOCK_RSRC_OPEN_RD (AD_FILELOCK_BASE + 3)
+#define AD_FILELOCK_DENY_WR (AD_FILELOCK_BASE + 4)
+#define AD_FILELOCK_DENY_RD (AD_FILELOCK_BASE + 5)
+#define AD_FILELOCK_RSRC_DENY_WR (AD_FILELOCK_BASE + 6)
+#define AD_FILELOCK_RSRC_DENY_RD (AD_FILELOCK_BASE + 7)
+#define AD_FILELOCK_OPEN_NONE (AD_FILELOCK_BASE + 8)
+#define AD_FILELOCK_RSRC_OPEN_NONE (AD_FILELOCK_BASE + 9)
+
+/* Time stuff we overload the bits a little */
+#define AD_DATE_CREATE 0
+#define AD_DATE_MODIFY 4
+#define AD_DATE_BACKUP 8
+#define AD_DATE_ACCESS 12
+#define AD_DATE_MASK (AD_DATE_CREATE | AD_DATE_MODIFY | \
+ AD_DATE_BACKUP | AD_DATE_ACCESS)
+#define AD_DATE_UNIX (1 << 10)
+#define AD_DATE_START 0x80000000
+#define AD_DATE_DELTA 946684800
+#define AD_DATE_FROM_UNIX(x) (htonl((x) - AD_DATE_DELTA))
+#define AD_DATE_TO_UNIX(x) (ntohl(x) + AD_DATE_DELTA)
+
+#define AD_CONV_WIPE_BLANK (1<<0)
+#define AD_CONV_DELETE (1<<1)
+
+struct adouble;
+
+size_t ad_getentrylen(const struct adouble *ad, int eid);
+size_t ad_getentryoff(const struct adouble *ad, int eid);
+size_t ad_setentrylen(struct adouble *ad, int eid, size_t len);
+size_t ad_setentryoff(struct adouble *ad, int eid, size_t off);
+char *ad_get_entry(const struct adouble *ad, int eid);
+int ad_getdate(const struct adouble *ad, unsigned int dateoff, uint32_t *date);
+int ad_setdate(struct adouble *ad, unsigned int dateoff, uint32_t date);
+int ad_convert(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ const char *catia_mappings,
+ uint32_t flags);
+bool ad_unconvert(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const char *catia_mappings,
+ struct smb_filename *smb_fname,
+ bool *converted);
+struct adouble *ad_init(TALLOC_CTX *ctx, adouble_type_t type);
+NTSTATUS adouble_open_from_base_fsp(const struct files_struct *dirfsp,
+ struct files_struct *base_fsp,
+ adouble_type_t type,
+ int flags,
+ mode_t mode,
+ struct files_struct **_ad_fsp);
+struct adouble *ad_get(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ adouble_type_t type);
+struct adouble *ad_fget(TALLOC_CTX *ctx, vfs_handle_struct *handle,
+ files_struct *fsp, adouble_type_t type);
+int ad_fset(struct vfs_handle_struct *handle,
+ struct adouble *ad,
+ files_struct *fsp);
+bool is_adouble_file(const char *path);
+int adouble_path(TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **pp_smb_fname_out);
+
+AfpInfo *afpinfo_new(TALLOC_CTX *ctx);
+ssize_t afpinfo_pack(const AfpInfo *ai, char *buf);
+AfpInfo *afpinfo_unpack(TALLOC_CTX *ctx, const void *data, bool validate);
+
+#endif
diff --git a/source3/lib/adt_tree.c b/source3/lib/adt_tree.c
new file mode 100644
index 0000000..5c2b266
--- /dev/null
+++ b/source3/lib/adt_tree.c
@@ -0,0 +1,448 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Generic Abstract Data Types
+ * Copyright (C) Gerald Carter 2002.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <talloc.h>
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+#include "util/charset/charset.h"
+#include "source3/include/smb_macros.h"
+#include "adt_tree.h"
+
+struct tree_node {
+ struct tree_node *parent;
+ struct tree_node **children;
+ int num_children;
+ char *key;
+ void *data_p;
+};
+
+struct sorted_tree {
+ struct tree_node *root;
+};
+
+/**************************************************************************
+ *************************************************************************/
+
+static bool trim_tree_keypath( char *path, char **base, char **new_path )
+{
+ char *p;
+
+ *new_path = *base = NULL;
+
+ if ( !path )
+ return false;
+
+ *base = path;
+
+ p = strchr( path, '\\' );
+
+ if ( p ) {
+ *p = '\0';
+ *new_path = p+1;
+ }
+
+ return true;
+}
+
+/**************************************************************************
+ Initialize the tree's root.
+ *************************************************************************/
+
+struct sorted_tree *pathtree_init(void *data_p)
+{
+ struct sorted_tree *tree = NULL;
+
+ tree = talloc_zero(NULL, struct sorted_tree);
+ if (tree == NULL) {
+ return NULL;
+ }
+
+ tree->root = talloc_zero(tree, struct tree_node);
+ if (tree->root == NULL) {
+ TALLOC_FREE( tree );
+ return NULL;
+ }
+
+ tree->root->data_p = data_p;
+
+ return tree;
+}
+
+
+/**************************************************************************
+ Find the next child given a key string
+ *************************************************************************/
+
+static struct tree_node *pathtree_birth_child(struct tree_node *node,
+ char* key )
+{
+ struct tree_node *infant = NULL;
+ struct tree_node **siblings;
+ int i;
+
+ infant = talloc_zero(node, struct tree_node);
+ if (infant == NULL) {
+ return NULL;
+ }
+
+ infant->key = talloc_strdup( infant, key );
+ infant->parent = node;
+
+ siblings = talloc_realloc(node, node->children, struct tree_node *,
+ node->num_children+1);
+
+ if ( siblings )
+ node->children = siblings;
+
+ node->num_children++;
+
+ /* first child */
+
+ if ( node->num_children == 1 ) {
+ DEBUG(11,("pathtree_birth_child: First child of node [%s]! [%s]\n",
+ node->key ? node->key : "NULL", infant->key ));
+ node->children[0] = infant;
+ }
+ else
+ {
+ /*
+ * multiple siblings .... (at least 2 children)
+ *
+ * work from the end of the list forward
+ * The last child is not set at this point
+ * Insert the new infanct in ascending order
+ * from left to right
+ */
+
+ for ( i = node->num_children-1; i>=1; i-- )
+ {
+ DEBUG(11,("pathtree_birth_child: Looking for crib; infant -> [%s], child -> [%s]\n",
+ infant->key, node->children[i-1]->key));
+
+ /* the strings should never match assuming that we
+ have called pathtree_find_child() first */
+
+ if ( strcasecmp_m( infant->key, node->children[i-1]->key ) > 0 ) {
+ DEBUG(11,("pathtree_birth_child: storing infant in i == [%d]\n",
+ i));
+ node->children[i] = infant;
+ break;
+ }
+
+ /* bump everything towards the end on slot */
+
+ node->children[i] = node->children[i-1];
+ }
+
+ DEBUG(11,("pathtree_birth_child: Exiting loop (i == [%d])\n", i ));
+
+ /* if we haven't found the correct slot yet, the child
+ will be first in the list */
+
+ if ( i == 0 )
+ node->children[0] = infant;
+ }
+
+ return infant;
+}
+
+/**************************************************************************
+ Find the next child given a key string
+ *************************************************************************/
+
+static struct tree_node *pathtree_find_child(struct tree_node *node,
+ char *key )
+{
+ struct tree_node *next = NULL;
+ int i, result;
+
+ if ( !node ) {
+ DEBUG(0,("pathtree_find_child: NULL node passed into function!\n"));
+ return NULL;
+ }
+
+ if ( !key ) {
+ DEBUG(0,("pathtree_find_child: NULL key string passed into function!\n"));
+ return NULL;
+ }
+
+ for ( i=0; i<node->num_children; i++ )
+ {
+ DEBUG(11,("pathtree_find_child: child key => [%s]\n",
+ node->children[i]->key));
+
+ result = strcasecmp_m( node->children[i]->key, key );
+
+ if ( result == 0 )
+ next = node->children[i];
+
+ /* if result > 0 then we've gone to far because
+ the list of children is sorted by key name
+ If result == 0, then we have a match */
+
+ if ( result > 0 )
+ break;
+ }
+
+ DEBUG(11,("pathtree_find_child: %s [%s]\n",
+ next ? "Found" : "Did not find", key ));
+
+ return next;
+}
+
+/**************************************************************************
+ Add a new node into the tree given a key path and a blob of data
+ *************************************************************************/
+
+bool pathtree_add(struct sorted_tree *tree, const char *path, void *data_p)
+{
+ char *str, *base, *path2;
+ struct tree_node *current, *next;
+ bool ret = true;
+
+ DEBUG(8,("pathtree_add: Enter\n"));
+
+ if ( !path || *path != '\\' ) {
+ DEBUG(0,("pathtree_add: Attempt to add a node with a bad path [%s]\n",
+ path ? path : "NULL" ));
+ return false;
+ }
+
+ if ( !tree ) {
+ DEBUG(0,("pathtree_add: Attempt to add a node to an uninitialized tree!\n"));
+ return false;
+ }
+
+ /* move past the first '\\' */
+
+ path++;
+ path2 = SMB_STRDUP( path );
+ if ( !path2 ) {
+ DEBUG(0,("pathtree_add: strdup() failed on string [%s]!?!?!\n", path));
+ return false;
+ }
+
+
+ /*
+ * this works sort of like a 'mkdir -p' call, possibly
+ * creating an entire path to the new node at once
+ * The path should be of the form /<key1>/<key2>/...
+ */
+
+ base = path2;
+ str = path2;
+ current = tree->root;
+
+ do {
+ /* break off the remaining part of the path */
+
+ str = strchr( str, '\\' );
+ if ( str )
+ *str = '\0';
+
+ /* iterate to the next child--birth it if necessary */
+
+ next = pathtree_find_child( current, base );
+ if ( !next ) {
+ next = pathtree_birth_child( current, base );
+ if ( !next ) {
+ DEBUG(0,("pathtree_add: Failed to create new child!\n"));
+ ret = false;
+ goto done;
+ }
+ }
+ current = next;
+
+ /* setup the next part of the path */
+
+ base = str;
+ if ( base ) {
+ *base = '\\';
+ base++;
+ str = base;
+ }
+
+ } while ( base != NULL );
+
+ current->data_p = data_p;
+
+ DEBUG(10,("pathtree_add: Successfully added node [%s] to tree\n",
+ path ));
+
+ DEBUG(8,("pathtree_add: Exit\n"));
+
+done:
+ SAFE_FREE( path2 );
+ return ret;
+}
+
+
+/**************************************************************************
+ Recursive routine to print out all children of a struct tree_node
+ *************************************************************************/
+
+static void pathtree_print_children(TALLOC_CTX *ctx,
+ struct tree_node *node,
+ int debug,
+ const char *path )
+{
+ int i;
+ int num_children;
+ char *path2 = NULL;
+
+ if ( !node )
+ return;
+
+ if ( node->key )
+ DEBUG(debug,("%s: [%s] (%s)\n", path ? path : "NULL", node->key,
+ node->data_p ? "data" : "NULL" ));
+
+ if ( path ) {
+ path2 = talloc_strdup(ctx, path);
+ if (!path2) {
+ return;
+ }
+ }
+
+ path2 = talloc_asprintf(ctx,
+ "%s%s/",
+ path ? path : "",
+ node->key ? node->key : "NULL");
+ if (!path2) {
+ return;
+ }
+
+ num_children = node->num_children;
+ for ( i=0; i<num_children; i++ ) {
+ pathtree_print_children(ctx, node->children[i], debug, path2 );
+ }
+}
+
+/**************************************************************************
+ Dump the kys for a tree to the log file
+ *************************************************************************/
+
+void pathtree_print_keys(struct sorted_tree *tree, int debug )
+{
+ int i;
+ int num_children = tree->root->num_children;
+
+ if ( tree->root->key )
+ DEBUG(debug,("ROOT/: [%s] (%s)\n", tree->root->key,
+ tree->root->data_p ? "data" : "NULL" ));
+
+ for ( i=0; i<num_children; i++ ) {
+ TALLOC_CTX *ctx = talloc_stackframe();
+ pathtree_print_children(ctx, tree->root->children[i], debug,
+ tree->root->key ? tree->root->key : "ROOT/" );
+ TALLOC_FREE(ctx);
+ }
+
+}
+
+/**************************************************************************
+ return the data_p for for the node in tree matching the key string
+ The key string is the full path. We must break it apart and walk
+ the tree
+ *************************************************************************/
+
+void* pathtree_find(struct sorted_tree *tree, char *key )
+{
+ char *keystr, *base = NULL, *str = NULL, *p;
+ struct tree_node *current;
+ void *result = NULL;
+
+ DEBUG(10,("pathtree_find: Enter [%s]\n", key ? key : "NULL" ));
+
+ /* sanity checks first */
+
+ if ( !key ) {
+ DEBUG(0,("pathtree_find: Attempt to search tree using NULL search string!\n"));
+ return NULL;
+ }
+
+ if ( !tree ) {
+ DEBUG(0,("pathtree_find: Attempt to search an uninitialized tree using string [%s]!\n",
+ key ? key : "NULL" ));
+ return NULL;
+ }
+
+ if ( !tree->root )
+ return NULL;
+
+ /* make a copy to play with */
+
+ if ( *key == '\\' )
+ keystr = SMB_STRDUP( key+1 );
+ else
+ keystr = SMB_STRDUP( key );
+
+ if ( !keystr ) {
+ DEBUG(0,("pathtree_find: strdup() failed on string [%s]!?!?!\n", key));
+ return NULL;
+ }
+
+ /* start breaking the path apart */
+
+ p = keystr;
+ current = tree->root;
+
+ if ( tree->root->data_p )
+ result = tree->root->data_p;
+
+ do
+ {
+ /* break off the remaining part of the path */
+
+ trim_tree_keypath( p, &base, &str );
+
+ DEBUG(11,("pathtree_find: [loop] base => [%s], new_path => [%s]\n",
+ base ? base : "",
+ str ? str : ""));
+
+ /* iterate to the next child */
+
+ current = pathtree_find_child( current, base );
+
+ /*
+ * the idea is that the data_p for a parent should
+ * be inherited by all children, but allow it to be
+ * overridden farther down
+ */
+
+ if ( current && current->data_p )
+ result = current->data_p;
+
+ /* reset the path pointer 'p' to the remaining part of the key string */
+
+ p = str;
+
+ } while ( str && current );
+
+ /* result should be the data_p from the lowest match node in the tree */
+ if ( result )
+ DEBUG(11,("pathtree_find: Found data_p!\n"));
+
+ SAFE_FREE( keystr );
+
+ DEBUG(10,("pathtree_find: Exit\n"));
+
+ return result;
+}
diff --git a/source3/lib/audit.c b/source3/lib/audit.c
new file mode 100644
index 0000000..f0dc5c2
--- /dev/null
+++ b/source3/lib/audit.c
@@ -0,0 +1,149 @@
+/*
+ Unix SMB/CIFS implementation.
+ Auditing helper functions.
+ Copyright (C) Guenther Deschner 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/lsa.h"
+
+static const struct audit_category_tab {
+ uint32_t category;
+ const char *category_str;
+ const char *param_str;
+ const char *description;
+} audit_category_tab [] = {
+ { LSA_AUDIT_CATEGORY_LOGON,
+ "LSA_AUDIT_CATEGORY_LOGON",
+ "LOGON", "Logon events" },
+ { LSA_AUDIT_CATEGORY_USE_OF_USER_RIGHTS,
+ "LSA_AUDIT_CATEGORY_USE_OF_USER_RIGHTS",
+ "PRIVILEGE", "Privilege Use" },
+ { LSA_AUDIT_CATEGORY_SYSTEM,
+ "LSA_AUDIT_CATEGORY_SYSTEM",
+ "SYSTEM", "System Events" },
+ { LSA_AUDIT_CATEGORY_SECURITY_POLICY_CHANGES,
+ "LSA_AUDIT_CATEGORY_SECURITY_POLICY_CHANGES",
+ "POLICY", "Policy Change" },
+ { LSA_AUDIT_CATEGORY_PROCCESS_TRACKING,
+ "LSA_AUDIT_CATEGORY_PROCCESS_TRACKING",
+ "PROCESS", "Process Tracking" },
+ { LSA_AUDIT_CATEGORY_FILE_AND_OBJECT_ACCESS,
+ "LSA_AUDIT_CATEGORY_FILE_AND_OBJECT_ACCESS",
+ "OBJECT", "Object Access" },
+ { LSA_AUDIT_CATEGORY_ACCOUNT_MANAGEMENT,
+ "LSA_AUDIT_CATEGORY_ACCOUNT_MANAGEMENT",
+ "SAM", "Account Management" },
+ { LSA_AUDIT_CATEGORY_DIRECTORY_SERVICE_ACCESS,
+ "LSA_AUDIT_CATEGORY_DIRECTORY_SERVICE_ACCESS",
+ "DIRECTORY", "Directory service access" },
+ { LSA_AUDIT_CATEGORY_ACCOUNT_LOGON,
+ "LSA_AUDIT_CATEGORY_ACCOUNT_LOGON",
+ "ACCOUNT", "Account logon events" },
+ { .category = 0 }
+};
+
+const char *audit_category_str(uint32_t category)
+{
+ int i;
+ for (i=0; audit_category_tab[i].category_str; i++) {
+ if (category == audit_category_tab[i].category) {
+ return audit_category_tab[i].category_str;
+ }
+ }
+ return NULL;
+}
+
+const char *audit_param_str(uint32_t category)
+{
+ int i;
+ for (i=0; audit_category_tab[i].param_str; i++) {
+ if (category == audit_category_tab[i].category) {
+ return audit_category_tab[i].param_str;
+ }
+ }
+ return NULL;
+}
+
+const char *audit_description_str(uint32_t category)
+{
+ int i;
+ for (i=0; audit_category_tab[i].description; i++) {
+ if (category == audit_category_tab[i].category) {
+ return audit_category_tab[i].description;
+ }
+ }
+ return NULL;
+}
+
+bool get_audit_category_from_param(const char *param, uint32_t *audit_category)
+{
+ *audit_category = Undefined;
+
+ if (strequal(param, "SYSTEM")) {
+ *audit_category = LSA_AUDIT_CATEGORY_SYSTEM;
+ } else if (strequal(param, "LOGON")) {
+ *audit_category = LSA_AUDIT_CATEGORY_LOGON;
+ } else if (strequal(param, "OBJECT")) {
+ *audit_category = LSA_AUDIT_CATEGORY_FILE_AND_OBJECT_ACCESS;
+ } else if (strequal(param, "PRIVILEGE")) {
+ *audit_category = LSA_AUDIT_CATEGORY_USE_OF_USER_RIGHTS;
+ } else if (strequal(param, "PROCESS")) {
+ *audit_category = LSA_AUDIT_CATEGORY_PROCCESS_TRACKING;
+ } else if (strequal(param, "POLICY")) {
+ *audit_category = LSA_AUDIT_CATEGORY_SECURITY_POLICY_CHANGES;
+ } else if (strequal(param, "SAM")) {
+ *audit_category = LSA_AUDIT_CATEGORY_ACCOUNT_MANAGEMENT;
+ } else if (strequal(param, "DIRECTORY")) {
+ *audit_category = LSA_AUDIT_CATEGORY_DIRECTORY_SERVICE_ACCESS;
+ } else if (strequal(param, "ACCOUNT")) {
+ *audit_category = LSA_AUDIT_CATEGORY_ACCOUNT_LOGON;
+ } else {
+ DEBUG(0,("unknown parameter: %s\n", param));
+ return False;
+ }
+
+ return True;
+}
+
+const char *audit_policy_str(TALLOC_CTX *mem_ctx, uint32_t policy)
+{
+ const char *ret = NULL;
+
+ if (policy == LSA_AUDIT_POLICY_NONE) {
+ return talloc_strdup(mem_ctx, "None");
+ }
+
+ if (policy & LSA_AUDIT_POLICY_SUCCESS) {
+ ret = talloc_strdup(mem_ctx, "Success");
+ if (ret == NULL) {
+ return NULL;
+ }
+ }
+
+ if (policy & LSA_AUDIT_POLICY_FAILURE) {
+ if (ret) {
+ ret = talloc_asprintf(mem_ctx, "%s, %s", ret, "Failure");
+ if (ret == NULL) {
+ return NULL;
+ }
+ } else {
+ return talloc_strdup(mem_ctx, "Failure");
+ }
+ }
+
+ return ret;
+}
diff --git a/source3/lib/avahi.c b/source3/lib/avahi.c
new file mode 100644
index 0000000..269b329
--- /dev/null
+++ b/source3/lib/avahi.c
@@ -0,0 +1,275 @@
+/*
+ Unix SMB/CIFS implementation.
+ Connect avahi to lib/tevents
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+#include <avahi-common/watch.h>
+
+struct avahi_poll_context {
+ struct tevent_context *ev;
+ AvahiWatch **watches;
+ AvahiTimeout **timeouts;
+};
+
+struct AvahiWatch {
+ struct avahi_poll_context *ctx;
+ struct tevent_fd *fde;
+ int fd;
+ AvahiWatchEvent latest_event;
+ AvahiWatchCallback callback;
+ void *userdata;
+};
+
+struct AvahiTimeout {
+ struct avahi_poll_context *ctx;
+ struct tevent_timer *te;
+ AvahiTimeoutCallback callback;
+ void *userdata;
+};
+
+static uint16_t avahi_flags_map_to_tevent(AvahiWatchEvent event)
+{
+ return ((event & AVAHI_WATCH_IN) ? TEVENT_FD_READ : 0)
+ | ((event & AVAHI_WATCH_OUT) ? TEVENT_FD_WRITE : 0);
+}
+
+static void avahi_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde, uint16_t flags,
+ void *private_data);
+
+static AvahiWatch *avahi_watch_new(const AvahiPoll *api, int fd,
+ AvahiWatchEvent event,
+ AvahiWatchCallback callback,
+ void *userdata)
+{
+ struct avahi_poll_context *ctx = talloc_get_type_abort(
+ api->userdata, struct avahi_poll_context);
+ int num_watches = talloc_array_length(ctx->watches);
+ AvahiWatch **tmp, *watch_ctx;
+
+ tmp = talloc_realloc(ctx, ctx->watches, AvahiWatch *, num_watches + 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ ctx->watches = tmp;
+
+ watch_ctx = talloc(tmp, AvahiWatch);
+ if (watch_ctx == NULL) {
+ goto fail;
+ }
+ ctx->watches[num_watches] = watch_ctx;
+
+ watch_ctx->ctx = ctx;
+ watch_ctx->fde = tevent_add_fd(ctx->ev, watch_ctx, fd,
+ avahi_flags_map_to_tevent(event),
+ avahi_fd_handler, watch_ctx);
+ if (watch_ctx->fde == NULL) {
+ goto fail;
+ }
+ watch_ctx->callback = callback;
+ watch_ctx->userdata = userdata;
+ return watch_ctx;
+
+ fail:
+ TALLOC_FREE(watch_ctx);
+ ctx->watches = talloc_realloc(ctx, ctx->watches, AvahiWatch *,
+ num_watches);
+ return NULL;
+}
+
+static void avahi_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde, uint16_t flags,
+ void *private_data)
+{
+ AvahiWatch *watch_ctx = talloc_get_type_abort(private_data, AvahiWatch);
+
+ watch_ctx->latest_event =
+ ((flags & TEVENT_FD_READ) ? AVAHI_WATCH_IN : 0)
+ | ((flags & TEVENT_FD_WRITE) ? AVAHI_WATCH_OUT : 0);
+
+ watch_ctx->callback(watch_ctx, watch_ctx->fd, watch_ctx->latest_event,
+ watch_ctx->userdata);
+}
+
+static void avahi_watch_update(AvahiWatch *w, AvahiWatchEvent event)
+{
+ tevent_fd_set_flags(w->fde, avahi_flags_map_to_tevent(event));
+}
+
+static AvahiWatchEvent avahi_watch_get_events(AvahiWatch *w)
+{
+ return w->latest_event;
+}
+
+static void avahi_watch_free(AvahiWatch *w)
+{
+ int i, num_watches;
+ AvahiWatch **watches = w->ctx->watches;
+ struct avahi_poll_context *ctx;
+
+ num_watches = talloc_array_length(watches);
+
+ for (i=0; i<num_watches; i++) {
+ if (w == watches[i]) {
+ break;
+ }
+ }
+ if (i == num_watches) {
+ return;
+ }
+ ctx = w->ctx;
+ TALLOC_FREE(w);
+ memmove(&watches[i], &watches[i+1],
+ sizeof(*watches) * (num_watches - i - 1));
+ ctx->watches = talloc_realloc(ctx, watches, AvahiWatch *,
+ num_watches - 1);
+}
+
+static void avahi_timeout_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data);
+
+static AvahiTimeout *avahi_timeout_new(const AvahiPoll *api,
+ const struct timeval *tv,
+ AvahiTimeoutCallback callback,
+ void *userdata)
+{
+ struct avahi_poll_context *ctx = talloc_get_type_abort(
+ api->userdata, struct avahi_poll_context);
+ int num_timeouts = talloc_array_length(ctx->timeouts);
+ AvahiTimeout **tmp, *timeout_ctx;
+
+ tmp = talloc_realloc(ctx, ctx->timeouts, AvahiTimeout *,
+ num_timeouts + 1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ ctx->timeouts = tmp;
+
+ timeout_ctx = talloc(tmp, AvahiTimeout);
+ if (timeout_ctx == NULL) {
+ goto fail;
+ }
+ ctx->timeouts[num_timeouts] = timeout_ctx;
+
+ timeout_ctx->ctx = ctx;
+ if (tv == NULL) {
+ timeout_ctx->te = NULL;
+ } else {
+ timeout_ctx->te = tevent_add_timer(ctx->ev, timeout_ctx,
+ *tv, avahi_timeout_handler,
+ timeout_ctx);
+ if (timeout_ctx->te == NULL) {
+ goto fail;
+ }
+ }
+ timeout_ctx->callback = callback;
+ timeout_ctx->userdata = userdata;
+ return timeout_ctx;
+
+ fail:
+ TALLOC_FREE(timeout_ctx);
+ ctx->timeouts = talloc_realloc(ctx, ctx->timeouts, AvahiTimeout *,
+ num_timeouts);
+ return NULL;
+}
+
+static void avahi_timeout_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ AvahiTimeout *timeout_ctx = talloc_get_type_abort(
+ private_data, AvahiTimeout);
+
+ TALLOC_FREE(timeout_ctx->te);
+ timeout_ctx->callback(timeout_ctx, timeout_ctx->userdata);
+}
+
+static void avahi_timeout_update(AvahiTimeout *t, const struct timeval *tv)
+{
+ TALLOC_FREE(t->te);
+
+ if (tv == NULL) {
+ /*
+ * Disable this timer
+ */
+ return;
+ }
+
+ t->te = tevent_add_timer(t->ctx->ev, t, *tv, avahi_timeout_handler, t);
+ /*
+ * No failure mode defined here
+ */
+ SMB_ASSERT(t->te != NULL);
+}
+
+static void avahi_timeout_free(AvahiTimeout *t)
+{
+ int i, num_timeouts;
+ AvahiTimeout **timeouts = t->ctx->timeouts;
+ struct avahi_poll_context *ctx;
+
+ num_timeouts = talloc_array_length(timeouts);
+
+ for (i=0; i<num_timeouts; i++) {
+ if (t == timeouts[i]) {
+ break;
+ }
+ }
+ if (i == num_timeouts) {
+ return;
+ }
+ ctx = t->ctx;
+ TALLOC_FREE(t);
+ memmove(&timeouts[i], &timeouts[i+1],
+ sizeof(*timeouts) * (num_timeouts - i - 1));
+ ctx->timeouts = talloc_realloc(ctx, timeouts, AvahiTimeout *,
+ num_timeouts - 1);
+}
+
+struct AvahiPoll *tevent_avahi_poll(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct AvahiPoll *result;
+ struct avahi_poll_context *ctx;
+
+ result = talloc(mem_ctx, struct AvahiPoll);
+ if (result == NULL) {
+ return result;
+ }
+ ctx = talloc_zero(result, struct avahi_poll_context);
+ if (ctx == NULL) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ ctx->ev = ev;
+
+ result->watch_new = avahi_watch_new;
+ result->watch_update = avahi_watch_update;
+ result->watch_get_events = avahi_watch_get_events;
+ result->watch_free = avahi_watch_free;
+ result->timeout_new = avahi_timeout_new;
+ result->timeout_update = avahi_timeout_update;
+ result->timeout_free = avahi_timeout_free;
+ result->userdata = ctx;
+
+ return result;
+}
diff --git a/source3/lib/background.c b/source3/lib/background.c
new file mode 100644
index 0000000..cf6cc3a
--- /dev/null
+++ b/source3/lib/background.c
@@ -0,0 +1,256 @@
+/*
+ Unix SMB/CIFS implementation.
+ Regular background jobs as forked helpers
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/async_req/async_sock.h"
+#include "include/messages.h"
+#include "background.h"
+
+struct background_job_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ uint32_t *trigger_msgs;
+ size_t num_trigger_msgs;
+ bool parent_longlived;
+ int (*fn)(void *private_data);
+ void *private_data;
+
+ struct tevent_req *wakeup_req;
+ int pipe_fd;
+ struct tevent_req *pipe_req;
+};
+
+static int background_job_state_destructor(struct background_job_state *s);
+static void background_job_waited(struct tevent_req *subreq);
+static void background_job_done(struct tevent_req *subreq);
+static bool background_job_trigger(
+ struct messaging_rec *rec, void *private_data);
+
+struct tevent_req *background_job_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ uint32_t *trigger_msgs,
+ size_t num_trigger_msgs,
+ time_t initial_wait_sec,
+ int (*fn)(void *private_data),
+ void *private_data)
+{
+ struct tevent_req *req, *subreq;
+ struct background_job_state *state;
+ size_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct background_job_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->msg = msg;
+
+ if (num_trigger_msgs != 0) {
+ state->trigger_msgs = (uint32_t *)talloc_memdup(
+ state, trigger_msgs,
+ sizeof(uint32_t) * num_trigger_msgs);
+ if (tevent_req_nomem(state->trigger_msgs, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->num_trigger_msgs = num_trigger_msgs;
+ }
+
+ state->fn = fn;
+ state->private_data = private_data;
+
+ state->pipe_fd = -1;
+ talloc_set_destructor(state, background_job_state_destructor);
+
+ for (i=0; i<num_trigger_msgs; i++) {
+ subreq = messaging_filtered_read_send(
+ state, ev, msg, background_job_trigger, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = tevent_wakeup_send(
+ state, state->ev, timeval_current_ofs(initial_wait_sec, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, background_job_waited, req);
+ state->wakeup_req = subreq;
+ return req;
+}
+
+static int background_job_state_destructor(struct background_job_state *state)
+{
+ TALLOC_FREE(state->pipe_req);
+ if (state->pipe_fd != -1) {
+ close(state->pipe_fd);
+ state->pipe_fd = -1;
+ }
+
+ return 0;
+}
+
+static bool background_job_trigger(
+ struct messaging_rec *rec, void *private_data)
+{
+ struct background_job_state *state = talloc_get_type_abort(
+ private_data, struct background_job_state);
+ size_t i;
+
+ if (state->wakeup_req == NULL) {
+ return false;
+ }
+ for (i=0; i<state->num_trigger_msgs; i++) {
+ if (rec->msg_type == state->trigger_msgs[i]) {
+ break;
+ }
+ }
+ if (i == state->num_trigger_msgs) {
+ return false;
+ }
+ if (!tevent_req_set_endtime(state->wakeup_req, state->ev,
+ timeval_zero())) {
+ DEBUG(10, ("tevent_req_set_endtime failed\n"));
+ }
+ return false;
+}
+
+static void background_job_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct background_job_state *state = tevent_req_data(
+ req, struct background_job_state);
+ int fds[2];
+ int res;
+ bool ret;
+
+ ret = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ state->wakeup_req = NULL;
+ if (!ret) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ res = pipe(fds);
+ if (res == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return;
+ }
+
+ res = fork();
+ if (res == -1) {
+ int err = errno;
+ close(fds[0]);
+ close(fds[1]);
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ if (res == 0) {
+ /* child */
+
+ NTSTATUS status;
+ ssize_t written;
+
+ close(fds[0]);
+
+ status = reinit_after_fork(state->msg, state->ev, true);
+ if (NT_STATUS_IS_OK(status)) {
+ res = state->fn(state->private_data);
+ } else {
+ res = -1;
+ }
+ written = write(fds[1], &res, sizeof(res));
+ if (written == -1) {
+ _exit(1);
+ }
+
+ /*
+ * No TALLOC_FREE here, messaging_parent_dgm_cleanup_init for
+ * example calls background_job_send with "messaging_context"
+ * as talloc parent. Thus "state" will be freed with the
+ * following talloc_free will have removed "state" when it
+ * returns. TALLOC_FREE will then write a NULL into free'ed
+ * memory. talloc_free() is required although we immediately
+ * exit, the messaging_context's destructor will want to clean
+ * up.
+ */
+ talloc_free(state->msg);
+ _exit(0);
+ }
+
+ /* parent */
+
+ close(fds[1]);
+ state->pipe_fd = fds[0];
+
+ subreq = read_packet_send(state, state->ev, state->pipe_fd,
+ sizeof(int), NULL, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, background_job_done, req);
+ state->pipe_req = subreq;
+}
+
+static void background_job_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct background_job_state *state = tevent_req_data(
+ req, struct background_job_state);
+ ssize_t ret;
+ uint8_t *buf;
+ int err;
+ int wait_secs;
+
+ state->pipe_req = NULL;
+
+ ret = read_packet_recv(subreq, talloc_tos(), &buf, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ close(state->pipe_fd);
+ state->pipe_fd = -1;
+ memcpy(&wait_secs, buf, sizeof(wait_secs));
+ if (wait_secs == -1) {
+ tevent_req_done(req);
+ return;
+ }
+ subreq = tevent_wakeup_send(
+ state, state->ev, timeval_current_ofs(wait_secs, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, background_job_waited, req);
+ state->wakeup_req = subreq;
+}
+
+NTSTATUS background_job_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/lib/background.h b/source3/lib/background.h
new file mode 100644
index 0000000..8c8fe22
--- /dev/null
+++ b/source3/lib/background.h
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+ Regular background jobs as forked helpers
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIB_BACKGROUND_H_
+#define _LIB_BACKGROUND_H_
+
+#include "replace.h"
+#include <tevent.h>
+#include "libcli/util/ntstatus.h"
+
+struct messaging_context;
+
+/*
+ * From a parent process regularly fork a process and execute fn(). fn()
+ * returns the number of seconds to wait before it is run next time. Returning
+ * -1 means stop the job.
+ */
+
+struct tevent_req *background_job_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ uint32_t *trigger_msgs,
+ size_t num_trigger_msgs,
+ time_t initial_wait_sec,
+ int (*fn)(void *private_data),
+ void *private_data);
+NTSTATUS background_job_recv(struct tevent_req *req);
+
+#endif
diff --git a/source3/lib/cbuf.c b/source3/lib/cbuf.c
new file mode 100644
index 0000000..de0518d
--- /dev/null
+++ b/source3/lib/cbuf.c
@@ -0,0 +1,328 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file cbuf.c
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Aug 2010
+ *
+ * @brief A talloced character buffer.
+ *
+ */
+
+
+#include "replace.h"
+#include "system/locale.h"
+#include "cbuf.h"
+#include <talloc.h>
+#include <assert.h>
+#include "lib/util/byteorder.h"
+
+
+struct cbuf {
+ char* buf;
+ size_t pos;
+ size_t size;
+};
+
+
+cbuf* cbuf_clear(cbuf* b)
+{
+ cbuf_setpos(b, 0);
+ return b;
+}
+
+cbuf* cbuf_new(const void* ctx)
+{
+ cbuf* s = talloc(ctx, cbuf);
+ if (s == NULL)
+ return NULL;
+ s->size = 32;
+ s->buf = (char *)talloc_size(s, s->size);
+ if (s->size && (s->buf == NULL)) {
+ talloc_free(s);
+ return NULL;
+ }
+ return cbuf_clear(s);
+}
+
+cbuf* cbuf_copy(const cbuf* b)
+{
+ cbuf* s = talloc(talloc_parent(b), cbuf);
+ if (s == NULL) {
+ return NULL;
+ }
+
+ s->buf = (char *)talloc_memdup(s, b->buf, b->size); /* only up to pos? */
+
+ /* XXX shallow did not work, because realloc */
+ /* fails with multiple references */
+ /* s->buf = talloc_reference(s, b->buf); */
+
+ if (s->buf == NULL) {
+ cbuf_delete(s);
+ return NULL;
+ }
+ s->size = b->size;
+ s->pos = b->pos;
+ return s;
+}
+
+void cbuf_delete(cbuf* b)
+{
+ talloc_free(b);
+}
+
+#define SWAP(A,B,T) do { \
+ T tmp = A; A = B; B = tmp; \
+ } while(0)
+
+
+void cbuf_swap(cbuf* b1, cbuf* b2)
+{
+ if (b1 == b2) {
+ return;
+ }
+ talloc_reparent(b1, b2, b1->buf);
+ talloc_reparent(b2, b1, b2->buf);
+ SWAP(b1->buf, b2->buf, char*);
+ SWAP(b1->pos, b2->pos, size_t);
+ SWAP(b1->size, b2->size, size_t);
+}
+
+
+
+cbuf* cbuf_takeover(cbuf* b1, cbuf* b2)
+{
+ talloc_reparent(b2, b1, b2->buf);
+ b1->buf = b2->buf;
+ b1->pos = b2->pos;
+ b1->size = b2->size;
+ cbuf_delete(b2);
+ return b1;
+}
+
+cbuf* cbuf_swapptr(cbuf* b, char** ptr, size_t len)
+{
+ void* p = talloc_parent(*ptr);
+ SWAP(b->buf, *ptr, char*);
+ talloc_steal(b, b->buf);
+ talloc_steal(p, *ptr);
+ b->size = talloc_get_size(b->buf);
+ b->pos = (len == -1) ? strlen(b->buf) : len;
+
+ assert(b->pos <= b->size);
+ return b;
+}
+
+cbuf* cbuf_resize(cbuf* b, size_t size)
+{
+ char* save_buf = b->buf;
+ b->buf = talloc_realloc(b, b->buf, char, size);
+ if (b->buf == NULL) {
+ talloc_free(save_buf);
+ b->size = 0;
+ } else {
+ b->size = size;
+ }
+ b->pos = MIN(b->pos, b->size);
+ return b->buf ? b : NULL;
+}
+
+char* cbuf_reserve(cbuf* b, size_t len)
+{
+ if(b->size < b->pos + len)
+ cbuf_resize(b, MAX(2*b->size, b->pos + len));
+ return b->buf ? b->buf + b->pos : NULL;
+}
+
+int cbuf_puts(cbuf* b, const char* str, size_t len)
+{
+ char* dst;
+
+ if (b == NULL)
+ return 0;
+
+ if (len == -1) {
+ len=strlen(str);
+ }
+
+ dst = cbuf_reserve(b, len+1);
+ if (dst == NULL)
+ return -1;
+
+ memcpy(dst, str, len);
+ dst[len] = '\0'; /* just to ease debugging */
+
+ b->pos += len;
+ assert(b->pos < b->size);
+
+ return len;
+}
+
+int cbuf_putc(cbuf* b, char c) {
+ char* dst;
+
+ if (b == NULL)
+ return 0;
+
+ dst = cbuf_reserve(b, 2);
+ if (dst == NULL) {
+ return -1;
+ }
+
+ dst[0] = c;
+ dst[1] = '\0'; /* just to ease debugging */
+
+ b->pos++;
+ assert(b->pos < b->size);
+
+ return 1;
+}
+
+int cbuf_putdw(cbuf* b, uint32_t u) {
+ char* dst;
+ static const size_t LEN = sizeof(uint32_t);
+
+ if (b == NULL)
+ return 0;
+
+ dst = cbuf_reserve(b, LEN);
+ if (dst == NULL) {
+ return -1;
+ }
+
+ SIVAL(dst, 0, u);
+
+ b->pos += LEN;
+ assert(b->pos <= b->size); /* no NULL termination*/
+
+ return LEN;
+}
+
+size_t cbuf_getpos(const cbuf* b) {
+ assert(b->pos <= b->size);
+ return b->pos;
+}
+
+void cbuf_setpos(cbuf* b, size_t pos) {
+ assert(pos <= b->size);
+ b->pos = pos;
+ if (pos < b->size)
+ b->buf[pos] = '\0'; /* just to ease debugging */
+}
+
+char* cbuf_gets(cbuf* b, size_t idx) {
+ assert(idx <= b->pos);
+
+ if (cbuf_reserve(b, 1) == NULL)
+ return NULL;
+
+ b->buf[b->pos] = '\0';
+ return b->buf + idx;
+}
+
+int cbuf_printf(cbuf* b, const char* fmt, ...)
+{
+ va_list args, args2;
+ int len;
+ char* dst = b->buf + b->pos;
+ const int avail = b->size - b->pos;
+ assert(avail >= 0);
+
+ va_start(args, fmt);
+ va_copy(args2, args);
+
+ len = vsnprintf(dst, avail, fmt, args);
+
+ if (len >= avail) {
+ dst = cbuf_reserve(b, len+1);
+ len = (dst != NULL) ? vsnprintf(dst, len+1, fmt, args2) : -1;
+ }
+
+ if (len > 0) {
+ b->pos += len;
+ }
+
+ va_end(args);
+ va_end(args2);
+ assert(b->pos <= b->size);
+
+ return len;
+}
+
+int cbuf_print_quoted_string(cbuf* ost, const char* s)
+{
+ int n = 1;
+ cbuf_putc(ost,'"');
+
+ while(true) {
+ switch (*s) {
+ case '\0':
+ cbuf_putc(ost, '"');
+ return n+1;
+
+ case '"':
+ case '\\':
+ cbuf_putc(ost, '\\');
+ n++;
+
+ FALL_THROUGH;
+ default:
+ cbuf_putc(ost, *s);
+ n++;
+ }
+ s++;
+ }
+}
+
+
+int cbuf_print_quoted(cbuf* ost, const char* s, size_t len)
+{
+ int n = 1;
+ int ret;
+ cbuf_reserve(ost, len+2);
+
+ cbuf_putc(ost,'"');
+
+ while(len--) {
+ switch (*s) {
+ case '"':
+ case '\\':
+ ret = cbuf_printf(ost, "\\%c", *s);
+ break;
+ default:
+ if (isprint(*s) && ((*s == ' ') || !isspace(*s))) {
+ ret = cbuf_putc(ost, *s);
+ } else {
+ ret = cbuf_printf(ost,
+ "\\%02x",
+ (unsigned char)*s);
+ }
+ }
+ s++;
+ if (ret == -1) {
+ return -1;
+ }
+ n += ret;
+ }
+ ret = cbuf_putc(ost,'"');
+
+ return (ret == -1) ? -1 : (n + ret);
+}
diff --git a/source3/lib/cbuf.h b/source3/lib/cbuf.h
new file mode 100644
index 0000000..d4f5a72
--- /dev/null
+++ b/source3/lib/cbuf.h
@@ -0,0 +1,245 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file cbuf.h
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Aug 2010
+ *
+ * @brief A talloced character buffer.
+ *
+ * A cbuf carries a write position and keeps track of its size.
+ */
+
+#ifndef __CBUF_H
+#define __CBUF_H
+
+struct cbuf;
+typedef struct cbuf cbuf;
+
+/**
+ * Create a new character buffer.
+ *
+ * @param talloc_ctx the talloc parent
+ *
+ * @return a new cbuf object, NULL on error
+ */
+cbuf* cbuf_new(const void* talloc_ctx);
+
+/**
+ * Create a copy of a character buffer.
+ *
+ * @param b the cbuf to copy
+ * @return a new cbuf object, NULL on error
+ */
+cbuf* cbuf_copy(const cbuf* b);
+
+/**
+ * Delete a character buffer.
+ * This invalidates b and free's the memory allocated.
+ * @warning don't talloc_free b directly, however freeing
+ * the parent works as expected
+ * @param b the cbuf to delete
+ */
+void cbuf_delete(cbuf* b);
+
+/**
+ * Reset the buffer to initial state.
+ * Set the write position to the start of buffer, effectivly
+ * clearing its contents. Doesn't free memory.
+ *
+ * @param b the buffer to clear
+ *
+ * @return b
+ */
+cbuf* cbuf_clear(cbuf* b);
+
+/**
+ * Swap the contents of two buffers in O(1).
+ *
+ * @param b1 a character buffer
+ * @param b2 another character buffer
+ */
+void cbuf_swap(cbuf* b1, cbuf* b2);
+
+/**
+ * Swap the contents of a buffer with a talloced string.
+ *
+ * @param b a character buffer
+ * @param ptr a pointer to a talloced string
+ * @param len size of string, -1 means strlen(*ptr)
+ *
+ * @return b
+ */
+cbuf* cbuf_swapptr(cbuf* b, char** ptr, size_t len);
+
+/**
+ * Let a character buffer takeover the contents of another.
+ * This is equivalent to @code
+ * cbuf_swap(b1, b2);
+ * cbuf_delete(b2);
+ * @endcode
+ * @param b1 the destination
+ * @param b2 the victim
+ *
+ * @return b1
+ */
+cbuf* cbuf_takeover(cbuf* b1, cbuf* b2);
+
+/**
+ * Resize a character buffer.
+ * This may free allocated memory.
+ *
+ * @param b the character buffer.
+ * @param size the new size
+ *
+ * @return b, NULL on error
+ */
+cbuf* cbuf_resize(cbuf* b, size_t size);
+
+/**
+ * Reserve space in a character buffer.
+ * Assert there are at least len bytes following the current write position.
+ *
+ * @param b a character buffer
+ * @param len number of bytes to reserve.
+ *
+ * @return a pointer to the current write position, NULL on error
+ */
+char* cbuf_reserve(cbuf* b, size_t len);
+
+/**
+ * Put a character into the buffer.
+ *
+ * @param b a character buffer, may be NULL.
+ * @param c a character
+ * @return number of characters written ((b==NULL) ? 0 : 1)
+ *
+ * @retval -1 on error
+ */
+int cbuf_putc(cbuf* b, char c);
+
+/**
+ * Put a string into the buffer.
+ *
+ * @param b a character buffer, may be NULL
+ * @param str a string
+ * @param len number of bytes to write, -1 means strlen(str)
+ *
+ * @return number of characters written, -1 on error
+ */
+int cbuf_puts(cbuf* b, const char* str, size_t len);
+
+/* /\** */
+/* * Put a string into the buffer, changing case. */
+/* * */
+/* * @param b a character buffer, may be NULL */
+/* * @param str a string */
+/* * @param len number of bytes to write, -1 means strlen(str) */
+/* * @param c a character specifying case: */
+/* * @li 'U' upper case */
+/* * @li 'L' lower case */
+/* * @li 'T' title case */
+/* * @li 'P' preserve case */
+/* * @return number of characters written, -1 on error */
+/* *\/ */
+/* int cbuf_puts_case(cbuf* b, const char* str, size_t len, char c); */
+
+
+
+/**
+ * Put a uint32 into the buffer.
+ * Write in little endian order.
+ *
+ * @param b a character buffer, may be NULL
+ * @param u an uint32
+ *
+ * @return number of characters written, -1 on error
+ */
+int cbuf_putdw(cbuf* b, uint32_t u);
+
+/**
+ * Print formatted to a character buffer.
+ *
+ * @param b a character buffer
+ * @param fmt a printf format string
+ *
+ * @return number of characters written, negative on error
+ */
+int cbuf_printf(cbuf* b, const char* fmt, ...) PRINTF_ATTRIBUTE(2,3);
+
+
+/**
+ * Get the current write position.
+ *
+ * @param b a character buffer.
+ *
+ * @return index of the next character to write.
+ */
+size_t cbuf_getpos(const cbuf* b);
+
+/**
+ * Set the current write position of a buffer.
+ * Invalidates the buffer contents from on the new position.
+ *
+ * @param b a character buffer
+ * @param pos a position obtained by cbuf_getpos
+ */
+void cbuf_setpos(cbuf* b, size_t pos);
+
+/**
+ * Get the buffer contents
+ * starting at idx.
+ * @pre @code idx <= cbuf_getpos(b) @endcode
+ * @param b a character buffer
+ * @param idx a position obtained by cbuf_getpos
+ *
+ * @return a NUL terminated string
+ */
+char* cbuf_gets(cbuf* b, size_t idx);
+
+/**
+ * Print quoted string to stream.
+ *
+ * @todo check for ssputc failure
+ * @see srprs_quoted_string
+ *
+ * @param[out] ost outstream
+ * @param[in] s '\0' terminated string of printable characters.
+ *
+ * @return number of bytes written, -1 on error
+ */
+int cbuf_print_quoted_string(cbuf* ost, const char* s);
+
+/**
+ * Print quoted string to stream.
+ * Escapes nonprintable characters.
+ *
+ * @todo check for ssputc failure
+ * @see srprs_quoted
+ *
+ * @param[out] ost outstream
+ * @param[in] s string of bytes
+ * @param[in] len number of bytes
+ *
+ * @return number of bytes written, -1 on error
+ */
+int cbuf_print_quoted(cbuf* ost, const char* s, size_t len);
+
+
+#endif /*__CBUF_H*/
diff --git a/source3/lib/charcnv.c b/source3/lib/charcnv.c
new file mode 100644
index 0000000..d00a3a3
--- /dev/null
+++ b/source3/lib/charcnv.c
@@ -0,0 +1,535 @@
+/*
+ Unix SMB/CIFS implementation.
+ Character set conversion Extensions
+ Copyright (C) Igor Vergeichik <iverg@mail.ru> 2001
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Martin Pool 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+#include "includes.h"
+
+/**
+ * Destroy global objects allocated by init_iconv()
+ **/
+void gfree_charcnv(void)
+{
+ free_iconv_handle();
+}
+
+/**
+ * Copy a string from a char* unix src to a dos codepage string destination.
+ *
+ * @return the number of bytes occupied by the string in the destination.
+ *
+ * @param flags can include
+ * <dl>
+ * <dt>STR_TERMINATE</dt> <dd>means include the null termination</dd>
+ * <dt>STR_UPPER</dt> <dd>means uppercase in the destination</dd>
+ * </dl>
+ *
+ * @param dest_len the maximum length in bytes allowed in the
+ * destination.
+ **/
+size_t push_ascii(void *dest, const char *src, size_t dest_len, int flags)
+{
+ size_t src_len = 0;
+ char *tmpbuf = NULL;
+ size_t size = 0;
+ bool ret;
+
+ /* No longer allow a length of -1. */
+ if (dest_len == (size_t)-1) {
+ smb_panic("push_ascii - dest_len == -1");
+ }
+
+ if (flags & STR_UPPER) {
+ tmpbuf = SMB_STRDUP(src);
+ if (!tmpbuf) {
+ smb_panic("malloc fail");
+ }
+ if (!strupper_m(tmpbuf)) {
+ if ((flags & (STR_TERMINATE|STR_TERMINATE_ASCII)) &&
+ dest &&
+ dest_len > 0) {
+ *(char *)dest = 0;
+ }
+ SAFE_FREE(tmpbuf);
+ return 0;
+ }
+ src = tmpbuf;
+ }
+
+ src_len = strlen(src);
+ if (flags & (STR_TERMINATE | STR_TERMINATE_ASCII)) {
+ src_len++;
+ }
+
+ ret = convert_string(CH_UNIX, CH_DOS, src, src_len, dest, dest_len, &size);
+ SAFE_FREE(tmpbuf);
+ if (ret == false) {
+ if ((flags & (STR_TERMINATE | STR_TERMINATE_ASCII)) &&
+ dest_len > 0) {
+ ((char *)dest)[0] = '\0';
+ }
+ return 0;
+ }
+ return size;
+}
+
+/********************************************************************
+ Push and malloc an ascii string. src and dest null terminated.
+********************************************************************/
+
+/**
+ * Copy a string from a dos codepage source to a unix char* destination.
+ *
+ * The resulting string in "dest" is always null terminated.
+ *
+ * @param flags can have:
+ * <dl>
+ * <dt>STR_TERMINATE</dt>
+ * <dd>STR_TERMINATE means the string in @p src
+ * is null terminated, and src_len is ignored.</dd>
+ * </dl>
+ *
+ * @param src_len is the length of the source area in bytes.
+ * @returns the number of bytes occupied by the string in @p src.
+ **/
+size_t pull_ascii(char *dest, const void *src, size_t dest_len, size_t src_len, int flags)
+{
+ bool ret;
+ size_t size = 0;
+
+ if (dest_len == (size_t)-1) {
+ /* No longer allow dest_len of -1. */
+ smb_panic("pull_ascii - invalid dest_len of -1");
+ }
+
+ if (flags & STR_TERMINATE) {
+ if (src_len == (size_t)-1) {
+ src_len = strlen((const char *)src) + 1;
+ } else {
+ size_t len = strnlen((const char *)src, src_len);
+ if (len < src_len)
+ len++;
+ src_len = len;
+ }
+ }
+
+ ret = convert_string(CH_DOS, CH_UNIX, src, src_len, dest, dest_len, &size);
+ if (ret == false) {
+ size = 0;
+ dest_len = 0;
+ }
+
+ if (dest_len && size) {
+ /* Did we already process the terminating zero ? */
+ if (dest[MIN(size-1, dest_len-1)] != 0) {
+ dest[MIN(size, dest_len-1)] = 0;
+ }
+ } else {
+ dest[0] = 0;
+ }
+
+ return src_len;
+}
+
+/**
+ * Copy a string from a dos codepage source to a unix char* destination.
+ * Talloc version.
+ *
+ * The resulting string in "dest" is always null terminated.
+ *
+ * @param flags can have:
+ * <dl>
+ * <dt>STR_TERMINATE</dt>
+ * <dd>STR_TERMINATE means the string in @p src
+ * is null terminated, and src_len is ignored.</dd>
+ * </dl>
+ *
+ * @param src_len is the length of the source area in bytes.
+ * @returns the number of bytes occupied by the string in @p src.
+ **/
+
+static size_t pull_ascii_base_talloc(TALLOC_CTX *ctx,
+ char **ppdest,
+ const void *src,
+ size_t src_len,
+ int flags)
+{
+ char *dest = NULL;
+ size_t dest_len;
+
+ *ppdest = NULL;
+
+ if (!src_len) {
+ return 0;
+ }
+
+ if (src_len == (size_t)-1) {
+ smb_panic("src_len == -1 in pull_ascii_base_talloc");
+ }
+
+ if (flags & STR_TERMINATE) {
+ size_t len = strnlen((const char *)src, src_len);
+ if (len < src_len)
+ len++;
+ src_len = len;
+ /* Ensure we don't use an insane length from the client. */
+ if (src_len >= 1024*1024) {
+ char *msg = talloc_asprintf(ctx,
+ "Bad src length (%u) in "
+ "pull_ascii_base_talloc",
+ (unsigned int)src_len);
+ smb_panic(msg);
+ }
+ }
+
+ /* src_len != -1 here. */
+
+ if (!convert_string_talloc(ctx, CH_DOS, CH_UNIX, src, src_len, &dest,
+ &dest_len)) {
+ dest_len = 0;
+ }
+
+ if (dest_len && dest) {
+ /* Did we already process the terminating zero ? */
+ if (dest[dest_len-1] != 0) {
+ size_t size = talloc_get_size(dest);
+ /* Have we got space to append the '\0' ? */
+ if (size <= dest_len) {
+ /* No, realloc. */
+ dest = talloc_realloc(ctx, dest, char,
+ dest_len+1);
+ if (!dest) {
+ /* talloc fail. */
+ dest_len = (size_t)-1;
+ return 0;
+ }
+ }
+ /* Yay - space ! */
+ dest[dest_len] = '\0';
+ dest_len++;
+ }
+ } else if (dest) {
+ dest[0] = 0;
+ }
+
+ *ppdest = dest;
+ return src_len;
+}
+
+/**
+ * Copy a string from a char* src to a unicode destination.
+ *
+ * @returns the number of bytes occupied by the string in the destination.
+ *
+ * @param flags can have:
+ *
+ * <dl>
+ * <dt>STR_TERMINATE <dd>means include the null termination.
+ * <dt>STR_UPPER <dd>means uppercase in the destination.
+ * <dt>STR_NOALIGN <dd>means don't do alignment.
+ * </dl>
+ *
+ * @param dest_len is the maximum length allowed in the
+ * destination.
+ **/
+
+static size_t push_ucs2(const void *base_ptr, void *dest, const char *src, size_t dest_len, int flags)
+{
+ size_t len=0;
+ size_t src_len;
+ size_t size = 0;
+ bool ret;
+
+ if (dest_len == (size_t)-1) {
+ /* No longer allow dest_len of -1. */
+ smb_panic("push_ucs2 - invalid dest_len of -1");
+ }
+
+ if (flags & STR_TERMINATE)
+ src_len = (size_t)-1;
+ else
+ src_len = strlen(src);
+
+ if (ucs2_align(base_ptr, dest, flags)) {
+ *(char *)dest = 0;
+ dest = (void *)((char *)dest + 1);
+ if (dest_len)
+ dest_len--;
+ len++;
+ }
+
+ /* ucs2 is always a multiple of 2 bytes */
+ dest_len &= ~1;
+
+ ret = convert_string(CH_UNIX, CH_UTF16LE, src, src_len, dest, dest_len, &size);
+ if (ret == false) {
+ if ((flags & STR_TERMINATE) &&
+ dest &&
+ dest_len) {
+ *(char *)dest = 0;
+ }
+ return len;
+ }
+
+ len += size;
+
+ if (flags & STR_UPPER) {
+ smb_ucs2_t *dest_ucs2 = (smb_ucs2_t *)dest;
+ size_t i;
+
+ /* We check for i < (size / 2) below as the dest string isn't null
+ terminated if STR_TERMINATE isn't set. */
+
+ for (i = 0; i < (size / 2) && i < (dest_len / 2) && dest_ucs2[i]; i++) {
+ smb_ucs2_t v = toupper_w(dest_ucs2[i]);
+ if (v != dest_ucs2[i]) {
+ dest_ucs2[i] = v;
+ }
+ }
+ }
+
+ return len;
+}
+
+/**
+ Copy a string from a ucs2 source to a unix char* destination.
+ Talloc version with a base pointer.
+ Uses malloc if TALLOC_CTX is NULL (this is a bad interface and
+ needs fixing. JRA).
+ Flags can have:
+ STR_TERMINATE means the string in src is null terminated.
+ STR_NOALIGN means don't try to align.
+ if STR_TERMINATE is set then src_len is ignored if it is -1.
+ src_len is the length of the source area in bytes
+ Return the number of bytes occupied by the string in src.
+ The resulting string in "dest" is always null terminated.
+**/
+
+static size_t pull_ucs2_base_talloc(TALLOC_CTX *ctx,
+ const void *base_ptr,
+ char **ppdest,
+ const void *src,
+ size_t src_len,
+ int flags)
+{
+ char *dest;
+ size_t dest_len;
+ size_t ucs2_align_len = 0;
+
+ *ppdest = NULL;
+
+#ifdef DEVELOPER
+ /* Ensure we never use the braindead "malloc" variant. */
+ if (ctx == NULL) {
+ smb_panic("NULL talloc CTX in pull_ucs2_base_talloc\n");
+ }
+#endif
+
+ if (!src_len) {
+ return 0;
+ }
+
+ if (src_len == (size_t)-1) {
+ /* no longer used anywhere, but worth checking */
+ smb_panic("sec_len == -1 in pull_ucs2_base_talloc");
+ }
+
+ if (ucs2_align(base_ptr, src, flags)) {
+ src = (const void *)((const char *)src + 1);
+ src_len--;
+ ucs2_align_len = 1;
+ }
+
+ if (flags & STR_TERMINATE) {
+ /* src_len -1 is the default for null terminated strings. */
+ size_t len = strnlen_w((const smb_ucs2_t *)src,
+ src_len/2);
+ if (len < src_len/2)
+ len++;
+ src_len = len*2;
+
+ /* Ensure we don't use an insane length from the client. */
+ if (src_len >= 1024*1024) {
+ smb_panic("Bad src length in pull_ucs2_base_talloc\n");
+ }
+ }
+
+ /* ucs2 is always a multiple of 2 bytes */
+ src_len &= ~1;
+
+ if (!convert_string_talloc(ctx, CH_UTF16LE, CH_UNIX, src, src_len,
+ (void *)&dest, &dest_len)) {
+ dest_len = 0;
+ }
+
+ if (dest_len) {
+ /* Did we already process the terminating zero ? */
+ if (dest[dest_len-1] != 0) {
+ size_t size = talloc_get_size(dest);
+ /* Have we got space to append the '\0' ? */
+ if (size <= dest_len) {
+ /* No, realloc. */
+ dest = talloc_realloc(ctx, dest, char,
+ dest_len+1);
+ if (!dest) {
+ /* talloc fail. */
+ dest_len = (size_t)-1;
+ return 0;
+ }
+ }
+ /* Yay - space ! */
+ dest[dest_len] = '\0';
+ dest_len++;
+ }
+ } else if (dest) {
+ dest[0] = 0;
+ }
+
+ *ppdest = dest;
+ return src_len + ucs2_align_len;
+}
+
+/**
+ Copy a string from a char* src to a unicode or ascii
+ dos codepage destination choosing unicode or ascii based on the
+ flags supplied
+ Return the number of bytes occupied by the string in the destination.
+ flags can have:
+ STR_TERMINATE means include the null termination.
+ STR_UPPER means uppercase in the destination.
+ STR_ASCII use ascii even with unicode packet.
+ STR_NOALIGN means don't do alignment.
+ dest_len is the maximum length allowed in the destination. If dest_len
+ is -1 then no maximum is used.
+**/
+
+size_t push_string_check_fn(void *dest, const char *src,
+ size_t dest_len, int flags)
+{
+ if (!(flags & STR_ASCII) && (flags & STR_UNICODE)) {
+ return push_ucs2(NULL, dest, src, dest_len, flags);
+ }
+ return push_ascii(dest, src, dest_len, flags);
+}
+
+
+/**
+ Copy a string from a char* src to a unicode or ascii
+ dos codepage destination choosing unicode or ascii based on the
+ flags in the SMB buffer starting at base_ptr.
+ Return the number of bytes occupied by the string in the destination.
+ flags can have:
+ STR_TERMINATE means include the null termination.
+ STR_UPPER means uppercase in the destination.
+ STR_ASCII use ascii even with unicode packet.
+ STR_NOALIGN means don't do alignment.
+ dest_len is the maximum length allowed in the destination. If dest_len
+ is -1 then no maximum is used.
+**/
+
+size_t push_string_base(const char *base, uint16_t flags2,
+ void *dest, const char *src,
+ size_t dest_len, int flags)
+{
+
+ if (!(flags & STR_ASCII) && \
+ ((flags & STR_UNICODE || \
+ (flags2 & FLAGS2_UNICODE_STRINGS)))) {
+ return push_ucs2(base, dest, src, dest_len, flags);
+ }
+ return push_ascii(dest, src, dest_len, flags);
+}
+
+/**
+ Copy a string from a unicode or ascii source (depending on
+ the packet flags) to a char* destination.
+ Variant that uses talloc.
+ Flags can have:
+ STR_TERMINATE means the string in src is null terminated.
+ STR_UNICODE means to force as unicode.
+ STR_ASCII use ascii even with unicode packet.
+ STR_NOALIGN means don't do alignment.
+ if STR_TERMINATE is set then src_len is ignored is it is -1
+ src_len is the length of the source area in bytes.
+ Return the number of bytes occupied by the string in src.
+ The resulting string in "dest" is always null terminated.
+**/
+
+size_t pull_string_talloc(TALLOC_CTX *ctx,
+ const void *base_ptr,
+ uint16_t smb_flags2,
+ char **ppdest,
+ const void *src,
+ size_t src_len,
+ int flags)
+{
+ if ((base_ptr == NULL) && ((flags & (STR_ASCII|STR_UNICODE)) == 0)) {
+ smb_panic("No base ptr to get flg2 and neither ASCII nor "
+ "UNICODE defined");
+ }
+
+ if (!(flags & STR_ASCII) && \
+ ((flags & STR_UNICODE || \
+ (smb_flags2 & FLAGS2_UNICODE_STRINGS)))) {
+ return pull_ucs2_base_talloc(ctx,
+ base_ptr,
+ ppdest,
+ src,
+ src_len,
+ flags);
+ }
+ return pull_ascii_base_talloc(ctx,
+ ppdest,
+ src,
+ src_len,
+ flags);
+}
+
+/*******************************************************************
+ Write a string in (little-endian) unicode format. src is in
+ the current DOS codepage. len is the length in bytes of the
+ string pointed to by dst.
+
+ if null_terminate is True then null terminate the packet (adds 2 bytes)
+
+ the return value is the length in bytes consumed by the string, including the
+ null termination if applied
+********************************************************************/
+
+size_t dos_PutUniCode(char *dst,const char *src, size_t len, bool null_terminate)
+{
+ int flags = null_terminate ? STR_UNICODE|STR_NOALIGN|STR_TERMINATE
+ : STR_UNICODE|STR_NOALIGN;
+ return push_ucs2(NULL, dst, src, len, flags);
+}
+
+
+/* Converts a string from internal samba format to unicode. Always terminates.
+ * Actually just a wrapper round push_ucs2_talloc().
+ */
+
+int rpcstr_push_talloc(TALLOC_CTX *ctx, smb_ucs2_t **dest, const char *src)
+{
+ size_t size;
+ if (push_ucs2_talloc(ctx, dest, src, &size))
+ return size;
+ else
+ return -1;
+}
diff --git a/source3/lib/cleanupdb.c b/source3/lib/cleanupdb.c
new file mode 100644
index 0000000..1b3c5a2
--- /dev/null
+++ b/source3/lib/cleanupdb.c
@@ -0,0 +1,169 @@
+/*
+ Unix SMB/CIFS implementation.
+ Implementation of reliable cleanup events
+ Copyright (C) Ralph Boehme 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/debug.h"
+#include "source3/lib/cleanupdb.h"
+#include "source3/lib/util_path.h"
+
+struct cleanup_key {
+ pid_t pid;
+};
+
+struct cleanup_rec {
+ bool unclean;
+};
+
+static struct tdb_wrap *cleanup_db(void)
+{
+ static struct tdb_wrap *db;
+ char *db_path = NULL;
+ int tdbflags = TDB_INCOMPATIBLE_HASH | TDB_CLEAR_IF_FIRST |
+ TDB_MUTEX_LOCKING;
+
+ if (db != NULL) {
+ return db;
+ }
+
+ db_path = lock_path(talloc_tos(), "smbd_cleanupd.tdb");
+ if (db_path == NULL) {
+ return NULL;
+ }
+
+ db = tdb_wrap_open(NULL, db_path, 0, tdbflags,
+ O_CREAT | O_RDWR, 0644);
+ if (db == NULL) {
+ DBG_ERR("Failed to open smbd_cleanupd.tdb\n");
+ }
+
+ TALLOC_FREE(db_path);
+ return db;
+}
+
+bool cleanupdb_store_child(const pid_t pid, const bool unclean)
+{
+ struct tdb_wrap *db;
+ struct cleanup_key key = { .pid = pid };
+ struct cleanup_rec rec = { .unclean = unclean };
+ TDB_DATA tdbkey = { .dptr = (uint8_t *)&key, .dsize = sizeof(key) };
+ TDB_DATA tdbdata = { .dptr = (uint8_t *)&rec, .dsize = sizeof(rec) };
+ int result;
+
+ db = cleanup_db();
+ if (db == NULL) {
+ return false;
+ }
+
+ result = tdb_store(db->tdb, tdbkey, tdbdata, TDB_REPLACE);
+ if (result != 0) {
+ DBG_ERR("tdb_store failed for pid %d\n", (int)pid);
+ return false;
+ }
+
+ return true;
+}
+
+bool cleanupdb_delete_child(const pid_t pid)
+{
+ struct tdb_wrap *db;
+ struct cleanup_key key = { .pid = pid };
+ TDB_DATA tdbkey = { .dptr = (uint8_t *)&key, .dsize = sizeof(key) };
+ int result;
+
+ db = cleanup_db();
+ if (db == NULL) {
+ return false;
+ }
+
+ result = tdb_delete(db->tdb, tdbkey);
+ if (result != 0) {
+ DBG_ERR("tdb_delete failed for pid %d\n", (int)pid);
+ return false;
+ }
+
+ return true;
+}
+
+struct cleanup_read_state {
+ int (*fn)(const pid_t pid, const bool cleanup, void *private_data);
+ void *private_data;
+};
+
+static int cleanup_traverse_fn(struct tdb_context *tdb,
+ TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct cleanup_read_state *state =
+ (struct cleanup_read_state *)private_data;
+ struct cleanup_key ckey;
+ struct cleanup_rec rec;
+ int result;
+
+ if (key.dsize != sizeof(struct cleanup_key)) {
+ DBG_ERR("Found invalid key length %zu in cleanup.tdb\n",
+ key.dsize);
+ return -1;
+ }
+ memcpy(&ckey, key.dptr, sizeof(struct cleanup_key));
+
+ if (value.dsize != sizeof(struct cleanup_rec)) {
+ DBG_ERR("Found invalid value length %zu in cleanup.tdb\n",
+ value.dsize);
+ return -1;
+ }
+ memcpy(&rec, value.dptr, sizeof(struct cleanup_rec));
+
+ result = state->fn(ckey.pid, rec.unclean, state->private_data);
+ if (result != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+int cleanupdb_traverse_read(int (*fn)(const pid_t pid,
+ const bool cleanup,
+ void *private_data),
+ void *private_data)
+{
+ struct tdb_wrap *db;
+ struct cleanup_read_state state;
+ int result;
+
+ db = cleanup_db();
+ if (db == NULL) {
+ return -1;
+ }
+
+ state = (struct cleanup_read_state) {
+ .fn = fn,
+ .private_data = private_data
+ };
+
+ result = tdb_traverse_read(db->tdb, cleanup_traverse_fn, &state);
+ if (result < 0) {
+ DBG_ERR("tdb_traverse_read failed\n");
+ return -1;
+ }
+
+ return result;
+}
diff --git a/source3/lib/cleanupdb.h b/source3/lib/cleanupdb.h
new file mode 100644
index 0000000..83f51bb
--- /dev/null
+++ b/source3/lib/cleanupdb.h
@@ -0,0 +1,32 @@
+/*
+ Unix SMB/CIFS implementation.
+ Implementation of reliable cleanup events
+ Copyright (C) Ralph Boehme 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __LIB_CLEANUPDB_H__
+#define __LIB_CLEANUPDB_H__
+
+#include "replace.h"
+
+bool cleanupdb_store_child(const pid_t pid, const bool unclean);
+bool cleanupdb_delete_child(const pid_t pid);
+int cleanupdb_traverse_read(int (*fn)(const pid_t pid,
+ const bool cleanup,
+ void *private_data),
+ void *private_data);
+
+#endif
diff --git a/source3/lib/cluster_support.c b/source3/lib/cluster_support.c
new file mode 100644
index 0000000..c11b1f7
--- /dev/null
+++ b/source3/lib/cluster_support.c
@@ -0,0 +1,72 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) 2014 Stefan Metzmacher
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include <tdb.h>
+#include "cluster_support.h"
+
+#ifdef CLUSTER_SUPPORT
+#include <ctdb_protocol.h>
+#endif
+
+bool cluster_support_available(void)
+{
+#ifdef CLUSTER_SUPPORT
+ return true;
+#else
+ return false;
+#endif
+}
+
+const char *cluster_support_features(void)
+{
+#define _LINE_DEF(x) " " #x "\n"
+#define _LINE_STR(x) " " #x ": " x "\n"
+#define _LINE_INT(x) " " #x ": " __STRINGSTRING(x) "\n"
+ static const char *v = "Cluster support features:\n"
+#ifdef CLUSTER_SUPPORT
+ _LINE_DEF(CLUSTER_SUPPORT)
+#else
+ " NONE\n"
+#endif
+#ifdef CTDB_SOCKET
+ _LINE_STR(CTDB_SOCKET)
+#endif
+#ifdef CTDB_PROTOCOL
+ _LINE_INT(CTDB_PROTOCOL)
+#endif
+ "";
+
+ return v;
+}
+
+const char *lp_ctdbd_socket(void)
+{
+ const char *ret;
+
+ ret = lp__ctdbd_socket();
+ if (ret != NULL && strlen(ret) > 0) {
+ return ret;
+ }
+
+#ifdef CTDB_SOCKET
+ return CTDB_SOCKET;
+#else
+ return "";
+#endif
+}
diff --git a/source3/lib/cluster_support.h b/source3/lib/cluster_support.h
new file mode 100644
index 0000000..5e040bb
--- /dev/null
+++ b/source3/lib/cluster_support.h
@@ -0,0 +1,21 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) 2014 Stefan Metzmacher
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+bool cluster_support_available(void);
+const char *cluster_support_features(void);
+const char *lp_ctdbd_socket(void);
diff --git a/source3/lib/cmdline_contexts.c b/source3/lib/cmdline_contexts.c
new file mode 100644
index 0000000..627ee4f
--- /dev/null
+++ b/source3/lib/cmdline_contexts.c
@@ -0,0 +1,71 @@
+/*
+ Unix SMB/CIFS implementation.
+ cmdline context wrapper.
+
+ Copyright (C) Christof Schmitt <cs@samba.org> 2018
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "cmdline_contexts.h"
+#include "includes.h"
+#include "messages.h"
+#include "lib/global_contexts.h"
+
+struct messaging_context *cmdline_messaging_context(const char *config_file)
+{
+ struct messaging_context *msg_ctx = NULL;
+
+ /*
+ * Ensure that a config is loaded, in case the underlying
+ * messaging_init needs to create directories or sockets.
+ */
+ if (!lp_loaded()) {
+ if (!lp_load_initial_only(config_file)) {
+ return NULL;
+ }
+ }
+
+ /*
+ * Clustered Samba can only work as root due to required
+ * access to the registry and ctdb, which in turn requires
+ * messaging access as root.
+ */
+ if (lp_clustering() && geteuid() != 0) {
+ fprintf(stderr, "Cluster mode requires running as root.\n");
+ exit(1);
+ }
+
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ if (geteuid() == 0) {
+ fprintf(stderr,
+ "Unable to initialize messaging context!\n");
+ exit(1);
+ } else {
+ /*
+ * Non-cluster, non-root: Log error, but leave
+ * it up to the caller how to proceed.
+ */
+ DBG_NOTICE("Unable to initialize messaging context.\n");
+ }
+ }
+
+ return msg_ctx;
+}
+
+void cmdline_messaging_context_free(void)
+{
+ global_messaging_context_free();
+}
diff --git a/source3/lib/cmdline_contexts.h b/source3/lib/cmdline_contexts.h
new file mode 100644
index 0000000..21f81f0
--- /dev/null
+++ b/source3/lib/cmdline_contexts.h
@@ -0,0 +1,27 @@
+/*
+ Unix SMB/CIFS implementation.
+ cmdline context wrapper.
+
+ Copyright (C) Christof Schmitt <cs@samba.org> 2018
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIB_CMDLINE_CONTEXTS_H
+#define _LIB_CMDLINE_CONTEXTS_H
+
+struct messaging_context *cmdline_messaging_context(const char *config_file);
+void cmdline_messaging_context_free(void);
+
+#endif
diff --git a/source3/lib/ctdb_dummy.c b/source3/lib/ctdb_dummy.c
new file mode 100644
index 0000000..f159b7d
--- /dev/null
+++ b/source3/lib/ctdb_dummy.c
@@ -0,0 +1,180 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) 2014 Björn Baumbach
+ Copyright (C) 2014 Stefan Metzmacher
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "lib/messages_ctdb_ref.h"
+#include "ctdbd_conn.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_ctdb.h"
+#include "torture/proto.h"
+
+int ctdbd_probe(const char *sockname, int timeout)
+{
+ return ENOSYS;
+}
+
+int ctdbd_messaging_send_iov(struct ctdbd_connection *conn,
+ uint32_t dst_vnn, uint64_t dst_srvid,
+ const struct iovec *iov, int iovlen)
+{
+ return ENOSYS;
+}
+
+int register_with_ctdbd(struct ctdbd_connection *conn, uint64_t srvid,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ return ENOSYS;
+}
+
+void deregister_from_ctdbd(struct ctdbd_connection *conn,
+ uint64_t srvid,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+}
+
+int ctdbd_register_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ return ENOSYS;
+}
+
+void ctdbd_unregister_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ return;
+}
+void ctdbd_passed_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ return;
+}
+
+int ctdbd_public_ip_foreach(struct ctdbd_connection *conn,
+ int (*cb)(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ bool is_movable_ip,
+ void *private_data),
+ void *private_data)
+{
+ return ENOSYS;
+}
+
+int ctdbd_all_ip_foreach(struct ctdbd_connection *conn,
+ bool include_node_ips,
+ bool include_public_ips,
+ int (*cb)(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ uint32_t pinned_pnn,
+ uint32_t current_pnn,
+ void *private_data),
+ void *private_data)
+{
+ return ENOSYS;
+}
+
+bool ctdbd_process_exists(struct ctdbd_connection *conn, uint32_t vnn,
+ pid_t pid, uint64_t unique_id)
+{
+ return false;
+}
+
+struct db_context *db_open_ctdb(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *name,
+ int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ enum dbwrap_lock_order lock_order,
+ uint64_t dbwrap_flags)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+
+int messaging_ctdb_send(uint32_t dst_vnn, uint64_t dst_srvid,
+ const struct iovec *iov, int iovlen)
+{
+ return ENOSYS;
+}
+
+void *messaging_ctdb_ref(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *sockname, int timeout, uint64_t unique_id,
+ void (*recv_cb)(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds,
+ void *private_data),
+ void *private_data,
+ int *err)
+{
+ return NULL;
+}
+
+struct messaging_ctdb_fde *messaging_ctdb_register_tevent_context(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev)
+{
+ return NULL;
+}
+
+struct ctdbd_connection *messaging_ctdb_connection(void)
+{
+ return NULL;
+}
+
+int ctdb_async_ctx_reinit(TALLOC_CTX *mem_ctx, struct tevent_context *ev)
+{
+ return ENOSYS;
+}
diff --git a/source3/lib/ctdbd_conn.c b/source3/lib/ctdbd_conn.c
new file mode 100644
index 0000000..9f8dce6
--- /dev/null
+++ b/source3/lib/ctdbd_conn.c
@@ -0,0 +1,2341 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba internal messaging functions
+ Copyright (C) 2007 by Volker Lendecke
+ Copyright (C) 2007 by Andrew Tridgell
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include <tevent.h>
+#include "util_tdb.h"
+#include "serverid.h"
+#include "ctdbd_conn.h"
+#include "system/select.h"
+#include "lib/util/util_net.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/iov_buf.h"
+#include "lib/util/select.h"
+#include "lib/util/debug.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/genrand.h"
+#include "lib/util/fault.h"
+#include "lib/util/dlinklist.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/blocking.h"
+#include "ctdb/include/ctdb_protocol.h"
+#include "lib/async_req/async_sock.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+
+/* paths to these include files come from --with-ctdb= in configure */
+
+struct ctdbd_srvid_cb {
+ uint64_t srvid;
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data);
+ void *private_data;
+};
+
+struct ctdbd_connection {
+ uint32_t reqid;
+ uint32_t our_vnn;
+ uint64_t rand_srvid;
+ struct ctdbd_srvid_cb *callbacks;
+ int fd;
+ int timeout;
+
+ /*
+ * Outgoing queue for writev_send of asynchronous ctdb requests
+ */
+ struct tevent_queue *outgoing;
+ struct tevent_req **pending;
+ struct tevent_req *read_req;
+};
+
+static bool ctdbd_conn_has_async_reqs(struct ctdbd_connection *conn)
+{
+ size_t len = talloc_array_length(conn->pending);
+ return (len != 0);
+}
+
+static uint32_t ctdbd_next_reqid(struct ctdbd_connection *conn)
+{
+ conn->reqid += 1;
+ if (conn->reqid == 0) {
+ conn->reqid += 1;
+ }
+ return conn->reqid;
+}
+
+static int ctdbd_control(struct ctdbd_connection *conn,
+ uint32_t vnn, uint32_t opcode,
+ uint64_t srvid, uint32_t flags,
+ TDB_DATA data,
+ TALLOC_CTX *mem_ctx, TDB_DATA *outdata,
+ int32_t *cstatus);
+
+/*
+ * exit on fatal communications errors with the ctdbd daemon
+ */
+static void cluster_fatal(const char *why)
+{
+ DEBUG(0,("cluster fatal event: %s - exiting immediately\n", why));
+ /* we don't use smb_panic() as we don't want to delay to write
+ a core file. We need to release this process id immediately
+ so that someone else can take over without getting sharing
+ violations */
+ _exit(1);
+}
+
+/*
+ *
+ */
+static void ctdb_packet_dump(struct ctdb_req_header *hdr)
+{
+ if (DEBUGLEVEL < 11) {
+ return;
+ }
+ DEBUGADD(11, ("len=%"PRIu32", magic=%"PRIu32", vers=%"PRIu32", "
+ "gen=%"PRIu32", op=%"PRIu32", reqid=%"PRIu32"\n",
+ hdr->length,
+ hdr->ctdb_magic,
+ hdr->ctdb_version,
+ hdr->generation,
+ hdr->operation,
+ hdr->reqid));
+}
+
+/*
+ * Register a srvid with ctdbd
+ */
+int register_with_ctdbd(struct ctdbd_connection *conn, uint64_t srvid,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ size_t num_callbacks = talloc_array_length(conn->callbacks);
+ struct ctdbd_srvid_cb *tmp;
+ bool need_register = true;
+ size_t i;
+
+ for (i = 0; i < num_callbacks; i++) {
+ struct ctdbd_srvid_cb *c = &conn->callbacks[i];
+
+ if (c->srvid == srvid) {
+ need_register = false;
+ break;
+ }
+ }
+
+ if (need_register) {
+ int ret;
+ int32_t cstatus;
+
+ ret = ctdbd_control_local(conn, CTDB_CONTROL_REGISTER_SRVID,
+ srvid, 0, tdb_null, NULL, NULL,
+ &cstatus);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+
+ tmp = talloc_realloc(conn, conn->callbacks, struct ctdbd_srvid_cb,
+ num_callbacks + 1);
+ if (tmp == NULL) {
+ return ENOMEM;
+ }
+ conn->callbacks = tmp;
+
+ conn->callbacks[num_callbacks] = (struct ctdbd_srvid_cb) {
+ .srvid = srvid, .cb = cb, .private_data = private_data
+ };
+
+ return 0;
+}
+
+void deregister_from_ctdbd(struct ctdbd_connection *conn,
+ uint64_t srvid,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ struct ctdbd_srvid_cb *cbs = conn->callbacks;
+ size_t i, num_callbacks = talloc_array_length(cbs);
+ bool need_deregister = false;
+ bool keep_registration = false;
+
+ if (num_callbacks == 0) {
+ return;
+ }
+
+ for (i = 0; i < num_callbacks;) {
+ struct ctdbd_srvid_cb *c = &cbs[i];
+
+ if (c->srvid != srvid) {
+ i++;
+ continue;
+ }
+
+ if ((c->cb == cb) && (c->private_data == private_data)) {
+ need_deregister = true;
+ ARRAY_DEL_ELEMENT(cbs, i, num_callbacks);
+ num_callbacks--;
+ continue;
+ }
+
+ keep_registration = true;
+ i++;
+ }
+
+ conn->callbacks = talloc_realloc(conn,
+ cbs,
+ struct ctdbd_srvid_cb,
+ num_callbacks);
+
+ if (keep_registration) {
+ need_deregister = false;
+ }
+
+ if (need_deregister) {
+ int ret;
+ int32_t cstatus;
+
+ ret = ctdbd_control_local(conn, CTDB_CONTROL_DEREGISTER_SRVID,
+ srvid, 0, tdb_null, NULL, NULL,
+ &cstatus);
+ if (ret != 0) {
+ /*
+ * If CTDB_CONTROL_DEREGISTER_SRVID fails we may still
+ * get messages later, but we don't have a callback
+ * anymore, we just ignore these.
+ */
+ }
+ }
+
+ return;
+}
+
+static int ctdbd_msg_call_back(struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ struct ctdb_req_message_old *msg)
+{
+ uint32_t msg_len;
+ size_t i, num_callbacks;
+
+ msg_len = msg->hdr.length;
+ if (msg_len < offsetof(struct ctdb_req_message_old, data)) {
+ DBG_DEBUG("len %"PRIu32" too small\n", msg_len);
+ return 0;
+ }
+ msg_len -= offsetof(struct ctdb_req_message_old, data);
+
+ if (msg_len < msg->datalen) {
+ DBG_DEBUG("msg_len=%"PRIu32" < msg->datalen=%"PRIu32"\n",
+ msg_len, msg->datalen);
+ return 0;
+ }
+
+ num_callbacks = talloc_array_length(conn->callbacks);
+
+ for (i=0; i<num_callbacks; i++) {
+ struct ctdbd_srvid_cb *cb = &conn->callbacks[i];
+
+ if ((cb->srvid == msg->srvid) && (cb->cb != NULL)) {
+ int ret;
+
+ ret = cb->cb(ev,
+ msg->hdr.srcnode, msg->hdr.destnode,
+ msg->srvid, msg->data, msg->datalen,
+ cb->private_data);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * get our vnn from the cluster
+ */
+static int get_cluster_vnn(struct ctdbd_connection *conn, uint32_t *vnn)
+{
+ int32_t cstatus=-1;
+ int ret;
+ ret = ctdbd_control_local(conn, CTDB_CONTROL_GET_PNN, 0, 0,
+ tdb_null, NULL, NULL, &cstatus);
+ if (ret != 0) {
+ DEBUG(1, ("ctdbd_control failed: %s\n", strerror(ret)));
+ return ret;
+ }
+ *vnn = (uint32_t)cstatus;
+ return ret;
+}
+
+static int ctdbd_control_get_nodemap(struct ctdbd_connection *conn,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_node_map_old **_nodemap)
+{
+ int32_t cstatus=-1;
+ TDB_DATA outdata = {0};
+ int ret;
+
+ ret = ctdbd_control_local(conn, CTDB_CONTROL_GET_NODEMAP, 0, 0,
+ tdb_null, mem_ctx, &outdata, &cstatus);
+ if (ret != 0) {
+ DEBUG(1, ("ctdbd_control failed: %s\n", strerror(ret)));
+ return ret;
+ }
+ if ((cstatus != 0) || (outdata.dptr == NULL)) {
+ DEBUG(2, ("Received invalid ctdb data\n"));
+ return EINVAL;
+ }
+
+ *_nodemap = (struct ctdb_node_map_old *)outdata.dptr;
+ return 0;
+}
+
+/*
+ * Are we active (i.e. not banned or stopped?)
+ */
+static bool ctdbd_working(struct ctdbd_connection *conn, uint32_t vnn)
+{
+ struct ctdb_node_map_old *m = NULL;
+ bool ok = false;
+ uint32_t i;
+ int ret;
+
+ ret = ctdbd_control_get_nodemap(conn, talloc_tos(), &m);
+ if (ret != 0) {
+ DEBUG(1, ("ctdbd_control_get_nodemap() failed: %s\n", strerror(ret)));
+ return false;
+ }
+
+ for (i=0; i<m->num; i++) {
+ if (vnn == m->nodes[i].pnn) {
+ break;
+ }
+ }
+
+ if (i == m->num) {
+ DEBUG(2, ("Did not find ourselves (node %d) in nodemap\n",
+ (int)vnn));
+ goto fail;
+ }
+
+ if ((m->nodes[i].flags & NODE_FLAGS_INACTIVE) != 0) {
+ DEBUG(2, ("Node has status %x, not active\n",
+ (int)m->nodes[i].flags));
+ goto fail;
+ }
+
+ ok = true;
+fail:
+ TALLOC_FREE(m);
+ return ok;
+}
+
+uint32_t ctdbd_vnn(const struct ctdbd_connection *conn)
+{
+ return conn->our_vnn;
+}
+
+/*
+ * Get us a ctdb connection
+ */
+
+static int ctdbd_connect(const char *sockname, int *pfd)
+{
+ struct samba_sockaddr addr = {
+ .sa_socklen = sizeof(struct sockaddr_un),
+ .u = {
+ .un = {
+ .sun_family = AF_UNIX,
+ },
+ },
+ };
+ int fd;
+ size_t namelen;
+ int ret;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ int err = errno;
+ DEBUG(3, ("Could not create socket: %s\n", strerror(err)));
+ return err;
+ }
+
+ namelen = strlcpy(addr.u.un.sun_path,
+ sockname,
+ sizeof(addr.u.un.sun_path));
+ if (namelen >= sizeof(addr.u.un.sun_path)) {
+ DEBUG(3, ("%s: Socket name too long: %s\n", __func__,
+ sockname));
+ close(fd);
+ return ENAMETOOLONG;
+ }
+
+ ret = connect(fd, &addr.u.sa, addr.sa_socklen);
+ if (ret == -1) {
+ int err = errno;
+ DEBUG(1, ("connect(%s) failed: %s\n", sockname,
+ strerror(err)));
+ close(fd);
+ return err;
+ }
+
+ *pfd = fd;
+ return 0;
+}
+
+static int ctdb_read_packet(int fd, int timeout, TALLOC_CTX *mem_ctx,
+ struct ctdb_req_header **result)
+{
+ struct ctdb_req_header *req;
+ uint32_t msglen;
+ ssize_t nread;
+
+ if (timeout != -1) {
+ struct pollfd pfd = { .fd = fd, .events = POLLIN };
+ int ret;
+
+ ret = sys_poll_intr(&pfd, 1, timeout);
+ if (ret == -1) {
+ return errno;
+ }
+ if (ret == 0) {
+ return ETIMEDOUT;
+ }
+ if (ret != 1) {
+ return EIO;
+ }
+ }
+
+ nread = read_data(fd, &msglen, sizeof(msglen));
+ if (nread == -1) {
+ return errno;
+ }
+ if (nread == 0) {
+ return EIO;
+ }
+
+ if (msglen < sizeof(struct ctdb_req_header)) {
+ return EIO;
+ }
+
+ req = talloc_size(mem_ctx, msglen);
+ if (req == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_name_const(req, "struct ctdb_req_header");
+
+ req->length = msglen;
+
+ nread = read_data(fd, ((char *)req) + sizeof(msglen),
+ msglen - sizeof(msglen));
+ if (nread == -1) {
+ TALLOC_FREE(req);
+ return errno;
+ }
+ if (nread == 0) {
+ TALLOC_FREE(req);
+ return EIO;
+ }
+
+ *result = req;
+ return 0;
+}
+
+/*
+ * Read a full ctdbd request. If we have a messaging context, defer incoming
+ * messages that might come in between.
+ */
+
+static int ctdb_read_req(struct ctdbd_connection *conn, uint32_t reqid,
+ TALLOC_CTX *mem_ctx, struct ctdb_req_header **result)
+{
+ struct ctdb_req_header *hdr = NULL;
+ int ret;
+
+ next_pkt:
+
+ ret = ctdb_read_packet(conn->fd, conn->timeout, mem_ctx, &hdr);
+ if (ret != 0) {
+ DBG_ERR("ctdb_read_packet failed: %s\n", strerror(ret));
+ cluster_fatal("failed to read data from ctdbd\n");
+ return -1;
+ }
+ SMB_ASSERT(hdr != NULL);
+
+ DEBUG(11, ("Received ctdb packet\n"));
+ ctdb_packet_dump(hdr);
+
+ if (hdr->operation == CTDB_REQ_MESSAGE) {
+ struct ctdb_req_message_old *msg = (struct ctdb_req_message_old *)hdr;
+
+ ret = ctdbd_msg_call_back(NULL, conn, msg);
+ if (ret != 0) {
+ TALLOC_FREE(hdr);
+ return ret;
+ }
+
+ TALLOC_FREE(hdr);
+ goto next_pkt;
+ }
+
+ if ((reqid != 0) && (hdr->reqid != reqid)) {
+ /* we got the wrong reply */
+ DEBUG(0,("Discarding mismatched ctdb reqid %u should have "
+ "been %u\n", hdr->reqid, reqid));
+ TALLOC_FREE(hdr);
+ goto next_pkt;
+ }
+
+ *result = talloc_move(mem_ctx, &hdr);
+
+ return 0;
+}
+
+static int ctdbd_connection_destructor(struct ctdbd_connection *c);
+
+/*
+ * Get us a ctdbd connection
+ */
+
+static int ctdbd_init_connection_internal(TALLOC_CTX *mem_ctx,
+ const char *sockname, int timeout,
+ struct ctdbd_connection *conn)
+{
+ int ret;
+
+ conn->timeout = timeout;
+ if (conn->timeout == 0) {
+ conn->timeout = -1;
+ }
+
+ ret = ctdbd_connect(sockname, &conn->fd);
+ if (ret != 0) {
+ DEBUG(1, ("ctdbd_connect failed: %s\n", strerror(ret)));
+ return ret;
+ }
+ talloc_set_destructor(conn, ctdbd_connection_destructor);
+
+ ret = get_cluster_vnn(conn, &conn->our_vnn);
+ if (ret != 0) {
+ DEBUG(10, ("get_cluster_vnn failed: %s\n", strerror(ret)));
+ return ret;
+ }
+
+ if (!ctdbd_working(conn, conn->our_vnn)) {
+ DEBUG(2, ("Node is not working, can not connect\n"));
+ return EIO;
+ }
+
+ generate_random_buffer((unsigned char *)&conn->rand_srvid,
+ sizeof(conn->rand_srvid));
+
+ ret = register_with_ctdbd(conn, conn->rand_srvid, NULL, NULL);
+ if (ret != 0) {
+ DEBUG(5, ("Could not register random srvid: %s\n",
+ strerror(ret)));
+ return ret;
+ }
+
+ return 0;
+}
+
+int ctdbd_init_connection(TALLOC_CTX *mem_ctx,
+ const char *sockname, int timeout,
+ struct ctdbd_connection **pconn)
+{
+ struct ctdbd_connection *conn;
+ int ret;
+
+ if (!(conn = talloc_zero(mem_ctx, struct ctdbd_connection))) {
+ DEBUG(0, ("talloc failed\n"));
+ return ENOMEM;
+ }
+
+ ret = ctdbd_init_connection_internal(mem_ctx,
+ sockname,
+ timeout,
+ conn);
+ if (ret != 0) {
+ DBG_ERR("ctdbd_init_connection_internal failed (%s)\n",
+ strerror(ret));
+ goto fail;
+ }
+
+ *pconn = conn;
+ return 0;
+
+ fail:
+ TALLOC_FREE(conn);
+ return ret;
+}
+
+int ctdbd_reinit_connection(TALLOC_CTX *mem_ctx,
+ const char *sockname, int timeout,
+ struct ctdbd_connection *conn)
+{
+ int ret;
+
+ ret = ctdbd_connection_destructor(conn);
+ if (ret != 0) {
+ DBG_ERR("ctdbd_connection_destructor failed\n");
+ return ret;
+ }
+
+ ret = ctdbd_init_connection_internal(mem_ctx,
+ sockname,
+ timeout,
+ conn);
+ if (ret != 0) {
+ DBG_ERR("ctdbd_init_connection_internal failed (%s)\n",
+ strerror(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+int ctdbd_init_async_connection(
+ TALLOC_CTX *mem_ctx,
+ const char *sockname,
+ int timeout,
+ struct ctdbd_connection **pconn)
+{
+ struct ctdbd_connection *conn = NULL;
+ int ret;
+
+ *pconn = NULL;
+
+ ret = ctdbd_init_connection(mem_ctx, sockname, timeout, &conn);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = set_blocking(conn->fd, false);
+ if (ret == -1) {
+ int err = errno;
+ SMB_ASSERT(err != 0);
+ TALLOC_FREE(conn);
+ return err;
+ }
+
+ conn->outgoing = tevent_queue_create(conn, "ctdb async outgoing");
+ if (conn->outgoing == NULL) {
+ TALLOC_FREE(conn);
+ return ENOMEM;
+ }
+
+ *pconn = conn;
+ return 0;
+}
+
+int ctdbd_conn_get_fd(struct ctdbd_connection *conn)
+{
+ return conn->fd;
+}
+
+/*
+ * Packet handler to receive and handle a ctdb message
+ */
+static int ctdb_handle_message(struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ struct ctdb_req_header *hdr)
+{
+ struct ctdb_req_message_old *msg;
+
+ if (hdr->operation != CTDB_REQ_MESSAGE) {
+ DEBUG(0, ("Received async msg of type %u, discarding\n",
+ hdr->operation));
+ return EINVAL;
+ }
+
+ msg = (struct ctdb_req_message_old *)hdr;
+
+ ctdbd_msg_call_back(ev, conn, msg);
+
+ return 0;
+}
+
+void ctdbd_socket_readable(struct tevent_context *ev,
+ struct ctdbd_connection *conn)
+{
+ struct ctdb_req_header *hdr = NULL;
+ int ret;
+
+ ret = ctdb_read_packet(conn->fd, conn->timeout, talloc_tos(), &hdr);
+ if (ret != 0) {
+ DBG_ERR("ctdb_read_packet failed: %s\n", strerror(ret));
+ cluster_fatal("failed to read data from ctdbd\n");
+ }
+ SMB_ASSERT(hdr != NULL);
+
+ ret = ctdb_handle_message(ev, conn, hdr);
+
+ TALLOC_FREE(hdr);
+
+ if (ret != 0) {
+ DEBUG(10, ("could not handle incoming message: %s\n",
+ strerror(ret)));
+ }
+}
+
+int ctdbd_messaging_send_iov(struct ctdbd_connection *conn,
+ uint32_t dst_vnn, uint64_t dst_srvid,
+ const struct iovec *iov, int iovlen)
+{
+ struct ctdb_req_message_old r;
+ struct iovec iov2[iovlen+1];
+ size_t buflen = iov_buflen(iov, iovlen);
+ ssize_t nwritten;
+
+ r.hdr.length = offsetof(struct ctdb_req_message_old, data) + buflen;
+ r.hdr.ctdb_magic = CTDB_MAGIC;
+ r.hdr.ctdb_version = CTDB_PROTOCOL;
+ r.hdr.generation = 1;
+ r.hdr.operation = CTDB_REQ_MESSAGE;
+ r.hdr.destnode = dst_vnn;
+ r.hdr.srcnode = conn->our_vnn;
+ r.hdr.reqid = 0;
+ r.srvid = dst_srvid;
+ r.datalen = buflen;
+
+ DEBUG(10, ("ctdbd_messaging_send: Sending ctdb packet\n"));
+ ctdb_packet_dump(&r.hdr);
+
+ iov2[0].iov_base = &r;
+ iov2[0].iov_len = offsetof(struct ctdb_req_message_old, data);
+ memcpy(&iov2[1], iov, iovlen * sizeof(struct iovec));
+
+ nwritten = write_data_iov(conn->fd, iov2, iovlen+1);
+ if (nwritten == -1) {
+ DEBUG(3, ("write_data_iov failed: %s\n", strerror(errno)));
+ cluster_fatal("cluster dispatch daemon msg write error\n");
+ }
+
+ return 0;
+}
+
+/*
+ * send/recv a generic ctdb control message
+ */
+static int ctdbd_control(struct ctdbd_connection *conn,
+ uint32_t vnn, uint32_t opcode,
+ uint64_t srvid, uint32_t flags,
+ TDB_DATA data,
+ TALLOC_CTX *mem_ctx, TDB_DATA *outdata,
+ int32_t *cstatus)
+{
+ struct ctdb_req_control_old req;
+ struct ctdb_req_header *hdr;
+ struct ctdb_reply_control_old *reply = NULL;
+ struct iovec iov[2];
+ ssize_t nwritten;
+ int ret;
+
+ if (ctdbd_conn_has_async_reqs(conn)) {
+ /*
+ * Can't use sync call while an async call is in flight. Adding
+ * this check as a safety net. We'll be using different
+ * connections for sync and async requests, so this shouldn't
+ * happen, but who knows...
+ */
+ DBG_ERR("Async ctdb req on sync connection\n");
+ return EINVAL;
+ }
+
+ ZERO_STRUCT(req);
+ req.hdr.length = offsetof(struct ctdb_req_control_old, data) + data.dsize;
+ req.hdr.ctdb_magic = CTDB_MAGIC;
+ req.hdr.ctdb_version = CTDB_PROTOCOL;
+ req.hdr.operation = CTDB_REQ_CONTROL;
+ req.hdr.reqid = ctdbd_next_reqid(conn);
+ req.hdr.destnode = vnn;
+ req.opcode = opcode;
+ req.srvid = srvid;
+ req.datalen = data.dsize;
+ req.flags = flags;
+
+ DBG_DEBUG("Sending ctdb packet reqid=%"PRIu32", vnn=%"PRIu32", "
+ "opcode=%"PRIu32", srvid=%"PRIu64"\n", req.hdr.reqid,
+ req.hdr.destnode, req.opcode, req.srvid);
+ ctdb_packet_dump(&req.hdr);
+
+ iov[0].iov_base = &req;
+ iov[0].iov_len = offsetof(struct ctdb_req_control_old, data);
+ iov[1].iov_base = data.dptr;
+ iov[1].iov_len = data.dsize;
+
+ nwritten = write_data_iov(conn->fd, iov, ARRAY_SIZE(iov));
+ if (nwritten == -1) {
+ DEBUG(3, ("write_data_iov failed: %s\n", strerror(errno)));
+ cluster_fatal("cluster dispatch daemon msg write error\n");
+ }
+
+ if (flags & CTDB_CTRL_FLAG_NOREPLY) {
+ if (cstatus) {
+ *cstatus = 0;
+ }
+ return 0;
+ }
+
+ ret = ctdb_read_req(conn, req.hdr.reqid, NULL, &hdr);
+ if (ret != 0) {
+ DEBUG(10, ("ctdb_read_req failed: %s\n", strerror(ret)));
+ return ret;
+ }
+
+ if (hdr->operation != CTDB_REPLY_CONTROL) {
+ DEBUG(0, ("received invalid reply\n"));
+ TALLOC_FREE(hdr);
+ return EIO;
+ }
+ reply = (struct ctdb_reply_control_old *)hdr;
+
+ if (outdata) {
+ if (!(outdata->dptr = (uint8_t *)talloc_memdup(
+ mem_ctx, reply->data, reply->datalen))) {
+ TALLOC_FREE(reply);
+ return ENOMEM;
+ }
+ outdata->dsize = reply->datalen;
+ }
+ if (cstatus) {
+ (*cstatus) = reply->status;
+ }
+
+ TALLOC_FREE(reply);
+ return ret;
+}
+
+/*
+ * see if a remote process exists
+ */
+bool ctdbd_process_exists(struct ctdbd_connection *conn, uint32_t vnn,
+ pid_t pid, uint64_t unique_id)
+{
+ uint8_t buf[sizeof(pid)+sizeof(unique_id)];
+ int32_t cstatus = 0;
+ int ret;
+
+ if (unique_id == SERVERID_UNIQUE_ID_NOT_TO_VERIFY) {
+ ret = ctdbd_control(conn, vnn, CTDB_CONTROL_PROCESS_EXISTS,
+ 0, 0,
+ (TDB_DATA) { .dptr = (uint8_t *)&pid,
+ .dsize = sizeof(pid) },
+ NULL, NULL, &cstatus);
+ if (ret != 0) {
+ return false;
+ }
+ return (cstatus == 0);
+ }
+
+ memcpy(buf, &pid, sizeof(pid));
+ memcpy(buf+sizeof(pid), &unique_id, sizeof(unique_id));
+
+ ret = ctdbd_control(conn, vnn, CTDB_CONTROL_CHECK_PID_SRVID, 0, 0,
+ (TDB_DATA) { .dptr = buf, .dsize = sizeof(buf) },
+ NULL, NULL, &cstatus);
+ if (ret != 0) {
+ return false;
+ }
+ return (cstatus == 0);
+}
+
+/*
+ * Get a db path
+ */
+char *ctdbd_dbpath(struct ctdbd_connection *conn,
+ TALLOC_CTX *mem_ctx, uint32_t db_id)
+{
+ int ret;
+ TDB_DATA data;
+ TDB_DATA rdata = {0};
+ int32_t cstatus = 0;
+
+ data.dptr = (uint8_t*)&db_id;
+ data.dsize = sizeof(db_id);
+
+ ret = ctdbd_control_local(conn, CTDB_CONTROL_GETDBPATH, 0, 0, data,
+ mem_ctx, &rdata, &cstatus);
+ if ((ret != 0) || cstatus != 0) {
+ DEBUG(0, (__location__ " ctdb_control for getdbpath failed: %s\n",
+ strerror(ret)));
+ TALLOC_FREE(rdata.dptr);
+ }
+
+ return (char *)rdata.dptr;
+}
+
+/*
+ * attach to a ctdb database
+ */
+int ctdbd_db_attach(struct ctdbd_connection *conn,
+ const char *name, uint32_t *db_id, bool persistent)
+{
+ int ret;
+ TDB_DATA data = {0};
+ int32_t cstatus;
+
+ data = string_term_tdb_data(name);
+
+ ret = ctdbd_control_local(conn,
+ persistent
+ ? CTDB_CONTROL_DB_ATTACH_PERSISTENT
+ : CTDB_CONTROL_DB_ATTACH,
+ 0, 0, data, NULL, &data, &cstatus);
+ if (ret != 0) {
+ DEBUG(0, (__location__ " ctdb_control for db_attach "
+ "failed: %s\n", strerror(ret)));
+ return ret;
+ }
+
+ if (cstatus != 0 || data.dsize != sizeof(uint32_t)) {
+ DEBUG(0,(__location__ " ctdb_control for db_attach failed\n"));
+ TALLOC_FREE(data.dptr);
+ return EIO;
+ }
+
+ *db_id = *(uint32_t *)data.dptr;
+ talloc_free(data.dptr);
+
+ return 0;
+}
+
+/*
+ * force the migration of a record to this node
+ */
+int ctdbd_migrate(struct ctdbd_connection *conn, uint32_t db_id, TDB_DATA key)
+{
+ struct ctdb_req_call_old req;
+ struct ctdb_req_header *hdr = NULL;
+ struct iovec iov[2];
+ ssize_t nwritten;
+ int ret;
+
+ if (ctdbd_conn_has_async_reqs(conn)) {
+ /*
+ * Can't use sync call while an async call is in flight. Adding
+ * this check as a safety net. We'll be using different
+ * connections for sync and async requests, so this shouldn't
+ * happen, but who knows...
+ */
+ DBG_ERR("Async ctdb req on sync connection\n");
+ return EINVAL;
+ }
+
+ ZERO_STRUCT(req);
+
+ req.hdr.length = offsetof(struct ctdb_req_call_old, data) + key.dsize;
+ req.hdr.ctdb_magic = CTDB_MAGIC;
+ req.hdr.ctdb_version = CTDB_PROTOCOL;
+ req.hdr.operation = CTDB_REQ_CALL;
+ req.hdr.reqid = ctdbd_next_reqid(conn);
+ req.flags = CTDB_IMMEDIATE_MIGRATION;
+ req.callid = CTDB_NULL_FUNC;
+ req.db_id = db_id;
+ req.keylen = key.dsize;
+
+ DEBUG(10, ("ctdbd_migrate: Sending ctdb packet\n"));
+ ctdb_packet_dump(&req.hdr);
+
+ iov[0].iov_base = &req;
+ iov[0].iov_len = offsetof(struct ctdb_req_call_old, data);
+ iov[1].iov_base = key.dptr;
+ iov[1].iov_len = key.dsize;
+
+ nwritten = write_data_iov(conn->fd, iov, ARRAY_SIZE(iov));
+ if (nwritten == -1) {
+ DEBUG(3, ("write_data_iov failed: %s\n", strerror(errno)));
+ cluster_fatal("cluster dispatch daemon msg write error\n");
+ }
+
+ ret = ctdb_read_req(conn, req.hdr.reqid, NULL, &hdr);
+ if (ret != 0) {
+ DEBUG(10, ("ctdb_read_req failed: %s\n", strerror(ret)));
+ goto fail;
+ }
+
+ if (hdr->operation != CTDB_REPLY_CALL) {
+ if (hdr->operation == CTDB_REPLY_ERROR) {
+ DBG_ERR("received error from ctdb\n");
+ } else {
+ DBG_ERR("received invalid reply\n");
+ }
+ ret = EIO;
+ goto fail;
+ }
+
+ fail:
+
+ TALLOC_FREE(hdr);
+ return ret;
+}
+
+/*
+ * Fetch a record and parse it
+ */
+int ctdbd_parse(struct ctdbd_connection *conn, uint32_t db_id,
+ TDB_DATA key, bool local_copy,
+ void (*parser)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data)
+{
+ struct ctdb_req_call_old req;
+ struct ctdb_req_header *hdr = NULL;
+ struct ctdb_reply_call_old *reply;
+ struct iovec iov[2];
+ ssize_t nwritten;
+ uint32_t flags;
+ int ret;
+
+ if (ctdbd_conn_has_async_reqs(conn)) {
+ /*
+ * Can't use sync call while an async call is in flight. Adding
+ * this check as a safety net. We'll be using different
+ * connections for sync and async requests, so this shouldn't
+ * happen, but who knows...
+ */
+ DBG_ERR("Async ctdb req on sync connection\n");
+ return EINVAL;
+ }
+
+ flags = local_copy ? CTDB_WANT_READONLY : 0;
+
+ ZERO_STRUCT(req);
+
+ req.hdr.length = offsetof(struct ctdb_req_call_old, data) + key.dsize;
+ req.hdr.ctdb_magic = CTDB_MAGIC;
+ req.hdr.ctdb_version = CTDB_PROTOCOL;
+ req.hdr.operation = CTDB_REQ_CALL;
+ req.hdr.reqid = ctdbd_next_reqid(conn);
+ req.flags = flags;
+ req.callid = CTDB_FETCH_FUNC;
+ req.db_id = db_id;
+ req.keylen = key.dsize;
+
+ iov[0].iov_base = &req;
+ iov[0].iov_len = offsetof(struct ctdb_req_call_old, data);
+ iov[1].iov_base = key.dptr;
+ iov[1].iov_len = key.dsize;
+
+ nwritten = write_data_iov(conn->fd, iov, ARRAY_SIZE(iov));
+ if (nwritten == -1) {
+ DEBUG(3, ("write_data_iov failed: %s\n", strerror(errno)));
+ cluster_fatal("cluster dispatch daemon msg write error\n");
+ }
+
+ ret = ctdb_read_req(conn, req.hdr.reqid, NULL, &hdr);
+ if (ret != 0) {
+ DEBUG(10, ("ctdb_read_req failed: %s\n", strerror(ret)));
+ goto fail;
+ }
+
+ if ((hdr == NULL) || (hdr->operation != CTDB_REPLY_CALL)) {
+ DEBUG(0, ("received invalid reply\n"));
+ ret = EIO;
+ goto fail;
+ }
+ reply = (struct ctdb_reply_call_old *)hdr;
+
+ if (reply->datalen == 0) {
+ /*
+ * Treat an empty record as non-existing
+ */
+ ret = ENOENT;
+ goto fail;
+ }
+
+ parser(key, make_tdb_data(&reply->data[0], reply->datalen),
+ private_data);
+
+ ret = 0;
+ fail:
+ TALLOC_FREE(hdr);
+ return ret;
+}
+
+/*
+ Traverse a ctdb database. "conn" must be an otherwise unused
+ ctdb_connection where no other messages but the traverse ones are
+ expected.
+*/
+
+int ctdbd_traverse(struct ctdbd_connection *conn, uint32_t db_id,
+ void (*fn)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data)
+{
+ int ret;
+ TDB_DATA key, data;
+ struct ctdb_traverse_start t;
+ int32_t cstatus = 0;
+
+ if (ctdbd_conn_has_async_reqs(conn)) {
+ /*
+ * Can't use sync call while an async call is in flight. Adding
+ * this check as a safety net. We'll be using different
+ * connections for sync and async requests, so this shouldn't
+ * happen, but who knows...
+ */
+ DBG_ERR("Async ctdb req on sync connection\n");
+ return EINVAL;
+ }
+
+ t.db_id = db_id;
+ t.srvid = conn->rand_srvid;
+ t.reqid = ctdbd_next_reqid(conn);
+
+ data.dptr = (uint8_t *)&t;
+ data.dsize = sizeof(t);
+
+ ret = ctdbd_control_local(conn, CTDB_CONTROL_TRAVERSE_START,
+ conn->rand_srvid,
+ 0, data, NULL, NULL, &cstatus);
+
+ if ((ret != 0) || (cstatus != 0)) {
+ DEBUG(0,("ctdbd_control failed: %s, %d\n", strerror(ret),
+ cstatus));
+
+ if (ret == 0) {
+ /*
+ * We need a mapping here
+ */
+ ret = EIO;
+ }
+ return ret;
+ }
+
+ while (true) {
+ struct ctdb_req_header *hdr = NULL;
+ struct ctdb_req_message_old *m;
+ struct ctdb_rec_data_old *d;
+
+ ret = ctdb_read_packet(conn->fd, conn->timeout, conn, &hdr);
+ if (ret != 0) {
+ DBG_ERR("ctdb_read_packet failed: %s\n", strerror(ret));
+ cluster_fatal("failed to read data from ctdbd\n");
+ }
+ SMB_ASSERT(hdr != NULL);
+
+ if (hdr->operation != CTDB_REQ_MESSAGE) {
+ DEBUG(0, ("Got operation %u, expected a message\n",
+ (unsigned)hdr->operation));
+ return EIO;
+ }
+
+ m = (struct ctdb_req_message_old *)hdr;
+ d = (struct ctdb_rec_data_old *)&m->data[0];
+ if (m->datalen < sizeof(uint32_t) || m->datalen != d->length) {
+ DEBUG(0, ("Got invalid traverse data of length %d\n",
+ (int)m->datalen));
+ return EIO;
+ }
+
+ key.dsize = d->keylen;
+ key.dptr = &d->data[0];
+ data.dsize = d->datalen;
+ data.dptr = &d->data[d->keylen];
+
+ if (key.dsize == 0 && data.dsize == 0) {
+ /* end of traverse */
+ return 0;
+ }
+
+ if (data.dsize < sizeof(struct ctdb_ltdb_header)) {
+ DEBUG(0, ("Got invalid ltdb header length %d\n",
+ (int)data.dsize));
+ return EIO;
+ }
+ data.dsize -= sizeof(struct ctdb_ltdb_header);
+ data.dptr += sizeof(struct ctdb_ltdb_header);
+
+ if (fn != NULL) {
+ fn(key, data, private_data);
+ }
+ }
+ return 0;
+}
+
+/*
+ This is used to canonicalize a ctdb_sock_addr structure.
+*/
+static void smbd_ctdb_canonicalize_ip(const struct sockaddr_storage *in,
+ struct sockaddr_storage *out)
+{
+ memcpy(out, in, sizeof (*out));
+
+#ifdef HAVE_IPV6
+ if (in->ss_family == AF_INET6) {
+ const char prefix[12] = { 0,0,0,0,0,0,0,0,0,0,0xff,0xff };
+ const struct sockaddr_in6 *in6 =
+ (const struct sockaddr_in6 *)in;
+ struct sockaddr_in *out4 = (struct sockaddr_in *)out;
+ if (memcmp(&in6->sin6_addr, prefix, 12) == 0) {
+ memset(out, 0, sizeof(*out));
+#ifdef HAVE_SOCK_SIN_LEN
+ out4->sin_len = sizeof(*out);
+#endif
+ out4->sin_family = AF_INET;
+ out4->sin_port = in6->sin6_port;
+ memcpy(&out4->sin_addr, &in6->sin6_addr.s6_addr[12], 4);
+ }
+ }
+#endif
+}
+
+/*
+ * Register us as a server for a particular tcp connection
+ */
+
+int ctdbd_register_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ struct ctdb_connection p;
+ TDB_DATA data = { .dptr = (uint8_t *)&p, .dsize = sizeof(p) };
+ int ret;
+ struct sockaddr_storage client;
+ struct sockaddr_storage server;
+
+ /*
+ * Only one connection so far
+ */
+
+ smbd_ctdb_canonicalize_ip(_client, &client);
+ smbd_ctdb_canonicalize_ip(_server, &server);
+
+ ZERO_STRUCT(p);
+ switch (client.ss_family) {
+ case AF_INET:
+ memcpy(&p.dst.ip, &server, sizeof(p.dst.ip));
+ memcpy(&p.src.ip, &client, sizeof(p.src.ip));
+ break;
+ case AF_INET6:
+ memcpy(&p.dst.ip6, &server, sizeof(p.dst.ip6));
+ memcpy(&p.src.ip6, &client, sizeof(p.src.ip6));
+ break;
+ default:
+ return EIO;
+ }
+
+ /*
+ * We want to be told about IP releases
+ */
+
+ ret = register_with_ctdbd(conn, CTDB_SRVID_RELEASE_IP,
+ cb, private_data);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /*
+ * inform ctdb of our tcp connection, so if IP takeover happens ctdb
+ * can send an extra ack to trigger a reset for our client, so it
+ * immediately reconnects
+ */
+ ret = ctdbd_control_local(conn,
+ CTDB_CONTROL_TCP_CLIENT, 0,
+ CTDB_CTRL_FLAG_NOREPLY, data, NULL, NULL,
+ NULL);
+ if (ret != 0) {
+ return ret;
+ }
+ return 0;
+}
+
+void ctdbd_unregister_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ struct ctdb_connection p;
+ TDB_DATA data = { .dptr = (uint8_t *)&p, .dsize = sizeof(p) };
+ int ret;
+ struct sockaddr_storage client;
+ struct sockaddr_storage server;
+
+ /*
+ * Only one connection so far
+ */
+
+ smbd_ctdb_canonicalize_ip(_client, &client);
+ smbd_ctdb_canonicalize_ip(_server, &server);
+
+ ZERO_STRUCT(p);
+ switch (client.ss_family) {
+ case AF_INET:
+ memcpy(&p.dst.ip, &server, sizeof(p.dst.ip));
+ memcpy(&p.src.ip, &client, sizeof(p.src.ip));
+ break;
+ case AF_INET6:
+ memcpy(&p.dst.ip6, &server, sizeof(p.dst.ip6));
+ memcpy(&p.src.ip6, &client, sizeof(p.src.ip6));
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * We no longer want to be told about IP releases
+ * for the given callback/private_data combination
+ */
+ deregister_from_ctdbd(conn, CTDB_SRVID_RELEASE_IP,
+ cb, private_data);
+
+ /*
+ * inform ctdb of our tcp connection is no longer active
+ */
+ ret = ctdbd_control_local(conn,
+ CTDB_CONTROL_TCP_CLIENT_DISCONNECTED, 0,
+ CTDB_CTRL_FLAG_NOREPLY, data, NULL, NULL,
+ NULL);
+ if (ret != 0) {
+ /*
+ * We ignore errors here, as we'll just
+ * no longer have a callback handler
+ * registered and messages may just be ignored
+ */
+ }
+
+ return;
+}
+
+void ctdbd_passed_ips(struct ctdbd_connection *conn,
+ const struct sockaddr_storage *_server,
+ const struct sockaddr_storage *_client,
+ int (*cb)(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data),
+ void *private_data)
+{
+ struct ctdb_connection p;
+ TDB_DATA data = { .dptr = (uint8_t *)&p, .dsize = sizeof(p) };
+ int ret;
+ struct sockaddr_storage client;
+ struct sockaddr_storage server;
+
+ /*
+ * Only one connection so far
+ */
+
+ smbd_ctdb_canonicalize_ip(_client, &client);
+ smbd_ctdb_canonicalize_ip(_server, &server);
+
+ ZERO_STRUCT(p);
+ switch (client.ss_family) {
+ case AF_INET:
+ memcpy(&p.dst.ip, &server, sizeof(p.dst.ip));
+ memcpy(&p.src.ip, &client, sizeof(p.src.ip));
+ break;
+ case AF_INET6:
+ memcpy(&p.dst.ip6, &server, sizeof(p.dst.ip6));
+ memcpy(&p.src.ip6, &client, sizeof(p.src.ip6));
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * We no longer want to be told about IP releases
+ * for the given callback/private_data combination
+ */
+ deregister_from_ctdbd(conn, CTDB_SRVID_RELEASE_IP,
+ cb, private_data);
+
+ /*
+ * inform ctdb of our tcp connection is now passed to
+ * another process.
+ */
+ ret = ctdbd_control_local(conn,
+ CTDB_CONTROL_TCP_CLIENT_PASSED, 0,
+ CTDB_CTRL_FLAG_NOREPLY, data, NULL, NULL,
+ NULL);
+ if (ret != 0) {
+ /*
+ * We ignore errors here, as we'll just
+ * no longer have a callback handler
+ * registered and messages may just be ignored
+ */
+ }
+
+ return;
+}
+
+static int ctdbd_control_get_public_ips(struct ctdbd_connection *conn,
+ uint32_t vnn,
+ uint32_t flags,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_list_old **_ips)
+{
+ struct ctdb_public_ip_list_old *ips = NULL;
+ TDB_DATA outdata;
+ int32_t cstatus = -1;
+ size_t min_dsize;
+ size_t max_ips;
+ int ret;
+
+ *_ips = NULL;
+
+ ret = ctdbd_control(conn,
+ vnn,
+ CTDB_CONTROL_GET_PUBLIC_IPS,
+ 0, /* srvid */
+ flags,
+ tdb_null, /* indata */
+ mem_ctx,
+ &outdata,
+ &cstatus);
+ if (ret != 0 || cstatus != 0) {
+ DBG_ERR("ctdb_control for getpublicips failed ret:%d cstatus:%d\n",
+ ret, (int)cstatus);
+ return -1;
+ }
+
+ min_dsize = offsetof(struct ctdb_public_ip_list_old, ips);
+ if (outdata.dsize < min_dsize) {
+ DBG_ERR("outdata.dsize=%zu < min_dsize=%zu\n",
+ outdata.dsize, min_dsize);
+ return -1;
+ }
+ max_ips = (outdata.dsize - min_dsize)/sizeof(struct ctdb_public_ip);
+ ips = (struct ctdb_public_ip_list_old *)outdata.dptr;
+ if ((size_t)ips->num > max_ips) {
+ DBG_ERR("ips->num=%zu > max_ips=%zu\n",
+ (size_t)ips->num, max_ips);
+ return -1;
+ }
+
+ *_ips = ips;
+ return 0;
+}
+
+static struct samba_sockaddr ctdbd_sock_addr_to_samba(const ctdb_sock_addr *c)
+{
+ struct samba_sockaddr s = {};
+
+ switch (c->sa.sa_family) {
+ case AF_INET:
+ s.u.in = c->ip;
+ break;
+ case AF_INET6:
+ /*
+ * ctdb always requires HAVE_IPV6,
+ * so we don't need an ifdef here.
+ */
+ s.u.in6 = c->ip6;
+ break;
+ default:
+ /*
+ * ctdb_sock_addr only supports ipv4 and ipv6
+ */
+ smb_panic(__location__);
+ break;
+ }
+
+ return s;
+}
+
+int ctdbd_public_ip_foreach(struct ctdbd_connection *conn,
+ int (*cb)(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ bool is_movable_ip,
+ void *private_data),
+ void *private_data)
+{
+ uint32_t i;
+ struct ctdb_public_ip_list_old *ips = NULL;
+ int ret = ENOMEM;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ret = ctdbd_control_get_public_ips(conn, CTDB_CURRENT_NODE, 0, frame, &ips);
+ if (ret < 0) {
+ ret = EIO;
+ goto out_free;
+ }
+
+ for (i=0; i < ips->num; i++) {
+ const ctdb_sock_addr *addr = &ips->ips[i].addr;
+ struct samba_sockaddr tmp = ctdbd_sock_addr_to_samba(addr);
+
+ ret = cb(ips->num,
+ &tmp.u.ss,
+ true, /* all ctdb public ips are movable */
+ private_data);
+ if (ret != 0) {
+ goto out_free;
+ }
+ }
+
+ ret = 0;
+out_free:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static int count_ips(struct db_record *rec, void *private_data)
+{
+ return 0;
+}
+
+static int collect_ips(struct db_record *rec, void *private_data)
+{
+ struct ctdb_public_ip_list_old *ips = talloc_get_type_abort(
+ private_data, struct ctdb_public_ip_list_old);
+ struct ctdb_public_ip *ip;
+ TDB_DATA val = dbwrap_record_get_value(rec);
+
+ SMB_ASSERT(val.dsize == sizeof(*ip));
+
+ ip = (struct ctdb_public_ip *)val.dptr;
+ ips->ips[ips->num] = *ip;
+ ips->num += 1;
+
+ return 0;
+}
+
+static int ctdbd_control_get_all_public_ips(struct ctdbd_connection *conn,
+ const struct ctdb_node_map_old *nodemap,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_public_ip_list_old **_ips)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint32_t ni;
+ struct ctdb_public_ip_list_old *ips = NULL;
+ struct db_context *rbt = NULL;
+ NTSTATUS status;
+ size_t len;
+ int ret;
+ int count;
+
+ rbt = db_open_rbt(frame);
+ if (rbt == NULL) {
+ DBG_WARNING("db_open_rbt() failed\n");
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ for (ni=0; ni < nodemap->num; ni++) {
+ const struct ctdb_node_and_flags *n = &nodemap->nodes[ni];
+ uint32_t j;
+
+ if (n->flags & NODE_FLAGS_INACTIVE) {
+ continue;
+ }
+
+ ret = ctdbd_control_get_public_ips(conn,
+ n->pnn,
+ 0,
+ frame,
+ &ips);
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ for (j=0; j<ips->num; j++) {
+ struct ctdb_public_ip ip;
+ TDB_DATA key;
+ TDB_DATA val;
+
+ ip.pnn = ips->ips[j].pnn;
+ ip.addr = ips->ips[j].addr;
+
+ key = make_tdb_data((uint8_t *)&ip.addr, sizeof(ip.addr));
+ val = make_tdb_data((uint8_t *)&ip, sizeof(ip));
+
+ if (n->pnn == ip.pnn) {
+ /*
+ * Node claims IP is hosted on it, so
+ * save that information
+ */
+ status = dbwrap_store(rbt, key, val,
+ TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ } else {
+ /*
+ * Node thinks IP is hosted elsewhere,
+ * so overwrite with CTDB_UNKNOWN_PNN
+ * if there's no existing entry
+ */
+ bool exists = dbwrap_exists(rbt, key);
+ if (!exists) {
+ ip.pnn = CTDB_UNKNOWN_PNN;
+ status = dbwrap_store(rbt, key, val,
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+ }
+ }
+
+ TALLOC_FREE(ips);
+ }
+
+ status = dbwrap_traverse_read(rbt, count_ips, NULL, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ len = offsetof(struct ctdb_public_ip_list_old, ips) +
+ count*sizeof(struct ctdb_public_ip);
+ ips = talloc_zero_size(mem_ctx, len);
+ if (ips == NULL) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ talloc_set_type(ips, struct ctdb_public_ip_list_old);
+ talloc_reparent(mem_ctx, frame, ips);
+
+ status = dbwrap_traverse_read(rbt, collect_ips, ips, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if ((unsigned int)count != ips->num) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ *_ips = talloc_move(mem_ctx, &ips);
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * This includes all node and/or public ips
+ * of the whole cluster.
+ *
+ * node ips have:
+ * - a valid pinned_pnn value.
+ * - current_pnn is valid if the node is healthy
+ *
+ * public ips have:
+ * - pinned_pnn as CTDB_UNKNOWN_PNN
+ * - current_pnn is valid if a node healthy and hosting this ip.
+ */
+int ctdbd_all_ip_foreach(struct ctdbd_connection *conn,
+ bool include_node_ips,
+ bool include_public_ips,
+ int (*cb)(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ uint32_t pinned_pnn,
+ uint32_t current_pnn,
+ void *private_data),
+ void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct ctdb_node_map_old *nodemap = NULL;
+ struct ctdb_public_ip_list_old *ips = NULL;
+ int ret = ENOMEM;
+ uint32_t total_ip_count = 0;
+ uint32_t i;
+
+ ret = ctdbd_control_get_nodemap(conn, frame, &nodemap);
+ if (ret != 0) {
+ DBG_WARNING("ctdbd_control_get_nodemap() failed: %s\n", strerror(ret));
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ for (i=0; include_node_ips && i < nodemap->num; i++) {
+ const struct ctdb_node_and_flags *n = &nodemap->nodes[i];
+
+ if (n->flags & NODE_FLAGS_DELETED) {
+ continue;
+ }
+
+ total_ip_count += 1;
+ }
+
+ if (include_public_ips) {
+ ret = ctdbd_control_get_all_public_ips(conn, nodemap,
+ frame, &ips);
+ if (ret < 0) {
+ ret = EIO;
+ goto out_free;
+ }
+
+ total_ip_count += ips->num;
+ }
+
+ for (i=0; include_node_ips && i < nodemap->num; i++) {
+ const struct ctdb_node_and_flags *n = &nodemap->nodes[i];
+ struct samba_sockaddr tmp = ctdbd_sock_addr_to_samba(&n->addr);
+ uint32_t pinned_pnn = n->pnn;
+ uint32_t current_pnn = n->pnn;
+
+ if (n->flags & NODE_FLAGS_DELETED) {
+ continue;
+ }
+
+ if (n->flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) {
+ /*
+ * The ip address is not available
+ * unless the node is up and
+ * healthy.
+ */
+ current_pnn = CTDB_UNKNOWN_PNN;
+ }
+
+ ret = cb(total_ip_count,
+ &tmp.u.ss,
+ pinned_pnn,
+ current_pnn,
+ private_data);
+ if (ret != 0) {
+ goto out_free;
+ }
+ }
+
+ for (i=0; include_public_ips && i < ips->num; i++) {
+ const ctdb_sock_addr *addr = &ips->ips[i].addr;
+ struct samba_sockaddr tmp = ctdbd_sock_addr_to_samba(addr);
+ /* all ctdb public ips are movable and not pinned */
+ uint32_t pinned_pnn = CTDB_UNKNOWN_PNN;
+ uint32_t current_pnn = ips->ips[i].pnn;
+ uint32_t ni;
+
+ for (ni=0; ni < nodemap->num; ni++) {
+ const struct ctdb_node_and_flags *n = &nodemap->nodes[ni];
+
+ if (n->pnn != current_pnn) {
+ continue;
+ }
+
+ if (n->flags & (NODE_FLAGS_INACTIVE|NODE_FLAGS_DISABLED)) {
+ current_pnn = CTDB_UNKNOWN_PNN;
+ }
+ break;
+ }
+
+ ret = cb(total_ip_count,
+ &tmp.u.ss,
+ pinned_pnn,
+ current_pnn,
+ private_data);
+ if (ret != 0) {
+ goto out_free;
+ }
+ }
+
+ ret = 0;
+out_free:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/*
+ call a control on the local node
+ */
+int ctdbd_control_local(struct ctdbd_connection *conn, uint32_t opcode,
+ uint64_t srvid, uint32_t flags, TDB_DATA data,
+ TALLOC_CTX *mem_ctx, TDB_DATA *outdata,
+ int32_t *cstatus)
+{
+ return ctdbd_control(conn, CTDB_CURRENT_NODE, opcode, srvid, flags, data,
+ mem_ctx, outdata, cstatus);
+}
+
+int ctdb_watch_us(struct ctdbd_connection *conn)
+{
+ struct ctdb_notify_data_old reg_data;
+ size_t struct_len;
+ int ret;
+ int32_t cstatus;
+
+ reg_data.srvid = CTDB_SRVID_SAMBA_NOTIFY;
+ reg_data.len = 1;
+ reg_data.notify_data[0] = 0;
+
+ struct_len = offsetof(struct ctdb_notify_data_old,
+ notify_data) + reg_data.len;
+
+ ret = ctdbd_control_local(
+ conn, CTDB_CONTROL_REGISTER_NOTIFY, conn->rand_srvid, 0,
+ make_tdb_data((uint8_t *)&reg_data, struct_len),
+ NULL, NULL, &cstatus);
+ if (ret != 0) {
+ DEBUG(1, ("ctdbd_control_local failed: %s\n",
+ strerror(ret)));
+ }
+ return ret;
+}
+
+int ctdb_unwatch(struct ctdbd_connection *conn)
+{
+ uint64_t srvid = CTDB_SRVID_SAMBA_NOTIFY;
+ int ret;
+ int32_t cstatus;
+
+ ret = ctdbd_control_local(
+ conn, CTDB_CONTROL_DEREGISTER_NOTIFY, conn->rand_srvid, 0,
+ make_tdb_data((uint8_t *)&srvid, sizeof(srvid)),
+ NULL, NULL, &cstatus);
+ if (ret != 0) {
+ DEBUG(1, ("ctdbd_control_local failed: %s\n",
+ strerror(ret)));
+ }
+ return ret;
+}
+
+int ctdbd_probe(const char *sockname, int timeout)
+{
+ /*
+ * Do a very early check if ctdbd is around to avoid an abort and core
+ * later
+ */
+ struct ctdbd_connection *conn = NULL;
+ int ret;
+
+ ret = ctdbd_init_connection(talloc_tos(), sockname, timeout,
+ &conn);
+
+ /*
+ * We only care if we can connect.
+ */
+ TALLOC_FREE(conn);
+
+ return ret;
+}
+
+static int ctdbd_connection_destructor(struct ctdbd_connection *c)
+{
+ if (c->fd != -1) {
+ close(c->fd);
+ c->fd = -1;
+ }
+ return 0;
+}
+
+void ctdbd_prep_hdr_next_reqid(
+ struct ctdbd_connection *conn, struct ctdb_req_header *hdr)
+{
+ *hdr = (struct ctdb_req_header) {
+ .ctdb_magic = CTDB_MAGIC,
+ .ctdb_version = CTDB_PROTOCOL,
+ .reqid = ctdbd_next_reqid(conn),
+ .destnode = CTDB_CURRENT_NODE,
+ };
+}
+
+struct ctdbd_pkt_read_state {
+ uint8_t *pkt;
+};
+
+static ssize_t ctdbd_pkt_read_more(
+ uint8_t *buf, size_t buflen, void *private_data);
+static void ctdbd_pkt_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *ctdbd_pkt_read_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, int fd)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct ctdbd_pkt_read_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct ctdbd_pkt_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = read_packet_send(state, ev, fd, 4, ctdbd_pkt_read_more, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ctdbd_pkt_read_done, req);
+ return req;
+}
+
+static ssize_t ctdbd_pkt_read_more(
+ uint8_t *buf, size_t buflen, void *private_data)
+{
+ uint32_t msglen;
+ if (buflen < 4) {
+ return -1;
+ }
+ if (buflen > 4) {
+ return 0; /* Been here, done */
+ }
+ memcpy(&msglen, buf, 4);
+
+ if (msglen < sizeof(struct ctdb_req_header)) {
+ return -1;
+ }
+ return msglen - sizeof(msglen);
+}
+
+static void ctdbd_pkt_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct ctdbd_pkt_read_state *state = tevent_req_data(
+ req, struct ctdbd_pkt_read_state);
+ ssize_t nread;
+ int err;
+
+ nread = read_packet_recv(subreq, state, &state->pkt, &err);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static int ctdbd_pkt_read_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, uint8_t **pkt)
+{
+ struct ctdbd_pkt_read_state *state = tevent_req_data(
+ req, struct ctdbd_pkt_read_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ *pkt = talloc_move(mem_ctx, &state->pkt);
+ tevent_req_received(req);
+ return 0;
+}
+
+static bool ctdbd_conn_receive_next(struct ctdbd_connection *conn);
+static void ctdbd_conn_received(struct tevent_req *subreq);
+
+struct ctdbd_req_state {
+ struct ctdbd_connection *conn;
+ struct tevent_context *ev;
+ uint32_t reqid;
+ struct ctdb_req_header *reply;
+};
+
+static void ctdbd_req_unset_pending(struct tevent_req *req)
+{
+ struct ctdbd_req_state *state = tevent_req_data(
+ req, struct ctdbd_req_state);
+ struct ctdbd_connection *conn = state->conn;
+ size_t num_pending = talloc_array_length(conn->pending);
+ size_t i, num_after;
+
+ tevent_req_set_cleanup_fn(req, NULL);
+
+ if (num_pending == 1) {
+ /*
+ * conn->read_req is a child of conn->pending
+ */
+ TALLOC_FREE(conn->pending);
+ conn->read_req = NULL;
+ return;
+ }
+
+ for (i=0; i<num_pending; i++) {
+ if (req == conn->pending[i]) {
+ break;
+ }
+ }
+ if (i == num_pending) {
+ /*
+ * Something's seriously broken. Just returning here is the
+ * right thing nevertheless, the point of this routine is to
+ * remove ourselves from conn->pending.
+ */
+ return;
+ }
+
+ num_after = num_pending - i - 1;
+ if (num_after > 0) {
+ memmove(&conn->pending[i],
+ &conn->pending[i] + 1,
+ sizeof(*conn->pending) * num_after);
+ }
+ conn->pending = talloc_realloc(
+ NULL, conn->pending, struct tevent_req *, num_pending - 1);
+}
+
+static void ctdbd_req_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ ctdbd_req_unset_pending(req);
+}
+
+static bool ctdbd_req_set_pending(struct tevent_req *req)
+{
+ struct ctdbd_req_state *state = tevent_req_data(
+ req, struct ctdbd_req_state);
+ struct ctdbd_connection *conn = state->conn;
+ struct tevent_req **pending = NULL;
+ size_t num_pending = talloc_array_length(conn->pending);
+ bool ok;
+
+ pending = talloc_realloc(
+ conn, conn->pending, struct tevent_req *, num_pending + 1);
+ if (pending == NULL) {
+ return false;
+ }
+ pending[num_pending] = req;
+ conn->pending = pending;
+
+ tevent_req_set_cleanup_fn(req, ctdbd_req_cleanup);
+
+ ok = ctdbd_conn_receive_next(conn);
+ if (!ok) {
+ ctdbd_req_unset_pending(req);
+ return false;
+ }
+
+ return true;
+}
+
+static bool ctdbd_conn_receive_next(struct ctdbd_connection *conn)
+{
+ size_t num_pending = talloc_array_length(conn->pending);
+ struct tevent_req *req = NULL;
+ struct ctdbd_req_state *state = NULL;
+
+ if (conn->read_req != NULL) {
+ return true;
+ }
+ if (num_pending == 0) {
+ /*
+ * done for now
+ */
+ return true;
+ }
+
+ req = conn->pending[0];
+ state = tevent_req_data(req, struct ctdbd_req_state);
+
+ conn->read_req = ctdbd_pkt_read_send(
+ conn->pending, state->ev, conn->fd);
+ if (conn->read_req == NULL) {
+ return false;
+ }
+ tevent_req_set_callback(conn->read_req, ctdbd_conn_received, conn);
+ return true;
+}
+
+static void ctdbd_conn_received(struct tevent_req *subreq)
+{
+ struct ctdbd_connection *conn = tevent_req_callback_data(
+ subreq, struct ctdbd_connection);
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint8_t *pkt = NULL;
+ int ret;
+ struct ctdb_req_header *hdr = NULL;
+ uint32_t reqid;
+ struct tevent_req *req = NULL;
+ struct ctdbd_req_state *state = NULL;
+ size_t i, num_pending;
+ bool ok;
+
+ SMB_ASSERT(subreq == conn->read_req);
+ conn->read_req = NULL;
+
+ ret = ctdbd_pkt_read_recv(subreq, frame, &pkt);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ cluster_fatal("ctdbd_pkt_read failed\n");
+ }
+
+ hdr = (struct ctdb_req_header *)pkt;
+ reqid = hdr->reqid;
+ num_pending = talloc_array_length(conn->pending);
+
+ for (i=0; i<num_pending; i++) {
+ req = conn->pending[i];
+ state = tevent_req_data(req, struct ctdbd_req_state);
+ if (state->reqid == reqid) {
+ break;
+ }
+ }
+
+ if (i == num_pending) {
+ /* not found */
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ state->reply = talloc_move(state, &hdr);
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_done(req);
+
+ TALLOC_FREE(frame);
+
+ ok = ctdbd_conn_receive_next(conn);
+ if (!ok) {
+ cluster_fatal("ctdbd_conn_receive_next failed\n");
+ }
+}
+
+static void ctdbd_req_written(struct tevent_req *subreq);
+
+struct tevent_req *ctdbd_req_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ struct iovec *iov,
+ size_t num_iov)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct ctdbd_req_state *state = NULL;
+ struct ctdb_req_header *hdr = NULL;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct ctdbd_req_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->conn = conn;
+ state->ev = ev;
+
+ if ((num_iov == 0) ||
+ (iov[0].iov_len < sizeof(struct ctdb_req_header))) {
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+ hdr = iov[0].iov_base;
+ state->reqid = hdr->reqid;
+
+ ok = ctdbd_req_set_pending(req);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = writev_send(
+ state, ev, conn->outgoing, conn->fd, false, iov, num_iov);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ctdbd_req_written, req);
+
+ return req;
+}
+
+static void ctdbd_req_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ ssize_t nwritten;
+ int err;
+
+ nwritten = writev_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (nwritten == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+}
+
+int ctdbd_req_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct ctdb_req_header **reply)
+{
+ struct ctdbd_req_state *state = tevent_req_data(
+ req, struct ctdbd_req_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ *reply = talloc_move(mem_ctx, &state->reply);
+ tevent_req_received(req);
+ return 0;
+}
+
+struct ctdbd_parse_state {
+ struct tevent_context *ev;
+ struct ctdbd_connection *conn;
+ uint32_t reqid;
+ TDB_DATA key;
+ uint8_t _keybuf[64];
+ struct ctdb_req_call_old ctdb_req;
+ struct iovec iov[2];
+ void (*parser)(TDB_DATA key,
+ TDB_DATA data,
+ void *private_data);
+ void *private_data;
+};
+
+static void ctdbd_parse_done(struct tevent_req *subreq);
+
+struct tevent_req *ctdbd_parse_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ uint32_t db_id,
+ TDB_DATA key,
+ bool local_copy,
+ void (*parser)(TDB_DATA key,
+ TDB_DATA data,
+ void *private_data),
+ void *private_data,
+ enum dbwrap_req_state *req_state)
+{
+ struct tevent_req *req = NULL;
+ struct ctdbd_parse_state *state = NULL;
+ uint32_t flags;
+ uint32_t packet_length;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct ctdbd_parse_state);
+ if (req == NULL) {
+ *req_state = DBWRAP_REQ_ERROR;
+ return NULL;
+ }
+
+ *req_state = DBWRAP_REQ_DISPATCHED;
+
+ *state = (struct ctdbd_parse_state) {
+ .ev = ev,
+ .conn = conn,
+ .reqid = ctdbd_next_reqid(conn),
+ .parser = parser,
+ .private_data = private_data,
+ };
+
+ flags = local_copy ? CTDB_WANT_READONLY : 0;
+ packet_length = offsetof(struct ctdb_req_call_old, data) + key.dsize;
+
+ /*
+ * Copy the key into our state, as ctdb_pkt_send_cleanup() requires that
+ * all passed iov elements have a lifetime longer that the tevent_req
+ * returned by ctdb_pkt_send_send(). This is required continue sending a
+ * the low level request into the ctdb socket, if a higher level
+ * ('this') request is canceled (or talloc free'd) by the application
+ * layer, without sending invalid packets to ctdb.
+ */
+ if (key.dsize > sizeof(state->_keybuf)) {
+ state->key.dptr = talloc_memdup(state, key.dptr, key.dsize);
+ if (tevent_req_nomem(state->key.dptr, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ memcpy(state->_keybuf, key.dptr, key.dsize);
+ state->key.dptr = state->_keybuf;
+ }
+ state->key.dsize = key.dsize;
+
+ state->ctdb_req.hdr.length = packet_length;
+ state->ctdb_req.hdr.ctdb_magic = CTDB_MAGIC;
+ state->ctdb_req.hdr.ctdb_version = CTDB_PROTOCOL;
+ state->ctdb_req.hdr.operation = CTDB_REQ_CALL;
+ state->ctdb_req.hdr.reqid = state->reqid;
+ state->ctdb_req.flags = flags;
+ state->ctdb_req.callid = CTDB_FETCH_FUNC;
+ state->ctdb_req.db_id = db_id;
+ state->ctdb_req.keylen = state->key.dsize;
+
+ state->iov[0].iov_base = &state->ctdb_req;
+ state->iov[0].iov_len = offsetof(struct ctdb_req_call_old, data);
+ state->iov[1].iov_base = state->key.dptr;
+ state->iov[1].iov_len = state->key.dsize;
+
+ subreq = ctdbd_req_send(
+ state, ev, conn, state->iov, ARRAY_SIZE(state->iov));
+ if (tevent_req_nomem(subreq, req)) {
+ *req_state = DBWRAP_REQ_ERROR;
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ctdbd_parse_done, req);
+
+ return req;
+}
+
+static void ctdbd_parse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct ctdbd_parse_state *state = tevent_req_data(
+ req, struct ctdbd_parse_state);
+ struct ctdb_req_header *hdr = NULL;
+ struct ctdb_reply_call_old *reply = NULL;
+ int ret;
+
+ ret = ctdbd_req_recv(subreq, state, &hdr);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ DBG_DEBUG("ctdb_req_recv failed %s\n", strerror(ret));
+ return;
+ }
+ SMB_ASSERT(hdr != NULL);
+
+ if (hdr->operation != CTDB_REPLY_CALL) {
+ DBG_ERR("received invalid reply\n");
+ ctdb_packet_dump(hdr);
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ reply = (struct ctdb_reply_call_old *)hdr;
+
+ if (reply->datalen == 0) {
+ /*
+ * Treat an empty record as non-existing
+ */
+ tevent_req_error(req, ENOENT);
+ return;
+ }
+
+ state->parser(state->key,
+ make_tdb_data(&reply->data[0], reply->datalen),
+ state->private_data);
+
+ tevent_req_done(req);
+ return;
+}
+
+int ctdbd_parse_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
diff --git a/source3/lib/dbwrap/dbwrap_ctdb.c b/source3/lib/dbwrap/dbwrap_ctdb.c
new file mode 100644
index 0000000..46165e8
--- /dev/null
+++ b/source3/lib/dbwrap/dbwrap_ctdb.c
@@ -0,0 +1,1985 @@
+/*
+ Unix SMB/CIFS implementation.
+ Database interface wrapper around ctdbd
+ Copyright (C) Volker Lendecke 2007-2009
+ Copyright (C) Michael Adam 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "util_tdb.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_ctdb.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "lib/param/param.h"
+
+#include "ctdb/include/ctdb_protocol.h"
+#include "ctdbd_conn.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_private.h"
+#include "dbwrap/dbwrap_ctdb.h"
+#include "g_lock.h"
+#include "messages.h"
+#include "messages_ctdb.h"
+#include "lib/cluster_support.h"
+#include "lib/util/tevent_ntstatus.h"
+
+struct db_ctdb_transaction_handle {
+ struct db_ctdb_ctx *ctx;
+ /*
+ * we store the writes done under a transaction:
+ */
+ struct ctdb_marshall_buffer *m_write;
+ uint32_t nesting;
+ bool nested_cancel;
+ char *lock_name;
+};
+
+struct db_ctdb_ctx {
+ struct db_context *db;
+ struct tdb_wrap *wtdb;
+ uint32_t db_id;
+ struct db_ctdb_transaction_handle *transaction;
+ struct g_lock_ctx *lock_ctx;
+
+ /* thresholds for warning messages */
+ int warn_unlock_msecs;
+ int warn_migrate_msecs;
+ int warn_migrate_attempts;
+ int warn_locktime_msecs;
+};
+
+struct db_ctdb_rec {
+ struct db_ctdb_ctx *ctdb_ctx;
+ struct ctdb_ltdb_header header;
+ struct timeval lock_time;
+};
+
+struct ctdb_async_ctx {
+ bool initialized;
+ struct ctdbd_connection *async_conn;
+};
+
+static struct ctdb_async_ctx ctdb_async_ctx;
+
+static int ctdb_async_ctx_init_internal(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ bool reinit)
+{
+ int ret;
+
+ if (reinit) {
+ TALLOC_FREE(ctdb_async_ctx.async_conn);
+ ctdb_async_ctx.initialized = false;
+ }
+
+ if (ctdb_async_ctx.initialized) {
+ return 0;
+ }
+
+ become_root();
+ ret = ctdbd_init_async_connection(
+ mem_ctx,
+ lp_ctdbd_socket(),
+ lp_ctdb_timeout(),
+ &ctdb_async_ctx.async_conn);
+ unbecome_root();
+
+ if (ret != 0) {
+ DBG_ERR("ctdbd_init_async_connection(%s, timeout=%d) "
+ "failed: ret=%d %s\n",
+ lp_ctdbd_socket(),
+ lp_ctdb_timeout(),
+ ret, strerror(ret));
+ return ret;
+ }
+
+ SMB_ASSERT(ctdb_async_ctx.async_conn != NULL);
+
+ ctdb_async_ctx.initialized = true;
+ return 0;
+}
+
+static int ctdb_async_ctx_init(TALLOC_CTX *mem_ctx, struct tevent_context *ev)
+{
+ return ctdb_async_ctx_init_internal(mem_ctx, ev, false);
+}
+
+int ctdb_async_ctx_reinit(TALLOC_CTX *mem_ctx, struct tevent_context *ev)
+{
+ return ctdb_async_ctx_init_internal(mem_ctx, ev, true);
+}
+
+static NTSTATUS tdb_error_to_ntstatus(struct tdb_context *tdb)
+{
+ enum TDB_ERROR tret = tdb_error(tdb);
+
+ return map_nt_error_from_tdb(tret);
+}
+
+struct db_ctdb_ltdb_parse_state {
+ void (*parser)(TDB_DATA key, struct ctdb_ltdb_header *header,
+ TDB_DATA data, void *private_data);
+ void *private_data;
+};
+
+static int db_ctdb_ltdb_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct db_ctdb_ltdb_parse_state *state =
+ (struct db_ctdb_ltdb_parse_state *)private_data;
+
+ if (data.dsize < sizeof(struct ctdb_ltdb_header)) {
+ return -1;
+ }
+
+ state->parser(
+ key, (struct ctdb_ltdb_header *)data.dptr,
+ make_tdb_data(data.dptr + sizeof(struct ctdb_ltdb_header),
+ data.dsize - sizeof(struct ctdb_ltdb_header)),
+ state->private_data);
+ return 0;
+}
+
+static NTSTATUS db_ctdb_ltdb_parse(
+ struct db_ctdb_ctx *db, TDB_DATA key,
+ void (*parser)(TDB_DATA key, struct ctdb_ltdb_header *header,
+ TDB_DATA data, void *private_data),
+ void *private_data)
+{
+ struct db_ctdb_ltdb_parse_state state;
+ int ret;
+
+ state.parser = parser;
+ state.private_data = private_data;
+
+ ret = tdb_parse_record(db->wtdb->tdb, key, db_ctdb_ltdb_parser,
+ &state);
+ if (ret == -1) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Store a record together with the ctdb record header
+ * in the local copy of the database.
+ */
+static NTSTATUS db_ctdb_ltdb_store(struct db_ctdb_ctx *db,
+ TDB_DATA key,
+ struct ctdb_ltdb_header *header,
+ const TDB_DATA *dbufs, int num_dbufs)
+{
+ TDB_DATA recs[num_dbufs+1];
+ int ret;
+
+ recs[0] = (TDB_DATA) { .dptr = (uint8_t *)header,
+ .dsize = sizeof(struct ctdb_ltdb_header) };
+ memcpy(&recs[1], dbufs, sizeof(TDB_DATA) * num_dbufs);
+
+ ret = tdb_storev(db->wtdb->tdb, key, recs, num_dbufs + 1, TDB_REPLACE);
+
+ return (ret == 0) ? NT_STATUS_OK
+ : tdb_error_to_ntstatus(db->wtdb->tdb);
+
+}
+
+/*
+ form a ctdb_rec_data record from a key/data pair
+ */
+static struct ctdb_rec_data_old *db_ctdb_marshall_record(TALLOC_CTX *mem_ctx, uint32_t reqid,
+ TDB_DATA key,
+ struct ctdb_ltdb_header *header,
+ TDB_DATA data)
+{
+ size_t length;
+ struct ctdb_rec_data_old *d;
+
+ length = offsetof(struct ctdb_rec_data_old, data) + key.dsize +
+ data.dsize + sizeof(*header);
+ d = (struct ctdb_rec_data_old *)talloc_size(mem_ctx, length);
+ if (d == NULL) {
+ return NULL;
+ }
+ d->length = length;
+ d->reqid = reqid;
+ d->keylen = key.dsize;
+ memcpy(&d->data[0], key.dptr, key.dsize);
+
+ d->datalen = data.dsize + sizeof(*header);
+ memcpy(&d->data[key.dsize], header, sizeof(*header));
+ memcpy(&d->data[key.dsize+sizeof(*header)], data.dptr, data.dsize);
+ return d;
+}
+
+
+/* helper function for marshalling multiple records */
+static struct ctdb_marshall_buffer *db_ctdb_marshall_add(TALLOC_CTX *mem_ctx,
+ struct ctdb_marshall_buffer *m,
+ uint32_t db_id,
+ uint32_t reqid,
+ TDB_DATA key,
+ struct ctdb_ltdb_header *header,
+ TDB_DATA data)
+{
+ struct ctdb_rec_data_old *r;
+ size_t m_size, r_size;
+ struct ctdb_marshall_buffer *m2 = NULL;
+
+ r = db_ctdb_marshall_record(talloc_tos(), reqid, key, header, data);
+ if (r == NULL) {
+ talloc_free(m);
+ return NULL;
+ }
+
+ if (m == NULL) {
+ m = (struct ctdb_marshall_buffer *)talloc_zero_size(
+ mem_ctx, offsetof(struct ctdb_marshall_buffer, data));
+ if (m == NULL) {
+ goto done;
+ }
+ m->db_id = db_id;
+ }
+
+ m_size = talloc_get_size(m);
+ r_size = talloc_get_size(r);
+
+ m2 = (struct ctdb_marshall_buffer *)talloc_realloc_size(
+ mem_ctx, m, m_size + r_size);
+ if (m2 == NULL) {
+ talloc_free(m);
+ goto done;
+ }
+
+ memcpy(m_size + (uint8_t *)m2, r, r_size);
+
+ m2->count++;
+
+done:
+ talloc_free(r);
+ return m2;
+}
+
+/* we've finished marshalling, return a data blob with the marshalled records */
+static TDB_DATA db_ctdb_marshall_finish(struct ctdb_marshall_buffer *m)
+{
+ TDB_DATA data;
+ data.dptr = (uint8_t *)m;
+ data.dsize = talloc_get_size(m);
+ return data;
+}
+
+/*
+ loop over a marshalling buffer
+
+ - pass r==NULL to start
+ - loop the number of times indicated by m->count
+*/
+static struct ctdb_rec_data_old *db_ctdb_marshall_loop_next_key(
+ struct ctdb_marshall_buffer *m, struct ctdb_rec_data_old *r, TDB_DATA *key)
+{
+ if (r == NULL) {
+ r = (struct ctdb_rec_data_old *)&m->data[0];
+ } else {
+ r = (struct ctdb_rec_data_old *)(r->length + (uint8_t *)r);
+ }
+
+ key->dptr = &r->data[0];
+ key->dsize = r->keylen;
+ return r;
+}
+
+static bool db_ctdb_marshall_buf_parse(
+ struct ctdb_rec_data_old *r, uint32_t *reqid,
+ struct ctdb_ltdb_header **header, TDB_DATA *data)
+{
+ if (r->datalen < sizeof(struct ctdb_ltdb_header)) {
+ return false;
+ }
+
+ *reqid = r->reqid;
+
+ data->dptr = &r->data[r->keylen] + sizeof(struct ctdb_ltdb_header);
+ data->dsize = r->datalen - sizeof(struct ctdb_ltdb_header);
+
+ *header = (struct ctdb_ltdb_header *)&r->data[r->keylen];
+
+ return true;
+}
+
+/**
+ * CTDB transaction destructor
+ */
+static int db_ctdb_transaction_destructor(struct db_ctdb_transaction_handle *h)
+{
+ NTSTATUS status;
+
+ status = g_lock_unlock(h->ctx->lock_ctx,
+ string_term_tdb_data(h->lock_name));
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("g_lock_unlock failed for %s: %s\n", h->lock_name,
+ nt_errstr(status)));
+ return -1;
+ }
+ return 0;
+}
+
+/**
+ * CTDB dbwrap API: transaction_start function
+ * starts a transaction on a persistent database
+ */
+static int db_ctdb_transaction_start(struct db_context *db)
+{
+ struct db_ctdb_transaction_handle *h;
+ NTSTATUS status;
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(db->private_data,
+ struct db_ctdb_ctx);
+
+ if (!db->persistent) {
+ DEBUG(0,("transactions not supported on non-persistent database 0x%08x\n",
+ ctx->db_id));
+ return -1;
+ }
+
+ if (ctx->transaction) {
+ ctx->transaction->nesting++;
+ DEBUG(5, (__location__ " transaction start on db 0x%08x: nesting %d -> %d\n",
+ ctx->db_id, ctx->transaction->nesting - 1, ctx->transaction->nesting));
+ return 0;
+ }
+
+ h = talloc_zero(db, struct db_ctdb_transaction_handle);
+ if (h == NULL) {
+ DEBUG(0,(__location__ " oom for transaction handle\n"));
+ return -1;
+ }
+
+ h->ctx = ctx;
+
+ h->lock_name = talloc_asprintf(h, "transaction_db_0x%08x",
+ (unsigned int)ctx->db_id);
+ if (h->lock_name == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ TALLOC_FREE(h);
+ return -1;
+ }
+
+ /*
+ * Wait a day, i.e. forever...
+ */
+ status = g_lock_lock(ctx->lock_ctx, string_term_tdb_data(h->lock_name),
+ G_LOCK_WRITE, timeval_set(86400, 0), NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("g_lock_lock failed: %s\n", nt_errstr(status)));
+ TALLOC_FREE(h);
+ return -1;
+ }
+
+ talloc_set_destructor(h, db_ctdb_transaction_destructor);
+
+ ctx->transaction = h;
+
+ DEBUG(5,(__location__ " transaction started on db 0x%08x\n", ctx->db_id));
+
+ return 0;
+}
+
+static bool parse_newest_in_marshall_buffer(
+ struct ctdb_marshall_buffer *buf, TDB_DATA key,
+ void (*parser)(TDB_DATA key, struct ctdb_ltdb_header *header,
+ TDB_DATA data, void *private_data),
+ void *private_data)
+{
+ struct ctdb_rec_data_old *rec = NULL;
+ struct ctdb_ltdb_header *h = NULL;
+ TDB_DATA data;
+ uint32_t i;
+
+ if (buf == NULL) {
+ return false;
+ }
+
+ /*
+ * Walk the list of records written during this
+ * transaction. If we want to read one we have already
+ * written, return the last written sample. Thus we do not do
+ * a "break;" for the first hit, this record might have been
+ * overwritten later.
+ */
+
+ for (i=0; i<buf->count; i++) {
+ TDB_DATA tkey;
+ uint32_t reqid;
+
+ rec = db_ctdb_marshall_loop_next_key(buf, rec, &tkey);
+ if (rec == NULL) {
+ return false;
+ }
+
+ if (!tdb_data_equal(key, tkey)) {
+ continue;
+ }
+
+ if (!db_ctdb_marshall_buf_parse(rec, &reqid, &h, &data)) {
+ return false;
+ }
+ }
+
+ if (h == NULL) {
+ return false;
+ }
+
+ parser(key, h, data, private_data);
+
+ return true;
+}
+
+struct pull_newest_from_marshall_buffer_state {
+ struct ctdb_ltdb_header *pheader;
+ TALLOC_CTX *mem_ctx;
+ TDB_DATA *pdata;
+};
+
+static void pull_newest_from_marshall_buffer_parser(
+ TDB_DATA key, struct ctdb_ltdb_header *header,
+ TDB_DATA data, void *private_data)
+{
+ struct pull_newest_from_marshall_buffer_state *state =
+ (struct pull_newest_from_marshall_buffer_state *)private_data;
+
+ if (state->pheader != NULL) {
+ memcpy(state->pheader, header, sizeof(*state->pheader));
+ }
+ if (state->pdata != NULL) {
+ state->pdata->dsize = data.dsize;
+ state->pdata->dptr = (uint8_t *)talloc_memdup(
+ state->mem_ctx, data.dptr, data.dsize);
+ }
+}
+
+static bool pull_newest_from_marshall_buffer(struct ctdb_marshall_buffer *buf,
+ TDB_DATA key,
+ struct ctdb_ltdb_header *pheader,
+ TALLOC_CTX *mem_ctx,
+ TDB_DATA *pdata)
+{
+ struct pull_newest_from_marshall_buffer_state state;
+
+ state.pheader = pheader;
+ state.mem_ctx = mem_ctx;
+ state.pdata = pdata;
+
+ if (!parse_newest_in_marshall_buffer(
+ buf, key, pull_newest_from_marshall_buffer_parser,
+ &state)) {
+ return false;
+ }
+ if ((pdata != NULL) && (pdata->dsize != 0) && (pdata->dptr == NULL)) {
+ /* ENOMEM */
+ return false;
+ }
+ return true;
+}
+
+static NTSTATUS db_ctdb_storev_transaction(struct db_record *rec,
+ const TDB_DATA *dbufs, int num_dbufs,
+ int flag);
+static NTSTATUS db_ctdb_delete_transaction(struct db_record *rec);
+
+static struct db_record *db_ctdb_fetch_locked_transaction(struct db_ctdb_ctx *ctx,
+ TALLOC_CTX *mem_ctx,
+ TDB_DATA key)
+{
+ struct db_record *result;
+ TDB_DATA ctdb_data;
+
+ if (!(result = talloc(mem_ctx, struct db_record))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ result->db = ctx->db;
+ result->private_data = ctx->transaction;
+
+ result->key.dsize = key.dsize;
+ result->key.dptr = (uint8_t *)talloc_memdup(result, key.dptr,
+ key.dsize);
+ if (result->key.dptr == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->storev = db_ctdb_storev_transaction;
+ result->delete_rec = db_ctdb_delete_transaction;
+
+ if (ctx->transaction == NULL) {
+ DEBUG(0, ("no transaction available\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ if (pull_newest_from_marshall_buffer(ctx->transaction->m_write, key,
+ NULL, result, &result->value)) {
+ result->value_valid = true;
+ return result;
+ }
+
+ ctdb_data = tdb_fetch(ctx->wtdb->tdb, key);
+ if (ctdb_data.dptr == NULL) {
+ /* create the record */
+ result->value = tdb_null;
+ result->value_valid = true;
+ return result;
+ }
+
+ result->value.dsize = ctdb_data.dsize - sizeof(struct ctdb_ltdb_header);
+ result->value.dptr = NULL;
+
+ if ((result->value.dsize != 0)
+ && !(result->value.dptr = (uint8_t *)talloc_memdup(
+ result, ctdb_data.dptr + sizeof(struct ctdb_ltdb_header),
+ result->value.dsize))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ result->value_valid = true;
+
+ SAFE_FREE(ctdb_data.dptr);
+
+ return result;
+}
+
+static int db_ctdb_record_destructor(struct db_record **recp)
+{
+ struct db_record *rec = talloc_get_type_abort(*recp, struct db_record);
+ struct db_ctdb_transaction_handle *h = talloc_get_type_abort(
+ rec->private_data, struct db_ctdb_transaction_handle);
+ int ret = h->ctx->db->transaction_commit(h->ctx->db);
+ if (ret != 0) {
+ DEBUG(0,(__location__ " transaction_commit failed\n"));
+ }
+ return 0;
+}
+
+/*
+ auto-create a transaction for persistent databases
+ */
+static struct db_record *db_ctdb_fetch_locked_persistent(struct db_ctdb_ctx *ctx,
+ TALLOC_CTX *mem_ctx,
+ TDB_DATA key)
+{
+ int res;
+ struct db_record *rec, **recp;
+
+ res = db_ctdb_transaction_start(ctx->db);
+ if (res == -1) {
+ return NULL;
+ }
+
+ rec = db_ctdb_fetch_locked_transaction(ctx, mem_ctx, key);
+ if (rec == NULL) {
+ ctx->db->transaction_cancel(ctx->db);
+ return NULL;
+ }
+
+ /* destroy this transaction when we release the lock */
+ recp = talloc(rec, struct db_record *);
+ if (recp == NULL) {
+ ctx->db->transaction_cancel(ctx->db);
+ talloc_free(rec);
+ return NULL;
+ }
+ *recp = rec;
+ talloc_set_destructor(recp, db_ctdb_record_destructor);
+ return rec;
+}
+
+
+/*
+ stores a record inside a transaction
+ */
+static NTSTATUS db_ctdb_transaction_store(struct db_ctdb_transaction_handle *h,
+ TDB_DATA key, TDB_DATA data)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(h);
+ TDB_DATA rec;
+ struct ctdb_ltdb_header header;
+
+ ZERO_STRUCT(header);
+
+ /* we need the header so we can update the RSN */
+
+ if (!pull_newest_from_marshall_buffer(h->m_write, key, &header,
+ NULL, NULL)) {
+
+ rec = tdb_fetch(h->ctx->wtdb->tdb, key);
+
+ if (rec.dptr != NULL) {
+ memcpy(&header, rec.dptr,
+ sizeof(struct ctdb_ltdb_header));
+ rec.dsize -= sizeof(struct ctdb_ltdb_header);
+
+ /*
+ * a special case, we are writing the same
+ * data that is there now
+ */
+ if (data.dsize == rec.dsize &&
+ memcmp(data.dptr,
+ rec.dptr + sizeof(struct ctdb_ltdb_header),
+ data.dsize) == 0) {
+ SAFE_FREE(rec.dptr);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+ }
+ SAFE_FREE(rec.dptr);
+ }
+
+ header.dmaster = get_my_vnn();
+ header.rsn++;
+
+ h->m_write = db_ctdb_marshall_add(h, h->m_write, h->ctx->db_id, 0, key, &header, data);
+ if (h->m_write == NULL) {
+ DEBUG(0,(__location__ " Failed to add to marshalling record\n"));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+
+/*
+ a record store inside a transaction
+ */
+static NTSTATUS db_ctdb_storev_transaction(
+ struct db_record *rec, const TDB_DATA *dbufs, int num_dbufs, int flag)
+{
+ struct db_ctdb_transaction_handle *h = talloc_get_type_abort(
+ rec->private_data, struct db_ctdb_transaction_handle);
+ NTSTATUS status;
+ TDB_DATA data = {0};
+
+ status = dbwrap_merge_dbufs(&data, rec, dbufs, num_dbufs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = db_ctdb_transaction_store(h, rec->key, data);
+
+ TALLOC_FREE(data.dptr);
+
+ return status;
+}
+
+/*
+ a record delete inside a transaction
+ */
+static NTSTATUS db_ctdb_delete_transaction(struct db_record *rec)
+{
+ struct db_ctdb_transaction_handle *h = talloc_get_type_abort(
+ rec->private_data, struct db_ctdb_transaction_handle);
+ NTSTATUS status;
+
+ status = db_ctdb_transaction_store(h, rec->key, tdb_null);
+ return status;
+}
+
+static void db_ctdb_fetch_db_seqnum_parser(
+ TDB_DATA key, struct ctdb_ltdb_header *header,
+ TDB_DATA data, void *private_data)
+{
+ uint64_t *seqnum = (uint64_t *)private_data;
+
+ if (data.dsize != sizeof(uint64_t)) {
+ *seqnum = 0;
+ return;
+ }
+ memcpy(seqnum, data.dptr, sizeof(*seqnum));
+}
+
+/**
+ * Fetch the db sequence number of a persistent db directly from the db.
+ */
+static NTSTATUS db_ctdb_fetch_db_seqnum_from_db(struct db_ctdb_ctx *db,
+ uint64_t *seqnum)
+{
+ NTSTATUS status;
+ TDB_DATA key;
+
+ if (seqnum == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ key = string_term_tdb_data(CTDB_DB_SEQNUM_KEY);
+
+ status = db_ctdb_ltdb_parse(
+ db, key, db_ctdb_fetch_db_seqnum_parser, seqnum);
+
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ *seqnum = 0;
+ return NT_STATUS_OK;
+ }
+ return status;
+}
+
+/**
+ * Store the database sequence number inside a transaction.
+ */
+static NTSTATUS db_ctdb_store_db_seqnum(struct db_ctdb_transaction_handle *h,
+ uint64_t seqnum)
+{
+ NTSTATUS status;
+ TDB_DATA key = string_term_tdb_data(CTDB_DB_SEQNUM_KEY);
+ TDB_DATA data = { .dptr=(uint8_t *)&seqnum, .dsize=sizeof(seqnum) };
+
+ status = db_ctdb_transaction_store(h, key, data);
+
+ return status;
+}
+
+/*
+ commit a transaction
+ */
+static int db_ctdb_transaction_commit(struct db_context *db)
+{
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(db->private_data,
+ struct db_ctdb_ctx);
+ NTSTATUS rets;
+ int32_t status;
+ struct db_ctdb_transaction_handle *h = ctx->transaction;
+ uint64_t old_seqnum, new_seqnum;
+ int ret;
+
+ if (h == NULL) {
+ DEBUG(0,(__location__ " transaction commit with no open transaction on db 0x%08x\n", ctx->db_id));
+ return -1;
+ }
+
+ if (h->nested_cancel) {
+ db->transaction_cancel(db);
+ DEBUG(5,(__location__ " Failed transaction commit after nested cancel\n"));
+ return -1;
+ }
+
+ if (h->nesting != 0) {
+ h->nesting--;
+ DEBUG(5, (__location__ " transaction commit on db 0x%08x: nesting %d -> %d\n",
+ ctx->db_id, ctx->transaction->nesting + 1, ctx->transaction->nesting));
+ return 0;
+ }
+
+ if (h->m_write == NULL) {
+ /*
+ * No changes were made, so don't change the seqnum,
+ * don't push to other node, just exit with success.
+ */
+ ret = 0;
+ goto done;
+ }
+
+ DEBUG(5,(__location__ " transaction commit on db 0x%08x\n", ctx->db_id));
+
+ /*
+ * As the last db action before committing, bump the database sequence
+ * number. Note that this undoes all changes to the seqnum records
+ * performed under the transaction. This record is not meant to be
+ * modified by user interaction. It is for internal use only...
+ */
+ rets = db_ctdb_fetch_db_seqnum_from_db(ctx, &old_seqnum);
+ if (!NT_STATUS_IS_OK(rets)) {
+ DEBUG(1, (__location__ " failed to fetch the db sequence number "
+ "in transaction commit on db 0x%08x\n", ctx->db_id));
+ ret = -1;
+ goto done;
+ }
+
+ new_seqnum = old_seqnum + 1;
+
+ rets = db_ctdb_store_db_seqnum(h, new_seqnum);
+ if (!NT_STATUS_IS_OK(rets)) {
+ DEBUG(1, (__location__ "failed to store the db sequence number "
+ " in transaction commit on db 0x%08x\n", ctx->db_id));
+ ret = -1;
+ goto done;
+ }
+
+again:
+ /* tell ctdbd to commit to the other nodes */
+ ret = ctdbd_control_local(messaging_ctdb_connection(),
+ CTDB_CONTROL_TRANS3_COMMIT,
+ h->ctx->db_id, 0,
+ db_ctdb_marshall_finish(h->m_write),
+ NULL, NULL, &status);
+ if ((ret != 0) || status != 0) {
+ /*
+ * The TRANS3_COMMIT control should only possibly fail when a
+ * recovery has been running concurrently. In any case, the db
+ * will be the same on all nodes, either the new copy or the
+ * old copy. This can be detected by comparing the old and new
+ * local sequence numbers.
+ */
+ rets = db_ctdb_fetch_db_seqnum_from_db(ctx, &new_seqnum);
+ if (!NT_STATUS_IS_OK(rets)) {
+ DEBUG(1, (__location__ " failed to refetch db sequence "
+ "number after failed TRANS3_COMMIT\n"));
+ ret = -1;
+ goto done;
+ }
+
+ if (new_seqnum == old_seqnum) {
+ /* Recovery prevented all our changes: retry. */
+ goto again;
+ }
+ if (new_seqnum != (old_seqnum + 1)) {
+ DEBUG(0, (__location__ " ERROR: new_seqnum[%lu] != "
+ "old_seqnum[%lu] + (0 or 1) after failed "
+ "TRANS3_COMMIT - this should not happen!\n",
+ (unsigned long)new_seqnum,
+ (unsigned long)old_seqnum));
+ ret = -1;
+ goto done;
+ }
+ /*
+ * Recovery propagated our changes to all nodes, completing
+ * our commit for us - succeed.
+ */
+ }
+
+ ret = 0;
+
+done:
+ h->ctx->transaction = NULL;
+ talloc_free(h);
+ return ret;
+}
+
+
+/*
+ cancel a transaction
+ */
+static int db_ctdb_transaction_cancel(struct db_context *db)
+{
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(db->private_data,
+ struct db_ctdb_ctx);
+ struct db_ctdb_transaction_handle *h = ctx->transaction;
+
+ if (h == NULL) {
+ DEBUG(0,(__location__ " transaction cancel with no open transaction on db 0x%08x\n", ctx->db_id));
+ return -1;
+ }
+
+ if (h->nesting != 0) {
+ h->nesting--;
+ h->nested_cancel = true;
+ DEBUG(5, (__location__ " transaction cancel on db 0x%08x: nesting %d -> %d\n",
+ ctx->db_id, ctx->transaction->nesting + 1, ctx->transaction->nesting));
+ return 0;
+ }
+
+ DEBUG(5,(__location__ " Cancel transaction on db 0x%08x\n", ctx->db_id));
+
+ ctx->transaction = NULL;
+ talloc_free(h);
+ return 0;
+}
+
+
+static NTSTATUS db_ctdb_storev(struct db_record *rec,
+ const TDB_DATA *dbufs, int num_dbufs, int flag)
+{
+ struct db_ctdb_rec *crec = talloc_get_type_abort(
+ rec->private_data, struct db_ctdb_rec);
+ NTSTATUS status;
+
+ status = db_ctdb_ltdb_store(crec->ctdb_ctx, rec->key, &(crec->header),
+ dbufs, num_dbufs);
+ return status;
+}
+
+
+
+static NTSTATUS db_ctdb_send_schedule_for_deletion(struct db_record *rec)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+ struct ctdb_control_schedule_for_deletion *dd;
+ TDB_DATA indata;
+ int32_t cstatus;
+ struct db_ctdb_rec *crec = talloc_get_type_abort(
+ rec->private_data, struct db_ctdb_rec);
+ struct db_ctdb_ctx *ctx = crec->ctdb_ctx;
+
+ indata.dsize = offsetof(struct ctdb_control_schedule_for_deletion, key) + rec->key.dsize;
+ indata.dptr = talloc_zero_array(crec, uint8_t, indata.dsize);
+ if (indata.dptr == NULL) {
+ DEBUG(0, (__location__ " talloc failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dd = (struct ctdb_control_schedule_for_deletion *)(void *)indata.dptr;
+ dd->db_id = ctx->db_id;
+ dd->hdr = crec->header;
+ dd->keylen = rec->key.dsize;
+ memcpy(dd->key, rec->key.dptr, rec->key.dsize);
+
+ ret = ctdbd_control_local(messaging_ctdb_connection(),
+ CTDB_CONTROL_SCHEDULE_FOR_DELETION,
+ crec->ctdb_ctx->db_id,
+ CTDB_CTRL_FLAG_NOREPLY, /* flags */
+ indata,
+ NULL, /* mem_ctx */
+ NULL, /* outdata */
+ &cstatus);
+ talloc_free(indata.dptr);
+
+ if ((ret != 0) || cstatus != 0) {
+ DEBUG(1, (__location__ " Error sending local control "
+ "SCHEDULE_FOR_DELETION: %s, cstatus = %"PRIi32"\n",
+ strerror(ret), cstatus));
+ if (ret != 0) {
+ status = map_nt_error_from_unix(ret);
+ } else {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ return status;
+}
+
+static NTSTATUS db_ctdb_delete(struct db_record *rec)
+{
+ NTSTATUS status;
+
+ /*
+ * We have to store the header with empty data. TODO: Fix the
+ * tdb-level cleanup
+ */
+
+ status = db_ctdb_storev(rec, &tdb_null, 1, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = db_ctdb_send_schedule_for_deletion(rec);
+ return status;
+}
+
+static int db_ctdb_record_destr(struct db_record* data)
+{
+ struct db_ctdb_rec *crec = talloc_get_type_abort(
+ data->private_data, struct db_ctdb_rec);
+ int threshold;
+ int ret;
+ struct timeval before;
+ double timediff;
+
+ DEBUG(10, (DEBUGLEVEL > 10
+ ? "Unlocking db %u key %s\n"
+ : "Unlocking db %u key %.20s\n",
+ (int)crec->ctdb_ctx->db_id,
+ hex_encode_talloc(data, (unsigned char *)data->key.dptr,
+ data->key.dsize)));
+
+ before = timeval_current();
+
+ ret = tdb_chainunlock(crec->ctdb_ctx->wtdb->tdb, data->key);
+
+ timediff = timeval_elapsed(&before);
+ timediff *= 1000; /* get us milliseconds */
+
+ if (timediff > crec->ctdb_ctx->warn_unlock_msecs) {
+ char *key;
+ key = hex_encode_talloc(talloc_tos(),
+ (unsigned char *)data->key.dptr,
+ data->key.dsize);
+ DEBUG(0, ("tdb_chainunlock on db %s, key %s took %f milliseconds\n",
+ tdb_name(crec->ctdb_ctx->wtdb->tdb), key,
+ timediff));
+ TALLOC_FREE(key);
+ }
+
+ if (ret != 0) {
+ DEBUG(0, ("tdb_chainunlock failed\n"));
+ return -1;
+ }
+
+ threshold = crec->ctdb_ctx->warn_locktime_msecs;
+ if (threshold != 0) {
+ timediff = timeval_elapsed(&crec->lock_time) * 1000;
+ if (timediff > threshold) {
+ const char *key;
+
+ key = hex_encode_talloc(data,
+ (unsigned char *)data->key.dptr,
+ data->key.dsize);
+ DEBUG(0, ("Held tdb lock on db %s, key %s "
+ "%f milliseconds\n",
+ tdb_name(crec->ctdb_ctx->wtdb->tdb),
+ key, timediff));
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Check whether we have a valid local copy of the given record,
+ * either for reading or for writing.
+ */
+static bool db_ctdb_can_use_local_hdr(const struct ctdb_ltdb_header *hdr,
+ uint32_t my_vnn, bool read_only)
+{
+ if (hdr->dmaster != my_vnn) {
+ /* If we're not dmaster, it must be r/o copy. */
+ return read_only && (hdr->flags & CTDB_REC_RO_HAVE_READONLY);
+ }
+
+ /*
+ * If we want write access, no one may have r/o copies.
+ */
+ return read_only || !(hdr->flags & CTDB_REC_RO_HAVE_DELEGATIONS);
+}
+
+static bool db_ctdb_can_use_local_copy(TDB_DATA ctdb_data, uint32_t my_vnn,
+ bool read_only)
+{
+ if (ctdb_data.dptr == NULL) {
+ return false;
+ }
+
+ if (ctdb_data.dsize < sizeof(struct ctdb_ltdb_header)) {
+ return false;
+ }
+
+ return db_ctdb_can_use_local_hdr(
+ (struct ctdb_ltdb_header *)ctdb_data.dptr, my_vnn, read_only);
+}
+
+static struct db_record *fetch_locked_internal(struct db_ctdb_ctx *ctx,
+ TALLOC_CTX *mem_ctx,
+ TDB_DATA key)
+{
+ struct db_record *result;
+ struct db_ctdb_rec *crec;
+ TDB_DATA ctdb_data;
+ int migrate_attempts;
+ struct timeval migrate_start;
+ struct timeval chainlock_start;
+ struct timeval ctdb_start_time;
+ double chainlock_time = 0;
+ double ctdb_time = 0;
+ int duration_msecs;
+ int lockret;
+ int ret;
+
+ if (!(result = talloc(mem_ctx, struct db_record))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ if (!(crec = talloc_zero(result, struct db_ctdb_rec))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->db = ctx->db;
+ result->private_data = (void *)crec;
+ crec->ctdb_ctx = ctx;
+
+ result->key.dsize = key.dsize;
+ result->key.dptr = (uint8_t *)talloc_memdup(result, key.dptr,
+ key.dsize);
+ if (result->key.dptr == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ migrate_attempts = 0;
+ GetTimeOfDay(&migrate_start);
+
+ /*
+ * Do a blocking lock on the record
+ */
+again:
+
+ if (DEBUGLEVEL >= 10) {
+ char *keystr = hex_encode_talloc(result, key.dptr, key.dsize);
+ DEBUG(10, (DEBUGLEVEL > 10
+ ? "Locking db %u key %s\n"
+ : "Locking db %u key %.20s\n",
+ (int)crec->ctdb_ctx->db_id, keystr));
+ TALLOC_FREE(keystr);
+ }
+
+ GetTimeOfDay(&chainlock_start);
+ lockret = tdb_chainlock(ctx->wtdb->tdb, key);
+ chainlock_time += timeval_elapsed(&chainlock_start);
+
+ if (lockret != 0) {
+ DEBUG(3, ("tdb_chainlock failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->storev = db_ctdb_storev;
+ result->delete_rec = db_ctdb_delete;
+ talloc_set_destructor(result, db_ctdb_record_destr);
+
+ ctdb_data = tdb_fetch(ctx->wtdb->tdb, key);
+
+ /*
+ * See if we have a valid record and we are the dmaster. If so, we can
+ * take the shortcut and just return it.
+ */
+
+ if (!db_ctdb_can_use_local_copy(ctdb_data, get_my_vnn(), false)) {
+ SAFE_FREE(ctdb_data.dptr);
+ tdb_chainunlock(ctx->wtdb->tdb, key);
+ talloc_set_destructor(result, NULL);
+
+ migrate_attempts += 1;
+
+ DEBUG(10, ("ctdb_data.dptr = %p, dmaster = %"PRIu32" "
+ "(%"PRIu32") %"PRIu32"\n",
+ ctdb_data.dptr, ctdb_data.dptr ?
+ ((struct ctdb_ltdb_header *)ctdb_data.dptr)->dmaster :
+ UINT32_MAX,
+ get_my_vnn(),
+ ctdb_data.dptr ?
+ ((struct ctdb_ltdb_header *)ctdb_data.dptr)->flags : 0));
+
+ GetTimeOfDay(&ctdb_start_time);
+ ret = ctdbd_migrate(messaging_ctdb_connection(), ctx->db_id,
+ key);
+ ctdb_time += timeval_elapsed(&ctdb_start_time);
+
+ if (ret != 0) {
+ DEBUG(5, ("ctdbd_migrate failed: %s\n",
+ strerror(ret)));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ /* now its migrated, try again */
+ goto again;
+ }
+
+ {
+ double duration;
+ duration = timeval_elapsed(&migrate_start);
+
+ /*
+ * Convert the duration to milliseconds to avoid a
+ * floating-point division of
+ * lp_parm_int("migrate_duration") by 1000.
+ */
+ duration_msecs = duration * 1000;
+ }
+
+ if ((migrate_attempts > ctx->warn_migrate_attempts) ||
+ (duration_msecs > ctx->warn_migrate_msecs)) {
+ int chain = 0;
+
+ if (tdb_get_flags(ctx->wtdb->tdb) & TDB_INCOMPATIBLE_HASH) {
+ chain = tdb_jenkins_hash(&key) %
+ tdb_hash_size(ctx->wtdb->tdb);
+ }
+
+ DEBUG(0, ("db_ctdb_fetch_locked for %s key %s, chain %d "
+ "needed %d attempts, %d milliseconds, "
+ "chainlock: %f ms, CTDB %f ms\n",
+ tdb_name(ctx->wtdb->tdb),
+ hex_encode_talloc(talloc_tos(),
+ (unsigned char *)key.dptr,
+ key.dsize),
+ chain,
+ migrate_attempts, duration_msecs,
+ chainlock_time * 1000.0,
+ ctdb_time * 1000.0));
+ }
+
+ GetTimeOfDay(&crec->lock_time);
+
+ memcpy(&crec->header, ctdb_data.dptr, sizeof(crec->header));
+
+ result->value.dsize = ctdb_data.dsize - sizeof(crec->header);
+ result->value.dptr = NULL;
+
+ if (result->value.dsize != 0) {
+ result->value.dptr = talloc_memdup(
+ result, ctdb_data.dptr + sizeof(crec->header),
+ result->value.dsize);
+ if (result->value.dptr == NULL) {
+ DBG_ERR("talloc failed\n");
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ }
+ result->value_valid = true;
+
+ SAFE_FREE(ctdb_data.dptr);
+
+ return result;
+}
+
+static struct db_record *db_ctdb_fetch_locked(struct db_context *db,
+ TALLOC_CTX *mem_ctx,
+ TDB_DATA key)
+{
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(db->private_data,
+ struct db_ctdb_ctx);
+
+ if (ctx->transaction != NULL) {
+ return db_ctdb_fetch_locked_transaction(ctx, mem_ctx, key);
+ }
+
+ if (db->persistent) {
+ return db_ctdb_fetch_locked_persistent(ctx, mem_ctx, key);
+ }
+
+ return fetch_locked_internal(ctx, mem_ctx, key);
+}
+
+struct db_ctdb_parse_record_state {
+ void (*parser)(TDB_DATA key, TDB_DATA data, void *private_data);
+ void *private_data;
+ uint32_t my_vnn;
+ bool ask_for_readonly_copy;
+ bool done;
+ bool empty_record;
+};
+
+static void db_ctdb_parse_record_parser(
+ TDB_DATA key, struct ctdb_ltdb_header *header,
+ TDB_DATA data, void *private_data)
+{
+ struct db_ctdb_parse_record_state *state =
+ (struct db_ctdb_parse_record_state *)private_data;
+ state->parser(key, data, state->private_data);
+}
+
+static void db_ctdb_parse_record_parser_nonpersistent(
+ TDB_DATA key, struct ctdb_ltdb_header *header,
+ TDB_DATA data, void *private_data)
+{
+ struct db_ctdb_parse_record_state *state =
+ (struct db_ctdb_parse_record_state *)private_data;
+
+ if (db_ctdb_can_use_local_hdr(header, state->my_vnn, true)) {
+ /*
+ * A record consisting only of the ctdb header can be
+ * a validly created empty record or a tombstone
+ * record of a deleted record (not vacuumed yet). Mark
+ * it accordingly.
+ */
+ state->empty_record = (data.dsize == 0);
+ if (!state->empty_record) {
+ state->parser(key, data, state->private_data);
+ }
+ state->done = true;
+ } else {
+ /*
+ * We found something in the db, so it seems that this record,
+ * while not usable locally right now, is popular. Ask for a
+ * R/O copy.
+ */
+ state->ask_for_readonly_copy = true;
+ }
+}
+
+static NTSTATUS db_ctdb_try_parse_local_record(struct db_ctdb_ctx *ctx,
+ TDB_DATA key,
+ struct db_ctdb_parse_record_state *state)
+{
+ NTSTATUS status;
+
+ if (ctx->transaction != NULL) {
+ struct db_ctdb_transaction_handle *h = ctx->transaction;
+ bool found;
+
+ /*
+ * Transactions only happen for persistent db's.
+ */
+
+ found = parse_newest_in_marshall_buffer(
+ h->m_write, key, db_ctdb_parse_record_parser, state);
+
+ if (found) {
+ return NT_STATUS_OK;
+ }
+ }
+
+ if (ctx->db->persistent) {
+ /*
+ * Persistent db, but not found in the transaction buffer
+ */
+ return db_ctdb_ltdb_parse(
+ ctx, key, db_ctdb_parse_record_parser, state);
+ }
+
+ state->done = false;
+ state->ask_for_readonly_copy = false;
+
+ status = db_ctdb_ltdb_parse(
+ ctx, key, db_ctdb_parse_record_parser_nonpersistent, state);
+ if (NT_STATUS_IS_OK(status) && state->done) {
+ if (state->empty_record) {
+ /*
+ * We know authoritatively, that this is an empty
+ * record. Since ctdb does not distinguish between empty
+ * and deleted records, this can be a record stored as
+ * empty or a not-yet-vacuumed tombstone record of a
+ * deleted record. Now Samba right now can live without
+ * empty records, so we can safely report this record
+ * as non-existing.
+ *
+ * See bugs 10008 and 12005.
+ */
+ return NT_STATUS_NOT_FOUND;
+ }
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS db_ctdb_parse_record(struct db_context *db, TDB_DATA key,
+ void (*parser)(TDB_DATA key,
+ TDB_DATA data,
+ void *private_data),
+ void *private_data)
+{
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_ctdb_ctx);
+ struct db_ctdb_parse_record_state state;
+ NTSTATUS status;
+ int ret;
+
+ state.parser = parser;
+ state.private_data = private_data;
+ state.my_vnn = get_my_vnn();
+ state.empty_record = false;
+
+ status = db_ctdb_try_parse_local_record(ctx, key, &state);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return status;
+ }
+
+ ret = ctdbd_parse(messaging_ctdb_connection(), ctx->db_id, key,
+ state.ask_for_readonly_copy, parser, private_data);
+ if (ret != 0) {
+ if (ret == ENOENT) {
+ /*
+ * This maps to
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND. Our upper
+ * layers expect NT_STATUS_NOT_FOUND for "no
+ * record around". We need to convert dbwrap
+ * to 0/errno away from NTSTATUS ... :-)
+ */
+ return NT_STATUS_NOT_FOUND;
+ }
+ return map_nt_error_from_unix(ret);
+ }
+ return NT_STATUS_OK;
+}
+
+static void db_ctdb_parse_record_done(struct tevent_req *subreq);
+
+static struct tevent_req *db_ctdb_parse_record_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct db_context *db,
+ TDB_DATA key,
+ void (*parser)(TDB_DATA key,
+ TDB_DATA data,
+ void *private_data),
+ void *private_data,
+ enum dbwrap_req_state *req_state)
+{
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_ctdb_ctx);
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct db_ctdb_parse_record_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct db_ctdb_parse_record_state);
+ if (req == NULL) {
+ *req_state = DBWRAP_REQ_ERROR;
+ return NULL;
+
+ }
+
+ *state = (struct db_ctdb_parse_record_state) {
+ .parser = parser,
+ .private_data = private_data,
+ .my_vnn = get_my_vnn(),
+ .empty_record = false,
+ };
+
+ status = db_ctdb_try_parse_local_record(ctx, key, state);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ if (tevent_req_nterror(req, status)) {
+ *req_state = DBWRAP_REQ_ERROR;
+ return tevent_req_post(req, ev);
+ }
+ *req_state = DBWRAP_REQ_DONE;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = ctdbd_parse_send(state,
+ ev,
+ ctdb_async_ctx.async_conn,
+ ctx->db_id,
+ key,
+ state->ask_for_readonly_copy,
+ parser,
+ private_data,
+ req_state);
+ if (tevent_req_nomem(subreq, req)) {
+ *req_state = DBWRAP_REQ_ERROR;
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, db_ctdb_parse_record_done, req);
+
+ return req;
+}
+
+static void db_ctdb_parse_record_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = ctdbd_parse_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ if (ret == ENOENT) {
+ /*
+ * This maps to NT_STATUS_OBJECT_NAME_NOT_FOUND. Our
+ * upper layers expect NT_STATUS_NOT_FOUND for "no
+ * record around". We need to convert dbwrap to 0/errno
+ * away from NTSTATUS ... :-)
+ */
+ tevent_req_nterror(req, NT_STATUS_NOT_FOUND);
+ return;
+ }
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS db_ctdb_parse_record_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct traverse_state {
+ struct db_context *db;
+ int (*fn)(struct db_record *rec, void *private_data);
+ void *private_data;
+ int count;
+};
+
+static void traverse_callback(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct traverse_state *state = (struct traverse_state *)private_data;
+ struct db_record *rec = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(state->db);
+ if (tmp_ctx == NULL) {
+ DBG_ERR("talloc_new failed\n");
+ return;
+ }
+
+ /* we have to give them a locked record to prevent races */
+ rec = db_ctdb_fetch_locked(state->db, tmp_ctx, key);
+ if (rec != NULL && rec->value.dsize > 0) {
+ state->fn(rec, state->private_data);
+ state->count++;
+ }
+ talloc_free(tmp_ctx);
+}
+
+static int traverse_persistent_callback(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf,
+ void *private_data)
+{
+ struct traverse_state *state = (struct traverse_state *)private_data;
+ struct db_record *rec;
+ TALLOC_CTX *tmp_ctx = talloc_new(state->db);
+ int ret = 0;
+
+ /*
+ * Skip the __db_sequence_number__ key:
+ * This is used for persistent transactions internally.
+ */
+ if (kbuf.dsize == strlen(CTDB_DB_SEQNUM_KEY) + 1 &&
+ strcmp((const char*)kbuf.dptr, CTDB_DB_SEQNUM_KEY) == 0)
+ {
+ goto done;
+ }
+
+ /* we have to give them a locked record to prevent races */
+ rec = db_ctdb_fetch_locked(state->db, tmp_ctx, kbuf);
+ if (rec && rec->value.dsize > 0) {
+ ret = state->fn(rec, state->private_data);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/* wrapper to use traverse_persistent_callback with dbwrap */
+static int traverse_persistent_callback_dbwrap(struct db_record *rec, void* data)
+{
+ return traverse_persistent_callback(NULL, rec->key, rec->value, data);
+}
+
+static int db_ctdbd_traverse(uint32_t db_id,
+ void (*fn)(TDB_DATA key, TDB_DATA data,
+ void *private_data),
+ void *private_data)
+{
+ struct ctdbd_connection *conn;
+ int ret;
+
+ become_root();
+ ret = ctdbd_init_connection(talloc_tos(), lp_ctdbd_socket(),
+ lp_ctdb_timeout(), &conn);
+ unbecome_root();
+ if (ret != 0) {
+ DBG_WARNING("ctdbd_init_connection failed: %s\n",
+ strerror(ret));
+ return ret;
+ }
+
+ ret = ctdbd_traverse(conn, db_id, fn, private_data);
+ TALLOC_FREE(conn);
+
+ if (ret != 0) {
+ DBG_WARNING("ctdbd_traverse failed: %s\n",
+ strerror(ret));
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static int db_ctdb_traverse(struct db_context *db,
+ int (*fn)(struct db_record *rec,
+ void *private_data),
+ void *private_data)
+{
+ int ret;
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(db->private_data,
+ struct db_ctdb_ctx);
+ struct traverse_state state;
+
+ state = (struct traverse_state) {
+ .db = db,
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ if (db->persistent) {
+ struct tdb_context *ltdb = ctx->wtdb->tdb;
+
+ /* for persistent databases we don't need to do a ctdb traverse,
+ we can do a faster local traverse */
+ ret = tdb_traverse(ltdb, traverse_persistent_callback, &state);
+ if (ret < 0) {
+ return ret;
+ }
+ if (ctx->transaction && ctx->transaction->m_write) {
+ /*
+ * we now have to handle keys not yet
+ * present at transaction start
+ */
+ struct db_context *newkeys = db_open_rbt(talloc_tos());
+ struct ctdb_marshall_buffer *mbuf = ctx->transaction->m_write;
+ struct ctdb_rec_data_old *rec=NULL;
+ uint32_t i;
+ int count = 0;
+ NTSTATUS status;
+
+ if (newkeys == NULL) {
+ return -1;
+ }
+
+ for (i=0; i<mbuf->count; i++) {
+ TDB_DATA key;
+ rec = db_ctdb_marshall_loop_next_key(
+ mbuf, rec, &key);
+ SMB_ASSERT(rec != NULL);
+
+ if (!tdb_exists(ltdb, key)) {
+ dbwrap_store(newkeys, key, tdb_null, 0);
+ }
+ }
+ status = dbwrap_traverse(newkeys,
+ traverse_persistent_callback_dbwrap,
+ &state,
+ &count);
+ talloc_free(newkeys);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ ret += count;
+ }
+ return ret;
+ }
+
+ ret = db_ctdbd_traverse(ctx->db_id, traverse_callback, &state);
+ if (ret != 0) {
+ return -1;
+ }
+ return state.count;
+}
+
+static NTSTATUS db_ctdb_storev_deny(struct db_record *rec,
+ const TDB_DATA *dbufs, int num_dbufs, int flag)
+{
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+}
+
+static NTSTATUS db_ctdb_delete_deny(struct db_record *rec)
+{
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+}
+
+static void traverse_read_callback(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct traverse_state *state = (struct traverse_state *)private_data;
+ struct db_record rec;
+
+ ZERO_STRUCT(rec);
+ rec.db = state->db;
+ rec.key = key;
+ rec.value = data;
+ rec.storev = db_ctdb_storev_deny;
+ rec.delete_rec = db_ctdb_delete_deny;
+ rec.private_data = NULL;
+ rec.value_valid = true;
+ state->fn(&rec, state->private_data);
+ state->count++;
+}
+
+static int traverse_persistent_callback_read(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf,
+ void *private_data)
+{
+ struct traverse_state *state = (struct traverse_state *)private_data;
+ struct db_record rec;
+
+ /*
+ * Skip the __db_sequence_number__ key:
+ * This is used for persistent transactions internally.
+ */
+ if (kbuf.dsize == strlen(CTDB_DB_SEQNUM_KEY) + 1 &&
+ strcmp((const char*)kbuf.dptr, CTDB_DB_SEQNUM_KEY) == 0)
+ {
+ return 0;
+ }
+
+ ZERO_STRUCT(rec);
+ rec.db = state->db;
+ rec.key = kbuf;
+ rec.value = dbuf;
+ rec.value_valid = true;
+ rec.storev = db_ctdb_storev_deny;
+ rec.delete_rec = db_ctdb_delete_deny;
+ rec.private_data = NULL;
+
+ if (rec.value.dsize <= sizeof(struct ctdb_ltdb_header)) {
+ /* a deleted record */
+ return 0;
+ }
+ rec.value.dsize -= sizeof(struct ctdb_ltdb_header);
+ rec.value.dptr += sizeof(struct ctdb_ltdb_header);
+
+ state->count++;
+ return state->fn(&rec, state->private_data);
+}
+
+static int db_ctdb_traverse_read(struct db_context *db,
+ int (*fn)(struct db_record *rec,
+ void *private_data),
+ void *private_data)
+{
+ int ret;
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(db->private_data,
+ struct db_ctdb_ctx);
+ struct traverse_state state;
+
+ state = (struct traverse_state) {
+ .db = db,
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ if (db->persistent) {
+ /* for persistent databases we don't need to do a ctdb traverse,
+ we can do a faster local traverse */
+ int nrecs;
+
+ nrecs = tdb_traverse_read(ctx->wtdb->tdb,
+ traverse_persistent_callback_read,
+ &state);
+ if (nrecs == -1) {
+ return -1;
+ }
+ return state.count;
+ }
+
+ ret = db_ctdbd_traverse(ctx->db_id, traverse_read_callback, &state);
+ if (ret != 0) {
+ return -1;
+ }
+ return state.count;
+}
+
+static int db_ctdb_get_seqnum(struct db_context *db)
+{
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(db->private_data,
+ struct db_ctdb_ctx);
+ return tdb_get_seqnum(ctx->wtdb->tdb);
+}
+
+static size_t db_ctdb_id(struct db_context *db, uint8_t *id, size_t idlen)
+{
+ struct db_ctdb_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_ctdb_ctx);
+
+ if (idlen >= sizeof(ctx->db_id)) {
+ memcpy(id, &ctx->db_id, sizeof(ctx->db_id));
+ }
+
+ return sizeof(ctx->db_id);
+}
+
+struct db_context *db_open_ctdb(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *name,
+ int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ enum dbwrap_lock_order lock_order,
+ uint64_t dbwrap_flags)
+{
+ struct db_context *result;
+ struct db_ctdb_ctx *db_ctdb;
+ char *db_path;
+ struct loadparm_context *lp_ctx;
+ TDB_DATA data;
+ TDB_DATA outdata = {0};
+ bool persistent = (tdb_flags & TDB_CLEAR_IF_FIRST) == 0;
+ int32_t cstatus;
+ int ret;
+
+ if (!lp_clustering()) {
+ DEBUG(10, ("Clustering disabled -- no ctdb\n"));
+ return NULL;
+ }
+
+ if (!(result = talloc_zero(mem_ctx, struct db_context))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ if (!(db_ctdb = talloc(result, struct db_ctdb_ctx))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->name = talloc_strdup(result, name);
+ if (result->name == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ db_ctdb->transaction = NULL;
+ db_ctdb->db = result;
+
+ ret = ctdbd_db_attach(messaging_ctdb_connection(), name,
+ &db_ctdb->db_id, persistent);
+ if (ret != 0) {
+ DEBUG(0, ("ctdbd_db_attach failed for %s: %s\n", name,
+ strerror(ret)));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ if (tdb_flags & TDB_SEQNUM) {
+ data.dptr = (uint8_t *)&db_ctdb->db_id;
+ data.dsize = sizeof(db_ctdb->db_id);
+
+ ret = ctdbd_control_local(messaging_ctdb_connection(),
+ CTDB_CONTROL_ENABLE_SEQNUM,
+ 0, 0, data,
+ NULL, NULL, &cstatus);
+ if ((ret != 0) || cstatus != 0) {
+ DBG_ERR("ctdb_control for enable seqnum "
+ "failed: %s\n", strerror(ret));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ }
+
+ db_path = ctdbd_dbpath(messaging_ctdb_connection(), db_ctdb,
+ db_ctdb->db_id);
+ if (db_path == NULL) {
+ DBG_ERR("ctdbd_dbpath failed\n");
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->persistent = persistent;
+ result->lock_order = lock_order;
+
+ data.dptr = (uint8_t *)&db_ctdb->db_id;
+ data.dsize = sizeof(db_ctdb->db_id);
+
+ ret = ctdbd_control_local(messaging_ctdb_connection(),
+ CTDB_CONTROL_DB_OPEN_FLAGS,
+ 0, 0, data, NULL, &outdata, &cstatus);
+ if (ret != 0) {
+ DBG_ERR(" ctdb control for db_open_flags "
+ "failed: %s\n", strerror(ret));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ if (cstatus != 0 || outdata.dsize != sizeof(int)) {
+ DBG_ERR("ctdb_control for db_open_flags failed\n");
+ TALLOC_FREE(outdata.dptr);
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ tdb_flags = *(int *)outdata.dptr;
+ TALLOC_FREE(outdata.dptr);
+
+ if (!result->persistent) {
+ ret = ctdb_async_ctx_init(NULL, messaging_tevent_context(msg_ctx));
+ if (ret != 0) {
+ DBG_ERR("ctdb_async_ctx_init failed: %s\n", strerror(ret));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ }
+
+ if (!result->persistent &&
+ (dbwrap_flags & DBWRAP_FLAG_OPTIMIZE_READONLY_ACCESS))
+ {
+ TDB_DATA indata;
+
+ indata = make_tdb_data((uint8_t *)&db_ctdb->db_id,
+ sizeof(db_ctdb->db_id));
+
+ ret = ctdbd_control_local(
+ messaging_ctdb_connection(),
+ CTDB_CONTROL_SET_DB_READONLY, 0, 0,
+ indata, NULL, NULL, &cstatus);
+ if ((ret != 0) || (cstatus != 0)) {
+ DEBUG(1, ("CTDB_CONTROL_SET_DB_READONLY failed: "
+ "%s, %"PRIi32"\n", strerror(ret), cstatus));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ }
+
+ lp_ctx = loadparm_init_s3(db_path, loadparm_s3_helpers());
+
+ if (hash_size == 0) {
+ hash_size = lpcfg_tdb_hash_size(lp_ctx, db_path);
+ }
+
+ db_ctdb->wtdb = tdb_wrap_open(db_ctdb, db_path, hash_size,
+ lpcfg_tdb_flags(lp_ctx, tdb_flags),
+ O_RDWR, 0);
+ talloc_unlink(db_path, lp_ctx);
+ if (db_ctdb->wtdb == NULL) {
+ DEBUG(0, ("Could not open tdb %s: %s\n", db_path, strerror(errno)));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ talloc_free(db_path);
+
+ /* honor permissions if user has specified O_CREAT */
+ if (open_flags & O_CREAT) {
+ int fd;
+ fd = tdb_fd(db_ctdb->wtdb->tdb);
+ ret = fchmod(fd, mode);
+ if (ret == -1) {
+ DBG_WARNING("fchmod failed: %s\n",
+ strerror(errno));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ }
+
+ if (result->persistent) {
+ db_ctdb->lock_ctx = g_lock_ctx_init(db_ctdb, msg_ctx);
+ if (db_ctdb->lock_ctx == NULL) {
+ DEBUG(0, ("g_lock_ctx_init failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ }
+
+ db_ctdb->warn_unlock_msecs = lp_parm_int(-1, "ctdb",
+ "unlock_warn_threshold", 5);
+ db_ctdb->warn_migrate_attempts = lp_parm_int(-1, "ctdb",
+ "migrate_attempts", 10);
+ db_ctdb->warn_migrate_msecs = lp_parm_int(-1, "ctdb",
+ "migrate_duration", 5000);
+ db_ctdb->warn_locktime_msecs = lp_ctdb_locktime_warn_threshold();
+
+ result->private_data = (void *)db_ctdb;
+ result->fetch_locked = db_ctdb_fetch_locked;
+ result->parse_record = db_ctdb_parse_record;
+ result->parse_record_send = db_ctdb_parse_record_send;
+ result->parse_record_recv = db_ctdb_parse_record_recv;
+ result->traverse = db_ctdb_traverse;
+ result->traverse_read = db_ctdb_traverse_read;
+ result->get_seqnum = db_ctdb_get_seqnum;
+ result->transaction_start = db_ctdb_transaction_start;
+ result->transaction_commit = db_ctdb_transaction_commit;
+ result->transaction_cancel = db_ctdb_transaction_cancel;
+ result->id = db_ctdb_id;
+
+ DEBUG(3,("db_open_ctdb: opened database '%s' with dbid 0x%x\n",
+ name, db_ctdb->db_id));
+
+ return result;
+}
diff --git a/source3/lib/dbwrap/dbwrap_ctdb.h b/source3/lib/dbwrap/dbwrap_ctdb.h
new file mode 100644
index 0000000..0b82479
--- /dev/null
+++ b/source3/lib/dbwrap/dbwrap_ctdb.h
@@ -0,0 +1,40 @@
+/*
+ Unix SMB/CIFS implementation.
+ Database interface wrapper around ctdbd
+ Copyright (C) Volker Lendecke 2007-2009
+ Copyright (C) Michael Adam 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __DBWRAP_CTDB_H__
+#define __DBWRAP_CTDB_H__
+
+#include <talloc.h>
+
+#include "dbwrap/dbwrap_private.h"
+
+struct db_context;
+struct ctdbd_connection;
+
+struct db_context *db_open_ctdb(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *name,
+ int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ enum dbwrap_lock_order lock_order,
+ uint64_t dbwrap_flags);
+int ctdb_async_ctx_reinit(TALLOC_CTX *mem_ctx, struct tevent_context *ev);
+
+#endif /* __DBWRAP_CTDB_H__ */
diff --git a/source3/lib/dbwrap/dbwrap_open.c b/source3/lib/dbwrap/dbwrap_open.c
new file mode 100644
index 0000000..52c8a94
--- /dev/null
+++ b/source3/lib/dbwrap/dbwrap_open.c
@@ -0,0 +1,197 @@
+/*
+ Unix SMB/CIFS implementation.
+ Database interface wrapper
+
+ Copyright (C) Volker Lendecke 2005-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_private.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_tdb.h"
+#include "dbwrap/dbwrap_ctdb.h"
+#include "lib/param/param.h"
+#include "lib/cluster_support.h"
+#include "lib/messages_ctdb.h"
+#include "util_tdb.h"
+#include "ctdbd_conn.h"
+#include "global_contexts.h"
+
+bool db_is_local(const char *name)
+{
+ const char *sockname = lp_ctdbd_socket();
+
+ if (lp_clustering() && socket_exist(sockname)) {
+ const char *partname;
+ /* ctdb only wants the file part of the name */
+ partname = strrchr(name, '/');
+ if (partname) {
+ partname++;
+ } else {
+ partname = name;
+ }
+ /* allow ctdb for individual databases to be disabled */
+ if (lp_parm_bool(-1, "ctdb", partname, True)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * open a database
+ */
+struct db_context *db_open(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ enum dbwrap_lock_order lock_order,
+ uint64_t dbwrap_flags)
+{
+ struct db_context *result = NULL;
+ const char *base;
+ struct loadparm_context *lp_ctx = NULL;
+
+ if ((lock_order != DBWRAP_LOCK_ORDER_NONE) &&
+ !DBWRAP_LOCK_ORDER_VALID(lock_order)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ base = strrchr_m(name, '/');
+ if (base != NULL) {
+ base++;
+ } else {
+ base = name;
+ }
+
+ if (tdb_flags & TDB_CLEAR_IF_FIRST) {
+ bool try_readonly = false;
+
+ if (dbwrap_flags & DBWRAP_FLAG_OPTIMIZE_READONLY_ACCESS) {
+ try_readonly = true;
+ }
+
+ try_readonly = lp_parm_bool(-1, "dbwrap_optimize_readonly", "*", try_readonly);
+ try_readonly = lp_parm_bool(-1, "dbwrap_optimize_readonly", base, try_readonly);
+
+ if (try_readonly) {
+ dbwrap_flags |= DBWRAP_FLAG_OPTIMIZE_READONLY_ACCESS;
+ } else {
+ dbwrap_flags &= ~DBWRAP_FLAG_OPTIMIZE_READONLY_ACCESS;
+ }
+ }
+
+ if (tdb_flags & TDB_CLEAR_IF_FIRST) {
+ bool try_mutex = true;
+ bool require_mutex = false;
+
+ try_mutex = lp_parm_bool(-1, "dbwrap_tdb_mutexes", "*", try_mutex);
+ try_mutex = lp_parm_bool(-1, "dbwrap_tdb_mutexes", base, try_mutex);
+
+ if (!lp_use_mmap()) {
+ /*
+ * Mutexes require mmap. "use mmap = no" can
+ * be a debugging tool, so let it override the
+ * mutex parameters
+ */
+ try_mutex = false;
+ }
+
+ if (try_mutex && tdb_runtime_check_for_robust_mutexes()) {
+ tdb_flags |= TDB_MUTEX_LOCKING;
+ }
+
+ require_mutex = lp_parm_bool(-1, "dbwrap_tdb_require_mutexes",
+ "*", require_mutex);
+ require_mutex = lp_parm_bool(-1, "dbwrap_tdb_require_mutexes",
+ base, require_mutex);
+
+ if (require_mutex) {
+ tdb_flags |= TDB_MUTEX_LOCKING;
+ }
+ }
+
+ if (lp_clustering()) {
+ const char *sockname;
+
+ sockname = lp_ctdbd_socket();
+ if (!socket_exist(sockname)) {
+ DBG_WARNING("ctdb socket does %s not exist - "
+ "is ctdb not running?\n",
+ sockname);
+ return NULL;
+ }
+
+ /* allow ctdb for individual databases to be disabled */
+ if (lp_parm_bool(-1, "ctdb", base, true)) {
+ struct messaging_context *msg_ctx;
+ struct ctdbd_connection *conn;
+
+ /*
+ * Initialize messaging before getting the ctdb
+ * connection, as the ctdb connection requires messaging
+ * to be initialized.
+ */
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ DBG_ERR("Failed to initialize messaging\n");
+ return NULL;
+ }
+
+ conn = messaging_ctdb_connection();
+ if (conn == NULL) {
+ DBG_WARNING("No ctdb connection\n");
+ errno = EIO;
+ return NULL;
+ }
+
+ result = db_open_ctdb(mem_ctx, msg_ctx, base,
+ hash_size,
+ tdb_flags, open_flags, mode,
+ lock_order, dbwrap_flags);
+ if (result == NULL) {
+ DBG_ERR("failed to attach to ctdb %s\n", base);
+ if (errno == 0) {
+ errno = EIO;
+ }
+ return NULL;
+ }
+
+ return result;
+ }
+ }
+
+ lp_ctx = loadparm_init_s3(mem_ctx, loadparm_s3_helpers());
+
+ if (hash_size == 0) {
+ hash_size = lpcfg_tdb_hash_size(lp_ctx, name);
+ }
+ tdb_flags = lpcfg_tdb_flags(lp_ctx, tdb_flags);
+
+ result = dbwrap_local_open(mem_ctx,
+ name,
+ hash_size,
+ tdb_flags,
+ open_flags,
+ mode,
+ lock_order,
+ dbwrap_flags);
+ talloc_unlink(mem_ctx, lp_ctx);
+ return result;
+}
diff --git a/source3/lib/dbwrap/dbwrap_open.h b/source3/lib/dbwrap/dbwrap_open.h
new file mode 100644
index 0000000..d14794e
--- /dev/null
+++ b/source3/lib/dbwrap/dbwrap_open.h
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+ Database interface wrapper around tdb
+
+ Copyright (C) Volker Lendecke 2005-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __DBWRAP_OPEN_H__
+#define __DBWRAP_OPEN_H__
+
+struct db_context;
+
+/**
+ * Convenience function to check whether a tdb database
+ * is local or clustered (ctdb) in a clustered environment.
+ */
+bool db_is_local(const char *name);
+
+/**
+ * Convenience function that will determine whether to
+ * open a tdb database via the tdb backend or via the ctdb
+ * backend, based on lp_clustering() and a db-specific
+ * settings.
+ */
+struct db_context *db_open(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int hash_size, int tdb_flags,
+ int open_flags, mode_t mode,
+ enum dbwrap_lock_order lock_order,
+ uint64_t dbwrap_flags);
+
+#endif /* __DBWRAP_OPEN_H__ */
diff --git a/source3/lib/dbwrap/dbwrap_watch.c b/source3/lib/dbwrap/dbwrap_watch.c
new file mode 100644
index 0000000..df93119
--- /dev/null
+++ b/source3/lib/dbwrap/dbwrap_watch.c
@@ -0,0 +1,1285 @@
+/*
+ Unix SMB/CIFS implementation.
+ Watch dbwrap record changes
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap_watch.h"
+#include "dbwrap_open.h"
+#include "lib/util/util_tdb.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "serverid.h"
+#include "server_id_watch.h"
+#include "lib/dbwrap/dbwrap_private.h"
+
+struct dbwrap_watcher {
+ /*
+ * Process watching this record
+ */
+ struct server_id pid;
+ /*
+ * Individual instance inside the waiter, incremented each
+ * time a watcher is created
+ */
+ uint64_t instance;
+};
+
+#define DBWRAP_WATCHER_BUF_LENGTH (SERVER_ID_BUF_LENGTH + sizeof(uint64_t))
+#define DBWRAP_MAX_WATCHERS (INT32_MAX/DBWRAP_WATCHER_BUF_LENGTH)
+
+/*
+ * Watched records contain a header of:
+ *
+ * [uint32] num_records
+ * 0 [DBWRAP_WATCHER_BUF_LENGTH] \
+ * 1 [DBWRAP_WATCHER_BUF_LENGTH] |
+ * .. |- Array of watchers
+ * (num_records-1)[DBWRAP_WATCHER_BUF_LENGTH] /
+ *
+ * [Remainder of record....]
+ *
+ * If this header is absent then this is a
+ * fresh record of length zero (no watchers).
+ */
+
+static bool dbwrap_watch_rec_parse(
+ TDB_DATA data,
+ uint8_t **pwatchers,
+ size_t *pnum_watchers,
+ TDB_DATA *pdata)
+{
+ size_t num_watchers;
+
+ if (data.dsize == 0) {
+ /* Fresh record */
+ if (pwatchers != NULL) {
+ *pwatchers = NULL;
+ }
+ if (pnum_watchers != NULL) {
+ *pnum_watchers = 0;
+ }
+ if (pdata != NULL) {
+ *pdata = (TDB_DATA) { .dptr = NULL };
+ }
+ return true;
+ }
+
+ if (data.dsize < sizeof(uint32_t)) {
+ /* Invalid record */
+ return false;
+ }
+
+ num_watchers = IVAL(data.dptr, 0);
+
+ data.dptr += sizeof(uint32_t);
+ data.dsize -= sizeof(uint32_t);
+
+ if (num_watchers > data.dsize/DBWRAP_WATCHER_BUF_LENGTH) {
+ /* Invalid record */
+ return false;
+ }
+
+ if (pwatchers != NULL) {
+ *pwatchers = data.dptr;
+ }
+ if (pnum_watchers != NULL) {
+ *pnum_watchers = num_watchers;
+ }
+ if (pdata != NULL) {
+ size_t watchers_len = num_watchers * DBWRAP_WATCHER_BUF_LENGTH;
+ *pdata = (TDB_DATA) {
+ .dptr = data.dptr + watchers_len,
+ .dsize = data.dsize - watchers_len
+ };
+ }
+
+ return true;
+}
+
+static void dbwrap_watcher_get(struct dbwrap_watcher *w,
+ const uint8_t buf[DBWRAP_WATCHER_BUF_LENGTH])
+{
+ server_id_get(&w->pid, buf);
+ w->instance = BVAL(buf, SERVER_ID_BUF_LENGTH);
+}
+
+static void dbwrap_watcher_put(uint8_t buf[DBWRAP_WATCHER_BUF_LENGTH],
+ const struct dbwrap_watcher *w)
+{
+ server_id_put(buf, w->pid);
+ SBVAL(buf, SERVER_ID_BUF_LENGTH, w->instance);
+}
+
+static void dbwrap_watch_log_invalid_record(
+ struct db_context *db, TDB_DATA key, TDB_DATA value)
+{
+ DBG_ERR("Found invalid record in %s\n", dbwrap_name(db));
+ dump_data(1, key.dptr, key.dsize);
+ dump_data(1, value.dptr, value.dsize);
+}
+
+struct db_watched_ctx {
+ struct db_context *backend;
+ struct messaging_context *msg;
+};
+
+struct db_watched_record {
+ struct db_record *rec;
+ struct server_id self;
+ struct {
+ struct db_record *rec;
+ TDB_DATA initial_value;
+ bool initial_valid;
+ } backend;
+ bool force_fini_store;
+ struct dbwrap_watcher added;
+ bool removed_first;
+ struct {
+ /*
+ * The is the number of watcher records
+ * parsed from backend.initial_value
+ */
+ size_t count;
+ /*
+ * This is the pointer to
+ * the optentially first watcher record
+ * parsed from backend.initial_value
+ *
+ * The pointer actually points to memory
+ * in backend.initial_value.
+ *
+ * Note it might be NULL, if count is 0.
+ */
+ uint8_t *first;
+ /*
+ * This remembers if we already
+ * notified the watchers.
+ *
+ * As we only need to do that once during:
+ * do_locked
+ * or:
+ * between rec = fetch_locked
+ * and
+ * TALLOC_FREE(rec)
+ */
+ bool alerted;
+ } watchers;
+ struct {
+ struct dbwrap_watcher watcher;
+ } wakeup;
+};
+
+static struct db_watched_record *db_record_get_watched_record(struct db_record *rec)
+{
+ /*
+ * we can't use wrec = talloc_get_type_abort() here!
+ * because wrec is likely a stack variable in
+ * dbwrap_watched_do_locked_fn()
+ *
+ * In order to have a least some protection
+ * we verify the cross reference pointers
+ * between rec and wrec
+ */
+ struct db_watched_record *wrec =
+ (struct db_watched_record *)rec->private_data;
+ SMB_ASSERT(wrec->rec == rec);
+ return wrec;
+}
+
+static NTSTATUS dbwrap_watched_record_storev(
+ struct db_watched_record *wrec,
+ const TDB_DATA *dbufs, int num_dbufs, int flags);
+static NTSTATUS dbwrap_watched_storev(struct db_record *rec,
+ const TDB_DATA *dbufs, int num_dbufs,
+ int flags);
+static NTSTATUS dbwrap_watched_delete(struct db_record *rec);
+static void dbwrap_watched_trigger_wakeup(struct messaging_context *msg_ctx,
+ struct dbwrap_watcher *watcher);
+static int db_watched_record_destructor(struct db_watched_record *wrec);
+
+static void db_watched_record_init(struct db_context *db,
+ struct messaging_context *msg_ctx,
+ struct db_record *rec,
+ struct db_watched_record *wrec,
+ struct db_record *backend_rec,
+ TDB_DATA backend_value)
+{
+ bool ok;
+
+ *rec = (struct db_record) {
+ .db = db,
+ .key = dbwrap_record_get_key(backend_rec),
+ .storev = dbwrap_watched_storev,
+ .delete_rec = dbwrap_watched_delete,
+ .private_data = wrec,
+ };
+
+ *wrec = (struct db_watched_record) {
+ .rec = rec,
+ .self = messaging_server_id(msg_ctx),
+ .backend = {
+ .rec = backend_rec,
+ .initial_value = backend_value,
+ .initial_valid = true,
+ },
+ };
+
+ ok = dbwrap_watch_rec_parse(backend_value,
+ &wrec->watchers.first,
+ &wrec->watchers.count,
+ &rec->value);
+ if (!ok) {
+ dbwrap_watch_log_invalid_record(rec->db, rec->key, backend_value);
+ /* wipe invalid data */
+ rec->value = (TDB_DATA) { .dptr = NULL, .dsize = 0 };
+ }
+}
+
+static struct db_record *dbwrap_watched_fetch_locked(
+ struct db_context *db, TALLOC_CTX *mem_ctx, TDB_DATA key)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ struct db_record *rec = NULL;
+ struct db_watched_record *wrec = NULL;
+ struct db_record *backend_rec = NULL;
+ TDB_DATA backend_value = { .dptr = NULL, };
+
+ rec = talloc_zero(mem_ctx, struct db_record);
+ if (rec == NULL) {
+ return NULL;
+ }
+ wrec = talloc_zero(rec, struct db_watched_record);
+ if (wrec == NULL) {
+ TALLOC_FREE(rec);
+ return NULL;
+ }
+
+ backend_rec = dbwrap_fetch_locked(ctx->backend, wrec, key);
+ if (backend_rec == NULL) {
+ TALLOC_FREE(rec);
+ return NULL;
+ }
+ backend_value = dbwrap_record_get_value(backend_rec);
+
+ db_watched_record_init(db, ctx->msg,
+ rec, wrec,
+ backend_rec, backend_value);
+ rec->value_valid = true;
+ talloc_set_destructor(wrec, db_watched_record_destructor);
+
+ return rec;
+}
+
+struct db_watched_record_fini_state {
+ struct db_watched_record *wrec;
+ TALLOC_CTX *frame;
+ TDB_DATA dbufs[2];
+ int num_dbufs;
+ bool ok;
+};
+
+static void db_watched_record_fini_fetcher(TDB_DATA key,
+ TDB_DATA backend_value,
+ void *private_data)
+{
+ struct db_watched_record_fini_state *state =
+ (struct db_watched_record_fini_state *)private_data;
+ struct db_watched_record *wrec = state->wrec;
+ struct db_record *rec = wrec->rec;
+ TDB_DATA value = {};
+ bool ok;
+ size_t copy_size;
+
+ /*
+ * We're within dbwrap_parse_record()
+ * and backend_value directly points into
+ * the mmap'ed tdb, so we need to copy the
+ * parts we require.
+ */
+
+ ok = dbwrap_watch_rec_parse(backend_value, NULL, NULL, &value);
+ if (!ok) {
+ struct db_context *db = dbwrap_record_get_db(rec);
+
+ dbwrap_watch_log_invalid_record(db, key, backend_value);
+
+ /* wipe invalid data */
+ value = (TDB_DATA) { .dptr = NULL, .dsize = 0 };
+ }
+
+ copy_size = MIN(rec->value.dsize, value.dsize);
+ if (copy_size != 0) {
+ /*
+ * First reuse the buffer we already had
+ * as much as we can.
+ */
+ memcpy(rec->value.dptr, value.dptr, copy_size);
+ state->dbufs[state->num_dbufs++] = rec->value;
+ value.dsize -= copy_size;
+ value.dptr += copy_size;
+ }
+
+ if (value.dsize != 0) {
+ uint8_t *p = NULL;
+
+ /*
+ * There's still new data left
+ * allocate it on callers stackframe
+ */
+ p = talloc_memdup(state->frame, value.dptr, value.dsize);
+ if (p == NULL) {
+ DBG_WARNING("failed to allocate %zu bytes\n",
+ value.dsize);
+ return;
+ }
+
+ state->dbufs[state->num_dbufs++] = (TDB_DATA) {
+ .dptr = p, .dsize = value.dsize,
+ };
+ }
+
+ state->ok = true;
+}
+
+static void db_watched_record_fini(struct db_watched_record *wrec)
+{
+ struct db_watched_record_fini_state state = { .wrec = wrec, };
+ struct db_context *backend = dbwrap_record_get_db(wrec->backend.rec);
+ struct db_record *rec = wrec->rec;
+ TDB_DATA key = dbwrap_record_get_key(wrec->backend.rec);
+ NTSTATUS status;
+
+ if (!wrec->force_fini_store) {
+ return;
+ }
+
+ if (wrec->backend.initial_valid) {
+ if (rec->value.dsize != 0) {
+ state.dbufs[state.num_dbufs++] = rec->value;
+ }
+ } else {
+ /*
+ * We need to fetch the current
+ * value from the backend again,
+ * which may need to allocate memory
+ * on the provided stackframe.
+ */
+
+ state.frame = talloc_stackframe();
+
+ status = dbwrap_parse_record(backend, key,
+ db_watched_record_fini_fetcher, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_parse_record failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(state.frame);
+ return;
+ }
+ if (!state.ok) {
+ TALLOC_FREE(state.frame);
+ return;
+ }
+ }
+
+ /*
+ * We don't want to wake up others just because
+ * we added ourself as new watcher. But if we
+ * removed outself from the first position
+ * we need to alert the next one.
+ */
+ if (!wrec->removed_first) {
+ dbwrap_watched_watch_skip_alerting(rec);
+ }
+
+ status = dbwrap_watched_record_storev(wrec, state.dbufs, state.num_dbufs, 0);
+ TALLOC_FREE(state.frame);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_watched_record_storev failed: %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ return;
+}
+
+static int db_watched_record_destructor(struct db_watched_record *wrec)
+{
+ struct db_record *rec = wrec->rec;
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ rec->db->private_data, struct db_watched_ctx);
+
+ db_watched_record_fini(wrec);
+ TALLOC_FREE(wrec->backend.rec);
+ dbwrap_watched_trigger_wakeup(ctx->msg, &wrec->wakeup.watcher);
+ return 0;
+}
+
+struct dbwrap_watched_do_locked_state {
+ struct db_context *db;
+ struct messaging_context *msg_ctx;
+ struct db_watched_record *wrec;
+ struct db_record *rec;
+ void (*fn)(struct db_record *rec,
+ TDB_DATA value,
+ void *private_data);
+ void *private_data;
+};
+
+static void dbwrap_watched_do_locked_fn(
+ struct db_record *backend_rec,
+ TDB_DATA backend_value,
+ void *private_data)
+{
+ struct dbwrap_watched_do_locked_state *state =
+ (struct dbwrap_watched_do_locked_state *)private_data;
+
+ db_watched_record_init(state->db, state->msg_ctx,
+ state->rec, state->wrec,
+ backend_rec, backend_value);
+
+ state->fn(state->rec, state->rec->value, state->private_data);
+
+ db_watched_record_fini(state->wrec);
+}
+
+static NTSTATUS dbwrap_watched_do_locked(struct db_context *db, TDB_DATA key,
+ void (*fn)(struct db_record *rec,
+ TDB_DATA value,
+ void *private_data),
+ void *private_data)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ struct db_watched_record wrec;
+ struct db_record rec;
+ struct dbwrap_watched_do_locked_state state = {
+ .db = db, .msg_ctx = ctx->msg,
+ .rec = &rec, .wrec = &wrec,
+ .fn = fn, .private_data = private_data,
+ };
+ NTSTATUS status;
+
+ status = dbwrap_do_locked(
+ ctx->backend, key, dbwrap_watched_do_locked_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_do_locked returned %s\n", nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("dbwrap_watched_do_locked_fn returned\n");
+
+ dbwrap_watched_trigger_wakeup(state.msg_ctx, &wrec.wakeup.watcher);
+
+ return NT_STATUS_OK;
+}
+
+static void dbwrap_watched_record_prepare_wakeup(
+ struct db_watched_record *wrec)
+{
+ /*
+ * Wakeup only needs to happen once (if at all)
+ */
+ if (wrec->watchers.alerted) {
+ /* already done */
+ return;
+ }
+ wrec->watchers.alerted = true;
+
+ if (wrec->watchers.count == 0) {
+ DBG_DEBUG("No watchers\n");
+ return;
+ }
+
+ while (wrec->watchers.count != 0) {
+ struct server_id_buf tmp;
+ bool exists;
+
+ dbwrap_watcher_get(&wrec->wakeup.watcher, wrec->watchers.first);
+ exists = serverid_exists(&wrec->wakeup.watcher.pid);
+ if (!exists) {
+ DBG_DEBUG("Discard non-existing waiter %s:%"PRIu64"\n",
+ server_id_str_buf(wrec->wakeup.watcher.pid, &tmp),
+ wrec->wakeup.watcher.instance);
+ wrec->watchers.first += DBWRAP_WATCHER_BUF_LENGTH;
+ wrec->watchers.count -= 1;
+ continue;
+ }
+
+ /*
+ * We will only wakeup the first waiter, via
+ * dbwrap_watched_trigger_wakeup(), but keep
+ * all (including the first one) in the list that
+ * will be flushed back to the backend record
+ * again. Waiters are removing their entries
+ * via dbwrap_watched_watch_remove_instance()
+ * when they no longer want to monitor the record.
+ */
+ DBG_DEBUG("Will alert first waiter %s:%"PRIu64"\n",
+ server_id_str_buf(wrec->wakeup.watcher.pid, &tmp),
+ wrec->wakeup.watcher.instance);
+ break;
+ }
+}
+
+static void dbwrap_watched_trigger_wakeup(struct messaging_context *msg_ctx,
+ struct dbwrap_watcher *watcher)
+{
+ struct server_id_buf tmp;
+ uint8_t instance_buf[8];
+ NTSTATUS status;
+
+ if (watcher->instance == 0) {
+ DBG_DEBUG("No one to wakeup\n");
+ return;
+ }
+
+ DBG_DEBUG("Alerting %s:%"PRIu64"\n",
+ server_id_str_buf(watcher->pid, &tmp),
+ watcher->instance);
+
+ SBVAL(instance_buf, 0, watcher->instance);
+
+ status = messaging_send_buf(
+ msg_ctx,
+ watcher->pid,
+ MSG_DBWRAP_MODIFIED,
+ instance_buf,
+ sizeof(instance_buf));
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("messaging_send_buf to %s failed: %s - ignoring...\n",
+ server_id_str_buf(watcher->pid, &tmp),
+ nt_errstr(status));
+ }
+}
+
+static NTSTATUS dbwrap_watched_record_storev(
+ struct db_watched_record *wrec,
+ const TDB_DATA *dbufs, int num_dbufs, int flags)
+{
+ uint8_t num_watchers_buf[4] = { 0 };
+ uint8_t add_buf[DBWRAP_WATCHER_BUF_LENGTH];
+ size_t num_store_watchers;
+ TDB_DATA my_dbufs[num_dbufs+3];
+ int num_my_dbufs = 0;
+ NTSTATUS status;
+ size_t add_count = 0;
+
+ dbwrap_watched_record_prepare_wakeup(wrec);
+
+ wrec->backend.initial_valid = false;
+ wrec->force_fini_store = false;
+
+ if (wrec->added.pid.pid != 0) {
+ dbwrap_watcher_put(add_buf, &wrec->added);
+ add_count = 1;
+ }
+
+ num_store_watchers = wrec->watchers.count + add_count;
+ if (num_store_watchers == 0 && num_dbufs == 0) {
+ status = dbwrap_record_delete(wrec->backend.rec);
+ return status;
+ }
+ if (num_store_watchers >= DBWRAP_MAX_WATCHERS) {
+ DBG_WARNING("Can't handle %zu watchers\n",
+ num_store_watchers);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ SIVAL(num_watchers_buf, 0, num_store_watchers);
+
+ my_dbufs[num_my_dbufs++] = (TDB_DATA) {
+ .dptr = num_watchers_buf, .dsize = sizeof(num_watchers_buf),
+ };
+ if (wrec->watchers.count != 0) {
+ my_dbufs[num_my_dbufs++] = (TDB_DATA) {
+ .dptr = wrec->watchers.first, .dsize = wrec->watchers.count * DBWRAP_WATCHER_BUF_LENGTH,
+ };
+ }
+ if (add_count != 0) {
+ my_dbufs[num_my_dbufs++] = (TDB_DATA) {
+ .dptr = add_buf,
+ .dsize = sizeof(add_buf),
+ };
+ }
+ if (num_dbufs != 0) {
+ memcpy(my_dbufs+num_my_dbufs, dbufs, num_dbufs * sizeof(*dbufs));
+ num_my_dbufs += num_dbufs;
+ }
+
+ SMB_ASSERT(num_my_dbufs <= ARRAY_SIZE(my_dbufs));
+
+ status = dbwrap_record_storev(
+ wrec->backend.rec, my_dbufs, num_my_dbufs, flags);
+ return status;
+}
+
+static NTSTATUS dbwrap_watched_storev(struct db_record *rec,
+ const TDB_DATA *dbufs, int num_dbufs,
+ int flags)
+{
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+
+ return dbwrap_watched_record_storev(wrec, dbufs, num_dbufs, flags);
+}
+
+static NTSTATUS dbwrap_watched_delete(struct db_record *rec)
+{
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+
+ /*
+ * dbwrap_watched_record_storev() will figure out
+ * if the record should be deleted or if there are still
+ * watchers to be stored.
+ */
+ return dbwrap_watched_record_storev(wrec, NULL, 0, 0);
+}
+
+struct dbwrap_watched_traverse_state {
+ int (*fn)(struct db_record *rec, void *private_data);
+ void *private_data;
+};
+
+static int dbwrap_watched_traverse_fn(struct db_record *rec,
+ void *private_data)
+{
+ struct dbwrap_watched_traverse_state *state = private_data;
+ struct db_record prec = *rec;
+ bool ok;
+
+ ok = dbwrap_watch_rec_parse(rec->value, NULL, NULL, &prec.value);
+ if (!ok) {
+ return 0;
+ }
+ if (prec.value.dsize == 0) {
+ return 0;
+ }
+ prec.value_valid = true;
+
+ return state->fn(&prec, state->private_data);
+}
+
+static int dbwrap_watched_traverse(struct db_context *db,
+ int (*fn)(struct db_record *rec,
+ void *private_data),
+ void *private_data)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ struct dbwrap_watched_traverse_state state = {
+ .fn = fn, .private_data = private_data };
+ NTSTATUS status;
+ int ret;
+
+ status = dbwrap_traverse(
+ ctx->backend, dbwrap_watched_traverse_fn, &state, &ret);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ return ret;
+}
+
+static int dbwrap_watched_traverse_read(struct db_context *db,
+ int (*fn)(struct db_record *rec,
+ void *private_data),
+ void *private_data)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ struct dbwrap_watched_traverse_state state = {
+ .fn = fn, .private_data = private_data };
+ NTSTATUS status;
+ int ret;
+
+ status = dbwrap_traverse_read(
+ ctx->backend, dbwrap_watched_traverse_fn, &state, &ret);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ return ret;
+}
+
+static int dbwrap_watched_get_seqnum(struct db_context *db)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ return dbwrap_get_seqnum(ctx->backend);
+}
+
+static int dbwrap_watched_transaction_start(struct db_context *db)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ return dbwrap_transaction_start(ctx->backend);
+}
+
+static int dbwrap_watched_transaction_commit(struct db_context *db)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ return dbwrap_transaction_commit(ctx->backend);
+}
+
+static int dbwrap_watched_transaction_cancel(struct db_context *db)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ return dbwrap_transaction_cancel(ctx->backend);
+}
+
+struct dbwrap_watched_parse_record_state {
+ struct db_context *db;
+ void (*parser)(TDB_DATA key, TDB_DATA data, void *private_data);
+ void *private_data;
+ bool ok;
+};
+
+static void dbwrap_watched_parse_record_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct dbwrap_watched_parse_record_state *state = private_data;
+ TDB_DATA userdata;
+
+ state->ok = dbwrap_watch_rec_parse(data, NULL, NULL, &userdata);
+ if (!state->ok) {
+ dbwrap_watch_log_invalid_record(state->db, key, data);
+ return;
+ }
+
+ state->parser(key, userdata, state->private_data);
+}
+
+static NTSTATUS dbwrap_watched_parse_record(
+ struct db_context *db, TDB_DATA key,
+ void (*parser)(TDB_DATA key, TDB_DATA data, void *private_data),
+ void *private_data)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ struct dbwrap_watched_parse_record_state state = {
+ .db = db,
+ .parser = parser,
+ .private_data = private_data,
+ };
+ NTSTATUS status;
+
+ status = dbwrap_parse_record(
+ ctx->backend, key, dbwrap_watched_parse_record_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!state.ok) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ return NT_STATUS_OK;
+}
+
+static void dbwrap_watched_parse_record_done(struct tevent_req *subreq);
+
+static struct tevent_req *dbwrap_watched_parse_record_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct db_context *db,
+ TDB_DATA key,
+ void (*parser)(TDB_DATA key, TDB_DATA data, void *private_data),
+ void *private_data,
+ enum dbwrap_req_state *req_state)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct dbwrap_watched_parse_record_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dbwrap_watched_parse_record_state);
+ if (req == NULL) {
+ *req_state = DBWRAP_REQ_ERROR;
+ return NULL;
+ }
+
+ *state = (struct dbwrap_watched_parse_record_state) {
+ .parser = parser,
+ .private_data = private_data,
+ .ok = true,
+ };
+
+ subreq = dbwrap_parse_record_send(state,
+ ev,
+ ctx->backend,
+ key,
+ dbwrap_watched_parse_record_parser,
+ state,
+ req_state);
+ if (tevent_req_nomem(subreq, req)) {
+ *req_state = DBWRAP_REQ_ERROR;
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, dbwrap_watched_parse_record_done, req);
+ return req;
+}
+
+static void dbwrap_watched_parse_record_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct dbwrap_watched_parse_record_state *state = tevent_req_data(
+ req, struct dbwrap_watched_parse_record_state);
+ NTSTATUS status;
+
+ status = dbwrap_parse_record_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (!state->ok) {
+ tevent_req_nterror(req, NT_STATUS_NOT_FOUND);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS dbwrap_watched_parse_record_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static int dbwrap_watched_exists(struct db_context *db, TDB_DATA key)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+
+ return dbwrap_exists(ctx->backend, key);
+}
+
+static size_t dbwrap_watched_id(struct db_context *db, uint8_t *id,
+ size_t idlen)
+{
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+
+ return dbwrap_db_id(ctx->backend, id, idlen);
+}
+
+struct db_context *db_open_watched(TALLOC_CTX *mem_ctx,
+ struct db_context **backend,
+ struct messaging_context *msg)
+{
+ struct db_context *db;
+ struct db_watched_ctx *ctx;
+
+ db = talloc_zero(mem_ctx, struct db_context);
+ if (db == NULL) {
+ return NULL;
+ }
+ ctx = talloc_zero(db, struct db_watched_ctx);
+ if (ctx == NULL) {
+ TALLOC_FREE(db);
+ return NULL;
+ }
+ db->private_data = ctx;
+
+ ctx->msg = msg;
+
+ ctx->backend = talloc_move(ctx, backend);
+ db->lock_order = ctx->backend->lock_order;
+ ctx->backend->lock_order = DBWRAP_LOCK_ORDER_NONE;
+
+ db->fetch_locked = dbwrap_watched_fetch_locked;
+ db->do_locked = dbwrap_watched_do_locked;
+ db->traverse = dbwrap_watched_traverse;
+ db->traverse_read = dbwrap_watched_traverse_read;
+ db->get_seqnum = dbwrap_watched_get_seqnum;
+ db->transaction_start = dbwrap_watched_transaction_start;
+ db->transaction_commit = dbwrap_watched_transaction_commit;
+ db->transaction_cancel = dbwrap_watched_transaction_cancel;
+ db->parse_record = dbwrap_watched_parse_record;
+ db->parse_record_send = dbwrap_watched_parse_record_send;
+ db->parse_record_recv = dbwrap_watched_parse_record_recv;
+ db->exists = dbwrap_watched_exists;
+ db->id = dbwrap_watched_id;
+ db->name = dbwrap_name(ctx->backend);
+
+ return db;
+}
+
+uint64_t dbwrap_watched_watch_add_instance(struct db_record *rec)
+{
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+ static uint64_t global_instance = 1;
+
+ SMB_ASSERT(wrec->added.instance == 0);
+
+ wrec->added = (struct dbwrap_watcher) {
+ .pid = wrec->self,
+ .instance = global_instance++,
+ };
+
+ wrec->force_fini_store = true;
+
+ return wrec->added.instance;
+}
+
+void dbwrap_watched_watch_remove_instance(struct db_record *rec, uint64_t instance)
+{
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+ struct dbwrap_watcher clear_watcher = {
+ .pid = wrec->self,
+ .instance = instance,
+ };
+ size_t i;
+ struct server_id_buf buf;
+
+ if (instance == 0) {
+ return;
+ }
+
+ if (wrec->added.instance == instance) {
+ SMB_ASSERT(server_id_equal(&wrec->added.pid, &wrec->self));
+ DBG_DEBUG("Watcher %s:%"PRIu64" reverted from adding\n",
+ server_id_str_buf(clear_watcher.pid, &buf),
+ clear_watcher.instance);
+ ZERO_STRUCT(wrec->added);
+ }
+
+ for (i=0; i < wrec->watchers.count; i++) {
+ struct dbwrap_watcher watcher;
+ size_t off = i*DBWRAP_WATCHER_BUF_LENGTH;
+ size_t next_off;
+ size_t full_len;
+ size_t move_len;
+
+ dbwrap_watcher_get(&watcher, wrec->watchers.first + off);
+
+ if (clear_watcher.instance != watcher.instance) {
+ continue;
+ }
+ if (!server_id_equal(&clear_watcher.pid, &watcher.pid)) {
+ continue;
+ }
+
+ wrec->force_fini_store = true;
+
+ if (i == 0) {
+ DBG_DEBUG("Watcher %s:%"PRIu64" removed from first position of %zu\n",
+ server_id_str_buf(clear_watcher.pid, &buf),
+ clear_watcher.instance,
+ wrec->watchers.count);
+ wrec->watchers.first += DBWRAP_WATCHER_BUF_LENGTH;
+ wrec->watchers.count -= 1;
+ wrec->removed_first = true;
+ return;
+ }
+ if (i == (wrec->watchers.count-1)) {
+ DBG_DEBUG("Watcher %s:%"PRIu64" removed from last position of %zu\n",
+ server_id_str_buf(clear_watcher.pid, &buf),
+ clear_watcher.instance,
+ wrec->watchers.count);
+ wrec->watchers.count -= 1;
+ return;
+ }
+
+ DBG_DEBUG("Watcher %s:%"PRIu64" cleared at position %zu from %zu\n",
+ server_id_str_buf(clear_watcher.pid, &buf),
+ clear_watcher.instance, i+1,
+ wrec->watchers.count);
+
+ next_off = off + DBWRAP_WATCHER_BUF_LENGTH;
+ full_len = wrec->watchers.count * DBWRAP_WATCHER_BUF_LENGTH;
+ move_len = full_len - next_off;
+ memmove(wrec->watchers.first + off,
+ wrec->watchers.first + next_off,
+ move_len);
+ wrec->watchers.count -= 1;
+ return;
+ }
+
+ DBG_DEBUG("Watcher %s:%"PRIu64" not found in %zu watchers\n",
+ server_id_str_buf(clear_watcher.pid, &buf),
+ clear_watcher.instance,
+ wrec->watchers.count);
+ return;
+}
+
+void dbwrap_watched_watch_skip_alerting(struct db_record *rec)
+{
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+
+ wrec->wakeup.watcher = (struct dbwrap_watcher) { .instance = 0, };
+ wrec->watchers.alerted = true;
+}
+
+void dbwrap_watched_watch_reset_alerting(struct db_record *rec)
+{
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+
+ wrec->wakeup.watcher = (struct dbwrap_watcher) { .instance = 0, };
+ wrec->watchers.alerted = false;
+}
+
+void dbwrap_watched_watch_force_alerting(struct db_record *rec)
+{
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+
+ dbwrap_watched_record_prepare_wakeup(wrec);
+}
+
+struct dbwrap_watched_watch_state {
+ struct db_context *db;
+ TDB_DATA key;
+ struct dbwrap_watcher watcher;
+ struct server_id blocker;
+ bool blockerdead;
+};
+
+static bool dbwrap_watched_msg_filter(struct messaging_rec *rec,
+ void *private_data);
+static void dbwrap_watched_watch_done(struct tevent_req *subreq);
+static void dbwrap_watched_watch_blocker_died(struct tevent_req *subreq);
+static int dbwrap_watched_watch_state_destructor(
+ struct dbwrap_watched_watch_state *state);
+
+struct tevent_req *dbwrap_watched_watch_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct db_record *rec,
+ uint64_t resumed_instance,
+ struct server_id blocker)
+{
+ struct db_context *db = dbwrap_record_get_db(rec);
+ struct db_watched_ctx *ctx = talloc_get_type_abort(
+ db->private_data, struct db_watched_ctx);
+ struct db_watched_record *wrec = db_record_get_watched_record(rec);
+ struct tevent_req *req, *subreq;
+ struct dbwrap_watched_watch_state *state;
+ uint64_t instance;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dbwrap_watched_watch_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->db = db;
+ state->blocker = blocker;
+
+ if (ctx->msg == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (resumed_instance == 0 && wrec->added.instance == 0) {
+ /*
+ * Adding a new instance
+ */
+ instance = dbwrap_watched_watch_add_instance(rec);
+ } else if (resumed_instance != 0 && wrec->added.instance == 0) {
+ /*
+ * Resuming an existing instance that was
+ * already present before do_locked started
+ */
+ instance = resumed_instance;
+ } else if (resumed_instance == wrec->added.instance) {
+ /*
+ * The caller used dbwrap_watched_watch_add_instance()
+ * already during this do_locked() invocation.
+ */
+ instance = resumed_instance;
+ } else {
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ state->watcher = (struct dbwrap_watcher) {
+ .pid = messaging_server_id(ctx->msg),
+ .instance = instance,
+ };
+
+ state->key = tdb_data_talloc_copy(state, rec->key);
+ if (tevent_req_nomem(state->key.dptr, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = messaging_filtered_read_send(
+ state, ev, ctx->msg, dbwrap_watched_msg_filter, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, dbwrap_watched_watch_done, req);
+
+ talloc_set_destructor(state, dbwrap_watched_watch_state_destructor);
+
+ if (blocker.pid != 0) {
+ subreq = server_id_watch_send(state, ev, blocker);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, dbwrap_watched_watch_blocker_died, req);
+ }
+
+ return req;
+}
+
+static void dbwrap_watched_watch_blocker_died(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct dbwrap_watched_watch_state *state = tevent_req_data(
+ req, struct dbwrap_watched_watch_state);
+ int ret;
+
+ ret = server_id_watch_recv(subreq, NULL);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+ state->blockerdead = true;
+ tevent_req_done(req);
+}
+
+static void dbwrap_watched_watch_state_destructor_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct dbwrap_watched_watch_state *state = talloc_get_type_abort(
+ private_data, struct dbwrap_watched_watch_state);
+
+ /*
+ * Here we just remove ourself from the in memory
+ * watchers array and let db_watched_record_fini()
+ * call dbwrap_watched_record_storev() to do the magic
+ * of writing back the modified in memory copy.
+ */
+ dbwrap_watched_watch_remove_instance(rec, state->watcher.instance);
+ return;
+}
+
+static int dbwrap_watched_watch_state_destructor(
+ struct dbwrap_watched_watch_state *state)
+{
+ NTSTATUS status;
+
+ status = dbwrap_do_locked(
+ state->db,
+ state->key,
+ dbwrap_watched_watch_state_destructor_fn,
+ state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ }
+ return 0;
+}
+
+static bool dbwrap_watched_msg_filter(struct messaging_rec *rec,
+ void *private_data)
+{
+ struct dbwrap_watched_watch_state *state = talloc_get_type_abort(
+ private_data, struct dbwrap_watched_watch_state);
+ uint64_t instance;
+
+ if (rec->msg_type != MSG_DBWRAP_MODIFIED) {
+ return false;
+ }
+ if (rec->num_fds != 0) {
+ return false;
+ }
+
+ if (rec->buf.length != sizeof(instance)) {
+ DBG_DEBUG("Got size %zu, expected %zu\n",
+ rec->buf.length,
+ sizeof(instance));
+ return false;
+ }
+
+ instance = BVAL(rec->buf.data, 0);
+
+ if (instance != state->watcher.instance) {
+ DBG_DEBUG("Got instance %"PRIu64", expected %"PRIu64"\n",
+ instance,
+ state->watcher.instance);
+ return false;
+ }
+
+ return true;
+}
+
+static void dbwrap_watched_watch_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct dbwrap_watched_watch_state *state = tevent_req_data(
+ req, struct dbwrap_watched_watch_state);
+ struct messaging_rec *rec;
+ int ret;
+
+ ret = messaging_filtered_read_recv(subreq, state, &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS dbwrap_watched_watch_recv(struct tevent_req *req,
+ uint64_t *pkeep_instance,
+ bool *blockerdead,
+ struct server_id *blocker)
+{
+ struct dbwrap_watched_watch_state *state = tevent_req_data(
+ req, struct dbwrap_watched_watch_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ if (pkeep_instance != NULL) {
+ *pkeep_instance = state->watcher.instance;
+ /*
+ * No need to remove ourselves anymore,
+ * the caller will take care of removing itself.
+ */
+ talloc_set_destructor(state, NULL);
+ }
+ if (blockerdead != NULL) {
+ *blockerdead = state->blockerdead;
+ }
+ if (blocker != NULL) {
+ *blocker = state->blocker;
+ }
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
diff --git a/source3/lib/dbwrap/dbwrap_watch.h b/source3/lib/dbwrap/dbwrap_watch.h
new file mode 100644
index 0000000..1b93e13
--- /dev/null
+++ b/source3/lib/dbwrap/dbwrap_watch.h
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+ Watch dbwrap record changes
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __DBWRAP_WATCH_H__
+#define __DBWRAP_WATCH_H__
+
+#include <tevent.h>
+#include "dbwrap/dbwrap.h"
+#include "messages.h"
+
+struct db_context *db_open_watched(TALLOC_CTX *mem_ctx,
+ struct db_context **backend,
+ struct messaging_context *msg);
+uint64_t dbwrap_watched_watch_add_instance(struct db_record *rec);
+void dbwrap_watched_watch_remove_instance(struct db_record *rec, uint64_t instance);
+void dbwrap_watched_watch_skip_alerting(struct db_record *rec);
+void dbwrap_watched_watch_reset_alerting(struct db_record *rec);
+void dbwrap_watched_watch_force_alerting(struct db_record *rec);
+struct tevent_req *dbwrap_watched_watch_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct db_record *rec,
+ uint64_t resume_instance,
+ struct server_id blocker);
+NTSTATUS dbwrap_watched_watch_recv(struct tevent_req *req,
+ uint64_t *pkeep_instance,
+ bool *blockerdead,
+ struct server_id *blocker);
+
+#endif /* __DBWRAP_WATCH_H__ */
diff --git a/source3/lib/dmallocmsg.c b/source3/lib/dmallocmsg.c
new file mode 100644
index 0000000..889f4ed
--- /dev/null
+++ b/source3/lib/dmallocmsg.c
@@ -0,0 +1,79 @@
+/*
+ samba -- Unix SMB/CIFS implementation.
+ Copyright (C) 2002 by Martin Pool
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "messages.h"
+
+/**
+ * @file dmallocmsg.c
+ *
+ * Glue code to cause dmalloc info to come out when we receive a prod
+ * over samba messaging.
+ **/
+
+#ifdef ENABLE_DMALLOC
+static unsigned long our_dm_mark = 0;
+#endif /* ENABLE_DMALLOC */
+
+
+/**
+ * Respond to a POOL_USAGE message by sending back string form of memory
+ * usage stats.
+ **/
+static void msg_req_dmalloc_mark(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+#ifdef ENABLE_DMALLOC
+ our_dm_mark = dmalloc_mark();
+ DEBUG(2,("Got MSG_REQ_DMALLOC_MARK: mark set\n"));
+#else
+ DEBUG(2,("Got MSG_REQ_DMALLOC_MARK but dmalloc not in this process\n"));
+#endif
+}
+
+
+
+static void msg_req_dmalloc_log_changed(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+#ifdef ENABLE_DMALLOC
+ dmalloc_log_changed(our_dm_mark, True, True, True);
+ DEBUG(2,("Got MSG_REQ_DMALLOC_LOG_CHANGED: done\n"));
+#else
+ DEBUG(2,("Got MSG_REQ_DMALLOC_LOG_CHANGED but dmalloc not in this process\n"));
+#endif
+}
+
+
+/**
+ * Register handler for MSG_REQ_POOL_USAGE
+ **/
+void register_dmalloc_msgs(struct messaging_context *msg_ctx)
+{
+ messaging_register(msg_ctx, NULL, MSG_REQ_DMALLOC_MARK,
+ msg_req_dmalloc_mark);
+ messaging_register(msg_ctx, NULL, MSG_REQ_DMALLOC_LOG_CHANGED,
+ msg_req_dmalloc_log_changed);
+ DEBUG(2, ("Registered MSG_REQ_DMALLOC_MARK and LOG_CHANGED\n"));
+}
diff --git a/source3/lib/dumpcore.c b/source3/lib/dumpcore.c
new file mode 100644
index 0000000..0c91206
--- /dev/null
+++ b/source3/lib/dumpcore.c
@@ -0,0 +1,343 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+
+ Copyright (C) Andrew Tridgell 1992-2011
+
+ based on old fault.c code, which had:
+
+ Copyright (C) Jeremy Allison 2001-2007
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+
+#ifdef HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+static char *corepath;
+static bool using_helper_binary = false;
+
+/**
+ * Build up the default corepath as "<logbase>/cores/<progname>"
+ */
+static char *get_default_corepath(const char *logbase, const char *progname)
+{
+ const mode_t mode = 0700;
+ const uid_t uid = getuid();
+ char *tmp_corepath;
+
+ /* Setup core dir in logbase. */
+ tmp_corepath = talloc_asprintf(NULL, "%s/cores", logbase);
+ if (!tmp_corepath) {
+ DEBUG(0, ("Out of memory\n"));
+ return NULL;
+ }
+
+ if (!directory_create_or_exist_strict(tmp_corepath, uid, mode)) {
+ DEBUG(0, ("Failed to create %s for user %d with mode 0%o\n",
+ tmp_corepath, (int)uid, (int)mode));
+ goto err_out;
+ }
+
+ /* Setup progname-specific core subdir */
+ tmp_corepath = talloc_asprintf_append(tmp_corepath, "/%s", progname);
+ if (!tmp_corepath) {
+ DEBUG(0, ("Out of memory\n"));
+ goto err_out;
+ }
+
+ if (!directory_create_or_exist(tmp_corepath, mode)) {
+ DEBUG(0, ("Failed to create %s for user %d with mode 0%o\n",
+ tmp_corepath, (int)uid, (int)mode));
+ goto err_out;
+ }
+
+ return tmp_corepath;
+
+ err_out:
+ talloc_free(tmp_corepath);
+ return NULL;
+}
+
+
+/**
+ * Get the FreeBSD corepath.
+ *
+ * On FreeBSD the current working directory is ignored when creating a core
+ * file. Instead the core directory is controlled via sysctl. This consults
+ * the value of "kern.corefile" so the correct corepath can be printed out
+ * before dump_core() calls abort.
+ */
+#if (defined(FREEBSD) && defined(HAVE_SYSCTLBYNAME))
+static char *get_freebsd_corepath(void)
+{
+ char *tmp_corepath = NULL;
+ char *end = NULL;
+ size_t len = 128;
+ int ret;
+
+ /* Loop with increasing sizes so we don't allocate too much. */
+ do {
+ if (len > 1024) {
+ goto err_out;
+ }
+
+ tmp_corepath = (char *)talloc_realloc(NULL, tmp_corepath,
+ char, len);
+ if (!tmp_corepath) {
+ return NULL;
+ }
+
+ ret = sysctlbyname("kern.corefile", tmp_corepath, &len, NULL,
+ 0);
+ if (ret == -1) {
+ if (errno != ENOMEM) {
+ DEBUG(0, ("sysctlbyname failed getting "
+ "kern.corefile %s\n",
+ strerror(errno)));
+ goto err_out;
+ }
+
+ /* Not a large enough array, try a bigger one. */
+ len = len << 1;
+ }
+ } while (ret == -1);
+
+ /* Strip off the common filename expansion */
+ if ((end = strrchr_m(tmp_corepath, '/'))) {
+ *end = '\0';
+ }
+
+ return tmp_corepath;
+
+ err_out:
+ if (tmp_corepath) {
+ talloc_free(tmp_corepath);
+ }
+ return NULL;
+}
+#endif
+
+#if defined(HAVE_SYS_KERNEL_PROC_CORE_PATTERN)
+
+/**
+ * Get the Linux corepath.
+ *
+ * On Linux the contents of /proc/sys/kernel/core_pattern indicates the
+ * location of the core path.
+ */
+static char *get_linux_corepath(void)
+{
+ char *end;
+ int fd;
+ char *result;
+
+ fd = open("/proc/sys/kernel/core_pattern", O_RDONLY, 0);
+ if (fd == -1) {
+ return NULL;
+ }
+
+ result = afdgets(fd, NULL, 0);
+ close(fd);
+
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (result[0] != '/') {
+ /*
+ * No absolute path, use the default (cwd)
+ */
+ if (result[0] == '|') {
+ /*
+ * Core dump handled by helper binaries
+ */
+ using_helper_binary = true;
+ }
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ /* Strip off the common filename expansion */
+
+ end = strrchr_m(result, '/');
+
+ if ((end != result) /* this would be the only / */
+ && (end != NULL)) {
+ *end = '\0';
+ }
+ return result;
+}
+#endif
+
+
+/**
+ * Try getting system-specific corepath if one exists.
+ *
+ * If the system doesn't define a corepath, then the default is used.
+ */
+static char *get_corepath(const char *logbase, const char *progname)
+{
+#if (defined(FREEBSD) && defined(HAVE_SYSCTLBYNAME))
+ char *tmp_corepath = NULL;
+ tmp_corepath = get_freebsd_corepath();
+
+ /* If this has been set correctly, we're done. */
+ if (tmp_corepath) {
+ return tmp_corepath;
+ }
+#endif
+
+#if defined(HAVE_SYS_KERNEL_PROC_CORE_PATTERN)
+ char *tmp_corepath = NULL;
+ tmp_corepath = get_linux_corepath();
+
+ /* If this has been set correctly, we're done. */
+ if (tmp_corepath) {
+ return tmp_corepath;
+ }
+#endif
+
+ /* Fall back to the default. */
+ return get_default_corepath(logbase, progname);
+}
+
+/*******************************************************************
+make all the preparations to safely dump a core file
+********************************************************************/
+
+void dump_core_setup(const char *progname, const char *log_file)
+{
+ char *logbase = NULL;
+ char *end = NULL;
+
+ if (log_file && *log_file) {
+ if (asprintf(&logbase, "%s", log_file) < 0) {
+ return;
+ }
+ if ((end = strrchr_m(logbase, '/'))) {
+ *end = '\0';
+ }
+ } else {
+ /* We will end up here if the log file is given on the command
+ * line by the -l option but the "log file" option is not set
+ * in smb.conf.
+ */
+ if (asprintf(&logbase, "%s", get_dyn_LOGFILEBASE()) < 0) {
+ return;
+ }
+ }
+
+ SMB_ASSERT(progname != NULL);
+
+ corepath = get_corepath(logbase, progname);
+ if (!corepath) {
+ DEBUG(0, ("Unable to setup corepath for %s: %s\n", progname,
+ strerror(errno)));
+ goto out;
+ }
+
+ /* FIXME: if we have a core-plus-pid facility, configurably set
+ * this up here.
+ */
+ out:
+ SAFE_FREE(logbase);
+}
+
+ void dump_core(void)
+{
+ static bool called;
+
+ if (called) {
+ DEBUG(0, ("dump_core() called recursive\n"));
+ exit(1);
+ }
+ called = true;
+
+ /* Note that even if core dumping has been disabled, we still set up
+ * the core path. This is to handle the case where core dumping is
+ * turned on in smb.conf and the relevant daemon is not restarted.
+ */
+ if (!lp_enable_core_files()) {
+ DEBUG(0, ("Exiting on internal error (core file administratively disabled)\n"));
+ exit(1);
+ }
+
+#if DUMP_CORE
+ /* If we're running as non root we might not be able to dump the core
+ * file to the corepath. There must not be an unbecome_root() before
+ * we call abort(). */
+ if (geteuid() != sec_initial_uid()) {
+ become_root();
+ }
+
+ if (corepath == NULL) {
+ DEBUG(0, ("Can not dump core: corepath not set up\n"));
+ exit(1);
+ }
+
+ if (*corepath != '\0') {
+ /*
+ * Check whether coredump is handled by helper binaries or not.
+ * If so skip chdir().
+ */
+ if (!using_helper_binary) {
+ /* The chdir might fail if we dump core before we finish
+ * processing the config file.
+ */
+ if (chdir(corepath) != 0) {
+ DEBUG(0, ("unable to change to %s\n", corepath));
+ DEBUGADD(0, ("refusing to dump core\n"));
+ exit(1);
+ }
+
+ DEBUG(0,("dumping core in %s\n", corepath));
+ } else {
+ DEBUG(0,("coredump is handled by helper binary "
+ "specified at /proc/sys/kernel/core_pattern\n"));
+ }
+ }
+
+ umask(~(0700));
+ dbgflush();
+
+#if defined(HAVE_PRCTL) && defined(PR_SET_DUMPABLE)
+ /* On Linux we lose the ability to dump core when we change our user
+ * ID. We know how to dump core safely, so let's make sure we have our
+ * dumpable flag set.
+ */
+ prctl(PR_SET_DUMPABLE, 1);
+#endif
+
+ /* Ensure we don't have a signal handler for abort. */
+#ifdef SIGABRT
+ CatchSignal(SIGABRT, SIG_DFL);
+#endif
+
+ abort();
+
+#else /* DUMP_CORE */
+ exit(1);
+#endif /* DUMP_CORE */
+}
diff --git a/source3/lib/errmap_unix.c b/source3/lib/errmap_unix.c
new file mode 100644
index 0000000..3aa2cd1
--- /dev/null
+++ b/source3/lib/errmap_unix.c
@@ -0,0 +1,158 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * map unix to NT errors, an excerpt of libsmb/errormap.c
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Tim Potter 2000
+ * Copyright (C) Jeremy Allison 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+/* Mapping from Unix, to NT error numbers */
+
+static const struct {
+ int unix_error;
+ NTSTATUS nt_error;
+} unix_nt_errmap[] = {
+ { EAGAIN, NT_STATUS_NETWORK_BUSY },
+ { EINTR, NT_STATUS_RETRY },
+#ifdef ENOBUFS
+ { ENOBUFS, NT_STATUS_INSUFFICIENT_RESOURCES },
+#endif
+#ifdef EWOULDBLOCK
+ { EWOULDBLOCK, NT_STATUS_NETWORK_BUSY },
+#endif
+ { EPERM, NT_STATUS_ACCESS_DENIED },
+ { EACCES, NT_STATUS_ACCESS_DENIED },
+ { ENOENT, NT_STATUS_OBJECT_NAME_NOT_FOUND },
+ { ENOTDIR, NT_STATUS_NOT_A_DIRECTORY },
+ { EIO, NT_STATUS_IO_DEVICE_ERROR },
+ { EBADF, NT_STATUS_INVALID_HANDLE },
+ { EINVAL, NT_STATUS_INVALID_PARAMETER },
+ { EEXIST, NT_STATUS_OBJECT_NAME_COLLISION},
+ { ENFILE, NT_STATUS_TOO_MANY_OPENED_FILES },
+ { EMFILE, NT_STATUS_TOO_MANY_OPENED_FILES },
+ { ENOSPC, NT_STATUS_DISK_FULL },
+ { ENOMEM, NT_STATUS_NO_MEMORY },
+ { EISDIR, NT_STATUS_FILE_IS_A_DIRECTORY},
+ { EMSGSIZE, NT_STATUS_PORT_MESSAGE_TOO_LONG },
+#ifdef EPIPE
+ { EPIPE, NT_STATUS_CONNECTION_DISCONNECTED},
+#endif
+ { EMLINK, NT_STATUS_TOO_MANY_LINKS },
+ { ENOSYS, NT_STATUS_NOT_SUPPORTED },
+#ifdef ELOOP
+ { ELOOP, NT_STATUS_OBJECT_PATH_NOT_FOUND },
+#endif
+#ifdef EFTYPE
+ { EFTYPE, NT_STATUS_OBJECT_PATH_NOT_FOUND },
+#endif
+#ifdef EDQUOT
+ { EDQUOT, NT_STATUS_DISK_FULL }, /* Windows apps need this, not NT_STATUS_QUOTA_EXCEEDED */
+#endif
+#ifdef ENOTEMPTY
+ { ENOTEMPTY, NT_STATUS_DIRECTORY_NOT_EMPTY },
+#endif
+#ifdef EXDEV
+ { EXDEV, NT_STATUS_NOT_SAME_DEVICE },
+#endif
+#ifdef EROFS
+ { EROFS, NT_STATUS_MEDIA_WRITE_PROTECTED },
+#endif
+#ifdef ENAMETOOLONG
+ { ENAMETOOLONG, NT_STATUS_OBJECT_NAME_INVALID },
+#endif
+#ifdef EFBIG
+ { EFBIG, NT_STATUS_DISK_FULL },
+#endif
+#ifdef EADDRINUSE
+ { EADDRINUSE, NT_STATUS_ADDRESS_ALREADY_ASSOCIATED},
+#endif
+#ifdef ENETUNREACH
+ { ENETUNREACH, NT_STATUS_NETWORK_UNREACHABLE},
+#endif
+#ifdef EHOSTUNREACH
+ { EHOSTUNREACH, NT_STATUS_HOST_UNREACHABLE},
+#endif
+#ifdef ECONNREFUSED
+ { ECONNREFUSED, NT_STATUS_CONNECTION_REFUSED},
+#endif
+#ifdef ETIMEDOUT
+ { ETIMEDOUT, NT_STATUS_IO_TIMEOUT},
+#endif
+#ifdef ECONNABORTED
+ { ECONNABORTED, NT_STATUS_CONNECTION_ABORTED},
+#endif
+#ifdef ECONNRESET
+ { ECONNRESET, NT_STATUS_CONNECTION_RESET},
+#endif
+#ifdef ENODEV
+ { ENODEV, NT_STATUS_DEVICE_DOES_NOT_EXIST},
+#endif
+#ifdef ENOATTR
+ { ENOATTR, NT_STATUS_NOT_FOUND },
+#endif
+#ifdef ECANCELED
+ { ECANCELED, NT_STATUS_CANCELLED},
+#endif
+#ifdef ENOTSUP
+ { ENOTSUP, NT_STATUS_NOT_SUPPORTED},
+#endif
+#ifdef ETXTBSY
+ { ETXTBSY, NT_STATUS_SHARING_VIOLATION },
+#endif
+#ifdef EOVERFLOW
+ { EOVERFLOW, NT_STATUS_ALLOTTED_SPACE_EXCEEDED },
+#endif
+ { EINPROGRESS, NT_STATUS_MORE_PROCESSING_REQUIRED },
+#ifdef ERANGE
+ { ERANGE, NT_STATUS_INTEGER_OVERFLOW },
+#endif
+#ifdef ENXIO
+ { ENXIO, NT_STATUS_ILLEGAL_FUNCTION },
+#endif
+};
+
+/*********************************************************************
+ Map an NT error code from a Unix error code.
+*********************************************************************/
+
+NTSTATUS map_nt_error_from_unix(int unix_error)
+{
+ size_t i = 0;
+
+ if (unix_error == 0) {
+ /* we map this to an error, not success, as this
+ function is only called in an error path. Lots of
+ our virtualised functions may fail without making a
+ unix system call that fails (such as when they are
+ checking for some handle existing), so unix_error
+ may be unset
+ */
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* Look through list */
+ for (i=0;i<ARRAY_SIZE(unix_nt_errmap);i++) {
+ if (unix_nt_errmap[i].unix_error == unix_error) {
+ return unix_nt_errmap[i].nt_error;
+ }
+ }
+
+ /* Default return */
+ return NT_STATUS_ACCESS_DENIED;
+}
diff --git a/source3/lib/eventlog/eventlog.c b/source3/lib/eventlog/eventlog.c
new file mode 100644
index 0000000..031c749
--- /dev/null
+++ b/source3/lib/eventlog/eventlog.c
@@ -0,0 +1,1066 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Eventlog utility routines
+ * Copyright (C) Marcin Krzysztof Porwit 2005,
+ * Copyright (C) Brian Moran 2005.
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ * Copyright (C) Guenther Deschner 2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/eventlog/eventlog.h"
+#include "../libcli/security/security.h"
+#include "util_tdb.h"
+
+/* maintain a list of open eventlog tdbs with reference counts */
+
+static ELOG_TDB *open_elog_list;
+
+/********************************************************************
+ Init an Eventlog TDB, and return it. If null, something bad
+ happened.
+********************************************************************/
+
+TDB_CONTEXT *elog_init_tdb( char *tdbfilename )
+{
+ TDB_CONTEXT *tdb;
+
+ DEBUG(10,("elog_init_tdb: Initializing eventlog tdb (%s)\n",
+ tdbfilename));
+
+ tdb = tdb_open_log( tdbfilename, 0, TDB_DEFAULT,
+ O_RDWR|O_CREAT|O_TRUNC, 0660 );
+
+ if ( !tdb ) {
+ DEBUG( 0, ( "Can't open tdb for [%s]\n", tdbfilename ) );
+ return NULL;
+ }
+
+ /* initialize with defaults, copy real values in here from registry */
+
+ tdb_store_int32( tdb, EVT_OLDEST_ENTRY, 1 );
+ tdb_store_int32( tdb, EVT_NEXT_RECORD, 1 );
+ tdb_store_int32( tdb, EVT_MAXSIZE, 0x80000 );
+ tdb_store_int32( tdb, EVT_RETENTION, 0x93A80 );
+
+ tdb_store_int32( tdb, EVT_VERSION, EVENTLOG_DATABASE_VERSION_V1 );
+
+ return tdb;
+}
+
+/********************************************************************
+ make the tdb file name for an event log, given destination buffer
+ and size. Caller must free memory.
+********************************************************************/
+
+char *elog_tdbname(TALLOC_CTX *ctx, const char *name )
+{
+ char *path;
+ char *file;
+ char *tdbname;
+
+ path = state_path(talloc_tos(), "eventlog");
+ if (!path) {
+ return NULL;
+ }
+
+ file = talloc_asprintf_strlower_m(path, "%s.tdb", name);
+ if (!file) {
+ talloc_free(path);
+ return NULL;
+ }
+
+ tdbname = talloc_asprintf(ctx, "%s/%s", path, file);
+ if (!tdbname) {
+ talloc_free(path);
+ return NULL;
+ }
+
+ talloc_free(path);
+ return tdbname;
+}
+
+
+/********************************************************************
+ this function is used to count up the number of bytes in a
+ particular TDB
+********************************************************************/
+
+struct trav_size_struct {
+ int size;
+ int rec_count;
+};
+
+static int eventlog_tdb_size_fn( TDB_CONTEXT * tdb, TDB_DATA key, TDB_DATA data,
+ void *state )
+{
+ struct trav_size_struct *tsize = (struct trav_size_struct *)state;
+
+ tsize->size += data.dsize;
+ tsize->rec_count++;
+
+ return 0;
+}
+
+/********************************************************************
+ returns the size of the eventlog, and if MaxSize is a non-null
+ ptr, puts the MaxSize there. This is purely a way not to have yet
+ another function that solely reads the maxsize of the eventlog.
+ Yeah, that's it.
+********************************************************************/
+
+int elog_tdb_size( TDB_CONTEXT * tdb, int *MaxSize, int *Retention )
+{
+ struct trav_size_struct tsize;
+
+ if ( !tdb )
+ return 0;
+
+ ZERO_STRUCT( tsize );
+
+ tdb_traverse( tdb, eventlog_tdb_size_fn, &tsize );
+
+ if ( MaxSize != NULL ) {
+ *MaxSize = tdb_fetch_int32( tdb, EVT_MAXSIZE );
+ }
+
+ if ( Retention != NULL ) {
+ *Retention = tdb_fetch_int32( tdb, EVT_RETENTION );
+ }
+
+ DEBUG( 1,
+ ( "eventlog size: [%d] for [%d] records\n", tsize.size,
+ tsize.rec_count ) );
+ return tsize.size;
+}
+
+/********************************************************************
+ Discard early event logs until we have enough for 'needed' bytes...
+ NO checking done beforehand to see that we actually need to do
+ this, and it's going to pluck records one-by-one. So, it's best
+ to determine that this needs to be done before doing it.
+
+ Setting whack_by_date to True indicates that eventlogs falling
+ outside of the retention range need to go...
+
+ return True if we made enough room to accommodate needed bytes
+********************************************************************/
+
+static bool make_way_for_eventlogs( TDB_CONTEXT * the_tdb, int32_t needed,
+ bool whack_by_date )
+{
+ int32_t start_record, i, new_start;
+ int32_t end_record;
+ int32_t reclen, tresv1, trecnum, timegen, timewr;
+ int nbytes, len, Retention, MaxSize;
+ TDB_DATA key, ret;
+ time_t current_time, exp_time;
+
+ /* discard some eventlogs */
+
+ /* read eventlogs from oldest_entry -- there can't be any discontinuity in recnos,
+ although records not necessarily guaranteed to have successive times */
+ /* */
+
+ /* lock */
+ tdb_lock_bystring_with_timeout( the_tdb, EVT_NEXT_RECORD, 1 );
+ /* read */
+ end_record = tdb_fetch_int32( the_tdb, EVT_NEXT_RECORD );
+ start_record = tdb_fetch_int32( the_tdb, EVT_OLDEST_ENTRY );
+ Retention = tdb_fetch_int32( the_tdb, EVT_RETENTION );
+ MaxSize = tdb_fetch_int32( the_tdb, EVT_MAXSIZE );
+
+ time( &current_time );
+
+ /* calculate ... */
+ exp_time = current_time - Retention; /* discard older than exp_time */
+
+ /* todo - check for sanity in next_record */
+ nbytes = 0;
+
+ DEBUG( 3,
+ ( "MaxSize [%d] Retention [%d] Current Time [%u] exp_time [%u]\n",
+ MaxSize, Retention, (unsigned int)current_time, (unsigned int)exp_time ) );
+ DEBUG( 3,
+ ( "Start Record [%u] End Record [%u]\n",
+ (unsigned int)start_record,
+ (unsigned int)end_record ));
+
+ for ( i = start_record; i < end_record; i++ ) {
+ /* read a record, add the amt to nbytes */
+ key.dsize = sizeof(int32_t);
+ key.dptr = (unsigned char *)&i;
+ ret = tdb_fetch( the_tdb, key );
+ if ( ret.dsize == 0 ) {
+ DEBUG( 8,
+ ( "Can't find a record for the key, record [%d]\n",
+ i ) );
+ tdb_unlock_bystring( the_tdb, EVT_NEXT_RECORD );
+ return False;
+ }
+ nbytes += ret.dsize; /* note this includes overhead */
+
+ len = tdb_unpack( ret.dptr, ret.dsize, "ddddd", &reclen,
+ &tresv1, &trecnum, &timegen, &timewr );
+ if (len == -1) {
+ DEBUG( 10,("make_way_for_eventlogs: tdb_unpack failed.\n"));
+ tdb_unlock_bystring( the_tdb, EVT_NEXT_RECORD );
+ SAFE_FREE( ret.dptr );
+ return False;
+ }
+
+ DEBUG( 8,
+ ( "read record %u, record size is [%d], total so far [%d]\n",
+ (unsigned int)i, reclen, nbytes ) );
+
+ SAFE_FREE( ret.dptr );
+
+ /* note that other servers may just stop writing records when the size limit
+ is reached, and there are no records older than 'retention'. This doesn't
+ like a very useful thing to do, so instead we whack (as in sleeps with the
+ fishes) just enough records to fit the what we need. This behavior could
+ be changed to 'match', if the need arises. */
+
+ if ( !whack_by_date && ( nbytes >= needed ) )
+ break; /* done */
+ if ( whack_by_date && ( timegen >= exp_time ) )
+ break; /* done */
+ }
+
+ DEBUG( 3,
+ ( "nbytes [%d] needed [%d] start_record is [%u], should be set to [%u]\n",
+ nbytes, needed, (unsigned int)start_record, (unsigned int)i ) );
+ /* todo - remove eventlog entries here and set starting record to start_record... */
+ new_start = i;
+ if ( start_record != new_start ) {
+ for ( i = start_record; i < new_start; i++ ) {
+ key.dsize = sizeof(int32_t);
+ key.dptr = (unsigned char *)&i;
+ tdb_delete( the_tdb, key );
+ }
+
+ tdb_store_int32( the_tdb, EVT_OLDEST_ENTRY, new_start );
+ }
+ tdb_unlock_bystring( the_tdb, EVT_NEXT_RECORD );
+ return True;
+}
+
+/********************************************************************
+ some hygiene for an eventlog - see how big it is, and then
+ calculate how many bytes we need to remove
+********************************************************************/
+
+bool prune_eventlog( TDB_CONTEXT * tdb )
+{
+ int MaxSize, Retention, CalcdSize;
+
+ if ( !tdb ) {
+ DEBUG( 4, ( "No eventlog tdb handle\n" ) );
+ return False;
+ }
+
+ CalcdSize = elog_tdb_size( tdb, &MaxSize, &Retention );
+ DEBUG( 3,
+ ( "Calculated size [%d] MaxSize [%d]\n", CalcdSize,
+ MaxSize ) );
+
+ if ( CalcdSize > MaxSize ) {
+ return make_way_for_eventlogs( tdb, CalcdSize - MaxSize,
+ False );
+ }
+
+ return make_way_for_eventlogs( tdb, 0, True );
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool can_write_to_eventlog( TDB_CONTEXT * tdb, int32_t needed )
+{
+ int calcd_size;
+ int MaxSize, Retention;
+
+ /* see if we can write to the eventlog -- do a policy enforcement */
+ if ( !tdb )
+ return False; /* tdb is null, so we can't write to it */
+
+
+ if ( needed < 0 )
+ return False;
+ MaxSize = 0;
+ Retention = 0;
+
+ calcd_size = elog_tdb_size( tdb, &MaxSize, &Retention );
+
+ if ( calcd_size <= MaxSize )
+ return True; /* you betcha */
+ if ( calcd_size + needed < MaxSize )
+ return True;
+
+ if ( Retention == 0xffffffff ) {
+ return False; /* see msdn - we can't write no room, discard */
+ }
+ /*
+ note don't have to test, but always good to show intent, in case changes needed
+ later
+ */
+
+ if ( Retention == 0x00000000 ) {
+ /* discard record(s) */
+ /* todo - decide when to remove a bunch vs. just what we need... */
+ return make_way_for_eventlogs( tdb, calcd_size - MaxSize,
+ True );
+ }
+
+ return make_way_for_eventlogs( tdb, calcd_size - MaxSize, False );
+}
+
+/*******************************************************************
+*******************************************************************/
+
+ELOG_TDB *elog_open_tdb( const char *logname, bool force_clear, bool read_only )
+{
+ TDB_CONTEXT *tdb = NULL;
+ uint32_t vers_id;
+ ELOG_TDB *ptr;
+ char *tdbpath = NULL;
+ ELOG_TDB *tdb_node = NULL;
+ char *eventlogdir;
+ TALLOC_CTX *ctx = talloc_tos();
+ bool ok;
+
+ /* check for invalid options */
+
+ if (force_clear && read_only) {
+ DEBUG(1,("elog_open_tdb: Invalid flags\n"));
+ return NULL;
+ }
+
+ /* first see if we have an open context */
+
+ for ( ptr=open_elog_list; ptr; ptr=ptr->next ) {
+ if ( strequal( ptr->name, logname ) ) {
+ ptr->ref_count++;
+
+ /* trick to allow clearing of the eventlog tdb.
+ The force_clear flag should imply that someone
+ has done a force close. So make sure the tdb
+ is NULL. If this is a normal open, then just
+ return the existing reference */
+
+ if ( force_clear ) {
+ SMB_ASSERT( ptr->tdb == NULL );
+ break;
+ }
+ else
+ return ptr;
+ }
+ }
+
+ /* make sure that the eventlog dir exists */
+
+ eventlogdir = state_path(talloc_tos(), "eventlog");
+ if (eventlogdir == NULL) {
+ return NULL;
+ }
+ ok = directory_create_or_exist(eventlogdir, 0755);
+ TALLOC_FREE(eventlogdir);
+ if (!ok) {
+ return NULL;
+ }
+
+ /* get the path on disk */
+
+ tdbpath = elog_tdbname(ctx, logname);
+ if (!tdbpath) {
+ return NULL;
+ }
+
+ DEBUG(7,("elog_open_tdb: Opening %s...(force_clear == %s)\n",
+ tdbpath, force_clear?"True":"False" ));
+
+ /* the tdb wasn't already open or this is a forced clear open */
+
+ if ( !force_clear ) {
+
+ tdb = tdb_open_log( tdbpath, 0, TDB_DEFAULT, read_only ? O_RDONLY : O_RDWR , 0 );
+ if ( tdb ) {
+ vers_id = tdb_fetch_int32( tdb, EVT_VERSION );
+
+ if ( vers_id != EVENTLOG_DATABASE_VERSION_V1 ) {
+ DEBUG(1,("elog_open_tdb: Invalid version [%d] on file [%s].\n",
+ vers_id, tdbpath));
+ tdb_close( tdb );
+ tdb = elog_init_tdb( tdbpath );
+ }
+ }
+ }
+
+ if ( !tdb )
+ tdb = elog_init_tdb( tdbpath );
+
+ /* if we got a valid context, then add it to the list */
+
+ if ( tdb ) {
+ /* on a forced clear, just reset the tdb context if we already
+ have an open entry in the list */
+
+ if ( ptr ) {
+ ptr->tdb = tdb;
+ return ptr;
+ }
+
+ if ( !(tdb_node = talloc_zero( NULL, ELOG_TDB)) ) {
+ DEBUG(0,("elog_open_tdb: talloc() failure!\n"));
+ tdb_close( tdb );
+ return NULL;
+ }
+
+ tdb_node->name = talloc_strdup( tdb_node, logname );
+ tdb_node->tdb = tdb;
+ tdb_node->ref_count = 1;
+
+ DLIST_ADD( open_elog_list, tdb_node );
+ }
+
+ return tdb_node;
+}
+
+/*******************************************************************
+ Wrapper to handle reference counts to the tdb
+*******************************************************************/
+
+int elog_close_tdb( ELOG_TDB *etdb, bool force_close )
+{
+ TDB_CONTEXT *tdb;
+
+ if ( !etdb )
+ return 0;
+
+ etdb->ref_count--;
+
+ SMB_ASSERT( etdb->ref_count >= 0 );
+
+ if ( etdb->ref_count == 0 ) {
+ tdb = etdb->tdb;
+ DLIST_REMOVE( open_elog_list, etdb );
+ TALLOC_FREE( etdb );
+ return tdb_close( tdb );
+ }
+
+ if ( force_close ) {
+ tdb = etdb->tdb;
+ etdb->tdb = NULL;
+ return tdb_close( tdb );
+ }
+
+ return 0;
+}
+
+/********************************************************************
+ Note that it's a pretty good idea to initialize the Eventlog_entry
+ structure to zero's before calling parse_logentry on an batch of
+ lines that may resolve to a record. ALSO, it's a good idea to
+ remove any linefeeds (that's EOL to you and me) on the lines
+ going in.
+********************************************************************/
+
+bool parse_logentry( TALLOC_CTX *mem_ctx, char *line, struct eventlog_Record_tdb *entry, bool * eor )
+{
+ char *start = NULL, *stop = NULL;
+
+ start = line;
+
+ /* empty line signifying record delimiter, or we're at the end of the buffer */
+ if ( start == NULL || strlen( start ) == 0 ) {
+ DEBUG( 6,
+ ( "parse_logentry: found end-of-record indicator.\n" ) );
+ *eor = True;
+ return True;
+ }
+ if ( !( stop = strchr( line, ':' ) ) ) {
+ return False;
+ }
+
+ DEBUG( 6, ( "parse_logentry: trying to parse [%s].\n", line ) );
+
+ if ( 0 == strncmp( start, "LEN", stop - start ) ) {
+ /* This will get recomputed later anyway -- probably not necessary */
+ entry->size = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "RS1", stop - start ) ) {
+ /* For now all these reserved entries seem to have the same value,
+ which can be hardcoded to int(1699505740) for now */
+ entry->reserved = talloc_strdup(mem_ctx, "eLfL");
+ } else if ( 0 == strncmp( start, "RCN", stop - start ) ) {
+ entry->record_number = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "TMG", stop - start ) ) {
+ entry->time_generated = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "TMW", stop - start ) ) {
+ entry->time_written = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "EID", stop - start ) ) {
+ entry->event_id = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "ETP", stop - start ) ) {
+ if ( strstr( start, "ERROR" ) ) {
+ entry->event_type = EVENTLOG_ERROR_TYPE;
+ } else if ( strstr( start, "WARNING" ) ) {
+ entry->event_type = EVENTLOG_WARNING_TYPE;
+ } else if ( strstr( start, "INFO" ) ) {
+ entry->event_type = EVENTLOG_INFORMATION_TYPE;
+ } else if ( strstr( start, "AUDIT_SUCCESS" ) ) {
+ entry->event_type = EVENTLOG_AUDIT_SUCCESS;
+ } else if ( strstr( start, "AUDIT_FAILURE" ) ) {
+ entry->event_type = EVENTLOG_AUDIT_FAILURE;
+ } else if ( strstr( start, "SUCCESS" ) ) {
+ entry->event_type = EVENTLOG_SUCCESS;
+ } else {
+ /* some other eventlog type -- currently not defined in MSDN docs, so error out */
+ return False;
+ }
+ }
+
+/*
+ else if(0 == strncmp(start, "NST", stop - start))
+ {
+ entry->num_of_strings = atoi(stop + 1);
+ }
+*/
+ else if ( 0 == strncmp( start, "ECT", stop - start ) ) {
+ entry->event_category = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "RS2", stop - start ) ) {
+ entry->reserved_flags = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "CRN", stop - start ) ) {
+ entry->closing_record_number = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "USL", stop - start ) ) {
+ entry->sid_length = atoi( stop + 1 );
+ } else if ( 0 == strncmp( start, "SRC", stop - start ) ) {
+ stop++;
+ while ( isspace( stop[0] ) ) {
+ stop++;
+ }
+ entry->source_name_len = strlen_m_term(stop);
+ entry->source_name = talloc_strdup(mem_ctx, stop);
+ if (entry->source_name_len == (uint32_t)-1 ||
+ entry->source_name == NULL) {
+ return false;
+ }
+ } else if ( 0 == strncmp( start, "SRN", stop - start ) ) {
+ stop++;
+ while ( isspace( stop[0] ) ) {
+ stop++;
+ }
+ entry->computer_name_len = strlen_m_term(stop);
+ entry->computer_name = talloc_strdup(mem_ctx, stop);
+ if (entry->computer_name_len == (uint32_t)-1 ||
+ entry->computer_name == NULL) {
+ return false;
+ }
+ } else if ( 0 == strncmp( start, "SID", stop - start ) ) {
+ smb_ucs2_t *dummy = NULL;
+ stop++;
+ while ( isspace( stop[0] ) ) {
+ stop++;
+ }
+ entry->sid_length = rpcstr_push_talloc(mem_ctx,
+ &dummy,
+ stop);
+ if (entry->sid_length == (uint32_t)-1) {
+ return false;
+ }
+ entry->sid = data_blob_talloc(mem_ctx, dummy, entry->sid_length);
+ if (entry->sid.data == NULL) {
+ return false;
+ }
+ } else if ( 0 == strncmp( start, "STR", stop - start ) ) {
+ size_t tmp_len;
+ size_t num_of_strings;
+ /* skip past initial ":" */
+ stop++;
+ /* now skip any other leading whitespace */
+ while ( isspace(stop[0])) {
+ stop++;
+ }
+ tmp_len = strlen_m_term(stop);
+ if (tmp_len == (size_t)-1) {
+ return false;
+ }
+ num_of_strings = entry->num_of_strings;
+ if (!add_string_to_array(mem_ctx, stop, &entry->strings,
+ &num_of_strings)) {
+ return false;
+ }
+ if (num_of_strings > 0xffff) {
+ return false;
+ }
+ entry->num_of_strings = num_of_strings;
+ entry->strings_len += tmp_len;
+ } else if ( 0 == strncmp( start, "DAT", stop - start ) ) {
+ /* skip past initial ":" */
+ stop++;
+ /* now skip any other leading whitespace */
+ while ( isspace( stop[0] ) ) {
+ stop++;
+ }
+ entry->data_length = strlen_m(stop);
+ entry->data = data_blob_talloc(mem_ctx, stop, entry->data_length);
+ if (!entry->data.data) {
+ return false;
+ }
+ } else {
+ /* some other eventlog entry -- not implemented, so dropping on the floor */
+ DEBUG( 10, ( "Unknown entry [%s]. Ignoring.\n", line ) );
+ /* For now return true so that we can keep on parsing this mess. Eventually
+ we will return False here. */
+ return true;
+ }
+ return true;
+}
+
+/*******************************************************************
+ calculate the correct fields etc for an eventlog entry
+*******************************************************************/
+
+size_t fixup_eventlog_record_tdb(struct eventlog_Record_tdb *r)
+{
+ size_t size = 56; /* static size of integers before buffers start */
+
+ r->source_name_len = strlen_m_term(r->source_name) * 2;
+ r->computer_name_len = strlen_m_term(r->computer_name) * 2;
+ r->strings_len = ndr_size_string_array(r->strings,
+ r->num_of_strings, LIBNDR_FLAG_STR_NULLTERM) * 2;
+
+ /* fix up the eventlog entry structure as necessary */
+ r->sid_padding = ( ( 4 - ( ( r->source_name_len + r->computer_name_len ) % 4 ) ) % 4 );
+ r->padding = ( 4 - ( ( r->strings_len + r->data_length ) % 4 ) ) % 4;
+
+ if (r->sid_length == 0) {
+ /* Should not pad to a DWORD boundary for writing out the sid if there is
+ no SID, so just propagate the padding to pad the data */
+ r->padding += r->sid_padding;
+ r->sid_padding = 0;
+ }
+
+ size += r->source_name_len;
+ size += r->computer_name_len;
+ size += r->sid_padding;
+ size += r->sid_length;
+ size += r->strings_len;
+ size += r->data_length;
+ size += r->padding;
+ /* need another copy of length at the end of the data */
+ size += sizeof(r->size);
+
+ r->size = size;
+
+ return size;
+}
+
+
+/********************************************************************
+ ********************************************************************/
+
+struct eventlog_Record_tdb *evlog_pull_record_tdb(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ uint32_t record_number)
+{
+ struct eventlog_Record_tdb *r;
+ TDB_DATA data, key;
+
+ int32_t srecno;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ srecno = record_number;
+ key.dptr = (unsigned char *)&srecno;
+ key.dsize = sizeof(int32_t);
+
+ data = tdb_fetch(tdb, key);
+ if (data.dsize == 0) {
+ DEBUG(8,("evlog_pull_record_tdb: "
+ "Can't find a record for the key, record %d\n",
+ record_number));
+ return NULL;
+ }
+
+ r = talloc_zero(mem_ctx, struct eventlog_Record_tdb);
+ if (!r) {
+ goto done;
+ }
+
+ blob = data_blob_const(data.dptr, data.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, r,
+ (ndr_pull_flags_fn_t)ndr_pull_eventlog_Record_tdb);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10,("evlog_pull_record_tdb: failed to decode record %d\n",
+ record_number));
+ TALLOC_FREE(r);
+ goto done;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(eventlog_Record_tdb, r);
+ }
+
+ DEBUG(10,("evlog_pull_record_tdb: retrieved entry for record %d\n",
+ record_number));
+ done:
+ SAFE_FREE(data.dptr);
+
+ return r;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+struct EVENTLOGRECORD *evlog_pull_record(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ uint32_t record_number)
+{
+ struct eventlog_Record_tdb *t;
+ struct EVENTLOGRECORD *r;
+ NTSTATUS status;
+
+ r = talloc_zero(mem_ctx, struct EVENTLOGRECORD);
+ if (!r) {
+ return NULL;
+ }
+
+ t = evlog_pull_record_tdb(r, tdb, record_number);
+ if (!t) {
+ talloc_free(r);
+ return NULL;
+ }
+
+ status = evlog_tdb_entry_to_evt_entry(r, t, r);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(r);
+ return NULL;
+ }
+
+ r->Length = r->Length2 = ndr_size_EVENTLOGRECORD(r, 0);
+
+ return r;
+}
+
+/********************************************************************
+ write an eventlog entry. Note that we have to lock, read next
+ eventlog, increment, write, write the record, unlock
+
+ coming into this, ee has the eventlog record, and the auxiliary date
+ (computer name, etc.) filled into the other structure. Before packing
+ into a record, this routine will calc the appropriate padding, etc.,
+ and then blast out the record in a form that can be read back in
+ ********************************************************************/
+
+NTSTATUS evlog_push_record_tdb(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ struct eventlog_Record_tdb *r,
+ uint32_t *record_number)
+{
+ TDB_DATA kbuf, ebuf;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ int ret;
+
+ if (!r) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!can_write_to_eventlog(tdb, r->size)) {
+ return NT_STATUS_EVENTLOG_CANT_START;
+ }
+
+ /* need to read the record number and insert it into the entry here */
+
+ /* lock */
+ ret = tdb_lock_bystring_with_timeout(tdb, EVT_NEXT_RECORD, 1);
+ if (ret != 0) {
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ /* read */
+ r->record_number = tdb_fetch_int32(tdb, EVT_NEXT_RECORD);
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, r,
+ (ndr_push_flags_fn_t)ndr_push_eventlog_Record_tdb);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ tdb_unlock_bystring(tdb, EVT_NEXT_RECORD);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ /* increment the record count */
+
+ kbuf.dsize = sizeof(int32_t);
+ kbuf.dptr = (uint8_t *)&r->record_number;
+
+ ebuf.dsize = blob.length;
+ ebuf.dptr = blob.data;
+
+ ret = tdb_store(tdb, kbuf, ebuf, 0);
+ if (ret != 0) {
+ tdb_unlock_bystring(tdb, EVT_NEXT_RECORD);
+ return NT_STATUS_EVENTLOG_FILE_CORRUPT;
+ }
+
+ ret = tdb_store_int32(tdb, EVT_NEXT_RECORD, r->record_number + 1);
+ if (ret != 0) {
+ tdb_unlock_bystring(tdb, EVT_NEXT_RECORD);
+ return NT_STATUS_EVENTLOG_FILE_CORRUPT;
+ }
+ tdb_unlock_bystring(tdb, EVT_NEXT_RECORD);
+
+ if (record_number) {
+ *record_number = r->record_number;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+NTSTATUS evlog_push_record(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ struct EVENTLOGRECORD *r,
+ uint32_t *record_number)
+{
+ struct eventlog_Record_tdb *t;
+ NTSTATUS status;
+
+ t = talloc_zero(mem_ctx, struct eventlog_Record_tdb);
+ if (!t) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = evlog_evt_entry_to_tdb_entry(t, r, t);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(t);
+ return status;
+ }
+
+ status = evlog_push_record_tdb(mem_ctx, tdb, t, record_number);
+ talloc_free(t);
+
+ return status;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+NTSTATUS evlog_evt_entry_to_tdb_entry(TALLOC_CTX *mem_ctx,
+ const struct EVENTLOGRECORD *e,
+ struct eventlog_Record_tdb *t)
+{
+ uint32_t i;
+
+ ZERO_STRUCTP(t);
+
+ t->size = e->Length;
+ t->reserved = e->Reserved;
+ t->record_number = e->RecordNumber;
+ t->time_generated = e->TimeGenerated;
+ t->time_written = e->TimeWritten;
+ t->event_id = e->EventID;
+ t->event_type = e->EventType;
+ t->num_of_strings = e->NumStrings;
+ t->event_category = e->EventCategory;
+ t->reserved_flags = e->ReservedFlags;
+ t->closing_record_number = e->ClosingRecordNumber;
+
+ t->stringoffset = e->StringOffset;
+ t->sid_length = e->UserSidLength;
+ t->sid_offset = e->UserSidOffset;
+ t->data_length = e->DataLength;
+ t->data_offset = e->DataOffset;
+
+ t->source_name_len = 2 * strlen_m_term(e->SourceName);
+ t->source_name = talloc_strdup(mem_ctx, e->SourceName);
+ NT_STATUS_HAVE_NO_MEMORY(t->source_name);
+
+ t->computer_name_len = 2 * strlen_m_term(e->Computername);
+ t->computer_name = talloc_strdup(mem_ctx, e->Computername);
+ NT_STATUS_HAVE_NO_MEMORY(t->computer_name);
+
+ /* t->sid_padding; */
+ if (e->UserSidLength > 0) {
+ struct dom_sid_buf sid_str;
+ smb_ucs2_t *dummy = NULL;
+ t->sid_length = rpcstr_push_talloc(
+ mem_ctx,
+ &dummy,
+ dom_sid_str_buf(&e->UserSid, &sid_str));
+ if (t->sid_length == -1) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ t->sid = data_blob_talloc(mem_ctx, (uint8_t *)dummy, t->sid_length);
+ NT_STATUS_HAVE_NO_MEMORY(t->sid.data);
+ }
+
+ t->strings = talloc_array(mem_ctx, const char *, e->NumStrings);
+ for (i=0; i < e->NumStrings; i++) {
+ t->strings[i] = talloc_strdup(t->strings, e->Strings[i]);
+ NT_STATUS_HAVE_NO_MEMORY(t->strings[i]);
+ }
+
+ t->strings_len = 2 * ndr_size_string_array(t->strings, t->num_of_strings, LIBNDR_FLAG_STR_NULLTERM);
+ t->data = data_blob_talloc(mem_ctx, e->Data, e->DataLength);
+ /* t->padding = r->Pad; */
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+NTSTATUS evlog_tdb_entry_to_evt_entry(TALLOC_CTX *mem_ctx,
+ const struct eventlog_Record_tdb *t,
+ struct EVENTLOGRECORD *e)
+{
+ uint32_t i;
+
+ ZERO_STRUCTP(e);
+
+ e->Length = t->size;
+ e->Reserved = t->reserved;
+ e->RecordNumber = t->record_number;
+ e->TimeGenerated = t->time_generated;
+ e->TimeWritten = t->time_written;
+ e->EventID = t->event_id;
+ e->EventType = t->event_type;
+ e->NumStrings = t->num_of_strings;
+ e->EventCategory = t->event_category;
+ e->ReservedFlags = t->reserved_flags;
+ e->ClosingRecordNumber = t->closing_record_number;
+
+ e->StringOffset = t->stringoffset;
+ e->UserSidLength = t->sid_length;
+ e->UserSidOffset = t->sid_offset;
+ e->DataLength = t->data_length;
+ e->DataOffset = t->data_offset;
+
+ e->SourceName = talloc_strdup(mem_ctx, t->source_name);
+ NT_STATUS_HAVE_NO_MEMORY(e->SourceName);
+
+ e->Computername = talloc_strdup(mem_ctx, t->computer_name);
+ NT_STATUS_HAVE_NO_MEMORY(e->Computername);
+
+ if (t->sid_length > 0) {
+ char *sid_str = NULL;
+ size_t len;
+ if (!convert_string_talloc(mem_ctx, CH_UTF16, CH_UNIX,
+ t->sid.data, t->sid.length,
+ (void *)&sid_str, &len)) {
+ return NT_STATUS_INVALID_SID;
+ }
+ if (len > 0) {
+ bool ok = string_to_sid(&e->UserSid, sid_str);
+ if (!ok) {
+ return NT_STATUS_INVALID_SID;
+ }
+ }
+ TALLOC_FREE(sid_str);
+ }
+
+ e->Strings = talloc_array(mem_ctx, const char *, t->num_of_strings);
+ for (i=0; i < t->num_of_strings; i++) {
+ e->Strings[i] = talloc_strdup(e->Strings, t->strings[i]);
+ NT_STATUS_HAVE_NO_MEMORY(e->Strings[i]);
+ }
+
+ e->Data = (uint8_t *)talloc_memdup(mem_ctx, t->data.data, t->data_length);
+ e->Pad = talloc_strdup(mem_ctx, "");
+ NT_STATUS_HAVE_NO_MEMORY(e->Pad);
+
+ e->Length2 = t->size;
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+NTSTATUS evlog_convert_tdb_to_evt(TALLOC_CTX *mem_ctx,
+ ELOG_TDB *etdb,
+ DATA_BLOB *blob_p,
+ uint32_t *num_records_p)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ uint32_t num_records = 0;
+ struct EVENTLOG_EVT_FILE evt;
+ uint32_t count = 1;
+ size_t endoffset = 0;
+
+ ZERO_STRUCT(evt);
+
+ while (1) {
+
+ struct eventlog_Record_tdb *r;
+ struct EVENTLOGRECORD e;
+
+ r = evlog_pull_record_tdb(mem_ctx, etdb->tdb, count);
+ if (!r) {
+ break;
+ }
+
+ status = evlog_tdb_entry_to_evt_entry(mem_ctx, r, &e);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ endoffset += ndr_size_EVENTLOGRECORD(&e, 0);
+
+ ADD_TO_ARRAY(mem_ctx, struct EVENTLOGRECORD, e, &evt.records, &num_records);
+ count++;
+ }
+
+ evt.hdr.StartOffset = 0x30;
+ evt.hdr.EndOffset = evt.hdr.StartOffset + endoffset;
+ evt.hdr.CurrentRecordNumber = count;
+ evt.hdr.OldestRecordNumber = 1;
+ evt.hdr.MaxSize = tdb_fetch_int32(etdb->tdb, EVT_MAXSIZE);
+ evt.hdr.Flags = 0;
+ evt.hdr.Retention = tdb_fetch_int32(etdb->tdb, EVT_RETENTION);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(EVENTLOGHEADER, &evt.hdr);
+ }
+
+ evt.eof.BeginRecord = 0x30;
+ evt.eof.EndRecord = evt.hdr.StartOffset + endoffset;
+ evt.eof.CurrentRecordNumber = evt.hdr.CurrentRecordNumber;
+ evt.eof.OldestRecordNumber = evt.hdr.OldestRecordNumber;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(EVENTLOGEOF, &evt.eof);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, &evt,
+ (ndr_push_flags_fn_t)ndr_push_EVENTLOG_EVT_FILE);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+
+ *blob_p = blob;
+ *num_records_p = num_records;
+
+ done:
+ return status;
+}
diff --git a/source3/lib/eventlog/eventlog.h b/source3/lib/eventlog/eventlog.h
new file mode 100644
index 0000000..b485bfe
--- /dev/null
+++ b/source3/lib/eventlog/eventlog.h
@@ -0,0 +1,45 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Marcin Krzysztof Porwit 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <tdb.h>
+
+/* Defines for TDB keys */
+#define EVT_OLDEST_ENTRY "INFO/oldest_entry"
+#define EVT_NEXT_RECORD "INFO/next_record"
+#define EVT_VERSION "INFO/version"
+#define EVT_MAXSIZE "INFO/maxsize"
+#define EVT_RETENTION "INFO/retention"
+
+#define ELOG_APPL "Application"
+#define ELOG_SYS "System"
+#define ELOG_SEC "Security"
+
+typedef struct elog_tdb {
+ struct elog_tdb *prev, *next;
+ char *name;
+ TDB_CONTEXT *tdb;
+ int ref_count;
+} ELOG_TDB;
+
+#define ELOG_TDB_CTX(x) ((x)->tdb)
+
+#define EVENTLOG_DATABASE_VERSION_V1 1
+
+#include "../librpc/gen_ndr/ndr_eventlog.h"
+#include "lib/eventlog/proto.h"
diff --git a/source3/lib/eventlog/proto.h b/source3/lib/eventlog/proto.h
new file mode 100644
index 0000000..d3341ce
--- /dev/null
+++ b/source3/lib/eventlog/proto.h
@@ -0,0 +1,62 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Eventlog utility routines
+ *
+ * Copyright (C) Marcin Krzysztof Porwit 2005
+ * Copyright (C) Brian Moran 2005
+ * Copyright (C) Gerald (Jerry) Carter 2005
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIB_EVENTLOG_PROTO_H_
+#define _LIB_EVENTLOG_PROTO_H_
+
+/* The following definitions come from lib/eventlog/eventlog.c */
+
+TDB_CONTEXT *elog_init_tdb( char *tdbfilename );
+char *elog_tdbname(TALLOC_CTX *ctx, const char *name );
+int elog_tdb_size( TDB_CONTEXT * tdb, int *MaxSize, int *Retention );
+bool prune_eventlog( TDB_CONTEXT * tdb );
+ELOG_TDB *elog_open_tdb( const char *logname, bool force_clear, bool read_only );
+int elog_close_tdb( ELOG_TDB *etdb, bool force_close );
+bool parse_logentry( TALLOC_CTX *mem_ctx, char *line, struct eventlog_Record_tdb *entry, bool * eor );
+size_t fixup_eventlog_record_tdb(struct eventlog_Record_tdb *r);
+struct eventlog_Record_tdb *evlog_pull_record_tdb(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ uint32_t record_number);
+NTSTATUS evlog_push_record_tdb(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ struct eventlog_Record_tdb *r,
+ uint32_t *record_number);
+NTSTATUS evlog_push_record(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ struct EVENTLOGRECORD *r,
+ uint32_t *record_number);
+struct EVENTLOGRECORD *evlog_pull_record(TALLOC_CTX *mem_ctx,
+ TDB_CONTEXT *tdb,
+ uint32_t record_number);
+NTSTATUS evlog_evt_entry_to_tdb_entry(TALLOC_CTX *mem_ctx,
+ const struct EVENTLOGRECORD *e,
+ struct eventlog_Record_tdb *t);
+NTSTATUS evlog_tdb_entry_to_evt_entry(TALLOC_CTX *mem_ctx,
+ const struct eventlog_Record_tdb *t,
+ struct EVENTLOGRECORD *e);
+NTSTATUS evlog_convert_tdb_to_evt(TALLOC_CTX *mem_ctx,
+ ELOG_TDB *etdb,
+ DATA_BLOB *blob_p,
+ uint32_t *num_records_p);
+
+#endif /* _LIB_EVENTLOG_PROTO_H_ */
diff --git a/source3/lib/file_id.c b/source3/lib/file_id.c
new file mode 100644
index 0000000..545437a
--- /dev/null
+++ b/source3/lib/file_id.c
@@ -0,0 +1,82 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ file_id structure handling
+
+ Copyright (C) Andrew Tridgell 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/file_id.h"
+
+/*
+ return True if two file_id structures are equal
+ */
+bool file_id_equal(const struct file_id *id1, const struct file_id *id2)
+{
+ return id1->inode == id2->inode && id1->devid == id2->devid &&
+ id1->extid == id2->extid;
+}
+
+char *file_id_str_buf(struct file_id fid, struct file_id_buf *dst)
+{
+ snprintf(dst->buf,
+ sizeof(dst->buf),
+ "%"PRIu64":%"PRIu64":%"PRIu64,
+ fid.devid,
+ fid.inode,
+ fid.extid);
+ return dst->buf;
+}
+
+/*
+ push a 16 byte version of a file id into a buffer. This ignores the extid
+ and is needed when dev/inodes are stored in persistent storage (tdbs).
+ */
+void push_file_id_16(char *buf, const struct file_id *id)
+{
+ SIVAL(buf, 0, id->devid&0xFFFFFFFF);
+ SIVAL(buf, 4, id->devid>>32);
+ SIVAL(buf, 8, id->inode&0xFFFFFFFF);
+ SIVAL(buf, 12, id->inode>>32);
+}
+
+/*
+ push a 24 byte version of a file id into a buffer
+ */
+void push_file_id_24(char *buf, const struct file_id *id)
+{
+ SIVAL(buf, 0, id->devid&0xFFFFFFFF);
+ SIVAL(buf, 4, id->devid>>32);
+ SIVAL(buf, 8, id->inode&0xFFFFFFFF);
+ SIVAL(buf, 12, id->inode>>32);
+ SIVAL(buf, 16, id->extid&0xFFFFFFFF);
+ SIVAL(buf, 20, id->extid>>32);
+}
+
+/*
+ pull a 24 byte version of a file id from a buffer
+ */
+void pull_file_id_24(const char *buf, struct file_id *id)
+{
+ ZERO_STRUCTP(id);
+ id->devid = IVAL(buf, 0);
+ id->devid |= ((uint64_t)IVAL(buf,4))<<32;
+ id->inode = IVAL(buf, 8);
+ id->inode |= ((uint64_t)IVAL(buf,12))<<32;
+ id->extid = IVAL(buf, 16);
+ id->extid |= ((uint64_t)IVAL(buf,20))<<32;
+}
diff --git a/source3/lib/file_id.h b/source3/lib/file_id.h
new file mode 100644
index 0000000..255b1dd
--- /dev/null
+++ b/source3/lib/file_id.h
@@ -0,0 +1,44 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ file_id structure handling
+
+ Copyright (C) Andrew Tridgell 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __LIB_FILE_ID_H__
+#define __LIB_FILE_ID_H__
+
+#include "librpc/gen_ndr/file_id.h"
+
+/* The following definitions come from lib/file_id.c */
+
+bool file_id_equal(const struct file_id *id1, const struct file_id *id2);
+
+/*
+ * strlen("18446744073709551615")=20 times 3 plus 2 colons plus trailing 0
+ */
+struct file_id_buf { char buf[63]; };
+char *file_id_str_buf(struct file_id fid, struct file_id_buf *dst);
+
+/*
+ an allocated string for a file_id structure
+ */
+void push_file_id_16(char *buf, const struct file_id *id);
+void push_file_id_24(char *buf, const struct file_id *id);
+void pull_file_id_24(const char *buf, struct file_id *id);
+
+#endif
diff --git a/source3/lib/filename_util.c b/source3/lib/filename_util.c
new file mode 100644
index 0000000..3f8e903
--- /dev/null
+++ b/source3/lib/filename_util.c
@@ -0,0 +1,406 @@
+/*
+ Unix SMB/CIFS implementation.
+ Filename utility functions.
+ Copyright (C) Tim Prouty 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "includes.h"
+
+/**
+ * XXX: This is temporary and there should be no callers of this outside of
+ * this file once smb_filename is plumbed through all path based operations.
+ * The one legitimate caller currently is smb_fname_str_dbg(), which this
+ * could be made static for.
+ */
+NTSTATUS get_full_smb_filename(TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname,
+ char **full_name)
+{
+ if (smb_fname->stream_name) {
+ /* stream_name must always be NULL if there is no stream. */
+ SMB_ASSERT(smb_fname->stream_name[0] != '\0');
+
+ *full_name = talloc_asprintf(ctx, "%s%s", smb_fname->base_name,
+ smb_fname->stream_name);
+ } else {
+ *full_name = talloc_strdup(ctx, smb_fname->base_name);
+ }
+
+ if (!*full_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * There are actually legitimate callers of this such as functions that
+ * enumerate streams using the vfs_streaminfo interface and then want to
+ * operate on each stream.
+ */
+struct smb_filename *synthetic_smb_fname(TALLOC_CTX *mem_ctx,
+ const char *base_name,
+ const char *stream_name,
+ const SMB_STRUCT_STAT *psbuf,
+ NTTIME twrp,
+ uint32_t flags)
+{
+ /* Setup the base_name/stream_name. */
+
+ struct smb_filename smb_fname_loc = {
+ .base_name = discard_const_p(char, base_name),
+ .stream_name = discard_const_p(char, stream_name),
+ .flags = flags,
+ .twrp = twrp,
+ };
+
+ /* Copy the psbuf if one was given. */
+ if (psbuf)
+ smb_fname_loc.st = *psbuf;
+
+ /* Let cp_smb_filename() do the heavy lifting. */
+ return cp_smb_filename(mem_ctx, &smb_fname_loc);
+}
+
+/**
+ * Utility function used by VFS calls that must *NOT* operate
+ * on a stream filename, only the base_name.
+ */
+struct smb_filename *cp_smb_filename_nostream(TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in)
+{
+ struct smb_filename smb_fname_loc = *smb_fname_in;
+ struct smb_filename *smb_fname = NULL;
+
+ smb_fname_loc.stream_name = NULL;
+
+ smb_fname = cp_smb_filename(mem_ctx, &smb_fname_loc);
+ return smb_fname;
+}
+
+/**
+ * There are a few legitimate users of this.
+ */
+struct smb_filename *synthetic_smb_fname_split(TALLOC_CTX *ctx,
+ const char *fname,
+ bool posix_path)
+{
+ char *stream_name = NULL;
+ char *base_name = NULL;
+ struct smb_filename *ret;
+ bool ok;
+
+ if (posix_path) {
+ /* No stream name looked for. */
+ return synthetic_smb_fname(ctx,
+ fname,
+ NULL,
+ NULL,
+ 0,
+ SMB_FILENAME_POSIX_PATH);
+ }
+
+ ok = split_stream_filename(ctx,
+ fname,
+ &base_name,
+ &stream_name);
+ if (!ok) {
+ return NULL;
+ }
+
+ ret = synthetic_smb_fname(ctx,
+ base_name,
+ stream_name,
+ NULL,
+ 0,
+ 0);
+ TALLOC_FREE(base_name);
+ TALLOC_FREE(stream_name);
+ return ret;
+}
+
+/**
+ * Return a string using the talloc_tos()
+ */
+const char *smb_fname_str_dbg(const struct smb_filename *smb_fname)
+{
+ char *fname = NULL;
+ time_t t;
+ struct tm tm;
+ struct tm *ptm = NULL;
+ fstring tstr;
+ ssize_t slen;
+ NTSTATUS status;
+
+ if (smb_fname == NULL) {
+ return "";
+ }
+ status = get_full_smb_filename(talloc_tos(), smb_fname, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return "";
+ }
+ if (smb_fname->twrp == 0) {
+ return fname;
+ }
+
+ t = nt_time_to_unix(smb_fname->twrp);
+ ptm = gmtime_r(&t, &tm);
+ if (ptm == NULL) {
+ return "";
+ }
+
+ slen = strftime(tstr, sizeof(tstr), GMT_FORMAT, &tm);
+ if (slen == 0) {
+ return "";
+ }
+
+ fname = talloc_asprintf_append_buffer(
+ fname, " {%s}", tstr);
+ if (fname == NULL) {
+ return "";
+ }
+ return fname;
+}
+
+/**
+ * Return a debug string of the path name of an fsp using the talloc_tos().
+ */
+const char *fsp_str_dbg(const struct files_struct *fsp)
+{
+ const char *name = NULL;
+
+ name = smb_fname_str_dbg(fsp->fsp_name);
+ if (name == NULL) {
+ return "";
+ }
+
+ return name;
+}
+
+/**
+ * Create a debug string for the fnum of an fsp.
+ *
+ * This is allocated to talloc_tos() or a string constant
+ * in certain corner cases. The returned string should
+ * hence not be free'd directly but only via the talloc stack.
+ */
+const char *fsp_fnum_dbg(const struct files_struct *fsp)
+{
+ char *str;
+
+ if (fsp == NULL) {
+ return "fnum [fsp is NULL]";
+ }
+
+ if (fsp->fnum == FNUM_FIELD_INVALID) {
+ return "fnum [invalid value]";
+ }
+
+ str = talloc_asprintf(talloc_tos(), "fnum %"PRIu64, fsp->fnum);
+ if (str == NULL) {
+ DEBUG(1, ("%s: talloc_asprintf failed\n", __FUNCTION__));
+ return "fnum [talloc failed!]";
+ }
+
+ return str;
+}
+
+struct smb_filename *cp_smb_filename(TALLOC_CTX *mem_ctx,
+ const struct smb_filename *in)
+{
+ struct smb_filename *out;
+ size_t base_len = 0;
+ size_t stream_len = 0;
+ int num = 0;
+
+ /* stream_name must always be NULL if there is no stream. */
+ if (in->stream_name) {
+ SMB_ASSERT(in->stream_name[0] != '\0');
+ }
+
+ if (in->base_name != NULL) {
+ base_len = strlen(in->base_name) + 1;
+ num += 1;
+ }
+ if (in->stream_name != NULL) {
+ stream_len = strlen(in->stream_name) + 1;
+ num += 1;
+ }
+
+ out = talloc_pooled_object(mem_ctx, struct smb_filename,
+ num, stream_len + base_len);
+ if (out == NULL) {
+ return NULL;
+ }
+ ZERO_STRUCTP(out);
+
+ /*
+ * The following allocations cannot fail as we
+ * pre-allocated space for them in the out pooled
+ * object.
+ */
+ if (in->base_name != NULL) {
+ out->base_name = talloc_memdup(
+ out, in->base_name, base_len);
+ talloc_set_name_const(out->base_name,
+ out->base_name);
+ }
+ if (in->stream_name != NULL) {
+ out->stream_name = talloc_memdup(
+ out, in->stream_name, stream_len);
+ talloc_set_name_const(out->stream_name,
+ out->stream_name);
+ }
+ out->flags = in->flags;
+ out->st = in->st;
+ out->twrp = in->twrp;
+ return out;
+}
+
+static void assert_valid_stream_smb_fname(const struct smb_filename *smb_fname)
+{
+ /* stream_name must always be NULL if there is no stream. */
+ if (smb_fname->stream_name) {
+ SMB_ASSERT(smb_fname->stream_name[0] != '\0');
+ }
+
+ if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ SMB_ASSERT(smb_fname->stream_name == NULL);
+ }
+}
+
+/****************************************************************************
+ Simple check to determine if a smb_fname is a real named stream or the
+ default stream.
+ ***************************************************************************/
+
+bool is_ntfs_stream_smb_fname(const struct smb_filename *smb_fname)
+{
+ assert_valid_stream_smb_fname(smb_fname);
+
+ return (smb_fname->stream_name != NULL);
+}
+
+/****************************************************************************
+ Simple check to determine if a smb_fname is pointing to a normal file or
+ a named stream that is not the default stream "::$DATA".
+
+ foo -> false
+ foo::$DATA -> false
+ foo:bar -> true
+ foo:bar:$DATA -> true
+
+ ***************************************************************************/
+
+bool is_named_stream(const struct smb_filename *smb_fname)
+{
+ assert_valid_stream_smb_fname(smb_fname);
+
+ if (smb_fname->stream_name == NULL) {
+ return false;
+ }
+
+ if (strequal_m(smb_fname->stream_name, "::$DATA")) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Returns true if the filename's stream == "::$DATA"
+ ***************************************************************************/
+bool is_ntfs_default_stream_smb_fname(const struct smb_filename *smb_fname)
+{
+ assert_valid_stream_smb_fname(smb_fname);
+
+ if (smb_fname->stream_name == NULL) {
+ return false;
+ }
+
+ return strequal_m(smb_fname->stream_name, "::$DATA");
+}
+
+/****************************************************************************
+ Filter out Windows invalid EA names (list probed from Windows 2012).
+****************************************************************************/
+
+static const char bad_ea_name_chars[] = "\"*+,/:;<=>?[\\]|";
+
+bool is_invalid_windows_ea_name(const char *name)
+{
+ int i;
+ /* EA name is pulled as ascii so we can examine
+ individual bytes here. */
+ for (i = 0; name[i] != 0; i++) {
+ int val = (name[i] & 0xff);
+ if (val < ' ' || strchr(bad_ea_name_chars, val)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ea_list_has_invalid_name(struct ea_list *ea_list)
+{
+ for (;ea_list; ea_list = ea_list->next) {
+ if (is_invalid_windows_ea_name(ea_list->ea.name)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/****************************************************************************
+ Split an incoming name into tallocd filename and stream components.
+ Returns true on success, false on out of memory.
+****************************************************************************/
+
+bool split_stream_filename(TALLOC_CTX *ctx,
+ const char *filename_in,
+ char **filename_out,
+ char **streamname_out)
+{
+ const char *stream_name = NULL;
+ char *stream_out = NULL;
+ char *file_out = NULL;
+
+ stream_name = strchr_m(filename_in, ':');
+
+ if (stream_name) {
+ stream_out = talloc_strdup(ctx, stream_name);
+ if (stream_out == NULL) {
+ return false;
+ }
+ file_out = talloc_strndup(ctx,
+ filename_in,
+ PTR_DIFF(stream_name, filename_in));
+ } else {
+ file_out = talloc_strdup(ctx, filename_in);
+ }
+
+ if (file_out == NULL) {
+ TALLOC_FREE(stream_out);
+ return false;
+ }
+
+ if (filename_out) {
+ *filename_out = file_out;
+ }
+ if (streamname_out) {
+ *streamname_out = stream_out;
+ }
+ return true;
+}
diff --git a/source3/lib/fstring.c b/source3/lib/fstring.c
new file mode 100644
index 0000000..3ed1db1
--- /dev/null
+++ b/source3/lib/fstring.c
@@ -0,0 +1,67 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ fixed string functions
+
+ Copyright (C) Igor Vergeichik <iverg@mail.ru> 2001
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Martin Pool 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+
+size_t push_ascii_fstring(void *dest, const char *src)
+{
+ return push_ascii(dest, src, sizeof(fstring), STR_TERMINATE);
+}
+
+/********************************************************************
+ Push an nstring (a netbios string)
+ this function uses convert_string_error() to avoid common debug
+ warnings where is unable to convert strings to CH_DOS. The target
+ string is truncated at the first character that cannot be converted
+ The target is always null terminated.
+********************************************************************/
+
+size_t push_ascii_nstring(void *dest, const char *src)
+{
+ size_t converted_size = 0;
+ bool ret;
+
+ errno = 0;
+ ret = convert_string_error(CH_UNIX, CH_DOS, src, -1, dest, sizeof(nstring), &converted_size);
+ if (ret || errno == E2BIG) {
+ SCVAL(dest, sizeof(nstring)-1, 0);
+ } else {
+ SCVAL(dest, 0, 0);
+ }
+ return ret ? converted_size : (size_t)-1;
+}
+
+size_t pull_ascii_fstring(char *dest, const void *src)
+{
+ return pull_ascii(dest, src, sizeof(fstring), -1, STR_TERMINATE);
+}
+
+/* When pulling an nstring it can expand into a larger size (dos cp -> utf8). Cope with this. */
+
+size_t pull_ascii_nstring(char *dest, size_t dest_len, const void *src)
+{
+ return pull_ascii(dest, src, dest_len, sizeof(nstring), STR_TERMINATE);
+}
+
diff --git a/source3/lib/g_lock.c b/source3/lib/g_lock.c
new file mode 100644
index 0000000..33f088b
--- /dev/null
+++ b/source3/lib/g_lock.c
@@ -0,0 +1,1984 @@
+/*
+ Unix SMB/CIFS implementation.
+ global locks based on dbwrap and messaging
+ Copyright (C) 2009 by Volker Lendecke
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "lib/util/debug.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/samba_util.h"
+#include "lib/util_path.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_watch.h"
+#include "g_lock.h"
+#include "util_tdb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "messages.h"
+#include "serverid.h"
+
+struct g_lock_ctx {
+ struct db_context *db;
+ struct messaging_context *msg;
+ enum dbwrap_lock_order lock_order;
+ bool busy;
+};
+
+struct g_lock {
+ struct server_id exclusive;
+ size_t num_shared;
+ uint8_t *shared;
+ uint64_t unique_lock_epoch;
+ uint64_t unique_data_epoch;
+ size_t datalen;
+ uint8_t *data;
+};
+
+static bool g_lock_parse(uint8_t *buf, size_t buflen, struct g_lock *lck)
+{
+ struct server_id exclusive;
+ size_t num_shared, shared_len;
+ uint64_t unique_lock_epoch;
+ uint64_t unique_data_epoch;
+
+ if (buflen < (SERVER_ID_BUF_LENGTH + /* exclusive */
+ sizeof(uint64_t) + /* seqnum */
+ sizeof(uint32_t))) { /* num_shared */
+ struct g_lock ret = {
+ .exclusive.pid = 0,
+ .unique_lock_epoch = generate_unique_u64(0),
+ .unique_data_epoch = generate_unique_u64(0),
+ };
+ *lck = ret;
+ return true;
+ }
+
+ server_id_get(&exclusive, buf);
+ buf += SERVER_ID_BUF_LENGTH;
+ buflen -= SERVER_ID_BUF_LENGTH;
+
+ unique_lock_epoch = BVAL(buf, 0);
+ buf += sizeof(uint64_t);
+ buflen -= sizeof(uint64_t);
+
+ unique_data_epoch = BVAL(buf, 0);
+ buf += sizeof(uint64_t);
+ buflen -= sizeof(uint64_t);
+
+ num_shared = IVAL(buf, 0);
+ buf += sizeof(uint32_t);
+ buflen -= sizeof(uint32_t);
+
+ if (num_shared > buflen/SERVER_ID_BUF_LENGTH) {
+ DBG_DEBUG("num_shared=%zu, buflen=%zu\n",
+ num_shared,
+ buflen);
+ return false;
+ }
+
+ shared_len = num_shared * SERVER_ID_BUF_LENGTH;
+
+ *lck = (struct g_lock) {
+ .exclusive = exclusive,
+ .num_shared = num_shared,
+ .shared = buf,
+ .unique_lock_epoch = unique_lock_epoch,
+ .unique_data_epoch = unique_data_epoch,
+ .datalen = buflen-shared_len,
+ .data = buf+shared_len,
+ };
+
+ return true;
+}
+
+static void g_lock_get_shared(const struct g_lock *lck,
+ size_t i,
+ struct server_id *shared)
+{
+ if (i >= lck->num_shared) {
+ abort();
+ }
+ server_id_get(shared, lck->shared + i*SERVER_ID_BUF_LENGTH);
+}
+
+static void g_lock_del_shared(struct g_lock *lck, size_t i)
+{
+ if (i >= lck->num_shared) {
+ abort();
+ }
+ lck->num_shared -= 1;
+ if (i < lck->num_shared) {
+ memcpy(lck->shared + i*SERVER_ID_BUF_LENGTH,
+ lck->shared + lck->num_shared*SERVER_ID_BUF_LENGTH,
+ SERVER_ID_BUF_LENGTH);
+ }
+}
+
+static NTSTATUS g_lock_store(
+ struct db_record *rec,
+ struct g_lock *lck,
+ struct server_id *new_shared,
+ const TDB_DATA *new_dbufs,
+ size_t num_new_dbufs)
+{
+ uint8_t exclusive[SERVER_ID_BUF_LENGTH];
+ uint8_t seqnum_buf[sizeof(uint64_t)*2];
+ uint8_t sizebuf[sizeof(uint32_t)];
+ uint8_t new_shared_buf[SERVER_ID_BUF_LENGTH];
+
+ struct TDB_DATA dbufs[6 + num_new_dbufs];
+
+ dbufs[0] = (TDB_DATA) {
+ .dptr = exclusive, .dsize = sizeof(exclusive),
+ };
+ dbufs[1] = (TDB_DATA) {
+ .dptr = seqnum_buf, .dsize = sizeof(seqnum_buf),
+ };
+ dbufs[2] = (TDB_DATA) {
+ .dptr = sizebuf, .dsize = sizeof(sizebuf),
+ };
+ dbufs[3] = (TDB_DATA) {
+ .dptr = lck->shared,
+ .dsize = lck->num_shared * SERVER_ID_BUF_LENGTH,
+ };
+ dbufs[4] = (TDB_DATA) { 0 };
+ dbufs[5] = (TDB_DATA) {
+ .dptr = lck->data, .dsize = lck->datalen,
+ };
+
+ if (num_new_dbufs != 0) {
+ memcpy(&dbufs[6],
+ new_dbufs,
+ num_new_dbufs * sizeof(TDB_DATA));
+ }
+
+ server_id_put(exclusive, lck->exclusive);
+ SBVAL(seqnum_buf, 0, lck->unique_lock_epoch);
+ SBVAL(seqnum_buf, 8, lck->unique_data_epoch);
+
+ if (new_shared != NULL) {
+ if (lck->num_shared >= UINT32_MAX) {
+ return NT_STATUS_BUFFER_OVERFLOW;
+ }
+
+ server_id_put(new_shared_buf, *new_shared);
+
+ dbufs[4] = (TDB_DATA) {
+ .dptr = new_shared_buf,
+ .dsize = sizeof(new_shared_buf),
+ };
+
+ lck->num_shared += 1;
+ }
+
+ SIVAL(sizebuf, 0, lck->num_shared);
+
+ return dbwrap_record_storev(rec, dbufs, ARRAY_SIZE(dbufs), 0);
+}
+
+struct g_lock_ctx *g_lock_ctx_init_backend(
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct db_context **backend)
+{
+ struct g_lock_ctx *result;
+
+ result = talloc_zero(mem_ctx, struct g_lock_ctx);
+ if (result == NULL) {
+ return NULL;
+ }
+ result->msg = msg;
+ result->lock_order = DBWRAP_LOCK_ORDER_NONE;
+
+ result->db = db_open_watched(result, backend, msg);
+ if (result->db == NULL) {
+ DBG_WARNING("db_open_watched failed\n");
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ return result;
+}
+
+void g_lock_set_lock_order(struct g_lock_ctx *ctx,
+ enum dbwrap_lock_order lock_order)
+{
+ ctx->lock_order = lock_order;
+}
+
+struct g_lock_ctx *g_lock_ctx_init(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg)
+{
+ char *db_path = NULL;
+ struct db_context *backend = NULL;
+ struct g_lock_ctx *ctx = NULL;
+
+ db_path = lock_path(mem_ctx, "g_lock.tdb");
+ if (db_path == NULL) {
+ return NULL;
+ }
+
+ backend = db_open(
+ mem_ctx,
+ db_path,
+ 0,
+ TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH|TDB_VOLATILE,
+ O_RDWR|O_CREAT,
+ 0600,
+ DBWRAP_LOCK_ORDER_3,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(db_path);
+ if (backend == NULL) {
+ DBG_WARNING("Could not open g_lock.tdb\n");
+ return NULL;
+ }
+
+ ctx = g_lock_ctx_init_backend(mem_ctx, msg, &backend);
+ return ctx;
+}
+
+static void g_lock_cleanup_dead(
+ struct g_lock *lck,
+ struct server_id *dead_blocker)
+{
+ bool exclusive_died;
+ struct server_id_buf tmp;
+
+ if (dead_blocker == NULL) {
+ return;
+ }
+
+ exclusive_died = server_id_equal(dead_blocker, &lck->exclusive);
+
+ if (exclusive_died) {
+ DBG_DEBUG("Exclusive holder %s died\n",
+ server_id_str_buf(lck->exclusive, &tmp));
+ lck->exclusive.pid = 0;
+ }
+
+ if (lck->num_shared != 0) {
+ bool shared_died;
+ struct server_id shared;
+
+ g_lock_get_shared(lck, 0, &shared);
+ shared_died = server_id_equal(dead_blocker, &shared);
+
+ if (shared_died) {
+ DBG_DEBUG("Shared holder %s died\n",
+ server_id_str_buf(shared, &tmp));
+ g_lock_del_shared(lck, 0);
+ }
+ }
+}
+
+static ssize_t g_lock_find_shared(
+ struct g_lock *lck,
+ const struct server_id *self)
+{
+ size_t i;
+
+ for (i=0; i<lck->num_shared; i++) {
+ struct server_id shared;
+ bool same;
+
+ g_lock_get_shared(lck, i, &shared);
+
+ same = server_id_equal(self, &shared);
+ if (same) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+static void g_lock_cleanup_shared(struct g_lock *lck)
+{
+ size_t i;
+ struct server_id check;
+ bool exists;
+
+ if (lck->num_shared == 0) {
+ return;
+ }
+
+ /*
+ * Read locks can stay around forever if the process dies. Do
+ * a heuristic check for process existence: Check one random
+ * process for existence. Hopefully this will keep runaway
+ * read locks under control.
+ */
+ i = generate_random() % lck->num_shared;
+ g_lock_get_shared(lck, i, &check);
+
+ exists = serverid_exists(&check);
+ if (!exists) {
+ struct server_id_buf tmp;
+ DBG_DEBUG("Shared locker %s died -- removing\n",
+ server_id_str_buf(check, &tmp));
+ g_lock_del_shared(lck, i);
+ }
+}
+
+struct g_lock_lock_cb_state {
+ struct g_lock_ctx *ctx;
+ struct db_record *rec;
+ struct g_lock *lck;
+ struct server_id *new_shared;
+ g_lock_lock_cb_fn_t cb_fn;
+ void *cb_private;
+ TALLOC_CTX *update_mem_ctx;
+ TDB_DATA updated_data;
+ bool existed;
+ bool modified;
+ bool unlock;
+};
+
+NTSTATUS g_lock_lock_cb_dump(struct g_lock_lock_cb_state *cb_state,
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data),
+ void *private_data)
+{
+ struct g_lock *lck = cb_state->lck;
+
+ /* We allow a cn_fn only for G_LOCK_WRITE for now... */
+ SMB_ASSERT(lck->num_shared == 0);
+
+ fn(lck->exclusive,
+ 0, /* num_shared */
+ NULL, /* shared */
+ lck->data,
+ lck->datalen,
+ private_data);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS g_lock_lock_cb_writev(struct g_lock_lock_cb_state *cb_state,
+ const TDB_DATA *dbufs,
+ size_t num_dbufs)
+{
+ NTSTATUS status;
+
+ status = dbwrap_merge_dbufs(&cb_state->updated_data,
+ cb_state->update_mem_ctx,
+ dbufs, num_dbufs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ cb_state->modified = true;
+ cb_state->lck->data = cb_state->updated_data.dptr;
+ cb_state->lck->datalen = cb_state->updated_data.dsize;
+
+ return NT_STATUS_OK;
+}
+
+void g_lock_lock_cb_unlock(struct g_lock_lock_cb_state *cb_state)
+{
+ cb_state->unlock = true;
+}
+
+struct g_lock_lock_cb_watch_data_state {
+ struct tevent_context *ev;
+ struct g_lock_ctx *ctx;
+ TDB_DATA key;
+ struct server_id blocker;
+ bool blockerdead;
+ uint64_t unique_lock_epoch;
+ uint64_t unique_data_epoch;
+ uint64_t watch_instance;
+ NTSTATUS status;
+};
+
+static void g_lock_lock_cb_watch_data_done(struct tevent_req *subreq);
+
+struct tevent_req *g_lock_lock_cb_watch_data_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_lock_cb_state *cb_state,
+ struct server_id blocker)
+{
+ struct tevent_req *req = NULL;
+ struct g_lock_lock_cb_watch_data_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ TDB_DATA key = dbwrap_record_get_key(cb_state->rec);
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct g_lock_lock_cb_watch_data_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctx = cb_state->ctx;
+ state->blocker = blocker;
+
+ state->key = tdb_data_talloc_copy(state, key);
+ if (tevent_req_nomem(state->key.dptr, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->unique_lock_epoch = cb_state->lck->unique_lock_epoch;
+ state->unique_data_epoch = cb_state->lck->unique_data_epoch;
+
+ DBG_DEBUG("state->unique_data_epoch=%"PRIu64"\n", state->unique_data_epoch);
+
+ subreq = dbwrap_watched_watch_send(
+ state, state->ev, cb_state->rec, 0, state->blocker);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, g_lock_lock_cb_watch_data_done, req);
+
+ return req;
+}
+
+static void g_lock_lock_cb_watch_data_done_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct g_lock_lock_cb_watch_data_state *state = tevent_req_data(
+ req, struct g_lock_lock_cb_watch_data_state);
+ struct tevent_req *subreq = NULL;
+ struct g_lock lck;
+ bool ok;
+
+ ok = g_lock_parse(value.dptr, value.dsize, &lck);
+ if (!ok) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+
+ if (lck.unique_data_epoch != state->unique_data_epoch) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ DBG_DEBUG("lck.unique_data_epoch=%"PRIu64", "
+ "state->unique_data_epoch=%"PRIu64"\n",
+ lck.unique_data_epoch,
+ state->unique_data_epoch);
+ state->status = NT_STATUS_OK;
+ return;
+ }
+
+ /*
+ * The lock epoch changed, so we better
+ * remove ourself from the waiter list
+ * (most likely the first position)
+ * and re-add us at the end of the list.
+ *
+ * This gives other lock waiters a change
+ * to make progress.
+ *
+ * Otherwise we'll keep our waiter instance alive,
+ * keep waiting (most likely at first position).
+ */
+ if (lck.unique_lock_epoch != state->unique_lock_epoch) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ state->watch_instance = dbwrap_watched_watch_add_instance(rec);
+ state->unique_lock_epoch = lck.unique_lock_epoch;
+ }
+
+ subreq = dbwrap_watched_watch_send(
+ state, state->ev, rec, state->watch_instance, state->blocker);
+ if (subreq == NULL) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ tevent_req_set_callback(subreq, g_lock_lock_cb_watch_data_done, req);
+
+ state->status = NT_STATUS_EVENT_PENDING;
+}
+
+static void g_lock_lock_cb_watch_data_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct g_lock_lock_cb_watch_data_state *state = tevent_req_data(
+ req, struct g_lock_lock_cb_watch_data_state);
+ NTSTATUS status;
+ uint64_t instance = 0;
+
+ status = dbwrap_watched_watch_recv(
+ subreq, &instance, &state->blockerdead, &state->blocker);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dbwrap_watched_watch_recv returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ state->watch_instance = instance;
+
+ status = dbwrap_do_locked(
+ state->ctx->db, state->key, g_lock_lock_cb_watch_data_done_fn, req);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dbwrap_do_locked returned %s\n", nt_errstr(status));
+ return;
+ }
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_EVENT_PENDING)) {
+ return;
+ }
+ if (tevent_req_nterror(req, state->status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS g_lock_lock_cb_watch_data_recv(
+ struct tevent_req *req,
+ bool *blockerdead,
+ struct server_id *blocker)
+{
+ struct g_lock_lock_cb_watch_data_state *state = tevent_req_data(
+ req, struct g_lock_lock_cb_watch_data_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (blockerdead != NULL) {
+ *blockerdead = state->blockerdead;
+ }
+ if (blocker != NULL) {
+ *blocker = state->blocker;
+ }
+
+ return NT_STATUS_OK;
+}
+
+void g_lock_lock_cb_wake_watchers(struct g_lock_lock_cb_state *cb_state)
+{
+ struct g_lock *lck = cb_state->lck;
+
+ lck->unique_data_epoch = generate_unique_u64(lck->unique_data_epoch);
+ cb_state->modified = true;
+}
+
+static NTSTATUS g_lock_lock_cb_run_and_store(struct g_lock_lock_cb_state *cb_state)
+{
+ struct g_lock *lck = cb_state->lck;
+ NTSTATUS success_status = NT_STATUS_OK;
+ NTSTATUS status;
+
+ if (cb_state->cb_fn != NULL) {
+
+ SMB_ASSERT(lck->num_shared == 0);
+ SMB_ASSERT(cb_state->new_shared == NULL);
+
+ if (cb_state->ctx->lock_order != DBWRAP_LOCK_ORDER_NONE) {
+ const char *name = dbwrap_name(cb_state->ctx->db);
+ dbwrap_lock_order_lock(name, cb_state->ctx->lock_order);
+ }
+
+ cb_state->ctx->busy = true;
+ cb_state->cb_fn(cb_state, cb_state->cb_private);
+ cb_state->ctx->busy = false;
+
+ if (cb_state->ctx->lock_order != DBWRAP_LOCK_ORDER_NONE) {
+ const char *name = dbwrap_name(cb_state->ctx->db);
+ dbwrap_lock_order_unlock(name, cb_state->ctx->lock_order);
+ }
+ }
+
+ if (cb_state->unlock) {
+ /*
+ * Unlocked should wake up watchers.
+ *
+ * We no longer need the lock, so
+ * force a wakeup of the next watchers,
+ * even if we don't do any update.
+ */
+ dbwrap_watched_watch_reset_alerting(cb_state->rec);
+ dbwrap_watched_watch_force_alerting(cb_state->rec);
+ if (!cb_state->modified) {
+ /*
+ * The record was not changed at
+ * all, so we can also avoid
+ * storing the lck.unique_lock_epoch
+ * change
+ */
+ return NT_STATUS_WAS_UNLOCKED;
+ }
+ lck->exclusive = (struct server_id) { .pid = 0 };
+ cb_state->new_shared = NULL;
+
+ if (lck->datalen == 0) {
+ if (!cb_state->existed) {
+ return NT_STATUS_WAS_UNLOCKED;
+ }
+
+ status = dbwrap_record_delete(cb_state->rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_record_delete() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ return NT_STATUS_WAS_UNLOCKED;
+ }
+
+ success_status = NT_STATUS_WAS_UNLOCKED;
+ }
+
+ status = g_lock_store(cb_state->rec,
+ cb_state->lck,
+ cb_state->new_shared,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_store() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ return success_status;
+}
+
+struct g_lock_lock_state {
+ struct tevent_context *ev;
+ struct g_lock_ctx *ctx;
+ TDB_DATA key;
+ enum g_lock_type type;
+ bool retry;
+ g_lock_lock_cb_fn_t cb_fn;
+ void *cb_private;
+};
+
+struct g_lock_lock_fn_state {
+ struct g_lock_lock_state *req_state;
+ struct server_id *dead_blocker;
+
+ struct tevent_req *watch_req;
+ uint64_t watch_instance;
+ NTSTATUS status;
+};
+
+static int g_lock_lock_state_destructor(struct g_lock_lock_state *s);
+
+static NTSTATUS g_lock_trylock(
+ struct db_record *rec,
+ struct g_lock_lock_fn_state *state,
+ TDB_DATA data,
+ struct server_id *blocker)
+{
+ struct g_lock_lock_state *req_state = state->req_state;
+ struct server_id self = messaging_server_id(req_state->ctx->msg);
+ enum g_lock_type type = req_state->type;
+ bool retry = req_state->retry;
+ struct g_lock lck = { .exclusive.pid = 0 };
+ struct g_lock_lock_cb_state cb_state = {
+ .ctx = req_state->ctx,
+ .rec = rec,
+ .lck = &lck,
+ .cb_fn = req_state->cb_fn,
+ .cb_private = req_state->cb_private,
+ .existed = data.dsize != 0,
+ .update_mem_ctx = talloc_tos(),
+ };
+ struct server_id_buf tmp;
+ NTSTATUS status;
+ bool ok;
+
+ ok = g_lock_parse(data.dptr, data.dsize, &lck);
+ if (!ok) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ DBG_DEBUG("g_lock_parse failed\n");
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ g_lock_cleanup_dead(&lck, state->dead_blocker);
+
+ lck.unique_lock_epoch = generate_unique_u64(lck.unique_lock_epoch);
+
+ if (lck.exclusive.pid != 0) {
+ bool self_exclusive = server_id_equal(&self, &lck.exclusive);
+
+ if (!self_exclusive) {
+ bool exists = serverid_exists(&lck.exclusive);
+ if (!exists) {
+ lck.exclusive = (struct server_id) { .pid=0 };
+ goto noexclusive;
+ }
+
+ DBG_DEBUG("%s has an exclusive lock\n",
+ server_id_str_buf(lck.exclusive, &tmp));
+
+ if (type == G_LOCK_DOWNGRADE) {
+ struct server_id_buf tmp2;
+
+ dbwrap_watched_watch_remove_instance(rec,
+ state->watch_instance);
+
+ DBG_DEBUG("%s: Trying to downgrade %s\n",
+ server_id_str_buf(self, &tmp),
+ server_id_str_buf(
+ lck.exclusive, &tmp2));
+ return NT_STATUS_NOT_LOCKED;
+ }
+
+ if (type == G_LOCK_UPGRADE) {
+ ssize_t shared_idx;
+
+ dbwrap_watched_watch_remove_instance(rec,
+ state->watch_instance);
+
+ shared_idx = g_lock_find_shared(&lck, &self);
+
+ if (shared_idx == -1) {
+ DBG_DEBUG("Trying to upgrade %s "
+ "without "
+ "existing shared lock\n",
+ server_id_str_buf(
+ self, &tmp));
+ return NT_STATUS_NOT_LOCKED;
+ }
+
+ /*
+ * We're trying to upgrade, and the
+ * exclusive lock is taken by someone
+ * else. This means that someone else
+ * is waiting for us to give up our
+ * shared lock. If we now also wait
+ * for someone to give their shared
+ * lock, we will deadlock.
+ */
+
+ DBG_DEBUG("Trying to upgrade %s while "
+ "someone else is also "
+ "trying to upgrade\n",
+ server_id_str_buf(self, &tmp));
+ return NT_STATUS_POSSIBLE_DEADLOCK;
+ }
+
+ DBG_DEBUG("Waiting for lck.exclusive=%s\n",
+ server_id_str_buf(lck.exclusive, &tmp));
+
+ /*
+ * We will return NT_STATUS_LOCK_NOT_GRANTED
+ * and need to monitor the record.
+ *
+ * If we don't have a watcher instance yet,
+ * we should add one.
+ */
+ if (state->watch_instance == 0) {
+ state->watch_instance =
+ dbwrap_watched_watch_add_instance(rec);
+ }
+
+ *blocker = lck.exclusive;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ if (type == G_LOCK_DOWNGRADE) {
+ DBG_DEBUG("Downgrading %s from WRITE to READ\n",
+ server_id_str_buf(self, &tmp));
+
+ lck.exclusive = (struct server_id) { .pid = 0 };
+ goto do_shared;
+ }
+
+ if (!retry) {
+ dbwrap_watched_watch_remove_instance(rec,
+ state->watch_instance);
+
+ DBG_DEBUG("%s already locked by self\n",
+ server_id_str_buf(self, &tmp));
+ return NT_STATUS_WAS_LOCKED;
+ }
+
+ g_lock_cleanup_shared(&lck);
+
+ if (lck.num_shared != 0) {
+ g_lock_get_shared(&lck, 0, blocker);
+
+ DBG_DEBUG("Continue waiting for shared lock %s\n",
+ server_id_str_buf(*blocker, &tmp));
+
+ /*
+ * We will return NT_STATUS_LOCK_NOT_GRANTED
+ * and need to monitor the record.
+ *
+ * If we don't have a watcher instance yet,
+ * we should add one.
+ */
+ if (state->watch_instance == 0) {
+ state->watch_instance =
+ dbwrap_watched_watch_add_instance(rec);
+ }
+
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ /*
+ * Retry after a conflicting lock was released..
+ * All pending readers are gone so we got the lock...
+ */
+ goto got_lock;
+ }
+
+noexclusive:
+
+ if (type == G_LOCK_UPGRADE) {
+ ssize_t shared_idx = g_lock_find_shared(&lck, &self);
+
+ if (shared_idx == -1) {
+ dbwrap_watched_watch_remove_instance(rec,
+ state->watch_instance);
+
+ DBG_DEBUG("Trying to upgrade %s without "
+ "existing shared lock\n",
+ server_id_str_buf(self, &tmp));
+ return NT_STATUS_NOT_LOCKED;
+ }
+
+ g_lock_del_shared(&lck, shared_idx);
+ type = G_LOCK_WRITE;
+ }
+
+ if (type == G_LOCK_WRITE) {
+ ssize_t shared_idx = g_lock_find_shared(&lck, &self);
+
+ if (shared_idx != -1) {
+ dbwrap_watched_watch_remove_instance(rec,
+ state->watch_instance);
+ DBG_DEBUG("Trying to writelock existing shared %s\n",
+ server_id_str_buf(self, &tmp));
+ return NT_STATUS_WAS_LOCKED;
+ }
+
+ lck.exclusive = self;
+
+ g_lock_cleanup_shared(&lck);
+
+ if (lck.num_shared == 0) {
+ /*
+ * If we store ourself as exclusive writer,
+ * without any pending readers ...
+ */
+ goto got_lock;
+ }
+
+ if (state->watch_instance == 0) {
+ /*
+ * Here we have lck.num_shared != 0.
+ *
+ * We will return NT_STATUS_LOCK_NOT_GRANTED
+ * below.
+ *
+ * And don't have a watcher instance yet!
+ *
+ * We add it here before g_lock_store()
+ * in order to trigger just one
+ * low level dbwrap_do_locked() call.
+ */
+ state->watch_instance =
+ dbwrap_watched_watch_add_instance(rec);
+ }
+
+ status = g_lock_store(rec, &lck, NULL, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("g_lock_store() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ talloc_set_destructor(
+ req_state, g_lock_lock_state_destructor);
+
+ g_lock_get_shared(&lck, 0, blocker);
+
+ DBG_DEBUG("Waiting for %zu shared locks, "
+ "picking blocker %s\n",
+ lck.num_shared,
+ server_id_str_buf(*blocker, &tmp));
+
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+do_shared:
+
+ g_lock_cleanup_shared(&lck);
+ cb_state.new_shared = &self;
+ goto got_lock;
+
+got_lock:
+ /*
+ * We got the lock we asked for, so we no
+ * longer need to monitor the record.
+ */
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+
+ status = g_lock_lock_cb_run_and_store(&cb_state);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_WAS_UNLOCKED))
+ {
+ DBG_WARNING("g_lock_lock_cb_run_and_store() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ talloc_set_destructor(req_state, NULL);
+ return status;
+}
+
+static void g_lock_lock_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct g_lock_lock_fn_state *state = private_data;
+ struct server_id blocker = {0};
+
+ /*
+ * We're trying to get a lock and if we are
+ * successful in doing that, we should not
+ * wakeup any other waiters, all they would
+ * find is that we're holding a lock they
+ * are conflicting with.
+ */
+ dbwrap_watched_watch_skip_alerting(rec);
+
+ state->status = g_lock_trylock(rec, state, value, &blocker);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_DEBUG("g_lock_trylock returned %s\n",
+ nt_errstr(state->status));
+ }
+ if (!NT_STATUS_EQUAL(state->status, NT_STATUS_LOCK_NOT_GRANTED)) {
+ return;
+ }
+
+ state->watch_req = dbwrap_watched_watch_send(
+ state->req_state, state->req_state->ev, rec, state->watch_instance, blocker);
+ if (state->watch_req == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ }
+}
+
+static int g_lock_lock_state_destructor(struct g_lock_lock_state *s)
+{
+ NTSTATUS status = g_lock_unlock(s->ctx, s->key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("g_lock_unlock failed: %s\n", nt_errstr(status));
+ }
+ return 0;
+}
+
+static void g_lock_lock_retry(struct tevent_req *subreq);
+
+struct tevent_req *g_lock_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ enum g_lock_type type,
+ g_lock_lock_cb_fn_t cb_fn,
+ void *cb_private)
+{
+ struct tevent_req *req;
+ struct g_lock_lock_state *state;
+ struct g_lock_lock_fn_state fn_state;
+ NTSTATUS status;
+ bool ok;
+
+ SMB_ASSERT(!ctx->busy);
+
+ req = tevent_req_create(mem_ctx, &state, struct g_lock_lock_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctx = ctx;
+ state->key = key;
+ state->type = type;
+ state->cb_fn = cb_fn;
+ state->cb_private = cb_private;
+
+ fn_state = (struct g_lock_lock_fn_state) {
+ .req_state = state,
+ };
+
+ /*
+ * We allow a cn_fn only for G_LOCK_WRITE for now.
+ *
+ * It's all we currently need and it makes a few things
+ * easier to implement.
+ */
+ if (unlikely(cb_fn != NULL && type != G_LOCK_WRITE)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_6);
+ return tevent_req_post(req, ev);
+ }
+
+ status = dbwrap_do_locked(ctx->db, key, g_lock_lock_fn, &fn_state);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ return tevent_req_post(req, ev);
+ }
+
+ if (NT_STATUS_IS_OK(fn_state.status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (!NT_STATUS_EQUAL(fn_state.status, NT_STATUS_LOCK_NOT_GRANTED)) {
+ tevent_req_nterror(req, fn_state.status);
+ return tevent_req_post(req, ev);
+ }
+
+ if (tevent_req_nomem(fn_state.watch_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ok = tevent_req_set_endtime(
+ fn_state.watch_req,
+ state->ev,
+ timeval_current_ofs(5 + generate_random() % 5, 0));
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(fn_state.watch_req, g_lock_lock_retry, req);
+
+ return req;
+}
+
+static void g_lock_lock_retry(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct g_lock_lock_state *state = tevent_req_data(
+ req, struct g_lock_lock_state);
+ struct g_lock_lock_fn_state fn_state;
+ struct server_id blocker = { .pid = 0 };
+ bool blockerdead = false;
+ NTSTATUS status;
+ uint64_t instance = 0;
+
+ status = dbwrap_watched_watch_recv(subreq, &instance, &blockerdead, &blocker);
+ DBG_DEBUG("watch_recv returned %s\n", nt_errstr(status));
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->retry = true;
+
+ fn_state = (struct g_lock_lock_fn_state) {
+ .req_state = state,
+ .dead_blocker = blockerdead ? &blocker : NULL,
+ .watch_instance = instance,
+ };
+
+ status = dbwrap_do_locked(state->ctx->db, state->key,
+ g_lock_lock_fn, &fn_state);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(fn_state.status)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (!NT_STATUS_EQUAL(fn_state.status, NT_STATUS_LOCK_NOT_GRANTED)) {
+ tevent_req_nterror(req, fn_state.status);
+ return;
+ }
+
+ if (tevent_req_nomem(fn_state.watch_req, req)) {
+ return;
+ }
+
+ if (!tevent_req_set_endtime(
+ fn_state.watch_req, state->ev,
+ timeval_current_ofs(5 + generate_random() % 5, 0))) {
+ return;
+ }
+ tevent_req_set_callback(fn_state.watch_req, g_lock_lock_retry, req);
+}
+
+NTSTATUS g_lock_lock_recv(struct tevent_req *req)
+{
+ struct g_lock_lock_state *state = tevent_req_data(
+ req, struct g_lock_lock_state);
+ struct g_lock_ctx *ctx = state->ctx;
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_WAS_UNLOCKED)) {
+ return NT_STATUS_OK;
+ }
+ return status;
+ }
+
+ if ((ctx->lock_order != DBWRAP_LOCK_ORDER_NONE) &&
+ ((state->type == G_LOCK_READ) ||
+ (state->type == G_LOCK_WRITE))) {
+ const char *name = dbwrap_name(ctx->db);
+ dbwrap_lock_order_lock(name, ctx->lock_order);
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct g_lock_lock_simple_state {
+ struct g_lock_ctx *ctx;
+ struct server_id me;
+ enum g_lock_type type;
+ NTSTATUS status;
+ g_lock_lock_cb_fn_t cb_fn;
+ void *cb_private;
+};
+
+static void g_lock_lock_simple_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct g_lock_lock_simple_state *state = private_data;
+ struct server_id_buf buf;
+ struct g_lock lck = { .exclusive.pid = 0 };
+ struct g_lock_lock_cb_state cb_state = {
+ .ctx = state->ctx,
+ .rec = rec,
+ .lck = &lck,
+ .cb_fn = state->cb_fn,
+ .cb_private = state->cb_private,
+ .existed = value.dsize != 0,
+ .update_mem_ctx = talloc_tos(),
+ };
+ bool ok;
+
+ ok = g_lock_parse(value.dptr, value.dsize, &lck);
+ if (!ok) {
+ DBG_DEBUG("g_lock_parse failed\n");
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+
+ if (lck.exclusive.pid != 0) {
+ DBG_DEBUG("locked by %s\n",
+ server_id_str_buf(lck.exclusive, &buf));
+ goto not_granted;
+ }
+
+ if (state->type == G_LOCK_WRITE) {
+ if (lck.num_shared != 0) {
+ DBG_DEBUG("num_shared=%zu\n", lck.num_shared);
+ goto not_granted;
+ }
+ lck.exclusive = state->me;
+ } else if (state->type == G_LOCK_READ) {
+ g_lock_cleanup_shared(&lck);
+ cb_state.new_shared = &state->me;
+ } else {
+ smb_panic(__location__);
+ }
+
+ lck.unique_lock_epoch = generate_unique_u64(lck.unique_lock_epoch);
+
+ /*
+ * We are going to store us as owner,
+ * so we got what we were waiting for.
+ *
+ * So we no longer need to monitor the
+ * record.
+ */
+ dbwrap_watched_watch_skip_alerting(rec);
+
+ state->status = g_lock_lock_cb_run_and_store(&cb_state);
+ if (!NT_STATUS_IS_OK(state->status) &&
+ !NT_STATUS_EQUAL(state->status, NT_STATUS_WAS_UNLOCKED))
+ {
+ DBG_WARNING("g_lock_lock_cb_run_and_store() failed: %s\n",
+ nt_errstr(state->status));
+ return;
+ }
+
+ return;
+
+not_granted:
+ state->status = NT_STATUS_LOCK_NOT_GRANTED;
+}
+
+NTSTATUS g_lock_lock(struct g_lock_ctx *ctx, TDB_DATA key,
+ enum g_lock_type type, struct timeval timeout,
+ g_lock_lock_cb_fn_t cb_fn,
+ void *cb_private)
+{
+ TALLOC_CTX *frame;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ struct timeval end;
+ NTSTATUS status;
+
+ SMB_ASSERT(!ctx->busy);
+
+ /*
+ * We allow a cn_fn only for G_LOCK_WRITE for now.
+ *
+ * It's all we currently need and it makes a few things
+ * easier to implement.
+ */
+ if (unlikely(cb_fn != NULL && type != G_LOCK_WRITE)) {
+ return NT_STATUS_INVALID_PARAMETER_5;
+ }
+
+ if ((type == G_LOCK_READ) || (type == G_LOCK_WRITE)) {
+ /*
+ * This is an abstraction violation: Normally we do
+ * the sync wrappers around async functions with full
+ * nested event contexts. However, this is used in
+ * very hot code paths, so avoid the event context
+ * creation for the good path where there's no lock
+ * contention. My benchmark gave a factor of 2
+ * improvement for lock/unlock.
+ */
+ struct g_lock_lock_simple_state state = {
+ .ctx = ctx,
+ .me = messaging_server_id(ctx->msg),
+ .type = type,
+ .cb_fn = cb_fn,
+ .cb_private = cb_private,
+ };
+ status = dbwrap_do_locked(
+ ctx->db, key, g_lock_lock_simple_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_do_locked() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("status=%s, state.status=%s\n",
+ nt_errstr(status),
+ nt_errstr(state.status));
+
+ if (NT_STATUS_IS_OK(state.status)) {
+ if (ctx->lock_order != DBWRAP_LOCK_ORDER_NONE) {
+ const char *name = dbwrap_name(ctx->db);
+ dbwrap_lock_order_lock(name, ctx->lock_order);
+ }
+ return NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(state.status, NT_STATUS_WAS_UNLOCKED)) {
+ /* without dbwrap_lock_order_lock() */
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(
+ state.status, NT_STATUS_LOCK_NOT_GRANTED)) {
+ return state.status;
+ }
+
+ if (timeval_is_zero(&timeout)) {
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ /*
+ * Fall back to the full g_lock_trylock logic,
+ * g_lock_lock_simple_fn() called above only covers
+ * the uncontended path.
+ */
+ }
+
+ frame = talloc_stackframe();
+ status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = g_lock_lock_send(frame, ev, ctx, key, type, cb_fn, cb_private);
+ if (req == NULL) {
+ goto fail;
+ }
+ end = timeval_current_ofs(timeout.tv_sec, timeout.tv_usec);
+ if (!tevent_req_set_endtime(req, ev, end)) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = g_lock_lock_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct g_lock_unlock_state {
+ struct server_id self;
+ NTSTATUS status;
+};
+
+static void g_lock_unlock_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct g_lock_unlock_state *state = private_data;
+ struct server_id_buf tmp1, tmp2;
+ struct g_lock lck;
+ size_t i;
+ bool ok, exclusive;
+
+ ok = g_lock_parse(value.dptr, value.dsize, &lck);
+ if (!ok) {
+ DBG_DEBUG("g_lock_parse() failed\n");
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+
+ exclusive = server_id_equal(&state->self, &lck.exclusive);
+
+ for (i=0; i<lck.num_shared; i++) {
+ struct server_id shared;
+ g_lock_get_shared(&lck, i, &shared);
+ if (server_id_equal(&state->self, &shared)) {
+ break;
+ }
+ }
+
+ if (i < lck.num_shared) {
+ if (exclusive) {
+ DBG_DEBUG("%s both exclusive and shared (%zu)\n",
+ server_id_str_buf(state->self, &tmp1),
+ i);
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+ g_lock_del_shared(&lck, i);
+ } else {
+ if (!exclusive) {
+ DBG_DEBUG("Lock not found, self=%s, lck.exclusive=%s, "
+ "num_shared=%zu\n",
+ server_id_str_buf(state->self, &tmp1),
+ server_id_str_buf(lck.exclusive, &tmp2),
+ lck.num_shared);
+ state->status = NT_STATUS_NOT_FOUND;
+ return;
+ }
+ lck.exclusive = (struct server_id) { .pid = 0 };
+ }
+
+ if ((lck.exclusive.pid == 0) &&
+ (lck.num_shared == 0) &&
+ (lck.datalen == 0)) {
+ state->status = dbwrap_record_delete(rec);
+ return;
+ }
+
+ if (!exclusive && lck.exclusive.pid != 0) {
+ /*
+ * We only had a read lock and there's
+ * someone waiting for an exclusive lock.
+ *
+ * Don't alert the exclusive lock waiter
+ * if there are still other read lock holders.
+ */
+ g_lock_cleanup_shared(&lck);
+ if (lck.num_shared != 0) {
+ dbwrap_watched_watch_skip_alerting(rec);
+ }
+ }
+
+ lck.unique_lock_epoch = generate_unique_u64(lck.unique_lock_epoch);
+
+ state->status = g_lock_store(rec, &lck, NULL, NULL, 0);
+}
+
+NTSTATUS g_lock_unlock(struct g_lock_ctx *ctx, TDB_DATA key)
+{
+ struct g_lock_unlock_state state = {
+ .self = messaging_server_id(ctx->msg),
+ };
+ NTSTATUS status;
+
+ SMB_ASSERT(!ctx->busy);
+
+ status = dbwrap_do_locked(ctx->db, key, g_lock_unlock_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_WARNING("g_lock_unlock_fn failed: %s\n",
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ if (ctx->lock_order != DBWRAP_LOCK_ORDER_NONE) {
+ const char *name = dbwrap_name(ctx->db);
+ dbwrap_lock_order_unlock(name, ctx->lock_order);
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct g_lock_writev_data_state {
+ TDB_DATA key;
+ struct server_id self;
+ const TDB_DATA *dbufs;
+ size_t num_dbufs;
+ NTSTATUS status;
+};
+
+static void g_lock_writev_data_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct g_lock_writev_data_state *state = private_data;
+ struct g_lock lck;
+ bool exclusive;
+ bool ok;
+
+ /*
+ * We're holding an exclusive write lock.
+ *
+ * Now we're updating the content of the record.
+ *
+ * We should not wakeup any other waiters, all they
+ * would find is that we're still holding a lock they
+ * are conflicting with.
+ */
+ dbwrap_watched_watch_skip_alerting(rec);
+
+ ok = g_lock_parse(value.dptr, value.dsize, &lck);
+ if (!ok) {
+ DBG_DEBUG("g_lock_parse for %s failed\n",
+ tdb_data_dbg(state->key));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+
+ exclusive = server_id_equal(&state->self, &lck.exclusive);
+
+ /*
+ * Make sure we're really exclusive. We are marked as
+ * exclusive when we are waiting for an exclusive lock
+ */
+ exclusive &= (lck.num_shared == 0);
+
+ if (!exclusive) {
+ struct server_id_buf buf1, buf2;
+ DBG_DEBUG("Not locked by us: self=%s, lck.exclusive=%s, "
+ "lck.num_shared=%zu\n",
+ server_id_str_buf(state->self, &buf1),
+ server_id_str_buf(lck.exclusive, &buf2),
+ lck.num_shared);
+ state->status = NT_STATUS_NOT_LOCKED;
+ return;
+ }
+
+ lck.unique_data_epoch = generate_unique_u64(lck.unique_data_epoch);
+ lck.data = NULL;
+ lck.datalen = 0;
+ state->status = g_lock_store(
+ rec, &lck, NULL, state->dbufs, state->num_dbufs);
+}
+
+NTSTATUS g_lock_writev_data(
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ const TDB_DATA *dbufs,
+ size_t num_dbufs)
+{
+ struct g_lock_writev_data_state state = {
+ .key = key,
+ .self = messaging_server_id(ctx->msg),
+ .dbufs = dbufs,
+ .num_dbufs = num_dbufs,
+ };
+ NTSTATUS status;
+
+ SMB_ASSERT(!ctx->busy);
+
+ status = dbwrap_do_locked(
+ ctx->db, key, g_lock_writev_data_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_WARNING("g_lock_writev_data_fn failed: %s\n",
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS g_lock_write_data(struct g_lock_ctx *ctx, TDB_DATA key,
+ const uint8_t *buf, size_t buflen)
+{
+ TDB_DATA dbuf = {
+ .dptr = discard_const_p(uint8_t, buf),
+ .dsize = buflen,
+ };
+ return g_lock_writev_data(ctx, key, &dbuf, 1);
+}
+
+struct g_lock_locks_state {
+ int (*fn)(TDB_DATA key, void *private_data);
+ void *private_data;
+};
+
+static int g_lock_locks_fn(struct db_record *rec, void *priv)
+{
+ TDB_DATA key;
+ struct g_lock_locks_state *state = (struct g_lock_locks_state *)priv;
+
+ key = dbwrap_record_get_key(rec);
+ return state->fn(key, state->private_data);
+}
+
+int g_lock_locks(struct g_lock_ctx *ctx,
+ int (*fn)(TDB_DATA key, void *private_data),
+ void *private_data)
+{
+ struct g_lock_locks_state state;
+ NTSTATUS status;
+ int count;
+
+ SMB_ASSERT(!ctx->busy);
+
+ state.fn = fn;
+ state.private_data = private_data;
+
+ status = dbwrap_traverse_read(ctx->db, g_lock_locks_fn, &state, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ return count;
+}
+
+struct g_lock_dump_state {
+ TALLOC_CTX *mem_ctx;
+ TDB_DATA key;
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data);
+ void *private_data;
+ NTSTATUS status;
+ enum dbwrap_req_state req_state;
+};
+
+static void g_lock_dump_fn(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct g_lock_dump_state *state = private_data;
+ struct g_lock lck = (struct g_lock) { .exclusive.pid = 0 };
+ struct server_id *shared = NULL;
+ size_t i;
+ bool ok;
+
+ ok = g_lock_parse(data.dptr, data.dsize, &lck);
+ if (!ok) {
+ DBG_DEBUG("g_lock_parse failed for %s\n",
+ tdb_data_dbg(state->key));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+
+ if (lck.num_shared > 0) {
+ shared = talloc_array(
+ state->mem_ctx, struct server_id, lck.num_shared);
+ if (shared == NULL) {
+ DBG_DEBUG("talloc failed\n");
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ }
+
+ for (i=0; i<lck.num_shared; i++) {
+ g_lock_get_shared(&lck, i, &shared[i]);
+ }
+
+ state->fn(lck.exclusive,
+ lck.num_shared,
+ shared,
+ lck.data,
+ lck.datalen,
+ state->private_data);
+
+ TALLOC_FREE(shared);
+
+ state->status = NT_STATUS_OK;
+}
+
+NTSTATUS g_lock_dump(struct g_lock_ctx *ctx, TDB_DATA key,
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data),
+ void *private_data)
+{
+ struct g_lock_dump_state state = {
+ .mem_ctx = ctx, .key = key,
+ .fn = fn, .private_data = private_data
+ };
+ NTSTATUS status;
+
+ SMB_ASSERT(!ctx->busy);
+
+ status = dbwrap_parse_record(ctx->db, key, g_lock_dump_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_parse_record returned %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_DEBUG("g_lock_dump_fn returned %s\n",
+ nt_errstr(state.status));
+ return state.status;
+ }
+ return NT_STATUS_OK;
+}
+
+static void g_lock_dump_done(struct tevent_req *subreq);
+
+struct tevent_req *g_lock_dump_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data),
+ void *private_data)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct g_lock_dump_state *state = NULL;
+
+ SMB_ASSERT(!ctx->busy);
+
+ req = tevent_req_create(mem_ctx, &state, struct g_lock_dump_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->mem_ctx = state;
+ state->key = key;
+ state->fn = fn;
+ state->private_data = private_data;
+
+ SMB_ASSERT(!ctx->busy);
+
+ subreq = dbwrap_parse_record_send(
+ state,
+ ev,
+ ctx->db,
+ key,
+ g_lock_dump_fn,
+ state,
+ &state->req_state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, g_lock_dump_done, req);
+ return req;
+}
+
+static void g_lock_dump_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct g_lock_dump_state *state = tevent_req_data(
+ req, struct g_lock_dump_state);
+ NTSTATUS status;
+
+ status = dbwrap_parse_record_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status) ||
+ tevent_req_nterror(req, state->status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS g_lock_dump_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+int g_lock_seqnum(struct g_lock_ctx *ctx)
+{
+ return dbwrap_get_seqnum(ctx->db);
+}
+
+struct g_lock_watch_data_state {
+ struct tevent_context *ev;
+ struct g_lock_ctx *ctx;
+ TDB_DATA key;
+ struct server_id blocker;
+ bool blockerdead;
+ uint64_t unique_lock_epoch;
+ uint64_t unique_data_epoch;
+ uint64_t watch_instance;
+ NTSTATUS status;
+};
+
+static void g_lock_watch_data_done(struct tevent_req *subreq);
+
+static void g_lock_watch_data_send_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct g_lock_watch_data_state *state = tevent_req_data(
+ req, struct g_lock_watch_data_state);
+ struct tevent_req *subreq = NULL;
+ struct g_lock lck;
+ bool ok;
+
+ ok = g_lock_parse(value.dptr, value.dsize, &lck);
+ if (!ok) {
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+ state->unique_lock_epoch = lck.unique_lock_epoch;
+ state->unique_data_epoch = lck.unique_data_epoch;
+
+ DBG_DEBUG("state->unique_data_epoch=%"PRIu64"\n", state->unique_data_epoch);
+
+ subreq = dbwrap_watched_watch_send(
+ state, state->ev, rec, 0, state->blocker);
+ if (subreq == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ tevent_req_set_callback(subreq, g_lock_watch_data_done, req);
+
+ state->status = NT_STATUS_EVENT_PENDING;
+}
+
+struct tevent_req *g_lock_watch_data_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct g_lock_ctx *ctx,
+ TDB_DATA key,
+ struct server_id blocker)
+{
+ struct tevent_req *req = NULL;
+ struct g_lock_watch_data_state *state = NULL;
+ NTSTATUS status;
+
+ SMB_ASSERT(!ctx->busy);
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct g_lock_watch_data_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctx = ctx;
+ state->blocker = blocker;
+
+ state->key = tdb_data_talloc_copy(state, key);
+ if (tevent_req_nomem(state->key.dptr, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = dbwrap_do_locked(
+ ctx->db, key, g_lock_watch_data_send_fn, req);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dbwrap_do_locked returned %s\n", nt_errstr(status));
+ return tevent_req_post(req, ev);
+ }
+
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_EVENT_PENDING)) {
+ return req;
+ }
+ if (tevent_req_nterror(req, state->status)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void g_lock_watch_data_done_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct g_lock_watch_data_state *state = tevent_req_data(
+ req, struct g_lock_watch_data_state);
+ struct tevent_req *subreq = NULL;
+ struct g_lock lck;
+ bool ok;
+
+ ok = g_lock_parse(value.dptr, value.dsize, &lck);
+ if (!ok) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+
+ if (lck.unique_data_epoch != state->unique_data_epoch) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ DBG_DEBUG("lck.unique_data_epoch=%"PRIu64", "
+ "state->unique_data_epoch=%"PRIu64"\n",
+ lck.unique_data_epoch,
+ state->unique_data_epoch);
+ state->status = NT_STATUS_OK;
+ return;
+ }
+
+ /*
+ * The lock epoch changed, so we better
+ * remove ourself from the waiter list
+ * (most likely the first position)
+ * and re-add us at the end of the list.
+ *
+ * This gives other lock waiters a change
+ * to make progress.
+ *
+ * Otherwise we'll keep our waiter instance alive,
+ * keep waiting (most likely at first position).
+ */
+ if (lck.unique_lock_epoch != state->unique_lock_epoch) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ state->watch_instance = dbwrap_watched_watch_add_instance(rec);
+ state->unique_lock_epoch = lck.unique_lock_epoch;
+ }
+
+ subreq = dbwrap_watched_watch_send(
+ state, state->ev, rec, state->watch_instance, state->blocker);
+ if (subreq == NULL) {
+ dbwrap_watched_watch_remove_instance(rec, state->watch_instance);
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ tevent_req_set_callback(subreq, g_lock_watch_data_done, req);
+
+ state->status = NT_STATUS_EVENT_PENDING;
+}
+
+static void g_lock_watch_data_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct g_lock_watch_data_state *state = tevent_req_data(
+ req, struct g_lock_watch_data_state);
+ NTSTATUS status;
+ uint64_t instance = 0;
+
+ status = dbwrap_watched_watch_recv(
+ subreq, &instance, &state->blockerdead, &state->blocker);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dbwrap_watched_watch_recv returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ state->watch_instance = instance;
+
+ status = dbwrap_do_locked(
+ state->ctx->db, state->key, g_lock_watch_data_done_fn, req);
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dbwrap_do_locked returned %s\n", nt_errstr(status));
+ return;
+ }
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_EVENT_PENDING)) {
+ return;
+ }
+ if (tevent_req_nterror(req, state->status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS g_lock_watch_data_recv(
+ struct tevent_req *req,
+ bool *blockerdead,
+ struct server_id *blocker)
+{
+ struct g_lock_watch_data_state *state = tevent_req_data(
+ req, struct g_lock_watch_data_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (blockerdead != NULL) {
+ *blockerdead = state->blockerdead;
+ }
+ if (blocker != NULL) {
+ *blocker = state->blocker;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void g_lock_wake_watchers_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct g_lock lck = { .exclusive.pid = 0 };
+ NTSTATUS status;
+ bool ok;
+
+ ok = g_lock_parse(value.dptr, value.dsize, &lck);
+ if (!ok) {
+ DBG_WARNING("g_lock_parse failed\n");
+ return;
+ }
+
+ lck.unique_data_epoch = generate_unique_u64(lck.unique_data_epoch);
+
+ status = g_lock_store(rec, &lck, NULL, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_store failed: %s\n", nt_errstr(status));
+ return;
+ }
+}
+
+void g_lock_wake_watchers(struct g_lock_ctx *ctx, TDB_DATA key)
+{
+ NTSTATUS status;
+
+ SMB_ASSERT(!ctx->busy);
+
+ status = dbwrap_do_locked(ctx->db, key, g_lock_wake_watchers_fn, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_do_locked returned %s\n",
+ nt_errstr(status));
+ }
+}
diff --git a/source3/lib/gencache.c b/source3/lib/gencache.c
new file mode 100644
index 0000000..07a08fa
--- /dev/null
+++ b/source3/lib/gencache.c
@@ -0,0 +1,742 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Generic, persistent and shared between processes cache mechanism for use
+ by various parts of the Samba code
+
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/gencache.h"
+#include "system/filesys.h"
+#include "system/glob.h"
+#include "util_tdb.h"
+#include "tdb_wrap/tdb_wrap.h"
+#include "zlib.h"
+#include "lib/util/strv.h"
+#include "lib/util/util_paths.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_TDB
+
+#define GENCACHE_USER_PATH "~/.cache/samba/gencache.tdb"
+
+static struct tdb_wrap *cache;
+
+/**
+ * @file gencache.c
+ * @brief Generic, persistent and shared between processes cache mechanism
+ * for use by various parts of the Samba code
+ *
+ **/
+
+static bool gencache_pull_timeout(TDB_DATA key,
+ TDB_DATA data,
+ time_t *pres,
+ DATA_BLOB *payload);
+
+struct gencache_timeout {
+ time_t timeout;
+};
+
+bool gencache_timeout_expired(const struct gencache_timeout *t)
+{
+ return t->timeout <= time(NULL);
+}
+
+/**
+ * Cache initialisation function. Opens cache tdb file or creates
+ * it if does not exist.
+ *
+ * @return true on successful initialisation of the cache or
+ * false on failure
+ **/
+
+static bool gencache_init(void)
+{
+ char* cache_fname = NULL;
+ int open_flags = O_RDWR|O_CREAT;
+ int tdb_flags = TDB_INCOMPATIBLE_HASH|TDB_NOSYNC|TDB_MUTEX_LOCKING;
+ int hash_size;
+
+ /* skip file open if it's already opened */
+ if (cache) {
+ return true;
+ }
+
+ hash_size = lp_parm_int(-1, "gencache", "hash_size", 10000);
+
+ cache_fname = lock_path(talloc_tos(), "gencache.tdb");
+ if (cache_fname == NULL) {
+ return false;
+ }
+
+ DEBUG(5, ("Opening cache file at %s\n", cache_fname));
+
+ cache = tdb_wrap_open(NULL, cache_fname, hash_size,
+ tdb_flags,
+ open_flags, 0644);
+ /*
+ * Allow client tools to create a gencache in the home directory
+ * as a normal user.
+ */
+ if (cache == NULL && errno == EACCES && geteuid() != 0) {
+ char *cache_dname = NULL, *tmp = NULL;
+ bool ok;
+
+ TALLOC_FREE(cache_fname);
+
+ cache_fname = path_expand_tilde(talloc_tos(),
+ GENCACHE_USER_PATH);
+ if (cache_fname == NULL) {
+ DBG_ERR("Failed to expand path: %s\n",
+ GENCACHE_USER_PATH);
+ return false;
+ }
+
+ tmp = talloc_strdup(talloc_tos(), cache_fname);
+ if (tmp == NULL) {
+ DBG_ERR("No memory!\n");
+ TALLOC_FREE(cache_fname);
+ return false;
+ }
+
+ cache_dname = dirname(tmp);
+ if (cache_dname == NULL) {
+ DBG_ERR("Invalid path: %s\n", cache_fname);
+ TALLOC_FREE(tmp);
+ TALLOC_FREE(cache_fname);
+ return false;
+ }
+
+ ok = directory_create_or_exists_recursive(cache_dname, 0700);
+ if (!ok) {
+ DBG_ERR("Failed to create directory: %s - %s\n",
+ cache_dname, strerror(errno));
+ TALLOC_FREE(tmp);
+ TALLOC_FREE(cache_fname);
+ return false;
+ }
+ TALLOC_FREE(tmp);
+
+ cache = tdb_wrap_open(NULL,
+ cache_fname,
+ hash_size,
+ tdb_flags,
+ open_flags,
+ 0644);
+ if (cache != NULL) {
+ DBG_INFO("Opening user cache file %s.\n",
+ cache_fname);
+ }
+ }
+
+ if (cache == NULL) {
+ DEBUG(5, ("Opening %s failed: %s\n", cache_fname,
+ strerror(errno)));
+ TALLOC_FREE(cache_fname);
+ return false;
+ }
+ TALLOC_FREE(cache_fname);
+
+ return true;
+}
+
+/*
+ * Walk the hash chain for "key", deleting all expired entries for
+ * that hash chain
+ */
+struct gencache_prune_expired_state {
+ TALLOC_CTX *mem_ctx;
+ char *keys;
+};
+
+static int gencache_prune_expired_fn(struct tdb_context *tdb,
+ TDB_DATA key,
+ TDB_DATA data,
+ void *private_data)
+{
+ struct gencache_prune_expired_state *state = private_data;
+ struct gencache_timeout t;
+ bool ok = false;
+ bool expired = false;
+
+ if ((key.dsize == 0) || (key.dptr[key.dsize-1] != '\0')) {
+ /* not a valid record, should never happen */
+ return 0;
+ }
+
+ ok = gencache_pull_timeout(key, data, &t.timeout, NULL);
+ if (ok) {
+ expired = gencache_timeout_expired(&t);
+ }
+
+ if (!ok || expired) {
+ int ret;
+
+ ret = strv_add(state->mem_ctx, &state->keys, (char *)key.dptr);
+ if (ret != 0) {
+ /*
+ * Exit the loop. It's unlikely that it will
+ * succeed next time.
+ */
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void gencache_prune_expired(struct tdb_context *tdb,
+ TDB_DATA chain_key)
+{
+ struct gencache_prune_expired_state state = {
+ .mem_ctx = talloc_tos(),
+ };
+ char *keystr = NULL;
+ int ret;
+
+ ret = tdb_traverse_key_chain(
+ tdb, chain_key, gencache_prune_expired_fn, &state);
+ if (ret == -1) {
+ DBG_DEBUG("tdb_traverse_key_chain failed: %s\n",
+ tdb_errorstr(tdb));
+ return;
+ }
+
+ while ((keystr = strv_next(state.keys, keystr)) != NULL) {
+ TDB_DATA key = string_term_tdb_data(keystr);
+
+ /*
+ * We expect the hash chain of "chain_key" to be
+ * locked. So between gencache_prune_expired_fn
+ * figuring out "keystr" is expired and the
+ * tdb_delete, nobody can have reset the timeout.
+ */
+ tdb_delete(tdb, key);
+ }
+
+ TALLOC_FREE(state.keys);
+}
+
+/**
+ * Set an entry in the cache file. If there's no such
+ * one, then add it.
+ *
+ * @param keystr string that represents a key of this entry
+ * @param blob DATA_BLOB value being cached
+ * @param timeout time when the value is expired
+ *
+ * @retval true when entry is successfully stored
+ * @retval false on failure
+ **/
+
+bool gencache_set_data_blob(const char *keystr, DATA_BLOB blob,
+ time_t timeout)
+{
+ TDB_DATA key;
+ int ret;
+ TDB_DATA dbufs[3];
+ uint32_t crc;
+
+ if ((keystr == NULL) || (blob.data == NULL)) {
+ return false;
+ }
+
+ key = string_term_tdb_data(keystr);
+
+ if (!gencache_init()) {
+ return false;
+ }
+
+ dbufs[0] = (TDB_DATA) { .dptr = (uint8_t *)&timeout,
+ .dsize = sizeof(time_t) };
+ dbufs[1] = (TDB_DATA) { .dptr = blob.data, .dsize = blob.length };
+
+ crc = crc32(0, Z_NULL, 0);
+ crc = crc32(crc, key.dptr, key.dsize);
+ crc = crc32(crc, dbufs[0].dptr, dbufs[0].dsize);
+ crc = crc32(crc, dbufs[1].dptr, dbufs[1].dsize);
+
+ dbufs[2] = (TDB_DATA) { .dptr = (uint8_t *)&crc,
+ .dsize = sizeof(crc) };
+
+ DBG_DEBUG("Adding cache entry with key=[%s] and timeout="
+ "[%s] (%ld seconds %s)\n", keystr,
+ timestring(talloc_tos(), timeout),
+ ((long int)timeout) - time(NULL),
+ timeout > time(NULL) ? "ahead" : "in the past");
+
+ ret = tdb_chainlock(cache->tdb, key);
+ if (ret == -1) {
+ DBG_WARNING("tdb_chainlock for key [%s] failed: %s\n",
+ keystr, tdb_errorstr(cache->tdb));
+ return false;
+ }
+
+ gencache_prune_expired(cache->tdb, key);
+
+ ret = tdb_storev(cache->tdb, key, dbufs, ARRAY_SIZE(dbufs), 0);
+
+ tdb_chainunlock(cache->tdb, key);
+
+ if (ret == 0) {
+ return true;
+ }
+ if (tdb_error(cache->tdb) != TDB_ERR_CORRUPT) {
+ return false;
+ }
+
+ ret = tdb_wipe_all(cache->tdb);
+ SMB_ASSERT(ret == 0);
+
+ return false;
+}
+
+/**
+ * Delete one entry from the cache file.
+ *
+ * @param keystr string that represents a key of this entry
+ *
+ * @retval true upon successful deletion
+ * @retval false in case of failure
+ **/
+
+bool gencache_del(const char *keystr)
+{
+ TDB_DATA key = string_term_tdb_data(keystr);
+ int ret;
+
+ if (keystr == NULL) {
+ return false;
+ }
+
+ if (!gencache_init()) {
+ return false;
+ }
+
+ DEBUG(10, ("Deleting cache entry (key=[%s])\n", keystr));
+
+ ret = tdb_delete(cache->tdb, key);
+
+ if (ret == 0) {
+ return true;
+ }
+ if (tdb_error(cache->tdb) != TDB_ERR_CORRUPT) {
+ return false;
+ }
+
+ ret = tdb_wipe_all(cache->tdb);
+ SMB_ASSERT(ret == 0);
+
+ return true; /* We've deleted a bit more... */
+}
+
+static bool gencache_pull_timeout(TDB_DATA key,
+ TDB_DATA data,
+ time_t *pres,
+ DATA_BLOB *payload)
+{
+ size_t crc_ofs;
+ uint32_t crc, stored_crc;
+
+ if ((data.dptr == NULL) ||
+ (data.dsize < (sizeof(time_t) + sizeof(uint32_t)))) {
+ return false;
+ }
+
+ crc_ofs = data.dsize - sizeof(uint32_t);
+
+ crc = crc32(0, Z_NULL, 0);
+ crc = crc32(crc, key.dptr, key.dsize);
+ crc = crc32(crc, data.dptr, crc_ofs);
+
+ memcpy(&stored_crc, data.dptr + crc_ofs, sizeof(uint32_t));
+
+ if (stored_crc != crc) {
+ return false;
+ }
+
+ if (pres != NULL) {
+ memcpy(pres, data.dptr, sizeof(time_t));
+ }
+ if (payload != NULL) {
+ *payload = (DATA_BLOB) {
+ .data = data.dptr+sizeof(time_t),
+ .length = data.dsize-sizeof(time_t)-sizeof(uint32_t),
+ };
+ }
+ return true;
+}
+
+struct gencache_parse_state {
+ void (*parser)(const struct gencache_timeout *timeout,
+ DATA_BLOB blob,
+ void *private_data);
+ void *private_data;
+ bool format_error;
+};
+
+static int gencache_parse_fn(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct gencache_parse_state *state = private_data;
+ struct gencache_timeout t;
+ DATA_BLOB payload;
+ bool ret;
+
+ ret = gencache_pull_timeout(key, data, &t.timeout, &payload);
+ if (!ret) {
+ state->format_error = true;
+ return 0;
+ }
+ state->parser(&t, payload, state->private_data);
+
+ return 0;
+}
+
+bool gencache_parse(const char *keystr,
+ void (*parser)(const struct gencache_timeout *timeout,
+ DATA_BLOB blob,
+ void *private_data),
+ void *private_data)
+{
+ struct gencache_parse_state state = {
+ .parser = parser, .private_data = private_data
+ };
+ TDB_DATA key = string_term_tdb_data(keystr);
+ int ret;
+
+ if (keystr == NULL) {
+ return false;
+ }
+ if (!gencache_init()) {
+ return false;
+ }
+
+ ret = tdb_parse_record(cache->tdb, key,
+ gencache_parse_fn, &state);
+ if ((ret == -1) && (tdb_error(cache->tdb) == TDB_ERR_CORRUPT)) {
+ goto wipe;
+ }
+ if (ret == -1) {
+ return false;
+ }
+ if (state.format_error) {
+ ret = tdb_delete(cache->tdb, key);
+ if (ret == -1) {
+ goto wipe;
+ }
+ return false;
+ }
+ return true;
+
+wipe:
+ ret = tdb_wipe_all(cache->tdb);
+ SMB_ASSERT(ret == 0);
+ return false;
+}
+
+struct gencache_get_data_blob_state {
+ TALLOC_CTX *mem_ctx;
+ DATA_BLOB *blob;
+ time_t timeout;
+ bool result;
+};
+
+static void gencache_get_data_blob_parser(const struct gencache_timeout *t,
+ DATA_BLOB blob,
+ void *private_data)
+{
+ struct gencache_get_data_blob_state *state =
+ (struct gencache_get_data_blob_state *)private_data;
+
+ if (t->timeout == 0) {
+ state->result = false;
+ return;
+ }
+ state->timeout = t->timeout;
+
+ if (state->blob == NULL) {
+ state->result = true;
+ return;
+ }
+
+ *state->blob = data_blob_talloc(state->mem_ctx, blob.data,
+ blob.length);
+ if (state->blob->data == NULL) {
+ state->result = false;
+ return;
+ }
+ state->result = true;
+}
+
+/**
+ * Get existing entry from the cache file.
+ *
+ * @param keystr string that represents a key of this entry
+ * @param blob DATA_BLOB that is filled with entry's blob
+ * @param timeout pointer to a time_t that is filled with entry's
+ * timeout
+ *
+ * @retval true when entry is successfully fetched
+ * @retval false for failure
+ **/
+
+bool gencache_get_data_blob(const char *keystr, TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ time_t *timeout, bool *was_expired)
+{
+ struct gencache_get_data_blob_state state;
+ bool expired = false;
+
+ state.result = false;
+ state.mem_ctx = mem_ctx;
+ state.blob = blob;
+
+ if (!gencache_parse(keystr, gencache_get_data_blob_parser, &state)) {
+ goto fail;
+ }
+ if (!state.result) {
+ goto fail;
+ }
+ if (state.timeout <= time(NULL)) {
+ /*
+ * We're expired, delete the entry. We can't use gencache_del
+ * here, because that uses gencache_get_data_blob for checking
+ * the existence of a record. We know the thing exists and
+ * directly store an empty value with 0 timeout.
+ */
+ gencache_set(keystr, "", 0);
+ expired = true;
+ goto fail;
+ }
+ if (timeout) {
+ *timeout = state.timeout;
+ }
+
+ return true;
+
+fail:
+ if (was_expired != NULL) {
+ *was_expired = expired;
+ }
+ if (state.result && state.blob) {
+ data_blob_free(state.blob);
+ }
+ return false;
+}
+
+/**
+ * Get existing entry from the cache file.
+ *
+ * @param keystr string that represents a key of this entry
+ * @param valstr buffer that is allocated and filled with the entry value
+ * buffer's disposing must be done outside
+ * @param timeout pointer to a time_t that is filled with entry's
+ * timeout
+ *
+ * @retval true when entry is successfully fetched
+ * @retval false for failure
+ **/
+
+bool gencache_get(const char *keystr, TALLOC_CTX *mem_ctx, char **value,
+ time_t *ptimeout)
+{
+ DATA_BLOB blob;
+ bool ret = false;
+
+ ret = gencache_get_data_blob(keystr, mem_ctx, &blob, ptimeout, NULL);
+ if (!ret) {
+ return false;
+ }
+ if ((blob.data == NULL) || (blob.length == 0)) {
+ data_blob_free(&blob);
+ return false;
+ }
+ if (blob.data[blob.length-1] != '\0') {
+ /* Not NULL terminated, can't be a string */
+ data_blob_free(&blob);
+ return false;
+ }
+ if (value) {
+ /*
+ * talloc_move generates a type-punned warning here. As we
+ * leave the function immediately, do a simple talloc_steal.
+ */
+ *value = (char *)talloc_steal(mem_ctx, blob.data);
+ return true;
+ }
+ data_blob_free(&blob);
+ return true;
+}
+
+/**
+ * Set an entry in the cache file. If there's no such
+ * one, then add it.
+ *
+ * @param keystr string that represents a key of this entry
+ * @param value text representation value being cached
+ * @param timeout time when the value is expired
+ *
+ * @retval true when entry is successfully stored
+ * @retval false on failure
+ **/
+
+bool gencache_set(const char *keystr, const char *value, time_t timeout)
+{
+ DATA_BLOB blob = data_blob_const(value, strlen(value)+1);
+ return gencache_set_data_blob(keystr, blob, timeout);
+}
+
+struct gencache_iterate_blobs_state {
+ void (*fn)(const char *key, DATA_BLOB value,
+ time_t timeout, void *private_data);
+ const char *pattern;
+ void *private_data;
+};
+
+static int gencache_iterate_blobs_fn(struct tdb_context *tdb, TDB_DATA key,
+ TDB_DATA data, void *priv)
+{
+ struct gencache_iterate_blobs_state *state =
+ (struct gencache_iterate_blobs_state *)priv;
+ char *keystr;
+ char *free_key = NULL;
+ time_t timeout;
+ DATA_BLOB payload;
+
+ if (key.dptr[key.dsize-1] == '\0') {
+ keystr = (char *)key.dptr;
+ } else {
+ /* ensure 0-termination */
+ keystr = talloc_strndup(talloc_tos(), (char *)key.dptr, key.dsize);
+ free_key = keystr;
+ if (keystr == NULL) {
+ goto done;
+ }
+ }
+
+ if (!gencache_pull_timeout(key, data, &timeout, &payload)) {
+ goto done;
+ }
+
+ if (timeout == 0) {
+ /* delete marker */
+ goto done;
+ }
+
+ if (fnmatch(state->pattern, keystr, 0) != 0) {
+ goto done;
+ }
+
+ DEBUG(10, ("Calling function with arguments "
+ "(key=[%s], timeout=[%s])\n",
+ keystr, timestring(talloc_tos(), timeout)));
+
+ state->fn(keystr, payload, timeout, state->private_data);
+
+ done:
+ TALLOC_FREE(free_key);
+ return 0;
+}
+
+void gencache_iterate_blobs(void (*fn)(const char *key, DATA_BLOB value,
+ time_t timeout, void *private_data),
+ void *private_data, const char *pattern)
+{
+ struct gencache_iterate_blobs_state state;
+ int ret;
+
+ if ((fn == NULL) || (pattern == NULL) || !gencache_init()) {
+ return;
+ }
+
+ DEBUG(5, ("Searching cache keys with pattern %s\n", pattern));
+
+ state.fn = fn;
+ state.pattern = pattern;
+ state.private_data = private_data;
+
+ ret = tdb_traverse(cache->tdb, gencache_iterate_blobs_fn, &state);
+
+ if ((ret == -1) && (tdb_error(cache->tdb) == TDB_ERR_CORRUPT)) {
+ ret = tdb_wipe_all(cache->tdb);
+ SMB_ASSERT(ret == 0);
+ }
+}
+
+/**
+ * Iterate through all entries which key matches to specified pattern
+ *
+ * @param fn pointer to the function that will be supplied with each single
+ * matching cache entry (key, value and timeout) as an arguments
+ * @param data void pointer to an arbitrary data that is passed directly to the fn
+ * function on each call
+ * @param keystr_pattern pattern the existing entries' keys are matched to
+ *
+ **/
+
+struct gencache_iterate_state {
+ void (*fn)(const char *key, const char *value, time_t timeout,
+ void *priv);
+ void *private_data;
+};
+
+static void gencache_iterate_fn(const char *key, DATA_BLOB value,
+ time_t timeout, void *private_data)
+{
+ struct gencache_iterate_state *state =
+ (struct gencache_iterate_state *)private_data;
+ char *valstr;
+ char *free_val = NULL;
+
+ if (value.data[value.length-1] == '\0') {
+ valstr = (char *)value.data;
+ } else {
+ /* ensure 0-termination */
+ valstr = talloc_strndup(talloc_tos(), (char *)value.data, value.length);
+ free_val = valstr;
+ if (valstr == NULL) {
+ goto done;
+ }
+ }
+
+ DEBUG(10, ("Calling function with arguments "
+ "(key=[%s], value=[%s], timeout=[%s])\n",
+ key, valstr, timestring(talloc_tos(), timeout)));
+
+ state->fn(key, valstr, timeout, state->private_data);
+
+ done:
+
+ TALLOC_FREE(free_val);
+}
+
+void gencache_iterate(void (*fn)(const char *key, const char *value,
+ time_t timeout, void *dptr),
+ void *private_data, const char *pattern)
+{
+ struct gencache_iterate_state state;
+
+ if (fn == NULL) {
+ return;
+ }
+ state.fn = fn;
+ state.private_data = private_data;
+ gencache_iterate_blobs(gencache_iterate_fn, &state, pattern);
+}
diff --git a/source3/lib/gencache.h b/source3/lib/gencache.h
new file mode 100644
index 0000000..ccf16e9
--- /dev/null
+++ b/source3/lib/gencache.h
@@ -0,0 +1,60 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Generic, persistent and shared between processes cache mechanism for use
+ * by various parts of the Samba code
+ *
+ * Copyright (C) Rafal Szczesniak 2002
+ * Copyright (C) Volker Lendecke 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_GENCACHE_H__
+#define __LIB_GENCACHE_H__
+
+#include "replace.h"
+#include "system/time.h"
+#include "lib/util/data_blob.h"
+
+bool gencache_set(const char *keystr, const char *value, time_t timeout);
+bool gencache_del(const char *keystr);
+bool gencache_get(const char *keystr, TALLOC_CTX *mem_ctx, char **value,
+ time_t *ptimeout);
+
+/*
+ * This might look like overkill, but namemap_cache.c shows it's
+ * necessary :-)
+ */
+struct gencache_timeout;
+bool gencache_timeout_expired(const struct gencache_timeout *t);
+
+bool gencache_parse(const char *keystr,
+ void (*parser)(const struct gencache_timeout *timeout,
+ DATA_BLOB blob,
+ void *private_data),
+ void *private_data);
+bool gencache_get_data_blob(const char *keystr, TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ time_t *timeout, bool *was_expired);
+bool gencache_set_data_blob(const char *keystr, DATA_BLOB blob,
+ time_t timeout);
+void gencache_iterate_blobs(void (*fn)(const char *key, DATA_BLOB value,
+ time_t timeout, void *private_data),
+ void *private_data, const char *pattern);
+void gencache_iterate(void (*fn)(const char* key, const char *value,
+ time_t timeout, void* dptr),
+ void* data, const char* keystr_pattern);
+
+#endif
diff --git a/source3/lib/global_contexts.c b/source3/lib/global_contexts.c
new file mode 100644
index 0000000..4e3bbab
--- /dev/null
+++ b/source3/lib/global_contexts.c
@@ -0,0 +1,71 @@
+/*
+ Unix SMB/CIFS implementation.
+ Global contexts
+
+ Copyright (C) Simo Sorce <idra@samba.org> 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "replace.h"
+#include "global_contexts.h"
+#include <tevent.h>
+#include "lib/util/fault.h"
+#include "lib/util/samba_util.h"
+#include "messages.h"
+
+static struct tevent_context *global_event_ctx = NULL;
+
+struct tevent_context *global_event_context(void)
+{
+ if (!global_event_ctx) {
+ /*
+ * Note we MUST use the NULL context here, not the
+ * autofree context, to avoid side effects in forked
+ * children exiting.
+ */
+ global_event_ctx = samba_tevent_context_init(NULL);
+ }
+ if (!global_event_ctx) {
+ smb_panic("Could not init global event context");
+ }
+ return global_event_ctx;
+}
+
+void global_event_context_free(void)
+{
+ TALLOC_FREE(global_event_ctx);
+}
+
+static struct messaging_context *global_msg_ctx = NULL;
+
+struct messaging_context *global_messaging_context(void)
+{
+ if (global_msg_ctx == NULL) {
+ /*
+ * Note we MUST use the NULL context here, not the
+ * autofree context, to avoid side effects in forked
+ * children exiting.
+ */
+ global_msg_ctx = messaging_init(NULL,
+ global_event_context());
+ }
+ return global_msg_ctx;
+}
+
+void global_messaging_context_free(void)
+{
+ TALLOC_FREE(global_msg_ctx);
+}
diff --git a/source3/lib/global_contexts.h b/source3/lib/global_contexts.h
new file mode 100644
index 0000000..8c5cd09
--- /dev/null
+++ b/source3/lib/global_contexts.h
@@ -0,0 +1,31 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Global contexts
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __GLOBAL_CONTEXTS_H__
+#define __GLOBAL_CONTEXTS_H__
+
+struct tevent_context;
+
+struct tevent_context *global_event_context(void);
+void global_event_context_free(void);
+
+struct messaging_context;
+struct messaging_context *global_messaging_context(void);
+void global_messaging_context_free(void);
+
+#endif
diff --git a/source3/lib/id_cache.c b/source3/lib/id_cache.c
new file mode 100644
index 0000000..adf6ccd
--- /dev/null
+++ b/source3/lib/id_cache.c
@@ -0,0 +1,116 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Notify smbd about idmap changes
+ * @file msg_idmap.c
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Feb 2011
+ *
+ */
+
+#include "includes.h"
+#include "messages.h"
+#include "lib/id_cache.h"
+#include "../lib/util/memcache.h"
+#include "idmap_cache.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../libcli/security/dom_sid.h"
+
+bool id_cache_ref_parse(const char* str, struct id_cache_ref* id)
+{
+ struct dom_sid sid;
+ unsigned long ul;
+ char c, trash;
+
+ if (sscanf(str, "%cID %lu%c", &c, &ul, &trash) == 2) {
+ switch(c) {
+ case 'G':
+ id->id.gid = ul;
+ id->type = GID;
+ return true;
+ case 'U':
+ id->id.uid = ul;
+ id->type = UID;
+ return true;
+ default:
+ break;
+ }
+ } else if (string_to_sid(&sid, str)) {
+ id->id.sid = sid;
+ id->type = SID;
+ return true;
+ } else if (strncmp(str, "USER ", 5) == 0) {
+ id->id.name = str + 5;
+ id->type = USERNAME;
+ return true;
+ }
+ return false;
+}
+
+static bool delete_getpwnam_cache(const char *username)
+{
+ DATA_BLOB name = data_blob_string_const_null(username);
+ DEBUG(6, ("Delete passwd struct for %s from memcache\n",
+ username));
+ memcache_delete(NULL, GETPWNAM_CACHE, name);
+ return true;
+}
+
+void id_cache_delete_from_cache(const struct id_cache_ref* id)
+{
+ switch(id->type) {
+ case UID:
+ idmap_cache_del_uid(id->id.uid);
+ break;
+ case GID:
+ idmap_cache_del_gid(id->id.gid);
+ break;
+ case SID:
+ idmap_cache_del_sid(&id->id.sid);
+ break;
+ case USERNAME:
+ delete_getpwnam_cache(id->id.name);
+ break;
+ default:
+ break;
+ }
+}
+
+void id_cache_delete_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB* data)
+{
+ const char *msg = (data && data->data) ? (const char *)data->data : "<NULL>";
+ struct id_cache_ref id;
+
+ if (!id_cache_ref_parse(msg, &id)) {
+ DEBUG(0, ("Invalid ?ID: %s\n", msg));
+ return;
+ }
+
+ id_cache_delete_from_cache(&id);
+}
+
+void id_cache_register_msgs(struct messaging_context *ctx)
+{
+ messaging_register(ctx, NULL, ID_CACHE_DELETE, id_cache_delete_message);
+}
diff --git a/source3/lib/id_cache.h b/source3/lib/id_cache.h
new file mode 100644
index 0000000..e77486e
--- /dev/null
+++ b/source3/lib/id_cache.h
@@ -0,0 +1,40 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+struct id_cache_ref {
+ union {
+ uid_t uid;
+ gid_t gid;
+ struct dom_sid sid;
+ const char *name;
+ } id;
+ enum {UID, GID, SID, USERNAME} type;
+};
+
+bool id_cache_ref_parse(const char* str, struct id_cache_ref* id);
+
+void id_cache_delete_from_cache(const struct id_cache_ref* id);
+
+void id_cache_register_msgs(struct messaging_context *ctx);
+
+void id_cache_delete_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB* data);
diff --git a/source3/lib/idmap_cache.c b/source3/lib/idmap_cache.c
new file mode 100644
index 0000000..a4b8861
--- /dev/null
+++ b/source3/lib/idmap_cache.c
@@ -0,0 +1,442 @@
+/*
+ Unix SMB/CIFS implementation.
+ ID Mapping Cache
+
+ Copyright (C) Volker Lendecke 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.*/
+
+#include "includes.h"
+#include "idmap_cache.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "lib/gencache.h"
+#include "lib/util/string_wrappers.h"
+
+/**
+ * Find a sid2xid mapping
+ * @param[in] sid the sid to map
+ * @param[out] id where to put the result
+ * @param[out] expired is the cache entry expired?
+ * @retval Was anything in the cache at all?
+ *
+ * If id->id == -1 this was a negative mapping.
+ */
+
+bool idmap_cache_find_sid2unixid(const struct dom_sid *sid, struct unixid *id,
+ bool *expired)
+{
+ struct dom_sid_buf sidstr;
+ char *key;
+ char *value = NULL;
+ char *endptr;
+ time_t timeout;
+ bool ret;
+ struct unixid tmp_id;
+
+ key = talloc_asprintf(talloc_tos(), "IDMAP/SID2XID/%s",
+ dom_sid_str_buf(sid, &sidstr));
+ if (key == NULL) {
+ return false;
+ }
+ ret = gencache_get(key, talloc_tos(), &value, &timeout);
+ if (!ret) {
+ goto done;
+ }
+
+ DEBUG(10, ("Parsing value for key [%s]: value=[%s]\n", key, value));
+
+ if (value[0] == '\0') {
+ DEBUG(0, ("Failed to parse value for key [%s]: "
+ "value is empty\n", key));
+ ret = false;
+ goto done;
+ }
+
+ tmp_id.id = strtol(value, &endptr, 10);
+
+ if ((value == endptr) && (tmp_id.id == 0)) {
+ DEBUG(0, ("Failed to parse value for key [%s]: value[%s] does "
+ "not start with a number\n", key, value));
+ ret = false;
+ goto done;
+ }
+
+ DEBUG(10, ("Parsing value for key [%s]: id=[%llu], endptr=[%s]\n",
+ key, (unsigned long long)tmp_id.id, endptr));
+
+ ret = (*endptr == ':');
+ if (ret) {
+ switch (endptr[1]) {
+ case 'U':
+ tmp_id.type = ID_TYPE_UID;
+ break;
+
+ case 'G':
+ tmp_id.type = ID_TYPE_GID;
+ break;
+
+ case 'B':
+ tmp_id.type = ID_TYPE_BOTH;
+ break;
+
+ case 'N':
+ tmp_id.type = ID_TYPE_NOT_SPECIFIED;
+ break;
+
+ case '\0':
+ DEBUG(0, ("FAILED to parse value for key [%s] "
+ "(id=[%llu], endptr=[%s]): "
+ "no type character after colon\n",
+ key, (unsigned long long)tmp_id.id, endptr));
+ ret = false;
+ goto done;
+ default:
+ DEBUG(0, ("FAILED to parse value for key [%s] "
+ "(id=[%llu], endptr=[%s]): "
+ "illegal type character '%c'\n",
+ key, (unsigned long long)tmp_id.id, endptr,
+ endptr[1]));
+ ret = false;
+ goto done;
+ }
+ if (endptr[2] != '\0') {
+ DEBUG(0, ("FAILED to parse value for key [%s] "
+ "(id=[%llu], endptr=[%s]): "
+ "more than 1 type character after colon\n",
+ key, (unsigned long long)tmp_id.id, endptr));
+ ret = false;
+ goto done;
+ }
+
+ *id = tmp_id;
+ *expired = (timeout <= time(NULL));
+ } else {
+ DEBUG(0, ("FAILED to parse value for key [%s] (value=[%s]): "
+ "colon missing after id=[%llu]\n",
+ key, value, (unsigned long long)tmp_id.id));
+ }
+
+done:
+ TALLOC_FREE(key);
+ TALLOC_FREE(value);
+ return ret;
+}
+
+/**
+ * Find a sid2uid mapping
+ * @param[in] sid the sid to map
+ * @param[out] puid where to put the result
+ * @param[out] expired is the cache entry expired?
+ * @retval Was anything in the cache at all?
+ *
+ * If *puid == -1 this was a negative mapping.
+ */
+
+bool idmap_cache_find_sid2uid(const struct dom_sid *sid, uid_t *puid,
+ bool *expired)
+{
+ bool ret;
+ struct unixid id;
+ ret = idmap_cache_find_sid2unixid(sid, &id, expired);
+ if (!ret) {
+ return false;
+ }
+
+ if (id.type == ID_TYPE_BOTH || id.type == ID_TYPE_UID) {
+ *puid = id.id;
+ } else {
+ *puid = -1;
+ }
+ return true;
+}
+
+/**
+ * Find a sid2gid mapping
+ * @param[in] sid the sid to map
+ * @param[out] pgid where to put the result
+ * @param[out] expired is the cache entry expired?
+ * @retval Was anything in the cache at all?
+ *
+ * If *pgid == -1 this was a negative mapping.
+ */
+
+bool idmap_cache_find_sid2gid(const struct dom_sid *sid, gid_t *pgid,
+ bool *expired)
+{
+ bool ret;
+ struct unixid id;
+ ret = idmap_cache_find_sid2unixid(sid, &id, expired);
+ if (!ret) {
+ return false;
+ }
+
+ if (id.type == ID_TYPE_BOTH || id.type == ID_TYPE_GID) {
+ *pgid = id.id;
+ } else {
+ *pgid = -1;
+ }
+ return true;
+}
+
+struct idmap_cache_xid2sid_state {
+ struct dom_sid *sid;
+ bool *expired;
+ bool ret;
+};
+
+static void idmap_cache_xid2sid_parser(const struct gencache_timeout *timeout,
+ DATA_BLOB blob,
+ void *private_data)
+{
+ struct idmap_cache_xid2sid_state *state =
+ (struct idmap_cache_xid2sid_state *)private_data;
+ char *value;
+
+ if ((blob.length == 0) || (blob.data[blob.length-1] != 0)) {
+ /*
+ * Not a string, can't be a valid mapping
+ */
+ state->ret = false;
+ return;
+ }
+
+ value = (char *)blob.data;
+
+ if ((value[0] == '-') && (value[1] == '\0')) {
+ /*
+ * Return NULL SID, see comment to uid2sid
+ */
+ *state->sid = (struct dom_sid) {0};
+ state->ret = true;
+ } else {
+ state->ret = string_to_sid(state->sid, value);
+ }
+ if (state->ret) {
+ *state->expired = gencache_timeout_expired(timeout);
+ }
+}
+
+/**
+ * Find a xid2sid mapping
+ * @param[in] id the unix id to map
+ * @param[out] sid where to put the result
+ * @param[out] expired is the cache entry expired?
+ * @retval Was anything in the cache at all?
+ *
+ * If "is_null_sid(sid)", this was a negative mapping.
+ */
+bool idmap_cache_find_xid2sid(
+ const struct unixid *id, struct dom_sid *sid, bool *expired)
+{
+ struct idmap_cache_xid2sid_state state = {
+ .sid = sid, .expired = expired
+ };
+ fstring key;
+ char c;
+
+ switch (id->type) {
+ case ID_TYPE_UID:
+ c = 'U';
+ break;
+ case ID_TYPE_GID:
+ c = 'G';
+ break;
+ default:
+ return false;
+ }
+
+ fstr_sprintf(key, "IDMAP/%cID2SID/%d", c, (int)id->id);
+
+ gencache_parse(key, idmap_cache_xid2sid_parser, &state);
+ return state.ret;
+}
+
+
+/**
+ * Store a mapping in the idmap cache
+ * @param[in] sid the sid to map
+ * @param[in] unix_id the unix_id to map
+ *
+ * If both parameters are valid values, then a positive mapping in both
+ * directions is stored. If "is_null_sid(sid)" is true, then this will be a
+ * negative mapping of xid, we want to cache that for this xid we could not
+ * find anything. Likewise if "xid==-1", then we want to cache that we did not
+ * find a mapping for the sid passed here.
+ */
+
+void idmap_cache_set_sid2unixid(const struct dom_sid *sid, struct unixid *unix_id)
+{
+ time_t now = time(NULL);
+ time_t timeout;
+ fstring key, value;
+
+ if (!is_null_sid(sid)) {
+ struct dom_sid_buf sidstr;
+ fstr_sprintf(key, "IDMAP/SID2XID/%s",
+ dom_sid_str_buf(sid, &sidstr));
+ switch (unix_id->type) {
+ case ID_TYPE_UID:
+ fstr_sprintf(value, "%d:U", (int)unix_id->id);
+ break;
+ case ID_TYPE_GID:
+ fstr_sprintf(value, "%d:G", (int)unix_id->id);
+ break;
+ case ID_TYPE_BOTH:
+ fstr_sprintf(value, "%d:B", (int)unix_id->id);
+ break;
+ case ID_TYPE_NOT_SPECIFIED:
+ fstr_sprintf(value, "%d:N", (int)unix_id->id);
+ break;
+ default:
+ return;
+ }
+ timeout = (unix_id->id == -1)
+ ? lp_idmap_negative_cache_time()
+ : lp_idmap_cache_time();
+ gencache_set(key, value, now + timeout);
+ }
+ if (unix_id->id != -1) {
+ if (is_null_sid(sid)) {
+ /* negative xid mapping */
+ fstrcpy(value, "-");
+ timeout = lp_idmap_negative_cache_time();
+ }
+ else {
+ sid_to_fstring(value, sid);
+ timeout = lp_idmap_cache_time();
+ }
+ switch (unix_id->type) {
+ case ID_TYPE_BOTH:
+ fstr_sprintf(key, "IDMAP/UID2SID/%d", (int)unix_id->id);
+ gencache_set(key, value, now + timeout);
+ fstr_sprintf(key, "IDMAP/GID2SID/%d", (int)unix_id->id);
+ gencache_set(key, value, now + timeout);
+ return;
+
+ case ID_TYPE_UID:
+ fstr_sprintf(key, "IDMAP/UID2SID/%d", (int)unix_id->id);
+ break;
+
+ case ID_TYPE_GID:
+ fstr_sprintf(key, "IDMAP/GID2SID/%d", (int)unix_id->id);
+ break;
+
+ default:
+ return;
+ }
+ gencache_set(key, value, now + timeout);
+ }
+}
+
+static char* key_xid2sid_str(TALLOC_CTX* mem_ctx, char t, const char* id) {
+ return talloc_asprintf(mem_ctx, "IDMAP/%cID2SID/%s", t, id);
+}
+
+static char* key_xid2sid(TALLOC_CTX* mem_ctx, char t, int id) {
+ char str[32];
+ snprintf(str, sizeof(str), "%d", id);
+ return key_xid2sid_str(mem_ctx, t, str);
+}
+
+static char* key_sid2xid_str(TALLOC_CTX* mem_ctx, const char* id) {
+ return talloc_asprintf(mem_ctx, "IDMAP/SID2XID/%s", id);
+}
+
+static bool idmap_cache_del_xid(char t, int xid)
+{
+ TALLOC_CTX* mem_ctx = talloc_stackframe();
+ const char* key = key_xid2sid(mem_ctx, t, xid);
+ char* sid_str = NULL;
+ time_t timeout;
+ bool ret = true;
+
+ if (!gencache_get(key, mem_ctx, &sid_str, &timeout)) {
+ DEBUG(3, ("no entry: %s\n", key));
+ ret = false;
+ goto done;
+ }
+
+ if (sid_str[0] != '-') {
+ const char* sid_key = key_sid2xid_str(mem_ctx, sid_str);
+ if (!gencache_del(sid_key)) {
+ DEBUG(2, ("failed to delete: %s\n", sid_key));
+ ret = false;
+ } else {
+ DEBUG(5, ("delete: %s\n", sid_key));
+ }
+
+ }
+
+ if (!gencache_del(key)) {
+ DEBUG(1, ("failed to delete: %s\n", key));
+ ret = false;
+ } else {
+ DEBUG(5, ("delete: %s\n", key));
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+bool idmap_cache_del_uid(uid_t uid) {
+ return idmap_cache_del_xid('U', uid);
+}
+
+bool idmap_cache_del_gid(gid_t gid) {
+ return idmap_cache_del_xid('G', gid);
+}
+
+bool idmap_cache_del_sid(const struct dom_sid *sid)
+{
+ TALLOC_CTX* mem_ctx = talloc_stackframe();
+ bool ret = true;
+ bool expired;
+ struct unixid id;
+ struct dom_sid_buf sidbuf;
+ const char *sid_key;
+
+ if (!idmap_cache_find_sid2unixid(sid, &id, &expired)) {
+ ret = false;
+ goto done;
+ }
+
+ if (id.id != -1) {
+ switch (id.type) {
+ case ID_TYPE_BOTH:
+ idmap_cache_del_xid('U', id.id);
+ idmap_cache_del_xid('G', id.id);
+ break;
+ case ID_TYPE_UID:
+ idmap_cache_del_xid('U', id.id);
+ break;
+ case ID_TYPE_GID:
+ idmap_cache_del_xid('G', id.id);
+ break;
+ default:
+ break;
+ }
+ }
+
+ sid_key = key_sid2xid_str(mem_ctx, dom_sid_str_buf(sid, &sidbuf));
+ if (sid_key == NULL) {
+ return false;
+ }
+ /* If the mapping was symmetric, then this should fail */
+ gencache_del(sid_key);
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
diff --git a/source3/lib/idmap_cache.h b/source3/lib/idmap_cache.h
new file mode 100644
index 0000000..5a90902
--- /dev/null
+++ b/source3/lib/idmap_cache.h
@@ -0,0 +1,40 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * ID Mapping Cache
+ *
+ * Copyright (C) Volker Lendecke 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIB_IDMAP_CACHE_H_
+#define _LIB_IDMAP_CACHE_H_
+
+/* The following definitions come from lib/idmap_cache.c */
+struct unixid;
+bool idmap_cache_find_sid2unixid(const struct dom_sid *sid, struct unixid *id,
+ bool *expired);
+bool idmap_cache_find_sid2uid(const struct dom_sid *sid, uid_t *puid,
+ bool *expired);
+bool idmap_cache_find_sid2gid(const struct dom_sid *sid, gid_t *pgid,
+ bool *expired);
+bool idmap_cache_find_xid2sid(
+ const struct unixid *id, struct dom_sid *sid, bool *expired);
+void idmap_cache_set_sid2unixid(const struct dom_sid *sid, struct unixid *unix_id);
+
+bool idmap_cache_del_uid(uid_t uid);
+bool idmap_cache_del_gid(gid_t gid);
+bool idmap_cache_del_sid(const struct dom_sid *sid);
+
+#endif /* _LIB_IDMAP_CACHE_H_ */
diff --git a/source3/lib/interface.c b/source3/lib/interface.c
new file mode 100644
index 0000000..beb1003
--- /dev/null
+++ b/source3/lib/interface.c
@@ -0,0 +1,721 @@
+/*
+ Unix SMB/CIFS implementation.
+ multiple interface handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/socket/interfaces.h"
+#include "librpc/gen_ndr/ioctl.h"
+#include "lib/util/smb_strtox.h"
+
+static struct iface_struct *probed_ifaces;
+static int total_probed;
+
+static struct interface *local_interfaces;
+
+/****************************************************************************
+ Check if an IP is one of mine.
+**************************************************************************/
+
+bool ismyaddr(const struct sockaddr *ip)
+{
+ struct interface *i;
+ for (i=local_interfaces;i;i=i->next) {
+ if (sockaddr_equal((struct sockaddr *)&i->ip,ip)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ismyip_v4(struct in_addr ip)
+{
+ struct sockaddr_storage ss;
+ in_addr_to_sockaddr_storage(&ss, ip);
+ return ismyaddr((struct sockaddr *)&ss);
+}
+
+/****************************************************************************
+ Try and find an interface that matches an ip. If we cannot, return NULL.
+**************************************************************************/
+
+static struct interface *iface_find(const struct sockaddr *ip,
+ bool check_mask)
+{
+ struct interface *i;
+
+ if (is_address_any(ip)) {
+ return local_interfaces;
+ }
+
+ for (i=local_interfaces;i;i=i->next) {
+ if (check_mask) {
+ if (same_net(ip, (struct sockaddr *)&i->ip, (struct sockaddr *)&i->netmask)) {
+ return i;
+ }
+ } else if (sockaddr_equal((struct sockaddr *)&i->ip, ip)) {
+ return i;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Check if a packet is from a local (known) net.
+**************************************************************************/
+
+bool is_local_net(const struct sockaddr *from)
+{
+ struct interface *i;
+ for (i=local_interfaces;i;i=i->next) {
+ if (same_net(from, (struct sockaddr *)&i->ip, (struct sockaddr *)&i->netmask)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+#if defined(HAVE_IPV6)
+void setup_linklocal_scope_id(struct sockaddr *pss)
+{
+ struct interface *i;
+ for (i=local_interfaces;i;i=i->next) {
+ if (sockaddr_equal((struct sockaddr *)&i->ip,pss)) {
+ struct sockaddr_in6 *psa6 =
+ (struct sockaddr_in6 *)pss;
+ psa6->sin6_scope_id = if_nametoindex(i->name);
+ return;
+ }
+ }
+}
+#endif
+
+/****************************************************************************
+ Check if a packet is from a local (known) net.
+**************************************************************************/
+
+bool is_local_net_v4(struct in_addr from)
+{
+ struct sockaddr_storage ss;
+
+ in_addr_to_sockaddr_storage(&ss, from);
+ return is_local_net((struct sockaddr *)&ss);
+}
+
+/****************************************************************************
+ How many interfaces do we have ?
+**************************************************************************/
+
+int iface_count(void)
+{
+ int ret = 0;
+ struct interface *i;
+
+ for (i=local_interfaces;i;i=i->next) {
+ ret++;
+ }
+ return ret;
+}
+
+/****************************************************************************
+ How many non-loopback IPv4 interfaces do we have ?
+**************************************************************************/
+
+int iface_count_v4_nl(void)
+{
+ int ret = 0;
+ struct interface *i;
+
+ for (i=local_interfaces;i;i=i->next) {
+ if (is_loopback_addr((struct sockaddr *)&i->ip)) {
+ continue;
+ }
+ if (i->ip.ss_family == AF_INET) {
+ ret++;
+ }
+ }
+ return ret;
+}
+
+/****************************************************************************
+ Return a pointer to the in_addr of the first IPv4 interface that's
+ not 0.0.0.0.
+**************************************************************************/
+
+const struct in_addr *first_ipv4_iface(void)
+{
+ struct interface *i;
+
+ for (i=local_interfaces;i ;i=i->next) {
+ if ((i->ip.ss_family == AF_INET) &&
+ (!is_zero_ip_v4(((struct sockaddr_in *)&i->ip)->sin_addr)))
+ {
+ break;
+ }
+ }
+
+ if (!i) {
+ return NULL;
+ }
+ return &((const struct sockaddr_in *)&i->ip)->sin_addr;
+}
+
+/****************************************************************************
+ Return the Nth interface.
+**************************************************************************/
+
+struct interface *get_interface(int n)
+{
+ struct interface *i;
+
+ for (i=local_interfaces;i && n;i=i->next) {
+ n--;
+ }
+
+ if (i) {
+ return i;
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Return IP sockaddr_storage of the Nth interface.
+**************************************************************************/
+
+const struct sockaddr_storage *iface_n_sockaddr_storage(int n)
+{
+ struct interface *i;
+
+ for (i=local_interfaces;i && n;i=i->next) {
+ n--;
+ }
+
+ if (i) {
+ return &i->ip;
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Return IPv4 of the Nth interface (if a v4 address). NULL otherwise.
+**************************************************************************/
+
+const struct in_addr *iface_n_ip_v4(int n)
+{
+ struct interface *i;
+
+ for (i=local_interfaces;i && n;i=i->next) {
+ n--;
+ }
+
+ if (i && i->ip.ss_family == AF_INET) {
+ return &((const struct sockaddr_in *)&i->ip)->sin_addr;
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Return IPv4 bcast of the Nth interface (if a v4 address). NULL otherwise.
+**************************************************************************/
+
+const struct in_addr *iface_n_bcast_v4(int n)
+{
+ struct interface *i;
+
+ for (i=local_interfaces;i && n;i=i->next) {
+ n--;
+ }
+
+ if (i && i->ip.ss_family == AF_INET) {
+ return &((const struct sockaddr_in *)&i->bcast)->sin_addr;
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Return bcast of the Nth interface.
+**************************************************************************/
+
+const struct sockaddr_storage *iface_n_bcast(int n)
+{
+ struct interface *i;
+
+ for (i=local_interfaces;i && n;i=i->next) {
+ n--;
+ }
+
+ if (i) {
+ return &i->bcast;
+ }
+ return NULL;
+}
+
+/* these 3 functions return the ip/bcast/nmask for the interface
+ most appropriate for the given ip address. If they can't find
+ an appropriate interface they return the requested field of the
+ first known interface. */
+
+const struct sockaddr_storage *iface_ip(const struct sockaddr *ip)
+{
+ struct interface *i = iface_find(ip, true);
+ if (i) {
+ return &i->ip;
+ }
+
+ /* Search for the first interface with
+ * matching address family. */
+
+ for (i=local_interfaces;i;i=i->next) {
+ if (i->ip.ss_family == ip->sa_family) {
+ return &i->ip;
+ }
+ }
+ return NULL;
+}
+
+/*
+ return True if a IP is directly reachable on one of our interfaces
+*/
+
+bool iface_local(const struct sockaddr *ip)
+{
+ return iface_find(ip, true) ? true : false;
+}
+
+/****************************************************************************
+ Add an interface to the linked list of interfaces.
+****************************************************************************/
+
+static void add_interface(const struct iface_struct *ifs)
+{
+ char addr[INET6_ADDRSTRLEN];
+ struct interface *iface;
+
+ if (iface_find((const struct sockaddr *)&ifs->ip, False)) {
+ DEBUG(3,("add_interface: not adding duplicate interface %s\n",
+ print_sockaddr(addr, sizeof(addr), &ifs->ip) ));
+ return;
+ }
+
+ if (!(ifs->flags & (IFF_BROADCAST|IFF_LOOPBACK))) {
+ DEBUG(3,("not adding non-broadcast interface %s\n",
+ ifs->name ));
+ return;
+ }
+
+ iface = SMB_MALLOC_P(struct interface);
+ if (!iface) {
+ return;
+ }
+
+ ZERO_STRUCTPN(iface);
+
+ iface->name = SMB_STRDUP(ifs->name);
+ if (!iface->name) {
+ SAFE_FREE(iface);
+ return;
+ }
+ iface->flags = ifs->flags;
+ iface->ip = ifs->ip;
+ iface->netmask = ifs->netmask;
+ iface->bcast = ifs->bcast;
+ iface->linkspeed = ifs->linkspeed;
+ iface->capability = ifs->capability;
+ iface->if_index = ifs->if_index;
+
+ DLIST_ADD(local_interfaces, iface);
+
+ DEBUG(2,("added interface %s ip=%s ",
+ iface->name,
+ print_sockaddr(addr, sizeof(addr), &iface->ip) ));
+ DEBUG(2,("bcast=%s ",
+ print_sockaddr(addr, sizeof(addr),
+ &iface->bcast) ));
+ DEBUG(2,("netmask=%s\n",
+ print_sockaddr(addr, sizeof(addr),
+ &iface->netmask) ));
+}
+
+
+static void parse_extra_info(char *key, uint64_t *speed, uint32_t *cap,
+ uint32_t *if_index)
+{
+ while (key != NULL && *key != '\0') {
+ char *next_key;
+ char *val;
+ int error = 0;
+
+ next_key = strchr_m(key, ',');
+ if (next_key != NULL) {
+ *next_key++ = 0;
+ }
+
+ val = strchr_m(key, '=');
+ if (val != NULL) {
+ *val++ = 0;
+
+ if (strequal_m(key, "speed")) {
+ *speed = (uint64_t)smb_strtoull(val,
+ NULL,
+ 0,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_DEBUG("Invalid speed value (%s)\n", val);
+ }
+ } else if (strequal_m(key, "capability")) {
+ if (strequal_m(val, "RSS")) {
+ *cap |= FSCTL_NET_IFACE_RSS_CAPABLE;
+ } else if (strequal(val, "RDMA")) {
+ *cap |= FSCTL_NET_IFACE_RDMA_CAPABLE;
+ } else {
+ DBG_WARNING("Capability unknown: "
+ "'%s'\n", val);
+ }
+ } else if (strequal_m(key, "if_index")) {
+ *if_index = (uint32_t)smb_strtoul(val,
+ NULL,
+ 0,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_DEBUG("Invalid key value (%s)\n", val);
+ }
+ } else {
+ DBG_DEBUG("Key unknown: '%s'\n", key);
+ }
+ }
+
+ key = next_key;
+ }
+}
+
+/****************************************************************************
+ Interpret a single element from a interfaces= config line.
+
+ This handles the following different forms:
+
+ 1) wildcard interface name
+ 2) DNS name
+ 3) IP/masklen
+ 4) ip/mask
+ 5) bcast/mask
+
+ Additional information for an interface can be specified with
+ this extended syntax:
+
+ "interface[;key1=value1[,key2=value2[...]]]"
+
+ Note: The double quoting is important for the
+ smb.conf parser! Otherwise the ';' and ',' separates
+ two interfaces.
+
+ where
+ - keys known: 'speed', 'capability', 'if_index'
+ - speed is in bits per second
+ - capabilities known: 'RSS', 'RDMA'
+ - if_index should be used with care, because
+ these indexes should not conicide with indexes
+ the kernel sets...
+
+ Note: The specified values overwrite the autodetected values!
+
+****************************************************************************/
+
+static void interpret_interface(char *token)
+{
+ struct sockaddr_storage ss;
+ struct sockaddr_storage ss_mask;
+ struct sockaddr_storage ss_net;
+ struct sockaddr_storage ss_bcast;
+ struct iface_struct ifs;
+ char *p;
+ int i;
+ bool added=false;
+ bool goodaddr = false;
+ uint64_t speed = 0;
+ uint32_t cap = FSCTL_NET_IFACE_NONE_CAPABLE;
+ uint32_t if_index = 0;
+ bool speed_set = false;
+ bool cap_set = false;
+ bool if_index_set = false;
+
+ /*
+ * extract speed / capability information if present
+ */
+ p = strchr_m(token, ';');
+ if (p != NULL) {
+ *p++ = 0;
+ parse_extra_info(p, &speed, &cap, &if_index);
+ if (speed != 0) {
+ speed_set = true;
+ }
+ if (cap != FSCTL_NET_IFACE_NONE_CAPABLE) {
+ cap_set = true;
+ }
+ if (if_index != 0) {
+ if_index_set = true;
+ }
+ }
+
+ /* first check if it is an interface name */
+ for (i=0;i<total_probed;i++) {
+ if (gen_fnmatch(token, probed_ifaces[i].name) == 0) {
+ if (speed_set) {
+ probed_ifaces[i].linkspeed = speed;
+ }
+ if (cap_set) {
+ probed_ifaces[i].capability = cap;
+ }
+ if (if_index_set) {
+ probed_ifaces[i].if_index = if_index;
+ }
+ add_interface(&probed_ifaces[i]);
+ added = true;
+ }
+ }
+ if (added) {
+ return;
+ }
+
+ p = strchr_m(token,'/');
+ if (p == NULL) {
+ if (!interpret_string_addr(&ss, token, 0)) {
+ DEBUG(2, ("interpret_interface: Can't find address "
+ "for %s\n", token));
+ return;
+ }
+
+ for (i=0;i<total_probed;i++) {
+ if (sockaddr_equal((struct sockaddr *)&ss,
+ (struct sockaddr *)&probed_ifaces[i].ip))
+ {
+ if (speed_set) {
+ probed_ifaces[i].linkspeed = speed;
+ }
+ if (cap_set) {
+ probed_ifaces[i].capability = cap;
+ }
+ if (if_index_set) {
+ probed_ifaces[i].if_index = if_index;
+ }
+ add_interface(&probed_ifaces[i]);
+ return;
+ }
+ }
+ DEBUG(2,("interpret_interface: "
+ "can't determine interface for %s\n",
+ token));
+ return;
+ }
+
+ /* parse it into an IP address/netmasklength pair */
+ *p = 0;
+ goodaddr = interpret_string_addr(&ss, token, 0);
+ *p++ = '/';
+
+ if (!goodaddr) {
+ DEBUG(2,("interpret_interface: "
+ "can't determine interface for %s\n",
+ token));
+ return;
+ }
+
+ if (strlen(p) > 2) {
+ goodaddr = interpret_string_addr(&ss_mask, p, 0);
+ if (!goodaddr) {
+ DEBUG(2,("interpret_interface: "
+ "can't determine netmask from %s\n",
+ p));
+ return;
+ }
+ } else {
+ int error = 0;
+ unsigned long val;
+
+ val = smb_strtoul(p, NULL, 0, &error, SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ DEBUG(2,("interpret_interface: "
+ "can't determine netmask value from %s\n",
+ p));
+ return;
+ }
+ if (!make_netmask(&ss_mask, &ss, val)) {
+ DEBUG(2,("interpret_interface: "
+ "can't apply netmask value %lu from %s\n",
+ val,
+ p));
+ return;
+ }
+ }
+
+ make_bcast(&ss_bcast, &ss, &ss_mask);
+ make_net(&ss_net, &ss, &ss_mask);
+
+ /* Maybe the first component was a broadcast address. */
+ if (sockaddr_equal((struct sockaddr *)&ss_bcast, (struct sockaddr *)&ss) ||
+ sockaddr_equal((struct sockaddr *)&ss_net, (struct sockaddr *)&ss)) {
+ for (i=0;i<total_probed;i++) {
+ if (same_net((struct sockaddr *)&ss,
+ (struct sockaddr *)&probed_ifaces[i].ip,
+ (struct sockaddr *)&ss_mask)) {
+ /* Temporarily replace netmask on
+ * the detected interface - user knows
+ * best.... */
+ struct sockaddr_storage saved_mask =
+ probed_ifaces[i].netmask;
+ probed_ifaces[i].netmask = ss_mask;
+ DEBUG(2,("interpret_interface: "
+ "using netmask value %s from "
+ "config file on interface %s\n",
+ p,
+ probed_ifaces[i].name));
+ if (speed_set) {
+ probed_ifaces[i].linkspeed = speed;
+ }
+ if (cap_set) {
+ probed_ifaces[i].capability = cap;
+ }
+ if (if_index_set) {
+ probed_ifaces[i].if_index = if_index;
+ }
+ add_interface(&probed_ifaces[i]);
+ probed_ifaces[i].netmask = saved_mask;
+ return;
+ }
+ }
+ DEBUG(2,("interpret_interface: Can't determine ip for "
+ "broadcast address %s\n",
+ token));
+ return;
+ }
+
+ /* Just fake up the interface definition. User knows best. */
+
+ DEBUG(2,("interpret_interface: Adding interface %s\n",
+ token));
+
+ ZERO_STRUCT(ifs);
+ (void)strlcpy(ifs.name, token, sizeof(ifs.name));
+ ifs.flags = IFF_BROADCAST;
+ ifs.ip = ss;
+ ifs.netmask = ss_mask;
+ ifs.bcast = ss_bcast;
+ if (if_index_set) {
+ ifs.if_index = if_index;
+ }
+ if (speed_set) {
+ ifs.linkspeed = speed;
+ } else {
+ ifs.linkspeed = 1000 * 1000 * 1000;
+ }
+ ifs.capability = cap;
+ add_interface(&ifs);
+}
+
+/****************************************************************************
+ Load the list of network interfaces.
+****************************************************************************/
+
+void load_interfaces(void)
+{
+ struct iface_struct *ifaces = NULL;
+ const char **ptr = lp_interfaces();
+ int i;
+
+ gfree_interfaces();
+
+ /* Probe the kernel for interfaces */
+ total_probed = get_interfaces(talloc_tos(), &ifaces);
+
+ if (total_probed > 0) {
+ probed_ifaces = (struct iface_struct *)smb_memdup(ifaces,
+ sizeof(ifaces[0])*total_probed);
+ if (!probed_ifaces) {
+ DEBUG(0,("ERROR: smb_memdup failed\n"));
+ exit(1);
+ }
+ }
+ TALLOC_FREE(ifaces);
+
+ /* if we don't have a interfaces line then use all broadcast capable
+ interfaces except loopback */
+ if (!ptr || !*ptr || !**ptr) {
+ if (total_probed <= 0) {
+ DEBUG(0,("ERROR: Could not determine network "
+ "interfaces, you must use a interfaces config line\n"));
+ exit(1);
+ }
+ for (i=0;i<total_probed;i++) {
+ if (probed_ifaces[i].flags & IFF_BROADCAST) {
+ add_interface(&probed_ifaces[i]);
+ }
+ }
+ return;
+ }
+
+ if (ptr) {
+ while (*ptr) {
+ char *ptr_cpy = SMB_STRDUP(*ptr);
+ if (ptr_cpy) {
+ interpret_interface(ptr_cpy);
+ free(ptr_cpy);
+ }
+ ptr++;
+ }
+ }
+
+ if (!local_interfaces) {
+ DEBUG(0,("WARNING: no network interfaces found\n"));
+ }
+}
+
+
+void gfree_interfaces(void)
+{
+ while (local_interfaces) {
+ struct interface *iface = local_interfaces;
+ DLIST_REMOVE(local_interfaces, local_interfaces);
+ SAFE_FREE(iface->name);
+ SAFE_FREE(iface);
+ }
+
+ SAFE_FREE(probed_ifaces);
+}
+
+/****************************************************************************
+ Return True if the list of probed interfaces has changed.
+****************************************************************************/
+
+bool interfaces_changed(void)
+{
+ bool ret = false;
+ int n;
+ struct iface_struct *ifaces = NULL;
+
+ n = get_interfaces(talloc_tos(), &ifaces);
+
+ if ((n > 0 )&& (n != total_probed ||
+ memcmp(ifaces, probed_ifaces, sizeof(ifaces[0])*n))) {
+ ret = true;
+ }
+
+ TALLOC_FREE(ifaces);
+ return ret;
+}
diff --git a/source3/lib/interface.h b/source3/lib/interface.h
new file mode 100644
index 0000000..f45435b
--- /dev/null
+++ b/source3/lib/interface.h
@@ -0,0 +1,46 @@
+/*
+ *
+ * Unix SMB/CIFS implementation.
+ *
+ * Type definitions for interfaces
+ *
+ * Copyright (c) 2020 Andreas Schneider <asn@samba.org>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _INTERFACE_H
+#define _INTERFACE_H
+
+#include <system/network.h>
+
+bool ismyaddr(const struct sockaddr *ip);
+bool ismyip_v4(struct in_addr ip);
+bool is_local_net(const struct sockaddr *from);
+void setup_linklocal_scope_id(struct sockaddr *pss);
+bool is_local_net_v4(struct in_addr from);
+int iface_count(void);
+int iface_count_v4_nl(void);
+const struct in_addr *first_ipv4_iface(void);
+struct interface *get_interface(int n);
+const struct sockaddr_storage *iface_n_sockaddr_storage(int n);
+const struct in_addr *iface_n_ip_v4(int n);
+const struct in_addr *iface_n_bcast_v4(int n);
+const struct sockaddr_storage *iface_n_bcast(int n);
+const struct sockaddr_storage *iface_ip(const struct sockaddr *ip);
+bool iface_local(const struct sockaddr *ip);
+void load_interfaces(void);
+void gfree_interfaces(void);
+bool interfaces_changed(void);
+
+#endif /* _INTERFACE_H */
diff --git a/source3/lib/ldap_debug_handler.c b/source3/lib/ldap_debug_handler.c
new file mode 100644
index 0000000..27d9a20
--- /dev/null
+++ b/source3/lib/ldap_debug_handler.c
@@ -0,0 +1,51 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Intercept libldap debug output.
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smb_ldap.h"
+
+#if defined(HAVE_LDAP) && defined(HAVE_LBER_LOG_PRINT_FN)
+static void samba_ldap_log_print_fn(LDAP_CONST char *data)
+{
+ DEBUG(lp_ldap_debug_threshold(), ("[LDAP] %s", data));
+}
+#endif
+
+void init_ldap_debugging(void)
+{
+#if defined(HAVE_LDAP) && defined(HAVE_LBER_LOG_PRINT_FN)
+ int ret;
+ int ldap_debug_level = lp_ldap_debug_level();
+
+ ret = ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &ldap_debug_level);
+ if (ret != LDAP_OPT_SUCCESS) {
+ DEBUG(10, ("Error setting LDAP debug level.\n"));
+ }
+
+ if (ldap_debug_level == 0) {
+ return;
+ }
+
+ ret = ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN,
+ (void *)samba_ldap_log_print_fn);
+ if (ret != LBER_OPT_SUCCESS) {
+ DEBUG(10, ("Error setting LBER log print function.\n"));
+ }
+#endif /* HAVE_LDAP && HAVE_LBER_LOG_PRINT_FN */
+}
diff --git a/source3/lib/ldap_escape.c b/source3/lib/ldap_escape.c
new file mode 100644
index 0000000..b44662c
--- /dev/null
+++ b/source3/lib/ldap_escape.c
@@ -0,0 +1,137 @@
+/*
+ Unix SMB/CIFS implementation.
+ ldap filter argument escaping
+
+ Copyright (C) 1998, 1999, 2000 Luke Howard <lukeh@padl.com>,
+ Copyright (C) 2003 Andrew Bartlett <abartlet@samba.org>
+
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+/**
+ * Escape a parameter to an LDAP filter string, so they cannot contain
+ * embedded ( ) * or \ chars which may cause it not to parse correctly.
+ *
+ * @param s The input string
+ *
+ * @return A string allocated with talloc(), containing the escaped string,
+ * and to be talloc_free()ed by the caller.
+ **/
+
+char *escape_ldap_string(TALLOC_CTX *mem_ctx, const char *s)
+{
+ size_t len = strlen(s)+1;
+ char *output = talloc_array(mem_ctx, char, len);
+ const char *sub;
+ int i = 0;
+ char *p = output;
+
+ if (output == NULL) {
+ return NULL;
+ }
+
+ while (*s)
+ {
+ switch (*s)
+ {
+ case '*':
+ sub = "\\2a";
+ break;
+ case '(':
+ sub = "\\28";
+ break;
+ case ')':
+ sub = "\\29";
+ break;
+ case '\\':
+ sub = "\\5c";
+ break;
+ default:
+ sub = NULL;
+ break;
+ }
+
+ if (sub) {
+ char *tmp;
+ len = len + 3;
+ tmp = talloc_realloc(mem_ctx, output, char, len);
+ if (tmp == NULL) {
+ TALLOC_FREE(output);
+ return NULL;
+ }
+ output = tmp;
+
+ p = &output[i];
+ memcpy(p, sub, 3);
+ p += 3;
+ i += 3;
+
+ } else {
+ *p = *s;
+ p++;
+ i++;
+ }
+ s++;
+ }
+
+ *p = '\0';
+ return output;
+}
+
+char *escape_rdn_val_string_alloc(const char *s)
+{
+ char *output, *p;
+
+ /* The maximum size of the escaped string can be twice the actual size */
+ output = (char *)SMB_MALLOC(2*strlen(s) + 1);
+
+ if (output == NULL) {
+ return NULL;
+ }
+
+ p = output;
+
+ while (*s)
+ {
+ switch (*s)
+ {
+ case ',':
+ case '=':
+ case '+':
+ case '<':
+ case '>':
+ case '#':
+ case ';':
+ case '\\':
+ case '\"':
+ *p++ = '\\';
+ *p++ = *s;
+ break;
+ default:
+ *p = *s;
+ p++;
+ }
+
+ s++;
+ }
+
+ *p = '\0';
+
+ /* resize the string to the actual final size */
+ output = (char *)SMB_REALLOC(output, strlen(output) + 1);
+ return output;
+}
diff --git a/source3/lib/lsa.c b/source3/lib/lsa.c
new file mode 100644
index 0000000..1c24a42
--- /dev/null
+++ b/source3/lib/lsa.c
@@ -0,0 +1,73 @@
+/*
+ * Helper functions related to the LSA server
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/***************************************************************************
+ init_lsa_ref_domain_list - adds a domain if it's not already in, returns index.
+***************************************************************************/
+
+#include "replace.h"
+#include "lib/util/data_blob.h"
+#include "lib/util/time.h"
+#include "libcli/security/dom_sid.h"
+#include "librpc/gen_ndr/lsa.h"
+#include "lsa.h"
+
+int init_lsa_ref_domain_list(TALLOC_CTX *mem_ctx,
+ struct lsa_RefDomainList *ref,
+ const char *dom_name,
+ struct dom_sid *dom_sid)
+{
+ int num = 0;
+
+ if (dom_name != NULL) {
+ for (num = 0; num < ref->count; num++) {
+ if (dom_sid_equal(dom_sid, ref->domains[num].sid)) {
+ return num;
+ }
+ }
+ } else {
+ num = ref->count;
+ }
+
+ if (num >= LSA_REF_DOMAIN_LIST_MULTIPLIER) {
+ /* index not found, already at maximum domain limit */
+ return -1;
+ }
+
+ ref->count = num + 1;
+ ref->max_size = LSA_REF_DOMAIN_LIST_MULTIPLIER;
+
+ ref->domains = talloc_realloc(mem_ctx, ref->domains,
+ struct lsa_DomainInfo, ref->count);
+ if (!ref->domains) {
+ return -1;
+ }
+
+ ZERO_STRUCT(ref->domains[num]);
+
+ ref->domains[num].name.string = talloc_strdup(mem_ctx, dom_name);
+ if (!ref->domains[num].name.string) {
+ return -1;
+ }
+
+ ref->domains[num].sid = dom_sid_dup(mem_ctx, dom_sid);
+ if (!ref->domains[num].sid) {
+ return -1;
+ }
+
+ return num;
+}
diff --git a/source3/lib/messages.c b/source3/lib/messages.c
new file mode 100644
index 0000000..7d3d469
--- /dev/null
+++ b/source3/lib/messages.c
@@ -0,0 +1,1480 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba internal messaging functions
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) 2001 by Martin Pool
+ Copyright (C) 2002 by Jeremy Allison
+ Copyright (C) 2007 by Volker Lendecke
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ @defgroup messages Internal messaging framework
+ @{
+ @file messages.c
+
+ @brief Module for internal messaging between Samba daemons.
+
+ The idea is that if a part of Samba wants to do communication with
+ another Samba process then it will do a message_register() of a
+ dispatch function, and use message_send_pid() to send messages to
+ that process.
+
+ The dispatch function is given the pid of the sender, and it can
+ use that to reply by message_send_pid(). See ping_message() for a
+ simple example.
+
+ @caution Dispatch functions must be able to cope with incoming
+ messages on an *odd* byte boundary.
+
+ This system doesn't have any inherent size limitations but is not
+ very efficient for large messages or when messages are sent in very
+ quick succession.
+
+*/
+
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "dbwrap/dbwrap.h"
+#include "serverid.h"
+#include "messages.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/background.h"
+#include "lib/messaging/messages_dgm.h"
+#include "lib/util/iov_buf.h"
+#include "lib/util/server_id_db.h"
+#include "lib/messaging/messages_dgm_ref.h"
+#include "lib/messages_ctdb.h"
+#include "lib/messages_ctdb_ref.h"
+#include "lib/messages_util.h"
+#include "cluster_support.h"
+#include "ctdbd_conn.h"
+#include "ctdb_srvids.h"
+
+#ifdef CLUSTER_SUPPORT
+#include "ctdb_protocol.h"
+#endif
+
+struct messaging_callback {
+ struct messaging_callback *prev, *next;
+ uint32_t msg_type;
+ void (*fn)(struct messaging_context *msg, void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data);
+ void *private_data;
+};
+
+struct messaging_registered_ev {
+ struct tevent_context *ev;
+ struct tevent_immediate *im;
+ size_t refcount;
+};
+
+struct messaging_context {
+ struct server_id id;
+ struct tevent_context *event_ctx;
+ struct messaging_callback *callbacks;
+
+ struct messaging_rec *posted_msgs;
+
+ struct messaging_registered_ev *event_contexts;
+
+ struct tevent_req **new_waiters;
+ size_t num_new_waiters;
+
+ struct tevent_req **waiters;
+ size_t num_waiters;
+
+ struct server_id_db *names_db;
+
+ TALLOC_CTX *per_process_talloc_ctx;
+};
+
+static struct messaging_rec *messaging_rec_dup(TALLOC_CTX *mem_ctx,
+ struct messaging_rec *rec);
+static bool messaging_dispatch_classic(struct messaging_context *msg_ctx,
+ struct messaging_rec *rec);
+static bool messaging_dispatch_waiters(struct messaging_context *msg_ctx,
+ struct tevent_context *ev,
+ struct messaging_rec *rec);
+static void messaging_dispatch_rec(struct messaging_context *msg_ctx,
+ struct tevent_context *ev,
+ struct messaging_rec *rec);
+
+/****************************************************************************
+ A useful function for testing the message system.
+****************************************************************************/
+
+static void ping_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct server_id_buf idbuf;
+
+ DEBUG(1, ("INFO: Received PING message from PID %s [%.*s]\n",
+ server_id_str_buf(src, &idbuf), (int)data->length,
+ data->data ? (char *)data->data : ""));
+
+ messaging_send(msg_ctx, src, MSG_PONG, data);
+}
+
+struct messaging_rec *messaging_rec_create(
+ TALLOC_CTX *mem_ctx, struct server_id src, struct server_id dst,
+ uint32_t msg_type, const struct iovec *iov, int iovlen,
+ const int *fds, size_t num_fds)
+{
+ ssize_t buflen;
+ uint8_t *buf;
+ struct messaging_rec *result;
+
+ if (num_fds > INT8_MAX) {
+ return NULL;
+ }
+
+ buflen = iov_buflen(iov, iovlen);
+ if (buflen == -1) {
+ return NULL;
+ }
+ buf = talloc_array(mem_ctx, uint8_t, buflen);
+ if (buf == NULL) {
+ return NULL;
+ }
+ iov_buf(iov, iovlen, buf, buflen);
+
+ {
+ struct messaging_rec rec;
+ int64_t fds64[MAX(1, num_fds)];
+ size_t i;
+
+ for (i=0; i<num_fds; i++) {
+ fds64[i] = fds[i];
+ }
+
+ rec = (struct messaging_rec) {
+ .msg_version = MESSAGE_VERSION, .msg_type = msg_type,
+ .src = src, .dest = dst,
+ .buf.data = buf, .buf.length = buflen,
+ .num_fds = num_fds, .fds = fds64,
+ };
+
+ result = messaging_rec_dup(mem_ctx, &rec);
+ }
+
+ TALLOC_FREE(buf);
+
+ return result;
+}
+
+static bool messaging_register_event_context(struct messaging_context *ctx,
+ struct tevent_context *ev)
+{
+ size_t i, num_event_contexts;
+ struct messaging_registered_ev *free_reg = NULL;
+ struct messaging_registered_ev *tmp;
+
+ num_event_contexts = talloc_array_length(ctx->event_contexts);
+
+ for (i=0; i<num_event_contexts; i++) {
+ struct messaging_registered_ev *reg = &ctx->event_contexts[i];
+
+ if (reg->refcount == 0) {
+ if (reg->ev != NULL) {
+ abort();
+ }
+ free_reg = reg;
+ /*
+ * We continue here and may find another
+ * free_req, but the important thing is
+ * that we continue to search for an
+ * existing registration in the loop.
+ */
+ continue;
+ }
+
+ if (reg->ev == ev) {
+ reg->refcount += 1;
+ return true;
+ }
+ }
+
+ if (free_reg == NULL) {
+ struct tevent_immediate *im = NULL;
+
+ im = tevent_create_immediate(ctx);
+ if (im == NULL) {
+ return false;
+ }
+
+ tmp = talloc_realloc(ctx, ctx->event_contexts,
+ struct messaging_registered_ev,
+ num_event_contexts+1);
+ if (tmp == NULL) {
+ return false;
+ }
+ ctx->event_contexts = tmp;
+
+ free_reg = &ctx->event_contexts[num_event_contexts];
+ free_reg->im = talloc_move(ctx->event_contexts, &im);
+ }
+
+ /*
+ * free_reg->im might be cached
+ */
+ free_reg->ev = ev;
+ free_reg->refcount = 1;
+
+ return true;
+}
+
+static bool messaging_deregister_event_context(struct messaging_context *ctx,
+ struct tevent_context *ev)
+{
+ size_t i, num_event_contexts;
+
+ num_event_contexts = talloc_array_length(ctx->event_contexts);
+
+ for (i=0; i<num_event_contexts; i++) {
+ struct messaging_registered_ev *reg = &ctx->event_contexts[i];
+
+ if (reg->refcount == 0) {
+ continue;
+ }
+
+ if (reg->ev == ev) {
+ reg->refcount -= 1;
+
+ if (reg->refcount == 0) {
+ /*
+ * The primary event context
+ * is never unregistered using
+ * messaging_deregister_event_context()
+ * it's only registered using
+ * messaging_register_event_context().
+ */
+ SMB_ASSERT(ev != ctx->event_ctx);
+ SMB_ASSERT(reg->ev != ctx->event_ctx);
+
+ /*
+ * Not strictly necessary, just
+ * paranoia
+ */
+ reg->ev = NULL;
+
+ /*
+ * Do not talloc_free(reg->im),
+ * recycle immediates events.
+ *
+ * We just invalidate it using
+ * the primary event context,
+ * which is never unregistered.
+ */
+ tevent_schedule_immediate(reg->im,
+ ctx->event_ctx,
+ NULL, NULL);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+static void messaging_post_main_event_context(struct tevent_context *ev,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct messaging_context *ctx = talloc_get_type_abort(
+ private_data, struct messaging_context);
+
+ while (ctx->posted_msgs != NULL) {
+ struct messaging_rec *rec = ctx->posted_msgs;
+ bool consumed;
+
+ DLIST_REMOVE(ctx->posted_msgs, rec);
+
+ consumed = messaging_dispatch_classic(ctx, rec);
+ if (!consumed) {
+ consumed = messaging_dispatch_waiters(
+ ctx, ctx->event_ctx, rec);
+ }
+
+ if (!consumed) {
+ uint8_t i;
+
+ for (i=0; i<rec->num_fds; i++) {
+ close(rec->fds[i]);
+ }
+ }
+
+ TALLOC_FREE(rec);
+ }
+}
+
+static void messaging_post_sub_event_context(struct tevent_context *ev,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct messaging_context *ctx = talloc_get_type_abort(
+ private_data, struct messaging_context);
+ struct messaging_rec *rec, *next;
+
+ for (rec = ctx->posted_msgs; rec != NULL; rec = next) {
+ bool consumed;
+
+ next = rec->next;
+
+ consumed = messaging_dispatch_waiters(ctx, ev, rec);
+ if (consumed) {
+ DLIST_REMOVE(ctx->posted_msgs, rec);
+ TALLOC_FREE(rec);
+ }
+ }
+}
+
+static bool messaging_alert_event_contexts(struct messaging_context *ctx)
+{
+ size_t i, num_event_contexts;
+
+ num_event_contexts = talloc_array_length(ctx->event_contexts);
+
+ for (i=0; i<num_event_contexts; i++) {
+ struct messaging_registered_ev *reg = &ctx->event_contexts[i];
+
+ if (reg->refcount == 0) {
+ continue;
+ }
+
+ /*
+ * We depend on schedule_immediate to work
+ * multiple times. Might be a bit inefficient,
+ * but this needs to be proven in tests. The
+ * alternatively would be to track whether the
+ * immediate has already been scheduled. For
+ * now, avoid that complexity here.
+ */
+
+ if (reg->ev == ctx->event_ctx) {
+ tevent_schedule_immediate(
+ reg->im, reg->ev,
+ messaging_post_main_event_context,
+ ctx);
+ } else {
+ tevent_schedule_immediate(
+ reg->im, reg->ev,
+ messaging_post_sub_event_context,
+ ctx);
+ }
+
+ }
+ return true;
+}
+
+static void messaging_recv_cb(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds,
+ void *private_data)
+{
+ struct messaging_context *msg_ctx = talloc_get_type_abort(
+ private_data, struct messaging_context);
+ struct server_id_buf idbuf;
+ struct messaging_rec rec;
+ int64_t fds64[MAX(1, MIN(num_fds, INT8_MAX))];
+ size_t i;
+
+ if (msg_len < MESSAGE_HDR_LENGTH) {
+ DBG_WARNING("message too short: %zu\n", msg_len);
+ return;
+ }
+
+ if (num_fds > INT8_MAX) {
+ DBG_WARNING("too many fds: %zu\n", num_fds);
+ return;
+ }
+
+ for (i=0; i < num_fds; i++) {
+ fds64[i] = fds[i];
+ }
+
+ rec = (struct messaging_rec) {
+ .msg_version = MESSAGE_VERSION,
+ .buf.data = discard_const_p(uint8_t, msg) + MESSAGE_HDR_LENGTH,
+ .buf.length = msg_len - MESSAGE_HDR_LENGTH,
+ .num_fds = num_fds,
+ .fds = fds64,
+ };
+
+ message_hdr_get(&rec.msg_type, &rec.src, &rec.dest, msg);
+
+ DBG_DEBUG("Received message 0x%x len %zu (num_fds:%zu) from %s\n",
+ (unsigned)rec.msg_type, rec.buf.length, num_fds,
+ server_id_str_buf(rec.src, &idbuf));
+
+ if (server_id_same_process(&rec.src, &msg_ctx->id)) {
+ DBG_DEBUG("Ignoring self-send\n");
+ return;
+ }
+
+ messaging_dispatch_rec(msg_ctx, ev, &rec);
+
+ for (i=0; i<num_fds; i++) {
+ fds[i] = fds64[i];
+ }
+}
+
+static int messaging_context_destructor(struct messaging_context *ctx)
+{
+ size_t i;
+
+ for (i=0; i<ctx->num_new_waiters; i++) {
+ if (ctx->new_waiters[i] != NULL) {
+ tevent_req_set_cleanup_fn(ctx->new_waiters[i], NULL);
+ ctx->new_waiters[i] = NULL;
+ }
+ }
+ for (i=0; i<ctx->num_waiters; i++) {
+ if (ctx->waiters[i] != NULL) {
+ tevent_req_set_cleanup_fn(ctx->waiters[i], NULL);
+ ctx->waiters[i] = NULL;
+ }
+ }
+
+ /*
+ * The immediates from messaging_alert_event_contexts
+ * reference "ctx". Don't let them outlive the
+ * messaging_context we're destroying here.
+ */
+ TALLOC_FREE(ctx->event_contexts);
+
+ return 0;
+}
+
+static const char *private_path(const char *name)
+{
+ return talloc_asprintf(talloc_tos(), "%s/%s", lp_private_dir(), name);
+}
+
+static NTSTATUS messaging_init_internal(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context **pmsg_ctx)
+{
+ TALLOC_CTX *frame;
+ struct messaging_context *ctx;
+ NTSTATUS status;
+ int ret;
+ const char *lck_path;
+ const char *priv_path;
+ void *ref;
+ bool ok;
+
+ /*
+ * sec_init() *must* be called before any other
+ * functions that use sec_XXX(). e.g. sec_initial_uid().
+ */
+
+ sec_init();
+
+ lck_path = lock_path(talloc_tos(), "msg.lock");
+ if (lck_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = directory_create_or_exist_strict(lck_path,
+ sec_initial_uid(),
+ 0755);
+ if (!ok) {
+ DBG_DEBUG("Could not create lock directory: %s\n",
+ strerror(errno));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ priv_path = private_path("msg.sock");
+ if (priv_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = directory_create_or_exist_strict(priv_path, sec_initial_uid(),
+ 0700);
+ if (!ok) {
+ DBG_DEBUG("Could not create msg directory: %s\n",
+ strerror(errno));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ frame = talloc_stackframe();
+ if (frame == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ctx = talloc_zero(frame, struct messaging_context);
+ if (ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ctx->id = (struct server_id) {
+ .pid = tevent_cached_getpid(), .vnn = NONCLUSTER_VNN
+ };
+
+ ctx->event_ctx = ev;
+
+ ctx->per_process_talloc_ctx = talloc_new(ctx);
+ if (ctx->per_process_talloc_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ok = messaging_register_event_context(ctx, ev);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ref = messaging_dgm_ref(
+ ctx->per_process_talloc_ctx,
+ ctx->event_ctx,
+ &ctx->id.unique_id,
+ priv_path,
+ lck_path,
+ messaging_recv_cb,
+ ctx,
+ &ret);
+ if (ref == NULL) {
+ DEBUG(2, ("messaging_dgm_ref failed: %s\n", strerror(ret)));
+ status = map_nt_error_from_unix(ret);
+ goto done;
+ }
+ talloc_set_destructor(ctx, messaging_context_destructor);
+
+#ifdef CLUSTER_SUPPORT
+ if (lp_clustering()) {
+ ref = messaging_ctdb_ref(
+ ctx->per_process_talloc_ctx,
+ ctx->event_ctx,
+ lp_ctdbd_socket(),
+ lp_ctdb_timeout(),
+ ctx->id.unique_id,
+ messaging_recv_cb,
+ ctx,
+ &ret);
+ if (ref == NULL) {
+ DBG_NOTICE("messaging_ctdb_ref failed: %s\n",
+ strerror(ret));
+ status = map_nt_error_from_unix(ret);
+ goto done;
+ }
+ }
+#endif
+
+ ctx->id.vnn = get_my_vnn();
+
+ ctx->names_db = server_id_db_init(ctx,
+ ctx->id,
+ lp_lock_directory(),
+ 0,
+ TDB_INCOMPATIBLE_HASH|TDB_CLEAR_IF_FIRST);
+ if (ctx->names_db == NULL) {
+ DBG_DEBUG("server_id_db_init failed\n");
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ messaging_register(ctx, NULL, MSG_PING, ping_message);
+
+ /* Register some debugging related messages */
+
+ register_msg_pool_usage(ctx->per_process_talloc_ctx, ctx);
+ register_dmalloc_msgs(ctx);
+ debug_register_msgs(ctx);
+
+ {
+ struct server_id_buf tmp;
+ DBG_DEBUG("my id: %s\n", server_id_str_buf(ctx->id, &tmp));
+ }
+
+ *pmsg_ctx = talloc_steal(mem_ctx, ctx);
+
+ status = NT_STATUS_OK;
+done:
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+struct messaging_context *messaging_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct messaging_context *ctx = NULL;
+ NTSTATUS status;
+
+ status = messaging_init_internal(mem_ctx,
+ ev,
+ &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ return ctx;
+}
+
+struct server_id messaging_server_id(const struct messaging_context *msg_ctx)
+{
+ return msg_ctx->id;
+}
+
+/*
+ * re-init after a fork
+ */
+NTSTATUS messaging_reinit(struct messaging_context *msg_ctx)
+{
+ int ret;
+ char *lck_path;
+ void *ref;
+
+ TALLOC_FREE(msg_ctx->per_process_talloc_ctx);
+
+ msg_ctx->per_process_talloc_ctx = talloc_new(msg_ctx);
+ if (msg_ctx->per_process_talloc_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ msg_ctx->id = (struct server_id) {
+ .pid = tevent_cached_getpid(), .vnn = msg_ctx->id.vnn
+ };
+
+ lck_path = lock_path(talloc_tos(), "msg.lock");
+ if (lck_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ref = messaging_dgm_ref(
+ msg_ctx->per_process_talloc_ctx,
+ msg_ctx->event_ctx,
+ &msg_ctx->id.unique_id,
+ private_path("msg.sock"),
+ lck_path,
+ messaging_recv_cb,
+ msg_ctx,
+ &ret);
+
+ if (ref == NULL) {
+ DEBUG(2, ("messaging_dgm_ref failed: %s\n", strerror(ret)));
+ return map_nt_error_from_unix(ret);
+ }
+
+ if (lp_clustering()) {
+ ref = messaging_ctdb_ref(
+ msg_ctx->per_process_talloc_ctx,
+ msg_ctx->event_ctx,
+ lp_ctdbd_socket(),
+ lp_ctdb_timeout(),
+ msg_ctx->id.unique_id,
+ messaging_recv_cb,
+ msg_ctx,
+ &ret);
+ if (ref == NULL) {
+ DBG_NOTICE("messaging_ctdb_ref failed: %s\n",
+ strerror(ret));
+ return map_nt_error_from_unix(ret);
+ }
+ }
+
+ server_id_db_reinit(msg_ctx->names_db, msg_ctx->id);
+ register_msg_pool_usage(msg_ctx->per_process_talloc_ctx, msg_ctx);
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ * Register a dispatch function for a particular message type. Allow multiple
+ * registrants
+*/
+NTSTATUS messaging_register(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ void (*fn)(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data))
+{
+ struct messaging_callback *cb;
+
+ DEBUG(5, ("Registering messaging pointer for type %u - "
+ "private_data=%p\n",
+ (unsigned)msg_type, private_data));
+
+ /*
+ * Only one callback per type
+ */
+
+ for (cb = msg_ctx->callbacks; cb != NULL; cb = cb->next) {
+ /* we allow a second registration of the same message
+ type if it has a different private pointer. This is
+ needed in, for example, the internal notify code,
+ which creates a new notify context for each tree
+ connect, and expects to receive messages to each of
+ them. */
+ if (cb->msg_type == msg_type && private_data == cb->private_data) {
+ DEBUG(5,("Overriding messaging pointer for type %u - private_data=%p\n",
+ (unsigned)msg_type, private_data));
+ cb->fn = fn;
+ cb->private_data = private_data;
+ return NT_STATUS_OK;
+ }
+ }
+
+ if (!(cb = talloc(msg_ctx, struct messaging_callback))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cb->msg_type = msg_type;
+ cb->fn = fn;
+ cb->private_data = private_data;
+
+ DLIST_ADD(msg_ctx->callbacks, cb);
+ return NT_STATUS_OK;
+}
+
+/*
+ De-register the function for a particular message type.
+*/
+void messaging_deregister(struct messaging_context *ctx, uint32_t msg_type,
+ void *private_data)
+{
+ struct messaging_callback *cb, *next;
+
+ for (cb = ctx->callbacks; cb; cb = next) {
+ next = cb->next;
+ if ((cb->msg_type == msg_type)
+ && (cb->private_data == private_data)) {
+ DEBUG(5,("Deregistering messaging pointer for type %u - private_data=%p\n",
+ (unsigned)msg_type, private_data));
+ DLIST_REMOVE(ctx->callbacks, cb);
+ TALLOC_FREE(cb);
+ }
+ }
+}
+
+/*
+ Send a message to a particular server
+*/
+NTSTATUS messaging_send(struct messaging_context *msg_ctx,
+ struct server_id server, uint32_t msg_type,
+ const DATA_BLOB *data)
+{
+ struct iovec iov = {0};
+
+ if (data != NULL) {
+ iov.iov_base = data->data;
+ iov.iov_len = data->length;
+ };
+
+ return messaging_send_iov(msg_ctx, server, msg_type, &iov, 1, NULL, 0);
+}
+
+NTSTATUS messaging_send_buf(struct messaging_context *msg_ctx,
+ struct server_id server, uint32_t msg_type,
+ const uint8_t *buf, size_t len)
+{
+ DATA_BLOB blob = data_blob_const(buf, len);
+ return messaging_send(msg_ctx, server, msg_type, &blob);
+}
+
+static int messaging_post_self(struct messaging_context *msg_ctx,
+ struct server_id src, struct server_id dst,
+ uint32_t msg_type,
+ const struct iovec *iov, int iovlen,
+ const int *fds, size_t num_fds)
+{
+ struct messaging_rec *rec;
+ bool ok;
+
+ rec = messaging_rec_create(
+ msg_ctx, src, dst, msg_type, iov, iovlen, fds, num_fds);
+ if (rec == NULL) {
+ return ENOMEM;
+ }
+
+ ok = messaging_alert_event_contexts(msg_ctx);
+ if (!ok) {
+ TALLOC_FREE(rec);
+ return ENOMEM;
+ }
+
+ DLIST_ADD_END(msg_ctx->posted_msgs, rec);
+
+ return 0;
+}
+
+int messaging_send_iov_from(struct messaging_context *msg_ctx,
+ struct server_id src, struct server_id dst,
+ uint32_t msg_type,
+ const struct iovec *iov, int iovlen,
+ const int *fds, size_t num_fds)
+{
+ int ret;
+ uint8_t hdr[MESSAGE_HDR_LENGTH];
+ struct iovec iov2[iovlen+1];
+
+ if (server_id_is_disconnected(&dst)) {
+ return EINVAL;
+ }
+
+ if (num_fds > INT8_MAX) {
+ return EINVAL;
+ }
+
+ if (server_id_equal(&dst, &msg_ctx->id)) {
+ ret = messaging_post_self(msg_ctx, src, dst, msg_type,
+ iov, iovlen, fds, num_fds);
+ return ret;
+ }
+
+ message_hdr_put(hdr, msg_type, src, dst);
+ iov2[0] = (struct iovec){ .iov_base = hdr, .iov_len = sizeof(hdr) };
+ memcpy(&iov2[1], iov, iovlen * sizeof(*iov));
+
+ if (dst.vnn != msg_ctx->id.vnn) {
+ if (num_fds > 0) {
+ return ENOSYS;
+ }
+
+ ret = messaging_ctdb_send(dst.vnn, dst.pid, iov2, iovlen+1);
+ return ret;
+ }
+
+ ret = messaging_dgm_send(dst.pid, iov2, iovlen+1, fds, num_fds);
+
+ if (ret == EACCES) {
+ become_root();
+ ret = messaging_dgm_send(dst.pid, iov2, iovlen+1,
+ fds, num_fds);
+ unbecome_root();
+ }
+
+ if (ret == ECONNREFUSED) {
+ /*
+ * Linux returns this when a socket exists in the file
+ * system without a listening process. This is not
+ * documented in susv4 or the linux manpages, but it's
+ * easily testable. For the higher levels this is the
+ * same as "destination does not exist"
+ */
+ ret = ENOENT;
+ }
+
+ return ret;
+}
+
+NTSTATUS messaging_send_iov(struct messaging_context *msg_ctx,
+ struct server_id dst, uint32_t msg_type,
+ const struct iovec *iov, int iovlen,
+ const int *fds, size_t num_fds)
+{
+ int ret;
+
+ ret = messaging_send_iov_from(msg_ctx, msg_ctx->id, dst, msg_type,
+ iov, iovlen, fds, num_fds);
+ if (ret != 0) {
+ return map_nt_error_from_unix(ret);
+ }
+ return NT_STATUS_OK;
+}
+
+struct send_all_state {
+ struct messaging_context *msg_ctx;
+ int msg_type;
+ const void *buf;
+ size_t len;
+};
+
+static int send_all_fn(pid_t pid, void *private_data)
+{
+ struct send_all_state *state = private_data;
+ NTSTATUS status;
+
+ if (pid == tevent_cached_getpid()) {
+ DBG_DEBUG("Skip ourselves in messaging_send_all\n");
+ return 0;
+ }
+
+ status = messaging_send_buf(state->msg_ctx, pid_to_procid(pid),
+ state->msg_type, state->buf, state->len);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("messaging_send_buf to %ju failed: %s\n",
+ (uintmax_t)pid, nt_errstr(status));
+ }
+
+ return 0;
+}
+
+void messaging_send_all(struct messaging_context *msg_ctx,
+ int msg_type, const void *buf, size_t len)
+{
+ struct send_all_state state = {
+ .msg_ctx = msg_ctx, .msg_type = msg_type,
+ .buf = buf, .len = len
+ };
+ int ret;
+
+#ifdef CLUSTER_SUPPORT
+ if (lp_clustering()) {
+ struct ctdbd_connection *conn = messaging_ctdb_connection();
+ uint8_t msghdr[MESSAGE_HDR_LENGTH];
+ struct iovec iov[] = {
+ { .iov_base = msghdr,
+ .iov_len = sizeof(msghdr) },
+ { .iov_base = discard_const_p(void, buf),
+ .iov_len = len }
+ };
+
+ message_hdr_put(msghdr, msg_type, messaging_server_id(msg_ctx),
+ (struct server_id) {0});
+
+ ret = ctdbd_messaging_send_iov(
+ conn, CTDB_BROADCAST_CONNECTED,
+ CTDB_SRVID_SAMBA_PROCESS,
+ iov, ARRAY_SIZE(iov));
+ if (ret != 0) {
+ DBG_WARNING("ctdbd_messaging_send_iov failed: %s\n",
+ strerror(ret));
+ }
+
+ return;
+ }
+#endif
+
+ ret = messaging_dgm_forall(send_all_fn, &state);
+ if (ret != 0) {
+ DBG_WARNING("messaging_dgm_forall failed: %s\n",
+ strerror(ret));
+ }
+}
+
+static struct messaging_rec *messaging_rec_dup(TALLOC_CTX *mem_ctx,
+ struct messaging_rec *rec)
+{
+ struct messaging_rec *result;
+ size_t fds_size = sizeof(int64_t) * rec->num_fds;
+ size_t payload_len;
+
+ payload_len = rec->buf.length + fds_size;
+ if (payload_len < rec->buf.length) {
+ /* overflow */
+ return NULL;
+ }
+
+ result = talloc_pooled_object(mem_ctx, struct messaging_rec, 2,
+ payload_len);
+ if (result == NULL) {
+ return NULL;
+ }
+ *result = *rec;
+
+ /* Doesn't fail, see talloc_pooled_object */
+
+ result->buf.data = talloc_memdup(result, rec->buf.data,
+ rec->buf.length);
+
+ result->fds = NULL;
+ if (result->num_fds > 0) {
+ size_t i;
+
+ result->fds = talloc_memdup(result, rec->fds, fds_size);
+
+ for (i=0; i<rec->num_fds; i++) {
+ /*
+ * fd's can only exist once
+ */
+ rec->fds[i] = -1;
+ }
+ }
+
+ return result;
+}
+
+struct messaging_filtered_read_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct messaging_dgm_fde *fde;
+ struct messaging_ctdb_fde *cluster_fde;
+
+ bool (*filter)(struct messaging_rec *rec, void *private_data);
+ void *private_data;
+
+ struct messaging_rec *rec;
+};
+
+static void messaging_filtered_read_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
+
+struct tevent_req *messaging_filtered_read_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ bool (*filter)(struct messaging_rec *rec, void *private_data),
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct messaging_filtered_read_state *state;
+ size_t new_waiters_len;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct messaging_filtered_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->filter = filter;
+ state->private_data = private_data;
+
+ /*
+ * We have to defer the callback here, as we might be called from
+ * within a different tevent_context than state->ev
+ */
+ tevent_req_defer_callback(req, state->ev);
+
+ state->fde = messaging_dgm_register_tevent_context(state, ev);
+ if (tevent_req_nomem(state->fde, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (lp_clustering()) {
+ state->cluster_fde =
+ messaging_ctdb_register_tevent_context(state, ev);
+ if (tevent_req_nomem(state->cluster_fde, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ /*
+ * We add ourselves to the "new_waiters" array, not the "waiters"
+ * array. If we are called from within messaging_read_done,
+ * messaging_dispatch_rec will be in an active for-loop on
+ * "waiters". We must be careful not to mess with this array, because
+ * it could mean that a single event is being delivered twice.
+ */
+
+ new_waiters_len = talloc_array_length(msg_ctx->new_waiters);
+
+ if (new_waiters_len == msg_ctx->num_new_waiters) {
+ struct tevent_req **tmp;
+
+ tmp = talloc_realloc(msg_ctx, msg_ctx->new_waiters,
+ struct tevent_req *, new_waiters_len+1);
+ if (tevent_req_nomem(tmp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ msg_ctx->new_waiters = tmp;
+ }
+
+ msg_ctx->new_waiters[msg_ctx->num_new_waiters] = req;
+ msg_ctx->num_new_waiters += 1;
+ tevent_req_set_cleanup_fn(req, messaging_filtered_read_cleanup);
+
+ ok = messaging_register_event_context(msg_ctx, ev);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void messaging_filtered_read_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct messaging_filtered_read_state *state = tevent_req_data(
+ req, struct messaging_filtered_read_state);
+ struct messaging_context *msg_ctx = state->msg_ctx;
+ size_t i;
+ bool ok;
+
+ tevent_req_set_cleanup_fn(req, NULL);
+
+ TALLOC_FREE(state->fde);
+ TALLOC_FREE(state->cluster_fde);
+
+ ok = messaging_deregister_event_context(msg_ctx, state->ev);
+ if (!ok) {
+ abort();
+ }
+
+ /*
+ * Just set the [new_]waiters entry to NULL, be careful not to mess
+ * with the other "waiters" array contents. We are often called from
+ * within "messaging_dispatch_rec", which loops over
+ * "waiters". Messing with the "waiters" array will mess up that
+ * for-loop.
+ */
+
+ for (i=0; i<msg_ctx->num_waiters; i++) {
+ if (msg_ctx->waiters[i] == req) {
+ msg_ctx->waiters[i] = NULL;
+ return;
+ }
+ }
+
+ for (i=0; i<msg_ctx->num_new_waiters; i++) {
+ if (msg_ctx->new_waiters[i] == req) {
+ msg_ctx->new_waiters[i] = NULL;
+ return;
+ }
+ }
+}
+
+static void messaging_filtered_read_done(struct tevent_req *req,
+ struct messaging_rec *rec)
+{
+ struct messaging_filtered_read_state *state = tevent_req_data(
+ req, struct messaging_filtered_read_state);
+
+ state->rec = messaging_rec_dup(state, rec);
+ if (tevent_req_nomem(state->rec, req)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+int messaging_filtered_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct messaging_rec **presult)
+{
+ struct messaging_filtered_read_state *state = tevent_req_data(
+ req, struct messaging_filtered_read_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ tevent_req_received(req);
+ return err;
+ }
+ if (presult != NULL) {
+ *presult = talloc_move(mem_ctx, &state->rec);
+ }
+ tevent_req_received(req);
+ return 0;
+}
+
+struct messaging_read_state {
+ uint32_t msg_type;
+ struct messaging_rec *rec;
+};
+
+static bool messaging_read_filter(struct messaging_rec *rec,
+ void *private_data);
+static void messaging_read_done(struct tevent_req *subreq);
+
+struct tevent_req *messaging_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ uint32_t msg_type)
+{
+ struct tevent_req *req, *subreq;
+ struct messaging_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct messaging_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->msg_type = msg_type;
+
+ subreq = messaging_filtered_read_send(state, ev, msg,
+ messaging_read_filter, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, messaging_read_done, req);
+ return req;
+}
+
+static bool messaging_read_filter(struct messaging_rec *rec,
+ void *private_data)
+{
+ struct messaging_read_state *state = talloc_get_type_abort(
+ private_data, struct messaging_read_state);
+
+ if (rec->num_fds != 0) {
+ return false;
+ }
+
+ return rec->msg_type == state->msg_type;
+}
+
+static void messaging_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct messaging_read_state *state = tevent_req_data(
+ req, struct messaging_read_state);
+ int ret;
+
+ ret = messaging_filtered_read_recv(subreq, state, &state->rec);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+int messaging_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct messaging_rec **presult)
+{
+ struct messaging_read_state *state = tevent_req_data(
+ req, struct messaging_read_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ if (presult != NULL) {
+ *presult = talloc_move(mem_ctx, &state->rec);
+ }
+ return 0;
+}
+
+static bool messaging_append_new_waiters(struct messaging_context *msg_ctx)
+{
+ if (msg_ctx->num_new_waiters == 0) {
+ return true;
+ }
+
+ if (talloc_array_length(msg_ctx->waiters) <
+ (msg_ctx->num_waiters + msg_ctx->num_new_waiters)) {
+ struct tevent_req **tmp;
+ tmp = talloc_realloc(
+ msg_ctx, msg_ctx->waiters, struct tevent_req *,
+ msg_ctx->num_waiters + msg_ctx->num_new_waiters);
+ if (tmp == NULL) {
+ DEBUG(1, ("%s: talloc failed\n", __func__));
+ return false;
+ }
+ msg_ctx->waiters = tmp;
+ }
+
+ memcpy(&msg_ctx->waiters[msg_ctx->num_waiters], msg_ctx->new_waiters,
+ sizeof(struct tevent_req *) * msg_ctx->num_new_waiters);
+
+ msg_ctx->num_waiters += msg_ctx->num_new_waiters;
+ msg_ctx->num_new_waiters = 0;
+
+ return true;
+}
+
+static bool messaging_dispatch_classic(struct messaging_context *msg_ctx,
+ struct messaging_rec *rec)
+{
+ struct messaging_callback *cb, *next;
+
+ for (cb = msg_ctx->callbacks; cb != NULL; cb = next) {
+ size_t j;
+
+ next = cb->next;
+ if (cb->msg_type != rec->msg_type) {
+ continue;
+ }
+
+ /*
+ * the old style callbacks don't support fd passing
+ */
+ for (j=0; j < rec->num_fds; j++) {
+ int fd = rec->fds[j];
+ close(fd);
+ }
+ rec->num_fds = 0;
+ rec->fds = NULL;
+
+ cb->fn(msg_ctx, cb->private_data, rec->msg_type,
+ rec->src, &rec->buf);
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool messaging_dispatch_waiters(struct messaging_context *msg_ctx,
+ struct tevent_context *ev,
+ struct messaging_rec *rec)
+{
+ size_t i;
+
+ if (!messaging_append_new_waiters(msg_ctx)) {
+ return false;
+ }
+
+ i = 0;
+ while (i < msg_ctx->num_waiters) {
+ struct tevent_req *req;
+ struct messaging_filtered_read_state *state;
+
+ req = msg_ctx->waiters[i];
+ if (req == NULL) {
+ /*
+ * This got cleaned up. In the meantime,
+ * move everything down one. We need
+ * to keep the order of waiters, as
+ * other code may depend on this.
+ */
+ ARRAY_DEL_ELEMENT(
+ msg_ctx->waiters, i, msg_ctx->num_waiters);
+ msg_ctx->num_waiters -= 1;
+ continue;
+ }
+
+ state = tevent_req_data(
+ req, struct messaging_filtered_read_state);
+ if ((ev == state->ev) &&
+ state->filter(rec, state->private_data)) {
+ messaging_filtered_read_done(req, rec);
+ return true;
+ }
+
+ i += 1;
+ }
+
+ return false;
+}
+
+/*
+ Dispatch one messaging_rec
+*/
+static void messaging_dispatch_rec(struct messaging_context *msg_ctx,
+ struct tevent_context *ev,
+ struct messaging_rec *rec)
+{
+ bool consumed;
+ size_t i;
+
+ if (ev == msg_ctx->event_ctx) {
+ consumed = messaging_dispatch_classic(msg_ctx, rec);
+ if (consumed) {
+ return;
+ }
+ }
+
+ consumed = messaging_dispatch_waiters(msg_ctx, ev, rec);
+ if (consumed) {
+ return;
+ }
+
+ if (ev != msg_ctx->event_ctx) {
+ struct iovec iov;
+ int fds[MAX(1, rec->num_fds)];
+ int ret;
+
+ /*
+ * We've been listening on a nested event
+ * context. Messages need to be handled in the main
+ * event context, so post to ourselves
+ */
+
+ iov.iov_base = rec->buf.data;
+ iov.iov_len = rec->buf.length;
+
+ for (i=0; i<rec->num_fds; i++) {
+ fds[i] = rec->fds[i];
+ }
+
+ ret = messaging_post_self(
+ msg_ctx, rec->src, rec->dest, rec->msg_type,
+ &iov, 1, fds, rec->num_fds);
+ if (ret == 0) {
+ return;
+ }
+ }
+}
+
+static int mess_parent_dgm_cleanup(void *private_data);
+static void mess_parent_dgm_cleanup_done(struct tevent_req *req);
+
+bool messaging_parent_dgm_cleanup_init(struct messaging_context *msg)
+{
+ struct tevent_req *req;
+
+ req = background_job_send(
+ msg, msg->event_ctx, msg, NULL, 0,
+ lp_parm_int(-1, "messaging", "messaging dgm cleanup interval",
+ 60*15),
+ mess_parent_dgm_cleanup, msg);
+ if (req == NULL) {
+ DBG_WARNING("background_job_send failed\n");
+ return false;
+ }
+ tevent_req_set_callback(req, mess_parent_dgm_cleanup_done, msg);
+ return true;
+}
+
+static int mess_parent_dgm_cleanup(void *private_data)
+{
+ int ret;
+
+ ret = messaging_dgm_wipe();
+ DEBUG(10, ("messaging_dgm_wipe returned %s\n",
+ ret ? strerror(ret) : "ok"));
+ return lp_parm_int(-1, "messaging", "messaging dgm cleanup interval",
+ 60*15);
+}
+
+static void mess_parent_dgm_cleanup_done(struct tevent_req *req)
+{
+ struct messaging_context *msg = tevent_req_callback_data(
+ req, struct messaging_context);
+ NTSTATUS status;
+
+ status = background_job_recv(req);
+ TALLOC_FREE(req);
+ DEBUG(1, ("messaging dgm cleanup job ended with %s\n",
+ nt_errstr(status)));
+
+ req = background_job_send(
+ msg, msg->event_ctx, msg, NULL, 0,
+ lp_parm_int(-1, "messaging", "messaging dgm cleanup interval",
+ 60*15),
+ mess_parent_dgm_cleanup, msg);
+ if (req == NULL) {
+ DEBUG(1, ("background_job_send failed\n"));
+ return;
+ }
+ tevent_req_set_callback(req, mess_parent_dgm_cleanup_done, msg);
+}
+
+int messaging_cleanup(struct messaging_context *msg_ctx, pid_t pid)
+{
+ int ret;
+
+ if (pid == 0) {
+ ret = messaging_dgm_wipe();
+ } else {
+ ret = messaging_dgm_cleanup(pid);
+ }
+
+ return ret;
+}
+
+struct tevent_context *messaging_tevent_context(
+ struct messaging_context *msg_ctx)
+{
+ return msg_ctx->event_ctx;
+}
+
+struct server_id_db *messaging_names_db(struct messaging_context *msg_ctx)
+{
+ return msg_ctx->names_db;
+}
+
+/** @} **/
diff --git a/source3/lib/messages_ctdb.c b/source3/lib/messages_ctdb.c
new file mode 100644
index 0000000..d55b53b
--- /dev/null
+++ b/source3/lib/messages_ctdb.c
@@ -0,0 +1,306 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba internal messaging functions
+ * Copyright (C) 2017 by Volker Lendecke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/messages_ctdb.h"
+#include "lib/util/server_id.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "lib/util/iov_buf.h"
+#include "lib/messages_util.h"
+#include "ctdbd_conn.h"
+#include "lib/cluster_support.h"
+#include "ctdb_srvids.h"
+
+struct messaging_ctdb_context;
+
+/*
+ * We can only have one tevent_fd per ctdb_context and per
+ * tevent_context. Maintain a list of registered tevent_contexts per
+ * ctdb_context.
+ */
+struct messaging_ctdb_fde_ev {
+ struct messaging_ctdb_fde_ev *prev, *next;
+
+ /*
+ * Backreference to enable DLIST_REMOVE from our
+ * destructor. Also, set to NULL when the ctdb_context dies
+ * before the messaging_ctdb_fde_ev.
+ */
+ struct messaging_ctdb_context *ctx;
+
+ struct tevent_context *ev;
+ struct tevent_fd *fde;
+};
+
+struct messaging_ctdb_context {
+ struct ctdbd_connection *conn;
+
+ void (*recv_cb)(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds,
+ void *private_data);
+ void *recv_cb_private_data;
+
+ struct messaging_ctdb_fde_ev *fde_evs;
+};
+
+static int messaging_ctdb_recv(
+ struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn, uint64_t dst_srvid,
+ const uint8_t *msg, size_t msg_len, void *private_data)
+{
+ struct messaging_ctdb_context *state = talloc_get_type_abort(
+ private_data, struct messaging_ctdb_context);
+
+ state->recv_cb(ev, msg, msg_len, NULL, 0, state->recv_cb_private_data);
+
+ return 0;
+}
+
+struct messaging_ctdb_context *global_ctdb_context;
+
+static int global_ctdb_ctx_destructor(struct messaging_ctdb_context *ctx)
+{
+ if (ctx != NULL) {
+ struct messaging_ctdb_fde_ev *fde_ev = NULL;
+ for (fde_ev = ctx->fde_evs;
+ fde_ev != NULL;
+ fde_ev = fde_ev->next) {
+ if (fde_ev->ctx == ctx) {
+ fde_ev->ctx = NULL;
+ }
+ }
+ }
+ return 0;
+}
+
+int messaging_ctdb_init(const char *sockname, int timeout, uint64_t unique_id,
+ void (*recv_cb)(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds,
+ void *private_data),
+ void *private_data)
+{
+ struct messaging_ctdb_context *ctx;
+ int ret;
+
+ if (global_ctdb_context != NULL) {
+ return EBUSY;
+ }
+
+ ctx = talloc_zero(NULL, struct messaging_ctdb_context);
+ if (ctx == NULL) {
+ return ENOMEM;
+ }
+
+ talloc_set_destructor(ctx,
+ global_ctdb_ctx_destructor);
+
+ ctx->recv_cb = recv_cb;
+ ctx->recv_cb_private_data = private_data;
+
+ ret = ctdbd_init_connection(ctx, sockname, timeout, &ctx->conn);
+ if (ret != 0) {
+ DBG_DEBUG("ctdbd_init_connection returned %s\n",
+ strerror(ret));
+ goto fail;
+ }
+
+ ret = register_with_ctdbd(ctx->conn, tevent_cached_getpid(), messaging_ctdb_recv,
+ ctx);
+ if (ret != 0) {
+ DBG_DEBUG("register_with_ctdbd returned %s (%d)\n",
+ strerror(ret), ret);
+ goto fail;
+ }
+
+ ret = register_with_ctdbd(ctx->conn, CTDB_SRVID_SAMBA_PROCESS,
+ messaging_ctdb_recv, ctx);
+ if (ret != 0) {
+ DBG_DEBUG("register_with_ctdbd returned %s (%d)\n",
+ strerror(ret), ret);
+ goto fail;
+ }
+
+ ret = register_with_ctdbd(ctx->conn, unique_id, NULL, NULL);
+ if (ret != 0) {
+ DBG_DEBUG("register_with_ctdbd returned %s (%d)\n",
+ strerror(ret), ret);
+ goto fail;
+ }
+
+ set_my_vnn(ctdbd_vnn(ctx->conn));
+
+ global_ctdb_context = ctx;
+ return 0;
+fail:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+void messaging_ctdb_destroy(void)
+{
+ TALLOC_FREE(global_ctdb_context);
+}
+
+int messaging_ctdb_send(uint32_t dst_vnn, uint64_t dst_srvid,
+ const struct iovec *iov, int iovlen)
+{
+ struct messaging_ctdb_context *ctx = global_ctdb_context;
+ int ret;
+
+ if (ctx == NULL) {
+ return ENOTCONN;
+ }
+
+ ret = ctdbd_messaging_send_iov(ctx->conn, dst_vnn, dst_srvid,
+ iov, iovlen);
+ return ret;
+}
+
+static void messaging_ctdb_read_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct messaging_ctdb_context *ctx = talloc_get_type_abort(
+ private_data, struct messaging_ctdb_context);
+
+ if ((flags & TEVENT_FD_READ) == 0) {
+ return;
+ }
+ ctdbd_socket_readable(ev, ctx->conn);
+}
+
+struct messaging_ctdb_fde {
+ struct tevent_fd *fde;
+};
+
+static int messaging_ctdb_fde_ev_destructor(
+ struct messaging_ctdb_fde_ev *fde_ev)
+{
+ if (fde_ev->ctx != NULL) {
+ DLIST_REMOVE(fde_ev->ctx->fde_evs, fde_ev);
+ fde_ev->ctx = NULL;
+ }
+ return 0;
+}
+
+/*
+ * Reference counter for a struct tevent_fd messaging read event
+ * (with callback function) on a struct tevent_context registered
+ * on a messaging context.
+ *
+ * If we've already registered this struct tevent_context before
+ * (so already have a read event), just increase the reference count.
+ *
+ * Otherwise create a new struct tevent_fd messaging read event on the
+ * previously unseen struct tevent_context - this is what drives
+ * the message receive processing.
+ *
+ */
+
+struct messaging_ctdb_fde *messaging_ctdb_register_tevent_context(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev)
+{
+ struct messaging_ctdb_context *ctx = global_ctdb_context;
+ struct messaging_ctdb_fde_ev *fde_ev;
+ struct messaging_ctdb_fde *fde;
+
+ if (ctx == NULL) {
+ return NULL;
+ }
+
+ fde = talloc(mem_ctx, struct messaging_ctdb_fde);
+ if (fde == NULL) {
+ return NULL;
+ }
+
+ for (fde_ev = ctx->fde_evs; fde_ev != NULL; fde_ev = fde_ev->next) {
+ if (tevent_fd_get_flags(fde_ev->fde) == 0) {
+ /*
+ * If the event context got deleted,
+ * tevent_fd_get_flags() will return 0
+ * for the stale fde.
+ *
+ * In that case we should not
+ * use fde_ev->ev anymore.
+ */
+ continue;
+ }
+ if (fde_ev->ev == ev) {
+ break;
+ }
+ }
+
+ if (fde_ev == NULL) {
+ int sock = ctdbd_conn_get_fd(ctx->conn);
+
+ fde_ev = talloc(fde, struct messaging_ctdb_fde_ev);
+ if (fde_ev == NULL) {
+ return NULL;
+ }
+ fde_ev->fde = tevent_add_fd(
+ ev, fde_ev, sock, TEVENT_FD_READ,
+ messaging_ctdb_read_handler, ctx);
+ if (fde_ev->fde == NULL) {
+ TALLOC_FREE(fde);
+ return NULL;
+ }
+ fde_ev->ev = ev;
+ fde_ev->ctx = ctx;
+ DLIST_ADD(ctx->fde_evs, fde_ev);
+ talloc_set_destructor(
+ fde_ev, messaging_ctdb_fde_ev_destructor);
+ } else {
+ /*
+ * Same trick as with tdb_wrap: The caller will never
+ * see the talloc_referenced object, the
+ * messaging_ctdb_fde_ev, so problems with
+ * talloc_unlink will not happen.
+ */
+ if (talloc_reference(fde, fde_ev) == NULL) {
+ TALLOC_FREE(fde);
+ return NULL;
+ }
+ }
+
+ fde->fde = fde_ev->fde;
+ return fde;
+}
+
+bool messaging_ctdb_fde_active(struct messaging_ctdb_fde *fde)
+{
+ uint16_t flags;
+
+ if (fde == NULL) {
+ return false;
+ }
+ flags = tevent_fd_get_flags(fde->fde);
+ return (flags != 0);
+}
+
+struct ctdbd_connection *messaging_ctdb_connection(void)
+{
+ if (global_ctdb_context == NULL) {
+ smb_panic("messaging not initialized\n");
+ }
+ return global_ctdb_context->conn;
+}
diff --git a/source3/lib/messages_ctdb.h b/source3/lib/messages_ctdb.h
new file mode 100644
index 0000000..9d56343
--- /dev/null
+++ b/source3/lib/messages_ctdb.h
@@ -0,0 +1,44 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba internal messaging functions
+ * Copyright (C) 2017 by Volker Lendecke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MESSAGES_CTDB_H__
+#define __MESSAGES_CTDB_H__
+
+#include "replace.h"
+#include "system/filesys.h"
+#include <tevent.h>
+
+int messaging_ctdb_init(const char *sockname, int timeout, uint64_t unique_id,
+ void (*recv_cb)(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds,
+ void *private_data),
+ void *private_data);
+void messaging_ctdb_destroy(void);
+int messaging_ctdb_send(uint32_t dst_vnn, uint64_t dst_srvid,
+ const struct iovec *iov, int iovlen);
+
+struct messaging_ctdb_fde;
+struct messaging_ctdb_fde *messaging_ctdb_register_tevent_context(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev);
+bool messaging_ctdb_fde_active(struct messaging_ctdb_fde *fde);
+
+struct ctdbd_connection *messaging_ctdb_connection(void);
+
+#endif
diff --git a/source3/lib/messages_ctdb_ref.c b/source3/lib/messages_ctdb_ref.c
new file mode 100644
index 0000000..ed6285f
--- /dev/null
+++ b/source3/lib/messages_ctdb_ref.c
@@ -0,0 +1,145 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba internal messaging functions
+ * Copyright (C) 2017 by Volker Lendecke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <talloc.h>
+#include "messages_ctdb.h"
+#include "messages_ctdb_ref.h"
+#include "lib/util/debug.h"
+#include "lib/util/dlinklist.h"
+
+struct msg_ctdb_ref {
+ struct msg_ctdb_ref *prev, *next;
+ struct messaging_ctdb_fde *fde;
+ void (*recv_cb)(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds, void *private_data);
+ void *recv_cb_private_data;
+};
+
+static pid_t ctdb_pid = 0;
+static struct msg_ctdb_ref *refs = NULL;
+
+static int msg_ctdb_ref_destructor(struct msg_ctdb_ref *r);
+static void msg_ctdb_ref_recv(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds, void *private_data);
+
+void *messaging_ctdb_ref(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *sockname, int timeout, uint64_t unique_id,
+ void (*recv_cb)(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds,
+ void *private_data),
+ void *recv_cb_private_data,
+ int *err)
+{
+ struct msg_ctdb_ref *result, *tmp_refs;
+
+ result = talloc(mem_ctx, struct msg_ctdb_ref);
+ if (result == NULL) {
+ *err = ENOMEM;
+ return NULL;
+ }
+ result->fde = NULL;
+
+ tmp_refs = refs;
+
+ if ((refs != NULL) && (ctdb_pid != tevent_cached_getpid())) {
+ /*
+ * Have to reinit after fork
+ */
+ messaging_ctdb_destroy();
+ refs = NULL;
+ }
+
+ if (refs == NULL) {
+ int ret;
+
+ ret = messaging_ctdb_init(sockname, timeout, unique_id,
+ msg_ctdb_ref_recv, NULL);
+ DBG_DEBUG("messaging_ctdb_init returned %s\n", strerror(ret));
+ if (ret != 0) {
+ DEBUG(10, ("messaging_ctdb_init failed: %s\n",
+ strerror(ret)));
+ TALLOC_FREE(result);
+ *err = ret;
+ return NULL;
+ }
+ ctdb_pid = tevent_cached_getpid();
+ }
+
+ result->fde = messaging_ctdb_register_tevent_context(result, ev);
+ if (result->fde == NULL) {
+ TALLOC_FREE(result);
+ *err = ENOMEM;
+ return NULL;
+ }
+
+ refs = tmp_refs;
+
+ result->recv_cb = recv_cb;
+ result->recv_cb_private_data = recv_cb_private_data;
+ DLIST_ADD(refs, result);
+ talloc_set_destructor(result, msg_ctdb_ref_destructor);
+
+ return result;
+}
+
+static void msg_ctdb_ref_recv(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds, void *private_data)
+{
+ struct msg_ctdb_ref *r, *next;
+
+ for (r = refs; r != NULL; r = next) {
+ bool active;
+
+ next = r->next;
+
+ active = messaging_ctdb_fde_active(r->fde);
+ if (!active) {
+ /*
+ * r's tevent_context has died.
+ */
+ continue;
+ }
+
+ r->recv_cb(ev, msg, msg_len, fds, num_fds,
+ r->recv_cb_private_data);
+ break;
+ }
+}
+
+static int msg_ctdb_ref_destructor(struct msg_ctdb_ref *r)
+{
+ if (refs == NULL) {
+ abort();
+ }
+ DLIST_REMOVE(refs, r);
+
+ TALLOC_FREE(r->fde);
+
+ DBG_DEBUG("refs=%p\n", refs);
+
+ if (refs == NULL) {
+ messaging_ctdb_destroy();
+ }
+ return 0;
+}
diff --git a/source3/lib/messages_ctdb_ref.h b/source3/lib/messages_ctdb_ref.h
new file mode 100644
index 0000000..44541d8
--- /dev/null
+++ b/source3/lib/messages_ctdb_ref.h
@@ -0,0 +1,35 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba internal messaging functions
+ * Copyright (C) 2017 by Volker Lendecke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __MESSAGES_CTDB_REF_H__
+#define __MESSAGES_CTDB_REF_H__
+
+#include "replace.h"
+#include <tevent.h>
+
+void *messaging_ctdb_ref(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *sockname, int timeout, uint64_t unique_id,
+ void (*recv_cb)(struct tevent_context *ev,
+ const uint8_t *msg, size_t msg_len,
+ int *fds, size_t num_fds,
+ void *private_data),
+ void *private_data,
+ int *err);
+
+#endif
diff --git a/source3/lib/messages_util.c b/source3/lib/messages_util.c
new file mode 100644
index 0000000..d712dfe
--- /dev/null
+++ b/source3/lib/messages_util.c
@@ -0,0 +1,42 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba internal messaging functions
+ * Copyright (C) 2013 by Volker Lendecke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "lib/util/server_id.h"
+#include "lib/util/samba_util.h"
+#include "librpc/gen_ndr/server_id.h"
+#include "lib/util/byteorder.h"
+#include "messages_util.h"
+
+void message_hdr_put(uint8_t buf[MESSAGE_HDR_LENGTH], uint32_t msg_type,
+ struct server_id src, struct server_id dst)
+{
+ server_id_put(buf, dst);
+ server_id_put(buf + SERVER_ID_BUF_LENGTH, src);
+ SIVAL(buf, 2 * SERVER_ID_BUF_LENGTH, msg_type);
+}
+
+void message_hdr_get(uint32_t *msg_type, struct server_id *src,
+ struct server_id *dst,
+ const uint8_t buf[MESSAGE_HDR_LENGTH])
+{
+ server_id_get(dst, buf);
+ server_id_get(src, buf + SERVER_ID_BUF_LENGTH);
+ *msg_type = IVAL(buf, 2 * SERVER_ID_BUF_LENGTH);
+}
diff --git a/source3/lib/messages_util.h b/source3/lib/messages_util.h
new file mode 100644
index 0000000..5b22f5e
--- /dev/null
+++ b/source3/lib/messages_util.h
@@ -0,0 +1,33 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba internal messaging functions
+ * Copyright (C) 2013 by Volker Lendecke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _MESSAGES_UTIL_H_
+#define _MESSAGES_UTIL_H_
+
+struct message_hdr;
+
+#define MESSAGE_HDR_LENGTH 52
+
+void message_hdr_put(uint8_t buf[MESSAGE_HDR_LENGTH], uint32_t msg_type,
+ struct server_id src, struct server_id dst);
+void message_hdr_get(uint32_t *msg_type, struct server_id *src,
+ struct server_id *dst,
+ const uint8_t buf[MESSAGE_HDR_LENGTH]);
+
+#endif
diff --git a/source3/lib/ms_fnmatch.c b/source3/lib/ms_fnmatch.c
new file mode 100644
index 0000000..4fbed58
--- /dev/null
+++ b/source3/lib/ms_fnmatch.c
@@ -0,0 +1,232 @@
+/*
+ Unix SMB/CIFS implementation.
+ filename matching routine
+ Copyright (C) Andrew Tridgell 1992-2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ This module was originally based on fnmatch.c copyright by the Free
+ Software Foundation. It bears little (if any) resemblance to that
+ code now
+*/
+
+
+#include "includes.h"
+
+static int null_match(const smb_ucs2_t *p)
+{
+ for (;*p;p++) {
+ if (*p != UCS2_CHAR('*') &&
+ *p != UCS2_CHAR('<') &&
+ *p != UCS2_CHAR('"') &&
+ *p != UCS2_CHAR('>')) return -1;
+ }
+ return 0;
+}
+
+/*
+ the max_n structure is purely for efficiency, it doesn't contribute
+ to the matching algorithm except by ensuring that the algorithm does
+ not grow exponentially
+*/
+struct max_n {
+ const smb_ucs2_t *predot;
+ const smb_ucs2_t *postdot;
+};
+
+
+/*
+ p and n are the pattern and string being matched. The max_n array is
+ an optimisation only. The ldot pointer is NULL if the string does
+ not contain a '.', otherwise it points at the last dot in 'n'.
+*/
+static int ms_fnmatch_core(const smb_ucs2_t *p, const smb_ucs2_t *n,
+ struct max_n *max_n, const smb_ucs2_t *ldot,
+ bool is_case_sensitive)
+{
+ smb_ucs2_t c;
+ int i;
+
+ while ((c = *p++)) {
+ switch (c) {
+ /* a '*' matches zero or more characters of any type */
+ case UCS2_CHAR('*'):
+ if (max_n->predot && max_n->predot <= n) {
+ return null_match(p);
+ }
+ for (i=0; n[i]; i++) {
+ if (ms_fnmatch_core(p, n+i, max_n+1, ldot, is_case_sensitive) == 0) {
+ return 0;
+ }
+ }
+ if (!max_n->predot || max_n->predot > n) max_n->predot = n;
+ return null_match(p);
+
+ /* a '<' matches zero or more characters of
+ any type, but stops matching at the last
+ '.' in the string. */
+ case UCS2_CHAR('<'):
+ if (max_n->predot && max_n->predot <= n) {
+ return null_match(p);
+ }
+ if (max_n->postdot && max_n->postdot <= n && n <= ldot) {
+ return -1;
+ }
+ for (i=0; n[i]; i++) {
+ if (ms_fnmatch_core(p, n+i, max_n+1, ldot, is_case_sensitive) == 0) return 0;
+ if (n+i == ldot) {
+ if (ms_fnmatch_core(p, n+i+1, max_n+1, ldot, is_case_sensitive) == 0) return 0;
+ if (!max_n->postdot || max_n->postdot > n) max_n->postdot = n;
+ return -1;
+ }
+ }
+ if (!max_n->predot || max_n->predot > n) max_n->predot = n;
+ return null_match(p);
+
+ /* a '?' matches any single character */
+ case UCS2_CHAR('?'):
+ if (! *n) {
+ return -1;
+ }
+ n++;
+ break;
+
+ /* a '?' matches any single character */
+ case UCS2_CHAR('>'):
+ if (n[0] == UCS2_CHAR('.')) {
+ if (! n[1] && null_match(p) == 0) {
+ return 0;
+ }
+ break;
+ }
+ if (! *n) return null_match(p);
+ n++;
+ break;
+
+ case UCS2_CHAR('"'):
+ if (*n == 0 && null_match(p) == 0) {
+ return 0;
+ }
+ if (*n != UCS2_CHAR('.')) return -1;
+ n++;
+ break;
+
+ default:
+ if (c != *n) {
+ if (is_case_sensitive) {
+ return -1;
+ }
+ if (toupper_w(c) != toupper_w(*n)) {
+ return -1;
+ }
+ }
+ n++;
+ break;
+ }
+ }
+
+ if (! *n) {
+ return 0;
+ }
+
+ return -1;
+}
+
+int ms_fnmatch(const char *pattern, const char *string, bool translate_pattern,
+ bool is_case_sensitive)
+{
+ smb_ucs2_t *p = NULL;
+ smb_ucs2_t *s = NULL;
+ int ret;
+ size_t count, i;
+ struct max_n *max_n = NULL;
+ struct max_n *max_n_free = NULL;
+ struct max_n one_max_n;
+ size_t converted_size;
+
+ if (ISDOTDOT(string)) {
+ string = ".";
+ }
+
+ if (strpbrk(pattern, "<>*?\"") == NULL) {
+ /* this is not just an optimisation - it is essential
+ for LANMAN1 correctness */
+ if (is_case_sensitive) {
+ return strcmp(pattern, string);
+ } else {
+ return strcasecmp_m(pattern, string);
+ }
+ }
+
+ if (!push_ucs2_talloc(talloc_tos(), &p, pattern, &converted_size)) {
+ return -1;
+ }
+
+ if (!push_ucs2_talloc(talloc_tos(), &s, string, &converted_size)) {
+ TALLOC_FREE(p);
+ return -1;
+ }
+
+ if (translate_pattern) {
+ /*
+ for older negotiated protocols it is possible to
+ translate the pattern to produce a "new style"
+ pattern that exactly matches w2k behaviour
+ */
+ for (i=0;p[i];i++) {
+ if (p[i] == UCS2_CHAR('?')) {
+ p[i] = UCS2_CHAR('>');
+ } else if (p[i] == UCS2_CHAR('.') &&
+ (p[i+1] == UCS2_CHAR('?') ||
+ p[i+1] == UCS2_CHAR('*') ||
+ p[i+1] == 0)) {
+ p[i] = UCS2_CHAR('"');
+ } else if (p[i] == UCS2_CHAR('*') && p[i+1] == UCS2_CHAR('.')) {
+ p[i] = UCS2_CHAR('<');
+ }
+ }
+ }
+
+ for (count=i=0;p[i];i++) {
+ if (p[i] == UCS2_CHAR('*') || p[i] == UCS2_CHAR('<')) count++;
+ }
+
+ if (count != 0) {
+ if (count == 1) {
+ /*
+ * We're doing this a LOT, so save the effort to allocate
+ */
+ ZERO_STRUCT(one_max_n);
+ max_n = &one_max_n;
+ }
+ else {
+ max_n = SMB_CALLOC_ARRAY(struct max_n, count);
+ if (!max_n) {
+ TALLOC_FREE(p);
+ TALLOC_FREE(s);
+ return -1;
+ }
+ max_n_free = max_n;
+ }
+ }
+
+ ret = ms_fnmatch_core(p, s, max_n, strrchr_w(s, UCS2_CHAR('.')), is_case_sensitive);
+
+ SAFE_FREE(max_n_free);
+ TALLOC_FREE(p);
+ TALLOC_FREE(s);
+ return ret;
+}
diff --git a/source3/lib/namearray.c b/source3/lib/namearray.c
new file mode 100644
index 0000000..e5c3bd9
--- /dev/null
+++ b/source3/lib/namearray.c
@@ -0,0 +1,39 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2001-2007
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+/****************************************************************************
+ Routine to free a namearray.
+****************************************************************************/
+
+void free_namearray(name_compare_entry *name_array)
+{
+ int i;
+
+ if(name_array == NULL)
+ return;
+
+ for(i=0; name_array[i].name!=NULL; i++)
+ SAFE_FREE(name_array[i].name);
+ SAFE_FREE(name_array);
+}
diff --git a/source3/lib/namemap_cache.c b/source3/lib/namemap_cache.c
new file mode 100644
index 0000000..2e8d298
--- /dev/null
+++ b/source3/lib/namemap_cache.c
@@ -0,0 +1,333 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utils for caching sid2name and name2sid
+ * Copyright (C) Volker Lendecke 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "namemap_cache.h"
+#include "source3/lib/gencache.h"
+#include "lib/util/debug.h"
+#include "lib/util/strv.h"
+#include "lib/util/util.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/charset/charset.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/util/smb_strtox.h"
+
+bool namemap_cache_set_sid2name(const struct dom_sid *sid,
+ const char *domain, const char *name,
+ enum lsa_SidType type, time_t timeout)
+{
+ char typebuf[16];
+ struct dom_sid_buf sidbuf;
+ char keybuf[sizeof(sidbuf.buf)+10];
+ char *val = NULL;
+ DATA_BLOB data;
+ int ret;
+ bool ok = false;
+
+ if ((sid == NULL) || is_null_sid(sid)) {
+ return true;
+ }
+ if (domain == NULL) {
+ domain = "";
+ }
+ if (name == NULL) {
+ name = "";
+ }
+ if (type == SID_NAME_UNKNOWN) {
+ domain = "";
+ name = "";
+ }
+
+ snprintf(typebuf, sizeof(typebuf), "%d", (int)type);
+
+ ret = strv_add(talloc_tos(), &val, domain);
+ if (ret != 0) {
+ DBG_DEBUG("strv_add failed: %s\n", strerror(ret));
+ goto fail;
+ }
+ ret = strv_add(NULL, &val, name);
+ if (ret != 0) {
+ DBG_DEBUG("strv_add failed: %s\n", strerror(ret));
+ goto fail;
+ }
+ ret = strv_add(NULL, &val, typebuf);
+ if (ret != 0) {
+ DBG_DEBUG("strv_add failed: %s\n", strerror(ret));
+ goto fail;
+ }
+
+ dom_sid_str_buf(sid, &sidbuf);
+ snprintf(keybuf, sizeof(keybuf), "SID2NAME/%s", sidbuf.buf);
+
+ data = data_blob_const(val, talloc_get_size(val));
+
+ ok = gencache_set_data_blob(keybuf, data, timeout);
+ if (!ok) {
+ DBG_DEBUG("gencache_set_data_blob failed\n");
+ }
+fail:
+ TALLOC_FREE(val);
+ return ok;
+}
+
+struct namemap_cache_find_sid_state {
+ void (*fn)(const char *domain,
+ const char *name,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data);
+ void *private_data;
+ bool ok;
+};
+
+static void namemap_cache_find_sid_parser(
+ const struct gencache_timeout *timeout,
+ DATA_BLOB blob,
+ void *private_data)
+{
+ struct namemap_cache_find_sid_state *state = private_data;
+ const char *strv = (const char *)blob.data;
+ size_t strv_len = blob.length;
+ const char *domain;
+ const char *name;
+ const char *typebuf;
+ int error = 0;
+ unsigned long type;
+
+ state->ok = false;
+
+ domain = strv_len_next(strv, strv_len, NULL);
+ if (domain == NULL) {
+ return;
+ }
+ name = strv_len_next(strv, strv_len, domain);
+ if (name == NULL) {
+ return;
+ }
+ typebuf = strv_len_next(strv, strv_len, name);
+ if (typebuf == NULL) {
+ return;
+ }
+
+ type = smb_strtoul(typebuf, NULL, 10, &error, SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ return;
+ }
+
+ state->fn(domain,
+ name,
+ (enum lsa_SidType)type,
+ gencache_timeout_expired(timeout),
+ state->private_data);
+
+ state->ok = true;
+}
+
+bool namemap_cache_find_sid(const struct dom_sid *sid,
+ void (*fn)(const char *domain,
+ const char *name,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data),
+ void *private_data)
+{
+ struct namemap_cache_find_sid_state state = {
+ .fn = fn, .private_data = private_data
+ };
+ struct dom_sid_buf sidbuf;
+ char keybuf[sizeof(sidbuf.buf)+10];
+ bool ok;
+
+ dom_sid_str_buf(sid, &sidbuf);
+ snprintf(keybuf, sizeof(keybuf), "SID2NAME/%s", sidbuf.buf);
+
+ ok = gencache_parse(keybuf, namemap_cache_find_sid_parser, &state);
+ if (!ok) {
+ DBG_DEBUG("gencache_parse(%s) failed\n", keybuf);
+ return false;
+ }
+
+ if (!state.ok) {
+ DBG_DEBUG("Could not parse %s, deleting\n", keybuf);
+ gencache_del(keybuf);
+ return false;
+ }
+
+ return true;
+}
+
+bool namemap_cache_set_name2sid(const char *domain, const char *name,
+ const struct dom_sid *sid,
+ enum lsa_SidType type,
+ time_t timeout)
+{
+ char typebuf[16];
+ struct dom_sid_buf sidbuf = {{0}};
+ char *key;
+ char *key_upper;
+ char *val = NULL;
+ DATA_BLOB data;
+ int ret;
+ bool ok = false;
+
+ if (domain == NULL) {
+ domain = "";
+ }
+ if (name == NULL) {
+ name = "";
+ }
+ if (type != SID_NAME_UNKNOWN) {
+ dom_sid_str_buf(sid, &sidbuf);
+ }
+
+ snprintf(typebuf, sizeof(typebuf), "%d", (int)type);
+
+ key = talloc_asprintf(talloc_tos(), "NAME2SID/%s\\%s", domain, name);
+ if (key == NULL) {
+ DBG_DEBUG("talloc_asprintf failed\n");
+ goto fail;
+ }
+ key_upper = strupper_talloc(key, key);
+ if (key_upper == NULL) {
+ DBG_DEBUG("strupper_talloc failed\n");
+ goto fail;
+ }
+
+ ret = strv_add(key, &val, sidbuf.buf);
+ if (ret != 0) {
+ DBG_DEBUG("strv_add failed: %s\n", strerror(ret));
+ goto fail;
+ }
+ ret = strv_add(NULL, &val, typebuf);
+ if (ret != 0) {
+ DBG_DEBUG("strv_add failed: %s\n", strerror(ret));
+ goto fail;
+ }
+
+ data = data_blob_const(val, talloc_get_size(val));
+
+ ok = gencache_set_data_blob(key_upper, data, timeout);
+ if (!ok) {
+ DBG_DEBUG("gencache_set_data_blob failed\n");
+ }
+fail:
+ TALLOC_FREE(key);
+ return ok;
+}
+
+struct namemap_cache_find_name_state {
+ void (*fn)(const struct dom_sid *sid,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data);
+ void *private_data;
+ bool ok;
+};
+
+static void namemap_cache_find_name_parser(
+ const struct gencache_timeout *timeout,
+ DATA_BLOB blob,
+ void *private_data)
+{
+ struct namemap_cache_find_name_state *state = private_data;
+ const char *strv = (const char *)blob.data;
+ size_t strv_len = blob.length;
+ const char *sidbuf;
+ const char *sid_endptr;
+ const char *typebuf;
+ int error = 0;
+ struct dom_sid sid;
+ unsigned long type;
+ bool ok;
+
+ state->ok = false;
+
+ sidbuf = strv_len_next(strv, strv_len, NULL);
+ if (sidbuf == NULL) {
+ return;
+ }
+ typebuf = strv_len_next(strv, strv_len, sidbuf);
+ if (typebuf == NULL) {
+ return;
+ }
+
+ ok = dom_sid_parse_endp(sidbuf, &sid, &sid_endptr);
+ if (!ok) {
+ return;
+ }
+ if (*sid_endptr != '\0') {
+ return;
+ }
+
+ type = smb_strtoul(typebuf, NULL, 10, &error, SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ return;
+ }
+
+ state->fn(&sid,
+ (enum lsa_SidType)type,
+ gencache_timeout_expired(timeout),
+ state->private_data);
+
+ state->ok = true;
+}
+
+bool namemap_cache_find_name(const char *domain,
+ const char *name,
+ void (*fn)(const struct dom_sid *sid,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data),
+ void *private_data)
+{
+ struct namemap_cache_find_name_state state = {
+ .fn = fn, .private_data = private_data
+ };
+ char *key;
+ char *key_upper;
+ bool ret = false;
+ bool ok;
+
+ key = talloc_asprintf(talloc_tos(), "NAME2SID/%s\\%s", domain, name);
+ if (key == NULL) {
+ DBG_DEBUG("talloc_asprintf failed\n");
+ return false;
+ }
+ key_upper = strupper_talloc(key, key);
+ if (key_upper == NULL) {
+ DBG_DEBUG("strupper_talloc failed\n");
+ goto fail;
+ }
+
+ ok = gencache_parse(key_upper, namemap_cache_find_name_parser, &state);
+ if (!ok) {
+ DBG_DEBUG("gencache_parse(%s) failed\n", key_upper);
+ goto fail;
+ }
+
+ if (!state.ok) {
+ DBG_DEBUG("Could not parse %s, deleting\n", key_upper);
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(key);
+ return ret;
+}
diff --git a/source3/lib/namemap_cache.h b/source3/lib/namemap_cache.h
new file mode 100644
index 0000000..5de8ce4
--- /dev/null
+++ b/source3/lib/namemap_cache.h
@@ -0,0 +1,49 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utils for caching sid2name and name2sid
+ * Copyright (C) Volker Lendecke 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_NAMEMAP_CACHE_H__
+#define __LIB_NAMEMAP_CACHE_H__
+
+#include "lib/util/time.h"
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/lsa.h"
+
+bool namemap_cache_set_sid2name(const struct dom_sid *sid,
+ const char *domain, const char *name,
+ enum lsa_SidType type, time_t timeout);
+bool namemap_cache_set_name2sid(const char *domain, const char *name,
+ const struct dom_sid *sid,
+ enum lsa_SidType type,
+ time_t timeout);
+bool namemap_cache_find_sid(const struct dom_sid *sid,
+ void (*fn)(const char *domain,
+ const char *name,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data),
+ void *private_data);
+bool namemap_cache_find_name(const char *domain,
+ const char *name,
+ void (*fn)(const struct dom_sid *sid,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data),
+ void *private_data);
+
+#endif
diff --git a/source3/lib/netapi/Doxyfile b/source3/lib/netapi/Doxyfile
new file mode 100644
index 0000000..44bf78b
--- /dev/null
+++ b/source3/lib/netapi/Doxyfile
@@ -0,0 +1,1362 @@
+# Doxyfile 1.5.5
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = Samba
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = 3.2.0pre3
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = dox
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
+# and Ukrainian.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH = $(PWD)/
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member
+# documentation.
+
+DETAILS_AT_TOP = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = YES
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = YES
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = NO
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = NO
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = netapi.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS = *.c \
+ *.h \
+ *.idl
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE = include/includes.h \
+ include/proto.h \
+ libnetapi.c \
+ libnetapi.h \
+ netapi.c
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH = examples
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output. If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = YES
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = NO
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default)
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default)
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code. Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 1
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = .
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 3
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = YES
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader. This is useful
+# if you want to understand what is going on. On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = NO
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is enabled by default, which results in a transparent
+# background. Warning: Depending on the platform used, enabling this option
+# may lead to badly anti-aliased labels on the edges of a graph (i.e. they
+# become hard to read).
+
+DOT_TRANSPARENT = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE = NO
diff --git a/source3/lib/netapi/cm.c b/source3/lib/netapi/cm.c
new file mode 100644
index 0000000..c54f955
--- /dev/null
+++ b/source3/lib/netapi/cm.c
@@ -0,0 +1,284 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "libsmb/libsmb.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/********************************************************************
+********************************************************************/
+
+struct client_ipc_connection {
+ struct client_ipc_connection *prev, *next;
+ struct cli_state *cli;
+ struct client_pipe_connection *pipe_connections;
+};
+
+struct client_pipe_connection {
+ struct client_pipe_connection *prev, *next;
+ struct rpc_pipe_client *pipe;
+};
+
+/********************************************************************
+********************************************************************/
+
+static struct client_ipc_connection *ipc_cm_find(
+ struct libnetapi_private_ctx *priv_ctx, const char *server_name)
+{
+ struct client_ipc_connection *p;
+
+ for (p = priv_ctx->ipc_connections; p; p = p->next) {
+ const char *remote_name = smbXcli_conn_remote_name(p->cli->conn);
+
+ if (strequal(remote_name, server_name)) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+/********************************************************************
+********************************************************************/
+
+static WERROR libnetapi_open_ipc_connection(struct libnetapi_ctx *ctx,
+ const char *server_name,
+ struct client_ipc_connection **pp)
+{
+ struct libnetapi_private_ctx *priv_ctx;
+ struct cli_state *cli_ipc = NULL;
+ struct client_ipc_connection *p;
+ NTSTATUS status;
+ const char *username = NULL;
+ const char *password = NULL;
+ NET_API_STATUS rc;
+ enum credentials_use_kerberos krb5_state;
+
+ if (!ctx || !pp || !server_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ priv_ctx = (struct libnetapi_private_ctx *)ctx->private_data;
+
+ p = ipc_cm_find(priv_ctx, server_name);
+ if (p) {
+ *pp = p;
+ return WERR_OK;
+ }
+
+ rc = libnetapi_get_username(ctx, &username);
+ if (rc != 0) {
+ return WERR_INTERNAL_ERROR;
+ }
+
+ rc = libnetapi_get_password(ctx, &password);
+ if (rc != 0) {
+ return WERR_INTERNAL_ERROR;
+ }
+
+ if (password == NULL) {
+ cli_credentials_set_cmdline_callbacks(ctx->creds);
+ }
+
+ krb5_state = cli_credentials_get_kerberos_state(ctx->creds);
+
+ if (username != NULL && username[0] != '\0' &&
+ password != NULL && password[0] != '\0' &&
+ krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ cli_credentials_set_kerberos_state(ctx->creds,
+ CRED_USE_KERBEROS_DESIRED,
+ CRED_SPECIFIED);
+ }
+
+ status = cli_cm_open(ctx, NULL,
+ server_name, "IPC$",
+ ctx->creds,
+ NULL, 0, 0x20, &cli_ipc);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_ipc = NULL;
+ }
+
+ if (!cli_ipc) {
+ libnetapi_set_error_string(ctx,
+ "Failed to connect to IPC$ share on %s", server_name);
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ p = talloc_zero(ctx, struct client_ipc_connection);
+ if (p == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p->cli = cli_ipc;
+ DLIST_ADD(priv_ctx->ipc_connections, p);
+
+ *pp = p;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR libnetapi_shutdown_cm(struct libnetapi_ctx *ctx)
+{
+ struct libnetapi_private_ctx *priv_ctx =
+ (struct libnetapi_private_ctx *)ctx->private_data;
+ struct client_ipc_connection *p;
+
+ for (p = priv_ctx->ipc_connections; p; p = p->next) {
+ cli_shutdown(p->cli);
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS pipe_cm_find(struct client_ipc_connection *ipc,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ struct client_pipe_connection *p;
+
+ for (p = ipc->pipe_connections; p; p = p->next) {
+ const char *ipc_remote_name;
+
+ if (!rpccli_is_connected(p->pipe)) {
+ return NT_STATUS_PIPE_EMPTY;
+ }
+
+ ipc_remote_name = smbXcli_conn_remote_name(ipc->cli->conn);
+
+ if (strequal(ipc_remote_name, p->pipe->desthost)
+ && ndr_syntax_id_equal(&p->pipe->abstract_syntax,
+ &table->syntax_id)) {
+ *presult = p->pipe;
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_PIPE_NOT_AVAILABLE;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS pipe_cm_connect(TALLOC_CTX *mem_ctx,
+ struct client_ipc_connection *ipc,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ struct client_pipe_connection *p;
+ NTSTATUS status;
+
+ p = talloc_zero_array(mem_ctx, struct client_pipe_connection, 1);
+ if (!p) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_rpc_pipe_open_noauth(ipc->cli, table, &p->pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(p);
+ return status;
+ }
+
+ DLIST_ADD(ipc->pipe_connections, p);
+
+ *presult = p->pipe;
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS pipe_cm_open(TALLOC_CTX *ctx,
+ struct client_ipc_connection *ipc,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ if (NT_STATUS_IS_OK(pipe_cm_find(ipc, table, presult))) {
+ return NT_STATUS_OK;
+ }
+
+ return pipe_cm_connect(ctx, ipc, table, presult);
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR libnetapi_open_pipe(struct libnetapi_ctx *ctx,
+ const char *server_name,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ struct rpc_pipe_client *result = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ struct client_ipc_connection *ipc = NULL;
+
+ if (!presult) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnetapi_open_ipc_connection(ctx, server_name, &ipc);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ status = pipe_cm_open(ctx, ipc, table, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnetapi_set_error_string(ctx, "failed to open PIPE %s: %s",
+ table->name,
+ get_friendly_nt_error_msg(status));
+ return WERR_NERR_DESTNOTFOUND;
+ }
+
+ *presult = result;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR libnetapi_get_binding_handle(struct libnetapi_ctx *ctx,
+ const char *server_name,
+ const struct ndr_interface_table *table,
+ struct dcerpc_binding_handle **binding_handle)
+{
+ struct rpc_pipe_client *pipe_cli;
+ WERROR result;
+
+ *binding_handle = NULL;
+
+ result = libnetapi_open_pipe(ctx, server_name, table, &pipe_cli);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *binding_handle = pipe_cli->binding_handle;
+
+ return WERR_OK;
+}
diff --git a/source3/lib/netapi/examples/common.c b/source3/lib/netapi/examples/common.c
new file mode 100644
index 0000000..72d7150
--- /dev/null
+++ b/source3/lib/netapi/examples/common.c
@@ -0,0 +1,249 @@
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <popt.h>
+#include <netapi.h>
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <iconv.h>
+
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+
+#include "common.h"
+
+void popt_common_callback(poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption *opt,
+ const char *arg, const void *data)
+{
+ struct libnetapi_ctx *ctx = NULL;
+
+ libnetapi_getctx(&ctx);
+
+ if (reason == POPT_CALLBACK_REASON_PRE) {
+ }
+
+ if (reason == POPT_CALLBACK_REASON_POST) {
+ }
+
+ if (!opt) {
+ return;
+ }
+ switch (opt->val) {
+ case 'U': {
+ char *puser = strdup(arg);
+ char *p = NULL;
+
+ if ((p = strchr(puser,'%'))) {
+ size_t len;
+ *p = 0;
+ libnetapi_set_username(ctx, puser);
+ libnetapi_set_password(ctx, p+1);
+ len = strlen(p+1);
+ memset(strchr(arg,'%')+1,'X',len);
+ } else {
+ libnetapi_set_username(ctx, puser);
+ }
+ free(puser);
+ break;
+ }
+ case 'd':
+ libnetapi_set_debuglevel(ctx, arg);
+ break;
+ case 'p':
+ libnetapi_set_password(ctx, arg);
+ break;
+ case 'k':
+ libnetapi_set_use_kerberos(ctx);
+ break;
+ }
+}
+
+struct poptOption popt_common_netapi_examples[] = {
+ {
+ .argInfo = POPT_ARG_CALLBACK|POPT_CBFLAG_PRE|POPT_CBFLAG_POST,
+ .arg = (void *)popt_common_callback,
+ },
+ {
+ .longName = "user",
+ .shortName = 'U',
+ .argInfo = POPT_ARG_STRING,
+ .val = 'U',
+ .descrip = "Username used for connection",
+ .argDescrip = "USERNAME",
+ },
+ {
+ .longName = "password",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_STRING,
+ .val = 'p',
+ .descrip = "Password used for connection",
+ .argDescrip = "PASSWORD",
+ },
+ {
+ .longName = "debuglevel",
+ .shortName = 'd',
+ .argInfo = POPT_ARG_STRING,
+ .val = 'd',
+ .descrip = "Debuglevel",
+ .argDescrip = "DEBUGLEVEL",
+ },
+ {
+ .longName = "kerberos",
+ .shortName = 'k',
+ .argInfo = POPT_ARG_NONE,
+ .val = 'k',
+ .descrip = "Use Kerberos",
+ },
+ POPT_TABLEEND
+};
+
+char *netapi_read_file(const char *filename, uint32_t *psize)
+{
+ int fd;
+ FILE *file = NULL;
+ char *p = NULL;
+ size_t size = 0;
+ size_t chunk = 1024;
+ size_t maxsize = SIZE_MAX;
+ int err;
+
+ fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ goto fail;
+ }
+
+ file = fdopen(fd, "r");
+ if (file == NULL) {
+ goto fail;
+ }
+
+ while (size < maxsize) {
+ char *tmp = NULL;
+ size_t newbufsize;
+ size_t nread;
+
+ chunk = MIN(chunk, (maxsize - size));
+
+ newbufsize = size + (chunk+1); /* chunk+1 can't overflow */
+ if (newbufsize < size) {
+ goto fail; /* overflow */
+ }
+
+ tmp = realloc(p, sizeof(char) * newbufsize);
+ if (tmp == NULL) {
+ free(p);
+ p = NULL;
+ goto fail;
+ }
+ p = tmp;
+
+ nread = fread(p+size, 1, chunk, file);
+ size += nread;
+
+ if (nread != chunk) {
+ break;
+ }
+ }
+
+ err = ferror(file);
+ if (err != 0) {
+ free(p);
+ goto fail;
+ }
+
+ p[size] = '\0';
+
+ if (psize != NULL) {
+ *psize = size;
+ }
+ fail:
+ if (file != NULL) {
+ fclose(file);
+ }
+ if (fd >= 0) {
+ close(fd);
+ }
+
+ return p;
+}
+
+int netapi_save_file(const char *fname, void *ppacket, size_t length)
+{
+ int fd;
+ fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd == -1) {
+ perror(fname);
+ return -1;
+ }
+ if (write(fd, ppacket, length) != length) {
+ fprintf(stderr,"Failed to write %s\n", fname);
+ close(fd);
+ return -1;
+ }
+ close(fd);
+ return 0;
+}
+
+int netapi_save_file_ucs2(const char *fname, const char *str)
+{
+ char *str_p = NULL;
+ char *ucs2_str = NULL;
+ size_t str_len = 0;
+ size_t ucs2_str_len = 0;
+ iconv_t cd;
+ int ret;
+ char *start;
+ size_t start_len;
+ char *p;
+
+ str_len = strlen(str) + 1;
+ ucs2_str_len = 2 * str_len; /* room for ucs2 */
+ ucs2_str_len += 2;
+
+ ucs2_str = calloc(ucs2_str_len, sizeof(char));
+ if (ucs2_str == NULL) {
+ return -1;
+ }
+ p = ucs2_str; /* store for free */
+
+ ucs2_str[0] = 0xff;
+ ucs2_str[1] = 0xfe;
+
+ start = ucs2_str;
+ start_len = ucs2_str_len;
+
+ ucs2_str += 2;
+ ucs2_str_len -= 2;
+
+ cd = iconv_open("UTF-16LE", "ASCII");
+ if (cd == (iconv_t)-1) {
+ free(p);
+ return -1;
+ }
+
+ str_p = (void *)((uintptr_t)str);
+
+ ret = iconv(cd,
+ &str_p,
+ &str_len,
+ &ucs2_str,
+ &ucs2_str_len);
+ if (ret == -1) {
+ free(p);
+ return -1;
+ }
+ iconv_close(cd);
+
+ ret = netapi_save_file(fname, start, start_len);
+ free(p);
+
+ return ret;
+}
diff --git a/source3/lib/netapi/examples/common.h b/source3/lib/netapi/examples/common.h
new file mode 100644
index 0000000..df7f176
--- /dev/null
+++ b/source3/lib/netapi/examples/common.h
@@ -0,0 +1,18 @@
+#include <popt.h>
+
+void popt_common_callback(poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption *opt,
+ const char *arg, const void *data);
+
+extern struct poptOption popt_common_netapi_examples[];
+
+#ifndef POPT_TABLEEND
+#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL }
+#endif
+
+#define POPT_COMMON_LIBNETAPI_EXAMPLES { NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_netapi_examples, 0, "Common samba netapi example options:", NULL },
+
+char *netapi_read_file(const char *filename, uint32_t *psize);
+int netapi_save_file(const char *fname, void *ppacket, size_t length);
+int netapi_save_file_ucs2(const char *fname, const char *str);
diff --git a/source3/lib/netapi/examples/dsgetdc/dsgetdc.c b/source3/lib/netapi/examples/dsgetdc/dsgetdc.c
new file mode 100644
index 0000000..6265e66
--- /dev/null
+++ b/source3/lib/netapi/examples/dsgetdc/dsgetdc.c
@@ -0,0 +1,101 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * DsGetDcName query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+
+ const char *hostname = NULL;
+ const char *domain = NULL;
+ uint32_t flags = 0;
+ struct DOMAIN_CONTROLLER_INFO *info = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ { "flags", 0, POPT_ARG_INT, NULL, 'f', "Query flags", "FLAGS" },
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("dsgetdc", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname domainname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'f':
+ sscanf(poptGetOptArg(pc), "%x", &flags);
+ }
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ domain = poptGetArg(pc);
+
+ /* DsGetDcName */
+
+ status = DsGetDcName(hostname, domain, NULL, NULL, flags, &info);
+ if (status != 0) {
+ printf("DsGetDcName failed with: %s\n",
+ libnetapi_errstr(status));
+ return status;
+ }
+
+ printf("DC Name:\t\t%s\n", info->domain_controller_name);
+ printf("DC Address:\t\t%s\n", info->domain_controller_address);
+ printf("DC Address Type:\t%d\n", info->domain_controller_address_type);
+ printf("Domain Name:\t\t%s\n", info->domain_name);
+ printf("DNS Forest Name:\t%s\n", info->dns_forest_name);
+ printf("DC flags:\t\t0x%08x\n", info->flags);
+ printf("DC Sitename:\t\t%s\n", info->dc_site_name);
+ printf("Client Sitename:\t%s\n", info->client_site_name);
+
+ out:
+ NetApiBufferFree(info);
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/file/file_close.c b/source3/lib/netapi/examples/file/file_close.c
new file mode 100644
index 0000000..759173a
--- /dev/null
+++ b/source3/lib/netapi/examples/file/file_close.c
@@ -0,0 +1,83 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetFileClose query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t fileid = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("file_close", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname fileid");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ fileid = atoi(poptGetArg(pc));
+
+ /* NetFileClose */
+
+ status = NetFileClose(hostname, fileid);
+ if (status != 0) {
+ printf("NetFileClose failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/file/file_enum.c b/source3/lib/netapi/examples/file/file_enum.c
new file mode 100644
index 0000000..5fbb285
--- /dev/null
+++ b/source3/lib/netapi/examples/file/file_enum.c
@@ -0,0 +1,146 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetFileEnum query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *basepath = NULL;
+ const char *username = NULL;
+ uint32_t level = 3;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int i;
+
+ struct FILE_INFO_2 *i2 = NULL;
+ struct FILE_INFO_3 *i3 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("file_enum", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname basepath username level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ basepath = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetFileEnum */
+
+ do {
+
+ status = NetFileEnum(hostname,
+ basepath,
+ username,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ printf("total entries: %d\n", total_entries);
+ switch (level) {
+ case 2:
+ i2 = (struct FILE_INFO_2 *)buffer;
+ break;
+ case 3:
+ i3 = (struct FILE_INFO_3 *)buffer;
+ break;
+ default:
+ break;
+ }
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 2:
+ printf("file_id: %d\n", i2->fi2_id);
+ i2++;
+ break;
+ case 3:
+ printf("file_id: %d\n", i3->fi3_id);
+ printf("permissions: %d\n", i3->fi3_permissions);
+ printf("num_locks: %d\n", i3->fi3_num_locks);
+ printf("pathname: %s\n", i3->fi3_pathname);
+ printf("username: %s\n", i3->fi3_username);
+ i3++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetFileEnum failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/file/file_getinfo.c b/source3/lib/netapi/examples/file/file_getinfo.c
new file mode 100644
index 0000000..9ad8305
--- /dev/null
+++ b/source3/lib/netapi/examples/file/file_getinfo.c
@@ -0,0 +1,112 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetFileGetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t fileid = 0;
+ uint32_t level = 3;
+ uint8_t *buffer = NULL;
+
+ struct FILE_INFO_2 *i2 = NULL;
+ struct FILE_INFO_3 *i3 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("file_getinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname fileid");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ fileid = atoi(poptGetArg(pc));
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetFileGetInfo */
+
+ status = NetFileGetInfo(hostname,
+ fileid,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("NetFileGetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ switch (level) {
+ case 2:
+ i2 = (struct FILE_INFO_2 *)buffer;
+ printf("file_id: %d\n", i2->fi2_id);
+ break;
+ case 3:
+ i3 = (struct FILE_INFO_3 *)buffer;
+ printf("file_id: %d\n", i3->fi3_id);
+ printf("permissions: %d\n", i3->fi3_permissions);
+ printf("num_locks: %d\n", i3->fi3_num_locks);
+ printf("pathname: %s\n", i3->fi3_pathname);
+ printf("username: %s\n", i3->fi3_username);
+ break;
+ default:
+ break;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/getdc/getdc.c b/source3/lib/netapi/examples/getdc/getdc.c
new file mode 100644
index 0000000..98bb6a1
--- /dev/null
+++ b/source3/lib/netapi/examples/getdc/getdc.c
@@ -0,0 +1,86 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * GetDCName query
+ * Copyright (C) Guenther Deschner 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+
+ const char *hostname = NULL;
+ const char *domain = NULL;
+ uint8_t *buffer = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("getdc", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname domainname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ domain = poptGetArg(pc);
+
+ /* NetGetDCName */
+
+ status = NetGetDCName(hostname, domain, &buffer);
+ if (status != 0) {
+ printf("GetDcName failed with: %s\n", libnetapi_errstr(status));
+ } else {
+ printf("%s\n", (char *)buffer);
+ }
+
+ out:
+ NetApiBufferFree(buffer);
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_add.c b/source3/lib/netapi/examples/group/group_add.c
new file mode 100644
index 0000000..4da97c5
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_add.c
@@ -0,0 +1,90 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupAdd query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ struct GROUP_INFO_1 g1;
+ uint32_t parm_error = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_add", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ /* NetGroupAdd */
+
+ g1.grpi1_name = groupname;
+ g1.grpi1_comment = "Domain Group created using NetApi example code";
+
+ status = NetGroupAdd(hostname,
+ 1,
+ (uint8_t *)&g1,
+ &parm_error);
+ if (status != 0) {
+ printf("NetGroupAdd failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_adduser.c b/source3/lib/netapi/examples/group/group_adduser.c
new file mode 100644
index 0000000..253b3c5
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_adduser.c
@@ -0,0 +1,91 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupAddUser query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ const char *username = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_adduser", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname username");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ /* NetGroupAddUser */
+
+ status = NetGroupAddUser(hostname,
+ groupname,
+ username);
+ if (status != 0) {
+ printf("NetGroupAddUser failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_del.c b/source3/lib/netapi/examples/group/group_del.c
new file mode 100644
index 0000000..789e429
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_del.c
@@ -0,0 +1,82 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupDel query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_del", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ /* NetGroupDel */
+
+ status = NetGroupDel(hostname, groupname);
+ if (status != 0) {
+ printf("NetGroupDel failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_deluser.c b/source3/lib/netapi/examples/group/group_deluser.c
new file mode 100644
index 0000000..751ab5c
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_deluser.c
@@ -0,0 +1,91 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupDelUser query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ const char *username = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_deluser", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname username");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ /* NetGroupDelUser */
+
+ status = NetGroupDelUser(hostname,
+ groupname,
+ username);
+ if (status != 0) {
+ printf("NetGroupDelUser failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_enum.c b/source3/lib/netapi/examples/group/group_enum.c
new file mode 100644
index 0000000..fe2aee1
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_enum.c
@@ -0,0 +1,153 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupEnum query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int i;
+ char *sid_str = NULL;
+
+ struct GROUP_INFO_0 *info0 = NULL;
+ struct GROUP_INFO_1 *info1 = NULL;
+ struct GROUP_INFO_2 *info2 = NULL;
+ struct GROUP_INFO_3 *info3 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_enum", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetGroupEnum */
+
+ do {
+ status = NetGroupEnum(hostname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ printf("total entries: %d\n", total_entries);
+ switch (level) {
+ case 0:
+ info0 = (struct GROUP_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct GROUP_INFO_1 *)buffer;
+ break;
+ case 2:
+ info2 = (struct GROUP_INFO_2 *)buffer;
+ break;
+ case 3:
+ info3 = (struct GROUP_INFO_3 *)buffer;
+ break;
+ default:
+ break;
+ }
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ printf("#%d group: %s\n", i, info0->grpi0_name);
+ info0++;
+ break;
+ case 1:
+ printf("#%d group: %s\n", i, info1->grpi1_name);
+ printf("#%d comment: %s\n", i, info1->grpi1_comment);
+ info1++;
+ break;
+ case 2:
+ printf("#%d group: %s\n", i, info2->grpi2_name);
+ printf("#%d comment: %s\n", i, info2->grpi2_comment);
+ printf("#%d rid: %d\n", i, info2->grpi2_group_id);
+ printf("#%d attributes: 0x%08x\n", i, info2->grpi2_attributes);
+ info2++;
+ break;
+ case 3:
+ printf("#%d group: %s\n", i, info3->grpi3_name);
+ printf("#%d comment: %s\n", i, info3->grpi3_comment);
+ if (ConvertSidToStringSid(info3->grpi3_group_sid,
+ &sid_str)) {
+ printf("#%d group_sid: %s\n", i, sid_str);
+ free(sid_str);
+ }
+ printf("#%d attributes: 0x%08x\n", i, info3->grpi3_attributes);
+ info3++;
+ break;
+
+
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetGroupEnum failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_getinfo.c b/source3/lib/netapi/examples/group/group_getinfo.c
new file mode 100644
index 0000000..2e5b793
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_getinfo.c
@@ -0,0 +1,127 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupGetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 0;
+ struct GROUP_INFO_0 *g0;
+ struct GROUP_INFO_1 *g1;
+ struct GROUP_INFO_2 *g2;
+ struct GROUP_INFO_3 *g3;
+ char *sid_str = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_getinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetGroupGetInfo */
+
+ status = NetGroupGetInfo(hostname,
+ groupname,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("NetGroupGetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ switch (level) {
+ case 0:
+ g0 = (struct GROUP_INFO_0 *)buffer;
+ printf("name: %s\n", g0->grpi0_name);
+ break;
+ case 1:
+ g1 = (struct GROUP_INFO_1 *)buffer;
+ printf("name: %s\n", g1->grpi1_name);
+ printf("comment: %s\n", g1->grpi1_comment);
+ break;
+ case 2:
+ g2 = (struct GROUP_INFO_2 *)buffer;
+ printf("name: %s\n", g2->grpi2_name);
+ printf("comment: %s\n", g2->grpi2_comment);
+ printf("group_id: %d\n", g2->grpi2_group_id);
+ printf("attributes: %d\n", g2->grpi2_attributes);
+ break;
+ case 3:
+ g3 = (struct GROUP_INFO_3 *)buffer;
+ printf("name: %s\n", g3->grpi3_name);
+ printf("comment: %s\n", g3->grpi3_comment);
+ if (ConvertSidToStringSid(g3->grpi3_group_sid,
+ &sid_str)) {
+ printf("group_sid: %s\n", sid_str);
+ free(sid_str);
+ }
+ printf("attributes: %d\n", g3->grpi3_attributes);
+ break;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_getusers.c b/source3/lib/netapi/examples/group/group_getusers.c
new file mode 100644
index 0000000..72e79ec
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_getusers.c
@@ -0,0 +1,132 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupGetUsers query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int i;
+
+ struct GROUP_USERS_INFO_0 *info0 = NULL;
+ struct GROUP_USERS_INFO_1 *info1 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_getusers", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetGroupGetUsers */
+
+ do {
+ status = NetGroupGetUsers(hostname,
+ groupname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ printf("total entries: %d\n", total_entries);
+ switch (level) {
+ case 0:
+ info0 = (struct GROUP_USERS_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct GROUP_USERS_INFO_1 *)buffer;
+ break;
+ default:
+ break;
+ }
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ printf("#%d member: %s\n", i, info0->grui0_name);
+ info0++;
+ break;
+ case 1:
+ printf("#%d member: %s\n", i, info1->grui1_name);
+ printf("#%d attributes: %d\n", i, info1->grui1_attributes);
+ info1++;
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetGroupGetUsers failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_setinfo.c b/source3/lib/netapi/examples/group/group_setinfo.c
new file mode 100644
index 0000000..cd30d8b
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_setinfo.c
@@ -0,0 +1,142 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupSetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ const char *option = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 0;
+ uint32_t parm_err = 0;
+ struct GROUP_INFO_0 g0;
+ struct GROUP_INFO_1 g1;
+ struct GROUP_INFO_2 g2;
+ struct GROUP_INFO_3 g3;
+ struct GROUP_INFO_1002 g1002;
+ struct GROUP_INFO_1005 g1005;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_setinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname level option");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ level = atoi(poptGetArg(pc));
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ option = poptGetArg(pc);
+
+ /* NetGroupSetInfo */
+
+ switch (level) {
+ case 0:
+ g0.grpi0_name = option;
+ buffer = (uint8_t *)&g0;
+ break;
+ case 1:
+ g1.grpi1_name = option; /* this one will be ignored */
+ g1.grpi1_comment = option;
+ buffer = (uint8_t *)&g1;
+ break;
+ case 2:
+ g2.grpi2_name = option; /* this one will be ignored */
+ g2.grpi2_comment = option;
+ g2.grpi2_group_id = 4711; /* this one will be ignored */
+ g2.grpi2_attributes = 7;
+ buffer = (uint8_t *)&g2;
+ break;
+ case 3:
+ g3.grpi3_name = option; /* this one will be ignored */
+ g3.grpi3_comment = option;
+ g2.grpi2_attributes = 7;
+ buffer = (uint8_t *)&g3;
+ break;
+ case 1002:
+ g1002.grpi1002_comment = option;
+ buffer = (uint8_t *)&g1002;
+ break;
+ case 1005:
+ g1005.grpi1005_attributes = atoi(option);
+ buffer = (uint8_t *)&g1005;
+ break;
+ }
+
+ status = NetGroupSetInfo(hostname,
+ groupname,
+ level,
+ buffer,
+ &parm_err);
+ if (status != 0) {
+ printf("NetGroupSetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/group/group_setusers.c b/source3/lib/netapi/examples/group/group_setusers.c
new file mode 100644
index 0000000..70cf105
--- /dev/null
+++ b/source3/lib/netapi/examples/group/group_setusers.c
@@ -0,0 +1,142 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroupSetUsers query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t num_entries = 0;
+ const char **names = NULL;
+ int i = 0;
+ size_t buf_size = 0;
+
+ struct GROUP_USERS_INFO_0 *g0 = NULL;
+ struct GROUP_USERS_INFO_1 *g1 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("group_setusers", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+
+ names = poptGetArgs(pc);
+ for (i=0; names[i] != NULL; i++) {
+ num_entries++;
+ }
+
+ switch (level) {
+ case 0:
+ buf_size = sizeof(struct GROUP_USERS_INFO_0) * num_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g0);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ g0[i].grui0_name = names[i];
+ }
+
+ buffer = (uint8_t *)g0;
+ break;
+ case 1:
+ buf_size = sizeof(struct GROUP_USERS_INFO_1) * num_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g1);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ g1[i].grui1_name = names[i];
+ }
+
+ buffer = (uint8_t *)g1;
+ break;
+ default:
+ break;
+ }
+
+ /* NetGroupSetUsers */
+
+ status = NetGroupSetUsers(hostname,
+ groupname,
+ level,
+ buffer,
+ num_entries);
+ if (status != 0) {
+ printf("NetGroupSetUsers failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/join/djoin.c b/source3/lib/netapi/examples/join/djoin.c
new file mode 100644
index 0000000..737f330
--- /dev/null
+++ b/source3/lib/netapi/examples/join/djoin.c
@@ -0,0 +1,166 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Offline Domain Join utility
+ * Copyright (C) Guenther Deschner 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *domain = NULL;
+ const char *machine_name = NULL;
+ const char *windows_path = NULL;
+ const char *dcname = NULL;
+ const char *loadfile = NULL;
+ const char *savefile = NULL;
+ const char *machine_account_ou = NULL;
+ uint32_t options = 0;
+ uint8_t *provision_bin_data = NULL;
+ uint32_t provision_bin_data_size = 0;
+ const char *provision_text_data = NULL;
+ int provision = 0;
+ int requestodj = 0;
+ int default_password = 0;
+ int print_blob = 0;
+ int localos = 0;
+ int reuse = 0;
+
+ struct libnetapi_ctx *ctx = NULL;
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "provision", 0, POPT_ARG_NONE, &provision, 'D', "Create computer account in AD", NULL },
+ { "dcname", 0, POPT_ARG_STRING, &dcname, 'D', "Domain Controller Name", "DCNAME" },
+ { "machine_account_ou", 0, POPT_ARG_STRING, &machine_account_ou, 'D', "LDAP DN for Machine Account OU", "MACHINE_ACCOUNT_OU" },
+ { "domain", 0, POPT_ARG_STRING, &domain, 'D', "Domain name", "DOMAIN" },
+ { "machine_name", 0, POPT_ARG_STRING, &machine_name, 'D', "Computer Account Name", "MACHINENAME" },
+ { "defpwd", 0, POPT_ARG_NONE, &default_password, 'D', "Use default password for machine account (not recommended)", "" },
+ { "printblob", 0, POPT_ARG_NONE, &print_blob, 'D', "Print base64 encoded ODJ blob (for Windows answer files)", "" },
+ { "savefile", 0, POPT_ARG_STRING, &savefile, 'D', "Save ODJ blob to file (for Windows answer files)", "FILENAME" },
+ { "reuse", 0, POPT_ARG_NONE, &reuse, 'D', "Reuse machine account", "" },
+ { "requestodj", 0, POPT_ARG_NONE, &requestodj, 'D', "Load offline join data", NULL },
+ { "loadfile", 0, POPT_ARG_STRING, &loadfile, 'D', "Load file from previous provision", "FILENAME" },
+ { "localos", 0, POPT_ARG_NONE, &localos, 'D', "Request local OS to load offline join information", "" },
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("djoin", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "[provision|requestodj]");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (provision) {
+
+ if (domain == NULL) {
+ printf("domain must be defined\n");
+ goto out;
+ }
+
+ if (machine_name == NULL) {
+ printf("machine_name must be defined\n");
+ goto out;
+ }
+
+ if (default_password) {
+ options |= NETSETUP_PROVISION_USE_DEFAULT_PASSWORD;
+ }
+
+ if (reuse) {
+ options |= NETSETUP_PROVISION_REUSE_ACCOUNT;
+ }
+
+ status = NetProvisionComputerAccount(domain,
+ machine_name,
+ machine_account_ou,
+ dcname,
+ options,
+ NULL,
+ 0,
+ &provision_text_data);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ if (print_blob) {
+ printf("Provision Text Data: %s\n", provision_text_data);
+ }
+
+ if (savefile != NULL) {
+ status = netapi_save_file_ucs2(savefile, provision_text_data);
+ if (status != 0) {
+ goto out;
+ }
+ }
+ }
+
+ if (requestodj) {
+
+ if (loadfile == NULL) {
+ printf("--loadfile <FILENAME> is required\n");
+ goto out;
+ }
+ provision_bin_data = (uint8_t *)netapi_read_file(loadfile,
+ &provision_bin_data_size);
+ if (provision_bin_data == NULL) {
+ printf("failed to read loadfile: %s\n", loadfile);
+ goto out;
+ }
+
+ if (localos) {
+ options |= NETSETUP_PROVISION_ONLINE_CALLER;
+ }
+
+ status = NetRequestOfflineDomainJoin(provision_bin_data,
+ provision_bin_data_size,
+ options,
+ windows_path);
+ free(provision_bin_data);
+
+ if (status != 0 && status != 0x00000a99) {
+ /* NERR_JoinPerformedMustRestart */
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/join/getjoinableous.c b/source3/lib/netapi/examples/join/getjoinableous.c
new file mode 100644
index 0000000..c0fba57
--- /dev/null
+++ b/source3/lib/netapi/examples/join/getjoinableous.c
@@ -0,0 +1,110 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Join Support (cmdline + netapi)
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *host_name = NULL;
+ const char *domain_name = NULL;
+ const char **ous = NULL;
+ uint32_t num_ous = 0;
+ struct libnetapi_ctx *ctx = NULL;
+ int i;
+ const char *username = NULL;
+ const char *password = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "domain", 0, POPT_ARG_STRING, NULL, 'D', "Domain name", "DOMAIN" },
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("getjoinableous", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname domainname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'D':
+ domain_name = poptGetOptArg(pc);
+ break;
+ }
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ host_name = poptGetArg(pc);
+
+ /* NetGetJoinableOUs */
+
+ status = libnetapi_get_username(ctx, &username);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+ status = libnetapi_get_password(ctx, &password);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ status = NetGetJoinableOUs(host_name,
+ domain_name,
+ username,
+ password,
+ &num_ous,
+ &ous);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ } else {
+ printf("Successfully queried joinable ous:\n");
+ for (i=0; i<num_ous; i++) {
+ printf("ou: %s\n", ous[i]);
+ }
+ }
+
+ out:
+ NetApiBufferFree(ous);
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/join/getjoininformation.c b/source3/lib/netapi/examples/join/getjoininformation.c
new file mode 100644
index 0000000..7dac456
--- /dev/null
+++ b/source3/lib/netapi/examples/join/getjoininformation.c
@@ -0,0 +1,105 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Join Support (cmdline + netapi)
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *host_name = NULL;
+ char *name_buffer = NULL;
+ const char *p = NULL;
+ uint16_t name_type = 0;
+ struct libnetapi_ctx *ctx = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("getjoininformation", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ host_name = poptGetArg(pc);
+
+ /* NetGetJoinInformation */
+
+ status = NetGetJoinInformation(host_name, &p, &name_type);
+ name_buffer = discard_const_p(char, p);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ } else {
+ printf("Successfully queried join information:\n");
+
+ switch (name_type) {
+ case NetSetupUnknownStatus:
+ printf("%s's join status unknown (name: %s)\n",
+ host_name, name_buffer);
+ break;
+ case NetSetupUnjoined:
+ printf("%s is not joined (name: %s)\n",
+ host_name, name_buffer);
+ break;
+ case NetSetupWorkgroupName:
+ printf("%s is joined to workgroup %s\n",
+ host_name, name_buffer);
+ break;
+ case NetSetupDomainName:
+ printf("%s is joined to domain %s\n",
+ host_name, name_buffer);
+ break;
+ default:
+ printf("%s is in unknown status %d (name: %s)\n",
+ host_name, name_type, name_buffer);
+ break;
+ }
+ }
+
+ out:
+ NetApiBufferFree(name_buffer);
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/join/netdomjoin.c b/source3/lib/netapi/examples/join/netdomjoin.c
new file mode 100644
index 0000000..08ce71b
--- /dev/null
+++ b/source3/lib/netapi/examples/join/netdomjoin.c
@@ -0,0 +1,104 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Join Support (cmdline + netapi)
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+enum {
+ OPT_OU = 1000
+};
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *host_name = NULL;
+ const char *domain_name = NULL;
+ const char *account_ou = NULL;
+ const char *account = NULL;
+ const char *password = NULL;
+ uint32_t join_flags = NETSETUP_JOIN_DOMAIN |
+ NETSETUP_ACCT_CREATE |
+ NETSETUP_DOMAIN_JOIN_IF_JOINED;
+ struct libnetapi_ctx *ctx = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "ou", 0, POPT_ARG_STRING, &account_ou, 'U', "Account ou", "ACCOUNT_OU" },
+ { "domain", 0, POPT_ARG_STRING, &domain_name, 'U', "Domain name (required)", "DOMAIN" },
+ { "userd", 0, POPT_ARG_STRING, &account, 'U', "Domain admin account", "USERNAME" },
+ { "passwordd", 0, POPT_ARG_STRING, &password, 'U', "Domain admin password", "PASSWORD" },
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("netdomjoin", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ host_name = poptGetArg(pc);
+
+ if (!domain_name) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+
+ /* NetJoinDomain */
+
+ status = NetJoinDomain(host_name,
+ domain_name,
+ account_ou,
+ account,
+ password,
+ join_flags);
+ if (status != 0) {
+ const char *errstr = NULL;
+ errstr = libnetapi_get_error_string(ctx, status);
+ printf("Join failed with: %s\n", errstr);
+ } else {
+ printf("Successfully joined\n");
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/join/provision_computer_account.c b/source3/lib/netapi/examples/join/provision_computer_account.c
new file mode 100644
index 0000000..cc2dde5
--- /dev/null
+++ b/source3/lib/netapi/examples/join/provision_computer_account.c
@@ -0,0 +1,122 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetProvisionComputerAccount query
+ * Copyright (C) Guenther Deschner 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *domain = NULL;
+ const char *machine_name = NULL;
+ const char *machine_account_ou = NULL;
+ const char *dcname = NULL;
+ uint32_t options = 0;
+ const char *provision_text_data = NULL;
+ int default_password = 0;
+ int print_blob = 0;
+ const char *savefile = NULL;
+ int reuse = 0;
+
+ struct libnetapi_ctx *ctx = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "dcname", 0, POPT_ARG_STRING, &dcname, 'D', "Domain Controller Name", "DCNAME" },
+ { "machine_account_ou", 0, POPT_ARG_STRING, &machine_account_ou, 'D', "LDAP DN for Machine Account OU", "MACHINE_ACCOUNT_OU" },
+ { "defpwd", 0, POPT_ARG_NONE, &default_password, 'D', "Use default password for machine account (not recommended)", "" },
+ { "printblob", 0, POPT_ARG_NONE, &print_blob, 'D', "Print base64 encoded ODJ blob (for Windows answer files)", "" },
+ { "savefile", 0, POPT_ARG_STRING, &savefile, 'D', "Save ODJ blob to file (for Windows answer files)", "FILENAME" },
+ { "reuse", 0, POPT_ARG_NONE, &reuse, 'D', "Reuse machine account", "" },
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("provision_computer_account", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "domain machine_name");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ domain = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ machine_name = poptGetArg(pc);
+
+ if (default_password) {
+ options |= NETSETUP_PROVISION_USE_DEFAULT_PASSWORD;
+ }
+ if (reuse) {
+ options |= NETSETUP_PROVISION_REUSE_ACCOUNT;
+ }
+
+ /* NetProvisionComputerAccount */
+
+ status = NetProvisionComputerAccount(domain,
+ machine_name,
+ machine_account_ou,
+ dcname,
+ options,
+ NULL,
+ 0,
+ &provision_text_data);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ if (print_blob) {
+ printf("Provision data: %s\n", provision_text_data);
+ }
+
+ if (savefile != NULL) {
+ status = netapi_save_file_ucs2(savefile, provision_text_data);
+ if (status != 0) {
+ goto out;
+ }
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/join/rename_machine.c b/source3/lib/netapi/examples/join/rename_machine.c
new file mode 100644
index 0000000..7be6dc2
--- /dev/null
+++ b/source3/lib/netapi/examples/join/rename_machine.c
@@ -0,0 +1,101 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetRenameMachineInDomain query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *host_name = NULL;
+ const char *new_machine_name = NULL;
+ uint32_t rename_opt = 0;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *username = NULL;
+ const char *password = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("rename_machine", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname newmachinename");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ host_name = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ new_machine_name = poptGetArg(pc);
+
+ /* NetRenameMachineInDomain */
+
+ status = libnetapi_get_username(ctx, &username);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+ status = libnetapi_get_password(ctx, &password);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ status = NetRenameMachineInDomain(host_name,
+ new_machine_name,
+ username,
+ password,
+ rename_opt);
+ if (status != 0) {
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/join/request_offline_domain_join.c b/source3/lib/netapi/examples/join/request_offline_domain_join.c
new file mode 100644
index 0000000..96bbe0b
--- /dev/null
+++ b/source3/lib/netapi/examples/join/request_offline_domain_join.c
@@ -0,0 +1,97 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetRequestOfflineDomainJoin
+ * Copyright (C) Guenther Deschner 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdlib.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *windows_path = NULL;
+ uint32_t options = 0;
+ uint8_t *provision_bin_data = NULL;
+ uint32_t provision_bin_data_size = 0;
+ const char *loadfile = NULL;
+ struct libnetapi_ctx *ctx = NULL;
+ int localos = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "loadfile", 0, POPT_ARG_STRING, &loadfile, 'D', "Load file from previous provision", "FILENAME" },
+ { "localos", 0, POPT_ARG_NONE, &localos, 'D', "Request local OS to load offline join information", "" },
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("request_offline_domain_join", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (loadfile == NULL) {
+ printf("--loadfile <FILENAME> is required\n");
+ goto out;
+ }
+ provision_bin_data = (uint8_t *)netapi_read_file(loadfile,
+ &provision_bin_data_size);
+ if (provision_bin_data == NULL) {
+ printf("failed to read loadfile: %s\n", loadfile);
+ goto out;
+ }
+
+ if (localos) {
+ options |= NETSETUP_PROVISION_ONLINE_CALLER;
+ }
+
+ /* NetRequestOfflineDomainJoin */
+
+ status = NetRequestOfflineDomainJoin(provision_bin_data,
+ provision_bin_data_size,
+ options,
+ windows_path);
+ free(provision_bin_data);
+ if (status != 0 && status != 0x00000a99) {
+ /* NERR_JoinPerformedMustRestart */
+ printf("failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_add.c b/source3/lib/netapi/examples/localgroup/localgroup_add.c
new file mode 100644
index 0000000..7f23c99
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_add.c
@@ -0,0 +1,106 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupAdd query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ const char *comment = NULL;
+ struct LOCALGROUP_INFO_0 g0;
+ struct LOCALGROUP_INFO_1 g1;
+ uint32_t parm_error = 0;
+ uint8_t *buf = NULL;
+ uint32_t level = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_add", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname comment");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ comment = poptGetArg(pc);
+ }
+
+ /* NetLocalGroupAdd */
+
+ if (comment) {
+ level = 1;
+ g1.lgrpi1_name = groupname;
+ g1.lgrpi1_comment = comment;
+ buf = (uint8_t *)&g1;
+ } else {
+ level = 0;
+ g0.lgrpi0_name = groupname;
+ buf = (uint8_t *)&g0;
+ }
+
+ status = NetLocalGroupAdd(hostname,
+ level,
+ buf,
+ &parm_error);
+ if (status != 0) {
+ printf("NetLocalGroupAdd failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_addmembers.c b/source3/lib/netapi/examples/localgroup/localgroup_addmembers.c
new file mode 100644
index 0000000..aa4a9b5
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_addmembers.c
@@ -0,0 +1,141 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupAddMembers query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ struct LOCALGROUP_MEMBERS_INFO_0 *g0;
+ struct LOCALGROUP_MEMBERS_INFO_3 *g3;
+ uint32_t total_entries = 0;
+ uint8_t *buffer = NULL;
+ uint32_t level = 3;
+ const char **names = NULL;
+ int i = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_addmembers", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname member1 member2 ...");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+
+ names = poptGetArgs(pc);
+ for (i=0; names[i] != NULL; i++) {
+ total_entries++;
+ }
+
+ switch (level) {
+ case 0:
+ status = NetApiBufferAllocate(sizeof(struct LOCALGROUP_MEMBERS_INFO_0) * total_entries,
+ (void **)&g0);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<total_entries; i++) {
+ if (!ConvertStringSidToSid(names[i], &g0[i].lgrmi0_sid)) {
+ printf("could not convert sid\n");
+ goto out;
+ }
+ }
+
+ buffer = (uint8_t *)g0;
+ break;
+ case 3:
+ status = NetApiBufferAllocate(sizeof(struct LOCALGROUP_MEMBERS_INFO_3) * total_entries,
+ (void **)&g3);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<total_entries; i++) {
+ g3[i].lgrmi3_domainandname = names[i];
+ }
+
+ buffer = (uint8_t *)g3;
+ break;
+ default:
+ break;
+ }
+
+ /* NetLocalGroupAddMembers */
+
+ status = NetLocalGroupAddMembers(hostname,
+ groupname,
+ level,
+ buffer,
+ total_entries);
+ if (status != 0) {
+ printf("NetLocalGroupAddMembers failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_del.c b/source3/lib/netapi/examples/localgroup/localgroup_del.c
new file mode 100644
index 0000000..a2515df
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_del.c
@@ -0,0 +1,83 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupDel query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_del", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ /* NetLocalGroupDel */
+
+ status = NetLocalGroupDel(hostname,
+ groupname);
+ if (status != 0) {
+ printf("NetLocalGroupDel failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_delmembers.c b/source3/lib/netapi/examples/localgroup/localgroup_delmembers.c
new file mode 100644
index 0000000..7bd3ec0
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_delmembers.c
@@ -0,0 +1,141 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupDelMembers query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ struct LOCALGROUP_MEMBERS_INFO_0 *g0;
+ struct LOCALGROUP_MEMBERS_INFO_3 *g3;
+ uint32_t total_entries = 0;
+ uint8_t *buffer = NULL;
+ uint32_t level = 3;
+ const char **names = NULL;
+ int i = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_delmembers", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname member1 member2 ...");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+
+ names = poptGetArgs(pc);
+ for (i=0; names[i] != NULL; i++) {
+ total_entries++;
+ }
+
+ switch (level) {
+ case 0:
+ status = NetApiBufferAllocate(sizeof(struct LOCALGROUP_MEMBERS_INFO_0) * total_entries,
+ (void **)&g0);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<total_entries; i++) {
+ if (!ConvertStringSidToSid(names[i], &g0[i].lgrmi0_sid)) {
+ printf("could not convert sid\n");
+ goto out;
+ }
+ }
+
+ buffer = (uint8_t *)g0;
+ break;
+ case 3:
+ status = NetApiBufferAllocate(sizeof(struct LOCALGROUP_MEMBERS_INFO_3) * total_entries,
+ (void **)&g3);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<total_entries; i++) {
+ g3[i].lgrmi3_domainandname = names[i];
+ }
+
+ buffer = (uint8_t *)g3;
+ break;
+ default:
+ break;
+ }
+
+ /* NetLocalGroupDelMembers */
+
+ status = NetLocalGroupDelMembers(hostname,
+ groupname,
+ level,
+ buffer,
+ total_entries);
+ if (status != 0) {
+ printf("NetLocalGroupDelMembers failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_enum.c b/source3/lib/netapi/examples/localgroup/localgroup_enum.c
new file mode 100644
index 0000000..6fe0cf4
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_enum.c
@@ -0,0 +1,126 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupEnum query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int i;
+
+ struct LOCALGROUP_INFO_0 *info0 = NULL;
+ struct LOCALGROUP_INFO_1 *info1 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_enum", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetLocalGroupEnum */
+
+ do {
+ status = NetLocalGroupEnum(hostname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ printf("total entries: %d\n", total_entries);
+ switch (level) {
+ case 0:
+ info0 = (struct LOCALGROUP_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct LOCALGROUP_INFO_1 *)buffer;
+ break;
+ default:
+ break;
+ }
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ printf("#%d group: %s\n", i, info0->lgrpi0_name);
+ info0++;
+ break;
+ case 1:
+ printf("#%d group: %s\n", i, info1->lgrpi1_name);
+ printf("#%d comment: %s\n", i, info1->lgrpi1_comment);
+ info1++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetLocalGroupEnum failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_getinfo.c b/source3/lib/netapi/examples/localgroup/localgroup_getinfo.c
new file mode 100644
index 0000000..cd8fa8c
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_getinfo.c
@@ -0,0 +1,112 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupGetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 0;
+ struct LOCALGROUP_INFO_0 *g0;
+ struct LOCALGROUP_INFO_1 *g1;
+ struct LOCALGROUP_INFO_1002 *g1002;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_getinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetLocalGroupGetInfo */
+
+ status = NetLocalGroupGetInfo(hostname,
+ groupname,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("NetLocalGroupGetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ switch (level) {
+ case 0:
+ g0 = (struct LOCALGROUP_INFO_0 *)buffer;
+ printf("name: %s\n", g0->lgrpi0_name);
+ break;
+ case 1:
+ g1 = (struct LOCALGROUP_INFO_1 *)buffer;
+ printf("name: %s\n", g1->lgrpi1_name);
+ printf("comment: %s\n", g1->lgrpi1_comment);
+ break;
+ case 1002:
+ g1002 = (struct LOCALGROUP_INFO_1002 *)buffer;
+ printf("comment: %s\n", g1002->lgrpi1002_comment);
+ break;
+ }
+ NetApiBufferFree(buffer);
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_getmembers.c b/source3/lib/netapi/examples/localgroup/localgroup_getmembers.c
new file mode 100644
index 0000000..0589870
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_getmembers.c
@@ -0,0 +1,165 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupGetMembers query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ char *sid_str = NULL;
+ int i;
+
+ struct LOCALGROUP_MEMBERS_INFO_0 *info0 = NULL;
+ struct LOCALGROUP_MEMBERS_INFO_1 *info1 = NULL;
+ struct LOCALGROUP_MEMBERS_INFO_2 *info2 = NULL;
+ struct LOCALGROUP_MEMBERS_INFO_3 *info3 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_getmembers", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetLocalGroupGetMembers */
+
+ do {
+ status = NetLocalGroupGetMembers(hostname,
+ groupname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ printf("total entries: %d\n", total_entries);
+ switch (level) {
+ case 0:
+ info0 = (struct LOCALGROUP_MEMBERS_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct LOCALGROUP_MEMBERS_INFO_1 *)buffer;
+ break;
+ case 2:
+ info2 = (struct LOCALGROUP_MEMBERS_INFO_2 *)buffer;
+ break;
+ case 3:
+ info3 = (struct LOCALGROUP_MEMBERS_INFO_3 *)buffer;
+ break;
+ default:
+ break;
+ }
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ if (ConvertSidToStringSid(info0->lgrmi0_sid,
+ &sid_str)) {
+ printf("#%d member sid: %s\n", i, sid_str);
+ free(sid_str);
+ }
+ info0++;
+ break;
+ case 1:
+ if (ConvertSidToStringSid(info1->lgrmi1_sid,
+ &sid_str)) {
+ printf("#%d member sid: %s\n", i, sid_str);
+ free(sid_str);
+ }
+ printf("#%d sid type: %d\n", i, info1->lgrmi1_sidusage);
+ printf("#%d name: %s\n", i, info1->lgrmi1_name);
+ info1++;
+ break;
+ case 2:
+ if (ConvertSidToStringSid(info2->lgrmi2_sid,
+ &sid_str)) {
+ printf("#%d member sid: %s\n", i, sid_str);
+ free(sid_str);
+ }
+ printf("#%d sid type: %d\n", i, info2->lgrmi2_sidusage);
+ printf("#%d full name: %s\n", i, info2->lgrmi2_domainandname);
+ info2++;
+ break;
+ case 3:
+ printf("#%d full name: %s\n", i, info3->lgrmi3_domainandname);
+ info3++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetLocalGroupGetMembers failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_setinfo.c b/source3/lib/netapi/examples/localgroup/localgroup_setinfo.c
new file mode 100644
index 0000000..efcec76
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_setinfo.c
@@ -0,0 +1,128 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupSetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 0;
+ struct LOCALGROUP_INFO_0 g0;
+ struct LOCALGROUP_INFO_1 g1;
+ struct LOCALGROUP_INFO_1002 g1002;
+ const char *newname = NULL;
+ const char *newcomment = NULL;
+ uint32_t parm_err = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ { "newname", 0, POPT_ARG_STRING, NULL, 'n', "New Local Group Name", "NEWNAME" },
+ { "newcomment", 0, POPT_ARG_STRING, NULL, 'c', "New Local Group Comment", "NETCOMMENT" },
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_setinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'n':
+ newname = poptGetOptArg(pc);
+ break;
+ case 'c':
+ newcomment = poptGetOptArg(pc);
+ break;
+ }
+
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ if (newname && !newcomment) {
+ g0.lgrpi0_name = newname;
+ buffer = (uint8_t *)&g0;
+ level = 0;
+ } else if (newcomment && !newname) {
+ g1002.lgrpi1002_comment = newcomment;
+ buffer = (uint8_t *)&g1002;
+ level = 1002;
+ } else if (newname && newcomment) {
+ g1.lgrpi1_name = newname;
+ g1.lgrpi1_comment = newcomment;
+ buffer = (uint8_t *)&g1;
+ level = 1;
+ } else {
+ printf("not enough input\n");
+ goto out;
+ }
+
+ /* NetLocalGroupSetInfo */
+
+ status = NetLocalGroupSetInfo(hostname,
+ groupname,
+ level,
+ buffer,
+ &parm_err);
+ if (status != 0) {
+ printf("NetLocalGroupSetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/localgroup/localgroup_setmembers.c b/source3/lib/netapi/examples/localgroup/localgroup_setmembers.c
new file mode 100644
index 0000000..c35f2bb
--- /dev/null
+++ b/source3/lib/netapi/examples/localgroup/localgroup_setmembers.c
@@ -0,0 +1,146 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroupSetMembers query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *groupname = NULL;
+ struct LOCALGROUP_MEMBERS_INFO_0 *g0;
+ struct LOCALGROUP_MEMBERS_INFO_3 *g3;
+ uint32_t total_entries = 0;
+ uint8_t *buffer = NULL;
+ uint32_t level = 3;
+ const char **names = NULL;
+ int i = 0;
+ size_t buf_size = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("localgroup_setmembers", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname groupname member1 member2 ...");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ groupname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+
+ names = poptGetArgs(pc);
+ for (i=0; names[i] != NULL; i++) {
+ total_entries++;
+ }
+
+ switch (level) {
+ case 0:
+ buf_size = sizeof(struct LOCALGROUP_MEMBERS_INFO_0) * total_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g0);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<total_entries; i++) {
+ if (!ConvertStringSidToSid(names[i], &g0[i].lgrmi0_sid)) {
+ printf("could not convert sid\n");
+ goto out;
+ }
+ }
+
+ buffer = (uint8_t *)g0;
+ break;
+ case 3:
+ buf_size = sizeof(struct LOCALGROUP_MEMBERS_INFO_3) * total_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g3);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<total_entries; i++) {
+ g3[i].lgrmi3_domainandname = names[i];
+ }
+
+ buffer = (uint8_t *)g3;
+ break;
+ default:
+ break;
+ }
+
+ /* NetLocalGroupSetMembers */
+
+ status = NetLocalGroupSetMembers(hostname,
+ groupname,
+ level,
+ buffer,
+ total_entries);
+ if (status != 0) {
+ printf("NetLocalGroupSetMembers failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ NetApiBufferFree(buffer);
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/netdomjoin-gui/logo-small.png b/source3/lib/netapi/examples/netdomjoin-gui/logo-small.png
new file mode 100644
index 0000000..f041198
--- /dev/null
+++ b/source3/lib/netapi/examples/netdomjoin-gui/logo-small.png
Binary files differ
diff --git a/source3/lib/netapi/examples/netdomjoin-gui/logo.png b/source3/lib/netapi/examples/netdomjoin-gui/logo.png
new file mode 100644
index 0000000..6df4ace
--- /dev/null
+++ b/source3/lib/netapi/examples/netdomjoin-gui/logo.png
Binary files differ
diff --git a/source3/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c b/source3/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c
new file mode 100644
index 0000000..6fd584a
--- /dev/null
+++ b/source3/lib/netapi/examples/netdomjoin-gui/netdomjoin-gui.c
@@ -0,0 +1,1888 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Join Support (gtk + netapi)
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <netdb.h>
+
+#include <gtk/gtk.h>
+#include <glib/gprintf.h>
+
+#include <netapi.h>
+
+#define MAX_CRED_LEN 256
+#define MAX_NETBIOS_NAME_LEN 15
+
+#define SAMBA_ICON_PATH "/usr/share/pixmaps/samba/samba.ico"
+#define SAMBA_IMAGE_PATH "/usr/share/pixmaps/samba/logo.png"
+#define SAMBA_IMAGE_PATH_SMALL "/usr/share/pixmaps/samba/logo-small.png"
+
+#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0)
+
+static gboolean verbose = FALSE;
+
+typedef struct join_state {
+ struct libnetapi_ctx *ctx;
+ GtkWidget *window_main;
+ GtkWidget *window_parent;
+ GtkWidget *window_do_change;
+ GtkWidget *window_creds_prompt;
+ GtkWidget *entry_account;
+ GtkWidget *entry_password;
+ GtkWidget *entry_domain;
+ GtkWidget *entry_ou_list;
+ GtkWidget *entry_workgroup;
+ GtkWidget *button_ok;
+ GtkWidget *button_apply;
+ GtkWidget *button_ok_creds;
+ GtkWidget *button_get_ous;
+ GtkWidget *label_reboot;
+ GtkWidget *label_current_name_buffer;
+ GtkWidget *label_current_name_type;
+ GtkWidget *label_full_computer_name;
+ GtkWidget *label_winbind;
+ uint16_t name_type_initial;
+ uint16_t name_type_new;
+ char *name_buffer_initial;
+ char *name_buffer_new;
+ char *password;
+ char *account;
+ char *comment;
+ char *comment_new;
+ char *my_fqdn;
+ char *my_dnsdomain;
+ char *my_hostname;
+ uint16_t server_role;
+ gboolean settings_changed;
+ gboolean hostname_changed;
+ uint32_t stored_num_ous;
+ char *target_hostname;
+ uid_t uid;
+} join_state;
+
+static void callback_creds_prompt(GtkWidget *widget,
+ gpointer data,
+ const char *label_string,
+ gpointer cont_fn);
+
+
+static void debug(const char *format, ...)
+{
+ va_list args;
+
+ if (!verbose) {
+ return;
+ }
+
+ va_start(args, format);
+ g_vprintf(format, args);
+ va_end(args);
+}
+
+static gboolean callback_delete_event(GtkWidget *widget,
+ GdkEvent *event,
+ gpointer data)
+{
+ gtk_main_quit();
+ return FALSE;
+}
+
+static void callback_do_close_data(GtkWidget *widget,
+ gpointer data)
+{
+ debug("callback_do_close_data called\n");
+
+ if (data) {
+ gtk_widget_destroy(GTK_WIDGET(data));
+ }
+}
+
+static void callback_do_close_widget(GtkWidget *widget,
+ gpointer data)
+{
+ debug("callback_do_close_widget called\n");
+
+ if (widget) {
+ gtk_widget_destroy(widget);
+ }
+}
+
+static void callback_do_freeauth(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+
+ debug("callback_do_freeauth called\n");
+
+ SAFE_FREE(state->account);
+ SAFE_FREE(state->password);
+
+ if (state->window_creds_prompt) {
+ gtk_widget_destroy(GTK_WIDGET(state->window_creds_prompt));
+ state->window_creds_prompt = NULL;
+ }
+}
+
+static void callback_do_freeauth_and_close(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+
+ debug("callback_do_freeauth_and_close called\n");
+
+ SAFE_FREE(state->account);
+ SAFE_FREE(state->password);
+
+ if (state->window_creds_prompt) {
+ gtk_widget_destroy(GTK_WIDGET(state->window_creds_prompt));
+ state->window_creds_prompt = NULL;
+ }
+ if (state->window_do_change) {
+ gtk_widget_destroy(GTK_WIDGET(state->window_do_change));
+ state->window_do_change = NULL;
+ }
+}
+
+static void free_join_state(struct join_state *s)
+{
+ SAFE_FREE(s->name_buffer_initial);
+ SAFE_FREE(s->name_buffer_new);
+ SAFE_FREE(s->password);
+ SAFE_FREE(s->account);
+ SAFE_FREE(s->comment);
+ SAFE_FREE(s->comment_new);
+ SAFE_FREE(s->my_fqdn);
+ SAFE_FREE(s->my_dnsdomain);
+ SAFE_FREE(s->my_hostname);
+}
+
+static void do_cleanup(struct join_state *state)
+{
+ libnetapi_free(state->ctx);
+ free_join_state(state);
+}
+
+static void callback_apply_description_change(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+ NET_API_STATUS status = 0;
+ uint32_t parm_err = 0;
+ struct SERVER_INFO_1005 info1005;
+ GtkWidget *dialog;
+
+ info1005.sv1005_comment = state->comment_new;
+
+ status = NetServerSetInfo(state->target_hostname,
+ 1005,
+ (uint8_t *)&info1005,
+ &parm_err);
+ if (status) {
+ debug("NetServerSetInfo failed with: %s\n",
+ libnetapi_errstr(status));
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_main),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "Failed to change computer description: %s.",
+ libnetapi_get_error_string(state->ctx, status));
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_main));
+
+ g_signal_connect_swapped(dialog, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ dialog);
+
+ gtk_widget_show(dialog);
+ return;
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_apply), FALSE);
+}
+
+static void callback_do_exit(GtkWidget *widget,
+ gpointer data)
+{
+#if 0
+ GtkWidget *dialog;
+ gint result;
+#endif
+ struct join_state *state = (struct join_state *)data;
+
+ if (!state->settings_changed) {
+ callback_delete_event(NULL, NULL, NULL);
+ return;
+ }
+
+#if 0
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_main),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_YES_NO,
+ "You must restart your computer before the new settings will take effect.");
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ result = gtk_dialog_run(GTK_DIALOG(dialog));
+ switch (result) {
+ case GTK_RESPONSE_YES:
+ g_print("would reboot here\n");
+ break;
+ case GTK_RESPONSE_NO:
+ default:
+ break;
+ }
+ if (dialog) {
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ }
+#endif
+ if (state->window_main) {
+ gtk_widget_destroy(GTK_WIDGET(state->window_main));
+ state->window_main = NULL;
+ }
+ do_cleanup(state);
+ exit(0);
+}
+
+
+static void callback_do_reboot(GtkWidget *widget,
+ gpointer data,
+ gpointer data2)
+{
+ GtkWidget *dialog;
+ struct join_state *state = (struct join_state *)data2;
+
+ debug("callback_do_reboot\n");
+
+ state->settings_changed = TRUE;
+ dialog = gtk_message_dialog_new(GTK_WINDOW(data),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK,
+ "You must restart this computer for the changes to take effect.");
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_do_change));
+#if 0
+ g_signal_connect_swapped(dialog, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ dialog);
+
+ debug("showing dialog\n");
+ gtk_widget_show(dialog);
+#else
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+#endif
+
+ gtk_label_set_text(GTK_LABEL(state->label_reboot),
+ "Changes will take effect after you restart this computer");
+
+ debug("destroying do_change window\n");
+ gtk_widget_destroy(GTK_WIDGET(state->window_do_change));
+
+ {
+ uint32_t status;
+ const char *buffer;
+ uint16_t type;
+
+ status = NetGetJoinInformation(state->target_hostname,
+ &buffer,
+ &type);
+ if (status != 0) {
+ g_print("failed to query status\n");
+ return;
+ }
+
+ debug("got new status: %s\n", buffer);
+
+ SAFE_FREE(state->name_buffer_new);
+ state->name_buffer_new = strdup(buffer);
+ state->name_type_new = type;
+ state->name_buffer_initial = strdup(buffer);
+ state->name_type_initial = type;
+ NetApiBufferFree((void *)buffer);
+
+ gtk_label_set_text(GTK_LABEL(state->label_current_name_buffer),
+ state->name_buffer_new);
+ if (state->name_type_new == NetSetupDomainName) {
+ gtk_label_set_text(GTK_LABEL(state->label_current_name_type),
+ "Domain:");
+ } else {
+ gtk_label_set_text(GTK_LABEL(state->label_current_name_type),
+ "Workgroup:");
+ }
+ }
+}
+
+static void callback_return_username(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text;
+ struct join_state *state = (struct join_state *)data;
+ debug("callback_return_username called\n");
+ if (!widget) {
+ return;
+ }
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ if (!entry_text) {
+ return;
+ }
+ debug("callback_return_username: %s\n", entry_text);
+ SAFE_FREE(state->account);
+ state->account = strdup(entry_text);
+}
+
+static void callback_return_username_and_enter(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text;
+ struct join_state *state = (struct join_state *)data;
+ if (!widget) {
+ return;
+ }
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ if (!entry_text) {
+ return;
+ }
+ debug("callback_return_username_and_enter: %s\n", entry_text);
+ SAFE_FREE(state->account);
+ state->account = strdup(entry_text);
+ g_signal_emit_by_name(state->button_ok_creds, "clicked");
+}
+
+static void callback_return_password(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text;
+ struct join_state *state = (struct join_state *)data;
+ debug("callback_return_password called\n");
+ if (!widget) {
+ return;
+ }
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ if (!entry_text) {
+ return;
+ }
+#ifdef DEBUG_PASSWORD
+ debug("callback_return_password: %s\n", entry_text);
+#else
+ debug("callback_return_password: (not printed)\n");
+#endif
+ SAFE_FREE(state->password);
+ state->password = strdup(entry_text);
+}
+
+static void callback_return_password_and_enter(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text;
+ struct join_state *state = (struct join_state *)data;
+ if (!widget) {
+ return;
+ }
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ if (!entry_text) {
+ return;
+ }
+#ifdef DEBUG_PASSWORD
+ debug("callback_return_password_and_enter: %s\n", entry_text);
+#else
+ debug("callback_return_password_and_enter: (not printed)\n");
+#endif
+ SAFE_FREE(state->password);
+ state->password = strdup(entry_text);
+ g_signal_emit_by_name(state->button_ok_creds, "clicked");
+}
+
+static void callback_do_storeauth(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+
+ debug("callback_do_storeauth called\n");
+
+ SAFE_FREE(state->account);
+ SAFE_FREE(state->password);
+
+ callback_return_username(state->entry_account, (gpointer)state);
+ callback_return_password(state->entry_password, (gpointer)state);
+
+ if (state->window_creds_prompt) {
+ gtk_widget_destroy(GTK_WIDGET(state->window_creds_prompt));
+ state->window_creds_prompt = NULL;
+ }
+}
+
+static void callback_continue(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+
+ gtk_widget_grab_focus(GTK_WIDGET(state->button_ok));
+ g_signal_emit_by_name(state->button_ok, "clicked");
+}
+
+static void callback_do_storeauth_and_continue(GtkWidget *widget,
+ gpointer data)
+{
+ callback_do_storeauth(widget, data);
+ callback_continue(NULL, data);
+}
+
+static void callback_do_storeauth_and_scan(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+ callback_do_storeauth(widget, data);
+ g_signal_emit_by_name(state->button_get_ous, "clicked");
+}
+
+static void callback_do_hostname_change(GtkWidget *widget,
+ gpointer data)
+{
+ GtkWidget *dialog;
+ const char *str = NULL;
+
+ struct join_state *state = (struct join_state *)data;
+
+ switch (state->name_type_initial) {
+ case NetSetupDomainName: {
+#if 0
+ NET_API_STATUS status;
+ const char *newname;
+ char *p = NULL;
+
+ newname = strdup(gtk_label_get_text(GTK_LABEL(state->label_full_computer_name)));
+ if (!newname) {
+ return;
+ }
+
+ p = strchr(newname, '.');
+ if (p) {
+ *p = '\0';
+ }
+
+ if (!state->account || !state->password) {
+ debug("callback_do_hostname_change: no creds yet\n");
+ callback_creds_prompt(NULL, state,
+ "Enter the name and password of an account with permission to change a computer name in the domain.",
+ callback_do_storeauth_and_continue);
+ }
+
+ if (!state->account || !state->password) {
+ debug("callback_do_hostname_change: still no creds???\n");
+ return;
+ }
+
+ status = NetRenameMachineInDomain(state->target_hostname,
+ newname,
+ state->account,
+ state->password,
+ NETSETUP_ACCT_CREATE);
+ SAFE_FREE(newname);
+ /* we renamed the machine in the domain */
+ if (status == 0) {
+ return;
+ }
+ str = libnetapi_get_error_string(state->ctx, status);
+#else
+ str = "To be implemented: call NetRenameMachineInDomain\n";
+#endif
+ break;
+ }
+ case NetSetupWorkgroupName:
+ str = "To be implemented: call SetComputerNameEx\n";
+ break;
+ default:
+ break;
+ }
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s",str);
+
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_main));
+ g_signal_connect_swapped(dialog, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ dialog);
+ gtk_widget_show(dialog);
+}
+
+static void callback_creds_prompt(GtkWidget *widget,
+ gpointer data,
+ const char *label_string,
+ gpointer cont_fn)
+{
+ GtkWidget *window;
+ GtkWidget *box1;
+ GtkWidget *bbox;
+ GtkWidget *button;
+ GtkWidget *label;
+
+ struct join_state *state = (struct join_state *)data;
+
+ debug("callback_creds_prompt\n");
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_modal(GTK_WINDOW(window), TRUE);
+
+ gtk_window_set_title(GTK_WINDOW(window), "Computer Name Changes");
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+ gtk_widget_set_size_request(GTK_WIDGET(window), 380, 280);
+ gtk_window_set_icon_from_file(GTK_WINDOW(window), SAMBA_ICON_PATH, NULL);
+ gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(state->window_do_change));
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS);
+
+ g_signal_connect(G_OBJECT(window), "delete_event",
+ G_CALLBACK(callback_do_close_widget), NULL);
+
+ state->window_creds_prompt = window;
+ gtk_container_set_border_width(GTK_CONTAINER(window), 10);
+
+ box1 = gtk_vbox_new(FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(window), box1);
+
+ label = gtk_label_new(label_string);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+
+ gtk_box_pack_start(GTK_BOX(box1), label, FALSE, FALSE, 0);
+
+ gtk_widget_show(label);
+
+ /* USER NAME */
+ label = gtk_label_new("User name:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_box_pack_start(GTK_BOX(box1), label, FALSE, FALSE, 0);
+ gtk_widget_show(label);
+
+ state->entry_account = gtk_entry_new();
+ gtk_entry_set_max_length(GTK_ENTRY(state->entry_account), MAX_CRED_LEN);
+ g_signal_connect(G_OBJECT(state->entry_account), "activate",
+ G_CALLBACK(callback_return_username_and_enter),
+ (gpointer)state);
+ gtk_editable_select_region(GTK_EDITABLE(state->entry_account),
+ 0, GTK_ENTRY(state->entry_account)->text_length);
+ gtk_box_pack_start(GTK_BOX(box1), state->entry_account, TRUE, TRUE, 0);
+ gtk_widget_show(state->entry_account);
+
+ /* PASSWORD */
+ label = gtk_label_new("Password:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_box_pack_start(GTK_BOX(box1), label, FALSE, FALSE, 0);
+ gtk_widget_show(label);
+
+ state->entry_password = gtk_entry_new();
+ gtk_entry_set_max_length(GTK_ENTRY(state->entry_password), MAX_CRED_LEN);
+ gtk_entry_set_visibility(GTK_ENTRY(state->entry_password), FALSE);
+ g_signal_connect(G_OBJECT(state->entry_password), "activate",
+ G_CALLBACK(callback_return_password_and_enter),
+ (gpointer)state);
+ gtk_editable_set_editable(GTK_EDITABLE(state->entry_password), TRUE);
+ gtk_editable_select_region(GTK_EDITABLE(state->entry_password),
+ 0, GTK_ENTRY(state->entry_password)->text_length);
+ gtk_box_pack_start(GTK_BOX(box1), state->entry_password, TRUE, TRUE, 0);
+ gtk_widget_show(state->entry_password);
+
+ /* BUTTONS */
+ bbox = gtk_hbutton_box_new();
+ gtk_container_set_border_width(GTK_CONTAINER(bbox), 5);
+ gtk_container_add(GTK_CONTAINER(box1), bbox);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+ gtk_box_set_spacing(GTK_BOX(bbox), 10);
+
+ state->button_ok_creds = gtk_button_new_from_stock(GTK_STOCK_OK);
+ gtk_widget_grab_focus(GTK_WIDGET(state->button_ok_creds));
+ gtk_container_add(GTK_CONTAINER(bbox), state->button_ok_creds);
+ g_signal_connect(G_OBJECT(state->button_ok_creds), "clicked",
+ G_CALLBACK(cont_fn),
+ (gpointer)state);
+ gtk_widget_show(state->button_ok_creds);
+
+ button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+ gtk_container_add(GTK_CONTAINER(bbox), button);
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(callback_do_freeauth),
+ (gpointer)state);
+ gtk_widget_show_all(window);
+}
+
+static void callback_do_join(GtkWidget *widget,
+ gpointer data)
+{
+ GtkWidget *dialog;
+
+ NET_API_STATUS status;
+ const char *err_str = NULL;
+ uint32_t join_flags = 0;
+ uint32_t unjoin_flags = 0;
+ gboolean domain_join = FALSE;
+ gboolean try_unjoin = FALSE;
+ gboolean join_creds_required = TRUE;
+ gboolean unjoin_creds_required = TRUE;
+ const char *new_workgroup_type = NULL;
+ const char *initial_workgroup_type = NULL;
+ const char *account_ou = NULL;
+
+ struct join_state *state = (struct join_state *)data;
+
+ if (state->hostname_changed) {
+ callback_do_hostname_change(NULL, state);
+ return;
+ }
+
+ switch (state->name_type_initial) {
+ case NetSetupWorkgroupName:
+ initial_workgroup_type = "workgroup";
+ break;
+ case NetSetupDomainName:
+ initial_workgroup_type = "domain";
+ break;
+ default:
+ break;
+ }
+
+ switch (state->name_type_new) {
+ case NetSetupWorkgroupName:
+ new_workgroup_type = "workgroup";
+ break;
+ case NetSetupDomainName:
+ new_workgroup_type = "domain";
+ break;
+ default:
+ break;
+ }
+
+ account_ou = gtk_combo_box_get_active_text(GTK_COMBO_BOX(state->entry_ou_list));
+ if (account_ou && strlen(account_ou) == 0) {
+ account_ou = NULL;
+ }
+
+ if ((state->name_type_initial != NetSetupDomainName) &&
+ (state->name_type_new != NetSetupDomainName)) {
+ join_creds_required = FALSE;
+ unjoin_creds_required = FALSE;
+ }
+
+ if (state->name_type_new == NetSetupDomainName) {
+ domain_join = TRUE;
+ join_creds_required = TRUE;
+ join_flags = NETSETUP_JOIN_DOMAIN |
+ NETSETUP_ACCT_CREATE |
+ NETSETUP_DOMAIN_JOIN_IF_JOINED; /* for testing */
+ }
+
+ if ((state->name_type_initial == NetSetupDomainName) &&
+ (state->name_type_new == NetSetupWorkgroupName)) {
+ try_unjoin = TRUE;
+ unjoin_creds_required = TRUE;
+ join_creds_required = FALSE;
+ unjoin_flags = NETSETUP_JOIN_DOMAIN |
+ NETSETUP_ACCT_DELETE |
+ NETSETUP_IGNORE_UNSUPPORTED_FLAGS;
+ }
+
+ if (try_unjoin) {
+
+ debug("callback_do_join: Unjoining\n");
+
+ if (unjoin_creds_required) {
+ if (!state->account || !state->password) {
+ debug("callback_do_join: no creds yet\n");
+ callback_creds_prompt(NULL, state,
+ "Enter the name and password of an account with permission to leave the domain.",
+ callback_do_storeauth_and_continue);
+ }
+
+ if (!state->account || !state->password) {
+ debug("callback_do_join: still no creds???\n");
+ return;
+ }
+ }
+
+ status = NetUnjoinDomain(state->target_hostname,
+ state->account,
+ state->password,
+ unjoin_flags);
+ if (status != 0) {
+ callback_do_freeauth(NULL, state);
+ err_str = libnetapi_get_error_string(state->ctx, status);
+ g_print("callback_do_join: failed to unjoin (%s)\n",
+ err_str);
+#if 0
+
+ /* in fact we shouldn't annoy the user with an error message here */
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "The following error occurred attempting to unjoin the %s: \"%s\": %s",
+ initial_workgroup_type,
+ state->name_buffer_initial,
+ err_str);
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+#endif
+ }
+
+ }
+
+ /* before prompting for creds, make sure we can find a dc */
+
+ if (domain_join) {
+
+ struct DOMAIN_CONTROLLER_INFO *dc_info = NULL;
+
+ status = DsGetDcName(NULL,
+ state->name_buffer_new,
+ NULL,
+ NULL,
+ 0,
+ &dc_info);
+ if (status != 0) {
+ err_str = libnetapi_get_error_string(state->ctx, status);
+ g_print("callback_do_join: failed find dc (%s)\n", err_str);
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Failed to find a domain controller for domain: \"%s\": %s",
+ state->name_buffer_new,
+ err_str);
+
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_do_change));
+ g_signal_connect_swapped(dialog, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ dialog);
+
+ gtk_widget_show(dialog);
+
+ return;
+ }
+ }
+
+ if (join_creds_required) {
+ if (!state->account || !state->password) {
+ debug("callback_do_join: no creds yet\n");
+ callback_creds_prompt(NULL, state,
+ "Enter the name and password of an account with permission to join the domain.",
+ callback_do_storeauth_and_continue);
+ }
+
+ if (!state->account || !state->password) {
+ debug("callback_do_join: still no creds???\n");
+ return;
+ }
+ }
+
+ debug("callback_do_join: Joining a %s named %s using join_flags 0x%08x ",
+ new_workgroup_type,
+ state->name_buffer_new,
+ join_flags);
+ if (domain_join) {
+ debug("as %s ", state->account);
+#ifdef DEBUG_PASSWORD
+ debug("with %s ", state->password);
+#endif
+ }
+ debug("\n");
+
+ status = NetJoinDomain(state->target_hostname,
+ state->name_buffer_new,
+ account_ou,
+ state->account,
+ state->password,
+ join_flags);
+ if (status != 0) {
+ callback_do_freeauth(NULL, state);
+ err_str = libnetapi_get_error_string(state->ctx, status);
+ g_print("callback_do_join: failed to join (%s)\n", err_str);
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "The following error occurred attempting to join the %s: \"%s\": %s",
+ new_workgroup_type,
+ state->name_buffer_new,
+ err_str);
+
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_do_change));
+ g_signal_connect_swapped(dialog, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ dialog);
+
+ gtk_widget_show(dialog);
+
+ return;
+ }
+
+ debug("callback_do_join: Successfully joined %s\n",
+ new_workgroup_type);
+
+ callback_do_freeauth(NULL, state);
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK,
+ "Welcome to the %s %s.",
+ state->name_buffer_new,
+ new_workgroup_type);
+
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_do_change));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+
+ callback_do_reboot(NULL, state->window_parent, state);
+}
+
+static void callback_enter_hostname_and_unlock(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text = NULL;
+ char *str = NULL;
+ struct join_state *state = (struct join_state *)data;
+
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ debug("callback_enter_hostname_and_unlock: %s\n", entry_text);
+ if (!entry_text || entry_text[0] == 0) {
+ state->hostname_changed = FALSE;
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), FALSE);
+ gtk_label_set_text(GTK_LABEL(state->label_full_computer_name), "");
+ return;
+ }
+ if (strcasecmp(state->my_hostname, entry_text) == 0) {
+ state->hostname_changed = FALSE;
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), FALSE);
+ /* return; */
+ } else {
+ state->hostname_changed = TRUE;
+ }
+
+ if (state->name_type_initial == NetSetupDomainName) {
+ if (asprintf(&str, "%s.%s", entry_text, state->my_dnsdomain) == -1) {
+ return;
+ }
+ } else {
+ if (asprintf(&str, "%s.", entry_text) == -1) {
+ return;
+ }
+ }
+ gtk_label_set_text(GTK_LABEL(state->label_full_computer_name), str);
+ free(str);
+
+ if (state->hostname_changed && entry_text && entry_text[0] != 0 && entry_text[0] != '.') {
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), TRUE);
+ }
+}
+
+static void callback_enter_computer_description_and_unlock(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text = NULL;
+ struct join_state *state = (struct join_state *)data;
+ int string_unchanged = FALSE;
+
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ debug("callback_enter_computer_description_and_unlock: %s\n",
+ entry_text);
+#if 0
+ if (!entry_text || entry_text[0] == 0) {
+ string_unchanged = 1;
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_apply),
+ FALSE);
+ return;
+ }
+#endif
+ if (entry_text && state->comment && strcasecmp(state->comment, entry_text) == 0) {
+ string_unchanged = TRUE;
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_apply),
+ FALSE);
+ return;
+ }
+
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_apply), TRUE);
+ SAFE_FREE(state->comment_new);
+ state->comment_new = strdup(entry_text);
+
+}
+
+
+static void callback_enter_workgroup_and_unlock(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text = NULL;
+ struct join_state *state = (struct join_state *)data;
+
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ debug("callback_enter_workgroup_and_unlock: %s\n", entry_text);
+ if (!entry_text || entry_text[0] == 0) {
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), FALSE);
+ return;
+ }
+ if ((strcasecmp(state->name_buffer_initial, entry_text) == 0) &&
+ (state->name_type_initial == NetSetupWorkgroupName)) {
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), FALSE);
+ return;
+ }
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), TRUE);
+ SAFE_FREE(state->name_buffer_new);
+ state->name_buffer_new = strdup(entry_text);
+ state->name_type_new = NetSetupWorkgroupName;
+}
+
+static void callback_enter_domain_and_unlock(GtkWidget *widget,
+ gpointer data)
+{
+ const gchar *entry_text = NULL;
+ struct join_state *state = (struct join_state *)data;
+
+ entry_text = gtk_entry_get_text(GTK_ENTRY(widget));
+ debug("callback_enter_domain_and_unlock: %s\n", entry_text);
+ if (!entry_text || entry_text[0] == 0) {
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), FALSE);
+ return;
+ }
+ if ((strcasecmp(state->name_buffer_initial, entry_text) == 0) &&
+ (state->name_type_initial == NetSetupDomainName)) {
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), FALSE);
+ return;
+ }
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_ou_list), TRUE);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_get_ous), TRUE);
+ SAFE_FREE(state->name_buffer_new);
+ state->name_buffer_new = strdup(entry_text);
+ state->name_type_new = NetSetupDomainName;
+}
+
+static void callback_apply_continue(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+
+ gtk_widget_grab_focus(GTK_WIDGET(state->button_apply));
+ g_signal_emit_by_name(state->button_apply, "clicked");
+}
+
+static void callback_do_join_workgroup(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+ debug("callback_do_join_workgroup chosen\n");
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_workgroup), TRUE);
+ gtk_widget_grab_focus(GTK_WIDGET(state->entry_workgroup));
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_domain), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_ou_list), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_get_ous), FALSE);
+ callback_enter_workgroup_and_unlock(state->entry_workgroup, state); /* TEST */
+}
+
+static void callback_do_join_domain(GtkWidget *widget,
+ gpointer data)
+{
+ struct join_state *state = (struct join_state *)data;
+ debug("callback_do_join_domain chosen\n");
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_domain), TRUE);
+ gtk_widget_grab_focus(GTK_WIDGET(state->entry_domain));
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_workgroup), FALSE);
+ callback_enter_domain_and_unlock(state->entry_domain, state); /* TEST */
+}
+
+static void callback_do_getous(GtkWidget *widget,
+ gpointer data)
+{
+ NET_API_STATUS status;
+ uint32_t num_ous = 0;
+ const char **ous = NULL;
+ int i;
+ const char *domain = NULL;
+ struct DOMAIN_CONTROLLER_INFO *dc_info = NULL;
+ const char *err_str = NULL;
+ GtkWidget *dialog;
+
+ struct join_state *state = (struct join_state *)data;
+
+ debug("callback_do_getous called\n");
+
+ domain = state->name_buffer_new ? state->name_buffer_new : state->name_buffer_initial;
+
+ status = DsGetDcName(NULL,
+ domain,
+ NULL,
+ NULL,
+ 0,
+ &dc_info);
+ if (status != 0) {
+ err_str = libnetapi_get_error_string(state->ctx, status);
+ g_print("callback_do_getous: failed find dc (%s)\n", err_str);
+
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_parent),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "Failed to find a domain controller for domain: \"%s\": %s",
+ domain,
+ err_str);
+
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_do_change));
+ g_signal_connect_swapped(dialog, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ dialog);
+
+ gtk_widget_show(dialog);
+
+ return;
+ }
+
+ if (!state->account || !state->password) {
+ debug("callback_do_getous: no creds yet\n");
+ callback_creds_prompt(NULL, state,
+ "Enter the name and password of an account with permission to join the domain.",
+ callback_do_storeauth_and_scan);
+ }
+
+ if (!state->account || !state->password) {
+ debug("callback_do_getous: still no creds ???\n");
+ return;
+ }
+
+ status = NetGetJoinableOUs(state->target_hostname,
+ domain,
+ state->account,
+ state->password,
+ &num_ous, &ous);
+ if (status != NET_API_STATUS_SUCCESS) {
+ callback_do_freeauth(NULL, state);
+ debug("failed to call NetGetJoinableOUs: %s\n",
+ libnetapi_get_error_string(state->ctx, status));
+ dialog = gtk_message_dialog_new(NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK,
+ "Failed to query joinable OUs: %s",
+ libnetapi_get_error_string(state->ctx, status));
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(state->window_do_change));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return;
+ }
+
+ for (i=0; i<state->stored_num_ous; i++) {
+ gtk_combo_box_remove_text(GTK_COMBO_BOX(state->entry_ou_list), 0);
+ }
+ for (i=0; i<num_ous && ous[i] != NULL; i++) {
+ gtk_combo_box_append_text(GTK_COMBO_BOX(state->entry_ou_list),
+ ous[i]);
+ }
+ NetApiBufferFree(ous);
+ state->stored_num_ous = num_ous;
+ gtk_combo_box_set_active(GTK_COMBO_BOX(state->entry_ou_list), num_ous-1);
+}
+
+static void callback_do_change(GtkWidget *widget,
+ gpointer data)
+{
+ GtkWidget *window;
+ GtkWidget *box1;
+ GtkWidget *bbox;
+ GtkWidget *button_workgroup;
+ GtkWidget *button_domain;
+ GtkWidget *button;
+ GtkWidget *label;
+ GtkWidget *frame_horz;
+ GtkWidget *vbox;
+ GtkWidget *entry;
+ GSList *group;
+
+ struct join_state *state = (struct join_state *)data;
+
+ debug("callback_do_change called\n");
+
+#if 0
+ /* FIXME: add proper warnings for Samba as a DC */
+ if (state->server_role == 3) {
+ GtkWidget *dialog;
+ callback_do_freeauth(NULL, state);
+ dialog = gtk_message_dialog_new(GTK_WINDOW(state->window_main),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "Domain controller cannot be moved from one domain to another, they must first be demoted. Renaming this domain controller may cause it to become temporarily unavailable to users and computers. For information on renaming domain controllers, including alternate renaming methods, see Help and Support. To continue renaming this domain controller, click OK.");
+ gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
+ g_signal_connect_swapped(dialog, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ dialog);
+
+ gtk_widget_show(dialog);
+ return;
+ }
+#endif
+
+ state->button_ok = gtk_button_new_from_stock(GTK_STOCK_OK);
+ state->button_get_ous = gtk_button_new_with_label("Scan for joinable OUs");
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_modal(GTK_WINDOW(window), TRUE);
+
+ gtk_window_set_title(GTK_WINDOW(window), "Computer Name Changes");
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+ gtk_widget_set_size_request(GTK_WIDGET(window), 480, 650);
+ gtk_window_set_icon_from_file(GTK_WINDOW(window), SAMBA_ICON_PATH, NULL);
+ gtk_window_set_transient_for(GTK_WINDOW(window), GTK_WINDOW(state->window_main));
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS);
+
+ g_signal_connect(G_OBJECT(window), "delete_event",
+ G_CALLBACK(callback_do_close_widget), NULL);
+
+ gtk_container_set_border_width(GTK_CONTAINER(window), 10);
+
+ box1 = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(window), box1);
+
+ label = gtk_label_new("You can change the name and membership of this computer. Changes may affect access to network resources.");
+ gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_box_pack_start(GTK_BOX(box1), label, TRUE, TRUE, 0);
+ gtk_widget_show(label);
+
+ /* COMPUTER NAME */
+ label = gtk_label_new("Computer name:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_box_pack_start(GTK_BOX(box1), label, TRUE, TRUE, 0);
+ gtk_widget_show(label);
+
+ state->label_full_computer_name = gtk_label_new(NULL);
+ {
+ entry = gtk_entry_new();
+ gtk_entry_set_max_length(GTK_ENTRY(entry), MAX_NETBIOS_NAME_LEN);
+ g_signal_connect(G_OBJECT(entry), "changed",
+ G_CALLBACK(callback_enter_hostname_and_unlock),
+ (gpointer)state);
+ gtk_entry_set_text(GTK_ENTRY(entry), state->my_hostname);
+ gtk_editable_select_region(GTK_EDITABLE(entry),
+ 0, GTK_ENTRY(entry)->text_length);
+
+ gtk_editable_set_editable(GTK_EDITABLE(entry), TRUE); /* ! */
+ gtk_box_pack_start(GTK_BOX(box1), entry, TRUE, TRUE, 0);
+ gtk_widget_show(entry);
+ }
+
+ /* FULL COMPUTER NAME */
+ label = gtk_label_new("Full computer name:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_box_pack_start(GTK_BOX(box1), label, TRUE, TRUE, 0);
+ gtk_widget_show(label);
+
+ {
+ const gchar *entry_text;
+ char *str = NULL;
+ entry_text = gtk_entry_get_text(GTK_ENTRY(entry));
+ if (state->name_type_initial == NetSetupDomainName) {
+ if (asprintf(&str, "%s.%s", entry_text,
+ state->my_dnsdomain) == -1) {
+ return;
+ }
+ } else {
+ if (asprintf(&str, "%s.", entry_text) == -1) {
+ return;
+ }
+ }
+ gtk_label_set_text(GTK_LABEL(state->label_full_computer_name),
+ str);
+ free(str);
+ gtk_misc_set_alignment(GTK_MISC(state->label_full_computer_name), 0, 0);
+ gtk_box_pack_start(GTK_BOX(box1),
+ state->label_full_computer_name, TRUE, TRUE, 0);
+ gtk_widget_show(state->label_full_computer_name);
+ }
+
+ /* BOX */
+ frame_horz = gtk_frame_new ("Member Of");
+ gtk_box_pack_start(GTK_BOX(box1), frame_horz, TRUE, TRUE, 10);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
+ gtk_container_add(GTK_CONTAINER(frame_horz), vbox);
+
+ /* TWO ENTRIES */
+ state->entry_workgroup = gtk_entry_new();
+ state->entry_domain = gtk_entry_new();
+
+ /* DOMAIN */
+ button_domain = gtk_radio_button_new_with_label(NULL, "Domain");
+ if (state->name_type_initial == NetSetupDomainName) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_domain), TRUE);
+ }
+ gtk_box_pack_start(GTK_BOX(vbox), button_domain, TRUE, TRUE, 0);
+ g_signal_connect(G_OBJECT(button_domain), "clicked",
+ G_CALLBACK(callback_do_join_domain),
+ (gpointer)state);
+
+ {
+ gtk_entry_set_max_length(GTK_ENTRY(state->entry_domain), 50);
+ g_signal_connect(G_OBJECT(state->entry_domain), "changed",
+ G_CALLBACK(callback_enter_domain_and_unlock),
+ (gpointer)state);
+ g_signal_connect(G_OBJECT(state->entry_domain), "activate",
+ G_CALLBACK(callback_continue),
+ (gpointer)state);
+ if (state->name_type_initial == NetSetupDomainName) {
+ gtk_entry_set_text(GTK_ENTRY(state->entry_domain),
+ state->name_buffer_initial);
+ gtk_widget_set_sensitive(state->entry_workgroup, FALSE);
+ gtk_widget_set_sensitive(state->entry_domain, TRUE);
+ }
+ gtk_editable_set_editable(GTK_EDITABLE(state->entry_domain), TRUE);
+ gtk_box_pack_start(GTK_BOX(vbox), state->entry_domain, TRUE, TRUE, 0);
+ gtk_widget_show(state->entry_domain);
+ }
+ gtk_widget_show(button_domain);
+
+ /* WORKGROUP */
+ group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button_domain));
+ button_workgroup = gtk_radio_button_new_with_label(group, "Workgroup");
+ if (state->name_type_initial == NetSetupWorkgroupName) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_workgroup), TRUE);
+ }
+ gtk_box_pack_start(GTK_BOX(vbox), button_workgroup, TRUE, TRUE, 0);
+ g_signal_connect(G_OBJECT(button_workgroup), "clicked",
+ G_CALLBACK(callback_do_join_workgroup),
+ (gpointer)state);
+ {
+ gtk_entry_set_max_length(GTK_ENTRY(state->entry_workgroup),
+ MAX_NETBIOS_NAME_LEN);
+ g_signal_connect(G_OBJECT(state->entry_workgroup), "changed",
+ G_CALLBACK(callback_enter_workgroup_and_unlock),
+ (gpointer)state);
+ g_signal_connect(G_OBJECT(state->entry_workgroup), "activate",
+ G_CALLBACK(callback_continue),
+ (gpointer)state);
+
+ if (state->name_type_initial == NetSetupWorkgroupName) {
+ gtk_entry_set_text(GTK_ENTRY(state->entry_workgroup),
+ state->name_buffer_initial);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_domain), FALSE);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->entry_workgroup), TRUE);
+ }
+ gtk_box_pack_start(GTK_BOX(vbox), state->entry_workgroup, TRUE, TRUE, 0);
+ gtk_widget_show(state->entry_workgroup);
+ }
+ gtk_widget_show(button_workgroup);
+
+ /* Advanced Options */
+ frame_horz = gtk_frame_new("Advanced Options");
+ gtk_box_pack_start(GTK_BOX(box1), frame_horz, TRUE, TRUE, 10);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
+ gtk_container_add(GTK_CONTAINER(frame_horz), vbox);
+
+ /* OUs */
+ gtk_container_add(GTK_CONTAINER(vbox), state->button_get_ous);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_get_ous), FALSE);
+ g_signal_connect(G_OBJECT(state->button_get_ous), "clicked",
+ G_CALLBACK(callback_do_getous),
+ (gpointer)state);
+
+ state->entry_ou_list = gtk_combo_box_entry_new_text();
+ gtk_widget_set_sensitive(state->entry_ou_list, FALSE);
+ if (state->name_type_initial == NetSetupWorkgroupName) {
+ gtk_widget_set_sensitive(state->entry_ou_list, FALSE);
+ gtk_widget_set_sensitive(state->button_get_ous, FALSE);
+ }
+ gtk_box_pack_start(GTK_BOX(vbox), state->entry_ou_list, TRUE, TRUE, 0);
+ gtk_widget_show(state->entry_ou_list);
+
+ {
+ state->label_winbind = gtk_check_button_new_with_label("Modify winbind configuration");
+ gtk_box_pack_start(GTK_BOX(vbox), state->label_winbind, TRUE, TRUE, 0);
+ gtk_widget_set_sensitive(state->label_winbind, FALSE);
+ }
+
+
+ /* BUTTONS */
+ bbox = gtk_hbutton_box_new();
+ gtk_container_set_border_width(GTK_CONTAINER(bbox), 5);
+ gtk_container_add(GTK_CONTAINER(box1), bbox);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+ gtk_box_set_spacing(GTK_BOX(bbox), 10);
+
+ state->window_do_change = window;
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_ok), FALSE);
+ gtk_container_add(GTK_CONTAINER(bbox), state->button_ok);
+ g_signal_connect(G_OBJECT(state->button_ok), "clicked",
+ G_CALLBACK(callback_do_join),
+ (gpointer)state);
+
+ button = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+ gtk_container_add(GTK_CONTAINER(bbox), button);
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(callback_do_freeauth_and_close),
+ (gpointer)state);
+
+ gtk_widget_show_all(window);
+}
+
+static void callback_do_about(GtkWidget *widget,
+ gpointer data)
+{
+ GdkPixbuf *logo;
+ GError *error = NULL;
+ GtkWidget *about;
+
+ struct join_state *state = (struct join_state *)data;
+
+ debug("callback_do_about called\n");
+
+ logo = gdk_pixbuf_new_from_file(SAMBA_IMAGE_PATH,
+ &error);
+ if (logo == NULL) {
+ g_print("failed to load logo from %s: %s\n",
+ SAMBA_IMAGE_PATH, error->message);
+ }
+
+ about = gtk_about_dialog_new();
+ gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(about), "Samba");
+ gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about), "3.2.0pre3");
+ gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(about),
+ "Copyright Andrew Tridgell and the Samba Team 1992-2008\n"
+ "Copyright Günther Deschner 2007-2008");
+ gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about), "GPLv3");
+ gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about), "http://www.samba.org");
+ gtk_about_dialog_set_website_label(GTK_ABOUT_DIALOG(about), "http://www.samba.org");
+ if (logo) {
+ gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about), logo);
+ }
+ gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(about), "Samba gtk domain join utility");
+ gtk_window_set_modal(GTK_WINDOW(about), TRUE);
+ gtk_window_set_transient_for(GTK_WINDOW(about), GTK_WINDOW(state->window_main));
+ g_signal_connect_swapped(about, "response",
+ G_CALLBACK(gtk_widget_destroy),
+ about);
+
+ gtk_widget_show(about);
+}
+
+static int draw_main_window(struct join_state *state)
+{
+ GtkWidget *window;
+ GtkWidget *button;
+ GtkWidget *label;
+ GtkWidget *main_vbox;
+ GtkWidget *vbox;
+ GtkWidget *hbox;
+ GtkWidget *bbox;
+ GtkWidget *image;
+ GtkWidget *table;
+ GtkWidget *entry;
+ GdkPixbuf *icon;
+ GError *error = NULL;
+
+ icon = gdk_pixbuf_new_from_file(SAMBA_ICON_PATH,
+ &error);
+ if (icon == NULL) {
+ g_print("failed to load icon from %s : %s\n",
+ SAMBA_ICON_PATH, error->message);
+ }
+
+#if 1
+ image = gtk_image_new_from_file(SAMBA_IMAGE_PATH_SMALL);
+#else
+ image = gtk_image_new_from_file("/usr/share/pixmaps/redhat-system_settings.png");
+#endif
+ if (image == NULL) {
+ g_print("failed to load logo from %s : %s\n",
+ SAMBA_IMAGE_PATH_SMALL, error->message);
+ }
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ state->window_main = window;
+
+ gtk_window_set_title(GTK_WINDOW(window), "Samba - Join Domain dialogue");
+ gtk_widget_set_size_request(GTK_WIDGET(window), 600, 600);
+ gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
+ gtk_window_set_icon_from_file(GTK_WINDOW(window), SAMBA_ICON_PATH, NULL);
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS);
+
+ g_signal_connect(G_OBJECT(window), "delete_event",
+ G_CALLBACK(callback_delete_event), NULL);
+
+ gtk_container_set_border_width(GTK_CONTAINER(window), 10);
+
+ main_vbox = gtk_vbox_new(FALSE, 10);
+ gtk_container_add(GTK_CONTAINER(window), main_vbox);
+
+#if 0
+ gtk_box_pack_start(GTK_BOX(main_vbox), image, TRUE, TRUE, 10);
+ gtk_widget_show(image);
+#endif
+ /* Hbox */
+ hbox = gtk_hbox_new(FALSE, 10);
+ gtk_container_add(GTK_CONTAINER(main_vbox), hbox);
+
+ {
+/* gtk_box_pack_start(GTK_BOX(main_vbox), image, TRUE, TRUE, 10); */
+/* gtk_misc_set_alignment(GTK_MISC(image), 0, 0); */
+ gtk_widget_set_size_request(GTK_WIDGET(image), 150, 40);
+ gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 10);
+ gtk_widget_show(image);
+
+ /* Label */
+ label = gtk_label_new("Samba uses the following information to identify your computer on the network.");
+/* gtk_misc_set_alignment(GTK_MISC(label), 0, 0); */
+ gtk_widget_set_size_request(GTK_WIDGET(label), 400, 40);
+ gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+ gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+ gtk_widget_show(label);
+ }
+
+ gtk_widget_show(hbox);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
+ gtk_container_add(GTK_CONTAINER(main_vbox), vbox);
+
+ /* Table */
+ table = gtk_table_new(6, 3, TRUE);
+ gtk_table_set_row_spacings(GTK_TABLE(table), 5);
+ gtk_table_set_col_spacings(GTK_TABLE(table), 5);
+ gtk_container_add(GTK_CONTAINER(vbox), table);
+
+ {
+ /* Label */
+ label = gtk_label_new("Computer description:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
+ gtk_widget_show(label);
+
+ state->button_apply = gtk_button_new_from_stock(GTK_STOCK_APPLY);
+
+ /* Entry */
+ entry = gtk_entry_new();
+ gtk_entry_set_max_length(GTK_ENTRY(entry), 256);
+
+ if (!state->target_hostname && state->uid != 0) {
+ gtk_widget_set_sensitive(GTK_WIDGET(entry), FALSE);
+ }
+ g_signal_connect(G_OBJECT(entry), "changed",
+ G_CALLBACK(callback_enter_computer_description_and_unlock),
+ state);
+ g_signal_connect(G_OBJECT(entry), "activate",
+ G_CALLBACK(callback_apply_continue),
+ (gpointer)state);
+
+ gtk_entry_set_text(GTK_ENTRY(entry), (char *)state->comment);
+ gtk_editable_set_editable(GTK_EDITABLE(entry), TRUE); /* ! */
+ gtk_table_attach_defaults(GTK_TABLE(table), entry, 1, 3, 0, 1);
+ gtk_widget_show(entry);
+ }
+
+ /* Label */
+ label = gtk_label_new("For example: \"Samba \%v\".");
+ gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 3, 1, 2);
+ gtk_widget_show(label);
+
+ /* Label */
+ label = gtk_label_new("Full computer name:");
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
+ gtk_widget_show(label);
+
+ {
+ /* Label */
+ char *str = NULL;
+ if (state->name_type_initial == NetSetupDomainName) {
+ if (asprintf(&str, "%s.%s", state->my_hostname,
+ state->my_dnsdomain) == -1) {
+ return -1;
+ }
+ } else {
+ if (asprintf(&str, "%s.", state->my_hostname) == -1) {
+ return -1;
+ }
+ }
+
+ label = gtk_label_new(str);
+ SAFE_FREE(str);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 3, 2, 3);
+ gtk_widget_show(label);
+ }
+
+ /* Label */
+ if (state->name_type_initial == NetSetupDomainName) {
+ label = gtk_label_new("Domain:");
+ } else {
+ label = gtk_label_new("Workgroup:");
+ }
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
+ gtk_widget_show(label);
+ state->label_current_name_type = label;
+
+ /* Label */
+ label = gtk_label_new(state->name_buffer_initial);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
+ gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 3, 3, 4);
+ gtk_widget_show(label);
+ state->label_current_name_buffer = label;
+
+ {
+ hbox = gtk_hbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(vbox), hbox);
+ label = gtk_label_new("To rename this computer or join a domain, click Change.");
+ gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+
+
+ }
+
+ /* bbox */
+ bbox = gtk_hbutton_box_new();
+ gtk_container_set_border_width(GTK_CONTAINER(bbox), 5);
+ gtk_container_add(GTK_CONTAINER(hbox), bbox);
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+ gtk_box_set_spacing(GTK_BOX(bbox), 10);
+
+ button = gtk_button_new_with_mnemonic("Ch_ange");
+ g_signal_connect(G_OBJECT(button), "clicked",
+ G_CALLBACK(callback_do_change),
+ (gpointer)state);
+ gtk_box_pack_start(GTK_BOX(bbox), button, TRUE, TRUE, 0);
+ if (!state->target_hostname && state->uid != 0) {
+ gtk_widget_set_sensitive(GTK_WIDGET(button), FALSE);
+ }
+ gtk_widget_show(button);
+
+ /* Label (hidden) */
+ state->label_reboot = gtk_label_new(NULL);
+ gtk_label_set_line_wrap(GTK_LABEL(state->label_reboot), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(state->label_reboot), 0, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), state->label_reboot, TRUE, TRUE, 0);
+ if (!state->target_hostname && state->uid != 0) {
+ gtk_label_set_text(GTK_LABEL(state->label_reboot),
+ "You cannot change computer description as you're not running with root permissions");
+ }
+
+ gtk_widget_show(state->label_reboot);
+
+#if 0
+ gtk_box_pack_start(GTK_BOX(vbox),
+ create_bbox(window, TRUE, NULL, 10, 85, 20, GTK_BUTTONBOX_END),
+ TRUE, TRUE, 5);
+#endif
+ {
+
+ GtkWidget *frame;
+ GtkWidget *bbox2;
+ GtkWidget *button2;
+
+ frame = gtk_frame_new(NULL);
+ bbox2 = gtk_hbutton_box_new();
+
+ gtk_container_set_border_width(GTK_CONTAINER(bbox2), 5);
+ gtk_container_add(GTK_CONTAINER(frame), bbox2);
+
+ /* Set the appearance of the Button Box */
+ gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox2), GTK_BUTTONBOX_END);
+ gtk_box_set_spacing(GTK_BOX(bbox2), 10);
+ /*gtk_button_box_set_child_size(GTK_BUTTON_BOX(bbox2), child_w, child_h);*/
+
+ button2 = gtk_button_new_from_stock(GTK_STOCK_OK);
+ gtk_container_add(GTK_CONTAINER(bbox2), button2);
+ g_signal_connect(G_OBJECT(button2), "clicked", G_CALLBACK(callback_do_exit), state);
+
+ button2 = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
+ gtk_container_add(GTK_CONTAINER(bbox2), button2);
+ g_signal_connect(G_OBJECT(button2), "clicked",
+ G_CALLBACK(callback_delete_event),
+ window);
+
+ gtk_container_add(GTK_CONTAINER(bbox2), state->button_apply);
+ g_signal_connect(G_OBJECT(state->button_apply), "clicked",
+ G_CALLBACK(callback_apply_description_change),
+ state);
+ gtk_widget_set_sensitive(GTK_WIDGET(state->button_apply), FALSE);
+
+ button2 = gtk_button_new_from_stock(GTK_STOCK_ABOUT);
+ gtk_container_add(GTK_CONTAINER(bbox2), button2);
+ g_signal_connect(G_OBJECT(button2), "clicked",
+ G_CALLBACK(callback_do_about),
+ state);
+#if 0
+ button2 = gtk_button_new_from_stock(GTK_STOCK_HELP);
+ gtk_container_add(GTK_CONTAINER(bbox2), button2);
+ g_signal_connect(G_OBJECT(button2), "clicked",
+ G_CALLBACK(callback_do_about),
+ window);
+#endif
+ gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 5);
+ }
+
+ gtk_widget_show_all(window);
+
+ return 0;
+}
+
+static int init_join_state(struct join_state **state)
+{
+ struct join_state *s;
+
+ s = (struct join_state *)malloc(sizeof(struct join_state));
+ if (!s) {
+ return -1;
+ }
+
+ memset(s, '\0', sizeof(struct join_state));
+
+ *state = s;
+
+ return 0;
+}
+
+static NET_API_STATUS get_server_properties(struct join_state *state)
+{
+ struct SERVER_INFO_101 *info101 = NULL;
+ struct SERVER_INFO_1005 *info1005 = NULL;
+ NET_API_STATUS status;
+
+ status = NetServerGetInfo(state->target_hostname,
+ 101,
+ (uint8_t **)&info101);
+ if (status == 0) {
+ state->comment = strdup(info101->sv101_comment);
+ if (!state->comment) {
+ return -1;
+ }
+ SAFE_FREE(state->my_hostname);
+ state->my_hostname = strdup(info101->sv101_name);
+ if (!state->my_hostname) {
+ return -1;
+ }
+ NetApiBufferFree(info101);
+ return NET_API_STATUS_SUCCESS;
+ }
+
+ switch (status) {
+ case 124: /* WERR_INVALID_LEVEL */
+ case 50: /* WERR_NOT_SUPPORTED */
+ break;
+ default:
+ goto failed;
+ }
+
+ status = NetServerGetInfo(state->target_hostname,
+ 1005,
+ (uint8_t **)&info1005);
+ if (status == 0) {
+ state->comment = strdup(info1005->sv1005_comment);
+ if (!state->comment) {
+ return -1;
+ }
+ NetApiBufferFree(info1005);
+ return NET_API_STATUS_SUCCESS;
+ }
+
+ failed:
+ printf("NetServerGetInfo failed with: %s\n",
+ libnetapi_get_error_string(state->ctx, status));
+
+ return status;
+}
+
+static int initialize_join_state(struct join_state *state,
+ const char *debug_level,
+ const char *target_hostname,
+ const char *target_username)
+{
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status = 0;
+
+ status = libnetapi_init(&ctx);
+ if (status) {
+ return status;
+ }
+
+ if (debug_level) {
+ libnetapi_set_debuglevel(ctx, debug_level);
+ }
+
+ if (target_hostname) {
+ state->target_hostname = strdup(target_hostname);
+ if (!state->target_hostname) {
+ return -1;
+ }
+ }
+
+ if (target_username) {
+ char *puser = strdup(target_username);
+ char *p = NULL;
+
+ if ((p = strchr(puser,'%'))) {
+ size_t len;
+ *p = 0;
+ libnetapi_set_username(ctx, puser);
+ libnetapi_set_password(ctx, p+1);
+ len = strlen(p+1);
+ memset(strchr(target_username,'%')+1,'X',len);
+ } else {
+ libnetapi_set_username(ctx, puser);
+ }
+ free(puser);
+ }
+
+ {
+ char my_hostname[HOST_NAME_MAX];
+ const char *p = NULL;
+ struct hostent *hp = NULL;
+
+ if (gethostname(my_hostname, sizeof(my_hostname)) == -1) {
+ return -1;
+ }
+
+ p = strchr(my_hostname, '.');
+ if (p) {
+ my_hostname[strlen(my_hostname)-strlen(p)] = '\0';
+ }
+ state->my_hostname = strdup(my_hostname);
+ if (!state->my_hostname) {
+ return -1;
+ }
+ debug("state->my_hostname: %s\n", state->my_hostname);
+
+ hp = gethostbyname(my_hostname);
+ if (!hp || !hp->h_name || !*hp->h_name) {
+ return -1;
+ }
+
+ state->my_fqdn = strdup(hp->h_name);
+ if (!state->my_fqdn) {
+ return -1;
+ }
+ debug("state->my_fqdn: %s\n", state->my_fqdn);
+
+ p = strchr(state->my_fqdn, '.');
+ if (p) {
+ p++;
+ state->my_dnsdomain = strdup(p);
+ } else {
+ state->my_dnsdomain = strdup("");
+ }
+ if (!state->my_dnsdomain) {
+ return -1;
+ }
+ debug("state->my_dnsdomain: %s\n", state->my_dnsdomain);
+ }
+
+ {
+ const char *buffer = NULL;
+ uint16_t type = 0;
+ status = NetGetJoinInformation(state->target_hostname,
+ &buffer,
+ &type);
+ if (status != 0) {
+ printf("NetGetJoinInformation failed with: %s\n",
+ libnetapi_get_error_string(state->ctx, status));
+ return status;
+ }
+ debug("NetGetJoinInformation gave: %s and %d\n", buffer, type);
+ state->name_buffer_initial = strdup(buffer);
+ if (!state->name_buffer_initial) {
+ return -1;
+ }
+ state->name_type_initial = type;
+ NetApiBufferFree((void *)buffer);
+ }
+
+ status = get_server_properties(state);
+ if (status != 0) {
+ return -1;
+ }
+
+ state->uid = geteuid();
+
+ state->ctx = ctx;
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ GOptionContext *context = NULL;
+ static const char *debug_level = NULL;
+ static const char *target_hostname = NULL;
+ static const char *target_username = NULL;
+ struct join_state *state = NULL;
+ GError *error = NULL;
+ int ret = 0;
+
+ static GOptionEntry entries[] = {
+ { "debug", 'd', 0, G_OPTION_ARG_STRING, &debug_level, "Debug level (for samba)", "N" },
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Verbose output", 0 },
+ { "target", 'S', 0, G_OPTION_ARG_STRING, &target_hostname, "Target hostname", 0 },
+ { "username", 'U', 0, G_OPTION_ARG_STRING, &target_username, "Target hostname", 0 },
+ { NULL }
+ };
+
+ context = g_option_context_new("- Samba domain join utility");
+ g_option_context_add_main_entries(context, entries, NULL);
+/* g_option_context_add_main_entries(context, entries, GETTEXT_PACKAGE); */
+ g_option_context_add_group(context, gtk_get_option_group(TRUE));
+ g_option_context_parse(context, &argc, &argv, &error);
+
+ gtk_init(&argc, &argv);
+ g_set_application_name("Samba");
+
+ ret = init_join_state(&state);
+ if (ret) {
+ return ret;
+ }
+
+ ret = initialize_join_state(state, debug_level,
+ target_hostname,
+ target_username);
+ if (ret) {
+ return ret;
+ }
+
+ draw_main_window(state);
+
+ gtk_main();
+
+ do_cleanup(state);
+
+ return 0;
+}
diff --git a/source3/lib/netapi/examples/netdomjoin-gui/samba.ico b/source3/lib/netapi/examples/netdomjoin-gui/samba.ico
new file mode 100755
index 0000000..b70c959
--- /dev/null
+++ b/source3/lib/netapi/examples/netdomjoin-gui/samba.ico
Binary files differ
diff --git a/source3/lib/netapi/examples/netlogon/netlogon_control.c b/source3/lib/netapi/examples/netlogon/netlogon_control.c
new file mode 100644
index 0000000..34361cd
--- /dev/null
+++ b/source3/lib/netapi/examples/netlogon/netlogon_control.c
@@ -0,0 +1,143 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * I_NetLogonControl query
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t function_code = NETLOGON_CONTROL_QUERY;
+ uint32_t level = 1;
+ uint8_t *buffer = NULL;
+ struct NETLOGON_INFO_1 *i1 = NULL;
+ struct NETLOGON_INFO_2 *i2 = NULL;
+ struct NETLOGON_INFO_3 *i3 = NULL;
+ struct NETLOGON_INFO_4 *i4 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("netlogon_control", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ function_code = atoi(poptGetArg(pc));
+ }
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* I_NetLogonControl */
+
+ status = I_NetLogonControl(hostname,
+ function_code,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("I_NetLogonControl failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ if (!buffer) {
+ goto out;
+ }
+
+ switch (level) {
+ case 1:
+ i1 = (struct NETLOGON_INFO_1 *)buffer;
+
+ printf("Flags: %x\n", i1->netlog1_flags);
+ printf("Connection Status Status = %d 0x%x %s\n",
+ i1->netlog1_pdc_connection_status,
+ i1->netlog1_pdc_connection_status,
+ libnetapi_errstr(i1->netlog1_pdc_connection_status));
+
+ break;
+ case 2:
+ i2 = (struct NETLOGON_INFO_2 *)buffer;
+
+ printf("Flags: %x\n", i2->netlog2_flags);
+ printf("Trusted DC Name %s\n", i2->netlog2_trusted_dc_name);
+ printf("Trusted DC Connection Status Status = %d 0x%x %s\n",
+ i2->netlog2_tc_connection_status,
+ i2->netlog2_tc_connection_status,
+ libnetapi_errstr(i2->netlog2_tc_connection_status));
+ printf("Trust Verification Status Status = %d 0x%x %s\n",
+ i2->netlog2_pdc_connection_status,
+ i2->netlog2_pdc_connection_status,
+ libnetapi_errstr(i2->netlog2_pdc_connection_status));
+
+ break;
+ case 3:
+ i3 = (struct NETLOGON_INFO_3 *)buffer;
+
+ printf("Flags: %x\n", i3->netlog1_flags);
+ printf("Logon Attempts: %d\n", i3->netlog3_logon_attempts);
+
+ break;
+ case 4:
+ i4 = (struct NETLOGON_INFO_4 *)buffer;
+
+ printf("Trusted DC Name %s\n", i4->netlog4_trusted_dc_name);
+ printf("Trusted Domain Name %s\n", i4->netlog4_trusted_domain_name);
+
+ break;
+ default:
+ break;
+ }
+
+ out:
+ NetApiBufferFree(buffer);
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/netlogon/netlogon_control2.c b/source3/lib/netapi/examples/netlogon/netlogon_control2.c
new file mode 100644
index 0000000..72315c9
--- /dev/null
+++ b/source3/lib/netapi/examples/netlogon/netlogon_control2.c
@@ -0,0 +1,147 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * I_NetLogonControl2 query
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t function_code = NETLOGON_CONTROL_QUERY;
+ uint32_t level = 1;
+ uint8_t *buffer = NULL;
+ struct NETLOGON_INFO_1 *i1 = NULL;
+ struct NETLOGON_INFO_2 *i2 = NULL;
+ struct NETLOGON_INFO_3 *i3 = NULL;
+ struct NETLOGON_INFO_4 *i4 = NULL;
+ char *domain = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("netlogon_control", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ function_code = atoi(poptGetArg(pc));
+ }
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ domain = strdup("TEST");
+
+ /* I_NetLogonControl2 */
+
+ status = I_NetLogonControl2(hostname,
+ function_code,
+ level,
+ (uint8_t *)domain,
+ &buffer);
+ if (status != 0) {
+ printf("I_NetLogonControl2 failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ if (!buffer) {
+ goto out;
+ }
+
+ switch (level) {
+ case 1:
+ i1 = (struct NETLOGON_INFO_1 *)buffer;
+
+ printf("Flags: %x\n", i1->netlog1_flags);
+ printf("Connection Status Status = %d 0x%x %s\n",
+ i1->netlog1_pdc_connection_status,
+ i1->netlog1_pdc_connection_status,
+ libnetapi_errstr(i1->netlog1_pdc_connection_status));
+
+ break;
+ case 2:
+ i2 = (struct NETLOGON_INFO_2 *)buffer;
+
+ printf("Flags: %x\n", i2->netlog2_flags);
+ printf("Trusted DC Name %s\n", i2->netlog2_trusted_dc_name);
+ printf("Trusted DC Connection Status Status = %d 0x%x %s\n",
+ i2->netlog2_tc_connection_status,
+ i2->netlog2_tc_connection_status,
+ libnetapi_errstr(i2->netlog2_tc_connection_status));
+ printf("Trust Verification Status Status = %d 0x%x %s\n",
+ i2->netlog2_pdc_connection_status,
+ i2->netlog2_pdc_connection_status,
+ libnetapi_errstr(i2->netlog2_pdc_connection_status));
+
+ break;
+ case 3:
+ i3 = (struct NETLOGON_INFO_3 *)buffer;
+
+ printf("Flags: %x\n", i3->netlog1_flags);
+ printf("Logon Attempts: %d\n", i3->netlog3_logon_attempts);
+
+ break;
+ case 4:
+ i4 = (struct NETLOGON_INFO_4 *)buffer;
+
+ printf("Trusted DC Name %s\n", i4->netlog4_trusted_dc_name);
+ printf("Trusted Domain Name %s\n", i4->netlog4_trusted_domain_name);
+
+ break;
+ default:
+ break;
+ }
+
+ out:
+ NetApiBufferFree(buffer);
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/netlogon/nltest.c b/source3/lib/netapi/examples/netlogon/nltest.c
new file mode 100644
index 0000000..2dd368f
--- /dev/null
+++ b/source3/lib/netapi/examples/netlogon/nltest.c
@@ -0,0 +1,677 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Nltest netlogon testing tool
+ *
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+enum {
+ OPT_SERVER = 1,
+ OPT_DBFLAG,
+ OPT_SC_QUERY,
+ OPT_SC_RESET,
+ OPT_SC_VERIFY,
+ OPT_SC_CHANGE_PWD,
+ OPT_DSGETDC,
+ OPT_PDC,
+ OPT_DS,
+ OPT_DSP,
+ OPT_GC,
+ OPT_KDC,
+ OPT_TIMESERV,
+ OPT_GTIMESERV,
+ OPT_WS,
+ OPT_NETBIOS,
+ OPT_DNS,
+ OPT_IP,
+ OPT_FORCE,
+ OPT_WRITABLE,
+ OPT_AVOIDSELF,
+ OPT_LDAPONLY,
+ OPT_BACKG,
+ OPT_DS_6,
+ OPT_TRY_NEXT_CLOSEST_SITE,
+ OPT_SITE,
+ OPT_ACCOUNT,
+ OPT_RET_DNS,
+ OPT_RET_NETBIOS,
+ OPT_DSREGDNS
+};
+
+/****************************************************************
+****************************************************************/
+
+static void print_netlogon_info_result(uint32_t level,
+ uint8_t *buffer)
+{
+ struct NETLOGON_INFO_1 *i1 = NULL;
+ struct NETLOGON_INFO_2 *i2 = NULL;
+ struct NETLOGON_INFO_3 *i3 = NULL;
+ struct NETLOGON_INFO_4 *i4 = NULL;
+
+ if (!buffer) {
+ return;
+ }
+
+ switch (level) {
+ case 1:
+ i1 = (struct NETLOGON_INFO_1 *)buffer;
+
+ printf("Flags: %x\n", i1->netlog1_flags);
+ printf("Connection Status Status = %d 0x%x %s\n",
+ i1->netlog1_pdc_connection_status,
+ i1->netlog1_pdc_connection_status,
+ libnetapi_errstr(i1->netlog1_pdc_connection_status));
+
+ break;
+ case 2:
+ i2 = (struct NETLOGON_INFO_2 *)buffer;
+
+ printf("Flags: %x\n", i2->netlog2_flags);
+ printf("Trusted DC Name %s\n", i2->netlog2_trusted_dc_name);
+ printf("Trusted DC Connection Status Status = %d 0x%x %s\n",
+ i2->netlog2_tc_connection_status,
+ i2->netlog2_tc_connection_status,
+ libnetapi_errstr(i2->netlog2_tc_connection_status));
+ printf("Trust Verification Status Status = %d 0x%x %s\n",
+ i2->netlog2_pdc_connection_status,
+ i2->netlog2_pdc_connection_status,
+ libnetapi_errstr(i2->netlog2_pdc_connection_status));
+
+ break;
+ case 3:
+ i3 = (struct NETLOGON_INFO_3 *)buffer;
+
+ printf("Flags: %x\n", i3->netlog1_flags);
+ printf("Logon Attempts: %d\n", i3->netlog3_logon_attempts);
+
+ break;
+ case 4:
+ i4 = (struct NETLOGON_INFO_4 *)buffer;
+
+ printf("Trusted DC Name %s\n", i4->netlog4_trusted_dc_name);
+ printf("Trusted Domain Name %s\n", i4->netlog4_trusted_domain_name);
+
+ break;
+ default:
+ break;
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static void print_dc_info_flags(uint32_t flags)
+{
+ if (flags & DS_PDC_FLAG)
+ printf("PDC ");
+ if (flags & DS_GC_FLAG)
+ printf("GC ");
+ if (flags & DS_DS_FLAG)
+ printf("DS ");
+ if (flags & DS_LDAP_FLAG)
+ printf("LDAP ");
+ if (flags & DS_KDC_FLAG)
+ printf("KDC ");
+ if (flags & DS_TIMESERV_FLAG)
+ printf("TIMESERV ");
+ if (flags & DS_GOOD_TIMESERV_FLAG)
+ printf("GTIMESERV ");
+ if (flags & DS_WRITABLE_FLAG)
+ printf("WRITABLE ");
+ if (flags & DS_DNS_FOREST_FLAG)
+ printf("DNS_FOREST ");
+ if (flags & DS_CLOSEST_FLAG)
+ printf("CLOSE_SITE ");
+ if (flags & DS_FULL_SECRET_DOMAIN_6_FLAG)
+ printf("FULL_SECRET ");
+ if (flags & DS_WS_FLAG)
+ printf("WS ");
+ if (flags & DS_DS_8_FLAG)
+ printf("DS_8 ");
+ printf("\n");
+}
+
+/****************************************************************
+****************************************************************/
+
+static void print_dc_info(struct DOMAIN_CONTROLLER_INFO *dc_info)
+{
+ if (dc_info->flags) {
+ printf(" DC: %s\n", dc_info->domain_controller_name);
+ printf(" Address: %s\n", dc_info->domain_controller_address);
+/* printf(" Dom Guid: %s\n", X(domain_guid)); */
+ printf(" Dom Name: %s\n", dc_info->domain_name);
+ printf(" Forest Name: %s\n", dc_info->dns_forest_name);
+ printf(" Dc Site Name: %s\n", dc_info->dc_site_name);
+ printf("Our Site Name: %s\n", dc_info->client_site_name);
+ printf(" Flags: ");
+ print_dc_info_flags(dc_info->flags);
+ } else {
+ printf(" DC: %s\n", dc_info->domain_controller_name);
+ printf(" Address: %s\n", dc_info->domain_controller_address);
+ printf(" Dom Name: %s\n", dc_info->domain_name);
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+int main(int argc, const char **argv)
+{
+ int opt;
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ char *opt_server = NULL;
+ char *opt_domain = NULL;
+ int opt_dbflag = 0;
+ int opt_pdc = 0;
+ int opt_ds = 0;
+ int opt_dsp = 0;
+ int opt_gc = 0;
+ int opt_kdc = 0;
+ int opt_timeserv = 0;
+ int opt_gtimeserv = 0;
+ int opt_ws = 0;
+ int opt_netbios = 0;
+ int opt_dns = 0;
+ int opt_ip = 0;
+ int opt_force = 0;
+ int opt_writable = 0;
+ int opt_avoidself = 0;
+ int opt_ldaponly = 0;
+ int opt_backg = 0;
+ int opt_ds_6 = 0;
+ int opt_try_next_closest_site = 0;
+ char *opt_site = NULL;
+ char *opt_account = NULL;
+ int opt_ret_dns = 0;
+ int opt_ret_netbios = 0;
+ int opt_dsregdns = 0;
+ uint32_t query_level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t flags = 0;
+ struct DOMAIN_CONTROLLER_INFO *dc_info = NULL;
+
+ poptContext pc;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "server",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_server,
+ .val = OPT_SERVER,
+ .descrip = "Servername",
+ .argDescrip = "SERVER",
+ },
+ {
+ .longName = "dbflag",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &opt_dbflag,
+ .val = OPT_DBFLAG,
+ .descrip = "New Debug Flag",
+ .argDescrip = "HEXFLAGS",
+ },
+ {
+ .longName = "sc_query",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_domain,
+ .val = OPT_SC_QUERY,
+ .descrip = "Query secure channel for domain on server",
+ .argDescrip = "DOMAIN",
+ },
+ {
+ .longName = "sc_reset",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_domain,
+ .val = OPT_SC_RESET,
+ .descrip = "Reset secure channel for domain on server to dcname",
+ .argDescrip = "DOMAIN",
+ },
+ {
+ .longName = "sc_verify",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_domain,
+ .val = OPT_SC_VERIFY,
+ .descrip = "Verify secure channel for domain on server",
+ .argDescrip = "DOMAIN",
+ },
+ {
+ .longName = "sc_change_pwd",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_domain,
+ .val = OPT_SC_CHANGE_PWD,
+ .descrip = "Change a secure channel password for domain on server",
+ .argDescrip = "DOMAIN",
+ },
+ {
+ .longName = "dsgetdc",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_domain,
+ .val = OPT_DSGETDC,
+ .descrip = "Call DsGetDcName",
+ .argDescrip = "DOMAIN",
+ },
+ {
+ .longName = "pdc",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_pdc,
+ .val = OPT_PDC,
+ .descrip = NULL,
+ },
+ {
+ .longName = "ds",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_ds,
+ .val = OPT_DS,
+ .descrip = NULL,
+ },
+ {
+ .longName = "dsp",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_dsp,
+ .val = OPT_DSP,
+ .descrip = NULL,
+ },
+ {
+ .longName = "gc",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_gc,
+ .val = OPT_GC,
+ .descrip = NULL,
+ },
+ {
+ .longName = "kdc",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_kdc,
+ .val = OPT_KDC,
+ .descrip = NULL,
+ },
+ {
+ .longName = "timeserv",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_timeserv,
+ .val = OPT_TIMESERV,
+ .descrip = NULL,
+ },
+ {
+ .longName = "gtimeserv",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_gtimeserv,
+ .val = OPT_GTIMESERV,
+ .descrip = NULL,
+ },
+ {
+ .longName = "ws",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_ws,
+ .val = OPT_WS,
+ .descrip = NULL,
+ },
+ {
+ .longName = "netbios",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_netbios,
+ .val = OPT_NETBIOS,
+ .descrip = NULL,
+ },
+ {
+ .longName = "dns",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_dns,
+ .val = OPT_DNS,
+ .descrip = NULL,
+ },
+ {
+ .longName = "ip",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_ip,
+ .val = OPT_IP,
+ .descrip = NULL,
+ },
+ {
+ .longName = "force",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_force,
+ .val = OPT_FORCE,
+ .descrip = NULL,
+ },
+ {
+ .longName = "writable",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_writable,
+ .val = OPT_WRITABLE,
+ .descrip = NULL,
+ },
+ {
+ .longName = "avoidself",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_avoidself,
+ .val = OPT_AVOIDSELF,
+ .descrip = NULL,
+ },
+ {
+ .longName = "ldaponly",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_ldaponly,
+ .val = OPT_LDAPONLY,
+ .descrip = NULL,
+ },
+ {
+ .longName = "backg",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_backg,
+ .val = OPT_BACKG,
+ .descrip = NULL,
+ },
+ {
+ .longName = "ds_6",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_ds_6,
+ .val = OPT_DS_6,
+ .descrip = NULL,
+ },
+ {
+ .longName = "try_next_closest_site",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_try_next_closest_site,
+ .val = OPT_TRY_NEXT_CLOSEST_SITE,
+ .descrip = NULL,
+ },
+ {
+ .longName = "site",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_site,
+ .val = OPT_SITE,
+ .descrip = "SITE",
+ },
+ {
+ .longName = "account",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_account,
+ .val = OPT_ACCOUNT,
+ .descrip = "ACCOUNT",
+ },
+ {
+ .longName = "ret_dns",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_ret_dns,
+ .val = OPT_RET_DNS,
+ .descrip = NULL,
+ },
+ {
+ .longName = "ret_netbios",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_ret_netbios,
+ .val = OPT_RET_NETBIOS,
+ .descrip = NULL,
+ },
+ {
+ .longName = "dsregdns",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_dsregdns,
+ .val = OPT_DSREGDNS,
+ .descrip = "Force registration of all DC-specific DNS records",
+ },
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("nltest", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "<options>");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (argc == 1) {
+ poptPrintHelp(pc, stderr, 0);
+ goto done;
+ }
+
+ poptResetContext(pc);
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+
+ case OPT_SERVER:
+
+ if ((opt_server[0] == '/' && opt_server[1] == '/') ||
+ (opt_server[0] == '\\' && opt_server[1] == '\\')) {
+ opt_server += 2;
+ }
+
+ break;
+
+ case OPT_DBFLAG:
+ query_level = 1;
+ status = I_NetLogonControl2(opt_server,
+ NETLOGON_CONTROL_SET_DBFLAG,
+ query_level,
+ (uint8_t *)&opt_dbflag,
+ &buffer);
+ if (status != 0) {
+ fprintf(stderr, "I_NetlogonControl failed: Status = %d 0x%x %s\n",
+ status, status,
+ libnetapi_get_error_string(ctx, status));
+ goto done;
+ }
+
+ print_netlogon_info_result(query_level, buffer);
+
+ break;
+ case OPT_SC_QUERY:
+ query_level = 2;
+ status = I_NetLogonControl2(opt_server,
+ NETLOGON_CONTROL_TC_QUERY,
+ query_level,
+ (uint8_t *)opt_domain,
+ &buffer);
+ if (status != 0) {
+ fprintf(stderr, "I_NetlogonControl failed: Status = %d 0x%x %s\n",
+ status, status,
+ libnetapi_get_error_string(ctx, status));
+ goto done;
+ }
+
+ print_netlogon_info_result(query_level, buffer);
+
+ break;
+ case OPT_SC_VERIFY:
+ query_level = 2;
+ status = I_NetLogonControl2(opt_server,
+ NETLOGON_CONTROL_TC_VERIFY,
+ query_level,
+ (uint8_t *)opt_domain,
+ &buffer);
+ if (status != 0) {
+ fprintf(stderr, "I_NetlogonControl failed: Status = %d 0x%x %s\n",
+ status, status,
+ libnetapi_get_error_string(ctx, status));
+ goto done;
+ }
+
+ print_netlogon_info_result(query_level, buffer);
+
+ break;
+ case OPT_SC_RESET:
+ query_level = 2;
+ status = I_NetLogonControl2(opt_server,
+ NETLOGON_CONTROL_REDISCOVER,
+ query_level,
+ (uint8_t *)opt_domain,
+ &buffer);
+ if (status != 0) {
+ fprintf(stderr, "I_NetlogonControl failed: Status = %d 0x%x %s\n",
+ status, status,
+ libnetapi_get_error_string(ctx, status));
+ goto done;
+ }
+
+ print_netlogon_info_result(query_level, buffer);
+
+ break;
+ case OPT_SC_CHANGE_PWD:
+ query_level = 1;
+ status = I_NetLogonControl2(opt_server,
+ NETLOGON_CONTROL_CHANGE_PASSWORD,
+ query_level,
+ (uint8_t *)opt_domain,
+ &buffer);
+ if (status != 0) {
+ fprintf(stderr, "I_NetlogonControl failed: Status = %d 0x%x %s\n",
+ status, status,
+ libnetapi_get_error_string(ctx, status));
+ goto done;
+ }
+
+ print_netlogon_info_result(query_level, buffer);
+
+ break;
+ case OPT_DSREGDNS:
+ query_level = 1;
+ status = I_NetLogonControl2(opt_server,
+ NETLOGON_CONTROL_FORCE_DNS_REG,
+ query_level,
+ NULL,
+ &buffer);
+ if (status != 0) {
+ fprintf(stderr, "I_NetlogonControl failed: Status = %d 0x%x %s\n",
+ status, status,
+ libnetapi_get_error_string(ctx, status));
+ goto done;
+ }
+
+ print_netlogon_info_result(query_level, buffer);
+
+ break;
+ case OPT_DSGETDC:
+ if (opt_pdc)
+ flags |= DS_PDC_REQUIRED;
+ if (opt_ds)
+ flags |= DS_DIRECTORY_SERVICE_REQUIRED;
+ if (opt_dsp)
+ flags |= DS_DIRECTORY_SERVICE_PREFERRED;
+ if (opt_kdc)
+ flags |= DS_KDC_REQUIRED;
+ if (opt_timeserv)
+ flags |= DS_TIMESERV_REQUIRED;
+ if (opt_gtimeserv)
+ flags |= DS_GOOD_TIMESERV_PREFERRED;
+ if (opt_ws)
+ flags |= DS_WEB_SERVICE_REQUIRED;
+ if (opt_netbios)
+ flags |= DS_IS_FLAT_NAME;
+ if (opt_dns)
+ flags |= DS_IS_DNS_NAME;
+ if (opt_ip)
+ flags |= DS_IP_REQUIRED;
+ if (opt_force)
+ flags |= DS_FORCE_REDISCOVERY;
+ if (opt_writable)
+ flags |= DS_WRITABLE_REQUIRED;
+ if (opt_avoidself)
+ flags |= DS_AVOID_SELF;
+ if (opt_ldaponly)
+ flags |= DS_ONLY_LDAP_NEEDED;
+ if (opt_backg)
+ flags |= DS_BACKGROUND_ONLY;
+ if (opt_ds_6)
+ flags |= DS_DIRECTORY_SERVICE_6_REQUIRED;
+ if (opt_try_next_closest_site)
+ flags |= DS_TRY_NEXTCLOSEST_SITE;
+ if (opt_ret_dns)
+ flags |= DS_RETURN_DNS_NAME;
+ if (opt_ret_netbios)
+ flags |= DS_RETURN_FLAT_NAME;
+
+ status = DsGetDcName(opt_server,
+ opt_domain,
+ NULL, /* domain_guid */
+ opt_site,
+ flags,
+ &dc_info);
+ if (status != 0) {
+ fprintf(stderr, "DsGetDcName failed: Status = %d 0x%x %s\n",
+ status, status,
+ libnetapi_get_error_string(ctx, status));
+ goto done;
+ }
+
+ print_dc_info(dc_info);
+
+ break;
+ default:
+ continue;
+ }
+ }
+
+ printf("The command completed successfully\n");
+ status = 0;
+
+ done:
+
+ printf("\n");
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/server/remote_tod.c b/source3/lib/netapi/examples/server/remote_tod.c
new file mode 100644
index 0000000..7636f6a
--- /dev/null
+++ b/source3/lib/netapi/examples/server/remote_tod.c
@@ -0,0 +1,83 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetRemoteTOD query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ struct TIME_OF_DAY_INFO *tod = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("tod", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+
+ /* NetRemoteTOD */
+
+ status = NetRemoteTOD(hostname,
+ (uint8_t **)&tod);
+ if (status != 0) {
+ printf("NetRemoteTOD failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ } else {
+ printf("%d-%d-%d %d:%d:%d\n",
+ tod->tod_day, tod->tod_month, tod->tod_year,
+ tod->tod_hours, tod->tod_mins, tod->tod_secs);
+ NetApiBufferFree(tod);
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/server/server_getinfo.c b/source3/lib/netapi/examples/server/server_getinfo.c
new file mode 100644
index 0000000..7836faa
--- /dev/null
+++ b/source3/lib/netapi/examples/server/server_getinfo.c
@@ -0,0 +1,136 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetServerGetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 100;
+
+ struct SERVER_INFO_100 *i100;
+ struct SERVER_INFO_101 *i101;
+ struct SERVER_INFO_102 *i102;
+ struct SERVER_INFO_1005 *i1005;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("server_getinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetServerGetInfo */
+
+ status = NetServerGetInfo(hostname,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("NetServerGetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ switch (level) {
+ case 100:
+ i100 = (struct SERVER_INFO_100 *)buffer;
+ printf("platform id: %d\n", i100->sv100_platform_id);
+ printf("name: %s\n", i100->sv100_name);
+ break;
+ case 101:
+ i101 = (struct SERVER_INFO_101 *)buffer;
+ printf("platform id: %d\n", i101->sv101_platform_id);
+ printf("name: %s\n", i101->sv101_name);
+ printf("version major: %d\n", i101->sv101_version_major);
+ printf("version minor: %d\n", i101->sv101_version_minor);
+ printf("type: 0x%08x\n", i101->sv101_type);
+ printf("comment: %s\n", i101->sv101_comment);
+ break;
+ case 102:
+ i102 = (struct SERVER_INFO_102 *)buffer;
+ printf("platform id: %d\n", i102->sv102_platform_id);
+ printf("name: %s\n", i102->sv102_name);
+ printf("version major: %d\n", i102->sv102_version_major);
+ printf("version minor: %d\n", i102->sv102_version_minor);
+ printf("type: 0x%08x\n", i102->sv102_type);
+ printf("comment: %s\n", i102->sv102_comment);
+ printf("users: %d\n", i102->sv102_users);
+ printf("disc: %d\n", i102->sv102_disc);
+ printf("hidden: %d\n", i102->sv102_hidden);
+ printf("announce: %d\n", i102->sv102_announce);
+ printf("anndelta: %d\n", i102->sv102_anndelta);
+ printf("licenses: %d\n", i102->sv102_licenses);
+ printf("userpath: %s\n", i102->sv102_userpath);
+ break;
+ case 402:
+ break;
+ case 403:
+ break;
+ case 502:
+ break;
+ case 503:
+ break;
+ case 1005:
+ i1005 = (struct SERVER_INFO_1005 *)buffer;
+ printf("comment: %s\n", i1005->sv1005_comment);
+ break;
+ default:
+ break;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/share/share_add.c b/source3/lib/netapi/examples/share/share_add.c
new file mode 100644
index 0000000..760381a
--- /dev/null
+++ b/source3/lib/netapi/examples/share/share_add.c
@@ -0,0 +1,105 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShareAdd query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *sharename = NULL;
+ const char *path = NULL;
+ uint32_t parm_err = 0;
+
+ struct SHARE_INFO_2 i2;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("share_add", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname sharename path");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ sharename = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ path = poptGetArg(pc);
+
+ /* NetShareAdd */
+
+ i2.shi2_netname = sharename;
+ i2.shi2_type = 0;
+ i2.shi2_remark = "Test share created via NetApi";
+ i2.shi2_permissions = 0;
+ i2.shi2_max_uses = (uint32_t)-1;
+ i2.shi2_current_uses = 0;
+ i2.shi2_path = path;
+ i2.shi2_passwd = NULL;
+
+ status = NetShareAdd(hostname,
+ 2,
+ (uint8_t *)&i2,
+ &parm_err);
+ if (status != 0) {
+ printf("NetShareAdd failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/share/share_del.c b/source3/lib/netapi/examples/share/share_del.c
new file mode 100644
index 0000000..20e3ce5
--- /dev/null
+++ b/source3/lib/netapi/examples/share/share_del.c
@@ -0,0 +1,85 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShareDel query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *sharename = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("share_del", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname sharename");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ sharename = poptGetArg(pc);
+
+ /* NetShareDel */
+
+ status = NetShareDel(hostname,
+ sharename,
+ 0);
+ if (status != 0) {
+ printf("NetShareDel failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/share/share_enum.c b/source3/lib/netapi/examples/share/share_enum.c
new file mode 100644
index 0000000..b1f4043
--- /dev/null
+++ b/source3/lib/netapi/examples/share/share_enum.c
@@ -0,0 +1,142 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShareEnum query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int i;
+
+ struct SHARE_INFO_0 *i0 = NULL;
+ struct SHARE_INFO_1 *i1 = NULL;
+ struct SHARE_INFO_2 *i2 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("share_enum", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetShareEnum */
+
+ do {
+ status = NetShareEnum(hostname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ printf("total entries: %d\n", total_entries);
+ switch (level) {
+ case 0:
+ i0 = (struct SHARE_INFO_0 *)buffer;
+ break;
+ case 1:
+ i1 = (struct SHARE_INFO_1 *)buffer;
+ break;
+ case 2:
+ i2 = (struct SHARE_INFO_2 *)buffer;
+ break;
+ default:
+ break;
+ }
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ printf("#%d netname: %s\n", i, i0->shi0_netname);
+ i0++;
+ break;
+ case 1:
+ printf("#%d netname: %s\n", i, i1->shi1_netname);
+ printf("#%d type: %d\n", i, i1->shi1_type);
+ printf("#%d remark: %s\n", i, i1->shi1_remark);
+ i1++;
+ break;
+ case 2:
+ printf("#%d netname: %s\n", i, i2->shi2_netname);
+ printf("#%d type: %d\n", i, i2->shi2_type);
+ printf("#%d remark: %s\n", i, i2->shi2_remark);
+ printf("#%d permissions: %d\n", i, i2->shi2_permissions);
+ printf("#%d max users: %d\n", i, i2->shi2_max_uses);
+ printf("#%d current users: %d\n", i, i2->shi2_current_uses);
+ printf("#%d path: %s\n", i, i2->shi2_path);
+ printf("#%d password: %s\n", i, i2->shi2_passwd);
+ i2++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetShareEnum failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/share/share_getinfo.c b/source3/lib/netapi/examples/share/share_getinfo.c
new file mode 100644
index 0000000..479da5c
--- /dev/null
+++ b/source3/lib/netapi/examples/share/share_getinfo.c
@@ -0,0 +1,152 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShareGetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *sharename = NULL;
+ uint32_t level = 2;
+ uint8_t *buffer = NULL;
+
+ struct SHARE_INFO_0 *i0 = NULL;
+ struct SHARE_INFO_1 *i1 = NULL;
+ struct SHARE_INFO_2 *i2 = NULL;
+ struct SHARE_INFO_501 *i501 = NULL;
+ struct SHARE_INFO_1005 *i1005 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("share_getinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname sharename level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ sharename = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetShareGetInfo */
+
+ status = NetShareGetInfo(hostname,
+ sharename,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("NetShareGetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ switch (level) {
+ case 0:
+ i0 = (struct SHARE_INFO_0 *)buffer;
+ break;
+ case 1:
+ i1 = (struct SHARE_INFO_1 *)buffer;
+ break;
+ case 2:
+ i2 = (struct SHARE_INFO_2 *)buffer;
+ break;
+ case 501:
+ i501 = (struct SHARE_INFO_501 *)buffer;
+ break;
+ case 1005:
+ i1005 = (struct SHARE_INFO_1005 *)buffer;
+ break;
+
+ default:
+ break;
+ }
+
+ switch (level) {
+ case 0:
+ printf("netname: %s\n", i0->shi0_netname);
+ break;
+ case 1:
+ printf("netname: %s\n", i1->shi1_netname);
+ printf("type: %d\n", i1->shi1_type);
+ printf("remark: %s\n", i1->shi1_remark);
+ break;
+ case 2:
+ printf("netname: %s\n", i2->shi2_netname);
+ printf("type: %d\n", i2->shi2_type);
+ printf("remark: %s\n", i2->shi2_remark);
+ printf("permissions: %d\n", i2->shi2_permissions);
+ printf("max users: %d\n", i2->shi2_max_uses);
+ printf("current users: %d\n", i2->shi2_current_uses);
+ printf("path: %s\n", i2->shi2_path);
+ printf("password: %s\n", i2->shi2_passwd);
+ break;
+ case 501:
+ printf("netname: %s\n", i501->shi501_netname);
+ printf("type: %d\n", i501->shi501_type);
+ printf("remark: %s\n", i501->shi501_remark);
+ printf("flags: %d\n", i501->shi501_flags);
+ break;
+ case 1005:
+ printf("flags: %d\n", i1005->shi1005_flags);
+ break;
+ default:
+ break;
+ }
+ NetApiBufferFree(buffer);
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/share/share_setinfo.c b/source3/lib/netapi/examples/share/share_setinfo.c
new file mode 100644
index 0000000..f4748f4
--- /dev/null
+++ b/source3/lib/netapi/examples/share/share_setinfo.c
@@ -0,0 +1,105 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShareSetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *sharename = NULL;
+ const char *comment = "NetApi generated Share comment";
+ uint32_t level = 1004;
+ uint8_t *buffer = NULL;
+ uint32_t parm_err = 0;
+
+ struct SHARE_INFO_1004 i1004;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("share_setinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname sharename comment");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ sharename = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ comment = poptGetArg(pc);
+ }
+
+ /* NetShareSetInfo */
+ switch (level) {
+ case 1004:
+ i1004.shi1004_remark = comment;
+ buffer = (uint8_t *)&i1004;
+ break;
+ default:
+ break;
+ }
+
+ status = NetShareSetInfo(hostname,
+ sharename,
+ level,
+ buffer,
+ &parm_err);
+ if (status != 0) {
+ printf("NetShareSetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/shutdown/shutdown_abort.c b/source3/lib/netapi/examples/shutdown/shutdown_abort.c
new file mode 100644
index 0000000..8f24a7a
--- /dev/null
+++ b/source3/lib/netapi/examples/shutdown/shutdown_abort.c
@@ -0,0 +1,76 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShutdownAbort query
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("shutdown_abort", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ /* NetShutdownAbort */
+
+ status = NetShutdownAbort(hostname);
+ if (status != 0) {
+ printf("NetShutdownAbort failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/shutdown/shutdown_init.c b/source3/lib/netapi/examples/shutdown/shutdown_init.c
new file mode 100644
index 0000000..73d23bb
--- /dev/null
+++ b/source3/lib/netapi/examples/shutdown/shutdown_init.c
@@ -0,0 +1,94 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShutdownInit query
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *message = NULL;
+ uint32_t timeout = 30;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("shutdown_init", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname message timeout");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ message = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ timeout = atoi(poptGetArg(pc));
+
+ /* NetShutdownInit */
+
+ status = NetShutdownInit(hostname,
+ message,
+ timeout,
+ 1, /* close apps */
+ 1); /* reboot */
+ if (status != 0) {
+ printf("NetShutdownInit failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_add.c b/source3/lib/netapi/examples/user/user_add.c
new file mode 100644
index 0000000..5452f77
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_add.c
@@ -0,0 +1,103 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserAdd query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+ const char *password = NULL;
+ struct USER_INFO_1 info1;
+ uint32_t parm_error = 0;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_add", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username password");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ password = poptGetArg(pc);
+
+ /* NetUserAdd */
+
+ info1.usri1_name = username;
+ info1.usri1_password = password;
+ info1.usri1_password_age = 0;
+ info1.usri1_priv = 0;
+ info1.usri1_home_dir = NULL;
+ info1.usri1_comment = "User created using Samba NetApi Example code";
+ info1.usri1_flags = 0;
+ info1.usri1_script_path = NULL;
+
+ status = NetUserAdd(hostname,
+ 1,
+ (uint8_t *)&info1,
+ &parm_error);
+ if (status != 0) {
+ printf("NetUserAdd failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_chgpwd.c b/source3/lib/netapi/examples/user/user_chgpwd.c
new file mode 100644
index 0000000..8b37ec2
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_chgpwd.c
@@ -0,0 +1,99 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserChangePassword query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+ const char *old_password = NULL;
+ const char *new_password = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_chgpwd", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username old_password new_password");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ old_password = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ new_password = poptGetArg(pc);
+
+ /* NetUserChangePassword */
+
+ status = NetUserChangePassword(hostname,
+ username,
+ old_password,
+ new_password);
+ if (status != 0) {
+ printf("NetUserChangePassword failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_del.c b/source3/lib/netapi/examples/user/user_del.c
new file mode 100644
index 0000000..9cf28a9
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_del.c
@@ -0,0 +1,82 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserDel query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_del", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ /* NetUserDel */
+
+ status = NetUserDel(hostname, username);
+ if (status != 0) {
+ printf("NetUserDel failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_dispinfo.c b/source3/lib/netapi/examples/user/user_dispinfo.c
new file mode 100644
index 0000000..23024fe
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_dispinfo.c
@@ -0,0 +1,100 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetQueryDisplayInformation query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ void *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t idx = 0;
+ int i;
+
+ struct NET_DISPLAY_USER *user;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_dispinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ /* NetQueryDisplayInformation */
+
+ do {
+ status = NetQueryDisplayInformation(hostname,
+ 1,
+ idx,
+ 1000,
+ (uint32_t)-1,
+ &entries_read,
+ &buffer);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ user = (struct NET_DISPLAY_USER *)buffer;
+ for (i=0; i<entries_read; i++) {
+ printf("user %d: %s\n", i + idx,
+ user->usri1_name);
+ user++;
+ }
+ NetApiBufferFree(buffer);
+ }
+ idx += entries_read;
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetQueryDisplayInformation failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_enum.c b/source3/lib/netapi/examples/user/user_enum.c
new file mode 100644
index 0000000..cf77bf2
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_enum.c
@@ -0,0 +1,157 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserEnum query
+ * Copyright (C) Guenther Deschner 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ char *sid_str = NULL;
+ int i;
+
+ struct USER_INFO_0 *info0 = NULL;
+ struct USER_INFO_10 *info10 = NULL;
+ struct USER_INFO_20 *info20 = NULL;
+ struct USER_INFO_23 *info23 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_enum", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetUserEnum */
+
+ do {
+ status = NetUserEnum(hostname,
+ level,
+ FILTER_NORMAL_ACCOUNT,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+
+ switch (level) {
+ case 0:
+ info0 = (struct USER_INFO_0 *)buffer;
+ break;
+ case 10:
+ info10 = (struct USER_INFO_10 *)buffer;
+ break;
+ case 20:
+ info20 = (struct USER_INFO_20 *)buffer;
+ break;
+ case 23:
+ info23 = (struct USER_INFO_23 *)buffer;
+ break;
+ default:
+ break;
+ }
+
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ printf("#%d user: %s\n", i, info0->usri0_name);
+ info0++;
+ break;
+ case 10:
+ printf("#%d user: %s\n", i, info10->usri10_name);
+ printf("#%d comment: %s\n", i, info10->usri10_comment);
+ printf("#%d usr_comment: %s\n", i, info10->usri10_usr_comment);
+ printf("#%d full_name: %s\n", i, info10->usri10_full_name);
+ info10++;
+ break;
+ case 20:
+ printf("#%d user: %s\n", i, info20->usri20_name);
+ printf("#%d comment: %s\n", i, info20->usri20_comment);
+ printf("#%d flags: 0x%08x\n", i, info20->usri20_flags);
+ printf("#%d rid: %d\n", i, info20->usri20_user_id);
+ info20++;
+ break;
+ case 23:
+ printf("#%d user: %s\n", i, info23->usri23_name);
+ printf("#%d comment: %s\n", i, info23->usri23_comment);
+ printf("#%d flags: 0x%08x\n", i, info23->usri23_flags);
+ if (ConvertSidToStringSid(info23->usri23_user_sid,
+ &sid_str)) {
+ printf("#%d sid: %s\n", i, sid_str);
+ free(sid_str);
+ }
+ info23++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetUserEnum failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_getgroups.c b/source3/lib/netapi/examples/user/user_getgroups.c
new file mode 100644
index 0000000..939415e
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_getgroups.c
@@ -0,0 +1,133 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserGetGroups query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ int i;
+
+ struct GROUP_USERS_INFO_0 *info0 = NULL;
+ struct GROUP_USERS_INFO_1 *info1 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_getgroups", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetUserGetGroups */
+
+ do {
+ status = NetUserGetGroups(hostname,
+ username,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+
+ switch (level) {
+ case 0:
+ info0 = (struct GROUP_USERS_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct GROUP_USERS_INFO_1 *)buffer;
+ break;
+ default:
+ break;
+ }
+
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ printf("#%d group: %s\n", i, info0->grui0_name);
+ info0++;
+ break;
+ case 1:
+ printf("#%d group: %s\n", i, info1->grui1_name);
+ printf("#%d attributes: %d\n", i, info1->grui1_attributes);
+ info1++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetUserGetGroups failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_getinfo.c b/source3/lib/netapi/examples/user/user_getinfo.c
new file mode 100644
index 0000000..9e95260
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_getinfo.c
@@ -0,0 +1,293 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserGetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 0;
+ char *sid_str = NULL;
+ int i;
+
+ struct USER_INFO_0 *u0;
+ struct USER_INFO_1 *u1;
+ struct USER_INFO_2 *u2;
+ struct USER_INFO_3 *u3;
+ struct USER_INFO_4 *u4;
+ struct USER_INFO_10 *u10;
+ struct USER_INFO_11 *u11;
+ struct USER_INFO_20 *u20;
+ struct USER_INFO_23 *u23;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_getinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetUserGetInfo */
+
+ status = NetUserGetInfo(hostname,
+ username,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("NetUserGetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ switch (level) {
+ case 0:
+ u0 = (struct USER_INFO_0 *)buffer;
+ printf("name: %s\n", u0->usri0_name);
+ break;
+ case 1:
+ u1 = (struct USER_INFO_1 *)buffer;
+ printf("name: %s\n", u1->usri1_name);
+ printf("password: %s\n", u1->usri1_password);
+ printf("password_age: %d\n", u1->usri1_password_age);
+ printf("priv: %d\n", u1->usri1_priv);
+ printf("homedir: %s\n", u1->usri1_home_dir);
+ printf("comment: %s\n", u1->usri1_comment);
+ printf("flags: 0x%08x\n", u1->usri1_flags);
+ printf("script: %s\n", u1->usri1_script_path);
+ break;
+ case 2:
+ u2 = (struct USER_INFO_2 *)buffer;
+ printf("name: %s\n", u2->usri2_name);
+ printf("password: %s\n", u2->usri2_password);
+ printf("password_age: %d\n", u2->usri2_password_age);
+ printf("priv: %d\n", u2->usri2_priv);
+ printf("homedir: %s\n", u2->usri2_home_dir);
+ printf("comment: %s\n", u2->usri2_comment);
+ printf("flags: 0x%08x\n", u2->usri2_flags);
+ printf("script: %s\n", u2->usri2_script_path);
+ printf("auth flags: 0x%08x\n", u2->usri2_auth_flags);
+ printf("full name: %s\n", u2->usri2_full_name);
+ printf("user comment: %s\n", u2->usri2_usr_comment);
+ printf("user parameters: %s\n", u2->usri2_parms);
+ printf("workstations: %s\n", u2->usri2_workstations);
+ printf("last logon (seconds since jan. 1, 1970 GMT): %d\n",
+ u2->usri2_last_logon);
+ printf("last logoff (seconds since jan. 1, 1970 GMT): %d\n",
+ u2->usri2_last_logoff);
+ printf("account expires (seconds since jan. 1, 1970 GMT): %d\n",
+ u2->usri2_acct_expires);
+ printf("max storage: %d\n", u2->usri2_max_storage);
+ printf("units per week: %d\n", u2->usri2_units_per_week);
+ printf("logon hours:");
+ for (i=0; i<21; i++) {
+ printf(" %x", (uint8_t)u2->usri2_logon_hours[i]);
+ }
+ printf("\n");
+ printf("bad password count: %d\n", u2->usri2_bad_pw_count);
+ printf("logon count: %d\n", u2->usri2_num_logons);
+ printf("logon server: %s\n", u2->usri2_logon_server);
+ printf("country code: %d\n", u2->usri2_country_code);
+ printf("code page: %d\n", u2->usri2_code_page);
+ break;
+ case 3:
+ u3 = (struct USER_INFO_3 *)buffer;
+ printf("name: %s\n", u3->usri3_name);
+ printf("password_age: %d\n", u3->usri3_password_age);
+ printf("priv: %d\n", u3->usri3_priv);
+ printf("homedir: %s\n", u3->usri3_home_dir);
+ printf("comment: %s\n", u3->usri3_comment);
+ printf("flags: 0x%08x\n", u3->usri3_flags);
+ printf("script: %s\n", u3->usri3_script_path);
+ printf("auth flags: 0x%08x\n", u3->usri3_auth_flags);
+ printf("full name: %s\n", u3->usri3_full_name);
+ printf("user comment: %s\n", u3->usri3_usr_comment);
+ printf("user parameters: %s\n", u3->usri3_parms);
+ printf("workstations: %s\n", u3->usri3_workstations);
+ printf("last logon (seconds since jan. 1, 1970 GMT): %d\n",
+ u3->usri3_last_logon);
+ printf("last logoff (seconds since jan. 1, 1970 GMT): %d\n",
+ u3->usri3_last_logoff);
+ printf("account expires (seconds since jan. 1, 1970 GMT): %d\n",
+ u3->usri3_acct_expires);
+ printf("max storage: %d\n", u3->usri3_max_storage);
+ printf("units per week: %d\n", u3->usri3_units_per_week);
+ printf("logon hours:");
+ for (i=0; i<21; i++) {
+ printf(" %x", (uint8_t)u3->usri3_logon_hours[i]);
+ }
+ printf("\n");
+ printf("bad password count: %d\n", u3->usri3_bad_pw_count);
+ printf("logon count: %d\n", u3->usri3_num_logons);
+ printf("logon server: %s\n", u3->usri3_logon_server);
+ printf("country code: %d\n", u3->usri3_country_code);
+ printf("code page: %d\n", u3->usri3_code_page);
+ printf("user id: %d\n", u3->usri3_user_id);
+ printf("primary group id: %d\n", u3->usri3_primary_group_id);
+ printf("profile: %s\n", u3->usri3_profile);
+ printf("home dir drive: %s\n", u3->usri3_home_dir_drive);
+ printf("password expired: %d\n", u3->usri3_password_expired);
+ break;
+ case 4:
+ u4 = (struct USER_INFO_4 *)buffer;
+ printf("name: %s\n", u4->usri4_name);
+ printf("password: %s\n", u4->usri4_password);
+ printf("password_age: %d\n", u4->usri4_password_age);
+ printf("priv: %d\n", u4->usri4_priv);
+ printf("homedir: %s\n", u4->usri4_home_dir);
+ printf("comment: %s\n", u4->usri4_comment);
+ printf("flags: 0x%08x\n", u4->usri4_flags);
+ printf("script: %s\n", u4->usri4_script_path);
+ printf("auth flags: 0x%08x\n", u4->usri4_auth_flags);
+ printf("full name: %s\n", u4->usri4_full_name);
+ printf("user comment: %s\n", u4->usri4_usr_comment);
+ printf("user parameters: %s\n", u4->usri4_parms);
+ printf("workstations: %s\n", u4->usri4_workstations);
+ printf("last logon (seconds since jan. 1, 1970 GMT): %d\n",
+ u4->usri4_last_logon);
+ printf("last logoff (seconds since jan. 1, 1970 GMT): %d\n",
+ u4->usri4_last_logoff);
+ printf("account expires (seconds since jan. 1, 1970 GMT): %d\n",
+ u4->usri4_acct_expires);
+ printf("max storage: %d\n", u4->usri4_max_storage);
+ printf("units per week: %d\n", u4->usri4_units_per_week);
+ printf("logon hours:");
+ for (i=0; i<21; i++) {
+ printf(" %x", (uint8_t)u4->usri4_logon_hours[i]);
+ }
+ printf("\n");
+ printf("bad password count: %d\n", u4->usri4_bad_pw_count);
+ printf("logon count: %d\n", u4->usri4_num_logons);
+ printf("logon server: %s\n", u4->usri4_logon_server);
+ printf("country code: %d\n", u4->usri4_country_code);
+ printf("code page: %d\n", u4->usri4_code_page);
+ if (ConvertSidToStringSid(u4->usri4_user_sid,
+ &sid_str)) {
+ printf("user_sid: %s\n", sid_str);
+ free(sid_str);
+ }
+ printf("primary group id: %d\n", u4->usri4_primary_group_id);
+ printf("profile: %s\n", u4->usri4_profile);
+ printf("home dir drive: %s\n", u4->usri4_home_dir_drive);
+ printf("password expired: %d\n", u4->usri4_password_expired);
+ break;
+ case 10:
+ u10 = (struct USER_INFO_10 *)buffer;
+ printf("name: %s\n", u10->usri10_name);
+ printf("comment: %s\n", u10->usri10_comment);
+ printf("usr_comment: %s\n", u10->usri10_usr_comment);
+ printf("full_name: %s\n", u10->usri10_full_name);
+ break;
+ case 11:
+ u11 = (struct USER_INFO_11 *)buffer;
+ printf("name: %s\n", u11->usri11_name);
+ printf("comment: %s\n", u11->usri11_comment);
+ printf("user comment: %s\n", u11->usri11_usr_comment);
+ printf("full name: %s\n", u11->usri11_full_name);
+ printf("priv: %d\n", u11->usri11_priv);
+ printf("auth flags: 0x%08x\n", u11->usri11_auth_flags);
+ printf("password_age: %d\n", u11->usri11_password_age);
+ printf("homedir: %s\n", u11->usri11_home_dir);
+ printf("user parameters: %s\n", u11->usri11_parms);
+ printf("last logon (seconds since jan. 1, 1970 GMT): %d\n",
+ u11->usri11_last_logon);
+ printf("last logoff (seconds since jan. 1, 1970 GMT): %d\n",
+ u11->usri11_last_logoff);
+ printf("bad password count: %d\n", u11->usri11_bad_pw_count);
+ printf("logon count: %d\n", u11->usri11_num_logons);
+ printf("logon server: %s\n", u11->usri11_logon_server);
+ printf("country code: %d\n", u11->usri11_country_code);
+ printf("workstations: %s\n", u11->usri11_workstations);
+ printf("max storage: %d\n", u11->usri11_max_storage);
+ printf("units per week: %d\n", u11->usri11_units_per_week);
+ printf("logon hours:");
+ for (i=0; i<21; i++) {
+ printf(" %x", (uint8_t)u11->usri11_logon_hours[i]);
+ }
+ printf("\n");
+ printf("code page: %d\n", u11->usri11_code_page);
+ break;
+ case 20:
+ u20 = (struct USER_INFO_20 *)buffer;
+ printf("name: %s\n", u20->usri20_name);
+ printf("comment: %s\n", u20->usri20_comment);
+ printf("flags: 0x%08x\n", u20->usri20_flags);
+ printf("rid: %d\n", u20->usri20_user_id);
+ break;
+ case 23:
+ u23 = (struct USER_INFO_23 *)buffer;
+ printf("name: %s\n", u23->usri23_name);
+ printf("comment: %s\n", u23->usri23_comment);
+ printf("flags: 0x%08x\n", u23->usri23_flags);
+ if (ConvertSidToStringSid(u23->usri23_user_sid,
+ &sid_str)) {
+ printf("user_sid: %s\n", sid_str);
+ free(sid_str);
+ }
+ break;
+ default:
+ break;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_getlocalgroups.c b/source3/lib/netapi/examples/user/user_getlocalgroups.c
new file mode 100644
index 0000000..133104d
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_getlocalgroups.c
@@ -0,0 +1,122 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserGetLocalGroups query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t flags = 0;
+ int i;
+
+ struct LOCALGROUP_USERS_INFO_0 *info0 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_getlocalgroups", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ /* NetUserGetLocalGroups */
+
+ do {
+ status = NetUserGetLocalGroups(hostname,
+ username,
+ level,
+ flags,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+
+ switch (level) {
+ case 0:
+ info0 = (struct LOCALGROUP_USERS_INFO_0 *)buffer;
+ break;
+ default:
+ break;
+ }
+
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ printf("#%d group: %s\n", i, info0->lgrui0_name);
+ info0++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status != 0) {
+ printf("NetUserGetLocalGroups failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_modalsget.c b/source3/lib/netapi/examples/user/user_modalsget.c
new file mode 100644
index 0000000..4dcb41b
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_modalsget.c
@@ -0,0 +1,131 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserModalsGet query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 0;
+ char *sid_str = NULL;
+
+ struct USER_MODALS_INFO_0 *u0;
+ struct USER_MODALS_INFO_1 *u1;
+ struct USER_MODALS_INFO_2 *u2;
+ struct USER_MODALS_INFO_3 *u3;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_modalsget", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ /* NetUserModalsGet */
+
+ status = NetUserModalsGet(hostname,
+ level,
+ &buffer);
+ if (status != 0) {
+ printf("NetUserModalsGet failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ switch (level) {
+ case 0:
+ u0 = (struct USER_MODALS_INFO_0 *)buffer;
+ printf("min passwd len: %d character\n",
+ u0->usrmod0_min_passwd_len);
+ printf("max passwd age: %d (days)\n",
+ u0->usrmod0_max_passwd_age/86400);
+ printf("min passwd age: %d (days)\n",
+ u0->usrmod0_min_passwd_age/86400);
+ printf("force logoff: %d (seconds)\n",
+ u0->usrmod0_force_logoff);
+ printf("password history length: %d entries\n",
+ u0->usrmod0_password_hist_len);
+ break;
+ case 1:
+ u1 = (struct USER_MODALS_INFO_1 *)buffer;
+ printf("role: %d\n", u1->usrmod1_role);
+ printf("primary: %s\n", u1->usrmod1_primary);
+ break;
+ case 2:
+ u2 = (struct USER_MODALS_INFO_2 *)buffer;
+ printf("domain name: %s\n", u2->usrmod2_domain_name);
+ if (ConvertSidToStringSid(u2->usrmod2_domain_id,
+ &sid_str)) {
+ printf("domain sid: %s\n", sid_str);
+ free(sid_str);
+ }
+ break;
+ case 3:
+ u3 = (struct USER_MODALS_INFO_3 *)buffer;
+ printf("lockout duration: %d (seconds)\n",
+ u3->usrmod3_lockout_duration);
+ printf("lockout observation window: %d (seconds)\n",
+ u3->usrmod3_lockout_observation_window);
+ printf("lockout threshold: %d entries\n",
+ u3->usrmod3_lockout_threshold);
+ break;
+ default:
+ break;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_modalsset.c b/source3/lib/netapi/examples/user/user_modalsset.c
new file mode 100644
index 0000000..a5cdc44
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_modalsset.c
@@ -0,0 +1,132 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserModalsSet query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t level = 0;
+ uint32_t parm_err = 0;
+
+ struct USER_MODALS_INFO_0 u0;
+ struct USER_MODALS_INFO_1001 u1001;
+ struct USER_MODALS_INFO_1002 u1002;
+ struct USER_MODALS_INFO_1003 u1003;
+ struct USER_MODALS_INFO_1004 u1004;
+ struct USER_MODALS_INFO_1005 u1005;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_modalsset", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname level value");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (poptPeekArg(pc)) {
+ level = atoi(poptGetArg(pc));
+ }
+
+ switch (level) {
+ case 0:
+ u0.usrmod0_min_passwd_len = 0;
+ u0.usrmod0_max_passwd_age = (86400 * 30); /* once a month */
+ u0.usrmod0_min_passwd_age = 0;
+ u0.usrmod0_force_logoff = TIMEQ_FOREVER;
+ u0.usrmod0_password_hist_len = 0;
+ buffer = (uint8_t *)&u0;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ break;
+ case 1001:
+ u1001.usrmod1001_min_passwd_len = 0;
+ buffer = (uint8_t *)&u1001;
+ break;
+ case 1002:
+ u1002.usrmod1002_max_passwd_age = 0;
+ buffer = (uint8_t *)&u1002;
+ break;
+ case 1003:
+ u1003.usrmod1003_min_passwd_age = (86400 * 30); /* once a month */
+ buffer = (uint8_t *)&u1003;
+ break;
+ case 1004:
+ u1004.usrmod1004_force_logoff = TIMEQ_FOREVER;
+ buffer = (uint8_t *)&u1004;
+ break;
+ case 1005:
+ u1005.usrmod1005_password_hist_len = 0;
+ buffer = (uint8_t *)&u1005;
+ break;
+ case 1006:
+ case 1007:
+ default:
+ break;
+ }
+
+ /* NetUserModalsSet */
+
+ status = NetUserModalsSet(hostname,
+ level,
+ buffer,
+ &parm_err);
+ if (status != 0) {
+ printf("NetUserModalsSet failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_setgroups.c b/source3/lib/netapi/examples/user/user_setgroups.c
new file mode 100644
index 0000000..de3ff22
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_setgroups.c
@@ -0,0 +1,144 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserSetGroups query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+ uint32_t level = 0;
+ uint8_t *buffer = NULL;
+ uint32_t num_entries = 0;
+ const char **names = NULL;
+ int i = 0;
+ size_t buf_size = 0;
+
+ struct GROUP_USERS_INFO_0 *g0 = NULL;
+ struct GROUP_USERS_INFO_1 *g1 = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_setgroups", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username group1 group2 ...");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+
+ names = poptGetArgs(pc);
+ for (i=0; names[i] != NULL; i++) {
+ num_entries++;
+ }
+
+ switch (level) {
+ case 0:
+ buf_size = sizeof(struct GROUP_USERS_INFO_0) * num_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g0);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ g0[i].grui0_name = names[i];
+ }
+
+ buffer = (uint8_t *)g0;
+ break;
+ case 1:
+ buf_size = sizeof(struct GROUP_USERS_INFO_1) * num_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g1);
+ if (status) {
+ printf("NetApiBufferAllocate failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ g1[i].grui1_name = names[i];
+ g1[i].grui1_attributes = 0; /* ? */
+ }
+
+ buffer = (uint8_t *)g1;
+ break;
+ default:
+ break;
+ }
+
+ /* NetUserSetGroups */
+
+ status = NetUserSetGroups(hostname,
+ username,
+ level,
+ buffer,
+ num_entries);
+ if (status != 0) {
+ printf("NetUserSetGroups failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ out:
+ NetApiBufferFree(buffer);
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/user/user_setinfo.c b/source3/lib/netapi/examples/user/user_setinfo.c
new file mode 100644
index 0000000..56c9822
--- /dev/null
+++ b/source3/lib/netapi/examples/user/user_setinfo.c
@@ -0,0 +1,200 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUserSetInfo query
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+ const char *username = NULL;
+ uint32_t level = 0;
+ uint32_t parm_err = 0;
+ uint8_t *buffer = NULL;
+ const char *val = NULL;
+
+ struct USER_INFO_0 u0;
+ struct USER_INFO_1003 u1003;
+ struct USER_INFO_1005 u1005;
+ struct USER_INFO_1006 u1006;
+ struct USER_INFO_1007 u1007;
+ struct USER_INFO_1008 u1008;
+ struct USER_INFO_1009 u1009;
+ struct USER_INFO_1010 u1010;
+ struct USER_INFO_1011 u1011;
+ struct USER_INFO_1012 u1012;
+ struct USER_INFO_1014 u1014;
+ struct USER_INFO_1017 u1017;
+ struct USER_INFO_1024 u1024;
+ struct USER_INFO_1051 u1051;
+ struct USER_INFO_1052 u1052;
+ struct USER_INFO_1053 u1053;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("user_setinfo", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname username level");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ username = poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ level = atoi(poptGetArg(pc));
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ val = poptGetArg(pc);
+
+ /* NetUserSetInfo */
+
+ switch (level) {
+ case 0:
+ u0.usri0_name = val;
+ buffer = (uint8_t *)&u0;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ break;
+ case 21:
+ break;
+ case 22:
+ break;
+ case 1003:
+ u1003.usri1003_password = val;
+ buffer = (uint8_t *)&u1003;
+ break;
+ case 1005:
+ u1005.usri1005_priv = atoi(val);
+ buffer = (uint8_t *)&u1005;
+ break;
+ case 1006:
+ u1006.usri1006_home_dir = val;
+ buffer = (uint8_t *)&u1006;
+ break;
+ case 1007:
+ u1007.usri1007_comment = val;
+ buffer = (uint8_t *)&u1007;
+ break;
+ case 1008:
+ u1008.usri1008_flags = atoi(val);
+ buffer = (uint8_t *)&u1008;
+ break;
+ case 1009:
+ u1009.usri1009_script_path = val;
+ buffer = (uint8_t *)&u1009;
+ break;
+ case 1010:
+ u1010.usri1010_auth_flags = atoi(val);
+ buffer = (uint8_t *)&u1010;
+ break;
+ case 1011:
+ u1011.usri1011_full_name = val;
+ buffer = (uint8_t *)&u1011;
+ break;
+ case 1012:
+ u1012.usri1012_usr_comment = val;
+ buffer = (uint8_t *)&u1012;
+ break;
+ case 1014:
+ u1014.usri1014_workstations = val;
+ buffer = (uint8_t *)&u1014;
+ break;
+ case 1017:
+ u1017.usri1017_acct_expires = atoi(val);
+ buffer = (uint8_t *)&u1017;
+ break;
+ case 1020:
+ break;
+ case 1024:
+ u1024.usri1024_country_code = atoi(val);
+ buffer = (uint8_t *)&u1024;
+ break;
+ case 1051:
+ u1051.usri1051_primary_group_id = atoi(val);
+ buffer = (uint8_t *)&u1051;
+ break;
+ case 1052:
+ u1052.usri1052_profile = val;
+ buffer = (uint8_t *)&u1052;
+ break;
+ case 1053:
+ u1053.usri1053_home_dir_drive = val;
+ buffer = (uint8_t *)&u1053;
+ break;
+ default:
+ break;
+ }
+
+ status = NetUserSetInfo(hostname,
+ username,
+ level,
+ buffer,
+ &parm_err);
+ if (status != 0) {
+ printf("NetUserSetInfo failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ goto out;
+ }
+
+ out:
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/examples/wscript_build b/source3/lib/netapi/examples/wscript_build
new file mode 100644
index 0000000..1d56840
--- /dev/null
+++ b/source3/lib/netapi/examples/wscript_build
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+# The remaining task is to build the gtk example, but we first need to find the gtk libs
+# netdomjoin-gui/netdomjoin-gui.c
+
+names = [
+ ("getdc", "getdc"),
+ ("dsgetdc", "dsgetdc"),
+ ("join", "netdomjoin"),
+ ("join", "getjoinableous"),
+ ("join", "getjoininformation"),
+ ("join", "rename_machine"),
+ ("join", "provision_computer_account"),
+ ("join", "request_offline_domain_join"),
+ ("join", "djoin"),
+ ("user", "user_add"),
+ ("user", "user_del"),
+ ("user", "user_enum"),
+ ("user", "user_dispinfo"),
+ ("user", "user_chgpwd"),
+ ("user", "user_getinfo"),
+ ("user", "user_setinfo"),
+ ("user", "user_modalsget"),
+ ("user", "user_modalsset"),
+ ("user", "user_getgroups"),
+ ("user", "user_setgroups"),
+ ("user", "user_getlocalgroups"),
+ ("group", "group_add"),
+ ("group", "group_del"),
+ ("group", "group_enum"),
+ ("group", "group_setinfo"),
+ ("group", "group_getinfo"),
+ ("group", "group_adduser"),
+ ("group", "group_deluser"),
+ ("group", "group_getusers"),
+ ("group", "group_setusers"),
+ ("localgroup", "localgroup_add"),
+ ("localgroup", "localgroup_del"),
+ ("localgroup", "localgroup_getinfo"),
+ ("localgroup", "localgroup_setinfo"),
+ ("localgroup", "localgroup_enum"),
+ ("localgroup", "localgroup_addmembers"),
+ ("localgroup", "localgroup_delmembers"),
+ ("localgroup", "localgroup_setmembers"),
+ ("localgroup", "localgroup_getmembers"),
+ ("server", "remote_tod"),
+ ("server", "server_getinfo"),
+ ("share", "share_add"),
+ ("share", "share_del"),
+ ("share", "share_enum"),
+ ("share", "share_getinfo"),
+ ("share", "share_setinfo"),
+ ("file", "file_close"),
+ ("file", "file_getinfo"),
+ ("file", "file_enum"),
+ ("shutdown", "shutdown_init"),
+ ("shutdown", "shutdown_abort"),
+ ("netlogon", "netlogon_control"),
+ ("netlogon", "netlogon_control2"),
+ ("netlogon", "nltest")]
+
+
+bld.SAMBA_SUBSYSTEM('LIBNETAPI_EXAMPLES_COMMON',
+ source='common.c',
+ deps='netapi popt')
+
+for pattern in names:
+ (subdir, name) = pattern
+ bld.SAMBA_BINARY('%s/%s' % (subdir, name),
+ source='%s/%s.c' % (subdir, name),
+ deps='netapi popt LIBNETAPI_EXAMPLES_COMMON',
+ install=False)
diff --git a/source3/lib/netapi/file.c b/source3/lib/netapi/file.c
new file mode 100644
index 0000000..411747a
--- /dev/null
+++ b/source3/lib/netapi/file.c
@@ -0,0 +1,283 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi File Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetFileClose_r(struct libnetapi_ctx *ctx,
+ struct NetFileClose *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetFileClose(b, talloc_tos(),
+ r->in.server_name,
+ r->in.fileid,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetFileClose_l(struct libnetapi_ctx *ctx,
+ struct NetFileClose *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetFileClose);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS map_srvsvc_FileInfo_to_FILE_INFO_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ union srvsvc_NetFileInfo *info,
+ uint8_t **buffer,
+ uint32_t *num_entries)
+{
+ struct FILE_INFO_2 i2;
+ struct FILE_INFO_3 i3;
+
+ switch (level) {
+ case 2:
+ i2.fi2_id = info->info2->fid;
+
+ ADD_TO_ARRAY(mem_ctx, struct FILE_INFO_2, i2,
+ (struct FILE_INFO_2 **)buffer,
+ num_entries);
+ break;
+ case 3:
+ i3.fi3_id = info->info3->fid;
+ i3.fi3_permissions = info->info3->permissions;
+ i3.fi3_num_locks = info->info3->num_locks;
+ i3.fi3_pathname = talloc_strdup(mem_ctx, info->info3->path);
+ i3.fi3_username = talloc_strdup(mem_ctx, info->info3->user);
+
+ NT_STATUS_HAVE_NO_MEMORY(i3.fi3_pathname);
+ NT_STATUS_HAVE_NO_MEMORY(i3.fi3_username);
+
+ ADD_TO_ARRAY(mem_ctx, struct FILE_INFO_3, i3,
+ (struct FILE_INFO_3 **)buffer,
+ num_entries);
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetFileGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetFileGetInfo *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ union srvsvc_NetFileInfo info;
+ uint32_t num_entries = 0;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 2:
+ case 3:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetFileGetInfo(b, talloc_tos(),
+ r->in.server_name,
+ r->in.fileid,
+ r->in.level,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = map_srvsvc_FileInfo_to_FILE_INFO_buffer(ctx,
+ r->in.level,
+ &info,
+ r->out.buffer,
+ &num_entries);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetFileGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetFileGetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetFileGetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetFileEnum_r(struct libnetapi_ctx *ctx,
+ struct NetFileEnum *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ struct srvsvc_NetFileInfoCtr info_ctr;
+ struct srvsvc_NetFileCtr2 ctr2;
+ struct srvsvc_NetFileCtr3 ctr3;
+ uint32_t num_entries = 0;
+ uint32_t i;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 2:
+ case 3:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ ZERO_STRUCT(info_ctr);
+
+ info_ctr.level = r->in.level;
+ switch (r->in.level) {
+ case 2:
+ ZERO_STRUCT(ctr2);
+ info_ctr.ctr.ctr2 = &ctr2;
+ break;
+ case 3:
+ ZERO_STRUCT(ctr3);
+ info_ctr.ctr.ctr3 = &ctr3;
+ break;
+ }
+
+ status = dcerpc_srvsvc_NetFileEnum(b, talloc_tos(),
+ r->in.server_name,
+ r->in.base_path,
+ r->in.user_name,
+ &info_ctr,
+ r->in.prefmaxlen,
+ r->out.total_entries,
+ r->out.resume_handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr) && !W_ERROR_EQUAL(werr, WERR_MORE_DATA)) {
+ goto done;
+ }
+
+ for (i=0; i < info_ctr.ctr.ctr2->count; i++) {
+ union srvsvc_NetFileInfo _i = {0};
+ switch (r->in.level) {
+ case 2:
+ _i.info2 = &info_ctr.ctr.ctr2->array[i];
+ break;
+ case 3:
+ _i.info3 = &info_ctr.ctr.ctr3->array[i];
+ break;
+ }
+
+ status = map_srvsvc_FileInfo_to_FILE_INFO_buffer(ctx,
+ r->in.level,
+ &_i,
+ r->out.buffer,
+ &num_entries);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ if (r->out.entries_read) {
+ *r->out.entries_read = num_entries;
+ }
+
+ if (r->out.total_entries) {
+ *r->out.total_entries = num_entries;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetFileEnum_l(struct libnetapi_ctx *ctx,
+ struct NetFileEnum *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetFileEnum);
+}
diff --git a/source3/lib/netapi/getdc.c b/source3/lib/netapi/getdc.c
new file mode 100644
index 0000000..7e5fab3
--- /dev/null
+++ b/source3/lib/netapi/getdc.c
@@ -0,0 +1,212 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi GetDC Support
+ * Copyright (C) Guenther Deschner 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "libsmb/dsgetdcname.h"
+
+/********************************************************************
+********************************************************************/
+
+WERROR NetGetDCName_l(struct libnetapi_ctx *ctx,
+ struct NetGetDCName *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGetDCName);
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR NetGetDCName_r(struct libnetapi_ctx *ctx,
+ struct NetGetDCName *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b;
+ const char *dcname;
+ void *buffer;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_netlogon,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_netr_GetDcName(b, talloc_tos(),
+ r->in.server_name,
+ r->in.domain_name,
+ &dcname,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (NetApiBufferAllocate(strlen_m_term(dcname), &buffer)) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ memcpy(buffer, dcname, strlen_m_term(dcname));
+ *r->out.buffer = buffer;
+ done:
+
+ return werr;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR NetGetAnyDCName_l(struct libnetapi_ctx *ctx,
+ struct NetGetAnyDCName *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGetAnyDCName);
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR NetGetAnyDCName_r(struct libnetapi_ctx *ctx,
+ struct NetGetAnyDCName *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b;
+ const char *dcname;
+ void *buffer;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_netlogon,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_netr_GetAnyDCName(b, talloc_tos(),
+ r->in.server_name,
+ r->in.domain_name,
+ &dcname,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (NetApiBufferAllocate(strlen_m_term(dcname), &buffer)) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ memcpy(buffer, dcname, strlen_m_term(dcname));
+ *r->out.buffer = buffer;
+
+ done:
+
+ return werr;
+
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR DsGetDcName_l(struct libnetapi_ctx *ctx,
+ struct DsGetDcName *r)
+{
+ NTSTATUS status;
+ struct libnetapi_private_ctx *priv;
+
+ priv = talloc_get_type_abort(ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ status = dsgetdcname(ctx,
+ priv->msg_ctx,
+ r->in.domain_name,
+ r->in.domain_guid,
+ r->in.site_name,
+ r->in.flags,
+ (struct netr_DsRGetDCNameInfo **)r->out.dc_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnetapi_set_error_string(ctx,
+ "failed to find DC: %s",
+ get_friendly_nt_error_msg(status));
+ }
+
+ return ntstatus_to_werror(status);
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR DsGetDcName_r(struct libnetapi_ctx *ctx,
+ struct DsGetDcName *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_netlogon,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_netr_DsRGetDCNameEx(b,
+ ctx,
+ r->in.server_name,
+ r->in.domain_name,
+ r->in.domain_guid,
+ r->in.site_name,
+ r->in.flags,
+ (struct netr_DsRGetDCNameInfo **)r->out.dc_info,
+ &werr);
+ if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_netr_DsRGetDCName(b,
+ ctx,
+ r->in.server_name,
+ r->in.domain_name,
+ r->in.domain_guid,
+ NULL,
+ r->in.flags,
+ (struct netr_DsRGetDCNameInfo **)r->out.dc_info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
diff --git a/source3/lib/netapi/group.c b/source3/lib/netapi/group.c
new file mode 100644
index 0000000..5258549
--- /dev/null
+++ b/source3/lib/netapi/group.c
@@ -0,0 +1,1848 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Group Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupAdd_r(struct libnetapi_ctx *ctx,
+ struct NetGroupAdd *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_group_name;
+ struct dom_sid2 *domain_sid = NULL;
+ uint32_t rid = 0;
+ struct dcerpc_binding_handle *b = NULL;
+
+ struct GROUP_INFO_0 *info0 = NULL;
+ struct GROUP_INFO_1 *info1 = NULL;
+ struct GROUP_INFO_2 *info2 = NULL;
+ struct GROUP_INFO_3 *info3 = NULL;
+ union samr_GroupInfo info;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ info0 = (struct GROUP_INFO_0 *)r->in.buffer;
+ break;
+ case 1:
+ info1 = (struct GROUP_INFO_1 *)r->in.buffer;
+ break;
+ case 2:
+ info2 = (struct GROUP_INFO_2 *)r->in.buffer;
+ break;
+ case 3:
+ info3 = (struct GROUP_INFO_3 *)r->in.buffer;
+ break;
+ default:
+ werr = WERR_INVALID_LEVEL;
+ goto done;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_CREATE_GROUP |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ init_lsa_String(&lsa_group_name, info0->grpi0_name);
+ break;
+ case 1:
+ init_lsa_String(&lsa_group_name, info1->grpi1_name);
+ break;
+ case 2:
+ init_lsa_String(&lsa_group_name, info2->grpi2_name);
+ break;
+ case 3:
+ init_lsa_String(&lsa_group_name, info3->grpi3_name);
+ break;
+ }
+
+ status = dcerpc_samr_CreateDomainGroup(b, talloc_tos(),
+ &domain_handle,
+ &lsa_group_name,
+ SEC_STD_DELETE |
+ SAMR_GROUP_ACCESS_SET_INFO,
+ &group_handle,
+ &rid,
+ &result);
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ if (info1->grpi1_comment) {
+ init_lsa_String(&info.description,
+ info1->grpi1_comment);
+
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFODESCRIPTION,
+ &info,
+ &result);
+ }
+ break;
+ case 2:
+ if (info2->grpi2_comment) {
+ init_lsa_String(&info.description,
+ info2->grpi2_comment);
+
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFODESCRIPTION,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(
+ status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto failed;
+ }
+ }
+
+ if (info2->grpi2_attributes != 0) {
+ info.attributes.attributes = info2->grpi2_attributes;
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOATTRIBUTES,
+ &info,
+ &result);
+
+ }
+ break;
+ case 3:
+ if (info3->grpi3_comment) {
+ init_lsa_String(&info.description,
+ info3->grpi3_comment);
+
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFODESCRIPTION,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(
+ status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto failed;
+ }
+ }
+
+ if (info3->grpi3_attributes != 0) {
+ info.attributes.attributes = info3->grpi3_attributes;
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOATTRIBUTES,
+ &info,
+ &result);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto failed;
+ }
+
+ werr = WERR_OK;
+ goto done;
+
+ failed:
+ dcerpc_samr_DeleteDomainGroup(b, talloc_tos(),
+ &group_handle, &result);
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupAdd_l(struct libnetapi_ctx *ctx,
+ struct NetGroupAdd *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupAdd);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupDel_r(struct libnetapi_ctx *ctx,
+ struct NetGroupDel *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_group_name;
+ struct dom_sid2 *domain_sid = NULL;
+ int i = 0;
+ struct dcerpc_binding_handle *b = NULL;
+
+ struct samr_Ids rids;
+ struct samr_Ids types;
+ union samr_GroupInfo *info = NULL;
+ struct samr_RidAttrArray *rid_array = NULL;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_group_name, r->in.group_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_group_name,
+ &rids,
+ &types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ if (types.ids[0] != SID_NAME_DOM_GRP) {
+ werr = WERR_INVALID_DATATYPE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SEC_STD_DELETE |
+ SAMR_GROUP_ACCESS_GET_MEMBERS |
+ SAMR_GROUP_ACCESS_REMOVE_MEMBER |
+ SAMR_GROUP_ACCESS_ADD_MEMBER |
+ SAMR_GROUP_ACCESS_LOOKUP_INFO,
+ rids.ids[0],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOATTRIBUTES,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+#if 0
+ /* breaks against NT4 */
+ if (!(info->attributes.attributes & SE_GROUP_ENABLED)) {
+ werr = WERR_ACCESS_DENIED;
+ goto done;
+ }
+#endif
+ status = dcerpc_samr_QueryGroupMember(b, talloc_tos(),
+ &group_handle,
+ &rid_array,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ {
+ struct lsa_Strings names;
+ struct samr_Ids member_types;
+
+ status = dcerpc_samr_LookupRids(b, talloc_tos(),
+ &domain_handle,
+ rid_array->count,
+ rid_array->rids,
+ &names,
+ &member_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (names.count != rid_array->count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (member_types.count != rid_array->count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ }
+
+ for (i=0; i < rid_array->count; i++) {
+
+ status = dcerpc_samr_DeleteGroupMember(b, talloc_tos(),
+ &group_handle,
+ rid_array->rids[i],
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ status = dcerpc_samr_DeleteDomainGroup(b, talloc_tos(),
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ ZERO_STRUCT(group_handle);
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupDel_l(struct libnetapi_ctx *ctx,
+ struct NetGroupDel *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupDel);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetGroupSetInfo *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_group_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ struct samr_Ids rids;
+ struct samr_Ids types;
+ union samr_GroupInfo info;
+ struct GROUP_INFO_0 *g0;
+ struct GROUP_INFO_1 *g1;
+ struct GROUP_INFO_2 *g2;
+ struct GROUP_INFO_3 *g3;
+ struct GROUP_INFO_1002 *g1002;
+ struct GROUP_INFO_1005 *g1005;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_group_name, r->in.group_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_group_name,
+ &rids,
+ &types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ if (types.ids[0] != SID_NAME_DOM_GRP) {
+ werr = WERR_INVALID_DATATYPE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_SET_INFO |
+ SAMR_GROUP_ACCESS_LOOKUP_INFO,
+ rids.ids[0],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ g0 = (struct GROUP_INFO_0 *)r->in.buffer;
+ init_lsa_String(&info.name, g0->grpi0_name);
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFONAME,
+ &info,
+ &result);
+ break;
+ case 1:
+ g1 = (struct GROUP_INFO_1 *)r->in.buffer;
+ init_lsa_String(&info.description, g1->grpi1_comment);
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFODESCRIPTION,
+ &info,
+ &result);
+ break;
+ case 2:
+ g2 = (struct GROUP_INFO_2 *)r->in.buffer;
+ init_lsa_String(&info.description, g2->grpi2_comment);
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFODESCRIPTION,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ info.attributes.attributes = g2->grpi2_attributes;
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOATTRIBUTES,
+ &info,
+ &result);
+ break;
+ case 3:
+ g3 = (struct GROUP_INFO_3 *)r->in.buffer;
+ init_lsa_String(&info.description, g3->grpi3_comment);
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFODESCRIPTION,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ info.attributes.attributes = g3->grpi3_attributes;
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOATTRIBUTES,
+ &info,
+ &result);
+ break;
+ case 1002:
+ g1002 = (struct GROUP_INFO_1002 *)r->in.buffer;
+ init_lsa_String(&info.description, g1002->grpi1002_comment);
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFODESCRIPTION,
+ &info,
+ &result);
+ break;
+ case 1005:
+ g1005 = (struct GROUP_INFO_1005 *)r->in.buffer;
+ info.attributes.attributes = g1005->grpi1005_attributes;
+ status = dcerpc_samr_SetGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOATTRIBUTES,
+ &info,
+ &result);
+ break;
+ default:
+ status = NT_STATUS_INVALID_LEVEL;
+ break;
+ }
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetGroupSetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupSetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR map_group_info_to_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ struct samr_GroupInfoAll *info,
+ struct dom_sid2 *domain_sid,
+ uint32_t rid,
+ uint8_t **buffer)
+{
+ struct GROUP_INFO_0 info0;
+ struct GROUP_INFO_1 info1;
+ struct GROUP_INFO_2 info2;
+ struct GROUP_INFO_3 info3;
+ struct dom_sid sid;
+
+ switch (level) {
+ case 0:
+ info0.grpi0_name = info->name.string;
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info0, sizeof(info0));
+
+ break;
+ case 1:
+ info1.grpi1_name = info->name.string;
+ info1.grpi1_comment = info->description.string;
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info1, sizeof(info1));
+
+ break;
+ case 2:
+ info2.grpi2_name = info->name.string;
+ info2.grpi2_comment = info->description.string;
+ info2.grpi2_group_id = rid;
+ info2.grpi2_attributes = info->attributes;
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info2, sizeof(info2));
+
+ break;
+ case 3:
+ if (!sid_compose(&sid, domain_sid, rid)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ info3.grpi3_name = info->name.string;
+ info3.grpi3_comment = info->description.string;
+ info3.grpi3_attributes = info->attributes;
+ info3.grpi3_group_sid = (struct domsid *)dom_sid_dup(mem_ctx, &sid);
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info3, sizeof(info3));
+
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetGroupGetInfo *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_group_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ struct samr_Ids rids;
+ struct samr_Ids types;
+ union samr_GroupInfo *info = NULL;
+ bool group_info_all = false;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_group_name, r->in.group_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_group_name,
+ &rids,
+ &types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ if (types.ids[0] != SID_NAME_DOM_GRP) {
+ werr = WERR_INVALID_DATATYPE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_LOOKUP_INFO,
+ rids.ids[0],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOALL2,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_INVALID_INFO_CLASS)) {
+ status = dcerpc_samr_QueryGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOALL,
+ &info,
+ &result);
+ group_info_all = true;
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ werr = ntstatus_to_werror(result);
+ goto done;
+ }
+
+ werr = map_group_info_to_buffer(ctx, r->in.level,
+ group_info_all ? &info->all : &info->all2,
+ domain_sid, rids.ids[0],
+ r->out.buffer);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetGroupGetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupGetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupAddUser_r(struct libnetapi_ctx *ctx,
+ struct NetGroupAddUser *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_group_name, lsa_user_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ struct samr_Ids rids;
+ struct samr_Ids types;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_group_name, r->in.group_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_group_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ werr = WERR_NERR_GROUPNOTFOUND;
+ goto done;
+ }
+ if (rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ if (types.ids[0] != SID_NAME_DOM_GRP) {
+ werr = WERR_NERR_GROUPNOTFOUND;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_ADD_MEMBER,
+ rids.ids[0],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ init_lsa_String(&lsa_user_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_user_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ werr = WERR_NERR_USERNOTFOUND;
+ goto done;
+ }
+ if (rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ if (types.ids[0] != SID_NAME_USER) {
+ werr = WERR_NERR_USERNOTFOUND;
+ goto done;
+ }
+
+ status = dcerpc_samr_AddGroupMember(b, talloc_tos(),
+ &group_handle,
+ rids.ids[0],
+ 7, /* why ? */
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupAddUser_l(struct libnetapi_ctx *ctx,
+ struct NetGroupAddUser *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupAddUser);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupDelUser_r(struct libnetapi_ctx *ctx,
+ struct NetGroupDelUser *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_group_name, lsa_user_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ struct samr_Ids rids;
+ struct samr_Ids types;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_group_name, r->in.group_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_group_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ werr = WERR_NERR_GROUPNOTFOUND;
+ goto done;
+ }
+ if (rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ if (types.ids[0] != SID_NAME_DOM_GRP) {
+ werr = WERR_NERR_GROUPNOTFOUND;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_REMOVE_MEMBER,
+ rids.ids[0],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ init_lsa_String(&lsa_user_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_user_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ werr = WERR_NERR_USERNOTFOUND;
+ goto done;
+ }
+ if (rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ if (types.ids[0] != SID_NAME_USER) {
+ werr = WERR_NERR_USERNOTFOUND;
+ goto done;
+ }
+
+ status = dcerpc_samr_DeleteGroupMember(b, talloc_tos(),
+ &group_handle,
+ rids.ids[0],
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupDelUser_l(struct libnetapi_ctx *ctx,
+ struct NetGroupDelUser *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupDelUser);
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_disp_groups_to_GROUP_INFO_0_buffer(TALLOC_CTX *mem_ctx,
+ struct samr_DispInfoFullGroups *groups,
+ uint8_t **buffer)
+{
+ struct GROUP_INFO_0 *g0;
+ int i;
+
+ g0 = talloc_zero_array(mem_ctx, struct GROUP_INFO_0, groups->count);
+ W_ERROR_HAVE_NO_MEMORY(g0);
+
+ for (i=0; i<groups->count; i++) {
+ g0[i].grpi0_name = talloc_strdup(mem_ctx,
+ groups->entries[i].account_name.string);
+ W_ERROR_HAVE_NO_MEMORY(g0[i].grpi0_name);
+ }
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, g0,
+ sizeof(struct GROUP_INFO_0) * groups->count);
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_disp_groups_to_GROUP_INFO_1_buffer(TALLOC_CTX *mem_ctx,
+ struct samr_DispInfoFullGroups *groups,
+ uint8_t **buffer)
+{
+ struct GROUP_INFO_1 *g1;
+ int i;
+
+ g1 = talloc_zero_array(mem_ctx, struct GROUP_INFO_1, groups->count);
+ W_ERROR_HAVE_NO_MEMORY(g1);
+
+ for (i=0; i<groups->count; i++) {
+ g1[i].grpi1_name = talloc_strdup(mem_ctx,
+ groups->entries[i].account_name.string);
+ g1[i].grpi1_comment = talloc_strdup(mem_ctx,
+ groups->entries[i].description.string);
+ W_ERROR_HAVE_NO_MEMORY(g1[i].grpi1_name);
+ }
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, g1,
+ sizeof(struct GROUP_INFO_1) * groups->count);
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_disp_groups_to_GROUP_INFO_2_buffer(TALLOC_CTX *mem_ctx,
+ struct samr_DispInfoFullGroups *groups,
+ uint8_t **buffer)
+{
+ struct GROUP_INFO_2 *g2;
+ int i;
+
+ g2 = talloc_zero_array(mem_ctx, struct GROUP_INFO_2, groups->count);
+ W_ERROR_HAVE_NO_MEMORY(g2);
+
+ for (i=0; i<groups->count; i++) {
+ g2[i].grpi2_name = talloc_strdup(mem_ctx,
+ groups->entries[i].account_name.string);
+ g2[i].grpi2_comment = talloc_strdup(mem_ctx,
+ groups->entries[i].description.string);
+ g2[i].grpi2_group_id = groups->entries[i].rid;
+ g2[i].grpi2_attributes = groups->entries[i].acct_flags;
+ W_ERROR_HAVE_NO_MEMORY(g2[i].grpi2_name);
+ }
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, g2,
+ sizeof(struct GROUP_INFO_2) * groups->count);
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_disp_groups_to_GROUP_INFO_3_buffer(TALLOC_CTX *mem_ctx,
+ struct samr_DispInfoFullGroups *groups,
+ const struct dom_sid *domain_sid,
+ uint8_t **buffer)
+{
+ struct GROUP_INFO_3 *g3;
+ int i;
+
+ g3 = talloc_zero_array(mem_ctx, struct GROUP_INFO_3, groups->count);
+ W_ERROR_HAVE_NO_MEMORY(g3);
+
+ for (i=0; i<groups->count; i++) {
+
+ struct dom_sid sid;
+
+ if (!sid_compose(&sid, domain_sid, groups->entries[i].rid)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ g3[i].grpi3_name = talloc_strdup(mem_ctx,
+ groups->entries[i].account_name.string);
+ g3[i].grpi3_comment = talloc_strdup(mem_ctx,
+ groups->entries[i].description.string);
+ g3[i].grpi3_group_sid = (struct domsid *)dom_sid_dup(mem_ctx, &sid);
+ g3[i].grpi3_attributes = groups->entries[i].acct_flags;
+ W_ERROR_HAVE_NO_MEMORY(g3[i].grpi3_name);
+ }
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, g3,
+ sizeof(struct GROUP_INFO_3) * groups->count);
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_disp_groups_to_GROUP_INFO_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ struct samr_DispInfoFullGroups *groups,
+ const struct dom_sid *domain_sid,
+ uint32_t *entries_read,
+ uint8_t **buffer)
+{
+ if (entries_read) {
+ *entries_read = groups->count;
+ }
+
+ switch (level) {
+ case 0:
+ return convert_samr_disp_groups_to_GROUP_INFO_0_buffer(mem_ctx, groups, buffer);
+ case 1:
+ return convert_samr_disp_groups_to_GROUP_INFO_1_buffer(mem_ctx, groups, buffer);
+ case 2:
+ return convert_samr_disp_groups_to_GROUP_INFO_2_buffer(mem_ctx, groups, buffer);
+ case 3:
+ return convert_samr_disp_groups_to_GROUP_INFO_3_buffer(mem_ctx, groups, domain_sid, buffer);
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupEnum_r(struct libnetapi_ctx *ctx,
+ struct NetGroupEnum *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ struct policy_handle domain_handle;
+ union samr_DispInfo info;
+ union samr_DomainInfo *domain_info = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ uint32_t total_size = 0;
+ uint32_t returned_size = 0;
+
+ NTSTATUS result = NT_STATUS_OK;
+ NTSTATUS status;
+ WERROR werr, tmp_werr;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2 |
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryDomainInfo(b, talloc_tos(),
+ &domain_handle,
+ 2,
+ &domain_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (r->out.total_entries) {
+ *r->out.total_entries = domain_info->general.num_groups;
+ }
+
+ status = dcerpc_samr_QueryDisplayInfo2(b,
+ ctx,
+ &domain_handle,
+ 3,
+ r->in.resume_handle ?
+ *r->in.resume_handle : 0,
+ (uint32_t)-1,
+ r->in.prefmaxlen,
+ &total_size,
+ &returned_size,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = ntstatus_to_werror(result);
+ if (NT_STATUS_IS_ERR(result)) {
+ goto done;
+ }
+
+ if (r->out.resume_handle && info.info3.count > 0) {
+ *r->out.resume_handle =
+ info.info3.entries[info.info3.count-1].idx;
+ }
+
+ tmp_werr = convert_samr_disp_groups_to_GROUP_INFO_buffer(ctx,
+ r->in.level,
+ &info.info3,
+ domain_sid,
+ r->out.entries_read,
+ r->out.buffer);
+ if (!W_ERROR_IS_OK(tmp_werr)) {
+ werr = tmp_werr;
+ goto done;
+ }
+
+ done:
+ /* if last query */
+ if (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_IS_ERR(result)) {
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupEnum_l(struct libnetapi_ctx *ctx,
+ struct NetGroupEnum *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupEnum);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupGetUsers_r(struct libnetapi_ctx *ctx,
+ struct NetGroupGetUsers *r)
+{
+ /* FIXME: this call needs to cope with large replies */
+
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct samr_Ids group_rids, name_types;
+ struct samr_RidAttrArray *rid_array = NULL;
+ struct lsa_Strings names;
+ struct samr_Ids member_types;
+ struct dcerpc_binding_handle *b = NULL;
+
+ int i;
+ uint32_t entries_read = 0;
+
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ WERROR werr;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ *r->out.buffer = NULL;
+ *r->out.entries_read = 0;
+ *r->out.total_entries = 0;
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.group_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &group_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (group_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_GET_MEMBERS,
+ group_rids.ids[0],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryGroupMember(b, talloc_tos(),
+ &group_handle,
+ &rid_array,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_LookupRids(b, talloc_tos(),
+ &domain_handle,
+ rid_array->count,
+ rid_array->rids,
+ &names,
+ &member_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (names.count != rid_array->count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (member_types.count != rid_array->count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ for (i=0; i < names.count; i++) {
+
+ if (member_types.ids[i] != SID_NAME_USER) {
+ continue;
+ }
+
+ status = add_GROUP_USERS_INFO_X_buffer(ctx,
+ r->in.level,
+ names.names[i].string,
+ 7,
+ r->out.buffer,
+ &entries_read);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ *r->out.entries_read = entries_read;
+ *r->out.total_entries = entries_read;
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupGetUsers_l(struct libnetapi_ctx *ctx,
+ struct NetGroupGetUsers *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupGetUsers);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupSetUsers_r(struct libnetapi_ctx *ctx,
+ struct NetGroupSetUsers *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle, domain_handle, group_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ union samr_GroupInfo *group_info = NULL;
+ struct samr_Ids user_rids, name_types;
+ struct samr_Ids group_rids, group_types;
+ struct samr_RidAttrArray *rid_array = NULL;
+ struct lsa_String *lsa_names = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ uint32_t *add_rids = NULL;
+ uint32_t *del_rids = NULL;
+ size_t num_add_rids = 0;
+ size_t num_del_rids = 0;
+
+ uint32_t *member_rids = NULL;
+
+ struct GROUP_USERS_INFO_0 *i0 = NULL;
+ struct GROUP_USERS_INFO_1 *i1 = NULL;
+
+ int i, k;
+
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ WERROR werr;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.group_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &group_rids,
+ &group_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (group_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (group_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_GET_MEMBERS |
+ SAMR_GROUP_ACCESS_ADD_MEMBER |
+ SAMR_GROUP_ACCESS_REMOVE_MEMBER |
+ SAMR_GROUP_ACCESS_LOOKUP_INFO,
+ group_rids.ids[0],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryGroupInfo(b, talloc_tos(),
+ &group_handle,
+ GROUPINFOATTRIBUTES,
+ &group_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ i0 = (struct GROUP_USERS_INFO_0 *)r->in.buffer;
+ break;
+ case 1:
+ i1 = (struct GROUP_USERS_INFO_1 *)r->in.buffer;
+ break;
+ }
+
+ lsa_names = talloc_array(ctx, struct lsa_String, r->in.num_entries);
+ if (!lsa_names) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i < r->in.num_entries; i++) {
+
+ switch (r->in.level) {
+ case 0:
+ init_lsa_String(&lsa_names[i], i0->grui0_name);
+ i0++;
+ break;
+ case 1:
+ init_lsa_String(&lsa_names[i], i1->grui1_name);
+ i1++;
+ break;
+ }
+ }
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ r->in.num_entries,
+ lsa_names,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (r->in.num_entries != user_rids.count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (r->in.num_entries != name_types.count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ member_rids = user_rids.ids;
+
+ status = dcerpc_samr_QueryGroupMember(b, talloc_tos(),
+ &group_handle,
+ &rid_array,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ /* add list */
+
+ for (i=0; i < r->in.num_entries; i++) {
+ bool already_member = false;
+ for (k=0; k < rid_array->count; k++) {
+ if (member_rids[i] == rid_array->rids[k]) {
+ already_member = true;
+ break;
+ }
+ }
+ if (!already_member) {
+ if (!add_rid_to_array_unique(ctx,
+ member_rids[i],
+ &add_rids, &num_add_rids)) {
+ werr = WERR_GEN_FAILURE;
+ goto done;
+ }
+ }
+ }
+
+ /* del list */
+
+ for (k=0; k < rid_array->count; k++) {
+ bool keep_member = false;
+ for (i=0; i < r->in.num_entries; i++) {
+ if (member_rids[i] == rid_array->rids[k]) {
+ keep_member = true;
+ break;
+ }
+ }
+ if (!keep_member) {
+ if (!add_rid_to_array_unique(ctx,
+ rid_array->rids[k],
+ &del_rids, &num_del_rids)) {
+ werr = WERR_GEN_FAILURE;
+ goto done;
+ }
+ }
+ }
+
+ /* add list */
+
+ for (i=0; i < num_add_rids; i++) {
+ status = dcerpc_samr_AddGroupMember(b, talloc_tos(),
+ &group_handle,
+ add_rids[i],
+ 7 /* ? */,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ /* del list */
+
+ for (i=0; i < num_del_rids; i++) {
+ status = dcerpc_samr_DeleteGroupMember(b, talloc_tos(),
+ &group_handle,
+ del_rids[i],
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGroupSetUsers_l(struct libnetapi_ctx *ctx,
+ struct NetGroupSetUsers *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetGroupSetUsers);
+}
diff --git a/source3/lib/netapi/joindomain.c b/source3/lib/netapi/joindomain.c
new file mode 100644
index 0000000..04fc423
--- /dev/null
+++ b/source3/lib/netapi/joindomain.c
@@ -0,0 +1,1145 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Join Support
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ads.h"
+#include "librpc/gen_ndr/libnetapi.h"
+#include "libcli/auth/libcli_auth.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "librpc/gen_ndr/libnet_join.h"
+#include "libnet/libnet_join.h"
+#include "../librpc/gen_ndr/ndr_wkssvc_c.h"
+#include "rpc_client/cli_pipe.h"
+#include "secrets.h"
+#include "libsmb/dsgetdcname.h"
+#include "../librpc/gen_ndr/ndr_ODJ.h"
+#include "lib/util/base64.h"
+#include "libnet/libnet_join_offline.h"
+#include "libcli/security/dom_sid.h"
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetJoinDomain_l(struct libnetapi_ctx *mem_ctx,
+ struct NetJoinDomain *r)
+{
+ struct libnet_JoinCtx *j = NULL;
+ struct libnetapi_private_ctx *priv;
+ WERROR werr;
+
+ priv = talloc_get_type_abort(mem_ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ if (!r->in.domain) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnet_init_JoinCtx(mem_ctx, &j);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ j->in.domain_name = talloc_strdup(mem_ctx, r->in.domain);
+ W_ERROR_HAVE_NO_MEMORY(j->in.domain_name);
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+ NTSTATUS status;
+ struct netr_DsRGetDCNameInfo *info = NULL;
+ const char *dc = NULL;
+ uint32_t flags = DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME;
+ status = dsgetdcname(mem_ctx, priv->msg_ctx, r->in.domain,
+ NULL, NULL, flags, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnetapi_set_error_string(mem_ctx,
+ "%s", get_friendly_nt_error_msg(status));
+ return ntstatus_to_werror(status);
+ }
+
+ dc = strip_hostname(info->dc_unc);
+ j->in.dc_name = talloc_strdup(mem_ctx, dc);
+ W_ERROR_HAVE_NO_MEMORY(j->in.dc_name);
+ }
+
+ if (r->in.account_ou) {
+ j->in.account_ou = talloc_strdup(mem_ctx, r->in.account_ou);
+ W_ERROR_HAVE_NO_MEMORY(j->in.account_ou);
+ }
+
+ if (r->in.account) {
+ j->in.admin_account = talloc_strdup(mem_ctx, r->in.account);
+ W_ERROR_HAVE_NO_MEMORY(j->in.admin_account);
+ }
+
+ if (r->in.password) {
+ j->in.admin_password = talloc_strdup(mem_ctx, r->in.password);
+ W_ERROR_HAVE_NO_MEMORY(j->in.admin_password);
+ }
+
+ j->in.join_flags = r->in.join_flags;
+ j->in.modify_config = true;
+ j->in.debug = true;
+
+ werr = libnet_Join(mem_ctx, j);
+ if (!W_ERROR_IS_OK(werr) && j->out.error_string) {
+ libnetapi_set_error_string(mem_ctx, "%s", j->out.error_string);
+ }
+ TALLOC_FREE(j);
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetJoinDomain_r(struct libnetapi_ctx *ctx,
+ struct NetJoinDomain *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct wkssvc_PasswordBuffer *encrypted_password = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ unsigned int old_timeout = 0;
+ struct dcerpc_binding_handle *b;
+ DATA_BLOB session_key;
+
+ if (IS_DC) {
+ return WERR_NERR_SETUPDOMAINCONTROLLER;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server,
+ &ndr_table_wkssvc,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ if (r->in.password) {
+
+ status = cli_get_session_key(talloc_tos(), pipe_cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = encode_wkssvc_join_password_buffer(ctx,
+ r->in.password,
+ &session_key,
+ &encrypted_password);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ }
+
+ old_timeout = rpccli_set_timeout(pipe_cli, 600000);
+
+ status = dcerpc_wkssvc_NetrJoinDomain2(b, talloc_tos(),
+ r->in.server,
+ r->in.domain,
+ r->in.account_ou,
+ r->in.account,
+ encrypted_password,
+ r->in.join_flags,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ if (pipe_cli && old_timeout) {
+ rpccli_set_timeout(pipe_cli, old_timeout);
+ }
+
+ return werr;
+}
+/****************************************************************
+****************************************************************/
+
+WERROR NetUnjoinDomain_l(struct libnetapi_ctx *mem_ctx,
+ struct NetUnjoinDomain *r)
+{
+ struct libnet_UnjoinCtx *u = NULL;
+ struct dom_sid domain_sid;
+ const char *domain = NULL;
+ WERROR werr;
+ struct libnetapi_private_ctx *priv;
+ const char *realm = lp_realm();
+
+ priv = talloc_get_type_abort(mem_ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ if (!secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) {
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+
+ werr = libnet_init_UnjoinCtx(mem_ctx, &u);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ if (realm[0] != '\0') {
+ domain = realm;
+ } else {
+ domain = lp_workgroup();
+ }
+
+ if (r->in.server_name) {
+ u->in.dc_name = talloc_strdup(mem_ctx, r->in.server_name);
+ W_ERROR_HAVE_NO_MEMORY(u->in.dc_name);
+ } else {
+ NTSTATUS status;
+ struct netr_DsRGetDCNameInfo *info = NULL;
+ const char *dc = NULL;
+ uint32_t flags = DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME;
+ status = dsgetdcname(mem_ctx, priv->msg_ctx, domain,
+ NULL, NULL, flags, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnetapi_set_error_string(mem_ctx,
+ "failed to find DC for domain %s: %s",
+ domain,
+ get_friendly_nt_error_msg(status));
+ return ntstatus_to_werror(status);
+ }
+
+ dc = strip_hostname(info->dc_unc);
+ u->in.dc_name = talloc_strdup(mem_ctx, dc);
+ W_ERROR_HAVE_NO_MEMORY(u->in.dc_name);
+
+ u->in.domain_name = domain;
+ }
+
+ if (r->in.account) {
+ u->in.admin_account = talloc_strdup(mem_ctx, r->in.account);
+ W_ERROR_HAVE_NO_MEMORY(u->in.admin_account);
+ }
+
+ if (r->in.password) {
+ u->in.admin_password = talloc_strdup(mem_ctx, r->in.password);
+ W_ERROR_HAVE_NO_MEMORY(u->in.admin_password);
+ }
+
+ u->in.domain_name = domain;
+ u->in.unjoin_flags = r->in.unjoin_flags;
+ u->in.delete_machine_account = false;
+ u->in.modify_config = true;
+ u->in.debug = true;
+
+ u->in.domain_sid = &domain_sid;
+
+ werr = libnet_Unjoin(mem_ctx, u);
+ if (!W_ERROR_IS_OK(werr) && u->out.error_string) {
+ libnetapi_set_error_string(mem_ctx, "%s", u->out.error_string);
+ }
+ TALLOC_FREE(u);
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUnjoinDomain_r(struct libnetapi_ctx *ctx,
+ struct NetUnjoinDomain *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct wkssvc_PasswordBuffer *encrypted_password = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ unsigned int old_timeout = 0;
+ struct dcerpc_binding_handle *b;
+ DATA_BLOB session_key;
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_wkssvc,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ if (r->in.password) {
+
+ status = cli_get_session_key(talloc_tos(), pipe_cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = encode_wkssvc_join_password_buffer(ctx,
+ r->in.password,
+ &session_key,
+ &encrypted_password);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ }
+
+ old_timeout = rpccli_set_timeout(pipe_cli, 60000);
+
+ status = dcerpc_wkssvc_NetrUnjoinDomain2(b, talloc_tos(),
+ r->in.server_name,
+ r->in.account,
+ encrypted_password,
+ r->in.unjoin_flags,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ if (pipe_cli && old_timeout) {
+ rpccli_set_timeout(pipe_cli, old_timeout);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGetJoinInformation_r(struct libnetapi_ctx *ctx,
+ struct NetGetJoinInformation *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ const char *buffer = NULL;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_wkssvc,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ status = dcerpc_wkssvc_NetrGetJoinInformation(b, talloc_tos(),
+ r->in.server_name,
+ &buffer,
+ (enum wkssvc_NetJoinStatus *)r->out.name_type,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ *r->out.name_buffer = talloc_strdup(ctx, buffer);
+ W_ERROR_HAVE_NO_MEMORY(*r->out.name_buffer);
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGetJoinInformation_l(struct libnetapi_ctx *ctx,
+ struct NetGetJoinInformation *r)
+{
+ const char *realm = lp_realm();
+
+ if ((lp_security() == SEC_ADS) && realm[0] != '\0') {
+ *r->out.name_buffer = talloc_strdup(ctx, realm);
+ } else {
+ *r->out.name_buffer = talloc_strdup(ctx, lp_workgroup());
+ }
+ if (!*r->out.name_buffer) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ switch (lp_server_role()) {
+ case ROLE_DOMAIN_MEMBER:
+ case ROLE_DOMAIN_PDC:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_IPA_DC:
+ *r->out.name_type = NetSetupDomainName;
+ break;
+ case ROLE_STANDALONE:
+ default:
+ *r->out.name_type = NetSetupWorkgroupName;
+ break;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGetJoinableOUs_l(struct libnetapi_ctx *ctx,
+ struct NetGetJoinableOUs *r)
+{
+#ifdef HAVE_ADS
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ WERROR ret;
+ NTSTATUS status;
+ ADS_STATUS ads_status;
+ ADS_STRUCT *ads = NULL;
+ struct netr_DsRGetDCNameInfo *info = NULL;
+ const char *dc = NULL;
+ uint32_t flags = DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_RETURN_DNS_NAME;
+ struct libnetapi_private_ctx *priv;
+ char **p;
+ size_t s;
+
+ priv = talloc_get_type_abort(ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ status = dsgetdcname(tmp_ctx, priv->msg_ctx, r->in.domain,
+ NULL, NULL, flags, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnetapi_set_error_string(ctx, "%s",
+ get_friendly_nt_error_msg(status));
+ ret = ntstatus_to_werror(status);
+ goto out;
+ }
+
+ dc = strip_hostname(info->dc_unc);
+
+ ads = ads_init(tmp_ctx,
+ info->domain_name,
+ info->domain_name,
+ dc,
+ ADS_SASL_PLAIN);
+ if (!ads) {
+ ret = WERR_GEN_FAILURE;
+ goto out;
+ }
+
+ ADS_TALLOC_CONST_FREE(ads->auth.user_name);
+ if (r->in.account) {
+ ads->auth.user_name = talloc_strdup(ads, r->in.account);
+ if (ads->auth.user_name == NULL) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+ } else {
+ const char *username = NULL;
+
+ libnetapi_get_username(ctx, &username);
+ if (username != NULL) {
+ ads->auth.user_name = talloc_strdup(ads, username);
+ if (ads->auth.user_name == NULL) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+ }
+ }
+
+ ADS_TALLOC_CONST_FREE(ads->auth.password);
+ if (r->in.password) {
+ ads->auth.password = talloc_strdup(ads, r->in.password);
+ if (ads->auth.password == NULL) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+ } else {
+ const char *password = NULL;
+
+ libnetapi_get_password(ctx, &password);
+ if (password != NULL) {
+ ads->auth.password = talloc_strdup(ads, password);
+ if (ads->auth.password == NULL) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+ }
+ }
+
+ ads_status = ads_connect_user_creds(ads);
+ if (!ADS_ERR_OK(ads_status)) {
+ ret = WERR_NERR_DEFAULTJOINREQUIRED;
+ goto out;
+ }
+
+ ads_status = ads_get_joinable_ous(ads, ctx, &p, &s);
+ if (!ADS_ERR_OK(ads_status)) {
+ ret = WERR_NERR_DEFAULTJOINREQUIRED;
+ goto out;
+ }
+ *r->out.ous = discard_const_p(const char *, p);
+ *r->out.ou_count = s;
+
+ ret = WERR_OK;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return ret;
+#else
+ return WERR_NOT_SUPPORTED;
+#endif
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetGetJoinableOUs_r(struct libnetapi_ctx *ctx,
+ struct NetGetJoinableOUs *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct wkssvc_PasswordBuffer *encrypted_password = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b;
+ DATA_BLOB session_key;
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_wkssvc,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ if (r->in.password) {
+
+ status = cli_get_session_key(talloc_tos(), pipe_cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = encode_wkssvc_join_password_buffer(ctx,
+ r->in.password,
+ &session_key,
+ &encrypted_password);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ }
+
+ status = dcerpc_wkssvc_NetrGetJoinableOus2(b, talloc_tos(),
+ r->in.server_name,
+ r->in.domain,
+ r->in.account,
+ encrypted_password,
+ r->out.ou_count,
+ r->out.ous,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetRenameMachineInDomain_r(struct libnetapi_ctx *ctx,
+ struct NetRenameMachineInDomain *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct wkssvc_PasswordBuffer *encrypted_password = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b;
+ DATA_BLOB session_key;
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_wkssvc,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ if (r->in.password) {
+
+ status = cli_get_session_key(talloc_tos(), pipe_cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = encode_wkssvc_join_password_buffer(ctx,
+ r->in.password,
+ &session_key,
+ &encrypted_password);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ }
+
+ status = dcerpc_wkssvc_NetrRenameMachineInDomain2(b, talloc_tos(),
+ r->in.server_name,
+ r->in.new_machine_name,
+ r->in.account,
+ encrypted_password,
+ r->in.rename_options,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetRenameMachineInDomain_l(struct libnetapi_ctx *ctx,
+ struct NetRenameMachineInDomain *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetRenameMachineInDomain);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetProvisionComputerAccount_r(struct libnetapi_ctx *ctx,
+ struct NetProvisionComputerAccount *r)
+{
+ return NetProvisionComputerAccount_l(ctx, r);
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR NetProvisionComputerAccount_backend(struct libnetapi_ctx *ctx,
+ struct NetProvisionComputerAccount *r,
+ TALLOC_CTX *mem_ctx,
+ struct ODJ_PROVISION_DATA **p)
+{
+ WERROR werr;
+ struct libnet_JoinCtx *j = NULL;
+ int use_kerberos = 0;
+ const char *username = NULL;
+
+ werr = libnet_init_JoinCtx(mem_ctx, &j);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ j->in.domain_name = talloc_strdup(j, r->in.domain);
+ if (j->in.domain_name == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ talloc_free(discard_const_p(char *, j->in.machine_name));
+ j->in.machine_name = talloc_strdup(j, r->in.machine_name);
+ if (j->in.machine_name == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (r->in.dcname) {
+ j->in.dc_name = talloc_strdup(j, r->in.dcname);
+ if (j->in.dc_name == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (r->in.machine_account_ou) {
+ j->in.account_ou = talloc_strdup(j, r->in.machine_account_ou);
+ if (j->in.account_ou == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ libnetapi_get_username(ctx, &username);
+ if (username == NULL) {
+ talloc_free(j);
+ return WERR_NERR_BADUSERNAME;
+ }
+
+ j->in.admin_account = talloc_strdup(j, username);
+ if (j->in.admin_account == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ libnetapi_get_use_kerberos(ctx, &use_kerberos);
+ if (!use_kerberos) {
+ const char *password = NULL;
+
+ libnetapi_get_password(ctx, &password);
+ if (password == NULL) {
+ talloc_free(j);
+ return WERR_NERR_BADPASSWORD;
+ }
+ j->in.admin_password = talloc_strdup(j, password);
+ if (j->in.admin_password == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ j->in.use_kerberos = use_kerberos;
+ j->in.debug = true;
+ j->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE;
+
+ if (r->in.options & NETSETUP_PROVISION_REUSE_ACCOUNT) {
+ j->in.join_flags |= WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED;
+ }
+
+ if (r->in.options & NETSETUP_PROVISION_USE_DEFAULT_PASSWORD) {
+ j->in.join_flags |= WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED;
+ j->in.machine_password = talloc_strdup(j, r->in.machine_name);
+ if (j->in.machine_password == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ j->in.provision_computer_account_only = true;
+
+ werr = libnet_Join(mem_ctx, j);
+ if (!W_ERROR_IS_OK(werr) && j->out.error_string) {
+ libnetapi_set_error_string(ctx, "%s", j->out.error_string);
+ talloc_free(j);
+ return werr;
+ }
+
+ werr = libnet_odj_compose_ODJ_PROVISION_DATA(mem_ctx, j, p);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(j);
+ return werr;
+ }
+
+ TALLOC_FREE(j);
+
+ return WERR_OK;
+}
+
+WERROR NetProvisionComputerAccount_l(struct libnetapi_ctx *ctx,
+ struct NetProvisionComputerAccount *r)
+{
+ WERROR werr;
+ enum ndr_err_code ndr_err;
+ const char *b64_bin_data_str;
+ DATA_BLOB blob;
+ struct ODJ_PROVISION_DATA_serialized_ptr odj_provision_data;
+ struct ODJ_PROVISION_DATA *p;
+ TALLOC_CTX *mem_ctx = talloc_new(ctx);
+
+ if (r->in.provision_bin_data == NULL &&
+ r->in.provision_text_data == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+ if (r->in.provision_bin_data != NULL &&
+ r->in.provision_text_data != NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+ if (r->in.provision_bin_data == NULL &&
+ r->in.provision_bin_data_size != NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+ if (r->in.provision_bin_data != NULL &&
+ r->in.provision_bin_data_size == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (r->in.domain == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (r->in.machine_name == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = NetProvisionComputerAccount_backend(ctx, r, mem_ctx, &p);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(mem_ctx);
+ return werr;
+ }
+
+ ZERO_STRUCT(odj_provision_data);
+
+ odj_provision_data.s.p = p;
+
+ ndr_err = ndr_push_struct_blob(&blob, ctx, &odj_provision_data,
+ (ndr_push_flags_fn_t)ndr_push_ODJ_PROVISION_DATA_serialized_ptr);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(mem_ctx);
+ return W_ERROR(NERR_BadOfflineJoinInfo);
+ }
+
+ talloc_free(mem_ctx);
+
+ if (r->out.provision_text_data != NULL) {
+ b64_bin_data_str = base64_encode_data_blob(ctx, blob);
+ if (b64_bin_data_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *r->out.provision_text_data = b64_bin_data_str;
+ }
+
+ if (r->out.provision_bin_data != NULL &&
+ r->out.provision_bin_data_size != NULL) {
+ *r->out.provision_bin_data = blob.data;
+ *r->out.provision_bin_data_size = blob.length;
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetRequestOfflineDomainJoin_r(struct libnetapi_ctx *ctx,
+ struct NetRequestOfflineDomainJoin *r)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR NetRequestOfflineDomainJoin_backend(struct libnetapi_ctx *ctx,
+ const struct ODJ_WIN7BLOB *win7blob,
+ const struct ODJ_PROVISION_DATA *odj_provision_data)
+{
+ struct libnet_JoinCtx *j = NULL;
+ WERROR werr;
+
+ werr = libnet_init_JoinCtx(ctx, &j);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ j->in.domain_name = talloc_strdup(j, win7blob->lpDomain);
+ if (j->in.domain_name == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ talloc_free(discard_const_p(char *, j->in.machine_name));
+ j->in.machine_name = talloc_strdup(j, win7blob->lpMachineName);
+ if (j->in.machine_name == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ j->in.machine_password = talloc_strdup(j, win7blob->lpMachinePassword);
+ if (j->in.machine_password == NULL) {
+ talloc_free(j);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ j->in.request_offline_join = true;
+ j->in.odj_provision_data = discard_const(odj_provision_data);
+ j->in.debug = true;
+ j->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED;
+
+ werr = libnet_Join(j, j);
+ if (!W_ERROR_IS_OK(werr)) {
+ if (j->out.error_string != NULL) {
+ libnetapi_set_error_string(ctx, "%s", j->out.error_string);
+ }
+ talloc_free(j);
+ return werr;
+ }
+
+ TALLOC_FREE(j);
+
+ return WERR_OK;
+}
+
+WERROR NetRequestOfflineDomainJoin_l(struct libnetapi_ctx *ctx,
+ struct NetRequestOfflineDomainJoin *r)
+{
+ DATA_BLOB blob, blob_base64;
+ enum ndr_err_code ndr_err;
+ struct ODJ_PROVISION_DATA_serialized_ptr odj_provision_data;
+ bool ok;
+ struct ODJ_WIN7BLOB win7blob = { 0 };
+ WERROR werr;
+
+ if (r->in.provision_bin_data == NULL ||
+ r->in.provision_bin_data_size == 0) {
+ return W_ERROR(NERR_NoOfflineJoinInfo);
+ }
+
+ if (r->in.provision_bin_data_size < 2) {
+ return W_ERROR(NERR_BadOfflineJoinInfo);
+ }
+
+ /*
+ * Windows produces and consumes UTF16/UCS2 encoded blobs. Check for the
+ * unicode BOM mark and convert back to UNIX charset if necessary.
+ */
+ if (r->in.provision_bin_data[0] == 0xff &&
+ r->in.provision_bin_data[1] == 0xfe) {
+ ok = convert_string_talloc(ctx, CH_UTF16LE, CH_UNIX,
+ r->in.provision_bin_data+2,
+ r->in.provision_bin_data_size-2,
+ &blob_base64.data,
+ &blob_base64.length);
+ if (!ok) {
+ return W_ERROR(NERR_BadOfflineJoinInfo);
+ }
+ } else {
+ blob_base64 = data_blob(r->in.provision_bin_data,
+ r->in.provision_bin_data_size);
+ }
+
+ blob = base64_decode_data_blob_talloc(ctx, (const char *)blob_base64.data);
+
+ ndr_err = ndr_pull_struct_blob(&blob, ctx, &odj_provision_data,
+ (ndr_pull_flags_fn_t)ndr_pull_ODJ_PROVISION_DATA_serialized_ptr);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return W_ERROR(NERR_BadOfflineJoinInfo);
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(ODJ_PROVISION_DATA_serialized_ptr, &odj_provision_data);
+ }
+
+ if (odj_provision_data.s.p->ulVersion != 1) {
+ return W_ERROR(NERR_ProvisioningBlobUnsupported);
+ }
+
+ werr = libnet_odj_find_win7blob(odj_provision_data.s.p, &win7blob);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ if (!(r->in.options & NETSETUP_PROVISION_ONLINE_CALLER)) {
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+
+ werr = NetRequestOfflineDomainJoin_backend(ctx,
+ &win7blob,
+ odj_provision_data.s.p);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return W_ERROR(NERR_JoinPerformedMustRestart);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetComposeOfflineDomainJoin_r(struct libnetapi_ctx *ctx,
+ struct NetComposeOfflineDomainJoin *r)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR NetComposeOfflineDomainJoin_backend(struct libnetapi_ctx *ctx,
+ struct NetComposeOfflineDomainJoin *r,
+ TALLOC_CTX *mem_ctx,
+ struct ODJ_PROVISION_DATA **p)
+{
+ struct libnet_JoinCtx *j = NULL;
+ WERROR werr;
+
+ werr = libnet_init_JoinCtx(ctx, &j);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ j->in.domain_name = talloc_strdup(j, r->in.dns_domain_name);
+ if (j->in.domain_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ j->in.dc_name = talloc_strdup(j, r->in.dc_name);
+ W_ERROR_HAVE_NO_MEMORY(j->in.dc_name);
+
+ j->in.machine_password = talloc_strdup(j, r->in.machine_account_password);
+ W_ERROR_HAVE_NO_MEMORY(j->in.machine_password);
+
+ j->out.account_name = talloc_strdup(j, r->in.machine_account_name);
+ W_ERROR_HAVE_NO_MEMORY(j->out.account_name);
+
+ j->out.dns_domain_name = talloc_strdup(j, r->in.dns_domain_name);
+ W_ERROR_HAVE_NO_MEMORY(j->out.dns_domain_name);
+
+ j->out.netbios_domain_name = talloc_strdup(j, r->in.netbios_domain_name);
+ W_ERROR_HAVE_NO_MEMORY(j->out.netbios_domain_name);
+
+ j->out.domain_sid = dom_sid_dup(j, (struct dom_sid *)r->in.domain_sid);
+ W_ERROR_HAVE_NO_MEMORY(j->out.domain_sid);
+
+ j->out.domain_guid = *r->in.domain_guid;
+
+ j->out.forest_name = talloc_strdup(j, r->in.forest_name);
+ W_ERROR_HAVE_NO_MEMORY(j->out.forest_name);
+
+ j->out.domain_is_ad = r->in.domain_is_ad;
+
+ j->out.dcinfo = talloc_zero(j, struct netr_DsRGetDCNameInfo);
+ W_ERROR_HAVE_NO_MEMORY(j->out.dcinfo);
+
+ j->out.dcinfo->dc_unc = talloc_asprintf(j->out.dcinfo, "\\\\%s", r->in.dc_name);
+ W_ERROR_HAVE_NO_MEMORY(j->out.dcinfo->dc_unc);
+
+ j->out.dcinfo->dc_address = talloc_asprintf(j->out.dcinfo, "\\\\%s", r->in.dc_address);
+ W_ERROR_HAVE_NO_MEMORY(j->out.dcinfo->dc_address);
+
+ j->out.dcinfo->dc_address_type = DS_ADDRESS_TYPE_INET;
+
+ j->out.dcinfo->domain_guid = *r->in.domain_guid;
+
+ j->out.dcinfo->domain_name = talloc_strdup(j->out.dcinfo, r->in.dns_domain_name);
+ W_ERROR_HAVE_NO_MEMORY(j->out.dcinfo->domain_name);
+
+ j->out.dcinfo->forest_name = talloc_strdup(j->out.dcinfo, r->in.forest_name);
+ W_ERROR_HAVE_NO_MEMORY(j->out.dcinfo->forest_name);
+
+ werr = libnet_odj_compose_ODJ_PROVISION_DATA(mem_ctx, j, p);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
+WERROR NetComposeOfflineDomainJoin_l(struct libnetapi_ctx *ctx,
+ struct NetComposeOfflineDomainJoin *r)
+{
+ WERROR werr;
+ enum ndr_err_code ndr_err;
+ const char *b64_bin_data_str;
+ DATA_BLOB blob;
+ struct ODJ_PROVISION_DATA_serialized_ptr odj_compose_data;
+ struct ODJ_PROVISION_DATA *p;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if (r->in.compose_bin_data == NULL &&
+ r->in.compose_text_data == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+ if (r->in.compose_bin_data != NULL &&
+ r->in.compose_text_data != NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+ if (r->in.compose_bin_data == NULL &&
+ r->in.compose_bin_data_size != NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+ if (r->in.compose_bin_data != NULL &&
+ r->in.compose_bin_data_size == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.dns_domain_name == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.netbios_domain_name == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.domain_sid == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.domain_guid == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.forest_name == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.machine_account_name == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.machine_account_password == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.dc_name == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r->in.dc_address == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto out;
+ }
+
+ werr = NetComposeOfflineDomainJoin_backend(ctx, r, tmp_ctx, &p);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto out;
+ }
+
+ ZERO_STRUCT(odj_compose_data);
+
+ odj_compose_data.s.p = p;
+
+ ndr_err = ndr_push_struct_blob(&blob, ctx, &odj_compose_data,
+ (ndr_push_flags_fn_t)ndr_push_ODJ_PROVISION_DATA_serialized_ptr);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ werr = W_ERROR(NERR_BadOfflineJoinInfo);
+ goto out;
+ }
+
+ if (r->out.compose_text_data != NULL) {
+ b64_bin_data_str = base64_encode_data_blob(ctx, blob);
+ if (b64_bin_data_str == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ }
+ *r->out.compose_text_data = b64_bin_data_str;
+ }
+
+ if (r->out.compose_bin_data != NULL &&
+ r->out.compose_bin_data_size != NULL) {
+ *r->out.compose_bin_data = blob.data;
+ *r->out.compose_bin_data_size = blob.length;
+ }
+
+ werr = WERR_OK;
+out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
diff --git a/source3/lib/netapi/libnetapi.c b/source3/lib/netapi/libnetapi.c
new file mode 100644
index 0000000..2907316
--- /dev/null
+++ b/source3/lib/netapi/libnetapi.c
@@ -0,0 +1,3019 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "librpc/gen_ndr/ndr_libnetapi.h"
+
+/****************************************************************
+ NetJoinDomain
+****************************************************************/
+
+NET_API_STATUS NetJoinDomain(const char * server /* [in] [unique] */,
+ const char * domain /* [in] [ref] */,
+ const char * account_ou /* [in] [unique] */,
+ const char * account /* [in] [unique] */,
+ const char * password /* [in] [unique] */,
+ uint32_t join_flags /* [in] */)
+{
+ struct NetJoinDomain r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server = server;
+ r.in.domain = domain;
+ r.in.account_ou = account_ou;
+ r.in.account = account;
+ r.in.password = password;
+ r.in.join_flags = join_flags;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetJoinDomain, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server)) {
+ werr = NetJoinDomain_l(ctx, &r);
+ } else {
+ werr = NetJoinDomain_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetJoinDomain, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUnjoinDomain
+****************************************************************/
+
+NET_API_STATUS NetUnjoinDomain(const char * server_name /* [in] [unique] */,
+ const char * account /* [in] [unique] */,
+ const char * password /* [in] [unique] */,
+ uint32_t unjoin_flags /* [in] */)
+{
+ struct NetUnjoinDomain r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.account = account;
+ r.in.password = password;
+ r.in.unjoin_flags = unjoin_flags;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUnjoinDomain, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUnjoinDomain_l(ctx, &r);
+ } else {
+ werr = NetUnjoinDomain_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUnjoinDomain, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGetJoinInformation
+****************************************************************/
+
+NET_API_STATUS NetGetJoinInformation(const char * server_name /* [in] [unique] */,
+ const char * *name_buffer /* [out] [ref] */,
+ uint16_t *name_type /* [out] [ref] */)
+{
+ struct NetGetJoinInformation r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+
+ /* Out parameters */
+ r.out.name_buffer = name_buffer;
+ r.out.name_type = name_type;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGetJoinInformation, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGetJoinInformation_l(ctx, &r);
+ } else {
+ werr = NetGetJoinInformation_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGetJoinInformation, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGetJoinableOUs
+****************************************************************/
+
+NET_API_STATUS NetGetJoinableOUs(const char * server_name /* [in] [unique] */,
+ const char * domain /* [in] [ref] */,
+ const char * account /* [in] [unique] */,
+ const char * password /* [in] [unique] */,
+ uint32_t *ou_count /* [out] [ref] */,
+ const char * **ous /* [out] [ref] */)
+{
+ struct NetGetJoinableOUs r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.domain = domain;
+ r.in.account = account;
+ r.in.password = password;
+
+ /* Out parameters */
+ r.out.ou_count = ou_count;
+ r.out.ous = ous;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGetJoinableOUs, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGetJoinableOUs_l(ctx, &r);
+ } else {
+ werr = NetGetJoinableOUs_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGetJoinableOUs, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetRenameMachineInDomain
+****************************************************************/
+
+NET_API_STATUS NetRenameMachineInDomain(const char * server_name /* [in] */,
+ const char * new_machine_name /* [in] */,
+ const char * account /* [in] */,
+ const char * password /* [in] */,
+ uint32_t rename_options /* [in] */)
+{
+ struct NetRenameMachineInDomain r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.new_machine_name = new_machine_name;
+ r.in.account = account;
+ r.in.password = password;
+ r.in.rename_options = rename_options;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetRenameMachineInDomain, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetRenameMachineInDomain_l(ctx, &r);
+ } else {
+ werr = NetRenameMachineInDomain_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetRenameMachineInDomain, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetProvisionComputerAccount
+****************************************************************/
+
+NET_API_STATUS NetProvisionComputerAccount(const char * domain /* [in] [ref] */,
+ const char * machine_name /* [in] [ref] */,
+ const char * machine_account_ou /* [in] [unique] */,
+ const char * dcname /* [in] [unique] */,
+ uint32_t options /* [in] */,
+ uint8_t **provision_bin_data /* [in,out] [unique] */,
+ uint32_t *provision_bin_data_size /* [in,out] [unique] */,
+ const char * *provision_text_data /* [in,out] [unique] */)
+{
+ struct NetProvisionComputerAccount r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.domain = domain;
+ r.in.machine_name = machine_name;
+ r.in.machine_account_ou = machine_account_ou;
+ r.in.dcname = dcname;
+ r.in.options = options;
+ r.in.provision_bin_data = provision_bin_data;
+ r.in.provision_bin_data_size = provision_bin_data_size;
+ r.in.provision_text_data = provision_text_data;
+
+ /* Out parameters */
+ r.out.provision_bin_data = provision_bin_data;
+ r.out.provision_bin_data_size = provision_bin_data_size;
+ r.out.provision_text_data = provision_text_data;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetProvisionComputerAccount, &r);
+ }
+
+ werr = NetProvisionComputerAccount_l(ctx, &r);
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetProvisionComputerAccount, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetRequestOfflineDomainJoin
+****************************************************************/
+
+NET_API_STATUS NetRequestOfflineDomainJoin(uint8_t *provision_bin_data /* [in] [unique] */,
+ uint32_t provision_bin_data_size /* [in] */,
+ uint32_t options /* [in] */,
+ const char * windows_path /* [in] [unique] */)
+{
+ struct NetRequestOfflineDomainJoin r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.provision_bin_data = provision_bin_data;
+ r.in.provision_bin_data_size = provision_bin_data_size;
+ r.in.options = options;
+ r.in.windows_path = windows_path;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetRequestOfflineDomainJoin, &r);
+ }
+
+ werr = NetRequestOfflineDomainJoin_l(ctx, &r);
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetRequestOfflineDomainJoin, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetComposeOfflineDomainJoin
+****************************************************************/
+NET_API_STATUS NetComposeOfflineDomainJoin(const char *dns_domain_name /* [in] [ref] */,
+ const char *netbios_domain_name /* [in] [ref] */,
+ struct domsid *domain_sid /* [in] [ref] */,
+ struct GUID *domain_guid /* [in] [ref] */,
+ const char *forest_name /* [in] [ref] */,
+ const char *machine_account_name /* [in] [ref] */,
+ const char *machine_account_password /* [in] [ref] */,
+ const char *dc_name /* [in] [unique] */,
+ const char *dc_address /* [in] [unique] */,
+ int domain_is_ad /* [in] */,
+ uint8_t **compose_bin_data /* [in,out] [unique] */,
+ uint32_t *compose_bin_data_size /* [in,out] [unique] */,
+ const char * *compose_text_data /* [in,out] [unique] */)
+{
+ struct NetComposeOfflineDomainJoin r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.dns_domain_name = dns_domain_name;
+ r.in.netbios_domain_name = netbios_domain_name;
+ r.in.domain_sid = domain_sid;
+ r.in.domain_guid = domain_guid;
+ r.in.forest_name = forest_name;
+ r.in.machine_account_name = machine_account_name;
+ r.in.machine_account_password = machine_account_password;
+ r.in.dc_name = dc_name;
+ r.in.dc_address = dc_address;
+ r.in.domain_is_ad = domain_is_ad;
+ r.in.compose_bin_data = compose_bin_data;
+ r.in.compose_bin_data_size = compose_bin_data_size;
+ r.in.compose_text_data = compose_text_data;
+
+ /* Out parameters */
+ r.out.compose_bin_data = compose_bin_data;
+ r.out.compose_bin_data_size = compose_bin_data_size;
+ r.out.compose_text_data = compose_text_data;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetComposeOfflineDomainJoin, &r);
+ }
+
+ werr = NetComposeOfflineDomainJoin_l(ctx, &r);
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetComposeOfflineDomainJoin, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetServerGetInfo
+****************************************************************/
+
+NET_API_STATUS NetServerGetInfo(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetServerGetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetServerGetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetServerGetInfo_l(ctx, &r);
+ } else {
+ werr = NetServerGetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetServerGetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetServerSetInfo
+****************************************************************/
+
+NET_API_STATUS NetServerSetInfo(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_error /* [out] [ref] */)
+{
+ struct NetServerSetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_error = parm_error;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetServerSetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetServerSetInfo_l(ctx, &r);
+ } else {
+ werr = NetServerSetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetServerSetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetWkstaGetInfo
+****************************************************************/
+
+NET_API_STATUS NetWkstaGetInfo(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetWkstaGetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetWkstaGetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetWkstaGetInfo_l(ctx, &r);
+ } else {
+ werr = NetWkstaGetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetWkstaGetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGetDCName
+****************************************************************/
+
+NET_API_STATUS NetGetDCName(const char * server_name /* [in] [unique] */,
+ const char * domain_name /* [in] [unique] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetGetDCName r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.domain_name = domain_name;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGetDCName, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGetDCName_l(ctx, &r);
+ } else {
+ werr = NetGetDCName_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGetDCName, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGetAnyDCName
+****************************************************************/
+
+NET_API_STATUS NetGetAnyDCName(const char * server_name /* [in] [unique] */,
+ const char * domain_name /* [in] [unique] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetGetAnyDCName r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.domain_name = domain_name;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGetAnyDCName, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGetAnyDCName_l(ctx, &r);
+ } else {
+ werr = NetGetAnyDCName_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGetAnyDCName, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ DsGetDcName
+****************************************************************/
+
+NET_API_STATUS DsGetDcName(const char * server_name /* [in] [unique] */,
+ const char * domain_name /* [in] [ref] */,
+ struct GUID *domain_guid /* [in] [unique] */,
+ const char * site_name /* [in] [unique] */,
+ uint32_t flags /* [in] */,
+ struct DOMAIN_CONTROLLER_INFO **dc_info /* [out] [ref] */)
+{
+ struct DsGetDcName r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.domain_name = domain_name;
+ r.in.domain_guid = domain_guid;
+ r.in.site_name = site_name;
+ r.in.flags = flags;
+
+ /* Out parameters */
+ r.out.dc_info = dc_info;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(DsGetDcName, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = DsGetDcName_l(ctx, &r);
+ } else {
+ werr = DsGetDcName_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(DsGetDcName, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserAdd
+****************************************************************/
+
+NET_API_STATUS NetUserAdd(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_error /* [out] [ref] */)
+{
+ struct NetUserAdd r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_error = parm_error;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserAdd, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserAdd_l(ctx, &r);
+ } else {
+ werr = NetUserAdd_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserAdd, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserDel
+****************************************************************/
+
+NET_API_STATUS NetUserDel(const char * server_name /* [in] [unique] */,
+ const char * user_name /* [in] [ref] */)
+{
+ struct NetUserDel r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.user_name = user_name;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserDel, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserDel_l(ctx, &r);
+ } else {
+ werr = NetUserDel_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserDel, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserEnum
+****************************************************************/
+
+NET_API_STATUS NetUserEnum(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint32_t filter /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */)
+{
+ struct NetUserEnum r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.filter = filter;
+ r.in.prefmaxlen = prefmaxlen;
+ r.in.resume_handle = resume_handle;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+ r.out.resume_handle = resume_handle;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserEnum, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserEnum_l(ctx, &r);
+ } else {
+ werr = NetUserEnum_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserEnum, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserChangePassword
+****************************************************************/
+
+NET_API_STATUS NetUserChangePassword(const char * domain_name /* [in] */,
+ const char * user_name /* [in] */,
+ const char * old_password /* [in] */,
+ const char * new_password /* [in] */)
+{
+ struct NetUserChangePassword r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.domain_name = domain_name;
+ r.in.user_name = user_name;
+ r.in.old_password = old_password;
+ r.in.new_password = new_password;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserChangePassword, &r);
+ }
+
+ werr = NetUserChangePassword_l(ctx, &r);
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserChangePassword, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserGetInfo
+****************************************************************/
+
+NET_API_STATUS NetUserGetInfo(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetUserGetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.user_name = user_name;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserGetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserGetInfo_l(ctx, &r);
+ } else {
+ werr = NetUserGetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserGetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserSetInfo
+****************************************************************/
+
+NET_API_STATUS NetUserSetInfo(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetUserSetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.user_name = user_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserSetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserSetInfo_l(ctx, &r);
+ } else {
+ werr = NetUserSetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserSetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserGetGroups
+****************************************************************/
+
+NET_API_STATUS NetUserGetGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */)
+{
+ struct NetUserGetGroups r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.user_name = user_name;
+ r.in.level = level;
+ r.in.prefmaxlen = prefmaxlen;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserGetGroups, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserGetGroups_l(ctx, &r);
+ } else {
+ werr = NetUserGetGroups_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserGetGroups, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserSetGroups
+****************************************************************/
+
+NET_API_STATUS NetUserSetGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t num_entries /* [in] */)
+{
+ struct NetUserSetGroups r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.user_name = user_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+ r.in.num_entries = num_entries;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserSetGroups, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserSetGroups_l(ctx, &r);
+ } else {
+ werr = NetUserSetGroups_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserSetGroups, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserGetLocalGroups
+****************************************************************/
+
+NET_API_STATUS NetUserGetLocalGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint32_t flags /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */)
+{
+ struct NetUserGetLocalGroups r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.user_name = user_name;
+ r.in.level = level;
+ r.in.flags = flags;
+ r.in.prefmaxlen = prefmaxlen;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserGetLocalGroups, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserGetLocalGroups_l(ctx, &r);
+ } else {
+ werr = NetUserGetLocalGroups_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserGetLocalGroups, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserModalsGet
+****************************************************************/
+
+NET_API_STATUS NetUserModalsGet(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetUserModalsGet r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserModalsGet, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserModalsGet_l(ctx, &r);
+ } else {
+ werr = NetUserModalsGet_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserModalsGet, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetUserModalsSet
+****************************************************************/
+
+NET_API_STATUS NetUserModalsSet(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetUserModalsSet r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetUserModalsSet, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetUserModalsSet_l(ctx, &r);
+ } else {
+ werr = NetUserModalsSet_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetUserModalsSet, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetQueryDisplayInformation
+****************************************************************/
+
+NET_API_STATUS NetQueryDisplayInformation(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint32_t idx /* [in] */,
+ uint32_t entries_requested /* [in] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ void **buffer /* [out] [noprint,ref] */)
+{
+ struct NetQueryDisplayInformation r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.idx = idx;
+ r.in.entries_requested = entries_requested;
+ r.in.prefmaxlen = prefmaxlen;
+
+ /* Out parameters */
+ r.out.entries_read = entries_read;
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetQueryDisplayInformation, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetQueryDisplayInformation_l(ctx, &r);
+ } else {
+ werr = NetQueryDisplayInformation_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetQueryDisplayInformation, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupAdd
+****************************************************************/
+
+NET_API_STATUS NetGroupAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetGroupAdd r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupAdd, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupAdd_l(ctx, &r);
+ } else {
+ werr = NetGroupAdd_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupAdd, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupDel
+****************************************************************/
+
+NET_API_STATUS NetGroupDel(const char * server_name /* [in] */,
+ const char * group_name /* [in] */)
+{
+ struct NetGroupDel r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupDel, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupDel_l(ctx, &r);
+ } else {
+ werr = NetGroupDel_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupDel, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupEnum
+****************************************************************/
+
+NET_API_STATUS NetGroupEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */)
+{
+ struct NetGroupEnum r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.prefmaxlen = prefmaxlen;
+ r.in.resume_handle = resume_handle;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+ r.out.resume_handle = resume_handle;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupEnum, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupEnum_l(ctx, &r);
+ } else {
+ werr = NetGroupEnum_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupEnum, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupSetInfo
+****************************************************************/
+
+NET_API_STATUS NetGroupSetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetGroupSetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupSetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupSetInfo_l(ctx, &r);
+ } else {
+ werr = NetGroupSetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupSetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupGetInfo
+****************************************************************/
+
+NET_API_STATUS NetGroupGetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetGroupGetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupGetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupGetInfo_l(ctx, &r);
+ } else {
+ werr = NetGroupGetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupGetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupAddUser
+****************************************************************/
+
+NET_API_STATUS NetGroupAddUser(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ const char * user_name /* [in] */)
+{
+ struct NetGroupAddUser r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.user_name = user_name;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupAddUser, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupAddUser_l(ctx, &r);
+ } else {
+ werr = NetGroupAddUser_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupAddUser, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupDelUser
+****************************************************************/
+
+NET_API_STATUS NetGroupDelUser(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ const char * user_name /* [in] */)
+{
+ struct NetGroupDelUser r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.user_name = user_name;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupDelUser, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupDelUser_l(ctx, &r);
+ } else {
+ werr = NetGroupDelUser_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupDelUser, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupGetUsers
+****************************************************************/
+
+NET_API_STATUS NetGroupGetUsers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */)
+{
+ struct NetGroupGetUsers r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+ r.in.prefmaxlen = prefmaxlen;
+ r.in.resume_handle = resume_handle;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+ r.out.resume_handle = resume_handle;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupGetUsers, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupGetUsers_l(ctx, &r);
+ } else {
+ werr = NetGroupGetUsers_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupGetUsers, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetGroupSetUsers
+****************************************************************/
+
+NET_API_STATUS NetGroupSetUsers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t num_entries /* [in] */)
+{
+ struct NetGroupSetUsers r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+ r.in.num_entries = num_entries;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetGroupSetUsers, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetGroupSetUsers_l(ctx, &r);
+ } else {
+ werr = NetGroupSetUsers_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetGroupSetUsers, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupAdd
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetLocalGroupAdd r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupAdd, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupAdd_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupAdd_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupAdd, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupDel
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupDel(const char * server_name /* [in] */,
+ const char * group_name /* [in] */)
+{
+ struct NetLocalGroupDel r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupDel, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupDel_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupDel_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupDel, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupGetInfo
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupGetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetLocalGroupGetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupGetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupGetInfo_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupGetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupGetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupSetInfo
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupSetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetLocalGroupSetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupSetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupSetInfo_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupSetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupSetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupEnum
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */)
+{
+ struct NetLocalGroupEnum r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.prefmaxlen = prefmaxlen;
+ r.in.resume_handle = resume_handle;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+ r.out.resume_handle = resume_handle;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupEnum, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupEnum_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupEnum_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupEnum, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupAddMembers
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupAddMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */)
+{
+ struct NetLocalGroupAddMembers r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+ r.in.total_entries = total_entries;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupAddMembers, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupAddMembers_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupAddMembers_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupAddMembers, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupDelMembers
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupDelMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */)
+{
+ struct NetLocalGroupDelMembers r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+ r.in.total_entries = total_entries;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupDelMembers, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupDelMembers_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupDelMembers_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupDelMembers, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupGetMembers
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupGetMembers(const char * server_name /* [in] */,
+ const char * local_group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */)
+{
+ struct NetLocalGroupGetMembers r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.local_group_name = local_group_name;
+ r.in.level = level;
+ r.in.prefmaxlen = prefmaxlen;
+ r.in.resume_handle = resume_handle;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+ r.out.resume_handle = resume_handle;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupGetMembers, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupGetMembers_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupGetMembers_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupGetMembers, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetLocalGroupSetMembers
+****************************************************************/
+
+NET_API_STATUS NetLocalGroupSetMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */)
+{
+ struct NetLocalGroupSetMembers r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.group_name = group_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+ r.in.total_entries = total_entries;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetLocalGroupSetMembers, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetLocalGroupSetMembers_l(ctx, &r);
+ } else {
+ werr = NetLocalGroupSetMembers_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetLocalGroupSetMembers, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetRemoteTOD
+****************************************************************/
+
+NET_API_STATUS NetRemoteTOD(const char * server_name /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetRemoteTOD r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetRemoteTOD, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetRemoteTOD_l(ctx, &r);
+ } else {
+ werr = NetRemoteTOD_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetRemoteTOD, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetShareAdd
+****************************************************************/
+
+NET_API_STATUS NetShareAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetShareAdd r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetShareAdd, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetShareAdd_l(ctx, &r);
+ } else {
+ werr = NetShareAdd_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetShareAdd, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetShareDel
+****************************************************************/
+
+NET_API_STATUS NetShareDel(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t reserved /* [in] */)
+{
+ struct NetShareDel r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.net_name = net_name;
+ r.in.reserved = reserved;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetShareDel, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetShareDel_l(ctx, &r);
+ } else {
+ werr = NetShareDel_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetShareDel, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetShareEnum
+****************************************************************/
+
+NET_API_STATUS NetShareEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */)
+{
+ struct NetShareEnum r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.level = level;
+ r.in.prefmaxlen = prefmaxlen;
+ r.in.resume_handle = resume_handle;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+ r.out.resume_handle = resume_handle;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetShareEnum, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetShareEnum_l(ctx, &r);
+ } else {
+ werr = NetShareEnum_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetShareEnum, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetShareGetInfo
+****************************************************************/
+
+NET_API_STATUS NetShareGetInfo(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetShareGetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.net_name = net_name;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetShareGetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetShareGetInfo_l(ctx, &r);
+ } else {
+ werr = NetShareGetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetShareGetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetShareSetInfo
+****************************************************************/
+
+NET_API_STATUS NetShareSetInfo(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */)
+{
+ struct NetShareSetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.net_name = net_name;
+ r.in.level = level;
+ r.in.buffer = buffer;
+
+ /* Out parameters */
+ r.out.parm_err = parm_err;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetShareSetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetShareSetInfo_l(ctx, &r);
+ } else {
+ werr = NetShareSetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetShareSetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetFileClose
+****************************************************************/
+
+NET_API_STATUS NetFileClose(const char * server_name /* [in] */,
+ uint32_t fileid /* [in] */)
+{
+ struct NetFileClose r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.fileid = fileid;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetFileClose, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetFileClose_l(ctx, &r);
+ } else {
+ werr = NetFileClose_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetFileClose, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetFileGetInfo
+****************************************************************/
+
+NET_API_STATUS NetFileGetInfo(const char * server_name /* [in] */,
+ uint32_t fileid /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct NetFileGetInfo r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.fileid = fileid;
+ r.in.level = level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetFileGetInfo, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetFileGetInfo_l(ctx, &r);
+ } else {
+ werr = NetFileGetInfo_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetFileGetInfo, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetFileEnum
+****************************************************************/
+
+NET_API_STATUS NetFileEnum(const char * server_name /* [in] */,
+ const char * base_path /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */)
+{
+ struct NetFileEnum r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.base_path = base_path;
+ r.in.user_name = user_name;
+ r.in.level = level;
+ r.in.prefmaxlen = prefmaxlen;
+ r.in.resume_handle = resume_handle;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+ r.out.entries_read = entries_read;
+ r.out.total_entries = total_entries;
+ r.out.resume_handle = resume_handle;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetFileEnum, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetFileEnum_l(ctx, &r);
+ } else {
+ werr = NetFileEnum_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetFileEnum, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetShutdownInit
+****************************************************************/
+
+NET_API_STATUS NetShutdownInit(const char * server_name /* [in] */,
+ const char * message /* [in] */,
+ uint32_t timeout /* [in] */,
+ uint8_t force_apps /* [in] */,
+ uint8_t do_reboot /* [in] */)
+{
+ struct NetShutdownInit r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.message = message;
+ r.in.timeout = timeout;
+ r.in.force_apps = force_apps;
+ r.in.do_reboot = do_reboot;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetShutdownInit, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetShutdownInit_l(ctx, &r);
+ } else {
+ werr = NetShutdownInit_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetShutdownInit, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ NetShutdownAbort
+****************************************************************/
+
+NET_API_STATUS NetShutdownAbort(const char * server_name /* [in] */)
+{
+ struct NetShutdownAbort r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+
+ /* Out parameters */
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(NetShutdownAbort, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = NetShutdownAbort_l(ctx, &r);
+ } else {
+ werr = NetShutdownAbort_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(NetShutdownAbort, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ I_NetLogonControl
+****************************************************************/
+
+NET_API_STATUS I_NetLogonControl(const char * server_name /* [in] */,
+ uint32_t function_code /* [in] */,
+ uint32_t query_level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct I_NetLogonControl r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.function_code = function_code;
+ r.in.query_level = query_level;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(I_NetLogonControl, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = I_NetLogonControl_l(ctx, &r);
+ } else {
+ werr = I_NetLogonControl_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(I_NetLogonControl, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
+/****************************************************************
+ I_NetLogonControl2
+****************************************************************/
+
+NET_API_STATUS I_NetLogonControl2(const char * server_name /* [in] */,
+ uint32_t function_code /* [in] */,
+ uint32_t query_level /* [in] */,
+ uint8_t *data /* [in] [unique] */,
+ uint8_t **buffer /* [out] [ref] */)
+{
+ struct I_NetLogonControl2 r;
+ struct libnetapi_ctx *ctx = NULL;
+ NET_API_STATUS status;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ZERO_STRUCT(r);
+
+ status = libnetapi_getctx(&ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* In parameters */
+ r.in.server_name = server_name;
+ r.in.function_code = function_code;
+ r.in.query_level = query_level;
+ r.in.data = data;
+
+ /* Out parameters */
+ r.out.buffer = buffer;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_IN_DEBUG(I_NetLogonControl2, &r);
+ }
+
+ if (LIBNETAPI_LOCAL_SERVER(server_name)) {
+ werr = I_NetLogonControl2_l(ctx, &r);
+ } else {
+ werr = I_NetLogonControl2_r(ctx, &r);
+ }
+
+ r.out.result = W_ERROR_V(werr);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_OUT_DEBUG(I_NetLogonControl2, &r);
+ }
+
+ TALLOC_FREE(frame);
+ return (NET_API_STATUS)r.out.result;
+}
+
diff --git a/source3/lib/netapi/libnetapi.h b/source3/lib/netapi/libnetapi.h
new file mode 100644
index 0000000..784d467
--- /dev/null
+++ b/source3/lib/netapi/libnetapi.h
@@ -0,0 +1,523 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBNETAPI_LIBNETAPI__
+#define __LIBNETAPI_LIBNETAPI__
+NET_API_STATUS NetJoinDomain(const char * server /* [in] [unique] */,
+ const char * domain /* [in] [ref] */,
+ const char * account_ou /* [in] [unique] */,
+ const char * account /* [in] [unique] */,
+ const char * password /* [in] [unique] */,
+ uint32_t join_flags /* [in] */);
+WERROR NetJoinDomain_r(struct libnetapi_ctx *ctx,
+ struct NetJoinDomain *r);
+WERROR NetJoinDomain_l(struct libnetapi_ctx *ctx,
+ struct NetJoinDomain *r);
+NET_API_STATUS NetUnjoinDomain(const char * server_name /* [in] [unique] */,
+ const char * account /* [in] [unique] */,
+ const char * password /* [in] [unique] */,
+ uint32_t unjoin_flags /* [in] */);
+WERROR NetUnjoinDomain_r(struct libnetapi_ctx *ctx,
+ struct NetUnjoinDomain *r);
+WERROR NetUnjoinDomain_l(struct libnetapi_ctx *ctx,
+ struct NetUnjoinDomain *r);
+NET_API_STATUS NetGetJoinInformation(const char * server_name /* [in] [unique] */,
+ const char * *name_buffer /* [out] [ref] */,
+ uint16_t *name_type /* [out] [ref] */);
+WERROR NetGetJoinInformation_r(struct libnetapi_ctx *ctx,
+ struct NetGetJoinInformation *r);
+WERROR NetGetJoinInformation_l(struct libnetapi_ctx *ctx,
+ struct NetGetJoinInformation *r);
+NET_API_STATUS NetGetJoinableOUs(const char * server_name /* [in] [unique] */,
+ const char * domain /* [in] [ref] */,
+ const char * account /* [in] [unique] */,
+ const char * password /* [in] [unique] */,
+ uint32_t *ou_count /* [out] [ref] */,
+ const char * **ous /* [out] [ref] */);
+WERROR NetGetJoinableOUs_r(struct libnetapi_ctx *ctx,
+ struct NetGetJoinableOUs *r);
+WERROR NetGetJoinableOUs_l(struct libnetapi_ctx *ctx,
+ struct NetGetJoinableOUs *r);
+NET_API_STATUS NetRenameMachineInDomain(const char * server_name /* [in] */,
+ const char * new_machine_name /* [in] */,
+ const char * account /* [in] */,
+ const char * password /* [in] */,
+ uint32_t rename_options /* [in] */);
+WERROR NetRenameMachineInDomain_r(struct libnetapi_ctx *ctx,
+ struct NetRenameMachineInDomain *r);
+WERROR NetRenameMachineInDomain_l(struct libnetapi_ctx *ctx,
+ struct NetRenameMachineInDomain *r);
+NET_API_STATUS NetProvisionComputerAccount(const char * domain /* [in] [ref] */,
+ const char * machine_name /* [in] [ref] */,
+ const char * machine_account_ou /* [in] [unique] */,
+ const char * dcname /* [in] [unique] */,
+ uint32_t options /* [in] */,
+ uint8_t **provision_bin_data /* [in,out] [unique] */,
+ uint32_t *provision_bin_data_size /* [in,out] [unique] */,
+ const char * *provision_text_data /* [in,out] [unique] */);
+WERROR NetProvisionComputerAccount_r(struct libnetapi_ctx *ctx,
+ struct NetProvisionComputerAccount *r);
+WERROR NetProvisionComputerAccount_l(struct libnetapi_ctx *ctx,
+ struct NetProvisionComputerAccount *r);
+NET_API_STATUS NetRequestOfflineDomainJoin(uint8_t *provision_bin_data /* [in] [unique] */,
+ uint32_t provision_bin_data_size /* [in] */,
+ uint32_t options /* [in] */,
+ const char * windows_path /* [in] [unique] */);
+WERROR NetRequestOfflineDomainJoin_r(struct libnetapi_ctx *ctx,
+ struct NetRequestOfflineDomainJoin *r);
+WERROR NetRequestOfflineDomainJoin_l(struct libnetapi_ctx *ctx,
+ struct NetRequestOfflineDomainJoin *r);
+NET_API_STATUS NetComposeOfflineDomainJoin(const char *dns_domain_name /* [in] [ref] */,
+ const char *netbios_domain_name /* [in] [ref] */,
+ struct domsid *domain_sid /* [in] [ref] */,
+ struct GUID *domain_guid /* [in] [ref] */,
+ const char *forest_name /* [in] [ref] */,
+ const char *machine_account_name /* [in] [ref] */,
+ const char *machine_account_password /* [in] [ref] */,
+ const char *dc_name /* [in] [unique] */,
+ const char *dc_address /* [in] [unique] */,
+ int domain_is_ad /* [in] */,
+ uint8_t **provision_bin_data /* [in,out] [unique] */,
+ uint32_t *provision_bin_data_size /* [in,out] [unique] */,
+ const char * *provision_text_data /* [in,out] [unique] */);
+WERROR NetComposeOfflineDomainJoin_r(struct libnetapi_ctx *ctx,
+ struct NetComposeOfflineDomainJoin *r);
+WERROR NetComposeOfflineDomainJoin_l(struct libnetapi_ctx *ctx,
+ struct NetComposeOfflineDomainJoin *r);
+NET_API_STATUS NetServerGetInfo(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetServerGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetServerGetInfo *r);
+WERROR NetServerGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetServerGetInfo *r);
+NET_API_STATUS NetServerSetInfo(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_error /* [out] [ref] */);
+WERROR NetServerSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetServerSetInfo *r);
+WERROR NetServerSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetServerSetInfo *r);
+NET_API_STATUS NetWkstaGetInfo(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetWkstaGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetWkstaGetInfo *r);
+WERROR NetWkstaGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetWkstaGetInfo *r);
+NET_API_STATUS NetGetDCName(const char * server_name /* [in] [unique] */,
+ const char * domain_name /* [in] [unique] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetGetDCName_r(struct libnetapi_ctx *ctx,
+ struct NetGetDCName *r);
+WERROR NetGetDCName_l(struct libnetapi_ctx *ctx,
+ struct NetGetDCName *r);
+NET_API_STATUS NetGetAnyDCName(const char * server_name /* [in] [unique] */,
+ const char * domain_name /* [in] [unique] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetGetAnyDCName_r(struct libnetapi_ctx *ctx,
+ struct NetGetAnyDCName *r);
+WERROR NetGetAnyDCName_l(struct libnetapi_ctx *ctx,
+ struct NetGetAnyDCName *r);
+NET_API_STATUS DsGetDcName(const char * server_name /* [in] [unique] */,
+ const char * domain_name /* [in] [ref] */,
+ struct GUID *domain_guid /* [in] [unique] */,
+ const char * site_name /* [in] [unique] */,
+ uint32_t flags /* [in] */,
+ struct DOMAIN_CONTROLLER_INFO **dc_info /* [out] [ref] */);
+WERROR DsGetDcName_r(struct libnetapi_ctx *ctx,
+ struct DsGetDcName *r);
+WERROR DsGetDcName_l(struct libnetapi_ctx *ctx,
+ struct DsGetDcName *r);
+NET_API_STATUS NetUserAdd(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_error /* [out] [ref] */);
+WERROR NetUserAdd_r(struct libnetapi_ctx *ctx,
+ struct NetUserAdd *r);
+WERROR NetUserAdd_l(struct libnetapi_ctx *ctx,
+ struct NetUserAdd *r);
+NET_API_STATUS NetUserDel(const char * server_name /* [in] [unique] */,
+ const char * user_name /* [in] [ref] */);
+WERROR NetUserDel_r(struct libnetapi_ctx *ctx,
+ struct NetUserDel *r);
+WERROR NetUserDel_l(struct libnetapi_ctx *ctx,
+ struct NetUserDel *r);
+NET_API_STATUS NetUserEnum(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint32_t filter /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+WERROR NetUserEnum_r(struct libnetapi_ctx *ctx,
+ struct NetUserEnum *r);
+WERROR NetUserEnum_l(struct libnetapi_ctx *ctx,
+ struct NetUserEnum *r);
+NET_API_STATUS NetUserChangePassword(const char * domain_name /* [in] */,
+ const char * user_name /* [in] */,
+ const char * old_password /* [in] */,
+ const char * new_password /* [in] */);
+WERROR NetUserChangePassword_r(struct libnetapi_ctx *ctx,
+ struct NetUserChangePassword *r);
+WERROR NetUserChangePassword_l(struct libnetapi_ctx *ctx,
+ struct NetUserChangePassword *r);
+NET_API_STATUS NetUserGetInfo(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetUserGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetUserGetInfo *r);
+WERROR NetUserGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetUserGetInfo *r);
+NET_API_STATUS NetUserSetInfo(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetUserSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetUserSetInfo *r);
+WERROR NetUserSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetUserSetInfo *r);
+NET_API_STATUS NetUserGetGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */);
+WERROR NetUserGetGroups_r(struct libnetapi_ctx *ctx,
+ struct NetUserGetGroups *r);
+WERROR NetUserGetGroups_l(struct libnetapi_ctx *ctx,
+ struct NetUserGetGroups *r);
+NET_API_STATUS NetUserSetGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t num_entries /* [in] */);
+WERROR NetUserSetGroups_r(struct libnetapi_ctx *ctx,
+ struct NetUserSetGroups *r);
+WERROR NetUserSetGroups_l(struct libnetapi_ctx *ctx,
+ struct NetUserSetGroups *r);
+NET_API_STATUS NetUserGetLocalGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint32_t flags /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */);
+WERROR NetUserGetLocalGroups_r(struct libnetapi_ctx *ctx,
+ struct NetUserGetLocalGroups *r);
+WERROR NetUserGetLocalGroups_l(struct libnetapi_ctx *ctx,
+ struct NetUserGetLocalGroups *r);
+NET_API_STATUS NetUserModalsGet(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetUserModalsGet_r(struct libnetapi_ctx *ctx,
+ struct NetUserModalsGet *r);
+WERROR NetUserModalsGet_l(struct libnetapi_ctx *ctx,
+ struct NetUserModalsGet *r);
+NET_API_STATUS NetUserModalsSet(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetUserModalsSet_r(struct libnetapi_ctx *ctx,
+ struct NetUserModalsSet *r);
+WERROR NetUserModalsSet_l(struct libnetapi_ctx *ctx,
+ struct NetUserModalsSet *r);
+NET_API_STATUS NetQueryDisplayInformation(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint32_t idx /* [in] */,
+ uint32_t entries_requested /* [in] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ void **buffer /* [out] [noprint,ref] */);
+WERROR NetQueryDisplayInformation_r(struct libnetapi_ctx *ctx,
+ struct NetQueryDisplayInformation *r);
+WERROR NetQueryDisplayInformation_l(struct libnetapi_ctx *ctx,
+ struct NetQueryDisplayInformation *r);
+NET_API_STATUS NetGroupAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetGroupAdd_r(struct libnetapi_ctx *ctx,
+ struct NetGroupAdd *r);
+WERROR NetGroupAdd_l(struct libnetapi_ctx *ctx,
+ struct NetGroupAdd *r);
+NET_API_STATUS NetGroupDel(const char * server_name /* [in] */,
+ const char * group_name /* [in] */);
+WERROR NetGroupDel_r(struct libnetapi_ctx *ctx,
+ struct NetGroupDel *r);
+WERROR NetGroupDel_l(struct libnetapi_ctx *ctx,
+ struct NetGroupDel *r);
+NET_API_STATUS NetGroupEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+WERROR NetGroupEnum_r(struct libnetapi_ctx *ctx,
+ struct NetGroupEnum *r);
+WERROR NetGroupEnum_l(struct libnetapi_ctx *ctx,
+ struct NetGroupEnum *r);
+NET_API_STATUS NetGroupSetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetGroupSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetGroupSetInfo *r);
+WERROR NetGroupSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetGroupSetInfo *r);
+NET_API_STATUS NetGroupGetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetGroupGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetGroupGetInfo *r);
+WERROR NetGroupGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetGroupGetInfo *r);
+NET_API_STATUS NetGroupAddUser(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ const char * user_name /* [in] */);
+WERROR NetGroupAddUser_r(struct libnetapi_ctx *ctx,
+ struct NetGroupAddUser *r);
+WERROR NetGroupAddUser_l(struct libnetapi_ctx *ctx,
+ struct NetGroupAddUser *r);
+NET_API_STATUS NetGroupDelUser(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ const char * user_name /* [in] */);
+WERROR NetGroupDelUser_r(struct libnetapi_ctx *ctx,
+ struct NetGroupDelUser *r);
+WERROR NetGroupDelUser_l(struct libnetapi_ctx *ctx,
+ struct NetGroupDelUser *r);
+NET_API_STATUS NetGroupGetUsers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+WERROR NetGroupGetUsers_r(struct libnetapi_ctx *ctx,
+ struct NetGroupGetUsers *r);
+WERROR NetGroupGetUsers_l(struct libnetapi_ctx *ctx,
+ struct NetGroupGetUsers *r);
+NET_API_STATUS NetGroupSetUsers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t num_entries /* [in] */);
+WERROR NetGroupSetUsers_r(struct libnetapi_ctx *ctx,
+ struct NetGroupSetUsers *r);
+WERROR NetGroupSetUsers_l(struct libnetapi_ctx *ctx,
+ struct NetGroupSetUsers *r);
+NET_API_STATUS NetLocalGroupAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetLocalGroupAdd_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAdd *r);
+WERROR NetLocalGroupAdd_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAdd *r);
+NET_API_STATUS NetLocalGroupDel(const char * server_name /* [in] */,
+ const char * group_name /* [in] */);
+WERROR NetLocalGroupDel_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDel *r);
+WERROR NetLocalGroupDel_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDel *r);
+NET_API_STATUS NetLocalGroupGetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetLocalGroupGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetInfo *r);
+WERROR NetLocalGroupGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetInfo *r);
+NET_API_STATUS NetLocalGroupSetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetLocalGroupSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetInfo *r);
+WERROR NetLocalGroupSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetInfo *r);
+NET_API_STATUS NetLocalGroupEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+WERROR NetLocalGroupEnum_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupEnum *r);
+WERROR NetLocalGroupEnum_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupEnum *r);
+NET_API_STATUS NetLocalGroupAddMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */);
+WERROR NetLocalGroupAddMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAddMembers *r);
+WERROR NetLocalGroupAddMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAddMembers *r);
+NET_API_STATUS NetLocalGroupDelMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */);
+WERROR NetLocalGroupDelMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDelMembers *r);
+WERROR NetLocalGroupDelMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDelMembers *r);
+NET_API_STATUS NetLocalGroupGetMembers(const char * server_name /* [in] */,
+ const char * local_group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+WERROR NetLocalGroupGetMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetMembers *r);
+WERROR NetLocalGroupGetMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetMembers *r);
+NET_API_STATUS NetLocalGroupSetMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */);
+WERROR NetLocalGroupSetMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetMembers *r);
+WERROR NetLocalGroupSetMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetMembers *r);
+NET_API_STATUS NetRemoteTOD(const char * server_name /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetRemoteTOD_r(struct libnetapi_ctx *ctx,
+ struct NetRemoteTOD *r);
+WERROR NetRemoteTOD_l(struct libnetapi_ctx *ctx,
+ struct NetRemoteTOD *r);
+NET_API_STATUS NetShareAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetShareAdd_r(struct libnetapi_ctx *ctx,
+ struct NetShareAdd *r);
+WERROR NetShareAdd_l(struct libnetapi_ctx *ctx,
+ struct NetShareAdd *r);
+NET_API_STATUS NetShareDel(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t reserved /* [in] */);
+WERROR NetShareDel_r(struct libnetapi_ctx *ctx,
+ struct NetShareDel *r);
+WERROR NetShareDel_l(struct libnetapi_ctx *ctx,
+ struct NetShareDel *r);
+NET_API_STATUS NetShareEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+WERROR NetShareEnum_r(struct libnetapi_ctx *ctx,
+ struct NetShareEnum *r);
+WERROR NetShareEnum_l(struct libnetapi_ctx *ctx,
+ struct NetShareEnum *r);
+NET_API_STATUS NetShareGetInfo(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetShareGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetShareGetInfo *r);
+WERROR NetShareGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetShareGetInfo *r);
+NET_API_STATUS NetShareSetInfo(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+WERROR NetShareSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetShareSetInfo *r);
+WERROR NetShareSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetShareSetInfo *r);
+NET_API_STATUS NetFileClose(const char * server_name /* [in] */,
+ uint32_t fileid /* [in] */);
+WERROR NetFileClose_r(struct libnetapi_ctx *ctx,
+ struct NetFileClose *r);
+WERROR NetFileClose_l(struct libnetapi_ctx *ctx,
+ struct NetFileClose *r);
+NET_API_STATUS NetFileGetInfo(const char * server_name /* [in] */,
+ uint32_t fileid /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR NetFileGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetFileGetInfo *r);
+WERROR NetFileGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetFileGetInfo *r);
+NET_API_STATUS NetFileEnum(const char * server_name /* [in] */,
+ const char * base_path /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+WERROR NetFileEnum_r(struct libnetapi_ctx *ctx,
+ struct NetFileEnum *r);
+WERROR NetFileEnum_l(struct libnetapi_ctx *ctx,
+ struct NetFileEnum *r);
+NET_API_STATUS NetShutdownInit(const char * server_name /* [in] */,
+ const char * message /* [in] */,
+ uint32_t timeout /* [in] */,
+ uint8_t force_apps /* [in] */,
+ uint8_t do_reboot /* [in] */);
+WERROR NetShutdownInit_r(struct libnetapi_ctx *ctx,
+ struct NetShutdownInit *r);
+WERROR NetShutdownInit_l(struct libnetapi_ctx *ctx,
+ struct NetShutdownInit *r);
+NET_API_STATUS NetShutdownAbort(const char * server_name /* [in] */);
+WERROR NetShutdownAbort_r(struct libnetapi_ctx *ctx,
+ struct NetShutdownAbort *r);
+WERROR NetShutdownAbort_l(struct libnetapi_ctx *ctx,
+ struct NetShutdownAbort *r);
+NET_API_STATUS I_NetLogonControl(const char * server_name /* [in] */,
+ uint32_t function_code /* [in] */,
+ uint32_t query_level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR I_NetLogonControl_r(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl *r);
+WERROR I_NetLogonControl_l(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl *r);
+NET_API_STATUS I_NetLogonControl2(const char * server_name /* [in] */,
+ uint32_t function_code /* [in] */,
+ uint32_t query_level /* [in] */,
+ uint8_t *data /* [in] [unique] */,
+ uint8_t **buffer /* [out] [ref] */);
+WERROR I_NetLogonControl2_r(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl2 *r);
+WERROR I_NetLogonControl2_l(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl2 *r);
+#endif /* __LIBNETAPI_LIBNETAPI__ */
diff --git a/source3/lib/netapi/localgroup.c b/source3/lib/netapi/localgroup.c
new file mode 100644
index 0000000..a63fca4
--- /dev/null
+++ b/source3/lib/netapi/localgroup.c
@@ -0,0 +1,1390 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi LocalGroup Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+
+static NTSTATUS libnetapi_samr_lookup_and_open_alias(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ const char *group_name,
+ uint32_t access_rights,
+ struct policy_handle *alias_handle)
+{
+ NTSTATUS status, result;
+
+ struct lsa_String lsa_account_name;
+ struct samr_Ids user_rids, name_types;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ init_lsa_String(&lsa_account_name, group_name);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ domain_handle,
+ 1,
+ &lsa_account_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+ if (user_rids.count != 1) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (name_types.count != 1) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ switch (name_types.ids[0]) {
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ break;
+ default:
+ return NT_STATUS_INVALID_SID;
+ }
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ domain_handle,
+ access_rights,
+ user_rids.ids[0],
+ alias_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnetapi_samr_open_alias_queryinfo(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *handle,
+ uint32_t rid,
+ uint32_t access_rights,
+ enum samr_AliasInfoEnum level,
+ union samr_AliasInfo **alias_info)
+{
+ NTSTATUS status, result;
+ struct policy_handle alias_handle;
+ union samr_AliasInfo *_alias_info = NULL;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ ZERO_STRUCT(alias_handle);
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ handle,
+ access_rights,
+ rid,
+ &alias_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryAliasInfo(b, mem_ctx,
+ &alias_handle,
+ level,
+ &_alias_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ *alias_info = _alias_info;
+
+ done:
+ if (is_valid_policy_hnd(&alias_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &alias_handle, &result);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupAdd_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAdd *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct lsa_String lsa_account_name;
+ struct policy_handle connect_handle, domain_handle, builtin_handle, alias_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ uint32_t rid;
+ struct dcerpc_binding_handle *b = NULL;
+
+ struct LOCALGROUP_INFO_0 *info0 = NULL;
+ struct LOCALGROUP_INFO_1 *info1 = NULL;
+
+ const char *alias_name = NULL;
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(alias_handle);
+
+ switch (r->in.level) {
+ case 0:
+ info0 = (struct LOCALGROUP_INFO_0 *)r->in.buffer;
+ alias_name = info0->lgrpi0_name;
+ break;
+ case 1:
+ info1 = (struct LOCALGROUP_INFO_1 *)r->in.buffer;
+ alias_name = info1->lgrpi1_name;
+ break;
+ default:
+ werr = WERR_INVALID_LEVEL;
+ goto done;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_LOOKUP_DOMAIN |
+ SAMR_ACCESS_ENUM_DOMAINS,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &builtin_handle,
+ alias_name,
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ &alias_handle);
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ werr = WERR_ALIAS_EXISTS;
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_CREATE_ALIAS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, alias_name);
+
+ status = dcerpc_samr_CreateDomAlias(b, talloc_tos(),
+ &domain_handle,
+ &lsa_account_name,
+ SEC_STD_DELETE |
+ SAMR_ALIAS_ACCESS_SET_INFO,
+ &alias_handle,
+ &rid,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (r->in.level == 1 && info1->lgrpi1_comment) {
+
+ union samr_AliasInfo alias_info;
+
+ init_lsa_String(&alias_info.description, info1->lgrpi1_comment);
+
+ status = dcerpc_samr_SetAliasInfo(b, talloc_tos(),
+ &alias_handle,
+ ALIASINFODESCRIPTION,
+ &alias_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&alias_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &alias_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupAdd_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAdd *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupAdd);
+}
+
+/****************************************************************
+****************************************************************/
+
+
+WERROR NetLocalGroupDel_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDel *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, builtin_handle, alias_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(alias_handle);
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_LOOKUP_DOMAIN |
+ SAMR_ACCESS_ENUM_DOMAINS,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &builtin_handle,
+ r->in.group_name,
+ SEC_STD_DELETE,
+ &alias_handle);
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ goto delete_alias;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_CREATE_ALIAS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &domain_handle,
+ r->in.group_name,
+ SEC_STD_DELETE,
+ &alias_handle);
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+
+ delete_alias:
+ status = dcerpc_samr_DeleteDomAlias(b, talloc_tos(),
+ &alias_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ ZERO_STRUCT(alias_handle);
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&alias_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &alias_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupDel_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDel *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupDel);
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR map_alias_info_to_buffer(TALLOC_CTX *mem_ctx,
+ const char *alias_name,
+ struct samr_AliasInfoAll *info,
+ uint32_t level,
+ uint32_t *entries_read,
+ uint8_t **buffer)
+{
+ struct LOCALGROUP_INFO_0 g0;
+ struct LOCALGROUP_INFO_1 g1;
+ struct LOCALGROUP_INFO_1002 g1002;
+
+ switch (level) {
+ case 0:
+ g0.lgrpi0_name = talloc_strdup(mem_ctx, alias_name);
+ W_ERROR_HAVE_NO_MEMORY(g0.lgrpi0_name);
+
+ ADD_TO_ARRAY(mem_ctx, struct LOCALGROUP_INFO_0, g0,
+ (struct LOCALGROUP_INFO_0 **)buffer, entries_read);
+
+ break;
+ case 1:
+ g1.lgrpi1_name = talloc_strdup(mem_ctx, alias_name);
+ g1.lgrpi1_comment = talloc_strdup(mem_ctx, info->description.string);
+ W_ERROR_HAVE_NO_MEMORY(g1.lgrpi1_name);
+
+ ADD_TO_ARRAY(mem_ctx, struct LOCALGROUP_INFO_1, g1,
+ (struct LOCALGROUP_INFO_1 **)buffer, entries_read);
+
+ break;
+ case 1002:
+ g1002.lgrpi1002_comment = talloc_strdup(mem_ctx, info->description.string);
+
+ ADD_TO_ARRAY(mem_ctx, struct LOCALGROUP_INFO_1002, g1002,
+ (struct LOCALGROUP_INFO_1002 **)buffer, entries_read);
+
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetInfo *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, builtin_handle, alias_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ union samr_AliasInfo *alias_info = NULL;
+ uint32_t entries_read = 0;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ case 1002:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(alias_handle);
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_LOOKUP_DOMAIN |
+ SAMR_ACCESS_ENUM_DOMAINS,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &builtin_handle,
+ r->in.group_name,
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ &alias_handle);
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ goto query_alias;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_CREATE_ALIAS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &domain_handle,
+ r->in.group_name,
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ &alias_handle);
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ query_alias:
+ status = dcerpc_samr_QueryAliasInfo(b, talloc_tos(),
+ &alias_handle,
+ ALIASINFOALL,
+ &alias_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = map_alias_info_to_buffer(ctx,
+ r->in.group_name,
+ &alias_info->all,
+ r->in.level, &entries_read,
+ r->out.buffer);
+
+ done:
+ if (is_valid_policy_hnd(&alias_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &alias_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupGetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR map_buffer_to_alias_info(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ uint8_t *buffer,
+ enum samr_AliasInfoEnum *alias_level,
+ union samr_AliasInfo **alias_info)
+{
+ struct LOCALGROUP_INFO_0 *info0;
+ struct LOCALGROUP_INFO_1 *info1;
+ struct LOCALGROUP_INFO_1002 *info1002;
+ union samr_AliasInfo *info = NULL;
+
+ info = talloc_zero(mem_ctx, union samr_AliasInfo);
+ W_ERROR_HAVE_NO_MEMORY(info);
+
+ switch (level) {
+ case 0:
+ info0 = (struct LOCALGROUP_INFO_0 *)buffer;
+ init_lsa_String(&info->name, info0->lgrpi0_name);
+ *alias_level = ALIASINFONAME;
+ break;
+ case 1:
+ info1 = (struct LOCALGROUP_INFO_1 *)buffer;
+ /* group name will be ignored */
+ init_lsa_String(&info->description, info1->lgrpi1_comment);
+ *alias_level = ALIASINFODESCRIPTION;
+ break;
+ case 1002:
+ info1002 = (struct LOCALGROUP_INFO_1002 *)buffer;
+ init_lsa_String(&info->description, info1002->lgrpi1002_comment);
+ *alias_level = ALIASINFODESCRIPTION;
+ break;
+ }
+
+ *alias_info = info;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetInfo *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct lsa_String lsa_account_name;
+ struct policy_handle connect_handle, domain_handle, builtin_handle, alias_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ enum samr_AliasInfoEnum alias_level = 0;
+ union samr_AliasInfo *alias_info = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (!r->in.group_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ case 1002:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(alias_handle);
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_LOOKUP_DOMAIN |
+ SAMR_ACCESS_ENUM_DOMAINS,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.group_name);
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &builtin_handle,
+ r->in.group_name,
+ SAMR_ALIAS_ACCESS_SET_INFO,
+ &alias_handle);
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ goto set_alias;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &domain_handle,
+ r->in.group_name,
+ SAMR_ALIAS_ACCESS_SET_INFO,
+ &alias_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ }
+
+ set_alias:
+
+ werr = map_buffer_to_alias_info(ctx, r->in.level, r->in.buffer,
+ &alias_level, &alias_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_SetAliasInfo(b, talloc_tos(),
+ &alias_handle,
+ alias_level,
+ alias_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&alias_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &alias_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupSetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupEnum_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupEnum *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, builtin_handle, alias_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ uint32_t entries_read = 0;
+ union samr_DomainInfo *domain_info = NULL;
+ union samr_DomainInfo *builtin_info = NULL;
+ struct samr_SamArray *domain_sam_array = NULL;
+ struct samr_SamArray *builtin_sam_array = NULL;
+ int i;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (r->out.total_entries) {
+ *r->out.total_entries = 0;
+ }
+ if (r->out.entries_read) {
+ *r->out.entries_read = 0;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(alias_handle);
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_LOOKUP_DOMAIN |
+ SAMR_ACCESS_ENUM_DOMAINS,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2 |
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_LOOKUP_DOMAIN |
+ SAMR_ACCESS_ENUM_DOMAINS,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2 |
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryDomainInfo(b, talloc_tos(),
+ &builtin_handle,
+ 2,
+ &builtin_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (r->out.total_entries) {
+ *r->out.total_entries += builtin_info->general.num_aliases;
+ }
+
+ status = dcerpc_samr_QueryDomainInfo(b, talloc_tos(),
+ &domain_handle,
+ 2,
+ &domain_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (r->out.total_entries) {
+ *r->out.total_entries += domain_info->general.num_aliases;
+ }
+
+ status = dcerpc_samr_EnumDomainAliases(b, talloc_tos(),
+ &builtin_handle,
+ r->in.resume_handle,
+ &builtin_sam_array,
+ r->in.prefmaxlen,
+ &entries_read,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ for (i=0; i<builtin_sam_array->count; i++) {
+ union samr_AliasInfo *alias_info = NULL;
+
+ if (r->in.level == 1) {
+
+ status = libnetapi_samr_open_alias_queryinfo(ctx, pipe_cli,
+ &builtin_handle,
+ builtin_sam_array->entries[i].idx,
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ ALIASINFOALL,
+ &alias_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ werr = map_alias_info_to_buffer(ctx,
+ builtin_sam_array->entries[i].name.string,
+ alias_info ? &alias_info->all : NULL,
+ r->in.level,
+ r->out.entries_read,
+ r->out.buffer);
+ }
+
+ status = dcerpc_samr_EnumDomainAliases(b, talloc_tos(),
+ &domain_handle,
+ r->in.resume_handle,
+ &domain_sam_array,
+ r->in.prefmaxlen,
+ &entries_read,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ for (i=0; i<domain_sam_array->count; i++) {
+
+ union samr_AliasInfo *alias_info = NULL;
+
+ if (r->in.level == 1) {
+ status = libnetapi_samr_open_alias_queryinfo(ctx, pipe_cli,
+ &domain_handle,
+ domain_sam_array->entries[i].idx,
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ ALIASINFOALL,
+ &alias_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ werr = map_alias_info_to_buffer(ctx,
+ domain_sam_array->entries[i].name.string,
+ alias_info ? &alias_info->all : NULL,
+ r->in.level,
+ r->out.entries_read,
+ r->out.buffer);
+ }
+
+ done:
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupEnum_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupEnum *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupEnum);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnetapi_lsa_lookup_names3(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *lsa_pipe,
+ const char *name,
+ struct dom_sid *sid)
+{
+ NTSTATUS status, result;
+ struct policy_handle lsa_handle;
+ struct dcerpc_binding_handle *b = lsa_pipe->binding_handle;
+
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransSidArray3 sids;
+ uint32_t count = 0;
+
+ struct lsa_String names;
+ uint32_t num_names = 1;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (!sid || !name) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(sids);
+
+ init_lsa_String(&names, name);
+
+ status = dcerpc_lsa_open_policy_fallback(
+ b,
+ mem_ctx,
+ lsa_pipe->srv_name_slash,
+ false,
+ SEC_STD_READ_CONTROL |
+ LSA_POLICY_VIEW_LOCAL_INFORMATION |
+ LSA_POLICY_LOOKUP_NAMES,
+ &out_version,
+ &out_revision_info,
+ &lsa_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ status = dcerpc_lsa_LookupNames3(b, mem_ctx,
+ &lsa_handle,
+ num_names,
+ &names,
+ &domains,
+ &sids,
+ LSA_LOOKUP_NAMES_ALL, /* sure ? */
+ &count,
+ 0, 0,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+
+ if (count != 1 || sids.count != 1) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ sid_copy(sid, sids.sids[0].sid);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR NetLocalGroupModifyMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAddMembers *add,
+ struct NetLocalGroupDelMembers *del,
+ struct NetLocalGroupSetMembers *set)
+{
+ struct NetLocalGroupAddMembers *r = NULL;
+
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct rpc_pipe_client *lsa_pipe = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct lsa_String lsa_account_name;
+ struct policy_handle connect_handle, domain_handle, builtin_handle, alias_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ struct dom_sid *member_sids = NULL;
+ int i = 0, k = 0;
+
+ struct LOCALGROUP_MEMBERS_INFO_0 *info0 = NULL;
+ struct LOCALGROUP_MEMBERS_INFO_3 *info3 = NULL;
+
+ struct dom_sid *add_sids = NULL;
+ struct dom_sid *del_sids = NULL;
+ uint32_t num_add_sids = 0;
+ uint32_t num_del_sids = 0;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if ((!add && !del && !set) || (add && del && set)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (add) {
+ r = add;
+ }
+
+ if (del) {
+ r = (struct NetLocalGroupAddMembers *)del;
+ }
+
+ if (set) {
+ r = (struct NetLocalGroupAddMembers *)set;
+ }
+
+ if (r==NULL || r->in.group_name == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 3:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (r->in.total_entries == 0 || !r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(alias_handle);
+
+ member_sids = talloc_zero_array(ctx, struct dom_sid,
+ r->in.total_entries);
+ W_ERROR_HAVE_NO_MEMORY(member_sids);
+
+ switch (r->in.level) {
+ case 0:
+ info0 = (struct LOCALGROUP_MEMBERS_INFO_0 *)r->in.buffer;
+ for (i=0; i < r->in.total_entries; i++) {
+ sid_copy(&member_sids[i], (struct dom_sid *)info0[i].lgrmi0_sid);
+ }
+ break;
+ case 3:
+ info3 = (struct LOCALGROUP_MEMBERS_INFO_3 *)r->in.buffer;
+ break;
+ default:
+ break;
+ }
+
+ if (r->in.level == 3) {
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_lsarpc,
+ &lsa_pipe);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ for (i=0; i < r->in.total_entries; i++) {
+ status = libnetapi_lsa_lookup_names3(ctx, lsa_pipe,
+ info3[i].lgrmi3_domainandname,
+ &member_sids[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+ TALLOC_FREE(lsa_pipe);
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_LOOKUP_DOMAIN |
+ SAMR_ACCESS_ENUM_DOMAINS,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.group_name);
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &builtin_handle,
+ r->in.group_name,
+ SAMR_ALIAS_ACCESS_ADD_MEMBER |
+ SAMR_ALIAS_ACCESS_REMOVE_MEMBER |
+ SAMR_ALIAS_ACCESS_GET_MEMBERS |
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ &alias_handle);
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ goto modify_membership;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_and_open_alias(ctx, pipe_cli,
+ &domain_handle,
+ r->in.group_name,
+ SAMR_ALIAS_ACCESS_ADD_MEMBER |
+ SAMR_ALIAS_ACCESS_REMOVE_MEMBER |
+ SAMR_ALIAS_ACCESS_GET_MEMBERS |
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ &alias_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ }
+
+ modify_membership:
+
+ if (add) {
+ for (i=0; i < r->in.total_entries; i++) {
+ status = add_sid_to_array_unique(ctx, &member_sids[i],
+ &add_sids,
+ &num_add_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+ }
+
+ if (del) {
+ for (i=0; i < r->in.total_entries; i++) {
+ status = add_sid_to_array_unique(ctx, &member_sids[i],
+ &del_sids,
+ &num_del_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+ }
+
+ if (set) {
+
+ struct lsa_SidArray current_sids;
+
+ status = dcerpc_samr_GetMembersInAlias(b, talloc_tos(),
+ &alias_handle,
+ &current_sids,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ /* add list */
+
+ for (i=0; i < r->in.total_entries; i++) {
+ bool already_member = false;
+ for (k=0; k < current_sids.num_sids; k++) {
+ if (dom_sid_equal(&member_sids[i],
+ current_sids.sids[k].sid)) {
+ already_member = true;
+ break;
+ }
+ }
+ if (!already_member) {
+ status = add_sid_to_array_unique(ctx,
+ &member_sids[i],
+ &add_sids, &num_add_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+ }
+
+ /* del list */
+
+ for (k=0; k < current_sids.num_sids; k++) {
+ bool keep_member = false;
+ for (i=0; i < r->in.total_entries; i++) {
+ if (dom_sid_equal(&member_sids[i],
+ current_sids.sids[k].sid)) {
+ keep_member = true;
+ break;
+ }
+ }
+ if (!keep_member) {
+ status = add_sid_to_array_unique(ctx,
+ current_sids.sids[k].sid,
+ &del_sids, &num_del_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+ }
+ }
+
+ /* add list */
+
+ for (i=0; i < num_add_sids; i++) {
+ status = dcerpc_samr_AddAliasMember(b, talloc_tos(),
+ &alias_handle,
+ &add_sids[i],
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ /* del list */
+
+ for (i=0; i < num_del_sids; i++) {
+ status = dcerpc_samr_DeleteAliasMember(b, talloc_tos(),
+ &alias_handle,
+ &del_sids[i],
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (b && is_valid_policy_hnd(&alias_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &alias_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupAddMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAddMembers *r)
+{
+ return NetLocalGroupModifyMembers_r(ctx, r, NULL, NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupAddMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupAddMembers *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupAddMembers);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupDelMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDelMembers *r)
+{
+ return NetLocalGroupModifyMembers_r(ctx, NULL, r, NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupDelMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupDelMembers *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupDelMembers);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupGetMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetMembers *r)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupGetMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupGetMembers *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupGetMembers);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupSetMembers_r(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetMembers *r)
+{
+ return NetLocalGroupModifyMembers_r(ctx, NULL, NULL, r);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetLocalGroupSetMembers_l(struct libnetapi_ctx *ctx,
+ struct NetLocalGroupSetMembers *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetLocalGroupSetMembers);
+}
diff --git a/source3/lib/netapi/netapi.c b/source3/lib/netapi/netapi.c
new file mode 100644
index 0000000..9c049a8
--- /dev/null
+++ b/source3/lib/netapi/netapi.c
@@ -0,0 +1,523 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../libcli/auth/netlogon_creds_cli.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "secrets.h"
+#include "krb5_env.h"
+#include "source3/param/loadparm.h"
+#include "lib/param/param.h"
+#include "auth/gensec/gensec.h"
+
+struct libnetapi_ctx *stat_ctx = NULL;
+static bool libnetapi_initialized = false;
+
+/****************************************************************
+****************************************************************/
+
+static NET_API_STATUS libnetapi_init_private_context(struct libnetapi_ctx *ctx)
+{
+ struct libnetapi_private_ctx *priv;
+
+ if (!ctx) {
+ return W_ERROR_V(WERR_INVALID_PARAMETER);
+ }
+
+ priv = talloc_zero(ctx, struct libnetapi_private_ctx);
+ if (!priv) {
+ return W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ }
+
+ ctx->private_data = priv;
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+Create a libnetapi context, for use in non-Samba applications. This
+loads the smb.conf file and sets the debug level to 0, so that
+applications are not flooded with debug logs at level 10, when they
+were not expecting it.
+****************************************************************/
+
+NET_API_STATUS libnetapi_init(struct libnetapi_ctx **context)
+{
+ NET_API_STATUS ret;
+ TALLOC_CTX *frame;
+ struct loadparm_context *lp_ctx = NULL;
+
+ if (stat_ctx && libnetapi_initialized) {
+ *context = stat_ctx;
+ return NET_API_STATUS_SUCCESS;
+ }
+
+#if 0
+ talloc_enable_leak_report();
+#endif
+ frame = talloc_stackframe();
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ }
+
+ /* When libnetapi is invoked from an application, it does not
+ * want to be swamped with level 10 debug messages, even if
+ * this has been set for the server in smb.conf */
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+ setup_logging("libnetapi", DEBUG_STDERR);
+
+ if (!lp_load_global(get_dyn_CONFIGFILE())) {
+ TALLOC_FREE(frame);
+ fprintf(stderr, "error loading %s\n", get_dyn_CONFIGFILE() );
+ return W_ERROR_V(WERR_GEN_FAILURE);
+ }
+
+ load_interfaces();
+ reopen_logs();
+
+ BlockSignals(True, SIGPIPE);
+
+ ret = libnetapi_net_init(context, lp_ctx, NULL);
+ if (ret == NET_API_STATUS_SUCCESS) {
+ talloc_steal(*context, lp_ctx);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/****************************************************************
+Create a libnetapi context, for use inside the 'net' binary.
+
+As we know net has already loaded the smb.conf file, and set the debug
+level etc, this avoids doing so again (which causes trouble with -d on
+the command line).
+****************************************************************/
+
+NET_API_STATUS libnetapi_net_init(struct libnetapi_ctx **context,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *creds)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *ctx = NULL;
+ TALLOC_CTX *frame = NULL;
+
+ if (stat_ctx != NULL && libnetapi_initialized) {
+ *context = stat_ctx;
+ return NET_API_STATUS_SUCCESS;
+ }
+
+ frame = talloc_stackframe();
+ ctx = talloc_zero(frame, struct libnetapi_ctx);
+ if (!ctx) {
+ TALLOC_FREE(frame);
+ return W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ }
+
+ ctx->lp_ctx = lp_ctx;
+
+ ctx->creds = creds;
+ if (ctx->creds == NULL) {
+ ctx->creds = cli_credentials_init(ctx);
+ if (ctx->creds == NULL) {
+ TALLOC_FREE(frame);
+ return W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ }
+ /* Ignore return code, as we might not have a smb.conf */
+ (void)cli_credentials_guess(ctx->creds, lp_ctx);
+ }
+
+ BlockSignals(True, SIGPIPE);
+
+ status = libnetapi_init_private_context(ctx);
+ if (status != 0) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ libnetapi_initialized = true;
+
+ talloc_steal(NULL, ctx);
+ *context = stat_ctx = ctx;
+
+ TALLOC_FREE(frame);
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+ Return the static libnetapi context
+****************************************************************/
+
+NET_API_STATUS libnetapi_getctx(struct libnetapi_ctx **ctx)
+{
+ if (stat_ctx) {
+ *ctx = stat_ctx;
+ return NET_API_STATUS_SUCCESS;
+ }
+
+ return libnetapi_init(ctx);
+}
+
+/****************************************************************
+ Free the static libnetapi context
+****************************************************************/
+
+NET_API_STATUS libnetapi_free(struct libnetapi_ctx *ctx)
+{
+ TALLOC_CTX *frame;
+
+ if (!ctx) {
+ return NET_API_STATUS_SUCCESS;
+ }
+
+ frame = talloc_stackframe();
+ libnetapi_samr_free(ctx);
+
+ libnetapi_shutdown_cm(ctx);
+
+ gfree_loadparm();
+ gfree_charcnv();
+ gfree_interfaces();
+
+ secrets_shutdown();
+
+ netlogon_creds_cli_close_global_db();
+
+ if (ctx == stat_ctx) {
+ stat_ctx = NULL;
+ }
+ TALLOC_FREE(ctx);
+
+ gfree_debugsyms();
+ talloc_free(frame);
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+ Override the current log level for libnetapi
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_debuglevel(struct libnetapi_ctx *ctx,
+ const char *debuglevel)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ ctx->debuglevel = talloc_strdup(ctx, debuglevel);
+
+ if (!lpcfg_set_cmdline(ctx->lp_ctx, "log level", debuglevel)) {
+ TALLOC_FREE(frame);
+ return W_ERROR_V(WERR_GEN_FAILURE);
+ }
+ TALLOC_FREE(frame);
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_logfile(struct libnetapi_ctx *ctx,
+ const char *logfile)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ ctx->logfile = talloc_strdup(ctx, logfile);
+
+ if (!lpcfg_set_cmdline(ctx->lp_ctx, "log file", logfile)) {
+ TALLOC_FREE(frame);
+ return W_ERROR_V(WERR_GEN_FAILURE);
+ }
+ debug_set_logfile(logfile);
+ setup_logging("libnetapi", DEBUG_FILE);
+ TALLOC_FREE(frame);
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_get_debuglevel(struct libnetapi_ctx *ctx,
+ char **debuglevel)
+{
+ *debuglevel = ctx->debuglevel;
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+/**
+ * @brief Get the username of the libnet context
+ *
+ * @param[in] ctx The netapi context
+ *
+ * @param[in] username A pointer to hold the username.
+ *
+ * @return 0 on success, an werror code otherwise.
+ */
+NET_API_STATUS libnetapi_get_username(struct libnetapi_ctx *ctx,
+ const char **username)
+{
+ if (ctx == NULL) {
+ return W_ERROR_V(WERR_INVALID_PARAMETER);
+ }
+
+ if (username != NULL) {
+ *username = cli_credentials_get_username(ctx->creds);
+ }
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/**
+ * @brief Get the password of the libnet context
+ *
+ * @param[in] ctx The netapi context
+ *
+ * @param[in] password A pointer to hold the password.
+ *
+ * @return 0 on success, an werror code otherwise.
+ */
+NET_API_STATUS libnetapi_get_password(struct libnetapi_ctx *ctx,
+ const char **password)
+{
+ if (ctx == NULL) {
+ return W_ERROR_V(WERR_INVALID_PARAMETER);
+ }
+
+ if (password != NULL) {
+ *password = cli_credentials_get_password(ctx->creds);
+ }
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+NET_API_STATUS libnetapi_set_username(struct libnetapi_ctx *ctx,
+ const char *username)
+{
+ if (ctx == NULL || username == NULL) {
+ return W_ERROR_V(WERR_INVALID_PARAMETER);
+ }
+
+ cli_credentials_parse_string(ctx->creds, username, CRED_SPECIFIED);
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+NET_API_STATUS libnetapi_set_password(struct libnetapi_ctx *ctx,
+ const char *password)
+{
+ bool ok;
+
+ if (ctx == NULL || password == NULL) {
+ return W_ERROR_V(WERR_INVALID_PARAMETER);
+ }
+
+ ok = cli_credentials_set_password(ctx->creds, password, CRED_SPECIFIED);
+ if (!ok) {
+ return W_ERROR_V(WERR_INTERNAL_ERROR);
+ }
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+NET_API_STATUS libnetapi_set_workgroup(struct libnetapi_ctx *ctx,
+ const char *workgroup)
+{
+ bool ok;
+
+ ok = cli_credentials_set_domain(ctx->creds, workgroup, CRED_SPECIFIED);
+ if (!ok) {
+ return W_ERROR_V(WERR_INTERNAL_ERROR);
+ }
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/**
+ * @brief Set the cli_credentials to be used in the netapi context
+ *
+ * @param[in] ctx The netapi context
+ *
+ * @param[in] creds The cli_credentials which should be used by netapi.
+ *
+ * @return 0 on success, an werror code otherwise.
+ */
+NET_API_STATUS libnetapi_set_creds(struct libnetapi_ctx *ctx,
+ struct cli_credentials *creds)
+{
+ if (ctx == NULL || creds == NULL) {
+ return W_ERROR_V(WERR_INVALID_PARAMETER);
+ }
+
+ ctx->creds = creds;
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_use_kerberos(struct libnetapi_ctx *ctx)
+{
+ cli_credentials_set_kerberos_state(ctx->creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_get_use_kerberos(struct libnetapi_ctx *ctx,
+ int *use_kerberos)
+{
+ enum credentials_use_kerberos creds_use_kerberos;
+
+ *use_kerberos = 0;
+
+ creds_use_kerberos = cli_credentials_get_kerberos_state(ctx->creds);
+ if (creds_use_kerberos > CRED_USE_KERBEROS_DESIRED) {
+ *use_kerberos = 1;
+ }
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_use_ccache(struct libnetapi_ctx *ctx)
+{
+ uint32_t gensec_features;
+
+ gensec_features = cli_credentials_get_gensec_features(ctx->creds);
+ gensec_features |= GENSEC_FEATURE_NTLM_CCACHE;
+ cli_credentials_set_gensec_features(ctx->creds,
+ gensec_features,
+ CRED_SPECIFIED);
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+Return a libnetapi error as a string, caller must free with NetApiBufferFree
+****************************************************************/
+
+char *libnetapi_errstr(NET_API_STATUS status)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *ret;
+ if (status & 0xc0000000) {
+ ret = talloc_strdup(NULL,
+ get_friendly_nt_error_msg(NT_STATUS(status)));
+ } else {
+ ret = talloc_strdup(NULL,
+ get_friendly_werror_msg(W_ERROR(status)));
+ }
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_error_string(struct libnetapi_ctx *ctx,
+ const char *format, ...)
+{
+ va_list args;
+
+ TALLOC_FREE(ctx->error_string);
+
+ va_start(args, format);
+ ctx->error_string = talloc_vasprintf(ctx, format, args);
+ va_end(args);
+
+ if (!ctx->error_string) {
+ return W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ }
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+Return a libnetapi_errstr(), caller must free with NetApiBufferFree
+****************************************************************/
+
+char *libnetapi_get_error_string(struct libnetapi_ctx *ctx,
+ NET_API_STATUS status_in)
+{
+ NET_API_STATUS status;
+ struct libnetapi_ctx *tmp_ctx = ctx;
+
+ if (!tmp_ctx) {
+ status = libnetapi_getctx(&tmp_ctx);
+ if (status != 0) {
+ return NULL;
+ }
+ }
+
+ if (tmp_ctx->error_string) {
+ return talloc_strdup(NULL, tmp_ctx->error_string);
+ }
+
+ return libnetapi_errstr(status_in);
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS NetApiBufferAllocate(uint32_t byte_count,
+ void **buffer)
+{
+ void *buf = NULL;
+
+ if (!buffer) {
+ return W_ERROR_V(WERR_INSUFFICIENT_BUFFER);
+ }
+
+ if (byte_count == 0) {
+ goto done;
+ }
+
+ buf = talloc_size(NULL, byte_count);
+ if (!buf) {
+ return W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ }
+
+ done:
+ *buffer = buf;
+
+ return NET_API_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS NetApiBufferFree(void *buffer)
+{
+ if (!buffer) {
+ return W_ERROR_V(WERR_INSUFFICIENT_BUFFER);
+ }
+
+ talloc_free(buffer);
+
+ return NET_API_STATUS_SUCCESS;
+}
diff --git a/source3/lib/netapi/netapi.h b/source3/lib/netapi/netapi.h
new file mode 100644
index 0000000..660a776
--- /dev/null
+++ b/source3/lib/netapi/netapi.h
@@ -0,0 +1,2778 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Guenther Deschner 2007-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_NETAPI_H__
+#define __LIB_NETAPI_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+struct cli_credentials;
+
+/****************************************************************
+ NET_API_STATUS
+****************************************************************/
+typedef enum {
+ NET_API_STATUS_SUCCESS = 0
+} NET_API_STATUS;
+
+#define ERROR_MORE_DATA ( 234L )
+
+#define ENCRYPTED_PWLEN ( 16 )
+
+/****************************************************************
+****************************************************************/
+
+#ifndef _HEADER_misc
+
+struct GUID {
+ uint32_t time_low;
+ uint16_t time_mid;
+ uint16_t time_hi_and_version;
+ uint8_t clock_seq[2];
+ uint8_t node[6];
+};
+
+#endif /* _HEADER_misc */
+
+#ifndef _HEADER_libnetapi
+
+#ifndef MAXSUBAUTHS
+#define MAXSUBAUTHS 15 /* max sub authorities in a SID */
+#endif
+
+struct domsid {
+ uint8_t sid_rev_num;
+ uint8_t num_auths;
+ uint8_t id_auth[6];
+ uint32_t sub_auths[MAXSUBAUTHS];
+};
+
+struct DOMAIN_CONTROLLER_INFO {
+ const char * domain_controller_name;
+ const char * domain_controller_address;
+ uint32_t domain_controller_address_type;
+ struct GUID domain_guid;
+ const char * domain_name;
+ const char * dns_forest_name;
+ uint32_t flags;
+ const char * dc_site_name;
+ const char * client_site_name;
+};
+
+/* bitmap NetJoinFlags */
+#define NETSETUP_JOIN_DOMAIN ( 0x00000001 )
+#define NETSETUP_ACCT_CREATE ( 0x00000002 )
+#define NETSETUP_ACCT_DELETE ( 0x00000004 )
+#define NETSETUP_WIN9X_UPGRADE ( 0x00000010 )
+#define NETSETUP_DOMAIN_JOIN_IF_JOINED ( 0x00000020 )
+#define NETSETUP_JOIN_UNSECURE ( 0x00000040 )
+#define NETSETUP_MACHINE_PWD_PASSED ( 0x00000080 )
+#define NETSETUP_DEFER_SPN_SET ( 0x00000100 )
+#define NETSETUP_JOIN_DC_ACCOUNT ( 0x00000200 )
+#define NETSETUP_JOIN_WITH_NEW_NAME ( 0x00000400 )
+#define NETSETUP_JOIN_READONLY ( 0x00000800 )
+#define NETSETUP_AMBIGUOUS_DC ( 0x00001000 )
+#define NETSETUP_NO_NETLOGON_CACHE ( 0x00002000 )
+#define NETSETUP_DONT_CONTROL_SERVICES ( 0x00004000 )
+#define NETSETUP_SET_MACHINE_NAME ( 0x00008000 )
+#define NETSETUP_FORCE_SPN_SET ( 0x00010000 )
+#define NETSETUP_NO_ACCT_REUSE ( 0x00020000 )
+#define NETSETUP_INSTALL_INVOCATION ( 0x00040000 )
+#define NETSETUP_IGNORE_UNSUPPORTED_FLAGS ( 0x10000000 )
+
+/* bitmap NetProvisionFlags */
+#define NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT ( 0x00000001 )
+#define NETSETUP_PROVISION_REUSE_ACCOUNT ( 0x00000002 )
+#define NETSETUP_PROVISION_USE_DEFAULT_PASSWORD ( 0x00000004 )
+#define NETSETUP_PROVISION_SKIP_ACCOUNT_SEARCH ( 0x00000008 )
+#define NETSETUP_PROVISION_ROOT_CA_CERTS ( 0x00000010 )
+
+/* bitmap NetProvisionJoinFlags */
+#define NETSETUP_PROVISION_ONLINE_CALLER ( 0x40000000 )
+
+#define FILTER_TEMP_DUPLICATE_ACCOUNT ( 0x0001 )
+#define FILTER_NORMAL_ACCOUNT ( 0x0002 )
+#define FILTER_INTERDOMAIN_TRUST_ACCOUNT ( 0x0008 )
+#define FILTER_WORKSTATION_TRUST_ACCOUNT ( 0x0010 )
+#define FILTER_SERVER_TRUST_ACCOUNT ( 0x0020 )
+
+#define TIMEQ_FOREVER ( (uint32_t)-1L )
+
+enum NETSETUP_JOIN_STATUS {
+ NetSetupUnknownStatus=0,
+ NetSetupUnjoined=1,
+ NetSetupWorkgroupName=2,
+ NetSetupDomainName=3
+};
+
+struct SERVER_INFO_100 {
+ uint32_t sv100_platform_id;
+ const char * sv100_name;
+};
+
+struct SERVER_INFO_101 {
+ uint32_t sv101_platform_id;
+ const char * sv101_name;
+ uint32_t sv101_version_major;
+ uint32_t sv101_version_minor;
+ uint32_t sv101_type;
+ const char * sv101_comment;
+};
+
+struct SERVER_INFO_102 {
+ uint32_t sv102_platform_id;
+ const char * sv102_name;
+ uint32_t sv102_version_major;
+ uint32_t sv102_version_minor;
+ uint32_t sv102_type;
+ const char * sv102_comment;
+ uint32_t sv102_users;
+ uint32_t sv102_disc;
+ uint8_t sv102_hidden;
+ uint32_t sv102_announce;
+ uint32_t sv102_anndelta;
+ uint32_t sv102_licenses;
+ const char * sv102_userpath;
+};
+
+struct SERVER_INFO_402 {
+ uint32_t sv402_ulist_mtime;
+ uint32_t sv402_glist_mtime;
+ uint32_t sv402_alist_mtime;
+ const char * sv402_alerts;
+ uint32_t sv402_security;
+ uint32_t sv402_numadmin;
+ uint32_t sv402_lanmask;
+ const char * sv402_guestacct;
+ uint32_t sv402_chdevs;
+ uint32_t sv402_chdevq;
+ uint32_t sv402_chdevjobs;
+ uint32_t sv402_connections;
+ uint32_t sv402_shares;
+ uint32_t sv402_openfiles;
+ uint32_t sv402_sessopens;
+ uint32_t sv402_sessvcs;
+ uint32_t sv402_sessreqs;
+ uint32_t sv402_opensearch;
+ uint32_t sv402_activelocks;
+ uint32_t sv402_numreqbuf;
+ uint32_t sv402_sizreqbuf;
+ uint32_t sv402_numbigbuf;
+ uint32_t sv402_numfiletasks;
+ uint32_t sv402_alertsched;
+ uint32_t sv402_erroralert;
+ uint32_t sv402_logonalert;
+ uint32_t sv402_accessalert;
+ uint32_t sv402_diskalert;
+ uint32_t sv402_netioalert;
+ uint32_t sv402_maxauditsz;
+ const char * sv402_srvheuristics;
+};
+
+struct SERVER_INFO_403 {
+ uint32_t sv403_ulist_mtime;
+ uint32_t sv403_glist_mtime;
+ uint32_t sv403_alist_mtime;
+ const char * sv403_alerts;
+ uint32_t sv403_security;
+ uint32_t sv403_numadmin;
+ uint32_t sv403_lanmask;
+ const char * sv403_guestacct;
+ uint32_t sv403_chdevs;
+ uint32_t sv403_chdevq;
+ uint32_t sv403_chdevjobs;
+ uint32_t sv403_connections;
+ uint32_t sv403_shares;
+ uint32_t sv403_openfiles;
+ uint32_t sv403_sessopens;
+ uint32_t sv403_sessvcs;
+ uint32_t sv403_sessreqs;
+ uint32_t sv403_opensearch;
+ uint32_t sv403_activelocks;
+ uint32_t sv403_numreqbuf;
+ uint32_t sv403_sizreqbuf;
+ uint32_t sv403_numbigbuf;
+ uint32_t sv403_numfiletasks;
+ uint32_t sv403_alertsched;
+ uint32_t sv403_erroralert;
+ uint32_t sv403_logonalert;
+ uint32_t sv403_accessalert;
+ uint32_t sv403_diskalert;
+ uint32_t sv403_netioalert;
+ uint32_t sv403_maxauditsz;
+ const char * sv403_srvheuristics;
+ uint32_t sv403_auditedevents;
+ uint32_t sv403_autoprofile;
+ const char * sv403_autopath;
+};
+
+struct SERVER_INFO_502 {
+ uint32_t sv502_sessopens;
+ uint32_t sv502_sessvcs;
+ uint32_t sv502_opensearch;
+ uint32_t sv502_sizreqbuf;
+ uint32_t sv502_initworkitems;
+ uint32_t sv502_maxworkitems;
+ uint32_t sv502_rawworkitems;
+ uint32_t sv502_irpstacksize;
+ uint32_t sv502_maxrawbuflen;
+ uint32_t sv502_sessusers;
+ uint32_t sv502_sessconns;
+ uint32_t sv502_maxpagedmemoryusage;
+ uint32_t sv502_maxnonpagedmemoryusage;
+ uint8_t sv502_enablesoftcompat;
+ uint8_t sv502_enableforcedlogoff;
+ uint8_t sv502_timesource;
+ uint8_t sv502_acceptdownlevelapis;
+ uint8_t sv502_lmannounce;
+};
+
+struct SERVER_INFO_503 {
+ uint32_t sv503_sessopens;
+ uint32_t sv503_sessvcs;
+ uint32_t sv503_opensearch;
+ uint32_t sv503_sizreqbuf;
+ uint32_t sv503_initworkitems;
+ uint32_t sv503_maxworkitems;
+ uint32_t sv503_rawworkitems;
+ uint32_t sv503_irpstacksize;
+ uint32_t sv503_maxrawbuflen;
+ uint32_t sv503_sessusers;
+ uint32_t sv503_sessconns;
+ uint32_t sv503_maxpagedmemoryusage;
+ uint32_t sv503_maxnonpagedmemoryusage;
+ uint8_t sv503_enablesoftcompat;
+ uint8_t sv503_enableforcedlogoff;
+ uint8_t sv503_timesource;
+ uint8_t sv503_acceptdownlevelapis;
+ uint8_t sv503_lmannounce;
+ const char * sv503_domain;
+ uint32_t sv503_maxcopyreadlen;
+ uint32_t sv503_maxcopywritelen;
+ uint32_t sv503_minkeepsearch;
+ uint32_t sv503_maxkeepsearch;
+ uint32_t sv503_minkeepcomplsearch;
+ uint32_t sv503_maxkeepcomplsearch;
+ uint32_t sv503_threadcountadd;
+ uint32_t sv503_numblockthreads;
+ uint32_t sv503_scavtimeout;
+ uint32_t sv503_minrcvqueue;
+ uint32_t sv503_minfreeworkitems;
+ uint32_t sv503_xactmemsize;
+ uint32_t sv503_threadpriority;
+ uint32_t sv503_maxmpxct;
+ uint32_t sv503_oplockbreakwait;
+ uint32_t sv503_oplockbreakresponsewait;
+ uint8_t sv503_enableoplocks;
+ uint8_t sv503_enableoplockforceclose;
+ uint8_t sv503_enablefcbopens;
+ uint8_t sv503_enableraw;
+ uint8_t sv503_enablesharednetdrives;
+ uint32_t sv503_minfreeconnections;
+ uint32_t sv503_maxfreeconnections;
+};
+
+struct SERVER_INFO_599 {
+ uint32_t sv599_sessopens;
+ uint32_t sv599_sessvcs;
+ uint32_t sv599_opensearch;
+ uint32_t sv599_sizreqbuf;
+ uint32_t sv599_initworkitems;
+ uint32_t sv599_maxworkitems;
+ uint32_t sv599_rawworkitems;
+ uint32_t sv599_irpstacksize;
+ uint32_t sv599_maxrawbuflen;
+ uint32_t sv599_sessusers;
+ uint32_t sv599_sessconns;
+ uint32_t sv599_maxpagedmemoryusage;
+ uint32_t sv599_maxnonpagedmemoryusage;
+ uint8_t sv599_enablesoftcompat;
+ uint8_t sv599_enableforcedlogoff;
+ uint8_t sv599_timesource;
+ uint8_t sv599_acceptdownlevelapis;
+ uint8_t sv599_lmannounce;
+ const char * sv599_domain;
+ uint32_t sv599_maxcopyreadlen;
+ uint32_t sv599_maxcopywritelen;
+ uint32_t sv599_minkeepsearch;
+ uint32_t sv599_maxkeepsearch;
+ uint32_t sv599_minkeepcomplsearch;
+ uint32_t sv599_maxkeepcomplsearch;
+ uint32_t sv599_threadcountadd;
+ uint32_t sv599_numblockthreads;
+ uint32_t sv599_scavtimeout;
+ uint32_t sv599_minrcvqueue;
+ uint32_t sv599_minfreeworkitems;
+ uint32_t sv599_xactmemsize;
+ uint32_t sv599_threadpriority;
+ uint32_t sv599_maxmpxct;
+ uint32_t sv599_oplockbreakwait;
+ uint32_t sv599_oplockbreakresponsewait;
+ uint8_t sv599_enableoplocks;
+ uint8_t sv599_enableoplockforceclose;
+ uint8_t sv599_enablefcbopens;
+ uint8_t sv599_enableraw;
+ uint8_t sv599_enablesharednetdrives;
+ uint32_t sv599_minfreeconnections;
+ uint32_t sv599_maxfreeconnections;
+ uint32_t sv599_initsesstable;
+ uint32_t sv599_initconntable;
+ uint32_t sv599_initfiletable;
+ uint32_t sv599_initsearchtable;
+ uint32_t sv599_alertschedule;
+ uint32_t sv599_errorthreshold;
+ uint32_t sv599_networkerrorthreshold;
+ uint32_t sv599_diskspacethreshold;
+ uint32_t sv599_reserved;
+ uint32_t sv599_maxlinkdelay;
+ uint32_t sv599_minlinkthroughput;
+ uint32_t sv599_linkinfovalidtime;
+ uint32_t sv599_scavqosinfoupdatetime;
+ uint32_t sv599_maxworkitemidletime;
+};
+
+struct SERVER_INFO_598 {
+ uint32_t sv598_maxrawworkitems;
+ uint32_t sv598_maxthreadsperqueue;
+ uint32_t sv598_producttype;
+ uint32_t sv598_serversize;
+ uint32_t sv598_connectionlessautodisc;
+ uint32_t sv598_sharingviolationretries;
+ uint32_t sv598_sharingviolationdelay;
+ uint32_t sv598_maxglobalopensearch;
+ uint32_t sv598_removeduplicatesearches;
+ uint32_t sv598_lockviolationoffset;
+ uint32_t sv598_lockviolationdelay;
+ uint32_t sv598_mdlreadswitchover;
+ uint32_t sv598_cachedopenlimit;
+ uint32_t sv598_otherqueueaffinity;
+ uint8_t sv598_restrictnullsessaccess;
+ uint8_t sv598_enablewfw311directipx;
+ uint32_t sv598_queuesamplesecs;
+ uint32_t sv598_balancecount;
+ uint32_t sv598_preferredaffinity;
+ uint32_t sv598_maxfreerfcbs;
+ uint32_t sv598_maxfreemfcbs;
+ uint32_t sv598_maxfreelfcbs;
+ uint32_t sv598_maxfreepagedpoolchunks;
+ uint32_t sv598_minpagedpoolchunksize;
+ uint32_t sv598_maxpagedpoolchunksize;
+ uint8_t sv598_sendsfrompreferredprocessor;
+ uint32_t sv598_cacheddirectorylimit;
+ uint32_t sv598_maxcopylength;
+ uint8_t sv598_enablecompression;
+ uint8_t sv598_autosharewks;
+ uint8_t sv598_autoshareserver;
+ uint8_t sv598_enablesecuritysignature;
+ uint8_t sv598_requiresecuritysignature;
+ uint32_t sv598_minclientbuffersize;
+ struct GUID sv598_serverguid;
+ uint32_t sv598_ConnectionNoSessionsTimeout;
+ uint32_t sv598_IdleThreadTimeOut;
+ uint8_t sv598_enableW9xsecuritysignature;
+ uint8_t sv598_enforcekerberosreauthentication;
+ uint8_t sv598_disabledos;
+ uint32_t sv598_lowdiskspaceminimum;
+ uint8_t sv598_disablestrictnamechecking;
+};
+
+struct SERVER_INFO_1005 {
+ const char * sv1005_comment;
+};
+
+struct SERVER_INFO_1107 {
+ uint32_t sv1107_users;
+};
+
+struct SERVER_INFO_1010 {
+ int32_t sv1010_disc;
+};
+
+struct SERVER_INFO_1016 {
+ uint8_t sv1016_hidden;
+};
+
+struct SERVER_INFO_1017 {
+ uint32_t sv1017_announce;
+};
+
+struct SERVER_INFO_1018 {
+ uint32_t sv1018_anndelta;
+};
+
+struct SERVER_INFO_1501 {
+ uint32_t sv1501_sessopens;
+};
+
+struct SERVER_INFO_1502 {
+ uint32_t sv1502_sessvcs;
+};
+
+struct SERVER_INFO_1503 {
+ uint32_t sv1503_opensearch;
+};
+
+struct SERVER_INFO_1506 {
+ uint32_t sv1506_maxworkitems;
+};
+
+struct SERVER_INFO_1509 {
+ uint32_t sv1509_maxrawbuflen;
+};
+
+struct SERVER_INFO_1510 {
+ uint32_t sv1510_sessusers;
+};
+
+struct SERVER_INFO_1511 {
+ uint32_t sv1511_sessconns;
+};
+
+struct SERVER_INFO_1512 {
+ uint32_t sv1512_maxnonpagedmemoryusage;
+};
+
+struct SERVER_INFO_1513 {
+ uint32_t sv1513_maxpagedmemoryusage;
+};
+
+struct SERVER_INFO_1514 {
+ uint8_t sv1514_enablesoftcompat;
+};
+
+struct SERVER_INFO_1515 {
+ uint8_t sv1515_enableforcedlogoff;
+};
+
+struct SERVER_INFO_1516 {
+ uint8_t sv1516_timesource;
+};
+
+struct SERVER_INFO_1518 {
+ uint8_t sv1518_lmannounce;
+};
+
+struct SERVER_INFO_1520 {
+ uint32_t sv1520_maxcopyreadlen;
+};
+
+struct SERVER_INFO_1521 {
+ uint32_t sv1521_maxcopywritelen;
+};
+
+struct SERVER_INFO_1522 {
+ uint32_t sv1522_minkeepsearch;
+};
+
+struct SERVER_INFO_1523 {
+ uint32_t sv1523_maxkeepsearch;
+};
+
+struct SERVER_INFO_1524 {
+ uint32_t sv1524_minkeepcomplsearch;
+};
+
+struct SERVER_INFO_1525 {
+ uint32_t sv1525_maxkeepcomplsearch;
+};
+
+struct SERVER_INFO_1528 {
+ uint32_t sv1528_scavtimeout;
+};
+
+struct SERVER_INFO_1529 {
+ uint32_t sv1529_minrcvqueue;
+};
+
+struct SERVER_INFO_1530 {
+ uint32_t sv1530_minfreeworkitems;
+};
+
+struct SERVER_INFO_1533 {
+ uint32_t sv1533_maxmpxct;
+};
+
+struct SERVER_INFO_1534 {
+ uint32_t sv1534_oplockbreakwait;
+};
+
+struct SERVER_INFO_1535 {
+ uint32_t sv1535_oplockbreakresponsewait;
+};
+
+struct SERVER_INFO_1536 {
+ uint8_t sv1536_enableoplocks;
+};
+
+struct SERVER_INFO_1537 {
+ uint8_t sv1537_enableoplockforceclose;
+};
+
+struct SERVER_INFO_1538 {
+ uint8_t sv1538_enablefcbopens;
+};
+
+struct SERVER_INFO_1539 {
+ uint8_t sv1539_enableraw;
+};
+
+struct SERVER_INFO_1540 {
+ uint8_t sv1540_enablesharednetdrives;
+};
+
+struct SERVER_INFO_1541 {
+ uint8_t sv1541_minfreeconnections;
+};
+
+struct SERVER_INFO_1542 {
+ uint8_t sv1542_maxfreeconnections;
+};
+
+struct SERVER_INFO_1543 {
+ uint32_t sv1543_initsesstable;
+};
+
+struct SERVER_INFO_1544 {
+ uint32_t sv1544_initconntable;
+};
+
+struct SERVER_INFO_1545 {
+ uint32_t sv1545_initfiletable;
+};
+
+struct SERVER_INFO_1546 {
+ uint32_t sv1546_initsearchtable;
+};
+
+struct SERVER_INFO_1547 {
+ uint32_t sv1547_alertschedule;
+};
+
+struct SERVER_INFO_1548 {
+ uint32_t sv1548_errorthreshold;
+};
+
+struct SERVER_INFO_1549 {
+ uint32_t sv1549_networkerrorthreshold;
+};
+
+struct SERVER_INFO_1550 {
+ uint32_t sv1550_diskspacethreshold;
+};
+
+struct SERVER_INFO_1552 {
+ uint32_t sv1552_maxlinkdelay;
+};
+
+struct SERVER_INFO_1553 {
+ uint32_t sv1553_minlinkthroughput;
+};
+
+struct SERVER_INFO_1554 {
+ uint32_t sv1554_linkinfovalidtime;
+};
+
+struct SERVER_INFO_1555 {
+ uint32_t sv1555_scavqosinfoupdatetime;
+};
+
+struct SERVER_INFO_1556 {
+ uint32_t sv1556_maxworkitemidletime;
+};
+
+struct SERVER_INFO_1557 {
+ uint32_t sv1557_maxrawworkitems;
+};
+
+struct SERVER_INFO_1560 {
+ uint32_t sv1560_producttype;
+};
+
+struct SERVER_INFO_1561 {
+ uint32_t sv1561_serversize;
+};
+
+struct SERVER_INFO_1562 {
+ uint32_t sv1562_connectionlessautodisc;
+};
+
+struct SERVER_INFO_1563 {
+ uint32_t sv1563_sharingviolationretries;
+};
+
+struct SERVER_INFO_1564 {
+ uint32_t sv1564_sharingviolationdelay;
+};
+
+struct SERVER_INFO_1565 {
+ uint32_t sv1565_maxglobalopensearch;
+};
+
+struct SERVER_INFO_1566 {
+ uint8_t sv1566_removeduplicatesearches;
+};
+
+struct SERVER_INFO_1567 {
+ uint32_t sv1567_lockviolationretries;
+};
+
+struct SERVER_INFO_1568 {
+ uint32_t sv1568_lockviolationoffset;
+};
+
+struct SERVER_INFO_1569 {
+ uint32_t sv1569_lockviolationdelay;
+};
+
+struct SERVER_INFO_1570 {
+ uint32_t sv1570_mdlreadswitchover;
+};
+
+struct SERVER_INFO_1571 {
+ uint32_t sv1571_cachedopenlimit;
+};
+
+struct SERVER_INFO_1572 {
+ uint32_t sv1572_criticalthreads;
+};
+
+struct SERVER_INFO_1573 {
+ uint32_t sv1573_restrictnullsessaccess;
+};
+
+struct SERVER_INFO_1574 {
+ uint32_t sv1574_enablewfw311directipx;
+};
+
+struct SERVER_INFO_1575 {
+ uint32_t sv1575_otherqueueaffinity;
+};
+
+struct SERVER_INFO_1576 {
+ uint32_t sv1576_queuesamplesecs;
+};
+
+struct SERVER_INFO_1577 {
+ uint32_t sv1577_balancecount;
+};
+
+struct SERVER_INFO_1578 {
+ uint32_t sv1578_preferredaffinity;
+};
+
+struct SERVER_INFO_1579 {
+ uint32_t sv1579_maxfreerfcbs;
+};
+
+struct SERVER_INFO_1580 {
+ uint32_t sv1580_maxfreemfcbs;
+};
+
+struct SERVER_INFO_1581 {
+ uint32_t sv1581_maxfreemlcbs;
+};
+
+struct SERVER_INFO_1582 {
+ uint32_t sv1582_maxfreepagedpoolchunks;
+};
+
+struct SERVER_INFO_1583 {
+ uint32_t sv1583_minpagedpoolchunksize;
+};
+
+struct SERVER_INFO_1584 {
+ uint32_t sv1584_maxpagedpoolchunksize;
+};
+
+struct SERVER_INFO_1585 {
+ uint8_t sv1585_sendsfrompreferredprocessor;
+};
+
+struct SERVER_INFO_1586 {
+ uint32_t sv1586_maxthreadsperqueue;
+};
+
+struct SERVER_INFO_1587 {
+ uint32_t sv1587_cacheddirectorylimit;
+};
+
+struct SERVER_INFO_1588 {
+ uint32_t sv1588_maxcopylength;
+};
+
+struct SERVER_INFO_1590 {
+ uint32_t sv1590_enablecompression;
+};
+
+struct SERVER_INFO_1591 {
+ uint32_t sv1591_autosharewks;
+};
+
+struct SERVER_INFO_1592 {
+ uint32_t sv1592_autosharewks;
+};
+
+struct SERVER_INFO_1593 {
+ uint32_t sv1593_enablesecuritysignature;
+};
+
+struct SERVER_INFO_1594 {
+ uint32_t sv1594_requiresecuritysignature;
+};
+
+struct SERVER_INFO_1595 {
+ uint32_t sv1595_minclientbuffersize;
+};
+
+struct SERVER_INFO_1596 {
+ uint32_t sv1596_ConnectionNoSessionsTimeout;
+};
+
+struct SERVER_INFO_1597 {
+ uint32_t sv1597_IdleThreadTimeOut;
+};
+
+struct SERVER_INFO_1598 {
+ uint32_t sv1598_enableW9xsecuritysignature;
+};
+
+struct SERVER_INFO_1599 {
+ uint8_t sv1598_enforcekerberosreauthentication;
+};
+
+struct SERVER_INFO_1600 {
+ uint8_t sv1598_disabledos;
+};
+
+struct SERVER_INFO_1601 {
+ uint32_t sv1598_lowdiskspaceminimum;
+};
+
+struct SERVER_INFO_1602 {
+ uint8_t sv_1598_disablestrictnamechecking;
+};
+
+struct USER_INFO_0 {
+ const char * usri0_name;
+};
+
+#define USER_PRIV_GUEST ( 0 )
+#define USER_PRIV_USER ( 1 )
+#define USER_PRIV_ADMIN ( 2 )
+
+struct USER_INFO_1 {
+ const char * usri1_name;
+ const char * usri1_password;
+ uint32_t usri1_password_age;
+ uint32_t usri1_priv;
+ const char * usri1_home_dir;
+ const char * usri1_comment;
+ uint32_t usri1_flags;
+ const char * usri1_script_path;
+};
+
+#define AF_OP_PRINT ( 0x1 )
+#define AF_OP_COMM ( 0x2 )
+#define AF_OP_SERVER ( 0x4 )
+#define AF_OP_ACCOUNTS ( 0x8 )
+
+struct USER_INFO_2 {
+ const char * usri2_name;
+ const char * usri2_password;
+ uint32_t usri2_password_age;
+ uint32_t usri2_priv;
+ const char * usri2_home_dir;
+ const char * usri2_comment;
+ uint32_t usri2_flags;
+ const char * usri2_script_path;
+ uint32_t usri2_auth_flags;
+ const char * usri2_full_name;
+ const char * usri2_usr_comment;
+ const char * usri2_parms;
+ const char * usri2_workstations;
+ uint32_t usri2_last_logon;
+ uint32_t usri2_last_logoff;
+ uint32_t usri2_acct_expires;
+ uint32_t usri2_max_storage;
+ uint32_t usri2_units_per_week;
+ uint8_t *usri2_logon_hours;/* [unique] */
+ uint32_t usri2_bad_pw_count;
+ uint32_t usri2_num_logons;
+ const char * usri2_logon_server;
+ uint32_t usri2_country_code;
+ uint32_t usri2_code_page;
+};
+
+struct USER_INFO_3 {
+ const char * usri3_name;
+ uint32_t usri3_password_age;
+ uint32_t usri3_priv;
+ const char * usri3_home_dir;
+ const char * usri3_comment;
+ uint32_t usri3_flags;
+ const char * usri3_script_path;
+ uint32_t usri3_auth_flags;
+ const char * usri3_full_name;
+ const char * usri3_usr_comment;
+ const char * usri3_parms;
+ const char * usri3_workstations;
+ uint32_t usri3_last_logon;
+ uint32_t usri3_last_logoff;
+ uint32_t usri3_acct_expires;
+ uint32_t usri3_max_storage;
+ uint32_t usri3_units_per_week;
+ uint8_t *usri3_logon_hours;/* [unique] */
+ uint32_t usri3_bad_pw_count;
+ uint32_t usri3_num_logons;
+ const char * usri3_logon_server;
+ uint32_t usri3_country_code;
+ uint32_t usri3_code_page;
+ uint32_t usri3_user_id;
+ uint32_t usri3_primary_group_id;
+ const char * usri3_profile;
+ const char * usri3_home_dir_drive;
+ uint32_t usri3_password_expired;
+};
+
+struct USER_INFO_4 {
+ const char * usri4_name;
+ const char * usri4_password;
+ uint32_t usri4_password_age;
+ uint32_t usri4_priv;
+ const char * usri4_home_dir;
+ const char * usri4_comment;
+ uint32_t usri4_flags;
+ const char * usri4_script_path;
+ uint32_t usri4_auth_flags;
+ const char * usri4_full_name;
+ const char * usri4_usr_comment;
+ const char * usri4_parms;
+ const char * usri4_workstations;
+ uint32_t usri4_last_logon;
+ uint32_t usri4_last_logoff;
+ uint32_t usri4_acct_expires;
+ uint32_t usri4_max_storage;
+ uint32_t usri4_units_per_week;
+ uint8_t *usri4_logon_hours;/* [unique] */
+ uint32_t usri4_bad_pw_count;
+ uint32_t usri4_num_logons;
+ const char * usri4_logon_server;
+ uint32_t usri4_country_code;
+ uint32_t usri4_code_page;
+ struct domsid *usri4_user_sid;/* [unique] */
+ uint32_t usri4_primary_group_id;
+ const char * usri4_profile;
+ const char * usri4_home_dir_drive;
+ uint32_t usri4_password_expired;
+};
+
+struct USER_INFO_10 {
+ const char * usri10_name;
+ const char * usri10_comment;
+ const char * usri10_usr_comment;
+ const char * usri10_full_name;
+};
+
+struct USER_INFO_11 {
+ const char * usri11_name;
+ const char * usri11_comment;
+ const char * usri11_usr_comment;
+ const char * usri11_full_name;
+ uint32_t usri11_priv;
+ uint32_t usri11_auth_flags;
+ uint32_t usri11_password_age;
+ const char * usri11_home_dir;
+ const char * usri11_parms;
+ uint32_t usri11_last_logon;
+ uint32_t usri11_last_logoff;
+ uint32_t usri11_bad_pw_count;
+ uint32_t usri11_num_logons;
+ const char * usri11_logon_server;
+ uint32_t usri11_country_code;
+ const char * usri11_workstations;
+ uint32_t usri11_max_storage;
+ uint32_t usri11_units_per_week;
+ uint8_t *usri11_logon_hours;/* [unique] */
+ uint32_t usri11_code_page;
+};
+
+struct USER_INFO_20 {
+ const char * usri20_name;
+ const char * usri20_full_name;
+ const char * usri20_comment;
+ uint32_t usri20_flags;
+ uint32_t usri20_user_id;
+};
+
+struct USER_INFO_21 {
+ uint8_t *usri21_password;
+};
+
+struct USER_INFO_22 {
+ const char * usri22_name;
+ uint8_t *usri22_password;
+ uint32_t usri22_password_age;
+ uint32_t usri22_priv;
+ const char * usri22_home_dir;
+ const char * usri22_comment;
+ uint32_t usri22_flags;
+ uint32_t usri22_script_path;
+ uint32_t usri22_auth_flags;
+ const char * usri22_full_name;
+ const char * usri22_usr_comment;
+ const char * usri22_parms;
+ const char * usri22_workstations;
+ uint32_t usri22_last_logon;
+ uint32_t usri22_last_logoff;
+ uint32_t usri22_acct_expires;
+ uint32_t usri22_max_storage;
+ uint32_t usri22_units_per_week;
+ uint8_t *usri22_logon_hours;/* [unique] */
+ uint32_t usri22_bad_pw_count;
+ uint32_t usri22_num_logons;
+ const char * usri22_logon_server;
+ uint32_t usri22_country_code;
+ uint32_t usri22_code_page;
+};
+
+struct USER_INFO_23 {
+ const char * usri23_name;
+ const char * usri23_full_name;
+ const char * usri23_comment;
+ uint32_t usri23_flags;
+ struct domsid *usri23_user_sid;/* [unique] */
+};
+
+struct USER_INFO_1003 {
+ const char * usri1003_password;
+};
+
+struct USER_INFO_1005 {
+ uint32_t usri1005_priv;
+};
+
+struct USER_INFO_1006 {
+ const char * usri1006_home_dir;
+};
+
+struct USER_INFO_1007 {
+ const char * usri1007_comment;
+};
+
+struct USER_INFO_1008 {
+ uint32_t usri1008_flags;
+};
+
+struct USER_INFO_1009 {
+ const char * usri1009_script_path;
+};
+
+struct USER_INFO_1010 {
+ uint32_t usri1010_auth_flags;
+};
+
+struct USER_INFO_1011 {
+ const char * usri1011_full_name;
+};
+
+struct USER_INFO_1012 {
+ const char * usri1012_usr_comment;
+};
+
+struct USER_INFO_1013 {
+ const char * usri1013_parms;
+};
+
+struct USER_INFO_1014 {
+ const char * usri1014_workstations;
+};
+
+struct USER_INFO_1017 {
+ uint32_t usri1017_acct_expires;
+};
+
+struct USER_INFO_1018 {
+ uint32_t usri1018_max_storage;
+};
+
+struct USER_INFO_1020 {
+ uint32_t usri1020_units_per_week;
+ uint8_t *usri1020_logon_hours;/* [unique] */
+};
+
+struct USER_INFO_1023 {
+ const char * usri1023_logon_server;
+};
+
+struct USER_INFO_1024 {
+ uint32_t usri1024_country_code;
+};
+
+struct USER_INFO_1025 {
+ uint32_t usri1025_code_page;
+};
+
+struct USER_INFO_1051 {
+ uint32_t usri1051_primary_group_id;
+};
+
+struct USER_INFO_1052 {
+ const char * usri1052_profile;
+};
+
+struct USER_INFO_1053 {
+ const char * usri1053_home_dir_drive;
+};
+
+struct USER_MODALS_INFO_0 {
+ uint32_t usrmod0_min_passwd_len;
+ uint32_t usrmod0_max_passwd_age;
+ uint32_t usrmod0_min_passwd_age;
+ uint32_t usrmod0_force_logoff;
+ uint32_t usrmod0_password_hist_len;
+};
+
+struct USER_MODALS_INFO_1 {
+ uint32_t usrmod1_role;
+ const char * usrmod1_primary;
+};
+
+struct USER_MODALS_INFO_2 {
+ const char * usrmod2_domain_name;
+ struct domsid *usrmod2_domain_id;/* [unique] */
+};
+
+struct USER_MODALS_INFO_3 {
+ uint32_t usrmod3_lockout_duration;
+ uint32_t usrmod3_lockout_observation_window;
+ uint32_t usrmod3_lockout_threshold;
+};
+
+struct USER_MODALS_INFO_1001 {
+ uint32_t usrmod1001_min_passwd_len;
+};
+
+struct USER_MODALS_INFO_1002 {
+ uint32_t usrmod1002_max_passwd_age;
+};
+
+struct USER_MODALS_INFO_1003 {
+ uint32_t usrmod1003_min_passwd_age;
+};
+
+struct USER_MODALS_INFO_1004 {
+ uint32_t usrmod1004_force_logoff;
+};
+
+struct USER_MODALS_INFO_1005 {
+ uint32_t usrmod1005_password_hist_len;
+};
+
+struct USER_MODALS_INFO_1006 {
+ uint32_t usrmod1006_role;
+};
+
+struct USER_MODALS_INFO_1007 {
+ const char * usrmod1007_primary;
+};
+
+struct NET_DISPLAY_USER {
+ const char * usri1_name;
+ const char * usri1_comment;
+ uint32_t usri1_flags;
+ const char * usri1_full_name;
+ uint32_t usri1_user_id;
+ uint32_t usri1_next_index;
+};
+
+struct NET_DISPLAY_MACHINE {
+ const char * usri2_name;
+ const char * usri2_comment;
+ uint32_t usri2_flags;
+ uint32_t usri2_user_id;
+ uint32_t usri2_next_index;
+};
+
+struct NET_DISPLAY_GROUP {
+ const char * grpi3_name;
+ const char * grpi3_comment;
+ uint32_t grpi3_group_id;
+ uint32_t grpi3_attributes;
+ uint32_t grpi3_next_index;
+};
+
+struct GROUP_INFO_0 {
+ const char * grpi0_name;
+};
+
+struct GROUP_INFO_1 {
+ const char * grpi1_name;
+ const char * grpi1_comment;
+};
+
+struct GROUP_INFO_2 {
+ const char * grpi2_name;
+ const char * grpi2_comment;
+ uint32_t grpi2_group_id;
+ uint32_t grpi2_attributes;
+};
+
+struct GROUP_INFO_3 {
+ const char * grpi3_name;
+ const char * grpi3_comment;
+ struct domsid * grpi3_group_sid;
+ uint32_t grpi3_attributes;
+};
+
+struct GROUP_INFO_1002 {
+ const char * grpi1002_comment;
+};
+
+struct GROUP_INFO_1005 {
+ uint32_t grpi1005_attributes;
+};
+
+struct GROUP_USERS_INFO_0 {
+ const char * grui0_name;
+};
+
+struct GROUP_USERS_INFO_1 {
+ const char * grui1_name;
+ uint32_t grui1_attributes;
+};
+
+struct LOCALGROUP_INFO_0 {
+ const char * lgrpi0_name;
+};
+
+struct LOCALGROUP_INFO_1 {
+ const char * lgrpi1_name;
+ const char * lgrpi1_comment;
+};
+
+struct LOCALGROUP_INFO_1002 {
+ const char * lgrpi1002_comment;
+};
+
+enum SID_NAME_USE {
+ SidTypeUser=1,
+ SidTypeGroup=2,
+ SidTypeDomain=3,
+ SidTypeAlias=4,
+ SidTypeWellKnownGroup=5,
+ SidTypeDeletedAccount=6,
+ SidTypeInvalid=7,
+ SidTypeUnknown=8,
+ SidTypeComputer=9,
+ SidTypeLabel=10
+};
+
+struct LOCALGROUP_MEMBERS_INFO_0 {
+ struct domsid *lgrmi0_sid;/* [unique] */
+};
+
+struct LOCALGROUP_MEMBERS_INFO_1 {
+ struct domsid *lgrmi1_sid;/* [unique] */
+ enum SID_NAME_USE lgrmi1_sidusage;
+ const char * lgrmi1_name;
+};
+
+struct LOCALGROUP_MEMBERS_INFO_2 {
+ struct domsid *lgrmi2_sid;/* [unique] */
+ enum SID_NAME_USE lgrmi2_sidusage;
+ const char * lgrmi2_domainandname;
+};
+
+struct LOCALGROUP_MEMBERS_INFO_3 {
+ const char * lgrmi3_domainandname;
+};
+
+struct LOCALGROUP_USERS_INFO_0 {
+ const char * lgrui0_name;
+};
+
+struct TIME_OF_DAY_INFO {
+ uint32_t tod_elapsedt;
+ uint32_t tod_msecs;
+ uint32_t tod_hours;
+ uint32_t tod_mins;
+ uint32_t tod_secs;
+ uint32_t tod_hunds;
+ int32_t tod_timezone;
+ uint32_t tod_tinterval;
+ uint32_t tod_day;
+ uint32_t tod_month;
+ uint32_t tod_year;
+ uint32_t tod_weekday;
+};
+
+struct SHARE_INFO_0 {
+ const char * shi0_netname;
+};
+
+struct SHARE_INFO_1 {
+ const char * shi1_netname;
+ uint32_t shi1_type;
+ const char * shi1_remark;
+};
+
+struct SHARE_INFO_2 {
+ const char * shi2_netname;
+ uint32_t shi2_type;
+ const char * shi2_remark;
+ uint32_t shi2_permissions;
+ uint32_t shi2_max_uses;
+ uint32_t shi2_current_uses;
+ const char * shi2_path;
+ const char * shi2_passwd;
+};
+
+struct SHARE_INFO_501 {
+ const char * shi501_netname;
+ uint32_t shi501_type;
+ const char * shi501_remark;
+ uint32_t shi501_flags;
+};
+
+struct SHARE_INFO_502 {
+ const char * shi502_netname;
+ uint32_t shi502_type;
+ const char * shi502_remark;
+ uint32_t shi502_permissions;
+ uint32_t shi502_max_uses;
+ uint32_t shi502_current_uses;
+ const char * shi502_path;
+ const char * shi502_passwd;
+ uint32_t shi502_reserved;
+ struct security_descriptor * shi502_security_descriptor;
+};
+
+struct SHARE_INFO_1004 {
+ const char * shi1004_remark;
+};
+
+struct SHARE_INFO_1005 {
+ uint32_t shi1005_flags;
+};
+
+struct SHARE_INFO_1006 {
+ uint32_t shi1006_max_uses;
+};
+
+struct FILE_INFO_2 {
+ uint32_t fi2_id;
+};
+
+struct FILE_INFO_3 {
+ uint32_t fi3_id;
+ uint32_t fi3_permissions;
+ uint32_t fi3_num_locks;
+ const char * fi3_pathname;
+ const char * fi3_username;
+};
+
+struct NETLOGON_INFO_1 {
+ uint32_t netlog1_flags;
+ NET_API_STATUS netlog1_pdc_connection_status;
+};
+
+struct NETLOGON_INFO_2 {
+ uint32_t netlog2_flags;
+ NET_API_STATUS netlog2_pdc_connection_status;
+ const char * netlog2_trusted_dc_name;
+ NET_API_STATUS netlog2_tc_connection_status;
+};
+
+struct NETLOGON_INFO_3 {
+ uint32_t netlog1_flags;
+ uint32_t netlog3_logon_attempts;
+ uint32_t netlog3_reserved1;
+ uint32_t netlog3_reserved2;
+ uint32_t netlog3_reserved3;
+ uint32_t netlog3_reserved4;
+ uint32_t netlog3_reserved5;
+};
+
+struct NETLOGON_INFO_4 {
+ const char * netlog4_trusted_dc_name;
+ const char * netlog4_trusted_domain_name;
+};
+
+#define DS_PDC_FLAG ( 0x00000001 )
+#define DS_GC_FLAG ( 0x00000004 )
+#define DS_LDAP_FLAG ( 0x00000008 )
+#define DS_DS_FLAG ( 0x00000010 )
+#define DS_KDC_FLAG ( 0x00000020 )
+#define DS_TIMESERV_FLAG ( 0x00000040 )
+#define DS_CLOSEST_FLAG ( 0x00000080 )
+#define DS_WRITABLE_FLAG ( 0x00000100 )
+#define DS_GOOD_TIMESERV_FLAG ( 0x00000200 )
+#define DS_NDNC_FLAG ( 0x00000400 )
+#define DS_SELECT_SECRET_DOMAIN_6_FLAG ( 0x00000800 )
+#define DS_FULL_SECRET_DOMAIN_6_FLAG ( 0x00001000 )
+#define DS_WS_FLAG ( 0x00002000 )
+#define DS_DS_8_FLAG ( 0x00004000 )
+#define DS_DNS_CONTROLLER_FLAG ( 0x20000000 )
+#define DS_DNS_DOMAIN_FLAG ( 0x40000000 )
+#define DS_DNS_FOREST_FLAG ( 0x80000000 )
+
+#endif /* _HEADER_libnetapi */
+
+#ifndef _HEADER_netlogon
+
+#define NETLOGON_CONTROL_QUERY ( 0x00000001 )
+#define NETLOGON_CONTROL_REPLICATE ( 0x00000002 )
+#define NETLOGON_CONTROL_SYNCHRONIZE ( 0x00000003 )
+#define NETLOGON_CONTROL_PDC_REPLICATE ( 0x00000004 )
+#define NETLOGON_CONTROL_REDISCOVER ( 0x00000005 )
+#define NETLOGON_CONTROL_TC_QUERY ( 0x00000006 )
+#define NETLOGON_CONTROL_TRANSPORT_NOTIFY ( 0x00000007 )
+#define NETLOGON_CONTROL_FIND_USER ( 0x00000008 )
+#define NETLOGON_CONTROL_CHANGE_PASSWORD ( 0x00000009 )
+#define NETLOGON_CONTROL_TC_VERIFY ( 0x0000000A )
+#define NETLOGON_CONTROL_FORCE_DNS_REG ( 0x0000000B )
+#define NETLOGON_CONTROL_QUERY_DNS_REG ( 0x0000000C )
+#define NETLOGON_CONTROL_BACKUP_CHANGE_LOG ( 0x0000FFFC )
+#define NETLOGON_CONTROL_TRUNCATE_LOG ( 0x0000FFFD )
+#define NETLOGON_CONTROL_SET_DBFLAG ( 0x0000FFFE )
+#define NETLOGON_CONTROL_BREAKPOINT ( 0x0000FFFF )
+
+#define DS_FORCE_REDISCOVERY ( 0x00000001 )
+#define DS_DIRECTORY_SERVICE_REQUIRED ( 0x00000010 )
+#define DS_DIRECTORY_SERVICE_PREFERRED ( 0x00000020 )
+#define DS_GC_SERVER_REQUIRED ( 0x00000040 )
+#define DS_PDC_REQUIRED ( 0x00000080 )
+#define DS_BACKGROUND_ONLY ( 0x00000100 )
+#define DS_IP_REQUIRED ( 0x00000200 )
+#define DS_KDC_REQUIRED ( 0x00000400 )
+#define DS_TIMESERV_REQUIRED ( 0x00000800 )
+#define DS_WRITABLE_REQUIRED ( 0x00001000 )
+#define DS_GOOD_TIMESERV_PREFERRED ( 0x00002000 )
+#define DS_AVOID_SELF ( 0x00004000 )
+#define DS_ONLY_LDAP_NEEDED ( 0x00008000 )
+#define DS_IS_FLAT_NAME ( 0x00010000 )
+#define DS_IS_DNS_NAME ( 0x00020000 )
+#define DS_TRY_NEXTCLOSEST_SITE ( 0x00040000 )
+#define DS_DIRECTORY_SERVICE_6_REQUIRED ( 0x00080000 )
+#define DS_WEB_SERVICE_REQUIRED ( 0x00100000 )
+#define DS_RETURN_DNS_NAME ( 0x40000000 )
+#define DS_RETURN_FLAT_NAME ( 0x80000000 )
+
+#endif /* _HEADER_netlogon */
+
+/****************************************************************
+****************************************************************/
+
+struct libnetapi_ctx;
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_init(struct libnetapi_ctx **ctx);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_free(struct libnetapi_ctx *ctx);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_getctx(struct libnetapi_ctx **ctx);
+
+NET_API_STATUS libnetapi_get_username(struct libnetapi_ctx *ctx,
+ const char **username);
+
+NET_API_STATUS libnetapi_get_password(struct libnetapi_ctx *ctx,
+ const char **password);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_debuglevel(struct libnetapi_ctx *ctx,
+ const char *debuglevel);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_creds(struct libnetapi_ctx *ctx,
+ struct cli_credentials *creds);
+
+
+NET_API_STATUS libnetapi_set_username(struct libnetapi_ctx *ctx,
+ const char *username);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_password(struct libnetapi_ctx *ctx,
+ const char *password);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_workgroup(struct libnetapi_ctx *ctx,
+ const char *workgroup);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_use_kerberos(struct libnetapi_ctx *ctx);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_get_use_kerberos(struct libnetapi_ctx *ctx,
+ int *use_kerberos);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_logfile(struct libnetapi_ctx *ctx,
+ const char *logfile);
+
+/****************************************************************
+****************************************************************/
+
+NET_API_STATUS libnetapi_set_use_ccache(struct libnetapi_ctx *ctx);
+
+/****************************************************************
+Return a specific libnetapi error as a string, caller must free with NetApiBufferFree
+****************************************************************/
+
+char *libnetapi_errstr(NET_API_STATUS status);
+
+/****************************************************************
+Return the last libnetapi error as a string, caller must free with NetApiBufferFree
+ctx is optional
+****************************************************************/
+
+char *libnetapi_get_error_string(struct libnetapi_ctx *ctx,
+ NET_API_STATUS status);
+
+/****************************************************************
+ NetApiBufferAllocate
+****************************************************************/
+
+NET_API_STATUS NetApiBufferAllocate(uint32_t byte_count,
+ void **buffer);
+
+/****************************************************************
+ NetApiBufferFree
+****************************************************************/
+
+NET_API_STATUS NetApiBufferFree(void *buffer);
+
+/************************************************************//**
+ *
+ * ConvertSidToStringSid
+ *
+ * @brief Convert a domain sid into a string
+ *
+ * @param[in] sid A pointer to a sid structure
+ * @param[in,out] sid_string A pointer that holds a pointer to a sid string. Caller
+ * needs to free with free(3)
+ * @return bool
+ ***************************************************************/
+
+int ConvertSidToStringSid(const struct domsid *sid,
+ char **sid_string);
+
+/************************************************************//**
+ *
+ * ConvertStringSidToSid
+ *
+ * @brief Convert a string into a domain sid
+ *
+ * @param[in] sid_string A pointer to a sid string.
+ * @param[in,out] sid A pointer that holds a pointer to a sid structure.
+ * Caller needs to free with free(3)
+ * @return bool
+ ***************************************************************/
+
+int ConvertStringSidToSid(const char *sid_string,
+ struct domsid **sid);
+
+/************************************************************//**
+ *
+ * NetJoinDomain
+ *
+ * @brief Join a computer to a domain or workgroup
+ *
+ * @param[in] server The server name to connect to
+ * @param[in] domain The domain or workgroup to join
+ * @param[in] account_ou The organizational Unit to create the computer account
+ * in (AD only)
+ * @param[in] account The domain account used for joining a domain
+ * @param[in] password The domain account's password used for joining a domain
+ * @param[in] join_flags Bitmask field to define specific join features
+ * @return NET_API_STATUS
+ *
+ * example netdomjoin/netdomjoin.c
+ ***************************************************************/
+
+NET_API_STATUS NetJoinDomain(const char * server /* [in] */,
+ const char * domain /* [in] [ref] */,
+ const char * account_ou /* [in] */,
+ const char * account /* [in] */,
+ const char * password /* [in] */,
+ uint32_t join_flags /* [in] */);
+
+/************************************************************//**
+ *
+ * NetUnjoinDomain
+ *
+ * @brief Unjoin a computer from a domain or workgroup
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] account The domain account used for unjoining a domain
+ * @param[in] password The domain account's password used for unjoining a domain
+ * @param[in] unjoin_flags Bitmask field to define specific unjoin features
+ * @return NET_API_STATUS
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetUnjoinDomain(const char * server_name /* [in] */,
+ const char * account /* [in] */,
+ const char * password /* [in] */,
+ uint32_t unjoin_flags /* [in] */);
+
+/************************************************************//**
+ *
+ * NetGetJoinInformation
+ *
+ * @brief Unjoin a computer from a domain or workgroup
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[out] name_buffer Returns the name of the workgroup or domain
+ * @param[out] name_type Returns the type of that name
+ * @return NET_API_STATUS
+ *
+ * example netdomjoin-gui/netdomjoin-gui.c
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetGetJoinInformation(const char * server_name /* [in] */,
+ const char * *name_buffer /* [out] [ref] */,
+ uint16_t *name_type /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGetJoinableOUs
+ *
+ * @brief Query for the list of joinable organizational Units that can be used
+ * for joining AD
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] domain The AD domain to query
+ * @param[in] account The domain account used for the query
+ * @param[in] password The domain account's password used for the query
+ * @param[out] ou_count The number of ous returned
+ * @param[out] ous Returned string array containing the ous
+ * @return NET_API_STATUS
+ *
+ * example netdomjoin-gui/netdomjoin-gui.c
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetGetJoinableOUs(const char * server_name /* [in] */,
+ const char * domain /* [in] [ref] */,
+ const char * account /* [in] */,
+ const char * password /* [in] */,
+ uint32_t *ou_count /* [out] [ref] */,
+ const char * **ous /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetRenameMachineInDomain
+ *
+ * @brief Rename a machine in a domain
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] new_machine_name The new machine name
+ * @param[in] account The domain account used for the query
+ * @param[in] password The domain account's password used for the query
+ * @param[in] rename_options Options used for the rename operation
+ * @return NET_API_STATUS
+ *
+ * example join/rename_machine.c
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetRenameMachineInDomain(const char * server_name /* [in] */,
+ const char * new_machine_name /* [in] */,
+ const char * account /* [in] */,
+ const char * password /* [in] */,
+ uint32_t rename_options /* [in] */);
+
+/************************************************************//**
+ *
+ * NetProvisionComputerAccount
+ *
+ * @brief Provision a machine for offline join
+ *
+ * @param[in] domain The domain to provision for
+ * @param[in] machine_name The machine account name
+ * @param[in] machine_account_ou The machine account ou to create the account in
+ * @param[in] dcname A specific domain controller to use for account creation
+ * @param[in] options The options used for account creation
+ * @param[in,out] provision_bin_data The generated binary buffer
+ * @param[in,out] provision_bin_data_size The generated binary buffer size
+ * @param[in,out] provision_text_data The generated text data blob
+ * @return NET_API_STATUS
+ *
+ * example join/provision_computer_account.c
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetProvisionComputerAccount(const char * domain /* [in] [ref] */,
+ const char * machine_name /* [in] [ref] */,
+ const char * machine_account_ou /* [in] [unique] */,
+ const char * dcname /* [in] [unique] */,
+ uint32_t options /* [in] */,
+ uint8_t **provision_bin_data /* [in,out] [unique] */,
+ uint32_t *provision_bin_data_size /* [in,out] [unique] */,
+ const char * *provision_text_data /* [in,out] [unique] */);
+
+/************************************************************//**
+ *
+ * NetRequestOfflineDomainJoin
+ *
+ * @brief Request an offline domain join
+ *
+ * @param[in] provision_bin_data The provided binary buffer
+ * @param[in] provision_bin_data_size The provided binary buffer size
+ * @param[in] options The options used for account creation
+ * @param[in] windows_path The path for the joined image
+ * @return NET_API_STATUS
+ *
+ * example join/request_offline_domain_join.c
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetRequestOfflineDomainJoin(uint8_t *provision_bin_data /* [in] [unique] */,
+ uint32_t provision_bin_data_size /* [in] */,
+ uint32_t options /* [in] */,
+ const char * windows_path /* [in] [unique] */);
+
+/************************************************************//**
+ *
+ * NetComposeOfflineDomainJoin
+ *
+ * @brief Compose an offline domain join blob
+ *
+ * Intended to be used by external applications who provision the computer
+ * acconut on their own.
+ *
+ *
+ * @param[in] dns_domain_name The domain DNS name
+ * @param[in] netbios_domain_name The domain NETBIOS name
+ * @param[in] domain_sid The domain SID
+ * @param[in] domain_guid The domain GUID
+ * @param[in] forest_name The forest name
+ * @param[in] machine_account_name The machine account name
+ * @param[in] machine_account_password The machine account password
+ * @param[in] dc_name The domain controller name used to provision the account
+ * @param[in] dc_address The domain controller address used to provision the account
+ * @param[in] domain_is_ad True if the domain is AD
+ * @param[in,out] compose_bin_data The generated binary buffer
+ * @param[in,out] compose_bin_data_size The generated binary buffer size
+ * @param[in,out] compose_text_data The generated text data blob
+ * @return NET_API_STATUS
+ *
+ * example join/compose_offline_domain_join.c
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetComposeOfflineDomainJoin(const char *dns_domain_name /* [in] [ref] */,
+ const char *netbios_domain_name /* [in] [ref] */,
+ struct domsid *domain_sid /* [in] [ref] */,
+ struct GUID *domain_guid /* [in] [ref] */,
+ const char *forest_name /* [in] [ref] */,
+ const char *machine_account_name /* [in] [ref] */,
+ const char *machine_account_password /* [in] [ref] */,
+ const char *dc_name /* [in] [unique] */,
+ const char *dc_address /* [in] [unique] */,
+ int domain_is_ad /* [in] */,
+ uint8_t **provision_bin_data /* [in,out] [unique] */,
+ uint32_t *provision_bin_data_size /* [in,out] [unique] */,
+ const char * *provision_text_data /* [in,out] [unique] */);
+
+/************************************************************//**
+ *
+ * NetServerGetInfo
+ *
+ * @brief Get Information on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level to define which information is requested
+ * @param[out] buffer The returned buffer carrying the SERVER_INFO structure
+ * @return NET_API_STATUS
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetServerGetInfo(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetServerSetInfo
+ *
+ * @brief Get Information on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level to define which information is set
+ * @param[in] buffer The buffer carrying the SERVER_INFO structure
+ * @param[out] parm_error On failure returns the invalid SERVER_INFO member
+ * @return NET_API_STATUS
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetServerSetInfo(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_error /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetWkstaGetInfo
+ *
+ * @brief Get Information on a workstation
+ *
+ * @param[in] wksta_name The workstation name to connect to
+ * @param[in] level The level to define which information is requested
+ * @param[out] buffer The returned buffer carrying the WKSTA_INFO structure
+ * @return NET_API_STATUS
+ *
+ ***************************************************************/
+
+NET_API_STATUS NetWkstaGetInfo(const char * wksta_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGetDCName
+ *
+ * @brief Query for the PDC for a given domain
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] domain_name The name of the domain to lookup
+ * @param[out] buffer The name of the domain to lookup
+ * @return NET_API_STATUS
+ *
+ * example getdc/getdc.c
+ ***************************************************************/
+
+NET_API_STATUS NetGetDCName(const char * server_name /* [in] */,
+ const char * domain_name /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGetAnyDCName
+ *
+ * @brief Query for any DC for a given domain
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] domain_name The name of the domain to lookup
+ * @param[out] buffer The name of the domain to lookup
+ * @return NET_API_STATUS
+ *
+ * example getdc/getdc.c
+ ***************************************************************/
+
+NET_API_STATUS NetGetAnyDCName(const char * server_name /* [in] */,
+ const char * domain_name /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+
+/************************************************************//**
+ *
+ * DsGetDcName
+ *
+ * @brief Lookup a DC for a given domain and return information structure
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] domain_name The name of the domain to lookup (cannot be NULL)
+ * @param[in] domain_guid The GUID of the domain to lookup (optional)
+ * @param[in] site_name The name of the site the DC should reside in
+ * @param[in] flags A bitmask to request specific features supported by the DC
+ * @param[out] dc_info Pointer to a DOMAIN_CONTROLLER_INFO structure
+ * @return NET_API_STATUS
+ *
+ * example dsgetdc/dsgetdc.c
+ ***************************************************************/
+
+NET_API_STATUS DsGetDcName(const char * server_name /* [in] [unique] */,
+ const char * domain_name /* [in] [ref] */,
+ struct GUID *domain_guid /* [in] [unique] */,
+ const char * site_name /* [in] [unique] */,
+ uint32_t flags /* [in] */,
+ struct DOMAIN_CONTROLLER_INFO **dc_info /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserAdd
+ *
+ * @brief Create a user on a given server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level of the USER_INFO structure passed in (Currently
+ * only level 1 is supported)
+ * @param[in] buffer The buffer carrying the USER_INFO structure
+ * @param[out] parm_error In case of error returns the failing member of the
+ * structure
+ * @return NET_API_STATUS
+ *
+ * example user/user_add.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_error /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserDel
+ *
+ * @brief Delete a user on a given server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] user_name The user account to delete
+ * @return NET_API_STATUS
+ *
+ * example user/user_del.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserDel(const char * server_name /* [in] */,
+ const char * user_name /* [in] */);
+
+/************************************************************//**
+ *
+ * NetUserEnum
+ *
+ * @brief Enumerate accounts on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The enumeration level used for the query (Currently only
+ * level 0 is supported)
+ * @param[in] filter The account flags filter used for the query
+ * @param[out] buffer The returned enumeration buffer
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of returned entries
+ * @param[out] total_entries The number of total entries
+ * @param[in,out] resume_handle A handle passed in and returned for resuming
+ * operations
+ * @return NET_API_STATUS
+ *
+ * example user/user_enum.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint32_t filter /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserChangePassword
+ *
+ * @brief Change the password for a user on a given server or in a given domain
+ *
+ * @param[in] domain_name The server or domain name to connect to
+ * @param[in] user_name The user account to change the password for
+ * @param[in] old_password The user account's old password
+ * @param[in] new_password The user account's new password
+ * @return NET_API_STATUS
+ *
+ * example user/user_chgpwd.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserChangePassword(const char * domain_name /* [in] */,
+ const char * user_name /* [in] */,
+ const char * old_password /* [in] */,
+ const char * new_password /* [in] */);
+
+/************************************************************//**
+ *
+ * NetUserGetInfo
+ *
+ * @brief Get User Information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] user_name The name of the user that is going to be queried
+ * @param[in] level The level defining the requested USER_INFO_X structure
+ * @param[out] buffer The buffer containing a USER_INFO_X structure
+ * @return NET_API_STATUS
+ *
+ * example user/user_getinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserGetInfo(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserSetInfo
+ *
+ * @brief Set User Information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] user_name The name of the user that is going to be modified
+ * @param[in] level The level defining the requested USER_INFO_X structure
+ * @param[in] buffer The buffer containing a USER_INFO_X structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example user/user_setinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserSetInfo(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserModalsGet
+ *
+ * @brief Get SAM domain and password information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level defining which USER_MODALS_INFO_X buffer to query
+ * @param[out] buffer The returned USER_MODALS_INFO_X buffer
+ * @return NET_API_STATUS
+ *
+ * example user/user_modalsget.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserModalsGet(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserModalsSet
+ *
+ * @brief Set SAM domain and password information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level defining which USER_MODALS_INFO_X buffer to query
+ * @param[out] buffer The buffer conntaing a USER_MODALS_INFO_X structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example user/user_modalsset.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserModalsSet(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserGetGroups
+ *
+ * @brief Enumerate grouplist of a user on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] user_name The user name to query
+ * @param[in] level The enumeration level used for the query (Currently only
+ * level 0 is supported)
+ * @param[out] buffer The returned enumeration buffer
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of returned entries
+ * @param[out] total_entries The number of total entries
+ * @return NET_API_STATUS
+ *
+ * example user/user_getgroups.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserGetGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetUserSetGroups
+ *
+ * @brief Set grouplist of a user on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] user_name The user name to query
+ * @param[in] level The level defining the GROUP_USERS_INFO_X structures in the buffer
+ * @param[in] buffer The buffer containing GROUP_USERS_INFO_X structures
+ * @param[in] num_entries The number of X structures in the buffer
+ * @return NET_API_STATUS
+ *
+ * example user/user_setgroups.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserSetGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t num_entries /* [in] */);
+
+/************************************************************//**
+ *
+ * NetUserGetLocalGroups
+ *
+ * @brief Enumerate local grouplist of a user on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] user_name The user name to query
+ * @param[in] level The enumeration level used for the query
+ * @param[in] flags The flags used for the query
+ * @param[out] buffer The returned enumeration buffer
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of returned entries
+ * @param[out] total_entries The number of total entries
+ * @return NET_API_STATUS
+ *
+ * example user/user_getlocalgroups.c
+ ***************************************************************/
+
+NET_API_STATUS NetUserGetLocalGroups(const char * server_name /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint32_t flags /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetQueryDisplayInformation
+ *
+ * @brief Enumerate accounts on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The enumeration level used for the query
+ * @param[in] idx The index to start the the display enumeration at
+ * @param[in] entries_requested The number of entries requested
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of returned entries
+ * @param[out] buffer The returned display information buffer
+ * @return NET_API_STATUS
+ *
+ * example user/user_dispinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetQueryDisplayInformation(const char * server_name /* [in] [unique] */,
+ uint32_t level /* [in] */,
+ uint32_t idx /* [in] */,
+ uint32_t entries_requested /* [in] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ void **buffer /* [out] [noprint,ref] */);
+
+/************************************************************//**
+ *
+ * NetGroupAdd
+ *
+ * @brief Create Domain Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level used for the new group creation
+ * @param[in] buf The buffer containing the group structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example group/group_add.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buf /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGroupDel
+ *
+ * @brief Delete Domain Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be deleted
+ * @return NET_API_STATUS
+ *
+ * example group/group_del.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupDel(const char * server_name /* [in] */,
+ const char * group_name /* [in] */);
+
+/************************************************************//**
+ *
+ * NetGroupEnum
+ *
+ * @brief Enumerate groups on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The enumeration level used for the query (Currently only
+ * level 0 is supported)
+ * @param[out] buffer The returned enumeration buffer
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of returned entries
+ * @param[out] total_entries The number of total entries
+ * @param[in,out] resume_handle A handle passed in and returned for resuming
+ * operations
+ * @return NET_API_STATUS
+ *
+ * example group/group_enum.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGroupSetInfo
+ *
+ * @brief Set Domain Group Information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be modified
+ * @param[in] level The level defining the structure type in buf
+ * @param[in] buf The buffer containing a GROUP_INFO_X structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example group/group_setinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupSetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buf /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGroupGetInfo
+ *
+ * @brief Get Domain Group Information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be queried
+ * @param[in] level The level defining the requested GROUP_INFO_X structure
+ * @param[out] buf The buffer containing a GROUP_INFO_X structure
+ * @return NET_API_STATUS
+ *
+ * example group/group_getinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupGetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buf /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGroupAddUser
+ *
+ * @brief Add existing User to existing Domain Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be modified
+ * @param[in] user_name The name of the user that is going to be added to the
+ * group
+ * @return NET_API_STATUS
+ *
+ * example group/group_adduser.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupAddUser(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ const char * user_name /* [in] */);
+
+/************************************************************//**
+ *
+ * NetGroupDelUser
+ *
+ * @brief Remove User from Domain Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be modified
+ * @param[in] user_name The name of the user that is going to be removed from
+ * the group
+ * @return NET_API_STATUS
+ *
+ * example group/group_deluser.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupDelUser(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ const char * user_name /* [in] */);
+
+/************************************************************//**
+ *
+ * NetGroupGetUsers
+ *
+ * @brief Get Users for a group on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The group name to enumerate users for
+ * @param[in] level The enumeration level used for the query
+ * @param[out] buffer The returned enumeration buffer
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of returned entries
+ * @param[out] total_entries The number of total entries
+ * @param[in,out] resume_handle A handle passed in and returned for resuming
+ * operations
+ * @return NET_API_STATUS
+ *
+ * example group/group_getusers.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupGetUsers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetGroupSetUsers
+ *
+ * @brief Set Users for a group on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The group name to enumerate users for
+ * @param[in] level The enumeration level used for the query
+ * @param[in] buffer The buffer containing a X structure
+ * @param[in] num_entries The number of X entries in the buffer
+ * @return NET_API_STATUS
+ *
+ * example group/group_setusers.c
+ ***************************************************************/
+
+NET_API_STATUS NetGroupSetUsers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t num_entries /* [in] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupAdd
+ *
+ * @brief Create Local Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level used for the new group creation
+ * @param[in] buf The buffer containing the group structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_add.c
+ ***************************************************************/
+
+NET_API_STATUS NetLocalGroupAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buf /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupDel
+ *
+ * @brief Delete Local Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be deleted
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_del.c
+ ***************************************************************/
+
+
+NET_API_STATUS NetLocalGroupDel(const char * server_name /* [in] */,
+ const char * group_name /* [in] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupGetInfo
+ *
+ * @brief Get Local Group Information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be queried
+ * @param[in] level The level defining the requested LOCALGROUP_INFO_X structure
+ * @param[out] buf The buffer containing a LOCALGROUP_INFO_X structure
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_getinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetLocalGroupGetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buf /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupSetInfo
+ *
+ * @brief Set Local Group Information
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to be modified
+ * @param[in] level The level defining the requested LOCALGROUP_INFO_X structure
+ * @param[in] buf The buffer containing a LOCALGROUP_INFO_X structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_setinfo.c
+ ***************************************************************/
+
+
+NET_API_STATUS NetLocalGroupSetInfo(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buf /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupEnum
+ *
+ * @brief Enumerate local groups on a server
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The enumeration level used for the query (Currently only
+ * level 0 is supported)
+ * @param[out] buffer The returned enumeration buffer
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of returned entries
+ * @param[out] total_entries The number of total entries
+ * @param[in,out] resume_handle A handle passed in and returned for resuming
+ * operations
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_enum.c
+ ***************************************************************/
+
+NET_API_STATUS NetLocalGroupEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupAddMembers
+ *
+ * @brief Add Members to a Local Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to modified
+ * @param[in] level The level defining the LOCALGROUP_MEMBERS_INFO_X structure
+ * @param[in] buffer The buffer containing a LOCALGROUP_MEMBERS_INFO_X structure
+ * @param[in] total_entries The number of LOCALGROUP_MEMBERS_INFO_X entries in
+ * the buffer
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_addmembers.c
+ ***************************************************************/
+
+NET_API_STATUS NetLocalGroupAddMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupDelMembers
+ *
+ * @brief Delete Members from a Local Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to modified
+ * @param[in] level The level defining the LOCALGROUP_MEMBERS_INFO_X structure
+ * @param[in] buffer The buffer containing a LOCALGROUP_MEMBERS_INFO_X structure
+ * @param[in] total_entries The number of LOCALGROUP_MEMBERS_INFO_X entries in
+ * the buffer
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_delmembers.c
+ ***************************************************************/
+
+NET_API_STATUS NetLocalGroupDelMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupGetMembers
+ *
+ * @brief Enumerate Members in a local group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] local_group_name The localgroup that is going to be queried
+ * @param[in] level The level defining the LOCALGROUP_MEMBERS_INFO_X structure
+ * @param[out] buffer The buffer containing a LOCALGROUP_MEMBERS_INFO_X
+ * structure
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of LOCALGROUP_MEMBERS_INFO_X entries in the buffer
+ * @param[out] total_entries The total number of LOCALGROUP_MEMBERS_INFO_X entries for that group
+ * @param[in,out] resume_handle A handle passed in and returned for resuming
+ * operations
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_getmembers.c
+ ***************************************************************/
+
+NET_API_STATUS NetLocalGroupGetMembers(const char * server_name /* [in] */,
+ const char * local_group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetLocalGroupSetMembers
+ *
+ * @brief Set Members in a Local Group
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] group_name The name of the group that is going to modified
+ * @param[in] level The level defining the LOCALGROUP_MEMBERS_INFO_X structure
+ * @param[in] buffer The buffer containing a LOCALGROUP_MEMBERS_INFO_X structure
+ * @param[in] total_entries The number of LOCALGROUP_MEMBERS_INFO_X entries in
+ * the buffer
+ * @return NET_API_STATUS
+ *
+ * example localgroup/localgroup_setmembers.c
+ ***************************************************************/
+
+NET_API_STATUS NetLocalGroupSetMembers(const char * server_name /* [in] */,
+ const char * group_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t total_entries /* [in] */);
+
+/************************************************************//**
+ *
+ * NetRemoteTOD
+ *
+ * @brief Query remote Time of Day
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[out] buf The buffer containing a TIME_OF_DAY_INFO structure
+ * @return NET_API_STATUS
+ *
+ * example server/remote_tod.c
+ ***************************************************************/
+
+NET_API_STATUS NetRemoteTOD(const char * server_name /* [in] */,
+ uint8_t **buf /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetShareAdd
+ *
+ * @brief Add Share
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level defining the requested SHARE_INFO_X structure
+ * @param[in] buffer The buffer containing a SHARE_INFO_X structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example share/share_add.c
+ ***************************************************************/
+
+NET_API_STATUS NetShareAdd(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetShareDel
+ *
+ * @brief Delete Share
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] net_name The name of the share to delete
+ * @param[in] reserved
+ * @return NET_API_STATUS
+ *
+ * example share/share_del.c
+ ***************************************************************/
+
+NET_API_STATUS NetShareDel(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t reserved /* [in] */);
+
+/************************************************************//**
+ *
+ * NetShareEnum
+ *
+ * @brief Enumerate Shares
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] level The level defining the SHARE_INFO_X structure
+ * @param[out] buffer The buffer containing a SHARE_INFO_X structure
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of SHARE_INFO_X entries in the buffer
+ * @param[out] total_entries The total number of SHARE_INFO_X entries
+ * @param[in,out] resume_handle A handle passed in and returned for resuming
+ * operations
+ * @return NET_API_STATUS
+ *
+ * example share/share_enum.c
+ ***************************************************************/
+
+NET_API_STATUS NetShareEnum(const char * server_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetShareGetInfo
+ *
+ * @brief Get Share Info
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] net_name The name of the share to query
+ * @param[in] level The level defining the SHARE_INFO_X structure
+ * @param[out] buffer The buffer containing a SHARE_INFO_X structure
+ * @return NET_API_STATUS
+ *
+ * example share/share_getinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetShareGetInfo(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetShareSetInfo
+ *
+ * @brief Set Share Info
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] net_name The name of the share to query
+ * @param[in] level The level defining the SHARE_INFO_X structure
+ * @param[in] buffer The buffer containing a SHARE_INFO_X structure
+ * @param[out] parm_err The returned parameter error number if any
+ * @return NET_API_STATUS
+ *
+ * example share/share_setinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetShareSetInfo(const char * server_name /* [in] */,
+ const char * net_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t *buffer /* [in] [ref] */,
+ uint32_t *parm_err /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetFileClose
+ *
+ * @brief Close a file
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] fileid The fileid of the file that is going to be closed
+ * @return NET_API_STATUS
+ *
+ * example file/file_close.c
+ ***************************************************************/
+
+NET_API_STATUS NetFileClose(const char * server_name /* [in] */,
+ uint32_t fileid /* [in] */);
+
+/************************************************************//**
+ *
+ * NetFileGetInfo
+ *
+ * @brief Close a file
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] fileid The fileid of the file that is going to be closed
+ * @param[in] level The level of the FILE_INFO_X buffer
+ * @param[out] buffer The buffer containing a FILE_INFO_X structure
+ * @return NET_API_STATUS
+ *
+ * example file/file_getinfo.c
+ ***************************************************************/
+
+NET_API_STATUS NetFileGetInfo(const char * server_name /* [in] */,
+ uint32_t fileid /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetFileEnum
+ *
+ * @brief Enumerate Files
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] base_path The
+ * @param[in] user_name The
+ * @param[in] level The level defining the FILE_INFO_X structure
+ * @param[out] buffer The buffer containing a FILE_INFO_X structure
+ * @param[in] prefmaxlen The requested maximal buffer size
+ * @param[out] entries_read The number of FILE_INFO_X entries in the buffer
+ * @param[out] total_entries The total number of FILE_INFO_X entries
+ * @param[in,out] resume_handle A handle passed in and returned for resuming
+ * operations
+ * @return NET_API_STATUS
+ *
+ * example file/file_enum.c
+ ***************************************************************/
+
+NET_API_STATUS NetFileEnum(const char * server_name /* [in] */,
+ const char * base_path /* [in] */,
+ const char * user_name /* [in] */,
+ uint32_t level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */,
+ uint32_t prefmaxlen /* [in] */,
+ uint32_t *entries_read /* [out] [ref] */,
+ uint32_t *total_entries /* [out] [ref] */,
+ uint32_t *resume_handle /* [in,out] [ref] */);
+
+/************************************************************//**
+ *
+ * NetShutdownInit
+ *
+ * @brief Start a machine shutdown
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] message The message that is displayed before the shutdown
+ * @param[in] timeout The amount of seconds to wait until shutting down
+ * @param[in] force_apps Whether to close all applications before the shutdown
+ * @param[in] do_reboot Whether to reboot after the shutdown
+ * @return NET_API_STATUS
+ *
+ * example shutdown/shutdown_init.c
+ ***************************************************************/
+
+NET_API_STATUS NetShutdownInit(const char * server_name /* [in] */,
+ const char * message /* [in] */,
+ uint32_t timeout /* [in] */,
+ uint8_t force_apps /* [in] */,
+ uint8_t do_reboot /* [in] */);
+
+/************************************************************//**
+ *
+ * NetShutdownAbort
+ *
+ * @brief Abort an initiated machine shutdown
+ *
+ * @param[in] server_name The server name to connect to
+ * @return NET_API_STATUS
+ *
+ * example shutdown/shutdown_abort.c
+ ***************************************************************/
+
+NET_API_STATUS NetShutdownAbort(const char * server_name /* [in] */);
+
+/************************************************************//**
+ *
+ * I_NetLogonControl
+ *
+ * @brief Control various aspects of the NETLOGON service
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] function_code The function code to call on the server
+ * @param[in] query_level The level of the NETLOGON_INFO structure returned
+ * @param[out] buffer The returned buffer containing the NETLOGON_INFO structure
+ * @return NET_API_STATUS
+ *
+ * example netlogon/netlogon_control.c
+ ***************************************************************/
+
+NET_API_STATUS I_NetLogonControl(const char * server_name /* [in] */,
+ uint32_t function_code /* [in] */,
+ uint32_t query_level /* [in] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+/************************************************************//**
+ *
+ * I_NetLogonControl2
+ *
+ * @brief Control various aspects of the NETLOGON service
+ *
+ * @param[in] server_name The server name to connect to
+ * @param[in] function_code The function code to call on the server
+ * @param[in] query_level The level of the NETLOGON_INFO structure returned
+ * @param[in] data The buffer containing information related to the function code
+ * @param[out] buffer The returned buffer containing the NETLOGON_INFO structure
+ * @return NET_API_STATUS
+ *
+ * example netlogon/netlogon_control2.c
+ ***************************************************************/
+
+NET_API_STATUS I_NetLogonControl2(const char * server_name /* [in] */,
+ uint32_t function_code /* [in] */,
+ uint32_t query_level /* [in] */,
+ uint8_t *data /* [in] [ref] */,
+ uint8_t **buffer /* [out] [ref] */);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __LIB_NETAPI_H__ */
diff --git a/source3/lib/netapi/netapi_net.h b/source3/lib/netapi/netapi_net.h
new file mode 100644
index 0000000..8febc2a
--- /dev/null
+++ b/source3/lib/netapi/netapi_net.h
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Andrew Bartlett 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This API header is private between the 'net' binary and and libnet */
+
+/* This function is to init the libnetapi subsystem, without
+ * re-reading config files or setting debug levels etc */
+NET_API_STATUS libnetapi_net_init(struct libnetapi_ctx **ctx,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *creds);
diff --git a/source3/lib/netapi/netapi_private.h b/source3/lib/netapi/netapi_private.h
new file mode 100644
index 0000000..c0d0ea0
--- /dev/null
+++ b/source3/lib/netapi/netapi_private.h
@@ -0,0 +1,114 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_NETAPI_PRIVATE_H__
+#define __LIB_NETAPI_PRIVATE_H__
+
+#include "lib/netapi/netapi_net.h"
+#include "auth/credentials/credentials.h"
+
+#define LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, fn) \
+ DEBUG(10,("redirecting call %s to localhost\n", #fn)); \
+ if (!r->in.server_name) { \
+ r->in.server_name = "localhost"; \
+ } \
+ return fn ## _r(ctx, r);
+
+struct dcerpc_binding_handle;
+struct ndr_interface_table;
+
+struct libnetapi_private_ctx {
+ struct {
+ const char *domain_name;
+ struct dom_sid *domain_sid;
+ struct rpc_pipe_client *cli;
+
+ uint32_t connect_mask;
+ struct policy_handle connect_handle;
+
+ uint32_t domain_mask;
+ struct policy_handle domain_handle;
+
+ uint32_t builtin_mask;
+ struct policy_handle builtin_handle;
+ } samr;
+
+ struct client_ipc_connection *ipc_connections;
+
+ struct messaging_context *msg_ctx;
+};
+
+struct libnetapi_ctx {
+ char *debuglevel;
+ char *logfile;
+ char *error_string;
+ int disable_policy_handle_cache;
+
+ struct cli_credentials *creds;
+
+ void *private_data;
+ struct loadparm_context *lp_ctx;
+};
+
+
+NET_API_STATUS libnetapi_set_error_string(struct libnetapi_ctx *ctx,
+ const char *format, ...)
+ PRINTF_ATTRIBUTE(2,3);
+NET_API_STATUS libnetapi_get_debuglevel(struct libnetapi_ctx *ctx, char **debuglevel);
+NET_API_STATUS libnetapi_set_logfile(struct libnetapi_ctx *ctx,
+ const char *logfile);
+
+WERROR libnetapi_shutdown_cm(struct libnetapi_ctx *ctx);
+WERROR libnetapi_open_pipe(struct libnetapi_ctx *ctx,
+ const char *server_name,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult);
+WERROR libnetapi_get_binding_handle(struct libnetapi_ctx *ctx,
+ const char *server_name,
+ const struct ndr_interface_table *table,
+ struct dcerpc_binding_handle **binding_handle);
+WERROR libnetapi_samr_open_domain(struct libnetapi_ctx *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ uint32_t connect_mask,
+ uint32_t domain_mask,
+ struct policy_handle *connect_handle,
+ struct policy_handle *domain_handle,
+ struct dom_sid2 **domain_sid);
+WERROR libnetapi_samr_open_builtin_domain(struct libnetapi_ctx *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ uint32_t connect_mask,
+ uint32_t builtin_mask,
+ struct policy_handle *connect_handle,
+ struct policy_handle *builtin_handle);
+void libnetapi_samr_close_domain_handle(struct libnetapi_ctx *ctx,
+ struct policy_handle *handle);
+void libnetapi_samr_close_builtin_handle(struct libnetapi_ctx *ctx,
+ struct policy_handle *handle);
+void libnetapi_samr_close_connect_handle(struct libnetapi_ctx *ctx,
+ struct policy_handle *handle);
+void libnetapi_samr_free(struct libnetapi_ctx *ctx);
+
+NTSTATUS add_GROUP_USERS_INFO_X_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ const char *group_name,
+ uint32_t attributes,
+ uint8_t **buffer,
+ uint32_t *num_entries);
+
+#endif
diff --git a/source3/lib/netapi/netlogon.c b/source3/lib/netapi/netlogon.c
new file mode 100644
index 0000000..717dd17
--- /dev/null
+++ b/source3/lib/netapi/netlogon.c
@@ -0,0 +1,248 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi LogonControl Support
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+
+static WERROR construct_data(enum netr_LogonControlCode function_code,
+ const uint8_t *data_in,
+ union netr_CONTROL_DATA_INFORMATION *data_out)
+{
+ switch (function_code) {
+ case NETLOGON_CONTROL_QUERY:
+ case NETLOGON_CONTROL_REDISCOVER:
+ case NETLOGON_CONTROL_TC_QUERY:
+ case NETLOGON_CONTROL_CHANGE_PASSWORD:
+ case NETLOGON_CONTROL_TC_VERIFY:
+ data_out->domain = (const char *)data_in;
+ break;
+ case NETLOGON_CONTROL_FIND_USER:
+ data_out->user = (const char *)data_in;
+ break;
+ case NETLOGON_CONTROL_SET_DBFLAG:
+ data_out->debug_level = atoi((const char *)data_in);
+ break;
+ case NETLOGON_CONTROL_FORCE_DNS_REG:
+ ZERO_STRUCTP(data_out);
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR construct_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ union netr_CONTROL_QUERY_INFORMATION *q,
+ uint8_t **buffer)
+{
+ struct NETLOGON_INFO_1 *i1;
+ struct NETLOGON_INFO_2 *i2;
+ struct NETLOGON_INFO_3 *i3;
+ struct NETLOGON_INFO_4 *i4;
+
+ if (!q) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (level) {
+ case 1:
+ i1 = talloc(mem_ctx, struct NETLOGON_INFO_1);
+ W_ERROR_HAVE_NO_MEMORY(i1);
+
+ i1->netlog1_flags = q->info1->flags;
+ i1->netlog1_pdc_connection_status = W_ERROR_V(q->info1->pdc_connection_status);
+
+ *buffer = (uint8_t *)i1;
+
+ break;
+ case 2:
+ i2 = talloc(mem_ctx, struct NETLOGON_INFO_2);
+ W_ERROR_HAVE_NO_MEMORY(i2);
+
+ i2->netlog2_flags = q->info2->flags;
+ i2->netlog2_pdc_connection_status = W_ERROR_V(q->info2->pdc_connection_status);
+ i2->netlog2_trusted_dc_name = talloc_strdup(mem_ctx, q->info2->trusted_dc_name);
+ i2->netlog2_tc_connection_status = W_ERROR_V(q->info2->tc_connection_status);
+
+ *buffer = (uint8_t *)i2;
+
+ break;
+ case 3:
+ i3 = talloc(mem_ctx, struct NETLOGON_INFO_3);
+ W_ERROR_HAVE_NO_MEMORY(i3);
+
+ i3->netlog1_flags = q->info3->flags;
+ i3->netlog3_logon_attempts = q->info3->logon_attempts;
+ i3->netlog3_reserved1 = q->info3->unknown1;
+ i3->netlog3_reserved2 = q->info3->unknown2;
+ i3->netlog3_reserved3 = q->info3->unknown3;
+ i3->netlog3_reserved4 = q->info3->unknown4;
+ i3->netlog3_reserved5 = q->info3->unknown5;
+
+ *buffer = (uint8_t *)i3;
+
+ break;
+ case 4:
+ i4 = talloc(mem_ctx, struct NETLOGON_INFO_4);
+ W_ERROR_HAVE_NO_MEMORY(i4);
+
+ i4->netlog4_trusted_dc_name = talloc_strdup(mem_ctx, q->info4->trusted_dc_name);
+ i4->netlog4_trusted_domain_name = talloc_strdup(mem_ctx, q->info4->trusted_domain_name);
+
+ *buffer = (uint8_t *)i4;
+
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR I_NetLogonControl_r(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ union netr_CONTROL_QUERY_INFORMATION query;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_netlogon,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_netr_LogonControl(b, talloc_tos(),
+ r->in.server_name,
+ r->in.function_code,
+ r->in.query_level,
+ &query,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = construct_buffer(ctx, r->in.query_level, &query,
+ r->out.buffer);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR I_NetLogonControl_l(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, I_NetLogonControl);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR I_NetLogonControl2_r(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl2 *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ union netr_CONTROL_DATA_INFORMATION data;
+ union netr_CONTROL_QUERY_INFORMATION query;
+ struct dcerpc_binding_handle *b;
+
+ werr = construct_data(r->in.function_code, r->in.data, &data);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_netlogon,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ switch (r->in.function_code) {
+ case NETLOGON_CONTROL_TC_VERIFY:
+ case NETLOGON_CONTROL_SET_DBFLAG:
+ case NETLOGON_CONTROL_FORCE_DNS_REG:
+ status = dcerpc_netr_LogonControl2Ex(b, talloc_tos(),
+ r->in.server_name,
+ r->in.function_code,
+ r->in.query_level,
+ &data,
+ &query,
+ &werr);
+ break;
+ default:
+ status = dcerpc_netr_LogonControl2(b, talloc_tos(),
+ r->in.server_name,
+ r->in.function_code,
+ r->in.query_level,
+ &data,
+ &query,
+ &werr);
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = construct_buffer(ctx, r->in.query_level, &query,
+ r->out.buffer);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR I_NetLogonControl2_l(struct libnetapi_ctx *ctx,
+ struct I_NetLogonControl2 *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, I_NetLogonControl2);
+}
diff --git a/source3/lib/netapi/samr.c b/source3/lib/netapi/samr.c
new file mode 100644
index 0000000..c1b7cfa
--- /dev/null
+++ b/source3/lib/netapi/samr.c
@@ -0,0 +1,346 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Samr Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/cli_samr.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnetapi_samr_open_domain(struct libnetapi_ctx *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ uint32_t connect_mask,
+ uint32_t domain_mask,
+ struct policy_handle *connect_handle,
+ struct policy_handle *domain_handle,
+ struct dom_sid2 **domain_sid)
+{
+ NTSTATUS status, result;
+ WERROR werr;
+ struct libnetapi_private_ctx *priv;
+ uint32_t resume_handle = 0;
+ uint32_t num_entries = 0;
+ struct samr_SamArray *sam = NULL;
+ const char *domain_name = NULL;
+ struct lsa_String lsa_domain_name;
+ bool domain_found = false;
+ int i;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ priv = talloc_get_type_abort(mem_ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ if (is_valid_policy_hnd(&priv->samr.connect_handle)) {
+ if ((priv->samr.connect_mask & connect_mask) == connect_mask) {
+ *connect_handle = priv->samr.connect_handle;
+ } else {
+ libnetapi_samr_close_connect_handle(mem_ctx,
+ &priv->samr.connect_handle);
+ }
+ }
+
+ if (is_valid_policy_hnd(&priv->samr.domain_handle)) {
+ if ((priv->samr.domain_mask & domain_mask) == domain_mask) {
+ *domain_handle = priv->samr.domain_handle;
+ } else {
+ libnetapi_samr_close_domain_handle(mem_ctx,
+ &priv->samr.domain_handle);
+ }
+ }
+
+ if (priv->samr.domain_sid) {
+ *domain_sid = priv->samr.domain_sid;
+ }
+
+ if (is_valid_policy_hnd(&priv->samr.connect_handle) &&
+ ((priv->samr.connect_mask & connect_mask) == connect_mask) &&
+ is_valid_policy_hnd(&priv->samr.domain_handle) &&
+ (priv->samr.domain_mask & domain_mask) == domain_mask) {
+ return WERR_OK;
+ }
+
+ if (!is_valid_policy_hnd(connect_handle)) {
+ status = dcerpc_try_samr_connects(pipe_cli->binding_handle, mem_ctx,
+ pipe_cli->srv_name_slash,
+ connect_mask,
+ connect_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ status = dcerpc_samr_EnumDomains(b, mem_ctx,
+ connect_handle,
+ &resume_handle,
+ &sam,
+ 0xffffffff,
+ &num_entries,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ for (i=0; i<num_entries; i++) {
+
+ domain_name = sam->entries[i].name.string;
+
+ if (strequal(domain_name, builtin_domain_name())) {
+ continue;
+ }
+
+ domain_found = true;
+ break;
+ }
+
+ if (!domain_found) {
+ werr = WERR_NO_SUCH_DOMAIN;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_domain_name, domain_name);
+
+ status = dcerpc_samr_LookupDomain(b, mem_ctx,
+ connect_handle,
+ &lsa_domain_name,
+ domain_sid,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ connect_handle,
+ domain_mask,
+ *domain_sid,
+ domain_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ priv->samr.cli = pipe_cli;
+
+ priv->samr.domain_name = domain_name;
+ priv->samr.domain_sid = *domain_sid;
+
+ priv->samr.connect_mask = connect_mask;
+ priv->samr.connect_handle = *connect_handle;
+
+ priv->samr.domain_mask = domain_mask;
+ priv->samr.domain_handle = *domain_handle;
+
+ werr = WERR_OK;
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnetapi_samr_open_builtin_domain(struct libnetapi_ctx *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ uint32_t connect_mask,
+ uint32_t builtin_mask,
+ struct policy_handle *connect_handle,
+ struct policy_handle *builtin_handle)
+{
+ NTSTATUS status, result;
+ WERROR werr;
+ struct libnetapi_private_ctx *priv;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ priv = talloc_get_type_abort(mem_ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ if (is_valid_policy_hnd(&priv->samr.connect_handle)) {
+ if ((priv->samr.connect_mask & connect_mask) == connect_mask) {
+ *connect_handle = priv->samr.connect_handle;
+ } else {
+ libnetapi_samr_close_connect_handle(mem_ctx,
+ &priv->samr.connect_handle);
+ }
+ }
+
+ if (is_valid_policy_hnd(&priv->samr.builtin_handle)) {
+ if ((priv->samr.builtin_mask & builtin_mask) == builtin_mask) {
+ *builtin_handle = priv->samr.builtin_handle;
+ } else {
+ libnetapi_samr_close_builtin_handle(mem_ctx,
+ &priv->samr.builtin_handle);
+ }
+ }
+
+ if (is_valid_policy_hnd(&priv->samr.connect_handle) &&
+ ((priv->samr.connect_mask & connect_mask) == connect_mask) &&
+ is_valid_policy_hnd(&priv->samr.builtin_handle) &&
+ (priv->samr.builtin_mask & builtin_mask) == builtin_mask) {
+ return WERR_OK;
+ }
+
+ if (!is_valid_policy_hnd(connect_handle)) {
+ status = dcerpc_try_samr_connects(pipe_cli->binding_handle, mem_ctx,
+ pipe_cli->srv_name_slash,
+ connect_mask,
+ connect_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ connect_handle,
+ builtin_mask,
+ discard_const_p(struct dom_sid, &global_sid_Builtin),
+ builtin_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ priv->samr.cli = pipe_cli;
+
+ priv->samr.connect_mask = connect_mask;
+ priv->samr.connect_handle = *connect_handle;
+
+ priv->samr.builtin_mask = builtin_mask;
+ priv->samr.builtin_handle = *builtin_handle;
+
+ werr = WERR_OK;
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+void libnetapi_samr_close_domain_handle(struct libnetapi_ctx *ctx,
+ struct policy_handle *handle)
+{
+ struct libnetapi_private_ctx *priv;
+ struct dcerpc_binding_handle *b;
+ NTSTATUS result;
+
+ if (!is_valid_policy_hnd(handle)) {
+ return;
+ }
+
+ priv = talloc_get_type_abort(ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ if (!ndr_policy_handle_equal(handle, &priv->samr.domain_handle)) {
+ return;
+ }
+
+ b = priv->samr.cli->binding_handle;
+
+ dcerpc_samr_Close(b, ctx, handle, &result);
+
+ ZERO_STRUCT(priv->samr.domain_handle);
+}
+
+/****************************************************************
+****************************************************************/
+
+void libnetapi_samr_close_builtin_handle(struct libnetapi_ctx *ctx,
+ struct policy_handle *handle)
+{
+ struct libnetapi_private_ctx *priv;
+ struct dcerpc_binding_handle *b;
+ NTSTATUS result;
+
+ if (!is_valid_policy_hnd(handle)) {
+ return;
+ }
+
+ priv = talloc_get_type_abort(ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ if (!ndr_policy_handle_equal(handle, &priv->samr.builtin_handle)) {
+ return;
+ }
+
+ b = priv->samr.cli->binding_handle;
+
+ dcerpc_samr_Close(b, ctx, handle, &result);
+
+ ZERO_STRUCT(priv->samr.builtin_handle);
+}
+
+/****************************************************************
+****************************************************************/
+
+void libnetapi_samr_close_connect_handle(struct libnetapi_ctx *ctx,
+ struct policy_handle *handle)
+{
+ struct libnetapi_private_ctx *priv;
+ struct dcerpc_binding_handle *b;
+ NTSTATUS result;
+
+ if (!is_valid_policy_hnd(handle)) {
+ return;
+ }
+
+ priv = talloc_get_type_abort(ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ if (!ndr_policy_handle_equal(handle, &priv->samr.connect_handle)) {
+ return;
+ }
+
+ b = priv->samr.cli->binding_handle;
+
+ dcerpc_samr_Close(b, ctx, handle, &result);
+
+ ZERO_STRUCT(priv->samr.connect_handle);
+}
+
+/****************************************************************
+****************************************************************/
+
+void libnetapi_samr_free(struct libnetapi_ctx *ctx)
+{
+ struct libnetapi_private_ctx *priv;
+
+ if (!ctx->private_data) {
+ return;
+ }
+
+ priv = talloc_get_type_abort(ctx->private_data,
+ struct libnetapi_private_ctx);
+
+ libnetapi_samr_close_domain_handle(ctx, &priv->samr.domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &priv->samr.builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &priv->samr.connect_handle);
+}
diff --git a/source3/lib/netapi/serverinfo.c b/source3/lib/netapi/serverinfo.c
new file mode 100644
index 0000000..cb5d9a2
--- /dev/null
+++ b/source3/lib/netapi/serverinfo.c
@@ -0,0 +1,701 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Server Support
+ * Copyright (C) Guenther Deschner 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "libsmb/dsgetdcname.h"
+
+/****************************************************************
+****************************************************************/
+
+static WERROR NetServerGetInfo_l_101(struct libnetapi_ctx *ctx,
+ uint8_t **buffer)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct SERVER_INFO_101 i;
+
+ i.sv101_platform_id = PLATFORM_ID_NT;
+ i.sv101_name = lp_netbios_name();
+ i.sv101_version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION;
+ i.sv101_version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION;
+ i.sv101_type = lp_default_server_announce();
+ i.sv101_comment = lp_server_string(ctx, lp_sub);
+
+ *buffer = (uint8_t *)talloc_memdup(ctx, &i, sizeof(i));
+ if (!*buffer) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR NetServerGetInfo_l_1005(struct libnetapi_ctx *ctx,
+ uint8_t **buffer)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct SERVER_INFO_1005 info1005;
+
+ info1005.sv1005_comment = lp_server_string(ctx, lp_sub);
+ *buffer = (uint8_t *)talloc_memdup(ctx, &info1005, sizeof(info1005));
+ if (!*buffer) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetServerGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetServerGetInfo *r)
+{
+ switch (r->in.level) {
+ case 101:
+ return NetServerGetInfo_l_101(ctx, r->out.buffer);
+ case 1005:
+ return NetServerGetInfo_l_1005(ctx, r->out.buffer);
+ default:
+ break;
+ }
+
+ return WERR_INVALID_LEVEL;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS map_server_info_to_SERVER_INFO_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ union srvsvc_NetSrvInfo *i,
+ uint8_t **buffer)
+{
+ struct SERVER_INFO_100 i100;
+ struct SERVER_INFO_101 i101;
+ struct SERVER_INFO_102 i102;
+ struct SERVER_INFO_402 i402;
+ struct SERVER_INFO_403 i403;
+ struct SERVER_INFO_502 i502;
+ struct SERVER_INFO_503 i503;
+ struct SERVER_INFO_599 i599;
+ struct SERVER_INFO_1005 i1005;
+#if 0
+ struct SERVER_INFO_1010 i1010;
+ struct SERVER_INFO_1016 i1016;
+ struct SERVER_INFO_1017 i1017;
+ struct SERVER_INFO_1018 i1018;
+ struct SERVER_INFO_1107 i1107;
+ struct SERVER_INFO_1501 i1501;
+ struct SERVER_INFO_1502 i1502;
+ struct SERVER_INFO_1503 i1503;
+ struct SERVER_INFO_1506 i1506;
+ struct SERVER_INFO_1509 i1509;
+ struct SERVER_INFO_1510 i1510;
+ struct SERVER_INFO_1511 i1511;
+ struct SERVER_INFO_1512 i1512;
+ struct SERVER_INFO_1513 i1513;
+ struct SERVER_INFO_1514 i1514;
+ struct SERVER_INFO_1515 i1515;
+ struct SERVER_INFO_1516 i1516;
+ struct SERVER_INFO_1518 i1518;
+ struct SERVER_INFO_1520 i1520;
+ struct SERVER_INFO_1521 i1521;
+ struct SERVER_INFO_1522 i1522;
+ struct SERVER_INFO_1523 i1523;
+ struct SERVER_INFO_1524 i1524;
+ struct SERVER_INFO_1525 i1525;
+ struct SERVER_INFO_1528 i1528;
+ struct SERVER_INFO_1529 i1529;
+ struct SERVER_INFO_1530 i1530;
+ struct SERVER_INFO_1533 i1533;
+ struct SERVER_INFO_1534 i1534;
+ struct SERVER_INFO_1535 i1535;
+ struct SERVER_INFO_1536 i1536;
+ struct SERVER_INFO_1537 i1537;
+ struct SERVER_INFO_1538 i1538;
+ struct SERVER_INFO_1539 i1539;
+ struct SERVER_INFO_1540 i1540;
+ struct SERVER_INFO_1541 i1541;
+ struct SERVER_INFO_1542 i1542;
+ struct SERVER_INFO_1543 i1543;
+ struct SERVER_INFO_1544 i1544;
+ struct SERVER_INFO_1545 i1545;
+ struct SERVER_INFO_1546 i1546;
+ struct SERVER_INFO_1547 i1547;
+ struct SERVER_INFO_1548 i1548;
+ struct SERVER_INFO_1549 i1549;
+ struct SERVER_INFO_1550 i1550;
+ struct SERVER_INFO_1552 i1552;
+ struct SERVER_INFO_1553 i1553;
+ struct SERVER_INFO_1554 i1554;
+ struct SERVER_INFO_1555 i1555;
+ struct SERVER_INFO_1556 i1556;
+ struct SERVER_INFO_1557 i1557;
+ struct SERVER_INFO_1560 i1560;
+ struct SERVER_INFO_1561 i1561;
+ struct SERVER_INFO_1562 i1562;
+ struct SERVER_INFO_1563 i1563;
+ struct SERVER_INFO_1564 i1564;
+ struct SERVER_INFO_1565 i1565;
+ struct SERVER_INFO_1566 i1566;
+ struct SERVER_INFO_1567 i1567;
+ struct SERVER_INFO_1568 i1568;
+ struct SERVER_INFO_1569 i1569;
+ struct SERVER_INFO_1570 i1570;
+ struct SERVER_INFO_1571 i1571;
+ struct SERVER_INFO_1572 i1572;
+ struct SERVER_INFO_1573 i1573;
+ struct SERVER_INFO_1574 i1574;
+ struct SERVER_INFO_1575 i1575;
+ struct SERVER_INFO_1576 i1576;
+ struct SERVER_INFO_1577 i1577;
+ struct SERVER_INFO_1578 i1578;
+ struct SERVER_INFO_1579 i1579;
+ struct SERVER_INFO_1580 i1580;
+ struct SERVER_INFO_1581 i1581;
+ struct SERVER_INFO_1582 i1582;
+ struct SERVER_INFO_1583 i1583;
+ struct SERVER_INFO_1584 i1584;
+ struct SERVER_INFO_1585 i1585;
+ struct SERVER_INFO_1586 i1586;
+ struct SERVER_INFO_1587 i1587;
+ struct SERVER_INFO_1588 i1588;
+ struct SERVER_INFO_1590 i1590;
+ struct SERVER_INFO_1591 i1591;
+ struct SERVER_INFO_1592 i1592;
+ struct SERVER_INFO_1593 i1593;
+ struct SERVER_INFO_1594 i1594;
+ struct SERVER_INFO_1595 i1595;
+ struct SERVER_INFO_1596 i1596;
+ struct SERVER_INFO_1597 i1597;
+ struct SERVER_INFO_1598 i1598;
+ struct SERVER_INFO_1599 i1599;
+ struct SERVER_INFO_1600 i1600;
+ struct SERVER_INFO_1601 i1601;
+ struct SERVER_INFO_1602 i1602;
+#endif
+ uint32_t num_info = 0;
+
+ switch (level) {
+ case 100:
+ i100.sv100_platform_id = i->info100->platform_id;
+ i100.sv100_name = talloc_strdup(mem_ctx, i->info100->server_name);
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_100, i100,
+ (struct SERVER_INFO_100 **)buffer,
+ &num_info);
+ break;
+
+ case 101:
+ i101.sv101_platform_id = i->info101->platform_id;
+ i101.sv101_name = talloc_strdup(mem_ctx, i->info101->server_name);
+ i101.sv101_version_major = i->info101->version_major;
+ i101.sv101_version_minor = i->info101->version_minor;
+ i101.sv101_type = i->info101->server_type;
+ i101.sv101_comment = talloc_strdup(mem_ctx, i->info101->comment);
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_101, i101,
+ (struct SERVER_INFO_101 **)buffer,
+ &num_info);
+ break;
+
+ case 102:
+ i102.sv102_platform_id = i->info102->platform_id;
+ i102.sv102_name = talloc_strdup(mem_ctx, i->info102->server_name);
+ i102.sv102_version_major = i->info102->version_major;
+ i102.sv102_version_minor = i->info102->version_minor;
+ i102.sv102_type = i->info102->server_type;
+ i102.sv102_comment = talloc_strdup(mem_ctx, i->info102->comment);
+ i102.sv102_users = i->info102->users;
+ i102.sv102_disc = i->info102->disc;
+ i102.sv102_hidden = i->info102->hidden;
+ i102.sv102_announce = i->info102->announce;
+ i102.sv102_anndelta = i->info102->anndelta;
+ i102.sv102_licenses = i->info102->licenses;
+ i102.sv102_userpath = talloc_strdup(mem_ctx, i->info102->userpath);
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_102, i102,
+ (struct SERVER_INFO_102 **)buffer,
+ &num_info);
+ break;
+
+ case 402:
+
+ i402.sv402_ulist_mtime = i->info402->ulist_mtime;
+ i402.sv402_glist_mtime = i->info402->glist_mtime;
+ i402.sv402_alist_mtime = i->info402->alist_mtime;
+ i402.sv402_alerts = talloc_strdup(mem_ctx, i->info402->alerts);
+ i402.sv402_security = i->info402->security;
+ i402.sv402_numadmin = i->info402->numadmin;
+ i402.sv402_lanmask = i->info402->lanmask;
+ i402.sv402_guestacct = talloc_strdup(mem_ctx, i->info402->guestaccount);
+ i402.sv402_chdevs = i->info402->chdevs;
+ i402.sv402_chdevq = i->info402->chdevqs;
+ i402.sv402_chdevjobs = i->info402->chdevjobs;
+ i402.sv402_connections = i->info402->connections;
+ i402.sv402_shares = i->info402->shares;
+ i402.sv402_openfiles = i->info402->openfiles;
+ i402.sv402_sessopens = i->info402->sessopen;
+ i402.sv402_sessvcs = i->info402->sesssvc;
+ i402.sv402_sessreqs = i->info402->sessreqs;
+ i402.sv402_opensearch = i->info402->opensearch;
+ i402.sv402_activelocks = i->info402->activelocks;
+ i402.sv402_numreqbuf = i->info402->numreqbufs;
+ i402.sv402_sizreqbuf = i->info402->sizereqbufs;
+ i402.sv402_numbigbuf = i->info402->numbigbufs;
+ i402.sv402_numfiletasks = i->info402->numfiletasks;
+ i402.sv402_alertsched = i->info402->alertsched;
+ i402.sv402_erroralert = i->info402->erroralert;
+ i402.sv402_logonalert = i->info402->logonalert;
+ i402.sv402_accessalert = i->info402->accessalert;
+ i402.sv402_diskalert = i->info402->diskalert;
+ i402.sv402_netioalert = i->info402->netioalert;
+ i402.sv402_maxauditsz = i->info402->maxaudits;
+ i402.sv402_srvheuristics = i->info402->srvheuristics;
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_402, i402,
+ (struct SERVER_INFO_402 **)buffer,
+ &num_info);
+ break;
+
+ case 403:
+
+ i403.sv403_ulist_mtime = i->info403->ulist_mtime;
+ i403.sv403_glist_mtime = i->info403->glist_mtime;
+ i403.sv403_alist_mtime = i->info403->alist_mtime;
+ i403.sv403_alerts = talloc_strdup(mem_ctx, i->info403->alerts);
+ i403.sv403_security = i->info403->security;
+ i403.sv403_numadmin = i->info403->numadmin;
+ i403.sv403_lanmask = i->info403->lanmask;
+ i403.sv403_guestacct = talloc_strdup(mem_ctx, i->info403->guestaccount);
+ i403.sv403_chdevs = i->info403->chdevs;
+ i403.sv403_chdevq = i->info403->chdevqs;
+ i403.sv403_chdevjobs = i->info403->chdevjobs;
+ i403.sv403_connections = i->info403->connections;
+ i403.sv403_shares = i->info403->shares;
+ i403.sv403_openfiles = i->info403->openfiles;
+ i403.sv403_sessopens = i->info403->sessopen;
+ i403.sv403_sessvcs = i->info403->sesssvc;
+ i403.sv403_sessreqs = i->info403->sessreqs;
+ i403.sv403_opensearch = i->info403->opensearch;
+ i403.sv403_activelocks = i->info403->activelocks;
+ i403.sv403_numreqbuf = i->info403->numreqbufs;
+ i403.sv403_sizreqbuf = i->info403->sizereqbufs;
+ i403.sv403_numbigbuf = i->info403->numbigbufs;
+ i403.sv403_numfiletasks = i->info403->numfiletasks;
+ i403.sv403_alertsched = i->info403->alertsched;
+ i403.sv403_erroralert = i->info403->erroralert;
+ i403.sv403_logonalert = i->info403->logonalert;
+ i403.sv403_accessalert = i->info403->accessalert;
+ i403.sv403_diskalert = i->info403->diskalert;
+ i403.sv403_netioalert = i->info403->netioalert;
+ i403.sv403_maxauditsz = i->info403->maxaudits;
+ i403.sv403_srvheuristics = i->info403->srvheuristics;
+ i403.sv403_auditedevents = i->info403->auditedevents;
+ i403.sv403_autoprofile = i->info403->auditprofile;
+ i403.sv403_autopath = talloc_strdup(mem_ctx, i->info403->autopath);
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_403, i403,
+ (struct SERVER_INFO_403 **)buffer,
+ &num_info);
+ break;
+
+ case 502:
+ i502.sv502_sessopens = i->info502->sessopen;
+ i502.sv502_sessvcs = i->info502->sesssvc;
+ i502.sv502_opensearch = i->info502->opensearch;
+ i502.sv502_sizreqbuf = i->info502->sizereqbufs;
+ i502.sv502_initworkitems = i->info502->initworkitems;
+ i502.sv502_maxworkitems = i->info502->maxworkitems;
+ i502.sv502_rawworkitems = i->info502->rawworkitems;
+ i502.sv502_irpstacksize = i->info502->irpstacksize;
+ i502.sv502_maxrawbuflen = i->info502->maxrawbuflen;
+ i502.sv502_sessusers = i->info502->sessusers;
+ i502.sv502_sessconns = i->info502->sessconns;
+ i502.sv502_maxpagedmemoryusage = i->info502->maxpagedmemoryusage;
+ i502.sv502_maxnonpagedmemoryusage = i->info502->maxnonpagedmemoryusage;
+ i502.sv502_enablesoftcompat = i->info502->enablesoftcompat;
+ i502.sv502_enableforcedlogoff = i->info502->enableforcedlogoff;
+ i502.sv502_timesource = i->info502->timesource;
+ i502.sv502_acceptdownlevelapis = i->info502->acceptdownlevelapis;
+ i502.sv502_lmannounce = i->info502->lmannounce;
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_502, i502,
+ (struct SERVER_INFO_502 **)buffer,
+ &num_info);
+ break;
+
+ case 503:
+ i503.sv503_sessopens = i->info503->sessopen;
+ i503.sv503_sessvcs = i->info503->sesssvc;
+ i503.sv503_opensearch = i->info503->opensearch;
+ i503.sv503_sizreqbuf = i->info503->sizereqbufs;
+ i503.sv503_initworkitems = i->info503->initworkitems;
+ i503.sv503_maxworkitems = i->info503->maxworkitems;
+ i503.sv503_rawworkitems = i->info503->rawworkitems;
+ i503.sv503_irpstacksize = i->info503->irpstacksize;
+ i503.sv503_maxrawbuflen = i->info503->maxrawbuflen;
+ i503.sv503_sessusers = i->info503->sessusers;
+ i503.sv503_sessconns = i->info503->sessconns;
+ i503.sv503_maxpagedmemoryusage = i->info503->maxpagedmemoryusage;
+ i503.sv503_maxnonpagedmemoryusage = i->info503->maxnonpagedmemoryusage;
+ i503.sv503_enablesoftcompat = i->info503->enablesoftcompat;
+ i503.sv503_enableforcedlogoff = i->info503->enableforcedlogoff;
+ i503.sv503_timesource = i->info503->timesource;
+ i503.sv503_acceptdownlevelapis = i->info503->acceptdownlevelapis;
+ i503.sv503_lmannounce = i->info503->lmannounce;
+ i503.sv503_domain = talloc_strdup(mem_ctx, i->info503->domain);
+ i503.sv503_maxcopyreadlen = i->info503->maxcopyreadlen;
+ i503.sv503_maxcopywritelen = i->info503->maxcopywritelen;
+ i503.sv503_minkeepsearch = i->info503->minkeepsearch;
+ i503.sv503_maxkeepsearch = i->info503->maxkeepsearch;
+ i503.sv503_minkeepcomplsearch = i->info503->minkeepcomplsearch;
+ i503.sv503_maxkeepcomplsearch = i->info503->maxkeepcomplsearch;
+ i503.sv503_threadcountadd = i->info503->threadcountadd;
+ i503.sv503_numblockthreads = i->info503->numlockthreads;
+ i503.sv503_scavtimeout = i->info503->scavtimeout;
+ i503.sv503_minrcvqueue = i->info503->minrcvqueue;
+ i503.sv503_minfreeworkitems = i->info503->minfreeworkitems;
+ i503.sv503_xactmemsize = i->info503->xactmemsize;
+ i503.sv503_threadpriority = i->info503->threadpriority;
+ i503.sv503_maxmpxct = i->info503->maxmpxct;
+ i503.sv503_oplockbreakwait = i->info503->oplockbreakwait;
+ i503.sv503_oplockbreakresponsewait = i->info503->oplockbreakresponsewait;
+ i503.sv503_enableoplocks = i->info503->enableoplocks;
+ i503.sv503_enableoplockforceclose = i->info503->enableoplockforceclose;
+ i503.sv503_enablefcbopens = i->info503->enablefcbopens;
+ i503.sv503_enableraw = i->info503->enableraw;
+ i503.sv503_enablesharednetdrives = i->info503->enablesharednetdrives;
+ i503.sv503_minfreeconnections = i->info503->minfreeconnections;
+ i503.sv503_maxfreeconnections = i->info503->maxfreeconnections;
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_503, i503,
+ (struct SERVER_INFO_503 **)buffer,
+ &num_info);
+ break;
+
+ case 599:
+ i599.sv599_sessopens = i->info599->sessopen;
+ i599.sv599_sessvcs = i->info599->sesssvc;
+ i599.sv599_opensearch = i->info599->opensearch;
+ i599.sv599_sizreqbuf = i->info599->sizereqbufs;
+ i599.sv599_initworkitems = i->info599->initworkitems;
+ i599.sv599_maxworkitems = i->info599->maxworkitems;
+ i599.sv599_rawworkitems = i->info599->rawworkitems;
+ i599.sv599_irpstacksize = i->info599->irpstacksize;
+ i599.sv599_maxrawbuflen = i->info599->maxrawbuflen;
+ i599.sv599_sessusers = i->info599->sessusers;
+ i599.sv599_sessconns = i->info599->sessconns;
+ i599.sv599_maxpagedmemoryusage = i->info599->maxpagedmemoryusage;
+ i599.sv599_maxnonpagedmemoryusage = i->info599->maxnonpagedmemoryusage;
+ i599.sv599_enablesoftcompat = i->info599->enablesoftcompat;
+ i599.sv599_enableforcedlogoff = i->info599->enableforcedlogoff;
+ i599.sv599_timesource = i->info599->timesource;
+ i599.sv599_acceptdownlevelapis = i->info599->acceptdownlevelapis;
+ i599.sv599_lmannounce = i->info599->lmannounce;
+ i599.sv599_domain = talloc_strdup(mem_ctx, i->info599->domain);
+ i599.sv599_maxcopyreadlen = i->info599->maxcopyreadlen;
+ i599.sv599_maxcopywritelen = i->info599->maxcopywritelen;
+ i599.sv599_minkeepsearch = i->info599->minkeepsearch;
+ i599.sv599_maxkeepsearch = 0; /* ?? */
+ i599.sv599_minkeepcomplsearch = i->info599->minkeepcomplsearch;
+ i599.sv599_maxkeepcomplsearch = i->info599->maxkeepcomplsearch;
+ i599.sv599_threadcountadd = i->info599->threadcountadd;
+ i599.sv599_numblockthreads = i->info599->numlockthreads; /* typo ? */
+ i599.sv599_scavtimeout = i->info599->scavtimeout;
+ i599.sv599_minrcvqueue = i->info599->minrcvqueue;
+ i599.sv599_minfreeworkitems = i->info599->minfreeworkitems;
+ i599.sv599_xactmemsize = i->info599->xactmemsize;
+ i599.sv599_threadpriority = i->info599->threadpriority;
+ i599.sv599_maxmpxct = i->info599->maxmpxct;
+ i599.sv599_oplockbreakwait = i->info599->oplockbreakwait;
+ i599.sv599_oplockbreakresponsewait = i->info599->oplockbreakresponsewait;
+ i599.sv599_enableoplocks = i->info599->enableoplocks;
+ i599.sv599_enableoplockforceclose = i->info599->enableoplockforceclose;
+ i599.sv599_enablefcbopens = i->info599->enablefcbopens;
+ i599.sv599_enableraw = i->info599->enableraw;
+ i599.sv599_enablesharednetdrives = i->info599->enablesharednetdrives;
+ i599.sv599_minfreeconnections = i->info599->minfreeconnections;
+ i599.sv599_maxfreeconnections = i->info599->maxfreeconnections;
+ i599.sv599_initsesstable = i->info599->initsesstable;
+ i599.sv599_initconntable = i->info599->initconntable;
+ i599.sv599_initfiletable = i->info599->initfiletable;
+ i599.sv599_initsearchtable = i->info599->initsearchtable;
+ i599.sv599_alertschedule = i->info599->alertsched;
+ i599.sv599_errorthreshold = i->info599->errortreshold;
+ i599.sv599_networkerrorthreshold = i->info599->networkerrortreshold;
+ i599.sv599_diskspacethreshold = i->info599->diskspacetreshold;
+ i599.sv599_reserved = i->info599->reserved;
+ i599.sv599_maxlinkdelay = i->info599->maxlinkdelay;
+ i599.sv599_minlinkthroughput = i->info599->minlinkthroughput;
+ i599.sv599_linkinfovalidtime = i->info599->linkinfovalidtime;
+ i599.sv599_scavqosinfoupdatetime = i->info599->scavqosinfoupdatetime;
+ i599.sv599_maxworkitemidletime = i->info599->maxworkitemidletime;
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_599, i599,
+ (struct SERVER_INFO_599 **)buffer,
+ &num_info);
+ break;
+
+ case 1005:
+ i1005.sv1005_comment = talloc_strdup(mem_ctx, i->info1005->comment);
+
+ ADD_TO_ARRAY(mem_ctx, struct SERVER_INFO_1005, i1005,
+ (struct SERVER_INFO_1005 **)buffer,
+ &num_info);
+ break;
+ default:
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetServerGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetServerGetInfo *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ union srvsvc_NetSrvInfo info;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 100:
+ case 101:
+ case 102:
+ case 402:
+ case 502:
+ case 503:
+ case 1005:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetSrvGetInfo(b, talloc_tos(),
+ r->in.server_name,
+ r->in.level,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = map_server_info_to_SERVER_INFO_buffer(ctx, r->in.level, &info,
+ r->out.buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR NetServerSetInfo_l_1005(struct libnetapi_ctx *ctx,
+ struct NetServerSetInfo *r)
+{
+ WERROR werr = WERR_OK;
+ sbcErr err;
+ struct smbconf_ctx *conf_ctx;
+ struct srvsvc_NetSrvInfo1005 *info1005;
+
+ if (!r->in.buffer) {
+ *r->out.parm_error = 1005; /* sure here ? */
+ return WERR_INVALID_PARAMETER;
+ }
+
+ info1005 = (struct srvsvc_NetSrvInfo1005 *)r->in.buffer;
+
+ if (!info1005->comment) {
+ *r->out.parm_error = 1005;
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!lp_config_backend_is_registry()) {
+ libnetapi_set_error_string(ctx,
+ "Configuration manipulation requested but not "
+ "supported by backend");
+ return WERR_NOT_SUPPORTED;
+ }
+
+ err = smbconf_init_reg(ctx, &conf_ctx, NULL);
+ if (!SBC_ERROR_IS_OK(err)) {
+ libnetapi_set_error_string(ctx,
+ "Could not initialize backend: %s",
+ sbcErrorString(err));
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(conf_ctx, "server string",
+ info1005->comment);
+ if (!SBC_ERROR_IS_OK(err)) {
+ libnetapi_set_error_string(ctx,
+ "Could not set global parameter: %s",
+ sbcErrorString(err));
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ done:
+ smbconf_shutdown(conf_ctx);
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetServerSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetServerSetInfo *r)
+{
+ switch (r->in.level) {
+ case 1005:
+ return NetServerSetInfo_l_1005(ctx, r);
+ default:
+ break;
+ }
+
+ return WERR_INVALID_LEVEL;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetServerSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetServerSetInfo *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ union srvsvc_NetSrvInfo info;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ switch (r->in.level) {
+ case 1005:
+ info.info1005 = (struct srvsvc_NetSrvInfo1005 *)r->in.buffer;
+ break;
+ default:
+ werr = WERR_NOT_SUPPORTED;
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetSrvSetInfo(b, talloc_tos(),
+ r->in.server_name,
+ r->in.level,
+ &info,
+ r->out.parm_error,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetRemoteTOD_r(struct libnetapi_ctx *ctx,
+ struct NetRemoteTOD *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct srvsvc_NetRemoteTODInfo *info = NULL;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetRemoteTOD(b, talloc_tos(),
+ r->in.server_name,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ *r->out.buffer = (uint8_t *)talloc_memdup(ctx, info,
+ sizeof(struct srvsvc_NetRemoteTODInfo));
+ W_ERROR_HAVE_NO_MEMORY(*r->out.buffer);
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetRemoteTOD_l(struct libnetapi_ctx *ctx,
+ struct NetRemoteTOD *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetRemoteTOD);
+}
+
diff --git a/source3/lib/netapi/share.c b/source3/lib/netapi/share.c
new file mode 100644
index 0000000..7f142fc
--- /dev/null
+++ b/source3/lib/netapi/share.c
@@ -0,0 +1,573 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Share Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS map_srvsvc_share_info_to_SHARE_INFO_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ union srvsvc_NetShareInfo *info,
+ uint8_t **buffer,
+ uint32_t *num_shares)
+{
+ struct SHARE_INFO_0 i0;
+ struct SHARE_INFO_1 i1;
+ struct SHARE_INFO_2 i2;
+ struct SHARE_INFO_501 i501;
+ struct SHARE_INFO_1005 i1005;
+
+ struct srvsvc_NetShareInfo0 *s0;
+ struct srvsvc_NetShareInfo1 *s1;
+ struct srvsvc_NetShareInfo2 *s2;
+ struct srvsvc_NetShareInfo501 *s501;
+ struct srvsvc_NetShareInfo1005 *s1005;
+
+ if (!buffer) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (level) {
+ case 0:
+ s0 = info->info0;
+
+ i0.shi0_netname = talloc_strdup(mem_ctx, s0->name);
+
+ ADD_TO_ARRAY(mem_ctx, struct SHARE_INFO_0, i0,
+ (struct SHARE_INFO_0 **)buffer,
+ num_shares);
+ break;
+
+ case 1:
+ s1 = info->info1;
+
+ i1.shi1_netname = talloc_strdup(mem_ctx, s1->name);
+ i1.shi1_type = s1->type;
+ i1.shi1_remark = talloc_strdup(mem_ctx, s1->comment);
+
+ ADD_TO_ARRAY(mem_ctx, struct SHARE_INFO_1, i1,
+ (struct SHARE_INFO_1 **)buffer,
+ num_shares);
+ break;
+
+ case 2:
+ s2 = info->info2;
+
+ i2.shi2_netname = talloc_strdup(mem_ctx, s2->name);
+ i2.shi2_type = s2->type;
+ i2.shi2_remark = talloc_strdup(mem_ctx, s2->comment);
+ i2.shi2_permissions = s2->permissions;
+ i2.shi2_max_uses = s2->max_users;
+ i2.shi2_current_uses = s2->current_users;
+ i2.shi2_path = talloc_strdup(mem_ctx, s2->path);
+ i2.shi2_passwd = talloc_strdup(mem_ctx, s2->password);
+
+ ADD_TO_ARRAY(mem_ctx, struct SHARE_INFO_2, i2,
+ (struct SHARE_INFO_2 **)buffer,
+ num_shares);
+ break;
+
+ case 501:
+ s501 = info->info501;
+
+ i501.shi501_netname = talloc_strdup(mem_ctx, s501->name);
+ i501.shi501_type = s501->type;
+ i501.shi501_remark = talloc_strdup(mem_ctx, s501->comment);
+ i501.shi501_flags = s501->csc_policy;
+
+ ADD_TO_ARRAY(mem_ctx, struct SHARE_INFO_501, i501,
+ (struct SHARE_INFO_501 **)buffer,
+ num_shares);
+ break;
+
+ case 1005:
+ s1005 = info->info1005;
+
+ i1005.shi1005_flags = s1005->dfs_flags;
+
+ ADD_TO_ARRAY(mem_ctx, struct SHARE_INFO_1005, i1005,
+ (struct SHARE_INFO_1005 **)buffer,
+ num_shares);
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS map_SHARE_INFO_buffer_to_srvsvc_share_info(TALLOC_CTX *mem_ctx,
+ uint8_t *buffer,
+ uint32_t level,
+ union srvsvc_NetShareInfo *info)
+{
+ struct SHARE_INFO_2 *i2 = NULL;
+ struct SHARE_INFO_502 *i502 = NULL;
+ struct SHARE_INFO_1004 *i1004 = NULL;
+ struct srvsvc_NetShareInfo2 *s2 = NULL;
+ struct srvsvc_NetShareInfo502 *s502 = NULL;
+ struct srvsvc_NetShareInfo1004 *s1004 = NULL;
+
+ if (!buffer) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (level) {
+ case 2:
+ i2 = (struct SHARE_INFO_2 *)buffer;
+
+ s2 = talloc(mem_ctx, struct srvsvc_NetShareInfo2);
+ NT_STATUS_HAVE_NO_MEMORY(s2);
+
+ s2->name = i2->shi2_netname;
+ s2->type = i2->shi2_type;
+ s2->comment = i2->shi2_remark;
+ s2->permissions = i2->shi2_permissions;
+ s2->max_users = i2->shi2_max_uses;
+ s2->current_users = i2->shi2_current_uses;
+ s2->path = i2->shi2_path;
+ s2->password = i2->shi2_passwd;
+
+ info->info2 = s2;
+
+ break;
+
+ case 502:
+ i502 = (struct SHARE_INFO_502 *)buffer;
+
+ s502 = talloc(mem_ctx, struct srvsvc_NetShareInfo502);
+ NT_STATUS_HAVE_NO_MEMORY(s502);
+
+ s502->name = i502->shi502_netname;
+ s502->type = i502->shi502_type;
+ s502->comment = i502->shi502_remark;
+ s502->permissions = i502->shi502_permissions;
+ s502->max_users = i502->shi502_max_uses;
+ s502->current_users = i502->shi502_current_uses;
+ s502->path = i502->shi502_path;
+ s502->password = i502->shi502_passwd;
+ s502->sd_buf.sd_size =
+ ndr_size_security_descriptor(i502->shi502_security_descriptor, 0);
+ s502->sd_buf.sd = i502->shi502_security_descriptor;
+
+ info->info502 = s502;
+
+ break;
+
+ case 1004:
+ i1004 = (struct SHARE_INFO_1004 *)buffer;
+
+ s1004 = talloc(mem_ctx, struct srvsvc_NetShareInfo1004);
+ NT_STATUS_HAVE_NO_MEMORY(s1004);
+
+ s1004->comment = i1004->shi1004_remark;
+
+ info->info1004 = s1004;
+
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareAdd_r(struct libnetapi_ctx *ctx,
+ struct NetShareAdd *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ union srvsvc_NetShareInfo info;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 2:
+ case 502:
+ break;
+ case 503:
+ return WERR_NOT_SUPPORTED;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = map_SHARE_INFO_buffer_to_srvsvc_share_info(ctx,
+ r->in.buffer,
+ r->in.level,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetShareAdd(b, talloc_tos(),
+ r->in.server_name,
+ r->in.level,
+ &info,
+ r->out.parm_err,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareAdd_l(struct libnetapi_ctx *ctx,
+ struct NetShareAdd *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetShareAdd);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareDel_r(struct libnetapi_ctx *ctx,
+ struct NetShareDel *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->in.net_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetShareDel(b, talloc_tos(),
+ r->in.server_name,
+ r->in.net_name,
+ r->in.reserved,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareDel_l(struct libnetapi_ctx *ctx,
+ struct NetShareDel *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetShareDel);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareEnum_r(struct libnetapi_ctx *ctx,
+ struct NetShareEnum *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ struct srvsvc_NetShareInfoCtr info_ctr;
+ struct srvsvc_NetShareCtr0 ctr0;
+ struct srvsvc_NetShareCtr1 ctr1;
+ struct srvsvc_NetShareCtr2 ctr2;
+ uint32_t i;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ case 2:
+ break;
+ case 502:
+ case 503:
+ return WERR_NOT_SUPPORTED;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ ZERO_STRUCT(info_ctr);
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ info_ctr.level = r->in.level;
+ switch (r->in.level) {
+ case 0:
+ ZERO_STRUCT(ctr0);
+ info_ctr.ctr.ctr0 = &ctr0;
+ break;
+ case 1:
+ ZERO_STRUCT(ctr1);
+ info_ctr.ctr.ctr1 = &ctr1;
+ break;
+ case 2:
+ ZERO_STRUCT(ctr2);
+ info_ctr.ctr.ctr2 = &ctr2;
+ break;
+ }
+
+ status = dcerpc_srvsvc_NetShareEnumAll(b, talloc_tos(),
+ r->in.server_name,
+ &info_ctr,
+ r->in.prefmaxlen,
+ r->out.total_entries,
+ r->out.resume_handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr) && !W_ERROR_EQUAL(werr, WERR_MORE_DATA)) {
+ goto done;
+ }
+
+ for (i=0; i < info_ctr.ctr.ctr1->count; i++) {
+ union srvsvc_NetShareInfo _i = {0};
+ switch (r->in.level) {
+ case 0:
+ _i.info0 = &info_ctr.ctr.ctr0->array[i];
+ break;
+ case 1:
+ _i.info1 = &info_ctr.ctr.ctr1->array[i];
+ break;
+ case 2:
+ _i.info2 = &info_ctr.ctr.ctr2->array[i];
+ break;
+ }
+
+ status = map_srvsvc_share_info_to_SHARE_INFO_buffer(ctx,
+ r->in.level,
+ &_i,
+ r->out.buffer,
+ r->out.entries_read);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareEnum_l(struct libnetapi_ctx *ctx,
+ struct NetShareEnum *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetShareEnum);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetShareGetInfo *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ union srvsvc_NetShareInfo info;
+ uint32_t num_entries = 0;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->in.net_name || !r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ case 2:
+ case 501:
+ case 1005:
+ break;
+ case 502:
+ case 503:
+ return WERR_NOT_SUPPORTED;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetShareGetInfo(b, talloc_tos(),
+ r->in.server_name,
+ r->in.net_name,
+ r->in.level,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = map_srvsvc_share_info_to_SHARE_INFO_buffer(ctx,
+ r->in.level,
+ &info,
+ r->out.buffer,
+ &num_entries);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetShareGetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetShareGetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetShareSetInfo *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ union srvsvc_NetShareInfo info;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 2:
+ case 1004:
+ break;
+ case 1:
+ case 502:
+ case 503:
+ case 1005:
+ case 1006:
+ case 1501:
+ return WERR_NOT_SUPPORTED;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_srvsvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = map_SHARE_INFO_buffer_to_srvsvc_share_info(ctx,
+ r->in.buffer,
+ r->in.level,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_srvsvc_NetShareSetInfo(b, talloc_tos(),
+ r->in.server_name,
+ r->in.net_name,
+ r->in.level,
+ &info,
+ r->out.parm_err,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShareSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetShareSetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetShareSetInfo);
+}
diff --git a/source3/lib/netapi/shutdown.c b/source3/lib/netapi/shutdown.c
new file mode 100644
index 0000000..9e1e8e1
--- /dev/null
+++ b/source3/lib/netapi/shutdown.c
@@ -0,0 +1,110 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Shutdown Support
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "../librpc/gen_ndr/ndr_initshutdown_c.h"
+#include "rpc_client/init_lsa.h"
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShutdownInit_r(struct libnetapi_ctx *ctx,
+ struct NetShutdownInit *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ struct lsa_StringLarge message;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_initshutdown,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_StringLarge(&message, r->in.message);
+
+ status = dcerpc_initshutdown_Init(b, talloc_tos(),
+ NULL,
+ &message,
+ r->in.timeout,
+ r->in.force_apps,
+ r->in.do_reboot,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShutdownInit_l(struct libnetapi_ctx *ctx,
+ struct NetShutdownInit *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetShutdownInit);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShutdownAbort_r(struct libnetapi_ctx *ctx,
+ struct NetShutdownAbort *r)
+{
+ WERROR werr;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b;
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_initshutdown,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_initshutdown_Abort(b, talloc_tos(),
+ NULL,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetShutdownAbort_l(struct libnetapi_ctx *ctx,
+ struct NetShutdownAbort *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetShutdownAbort);
+}
diff --git a/source3/lib/netapi/sid.c b/source3/lib/netapi/sid.c
new file mode 100644
index 0000000..ba22374
--- /dev/null
+++ b/source3/lib/netapi/sid.c
@@ -0,0 +1,71 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "lib/netapi/netapi.h"
+#include "../libcli/security/security.h"
+
+/****************************************************************
+****************************************************************/
+
+int ConvertSidToStringSid(const struct domsid *sid,
+ char **sid_string)
+{
+ struct dom_sid_buf buf;
+
+ if (!sid || !sid_string) {
+ return false;
+ }
+
+ *sid_string = SMB_STRDUP(
+ dom_sid_str_buf((const struct dom_sid *)sid, &buf));
+
+ if (!*sid_string) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+int ConvertStringSidToSid(const char *sid_string,
+ struct domsid **sid)
+{
+ struct dom_sid _sid;
+
+ if (!sid_string || !sid) {
+ return false;
+ }
+
+ if (!string_to_sid(&_sid, sid_string)) {
+ return false;
+ }
+
+ *sid = (struct domsid *)SMB_MALLOC(sizeof(struct domsid));
+ if (!*sid) {
+ return false;
+ }
+
+ sid_copy((struct dom_sid*)*sid, &_sid);
+
+ return true;
+}
diff --git a/source3/lib/netapi/tests/common.c b/source3/lib/netapi/tests/common.c
new file mode 100644
index 0000000..a01a870
--- /dev/null
+++ b/source3/lib/netapi/tests/common.c
@@ -0,0 +1,116 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <popt.h>
+#include <netapi.h>
+
+#include "common.h"
+
+void popt_common_callback(poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption *opt,
+ const char *arg, const void *data)
+{
+ struct libnetapi_ctx *ctx = NULL;
+
+ libnetapi_getctx(&ctx);
+
+ if (reason == POPT_CALLBACK_REASON_PRE) {
+ }
+
+ if (reason == POPT_CALLBACK_REASON_POST) {
+ }
+
+ if (!opt) {
+ return;
+ }
+ switch (opt->val) {
+ case 'U': {
+ char *puser = strdup(arg);
+ char *p = NULL;
+
+ if ((p = strchr(puser,'%'))) {
+ size_t len;
+ *p = 0;
+ libnetapi_set_username(ctx, puser);
+ libnetapi_set_password(ctx, p+1);
+ len = strlen(p+1);
+ memset(strchr(arg,'%')+1,'X',len);
+ } else {
+ libnetapi_set_username(ctx, puser);
+ }
+ free(puser);
+ break;
+ }
+ case 'd':
+ libnetapi_set_debuglevel(ctx, arg);
+ break;
+ case 'p':
+ libnetapi_set_password(ctx, arg);
+ break;
+ case 'k':
+ libnetapi_set_use_kerberos(ctx);
+ break;
+ }
+}
+
+struct poptOption popt_common_netapi_examples[] = {
+ {
+ .argInfo = POPT_ARG_CALLBACK|POPT_CBFLAG_PRE|POPT_CBFLAG_POST,
+ .arg = (void *)popt_common_callback,
+ },
+ {
+ .longName = "user",
+ .shortName = 'U',
+ .argInfo = POPT_ARG_STRING,
+ .val = 'U',
+ .descrip = "Username used for connection",
+ .argDescrip = "USERNAME",
+ },
+ {
+ .longName = "password",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_STRING,
+ .val = 'p',
+ .descrip = "Password used for connection",
+ .argDescrip = "PASSWORD",
+ },
+ {
+ .longName = "debuglevel",
+ .shortName = 'd',
+ .argInfo = POPT_ARG_STRING,
+ .val = 'd',
+ .descrip = "Debuglevel",
+ .argDescrip = "DEBUGLEVEL",
+ },
+ {
+ .longName = "kerberos",
+ .shortName = 'k',
+ .argInfo = POPT_ARG_NONE,
+ .val = 'k',
+ .descrip = "Use Kerberos",
+ },
+ POPT_TABLEEND
+};
+
diff --git a/source3/lib/netapi/tests/common.h b/source3/lib/netapi/tests/common.h
new file mode 100644
index 0000000..be1a2e4
--- /dev/null
+++ b/source3/lib/netapi/tests/common.h
@@ -0,0 +1,67 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <popt.h>
+
+void popt_common_callback(poptContext con,
+ enum poptCallbackReason reason,
+ const struct poptOption *opt,
+ const char *arg, const void *data);
+
+extern struct poptOption popt_common_netapi_examples[];
+
+#define POPT_COMMON_LIBNETAPI_EXAMPLES { NULL, 0, POPT_ARG_INCLUDE_TABLE, popt_common_netapi_examples, 0, "Common samba netapi example options:", NULL },
+
+#ifndef POPT_TABLEEND
+#define POPT_TABLEEND { NULL, '\0', 0, 0, 0, NULL, NULL }
+#endif
+
+NET_API_STATUS test_netuseradd(const char *hostname,
+ const char *username);
+
+NET_API_STATUS netapitest_localgroup(struct libnetapi_ctx *ctx,
+ const char *hostname);
+NET_API_STATUS netapitest_user(struct libnetapi_ctx *ctx,
+ const char *hostname);
+NET_API_STATUS netapitest_group(struct libnetapi_ctx *ctx,
+ const char *hostname);
+NET_API_STATUS netapitest_display(struct libnetapi_ctx *ctx,
+ const char *hostname);
+NET_API_STATUS netapitest_share(struct libnetapi_ctx *ctx,
+ const char *hostname);
+NET_API_STATUS netapitest_file(struct libnetapi_ctx *ctx,
+ const char *hostname);
+NET_API_STATUS netapitest_server(struct libnetapi_ctx *ctx,
+ const char *hostname);
+NET_API_STATUS netapitest_wksta(struct libnetapi_ctx *ctx,
+ const char *hostname);
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+#endif
+
+#define NETAPI_STATUS(x,y,fn) \
+ printf("FAILURE: line %d: %s failed with status: %s (%d)\n", \
+ __LINE__, fn, libnetapi_get_error_string(x,y), y);
+
+#define NETAPI_STATUS_MSG(x,y,fn,z) \
+ printf("FAILURE: line %d: %s failed with status: %s (%d), %s\n", \
+ __LINE__, fn, libnetapi_get_error_string(x,y), y, z);
+
+#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x))
diff --git a/source3/lib/netapi/tests/netapitest.c b/source3/lib/netapi/tests/netapitest.c
new file mode 100644
index 0000000..fd367bd
--- /dev/null
+++ b/source3/lib/netapi/tests/netapitest.c
@@ -0,0 +1,112 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+int main(int argc, const char **argv)
+{
+ NET_API_STATUS status = 0;
+ struct libnetapi_ctx *ctx = NULL;
+ const char *hostname = NULL;
+
+ poptContext pc;
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_LIBNETAPI_EXAMPLES
+ POPT_TABLEEND
+ };
+
+ status = libnetapi_init(&ctx);
+ if (status != 0) {
+ return status;
+ }
+
+ pc = poptGetContext("netapitest", argc, argv, long_options, 0);
+
+ poptSetOtherOptionHelp(pc, "hostname");
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ }
+
+ if (!poptPeekArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ goto out;
+ }
+ hostname = poptGetArg(pc);
+
+ status = netapitest_localgroup(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = netapitest_user(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = netapitest_group(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = netapitest_display(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = netapitest_share(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = netapitest_file(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = netapitest_server(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = netapitest_wksta(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ out:
+ if (status != 0) {
+ printf("testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ libnetapi_free(ctx);
+ poptFreeContext(pc);
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netdisplay.c b/source3/lib/netapi/tests/netdisplay.c
new file mode 100644
index 0000000..d7967fa
--- /dev/null
+++ b/source3/lib/netapi/tests/netdisplay.c
@@ -0,0 +1,150 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroup testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+static NET_API_STATUS test_netquerydisplayinformation(const char *hostname,
+ uint32_t level,
+ const char *name)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ int found_name = 0;
+ const char *current_name;
+ uint8_t *buffer = NULL;
+ uint32_t idx = 0;
+ int i;
+
+ struct NET_DISPLAY_USER *user = NULL;
+ struct NET_DISPLAY_GROUP *group = NULL;
+ struct NET_DISPLAY_MACHINE *machine = NULL;
+
+ printf("testing NetQueryDisplayInformation level %d\n", level);
+
+ do {
+ status = NetQueryDisplayInformation(hostname,
+ level,
+ idx,
+ 1000,
+ (uint32_t)-1,
+ &entries_read,
+ (void **)&buffer);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ switch (level) {
+ case 1:
+ user = (struct NET_DISPLAY_USER *)buffer;
+ break;
+ case 2:
+ machine = (struct NET_DISPLAY_MACHINE *)buffer;
+ break;
+ case 3:
+ group = (struct NET_DISPLAY_GROUP *)buffer;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i=0; i<entries_read; i++) {
+
+ switch (level) {
+ case 1:
+ current_name = user->usri1_name;
+ break;
+ case 2:
+ current_name = machine->usri2_name;
+ break;
+ case 3:
+ current_name = group->grpi3_name;
+ break;
+ default:
+ break;
+ }
+
+ if (name && strcasecmp(current_name, name) == 0) {
+ found_name = 1;
+ }
+
+ switch (level) {
+ case 1:
+ user++;
+ break;
+ case 2:
+ machine++;
+ break;
+ case 3:
+ group++;
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ idx += entries_read;
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ if (name && !found_name) {
+ printf("failed to get name\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+NET_API_STATUS netapitest_display(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ uint32_t levels[] = { 1, 2, 3};
+ int i;
+
+ printf("NetDisplay tests\n");
+
+ /* test enum */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+ status = test_netquerydisplayinformation(hostname, levels[i], NULL);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetQueryDisplayInformation");
+ goto out;
+ }
+ }
+
+ status = 0;
+
+ printf("NetDisplay tests succeeded\n");
+ out:
+ if (status != 0) {
+ printf("NetDisplay testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netfile.c b/source3/lib/netapi/tests/netfile.c
new file mode 100644
index 0000000..87aff02
--- /dev/null
+++ b/source3/lib/netapi/tests/netfile.c
@@ -0,0 +1,138 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetFile testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+static NET_API_STATUS test_netfileenum(const char *hostname,
+ uint32_t level)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ uint8_t *buffer = NULL;
+ int i;
+
+ printf("testing NetFileEnum level %d\n", level);
+
+ do {
+ status = NetFileEnum(hostname,
+ NULL,
+ NULL,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ switch (level) {
+ case 2:
+ break;
+ case 3:
+ break;
+ default:
+ return -1;
+ }
+
+ for (i=0; i<entries_read; i++) {
+
+ switch (level) {
+ case 2:
+ case 3:
+ break;
+ default:
+ break;
+ }
+
+ switch (level) {
+ case 2:
+ break;
+ case 3:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ return 0;
+}
+
+NET_API_STATUS netapitest_file(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ uint32_t enum_levels[] = { 2, 3 };
+ int i;
+
+ printf("NetFile tests\n");
+
+ /* test enum */
+
+ for (i=0; i<ARRAY_SIZE(enum_levels); i++) {
+
+ status = test_netfileenum(hostname, enum_levels[i]);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetFileEnum");
+ goto out;
+ }
+ }
+
+ /* basic queries */
+#if 0
+ {
+ uint32_t levels[] = { 2, 3 };
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+ uint8_t *buffer = NULL;
+
+ printf("testing NetFileGetInfo level %d\n", levels[i]);
+
+ status = NetFileGetInfo(hostname, fid, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetFileGetInfo");
+ goto out;
+ }
+ }
+ }
+#endif
+
+ status = 0;
+
+ printf("NetFile tests succeeded\n");
+ out:
+ if (status != 0) {
+ printf("NetFile testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netgroup.c b/source3/lib/netapi/tests/netgroup.c
new file mode 100644
index 0000000..5180c49
--- /dev/null
+++ b/source3/lib/netapi/tests/netgroup.c
@@ -0,0 +1,503 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetGroup testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+static NET_API_STATUS test_netgroupenum(const char *hostname,
+ uint32_t level,
+ const char *groupname)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int found_group = 0;
+ const char *current_name = NULL;
+ uint8_t *buffer = NULL;
+ int i;
+
+ struct GROUP_INFO_0 *info0 = NULL;
+ struct GROUP_INFO_1 *info1 = NULL;
+ struct GROUP_INFO_2 *info2 = NULL;
+ struct GROUP_INFO_3 *info3 = NULL;
+
+ printf("testing NetGroupEnum level %d\n", level);
+
+ do {
+ status = NetGroupEnum(hostname,
+ level,
+ &buffer,
+ 120, /*(uint32_t)-1, */
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ switch (level) {
+ case 0:
+ info0 = (struct GROUP_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct GROUP_INFO_1 *)buffer;
+ break;
+ case 2:
+ info2 = (struct GROUP_INFO_2 *)buffer;
+ break;
+ case 3:
+ info3 = (struct GROUP_INFO_3 *)buffer;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i=0; i<entries_read; i++) {
+
+ switch (level) {
+ case 0:
+ current_name = info0->grpi0_name;
+ break;
+ case 1:
+ current_name = info1->grpi1_name;
+ break;
+ case 2:
+ current_name = info2->grpi2_name;
+ break;
+ case 3:
+ current_name = info3->grpi3_name;
+ break;
+ default:
+ break;
+ }
+
+ if (strcasecmp(current_name, groupname) == 0) {
+ found_group = 1;
+ }
+
+ switch (level) {
+ case 0:
+ info0++;
+ break;
+ case 1:
+ info1++;
+ break;
+ case 2:
+ info2++;
+ break;
+ case 3:
+ info3++;
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ if (!found_group) {
+ printf("failed to get group\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static NET_API_STATUS test_netgroupgetusers(const char *hostname,
+ uint32_t level,
+ const char *groupname,
+ const char *username)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int found_user = 0;
+ const char *current_name = NULL;
+ uint8_t *buffer = NULL;
+ int i;
+
+ struct GROUP_USERS_INFO_0 *info0 = NULL;
+ struct GROUP_USERS_INFO_1 *info1 = NULL;
+
+ printf("testing NetGroupGetUsers level %d\n", level);
+
+ do {
+ status = NetGroupGetUsers(hostname,
+ groupname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+
+ switch (level) {
+ case 0:
+ info0 = (struct GROUP_USERS_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct GROUP_USERS_INFO_1 *)buffer;
+ break;
+ default:
+ break;
+ }
+ for (i=0; i<entries_read; i++) {
+ switch (level) {
+ case 0:
+ current_name = info0->grui0_name;
+ break;
+ case 1:
+ current_name = info1->grui1_name;
+ break;
+ default:
+ break;
+ }
+
+ if (username && strcasecmp(current_name, username) == 0) {
+ found_user = 1;
+ }
+
+ switch (level) {
+ case 0:
+ info0++;
+ break;
+ case 1:
+ info1++;
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ if (username && !found_user) {
+ printf("failed to get user\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static NET_API_STATUS test_netgroupsetusers(const char *hostname,
+ const char *groupname,
+ uint32_t level,
+ size_t num_entries,
+ const char **names)
+{
+ NET_API_STATUS status;
+ uint8_t *buffer = NULL;
+ int i = 0;
+ size_t buf_size = 0;
+
+ struct GROUP_USERS_INFO_0 *g0 = NULL;
+ struct GROUP_USERS_INFO_1 *g1 = NULL;
+
+ printf("testing NetGroupSetUsers level %d\n", level);
+
+ switch (level) {
+ case 0:
+ buf_size = sizeof(struct GROUP_USERS_INFO_0) * num_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g0);
+ if (status) {
+ goto out;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ g0[i].grui0_name = names[i];
+ }
+
+ buffer = (uint8_t *)g0;
+ break;
+ case 1:
+ buf_size = sizeof(struct GROUP_USERS_INFO_1) * num_entries;
+
+ status = NetApiBufferAllocate(buf_size, (void **)&g1);
+ if (status) {
+ goto out;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ g1[i].grui1_name = names[i];
+ }
+
+ buffer = (uint8_t *)g1;
+ break;
+ default:
+ break;
+ }
+
+ /* NetGroupSetUsers */
+
+ status = NetGroupSetUsers(hostname,
+ groupname,
+ level,
+ buffer,
+ num_entries);
+ if (status) {
+ goto out;
+ }
+
+ out:
+ NetApiBufferFree(buffer);
+ return status;
+}
+
+NET_API_STATUS netapitest_group(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ const char *username, *groupname, *groupname2;
+ uint8_t *buffer = NULL;
+ struct GROUP_INFO_0 g0;
+ uint32_t parm_err = 0;
+ uint32_t levels[] = { 0, 1, 2, 3};
+ uint32_t enum_levels[] = { 0, 1, 2, 3};
+ uint32_t getmem_levels[] = { 0, 1};
+ int i;
+
+ printf("NetGroup tests\n");
+
+ username = "torture_test_user";
+ groupname = "torture_test_group";
+ groupname2 = "torture_test_group2";
+
+ /* cleanup */
+ NetGroupDel(hostname, groupname);
+ NetGroupDel(hostname, groupname2);
+ NetUserDel(hostname, username);
+
+ /* add a group */
+
+ g0.grpi0_name = groupname;
+
+ printf("testing NetGroupAdd\n");
+
+ status = NetGroupAdd(hostname, 0, (uint8_t *)&g0, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupAdd");
+ goto out;
+ }
+
+ /* 2nd add must fail */
+
+ status = NetGroupAdd(hostname, 0, (uint8_t *)&g0, &parm_err);
+ if (status == 0) {
+ NETAPI_STATUS(ctx, status, "NetGroupAdd");
+ goto out;
+ }
+
+ /* test enum */
+
+ for (i=0; i<ARRAY_SIZE(enum_levels); i++) {
+
+ status = test_netgroupenum(hostname, enum_levels[i], groupname);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupEnum");
+ goto out;
+ }
+ }
+
+ /* basic queries */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+ printf("testing NetGroupGetInfo level %d\n", levels[i]);
+
+ status = NetGroupGetInfo(hostname, groupname, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetGroupGetInfo");
+ goto out;
+ }
+ }
+
+ /* group rename */
+
+ g0.grpi0_name = groupname2;
+
+ printf("testing NetGroupSetInfo level 0\n");
+
+ status = NetGroupSetInfo(hostname, groupname, 0, (uint8_t *)&g0, &parm_err);
+ switch ((int)status) {
+ case 0:
+ break;
+ case 50: /* not supported */
+ case 124: /* not implemented */
+ groupname2 = groupname;
+ goto skip_rename;
+ default:
+ NETAPI_STATUS(ctx, status, "NetGroupSetInfo");
+ goto out;
+ }
+
+ /* should not exist anymore */
+
+ status = NetGroupDel(hostname, groupname);
+ if (status == 0) {
+ NETAPI_STATUS(ctx, status, "NetGroupDel");
+ goto out;
+ }
+
+ skip_rename:
+ /* query info */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+ status = NetGroupGetInfo(hostname, groupname2, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetGroupGetInfo");
+ goto out;
+ }
+ }
+
+ /* add user to group */
+
+ status = test_netuseradd(hostname, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserAdd");
+ goto out;
+ }
+
+ /* should not be member */
+
+ for (i=0; i<ARRAY_SIZE(getmem_levels); i++) {
+
+ status = test_netgroupgetusers(hostname, getmem_levels[i], groupname2, NULL);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupGetUsers");
+ goto out;
+ }
+ }
+
+ printf("testing NetGroupAddUser\n");
+
+ status = NetGroupAddUser(hostname, groupname2, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupAddUser");
+ goto out;
+ }
+
+ /* should be member */
+
+ for (i=0; i<ARRAY_SIZE(getmem_levels); i++) {
+
+ status = test_netgroupgetusers(hostname, getmem_levels[i], groupname2, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupGetUsers");
+ goto out;
+ }
+ }
+
+ printf("testing NetGroupDelUser\n");
+
+ status = NetGroupDelUser(hostname, groupname2, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupDelUser");
+ goto out;
+ }
+
+ /* should not be member */
+
+ status = test_netgroupgetusers(hostname, 0, groupname2, NULL);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupGetUsers");
+ goto out;
+ }
+
+ /* set it again via explicit member set */
+
+ status = test_netgroupsetusers(hostname, groupname2, 0, 1, &username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupSetUsers");
+ goto out;
+ }
+
+ /* should be member */
+
+ status = test_netgroupgetusers(hostname, 0, groupname2, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupGetUsers");
+ goto out;
+ }
+#if 0
+ /* wipe out member list */
+
+ status = test_netgroupsetusers(hostname, groupname2, 0, 0, NULL);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupSetUsers");
+ goto out;
+ }
+
+ /* should not be member */
+
+ status = test_netgroupgetusers(hostname, 0, groupname2, NULL);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupGetUsers");
+ goto out;
+ }
+#endif
+ status = NetUserDel(hostname, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserDel");
+ goto out;
+ }
+
+ /* delete */
+
+ printf("testing NetGroupDel\n");
+
+ status = NetGroupDel(hostname, groupname2);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetGroupDel");
+ goto out;
+ };
+
+ /* should not exist anymore */
+
+ status = NetGroupGetInfo(hostname, groupname2, 0, &buffer);
+ if (status == 0) {
+ NETAPI_STATUS_MSG(ctx, status, "NetGroupGetInfo", "expected failure and error code");
+ goto out;
+ };
+
+ status = 0;
+
+ printf("NetGroup tests succeeded\n");
+ out:
+ if (status != 0) {
+ printf("NetGroup testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netlocalgroup.c b/source3/lib/netapi/tests/netlocalgroup.c
new file mode 100644
index 0000000..76c59c8
--- /dev/null
+++ b/source3/lib/netapi/tests/netlocalgroup.c
@@ -0,0 +1,226 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetLocalGroup testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+static NET_API_STATUS test_netlocalgroupenum(const char *hostname,
+ uint32_t level,
+ const char *groupname)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int found_group = 0;
+ const char *current_name = NULL;
+ uint8_t *buffer = NULL;
+ int i;
+
+ struct LOCALGROUP_INFO_0 *info0 = NULL;
+ struct LOCALGROUP_INFO_1 *info1 = NULL;
+
+ printf("testing NetLocalGroupEnum level %d\n", level);
+
+ do {
+ status = NetLocalGroupEnum(hostname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ switch (level) {
+ case 0:
+ info0 = (struct LOCALGROUP_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct LOCALGROUP_INFO_1 *)buffer;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i=0; i<entries_read; i++) {
+
+ switch (level) {
+ case 0:
+ current_name = info0->lgrpi0_name;
+ break;
+ case 1:
+ current_name = info1->lgrpi1_name;
+ break;
+ default:
+ break;
+ }
+
+ if (strcasecmp(current_name, groupname) == 0) {
+ found_group = 1;
+ }
+
+ switch (level) {
+ case 0:
+ info0++;
+ break;
+ case 1:
+ info1++;
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ if (!found_group) {
+ printf("failed to get group\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+NET_API_STATUS netapitest_localgroup(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ const char *groupname, *groupname2;
+ uint8_t *buffer = NULL;
+ struct LOCALGROUP_INFO_0 g0;
+ uint32_t parm_err = 0;
+ uint32_t levels[] = { 0, 1, 1002 };
+ uint32_t enum_levels[] = { 0, 1 };
+ int i;
+
+ printf("NetLocalgroup tests\n");
+
+ groupname = "torture_test_localgroup";
+ groupname2 = "torture_test_localgroup2";
+
+ /* cleanup */
+ NetLocalGroupDel(hostname, groupname);
+ NetLocalGroupDel(hostname, groupname2);
+
+ /* add a localgroup */
+
+ printf("testing NetLocalGroupAdd\n");
+
+ g0.lgrpi0_name = groupname;
+
+ status = NetLocalGroupAdd(hostname, 0, (uint8_t *)&g0, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupAdd");
+ goto out;
+ };
+
+ /* test enum */
+
+ for (i=0; i<ARRAY_SIZE(enum_levels); i++) {
+
+ status = test_netlocalgroupenum(hostname, enum_levels[i], groupname);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupEnum");
+ goto out;
+ }
+ }
+
+
+ /* basic queries */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+ printf("testing NetLocalGroupGetInfo level %d\n", levels[i]);
+
+ status = NetLocalGroupGetInfo(hostname, groupname, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupGetInfo");
+ goto out;
+ };
+ }
+
+ /* alias rename */
+
+ printf("testing NetLocalGroupSetInfo level 0\n");
+
+ g0.lgrpi0_name = groupname2;
+
+ status = NetLocalGroupSetInfo(hostname, groupname, 0, (uint8_t *)&g0, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupSetInfo");
+ goto out;
+ };
+
+ /* should not exist anymore */
+
+ status = NetLocalGroupDel(hostname, groupname);
+ if (status == 0) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupDel");
+ goto out;
+ };
+
+ /* query info */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+ status = NetLocalGroupGetInfo(hostname, groupname2, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupGetInfo");
+ goto out;
+ };
+ }
+
+ /* delete */
+
+ printf("testing NetLocalGroupDel\n");
+
+ status = NetLocalGroupDel(hostname, groupname2);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupDel");
+ goto out;
+ };
+
+ /* should not exist anymore */
+
+ status = NetLocalGroupGetInfo(hostname, groupname2, 0, &buffer);
+ if (status == 0) {
+ NETAPI_STATUS(ctx, status, "NetLocalGroupGetInfo");
+ goto out;
+ };
+
+ status = 0;
+
+ printf("NetLocalgroup tests succeeded\n");
+ out:
+ if (status != 0) {
+ printf("NetLocalGroup testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netserver.c b/source3/lib/netapi/tests/netserver.c
new file mode 100644
index 0000000..f7b9286
--- /dev/null
+++ b/source3/lib/netapi/tests/netserver.c
@@ -0,0 +1,61 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetServer testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+NET_API_STATUS netapitest_server(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ uint32_t levels[] = { 100, 101, 102, 402, 403, 502, 503, 1005 };
+ int i;
+
+ printf("NetServer tests\n");
+
+ /* basic queries */
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+ uint8_t *buffer = NULL;
+ printf("testing NetServerGetInfo level %d\n", levels[i]);
+
+ status = NetServerGetInfo(hostname, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetServerGetInfo");
+ goto out;
+ }
+ }
+
+ status = 0;
+
+ printf("NetServer tests succeeded\n");
+ out:
+ if (status != 0) {
+ printf("NetServer testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netshare.c b/source3/lib/netapi/tests/netshare.c
new file mode 100644
index 0000000..a518ce9
--- /dev/null
+++ b/source3/lib/netapi/tests/netshare.c
@@ -0,0 +1,250 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetShare testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+static NET_API_STATUS test_netshareenum(const char *hostname,
+ uint32_t level,
+ const char *sharename)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ int found_share = 0;
+ const char *current_name = NULL;
+ uint8_t *buffer = NULL;
+ int i;
+
+ struct SHARE_INFO_0 *i0 = NULL;
+ struct SHARE_INFO_1 *i1 = NULL;
+ struct SHARE_INFO_2 *i2 = NULL;
+
+ printf("testing NetShareEnum level %d\n", level);
+
+ do {
+ status = NetShareEnum(hostname,
+ level,
+ &buffer,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ switch (level) {
+ case 0:
+ i0 = (struct SHARE_INFO_0 *)buffer;
+ break;
+ case 1:
+ i1 = (struct SHARE_INFO_1 *)buffer;
+ break;
+ case 2:
+ i2 = (struct SHARE_INFO_2 *)buffer;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i=0; i<entries_read; i++) {
+
+ switch (level) {
+ case 0:
+ current_name = i0->shi0_netname;
+ break;
+ case 1:
+ current_name = i1->shi1_netname;
+ break;
+ case 2:
+ current_name = i2->shi2_netname;
+ break;
+ default:
+ break;
+ }
+
+ if (strcasecmp(current_name, sharename) == 0) {
+ found_share = 1;
+ }
+
+ switch (level) {
+ case 0:
+ i0++;
+ break;
+ case 1:
+ i1++;
+ break;
+ case 2:
+ i2++;
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ if (!found_share) {
+ printf("failed to get share\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+NET_API_STATUS netapitest_share(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ const char *sharename, *comment;
+ uint8_t *buffer = NULL;
+ struct SHARE_INFO_2 i2;
+ struct SHARE_INFO_502 i502;
+ struct SHARE_INFO_1004 i1004;
+ struct SHARE_INFO_501 *i501 = NULL;
+ uint32_t parm_err = 0;
+ uint32_t levels[] = { 0, 1, 2, 501, 1005 };
+ uint32_t enum_levels[] = { 0, 1, 2 };
+ int i;
+
+ printf("NetShare tests\n");
+
+ sharename = "torture_test_share";
+
+ /* cleanup */
+ NetShareDel(hostname, sharename, 0);
+
+ /* add a share */
+
+ printf("testing NetShareAdd\n");
+
+ ZERO_STRUCT(i502);
+
+ i502.shi502_netname = sharename;
+ i502.shi502_path = "c:\\";
+
+ status = NetShareAdd(hostname, 502, (uint8_t *)&i502, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetShareAdd");
+ goto out;
+ };
+
+ status = NetShareDel(hostname, sharename, 0);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetShareDel");
+ goto out;
+ };
+
+ ZERO_STRUCT(i2);
+
+ i2.shi2_netname = sharename;
+ i2.shi2_path = "c:\\";
+
+ status = NetShareAdd(hostname, 2, (uint8_t *)&i2, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetShareAdd");
+ goto out;
+ };
+
+ /* test enum */
+
+ for (i=0; i<ARRAY_SIZE(enum_levels); i++) {
+
+ status = test_netshareenum(hostname, enum_levels[i], sharename);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetShareEnum");
+ goto out;
+ }
+ }
+
+ /* basic queries */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+ printf("testing NetShareGetInfo level %d\n", levels[i]);
+
+ status = NetShareGetInfo(hostname, sharename, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetShareGetInfo");
+ goto out;
+ }
+ }
+
+
+ comment = "NetApi generated comment";
+
+ i1004.shi1004_remark = comment;
+
+ printf("testing NetShareSetInfo level 1004\n");
+
+ status = NetShareSetInfo(hostname, sharename, 1004, (uint8_t *)&i1004, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetShareSetInfo");
+ goto out;
+ }
+
+ status = NetShareGetInfo(hostname, sharename, 501, (uint8_t **)&i501);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetShareGetInfo");
+ goto out;
+ }
+
+ if (strcasecmp(i501->shi501_remark, comment) != 0) {
+ NETAPI_STATUS(ctx, status, "NetShareGetInfo");
+ goto out;
+ }
+
+ /* delete */
+
+ printf("testing NetShareDel\n");
+
+ status = NetShareDel(hostname, sharename, 0);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetShareDel");
+ goto out;
+ };
+
+ /* should not exist anymore */
+
+ status = NetShareGetInfo(hostname, sharename, 0, &buffer);
+ if (status == 0) {
+ NETAPI_STATUS(ctx, status, "NetShareGetInfo");
+ goto out;
+ };
+
+ status = 0;
+
+ printf("NetShare tests succeeded\n");
+ out:
+ if (status != 0) {
+ printf("NetShare testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netuser.c b/source3/lib/netapi/tests/netuser.c
new file mode 100644
index 0000000..ad2bb53
--- /dev/null
+++ b/source3/lib/netapi/tests/netuser.c
@@ -0,0 +1,462 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetUser testsuite
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+static NET_API_STATUS test_netuserenum(const char *hostname,
+ uint32_t level,
+ const char *username)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ const char *current_name = NULL;
+ int found_user = 0;
+ uint8_t *buffer = NULL;
+ int i;
+
+ struct USER_INFO_0 *info0 = NULL;
+ struct USER_INFO_1 *info1 = NULL;
+ struct USER_INFO_2 *info2 = NULL;
+ struct USER_INFO_3 *info3 = NULL;
+ struct USER_INFO_4 *info4 = NULL;
+ struct USER_INFO_10 *info10 = NULL;
+ struct USER_INFO_11 *info11 = NULL;
+ struct USER_INFO_20 *info20 = NULL;
+ struct USER_INFO_23 *info23 = NULL;
+
+ printf("testing NetUserEnum level %d\n", level);
+
+ do {
+ status = NetUserEnum(hostname,
+ level,
+ FILTER_NORMAL_ACCOUNT,
+ &buffer,
+ 120, /*(uint32_t)-1, */
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ switch (level) {
+ case 0:
+ info0 = (struct USER_INFO_0 *)buffer;
+ break;
+ case 1:
+ info1 = (struct USER_INFO_1 *)buffer;
+ break;
+ case 2:
+ info2 = (struct USER_INFO_2 *)buffer;
+ break;
+ case 3:
+ info3 = (struct USER_INFO_3 *)buffer;
+ break;
+ case 4:
+ info4 = (struct USER_INFO_4 *)buffer;
+ break;
+ case 10:
+ info10 = (struct USER_INFO_10 *)buffer;
+ break;
+ case 11:
+ info11 = (struct USER_INFO_11 *)buffer;
+ break;
+ case 20:
+ info20 = (struct USER_INFO_20 *)buffer;
+ break;
+ case 23:
+ info23 = (struct USER_INFO_23 *)buffer;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i=0; i<entries_read; i++) {
+
+ switch (level) {
+ case 0:
+ current_name = info0->usri0_name;
+ break;
+ case 1:
+ current_name = info1->usri1_name;
+ break;
+ case 2:
+ current_name = info2->usri2_name;
+ break;
+ case 3:
+ current_name = info3->usri3_name;
+ break;
+ case 4:
+ current_name = info4->usri4_name;
+ break;
+ case 10:
+ current_name = info10->usri10_name;
+ break;
+ case 11:
+ current_name = info11->usri11_name;
+ break;
+ case 20:
+ current_name = info20->usri20_name;
+ break;
+ case 23:
+ current_name = info23->usri23_name;
+ break;
+ default:
+ return -1;
+ }
+
+ if (strcasecmp(current_name, username) == 0) {
+ found_user = 1;
+ }
+
+ switch (level) {
+ case 0:
+ info0++;
+ break;
+ case 1:
+ info1++;
+ break;
+ case 2:
+ info2++;
+ break;
+ case 3:
+ info3++;
+ break;
+ case 4:
+ info4++;
+ break;
+ case 10:
+ info10++;
+ break;
+ case 11:
+ info11++;
+ break;
+ case 20:
+ info20++;
+ break;
+ case 23:
+ info23++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ if (!found_user) {
+ printf("failed to get user\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+NET_API_STATUS test_netuseradd(const char *hostname,
+ const char *username)
+{
+ struct USER_INFO_1 u1;
+ uint32_t parm_err = 0;
+
+ ZERO_STRUCT(u1);
+
+ printf("testing NetUserAdd\n");
+
+ u1.usri1_name = username;
+ u1.usri1_password = "W297!832jD8J";
+ u1.usri1_password_age = 0;
+ u1.usri1_priv = 0;
+ u1.usri1_home_dir = NULL;
+ u1.usri1_comment = "User created using Samba NetApi Example code";
+ u1.usri1_flags = 0;
+ u1.usri1_script_path = NULL;
+
+ return NetUserAdd(hostname, 1, (uint8_t *)&u1, &parm_err);
+}
+
+static NET_API_STATUS test_netusermodals(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status;
+ struct USER_MODALS_INFO_0 *u0 = NULL;
+ struct USER_MODALS_INFO_0 *_u0 = NULL;
+ uint8_t *buffer = NULL;
+ uint32_t parm_err = 0;
+ uint32_t levels[] = { 0, 1, 2, 3 };
+ int i = 0;
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+ printf("testing NetUserModalsGet level %d\n", levels[i]);
+
+ status = NetUserModalsGet(hostname, levels[i], &buffer);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserModalsGet");
+ return status;
+ }
+ }
+
+ status = NetUserModalsGet(hostname, 0, (uint8_t **)&u0);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserModalsGet");
+ return status;
+ }
+
+ printf("testing NetUserModalsSet\n");
+
+ status = NetUserModalsSet(hostname, 0, (uint8_t *)u0, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserModalsSet");
+ return status;
+ }
+
+ status = NetUserModalsGet(hostname, 0, (uint8_t **)&_u0);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserModalsGet");
+ return status;
+ }
+
+ if (memcmp(u0, _u0, sizeof(*u0)) != 0) {
+ printf("USER_MODALS_INFO_0 struct has changed!!!!\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static NET_API_STATUS test_netusergetgroups(const char *hostname,
+ uint32_t level,
+ const char *username,
+ const char *groupname)
+{
+ NET_API_STATUS status;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ const char *current_name;
+ int found_group = 0;
+ uint8_t *buffer = NULL;
+ int i;
+
+ struct GROUP_USERS_INFO_0 *i0 = NULL;
+ struct GROUP_USERS_INFO_1 *i1 = NULL;
+
+ printf("testing NetUserGetGroups level %d\n", level);
+
+ do {
+ status = NetUserGetGroups(hostname,
+ username,
+ level,
+ &buffer,
+ 120, /*(uint32_t)-1, */
+ &entries_read,
+ &total_entries);
+ if (status == 0 || status == ERROR_MORE_DATA) {
+ switch (level) {
+ case 0:
+ i0 = (struct GROUP_USERS_INFO_0 *)buffer;
+ break;
+ case 1:
+ i1 = (struct GROUP_USERS_INFO_1 *)buffer;
+ break;
+ default:
+ return -1;
+ }
+
+ for (i=0; i<entries_read; i++) {
+
+ switch (level) {
+ case 0:
+ current_name = i0->grui0_name;
+ break;
+ case 1:
+ current_name = i1->grui1_name;
+ break;
+ default:
+ return -1;
+ }
+
+ if (groupname && strcasecmp(current_name, groupname) == 0) {
+ found_group = 1;
+ }
+
+ switch (level) {
+ case 0:
+ i0++;
+ break;
+ case 1:
+ i1++;
+ break;
+ default:
+ break;
+ }
+ }
+ NetApiBufferFree(buffer);
+ }
+ } while (status == ERROR_MORE_DATA);
+
+ if (status) {
+ return status;
+ }
+
+ if (groupname && !found_group) {
+ printf("failed to get membership\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+NET_API_STATUS netapitest_user(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ const char *username, *username2;
+ uint8_t *buffer = NULL;
+ uint32_t levels[] = { 0, 1, 2, 3, 4, 10, 11, 20, 23 };
+ uint32_t enum_levels[] = { 0, 1, 2, 3, 4, 10, 11, 20, 23 };
+ uint32_t getgr_levels[] = { 0, 1 };
+ int i;
+
+ struct USER_INFO_1007 u1007;
+ uint32_t parm_err = 0;
+
+ printf("NetUser tests\n");
+
+ username = "torture_test_user";
+ username2 = "torture_test_user2";
+
+ /* cleanup */
+ NetUserDel(hostname, username);
+ NetUserDel(hostname, username2);
+
+ /* add a user */
+
+ status = test_netuseradd(hostname, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserAdd");
+ goto out;
+ }
+
+ /* enum the new user */
+
+ for (i=0; i<ARRAY_SIZE(enum_levels); i++) {
+
+ status = test_netuserenum(hostname, enum_levels[i], username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserEnum");
+ goto out;
+ }
+ }
+
+ /* basic queries */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+
+ printf("testing NetUserGetInfo level %d\n", levels[i]);
+
+ status = NetUserGetInfo(hostname, username, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetUserGetInfo");
+ goto out;
+ }
+ }
+
+ /* testing getgroups */
+
+ for (i=0; i<ARRAY_SIZE(getgr_levels); i++) {
+
+ status = test_netusergetgroups(hostname, getgr_levels[i], username, NULL);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserGetGroups");
+ goto out;
+ }
+ }
+
+ /* modify description */
+
+ printf("testing NetUserSetInfo level %d\n", 1007);
+
+ u1007.usri1007_comment = "NetApi modified user";
+
+ status = NetUserSetInfo(hostname, username, 1007, (uint8_t *)&u1007, &parm_err);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserSetInfo");
+ goto out;
+ }
+
+ /* query info */
+
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+ status = NetUserGetInfo(hostname, username, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetUserGetInfo");
+ goto out;
+ }
+ }
+
+ /* delete */
+
+ printf("testing NetUserDel\n");
+
+ status = NetUserDel(hostname, username);
+ if (status) {
+ NETAPI_STATUS(ctx, status, "NetUserDel");
+ goto out;
+ }
+
+ /* should not exist anymore */
+
+ status = NetUserGetInfo(hostname, username, 0, &buffer);
+ if (status == 0) {
+ NETAPI_STATUS(ctx, status, "NetUserGetInfo");
+ goto out;
+ }
+
+ status = test_netusermodals(ctx, hostname);
+ if (status) {
+ goto out;
+ }
+
+ status = 0;
+
+ printf("NetUser tests succeeded\n");
+ out:
+ /* cleanup */
+ NetUserDel(hostname, username);
+ NetUserDel(hostname, username2);
+
+ if (status != 0) {
+ printf("NetUser testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/netwksta.c b/source3/lib/netapi/tests/netwksta.c
new file mode 100644
index 0000000..2c3f57f
--- /dev/null
+++ b/source3/lib/netapi/tests/netwksta.c
@@ -0,0 +1,62 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetWorkstation testsuite
+ * Copyright (C) Guenther Deschner 2008
+ * Copyright (C) Hans Leidekker 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <netapi.h>
+
+#include "common.h"
+
+NET_API_STATUS netapitest_wksta(struct libnetapi_ctx *ctx,
+ const char *hostname)
+{
+ NET_API_STATUS status = 0;
+ uint32_t levels[] = { 100, 101, 102 };
+ int i;
+
+ printf("NetWorkstation tests\n");
+
+ /* basic queries */
+ for (i=0; i<ARRAY_SIZE(levels); i++) {
+ uint8_t *buffer = NULL;
+ printf("testing NetWkstaGetInfo level %d\n", levels[i]);
+
+ status = NetWkstaGetInfo(hostname, levels[i], &buffer);
+ if (status && status != 124) {
+ NETAPI_STATUS(ctx, status, "NetWkstaGetInfo");
+ goto out;
+ }
+ }
+
+ status = 0;
+
+ printf("NetWorkstation tests succeeded\n");
+ out:
+ if (status != 0) {
+ printf("NetWorkstation testsuite failed with: %s\n",
+ libnetapi_get_error_string(ctx, status));
+ }
+
+ return status;
+}
diff --git a/source3/lib/netapi/tests/wscript_build b/source3/lib/netapi/tests/wscript_build
new file mode 100644
index 0000000..5e37297
--- /dev/null
+++ b/source3/lib/netapi/tests/wscript_build
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+bld.SAMBA_BINARY('netapitest',
+ source='netapitest.c netlocalgroup.c netuser.c netgroup.c netdisplay.c netshare.c netfile.c netserver.c netwksta.c common.c',
+ deps='netapi popt',
+ install=False)
diff --git a/source3/lib/netapi/user.c b/source3/lib/netapi/user.c
new file mode 100644
index 0000000..a7f4c9d
--- /dev/null
+++ b/source3/lib/netapi/user.c
@@ -0,0 +1,3638 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi User Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/init_samr.h"
+#include "../libds/common/flags.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "../libds/common/flag_mapping.h"
+#include "rpc_client/cli_pipe.h"
+
+/****************************************************************
+****************************************************************/
+
+static void convert_USER_INFO_X_to_samr_user_info21(struct USER_INFO_X *infoX,
+ struct samr_UserInfo21 *info21)
+{
+ uint32_t fields_present = 0;
+ struct samr_LogonHours zero_logon_hours;
+ struct lsa_BinaryString zero_parameters;
+ NTTIME password_age;
+
+ ZERO_STRUCTP(info21);
+ ZERO_STRUCT(zero_logon_hours);
+ ZERO_STRUCT(zero_parameters);
+
+ if (infoX->usriX_flags) {
+ fields_present |= SAMR_FIELD_ACCT_FLAGS;
+ }
+ if (infoX->usriX_name) {
+ fields_present |= SAMR_FIELD_ACCOUNT_NAME;
+ }
+ if (infoX->usriX_password) {
+ fields_present |= SAMR_FIELD_NT_PASSWORD_PRESENT;
+ }
+ if (infoX->usriX_flags) {
+ fields_present |= SAMR_FIELD_ACCT_FLAGS;
+ }
+ if (infoX->usriX_home_dir) {
+ fields_present |= SAMR_FIELD_HOME_DIRECTORY;
+ }
+ if (infoX->usriX_script_path) {
+ fields_present |= SAMR_FIELD_LOGON_SCRIPT;
+ }
+ if (infoX->usriX_comment) {
+ fields_present |= SAMR_FIELD_DESCRIPTION;
+ }
+ if (infoX->usriX_password_age) {
+ fields_present |= SAMR_FIELD_EXPIRED_FLAG;
+ }
+ if (infoX->usriX_full_name) {
+ fields_present |= SAMR_FIELD_FULL_NAME;
+ }
+ if (infoX->usriX_usr_comment) {
+ fields_present |= SAMR_FIELD_COMMENT;
+ }
+ if (infoX->usriX_profile) {
+ fields_present |= SAMR_FIELD_PROFILE_PATH;
+ }
+ if (infoX->usriX_home_dir_drive) {
+ fields_present |= SAMR_FIELD_HOME_DRIVE;
+ }
+ if (infoX->usriX_primary_group_id) {
+ fields_present |= SAMR_FIELD_PRIMARY_GID;
+ }
+ if (infoX->usriX_country_code) {
+ fields_present |= SAMR_FIELD_COUNTRY_CODE;
+ }
+ if (infoX->usriX_workstations) {
+ fields_present |= SAMR_FIELD_WORKSTATIONS;
+ }
+
+ unix_to_nt_time_abs(&password_age, infoX->usriX_password_age);
+
+ /* TODO: infoX->usriX_priv */
+
+ info21->last_logon = 0;
+ info21->last_logoff = 0;
+ info21->last_password_change = 0;
+ info21->acct_expiry = 0;
+ info21->allow_password_change = 0;
+ info21->force_password_change = 0;
+ info21->account_name.string = infoX->usriX_name;
+ info21->full_name.string = infoX->usriX_full_name;
+ info21->home_directory.string = infoX->usriX_home_dir;
+ info21->home_drive.string = infoX->usriX_home_dir_drive;
+ info21->logon_script.string = infoX->usriX_script_path;
+ info21->profile_path.string = infoX->usriX_profile;
+ info21->description.string = infoX->usriX_comment;
+ info21->workstations.string = infoX->usriX_workstations;
+ info21->comment.string = infoX->usriX_usr_comment;
+ info21->parameters = zero_parameters;
+ info21->lm_owf_password = zero_parameters;
+ info21->nt_owf_password = zero_parameters;
+ info21->private_data.string = NULL;
+ info21->buf_count = 0;
+ info21->buffer = NULL;
+ info21->rid = infoX->usriX_user_id;
+ info21->primary_gid = infoX->usriX_primary_group_id;
+ info21->acct_flags = infoX->usriX_flags;
+ info21->fields_present = fields_present;
+ info21->logon_hours = zero_logon_hours;
+ info21->bad_password_count = infoX->usriX_bad_pw_count;
+ info21->logon_count = infoX->usriX_num_logons;
+ info21->country_code = infoX->usriX_country_code;
+ info21->code_page = infoX->usriX_code_page;
+ info21->lm_password_set = 0;
+ info21->nt_password_set = 0;
+ info21->password_expired = infoX->usriX_password_expired;
+ info21->private_data_sensitive = 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS construct_USER_INFO_X(uint32_t level,
+ uint8_t *buffer,
+ struct USER_INFO_X *uX)
+{
+ struct USER_INFO_0 *u0 = NULL;
+ struct USER_INFO_1 *u1 = NULL;
+ struct USER_INFO_2 *u2 = NULL;
+ struct USER_INFO_3 *u3 = NULL;
+ struct USER_INFO_1003 *u1003 = NULL;
+ struct USER_INFO_1006 *u1006 = NULL;
+ struct USER_INFO_1007 *u1007 = NULL;
+ struct USER_INFO_1009 *u1009 = NULL;
+ struct USER_INFO_1011 *u1011 = NULL;
+ struct USER_INFO_1012 *u1012 = NULL;
+ struct USER_INFO_1014 *u1014 = NULL;
+ struct USER_INFO_1024 *u1024 = NULL;
+ struct USER_INFO_1051 *u1051 = NULL;
+ struct USER_INFO_1052 *u1052 = NULL;
+ struct USER_INFO_1053 *u1053 = NULL;
+
+ if (!buffer || !uX) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCTP(uX);
+
+ switch (level) {
+ case 0:
+ u0 = (struct USER_INFO_0 *)buffer;
+ uX->usriX_name = u0->usri0_name;
+ break;
+ case 1:
+ u1 = (struct USER_INFO_1 *)buffer;
+ uX->usriX_name = u1->usri1_name;
+ uX->usriX_password = u1->usri1_password;
+ uX->usriX_password_age = u1->usri1_password_age;
+ uX->usriX_priv = u1->usri1_priv;
+ uX->usriX_home_dir = u1->usri1_home_dir;
+ uX->usriX_comment = u1->usri1_comment;
+ uX->usriX_flags = u1->usri1_flags;
+ uX->usriX_script_path = u1->usri1_script_path;
+ break;
+ case 2:
+ u2 = (struct USER_INFO_2 *)buffer;
+ uX->usriX_name = u2->usri2_name;
+ uX->usriX_password = u2->usri2_password;
+ uX->usriX_password_age = u2->usri2_password_age;
+ uX->usriX_priv = u2->usri2_priv;
+ uX->usriX_home_dir = u2->usri2_home_dir;
+ uX->usriX_comment = u2->usri2_comment;
+ uX->usriX_flags = u2->usri2_flags;
+ uX->usriX_script_path = u2->usri2_script_path;
+ uX->usriX_auth_flags = u2->usri2_auth_flags;
+ uX->usriX_full_name = u2->usri2_full_name;
+ uX->usriX_usr_comment = u2->usri2_usr_comment;
+ uX->usriX_parms = u2->usri2_parms;
+ uX->usriX_workstations = u2->usri2_workstations;
+ uX->usriX_last_logon = u2->usri2_last_logon;
+ uX->usriX_last_logoff = u2->usri2_last_logoff;
+ uX->usriX_acct_expires = u2->usri2_acct_expires;
+ uX->usriX_max_storage = u2->usri2_max_storage;
+ uX->usriX_units_per_week= u2->usri2_units_per_week;
+ uX->usriX_logon_hours = u2->usri2_logon_hours;
+ uX->usriX_bad_pw_count = u2->usri2_bad_pw_count;
+ uX->usriX_num_logons = u2->usri2_num_logons;
+ uX->usriX_logon_server = u2->usri2_logon_server;
+ uX->usriX_country_code = u2->usri2_country_code;
+ uX->usriX_code_page = u2->usri2_code_page;
+ break;
+ case 3:
+ u3 = (struct USER_INFO_3 *)buffer;
+ uX->usriX_name = u3->usri3_name;
+ uX->usriX_password_age = u3->usri3_password_age;
+ uX->usriX_priv = u3->usri3_priv;
+ uX->usriX_home_dir = u3->usri3_home_dir;
+ uX->usriX_comment = u3->usri3_comment;
+ uX->usriX_flags = u3->usri3_flags;
+ uX->usriX_script_path = u3->usri3_script_path;
+ uX->usriX_auth_flags = u3->usri3_auth_flags;
+ uX->usriX_full_name = u3->usri3_full_name;
+ uX->usriX_usr_comment = u3->usri3_usr_comment;
+ uX->usriX_parms = u3->usri3_parms;
+ uX->usriX_workstations = u3->usri3_workstations;
+ uX->usriX_last_logon = u3->usri3_last_logon;
+ uX->usriX_last_logoff = u3->usri3_last_logoff;
+ uX->usriX_acct_expires = u3->usri3_acct_expires;
+ uX->usriX_max_storage = u3->usri3_max_storage;
+ uX->usriX_units_per_week= u3->usri3_units_per_week;
+ uX->usriX_logon_hours = u3->usri3_logon_hours;
+ uX->usriX_bad_pw_count = u3->usri3_bad_pw_count;
+ uX->usriX_num_logons = u3->usri3_num_logons;
+ uX->usriX_logon_server = u3->usri3_logon_server;
+ uX->usriX_country_code = u3->usri3_country_code;
+ uX->usriX_code_page = u3->usri3_code_page;
+ uX->usriX_user_id = u3->usri3_user_id;
+ uX->usriX_primary_group_id = u3->usri3_primary_group_id;
+ uX->usriX_profile = u3->usri3_profile;
+ uX->usriX_home_dir_drive = u3->usri3_home_dir_drive;
+ uX->usriX_password_expired = u3->usri3_password_expired;
+ break;
+ case 1003:
+ u1003 = (struct USER_INFO_1003 *)buffer;
+ uX->usriX_password = u1003->usri1003_password;
+ break;
+ case 1006:
+ u1006 = (struct USER_INFO_1006 *)buffer;
+ uX->usriX_home_dir = u1006->usri1006_home_dir;
+ break;
+ case 1007:
+ u1007 = (struct USER_INFO_1007 *)buffer;
+ uX->usriX_comment = u1007->usri1007_comment;
+ break;
+ case 1009:
+ u1009 = (struct USER_INFO_1009 *)buffer;
+ uX->usriX_script_path = u1009->usri1009_script_path;
+ break;
+ case 1011:
+ u1011 = (struct USER_INFO_1011 *)buffer;
+ uX->usriX_full_name = u1011->usri1011_full_name;
+ break;
+ case 1012:
+ u1012 = (struct USER_INFO_1012 *)buffer;
+ uX->usriX_usr_comment = u1012->usri1012_usr_comment;
+ break;
+ case 1014:
+ u1014 = (struct USER_INFO_1014 *)buffer;
+ uX->usriX_workstations = u1014->usri1014_workstations;
+ break;
+ case 1024:
+ u1024 = (struct USER_INFO_1024 *)buffer;
+ uX->usriX_country_code = u1024->usri1024_country_code;
+ break;
+ case 1051:
+ u1051 = (struct USER_INFO_1051 *)buffer;
+ uX->usriX_primary_group_id = u1051->usri1051_primary_group_id;
+ break;
+ case 1052:
+ u1052 = (struct USER_INFO_1052 *)buffer;
+ uX->usriX_profile = u1052->usri1052_profile;
+ break;
+ case 1053:
+ u1053 = (struct USER_INFO_1053 *)buffer;
+ uX->usriX_home_dir_drive = u1053->usri1053_home_dir_drive;
+ break;
+ case 4:
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_user_info_USER_INFO_X(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ DATA_BLOB *session_key,
+ struct policy_handle *user_handle,
+ struct USER_INFO_X *uX)
+{
+ union samr_UserInfo user_info;
+ struct samr_UserInfo21 info21;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ if (!uX) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ convert_USER_INFO_X_to_samr_user_info21(uX, &info21);
+
+ ZERO_STRUCT(user_info);
+
+ if (uX->usriX_password) {
+
+ user_info.info25.info = info21;
+
+ status = init_samr_CryptPasswordEx(uX->usriX_password,
+ session_key,
+ &user_info.info25.password);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ user_handle,
+ 25,
+ &user_info,
+ &result);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) {
+
+ user_info.info23.info = info21;
+
+ status = init_samr_CryptPassword(uX->usriX_password,
+ session_key,
+ &user_info.info23.password);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ user_handle,
+ 23,
+ &user_info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+
+ user_info.info21 = info21;
+
+ status = dcerpc_samr_SetUserInfo(b, mem_ctx,
+ user_handle,
+ 21,
+ &user_info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return result;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserAdd_r(struct libnetapi_ctx *ctx,
+ struct NetUserAdd *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, domain_handle, user_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ union samr_UserInfo *user_info = NULL;
+ struct samr_PwInfo pw_info;
+ uint32_t access_granted = 0;
+ uint32_t rid = 0;
+ struct USER_INFO_X uX;
+ struct dcerpc_binding_handle *b = NULL;
+ DATA_BLOB session_key;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(user_handle);
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ break;
+ case 2:
+ case 3:
+ case 4:
+ default:
+ werr = WERR_NOT_SUPPORTED;
+ goto done;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ status = construct_USER_INFO_X(r->in.level, r->in.buffer, &uX);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1 |
+ SAMR_DOMAIN_ACCESS_CREATE_USER |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, uX.usriX_name);
+
+ status = dcerpc_samr_CreateUser2(b, talloc_tos(),
+ &domain_handle,
+ &lsa_account_name,
+ ACB_NORMAL,
+ SEC_STD_WRITE_DAC |
+ SEC_STD_DELETE |
+ SAMR_USER_ACCESS_SET_PASSWORD |
+ SAMR_USER_ACCESS_SET_ATTRIBUTES |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES,
+ &user_handle,
+ &access_granted,
+ &rid,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryUserInfo(b, talloc_tos(),
+ &user_handle,
+ 16,
+ &user_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!(user_info->info16.acct_flags & ACB_NORMAL)) {
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ status = dcerpc_samr_GetUserPwInfo(b, talloc_tos(),
+ &user_handle,
+ &pw_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = cli_get_session_key(talloc_tos(), pipe_cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ uX.usriX_flags |= ACB_NORMAL;
+
+ status = set_user_info_USER_INFO_X(ctx, pipe_cli,
+ &session_key,
+ &user_handle,
+ &uX);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto failed;
+ }
+
+ werr = WERR_OK;
+ goto done;
+
+ failed:
+ dcerpc_samr_DeleteUser(b, talloc_tos(),
+ &user_handle,
+ &result);
+
+ done:
+ if (is_valid_policy_hnd(&user_handle) && b) {
+ dcerpc_samr_Close(b, talloc_tos(), &user_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserAdd_l(struct libnetapi_ctx *ctx,
+ struct NetUserAdd *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserAdd);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserDel_r(struct libnetapi_ctx *ctx,
+ struct NetUserDel *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+ struct policy_handle connect_handle, builtin_handle, domain_handle, user_handle;
+ struct lsa_String lsa_account_name;
+ struct samr_Ids user_rids, name_types;
+ struct dom_sid2 *domain_sid = NULL;
+ struct dom_sid2 user_sid;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(user_handle);
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, talloc_tos(),
+ &connect_handle,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ discard_const_p(struct dom_sid, &global_sid_Builtin),
+ &builtin_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, talloc_tos(),
+ &domain_handle,
+ SEC_STD_DELETE,
+ user_rids.ids[0],
+ &user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ sid_compose(&user_sid, domain_sid, user_rids.ids[0]);
+
+ status = dcerpc_samr_RemoveMemberFromForeignDomain(b, talloc_tos(),
+ &builtin_handle,
+ &user_sid,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_DeleteUser(b, talloc_tos(),
+ &user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&user_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &user_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserDel_l(struct libnetapi_ctx *ctx,
+ struct NetUserDel *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserDel);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnetapi_samr_lookup_user(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct policy_handle *builtin_handle,
+ const char *user_name,
+ const struct dom_sid *domain_sid,
+ uint32_t rid,
+ uint32_t level,
+ struct samr_UserInfo21 **info21,
+ struct sec_desc_buf **sec_desc,
+ uint32_t *auth_flag_p)
+{
+ NTSTATUS status, result;
+
+ struct policy_handle user_handle;
+ union samr_UserInfo *user_info = NULL;
+ struct samr_RidWithAttributeArray *rid_array = NULL;
+ uint32_t access_mask = SEC_STD_READ_CONTROL |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_GET_NAME_ETC;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ ZERO_STRUCT(user_handle);
+
+ switch (level) {
+ case 0:
+ break;
+ case 1:
+ access_mask |= SAMR_USER_ACCESS_GET_LOGONINFO |
+ SAMR_USER_ACCESS_GET_GROUPS;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 11:
+ access_mask |= SAMR_USER_ACCESS_GET_LOGONINFO |
+ SAMR_USER_ACCESS_GET_GROUPS |
+ SAMR_USER_ACCESS_GET_LOCALE;
+ break;
+ case 10:
+ case 20:
+ case 23:
+ break;
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (level == 0) {
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ domain_handle,
+ access_mask,
+ rid,
+ &user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ &user_handle,
+ 21,
+ &user_info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_QuerySecurity(b, mem_ctx,
+ &user_handle,
+ SECINFO_DACL,
+ sec_desc,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ if (access_mask & SAMR_USER_ACCESS_GET_GROUPS) {
+
+ struct lsa_SidArray sid_array;
+ struct samr_Ids alias_rids;
+ int i;
+ uint32_t auth_flag = 0;
+ struct dom_sid sid;
+
+ status = dcerpc_samr_GetGroupsForUser(b, mem_ctx,
+ &user_handle,
+ &rid_array,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ sid_array.num_sids = rid_array->count + 1;
+ sid_array.sids = talloc_array(mem_ctx, struct lsa_SidPtr,
+ sid_array.num_sids);
+ NT_STATUS_HAVE_NO_MEMORY(sid_array.sids);
+
+ for (i=0; i<rid_array->count; i++) {
+ sid_compose(&sid, domain_sid, rid_array->rids[i].rid);
+ sid_array.sids[i].sid = dom_sid_dup(mem_ctx, &sid);
+ NT_STATUS_HAVE_NO_MEMORY(sid_array.sids[i].sid);
+ }
+
+ sid_compose(&sid, domain_sid, rid);
+ sid_array.sids[i].sid = dom_sid_dup(mem_ctx, &sid);
+ NT_STATUS_HAVE_NO_MEMORY(sid_array.sids[i].sid);
+
+ status = dcerpc_samr_GetAliasMembership(b, mem_ctx,
+ builtin_handle,
+ &sid_array,
+ &alias_rids,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ for (i=0; i<alias_rids.count; i++) {
+ switch (alias_rids.ids[i]) {
+ case 550: /* Print Operators */
+ auth_flag |= AF_OP_PRINT;
+ break;
+ case 549: /* Server Operators */
+ auth_flag |= AF_OP_SERVER;
+ break;
+ case 548: /* Account Operators */
+ auth_flag |= AF_OP_ACCOUNTS;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (auth_flag_p) {
+ *auth_flag_p = auth_flag;
+ }
+ }
+
+ *info21 = &user_info->info21;
+
+ done:
+ if (is_valid_policy_hnd(&user_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_handle, &result);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static uint32_t samr_rid_to_priv_level(uint32_t rid)
+{
+ switch (rid) {
+ case DOMAIN_RID_ADMINISTRATOR:
+ return USER_PRIV_ADMIN;
+ case DOMAIN_RID_GUEST:
+ return USER_PRIV_GUEST;
+ default:
+ return USER_PRIV_USER;
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static uint32_t samr_acb_flags_to_netapi_flags(uint32_t acb)
+{
+ uint32_t fl = UF_SCRIPT; /* god knows why */
+
+ fl |= ds_acb2uf(acb);
+
+ return fl;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_1(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ struct USER_INFO_1 *i)
+{
+ ZERO_STRUCTP(i);
+ i->usri1_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri1_name);
+ i->usri1_password = NULL;
+ i->usri1_password_age = time(NULL) - nt_time_to_unix(i21->last_password_change);
+ i->usri1_priv = samr_rid_to_priv_level(i21->rid);
+ i->usri1_home_dir = talloc_strdup(mem_ctx, i21->home_directory.string);
+ i->usri1_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri1_flags = samr_acb_flags_to_netapi_flags(i21->acct_flags);
+ i->usri1_script_path = talloc_strdup(mem_ctx, i21->logon_script.string);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_2(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ uint32_t auth_flag,
+ struct USER_INFO_2 *i)
+{
+ ZERO_STRUCTP(i);
+
+ i->usri2_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri2_name);
+ i->usri2_password = NULL;
+ i->usri2_password_age = time(NULL) - nt_time_to_unix(i21->last_password_change);
+ i->usri2_priv = samr_rid_to_priv_level(i21->rid);
+ i->usri2_home_dir = talloc_strdup(mem_ctx, i21->home_directory.string);
+ i->usri2_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri2_flags = samr_acb_flags_to_netapi_flags(i21->acct_flags);
+ i->usri2_script_path = talloc_strdup(mem_ctx, i21->logon_script.string);
+ i->usri2_auth_flags = auth_flag;
+ i->usri2_full_name = talloc_strdup(mem_ctx, i21->full_name.string);
+ i->usri2_usr_comment = talloc_strdup(mem_ctx, i21->comment.string);
+ i->usri2_parms = talloc_strndup(mem_ctx, (const char *)i21->parameters.array, i21->parameters.size/2);
+ i->usri2_workstations = talloc_strdup(mem_ctx, i21->workstations.string);
+ i->usri2_last_logon = nt_time_to_unix(i21->last_logon);
+ i->usri2_last_logoff = nt_time_to_unix(i21->last_logoff);
+ i->usri2_acct_expires = nt_time_to_unix(i21->acct_expiry);
+ i->usri2_max_storage = USER_MAXSTORAGE_UNLIMITED; /* FIXME */
+ i->usri2_units_per_week = i21->logon_hours.units_per_week;
+ i->usri2_logon_hours = (uint8_t *)talloc_memdup(mem_ctx, i21->logon_hours.bits, 21);
+ i->usri2_bad_pw_count = i21->bad_password_count;
+ i->usri2_num_logons = i21->logon_count;
+ i->usri2_logon_server = talloc_strdup(mem_ctx, "\\\\*");
+ i->usri2_country_code = i21->country_code;
+ i->usri2_code_page = i21->code_page;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_3(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ uint32_t auth_flag,
+ struct USER_INFO_3 *i)
+{
+ ZERO_STRUCTP(i);
+
+ i->usri3_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri3_name);
+ i->usri3_password_age = time(NULL) - nt_time_to_unix(i21->last_password_change);
+ i->usri3_priv = samr_rid_to_priv_level(i21->rid);
+ i->usri3_home_dir = talloc_strdup(mem_ctx, i21->home_directory.string);
+ i->usri3_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri3_flags = samr_acb_flags_to_netapi_flags(i21->acct_flags);
+ i->usri3_script_path = talloc_strdup(mem_ctx, i21->logon_script.string);
+ i->usri3_auth_flags = auth_flag;
+ i->usri3_full_name = talloc_strdup(mem_ctx, i21->full_name.string);
+ i->usri3_usr_comment = talloc_strdup(mem_ctx, i21->comment.string);
+ i->usri3_parms = talloc_strndup(mem_ctx, (const char *)i21->parameters.array, i21->parameters.size/2);
+ i->usri3_workstations = talloc_strdup(mem_ctx, i21->workstations.string);
+ i->usri3_last_logon = nt_time_to_unix(i21->last_logon);
+ i->usri3_last_logoff = nt_time_to_unix(i21->last_logoff);
+ i->usri3_acct_expires = nt_time_to_unix(i21->acct_expiry);
+ i->usri3_max_storage = USER_MAXSTORAGE_UNLIMITED; /* FIXME */
+ i->usri3_units_per_week = i21->logon_hours.units_per_week;
+ i->usri3_logon_hours = (uint8_t *)talloc_memdup(mem_ctx, i21->logon_hours.bits, 21);
+ i->usri3_bad_pw_count = i21->bad_password_count;
+ i->usri3_num_logons = i21->logon_count;
+ i->usri3_logon_server = talloc_strdup(mem_ctx, "\\\\*");
+ i->usri3_country_code = i21->country_code;
+ i->usri3_code_page = i21->code_page;
+ i->usri3_user_id = i21->rid;
+ i->usri3_primary_group_id = i21->primary_gid;
+ i->usri3_profile = talloc_strdup(mem_ctx, i21->profile_path.string);
+ i->usri3_home_dir_drive = talloc_strdup(mem_ctx, i21->home_drive.string);
+ i->usri3_password_expired = i21->password_expired;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_4(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ uint32_t auth_flag,
+ struct dom_sid *domain_sid,
+ struct USER_INFO_4 *i)
+{
+ struct dom_sid sid;
+
+ ZERO_STRUCTP(i);
+
+ i->usri4_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri4_name);
+ i->usri4_password_age = time(NULL) - nt_time_to_unix(i21->last_password_change);
+ i->usri4_password = NULL;
+ i->usri4_priv = samr_rid_to_priv_level(i21->rid);
+ i->usri4_home_dir = talloc_strdup(mem_ctx, i21->home_directory.string);
+ i->usri4_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri4_flags = samr_acb_flags_to_netapi_flags(i21->acct_flags);
+ i->usri4_script_path = talloc_strdup(mem_ctx, i21->logon_script.string);
+ i->usri4_auth_flags = auth_flag;
+ i->usri4_full_name = talloc_strdup(mem_ctx, i21->full_name.string);
+ i->usri4_usr_comment = talloc_strdup(mem_ctx, i21->comment.string);
+ i->usri4_parms = talloc_strndup(mem_ctx, (const char *)i21->parameters.array, i21->parameters.size/2);
+ i->usri4_workstations = talloc_strdup(mem_ctx, i21->workstations.string);
+ i->usri4_last_logon = nt_time_to_unix(i21->last_logon);
+ i->usri4_last_logoff = nt_time_to_unix(i21->last_logoff);
+ i->usri4_acct_expires = nt_time_to_unix(i21->acct_expiry);
+ i->usri4_max_storage = USER_MAXSTORAGE_UNLIMITED; /* FIXME */
+ i->usri4_units_per_week = i21->logon_hours.units_per_week;
+ i->usri4_logon_hours = (uint8_t *)talloc_memdup(mem_ctx, i21->logon_hours.bits, 21);
+ i->usri4_bad_pw_count = i21->bad_password_count;
+ i->usri4_num_logons = i21->logon_count;
+ i->usri4_logon_server = talloc_strdup(mem_ctx, "\\\\*");
+ i->usri4_country_code = i21->country_code;
+ i->usri4_code_page = i21->code_page;
+ if (!sid_compose(&sid, domain_sid, i21->rid)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ i->usri4_user_sid = (struct domsid *)dom_sid_dup(mem_ctx, &sid);
+ i->usri4_primary_group_id = i21->primary_gid;
+ i->usri4_profile = talloc_strdup(mem_ctx, i21->profile_path.string);
+ i->usri4_home_dir_drive = talloc_strdup(mem_ctx, i21->home_drive.string);
+ i->usri4_password_expired = i21->password_expired;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_10(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ struct USER_INFO_10 *i)
+{
+ ZERO_STRUCTP(i);
+
+ i->usri10_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri10_name);
+ i->usri10_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri10_full_name = talloc_strdup(mem_ctx, i21->full_name.string);
+ i->usri10_usr_comment = talloc_strdup(mem_ctx, i21->comment.string);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_11(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ uint32_t auth_flag,
+ struct USER_INFO_11 *i)
+{
+ ZERO_STRUCTP(i);
+
+ i->usri11_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri11_name);
+ i->usri11_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri11_usr_comment = talloc_strdup(mem_ctx, i21->comment.string);
+ i->usri11_full_name = talloc_strdup(mem_ctx, i21->full_name.string);
+ i->usri11_priv = samr_rid_to_priv_level(i21->rid);
+ i->usri11_auth_flags = auth_flag;
+ i->usri11_password_age = time(NULL) - nt_time_to_unix(i21->last_password_change);
+ i->usri11_home_dir = talloc_strdup(mem_ctx, i21->home_directory.string);
+ i->usri11_parms = talloc_strndup(mem_ctx, (const char *)i21->parameters.array, i21->parameters.size/2);
+ i->usri11_last_logon = nt_time_to_unix(i21->last_logon);
+ i->usri11_last_logoff = nt_time_to_unix(i21->last_logoff);
+ i->usri11_bad_pw_count = i21->bad_password_count;
+ i->usri11_num_logons = i21->logon_count;
+ i->usri11_logon_server = talloc_strdup(mem_ctx, "\\\\*");
+ i->usri11_country_code = i21->country_code;
+ i->usri11_workstations = talloc_strdup(mem_ctx, i21->workstations.string);
+ i->usri11_max_storage = USER_MAXSTORAGE_UNLIMITED; /* FIXME */
+ i->usri11_units_per_week = i21->logon_hours.units_per_week;
+ i->usri11_logon_hours = (uint8_t *)talloc_memdup(mem_ctx, i21->logon_hours.bits, 21);
+ i->usri11_code_page = i21->code_page;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_20(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ struct USER_INFO_20 *i)
+{
+ ZERO_STRUCTP(i);
+
+ i->usri20_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri20_name);
+ i->usri20_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri20_full_name = talloc_strdup(mem_ctx, i21->full_name.string);
+ i->usri20_flags = samr_acb_flags_to_netapi_flags(i21->acct_flags);
+ i->usri20_user_id = i21->rid;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS info21_to_USER_INFO_23(TALLOC_CTX *mem_ctx,
+ const struct samr_UserInfo21 *i21,
+ struct dom_sid *domain_sid,
+ struct USER_INFO_23 *i)
+{
+ struct dom_sid sid;
+
+ ZERO_STRUCTP(i);
+
+ i->usri23_name = talloc_strdup(mem_ctx, i21->account_name.string);
+ NT_STATUS_HAVE_NO_MEMORY(i->usri23_name);
+ i->usri23_comment = talloc_strdup(mem_ctx, i21->description.string);
+ i->usri23_full_name = talloc_strdup(mem_ctx, i21->full_name.string);
+ i->usri23_flags = samr_acb_flags_to_netapi_flags(i21->acct_flags);
+ if (!sid_compose(&sid, domain_sid, i21->rid)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ i->usri23_user_sid = (struct domsid *)dom_sid_dup(mem_ctx, &sid);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnetapi_samr_lookup_user_map_USER_INFO(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct dom_sid *domain_sid,
+ struct policy_handle *domain_handle,
+ struct policy_handle *builtin_handle,
+ const char *user_name,
+ uint32_t rid,
+ uint32_t level,
+ uint8_t **buffer,
+ uint32_t *num_entries)
+{
+ NTSTATUS status;
+
+ struct samr_UserInfo21 *info21 = NULL;
+ struct sec_desc_buf *sec_desc = NULL;
+ uint32_t auth_flag = 0;
+
+ struct USER_INFO_0 info0;
+ struct USER_INFO_1 info1;
+ struct USER_INFO_2 info2;
+ struct USER_INFO_3 info3;
+ struct USER_INFO_4 info4;
+ struct USER_INFO_10 info10;
+ struct USER_INFO_11 info11;
+ struct USER_INFO_20 info20;
+ struct USER_INFO_23 info23;
+
+ switch (level) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 10:
+ case 11:
+ case 20:
+ case 23:
+ break;
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (level == 0) {
+ info0.usri0_name = talloc_strdup(mem_ctx, user_name);
+ NT_STATUS_HAVE_NO_MEMORY(info0.usri0_name);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_0, info0,
+ (struct USER_INFO_0 **)buffer, num_entries);
+
+ return NT_STATUS_OK;
+ }
+
+ status = libnetapi_samr_lookup_user(mem_ctx, pipe_cli,
+ domain_handle,
+ builtin_handle,
+ user_name,
+ domain_sid,
+ rid,
+ level,
+ &info21,
+ &sec_desc,
+ &auth_flag);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ switch (level) {
+ case 1:
+ status = info21_to_USER_INFO_1(mem_ctx, info21, &info1);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_1, info1,
+ (struct USER_INFO_1 **)buffer, num_entries);
+
+ break;
+ case 2:
+ status = info21_to_USER_INFO_2(mem_ctx, info21, auth_flag, &info2);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_2, info2,
+ (struct USER_INFO_2 **)buffer, num_entries);
+
+ break;
+ case 3:
+ status = info21_to_USER_INFO_3(mem_ctx, info21, auth_flag, &info3);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_3, info3,
+ (struct USER_INFO_3 **)buffer, num_entries);
+
+ break;
+ case 4:
+ status = info21_to_USER_INFO_4(mem_ctx, info21, auth_flag, domain_sid, &info4);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_4, info4,
+ (struct USER_INFO_4 **)buffer, num_entries);
+
+ break;
+ case 10:
+ status = info21_to_USER_INFO_10(mem_ctx, info21, &info10);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_10, info10,
+ (struct USER_INFO_10 **)buffer, num_entries);
+
+ break;
+ case 11:
+ status = info21_to_USER_INFO_11(mem_ctx, info21, auth_flag, &info11);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_11, info11,
+ (struct USER_INFO_11 **)buffer, num_entries);
+
+ break;
+ case 20:
+ status = info21_to_USER_INFO_20(mem_ctx, info21, &info20);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_20, info20,
+ (struct USER_INFO_20 **)buffer, num_entries);
+
+ break;
+ case 23:
+ status = info21_to_USER_INFO_23(mem_ctx, info21, domain_sid, &info23);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ ADD_TO_ARRAY(mem_ctx, struct USER_INFO_23, info23,
+ (struct USER_INFO_23 **)buffer, num_entries);
+ break;
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ done:
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserEnum_r(struct libnetapi_ctx *ctx,
+ struct NetUserEnum *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ struct policy_handle domain_handle, builtin_handle;
+ struct samr_SamArray *sam = NULL;
+ uint32_t filter = ACB_NORMAL;
+ int i;
+ uint32_t entries_read = 0;
+
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(builtin_handle);
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ *r->out.buffer = NULL;
+ *r->out.entries_read = 0;
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 10:
+ case 11:
+ case 20:
+ case 23:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT |
+ SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2 |
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ switch (r->in.filter) {
+ case FILTER_NORMAL_ACCOUNT:
+ filter = ACB_NORMAL;
+ break;
+ case FILTER_TEMP_DUPLICATE_ACCOUNT:
+ filter = ACB_TEMPDUP;
+ break;
+ case FILTER_INTERDOMAIN_TRUST_ACCOUNT:
+ filter = ACB_DOMTRUST;
+ break;
+ case FILTER_WORKSTATION_TRUST_ACCOUNT:
+ filter = ACB_WSTRUST;
+ break;
+ case FILTER_SERVER_TRUST_ACCOUNT:
+ filter = ACB_SVRTRUST;
+ break;
+ default:
+ break;
+ }
+
+ status = dcerpc_samr_EnumDomainUsers(b,
+ ctx,
+ &domain_handle,
+ r->in.resume_handle,
+ filter,
+ &sam,
+ r->in.prefmaxlen,
+ &entries_read,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ werr = ntstatus_to_werror(result);
+ if (NT_STATUS_IS_ERR(result)) {
+ goto done;
+ }
+
+ for (i=0; i < sam->count; i++) {
+
+ status = libnetapi_samr_lookup_user_map_USER_INFO(ctx, pipe_cli,
+ domain_sid,
+ &domain_handle,
+ &builtin_handle,
+ sam->entries[i].name.string,
+ sam->entries[i].idx,
+ r->in.level,
+ r->out.buffer,
+ r->out.entries_read);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ done:
+ /* if last query */
+ if (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_IS_ERR(result)) {
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserEnum_l(struct libnetapi_ctx *ctx,
+ struct NetUserEnum *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserEnum);
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_dispinfo_to_NET_DISPLAY_USER(TALLOC_CTX *mem_ctx,
+ struct samr_DispInfoGeneral *info,
+ uint32_t *entries_read,
+ void **buffer)
+{
+ struct NET_DISPLAY_USER *user = NULL;
+ int i;
+
+ user = talloc_zero_array(mem_ctx,
+ struct NET_DISPLAY_USER,
+ info->count);
+ W_ERROR_HAVE_NO_MEMORY(user);
+
+ for (i = 0; i < info->count; i++) {
+ user[i].usri1_name = talloc_strdup(mem_ctx,
+ info->entries[i].account_name.string);
+ user[i].usri1_comment = talloc_strdup(mem_ctx,
+ info->entries[i].description.string);
+ user[i].usri1_flags =
+ info->entries[i].acct_flags;
+ user[i].usri1_full_name = talloc_strdup(mem_ctx,
+ info->entries[i].full_name.string);
+ user[i].usri1_user_id =
+ info->entries[i].rid;
+ user[i].usri1_next_index =
+ info->entries[i].idx;
+
+ if (!user[i].usri1_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ *buffer = talloc_memdup(mem_ctx, user,
+ sizeof(struct NET_DISPLAY_USER) * info->count);
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ *entries_read = info->count;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_dispinfo_to_NET_DISPLAY_MACHINE(TALLOC_CTX *mem_ctx,
+ struct samr_DispInfoFull *info,
+ uint32_t *entries_read,
+ void **buffer)
+{
+ struct NET_DISPLAY_MACHINE *machine = NULL;
+ int i;
+
+ machine = talloc_zero_array(mem_ctx,
+ struct NET_DISPLAY_MACHINE,
+ info->count);
+ W_ERROR_HAVE_NO_MEMORY(machine);
+
+ for (i = 0; i < info->count; i++) {
+ machine[i].usri2_name = talloc_strdup(mem_ctx,
+ info->entries[i].account_name.string);
+ machine[i].usri2_comment = talloc_strdup(mem_ctx,
+ info->entries[i].description.string);
+ machine[i].usri2_flags =
+ info->entries[i].acct_flags;
+ machine[i].usri2_user_id =
+ info->entries[i].rid;
+ machine[i].usri2_next_index =
+ info->entries[i].idx;
+
+ if (!machine[i].usri2_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ *buffer = talloc_memdup(mem_ctx, machine,
+ sizeof(struct NET_DISPLAY_MACHINE) * info->count);
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ *entries_read = info->count;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_dispinfo_to_NET_DISPLAY_GROUP(TALLOC_CTX *mem_ctx,
+ struct samr_DispInfoFullGroups *info,
+ uint32_t *entries_read,
+ void **buffer)
+{
+ struct NET_DISPLAY_GROUP *group = NULL;
+ int i;
+
+ group = talloc_zero_array(mem_ctx,
+ struct NET_DISPLAY_GROUP,
+ info->count);
+ W_ERROR_HAVE_NO_MEMORY(group);
+
+ for (i = 0; i < info->count; i++) {
+ group[i].grpi3_name = talloc_strdup(mem_ctx,
+ info->entries[i].account_name.string);
+ group[i].grpi3_comment = talloc_strdup(mem_ctx,
+ info->entries[i].description.string);
+ group[i].grpi3_group_id =
+ info->entries[i].rid;
+ group[i].grpi3_attributes =
+ info->entries[i].acct_flags;
+ group[i].grpi3_next_index =
+ info->entries[i].idx;
+
+ if (!group[i].grpi3_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ *buffer = talloc_memdup(mem_ctx, group,
+ sizeof(struct NET_DISPLAY_GROUP) * info->count);
+ W_ERROR_HAVE_NO_MEMORY(*buffer);
+
+ *entries_read = info->count;
+
+ return WERR_OK;
+
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR convert_samr_dispinfo_to_NET_DISPLAY(TALLOC_CTX *mem_ctx,
+ union samr_DispInfo *info,
+ uint32_t level,
+ uint32_t *entries_read,
+ void **buffer)
+{
+ switch (level) {
+ case 1:
+ return convert_samr_dispinfo_to_NET_DISPLAY_USER(mem_ctx,
+ &info->info1,
+ entries_read,
+ buffer);
+ case 2:
+ return convert_samr_dispinfo_to_NET_DISPLAY_MACHINE(mem_ctx,
+ &info->info2,
+ entries_read,
+ buffer);
+ case 3:
+ return convert_samr_dispinfo_to_NET_DISPLAY_GROUP(mem_ctx,
+ &info->info3,
+ entries_read,
+ buffer);
+ default:
+ break;
+ }
+
+ return WERR_INVALID_LEVEL;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetQueryDisplayInformation_r(struct libnetapi_ctx *ctx,
+ struct NetQueryDisplayInformation *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ struct policy_handle domain_handle;
+ union samr_DispInfo info;
+ struct dcerpc_binding_handle *b = NULL;
+
+ uint32_t total_size = 0;
+ uint32_t returned_size = 0;
+
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ WERROR werr;
+ WERROR werr_tmp;
+
+ *r->out.entries_read = 0;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+
+ switch (r->in.level) {
+ case 1:
+ case 2:
+ case 3:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2 |
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryDisplayInfo2(b,
+ ctx,
+ &domain_handle,
+ r->in.level,
+ r->in.idx,
+ r->in.entries_requested,
+ r->in.prefmaxlen,
+ &total_size,
+ &returned_size,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ werr = ntstatus_to_werror(result);
+ if (NT_STATUS_IS_ERR(result)) {
+ goto done;
+ }
+
+ werr_tmp = convert_samr_dispinfo_to_NET_DISPLAY(ctx, &info,
+ r->in.level,
+ r->out.entries_read,
+ r->out.buffer);
+ if (!W_ERROR_IS_OK(werr_tmp)) {
+ werr = werr_tmp;
+ }
+ done:
+ /* if last query */
+ if (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_IS_ERR(result)) {
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+ }
+
+ return werr;
+
+}
+
+/****************************************************************
+****************************************************************/
+
+
+WERROR NetQueryDisplayInformation_l(struct libnetapi_ctx *ctx,
+ struct NetQueryDisplayInformation *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetQueryDisplayInformation);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserChangePassword_r(struct libnetapi_ctx *ctx,
+ struct NetUserChangePassword *r)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserChangePassword_l(struct libnetapi_ctx *ctx,
+ struct NetUserChangePassword *r)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetUserGetInfo *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+
+ struct policy_handle connect_handle, domain_handle, builtin_handle, user_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct samr_Ids user_rids, name_types;
+ uint32_t num_entries = 0;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(user_handle);
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 10:
+ case 11:
+ case 20:
+ case 23:
+ break;
+ default:
+ werr = WERR_INVALID_LEVEL;
+ goto done;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT |
+ SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = libnetapi_samr_lookup_user_map_USER_INFO(ctx, pipe_cli,
+ domain_sid,
+ &domain_handle,
+ &builtin_handle,
+ r->in.user_name,
+ user_rids.ids[0],
+ r->in.level,
+ r->out.buffer,
+ &num_entries);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&user_handle) && b) {
+ dcerpc_samr_Close(b, talloc_tos(), &user_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetUserGetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserGetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserSetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetUserSetInfo *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status, result;
+ WERROR werr;
+
+ struct policy_handle connect_handle, domain_handle, builtin_handle, user_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct samr_Ids user_rids, name_types;
+ uint32_t user_mask = 0;
+
+ struct USER_INFO_X uX;
+ struct dcerpc_binding_handle *b = NULL;
+ DATA_BLOB session_key;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(builtin_handle);
+ ZERO_STRUCT(user_handle);
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ user_mask = SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ break;
+ case 1003:
+ user_mask = SAMR_USER_ACCESS_SET_PASSWORD;
+ break;
+ case 1006:
+ case 1007:
+ case 1009:
+ case 1011:
+ case 1014:
+ case 1052:
+ case 1053:
+ user_mask = SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ break;
+ case 1012:
+ case 1024:
+ user_mask = SAMR_USER_ACCESS_SET_LOC_COM;
+ break;
+ case 1051:
+ user_mask = SAMR_USER_ACCESS_SET_ATTRIBUTES |
+ SAMR_USER_ACCESS_GET_GROUPS;
+ break;
+ case 3:
+ user_mask = SEC_STD_READ_CONTROL |
+ SEC_STD_WRITE_DAC |
+ SAMR_USER_ACCESS_GET_GROUPS |
+ SAMR_USER_ACCESS_SET_PASSWORD |
+ SAMR_USER_ACCESS_SET_ATTRIBUTES |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_SET_LOC_COM;
+ break;
+ case 1:
+ case 2:
+ case 4:
+ case 21:
+ case 22:
+ case 1005:
+ case 1008:
+ case 1010:
+ case 1017:
+ case 1020:
+ werr = WERR_NOT_SUPPORTED;
+ goto done;
+ default:
+ werr = WERR_INVALID_LEVEL;
+ goto done;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1 |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT |
+ SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, talloc_tos(),
+ &domain_handle,
+ user_mask,
+ user_rids.ids[0],
+ &user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = construct_USER_INFO_X(r->in.level, r->in.buffer, &uX);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = cli_get_session_key(talloc_tos(), pipe_cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = set_user_info_USER_INFO_X(ctx, pipe_cli,
+ &session_key,
+ &user_handle,
+ &uX);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&user_handle) && b) {
+ dcerpc_samr_Close(b, talloc_tos(), &user_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_builtin_handle(ctx, &builtin_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserSetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetUserSetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserSetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS query_USER_MODALS_INFO_rpc(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct samr_DomInfo1 *info1,
+ struct samr_DomInfo3 *info3,
+ struct samr_DomInfo5 *info5,
+ struct samr_DomInfo6 *info6,
+ struct samr_DomInfo7 *info7,
+ struct samr_DomInfo12 *info12)
+{
+ NTSTATUS status, result;
+ union samr_DomainInfo *dom_info = NULL;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ if (info1) {
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ domain_handle,
+ 1,
+ &dom_info,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+
+ *info1 = dom_info->info1;
+ }
+
+ if (info3) {
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ domain_handle,
+ 3,
+ &dom_info,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+
+ *info3 = dom_info->info3;
+ }
+
+ if (info5) {
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ domain_handle,
+ 5,
+ &dom_info,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+
+ *info5 = dom_info->info5;
+ }
+
+ if (info6) {
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ domain_handle,
+ 6,
+ &dom_info,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+
+ *info6 = dom_info->info6;
+ }
+
+ if (info7) {
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ domain_handle,
+ 7,
+ &dom_info,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+
+ *info7 = dom_info->info7;
+ }
+
+ if (info12) {
+ status = dcerpc_samr_QueryDomainInfo2(b, mem_ctx,
+ domain_handle,
+ 12,
+ &dom_info,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+
+ *info12 = dom_info->info12;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS query_USER_MODALS_INFO_0(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_0 *info0)
+{
+ NTSTATUS status;
+ struct samr_DomInfo1 dom_info1;
+ struct samr_DomInfo3 dom_info3;
+
+ ZERO_STRUCTP(info0);
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info1,
+ &dom_info3,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ info0->usrmod0_min_passwd_len =
+ dom_info1.min_password_length;
+ info0->usrmod0_max_passwd_age =
+ nt_time_to_unix_abs((NTTIME *)&dom_info1.max_password_age);
+ info0->usrmod0_min_passwd_age =
+ nt_time_to_unix_abs((NTTIME *)&dom_info1.min_password_age);
+ info0->usrmod0_password_hist_len =
+ dom_info1.password_history_length;
+
+ info0->usrmod0_force_logoff =
+ nt_time_to_unix_abs(&dom_info3.force_logoff_time);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS query_USER_MODALS_INFO_1(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_1 *info1)
+{
+ NTSTATUS status;
+ struct samr_DomInfo6 dom_info6;
+ struct samr_DomInfo7 dom_info7;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ NULL,
+ NULL,
+ NULL,
+ &dom_info6,
+ &dom_info7,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ info1->usrmod1_primary =
+ talloc_strdup(mem_ctx, dom_info6.primary.string);
+
+ info1->usrmod1_role = dom_info7.role;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS query_USER_MODALS_INFO_2(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct dom_sid *domain_sid,
+ struct USER_MODALS_INFO_2 *info2)
+{
+ NTSTATUS status;
+ struct samr_DomInfo5 dom_info5;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ NULL,
+ NULL,
+ &dom_info5,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ info2->usrmod2_domain_name =
+ talloc_strdup(mem_ctx, dom_info5.domain_name.string);
+ info2->usrmod2_domain_id =
+ (struct domsid *)dom_sid_dup(mem_ctx, domain_sid);
+
+ NT_STATUS_HAVE_NO_MEMORY(info2->usrmod2_domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(info2->usrmod2_domain_id);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS query_USER_MODALS_INFO_3(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_3 *info3)
+{
+ NTSTATUS status;
+ struct samr_DomInfo12 dom_info12;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &dom_info12);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ info3->usrmod3_lockout_duration =
+ nt_time_to_unix_abs(&dom_info12.lockout_duration);
+ info3->usrmod3_lockout_observation_window =
+ nt_time_to_unix_abs(&dom_info12.lockout_window);
+ info3->usrmod3_lockout_threshold =
+ dom_info12.lockout_threshold;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS query_USER_MODALS_INFO_to_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ uint32_t level,
+ struct policy_handle *domain_handle,
+ struct dom_sid *domain_sid,
+ uint8_t **buffer)
+{
+ NTSTATUS status;
+
+ struct USER_MODALS_INFO_0 info0;
+ struct USER_MODALS_INFO_1 info1;
+ struct USER_MODALS_INFO_2 info2;
+ struct USER_MODALS_INFO_3 info3;
+
+ if (!buffer) {
+ return ERROR_INSUFFICIENT_BUFFER;
+ }
+
+ switch (level) {
+ case 0:
+ status = query_USER_MODALS_INFO_0(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &info0);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info0,
+ sizeof(info0));
+ break;
+
+ case 1:
+ status = query_USER_MODALS_INFO_1(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &info1);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info1,
+ sizeof(info1));
+ break;
+ case 2:
+ status = query_USER_MODALS_INFO_2(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ domain_sid,
+ &info2);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info2,
+ sizeof(info2));
+ break;
+ case 3:
+ status = query_USER_MODALS_INFO_3(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &info3);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ *buffer = (uint8_t *)talloc_memdup(mem_ctx, &info3,
+ sizeof(info3));
+ break;
+ default:
+ break;
+ }
+
+ NT_STATUS_HAVE_NO_MEMORY(*buffer);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserModalsGet_r(struct libnetapi_ctx *ctx,
+ struct NetUserModalsGet *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status;
+ WERROR werr;
+
+ struct policy_handle connect_handle, domain_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ uint32_t access_mask = SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ access_mask |= SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1 |
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2;
+ break;
+ case 1:
+ case 2:
+ access_mask |= SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2;
+ break;
+ case 3:
+ access_mask |= SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1;
+ break;
+ default:
+ werr = WERR_INVALID_LEVEL;
+ goto done;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ access_mask,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ /* 0: 1 + 3 */
+ /* 1: 6 + 7 */
+ /* 2: 5 */
+ /* 3: 12 (DomainInfo2) */
+
+ status = query_USER_MODALS_INFO_to_buffer(ctx,
+ pipe_cli,
+ r->in.level,
+ &domain_handle,
+ domain_sid,
+ r->out.buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserModalsGet_l(struct libnetapi_ctx *ctx,
+ struct NetUserModalsGet *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserModalsGet);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_rpc(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct samr_DomInfo1 *info1,
+ struct samr_DomInfo3 *info3,
+ struct samr_DomInfo12 *info12)
+{
+ NTSTATUS status, result;
+ union samr_DomainInfo dom_info;
+ struct dcerpc_binding_handle *b = pipe_cli->binding_handle;
+
+ if (info1) {
+
+ ZERO_STRUCT(dom_info);
+
+ dom_info.info1 = *info1;
+
+ status = dcerpc_samr_SetDomainInfo(b, mem_ctx,
+ domain_handle,
+ 1,
+ &dom_info,
+ &result);
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+ }
+
+ if (info3) {
+
+ ZERO_STRUCT(dom_info);
+
+ dom_info.info3 = *info3;
+
+ status = dcerpc_samr_SetDomainInfo(b, mem_ctx,
+ domain_handle,
+ 3,
+ &dom_info,
+ &result);
+
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+ }
+
+ if (info12) {
+
+ ZERO_STRUCT(dom_info);
+
+ dom_info.info12 = *info12;
+
+ status = dcerpc_samr_SetDomainInfo(b, mem_ctx,
+ domain_handle,
+ 12,
+ &dom_info,
+ &result);
+
+ NT_STATUS_NOT_OK_RETURN(status);
+ NT_STATUS_NOT_OK_RETURN(result);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_0_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_0 *info0)
+{
+ NTSTATUS status;
+ struct samr_DomInfo1 dom_info_1;
+ struct samr_DomInfo3 dom_info_3;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ &dom_info_3,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ dom_info_1.min_password_length =
+ info0->usrmod0_min_passwd_len;
+ dom_info_1.password_history_length =
+ info0->usrmod0_password_hist_len;
+
+ unix_to_nt_time_abs((NTTIME *)&dom_info_1.max_password_age,
+ info0->usrmod0_max_passwd_age);
+ unix_to_nt_time_abs((NTTIME *)&dom_info_1.min_password_age,
+ info0->usrmod0_min_passwd_age);
+
+ unix_to_nt_time_abs(&dom_info_3.force_logoff_time,
+ info0->usrmod0_force_logoff);
+
+ return set_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ &dom_info_3,
+ NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_3_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_3 *info3)
+{
+ NTSTATUS status;
+ struct samr_DomInfo12 dom_info_12;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &dom_info_12);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ unix_to_nt_time_abs((NTTIME *)&dom_info_12.lockout_duration,
+ info3->usrmod3_lockout_duration);
+ unix_to_nt_time_abs((NTTIME *)&dom_info_12.lockout_window,
+ info3->usrmod3_lockout_observation_window);
+ dom_info_12.lockout_threshold = info3->usrmod3_lockout_threshold;
+
+ return set_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ NULL,
+ NULL,
+ &dom_info_12);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_1001_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_1001 *info1001)
+{
+ NTSTATUS status;
+ struct samr_DomInfo1 dom_info_1;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ dom_info_1.min_password_length =
+ info1001->usrmod1001_min_passwd_len;
+
+ return set_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_1002_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_1002 *info1002)
+{
+ NTSTATUS status;
+ struct samr_DomInfo1 dom_info_1;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ unix_to_nt_time_abs((NTTIME *)&dom_info_1.max_password_age,
+ info1002->usrmod1002_max_passwd_age);
+
+ return set_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_1003_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_1003 *info1003)
+{
+ NTSTATUS status;
+ struct samr_DomInfo1 dom_info_1;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ unix_to_nt_time_abs((NTTIME *)&dom_info_1.min_password_age,
+ info1003->usrmod1003_min_passwd_age);
+
+ return set_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_1004_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_1004 *info1004)
+{
+ NTSTATUS status;
+ struct samr_DomInfo3 dom_info_3;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ NULL,
+ &dom_info_3,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ unix_to_nt_time_abs(&dom_info_3.force_logoff_time,
+ info1004->usrmod1004_force_logoff);
+
+ return set_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ NULL,
+ &dom_info_3,
+ NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_1005_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ struct policy_handle *domain_handle,
+ struct USER_MODALS_INFO_1005 *info1005)
+{
+ NTSTATUS status;
+ struct samr_DomInfo1 dom_info_1;
+
+ status = query_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ dom_info_1.password_history_length =
+ info1005->usrmod1005_password_hist_len;
+
+ return set_USER_MODALS_INFO_rpc(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ &dom_info_1,
+ NULL,
+ NULL);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS set_USER_MODALS_INFO_buffer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_cli,
+ uint32_t level,
+ struct policy_handle *domain_handle,
+ struct dom_sid *domain_sid,
+ uint8_t *buffer)
+{
+ struct USER_MODALS_INFO_0 *info0;
+ struct USER_MODALS_INFO_3 *info3;
+ struct USER_MODALS_INFO_1001 *info1001;
+ struct USER_MODALS_INFO_1002 *info1002;
+ struct USER_MODALS_INFO_1003 *info1003;
+ struct USER_MODALS_INFO_1004 *info1004;
+ struct USER_MODALS_INFO_1005 *info1005;
+
+ if (!buffer) {
+ return ERROR_INSUFFICIENT_BUFFER;
+ }
+
+ switch (level) {
+ case 0:
+ info0 = (struct USER_MODALS_INFO_0 *)buffer;
+ return set_USER_MODALS_INFO_0_buffer(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ info0);
+ case 3:
+ info3 = (struct USER_MODALS_INFO_3 *)buffer;
+ return set_USER_MODALS_INFO_3_buffer(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ info3);
+ case 1001:
+ info1001 = (struct USER_MODALS_INFO_1001 *)buffer;
+ return set_USER_MODALS_INFO_1001_buffer(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ info1001);
+ case 1002:
+ info1002 = (struct USER_MODALS_INFO_1002 *)buffer;
+ return set_USER_MODALS_INFO_1002_buffer(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ info1002);
+ case 1003:
+ info1003 = (struct USER_MODALS_INFO_1003 *)buffer;
+ return set_USER_MODALS_INFO_1003_buffer(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ info1003);
+ case 1004:
+ info1004 = (struct USER_MODALS_INFO_1004 *)buffer;
+ return set_USER_MODALS_INFO_1004_buffer(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ info1004);
+ case 1005:
+ info1005 = (struct USER_MODALS_INFO_1005 *)buffer;
+ return set_USER_MODALS_INFO_1005_buffer(mem_ctx,
+ pipe_cli,
+ domain_handle,
+ info1005);
+
+ default:
+ break;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserModalsSet_r(struct libnetapi_ctx *ctx,
+ struct NetUserModalsSet *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ NTSTATUS status;
+ WERROR werr;
+
+ struct policy_handle connect_handle, domain_handle;
+ struct dom_sid2 *domain_sid = NULL;
+ uint32_t access_mask = SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ access_mask |= SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1 |
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2 |
+ SAMR_DOMAIN_ACCESS_SET_INFO_1 |
+ SAMR_DOMAIN_ACCESS_SET_INFO_2;
+ break;
+ case 3:
+ case 1001:
+ case 1002:
+ case 1003:
+ case 1005:
+ access_mask |= SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1 |
+ SAMR_DOMAIN_ACCESS_SET_INFO_1;
+ break;
+ case 1004:
+ access_mask |= SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2 |
+ SAMR_DOMAIN_ACCESS_SET_INFO_2;
+ break;
+ case 1:
+ case 2:
+ case 1006:
+ case 1007:
+ werr = WERR_NOT_SUPPORTED;
+ break;
+ default:
+ werr = WERR_INVALID_LEVEL;
+ goto done;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ access_mask,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = set_USER_MODALS_INFO_buffer(ctx,
+ pipe_cli,
+ r->in.level,
+ &domain_handle,
+ domain_sid,
+ r->in.buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserModalsSet_l(struct libnetapi_ctx *ctx,
+ struct NetUserModalsSet *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserModalsSet);
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS add_GROUP_USERS_INFO_X_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ const char *group_name,
+ uint32_t attributes,
+ uint8_t **buffer,
+ uint32_t *num_entries)
+{
+ struct GROUP_USERS_INFO_0 u0;
+ struct GROUP_USERS_INFO_1 u1;
+
+ switch (level) {
+ case 0:
+ if (group_name) {
+ u0.grui0_name = talloc_strdup(mem_ctx, group_name);
+ NT_STATUS_HAVE_NO_MEMORY(u0.grui0_name);
+ } else {
+ u0.grui0_name = NULL;
+ }
+
+ ADD_TO_ARRAY(mem_ctx, struct GROUP_USERS_INFO_0, u0,
+ (struct GROUP_USERS_INFO_0 **)buffer, num_entries);
+ break;
+ case 1:
+ if (group_name) {
+ u1.grui1_name = talloc_strdup(mem_ctx, group_name);
+ NT_STATUS_HAVE_NO_MEMORY(u1.grui1_name);
+ } else {
+ u1.grui1_name = NULL;
+ }
+
+ u1.grui1_attributes = attributes;
+
+ ADD_TO_ARRAY(mem_ctx, struct GROUP_USERS_INFO_1, u1,
+ (struct GROUP_USERS_INFO_1 **)buffer, num_entries);
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserGetGroups_r(struct libnetapi_ctx *ctx,
+ struct NetUserGetGroups *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle, domain_handle, user_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct samr_Ids user_rids, name_types;
+ struct samr_RidWithAttributeArray *rid_array = NULL;
+ struct lsa_Strings names;
+ struct samr_Ids types;
+ uint32_t *rids = NULL;
+
+ int i;
+ uint32_t entries_read = 0;
+
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ *r->out.buffer = NULL;
+ *r->out.entries_read = 0;
+ *r->out.total_entries = 0;
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, talloc_tos(),
+ &domain_handle,
+ SAMR_USER_ACCESS_GET_GROUPS,
+ user_rids.ids[0],
+ &user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_GetGroupsForUser(b, talloc_tos(),
+ &user_handle,
+ &rid_array,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ rids = talloc_array(ctx, uint32_t, rid_array->count);
+ if (!rids) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i < rid_array->count; i++) {
+ rids[i] = rid_array->rids[i].rid;
+ }
+
+ status = dcerpc_samr_LookupRids(b, talloc_tos(),
+ &domain_handle,
+ rid_array->count,
+ rids,
+ &names,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) {
+ werr = ntstatus_to_werror(result);
+ goto done;
+ }
+ if (names.count != rid_array->count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != rid_array->count) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ for (i=0; i < names.count; i++) {
+ status = add_GROUP_USERS_INFO_X_buffer(ctx,
+ r->in.level,
+ names.names[i].string,
+ rid_array->rids[i].attributes,
+ r->out.buffer,
+ &entries_read);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ *r->out.entries_read = entries_read;
+ *r->out.total_entries = entries_read;
+
+ done:
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserGetGroups_l(struct libnetapi_ctx *ctx,
+ struct NetUserGetGroups *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserGetGroups);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserSetGroups_r(struct libnetapi_ctx *ctx,
+ struct NetUserSetGroups *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle, domain_handle, user_handle, group_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct samr_Ids user_rids, name_types;
+ struct samr_Ids group_rids;
+ struct samr_RidWithAttributeArray *rid_array = NULL;
+ struct lsa_String *lsa_names = NULL;
+
+ uint32_t *add_rids = NULL;
+ uint32_t *del_rids = NULL;
+ size_t num_add_rids = 0;
+ size_t num_del_rids = 0;
+
+ uint32_t *member_rids = NULL;
+
+ struct GROUP_USERS_INFO_0 *i0 = NULL;
+ struct GROUP_USERS_INFO_1 *i1 = NULL;
+
+ int i, k;
+
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(group_handle);
+
+ if (!r->in.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, talloc_tos(),
+ &domain_handle,
+ SAMR_USER_ACCESS_GET_GROUPS,
+ user_rids.ids[0],
+ &user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ i0 = (struct GROUP_USERS_INFO_0 *)r->in.buffer;
+ break;
+ case 1:
+ i1 = (struct GROUP_USERS_INFO_1 *)r->in.buffer;
+ break;
+ }
+
+ lsa_names = talloc_array(ctx, struct lsa_String, r->in.num_entries);
+ if (!lsa_names) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i < r->in.num_entries; i++) {
+
+ switch (r->in.level) {
+ case 0:
+ init_lsa_String(&lsa_names[i], i0->grui0_name);
+ i0++;
+ break;
+ case 1:
+ init_lsa_String(&lsa_names[i], i1->grui1_name);
+ i1++;
+ break;
+ }
+ }
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ r->in.num_entries,
+ lsa_names,
+ &group_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (group_rids.count != r->in.num_entries) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != r->in.num_entries) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ member_rids = group_rids.ids;
+
+ status = dcerpc_samr_GetGroupsForUser(b, talloc_tos(),
+ &user_handle,
+ &rid_array,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ /* add list */
+
+ for (i=0; i < r->in.num_entries; i++) {
+ bool already_member = false;
+ for (k=0; k < rid_array->count; k++) {
+ if (member_rids[i] == rid_array->rids[k].rid) {
+ already_member = true;
+ break;
+ }
+ }
+ if (!already_member) {
+ if (!add_rid_to_array_unique(ctx,
+ member_rids[i],
+ &add_rids, &num_add_rids)) {
+ werr = WERR_GEN_FAILURE;
+ goto done;
+ }
+ }
+ }
+
+ /* del list */
+
+ for (k=0; k < rid_array->count; k++) {
+ bool keep_member = false;
+ for (i=0; i < r->in.num_entries; i++) {
+ if (member_rids[i] == rid_array->rids[k].rid) {
+ keep_member = true;
+ break;
+ }
+ }
+ if (!keep_member) {
+ if (!add_rid_to_array_unique(ctx,
+ rid_array->rids[k].rid,
+ &del_rids, &num_del_rids)) {
+ werr = WERR_GEN_FAILURE;
+ goto done;
+ }
+ }
+ }
+
+ /* add list */
+
+ for (i=0; i < num_add_rids; i++) {
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_ADD_MEMBER,
+ add_rids[i],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_AddGroupMember(b, talloc_tos(),
+ &group_handle,
+ user_rids.ids[0],
+ 7 /* ? */,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+ }
+
+ /* del list */
+
+ for (i=0; i < num_del_rids; i++) {
+ status = dcerpc_samr_OpenGroup(b, talloc_tos(),
+ &domain_handle,
+ SAMR_GROUP_ACCESS_REMOVE_MEMBER,
+ del_rids[i],
+ &group_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_DeleteGroupMember(b, talloc_tos(),
+ &group_handle,
+ user_rids.ids[0],
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (is_valid_policy_hnd(&group_handle)) {
+ dcerpc_samr_Close(b, talloc_tos(), &group_handle, &result);
+ }
+
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserSetGroups_l(struct libnetapi_ctx *ctx,
+ struct NetUserSetGroups *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserSetGroups);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS add_LOCALGROUP_USERS_INFO_X_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ const char *group_name,
+ uint8_t **buffer,
+ uint32_t *num_entries)
+{
+ struct LOCALGROUP_USERS_INFO_0 u0;
+
+ switch (level) {
+ case 0:
+ u0.lgrui0_name = talloc_strdup(mem_ctx, group_name);
+ NT_STATUS_HAVE_NO_MEMORY(u0.lgrui0_name);
+
+ ADD_TO_ARRAY(mem_ctx, struct LOCALGROUP_USERS_INFO_0, u0,
+ (struct LOCALGROUP_USERS_INFO_0 **)buffer, num_entries);
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserGetLocalGroups_r(struct libnetapi_ctx *ctx,
+ struct NetUserGetLocalGroups *r)
+{
+ struct rpc_pipe_client *pipe_cli = NULL;
+ struct policy_handle connect_handle, domain_handle, user_handle,
+ builtin_handle;
+ struct lsa_String lsa_account_name;
+ struct dom_sid2 *domain_sid = NULL;
+ struct samr_Ids user_rids, name_types;
+ struct samr_RidWithAttributeArray *rid_array = NULL;
+ struct lsa_Strings names;
+ struct samr_Ids types;
+ uint32_t *rids = NULL;
+ size_t num_rids = 0;
+ struct dom_sid user_sid;
+ struct lsa_SidArray sid_array;
+ struct samr_Ids domain_rids;
+ struct samr_Ids builtin_rids;
+
+ int i;
+ uint32_t entries_read = 0;
+
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ *r->out.buffer = NULL;
+ *r->out.entries_read = 0;
+ *r->out.total_entries = 0;
+
+ switch (r->in.level) {
+ case 0:
+ case 1:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_open_pipe(ctx, r->in.server_name,
+ &ndr_table_samr,
+ &pipe_cli);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ b = pipe_cli->binding_handle;
+
+ werr = libnetapi_samr_open_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT |
+ SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS,
+ &connect_handle,
+ &domain_handle,
+ &domain_sid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = libnetapi_samr_open_builtin_domain(ctx, pipe_cli,
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT |
+ SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS,
+ &connect_handle,
+ &builtin_handle);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ init_lsa_String(&lsa_account_name, r->in.user_name);
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle,
+ 1,
+ &lsa_account_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, talloc_tos(),
+ &domain_handle,
+ SAMR_USER_ACCESS_GET_GROUPS,
+ user_rids.ids[0],
+ &user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ status = dcerpc_samr_GetGroupsForUser(b, talloc_tos(),
+ &user_handle,
+ &rid_array,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!sid_compose(&user_sid, domain_sid, user_rids.ids[0])) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ sid_array.num_sids = rid_array->count + 1;
+ sid_array.sids = talloc_array(ctx, struct lsa_SidPtr, sid_array.num_sids);
+ if (!sid_array.sids) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ sid_array.sids[0].sid = dom_sid_dup(ctx, &user_sid);
+ if (!sid_array.sids[0].sid) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i < rid_array->count; i++) {
+ struct dom_sid sid;
+
+ if (!sid_compose(&sid, domain_sid, rid_array->rids[i].rid)) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ sid_array.sids[i+1].sid = dom_sid_dup(ctx, &sid);
+ if (!sid_array.sids[i+1].sid) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+
+ status = dcerpc_samr_GetAliasMembership(b, talloc_tos(),
+ &domain_handle,
+ &sid_array,
+ &domain_rids,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ for (i=0; i < domain_rids.count; i++) {
+ if (!add_rid_to_array_unique(ctx, domain_rids.ids[i],
+ &rids, &num_rids)) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+
+ status = dcerpc_samr_GetAliasMembership(b, talloc_tos(),
+ &builtin_handle,
+ &sid_array,
+ &builtin_rids,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ for (i=0; i < builtin_rids.count; i++) {
+ if (!add_rid_to_array_unique(ctx, builtin_rids.ids[i],
+ &rids, &num_rids)) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+
+ status = dcerpc_samr_LookupRids(b, talloc_tos(),
+ &builtin_handle,
+ num_rids,
+ rids,
+ &names,
+ &types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (names.count != num_rids) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+ if (types.count != num_rids) {
+ werr = WERR_BAD_NET_RESP;
+ goto done;
+ }
+
+ for (i=0; i < names.count; i++) {
+ status = add_LOCALGROUP_USERS_INFO_X_buffer(ctx,
+ r->in.level,
+ names.names[i].string,
+ r->out.buffer,
+ &entries_read);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ *r->out.entries_read = entries_read;
+ *r->out.total_entries = entries_read;
+
+ done:
+ if (ctx->disable_policy_handle_cache) {
+ libnetapi_samr_close_domain_handle(ctx, &domain_handle);
+ libnetapi_samr_close_connect_handle(ctx, &connect_handle);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetUserGetLocalGroups_l(struct libnetapi_ctx *ctx,
+ struct NetUserGetLocalGroups *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetUserGetLocalGroups);
+}
diff --git a/source3/lib/netapi/wkstainfo.c b/source3/lib/netapi/wkstainfo.c
new file mode 100644
index 0000000..9cfcff4
--- /dev/null
+++ b/source3/lib/netapi/wkstainfo.c
@@ -0,0 +1,154 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NetApi Workstation Support
+ * Copyright (C) Guenther Deschner 2007
+ * Copyright (C) Hans Leidekker 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+#include "librpc/gen_ndr/libnetapi.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_private.h"
+#include "lib/netapi/libnetapi.h"
+#include "../librpc/gen_ndr/ndr_wkssvc_c.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_reg.h"
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetWkstaGetInfo_l(struct libnetapi_ctx *ctx,
+ struct NetWkstaGetInfo *r)
+{
+ LIBNETAPI_REDIRECT_TO_LOCALHOST(ctx, r, NetWkstaGetInfo);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS map_wksta_info_to_WKSTA_INFO_buffer(TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ union wkssvc_NetWkstaInfo *i,
+ uint8_t **buffer)
+{
+ struct WKSTA_INFO_100 i100;
+ struct WKSTA_INFO_101 i101;
+ struct WKSTA_INFO_102 i102;
+ uint32_t num_info = 0;
+
+ switch (level) {
+ case 100:
+ i100.wki100_platform_id = i->info100->platform_id;
+ i100.wki100_computername = talloc_strdup(mem_ctx, i->info100->server_name);
+ i100.wki100_langroup = talloc_strdup(mem_ctx, i->info100->domain_name);
+ i100.wki100_ver_major = i->info100->version_major;
+ i100.wki100_ver_minor = i->info100->version_minor;
+
+ ADD_TO_ARRAY(mem_ctx, struct WKSTA_INFO_100, i100,
+ (struct WKSTA_INFO_100 **)buffer,
+ &num_info);
+ break;
+
+ case 101:
+ i101.wki101_platform_id = i->info101->platform_id;
+ i101.wki101_computername = talloc_strdup(mem_ctx, i->info101->server_name);
+ i101.wki101_langroup = talloc_strdup(mem_ctx, i->info101->domain_name);
+ i101.wki101_ver_major = i->info101->version_major;
+ i101.wki101_ver_minor = i->info101->version_minor;
+ i101.wki101_lanroot = talloc_strdup(mem_ctx, i->info101->lan_root);
+
+ ADD_TO_ARRAY(mem_ctx, struct WKSTA_INFO_101, i101,
+ (struct WKSTA_INFO_101 **)buffer,
+ &num_info);
+ break;
+
+ case 102:
+ i102.wki102_platform_id = i->info102->platform_id;
+ i102.wki102_computername = talloc_strdup(mem_ctx, i->info102->server_name);
+ i102.wki102_langroup = talloc_strdup(mem_ctx, i->info102->domain_name);
+ i102.wki102_ver_major = i->info102->version_major;
+ i102.wki102_ver_minor = i->info102->version_minor;
+ i102.wki102_lanroot = talloc_strdup(mem_ctx, i->info102->lan_root);
+ i102.wki102_logged_on_users = i->info102->logged_on_users;
+
+ ADD_TO_ARRAY(mem_ctx, struct WKSTA_INFO_102, i102,
+ (struct WKSTA_INFO_102 **)buffer,
+ &num_info);
+ break;
+
+ default:
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR NetWkstaGetInfo_r(struct libnetapi_ctx *ctx,
+ struct NetWkstaGetInfo *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ union wkssvc_NetWkstaInfo info;
+ struct dcerpc_binding_handle *b;
+
+ if (!r->out.buffer) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 100:
+ case 101:
+ case 102:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ werr = libnetapi_get_binding_handle(ctx, r->in.server_name,
+ &ndr_table_wkssvc,
+ &b);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = dcerpc_wkssvc_NetWkstaGetInfo(b, talloc_tos(),
+ r->in.server_name,
+ r->in.level,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ status = map_wksta_info_to_WKSTA_INFO_buffer(ctx, r->in.level, &info,
+ r->out.buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ return werr;
+}
diff --git a/source3/lib/per_thread_cwd.c b/source3/lib/per_thread_cwd.c
new file mode 100644
index 0000000..b71d4c6
--- /dev/null
+++ b/source3/lib/per_thread_cwd.c
@@ -0,0 +1,156 @@
+/*
+ Unix SMB/Netbios implementation.
+
+ Copyright (C) Ralph Boehme 2019
+ Copyright (C) Stefan Metzmacher 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/threads.h"
+#ifdef HAVE_UNSHARE_CLONE_FS
+#include <sched.h>
+#endif /* HAVE_UNSHARE_CLONE_FS */
+
+static bool _per_thread_cwd_checked;
+static bool _per_thread_cwd_supported;
+#ifdef HAVE_UNSHARE_CLONE_FS
+static __thread bool _per_thread_cwd_disabled;
+static __thread bool _per_thread_cwd_activated;
+#endif /* HAVE_UNSHARE_CLONE_FS */
+
+/*
+ * This is the first function to be called!
+ * Typically in the main() function before
+ * any threads are created.
+ *
+ * This can be called multiple times
+ * as the result is cached the first time.
+ */
+void per_thread_cwd_check(void)
+{
+ if (_per_thread_cwd_checked) {
+ return;
+ }
+
+#ifdef HAVE_UNSHARE_CLONE_FS
+ /*
+ * While unshare(CLONE_FS) is available on
+ * Linux for ages, unshare() is also
+ * used to implement containers with various
+ * per container namespaces.
+ *
+ * It's possible that the whole unshare()
+ * is blocked in order to disallow nested
+ * containers.
+ *
+ * That's why we sadly need a runtime check
+ * for this.
+ */
+ {
+ int res;
+
+ res = unshare(CLONE_FS);
+ if (res == 0) {
+ _per_thread_cwd_supported = true;
+ }
+ }
+
+ /*
+ * We're in the main thread, so we should disallow
+ * per_thread_cwd_activate() here.
+ */
+ _per_thread_cwd_disabled = true;
+#endif /* HAVE_UNSHARE_CLONE_FS */
+
+ _per_thread_cwd_checked = true;
+}
+
+/*
+ * In order to use per_thread_cwd_supported()
+ * per_thread_cwd_check() needs to be called first!
+ * Otherwise an assert will be triggered!
+ */
+bool per_thread_cwd_supported(void)
+{
+ SMB_ASSERT(_per_thread_cwd_checked);
+ return _per_thread_cwd_supported;
+}
+
+/*
+ * In order to use per_thread_cwd_disable()
+ * should be called after any fork() in order
+ * to mark the main thread of the process,
+ * which should disallow per_thread_cwd_activate().
+ *
+ * This can be called without calling
+ * per_thread_cwd_check() first.
+ *
+ * And it can't be called after calling
+ * per_thread_cwd_activate()!
+ * Otherwise an assert will be triggered!
+ *
+ * This can be called multiple times
+ * as the result is cached the first time.
+ */
+void per_thread_cwd_disable(void)
+{
+#ifdef HAVE_UNSHARE_CLONE_FS
+ SMB_ASSERT(!_per_thread_cwd_activated);
+ if (_per_thread_cwd_disabled) {
+ return;
+ }
+ _per_thread_cwd_disabled = true;
+#endif /* HAVE_UNSHARE_CLONE_FS */
+}
+
+/*
+ * In order to use per_thread_cwd_activate()
+ * per_thread_cwd_supported() needs to be checked first!
+ * Otherwise an assert will be triggered!
+ *
+ * This MUST only be called within helper threads!
+ *
+ * That means it can't be called after calling
+ * per_thread_cwd_disable()!
+ * Otherwise an assert will be triggered!
+ *
+ * This can be called multiple times
+ * as the result is cached the first time.
+ */
+void per_thread_cwd_activate(void)
+{
+ SMB_ASSERT(_per_thread_cwd_checked);
+ SMB_ASSERT(_per_thread_cwd_supported);
+
+#ifdef HAVE_UNSHARE_CLONE_FS
+ if (_per_thread_cwd_activated) {
+ return;
+ }
+
+ SMB_ASSERT(!_per_thread_cwd_disabled);
+
+ {
+ int ret;
+ ret = unshare(CLONE_FS);
+ SMB_ASSERT(ret == 0);
+ }
+
+ _per_thread_cwd_activated = true;
+#else /* not HAVE_UNSHARE_CLONE_FS */
+ smb_panic(__location__);
+#endif /* not HAVE_UNSHARE_CLONE_FS */
+}
diff --git a/source3/lib/privileges.c b/source3/lib/privileges.c
new file mode 100644
index 0000000..05a4c9a
--- /dev/null
+++ b/source3/lib/privileges.c
@@ -0,0 +1,514 @@
+/*
+ Unix SMB/CIFS implementation.
+ Privileges handling functions
+ Copyright (C) Jean François Micouleau 1998-2001
+ Copyright (C) Simo Sorce 2002-2003
+ Copyright (C) Gerald (Jerry) Carter 2005
+ Copyright (C) Michael Adam 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "lib/privileges.h"
+#include "dbwrap/dbwrap.h"
+#include "libcli/security/privileges_private.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "lib/util/string_wrappers.h"
+
+#define PRIVPREFIX "PRIV_"
+
+typedef struct {
+ uint32_t count;
+ struct dom_sid *list;
+} SID_LIST;
+
+typedef struct {
+ TALLOC_CTX *mem_ctx;
+ uint64_t privilege;
+ SID_LIST sids;
+} PRIV_SID_LIST;
+
+/*
+ interpret an old style SE_PRIV structure
+ */
+static uint64_t map_old_SE_PRIV(unsigned char *dptr)
+{
+ uint32_t *old_masks = (uint32_t *)dptr;
+ /*
+ * the old privileges code only ever used up to 0x800, except
+ * for a special case of 'SE_ALL_PRIVS' which was 0xFFFFFFFF
+ */
+ if (old_masks[0] == 0xFFFFFFFF) {
+ /* they set all privileges */
+ return SE_ALL_PRIVS;
+ }
+
+ /* the old code used the machine byte order, but we don't know
+ * the byte order of the machine that wrote it. However we can
+ * tell what byte order it was by taking advantage of the fact
+ * that it only ever use up to 0x800
+ */
+ if (dptr[0] || dptr[1]) {
+ /* it was little endian */
+ return IVAL(dptr, 0);
+ }
+
+ /* it was either zero or big-endian */
+ return RIVAL(dptr, 0);
+}
+
+
+static bool get_privileges( const struct dom_sid *sid, uint64_t *mask )
+{
+ struct db_context *db = get_account_pol_db();
+ struct dom_sid_buf tmp;
+ fstring keystr;
+ TDB_DATA data;
+ NTSTATUS status;
+
+ /* Fail if the admin has not enable privileges */
+
+ if ( !lp_enable_privileges() ) {
+ return False;
+ }
+
+ if ( db == NULL )
+ return False;
+
+ /* PRIV_<SID> (NULL terminated) as the key */
+
+ fstr_sprintf(keystr, "%s%s", PRIVPREFIX, dom_sid_str_buf(sid, &tmp));
+
+ status = dbwrap_fetch_bystring(db, talloc_tos(), keystr, &data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(4, ("get_privileges: No privileges assigned to SID "
+ "[%s]\n", tmp.buf));
+ return False;
+ }
+
+ if (data.dsize == 4*4) {
+ /* it's an old style SE_PRIV structure. */
+ *mask = map_old_SE_PRIV(data.dptr);
+ } else {
+ if (data.dsize != sizeof( uint64_t ) ) {
+ DEBUG(3, ("get_privileges: Invalid privileges record assigned to SID "
+ "[%s]\n", tmp.buf));
+ return False;
+ }
+
+ *mask = BVAL(data.dptr, 0);
+ }
+
+ TALLOC_FREE(data.dptr);
+
+ return True;
+}
+
+/***************************************************************************
+ Store the privilege mask (set) for a given SID
+****************************************************************************/
+
+static bool set_privileges( const struct dom_sid *sid, uint64_t mask )
+{
+ struct db_context *db = get_account_pol_db();
+ uint8_t privbuf[8];
+ struct dom_sid_buf tmp;
+ fstring keystr;
+ TDB_DATA data = { .dptr = privbuf, .dsize = sizeof(privbuf), };
+
+ if ( !lp_enable_privileges() )
+ return False;
+
+ if ( db == NULL )
+ return False;
+
+ if ( !sid || (sid->num_auths == 0) ) {
+ DEBUG(0,("set_privileges: Refusing to store empty SID!\n"));
+ return False;
+ }
+
+ /* PRIV_<SID> (NULL terminated) as the key */
+
+ fstr_sprintf(keystr, "%s%s", PRIVPREFIX, dom_sid_str_buf(sid, &tmp));
+
+ /* This writes the 64 bit bitmask out in little endian format */
+ SBVAL(privbuf,0,mask);
+
+ return NT_STATUS_IS_OK(dbwrap_store_bystring(db, keystr, data,
+ TDB_REPLACE));
+}
+
+/*********************************************************************
+ get a list of all privileges for all sids in the list
+*********************************************************************/
+
+bool get_privileges_for_sids(uint64_t *privileges, struct dom_sid *slist, int scount)
+{
+ uint64_t mask;
+ int i;
+ bool found = False;
+
+ *privileges = 0;
+
+ for ( i=0; i<scount; i++ ) {
+ struct dom_sid_buf buf;
+
+ /* don't add unless we actually have a privilege assigned */
+
+ if ( !get_privileges( &slist[i], &mask ) )
+ continue;
+
+ DBG_INFO("sid = %s\nPrivilege set: 0x%"PRIx64"\n",
+ dom_sid_str_buf(&slist[i], &buf),
+ mask);
+
+ *privileges |= mask;
+ found = True;
+ }
+
+ return found;
+}
+
+NTSTATUS get_privileges_for_sid_as_set(TALLOC_CTX *mem_ctx, PRIVILEGE_SET **privileges, struct dom_sid *sid)
+{
+ uint64_t mask;
+ if (!get_privileges(sid, &mask)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ *privileges = talloc_zero(mem_ctx, PRIVILEGE_SET);
+ if (!*privileges) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!se_priv_to_privilege_set(*privileges, mask)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ traversal functions for privilege_enumerate_accounts
+*********************************************************************/
+
+static int priv_traverse_fn(struct db_record *rec, void *state)
+{
+ PRIV_SID_LIST *priv = (PRIV_SID_LIST *)state;
+ int prefixlen = strlen(PRIVPREFIX);
+ struct dom_sid sid;
+ fstring sid_string;
+ TDB_DATA key;
+
+ key = dbwrap_record_get_key(rec);
+
+ /* check we have a PRIV_+SID entry */
+
+ if (strncmp((char *)key.dptr, PRIVPREFIX, prefixlen) != 0)
+ return 0;
+
+ /* check to see if we are looking for a particular privilege */
+
+ fstrcpy( sid_string, (char *)&(key.dptr[strlen(PRIVPREFIX)]) );
+
+ if (priv->privilege != 0) {
+ uint64_t mask;
+ TDB_DATA value;
+
+ value = dbwrap_record_get_value(rec);
+
+ if (value.dsize == 4*4) {
+ mask = map_old_SE_PRIV(value.dptr);
+ } else {
+ if (value.dsize != sizeof( uint64_t ) ) {
+ DEBUG(3, ("get_privileges: Invalid privileges record assigned to SID "
+ "[%s]\n", sid_string));
+ return 0;
+ }
+ mask = BVAL(value.dptr, 0);
+ }
+
+ /* if the SID does not have the specified privilege
+ then just return */
+
+ if ((mask & priv->privilege) == 0) {
+ return 0;
+ }
+ }
+
+ /* this is a last ditch safety check to preventing returning
+ and invalid SID (i've somehow run into this on development branches) */
+
+ if ( strcmp( "S-0-0", sid_string ) == 0 )
+ return 0;
+
+ if ( !string_to_sid(&sid, sid_string) ) {
+ DBG_WARNING("Could not convert SID [%s]\n", sid_string);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(add_sid_to_array(priv->mem_ctx, &sid,
+ &priv->sids.list,
+ &priv->sids.count)))
+ {
+ return 0;
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ Retrieve list of privileged SIDs (for _lsa_enumerate_accounts()
+*********************************************************************/
+
+NTSTATUS privilege_enumerate_accounts(struct dom_sid **sids, int *num_sids)
+{
+ struct db_context *db = get_account_pol_db();
+ PRIV_SID_LIST priv;
+ NTSTATUS status;
+
+ if (db == NULL) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ ZERO_STRUCT(priv);
+
+ status = dbwrap_traverse_read(db, priv_traverse_fn, &priv, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* give the memory away; caller will free */
+
+ *sids = priv.sids.list;
+ *num_sids = priv.sids.count;
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ Retrieve list of SIDs granted a particular privilege
+*********************************************************************/
+
+NTSTATUS privilege_enum_sids(enum sec_privilege privilege, TALLOC_CTX *mem_ctx,
+ struct dom_sid **sids, int *num_sids)
+{
+ struct db_context *db = get_account_pol_db();
+ PRIV_SID_LIST priv;
+ NTSTATUS status;
+
+ if (db == NULL) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ ZERO_STRUCT(priv);
+
+ priv.privilege = sec_privilege_mask(privilege);
+ priv.mem_ctx = mem_ctx;
+
+ status = dbwrap_traverse_read(db, priv_traverse_fn, &priv, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* give the memory away; caller will free */
+
+ *sids = priv.sids.list;
+ *num_sids = priv.sids.count;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ Add privilege to sid
+****************************************************************************/
+
+static bool grant_privilege_bitmap(const struct dom_sid *sid, const uint64_t priv_mask)
+{
+ uint64_t old_mask = 0, new_mask = 0;
+ struct dom_sid_buf buf;
+
+ if ( get_privileges( sid, &old_mask ) ) {
+ new_mask = old_mask;
+ }
+
+ new_mask |= priv_mask;
+
+ DBG_DEBUG("%s\n"
+ "original privilege mask: 0x%"PRIx64"\n"
+ "new privilege mask: 0x%"PRIx64"\n",
+ dom_sid_str_buf(sid, &buf),
+ old_mask,
+ new_mask);
+
+ return set_privileges( sid, new_mask );
+}
+
+/*********************************************************************
+ Add a privilege based on its name
+*********************************************************************/
+
+bool grant_privilege_by_name(const struct dom_sid *sid, const char *name)
+{
+ uint64_t mask;
+
+ if (! se_priv_from_name(name, &mask)) {
+ DEBUG(3, ("grant_privilege_by_name: "
+ "No Such Privilege Found (%s)\n", name));
+ return False;
+ }
+
+ return grant_privilege_bitmap( sid, mask );
+}
+
+/***************************************************************************
+ Grant a privilege set (list of LUID values) from a sid
+****************************************************************************/
+
+bool grant_privilege_set(const struct dom_sid *sid, struct lsa_PrivilegeSet *set)
+{
+ uint64_t privilege_mask;
+ if (!privilege_set_to_se_priv(&privilege_mask, set)) {
+ return false;
+ }
+ return grant_privilege_bitmap(sid, privilege_mask);
+}
+
+/***************************************************************************
+ Remove privilege from sid
+****************************************************************************/
+
+static bool revoke_privilege_bitmap(const struct dom_sid *sid, const uint64_t priv_mask)
+{
+ uint64_t mask;
+ struct dom_sid_buf buf;
+
+ /* if the user has no privileges, then we can't revoke any */
+
+ if ( !get_privileges( sid, &mask ) )
+ return True;
+
+ DEBUG(10,("revoke_privilege: %s\n", dom_sid_str_buf(sid, &buf)));
+
+ DEBUGADD( 10, ("original privilege mask: 0x%llx\n", (unsigned long long)mask));
+
+ mask &= ~priv_mask;
+
+ DEBUGADD( 10, ("new privilege mask: 0x%llx\n", (unsigned long long)mask));
+
+ return set_privileges( sid, mask );
+}
+
+/***************************************************************************
+ Remove a privilege set (list of LUID values) from a sid
+****************************************************************************/
+
+bool revoke_privilege_set(const struct dom_sid *sid, struct lsa_PrivilegeSet *set)
+{
+ uint64_t privilege_mask;
+ if (!privilege_set_to_se_priv(&privilege_mask, set)) {
+ return false;
+ }
+ return revoke_privilege_bitmap(sid, privilege_mask);
+}
+
+/*********************************************************************
+ Revoke all privileges
+*********************************************************************/
+
+bool revoke_all_privileges( const struct dom_sid *sid )
+{
+ return revoke_privilege_bitmap( sid, SE_ALL_PRIVS);
+}
+
+/*********************************************************************
+ Add a privilege based on its name
+*********************************************************************/
+
+bool revoke_privilege_by_name(const struct dom_sid *sid, const char *name)
+{
+ uint64_t mask;
+
+ if (! se_priv_from_name(name, &mask)) {
+ DEBUG(3, ("revoke_privilege_by_name: "
+ "No Such Privilege Found (%s)\n", name));
+ return False;
+ }
+
+ return revoke_privilege_bitmap(sid, mask);
+
+}
+
+/***************************************************************************
+ Retrieve the SIDs assigned to a given privilege
+****************************************************************************/
+
+NTSTATUS privilege_create_account(const struct dom_sid *sid )
+{
+ return ( grant_privilege_bitmap(sid, 0) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL);
+}
+
+/***************************************************************************
+ Delete a privileged account
+****************************************************************************/
+
+NTSTATUS privilege_delete_account(const struct dom_sid *sid)
+{
+ struct db_context *db = get_account_pol_db();
+ struct dom_sid_buf tmp;
+ fstring keystr;
+
+ if (!lp_enable_privileges()) {
+ return NT_STATUS_OK;
+ }
+
+ if (!db) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!sid || (sid->num_auths == 0)) {
+ return NT_STATUS_INVALID_SID;
+ }
+
+ /* PRIV_<SID> (NULL terminated) as the key */
+
+ fstr_sprintf(keystr, "%s%s", PRIVPREFIX, dom_sid_str_buf(sid, &tmp));
+
+ return dbwrap_delete_bystring(db, keystr);
+}
+
+/*******************************************************************
+*******************************************************************/
+
+bool is_privileged_sid( const struct dom_sid *sid )
+{
+ uint64_t mask;
+
+ return get_privileges( sid, &mask );
+}
+
+/*******************************************************************
+*******************************************************************/
+
+bool grant_all_privileges( const struct dom_sid *sid )
+{
+ uint64_t mask;
+
+ se_priv_put_all_privileges(&mask);
+
+ return grant_privilege_bitmap( sid, mask );
+}
diff --git a/source3/lib/privileges.h b/source3/lib/privileges.h
new file mode 100644
index 0000000..ca2a7c9
--- /dev/null
+++ b/source3/lib/privileges.h
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+ Privileges handling functions
+ Copyright (C) Jean François Micouleau 1998-2001
+ Copyright (C) Simo Sorce 2002-2003
+ Copyright (C) Gerald (Jerry) Carter 2005
+ Copyright (C) Michael Adam 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIB_PRIVILEGES_H_
+#define _LIB_PRIVILEGES_H_
+
+#include "../libcli/security/privileges.h"
+
+/* The following definitions come from lib/privileges.c */
+
+bool get_privileges_for_sids(uint64_t *privileges, struct dom_sid *slist, int scount);
+NTSTATUS get_privileges_for_sid_as_set(TALLOC_CTX *mem_ctx, PRIVILEGE_SET **privileges, struct dom_sid *sid);
+NTSTATUS privilege_enumerate_accounts(struct dom_sid **sids, int *num_sids);
+NTSTATUS privilege_enum_sids(enum sec_privilege privilege, TALLOC_CTX *mem_ctx,
+ struct dom_sid **sids, int *num_sids);
+bool grant_privilege_set(const struct dom_sid *sid, struct lsa_PrivilegeSet *set);
+bool grant_privilege_by_name( const struct dom_sid *sid, const char *name);
+bool revoke_all_privileges( const struct dom_sid *sid );
+bool revoke_privilege_set(const struct dom_sid *sid, struct lsa_PrivilegeSet *set);
+bool revoke_privilege_by_name(const struct dom_sid *sid, const char *name);
+NTSTATUS privilege_create_account(const struct dom_sid *sid );
+NTSTATUS privilege_delete_account(const struct dom_sid *sid);
+bool is_privileged_sid( const struct dom_sid *sid );
+bool grant_all_privileges( const struct dom_sid *sid );
+
+#endif /* _LIB_PRIVILEGES_H_ */
diff --git a/source3/lib/readdir_attr.h b/source3/lib/readdir_attr.h
new file mode 100644
index 0000000..d2a814d
--- /dev/null
+++ b/source3/lib/readdir_attr.h
@@ -0,0 +1,37 @@
+/*
+ * Fetch filesystem metadata in readdir/marshall context
+ *
+ * Copyright (C) Ralph Boehme 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _READDIR_ATTR_H
+#define _READDIR_ATTR_H
+
+enum readdir_attr_type {RDATTR_NONE, RDATTR_AAPL};
+
+struct readdir_attr_data {
+ enum readdir_attr_type type;
+ union attr_data {
+ struct aapl {
+ uint64_t rfork_size;
+ char finder_info[16];
+ uint32_t max_access;
+ mode_t unix_mode;
+ } aapl;
+ } attr_data;
+};
+
+#endif /* _READDIR_ATTR_H */
diff --git a/source3/lib/recvfile.c b/source3/lib/recvfile.c
new file mode 100644
index 0000000..e1eb241
--- /dev/null
+++ b/source3/lib/recvfile.c
@@ -0,0 +1,310 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.2.x
+ recvfile implementations.
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This file handles the OS dependent recvfile implementations.
+ * The API is such that it returns -1 on error, else returns the
+ * number of bytes written.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/sys_rw.h"
+
+/* Do this on our own in TRANSFER_BUF_SIZE chunks.
+ * It's safe to make direct syscalls to lseek/write here
+ * as we're below the Samba vfs layer.
+ *
+ * Returns -1 on short reads from fromfd (read error)
+ * and sets errno.
+ *
+ * Returns number of bytes written to 'tofd'
+ * return != count then sets errno.
+ * Returns count if complete success.
+ */
+
+#ifndef TRANSFER_BUF_SIZE
+#define TRANSFER_BUF_SIZE (128*1024)
+#endif
+
+static ssize_t default_sys_recvfile(int fromfd,
+ int tofd,
+ off_t offset,
+ size_t count)
+{
+ int saved_errno = 0;
+ size_t total = 0;
+ size_t bufsize = MIN(TRANSFER_BUF_SIZE,count);
+ size_t total_written = 0;
+ char buffer[bufsize];
+
+ DEBUG(10,("default_sys_recvfile: from = %d, to = %d, "
+ "offset=%.0f, count = %lu\n",
+ fromfd, tofd, (double)offset,
+ (unsigned long)count));
+
+ if (count == 0) {
+ return 0;
+ }
+
+ if (tofd != -1 && offset != (off_t)-1) {
+ if (lseek(tofd, offset, SEEK_SET) == -1) {
+ if (errno != ESPIPE) {
+ return -1;
+ }
+ }
+ }
+
+ while (total < count) {
+ size_t num_written = 0;
+ ssize_t read_ret;
+ size_t toread = MIN(bufsize,count - total);
+
+ /*
+ * Read from socket - ignore EINTR.
+ * Can't use sys_read() as that also
+ * ignores EAGAIN and EWOULDBLOCK.
+ */
+ do {
+ read_ret = read(fromfd, buffer, toread);
+ } while (read_ret == -1 && errno == EINTR);
+
+ if (read_ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ /*
+ * fromfd socket is in non-blocking mode.
+ * If we already read some and wrote
+ * it successfully, return that.
+ * Only return -1 if this is the first read
+ * attempt. Caller will handle both cases.
+ */
+ if (total_written != 0) {
+ return total_written;
+ }
+ return -1;
+ }
+
+ if (read_ret <= 0) {
+ /* EOF or socket error. */
+ return -1;
+ }
+
+ num_written = 0;
+
+ /* Don't write any more after a write error. */
+ while (tofd != -1 && (num_written < read_ret)) {
+ ssize_t write_ret;
+
+ /* Write to file - ignore EINTR. */
+ write_ret = sys_write(tofd,
+ buffer + num_written,
+ read_ret - num_written);
+
+ if (write_ret <= 0) {
+ /* write error - stop writing. */
+ tofd = -1;
+ if (total_written == 0) {
+ /* Ensure we return
+ -1 if the first
+ write failed. */
+ total_written = -1;
+ }
+ saved_errno = errno;
+ break;
+ }
+
+ num_written += (size_t)write_ret;
+ total_written += (size_t)write_ret;
+ }
+
+ total += read_ret;
+ }
+
+ if (saved_errno) {
+ /* Return the correct write error. */
+ errno = saved_errno;
+ }
+ return (ssize_t)total_written;
+}
+
+#if defined(HAVE_LINUX_SPLICE)
+
+/*
+ * Try and use the Linux system call to do this.
+ * Remember we only return -1 if the socket read
+ * failed. Else we return the number of bytes
+ * actually written. We always read count bytes
+ * from the network in the case of return != -1.
+ */
+
+
+ssize_t sys_recvfile(int fromfd,
+ int tofd,
+ off_t offset,
+ size_t count)
+{
+ static int pipefd[2] = { -1, -1 };
+ static bool try_splice_call = false;
+ size_t total_written = 0;
+ loff_t splice_offset = offset;
+
+ DEBUG(10,("sys_recvfile: from = %d, to = %d, "
+ "offset=%.0f, count = %lu\n",
+ fromfd, tofd, (double)offset,
+ (unsigned long)count));
+
+ if (count == 0) {
+ return 0;
+ }
+
+ /*
+ * Older Linux kernels have splice for sendfile,
+ * but it fails for recvfile. Ensure we only try
+ * this once and always fall back to the userspace
+ * implementation if recvfile splice fails. JRA.
+ */
+
+ if (!try_splice_call) {
+ return default_sys_recvfile(fromfd,
+ tofd,
+ offset,
+ count);
+ }
+
+ if ((pipefd[0] == -1) && (pipe(pipefd) == -1)) {
+ try_splice_call = false;
+ return default_sys_recvfile(fromfd, tofd, offset, count);
+ }
+
+ while (count > 0) {
+ int nread, to_write;
+
+ nread = splice(fromfd, NULL, pipefd[1], NULL,
+ MIN(count, 16384), SPLICE_F_MOVE);
+ if (nread == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ if (total_written == 0 &&
+ (errno == EBADF || errno == EINVAL)) {
+ try_splice_call = false;
+ return default_sys_recvfile(fromfd, tofd,
+ offset, count);
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /*
+ * fromfd socket is in non-blocking mode.
+ * If we already read some and wrote
+ * it successfully, return that.
+ * Only return -1 if this is the first read
+ * attempt. Caller will handle both cases.
+ */
+ if (total_written != 0) {
+ return total_written;
+ }
+ return -1;
+ }
+ break;
+ }
+
+ to_write = nread;
+ while (to_write > 0) {
+ int thistime;
+ thistime = splice(pipefd[0], NULL, tofd,
+ &splice_offset, to_write,
+ SPLICE_F_MOVE);
+ if (thistime == -1) {
+ goto done;
+ }
+ to_write -= thistime;
+ }
+
+ total_written += nread;
+ count -= nread;
+ }
+
+ done:
+ if (count) {
+ int saved_errno = errno;
+ if (drain_socket(fromfd, count) != count) {
+ /* socket is dead. */
+ return -1;
+ }
+ errno = saved_errno;
+ }
+
+ return total_written;
+}
+#else
+
+/*****************************************************************
+ No recvfile system call - use the default 128 chunk implementation.
+*****************************************************************/
+
+ssize_t sys_recvfile(int fromfd,
+ int tofd,
+ off_t offset,
+ size_t count)
+{
+ return default_sys_recvfile(fromfd, tofd, offset, count);
+}
+#endif
+
+/*****************************************************************
+ Throw away "count" bytes from the client socket.
+ Returns count or -1 on error.
+ Must only operate on a blocking socket.
+*****************************************************************/
+
+ssize_t drain_socket(int sockfd, size_t count)
+{
+ size_t total = 0;
+ size_t bufsize = MIN(TRANSFER_BUF_SIZE,count);
+ char buffer[bufsize];
+ int old_flags = 0;
+
+ if (count == 0) {
+ return 0;
+ }
+
+ old_flags = fcntl(sockfd, F_GETFL, 0);
+ if (set_blocking(sockfd, true) == -1) {
+ return -1;
+ }
+
+ while (total < count) {
+ ssize_t read_ret;
+ size_t toread = MIN(bufsize,count - total);
+
+ /* Read from socket - ignore EINTR. */
+ read_ret = sys_read(sockfd, buffer, toread);
+ if (read_ret <= 0) {
+ /* EOF or socket error. */
+ count = (size_t)-1;
+ goto out;
+ }
+ total += read_ret;
+ }
+
+ out:
+
+ if (fcntl(sockfd, F_SETFL, old_flags) == -1) {
+ return -1;
+ }
+ return count;
+}
diff --git a/source3/lib/sendfile.c b/source3/lib/sendfile.c
new file mode 100644
index 0000000..69c89d2
--- /dev/null
+++ b/source3/lib/sendfile.c
@@ -0,0 +1,602 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 2.2.x / 3.0.x
+ sendfile implementations.
+ Copyright (C) Jeremy Allison 2002.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This file handles the OS dependent sendfile implementations.
+ * The API is such that it returns -1 on error, else returns the
+ * number of bytes written.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+
+#if defined(LINUX_SENDFILE_API)
+
+#include <sys/sendfile.h>
+
+#ifndef MSG_MORE
+#define MSG_MORE 0x8000
+#endif
+
+ssize_t sys_sendfile(int tofd, int fromfd, const DATA_BLOB *header, off_t offset, size_t count)
+{
+ size_t total=0;
+ ssize_t ret = -1;
+ size_t hdr_len = 0;
+ int old_flags = 0;
+ bool socket_flags_changed = false;
+
+ /*
+ * Send the header first.
+ * Use MSG_MORE to cork the TCP output until sendfile is called.
+ */
+
+ if (header) {
+ hdr_len = header->length;
+ while (total < hdr_len) {
+ ret = sys_send(tofd, header->data + total,hdr_len - total, MSG_MORE);
+ if (ret == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /*
+ * send() must complete before we can
+ * send any other outgoing data on the
+ * socket. Ensure socket is in blocking
+ * mode. For SMB2 by default the socket
+ * is in non-blocking mode.
+ */
+ old_flags = fcntl(tofd, F_GETFL, 0);
+ ret = set_blocking(tofd, true);
+ if (ret == -1) {
+ goto out;
+ }
+ socket_flags_changed = true;
+ continue;
+ }
+ goto out;
+ }
+ total += ret;
+ }
+ }
+
+ total = count;
+ while (total) {
+ ssize_t nwritten;
+ do {
+ nwritten = sendfile(tofd, fromfd, &offset, total);
+ } while (nwritten == -1 && errno == EINTR);
+ if (nwritten == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ if (socket_flags_changed) {
+ /*
+ * We're already in blocking
+ * mode. This is an error.
+ */
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * Sendfile must complete before we can
+ * send any other outgoing data on the socket.
+ * Ensure socket is in blocking mode.
+ * For SMB2 by default the socket is in
+ * non-blocking mode.
+ */
+ old_flags = fcntl(tofd, F_GETFL, 0);
+ ret = set_blocking(tofd, true);
+ if (ret == -1) {
+ goto out;
+ }
+ socket_flags_changed = true;
+ continue;
+ }
+
+ if (errno == ENOSYS || errno == EINVAL) {
+ /* Ok - we're in a world of pain here. We just sent
+ * the header, but the sendfile failed. We have to
+ * emulate the sendfile at an upper layer before we
+ * disable it's use. So we do something really ugly.
+ * We set the errno to a strange value so we can detect
+ * this at the upper level and take care of it without
+ * layer violation. JRA.
+ */
+ errno = EINTR; /* Normally we can never return this. */
+ }
+ ret = -1;
+ goto out;
+ }
+ if (nwritten == 0) {
+ /*
+ * EOF, return a short read
+ */
+ ret = hdr_len + (count - total);
+ goto out;
+ }
+ total -= nwritten;
+ }
+
+ ret = count + hdr_len;
+
+ out:
+
+ if (socket_flags_changed) {
+ int saved_errno = errno;
+ int err;
+
+ /* Restore the old state of the socket. */
+ err = fcntl(tofd, F_SETFL, old_flags);
+ if (err == -1) {
+ return -1;
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ }
+ }
+
+ return ret;
+}
+
+#elif defined(SOLARIS_SENDFILE_API)
+
+/*
+ * Solaris sendfile code written by Pierre Belanger <belanger@pobox.com>.
+ */
+
+#include <sys/sendfile.h>
+
+ssize_t sys_sendfile(int tofd, int fromfd, const DATA_BLOB *header, off_t offset, size_t count)
+{
+ int sfvcnt;
+ size_t total, xferred;
+ struct sendfilevec vec[2];
+ ssize_t hdr_len = 0;
+ int old_flags = 0;
+ ssize_t ret = -1;
+ bool socket_flags_changed = false;
+
+ if (header) {
+ sfvcnt = 2;
+
+ vec[0].sfv_fd = SFV_FD_SELF;
+ vec[0].sfv_flag = 0;
+ vec[0].sfv_off = (off_t)header->data;
+ vec[0].sfv_len = hdr_len = header->length;
+
+ vec[1].sfv_fd = fromfd;
+ vec[1].sfv_flag = 0;
+ vec[1].sfv_off = offset;
+ vec[1].sfv_len = count;
+
+ } else {
+ sfvcnt = 1;
+
+ vec[0].sfv_fd = fromfd;
+ vec[0].sfv_flag = 0;
+ vec[0].sfv_off = offset;
+ vec[0].sfv_len = count;
+ }
+
+ total = count + hdr_len;
+
+ while (total) {
+ ssize_t nwritten;
+
+ /*
+ * Although not listed in the API error returns, this is almost certainly
+ * a slow system call and will be interrupted by a signal with EINTR. JRA.
+ */
+
+ xferred = 0;
+
+ nwritten = sendfilev(tofd, vec, sfvcnt, &xferred);
+ if (nwritten == -1 && errno == EINTR) {
+ if (xferred == 0)
+ continue; /* Nothing written yet. */
+ else
+ nwritten = xferred;
+ }
+
+ if (nwritten == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /*
+ * Sendfile must complete before we can
+ * send any other outgoing data on the socket.
+ * Ensure socket is in blocking mode.
+ * For SMB2 by default the socket is in
+ * non-blocking mode.
+ */
+ old_flags = fcntl(tofd, F_GETFL, 0);
+ ret = set_blocking(tofd, true);
+ if (ret == -1) {
+ goto out;
+ }
+ socket_flags_changed = true;
+ continue;
+ }
+ ret = -1;
+ goto out;
+ }
+ if (nwritten == 0) {
+ ret = -1;
+ goto out; /* I think we're at EOF here... */
+ }
+
+ /*
+ * If this was a short (signal interrupted) write we may need
+ * to subtract it from the header data, or null out the header
+ * data altogether if we wrote more than vec[0].sfv_len bytes.
+ * We move vec[1].* to vec[0].* and set sfvcnt to 1
+ */
+
+ if (sfvcnt == 2 && nwritten >= vec[0].sfv_len) {
+ vec[1].sfv_off += nwritten - vec[0].sfv_len;
+ vec[1].sfv_len -= nwritten - vec[0].sfv_len;
+
+ /* Move vec[1].* to vec[0].* and set sfvcnt to 1 */
+ vec[0] = vec[1];
+ sfvcnt = 1;
+ } else {
+ vec[0].sfv_off += nwritten;
+ vec[0].sfv_len -= nwritten;
+ }
+ total -= nwritten;
+ }
+ ret = count + hdr_len;
+
+ out:
+
+ if (socket_flags_changed) {
+ int saved_errno;
+ int err;
+
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ /* Restore the old state of the socket. */
+ err = fcntl(tofd, F_SETFL, old_flags);
+ if (err == -1) {
+ return -1;
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ }
+ }
+
+ return ret;
+}
+
+#elif defined(HPUX_SENDFILE_API)
+
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+ssize_t sys_sendfile(int tofd, int fromfd, const DATA_BLOB *header, off_t offset, size_t count)
+{
+ size_t total=0;
+ struct iovec hdtrl[2];
+ size_t hdr_len = 0;
+ int old_flags = 0;
+ ssize_t ret = -1;
+ bool socket_flags_changed = false;
+
+ if (header) {
+ /* Set up the header/trailer iovec. */
+ hdtrl[0].iov_base = (void *)header->data;
+ hdtrl[0].iov_len = hdr_len = header->length;
+ } else {
+ hdtrl[0].iov_base = NULL;
+ hdtrl[0].iov_len = hdr_len = 0;
+ }
+ hdtrl[1].iov_base = NULL;
+ hdtrl[1].iov_len = 0;
+
+ total = count;
+ while (total + hdtrl[0].iov_len) {
+ ssize_t nwritten;
+
+ /*
+ * HPUX guarantees that if any data was written before
+ * a signal interrupt then sendfile returns the number of
+ * bytes written (which may be less than requested) not -1.
+ * nwritten includes the header data sent.
+ */
+
+ do {
+ nwritten = sendfile(tofd, fromfd, offset, total, &hdtrl[0], 0);
+ } while (nwritten == -1 && errno == EINTR);
+ if (nwritten == -1) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /*
+ * Sendfile must complete before we can
+ * send any other outgoing data on the socket.
+ * Ensure socket is in blocking mode.
+ * For SMB2 by default the socket is in
+ * non-blocking mode.
+ */
+ old_flags = fcntl(tofd, F_GETFL, 0);
+ ret = set_blocking(tofd, true);
+ if (ret == -1) {
+ goto out;
+ }
+ socket_flags_changed = true;
+ continue;
+ }
+ ret = -1;
+ goto out;
+ }
+ if (nwritten == 0) {
+ ret = -1; /* I think we're at EOF here... */
+ goto out;
+ }
+
+ /*
+ * If this was a short (signal interrupted) write we may need
+ * to subtract it from the header data, or null out the header
+ * data altogether if we wrote more than hdtrl[0].iov_len bytes.
+ * We change nwritten to be the number of file bytes written.
+ */
+
+ if (hdtrl[0].iov_base && hdtrl[0].iov_len) {
+ if (nwritten >= hdtrl[0].iov_len) {
+ nwritten -= hdtrl[0].iov_len;
+ hdtrl[0].iov_base = NULL;
+ hdtrl[0].iov_len = 0;
+ } else {
+ /* iov_base is defined as a void *... */
+ hdtrl[0].iov_base = (void *)(((char *)hdtrl[0].iov_base) + nwritten);
+ hdtrl[0].iov_len -= nwritten;
+ nwritten = 0;
+ }
+ }
+ total -= nwritten;
+ offset += nwritten;
+ }
+ ret = count + hdr_len;
+
+ out:
+
+ if (socket_flags_changed) {
+ int saved_errno;
+ int err;
+
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ /* Restore the old state of the socket. */
+ err = fcntl(tofd, F_SETFL, old_flags);
+ if (err == -1) {
+ return -1;
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ }
+ }
+
+ return ret;
+}
+
+#elif defined(FREEBSD_SENDFILE_API) || defined(DARWIN_SENDFILE_API)
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+ssize_t sys_sendfile(int tofd, int fromfd,
+ const DATA_BLOB *header, off_t offset, size_t count)
+{
+ struct sf_hdtr sf_header = {0};
+ struct iovec io_header = {0};
+ int old_flags = 0;
+
+ off_t nwritten;
+ ssize_t ret = -1;
+ bool socket_flags_changed = false;
+
+ if (header) {
+ sf_header.headers = &io_header;
+ sf_header.hdr_cnt = 1;
+ io_header.iov_base = header->data;
+ io_header.iov_len = header->length;
+ sf_header.trailers = NULL;
+ sf_header.trl_cnt = 0;
+ }
+
+ while (count != 0) {
+
+ nwritten = count;
+#if defined(DARWIN_SENDFILE_API)
+ /* Darwin recycles nwritten as a value-result parameter, apart from that this
+ sendfile implementation is quite the same as the FreeBSD one */
+ ret = sendfile(fromfd, tofd, offset, &nwritten, &sf_header, 0);
+#else
+ ret = sendfile(fromfd, tofd, offset, count, &sf_header, &nwritten, 0);
+#endif
+ if (ret == -1 && errno != EINTR) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /*
+ * Sendfile must complete before we can
+ * send any other outgoing data on the socket.
+ * Ensure socket is in blocking mode.
+ * For SMB2 by default the socket is in
+ * non-blocking mode.
+ */
+ old_flags = fcntl(tofd, F_GETFL, 0);
+ ret = set_blocking(tofd, true);
+ if (ret == -1) {
+ goto out;
+ }
+ socket_flags_changed = true;
+ continue;
+ }
+ /* Send failed, we are toast. */
+ ret = -1;
+ goto out;
+ }
+
+ if (nwritten == 0) {
+ /* EOF of offset is after EOF. */
+ break;
+ }
+
+ if (sf_header.hdr_cnt) {
+ if (io_header.iov_len <= nwritten) {
+ /* Entire header was sent. */
+ sf_header.headers = NULL;
+ sf_header.hdr_cnt = 0;
+ nwritten -= io_header.iov_len;
+ } else {
+ /* Partial header was sent. */
+ io_header.iov_len -= nwritten;
+ io_header.iov_base =
+ ((uint8_t *)io_header.iov_base) + nwritten;
+ nwritten = 0;
+ }
+ }
+
+ offset += nwritten;
+ count -= nwritten;
+ }
+
+ ret = nwritten;
+
+ out:
+
+ if (socket_flags_changed) {
+ int saved_errno;
+ int err;
+
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ /* Restore the old state of the socket. */
+ err = fcntl(tofd, F_SETFL, old_flags);
+ if (err == -1) {
+ return -1;
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ }
+ }
+
+ return ret;
+}
+
+#elif defined(AIX_SENDFILE_API)
+
+/* BEGIN AIX SEND_FILE */
+
+/* Contributed by William Jojo <jojowil@hvcc.edu> */
+#include <sys/socket.h>
+
+ssize_t sys_sendfile(int tofd, int fromfd, const DATA_BLOB *header, off_t offset, size_t count)
+{
+ struct sf_parms hdtrl;
+ int old_flags = 0;
+ ssize_t ret = -1;
+ bool socket_flags_changed = false;
+
+ /* Set up the header/trailer struct params. */
+ if (header) {
+ hdtrl.header_data = header->data;
+ hdtrl.header_length = header->length;
+ } else {
+ hdtrl.header_data = NULL;
+ hdtrl.header_length = 0;
+ }
+ hdtrl.trailer_data = NULL;
+ hdtrl.trailer_length = 0;
+
+ hdtrl.file_descriptor = fromfd;
+ hdtrl.file_offset = offset;
+ hdtrl.file_bytes = count;
+
+ while ( hdtrl.file_bytes + hdtrl.header_length ) {
+ /*
+ Return Value
+
+ There are three possible return values from send_file:
+
+ Value Description
+
+ -1 an error has occurred, errno contains the error code.
+
+ 0 the command has completed successfully.
+
+ 1 the command was completed partially, some data has been
+ transmitted but the command has to return for some reason,
+ for example, the command was interrupted by signals.
+ */
+ do {
+ ret = send_file(&tofd, &hdtrl, 0);
+ } while ((ret == 1) || (ret == -1 && errno == EINTR));
+ if ( ret == -1 ) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /*
+ * Sendfile must complete before we can
+ * send any other outgoing data on the socket.
+ * Ensure socket is in blocking mode.
+ * For SMB2 by default the socket is in
+ * non-blocking mode.
+ */
+ old_flags = fcntl(tofd, F_GETFL, 0);
+ ret = set_blocking(tofd, true);
+ if (ret == -1) {
+ goto out;
+ }
+ socket_flags_changed = true;
+ continue;
+ }
+ goto out;
+ }
+ }
+
+ ret = count + header->length;
+
+ out:
+
+ if (socket_flags_changed) {
+ int saved_errno;
+ int err;
+
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ /* Restore the old state of the socket. */
+ err = fcntl(tofd, F_SETFL, old_flags);
+ if (err == -1) {
+ return -1;
+ }
+ if (ret == -1) {
+ errno = saved_errno;
+ }
+ }
+
+ return ret;
+}
+/* END AIX SEND_FILE */
+
+#else /* No sendfile implementation. Return error. */
+
+ssize_t sys_sendfile(int tofd, int fromfd, const DATA_BLOB *header, off_t offset, size_t count)
+{
+ /* No sendfile syscall. */
+ errno = ENOSYS;
+ return -1;
+}
+#endif
diff --git a/source3/lib/server_id_db_util.c b/source3/lib/server_id_db_util.c
new file mode 100644
index 0000000..e73af24
--- /dev/null
+++ b/source3/lib/server_id_db_util.c
@@ -0,0 +1,99 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utils around server_id_db with more dependencies
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "server_id_db_util.h"
+#include "lib/util/server_id.h"
+#include "serverid.h"
+#include "lib/util/samba_util.h"
+
+static int server_id_db_check_exclusive(
+ struct server_id_db *db, const char *name,
+ unsigned num_servers, struct server_id *servers);
+
+int server_id_db_set_exclusive(struct server_id_db *db, const char *name)
+{
+ int ret;
+ unsigned num_servers;
+ struct server_id *servers;
+
+ ret = server_id_db_add(db, name);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = server_id_db_lookup(db, name, talloc_tos(),
+ &num_servers, &servers);
+ if (ret != 0) {
+ goto done;
+ }
+
+ /*
+ * Remove entries from the server_id_db for processes that have died
+ * and could not clean up. This is racy, as two processes could
+ * simultaneously try to register a name. Both would succeed in the
+ * server_id_db_add call, and both would see their peer active during
+ * the check_exclusive call. Both would get an EEXIST, and nobody
+ * would be able to register itself. But this is okay, as this is
+ * meant to be a cleanup routine, and normally only one daemon should
+ * start up at a time anyway. Getting this "right" would mean we would
+ * have to add locking to server_id_db, or add a dependency on
+ * serverids_exist to server_id_db. Both are too heavy-weight for my
+ * taste.
+ */
+
+ ret = server_id_db_check_exclusive(db, name, num_servers, servers);
+ TALLOC_FREE(servers);
+
+done:
+ if (ret != 0) {
+ server_id_db_remove(db, name);
+ }
+ return ret;
+}
+
+static int server_id_db_check_exclusive(
+ struct server_id_db *db, const char *name,
+ unsigned num_servers, struct server_id *servers)
+{
+ struct server_id me = server_id_db_pid(db);
+ unsigned i;
+
+ for (i=0; i<num_servers; i++) {
+ int ret;
+
+ if (server_id_same_process(&me, &servers[i])) {
+ /*
+ * I am always around ... :-)
+ */
+ continue;
+ }
+
+ if (serverid_exists(&servers[i])) {
+ return EEXIST;
+ }
+
+ ret = server_id_db_prune_name(db, name, servers[i]);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
diff --git a/source3/lib/server_id_db_util.h b/source3/lib/server_id_db_util.h
new file mode 100644
index 0000000..5fdd078
--- /dev/null
+++ b/source3/lib/server_id_db_util.h
@@ -0,0 +1,22 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utils around server_id_db with more dependencies
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "lib/util/server_id_db.h"
+
+int server_id_db_set_exclusive(struct server_id_db *db, const char *name);
diff --git a/source3/lib/server_id_watch.c b/source3/lib/server_id_watch.c
new file mode 100644
index 0000000..f0189e0
--- /dev/null
+++ b/source3/lib/server_id_watch.c
@@ -0,0 +1,104 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Wait for process death
+ * Copyright (C) Volker Lendecke 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <tevent.h>
+#include <talloc.h>
+#include "serverid.h"
+#include "server_id_watch.h"
+#include "lib/util/tevent_unix.h"
+
+struct server_id_watch_state {
+ struct tevent_context *ev;
+ struct server_id pid;
+};
+
+static void server_id_watch_waited(struct tevent_req *subreq);
+
+struct tevent_req *server_id_watch_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct server_id pid)
+{
+ struct tevent_req *req, *subreq;
+ struct server_id_watch_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct server_id_watch_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->pid = pid;
+
+ if (!serverid_exists(&state->pid)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = tevent_wakeup_send(
+ state, ev, tevent_timeval_current_ofs(0, 500000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, server_id_watch_waited, req);
+
+ return req;
+}
+
+static void server_id_watch_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct server_id_watch_state *state = tevent_req_data(
+ req, struct server_id_watch_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ if (!serverid_exists(&state->pid)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = tevent_wakeup_send(
+ state, state->ev, tevent_timeval_current_ofs(0, 500000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, server_id_watch_waited, req);
+}
+
+int server_id_watch_recv(struct tevent_req *req, struct server_id *pid)
+{
+ struct server_id_watch_state *state = tevent_req_data(
+ req, struct server_id_watch_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ if (pid) {
+ *pid = state->pid;
+ }
+ return 0;
+}
diff --git a/source3/lib/server_id_watch.h b/source3/lib/server_id_watch.h
new file mode 100644
index 0000000..7e88cb4
--- /dev/null
+++ b/source3/lib/server_id_watch.h
@@ -0,0 +1,33 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Wait for process death
+ * Copyright (C) Volker Lendecke 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_SERVER_ID_WATCH_H__
+#define __LIB_SERVER_ID_WATCH_H__
+
+#include "replace.h"
+#include <tevent.h>
+#include <talloc.h>
+#include "librpc/gen_ndr/server_id.h"
+
+struct tevent_req *server_id_watch_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct server_id pid);
+int server_id_watch_recv(struct tevent_req *req, struct server_id *pid);
+
+#endif
diff --git a/source3/lib/server_mutex.c b/source3/lib/server_mutex.c
new file mode 100644
index 0000000..cbb8357
--- /dev/null
+++ b/source3/lib/server_mutex.c
@@ -0,0 +1,104 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authenticate against a remote domain
+ Copyright (C) Andrew Tridgell 1992-2002
+ Copyright (C) Andrew Bartlett 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "util_tdb.h"
+#include "lib/param/param.h"
+
+/* For reasons known only to MS, many of their NT/Win2k versions
+ need serialised access only. Two connections at the same time
+ may (in certain situations) cause connections to be reset,
+ or access to be denied.
+
+ This locking allows smbd's multithread architecture to look
+ like the single-connection that NT makes. */
+
+struct named_mutex {
+ struct tdb_wrap *tdb;
+ char *name;
+};
+
+static int unlock_named_mutex(struct named_mutex *mutex)
+{
+ tdb_unlock_bystring(mutex->tdb->tdb, mutex->name);
+ return 0;
+}
+
+struct named_mutex *grab_named_mutex(TALLOC_CTX *mem_ctx, const char *name,
+ int timeout)
+{
+ struct named_mutex *result;
+ struct loadparm_context *lp_ctx;
+ char *fname;
+
+ result = talloc(mem_ctx, struct named_mutex);
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ lp_ctx = loadparm_init_s3(result, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(0, ("loadparm_init_s3 failed\n"));
+ talloc_free(result);
+ return NULL;
+ }
+
+ result->name = talloc_strdup(result, name);
+ if (result->name == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ fname = lock_path(talloc_tos(), "mutex.tdb");
+ if (fname == NULL) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->tdb = tdb_wrap_open(result, fname,
+ lpcfg_tdb_hash_size(lp_ctx, fname),
+ lpcfg_tdb_flags(lp_ctx,
+ TDB_DEFAULT |
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH),
+ O_RDWR|O_CREAT, 0600);
+ TALLOC_FREE(fname);
+ talloc_unlink(result, lp_ctx);
+ if (result->tdb == NULL) {
+ DEBUG(1, ("Could not open mutex.tdb: %s\n",
+ strerror(errno)));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ if (tdb_lock_bystring_with_timeout(result->tdb->tdb, name,
+ timeout) != 0) {
+ DEBUG(1, ("Could not get the lock for %s\n", name));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ talloc_set_destructor(result, unlock_named_mutex);
+ return result;
+}
diff --git a/source3/lib/serverid.c b/source3/lib/serverid.c
new file mode 100644
index 0000000..70739c7
--- /dev/null
+++ b/source3/lib/serverid.c
@@ -0,0 +1,62 @@
+/*
+ Unix SMB/CIFS implementation.
+ Implementation of a reliable server_exists()
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "serverid.h"
+#include "lib/param/param.h"
+#include "ctdbd_conn.h"
+#include "lib/messages_ctdb.h"
+#include "lib/messaging/messages_dgm.h"
+
+static bool serverid_exists_local(const struct server_id *id)
+{
+ bool exists = process_exists_by_pid(id->pid);
+ uint64_t unique;
+ int ret;
+
+ if (!exists) {
+ return false;
+ }
+
+ if (id->unique_id == SERVERID_UNIQUE_ID_NOT_TO_VERIFY) {
+ return true;
+ }
+
+ ret = messaging_dgm_get_unique(id->pid, &unique);
+ if (ret != 0) {
+ return false;
+ }
+
+ return (unique == id->unique_id);
+}
+
+bool serverid_exists(const struct server_id *id)
+{
+ if (procid_is_local(id)) {
+ return serverid_exists_local(id);
+ }
+
+ if (lp_clustering()) {
+ return ctdbd_process_exists(messaging_ctdb_connection(),
+ id->vnn, id->pid, id->unique_id);
+ }
+
+ return false;
+}
diff --git a/source3/lib/sessionid_tdb.c b/source3/lib/sessionid_tdb.c
new file mode 100644
index 0000000..2376fd4
--- /dev/null
+++ b/source3/lib/sessionid_tdb.c
@@ -0,0 +1,95 @@
+/*
+ Unix SMB/CIFS implementation.
+ Low-level sessionid.tdb access functions
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "session.h"
+#include "util_tdb.h"
+#include "smbd/globals.h"
+
+struct sessionid_traverse_read_state {
+ int (*fn)(const char *key, struct sessionid *session,
+ void *private_data);
+ void *private_data;
+};
+
+static int sessionid_traverse_read_fn(struct smbXsrv_session_global0 *global,
+ void *private_data)
+{
+ struct sessionid_traverse_read_state *state =
+ (struct sessionid_traverse_read_state *)private_data;
+ struct auth_session_info *session_info = global->auth_session_info;
+ struct sessionid session = {
+ .uid = -1,
+ .gid = -1,
+ .id_num = global->session_global_id,
+ .connect_start = nt_time_to_unix(global->creation_time),
+ .pid = global->channels[0].server_id,
+ .connection_dialect = global->connection_dialect,
+ .global = global,
+ };
+
+ if (session_info != NULL) {
+ session.uid = session_info->unix_token->uid;
+ session.gid = session_info->unix_token->gid;
+ strncpy(session.username,
+ session_info->unix_info->unix_name,
+ sizeof(fstring)-1);
+ }
+
+ strncpy(session.remote_machine,
+ global->channels[0].remote_name,
+ sizeof(fstring)-1);
+ strncpy(session.hostname,
+ global->channels[0].remote_address,
+ sizeof(fstring)-1);
+ strncpy(session.netbios_name,
+ global->channels[0].remote_name,
+ sizeof(fstring)-1);
+ snprintf(session.id_str, sizeof(fstring)-1,
+ "smb/%u", global->session_global_id);
+ strncpy(session.ip_addr_str,
+ global->channels[0].remote_address,
+ sizeof(fstring)-1);
+
+ session.encryption_flags = global->encryption_flags;
+ session.cipher = global->channels[0].encryption_cipher;
+ session.signing_flags = global->signing_flags;
+ session.signing = global->channels[0].signing_algo;
+
+ return state->fn(NULL, &session, state->private_data);
+}
+
+NTSTATUS sessionid_traverse_read(int (*fn)(const char *key,
+ struct sessionid *session,
+ void *private_data),
+ void *private_data)
+{
+ struct sessionid_traverse_read_state state;
+ NTSTATUS status;
+
+ state.fn = fn;
+ state.private_data = private_data;
+ status = smbXsrv_session_global_traverse(sessionid_traverse_read_fn,
+ &state);
+
+ return status;
+}
diff --git a/source3/lib/sharesec.c b/source3/lib/sharesec.c
new file mode 100644
index 0000000..fbb2d47
--- /dev/null
+++ b/source3/lib/sharesec.c
@@ -0,0 +1,588 @@
+/*
+ * Unix SMB/Netbios implementation.
+ * SEC_DESC handling functions
+ * Copyright (C) Jeremy R. Allison 1995-2003.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "util_tdb.h"
+#include "libcli/util/ntstatus.h"
+
+/*******************************************************************
+ Create the share security tdb.
+ ********************************************************************/
+
+static struct db_context *share_db; /* used for share security descriptors */
+#define SHARE_DATABASE_VERSION_V1 1
+#define SHARE_DATABASE_VERSION_V2 2 /* version id in little endian. */
+#define SHARE_DATABASE_VERSION_V3 3 /* canonicalized sharenames as lower case */
+
+#define SHARE_SECURITY_DB_KEY_PREFIX_STR "SECDESC/"
+/* Map generic permissions to file object specific permissions */
+
+extern const struct generic_mapping file_generic_mapping;
+
+static int delete_fn(struct db_record *rec, void *priv)
+{
+ dbwrap_record_delete(rec);
+ return 0;
+}
+
+/*****************************************************
+ Looking for keys of the form: SHARE_SECURITY_DB_KEY_PREFIX_STR + "non lower case str".
+ If we find one re-write it into a canonical case form.
+*****************************************************/
+
+static int upgrade_v2_to_v3(struct db_record *rec, void *priv)
+{
+ size_t prefix_len = strlen(SHARE_SECURITY_DB_KEY_PREFIX_STR);
+ const char *servicename = NULL;
+ char *c_servicename = NULL;
+ char *newkey = NULL;
+ bool *p_upgrade_ok = (bool *)priv;
+ NTSTATUS status;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+
+ /* Is there space for a one character sharename ? */
+ if (key.dsize <= prefix_len+2) {
+ return 0;
+ }
+
+ /* Does it start with the share key prefix ? */
+ if (memcmp(key.dptr, SHARE_SECURITY_DB_KEY_PREFIX_STR,
+ prefix_len) != 0) {
+ return 0;
+ }
+
+ /* Is it a null terminated string as a key ? */
+ if (key.dptr[key.dsize-1] != '\0') {
+ return 0;
+ }
+
+ /* Bytes after the prefix are the sharename string. */
+ servicename = (char *)&key.dptr[prefix_len];
+ c_servicename = canonicalize_servicename(talloc_tos(), servicename);
+ if (!c_servicename) {
+ smb_panic("out of memory upgrading share security db from v2 -> v3");
+ }
+
+ if (strcmp(servicename, c_servicename) == 0) {
+ /* Old and new names match. No canonicalization needed. */
+ TALLOC_FREE(c_servicename);
+ return 0;
+ }
+
+ /* Oops. Need to canonicalize name, delete old then store new. */
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("upgrade_v2_to_v3: Failed to delete secdesc for "
+ "%s: %s\n", (const char *)key.dptr,
+ nt_errstr(status)));
+ TALLOC_FREE(c_servicename);
+ *p_upgrade_ok = false;
+ return -1;
+ } else {
+ DEBUG(10, ("upgrade_v2_to_v3: deleted secdesc for "
+ "%s\n", (const char *)key.dptr));
+ }
+
+ if (!(newkey = talloc_asprintf(talloc_tos(),
+ SHARE_SECURITY_DB_KEY_PREFIX_STR "%s",
+ c_servicename))) {
+ smb_panic("out of memory upgrading share security db from v2 -> v3");
+ }
+
+ value = dbwrap_record_get_value(rec);
+ status = dbwrap_store(share_db,
+ string_term_tdb_data(newkey),
+ value,
+ TDB_REPLACE);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("upgrade_v2_to_v3: Failed to store secdesc for "
+ "%s: %s\n", c_servicename, nt_errstr(status)));
+ TALLOC_FREE(c_servicename);
+ TALLOC_FREE(newkey);
+ *p_upgrade_ok = false;
+ return -1;
+ } else {
+ DEBUG(10, ("upgrade_v2_to_v3: stored secdesc for "
+ "%s\n", newkey ));
+ }
+
+ TALLOC_FREE(newkey);
+ TALLOC_FREE(c_servicename);
+
+ return 0;
+}
+
+NTSTATUS share_info_db_init(void)
+{
+ const char *vstring = "INFO/version";
+ int32_t vers_id = 0;
+ bool upgrade_ok = true;
+ NTSTATUS status;
+ char *db_path;
+
+ if (share_db != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ db_path = state_path(talloc_tos(), "share_info.tdb");
+ if (db_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ share_db = db_open(NULL, db_path, 0,
+ TDB_DEFAULT, O_RDWR|O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (share_db == NULL) {
+ DEBUG(0,("Failed to open share info database %s (%s)\n",
+ db_path, strerror(errno)));
+ TALLOC_FREE(db_path);
+ return map_nt_error_from_unix_common(errno);
+ }
+ TALLOC_FREE(db_path);
+
+ status = dbwrap_fetch_int32_bystring(share_db, vstring, &vers_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ vers_id = 0;
+ }
+
+ if (vers_id == SHARE_DATABASE_VERSION_V3) {
+ return NT_STATUS_OK;
+ }
+
+ if (dbwrap_transaction_start(share_db) != 0) {
+ DEBUG(0, ("transaction_start failed\n"));
+ TALLOC_FREE(share_db);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = dbwrap_fetch_int32_bystring(share_db, vstring, &vers_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ vers_id = 0;
+ }
+
+ if (vers_id == SHARE_DATABASE_VERSION_V3) {
+ /*
+ * Race condition
+ */
+ if (dbwrap_transaction_cancel(share_db)) {
+ smb_panic("transaction_cancel failed");
+ }
+ return NT_STATUS_OK;
+ }
+
+ /* Move to at least V2. */
+
+ /* Cope with byte-reversed older versions of the db. */
+ if ((vers_id == SHARE_DATABASE_VERSION_V1) || (IREV(vers_id) == SHARE_DATABASE_VERSION_V1)) {
+ /* Written on a bigendian machine with old fetch_int code. Save as le. */
+
+ status = dbwrap_store_int32_bystring(
+ share_db, vstring, SHARE_DATABASE_VERSION_V2);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dbwrap_store_int32 failed: %s\n",
+ nt_errstr(status)));
+ goto cancel;
+ }
+ vers_id = SHARE_DATABASE_VERSION_V2;
+ }
+
+ if (vers_id != SHARE_DATABASE_VERSION_V2) {
+ status = dbwrap_traverse(share_db, delete_fn, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("traverse failed\n"));
+ goto cancel;
+ }
+ status = dbwrap_store_int32_bystring(
+ share_db, vstring, SHARE_DATABASE_VERSION_V2);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dbwrap_store_int32 failed: %s\n",
+ nt_errstr(status)));
+ goto cancel;
+ }
+ }
+
+ /* Finally upgrade to version 3, with canonicalized sharenames. */
+
+ status = dbwrap_traverse(share_db, upgrade_v2_to_v3, &upgrade_ok, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("traverse failed\n"));
+ goto cancel;
+ }
+ if (!upgrade_ok) {
+ DBG_ERR("upgrade failed.\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto cancel;
+ }
+
+ status = dbwrap_store_int32_bystring(
+ share_db, vstring, SHARE_DATABASE_VERSION_V3);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dbwrap_store_int32 failed: %s\n",
+ nt_errstr(status)));
+ goto cancel;
+ }
+
+ if (dbwrap_transaction_commit(share_db) != 0) {
+ DEBUG(0, ("transaction_commit failed\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+
+ cancel:
+ if (dbwrap_transaction_cancel(share_db)) {
+ smb_panic("transaction_cancel failed");
+ }
+
+ return status;
+}
+
+/*******************************************************************
+ Fake up a Everyone, default access as a default.
+ def_access is a GENERIC_XXX access mode.
+ ********************************************************************/
+
+static struct security_descriptor *get_share_security_default(TALLOC_CTX *ctx,
+ size_t *psize,
+ uint32_t def_access)
+{
+ uint32_t sa;
+ struct security_ace ace;
+ struct security_acl *psa = NULL;
+ struct security_descriptor *psd = NULL;
+ uint32_t spec_access = def_access;
+
+ se_map_generic(&spec_access, &file_generic_mapping);
+
+ sa = (def_access | spec_access );
+ init_sec_ace(&ace, &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED, sa, 0);
+
+ if ((psa = make_sec_acl(ctx, NT4_ACL_REVISION, 1, &ace)) != NULL) {
+ psd = make_sec_desc(ctx, SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE, NULL, NULL, NULL,
+ psa, psize);
+ }
+
+ if (!psd) {
+ DEBUG(0,("get_share_security: Failed to make SEC_DESC.\n"));
+ return NULL;
+ }
+
+ return psd;
+}
+
+/*******************************************************************
+ Pull a security descriptor from the share tdb.
+ ********************************************************************/
+
+struct security_descriptor *get_share_security( TALLOC_CTX *ctx, const char *servicename,
+ size_t *psize)
+{
+ char *key;
+ struct security_descriptor *psd = NULL;
+ TDB_DATA data;
+ char *c_servicename = canonicalize_servicename(talloc_tos(), servicename);
+ NTSTATUS status;
+
+ if (!c_servicename) {
+ return NULL;
+ }
+
+ status = share_info_db_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(c_servicename);
+ return NULL;
+ }
+
+ if (!(key = talloc_asprintf(ctx, SHARE_SECURITY_DB_KEY_PREFIX_STR "%s", c_servicename))) {
+ TALLOC_FREE(c_servicename);
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ return NULL;
+ }
+
+ TALLOC_FREE(c_servicename);
+
+ status = dbwrap_fetch_bystring(share_db, talloc_tos(), key, &data);
+
+ TALLOC_FREE(key);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return get_share_security_default(ctx, psize,
+ SEC_RIGHTS_DIR_ALL);
+ }
+
+ status = unmarshall_sec_desc(ctx, data.dptr, data.dsize, &psd);
+
+ TALLOC_FREE(data.dptr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return get_share_security_default(ctx, psize,
+ SEC_RIGHTS_DIR_ALL);
+ }
+
+ if (psd) {
+ *psize = ndr_size_security_descriptor(psd, 0);
+ } else {
+ return get_share_security_default(ctx, psize,
+ SEC_RIGHTS_DIR_ALL);
+ }
+
+ return psd;
+}
+
+/*******************************************************************
+ Store a security descriptor in the share db.
+ ********************************************************************/
+
+NTSTATUS set_share_security(const char *share_name,
+ struct security_descriptor *psd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *key;
+ TDB_DATA blob;
+ NTSTATUS status;
+ char *c_share_name = canonicalize_servicename(frame, share_name);
+
+ if (c_share_name == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ status = share_info_db_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = marshall_sec_desc(frame, psd, &blob.dptr, &blob.dsize);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("marshall_sec_desc failed: %s\n",
+ nt_errstr(status)));
+ goto out;
+ }
+
+ if (!(key = talloc_asprintf(frame, SHARE_SECURITY_DB_KEY_PREFIX_STR "%s", c_share_name))) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ status = dbwrap_trans_store(share_db, string_term_tdb_data(key), blob,
+ TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("set_share_security: Failed to store secdesc for "
+ "%s: %s\n", share_name, nt_errstr(status)));
+ goto out;
+ }
+
+ DEBUG(5,("set_share_security: stored secdesc for %s\n", share_name ));
+ status = NT_STATUS_OK;
+
+ out:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*******************************************************************
+ Delete a security descriptor.
+********************************************************************/
+
+NTSTATUS delete_share_security(const char *servicename)
+{
+ TDB_DATA kbuf;
+ char *key;
+ NTSTATUS status;
+ char *c_servicename = canonicalize_servicename(talloc_tos(), servicename);
+
+ if (c_servicename == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = share_info_db_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(c_servicename);
+ return status;
+ }
+
+ if (!(key = talloc_asprintf(talloc_tos(), SHARE_SECURITY_DB_KEY_PREFIX_STR "%s",
+ c_servicename))) {
+ TALLOC_FREE(c_servicename);
+ return NT_STATUS_NO_MEMORY;
+ }
+ kbuf = string_term_tdb_data(key);
+
+ status = dbwrap_trans_delete(share_db, kbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("delete_share_security: Failed to delete entry for "
+ "share %s: %s\n", c_servicename, nt_errstr(status)));
+ TALLOC_FREE(c_servicename);
+ return status;
+ }
+
+ TALLOC_FREE(c_servicename);
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Can this user access with share with the required permissions ?
+********************************************************************/
+
+bool share_access_check(const struct security_token *token,
+ const char *sharename,
+ uint32_t desired_access,
+ uint32_t *pgranted)
+{
+ uint32_t granted;
+ NTSTATUS status;
+ struct security_descriptor *psd = NULL;
+ size_t sd_size;
+
+ psd = get_share_security(talloc_tos(), sharename, &sd_size);
+
+ if (!psd) {
+ if (pgranted != NULL) {
+ *pgranted = desired_access;
+ }
+ return false;
+ }
+
+ status = se_file_access_check(psd, token, true, desired_access, &granted);
+
+ TALLOC_FREE(psd);
+
+ if (pgranted != NULL) {
+ *pgranted = granted;
+ }
+
+ return NT_STATUS_IS_OK(status);
+}
+
+/***************************************************************************
+ Parse the contents of an acl string from a usershare file.
+***************************************************************************/
+
+bool parse_usershare_acl(TALLOC_CTX *ctx, const char *acl_str, struct security_descriptor **ppsd)
+{
+ size_t s_size = 0;
+ const char *pacl = acl_str;
+ int num_aces = 0;
+ struct security_ace *ace_list = NULL;
+ struct security_acl *psa = NULL;
+ struct security_descriptor *psd = NULL;
+ size_t sd_size = 0;
+ int i;
+
+ *ppsd = NULL;
+
+ /* If the acl string is blank return "Everyone:R" */
+ if (!*acl_str) {
+ struct security_descriptor *default_psd = get_share_security_default(ctx, &s_size, GENERIC_READ_ACCESS);
+ if (!default_psd) {
+ return False;
+ }
+ *ppsd = default_psd;
+ return True;
+ }
+
+ num_aces = 1;
+
+ /* Add the number of ',' characters to get the number of aces. */
+ num_aces += count_chars(pacl,',');
+
+ ace_list = talloc_array(ctx, struct security_ace, num_aces);
+ if (!ace_list) {
+ return False;
+ }
+
+ for (i = 0; i < num_aces; i++) {
+ uint32_t sa;
+ uint32_t g_access;
+ uint32_t s_access;
+ struct dom_sid sid;
+ char *sidstr;
+ enum security_ace_type type = SEC_ACE_TYPE_ACCESS_ALLOWED;
+
+ if (!next_token_talloc(ctx, &pacl, &sidstr, ":")) {
+ DEBUG(0,("parse_usershare_acl: malformed usershare acl looking "
+ "for ':' in string '%s'\n", pacl));
+ return False;
+ }
+
+ if (!string_to_sid(&sid, sidstr)) {
+ DEBUG(0,("parse_usershare_acl: failed to convert %s to sid.\n",
+ sidstr ));
+ return False;
+ }
+
+ switch (*pacl) {
+ case 'F': /* Full Control, ie. R+W */
+ case 'f': /* Full Control, ie. R+W */
+ s_access = g_access = GENERIC_ALL_ACCESS;
+ break;
+ case 'R': /* Read only. */
+ case 'r': /* Read only. */
+ s_access = g_access = GENERIC_READ_ACCESS;
+ break;
+ case 'D': /* Deny all to this SID. */
+ case 'd': /* Deny all to this SID. */
+ type = SEC_ACE_TYPE_ACCESS_DENIED;
+ s_access = g_access = GENERIC_ALL_ACCESS;
+ break;
+ default:
+ DEBUG(0,("parse_usershare_acl: unknown acl type at %s.\n",
+ pacl ));
+ return False;
+ }
+
+ pacl++;
+ if (*pacl && *pacl != ',') {
+ DEBUG(0,("parse_usershare_acl: bad acl string at %s.\n",
+ pacl ));
+ return False;
+ }
+ pacl++; /* Go past any ',' */
+
+ se_map_generic(&s_access, &file_generic_mapping);
+ sa = (g_access | s_access);
+ init_sec_ace(&ace_list[i], &sid, type, sa, 0);
+ }
+
+ if ((psa = make_sec_acl(ctx, NT4_ACL_REVISION, num_aces, ace_list)) != NULL) {
+ psd = make_sec_desc(ctx, SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE, NULL, NULL, NULL,
+ psa, &sd_size);
+ }
+
+ if (!psd) {
+ DEBUG(0,("parse_usershare_acl: Failed to make SEC_DESC.\n"));
+ return False;
+ }
+
+ *ppsd = psd;
+ return True;
+}
diff --git a/source3/lib/smbconf/pys3smbconf.c b/source3/lib/smbconf/pys3smbconf.c
new file mode 100644
index 0000000..09d4881
--- /dev/null
+++ b/source3/lib/smbconf/pys3smbconf.c
@@ -0,0 +1,212 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libsmbconf - Samba configuration library - Python bindings
+ *
+ * Copyright (C) John Mulligan <phlogistonjohn@asynchrono.us> 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "lib/replace/system/python.h"
+#include "includes.h"
+#include "python/py3compat.h"
+
+#include "lib/smbconf/smbconf.h"
+#include "source3/lib/smbconf/smbconf_reg.h"
+#include "source3/lib/smbconf/smbconf_init.h"
+#include "lib/smbconf/pysmbconf.h"
+
+/*
+ * The name of the other, general, smbconf module that implements
+ * the common type. We import this module by name to access
+ * its methods by the python API.
+ */
+#define SMBCONF_MOD "samba.smbconf"
+
+/*
+ * Return a new but uninitialized SMBConf python object via
+ * the python API of the (other) smbconf module.
+ */
+static PyObject *py_new_SMBConf(PyObject * smbconf_mod)
+{
+ PyObject *obj = NULL;
+ PyObject *method = PyObject_GetAttrString(smbconf_mod, "SMBConf");
+ if (method == NULL) {
+ return NULL;
+ }
+
+ obj = PyObject_CallObject(method, NULL);
+ Py_CLEAR(method);
+ return obj;
+}
+
+/*
+ * Raise a new SMBConfError python exception given a error code.
+ * This uses the python API of the (other) smbconf module.
+ */
+static PyObject *py_raise_SMBConfError(PyObject * smbconf_mod, sbcErr err)
+{
+ PyObject *obj = NULL;
+ PyObject *method =
+ PyObject_GetAttrString(smbconf_mod, "_smbconf_error");
+ if (method == NULL) {
+ return NULL;
+ }
+
+ obj = PyObject_CallFunction(method, "i", err);
+ Py_CLEAR(method);
+ return obj;
+}
+
+static PyObject *py_init_reg(PyObject * module, PyObject * args)
+{
+ PyObject *obj = NULL;
+ PyObject *smbconf_mod = NULL;
+ char *path = NULL;
+ struct smbconf_ctx *conf_ctx = NULL;
+ TALLOC_CTX *mem_ctx = NULL;
+ sbcErr err;
+
+ /*
+ * The path here is _NOT_ the path to a file in the file
+ * system. It's a special HK registry thingy. But passing
+ * a null string to smbconf_init_reg populates it with
+ * a functional default value. So we allow the python
+ * caller to pass None and convert to NULL.
+ */
+ if (!PyArg_ParseTuple(args, "z", &path)) {
+ return NULL;
+ }
+
+ smbconf_mod = PyImport_ImportModule(SMBCONF_MOD);
+ if (smbconf_mod == NULL) {
+ return NULL;
+ }
+
+ obj = py_new_SMBConf(smbconf_mod);
+ if (obj == NULL) {
+ Py_CLEAR(smbconf_mod);
+ return NULL;
+ }
+
+ mem_ctx = ((py_SMBConf_Object *) obj)->mem_ctx;
+ err = smbconf_init_reg(mem_ctx, &conf_ctx, path);
+ if (err != SBC_ERR_OK) {
+ py_raise_SMBConfError(smbconf_mod, err);
+ Py_CLEAR(obj);
+ Py_CLEAR(smbconf_mod);
+ return NULL;
+ }
+ ((py_SMBConf_Object *) obj)->conf_ctx = conf_ctx;
+
+ Py_DECREF(smbconf_mod);
+ return obj;
+}
+
+static PyObject *py_init_str(PyObject * module, PyObject * args)
+{
+ PyObject *obj = NULL;
+ PyObject *smbconf_mod = NULL;
+ char *path = NULL;
+ struct smbconf_ctx *conf_ctx = NULL;
+ TALLOC_CTX *mem_ctx = NULL;
+ sbcErr err;
+
+ if (!PyArg_ParseTuple(args, "s", &path)) {
+ return NULL;
+ }
+
+ smbconf_mod = PyImport_ImportModule(SMBCONF_MOD);
+ if (smbconf_mod == NULL) {
+ return NULL;
+ }
+
+ obj = py_new_SMBConf(smbconf_mod);
+ if (obj == NULL) {
+ Py_CLEAR(smbconf_mod);
+ return NULL;
+ }
+
+ mem_ctx = ((py_SMBConf_Object *) obj)->mem_ctx;
+ err = smbconf_init(mem_ctx, &conf_ctx, path);
+ if (err != SBC_ERR_OK) {
+ py_raise_SMBConfError(smbconf_mod, err);
+ Py_CLEAR(obj);
+ Py_CLEAR(smbconf_mod);
+ return NULL;
+ }
+ ((py_SMBConf_Object *) obj)->conf_ctx = conf_ctx;
+
+ Py_DECREF(smbconf_mod);
+ return obj;
+}
+
+PyDoc_STRVAR(py_init_reg_doc,
+"Return an SMBConf object using the registry based configuration.\n"
+"The path argument provided must either be None to use the\n"
+"default path or a path within the registry. It must start with\n"
+"the characters 'HK' if provided. It is *not* a path to a\n"
+"file or database in the file system.\n");
+
+PyDoc_STRVAR(py_init_str_doc,
+"Return an SMBConf object opened using one of the backends\n"
+"supported by Samba.\n"
+"The provided string argument must be in the form \"backend:path\".\n"
+"The backend portion is to be the name of a supported backend\n"
+"such as 'file', or 'registry'. The path following the colon is\n"
+"backend specific. In the case of the file backend this is the path\n"
+"to a configuration file.\n"
+"Examples:\n"
+" c1 = samba.samba3.smbconfig.init(\"file:/tmp/smb.conf\")\n"
+" c2 = samba.samba3.smbconfig.init(\"registry:\")\n");
+/*
+ * The major advantage of having this `init` function in the
+ * python wrapper is that if a backend is added without
+ * explicit changes to the python wrapper libs, it should still
+ * be able to access that backend through the general init
+ * function. The value add is not huge but more like insurance.
+ */
+
+static PyMethodDef pys3smbconf_methods[] = {
+ { "init_reg", (PyCFunction) py_init_reg, METH_VARARGS,
+ py_init_reg_doc },
+ { "init", (PyCFunction) py_init_str, METH_VARARGS,
+ py_init_str_doc },
+ { 0 },
+};
+
+PyDoc_STRVAR(py_s3smbconf_doc,
+"The s3smbconf module is a wrapper for Samba's 'source3' smbconf library.\n"
+"This library provides functions to use configuration backends that are\n"
+"specific to the file server suite of components within Samba.\n"
+"This includes functions to access the registry backend of the\n"
+"smbconf subsystem. This backend is read-write.\n");
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "smbconf",
+ .m_doc = py_s3smbconf_doc,
+ .m_size = -1,
+ .m_methods = pys3smbconf_methods,
+};
+
+MODULE_INIT_FUNC(smbconf)
+{
+ PyObject *m = PyModule_Create(&moduledef);
+ if (m == NULL) {
+ return NULL;
+ }
+
+ return m;
+}
diff --git a/source3/lib/smbconf/smbconf_init.c b/source3/lib/smbconf/smbconf_init.c
new file mode 100644
index 0000000..e6bf6d8
--- /dev/null
+++ b/source3/lib/smbconf/smbconf_init.c
@@ -0,0 +1,96 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libsmbconf - Samba configuration library, init dispatcher
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/smbconf/smbconf_private.h"
+#include "lib/smbconf/smbconf_txt.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "lib/smbconf/smbconf_init.h"
+
+/**
+ * smbconf initialization dispatcher
+ *
+ * this takes a configuration source in the form of
+ * backend:path and calls the appropriate backend
+ * init function with the path argument
+ *
+ * known backends:
+ * - "registry" or "reg"
+ * - "txt" or "file"
+ */
+sbcErr smbconf_init(TALLOC_CTX *mem_ctx, struct smbconf_ctx **conf_ctx,
+ const char *source)
+{
+ sbcErr err;
+ char *backend = NULL;
+ char *path = NULL;
+ char *sep;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if (conf_ctx == NULL) {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ if ((source == NULL) || (*source == '\0')) {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ backend = talloc_strdup(tmp_ctx, source);
+ if (backend == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ sep = strchr(backend, ':');
+ if (sep != NULL) {
+ *sep = '\0';
+ path = sep + 1;
+ if (strlen(path) == 0) {
+ path = NULL;
+ }
+ }
+
+ if (strequal(backend, "registry") || strequal(backend, "reg")) {
+ err = smbconf_init_reg(mem_ctx, conf_ctx, path);
+ } else if (strequal(backend, "file") || strequal(backend, "txt")) {
+ err = smbconf_init_txt(mem_ctx, conf_ctx, path);
+ } else if (sep == NULL) {
+ /*
+ * If no separator was given in the source, and the string is
+ * not a known backend, assume file backend and use the source
+ * string as a path argument.
+ */
+ err = smbconf_init_txt(mem_ctx, conf_ctx, backend);
+ } else {
+ /*
+ * Separator was specified but this is not a known backend.
+ * As a last resort, try to interpret the original source
+ * string as a file name that contains a ":" sign.
+ * This may occur with an include directive like this:
+ * 'include = /path/to/file.%T'
+ */
+ err = smbconf_init_txt(mem_ctx, conf_ctx, source);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
diff --git a/source3/lib/smbconf/smbconf_init.h b/source3/lib/smbconf/smbconf_init.h
new file mode 100644
index 0000000..11ddb0c
--- /dev/null
+++ b/source3/lib/smbconf/smbconf_init.h
@@ -0,0 +1,32 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libsmbconf - Samba configuration library
+ * Copyright (C) Michael Adam 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBSMBCONF_INIT_H__
+#define __LIBSMBCONF_INIT_H__
+
+struct smbconf_ctx;
+
+/**
+ * initialization dispatcher function.
+ * takes source string in the form of "backend:path"
+ */
+sbcErr smbconf_init(TALLOC_CTX *mem_ctx, struct smbconf_ctx **conf_ctx,
+ const char *source);
+
+#endif /* _LIBSMBCONF_INIT_H_ */
diff --git a/source3/lib/smbconf/smbconf_reg.c b/source3/lib/smbconf/smbconf_reg.c
new file mode 100644
index 0000000..fc8aa62
--- /dev/null
+++ b/source3/lib/smbconf/smbconf_reg.c
@@ -0,0 +1,1246 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libsmbconf - Samba configuration library, registry backend
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/smbconf/smbconf_private.h"
+#include "registry.h"
+#include "registry/reg_api.h"
+#include "registry/reg_backend_db.h"
+#include "registry/reg_util_token.h"
+#include "registry/reg_api_util.h"
+#include "registry/reg_init_smbconf.h"
+#include "lib/smbconf/smbconf_init.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "../libcli/registry/util_reg.h"
+
+#define INCLUDES_VALNAME "includes"
+
+struct reg_private_data {
+ struct registry_key *base_key;
+ bool open; /* did _we_ open the registry? */
+};
+
+/**********************************************************************
+ *
+ * helper functions
+ *
+ **********************************************************************/
+
+/**
+ * a convenience helper to cast the private data structure
+ */
+static struct reg_private_data *rpd(struct smbconf_ctx *ctx)
+{
+ return (struct reg_private_data *)(ctx->data);
+}
+
+/**
+ * Check whether a given parameter name is valid in the
+ * smbconf registry backend.
+ */
+bool smbconf_reg_parameter_is_valid(const char *param_name)
+{
+ /* hard code the list of forbidden names here for now */
+ const char *forbidden_names[] = {
+ "state directory",
+ "lock directory",
+ "lock dir",
+ "config backend",
+ "include",
+ /*
+ * "includes" has a special meaning internally.
+ * It is currently not necessary to list it here since it is
+ * not a valid parameter. But for clarity and safety, we keep
+ * it for now.
+ */
+ INCLUDES_VALNAME,
+ NULL
+ };
+ const char **forbidden = NULL;
+
+ if (!lp_parameter_is_valid(param_name)) {
+ return false;
+ }
+
+ for (forbidden = forbidden_names; *forbidden != NULL; forbidden++) {
+ if (strwicmp(param_name, *forbidden) == 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * Open a subkey of the base key (i.e a service)
+ */
+static sbcErr smbconf_reg_open_service_key(TALLOC_CTX *mem_ctx,
+ struct smbconf_ctx *ctx,
+ const char *servicename,
+ uint32_t desired_access,
+ struct registry_key **key)
+{
+ WERROR werr;
+
+ if (servicename == NULL) {
+ *key = rpd(ctx)->base_key;
+ return SBC_ERR_OK;
+ }
+ werr = reg_openkey(mem_ctx, rpd(ctx)->base_key, servicename,
+ desired_access, key);
+ if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) {
+ return SBC_ERR_NO_SUCH_SERVICE;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return SBC_ERR_NOMEM;
+ }
+
+ return SBC_ERR_OK;
+}
+
+/**
+ * check if a value exists in a given registry key
+ */
+static bool smbconf_value_exists(struct registry_key *key, const char *param)
+{
+ bool ret = false;
+ WERROR werr;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ struct registry_value *value = NULL;
+
+ werr = reg_queryvalue(ctx, key, param, &value);
+ if (W_ERROR_IS_OK(werr)) {
+ ret = true;
+ }
+
+ talloc_free(ctx);
+ return ret;
+}
+
+/**
+ * create a subkey of the base key (i.e. a service...)
+ */
+static sbcErr smbconf_reg_create_service_key(TALLOC_CTX *mem_ctx,
+ struct smbconf_ctx *ctx,
+ const char * subkeyname,
+ struct registry_key **newkey)
+{
+ WERROR werr;
+ sbcErr err = SBC_ERR_OK;
+ TALLOC_CTX *create_ctx;
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+
+ /* create a new talloc ctx for creation. it will hold
+ * the intermediate parent key (SMBCONF) for creation
+ * and will be destroyed when leaving this function... */
+ create_ctx = talloc_stackframe();
+
+ werr = reg_createkey(mem_ctx, rpd(ctx)->base_key, subkeyname,
+ REG_KEY_WRITE, newkey, &action);
+ if (W_ERROR_IS_OK(werr) && (action != REG_CREATED_NEW_KEY)) {
+ DEBUG(10, ("Key '%s' already exists.\n", subkeyname));
+ err = SBC_ERR_FILE_EXISTS;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(5, ("Error creating key %s: %s\n",
+ subkeyname, win_errstr(werr)));
+ err = SBC_ERR_UNKNOWN_FAILURE;
+ }
+
+ talloc_free(create_ctx);
+ return err;
+}
+
+/**
+ * add a value to a key.
+ */
+static sbcErr smbconf_reg_set_value(struct registry_key *key,
+ const char *valname,
+ const char *valstr)
+{
+ struct registry_value val;
+ WERROR werr;
+ sbcErr err;
+ char *subkeyname;
+ const char *canon_valname;
+ const char *canon_valstr;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if (!lp_parameter_is_valid(valname)) {
+ DEBUG(5, ("Invalid parameter '%s' given.\n", valname));
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ if (!smbconf_reg_parameter_is_valid(valname)) {
+ DEBUG(5, ("Parameter '%s' not allowed in registry.\n",
+ valname));
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ subkeyname = strrchr_m(key->key->name, '\\');
+ if ((subkeyname == NULL) || (*(subkeyname +1) == '\0')) {
+ DEBUG(5, ("Invalid registry key '%s' given as "
+ "smbconf section.\n", key->key->name));
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+ subkeyname++;
+ if (!strequal(subkeyname, GLOBAL_NAME) &&
+ lp_parameter_is_global(valname))
+ {
+ DEBUG(5, ("Global parameter '%s' not allowed in "
+ "service definition ('%s').\n", valname,
+ subkeyname));
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ if (!lp_canonicalize_parameter_with_value(valname, valstr,
+ &canon_valname,
+ &canon_valstr))
+ {
+ /*
+ * We already know the parameter name is valid.
+ * So the value must be invalid.
+ */
+ DEBUG(5, ("invalid value '%s' given for parameter '%s'\n",
+ valstr, valname));
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ ZERO_STRUCT(val);
+
+ val.type = REG_SZ;
+ if (!push_reg_sz(tmp_ctx, &val.data, canon_valstr)) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ werr = reg_setvalue(key, canon_valname, &val);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(5, ("Error adding value '%s' to "
+ "key '%s': %s\n",
+ canon_valname, key->key->name, win_errstr(werr)));
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ err = SBC_ERR_OK;
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+static sbcErr smbconf_reg_set_multi_sz_value(struct registry_key *key,
+ const char *valname,
+ const uint32_t num_strings,
+ const char **strings)
+{
+ WERROR werr;
+ sbcErr err = SBC_ERR_OK;
+ struct registry_value *value;
+ uint32_t count;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char **array;
+
+ if (strings == NULL) {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ array = talloc_zero_array(tmp_ctx, const char *, num_strings + 1);
+ if (array == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ value = talloc_zero(tmp_ctx, struct registry_value);
+ if (value == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ value->type = REG_MULTI_SZ;
+
+ for (count = 0; count < num_strings; count++) {
+ array[count] = talloc_strdup(value, strings[count]);
+ if (array[count] == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+ }
+
+ if (!push_reg_multi_sz(value, &value->data, array)) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ werr = reg_setvalue(key, valname, value);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(5, ("Error adding value '%s' to key '%s': %s\n",
+ valname, key->key->name, win_errstr(werr)));
+ err = SBC_ERR_ACCESS_DENIED;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+/**
+ * format a registry_value into a string.
+ *
+ * This is intended to be used for smbconf registry values,
+ * which are ar stored as REG_SZ values, so the incomplete
+ * handling should be ok.
+ */
+static char *smbconf_format_registry_value(TALLOC_CTX *mem_ctx,
+ struct registry_value *value)
+{
+ char *result = NULL;
+
+ /* alternatively, create a new talloc context? */
+ if (mem_ctx == NULL) {
+ return result;
+ }
+
+ switch (value->type) {
+ case REG_DWORD:
+ if (value->data.length >= 4) {
+ uint32_t v = IVAL(value->data.data, 0);
+ result = talloc_asprintf(mem_ctx, "%d", v);
+ }
+ break;
+ case REG_SZ:
+ case REG_EXPAND_SZ: {
+ const char *s;
+ if (!pull_reg_sz(mem_ctx, &value->data, &s)) {
+ break;
+ }
+ result = talloc_strdup(mem_ctx, s);
+ break;
+ }
+ case REG_MULTI_SZ: {
+ uint32_t j;
+ const char **a = NULL;
+ if (!pull_reg_multi_sz(mem_ctx, &value->data, &a)) {
+ break;
+ }
+ for (j = 0; a[j] != NULL; j++) {
+ result = talloc_asprintf(mem_ctx, "%s\"%s\" ",
+ result ? result : "" ,
+ a[j]);
+ if (result == NULL) {
+ break;
+ }
+ }
+ break;
+ }
+ case REG_BINARY:
+ result = talloc_asprintf(mem_ctx, "binary (%d bytes)",
+ (int)value->data.length);
+ break;
+ default:
+ result = talloc_asprintf(mem_ctx, "<unprintable>");
+ break;
+ }
+ return result;
+}
+
+static sbcErr smbconf_reg_get_includes_internal(TALLOC_CTX *mem_ctx,
+ struct registry_key *key,
+ uint32_t *num_includes,
+ char ***includes)
+{
+ WERROR werr;
+ sbcErr err;
+ uint32_t count;
+ struct registry_value *value = NULL;
+ char **tmp_includes = NULL;
+ const char **array = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if (!smbconf_value_exists(key, INCLUDES_VALNAME)) {
+ /* no includes */
+ *num_includes = 0;
+ *includes = NULL;
+ err = SBC_ERR_OK;
+ goto done;
+ }
+
+ werr = reg_queryvalue(tmp_ctx, key, INCLUDES_VALNAME, &value);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ if (value->type != REG_MULTI_SZ) {
+ /* wrong type -- ignore */
+ err = SBC_ERR_OK;
+ goto done;
+ }
+
+ if (!pull_reg_multi_sz(tmp_ctx, &value->data, &array)) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ for (count = 0; array[count] != NULL; count++) {
+ err = smbconf_add_string_to_array(tmp_ctx,
+ &tmp_includes,
+ count,
+ array[count]);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+ }
+
+ if (count > 0) {
+ *includes = talloc_move(mem_ctx, &tmp_includes);
+ if (*includes == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+ *num_includes = count;
+ } else {
+ *num_includes = 0;
+ *includes = NULL;
+ }
+
+ err = SBC_ERR_OK;
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+/**
+ * Get the values of a key as a list of value names
+ * and a list of value strings (ordered)
+ */
+static sbcErr smbconf_reg_get_values(TALLOC_CTX *mem_ctx,
+ struct registry_key *key,
+ uint32_t *num_values,
+ char ***value_names,
+ char ***value_strings)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ WERROR werr;
+ sbcErr err;
+ uint32_t count;
+ struct registry_value *valvalue = NULL;
+ char *valname = NULL;
+ uint32_t tmp_num_values = 0;
+ char **tmp_valnames = NULL;
+ char **tmp_valstrings = NULL;
+ uint32_t num_includes = 0;
+ char **includes = NULL;
+
+ if ((num_values == NULL) || (value_names == NULL) ||
+ (value_strings == NULL))
+ {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ tmp_ctx = talloc_stackframe();
+
+ for (count = 0;
+ werr = reg_enumvalue(tmp_ctx, key, count, &valname, &valvalue),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ char *valstring;
+
+ if (!smbconf_reg_parameter_is_valid(valname)) {
+ continue;
+ }
+
+ err = smbconf_add_string_to_array(tmp_ctx,
+ &tmp_valnames,
+ tmp_num_values, valname);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ valstring = smbconf_format_registry_value(tmp_ctx, valvalue);
+ err = smbconf_add_string_to_array(tmp_ctx, &tmp_valstrings,
+ tmp_num_values, valstring);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+ tmp_num_values++;
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ /* now add the includes at the end */
+ err = smbconf_reg_get_includes_internal(tmp_ctx, key, &num_includes,
+ &includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ for (count = 0; count < num_includes; count++) {
+ err = smbconf_add_string_to_array(tmp_ctx, &tmp_valnames,
+ tmp_num_values, "include");
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ err = smbconf_add_string_to_array(tmp_ctx, &tmp_valstrings,
+ tmp_num_values,
+ includes[count]);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ tmp_num_values++;
+ }
+
+ *num_values = tmp_num_values;
+ if (tmp_num_values > 0) {
+ *value_names = talloc_move(mem_ctx, &tmp_valnames);
+ *value_strings = talloc_move(mem_ctx, &tmp_valstrings);
+ } else {
+ *value_names = NULL;
+ *value_strings = NULL;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+/**
+ * delete all values from a key
+ */
+static sbcErr smbconf_reg_delete_values(struct registry_key *key)
+{
+ WERROR werr;
+ sbcErr err;
+ char *valname;
+ struct registry_value *valvalue;
+ uint32_t count;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ for (count = 0;
+ werr = reg_enumvalue(mem_ctx, key, count, &valname, &valvalue),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ werr = reg_deletevalue(key, valname);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_ACCESS_DENIED;
+ goto done;
+ }
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ DEBUG(1, ("smbconf_reg_delete_values: "
+ "Error enumerating values of %s: %s\n",
+ key->key->name,
+ win_errstr(werr)));
+ err = SBC_ERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ err = SBC_ERR_OK;
+
+done:
+ talloc_free(mem_ctx);
+ return err;
+}
+
+/**********************************************************************
+ *
+ * smbconf operations: registry implementations
+ *
+ **********************************************************************/
+
+/**
+ * initialize the registry smbconf backend
+ */
+static sbcErr smbconf_reg_init(struct smbconf_ctx *ctx, const char *path)
+{
+ WERROR werr;
+ sbcErr err;
+ struct security_token *token;
+
+ if (path == NULL) {
+ path = KEY_SMBCONF;
+ }
+ ctx->path = talloc_strdup(ctx, path);
+ if (ctx->path == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ ctx->data = talloc_zero(ctx, struct reg_private_data);
+
+ werr = ntstatus_to_werror(registry_create_admin_token(ctx, &token));
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("Error creating admin token\n"));
+ err = SBC_ERR_UNKNOWN_FAILURE;
+ goto done;
+ }
+ rpd(ctx)->open = false;
+
+ werr = registry_init_smbconf(path);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_BADFILE;
+ goto done;
+ }
+
+ err = ctx->ops->open_conf(ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ DEBUG(1, ("Error opening the registry.\n"));
+ goto done;
+ }
+
+ werr = reg_open_path(ctx, ctx->path,
+ KEY_ENUMERATE_SUB_KEYS | REG_KEY_WRITE,
+ token, &rpd(ctx)->base_key);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_UNKNOWN_FAILURE;
+ goto done;
+ }
+
+done:
+ return err;
+}
+
+static int smbconf_reg_shutdown(struct smbconf_ctx *ctx)
+{
+ return ctx->ops->close_conf(ctx);
+}
+
+static bool smbconf_reg_requires_messaging(struct smbconf_ctx *ctx)
+{
+ if (lp_clustering() && lp_parm_bool(-1, "ctdb", "registry.tdb", true)) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool smbconf_reg_is_writeable(struct smbconf_ctx *ctx)
+{
+ /*
+ * The backend has write support.
+ *
+ * TODO: add access checks whether the concrete
+ * config source is really writeable by the calling user.
+ */
+ return true;
+}
+
+static sbcErr smbconf_reg_open(struct smbconf_ctx *ctx)
+{
+ WERROR werr;
+
+ if (rpd(ctx)->open) {
+ return SBC_ERR_OK;
+ }
+
+ werr = regdb_open();
+ if (!W_ERROR_IS_OK(werr)) {
+ return SBC_ERR_BADFILE;
+ }
+
+ rpd(ctx)->open = true;
+ return SBC_ERR_OK;
+}
+
+static int smbconf_reg_close(struct smbconf_ctx *ctx)
+{
+ int ret;
+
+ if (!rpd(ctx)->open) {
+ return 0;
+ }
+
+ ret = regdb_close();
+ if (ret == 0) {
+ rpd(ctx)->open = false;
+ }
+ return ret;
+}
+
+/**
+ * Get the change sequence number of the given service/parameter.
+ * service and parameter strings may be NULL.
+ */
+static void smbconf_reg_get_csn(struct smbconf_ctx *ctx,
+ struct smbconf_csn *csn,
+ const char *service, const char *param)
+{
+ if (csn == NULL) {
+ return;
+ }
+
+ if (!SBC_ERROR_IS_OK(ctx->ops->open_conf(ctx))) {
+ return;
+ }
+
+ csn->csn = (uint64_t)regdb_get_seqnum();
+}
+
+/**
+ * Drop the whole configuration (restarting empty) - registry version
+ */
+static sbcErr smbconf_reg_drop(struct smbconf_ctx *ctx)
+{
+ char *path, *p;
+ WERROR werr;
+ sbcErr err = SBC_ERR_OK;
+ struct registry_key *parent_key = NULL;
+ struct registry_key *new_key = NULL;
+ TALLOC_CTX* mem_ctx = talloc_stackframe();
+ enum winreg_CreateAction action;
+ struct security_token *token;
+
+ werr = ntstatus_to_werror(registry_create_admin_token(ctx, &token));
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("Error creating admin token\n"));
+ err = SBC_ERR_UNKNOWN_FAILURE;
+ goto done;
+ }
+
+ path = talloc_strdup(mem_ctx, ctx->path);
+ if (path == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+ p = strrchr(path, '\\');
+ if (p == NULL) {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+ *p = '\0';
+ werr = reg_open_path(mem_ctx, path, REG_KEY_WRITE, token,
+ &parent_key);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_IO_FAILURE;
+ goto done;
+ }
+
+ werr = reg_deletesubkeys_recursive(parent_key, p+1);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_IO_FAILURE;
+ goto done;
+ }
+
+ werr = reg_createkey(mem_ctx, parent_key, p+1, REG_KEY_WRITE,
+ &new_key, &action);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_IO_FAILURE;
+ goto done;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return err;
+}
+
+/**
+ * get the list of share names defined in the configuration.
+ * registry version.
+ */
+static sbcErr smbconf_reg_get_share_names(struct smbconf_ctx *ctx,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_shares,
+ char ***share_names)
+{
+ uint32_t count;
+ uint32_t added_count = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+ WERROR werr;
+ sbcErr err = SBC_ERR_OK;
+ char *subkey_name = NULL;
+ char **tmp_share_names = NULL;
+
+ if ((num_shares == NULL) || (share_names == NULL)) {
+ return SBC_ERR_INVALID_PARAM;
+ }
+
+ tmp_ctx = talloc_stackframe();
+
+ /* make sure "global" is always listed first */
+ if (smbconf_share_exists(ctx, GLOBAL_NAME)) {
+ err = smbconf_add_string_to_array(tmp_ctx, &tmp_share_names,
+ added_count, GLOBAL_NAME);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+ added_count++;
+ }
+
+ for (count = 0;
+ werr = reg_enumkey(tmp_ctx, rpd(ctx)->base_key, count,
+ &subkey_name, NULL),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ if (strequal(subkey_name, GLOBAL_NAME)) {
+ continue;
+ }
+
+ err = smbconf_add_string_to_array(tmp_ctx,
+ &tmp_share_names,
+ added_count,
+ subkey_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+ added_count++;
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ err = SBC_ERR_NO_MORE_ITEMS;
+ goto done;
+ }
+ err = SBC_ERR_OK;
+
+ *num_shares = added_count;
+ if (added_count > 0) {
+ *share_names = talloc_move(mem_ctx, &tmp_share_names);
+ } else {
+ *share_names = NULL;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+/**
+ * check if a share/service of a given name exists - registry version
+ */
+static bool smbconf_reg_share_exists(struct smbconf_ctx *ctx,
+ const char *servicename)
+{
+ bool ret = false;
+ sbcErr err;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct registry_key *key = NULL;
+
+ err = smbconf_reg_open_service_key(mem_ctx, ctx, servicename,
+ REG_KEY_READ, &key);
+ if (SBC_ERROR_IS_OK(err)) {
+ ret = true;
+ }
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/**
+ * Add a service if it does not already exist - registry version
+ */
+static sbcErr smbconf_reg_create_share(struct smbconf_ctx *ctx,
+ const char *servicename)
+{
+ sbcErr err;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if (servicename == NULL) {
+ return SBC_ERR_OK;
+ }
+
+ err = smbconf_reg_create_service_key(tmp_ctx, ctx,
+ servicename, &key);
+
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+/**
+ * get a definition of a share (service) from configuration.
+ */
+static sbcErr smbconf_reg_get_share(struct smbconf_ctx *ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *servicename,
+ struct smbconf_service **service)
+{
+ sbcErr err;
+ struct registry_key *key = NULL;
+ struct smbconf_service *tmp_service = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ err = smbconf_reg_open_service_key(tmp_ctx, ctx, servicename,
+ REG_KEY_READ, &key);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ tmp_service = talloc_zero(tmp_ctx, struct smbconf_service);
+ if (tmp_service == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ if (servicename != NULL) {
+ WERROR werr;
+ uint32_t count = 0;
+ char *name = NULL;
+
+ /*
+ * Determine correct upper/lowercase.
+ */
+ for (count = 0;
+ werr = reg_enumkey(tmp_ctx, rpd(ctx)->base_key, count,
+ &name, NULL),
+ W_ERROR_IS_OK(werr);
+ count++) {
+ if (!strequal(name, servicename)) {
+ continue;
+ }
+
+ tmp_service->name = talloc_strdup(tmp_service, name);
+ if (tmp_service->name == NULL) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+ break;
+ }
+ }
+
+ err = smbconf_reg_get_values(tmp_service, key,
+ &(tmp_service->num_params),
+ &(tmp_service->param_names),
+ &(tmp_service->param_values));
+ if (SBC_ERROR_IS_OK(err)) {
+ *service = talloc_move(mem_ctx, &tmp_service);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+/**
+ * delete a service from configuration
+ */
+static sbcErr smbconf_reg_delete_share(struct smbconf_ctx *ctx,
+ const char *servicename)
+{
+ WERROR werr;
+ sbcErr err = SBC_ERR_OK;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (servicename != NULL) {
+ werr = reg_deletekey_recursive(rpd(ctx)->base_key, servicename);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_ACCESS_DENIED;
+ }
+ } else {
+ err = smbconf_reg_delete_values(rpd(ctx)->base_key);
+ }
+
+ talloc_free(mem_ctx);
+ return err;
+}
+
+/**
+ * set a configuration parameter to the value provided.
+ */
+static sbcErr smbconf_reg_set_parameter(struct smbconf_ctx *ctx,
+ const char *service,
+ const char *param,
+ const char *valstr)
+{
+ sbcErr err;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ err = smbconf_reg_open_service_key(mem_ctx, ctx, service,
+ REG_KEY_WRITE, &key);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ err = smbconf_reg_set_value(key, param, valstr);
+
+done:
+ talloc_free(mem_ctx);
+ return err;
+}
+
+/**
+ * get the value of a configuration parameter as a string
+ */
+static sbcErr smbconf_reg_get_parameter(struct smbconf_ctx *ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *service,
+ const char *param,
+ char **valstr)
+{
+ WERROR werr;
+ sbcErr err;
+ struct registry_key *key = NULL;
+ struct registry_value *value = NULL;
+
+ err = smbconf_reg_open_service_key(mem_ctx, ctx, service,
+ REG_KEY_READ, &key);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ if (!smbconf_reg_parameter_is_valid(param)) {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ if (!smbconf_value_exists(key, param)) {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ werr = reg_queryvalue(mem_ctx, key, param, &value);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_NOMEM;
+ goto done;
+ }
+
+ *valstr = smbconf_format_registry_value(mem_ctx, value);
+ if (*valstr == NULL) {
+ err = SBC_ERR_NOMEM;
+ }
+
+done:
+ talloc_free(key);
+ talloc_free(value);
+ return err;
+}
+
+/**
+ * delete a parameter from configuration
+ */
+static sbcErr smbconf_reg_delete_parameter(struct smbconf_ctx *ctx,
+ const char *service,
+ const char *param)
+{
+ struct registry_key *key = NULL;
+ WERROR werr;
+ sbcErr err;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ err = smbconf_reg_open_service_key(mem_ctx, ctx, service,
+ REG_KEY_ALL, &key);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ if (!smbconf_reg_parameter_is_valid(param)) {
+ err = SBC_ERR_INVALID_PARAM;
+ goto done;
+ }
+
+ if (!smbconf_value_exists(key, param)) {
+ err = SBC_ERR_OK;
+ goto done;
+ }
+
+ werr = reg_deletevalue(key, param);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_ACCESS_DENIED;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return err;
+}
+
+static sbcErr smbconf_reg_get_includes(struct smbconf_ctx *ctx,
+ TALLOC_CTX *mem_ctx,
+ const char *service,
+ uint32_t *num_includes,
+ char ***includes)
+{
+ sbcErr err;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ err = smbconf_reg_open_service_key(tmp_ctx, ctx, service,
+ REG_KEY_READ, &key);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ err = smbconf_reg_get_includes_internal(mem_ctx, key, num_includes,
+ includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+static sbcErr smbconf_reg_set_includes(struct smbconf_ctx *ctx,
+ const char *service,
+ uint32_t num_includes,
+ const char **includes)
+{
+ sbcErr err;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ err = smbconf_reg_open_service_key(tmp_ctx, ctx, service,
+ REG_KEY_ALL, &key);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ if (num_includes == 0) {
+ WERROR werr;
+ if (!smbconf_value_exists(key, INCLUDES_VALNAME)) {
+ err = SBC_ERR_OK;
+ goto done;
+ }
+ werr = reg_deletevalue(key, INCLUDES_VALNAME);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_ACCESS_DENIED;
+ goto done;
+ }
+ } else {
+ err = smbconf_reg_set_multi_sz_value(key, INCLUDES_VALNAME,
+ num_includes, includes);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+static sbcErr smbconf_reg_delete_includes(struct smbconf_ctx *ctx,
+ const char *service)
+{
+ WERROR werr;
+ sbcErr err;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ err = smbconf_reg_open_service_key(tmp_ctx, ctx, service,
+ REG_KEY_ALL, &key);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ if (!smbconf_value_exists(key, INCLUDES_VALNAME)) {
+ err = SBC_ERR_OK;
+ goto done;
+ }
+
+ werr = reg_deletevalue(key, INCLUDES_VALNAME);
+ if (!W_ERROR_IS_OK(werr)) {
+ err = SBC_ERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ err = SBC_ERR_OK;
+done:
+ talloc_free(tmp_ctx);
+ return err;
+}
+
+static sbcErr smbconf_reg_transaction_start(struct smbconf_ctx *ctx)
+{
+ WERROR werr;
+
+ werr = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(werr)) {
+ return SBC_ERR_IO_FAILURE;
+ }
+
+ return SBC_ERR_OK;
+}
+
+static sbcErr smbconf_reg_transaction_commit(struct smbconf_ctx *ctx)
+{
+ WERROR werr;
+
+ werr = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(werr)) {
+ return SBC_ERR_IO_FAILURE;
+ }
+
+ return SBC_ERR_OK;
+}
+
+static sbcErr smbconf_reg_transaction_cancel(struct smbconf_ctx *ctx)
+{
+ WERROR werr;
+
+ werr = regdb_transaction_cancel();
+ if (!W_ERROR_IS_OK(werr)) {
+ return SBC_ERR_IO_FAILURE;
+ }
+
+ return SBC_ERR_OK;
+}
+
+struct smbconf_ops smbconf_ops_reg = {
+ .init = smbconf_reg_init,
+ .shutdown = smbconf_reg_shutdown,
+ .requires_messaging = smbconf_reg_requires_messaging,
+ .is_writeable = smbconf_reg_is_writeable,
+ .open_conf = smbconf_reg_open,
+ .close_conf = smbconf_reg_close,
+ .get_csn = smbconf_reg_get_csn,
+ .drop = smbconf_reg_drop,
+ .get_share_names = smbconf_reg_get_share_names,
+ .share_exists = smbconf_reg_share_exists,
+ .create_share = smbconf_reg_create_share,
+ .get_share = smbconf_reg_get_share,
+ .delete_share = smbconf_reg_delete_share,
+ .set_parameter = smbconf_reg_set_parameter,
+ .get_parameter = smbconf_reg_get_parameter,
+ .delete_parameter = smbconf_reg_delete_parameter,
+ .get_includes = smbconf_reg_get_includes,
+ .set_includes = smbconf_reg_set_includes,
+ .delete_includes = smbconf_reg_delete_includes,
+ .transaction_start = smbconf_reg_transaction_start,
+ .transaction_commit = smbconf_reg_transaction_commit,
+ .transaction_cancel = smbconf_reg_transaction_cancel,
+};
+
+
+/**
+ * initialize the smbconf registry backend
+ * the only function that is exported from this module
+ */
+sbcErr smbconf_init_reg(TALLOC_CTX *mem_ctx, struct smbconf_ctx **conf_ctx,
+ const char *path)
+{
+ /*
+ * this tmp_ctx stackframe is required to initialize the registry backend.
+ * Without it, the calls panics due to the use of talloc_tos in the
+ * source3/registry code.
+ */
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ sbcErr err = smbconf_init_internal(mem_ctx, conf_ctx, path, &smbconf_ops_reg);
+ talloc_free(tmp_ctx);
+ return err;
+}
diff --git a/source3/lib/smbconf/smbconf_reg.h b/source3/lib/smbconf/smbconf_reg.h
new file mode 100644
index 0000000..a3f343f
--- /dev/null
+++ b/source3/lib/smbconf/smbconf_reg.h
@@ -0,0 +1,38 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libsmbconf - Samba configuration library
+ * Copyright (C) Michael Adam 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBSMBCONF_REG_H__
+#define __LIBSMBCONF_REG_H__
+
+struct smbconf_ctx;
+
+/**
+ * initialization functions for the registry backend modules
+ */
+
+sbcErr smbconf_init_reg(TALLOC_CTX *mem_ctx, struct smbconf_ctx **conf_ctx,
+ const char *path);
+
+/**
+ * Check whether a given parameter name is valid in the
+ * smbconf registry backend.
+ */
+bool smbconf_reg_parameter_is_valid(const char *param_name);
+
+#endif /* _LIBSMBCONF_REG_H_ */
diff --git a/source3/lib/smbconf/testsuite.c b/source3/lib/smbconf/testsuite.c
new file mode 100644
index 0000000..1dd7eec
--- /dev/null
+++ b/source3/lib/smbconf/testsuite.c
@@ -0,0 +1,338 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libsmbconf - Samba configuration library: testsuite
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_init.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "lib/smbconf/smbconf_txt.h"
+
+static void print_strings(const char *prefix,
+ uint32_t num_strings,
+ const char * const *strings)
+{
+ uint32_t count;
+
+ if (prefix == NULL) {
+ prefix = "";
+ }
+
+ for (count = 0; count < num_strings; count++) {
+ printf("%s%s\n", prefix, strings[count]);
+ }
+}
+
+static bool test_get_includes(struct smbconf_ctx *ctx)
+{
+ sbcErr err;
+ bool ret = false;
+ uint32_t num_includes = 0;
+ char **includes = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ printf("TEST: get_includes\n");
+ err = smbconf_get_global_includes(ctx, mem_ctx,
+ &num_includes, &includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: get_includes - %s\n", sbcErrorString(err));
+ goto done;
+ }
+
+ printf("got %u includes%s\n", num_includes,
+ (num_includes > 0) ? ":" : ".");
+ print_strings("", num_includes, (const char * const *)includes);
+
+ printf("OK: get_includes\n");
+ ret = true;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static bool test_set_get_includes(struct smbconf_ctx *ctx)
+{
+ sbcErr err;
+ uint32_t count;
+ bool ret = false;
+ const char *set_includes[] = {
+ "/path/to/include1",
+ "/path/to/include2"
+ };
+ uint32_t set_num_includes = 2;
+ char **get_includes = NULL;
+ uint32_t get_num_includes = 0;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ printf("TEST: set_get_includes\n");
+
+ err = smbconf_set_global_includes(ctx, set_num_includes, set_includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: get_set_includes (setting includes) - %s\n",
+ sbcErrorString(err));
+ goto done;
+ }
+
+ err = smbconf_get_global_includes(ctx, mem_ctx, &get_num_includes,
+ &get_includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: get_set_includes (getting includes) - %s\n",
+ sbcErrorString(err));
+ goto done;
+ }
+
+ if (get_num_includes != set_num_includes) {
+ printf("FAIL: get_set_includes - set %d includes, got %d\n",
+ set_num_includes, get_num_includes);
+ goto done;
+ }
+
+ for (count = 0; count < get_num_includes; count++) {
+ if (!strequal(set_includes[count], get_includes[count])) {
+ printf("expected: \n");
+ print_strings("* ", set_num_includes,
+ (const char * const *)set_includes);
+ printf("got: \n");
+ print_strings("* ", get_num_includes,
+ (const char * const *)get_includes);
+ printf("FAIL: get_set_includes - data mismatch:\n");
+ goto done;
+ }
+ }
+
+ printf("OK: set_includes\n");
+ ret = true;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static bool test_delete_includes(struct smbconf_ctx *ctx)
+{
+ sbcErr err;
+ bool ret = false;
+ const char *set_includes[] = {
+ "/path/to/include",
+ };
+ uint32_t set_num_includes = 1;
+ char **get_includes = NULL;
+ uint32_t get_num_includes = 0;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ printf("TEST: delete_includes\n");
+
+ err = smbconf_set_global_includes(ctx, set_num_includes, set_includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: delete_includes (setting includes) - %s\n",
+ sbcErrorString(err));
+ goto done;
+ }
+
+ err = smbconf_delete_global_includes(ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: delete_includes (deleting includes) - %s\n",
+ sbcErrorString(err));
+ goto done;
+ }
+
+ err = smbconf_get_global_includes(ctx, mem_ctx, &get_num_includes,
+ &get_includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: delete_includes (getting includes) - %s\n",
+ sbcErrorString(err));
+ goto done;
+ }
+
+ if (get_num_includes != 0) {
+ printf("FAIL: delete_includes (not empty after delete)\n");
+ goto done;
+ }
+
+ err = smbconf_delete_global_includes(ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: delete_includes (delete empty includes) - "
+ "%s\n", sbcErrorString(err));
+ goto done;
+ }
+
+ printf("OK: delete_includes\n");
+ ret = true;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static bool create_conf_file(const char *filename)
+{
+ FILE *f;
+
+ printf("TEST: creating file\n");
+ f = fopen(filename, "w");
+ if (!f) {
+ printf("failure: failed to open %s for writing: %s\n",
+ filename, strerror(errno));
+ return false;
+ }
+
+ fprintf(f, "[global]\n");
+ fprintf(f, "\tserver string = smbconf testsuite\n");
+ fprintf(f, "\tworkgroup = SAMBA\n");
+ fprintf(f, "\tsecurity = user\n");
+
+ fclose(f);
+
+ printf("OK: create file\n");
+ return true;
+}
+
+static bool torture_smbconf_txt(void)
+{
+ sbcErr err;
+ bool ret = true;
+ const char *filename = "/tmp/smb.conf.smbconf_testsuite";
+ struct smbconf_ctx *conf_ctx = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ printf("test: text backend\n");
+
+ if (!create_conf_file(filename)) {
+ ret = false;
+ goto done;
+ }
+
+ printf("TEST: init\n");
+ err = smbconf_init_txt(mem_ctx, &conf_ctx, filename);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: text backend failed: %s\n", sbcErrorString(err));
+ ret = false;
+ goto done;
+ }
+ printf("OK: init\n");
+
+ ret &= test_get_includes(conf_ctx);
+
+ smbconf_shutdown(conf_ctx);
+
+ printf("TEST: unlink file\n");
+ if (unlink(filename) != 0) {
+ printf("OK: unlink failed: %s\n", strerror(errno));
+ ret = false;
+ goto done;
+ }
+ printf("OK: unlink file\n");
+
+done:
+ printf("%s: text backend\n", ret ? "success" : "failure");
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static bool torture_smbconf_reg(void)
+{
+ sbcErr err;
+ bool ret = true;
+ struct smbconf_ctx *conf_ctx = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ printf("test: registry backend\n");
+
+ printf("TEST: init\n");
+ err = smbconf_init_reg(mem_ctx, &conf_ctx, NULL);
+ if (!SBC_ERROR_IS_OK(err)) {
+ printf("FAIL: init failed: %s\n", sbcErrorString(err));
+ ret = false;
+ goto done;
+ }
+ printf("OK: init\n");
+
+ ret &= test_get_includes(conf_ctx);
+ ret &= test_set_get_includes(conf_ctx);
+ ret &= test_delete_includes(conf_ctx);
+
+ smbconf_shutdown(conf_ctx);
+
+done:
+ printf("%s: registry backend\n", ret ? "success" : "failure");
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static bool torture_smbconf(void)
+{
+ bool ret = true;
+ ret &= torture_smbconf_txt();
+ printf("\n");
+ ret &= torture_smbconf_reg();
+ return ret;
+}
+
+int main(int argc, const char **argv)
+{
+ bool ret;
+ poptContext pc;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ int opt;
+
+ struct poptOption long_options[] = {
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ smb_init_locale();
+
+ ret = samba_cmdline_init(mem_ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ true /* require_smbconf */);
+ if (!ret) {
+ goto done;
+ }
+
+ /* parse options */
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ (const char **)argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ ret = false;
+ goto done;
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptFreeContext(pc);
+
+ ret = torture_smbconf();
+
+done:
+ talloc_free(mem_ctx);
+ return ret ? 0 : -1;
+}
diff --git a/source3/lib/smbd_shim.c b/source3/lib/smbd_shim.c
new file mode 100644
index 0000000..c7c64f7
--- /dev/null
+++ b/source3/lib/smbd_shim.c
@@ -0,0 +1,116 @@
+/*
+ Unix SMB/CIFS implementation.
+ Runtime plugin adapter for various "smbd"-functions.
+
+ Copyright (C) Gerald (Jerry) Carter 2004.
+ Copyright (C) Andrew Bartlett 2011.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Shim functions required due to the horrible dependency mess
+ in Samba. */
+
+#include "includes.h"
+#include "smbd_shim.h"
+
+static struct smbd_shim shim;
+
+void set_smbd_shim(const struct smbd_shim *shim_functions)
+{
+ shim = *shim_functions;
+}
+
+bool change_to_root_user(void)
+{
+ if (shim.change_to_root_user) {
+ return shim.change_to_root_user();
+ }
+ return false;
+}
+
+bool become_authenticated_pipe_user(struct auth_session_info *session_info)
+{
+ if (shim.become_authenticated_pipe_user) {
+ return shim.become_authenticated_pipe_user(session_info);
+ }
+
+ return false;
+}
+
+bool unbecome_authenticated_pipe_user(void)
+{
+ if (shim.unbecome_authenticated_pipe_user) {
+ return shim.unbecome_authenticated_pipe_user();
+ }
+
+ return false;
+}
+
+/**
+ * The following two functions need to be called from inside the low-level BRL
+ * code for oplocks correctness in smbd. Since other utility binaries also
+ * link in some of the brl code directly, these dummy functions are necessary
+ * to avoid needing to link in the oplocks code and its dependencies to all of
+ * the utility binaries.
+ */
+void contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ if (shim.contend_level2_oplocks_begin) {
+ shim.contend_level2_oplocks_begin(fsp, type);
+ }
+ return;
+}
+
+void contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ if (shim.contend_level2_oplocks_end) {
+ shim.contend_level2_oplocks_end(fsp, type);
+ }
+ return;
+}
+
+void become_root(void)
+{
+ if (shim.become_root) {
+ shim.become_root();
+ }
+ return;
+}
+
+void unbecome_root(void)
+{
+ if (shim.unbecome_root) {
+ shim.unbecome_root();
+ }
+ return;
+}
+
+void exit_server(const char *reason)
+{
+ if (shim.exit_server) {
+ shim.exit_server(reason);
+ }
+ exit(1);
+}
+
+void exit_server_cleanly(const char *const reason)
+{
+ if (shim.exit_server_cleanly) {
+ shim.exit_server_cleanly(reason);
+ }
+ exit(0);
+}
diff --git a/source3/lib/smbd_shim.h b/source3/lib/smbd_shim.h
new file mode 100644
index 0000000..c4bf330
--- /dev/null
+++ b/source3/lib/smbd_shim.h
@@ -0,0 +1,53 @@
+/*
+ Unix SMB/CIFS implementation.
+ Runtime plugin adapter for various "smbd"-functions.
+
+ Copyright (C) Gerald (Jerry) Carter 2004.
+ Copyright (C) Andrew Bartlett 2011.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ shim functions are used required to allow library code to have
+ references to smbd specific code. The smbd daemon sets up the set
+ of function calls that it wants used by calling
+ set_smbd_shim(). Other executables don't make this call, and get
+ default (dummy) versions of these functions.
+*/
+
+struct smbd_shim
+{
+ bool (*change_to_root_user)(void);
+ bool (*become_authenticated_pipe_user)(struct auth_session_info *session_info);
+ bool (*unbecome_authenticated_pipe_user)(void);
+
+ void (*contend_level2_oplocks_begin)(files_struct *fsp,
+ enum level2_contention_type type);
+
+ void (*contend_level2_oplocks_end)(files_struct *fsp,
+ enum level2_contention_type type);
+
+ void (*become_root)(void);
+
+ void (*unbecome_root)(void);
+
+ void (*exit_server)(const char *const explanation) _NORETURN_;
+
+ void (*exit_server_cleanly)(const char *const explanation) _NORETURN_;
+};
+
+void set_smbd_shim(const struct smbd_shim *shim_functions);
+
+
diff --git a/source3/lib/smbldap.c b/source3/lib/smbldap.c
new file mode 100644
index 0000000..c0d6884
--- /dev/null
+++ b/source3/lib/smbldap.c
@@ -0,0 +1,1941 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Jean François Micouleau 1998
+ Copyright (C) Gerald Carter 2001-2003
+ Copyright (C) Shahms King 2001
+ Copyright (C) Andrew Bartlett 2002-2003
+ Copyright (C) Stefan (metze) Metzmacher 2002-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "smbldap.h"
+#include "../libcli/security/security.h"
+#include <tevent.h>
+#include "lib/param/loadparm.h"
+
+/* Try not to hit the up or down server forever */
+
+#define SMBLDAP_DONT_PING_TIME 10 /* ping only all 10 seconds */
+#define SMBLDAP_NUM_RETRIES 8 /* retry only 8 times */
+
+#define SMBLDAP_IDLE_TIME 150 /* After 2.5 minutes disconnect */
+
+struct smbldap_state {
+ LDAP *ldap_struct;
+ pid_t pid;
+ time_t last_ping; /* monotonic */
+ /* retrieve-once info */
+ const char *uri;
+
+ /* credentials */
+ bool anonymous;
+ char *bind_dn;
+ char *bind_secret;
+ smbldap_bind_callback_fn bind_callback;
+ void *bind_callback_data;
+
+ bool paged_results;
+
+ unsigned int num_failures;
+
+ time_t last_use; /* monotonic */
+ struct tevent_context *tevent_context;
+ struct tevent_timer *idle_event;
+
+ struct timeval last_rebind; /* monotonic */
+};
+
+LDAP *smbldap_get_ldap(struct smbldap_state *state)
+{
+ return state->ldap_struct;
+}
+
+bool smbldap_get_paged_results(struct smbldap_state *state)
+{
+ return state->paged_results;
+}
+
+void smbldap_set_paged_results(struct smbldap_state *state,
+ bool paged_results)
+{
+ state->paged_results = paged_results;
+}
+
+void smbldap_set_bind_callback(struct smbldap_state *state,
+ smbldap_bind_callback_fn callback,
+ void *callback_data)
+{
+ state->bind_callback = callback;
+ state->bind_callback_data = callback_data;
+}
+/*******************************************************************
+ Search an attribute and return the first value found.
+******************************************************************/
+
+ bool smbldap_get_single_attribute (LDAP * ldap_struct, LDAPMessage * entry,
+ const char *attribute, char *value,
+ int max_len)
+{
+ char **values;
+ size_t size = 0;
+
+ if ( !attribute )
+ return False;
+
+ value[0] = '\0';
+
+ if ((values = ldap_get_values (ldap_struct, entry, attribute)) == NULL) {
+ DEBUG (10, ("smbldap_get_single_attribute: [%s] = [<does not exist>]\n", attribute));
+
+ return False;
+ }
+
+ if (!convert_string(CH_UTF8, CH_UNIX,values[0], -1, value, max_len, &size)) {
+ DEBUG(1, ("smbldap_get_single_attribute: string conversion of [%s] = [%s] failed!\n",
+ attribute, values[0]));
+ ldap_value_free(values);
+ return False;
+ }
+
+ ldap_value_free(values);
+#ifdef DEBUG_PASSWORDS
+ DEBUG (100, ("smbldap_get_single_attribute: [%s] = [%s]\n", attribute, value));
+#endif
+ return True;
+}
+
+ char * smbldap_talloc_single_attribute(LDAP *ldap_struct, LDAPMessage *entry,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx)
+{
+ char **values;
+ char *result;
+ size_t converted_size;
+
+ if (attribute == NULL) {
+ return NULL;
+ }
+
+ values = ldap_get_values(ldap_struct, entry, attribute);
+
+ if (values == NULL) {
+ DEBUG(10, ("attribute %s does not exist\n", attribute));
+ return NULL;
+ }
+
+ if (ldap_count_values(values) != 1) {
+ DEBUG(10, ("attribute %s has %d values, expected only one\n",
+ attribute, ldap_count_values(values)));
+ ldap_value_free(values);
+ return NULL;
+ }
+
+ if (!pull_utf8_talloc(mem_ctx, &result, values[0], &converted_size)) {
+ DEBUG(10, ("pull_utf8_talloc failed\n"));
+ ldap_value_free(values);
+ return NULL;
+ }
+
+ ldap_value_free(values);
+
+#ifdef DEBUG_PASSWORDS
+ DEBUG (100, ("smbldap_get_single_attribute: [%s] = [%s]\n",
+ attribute, result));
+#endif
+ return result;
+}
+
+ char * smbldap_talloc_first_attribute(LDAP *ldap_struct, LDAPMessage *entry,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx)
+{
+ char **values;
+ char *result;
+ size_t converted_size;
+
+ if (attribute == NULL) {
+ return NULL;
+ }
+
+ values = ldap_get_values(ldap_struct, entry, attribute);
+
+ if (values == NULL) {
+ DEBUG(10, ("attribute %s does not exist\n", attribute));
+ return NULL;
+ }
+
+ if (!pull_utf8_talloc(mem_ctx, &result, values[0], &converted_size)) {
+ DEBUG(10, ("pull_utf8_talloc failed\n"));
+ ldap_value_free(values);
+ return NULL;
+ }
+
+ ldap_value_free(values);
+
+#ifdef DEBUG_PASSWORDS
+ DEBUG (100, ("smbldap_get_first_attribute: [%s] = [%s]\n",
+ attribute, result));
+#endif
+ return result;
+}
+
+ char * smbldap_talloc_smallest_attribute(LDAP *ldap_struct, LDAPMessage *entry,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx)
+{
+ char **values;
+ char *result;
+ size_t converted_size;
+ int i, num_values;
+
+ if (attribute == NULL) {
+ return NULL;
+ }
+
+ values = ldap_get_values(ldap_struct, entry, attribute);
+
+ if (values == NULL) {
+ DEBUG(10, ("attribute %s does not exist\n", attribute));
+ return NULL;
+ }
+
+ if (!pull_utf8_talloc(mem_ctx, &result, values[0], &converted_size)) {
+ DEBUG(10, ("pull_utf8_talloc failed\n"));
+ ldap_value_free(values);
+ return NULL;
+ }
+
+ num_values = ldap_count_values(values);
+
+ for (i=1; i<num_values; i++) {
+ char *tmp;
+
+ if (!pull_utf8_talloc(mem_ctx, &tmp, values[i],
+ &converted_size)) {
+ DEBUG(10, ("pull_utf8_talloc failed\n"));
+ TALLOC_FREE(result);
+ ldap_value_free(values);
+ return NULL;
+ }
+
+ if (strcasecmp_m(tmp, result) < 0) {
+ TALLOC_FREE(result);
+ result = tmp;
+ } else {
+ TALLOC_FREE(tmp);
+ }
+ }
+
+ ldap_value_free(values);
+
+#ifdef DEBUG_PASSWORDS
+ DEBUG (100, ("smbldap_get_single_attribute: [%s] = [%s]\n",
+ attribute, result));
+#endif
+ return result;
+}
+
+ bool smbldap_talloc_single_blob(TALLOC_CTX *mem_ctx, LDAP *ld,
+ LDAPMessage *msg, const char *attrib,
+ DATA_BLOB *blob)
+{
+ struct berval **values;
+
+ values = ldap_get_values_len(ld, msg, attrib);
+ if (!values) {
+ return false;
+ }
+
+ if (ldap_count_values_len(values) != 1) {
+ DEBUG(10, ("Expected one value for %s, got %d\n", attrib,
+ ldap_count_values_len(values)));
+ return false;
+ }
+
+ *blob = data_blob_talloc(mem_ctx, values[0]->bv_val,
+ values[0]->bv_len);
+ ldap_value_free_len(values);
+
+ return (blob->data != NULL);
+}
+
+ bool smbldap_pull_sid(LDAP *ld, LDAPMessage *msg, const char *attrib,
+ struct dom_sid *sid)
+{
+ DATA_BLOB blob;
+ ssize_t ret;
+
+ if (!smbldap_talloc_single_blob(talloc_tos(), ld, msg, attrib,
+ &blob)) {
+ return false;
+ }
+ ret = sid_parse(blob.data, blob.length, sid);
+ TALLOC_FREE(blob.data);
+ return (ret != -1);
+}
+
+ static int ldapmsg_destructor(LDAPMessage **result) {
+ ldap_msgfree(*result);
+ return 0;
+}
+
+ void smbldap_talloc_autofree_ldapmsg(TALLOC_CTX *mem_ctx, LDAPMessage *result)
+{
+ LDAPMessage **handle;
+
+ if (result == NULL) {
+ return;
+ }
+
+ handle = talloc(mem_ctx, LDAPMessage *);
+ SMB_ASSERT(handle != NULL);
+
+ *handle = result;
+ talloc_set_destructor(handle, ldapmsg_destructor);
+}
+
+ static int ldapmod_destructor(LDAPMod ***mod) {
+ ldap_mods_free(*mod, True);
+ return 0;
+}
+
+ void smbldap_talloc_autofree_ldapmod(TALLOC_CTX *mem_ctx, LDAPMod **mod)
+{
+ LDAPMod ***handle;
+
+ if (mod == NULL) {
+ return;
+ }
+
+ handle = talloc(mem_ctx, LDAPMod **);
+ SMB_ASSERT(handle != NULL);
+
+ *handle = mod;
+ talloc_set_destructor(handle, ldapmod_destructor);
+}
+
+/************************************************************************
+ Routine to manage the LDAPMod structure array
+ manage memory used by the array, by each struct, and values
+ ***********************************************************************/
+
+static void smbldap_set_mod_internal(LDAPMod *** modlist, int modop, const char *attribute, const char *value, const DATA_BLOB *blob)
+{
+ LDAPMod **mods;
+ int i;
+ int j;
+
+ mods = *modlist;
+
+ /* sanity checks on the mod values */
+
+ if (attribute == NULL || *attribute == '\0') {
+ return;
+ }
+
+#if 0 /* commented out after discussion with abartlet. Do not re-enable.
+ left here so other do not re-add similar code --jerry */
+ if (value == NULL || *value == '\0')
+ return;
+#endif
+
+ if (mods == NULL) {
+ mods = SMB_MALLOC_P(LDAPMod *);
+ if (mods == NULL) {
+ smb_panic("smbldap_set_mod: out of memory!");
+ /* notreached. */
+ }
+ mods[0] = NULL;
+ }
+
+ for (i = 0; mods[i] != NULL; ++i) {
+ if (mods[i]->mod_op == modop && strequal(mods[i]->mod_type, attribute))
+ break;
+ }
+
+ if (mods[i] == NULL) {
+ mods = SMB_REALLOC_ARRAY (mods, LDAPMod *, i + 2);
+ if (mods == NULL) {
+ smb_panic("smbldap_set_mod: out of memory!");
+ /* notreached. */
+ }
+ mods[i] = SMB_MALLOC_P(LDAPMod);
+ if (mods[i] == NULL) {
+ smb_panic("smbldap_set_mod: out of memory!");
+ /* notreached. */
+ }
+ mods[i]->mod_op = modop;
+ mods[i]->mod_values = NULL;
+ mods[i]->mod_type = SMB_STRDUP(attribute);
+ mods[i + 1] = NULL;
+ }
+
+ if (blob && (modop & LDAP_MOD_BVALUES)) {
+ j = 0;
+ if (mods[i]->mod_bvalues != NULL) {
+ for (; mods[i]->mod_bvalues[j] != NULL; j++);
+ }
+ mods[i]->mod_bvalues = SMB_REALLOC_ARRAY(mods[i]->mod_bvalues, struct berval *, j + 2);
+
+ if (mods[i]->mod_bvalues == NULL) {
+ smb_panic("smbldap_set_mod: out of memory!");
+ /* notreached. */
+ }
+
+ mods[i]->mod_bvalues[j] = SMB_MALLOC_P(struct berval);
+ SMB_ASSERT(mods[i]->mod_bvalues[j] != NULL);
+
+ mods[i]->mod_bvalues[j]->bv_val = (char *)smb_memdup(blob->data, blob->length);
+ SMB_ASSERT(mods[i]->mod_bvalues[j]->bv_val != NULL);
+ mods[i]->mod_bvalues[j]->bv_len = blob->length;
+
+ mods[i]->mod_bvalues[j + 1] = NULL;
+ } else if (value != NULL) {
+ char *utf8_value = NULL;
+ size_t converted_size;
+
+ j = 0;
+ if (mods[i]->mod_values != NULL) {
+ for (; mods[i]->mod_values[j] != NULL; j++);
+ }
+ mods[i]->mod_values = SMB_REALLOC_ARRAY(mods[i]->mod_values, char *, j + 2);
+
+ if (mods[i]->mod_values == NULL) {
+ smb_panic("smbldap_set_mod: out of memory!");
+ /* notreached. */
+ }
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_value, value, &converted_size)) {
+ smb_panic("smbldap_set_mod: String conversion failure!");
+ /* notreached. */
+ }
+
+ mods[i]->mod_values[j] = SMB_STRDUP(utf8_value);
+ TALLOC_FREE(utf8_value);
+ SMB_ASSERT(mods[i]->mod_values[j] != NULL);
+
+ mods[i]->mod_values[j + 1] = NULL;
+ }
+ *modlist = mods;
+}
+
+ void smbldap_set_mod (LDAPMod *** modlist, int modop, const char *attribute, const char *value)
+{
+ smbldap_set_mod_internal(modlist, modop, attribute, value, NULL);
+}
+
+ void smbldap_set_mod_blob(LDAPMod *** modlist, int modop, const char *attribute, const DATA_BLOB *value)
+{
+ smbldap_set_mod_internal(modlist, modop | LDAP_MOD_BVALUES, attribute, NULL, value);
+}
+
+/**********************************************************************
+ Set attribute to newval in LDAP, regardless of what value the
+ attribute had in LDAP before.
+*********************************************************************/
+
+static void smbldap_make_mod_internal(LDAP *ldap_struct, LDAPMessage *existing,
+ LDAPMod ***mods,
+ const char *attribute, int op,
+ const char *newval,
+ const DATA_BLOB *newblob)
+{
+ char oldval[2048]; /* current largest allowed value is mungeddial */
+ bool existed;
+ DATA_BLOB oldblob = data_blob_null;
+
+ if (existing != NULL) {
+ if (op & LDAP_MOD_BVALUES) {
+ existed = smbldap_talloc_single_blob(talloc_tos(), ldap_struct, existing, attribute, &oldblob);
+ } else {
+ existed = smbldap_get_single_attribute(ldap_struct, existing, attribute, oldval, sizeof(oldval));
+ }
+ } else {
+ existed = False;
+ *oldval = '\0';
+ }
+
+ if (existed) {
+ bool equal = false;
+ if (op & LDAP_MOD_BVALUES) {
+ equal = (newblob && (data_blob_cmp(&oldblob, newblob) == 0));
+ } else {
+ /* all of our string attributes are case insensitive */
+ equal = (newval && (strcasecmp_m(oldval, newval) == 0));
+ }
+
+ if (equal) {
+ /* Believe it or not, but LDAP will deny a delete and
+ an add at the same time if the values are the
+ same... */
+ DEBUG(10,("smbldap_make_mod: attribute |%s| not changed.\n", attribute));
+ return;
+ }
+
+ /* There has been no value before, so don't delete it.
+ * Here's a possible race: We might end up with
+ * duplicate attributes */
+ /* By deleting exactly the value we found in the entry this
+ * should be race-free in the sense that the LDAP-Server will
+ * deny the complete operation if somebody changed the
+ * attribute behind our back. */
+ /* This will also allow modifying single valued attributes
+ * in Novell NDS. In NDS you have to first remove attribute and then
+ * you could add new value */
+
+ if (op & LDAP_MOD_BVALUES) {
+ DEBUG(10,("smbldap_make_mod: deleting attribute |%s| blob\n", attribute));
+ smbldap_set_mod_blob(mods, LDAP_MOD_DELETE, attribute, &oldblob);
+ } else {
+ DEBUG(10,("smbldap_make_mod: deleting attribute |%s| values |%s|\n", attribute, oldval));
+ smbldap_set_mod(mods, LDAP_MOD_DELETE, attribute, oldval);
+ }
+ }
+
+ /* Regardless of the real operation (add or modify)
+ we add the new value here. We rely on deleting
+ the old value, should it exist. */
+
+ if (op & LDAP_MOD_BVALUES) {
+ if (newblob && newblob->length) {
+ DEBUG(10,("smbldap_make_mod: adding attribute |%s| blob\n", attribute));
+ smbldap_set_mod_blob(mods, LDAP_MOD_ADD, attribute, newblob);
+ }
+ } else {
+ if ((newval != NULL) && (strlen(newval) > 0)) {
+ DEBUG(10,("smbldap_make_mod: adding attribute |%s| value |%s|\n", attribute, newval));
+ smbldap_set_mod(mods, LDAP_MOD_ADD, attribute, newval);
+ }
+ }
+}
+
+ void smbldap_make_mod(LDAP *ldap_struct, LDAPMessage *existing,
+ LDAPMod ***mods,
+ const char *attribute, const char *newval)
+{
+ smbldap_make_mod_internal(ldap_struct, existing, mods, attribute,
+ 0, newval, NULL);
+}
+
+ void smbldap_make_mod_blob(LDAP *ldap_struct, LDAPMessage *existing,
+ LDAPMod ***mods,
+ const char *attribute, const DATA_BLOB *newblob)
+{
+ smbldap_make_mod_internal(ldap_struct, existing, mods, attribute,
+ LDAP_MOD_BVALUES, NULL, newblob);
+}
+
+/**********************************************************************
+ Some variants of the LDAP rebind code do not pass in the third 'arg'
+ pointer to a void*, so we try and work around it by assuming that the
+ value of the 'LDAP *' pointer is the same as the one we had passed in
+ **********************************************************************/
+
+struct smbldap_state_lookup {
+ LDAP *ld;
+ struct smbldap_state *smbldap_state;
+ struct smbldap_state_lookup *prev, *next;
+};
+
+static struct smbldap_state_lookup *smbldap_state_lookup_list;
+
+static struct smbldap_state *smbldap_find_state(LDAP *ld)
+{
+ struct smbldap_state_lookup *t;
+
+ for (t = smbldap_state_lookup_list; t; t = t->next) {
+ if (t->ld == ld) {
+ return t->smbldap_state;
+ }
+ }
+ return NULL;
+}
+
+static void smbldap_delete_state(struct smbldap_state *smbldap_state)
+{
+ struct smbldap_state_lookup *t;
+
+ for (t = smbldap_state_lookup_list; t; t = t->next) {
+ if (t->smbldap_state == smbldap_state) {
+ DLIST_REMOVE(smbldap_state_lookup_list, t);
+ SAFE_FREE(t);
+ return;
+ }
+ }
+}
+
+static void smbldap_store_state(LDAP *ld, struct smbldap_state *smbldap_state)
+{
+ struct smbldap_state *tmp_ldap_state;
+ struct smbldap_state_lookup *t;
+
+ if ((tmp_ldap_state = smbldap_find_state(ld))) {
+ SMB_ASSERT(tmp_ldap_state == smbldap_state);
+ return;
+ }
+
+ t = SMB_XMALLOC_P(struct smbldap_state_lookup);
+ ZERO_STRUCTP(t);
+
+ DLIST_ADD_END(smbldap_state_lookup_list, t);
+ t->ld = ld;
+ t->smbldap_state = smbldap_state;
+}
+
+/********************************************************************
+ start TLS on an existing LDAP connection per config
+*******************************************************************/
+
+int smbldap_start_tls(LDAP *ldap_struct, int version)
+{
+ if (lp_ldap_ssl() != LDAP_SSL_START_TLS) {
+ return LDAP_SUCCESS;
+ }
+
+ return smbldap_start_tls_start(ldap_struct, version);
+}
+
+/********************************************************************
+ start TLS on an existing LDAP connection unconditionally
+*******************************************************************/
+
+int smbldap_start_tls_start(LDAP *ldap_struct, int version)
+{
+#ifdef LDAP_OPT_X_TLS
+ int rc,tls;
+
+ /* check if we use ldaps already */
+ ldap_get_option(ldap_struct, LDAP_OPT_X_TLS, &tls);
+ if (tls == LDAP_OPT_X_TLS_HARD) {
+ return LDAP_SUCCESS;
+ }
+
+ if (version != LDAP_VERSION3) {
+ DEBUG(0, ("Need LDAPv3 for Start TLS\n"));
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if ((rc = ldap_start_tls_s (ldap_struct, NULL, NULL)) != LDAP_SUCCESS) {
+ DEBUG(0,("Failed to issue the StartTLS instruction: %s\n",
+ ldap_err2string(rc)));
+ return rc;
+ }
+
+ DEBUG (3, ("StartTLS issued: using a TLS connection\n"));
+ return LDAP_SUCCESS;
+#else
+ DEBUG(0,("StartTLS not supported by LDAP client libraries!\n"));
+ return LDAP_OPERATIONS_ERROR;
+#endif
+}
+
+/********************************************************************
+ setup a connection to the LDAP server based on a uri
+*******************************************************************/
+
+static int smb_ldap_setup_conn(LDAP **ldap_struct, const char *uri)
+{
+ int rc;
+
+ DEBUG(10, ("smb_ldap_setup_connection: %s\n", uri));
+
+#ifdef HAVE_LDAP_INITIALIZE
+
+ rc = ldap_initialize(ldap_struct, uri);
+ if (rc) {
+ DEBUG(0, ("ldap_initialize: %s\n", ldap_err2string(rc)));
+ return rc;
+ }
+
+ if (lp_ldap_follow_referral() != Auto) {
+ rc = ldap_set_option(*ldap_struct, LDAP_OPT_REFERRALS,
+ lp_ldap_follow_referral() ? LDAP_OPT_ON : LDAP_OPT_OFF);
+ if (rc != LDAP_SUCCESS)
+ DEBUG(0, ("Failed to set LDAP_OPT_REFERRALS: %s\n",
+ ldap_err2string(rc)));
+ }
+
+#else
+
+ /* Parse the string manually */
+
+ {
+ int port = 0;
+ fstring protocol;
+ fstring host;
+ SMB_ASSERT(sizeof(protocol)>10 && sizeof(host)>254);
+
+
+ /* skip leading "URL:" (if any) */
+ if ( strnequal( uri, "URL:", 4 ) ) {
+ uri += 4;
+ }
+
+ sscanf(uri, "%10[^:]://%254[^:/]:%d", protocol, host, &port);
+
+ if (port == 0) {
+ if (strequal(protocol, "ldap")) {
+ port = LDAP_PORT;
+ } else if (strequal(protocol, "ldaps")) {
+ port = LDAPS_PORT;
+ } else {
+ DEBUG(0, ("unrecognised protocol (%s)!\n", protocol));
+ }
+ }
+
+ if ((*ldap_struct = ldap_init(host, port)) == NULL) {
+ DEBUG(0, ("ldap_init failed !\n"));
+ return LDAP_OPERATIONS_ERROR;
+ }
+
+ if (strequal(protocol, "ldaps")) {
+#ifdef LDAP_OPT_X_TLS
+ int tls = LDAP_OPT_X_TLS_HARD;
+ if (ldap_set_option (*ldap_struct, LDAP_OPT_X_TLS, &tls) != LDAP_SUCCESS)
+ {
+ DEBUG(0, ("Failed to setup a TLS session\n"));
+ }
+
+ DEBUG(3,("LDAPS option set...!\n"));
+#else
+ DEBUG(0,("smbldap_open_connection: Secure connection not supported by LDAP client libraries!\n"));
+ return LDAP_OPERATIONS_ERROR;
+#endif /* LDAP_OPT_X_TLS */
+ }
+ }
+
+ /* now set connection timeout */
+#ifdef LDAP_X_OPT_CONNECT_TIMEOUT /* Netscape */
+ {
+ int ct = lp_ldap_connection_timeout()*1000;
+ rc = ldap_set_option(*ldap_struct, LDAP_X_OPT_CONNECT_TIMEOUT, &ct);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("Failed to setup an ldap connection timeout %d: %s\n",
+ ct, ldap_err2string(rc)));
+ }
+ }
+#elif defined (LDAP_OPT_NETWORK_TIMEOUT) /* OpenLDAP */
+ {
+ struct timeval ct;
+ ct.tv_usec = 0;
+ ct.tv_sec = lp_ldap_connection_timeout();
+ rc = ldap_set_option(*ldap_struct, LDAP_OPT_NETWORK_TIMEOUT, &ct);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("Failed to setup an ldap connection timeout %d: %s\n",
+ (int)ct.tv_sec, ldap_err2string(rc)));
+ }
+ }
+#endif
+
+#endif /* HAVE_LDAP_INITIALIZE */
+ return LDAP_SUCCESS;
+}
+
+/********************************************************************
+ try to upgrade to Version 3 LDAP if not already, in either case return current
+ version
+ *******************************************************************/
+
+static int smb_ldap_upgrade_conn(LDAP *ldap_struct, int *new_version)
+{
+ int version;
+ int rc;
+
+ /* assume the worst */
+ *new_version = LDAP_VERSION2;
+
+ rc = ldap_get_option(ldap_struct, LDAP_OPT_PROTOCOL_VERSION, &version);
+ if (rc) {
+ return rc;
+ }
+
+ if (version == LDAP_VERSION3) {
+ *new_version = LDAP_VERSION3;
+ return LDAP_SUCCESS;
+ }
+
+ /* try upgrade */
+ version = LDAP_VERSION3;
+ rc = ldap_set_option (ldap_struct, LDAP_OPT_PROTOCOL_VERSION, &version);
+ if (rc) {
+ return rc;
+ }
+
+ *new_version = LDAP_VERSION3;
+ return LDAP_SUCCESS;
+}
+
+/*******************************************************************
+ open a connection to the ldap server (just until the bind)
+ ******************************************************************/
+
+int smbldap_setup_full_conn(LDAP **ldap_struct, const char *uri)
+{
+ int rc, version;
+
+ rc = smb_ldap_setup_conn(ldap_struct, uri);
+ if (rc) {
+ return rc;
+ }
+
+ rc = smb_ldap_upgrade_conn(*ldap_struct, &version);
+ if (rc) {
+ return rc;
+ }
+
+ rc = smbldap_start_tls(*ldap_struct, version);
+ if (rc) {
+ return rc;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+/*******************************************************************
+ open a connection to the ldap server.
+******************************************************************/
+static int smbldap_open_connection (struct smbldap_state *ldap_state)
+
+{
+ int rc = LDAP_SUCCESS;
+ int version;
+ int deref;
+ LDAP **ldap_struct = &ldap_state->ldap_struct;
+
+ rc = smb_ldap_setup_conn(ldap_struct, ldap_state->uri);
+ if (rc) {
+ return rc;
+ }
+
+ /* Store the LDAP pointer in a lookup list */
+
+ smbldap_store_state(*ldap_struct, ldap_state);
+
+ /* Upgrade to LDAPv3 if possible */
+
+ rc = smb_ldap_upgrade_conn(*ldap_struct, &version);
+ if (rc) {
+ return rc;
+ }
+
+ /* Start TLS if required */
+
+ rc = smbldap_start_tls(*ldap_struct, version);
+ if (rc) {
+ return rc;
+ }
+
+ /* Set alias dereferencing method */
+ deref = lp_ldap_deref();
+ if (deref != -1) {
+ if (ldap_set_option (*ldap_struct, LDAP_OPT_DEREF, &deref) != LDAP_OPT_SUCCESS) {
+ DEBUG(1,("smbldap_open_connection: Failed to set dereferencing method: %d\n", deref));
+ } else {
+ DEBUG(5,("Set dereferencing method: %d\n", deref));
+ }
+ }
+
+ DEBUG(2, ("smbldap_open_connection: connection opened\n"));
+ return rc;
+}
+
+/*******************************************************************
+ a rebind function for authenticated referrals
+ This version takes a void* that we can shove useful stuff in :-)
+******************************************************************/
+#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
+#else
+static int rebindproc_with_state (LDAP * ld, char **whop, char **credp,
+ int *methodp, int freeit, void *arg)
+{
+ struct smbldap_state *ldap_state = arg;
+ struct timespec ts;
+
+ /** @TODO Should we be doing something to check what servers we rebind to?
+ Could we get a referral to a machine that we don't want to give our
+ username and password to? */
+
+ if (freeit) {
+ SAFE_FREE(*whop);
+ if (*credp) {
+ memset(*credp, '\0', strlen(*credp));
+ }
+ SAFE_FREE(*credp);
+ } else {
+ DEBUG(5,("rebind_proc_with_state: Rebinding as \"%s\"\n",
+ ldap_state->bind_dn?ldap_state->bind_dn:"[Anonymous bind]"));
+
+ if (ldap_state->anonymous) {
+ *whop = NULL;
+ *credp = NULL;
+ } else {
+ *whop = SMB_STRDUP(ldap_state->bind_dn);
+ if (!*whop) {
+ return LDAP_NO_MEMORY;
+ }
+ *credp = SMB_STRDUP(ldap_state->bind_secret);
+ if (!*credp) {
+ SAFE_FREE(*whop);
+ return LDAP_NO_MEMORY;
+ }
+ }
+ *methodp = LDAP_AUTH_SIMPLE;
+ }
+
+ clock_gettime_mono(&ts);
+ ldap_state->last_rebind = convert_timespec_to_timeval(ts);
+
+ return 0;
+}
+#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
+
+/*******************************************************************
+ a rebind function for authenticated referrals
+ This version takes a void* that we can shove useful stuff in :-)
+ and actually does the connection.
+******************************************************************/
+#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
+static int rebindproc_connect_with_state (LDAP *ldap_struct,
+ LDAP_CONST char *url,
+ ber_tag_t request,
+ ber_int_t msgid, void *arg)
+{
+ struct smbldap_state *ldap_state =
+ (struct smbldap_state *)arg;
+ int rc;
+ struct timespec ts;
+ int version;
+
+ DEBUG(5,("rebindproc_connect_with_state: Rebinding to %s as \"%s\"\n",
+ url, ldap_state->bind_dn?ldap_state->bind_dn:"[Anonymous bind]"));
+
+ /* call START_TLS again (ldaps:// is handled by the OpenLDAP library
+ * itself) before rebinding to another LDAP server to avoid to expose
+ * our credentials. At least *try* to secure the connection - Guenther */
+
+ smb_ldap_upgrade_conn(ldap_struct, &version);
+ smbldap_start_tls(ldap_struct, version);
+
+ /** @TODO Should we be doing something to check what servers we rebind to?
+ Could we get a referral to a machine that we don't want to give our
+ username and password to? */
+
+ rc = ldap_simple_bind_s(ldap_struct, ldap_state->bind_dn, ldap_state->bind_secret);
+
+ /* only set the last rebind timestamp when we did rebind after a
+ * non-read LDAP operation. That way we avoid the replication sleep
+ * after a simple redirected search operation - Guenther */
+
+ switch (request) {
+
+ case LDAP_REQ_MODIFY:
+ case LDAP_REQ_ADD:
+ case LDAP_REQ_DELETE:
+ case LDAP_REQ_MODDN:
+ case LDAP_REQ_EXTENDED:
+ DEBUG(10,("rebindproc_connect_with_state: "
+ "setting last_rebind timestamp "
+ "(req: 0x%02x)\n", (unsigned int)request));
+ clock_gettime_mono(&ts);
+ ldap_state->last_rebind = convert_timespec_to_timeval(ts);
+ break;
+ default:
+ ZERO_STRUCT(ldap_state->last_rebind);
+ break;
+ }
+
+ return rc;
+}
+#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
+
+/*******************************************************************
+ Add a rebind function for authenticated referrals
+******************************************************************/
+#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
+#else
+# if LDAP_SET_REBIND_PROC_ARGS == 2
+static int rebindproc (LDAP *ldap_struct, char **whop, char **credp,
+ int *method, int freeit )
+{
+ struct smbldap_state *ldap_state = smbldap_find_state(ldap_struct);
+
+ return rebindproc_with_state(ldap_struct, whop, credp,
+ method, freeit, ldap_state);
+}
+# endif /*LDAP_SET_REBIND_PROC_ARGS == 2*/
+#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
+
+/*******************************************************************
+ a rebind function for authenticated referrals
+ this also does the connection, but no void*.
+******************************************************************/
+#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
+# if LDAP_SET_REBIND_PROC_ARGS == 2
+static int rebindproc_connect (LDAP * ld, LDAP_CONST char *url, int request,
+ ber_int_t msgid)
+{
+ struct smbldap_state *ldap_state = smbldap_find_state(ld);
+
+ return rebindproc_connect_with_state(ld, url, (ber_tag_t)request, msgid,
+ ldap_state);
+}
+# endif /*LDAP_SET_REBIND_PROC_ARGS == 2*/
+#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
+
+/*******************************************************************
+ connect to the ldap server under system privilege.
+******************************************************************/
+static int smbldap_connect_system(struct smbldap_state *ldap_state)
+{
+ LDAP *ldap_struct = smbldap_get_ldap(ldap_state);
+ int rc;
+ int version;
+
+ /* removed the sasl_bind_s "EXTERNAL" stuff, as my testsuite
+ (OpenLDAP) doesn't seem to support it */
+
+ DEBUG(10,("ldap_connect_system: Binding to ldap server %s as \"%s\"\n",
+ ldap_state->uri, ldap_state->bind_dn));
+
+#ifdef HAVE_LDAP_SET_REBIND_PROC
+#if defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)
+# if LDAP_SET_REBIND_PROC_ARGS == 2
+ ldap_set_rebind_proc(ldap_struct, &rebindproc_connect);
+# endif
+# if LDAP_SET_REBIND_PROC_ARGS == 3
+ ldap_set_rebind_proc(ldap_struct, &rebindproc_connect_with_state, (void *)ldap_state);
+# endif
+#else /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
+# if LDAP_SET_REBIND_PROC_ARGS == 2
+ ldap_set_rebind_proc(ldap_struct, &rebindproc);
+# endif
+# if LDAP_SET_REBIND_PROC_ARGS == 3
+ ldap_set_rebind_proc(ldap_struct, &rebindproc_with_state, (void *)ldap_state);
+# endif
+#endif /*defined(LDAP_API_FEATURE_X_OPENLDAP) && (LDAP_API_VERSION > 2000)*/
+#endif
+
+ /* When there is an alternative bind callback is set,
+ attempt to use it to perform the bind */
+ if (ldap_state->bind_callback != NULL) {
+ /* We have to allow bind callback to be run under become_root/unbecome_root
+ to make sure within smbd the callback has proper write access to its resources,
+ like credential cache. This is similar to passdb case where this callback is supposed
+ to be used. When used outside smbd, become_root()/unbecome_root() are no-op.
+ */
+ become_root();
+ rc = ldap_state->bind_callback(ldap_struct, ldap_state, ldap_state->bind_callback_data);
+ unbecome_root();
+ } else {
+ rc = ldap_simple_bind_s(ldap_struct, ldap_state->bind_dn, ldap_state->bind_secret);
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ char *ld_error = NULL;
+ ldap_get_option(smbldap_get_ldap(ldap_state),
+ LDAP_OPT_ERROR_STRING,
+ &ld_error);
+ DEBUG(ldap_state->num_failures ? 2 : 0,
+ ("failed to bind to server %s with dn=\"%s\" Error: %s\n\t%s\n",
+ ldap_state->uri,
+ ldap_state->bind_dn ? ldap_state->bind_dn : "[Anonymous bind]",
+ ldap_err2string(rc),
+ ld_error ? ld_error : "(unknown)"));
+ SAFE_FREE(ld_error);
+ ldap_state->num_failures++;
+ goto done;
+ }
+
+ ldap_state->num_failures = 0;
+ ldap_state->paged_results = False;
+
+ ldap_get_option(smbldap_get_ldap(ldap_state),
+ LDAP_OPT_PROTOCOL_VERSION, &version);
+
+ if (smbldap_has_control(smbldap_get_ldap(ldap_state), ADS_PAGE_CTL_OID)
+ && version == 3) {
+ ldap_state->paged_results = True;
+ }
+
+ DEBUG(3, ("ldap_connect_system: successful connection to the LDAP server\n"));
+ DEBUGADD(10, ("ldap_connect_system: LDAP server %s support paged results\n",
+ ldap_state->paged_results ? "does" : "does not"));
+done:
+ if (rc != 0) {
+ ldap_unbind(ldap_struct);
+ ldap_state->ldap_struct = NULL;
+ }
+ return rc;
+}
+
+static void smbldap_idle_fn(struct tevent_context *tevent_ctx,
+ struct tevent_timer *te,
+ struct timeval now_abs,
+ void *private_data);
+
+/**********************************************************************
+ Connect to LDAP server (called before every ldap operation)
+*********************************************************************/
+static int smbldap_open(struct smbldap_state *ldap_state)
+{
+ int rc, opt_rc;
+ bool reopen = False;
+ SMB_ASSERT(ldap_state);
+
+ if ((smbldap_get_ldap(ldap_state) != NULL) &&
+ ((ldap_state->last_ping + SMBLDAP_DONT_PING_TIME) <
+ time_mono(NULL))) {
+
+#ifdef HAVE_UNIXSOCKET
+ struct sockaddr_un addr;
+#else
+ struct sockaddr_storage addr;
+#endif
+ socklen_t len = sizeof(addr);
+ int sd;
+
+ opt_rc = ldap_get_option(smbldap_get_ldap(ldap_state),
+ LDAP_OPT_DESC, &sd);
+ if (opt_rc == 0 && (getpeername(sd, (struct sockaddr *) &addr, &len)) < 0 )
+ reopen = True;
+
+#ifdef HAVE_UNIXSOCKET
+ if (opt_rc == 0 && addr.sun_family == AF_UNIX)
+ reopen = True;
+#endif
+ if (reopen) {
+ /* the other end has died. reopen. */
+ ldap_unbind(smbldap_get_ldap(ldap_state));
+ ldap_state->ldap_struct = NULL;
+ ldap_state->last_ping = (time_t)0;
+ } else {
+ ldap_state->last_ping = time_mono(NULL);
+ }
+ }
+
+ if (smbldap_get_ldap(ldap_state) != NULL) {
+ DEBUG(11,("smbldap_open: already connected to the LDAP server\n"));
+ return LDAP_SUCCESS;
+ }
+
+ if ((rc = smbldap_open_connection(ldap_state))) {
+ return rc;
+ }
+
+ if ((rc = smbldap_connect_system(ldap_state))) {
+ return rc;
+ }
+
+
+ ldap_state->last_ping = time_mono(NULL);
+ ldap_state->pid = getpid();
+
+ TALLOC_FREE(ldap_state->idle_event);
+
+ if (ldap_state->tevent_context != NULL) {
+ ldap_state->idle_event = tevent_add_timer(
+ ldap_state->tevent_context, ldap_state,
+ timeval_current_ofs(SMBLDAP_IDLE_TIME, 0),
+ smbldap_idle_fn, ldap_state);
+ }
+
+ DEBUG(4,("The LDAP server is successfully connected\n"));
+
+ return LDAP_SUCCESS;
+}
+
+/**********************************************************************
+Disconnect from LDAP server
+*********************************************************************/
+static NTSTATUS smbldap_close(struct smbldap_state *ldap_state)
+{
+ if (!ldap_state)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ if (smbldap_get_ldap(ldap_state) != NULL) {
+ ldap_unbind(smbldap_get_ldap(ldap_state));
+ ldap_state->ldap_struct = NULL;
+ }
+
+ smbldap_delete_state(ldap_state);
+
+ TALLOC_FREE(ldap_state->idle_event);
+
+ DEBUG(5,("The connection to the LDAP server was closed\n"));
+ /* maybe free the results here --metze */
+
+ return NT_STATUS_OK;
+}
+
+static SIG_ATOMIC_T got_alarm;
+
+static void gotalarm_sig(int dummy)
+{
+ got_alarm = 1;
+}
+
+static time_t calc_ldap_abs_endtime(int ldap_to)
+{
+ if (ldap_to == 0) {
+ /* No timeout - don't
+ return a value for
+ the alarm. */
+ return (time_t)0;
+ }
+
+ /* Make the alarm time one second beyond
+ the timeout we're setting for the
+ remote search timeout, to allow that
+ to fire in preference. */
+
+ return time_mono(NULL)+ldap_to+1;
+}
+
+static int end_ldap_local_alarm(time_t absolute_endtime, int rc)
+{
+ if (absolute_endtime) {
+ alarm(0);
+ CatchSignal(SIGALRM, SIG_IGN);
+ if (got_alarm) {
+ /* Client timeout error code. */
+ got_alarm = 0;
+ return LDAP_TIMEOUT;
+ }
+ }
+ return rc;
+}
+
+static void setup_ldap_local_alarm(struct smbldap_state *ldap_state, time_t absolute_endtime)
+{
+ time_t now = time_mono(NULL);
+
+ if (absolute_endtime) {
+ got_alarm = 0;
+ CatchSignal(SIGALRM, gotalarm_sig);
+ alarm(absolute_endtime - now);
+ }
+
+ if (ldap_state->pid != getpid()) {
+ smbldap_close(ldap_state);
+ }
+}
+
+static void get_ldap_errs(struct smbldap_state *ldap_state, char **pp_ld_error, int *p_ld_errno)
+{
+ ldap_get_option(smbldap_get_ldap(ldap_state),
+ LDAP_OPT_ERROR_NUMBER, p_ld_errno);
+
+ ldap_get_option(smbldap_get_ldap(ldap_state),
+ LDAP_OPT_ERROR_STRING, pp_ld_error);
+}
+
+static int get_cached_ldap_connect(struct smbldap_state *ldap_state, time_t abs_endtime)
+{
+ int attempts = 0;
+
+ while (1) {
+ int rc;
+ time_t now;
+
+ now = time_mono(NULL);
+ ldap_state->last_use = now;
+
+ if (abs_endtime && now > abs_endtime) {
+ smbldap_close(ldap_state);
+ return LDAP_TIMEOUT;
+ }
+
+ rc = smbldap_open(ldap_state);
+
+ if (rc == LDAP_SUCCESS) {
+ return LDAP_SUCCESS;
+ }
+
+ attempts++;
+ DEBUG(1, ("Connection to LDAP server failed for the "
+ "%d try!\n", attempts));
+
+ if (rc == LDAP_INSUFFICIENT_ACCESS) {
+ /* The fact that we are non-root or any other
+ * access-denied condition will not change in the next
+ * round of trying */
+ return rc;
+ }
+
+ if (got_alarm) {
+ smbldap_close(ldap_state);
+ return LDAP_TIMEOUT;
+ }
+
+ smb_msleep(1000);
+
+ if (got_alarm) {
+ smbldap_close(ldap_state);
+ return LDAP_TIMEOUT;
+ }
+ }
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static int smbldap_search_ext(struct smbldap_state *ldap_state,
+ const char *base, int scope, const char *filter,
+ const char *attrs[], int attrsonly,
+ LDAPControl **sctrls, LDAPControl **cctrls,
+ int sizelimit, LDAPMessage **res)
+{
+ int rc = LDAP_SERVER_DOWN;
+ char *utf8_filter;
+ int to = lp_ldap_timeout();
+ time_t abs_endtime = calc_ldap_abs_endtime(to);
+ struct timeval timeout;
+ struct timeval *timeout_ptr = NULL;
+ size_t converted_size;
+
+ SMB_ASSERT(ldap_state);
+
+ DEBUG(5,("smbldap_search_ext: base => [%s], filter => [%s], "
+ "scope => [%d]\n", base, filter, scope));
+
+ if (ldap_state->last_rebind.tv_sec > 0) {
+ struct timeval tval;
+ struct timespec ts;
+ int64_t tdiff = 0;
+ int sleep_time = 0;
+
+ clock_gettime_mono(&ts);
+ tval = convert_timespec_to_timeval(ts);
+
+ tdiff = usec_time_diff(&tval, &ldap_state->last_rebind);
+ tdiff /= 1000; /* Convert to milliseconds. */
+
+ sleep_time = lp_ldap_replication_sleep()-(int)tdiff;
+ sleep_time = MIN(sleep_time, MAX_LDAP_REPLICATION_SLEEP_TIME);
+
+ if (sleep_time > 0) {
+ /* we wait for the LDAP replication */
+ DEBUG(5,("smbldap_search_ext: waiting %d milliseconds "
+ "for LDAP replication.\n",sleep_time));
+ smb_msleep(sleep_time);
+ DEBUG(5,("smbldap_search_ext: go on!\n"));
+ }
+ ZERO_STRUCT(ldap_state->last_rebind);
+ }
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_filter, filter, &converted_size)) {
+ return LDAP_NO_MEMORY;
+ }
+
+ /* Setup remote timeout for the ldap_search_ext_s call. */
+ if (to) {
+ timeout.tv_sec = to;
+ timeout.tv_usec = 0;
+ timeout_ptr = &timeout;
+ }
+
+ setup_ldap_local_alarm(ldap_state, abs_endtime);
+
+ while (1) {
+ char *ld_error = NULL;
+ int ld_errno;
+
+ rc = get_cached_ldap_connect(ldap_state, abs_endtime);
+ if (rc != LDAP_SUCCESS) {
+ break;
+ }
+
+ rc = ldap_search_ext_s(smbldap_get_ldap(ldap_state),
+ base, scope,
+ utf8_filter,
+ discard_const_p(char *, attrs),
+ attrsonly, sctrls, cctrls, timeout_ptr,
+ sizelimit, res);
+ if (rc == LDAP_SUCCESS) {
+ break;
+ }
+
+ get_ldap_errs(ldap_state, &ld_error, &ld_errno);
+
+ DEBUG(10, ("Failed search for base: %s, error: %d (%s) "
+ "(%s)\n", base, ld_errno,
+ ldap_err2string(rc),
+ ld_error ? ld_error : "unknown"));
+ SAFE_FREE(ld_error);
+
+ if (ld_errno != LDAP_SERVER_DOWN) {
+ break;
+ }
+ ldap_unbind(smbldap_get_ldap(ldap_state));
+ ldap_state->ldap_struct = NULL;
+ }
+
+ TALLOC_FREE(utf8_filter);
+ return end_ldap_local_alarm(abs_endtime, rc);
+}
+
+int smbldap_search(struct smbldap_state *ldap_state,
+ const char *base, int scope, const char *filter,
+ const char *attrs[], int attrsonly,
+ LDAPMessage **res)
+{
+ return smbldap_search_ext(ldap_state, base, scope, filter, attrs,
+ attrsonly, NULL, NULL, LDAP_NO_LIMIT, res);
+}
+
+int smbldap_search_paged(struct smbldap_state *ldap_state,
+ const char *base, int scope, const char *filter,
+ const char **attrs, int attrsonly, int pagesize,
+ LDAPMessage **res, void **cookie)
+{
+ LDAPControl pr;
+ LDAPControl **rcontrols;
+ LDAPControl *controls[2] = { NULL, NULL};
+ BerElement *cookie_be = NULL;
+ struct berval *cookie_bv = NULL;
+ int tmp = 0, i, rc;
+ bool critical = True;
+
+ *res = NULL;
+
+ DEBUG(3,("smbldap_search_paged: base => [%s], filter => [%s],"
+ "scope => [%d], pagesize => [%d]\n",
+ base, filter, scope, pagesize));
+
+ cookie_be = ber_alloc_t(LBER_USE_DER);
+ if (cookie_be == NULL) {
+ DEBUG(0,("smbldap_create_page_control: ber_alloc_t returns "
+ "NULL\n"));
+ return LDAP_NO_MEMORY;
+ }
+
+ /* construct cookie */
+ if (*cookie != NULL) {
+ ber_printf(cookie_be, "{iO}", (ber_int_t) pagesize, *cookie);
+ ber_bvfree((struct berval *)*cookie); /* don't need it from last time */
+ *cookie = NULL;
+ } else {
+ ber_printf(cookie_be, "{io}", (ber_int_t) pagesize, "", 0);
+ }
+ ber_flatten(cookie_be, &cookie_bv);
+
+ pr.ldctl_oid = discard_const_p(char, ADS_PAGE_CTL_OID);
+ pr.ldctl_iscritical = (char) critical;
+ pr.ldctl_value.bv_len = cookie_bv->bv_len;
+ pr.ldctl_value.bv_val = cookie_bv->bv_val;
+
+ controls[0] = &pr;
+ controls[1] = NULL;
+
+ rc = smbldap_search_ext(ldap_state, base, scope, filter, attrs,
+ 0, controls, NULL, LDAP_NO_LIMIT, res);
+
+ ber_free(cookie_be, 1);
+ ber_bvfree(cookie_bv);
+
+ if (rc != 0) {
+ DEBUG(3,("smbldap_search_paged: smbldap_search_ext(%s) "
+ "failed with [%s]\n", filter, ldap_err2string(rc)));
+ goto done;
+ }
+
+ DEBUG(3,("smbldap_search_paged: search was successful\n"));
+
+ rc = ldap_parse_result(smbldap_get_ldap(ldap_state), *res, NULL, NULL,
+ NULL, NULL, &rcontrols, 0);
+ if (rc != 0) {
+ DEBUG(3,("smbldap_search_paged: ldap_parse_result failed " \
+ "with [%s]\n", ldap_err2string(rc)));
+ goto done;
+ }
+
+ if (rcontrols == NULL)
+ goto done;
+
+ for (i=0; rcontrols[i]; i++) {
+
+ if (strcmp(ADS_PAGE_CTL_OID, rcontrols[i]->ldctl_oid) != 0)
+ continue;
+
+ cookie_be = ber_init(&rcontrols[i]->ldctl_value);
+ ber_scanf(cookie_be,"{iO}", &tmp, &cookie_bv);
+ /* the berval is the cookie, but must be freed when it is all
+ done */
+ if (cookie_bv->bv_len)
+ *cookie=ber_bvdup(cookie_bv);
+ else
+ *cookie=NULL;
+ ber_bvfree(cookie_bv);
+ ber_free(cookie_be, 1);
+ break;
+ }
+ ldap_controls_free(rcontrols);
+done:
+ return rc;
+}
+
+int smbldap_modify(struct smbldap_state *ldap_state, const char *dn, LDAPMod *attrs[])
+{
+ int rc = LDAP_SERVER_DOWN;
+ char *utf8_dn;
+ time_t abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());
+ size_t converted_size;
+
+ SMB_ASSERT(ldap_state);
+
+ DEBUG(5,("smbldap_modify: dn => [%s]\n", dn ));
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_dn, dn, &converted_size)) {
+ return LDAP_NO_MEMORY;
+ }
+
+ setup_ldap_local_alarm(ldap_state, abs_endtime);
+
+ while (1) {
+ char *ld_error = NULL;
+ int ld_errno;
+
+ rc = get_cached_ldap_connect(ldap_state, abs_endtime);
+ if (rc != LDAP_SUCCESS) {
+ break;
+ }
+
+ rc = ldap_modify_s(smbldap_get_ldap(ldap_state), utf8_dn,
+ attrs);
+ if (rc == LDAP_SUCCESS) {
+ break;
+ }
+
+ get_ldap_errs(ldap_state, &ld_error, &ld_errno);
+
+ DEBUG(10, ("Failed to modify dn: %s, error: %d (%s) "
+ "(%s)\n", dn, ld_errno,
+ ldap_err2string(rc),
+ ld_error ? ld_error : "unknown"));
+ SAFE_FREE(ld_error);
+
+ if (ld_errno != LDAP_SERVER_DOWN) {
+ break;
+ }
+ ldap_unbind(smbldap_get_ldap(ldap_state));
+ ldap_state->ldap_struct = NULL;
+ }
+
+ TALLOC_FREE(utf8_dn);
+ return end_ldap_local_alarm(abs_endtime, rc);
+}
+
+int smbldap_add(struct smbldap_state *ldap_state, const char *dn, LDAPMod *attrs[])
+{
+ int rc = LDAP_SERVER_DOWN;
+ char *utf8_dn;
+ time_t abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());
+ size_t converted_size;
+
+ SMB_ASSERT(ldap_state);
+
+ DEBUG(5,("smbldap_add: dn => [%s]\n", dn ));
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_dn, dn, &converted_size)) {
+ return LDAP_NO_MEMORY;
+ }
+
+ setup_ldap_local_alarm(ldap_state, abs_endtime);
+
+ while (1) {
+ char *ld_error = NULL;
+ int ld_errno;
+
+ rc = get_cached_ldap_connect(ldap_state, abs_endtime);
+ if (rc != LDAP_SUCCESS) {
+ break;
+ }
+
+ rc = ldap_add_s(smbldap_get_ldap(ldap_state), utf8_dn, attrs);
+ if (rc == LDAP_SUCCESS) {
+ break;
+ }
+
+ get_ldap_errs(ldap_state, &ld_error, &ld_errno);
+
+ DEBUG(10, ("Failed to add dn: %s, error: %d (%s) "
+ "(%s)\n", dn, ld_errno,
+ ldap_err2string(rc),
+ ld_error ? ld_error : "unknown"));
+ SAFE_FREE(ld_error);
+
+ if (ld_errno != LDAP_SERVER_DOWN) {
+ break;
+ }
+ ldap_unbind(smbldap_get_ldap(ldap_state));
+ ldap_state->ldap_struct = NULL;
+ }
+
+ TALLOC_FREE(utf8_dn);
+ return end_ldap_local_alarm(abs_endtime, rc);
+}
+
+int smbldap_delete(struct smbldap_state *ldap_state, const char *dn)
+{
+ int rc = LDAP_SERVER_DOWN;
+ char *utf8_dn;
+ time_t abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());
+ size_t converted_size;
+
+ SMB_ASSERT(ldap_state);
+
+ DEBUG(5,("smbldap_delete: dn => [%s]\n", dn ));
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_dn, dn, &converted_size)) {
+ return LDAP_NO_MEMORY;
+ }
+
+ setup_ldap_local_alarm(ldap_state, abs_endtime);
+
+ while (1) {
+ char *ld_error = NULL;
+ int ld_errno;
+
+ rc = get_cached_ldap_connect(ldap_state, abs_endtime);
+ if (rc != LDAP_SUCCESS) {
+ break;
+ }
+
+ rc = ldap_delete_s(smbldap_get_ldap(ldap_state), utf8_dn);
+ if (rc == LDAP_SUCCESS) {
+ break;
+ }
+
+ get_ldap_errs(ldap_state, &ld_error, &ld_errno);
+
+ DEBUG(10, ("Failed to delete dn: %s, error: %d (%s) "
+ "(%s)\n", dn, ld_errno,
+ ldap_err2string(rc),
+ ld_error ? ld_error : "unknown"));
+ SAFE_FREE(ld_error);
+
+ if (ld_errno != LDAP_SERVER_DOWN) {
+ break;
+ }
+ ldap_unbind(smbldap_get_ldap(ldap_state));
+ ldap_state->ldap_struct = NULL;
+ }
+
+ TALLOC_FREE(utf8_dn);
+ return end_ldap_local_alarm(abs_endtime, rc);
+}
+
+int smbldap_extended_operation(struct smbldap_state *ldap_state,
+ LDAP_CONST char *reqoid, struct berval *reqdata,
+ LDAPControl **serverctrls, LDAPControl **clientctrls,
+ char **retoidp, struct berval **retdatap)
+{
+ int rc = LDAP_SERVER_DOWN;
+ time_t abs_endtime = calc_ldap_abs_endtime(lp_ldap_timeout());
+
+ if (!ldap_state)
+ return (-1);
+
+ setup_ldap_local_alarm(ldap_state, abs_endtime);
+
+ while (1) {
+ char *ld_error = NULL;
+ int ld_errno;
+
+ rc = get_cached_ldap_connect(ldap_state, abs_endtime);
+ if (rc != LDAP_SUCCESS) {
+ break;
+ }
+
+ rc = ldap_extended_operation_s(smbldap_get_ldap(ldap_state),
+ reqoid,
+ reqdata, serverctrls,
+ clientctrls, retoidp, retdatap);
+ if (rc == LDAP_SUCCESS) {
+ break;
+ }
+
+ get_ldap_errs(ldap_state, &ld_error, &ld_errno);
+
+ DEBUG(10, ("Extended operation failed with error: "
+ "%d (%s) (%s)\n", ld_errno,
+ ldap_err2string(rc),
+ ld_error ? ld_error : "unknown"));
+ SAFE_FREE(ld_error);
+
+ if (ld_errno != LDAP_SERVER_DOWN) {
+ break;
+ }
+ ldap_unbind(smbldap_get_ldap(ldap_state));
+ ldap_state->ldap_struct = NULL;
+ }
+
+ return end_ldap_local_alarm(abs_endtime, rc);
+}
+
+/*******************************************************************
+ run the search by name.
+******************************************************************/
+int smbldap_search_suffix (struct smbldap_state *ldap_state,
+ const char *filter, const char **search_attr,
+ LDAPMessage ** result)
+{
+ return smbldap_search(ldap_state, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE,
+ filter, search_attr, 0, result);
+}
+
+static void smbldap_idle_fn(struct tevent_context *tevent_ctx,
+ struct tevent_timer *te,
+ struct timeval now_abs,
+ void *private_data)
+{
+ struct smbldap_state *state = (struct smbldap_state *)private_data;
+
+ TALLOC_FREE(state->idle_event);
+
+ if (smbldap_get_ldap(state) == NULL) {
+ DEBUG(10,("ldap connection not connected...\n"));
+ return;
+ }
+
+ if ((state->last_use+SMBLDAP_IDLE_TIME) > time_mono(NULL)) {
+ DEBUG(10,("ldap connection not idle...\n"));
+
+ /* this needs to be made monotonic clock aware inside tevent: */
+ state->idle_event = tevent_add_timer(
+ tevent_ctx, state,
+ timeval_add(&now_abs, SMBLDAP_IDLE_TIME, 0),
+ smbldap_idle_fn,
+ private_data);
+ return;
+ }
+
+ DEBUG(7,("ldap connection idle...closing connection\n"));
+ smbldap_close(state);
+}
+
+/**********************************************************************
+ Housekeeping
+ *********************************************************************/
+
+void smbldap_free_struct(struct smbldap_state **ldap_state)
+{
+ smbldap_close(*ldap_state);
+
+ if ((*ldap_state)->bind_secret) {
+ memset((*ldap_state)->bind_secret, '\0', strlen((*ldap_state)->bind_secret));
+ }
+
+ SAFE_FREE((*ldap_state)->bind_dn);
+ BURN_FREE_STR((*ldap_state)->bind_secret);
+ smbldap_set_bind_callback(*ldap_state, NULL, NULL);
+
+ TALLOC_FREE(*ldap_state);
+
+ /* No need to free any further, as it is talloc()ed */
+}
+
+static int smbldap_state_destructor(struct smbldap_state *state)
+{
+ smbldap_free_struct(&state);
+ return 0;
+}
+
+
+/**********************************************************************
+ Initialise the 'general' ldap structures, on which ldap operations may be conducted
+ *********************************************************************/
+
+NTSTATUS smbldap_init(TALLOC_CTX *mem_ctx, struct tevent_context *tevent_ctx,
+ const char *location,
+ bool anon,
+ const char *bind_dn,
+ const char *bind_secret,
+ struct smbldap_state **smbldap_state)
+{
+ *smbldap_state = talloc_zero(mem_ctx, struct smbldap_state);
+ if (!*smbldap_state) {
+ DEBUG(0, ("talloc() failed for ldapsam private_data!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (location) {
+ (*smbldap_state)->uri = talloc_strdup(mem_ctx, location);
+ } else {
+ (*smbldap_state)->uri = "ldap://localhost";
+ }
+
+ (*smbldap_state)->tevent_context = tevent_ctx;
+
+ if (bind_dn && bind_secret) {
+ smbldap_set_creds(*smbldap_state, anon, bind_dn, bind_secret);
+ }
+
+ talloc_set_destructor(*smbldap_state, smbldap_state_destructor);
+ return NT_STATUS_OK;
+}
+
+ char *smbldap_talloc_dn(TALLOC_CTX *mem_ctx, LDAP *ld,
+ LDAPMessage *entry)
+{
+ char *utf8_dn, *unix_dn;
+ size_t converted_size;
+
+ utf8_dn = ldap_get_dn(ld, entry);
+ if (!utf8_dn) {
+ DEBUG (5, ("smbldap_talloc_dn: ldap_get_dn failed\n"));
+ return NULL;
+ }
+ if (!pull_utf8_talloc(mem_ctx, &unix_dn, utf8_dn, &converted_size)) {
+ DEBUG (0, ("smbldap_talloc_dn: String conversion failure utf8 "
+ "[%s]\n", utf8_dn));
+ return NULL;
+ }
+ ldap_memfree(utf8_dn);
+ return unix_dn;
+}
+
+/*******************************************************************
+ Check if root-dse has a certain Control or Extension
+********************************************************************/
+
+static bool smbldap_check_root_dse(LDAP *ld, const char **attrs, const char *value)
+{
+ LDAPMessage *msg = NULL;
+ LDAPMessage *entry = NULL;
+ char **values = NULL;
+ int rc, num_result, num_values, i;
+ bool result = False;
+
+ if (!attrs[0]) {
+ DEBUG(3,("smbldap_check_root_dse: nothing to look for\n"));
+ return False;
+ }
+
+ if (!strequal(attrs[0], "supportedExtension") &&
+ !strequal(attrs[0], "supportedControl") &&
+ !strequal(attrs[0], "namingContexts")) {
+ DEBUG(3,("smbldap_check_root_dse: no idea what to query root-dse for: %s ?\n", attrs[0]));
+ return False;
+ }
+
+ rc = ldap_search_s(ld, "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", discard_const_p(char *, attrs), 0 , &msg);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(3,("smbldap_check_root_dse: Could not search rootDSE\n"));
+ return False;
+ }
+
+ num_result = ldap_count_entries(ld, msg);
+
+ if (num_result != 1) {
+ DEBUG(3,("smbldap_check_root_dse: Expected one rootDSE, got %d\n", num_result));
+ goto done;
+ }
+
+ entry = ldap_first_entry(ld, msg);
+
+ if (entry == NULL) {
+ DEBUG(3,("smbldap_check_root_dse: Could not retrieve rootDSE\n"));
+ goto done;
+ }
+
+ values = ldap_get_values(ld, entry, attrs[0]);
+
+ if (values == NULL) {
+ DEBUG(5,("smbldap_check_root_dse: LDAP Server does not support any %s\n", attrs[0]));
+ goto done;
+ }
+
+ num_values = ldap_count_values(values);
+
+ if (num_values == 0) {
+ DEBUG(5,("smbldap_check_root_dse: LDAP Server does not have any %s\n", attrs[0]));
+ goto done;
+ }
+
+ for (i=0; i<num_values; i++) {
+ if (strcmp(values[i], value) == 0)
+ result = True;
+ }
+
+
+ done:
+ if (values != NULL)
+ ldap_value_free(values);
+ if (msg != NULL)
+ ldap_msgfree(msg);
+
+ return result;
+
+}
+
+/*******************************************************************
+ Check if LDAP-Server supports a certain Control (OID in string format)
+********************************************************************/
+
+bool smbldap_has_control(LDAP *ld, const char *control)
+{
+ const char *attrs[] = { "supportedControl", NULL };
+ return smbldap_check_root_dse(ld, attrs, control);
+}
+
+/*******************************************************************
+ Check if LDAP-Server supports a certain Extension (OID in string format)
+********************************************************************/
+
+bool smbldap_has_extension(LDAP *ld, const char *extension)
+{
+ const char *attrs[] = { "supportedExtension", NULL };
+ return smbldap_check_root_dse(ld, attrs, extension);
+}
+
+/*******************************************************************
+ Check if LDAP-Server holds a given namingContext
+********************************************************************/
+
+bool smbldap_has_naming_context(LDAP *ld, const char *naming_context)
+{
+ const char *attrs[] = { "namingContexts", NULL };
+ return smbldap_check_root_dse(ld, attrs, naming_context);
+}
+
+bool smbldap_set_creds(struct smbldap_state *ldap_state, bool anon, const char *dn, const char *secret)
+{
+ ldap_state->anonymous = anon;
+
+ /* free any previously set credential */
+
+ SAFE_FREE(ldap_state->bind_dn);
+ smbldap_set_bind_callback(ldap_state, NULL, NULL);
+
+ if (ldap_state->bind_secret) {
+ /* make sure secrets are zeroed out of memory */
+ memset(ldap_state->bind_secret, '\0', strlen(ldap_state->bind_secret));
+ SAFE_FREE(ldap_state->bind_secret);
+ }
+
+ if ( ! anon) {
+ ldap_state->bind_dn = SMB_STRDUP(dn);
+ ldap_state->bind_secret = SMB_STRDUP(secret);
+ }
+
+ return True;
+}
diff --git a/source3/lib/smbrun.c b/source3/lib/smbrun.c
new file mode 100644
index 0000000..8e3675f
--- /dev/null
+++ b/source3/lib/smbrun.c
@@ -0,0 +1,354 @@
+/*
+ Unix SMB/CIFS implementation.
+ run a command as a specified user
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+
+/* need to move this from here!! need some sleep ... */
+struct current_user current_user;
+
+/****************************************************************************
+This is a utility function of smbrun().
+****************************************************************************/
+
+static int setup_out_fd(void)
+{
+ int fd;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ char *path = NULL;
+ mode_t mask;
+
+ path = talloc_asprintf(ctx,
+ "%s/smb.XXXXXX",
+ tmpdir());
+ if (!path) {
+ TALLOC_FREE(ctx);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* now create the file */
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(path);
+ umask(mask);
+
+ if (fd == -1) {
+ DEBUG(0,("setup_out_fd: Failed to create file %s. (%s)\n",
+ path, strerror(errno) ));
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ DEBUG(10,("setup_out_fd: Created tmp file %s\n", path ));
+
+ /* Ensure file only kept around by open fd. */
+ unlink(path);
+ TALLOC_FREE(ctx);
+ return fd;
+}
+
+/****************************************************************************
+run a command being careful about uid/gid handling and putting the output in
+outfd (or discard it if outfd is NULL).
+****************************************************************************/
+
+static int smbrun_internal(const char *cmd, int *outfd, bool sanitize,
+ char * const *env)
+{
+ pid_t pid;
+ uid_t uid = current_user.ut.uid;
+ gid_t gid = current_user.ut.gid;
+ void (*saved_handler)(int);
+
+ /*
+ * Lose any elevated privileges.
+ */
+ drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
+ drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
+
+ /* point our stdout at the file we want output to go into */
+
+ if (outfd && ((*outfd = setup_out_fd()) == -1)) {
+ return -1;
+ }
+
+ /* in this method we will exec /bin/sh with the correct
+ arguments, after first setting stdout to point at the file */
+
+ /*
+ * We need to temporarily stop CatchChild from eating
+ * SIGCLD signals as it also eats the exit status code. JRA.
+ */
+
+ saved_handler = CatchChildLeaveStatus();
+
+ if ((pid=fork()) < 0) {
+ DEBUG(0,("smbrun: fork failed with error %s\n", strerror(errno) ));
+ (void)CatchSignal(SIGCLD, saved_handler);
+ if (outfd) {
+ close(*outfd);
+ *outfd = -1;
+ }
+ return errno;
+ }
+
+ if (pid) {
+ /*
+ * Parent.
+ */
+ int status=0;
+ pid_t wpid;
+
+
+ /* the parent just waits for the child to exit */
+ while((wpid = waitpid(pid,&status,0)) < 0) {
+ if(errno == EINTR) {
+ errno = 0;
+ continue;
+ }
+ break;
+ }
+
+ (void)CatchSignal(SIGCLD, saved_handler);
+
+ if (wpid != pid) {
+ DEBUG(2,("waitpid(%d) : %s\n",(int)pid,strerror(errno)));
+ if (outfd) {
+ close(*outfd);
+ *outfd = -1;
+ }
+ return -1;
+ }
+
+ /* Reset the seek pointer. */
+ if (outfd) {
+ lseek(*outfd, 0, SEEK_SET);
+ }
+
+#if defined(WIFEXITED) && defined(WEXITSTATUS)
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ }
+#endif
+
+ return status;
+ }
+
+ (void)CatchChild();
+
+ /* we are in the child. we exec /bin/sh to do the work for us. we
+ don't directly exec the command we want because it may be a
+ pipeline or anything else the config file specifies */
+
+ /* point our stdout at the file we want output to go into */
+ if (outfd) {
+ close(1);
+ if (dup2(*outfd,1) != 1) {
+ DEBUG(2,("Failed to create stdout file descriptor\n"));
+ close(*outfd);
+ exit(80);
+ }
+ }
+
+ /* now completely lose our privileges. This is a fairly paranoid
+ way of doing it, but it does work on all systems that I know of */
+
+ become_user_permanently(uid, gid);
+
+ if (!non_root_mode()) {
+ if (getuid() != uid || geteuid() != uid ||
+ getgid() != gid || getegid() != gid) {
+ /* we failed to lose our privileges - do not execute
+ the command */
+ exit(81); /* we can't print stuff at this stage,
+ instead use exit codes for debugging */
+ }
+ }
+
+ /* close all other file descriptors, leaving only 0, 1 and 2. 0 and
+ 2 point to /dev/null from the startup code */
+ closefrom(3);
+
+ {
+ char *newcmd = NULL;
+ if (sanitize) {
+ newcmd = escape_shell_string(cmd);
+ if (!newcmd)
+ exit(82);
+ }
+
+ if (env != NULL) {
+ execle("/bin/sh","sh","-c",
+ newcmd ? (const char *)newcmd : cmd, NULL,
+ env);
+ } else {
+ execl("/bin/sh","sh","-c",
+ newcmd ? (const char *)newcmd : cmd, NULL);
+ }
+
+ SAFE_FREE(newcmd);
+ }
+
+ /* not reached */
+ exit(83);
+ return 1;
+}
+
+/****************************************************************************
+ Use only in known safe shell calls (printing).
+****************************************************************************/
+
+int smbrun_no_sanitize(const char *cmd, int *outfd, char * const *env)
+{
+ return smbrun_internal(cmd, outfd, false, env);
+}
+
+/****************************************************************************
+ By default this now sanitizes shell expansion.
+****************************************************************************/
+
+int smbrun(const char *cmd, int *outfd, char * const *env)
+{
+ return smbrun_internal(cmd, outfd, true, env);
+}
+
+/****************************************************************************
+run a command being careful about uid/gid handling and putting the output in
+outfd (or discard it if outfd is NULL).
+sends the provided secret to the child stdin.
+****************************************************************************/
+
+int smbrunsecret(const char *cmd, const char *secret)
+{
+ pid_t pid;
+ uid_t uid = current_user.ut.uid;
+ gid_t gid = current_user.ut.gid;
+ int ifd[2];
+ void (*saved_handler)(int);
+
+ /*
+ * Lose any elevated privileges.
+ */
+ drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
+ drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
+
+ /* build up an input pipe */
+ if(pipe(ifd)) {
+ return -1;
+ }
+
+ /* in this method we will exec /bin/sh with the correct
+ arguments, after first setting stdout to point at the file */
+
+ /*
+ * We need to temporarily stop CatchChild from eating
+ * SIGCLD signals as it also eats the exit status code. JRA.
+ */
+
+ saved_handler = CatchChildLeaveStatus();
+
+ if ((pid=fork()) < 0) {
+ DEBUG(0, ("smbrunsecret: fork failed with error %s\n", strerror(errno)));
+ (void)CatchSignal(SIGCLD, saved_handler);
+ return errno;
+ }
+
+ if (pid) {
+ /*
+ * Parent.
+ */
+ int status = 0;
+ pid_t wpid;
+ size_t towrite;
+ ssize_t wrote;
+
+ close(ifd[0]);
+ /* send the secret */
+ towrite = strlen(secret);
+ wrote = write(ifd[1], secret, towrite);
+ if ( wrote != towrite ) {
+ DEBUG(0,("smbrunsecret: wrote %ld of %lu bytes\n",(long)wrote,(unsigned long)towrite));
+ }
+ fsync(ifd[1]);
+ close(ifd[1]);
+
+ /* the parent just waits for the child to exit */
+ while((wpid = waitpid(pid, &status, 0)) < 0) {
+ if(errno == EINTR) {
+ errno = 0;
+ continue;
+ }
+ break;
+ }
+
+ (void)CatchSignal(SIGCLD, saved_handler);
+
+ if (wpid != pid) {
+ DEBUG(2, ("waitpid(%d) : %s\n", (int)pid, strerror(errno)));
+ return -1;
+ }
+
+#if defined(WIFEXITED) && defined(WEXITSTATUS)
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ }
+#endif
+
+ return status;
+ }
+
+ (void)CatchChild();
+
+ /* we are in the child. we exec /bin/sh to do the work for us. we
+ don't directly exec the command we want because it may be a
+ pipeline or anything else the config file specifies */
+
+ close(ifd[1]);
+ close(0);
+ if (dup2(ifd[0], 0) != 0) {
+ DEBUG(2,("Failed to create stdin file descriptor\n"));
+ close(ifd[0]);
+ exit(80);
+ }
+
+ /* now completely lose our privileges. This is a fairly paranoid
+ way of doing it, but it does work on all systems that I know of */
+
+ become_user_permanently(uid, gid);
+
+ if (!non_root_mode()) {
+ if (getuid() != uid || geteuid() != uid ||
+ getgid() != gid || getegid() != gid) {
+ /* we failed to lose our privileges - do not execute
+ the command */
+ exit(81); /* we can't print stuff at this stage,
+ instead use exit codes for debugging */
+ }
+ }
+
+ /* close all other file descriptors, leaving only 0, 1 and 2. 0 and
+ 2 point to /dev/null from the startup code */
+ closefrom(3);
+
+ execl("/bin/sh", "sh", "-c", cmd, NULL);
+
+ /* not reached */
+ exit(82);
+ return 1;
+}
diff --git a/source3/lib/srprs.c b/source3/lib/srprs.c
new file mode 100644
index 0000000..67ada37
--- /dev/null
+++ b/source3/lib/srprs.c
@@ -0,0 +1,228 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file srprs.c
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Aug 2010
+ * @brief A simple recursive parser.
+ */
+
+#include "replace.h"
+#include "system/locale.h"
+#include "srprs.h"
+#include "cbuf.h"
+#include <assert.h>
+
+bool srprs_skipws(const char** ptr) {
+ while (isspace(**ptr))
+ ++(*ptr);
+ return true;
+}
+
+bool srprs_char(const char** ptr, char c) {
+ if (**ptr == c) {
+ ++(*ptr);
+ return true;
+ }
+ return false;
+}
+
+bool srprs_str(const char** ptr, const char* str, ssize_t len)
+{
+ /* By definition *ptr must be null terminated. */
+ size_t ptr_len = strlen(*ptr);
+
+ if (len == -1)
+ len = strlen(str);
+
+ /* Don't memcmp read past end of buffer. */
+ if (len > ptr_len) {
+ return false;
+ }
+
+ if (memcmp(*ptr, str, len) == 0) {
+ *ptr += len;
+ return true;
+ }
+ return false;
+}
+
+bool srprs_charset(const char** ptr, const char* set, cbuf* oss)
+{
+ const char* p = strchr(set, **ptr);
+ if (p != NULL && *p != '\0') {
+ cbuf_putc(oss, **ptr);
+ ++(*ptr);
+ return true;
+ }
+ return false;
+}
+
+bool srprs_charsetinv(const char** ptr, const char* set, cbuf* oss)
+{
+ if ((**ptr != '\0') && (strchr(set, **ptr) == NULL)) {
+ cbuf_putc(oss, **ptr);
+ ++(*ptr);
+ return true;
+ }
+ return false;
+}
+
+
+
+bool srprs_quoted_string(const char** ptr, cbuf* str, bool* cont)
+{
+ const char* pos = *ptr;
+ const size_t spos = cbuf_getpos(str);
+
+ if (cont == NULL || *cont == false) {
+ if (!srprs_char(&pos, '\"'))
+ goto fail;
+ }
+
+ while (true) {
+ while (srprs_charsetinv(&pos, "\\\"", str))
+ ;
+
+ switch (*pos) {
+ case '\0':
+ if (cont == NULL) {
+ goto fail;
+ } else {
+ *ptr = pos;
+ *cont = true;
+ return true;
+ }
+ case '\"':
+ *ptr = pos+1;
+ if (cont != NULL) {
+ *cont = false;
+ }
+ return true;
+
+ case '\\':
+ pos++;
+ if (!srprs_charset(&pos, "\\\"", str))
+ goto fail;
+ break;
+
+ default:
+ assert(false);
+ }
+ }
+
+fail:
+ cbuf_setpos(str, spos);
+ return false;
+}
+
+bool srprs_hex(const char** ptr, size_t len, unsigned* u)
+{
+ const char *str = *ptr;
+ const char *pos = *ptr;
+ int ret;
+ size_t i;
+ char buf[8+1] = {};
+
+ assert((len >= 1) && (len <= 8));
+
+ for (i=0; i<len; i++) {
+ if (!srprs_charset(&pos, "0123456789abcdefABCDEF", NULL)) {
+ break;
+ }
+ buf[i] = str[i];
+ }
+
+ ret = sscanf(buf, "%8x", u);
+
+ if ( ret != 1 ) {
+ return false;
+ }
+
+ *ptr = pos;
+ return true;
+}
+
+bool srprs_nl(const char** ptr, cbuf* nl)
+{
+ static const char CRLF[] = "\r\n";
+ if (srprs_str(ptr, CRLF, sizeof(CRLF) - 1)) {
+ cbuf_puts(nl, CRLF, sizeof(CRLF) - 1);
+ return true;
+ }
+ return srprs_charset(ptr, "\n\r", nl);
+}
+
+bool srprs_eos(const char** ptr)
+{
+ return (**ptr == '\0');
+}
+
+bool srprs_eol(const char** ptr, cbuf* nl)
+{
+ return srprs_eos(ptr) || srprs_nl(ptr, nl);
+}
+
+bool srprs_line(const char** ptr, cbuf* str)
+{
+ while (srprs_charsetinv(ptr, "\n\r", str))
+ ;
+ return true;
+}
+
+bool srprs_quoted(const char** ptr, cbuf* str)
+{
+ const char* pos = *ptr;
+ const size_t spos = cbuf_getpos(str);
+
+ if (!srprs_char(&pos, '"')) {
+ goto fail;
+ }
+
+ while (true) {
+ while (srprs_charsetinv(&pos, "\\\"", str))
+ ;
+
+ switch (*pos) {
+ case '\0':
+ goto fail;
+ case '"':
+ *ptr = pos+1;
+ return true;
+
+ case '\\':
+ pos++;
+ if (!srprs_charset(&pos, "\\\"", str)) {
+ unsigned u;
+ if (!srprs_hex(&pos, 2, &u)) {
+ goto fail;
+ }
+ cbuf_putc(str, u);
+ }
+ break;
+ default:
+ assert(false);
+ }
+ }
+
+fail:
+ cbuf_setpos(str, spos);
+ return false;
+}
diff --git a/source3/lib/srprs.h b/source3/lib/srprs.h
new file mode 100644
index 0000000..fb3ca1b
--- /dev/null
+++ b/source3/lib/srprs.h
@@ -0,0 +1,189 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file srprs.h
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Aug 2010
+ *
+ * @brief A simple recursive parser.
+ *
+ * This file contains functions which may be used to build a simple recursive
+ * parser. They all take the parse position as their first argument. If they
+ * match the parse position and the output arguments are updated accordingly and
+ * true is returned else no argument is altered. For arguments of type @ref cbuf
+ * this may hold only up to the current write position.
+ */
+
+#ifndef __SRPRS_H
+#define __SRPRS_H
+
+struct cbuf;
+
+/**
+ * Matches any amount of whitespace.
+ *
+ * @see isspace
+ * @param ptr parse position
+ *
+ * @return true
+ */
+bool srprs_skipws(const char** ptr);
+
+/**
+ * Match a single character.
+ *
+ * @param[in,out] ptr parse position
+ * @param c the character to match
+ *
+ * @return true if matched
+ */
+bool srprs_char(const char** ptr, char c);
+
+/**
+ * Match a string.
+ *
+ * @param[in,out] ptr parse position
+ * @param str string to match
+ * @param len number of bytes to compare, -1 means strlen(str)
+ *
+ * @return true if matched
+ */
+bool srprs_str(const char** ptr, const char* str, ssize_t len);
+
+/**
+ * Match a single character from a set.
+ * Didn't match '\\0'
+ *
+ * @param[in,out] ptr parse position
+ * @param[in] set the character set to look for
+ * @param[out] oss output buffer where to put the match, may be NULL
+ *
+ * @return true if matched
+ */
+bool srprs_charset(const char** ptr, const char* set, struct cbuf* oss);
+
+/**
+ * Match a single character not in set.
+ * Didn't match '\\0'
+ *
+ * @param[in,out] ptr parse position
+ * @param[in] set the character set to look for
+ * @param[out] oss output buffer where to put the match, may be NULL
+ *
+ * @return true if matched
+ */
+bool srprs_charsetinv(const char** ptr, const char* set, struct cbuf* oss);
+
+/**
+ * Match a quoted string.
+ *
+ *
+ * If cont is not NULL the match may span multiple invocations.
+ * @code
+ * const char* start = "\"start...";
+ * const char* cont = "continued...";
+ * const char* end = "end\"";
+ * bool cont = false;
+ * cbuf* out = cbuf_new(talloc_tos());
+ * srprs_quoted_string(&start, out, &cont);
+ * assert(*cont == true);
+ * srprs_quoted_string(&cont, out, &cont);
+ * assert(*cont == true);
+ * srprs_quoted_string(&end, out, &cont);
+ * assert(*cont == false);
+ * assert(strcmp(cbuf_gets(out, 0), "start...continued...end")==0);
+ * @endcode
+ * @see cbuf_print_quoted_string
+ *
+ * @param[in,out] ptr parse position
+ * @param[out] str output buffer where to put the match
+ * @param[in,out] cont
+ *
+ * @return true if matched
+ */
+bool srprs_quoted_string(const char** ptr, struct cbuf* str, bool* cont);
+
+/**
+ * Match a hex string.
+ *
+ * @param[in,out] ptr parse position
+ * @param len maximum number of digits to match
+ * @param[out] u value of the match
+ *
+ * @return true if matched
+ */
+bool srprs_hex(const char** ptr, size_t len, unsigned* u);
+
+/**
+ * Match the empty string at End Of String.
+ * It doesn't consume the '\\0' unlike
+ * @code
+ * srprs_char(ptr, '\0', NULL);
+ * @endcode
+ *
+ * @param[in,out] ptr parse position
+ *
+ * @return true if **ptr == '\\0'
+ */
+bool srprs_eos(const char** ptr);
+
+/**
+ * Match a newline.
+ * A newline is either '\\n' (LF), '\\r' (CR), or "\r\n" (CRLF)
+ *
+ * @param[in,out] ptr parse position
+ * @param[out] nl output buffer where to put the match, may be NULL
+ *
+ * @return true if matched
+ */
+bool srprs_nl(const char** ptr, struct cbuf* nl);
+
+/**
+ * Match a newline or eos.
+ *
+ * @param ptr parse position
+ * @param nl output buffer where to put the match, may be NULL
+ *
+ * @return true if matched
+ */
+bool srprs_eol(const char** ptr, struct cbuf* nl);
+
+/**
+ * Match a line up to but not including the newline.
+ *
+ * @param[in,out] ptr parse position
+ * @param[out] str output buffer where to put the match, may be NULL
+ *
+ * @return true
+ */
+bool srprs_line(const char** ptr, struct cbuf* str);
+
+/**
+ * Match a quoted string with escaped characters.
+ * @see cbuf_print_quoted
+ *
+ * @param[in,out] ptr parse position
+ * @param[out] str output buffer where to put the match
+ *
+ * @return true if matched
+ */
+bool srprs_quoted(const char** ptr, struct cbuf* str);
+
+#endif /* __SRPRS_H */
diff --git a/source3/lib/string_replace.c b/source3/lib/string_replace.c
new file mode 100644
index 0000000..f66f548
--- /dev/null
+++ b/source3/lib/string_replace.c
@@ -0,0 +1,192 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke, 2005
+ * Copyright (C) Aravind Srinivasan, 2009
+ * Copyright (C) Guenter Kukkukk, 2013
+ * Copyright (C) Ralph Boehme, 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "string_replace.h"
+#include "lib/util/string_wrappers.h"
+
+#define MAP_SIZE 0xFF
+#define MAP_NUM 0x101 /* max unicode charval / MAP_SIZE */
+#define T_OFFSET(_v_) ((_v_ % MAP_SIZE))
+#define T_START(_v_) (((_v_ / MAP_SIZE) * MAP_SIZE))
+#define T_PICK(_v_) ((_v_ / MAP_SIZE))
+
+struct char_mappings {
+ smb_ucs2_t entry[MAP_SIZE][2];
+};
+
+static bool build_table(struct char_mappings **cmaps, int value)
+{
+ int i;
+ int start = T_START(value);
+
+ (*cmaps) = talloc_zero(NULL, struct char_mappings);
+
+ if (!*cmaps)
+ return False;
+
+ for (i = 0; i < MAP_SIZE;i++) {
+ (*cmaps)->entry[i][vfs_translate_to_unix] = start + i;
+ (*cmaps)->entry[i][vfs_translate_to_windows] = start + i;
+ }
+
+ return True;
+}
+
+static void set_tables(struct char_mappings **cmaps,
+ long unix_map,
+ long windows_map)
+{
+ int i;
+
+ /* set unix -> windows */
+ i = T_OFFSET(unix_map);
+ cmaps[T_PICK(unix_map)]->entry[i][vfs_translate_to_windows] = windows_map;
+
+ /* set windows -> unix */
+ i = T_OFFSET(windows_map);
+ cmaps[T_PICK(windows_map)]->entry[i][vfs_translate_to_unix] = unix_map;
+}
+
+static bool build_ranges(struct char_mappings **cmaps,
+ long unix_map,
+ long windows_map)
+{
+
+ if (!cmaps[T_PICK(unix_map)]) {
+ if (!build_table(&cmaps[T_PICK(unix_map)], unix_map))
+ return False;
+ }
+
+ if (!cmaps[T_PICK(windows_map)]) {
+ if (!build_table(&cmaps[T_PICK(windows_map)], windows_map))
+ return False;
+ }
+
+ set_tables(cmaps, unix_map, windows_map);
+
+ return True;
+}
+
+struct char_mappings **string_replace_init_map(TALLOC_CTX *mem_ctx,
+ const char **mappings)
+{
+ int i;
+ char *tmp;
+ fstring mapping;
+ long unix_map, windows_map;
+ struct char_mappings **cmaps = NULL;
+
+ if (mappings == NULL) {
+ return NULL;
+ }
+
+ cmaps = talloc_zero_array(mem_ctx, struct char_mappings *, MAP_NUM);
+ if (cmaps == NULL) {
+ return NULL;
+ }
+
+ /*
+ * catia mappings are of the form :
+ * UNIX char (in 0xnn hex) : WINDOWS char (in 0xnn hex)
+ *
+ * multiple mappings are comma separated in smb.conf
+ */
+
+ for (i = 0; mappings[i]; i++) {
+ fstrcpy(mapping, mappings[i]);
+ unix_map = strtol(mapping, &tmp, 16);
+ if (unix_map == 0 && errno == EINVAL) {
+ DEBUG(0, ("INVALID CATIA MAPPINGS - %s\n", mapping));
+ continue;
+ }
+ windows_map = strtol(++tmp, NULL, 16);
+ if (windows_map == 0 && errno == EINVAL) {
+ DEBUG(0, ("INVALID CATIA MAPPINGS - %s\n", mapping));
+ continue;
+ }
+
+ if (!build_ranges(cmaps, unix_map, windows_map)) {
+ DEBUG(0, ("TABLE ERROR - CATIA MAPPINGS - %s\n", mapping));
+ continue;
+ }
+ }
+
+ return cmaps;
+}
+
+NTSTATUS string_replace_allocate(connection_struct *conn,
+ const char *name_in,
+ struct char_mappings **cmaps,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name,
+ enum vfs_translate_direction direction)
+{
+ static smb_ucs2_t *tmpbuf = NULL;
+ smb_ucs2_t *ptr = NULL;
+ struct char_mappings *map = NULL;
+ size_t converted_size;
+ bool ok;
+
+ ok = push_ucs2_talloc(talloc_tos(), &tmpbuf, name_in,
+ &converted_size);
+ if (!ok) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ for (ptr = tmpbuf; *ptr; ptr++) {
+ if (*ptr == 0) {
+ break;
+ }
+ if (cmaps == NULL) {
+ continue;
+ }
+ map = cmaps[T_PICK((*ptr))];
+ if (map == NULL) {
+ /* nothing to do */
+ continue;
+ }
+
+ *ptr = map->entry[T_OFFSET((*ptr))][direction];
+ }
+
+ ok = pull_ucs2_talloc(mem_ctx, mapped_name, tmpbuf,
+ &converted_size);
+ TALLOC_FREE(tmpbuf);
+ if (!ok) {
+ return map_nt_error_from_unix(errno);
+ }
+ return NT_STATUS_OK;
+}
+
+const char *macos_string_replace_map =
+ "0x01:0xf001,0x02:0xf002,0x03:0xf003,0x04:0xf004,"
+ "0x05:0xf005,0x06:0xf006,0x07:0xf007,0x08:0xf008,"
+ "0x09:0xf009,0x0a:0xf00a,0x0b:0xf00b,0x0c:0xf00c,"
+ "0x0d:0xf00d,0x0e:0xf00e,0x0f:0xf00f,0x10:0xf010,"
+ "0x11:0xf011,0x12:0xf012,0x13:0xf013,0x14:0xf014,"
+ "0x15:0xf015,0x16:0xf016,0x17:0xf017,0x18:0xf018,"
+ "0x19:0xf019,0x1a:0xf01a,0x1b:0xf01b,0x1c:0xf01c,"
+ "0x1d:0xf01d,0x1e:0xf01e,0x1f:0xf01f,"
+ "0x22:0xf020,0x2a:0xf021,0x3a:0xf022,0x3c:0xf023,"
+ "0x3e:0xf024,0x3f:0xf025,0x5c:0xf026,0x7c:0xf027";
diff --git a/source3/lib/string_replace.h b/source3/lib/string_replace.h
new file mode 100644
index 0000000..012b1aa
--- /dev/null
+++ b/source3/lib/string_replace.h
@@ -0,0 +1,35 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke, 2005
+ * Copyright (C) Aravind Srinivasan, 2009
+ * Copyright (C) Guenter Kukkukk, 2013
+ * Copyright (C) Ralph Boehme, 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+struct char_mappings;
+
+struct char_mappings **string_replace_init_map(TALLOC_CTX *mem_ctx,
+ const char **mappings);
+
+NTSTATUS string_replace_allocate(connection_struct *conn,
+ const char *name_in,
+ struct char_mappings **cmaps,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name,
+ enum vfs_translate_direction direction);
+
+extern const char *macos_string_replace_map;
diff --git a/source3/lib/substitute.c b/source3/lib/substitute.c
new file mode 100644
index 0000000..40eb15a
--- /dev/null
+++ b/source3/lib/substitute.c
@@ -0,0 +1,675 @@
+/*
+ Unix SMB/CIFS implementation.
+ string substitution functions
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "substitute.h"
+#include "system/passwd.h"
+#include "secrets.h"
+#include "auth.h"
+#include "lib/util/string_wrappers.h"
+
+/* Max DNS name is 253 + '\0' */
+#define MACHINE_NAME_SIZE 254
+
+static char local_machine[MACHINE_NAME_SIZE];
+static char remote_machine[MACHINE_NAME_SIZE];
+
+userdom_struct current_user_info;
+static fstring remote_proto="UNKNOWN";
+
+void set_remote_proto(const char *proto)
+{
+ fstrcpy(remote_proto, proto);
+}
+
+/**
+ * Set the 'local' machine name
+ * @param local_name the name we are being called
+ * @param if this is the 'final' name for us, not be be changed again
+ */
+bool set_local_machine_name(const char *local_name, bool perm)
+{
+ static bool already_perm = false;
+ char tmp[MACHINE_NAME_SIZE];
+
+ if (already_perm) {
+ return true;
+ }
+
+ strlcpy(tmp, local_name, sizeof(tmp));
+ trim_char(tmp, ' ', ' ');
+
+ alpha_strcpy(local_machine,
+ tmp,
+ SAFE_NETBIOS_CHARS,
+ sizeof(local_machine) - 1);
+ if (!strlower_m(local_machine)) {
+ return false;
+ }
+
+ already_perm = perm;
+
+ return true;
+}
+
+const char *get_local_machine_name(void)
+{
+ if (local_machine[0] == '\0') {
+ return lp_netbios_name();
+ }
+
+ return local_machine;
+}
+
+/**
+ * Set the 'remote' machine name
+ *
+ * @param remote_name the name our client wants to be called by
+ * @param if this is the 'final' name for them, not be be changed again
+ */
+bool set_remote_machine_name(const char *remote_name, bool perm)
+{
+ static bool already_perm = False;
+ char tmp[MACHINE_NAME_SIZE];
+
+ if (already_perm) {
+ return true;
+ }
+
+ strlcpy(tmp, remote_name, sizeof(tmp));
+ trim_char(tmp, ' ', ' ');
+
+ alpha_strcpy(remote_machine,
+ tmp,
+ SAFE_NETBIOS_CHARS,
+ sizeof(remote_machine) - 1);
+ if (!strlower_m(remote_machine)) {
+ return false;
+ }
+
+ already_perm = perm;
+
+ return true;
+}
+
+const char *get_remote_machine_name(void)
+{
+ return remote_machine;
+}
+
+static char sub_peeraddr[INET6_ADDRSTRLEN];
+static const char *sub_peername = NULL;
+static char sub_sockaddr[INET6_ADDRSTRLEN];
+
+void sub_set_socket_ids(const char *peeraddr, const char *peername,
+ const char *sockaddr)
+{
+ const char *addr = peeraddr;
+
+ if (strnequal(addr, "::ffff:", 7)) {
+ addr += 7;
+ }
+ strlcpy(sub_peeraddr, addr, sizeof(sub_peeraddr));
+
+ if (sub_peername != NULL &&
+ sub_peername != sub_peeraddr) {
+ talloc_free(discard_const_p(char,sub_peername));
+ sub_peername = NULL;
+ }
+ sub_peername = talloc_strdup(NULL, peername);
+ if (sub_peername == NULL) {
+ sub_peername = sub_peeraddr;
+ }
+
+ /*
+ * Shouldn't we do the ::ffff: cancellation here as well? The
+ * original code in talloc_sub_basic() did not do it, so I'm
+ * leaving it out here as well for compatibility.
+ */
+ strlcpy(sub_sockaddr, sockaddr, sizeof(sub_sockaddr));
+}
+
+/*******************************************************************
+ Setup the strings used by substitutions. Called per packet. Ensure
+ %U name is set correctly also.
+
+ smb_name must be sanitized by alpha_strcpy
+********************************************************************/
+
+void set_current_user_info(const char *smb_name, const char *unix_name,
+ const char *domain)
+{
+ static const void *last_smb_name;
+ static const void *last_unix_name;
+ static const void *last_domain;
+
+ if (likely(last_smb_name == smb_name &&
+ last_unix_name == unix_name &&
+ last_domain == domain))
+ {
+ return;
+ }
+
+ fstrcpy(current_user_info.smb_name, smb_name);
+ fstrcpy(current_user_info.unix_name, unix_name);
+ fstrcpy(current_user_info.domain, domain);
+
+ last_smb_name = smb_name;
+ last_unix_name = unix_name;
+ last_domain = domain;
+}
+
+/*******************************************************************
+ Return the current active user name.
+*******************************************************************/
+
+const char *get_current_username(void)
+{
+ return current_user_info.smb_name;
+}
+
+const char *get_current_user_info_domain(void)
+{
+ return current_user_info.domain;
+}
+
+/*******************************************************************
+ Given a pointer to a %$(NAME) in p and the whole string in str
+ expand it as an environment variable.
+ str must be a talloced string.
+ Return a new allocated and expanded string.
+ Based on code by Branko Cibej <branko.cibej@hermes.si>
+ When this is called p points at the '%' character.
+ May substitute multiple occurrences of the same env var.
+********************************************************************/
+
+static char *realloc_expand_env_var(char *str, char *p)
+{
+ char *envname;
+ char *envval;
+ char *q, *r;
+ int copylen;
+
+ if (p[0] != '%' || p[1] != '$' || p[2] != '(') {
+ return str;
+ }
+
+ /*
+ * Look for the terminating ')'.
+ */
+
+ if ((q = strchr_m(p,')')) == NULL) {
+ DEBUG(0,("expand_env_var: Unterminated environment variable [%s]\n", p));
+ return str;
+ }
+
+ /*
+ * Extract the name from within the %$(NAME) string.
+ */
+
+ r = p + 3;
+ copylen = q - r;
+
+ /* reserve space for use later add %$() chars */
+ if ( (envname = talloc_array(talloc_tos(), char, copylen + 1 + 4)) == NULL ) {
+ return NULL;
+ }
+
+ strncpy(envname,r,copylen);
+ envname[copylen] = '\0';
+
+ if ((envval = getenv(envname)) == NULL) {
+ DEBUG(0,("expand_env_var: Environment variable [%s] not set\n", envname));
+ TALLOC_FREE(envname);
+ return str;
+ }
+
+ /*
+ * Copy the full %$(NAME) into envname so it
+ * can be replaced.
+ */
+
+ copylen = q + 1 - p;
+ strncpy(envname,p,copylen);
+ envname[copylen] = '\0';
+ r = realloc_string_sub(str, envname, envval);
+ TALLOC_FREE(envname);
+
+ return r;
+}
+
+/****************************************************************************
+ Do some standard substitutions in a string.
+ len is the length in bytes of the space allowed in string str. If zero means
+ don't allow expansions.
+****************************************************************************/
+
+void standard_sub_basic(const char *smb_name, const char *domain_name,
+ char *str, size_t len)
+{
+ char *s;
+
+ if ( (s = talloc_sub_basic(talloc_tos(), smb_name, domain_name, str )) != NULL ) {
+ strncpy( str, s, len );
+ }
+
+ TALLOC_FREE( s );
+}
+
+/*
+ * Limit addresses to hexalpha characters and underscore, safe for path
+ * components for Windows clients.
+ */
+static void make_address_pathsafe(char *addr)
+{
+ while(addr && *addr) {
+ if(!isxdigit(*addr)) {
+ *addr = '_';
+ }
+ ++addr;
+ }
+}
+
+/****************************************************************************
+ Do some standard substitutions in a string.
+ This function will return a talloced string that has to be freed.
+****************************************************************************/
+
+char *talloc_sub_basic(TALLOC_CTX *mem_ctx,
+ const char *smb_name,
+ const char *domain_name,
+ const char *str)
+{
+ char *b, *p, *s, *r, *a_string;
+ fstring pidstr, vnnstr;
+ const char *local_machine_name = get_local_machine_name();
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ /* workaround to prevent a crash while looking at bug #687 */
+
+ if (!str) {
+ DEBUG(0,("talloc_sub_basic: NULL source string! This should not happen\n"));
+ return NULL;
+ }
+
+ a_string = talloc_strdup(mem_ctx, str);
+ if (a_string == NULL) {
+ DEBUG(0, ("talloc_sub_basic: Out of memory!\n"));
+ return NULL;
+ }
+
+ tmp_ctx = talloc_stackframe();
+
+ for (s = a_string; (p = strchr_m(s, '%')); s = a_string + (p - b)) {
+
+ r = NULL;
+ b = a_string;
+
+ switch (*(p+1)) {
+ case 'U' :
+ r = strlower_talloc(tmp_ctx, smb_name);
+ if (r == NULL) {
+ goto error;
+ }
+ a_string = realloc_string_sub(a_string, "%U", r);
+ break;
+ case 'G' : {
+ struct passwd *pass;
+ bool is_domain_name = false;
+ const char *sep = lp_winbind_separator();
+
+ if (domain_name != NULL && domain_name[0] != '\0' &&
+ (lp_security() == SEC_ADS ||
+ lp_security() == SEC_DOMAIN)) {
+ r = talloc_asprintf(tmp_ctx,
+ "%s%c%s",
+ domain_name,
+ *sep,
+ smb_name);
+ is_domain_name = true;
+ } else {
+ r = talloc_strdup(tmp_ctx, smb_name);
+ }
+ if (r == NULL) {
+ goto error;
+ }
+
+ pass = Get_Pwnam_alloc(tmp_ctx, r);
+ if (pass != NULL) {
+ char *group_name;
+
+ group_name = gidtoname(pass->pw_gid);
+ if (is_domain_name) {
+ char *group_sep;
+ group_sep = strchr_m(group_name, *sep);
+ if (group_sep != NULL) {
+ group_name = group_sep + 1;
+ }
+ }
+ a_string = realloc_string_sub(a_string,
+ "%G",
+ group_name);
+ }
+ TALLOC_FREE(pass);
+ break;
+ }
+ case 'D' :
+ r = strupper_talloc(tmp_ctx, domain_name);
+ if (r == NULL) {
+ goto error;
+ }
+ a_string = realloc_string_sub(a_string, "%D", r);
+ break;
+ case 'I' : {
+ a_string = realloc_string_sub(
+ a_string, "%I",
+ sub_peeraddr[0] ? sub_peeraddr : "0.0.0.0");
+ break;
+ }
+ case 'J' : {
+ r = talloc_strdup(tmp_ctx,
+ sub_peeraddr[0] ? sub_peeraddr : "0.0.0.0");
+ make_address_pathsafe(r);
+ a_string = realloc_string_sub(a_string, "%J", r);
+ break;
+ }
+ case 'i':
+ a_string = realloc_string_sub(
+ a_string, "%i",
+ sub_sockaddr[0] ? sub_sockaddr : "0.0.0.0");
+ break;
+ case 'j' : {
+ r = talloc_strdup(tmp_ctx,
+ sub_sockaddr[0] ? sub_sockaddr : "0.0.0.0");
+ make_address_pathsafe(r);
+ a_string = realloc_string_sub(a_string, "%j", r);
+ break;
+ }
+ case 'L' :
+ if ( strncasecmp_m(p, "%LOGONSERVER%", strlen("%LOGONSERVER%")) == 0 ) {
+ break;
+ }
+ if (local_machine_name && *local_machine_name) {
+ a_string = realloc_string_sub(a_string, "%L", local_machine_name);
+ } else {
+ a_string = realloc_string_sub(a_string, "%L", lp_netbios_name());
+ }
+ break;
+ case 'N' :
+ a_string = realloc_string_sub(a_string,
+ "%N",
+ lp_netbios_name());
+ break;
+ case 'M' :
+ a_string = realloc_string_sub(a_string, "%M",
+ sub_peername ? sub_peername : "");
+ break;
+ case 'R' :
+ a_string = realloc_string_sub(a_string, "%R", remote_proto);
+ break;
+ case 'T' :
+ a_string = realloc_string_sub(a_string, "%T", current_timestring(tmp_ctx, False));
+ break;
+ case 't' :
+ a_string = realloc_string_sub(a_string, "%t",
+ current_minimal_timestring(tmp_ctx, False));
+ break;
+ case 'a' :
+ a_string = realloc_string_sub(a_string, "%a",
+ get_remote_arch_str());
+ break;
+ case 'd' :
+ slprintf(pidstr,sizeof(pidstr)-1, "%d",(int)getpid());
+ a_string = realloc_string_sub(a_string, "%d", pidstr);
+ break;
+ case 'h' :
+ a_string = realloc_string_sub(a_string, "%h", myhostname());
+ break;
+ case 'm' :
+ a_string = realloc_string_sub(a_string, "%m",
+ remote_machine);
+ break;
+ case 'v' :
+ a_string = realloc_string_sub(a_string, "%v", samba_version_string());
+ break;
+ case 'w' :
+ a_string = realloc_string_sub(a_string, "%w", lp_winbind_separator());
+ break;
+ case '$' :
+ a_string = realloc_expand_env_var(a_string, p); /* Expand environment variables */
+ break;
+ case 'V' :
+ slprintf(vnnstr,sizeof(vnnstr)-1, "%u", get_my_vnn());
+ a_string = realloc_string_sub(a_string, "%V", vnnstr);
+ break;
+ default:
+ break;
+ }
+
+ p++;
+ TALLOC_FREE(r);
+
+ if (a_string == NULL) {
+ goto done;
+ }
+ }
+
+ goto done;
+
+error:
+ TALLOC_FREE(a_string);
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return a_string;
+}
+
+/****************************************************************************
+ Do some specific substitutions in a string.
+ This function will return an allocated string that have to be freed.
+****************************************************************************/
+
+char *talloc_sub_specified(TALLOC_CTX *mem_ctx,
+ const char *input_string,
+ const char *username,
+ const char *grpname,
+ const char *domain,
+ uid_t uid,
+ gid_t gid)
+{
+ char *a_string;
+ char *ret_string = NULL;
+ char *b, *p, *s;
+ TALLOC_CTX *tmp_ctx;
+
+ if (!(tmp_ctx = talloc_new(mem_ctx))) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return NULL;
+ }
+
+ a_string = talloc_strdup(tmp_ctx, input_string);
+ if (a_string == NULL) {
+ DEBUG(0, ("talloc_sub_specified: Out of memory!\n"));
+ goto done;
+ }
+
+ for (s = a_string; (p = strchr_m(s, '%')); s = a_string + (p - b)) {
+
+ b = a_string;
+
+ switch (*(p+1)) {
+ case 'U' :
+ a_string = talloc_string_sub(
+ tmp_ctx, a_string, "%U", username);
+ break;
+ case 'u' :
+ a_string = talloc_string_sub(
+ tmp_ctx, a_string, "%u", username);
+ break;
+ case 'G' :
+ if (gid != -1) {
+ const char *name;
+
+ if (grpname != NULL) {
+ name = grpname;
+ } else {
+ name = gidtoname(gid);
+ }
+
+ a_string = talloc_string_sub(tmp_ctx,
+ a_string,
+ "%G",
+ name);
+ } else {
+ a_string = talloc_string_sub(
+ tmp_ctx, a_string,
+ "%G", "NO_GROUP");
+ }
+ break;
+ case 'g' :
+ if (gid != -1) {
+ const char *name;
+
+ if (grpname != NULL) {
+ name = grpname;
+ } else {
+ name = gidtoname(gid);
+ }
+
+ a_string = talloc_string_sub(tmp_ctx,
+ a_string,
+ "%g",
+ name);
+ } else {
+ a_string = talloc_string_sub(
+ tmp_ctx, a_string, "%g", "NO_GROUP");
+ }
+ break;
+ case 'D' :
+ a_string = talloc_string_sub(tmp_ctx, a_string,
+ "%D", domain);
+ break;
+ case 'N' :
+ a_string = talloc_string_sub(tmp_ctx, a_string,
+ "%N", lp_netbios_name());
+ break;
+ default:
+ break;
+ }
+
+ p++;
+ if (a_string == NULL) {
+ goto done;
+ }
+ }
+
+ /* Watch out, using "mem_ctx" here, so all intermediate stuff goes
+ * away with the TALLOC_FREE(tmp_ctx) further down. */
+
+ ret_string = talloc_sub_basic(mem_ctx, username, domain, a_string);
+
+ done:
+ TALLOC_FREE(tmp_ctx);
+ return ret_string;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+char *talloc_sub_advanced(TALLOC_CTX *ctx,
+ const char *servicename,
+ const char *user,
+ const char *connectpath,
+ gid_t gid,
+ const char *str)
+{
+ char *a_string;
+ char *b, *p, *s;
+
+ a_string = talloc_strdup(talloc_tos(), str);
+ if (a_string == NULL) {
+ DEBUG(0, ("talloc_sub_advanced_only: Out of memory!\n"));
+ return NULL;
+ }
+
+ for (s = a_string; (p = strchr_m(s, '%')); s = a_string + (p - b)) {
+
+ b = a_string;
+
+ switch (*(p+1)) {
+ case 'N':
+ a_string = realloc_string_sub(a_string,
+ "%N",
+ lp_netbios_name());
+ break;
+ case 'H': {
+ char *h;
+ if ((h = get_user_home_dir(talloc_tos(), user)))
+ a_string = realloc_string_sub(a_string, "%H", h);
+ TALLOC_FREE(h);
+ break;
+ }
+ case 'P':
+ a_string = realloc_string_sub(a_string, "%P", connectpath);
+ break;
+ case 'S':
+ a_string = realloc_string_sub(a_string, "%S", servicename);
+ break;
+ case 'g':
+ a_string = realloc_string_sub(a_string, "%g", gidtoname(gid));
+ break;
+ case 'u':
+ a_string = realloc_string_sub(a_string, "%u", user);
+ break;
+ default:
+ break;
+ }
+
+ p++;
+ if (a_string == NULL) {
+ return NULL;
+ }
+ }
+
+ return a_string;
+}
+
+char *talloc_sub_full(TALLOC_CTX *ctx,
+ const char *servicename,
+ const char *user,
+ const char *connectpath,
+ gid_t gid,
+ const char *smb_name,
+ const char *domain_name,
+ const char *str)
+{
+ char *a_string, *ret_string;
+
+ a_string = talloc_sub_advanced(ctx, servicename, user, connectpath,
+ gid, str);
+ if (a_string == NULL) {
+ return NULL;
+ }
+
+ ret_string = talloc_sub_basic(ctx, smb_name, domain_name, a_string);
+ TALLOC_FREE(a_string);
+ return ret_string;
+}
diff --git a/source3/lib/substitute.h b/source3/lib/substitute.h
new file mode 100644
index 0000000..6596ffd
--- /dev/null
+++ b/source3/lib/substitute.h
@@ -0,0 +1,65 @@
+/*
+ Unix SMB/CIFS implementation.
+ string substitution functions
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SUBSTITUTE_H
+#define SUBSTITUTE_H
+
+void set_remote_proto(const char *proto);
+bool set_local_machine_name(const char *local_name, bool perm);
+const char *get_local_machine_name(void);
+bool set_remote_machine_name(const char *remote_name, bool perm);
+const char *get_remote_machine_name(void);
+void sub_set_socket_ids(const char *peeraddr, const char *peername,
+ const char *sockaddr);
+void set_current_user_info(const char *smb_name,
+ const char *unix_name,
+ const char *domain);
+const char *get_current_username(void);
+const char *get_current_user_info_domain(void);
+void standard_sub_basic(const char *smb_name,
+ const char *domain_name,
+ char *str,
+ size_t len);
+char *talloc_sub_basic(TALLOC_CTX *mem_ctx,
+ const char *smb_name,
+ const char *domain_name,
+ const char *str);
+char *talloc_sub_specified(TALLOC_CTX *mem_ctx,
+ const char *input_string,
+ const char *username,
+ const char *grpname,
+ const char *domain,
+ uid_t uid,
+ gid_t gid);
+char *talloc_sub_advanced(TALLOC_CTX *ctx,
+ const char *servicename,
+ const char *user,
+ const char *connectpath,
+ gid_t gid,
+ const char *str);
+char *talloc_sub_full(TALLOC_CTX *ctx,
+ const char *servicename,
+ const char *user,
+ const char *connectpath,
+ gid_t gid,
+ const char *smb_name,
+ const char *domain_name,
+ const char *str);
+#endif
diff --git a/source3/lib/substitute_generic.c b/source3/lib/substitute_generic.c
new file mode 100644
index 0000000..26c5ee7
--- /dev/null
+++ b/source3/lib/substitute_generic.c
@@ -0,0 +1,113 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+
+ Copyright (C) Andrew Tridgell 1992-2001
+ Copyright (C) Simo Sorce 2001-2002
+ Copyright (C) Martin Pool 2003
+ Copyright (C) James Peach 2006
+ Copyright (C) Jeremy Allison 1992-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+/**
+ Similar to string_sub2, but it will accept only allocated strings
+ and may realloc them so pay attention at what you pass on no
+ pointers inside strings, no const may be passed
+ as string.
+**/
+
+char *realloc_string_sub2(char *string,
+ const char *pattern,
+ const char *insert,
+ bool remove_unsafe_characters,
+ bool allow_trailing_dollar)
+{
+ char *p, *in;
+ char *s;
+ ssize_t ls,lp,li,ld, i;
+
+ if (!insert || !pattern || !*pattern || !string || !*string)
+ return NULL;
+
+ s = string;
+
+ in = talloc_strdup(talloc_tos(), insert);
+ if (!in) {
+ DEBUG(0, ("realloc_string_sub: out of memory!\n"));
+ return NULL;
+ }
+ ls = (ssize_t)strlen(s);
+ lp = (ssize_t)strlen(pattern);
+ li = (ssize_t)strlen(insert);
+ ld = li - lp;
+ for (i=0;i<li;i++) {
+ switch (in[i]) {
+ case '$':
+ /* allow a trailing $
+ * (as in machine accounts) */
+ if (allow_trailing_dollar && (i == li - 1 )) {
+ break;
+ }
+ FALL_THROUGH;
+ case '`':
+ case '"':
+ case '\'':
+ case ';':
+ case '%':
+ case '\r':
+ case '\n':
+ if ( remove_unsafe_characters ) {
+ in[i] = '_';
+ break;
+ }
+ FALL_THROUGH;
+ default:
+ /* ok */
+ break;
+ }
+ }
+
+ while ((p = strstr_m(s,pattern))) {
+ if (ld > 0) {
+ int offset = PTR_DIFF(s,string);
+ string = talloc_realloc(NULL, string, char, ls + ld + 1);
+ if (!string) {
+ DEBUG(0, ("realloc_string_sub: "
+ "out of memory!\n"));
+ talloc_free(in);
+ return NULL;
+ }
+ p = string + offset + (p - s);
+ }
+ if (li != lp) {
+ memmove(p+li,p+lp,strlen(p+lp)+1);
+ }
+ memcpy(p, in, li);
+ s = p + li;
+ ls += ld;
+ }
+ talloc_free(in);
+ return string;
+}
+
+char *realloc_string_sub(char *string,
+ const char *pattern,
+ const char *insert)
+{
+ return realloc_string_sub2(string, pattern, insert, true, false);
+}
diff --git a/source3/lib/sysacls.c b/source3/lib/sysacls.c
new file mode 100644
index 0000000..234094a
--- /dev/null
+++ b/source3/lib/sysacls.c
@@ -0,0 +1,518 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba system utilities for ACL support.
+ Copyright (C) Jeremy Allison 2000.
+ Copyright (C) Volker Lendecke 2006
+ Copyright (C) Michael Adam 2006,2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+
+#if defined(HAVE_POSIX_ACLS)
+#include "modules/vfs_posixacl.h"
+#endif
+
+#if defined(HAVE_SOLARIS_UNIXWARE_ACLS)
+#include "modules/vfs_solarisacl.h"
+#endif
+
+#if defined(HAVE_HPUX_ACLS)
+#include "modules/vfs_hpuxacl.h"
+#endif
+
+#if defined(HAVE_AIX_ACLS)
+#include "modules/vfs_aixacl.h"
+#endif
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+/*
+ * Note that while this code implements sufficient functionality
+ * to support the sys_acl_* interfaces it does not provide all
+ * of the semantics of the POSIX ACL interfaces.
+ *
+ * In particular, an ACL entry descriptor (SMB_ACL_ENTRY_T) returned
+ * from a call to sys_acl_get_entry() should not be assumed to be
+ * valid after calling any of the following functions, which may
+ * reorder the entries in the ACL.
+ *
+ * sys_acl_valid()
+ * sys_acl_set_fd()
+ */
+
+int sys_acl_get_entry(SMB_ACL_T acl_d, int entry_id, SMB_ACL_ENTRY_T *entry_p)
+{
+ if (entry_id != SMB_ACL_FIRST_ENTRY && entry_id != SMB_ACL_NEXT_ENTRY) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (entry_p == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (entry_id == SMB_ACL_FIRST_ENTRY) {
+ acl_d->next = 0;
+ }
+
+ if (acl_d->next < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (acl_d->next >= acl_d->count) {
+ return 0;
+ }
+
+ *entry_p = &acl_d->acl[acl_d->next++];
+
+ return 1;
+}
+
+int sys_acl_get_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T *type_p)
+{
+ *type_p = entry_d->a_type;
+
+ return 0;
+}
+
+int sys_acl_get_permset(SMB_ACL_ENTRY_T entry_d, SMB_ACL_PERMSET_T *permset_p)
+{
+ *permset_p = &entry_d->a_perm;
+
+ return 0;
+}
+
+void *sys_acl_get_qualifier(SMB_ACL_ENTRY_T entry_d)
+{
+ if (entry_d->a_type == SMB_ACL_USER) {
+ return &entry_d->info.user.uid;
+ }
+
+ if (entry_d->a_type == SMB_ACL_GROUP) {
+ return &entry_d->info.group.gid;
+ }
+
+ errno = EINVAL;
+ return NULL;
+}
+
+int sys_acl_clear_perms(SMB_ACL_PERMSET_T permset_d)
+{
+ *permset_d = 0;
+
+ return 0;
+}
+
+int sys_acl_add_perm(SMB_ACL_PERMSET_T permset_d, SMB_ACL_PERM_T perm)
+{
+ if (perm != SMB_ACL_READ && perm != SMB_ACL_WRITE
+ && perm != SMB_ACL_EXECUTE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (permset_d == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *permset_d |= perm;
+
+ return 0;
+}
+
+int sys_acl_get_perm(SMB_ACL_PERMSET_T permset_d, SMB_ACL_PERM_T perm)
+{
+ return *permset_d & perm;
+}
+
+char *sys_acl_to_text(const struct smb_acl_t *acl_d, ssize_t *len_p)
+{
+ int i;
+ int len, maxlen;
+ char *text;
+
+ /*
+ * use an initial estimate of 20 bytes per ACL entry
+ * when allocating memory for the text representation
+ * of the ACL
+ */
+ len = 0;
+ maxlen = 20 * acl_d->count;
+ if ((text = (char *)SMB_MALLOC(maxlen)) == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ for (i = 0; i < acl_d->count; i++) {
+ struct smb_acl_entry *ap = &acl_d->acl[i];
+ struct group *gr;
+ char tagbuf[12];
+ char idbuf[12];
+ const char *tag;
+ const char *id = "";
+ char perms[4];
+ int nbytes;
+
+ switch (ap->a_type) {
+ /*
+ * for debugging purposes it's probably more
+ * useful to dump unknown tag types rather
+ * than just returning an error
+ */
+ default:
+ slprintf(tagbuf, sizeof(tagbuf)-1, "0x%x",
+ ap->a_type);
+ tag = tagbuf;
+ break;
+
+ case SMB_ACL_USER:
+ id = uidtoname(ap->info.user.uid);
+
+ FALL_THROUGH;
+ case SMB_ACL_USER_OBJ:
+ tag = "user";
+ break;
+
+ case SMB_ACL_GROUP:
+ if ((gr = getgrgid(ap->info.group.gid)) == NULL) {
+ slprintf(idbuf, sizeof(idbuf)-1, "%ld",
+ (long)ap->info.group.gid);
+ id = idbuf;
+ } else {
+ id = gr->gr_name;
+ }
+
+ FALL_THROUGH;
+ case SMB_ACL_GROUP_OBJ:
+ tag = "group";
+ break;
+
+ case SMB_ACL_OTHER:
+ tag = "other";
+ break;
+
+ case SMB_ACL_MASK:
+ tag = "mask";
+ break;
+
+ }
+
+ perms[0] = (ap->a_perm & SMB_ACL_READ) ? 'r' : '-';
+ perms[1] = (ap->a_perm & SMB_ACL_WRITE) ? 'w' : '-';
+ perms[2] = (ap->a_perm & SMB_ACL_EXECUTE) ? 'x' : '-';
+ perms[3] = '\0';
+
+ /* <tag> : <qualifier> : rwx \n \0 */
+ nbytes = strlen(tag) + 1 + strlen(id) + 1 + 3 + 1 + 1;
+
+ /*
+ * If this entry would overflow the buffer
+ * allocate enough additional memory for this
+ * entry and an estimate of another 20 bytes
+ * for each entry still to be processed
+ */
+ if ((len + nbytes) > maxlen) {
+ maxlen += nbytes + 20 * (acl_d->count - i);
+ if ((text = (char *)SMB_REALLOC(text, maxlen)) == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+
+ slprintf(&text[len], nbytes, "%s:%s:%s\n", tag, id, perms);
+ len += (nbytes - 1);
+ }
+
+ if (len_p)
+ *len_p = len;
+
+ return text;
+}
+
+SMB_ACL_T sys_acl_init(TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T a;
+
+ if ((a = talloc(mem_ctx, struct smb_acl_t)) == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ a->count = 0;
+ a->next = -1;
+
+ a->acl = talloc_array(a, struct smb_acl_entry, 0);
+ if (!a->acl) {
+ TALLOC_FREE(a);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return a;
+}
+
+int sys_acl_create_entry(SMB_ACL_T *acl_p, SMB_ACL_ENTRY_T *entry_p)
+{
+ SMB_ACL_T acl_d;
+ SMB_ACL_ENTRY_T entry_d;
+ struct smb_acl_entry *acl;
+
+ if (acl_p == NULL || entry_p == NULL || (acl_d = *acl_p) == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ acl = talloc_realloc(acl_d, acl_d->acl, struct smb_acl_entry, acl_d->count+1);
+ if (!acl) {
+ errno = ENOMEM;
+ return -1;
+ }
+ acl_d->acl = acl;
+ entry_d = &acl_d->acl[acl_d->count];
+ entry_d->a_type = SMB_ACL_TAG_INVALID;
+ entry_d->a_perm = 0;
+ *entry_p = entry_d;
+
+ acl_d->count++;
+ return 0;
+}
+
+int sys_acl_set_tag_type(SMB_ACL_ENTRY_T entry_d, SMB_ACL_TAG_T tag_type)
+{
+ switch (tag_type) {
+ case SMB_ACL_USER:
+ case SMB_ACL_USER_OBJ:
+ case SMB_ACL_GROUP:
+ case SMB_ACL_GROUP_OBJ:
+ case SMB_ACL_OTHER:
+ case SMB_ACL_MASK:
+ entry_d->a_type = tag_type;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ return 0;
+}
+
+int sys_acl_set_qualifier(SMB_ACL_ENTRY_T entry_d, void *qual_p)
+{
+ if (entry_d->a_type == SMB_ACL_USER) {
+ entry_d->info.user.uid = *((uid_t *)qual_p);
+ return 0;
+ }
+ if (entry_d->a_type == SMB_ACL_GROUP) {
+ entry_d->info.group.gid = *((gid_t *)qual_p);
+ return 0;
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+
+int sys_acl_set_permset(SMB_ACL_ENTRY_T entry_d, SMB_ACL_PERMSET_T permset_d)
+{
+ if (*permset_d & ~(SMB_ACL_READ|SMB_ACL_WRITE|SMB_ACL_EXECUTE)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ entry_d->a_perm = *permset_d;
+
+ return 0;
+}
+
+int sys_acl_free_text(char *text)
+{
+ SAFE_FREE(text);
+ return 0;
+}
+
+int sys_acl_valid(SMB_ACL_T acl_d)
+{
+ errno = EINVAL;
+ return -1;
+}
+
+/*
+ * acl_get_file, acl_get_fd, acl_set_file, acl_set_fd and
+ * sys_acl_delete_def_fd are to be redirected to the default
+ * statically-bound acl vfs module, but they are replaceable.
+ */
+
+#if defined(HAVE_POSIX_ACLS)
+
+SMB_ACL_T sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ return posixacl_sys_acl_get_fd(handle, fsp, type, mem_ctx);
+}
+
+int sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T acl_d)
+{
+ return posixacl_sys_acl_set_fd(handle, fsp, type, acl_d);
+}
+
+int sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return posixacl_sys_acl_delete_def_fd(handle, fsp);
+}
+
+#elif defined(HAVE_AIX_ACLS)
+
+SMB_ACL_T sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ return aixacl_sys_acl_get_fd(handle, fsp, type, mem_ctx);
+}
+
+int sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T acl_d)
+{
+ return aixacl_sys_acl_set_fd(handle, fsp, type, acl_d);
+}
+
+int sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return aixacl_sys_acl_delete_def_fd(handle, fsp);
+}
+#elif defined(HAVE_SOLARIS_UNIXWARE_ACLS)
+
+SMB_ACL_T sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ return solarisacl_sys_acl_get_fd(handle, fsp, type,
+ mem_ctx);
+}
+
+int sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T acl_d)
+{
+ return solarisacl_sys_acl_set_fd(handle,
+ fsp,
+ type,
+ acl_d);
+}
+
+int sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return solarisacl_sys_acl_delete_def_fd(handle, fsp);
+}
+#elif defined(HAVE_HPUX_ACLS)
+
+SMB_ACL_T sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ return hpuxacl_sys_acl_get_fd(handle, fsp, mem_ctx);
+}
+
+int sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T acl_d)
+{
+ return hpuxacl_sys_acl_set_file(handle, fsp->fsp_name, type, acl_d);
+}
+
+int sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return hpuxacl_sys_acl_delete_def_fd(handle, fsp);
+}
+#else /* No ACLs. */
+
+SMB_ACL_T sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+#ifdef ENOTSUP
+ errno = ENOTSUP;
+#else
+ errno = ENOSYS;
+#endif
+ return NULL;
+}
+
+int sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T acl_d)
+{
+#ifdef ENOTSUP
+ errno = ENOTSUP;
+#else
+ errno = ENOSYS;
+#endif
+ return -1;
+}
+
+int sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+#ifdef ENOTSUP
+ errno = ENOTSUP;
+#else
+ errno = ENOSYS;
+#endif
+ return -1;
+}
+#endif
+
+/************************************************************************
+ Deliberately outside the ACL defines. Return 1 if this is a "no acls"
+ errno, 0 if not.
+************************************************************************/
+
+int no_acl_syscall_error(int err)
+{
+#if defined(ENOSYS)
+ if (err == ENOSYS) {
+ return 1;
+ }
+#endif
+#if defined(ENOTSUP)
+ if (err == ENOTSUP) {
+ return 1;
+ }
+#endif
+ return 0;
+}
diff --git a/source3/lib/sysquotas.c b/source3/lib/sysquotas.c
new file mode 100644
index 0000000..b7eedcd
--- /dev/null
+++ b/source3/lib/sysquotas.c
@@ -0,0 +1,619 @@
+/*
+ Unix SMB/CIFS implementation.
+ System QUOTA function wrappers
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "lib/util_file.h"
+#include "lib/util/smb_strtox.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifdef HAVE_SYS_QUOTAS
+
+#if defined(HAVE_QUOTACTL_4A)
+
+/*#endif HAVE_QUOTACTL_4A */
+#elif defined(HAVE_QUOTACTL_4B)
+
+/*#endif HAVE_QUOTACTL_4B */
+#elif defined(HAVE_QUOTACTL_3)
+
+#error HAVE_QUOTACTL_3 not implemented
+
+/* #endif HAVE_QUOTACTL_3 */
+#else /* NO_QUOTACTL_USED */
+
+#endif /* NO_QUOTACTL_USED */
+
+#if defined(HAVE_MNTENT) && defined(HAVE_REALPATH)
+static int sys_path_to_bdev(const char *path, char **mntpath, char **bdev, char **fs)
+{
+ int ret = -1;
+ SMB_STRUCT_STAT S;
+ FILE *fp;
+ struct mntent *mnt = NULL;
+ SMB_DEV_T devno;
+ char *stat_mntpath = NULL;
+ char *p;
+
+ /* find the block device file */
+ (*mntpath) = NULL;
+ (*bdev) = NULL;
+ (*fs) = NULL;
+
+ if (sys_stat(path, &S, false) != 0) {
+ return -1;
+ }
+
+ devno = S.st_ex_dev ;
+
+ stat_mntpath = sys_realpath(path);
+ if (stat_mntpath == NULL) {
+ DBG_WARNING("realpath(%s) failed - %s\n", path,
+ strerror(errno));
+ goto out;
+ }
+
+ if (sys_stat(stat_mntpath, &S, false) != 0) {
+ DBG_WARNING("cannot stat real path %s - %s\n", stat_mntpath,
+ strerror(errno));
+ goto out;
+ }
+
+ if (S.st_ex_dev != devno) {
+ DBG_WARNING("device on real path has changed\n");
+ goto out;
+ }
+
+ while (true) {
+ char save_ch;
+
+ p = strrchr(stat_mntpath, '/');
+ if (p == NULL) {
+ DBG_ERR("realpath for %s does not begin with a '/'\n",
+ path);
+ goto out;
+ }
+
+ if (p == stat_mntpath) {
+ ++p;
+ }
+
+ save_ch = *p;
+ *p = 0;
+ if (sys_stat(stat_mntpath, &S, false) != 0) {
+ DBG_WARNING("cannot stat real path component %s - %s\n",
+ stat_mntpath, strerror(errno));
+ goto out;
+ }
+ if (S.st_ex_dev != devno) {
+ *p = save_ch;
+ break;
+ }
+
+ if (p <= stat_mntpath + 1) {
+ break;
+ }
+ }
+
+ fp = setmntent(MOUNTED,"r");
+ if (fp == NULL) {
+ goto out;
+ }
+
+ while ((mnt = getmntent(fp))) {
+ if (!strequal(mnt->mnt_dir, stat_mntpath)) {
+ continue;
+ }
+
+ if ( sys_stat(mnt->mnt_dir, &S, false) == -1 )
+ continue ;
+
+ if (S.st_ex_dev == devno) {
+ (*mntpath) = SMB_STRDUP(mnt->mnt_dir);
+ (*bdev) = SMB_STRDUP(mnt->mnt_fsname);
+ (*fs) = SMB_STRDUP(mnt->mnt_type);
+ if ((*mntpath)&&(*bdev)&&(*fs)) {
+ ret = 0;
+ } else {
+ SAFE_FREE(*mntpath);
+ SAFE_FREE(*bdev);
+ SAFE_FREE(*fs);
+ ret = -1;
+ }
+
+ break;
+ }
+ }
+
+ endmntent(fp) ;
+
+out:
+ SAFE_FREE(stat_mntpath);
+ return ret;
+}
+/* #endif HAVE_MNTENT */
+#elif defined(HAVE_DEVNM)
+
+/* we have this on HPUX, ... */
+static int sys_path_to_bdev(const char *path, char **mntpath, char **bdev, char **fs)
+{
+ int ret = -1;
+ char dev_disk[256];
+ SMB_STRUCT_STAT S;
+
+ if (!path||!mntpath||!bdev||!fs)
+ smb_panic("sys_path_to_bdev: called with NULL pointer");
+
+ (*mntpath) = NULL;
+ (*bdev) = NULL;
+ (*fs) = NULL;
+
+ /* find the block device file */
+
+ if ((ret=sys_stat(path, &S, false))!=0) {
+ return ret;
+ }
+
+ if ((ret=devnm(S_IFBLK, S.st_ex_dev, dev_disk, 256, 1))!=0) {
+ return ret;
+ }
+
+ /* we should get the mntpath right...
+ * but I don't know how
+ * --metze
+ */
+ (*mntpath) = SMB_STRDUP(path);
+ (*bdev) = SMB_STRDUP(dev_disk);
+ if ((*mntpath)&&(*bdev)) {
+ ret = 0;
+ } else {
+ SAFE_FREE(*mntpath);
+ SAFE_FREE(*bdev);
+ ret = -1;
+ }
+
+
+ return ret;
+}
+
+/* #endif HAVE_DEVNM */
+#else
+/* we should fake this up...*/
+static int sys_path_to_bdev(const char *path, char **mntpath, char **bdev, char **fs)
+{
+ int ret = -1;
+
+ if (!path||!mntpath||!bdev||!fs)
+ smb_panic("sys_path_to_bdev: called with NULL pointer");
+
+ (*mntpath) = NULL;
+ (*bdev) = NULL;
+ (*fs) = NULL;
+
+ (*mntpath) = SMB_STRDUP(path);
+ if (*mntpath) {
+ ret = 0;
+ } else {
+ SAFE_FREE(*mntpath);
+ ret = -1;
+ }
+
+ return ret;
+}
+#endif
+
+/*********************************************************************
+ Now the list of all filesystem specific quota systems we have found
+**********************************************************************/
+static struct {
+ const char *name;
+ int (*get_quota)(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+ int (*set_quota)(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp);
+} sys_quota_backends[] = {
+#ifdef HAVE_JFS_QUOTA_H
+ {"jfs2", sys_get_jfs2_quota, sys_set_jfs2_quota},
+#endif
+#if defined HAVE_XFS_QUOTAS
+ {"xfs", sys_get_xfs_quota, sys_set_xfs_quota},
+ {"gfs", sys_get_xfs_quota, sys_set_xfs_quota},
+ {"gfs2", sys_get_xfs_quota, sys_set_xfs_quota},
+#endif /* HAVE_XFS_QUOTAS */
+#ifdef HAVE_NFS_QUOTAS
+ {"nfs", sys_get_nfs_quota, sys_set_nfs_quota},
+ {"nfs4", sys_get_nfs_quota, sys_set_nfs_quota},
+#endif /* HAVE_NFS_QUOTAS */
+ {NULL, NULL, NULL}
+};
+
+static int command_get_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *get_quota_command;
+ char **lines = NULL;
+
+ get_quota_command = lp_get_quota_command(talloc_tos(), lp_sub);
+ if (get_quota_command && *get_quota_command) {
+ const char *p;
+ char *p2;
+ int _id = -1;
+ int error = 0;
+ char **argl = NULL;
+
+ switch(qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ case SMB_USER_FS_QUOTA_TYPE:
+ _id = id.uid;
+ break;
+ case SMB_GROUP_QUOTA_TYPE:
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ _id = id.gid;
+ break;
+ default:
+ DEBUG(0,("invalid quota type.\n"));
+ return -1;
+ }
+
+ argl = str_list_make_empty(talloc_tos());
+ str_list_add_printf(&argl, "%s", get_quota_command);
+ str_list_add_printf(&argl, "%s", path);
+ str_list_add_printf(&argl, "%d", qtype);
+ str_list_add_printf(&argl, "%d", _id);
+ if (argl == NULL) {
+ return -1;
+ }
+
+ DBG_NOTICE("Running command %s %s %d %d\n",
+ get_quota_command,
+ path,
+ qtype,
+ _id);
+
+ lines = file_lines_ploadv(talloc_tos(), argl, NULL);
+ TALLOC_FREE(argl);
+
+ if (lines) {
+ char *line = lines[0];
+
+ DEBUG (3, ("Read output from get_quota, \"%s\"\n", line));
+
+ /* we need to deal with long long unsigned here, if supported */
+
+ dp->qflags = smb_strtoul(line,
+ &p2,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ goto invalid_param;
+ }
+
+ p = p2;
+ while (p && *p && isspace(*p)) {
+ p++;
+ }
+
+ if (p && *p) {
+ dp->curblocks = STR_TO_SMB_BIG_UINT(p, &p);
+ } else {
+ goto invalid_param;
+ }
+
+ while (p && *p && isspace(*p)) {
+ p++;
+ }
+
+ if (p && *p) {
+ dp->softlimit = STR_TO_SMB_BIG_UINT(p, &p);
+ } else {
+ goto invalid_param;
+ }
+
+ while (p && *p && isspace(*p)) {
+ p++;
+ }
+
+ if (p && *p) {
+ dp->hardlimit = STR_TO_SMB_BIG_UINT(p, &p);
+ } else {
+ goto invalid_param;
+ }
+
+ while (p && *p && isspace(*p)) {
+ p++;
+ }
+
+ if (p && *p) {
+ dp->curinodes = STR_TO_SMB_BIG_UINT(p, &p);
+ } else {
+ goto invalid_param;
+ }
+
+ while (p && *p && isspace(*p)) {
+ p++;
+ }
+
+ if (p && *p) {
+ dp->isoftlimit = STR_TO_SMB_BIG_UINT(p, &p);
+ } else {
+ goto invalid_param;
+ }
+
+ while (p && *p && isspace(*p)) {
+ p++;
+ }
+
+ if (p && *p) {
+ dp->ihardlimit = STR_TO_SMB_BIG_UINT(p, &p);
+ } else {
+ goto invalid_param;
+ }
+
+ while (p && *p && isspace(*p)) {
+ p++;
+ }
+
+ if (p && *p) {
+ dp->bsize = STR_TO_SMB_BIG_UINT(p, NULL);
+ } else {
+ dp->bsize = 1024;
+ }
+
+ TALLOC_FREE(lines);
+ lines = NULL;
+
+ DEBUG (3, ("Parsed output of get_quota, ...\n"));
+
+ DEBUGADD (5,(
+ "qflags:%"PRIu32" curblocks:%"PRIu64" softlimit:%"PRIu64" hardlimit:%"PRIu64"\n"
+ "curinodes:%"PRIu64" isoftlimit:%"PRIu64" ihardlimit:%"PRIu64" bsize:%"PRIu64"\n",
+ dp->qflags,dp->curblocks,
+ dp->softlimit,dp->hardlimit,
+ dp->curinodes,
+ dp->isoftlimit,dp->ihardlimit,
+ dp->bsize));
+ return 0;
+ }
+
+ DEBUG (0, ("get_quota_command failed!\n"));
+ return -1;
+ }
+
+ errno = ENOSYS;
+ return -1;
+
+invalid_param:
+
+ TALLOC_FREE(lines);
+ DEBUG(0,("The output of get_quota_command is invalid!\n"));
+ return -1;
+}
+
+static int command_set_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *set_quota_command;
+
+ set_quota_command = lp_set_quota_command(talloc_tos(), lp_sub);
+ if (set_quota_command && *set_quota_command) {
+ char **lines = NULL;
+ int _id = -1;
+ char **argl = NULL;
+
+ switch(qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ case SMB_USER_FS_QUOTA_TYPE:
+ _id = id.uid;
+ break;
+ case SMB_GROUP_QUOTA_TYPE:
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ _id = id.gid;
+ break;
+ default:
+ return -1;
+ }
+
+ argl = str_list_make_empty(talloc_tos());
+ str_list_add_printf(&argl, "%s", set_quota_command);
+ str_list_add_printf(&argl, "%s", path);
+ str_list_add_printf(&argl, "%d", qtype);
+ str_list_add_printf(&argl, "%d", _id);
+ str_list_add_printf(&argl, "%u", dp->qflags);
+ str_list_add_printf(
+ &argl, "%"PRIu64, dp->softlimit);
+ str_list_add_printf(
+ &argl, "%"PRIu64, dp->hardlimit);
+ str_list_add_printf(
+ &argl, "%"PRIu64, dp->isoftlimit);
+ str_list_add_printf(
+ &argl, "%"PRIu64, dp->ihardlimit);
+ str_list_add_printf(
+ &argl, "%"PRIu64, dp->bsize);
+ if (argl == NULL) {
+ return -1;
+ }
+
+ DBG_NOTICE("Running command "
+ "%s %s %d %d "
+ "%"PRIu32" %"PRIu64" %"PRIu64" "
+ "%"PRIu64" %"PRIu64" %"PRIu64"\n",
+ set_quota_command,
+ path,
+ qtype,
+ _id,
+ dp->qflags,
+ dp->softlimit,
+ dp->hardlimit,
+ dp->isoftlimit,
+ dp->ihardlimit,
+ dp->bsize);
+
+ lines = file_lines_ploadv(talloc_tos(), argl, NULL);
+ TALLOC_FREE(argl);
+ if (lines) {
+ char *line = lines[0];
+
+ DEBUG (3, ("Read output from set_quota, \"%s\"\n", line));
+
+ TALLOC_FREE(lines);
+
+ return 0;
+ }
+ DEBUG (0, ("set_quota_command failed!\n"));
+ return -1;
+ }
+
+ errno = ENOSYS;
+ return -1;
+}
+
+int sys_get_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ int i;
+ bool ready = False;
+ char *mntpath = NULL;
+ char *bdev = NULL;
+ char *fs = NULL;
+
+ if (!path||!dp)
+ smb_panic("sys_get_quota: called with NULL pointer");
+
+ if (command_get_quota(path, qtype, id, dp)==0) {
+ return 0;
+ } else if (errno != ENOSYS) {
+ return -1;
+ }
+
+ if ((ret=sys_path_to_bdev(path,&mntpath,&bdev,&fs))!=0) {
+ DEBUG(0,("sys_path_to_bdev() failed for path [%s]!\n",path));
+ return ret;
+ }
+
+ errno = 0;
+ DEBUG(10,("sys_get_quota() uid(%u, %u), fs(%s)\n", (unsigned)getuid(), (unsigned)geteuid(), fs));
+
+ for (i=0;(fs && sys_quota_backends[i].name && sys_quota_backends[i].get_quota);i++) {
+ if (strcmp(fs,sys_quota_backends[i].name)==0) {
+ ret = sys_quota_backends[i].get_quota(mntpath, bdev, qtype, id, dp);
+ if (ret!=0) {
+ DEBUG(3,("sys_get_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s.\n",
+ fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno)));
+ } else {
+ DEBUG(10,("sys_get_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n",
+ fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid)));
+ }
+ ready = True;
+ break;
+ }
+ }
+
+ if (!ready) {
+ /* use the default vfs quota functions */
+ ret=sys_get_vfs_quota(mntpath, bdev, qtype, id, dp);
+ if (ret!=0) {
+ DEBUG(3,("sys_get_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s\n",
+ "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno)));
+ } else {
+ DEBUG(10,("sys_get_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n",
+ "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid)));
+ }
+ }
+
+ SAFE_FREE(mntpath);
+ SAFE_FREE(bdev);
+ SAFE_FREE(fs);
+
+ return ret;
+}
+
+int sys_set_quota(const char *path, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ int i;
+ bool ready = False;
+ char *mntpath = NULL;
+ char *bdev = NULL;
+ char *fs = NULL;
+
+ /* find the block device file */
+
+ if (!path||!dp)
+ smb_panic("get_smb_quota: called with NULL pointer");
+
+ if (command_set_quota(path, qtype, id, dp)==0) {
+ return 0;
+ } else if (errno != ENOSYS) {
+ return -1;
+ }
+
+ if ((ret=sys_path_to_bdev(path,&mntpath,&bdev,&fs))!=0) {
+ DEBUG(0,("sys_path_to_bdev() failed for path [%s]!\n",path));
+ return ret;
+ }
+
+ errno = 0;
+ DEBUG(10,("sys_set_quota() uid(%u, %u)\n", (unsigned)getuid(), (unsigned)geteuid()));
+
+ for (i=0;(fs && sys_quota_backends[i].name && sys_quota_backends[i].set_quota);i++) {
+ if (strcmp(fs,sys_quota_backends[i].name)==0) {
+ ret = sys_quota_backends[i].set_quota(mntpath, bdev, qtype, id, dp);
+ if (ret!=0) {
+ DEBUG(3,("sys_set_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s.\n",
+ fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno)));
+ } else {
+ DEBUG(10,("sys_set_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n",
+ fs,mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid)));
+ }
+ ready = True;
+ break;
+ }
+ }
+
+ if (!ready) {
+ /* use the default vfs quota functions */
+ ret=sys_set_vfs_quota(mntpath, bdev, qtype, id, dp);
+ if (ret!=0) {
+ DEBUG(3,("sys_set_%s_quota() failed for mntpath[%s] bdev[%s] qtype[%d] id[%d]: %s.\n",
+ "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid),strerror(errno)));
+ } else {
+ DEBUG(10,("sys_set_%s_quota() called for mntpath[%s] bdev[%s] qtype[%d] id[%d].\n",
+ "vfs",mntpath,bdev,qtype,(qtype==SMB_GROUP_QUOTA_TYPE?id.gid:id.uid)));
+ }
+ }
+
+ SAFE_FREE(mntpath);
+ SAFE_FREE(bdev);
+ SAFE_FREE(fs);
+
+ return ret;
+}
+
+#else /* HAVE_SYS_QUOTAS */
+ void dummy_sysquotas_c(void);
+
+ void dummy_sysquotas_c(void)
+{
+ return;
+}
+#endif /* HAVE_SYS_QUOTAS */
+
diff --git a/source3/lib/sysquotas_4A.c b/source3/lib/sysquotas_4A.c
new file mode 100644
index 0000000..674c4ee
--- /dev/null
+++ b/source3/lib/sysquotas_4A.c
@@ -0,0 +1,334 @@
+/*
+ Unix SMB/CIFS implementation.
+ System QUOTA function wrappers for QUOTACTL_4A
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifndef HAVE_SYS_QUOTAS
+#ifdef HAVE_QUOTACTL_4A
+#undef HAVE_QUOTACTL_4A
+#endif
+#endif
+
+#ifdef HAVE_QUOTACTL_4A
+/* long quotactl(int cmd, char *special, qid_t id, caddr_t addr) */
+/* this is used by: HPUX,IRIX */
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_ASM_TYPES_H
+#include <asm/types.h>
+#endif
+
+#ifdef HAVE_SYS_QUOTA_H
+#include <sys/quota.h>
+#endif
+
+#ifndef Q_SETQLIM
+#define Q_SETQLIM Q_SETQUOTA
+#endif
+
+#ifndef QCMD
+#define QCMD(x,y) x
+#endif
+
+#ifndef QCMD
+#define QCMD(x,y) x
+#endif
+
+#ifdef GRPQUOTA
+#define HAVE_GROUP_QUOTA
+#endif
+
+#ifndef QUOTABLOCK_SIZE
+#define QUOTABLOCK_SIZE DEV_BSIZE
+#endif
+
+#ifdef HAVE_DQB_FSOFTLIMIT
+#define dqb_isoftlimit dqb_fsoftlimit
+#define dqb_ihardlimit dqb_fhardlimit
+#define dqb_curinodes dqb_curfiles
+#endif
+
+#ifdef INITQFNAMES
+#define USERQUOTAFILE_EXTENSION ".user"
+#else
+#define USERQUOTAFILE_EXTENSION ""
+#endif
+
+#if !defined(QUOTAFILENAME) && defined(QFILENAME)
+#define QUOTAFILENAME QFILENAME
+#endif
+
+/****************************************************************************
+ Abstract out the quotactl_4A get calls.
+****************************************************************************/
+int sys_get_vfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ uint32_t qflags = 0;
+ struct dqblk D;
+ uint64_t bsize = (uint64_t)QUOTABLOCK_SIZE;
+
+ ZERO_STRUCT(D);
+ ZERO_STRUCT(*dp);
+ dp->qtype = qtype;
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ DEBUG(10,("sys_get_vfs_quota: path[%s] bdev[%s] SMB_USER_QUOTA_TYPE uid[%u]\n",
+ path, bdev, (unsigned)id.uid));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA,USRQUOTA), (caddr_t)bdev, id.uid, (void *)&D))&&errno != EDQUOT) {
+ return ret;
+ }
+
+ ret = 0;
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_QUOTA_TYPE:
+ DEBUG(10,("sys_get_vfs_quota: path[%s] bdev[%s] SMB_GROUP_QUOTA_TYPE gid[%u]\n",
+ path, bdev, (unsigned)id.gid));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA,GRPQUOTA), (caddr_t)bdev, id.gid, (void *)&D))&&errno != EDQUOT) {
+ return ret;
+ }
+
+ ret = 0;
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ case SMB_USER_FS_QUOTA_TYPE:
+ id.uid = getuid();
+
+ DEBUG(10,("sys_get_vfs_quota: path[%s] bdev[%s] SMB_USER_FS_QUOTA_TYPE (uid[%u])\n",
+ path, (caddr_t)bdev, (unsigned)id.uid));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA,USRQUOTA), (caddr_t)bdev, id.uid, (void *)&D))==0) {
+ qflags |= QUOTAS_DENY_DISK;
+ }
+
+ ret = 0;
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ id.gid = getgid();
+
+ DEBUG(10,("sys_get_vfs_quota: path[%s] bdev[%s] SMB_GROUP_FS_QUOTA_TYPE (gid[%u])\n",
+ path, bdev, (unsigned)id.gid));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA,GRPQUOTA), (caddr_t)bdev, id.gid, (void *)&D))==0) {
+ qflags |= QUOTAS_DENY_DISK;
+ }
+
+ ret = 0;
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ default:
+ errno = ENOSYS;
+ return -1;
+ }
+
+ dp->bsize = bsize;
+ dp->softlimit = (uint64_t)D.dqb_bsoftlimit;
+ dp->hardlimit = (uint64_t)D.dqb_bhardlimit;
+ dp->ihardlimit = (uint64_t)D.dqb_ihardlimit;
+ dp->isoftlimit = (uint64_t)D.dqb_isoftlimit;
+ dp->curinodes = (uint64_t)D.dqb_curinodes;
+ dp->curblocks = (uint64_t)D.dqb_curblocks;
+
+
+ dp->qflags = qflags;
+
+ return ret;
+}
+
+/****************************************************************************
+ Abstract out the quotactl_4A set calls.
+****************************************************************************/
+int sys_set_vfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ uint32_t qflags = 0;
+ uint32_t oldqflags = 0;
+ struct dqblk D;
+ uint64_t bsize = (uint64_t)QUOTABLOCK_SIZE;
+
+ ZERO_STRUCT(D);
+
+ if (bsize == dp->bsize) {
+ D.dqb_bsoftlimit = dp->softlimit;
+ D.dqb_bhardlimit = dp->hardlimit;
+ D.dqb_ihardlimit = dp->ihardlimit;
+ D.dqb_isoftlimit = dp->isoftlimit;
+ } else {
+ D.dqb_bsoftlimit = (dp->softlimit*dp->bsize)/bsize;
+ D.dqb_bhardlimit = (dp->hardlimit*dp->bsize)/bsize;
+ D.dqb_ihardlimit = (dp->ihardlimit*dp->bsize)/bsize;
+ D.dqb_isoftlimit = (dp->isoftlimit*dp->bsize)/bsize;
+ }
+
+ qflags = dp->qflags;
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ DEBUG(10,("sys_set_vfs_quota: path[%s] bdev[%s] SMB_USER_QUOTA_TYPE uid[%u]\n",
+ path, bdev, (unsigned)id.uid));
+
+ ret = quotactl(QCMD(Q_SETQLIM,USRQUOTA), (caddr_t)bdev, id.uid, (void *)&D);
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_QUOTA_TYPE:
+ DEBUG(10,("sys_set_vfs_quota: path[%s] bdev[%s] SMB_GROUP_QUOTA_TYPE gid[%u]\n",
+ path, bdev, (unsigned)id.gid));
+
+ ret = quotactl(QCMD(Q_SETQLIM,GRPQUOTA), (caddr_t)bdev, id.gid, (void *)&D);
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ case SMB_USER_FS_QUOTA_TYPE:
+ /* this stuff didn't work as it should:
+ * switching on/off quota via quotactl()
+ * didn't work!
+ * So we just return 0
+ * --metze
+ *
+ * On HPUX we didn't have the mount path,
+ * we need to fix sys_path_to_bdev()
+ *
+ */
+ id.uid = getuid();
+ DEBUG(10,("sys_set_vfs_quota: path[%s] bdev[%s] SMB_USER_FS_QUOTA_TYPE (uid[%u])\n",
+ path, bdev, (unsigned)id.uid));
+
+#if 0
+ ret = quotactl(QCMD(Q_GETQUOTA,USRQUOTA), (caddr_t)bdev, id.uid, (void *)&D);
+
+ if ((qflags&QUOTAS_DENY_DISK)||(qflags&QUOTAS_ENABLED)) {
+ if (ret == 0) {
+ char *quota_file = NULL;
+
+ asprintf(&quota_file,"/%s/%s%s",path, QUOTAFILENAME,USERQUOTAFILE_EXTENSION);
+ if (quota_file == NULL) {
+ DEBUG(0,("asprintf() failed!\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = quotactl(QCMD(Q_QUOTAON,USRQUOTA), (caddr_t)bdev, -1,(void *)quota_file);
+ } else {
+ ret = 0;
+ }
+ } else {
+ if (ret != 0) {
+ /* turn off */
+ ret = quotactl(QCMD(Q_QUOTAOFF,USRQUOTA), (caddr_t)bdev, -1, (void *)0);
+ } else {
+ ret = 0;
+ }
+ }
+
+ DEBUG(0,("sys_set_vfs_quota: ret(%d) errno(%d)[%s] uid(%d) bdev[%s]\n",
+ ret,errno,strerror(errno),id.uid,bdev));
+#else
+ if ((ret = quotactl(QCMD(Q_GETQUOTA,USRQUOTA), (caddr_t)bdev, id.uid, (void *)&D))==0) {
+ oldqflags |= QUOTAS_DENY_DISK;
+ }
+
+ if (oldqflags == qflags) {
+ ret = 0;
+ } else {
+ ret = -1;
+ }
+#endif
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ /* this stuff didn't work as it should:
+ * switching on/off quota via quotactl()
+ * didn't work!
+ * So we just return 0
+ * --metze
+ *
+ * On HPUX we didn't have the mount path,
+ * we need to fix sys_path_to_bdev()
+ *
+ */
+ id.gid = getgid();
+ DEBUG(10,("sys_set_vfs_quota: path[%s] bdev[%s] SMB_GROUP_FS_QUOTA_TYPE (gid[%u])\n",
+ path, bdev, (unsigned)id.gid));
+
+#if 0
+ ret = quotactl(QCMD(Q_GETQUOTA,GRPQUOTA), bdev, id, (void *)&D);
+
+ if ((qflags&QUOTAS_DENY_DISK)||(qflags&QUOTAS_ENABLED)) {
+ if (ret == 0) {
+ char *quota_file = NULL;
+
+ asprintf(&quota_file,"/%s/%s%s",path, QUOTAFILENAME,GROUPQUOTAFILE_EXTENSION);
+ if (quota_file == NULL) {
+ DEBUG(0,("asprintf() failed!\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = quotactl(QCMD(Q_QUOTAON,GRPQUOTA), (caddr_t)bdev, -1,(void *)quota_file);
+ } else {
+ ret = 0;
+ }
+ } else {
+ if (ret != 0) {
+ /* turn off */
+ ret = quotactl(QCMD(Q_QUOTAOFF,GRPQUOTA), (caddr_t)bdev, -1, (void *)0);
+ } else {
+ ret = 0;
+ }
+ }
+
+ DEBUG(0,("sys_set_vfs_quota: ret(%d) errno(%d)[%s] uid(%d) bdev[%s]\n",
+ ret,errno,strerror(errno),id.gid,bdev));
+#else
+ if ((ret = quotactl(QCMD(Q_GETQUOTA,GRPQUOTA), (caddr_t)bdev, id.gid, (void *)&D))==0) {
+ oldqflags |= QUOTAS_DENY_DISK;
+ }
+
+ if (oldqflags == qflags) {
+ ret = 0;
+ } else {
+ ret = -1;
+ }
+#endif
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ default:
+ errno = ENOSYS;
+ return -1;
+ }
+
+ return ret;
+}
+
+#else /* HAVE_QUOTACTL_4A */
+ void dummy_sysquotas_4A(void);
+
+ void dummy_sysquotas_4A(void){}
+#endif /* HAVE_QUOTACTL_4A */
diff --git a/source3/lib/sysquotas_4B.c b/source3/lib/sysquotas_4B.c
new file mode 100644
index 0000000..d9beb92
--- /dev/null
+++ b/source3/lib/sysquotas_4B.c
@@ -0,0 +1,243 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * System QUOTA function wrappers for QUOTACTL_4B
+
+ * Copyright (C) 2011 James Peach.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifndef HAVE_SYS_QUOTAS
+#undef HAVE_QUOTACTL_4B
+#endif
+
+#ifdef HAVE_QUOTACTL_4B
+/* int quotactl(const char *path, int cmd, int id, char *addr)
+ *
+ * This is used by many (all?) BSD-derived systems. This implementation has
+ * been developed and tested on Darwin, but may also work on other BSD systems.
+ */
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_SYS_QUOTA_H
+#include <sys/quota.h>
+#endif
+
+#ifdef HAVE_UFS_UFS_QUOTA_H
+#include <ufs/ufs/quota.h>
+#endif
+
+#ifdef HAVE_JFS_QUOTA_H
+#include <jfs/quota.h>
+#endif
+
+#if defined(DARWINOS)
+/* WorkARound broken HFS access checks in hfs_quotactl. Darwin only(?) */
+#define HFS_QUOTACTL_WAR 1
+#endif
+
+#ifdef HAVE_STRUCT_DQBLK_DQB_CURBYTES
+/* we handle the byte vs. block count dynamically via QUOTABLOCK_SIZE 1 */
+#define dqb_curblocks dqb_curbytes
+#endif
+
+static void xlate_qblk_to_smb(const struct dqblk * const qblk,
+ SMB_DISK_QUOTA *dp)
+{
+ ZERO_STRUCTP(dp);
+
+ DEBUG(10, ("unix softlimit=%u hardlimit=%u curblock=%u\n",
+ (unsigned)qblk->dqb_bsoftlimit, (unsigned)qblk->dqb_bhardlimit,
+ (unsigned)qblk->dqb_curblocks));
+
+ DEBUGADD(10, ("unix softinodes=%u hardinodes=%u curinodes=%u\n",
+ (unsigned)qblk->dqb_isoftlimit, (unsigned)qblk->dqb_ihardlimit,
+ (unsigned)qblk->dqb_curinodes));
+
+ dp->bsize = QUOTABLOCK_SIZE;
+
+ dp->softlimit = qblk->dqb_bsoftlimit;
+ dp->hardlimit = qblk->dqb_bhardlimit;
+ dp->curblocks = qblk->dqb_curblocks;
+/* our Darwin quotas used to never return 0byte usage but this is probably not needed,
+ * let's comment this out for now
+#ifdef HAVE_STRUCT_DQBLK_DQB_CURBYTES
+ if (dp->curblocks == 0) {
+ dp->curblocks = 1;
+ }
+#endif
+ */
+
+ dp->ihardlimit = qblk->dqb_ihardlimit;
+ dp->isoftlimit = qblk->dqb_isoftlimit;
+ dp->curinodes = qblk->dqb_curinodes;
+
+ dp->qflags = QUOTAS_ENABLED | QUOTAS_DENY_DISK;
+
+ DEBUG(10, ("softlimit=%u hardlimit=%u curblock=%u\n",
+ (unsigned)dp->softlimit, (unsigned)dp->hardlimit,
+ (unsigned)dp->curblocks));
+
+ DEBUGADD(10, ("softinodes=%u hardinodes=%u curinodes=%u\n",
+ (unsigned)dp->isoftlimit, (unsigned)dp->ihardlimit,
+ (unsigned)dp->curinodes));
+
+}
+
+static void xlate_smb_to_qblk(const SMB_DISK_QUOTA * const dp,
+ struct dqblk *qblk)
+{
+ ZERO_STRUCTP(qblk);
+
+ if (dp->bsize == QUOTABLOCK_SIZE) {
+ qblk->dqb_bsoftlimit = dp->softlimit;
+ qblk->dqb_bhardlimit = dp->hardlimit;
+ } else {
+ qblk->dqb_bsoftlimit = dp->softlimit * dp->bsize / QUOTABLOCK_SIZE;
+ qblk->dqb_bhardlimit = dp->hardlimit * dp->bsize / QUOTABLOCK_SIZE;
+ }
+ qblk->dqb_ihardlimit = dp->ihardlimit;
+ qblk->dqb_isoftlimit = dp->isoftlimit;
+}
+
+static int sys_quotactl_4B(const char * path, int cmd,
+ int id, struct dqblk *qblk)
+{
+ int ret;
+
+ /* NB: We must test GRPQUOTA here, because USRQUOTA is 0. */
+ DEBUG(10, ("%s quota for %s ID %u on %s\n",
+ (cmd & QCMD(Q_GETQUOTA, 0)) ? "getting" : "setting",
+ (cmd & QCMD(0, GRPQUOTA)) ? "group" : "user",
+ (unsigned)id, path));
+
+#ifdef HFS_QUOTACTL_WAR
+ become_root();
+#endif /* HFS_QUOTACTL_WAR */
+
+ ret = quotactl(path, cmd, id, qblk);
+ if (ret == -1) {
+ /* ENOTSUP means quota support is not compiled in. EINVAL
+ * means that quotas are not configured (commonly).
+ */
+ if (errno != ENOTSUP && errno != EINVAL) {
+ DEBUG(5, ("failed to %s quota for %s ID %u on %s: %s\n",
+ (cmd & QCMD(Q_GETQUOTA, 0)) ? "get" : "set",
+ (cmd & QCMD(0, GRPQUOTA)) ? "group" : "user",
+ (unsigned)id, path, strerror(errno)));
+ }
+
+#ifdef HFS_QUOTACTL_WAR
+ unbecome_root();
+#endif /* HFS_QUOTACTL_WAR */
+
+
+ return -1;
+ }
+
+#ifdef HFS_QUOTACTL_WAR
+ unbecome_root();
+#endif /* HFS_QUOTACTL_WAR */
+
+ return 0;
+}
+
+int sys_get_vfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret;
+ struct dqblk qblk;
+
+ ZERO_STRUCT(qblk);
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ /* Get quota for provided UID. */
+ ret = sys_quotactl_4B(path, QCMD(Q_GETQUOTA, USRQUOTA),
+ id.uid, &qblk);
+ break;
+ case SMB_USER_FS_QUOTA_TYPE:
+ /* Get quota for current UID. */
+ ret = sys_quotactl_4B(path, QCMD(Q_GETQUOTA, USRQUOTA),
+ geteuid(), &qblk);
+ break;
+ case SMB_GROUP_QUOTA_TYPE:
+ /* Get quota for provided GID. */
+ ret = sys_quotactl_4B(path, QCMD(Q_GETQUOTA, GRPQUOTA),
+ id.gid, &qblk);
+ break;
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ /* Get quota for current GID. */
+ ret = sys_quotactl_4B(path, QCMD(Q_GETQUOTA, GRPQUOTA),
+ getegid(), &qblk);
+ break;
+ default:
+ DEBUG(0, ("cannot get unsupported quota type: %u\n",
+ (unsigned)qtype));
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (ret == -1) {
+ return -1;
+ }
+
+ xlate_qblk_to_smb(&qblk, dp);
+ dp->qtype = qtype;
+
+ return ret;
+}
+
+int sys_set_vfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ struct dqblk qblk;
+
+ xlate_smb_to_qblk(dp, &qblk);
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ /* Set quota for provided UID. */
+ return sys_quotactl_4B(path, QCMD(Q_SETQUOTA, USRQUOTA),
+ id.uid, &qblk);
+ case SMB_USER_FS_QUOTA_TYPE:
+ /* Set quota for current UID. */
+ return sys_quotactl_4B(path, QCMD(Q_SETQUOTA, USRQUOTA),
+ geteuid(), &qblk);
+ case SMB_GROUP_QUOTA_TYPE:
+ /* Set quota for provided GID. */
+ return sys_quotactl_4B(path, QCMD(Q_SETQUOTA, GRPQUOTA),
+ id.gid, &qblk);
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ /* Set quota for current GID. */
+ return sys_quotactl_4B(path, QCMD(Q_SETQUOTA, GRPQUOTA),
+ getegid(), &qblk);
+ default:
+ DEBUG(0, ("cannot set unsupported quota type: %u\n",
+ (unsigned)qtype));
+ errno = ENOSYS;
+ return -1;
+ }
+}
+
+#endif /* HAVE_QUOTACTL_4B */
diff --git a/source3/lib/sysquotas_jfs2.c b/source3/lib/sysquotas_jfs2.c
new file mode 100644
index 0000000..999b7e0
--- /dev/null
+++ b/source3/lib/sysquotas_jfs2.c
@@ -0,0 +1,150 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * System QUOTA function wrappers for JFS2 on AIX
+
+ * Copyright (C) 2019 Bjoern Jacke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#if defined(HAVE_JFS_QUOTA_H)
+#include <jfs/quota.h>
+
+#if defined(Q_J2GETQUOTA) /* when have JFS2 */
+
+/* int quotactl(const char *path, int cmd, int id, char *addr)
+ *
+ * This is very similar to sysquotas_4B but JFS2 has different quota cmds
+ * (why?) and for some reason wants root even for querying your own quota,
+ * which seems to be an AIX bug because the docs say root is only
+ * required for querying other users' quota
+ */
+
+
+#ifdef HAVE_SYS_TYPES_H
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_JFS_QUOTA_H
+#include <jfs/quota.h>
+#endif
+
+
+static int sys_quotactl_JFS2(const char * path, int cmd,
+ int id, quota64_t *quota)
+{
+ int ret;
+
+ /* NB: We must test GRPQUOTA here, because USRQUOTA is 0. */
+ DEBUG(10, ("%s quota for %s ID %u on %s\n",
+ (cmd & QCMD(Q_J2GETQUOTA, 0)) ? "getting" : "setting",
+ (cmd & QCMD(0, GRPQUOTA)) ? "group" : "user",
+ (unsigned)id, path));
+
+ become_root();
+
+ ret = quotactl((char *) path, cmd, id, (char *) quota);
+ if (ret == -1) {
+ /* ENOTSUP means quota support is not compiled in. EINVAL
+ * means that quotas are not configured (commonly).
+ */
+ if (errno != ENOTSUP && errno != EINVAL) {
+ DEBUG(0, ("failed to %s quota for %s ID %u on %s: %s\n",
+ (cmd & QCMD(Q_J2GETQUOTA, 0)) ? "get" : "set",
+ (cmd & QCMD(0, GRPQUOTA)) ? "group" : "user",
+ (unsigned)id, path, strerror(errno)));
+ }
+ }
+
+ unbecome_root();
+
+ return ret;
+}
+
+
+int sys_get_jfs2_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret;
+ quota64_t quota;
+
+ ZERO_STRUCT(quota);
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ /* Get quota for provided UID. */
+ ret = sys_quotactl_JFS2(path, QCMD(Q_J2GETQUOTA, USRQUOTA),
+ id.uid, &quota);
+ break;
+ case SMB_USER_FS_QUOTA_TYPE:
+ /* Get quota for current UID. */
+ ret = sys_quotactl_JFS2(path, QCMD(Q_J2GETQUOTA, USRQUOTA),
+ geteuid(), &quota);
+ break;
+ case SMB_GROUP_QUOTA_TYPE:
+ /* Get quota for provided GID. */
+ ret = sys_quotactl_JFS2(path, QCMD(Q_J2GETQUOTA, GRPQUOTA),
+ id.gid, &quota);
+ break;
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ /* Get quota for current GID. */
+ ret = sys_quotactl_JFS2(path, QCMD(Q_J2GETQUOTA, GRPQUOTA),
+ getegid(), &quota);
+ break;
+ default:
+ DEBUG(0, ("cannot get unsupported quota type: %u\n",
+ (unsigned)qtype));
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (ret == -1) {
+ return -1;
+ }
+
+ dp->softlimit = quota.bsoft;
+ dp->hardlimit = quota.bhard;
+ dp->ihardlimit = quota.ihard;
+ dp->isoftlimit = quota.isoft;
+ dp->curinodes = quota.iused;
+ dp->curblocks = quota.bused;
+
+ dp->qflags = QUOTAS_ENABLED | QUOTAS_DENY_DISK;
+ dp->qtype = qtype;
+ dp->bsize = QUOTABLOCK_SIZE;
+
+ return ret;
+}
+
+int sys_set_jfs2_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ /* JFS2 supports fancy quota limit classes for setting user quota.
+ * Unfortunately, this makes them quite unmanagable for Samba.
+ */
+ DEBUG(1, ("cannot set quota on JFS2!\n"));
+ errno = ENOSYS;
+ return -1;
+}
+
+
+#endif /* JFS2 */
+#endif /* AIX quotas */
diff --git a/source3/lib/sysquotas_linux.c b/source3/lib/sysquotas_linux.c
new file mode 100644
index 0000000..4415f51
--- /dev/null
+++ b/source3/lib/sysquotas_linux.c
@@ -0,0 +1,220 @@
+/*
+ Unix SMB/CIFS implementation.
+ System QUOTA function wrappers for LINUX
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifndef HAVE_SYS_QUOTAS
+#ifdef HAVE_QUOTACTL_LINUX
+#undef HAVE_QUOTACTL_LINUX
+#endif
+#endif
+
+#ifdef HAVE_QUOTACTL_LINUX
+
+#include <sys/quota.h>
+
+/****************************************************************************
+ Linux quota get calls.
+****************************************************************************/
+int sys_get_vfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ uint32_t qflags = 0;
+ struct dqblk D;
+ uint64_t bsize = (uint64_t)QUOTABLOCK_SIZE;
+
+ if (!path || !bdev || !dp) {
+ smb_panic("sys_get_vfs_quota: called with NULL pointer");
+ }
+
+ ZERO_STRUCT(*dp);
+ dp->qtype = qtype;
+
+ ZERO_STRUCT(D);
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ DEBUG(10, ("sys_get_vfs_quota: path[%s] bdev[%s] "
+ "SMB_USER_QUOTA_TYPE uid[%u]\n",
+ path, bdev, (unsigned)id.uid));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA, USRQUOTA), bdev,
+ id.uid, (caddr_t)&D))) {
+ return ret;
+ }
+
+ break;
+ case SMB_GROUP_QUOTA_TYPE:
+ DEBUG(10, ("sys_get_vfs_quota: path[%s] bdev[%s] "
+ "SMB_GROUP_QUOTA_TYPE gid[%u]\n",
+ path, bdev, (unsigned)id.gid));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), bdev,
+ id.gid, (caddr_t)&D))) {
+ return ret;
+ }
+
+ break;
+ case SMB_USER_FS_QUOTA_TYPE:
+ DEBUG(10, ("sys_get_vfs_quota: path[%s] bdev[%s] "
+ "SMB_USER_FS_QUOTA_TYPE (uid[%u])\n",
+ path, bdev, (unsigned)geteuid()));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA, USRQUOTA), bdev,
+ geteuid(), (caddr_t)&D)) == 0) {
+ qflags |= QUOTAS_DENY_DISK;
+ }
+
+ ret = 0;
+
+ break;
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ DEBUG(10, ("sys_get_vfs_quota: path[%s] bdev[%s] "
+ "SMB_GROUP_FS_QUOTA_TYPE (gid[%u])\n",
+ path, bdev, (unsigned)getegid()));
+
+ if ((ret = quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), bdev,
+ getegid(), (caddr_t)&D)) == 0) {
+ qflags |= QUOTAS_DENY_DISK;
+ }
+
+ ret = 0;
+ break;
+ default:
+ errno = ENOSYS;
+ return -1;
+ }
+
+ dp->bsize = bsize;
+ dp->softlimit = (uint64_t)D.dqb_bsoftlimit;
+ dp->hardlimit = (uint64_t)D.dqb_bhardlimit;
+ dp->ihardlimit = (uint64_t)D.dqb_ihardlimit;
+ dp->isoftlimit = (uint64_t)D.dqb_isoftlimit;
+ dp->curinodes = (uint64_t)D.dqb_curinodes;
+ dp->curblocks = (uint64_t)D.dqb_curspace/bsize;
+
+
+ dp->qflags = qflags;
+
+ return ret;
+}
+
+/****************************************************************************
+ Linux quota set calls.
+****************************************************************************/
+int sys_set_vfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ struct dqblk D;
+ uint64_t bsize = (uint64_t)QUOTABLOCK_SIZE;
+ bool cur_enf, new_enf;
+
+ if (!path || !bdev || !dp) {
+ smb_panic("sys_set_vfs_quota: called with NULL pointer");
+ }
+
+ ZERO_STRUCT(D);
+
+ if (bsize == dp->bsize) {
+ D.dqb_bsoftlimit = dp->softlimit;
+ D.dqb_bhardlimit = dp->hardlimit;
+ } else {
+ D.dqb_bsoftlimit = (dp->softlimit*dp->bsize)/bsize;
+ D.dqb_bhardlimit = (dp->hardlimit*dp->bsize)/bsize;
+ }
+ D.dqb_ihardlimit = dp->ihardlimit;
+ D.dqb_isoftlimit = dp->isoftlimit;
+ D.dqb_valid = QIF_LIMITS;
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ DEBUG(10, ("sys_set_vfs_quota: path[%s] bdev[%s] "
+ "SMB_USER_QUOTA_TYPE uid[%u]\n",
+ path, bdev, (unsigned)id.uid));
+
+ ret = quotactl(QCMD(Q_SETQUOTA,USRQUOTA), bdev, id.uid, (caddr_t)&D);
+ break;
+ case SMB_GROUP_QUOTA_TYPE:
+ DEBUG(10, ("sys_set_vfs_quota: path[%s] bdev[%s] "
+ "SMB_GROUP_QUOTA_TYPE gid[%u]\n",
+ path, bdev, (unsigned)id.gid));
+
+ ret = quotactl(QCMD(Q_SETQUOTA,GRPQUOTA), bdev, id.gid, (caddr_t)&D);
+ break;
+ case SMB_USER_FS_QUOTA_TYPE:
+ DEBUG(10, ("sys_set_vfs_quota: path[%s] bdev[%s] "
+ "SMB_USER_FS_QUOTA_TYPE (uid[%u])\n",
+ path, bdev, (unsigned)geteuid()));
+
+ ret = quotactl(QCMD(Q_GETQUOTA, USRQUOTA), bdev,
+ geteuid(), (caddr_t)&D);
+ cur_enf = (ret == 0);
+ new_enf = ((dp->qflags & QUOTAS_DENY_DISK) != 0);
+ /* We're not changing quota enforcement, so return
+ * success
+ * IFF the wanted state is identical to the current
+ * state */
+ if (cur_enf == new_enf) {
+ ret = 0;
+ } else {
+ errno = EPERM;
+ ret = -1;
+ }
+
+ break;
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ DEBUG(10, ("sys_set_vfs_quota: path[%s] bdev[%s] "
+ "SMB_GROUP_FS_QUOTA_TYPE (gid[%u])\n",
+ path, bdev, (unsigned)getegid()));
+
+ ret = quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), bdev,
+ getegid(), (caddr_t)&D);
+ cur_enf = (ret == 0);
+ new_enf = ((dp->qflags & QUOTAS_DENY_DISK) != 0);
+ /* We're not changing quota enforcement, so return
+ * success
+ * IFF the wanted state is identical to the current
+ * state */
+ if (cur_enf == new_enf) {
+ ret = 0;
+ } else {
+ errno = EPERM;
+ ret = -1;
+ }
+
+ break;
+ default:
+ errno = ENOSYS;
+ return -1;
+ }
+
+ return ret;
+}
+
+#else /* HAVE_QUOTACTL_LINUX */
+ void dummy_sysquotas_linux(void);
+
+ void dummy_sysquotas_linux(void){}
+#endif /* HAVE_QUOTACTL_LINUX */
diff --git a/source3/lib/sysquotas_nfs.c b/source3/lib/sysquotas_nfs.c
new file mode 100644
index 0000000..14dcb02
--- /dev/null
+++ b/source3/lib/sysquotas_nfs.c
@@ -0,0 +1,303 @@
+/*
+ Unix SMB/CIFS implementation.
+ System QUOTA function wrappers for NFS
+ Copyright (C) Michael Adam 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifndef HAVE_SYS_QUOTAS
+#ifdef HAVE_NFS_QUOTAS
+#undef HAVE_NFS_QUOTAS
+#endif
+#endif
+
+#ifdef HAVE_NFS_QUOTAS
+
+/*
+ * nfs quota support
+ * This is based on the FreeBSD / SUNOS5 section of quotas.c
+ */
+
+/* <rpc/xdr.h> uses TRUE and FALSE */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#include <rpc/rpc.h>
+#include <rpc/types.h>
+#include <rpc/xdr.h>
+#include <rpcsvc/rquota.h>
+#ifdef HAVE_RPC_NETTYPE_H
+#include <rpc/nettype.h>
+#endif
+
+#ifndef RQ_PATHLEN
+#define RQ_PATHLEN 1024
+#endif
+
+#ifdef HAVE_GETQUOTA_RSLT_GETQUOTA_RSLT_U
+#define GQR_RQUOTA getquota_rslt_u.gqr_rquota
+#define GQR_STATUS status
+#else
+#define GQR_RQUOTA gqr_rquota
+#define GQR_STATUS gqr_status
+#endif
+
+static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args)
+{
+ if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN ))
+ return(0);
+ if (!xdr_int(xdrsp, &args->gqa_uid))
+ return(0);
+ return (1);
+}
+
+static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr)
+{
+ int quotastat;
+
+ if (!xdr_int(xdrsp, &quotastat)) {
+ DEBUG(6,("nfs_quotas: Status bad or zero\n"));
+ return 0;
+ }
+ gqr->GQR_STATUS = quotastat;
+
+ if (!xdr_int(xdrsp, &gqr->GQR_RQUOTA.rq_bsize)) {
+ DEBUG(6,("nfs_quotas: Block size bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_bool(xdrsp, &gqr->GQR_RQUOTA.rq_active)) {
+ DEBUG(6,("nfs_quotas: Active bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, (int *)&gqr->GQR_RQUOTA.rq_bhardlimit)) {
+ DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, (int *)&gqr->GQR_RQUOTA.rq_bsoftlimit)) {
+ DEBUG(6,("nfs_quotas: Softlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, (int *)&gqr->GQR_RQUOTA.rq_curblocks)) {
+ DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n"));
+ return 0;
+ }
+ return (1);
+}
+
+
+int sys_get_nfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id, SMB_DISK_QUOTA *dp)
+{
+ CLIENT *clnt = NULL;
+ struct getquota_rslt gq_rslt;
+ struct getquota_args gq_args;
+ const char *mnttype;
+ char *cutstr, *host, *testpath;
+ int len;
+ static struct timeval timeout = {2,0};
+ enum clnt_stat clnt_stat;
+
+ int ret = -1;
+ uint32_t qflags = 0;
+
+ if (!path || !bdev || !dp) {
+ smb_panic("sys_get_nfs_quota: called with NULL pointer");
+ }
+
+ DEBUG(10, ("sys_get_nfs_quota: path[%s] bdev[%s] qtype[%d]\n",
+ path, bdev, qtype));
+
+ ZERO_STRUCT(*dp);
+
+ dp->qtype = qtype;
+
+ if (qtype != SMB_USER_QUOTA_TYPE) {
+ DEBUG(3, ("sys_get_nfs_quota: got unsupported quota type '%d', "
+ "only supported type is '%d' (SMB_USER_QUOTA_TYPE)\n",
+ qtype, SMB_USER_QUOTA_TYPE));
+ errno = ENOSYS;
+ return -1;
+ }
+
+ mnttype = bdev;
+ len = strcspn(mnttype, ":");
+ cutstr = (char *) SMB_MALLOC(len+1);
+ if (cutstr == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ memset(cutstr, '\0', len+1);
+ host = strncat(cutstr, mnttype, sizeof(char) * len);
+ testpath = strchr_m(mnttype, ':');
+ if (testpath == NULL) {
+ errno = EINVAL;
+ goto out;
+ }
+ testpath++;
+ gq_args.gqa_pathp = testpath;
+ gq_args.gqa_uid = id.uid;
+
+ DEBUG(10, ("sys_get_nfs_quotas: Asking for quota of path '%s' on "
+ "host '%s', rpcprog '%i', rpcvers '%i', network '%s'\n",
+ host, testpath+1, (int)RQUOTAPROG, (int)RQUOTAVERS, "udp"));
+
+ clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp");
+ if (clnt == NULL) {
+ ret = -1;
+ goto out;
+ }
+
+ clnt->cl_auth = authunix_create_default();
+ if (clnt->cl_auth == NULL) {
+ DEBUG(3, ("sys_get_nfs_quotas: authunix_create_default "
+ "failed\n"));
+ ret = -1;
+ goto out;
+ }
+
+ clnt_stat = clnt_call(clnt,
+ RQUOTAPROC_GETQUOTA,
+ (const xdrproc_t) my_xdr_getquota_args,
+ (caddr_t)&gq_args,
+ (const xdrproc_t) my_xdr_getquota_rslt,
+ (caddr_t)&gq_rslt,
+ timeout);
+
+ if (clnt_stat != RPC_SUCCESS) {
+ if (errno == ECONNREFUSED) {
+ /* If we cannot connect with rpc.quotad, it may
+ * simply be because there's no quota on the remote
+ * system
+ */
+ DBG_INFO("clnt_call failed with ECONNREFUSED - "
+ "assuming no quotas on server\n");
+ ret = 0;
+ } else {
+ int save_errno = errno;
+ DBG_NOTICE("clnt_call failed - %s\n", strerror(errno));
+ errno = save_errno;
+ ret = -1;
+ }
+ goto out;
+ }
+
+ DEBUG(10, ("sys_get_nfs_quotas: getquota_rslt:\n"
+ "status : '%i'\n"
+ "bsize : '%i'\n"
+ "active : '%s'\n"
+ "bhardlimit : '%u'\n"
+ "bsoftlimit : '%u'\n"
+ "curblocks : '%u'\n"
+ "fhardlimit : '%u'\n"
+ "fsoftlimit : '%u'\n"
+ "curfiles : '%u'\n"
+ "btimeleft : '%u'\n"
+ "ftimeleft : '%u'\n",
+ gq_rslt.GQR_STATUS,
+ gq_rslt.GQR_RQUOTA.rq_bsize,
+ gq_rslt.GQR_RQUOTA.rq_active?"yes":"no",
+ gq_rslt.GQR_RQUOTA.rq_bhardlimit,
+ gq_rslt.GQR_RQUOTA.rq_bsoftlimit,
+ gq_rslt.GQR_RQUOTA.rq_curblocks,
+ gq_rslt.GQR_RQUOTA.rq_fhardlimit,
+ gq_rslt.GQR_RQUOTA.rq_fsoftlimit,
+ gq_rslt.GQR_RQUOTA.rq_curfiles,
+ gq_rslt.GQR_RQUOTA.rq_btimeleft,
+ gq_rslt.GQR_RQUOTA.rq_ftimeleft));
+
+ /*
+ * gqr.status returns
+ * 1 if quotas exist,
+ * 2 if there is no quota set, and
+ * 3 if no permission to get the quota.
+ */
+
+ switch (gq_rslt.GQR_STATUS) {
+ case 1:
+ DEBUG(10, ("sys_get_nfs_quotas: Good quota data\n"));
+ dp->bsize = (uint64_t)gq_rslt.GQR_RQUOTA.rq_bsize;
+ dp->softlimit = gq_rslt.GQR_RQUOTA.rq_bsoftlimit;
+ dp->hardlimit = gq_rslt.GQR_RQUOTA.rq_bhardlimit;
+ dp->curblocks = gq_rslt.GQR_RQUOTA.rq_curblocks;
+ dp->isoftlimit = gq_rslt.GQR_RQUOTA.rq_fsoftlimit;
+ dp->ihardlimit = gq_rslt.GQR_RQUOTA.rq_fhardlimit;
+ dp->curinodes = gq_rslt.GQR_RQUOTA.rq_curfiles;
+ break;
+
+ case 2:
+ DEBUG(5, ("sys_get_nfs_quotas: No quota set\n"));
+ SMB_QUOTAS_SET_NO_LIMIT(dp);
+ break;
+
+ case 3:
+ DEBUG(3, ("sys_get_nfs_quotas: no permission to get quota\n"));
+ errno = EPERM;
+ ret = -1;
+ goto out;
+
+ default:
+ DEBUG(5, ("sys_get_nfs_quotas: Unknown remote quota status "
+ "code '%i'\n", gq_rslt.GQR_STATUS));
+ ret = -1;
+ goto out;
+ break;
+ }
+
+ dp->qflags = qflags;
+
+ ret = 0;
+
+out:
+ if (clnt) {
+ if (clnt->cl_auth) {
+ auth_destroy(clnt->cl_auth);
+ }
+ clnt_destroy(clnt);
+ }
+
+ SAFE_FREE(cutstr);
+
+ DEBUG(10, ("sys_get_nfs_quotas: finished\n" ));
+ return ret;
+}
+
+int sys_set_nfs_quota(const char *path, const char *bdev,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id, SMB_DISK_QUOTA *dp)
+{
+ DEBUG(1, ("sys_set_nfs_quota : not supported\n"));
+ errno = ENOSYS;
+ return -1;
+}
+
+#else /* HAVE_NFS_QUOTAS */
+
+void dummy_sysquotas_nfs(void);
+void dummy_sysquotas_nfs(void) {}
+
+#endif /* HAVE_NFS_QUOTAS */
diff --git a/source3/lib/sysquotas_xfs.c b/source3/lib/sysquotas_xfs.c
new file mode 100644
index 0000000..6b18487
--- /dev/null
+++ b/source3/lib/sysquotas_xfs.c
@@ -0,0 +1,345 @@
+/*
+ Unix SMB/CIFS implementation.
+ System QUOTA function wrappers for XFS
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#if defined(HAVE_SYS_QUOTAS) && defined(HAVE_XFS_QUOTAS)
+
+#ifdef HAVE_SYS_QUOTA_H
+#include <sys/quota.h>
+#endif
+
+/* this one should actually come from glibc: */
+/* #include "samba_linux_quota.h" */
+
+#ifdef HAVE_XFS_XQM_H
+#include <xfs/xqm.h>
+#endif
+
+#define HAVE_GROUP_QUOTA
+
+/* on IRIX */
+#ifndef Q_XQUOTAON
+#define Q_XQUOTAON Q_QUOTAON
+#endif /* Q_XQUOTAON */
+#ifndef Q_XQUOTAOFF
+#define Q_XQUOTAOFF Q_QUOTAOFF
+#endif /* Q_XQUOTAOFF */
+#ifndef Q_XGETQSTAT
+#define Q_XGETQSTAT Q_GETQSTAT
+#endif /* Q_XGETQSTAT */
+
+/* currently doesn't support Group and Project quotas on IRIX
+ */
+
+#ifndef QCMD
+#define QCMD(x,y) x
+#endif
+
+/*
+ * IRIX has BBSIZE in <sys/param.h>
+ */
+#ifndef BBSHIFT
+#define BBSHIFT 9
+#endif /* BBSHIFT */
+#ifndef BBSIZE
+#define BBSIZE (1<<BBSHIFT)
+#endif /* BBSIZE */
+
+/****************************************************************************
+ Abstract out the XFS Quota Manager quota get call.
+****************************************************************************/
+int sys_get_xfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ uint32_t qflags = 0;
+ uint64_t bsize = (uint64_t)BBSIZE;
+ struct fs_disk_quota D;
+ struct fs_quota_stat F;
+ ZERO_STRUCT(D);
+ ZERO_STRUCT(F);
+
+ if (!bdev||!dp)
+ smb_panic("sys_get_xfs_quota: called with NULL pointer");
+
+ ZERO_STRUCT(*dp);
+ dp->qtype = qtype;
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ DEBUG(10,("sys_get_xfs_quota: path[%s] bdev[%s] SMB_USER_QUOTA_TYPE uid[%u]\n",
+ path, bdev, (unsigned)id.uid));
+
+ ret=quotactl(QCMD(Q_XGETQUOTA,USRQUOTA), bdev, id.uid, (caddr_t)&D);
+ /* XFS fails with ENOENT if the user has no
+ * quota. Our protocol in that case is to
+ * succeed and return 0 as quota.
+ */
+ if (ret != 0 && errno != ENOENT) {
+ return ret;
+ }
+
+ ret = 0;
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_QUOTA_TYPE:
+ DEBUG(10,("sys_get_xfs_quota: path[%s] bdev[%s] SMB_GROUP_QUOTA_TYPE gid[%u]\n",
+ path, bdev, (unsigned)id.gid));
+
+ ret=quotactl(QCMD(Q_XGETQUOTA,GRPQUOTA), bdev, id.gid, (caddr_t)&D);
+ /* XFS fails with ENOENT if the user has no
+ * quota. Our protocol in that case is to
+ * succeed and return 0 as quota.
+ */
+ if (ret != 0 && errno != ENOENT) {
+ return ret;
+ }
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ case SMB_USER_FS_QUOTA_TYPE:
+ DEBUG(10,("sys_get_xfs_quota: path[%s] bdev[%s] SMB_USER_FS_QUOTA_TYPE (uid[%u])\n",
+ path, bdev, (unsigned)id.uid));
+
+ quotactl(QCMD(Q_XGETQSTAT,USRQUOTA), bdev, -1, (caddr_t)&F);
+
+ if (F.qs_flags & XFS_QUOTA_UDQ_ENFD) {
+ qflags |= QUOTAS_DENY_DISK;
+ }
+ else if (F.qs_flags & XFS_QUOTA_UDQ_ACCT) {
+ qflags |= QUOTAS_ENABLED;
+ }
+
+ ret = 0;
+
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ DEBUG(10,("sys_get_xfs_quota: path[%s] bdev[%s] SMB_GROUP_FS_QUOTA_TYPE (gid[%u])\n",
+ path, bdev, (unsigned)id.gid));
+
+ quotactl(QCMD(Q_XGETQSTAT,GRPQUOTA), bdev, -1, (caddr_t)&F);
+
+ if (F.qs_flags & XFS_QUOTA_GDQ_ENFD) {
+ qflags |= QUOTAS_DENY_DISK;
+ }
+ else if (F.qs_flags & XFS_QUOTA_GDQ_ACCT) {
+ qflags |= QUOTAS_ENABLED;
+ }
+
+ ret = 0;
+
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ default:
+ errno = ENOSYS;
+ return -1;
+ }
+
+ dp->bsize = bsize;
+ dp->softlimit = (uint64_t)D.d_blk_softlimit;
+ dp->hardlimit = (uint64_t)D.d_blk_hardlimit;
+ dp->ihardlimit = (uint64_t)D.d_ino_hardlimit;
+ dp->isoftlimit = (uint64_t)D.d_ino_softlimit;
+ dp->curinodes = (uint64_t)D.d_icount;
+ dp->curblocks = (uint64_t)D.d_bcount;
+ dp->qflags = qflags;
+
+ return ret;
+}
+
+/****************************************************************************
+ Abstract out the XFS Quota Manager quota set call.
+****************************************************************************/
+int sys_set_xfs_quota(const char *path, const char *bdev, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dp)
+{
+ int ret = -1;
+ uint32_t qflags = 0;
+ uint64_t bsize = (uint64_t)BBSIZE;
+ struct fs_disk_quota D;
+ struct fs_quota_stat F;
+ int q_on = 0;
+ int q_off = 0;
+ ZERO_STRUCT(D);
+ ZERO_STRUCT(F);
+
+ if (!bdev||!dp)
+ smb_panic("sys_set_xfs_quota: called with NULL pointer");
+
+ if (bsize == dp->bsize) {
+ D.d_blk_softlimit = dp->softlimit;
+ D.d_blk_hardlimit = dp->hardlimit;
+ } else {
+ D.d_blk_softlimit = (dp->softlimit*dp->bsize)/bsize;
+ D.d_blk_hardlimit = (dp->hardlimit*dp->bsize)/bsize;
+ }
+ D.d_ino_hardlimit = dp->ihardlimit;
+ D.d_ino_softlimit = dp->isoftlimit;
+
+ qflags = dp->qflags;
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ DEBUG(10,("sys_set_xfs_quota: path[%s] bdev[%s] SMB_USER_QUOTA_TYPE uid[%u]\n",
+ path, bdev, (unsigned)id.uid));
+
+ D.d_fieldmask |= FS_DQ_LIMIT_MASK;
+ ret = quotactl(QCMD(Q_XSETQLIM,USRQUOTA), bdev, id.uid, (caddr_t)&D);
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_QUOTA_TYPE:
+ DEBUG(10,("sys_set_xfs_quota: path[%s] bdev[%s] SMB_GROUP_QUOTA_TYPE gid[%u]\n",
+ path, bdev, (unsigned)id.gid));
+
+ D.d_fieldmask |= FS_DQ_LIMIT_MASK;
+ ret = quotactl(QCMD(Q_XSETQLIM,GRPQUOTA), bdev, id.gid, (caddr_t)&D);
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ case SMB_USER_FS_QUOTA_TYPE:
+ DEBUG(10,("sys_set_xfs_quota: path[%s] bdev[%s] SMB_USER_FS_QUOTA_TYPE (uid[%u])\n",
+ path, bdev, (unsigned)id.uid));
+
+ quotactl(QCMD(Q_XGETQSTAT,USRQUOTA), bdev, -1, (caddr_t)&F);
+
+ if (qflags & QUOTAS_DENY_DISK) {
+ if (!(F.qs_flags & XFS_QUOTA_UDQ_ENFD))
+ q_on |= XFS_QUOTA_UDQ_ENFD;
+ if (!(F.qs_flags & XFS_QUOTA_UDQ_ACCT))
+ q_on |= XFS_QUOTA_UDQ_ACCT;
+
+ if (q_on != 0) {
+ ret = quotactl(QCMD(Q_XQUOTAON,USRQUOTA),bdev, -1, (caddr_t)&q_on);
+ } else {
+ ret = 0;
+ }
+
+ } else if (qflags & QUOTAS_ENABLED) {
+ if (F.qs_flags & XFS_QUOTA_UDQ_ENFD)
+ q_off |= XFS_QUOTA_UDQ_ENFD;
+
+ if (q_off != 0) {
+ ret = quotactl(QCMD(Q_XQUOTAOFF,USRQUOTA),bdev, -1, (caddr_t)&q_off);
+ } else {
+ ret = 0;
+ }
+
+ if (!(F.qs_flags & XFS_QUOTA_UDQ_ACCT))
+ q_on |= XFS_QUOTA_UDQ_ACCT;
+
+ if (q_on != 0) {
+ ret = quotactl(QCMD(Q_XQUOTAON,USRQUOTA),bdev, -1, (caddr_t)&q_on);
+ } else {
+ ret = 0;
+ }
+ } else {
+#if 0
+ /* Switch on XFS_QUOTA_UDQ_ACCT didn't work!
+ * only swittching off XFS_QUOTA_UDQ_ACCT work
+ */
+ if (F.qs_flags & XFS_QUOTA_UDQ_ENFD)
+ q_off |= XFS_QUOTA_UDQ_ENFD;
+ if (F.qs_flags & XFS_QUOTA_UDQ_ACCT)
+ q_off |= XFS_QUOTA_UDQ_ACCT;
+
+ if (q_off !=0) {
+ ret = quotactl(QCMD(Q_XQUOTAOFF,USRQUOTA),bdev, -1, (caddr_t)&q_off);
+ } else {
+ ret = 0;
+ }
+#else
+ ret = -1;
+#endif
+ }
+
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ DEBUG(10,("sys_set_xfs_quota: path[%s] bdev[%s] SMB_GROUP_FS_QUOTA_TYPE (gid[%u])\n",
+ path, bdev, (unsigned)id.gid));
+
+ quotactl(QCMD(Q_XGETQSTAT,GRPQUOTA), bdev, -1, (caddr_t)&F);
+
+ if (qflags & QUOTAS_DENY_DISK) {
+ if (!(F.qs_flags & XFS_QUOTA_GDQ_ENFD))
+ q_on |= XFS_QUOTA_GDQ_ENFD;
+ if (!(F.qs_flags & XFS_QUOTA_GDQ_ACCT))
+ q_on |= XFS_QUOTA_GDQ_ACCT;
+
+ if (q_on != 0) {
+ ret = quotactl(QCMD(Q_XQUOTAON,GRPQUOTA),bdev, -1, (caddr_t)&q_on);
+ } else {
+ ret = 0;
+ }
+
+ } else if (qflags & QUOTAS_ENABLED) {
+ if (F.qs_flags & XFS_QUOTA_GDQ_ENFD)
+ q_off |= XFS_QUOTA_GDQ_ENFD;
+
+ if (q_off != 0) {
+ ret = quotactl(QCMD(Q_XQUOTAOFF,GRPQUOTA),bdev, -1, (caddr_t)&q_off);
+ } else {
+ ret = 0;
+ }
+
+ if (!(F.qs_flags & XFS_QUOTA_GDQ_ACCT))
+ q_on |= XFS_QUOTA_GDQ_ACCT;
+
+ if (q_on != 0) {
+ ret = quotactl(QCMD(Q_XQUOTAON,GRPQUOTA),bdev, -1, (caddr_t)&q_on);
+ } else {
+ ret = 0;
+ }
+ } else {
+#if 0
+ /* Switch on XFS_QUOTA_UDQ_ACCT didn't work!
+ * only swittching off XFS_QUOTA_UDQ_ACCT work
+ */
+ if (F.qs_flags & XFS_QUOTA_GDQ_ENFD)
+ q_off |= XFS_QUOTA_GDQ_ENFD;
+ if (F.qs_flags & XFS_QUOTA_GDQ_ACCT)
+ q_off |= XFS_QUOTA_GDQ_ACCT;
+
+ if (q_off !=0) {
+ ret = quotactl(QCMD(Q_XQUOTAOFF,GRPQUOTA),bdev, -1, (caddr_t)&q_off);
+ } else {
+ ret = 0;
+ }
+#else
+ ret = -1;
+#endif
+ }
+
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ default:
+ errno = ENOSYS;
+ return -1;
+ }
+
+ return ret;
+}
+
+#else /* HAVE_XFS_QUOTAS */
+ void dummy_sysquotas_xfs(void);
+
+ void dummy_sysquotas_xfs(void){}
+#endif /* HAVE_XFS_QUOTAS */
diff --git a/source3/lib/system.c b/source3/lib/system.c
new file mode 100644
index 0000000..1ec0ae9
--- /dev/null
+++ b/source3/lib/system.c
@@ -0,0 +1,1076 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba system utilities
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998-2005
+ Copyright (C) Timur Bakeyev 2005
+ Copyright (C) Bjoern Jacke 2006-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/syslog.h"
+#include "system/capability.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "lib/util/setid.h"
+#include "lib/util/time.h"
+
+#ifdef HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+/*
+ The idea is that this file will eventually have wrappers around all
+ important system calls in samba. The aims are:
+
+ - to enable easier porting by putting OS dependent stuff in here
+
+ - to allow for hooks into other "pseudo-filesystems"
+
+ - to allow easier integration of things like the japanese extensions
+
+ - to support the philosophy of Samba to expose the features of
+ the OS within the SMB model. In general whatever file/printer/variable
+ expansions/etc make sense to the OS should be acceptable to Samba.
+*/
+
+/*******************************************************************
+A send wrapper that will deal with EINTR or EAGAIN or EWOULDBLOCK.
+********************************************************************/
+
+ssize_t sys_send(int s, const void *msg, size_t len, int flags)
+{
+ ssize_t ret;
+
+ do {
+ ret = send(s, msg, len, flags);
+ } while (ret == -1 && (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK));
+
+ return ret;
+}
+
+/*******************************************************************
+A recvfrom wrapper that will deal with EINTR.
+NB. As used with non-blocking sockets, return on EAGAIN/EWOULDBLOCK
+********************************************************************/
+
+ssize_t sys_recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen)
+{
+ ssize_t ret;
+
+ do {
+ ret = recvfrom(s, buf, len, flags, from, fromlen);
+ } while (ret == -1 && (errno == EINTR));
+ return ret;
+}
+
+/*******************************************************************
+A fcntl wrapper that will deal with EINTR.
+********************************************************************/
+
+int sys_fcntl_ptr(int fd, int cmd, void *arg)
+{
+ int ret;
+
+ do {
+ ret = fcntl(fd, cmd, arg);
+ } while (ret == -1 && errno == EINTR);
+ return ret;
+}
+
+/*******************************************************************
+A fcntl wrapper that will deal with EINTR.
+********************************************************************/
+
+int sys_fcntl_long(int fd, int cmd, long arg)
+{
+ int ret;
+
+ do {
+ ret = fcntl(fd, cmd, arg);
+ } while (ret == -1 && errno == EINTR);
+ return ret;
+}
+
+/*******************************************************************
+A fcntl wrapper that will deal with EINTR.
+********************************************************************/
+
+int sys_fcntl_int(int fd, int cmd, int arg)
+{
+ int ret;
+
+ do {
+ ret = fcntl(fd, cmd, arg);
+ } while (ret == -1 && errno == EINTR);
+ return ret;
+}
+
+/****************************************************************************
+ Return the best approximation to a 'create time' under UNIX from a stat
+ structure.
+****************************************************************************/
+
+static struct timespec calc_create_time_stat(const struct stat *st)
+{
+ struct timespec ret, ret1;
+ struct timespec c_time = get_ctimespec(st);
+ struct timespec m_time = get_mtimespec(st);
+ struct timespec a_time = get_atimespec(st);
+
+ ret = timespec_compare(&c_time, &m_time) < 0 ? c_time : m_time;
+ ret1 = timespec_compare(&ret, &a_time) < 0 ? ret : a_time;
+
+ if(!null_timespec(ret1)) {
+ return ret1;
+ }
+
+ /*
+ * One of ctime, mtime or atime was zero (probably atime).
+ * Just return MIN(ctime, mtime).
+ */
+ return ret;
+}
+
+/****************************************************************************
+ Return the best approximation to a 'create time' under UNIX from a stat_ex
+ structure.
+****************************************************************************/
+
+static struct timespec calc_create_time_stat_ex(const struct stat_ex *st)
+{
+ struct timespec ret, ret1;
+ struct timespec c_time = st->st_ex_ctime;
+ struct timespec m_time = st->st_ex_mtime;
+ struct timespec a_time = st->st_ex_atime;
+
+ ret = timespec_compare(&c_time, &m_time) < 0 ? c_time : m_time;
+ ret1 = timespec_compare(&ret, &a_time) < 0 ? ret : a_time;
+
+ if(!null_timespec(ret1)) {
+ return ret1;
+ }
+
+ /*
+ * One of ctime, mtime or atime was zero (probably atime).
+ * Just return MIN(ctime, mtime).
+ */
+ return ret;
+}
+
+/****************************************************************************
+ Return the 'create time' from a stat struct if it exists (birthtime) or else
+ use the best approximation.
+****************************************************************************/
+
+static void make_create_timespec(const struct stat *pst, struct stat_ex *dst,
+ bool fake_dir_create_times)
+{
+ if (S_ISDIR(pst->st_mode) && fake_dir_create_times) {
+ dst->st_ex_btime.tv_sec = 315493200L; /* 1/1/1980 */
+ dst->st_ex_btime.tv_nsec = 0;
+ return;
+ }
+
+ dst->st_ex_iflags &= ~ST_EX_IFLAG_CALCULATED_BTIME;
+
+#if defined(HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC)
+ dst->st_ex_btime = pst->st_birthtimespec;
+#elif defined(HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC)
+ dst->st_ex_btime.tv_sec = pst->st_birthtime;
+ dst->st_ex_btime.tv_nsec = pst->st_birthtimenspec;
+#elif defined(HAVE_STRUCT_STAT_ST_BIRTHTIME)
+ dst->st_ex_btime.tv_sec = pst->st_birthtime;
+ dst->st_ex_btime.tv_nsec = 0;
+#else
+ dst->st_ex_btime = calc_create_time_stat(pst);
+ dst->st_ex_iflags |= ST_EX_IFLAG_CALCULATED_BTIME;
+#endif
+
+ /* Deal with systems that don't initialize birthtime correctly.
+ * Pointed out by SATOH Fumiyasu <fumiyas@osstech.jp>.
+ */
+ if (null_timespec(dst->st_ex_btime)) {
+ dst->st_ex_btime = calc_create_time_stat(pst);
+ dst->st_ex_iflags |= ST_EX_IFLAG_CALCULATED_BTIME;
+ }
+}
+
+/****************************************************************************
+ If we update a timestamp in a stat_ex struct we may have to recalculate
+ the birthtime. For now only implement this for write time, but we may
+ also need to do it for atime and ctime. JRA.
+****************************************************************************/
+
+void update_stat_ex_mtime(struct stat_ex *dst,
+ struct timespec write_ts)
+{
+ dst->st_ex_mtime = write_ts;
+
+ /* We may have to recalculate btime. */
+ if (dst->st_ex_iflags & ST_EX_IFLAG_CALCULATED_BTIME) {
+ dst->st_ex_btime = calc_create_time_stat_ex(dst);
+ }
+}
+
+void update_stat_ex_create_time(struct stat_ex *dst,
+ struct timespec create_time)
+{
+ dst->st_ex_btime = create_time;
+ dst->st_ex_iflags &= ~ST_EX_IFLAG_CALCULATED_BTIME;
+}
+
+void update_stat_ex_from_saved_stat(struct stat_ex *dst,
+ const struct stat_ex *src)
+{
+ if (!VALID_STAT(*src)) {
+ return;
+ }
+
+ if (!(src->st_ex_iflags & ST_EX_IFLAG_CALCULATED_BTIME)) {
+ update_stat_ex_create_time(dst, src->st_ex_btime);
+ }
+}
+
+void init_stat_ex_from_stat (struct stat_ex *dst,
+ const struct stat *src,
+ bool fake_dir_create_times)
+{
+ dst->st_ex_dev = src->st_dev;
+ dst->st_ex_ino = src->st_ino;
+ dst->st_ex_mode = src->st_mode;
+ dst->st_ex_nlink = src->st_nlink;
+ dst->st_ex_uid = src->st_uid;
+ dst->st_ex_gid = src->st_gid;
+ dst->st_ex_rdev = src->st_rdev;
+ dst->st_ex_size = src->st_size;
+ dst->st_ex_atime = get_atimespec(src);
+ dst->st_ex_mtime = get_mtimespec(src);
+ dst->st_ex_ctime = get_ctimespec(src);
+ dst->st_ex_iflags = 0;
+ make_create_timespec(src, dst, fake_dir_create_times);
+#ifdef HAVE_STAT_ST_BLKSIZE
+ dst->st_ex_blksize = src->st_blksize;
+#else
+ dst->st_ex_blksize = STAT_ST_BLOCKSIZE;
+#endif
+
+#ifdef HAVE_STAT_ST_BLOCKS
+ dst->st_ex_blocks = src->st_blocks;
+#else
+ dst->st_ex_blocks = src->st_size / dst->st_ex_blksize + 1;
+#endif
+
+#ifdef HAVE_STAT_ST_FLAGS
+ dst->st_ex_flags = src->st_flags;
+#else
+ dst->st_ex_flags = 0;
+#endif
+}
+
+/*******************************************************************
+A stat() wrapper.
+********************************************************************/
+
+int sys_stat(const char *fname, SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times)
+{
+ int ret;
+ struct stat statbuf;
+ ret = stat(fname, &statbuf);
+ if (ret == 0) {
+ /* we always want directories to appear zero size */
+ if (S_ISDIR(statbuf.st_mode)) {
+ statbuf.st_size = 0;
+ }
+ init_stat_ex_from_stat(sbuf, &statbuf, fake_dir_create_times);
+ }
+ return ret;
+}
+
+/*******************************************************************
+ An fstat() wrapper.
+********************************************************************/
+
+int sys_fstat(int fd, SMB_STRUCT_STAT *sbuf, bool fake_dir_create_times)
+{
+ int ret;
+ struct stat statbuf;
+ ret = fstat(fd, &statbuf);
+ if (ret == 0) {
+ /* we always want directories to appear zero size */
+ if (S_ISDIR(statbuf.st_mode)) {
+ statbuf.st_size = 0;
+ }
+ init_stat_ex_from_stat(sbuf, &statbuf, fake_dir_create_times);
+ }
+ return ret;
+}
+
+/*******************************************************************
+ An lstat() wrapper.
+********************************************************************/
+
+int sys_lstat(const char *fname,SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times)
+{
+ int ret;
+ struct stat statbuf;
+ ret = lstat(fname, &statbuf);
+ if (ret == 0) {
+ /* we always want directories to appear zero size */
+ if (S_ISDIR(statbuf.st_mode)) {
+ statbuf.st_size = 0;
+ }
+ init_stat_ex_from_stat(sbuf, &statbuf, fake_dir_create_times);
+ }
+ return ret;
+}
+
+/*******************************************************************
+ An fstatat() wrapper.
+********************************************************************/
+
+int sys_fstatat(int fd,
+ const char *pathname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags,
+ bool fake_dir_create_times)
+{
+ int ret;
+ struct stat statbuf;
+
+ ret = fstatat(fd, pathname, &statbuf, flags);
+ if (ret != 0) {
+ return -1;
+ }
+
+ /* we always want directories to appear zero size */
+ if (S_ISDIR(statbuf.st_mode)) {
+ statbuf.st_size = 0;
+ }
+ init_stat_ex_from_stat(sbuf, &statbuf, fake_dir_create_times);
+ return 0;
+}
+
+/*******************************************************************
+ An posix_fallocate() wrapper.
+********************************************************************/
+int sys_posix_fallocate(int fd, off_t offset, off_t len)
+{
+#if defined(HAVE_POSIX_FALLOCATE)
+ return posix_fallocate(fd, offset, len);
+#elif defined(F_RESVSP64)
+ /* this handles XFS on IRIX */
+ struct flock64 fl;
+ off_t new_len = offset + len;
+ int ret;
+ struct stat64 sbuf;
+
+ /* unlikely to get a too large file on a 64bit system but ... */
+ if (new_len < 0)
+ return EFBIG;
+
+ fl.l_whence = SEEK_SET;
+ fl.l_start = offset;
+ fl.l_len = len;
+
+ ret=fcntl(fd, F_RESVSP64, &fl);
+
+ if (ret != 0)
+ return errno;
+
+ /* Make sure the file gets enlarged after we allocated space: */
+ fstat64(fd, &sbuf);
+ if (new_len > sbuf.st_size)
+ ftruncate64(fd, new_len);
+ return 0;
+#else
+ return ENOSYS;
+#endif
+}
+
+/*******************************************************************
+ An fallocate() function that matches the semantics of the Linux one.
+********************************************************************/
+
+#ifdef HAVE_LINUX_FALLOC_H
+#include <linux/falloc.h>
+#endif
+
+int sys_fallocate(int fd, uint32_t mode, off_t offset, off_t len)
+{
+#if defined(HAVE_LINUX_FALLOCATE)
+ int lmode = 0;
+
+ if (mode & VFS_FALLOCATE_FL_KEEP_SIZE) {
+ lmode |= FALLOC_FL_KEEP_SIZE;
+ mode &= ~VFS_FALLOCATE_FL_KEEP_SIZE;
+ }
+
+#if defined(HAVE_FALLOC_FL_PUNCH_HOLE)
+ if (mode & VFS_FALLOCATE_FL_PUNCH_HOLE) {
+ lmode |= FALLOC_FL_PUNCH_HOLE;
+ mode &= ~VFS_FALLOCATE_FL_PUNCH_HOLE;
+ }
+#endif /* HAVE_FALLOC_FL_PUNCH_HOLE */
+
+ if (mode != 0) {
+ DEBUG(2, ("unmapped fallocate flags: %lx\n",
+ (unsigned long)mode));
+ errno = EINVAL;
+ return -1;
+ }
+ return fallocate(fd, lmode, offset, len);
+#else /* HAVE_LINUX_FALLOCATE */
+ /* TODO - plumb in fallocate from other filesysetms like VXFS etc. JRA. */
+ errno = ENOSYS;
+ return -1;
+#endif /* HAVE_LINUX_FALLOCATE */
+}
+
+/*******************************************************************
+ An fdopendir wrapper.
+********************************************************************/
+
+DIR *sys_fdopendir(int fd)
+{
+#if defined(HAVE_FDOPENDIR)
+ return fdopendir(fd);
+#else
+ errno = ENOSYS;
+ return NULL;
+#endif
+}
+
+/*******************************************************************
+ An mknod() wrapper.
+********************************************************************/
+
+int sys_mknod(const char *path, mode_t mode, SMB_DEV_T dev)
+{
+#if defined(HAVE_MKNOD)
+ return mknod(path, mode, dev);
+#else
+ /* No mknod system call. */
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+/*******************************************************************
+ A mknodat() wrapper.
+********************************************************************/
+
+int sys_mknodat(int dirfd, const char *path, mode_t mode, SMB_DEV_T dev)
+{
+#if defined(HAVE_MKNODAT)
+ return mknodat(dirfd, path, mode, dev);
+#else
+ /* No mknod system call. */
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+/*******************************************************************
+ System wrapper for getwd. Always returns MALLOC'ed memory, or NULL
+ on error (malloc fail usually).
+********************************************************************/
+
+char *sys_getwd(void)
+{
+#ifdef GETCWD_TAKES_NULL
+ return getcwd(NULL, 0);
+#elif defined(HAVE_GETCWD)
+ char *wd = NULL, *s = NULL;
+ size_t allocated = PATH_MAX;
+
+ while (1) {
+ s = SMB_REALLOC_ARRAY(s, char, allocated);
+ if (s == NULL) {
+ return NULL;
+ }
+ wd = getcwd(s, allocated);
+ if (wd) {
+ break;
+ }
+ if (errno != ERANGE) {
+ int saved_errno = errno;
+ SAFE_FREE(s);
+ errno = saved_errno;
+ break;
+ }
+ allocated *= 2;
+ if (allocated < PATH_MAX) {
+ SAFE_FREE(s);
+ break;
+ }
+ }
+ return wd;
+#else
+ char *wd = NULL;
+ char *s = SMB_MALLOC_ARRAY(char, PATH_MAX);
+ if (s == NULL) {
+ return NULL;
+ }
+ wd = getwd(s);
+ if (wd == NULL) {
+ int saved_errno = errno;
+ SAFE_FREE(s);
+ errno = saved_errno;
+ }
+ return wd;
+#endif
+}
+
+#if defined(HAVE_POSIX_CAPABILITIES)
+
+/**************************************************************************
+ Try and abstract process capabilities (for systems that have them).
+****************************************************************************/
+
+/* Set the POSIX capabilities needed for the given purpose into the effective
+ * capability set of the current process. Make sure they are always removed
+ * from the inheritable set, because there is no circumstance in which our
+ * children should inherit our elevated privileges.
+ */
+static bool set_process_capability(enum smbd_capability capability,
+ bool enable)
+{
+ /* "5" is the number of "num_cap_vals++" below */
+ cap_value_t cap_vals[5] = {0};
+ size_t num_cap_vals = 0;
+
+ cap_t cap;
+
+#if defined(HAVE_PRCTL) && defined(PR_GET_KEEPCAPS) && defined(PR_SET_KEEPCAPS)
+ /* On Linux, make sure that any capabilities we grab are sticky
+ * across UID changes. We expect that this would allow us to keep both
+ * the effective and permitted capability sets, but as of circa 2.6.16,
+ * only the permitted set is kept. It is a bug (which we work around)
+ * that the effective set is lost, but we still require the effective
+ * set to be kept.
+ */
+ if (!prctl(PR_GET_KEEPCAPS)) {
+ prctl(PR_SET_KEEPCAPS, 1);
+ }
+#endif
+
+ cap = cap_get_proc();
+ if (cap == NULL) {
+ DEBUG(0,("set_process_capability: cap_get_proc failed: %s\n",
+ strerror(errno)));
+ return False;
+ }
+
+ switch (capability) {
+ /*
+ * WARNING: If you add any #ifdef for a fresh
+ * capability, bump up the array size in the
+ * declaration of cap_vals[] above just to be
+ * trivially safe to never overwrite cap_vals[].
+ */
+ case KERNEL_OPLOCK_CAPABILITY:
+#ifdef CAP_NETWORK_MGT
+ /* IRIX has CAP_NETWORK_MGT for oplocks. */
+ cap_vals[num_cap_vals++] = CAP_NETWORK_MGT;
+#endif
+ break;
+ case DMAPI_ACCESS_CAPABILITY:
+#ifdef CAP_DEVICE_MGT
+ /* IRIX has CAP_DEVICE_MGT for DMAPI access. */
+ cap_vals[num_cap_vals++] = CAP_DEVICE_MGT;
+#elif CAP_MKNOD
+ /* Linux has CAP_MKNOD for DMAPI access. */
+ cap_vals[num_cap_vals++] = CAP_MKNOD;
+#endif
+ break;
+ case LEASE_CAPABILITY:
+#ifdef CAP_LEASE
+ cap_vals[num_cap_vals++] = CAP_LEASE;
+#endif
+ break;
+ case DAC_OVERRIDE_CAPABILITY:
+#ifdef CAP_DAC_OVERRIDE
+ cap_vals[num_cap_vals++] = CAP_DAC_OVERRIDE;
+#endif
+ }
+
+ if (num_cap_vals == 0) {
+ cap_free(cap);
+ return True;
+ }
+
+ cap_set_flag(cap, CAP_EFFECTIVE, num_cap_vals, cap_vals,
+ enable ? CAP_SET : CAP_CLEAR);
+
+ /* We never want to pass capabilities down to our children, so make
+ * sure they are not inherited.
+ */
+ cap_set_flag(cap, CAP_INHERITABLE, num_cap_vals, cap_vals, CAP_CLEAR);
+
+ if (cap_set_proc(cap) == -1) {
+ DBG_ERR("%s capability %d: cap_set_proc failed: %s\n",
+ enable ? "adding" : "dropping",
+ capability, strerror(errno));
+ cap_free(cap);
+ return False;
+ }
+ DBG_INFO("%s capability %d\n",
+ enable ? "added" : "dropped", capability);
+
+ cap_free(cap);
+ return True;
+}
+
+#endif /* HAVE_POSIX_CAPABILITIES */
+
+/****************************************************************************
+ Gain the oplock capability from the kernel if possible.
+****************************************************************************/
+
+#if defined(HAVE_POSIX_CAPABILITIES) && defined(CAP_DAC_OVERRIDE)
+static bool have_cap_dac_override = true;
+#else
+static bool have_cap_dac_override = false;
+#endif
+
+void set_effective_capability(enum smbd_capability capability)
+{
+ bool ret = false;
+
+ if (capability != DAC_OVERRIDE_CAPABILITY || have_cap_dac_override) {
+#if defined(HAVE_POSIX_CAPABILITIES)
+ ret = set_process_capability(capability, True);
+#endif /* HAVE_POSIX_CAPABILITIES */
+ }
+
+ /*
+ * Fallback to become_root() if CAP_DAC_OVERRIDE is not
+ * available.
+ */
+ if (capability == DAC_OVERRIDE_CAPABILITY) {
+ if (!ret) {
+ have_cap_dac_override = false;
+ }
+ if (!have_cap_dac_override) {
+ become_root();
+ }
+ }
+}
+
+void drop_effective_capability(enum smbd_capability capability)
+{
+ if (capability != DAC_OVERRIDE_CAPABILITY || have_cap_dac_override) {
+#if defined(HAVE_POSIX_CAPABILITIES)
+ set_process_capability(capability, False);
+#endif /* HAVE_POSIX_CAPABILITIES */
+ } else {
+ unbecome_root();
+ }
+}
+
+/**************************************************************************
+ Wrapper for random().
+****************************************************************************/
+
+long sys_random(void)
+{
+#if defined(HAVE_RANDOM)
+ return (long)random();
+#elif defined(HAVE_RAND)
+ return (long)rand();
+#else
+ DEBUG(0,("Error - no random function available !\n"));
+ exit(1);
+#endif
+}
+
+/**************************************************************************
+ Wrapper for srandom().
+****************************************************************************/
+
+void sys_srandom(unsigned int seed)
+{
+#if defined(HAVE_SRANDOM)
+ srandom(seed);
+#elif defined(HAVE_SRAND)
+ srand(seed);
+#else
+ DEBUG(0,("Error - no srandom function available !\n"));
+ exit(1);
+#endif
+}
+
+#ifndef NGROUPS_MAX
+#define NGROUPS_MAX 32 /* Guess... */
+#endif
+
+/**************************************************************************
+ Returns equivalent to NGROUPS_MAX - using sysconf if needed.
+****************************************************************************/
+
+int setgroups_max(void)
+{
+#if defined(SYSCONF_SC_NGROUPS_MAX)
+ int ret = sysconf(_SC_NGROUPS_MAX);
+ return (ret == -1) ? NGROUPS_MAX : ret;
+#else
+ return NGROUPS_MAX;
+#endif
+}
+
+int getgroups_max(void)
+{
+#if defined(DARWINOS)
+ /*
+ * On MacOS sysconf(_SC_NGROUPS_MAX) returns 16 due to MacOS's group
+ * nesting. However, The initgroups() manpage states the following:
+ * "Note that OS X supports group membership in an unlimited number
+ * of groups. The OS X kernel uses the group list stored in the process
+ * credentials only as an initial cache. Additional group memberships
+ * are determined by communication between the operating system and the
+ * opendirectoryd daemon."
+ */
+ return INT_MAX;
+#else
+ return setgroups_max();
+#endif
+}
+
+/**************************************************************************
+ Wrap setgroups and getgroups for systems that declare getgroups() as
+ returning an array of gid_t, but actually return an array of int.
+****************************************************************************/
+
+#if defined(HAVE_BROKEN_GETGROUPS)
+
+#ifdef HAVE_BROKEN_GETGROUPS
+#define GID_T int
+#else
+#define GID_T gid_t
+#endif
+
+static int sys_broken_getgroups(int setlen, gid_t *gidset)
+{
+ GID_T *group_list;
+ int i, ngroups;
+
+ if(setlen == 0) {
+ return getgroups(0, NULL);
+ }
+
+ /*
+ * Broken case. We need to allocate a
+ * GID_T array of size setlen.
+ */
+
+ if(setlen < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if((group_list = SMB_MALLOC_ARRAY(GID_T, setlen)) == NULL) {
+ DEBUG(0,("sys_getgroups: Malloc fail.\n"));
+ return -1;
+ }
+
+ if((ngroups = getgroups(setlen, group_list)) < 0) {
+ int saved_errno = errno;
+ SAFE_FREE(group_list);
+ errno = saved_errno;
+ return -1;
+ }
+
+ /*
+ * We're safe here as if ngroups > setlen then
+ * getgroups *must* return EINVAL.
+ * pubs.opengroup.org/onlinepubs/009695399/functions/getgroups.html
+ */
+
+ for(i = 0; i < ngroups; i++)
+ gidset[i] = (gid_t)group_list[i];
+
+ SAFE_FREE(group_list);
+ return ngroups;
+}
+
+static int sys_broken_setgroups(int setlen, gid_t *gidset)
+{
+ GID_T *group_list;
+ int i ;
+
+ if (setlen == 0)
+ return 0 ;
+
+ if (setlen < 0 || setlen > setgroups_max()) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * Broken case. We need to allocate a
+ * GID_T array of size setlen.
+ */
+
+ if((group_list = SMB_MALLOC_ARRAY(GID_T, setlen)) == NULL) {
+ DEBUG(0,("sys_setgroups: Malloc fail.\n"));
+ return -1;
+ }
+
+ for(i = 0; i < setlen; i++)
+ group_list[i] = (GID_T) gidset[i];
+
+ if(samba_setgroups(setlen, group_list) != 0) {
+ int saved_errno = errno;
+ SAFE_FREE(group_list);
+ errno = saved_errno;
+ return -1;
+ }
+
+ SAFE_FREE(group_list);
+ return 0 ;
+}
+
+#endif /* HAVE_BROKEN_GETGROUPS */
+
+/* This is a list of systems that require the first GID passed to setgroups(2)
+ * to be the effective GID. If your system is one of these, add it here.
+ */
+#if defined (FREEBSD) || defined (DARWINOS)
+#define USE_BSD_SETGROUPS
+#endif
+
+#if defined(USE_BSD_SETGROUPS)
+/* Depending on the particular BSD implementation, the first GID that is
+ * passed to setgroups(2) will either be ignored or will set the credential's
+ * effective GID. In either case, the right thing to do is to guarantee that
+ * gidset[0] is the effective GID.
+ */
+static int sys_bsd_setgroups(gid_t primary_gid, int setlen, const gid_t *gidset)
+{
+ gid_t *new_gidset = NULL;
+ int max;
+ int ret;
+
+ /* setgroups(2) will fail with EINVAL if we pass too many groups. */
+ max = setgroups_max();
+
+ /* No group list, just make sure we are setting the effective GID. */
+ if (setlen == 0) {
+ return samba_setgroups(1, &primary_gid);
+ }
+
+ /* If the primary gid is not the first array element, grow the array
+ * and insert it at the front.
+ */
+ if (gidset[0] != primary_gid) {
+ new_gidset = SMB_MALLOC_ARRAY(gid_t, setlen + 1);
+ if (new_gidset == NULL) {
+ return -1;
+ }
+
+ memcpy(new_gidset + 1, gidset, (setlen * sizeof(gid_t)));
+ new_gidset[0] = primary_gid;
+ setlen++;
+ }
+
+ if (setlen > max) {
+ DEBUG(3, ("forced to truncate group list from %d to %d\n",
+ setlen, max));
+ setlen = max;
+ }
+
+#if defined(HAVE_BROKEN_GETGROUPS)
+ ret = sys_broken_setgroups(setlen, new_gidset ? new_gidset : gidset);
+#else
+ ret = samba_setgroups(setlen, new_gidset ? new_gidset : gidset);
+#endif
+
+ if (new_gidset) {
+ int errsav = errno;
+ SAFE_FREE(new_gidset);
+ errno = errsav;
+ }
+
+ return ret;
+}
+
+#endif /* USE_BSD_SETGROUPS */
+
+/**************************************************************************
+ Wrapper for getgroups. Deals with broken (int) case.
+****************************************************************************/
+
+int sys_getgroups(int setlen, gid_t *gidset)
+{
+#if defined(HAVE_BROKEN_GETGROUPS)
+ return sys_broken_getgroups(setlen, gidset);
+#else
+ return getgroups(setlen, gidset);
+#endif
+}
+
+/**************************************************************************
+ Wrapper for setgroups. Deals with broken (int) case and BSD case.
+****************************************************************************/
+
+int sys_setgroups(gid_t UNUSED(primary_gid), int setlen, gid_t *gidset)
+{
+#if !defined(HAVE_SETGROUPS)
+ errno = ENOSYS;
+ return -1;
+#endif /* HAVE_SETGROUPS */
+
+#if defined(USE_BSD_SETGROUPS)
+ return sys_bsd_setgroups(primary_gid, setlen, gidset);
+#elif defined(HAVE_BROKEN_GETGROUPS)
+ return sys_broken_setgroups(setlen, gidset);
+#else
+ return samba_setgroups(setlen, gidset);
+#endif
+}
+
+/****************************************************************************
+ Return the major devicenumber for UNIX extensions.
+****************************************************************************/
+
+uint32_t unix_dev_major(SMB_DEV_T dev)
+{
+#if defined(HAVE_DEVICE_MAJOR_FN)
+ return (uint32_t)major(dev);
+#else
+ return (uint32_t)(dev >> 8);
+#endif
+}
+
+/****************************************************************************
+ Return the minor devicenumber for UNIX extensions.
+****************************************************************************/
+
+uint32_t unix_dev_minor(SMB_DEV_T dev)
+{
+#if defined(HAVE_DEVICE_MINOR_FN)
+ return (uint32_t)minor(dev);
+#else
+ return (uint32_t)(dev & 0xff);
+#endif
+}
+
+/**************************************************************************
+ Wrapper for realpath.
+****************************************************************************/
+
+char *sys_realpath(const char *path)
+{
+ char *result;
+
+#ifdef REALPATH_TAKES_NULL
+ result = realpath(path, NULL);
+#else
+ result = SMB_MALLOC_ARRAY(char, PATH_MAX + 1);
+ if (result) {
+ char *resolved_path = realpath(path, result);
+ if (!resolved_path) {
+ SAFE_FREE(result);
+ } else {
+ /* SMB_ASSERT(result == resolved_path) ? */
+ result = resolved_path;
+ }
+ }
+#endif
+ return result;
+}
+
+#if 0
+/*******************************************************************
+ Return the number of CPUs.
+********************************************************************/
+
+int sys_get_number_of_cores(void)
+{
+ int ret = -1;
+
+#if defined(HAVE_SYSCONF)
+#if defined(_SC_NPROCESSORS_ONLN)
+ ret = (int)sysconf(_SC_NPROCESSORS_ONLN);
+#endif
+#if defined(_SC_NPROCESSORS_CONF)
+ if (ret < 1) {
+ ret = (int)sysconf(_SC_NPROCESSORS_CONF);
+ }
+#endif
+#elif defined(HAVE_SYSCTL) && defined(CTL_HW)
+ int name[2];
+ unsigned int len = sizeof(ret);
+
+ name[0] = CTL_HW;
+#if defined(HW_AVAILCPU)
+ name[1] = HW_AVAILCPU;
+
+ if (sysctl(name, 2, &ret, &len, NULL, 0) == -1) {
+ ret = -1;
+ }
+#endif
+#if defined(HW_NCPU)
+ if(ret < 1) {
+ name[0] = CTL_HW;
+ name[1] = HW_NCPU;
+ if (sysctl(nm, 2, &count, &len, NULL, 0) == -1) {
+ ret = -1;
+ }
+ }
+#endif
+#endif
+ if (ret < 1) {
+ ret = 1;
+ }
+ return ret;
+}
+#endif
+
+bool sys_have_proc_fds(void)
+{
+ static bool checked = false;
+ static bool have_proc_fds = false;
+ struct stat sb;
+ int ret;
+
+ if (checked) {
+ return have_proc_fds;
+ }
+
+ ret = stat("/proc/self/fd/0", &sb);
+ have_proc_fds = (ret == 0);
+ checked = true;
+
+ return have_proc_fds;
+}
+
+char *sys_proc_fd_path(int fd, struct sys_proc_fd_path_buf *buf)
+{
+ int written =
+ snprintf(buf->buf, sizeof(buf->buf), "/proc/self/fd/%d", fd);
+
+ SMB_ASSERT(sys_have_proc_fds() && (written >= 0));
+
+ return buf->buf;
+}
diff --git a/source3/lib/system_smbd.c b/source3/lib/system_smbd.c
new file mode 100644
index 0000000..4a14744
--- /dev/null
+++ b/source3/lib/system_smbd.c
@@ -0,0 +1,253 @@
+/*
+ Unix SMB/CIFS implementation.
+ system call wrapper interface.
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Andrew Barteltt 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ This file may assume linkage with smbd - for things like become_root()
+ etc.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "nsswitch/winbind_client.h"
+#include "../lib/util/setid.h"
+
+#ifndef HAVE_GETGROUPLIST
+
+#ifdef HAVE_GETGRSET
+static int getgrouplist_getgrset(const char *user, gid_t gid, gid_t *groups,
+ int *grpcnt)
+{
+ char *grplist;
+ char *grp;
+ gid_t temp_gid;
+ int num_gids = 1;
+ int ret = 0;
+ long l;
+
+ grplist = getgrset(user);
+
+ DEBUG(10, ("getgrset returned %s\n", grplist));
+
+ if (grplist == NULL) {
+ return -1;
+ }
+
+ if (*grpcnt > 0) {
+ groups[0] = gid;
+ }
+
+ while ((grp = strsep(&grplist, ",")) != NULL) {
+ l = strtol(grp, NULL, 10);
+ temp_gid = (gid_t) l;
+ if (temp_gid == gid) {
+ continue;
+ }
+
+ if (num_gids + 1 > *grpcnt) {
+ num_gids++;
+ continue;
+ }
+ groups[num_gids++] = temp_gid;
+ }
+ free(grplist);
+
+ if (num_gids > *grpcnt) {
+ ret = -1;
+ }
+ *grpcnt = num_gids;
+
+ DEBUG(10, ("Found %d groups for user %s\n", *grpcnt, user));
+
+ return ret;
+}
+
+#else /* HAVE_GETGRSET */
+
+/*
+ This is a *much* faster way of getting the list of groups for a user
+ without changing the current supplementary group list. The old
+ method used getgrent() which could take 20 minutes on a really big
+ network with hundeds of thousands of groups and users. The new method
+ takes a couple of seconds.
+
+ NOTE!! this function only works if it is called as root!
+ */
+
+static int getgrouplist_internals(const char *user, gid_t gid, gid_t *groups,
+ int *grpcnt)
+{
+ gid_t *gids_saved;
+ int ret, ngrp_saved, num_gids;
+
+ if (non_root_mode()) {
+ *grpcnt = 0;
+ return 0;
+ }
+
+ /* work out how many groups we need to save */
+ ngrp_saved = getgroups(0, NULL);
+ if (ngrp_saved == -1) {
+ /* this shouldn't happen */
+ return -1;
+ }
+
+ gids_saved = SMB_MALLOC_ARRAY(gid_t, ngrp_saved+1);
+ if (!gids_saved) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ngrp_saved = getgroups(ngrp_saved, gids_saved);
+ if (ngrp_saved == -1) {
+ SAFE_FREE(gids_saved);
+ /* very strange! */
+ return -1;
+ }
+
+ if (initgroups(user, gid) == -1) {
+ DEBUG(0, ("getgrouplist_internals: initgroups() failed!\n"));
+ SAFE_FREE(gids_saved);
+ return -1;
+ }
+
+ /* this must be done to cope with systems that put the current egid in the
+ return from getgroups() */
+ save_re_gid();
+ set_effective_gid(gid);
+ samba_setgid(gid);
+
+ num_gids = getgroups(0, NULL);
+ if (num_gids == -1) {
+ SAFE_FREE(gids_saved);
+ /* very strange! */
+ return -1;
+ }
+
+ if (num_gids + 1 > *grpcnt) {
+ *grpcnt = num_gids + 1;
+ ret = -1;
+ } else {
+ ret = getgroups(*grpcnt - 1, &groups[1]);
+ if (ret < 0) {
+ SAFE_FREE(gids_saved);
+ /* very strange! */
+ return -1;
+ }
+ groups[0] = gid;
+ *grpcnt = ret + 1;
+ }
+
+ restore_re_gid();
+
+ if (sys_setgroups(gid, ngrp_saved, gids_saved) != 0) {
+ /* yikes! */
+ DEBUG(0,("ERROR: getgrouplist: failed to reset group list!\n"));
+ smb_panic("getgrouplist: failed to reset group list!");
+ }
+
+ free(gids_saved);
+ return ret;
+}
+#endif /* HAVE_GETGRSET */
+#endif /* HAVE_GETGROUPLIST */
+
+static int sys_getgrouplist(const char *user, gid_t gid, gid_t *groups, int *grpcnt)
+{
+ int retval;
+ bool winbind_env;
+
+ DEBUG(10,("sys_getgrouplist: user [%s]\n", user));
+
+ /* This is only ever called for Unix users, remote memberships are
+ * always determined by the info3 coming back from auth3 or the
+ * PAC. */
+ winbind_env = winbind_env_set();
+ (void)winbind_off();
+
+#ifdef HAVE_GETGROUPLIST
+ retval = getgrouplist(user, gid, groups, grpcnt);
+#else
+#ifdef HAVE_GETGRSET
+ retval = getgrouplist_getgrset(user, gid, groups, grpcnt);
+#else
+ become_root();
+ retval = getgrouplist_internals(user, gid, groups, grpcnt);
+ unbecome_root();
+#endif /* HAVE_GETGRSET */
+#endif /* HAVE_GETGROUPLIST */
+
+ /* allow winbindd lookups, but only if they were not already disabled */
+ if (!winbind_env) {
+ (void)winbind_on();
+ }
+
+ return retval;
+}
+
+bool getgroups_unix_user(TALLOC_CTX *mem_ctx, const char *user,
+ gid_t primary_gid,
+ gid_t **ret_groups, uint32_t *p_ngroups)
+{
+ int max_grp = MIN(128, getgroups_max());
+ gid_t stack_groups[max_grp];
+ uint32_t ngrp;
+ gid_t *temp_groups = stack_groups;
+ gid_t *to_free = NULL;
+ gid_t *groups;
+ int i;
+
+ if (sys_getgrouplist(user, primary_gid, temp_groups, &max_grp) == -1) {
+ to_free = talloc_array(mem_ctx, gid_t, max_grp);
+ if (!to_free) {
+ return False;
+ }
+ temp_groups = to_free;
+
+ if (sys_getgrouplist(user, primary_gid,
+ temp_groups, &max_grp) == -1) {
+ DEBUG(0, ("get_user_groups: failed to get the unix "
+ "group list\n"));
+ TALLOC_FREE(to_free);
+ return False;
+ }
+ }
+
+ ngrp = 0;
+ groups = NULL;
+
+ /* Add in primary group first */
+ if (!add_gid_to_array_unique(mem_ctx, primary_gid, &groups, &ngrp)) {
+ TALLOC_FREE(to_free);
+ return False;
+ }
+
+ for (i=0; i<max_grp; i++) {
+ if (!add_gid_to_array_unique(mem_ctx, temp_groups[i],
+ &groups, &ngrp)) {
+ TALLOC_FREE(to_free);
+ return False;
+ }
+ }
+
+ *p_ngroups = ngrp;
+ *ret_groups = groups;
+ TALLOC_FREE(to_free);
+ return True;
+}
diff --git a/source3/lib/tallocmsg.c b/source3/lib/tallocmsg.c
new file mode 100644
index 0000000..af5a90b
--- /dev/null
+++ b/source3/lib/tallocmsg.c
@@ -0,0 +1,85 @@
+/*
+ samba -- Unix SMB/CIFS implementation.
+ Copyright (C) 2001, 2002 by Martin Pool
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "messages.h"
+#include "lib/util/talloc_report_printf.h"
+
+static bool pool_usage_filter(struct messaging_rec *rec, void *private_data)
+{
+ FILE *f = NULL;
+ int fd;
+
+ if (rec->msg_type != MSG_REQ_POOL_USAGE) {
+ return false;
+ }
+
+ DBG_DEBUG("Got MSG_REQ_POOL_USAGE\n");
+
+ if (rec->num_fds != 1) {
+ DBG_DEBUG("Got %"PRIu8" fds, expected one\n", rec->num_fds);
+ return false;
+ }
+
+ fd = dup(rec->fds[0]);
+ if (fd == -1) {
+ DBG_DEBUG("dup(%"PRIi64") failed: %s\n",
+ rec->fds[0],
+ strerror(errno));
+ return false;
+ }
+
+ f = fdopen(fd, "w");
+ if (f == NULL) {
+ DBG_DEBUG("fdopen failed: %s\n", strerror(errno));
+ close(fd);
+ return false;
+ }
+
+ talloc_full_report_printf(NULL, f);
+
+ fclose(f);
+ /*
+ * Returning false, means messaging_dispatch_waiters()
+ * won't call messaging_filtered_read_done() and
+ * our messaging_filtered_read_send() stays alive
+ * and will get messages.
+ */
+ return false;
+}
+
+/**
+ * Register handler for MSG_REQ_POOL_USAGE
+ **/
+void register_msg_pool_usage(
+ TALLOC_CTX *mem_ctx, struct messaging_context *msg_ctx)
+{
+ struct tevent_req *req = NULL;
+
+ req = messaging_filtered_read_send(
+ mem_ctx,
+ messaging_tevent_context(msg_ctx),
+ msg_ctx,
+ pool_usage_filter,
+ NULL);
+ if (req == NULL) {
+ DBG_WARNING("messaging_filtered_read_send failed\n");
+ return;
+ }
+ DBG_INFO("Registered MSG_REQ_POOL_USAGE\n");
+}
diff --git a/source3/lib/tdb_validate.c b/source3/lib/tdb_validate.c
new file mode 100644
index 0000000..78bd824
--- /dev/null
+++ b/source3/lib/tdb_validate.c
@@ -0,0 +1,538 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * A general tdb content validation mechanism
+ *
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "util_tdb.h"
+#include "tdb_validate.h"
+
+/*
+ * internal validation function, executed by the child.
+ */
+static int tdb_validate_child(struct tdb_context *tdb,
+ tdb_validate_data_func validate_fn)
+{
+ int ret = 1;
+ int check_rc;
+ int num_entries = 0;
+ struct tdb_validation_status v_status;
+
+ v_status.tdb_error = False;
+ v_status.bad_freelist = False;
+ v_status.bad_entry = False;
+ v_status.unknown_key = False;
+ v_status.success = True;
+
+ if (!tdb) {
+ v_status.tdb_error = True;
+ v_status.success = False;
+ goto out;
+ }
+
+ /*
+ * we can simplify this by passing a check function,
+ * but I don't want to change all the callers...
+ */
+ check_rc = tdb_check(tdb, NULL, NULL);
+ if (check_rc != 0) {
+ v_status.tdb_error = True;
+ v_status.success = False;
+ goto out;
+ }
+
+ /* Check if the tdb's freelist is good. */
+ if (tdb_validate_freelist(tdb, &num_entries) == -1) {
+ v_status.bad_freelist = True;
+ v_status.success = False;
+ goto out;
+ }
+
+ DEBUG(10,("tdb_validate_child: tdb %s freelist has %d entries\n",
+ tdb_name(tdb), num_entries));
+
+ /* Now traverse the tdb to validate it. */
+ num_entries = tdb_traverse(tdb, validate_fn, (void *)&v_status);
+ if (!v_status.success) {
+ goto out;
+ } else if (num_entries < 0) {
+ v_status.tdb_error = True;
+ v_status.success = False;
+ goto out;
+ }
+
+ DEBUG(10,("tdb_validate_child: tdb %s is good with %d entries\n",
+ tdb_name(tdb), num_entries));
+ ret = 0; /* Cache is good. */
+
+out:
+ DEBUG(10, ("tdb_validate_child: summary of validation status:\n"));
+ DEBUGADD(10,(" * tdb error: %s\n", v_status.tdb_error ? "yes" : "no"));
+ DEBUGADD(10,(" * bad freelist: %s\n",v_status.bad_freelist?"yes":"no"));
+ DEBUGADD(10,(" * bad entry: %s\n", v_status.bad_entry ? "yes" : "no"));
+ DEBUGADD(10,(" * unknown key: %s\n", v_status.unknown_key?"yes":"no"));
+ DEBUGADD(10,(" => overall success: %s\n", v_status.success?"yes":"no"));
+
+ return ret;
+}
+
+/*
+ * tdb validation function.
+ * returns 0 if tdb is ok, != 0 if it isn't.
+ * this function expects an opened tdb.
+ */
+int tdb_validate(struct tdb_context *tdb, tdb_validate_data_func validate_fn)
+{
+ pid_t child_pid = -1;
+ int child_status = 0;
+ int wait_pid = 0;
+ int ret = 1;
+
+ if (tdb == NULL) {
+ DEBUG(1, ("Error: tdb_validate called with tdb == NULL\n"));
+ return ret;
+ }
+
+ DEBUG(5, ("tdb_validate called for tdb '%s'\n", tdb_name(tdb)));
+
+ /* fork and let the child do the validation.
+ * benefit: no need to twist signal handlers and panic functions.
+ * just let the child panic. we catch the signal. */
+
+ DEBUG(10, ("tdb_validate: forking to let child do validation.\n"));
+ child_pid = fork();
+ if (child_pid == 0) {
+ /* child code */
+ DEBUG(10, ("tdb_validate (validation child): created\n"));
+ DEBUG(10, ("tdb_validate (validation child): "
+ "calling tdb_validate_child\n"));
+ exit(tdb_validate_child(tdb, validate_fn));
+ }
+ else if (child_pid < 0) {
+ DEBUG(1, ("tdb_validate: fork for validation failed.\n"));
+ goto done;
+ }
+
+ /* parent */
+
+ DEBUG(10, ("tdb_validate: fork succeeded, child PID = %u\n",
+ (unsigned int)child_pid));
+
+ DEBUG(10, ("tdb_validate: waiting for child to finish...\n"));
+ while ((wait_pid = waitpid(child_pid, &child_status, 0)) < 0) {
+ if (errno == EINTR) {
+ DEBUG(10, ("tdb_validate: got signal during waitpid, "
+ "retrying\n"));
+ errno = 0;
+ continue;
+ }
+ DEBUG(1, ("tdb_validate: waitpid failed with error '%s'.\n",
+ strerror(errno)));
+ goto done;
+ }
+ if (wait_pid != child_pid) {
+ DEBUG(1, ("tdb_validate: waitpid returned pid %d, "
+ "but %u was expected\n", wait_pid, (unsigned int)child_pid));
+ goto done;
+ }
+
+ DEBUG(10, ("tdb_validate: validating child returned.\n"));
+ if (WIFEXITED(child_status)) {
+ DEBUG(10, ("tdb_validate: child exited, code %d.\n",
+ WEXITSTATUS(child_status)));
+ ret = WEXITSTATUS(child_status);
+ }
+ if (WIFSIGNALED(child_status)) {
+ DEBUG(10, ("tdb_validate: child terminated by signal %d\n",
+ WTERMSIG(child_status)));
+#ifdef WCOREDUMP
+ if (WCOREDUMP(child_status)) {
+ DEBUGADD(10, ("core dumped\n"));
+ }
+#endif
+ ret = WTERMSIG(child_status);
+ }
+ if (WIFSTOPPED(child_status)) {
+ DEBUG(10, ("tdb_validate: child was stopped by signal %d\n",
+ WSTOPSIG(child_status)));
+ ret = WSTOPSIG(child_status);
+ }
+
+done:
+ DEBUG(5, ("tdb_validate returning code '%d' for tdb '%s'\n", ret,
+ tdb_name(tdb)));
+
+ return ret;
+}
+
+/*
+ * tdb validation function.
+ * returns 0 if tdb is ok, != 0 if it isn't.
+ * this is a wrapper around the actual validation function that opens and closes
+ * the tdb.
+ */
+int tdb_validate_open(const char *tdb_path, tdb_validate_data_func validate_fn)
+{
+ TDB_CONTEXT *tdb = NULL;
+ int ret = 1;
+
+ DEBUG(5, ("tdb_validate_open called for tdb '%s'\n", tdb_path));
+
+ tdb = tdb_open_log(tdb_path, 0, TDB_DEFAULT, O_RDWR, 0);
+ if (!tdb) {
+ DEBUG(1, ("Error opening tdb %s\n", tdb_path));
+ return ret;
+ }
+
+ ret = tdb_validate(tdb, validate_fn);
+ tdb_close(tdb);
+ return ret;
+}
+
+/*
+ * tdb backup function and helpers for tdb_validate wrapper with backup
+ * handling.
+ */
+
+/* this structure eliminates the need for a global overall status for
+ * the traverse-copy */
+struct tdb_copy_data {
+ struct tdb_context *dst;
+ bool success;
+};
+
+static int traverse_copy_fn(struct tdb_context *tdb, TDB_DATA key,
+ TDB_DATA dbuf, void *private_data)
+{
+ struct tdb_copy_data *data = (struct tdb_copy_data *)private_data;
+
+ if (tdb_store(data->dst, key, dbuf, TDB_INSERT) != 0) {
+ DEBUG(4, ("Failed to insert into %s: %s\n", tdb_name(data->dst),
+ strerror(errno)));
+ data->success = False;
+ return 1;
+ }
+ return 0;
+}
+
+static int tdb_copy(struct tdb_context *src, struct tdb_context *dst)
+{
+ struct tdb_copy_data data;
+ int count;
+
+ data.dst = dst;
+ data.success = True;
+
+ count = tdb_traverse(src, traverse_copy_fn, (void *)(&data));
+ if ((count < 0) || (data.success == False)) {
+ return -1;
+ }
+ return count;
+}
+
+static int tdb_verify_basic(struct tdb_context *tdb)
+{
+ return tdb_traverse(tdb, NULL, NULL);
+}
+
+/* this backup function is essentially taken from lib/tdb/tools/tdbbackup.tdb
+ */
+static int tdb_backup(TALLOC_CTX *ctx, const char *src_path,
+ const char *dst_path, int hash_size)
+{
+ struct tdb_context *src_tdb = NULL;
+ struct tdb_context *dst_tdb = NULL;
+ char *tmp_path = NULL;
+ struct stat st;
+ int count1, count2;
+ int saved_errno = 0;
+ int ret = -1;
+
+ if (stat(src_path, &st) != 0) {
+ DEBUG(3, ("Could not stat '%s': %s\n", src_path,
+ strerror(errno)));
+ goto done;
+ }
+
+ /* open old tdb RDWR - so we can lock it */
+ src_tdb = tdb_open_log(src_path, 0, TDB_DEFAULT, O_RDWR, 0);
+ if (src_tdb == NULL) {
+ DEBUG(3, ("Failed to open tdb '%s'\n", src_path));
+ goto done;
+ }
+
+ if (tdb_lockall(src_tdb) != 0) {
+ DEBUG(3, ("Failed to lock tdb '%s'\n", src_path));
+ goto done;
+ }
+
+ tmp_path = talloc_asprintf(ctx, "%s%s", dst_path, ".tmp");
+ if (!tmp_path) {
+ DEBUG(3, ("talloc fail\n"));
+ goto done;
+ }
+
+ unlink(tmp_path);
+
+ if (!hash_size) {
+ hash_size = tdb_hash_size(src_tdb);
+ }
+
+ dst_tdb = tdb_open_log(tmp_path, hash_size,
+ TDB_DEFAULT, O_RDWR | O_CREAT | O_EXCL,
+ st.st_mode & 0777);
+ if (dst_tdb == NULL) {
+ DEBUG(3, ("Error creating tdb '%s': %s\n", tmp_path,
+ strerror(errno)));
+ saved_errno = errno;
+ unlink(tmp_path);
+ goto done;
+ }
+
+ count1 = tdb_copy(src_tdb, dst_tdb);
+ if (count1 < 0) {
+ DEBUG(3, ("Failed to copy tdb '%s': %s\n", src_path,
+ strerror(errno)));
+ tdb_close(dst_tdb);
+ goto done;
+ }
+
+ /* reopen ro and do basic verification */
+ tdb_close(dst_tdb);
+ dst_tdb = tdb_open_log(tmp_path, 0, TDB_DEFAULT, O_RDONLY, 0);
+ if (!dst_tdb) {
+ DEBUG(3, ("Failed to reopen tdb '%s': %s\n", tmp_path,
+ strerror(errno)));
+ goto done;
+ }
+ count2 = tdb_verify_basic(dst_tdb);
+ if (count2 != count1) {
+ DEBUG(3, ("Failed to verify result of copying tdb '%s'.\n",
+ src_path));
+ tdb_close(dst_tdb);
+ goto done;
+ }
+
+ DEBUG(10, ("tdb_backup: successfully copied %d entries\n", count1));
+
+ /* make sure the new tdb has reached stable storage
+ * then rename it to its destination */
+ fsync(tdb_fd(dst_tdb));
+ tdb_close(dst_tdb);
+ unlink(dst_path);
+ if (rename(tmp_path, dst_path) != 0) {
+ DEBUG(3, ("Failed to rename '%s' to '%s': %s\n",
+ tmp_path, dst_path, strerror(errno)));
+ goto done;
+ }
+
+ /* success */
+ ret = 0;
+
+done:
+ if (src_tdb != NULL) {
+ tdb_close(src_tdb);
+ }
+ if (tmp_path != NULL) {
+ unlink(tmp_path);
+ TALLOC_FREE(tmp_path);
+ }
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int rename_file_with_suffix(TALLOC_CTX *ctx, const char *path,
+ const char *suffix)
+{
+ int ret = -1;
+ char *dst_path;
+
+ dst_path = talloc_asprintf(ctx, "%s%s", path, suffix);
+ if (dst_path == NULL) {
+ DEBUG(3, ("error out of memory\n"));
+ return ret;
+ }
+
+ ret = (rename(path, dst_path) != 0);
+
+ if (ret == 0) {
+ DEBUG(5, ("moved '%s' to '%s'\n", path, dst_path));
+ } else if (errno == ENOENT) {
+ DEBUG(3, ("file '%s' does not exist - so not moved\n", path));
+ ret = 0;
+ } else {
+ DEBUG(3, ("error renaming %s to %s: %s\n", path, dst_path,
+ strerror(errno)));
+ }
+
+ TALLOC_FREE(dst_path);
+ return ret;
+}
+
+/*
+ * do a backup of a tdb, moving the destination out of the way first
+ */
+static int tdb_backup_with_rotate(TALLOC_CTX *ctx, const char *src_path,
+ const char *dst_path, int hash_size,
+ const char *rotate_suffix,
+ bool retry_norotate_if_nospc,
+ bool rename_as_last_resort_if_nospc)
+{
+ int ret;
+
+ rename_file_with_suffix(ctx, dst_path, rotate_suffix);
+
+ ret = tdb_backup(ctx, src_path, dst_path, hash_size);
+
+ if (ret != 0) {
+ DEBUG(10, ("backup of %s failed: %s\n", src_path, strerror(errno)));
+ }
+ if ((ret != 0) && (errno == ENOSPC) && retry_norotate_if_nospc)
+ {
+ char *rotate_path = talloc_asprintf(ctx, "%s%s", dst_path,
+ rotate_suffix);
+ if (rotate_path == NULL) {
+ DEBUG(10, ("talloc fail\n"));
+ return -1;
+ }
+ DEBUG(10, ("backup of %s failed due to lack of space\n",
+ src_path));
+ DEBUGADD(10, ("trying to free some space by removing rotated "
+ "dst %s\n", rotate_path));
+ if (unlink(rotate_path) == -1) {
+ DEBUG(10, ("unlink of %s failed: %s\n", rotate_path,
+ strerror(errno)));
+ } else {
+ ret = tdb_backup(ctx, src_path, dst_path, hash_size);
+ }
+ TALLOC_FREE(rotate_path);
+ }
+
+ if ((ret != 0) && (errno == ENOSPC) && rename_as_last_resort_if_nospc)
+ {
+ DEBUG(10, ("backup of %s failed due to lack of space\n",
+ src_path));
+ DEBUGADD(10, ("using 'rename' as a last resort\n"));
+ ret = rename(src_path, dst_path);
+ }
+
+ return ret;
+}
+
+/*
+ * validation function with backup handling:
+ *
+ * - calls tdb_validate
+ * - if the tdb is ok, create a backup "name.bak", possibly moving
+ * existing backup to name.bak.old,
+ * return 0 (success) even if the backup fails
+ * - if the tdb is corrupt:
+ * - move the tdb to "name.corrupt"
+ * - check if there is valid backup.
+ * if so, restore the backup.
+ * if restore is successful, return 0 (success),
+ * - otherwise return -1 (failure)
+ */
+int tdb_validate_and_backup(const char *tdb_path,
+ tdb_validate_data_func validate_fn)
+{
+ int ret = -1;
+ const char *backup_suffix = ".bak";
+ const char *corrupt_suffix = ".corrupt";
+ const char *rotate_suffix = ".old";
+ char *tdb_path_backup;
+ struct stat st;
+ TALLOC_CTX *ctx = NULL;
+
+ ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ DEBUG(0, ("tdb_validate_and_backup: out of memory\n"));
+ goto done;
+ }
+
+ tdb_path_backup = talloc_asprintf(ctx, "%s%s", tdb_path, backup_suffix);
+ if (!tdb_path_backup) {
+ DEBUG(0, ("tdb_validate_and_backup: out of memory\n"));
+ goto done;
+ }
+
+ ret = tdb_validate_open(tdb_path, validate_fn);
+
+ if (ret == 0) {
+ DEBUG(1, ("tdb '%s' is valid\n", tdb_path));
+ ret = tdb_backup_with_rotate(ctx, tdb_path, tdb_path_backup, 0,
+ rotate_suffix, True, False);
+ if (ret != 0) {
+ DEBUG(1, ("Error creating backup of tdb '%s'\n",
+ tdb_path));
+ /* the actual validation was successful: */
+ ret = 0;
+ } else {
+ DEBUG(1, ("Created backup '%s' of tdb '%s'\n",
+ tdb_path_backup, tdb_path));
+ }
+ } else {
+ DEBUG(1, ("tdb '%s' is invalid\n", tdb_path));
+
+ ret =stat(tdb_path_backup, &st);
+ if (ret != 0) {
+ DEBUG(5, ("Could not stat '%s': %s\n", tdb_path_backup,
+ strerror(errno)));
+ DEBUG(1, ("No backup found.\n"));
+ } else {
+ DEBUG(1, ("backup '%s' found.\n", tdb_path_backup));
+ ret = tdb_validate_open(tdb_path_backup, validate_fn);
+ if (ret != 0) {
+ DEBUG(1, ("Backup '%s' is invalid.\n",
+ tdb_path_backup));
+ }
+ }
+
+ if (ret != 0) {
+ int renamed = rename_file_with_suffix(ctx, tdb_path,
+ corrupt_suffix);
+ if (renamed != 0) {
+ DEBUG(1, ("Error moving tdb to '%s%s'\n",
+ tdb_path, corrupt_suffix));
+ } else {
+ DEBUG(1, ("Corrupt tdb stored as '%s%s'\n",
+ tdb_path, corrupt_suffix));
+ }
+ goto done;
+ }
+
+ DEBUG(1, ("valid backup '%s' found\n", tdb_path_backup));
+ ret = tdb_backup_with_rotate(ctx, tdb_path_backup, tdb_path, 0,
+ corrupt_suffix, True, True);
+ if (ret != 0) {
+ DEBUG(1, ("Error restoring backup from '%s'\n",
+ tdb_path_backup));
+ } else {
+ DEBUG(1, ("Restored tdb backup from '%s'\n",
+ tdb_path_backup));
+ }
+ }
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
diff --git a/source3/lib/tdb_validate.h b/source3/lib/tdb_validate.h
new file mode 100644
index 0000000..3e7c20d
--- /dev/null
+++ b/source3/lib/tdb_validate.h
@@ -0,0 +1,79 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * A general tdb content validation mechanism
+ *
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TDB_VALIDATE_H__
+#define __TDB_VALIDATE_H__
+
+#include "lib/replace/replace.h"
+#include <tdb.h>
+
+/**
+ * Flag field for keeping track of the status of a validation.
+ */
+struct tdb_validation_status {
+ bool tdb_error;
+ bool bad_freelist;
+ bool bad_entry;
+ bool unknown_key;
+ bool success;
+};
+
+/**
+ * Callback function type for the validation mechanism.
+ */
+typedef int (*tdb_validate_data_func)(TDB_CONTEXT *the_tdb, TDB_DATA kbuf,
+ TDB_DATA dbuf, void *state);
+
+/**
+ * tdb validation function.
+ * returns 0 if tdb is ok, != 0 if it isn't.
+ * this function expects an opened tdb.
+ */
+int tdb_validate(struct tdb_context *tdb,
+ tdb_validate_data_func validate_fn);
+
+/**
+ * tdb validation function.
+ * returns 0 if tdb is ok, != 0 if it isn't.
+ * This is a wrapper around the actual validation function that
+ * opens and closes the tdb.
+ */
+int tdb_validate_open(const char *tdb_path,
+ tdb_validate_data_func validate_fn);
+
+/**
+ * validation function with backup handling:
+ *
+ * - calls tdb_validate
+ * - if the tdb is ok, create a backup "name.bak", possibly moving
+ * existing backup to name.bak.old,
+ * return 0 (success) even if the backup fails
+ * - if the tdb is corrupt:
+ * - move the tdb to "name.corrupt"
+ * - check if there is valid backup.
+ * if so, restore the backup.
+ * if restore is successful, return 0 (success),
+ * - otherwise return -1 (failure)
+ */
+int tdb_validate_and_backup(const char *tdb_path,
+ tdb_validate_data_func validate_fn);
+
+#endif /* __TDB_VALIDATE_H__ */
diff --git a/source3/lib/test_adouble.c b/source3/lib/test_adouble.c
new file mode 100644
index 0000000..615c224
--- /dev/null
+++ b/source3/lib/test_adouble.c
@@ -0,0 +1,389 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) 2021 Ralph Boehme <slow@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "adouble.c"
+#include <cmocka.h>
+
+static int setup_talloc_context(void **state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *state = frame;
+ return 0;
+}
+
+static int teardown_talloc_context(void **state)
+{
+ TALLOC_CTX *frame = *state;
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Basic and sane buffer.
+ */
+static uint8_t ad_basic[] = {
+ 0x00, 0x05, 0x16, 0x07, /* Magic */
+ 0x00, 0x02, 0x00, 0x00, /* Version */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x02, /* Count */
+ /* adentry 1: FinderInfo */
+ 0x00, 0x00, 0x00, 0x09, /* eid: FinderInfo */
+ 0x00, 0x00, 0x00, 0x32, /* offset */
+ 0x00, 0x00, 0x00, 0x20, /* length */
+ /* adentry 2: Resourcefork */
+ 0x00, 0x00, 0x00, 0x02, /* eid: Resourcefork */
+ 0x00, 0x00, 0x00, 0x52, /* offset */
+ 0xff, 0xff, 0xff, 0x00, /* length */
+ /* FinderInfo data: 32 bytes */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * An empty FinderInfo entry.
+ */
+static uint8_t ad_finderinfo1[] = {
+ 0x00, 0x05, 0x16, 0x07, /* Magic */
+ 0x00, 0x02, 0x00, 0x00, /* Version */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x02, /* Count */
+ /* adentry 1: FinderInfo */
+ 0x00, 0x00, 0x00, 0x09, /* eid: FinderInfo */
+ 0x00, 0x00, 0x00, 0x52, /* off: points at end of buffer */
+ 0x00, 0x00, 0x00, 0x00, /* len: 0, so off+len don't exceed bufferlen */
+ /* adentry 2: Resourcefork */
+ 0x00, 0x00, 0x00, 0x02, /* eid: Resourcefork */
+ 0x00, 0x00, 0x00, 0x52, /* offset */
+ 0xff, 0xff, 0xff, 0x00, /* length */
+ /* FinderInfo data: 32 bytes */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * A dangerous FinderInfo with correct length exceeding buffer by one byte.
+ */
+static uint8_t ad_finderinfo2[] = {
+ 0x00, 0x05, 0x16, 0x07, /* Magic */
+ 0x00, 0x02, 0x00, 0x00, /* Version */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x02, /* Count */
+ /* adentry 1: FinderInfo */
+ 0x00, 0x00, 0x00, 0x09, /* eid: FinderInfo */
+ 0x00, 0x00, 0x00, 0x33, /* off: points at beginng of data + 1 */
+ 0x00, 0x00, 0x00, 0x20, /* len: 32, so off+len exceeds bufferlen by 1 */
+ /* adentry 2: Resourcefork */
+ 0x00, 0x00, 0x00, 0x02, /* eid: Resourcefork */
+ 0x00, 0x00, 0x00, 0x52, /* offset */
+ 0xff, 0xff, 0xff, 0x00, /* length */
+ /* FinderInfo data: 32 bytes */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+static uint8_t ad_finderinfo3[] = {
+ 0x00, 0x05, 0x16, 0x07, /* Magic */
+ 0x00, 0x02, 0x00, 0x00, /* Version */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x02, /* Count */
+ /* adentry 1: FinderInfo */
+ 0x00, 0x00, 0x00, 0x09, /* eid: FinderInfo */
+ 0x00, 0x00, 0x00, 0x33, /* off: points at beginng of data + 1 */
+ 0x00, 0x00, 0x00, 0x1f, /* len: 31, so off+len don't exceed buf */
+ /* adentry 2: Resourcefork */
+ 0x00, 0x00, 0x00, 0x02, /* eid: Resourcefork */
+ 0x00, 0x00, 0x00, 0x52, /* offset */
+ 0xff, 0xff, 0xff, 0x00, /* length */
+ /* FinderInfo data: 32 bytes */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * A dangerous name entry.
+ */
+static uint8_t ad_name[] = {
+ 0x00, 0x05, 0x16, 0x07, /* Magic */
+ 0x00, 0x02, 0x00, 0x00, /* Version */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x02, /* Count */
+ /* adentry 1: FinderInfo */
+ 0x00, 0x00, 0x00, 0x09, /* eid: FinderInfo */
+ 0x00, 0x00, 0x00, 0x32, /* offset */
+ 0x00, 0x00, 0x00, 0x20, /* length */
+ /* adentry 2: Name */
+ 0x00, 0x00, 0x00, 0x03, /* eid: Name */
+ 0x00, 0x00, 0x00, 0x52, /* off: points at end of buffer */
+ 0x00, 0x00, 0x00, 0x01, /* len: 1, so off+len exceeds bufferlen */
+ /* FinderInfo data: 32 bytes */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * A empty ADEID_FILEDATESI entry.
+ */
+static uint8_t ad_date1[] = {
+ 0x00, 0x05, 0x16, 0x07, /* Magic */
+ 0x00, 0x02, 0x00, 0x00, /* Version */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x02, /* Count */
+ /* adentry 1: FinderInfo */
+ 0x00, 0x00, 0x00, 0x09, /* eid: FinderInfo */
+ 0x00, 0x00, 0x00, 0x32, /* offset */
+ 0x00, 0x00, 0x00, 0x20, /* length */
+ /* adentry 2: Dates */
+ 0x00, 0x00, 0x00, 0x08, /* eid: dates */
+ 0x00, 0x00, 0x00, 0x52, /* off: end of buffer */
+ 0x00, 0x00, 0x00, 0x00, /* len: 0, empty entry, valid */
+ /* FinderInfo data: 32 bytes */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+/*
+ * A dangerous ADEID_FILEDATESI entry, invalid length.
+ */
+static uint8_t ad_date2[] = {
+ 0x00, 0x05, 0x16, 0x07, /* Magic */
+ 0x00, 0x02, 0x00, 0x00, /* Version */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x00, 0x00, 0x00, /* Filler */
+ 0x00, 0x02, /* Count */
+ /* adentry 1: FinderInfo */
+ 0x00, 0x00, 0x00, 0x09, /* eid: FinderInfo */
+ 0x00, 0x00, 0x00, 0x32, /* offset */
+ 0x00, 0x00, 0x00, 0x20, /* length */
+ /* adentry 2: Dates */
+ 0x00, 0x00, 0x00, 0x08, /* eid: dates */
+ 0x00, 0x00, 0x00, 0x43, /* off: FinderInfo buf but one byte short */
+ 0x00, 0x00, 0x00, 0x0f, /* len: 15, so off+len don't exceed bufferlen */
+ /* FinderInfo data: 32 bytes */
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
+static struct adouble *parse_adouble(TALLOC_CTX *mem_ctx,
+ uint8_t *adbuf,
+ size_t adsize,
+ off_t filesize)
+{
+ struct adouble *ad = NULL;
+ bool ok;
+
+ ad = talloc_zero(mem_ctx, struct adouble);
+ ad->ad_data = talloc_zero_size(ad, adsize);
+ assert_non_null(ad);
+
+ memcpy(ad->ad_data, adbuf, adsize);
+
+ ok = ad_unpack(ad, 2, filesize);
+ if (!ok) {
+ return NULL;
+ }
+
+ return ad;
+}
+
+static void parse_abouble_basic(void **state)
+{
+ TALLOC_CTX *frame = *state;
+ struct adouble *ad = NULL;
+ char *p = NULL;
+
+ ad = parse_adouble(frame, ad_basic, sizeof(ad_basic), 0xffffff52);
+ assert_non_null(ad);
+
+ p = ad_get_entry(ad, ADEID_FINDERI);
+ assert_non_null(p);
+
+ return;
+}
+
+static void parse_abouble_finderinfo1(void **state)
+{
+ TALLOC_CTX *frame = *state;
+ struct adouble *ad = NULL;
+ char *p = NULL;
+
+ ad = parse_adouble(frame,
+ ad_finderinfo1,
+ sizeof(ad_finderinfo1),
+ 0xffffff52);
+ assert_non_null(ad);
+
+ p = ad_get_entry(ad, ADEID_FINDERI);
+ assert_null(p);
+
+ return;
+}
+
+static void parse_abouble_finderinfo2(void **state)
+{
+ TALLOC_CTX *frame = *state;
+ struct adouble *ad = NULL;
+
+ ad = parse_adouble(frame,
+ ad_finderinfo2,
+ sizeof(ad_finderinfo2),
+ 0xffffff52);
+ assert_null(ad);
+
+ return;
+}
+
+static void parse_abouble_finderinfo3(void **state)
+{
+ TALLOC_CTX *frame = *state;
+ struct adouble *ad = NULL;
+
+ ad = parse_adouble(frame,
+ ad_finderinfo3,
+ sizeof(ad_finderinfo3),
+ 0xffffff52);
+ assert_null(ad);
+
+ return;
+}
+
+static void parse_abouble_name(void **state)
+{
+ TALLOC_CTX *frame = *state;
+ struct adouble *ad = NULL;
+
+ ad = parse_adouble(frame, ad_name, sizeof(ad_name), 0x52);
+ assert_null(ad);
+
+ return;
+}
+
+static void parse_abouble_date1(void **state)
+{
+ TALLOC_CTX *frame = *state;
+ struct adouble *ad = NULL;
+ char *p = NULL;
+
+ ad = parse_adouble(frame, ad_date1, sizeof(ad_date1), 0x52);
+ assert_non_null(ad);
+
+ p = ad_get_entry(ad, ADEID_FILEDATESI);
+ assert_null(p);
+
+ return;
+}
+
+static void parse_abouble_date2(void **state)
+{
+ TALLOC_CTX *frame = *state;
+ struct adouble *ad = NULL;
+
+ ad = parse_adouble(frame, ad_date2, sizeof(ad_date2), 0x52);
+ assert_null(ad);
+
+ return;
+}
+
+int main(int argc, char *argv[])
+{
+ int rc;
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(parse_abouble_basic),
+ cmocka_unit_test(parse_abouble_finderinfo1),
+ cmocka_unit_test(parse_abouble_finderinfo2),
+ cmocka_unit_test(parse_abouble_finderinfo3),
+ cmocka_unit_test(parse_abouble_name),
+ cmocka_unit_test(parse_abouble_date1),
+ cmocka_unit_test(parse_abouble_date2),
+ };
+
+ if (argc == 2) {
+ cmocka_set_test_filter(argv[1]);
+ }
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ rc = cmocka_run_group_tests(tests,
+ setup_talloc_context,
+ teardown_talloc_context);
+
+ return rc;
+}
diff --git a/source3/lib/test_tldap.c b/source3/lib/test_tldap.c
new file mode 100644
index 0000000..659c5a7
--- /dev/null
+++ b/source3/lib/test_tldap.c
@@ -0,0 +1,63 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test suite for ldap client
+ *
+ * Copyright (C) 2018 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "source3/lib/tldap.c"
+
+static void test_tldap_unescape_ldapv3(void **state)
+{
+ const char *unescaped_dn = "(&(objectclass=group)(cn=Samba*))";
+ char dn[] = "\\28&\\28objectclass=group\\29\\28cn=Samba\\2a\\29\\29";
+ size_t dnlen = sizeof(dn);
+ bool ok;
+
+ ok = tldap_unescape_inplace(dn, &dnlen);
+ assert_true(ok);
+
+ assert_string_equal(dn, unescaped_dn);
+}
+
+static void test_tldap_unescape_ldapv2(void **state)
+{
+ const char *unescaped_dn = "(&(objectclass=group)(cn=Samba*))";
+ char dn[] = "\\(&\\(objectclass=group\\)\\(cn=Samba\\*\\)\\)";
+ size_t dnlen = sizeof(dn);
+ bool ok;
+
+ ok = tldap_unescape_inplace(dn, &dnlen);
+ assert_true(ok);
+
+ assert_string_equal(dn, unescaped_dn);
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_tldap_unescape_ldapv3),
+ cmocka_unit_test(test_tldap_unescape_ldapv2)
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source3/lib/tevent_barrier.c b/source3/lib/tevent_barrier.c
new file mode 100644
index 0000000..0fc0a31
--- /dev/null
+++ b/source3/lib/tevent_barrier.c
@@ -0,0 +1,192 @@
+/*
+ Unix SMB/CIFS implementation.
+ Implement a barrier
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "tevent_barrier.h"
+#include "lib/util/tevent_unix.h"
+
+struct tevent_barrier_waiter {
+ struct tevent_immediate *im;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+};
+
+struct tevent_barrier {
+ unsigned count;
+ struct tevent_barrier_waiter *waiters;
+ void (*trigger_cb)(void *private_data);
+ void *private_data;
+};
+
+static int tevent_barrier_destructor(struct tevent_barrier *b);
+static void tevent_barrier_release(struct tevent_barrier *b);
+static void tevent_barrier_release_one(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data);
+static void tevent_barrier_release_trigger(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data);
+
+struct tevent_barrier *tevent_barrier_init(
+ TALLOC_CTX *mem_ctx, unsigned count,
+ void (*trigger_cb)(void *private_data), void *private_data)
+{
+ struct tevent_barrier *b;
+ unsigned i;
+
+ if (count == 0) {
+ return NULL;
+ }
+
+ b = talloc(mem_ctx, struct tevent_barrier);
+ if (b == NULL) {
+ return NULL;
+ }
+ b->count = 0;
+ b->trigger_cb = trigger_cb;
+ b->private_data = private_data;
+
+ b->waiters = talloc_array(b, struct tevent_barrier_waiter, count);
+ if (b->waiters == NULL) {
+ goto fail;
+ }
+ for (i=0; i<count; i++) {
+ struct tevent_barrier_waiter *w = &b->waiters[i];
+
+ w->im = tevent_create_immediate(b->waiters);
+ if (w->im == NULL) {
+ goto fail;
+ }
+ w->req = NULL;
+ }
+ talloc_set_destructor(b, tevent_barrier_destructor);
+ return b;
+fail:
+ TALLOC_FREE(b);
+ return NULL;
+}
+
+static int tevent_barrier_destructor(struct tevent_barrier *b)
+{
+ tevent_barrier_release(b);
+ return 0;
+}
+
+struct tevent_barrier_wait_state {
+ struct tevent_barrier *b;
+ int index;
+};
+
+static void tevent_barrier_release(struct tevent_barrier *b)
+{
+ unsigned i;
+
+ for (i=0; i<b->count; i++) {
+ struct tevent_barrier_waiter *w = &b->waiters[i];
+ struct tevent_barrier_wait_state *state;
+
+ if (w->req == NULL) {
+ continue;
+ }
+ tevent_schedule_immediate(
+ w->im, w->ev, tevent_barrier_release_one, w->req);
+
+ state = tevent_req_data(
+ w->req, struct tevent_barrier_wait_state);
+ talloc_set_destructor(state, NULL);
+
+ w->req = NULL;
+ w->ev = NULL;
+ }
+ b->count = 0;
+ if (b->trigger_cb != NULL) {
+ b->trigger_cb(b->private_data);
+ }
+}
+
+static void tevent_barrier_release_one(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ tevent_req_done(req);
+}
+
+static int tevent_barrier_wait_state_destructor(
+ struct tevent_barrier_wait_state *s);
+
+struct tevent_req *tevent_barrier_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tevent_barrier *b)
+{
+ struct tevent_req *req;
+ struct tevent_barrier_wait_state *state;
+ struct tevent_barrier_waiter *w;
+ struct tevent_immediate *im;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tevent_barrier_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->b = b;
+ state->index = b->count;
+
+ w = &b->waiters[b->count];
+ w->ev = ev;
+ w->req = req;
+ b->count += 1;
+
+ talloc_set_destructor(state, tevent_barrier_wait_state_destructor);
+
+ if (b->count < talloc_array_length(b->waiters)) {
+ return req;
+ }
+
+ im = tevent_create_immediate(req);
+ if (tevent_req_nomem(im, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_schedule_immediate(im, ev, tevent_barrier_release_trigger, b);
+ return req;
+}
+
+static int tevent_barrier_wait_state_destructor(
+ struct tevent_barrier_wait_state *s)
+{
+ struct tevent_barrier *b = s->b;
+ b->waiters[s->index].req = b->waiters[b->count-1].req;
+ b->count -= 1;
+ return 0;
+}
+
+static void tevent_barrier_release_trigger(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct tevent_barrier *b = talloc_get_type_abort(
+ private_data, struct tevent_barrier);
+ tevent_barrier_release(b);
+}
+
+int tevent_barrier_wait_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
diff --git a/source3/lib/tevent_barrier.h b/source3/lib/tevent_barrier.h
new file mode 100644
index 0000000..2b35852
--- /dev/null
+++ b/source3/lib/tevent_barrier.h
@@ -0,0 +1,37 @@
+/*
+ Unix SMB/CIFS implementation.
+ Implement a barrier
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TEVENT_BARRIER_H
+#define _TEVENT_BARRIER_H
+
+#include "talloc.h"
+#include "tevent.h"
+
+struct tevent_barrier;
+
+struct tevent_barrier *tevent_barrier_init(
+ TALLOC_CTX *mem_ctx, unsigned count,
+ void (*trigger_cb)(void *private_data), void *private_data);
+
+struct tevent_req *tevent_barrier_wait_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tevent_barrier *b);
+int tevent_barrier_wait_recv(struct tevent_req *req);
+
+#endif /* _TEVENT_BARRIER_H */
diff --git a/source3/lib/tevent_glib_glue.c b/source3/lib/tevent_glib_glue.c
new file mode 100644
index 0000000..3be08fc
--- /dev/null
+++ b/source3/lib/tevent_glib_glue.c
@@ -0,0 +1,881 @@
+/*
+ Unix SMB/CIFS implementation.
+ Integration of a glib g_main_context into a tevent_context
+ Copyright (C) Stefan Metzmacher 2016
+ Copyright (C) Ralph Boehme 2016
+
+ ** NOTE! The following LGPL license applies to the tevent
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "lib/util/debug.h"
+#include "lib/util/select.h"
+#include <tevent.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_TEVENT
+
+#ifdef HAVE_GLIB
+#include <glib.h>
+#include "tevent_glib_glue.h"
+
+struct fd_map {
+ struct tevent_glib_glue *glue;
+ int fd;
+ struct tevent_fd *fd_event;
+};
+
+struct tevent_glib_glue {
+ /*
+ * The tevent context we're feeding.
+ */
+ struct tevent_context *ev;
+
+ /*
+ * The glib gmain context we're polling and whether we're currently
+ * owning it by virtue of g_main_context_acquire().
+ */
+ GMainContext *gmain_ctx;
+ bool gmain_owner;
+
+ /*
+ * Set by samba_tevent_glib_glue_quit().
+ */
+ bool quit;
+
+ /*
+ * tevent trace callback and data we got from tevent_get_trace_callback()
+ * before installing our own trace callback.
+ */
+ tevent_trace_callback_t prev_tevent_trace_cb;
+ void *prev_tevent_trace_data;
+
+ /*
+ * Don't call tevent_glib_prepare() in the tevent tracepoint handler if
+ * explicitly told so. This is an optimisation for the case that glib
+ * event sources are created from glib event callbacks.
+ */
+ bool skip_glib_refresh;
+
+ /*
+ * Used when acquiring the glib gmain context failed.
+ */
+ struct tevent_timer *acquire_retry_timer;
+
+ /*
+ * glib gmain context timeout and priority for the current event look
+ * iteration. gtimeout is translated to a tevent timer event, unless it
+ * is 0 which signals some event source is pending. In that case we
+ * dispatch an immediate event. gpriority is ignored by us, just passed
+ * to the glib relevant functions.
+ */
+ gint gtimeout;
+ gint gpriority;
+ struct tevent_timer *timer;
+ struct tevent_immediate *im;
+ bool scheduled_im;
+
+ /*
+ * glib gmain context fds returned from g_main_context_query(). These
+ * get translated to tevent fd events.
+ */
+ GPollFD *gpollfds;
+ gint num_gpollfds;
+
+ /*
+ * A copy of gpollfds and num_gpollfds from the previous event loop
+ * iteration, used to detect changes in the set of fds.
+ */
+ GPollFD *prev_gpollfds;
+ gint num_prev_gpollfds;
+
+ /*
+ * An array of pointers to fd_map's. The fd_map'd contain the tevent
+ * event fd as well as a pointer to the corresponding glib GPollFD.
+ */
+ struct fd_map **fd_map;
+ size_t num_maps;
+};
+
+static bool tevent_glib_prepare(struct tevent_glib_glue *glue);
+static bool tevent_glib_process(struct tevent_glib_glue *glue);
+static bool tevent_glib_glue_reinit(struct tevent_glib_glue *glue);
+static void tevent_glib_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+typedef int (*gfds_cmp_cb)(const void *fd1, const void *fd2);
+typedef bool (*gfds_found_cb)(struct tevent_glib_glue *glue,
+ const GPollFD *new,
+ const GPollFD *old);
+typedef bool (*gfds_new_cb)(struct tevent_glib_glue *glue,
+ const GPollFD *fd);
+typedef bool (*gfds_removed_cb)(struct tevent_glib_glue *glue,
+ const GPollFD *fd);
+
+/**
+ * Compare two sorted GPollFD arrays
+ *
+ * For every element that exists in gfds and prev_gfds found_fn() is called.
+ * For every element in gfds but not in prev_gfds, new_fn() is called.
+ * For every element in prev_gfds but not in gfds removed_fn() is called.
+ **/
+static bool cmp_gfds(struct tevent_glib_glue *glue,
+ GPollFD *gfds,
+ GPollFD *prev_gfds,
+ size_t num_gfds,
+ size_t num_prev_gfds,
+ gfds_cmp_cb cmp_cb,
+ gfds_found_cb found_cb,
+ gfds_new_cb new_cb,
+ gfds_removed_cb removed_cb)
+{
+ bool ok;
+ size_t i = 0, j = 0;
+ int cmp;
+
+ while (i < num_gfds && j < num_prev_gfds) {
+ cmp = cmp_cb(&gfds[i], &prev_gfds[j]);
+ if (cmp == 0) {
+ ok = found_cb(glue, &gfds[i], &prev_gfds[j]);
+ if (!ok) {
+ return false;
+ }
+ i++;
+ j++;
+ } else if (cmp < 0) {
+ ok = new_cb(glue, &gfds[i]);
+ if (!ok) {
+ return false;
+ }
+ i++;
+ } else {
+ ok = removed_cb(glue, &prev_gfds[j]);
+ if (!ok) {
+ return false;
+ }
+ j++;
+ }
+ }
+
+ while (i < num_gfds) {
+ ok = new_cb(glue, &gfds[i++]);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ while (j < num_prev_gfds) {
+ ok = removed_cb(glue, &prev_gfds[j++]);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int glib_fd_cmp_func(const void *p1, const void *p2)
+{
+ const GPollFD *lhs = p1;
+ const GPollFD *rhs = p2;
+
+ if (lhs->fd < rhs->fd) {
+ return -1;
+ } else if (lhs->fd > rhs->fd) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * We already have a tevent fd event for the glib GPollFD, but we may have to
+ * update flags.
+ */
+static bool match_gfd_cb(struct tevent_glib_glue *glue,
+ const GPollFD *new_gfd,
+ const GPollFD *old_gfd)
+{
+ size_t i;
+ struct fd_map *fd_map = NULL;
+ struct tevent_fd *fd_event = NULL;
+
+ if (new_gfd->events == old_gfd->events) {
+ return true;
+ }
+
+ for (i = 0; i < glue->num_maps; i++) {
+ if (glue->fd_map[i]->fd == new_gfd->fd) {
+ break;
+ }
+ }
+
+ if (i == glue->num_maps) {
+ DBG_ERR("match_gfd_cb: glib fd %d not in map\n", new_gfd->fd);
+ return false;
+ }
+
+ fd_map = glue->fd_map[i];
+ if (fd_map == NULL) {
+ DBG_ERR("fd_map for fd %d is NULL\n", new_gfd->fd);
+ return false;
+ }
+
+ fd_event = fd_map->fd_event;
+ if (fd_event == NULL) {
+ DBG_ERR("fd_event for fd %d is NULL\n", new_gfd->fd);
+ return false;
+ }
+
+ tevent_fd_set_flags(fd_event, 0);
+
+ if (new_gfd->events & (G_IO_IN | G_IO_HUP | G_IO_ERR)) {
+ TEVENT_FD_READABLE(fd_event);
+ }
+ if (new_gfd->events & G_IO_OUT) {
+ TEVENT_FD_WRITEABLE(fd_event);
+ }
+
+ return true;
+}
+
+static bool new_gfd_cb(struct tevent_glib_glue *glue, const GPollFD *gfd)
+{
+ struct tevent_fd *fd_event = NULL;
+ struct fd_map *fd_map = NULL;
+ uint16_t events = 0;
+ bool revent;
+ bool wevent;
+
+ revent = (gfd->events & (G_IO_IN | G_IO_HUP | G_IO_ERR));
+ wevent = (gfd->events & G_IO_OUT);
+ if (revent) {
+ events |= TEVENT_FD_READ;
+ }
+ if (wevent) {
+ events |= TEVENT_FD_WRITE;
+ }
+
+ glue->fd_map = talloc_realloc(glue,
+ glue->fd_map,
+ struct fd_map *,
+ glue->num_maps + 1);
+ if (glue->fd_map == NULL) {
+ DBG_ERR("talloc_realloc failed\n");
+ return false;
+ }
+ fd_map = talloc_zero(glue->fd_map, struct fd_map);
+ if (fd_map == NULL) {
+ DBG_ERR("talloc_realloc failed\n");
+ return false;
+ }
+ glue->fd_map[glue->num_maps] = fd_map;
+ glue->num_maps++;
+
+ fd_event = tevent_add_fd(glue->ev,
+ glue->fd_map,
+ gfd->fd,
+ events,
+ tevent_glib_fd_handler,
+ fd_map);
+ if (fd_event == NULL) {
+ DBG_ERR("tevent_add_fd failed\n");
+ return false;
+ }
+
+ *fd_map = (struct fd_map) {
+ .glue = glue,
+ .fd = gfd->fd,
+ .fd_event = fd_event,
+ };
+
+ DBG_DEBUG("added tevent_fd for glib fd %d\n", gfd->fd);
+
+ return true;
+}
+
+static bool remove_gfd_cb(struct tevent_glib_glue *glue, const GPollFD *gfd)
+{
+ size_t i;
+
+ for (i = 0; i < glue->num_maps; i++) {
+ if (glue->fd_map[i]->fd == gfd->fd) {
+ break;
+ }
+ }
+
+ if (i == glue->num_maps) {
+ DBG_ERR("remove_gfd_cb: glib fd %d not in map\n", gfd->fd);
+ return false;
+ }
+
+ TALLOC_FREE(glue->fd_map[i]->fd_event);
+ TALLOC_FREE(glue->fd_map[i]);
+
+ if (i + 1 < glue->num_maps) {
+ memmove(&glue->fd_map[i],
+ &glue->fd_map[i+1],
+ (glue->num_maps - (i + 1)) * sizeof(struct fd_map *));
+ }
+
+ glue->fd_map = talloc_realloc(glue,
+ glue->fd_map,
+ struct fd_map *,
+ glue->num_maps - 1);
+ if (glue->num_maps > 0 && glue->fd_map == NULL) {
+ DBG_ERR("talloc_realloc failed\n");
+ return false;
+ }
+ glue->num_maps--;
+
+ return true;
+}
+
+static short gpoll_to_poll_event(gushort gevent)
+{
+ short pevent = 0;
+
+ if (gevent & G_IO_IN) {
+ pevent |= POLLIN;
+ }
+ if (gevent & G_IO_OUT) {
+ pevent |= POLLOUT;
+ }
+ if (gevent & G_IO_HUP) {
+ pevent |= POLLHUP;
+ }
+ if (gevent & G_IO_ERR) {
+ pevent |= POLLERR;
+ }
+
+ return pevent;
+}
+
+static gushort poll_to_gpoll_event(short pevent)
+{
+ gushort gevent = 0;
+
+ if (pevent & POLLIN) {
+ gevent |= G_IO_IN;
+ }
+ if (pevent & POLLOUT) {
+ gevent |= G_IO_OUT;
+ }
+ if (pevent & POLLHUP) {
+ gevent |= G_IO_HUP;
+ }
+ if (pevent & POLLERR) {
+ gevent |= G_IO_ERR;
+ }
+
+ return gevent;
+}
+
+static void tevent_glib_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct fd_map *fd_map = talloc_get_type_abort(
+ private_data, struct fd_map);
+ struct tevent_glib_glue *glue = NULL;
+ GPollFD *gpollfd = NULL;
+ struct pollfd fd;
+ int ret;
+ int i;
+
+ glue = fd_map->glue;
+
+ for (i = 0; i < glue->num_gpollfds; i++) {
+ if (glue->gpollfds[i].fd != fd_map->fd) {
+ continue;
+ }
+ gpollfd = &glue->gpollfds[i];
+ break;
+ }
+ if (gpollfd == NULL) {
+ DBG_ERR("No gpollfd for fd_map [%p] fd [%d]\n",
+ fd_map, fd_map->fd);
+ return;
+ }
+ /*
+ * We have to poll() the fd to get the correct fd event for glib. tevent
+ * only tells us about readable/writable in flags, but we need the full
+ * glory for glib.
+ */
+
+ fd = (struct pollfd) {
+ .fd = gpollfd->fd,
+ .events = gpoll_to_poll_event(gpollfd->events),
+ };
+
+ ret = sys_poll_intr(&fd, 1, 0);
+ if (ret == -1) {
+ DBG_ERR("poll: %s\n", strerror(errno));
+ return;
+ }
+ if (ret == 0) {
+ return;
+ }
+
+ gpollfd->revents = poll_to_gpoll_event(fd.revents);
+
+ tevent_glib_process(glue);
+ return;
+}
+
+static void tevent_glib_timer_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct tevent_glib_glue *glue = talloc_get_type_abort(
+ private_data, struct tevent_glib_glue);
+
+ glue->timer = NULL;
+ tevent_glib_process(glue);
+ return;
+}
+
+static void tevent_glib_im_handler(struct tevent_context *ev,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct tevent_glib_glue *glue = talloc_get_type_abort(
+ private_data, struct tevent_glib_glue);
+
+ glue->scheduled_im = false;
+ tevent_glib_process(glue);
+ return;
+}
+
+static bool save_current_fdset(struct tevent_glib_glue *glue)
+{
+ /*
+ * Save old glib fds. We only grow the prev array.
+ */
+
+ if (glue->num_prev_gpollfds < glue->num_gpollfds) {
+ glue->prev_gpollfds = talloc_realloc(glue,
+ glue->prev_gpollfds,
+ GPollFD,
+ glue->num_gpollfds);
+ if (glue->prev_gpollfds == NULL) {
+ DBG_ERR("talloc_realloc failed\n");
+ return false;
+ }
+ }
+ glue->num_prev_gpollfds = glue->num_gpollfds;
+ if (glue->num_gpollfds > 0) {
+ memcpy(glue->prev_gpollfds, glue->gpollfds,
+ sizeof(GPollFD) * glue->num_gpollfds);
+ memset(glue->gpollfds, 0, sizeof(GPollFD) * glue->num_gpollfds);
+ }
+
+ return true;
+}
+
+static bool get_glib_fds_and_timeout(struct tevent_glib_glue *glue)
+{
+ bool ok;
+ gint num_fds;
+
+ ok = save_current_fdset(glue);
+ if (!ok) {
+ return false;
+ }
+
+ while (true) {
+ num_fds = g_main_context_query(glue->gmain_ctx,
+ glue->gpriority,
+ &glue->gtimeout,
+ glue->gpollfds,
+ glue->num_gpollfds);
+ if (num_fds == glue->num_gpollfds) {
+ break;
+ }
+ glue->gpollfds = talloc_realloc(glue,
+ glue->gpollfds,
+ GPollFD,
+ num_fds);
+ if (num_fds > 0 && glue->gpollfds == NULL) {
+ DBG_ERR("talloc_realloc failed\n");
+ return false;
+ }
+ glue->num_gpollfds = num_fds;
+ };
+
+ if (glue->num_gpollfds > 0) {
+ qsort(glue->gpollfds,
+ num_fds,
+ sizeof(GPollFD),
+ glib_fd_cmp_func);
+ }
+
+ DBG_DEBUG("num fds: %d, timeout: %d ms\n",
+ num_fds, glue->gtimeout);
+
+ return true;
+}
+
+static bool tevent_glib_update_events(struct tevent_glib_glue *glue)
+{
+ struct timeval tv;
+ bool ok;
+
+ ok = cmp_gfds(glue,
+ glue->gpollfds,
+ glue->prev_gpollfds,
+ glue->num_gpollfds,
+ glue->num_prev_gpollfds,
+ glib_fd_cmp_func,
+ match_gfd_cb,
+ new_gfd_cb,
+ remove_gfd_cb);
+ if (!ok) {
+ return false;
+ }
+
+ TALLOC_FREE(glue->timer);
+
+ if (glue->gtimeout == -1) {
+ return true;
+ }
+
+ if (glue->gtimeout == 0) {
+ /*
+ * glue->gtimeout is 0 if g_main_context_query() returned
+ * timeout=0. That means there are pending events ready to be
+ * dispatched. We only want to run one event handler per loop
+ * iteration, so we schedule an immediate event to run it in the
+ * next iteration.
+ */
+ if (glue->scheduled_im) {
+ return true;
+ }
+ tevent_schedule_immediate(glue->im,
+ glue->ev,
+ tevent_glib_im_handler,
+ glue);
+ glue->scheduled_im = true;
+ return true;
+ }
+
+ tv = tevent_timeval_current_ofs(glue->gtimeout / 1000,
+ (glue->gtimeout % 1000) * 1000);
+
+ glue->timer = tevent_add_timer(glue->ev,
+ glue,
+ tv,
+ tevent_glib_timer_handler,
+ glue);
+ if (glue->timer == NULL) {
+ DBG_ERR("tevent_add_timer failed\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void tevent_glib_retry_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct tevent_glib_glue *glue = talloc_get_type_abort(
+ private_data, struct tevent_glib_glue);
+
+ glue->acquire_retry_timer = NULL;
+ (void)tevent_glib_prepare(glue);
+}
+
+/**
+ * Fetch glib event sources and add them to tevent
+ *
+ * Fetch glib event sources and attach corresponding tevent events to our tevent
+ * context. get_glib_fds_and_timeout() gets the relevant glib event sources: the
+ * set of active fds and the next timer. tevent_glib_update_events() then
+ * translates those to tevent and creates tevent events.
+ *
+ * When called, the thread must NOT be the owner to the glib main
+ * context. tevent_glib_prepare() is either the first function when the
+ * tevent_glib_glue is created, or after tevent_glib_process() has been called
+ * to process pending event, which will have ceased ownership.
+ **/
+static bool tevent_glib_prepare(struct tevent_glib_glue *glue)
+{
+ bool ok;
+ gboolean gok;
+
+ if (glue->quit) {
+ /* Set via samba_tevent_glib_glue_quit() */
+ return true;
+ }
+
+ if (glue->acquire_retry_timer != NULL) {
+ /*
+ * We're still waiting on the below g_main_context_acquire() to
+ * succeed, just return.
+ */
+ return true;
+ }
+
+ if (glue->gmain_owner) {
+ g_main_context_release(glue->gmain_ctx);
+ glue->gmain_owner = false;
+ }
+
+ gok = g_main_context_acquire(glue->gmain_ctx);
+ if (!gok) {
+ DBG_ERR("couldn't acquire g_main_context\n");
+
+ /*
+ * Ensure no tevent event fires while we're not the gmain
+ * context owner. The event handler would call
+ * tevent_glib_process() and that expects being the owner of the
+ * context.
+ */
+ ok = tevent_glib_glue_reinit(glue);
+ if (!ok) {
+ DBG_ERR("tevent_glib_glue_reinit failed\n");
+ samba_tevent_glib_glue_quit(glue);
+ return false;
+ }
+
+ glue->acquire_retry_timer = tevent_add_timer(
+ glue->ev,
+ glue,
+ tevent_timeval_current_ofs(0, 1000),
+ tevent_glib_retry_timer,
+ glue);
+ if (glue->acquire_retry_timer == NULL) {
+ DBG_ERR("tevent_add_timer failed\n");
+ samba_tevent_glib_glue_quit(glue);
+ return false;
+ }
+ return true;
+ }
+ glue->gmain_owner = true;
+
+ /*
+ * Discard "ready" return value from g_main_context_prepare(). We don't
+ * want to dispatch events here, that's only done in from the tevent loop.
+ */
+ (void)g_main_context_prepare(glue->gmain_ctx, &glue->gpriority);
+
+ ok = get_glib_fds_and_timeout(glue);
+ if (!ok) {
+ DBG_ERR("get_glib_fds_and_timeout failed\n");
+ samba_tevent_glib_glue_quit(glue);
+ return false;
+ }
+
+ ok = tevent_glib_update_events(glue);
+ if (!ok) {
+ DBG_ERR("tevent_glib_update_events failed\n");
+ samba_tevent_glib_glue_quit(glue);
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Process pending glib events
+ *
+ * tevent_glib_process() gets called to process pending glib events via
+ * g_main_context_check() and then g_main_context_dispatch().
+ *
+ * After pending event handlers are dispatched, we rearm the glib glue event
+ * handlers in tevent by calling tevent_glib_prepare().
+ *
+ * When tevent_glib_process() is called the thread must own the glib
+ * gmain_ctx. That is achieved by tevent_glib_prepare() being the only function
+ * that acquires context ownership.
+ *
+ * To give other threads that are blocked on g_main_context_acquire(gmain_ctx) a
+ * chance to acquire context ownership (eg needed to attach event sources), we
+ * release context ownership before calling tevent_glib_prepare() which will
+ * acquire it again.
+ */
+static bool tevent_glib_process(struct tevent_glib_glue *glue)
+{
+ bool ok;
+
+ DBG_DEBUG("tevent_glib_process\n");
+
+ /*
+ * Ignore the "sources_ready" return from g_main_context_check(). glib
+ * itself also ignores it in g_main_context_iterate(). In theory only
+ * calling g_main_context_dispatch() if g_main_context_check() returns
+ * true should work, but older glib versions had a bug where
+ * g_main_context_check() returns false even though events are pending.
+ *
+ * https://bugzilla.gnome.org/show_bug.cgi?id=11059
+ */
+ (void)g_main_context_check(glue->gmain_ctx,
+ glue->gpriority,
+ glue->gpollfds,
+ glue->num_gpollfds);
+
+ g_main_context_dispatch(glue->gmain_ctx);
+
+ ok = tevent_glib_prepare(glue);
+ if (!ok) {
+ return false;
+ }
+ glue->skip_glib_refresh = true;
+ return true;
+}
+
+static void tevent_glib_glue_trace_callback(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct tevent_glib_glue *glue = talloc_get_type_abort(
+ private_data, struct tevent_glib_glue);
+
+ if (point == TEVENT_TRACE_AFTER_LOOP_ONCE) {
+ if (!glue->skip_glib_refresh) {
+ tevent_glib_prepare(glue);
+ }
+ glue->skip_glib_refresh = false;
+ }
+
+ /* chain previous handler */
+ if (glue->prev_tevent_trace_cb != NULL) {
+ glue->prev_tevent_trace_cb(point, glue->prev_tevent_trace_data);
+ }
+}
+
+static void tevent_glib_glue_cleanup(struct tevent_glib_glue *glue)
+{
+ size_t n = talloc_array_length(glue->fd_map);
+ size_t i;
+
+ for (i = 0; i < n; i++) {
+ TALLOC_FREE(glue->fd_map[i]->fd_event);
+ TALLOC_FREE(glue->fd_map[i]);
+ }
+
+ tevent_set_trace_callback(glue->ev,
+ glue->prev_tevent_trace_cb,
+ glue->prev_tevent_trace_data);
+ glue->prev_tevent_trace_cb = NULL;
+ glue->prev_tevent_trace_data = NULL;
+
+ TALLOC_FREE(glue->fd_map);
+ glue->num_maps = 0;
+
+ TALLOC_FREE(glue->gpollfds);
+ glue->num_gpollfds = 0;
+
+ TALLOC_FREE(glue->prev_gpollfds);
+ glue->num_prev_gpollfds = 0;
+
+ TALLOC_FREE(glue->timer);
+ TALLOC_FREE(glue->acquire_retry_timer);
+ TALLOC_FREE(glue->im);
+
+ /*
+ * These are not really needed, but let's wipe the slate clean.
+ */
+ glue->skip_glib_refresh = false;
+ glue->gtimeout = 0;
+ glue->gpriority = 0;
+}
+
+static bool tevent_glib_glue_reinit(struct tevent_glib_glue *glue)
+{
+ tevent_glib_glue_cleanup(glue);
+
+ glue->im = tevent_create_immediate(glue);
+ if (glue->im == NULL) {
+ return false;
+ }
+
+ tevent_get_trace_callback(glue->ev,
+ &glue->prev_tevent_trace_cb,
+ &glue->prev_tevent_trace_data);
+ tevent_set_trace_callback(glue->ev,
+ tevent_glib_glue_trace_callback,
+ glue);
+
+ return true;
+}
+
+void samba_tevent_glib_glue_quit(struct tevent_glib_glue *glue)
+{
+ tevent_glib_glue_cleanup(glue);
+ glue->quit = true;
+ return;
+}
+
+struct tevent_glib_glue *samba_tevent_glib_glue_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ GMainContext *gmain_ctx)
+{
+ bool ok;
+ struct tevent_glib_glue *glue = NULL;
+
+ glue = talloc_zero(mem_ctx, struct tevent_glib_glue);
+ if (glue == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return NULL;
+ }
+
+ *glue = (struct tevent_glib_glue) {
+ .ev = ev,
+ .gmain_ctx = gmain_ctx,
+ };
+
+ glue->im = tevent_create_immediate(glue);
+
+ tevent_get_trace_callback(glue->ev,
+ &glue->prev_tevent_trace_cb,
+ &glue->prev_tevent_trace_data);
+ tevent_set_trace_callback(glue->ev,
+ tevent_glib_glue_trace_callback,
+ glue);
+
+ ok = tevent_glib_prepare(glue);
+ if (!ok) {
+ TALLOC_FREE(glue);
+ return NULL;
+ }
+
+ return glue;
+}
+
+#else /* HAVE_GLIB */
+
+struct tevent_glib_glue *samba_tevent_glib_glue_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ GMainContext *gmain_ctx)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+
+void samba_tevent_glib_glue_quit(struct tevent_glib_glue *glue)
+{
+ return;
+}
+#endif /* HAVE_GLIB */
diff --git a/source3/lib/tevent_glib_glue.h b/source3/lib/tevent_glib_glue.h
new file mode 100644
index 0000000..0d001fa
--- /dev/null
+++ b/source3/lib/tevent_glib_glue.h
@@ -0,0 +1,68 @@
+/*
+ Unix SMB/CIFS implementation.
+ Poll glib event loop from tevent
+
+ Copyright (C) Ralph Boehme 2016
+
+ ** NOTE! The following LGPL license applies to the tevent
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _TEVENT_GLIB_GLUE_H
+#define _TEVENT_GLIB_GLUE_H
+
+#include <talloc.h>
+#include <tevent.h>
+
+/**
+ * @brief Add a glib GmainContext to a tevent context
+ *
+ * tevent will poll the glib event sources and run handlers for
+ * pending events as detailed in the glib documentation:
+ *
+ * https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html
+ *
+ * If tevent was built without glib support, this function will always return
+ * NULL with an error number ENOSYS.
+ *
+ * @param[in] mem_ctx Memory context to use
+ *
+ * @param[in] ev Event context to use
+ *
+ * @param[in] gmain_ctx GMainContext that will be added to tevent
+ *
+ * @return A handle on the glue context that binds the
+ * the GMainContext to tevent. Pass the glue handle to
+ * tevent_glib_glue_quit() in a callback when you want
+ * stop processing glib events.
+ * You must not call talloc_free() on the handle while
+ * the loop is still in use and attached to tevent.
+ */
+struct tevent_glib_glue *samba_tevent_glib_glue_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ GMainContext *gmain_ctx);
+
+/**
+ * @brief Stop polling a GMainContext
+ *
+ * Used in a callback when you want to stop processing glib events.
+ *
+ * @param[in] glue And tevent_glib_glue handle
+ */
+void samba_tevent_glib_glue_quit(struct tevent_glib_glue *glue);
+
+#endif
diff --git a/source3/lib/tevent_glib_glue_tests.c b/source3/lib/tevent_glib_glue_tests.c
new file mode 100644
index 0000000..d6cf664
--- /dev/null
+++ b/source3/lib/tevent_glib_glue_tests.c
@@ -0,0 +1,397 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ testing of the tevent glib glue subsystem
+
+ Copyright (C) Ralph Boehme 2016
+
+ glib tests adapted from glib2 glib/tests/mainloop.c
+ Copyright (C) 2011 Red Hat Inc., Matthias Clasen
+
+ ** NOTE! The following LGPL license applies to the tevent
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+
+/*
+ * glib uses TRUE and FALSE which may have redefined by "includes.h" to be
+ * unusable. Unndefine so glib can establish its own working replacement.
+ */
+#undef TRUE
+#undef FALSE
+#include <glib.h>
+#include <glib-unix.h>
+#include "lib/tevent_glib_glue.h"
+
+/*
+ * Unfortunately the glib test suite runner doesn't pass args to tests
+ * so we must keep a few globals here.
+ */
+static struct tevent_context *ev;
+
+static gboolean count_calls(gpointer data)
+{
+ gint *i = (gint *)data;
+
+ (*i)++;
+
+ return TRUE;
+}
+
+static gboolean quit_loop(gpointer data)
+{
+ struct tevent_glib_glue *glue = talloc_get_type_abort(
+ data, struct tevent_glib_glue);
+
+ samba_tevent_glib_glue_quit(glue);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void test_timeouts(void)
+{
+ GMainContext *ctx = NULL;
+ struct tevent_glib_glue *glue = NULL;
+ GSource *source = NULL;
+ gint a;
+ gint b;
+ gint c;
+
+ a = b = c = 0;
+
+ ctx = g_main_context_new();
+ glue = samba_tevent_glib_glue_create(ev, ev, ctx);
+ g_assert(glue != NULL);
+
+ source = g_timeout_source_new(100);
+ g_source_set_callback(source, count_calls, &a, NULL);
+ g_source_attach(source, ctx);
+ g_source_unref(source);
+
+ source = g_timeout_source_new(250);
+ g_source_set_callback(source, count_calls, &b, NULL);
+ g_source_attach(source, ctx);
+ g_source_unref(source);
+
+ source = g_timeout_source_new(330);
+ g_source_set_callback(source, count_calls, &c, NULL);
+ g_source_attach(source, ctx);
+ g_source_unref(source);
+
+ source = g_timeout_source_new(1050);
+ g_source_set_callback(source, quit_loop, glue, NULL);
+ g_source_attach(source, ctx);
+ g_source_unref(source);
+
+ g_assert(tevent_loop_wait(ev) == 0);
+
+ /* We may be delayed for an arbitrary amount of time - for example,
+ * it's possible for all timeouts to fire exactly once.
+ */
+ g_assert_cmpint(a, >, 0);
+ g_assert_cmpint(a, >=, b);
+ g_assert_cmpint(b, >=, c);
+
+ g_assert_cmpint(a, <=, 10);
+ g_assert_cmpint(b, <=, 4);
+ g_assert_cmpint(c, <=, 3);
+
+ samba_tevent_glib_glue_quit(glue);
+ TALLOC_FREE(glue);
+ g_main_context_unref(ctx);
+}
+
+struct test_glib_ev_source_data {
+ GMainContext *ctx;
+ struct tevent_glib_glue *glue;
+};
+
+static gboolean test_glib_ev_source_quit_loop(gpointer data);
+
+static gboolean test_glib_ev_source_timeout_cb(gpointer data)
+{
+ struct test_glib_ev_source_data *state = talloc_get_type_abort(
+ data, struct test_glib_ev_source_data);
+ GSource *source = NULL;
+
+ source = g_timeout_source_new(100);
+ g_source_set_callback(source,
+ test_glib_ev_source_quit_loop,
+ state,
+ NULL);
+ g_source_attach(source, state->ctx);
+ g_source_unref(source);
+
+ return TRUE;
+}
+
+static gboolean test_glib_ev_source_quit_loop(gpointer data)
+{
+ struct test_glib_ev_source_data *state = talloc_get_type_abort(
+ data, struct test_glib_ev_source_data);
+
+ samba_tevent_glib_glue_quit(state->glue);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void test_glib_ev_source(void)
+{
+ GMainContext *ctx = NULL;
+ struct tevent_glib_glue *glue = NULL;
+ struct test_glib_ev_source_data *state = NULL;
+ GSource *source = NULL;
+
+ ctx = g_main_context_new();
+ g_assert(ctx != NULL);
+
+ glue = samba_tevent_glib_glue_create(ev, ev, ctx);
+ g_assert(glue != NULL);
+
+ state = talloc_zero(glue, struct test_glib_ev_source_data);
+ g_assert(state != NULL);
+
+ state->ctx = ctx;
+ state->glue = glue;
+
+ source = g_timeout_source_new(100);
+ g_source_set_callback(source,
+ test_glib_ev_source_timeout_cb,
+ state,
+ NULL);
+ g_source_attach(source, ctx);
+ g_source_unref(source);
+
+ g_assert(tevent_loop_wait(ev) == 0);
+
+ TALLOC_FREE(glue);
+ g_main_context_unref(ctx);
+}
+
+struct test_tevent_ev_source_data {
+ GMainContext *ctx;
+ struct tevent_glib_glue *glue;
+};
+
+static gboolean test_tevent_ev_source_quit_loop(gpointer data);
+
+static void test_tevent_ev_source_timeout_cb(struct tevent_context *_ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *data)
+{
+ struct test_tevent_ev_source_data *state = talloc_get_type_abort(
+ data, struct test_tevent_ev_source_data);
+ GSource *source = NULL;
+
+ source = g_timeout_source_new(100);
+ g_source_set_callback(source,
+ test_tevent_ev_source_quit_loop,
+ state,
+ NULL);
+ g_source_attach(source, state->ctx);
+ g_source_unref(source);
+
+ return;
+}
+
+static gboolean test_tevent_ev_source_quit_loop(gpointer data)
+{
+ struct test_tevent_ev_source_data *state = talloc_get_type_abort(
+ data, struct test_tevent_ev_source_data);
+
+ samba_tevent_glib_glue_quit(state->glue);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void test_tevent_ev_source(void)
+{
+ GMainContext *ctx = NULL;
+ struct tevent_glib_glue *glue = NULL;
+ struct test_tevent_ev_source_data *state = NULL;
+ struct tevent_timer *timer = NULL;
+
+ ctx = g_main_context_new();
+ g_assert(ctx != NULL);
+
+ glue = samba_tevent_glib_glue_create(ev, ev, ctx);
+ g_assert(glue != NULL);
+
+ state = talloc_zero(glue, struct test_tevent_ev_source_data);
+ g_assert(state != NULL);
+
+ state->ctx = ctx;
+ state->glue = glue;
+
+ timer = tevent_add_timer(ev,
+ state,
+ tevent_timeval_current_ofs(0, 1000),
+ test_tevent_ev_source_timeout_cb,
+ state);
+ g_assert(timer != NULL);
+
+ g_assert(tevent_loop_wait(ev) == 0);
+
+ TALLOC_FREE(glue);
+ g_main_context_unref(ctx);
+}
+
+static gchar zeros[1024];
+
+static gsize fill_a_pipe(gint fd)
+{
+ gsize written = 0;
+ GPollFD pfd;
+
+ pfd.fd = fd;
+ pfd.events = G_IO_OUT;
+ while (g_poll(&pfd, 1, 0) == 1)
+ /* we should never see -1 here */
+ written += write(fd, zeros, sizeof zeros);
+
+ return written;
+}
+
+static gboolean write_bytes(gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ gssize *to_write = user_data;
+ gint limit;
+
+ if (*to_write == 0)
+ return FALSE;
+
+ /* Detect if we run before we should */
+ g_assert(*to_write >= 0);
+
+ limit = MIN(*to_write, sizeof zeros);
+ *to_write -= write(fd, zeros, limit);
+
+ return TRUE;
+}
+
+static gboolean read_bytes(gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ static gchar buffer[1024];
+ gssize *to_read = user_data;
+
+ *to_read -= read(fd, buffer, sizeof buffer);
+
+ /* The loop will exit when there is nothing else to read, then we will
+ * use g_source_remove() to destroy this source.
+ */
+ return TRUE;
+}
+
+static void test_unix_fd(void)
+{
+ gssize to_write = -1;
+ gssize to_read;
+ gint fds[2];
+ gint a, b;
+ gint s;
+ GSource *source_a = NULL;
+ GSource *source_b = NULL;
+ struct tevent_glib_glue *glue = NULL;
+
+ glue = samba_tevent_glib_glue_create(ev, ev, g_main_context_default());
+ g_assert(glue != NULL);
+
+ s = pipe(fds);
+ g_assert(s == 0);
+
+ to_read = fill_a_pipe(fds[1]);
+ /* write at higher priority to keep the pipe full... */
+ a = g_unix_fd_add_full(G_PRIORITY_HIGH,
+ fds[1],
+ G_IO_OUT,
+ write_bytes,
+ &to_write,
+ NULL);
+ source_a = g_source_ref(g_main_context_find_source_by_id(NULL, a));
+ /* make sure no 'writes' get dispatched yet */
+ while (tevent_loop_once(ev));
+
+ to_read += 128 * 1024 * 1024;
+ to_write = 128 * 1024 * 1024;
+ b = g_unix_fd_add(fds[0], G_IO_IN, read_bytes, &to_read);
+ source_b = g_source_ref(g_main_context_find_source_by_id(NULL, b));
+
+ /* Assuming the kernel isn't internally 'laggy' then there will always
+ * be either data to read or room in which to write. That will keep
+ * the loop running until all data has been read and written.
+ */
+ while (to_write > 0 || to_read > 0)
+ {
+ gssize to_write_was = to_write;
+ gssize to_read_was = to_read;
+
+ if (tevent_loop_once(ev) != 0)
+ break;
+
+ /* Since the sources are at different priority, only one of them
+ * should possibly have run.
+ */
+ g_assert(to_write == to_write_was || to_read == to_read_was);
+ }
+
+ g_assert(to_write == 0);
+ g_assert(to_read == 0);
+
+ /* 'a' is already removed by itself */
+ g_assert(g_source_is_destroyed(source_a));
+ g_source_unref(source_a);
+ g_source_remove(b);
+ g_assert(g_source_is_destroyed(source_b));
+ g_source_unref(source_b);
+
+ samba_tevent_glib_glue_quit(glue);
+ TALLOC_FREE(glue);
+
+ close(fds[1]);
+ close(fds[0]);
+}
+
+int main(int argc, const char *argv[])
+{
+ int test_argc = 3;
+ char *test_argv[] = {
+ discard_const("test_glib_glue"),
+ discard_const("-m"),
+ discard_const("no-undefined")
+ };
+ char **argvp = test_argv;
+
+ g_test_init(&test_argc, &argvp, NULL);
+
+ ev = tevent_context_init(NULL);
+ if (ev == NULL) {
+ exit(1);
+ }
+
+ g_test_add_func("/mainloop/timeouts", test_timeouts);
+ g_test_add_func("/mainloop/glib_ev_source", test_glib_ev_source);
+ g_test_add_func("/mainloop/tevent_ev_source", test_tevent_ev_source);
+ g_test_add_func("/mainloop/unix-fd", test_unix_fd);
+
+ return g_test_run();
+}
diff --git a/source3/lib/time.c b/source3/lib/time.c
new file mode 100644
index 0000000..0d44a08
--- /dev/null
+++ b/source3/lib/time.c
@@ -0,0 +1,458 @@
+/*
+ Unix SMB/CIFS implementation.
+ time handling functions
+
+ Copyright (C) Andrew Tridgell 1992-2004
+ Copyright (C) Stefan (metze) Metzmacher 2002
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+/**
+ * @file
+ * @brief time handling functions
+ */
+
+
+#define NTTIME_INFINITY (NTTIME)0x8000000000000000LL
+
+#define TIME_FIXUP_CONSTANT_INT INT64_C(11644473600)
+
+/**************************************************************
+ Handle conversions between time_t and uint32, taking care to
+ preserve the "special" values.
+**************************************************************/
+
+uint32_t convert_time_t_to_uint32_t(time_t t)
+{
+#if (defined(SIZEOF_TIME_T) && (SIZEOF_TIME_T == 8))
+ /* time_t is 64-bit. */
+ if (t == 0x8000000000000000LL) {
+ return 0x80000000;
+ } else if (t == 0x7FFFFFFFFFFFFFFFLL) {
+ return 0x7FFFFFFF;
+ }
+#endif
+ return (uint32_t)t;
+}
+
+time_t convert_uint32_t_to_time_t(uint32_t u)
+{
+#if (defined(SIZEOF_TIME_T) && (SIZEOF_TIME_T == 8))
+ /* time_t is 64-bit. */
+ if (u == 0x80000000) {
+ return (time_t)0x8000000000000000LL;
+ } else if (u == 0x7FFFFFFF) {
+ return (time_t)0x7FFFFFFFFFFFFFFFLL;
+ }
+#endif
+ return (time_t)u;
+}
+
+/****************************************************************************
+ Check if NTTIME is 0.
+****************************************************************************/
+
+bool nt_time_is_zero(const NTTIME *nt)
+{
+ return (*nt == 0);
+}
+
+/****************************************************************************
+ Convert ASN.1 GeneralizedTime string to unix-time.
+ Returns 0 on failure; Currently ignores timezone.
+****************************************************************************/
+
+time_t generalized_to_unix_time(const char *str)
+{
+ struct tm tm;
+
+ ZERO_STRUCT(tm);
+
+ if (sscanf(str, "%4d%2d%2d%2d%2d%2d",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
+ return 0;
+ }
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+
+ return timegm(&tm);
+}
+
+/*******************************************************************
+ Accessor function for the server time zone offset.
+ set_server_zone_offset() must have been called first.
+******************************************************************/
+
+static int server_zone_offset;
+
+int get_server_zone_offset(void)
+{
+ return server_zone_offset;
+}
+
+/*******************************************************************
+ Initialize the server time zone offset. Called when a client connects.
+******************************************************************/
+
+int set_server_zone_offset(time_t t)
+{
+ server_zone_offset = get_time_zone(t);
+ return server_zone_offset;
+}
+
+/***************************************************************************
+ Server versions of the above functions.
+***************************************************************************/
+
+void srv_put_dos_date(char *buf,int offset,time_t unixdate)
+{
+ push_dos_date((uint8_t *)buf, offset, unixdate, server_zone_offset);
+}
+
+void srv_put_dos_date2_ts(char *buf, int offset, struct timespec unix_ts)
+{
+ time_t unixdate = convert_timespec_to_time_t(unix_ts);
+ push_dos_date2((uint8_t *)buf, offset, unixdate, server_zone_offset);
+}
+
+void srv_put_dos_date3(char *buf,int offset,time_t unixdate)
+{
+ push_dos_date3((uint8_t *)buf, offset, unixdate, server_zone_offset);
+}
+
+void round_timespec(enum timestamp_set_resolution res, struct timespec *ts)
+{
+ if (is_omit_timespec(ts)) {
+ return;
+ }
+
+ switch (res) {
+ case TIMESTAMP_SET_SECONDS:
+ round_timespec_to_sec(ts);
+ break;
+ case TIMESTAMP_SET_MSEC:
+ round_timespec_to_usec(ts);
+ break;
+ case TIMESTAMP_SET_NT_OR_BETTER:
+ /* No rounding needed. */
+ break;
+ }
+}
+
+/****************************************************************************
+ Take a Unix time and convert to an NTTIME structure and place in buffer
+ pointed to by p, rounded to the correct resolution.
+****************************************************************************/
+
+void put_long_date_timespec(enum timestamp_set_resolution res, char *p, struct timespec ts)
+{
+ NTTIME nt;
+ round_timespec(res, &ts);
+ nt = unix_timespec_to_nt_time(ts);
+ SBVAL(p, 0, nt);
+}
+
+void put_long_date_full_timespec(enum timestamp_set_resolution res,
+ char *p,
+ const struct timespec *_ts)
+{
+ struct timespec ts = *_ts;
+ NTTIME nt;
+
+ round_timespec(res, &ts);
+ nt = full_timespec_to_nt_time(&ts);
+ SBVAL(p, 0, nt);
+}
+
+struct timespec pull_long_date_full_timespec(const char *p)
+{
+ NTTIME nt = BVAL(p, 0);
+
+ return nt_time_to_full_timespec(nt);
+}
+
+void put_long_date(char *p, time_t t)
+{
+ struct timespec ts;
+ ts.tv_sec = t;
+ ts.tv_nsec = 0;
+ put_long_date_timespec(TIMESTAMP_SET_SECONDS, p, ts);
+}
+
+void dos_filetime_timespec(struct timespec *tsp)
+{
+ tsp->tv_sec &= ~1;
+ tsp->tv_nsec = 0;
+}
+
+/*******************************************************************
+ Create a unix date (int GMT) from a dos date (which is actually in
+ localtime).
+********************************************************************/
+
+time_t make_unix_date(const void *date_ptr, int zone_offset)
+{
+ return pull_dos_date(date_ptr, zone_offset);
+}
+
+/*******************************************************************
+ Like make_unix_date() but the words are reversed.
+********************************************************************/
+
+time_t make_unix_date2(const void *date_ptr, int zone_offset)
+{
+ return pull_dos_date2(date_ptr, zone_offset);
+}
+
+/*******************************************************************
+ Create a unix GMT date from a dos date in 32 bit "unix like" format
+ these generally arrive as localtimes, with corresponding DST.
+******************************************************************/
+
+time_t make_unix_date3(const void *date_ptr, int zone_offset)
+{
+ return pull_dos_date3(date_ptr, zone_offset);
+}
+
+time_t srv_make_unix_date(const void *date_ptr)
+{
+ return make_unix_date(date_ptr, server_zone_offset);
+}
+
+time_t srv_make_unix_date2(const void *date_ptr)
+{
+ return make_unix_date2(date_ptr, server_zone_offset);
+}
+
+time_t srv_make_unix_date3(const void *date_ptr)
+{
+ return make_unix_date3(date_ptr, server_zone_offset);
+}
+
+/****************************************************************************
+ Interprets an nt time into a unix struct timespec.
+ Differs from nt_time_to_unix in that an 8 byte value of 0xffffffffffffffff
+ will be returned as (time_t)-1, whereas nt_time_to_unix returns 0 in this case.
+****************************************************************************/
+
+struct timespec interpret_long_date(NTTIME nt)
+{
+ if (nt == (uint64_t)-1) {
+ struct timespec ret;
+ ret.tv_sec = (time_t)-1;
+ ret.tv_nsec = 0;
+ return ret;
+ }
+ return nt_time_to_full_timespec(nt);
+}
+
+/*******************************************************************
+ Re-read the smb serverzone value.
+******************************************************************/
+
+static struct timeval start_time_hires;
+
+void TimeInit(void)
+{
+ set_server_zone_offset(time(NULL));
+
+ DEBUG(4,("TimeInit: Serverzone is %d\n", server_zone_offset));
+
+ /* Save the start time of this process. */
+ if (start_time_hires.tv_sec == 0 && start_time_hires.tv_usec == 0) {
+ GetTimeOfDay(&start_time_hires);
+ }
+}
+
+/**********************************************************************
+ Return a timeval struct of the uptime of this process. As TimeInit is
+ done before a daemon fork then this is the start time from the parent
+ daemon start. JRA.
+***********************************************************************/
+
+void get_process_uptime(struct timeval *ret_time)
+{
+ struct timeval time_now_hires;
+
+ GetTimeOfDay(&time_now_hires);
+ ret_time->tv_sec = time_now_hires.tv_sec - start_time_hires.tv_sec;
+ if (time_now_hires.tv_usec < start_time_hires.tv_usec) {
+ ret_time->tv_sec -= 1;
+ ret_time->tv_usec = 1000000 + (time_now_hires.tv_usec - start_time_hires.tv_usec);
+ } else {
+ ret_time->tv_usec = time_now_hires.tv_usec - start_time_hires.tv_usec;
+ }
+}
+
+/**
+ * @brief Get the startup time of the server.
+ *
+ * @param[out] ret_time A pointer to a timveal structure to set the startup
+ * time.
+ */
+void get_startup_time(struct timeval *ret_time)
+{
+ ret_time->tv_sec = start_time_hires.tv_sec;
+ ret_time->tv_usec = start_time_hires.tv_usec;
+}
+
+
+/****************************************************************************
+ Convert a NTTIME structure to a time_t.
+ It's originally in "100ns units".
+
+ This is an absolute version of the one above.
+ By absolute I mean, it doesn't adjust from 1/1/1601 to 1/1/1970
+ if the NTTIME was 5 seconds, the time_t is 5 seconds. JFM
+****************************************************************************/
+
+time_t nt_time_to_unix_abs(const NTTIME *nt)
+{
+ uint64_t d;
+
+ if (*nt == 0) {
+ return (time_t)0;
+ }
+
+ if (*nt == (uint64_t)-1) {
+ return (time_t)-1;
+ }
+
+ if (*nt == NTTIME_INFINITY) {
+ return (time_t)-1;
+ }
+
+ /* reverse the time */
+ /* it's a negative value, turn it to positive */
+ d=~*nt;
+
+ d += 1000*1000*10/2;
+ d /= 1000*1000*10;
+
+ if (!(TIME_T_MIN <= ((time_t)d) && ((time_t)d) <= TIME_T_MAX)) {
+ return (time_t)0;
+ }
+
+ return (time_t)d;
+}
+
+/****************************************************************************
+ Convert a time_t to a NTTIME structure
+
+ This is an absolute version of the one above.
+ By absolute I mean, it doesn't adjust from 1/1/1970 to 1/1/1601
+ If the time_t was 5 seconds, the NTTIME is 5 seconds. JFM
+****************************************************************************/
+
+void unix_to_nt_time_abs(NTTIME *nt, time_t t)
+{
+ double d;
+
+ if (t==0) {
+ *nt = 0;
+ return;
+ }
+
+ if (t == TIME_T_MAX) {
+ *nt = 0x7fffffffffffffffLL;
+ return;
+ }
+
+ if (t == (time_t)-1) {
+ /* that's what NT uses for infinite */
+ *nt = NTTIME_INFINITY;
+ return;
+ }
+
+ d = (double)(t);
+ d *= 1.0e7;
+
+ *nt = (NTTIME)d;
+
+ /* convert to a negative value */
+ *nt=~*nt;
+}
+
+
+/****************************************************************************
+ Utility function that always returns a const string even if localtime
+ and asctime fail.
+****************************************************************************/
+
+const char *time_to_asc(const time_t t)
+{
+ const char *asct;
+ struct tm *lt = localtime(&t);
+
+ if (!lt) {
+ return "unknown time\n";
+ }
+
+ asct = asctime(lt);
+ if (!asct) {
+ return "unknown time\n";
+ }
+ return asct;
+}
+
+const char *display_time(NTTIME nttime)
+{
+ float high;
+ float low;
+ int sec;
+ int days, hours, mins, secs;
+
+ if (nttime==0)
+ return "Now";
+
+ if (nttime==NTTIME_INFINITY)
+ return "Never";
+
+ high = 65536;
+ high = high/10000;
+ high = high*65536;
+ high = high/1000;
+ high = high * (~(nttime >> 32));
+
+ low = ~(nttime & 0xFFFFFFFF);
+ low = low/(1000*1000*10);
+
+ sec=(int)(high+low);
+
+ days=sec/(60*60*24);
+ hours=(sec - (days*60*60*24)) / (60*60);
+ mins=(sec - (days*60*60*24) - (hours*60*60) ) / 60;
+ secs=sec - (days*60*60*24) - (hours*60*60) - (mins*60);
+
+ return talloc_asprintf(talloc_tos(), "%u days, %u hours, %u minutes, "
+ "%u seconds", days, hours, mins, secs);
+}
+
+bool nt_time_is_set(const NTTIME *nt)
+{
+ if (*nt == 0x7FFFFFFFFFFFFFFFLL) {
+ return false;
+ }
+
+ if (*nt == NTTIME_INFINITY) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/lib/tldap.c b/source3/lib/tldap.c
new file mode 100644
index 0000000..ad3232b
--- /dev/null
+++ b/source3/lib/tldap.c
@@ -0,0 +1,2726 @@
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for async ldap client requests
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "tldap.h"
+#include "system/network.h"
+#include "system/locale.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/samba_util.h"
+#include "lib/util_tsock.h"
+#include "../lib/util/asn1.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_unix.h"
+
+static TLDAPRC tldap_simple_recv(struct tevent_req *req);
+static bool tldap_msg_set_pending(struct tevent_req *req);
+
+#define TEVENT_TLDAP_RC_MAGIC (0x87bcd26e)
+
+bool tevent_req_ldap_error(struct tevent_req *req, TLDAPRC rc)
+{
+ uint64_t err;
+
+ if (TLDAP_RC_IS_SUCCESS(rc)) {
+ return false;
+ }
+
+ err = TEVENT_TLDAP_RC_MAGIC;
+ err <<= 32;
+ err |= TLDAP_RC_V(rc);
+
+ return tevent_req_error(req, err);
+}
+
+bool tevent_req_is_ldap_error(struct tevent_req *req, TLDAPRC *perr)
+{
+ enum tevent_req_state state;
+ uint64_t err;
+
+ if (!tevent_req_is_error(req, &state, &err)) {
+ return false;
+ }
+ switch (state) {
+ case TEVENT_REQ_TIMED_OUT:
+ *perr = TLDAP_TIMEOUT;
+ break;
+ case TEVENT_REQ_NO_MEMORY:
+ *perr = TLDAP_NO_MEMORY;
+ break;
+ case TEVENT_REQ_USER_ERROR:
+ if ((err >> 32) != TEVENT_TLDAP_RC_MAGIC) {
+ abort();
+ }
+ *perr = TLDAP_RC(err & 0xffffffff);
+ break;
+ default:
+ *perr = TLDAP_OPERATIONS_ERROR;
+ break;
+ }
+ return true;
+}
+
+struct tldap_ctx_attribute {
+ char *name;
+ void *ptr;
+};
+
+struct tldap_context {
+ int ld_version;
+ struct tstream_context *conn;
+ int msgid;
+ struct tevent_queue *outgoing;
+ struct tevent_req **pending;
+ struct tevent_req *read_req;
+
+ /* For the sync wrappers we need something like get_last_error... */
+ struct tldap_message *last_msg;
+
+ /* debug */
+ void (*log_fn)(void *context, enum tldap_debug_level level,
+ const char *fmt, va_list ap);
+ void *log_private;
+
+ struct tldap_ctx_attribute *ctx_attrs;
+};
+
+struct tldap_message {
+ struct asn1_data *data;
+ uint8_t *inbuf;
+ int type;
+ int id;
+
+ /* RESULT_ENTRY */
+ char *dn;
+ struct tldap_attribute *attribs;
+
+ /* Error data sent by the server */
+ TLDAPRC lderr;
+ char *res_matcheddn;
+ char *res_diagnosticmessage;
+ char *res_referral;
+ DATA_BLOB res_serverSaslCreds;
+ struct tldap_control *res_sctrls;
+
+ /* Controls sent by the server */
+ struct tldap_control *ctrls;
+};
+
+void tldap_set_debug(struct tldap_context *ld,
+ void (*log_fn)(void *log_private,
+ enum tldap_debug_level level,
+ const char *fmt,
+ va_list ap) PRINTF_ATTRIBUTE(3,0),
+ void *log_private)
+{
+ ld->log_fn = log_fn;
+ ld->log_private = log_private;
+}
+
+static void tldap_debug(
+ struct tldap_context *ld,
+ enum tldap_debug_level level,
+ const char *fmt, ...) PRINTF_ATTRIBUTE(3,4);
+
+static void tldap_debug(struct tldap_context *ld,
+ enum tldap_debug_level level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ if (!ld) {
+ return;
+ }
+ if (ld->log_fn == NULL) {
+ return;
+ }
+ va_start(ap, fmt);
+ ld->log_fn(ld->log_private, level, fmt, ap);
+ va_end(ap);
+}
+
+static int tldap_next_msgid(struct tldap_context *ld)
+{
+ int result;
+
+ result = ld->msgid++;
+ if (ld->msgid == INT_MAX) {
+ ld->msgid = 1;
+ }
+ return result;
+}
+
+struct tldap_context *tldap_context_create(TALLOC_CTX *mem_ctx, int fd)
+{
+ struct tldap_context *ctx;
+ int ret;
+
+ ctx = talloc_zero(mem_ctx, struct tldap_context);
+ if (ctx == NULL) {
+ return NULL;
+ }
+ ret = tstream_bsd_existing_socket(ctx, fd, &ctx->conn);
+ if (ret == -1) {
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+ ctx->msgid = 1;
+ ctx->ld_version = 3;
+ ctx->outgoing = tevent_queue_create(ctx, "tldap_outgoing");
+ if (ctx->outgoing == NULL) {
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+ return ctx;
+}
+
+bool tldap_connection_ok(struct tldap_context *ld)
+{
+ int ret;
+
+ if (ld == NULL) {
+ return false;
+ }
+
+ if (ld->conn == NULL) {
+ return false;
+ }
+
+ ret = tstream_pending_bytes(ld->conn);
+ if (ret == -1) {
+ return false;
+ }
+
+ return true;
+}
+
+static size_t tldap_pending_reqs(struct tldap_context *ld)
+{
+ return talloc_array_length(ld->pending);
+}
+
+struct tstream_context *tldap_get_tstream(struct tldap_context *ld)
+{
+ return ld->conn;
+}
+
+void tldap_set_tstream(struct tldap_context *ld,
+ struct tstream_context *stream)
+{
+ ld->conn = stream;
+}
+
+static struct tldap_ctx_attribute *tldap_context_findattr(
+ struct tldap_context *ld, const char *name)
+{
+ size_t i, num_attrs;
+
+ num_attrs = talloc_array_length(ld->ctx_attrs);
+
+ for (i=0; i<num_attrs; i++) {
+ if (strcmp(ld->ctx_attrs[i].name, name) == 0) {
+ return &ld->ctx_attrs[i];
+ }
+ }
+ return NULL;
+}
+
+bool tldap_context_setattr(struct tldap_context *ld,
+ const char *name, const void *_pptr)
+{
+ struct tldap_ctx_attribute *tmp, *attr;
+ char *tmpname;
+ int num_attrs;
+ void **pptr = (void **)discard_const_p(void,_pptr);
+
+ attr = tldap_context_findattr(ld, name);
+ if (attr != NULL) {
+ /*
+ * We don't actually delete attrs, we don't expect tons of
+ * attributes being shuffled around.
+ */
+ TALLOC_FREE(attr->ptr);
+ if (*pptr != NULL) {
+ attr->ptr = talloc_move(ld->ctx_attrs, pptr);
+ *pptr = NULL;
+ }
+ return true;
+ }
+
+ tmpname = talloc_strdup(ld, name);
+ if (tmpname == NULL) {
+ return false;
+ }
+
+ num_attrs = talloc_array_length(ld->ctx_attrs);
+
+ tmp = talloc_realloc(ld, ld->ctx_attrs, struct tldap_ctx_attribute,
+ num_attrs+1);
+ if (tmp == NULL) {
+ TALLOC_FREE(tmpname);
+ return false;
+ }
+ tmp[num_attrs].name = talloc_move(tmp, &tmpname);
+ if (*pptr != NULL) {
+ tmp[num_attrs].ptr = talloc_move(tmp, pptr);
+ } else {
+ tmp[num_attrs].ptr = NULL;
+ }
+ *pptr = NULL;
+ ld->ctx_attrs = tmp;
+ return true;
+}
+
+void *tldap_context_getattr(struct tldap_context *ld, const char *name)
+{
+ struct tldap_ctx_attribute *attr = tldap_context_findattr(ld, name);
+
+ if (attr == NULL) {
+ return NULL;
+ }
+ return attr->ptr;
+}
+
+struct read_ldap_state {
+ uint8_t *buf;
+ bool done;
+};
+
+static ssize_t read_ldap_more(uint8_t *buf, size_t buflen, void *private_data);
+static void read_ldap_done(struct tevent_req *subreq);
+
+static struct tevent_req *read_ldap_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tstream_context *conn)
+{
+ struct tevent_req *req, *subreq;
+ struct read_ldap_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct read_ldap_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->done = false;
+
+ subreq = tstream_read_packet_send(state, ev, conn, 2, read_ldap_more,
+ state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, read_ldap_done, req);
+ return req;
+}
+
+static ssize_t read_ldap_more(uint8_t *buf, size_t buflen, void *private_data)
+{
+ struct read_ldap_state *state = talloc_get_type_abort(
+ private_data, struct read_ldap_state);
+ size_t len;
+ int i, lensize;
+
+ if (state->done) {
+ /* We've been here, we're done */
+ return 0;
+ }
+
+ /*
+ * From ldap.h: LDAP_TAG_MESSAGE is 0x30
+ */
+ if (buf[0] != 0x30) {
+ return -1;
+ }
+
+ len = buf[1];
+ if ((len & 0x80) == 0) {
+ state->done = true;
+ return len;
+ }
+
+ lensize = (len & 0x7f);
+ len = 0;
+
+ if (buflen == 2) {
+ /* Please get us the full length */
+ return lensize;
+ }
+ if (buflen > 2 + lensize) {
+ state->done = true;
+ return 0;
+ }
+ if (buflen != 2 + lensize) {
+ return -1;
+ }
+
+ for (i=0; i<lensize; i++) {
+ len = (len << 8) | buf[2+i];
+ }
+ return len;
+}
+
+static void read_ldap_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct read_ldap_state *state = tevent_req_data(
+ req, struct read_ldap_state);
+ ssize_t nread;
+ int err;
+
+ nread = tstream_read_packet_recv(subreq, state, &state->buf, &err);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static ssize_t read_ldap_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **pbuf, int *perrno)
+{
+ struct read_ldap_state *state = tevent_req_data(
+ req, struct read_ldap_state);
+
+ if (tevent_req_is_unix_error(req, perrno)) {
+ return -1;
+ }
+ *pbuf = talloc_move(mem_ctx, &state->buf);
+ return talloc_get_size(*pbuf);
+}
+
+struct tldap_msg_state {
+ struct tldap_context *ld;
+ struct tevent_context *ev;
+ int id;
+ struct iovec iov;
+
+ struct asn1_data *data;
+ uint8_t *inbuf;
+};
+
+static bool tldap_push_controls(struct asn1_data *data,
+ struct tldap_control *sctrls,
+ int num_sctrls)
+{
+ int i;
+
+ if ((sctrls == NULL) || (num_sctrls == 0)) {
+ return true;
+ }
+
+ if (!asn1_push_tag(data, ASN1_CONTEXT(0))) return false;
+
+ for (i=0; i<num_sctrls; i++) {
+ struct tldap_control *c = &sctrls[i];
+ if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) return false;
+ if (!asn1_write_OctetString(data, c->oid, strlen(c->oid))) return false;
+ if (c->critical) {
+ if (!asn1_write_BOOLEAN(data, true)) return false;
+ }
+ if (c->value.data != NULL) {
+ if (!asn1_write_OctetString(data, c->value.data,
+ c->value.length)) return false;
+ }
+ if (!asn1_pop_tag(data)) return false; /* ASN1_SEQUENCE(0) */
+ }
+
+ return asn1_pop_tag(data); /* ASN1_CONTEXT(0) */
+}
+
+#define tldap_context_disconnect(ld, status) \
+ _tldap_context_disconnect(ld, status, __location__)
+
+static void _tldap_context_disconnect(struct tldap_context *ld,
+ TLDAPRC status,
+ const char *location)
+{
+ if (ld->conn == NULL) {
+ /*
+ * We don't need to tldap_debug() on
+ * a potential 2nd run.
+ *
+ * The rest of the function would just
+ * be a noop for the 2nd run anyway.
+ */
+ return;
+ }
+
+ tldap_debug(ld, TLDAP_DEBUG_WARNING,
+ "tldap_context_disconnect: %s at %s\n",
+ tldap_rc2string(status),
+ location);
+ tevent_queue_stop(ld->outgoing);
+ TALLOC_FREE(ld->read_req);
+ TALLOC_FREE(ld->conn);
+
+ while (talloc_array_length(ld->pending) > 0) {
+ struct tevent_req *req = NULL;
+ struct tldap_msg_state *state = NULL;
+
+ req = ld->pending[0];
+ state = tevent_req_data(req, struct tldap_msg_state);
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_ldap_error(req, status);
+ }
+}
+
+static void tldap_msg_sent(struct tevent_req *subreq);
+static void tldap_msg_received(struct tevent_req *subreq);
+
+static struct tevent_req *tldap_msg_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ int id, struct asn1_data *data,
+ struct tldap_control *sctrls,
+ int num_sctrls)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_msg_state *state;
+ DATA_BLOB blob;
+ bool ok;
+
+ tldap_debug(ld, TLDAP_DEBUG_TRACE, "tldap_msg_send: sending msg %d\n",
+ id);
+
+ req = tevent_req_create(mem_ctx, &state, struct tldap_msg_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ld = ld;
+ state->ev = ev;
+ state->id = id;
+
+ ok = tldap_connection_ok(ld);
+ if (!ok) {
+ tevent_req_ldap_error(req, TLDAP_SERVER_DOWN);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!tldap_push_controls(data, sctrls, num_sctrls)) {
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+
+ if (!asn1_pop_tag(data)) {
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!asn1_blob(data, &blob)) {
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!tldap_msg_set_pending(req)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);;
+ }
+
+ state->iov.iov_base = (void *)blob.data;
+ state->iov.iov_len = blob.length;
+
+ subreq = tstream_writev_queue_send(state, ev, ld->conn, ld->outgoing,
+ &state->iov, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_msg_sent, req);
+ return req;
+}
+
+static void tldap_msg_unset_pending(struct tevent_req *req)
+{
+ struct tldap_msg_state *state = tevent_req_data(
+ req, struct tldap_msg_state);
+ struct tldap_context *ld = state->ld;
+ int num_pending = tldap_pending_reqs(ld);
+ int i;
+
+ tevent_req_set_cleanup_fn(req, NULL);
+
+ for (i=0; i<num_pending; i++) {
+ if (req == ld->pending[i]) {
+ break;
+ }
+ }
+ if (i == num_pending) {
+ /*
+ * Something's seriously broken. Just returning here is the
+ * right thing nevertheless, the point of this routine is to
+ * remove ourselves from cli->pending.
+ */
+ return;
+ }
+
+ if (num_pending == 1) {
+ TALLOC_FREE(ld->pending);
+ return;
+ }
+
+ /*
+ * Remove ourselves from the cli->pending array
+ */
+ if (num_pending > 1) {
+ ld->pending[i] = ld->pending[num_pending-1];
+ }
+
+ /*
+ * No NULL check here, we're shrinking by sizeof(void *), and
+ * talloc_realloc just adjusts the size for this.
+ */
+ ld->pending = talloc_realloc(NULL, ld->pending, struct tevent_req *,
+ num_pending - 1);
+}
+
+static void tldap_msg_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ tldap_msg_unset_pending(req);
+}
+
+static bool tldap_msg_set_pending(struct tevent_req *req)
+{
+ struct tldap_msg_state *state = tevent_req_data(
+ req, struct tldap_msg_state);
+ struct tldap_context *ld;
+ struct tevent_req **pending;
+ int num_pending;
+
+ ld = state->ld;
+ num_pending = tldap_pending_reqs(ld);
+
+ pending = talloc_realloc(ld, ld->pending, struct tevent_req *,
+ num_pending+1);
+ if (pending == NULL) {
+ return false;
+ }
+ pending[num_pending] = req;
+ ld->pending = pending;
+ tevent_req_set_cleanup_fn(req, tldap_msg_cleanup);
+
+ if (ld->read_req != NULL) {
+ return true;
+ }
+
+ /*
+ * We're the first one, add the read_ldap request that waits for the
+ * answer from the server
+ */
+ ld->read_req = read_ldap_send(ld->pending, state->ev, ld->conn);
+ if (ld->read_req == NULL) {
+ tldap_msg_unset_pending(req);
+ return false;
+ }
+ tevent_req_set_callback(ld->read_req, tldap_msg_received, ld);
+ return true;
+}
+
+static void tldap_msg_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_msg_state *state = tevent_req_data(
+ req, struct tldap_msg_state);
+ ssize_t nwritten;
+ int err;
+
+ nwritten = tstream_writev_queue_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (nwritten == -1) {
+ tldap_context_disconnect(state->ld, TLDAP_SERVER_DOWN);
+ return;
+ }
+}
+
+static int tldap_msg_msgid(struct tevent_req *req)
+{
+ struct tldap_msg_state *state = tevent_req_data(
+ req, struct tldap_msg_state);
+
+ return state->id;
+}
+
+static void tldap_msg_received(struct tevent_req *subreq)
+{
+ struct tldap_context *ld = tevent_req_callback_data(
+ subreq, struct tldap_context);
+ struct tevent_req *req;
+ struct tldap_msg_state *state;
+ struct asn1_data *data;
+ uint8_t *inbuf;
+ ssize_t received;
+ size_t num_pending;
+ size_t i;
+ int err;
+ TLDAPRC status = TLDAP_PROTOCOL_ERROR;
+ int id;
+ uint8_t type;
+ bool ok;
+
+ received = read_ldap_recv(subreq, talloc_tos(), &inbuf, &err);
+ TALLOC_FREE(subreq);
+ ld->read_req = NULL;
+ if (received == -1) {
+ status = TLDAP_SERVER_DOWN;
+ goto fail;
+ }
+
+ data = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH);
+ if (data == NULL) {
+ /*
+ * We have to disconnect all, we can't tell which of
+ * the requests this reply is for.
+ */
+ status = TLDAP_NO_MEMORY;
+ goto fail;
+ }
+ asn1_load_nocopy(data, inbuf, received);
+
+ ok = true;
+ ok &= asn1_start_tag(data, ASN1_SEQUENCE(0));
+ ok &= asn1_read_Integer(data, &id);
+ ok &= asn1_peek_uint8(data, &type);
+
+ if (!ok) {
+ status = TLDAP_PROTOCOL_ERROR;
+ goto fail;
+ }
+
+ tldap_debug(ld, TLDAP_DEBUG_TRACE, "tldap_msg_received: got msg %d "
+ "type %d\n", id, (int)type);
+
+ if (id == 0) {
+ tldap_debug(
+ ld,
+ TLDAP_DEBUG_WARNING,
+ "tldap_msg_received: got msgid 0 of "
+ "type %"PRIu8", disconnecting\n",
+ type);
+ tldap_context_disconnect(ld, TLDAP_SERVER_DOWN);
+ return;
+ }
+
+ num_pending = talloc_array_length(ld->pending);
+
+ for (i=0; i<num_pending; i++) {
+ if (id == tldap_msg_msgid(ld->pending[i])) {
+ break;
+ }
+ }
+ if (i == num_pending) {
+ /* Dump unexpected reply */
+ tldap_debug(ld, TLDAP_DEBUG_WARNING, "tldap_msg_received: "
+ "No request pending for msg %d\n", id);
+ TALLOC_FREE(data);
+ TALLOC_FREE(inbuf);
+ goto done;
+ }
+
+ req = ld->pending[i];
+ state = tevent_req_data(req, struct tldap_msg_state);
+
+ state->inbuf = talloc_move(state, &inbuf);
+ state->data = talloc_move(state, &data);
+
+ tldap_msg_unset_pending(req);
+ num_pending = talloc_array_length(ld->pending);
+
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_done(req);
+
+ done:
+ if (num_pending == 0) {
+ return;
+ }
+
+ state = tevent_req_data(ld->pending[0], struct tldap_msg_state);
+ ld->read_req = read_ldap_send(ld->pending, state->ev, ld->conn);
+ if (ld->read_req == NULL) {
+ status = TLDAP_NO_MEMORY;
+ goto fail;
+ }
+ tevent_req_set_callback(ld->read_req, tldap_msg_received, ld);
+ return;
+
+ fail:
+ tldap_context_disconnect(ld, status);
+}
+
+static TLDAPRC tldap_msg_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct tldap_message **pmsg)
+{
+ struct tldap_msg_state *state = tevent_req_data(
+ req, struct tldap_msg_state);
+ struct tldap_message *msg;
+ TLDAPRC err;
+ uint8_t msgtype;
+
+ if (tevent_req_is_ldap_error(req, &err)) {
+ return err;
+ }
+
+ if (!asn1_peek_uint8(state->data, &msgtype)) {
+ return TLDAP_PROTOCOL_ERROR;
+ }
+
+ if (pmsg == NULL) {
+ return TLDAP_SUCCESS;
+ }
+
+ msg = talloc_zero(mem_ctx, struct tldap_message);
+ if (msg == NULL) {
+ return TLDAP_NO_MEMORY;
+ }
+ msg->id = state->id;
+
+ msg->inbuf = talloc_move(msg, &state->inbuf);
+ msg->data = talloc_move(msg, &state->data);
+ msg->type = msgtype;
+
+ *pmsg = msg;
+ return TLDAP_SUCCESS;
+}
+
+struct tldap_req_state {
+ int id;
+ struct asn1_data *out;
+ struct tldap_message *result;
+};
+
+static struct tevent_req *tldap_req_create(TALLOC_CTX *mem_ctx,
+ struct tldap_context *ld,
+ struct tldap_req_state **pstate)
+{
+ struct tevent_req *req;
+ struct tldap_req_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct tldap_req_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->out = asn1_init(state, ASN1_MAX_TREE_DEPTH);
+ if (state->out == NULL) {
+ goto err;
+ }
+ state->id = tldap_next_msgid(ld);
+
+ if (!asn1_push_tag(state->out, ASN1_SEQUENCE(0))) goto err;
+ if (!asn1_write_Integer(state->out, state->id)) goto err;
+
+ *pstate = state;
+ return req;
+
+ err:
+
+ TALLOC_FREE(req);
+ return NULL;
+}
+
+static void tldap_save_msg(struct tldap_context *ld, struct tevent_req *req)
+{
+ struct tldap_req_state *state = tevent_req_data(
+ req, struct tldap_req_state);
+
+ TALLOC_FREE(ld->last_msg);
+ ld->last_msg = talloc_move(ld, &state->result);
+}
+
+static char *blob2string_talloc(TALLOC_CTX *mem_ctx, DATA_BLOB blob)
+{
+ char *result = talloc_array(mem_ctx, char, blob.length+1);
+
+ if (result == NULL) {
+ return NULL;
+ }
+
+ memcpy(result, blob.data, blob.length);
+ result[blob.length] = '\0';
+ return result;
+}
+
+static bool asn1_read_OctetString_talloc(TALLOC_CTX *mem_ctx,
+ struct asn1_data *data,
+ char **presult)
+{
+ DATA_BLOB string;
+ char *result;
+ if (!asn1_read_OctetString(data, mem_ctx, &string))
+ return false;
+
+ result = blob2string_talloc(mem_ctx, string);
+
+ data_blob_free(&string);
+
+ if (result == NULL) {
+ return false;
+ }
+ *presult = result;
+ return true;
+}
+
+static bool tldap_decode_controls(struct tldap_req_state *state);
+
+static bool tldap_decode_response(struct tldap_req_state *state)
+{
+ struct asn1_data *data = state->result->data;
+ struct tldap_message *msg = state->result;
+ int rc;
+ bool ok = true;
+
+ ok &= asn1_read_enumerated(data, &rc);
+ if (ok) {
+ msg->lderr = TLDAP_RC(rc);
+ }
+
+ ok &= asn1_read_OctetString_talloc(msg, data, &msg->res_matcheddn);
+ ok &= asn1_read_OctetString_talloc(msg, data,
+ &msg->res_diagnosticmessage);
+ if (!ok) return ok;
+ if (asn1_peek_tag(data, ASN1_CONTEXT(3))) {
+ ok &= asn1_start_tag(data, ASN1_CONTEXT(3));
+ ok &= asn1_read_OctetString_talloc(msg, data,
+ &msg->res_referral);
+ ok &= asn1_end_tag(data);
+ } else {
+ msg->res_referral = NULL;
+ }
+
+ return ok;
+}
+
+static void tldap_sasl_bind_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_sasl_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ const char *mechanism,
+ DATA_BLOB *creds,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_req_state *state;
+
+ req = tldap_req_create(mem_ctx, ld, &state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (dn == NULL) {
+ dn = "";
+ }
+
+ if (!asn1_push_tag(state->out, TLDAP_REQ_BIND)) goto err;
+ if (!asn1_write_Integer(state->out, ld->ld_version)) goto err;
+ if (!asn1_write_OctetString(state->out, dn, strlen(dn))) goto err;
+
+ if (mechanism == NULL) {
+ if (!asn1_push_tag(state->out, ASN1_CONTEXT_SIMPLE(0))) goto err;
+ if (!asn1_write(state->out, creds->data, creds->length)) goto err;
+ if (!asn1_pop_tag(state->out)) goto err;
+ } else {
+ if (!asn1_push_tag(state->out, ASN1_CONTEXT(3))) goto err;
+ if (!asn1_write_OctetString(state->out, mechanism,
+ strlen(mechanism))) goto err;
+ if ((creds != NULL) && (creds->data != NULL)) {
+ if (!asn1_write_OctetString(state->out, creds->data,
+ creds->length)) goto err;
+ }
+ if (!asn1_pop_tag(state->out)) goto err;
+ }
+
+ if (!asn1_pop_tag(state->out)) goto err;
+
+ subreq = tldap_msg_send(state, ev, ld, state->id, state->out,
+ sctrls, num_sctrls);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_sasl_bind_done, req);
+ return req;
+
+ err:
+
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void tldap_sasl_bind_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_req_state *state = tevent_req_data(
+ req, struct tldap_req_state);
+ TLDAPRC rc;
+ bool ok;
+
+ rc = tldap_msg_recv(subreq, state, &state->result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_ldap_error(req, rc)) {
+ return;
+ }
+ if (state->result->type != TLDAP_RES_BIND) {
+ tevent_req_ldap_error(req, TLDAP_PROTOCOL_ERROR);
+ return;
+ }
+
+ ok = asn1_start_tag(state->result->data, TLDAP_RES_BIND);
+ ok &= tldap_decode_response(state);
+
+ if (asn1_peek_tag(state->result->data, ASN1_CONTEXT_SIMPLE(7))) {
+ int len;
+
+ ok &= asn1_start_tag(state->result->data,
+ ASN1_CONTEXT_SIMPLE(7));
+ if (!ok) {
+ goto decode_error;
+ }
+
+ len = asn1_tag_remaining(state->result->data);
+ if (len == -1) {
+ goto decode_error;
+ }
+
+ state->result->res_serverSaslCreds =
+ data_blob_talloc(state->result, NULL, len);
+ if (state->result->res_serverSaslCreds.data == NULL) {
+ goto decode_error;
+ }
+
+ ok = asn1_read(state->result->data,
+ state->result->res_serverSaslCreds.data,
+ state->result->res_serverSaslCreds.length);
+
+ ok &= asn1_end_tag(state->result->data);
+ }
+
+ ok &= asn1_end_tag(state->result->data);
+
+ if (!ok) {
+ goto decode_error;
+ }
+
+ if (!TLDAP_RC_IS_SUCCESS(state->result->lderr) &&
+ !TLDAP_RC_EQUAL(state->result->lderr,
+ TLDAP_SASL_BIND_IN_PROGRESS)) {
+ tevent_req_ldap_error(req, state->result->lderr);
+ return;
+ }
+ tevent_req_done(req);
+ return;
+
+decode_error:
+ tevent_req_ldap_error(req, TLDAP_DECODING_ERROR);
+ return;
+}
+
+TLDAPRC tldap_sasl_bind_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ DATA_BLOB *serverSaslCreds)
+{
+ struct tldap_req_state *state = tevent_req_data(
+ req, struct tldap_req_state);
+ TLDAPRC rc;
+
+ if (tevent_req_is_ldap_error(req, &rc)) {
+ return rc;
+ }
+
+ if (serverSaslCreds != NULL) {
+ serverSaslCreds->data = talloc_move(
+ mem_ctx, &state->result->res_serverSaslCreds.data);
+ serverSaslCreds->length =
+ state->result->res_serverSaslCreds.length;
+ }
+
+ return state->result->lderr;
+}
+
+TLDAPRC tldap_sasl_bind(struct tldap_context *ld,
+ const char *dn,
+ const char *mechanism,
+ DATA_BLOB *creds,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *serverSaslCreds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TLDAPRC rc = TLDAP_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = tldap_sasl_bind_send(frame, ev, ld, dn, mechanism, creds,
+ sctrls, num_sctrls, cctrls, num_cctrls);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ rc = TLDAP_OPERATIONS_ERROR;
+ goto fail;
+ }
+ rc = tldap_sasl_bind_recv(req, mem_ctx, serverSaslCreds);
+ tldap_save_msg(ld, req);
+ fail:
+ TALLOC_FREE(frame);
+ return rc;
+}
+
+struct tevent_req *tldap_simple_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ const char *passwd)
+{
+ DATA_BLOB cred;
+
+ if (passwd != NULL) {
+ cred.data = discard_const_p(uint8_t, passwd);
+ cred.length = strlen(passwd);
+ } else {
+ cred.data = discard_const_p(uint8_t, "");
+ cred.length = 0;
+ }
+ return tldap_sasl_bind_send(mem_ctx, ev, ld, dn, NULL, &cred, NULL, 0,
+ NULL, 0);
+}
+
+TLDAPRC tldap_simple_bind_recv(struct tevent_req *req)
+{
+ return tldap_sasl_bind_recv(req, NULL, NULL);
+}
+
+TLDAPRC tldap_simple_bind(struct tldap_context *ld, const char *dn,
+ const char *passwd)
+{
+ DATA_BLOB cred;
+
+ if (passwd != NULL) {
+ cred.data = discard_const_p(uint8_t, passwd);
+ cred.length = strlen(passwd);
+ } else {
+ cred.data = discard_const_p(uint8_t, "");
+ cred.length = 0;
+ }
+ return tldap_sasl_bind(ld, dn, NULL, &cred, NULL, 0, NULL, 0,
+ NULL, NULL);
+}
+
+/*****************************************************************************/
+
+/* can't use isalpha() as only a strict set is valid for LDAP */
+
+static bool tldap_is_alpha(char c)
+{
+ return (((c >= 'a') && (c <= 'z')) || \
+ ((c >= 'A') && (c <= 'Z')));
+}
+
+static bool tldap_is_adh(char c)
+{
+ return tldap_is_alpha(c) || isdigit(c) || (c == '-');
+}
+
+#define TLDAP_FILTER_AND ASN1_CONTEXT(0)
+#define TLDAP_FILTER_OR ASN1_CONTEXT(1)
+#define TLDAP_FILTER_NOT ASN1_CONTEXT(2)
+#define TLDAP_FILTER_EQ ASN1_CONTEXT(3)
+#define TLDAP_FILTER_SUB ASN1_CONTEXT(4)
+#define TLDAP_FILTER_LE ASN1_CONTEXT(5)
+#define TLDAP_FILTER_GE ASN1_CONTEXT(6)
+#define TLDAP_FILTER_PRES ASN1_CONTEXT_SIMPLE(7)
+#define TLDAP_FILTER_APX ASN1_CONTEXT(8)
+#define TLDAP_FILTER_EXT ASN1_CONTEXT(9)
+
+#define TLDAP_SUB_INI ASN1_CONTEXT_SIMPLE(0)
+#define TLDAP_SUB_ANY ASN1_CONTEXT_SIMPLE(1)
+#define TLDAP_SUB_FIN ASN1_CONTEXT_SIMPLE(2)
+
+
+/* oid's should be numerical only in theory,
+ * but apparently some broken servers may have alphanum aliases instead.
+ * Do like openldap libraries and allow alphanum aliases for oids, but
+ * do not allow Tagging options in that case.
+ */
+static bool tldap_is_attrdesc(const char *s, int len, bool no_tagopts)
+{
+ bool is_oid = false;
+ bool dot = false;
+ int i;
+
+ /* first char has stricter rules */
+ if (isdigit(*s)) {
+ is_oid = true;
+ } else if (!tldap_is_alpha(*s)) {
+ /* bad first char */
+ return false;
+ }
+
+ for (i = 1; i < len; i++) {
+
+ if (is_oid) {
+ if (isdigit(s[i])) {
+ dot = false;
+ continue;
+ }
+ if (s[i] == '.') {
+ if (dot) {
+ /* malformed */
+ return false;
+ }
+ dot = true;
+ continue;
+ }
+ } else {
+ if (tldap_is_adh(s[i])) {
+ continue;
+ }
+ }
+
+ if (s[i] == ';') {
+ if (no_tagopts) {
+ /* no tagging options */
+ return false;
+ }
+ if (dot) {
+ /* malformed */
+ return false;
+ }
+ if ((i + 1) == len) {
+ /* malformed */
+ return false;
+ }
+
+ is_oid = false;
+ continue;
+ }
+ }
+
+ if (dot) {
+ /* malformed */
+ return false;
+ }
+
+ return true;
+}
+
+/* this function copies the value until the closing parenthesis is found. */
+static char *tldap_get_val(TALLOC_CTX *memctx,
+ const char *value, const char **_s)
+{
+ const char *s = value;
+
+ /* find terminator */
+ while (*s) {
+ s = strchr(s, ')');
+ if (s && (*(s - 1) == '\\')) {
+ s++;
+ continue;
+ }
+ break;
+ }
+ if (!s || !(*s == ')')) {
+ /* malformed filter */
+ return NULL;
+ }
+
+ *_s = s;
+
+ return talloc_strndup(memctx, value, s - value);
+}
+
+static int tldap_hex2char(const char *x)
+{
+ if (isxdigit(x[0]) && isxdigit(x[1])) {
+ const char h1 = x[0], h2 = x[1];
+ int c = 0;
+
+ if (h1 >= 'a') c = h1 - (int)'a' + 10;
+ else if (h1 >= 'A') c = h1 - (int)'A' + 10;
+ else if (h1 >= '0') c = h1 - (int)'0';
+ c = c << 4;
+ if (h2 >= 'a') c += h2 - (int)'a' + 10;
+ else if (h2 >= 'A') c += h2 - (int)'A' + 10;
+ else if (h2 >= '0') c += h2 - (int)'0';
+
+ return c;
+ }
+
+ return -1;
+}
+
+static bool tldap_find_first_star(const char *val, const char **star)
+{
+ const char *s;
+
+ for (s = val; *s; s++) {
+ switch (*s) {
+ case '\\':
+ if (isxdigit(s[1]) && isxdigit(s[2])) {
+ s += 2;
+ break;
+ }
+ /* not hex based escape, check older syntax */
+ switch (s[1]) {
+ case '(':
+ case ')':
+ case '*':
+ case '\\':
+ s++;
+ break;
+ default:
+ /* invalid escape sequence */
+ return false;
+ }
+ break;
+ case ')':
+ /* end of val, nothing found */
+ *star = s;
+ return true;
+
+ case '*':
+ *star = s;
+ return true;
+ }
+ }
+
+ /* string ended without closing parenthesis, filter is malformed */
+ return false;
+}
+
+static bool tldap_unescape_inplace(char *value, size_t *val_len)
+{
+ int c;
+ size_t i, p;
+
+ for (i = 0,p = 0; i < *val_len; i++) {
+
+ switch (value[i]) {
+ case '(':
+ case ')':
+ case '*':
+ /* these must be escaped */
+ return false;
+
+ case '\\':
+ if (!value[i + 1]) {
+ /* invalid EOL */
+ return false;
+ }
+ i++;
+
+ /* LDAPv3 escaped */
+ c = tldap_hex2char(&value[i]);
+ if (c >= 0 && c < 256) {
+ value[p] = c;
+ i++;
+ p++;
+ break;
+ }
+
+ /* LDAPv2 escaped */
+ switch (value[i]) {
+ case '(':
+ case ')':
+ case '*':
+ case '\\':
+ value[p] = value[i];
+ p++;
+
+ break;
+ default:
+ /* invalid */
+ return false;
+ }
+ break;
+
+ default:
+ value[p] = value[i];
+ p++;
+ }
+ }
+ value[p] = '\0';
+ *val_len = p;
+ return true;
+}
+
+static bool tldap_push_filter_basic(struct tldap_context *ld,
+ struct asn1_data *data,
+ const char **_s);
+static bool tldap_push_filter_substring(struct tldap_context *ld,
+ struct asn1_data *data,
+ const char *val,
+ const char **_s);
+static bool tldap_push_filter_int(struct tldap_context *ld,
+ struct asn1_data *data,
+ const char **_s)
+{
+ const char *s = *_s;
+ bool ret;
+
+ if (*s != '(') {
+ tldap_debug(ld, TLDAP_DEBUG_ERROR,
+ "Incomplete or malformed filter\n");
+ return false;
+ }
+ s++;
+
+ /* we are right after a parenthesis,
+ * find out what op we have at hand */
+ switch (*s) {
+ case '&':
+ tldap_debug(ld, TLDAP_DEBUG_TRACE, "Filter op: AND\n");
+ if (!asn1_push_tag(data, TLDAP_FILTER_AND)) return false;
+ s++;
+ break;
+
+ case '|':
+ tldap_debug(ld, TLDAP_DEBUG_TRACE, "Filter op: OR\n");
+ if (!asn1_push_tag(data, TLDAP_FILTER_OR)) return false;
+ s++;
+ break;
+
+ case '!':
+ tldap_debug(ld, TLDAP_DEBUG_TRACE, "Filter op: NOT\n");
+ if (!asn1_push_tag(data, TLDAP_FILTER_NOT)) return false;
+ s++;
+ ret = tldap_push_filter_int(ld, data, &s);
+ if (!ret) {
+ return false;
+ }
+ if (!asn1_pop_tag(data)) return false;
+ goto done;
+
+ case '(':
+ case ')':
+ tldap_debug(ld, TLDAP_DEBUG_ERROR,
+ "Invalid parenthesis '%c'\n", *s);
+ return false;
+
+ case '\0':
+ tldap_debug(ld, TLDAP_DEBUG_ERROR,
+ "Invalid filter termination\n");
+ return false;
+
+ default:
+ ret = tldap_push_filter_basic(ld, data, &s);
+ if (!ret) {
+ return false;
+ }
+ goto done;
+ }
+
+ /* only and/or filters get here.
+ * go through the list of filters */
+
+ if (*s == ')') {
+ /* RFC 4526: empty and/or */
+ if (!asn1_pop_tag(data)) return false;
+ goto done;
+ }
+
+ while (*s) {
+ ret = tldap_push_filter_int(ld, data, &s);
+ if (!ret) {
+ return false;
+ }
+
+ if (*s == ')') {
+ /* end of list, return */
+ if (!asn1_pop_tag(data)) return false;
+ break;
+ }
+ }
+
+done:
+ if (*s != ')') {
+ tldap_debug(ld, TLDAP_DEBUG_ERROR,
+ "Incomplete or malformed filter\n");
+ return false;
+ }
+ s++;
+
+ if (asn1_has_error(data)) {
+ return false;
+ }
+
+ *_s = s;
+ return true;
+}
+
+
+static bool tldap_push_filter_basic(struct tldap_context *ld,
+ struct asn1_data *data,
+ const char **_s)
+{
+ TALLOC_CTX *tmpctx = talloc_tos();
+ const char *s = *_s;
+ const char *e;
+ const char *eq;
+ const char *val;
+ const char *type;
+ const char *dn;
+ const char *rule;
+ const char *star;
+ size_t type_len = 0;
+ char *uval;
+ size_t uval_len;
+ bool write_octect = true;
+ bool ret;
+
+ eq = strchr(s, '=');
+ if (!eq) {
+ tldap_debug(ld, TLDAP_DEBUG_ERROR,
+ "Invalid filter, missing equal sign\n");
+ return false;
+ }
+
+ val = eq + 1;
+ e = eq - 1;
+
+ switch (*e) {
+ case '<':
+ if (!asn1_push_tag(data, TLDAP_FILTER_LE)) return false;
+ break;
+
+ case '>':
+ if (!asn1_push_tag(data, TLDAP_FILTER_GE)) return false;
+ break;
+
+ case '~':
+ if (!asn1_push_tag(data, TLDAP_FILTER_APX)) return false;
+ break;
+
+ case ':':
+ if (!asn1_push_tag(data, TLDAP_FILTER_EXT)) return false;
+ write_octect = false;
+
+ type = NULL;
+ dn = NULL;
+ rule = NULL;
+
+ if (*s == ':') { /* [:dn]:rule:= value */
+ if (s == e) {
+ /* malformed filter */
+ return false;
+ }
+ dn = s;
+ } else { /* type[:dn][:rule]:= value */
+ type = s;
+ dn = strchr(s, ':');
+ type_len = dn - type;
+ if (dn == e) { /* type:= value */
+ dn = NULL;
+ }
+ }
+ if (dn) {
+ dn++;
+
+ rule = strchr(dn, ':');
+ if (rule == NULL) {
+ return false;
+ }
+ if ((rule == dn + 1) || rule + 1 == e) {
+ /* malformed filter, contains "::" */
+ return false;
+ }
+
+ if (strncasecmp_m(dn, "dn:", 3) != 0) {
+ if (rule == e) {
+ rule = dn;
+ dn = NULL;
+ } else {
+ /* malformed filter. With two
+ * optionals, the first must be "dn"
+ */
+ return false;
+ }
+ } else {
+ if (rule == e) {
+ rule = NULL;
+ } else {
+ rule++;
+ }
+ }
+ }
+
+ if (!type && !dn && !rule) {
+ /* malformed filter, there must be at least one */
+ return false;
+ }
+
+ /*
+ MatchingRuleAssertion ::= SEQUENCE {
+ matchingRule [1] MatchingRuleID OPTIONAL,
+ type [2] AttributeDescription OPTIONAL,
+ matchValue [3] AssertionValue,
+ dnAttributes [4] BOOLEAN DEFAULT FALSE
+ }
+ */
+
+ /* check and add rule */
+ if (rule) {
+ ret = tldap_is_attrdesc(rule, e - rule, true);
+ if (!ret) {
+ return false;
+ }
+ if (!asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(1))) return false;
+ if (!asn1_write(data, rule, e - rule)) return false;
+ if (!asn1_pop_tag(data)) return false;
+ }
+
+ /* check and add type */
+ if (type) {
+ ret = tldap_is_attrdesc(type, type_len, false);
+ if (!ret) {
+ return false;
+ }
+ if (!asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(2))) return false;
+ if (!asn1_write(data, type, type_len)) return false;
+ if (!asn1_pop_tag(data)) return false;
+ }
+
+ uval = tldap_get_val(tmpctx, val, _s);
+ if (!uval) {
+ return false;
+ }
+ uval_len = *_s - val;
+ ret = tldap_unescape_inplace(uval, &uval_len);
+ if (!ret) {
+ return false;
+ }
+
+ if (!asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(3))) return false;
+ if (!asn1_write(data, uval, uval_len)) return false;
+ if (!asn1_pop_tag(data)) return false;
+
+ if (!asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(4))) return false;
+ if (!asn1_write_uint8(data, dn?1:0)) return false;
+ if (!asn1_pop_tag(data)) return false;
+ break;
+
+ default:
+ e = eq;
+
+ ret = tldap_is_attrdesc(s, e - s, false);
+ if (!ret) {
+ return false;
+ }
+
+ if (strncmp(val, "*)", 2) == 0) {
+ /* presence */
+ if (!asn1_push_tag(data, TLDAP_FILTER_PRES)) return false;
+ if (!asn1_write(data, s, e - s)) return false;
+ *_s = val + 1;
+ write_octect = false;
+ break;
+ }
+
+ ret = tldap_find_first_star(val, &star);
+ if (!ret) {
+ return false;
+ }
+ if (*star == '*') {
+ /* substring */
+ if (!asn1_push_tag(data, TLDAP_FILTER_SUB)) return false;
+ if (!asn1_write_OctetString(data, s, e - s)) return false;
+ ret = tldap_push_filter_substring(ld, data, val, &s);
+ if (!ret) {
+ return false;
+ }
+ *_s = s;
+ write_octect = false;
+ break;
+ }
+
+ /* if nothing else, then it is just equality */
+ if (!asn1_push_tag(data, TLDAP_FILTER_EQ)) return false;
+ write_octect = true;
+ break;
+ }
+
+ if (write_octect) {
+ uval = tldap_get_val(tmpctx, val, _s);
+ if (!uval) {
+ return false;
+ }
+ uval_len = *_s - val;
+ ret = tldap_unescape_inplace(uval, &uval_len);
+ if (!ret) {
+ return false;
+ }
+
+ if (!asn1_write_OctetString(data, s, e - s)) return false;
+ if (!asn1_write_OctetString(data, uval, uval_len)) return false;
+ }
+
+ if (asn1_has_error(data)) {
+ return false;
+ }
+ return asn1_pop_tag(data);
+}
+
+static bool tldap_push_filter_substring(struct tldap_context *ld,
+ struct asn1_data *data,
+ const char *val,
+ const char **_s)
+{
+ TALLOC_CTX *tmpctx = talloc_tos();
+ bool initial = true;
+ const char *star;
+ char *chunk;
+ size_t chunk_len;
+ bool ret;
+
+ /*
+ SubstringFilter ::= SEQUENCE {
+ type AttributeDescription,
+ -- at least one must be present
+ substrings SEQUENCE OF CHOICE {
+ initial [0] LDAPString,
+ any [1] LDAPString,
+ final [2] LDAPString } }
+ */
+ if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) return false;
+
+ do {
+ ret = tldap_find_first_star(val, &star);
+ if (!ret) {
+ return false;
+ }
+ chunk_len = star - val;
+
+ switch (*star) {
+ case '*':
+ if (!initial && chunk_len == 0) {
+ /* found '**', which is illegal */
+ return false;
+ }
+ break;
+ case ')':
+ if (initial) {
+ /* no stars ?? */
+ return false;
+ }
+ /* we are done */
+ break;
+ default:
+ /* ?? */
+ return false;
+ }
+
+ if (initial && chunk_len == 0) {
+ val = star + 1;
+ initial = false;
+ continue;
+ }
+
+ chunk = talloc_strndup(tmpctx, val, chunk_len);
+ if (!chunk) {
+ return false;
+ }
+ ret = tldap_unescape_inplace(chunk, &chunk_len);
+ if (!ret) {
+ return false;
+ }
+ switch (*star) {
+ case '*':
+ if (initial) {
+ if (!asn1_push_tag(data, TLDAP_SUB_INI)) return false;
+ initial = false;
+ } else {
+ if (!asn1_push_tag(data, TLDAP_SUB_ANY)) return false;
+ }
+ break;
+ case ')':
+ if (!asn1_push_tag(data, TLDAP_SUB_FIN)) return false;
+ break;
+ default:
+ /* ?? */
+ return false;
+ }
+ if (!asn1_write(data, chunk, chunk_len)) return false;
+ if (!asn1_pop_tag(data)) return false;
+
+ val = star + 1;
+
+ } while (*star == '*');
+
+ *_s = star;
+
+ /* end of sequence */
+ return asn1_pop_tag(data);
+}
+
+/* NOTE: although openldap libraries allow for spaces in some places, mostly
+ * around parentheses, we do not allow any spaces (except in values of
+ * course) as I couldn't find any place in RFC 4512 or RFC 4515 where
+ * leading or trailing spaces were allowed.
+ */
+static bool tldap_push_filter(struct tldap_context *ld,
+ struct asn1_data *data,
+ const char *filter)
+{
+ const char *s = filter;
+ bool ret;
+
+ ret = tldap_push_filter_int(ld, data, &s);
+ if (ret && *s) {
+ tldap_debug(ld, TLDAP_DEBUG_ERROR,
+ "Incomplete or malformed filter\n");
+ return false;
+ }
+ return ret;
+}
+
+/*****************************************************************************/
+
+static void tldap_search_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *base, int scope,
+ const char *filter,
+ const char **attrs,
+ int num_attrs,
+ int attrsonly,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls,
+ int timelimit,
+ int sizelimit,
+ int deref)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_req_state *state;
+ int i;
+
+ req = tldap_req_create(mem_ctx, ld, &state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!asn1_push_tag(state->out, TLDAP_REQ_SEARCH)) goto encoding_error;
+ if (!asn1_write_OctetString(state->out, base, strlen(base))) goto encoding_error;
+ if (!asn1_write_enumerated(state->out, scope)) goto encoding_error;
+ if (!asn1_write_enumerated(state->out, deref)) goto encoding_error;
+ if (!asn1_write_Integer(state->out, sizelimit)) goto encoding_error;
+ if (!asn1_write_Integer(state->out, timelimit)) goto encoding_error;
+ if (!asn1_write_BOOLEAN(state->out, attrsonly)) goto encoding_error;
+
+ if (!tldap_push_filter(ld, state->out, filter)) {
+ goto encoding_error;
+ }
+
+ if (!asn1_push_tag(state->out, ASN1_SEQUENCE(0))) goto encoding_error;
+ for (i=0; i<num_attrs; i++) {
+ if (!asn1_write_OctetString(state->out, attrs[i], strlen(attrs[i]))) goto encoding_error;
+ }
+ if (!asn1_pop_tag(state->out)) goto encoding_error;
+ if (!asn1_pop_tag(state->out)) goto encoding_error;
+
+ subreq = tldap_msg_send(state, ev, ld, state->id, state->out,
+ sctrls, num_sctrls);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_search_done, req);
+ return req;
+
+ encoding_error:
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void tldap_search_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_req_state *state = tevent_req_data(
+ req, struct tldap_req_state);
+ TLDAPRC rc;
+
+ rc = tldap_msg_recv(subreq, state, &state->result);
+ if (tevent_req_ldap_error(req, rc)) {
+ return;
+ }
+ switch (state->result->type) {
+ case TLDAP_RES_SEARCH_ENTRY:
+ case TLDAP_RES_SEARCH_REFERENCE:
+ if (!tldap_msg_set_pending(subreq)) {
+ tevent_req_oom(req);
+ return;
+ }
+ tevent_req_notify_callback(req);
+ break;
+ case TLDAP_RES_SEARCH_RESULT:
+ TALLOC_FREE(subreq);
+ if (!asn1_start_tag(state->result->data,
+ state->result->type) ||
+ !tldap_decode_response(state) ||
+ !asn1_end_tag(state->result->data) ||
+ !tldap_decode_controls(state)) {
+ tevent_req_ldap_error(req, TLDAP_DECODING_ERROR);
+ return;
+ }
+ tevent_req_done(req);
+ break;
+ default:
+ tevent_req_ldap_error(req, TLDAP_PROTOCOL_ERROR);
+ return;
+ }
+}
+
+TLDAPRC tldap_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct tldap_message **pmsg)
+{
+ struct tldap_req_state *state = tevent_req_data(
+ req, struct tldap_req_state);
+ TLDAPRC rc;
+
+ if (!tevent_req_is_in_progress(req)
+ && tevent_req_is_ldap_error(req, &rc)) {
+ return rc;
+ }
+
+ if (tevent_req_is_in_progress(req)) {
+ switch (state->result->type) {
+ case TLDAP_RES_SEARCH_ENTRY:
+ case TLDAP_RES_SEARCH_REFERENCE:
+ break;
+ default:
+ return TLDAP_OPERATIONS_ERROR;
+ }
+ }
+
+ *pmsg = talloc_move(mem_ctx, &state->result);
+ return TLDAP_SUCCESS;
+}
+
+struct tldap_search_all_state {
+ struct tldap_message **msgs;
+ struct tldap_message *result;
+};
+
+static void tldap_search_all_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_search_all_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct tldap_context *ld, const char *base, int scope,
+ const char *filter, const char **attrs, int num_attrs, int attrsonly,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls,
+ int timelimit, int sizelimit, int deref)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_search_all_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tldap_search_all_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = tldap_search_send(state, ev, ld, base, scope, filter,
+ attrs, num_attrs, attrsonly,
+ sctrls, num_sctrls, cctrls, num_cctrls,
+ timelimit, sizelimit, deref);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_search_all_done, req);
+ return req;
+}
+
+static void tldap_search_all_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_search_all_state *state = tevent_req_data(
+ req, struct tldap_search_all_state);
+ struct tldap_message *msg, **tmp;
+ size_t num_msgs;
+ TLDAPRC rc;
+ int msgtype;
+
+ rc = tldap_search_recv(subreq, state, &msg);
+ /* No TALLOC_FREE(subreq), this is multi-step */
+ if (tevent_req_ldap_error(req, rc)) {
+ return;
+ }
+
+ msgtype = tldap_msg_type(msg);
+ if (msgtype == TLDAP_RES_SEARCH_RESULT) {
+ state->result = msg;
+ tevent_req_done(req);
+ return;
+ }
+
+ num_msgs = talloc_array_length(state->msgs);
+
+ tmp = talloc_realloc(state, state->msgs, struct tldap_message *,
+ num_msgs + 1);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ state->msgs = tmp;
+ state->msgs[num_msgs] = talloc_move(state->msgs, &msg);
+}
+
+TLDAPRC tldap_search_all_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct tldap_message ***msgs,
+ struct tldap_message **result)
+{
+ struct tldap_search_all_state *state = tevent_req_data(
+ req, struct tldap_search_all_state);
+ TLDAPRC rc;
+
+ if (tevent_req_is_ldap_error(req, &rc)) {
+ return rc;
+ }
+
+ if (msgs != NULL) {
+ *msgs = talloc_move(mem_ctx, &state->msgs);
+ }
+ if (result != NULL) {
+ *result = talloc_move(mem_ctx, &state->result);
+ }
+
+ return TLDAP_SUCCESS;
+}
+
+TLDAPRC tldap_search(struct tldap_context *ld,
+ const char *base, int scope, const char *filter,
+ const char **attrs, int num_attrs, int attrsonly,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls,
+ int timelimit, int sizelimit, int deref,
+ TALLOC_CTX *mem_ctx, struct tldap_message ***pmsgs)
+{
+ TALLOC_CTX *frame;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TLDAPRC rc = TLDAP_NO_MEMORY;
+ struct tldap_message **msgs;
+ struct tldap_message *result;
+
+ if (tldap_pending_reqs(ld)) {
+ return TLDAP_BUSY;
+ }
+
+ frame = talloc_stackframe();
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = tldap_search_all_send(frame, ev, ld, base, scope, filter,
+ attrs, num_attrs, attrsonly,
+ sctrls, num_sctrls, cctrls, num_cctrls,
+ timelimit, sizelimit, deref);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ rc = TLDAP_OPERATIONS_ERROR;
+ goto fail;
+ }
+ rc = tldap_search_all_recv(req, frame, &msgs, &result);
+ TALLOC_FREE(req);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ goto fail;
+ }
+
+ TALLOC_FREE(ld->last_msg);
+ ld->last_msg = talloc_move(ld, &result);
+
+ if (pmsgs != NULL) {
+ *pmsgs = talloc_move(mem_ctx, &msgs);
+ }
+fail:
+ TALLOC_FREE(frame);
+ return rc;
+}
+
+static bool tldap_parse_search_entry(struct tldap_message *msg)
+{
+ int num_attribs = 0;
+
+ if (msg->type != TLDAP_RES_SEARCH_ENTRY) {
+ return false;
+ }
+ if (!asn1_start_tag(msg->data, TLDAP_RES_SEARCH_ENTRY)) {
+ return false;
+ }
+
+ /* dn */
+
+ if (!asn1_read_OctetString_talloc(msg, msg->data, &msg->dn)) return false;
+
+ if (msg->dn == NULL) {
+ return false;
+ }
+
+ /*
+ * Attributes: We overallocate msg->attribs by one, so that while
+ * looping over the attributes we can directly parse into the last
+ * array element. Same for the values in the inner loop.
+ */
+
+ msg->attribs = talloc_array(msg, struct tldap_attribute, 1);
+ if (msg->attribs == NULL) {
+ return false;
+ }
+
+ if (!asn1_start_tag(msg->data, ASN1_SEQUENCE(0))) return false;
+ while (asn1_peek_tag(msg->data, ASN1_SEQUENCE(0))) {
+ struct tldap_attribute *attrib;
+ int num_values = 0;
+
+ attrib = &msg->attribs[num_attribs];
+ attrib->values = talloc_array(msg->attribs, DATA_BLOB, 1);
+ if (attrib->values == NULL) {
+ return false;
+ }
+ if (!asn1_start_tag(msg->data, ASN1_SEQUENCE(0))) return false;
+ if (!asn1_read_OctetString_talloc(msg->attribs, msg->data,
+ &attrib->name)) return false;
+ if (!asn1_start_tag(msg->data, ASN1_SET)) return false;
+
+ while (asn1_peek_tag(msg->data, ASN1_OCTET_STRING)) {
+ if (!asn1_read_OctetString(msg->data, msg,
+ &attrib->values[num_values])) return false;
+
+ attrib->values = talloc_realloc(
+ msg->attribs, attrib->values, DATA_BLOB,
+ num_values + 2);
+ if (attrib->values == NULL) {
+ return false;
+ }
+ num_values += 1;
+ }
+ attrib->values = talloc_realloc(msg->attribs, attrib->values,
+ DATA_BLOB, num_values);
+ attrib->num_values = num_values;
+
+ if (!asn1_end_tag(msg->data)) return false; /* ASN1_SET */
+ if (!asn1_end_tag(msg->data)) return false; /* ASN1_SEQUENCE(0) */
+ msg->attribs = talloc_realloc(
+ msg, msg->attribs, struct tldap_attribute,
+ num_attribs + 2);
+ if (msg->attribs == NULL) {
+ return false;
+ }
+ num_attribs += 1;
+ }
+ msg->attribs = talloc_realloc(
+ msg, msg->attribs, struct tldap_attribute, num_attribs);
+ return asn1_end_tag(msg->data);
+}
+
+bool tldap_entry_dn(struct tldap_message *msg, char **dn)
+{
+ if ((msg->dn == NULL) && (!tldap_parse_search_entry(msg))) {
+ return false;
+ }
+ *dn = msg->dn;
+ return true;
+}
+
+bool tldap_entry_attributes(struct tldap_message *msg,
+ struct tldap_attribute **attributes,
+ int *num_attributes)
+{
+ if ((msg->dn == NULL) && (!tldap_parse_search_entry(msg))) {
+ return false;
+ }
+ *attributes = msg->attribs;
+ *num_attributes = talloc_array_length(msg->attribs);
+ return true;
+}
+
+static bool tldap_decode_controls(struct tldap_req_state *state)
+{
+ struct tldap_message *msg = state->result;
+ struct asn1_data *data = msg->data;
+ struct tldap_control *sctrls = NULL;
+ int num_controls = 0;
+ bool ret = false;
+
+ msg->res_sctrls = NULL;
+
+ if (!asn1_peek_tag(data, ASN1_CONTEXT(0))) {
+ return true;
+ }
+
+ if (!asn1_start_tag(data, ASN1_CONTEXT(0))) goto out;
+
+ while (asn1_peek_tag(data, ASN1_SEQUENCE(0))) {
+ struct tldap_control *c;
+ char *oid = NULL;
+
+ sctrls = talloc_realloc(msg, sctrls, struct tldap_control,
+ num_controls + 1);
+ if (sctrls == NULL) {
+ goto out;
+ }
+ c = &sctrls[num_controls];
+
+ if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) goto out;
+ if (!asn1_read_OctetString_talloc(msg, data, &oid)) goto out;
+ if (asn1_has_error(data) || (oid == NULL)) {
+ goto out;
+ }
+ c->oid = oid;
+ if (asn1_peek_tag(data, ASN1_BOOLEAN)) {
+ if (!asn1_read_BOOLEAN(data, &c->critical)) goto out;
+ } else {
+ c->critical = false;
+ }
+ c->value = data_blob_null;
+ if (asn1_peek_tag(data, ASN1_OCTET_STRING) &&
+ !asn1_read_OctetString(data, msg, &c->value)) {
+ goto out;
+ }
+ if (!asn1_end_tag(data)) goto out; /* ASN1_SEQUENCE(0) */
+
+ num_controls += 1;
+ }
+
+ if (!asn1_end_tag(data)) goto out; /* ASN1_CONTEXT(0) */
+
+ ret = true;
+
+ out:
+
+ if (ret) {
+ msg->res_sctrls = sctrls;
+ } else {
+ TALLOC_FREE(sctrls);
+ }
+ return ret;
+}
+
+static void tldap_simple_done(struct tevent_req *subreq, int type)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_req_state *state = tevent_req_data(
+ req, struct tldap_req_state);
+ TLDAPRC rc;
+
+ rc = tldap_msg_recv(subreq, state, &state->result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_ldap_error(req, rc)) {
+ return;
+ }
+ if (state->result->type != type) {
+ tevent_req_ldap_error(req, TLDAP_PROTOCOL_ERROR);
+ return;
+ }
+ if (!asn1_start_tag(state->result->data, state->result->type) ||
+ !tldap_decode_response(state) ||
+ !asn1_end_tag(state->result->data) ||
+ !tldap_decode_controls(state)) {
+ tevent_req_ldap_error(req, TLDAP_DECODING_ERROR);
+ return;
+ }
+ if (!TLDAP_RC_IS_SUCCESS(state->result->lderr)) {
+ tevent_req_ldap_error(req, state->result->lderr);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static TLDAPRC tldap_simple_recv(struct tevent_req *req)
+{
+ TLDAPRC rc;
+ if (tevent_req_is_ldap_error(req, &rc)) {
+ return rc;
+ }
+ return TLDAP_SUCCESS;
+}
+
+static void tldap_add_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_add_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ struct tldap_mod *attributes,
+ int num_attributes,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_req_state *state;
+ int i, j;
+
+ req = tldap_req_create(mem_ctx, ld, &state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!asn1_push_tag(state->out, TLDAP_REQ_ADD)) goto err;
+ if (!asn1_write_OctetString(state->out, dn, strlen(dn))) goto err;
+ if (!asn1_push_tag(state->out, ASN1_SEQUENCE(0))) goto err;
+
+ for (i=0; i<num_attributes; i++) {
+ struct tldap_mod *attrib = &attributes[i];
+ if (!asn1_push_tag(state->out, ASN1_SEQUENCE(0))) goto err;
+ if (!asn1_write_OctetString(state->out, attrib->attribute,
+ strlen(attrib->attribute))) goto err;
+ if (!asn1_push_tag(state->out, ASN1_SET)) goto err;
+ for (j=0; j<attrib->num_values; j++) {
+ if (!asn1_write_OctetString(state->out,
+ attrib->values[j].data,
+ attrib->values[j].length)) goto err;
+ }
+ if (!asn1_pop_tag(state->out)) goto err;
+ if (!asn1_pop_tag(state->out)) goto err;
+ }
+
+ if (!asn1_pop_tag(state->out)) goto err;
+ if (!asn1_pop_tag(state->out)) goto err;
+
+ subreq = tldap_msg_send(state, ev, ld, state->id, state->out,
+ sctrls, num_sctrls);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_add_done, req);
+ return req;
+
+ err:
+
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void tldap_add_done(struct tevent_req *subreq)
+{
+ tldap_simple_done(subreq, TLDAP_RES_ADD);
+}
+
+TLDAPRC tldap_add_recv(struct tevent_req *req)
+{
+ return tldap_simple_recv(req);
+}
+
+TLDAPRC tldap_add(struct tldap_context *ld, const char *dn,
+ struct tldap_mod *attributes, int num_attributes,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TLDAPRC rc = TLDAP_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = tldap_add_send(frame, ev, ld, dn, attributes, num_attributes,
+ sctrls, num_sctrls, cctrls, num_cctrls);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ rc = TLDAP_OPERATIONS_ERROR;
+ goto fail;
+ }
+ rc = tldap_add_recv(req);
+ tldap_save_msg(ld, req);
+ fail:
+ TALLOC_FREE(frame);
+ return rc;
+}
+
+static void tldap_modify_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_modify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ struct tldap_mod *mods, int num_mods,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_req_state *state;
+ int i, j;
+
+ req = tldap_req_create(mem_ctx, ld, &state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!asn1_push_tag(state->out, TLDAP_REQ_MODIFY)) goto err;
+ if (!asn1_write_OctetString(state->out, dn, strlen(dn))) goto err;
+ if (!asn1_push_tag(state->out, ASN1_SEQUENCE(0))) goto err;
+
+ for (i=0; i<num_mods; i++) {
+ struct tldap_mod *mod = &mods[i];
+ if (!asn1_push_tag(state->out, ASN1_SEQUENCE(0))) goto err;
+ if (!asn1_write_enumerated(state->out, mod->mod_op)) goto err;
+ if (!asn1_push_tag(state->out, ASN1_SEQUENCE(0))) goto err;
+ if (!asn1_write_OctetString(state->out, mod->attribute,
+ strlen(mod->attribute))) goto err;
+ if (!asn1_push_tag(state->out, ASN1_SET)) goto err;
+ for (j=0; j<mod->num_values; j++) {
+ if (!asn1_write_OctetString(state->out,
+ mod->values[j].data,
+ mod->values[j].length)) goto err;
+ }
+ if (!asn1_pop_tag(state->out)) goto err;
+ if (!asn1_pop_tag(state->out)) goto err;
+ if (!asn1_pop_tag(state->out)) goto err;
+ }
+
+ if (!asn1_pop_tag(state->out)) goto err;
+ if (!asn1_pop_tag(state->out)) goto err;
+
+ subreq = tldap_msg_send(state, ev, ld, state->id, state->out,
+ sctrls, num_sctrls);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_modify_done, req);
+ return req;
+
+ err:
+
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void tldap_modify_done(struct tevent_req *subreq)
+{
+ tldap_simple_done(subreq, TLDAP_RES_MODIFY);
+}
+
+TLDAPRC tldap_modify_recv(struct tevent_req *req)
+{
+ return tldap_simple_recv(req);
+}
+
+TLDAPRC tldap_modify(struct tldap_context *ld, const char *dn,
+ struct tldap_mod *mods, int num_mods,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls)
+ {
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TLDAPRC rc = TLDAP_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = tldap_modify_send(frame, ev, ld, dn, mods, num_mods,
+ sctrls, num_sctrls, cctrls, num_cctrls);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ rc = TLDAP_OPERATIONS_ERROR;
+ goto fail;
+ }
+ rc = tldap_modify_recv(req);
+ tldap_save_msg(ld, req);
+ fail:
+ TALLOC_FREE(frame);
+ return rc;
+}
+
+static void tldap_delete_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_delete_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *dn,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_req_state *state;
+
+ req = tldap_req_create(mem_ctx, ld, &state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!asn1_push_tag(state->out, TLDAP_REQ_DELETE)) goto err;
+ if (!asn1_write(state->out, dn, strlen(dn))) goto err;
+ if (!asn1_pop_tag(state->out)) goto err;
+
+ subreq = tldap_msg_send(state, ev, ld, state->id, state->out,
+ sctrls, num_sctrls);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_delete_done, req);
+ return req;
+
+ err:
+
+ tevent_req_ldap_error(req, TLDAP_ENCODING_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void tldap_delete_done(struct tevent_req *subreq)
+{
+ tldap_simple_done(subreq, TLDAP_RES_DELETE);
+}
+
+TLDAPRC tldap_delete_recv(struct tevent_req *req)
+{
+ return tldap_simple_recv(req);
+}
+
+TLDAPRC tldap_delete(struct tldap_context *ld, const char *dn,
+ struct tldap_control *sctrls, int num_sctrls,
+ struct tldap_control *cctrls, int num_cctrls)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TLDAPRC rc = TLDAP_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = tldap_delete_send(frame, ev, ld, dn, sctrls, num_sctrls,
+ cctrls, num_cctrls);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ rc = TLDAP_OPERATIONS_ERROR;
+ goto fail;
+ }
+ rc = tldap_delete_recv(req);
+ tldap_save_msg(ld, req);
+ fail:
+ TALLOC_FREE(frame);
+ return rc;
+}
+
+int tldap_msg_id(const struct tldap_message *msg)
+{
+ return msg->id;
+}
+
+int tldap_msg_type(const struct tldap_message *msg)
+{
+ return msg->type;
+}
+
+const char *tldap_msg_matcheddn(struct tldap_message *msg)
+{
+ if (msg == NULL) {
+ return NULL;
+ }
+ return msg->res_matcheddn;
+}
+
+const char *tldap_msg_diagnosticmessage(struct tldap_message *msg)
+{
+ if (msg == NULL) {
+ return NULL;
+ }
+ return msg->res_diagnosticmessage;
+}
+
+const char *tldap_msg_referral(struct tldap_message *msg)
+{
+ if (msg == NULL) {
+ return NULL;
+ }
+ return msg->res_referral;
+}
+
+void tldap_msg_sctrls(struct tldap_message *msg, int *num_sctrls,
+ struct tldap_control **sctrls)
+{
+ if (msg == NULL) {
+ *sctrls = NULL;
+ *num_sctrls = 0;
+ return;
+ }
+ *sctrls = msg->res_sctrls;
+ *num_sctrls = talloc_array_length(msg->res_sctrls);
+}
+
+struct tldap_message *tldap_ctx_lastmsg(struct tldap_context *ld)
+{
+ return ld->last_msg;
+}
+
+static const struct { TLDAPRC rc; const char *string; } tldaprc_errmap[] =
+{
+ { TLDAP_SUCCESS,
+ "TLDAP_SUCCESS" },
+ { TLDAP_OPERATIONS_ERROR,
+ "TLDAP_OPERATIONS_ERROR" },
+ { TLDAP_PROTOCOL_ERROR,
+ "TLDAP_PROTOCOL_ERROR" },
+ { TLDAP_TIMELIMIT_EXCEEDED,
+ "TLDAP_TIMELIMIT_EXCEEDED" },
+ { TLDAP_SIZELIMIT_EXCEEDED,
+ "TLDAP_SIZELIMIT_EXCEEDED" },
+ { TLDAP_COMPARE_FALSE,
+ "TLDAP_COMPARE_FALSE" },
+ { TLDAP_COMPARE_TRUE,
+ "TLDAP_COMPARE_TRUE" },
+ { TLDAP_STRONG_AUTH_NOT_SUPPORTED,
+ "TLDAP_STRONG_AUTH_NOT_SUPPORTED" },
+ { TLDAP_STRONG_AUTH_REQUIRED,
+ "TLDAP_STRONG_AUTH_REQUIRED" },
+ { TLDAP_REFERRAL,
+ "TLDAP_REFERRAL" },
+ { TLDAP_ADMINLIMIT_EXCEEDED,
+ "TLDAP_ADMINLIMIT_EXCEEDED" },
+ { TLDAP_UNAVAILABLE_CRITICAL_EXTENSION,
+ "TLDAP_UNAVAILABLE_CRITICAL_EXTENSION" },
+ { TLDAP_CONFIDENTIALITY_REQUIRED,
+ "TLDAP_CONFIDENTIALITY_REQUIRED" },
+ { TLDAP_SASL_BIND_IN_PROGRESS,
+ "TLDAP_SASL_BIND_IN_PROGRESS" },
+ { TLDAP_NO_SUCH_ATTRIBUTE,
+ "TLDAP_NO_SUCH_ATTRIBUTE" },
+ { TLDAP_UNDEFINED_TYPE,
+ "TLDAP_UNDEFINED_TYPE" },
+ { TLDAP_INAPPROPRIATE_MATCHING,
+ "TLDAP_INAPPROPRIATE_MATCHING" },
+ { TLDAP_CONSTRAINT_VIOLATION,
+ "TLDAP_CONSTRAINT_VIOLATION" },
+ { TLDAP_TYPE_OR_VALUE_EXISTS,
+ "TLDAP_TYPE_OR_VALUE_EXISTS" },
+ { TLDAP_INVALID_SYNTAX,
+ "TLDAP_INVALID_SYNTAX" },
+ { TLDAP_NO_SUCH_OBJECT,
+ "TLDAP_NO_SUCH_OBJECT" },
+ { TLDAP_ALIAS_PROBLEM,
+ "TLDAP_ALIAS_PROBLEM" },
+ { TLDAP_INVALID_DN_SYNTAX,
+ "TLDAP_INVALID_DN_SYNTAX" },
+ { TLDAP_IS_LEAF,
+ "TLDAP_IS_LEAF" },
+ { TLDAP_ALIAS_DEREF_PROBLEM,
+ "TLDAP_ALIAS_DEREF_PROBLEM" },
+ { TLDAP_INAPPROPRIATE_AUTH,
+ "TLDAP_INAPPROPRIATE_AUTH" },
+ { TLDAP_INVALID_CREDENTIALS,
+ "TLDAP_INVALID_CREDENTIALS" },
+ { TLDAP_INSUFFICIENT_ACCESS,
+ "TLDAP_INSUFFICIENT_ACCESS" },
+ { TLDAP_BUSY,
+ "TLDAP_BUSY" },
+ { TLDAP_UNAVAILABLE,
+ "TLDAP_UNAVAILABLE" },
+ { TLDAP_UNWILLING_TO_PERFORM,
+ "TLDAP_UNWILLING_TO_PERFORM" },
+ { TLDAP_LOOP_DETECT,
+ "TLDAP_LOOP_DETECT" },
+ { TLDAP_NAMING_VIOLATION,
+ "TLDAP_NAMING_VIOLATION" },
+ { TLDAP_OBJECT_CLASS_VIOLATION,
+ "TLDAP_OBJECT_CLASS_VIOLATION" },
+ { TLDAP_NOT_ALLOWED_ON_NONLEAF,
+ "TLDAP_NOT_ALLOWED_ON_NONLEAF" },
+ { TLDAP_NOT_ALLOWED_ON_RDN,
+ "TLDAP_NOT_ALLOWED_ON_RDN" },
+ { TLDAP_ALREADY_EXISTS,
+ "TLDAP_ALREADY_EXISTS" },
+ { TLDAP_NO_OBJECT_CLASS_MODS,
+ "TLDAP_NO_OBJECT_CLASS_MODS" },
+ { TLDAP_RESULTS_TOO_LARGE,
+ "TLDAP_RESULTS_TOO_LARGE" },
+ { TLDAP_AFFECTS_MULTIPLE_DSAS,
+ "TLDAP_AFFECTS_MULTIPLE_DSAS" },
+ { TLDAP_OTHER,
+ "TLDAP_OTHER" },
+ { TLDAP_SERVER_DOWN,
+ "TLDAP_SERVER_DOWN" },
+ { TLDAP_LOCAL_ERROR,
+ "TLDAP_LOCAL_ERROR" },
+ { TLDAP_ENCODING_ERROR,
+ "TLDAP_ENCODING_ERROR" },
+ { TLDAP_DECODING_ERROR,
+ "TLDAP_DECODING_ERROR" },
+ { TLDAP_TIMEOUT,
+ "TLDAP_TIMEOUT" },
+ { TLDAP_AUTH_UNKNOWN,
+ "TLDAP_AUTH_UNKNOWN" },
+ { TLDAP_FILTER_ERROR,
+ "TLDAP_FILTER_ERROR" },
+ { TLDAP_USER_CANCELLED,
+ "TLDAP_USER_CANCELLED" },
+ { TLDAP_PARAM_ERROR,
+ "TLDAP_PARAM_ERROR" },
+ { TLDAP_NO_MEMORY,
+ "TLDAP_NO_MEMORY" },
+ { TLDAP_CONNECT_ERROR,
+ "TLDAP_CONNECT_ERROR" },
+ { TLDAP_NOT_SUPPORTED,
+ "TLDAP_NOT_SUPPORTED" },
+ { TLDAP_CONTROL_NOT_FOUND,
+ "TLDAP_CONTROL_NOT_FOUND" },
+ { TLDAP_NO_RESULTS_RETURNED,
+ "TLDAP_NO_RESULTS_RETURNED" },
+ { TLDAP_MORE_RESULTS_TO_RETURN,
+ "TLDAP_MORE_RESULTS_TO_RETURN" },
+ { TLDAP_CLIENT_LOOP,
+ "TLDAP_CLIENT_LOOP" },
+ { TLDAP_REFERRAL_LIMIT_EXCEEDED,
+ "TLDAP_REFERRAL_LIMIT_EXCEEDED" },
+};
+
+const char *tldap_rc2string(TLDAPRC rc)
+{
+ size_t i;
+
+ for (i=0; i<ARRAY_SIZE(tldaprc_errmap); i++) {
+ if (TLDAP_RC_EQUAL(rc, tldaprc_errmap[i].rc)) {
+ return tldaprc_errmap[i].string;
+ }
+ }
+
+ return "Unknown LDAP Error";
+}
diff --git a/source3/lib/tldap_gensec_bind.c b/source3/lib/tldap_gensec_bind.c
new file mode 100644
index 0000000..c409213
--- /dev/null
+++ b/source3/lib/tldap_gensec_bind.c
@@ -0,0 +1,366 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Gensec based tldap auth
+ * Copyright (C) Volker Lendecke 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "tldap_gensec_bind.h"
+#include "tldap_util.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/samba_util.h"
+#include "lib/util/debug.h"
+#include "auth/gensec/gensec.h"
+#include "auth/gensec/gensec_internal.h" /* TODO: remove this */
+#include "lib/param/param.h"
+#include "source4/auth/gensec/gensec_tstream.h"
+
+struct tldap_gensec_bind_state {
+ struct tevent_context *ev;
+ struct tldap_context *ctx;
+ struct cli_credentials *creds;
+ const char *target_service;
+ const char *target_hostname;
+ const char *target_principal;
+ struct loadparm_context *lp_ctx;
+ uint32_t gensec_features;
+
+ bool first;
+ struct gensec_security *gensec;
+ NTSTATUS gensec_status;
+ DATA_BLOB gensec_output;
+};
+
+static void tldap_gensec_bind_got_mechs(struct tevent_req *subreq);
+static void tldap_gensec_update_done(struct tldap_gensec_bind_state *state,
+ struct tevent_req *subreq);
+static void tldap_gensec_bind_done(struct tevent_req *subreq);
+
+static struct tevent_req *tldap_gensec_bind_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct tldap_context *ctx, struct cli_credentials *creds,
+ const char *target_service, const char *target_hostname,
+ const char *target_principal, struct loadparm_context *lp_ctx,
+ uint32_t gensec_features)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_gensec_bind_state *state;
+
+ const char *attrs[] = { "supportedSASLMechanisms" };
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tldap_gensec_bind_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctx = ctx;
+ state->creds = creds;
+ state->target_service = target_service;
+ state->target_hostname = target_hostname;
+ state->target_principal = target_principal;
+ state->lp_ctx = lp_ctx;
+ state->gensec_features = gensec_features;
+ state->first = true;
+
+ subreq = tldap_search_all_send(
+ state, state->ev, state->ctx, "", TLDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, ARRAY_SIZE(attrs),
+ false, NULL, 0, NULL, 0, 0, 1 /* sizelimit */, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_gensec_bind_got_mechs, req);
+ return req;
+}
+
+static void tldap_gensec_bind_got_mechs(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_gensec_bind_state *state = tevent_req_data(
+ req, struct tldap_gensec_bind_state);
+ struct tldap_message **msgs, *msg, *result;
+ struct tldap_attribute *attribs, *attrib;
+ int num_attribs;
+ size_t num_msgs;
+ TLDAPRC rc;
+ int i;
+ bool ok;
+ const char **sasl_mechs;
+ NTSTATUS status;
+
+ rc = tldap_search_all_recv(subreq, state, &msgs, &result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_ldap_error(req, rc)) {
+ return;
+ }
+
+ /*
+ * TODO: Inspect "Result"
+ */
+
+ num_msgs = talloc_array_length(msgs);
+ if (num_msgs != 1) {
+ DBG_DEBUG("num_msgs = %zu\n", num_msgs);
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+ msg = msgs[0];
+
+ ok = tldap_entry_attributes(msg, &attribs, &num_attribs);
+ if (!ok) {
+ DBG_DEBUG("tldap_entry_attributes failed\n");
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+
+ if (num_attribs != 1) {
+ DBG_DEBUG("num_attribs = %d\n", num_attribs);
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+ attrib = &attribs[0];
+
+ sasl_mechs = talloc_array(state, const char *, attrib->num_values+1);
+ if (tevent_req_nomem(sasl_mechs, req)) {
+ return;
+ }
+
+ for (i=0; i<attrib->num_values; i++) {
+ DATA_BLOB *v = &attrib->values[i];
+ size_t len;
+
+ ok = convert_string_talloc(sasl_mechs, CH_UTF8, CH_UNIX,
+ v->data, v->length,
+ &sasl_mechs[i], &len);
+ if (!ok) {
+ DBG_DEBUG("convert_string_talloc failed\n");
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+ }
+ sasl_mechs[attrib->num_values] = NULL;
+
+ gensec_init();
+
+ status = gensec_client_start(
+ state, &state->gensec,
+ lpcfg_gensec_settings(state, state->lp_ctx));
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("gensec_client_start failed: %s\n",
+ nt_errstr(status));
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+
+ status = gensec_set_credentials(state->gensec, state->creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("gensec_set_credentials failed: %s\n",
+ nt_errstr(status));
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+
+ status = gensec_set_target_service(state->gensec,
+ state->target_service);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("gensec_set_target_service failed: %s\n",
+ nt_errstr(status));
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+
+ if (state->target_hostname != NULL) {
+ status = gensec_set_target_hostname(state->gensec,
+ state->target_hostname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("gensec_set_target_hostname failed: %s\n",
+ nt_errstr(status));
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+ }
+
+ if (state->target_principal != NULL) {
+ status = gensec_set_target_principal(state->gensec,
+ state->target_principal);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("gensec_set_target_principal failed: %s\n",
+ nt_errstr(status));
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+ }
+
+ gensec_want_feature(state->gensec, state->gensec_features);
+
+ status = gensec_start_mech_by_sasl_list(state->gensec, sasl_mechs);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("gensec_start_mech_by_sasl_list failed: %s\n",
+ nt_errstr(status));
+ tevent_req_ldap_error(req, TLDAP_OPERATIONS_ERROR);
+ return;
+ }
+
+ state->gensec_status = gensec_update(state->gensec, state,
+ data_blob_null,
+ &state->gensec_output);
+ tldap_gensec_update_done(state, req);
+}
+
+static void tldap_gensec_update_done(struct tldap_gensec_bind_state *state,
+ struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+
+ if (!NT_STATUS_IS_OK(state->gensec_status) &&
+ !NT_STATUS_EQUAL(state->gensec_status,
+ NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ DBG_DEBUG("gensec_update failed: %s\n",
+ nt_errstr(state->gensec_status));
+ tevent_req_ldap_error(req, TLDAP_INVALID_CREDENTIALS);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(state->gensec_status) &&
+ (state->gensec_output.length == 0)) {
+
+ if (state->first) {
+ tevent_req_ldap_error(req, TLDAP_INVALID_CREDENTIALS);
+ } else {
+ tevent_req_done(req);
+ }
+ return;
+ }
+
+ state->first = false;
+
+ subreq = tldap_sasl_bind_send(
+ state, state->ev, state->ctx, "",
+ state->gensec->ops->sasl_name, &state->gensec_output,
+ NULL, 0, NULL, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tldap_gensec_bind_done, req);
+}
+
+static void tldap_gensec_bind_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_gensec_bind_state *state = tevent_req_data(
+ req, struct tldap_gensec_bind_state);
+ DATA_BLOB input;
+ TLDAPRC rc;
+
+ rc = tldap_sasl_bind_recv(subreq, state, &input);
+ TALLOC_FREE(subreq);
+ if (!TLDAP_RC_IS_SUCCESS(rc) &&
+ !TLDAP_RC_EQUAL(rc, TLDAP_SASL_BIND_IN_PROGRESS)) {
+ tevent_req_ldap_error(req, rc);
+ return;
+ }
+
+ if (TLDAP_RC_IS_SUCCESS(rc) && NT_STATUS_IS_OK(state->gensec_status)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ state->gensec_status = gensec_update(state->gensec, state,
+ input,
+ &state->gensec_output);
+ tldap_gensec_update_done(state, req);
+}
+
+static TLDAPRC tldap_gensec_bind_recv(struct tevent_req *req)
+{
+ struct tldap_gensec_bind_state *state = tevent_req_data(
+ req, struct tldap_gensec_bind_state);
+ struct tstream_context *plain, *sec;
+ NTSTATUS status;
+ TLDAPRC rc;
+
+ if (tevent_req_is_ldap_error(req, &rc)) {
+ return rc;
+ }
+
+ if ((state->gensec_features & GENSEC_FEATURE_SIGN) &&
+ !gensec_have_feature(state->gensec, GENSEC_FEATURE_SIGN)) {
+ return TLDAP_OPERATIONS_ERROR;
+ }
+ if ((state->gensec_features & GENSEC_FEATURE_SEAL) &&
+ !gensec_have_feature(state->gensec, GENSEC_FEATURE_SEAL)) {
+ return TLDAP_OPERATIONS_ERROR;
+ }
+
+ if (!gensec_have_feature(state->gensec, GENSEC_FEATURE_SIGN) &&
+ !gensec_have_feature(state->gensec, GENSEC_FEATURE_SEAL)) {
+ return TLDAP_SUCCESS;
+ }
+
+ /*
+ * The gensec ctx needs to survive as long as the ldap context
+ * lives
+ */
+ talloc_steal(state->ctx, state->gensec);
+
+ plain = tldap_get_tstream(state->ctx);
+
+ status = gensec_create_tstream(state->ctx, state->gensec,
+ plain, &sec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("gensec_create_tstream failed: %s\n",
+ nt_errstr(status));
+ return TLDAP_OPERATIONS_ERROR;
+ }
+
+ tldap_set_tstream(state->ctx, sec);
+
+ return TLDAP_SUCCESS;
+}
+
+TLDAPRC tldap_gensec_bind(
+ struct tldap_context *ctx, struct cli_credentials *creds,
+ const char *target_service, const char *target_hostname,
+ const char *target_principal, struct loadparm_context *lp_ctx,
+ uint32_t gensec_features)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TLDAPRC rc = TLDAP_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = tldap_gensec_bind_send(frame, ev, ctx, creds, target_service,
+ target_hostname, target_principal, lp_ctx,
+ gensec_features);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ rc = TLDAP_OPERATIONS_ERROR;
+ goto fail;
+ }
+ rc = tldap_gensec_bind_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return rc;
+}
diff --git a/source3/lib/tldap_gensec_bind.h b/source3/lib/tldap_gensec_bind.h
new file mode 100644
index 0000000..cb6b8e6
--- /dev/null
+++ b/source3/lib/tldap_gensec_bind.h
@@ -0,0 +1,33 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Gensec based tldap bind
+ * Copyright (C) Volker Lendecke 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __TLDAP_GENSEC_BIND_H__
+#define __TLDAP_GENSEC_BIND_H__
+
+#include "replace.h"
+#include "tldap.h"
+#include "auth/credentials/credentials.h"
+
+TLDAPRC tldap_gensec_bind(
+ struct tldap_context *ctx, struct cli_credentials *creds,
+ const char *target_service, const char *target_hostname,
+ const char *target_principal, struct loadparm_context *lp_ctx,
+ uint32_t gensec_features);
+
+#endif
diff --git a/source3/lib/tldap_util.c b/source3/lib/tldap_util.c
new file mode 100644
index 0000000..89aea4c
--- /dev/null
+++ b/source3/lib/tldap_util.c
@@ -0,0 +1,843 @@
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for async ldap client requests
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "tldap.h"
+#include "tldap_util.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/asn1.h"
+#include "lib/util/smb_strtox.h"
+
+bool tldap_entry_values(struct tldap_message *msg, const char *attribute,
+ DATA_BLOB **values, int *num_values)
+{
+ struct tldap_attribute *attributes;
+ int i, num_attributes;
+
+ if (!tldap_entry_attributes(msg, &attributes, &num_attributes)) {
+ return false;
+ }
+
+ for (i=0; i<num_attributes; i++) {
+ if (strequal(attribute, attributes[i].name)) {
+ break;
+ }
+ }
+ if (i == num_attributes) {
+ return false;
+ }
+ *num_values = attributes[i].num_values;
+ *values = attributes[i].values;
+ return true;
+}
+
+bool tldap_get_single_valueblob(struct tldap_message *msg,
+ const char *attribute, DATA_BLOB *blob)
+{
+ int num_values;
+ DATA_BLOB *values;
+
+ if (attribute == NULL) {
+ return NULL;
+ }
+ if (!tldap_entry_values(msg, attribute, &values, &num_values)) {
+ return NULL;
+ }
+ if (num_values != 1) {
+ return NULL;
+ }
+ *blob = values[0];
+ return true;
+}
+
+char *tldap_talloc_single_attribute(struct tldap_message *msg,
+ const char *attribute,
+ TALLOC_CTX *mem_ctx)
+{
+ DATA_BLOB val;
+ char *result;
+ size_t len;
+
+ if (!tldap_get_single_valueblob(msg, attribute, &val)) {
+ return NULL;
+ }
+ if (!convert_string_talloc(mem_ctx, CH_UTF8, CH_UNIX,
+ val.data, val.length,
+ &result, &len)) {
+ return NULL;
+ }
+ return result;
+}
+
+bool tldap_pull_binsid(struct tldap_message *msg, const char *attribute,
+ struct dom_sid *sid)
+{
+ DATA_BLOB val;
+ ssize_t ret;
+
+ if (!tldap_get_single_valueblob(msg, attribute, &val)) {
+ return false;
+ }
+ ret = sid_parse(val.data, val.length, sid);
+ return (ret != -1);
+}
+
+bool tldap_pull_guid(struct tldap_message *msg, const char *attribute,
+ struct GUID *guid)
+{
+ DATA_BLOB val;
+
+ if (!tldap_get_single_valueblob(msg, attribute, &val)) {
+ return false;
+ }
+ return NT_STATUS_IS_OK(GUID_from_data_blob(&val, guid));
+}
+
+static bool tldap_add_blob_vals(TALLOC_CTX *mem_ctx, struct tldap_mod *mod,
+ DATA_BLOB *newvals, int num_newvals)
+{
+ int num_values = talloc_array_length(mod->values);
+ int i;
+ DATA_BLOB *tmp;
+
+ tmp = talloc_realloc(mem_ctx, mod->values, DATA_BLOB,
+ num_values + num_newvals);
+ if (tmp == NULL) {
+ return false;
+ }
+ mod->values = tmp;
+
+ for (i=0; i<num_newvals; i++) {
+ mod->values[i+num_values].data = (uint8_t *)talloc_memdup(
+ mod->values, newvals[i].data, newvals[i].length);
+ if (mod->values[i+num_values].data == NULL) {
+ return false;
+ }
+ mod->values[i+num_values].length = newvals[i].length;
+ }
+ mod->num_values = num_values + num_newvals;
+ return true;
+}
+
+bool tldap_add_mod_blobs(TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ int mod_op, const char *attrib,
+ DATA_BLOB *newvals, int num_newvals)
+{
+ struct tldap_mod new_mod;
+ struct tldap_mod *mods = *pmods;
+ struct tldap_mod *mod = NULL;
+ int i, num_mods;
+
+ if (mods == NULL) {
+ mods = talloc_array(mem_ctx, struct tldap_mod, 0);
+ }
+ if (mods == NULL) {
+ return false;
+ }
+
+ num_mods = *pnum_mods;
+
+ for (i=0; i<num_mods; i++) {
+ if ((mods[i].mod_op == mod_op)
+ && strequal(mods[i].attribute, attrib)) {
+ mod = &mods[i];
+ break;
+ }
+ }
+
+ if (mod == NULL) {
+ new_mod.mod_op = mod_op;
+ new_mod.attribute = talloc_strdup(mods, attrib);
+ if (new_mod.attribute == NULL) {
+ return false;
+ }
+ new_mod.num_values = 0;
+ new_mod.values = NULL;
+ mod = &new_mod;
+ }
+
+ if ((num_newvals != 0)
+ && !tldap_add_blob_vals(mods, mod, newvals, num_newvals)) {
+ return false;
+ }
+
+ if ((i == num_mods) && (talloc_array_length(mods) < num_mods + 1)) {
+ mods = talloc_realloc(talloc_tos(), mods, struct tldap_mod,
+ num_mods+1);
+ if (mods == NULL) {
+ return false;
+ }
+ mods[num_mods] = *mod;
+ }
+
+ *pmods = mods;
+ *pnum_mods += 1;
+ return true;
+}
+
+bool tldap_add_mod_str(TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ int mod_op, const char *attrib, const char *str)
+{
+ DATA_BLOB utf8;
+ bool ret;
+
+ if (!convert_string_talloc(talloc_tos(), CH_UNIX, CH_UTF8, str,
+ strlen(str), &utf8.data, &utf8.length)) {
+ return false;
+ }
+
+ ret = tldap_add_mod_blobs(mem_ctx, pmods, pnum_mods, mod_op, attrib,
+ &utf8, 1);
+ TALLOC_FREE(utf8.data);
+ return ret;
+}
+
+static bool tldap_make_mod_blob_int(struct tldap_message *existing,
+ TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ const char *attrib, DATA_BLOB newval,
+ int (*comparison)(const DATA_BLOB *d1,
+ const DATA_BLOB *d2))
+{
+ int num_values = 0;
+ DATA_BLOB *values = NULL;
+ DATA_BLOB oldval = data_blob_null;
+
+ if ((existing != NULL)
+ && tldap_entry_values(existing, attrib, &values, &num_values)) {
+
+ if (num_values > 1) {
+ /* can't change multivalue attributes atm */
+ return false;
+ }
+ if (num_values == 1) {
+ oldval = values[0];
+ }
+ }
+
+ if ((oldval.data != NULL) && (newval.data != NULL)
+ && (comparison(&oldval, &newval) == 0)) {
+ /* Believe it or not, but LDAP will deny a delete and
+ an add at the same time if the values are the
+ same... */
+ DEBUG(10,("tldap_make_mod_blob_int: attribute |%s| not "
+ "changed.\n", attrib));
+ return true;
+ }
+
+ if (oldval.data != NULL) {
+ /* By deleting exactly the value we found in the entry this
+ * should be race-free in the sense that the LDAP-Server will
+ * deny the complete operation if somebody changed the
+ * attribute behind our back. */
+ /* This will also allow modifying single valued attributes in
+ * Novell NDS. In NDS you have to first remove attribute and
+ * then you could add new value */
+
+ DEBUG(10, ("tldap_make_mod_blob_int: deleting attribute |%s|\n",
+ attrib));
+ if (!tldap_add_mod_blobs(mem_ctx, pmods, pnum_mods,
+ TLDAP_MOD_DELETE,
+ attrib, &oldval, 1)) {
+ return false;
+ }
+ }
+
+ /* Regardless of the real operation (add or modify)
+ we add the new value here. We rely on deleting
+ the old value, should it exist. */
+
+ if (newval.data != NULL) {
+ DEBUG(10, ("tldap_make_mod_blob_int: adding attribute |%s| value len "
+ "%d\n", attrib, (int)newval.length));
+ if (!tldap_add_mod_blobs(mem_ctx, pmods, pnum_mods,
+ TLDAP_MOD_ADD,
+ attrib, &newval, 1)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool tldap_make_mod_blob(struct tldap_message *existing, TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ const char *attrib, DATA_BLOB newval)
+{
+ return tldap_make_mod_blob_int(existing, mem_ctx, pmods, pnum_mods,
+ attrib, newval, data_blob_cmp);
+}
+
+static int compare_utf8_blobs(const DATA_BLOB *d1, const DATA_BLOB *d2)
+{
+ char *s1, *s2;
+ size_t s1len, s2len;
+ int ret;
+
+ if (!convert_string_talloc(talloc_tos(), CH_UTF8, CH_UNIX, d1->data,
+ d1->length, &s1, &s1len)) {
+ /* can't do much here */
+ return 0;
+ }
+ if (!convert_string_talloc(talloc_tos(), CH_UTF8, CH_UNIX, d2->data,
+ d2->length, &s2, &s2len)) {
+ /* can't do much here */
+ TALLOC_FREE(s1);
+ return 0;
+ }
+ ret = strcasecmp_m(s1, s2);
+ TALLOC_FREE(s2);
+ TALLOC_FREE(s1);
+ return ret;
+}
+
+bool tldap_make_mod_fmt(struct tldap_message *existing, TALLOC_CTX *mem_ctx,
+ struct tldap_mod **pmods, int *pnum_mods,
+ const char *attrib, const char *fmt, ...)
+{
+ va_list ap;
+ char *newval;
+ bool ret;
+ DATA_BLOB blob = data_blob_null;
+
+ va_start(ap, fmt);
+ newval = talloc_vasprintf(talloc_tos(), fmt, ap);
+ va_end(ap);
+
+ if (newval == NULL) {
+ return false;
+ }
+
+ blob.length = strlen(newval);
+ if (blob.length != 0) {
+ blob.data = discard_const_p(uint8_t, newval);
+ }
+ ret = tldap_make_mod_blob_int(existing, mem_ctx, pmods, pnum_mods,
+ attrib, blob, compare_utf8_blobs);
+ TALLOC_FREE(newval);
+ return ret;
+}
+
+const char *tldap_errstr(TALLOC_CTX *mem_ctx, struct tldap_context *ld,
+ TLDAPRC rc)
+{
+ const char *ld_error = NULL;
+ char *res;
+
+ if (ld != NULL) {
+ ld_error = tldap_msg_diagnosticmessage(tldap_ctx_lastmsg(ld));
+ }
+ res = talloc_asprintf(mem_ctx, "LDAP error %d (%s), %s",
+ (int)TLDAP_RC_V(rc), tldap_rc2string(rc),
+ ld_error ? ld_error : "unknown");
+ return res;
+}
+
+TLDAPRC tldap_search_va(struct tldap_context *ld, const char *base, int scope,
+ const char *attrs[], int num_attrs, int attrsonly,
+ TALLOC_CTX *mem_ctx, struct tldap_message ***res,
+ const char *fmt, va_list ap)
+{
+ char *filter;
+ TLDAPRC rc;
+
+ filter = talloc_vasprintf(talloc_tos(), fmt, ap);
+ if (filter == NULL) {
+ return TLDAP_NO_MEMORY;
+ }
+
+ rc = tldap_search(ld, base, scope, filter,
+ attrs, num_attrs, attrsonly,
+ NULL /*sctrls*/, 0, NULL /*cctrls*/, 0,
+ 0 /*timelimit*/, 0 /*sizelimit*/, 0 /*deref*/,
+ mem_ctx, res);
+ TALLOC_FREE(filter);
+ return rc;
+}
+
+TLDAPRC tldap_search_fmt(struct tldap_context *ld, const char *base, int scope,
+ const char *attrs[], int num_attrs, int attrsonly,
+ TALLOC_CTX *mem_ctx, struct tldap_message ***res,
+ const char *fmt, ...)
+{
+ va_list ap;
+ TLDAPRC rc;
+
+ va_start(ap, fmt);
+ rc = tldap_search_va(ld, base, scope, attrs, num_attrs, attrsonly,
+ mem_ctx, res, fmt, ap);
+ va_end(ap);
+ return rc;
+}
+
+bool tldap_pull_uint64(struct tldap_message *msg, const char *attr,
+ uint64_t *presult)
+{
+ char *str;
+ uint64_t result;
+ int error = 0;
+
+ str = tldap_talloc_single_attribute(msg, attr, talloc_tos());
+ if (str == NULL) {
+ DEBUG(10, ("Could not find attribute %s\n", attr));
+ return false;
+ }
+
+ result = smb_strtoull(str, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_DEBUG("Attribute conversion failed (%s)\n",
+ strerror(error));
+ TALLOC_FREE(str);
+ return false;
+ }
+
+ TALLOC_FREE(str);
+ *presult = result;
+ return true;
+}
+
+bool tldap_pull_uint32(struct tldap_message *msg, const char *attr,
+ uint32_t *presult)
+{
+ uint64_t result;
+
+ if (!tldap_pull_uint64(msg, attr, &result)) {
+ return false;
+ }
+ *presult = (uint32_t)result;
+ return true;
+}
+
+struct tldap_fetch_rootdse_state {
+ struct tldap_context *ld;
+ struct tldap_message *rootdse;
+};
+
+static void tldap_fetch_rootdse_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_fetch_rootdse_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_fetch_rootdse_state *state;
+ static const char *attrs[2] = { "*", "+" };
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tldap_fetch_rootdse_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ld = ld;
+ state->rootdse = NULL;
+
+ subreq = tldap_search_send(
+ mem_ctx, ev, ld, "", TLDAP_SCOPE_BASE, "(objectclass=*)",
+ attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0, 0, 0, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_fetch_rootdse_done, req);
+ return req;
+}
+
+static void tldap_fetch_rootdse_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_fetch_rootdse_state *state = tevent_req_data(
+ req, struct tldap_fetch_rootdse_state);
+ struct tldap_message *msg;
+ TLDAPRC rc;
+
+ rc = tldap_search_recv(subreq, state, &msg);
+ if (tevent_req_ldap_error(req, rc)) {
+ return;
+ }
+
+ switch (tldap_msg_type(msg)) {
+ case TLDAP_RES_SEARCH_ENTRY:
+ if (state->rootdse != NULL) {
+ goto protocol_error;
+ }
+ state->rootdse = msg;
+ break;
+ case TLDAP_RES_SEARCH_RESULT:
+ TALLOC_FREE(subreq);
+ if (state->rootdse == NULL) {
+ goto protocol_error;
+ }
+ tevent_req_done(req);
+ break;
+ default:
+ goto protocol_error;
+ }
+ return;
+
+protocol_error:
+ tevent_req_ldap_error(req, TLDAP_PROTOCOL_ERROR);
+ return;
+}
+
+TLDAPRC tldap_fetch_rootdse_recv(struct tevent_req *req)
+{
+ struct tldap_fetch_rootdse_state *state = tevent_req_data(
+ req, struct tldap_fetch_rootdse_state);
+ TLDAPRC rc;
+ char *dn;
+
+ if (tevent_req_is_ldap_error(req, &rc)) {
+ return rc;
+ }
+ /* Trigger parsing the dn, just to make sure it's ok */
+ if (!tldap_entry_dn(state->rootdse, &dn)) {
+ return TLDAP_DECODING_ERROR;
+ }
+ if (!tldap_context_setattr(state->ld, "tldap:rootdse",
+ &state->rootdse)) {
+ return TLDAP_NO_MEMORY;
+ }
+ return TLDAP_SUCCESS;
+}
+
+TLDAPRC tldap_fetch_rootdse(struct tldap_context *ld)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TLDAPRC rc = TLDAP_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = tldap_fetch_rootdse_send(frame, ev, ld);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ rc = TLDAP_OPERATIONS_ERROR;
+ goto fail;
+ }
+
+ rc = tldap_fetch_rootdse_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return rc;
+}
+
+struct tldap_message *tldap_rootdse(struct tldap_context *ld)
+{
+ return talloc_get_type(tldap_context_getattr(ld, "tldap:rootdse"),
+ struct tldap_message);
+}
+
+bool tldap_entry_has_attrvalue(struct tldap_message *msg,
+ const char *attribute,
+ const DATA_BLOB blob)
+{
+ int i, num_values;
+ DATA_BLOB *values;
+
+ if (!tldap_entry_values(msg, attribute, &values, &num_values)) {
+ return false;
+ }
+ for (i=0; i<num_values; i++) {
+ if (data_blob_cmp(&values[i], &blob) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool tldap_supports_control(struct tldap_context *ld, const char *oid)
+{
+ struct tldap_message *rootdse = tldap_rootdse(ld);
+
+ if (rootdse == NULL) {
+ return false;
+ }
+ return tldap_entry_has_attrvalue(rootdse, "supportedControl",
+ data_blob_const(oid, strlen(oid)));
+}
+
+struct tldap_control *tldap_add_control(TALLOC_CTX *mem_ctx,
+ struct tldap_control *ctrls,
+ int num_ctrls,
+ struct tldap_control *ctrl)
+{
+ struct tldap_control *result;
+
+ result = talloc_array(mem_ctx, struct tldap_control, num_ctrls+1);
+ if (result == NULL) {
+ return NULL;
+ }
+ if (num_ctrls > 0) {
+ memcpy(result, ctrls, sizeof(struct tldap_control) * num_ctrls);
+ }
+ result[num_ctrls] = *ctrl;
+ return result;
+}
+
+/*
+ * Find a control returned by the server
+ */
+struct tldap_control *tldap_msg_findcontrol(struct tldap_message *msg,
+ const char *oid)
+{
+ struct tldap_control *controls;
+ int i, num_controls;
+
+ tldap_msg_sctrls(msg, &num_controls, &controls);
+
+ for (i=0; i<num_controls; i++) {
+ if (strcmp(controls[i].oid, oid) == 0) {
+ return &controls[i];
+ }
+ }
+ return NULL;
+}
+
+struct tldap_search_paged_state {
+ struct tevent_context *ev;
+ struct tldap_context *ld;
+ const char *base;
+ const char *filter;
+ int scope;
+ const char **attrs;
+ int num_attrs;
+ int attrsonly;
+ struct tldap_control *sctrls;
+ int num_sctrls;
+ struct tldap_control *cctrls;
+ int num_cctrls;
+ int timelimit;
+ int sizelimit;
+ int deref;
+
+ int page_size;
+ struct asn1_data *asn1;
+ DATA_BLOB cookie;
+ struct tldap_message *result;
+};
+
+static struct tevent_req *tldap_ship_paged_search(
+ TALLOC_CTX *mem_ctx,
+ struct tldap_search_paged_state *state)
+{
+ struct tldap_control *pgctrl;
+ struct asn1_data *asn1 = NULL;
+
+ asn1 = asn1_init(state, ASN1_MAX_TREE_DEPTH);
+ if (asn1 == NULL) {
+ return NULL;
+ }
+ if (!asn1_push_tag(asn1, ASN1_SEQUENCE(0))) goto err;
+ if (!asn1_write_Integer(asn1, state->page_size)) goto err;
+ if (!asn1_write_OctetString(asn1, state->cookie.data, state->cookie.length)) goto err;
+ if (!asn1_pop_tag(asn1)) goto err;
+ state->asn1 = asn1;
+
+ pgctrl = &state->sctrls[state->num_sctrls-1];
+ pgctrl->oid = TLDAP_CONTROL_PAGEDRESULTS;
+ pgctrl->critical = true;
+ if (!asn1_blob(state->asn1, &pgctrl->value)) {
+ goto err;
+ }
+ return tldap_search_send(mem_ctx, state->ev, state->ld, state->base,
+ state->scope, state->filter, state->attrs,
+ state->num_attrs, state->attrsonly,
+ state->sctrls, state->num_sctrls,
+ state->cctrls, state->num_cctrls,
+ state->timelimit, state->sizelimit,
+ state->deref);
+
+ err:
+
+ TALLOC_FREE(asn1);
+ return NULL;
+}
+
+static void tldap_search_paged_done(struct tevent_req *subreq);
+
+struct tevent_req *tldap_search_paged_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tldap_context *ld,
+ const char *base, int scope,
+ const char *filter,
+ const char **attrs,
+ int num_attrs,
+ int attrsonly,
+ struct tldap_control *sctrls,
+ int num_sctrls,
+ struct tldap_control *cctrls,
+ int num_cctrls,
+ int timelimit,
+ int sizelimit,
+ int deref,
+ int page_size)
+{
+ struct tevent_req *req, *subreq;
+ struct tldap_search_paged_state *state;
+ struct tldap_control empty_control;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tldap_search_paged_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ld = ld;
+ state->base = base;
+ state->filter = filter;
+ state->scope = scope;
+ state->attrs = attrs;
+ state->num_attrs = num_attrs;
+ state->attrsonly = attrsonly;
+ state->cctrls = cctrls;
+ state->num_cctrls = num_cctrls;
+ state->timelimit = timelimit;
+ state->sizelimit = sizelimit;
+ state->deref = deref;
+
+ state->page_size = page_size;
+ state->asn1 = NULL;
+ state->cookie = data_blob_null;
+
+ ZERO_STRUCT(empty_control);
+
+ state->sctrls = tldap_add_control(state, sctrls, num_sctrls,
+ &empty_control);
+ if (tevent_req_nomem(state->sctrls, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->num_sctrls = num_sctrls+1;
+
+ subreq = tldap_ship_paged_search(state, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tldap_search_paged_done, req);
+
+ return req;
+}
+
+static void tldap_search_paged_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tldap_search_paged_state *state = tevent_req_data(
+ req, struct tldap_search_paged_state);
+ struct asn1_data *asn1 = NULL;
+ struct tldap_control *pgctrl;
+ TLDAPRC rc;
+ int size;
+
+ rc = tldap_search_recv(subreq, state, &state->result);
+ if (tevent_req_ldap_error(req, rc)) {
+ return;
+ }
+
+ TALLOC_FREE(state->asn1);
+
+ switch (tldap_msg_type(state->result)) {
+ case TLDAP_RES_SEARCH_ENTRY:
+ case TLDAP_RES_SEARCH_REFERENCE:
+ tevent_req_notify_callback(req);
+ return;
+ case TLDAP_RES_SEARCH_RESULT:
+ break;
+ default:
+ TALLOC_FREE(subreq);
+ tevent_req_ldap_error(req, TLDAP_PROTOCOL_ERROR);
+ return;
+ }
+
+ TALLOC_FREE(subreq);
+
+ /* We've finished one paged search, fire the next */
+
+ pgctrl = tldap_msg_findcontrol(state->result,
+ TLDAP_CONTROL_PAGEDRESULTS);
+ if (pgctrl == NULL) {
+ /* RFC2696 requires the server to return the control */
+ tevent_req_ldap_error(req, TLDAP_PROTOCOL_ERROR);
+ return;
+ }
+
+ TALLOC_FREE(state->cookie.data);
+
+ asn1 = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH);
+ if (tevent_req_nomem(asn1, req)) {
+ return;
+ }
+
+ asn1_load_nocopy(asn1, pgctrl->value.data, pgctrl->value.length);
+ if (!asn1_start_tag(asn1, ASN1_SEQUENCE(0))) goto err;
+ if (!asn1_read_Integer(asn1, &size)) goto err;
+ if (!asn1_read_OctetString(asn1, state, &state->cookie)) goto err;
+ if (!asn1_end_tag(asn1)) goto err;
+
+ TALLOC_FREE(asn1);
+
+ if (state->cookie.length == 0) {
+ /* We're done, no cookie anymore */
+ tevent_req_done(req);
+ return;
+ }
+
+ TALLOC_FREE(state->result);
+
+ subreq = tldap_ship_paged_search(state, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tldap_search_paged_done, req);
+
+ return;
+err:
+
+ TALLOC_FREE(asn1);
+ tevent_req_ldap_error(req, TLDAP_DECODING_ERROR);
+}
+
+TLDAPRC tldap_search_paged_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct tldap_message **pmsg)
+{
+ struct tldap_search_paged_state *state = tevent_req_data(
+ req, struct tldap_search_paged_state);
+ TLDAPRC rc;
+
+ if (!tevent_req_is_in_progress(req)
+ && tevent_req_is_ldap_error(req, &rc)) {
+ return rc;
+ }
+ if (tevent_req_is_in_progress(req)) {
+ switch (tldap_msg_type(state->result)) {
+ case TLDAP_RES_SEARCH_ENTRY:
+ case TLDAP_RES_SEARCH_REFERENCE:
+ break;
+ default:
+ return TLDAP_PROTOCOL_ERROR;
+ }
+ }
+ *pmsg = talloc_move(mem_ctx, &state->result);
+ return TLDAP_SUCCESS;
+}
diff --git a/source3/lib/username.c b/source3/lib/username.c
new file mode 100644
index 0000000..280285e
--- /dev/null
+++ b/source3/lib/username.c
@@ -0,0 +1,243 @@
+/*
+ Unix SMB/CIFS implementation.
+ Username handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1997-2001.
+ Copyright (C) Andrew Bartlett 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "../lib/util/memcache.h"
+#include "../lib/util/util_pw.h"
+#include "lib/util/string_wrappers.h"
+
+/* internal functions */
+static struct passwd *uname_string_combinations(char *s, TALLOC_CTX *mem_ctx,
+ struct passwd * (*fn) (TALLOC_CTX *mem_ctx, const char *),
+ int N);
+static struct passwd *uname_string_combinations2(char *s, TALLOC_CTX *mem_ctx, int offset,
+ struct passwd * (*fn) (TALLOC_CTX *mem_ctx, const char *),
+ int N);
+
+static struct passwd *getpwnam_alloc_cached(TALLOC_CTX *mem_ctx, const char *name)
+{
+ struct passwd *pw, *for_cache;
+
+ pw = (struct passwd *)memcache_lookup_talloc(
+ NULL, GETPWNAM_CACHE, data_blob_string_const_null(name));
+ if (pw != NULL) {
+ return tcopy_passwd(mem_ctx, pw);
+ }
+
+ pw = getpwnam(name);
+ if (pw == NULL) {
+ return NULL;
+ }
+
+ for_cache = tcopy_passwd(talloc_tos(), pw);
+ if (for_cache == NULL) {
+ return NULL;
+ }
+
+ memcache_add_talloc(NULL, GETPWNAM_CACHE,
+ data_blob_string_const_null(name), &for_cache);
+
+ return tcopy_passwd(mem_ctx, pw);
+}
+
+/****************************************************************************
+ Flush all cached passwd structs.
+****************************************************************************/
+
+void flush_pwnam_cache(void)
+{
+ memcache_flush(NULL, GETPWNAM_CACHE);
+}
+
+/****************************************************************************
+ Get a users home directory.
+****************************************************************************/
+
+char *get_user_home_dir(TALLOC_CTX *mem_ctx, const char *user)
+{
+ struct passwd *pass;
+ char *result;
+
+ /* Ensure the user exists. */
+
+ pass = Get_Pwnam_alloc(mem_ctx, user);
+
+ if (!pass)
+ return(NULL);
+
+ /* Return home directory from struct passwd. */
+
+ result = talloc_move(mem_ctx, &pass->pw_dir);
+
+ TALLOC_FREE(pass);
+ return result;
+}
+
+/****************************************************************************
+ * A wrapper for getpwnam(). The following variations are tried:
+ * - as transmitted
+ * - in all lower case if this differs from transmitted
+ * - in all upper case if this differs from transmitted
+ * - using lp_username_level() for permutations.
+****************************************************************************/
+
+static struct passwd *Get_Pwnam_internals(TALLOC_CTX *mem_ctx,
+ const char *user, char *user2)
+{
+ struct passwd *ret = NULL;
+
+ if (!user2 || !(*user2))
+ return(NULL);
+
+ if (!user || !(*user))
+ return(NULL);
+
+ /* Try in all lower case first as this is the most
+ common case on UNIX systems */
+ if (!strlower_m(user2)) {
+ DEBUG(5,("strlower_m %s failed\n", user2));
+ goto done;
+ }
+
+ DEBUG(5,("Trying _Get_Pwnam(), username as lowercase is %s\n",user2));
+ ret = getpwnam_alloc_cached(mem_ctx, user2);
+ if(ret)
+ goto done;
+
+ /* Try as given, if username wasn't originally lowercase */
+ if(strcmp(user, user2) != 0) {
+ DEBUG(5,("Trying _Get_Pwnam(), username as given is %s\n",
+ user));
+ ret = getpwnam_alloc_cached(mem_ctx, user);
+ if(ret)
+ goto done;
+ }
+
+ /* Try as uppercase, if username wasn't originally uppercase */
+ if (!strupper_m(user2)) {
+ goto done;
+ }
+
+ if(strcmp(user, user2) != 0) {
+ DEBUG(5,("Trying _Get_Pwnam(), username as uppercase is %s\n",
+ user2));
+ ret = getpwnam_alloc_cached(mem_ctx, user2);
+ if(ret)
+ goto done;
+ }
+
+ /* Try all combinations up to usernamelevel */
+ if (!strlower_m(user2)) {
+ DEBUG(5,("strlower_m %s failed\n", user2));
+ goto done;
+ }
+ DEBUG(5,("Checking combinations of %d uppercase letters in %s\n",
+ lp_username_level(), user2));
+ ret = uname_string_combinations(user2, mem_ctx, getpwnam_alloc_cached,
+ lp_username_level());
+
+done:
+ DEBUG(5,("Get_Pwnam_internals %s find user [%s]!\n",ret ?
+ "did":"didn't", user));
+
+ return ret;
+}
+
+/****************************************************************************
+ Get_Pwnam wrapper without modification.
+ NOTE: This with NOT modify 'user'!
+ This will return an allocated structure
+****************************************************************************/
+
+struct passwd *Get_Pwnam_alloc(TALLOC_CTX *mem_ctx, const char *user)
+{
+ fstring user2;
+
+ if ( *user == '\0' ) {
+ DEBUG(10,("Get_Pwnam: empty username!\n"));
+ return NULL;
+ }
+
+ fstrcpy(user2, user);
+
+ DEBUG(5,("Finding user %s\n", user));
+
+ return Get_Pwnam_internals(mem_ctx, user, user2);
+}
+
+/* The functions below have been taken from password.c and slightly modified */
+/****************************************************************************
+ Apply a function to upper/lower case combinations
+ of a string and return true if one of them returns true.
+ Try all combinations with N uppercase letters.
+ offset is the first char to try and change (start with 0)
+ it assumes the string starts lowercased
+****************************************************************************/
+
+static struct passwd *uname_string_combinations2(char *s, TALLOC_CTX *mem_ctx,
+ int offset,
+ struct passwd *(*fn)(TALLOC_CTX *mem_ctx, const char *),
+ int N)
+{
+ ssize_t len = (ssize_t)strlen(s);
+ int i;
+ struct passwd *ret;
+
+ if (N <= 0 || offset >= len)
+ return(fn(mem_ctx, s));
+
+ for (i=offset;i<(len-(N-1));i++) {
+ char c = s[i];
+ if (!islower_m((int)c))
+ continue;
+ s[i] = toupper_m(c);
+ ret = uname_string_combinations2(s, mem_ctx, i+1, fn, N-1);
+ if(ret)
+ return(ret);
+ s[i] = c;
+ }
+ return(NULL);
+}
+
+/****************************************************************************
+ Apply a function to upper/lower case combinations
+ of a string and return true if one of them returns true.
+ Try all combinations with up to N uppercase letters.
+ offset is the first char to try and change (start with 0)
+ it assumes the string starts lowercased
+****************************************************************************/
+
+static struct passwd * uname_string_combinations(char *s, TALLOC_CTX *mem_ctx,
+ struct passwd * (*fn)(TALLOC_CTX *mem_ctx, const char *),
+ int N)
+{
+ int n;
+ struct passwd *ret;
+
+ for (n=1;n<=N;n++) {
+ ret = uname_string_combinations2(s,mem_ctx,0,fn,n);
+ if(ret)
+ return(ret);
+ }
+ return(NULL);
+}
+
diff --git a/source3/lib/util.c b/source3/lib/util.c
new file mode 100644
index 0000000..51dc50d
--- /dev/null
+++ b/source3/lib/util.c
@@ -0,0 +1,2013 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2001-2007
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @brief Small functions that don't fit anywhere else
+ * @file util.c
+ */
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "lib/util/memcache.h"
+#include "util_tdb.h"
+#include "ctdbd_conn.h"
+#include "../lib/util/util_pw.h"
+#include "messages.h"
+#include "lib/messaging/messages_dgm.h"
+#include "libcli/security/security.h"
+#include "serverid.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/util_process.h"
+#include "lib/dbwrap/dbwrap_ctdb.h"
+#include "lib/gencache.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_SYS_PRCTL_H
+#include <sys/prctl.h>
+#endif
+
+/* Max allowable allococation - 256mb - 0x10000000 */
+#define MAX_ALLOC_SIZE (1024*1024*256)
+
+static enum protocol_types Protocol = PROTOCOL_COREPLUS;
+
+enum protocol_types get_Protocol(void)
+{
+ return Protocol;
+}
+
+void set_Protocol(enum protocol_types p)
+{
+ Protocol = p;
+}
+
+static enum remote_arch_types ra_type = RA_UNKNOWN;
+
+void gfree_all( void )
+{
+ gfree_loadparm();
+ gfree_charcnv();
+ gfree_interfaces();
+ gfree_debugsyms();
+ gfree_memcache();
+
+}
+
+/*******************************************************************
+ Check if a file exists - call vfs_file_exist for samba files.
+********************************************************************/
+
+bool file_exist_stat(const char *fname,SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times)
+{
+ SMB_STRUCT_STAT st;
+ if (!sbuf)
+ sbuf = &st;
+
+ if (sys_stat(fname, sbuf, fake_dir_create_times) != 0)
+ return(False);
+
+ return((S_ISREG(sbuf->st_ex_mode)) || (S_ISFIFO(sbuf->st_ex_mode)));
+}
+
+/*******************************************************************
+ Check if a unix domain socket exists - call vfs_file_exist for samba files.
+********************************************************************/
+
+bool socket_exist(const char *fname)
+{
+ SMB_STRUCT_STAT st;
+ if (sys_stat(fname, &st, false) != 0)
+ return(False);
+
+ return S_ISSOCK(st.st_ex_mode);
+}
+
+/*******************************************************************
+ Returns the size in bytes of the named given the stat struct.
+********************************************************************/
+
+uint64_t get_file_size_stat(const SMB_STRUCT_STAT *sbuf)
+{
+ return sbuf->st_ex_size;
+}
+
+/****************************************************************************
+ Check two stats have identical dev and ino fields.
+****************************************************************************/
+
+bool check_same_dev_ino(const SMB_STRUCT_STAT *sbuf1,
+ const SMB_STRUCT_STAT *sbuf2)
+{
+ return ((sbuf1->st_ex_dev == sbuf2->st_ex_dev) &&
+ (sbuf1->st_ex_ino == sbuf2->st_ex_ino));
+}
+
+/****************************************************************************
+ Check if a stat struct is identical for use.
+****************************************************************************/
+
+bool check_same_stat(const SMB_STRUCT_STAT *sbuf1,
+ const SMB_STRUCT_STAT *sbuf2)
+{
+ return ((sbuf1->st_ex_uid == sbuf2->st_ex_uid) &&
+ (sbuf1->st_ex_gid == sbuf2->st_ex_gid) &&
+ check_same_dev_ino(sbuf1, sbuf2));
+}
+
+/*******************************************************************
+ Show a smb message structure.
+********************************************************************/
+
+void show_msg(const char *buf)
+{
+ int i;
+ int bcc=0;
+
+ if (!DEBUGLVL(5))
+ return;
+
+ DEBUG(5,("size=%d\nsmb_com=0x%x\nsmb_rcls=%d\nsmb_reh=%d\nsmb_err=%d\nsmb_flg=%d\nsmb_flg2=%d\n",
+ smb_len(buf),
+ (int)CVAL(buf,smb_com),
+ (int)CVAL(buf,smb_rcls),
+ (int)CVAL(buf,smb_reh),
+ (int)SVAL(buf,smb_err),
+ (int)CVAL(buf,smb_flg),
+ (int)SVAL(buf,smb_flg2)));
+ DEBUGADD(5,("smb_tid=%d\nsmb_pid=%d\nsmb_uid=%d\nsmb_mid=%d\n",
+ (int)SVAL(buf,smb_tid),
+ (int)SVAL(buf,smb_pid),
+ (int)SVAL(buf,smb_uid),
+ (int)SVAL(buf,smb_mid)));
+ DEBUGADD(5,("smt_wct=%d\n",(int)CVAL(buf,smb_wct)));
+
+ for (i=0;i<(int)CVAL(buf,smb_wct);i++)
+ DEBUGADD(5,("smb_vwv[%2d]=%5d (0x%X)\n",i,
+ SVAL(buf,smb_vwv+2*i),SVAL(buf,smb_vwv+2*i)));
+
+ bcc = (int)SVAL(buf,smb_vwv+2*(CVAL(buf,smb_wct)));
+
+ DEBUGADD(5,("smb_bcc=%d\n",bcc));
+
+ if (DEBUGLEVEL < 10)
+ return;
+
+ if (DEBUGLEVEL < 50)
+ bcc = MIN(bcc, 512);
+
+ dump_data(10, (const uint8_t *)smb_buf_const(buf), bcc);
+}
+
+/*******************************************************************
+ Setup only the byte count for a smb message.
+********************************************************************/
+
+int set_message_bcc(char *buf,int num_bytes)
+{
+ int num_words = CVAL(buf,smb_wct);
+ SSVAL(buf,smb_vwv + num_words*SIZEOFWORD,num_bytes);
+ _smb_setlen(buf,smb_size + num_words*2 + num_bytes - 4);
+ return (smb_size + num_words*2 + num_bytes);
+}
+
+/*******************************************************************
+ Add a data blob to the end of a smb_buf, adjusting bcc and smb_len.
+ Return the bytes added
+********************************************************************/
+
+ssize_t message_push_blob(uint8_t **outbuf, DATA_BLOB blob)
+{
+ size_t newlen = smb_len(*outbuf) + 4 + blob.length;
+ uint8_t *tmp;
+
+ if (!(tmp = talloc_realloc(NULL, *outbuf, uint8_t, newlen))) {
+ DEBUG(0, ("talloc failed\n"));
+ return -1;
+ }
+ *outbuf = tmp;
+
+ memcpy(tmp + smb_len(tmp) + 4, blob.data, blob.length);
+ set_message_bcc((char *)tmp, smb_buflen(tmp) + blob.length);
+ return blob.length;
+}
+
+/*******************************************************************
+ Reduce a file name, removing .. elements.
+********************************************************************/
+
+static char *dos_clean_name(TALLOC_CTX *ctx, const char *s)
+{
+ char *p = NULL;
+ char *str = NULL;
+
+ DEBUG(3,("dos_clean_name [%s]\n",s));
+
+ /* remove any double slashes */
+ str = talloc_all_string_sub(ctx, s, "\\\\", "\\");
+ if (!str) {
+ return NULL;
+ }
+
+ /* Remove leading .\\ characters */
+ if(strncmp(str, ".\\", 2) == 0) {
+ trim_string(str, ".\\", NULL);
+ if(*str == 0) {
+ str = talloc_strdup(ctx, ".\\");
+ if (!str) {
+ return NULL;
+ }
+ }
+ }
+
+ while ((p = strstr_m(str,"\\..\\")) != NULL) {
+ char *s1;
+
+ *p = 0;
+ s1 = p+3;
+
+ if ((p=strrchr_m(str,'\\')) != NULL) {
+ *p = 0;
+ } else {
+ *str = 0;
+ }
+ str = talloc_asprintf(ctx,
+ "%s%s",
+ str,
+ s1);
+ if (!str) {
+ return NULL;
+ }
+ }
+
+ trim_string(str,NULL,"\\..");
+ return talloc_all_string_sub(ctx, str, "\\.\\", "\\");
+}
+
+/*******************************************************************
+ Reduce a file name, removing .. elements.
+********************************************************************/
+
+char *unix_clean_name(TALLOC_CTX *ctx, const char *s)
+{
+ char *p = NULL;
+ char *str = NULL;
+
+ DEBUG(3,("unix_clean_name [%s]\n",s));
+
+ /* remove any double slashes */
+ str = talloc_all_string_sub(ctx, s, "//","/");
+ if (!str) {
+ return NULL;
+ }
+
+ /* Remove leading ./ characters */
+ if(strncmp(str, "./", 2) == 0) {
+ trim_string(str, "./", NULL);
+ if(*str == 0) {
+ str = talloc_strdup(ctx, "./");
+ if (!str) {
+ return NULL;
+ }
+ }
+ }
+
+ while ((p = strstr_m(str,"/../")) != NULL) {
+ char *s1;
+
+ *p = 0;
+ s1 = p+3;
+
+ if ((p=strrchr_m(str,'/')) != NULL) {
+ *p = 0;
+ } else {
+ *str = 0;
+ }
+ str = talloc_asprintf(ctx,
+ "%s%s",
+ str,
+ s1);
+ if (!str) {
+ return NULL;
+ }
+ }
+
+ trim_string(str,NULL,"/..");
+ return talloc_all_string_sub(ctx, str, "/./", "/");
+}
+
+char *clean_name(TALLOC_CTX *ctx, const char *s)
+{
+ char *str = dos_clean_name(ctx, s);
+ if (!str) {
+ return NULL;
+ }
+ return unix_clean_name(ctx, str);
+}
+
+/*******************************************************************
+ Write data into an fd at a given offset. Ignore seek errors.
+********************************************************************/
+
+ssize_t write_data_at_offset(int fd, const char *buffer, size_t N, off_t pos)
+{
+ size_t total=0;
+ ssize_t ret;
+
+ if (pos == (off_t)-1) {
+ return write_data(fd, buffer, N);
+ }
+#if defined(HAVE_PWRITE) || defined(HAVE_PRWITE64)
+ while (total < N) {
+ ret = sys_pwrite(fd,buffer + total,N - total, pos);
+ if (ret == -1 && errno == ESPIPE) {
+ return write_data(fd, buffer + total,N - total);
+ }
+ if (ret == -1) {
+ DEBUG(0,("write_data_at_offset: write failure. Error = %s\n", strerror(errno) ));
+ return -1;
+ }
+ if (ret == 0) {
+ return total;
+ }
+ total += ret;
+ pos += ret;
+ }
+ return (ssize_t)total;
+#else
+ /* Use lseek and write_data. */
+ if (lseek(fd, pos, SEEK_SET) == -1) {
+ if (errno != ESPIPE) {
+ return -1;
+ }
+ }
+ return write_data(fd, buffer, N);
+#endif
+}
+
+static int reinit_after_fork_pipe[2] = { -1, -1 };
+
+NTSTATUS init_before_fork(void)
+{
+ int ret;
+
+ ret = pipe(reinit_after_fork_pipe);
+ if (ret == -1) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ DEBUG(0, ("Error creating child_pipe: %s\n",
+ nt_errstr(status)));
+
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * @brief Get a fd to watch for our parent process to exit
+ *
+ * Samba parent processes open a pipe that naturally closes when the
+ * parent exits. Child processes can watch the read end of the pipe
+ * for readability: Readability with 0 bytes to read means the parent
+ * has exited and the child process might also want to exit.
+ */
+
+int parent_watch_fd(void)
+{
+ return reinit_after_fork_pipe[0];
+}
+
+/**
+ * Detect died parent by detecting EOF on the pipe
+ */
+static void reinit_after_fork_pipe_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ char c;
+
+ if (sys_read(reinit_after_fork_pipe[0], &c, 1) != 1) {
+ /*
+ * we have reached EOF on stdin, which means the
+ * parent has exited. Shutdown the server
+ */
+ TALLOC_FREE(fde);
+ (void)kill(getpid(), SIGTERM);
+ }
+}
+
+
+NTSTATUS reinit_after_fork(struct messaging_context *msg_ctx,
+ struct tevent_context *ev_ctx,
+ bool parent_longlived)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+
+ /*
+ * The main process thread should never
+ * allow per_thread_cwd_enable() to be
+ * called.
+ */
+ per_thread_cwd_disable();
+
+ if (reinit_after_fork_pipe[1] != -1) {
+ close(reinit_after_fork_pipe[1]);
+ reinit_after_fork_pipe[1] = -1;
+ }
+
+ /* tdb needs special fork handling */
+ if (tdb_reopen_all(parent_longlived ? 1 : 0) != 0) {
+ DEBUG(0,("tdb_reopen_all failed.\n"));
+ status = NT_STATUS_OPEN_FAILED;
+ goto done;
+ }
+
+ if (ev_ctx != NULL) {
+ /*
+ * The parent can have different private data for the callbacks,
+ * which are gone in the child. Reset the callbacks to be safe.
+ */
+ tevent_set_trace_callback(ev_ctx, NULL, NULL);
+ tevent_set_trace_fd_callback(ev_ctx, NULL, NULL);
+ tevent_set_trace_signal_callback(ev_ctx, NULL, NULL);
+ tevent_set_trace_timer_callback(ev_ctx, NULL, NULL);
+ tevent_set_trace_immediate_callback(ev_ctx, NULL, NULL);
+ tevent_set_trace_queue_callback(ev_ctx, NULL, NULL);
+ if (tevent_re_initialise(ev_ctx) != 0) {
+ smb_panic(__location__ ": Failed to re-initialise event context");
+ }
+ }
+
+ if (reinit_after_fork_pipe[0] != -1) {
+ struct tevent_fd *fde;
+
+ fde = tevent_add_fd(ev_ctx, ev_ctx /* TALLOC_CTX */,
+ reinit_after_fork_pipe[0], TEVENT_FD_READ,
+ reinit_after_fork_pipe_handler, NULL);
+ if (fde == NULL) {
+ smb_panic(__location__ ": Failed to add reinit_after_fork pipe event");
+ }
+ }
+
+ if (msg_ctx) {
+ /*
+ * For clustering, we need to re-init our ctdbd connection after the
+ * fork
+ */
+ status = messaging_reinit(msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("messaging_reinit() failed: %s\n",
+ nt_errstr(status)));
+ }
+
+ if (lp_clustering()) {
+ ret = ctdb_async_ctx_reinit(
+ NULL, messaging_tevent_context(msg_ctx));
+ if (ret != 0) {
+ DBG_ERR("db_ctdb_async_ctx_reinit failed: %s\n",
+ strerror(errno));
+ return map_nt_error_from_unix(ret);
+ }
+ }
+ }
+
+ done:
+ return status;
+}
+
+/****************************************************************************
+ (Hopefully) efficient array append.
+****************************************************************************/
+
+void add_to_large_array(TALLOC_CTX *mem_ctx, size_t element_size,
+ void *element, void *_array, uint32_t *num_elements,
+ ssize_t *array_size)
+{
+ void **array = (void **)_array;
+
+ if (*array_size < 0) {
+ return;
+ }
+
+ if (*array == NULL) {
+ if (*array_size == 0) {
+ *array_size = 128;
+ }
+
+ if (*array_size >= MAX_ALLOC_SIZE/element_size) {
+ goto error;
+ }
+
+ *array = TALLOC(mem_ctx, element_size * (*array_size));
+ if (*array == NULL) {
+ goto error;
+ }
+ }
+
+ if (*num_elements == *array_size) {
+ *array_size *= 2;
+
+ if (*array_size >= MAX_ALLOC_SIZE/element_size) {
+ goto error;
+ }
+
+ *array = TALLOC_REALLOC(mem_ctx, *array,
+ element_size * (*array_size));
+
+ if (*array == NULL) {
+ goto error;
+ }
+ }
+
+ memcpy((char *)(*array) + element_size*(*num_elements),
+ element, element_size);
+ *num_elements += 1;
+
+ return;
+
+ error:
+ *num_elements = 0;
+ *array_size = -1;
+}
+
+/****************************************************************************
+ Get my own domain name, or "" if we have none.
+****************************************************************************/
+
+char *get_mydnsdomname(TALLOC_CTX *ctx)
+{
+ const char *domname;
+ char *p;
+
+ domname = get_mydnsfullname();
+ if (!domname) {
+ return NULL;
+ }
+
+ p = strchr_m(domname, '.');
+ if (p) {
+ p++;
+ return talloc_strdup(ctx, p);
+ } else {
+ return talloc_strdup(ctx, "");
+ }
+}
+
+bool process_exists(const struct server_id pid)
+{
+ return serverid_exists(&pid);
+}
+
+/*******************************************************************
+ Convert a uid into a user name.
+********************************************************************/
+
+const char *uidtoname(uid_t uid)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *name = NULL;
+ struct passwd *pass = NULL;
+
+ pass = getpwuid_alloc(ctx,uid);
+ if (pass) {
+ name = talloc_strdup(ctx,pass->pw_name);
+ TALLOC_FREE(pass);
+ } else {
+ name = talloc_asprintf(ctx,
+ "%ld",
+ (long int)uid);
+ }
+ return name;
+}
+
+/*******************************************************************
+ Convert a gid into a group name.
+********************************************************************/
+
+char *gidtoname(gid_t gid)
+{
+ struct group *grp;
+
+ grp = getgrgid(gid);
+ if (grp) {
+ return talloc_strdup(talloc_tos(), grp->gr_name);
+ }
+ else {
+ return talloc_asprintf(talloc_tos(),
+ "%d",
+ (int)gid);
+ }
+}
+
+/*******************************************************************
+ Convert a user name into a uid.
+********************************************************************/
+
+uid_t nametouid(const char *name)
+{
+ struct passwd *pass;
+ char *p;
+ uid_t u;
+
+ pass = Get_Pwnam_alloc(talloc_tos(), name);
+ if (pass) {
+ u = pass->pw_uid;
+ TALLOC_FREE(pass);
+ return u;
+ }
+
+ u = (uid_t)strtol(name, &p, 0);
+ if ((p != name) && (*p == '\0'))
+ return u;
+
+ return (uid_t)-1;
+}
+
+/*******************************************************************
+ Convert a name to a gid_t if possible. Return -1 if not a group.
+********************************************************************/
+
+gid_t nametogid(const char *name)
+{
+ struct group *grp;
+ char *p;
+ gid_t g;
+
+ g = (gid_t)strtol(name, &p, 0);
+ if ((p != name) && (*p == '\0'))
+ return g;
+
+ grp = getgrnam(name);
+ if (grp)
+ return(grp->gr_gid);
+ return (gid_t)-1;
+}
+
+/*******************************************************************
+ Something really nasty happened - panic !
+********************************************************************/
+
+static void call_panic_action(const char *why, bool as_root)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *cmd;
+ int result;
+
+ cmd = lp_panic_action(talloc_tos(), lp_sub);
+ if (cmd == NULL || cmd[0] == '\0') {
+ return;
+ }
+
+ DBG_ERR("Calling panic action [%s]\n", cmd);
+
+#if defined(HAVE_PRCTL) && defined(PR_SET_PTRACER)
+ /*
+ * Make sure all children can attach a debugger.
+ */
+ prctl(PR_SET_PTRACER, getpid(), 0, 0, 0);
+#endif
+
+ if (as_root) {
+ become_root();
+ }
+
+ result = system(cmd);
+
+ if (as_root) {
+ unbecome_root();
+ }
+
+ if (result == -1)
+ DBG_ERR("fork failed in panic action: %s\n",
+ strerror(errno));
+ else
+ DBG_ERR("action returned status %d\n",
+ WEXITSTATUS(result));
+}
+
+void smb_panic_s3(const char *why)
+{
+ call_panic_action(why, false);
+ dump_core();
+}
+
+void log_panic_action(const char *msg)
+{
+ DBG_ERR("%s", msg);
+ call_panic_action(msg, true);
+}
+
+/*******************************************************************
+ A readdir wrapper which just returns the file name.
+ ********************************************************************/
+
+const char *readdirname(DIR *p)
+{
+ struct dirent *ptr;
+ char *dname;
+
+ if (!p)
+ return(NULL);
+
+ ptr = (struct dirent *)readdir(p);
+ if (!ptr)
+ return(NULL);
+
+ dname = ptr->d_name;
+
+ return talloc_strdup(talloc_tos(), dname);
+}
+
+/*******************************************************************
+ Utility function used to decide if the last component
+ of a path matches a (possibly wildcarded) entry in a namelist.
+********************************************************************/
+
+bool is_in_path(const char *name, name_compare_entry *namelist, bool case_sensitive)
+{
+ const char *last_component;
+
+ /* if we have no list it's obviously not in the path */
+ if ((namelist == NULL) || (namelist[0].name == NULL)) {
+ return False;
+ }
+
+ /* Do not reject path components if namelist is set to '.*' */
+ if (ISDOT(name) || ISDOTDOT(name)) {
+ return false;
+ }
+
+ DEBUG(8, ("is_in_path: %s\n", name));
+
+ /* Get the last component of the unix name. */
+ last_component = strrchr_m(name, '/');
+ if (!last_component) {
+ last_component = name;
+ } else {
+ last_component++; /* Go past '/' */
+ }
+
+ for(; namelist->name != NULL; namelist++) {
+ if(namelist->is_wild) {
+ if (mask_match(last_component, namelist->name, case_sensitive)) {
+ DEBUG(8,("is_in_path: mask match succeeded\n"));
+ return True;
+ }
+ } else {
+ if((case_sensitive && (strcmp(last_component, namelist->name) == 0))||
+ (!case_sensitive && (strcasecmp_m(last_component, namelist->name) == 0))) {
+ DEBUG(8,("is_in_path: match succeeded\n"));
+ return True;
+ }
+ }
+ }
+ DEBUG(8,("is_in_path: match not found\n"));
+ return False;
+}
+
+/*******************************************************************
+ Strip a '/' separated list into an array of
+ name_compare_enties structures suitable for
+ passing to is_in_path(). We do this for
+ speed so we can pre-parse all the names in the list
+ and don't do it for each call to is_in_path().
+ We also check if the entry contains a wildcard to
+ remove a potentially expensive call to mask_match
+ if possible.
+********************************************************************/
+
+void set_namearray(name_compare_entry **ppname_array, const char *namelist_in)
+{
+ char *name_end;
+ char *namelist;
+ char *namelist_end;
+ char *nameptr;
+ int num_entries = 0;
+ int i;
+
+ (*ppname_array) = NULL;
+
+ if((namelist_in == NULL ) || ((namelist_in != NULL) && (*namelist_in == '\0')))
+ return;
+
+ namelist = talloc_strdup(talloc_tos(), namelist_in);
+ if (namelist == NULL) {
+ DEBUG(0,("set_namearray: talloc fail\n"));
+ return;
+ }
+ nameptr = namelist;
+
+ namelist_end = &namelist[strlen(namelist)];
+
+ /* We need to make two passes over the string. The
+ first to count the number of elements, the second
+ to split it.
+ */
+
+ while(nameptr <= namelist_end) {
+ if ( *nameptr == '/' ) {
+ /* cope with multiple (useless) /s) */
+ nameptr++;
+ continue;
+ }
+ /* anything left? */
+ if ( *nameptr == '\0' )
+ break;
+
+ /* find the next '/' or consume remaining */
+ name_end = strchr_m(nameptr, '/');
+ if (name_end == NULL) {
+ /* Point nameptr at the terminating '\0' */
+ nameptr += strlen(nameptr);
+ } else {
+ /* next segment please */
+ nameptr = name_end + 1;
+ }
+ num_entries++;
+ }
+
+ if(num_entries == 0) {
+ talloc_free(namelist);
+ return;
+ }
+
+ if(( (*ppname_array) = SMB_MALLOC_ARRAY(name_compare_entry, num_entries + 1)) == NULL) {
+ DEBUG(0,("set_namearray: malloc fail\n"));
+ talloc_free(namelist);
+ return;
+ }
+
+ /* Now copy out the names */
+ nameptr = namelist;
+ i = 0;
+ while(nameptr <= namelist_end) {
+ if ( *nameptr == '/' ) {
+ /* cope with multiple (useless) /s) */
+ nameptr++;
+ continue;
+ }
+ /* anything left? */
+ if ( *nameptr == '\0' )
+ break;
+
+ /* find the next '/' or consume remaining */
+ name_end = strchr_m(nameptr, '/');
+ if (name_end != NULL) {
+ *name_end = '\0';
+ }
+
+ (*ppname_array)[i].is_wild = ms_has_wild(nameptr);
+ if(((*ppname_array)[i].name = SMB_STRDUP(nameptr)) == NULL) {
+ DEBUG(0,("set_namearray: malloc fail (1)\n"));
+ talloc_free(namelist);
+ return;
+ }
+
+ if (name_end == NULL) {
+ /* Point nameptr at the terminating '\0' */
+ nameptr += strlen(nameptr);
+ } else {
+ /* next segment please */
+ nameptr = name_end + 1;
+ }
+ i++;
+ }
+
+ (*ppname_array)[i].name = NULL;
+
+ talloc_free(namelist);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Simple routine to query existing file locks. Cruft in NFS and 64->32 bit mapping
+ is dealt with in posix.c
+ Returns True if we have information regarding this lock region (and returns
+ F_UNLCK in *ptype if the region is unlocked). False if the call failed.
+****************************************************************************/
+
+bool fcntl_getlock(int fd, int op, off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid)
+{
+ struct flock lock;
+ int ret;
+
+ DEBUG(8,("fcntl_getlock fd=%d op=%d offset=%.0f count=%.0f type=%d\n",
+ fd,op,(double)*poffset,(double)*pcount,*ptype));
+
+ lock.l_type = *ptype;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = *poffset;
+ lock.l_len = *pcount;
+ lock.l_pid = 0;
+
+ ret = sys_fcntl_ptr(fd,op,&lock);
+
+ if (ret == -1) {
+ int saved_errno = errno;
+ DEBUG(3,("fcntl_getlock: lock request failed at offset %.0f count %.0f type %d (%s)\n",
+ (double)*poffset,(double)*pcount,*ptype,strerror(errno)));
+ errno = saved_errno;
+ return False;
+ }
+
+ *ptype = lock.l_type;
+ *poffset = lock.l_start;
+ *pcount = lock.l_len;
+ *ppid = lock.l_pid;
+
+ DEBUG(3,("fcntl_getlock: fd %d is returned info %d pid %u\n",
+ fd, (int)lock.l_type, (unsigned int)lock.l_pid));
+ return True;
+}
+
+#if defined(HAVE_OFD_LOCKS)
+int map_process_lock_to_ofd_lock(int op)
+{
+ switch (op) {
+ case F_GETLK:
+ case F_OFD_GETLK:
+ op = F_OFD_GETLK;
+ break;
+ case F_SETLK:
+ case F_OFD_SETLK:
+ op = F_OFD_SETLK;
+ break;
+ case F_SETLKW:
+ case F_OFD_SETLKW:
+ op = F_OFD_SETLKW;
+ break;
+ default:
+ return -1;
+ }
+ return op;
+}
+#else /* HAVE_OFD_LOCKS */
+int map_process_lock_to_ofd_lock(int op)
+{
+ return op;
+}
+#endif /* HAVE_OFD_LOCKS */
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/*******************************************************************
+ Is the name specified one of my netbios names.
+ Returns true if it is equal, false otherwise.
+********************************************************************/
+
+static bool nb_name_equal(const char *s1, const char *s2)
+{
+ int cmp = strncasecmp_m(s1, s2, MAX_NETBIOSNAME_LEN-1);
+ return (cmp == 0);
+}
+
+bool is_myname(const char *s)
+{
+ const char **aliases = NULL;
+ bool ok = false;
+
+ ok = nb_name_equal(lp_netbios_name(), s);
+ if (ok) {
+ goto done;
+ }
+
+ aliases = lp_netbios_aliases();
+ if (aliases == NULL) {
+ goto done;
+ }
+
+ while (*aliases != NULL) {
+ ok = nb_name_equal(*aliases, s);
+ if (ok) {
+ goto done;
+ }
+ aliases += 1;
+ }
+
+done:
+ DBG_DEBUG("is_myname(\"%s\") returns %d\n", s, (int)ok);
+ return ok;
+}
+
+/*******************************************************************
+ we distinguish between 2K and XP by the "Native Lan Manager" string
+ WinXP => "Windows 2002 5.1"
+ WinXP 64bit => "Windows XP 5.2"
+ Win2k => "Windows 2000 5.0"
+ NT4 => "Windows NT 4.0"
+ Win9x => "Windows 4.0"
+ Windows 2003 doesn't set the native lan manager string but
+ they do set the domain to "Windows 2003 5.2" (probably a bug).
+********************************************************************/
+
+void ra_lanman_string( const char *native_lanman )
+{
+ if ( strcmp( native_lanman, "Windows 2002 5.1" ) == 0 )
+ set_remote_arch( RA_WINXP );
+ else if ( strcmp( native_lanman, "Windows XP 5.2" ) == 0 )
+ set_remote_arch( RA_WINXP64 );
+ else if ( strcmp( native_lanman, "Windows Server 2003 5.2" ) == 0 )
+ set_remote_arch( RA_WIN2K3 );
+}
+
+static const char *remote_arch_strings[] = {
+ [RA_UNKNOWN] = "UNKNOWN",
+ [RA_WFWG] = "WfWg",
+ [RA_OS2] = "OS2",
+ [RA_WIN95] = "Win95",
+ [RA_WINNT] = "WinNT",
+ [RA_WIN2K] = "Win2K",
+ [RA_WINXP] = "WinXP",
+ [RA_WIN2K3] = "Win2K3",
+ [RA_VISTA] = "Vista",
+ [RA_SAMBA] = "Samba",
+ [RA_CIFSFS] = "CIFSFS",
+ [RA_WINXP64] = "WinXP64",
+ [RA_OSX] = "OSX",
+};
+
+const char *get_remote_arch_str(void)
+{
+ if (ra_type >= ARRAY_SIZE(remote_arch_strings)) {
+ /*
+ * set_remote_arch() already checks this so ra_type
+ * should be in the allowed range, but anyway, let's
+ * do another bound check here.
+ */
+ DBG_ERR("Remote arch info out of sync [%d] missing\n", ra_type);
+ ra_type = RA_UNKNOWN;
+ }
+ return remote_arch_strings[ra_type];
+}
+
+enum remote_arch_types get_remote_arch_from_str(const char *remote_arch_string)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(remote_arch_strings); i++) {
+ if (strcmp(remote_arch_string, remote_arch_strings[i]) == 0) {
+ return i;
+ }
+ }
+ return RA_UNKNOWN;
+}
+
+/*******************************************************************
+ Set the horrid remote_arch string based on an enum.
+********************************************************************/
+
+void set_remote_arch(enum remote_arch_types type)
+{
+ if (ra_type >= ARRAY_SIZE(remote_arch_strings)) {
+ /*
+ * This protects against someone adding values to enum
+ * remote_arch_types without updating
+ * remote_arch_strings array.
+ */
+ DBG_ERR("Remote arch info out of sync [%d] missing\n", ra_type);
+ ra_type = RA_UNKNOWN;
+ return;
+ }
+
+ ra_type = type;
+ DEBUG(10,("set_remote_arch: Client arch is \'%s\'\n",
+ get_remote_arch_str()));
+}
+
+/*******************************************************************
+ Get the remote_arch type.
+********************************************************************/
+
+enum remote_arch_types get_remote_arch(void)
+{
+ return ra_type;
+}
+
+#define RA_CACHE_TTL 7*24*3600
+
+static bool remote_arch_cache_key(const struct GUID *client_guid,
+ fstring key)
+{
+ struct GUID_txt_buf guid_buf;
+ const char *guid_string = NULL;
+
+ guid_string = GUID_buf_string(client_guid, &guid_buf);
+ if (guid_string == NULL) {
+ return false;
+ }
+
+ fstr_sprintf(key, "RA/%s", guid_string);
+ return true;
+}
+
+struct ra_parser_state {
+ bool found;
+ enum remote_arch_types ra;
+};
+
+static void ra_parser(const struct gencache_timeout *t,
+ DATA_BLOB blob,
+ void *priv_data)
+{
+ struct ra_parser_state *state = (struct ra_parser_state *)priv_data;
+ const char *ra_str = NULL;
+
+ if (gencache_timeout_expired(t)) {
+ return;
+ }
+
+ if ((blob.length == 0) || (blob.data[blob.length-1] != '\0')) {
+ DBG_ERR("Remote arch cache key not a string\n");
+ return;
+ }
+
+ ra_str = (const char *)blob.data;
+ DBG_INFO("Got remote arch [%s] from cache\n", ra_str);
+
+ state->ra = get_remote_arch_from_str(ra_str);
+ state->found = true;
+ return;
+}
+
+static bool remote_arch_cache_get(const struct GUID *client_guid)
+{
+ bool ok;
+ fstring ra_key;
+ struct ra_parser_state state = (struct ra_parser_state) {
+ .found = false,
+ .ra = RA_UNKNOWN,
+ };
+
+ ok = remote_arch_cache_key(client_guid, ra_key);
+ if (!ok) {
+ return false;
+ }
+
+ ok = gencache_parse(ra_key, ra_parser, &state);
+ if (!ok || !state.found) {
+ return true;
+ }
+
+ if (state.ra == RA_UNKNOWN) {
+ return true;
+ }
+
+ set_remote_arch(state.ra);
+ return true;
+}
+
+static bool remote_arch_cache_set(const struct GUID *client_guid)
+{
+ bool ok;
+ fstring ra_key;
+ const char *ra_str = NULL;
+
+ if (get_remote_arch() == RA_UNKNOWN) {
+ return true;
+ }
+
+ ok = remote_arch_cache_key(client_guid, ra_key);
+ if (!ok) {
+ return false;
+ }
+
+ ra_str = get_remote_arch_str();
+ if (ra_str == NULL) {
+ return false;
+ }
+
+ ok = gencache_set(ra_key, ra_str, time(NULL) + RA_CACHE_TTL);
+ if (!ok) {
+ return false;
+ }
+
+ return true;
+}
+
+bool remote_arch_cache_update(const struct GUID *client_guid)
+{
+ bool ok;
+
+ if (get_remote_arch() == RA_UNKNOWN) {
+
+ become_root();
+ ok = remote_arch_cache_get(client_guid);
+ unbecome_root();
+
+ return ok;
+ }
+
+ become_root();
+ ok = remote_arch_cache_set(client_guid);
+ unbecome_root();
+
+ return ok;
+}
+
+bool remote_arch_cache_delete(const struct GUID *client_guid)
+{
+ bool ok;
+ fstring ra_key;
+
+ ok = remote_arch_cache_key(client_guid, ra_key);
+ if (!ok) {
+ return false;
+ }
+
+ become_root();
+ ok = gencache_del(ra_key);
+ unbecome_root();
+
+ if (!ok) {
+ return false;
+ }
+
+ return true;
+}
+
+
+/*****************************************************************************
+ Provide a checksum on a string
+
+ Input: s - the null-terminated character string for which the checksum
+ will be calculated.
+
+ Output: The checksum value calculated for s.
+*****************************************************************************/
+
+int str_checksum(const char *s)
+{
+ TDB_DATA key;
+ if (s == NULL)
+ return 0;
+
+ key = (TDB_DATA) { .dptr = discard_const_p(uint8_t, s),
+ .dsize = strlen(s) };
+
+ return tdb_jenkins_hash(&key);
+}
+
+/*****************************************************************
+ Zero a memory area then free it. Used to catch bugs faster.
+*****************************************************************/
+
+void zero_free(void *p, size_t size)
+{
+ memset(p, 0, size);
+ SAFE_FREE(p);
+}
+
+/*****************************************************************
+ Set our open file limit to a requested max and return the limit.
+*****************************************************************/
+
+int set_maxfiles(int requested_max)
+{
+#if (defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE))
+ struct rlimit rlp;
+ int saved_current_limit;
+
+ if(getrlimit(RLIMIT_NOFILE, &rlp)) {
+ DEBUG(0,("set_maxfiles: getrlimit (1) for RLIMIT_NOFILE failed with error %s\n",
+ strerror(errno) ));
+ /* just guess... */
+ return requested_max;
+ }
+
+ /*
+ * Set the fd limit to be real_max_open_files + MAX_OPEN_FUDGEFACTOR to
+ * account for the extra fd we need
+ * as well as the log files and standard
+ * handles etc. Save the limit we want to set in case
+ * we are running on an OS that doesn't support this limit (AIX)
+ * which always returns RLIM_INFINITY for rlp.rlim_max.
+ */
+
+ /* Try raising the hard (max) limit to the requested amount. */
+
+#if defined(RLIM_INFINITY)
+ if (rlp.rlim_max != RLIM_INFINITY) {
+ int orig_max = rlp.rlim_max;
+
+ if ( rlp.rlim_max < requested_max )
+ rlp.rlim_max = requested_max;
+
+ /* This failing is not an error - many systems (Linux) don't
+ support our default request of 10,000 open files. JRA. */
+
+ if(setrlimit(RLIMIT_NOFILE, &rlp)) {
+ DEBUG(3,("set_maxfiles: setrlimit for RLIMIT_NOFILE for %d max files failed with error %s\n",
+ (int)rlp.rlim_max, strerror(errno) ));
+
+ /* Set failed - restore original value from get. */
+ rlp.rlim_max = orig_max;
+ }
+ }
+#endif
+
+ /* Now try setting the soft (current) limit. */
+
+ saved_current_limit = rlp.rlim_cur = MIN(requested_max,rlp.rlim_max);
+
+ if(setrlimit(RLIMIT_NOFILE, &rlp)) {
+ DEBUG(0,("set_maxfiles: setrlimit for RLIMIT_NOFILE for %d files failed with error %s\n",
+ (int)rlp.rlim_cur, strerror(errno) ));
+ /* just guess... */
+ return saved_current_limit;
+ }
+
+ if(getrlimit(RLIMIT_NOFILE, &rlp)) {
+ DEBUG(0,("set_maxfiles: getrlimit (2) for RLIMIT_NOFILE failed with error %s\n",
+ strerror(errno) ));
+ /* just guess... */
+ return saved_current_limit;
+ }
+
+#if defined(RLIM_INFINITY)
+ if(rlp.rlim_cur == RLIM_INFINITY)
+ return saved_current_limit;
+#endif
+
+ if((int)rlp.rlim_cur > saved_current_limit)
+ return saved_current_limit;
+
+ return rlp.rlim_cur;
+#else /* !defined(HAVE_GETRLIMIT) || !defined(RLIMIT_NOFILE) */
+ /*
+ * No way to know - just guess...
+ */
+ return requested_max;
+#endif
+}
+
+/*****************************************************************
+ malloc that aborts with smb_panic on fail or zero size.
+ *****************************************************************/
+
+void *smb_xmalloc_array(size_t size, unsigned int count)
+{
+ void *p;
+ if (size == 0) {
+ smb_panic("smb_xmalloc_array: called with zero size");
+ }
+ if (count >= MAX_ALLOC_SIZE/size) {
+ smb_panic("smb_xmalloc_array: alloc size too large");
+ }
+ if ((p = SMB_MALLOC(size*count)) == NULL) {
+ DEBUG(0, ("smb_xmalloc_array failed to allocate %lu * %lu bytes\n",
+ (unsigned long)size, (unsigned long)count));
+ smb_panic("smb_xmalloc_array: malloc failed");
+ }
+ return p;
+}
+
+/*****************************************************************
+ Get local hostname and cache result.
+*****************************************************************/
+
+char *myhostname(void)
+{
+ static char *ret;
+ if (ret == NULL) {
+ ret = get_myname(NULL);
+ }
+ return ret;
+}
+
+/*****************************************************************
+ Get local hostname and cache result.
+*****************************************************************/
+
+char *myhostname_upper(void)
+{
+ static char *ret;
+ if (ret == NULL) {
+ char *name = get_myname(NULL);
+ if (name == NULL) {
+ return NULL;
+ }
+ ret = strupper_talloc(NULL, name);
+ talloc_free(name);
+ }
+ return ret;
+}
+
+/*******************************************************************
+ Given a filename - get its directory name
+********************************************************************/
+
+bool parent_dirname(TALLOC_CTX *mem_ctx, const char *dir, char **parent,
+ const char **name)
+{
+ char *p;
+ ptrdiff_t len;
+
+ p = strrchr_m(dir, '/'); /* Find final '/', if any */
+
+ if (p == NULL) {
+ if (!(*parent = talloc_strdup(mem_ctx, "."))) {
+ return False;
+ }
+ if (name) {
+ *name = dir;
+ }
+ return True;
+ }
+
+ len = p-dir;
+
+ *parent = talloc_strndup(mem_ctx, dir, len);
+ if (*parent == NULL) {
+ return False;
+ }
+
+ if (name) {
+ *name = p+1;
+ }
+ return True;
+}
+
+/*******************************************************************
+ Determine if a pattern contains any Microsoft wildcard characters.
+*******************************************************************/
+
+bool ms_has_wild(const char *s)
+{
+ const char *found = strpbrk(s, "*?<>\"");
+ return (found != NULL);
+}
+
+bool ms_has_wild_w(const smb_ucs2_t *s)
+{
+ smb_ucs2_t c;
+ if (!s) return False;
+ while ((c = *s++)) {
+ switch (c) {
+ case UCS2_CHAR('*'):
+ case UCS2_CHAR('?'):
+ case UCS2_CHAR('<'):
+ case UCS2_CHAR('>'):
+ case UCS2_CHAR('"'):
+ return True;
+ }
+ }
+ return False;
+}
+
+/*******************************************************************
+ A wrapper that handles case sensitivity and the special handling
+ of the ".." name.
+*******************************************************************/
+
+bool mask_match(const char *string, const char *pattern, bool is_case_sensitive)
+{
+ if (ISDOTDOT(string))
+ string = ".";
+ if (ISDOT(pattern))
+ return False;
+
+ return ms_fnmatch_protocol(pattern, string, Protocol, is_case_sensitive) == 0;
+}
+
+/*******************************************************************
+ A wrapper that handles a list of patterns and calls mask_match()
+ on each. Returns True if any of the patterns match.
+*******************************************************************/
+
+bool mask_match_list(const char *string, char **list, int listLen, bool is_case_sensitive)
+{
+ while (listLen-- > 0) {
+ if (mask_match(string, *list++, is_case_sensitive))
+ return True;
+ }
+ return False;
+}
+
+/**********************************************************************
+ Converts a name to a fully qualified domain name.
+ Returns true if lookup succeeded, false if not (then fqdn is set to name)
+ Uses getaddrinfo() with AI_CANONNAME flag to obtain the official
+ canonical name of the host. getaddrinfo() may use a variety of sources
+ including /etc/hosts to obtain the domainname. It expects aliases in
+ /etc/hosts to NOT be the FQDN. The FQDN should come first.
+************************************************************************/
+
+bool name_to_fqdn(fstring fqdn, const char *name)
+{
+ char *full = NULL;
+ struct addrinfo hints;
+ struct addrinfo *result;
+ int s;
+
+ /* Configure hints to obtain canonical name */
+
+ memset(&hints, 0, sizeof(struct addrinfo));
+ hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
+ hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
+ hints.ai_flags = AI_CANONNAME; /* Get host's FQDN */
+ hints.ai_protocol = 0; /* Any protocol */
+
+ s = getaddrinfo(name, NULL, &hints, &result);
+ if (s != 0) {
+ DBG_WARNING("getaddrinfo lookup for %s failed: %s\n",
+ name,
+ gai_strerror(s));
+ fstrcpy(fqdn, name);
+ return false;
+ }
+ full = result->ai_canonname;
+
+ /* Find out if the FQDN is returned as an alias
+ * to cope with /etc/hosts files where the first
+ * name is not the FQDN but the short name.
+ * getaddrinfo provides no easy way of handling aliases
+ * in /etc/hosts. Users should make sure the FQDN
+ * comes first in /etc/hosts. */
+ if (full && (! strchr_m(full, '.'))) {
+ DEBUG(1, ("WARNING: your /etc/hosts file may be broken!\n"));
+ DEBUGADD(1, (" Full qualified domain names (FQDNs) should not be specified\n"));
+ DEBUGADD(1, (" as an alias in /etc/hosts. FQDN should be the first name\n"));
+ DEBUGADD(1, (" prior to any aliases.\n"));
+ }
+ if (full && (strcasecmp_m(full, "localhost.localdomain") == 0)) {
+ DEBUG(1, ("WARNING: your /etc/hosts file may be broken!\n"));
+ DEBUGADD(1, (" Specifying the machine hostname for address 127.0.0.1 may lead\n"));
+ DEBUGADD(1, (" to Kerberos authentication problems as localhost.localdomain\n"));
+ DEBUGADD(1, (" may end up being used instead of the real machine FQDN.\n"));
+ }
+
+ DEBUG(10,("name_to_fqdn: lookup for %s -> %s.\n", name, full));
+ fstrcpy(fqdn, full);
+ freeaddrinfo(result); /* No longer needed */
+ return true;
+}
+
+struct server_id interpret_pid(const char *pid_string)
+{
+ return server_id_from_string(get_my_vnn(), pid_string);
+}
+
+/****************************************************************
+ Check if an offset into a buffer is safe.
+ If this returns True it's safe to indirect into the byte at
+ pointer ptr+off.
+****************************************************************/
+
+bool is_offset_safe(const char *buf_base, size_t buf_len, char *ptr, size_t off)
+{
+ const char *end_base = buf_base + buf_len;
+ char *end_ptr = ptr + off;
+
+ if (!buf_base || !ptr) {
+ return False;
+ }
+
+ if (end_base < buf_base || end_ptr < ptr) {
+ return False; /* wrap. */
+ }
+
+ if (end_ptr < end_base) {
+ return True;
+ }
+ return False;
+}
+
+/****************************************************************
+ Return a safe pointer into a string within a buffer, or NULL.
+****************************************************************/
+
+char *get_safe_str_ptr(const char *buf_base, size_t buf_len, char *ptr, size_t off)
+{
+ if (!is_offset_safe(buf_base, buf_len, ptr, off)) {
+ return NULL;
+ }
+ /* Check if a valid string exists at this offset. */
+ if (skip_string(buf_base,buf_len, ptr + off) == NULL) {
+ return NULL;
+ }
+ return ptr + off;
+}
+
+
+/****************************************************************
+ Split DOM\user into DOM and user. Do not mix with winbind variants of that
+ call (they take care of winbind separator and other winbind specific settings).
+****************************************************************/
+
+bool split_domain_user(TALLOC_CTX *mem_ctx,
+ const char *full_name,
+ char **domain,
+ char **user)
+{
+ const char *p = NULL;
+
+ p = strchr_m(full_name, '\\');
+
+ if (p != NULL) {
+ *domain = talloc_strndup(mem_ctx, full_name,
+ PTR_DIFF(p, full_name));
+ if (*domain == NULL) {
+ return false;
+ }
+ *user = talloc_strdup(mem_ctx, p+1);
+ if (*user == NULL) {
+ TALLOC_FREE(*domain);
+ return false;
+ }
+ } else {
+ *domain = NULL;
+ *user = talloc_strdup(mem_ctx, full_name);
+ if (*user == NULL) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/****************************************************************
+ strip off leading '\\' from a hostname
+****************************************************************/
+
+const char *strip_hostname(const char *s)
+{
+ if (!s) {
+ return NULL;
+ }
+
+ if (strlen_m(s) < 3) {
+ return s;
+ }
+
+ if (s[0] == '\\') s++;
+ if (s[0] == '\\') s++;
+
+ return s;
+}
+
+bool any_nt_status_not_ok(NTSTATUS err1, NTSTATUS err2, NTSTATUS *result)
+{
+ if (!NT_STATUS_IS_OK(err1)) {
+ *result = err1;
+ return true;
+ }
+ if (!NT_STATUS_IS_OK(err2)) {
+ *result = err2;
+ return true;
+ }
+ return false;
+}
+
+int timeval_to_msec(struct timeval t)
+{
+ return t.tv_sec * 1000 + (t.tv_usec+999) / 1000;
+}
+
+/*******************************************************************
+ Check a given DOS pathname is valid for a share.
+********************************************************************/
+
+char *valid_share_pathname(TALLOC_CTX *ctx, const char *dos_pathname)
+{
+ char *ptr = NULL;
+
+ if (!dos_pathname) {
+ return NULL;
+ }
+
+ ptr = talloc_strdup(ctx, dos_pathname);
+ if (!ptr) {
+ return NULL;
+ }
+ /* Convert any '\' paths to '/' */
+ unix_format(ptr);
+ ptr = unix_clean_name(ctx, ptr);
+ if (!ptr) {
+ return NULL;
+ }
+
+ /* NT is braindead - it wants a C: prefix to a pathname ! So strip it. */
+ if (strlen(ptr) > 2 && ptr[1] == ':' && ptr[0] != '/')
+ ptr += 2;
+
+ /* Only absolute paths allowed. */
+ if (*ptr != '/')
+ return NULL;
+
+ return ptr;
+}
+
+/*******************************************************************
+ Return True if the filename is one of the special executable types.
+********************************************************************/
+
+bool is_executable(const char *fname)
+{
+ if ((fname = strrchr_m(fname,'.'))) {
+ if (strequal(fname,".com") ||
+ strequal(fname,".dll") ||
+ strequal(fname,".exe") ||
+ strequal(fname,".sym")) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/****************************************************************************
+ Open a file with a share mode - old openX method - map into NTCreate.
+****************************************************************************/
+
+bool map_open_params_to_ntcreate(const char *smb_base_fname,
+ int deny_mode, int open_func,
+ uint32_t *paccess_mask,
+ uint32_t *pshare_mode,
+ uint32_t *pcreate_disposition,
+ uint32_t *pcreate_options,
+ uint32_t *pprivate_flags)
+{
+ uint32_t access_mask;
+ uint32_t share_mode;
+ uint32_t create_disposition;
+ uint32_t create_options = FILE_NON_DIRECTORY_FILE;
+ uint32_t private_flags = 0;
+
+ DEBUG(10,("map_open_params_to_ntcreate: fname = %s, deny_mode = 0x%x, "
+ "open_func = 0x%x\n",
+ smb_base_fname, (unsigned int)deny_mode,
+ (unsigned int)open_func ));
+
+ /* Create the NT compatible access_mask. */
+ switch (GET_OPENX_MODE(deny_mode)) {
+ case DOS_OPEN_EXEC: /* Implies read-only - used to be FILE_READ_DATA */
+ case DOS_OPEN_RDONLY:
+ access_mask = FILE_GENERIC_READ;
+ break;
+ case DOS_OPEN_WRONLY:
+ access_mask = FILE_GENERIC_WRITE;
+ break;
+ case DOS_OPEN_RDWR:
+ case DOS_OPEN_FCB:
+ access_mask = FILE_GENERIC_READ|FILE_GENERIC_WRITE;
+ break;
+ default:
+ DEBUG(10,("map_open_params_to_ntcreate: bad open mode = 0x%x\n",
+ (unsigned int)GET_OPENX_MODE(deny_mode)));
+ return False;
+ }
+
+ /* Create the NT compatible create_disposition. */
+ switch (open_func) {
+ case OPENX_FILE_EXISTS_FAIL|OPENX_FILE_CREATE_IF_NOT_EXIST:
+ create_disposition = FILE_CREATE;
+ break;
+
+ case OPENX_FILE_EXISTS_OPEN:
+ create_disposition = FILE_OPEN;
+ break;
+
+ case OPENX_FILE_EXISTS_OPEN|OPENX_FILE_CREATE_IF_NOT_EXIST:
+ create_disposition = FILE_OPEN_IF;
+ break;
+
+ case OPENX_FILE_EXISTS_TRUNCATE:
+ create_disposition = FILE_OVERWRITE;
+ break;
+
+ case OPENX_FILE_EXISTS_TRUNCATE|OPENX_FILE_CREATE_IF_NOT_EXIST:
+ create_disposition = FILE_OVERWRITE_IF;
+ break;
+
+ default:
+ /* From samba4 - to be confirmed. */
+ if (GET_OPENX_MODE(deny_mode) == DOS_OPEN_EXEC) {
+ create_disposition = FILE_CREATE;
+ break;
+ }
+ DEBUG(10,("map_open_params_to_ntcreate: bad "
+ "open_func 0x%x\n", (unsigned int)open_func));
+ return False;
+ }
+
+ /* Create the NT compatible share modes. */
+ switch (GET_DENY_MODE(deny_mode)) {
+ case DENY_ALL:
+ share_mode = FILE_SHARE_NONE;
+ break;
+
+ case DENY_WRITE:
+ share_mode = FILE_SHARE_READ;
+ break;
+
+ case DENY_READ:
+ share_mode = FILE_SHARE_WRITE;
+ break;
+
+ case DENY_NONE:
+ share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ break;
+
+ case DENY_DOS:
+ private_flags |= NTCREATEX_FLAG_DENY_DOS;
+ if (is_executable(smb_base_fname)) {
+ share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ } else {
+ if (GET_OPENX_MODE(deny_mode) == DOS_OPEN_RDONLY) {
+ share_mode = FILE_SHARE_READ;
+ } else {
+ share_mode = FILE_SHARE_NONE;
+ }
+ }
+ break;
+
+ case DENY_FCB:
+ private_flags |= NTCREATEX_FLAG_DENY_FCB;
+ share_mode = FILE_SHARE_NONE;
+ break;
+
+ default:
+ DEBUG(10,("map_open_params_to_ntcreate: bad deny_mode 0x%x\n",
+ (unsigned int)GET_DENY_MODE(deny_mode) ));
+ return False;
+ }
+
+ DEBUG(10,("map_open_params_to_ntcreate: file %s, access_mask = 0x%x, "
+ "share_mode = 0x%x, create_disposition = 0x%x, "
+ "create_options = 0x%x private_flags = 0x%x\n",
+ smb_base_fname,
+ (unsigned int)access_mask,
+ (unsigned int)share_mode,
+ (unsigned int)create_disposition,
+ (unsigned int)create_options,
+ (unsigned int)private_flags));
+
+ if (paccess_mask) {
+ *paccess_mask = access_mask;
+ }
+ if (pshare_mode) {
+ *pshare_mode = share_mode;
+ }
+ if (pcreate_disposition) {
+ *pcreate_disposition = create_disposition;
+ }
+ if (pcreate_options) {
+ *pcreate_options = create_options;
+ }
+ if (pprivate_flags) {
+ *pprivate_flags = private_flags;
+ }
+
+ return True;
+
+}
+
+/*************************************************************************
+ Return a talloced copy of a struct security_unix_token. NULL on fail.
+*************************************************************************/
+
+struct security_unix_token *copy_unix_token(TALLOC_CTX *ctx, const struct security_unix_token *tok)
+{
+ struct security_unix_token *cpy;
+
+ cpy = talloc(ctx, struct security_unix_token);
+ if (!cpy) {
+ return NULL;
+ }
+
+ cpy->uid = tok->uid;
+ cpy->gid = tok->gid;
+ cpy->ngroups = tok->ngroups;
+ if (tok->ngroups) {
+ /* Make this a talloc child of cpy. */
+ cpy->groups = (gid_t *)talloc_memdup(
+ cpy, tok->groups, tok->ngroups * sizeof(gid_t));
+ if (!cpy->groups) {
+ TALLOC_FREE(cpy);
+ return NULL;
+ }
+ } else {
+ cpy->groups = NULL;
+ }
+ return cpy;
+}
+
+/****************************************************************************
+ Return a root token
+****************************************************************************/
+
+struct security_unix_token *root_unix_token(TALLOC_CTX *mem_ctx)
+{
+ struct security_unix_token *t = NULL;
+
+ t = talloc_zero(mem_ctx, struct security_unix_token);
+ if (t == NULL) {
+ return NULL;
+ }
+
+ /*
+ * This is not needed, but lets make it explicit, not implicit.
+ */
+ *t = (struct security_unix_token) {
+ .uid = 0,
+ .gid = 0,
+ .ngroups = 0,
+ .groups = NULL
+ };
+
+ return t;
+}
+
+char *utok_string(TALLOC_CTX *mem_ctx, const struct security_unix_token *tok)
+{
+ char *str;
+ uint32_t i;
+
+ str = talloc_asprintf(
+ mem_ctx,
+ "uid=%ju, gid=%ju, %"PRIu32" groups:",
+ (uintmax_t)(tok->uid),
+ (uintmax_t)(tok->gid),
+ tok->ngroups);
+
+ for (i=0; i<tok->ngroups; i++) {
+ talloc_asprintf_addbuf(
+ &str, " %ju", (uintmax_t)tok->groups[i]);
+ }
+
+ return str;
+}
+
+/****************************************************************************
+ Check that a file matches a particular file type.
+****************************************************************************/
+
+bool dir_check_ftype(uint32_t mode, uint32_t dirtype)
+{
+ uint32_t mask;
+
+ /* Check the "may have" search bits. */
+ if (((mode & ~dirtype) &
+ (FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_DIRECTORY)) != 0) {
+ return false;
+ }
+
+ /* Check the "must have" bits,
+ which are the may have bits shifted eight */
+ /* If must have bit is set, the file/dir can
+ not be returned in search unless the matching
+ file attribute is set */
+ mask = ((dirtype >> 8) & (FILE_ATTRIBUTE_DIRECTORY|
+ FILE_ATTRIBUTE_ARCHIVE|
+ FILE_ATTRIBUTE_READONLY|
+ FILE_ATTRIBUTE_HIDDEN|
+ FILE_ATTRIBUTE_SYSTEM)); /* & 0x37 */
+ if(mask) {
+ if((mask & (mode & (FILE_ATTRIBUTE_DIRECTORY|
+ FILE_ATTRIBUTE_ARCHIVE|
+ FILE_ATTRIBUTE_READONLY|
+ FILE_ATTRIBUTE_HIDDEN|
+ FILE_ATTRIBUTE_SYSTEM))) == mask) {
+ /* check if matching attribute present */
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/source3/lib/util_builtin.c b/source3/lib/util_builtin.c
new file mode 100644
index 0000000..a456218
--- /dev/null
+++ b/source3/lib/util_builtin.c
@@ -0,0 +1,158 @@
+/*
+ Unix SMB/CIFS implementation.
+ Translate BUILTIN names to SIDs and vice versa
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../libcli/security/security.h"
+
+struct rid_name_map {
+ uint32_t rid;
+ const char *name;
+};
+
+static const struct rid_name_map builtin_aliases[] = {
+ { BUILTIN_RID_ADMINISTRATORS, "Administrators" },
+ { BUILTIN_RID_USERS, "Users" },
+ { BUILTIN_RID_GUESTS, "Guests" },
+ { BUILTIN_RID_POWER_USERS, "Power Users" },
+ { BUILTIN_RID_ACCOUNT_OPERATORS, "Account Operators" },
+ { BUILTIN_RID_SERVER_OPERATORS, "Server Operators" },
+ { BUILTIN_RID_PRINT_OPERATORS, "Print Operators" },
+ { BUILTIN_RID_BACKUP_OPERATORS, "Backup Operators" },
+ { BUILTIN_RID_REPLICATOR, "Replicator" },
+ { BUILTIN_RID_RAS_SERVERS, "RAS Servers" },
+ { BUILTIN_RID_PRE_2K_ACCESS,
+ "Pre-Windows 2000 Compatible Access" },
+ { BUILTIN_RID_REMOTE_DESKTOP_USERS,
+ "Remote Desktop Users" },
+ { BUILTIN_RID_NETWORK_CONF_OPERATORS,
+ "Network Configuration Operators" },
+ { BUILTIN_RID_INCOMING_FOREST_TRUST,
+ "Incoming Forest Trust Builders" },
+ { BUILTIN_RID_PERFMON_USERS,
+ "Performance Monitor Users" },
+ { BUILTIN_RID_PERFLOG_USERS,
+ "Performance Log Users" },
+ { BUILTIN_RID_AUTH_ACCESS,
+ "Windows Authorization Access Group" },
+ { BUILTIN_RID_TS_LICENSE_SERVERS,
+ "Terminal Server License Servers" },
+ { BUILTIN_RID_DISTRIBUTED_COM_USERS,
+ "Distributed COM Users" },
+ { BUILTIN_RID_CRYPTO_OPERATORS,
+ "Cryptographic Operators" },
+ { BUILTIN_RID_EVENT_LOG_READERS,
+ "Event Log Readers" },
+ { BUILTIN_RID_CERT_SERV_DCOM_ACCESS,
+ "Certificate Service DCOM Access" },
+ { 0, NULL}};
+
+/*******************************************************************
+ Look up a rid in the BUILTIN domain
+ ********************************************************************/
+bool lookup_builtin_rid(TALLOC_CTX *mem_ctx, uint32_t rid, const char **name)
+{
+ const struct rid_name_map *aliases = builtin_aliases;
+
+ while (aliases->name != NULL) {
+ if (rid == aliases->rid) {
+ *name = talloc_strdup(mem_ctx, aliases->name);
+ return True;
+ }
+ aliases++;
+ }
+
+ return False;
+}
+
+/*******************************************************************
+ Look up a name in the BUILTIN domain
+ ********************************************************************/
+bool lookup_builtin_name(const char *name, uint32_t *rid)
+{
+ const struct rid_name_map *aliases = builtin_aliases;
+
+ while (aliases->name != NULL) {
+ if (strequal(name, aliases->name)) {
+ *rid = aliases->rid;
+ return True;
+ }
+ aliases++;
+ }
+
+ return False;
+}
+
+/*****************************************************************
+ Return the name of the BUILTIN domain
+*****************************************************************/
+
+const char *builtin_domain_name(void)
+{
+ return "BUILTIN";
+}
+
+/*****************************************************************
+ Check if the SID is the builtin SID (S-1-5-32).
+*****************************************************************/
+
+bool sid_check_is_builtin(const struct dom_sid *sid)
+{
+ return dom_sid_equal(sid, &global_sid_Builtin);
+}
+
+/*****************************************************************
+ Check if the SID is one of the builtin SIDs (S-1-5-32-a).
+*****************************************************************/
+
+bool sid_check_is_in_builtin(const struct dom_sid *sid)
+{
+ struct dom_sid dom_sid;
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, NULL);
+
+ return sid_check_is_builtin(&dom_sid);
+}
+
+/********************************************************************
+ Check if the SID is one of the well-known builtin SIDs (S-1-5-32-x)
+*********************************************************************/
+
+bool sid_check_is_wellknown_builtin(const struct dom_sid *sid)
+{
+ struct dom_sid dom_sid;
+ const struct rid_name_map *aliases = builtin_aliases;
+ uint32_t rid;
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, &rid);
+
+ if (!sid_check_is_builtin(&dom_sid)) {
+ return false;
+ }
+
+ while (aliases->name != NULL) {
+ if (aliases->rid == rid) {
+ return True;
+ }
+ aliases++;
+ }
+
+ return False;
+}
diff --git a/source3/lib/util_cluster.c b/source3/lib/util_cluster.c
new file mode 100644
index 0000000..4287c2c
--- /dev/null
+++ b/source3/lib/util_cluster.c
@@ -0,0 +1,42 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * cluster utility functions
+ * Copyright (C) Volker Lendecke 2013
+ * Copyright (C) Michael Adam 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "ctdbd_conn.h"
+#include "util_cluster.h"
+#include "lib/cluster_support.h"
+#include "lib/util/debug.h"
+#include "source3/param/param_proto.h"
+
+bool cluster_probe_ok(void)
+{
+ if (lp_clustering()) {
+ int ret;
+
+ ret = ctdbd_probe(lp_ctdbd_socket(), lp_ctdb_timeout());
+ if (ret != 0) {
+ DEBUG(0, ("clustering=yes but ctdbd connect failed: "
+ "%s\n", strerror(ret)));
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/source3/lib/util_cluster.h b/source3/lib/util_cluster.h
new file mode 100644
index 0000000..6d05987
--- /dev/null
+++ b/source3/lib/util_cluster.h
@@ -0,0 +1,27 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * cluster utility functions
+ * Copyright (C) Volker Lendecke 2013
+ * Copyright (C) Michael Adam 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef __UTIL_CLUSTER_H__
+#define __UTIL_CLUSTER_H__
+
+bool cluster_probe_ok(void);
+
+#endif /* __UTIL_CLUSTER_H__ */
diff --git a/source3/lib/util_ea.c b/source3/lib/util_ea.c
new file mode 100644
index 0000000..dd48e77
--- /dev/null
+++ b/source3/lib/util_ea.c
@@ -0,0 +1,126 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Extended attribute buffer handling
+ Copyright (C) Jeremy Allison 2005-2013
+ Copyright (C) Tim Prouty 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/util_ea.h"
+
+/****************************************************************************
+ Read one EA list entry from the buffer.
+****************************************************************************/
+
+struct ea_list *read_ea_list_entry(TALLOC_CTX *ctx, const char *pdata, size_t data_size, size_t *pbytes_used)
+{
+ struct ea_list *eal = talloc_zero(ctx, struct ea_list);
+ uint16_t val_len;
+ unsigned int namelen;
+ size_t converted_size;
+
+ if (!eal) {
+ return NULL;
+ }
+
+ if (data_size < 6) {
+ return NULL;
+ }
+
+ eal->ea.flags = CVAL(pdata,0);
+ namelen = CVAL(pdata,1);
+ val_len = SVAL(pdata,2);
+
+ if (4 + namelen + 1 + val_len > data_size) {
+ return NULL;
+ }
+
+ /* Ensure the name is null terminated. */
+ if (pdata[namelen + 4] != '\0') {
+ return NULL;
+ }
+ if (!pull_ascii_talloc(ctx, &eal->ea.name, pdata + 4, &converted_size)) {
+ DEBUG(0,("read_ea_list_entry: pull_ascii_talloc failed: %s\n",
+ strerror(errno)));
+ }
+ if (!eal->ea.name) {
+ return NULL;
+ }
+
+ eal->ea.value = data_blob_talloc(eal, NULL, (size_t)val_len + 1);
+ if (!eal->ea.value.data) {
+ return NULL;
+ }
+
+ memcpy(eal->ea.value.data, pdata + 4 + namelen + 1, val_len);
+
+ /* Ensure we're null terminated just in case we print the value. */
+ eal->ea.value.data[val_len] = '\0';
+ /* But don't count the null. */
+ eal->ea.value.length--;
+
+ if (pbytes_used) {
+ *pbytes_used = 4 + namelen + 1 + val_len;
+ }
+
+ DEBUG(10,("read_ea_list_entry: read ea name %s\n", eal->ea.name));
+ dump_data(10, eal->ea.value.data, eal->ea.value.length);
+
+ return eal;
+}
+
+/****************************************************************************
+ Read a list of EA names and data from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+struct ea_list *read_nttrans_ea_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t offset = 0;
+
+ if (data_size < 4) {
+ return NULL;
+ }
+
+ while (offset + 4 <= data_size) {
+ size_t next_offset = IVAL(pdata,offset);
+ struct ea_list *eal = read_ea_list_entry(ctx, pdata + offset + 4, data_size - offset - 4, NULL);
+
+ if (!eal) {
+ return NULL;
+ }
+
+ DLIST_ADD_END(ea_list_head, eal);
+ if (next_offset == 0) {
+ break;
+ }
+
+ /* Integer wrap protection for the increment. */
+ if (offset + next_offset < offset) {
+ break;
+ }
+
+ offset += next_offset;
+
+ /* Integer wrap protection for while loop. */
+ if (offset + 4 < offset) {
+ break;
+ }
+
+ }
+
+ return ea_list_head;
+}
diff --git a/source3/lib/util_ea.h b/source3/lib/util_ea.h
new file mode 100644
index 0000000..54423ac
--- /dev/null
+++ b/source3/lib/util_ea.h
@@ -0,0 +1,36 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Extended attribute buffer handling
+ Copyright (C) Jeremy Allison 2005-2013
+ Copyright (C) Tim Prouty 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __LIB_UTIL_EA_H__
+#define __LIB_UTIL_EA_H__
+
+/****************************************************************************
+ Read one EA list entry from a buffer.
+****************************************************************************/
+
+struct ea_list *read_ea_list_entry(TALLOC_CTX *ctx, const char *pdata, size_t data_size, size_t *pbytes_used);
+
+/****************************************************************************
+ Read a list of EA names and data from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+struct ea_list *read_nttrans_ea_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size);
+
+#endif /* __LIB_UTIL_EA_H__ */
diff --git a/source3/lib/util_event.c b/source3/lib/util_event.c
new file mode 100644
index 0000000..ea7e7b7
--- /dev/null
+++ b/source3/lib/util_event.c
@@ -0,0 +1,102 @@
+/*
+ Unix SMB/CIFS implementation.
+ Timed event library.
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "util_event.h"
+
+struct idle_event {
+ struct tevent_timer *te;
+ struct timeval interval;
+ char *name;
+ bool (*handler)(const struct timeval *now, void *private_data);
+ void *private_data;
+};
+
+static void smbd_idle_event_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct idle_event *event =
+ talloc_get_type_abort(private_data, struct idle_event);
+
+ TALLOC_FREE(event->te);
+
+ DEBUG(10,("smbd_idle_event_handler: %s %p called\n",
+ event->name, event->te));
+
+ if (!event->handler(&now, event->private_data)) {
+ DEBUG(10,("smbd_idle_event_handler: %s %p stopped\n",
+ event->name, event->te));
+ /* Don't repeat, delete ourselves */
+ TALLOC_FREE(event);
+ return;
+ }
+
+ DEBUG(10,("smbd_idle_event_handler: %s %p rescheduled\n",
+ event->name, event->te));
+
+ event->te = tevent_add_timer(ctx, event,
+ timeval_sum(&now, &event->interval),
+ smbd_idle_event_handler, event);
+
+ /* We can't do much but fail here. */
+ SMB_ASSERT(event->te != NULL);
+}
+
+struct idle_event *event_add_idle(struct tevent_context *event_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct timeval interval,
+ const char *name,
+ bool (*handler)(const struct timeval *now,
+ void *private_data),
+ void *private_data)
+{
+ struct idle_event *result;
+ struct timeval now = timeval_current();
+
+ result = talloc(mem_ctx, struct idle_event);
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ result->interval = interval;
+ result->handler = handler;
+ result->private_data = private_data;
+
+ if (!(result->name = talloc_asprintf(result, "idle_evt(%s)", name))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->te = tevent_add_timer(event_ctx, result,
+ timeval_sum(&now, &interval),
+ smbd_idle_event_handler, result);
+ if (result->te == NULL) {
+ DEBUG(0, ("event_add_timed failed\n"));
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ DEBUG(10,("event_add_idle: %s %p\n", result->name, result->te));
+ return result;
+}
diff --git a/source3/lib/util_file.c b/source3/lib/util_file.c
new file mode 100644
index 0000000..ba96eee
--- /dev/null
+++ b/source3/lib/util_file.c
@@ -0,0 +1,187 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * SMB parameters and setup
+ * Copyright (C) Andrew Tridgell 1992-1998 Modified by Jeremy Allison 1995.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "lib/util_file.h"
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/sys_popen.h"
+#include "lib/async_req/async_sock.h"
+#include "lib/util/tevent_unix.h"
+
+struct file_ploadv_state {
+ struct tevent_context *ev;
+ struct tevent_req *subreq;
+ size_t maxsize;
+ int fd;
+ uint8_t *buf;
+};
+
+static void file_ploadv_cleanup_fn(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static void file_ploadv_readable(struct tevent_req *subreq);
+
+struct tevent_req *file_ploadv_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ char * const argl[], size_t maxsize)
+{
+ struct tevent_req *req = NULL;
+ struct file_ploadv_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct file_ploadv_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->maxsize = maxsize;
+
+ state->fd = sys_popenv(argl);
+ if (state->fd == -1) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_cleanup_fn(req, file_ploadv_cleanup_fn);
+
+ state->subreq = wait_for_read_send(state, state->ev, state->fd, false);
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreq, file_ploadv_readable, req);
+ return req;
+}
+
+static void file_ploadv_cleanup_fn(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct file_ploadv_state *state = tevent_req_data(
+ req, struct file_ploadv_state);
+
+ TALLOC_FREE(state->subreq);
+ if (state->fd != -1) {
+ sys_pclose(state->fd);
+ state->fd = -1;
+ }
+}
+
+static void file_ploadv_readable(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct file_ploadv_state *state = tevent_req_data(
+ req, struct file_ploadv_state);
+ uint8_t buf[1024];
+ uint8_t *tmp;
+ ssize_t nread;
+ size_t bufsize;
+ int err;
+ bool ok;
+
+ ok = wait_for_read_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (!ok) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ nread = sys_read(state->fd, buf, sizeof(buf));
+ if (nread == -1) {
+ tevent_req_error(req, errno);
+ return;
+ }
+ if (nread == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ bufsize = talloc_get_size(state->buf);
+ if (bufsize > 0) {
+ /*
+ * Last round we've added the trailing '\0'. Remove it
+ * for this round.
+ */
+ bufsize -= 1;
+ }
+
+ if (((bufsize + nread) < bufsize) ||
+ ((bufsize + nread + 1) < bufsize)) {
+ /* overflow */
+ tevent_req_error(req, EMSGSIZE);
+ return;
+ }
+
+ if ((state->maxsize != 0) && ((bufsize + nread) > state->maxsize)) {
+ tevent_req_error(req, EMSGSIZE);
+ return;
+ }
+
+ tmp = talloc_realloc(state, state->buf, uint8_t, bufsize + nread + 1);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ state->buf = tmp;
+
+ memcpy(state->buf + bufsize, buf, nread);
+ state->buf[bufsize+nread] = '\0';
+
+ state->subreq = wait_for_read_send(state, state->ev, state->fd, false);
+ if (tevent_req_nomem(state->subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(state->subreq, file_ploadv_readable, req);
+}
+
+int file_ploadv_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf)
+{
+ struct file_ploadv_state *state = tevent_req_data(
+ req, struct file_ploadv_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ *buf = talloc_move(mem_ctx, &state->buf);
+
+ tevent_req_received(req);
+
+ return 0;
+}
+
+
+/**
+ Load a pipe into memory and return an array of pointers to lines in the data
+ must be freed with TALLOC_FREE.
+**/
+
+char **file_lines_ploadv(TALLOC_CTX *mem_ctx,
+ char * const argl[],
+ int *numlines)
+{
+ char *p = NULL;
+ size_t size;
+
+ p = file_ploadv(argl, &size);
+ if (!p) {
+ return NULL;
+ }
+
+ return file_lines_parse(p, size, numlines, mem_ctx);
+}
diff --git a/source3/lib/util_file.h b/source3/lib/util_file.h
new file mode 100644
index 0000000..1aef5a2
--- /dev/null
+++ b/source3/lib/util_file.h
@@ -0,0 +1,35 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * SMB parameters and setup
+ * Copyright (C) Andrew Tridgell 1992-1998 Modified by Jeremy Allison 1995.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_UTIL_FILE_H__
+#define __LIB_UTIL_FILE_H__
+
+#include "replace.h"
+#include <tevent.h>
+
+struct tevent_req *file_ploadv_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ char * const argl[], size_t maxsize);
+int file_ploadv_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **buf);
+char **file_lines_ploadv(TALLOC_CTX *mem_ctx,
+ char * const argl[],
+ int *numlines);
+
+#endif
diff --git a/source3/lib/util_macstreams.c b/source3/lib/util_macstreams.c
new file mode 100644
index 0000000..787ad88
--- /dev/null
+++ b/source3/lib/util_macstreams.c
@@ -0,0 +1,73 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "MacExtensions.h"
+#include "util_macstreams.h"
+
+/* Yes, I have considered multibyte */
+#undef strncasecmp
+
+bool is_afpinfo_stream(const char *sname)
+{
+ int cmp;
+
+ if (sname == NULL) {
+ return false;
+ }
+
+ cmp = strncasecmp(sname,
+ AFPINFO_STREAM_NAME,
+ strlen(AFPINFO_STREAM_NAME));
+ if (cmp == 0) {
+ return true;
+ }
+ return false;
+}
+
+bool is_afpresource_stream(const char *sname)
+{
+ int cmp;
+
+ if (sname == NULL) {
+ return false;
+ }
+
+ cmp = strncasecmp(sname,
+ AFPRESOURCE_STREAM_NAME,
+ strlen(AFPRESOURCE_STREAM_NAME));
+ if (cmp == 0) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Test whether stream is an Apple stream.
+ **/
+bool is_apple_stream(const char *sname)
+{
+ if (is_afpinfo_stream(sname)) {
+ return true;
+ }
+ if (is_afpresource_stream(sname)) {
+ return true;
+ }
+ return false;
+}
diff --git a/source3/lib/util_macstreams.h b/source3/lib/util_macstreams.h
new file mode 100644
index 0000000..ad50abc
--- /dev/null
+++ b/source3/lib/util_macstreams.h
@@ -0,0 +1,27 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _UTIL_MACSTREAMS_H_
+#define _UTIL_MACSTREAMS_H_
+
+bool is_afpinfo_stream(const char *sname);
+bool is_afpresource_stream(const char *sname);
+bool is_apple_stream(const char *sname);
+
+#endif
diff --git a/source3/lib/util_malloc.c b/source3/lib/util_malloc.c
new file mode 100644
index 0000000..09586d5
--- /dev/null
+++ b/source3/lib/util_malloc.c
@@ -0,0 +1,122 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2001-2007
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+#if defined(PARANOID_MALLOC_CHECKER)
+
+/****************************************************************************
+ Internal malloc wrapper. Externally visible.
+****************************************************************************/
+
+void *malloc_(size_t size)
+{
+ if (size == 0) {
+ return NULL;
+ }
+#undef malloc
+ return malloc(size);
+#define malloc(s) __ERROR_DONT_USE_MALLOC_DIRECTLY
+}
+
+/****************************************************************************
+ Internal realloc wrapper. Not externally visible.
+****************************************************************************/
+
+static void *realloc_(void *ptr, size_t size)
+{
+#undef realloc
+ return realloc(ptr, size);
+#define realloc(p,s) __ERROR_DONT_USE_REALLOC_DIRECTLY
+}
+
+#endif /* PARANOID_MALLOC_CHECKER */
+
+/****************************************************************************
+ Expand a pointer to be a particular size.
+ Note that this version of Realloc has an extra parameter that decides
+ whether to free the passed in storage on allocation failure or if the
+ new size is zero.
+
+ This is designed for use in the typical idiom of :
+
+ p = SMB_REALLOC(p, size)
+ if (!p) {
+ return error;
+ }
+
+ and not to have to keep track of the old 'p' contents to free later, nor
+ to worry if the size parameter was zero. In the case where NULL is returned
+ we guarantee that p has been freed.
+
+ If free later semantics are desired, then pass 'free_old_on_error' as False which
+ guarantees that the old contents are not freed on error, even if size == 0. To use
+ this idiom use :
+
+ tmp = SMB_REALLOC_KEEP_OLD_ON_ERROR(p, size);
+ if (!tmp) {
+ SAFE_FREE(p);
+ return error;
+ } else {
+ p = tmp;
+ }
+
+ Changes were instigated by Coverity error checking. JRA.
+****************************************************************************/
+
+void *Realloc(void *p, size_t size, bool free_old_on_error)
+{
+ void *ret=NULL;
+
+ if (size == 0) {
+ if (free_old_on_error) {
+ SAFE_FREE(p);
+ }
+ DEBUG(2,("Realloc asked for 0 bytes\n"));
+ return NULL;
+ }
+
+#if defined(PARANOID_MALLOC_CHECKER)
+ if (!p) {
+ ret = (void *)malloc_(size);
+ } else {
+ ret = (void *)realloc_(p,size);
+ }
+#else
+ if (!p) {
+ ret = (void *)malloc(size);
+ } else {
+ ret = (void *)realloc(p,size);
+ }
+#endif
+
+ if (!ret) {
+ if (free_old_on_error && p) {
+ SAFE_FREE(p);
+ }
+ DEBUG(0,("Memory allocation error: failed to expand to %d bytes\n",(int)size));
+ }
+
+ return(ret);
+}
+
diff --git a/source3/lib/util_matching.c b/source3/lib/util_matching.c
new file mode 100644
index 0000000..4a321f2
--- /dev/null
+++ b/source3/lib/util_matching.c
@@ -0,0 +1,391 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Stefan Metzmacher 2021
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/util_matching.h"
+#include "lib/util/string_wrappers.h"
+
+struct samba_path_matching_entry {
+ const char *name;
+ bool is_wild;
+ regex_t re;
+};
+
+struct samba_path_matching_result {
+ ssize_t replace_start;
+ ssize_t replace_end;
+ bool match;
+};
+
+struct samba_path_matching {
+ bool case_sensitive;
+ NTSTATUS (*matching_fn)(const struct samba_path_matching *pm,
+ const struct samba_path_matching_entry *e,
+ const char *namecomponent,
+ struct samba_path_matching_result *result);
+ size_t num_entries;
+ struct samba_path_matching_entry *entries;
+};
+
+static NTSTATUS samba_path_matching_split(TALLOC_CTX *mem_ctx,
+ const char *namelist_in,
+ struct samba_path_matching **ppm)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *name_end = NULL;
+ char *namelist = NULL;
+ char *namelist_end = NULL;
+ char *nameptr = NULL;
+ struct samba_path_matching *pm = NULL;
+ size_t num_entries = 0;
+ struct samba_path_matching_entry *entries = NULL;
+
+ *ppm = NULL;
+
+ pm = talloc_zero(mem_ctx, struct samba_path_matching);
+ if (pm == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_reparent(mem_ctx, frame, pm);
+
+ namelist = talloc_strdup(frame, namelist_in);
+ if (namelist == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ nameptr = namelist;
+
+ namelist_end = &namelist[strlen(namelist)];
+
+ /*
+ * We need to make two passes over the string. The
+ * first to count the number of elements, the second
+ * to split it.
+ *
+ * The 1st time entries is NULL.
+ * the 2nd time entries is allocated.
+ */
+again:
+ while (nameptr <= namelist_end) {
+ /* anything left? */
+ if (*nameptr == '\0') {
+ break;
+ }
+
+ if (*nameptr == '/') {
+ /* cope with multiple (useless) /s) */
+ nameptr++;
+ continue;
+ }
+
+ /* find the next '/' or consume remaining */
+ name_end = strchr_m(nameptr, '/');
+ if (entries != NULL) {
+ if (name_end != NULL) {
+ *name_end = '\0';
+ }
+ entries[num_entries].name = talloc_strdup(entries,
+ nameptr);
+ if (entries[num_entries].name == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ num_entries++;
+ if (name_end != NULL) {
+ /* next segment please */
+ nameptr = name_end + 1;
+ continue;
+ }
+
+ /* no entries remaining */
+ break;
+ }
+
+ if (num_entries == 0) {
+ /*
+ * No entries in the first round => we're done
+ */
+ goto done;
+ }
+
+ if (entries != NULL) {
+ /*
+ * We finished the 2nd round => we're done
+ */
+ goto done;
+ }
+
+ /*
+ * Now allocate the array and loop again
+ * in order to split the names.
+ */
+ entries = talloc_zero_array(pm,
+ struct samba_path_matching_entry,
+ num_entries);
+ if (entries == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ num_entries = 0;
+ nameptr = namelist;
+ goto again;
+
+done:
+ pm->num_entries = num_entries;
+ pm->entries = entries;
+ *ppm = talloc_move(mem_ctx, &pm);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+};
+
+static NTSTATUS samba_path_create_mswild_fn(const struct samba_path_matching *pm,
+ const struct samba_path_matching_entry *e,
+ const char *namecomponent,
+ struct samba_path_matching_result *result)
+{
+ bool match = false;
+
+ if (e->is_wild) {
+ match = mask_match(namecomponent, e->name, pm->case_sensitive);
+ } else if (pm->case_sensitive) {
+ match = (strcmp(namecomponent, e->name) == 0);
+ } else {
+ match = (strcasecmp_m(namecomponent, e->name) == 0);
+ }
+
+ *result = (struct samba_path_matching_result) {
+ .match = match,
+ .replace_start = -1,
+ .replace_end = -1,
+ };
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_path_matching_mswild_create(TALLOC_CTX *mem_ctx,
+ bool case_sensitive,
+ const char *namelist_in,
+ struct samba_path_matching **ppm)
+{
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samba_path_matching *pm = NULL;
+ size_t i;
+
+ *ppm = NULL;
+
+ status = samba_path_matching_split(mem_ctx, namelist_in, &pm);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ talloc_reparent(mem_ctx, frame, pm);
+
+ for (i = 0; i < pm->num_entries; i++) {
+ struct samba_path_matching_entry *e = &pm->entries[i];
+
+ e->is_wild = ms_has_wild(e->name);
+ }
+
+ pm->case_sensitive = case_sensitive;
+ pm->matching_fn = samba_path_create_mswild_fn;
+ *ppm = talloc_move(mem_ctx, &pm);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+};
+
+static int samba_path_matching_regex_sub1_destructor(struct samba_path_matching *pm)
+{
+ ssize_t i;
+
+ for (i = 0; i < pm->num_entries; i++) {
+ struct samba_path_matching_entry *e = &pm->entries[i];
+
+ regfree(&e->re);
+ }
+
+ pm->num_entries = 0;
+
+ return 0;
+}
+
+static NTSTATUS samba_path_create_regex_sub1_fn(const struct samba_path_matching *pm,
+ const struct samba_path_matching_entry *e,
+ const char *namecomponent,
+ struct samba_path_matching_result *result)
+{
+ if (e->re.re_nsub == 1) {
+ regmatch_t matches[2] = { };
+ int ret;
+
+ ret = regexec(&e->re, namecomponent, 2, matches, 0);
+ if (ret == 0) {
+ *result = (struct samba_path_matching_result) {
+ .match = true,
+ .replace_start = matches[1].rm_so,
+ .replace_end = matches[1].rm_eo,
+ };
+
+ return NT_STATUS_OK;
+ }
+ }
+
+ *result = (struct samba_path_matching_result) {
+ .match = false,
+ .replace_start = -1,
+ .replace_end = -1,
+ };
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS samba_path_matching_regex_sub1_create(TALLOC_CTX *mem_ctx,
+ const char *namelist_in,
+ struct samba_path_matching **ppm)
+{
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samba_path_matching *pm = NULL;
+ ssize_t i;
+
+ *ppm = NULL;
+
+ status = samba_path_matching_split(mem_ctx, namelist_in, &pm);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ talloc_reparent(mem_ctx, frame, pm);
+
+ for (i = 0; i < pm->num_entries; i++) {
+ struct samba_path_matching_entry *e = &pm->entries[i];
+ int ret;
+
+ ret = regcomp(&e->re, e->name, 0);
+ if (ret != 0) {
+ fstring buf = { 0,};
+
+ regerror(ret, &e->re, buf, sizeof(buf));
+
+ DBG_ERR("idx[%zu] regcomp: /%s/ - %d - %s\n",
+ i, e->name, ret, buf);
+
+ status = NT_STATUS_INVALID_PARAMETER;
+ i--;
+ goto cleanup;
+ }
+
+ if (e->re.re_nsub != 1) {
+ DBG_ERR("idx[%zu] regcomp: /%s/ - re_nsub[%zu] != 1\n",
+ i, e->name, e->re.re_nsub);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto cleanup;
+ }
+ }
+
+ talloc_set_destructor(pm, samba_path_matching_regex_sub1_destructor);
+
+ pm->case_sensitive = true;
+ pm->matching_fn = samba_path_create_regex_sub1_fn;
+ *ppm = talloc_move(mem_ctx, &pm);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+
+cleanup:
+ for (; i >= 0; i--) {
+ struct samba_path_matching_entry *e = &pm->entries[i];
+
+ regfree(&e->re);
+ }
+
+ TALLOC_FREE(frame);
+ return status;
+};
+
+NTSTATUS samba_path_matching_check_last_component(struct samba_path_matching *pm,
+ const char *name,
+ ssize_t *p_match_idx,
+ ssize_t *p_replace_start,
+ ssize_t *p_replace_end)
+{
+ struct samba_path_matching_result result = {
+ .match = false,
+ .replace_start = -1,
+ .replace_end = -1,
+ };
+ ssize_t match_idx = -1;
+ NTSTATUS status = NT_STATUS_OK;
+ const char *last_component = NULL;
+ size_t i;
+
+ if (pm->num_entries == 0) {
+ goto finish;
+ }
+
+ /* Get the last component of the unix name. */
+ last_component = strrchr_m(name, '/');
+ if (last_component == NULL) {
+ last_component = name;
+ } else {
+ last_component++; /* Go past '/' */
+ }
+
+ for (i = 0; i < pm->num_entries; i++) {
+ struct samba_path_matching_entry *e = &pm->entries[i];
+
+ status = pm->matching_fn(pm, e, last_component, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = (struct samba_path_matching_result) {
+ .match = false,
+ .replace_start = -1,
+ .replace_end = -1,
+ };
+ goto finish;
+ }
+
+ if (result.match) {
+ match_idx = i;
+ goto finish;
+ }
+ }
+
+finish:
+ *p_match_idx = match_idx;
+ if (p_replace_start != NULL) {
+ size_t last_ofs = 0;
+
+ if (result.replace_start >= 0) {
+ last_ofs = PTR_DIFF(last_component, name);
+ }
+
+ *p_replace_start = last_ofs + result.replace_start;
+ }
+ if (p_replace_end != NULL) {
+ size_t last_ofs = 0;
+
+ if (result.replace_end >= 0) {
+ last_ofs = PTR_DIFF(last_component, name);
+ }
+
+ *p_replace_end = last_ofs + result.replace_end;
+ }
+ return status;
+}
diff --git a/source3/lib/util_matching.h b/source3/lib/util_matching.h
new file mode 100644
index 0000000..5abe40e
--- /dev/null
+++ b/source3/lib/util_matching.h
@@ -0,0 +1,40 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Stefan Metzmacher 2021
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SAMBA_LIB_UTIL_MATCHING_H_
+#define _SAMBA_LIB_UTIL_MATCHING_H_
+
+struct samba_path_matching;
+
+NTSTATUS samba_path_matching_mswild_create(TALLOC_CTX *mem_ctx,
+ bool case_sensitive,
+ const char *namelist_in,
+ struct samba_path_matching **ppm);
+
+NTSTATUS samba_path_matching_regex_sub1_create(TALLOC_CTX *mem_ctx,
+ const char *namelist_in,
+ struct samba_path_matching **ppm);
+
+NTSTATUS samba_path_matching_check_last_component(struct samba_path_matching *pm,
+ const char *name,
+ ssize_t *p_match_idx,
+ ssize_t *p_replace_start,
+ ssize_t *p_replace_end);
+
+#endif /* _SAMBA_LIB_UTIL_MATCHING_H_ */
diff --git a/source3/lib/util_names.c b/source3/lib/util_names.c
new file mode 100644
index 0000000..b62ddb3
--- /dev/null
+++ b/source3/lib/util_names.c
@@ -0,0 +1,86 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2001-2007
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) James Peach 2006
+ Copyright (C) Andrew Bartlett 2010-2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+/******************************************************************
+ get the default domain/netbios name to be used when dealing
+ with our passdb list of accounts
+******************************************************************/
+
+const char *get_global_sam_name(void)
+{
+ if (IS_DC) {
+ return lp_workgroup();
+ }
+ return lp_netbios_name();
+}
+
+
+/******************************************************************
+ Get the default domain/netbios name to be used when
+ testing authentication.
+******************************************************************/
+
+const char *my_sam_name(void)
+{
+ if (lp_server_role() == ROLE_STANDALONE) {
+ return lp_netbios_name();
+ }
+
+ return lp_workgroup();
+}
+
+bool is_allowed_domain(const char *domain_name)
+{
+ const char **ignored_domains = NULL;
+ const char **dom = NULL;
+
+ ignored_domains = lp_parm_string_list(-1,
+ "winbind",
+ "ignore domains",
+ NULL);
+
+ for (dom = ignored_domains; dom != NULL && *dom != NULL; dom++) {
+ if (gen_fnmatch(*dom, domain_name) == 0) {
+ DBG_NOTICE("Ignoring domain '%s'\n", domain_name);
+ return false;
+ }
+ }
+
+ if (lp_allow_trusted_domains()) {
+ return true;
+ }
+
+ if (strequal(lp_workgroup(), domain_name)) {
+ return true;
+ }
+
+ if (is_myname(domain_name)) {
+ return true;
+ }
+
+ DBG_NOTICE("Not trusted domain '%s'\n", domain_name);
+ return false;
+}
diff --git a/source3/lib/util_nscd.c b/source3/lib/util_nscd.c
new file mode 100644
index 0000000..6002f14
--- /dev/null
+++ b/source3/lib/util_nscd.c
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Guenther Deschner 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+#ifdef HAVE_LIBNSCD
+#include <libnscd.h>
+#endif
+
+static void smb_nscd_flush_cache(const char *service)
+{
+#ifdef HAVE_NSCD_FLUSH_CACHE
+ if (nscd_flush_cache(service)) {
+ DEBUG(10,("failed to flush nscd cache for '%s' service: %s. "
+ "Is nscd running?\n",
+ service, strerror(errno)));
+ }
+#endif
+}
+
+void smb_nscd_flush_user_cache(void)
+{
+ smb_nscd_flush_cache("passwd");
+}
+
+void smb_nscd_flush_group_cache(void)
+{
+ smb_nscd_flush_cache("group");
+}
diff --git a/source3/lib/util_nttoken.c b/source3/lib/util_nttoken.c
new file mode 100644
index 0000000..925b79c
--- /dev/null
+++ b/source3/lib/util_nttoken.c
@@ -0,0 +1,103 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Authentication utility functions
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Andrew Bartlett 2001-2023
+ * Copyright (C) Jeremy Allison 2000-2001
+ * Copyright (C) Rafal Szczesniak 2002
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007
+ * Copyright (C) Guenther Deschner 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* function(s) moved from auth/auth_util.c to minimize linker deps */
+
+#include "includes.h"
+#include "../libcli/security/security.h"
+
+/****************************************************************************
+ merge NT tokens
+****************************************************************************/
+
+NTSTATUS merge_with_system_token(TALLOC_CTX *mem_ctx,
+ const struct security_token *token_1,
+ struct security_token **token_out)
+{
+ const struct security_token *token_2 = get_system_token();
+ struct security_token *token = NULL;
+ NTSTATUS status;
+ uint32_t i;
+
+ if (!token_1 || !token_2 || !token_out) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ token = talloc_zero(mem_ctx, struct security_token);
+ NT_STATUS_HAVE_NO_MEMORY(token);
+
+ for (i=0; i < token_1->num_sids; i++) {
+ status = add_sid_to_array_unique(mem_ctx,
+ &token_1->sids[i],
+ &token->sids,
+ &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(token);
+ return status;
+ }
+ }
+
+ for (i=0; i < token_2->num_sids; i++) {
+ status = add_sid_to_array_unique(mem_ctx,
+ &token_2->sids[i],
+ &token->sids,
+ &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(token);
+ return status;
+ }
+ }
+
+ token->privilege_mask |= token_1->privilege_mask;
+ token->privilege_mask |= token_2->privilege_mask;
+
+ token->rights_mask |= token_1->rights_mask;
+ token->rights_mask |= token_2->rights_mask;
+
+ /*
+ * We don't need to merge claims as the system token has no
+ * claims
+ */
+
+ *token_out = token;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Check if this struct security_ace has a SID in common with the token.
+********************************************************************/
+
+bool token_sid_in_ace(const struct security_token *token, const struct security_ace *ace)
+{
+ size_t i;
+
+ for (i = 0; i < token->num_sids; i++) {
+ if (dom_sid_equal(&ace->trustee, &token->sids[i]))
+ return true;
+ }
+
+ return false;
+}
diff --git a/source3/lib/util_path.c b/source3/lib/util_path.c
new file mode 100644
index 0000000..a7d1a8b
--- /dev/null
+++ b/source3/lib/util_path.c
@@ -0,0 +1,350 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba utility functions
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Jeremy Allison 2001-2007
+ * Copyright (C) Simo Sorce 2001
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ * Copyright (C) James Peach 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <talloc.h>
+#include "lib/util/debug.h"
+#include "lib/util/samba_util.h"
+#include "lib/util_path.h"
+
+struct loadparm_substitution;
+struct share_params;
+#include "source3/param/param_proto.h"
+
+/**
+ * @brief Returns an absolute path to a file concatenating the provided
+ * @a rootpath and @a basename
+ *
+ * @param name Filename, relative to @a rootpath
+ *
+ * @retval Pointer to a string containing the full path.
+ **/
+
+static char *xx_path(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *rootpath)
+{
+ char *fname = NULL;
+
+ fname = talloc_strdup(mem_ctx, rootpath);
+ if (!fname) {
+ return NULL;
+ }
+ trim_string(fname,"","/");
+
+ if (!directory_create_or_exist(fname, 0755)) {
+ return NULL;
+ }
+
+ return talloc_asprintf_append(fname, "/%s", name);
+}
+
+/**
+ * @brief Returns an absolute path to a file in the Samba lock directory.
+ *
+ * @param name File to find, relative to LOCKDIR.
+ *
+ * @retval Pointer to a talloc'ed string containing the full path.
+ **/
+
+char *lock_path(TALLOC_CTX *mem_ctx, const char *name)
+{
+ return xx_path(mem_ctx, name, lp_lock_directory());
+}
+
+/**
+ * @brief Returns an absolute path to a file in the Samba state directory.
+ *
+ * @param name File to find, relative to STATEDIR.
+ *
+ * @retval Pointer to a talloc'ed string containing the full path.
+ **/
+
+char *state_path(TALLOC_CTX *mem_ctx, const char *name)
+{
+ return xx_path(mem_ctx, name, lp_state_directory());
+}
+
+/**
+ * @brief Returns an absolute path to a file in the Samba cache directory.
+ *
+ * @param name File to find, relative to CACHEDIR.
+ *
+ * @retval Pointer to a talloc'ed string containing the full path.
+ **/
+
+char *cache_path(TALLOC_CTX *mem_ctx, const char *name)
+{
+ return xx_path(mem_ctx, name, lp_cache_directory());
+}
+
+/**
+ * @brief Removes any invalid path components in an absolute POSIX path.
+ *
+ * @param ctx Talloc context to return string.
+ *
+ * @param abs_path Absolute path string to process.
+ *
+ * @retval Pointer to a talloc'ed string containing the absolute full path.
+ **/
+
+char *canonicalize_absolute_path(TALLOC_CTX *ctx, const char *pathname_in)
+{
+ /*
+ * Note we use +2 here so if pathname_in=="" then we
+ * have space to return "/".
+ */
+ char *pathname = talloc_array(ctx, char, strlen(pathname_in)+2);
+ const char *s = pathname_in;
+ char *p = pathname;
+
+ if (pathname == NULL) {
+ return NULL;
+ }
+
+ /* Always start with a '/'. */
+ *p++ = '/';
+
+ while (*s) {
+ /* Deal with '/' or multiples of '/'. */
+ if (s[0] == '/') {
+ while (s[0] == '/') {
+ /* Eat trailing '/' */
+ s++;
+ }
+ /* Update target with one '/' */
+ if (p[-1] != '/') {
+ *p++ = '/';
+ }
+ continue;
+ }
+ if (p[-1] == '/') {
+ /* Deal with "./" or ".\0" */
+ if (s[0] == '.' &&
+ (s[1] == '/' || s[1] == '\0')) {
+ /* Eat the dot. */
+ s++;
+ while (s[0] == '/') {
+ /* Eat any trailing '/' */
+ s++;
+ }
+ /* Don't write anything to target. */
+ continue;
+ }
+ /* Deal with "../" or "..\0" */
+ if (s[0] == '.' && s[1] == '.' &&
+ (s[2] == '/' || s[2] == '\0')) {
+ /* Eat the dot dot. */
+ s += 2;
+ while (s[0] == '/') {
+ /* Eat any trailing '/' */
+ s++;
+ }
+ /*
+ * As we're on the slash, we go back
+ * one character to point p at the
+ * slash we just saw.
+ */
+ if (p > pathname) {
+ p--;
+ }
+ /*
+ * Now go back to the slash
+ * before the one that p currently points to.
+ */
+ while (p > pathname) {
+ p--;
+ if (p[0] == '/') {
+ break;
+ }
+ }
+ /*
+ * Step forward one to leave the
+ * last written '/' alone.
+ */
+ p++;
+
+ /* Don't write anything to target. */
+ continue;
+ }
+ }
+ /* Non-separator character, just copy. */
+ *p++ = *s++;
+ }
+ if (p[-1] == '/') {
+ /*
+ * We finished on a '/'.
+ * Remove the trailing '/', but not if it's
+ * the sole character in the path.
+ */
+ if (p > pathname + 1) {
+ p--;
+ }
+ }
+ /* Terminate and we're done ! */
+ *p++ = '\0';
+ return pathname;
+}
+
+static bool find_snapshot_token(
+ const char *filename,
+ char sep,
+ const char **_start,
+ const char **_next_component,
+ NTTIME *twrp)
+{
+ const char *start = NULL;
+ const char *end = NULL;
+ struct tm tm = {};
+ time_t t;
+
+ start = strstr_m(filename, "@GMT-");
+
+ if (start == NULL) {
+ return false;
+ }
+
+ if ((start > filename) && (start[-1] != sep)) {
+ /* the GMT-token does not start a path-component */
+ return false;
+ }
+
+ end = strptime(start, GMT_FORMAT, &tm);
+ if (end == NULL) {
+ /* Not a valid timestring. */
+ return false;
+ }
+
+ if ((end[0] != '\0') && (end[0] != sep)) {
+ /*
+ * It is not a complete path component, i.e. the path
+ * component continues after the gmt-token.
+ */
+ return false;
+ }
+
+ tm.tm_isdst = -1;
+ t = timegm(&tm);
+ unix_to_nt_time(twrp, t);
+
+ DBG_DEBUG("Extracted @GMT-Timestamp %s\n",
+ nt_time_string(talloc_tos(), *twrp));
+
+ *_start = start;
+
+ if (end[0] == sep) {
+ end += 1;
+ }
+ *_next_component = end;
+
+ return true;
+}
+
+bool clistr_is_previous_version_path(const char *path)
+{
+ const char *start = NULL;
+ const char *next = NULL;
+ NTTIME twrp;
+ bool ok;
+
+ ok = find_snapshot_token(path, '\\', &start, &next, &twrp);
+ return ok;
+}
+
+static bool extract_snapshot_token_internal(char *fname, NTTIME *twrp, char sep)
+{
+ const char *start = NULL;
+ const char *next = NULL;
+ size_t remaining;
+ bool found;
+
+ found = find_snapshot_token(fname, sep, &start, &next, twrp);
+ if (!found) {
+ return false;
+ }
+
+ remaining = strlen(next);
+ memmove(discard_const_p(char, start), next, remaining+1);
+
+ return true;
+}
+
+bool extract_snapshot_token(char *fname, NTTIME *twrp)
+{
+ return extract_snapshot_token_internal(fname, twrp, '/');
+}
+
+bool clistr_smb2_extract_snapshot_token(char *fname, NTTIME *twrp)
+{
+ return extract_snapshot_token_internal(fname, twrp, '\\');
+}
+
+/*
+ * Take two absolute paths, figure out if "subdir" is a proper
+ * subdirectory of "parent". Return the component relative to the
+ * "parent" without the potential "/". Take care of "parent"
+ * possibly ending in "/".
+ */
+bool subdir_of(const char *parent,
+ size_t parent_len,
+ const char *subdir,
+ const char **_relative)
+{
+ const char *relative = NULL;
+ bool matched;
+
+ SMB_ASSERT(parent[0] == '/');
+ SMB_ASSERT(subdir[0] == '/');
+
+ if (parent_len == 1) {
+ /*
+ * Everything is below "/"
+ */
+ *_relative = subdir+1;
+ return true;
+ }
+
+ if (parent[parent_len-1] == '/') {
+ parent_len -= 1;
+ }
+
+ matched = (strncmp(subdir, parent, parent_len) == 0);
+ if (!matched) {
+ return false;
+ }
+
+ relative = &subdir[parent_len];
+
+ if (relative[0] == '\0') {
+ *_relative = relative; /* nothing left */
+ return true;
+ }
+
+ if (relative[0] == '/') {
+ /* End of parent must match a '/' in subdir. */
+ *_relative = relative+1;
+ return true;
+ }
+
+ return false;
+}
diff --git a/source3/lib/util_path.h b/source3/lib/util_path.h
new file mode 100644
index 0000000..9dcc1dd
--- /dev/null
+++ b/source3/lib/util_path.h
@@ -0,0 +1,55 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba utility functions
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Jeremy Allison 2001-2007
+ * Copyright (C) Simo Sorce 2001
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ * Copyright (C) James Peach 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_UTIL_PATH_H__
+#define __LIB_UTIL_PATH_H__
+
+#include "replace.h"
+#include <talloc.h>
+#include "lib/util/time.h"
+
+/*
+ * Timestamp format used in "previous versions":
+ * This is the windows-level format of the @GMT- token.
+ * It is a fixed format not to be confused with the
+ * format for the POSIX-Level token of the shadow_copy2
+ * VFS module that can be configured via the "shadow:format"
+ * configuration option but defaults to the same format.
+ * See the shadow_copy2 module.
+ */
+#define GMT_NAME_LEN 24 /* length of a @GMT- name */
+#define GMT_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S"
+
+char *lock_path(TALLOC_CTX *mem_ctx, const char *name);
+char *state_path(TALLOC_CTX *mem_ctx, const char *name);
+char *cache_path(TALLOC_CTX *mem_ctx, const char *name);
+char *canonicalize_absolute_path(TALLOC_CTX *ctx, const char *abs_path);
+bool extract_snapshot_token(char *fname, NTTIME *twrp);
+bool clistr_smb2_extract_snapshot_token(char *fname, NTTIME *twrp);
+bool clistr_is_previous_version_path(const char *path);
+bool subdir_of(const char *parent,
+ size_t parent_len,
+ const char *subdir,
+ const char **_relative);
+
+#endif
diff --git a/source3/lib/util_procid.c b/source3/lib/util_procid.c
new file mode 100644
index 0000000..5a4c081
--- /dev/null
+++ b/source3/lib/util_procid.c
@@ -0,0 +1,69 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba utility functions
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Jeremy Allison 2001-2007
+ * Copyright (C) Simo Sorce 2001
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ * Copyright (C) James Peach 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "util_procid.h"
+#include "lib/util/debug.h"
+#include "lib/messaging/messages_dgm.h"
+
+pid_t procid_to_pid(const struct server_id *proc)
+{
+ return proc->pid;
+}
+
+static uint32_t my_vnn = NONCLUSTER_VNN;
+
+void set_my_vnn(uint32_t vnn)
+{
+ DEBUG(10, ("vnn pid %d = %u\n", (int)getpid(), (unsigned int)vnn));
+ my_vnn = vnn;
+}
+
+uint32_t get_my_vnn(void)
+{
+ return my_vnn;
+}
+
+struct server_id pid_to_procid(pid_t pid)
+{
+ uint64_t unique = 0;
+ int ret;
+
+ ret = messaging_dgm_get_unique(pid, &unique);
+ if (ret != 0) {
+ DBG_NOTICE("messaging_dgm_get_unique failed: %s\n",
+ strerror(ret));
+ }
+
+ return (struct server_id) {
+ .pid = pid, .unique_id = unique, .vnn = my_vnn };
+}
+
+bool procid_valid(const struct server_id *pid)
+{
+ return (pid->pid != (uint64_t)-1);
+}
+
+bool procid_is_local(const struct server_id *pid)
+{
+ return pid->vnn == my_vnn;
+}
diff --git a/source3/lib/util_procid.h b/source3/lib/util_procid.h
new file mode 100644
index 0000000..9637363
--- /dev/null
+++ b/source3/lib/util_procid.h
@@ -0,0 +1,37 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba utility functions
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Jeremy Allison 2001-2007
+ * Copyright (C) Simo Sorce 2001
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ * Copyright (C) James Peach 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_UTIL_PROCID_H__
+#define __LIB_UTIL_PROCID_H__
+
+#include "replace.h"
+#include "librpc/gen_ndr/server_id.h"
+
+pid_t procid_to_pid(const struct server_id *proc);
+void set_my_vnn(uint32_t vnn);
+uint32_t get_my_vnn(void);
+struct server_id pid_to_procid(pid_t pid);
+bool procid_valid(const struct server_id *pid);
+bool procid_is_local(const struct server_id *pid);
+
+#endif
diff --git a/source3/lib/util_sd.c b/source3/lib/util_sd.c
new file mode 100644
index 0000000..23f37b7
--- /dev/null
+++ b/source3/lib/util_sd.c
@@ -0,0 +1,626 @@
+/*
+ Unix SMB/CIFS implementation.
+ Security Descriptor (SD) helper functions
+
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jeremy Allison 2000
+ Copyright (C) Jelmer Vernooij 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "util_sd.h"
+#include "librpc/gen_ndr/ndr_lsa.h"
+#include "../libcli/security/security.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "lib/util/string_wrappers.h"
+
+/* These values discovered by inspection */
+
+struct perm_value {
+ const char *perm;
+ uint32_t mask;
+};
+
+static const struct perm_value special_values[] = {
+ { "R", SEC_RIGHTS_FILE_READ },
+ { "W", SEC_RIGHTS_FILE_WRITE },
+ { "X", SEC_RIGHTS_FILE_EXECUTE },
+ { "D", SEC_STD_DELETE },
+ { "P", SEC_STD_WRITE_DAC },
+ { "O", SEC_STD_WRITE_OWNER },
+ { NULL, 0 },
+};
+
+static const struct perm_value standard_values[] = {
+ { "READ", SEC_RIGHTS_DIR_READ|SEC_DIR_TRAVERSE },
+ { "CHANGE", SEC_RIGHTS_DIR_READ|SEC_STD_DELETE|\
+ SEC_DIR_DELETE_CHILD|\
+ SEC_RIGHTS_DIR_WRITE|SEC_DIR_TRAVERSE },
+ { "FULL", SEC_RIGHTS_DIR_ALL },
+ { NULL, 0 },
+};
+
+static const struct {
+ uint16_t mask;
+ const char *str;
+ const char *desc;
+} sec_desc_ctrl_bits[] = {
+ {SEC_DESC_OWNER_DEFAULTED, "OD", "Owner Defaulted"},
+ {SEC_DESC_GROUP_DEFAULTED, "GD", "Group Defaulted"},
+ {SEC_DESC_DACL_PRESENT, "DP", "DACL Present"},
+ {SEC_DESC_DACL_DEFAULTED, "DD", "DACL Defaulted"},
+ {SEC_DESC_SACL_PRESENT, "SP", "SACL Present"},
+ {SEC_DESC_SACL_DEFAULTED, "SD", "SACL Defaulted"},
+ {SEC_DESC_DACL_TRUSTED, "DT", "DACL Trusted"},
+ {SEC_DESC_SERVER_SECURITY, "SS", "Server Security"},
+ {SEC_DESC_DACL_AUTO_INHERIT_REQ, "DR", "DACL Inheritance Required"},
+ {SEC_DESC_SACL_AUTO_INHERIT_REQ, "SR", "SACL Inheritance Required"},
+ {SEC_DESC_DACL_AUTO_INHERITED, "DI", "DACL Auto Inherited"},
+ {SEC_DESC_SACL_AUTO_INHERITED, "SI", "SACL Auto Inherited"},
+ {SEC_DESC_DACL_PROTECTED, "PD", "DACL Protected"},
+ {SEC_DESC_SACL_PROTECTED, "PS", "SACL Protected"},
+ {SEC_DESC_RM_CONTROL_VALID, "RM", "RM Control Valid"},
+ {SEC_DESC_SELF_RELATIVE , "SR", "Self Relative"},
+};
+
+/* Open cli connection and policy handle */
+static NTSTATUS cli_lsa_lookup_sid(struct cli_state *cli,
+ const struct dom_sid *sid,
+ TALLOC_CTX *mem_ctx,
+ enum lsa_SidType *type,
+ char **domain, char **name)
+{
+ struct smbXcli_tcon *orig_tcon = NULL;
+ char *orig_share = NULL;
+ struct rpc_pipe_client *p = NULL;
+ struct policy_handle handle;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ enum lsa_SidType *types;
+ char **domains;
+ char **names;
+
+ if (cli_state_has_tcon(cli)) {
+ cli_state_save_tcon_share(cli, &orig_tcon, &orig_share);
+ }
+
+ status = cli_tree_connect(cli, "IPC$", "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto tcon_fail;
+ }
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &p);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = rpccli_lsa_open_policy(p, talloc_tos(), True,
+ GENERIC_EXECUTE_ACCESS, &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = rpccli_lsa_lookup_sids(p, talloc_tos(), &handle, 1, sid,
+ &domains, &names, &types);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ *type = types[0];
+ *domain = talloc_move(mem_ctx, &domains[0]);
+ *name = talloc_move(mem_ctx, &names[0]);
+
+ status = NT_STATUS_OK;
+ fail:
+ TALLOC_FREE(p);
+ cli_tdis(cli);
+ tcon_fail:
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/* convert a SID to a string, either numeric or username/group */
+void SidToString(struct cli_state *cli, fstring str, const struct dom_sid *sid,
+ bool numeric)
+{
+ char *domain = NULL;
+ char *name = NULL;
+ enum lsa_SidType type;
+ NTSTATUS status;
+
+ sid_to_fstring(str, sid);
+
+ if (numeric || cli == NULL) {
+ return;
+ }
+
+ status = cli_lsa_lookup_sid(cli, sid, talloc_tos(), &type,
+ &domain, &name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ if (*domain) {
+ slprintf(str, sizeof(fstring) - 1, "%s%s%s",
+ domain, lp_winbind_separator(), name);
+ } else {
+ fstrcpy(str, name);
+ }
+}
+
+static NTSTATUS cli_lsa_lookup_name(struct cli_state *cli,
+ const char *name,
+ enum lsa_SidType *type,
+ struct dom_sid *sid)
+{
+ struct smbXcli_tcon *orig_tcon = NULL;
+ char *orig_share = NULL;
+ struct rpc_pipe_client *p = NULL;
+ struct policy_handle handle;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct dom_sid *sids;
+ enum lsa_SidType *types;
+
+ if (cli_state_has_tcon(cli)) {
+ cli_state_save_tcon_share(cli, &orig_tcon, &orig_share);
+ }
+
+ status = cli_tree_connect(cli, "IPC$", "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto tcon_fail;
+ }
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &p);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = rpccli_lsa_open_policy(p, talloc_tos(), True,
+ GENERIC_EXECUTE_ACCESS, &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = rpccli_lsa_lookup_names(p, talloc_tos(), &handle, 1, &name,
+ NULL, 1, &sids, &types);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ *type = types[0];
+ *sid = sids[0];
+
+ status = NT_STATUS_OK;
+ fail:
+ TALLOC_FREE(p);
+ cli_tdis(cli);
+ tcon_fail:
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/* convert a string to a SID, either numeric or username/group */
+bool StringToSid(struct cli_state *cli, struct dom_sid *sid, const char *str)
+{
+ enum lsa_SidType type;
+
+ if (string_to_sid(sid, str)) {
+ return true;
+ }
+
+ if (cli == NULL) {
+ return false;
+ }
+
+ return NT_STATUS_IS_OK(cli_lsa_lookup_name(cli, str, &type, sid));
+}
+
+static void print_ace_flags(FILE *f, uint8_t flags)
+{
+ char *str = talloc_strdup(NULL, "");
+ size_t len;
+
+ if (flags & SEC_ACE_FLAG_OBJECT_INHERIT) {
+ talloc_asprintf_addbuf(&str, "OI|");
+ }
+ if (flags & SEC_ACE_FLAG_CONTAINER_INHERIT) {
+ talloc_asprintf_addbuf(&str, "CI|");
+ }
+ if (flags & SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) {
+ talloc_asprintf_addbuf(&str, "NP|");
+ }
+ if (flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+ talloc_asprintf_addbuf(&str, "IO|");
+ }
+ if (flags & SEC_ACE_FLAG_INHERITED_ACE) {
+ talloc_asprintf_addbuf(&str, "I|");
+ }
+ if (str == NULL) {
+ goto out;
+ }
+
+ /* Ignore define SEC_ACE_FLAG_SUCCESSFUL_ACCESS ( 0x40 )
+ and SEC_ACE_FLAG_FAILED_ACCESS ( 0x80 ) as they're
+ audit ace flags. */
+
+ len = strlen(str);
+ if (len > 0) {
+ fprintf(f, "/%.*s/", (int)len-1, str);
+ } else {
+ fprintf(f, "/0x%x/", flags);
+ }
+ TALLOC_FREE(str);
+ return;
+
+ out:
+ fprintf(f, "/0x%x/", flags);
+}
+
+/* print an ACE on a FILE, using either numeric or ascii representation */
+void print_ace(struct cli_state *cli, FILE *f, struct security_ace *ace,
+ bool numeric)
+{
+ const struct perm_value *v;
+ fstring sidstr;
+ int do_print = 0;
+ uint32_t got_mask;
+
+ SidToString(cli, sidstr, &ace->trustee, numeric);
+
+ fprintf(f, "%s:", sidstr);
+
+ if (numeric) {
+ fprintf(f, "%d/0x%x/0x%08x",
+ ace->type, ace->flags, ace->access_mask);
+ return;
+ }
+
+ /* Ace type */
+
+ if (ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
+ fprintf(f, "ALLOWED");
+ } else if (ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
+ fprintf(f, "DENIED");
+ } else {
+ fprintf(f, "%d", ace->type);
+ }
+
+ print_ace_flags(f, ace->flags);
+
+ /* Standard permissions */
+
+ for (v = standard_values; v->perm; v++) {
+ if (ace->access_mask == v->mask) {
+ fprintf(f, "%s", v->perm);
+ return;
+ }
+ }
+
+ /* Special permissions. Print out a hex value if we have
+ leftover bits in the mask. */
+
+ got_mask = ace->access_mask;
+
+ again:
+ for (v = special_values; v->perm; v++) {
+ if ((ace->access_mask & v->mask) == v->mask) {
+ if (do_print) {
+ fprintf(f, "%s", v->perm);
+ }
+ got_mask &= ~v->mask;
+ }
+ }
+
+ if (!do_print) {
+ if (got_mask != 0) {
+ fprintf(f, "0x%08x", ace->access_mask);
+ } else {
+ do_print = 1;
+ goto again;
+ }
+ }
+}
+
+static bool parse_ace_flags(const char *str, unsigned int *pflags)
+{
+ const char *p = str;
+ *pflags = 0;
+
+ while (*p) {
+ if (strnequal(p, "OI", 2)) {
+ *pflags |= SEC_ACE_FLAG_OBJECT_INHERIT;
+ p += 2;
+ } else if (strnequal(p, "CI", 2)) {
+ *pflags |= SEC_ACE_FLAG_CONTAINER_INHERIT;
+ p += 2;
+ } else if (strnequal(p, "NP", 2)) {
+ *pflags |= SEC_ACE_FLAG_NO_PROPAGATE_INHERIT;
+ p += 2;
+ } else if (strnequal(p, "IO", 2)) {
+ *pflags |= SEC_ACE_FLAG_INHERIT_ONLY;
+ p += 2;
+ } else if (*p == 'I') {
+ *pflags |= SEC_ACE_FLAG_INHERITED_ACE;
+ p += 1;
+ } else if (*p) {
+ return false;
+ }
+
+ switch (*p) {
+ case '|':
+ p++;
+
+ FALL_THROUGH;
+ case '\0':
+ continue;
+ default:
+ return false;
+ }
+ }
+ return true;
+}
+
+/* parse an ACE in the same format as print_ace() */
+bool parse_ace(struct cli_state *cli, struct security_ace *ace,
+ const char *orig_str)
+{
+ char *p;
+ const char *cp;
+ char *tok;
+ unsigned int atype = 0;
+ unsigned int aflags = 0;
+ unsigned int amask = 0;
+ struct dom_sid sid;
+ uint32_t mask;
+ const struct perm_value *v;
+ char *str = SMB_STRDUP(orig_str);
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!str) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ ZERO_STRUCTP(ace);
+ p = strchr_m(str,':');
+ if (!p) {
+ printf("ACE '%s': missing ':'.\n", orig_str);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+ *p = '\0';
+ p++;
+
+ if (!StringToSid(cli, &sid, str)) {
+ printf("ACE '%s': failed to convert '%s' to SID\n",
+ orig_str, str);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ cp = p;
+ if (!next_token_talloc(frame, &cp, &tok, "/")) {
+ printf("ACE '%s': failed to find '/' character.\n",
+ orig_str);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ if (strncmp(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
+ atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
+ } else if (strncmp(tok, "DENIED", strlen("DENIED")) == 0) {
+ atype = SEC_ACE_TYPE_ACCESS_DENIED;
+
+ } else if (strnequal(tok, "0x", 2)) {
+ int result;
+
+ result = sscanf(tok, "%x", &atype);
+ if (result == 0 ||
+ (atype != SEC_ACE_TYPE_ACCESS_ALLOWED &&
+ atype != SEC_ACE_TYPE_ACCESS_DENIED)) {
+ printf("ACE '%s': bad hex value for type at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return false;
+ }
+ } else if(tok[0] >= '0' && tok[0] <= '9') {
+ int result;
+
+ result = sscanf(tok, "%u", &atype);
+ if (result == 0 ||
+ (atype != SEC_ACE_TYPE_ACCESS_ALLOWED &&
+ atype != SEC_ACE_TYPE_ACCESS_DENIED)) {
+ printf("ACE '%s': bad integer value for type at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return false;
+ }
+ } else {
+ printf("ACE '%s': missing 'ALLOWED' or 'DENIED' entry at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ if (!next_token_talloc(frame, &cp, &tok, "/")) {
+ printf("ACE '%s': bad flags entry at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ if (tok[0] < '0' || tok[0] > '9') {
+ if (!parse_ace_flags(tok, &aflags)) {
+ printf("ACE '%s': bad named flags entry at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+ } else if (strnequal(tok, "0x", 2)) {
+ if (!sscanf(tok, "%x", &aflags)) {
+ printf("ACE '%s': bad hex flags entry at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+ } else {
+ if (!sscanf(tok, "%u", &aflags)) {
+ printf("ACE '%s': bad integer flags entry at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+ }
+
+ if (!next_token_talloc(frame, &cp, &tok, "/")) {
+ printf("ACE '%s': missing / at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ if (strncmp(tok, "0x", 2) == 0) {
+ if (sscanf(tok, "%x", &amask) != 1) {
+ printf("ACE '%s': bad hex number at '%s'\n",
+ orig_str, tok);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+ goto done;
+ }
+
+ for (v = standard_values; v->perm; v++) {
+ if (strcmp(tok, v->perm) == 0) {
+ amask = v->mask;
+ goto done;
+ }
+ }
+
+ p = tok;
+
+ while(*p) {
+ bool found = False;
+
+ for (v = special_values; v->perm; v++) {
+ if (v->perm[0] == *p) {
+ amask |= v->mask;
+ found = True;
+ }
+ }
+
+ if (!found) {
+ printf("ACE '%s': bad permission value at '%s'\n",
+ orig_str, p);
+ SAFE_FREE(str);
+ TALLOC_FREE(frame);
+ return False;
+ }
+ p++;
+ }
+
+ if (*p) {
+ TALLOC_FREE(frame);
+ SAFE_FREE(str);
+ return False;
+ }
+
+ done:
+ mask = amask;
+ init_sec_ace(ace, &sid, atype, mask, aflags);
+ TALLOC_FREE(frame);
+ SAFE_FREE(str);
+ return True;
+}
+
+static void print_acl_ctrl(FILE *file, uint16_t ctrl, bool numeric)
+{
+ int i;
+ const char* separator = "";
+
+ fprintf(file, "CONTROL:");
+ if (numeric) {
+ fprintf(file, "0x%x\n", ctrl);
+ return;
+ }
+
+ for (i = ARRAY_SIZE(sec_desc_ctrl_bits) - 1; i >= 0; i--) {
+ if (ctrl & sec_desc_ctrl_bits[i].mask) {
+ fprintf(file, "%s%s",
+ separator, sec_desc_ctrl_bits[i].str);
+ separator = "|";
+ }
+ }
+ fputc('\n', file);
+}
+
+/* print a ascii version of a security descriptor on a FILE handle */
+void sec_desc_print(struct cli_state *cli, FILE *f,
+ struct security_descriptor *sd, bool numeric)
+{
+ fstring sidstr;
+ uint32_t i;
+
+ fprintf(f, "REVISION:%d\n", sd->revision);
+ print_acl_ctrl(f, sd->type, numeric);
+
+ /* Print owner and group sid */
+
+ if (sd->owner_sid) {
+ SidToString(cli, sidstr, sd->owner_sid, numeric);
+ } else {
+ fstrcpy(sidstr, "");
+ }
+
+ fprintf(f, "OWNER:%s\n", sidstr);
+
+ if (sd->group_sid) {
+ SidToString(cli, sidstr, sd->group_sid, numeric);
+ } else {
+ fstrcpy(sidstr, "");
+ }
+
+ fprintf(f, "GROUP:%s\n", sidstr);
+
+ /* Print aces */
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+ struct security_ace *ace = &sd->dacl->aces[i];
+ fprintf(f, "ACL:");
+ print_ace(cli, f, ace, numeric);
+ fprintf(f, "\n");
+ }
+
+}
diff --git a/source3/lib/util_sec.c b/source3/lib/util_sec.c
new file mode 100644
index 0000000..ba6f109
--- /dev/null
+++ b/source3/lib/util_sec.c
@@ -0,0 +1,598 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Jeremy Allison 1998.
+ rewritten for version 2.0.6 by Tridge
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef AUTOCONF_TEST
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "../lib/util/setid.h"
+
+#else
+/* we are running this code in autoconf test mode to see which type of setuid
+ function works */
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_PRIV_H
+#include <sys/priv.h>
+#endif
+#ifdef HAVE_SYS_ID_H
+#include <sys/id.h>
+#endif
+
+#define DEBUG(x, y) printf y
+#define smb_panic(x) exit(1)
+#endif
+
+/* are we running as non-root? This is used by the regression test code,
+ and potentially also for sites that want non-root smbd */
+static uid_t initial_uid;
+static gid_t initial_gid;
+
+/****************************************************************************
+remember what uid we got started as - this allows us to run correctly
+as non-root while catching trapdoor systems
+****************************************************************************/
+
+void sec_init(void)
+{
+ static int initialized;
+
+ if (!initialized) {
+
+#ifndef AUTOCONF_TEST
+ if (uid_wrapper_enabled()) {
+ setenv("UID_WRAPPER_MYUID", "1", 1);
+ }
+#endif
+
+ initial_uid = geteuid();
+ initial_gid = getegid();
+
+#ifndef AUTOCONF_TEST
+ if (uid_wrapper_enabled()) {
+ unsetenv("UID_WRAPPER_MYUID");
+ }
+#endif
+
+ initialized = 1;
+ }
+}
+
+/****************************************************************************
+some code (eg. winbindd) needs to know what uid we started as
+****************************************************************************/
+uid_t sec_initial_uid(void)
+{
+ return initial_uid;
+}
+
+/****************************************************************************
+some code (eg. winbindd, profiling shm) needs to know what gid we started as
+****************************************************************************/
+gid_t sec_initial_gid(void)
+{
+ return initial_gid;
+}
+
+/**
+ * @brief Check if we are running in root mode.
+ *
+ * @return Return whether Samba has root privileges
+ */
+bool root_mode(void)
+{
+ uid_t euid;
+
+ euid = geteuid();
+
+#ifndef AUTOCONF_TEST
+ if (uid_wrapper_enabled()) {
+ return (euid == initial_uid || euid == (uid_t)0);
+ }
+#endif
+
+ return (initial_uid == euid);
+}
+
+/****************************************************************************
+are we running in non-root mode?
+****************************************************************************/
+bool non_root_mode(void)
+{
+ return (initial_uid != (uid_t)0);
+}
+
+/****************************************************************************
+abort if we haven't set the uid correctly
+****************************************************************************/
+static void assert_uid(uid_t ruid, uid_t euid)
+{
+ if ((euid != (uid_t)-1 && geteuid() != euid) ||
+ (ruid != (uid_t)-1 && getuid() != ruid)) {
+ if (!non_root_mode()) {
+ DEBUG(0,("Failed to set uid privileges to (%d,%d) now set to (%d,%d)\n",
+ (int)ruid, (int)euid,
+ (int)getuid(), (int)geteuid()));
+ smb_panic("failed to set uid\n");
+ exit(1);
+ }
+ }
+}
+
+/****************************************************************************
+abort if we haven't set the gid correctly
+****************************************************************************/
+static void assert_gid(gid_t rgid, gid_t egid)
+{
+ if ((egid != (gid_t)-1 && getegid() != egid) ||
+ (rgid != (gid_t)-1 && getgid() != rgid)) {
+ if (!non_root_mode()) {
+ DEBUG(0,("Failed to set gid privileges to (%d,%d) now set to (%d,%d) uid=(%d,%d)\n",
+ (int)rgid, (int)egid,
+ (int)getgid(), (int)getegid(),
+ (int)getuid(), (int)geteuid()));
+ smb_panic("failed to set gid\n");
+ exit(1);
+ }
+ }
+}
+
+/****************************************************************************
+ Gain root privilege before doing something.
+ We want to end up with ruid==euid==0
+****************************************************************************/
+void gain_root_privilege(void)
+{
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresuid(0,0,0);
+#endif
+
+#if USE_SETEUID
+ samba_seteuid(0);
+#endif
+
+#if USE_SETREUID
+ samba_setreuid(0, 0);
+#endif
+
+#if USE_SETUIDX
+ samba_setuidx(ID_EFFECTIVE, 0);
+ samba_setuidx(ID_REAL, 0);
+#endif
+
+ /* this is needed on some systems */
+ samba_setuid(0);
+
+ assert_uid(0, 0);
+}
+
+
+/****************************************************************************
+ Ensure our real and effective groups are zero.
+ we want to end up with rgid==egid==0
+****************************************************************************/
+void gain_root_group_privilege(void)
+{
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresgid(0,0,0);
+#endif
+
+#if USE_SETREUID
+ samba_setregid(0,0);
+#endif
+
+#if USE_SETEUID
+ samba_setegid(0);
+#endif
+
+#if USE_SETUIDX
+ samba_setgidx(ID_EFFECTIVE, 0);
+ samba_setgidx(ID_REAL, 0);
+#endif
+
+ samba_setgid(0);
+
+ assert_gid(0, 0);
+}
+
+
+/****************************************************************************
+ Set effective uid, and possibly the real uid too.
+ We want to end up with either:
+
+ ruid==uid and euid==uid
+
+ or
+
+ ruid==0 and euid==uid
+
+ depending on what the local OS will allow us to regain root from.
+****************************************************************************/
+void set_effective_uid(uid_t uid)
+{
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ /* Set the effective as well as the real uid. */
+ if (samba_setresuid(uid,uid,-1) == -1) {
+ if (errno == EAGAIN) {
+ DEBUG(0, ("samba_setresuid failed with EAGAIN. uid(%d) "
+ "might be over its NPROC limit\n",
+ (int)uid));
+ }
+ }
+#endif
+
+#if USE_SETREUID
+ samba_setreuid(-1,uid);
+#endif
+
+#if USE_SETEUID
+ samba_seteuid(uid);
+#endif
+
+#if USE_SETUIDX
+ samba_setuidx(ID_EFFECTIVE, uid);
+#endif
+
+ assert_uid(-1, uid);
+}
+
+/****************************************************************************
+ Set *only* the effective gid.
+ we want to end up with rgid==0 and egid==gid
+****************************************************************************/
+void set_effective_gid(gid_t gid)
+{
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresgid(-1,gid,-1);
+#endif
+
+#if USE_SETREUID
+ samba_setregid(-1,gid);
+#endif
+
+#if USE_SETEUID
+ samba_setegid(gid);
+#endif
+
+#if USE_SETUIDX
+ samba_setgidx(ID_EFFECTIVE, gid);
+#endif
+
+ assert_gid(-1, gid);
+}
+
+static uid_t saved_euid, saved_ruid;
+static gid_t saved_egid, saved_rgid;
+
+/****************************************************************************
+ save the real and effective uid for later restoration. Used by the quotas
+ code
+****************************************************************************/
+void save_re_uid(void)
+{
+ saved_ruid = getuid();
+ saved_euid = geteuid();
+}
+
+
+/****************************************************************************
+ and restore them!
+****************************************************************************/
+
+void restore_re_uid_fromroot(void)
+{
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresuid(saved_ruid, saved_euid, -1);
+#elif USE_SETREUID
+ samba_setreuid(saved_ruid, -1);
+ samba_setreuid(-1,saved_euid);
+#elif USE_SETUIDX
+ samba_setuidx(ID_REAL, saved_ruid);
+ samba_setuidx(ID_EFFECTIVE, saved_euid);
+#else
+ set_effective_uid(saved_euid);
+ if (getuid() != saved_ruid)
+ samba_setuid(saved_ruid);
+ set_effective_uid(saved_euid);
+#endif
+
+ assert_uid(saved_ruid, saved_euid);
+}
+
+void restore_re_uid(void)
+{
+ set_effective_uid(0);
+ restore_re_uid_fromroot();
+}
+
+/****************************************************************************
+ save the real and effective gid for later restoration. Used by the
+ getgroups code
+****************************************************************************/
+void save_re_gid(void)
+{
+ saved_rgid = getgid();
+ saved_egid = getegid();
+}
+
+/****************************************************************************
+ and restore them!
+****************************************************************************/
+void restore_re_gid(void)
+{
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresgid(saved_rgid, saved_egid, -1);
+#elif USE_SETREUID
+ samba_setregid(saved_rgid, -1);
+ samba_setregid(-1,saved_egid);
+#elif USE_SETUIDX
+ samba_setgidx(ID_REAL, saved_rgid);
+ samba_setgidx(ID_EFFECTIVE, saved_egid);
+#else
+ set_effective_gid(saved_egid);
+ if (getgid() != saved_rgid)
+ samba_setgid(saved_rgid);
+ set_effective_gid(saved_egid);
+#endif
+
+ assert_gid(saved_rgid, saved_egid);
+}
+
+
+/****************************************************************************
+ set the real AND effective uid to the current effective uid in a way that
+ allows root to be regained.
+ This is only possible on some platforms.
+****************************************************************************/
+int set_re_uid(void)
+{
+ uid_t uid = geteuid();
+
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresuid(uid, uid, -1);
+#endif
+
+#if USE_SETREUID
+ samba_setreuid(0, 0);
+ samba_setreuid(uid, -1);
+ samba_setreuid(-1, uid);
+#endif
+
+#if USE_SETEUID
+ /* can't be done */
+ return -1;
+#endif
+
+#if USE_SETUIDX
+ /* can't be done */
+ return -1;
+#endif
+
+ assert_uid(uid, uid);
+ return 0;
+}
+
+
+/****************************************************************************
+ Become the specified uid and gid - permanently !
+ there should be no way back if possible
+****************************************************************************/
+void become_user_permanently(uid_t uid, gid_t gid)
+{
+ /*
+ * First - gain root privilege. We do this to ensure
+ * we can lose it again.
+ */
+
+ gain_root_privilege();
+ gain_root_group_privilege();
+
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresgid(gid,gid,gid);
+ samba_setgid(gid);
+ samba_setresuid(uid,uid,uid);
+ samba_setuid(uid);
+#endif
+
+#if USE_SETREUID
+ samba_setregid(gid,gid);
+ samba_setgid(gid);
+ samba_setreuid(uid,uid);
+ samba_setuid(uid);
+#endif
+
+#if USE_SETEUID
+ samba_setegid(gid);
+ samba_setgid(gid);
+ samba_setuid(uid);
+ samba_seteuid(uid);
+ samba_setuid(uid);
+#endif
+
+#if USE_SETUIDX
+ samba_setgidx(ID_REAL, gid);
+ samba_setgidx(ID_EFFECTIVE, gid);
+ samba_setgid(gid);
+ samba_setuidx(ID_REAL, uid);
+ samba_setuidx(ID_EFFECTIVE, uid);
+ samba_setuid(uid);
+#endif
+
+ assert_uid(uid, uid);
+ assert_gid(gid, gid);
+}
+
+#if defined(HAVE_LINUX_THREAD_CREDENTIALS) && defined(HAVE___THREAD)
+ struct set_thread_credentials_cache {
+ bool active;
+ uid_t uid;
+ gid_t gid;
+ size_t setlen;
+ uintptr_t gidset;
+ };
+static __thread struct set_thread_credentials_cache cache;
+#endif
+
+/**********************************************************
+ Function to set thread specific credentials. Leave
+ saved-set uid/gid alone.Must be thread-safe code.
+**********************************************************/
+
+int set_thread_credentials(uid_t uid,
+ gid_t gid,
+ size_t setlen,
+ const gid_t *gidset)
+{
+#if defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ /*
+ * With Linux thread-specific credentials
+ * we know we have setresuid/setresgid
+ * available.
+ */
+#ifdef HAVE___THREAD
+ if (cache.active &&
+ cache.uid == uid &&
+ cache.gid == gid &&
+ cache.setlen == setlen &&
+ (const gid_t *)cache.gidset == gidset)
+ {
+ return 0;
+ }
+#endif /* HAVE___THREAD */
+
+ /* Become root. */
+ /* Set ru=0, eu=0 */
+ if (samba_setresuid(0, 0, -1) != 0) {
+ return -1;
+ }
+ /* Set our primary gid. */
+ /* Set rg=gid, eg=gid */
+ if (samba_setresgid(gid, gid, -1) != 0) {
+ return -1;
+ }
+ /* Set extra groups list. */
+ if (samba_setgroups(setlen, gidset) != 0) {
+ return -1;
+ }
+ /* Become the requested user. */
+ /* Set ru=uid, eu=uid */
+ if (samba_setresuid(uid, uid, -1) != 0) {
+ return -1;
+ }
+ if (geteuid() != uid || getuid() != uid ||
+ getegid() != gid || getgid() != gid) {
+ smb_panic("set_thread_credentials failed\n");
+ return -1;
+ }
+
+#ifdef HAVE___THREAD
+ cache.active = true;
+ cache.uid = uid;
+ cache.gid = gid;
+ cache.setlen = setlen;
+ cache.gidset = (uintptr_t)gidset;
+#endif /* HAVE___THREAD */
+
+ return 0;
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+#ifdef AUTOCONF_TEST
+
+/****************************************************************************
+this function just checks that we don't get ENOSYS back
+****************************************************************************/
+static int have_syscall(void)
+{
+ errno = 0;
+
+#if defined(USE_SETRESUID) || defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ samba_setresuid(-1,-1,-1);
+#endif
+
+#if USE_SETREUID
+ samba_setreuid(-1,-1);
+#endif
+
+#if USE_SETEUID
+ samba_seteuid(-1);
+#endif
+
+#if USE_SETUIDX
+ samba_setuidx(ID_EFFECTIVE, -1);
+#endif
+
+ if (errno == ENOSYS) {
+ return -1;
+ }
+ return 0;
+}
+
+int main(void)
+{
+ if (getuid() != 0) {
+#if (defined(AIX) && defined(USE_SETREUID))
+ /* setreuid is badly broken on AIX 4.1, we avoid it completely */
+ fprintf(stderr,"avoiding possibly broken setreuid\n");
+ exit(1);
+#endif
+
+ /* if not running as root then at least check to see if we get ENOSYS - this
+ handles Linux 2.0.x with glibc 2.1 */
+ fprintf(stderr,"not running as root: checking for ENOSYS\n");
+ exit(have_syscall());
+ }
+
+ gain_root_privilege();
+ gain_root_group_privilege();
+ set_effective_gid(1);
+ set_effective_uid(1);
+ save_re_uid();
+ restore_re_uid();
+ gain_root_privilege();
+ gain_root_group_privilege();
+ become_user_permanently(1, 1);
+ samba_setuid(0);
+ if (getuid() == 0) {
+ fprintf(stderr,"uid not set permanently\n");
+ exit(1);
+ }
+
+ printf("OK\n");
+
+ exit(0);
+}
+#endif
+
+/****************************************************************************
+Check if we are setuid root. Used in libsmb and smbpasswd paranoia checks.
+****************************************************************************/
+bool is_setuid_root(void)
+{
+ return (geteuid() == (uid_t)0) && (getuid() != (uid_t)0);
+}
diff --git a/source3/lib/util_sid.c b/source3/lib/util_sid.c
new file mode 100644
index 0000000..fd767f9
--- /dev/null
+++ b/source3/lib/util_sid.c
@@ -0,0 +1,206 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Caseson Leighton 1998-1999
+ Copyright (C) Jeremy Allison 1999
+ Copyright (C) Stefan (metze) Metzmacher 2002
+ Copyright (C) Simo Sorce 2002
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/util_specialsids.h"
+
+
+/*****************************************************************
+ Convert a SID to an ascii string.
+*****************************************************************/
+
+char *sid_to_fstring(fstring sidstr_out, const struct dom_sid *sid)
+{
+ struct dom_sid_buf buf;
+ fstrcpy(sidstr_out, dom_sid_str_buf(sid, &buf));
+ return sidstr_out;
+}
+
+/*****************************************************************
+ Write a sid out into on-the-wire format.
+*****************************************************************/
+
+bool sid_linearize(uint8_t *outbuf, size_t len, const struct dom_sid *sid)
+{
+ struct ndr_push ndr = {
+ .data = outbuf, .alloc_size = len, .fixed_buf_size = true,
+ };
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_dom_sid(&ndr, NDR_SCALARS|NDR_BUFFERS, sid);
+ return NDR_ERR_CODE_IS_SUCCESS(ndr_err);
+}
+
+/*****************************************************************
+ Returns true if SID is internal (and non-mappable).
+*****************************************************************/
+
+bool non_mappable_sid(struct dom_sid *sid)
+{
+ struct dom_sid dom;
+
+ sid_copy(&dom, sid);
+ sid_split_rid(&dom, NULL);
+
+ if (dom_sid_equal(&dom, &global_sid_Builtin))
+ return True;
+
+ if (dom_sid_equal(&dom, &global_sid_NT_Authority))
+ return True;
+
+ return False;
+}
+
+/*****************************************************************
+ Return the binary string representation of a struct dom_sid.
+ Caller must free.
+*****************************************************************/
+
+char *sid_binstring_hex_talloc(TALLOC_CTX *mem_ctx, const struct dom_sid *sid)
+{
+ int len = ndr_size_dom_sid(sid, 0);
+ uint8_t buf[len];
+ sid_linearize(buf, len, sid);
+ return hex_encode_talloc(mem_ctx, buf, len);
+}
+
+NTSTATUS sid_array_from_info3(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo3 *info3,
+ struct dom_sid **user_sids,
+ uint32_t *num_user_sids,
+ bool include_user_group_rid)
+{
+ NTSTATUS status;
+ struct dom_sid sid;
+ struct dom_sid *sid_array = NULL;
+ uint32_t num_sids = 0;
+ uint32_t i;
+
+ if (include_user_group_rid) {
+ if (!sid_compose(&sid, info3->base.domain_sid, info3->base.rid)) {
+ DEBUG(3, ("could not compose user SID from rid 0x%x\n",
+ info3->base.rid));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ status = add_sid_to_array(mem_ctx, &sid, &sid_array, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not append user SID from rid 0x%x\n",
+ info3->base.rid));
+ return status;
+ }
+ }
+
+ if (!sid_compose(&sid, info3->base.domain_sid, info3->base.primary_gid)) {
+ DEBUG(3, ("could not compose group SID from rid 0x%x\n",
+ info3->base.primary_gid));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ status = add_sid_to_array(mem_ctx, &sid, &sid_array, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not append group SID from rid 0x%x\n",
+ info3->base.rid));
+ return status;
+ }
+
+ for (i = 0; i < info3->base.groups.count; i++) {
+ /* Don't add the primary group sid twice. */
+ if (!sid_compose(&sid, info3->base.domain_sid,
+ info3->base.groups.rids[i].rid)) {
+ DEBUG(3, ("could not compose SID from additional group "
+ "rid 0x%x\n", info3->base.groups.rids[i].rid));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ status = add_sid_to_array(mem_ctx, &sid, &sid_array, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not append SID from additional group "
+ "rid 0x%x\n", info3->base.groups.rids[i].rid));
+ return status;
+ }
+ }
+
+ /* Copy 'other' sids. We need to do sid filtering here to
+ prevent possible elevation of privileges. See:
+
+ http://www.microsoft.com/windows2000/techinfo/administration/security/sidfilter.asp
+ */
+
+ for (i = 0; i < info3->sidcount; i++) {
+
+ if (sid_check_is_in_asserted_identity(info3->sids[i].sid)) {
+ continue;
+ }
+
+ status = add_sid_to_array(mem_ctx, info3->sids[i].sid,
+ &sid_array, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct dom_sid_buf buf;
+ DEBUG(3, ("could not add SID to array: %s\n",
+ dom_sid_str_buf(info3->sids[i].sid, &buf)));
+ return status;
+ }
+ }
+
+ *user_sids = sid_array;
+ *num_user_sids = num_sids;
+
+ return NT_STATUS_OK;
+}
+
+bool security_token_find_npa_flags(const struct security_token *token,
+ uint32_t *_flags)
+{
+ const struct dom_sid *npa_flags_sid = NULL;
+ size_t num_npa_sids;
+
+ num_npa_sids =
+ security_token_count_flag_sids(token,
+ &global_sid_Samba_NPA_Flags,
+ 1,
+ &npa_flags_sid);
+ if (num_npa_sids != 1) {
+ return false;
+ }
+
+ sid_peek_rid(npa_flags_sid, _flags);
+ return true;
+}
+
+void security_token_del_npa_flags(struct security_token *token)
+{
+ const struct dom_sid *npa_flags_sid = NULL;
+ size_t num_npa_sids;
+
+ num_npa_sids =
+ security_token_count_flag_sids(token,
+ &global_sid_Samba_NPA_Flags,
+ 1,
+ &npa_flags_sid);
+ SMB_ASSERT(num_npa_sids == 1);
+
+ del_sid_from_array(npa_flags_sid, &token->sids, &token->num_sids);
+}
diff --git a/source3/lib/util_sid_passdb.c b/source3/lib/util_sid_passdb.c
new file mode 100644
index 0000000..e67a27d
--- /dev/null
+++ b/source3/lib/util_sid_passdb.c
@@ -0,0 +1,131 @@
+/*
+ Unix SMB/CIFS implementation.
+ sid utility functions
+
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/util_sid_passdb.h"
+#include "lib/util_unixsids.h"
+#include "passdb/machine_sid.h"
+#include "passdb.h"
+
+/**
+ * check whether this is an object-sid that should
+ * be treated by the passdb, e.g. for id-mapping.
+ */
+bool sid_check_object_is_for_passdb(const struct dom_sid *sid)
+{
+ if (sid_check_is_in_our_sam(sid) && pdb_is_responsible_for_our_sam()) {
+ return true;
+ }
+
+ if (sid_check_is_in_builtin(sid) && pdb_is_responsible_for_builtin()) {
+ return true;
+ }
+
+ if (sid_check_is_in_wellknown_domain(sid) &&
+ pdb_is_responsible_for_wellknown())
+ {
+ return true;
+ }
+
+ if (sid_check_is_in_unix_users(sid) &&
+ pdb_is_responsible_for_unix_users())
+ {
+ return true;
+ }
+
+ if (sid_check_is_in_unix_groups(sid) &&
+ pdb_is_responsible_for_unix_groups())
+ {
+ return true;
+ }
+
+ if (pdb_is_responsible_for_everything_else())
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * check whether this is an object- or domain-sid that should
+ * be treated by the passdb, e.g. for id-mapping.
+ */
+bool sid_check_is_for_passdb(const struct dom_sid *sid)
+{
+ if (sid_check_is_our_sam(sid) && pdb_is_responsible_for_our_sam()) {
+ return true;
+ }
+
+ if (sid_check_is_in_our_sam(sid) && pdb_is_responsible_for_our_sam()) {
+ return true;
+ }
+
+ if (sid_check_is_builtin(sid) && pdb_is_responsible_for_builtin()) {
+ return true;
+ }
+
+ if (sid_check_is_in_builtin(sid) && pdb_is_responsible_for_builtin()) {
+ return true;
+ }
+
+ if (sid_check_is_wellknown_domain(sid, NULL) &&
+ pdb_is_responsible_for_wellknown())
+ {
+ return true;
+ }
+
+ if (sid_check_is_in_wellknown_domain(sid) &&
+ pdb_is_responsible_for_wellknown())
+ {
+ return true;
+ }
+
+ if (sid_check_is_unix_users(sid) &&
+ pdb_is_responsible_for_unix_users())
+ {
+ return true;
+ }
+
+ if (sid_check_is_in_unix_users(sid) &&
+ pdb_is_responsible_for_unix_users())
+ {
+ return true;
+ }
+
+ if (sid_check_is_unix_groups(sid) &&
+ pdb_is_responsible_for_unix_groups())
+ {
+ return true;
+ }
+
+ if (sid_check_is_in_unix_groups(sid) &&
+ pdb_is_responsible_for_unix_groups())
+ {
+ return true;
+ }
+
+ if (pdb_is_responsible_for_everything_else())
+ {
+ return true;
+ }
+
+ return false;
+}
diff --git a/source3/lib/util_sid_passdb.h b/source3/lib/util_sid_passdb.h
new file mode 100644
index 0000000..381d63c
--- /dev/null
+++ b/source3/lib/util_sid_passdb.h
@@ -0,0 +1,36 @@
+/*
+ Unix SMB/CIFS implementation.
+ sid utility functions
+
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_UTIL_SID_PASSDB_H__
+#define __LIB_UTIL_SID_PASSDB_H__
+
+/**
+ * check whether this is an object-sid that should
+ * be treated by the passdb, e.g. for id-mapping.
+ */
+bool sid_check_object_is_for_passdb(const struct dom_sid *sid);
+
+/**
+ * check whether this is an object- or domain-sid that should
+ * be treated by the passdb, e.g. for id-mapping.
+ */
+bool sid_check_is_for_passdb(const struct dom_sid *sid);
+
+#endif /* __LIB_UTIL_SID_PASSDB_H__ */
diff --git a/source3/lib/util_sock.c b/source3/lib/util_sock.c
new file mode 100644
index 0000000..5ff875c
--- /dev/null
+++ b/source3/lib/util_sock.c
@@ -0,0 +1,1232 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Tim Potter 2000-2001
+ Copyright (C) Jeremy Allison 1992-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../lib/util/memcache.h"
+#include "../lib/async_req/async_sock.h"
+#include "../lib/util/select.h"
+#include "lib/socket/interfaces.h"
+#include "../lib/util/tevent_unix.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/sys_rw_data.h"
+#include "source3/lib/util_tsock.h"
+
+/****************************************************************************
+ Determine if a file descriptor is in fact a socket.
+****************************************************************************/
+
+bool is_a_socket(int fd)
+{
+ int v;
+ socklen_t l;
+ l = sizeof(int);
+ return(getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&v, &l) == 0);
+}
+
+/****************************************************************************
+ Read data from a file descriptor with a timeout in msec.
+ mincount = if timeout, minimum to read before returning
+ maxcount = number to be read.
+ time_out = timeout in milliseconds
+ NB. This can be called with a non-socket fd, don't change
+ sys_read() to sys_recv() or other socket call.
+****************************************************************************/
+
+NTSTATUS read_fd_with_timeout(int fd, char *buf,
+ size_t mincnt, size_t maxcnt,
+ unsigned int time_out,
+ size_t *size_ret)
+{
+ int pollrtn;
+ ssize_t readret;
+ size_t nread = 0;
+
+ /* just checking .... */
+ if (maxcnt <= 0)
+ return NT_STATUS_OK;
+
+ /* Blocking read */
+ if (time_out == 0) {
+ if (mincnt == 0) {
+ mincnt = maxcnt;
+ }
+
+ while (nread < mincnt) {
+ readret = sys_read(fd, buf + nread, maxcnt - nread);
+
+ if (readret == 0) {
+ DEBUG(5,("read_fd_with_timeout: "
+ "blocking read. EOF from client.\n"));
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ if (readret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ nread += readret;
+ }
+ goto done;
+ }
+
+ /* Most difficult - timeout read */
+ /* If this is ever called on a disk file and
+ mincnt is greater then the filesize then
+ system performance will suffer severely as
+ select always returns true on disk files */
+
+ for (nread=0; nread < mincnt; ) {
+ int revents;
+
+ pollrtn = poll_intr_one_fd(fd, POLLIN|POLLHUP, time_out,
+ &revents);
+
+ /* Check if error */
+ if (pollrtn == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Did we timeout ? */
+ if ((pollrtn == 0) ||
+ ((revents & (POLLIN|POLLHUP|POLLERR)) == 0)) {
+ DEBUG(10,("read_fd_with_timeout: timeout read. "
+ "select timed out.\n"));
+ return NT_STATUS_IO_TIMEOUT;
+ }
+
+ readret = sys_read(fd, buf+nread, maxcnt-nread);
+
+ if (readret == 0) {
+ /* we got EOF on the file descriptor */
+ DEBUG(5,("read_fd_with_timeout: timeout read. "
+ "EOF from client.\n"));
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ if (readret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ nread += readret;
+ }
+
+ done:
+ /* Return the number we got */
+ if (size_ret) {
+ *size_ret = nread;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Read data from an fd, reading exactly N bytes.
+ NB. This can be called with a non-socket fd, don't add dependencies
+ on socket calls.
+****************************************************************************/
+
+NTSTATUS read_data_ntstatus(int fd, char *buffer, size_t N)
+{
+ return read_fd_with_timeout(fd, buffer, N, N, 0, NULL);
+}
+
+/****************************************************************************
+ Read 4 bytes of a smb packet and return the smb length of the packet.
+ Store the result in the buffer.
+ This version of the function will return a length of zero on receiving
+ a keepalive packet.
+ Timeout is in milliseconds.
+****************************************************************************/
+
+NTSTATUS read_smb_length_return_keepalive(int fd, char *inbuf,
+ unsigned int timeout,
+ size_t *len)
+{
+ int msg_type;
+ NTSTATUS status;
+
+ status = read_fd_with_timeout(fd, inbuf, 4, 4, timeout, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *len = smb_len(inbuf);
+ msg_type = CVAL(inbuf,0);
+
+ if (msg_type == NBSSkeepalive) {
+ DEBUG(5,("Got keepalive packet\n"));
+ }
+
+ DEBUG(10,("got smb length of %lu\n",(unsigned long)(*len)));
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Read an smb from a fd.
+ The timeout is in milliseconds.
+ This function will return on receipt of a session keepalive packet.
+ maxlen is the max number of bytes to return, not including the 4 byte
+ length. If zero it means buflen limit.
+ Doesn't check the MAC on signed packets.
+****************************************************************************/
+
+NTSTATUS receive_smb_raw(int fd, char *buffer, size_t buflen, unsigned int timeout,
+ size_t maxlen, size_t *p_len)
+{
+ size_t len;
+ NTSTATUS status;
+
+ status = read_smb_length_return_keepalive(fd,buffer,timeout,&len);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("read_fd_with_timeout failed, read "
+ "error = %s.\n", nt_errstr(status)));
+ return status;
+ }
+
+ if (len > buflen) {
+ DEBUG(0,("Invalid packet length! (%lu bytes).\n",
+ (unsigned long)len));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if(len > 0) {
+ if (maxlen) {
+ len = MIN(len,maxlen);
+ }
+
+ status = read_fd_with_timeout(
+ fd, buffer+4, len, len, timeout, &len);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("read_fd_with_timeout failed, read error = "
+ "%s.\n", nt_errstr(status)));
+ return status;
+ }
+
+ /* not all of samba3 properly checks for packet-termination
+ * of strings. This ensures that we don't run off into
+ * empty space. */
+ SSVAL(buffer+4,len, 0);
+ }
+
+ *p_len = len;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Open a socket of the specified type, port, and address for incoming data.
+ *
+ * Return sock or -errno
+ */
+
+int open_socket_in(
+ int type,
+ const struct sockaddr_storage *paddr,
+ uint16_t port,
+ bool rebind)
+{
+ struct samba_sockaddr addr = {
+ .sa_socklen = sizeof(struct sockaddr_storage),
+ .u.ss = *paddr,
+ };
+ int ret, sock = -1;
+ int val = rebind ? 1 : 0;
+ bool ok;
+
+ switch (addr.u.sa.sa_family) {
+ case AF_INET6:
+ addr.sa_socklen = sizeof(struct sockaddr_in6);
+ break;
+ case AF_INET:
+ addr.sa_socklen = sizeof(struct sockaddr_in);
+ break;
+ }
+
+ ok = samba_sockaddr_set_port(&addr, port);
+ if (!ok) {
+ ret = -EINVAL;
+ DBG_DEBUG("samba_sockaddr_set_port failed\n");
+ goto fail;
+ }
+
+ sock = socket(addr.u.ss.ss_family, type, 0 );
+ if (sock == -1) {
+ ret = -errno;
+ DBG_DEBUG("socket() failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ ret = setsockopt(
+ sock, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val));
+ if (ret == -1) {
+ ret = -errno;
+ DBG_DEBUG("setsockopt(SO_REUSEADDR) failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+
+#ifdef SO_REUSEPORT
+ ret = setsockopt(
+ sock, SOL_SOCKET, SO_REUSEPORT, (char *)&val, sizeof(val));
+ if (ret == -1) {
+ ret = -errno;
+ DBG_DEBUG("setsockopt(SO_REUSEPORT) failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+#endif /* SO_REUSEPORT */
+
+#ifdef HAVE_IPV6
+ /*
+ * As IPV6_V6ONLY is the default on some systems,
+ * we better try to be consistent and always use it.
+ *
+ * This also avoids using IPv4 via AF_INET6 sockets
+ * and makes sure %I never resolves to a '::ffff:192.168.0.1'
+ * string.
+ */
+ if (addr.u.ss.ss_family == AF_INET6) {
+
+ val = 1;
+
+ ret = setsockopt(
+ sock,
+ IPPROTO_IPV6,
+ IPV6_V6ONLY,
+ (const void *)&val,
+ sizeof(val));
+ if (ret == -1) {
+ ret = -errno;
+ DBG_DEBUG("setsockopt(IPV6_V6ONLY) failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+ }
+#endif
+
+ /* now we've got a socket - we need to bind it */
+ ret = bind(sock, &addr.u.sa, addr.sa_socklen);
+ if (ret == -1) {
+ char addrstr[INET6_ADDRSTRLEN];
+
+ ret = -errno;
+
+ print_sockaddr(addrstr, sizeof(addrstr), &addr.u.ss);
+ DBG_DEBUG("bind for %s port %"PRIu16" failed: %s\n",
+ addrstr,
+ port,
+ strerror(-ret));
+ goto fail;
+ }
+
+ DBG_DEBUG("bind succeeded on port %"PRIu16"\n", port);
+
+ return sock;
+
+fail:
+ if (sock != -1) {
+ close(sock);
+ sock = -1;
+ }
+ return ret;
+ }
+
+struct open_socket_out_state {
+ int fd;
+ struct tevent_context *ev;
+ struct sockaddr_storage ss;
+ socklen_t salen;
+ uint16_t port;
+ struct tevent_req *connect_subreq;
+};
+
+static void open_socket_out_connected(struct tevent_req *subreq);
+
+static void open_socket_out_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct open_socket_out_state *state =
+ tevent_req_data(req, struct open_socket_out_state);
+
+ /*
+ * Make sure that the async_connect_send subreq has a chance to reset
+ * fcntl before the socket goes away.
+ */
+ TALLOC_FREE(state->connect_subreq);
+
+ if (req_state == TEVENT_REQ_DONE) {
+ /*
+ * we keep the socket open for the caller to use
+ */
+ return;
+ }
+
+ if (state->fd != -1) {
+ close(state->fd);
+ state->fd = -1;
+ }
+}
+
+/****************************************************************************
+ Create an outgoing socket. timeout is in milliseconds.
+**************************************************************************/
+
+struct tevent_req *open_socket_out_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct sockaddr_storage *pss,
+ uint16_t port,
+ int timeout)
+{
+ char addr[INET6_ADDRSTRLEN];
+ struct tevent_req *req;
+ struct open_socket_out_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct open_socket_out_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ss = *pss;
+ state->port = port;
+ state->salen = -1;
+
+ state->fd = socket(state->ss.ss_family, SOCK_STREAM, 0);
+ if (state->fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_cleanup_fn(req, open_socket_out_cleanup);
+
+ if ((timeout != 0) &&
+ !tevent_req_set_endtime(
+ req, ev, timeval_current_ofs_msec(timeout))) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+#if defined(HAVE_IPV6)
+ if (pss->ss_family == AF_INET6) {
+ struct sockaddr_in6 *psa6;
+ psa6 = (struct sockaddr_in6 *)&state->ss;
+ psa6->sin6_port = htons(port);
+ if (psa6->sin6_scope_id == 0
+ && IN6_IS_ADDR_LINKLOCAL(&psa6->sin6_addr)) {
+ setup_linklocal_scope_id(
+ (struct sockaddr *)&(state->ss));
+ }
+ state->salen = sizeof(struct sockaddr_in6);
+ }
+#endif
+ if (pss->ss_family == AF_INET) {
+ struct sockaddr_in *psa;
+ psa = (struct sockaddr_in *)&state->ss;
+ psa->sin_port = htons(port);
+ state->salen = sizeof(struct sockaddr_in);
+ }
+
+ if (pss->ss_family == AF_UNIX) {
+ state->salen = sizeof(struct sockaddr_un);
+ }
+
+ print_sockaddr(addr, sizeof(addr), &state->ss);
+ DEBUG(3,("Connecting to %s at port %u\n", addr, (unsigned int)port));
+
+ state->connect_subreq = async_connect_send(
+ state, state->ev, state->fd, (struct sockaddr *)&state->ss,
+ state->salen, NULL, NULL, NULL);
+ if (tevent_req_nomem(state->connect_subreq, NULL)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->connect_subreq,
+ open_socket_out_connected, req);
+ return req;
+}
+
+static void open_socket_out_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct open_socket_out_state *state =
+ tevent_req_data(req, struct open_socket_out_state);
+ int ret;
+ int sys_errno;
+
+ ret = async_connect_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ state->connect_subreq = NULL;
+ if (ret == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ tevent_req_nterror(req, map_nt_error_from_unix(sys_errno));
+}
+
+NTSTATUS open_socket_out_recv(struct tevent_req *req, int *pfd)
+{
+ struct open_socket_out_state *state =
+ tevent_req_data(req, struct open_socket_out_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *pfd = state->fd;
+ state->fd = -1;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/**
+* @brief open a socket
+*
+* @param pss a struct sockaddr_storage defining the address to connect to
+* @param port to connect to
+* @param timeout in MILLISECONDS
+* @param pfd file descriptor returned
+*
+* @return NTSTATUS code
+*/
+NTSTATUS open_socket_out(const struct sockaddr_storage *pss, uint16_t port,
+ int timeout, int *pfd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = open_socket_out_send(frame, ev, pss, port, timeout);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+ status = open_socket_out_recv(req, pfd);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct open_socket_out_defer_state {
+ struct tevent_context *ev;
+ struct sockaddr_storage ss;
+ uint16_t port;
+ int timeout;
+ int fd;
+};
+
+static void open_socket_out_defer_waited(struct tevent_req *subreq);
+static void open_socket_out_defer_connected(struct tevent_req *subreq);
+
+struct tevent_req *open_socket_out_defer_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct timeval wait_time,
+ const struct sockaddr_storage *pss,
+ uint16_t port,
+ int timeout)
+{
+ struct tevent_req *req, *subreq;
+ struct open_socket_out_defer_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct open_socket_out_defer_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ss = *pss;
+ state->port = port;
+ state->timeout = timeout;
+
+ subreq = tevent_wakeup_send(
+ state, ev,
+ timeval_current_ofs(wait_time.tv_sec, wait_time.tv_usec));
+ if (subreq == NULL) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, open_socket_out_defer_waited, req);
+ return req;
+ fail:
+ TALLOC_FREE(req);
+ return NULL;
+}
+
+static void open_socket_out_defer_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct open_socket_out_defer_state *state = tevent_req_data(
+ req, struct open_socket_out_defer_state);
+ bool ret;
+
+ ret = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ subreq = open_socket_out_send(state, state->ev, &state->ss,
+ state->port, state->timeout);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, open_socket_out_defer_connected, req);
+}
+
+static void open_socket_out_defer_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct open_socket_out_defer_state *state = tevent_req_data(
+ req, struct open_socket_out_defer_state);
+ NTSTATUS status;
+
+ status = open_socket_out_recv(subreq, &state->fd);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS open_socket_out_defer_recv(struct tevent_req *req, int *pfd)
+{
+ struct open_socket_out_defer_state *state = tevent_req_data(
+ req, struct open_socket_out_defer_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfd = state->fd;
+ state->fd = -1;
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Return the IP addr of the remote end of a socket as a string.
+ Optionally return the struct sockaddr_storage.
+ ******************************************************************/
+
+static const char *get_peer_addr_internal(int fd,
+ char *addr_buf,
+ size_t addr_buf_len,
+ struct sockaddr *pss,
+ socklen_t *plength)
+{
+ struct sockaddr_storage ss;
+ socklen_t length = sizeof(ss);
+
+ strlcpy(addr_buf,"0.0.0.0",addr_buf_len);
+
+ if (fd == -1) {
+ return addr_buf;
+ }
+
+ if (pss == NULL) {
+ pss = (struct sockaddr *)&ss;
+ plength = &length;
+ }
+
+ if (getpeername(fd, (struct sockaddr *)pss, plength) < 0) {
+ int level = (errno == ENOTCONN) ? 2 : 0;
+ DEBUG(level, ("getpeername failed. Error was %s\n",
+ strerror(errno)));
+ return addr_buf;
+ }
+
+ print_sockaddr_len(addr_buf,
+ addr_buf_len,
+ pss,
+ *plength);
+ return addr_buf;
+}
+
+/*******************************************************************
+ Matchname - determine if host name matches IP address. Used to
+ confirm a hostname lookup to prevent spoof attacks.
+******************************************************************/
+
+static bool matchname(const char *remotehost,
+ const struct sockaddr *pss,
+ socklen_t len)
+{
+ struct addrinfo *res = NULL;
+ struct addrinfo *ailist = NULL;
+ char addr_buf[INET6_ADDRSTRLEN];
+ bool ret = interpret_string_addr_internal(&ailist,
+ remotehost,
+ AI_ADDRCONFIG|AI_CANONNAME);
+
+ if (!ret || ailist == NULL) {
+ DEBUG(3,("matchname: getaddrinfo failed for "
+ "name %s [%s]\n",
+ remotehost,
+ gai_strerror(ret) ));
+ return false;
+ }
+
+ /*
+ * Make sure that getaddrinfo() returns the "correct" host name.
+ */
+
+ if (ailist->ai_canonname == NULL ||
+ (!strequal(remotehost, ailist->ai_canonname) &&
+ !strequal(remotehost, "localhost"))) {
+ DEBUG(0,("matchname: host name/name mismatch: %s != %s\n",
+ remotehost,
+ ailist->ai_canonname ?
+ ailist->ai_canonname : "(NULL)"));
+ freeaddrinfo(ailist);
+ return false;
+ }
+
+ /* Look up the host address in the address list we just got. */
+ for (res = ailist; res; res = res->ai_next) {
+ if (!res->ai_addr) {
+ continue;
+ }
+ if (sockaddr_equal((const struct sockaddr *)res->ai_addr,
+ (const struct sockaddr *)pss)) {
+ freeaddrinfo(ailist);
+ return true;
+ }
+ }
+
+ /*
+ * The host name does not map to the original host address. Perhaps
+ * someone has compromised a name server. More likely someone botched
+ * it, but that could be dangerous, too.
+ */
+
+ DEBUG(0,("matchname: host name/address mismatch: %s != %s\n",
+ print_sockaddr_len(addr_buf,
+ sizeof(addr_buf),
+ pss,
+ len),
+ ailist->ai_canonname ? ailist->ai_canonname : "(NULL)"));
+
+ if (ailist) {
+ freeaddrinfo(ailist);
+ }
+ return false;
+}
+
+/*******************************************************************
+ Deal with the singleton cache.
+******************************************************************/
+
+struct name_addr_pair {
+ struct sockaddr_storage ss;
+ const char *name;
+};
+
+/*******************************************************************
+ Lookup a name/addr pair. Returns memory allocated from memcache.
+******************************************************************/
+
+static bool lookup_nc(struct name_addr_pair *nc)
+{
+ DATA_BLOB tmp;
+
+ ZERO_STRUCTP(nc);
+
+ if (!memcache_lookup(
+ NULL, SINGLETON_CACHE,
+ data_blob_string_const_null("get_peer_name"),
+ &tmp)) {
+ return false;
+ }
+
+ memcpy(&nc->ss, tmp.data, sizeof(nc->ss));
+ nc->name = (const char *)tmp.data + sizeof(nc->ss);
+ return true;
+}
+
+/*******************************************************************
+ Save a name/addr pair.
+******************************************************************/
+
+static void store_nc(const struct name_addr_pair *nc)
+{
+ DATA_BLOB tmp;
+ size_t namelen = strlen(nc->name);
+
+ tmp = data_blob(NULL, sizeof(nc->ss) + namelen + 1);
+ if (!tmp.data) {
+ return;
+ }
+ memcpy(tmp.data, &nc->ss, sizeof(nc->ss));
+ memcpy(tmp.data+sizeof(nc->ss), nc->name, namelen+1);
+
+ memcache_add(NULL, SINGLETON_CACHE,
+ data_blob_string_const_null("get_peer_name"),
+ tmp);
+ data_blob_free(&tmp);
+}
+
+/*******************************************************************
+ Return the IP addr of the remote end of a socket as a string.
+ ******************************************************************/
+
+const char *get_peer_addr(int fd, char *addr, size_t addr_len)
+{
+ return get_peer_addr_internal(fd, addr, addr_len, NULL, NULL);
+}
+
+int get_remote_hostname(const struct tsocket_address *remote_address,
+ char **name,
+ TALLOC_CTX *mem_ctx)
+{
+ char name_buf[MAX_DNS_NAME_LENGTH];
+ char tmp_name[MAX_DNS_NAME_LENGTH];
+ struct name_addr_pair nc;
+ struct sockaddr_storage ss;
+ ssize_t len;
+ int rc;
+
+ if (!lp_hostname_lookups()) {
+ nc.name = tsocket_address_inet_addr_string(remote_address,
+ mem_ctx);
+ if (nc.name == NULL) {
+ return -1;
+ }
+
+ len = tsocket_address_bsd_sockaddr(remote_address,
+ (struct sockaddr *) &nc.ss,
+ sizeof(struct sockaddr_storage));
+ if (len < 0) {
+ return -1;
+ }
+
+ store_nc(&nc);
+ lookup_nc(&nc);
+
+ if (nc.name == NULL) {
+ *name = talloc_strdup(mem_ctx, "UNKNOWN");
+ } else {
+ *name = talloc_strdup(mem_ctx, nc.name);
+ }
+ return 0;
+ }
+
+ lookup_nc(&nc);
+
+ ZERO_STRUCT(ss);
+
+ len = tsocket_address_bsd_sockaddr(remote_address,
+ (struct sockaddr *) &ss,
+ sizeof(struct sockaddr_storage));
+ if (len < 0) {
+ return -1;
+ }
+
+ /* it might be the same as the last one - save some DNS work */
+ if (sockaddr_equal((struct sockaddr *)&ss, (struct sockaddr *)&nc.ss)) {
+ if (nc.name == NULL) {
+ *name = talloc_strdup(mem_ctx, "UNKNOWN");
+ } else {
+ *name = talloc_strdup(mem_ctx, nc.name);
+ }
+ return 0;
+ }
+
+ /* Look up the remote host name. */
+ rc = sys_getnameinfo((struct sockaddr *) &ss,
+ len,
+ name_buf,
+ sizeof(name_buf),
+ NULL,
+ 0,
+ 0);
+ if (rc < 0) {
+ char *p;
+
+ p = tsocket_address_inet_addr_string(remote_address, mem_ctx);
+ if (p == NULL) {
+ return -1;
+ }
+
+ DEBUG(1,("getnameinfo failed for %s with error %s\n",
+ p,
+ gai_strerror(rc)));
+ strlcpy(name_buf, p, sizeof(name_buf));
+
+ TALLOC_FREE(p);
+ } else {
+ if (!matchname(name_buf, (struct sockaddr *)&ss, len)) {
+ DEBUG(0,("matchname failed on %s\n", name_buf));
+ strlcpy(name_buf, "UNKNOWN", sizeof(name_buf));
+ }
+ }
+
+ strlcpy(tmp_name, name_buf, sizeof(tmp_name));
+ alpha_strcpy(name_buf, tmp_name, "_-.", sizeof(name_buf));
+ if (strstr(name_buf,"..")) {
+ strlcpy(name_buf, "UNKNOWN", sizeof(name_buf));
+ }
+
+ nc.name = name_buf;
+ nc.ss = ss;
+
+ store_nc(&nc);
+ lookup_nc(&nc);
+
+ if (nc.name == NULL) {
+ *name = talloc_strdup(mem_ctx, "UNKNOWN");
+ } else {
+ *name = talloc_strdup(mem_ctx, nc.name);
+ }
+
+ return 0;
+}
+
+/*******************************************************************
+ Create protected unix domain socket.
+
+ Some unixes cannot set permissions on a ux-dom-sock, so we
+ have to make sure that the directory contains the protection
+ permissions instead.
+ ******************************************************************/
+
+int create_pipe_sock(const char *socket_dir,
+ const char *socket_name,
+ mode_t dir_perms)
+{
+#ifdef HAVE_UNIXSOCKET
+ struct sockaddr_un sunaddr;
+ bool ok;
+ int sock = -1;
+ mode_t old_umask;
+ char *path = NULL;
+ size_t path_len;
+
+ old_umask = umask(0);
+
+ ok = directory_create_or_exist_strict(socket_dir,
+ sec_initial_uid(),
+ dir_perms);
+ if (!ok) {
+ goto out_close;
+ }
+
+ /* Create the socket file */
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+
+ if (sock == -1) {
+ DEBUG(0, ("create_pipe_sock: socket error %s\n",
+ strerror(errno) ));
+ goto out_close;
+ }
+
+ if (asprintf(&path, "%s/%s", socket_dir, socket_name) == -1) {
+ goto out_close;
+ }
+
+ unlink(path);
+ memset(&sunaddr, 0, sizeof(sunaddr));
+ sunaddr.sun_family = AF_UNIX;
+
+ path_len = strlcpy(sunaddr.sun_path, path, sizeof(sunaddr.sun_path));
+ if (path_len > sizeof(sunaddr.sun_path)) {
+ DBG_ERR("Refusing to attempt to create pipe socket "
+ "%s. Path is longer than permitted for a "
+ "unix domain socket. It would truncate to "
+ "%s\n",
+ path,
+ sunaddr.sun_path);
+ goto out_close;
+ }
+
+ if (bind(sock, (struct sockaddr *)&sunaddr, sizeof(sunaddr)) == -1) {
+ DEBUG(0, ("bind failed on pipe socket %s: %s\n", path,
+ strerror(errno)));
+ goto out_close;
+ }
+
+ SAFE_FREE(path);
+
+ umask(old_umask);
+ return sock;
+
+out_close:
+ SAFE_FREE(path);
+ if (sock != -1)
+ close(sock);
+
+ umask(old_umask);
+ return -1;
+
+#else
+ DEBUG(0, ("create_pipe_sock: No Unix sockets on this system\n"));
+ return -1;
+#endif /* HAVE_UNIXSOCKET */
+}
+
+/****************************************************************************
+ Get my own canonical name, including domain.
+****************************************************************************/
+
+const char *get_mydnsfullname(void)
+{
+ struct addrinfo *res = NULL;
+ char my_hostname[HOST_NAME_MAX];
+ bool ret;
+ DATA_BLOB tmp;
+
+ if (memcache_lookup(NULL, SINGLETON_CACHE,
+ data_blob_string_const_null("get_mydnsfullname"),
+ &tmp)) {
+ SMB_ASSERT(tmp.length > 0);
+ return (const char *)tmp.data;
+ }
+
+ /* get my host name */
+ if (gethostname(my_hostname, sizeof(my_hostname)) == -1) {
+ DEBUG(0,("get_mydnsfullname: gethostname failed\n"));
+ return NULL;
+ }
+
+ /* Ensure null termination. */
+ my_hostname[sizeof(my_hostname)-1] = '\0';
+
+ ret = interpret_string_addr_internal(&res,
+ my_hostname,
+ AI_ADDRCONFIG|AI_CANONNAME);
+
+ if (!ret || res == NULL) {
+ DEBUG(3,("get_mydnsfullname: getaddrinfo failed for "
+ "name %s [%s]\n",
+ my_hostname,
+ gai_strerror(ret) ));
+ return NULL;
+ }
+
+ /*
+ * Make sure that getaddrinfo() returns the "correct" host name.
+ */
+
+ if (res->ai_canonname == NULL) {
+ DEBUG(3,("get_mydnsfullname: failed to get "
+ "canonical name for %s\n",
+ my_hostname));
+ freeaddrinfo(res);
+ return NULL;
+ }
+
+ /* This copies the data, so we must do a lookup
+ * afterwards to find the value to return.
+ */
+
+ memcache_add(NULL, SINGLETON_CACHE,
+ data_blob_string_const_null("get_mydnsfullname"),
+ data_blob_string_const_null(res->ai_canonname));
+
+ if (!memcache_lookup(NULL, SINGLETON_CACHE,
+ data_blob_string_const_null("get_mydnsfullname"),
+ &tmp)) {
+ tmp = data_blob_talloc(talloc_tos(), res->ai_canonname,
+ strlen(res->ai_canonname) + 1);
+ }
+
+ freeaddrinfo(res);
+
+ return (const char *)tmp.data;
+}
+
+/************************************************************
+ Is this my ip address ?
+************************************************************/
+
+static bool is_my_ipaddr(const char *ipaddr_str)
+{
+ struct sockaddr_storage ss;
+ struct iface_struct *nics;
+ int i, n;
+
+ if (!interpret_string_addr(&ss, ipaddr_str, AI_NUMERICHOST)) {
+ return false;
+ }
+
+ if (is_zero_addr(&ss)) {
+ return false;
+ }
+
+ if (ismyaddr((struct sockaddr *)&ss) ||
+ is_loopback_addr((struct sockaddr *)&ss)) {
+ return true;
+ }
+
+ n = get_interfaces(talloc_tos(), &nics);
+ for (i=0; i<n; i++) {
+ if (sockaddr_equal((struct sockaddr *)&nics[i].ip, (struct sockaddr *)&ss)) {
+ TALLOC_FREE(nics);
+ return true;
+ }
+ }
+ TALLOC_FREE(nics);
+ return false;
+}
+
+/************************************************************
+ Is this my name ?
+************************************************************/
+
+bool is_myname_or_ipaddr(const char *s)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *name = NULL;
+ const char *dnsname;
+ char *servername = NULL;
+
+ if (!s) {
+ return false;
+ }
+
+ /* Sanitize the string from '\\name' */
+ name = talloc_strdup(ctx, s);
+ if (!name) {
+ return false;
+ }
+
+ servername = strrchr_m(name, '\\' );
+ if (!servername) {
+ servername = name;
+ } else {
+ servername++;
+ }
+
+ /* Optimize for the common case */
+ if (strequal(servername, lp_netbios_name())) {
+ return true;
+ }
+
+ /* Check for an alias */
+ if (is_myname(servername)) {
+ return true;
+ }
+
+ /* Check for loopback */
+ if (strequal(servername, "127.0.0.1") ||
+ strequal(servername, "::1")) {
+ return true;
+ }
+
+ if (strequal(servername, "localhost")) {
+ return true;
+ }
+
+ /* Maybe it's my dns name */
+ dnsname = get_mydnsfullname();
+ if (dnsname && strequal(servername, dnsname)) {
+ return true;
+ }
+
+ /* Maybe its an IP address? */
+ if (is_ipaddress(servername)) {
+ return is_my_ipaddr(servername);
+ }
+
+ /* Handle possible CNAME records - convert to an IP addr. list. */
+ {
+ /* Use DNS to resolve the name, check all addresses. */
+ struct addrinfo *p = NULL;
+ struct addrinfo *res = NULL;
+
+ if (!interpret_string_addr_internal(&res,
+ servername,
+ AI_ADDRCONFIG)) {
+ return false;
+ }
+
+ for (p = res; p; p = p->ai_next) {
+ char addr[INET6_ADDRSTRLEN];
+ struct sockaddr_storage ss;
+
+ ZERO_STRUCT(ss);
+ memcpy(&ss, p->ai_addr, p->ai_addrlen);
+ print_sockaddr(addr,
+ sizeof(addr),
+ &ss);
+ if (is_my_ipaddr(addr)) {
+ freeaddrinfo(res);
+ return true;
+ }
+ }
+ freeaddrinfo(res);
+ }
+
+ /* No match */
+ return false;
+}
+
+int poll_one_fd(int fd, int events, int timeout, int *revents)
+{
+ struct pollfd pfd;
+ int ret;
+
+ pfd.fd = fd;
+ pfd.events = events;
+
+ ret = poll(&pfd, 1, timeout);
+
+ /*
+ * Assign whatever poll did, even in the ret<=0 case.
+ */
+ *revents = pfd.revents;
+
+ return ret;
+}
+
+int poll_intr_one_fd(int fd, int events, int timeout, int *revents)
+{
+ struct pollfd pfd;
+ int ret;
+
+ pfd.fd = fd;
+ pfd.events = events;
+
+ ret = sys_poll_intr(&pfd, 1, timeout);
+ if (ret <= 0) {
+ *revents = 0;
+ return ret;
+ }
+ *revents = pfd.revents;
+ return 1;
+}
diff --git a/source3/lib/util_specialsids.c b/source3/lib/util_specialsids.c
new file mode 100644
index 0000000..10c189c
--- /dev/null
+++ b/source3/lib/util_specialsids.c
@@ -0,0 +1,42 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Guenther Deschner 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "libcli/security/dom_sid.h"
+#include "../libcli/security/security.h"
+#include "util_specialsids.h"
+
+bool sid_check_is_asserted_identity(const struct dom_sid *sid)
+{
+ return dom_sid_equal(sid, &global_sid_Asserted_Identity);
+}
+
+bool sid_check_is_in_asserted_identity(const struct dom_sid *sid)
+{
+ struct dom_sid dom_sid;
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, NULL);
+
+ return sid_check_is_asserted_identity(&dom_sid);
+}
+
+const char *asserted_identity_domain_name(void)
+{
+ return "Asserted Identity";
+}
diff --git a/source3/lib/util_specialsids.h b/source3/lib/util_specialsids.h
new file mode 100644
index 0000000..c650737
--- /dev/null
+++ b/source3/lib/util_specialsids.h
@@ -0,0 +1,29 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Copyright (C) Guenther Deschner 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIB_UTIL_SPECIALSIDS_H__
+#define __LIB_UTIL_SPECIALSIDS_H__
+
+#include "replace.h"
+
+struct dom_sid;
+bool sid_check_is_asserted_identity(const struct dom_sid *sid);
+bool sid_check_is_in_asserted_identity(const struct dom_sid *sid);
+const char *asserted_identity_domain_name(void);
+
+#endif
diff --git a/source3/lib/util_str.c b/source3/lib/util_str.c
new file mode 100644
index 0000000..3ac1e55
--- /dev/null
+++ b/source3/lib/util_str.c
@@ -0,0 +1,762 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+
+ Copyright (C) Andrew Tridgell 1992-2001
+ Copyright (C) Simo Sorce 2001-2002
+ Copyright (C) Martin Pool 2003
+ Copyright (C) James Peach 2006
+ Copyright (C) Jeremy Allison 1992-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/smb_strtox.h"
+
+static const char toupper_ascii_fast_table[128] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+ 0x60, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+ 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
+};
+
+/**
+ * Compare 2 strings up to and including the nth char.
+ *
+ * @note The comparison is case-insensitive.
+ **/
+bool strnequal(const char *s1,const char *s2,size_t n)
+{
+ if (s1 == s2)
+ return(true);
+ if (!s1 || !s2 || !n)
+ return(false);
+
+ return(strncasecmp_m(s1,s2,n)==0);
+}
+
+/**
+ Skip past a string in a buffer. Buffer may not be
+ null terminated. end_ptr points to the first byte after
+ then end of the buffer.
+**/
+
+char *skip_string(const char *base, size_t len, char *buf)
+{
+ const char *end_ptr = base + len;
+
+ if (end_ptr < base || !base || !buf || buf >= end_ptr) {
+ return NULL;
+ }
+
+ /* Skip the string */
+ while (*buf) {
+ buf++;
+ if (buf >= end_ptr) {
+ return NULL;
+ }
+ }
+ /* Skip the '\0' */
+ buf++;
+ return buf;
+}
+
+/**
+ Count the number of characters in a string. Normally this will
+ be the same as the number of bytes in a string for single byte strings,
+ but will be different for multibyte.
+**/
+
+size_t str_charnum(const char *s)
+{
+ size_t ret, converted_size;
+ smb_ucs2_t *tmpbuf2 = NULL;
+ if (!push_ucs2_talloc(talloc_tos(), &tmpbuf2, s, &converted_size)) {
+ return 0;
+ }
+ ret = strlen_w(tmpbuf2);
+ TALLOC_FREE(tmpbuf2);
+ return ret;
+}
+
+bool trim_char(char *s,char cfront,char cback)
+{
+ bool ret = false;
+ char *ep;
+ char *fp = s;
+
+ /* Ignore null or empty strings. */
+ if (!s || (s[0] == '\0'))
+ return false;
+
+ if (cfront) {
+ while (*fp && *fp == cfront)
+ fp++;
+ if (!*fp) {
+ /* We ate the string. */
+ s[0] = '\0';
+ return true;
+ }
+ if (fp != s)
+ ret = true;
+ }
+
+ ep = fp + strlen(fp) - 1;
+ if (cback) {
+ /* Attempt ascii only. Bail for mb strings. */
+ while ((ep >= fp) && (*ep == cback)) {
+ ret = true;
+ if ((ep > fp) && (((unsigned char)ep[-1]) & 0x80)) {
+ /* Could be mb... bail back to tim_string. */
+ char fs[2], bs[2];
+ if (cfront) {
+ fs[0] = cfront;
+ fs[1] = '\0';
+ }
+ bs[0] = cback;
+ bs[1] = '\0';
+ return trim_string(s, cfront ? fs : NULL, bs);
+ } else {
+ ep--;
+ }
+ }
+ if (ep < fp) {
+ /* We ate the string. */
+ s[0] = '\0';
+ return true;
+ }
+ }
+
+ ep[1] = '\0';
+ memmove(s, fp, ep-fp+2);
+ return ret;
+}
+
+/**
+ Check if a string is part of a list.
+**/
+
+bool in_list(const char *s, const char *list, bool casesensitive)
+{
+ char *tok = NULL;
+ bool ret = false;
+ TALLOC_CTX *frame;
+
+ if (!list) {
+ return false;
+ }
+
+ frame = talloc_stackframe();
+ while (next_token_talloc(frame, &list, &tok,LIST_SEP)) {
+ if (casesensitive) {
+ if (strcmp(tok,s) == 0) {
+ ret = true;
+ break;
+ }
+ } else {
+ if (strcasecmp_m(tok,s) == 0) {
+ ret = true;
+ break;
+ }
+ }
+ }
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/**
+ Truncate a string at a specified length.
+**/
+
+char *string_truncate(char *s, unsigned int length)
+{
+ if (s && strlen(s) > length)
+ s[length] = 0;
+ return s;
+}
+
+
+/***********************************************************************
+ Return the equivalent of doing strrchr 'n' times - always going
+ backwards.
+***********************************************************************/
+
+char *strnrchr_m(const char *s, char c, unsigned int n)
+{
+ smb_ucs2_t *ws = NULL;
+ char *s2 = NULL;
+ smb_ucs2_t *p;
+ char *ret;
+ size_t converted_size;
+
+ if (!push_ucs2_talloc(talloc_tos(), &ws, s, &converted_size)) {
+ /* Too hard to try and get right. */
+ return NULL;
+ }
+ p = strnrchr_w(ws, UCS2_CHAR(c), n);
+ if (!p) {
+ TALLOC_FREE(ws);
+ return NULL;
+ }
+ *p = 0;
+ if (!pull_ucs2_talloc(talloc_tos(), &s2, ws, &converted_size)) {
+ TALLOC_FREE(ws);
+ /* Too hard to try and get right. */
+ return NULL;
+ }
+ ret = discard_const_p(char, (s+strlen(s2)));
+ TALLOC_FREE(ws);
+ TALLOC_FREE(s2);
+ return ret;
+}
+
+static bool unix_strlower(const char *src, size_t srclen, char *dest, size_t destlen)
+{
+ size_t size;
+ smb_ucs2_t *buffer = NULL;
+ bool ret;
+
+ if (!convert_string_talloc(talloc_tos(), CH_UNIX, CH_UTF16LE, src, srclen,
+ (void **)(void *)&buffer, &size))
+ {
+ return false;
+ }
+ if (!strlower_w(buffer) && (dest == src)) {
+ TALLOC_FREE(buffer);
+ return true;
+ }
+ ret = convert_string(CH_UTF16LE, CH_UNIX, buffer, size, dest, destlen, &size);
+ TALLOC_FREE(buffer);
+ return ret;
+}
+
+#if 0 /* Alternate function that avoid talloc calls for ASCII and non ASCII */
+
+/**
+ Convert a string to lower case.
+**/
+_PUBLIC_ void strlower_m(char *s)
+{
+ char *d;
+ struct smb_iconv_handle *iconv_handle;
+
+ iconv_handle = get_iconv_handle();
+
+ d = s;
+
+ while (*s) {
+ size_t c_size, c_size2;
+ codepoint_t c = next_codepoint_handle(iconv_handle, s, &c_size);
+ c_size2 = push_codepoint_handle(iconv_handle, d, tolower_m(c));
+ if (c_size2 > c_size) {
+ DEBUG(0,("FATAL: codepoint 0x%x (0x%x) expanded from %d to %d bytes in strlower_m\n",
+ c, tolower_m(c), (int)c_size, (int)c_size2));
+ smb_panic("codepoint expansion in strlower_m\n");
+ }
+ s += c_size;
+ d += c_size2;
+ }
+ *d = 0;
+}
+
+#endif
+
+/**
+ Convert a string to lower case.
+**/
+
+bool strlower_m(char *s)
+{
+ size_t len;
+ int errno_save;
+ bool ret = false;
+
+ /* this is quite a common operation, so we want it to be
+ fast. We optimise for the ascii case, knowing that all our
+ supported multi-byte character sets are ascii-compatible
+ (ie. they match for the first 128 chars) */
+
+ while (*s && !(((unsigned char)s[0]) & 0x80)) {
+ *s = tolower_m((unsigned char)*s);
+ s++;
+ }
+
+ if (!*s)
+ return true;
+
+ /* I assume that lowercased string takes the same number of bytes
+ * as source string even in UTF-8 encoding. (VIV) */
+ len = strlen(s) + 1;
+ errno_save = errno;
+ errno = 0;
+ ret = unix_strlower(s,len,s,len);
+ /* Catch mb conversion errors that may not terminate. */
+ if (errno) {
+ s[len-1] = '\0';
+ }
+ errno = errno_save;
+ return ret;
+}
+
+static bool unix_strupper(const char *src, size_t srclen, char *dest, size_t destlen)
+{
+ size_t size;
+ smb_ucs2_t *buffer;
+ bool ret;
+
+ if (!push_ucs2_talloc(talloc_tos(), &buffer, src, &size)) {
+ return false;
+ }
+
+ if (!strupper_w(buffer) && (dest == src)) {
+ TALLOC_FREE(buffer);
+ return true;
+ }
+
+ ret = convert_string(CH_UTF16LE, CH_UNIX, buffer, size, dest, destlen, &size);
+ TALLOC_FREE(buffer);
+ return ret;
+}
+
+#if 0 /* Alternate function that avoid talloc calls for ASCII and non ASCII */
+
+/**
+ Convert a string to UPPER case.
+**/
+_PUBLIC_ void strupper_m(char *s)
+{
+ char *d;
+ struct smb_iconv_handle *iconv_handle;
+
+ iconv_handle = get_iconv_handle();
+
+ d = s;
+
+ while (*s) {
+ size_t c_size, c_size2;
+ codepoint_t c = next_codepoint_handle(iconv_handle, s, &c_size);
+ c_size2 = push_codepoint_handle(iconv_handle, d, toupper_m(c));
+ if (c_size2 > c_size) {
+ DEBUG(0,("FATAL: codepoint 0x%x (0x%x) expanded from %d to %d bytes in strupper_m\n",
+ c, toupper_m(c), (int)c_size, (int)c_size2));
+ smb_panic("codepoint expansion in strupper_m\n");
+ }
+ s += c_size;
+ d += c_size2;
+ }
+ *d = 0;
+}
+
+#endif
+
+/**
+ Convert a string to upper case.
+**/
+
+bool strupper_m(char *s)
+{
+ size_t len;
+ bool ret = false;
+
+ /* this is quite a common operation, so we want it to be
+ fast. We optimise for the ascii case, knowing that all our
+ supported multi-byte character sets are ascii-compatible
+ (ie. they match for the first 128 chars) */
+
+ while (*s && !(((unsigned char)s[0]) & 0x80)) {
+ *s = toupper_ascii_fast_table[(unsigned char)s[0]];
+ s++;
+ }
+
+ if (!*s)
+ return true;
+
+ /* I assume that uppercased string takes the same number of bytes
+ * as source string even in multibyte encoding. (VIV) */
+ len = strlen(s) + 1;
+ ret = unix_strupper(s,len,s,len);
+ /* Catch mb conversion errors that may not terminate. */
+ if (!ret) {
+ s[len-1] = '\0';
+ }
+ return ret;
+}
+
+/**
+ Just a typesafety wrapper for snprintf into a fstring.
+**/
+
+int fstr_sprintf(fstring s, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vsnprintf(s, FSTRING_LEN, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+/* read a SMB_BIG_UINT from a string */
+uint64_t STR_TO_SMB_BIG_UINT(const char *nptr, const char **entptr)
+{
+
+ uint64_t val = (uint64_t)-1;
+ const char *p = nptr;
+
+ if (!p) {
+ if (entptr) {
+ *entptr = p;
+ }
+ return val;
+ }
+
+ while (*p && isspace(*p))
+ p++;
+
+ sscanf(p,"%"SCNu64,&val);
+ if (entptr) {
+ while (*p && isdigit(*p))
+ p++;
+ *entptr = p;
+ }
+
+ return val;
+}
+
+/* Convert a size specification to a count of bytes. We accept the following
+ * suffixes:
+ * bytes if there is no suffix
+ * kK kibibytes
+ * mM mebibytes
+ * gG gibibytes
+ * tT tibibytes
+ * pP whatever the ISO name for petabytes is
+ *
+ * Returns 0 if the string can't be converted.
+ */
+uint64_t conv_str_size(const char * str)
+{
+ uint64_t lval;
+ char *end;
+ int error = 0;
+
+ if (str == NULL || *str == '\0') {
+ return 0;
+ }
+
+ lval = smb_strtoull(str, &end, 10, &error, SMB_STR_STANDARD);
+
+ if (error != 0) {
+ return 0;
+ }
+
+ if (*end == '\0') {
+ return lval;
+ }
+
+ if (strwicmp(end, "K") == 0) {
+ lval *= 1024ULL;
+ } else if (strwicmp(end, "M") == 0) {
+ lval *= (1024ULL * 1024ULL);
+ } else if (strwicmp(end, "G") == 0) {
+ lval *= (1024ULL * 1024ULL *
+ 1024ULL);
+ } else if (strwicmp(end, "T") == 0) {
+ lval *= (1024ULL * 1024ULL *
+ 1024ULL * 1024ULL);
+ } else if (strwicmp(end, "P") == 0) {
+ lval *= (1024ULL * 1024ULL *
+ 1024ULL * 1024ULL *
+ 1024ULL);
+ } else {
+ return 0;
+ }
+
+ return lval;
+}
+
+char *talloc_asprintf_strupper_m(TALLOC_CTX *t, const char *fmt, ...)
+{
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = talloc_vasprintf(t, fmt, ap);
+ va_end(ap);
+
+ if (ret == NULL) {
+ return NULL;
+ }
+ if (!strupper_m(ret)) {
+ TALLOC_FREE(ret);
+ return NULL;
+ }
+ return ret;
+}
+
+char *talloc_asprintf_strlower_m(TALLOC_CTX *t, const char *fmt, ...)
+{
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = talloc_vasprintf(t, fmt, ap);
+ va_end(ap);
+
+ if (ret == NULL) {
+ return NULL;
+ }
+ if (!strlower_m(ret)) {
+ TALLOC_FREE(ret);
+ return NULL;
+ }
+ return ret;
+}
+
+
+/********************************************************************
+ Check a string for any occurrences of a specified list of invalid
+ characters.
+********************************************************************/
+
+bool validate_net_name( const char *name,
+ const char *invalid_chars,
+ int max_len)
+{
+ int i;
+
+ if (!name) {
+ return false;
+ }
+
+ for ( i=0; i<max_len && name[i]; i++ ) {
+ /* fail if strchr_m() finds one of the invalid characters */
+ if ( name[i] && strchr_m( invalid_chars, name[i] ) ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/*******************************************************************
+ Add a shell escape character '\' to any character not in a known list
+ of characters. UNIX charset format.
+*******************************************************************/
+
+#define INCLUDE_LIST "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_/ \t.,"
+#define INSIDE_DQUOTE_LIST "$`\n\"\\"
+
+char *escape_shell_string(const char *src)
+{
+ size_t srclen = strlen(src);
+ char *ret = SMB_MALLOC_ARRAY(char, (srclen * 2) + 1);
+ char *dest = ret;
+ bool in_s_quote = false;
+ bool in_d_quote = false;
+ bool next_escaped = false;
+
+ if (!ret) {
+ return NULL;
+ }
+
+ while (*src) {
+ size_t c_size;
+ codepoint_t c = next_codepoint(src, &c_size);
+
+ if (c == INVALID_CODEPOINT) {
+ SAFE_FREE(ret);
+ return NULL;
+ }
+
+ if (c_size > 1) {
+ memcpy(dest, src, c_size);
+ src += c_size;
+ dest += c_size;
+ next_escaped = false;
+ continue;
+ }
+
+ /*
+ * Deal with backslash escaped state.
+ * This only lasts for one character.
+ */
+
+ if (next_escaped) {
+ *dest++ = *src++;
+ next_escaped = false;
+ continue;
+ }
+
+ /*
+ * Deal with single quote state. The
+ * only thing we care about is exiting
+ * this state.
+ */
+
+ if (in_s_quote) {
+ if (*src == '\'') {
+ in_s_quote = false;
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ /*
+ * Deal with double quote state. The most
+ * complex state. We must cope with \, meaning
+ * possibly escape next char (depending what it
+ * is), ", meaning exit this state, and possibly
+ * add an \ escape to any unprotected character
+ * (listed in INSIDE_DQUOTE_LIST).
+ */
+
+ if (in_d_quote) {
+ if (*src == '\\') {
+ /*
+ * Next character might be escaped.
+ * We have to peek. Inside double
+ * quotes only INSIDE_DQUOTE_LIST
+ * characters are escaped by a \.
+ */
+
+ char nextchar;
+
+ c = next_codepoint(&src[1], &c_size);
+ if (c == INVALID_CODEPOINT) {
+ SAFE_FREE(ret);
+ return NULL;
+ }
+ if (c_size > 1) {
+ /*
+ * Don't escape the next char.
+ * Just copy the \.
+ */
+ *dest++ = *src++;
+ continue;
+ }
+
+ nextchar = src[1];
+
+ if (nextchar && strchr(INSIDE_DQUOTE_LIST,
+ (int)nextchar)) {
+ next_escaped = true;
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ if (*src == '\"') {
+ /* Exit double quote state. */
+ in_d_quote = false;
+ *dest++ = *src++;
+ continue;
+ }
+
+ /*
+ * We know the character isn't \ or ",
+ * so escape it if it's any of the other
+ * possible unprotected characters.
+ */
+
+ if (strchr(INSIDE_DQUOTE_LIST, (int)*src)) {
+ *dest++ = '\\';
+ }
+ *dest++ = *src++;
+ continue;
+ }
+
+ /*
+ * From here to the end of the loop we're
+ * not in the single or double quote state.
+ */
+
+ if (*src == '\\') {
+ /* Next character must be escaped. */
+ next_escaped = true;
+ *dest++ = *src++;
+ continue;
+ }
+
+ if (*src == '\'') {
+ /* Go into single quote state. */
+ in_s_quote = true;
+ *dest++ = *src++;
+ continue;
+ }
+
+ if (*src == '\"') {
+ /* Go into double quote state. */
+ in_d_quote = true;
+ *dest++ = *src++;
+ continue;
+ }
+
+ /* Check if we need to escape the character. */
+
+ if (!strchr(INCLUDE_LIST, (int)*src)) {
+ *dest++ = '\\';
+ }
+ *dest++ = *src++;
+ }
+ *dest++ = '\0';
+ return ret;
+}
+
+/*
+ * This routine improves performance for operations temporarily acting on a
+ * full path. It is equivalent to the much more expensive
+ *
+ * talloc_asprintf(talloc_tos(), "%s/%s", dir, name)
+ *
+ * This actually does make a difference in metadata-heavy workloads (i.e. the
+ * "standard" client.txt nbench run.
+ */
+
+ssize_t full_path_tos(const char *dir, const char *name,
+ char *tmpbuf, size_t tmpbuf_len,
+ char **pdst, char **to_free)
+{
+ size_t dirlen, namelen, len;
+ char *dst;
+
+ dirlen = strlen(dir);
+ namelen = strlen(name);
+ len = dirlen + namelen + 1;
+
+ if (len < tmpbuf_len) {
+ dst = tmpbuf;
+ *to_free = NULL;
+ } else {
+ dst = talloc_array(talloc_tos(), char, len+1);
+ if (dst == NULL) {
+ return -1;
+ }
+ *to_free = dst;
+ }
+
+ memcpy(dst, dir, dirlen);
+ dst[dirlen] = '/';
+ memcpy(dst+dirlen+1, name, namelen+1);
+ *pdst = dst;
+ return len;
+}
diff --git a/source3/lib/util_tdb.c b/source3/lib/util_tdb.c
new file mode 100644
index 0000000..d85f676
--- /dev/null
+++ b/source3/lib/util_tdb.c
@@ -0,0 +1,449 @@
+/*
+ Unix SMB/CIFS implementation.
+ tdb utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Michael Adam 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "util_tdb.h"
+#include "cbuf.h"
+
+#undef malloc
+#undef realloc
+#undef calloc
+#undef strdup
+
+/****************************************************************************
+ Useful pair of routines for packing/unpacking data consisting of
+ integers and strings.
+****************************************************************************/
+
+static size_t tdb_pack_va(uint8_t *buf, int bufsize, const char *fmt, va_list ap)
+{
+ uint8_t bt;
+ uint16_t w;
+ uint32_t d;
+ int i;
+ void *p;
+ int len = 0;
+ char *s;
+ char c;
+ const char *fmt0 = fmt;
+ int bufsize0 = bufsize;
+ size_t to_write = 0;
+ while (*fmt) {
+ switch ((c = *fmt++)) {
+ case 'b': /* unsigned 8-bit integer */
+ len = 1;
+ bt = (uint8_t)va_arg(ap, int);
+ if (bufsize && bufsize >= len)
+ SSVAL(buf, 0, bt);
+ break;
+ case 'w': /* unsigned 16-bit integer */
+ len = 2;
+ w = (uint16_t)va_arg(ap, int);
+ if (bufsize && bufsize >= len)
+ SSVAL(buf, 0, w);
+ break;
+ case 'd': /* signed 32-bit integer (standard int in most systems) */
+ len = 4;
+ d = va_arg(ap, uint32_t);
+ if (bufsize && bufsize >= len)
+ SIVAL(buf, 0, d);
+ break;
+ case 'p': /* pointer */
+ len = 4;
+ p = va_arg(ap, void *);
+ d = p?1:0;
+ if (bufsize && bufsize >= len)
+ SIVAL(buf, 0, d);
+ break;
+ case 'P': /* null-terminated string */
+ case 'f': /* null-terminated string */
+ s = va_arg(ap,char *);
+ if (s == NULL) {
+ smb_panic("Invalid argument");
+ }
+ w = strlen(s);
+ len = w + 1;
+ if (bufsize && bufsize >= len)
+ memcpy(buf, s, len);
+ break;
+ case 'B': /* fixed-length string */
+ i = va_arg(ap, int);
+ s = va_arg(ap, char *);
+ len = 4+i;
+ if (bufsize && bufsize >= len) {
+ SIVAL(buf, 0, i);
+ if (s != NULL) {
+ memcpy(buf+4, s, i);
+ }
+ }
+ break;
+ default:
+ DEBUG(0,("Unknown tdb_pack format %c in %s\n",
+ c, fmt));
+ len = 0;
+ break;
+ }
+
+ to_write += len;
+ if (bufsize > 0) {
+ bufsize -= len;
+ buf += len;
+ }
+ if (bufsize < 0)
+ bufsize = 0;
+ }
+
+ DEBUG(18,("tdb_pack_va(%s, %d) -> %d\n",
+ fmt0, bufsize0, (int)to_write));
+
+ return to_write;
+}
+
+size_t tdb_pack(uint8_t *buf, int bufsize, const char *fmt, ...)
+{
+ va_list ap;
+ size_t result;
+
+ va_start(ap, fmt);
+ result = tdb_pack_va(buf, bufsize, fmt, ap);
+ va_end(ap);
+ return result;
+}
+
+/****************************************************************************
+ Useful pair of routines for packing/unpacking data consisting of
+ integers and strings.
+****************************************************************************/
+
+int tdb_unpack(const uint8_t *buf, int in_bufsize, const char *fmt, ...)
+{
+ va_list ap;
+ uint8_t *bt;
+ uint16_t *w;
+ uint32_t *d;
+ size_t bufsize = in_bufsize;
+ size_t len;
+ uint32_t *i;
+ void **p;
+ char *s, **b, **ps;
+ char c;
+ const uint8_t *buf0 = buf;
+ const char *fmt0 = fmt;
+
+ va_start(ap, fmt);
+
+ while (*fmt) {
+ switch ((c=*fmt++)) {
+ case 'b': /* unsigned 8-bit integer */
+ len = 1;
+ bt = va_arg(ap, uint8_t *);
+ if (bufsize < len)
+ goto no_space;
+ *bt = SVAL(buf, 0);
+ break;
+ case 'w': /* unsigned 16-bit integer */
+ len = 2;
+ w = va_arg(ap, uint16_t *);
+ if (bufsize < len)
+ goto no_space;
+ *w = SVAL(buf, 0);
+ break;
+ case 'd': /* unsigned 32-bit integer (standard int in most systems) */
+ len = 4;
+ d = va_arg(ap, uint32_t *);
+ if (bufsize < len)
+ goto no_space;
+ *d = IVAL(buf, 0);
+ break;
+ case 'p': /* pointer */
+ len = 4;
+ p = va_arg(ap, void **);
+ if (bufsize < len)
+ goto no_space;
+ /*
+ * This isn't a real pointer - only a token (1 or 0)
+ * to mark the fact a pointer is present.
+ */
+
+ *p = (void *)(IVAL(buf, 0) ? (void *)1 : NULL);
+ break;
+ case 'P': /* null-terminated string */
+ /* Return malloc'ed string. */
+ ps = va_arg(ap,char **);
+ len = strnlen((const char *)buf, bufsize) + 1;
+ if (bufsize < len)
+ goto no_space;
+ if (ps != NULL) {
+ *ps = SMB_STRDUP((const char *)buf);
+ if (*ps == NULL) {
+ goto no_space;
+ }
+ }
+ break;
+ case 'f': /* null-terminated string */
+ s = va_arg(ap,char *);
+ len = strnlen((const char *)buf, bufsize) + 1;
+ if (bufsize < len || len > sizeof(fstring))
+ goto no_space;
+ if (s != NULL) {
+ memcpy(s, buf, len);
+ }
+ break;
+ case 'B': /* fixed-length string */
+ i = va_arg(ap, uint32_t *);
+ b = va_arg(ap, char **);
+ len = 4;
+ if (bufsize < len)
+ goto no_space;
+ *i = IVAL(buf, 0);
+ if (! *i) {
+ *b = NULL;
+ break;
+ }
+ len += *i;
+ if (len < *i) {
+ goto no_space;
+ }
+ if (bufsize < len)
+ goto no_space;
+ if (b != NULL) {
+ *b = (char *)SMB_MALLOC(*i);
+ if (! *b)
+ goto no_space;
+ memcpy(*b, buf+4, *i);
+ }
+ break;
+ default:
+ DEBUG(0,("Unknown tdb_unpack format %c in %s\n",
+ c, fmt));
+
+ len = 0;
+ break;
+ }
+
+ buf += len;
+ bufsize -= len;
+ }
+
+ va_end(ap);
+
+ DEBUG(18,("tdb_unpack(%s, %d) -> %d\n",
+ fmt0, in_bufsize, (int)PTR_DIFF(buf, buf0)));
+
+ return PTR_DIFF(buf, buf0);
+
+ no_space:
+ va_end(ap);
+ return -1;
+}
+
+
+/****************************************************************************
+ Log tdb messages via DEBUG().
+****************************************************************************/
+
+static void tdb_log(TDB_CONTEXT *tdb, enum tdb_debug_level level,
+ const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+
+static void tdb_log(TDB_CONTEXT *tdb, enum tdb_debug_level level, const char *format, ...)
+{
+ va_list ap;
+ char *ptr = NULL;
+ int ret;
+
+ va_start(ap, format);
+ ret = vasprintf(&ptr, format, ap);
+ va_end(ap);
+
+ if ((ret == -1) || !*ptr)
+ return;
+
+ DEBUG((int)level, ("tdb(%s): %s", tdb_name(tdb) ? tdb_name(tdb) : "unnamed", ptr));
+ SAFE_FREE(ptr);
+}
+
+/****************************************************************************
+ Like tdb_open() but also setup a logging function that redirects to
+ the samba DEBUG() system.
+****************************************************************************/
+
+TDB_CONTEXT *tdb_open_log(const char *name, int hash_size, int tdb_flags,
+ int open_flags, mode_t mode)
+{
+ TDB_CONTEXT *tdb;
+ struct tdb_logging_context log_ctx = { .log_fn = tdb_log };
+
+ if (!lp_use_mmap())
+ tdb_flags |= TDB_NOMMAP;
+
+ if ((hash_size == 0) && (name != NULL)) {
+ const char *base = strrchr_m(name, '/');
+ if (base != NULL) {
+ base += 1;
+ }
+ else {
+ base = name;
+ }
+ hash_size = lp_parm_int(-1, "tdb_hashsize", base, 0);
+ }
+
+ tdb = tdb_open_ex(name, hash_size, tdb_flags,
+ open_flags, mode, &log_ctx, NULL);
+ if (!tdb)
+ return NULL;
+
+ return tdb;
+}
+
+int tdb_data_cmp(TDB_DATA t1, TDB_DATA t2)
+{
+ int ret;
+ if (t1.dptr == NULL && t2.dptr != NULL) {
+ return -1;
+ }
+ if (t1.dptr != NULL && t2.dptr == NULL) {
+ return 1;
+ }
+ if (t1.dptr == t2.dptr) {
+ return t1.dsize - t2.dsize;
+ }
+ ret = memcmp(t1.dptr, t2.dptr, MIN(t1.dsize, t2.dsize));
+ if (ret == 0) {
+ return t1.dsize - t2.dsize;
+ }
+ return ret;
+}
+
+char *tdb_data_string(TALLOC_CTX *mem_ctx, TDB_DATA d)
+{
+ int len;
+ char *ret = NULL;
+ cbuf *ost = cbuf_new(mem_ctx);
+
+ if (ost == NULL) {
+ return NULL;
+ }
+
+ len = cbuf_printf(ost, "%zu:", d.dsize);
+ if (len == -1) {
+ goto done;
+ }
+
+ if (d.dptr == NULL) {
+ len = cbuf_puts(ost, "<NULL>", -1);
+ } else {
+ len = cbuf_print_quoted(ost, (const char*)d.dptr, d.dsize);
+ }
+ if (len == -1) {
+ goto done;
+ }
+
+ cbuf_swapptr(ost, &ret, 0);
+ talloc_steal(mem_ctx, ret);
+
+done:
+ talloc_free(ost);
+ return ret;
+}
+
+char *tdb_data_dbg(TDB_DATA d)
+{
+ return hex_encode_talloc(talloc_tos(), d.dptr, d.dsize);
+}
+
+static sig_atomic_t gotalarm;
+
+/***************************************************************
+ Signal function to tell us we timed out.
+****************************************************************/
+
+static void gotalarm_sig(int signum)
+{
+ gotalarm = 1;
+}
+
+/****************************************************************************
+ Lock a chain with timeout (in seconds).
+****************************************************************************/
+
+static int tdb_chainlock_with_timeout_internal( TDB_CONTEXT *tdb, TDB_DATA key, unsigned int timeout, int rw_type)
+{
+ /* Allow tdb_chainlock to be interrupted by an alarm. */
+ int ret;
+ gotalarm = 0;
+
+ if (timeout) {
+ CatchSignal(SIGALRM, gotalarm_sig);
+ tdb_setalarm_sigptr(tdb, &gotalarm);
+ alarm(timeout);
+ }
+
+ if (rw_type == F_RDLCK)
+ ret = tdb_chainlock_read(tdb, key);
+ else
+ ret = tdb_chainlock(tdb, key);
+
+ if (timeout) {
+ alarm(0);
+ tdb_setalarm_sigptr(tdb, NULL);
+ CatchSignal(SIGALRM, SIG_IGN);
+ if (gotalarm && (ret != 0)) {
+ DEBUG(0,("tdb_chainlock_with_timeout_internal: alarm (%u) timed out for key %s in tdb %s\n",
+ timeout, key.dptr, tdb_name(tdb)));
+ /* TODO: If we time out waiting for a lock, it might
+ * be nice to use F_GETLK to get the pid of the
+ * process currently holding the lock and print that
+ * as part of the debugging message. -- mbp */
+ return -1;
+ }
+ }
+
+ return ret == 0 ? 0 : -1;
+}
+
+/****************************************************************************
+ Write lock a chain. Return non-zero if timeout or lock failed.
+****************************************************************************/
+
+int tdb_chainlock_with_timeout( TDB_CONTEXT *tdb, TDB_DATA key, unsigned int timeout)
+{
+ return tdb_chainlock_with_timeout_internal(tdb, key, timeout, F_WRLCK);
+}
+
+int tdb_lock_bystring_with_timeout(TDB_CONTEXT *tdb, const char *keyval,
+ int timeout)
+{
+ TDB_DATA key = string_term_tdb_data(keyval);
+
+ return tdb_chainlock_with_timeout(tdb, key, timeout);
+}
+
+/****************************************************************************
+ Read lock a chain by string. Return non-zero if timeout or lock failed.
+****************************************************************************/
+
+int tdb_read_lock_bystring_with_timeout(TDB_CONTEXT *tdb, const char *keyval, unsigned int timeout)
+{
+ TDB_DATA key = string_term_tdb_data(keyval);
+
+ return tdb_chainlock_with_timeout_internal(tdb, key, timeout, F_RDLCK);
+}
diff --git a/source3/lib/util_transfer_file.c b/source3/lib/util_transfer_file.c
new file mode 100644
index 0000000..5653906
--- /dev/null
+++ b/source3/lib/util_transfer_file.c
@@ -0,0 +1,119 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utility functions to transfer files.
+ *
+ * Copyright (C) Jeremy Allison 2001-2002
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <includes.h>
+#include "transfer_file.h"
+#include "lib/util/sys_rw.h"
+
+/****************************************************************************
+ Transfer some data between two fd's.
+****************************************************************************/
+
+#ifndef TRANSFER_BUF_SIZE
+#define TRANSFER_BUF_SIZE 65536
+#endif
+
+
+ssize_t transfer_file_internal(void *in_file,
+ void *out_file,
+ size_t n,
+ ssize_t (*pread_fn)(void *, void *, size_t, off_t),
+ ssize_t (*pwrite_fn)(void *, const void *, size_t, off_t))
+{
+ char *buf;
+ size_t total = 0;
+ ssize_t read_ret;
+ ssize_t write_ret;
+ size_t num_to_read_thistime;
+ size_t num_written = 0;
+ off_t offset = 0;
+
+ if (n == 0) {
+ return 0;
+ }
+
+ if ((buf = SMB_MALLOC_ARRAY(char, TRANSFER_BUF_SIZE)) == NULL) {
+ return -1;
+ }
+
+ do {
+ num_to_read_thistime = MIN((n - total), TRANSFER_BUF_SIZE);
+
+ read_ret = (*pread_fn)(in_file, buf, num_to_read_thistime, offset);
+ if (read_ret == -1) {
+ DEBUG(0,("transfer_file_internal: read failure. "
+ "Error = %s\n", strerror(errno) ));
+ SAFE_FREE(buf);
+ return -1;
+ }
+ if (read_ret == 0) {
+ break;
+ }
+
+ num_written = 0;
+
+ while (num_written < read_ret) {
+ write_ret = (*pwrite_fn)(out_file, buf + num_written,
+ read_ret - num_written,
+ offset + num_written);
+
+ if (write_ret == -1) {
+ DEBUG(0,("transfer_file_internal: "
+ "write failure. Error = %s\n",
+ strerror(errno) ));
+ SAFE_FREE(buf);
+ return -1;
+ }
+ if (write_ret == 0) {
+ return (ssize_t)total;
+ }
+
+ num_written += (size_t)write_ret;
+ }
+
+ total += (size_t)read_ret;
+ offset += (off_t)read_ret;
+ } while (total < n);
+
+ SAFE_FREE(buf);
+ return (ssize_t)total;
+}
+
+static ssize_t sys_pread_fn(void *file, void *buf, size_t len, off_t offset)
+{
+ int *fd = (int *)file;
+
+ return sys_pread(*fd, buf, len, offset);
+}
+
+static ssize_t sys_pwrite_fn(void *file, const void *buf, size_t len, off_t offset)
+{
+ int *fd = (int *)file;
+
+ return sys_pwrite(*fd, buf, len, offset);
+}
+
+off_t transfer_file(int infd, int outfd, off_t n)
+{
+ return (off_t)transfer_file_internal(&infd, &outfd, (size_t)n,
+ sys_pread_fn, sys_pwrite_fn);
+}
diff --git a/source3/lib/util_tsock.c b/source3/lib/util_tsock.c
new file mode 100644
index 0000000..6432ce4
--- /dev/null
+++ b/source3/lib/util_tsock.c
@@ -0,0 +1,149 @@
+/*
+ Unix SMB/CIFS implementation.
+ Utilities around tsocket
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/network.h"
+#include <tevent.h>
+#include "lib/util_tsock.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_unix.h"
+
+struct tstream_read_packet_state {
+ struct tevent_context *ev;
+ struct tstream_context *stream;
+ ssize_t (*more)(uint8_t *buf, size_t buflen, void *private_data);
+ void *private_data;
+ uint8_t *buf;
+ struct iovec iov;
+};
+
+static void tstream_read_packet_done(struct tevent_req *subreq);
+
+struct tevent_req *tstream_read_packet_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tstream_context *stream,
+ size_t initial,
+ ssize_t (*more)(uint8_t *buf,
+ size_t buflen,
+ void *private_data),
+ void *private_data)
+{
+ struct tevent_req *req, *subreq;
+ struct tstream_read_packet_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct tstream_read_packet_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->buf = talloc_array(state, uint8_t, initial);
+ if (tevent_req_nomem(state->buf, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->iov.iov_base = (void *)state->buf;
+ state->iov.iov_len = initial;
+
+ state->ev = ev;
+ state->stream = stream;
+ state->more = more;
+ state->private_data = private_data;
+
+ subreq = tstream_readv_send(state, ev, stream, &state->iov, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tstream_read_packet_done, req);
+
+ return req;
+}
+
+static void tstream_read_packet_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tstream_read_packet_state *state = tevent_req_data(
+ req, struct tstream_read_packet_state);
+ int ret, err;
+ size_t total;
+ ssize_t more;
+ uint8_t *tmp;
+
+ ret = tstream_readv_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (ret == 0) {
+ err = EPIPE;
+ }
+ if (ret <= 0) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ if (state->more == NULL) {
+ /* Nobody to ask, this is a async read_data */
+ tevent_req_done(req);
+ return;
+ }
+ total = talloc_array_length(state->buf);
+
+ more = state->more(state->buf, total, state->private_data);
+ if (more == -1) {
+ /* We got an invalid packet, tell the caller */
+ tevent_req_error(req, EIO);
+ return;
+ }
+ if (more == 0) {
+ /* We're done, full packet received */
+ tevent_req_done(req);
+ return;
+ }
+
+ if (total + more < total) {
+ tevent_req_error(req, EMSGSIZE);
+ return;
+ }
+
+ tmp = talloc_realloc(state, state->buf, uint8_t, total+more);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ state->buf = tmp;
+
+ state->iov.iov_base = (void *)(state->buf + total);
+ state->iov.iov_len = more;
+
+ subreq = tstream_readv_send(state, state->ev, state->stream,
+ &state->iov, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, tstream_read_packet_done, req);
+}
+
+ssize_t tstream_read_packet_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **pbuf, int *perrno)
+{
+ struct tstream_read_packet_state *state =
+ tevent_req_data(req, struct tstream_read_packet_state);
+
+ if (tevent_req_is_unix_error(req, perrno)) {
+ return -1;
+ }
+ *pbuf = talloc_move(mem_ctx, &state->buf);
+ return talloc_array_length(*pbuf);
+}
diff --git a/source3/lib/util_tsock.h b/source3/lib/util_tsock.h
new file mode 100644
index 0000000..de4381a
--- /dev/null
+++ b/source3/lib/util_tsock.h
@@ -0,0 +1,38 @@
+/*
+ Unix SMB/CIFS implementation.
+ Utilities around tsocket
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __UTIL_TSOCK_H__
+#define __UTIL_TSOCK_H__
+
+#include "replace.h"
+#include <tevent.h>
+
+struct tstream_context;
+struct tevent_req *tstream_read_packet_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tstream_context *stream,
+ size_t initial,
+ ssize_t (*more)(uint8_t *buf,
+ size_t buflen,
+ void *private_data),
+ void *private_data);
+ssize_t tstream_read_packet_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **pbuf, int *perrno);
+
+#endif
diff --git a/source3/lib/util_unixsids.c b/source3/lib/util_unixsids.c
new file mode 100644
index 0000000..387232c
--- /dev/null
+++ b/source3/lib/util_unixsids.c
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ Translate unix-defined names to SIDs and vice versa
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "util_unixsids.h"
+#include "../libcli/security/security.h"
+
+bool sid_check_is_unix_users(const struct dom_sid *sid)
+{
+ return dom_sid_equal(sid, &global_sid_Unix_Users);
+}
+
+bool sid_check_is_in_unix_users(const struct dom_sid *sid)
+{
+ struct dom_sid dom_sid;
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, NULL);
+
+ return sid_check_is_unix_users(&dom_sid);
+}
+
+void uid_to_unix_users_sid(uid_t uid, struct dom_sid *sid)
+{
+ /*
+ * This can never fail, we know that global_sid_Unix_Users is
+ * short enough for a domain sid.
+ */
+ sid_compose(sid, &global_sid_Unix_Users, uid);
+}
+
+void gid_to_unix_groups_sid(gid_t gid, struct dom_sid *sid)
+{
+ /*
+ * This can never fail, we know that global_sid_Unix_Groups is
+ * short enough for a domain sid.
+ */
+ sid_compose(sid, &global_sid_Unix_Groups, gid);
+}
+
+const char *unix_users_domain_name(void)
+{
+ return "Unix User";
+}
+
+bool sid_check_is_unix_groups(const struct dom_sid *sid)
+{
+ return dom_sid_equal(sid, &global_sid_Unix_Groups);
+}
+
+bool sid_check_is_in_unix_groups(const struct dom_sid *sid)
+{
+ struct dom_sid dom_sid;
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, NULL);
+
+ return sid_check_is_unix_groups(&dom_sid);
+}
+
+const char *unix_groups_domain_name(void)
+{
+ return "Unix Group";
+}
diff --git a/source3/lib/util_unixsids.h b/source3/lib/util_unixsids.h
new file mode 100644
index 0000000..b90a746
--- /dev/null
+++ b/source3/lib/util_unixsids.h
@@ -0,0 +1,36 @@
+/*
+ Unix SMB/CIFS implementation.
+ Translate unix-defined names to SIDs and vice versa
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __UTIL_UNIXSIDS_H__
+#define __UTIL_UNIXSIDS_H__
+
+#include "replace.h"
+
+struct dom_sid;
+
+bool sid_check_is_unix_users(const struct dom_sid *sid);
+bool sid_check_is_in_unix_users(const struct dom_sid *sid);
+void uid_to_unix_users_sid(uid_t uid, struct dom_sid *sid);
+void gid_to_unix_groups_sid(gid_t gid, struct dom_sid *sid);
+const char *unix_users_domain_name(void);
+bool sid_check_is_unix_groups(const struct dom_sid *sid);
+bool sid_check_is_in_unix_groups(const struct dom_sid *sid);
+const char *unix_groups_domain_name(void);
+
+#endif
diff --git a/source3/lib/util_wellknown.c b/source3/lib/util_wellknown.c
new file mode 100644
index 0000000..8cb7a36
--- /dev/null
+++ b/source3/lib/util_wellknown.c
@@ -0,0 +1,192 @@
+/*
+ Unix SMB/CIFS implementation.
+ Lookup routines for well-known SIDs
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Caseson Leighton 1998-1999
+ Copyright (C) Jeremy Allison 1999
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../libcli/security/security.h"
+
+struct rid_name_map {
+ uint32_t rid;
+ const char *name;
+};
+
+struct sid_name_map_info
+{
+ const struct dom_sid *sid;
+ const char *name;
+ const struct rid_name_map *known_users;
+};
+
+static const struct rid_name_map everyone_users[] = {
+ { 0, "Everyone" },
+ { 0, NULL}};
+
+static const struct rid_name_map local_authority_users[] = {
+ { 0, "Local" },
+ { 1, "Console Logon" },
+ { 0, NULL}};
+
+static const struct rid_name_map creator_owner_users[] = {
+ { 0, "Creator Owner" },
+ { 1, "Creator Group" },
+ { 2, "Creator Owner Server" },
+ { 3, "Creator Group Server" },
+ { 4, "Owner Rights" },
+ { 0, NULL}};
+
+static const struct rid_name_map nt_authority_users[] = {
+ { 1, "Dialup" },
+ { 2, "Network"},
+ { 3, "Batch"},
+ { 4, "Interactive"},
+ { 6, "Service"},
+ { 7, "Anonymous Logon"},
+ { 8, "Proxy"},
+ { 9, "Enterprise Domain Controllers"},
+ { 10, "Self"},
+ { 11, "Authenticated Users"},
+ { 12, "Restricted"},
+ { 13, "Terminal Server User"},
+ { 14, "Remote Interactive Logon"},
+ { 15, "This Organization"},
+ { 17, "IUSR"},
+ { 18, "SYSTEM"},
+ { 19, "Local Service"},
+ { 20, "Network Service"},
+ { 0, NULL}};
+
+static struct sid_name_map_info special_domains[] = {
+ { &global_sid_World_Domain, "", everyone_users },
+ { &global_sid_Local_Authority, "", local_authority_users },
+ { &global_sid_Creator_Owner_Domain, "", creator_owner_users },
+ { &global_sid_NT_Authority, "NT Authority", nt_authority_users },
+ { NULL, NULL, NULL }};
+
+bool sid_check_is_wellknown_domain(const struct dom_sid *sid, const char **name)
+{
+ int i;
+
+ for (i=0; special_domains[i].sid != NULL; i++) {
+ if (dom_sid_equal(sid, special_domains[i].sid)) {
+ if (name != NULL) {
+ *name = special_domains[i].name;
+ }
+ return True;
+ }
+ }
+ return False;
+}
+
+bool sid_check_is_in_wellknown_domain(const struct dom_sid *sid)
+{
+ struct dom_sid dom_sid;
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, NULL);
+
+ return sid_check_is_wellknown_domain(&dom_sid, NULL);
+}
+
+/**************************************************************************
+ Looks up a known username from one of the known domains.
+***************************************************************************/
+
+bool lookup_wellknown_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ const char **domain, const char **name)
+{
+ int i;
+ struct dom_sid dom_sid;
+ uint32_t rid;
+ const struct rid_name_map *users = NULL;
+ struct dom_sid_buf buf;
+
+ sid_copy(&dom_sid, sid);
+ if (!sid_split_rid(&dom_sid, &rid)) {
+ DEBUG(2, ("Could not split rid from SID\n"));
+ return False;
+ }
+
+ for (i=0; special_domains[i].sid != NULL; i++) {
+ if (dom_sid_equal(&dom_sid, special_domains[i].sid)) {
+ *domain = talloc_strdup(mem_ctx,
+ special_domains[i].name);
+ users = special_domains[i].known_users;
+ break;
+ }
+ }
+
+ if (users == NULL) {
+ DEBUG(10, ("SID %s is no special sid\n",
+ dom_sid_str_buf(sid, &buf)));
+ return False;
+ }
+
+ for (i=0; users[i].name != NULL; i++) {
+ if (rid == users[i].rid) {
+ *name = talloc_strdup(mem_ctx, users[i].name);
+ return True;
+ }
+ }
+
+ DEBUG(10, ("RID of special SID %s not found\n",
+ dom_sid_str_buf(sid, &buf)));
+
+ return False;
+}
+
+/**************************************************************************
+ Try and map a name to one of the well known SIDs.
+***************************************************************************/
+
+bool lookup_wellknown_name(TALLOC_CTX *mem_ctx, const char *name,
+ struct dom_sid *sid, const char **pdomain)
+{
+ int i, j;
+ const char *domain = *pdomain;
+
+ DEBUG(10,("map_name_to_wellknown_sid: looking up %s\\%s\n", domain, name));
+
+ for (i=0; special_domains[i].sid != NULL; i++) {
+ const struct rid_name_map *users =
+ special_domains[i].known_users;
+
+ if (domain[0] != '\0') {
+ if (!strequal(domain, special_domains[i].name)) {
+ continue;
+ }
+ }
+
+ if (users == NULL)
+ continue;
+
+ for (j=0; users[j].name != NULL; j++) {
+ if ( strequal(users[j].name, name) ) {
+ sid_compose(sid, special_domains[i].sid,
+ users[j].rid);
+ *pdomain = talloc_strdup(
+ mem_ctx, special_domains[i].name);
+ return True;
+ }
+ }
+ }
+
+ return False;
+}
diff --git a/source3/lib/version.c b/source3/lib/version.c
new file mode 100644
index 0000000..28fcd1b
--- /dev/null
+++ b/source3/lib/version.c
@@ -0,0 +1,32 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba Version functions
+
+ Copyright (C) Stefan Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "version.h"
+
+const char *samba_version_string(void)
+{
+ return SAMBA_VERSION_STRING;
+}
+
+const char *samba_copyright_string(void)
+{
+ return SAMBA_COPYRIGHT_STRING;
+}
diff --git a/source3/lib/version_test.c b/source3/lib/version_test.c
new file mode 100644
index 0000000..880cfeb
--- /dev/null
+++ b/source3/lib/version_test.c
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * version_test - test program for samba_version_strion()
+ * Copyright (C) Michael Adam 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+int main(void)
+{
+ printf("%s\n", samba_version_string());
+ return 0;
+}
diff --git a/source3/lib/winbind_util.c b/source3/lib/winbind_util.c
new file mode 100644
index 0000000..7e3f8ab
--- /dev/null
+++ b/source3/lib/winbind_util.c
@@ -0,0 +1,403 @@
+/*
+ Unix SMB/CIFS implementation.
+ Winbind Utility functions
+
+ Copyright (C) Gerald (Jerry) Carter 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/util_pw.h"
+#include "nsswitch/libwbclient/wbclient.h"
+
+#include "lib/winbind_util.h"
+
+#if defined(WITH_WINBIND)
+
+struct passwd * winbind_getpwnam(const char * name)
+{
+ wbcErr result;
+ struct passwd * tmp_pwd = NULL;
+ struct passwd * pwd = NULL;
+
+ result = wbcGetpwnam(name, &tmp_pwd);
+ if (result != WBC_ERR_SUCCESS)
+ return pwd;
+
+ pwd = tcopy_passwd(talloc_tos(), tmp_pwd);
+
+ wbcFreeMemory(tmp_pwd);
+
+ return pwd;
+}
+
+struct passwd * winbind_getpwsid(const struct dom_sid *sid)
+{
+ wbcErr result;
+ struct passwd * tmp_pwd = NULL;
+ struct passwd * pwd = NULL;
+ struct wbcDomainSid dom_sid;
+
+ memcpy(&dom_sid, sid, sizeof(dom_sid));
+
+ result = wbcGetpwsid(&dom_sid, &tmp_pwd);
+ if (result != WBC_ERR_SUCCESS)
+ return pwd;
+
+ pwd = tcopy_passwd(talloc_tos(), tmp_pwd);
+
+ wbcFreeMemory(tmp_pwd);
+
+ return pwd;
+}
+
+/* Call winbindd to convert a name to a sid */
+
+bool winbind_lookup_name(const char *dom_name, const char *name, struct dom_sid *sid,
+ enum lsa_SidType *name_type)
+{
+ struct wbcDomainSid dom_sid;
+ wbcErr result;
+ enum wbcSidType type;
+
+ result = wbcLookupName(dom_name, name, &dom_sid, &type);
+ if (result != WBC_ERR_SUCCESS)
+ return false;
+
+ memcpy(sid, &dom_sid, sizeof(struct dom_sid));
+ *name_type = (enum lsa_SidType)type;
+
+ return true;
+}
+
+/* Call winbindd to convert sid to name */
+
+bool winbind_lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ const char **domain, const char **name,
+ enum lsa_SidType *name_type)
+{
+ struct wbcDomainSid dom_sid;
+ wbcErr result;
+ enum wbcSidType type;
+ char *domain_name = NULL;
+ char *account_name = NULL;
+ struct dom_sid_buf buf;
+
+ memcpy(&dom_sid, sid, sizeof(dom_sid));
+
+ result = wbcLookupSid(&dom_sid, &domain_name, &account_name, &type);
+ if (result != WBC_ERR_SUCCESS)
+ return false;
+
+ /* Copy out result */
+
+ if (domain) {
+ *domain = talloc_strdup(mem_ctx, domain_name);
+ }
+ if (name) {
+ *name = talloc_strdup(mem_ctx, account_name);
+ }
+ *name_type = (enum lsa_SidType)type;
+
+ DEBUG(10, ("winbind_lookup_sid: SUCCESS: SID %s -> %s %s\n",
+ dom_sid_str_buf(sid, &buf), domain_name, account_name));
+
+ wbcFreeMemory(domain_name);
+ wbcFreeMemory(account_name);
+
+ if ((domain && !*domain) || (name && !*name)) {
+ DEBUG(0,("winbind_lookup_sid: talloc() failed!\n"));
+ return false;
+ }
+
+
+ return true;
+}
+
+/* Ping winbindd to see it is alive */
+
+bool winbind_ping(void)
+{
+ wbcErr result = wbcPing();
+
+ return (result == WBC_ERR_SUCCESS);
+}
+
+/* Call winbindd to convert SID to uid */
+
+bool winbind_sid_to_uid(uid_t *puid, const struct dom_sid *sid)
+{
+ struct wbcDomainSid dom_sid;
+ wbcErr result;
+
+ memcpy(&dom_sid, sid, sizeof(dom_sid));
+
+ result = wbcSidToUid(&dom_sid, puid);
+
+ return (result == WBC_ERR_SUCCESS);
+}
+
+/* Call winbindd to convert SID to gid */
+
+bool winbind_sid_to_gid(gid_t *pgid, const struct dom_sid *sid)
+{
+ struct wbcDomainSid dom_sid;
+ wbcErr result;
+
+ memcpy(&dom_sid, sid, sizeof(dom_sid));
+
+ result = wbcSidToGid(&dom_sid, pgid);
+
+ return (result == WBC_ERR_SUCCESS);
+}
+
+bool winbind_xid_to_sid(struct dom_sid *sid, const struct unixid *xid)
+{
+ struct wbcUnixId wbc_xid;
+ struct wbcDomainSid dom_sid;
+ wbcErr result;
+
+ switch (xid->type) {
+ case ID_TYPE_UID:
+ wbc_xid = (struct wbcUnixId) {
+ .type = WBC_ID_TYPE_UID, .id.uid = xid->id
+ };
+ break;
+ case ID_TYPE_GID:
+ wbc_xid = (struct wbcUnixId) {
+ .type = WBC_ID_TYPE_GID, .id.gid = xid->id
+ };
+ break;
+ default:
+ return false;
+ }
+
+ result = wbcUnixIdsToSids(&wbc_xid, 1, &dom_sid);
+ if (result != WBC_ERR_SUCCESS) {
+ return false;
+ }
+
+ memcpy(sid, &dom_sid, sizeof(struct dom_sid));
+ return true;
+}
+
+/* Check for a trusted domain */
+
+wbcErr wb_is_trusted_domain(const char *domain)
+{
+ wbcErr result;
+ struct wbcDomainInfo *info = NULL;
+
+ result = wbcDomainInfo(domain, &info);
+
+ if (WBC_ERROR_IS_OK(result)) {
+ wbcFreeMemory(info);
+ }
+
+ return result;
+}
+
+/* Lookup a set of rids in a given domain */
+
+bool winbind_lookup_rids(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ int num_rids, uint32_t *rids,
+ const char **domain_name,
+ const char ***names, enum lsa_SidType **types)
+{
+ const char *dom_name = NULL;
+ const char **namelist = NULL;
+ enum wbcSidType *name_types = NULL;
+ struct wbcDomainSid dom_sid;
+ wbcErr ret;
+ int i;
+
+ memcpy(&dom_sid, domain_sid, sizeof(struct wbcDomainSid));
+
+ ret = wbcLookupRids(&dom_sid, num_rids, rids,
+ &dom_name, &namelist, &name_types);
+ if (ret != WBC_ERR_SUCCESS) {
+ return false;
+ }
+
+ *domain_name = talloc_strdup(mem_ctx, dom_name);
+ *names = talloc_array(mem_ctx, const char*, num_rids);
+ *types = talloc_array(mem_ctx, enum lsa_SidType, num_rids);
+
+ for(i=0; i<num_rids; i++) {
+ (*names)[i] = talloc_strdup(*names, namelist[i]);
+ (*types)[i] = (enum lsa_SidType)name_types[i];
+ }
+
+ wbcFreeMemory(discard_const_p(char, dom_name));
+ wbcFreeMemory(namelist);
+ wbcFreeMemory(name_types);
+
+ return true;
+}
+
+/* Ask Winbind to allocate a new uid for us */
+
+bool winbind_allocate_uid(uid_t *uid)
+{
+ wbcErr ret;
+
+ ret = wbcAllocateUid(uid);
+
+ return (ret == WBC_ERR_SUCCESS);
+}
+
+/* Ask Winbind to allocate a new gid for us */
+
+bool winbind_allocate_gid(gid_t *gid)
+{
+ wbcErr ret;
+
+ ret = wbcAllocateGid(gid);
+
+ return (ret == WBC_ERR_SUCCESS);
+}
+
+bool winbind_lookup_usersids(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *p_num_sids,
+ struct dom_sid **p_sids)
+{
+ wbcErr ret;
+ struct wbcDomainSid dom_sid;
+ struct wbcDomainSid *sid_list = NULL;
+ uint32_t num_sids;
+
+ memcpy(&dom_sid, user_sid, sizeof(dom_sid));
+
+ ret = wbcLookupUserSids(&dom_sid,
+ false,
+ &num_sids,
+ &sid_list);
+ if (ret != WBC_ERR_SUCCESS) {
+ return false;
+ }
+
+ *p_sids = talloc_array(mem_ctx, struct dom_sid, num_sids);
+ if (*p_sids == NULL) {
+ wbcFreeMemory(sid_list);
+ return false;
+ }
+
+ memcpy(*p_sids, sid_list, sizeof(dom_sid) * num_sids);
+
+ *p_num_sids = num_sids;
+ wbcFreeMemory(sid_list);
+
+ return true;
+}
+
+#else /* WITH_WINBIND */
+
+struct passwd * winbind_getpwnam(const char * name)
+{
+ return NULL;
+}
+
+struct passwd * winbind_getpwsid(const struct dom_sid *sid)
+{
+ return NULL;
+}
+
+bool winbind_lookup_name(const char *dom_name, const char *name, struct dom_sid *sid,
+ enum lsa_SidType *name_type)
+{
+ return false;
+}
+
+/* Call winbindd to convert sid to name */
+
+bool winbind_lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ const char **domain, const char **name,
+ enum lsa_SidType *name_type)
+{
+ return false;
+}
+
+/* Ping winbindd to see it is alive */
+
+bool winbind_ping(void)
+{
+ return false;
+}
+
+/* Call winbindd to convert SID to uid */
+
+bool winbind_sid_to_uid(uid_t *puid, const struct dom_sid *sid)
+{
+ return false;
+}
+
+/* Call winbindd to convert SID to gid */
+
+bool winbind_sid_to_gid(gid_t *pgid, const struct dom_sid *sid)
+{
+ return false;
+}
+
+/* Call winbindd to convert uid or gid to SID */
+
+bool winbind_xid_to_sid(struct dom_sid *sid, const struct unixid *xid)
+{
+ return false;
+}
+
+/* Check for a trusted domain */
+
+wbcErr wb_is_trusted_domain(const char *domain)
+{
+ return WBC_ERR_UNKNOWN_FAILURE;
+}
+
+/* Lookup a set of rids in a given domain */
+
+bool winbind_lookup_rids(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ int num_rids, uint32_t *rids,
+ const char **domain_name,
+ const char ***names, enum lsa_SidType **types)
+{
+ return false;
+}
+
+/* Ask Winbind to allocate a new uid for us */
+
+bool winbind_allocate_uid(uid_t *uid)
+{
+ return false;
+}
+
+/* Ask Winbind to allocate a new gid for us */
+
+bool winbind_allocate_gid(gid_t *gid)
+{
+ return false;
+}
+
+bool winbind_lookup_usersids(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *p_num_sids,
+ struct dom_sid **p_sids)
+{
+ return false;
+}
+
+#endif /* WITH_WINBIND */
diff --git a/source3/lib/winbind_util.h b/source3/lib/winbind_util.h
new file mode 100644
index 0000000..6056190
--- /dev/null
+++ b/source3/lib/winbind_util.h
@@ -0,0 +1,56 @@
+/*
+ Unix SMB/CIFS implementation.
+ Winbind Utility functions
+
+ Copyright (C) Gerald (Jerry) Carter 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __LIB__WINBIND_UTIL_H__
+#define __LIB__WINBIND_UTIL_H__
+
+#include "../librpc/gen_ndr/lsa.h"
+#include "librpc/gen_ndr/idmap.h"
+
+/* needed for wbcErr below */
+#include "nsswitch/libwbclient/wbclient.h"
+
+/* The following definitions come from lib/winbind_util.c */
+
+bool winbind_lookup_name(const char *dom_name, const char *name, struct dom_sid *sid,
+ enum lsa_SidType *name_type);
+bool winbind_lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ const char **domain, const char **name,
+ enum lsa_SidType *name_type);
+bool winbind_ping(void);
+bool winbind_sid_to_uid(uid_t *puid, const struct dom_sid *sid);
+bool winbind_sid_to_gid(gid_t *pgid, const struct dom_sid *sid);
+bool winbind_xid_to_sid(struct dom_sid *sid, const struct unixid *xid);
+struct passwd * winbind_getpwnam(const char * sname);
+struct passwd * winbind_getpwsid(const struct dom_sid *sid);
+wbcErr wb_is_trusted_domain(const char *domain);
+bool winbind_lookup_rids(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ int num_rids, uint32_t *rids,
+ const char **domain_name,
+ const char ***names, enum lsa_SidType **types);
+bool winbind_allocate_uid(uid_t *uid);
+bool winbind_allocate_gid(gid_t *gid);
+bool winbind_lookup_usersids(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *p_num_sids,
+ struct dom_sid **p_sids);
+
+#endif /* __LIB__WINBIND_UTIL_H__ */
diff --git a/source3/lib/wins_srv.c b/source3/lib/wins_srv.c
new file mode 100644
index 0000000..ea94dc1
--- /dev/null
+++ b/source3/lib/wins_srv.c
@@ -0,0 +1,409 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba wins server helper functions
+ Copyright (C) Andrew Tridgell 1992-2002
+ Copyright (C) Christopher R. Hertel 2000
+ Copyright (C) Tim Potter 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/gencache.h"
+#include "lib/util/string_wrappers.h"
+
+/*
+ This is pretty much a complete rewrite of the earlier code. The main
+ aim of the rewrite is to add support for having multiple wins server
+ lists, so Samba can register with multiple groups of wins servers
+ and each group has a failover list of wins servers.
+
+ Central to the way it all works is the idea of a wins server
+ 'tag'. A wins tag is a label for a group of wins servers. For
+ example if you use
+
+ wins server = fred:192.168.2.10 mary:192.168.3.199 fred:192.168.2.61
+
+ then you would have two groups of wins servers, one tagged with the
+ name 'fred' and the other with the name 'mary'. I would usually
+ recommend using interface names instead of 'fred' and 'mary' but
+ they can be any alpha string.
+
+ Now, how does it all work. Well, nmbd needs to register each of its
+ IPs with each of its names once with each group of wins servers. So
+ it tries registering with the first one mentioned in the list, then
+ if that fails it marks that WINS server dead and moves onto the next
+ one.
+
+ In the client code things are a bit different. As each of the groups
+ of wins servers is a separate name space we need to try each of the
+ groups until we either succeed or we run out of wins servers to
+ try. If we get a negative response from a wins server then that
+ means the name doesn't exist in that group, so we give up on that
+ group and move to the next group. If we don't get a response at all
+ then maybe the wins server is down, in which case we need to
+ failover to the next one for that group.
+
+ confused yet? (tridge)
+*/
+
+/* how long a server is marked dead for */
+#define DEATH_TIME 600
+
+/* The list of dead wins servers is stored in gencache.tdb. Each server is
+ marked dead from the point of view of a given source address. We keep a
+ separate dead list for each src address to cope with multiple interfaces
+ that are not routable to each other.
+ */
+
+#define WINS_SRV_FMT "WINS_SRV_DEAD/%s,%s" /* wins_ip,src_ip */
+
+static char *wins_srv_keystr(struct in_addr wins_ip, struct in_addr src_ip)
+{
+ char *keystr = NULL, *wins_ip_addr = NULL, *src_ip_addr = NULL;
+
+ wins_ip_addr = SMB_STRDUP(inet_ntoa(wins_ip));
+ src_ip_addr = SMB_STRDUP(inet_ntoa(src_ip));
+
+ if ( !wins_ip_addr || !src_ip_addr ) {
+ DEBUG(0,("wins_srv_keystr: malloc error\n"));
+ goto done;
+ }
+
+ if (asprintf(&keystr, WINS_SRV_FMT, wins_ip_addr, src_ip_addr) == -1) {
+ DEBUG(0, (": ns_srv_keystr: malloc error for key string\n"));
+ }
+
+done:
+ SAFE_FREE(wins_ip_addr);
+ SAFE_FREE(src_ip_addr);
+
+ return keystr;
+}
+
+/*
+ see if an ip is on the dead list
+*/
+
+bool wins_srv_is_dead(struct in_addr wins_ip, struct in_addr src_ip)
+{
+ char *keystr = wins_srv_keystr(wins_ip, src_ip);
+ bool result;
+
+ /* If the key exists then the WINS server has been marked as dead */
+
+ result = gencache_get(keystr, NULL, NULL, NULL);
+ SAFE_FREE(keystr);
+
+ DEBUG(4, ("wins_srv_is_dead: %s is %s\n", inet_ntoa(wins_ip),
+ result ? "dead" : "alive"));
+
+ return result;
+}
+
+
+/*
+ mark a wins server as being alive (for the moment)
+*/
+void wins_srv_alive(struct in_addr wins_ip, struct in_addr src_ip)
+{
+ char *keystr = wins_srv_keystr(wins_ip, src_ip);
+
+ gencache_del(keystr);
+ SAFE_FREE(keystr);
+
+ DEBUG(4, ("wins_srv_alive: marking wins server %s alive\n",
+ inet_ntoa(wins_ip)));
+}
+
+/*
+ mark a wins server as temporarily dead
+*/
+void wins_srv_died(struct in_addr wins_ip, struct in_addr src_ip)
+{
+ char *keystr;
+
+ if (is_zero_ip_v4(wins_ip) || wins_srv_is_dead(wins_ip, src_ip))
+ return;
+
+ keystr = wins_srv_keystr(wins_ip, src_ip);
+
+ gencache_set(keystr, "DOWN", time(NULL) + DEATH_TIME);
+
+ SAFE_FREE(keystr);
+
+ DEBUG(4,("Marking wins server %s dead for %u seconds from source %s\n",
+ inet_ntoa(wins_ip), DEATH_TIME, inet_ntoa(src_ip)));
+}
+
+/*
+ return the total number of wins servers, dead or not
+*/
+unsigned wins_srv_count(void)
+{
+ const char **list;
+ int count = 0;
+
+ if (lp_we_are_a_wins_server()) {
+ /* simple - just talk to ourselves */
+ return 1;
+ }
+
+ list = lp_wins_server_list();
+ for (count=0; list && list[count]; count++)
+ /* nop */ ;
+
+ return count;
+}
+
+/* an internal convenience structure for an IP with a short string tag
+ attached */
+struct tagged_ip {
+ fstring tag;
+ struct in_addr ip;
+};
+
+/*
+ parse an IP string that might be in tagged format
+ the result is a tagged_ip structure containing the tag
+ and the ip in in_addr format. If there is no tag then
+ use the tag '*'
+*/
+static void parse_ip(struct tagged_ip *ip, const char *str)
+{
+ char *s = strchr(str, ':');
+ if (!s) {
+ fstrcpy(ip->tag, "*");
+ ip->ip = interpret_addr2(str);
+ return;
+ }
+
+ ip->ip = interpret_addr2(s+1);
+ fstrcpy(ip->tag, str);
+ s = strchr(ip->tag, ':');
+ if (s) {
+ *s = 0;
+ }
+}
+
+
+
+/*
+ return the list of wins server tags. A 'tag' is used to distinguish
+ wins server as either belonging to the same name space or a separate
+ name space. Usually you would setup your 'wins server' option to
+ list one or more wins server per interface and use the interface
+ name as your tag, but you are free to use any tag you like.
+*/
+char **wins_srv_tags(void)
+{
+ char **ret = NULL;
+ unsigned int count=0, i, j;
+ const char **list;
+
+ if (lp_we_are_a_wins_server()) {
+ /* give the caller something to chew on. This makes
+ the rest of the logic simpler (ie. less special cases) */
+ ret = SMB_MALLOC_ARRAY(char *, 2);
+ if (!ret) return NULL;
+ ret[0] = SMB_STRDUP("*");
+ ret[1] = NULL;
+ return ret;
+ }
+
+ list = lp_wins_server_list();
+ if (!list)
+ return NULL;
+
+ /* yes, this is O(n^2) but n is very small */
+ for (i=0;list[i];i++) {
+ struct tagged_ip t_ip;
+
+ parse_ip(&t_ip, list[i]);
+
+ /* see if we already have it */
+ for (j=0;j<count;j++) {
+ if (strcmp(ret[j], t_ip.tag) == 0) {
+ break;
+ }
+ }
+
+ if (j != count) {
+ /* we already have it. Move along */
+ continue;
+ }
+
+ /* add it to the list */
+ ret = SMB_REALLOC_ARRAY(ret, char *, count+2);
+ if (!ret) {
+ return NULL;
+ }
+ ret[count] = SMB_STRDUP(t_ip.tag);
+ if (!ret[count]) break;
+ count++;
+ }
+
+ if (count) {
+ /* make sure we null terminate */
+ ret[count] = NULL;
+ }
+
+ return ret;
+}
+
+/* free a list of wins server tags given by wins_srv_tags */
+void wins_srv_tags_free(char **list)
+{
+ int i;
+ if (!list) return;
+ for (i=0; list[i]; i++) {
+ free(list[i]);
+ }
+ free(list);
+}
+
+
+/*
+ return the IP of the currently active wins server for the given tag,
+ or the zero IP otherwise
+*/
+struct in_addr wins_srv_ip_tag(const char *tag, struct in_addr src_ip)
+{
+ const char **list;
+ int i;
+ struct tagged_ip t_ip;
+
+ /* if we are a wins server then we always just talk to ourselves */
+ if (lp_we_are_a_wins_server()) {
+ struct in_addr loopback_ip;
+ loopback_ip.s_addr = htonl(INADDR_LOOPBACK);
+ return loopback_ip;
+ }
+
+ list = lp_wins_server_list();
+ if (!list || !list[0]) {
+ struct in_addr ip;
+ zero_ip_v4(&ip);
+ return ip;
+ }
+
+ /* find the first live one for this tag */
+ for (i=0; list[i]; i++) {
+ parse_ip(&t_ip, list[i]);
+ if (strcmp(tag, t_ip.tag) != 0) {
+ /* not for the right tag. Move along */
+ continue;
+ }
+ if (!wins_srv_is_dead(t_ip.ip, src_ip)) {
+ fstring src_name;
+ fstrcpy(src_name, inet_ntoa(src_ip));
+ DEBUG(6,("Current wins server for tag '%s' with source %s is %s\n",
+ tag,
+ src_name,
+ inet_ntoa(t_ip.ip)));
+ return t_ip.ip;
+ }
+ }
+
+ /* they're all dead - try the first one until they revive */
+ for (i=0; list[i]; i++) {
+ parse_ip(&t_ip, list[i]);
+ if (strcmp(tag, t_ip.tag) != 0) {
+ continue;
+ }
+ return t_ip.ip;
+ }
+
+ /* this can't happen?? */
+ zero_ip_v4(&t_ip.ip);
+ return t_ip.ip;
+}
+
+bool wins_server_tag_ips(const char *tag, TALLOC_CTX *mem_ctx,
+ struct in_addr **pservers, size_t *pnum_servers)
+{
+ const char **list;
+ size_t i, num_servers;
+ struct in_addr *servers;
+
+ list = lp_wins_server_list();
+ if ((list == NULL) || (list[0] == NULL)) {
+ return false;
+ }
+
+ num_servers = 0;
+
+ for (i=0; list[i] != NULL; i++) {
+ struct tagged_ip t_ip;
+ parse_ip(&t_ip, list[i]);
+ if (strcmp(tag, t_ip.tag) == 0) {
+ /* Wrap check. */
+ if (num_servers + 1 < num_servers) {
+ return false;
+ }
+ num_servers += 1;
+ }
+ }
+
+ servers = talloc_array(mem_ctx, struct in_addr, num_servers);
+ if (servers == NULL) {
+ return false;
+ }
+
+ num_servers = 0;
+
+ for (i=0; list[i] != NULL; i++) {
+ struct tagged_ip t_ip;
+ parse_ip(&t_ip, list[i]);
+ if (strcmp(tag, t_ip.tag) == 0) {
+ servers[num_servers] = t_ip.ip;
+ num_servers += 1;
+ }
+ }
+ *pnum_servers = num_servers;
+ *pservers = servers;
+ return true;
+}
+
+
+/*
+ return a count of the number of IPs for a particular tag, including
+ dead ones
+*/
+unsigned wins_srv_count_tag(const char *tag)
+{
+ const char **list;
+ int i, count=0;
+
+ /* if we are a wins server then we always just talk to ourselves */
+ if (lp_we_are_a_wins_server()) {
+ return 1;
+ }
+
+ list = lp_wins_server_list();
+ if (!list || !list[0]) {
+ return 0;
+ }
+
+ /* find the first live one for this tag */
+ for (i=0; list[i]; i++) {
+ struct tagged_ip t_ip;
+ parse_ip(&t_ip, list[i]);
+ if (strcmp(tag, t_ip.tag) == 0) {
+ count++;
+ }
+ }
+
+ return count;
+}
diff --git a/source3/lib/xattr_tdb.c b/source3/lib/xattr_tdb.c
new file mode 100644
index 0000000..564cdd8
--- /dev/null
+++ b/source3/lib/xattr_tdb.c
@@ -0,0 +1,468 @@
+/*
+ * Store posix-level xattrs in a tdb
+ *
+ * Copyright (C) Andrew Bartlett 2011
+ *
+ * extracted from vfs_xattr_tdb by
+ *
+ * Copyright (C) Volker Lendecke, 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "source3/include/includes.h"
+#include "system/filesys.h"
+#include "librpc/gen_ndr/xattr.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+#include "librpc/gen_ndr/file_id.h"
+#include "dbwrap/dbwrap.h"
+#include "lib/util/util_tdb.h"
+#include "source3/lib/xattr_tdb.h"
+#include "source3/lib/file_id.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+/*
+ * unmarshall tdb_xattrs
+ */
+
+static NTSTATUS xattr_tdb_pull_attrs(TALLOC_CTX *mem_ctx,
+ const TDB_DATA *data,
+ struct tdb_xattrs **presult)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ struct tdb_xattrs *result;
+
+ if (!(result = talloc_zero(mem_ctx, struct tdb_xattrs))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (data->dsize == 0) {
+ *presult = result;
+ return NT_STATUS_OK;
+ }
+
+ blob = data_blob_const(data->dptr, data->dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, result, result,
+ (ndr_pull_flags_fn_t)ndr_pull_tdb_xattrs);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("ndr_pull_tdb_xattrs failed: %s\n",
+ ndr_errstr(ndr_err)));
+ TALLOC_FREE(result);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+}
+
+/*
+ * marshall tdb_xattrs
+ */
+
+static NTSTATUS xattr_tdb_push_attrs(TALLOC_CTX *mem_ctx,
+ const struct tdb_xattrs *attribs,
+ TDB_DATA *data)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, attribs,
+ (ndr_push_flags_fn_t)ndr_push_tdb_xattrs);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("ndr_push_tdb_xattrs failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ *data = make_tdb_data(blob.data, blob.length);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Load tdb_xattrs for a file from the tdb
+ */
+
+static NTSTATUS xattr_tdb_load_attrs(TALLOC_CTX *mem_ctx,
+ struct db_context *db_ctx,
+ const struct file_id *id,
+ struct tdb_xattrs **presult)
+{
+ uint8_t id_buf[16];
+ NTSTATUS status;
+ TDB_DATA data;
+
+ /* For backwards compatibility only store the dev/inode. */
+ push_file_id_16((char *)id_buf, id);
+
+ status = dbwrap_fetch(db_ctx, mem_ctx,
+ make_tdb_data(id_buf, sizeof(id_buf)),
+ &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return status;
+ }
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ status = xattr_tdb_pull_attrs(mem_ctx, &data, presult);
+ TALLOC_FREE(data.dptr);
+ return status;
+}
+
+/*
+ * fetch_lock the tdb_ea record for a file
+ */
+
+static struct db_record *xattr_tdb_lock_attrs(TALLOC_CTX *mem_ctx,
+ struct db_context *db_ctx,
+ const struct file_id *id)
+{
+ uint8_t id_buf[16];
+
+ /* For backwards compatibility only store the dev/inode. */
+ push_file_id_16((char *)id_buf, id);
+ return dbwrap_fetch_locked(db_ctx, mem_ctx,
+ make_tdb_data(id_buf, sizeof(id_buf)));
+}
+
+/*
+ * Save tdb_xattrs to a previously fetch_locked record
+ */
+
+static NTSTATUS xattr_tdb_save_attrs(struct db_record *rec,
+ const struct tdb_xattrs *attribs)
+{
+ TDB_DATA data = tdb_null;
+ NTSTATUS status;
+
+ status = xattr_tdb_push_attrs(talloc_tos(), attribs, &data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("xattr_tdb_push_attrs failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = dbwrap_record_store(rec, data, 0);
+
+ TALLOC_FREE(data.dptr);
+
+ return status;
+}
+
+/*
+ * Worker routine for getxattr and fgetxattr
+ */
+
+ssize_t xattr_tdb_getattr(struct db_context *db_ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct file_id *id,
+ const char *name, DATA_BLOB *blob)
+{
+ struct tdb_xattrs *attribs;
+ uint32_t i;
+ ssize_t result = -1;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct file_id_buf buf;
+
+ DBG_DEBUG("xattr_tdb_getattr called for file %s, name %s\n",
+ file_id_str_buf(*id, &buf), name);
+
+ status = xattr_tdb_load_attrs(frame, db_ctx, id, &attribs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("xattr_tdb_fetch_attrs failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return -1;
+ }
+
+ for (i=0; i<attribs->num_eas; i++) {
+ if (strcmp(attribs->eas[i].name, name) == 0) {
+ break;
+ }
+ }
+
+ if (i == attribs->num_eas) {
+ errno = ENOATTR;
+ goto fail;
+ }
+
+ *blob = attribs->eas[i].value;
+ talloc_steal(mem_ctx, blob->data);
+ result = attribs->eas[i].value.length;
+
+ fail:
+ TALLOC_FREE(frame);
+ return result;
+}
+
+/*
+ * Worker routine for setxattr and fsetxattr
+ */
+
+int xattr_tdb_setattr(struct db_context *db_ctx,
+ const struct file_id *id, const char *name,
+ const void *value, size_t size, int flags)
+{
+ NTSTATUS status;
+ struct db_record *rec;
+ struct tdb_xattrs *attribs;
+ uint32_t i;
+ TDB_DATA data;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct file_id_buf buf;
+
+ DBG_DEBUG("xattr_tdb_setattr called for file %s, name %s\n",
+ file_id_str_buf(*id, &buf), name);
+
+ rec = xattr_tdb_lock_attrs(frame, db_ctx, id);
+
+ if (rec == NULL) {
+ DEBUG(0, ("xattr_tdb_lock_attrs failed\n"));
+ errno = EINVAL;
+ return -1;
+ }
+
+ data = dbwrap_record_get_value(rec);
+
+ status = xattr_tdb_pull_attrs(rec, &data, &attribs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("xattr_tdb_fetch_attrs failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ for (i=0; i<attribs->num_eas; i++) {
+ if (strcmp(attribs->eas[i].name, name) == 0) {
+ if (flags & XATTR_CREATE) {
+ TALLOC_FREE(frame);
+ errno = EEXIST;
+ return -1;
+ }
+ break;
+ }
+ }
+
+ if (i == attribs->num_eas) {
+ struct xattr_EA *tmp;
+
+ if (flags & XATTR_REPLACE) {
+ TALLOC_FREE(frame);
+ errno = ENOATTR;
+ return -1;
+ }
+
+ tmp = talloc_realloc(
+ attribs, attribs->eas, struct xattr_EA,
+ attribs->num_eas+ 1);
+
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc_realloc failed\n"));
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ attribs->eas = tmp;
+ attribs->num_eas += 1;
+ }
+
+ attribs->eas[i].name = name;
+ attribs->eas[i].value.data = discard_const_p(uint8_t, value);
+ attribs->eas[i].value.length = size;
+
+ status = xattr_tdb_save_attrs(rec, attribs);
+
+ TALLOC_FREE(frame);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("save failed: %s\n", nt_errstr(status)));
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Worker routine for listxattr and flistxattr
+ */
+
+ssize_t xattr_tdb_listattr(struct db_context *db_ctx,
+ const struct file_id *id, char *list,
+ size_t size)
+{
+ NTSTATUS status;
+ struct tdb_xattrs *attribs;
+ uint32_t i;
+ size_t len = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ status = xattr_tdb_load_attrs(frame, db_ctx, id, &attribs);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND))
+ {
+ DEBUG(0, ("xattr_tdb_fetch_attrs failed: %s\n",
+ nt_errstr(status)));
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ DEBUG(10, ("xattr_tdb_listattr: Found %d xattrs\n",
+ attribs->num_eas));
+
+ for (i=0; i<attribs->num_eas; i++) {
+ size_t tmp;
+
+ DEBUG(10, ("xattr_tdb_listattr: xattrs[i].name: %s\n",
+ attribs->eas[i].name));
+
+ tmp = strlen(attribs->eas[i].name);
+
+ /*
+ * Try to protect against overflow
+ */
+
+ if (len + (tmp+1) < len) {
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return -1;
+ }
+
+ /*
+ * Take care of the terminating NULL
+ */
+ len += (tmp + 1);
+ }
+
+ if (len > size) {
+ TALLOC_FREE(frame);
+ errno = ERANGE;
+ return len;
+ }
+
+ len = 0;
+
+ for (i=0; i<attribs->num_eas; i++) {
+ strlcpy(list+len, attribs->eas[i].name,
+ size-len);
+ len += (strlen(attribs->eas[i].name) + 1);
+ }
+
+ TALLOC_FREE(frame);
+ return len;
+}
+
+/*
+ * Worker routine for removexattr and fremovexattr
+ */
+
+int xattr_tdb_removeattr(struct db_context *db_ctx,
+ const struct file_id *id, const char *name)
+{
+ NTSTATUS status;
+ struct db_record *rec;
+ struct tdb_xattrs *attribs;
+ uint32_t i;
+ TDB_DATA value;
+
+ rec = xattr_tdb_lock_attrs(talloc_tos(), db_ctx, id);
+
+ if (rec == NULL) {
+ DEBUG(0, ("xattr_tdb_lock_attrs failed\n"));
+ errno = EINVAL;
+ return -1;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ status = xattr_tdb_pull_attrs(rec, &value, &attribs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("xattr_tdb_fetch_attrs failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(rec);
+ return -1;
+ }
+
+ for (i=0; i<attribs->num_eas; i++) {
+ if (strcmp(attribs->eas[i].name, name) == 0) {
+ break;
+ }
+ }
+
+ if (i == attribs->num_eas) {
+ TALLOC_FREE(rec);
+ errno = ENOATTR;
+ return -1;
+ }
+
+ attribs->eas[i] =
+ attribs->eas[attribs->num_eas-1];
+ attribs->num_eas -= 1;
+
+ if (attribs->num_eas == 0) {
+ dbwrap_record_delete(rec);
+ TALLOC_FREE(rec);
+ return 0;
+ }
+
+ status = xattr_tdb_save_attrs(rec, attribs);
+
+ TALLOC_FREE(rec);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("save failed: %s\n", nt_errstr(status)));
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Worker routine for unlink and rmdir
+ */
+
+void xattr_tdb_remove_all_attrs(struct db_context *db_ctx,
+ const struct file_id *id)
+{
+ struct db_record *rec;
+ rec = xattr_tdb_lock_attrs(talloc_tos(), db_ctx, id);
+
+ /*
+ * If rec == NULL there's not much we can do about it
+ */
+
+ if (rec != NULL) {
+ dbwrap_record_delete(rec);
+ TALLOC_FREE(rec);
+ }
+}
diff --git a/source3/lib/xattr_tdb.h b/source3/lib/xattr_tdb.h
new file mode 100644
index 0000000..03bc43a
--- /dev/null
+++ b/source3/lib/xattr_tdb.h
@@ -0,0 +1,41 @@
+/*
+ * Store posix-level xattrs in a tdb
+ *
+ * Copyright (C) Andrew Bartlett 2011
+ *
+ * extracted from vfs_xattr_tdb by
+ *
+ * Copyright (C) Volker Lendecke, 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "librpc/gen_ndr/file_id.h"
+
+/* The following definitions come from lib/util/xattr_tdb.c */
+
+ssize_t xattr_tdb_getattr(struct db_context *db_ctx,
+ TALLOC_CTX *mem_ctx,
+ const struct file_id *id,
+ const char *name, DATA_BLOB *blob);
+int xattr_tdb_setattr(struct db_context *db_ctx,
+ const struct file_id *id, const char *name,
+ const void *value, size_t size, int flags);
+ssize_t xattr_tdb_listattr(struct db_context *db_ctx,
+ const struct file_id *id, char *list,
+ size_t size);
+int xattr_tdb_removeattr(struct db_context *db_ctx,
+ const struct file_id *id, const char *name);
+void xattr_tdb_remove_all_attrs(struct db_context *db_ctx,
+ const struct file_id *id);
diff --git a/source3/libads/ads_ldap_protos.h b/source3/libads/ads_ldap_protos.h
new file mode 100644
index 0000000..b063815
--- /dev/null
+++ b/source3/libads/ads_ldap_protos.h
@@ -0,0 +1,142 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Guenther Deschner 2005
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBADS_ADS_LDAP_PROTOS_H_
+#define _LIBADS_ADS_LDAP_PROTOS_H_
+
+#ifdef HAVE_LDAP_INIT_FD
+int ldap_init_fd(ber_socket_t fd, int proto, char *uri, LDAP **ldp);
+#endif
+
+/*
+ * Prototypes for ads
+ */
+
+LDAP *ldap_open_with_timeout(const char *server,
+ struct sockaddr_storage *ss,
+ int port, unsigned int to);
+void ads_msgfree(ADS_STRUCT *ads, LDAPMessage *msg);
+char *ads_get_dn(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, LDAPMessage *msg);
+
+char *ads_pull_string(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, LDAPMessage *msg,
+ const char *field);
+char **ads_pull_strings(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field,
+ size_t *num_values);
+char **ads_pull_strings_range(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field,
+ char **current_strings,
+ const char **next_attribute,
+ size_t *num_strings,
+ bool *more_strings);
+bool ads_pull_uint32(ADS_STRUCT *ads, LDAPMessage *msg, const char *field,
+ uint32_t *v);
+bool ads_pull_guid(ADS_STRUCT *ads, LDAPMessage *msg, struct GUID *guid);
+bool ads_pull_sid(ADS_STRUCT *ads, LDAPMessage *msg, const char *field,
+ struct dom_sid *sid);
+int ads_pull_sids(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field, struct dom_sid **sids);
+bool ads_pull_sd(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field, struct security_descriptor **sd);
+char *ads_pull_username(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg);
+ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *machine);
+ADS_STATUS ads_find_printer_on_server(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *printer,
+ const char *servername);
+ADS_STATUS ads_find_printers(ADS_STRUCT *ads, LDAPMessage **res);
+ADS_STATUS ads_find_user_acct(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *user);
+
+ADS_STATUS ads_do_search(ADS_STRUCT *ads, const char *bind_path, int scope,
+ const char *expr,
+ const char **attrs, LDAPMessage **res);
+ADS_STATUS ads_search(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *expr, const char **attrs);
+ADS_STATUS ads_search_dn(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *dn, const char **attrs);
+ADS_STATUS ads_do_search_all_args(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, void *args,
+ LDAPMessage **res);
+ADS_STATUS ads_do_search_all(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, LDAPMessage **res);
+ADS_STATUS ads_do_search_retry(ADS_STRUCT *ads, const char *bind_path,
+ int scope,
+ const char *expr,
+ const char **attrs, LDAPMessage **res);
+ADS_STATUS ads_search_retry(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *expr, const char **attrs);
+ADS_STATUS ads_search_retry_dn(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *dn,
+ const char **attrs);
+ADS_STATUS ads_search_retry_extended_dn_ranged(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ const char *dn,
+ const char **attrs,
+ enum ads_extended_dn_flags flags,
+ char ***strings,
+ size_t *num_strings);
+ADS_STATUS ads_search_retry_sid(ADS_STRUCT *ads, LDAPMessage **res,
+ const struct dom_sid *sid,
+ const char **attrs);
+
+
+LDAPMessage *ads_first_entry(ADS_STRUCT *ads, LDAPMessage *res);
+LDAPMessage *ads_next_entry(ADS_STRUCT *ads, LDAPMessage *res);
+LDAPMessage *ads_first_message(ADS_STRUCT *ads, LDAPMessage *res);
+LDAPMessage *ads_next_message(ADS_STRUCT *ads, LDAPMessage *res);
+void ads_process_results(ADS_STRUCT *ads, LDAPMessage *res,
+ bool (*fn)(ADS_STRUCT *,char *, void **, void *),
+ void *data_area);
+void ads_dump(ADS_STRUCT *ads, LDAPMessage *res);
+
+struct GROUP_POLICY_OBJECT;
+ADS_STATUS ads_parse_gpo(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ LDAPMessage *res,
+ const char *gpo_dn,
+ struct GROUP_POLICY_OBJECT *gpo);
+ADS_STATUS ads_search_retry_dn_sd_flags(ADS_STRUCT *ads, LDAPMessage **res,
+ uint32_t sd_flags,
+ const char *dn,
+ const char **attrs);
+ADS_STATUS ads_do_search_all_sd_flags(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, uint32_t sd_flags,
+ LDAPMessage **res);
+ADS_STATUS ads_get_tokensids(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ const char *dn,
+ struct dom_sid *user_sid,
+ struct dom_sid *primary_group_sid,
+ struct dom_sid **sids,
+ size_t *num_sids);
+ADS_STATUS ads_get_joinable_ous(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ char ***ous,
+ size_t *num_ous);
+
+#endif /* _LIBADS_ADS_LDAP_PROTOS_H_ */
diff --git a/source3/libads/ads_proto.h b/source3/libads/ads_proto.h
new file mode 100644
index 0000000..ceefcd6
--- /dev/null
+++ b/source3/libads/ads_proto.h
@@ -0,0 +1,229 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * ads (active directory) utility library
+ *
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Remus Koos (remuskoos@yahoo.com) 2001
+ * Copyright (C) Alexey Kotovich 2002
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002-2003
+ * Copyright (C) Luke Howard 2003
+ * Copyright (C) Guenther Deschner 2003-2008
+ * Copyright (C) Rakesh Patel 2004
+ * Copyright (C) Dan Perry 2004
+ * Copyright (C) Jeremy Allison 2004
+ * Copyright (C) Gerald Carter 2006
+ * Copyright (C) Stefan Metzmacher 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIBADS_ADS_PROTO_H_
+#define _LIBADS_ADS_PROTO_H_
+
+enum ads_sasl_state_e {
+ ADS_SASL_PLAIN = 0,
+ ADS_SASL_SIGN,
+ ADS_SASL_SEAL,
+};
+
+/* The following definitions come from libads/ads_struct.c */
+
+ADS_STATUS ads_build_path(const char *realm,
+ const char *sep,
+ const char *field,
+ int reverse,
+ char **_path);
+ADS_STATUS ads_build_dn(const char *realm, TALLOC_CTX *mem_ctx, char **_dn);
+char *ads_build_domain(const char *dn);
+ADS_STRUCT *ads_init(TALLOC_CTX *mem_ctx,
+ const char *realm,
+ const char *workgroup,
+ const char *ldap_server,
+ enum ads_sasl_state_e sasl_state);
+bool ads_set_sasl_wrap_flags(ADS_STRUCT *ads, unsigned flags);
+
+/* The following definitions come from libads/disp_sec.c */
+
+void ads_disp_sd(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, struct security_descriptor *sd);
+
+/* The following definitions come from libads/kerberos_keytab.c */
+
+int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc,
+ bool update_ads);
+int ads_keytab_delete_entry(ADS_STRUCT *ads, const char *srvPrinc);
+int ads_keytab_flush(ADS_STRUCT *ads);
+int ads_keytab_create_default(ADS_STRUCT *ads);
+int ads_keytab_list(const char *keytab_name);
+
+/* The following definitions come from libads/net_ads_setspn.c */
+bool ads_setspn_list(ADS_STRUCT *ads, const char *machine);
+bool ads_setspn_add(ADS_STRUCT *ads, const char *machine_name,
+ const char * spn);
+bool ads_setspn_delete(ADS_STRUCT *ads, const char *machine_name,
+ const char * spn);
+
+/* The following definitions come from libads/krb5_errs.c */
+
+/* The following definitions come from libads/kerberos_util.c */
+
+int ads_kinit_password(ADS_STRUCT *ads);
+
+/* The following definitions come from libads/ldap.c */
+
+bool ads_sitename_match(ADS_STRUCT *ads);
+bool ads_closest_dc(ADS_STRUCT *ads);
+ADS_STATUS ads_connect(ADS_STRUCT *ads);
+ADS_STATUS ads_connect_user_creds(ADS_STRUCT *ads);
+void ads_zero_ldap(ADS_STRUCT *ads);
+void ads_disconnect(ADS_STRUCT *ads);
+ADS_STATUS ads_do_search_all_fn(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr, const char **attrs,
+ bool (*fn)(ADS_STRUCT *, char *, void **, void *),
+ void *data_area);
+char *ads_parent_dn(const char *dn);
+ADS_MODLIST ads_init_mods(TALLOC_CTX *ctx);
+ADS_STATUS ads_mod_str(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const char *val);
+ADS_STATUS ads_mod_strlist(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const char **vals);
+ADS_STATUS ads_gen_mod(ADS_STRUCT *ads, const char *mod_dn, ADS_MODLIST mods);
+ADS_STATUS ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ADS_MODLIST mods);
+ADS_STATUS ads_del_dn(ADS_STRUCT *ads, char *del_dn);
+char *ads_ou_string(ADS_STRUCT *ads, const char *org_unit);
+char *ads_default_ou_string(ADS_STRUCT *ads, const char *wknguid);
+ADS_STATUS ads_add_strlist(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const char **vals);
+uint32_t ads_get_kvno(ADS_STRUCT *ads, const char *account_name);
+uint32_t ads_get_machine_kvno(ADS_STRUCT *ads, const char *machine_name);
+
+bool ads_element_in_array(const char **el_array, size_t num_el, const char *el);
+
+ADS_STATUS ads_get_service_principal_names(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char *machine_name,
+ char ***spn_array,
+ size_t *num_spns);
+ADS_STATUS ads_clear_service_principal_names(ADS_STRUCT *ads, const char *machine_name);
+ADS_STATUS ads_add_service_principal_names(ADS_STRUCT *ads, const char *machine_name,
+ const char **spns);
+ADS_STATUS ads_create_machine_acct(ADS_STRUCT *ads,
+ const char *machine_name,
+ const char *machine_password,
+ const char *org_unit,
+ uint32_t etype_list,
+ const char *dns_domain_name);
+ADS_STATUS ads_move_machine_acct(ADS_STRUCT *ads, const char *machine_name,
+ const char *org_unit, bool *moved);
+int ads_count_replies(ADS_STRUCT *ads, void *res);
+ADS_STATUS ads_USN(ADS_STRUCT *ads, uint32_t *usn);
+ADS_STATUS ads_current_time(ADS_STRUCT *ads);
+ADS_STATUS ads_domain_func_level(ADS_STRUCT *ads, uint32_t *val);
+ADS_STATUS ads_domain_sid(ADS_STRUCT *ads, struct dom_sid *sid);
+ADS_STATUS ads_site_dn(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const char **site_name);
+ADS_STATUS ads_site_dn_for_machine(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const char *computer_name, const char **site_dn);
+ADS_STATUS ads_upn_suffixes(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, char ***suffixes, size_t *num_suffixes);
+ADS_STATUS ads_get_joinable_ous(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ char ***ous,
+ size_t *num_ous);
+ADS_STATUS ads_get_sid_from_extended_dn(TALLOC_CTX *mem_ctx,
+ const char *extended_dn,
+ enum ads_extended_dn_flags flags,
+ struct dom_sid *sid);
+char* ads_get_dnshostname( ADS_STRUCT *ads, TALLOC_CTX *ctx, const char *machine_name );
+ADS_STATUS ads_get_additional_dns_hostnames(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char *machine_name,
+ char ***hostnames_array,
+ size_t *num_hostnames);
+char* ads_get_upn( ADS_STRUCT *ads, TALLOC_CTX *ctx, const char *machine_name );
+bool ads_has_samaccountname( ADS_STRUCT *ads, TALLOC_CTX *ctx, const char *machine_name );
+ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *machine_name,
+ uint32_t account_type, const char *org_unit);
+ADS_STATUS ads_leave_realm(ADS_STRUCT *ads, const char *hostname);
+ADS_STATUS ads_find_samaccount(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ const char *samaccountname,
+ uint32_t *uac_ret,
+ const char **dn_ret);
+ADS_STATUS ads_config_path(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ char **config_path);
+const char *ads_get_extended_right_name_by_guid(ADS_STRUCT *ads,
+ const char *config_path,
+ TALLOC_CTX *mem_ctx,
+ const struct GUID *rights_guid);
+ADS_STATUS ads_check_ou_dn(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char **account_ou);
+
+/* The following definitions come from libads/ldap_printer.c */
+
+ADS_STATUS ads_mod_printer_entry(ADS_STRUCT *ads, char *prt_dn,
+ TALLOC_CTX *ctx, const ADS_MODLIST *mods);
+ADS_STATUS ads_add_printer_entry(ADS_STRUCT *ads, char *prt_dn,
+ TALLOC_CTX *ctx, ADS_MODLIST *mods);
+WERROR get_remote_printer_publishing_data(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ ADS_MODLIST *mods,
+ const char *printer);
+
+/* The following definitions come from libads/ldap_user.c */
+
+ADS_STATUS ads_add_user_acct(ADS_STRUCT *ads, const char *user,
+ const char *container, const char *fullname);
+ADS_STATUS ads_add_group_acct(ADS_STRUCT *ads, const char *group,
+ const char *container, const char *comment);
+
+/* The following definitions come from libads/ldap_utils.c */
+
+ADS_STATUS ads_ranged_search(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ int scope,
+ const char *base,
+ const char *filter,
+ void *args,
+ const char *range_attr,
+ char ***strings,
+ size_t *num_strings);
+
+/* The following definitions come from libads/sasl.c */
+
+ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads);
+
+/* The following definitions come from libads/sasl_wrapping.c */
+
+ADS_STATUS ads_setup_sasl_wrapping(struct ads_saslwrap *wrap, LDAP *ld,
+ const struct ads_saslwrap_ops *ops,
+ void *private_data);
+void ndr_print_ads_saslwrap_struct(struct ndr_print *ndr,
+ const char *name,
+ const struct ads_saslwrap *r);
+
+/* The following definitions come from libads/util.c */
+
+ADS_STATUS ads_change_trust_account_password(ADS_STRUCT *ads, char *host_principal);
+
+struct spn_struct {
+ const char *serviceclass;
+ const char *servicename;
+ const char *host;
+ int32_t port;
+};
+
+/* parse a windows style SPN, returns NULL if parsing fails */
+struct spn_struct *parse_spn(TALLOC_CTX *ctx, const char *srvprinc);
+
+#endif /* _LIBADS_ADS_PROTO_H_ */
diff --git a/source3/libads/ads_status.c b/source3/libads/ads_status.c
new file mode 100644
index 0000000..7056994
--- /dev/null
+++ b/source3/libads/ads_status.c
@@ -0,0 +1,157 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Andrew Bartlett 2001
+
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smb_krb5.h"
+#include "system/gssapi.h"
+#include "smb_ldap.h"
+#include "libads/ads_status.h"
+
+/*
+ build a ADS_STATUS structure
+*/
+ADS_STATUS ads_build_error(enum ads_error_type etype,
+ int rc, int minor_status)
+{
+ ADS_STATUS ret;
+
+ if (etype == ENUM_ADS_ERROR_NT) {
+ DEBUG(0,("don't use ads_build_error with ENUM_ADS_ERROR_NT!\n"));
+ ret.err.rc = -1;
+ ret.error_type = ENUM_ADS_ERROR_SYSTEM;
+ ret.minor_status = 0;
+ return ret;
+ }
+
+ ret.err.rc = rc;
+ ret.error_type = etype;
+ ret.minor_status = minor_status;
+ return ret;
+}
+
+ADS_STATUS ads_build_nt_error(enum ads_error_type etype,
+ NTSTATUS nt_status)
+{
+ ADS_STATUS ret;
+
+ if (etype != ENUM_ADS_ERROR_NT) {
+ DEBUG(0,("don't use ads_build_nt_error without ENUM_ADS_ERROR_NT!\n"));
+ ret.err.rc = -1;
+ ret.error_type = ENUM_ADS_ERROR_SYSTEM;
+ ret.minor_status = 0;
+ return ret;
+ }
+ ret.err.nt_status = nt_status;
+ ret.error_type = etype;
+ ret.minor_status = 0;
+ return ret;
+}
+
+/*
+ do a rough conversion between ads error codes and NT status codes
+ we'll need to fill this in more
+*/
+NTSTATUS ads_ntstatus(ADS_STATUS status)
+{
+ switch (status.error_type) {
+ case ENUM_ADS_ERROR_NT:
+ return status.err.nt_status;
+ case ENUM_ADS_ERROR_SYSTEM:
+ return map_nt_error_from_unix(status.err.rc);
+#ifdef HAVE_LDAP
+ case ENUM_ADS_ERROR_LDAP:
+ if (status.err.rc == LDAP_SUCCESS) {
+ return NT_STATUS_OK;
+ }
+ if (status.err.rc == LDAP_TIMELIMIT_EXCEEDED) {
+ return NT_STATUS_IO_TIMEOUT;
+ }
+ return NT_STATUS_LDAP(status.err.rc);
+#endif
+#ifdef HAVE_KRB5
+ case ENUM_ADS_ERROR_KRB5:
+ return krb5_to_nt_status(status.err.rc);
+#endif
+ default:
+ break;
+ }
+
+ if (ADS_ERR_OK(status)) {
+ return NT_STATUS_OK;
+ }
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+/*
+ return a string for an error from a ads routine
+*/
+const char *ads_errstr(ADS_STATUS status)
+{
+ switch (status.error_type) {
+ case ENUM_ADS_ERROR_SYSTEM:
+ return strerror(status.err.rc);
+#ifdef HAVE_LDAP
+ case ENUM_ADS_ERROR_LDAP:
+ return ldap_err2string(status.err.rc);
+#endif
+#ifdef HAVE_KRB5
+ case ENUM_ADS_ERROR_KRB5:
+ return error_message(status.err.rc);
+ case ENUM_ADS_ERROR_GSS:
+ {
+ char *ret;
+ uint32_t msg_ctx;
+ uint32_t minor;
+ gss_buffer_desc msg1, msg2;
+
+ msg_ctx = 0;
+
+ msg1.value = NULL;
+ msg2.value = NULL;
+ gss_display_status(&minor, status.err.rc, GSS_C_GSS_CODE,
+ GSS_C_NULL_OID, &msg_ctx, &msg1);
+ gss_display_status(&minor, status.minor_status, GSS_C_MECH_CODE,
+ GSS_C_NULL_OID, &msg_ctx, &msg2);
+ ret = talloc_asprintf(talloc_tos(), "%s : %s",
+ (char *)msg1.value, (char *)msg2.value);
+ SMB_ASSERT(ret != NULL);
+ gss_release_buffer(&minor, &msg1);
+ gss_release_buffer(&minor, &msg2);
+ return ret;
+ }
+#endif
+ case ENUM_ADS_ERROR_NT:
+ return get_friendly_nt_error_msg(ads_ntstatus(status));
+ default:
+ return "Unknown ADS error type!? (not compiled in?)";
+ }
+}
+
+#ifdef HAVE_KRB5
+NTSTATUS gss_err_to_ntstatus(uint32_t maj, uint32_t min)
+{
+ ADS_STATUS adss = ADS_ERROR_GSS(maj, min);
+ DEBUG(10,("gss_err_to_ntstatus: Error %s\n",
+ ads_errstr(adss) ));
+ return ads_ntstatus(adss);
+}
+#endif
diff --git a/source3/libads/ads_status.h b/source3/libads/ads_status.h
new file mode 100644
index 0000000..2ff4ef0
--- /dev/null
+++ b/source3/libads/ads_status.h
@@ -0,0 +1,68 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Andrew Bartlett 2001
+
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBADS_ADS_STATUS_H_
+#define _LIBADS_ADS_STATUS_H_
+
+/* there are 5 possible types of errors the ads subsystem can produce */
+enum ads_error_type {ENUM_ADS_ERROR_KRB5, ENUM_ADS_ERROR_GSS,
+ ENUM_ADS_ERROR_LDAP, ENUM_ADS_ERROR_SYSTEM, ENUM_ADS_ERROR_NT};
+
+typedef struct {
+ enum ads_error_type error_type;
+ union err_state{
+ int rc;
+ NTSTATUS nt_status;
+ } err;
+ /* For error_type = ENUM_ADS_ERROR_GSS minor_status describe GSS API error */
+ /* Where rc represents major_status of GSS API error */
+ int minor_status;
+} ADS_STATUS;
+
+/* macros to simplify error returning */
+#define ADS_ERROR(rc) ADS_ERROR_LDAP(rc)
+#define ADS_ERROR_LDAP(rc) ads_build_error(ENUM_ADS_ERROR_LDAP, rc, 0)
+#define ADS_ERROR_SYSTEM(rc) ads_build_error(ENUM_ADS_ERROR_SYSTEM, rc?rc:EINVAL, 0)
+#define ADS_ERROR_KRB5(rc) ads_build_error(ENUM_ADS_ERROR_KRB5, rc, 0)
+#define ADS_ERROR_GSS(rc, minor) ads_build_error(ENUM_ADS_ERROR_GSS, rc, minor)
+#define ADS_ERROR_NT(rc) ads_build_nt_error(ENUM_ADS_ERROR_NT,rc)
+
+#define ADS_ERR_OK(status) ((status.error_type == ENUM_ADS_ERROR_NT) ? NT_STATUS_IS_OK(status.err.nt_status):(status.err.rc == 0))
+#define ADS_SUCCESS ADS_ERROR(0)
+
+#define ADS_ERROR_HAVE_NO_MEMORY(x) do { \
+ if (!(x)) {\
+ return ADS_ERROR(LDAP_NO_MEMORY);\
+ }\
+} while (0)
+
+/* The following definitions come from libads/ads_status.c */
+
+ADS_STATUS ads_build_error(enum ads_error_type etype,
+ int rc, int minor_status);
+ADS_STATUS ads_build_nt_error(enum ads_error_type etype,
+ NTSTATUS nt_status);
+NTSTATUS ads_ntstatus(ADS_STATUS status);
+const char *ads_errstr(ADS_STATUS status);
+NTSTATUS gss_err_to_ntstatus(uint32_t maj, uint32_t min);
+
+#endif /* _LIBADS_ADS_STATUS_H_ */
diff --git a/source3/libads/ads_struct.c b/source3/libads/ads_struct.c
new file mode 100644
index 0000000..97f84d1
--- /dev/null
+++ b/source3/libads/ads_struct.c
@@ -0,0 +1,239 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+
+/* return a ldap dn path from a string, given separators and field name
+ caller must free
+*/
+ADS_STATUS ads_build_path(const char *realm,
+ const char *sep,
+ const char *field,
+ int reverse,
+ char **_path)
+{
+ char *p, *r;
+ int numbits = 0;
+ char *ret;
+ int len;
+ char *saveptr;
+
+ *_path = NULL;
+
+ r = SMB_STRDUP(realm);
+ if (r == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ for (p=r; *p; p++) {
+ if (strchr(sep, *p)) {
+ numbits++;
+ }
+ }
+
+ len = (numbits+1)*(strlen(field)+1) + strlen(r) + 1;
+
+ ret = (char *)SMB_MALLOC(len);
+ if (!ret) {
+ free(r);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ if (strlcpy(ret,field, len) >= len) {
+ /* Truncate ! */
+ free(r);
+ free(ret);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+ p=strtok_r(r, sep, &saveptr);
+ if (p) {
+ if (strlcat(ret, p, len) >= len) {
+ free(r);
+ free(ret);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ while ((p=strtok_r(NULL, sep, &saveptr)) != NULL) {
+ int retval;
+ char *s = NULL;
+ if (reverse)
+ retval = asprintf(&s, "%s%s,%s", field, p, ret);
+ else
+ retval = asprintf(&s, "%s,%s%s", ret, field, p);
+ free(ret);
+ if (retval == -1) {
+ free(r);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+ ret = SMB_STRDUP(s);
+ free(s);
+ }
+ }
+
+ free(r);
+
+ *_path = ret;
+
+ return ADS_ERROR_NT(NT_STATUS_OK);
+}
+
+/* return a dn of the form "dc=AA,dc=BB,dc=CC" from a
+ realm of the form AA.BB.CC
+ caller must free
+*/
+ADS_STATUS ads_build_dn(const char *realm, TALLOC_CTX *mem_ctx, char **_dn)
+{
+ ADS_STATUS status;
+ char *dn = NULL;
+
+ status = ads_build_path(realm, ".", "dc=", 0, &dn);
+ if (!ADS_ERR_OK(status)) {
+ SAFE_FREE(dn);
+ return status;
+ }
+
+ *_dn = talloc_strdup(mem_ctx, dn);
+ SAFE_FREE(dn);
+ if (*_dn == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ return ADS_ERROR_NT(NT_STATUS_OK);
+}
+
+/* return a DNS name in the for aa.bb.cc from the DN
+ "dc=AA,dc=BB,dc=CC". caller must free
+*/
+char *ads_build_domain(const char *dn)
+{
+ char *dnsdomain = NULL;
+
+ /* result should always be shorter than the DN */
+
+ if ( (dnsdomain = SMB_STRDUP( dn )) == NULL ) {
+ DEBUG(0,("ads_build_domain: malloc() failed!\n"));
+ return NULL;
+ }
+
+ if (!strlower_m( dnsdomain )) {
+ SAFE_FREE(dnsdomain);
+ return NULL;
+ }
+
+ all_string_sub( dnsdomain, "dc=", "", 0);
+ all_string_sub( dnsdomain, ",", ".", 0 );
+
+ return dnsdomain;
+}
+
+static int ads_destructor(ADS_STRUCT *ads)
+{
+#ifdef HAVE_LDAP
+ ads_disconnect(ads);
+#endif
+ return 0;
+}
+
+/*
+ initialise a ADS_STRUCT, ready for some ads_ ops
+*/
+ADS_STRUCT *ads_init(TALLOC_CTX *mem_ctx,
+ const char *realm,
+ const char *workgroup,
+ const char *ldap_server,
+ enum ads_sasl_state_e sasl_state)
+{
+ ADS_STRUCT *ads = NULL;
+ int wrap_flags;
+
+ ads = talloc_zero(mem_ctx, ADS_STRUCT);
+ if (ads == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(ads, ads_destructor);
+
+#ifdef HAVE_LDAP
+ ads_zero_ldap(ads);
+#endif
+
+ ads->server.realm = talloc_strdup(ads, realm);
+ if (realm != NULL && ads->server.realm == NULL) {
+ DBG_WARNING("Out of memory\n");
+ TALLOC_FREE(ads);
+ return NULL;
+ }
+
+ ads->server.workgroup = talloc_strdup(ads, workgroup);
+ if (workgroup != NULL && ads->server.workgroup == NULL) {
+ DBG_WARNING("Out of memory\n");
+ TALLOC_FREE(ads);
+ return NULL;
+ }
+
+ ads->server.ldap_server = talloc_strdup(ads, ldap_server);
+ if (ldap_server != NULL && ads->server.ldap_server == NULL) {
+ DBG_WARNING("Out of memory\n");
+ TALLOC_FREE(ads);
+ return NULL;
+ }
+
+ wrap_flags = lp_client_ldap_sasl_wrapping();
+ if (wrap_flags == -1) {
+ wrap_flags = 0;
+ }
+
+ switch (sasl_state) {
+ case ADS_SASL_PLAIN:
+ break;
+ case ADS_SASL_SIGN:
+ wrap_flags |= ADS_AUTH_SASL_SIGN;
+ break;
+ case ADS_SASL_SEAL:
+ wrap_flags |= ADS_AUTH_SASL_SEAL;
+ break;
+ }
+
+ ads->auth.flags = wrap_flags;
+
+ /* Start with the configured page size when the connection is new,
+ * we will drop it by half we get a timeout. */
+ ads->config.ldap_page_size = lp_ldap_page_size();
+
+ return ads;
+}
+
+/****************************************************************
+****************************************************************/
+
+bool ads_set_sasl_wrap_flags(ADS_STRUCT *ads, unsigned flags)
+{
+ unsigned other_flags;
+
+ if (!ads) {
+ return false;
+ }
+
+ other_flags = ads->auth.flags & ~(ADS_AUTH_SASL_SIGN|ADS_AUTH_SASL_SEAL);
+
+ ads->auth.flags = flags | other_flags;
+
+ return true;
+}
diff --git a/source3/libads/authdata.c b/source3/libads/authdata.c
new file mode 100644
index 0000000..10adc3e
--- /dev/null
+++ b/source3/libads/authdata.c
@@ -0,0 +1,323 @@
+/*
+ Unix SMB/CIFS implementation.
+ kerberos authorization data (PAC) utility library
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Luke Howard 2002-2003
+ Copyright (C) Stefan Metzmacher 2004-2005
+ Copyright (C) Guenther Deschner 2005,2007,2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "smb_krb5.h"
+#include "libads/kerberos_proto.h"
+#include "auth/common_auth.h"
+#include "lib/param/param.h"
+#include "librpc/crypto/gse.h"
+#include "auth/gensec/gensec.h"
+#include "auth/gensec/gensec_internal.h" /* TODO: remove this */
+#include "../libcli/auth/spnego.h"
+#include "lib/util/asn1.h"
+
+#ifdef HAVE_KRB5
+
+#include "auth/kerberos/pac_utils.h"
+
+struct smb_krb5_context;
+
+/*
+ generate a krb5 GSS-API wrapper packet given a ticket
+*/
+static DATA_BLOB spnego_gen_krb5_wrap(
+ TALLOC_CTX *ctx, const DATA_BLOB ticket, const uint8_t tok_id[2])
+{
+ ASN1_DATA *data;
+ DATA_BLOB ret = data_blob_null;
+
+ data = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH);
+ if (data == NULL) {
+ return data_blob_null;
+ }
+
+ if (!asn1_push_tag(data, ASN1_APPLICATION(0))) goto err;
+ if (!asn1_write_OID(data, OID_KERBEROS5)) goto err;
+
+ if (!asn1_write(data, tok_id, 2)) goto err;
+ if (!asn1_write(data, ticket.data, ticket.length)) goto err;
+ if (!asn1_pop_tag(data)) goto err;
+
+ if (!asn1_extract_blob(data, ctx, &ret)) {
+ goto err;
+ }
+
+ asn1_free(data);
+ data = NULL;
+
+ err:
+
+ if (data != NULL) {
+ if (asn1_has_error(data)) {
+ DEBUG(1, ("Failed to build krb5 wrapper at offset %d\n",
+ (int)asn1_current_ofs(data)));
+ }
+
+ asn1_free(data);
+ }
+
+ return ret;
+}
+
+/*
+ * Given the username/password, do a kinit, store the ticket in
+ * cache_name if specified, and return the PAC_LOGON_INFO (the
+ * structure containing the important user information such as
+ * groups).
+ */
+NTSTATUS kerberos_return_pac(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *pass,
+ time_t time_offset,
+ time_t *expire_time,
+ time_t *renew_till_time,
+ const char *cache_name,
+ bool request_pac,
+ bool add_netbios_addr,
+ time_t renewable_time,
+ const char *impersonate_princ_s,
+ const char *local_service,
+ char **_canon_principal,
+ char **_canon_realm,
+ struct PAC_DATA_CTR **_pac_data_ctr)
+{
+ krb5_error_code ret;
+ NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
+ DATA_BLOB tkt = data_blob_null;
+ DATA_BLOB tkt_wrapped = data_blob_null;
+ DATA_BLOB ap_rep = data_blob_null;
+ DATA_BLOB sesskey1 = data_blob_null;
+ const char *auth_princ = NULL;
+ const char *cc = "MEMORY:kerberos_return_pac";
+ struct auth_session_info *session_info;
+ struct gensec_security *gensec_server_context;
+ const struct gensec_security_ops **backends;
+ struct gensec_settings *gensec_settings;
+ size_t idx = 0;
+ struct auth4_context *auth_context;
+ struct loadparm_context *lp_ctx;
+ struct PAC_DATA_CTR *pac_data_ctr = NULL;
+ char *canon_principal = NULL;
+ char *canon_realm = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ ZERO_STRUCT(tkt);
+ ZERO_STRUCT(ap_rep);
+ ZERO_STRUCT(sesskey1);
+
+ if (!name || !pass) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (_canon_principal != NULL) {
+ *_canon_principal = NULL;
+ }
+
+ if (_canon_realm != NULL) {
+ *_canon_realm = NULL;
+ }
+
+ if (cache_name) {
+ cc = cache_name;
+ }
+
+ if (!strchr_m(name, '@')) {
+ auth_princ = talloc_asprintf(mem_ctx, "%s@%s", name,
+ lp_realm());
+ } else {
+ auth_princ = name;
+ }
+ NT_STATUS_HAVE_NO_MEMORY(auth_princ);
+
+ ret = kerberos_kinit_password_ext(auth_princ,
+ pass,
+ time_offset,
+ expire_time,
+ renew_till_time,
+ cc,
+ request_pac,
+ add_netbios_addr,
+ renewable_time,
+ tmp_ctx,
+ &canon_principal,
+ &canon_realm,
+ &status);
+ if (ret) {
+ DEBUG(1,("kinit failed for '%s' with: %s (%d)\n",
+ auth_princ, error_message(ret), ret));
+ /* status already set */
+ goto out;
+ }
+
+ DEBUG(10,("got TGT for %s in %s\n", auth_princ, cc));
+ if (expire_time) {
+ DEBUGADD(10,("\tvalid until: %s (%d)\n",
+ http_timestring(talloc_tos(), *expire_time),
+ (int)*expire_time));
+ }
+ if (renew_till_time) {
+ DEBUGADD(10,("\trenewable till: %s (%d)\n",
+ http_timestring(talloc_tos(), *renew_till_time),
+ (int)*renew_till_time));
+ }
+
+ /* we cannot continue with krb5 when UF_DONT_REQUIRE_PREAUTH is set,
+ * in that case fallback to NTLM - gd */
+
+ if (expire_time && renew_till_time &&
+ (*expire_time == 0) && (*renew_till_time == 0)) {
+ status = NT_STATUS_INVALID_LOGON_TYPE;
+ goto out;
+ }
+
+ ret = ads_krb5_cli_get_ticket(mem_ctx,
+ local_service,
+ time_offset,
+ &tkt,
+ &sesskey1,
+ 0,
+ cc,
+ NULL,
+ impersonate_princ_s);
+ if (ret) {
+ DEBUG(1,("failed to get ticket for %s: %s\n",
+ local_service, error_message(ret)));
+ if (impersonate_princ_s) {
+ DEBUGADD(1,("tried S4U2SELF impersonation as: %s\n",
+ impersonate_princ_s));
+ }
+ status = krb5_to_nt_status(ret);
+ goto out;
+ }
+
+ /* wrap that up in a nice GSS-API wrapping */
+ tkt_wrapped = spnego_gen_krb5_wrap(tmp_ctx, tkt, TOK_ID_KRB_AP_REQ);
+ if (tkt_wrapped.data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ auth_context = auth4_context_for_PAC_DATA_CTR(tmp_ctx);
+ if (auth_context == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ lp_ctx = loadparm_init_s3(tmp_ctx, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ status = NT_STATUS_INVALID_SERVER_STATE;
+ DEBUG(10, ("loadparm_init_s3 failed\n"));
+ goto out;
+ }
+
+ gensec_settings = lpcfg_gensec_settings(tmp_ctx, lp_ctx);
+ if (gensec_settings == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DEBUG(10, ("lpcfg_gensec_settings failed\n"));
+ goto out;
+ }
+
+ backends = talloc_zero_array(gensec_settings,
+ const struct gensec_security_ops *, 2);
+ if (backends == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ gensec_settings->backends = backends;
+
+ gensec_init();
+
+ backends[idx++] = &gensec_gse_krb5_security_ops;
+
+ status = gensec_server_start(tmp_ctx, gensec_settings,
+ auth_context, &gensec_server_context);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, (__location__ "Failed to start server-side GENSEC to validate a Kerberos ticket: %s\n", nt_errstr(status)));
+ goto out;
+ }
+
+ talloc_unlink(tmp_ctx, lp_ctx);
+ talloc_unlink(tmp_ctx, gensec_settings);
+ talloc_unlink(tmp_ctx, auth_context);
+
+ /* Session info is not complete, do not pass to auth log */
+ gensec_want_feature(gensec_server_context, GENSEC_FEATURE_NO_AUTHZ_LOG);
+
+ status = gensec_start_mech_by_oid(gensec_server_context, GENSEC_OID_KERBEROS5);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, (__location__ "Failed to start server-side GENSEC krb5 to validate a Kerberos ticket: %s\n", nt_errstr(status)));
+ goto out;
+ }
+
+ /* Do a client-server update dance */
+ status = gensec_update(gensec_server_context, tmp_ctx, tkt_wrapped, &ap_rep);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("gensec_update() failed: %s\n", nt_errstr(status)));
+ goto out;
+ }
+
+ /* Now return the PAC information to the callers. We ignore
+ * the session_info and instead pick out the PAC via the
+ * private_data on the auth_context */
+ status = gensec_session_info(gensec_server_context, tmp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Unable to obtain PAC via gensec_session_info\n"));
+ goto out;
+ }
+
+ pac_data_ctr = auth4_context_get_PAC_DATA_CTR(auth_context, mem_ctx);
+ if (pac_data_ctr == NULL) {
+ DEBUG(1,("no PAC\n"));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ *_pac_data_ctr = talloc_move(mem_ctx, &pac_data_ctr);
+ if (_canon_principal != NULL) {
+ *_canon_principal = talloc_move(mem_ctx, &canon_principal);
+ }
+ if (_canon_realm != NULL) {
+ *_canon_realm = talloc_move(mem_ctx, &canon_realm);
+ }
+
+out:
+ talloc_free(tmp_ctx);
+ if (cc != cache_name) {
+ ads_kdestroy(cc);
+ }
+
+ data_blob_free(&tkt);
+ data_blob_free(&ap_rep);
+ data_blob_free(&sesskey1);
+
+ return status;
+}
+
+#endif
diff --git a/source3/libads/cldap.c b/source3/libads/cldap.c
new file mode 100644
index 0000000..56c2537
--- /dev/null
+++ b/source3/libads/cldap.c
@@ -0,0 +1,453 @@
+/*
+ Samba Unix/Linux SMB client library
+ net ads cldap functions
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2003 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2008 Guenther Deschner (gd@samba.org)
+ Copyright (C) 2009 Stefan Metzmacher (metze@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../libcli/cldap/cldap.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "libads/cldap.h"
+
+struct cldap_multi_netlogon_state {
+ struct tevent_context *ev;
+ const struct tsocket_address * const *servers;
+ int num_servers;
+ const char *domain;
+ const char *hostname;
+ unsigned ntversion;
+ int min_servers;
+
+ struct cldap_socket **cldap;
+ struct tevent_req **subreqs;
+ int num_sent;
+ int num_received;
+ int num_good_received;
+ struct cldap_netlogon *ios;
+ struct netlogon_samlogon_response **responses;
+};
+
+static void cldap_multi_netlogon_done(struct tevent_req *subreq);
+static void cldap_multi_netlogon_next(struct tevent_req *subreq);
+
+/****************************************************************
+****************************************************************/
+
+#define RETURN_ON_FALSE(x) if (!(x)) return false;
+
+bool check_cldap_reply_required_flags(uint32_t ret_flags,
+ uint32_t req_flags)
+{
+ if (req_flags == 0) {
+ return true;
+ }
+
+ if (req_flags & DS_PDC_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_PDC);
+
+ if (req_flags & DS_GC_SERVER_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_GC);
+
+ if (req_flags & DS_ONLY_LDAP_NEEDED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_LDAP);
+
+ if ((req_flags & DS_DIRECTORY_SERVICE_REQUIRED) ||
+ (req_flags & DS_DIRECTORY_SERVICE_PREFERRED))
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_DS);
+
+ if (req_flags & DS_KDC_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_KDC);
+
+ if (req_flags & DS_TIMESERV_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_TIMESERV);
+
+ if (req_flags & DS_WEB_SERVICE_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_ADS_WEB_SERVICE);
+
+ if (req_flags & DS_WRITABLE_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_WRITABLE);
+
+ if (req_flags & DS_DIRECTORY_SERVICE_6_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & (NBT_SERVER_SELECT_SECRET_DOMAIN_6
+ |NBT_SERVER_FULL_SECRET_DOMAIN_6));
+
+ if (req_flags & DS_DIRECTORY_SERVICE_8_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_DS_8);
+
+ if (req_flags & DS_DIRECTORY_SERVICE_9_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_DS_9);
+
+ if (req_flags & DS_DIRECTORY_SERVICE_10_REQUIRED)
+ RETURN_ON_FALSE(ret_flags & NBT_SERVER_DS_10);
+
+ return true;
+}
+
+/*
+ * Do a parallel cldap ping to the servers. The first "min_servers"
+ * are fired directly, the remaining ones in 100msec intervals. If
+ * "min_servers" responses came in successfully, we immediately reply,
+ * not waiting for the remaining ones.
+ */
+
+struct tevent_req *cldap_multi_netlogon_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const struct tsocket_address * const *servers, int num_servers,
+ const char *domain, const char *hostname, unsigned ntversion,
+ int min_servers)
+{
+ struct tevent_req *req, *subreq;
+ struct cldap_multi_netlogon_state *state;
+ int i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cldap_multi_netlogon_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->servers = servers;
+ state->num_servers = num_servers;
+ state->domain = domain;
+ state->hostname = hostname;
+ state->ntversion = ntversion;
+ state->min_servers = min_servers;
+
+ if (min_servers > num_servers) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->subreqs = talloc_zero_array(state,
+ struct tevent_req *,
+ num_servers);
+ if (tevent_req_nomem(state->subreqs, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->cldap = talloc_zero_array(state,
+ struct cldap_socket *,
+ num_servers);
+ if (tevent_req_nomem(state->cldap, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->responses = talloc_zero_array(state,
+ struct netlogon_samlogon_response *,
+ num_servers);
+ if (tevent_req_nomem(state->responses, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->ios = talloc_zero_array(state->responses,
+ struct cldap_netlogon,
+ num_servers);
+ if (tevent_req_nomem(state->ios, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_servers; i++) {
+ NTSTATUS status;
+
+ status = cldap_socket_init(state->cldap,
+ NULL, /* local_addr */
+ state->servers[i],
+ &state->cldap[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Don't error out all sends just
+ * because one cldap_socket_init() failed.
+ * Log it here, and the cldap_netlogon_send()
+ * will catch it (with in.dest_address == NULL)
+ * and correctly error out in
+ * cldap_multi_netlogon_done(). This still allows
+ * the other requests to be concurrently sent.
+ */
+ DBG_NOTICE("cldap_socket_init failed for %s "
+ " error %s\n",
+ tsocket_address_string(state->servers[i],
+ req),
+ nt_errstr(status));
+ }
+
+ state->ios[i].in.dest_address = NULL;
+ state->ios[i].in.dest_port = 0;
+ state->ios[i].in.realm = domain;
+ state->ios[i].in.host = NULL;
+ state->ios[i].in.user = NULL;
+ state->ios[i].in.domain_guid = NULL;
+ state->ios[i].in.domain_sid = NULL;
+ state->ios[i].in.acct_control = 0;
+ state->ios[i].in.version = ntversion;
+ state->ios[i].in.map_response = false;
+ }
+
+ for (i=0; i<min_servers; i++) {
+ state->subreqs[i] = cldap_netlogon_send(state->subreqs,
+ state->ev,
+ state->cldap[i],
+ &state->ios[i]);
+ if (tevent_req_nomem(state->subreqs[i], req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->subreqs[i], cldap_multi_netlogon_done, req);
+ }
+ state->num_sent = min_servers;
+
+ if (state->num_sent < state->num_servers) {
+ /*
+ * After 100 milliseconds fire the next one
+ */
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs(0, 100000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cldap_multi_netlogon_next,
+ req);
+ }
+
+ return req;
+}
+
+static void cldap_multi_netlogon_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cldap_multi_netlogon_state *state = tevent_req_data(
+ req, struct cldap_multi_netlogon_state);
+ NTSTATUS status;
+ struct netlogon_samlogon_response *response;
+ int i;
+
+ for (i=0; i<state->num_sent; i++) {
+ if (state->subreqs[i] == subreq) {
+ break;
+ }
+ }
+ if (i == state->num_sent) {
+ /*
+ * Got a response we did not fire...
+ */
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ state->subreqs[i] = NULL;
+
+ response = talloc_zero(state, struct netlogon_samlogon_response);
+ if (tevent_req_nomem(response, req)) {
+ return;
+ }
+
+ status = cldap_netlogon_recv(subreq, response,
+ &state->ios[i]);
+ TALLOC_FREE(subreq);
+ state->num_received += 1;
+
+ if (NT_STATUS_IS_OK(status)) {
+ *response = state->ios[i].out.netlogon;
+ state->responses[i] = talloc_move(state->responses,
+ &response);
+ state->num_good_received += 1;
+ }
+
+ if ((state->num_received == state->num_servers) ||
+ (state->num_good_received >= state->min_servers)) {
+ tevent_req_done(req);
+ return;
+ }
+}
+
+static void cldap_multi_netlogon_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cldap_multi_netlogon_state *state = tevent_req_data(
+ req, struct cldap_multi_netlogon_state);
+ bool ret;
+
+ ret = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ subreq = cldap_netlogon_send(state->subreqs,
+ state->ev,
+ state->cldap[state->num_sent],
+ &state->ios[state->num_sent]);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cldap_multi_netlogon_done, req);
+ state->subreqs[state->num_sent] = subreq;
+ state->num_sent += 1;
+
+ if (state->num_sent < state->num_servers) {
+ /*
+ * After 100 milliseconds fire the next one
+ */
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs(0, 100000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cldap_multi_netlogon_next,
+ req);
+ }
+}
+
+NTSTATUS cldap_multi_netlogon_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct netlogon_samlogon_response ***responses)
+{
+ struct cldap_multi_netlogon_state *state = tevent_req_data(
+ req, struct cldap_multi_netlogon_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ return status;
+ }
+ /*
+ * If we timeout, give back what we have so far
+ */
+ *responses = talloc_move(mem_ctx, &state->responses);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cldap_multi_netlogon(
+ TALLOC_CTX *mem_ctx,
+ const struct tsocket_address * const *servers,
+ int num_servers,
+ const char *domain, const char *hostname, unsigned ntversion,
+ int min_servers, struct timeval timeout,
+ struct netlogon_samlogon_response ***responses)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cldap_multi_netlogon_send(
+ ev, ev, servers, num_servers, domain, hostname, ntversion,
+ min_servers);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_set_endtime(req, ev, timeout)) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cldap_multi_netlogon_recv(req, mem_ctx, responses);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/*******************************************************************
+ do a cldap netlogon query. Always 389/udp
+*******************************************************************/
+
+bool ads_cldap_netlogon(TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage *ss,
+ const char *realm,
+ uint32_t nt_version,
+ struct netlogon_samlogon_response **_reply)
+{
+ NTSTATUS status;
+ char addrstr[INET6_ADDRSTRLEN];
+ const char *dest_str;
+ struct tsocket_address *dest_addr;
+ const struct tsocket_address * const *dest_addrs;
+ struct netlogon_samlogon_response **responses = NULL;
+ int ret;
+
+ dest_str = print_sockaddr(addrstr, sizeof(addrstr), ss);
+
+ ret = tsocket_address_inet_from_strings(mem_ctx, "ip",
+ dest_str, LDAP_PORT,
+ &dest_addr);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(2,("Failed to create cldap tsocket_address for %s - %s\n",
+ dest_str, nt_errstr(status)));
+ return false;
+ }
+
+ dest_addrs = (const struct tsocket_address * const *)&dest_addr;
+
+ status = cldap_multi_netlogon(talloc_tos(),
+ dest_addrs, 1,
+ realm, NULL,
+ nt_version, 1,
+ timeval_current_ofs(MAX(3,lp_ldap_timeout()/2), 0),
+ &responses);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("ads_cldap_netlogon: cldap_multi_netlogon "
+ "failed: %s\n", nt_errstr(status)));
+ return false;
+ }
+ if (responses == NULL || responses[0] == NULL) {
+ DEBUG(2, ("ads_cldap_netlogon: did not get a reply\n"));
+ TALLOC_FREE(responses);
+ return false;
+ }
+ *_reply = talloc_move(mem_ctx, &responses[0]);
+
+ return true;
+}
+
+/*******************************************************************
+ do a cldap netlogon query. Always 389/udp
+*******************************************************************/
+
+bool ads_cldap_netlogon_5(TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage *ss,
+ const char *realm,
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX *reply5)
+{
+ uint32_t nt_version = NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX;
+ struct netlogon_samlogon_response *reply = NULL;
+ bool ret;
+
+ ret = ads_cldap_netlogon(mem_ctx, ss, realm, nt_version, &reply);
+ if (!ret) {
+ return false;
+ }
+
+ if (reply->ntver != NETLOGON_NT_VERSION_5EX) {
+ DEBUG(0,("ads_cldap_netlogon_5: nt_version mismatch: 0x%08x\n",
+ reply->ntver));
+ return false;
+ }
+
+ *reply5 = reply->data.nt5_ex;
+
+ return true;
+}
diff --git a/source3/libads/cldap.h b/source3/libads/cldap.h
new file mode 100644
index 0000000..7fa1bdf
--- /dev/null
+++ b/source3/libads/cldap.h
@@ -0,0 +1,60 @@
+/*
+ Samba Unix/Linux SMB client library
+ net ads cldap functions
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2003 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2008 Guenther Deschner (gd@samba.org)
+ Copyright (C) 2009 Stefan Metzmacher (metze@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBADS_CLDAP_H_
+#define _LIBADS_CLDAP_H_
+
+#include "../libcli/netlogon/netlogon.h"
+
+/* The following definitions come from libads/cldap.c */
+
+bool check_cldap_reply_required_flags(uint32_t ret_flags,
+ uint32_t req_flags);
+
+struct tevent_req *cldap_multi_netlogon_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const struct tsocket_address * const *servers,
+ int num_servers,
+ const char *domain, const char *hostname, unsigned ntversion,
+ int min_servers);
+NTSTATUS cldap_multi_netlogon_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct netlogon_samlogon_response ***responses);
+NTSTATUS cldap_multi_netlogon(
+ TALLOC_CTX *mem_ctx,
+ const struct tsocket_address * const *servers,
+ int num_servers,
+ const char *domain, const char *hostname, unsigned ntversion,
+ int min_servers, struct timeval timeout,
+ struct netlogon_samlogon_response ***responses);
+
+bool ads_cldap_netlogon(TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage *ss,
+ const char *realm,
+ uint32_t nt_version,
+ struct netlogon_samlogon_response **reply);
+bool ads_cldap_netlogon_5(TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage *ss,
+ const char *realm,
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX *reply5);
+
+#endif /* _LIBADS_CLDAP_H_ */
diff --git a/source3/libads/disp_sec.c b/source3/libads/disp_sec.c
new file mode 100644
index 0000000..a193c5b
--- /dev/null
+++ b/source3/libads/disp_sec.c
@@ -0,0 +1,254 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions. ADS stuff
+ Copyright (C) Alexey Kotovich 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "libads/ldap_schema.h"
+#include "../libcli/security/secace.h"
+#include "../librpc/ndr/libndr.h"
+#include "libcli/security/dom_sid.h"
+
+/* for ADS */
+#define SEC_RIGHTS_FULL_CTRL 0xf01ff
+
+#ifdef HAVE_LDAP
+
+static struct perm_mask_str {
+ uint32_t mask;
+ const char *str;
+} perms[] = {
+ {SEC_RIGHTS_FULL_CTRL, "[Full Control]"},
+
+ {SEC_ADS_LIST, "[List Contents]"},
+ {SEC_ADS_LIST_OBJECT, "[List Object]"},
+
+ {SEC_ADS_READ_PROP, "[Read All Properties]"},
+ {SEC_STD_READ_CONTROL, "[Read Permissions]"},
+
+ {SEC_ADS_SELF_WRITE, "[All validate writes]"},
+ {SEC_ADS_WRITE_PROP, "[Write All Properties]"},
+
+ {SEC_STD_WRITE_DAC, "[Modify Permissions]"},
+ {SEC_STD_WRITE_OWNER, "[Modify Owner]"},
+
+ {SEC_ADS_CREATE_CHILD, "[Create All Child Objects]"},
+
+ {SEC_STD_DELETE, "[Delete]"},
+ {SEC_ADS_DELETE_TREE, "[Delete Subtree]"},
+ {SEC_ADS_DELETE_CHILD, "[Delete All Child Objects]"},
+
+ {SEC_ADS_CONTROL_ACCESS, "[Change Password]"},
+ {SEC_ADS_CONTROL_ACCESS, "[Reset Password]"},
+
+ {0, 0}
+};
+
+/* convert a security permissions into a string */
+static void ads_disp_perms(uint32_t type)
+{
+ int i = 0;
+ int j = 0;
+
+ printf("Permissions: ");
+
+ if (type == SEC_RIGHTS_FULL_CTRL) {
+ printf("%s\n", perms[j].str);
+ return;
+ }
+
+ for (i = 0; i < 32; i++) {
+ if (type & ((uint32_t)1 << i)) {
+ for (j = 1; perms[j].str; j ++) {
+ if (perms[j].mask == (((unsigned) 1) << i)) {
+ printf("\n\t%s (0x%08x)", perms[j].str, perms[j].mask);
+ }
+ }
+ type &= ~(1 << i);
+ }
+ }
+
+ /* remaining bits get added on as-is */
+ if (type != 0) {
+ printf("[%08x]", type);
+ }
+ puts("");
+}
+
+static const char *ads_interprete_guid_from_object(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ const struct GUID *guid)
+{
+ const char *ret = NULL;
+
+ if (!ads || !mem_ctx) {
+ return NULL;
+ }
+
+ ret = ads_get_attrname_by_guid(ads, ads->config.schema_path,
+ mem_ctx, guid);
+ if (ret) {
+ return talloc_asprintf(mem_ctx, "LDAP attribute: \"%s\"", ret);
+ }
+
+ ret = ads_get_extended_right_name_by_guid(ads, ads->config.config_path,
+ mem_ctx, guid);
+
+ if (ret) {
+ return talloc_asprintf(mem_ctx, "Extended right: \"%s\"", ret);
+ }
+
+ return ret;
+}
+
+static void ads_disp_sec_ace_object(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ struct security_ace_object *object)
+{
+ if (object->flags & SEC_ACE_OBJECT_TYPE_PRESENT) {
+ printf("Object type: SEC_ACE_OBJECT_TYPE_PRESENT\n");
+ printf("Object GUID: %s (%s)\n", GUID_string(mem_ctx,
+ &object->type.type),
+ ads_interprete_guid_from_object(ads, mem_ctx,
+ &object->type.type));
+ }
+ if (object->flags & SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT) {
+ printf("Object type: SEC_ACE_INHERITED_OBJECT_TYPE_PRESENT\n");
+ printf("Object GUID: %s (%s)\n", GUID_string(mem_ctx,
+ &object->inherited_type.inherited_type),
+ ads_interprete_guid_from_object(ads, mem_ctx,
+ &object->inherited_type.inherited_type));
+ }
+}
+
+/* display ACE */
+static void ads_disp_ace(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, struct security_ace *sec_ace)
+{
+ const char *access_type = "UNKNOWN";
+ struct dom_sid_buf sidbuf;
+
+ if (!sec_ace_object(sec_ace->type)) {
+ printf("------- ACE (type: 0x%02x, flags: 0x%02x, size: 0x%02x, mask: 0x%x)\n",
+ sec_ace->type,
+ sec_ace->flags,
+ sec_ace->size,
+ sec_ace->access_mask);
+ } else {
+ printf("------- ACE (type: 0x%02x, flags: 0x%02x, size: 0x%02x, mask: 0x%x, object flags: 0x%x)\n",
+ sec_ace->type,
+ sec_ace->flags,
+ sec_ace->size,
+ sec_ace->access_mask,
+ sec_ace->object.object.flags);
+ }
+
+ if (sec_ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED) {
+ access_type = "ALLOWED";
+ } else if (sec_ace->type == SEC_ACE_TYPE_ACCESS_DENIED) {
+ access_type = "DENIED";
+ } else if (sec_ace->type == SEC_ACE_TYPE_SYSTEM_AUDIT) {
+ access_type = "SYSTEM AUDIT";
+ } else if (sec_ace->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT) {
+ access_type = "ALLOWED OBJECT";
+ } else if (sec_ace->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT) {
+ access_type = "DENIED OBJECT";
+ } else if (sec_ace->type == SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT) {
+ access_type = "AUDIT OBJECT";
+ }
+
+ printf("access SID: %s\naccess type: %s\n",
+ dom_sid_str_buf(&sec_ace->trustee, &sidbuf),
+ access_type);
+
+ if (sec_ace_object(sec_ace->type)) {
+ ads_disp_sec_ace_object(ads, mem_ctx, &sec_ace->object.object);
+ }
+
+ ads_disp_perms(sec_ace->access_mask);
+}
+
+/* display ACL */
+static void ads_disp_acl(struct security_acl *sec_acl, const char *type)
+{
+ if (!sec_acl)
+ printf("------- (%s) ACL not present\n", type);
+ else {
+ printf("------- (%s) ACL (revision: %d, size: %d, number of ACEs: %d)\n",
+ type,
+ sec_acl->revision,
+ sec_acl->size,
+ sec_acl->num_aces);
+ }
+}
+
+/* display SD */
+void ads_disp_sd(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, struct security_descriptor *sd)
+{
+ uint32_t i;
+ char *tmp_path = NULL;
+ struct dom_sid_buf buf;
+
+ if (!sd) {
+ return;
+ }
+
+ if (ads && !ads->config.schema_path) {
+ if (ADS_ERR_OK(ads_schema_path(ads, mem_ctx, &tmp_path))) {
+ ads->config.schema_path = talloc_strdup(ads, tmp_path);
+ if (ads->config.schema_path == NULL) {
+ DBG_WARNING("Out of memory\n");
+ }
+ }
+ }
+
+ if (ads && !ads->config.config_path) {
+ if (ADS_ERR_OK(ads_config_path(ads, mem_ctx, &tmp_path))) {
+ ads->config.config_path = talloc_strdup(ads, tmp_path);
+ if (ads->config.config_path == NULL) {
+ DBG_WARNING("Out of memory\n");
+ }
+ }
+ }
+
+ printf("-------------- Security Descriptor (revision: %d, type: 0x%02x)\n",
+ sd->revision,
+ sd->type);
+
+ printf("owner SID: %s\n", sd->owner_sid ?
+ dom_sid_str_buf(sd->owner_sid, &buf) : "(null)");
+ printf("group SID: %s\n", sd->group_sid ?
+ dom_sid_str_buf(sd->group_sid, &buf) : "(null)");
+
+ ads_disp_acl(sd->sacl, "system");
+ if (sd->sacl) {
+ for (i = 0; i < sd->sacl->num_aces; i ++) {
+ ads_disp_ace(ads, mem_ctx, &sd->sacl->aces[i]);
+ }
+ }
+
+ ads_disp_acl(sd->dacl, "user");
+ if (sd->dacl) {
+ for (i = 0; i < sd->dacl->num_aces; i ++) {
+ ads_disp_ace(ads, mem_ctx, &sd->dacl->aces[i]);
+ }
+ }
+
+ printf("-------------- End Of Security Descriptor\n");
+}
+
+#endif
diff --git a/source3/libads/kerberos.c b/source3/libads/kerberos.c
new file mode 100644
index 0000000..f76c566
--- /dev/null
+++ b/source3/libads/kerberos.c
@@ -0,0 +1,908 @@
+/*
+ Unix SMB/CIFS implementation.
+ kerberos utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Nalin Dahyabhai <nalin@redhat.com> 2004.
+ Copyright (C) Jeremy Allison 2004.
+ Copyright (C) Gerald Carter 2006.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "system/filesys.h"
+#include "smb_krb5.h"
+#include "../librpc/gen_ndr/ndr_misc.h"
+#include "libads/kerberos_proto.h"
+#include "libads/cldap.h"
+#include "secrets.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/asn1.h"
+
+#ifdef HAVE_KRB5
+
+#define LIBADS_CCACHE_NAME "MEMORY:libads"
+
+/*
+ we use a prompter to avoid a crash bug in the kerberos libs when
+ dealing with empty passwords
+ this prompter is just a string copy ...
+*/
+static krb5_error_code
+kerb_prompter(krb5_context ctx, void *data,
+ const char *name,
+ const char *banner,
+ int num_prompts,
+ krb5_prompt prompts[])
+{
+ if (num_prompts == 0) return 0;
+ if (num_prompts == 2) {
+ /*
+ * only heimdal has a prompt type and we need to deal with it here to
+ * avoid loops.
+ *
+ * removing the prompter completely is not an option as at least these
+ * versions would crash: heimdal-1.0.2 and heimdal-1.1. Later heimdal
+ * version have looping detection and return with a proper error code.
+ */
+
+#if defined(HAVE_KRB5_PROMPT_TYPE) /* Heimdal */
+ if (prompts[0].type == KRB5_PROMPT_TYPE_NEW_PASSWORD &&
+ prompts[1].type == KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN) {
+ /*
+ * We don't want to change passwords here. We're
+ * called from heimdal when the KDC returns
+ * KRB5KDC_ERR_KEY_EXPIRED, but at this point we don't
+ * have the chance to ask the user for a new
+ * password. If we return 0 (i.e. success), we will be
+ * spinning in the endless for-loop in
+ * change_password() in
+ * third_party/heimdal/lib/krb5/init_creds_pw.c
+ */
+ return KRB5KDC_ERR_KEY_EXPIRED;
+ }
+#elif defined(HAVE_KRB5_GET_PROMPT_TYPES) /* MIT */
+ krb5_prompt_type *prompt_types = NULL;
+
+ prompt_types = krb5_get_prompt_types(ctx);
+ if (prompt_types != NULL) {
+ if (prompt_types[0] == KRB5_PROMPT_TYPE_NEW_PASSWORD &&
+ prompt_types[1] == KRB5_PROMPT_TYPE_NEW_PASSWORD_AGAIN) {
+ return KRB5KDC_ERR_KEY_EXP;
+ }
+ }
+#endif
+ }
+
+ memset(prompts[0].reply->data, '\0', prompts[0].reply->length);
+ if (prompts[0].reply->length > 0) {
+ if (data) {
+ strncpy((char *)prompts[0].reply->data, (const char *)data,
+ prompts[0].reply->length-1);
+ prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
+ } else {
+ prompts[0].reply->length = 0;
+ }
+ }
+ return 0;
+}
+
+/*
+ simulate a kinit, putting the tgt in the given cache location. If cache_name == NULL
+ place in default cache location.
+ remus@snapserver.com
+*/
+int kerberos_kinit_password_ext(const char *given_principal,
+ const char *password,
+ int time_offset,
+ time_t *expire_time,
+ time_t *renew_till_time,
+ const char *cache_name,
+ bool request_pac,
+ bool add_netbios_addr,
+ time_t renewable_time,
+ TALLOC_CTX *mem_ctx,
+ char **_canon_principal,
+ char **_canon_realm,
+ NTSTATUS *ntstatus)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ krb5_context ctx = NULL;
+ krb5_error_code code = 0;
+ krb5_ccache cc = NULL;
+ krb5_principal me = NULL;
+ krb5_principal canon_princ = NULL;
+ krb5_creds my_creds;
+ krb5_get_init_creds_opt *opt = NULL;
+ smb_krb5_addresses *addr = NULL;
+ char *canon_principal = NULL;
+ char *canon_realm = NULL;
+
+ ZERO_STRUCT(my_creds);
+
+ code = smb_krb5_init_context_common(&ctx);
+ if (code != 0) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(code));
+ TALLOC_FREE(frame);
+ return code;
+ }
+
+ if (time_offset != 0) {
+ krb5_set_real_time(ctx, time(NULL) + time_offset, 0);
+ }
+
+ DBG_DEBUG("as %s using [%s] as ccache and config [%s]\n",
+ given_principal,
+ cache_name ? cache_name: krb5_cc_default_name(ctx),
+ getenv("KRB5_CONFIG"));
+
+ if ((code = krb5_cc_resolve(ctx, cache_name ? cache_name : krb5_cc_default_name(ctx), &cc))) {
+ goto out;
+ }
+
+ if ((code = smb_krb5_parse_name(ctx, given_principal, &me))) {
+ goto out;
+ }
+
+ if ((code = krb5_get_init_creds_opt_alloc(ctx, &opt))) {
+ goto out;
+ }
+
+ krb5_get_init_creds_opt_set_renew_life(opt, renewable_time);
+ krb5_get_init_creds_opt_set_forwardable(opt, True);
+
+ /* Turn on canonicalization for lower case realm support */
+#ifdef SAMBA4_USES_HEIMDAL
+ krb5_get_init_creds_opt_set_win2k(ctx, opt, true);
+ krb5_get_init_creds_opt_set_canonicalize(ctx, opt, true);
+#else /* MIT */
+ krb5_get_init_creds_opt_set_canonicalize(opt, true);
+#endif /* MIT */
+#if 0
+ /* insane testing */
+ krb5_get_init_creds_opt_set_tkt_life(opt, 60);
+#endif
+
+#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PAC_REQUEST
+ if (request_pac) {
+ if ((code = krb5_get_init_creds_opt_set_pac_request(ctx, opt, (krb5_boolean)request_pac))) {
+ goto out;
+ }
+ }
+#endif
+ if (add_netbios_addr) {
+ if ((code = smb_krb5_gen_netbios_krb5_address(&addr,
+ lp_netbios_name()))) {
+ goto out;
+ }
+ krb5_get_init_creds_opt_set_address_list(opt, addr->addrs);
+ }
+
+ if ((code = krb5_get_init_creds_password(ctx, &my_creds, me, discard_const_p(char,password),
+ kerb_prompter, discard_const_p(char, password),
+ 0, NULL, opt))) {
+ goto out;
+ }
+
+ canon_princ = my_creds.client;
+
+ code = smb_krb5_unparse_name(frame,
+ ctx,
+ canon_princ,
+ &canon_principal);
+ if (code != 0) {
+ goto out;
+ }
+
+ DBG_DEBUG("%s mapped to %s\n", given_principal, canon_principal);
+
+ canon_realm = smb_krb5_principal_get_realm(frame, ctx, canon_princ);
+ if (canon_realm == NULL) {
+ code = ENOMEM;
+ goto out;
+ }
+
+ if ((code = krb5_cc_initialize(ctx, cc, canon_princ))) {
+ goto out;
+ }
+
+ if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) {
+ goto out;
+ }
+
+ if (expire_time) {
+ *expire_time = (time_t) my_creds.times.endtime;
+ }
+
+ if (renew_till_time) {
+ *renew_till_time = (time_t) my_creds.times.renew_till;
+ }
+
+ if (_canon_principal != NULL) {
+ *_canon_principal = talloc_move(mem_ctx, &canon_principal);
+ }
+ if (_canon_realm != NULL) {
+ *_canon_realm = talloc_move(mem_ctx, &canon_realm);
+ }
+ out:
+ if (ntstatus) {
+ /* fast path */
+ if (code == 0) {
+ *ntstatus = NT_STATUS_OK;
+ goto cleanup;
+ }
+
+ /* fall back to self-made-mapping */
+ *ntstatus = krb5_to_nt_status(code);
+ }
+
+ cleanup:
+ krb5_free_cred_contents(ctx, &my_creds);
+ if (me) {
+ krb5_free_principal(ctx, me);
+ }
+ if (addr) {
+ smb_krb5_free_addresses(ctx, addr);
+ }
+ if (opt) {
+ krb5_get_init_creds_opt_free(ctx, opt);
+ }
+ if (cc) {
+ krb5_cc_close(ctx, cc);
+ }
+ if (ctx) {
+ krb5_free_context(ctx);
+ }
+ TALLOC_FREE(frame);
+ return code;
+}
+
+int ads_kdestroy(const char *cc_name)
+{
+ krb5_error_code code;
+ krb5_context ctx = NULL;
+ krb5_ccache cc = NULL;
+
+ code = smb_krb5_init_context_common(&ctx);
+ if (code != 0) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(code));
+ return code;
+ }
+
+ if (!cc_name) {
+ if ((code = krb5_cc_default(ctx, &cc))) {
+ krb5_free_context(ctx);
+ return code;
+ }
+ } else {
+ if ((code = krb5_cc_resolve(ctx, cc_name, &cc))) {
+ DEBUG(3, ("ads_kdestroy: krb5_cc_resolve failed: %s\n",
+ error_message(code)));
+ krb5_free_context(ctx);
+ return code;
+ }
+ }
+
+ if ((code = krb5_cc_destroy (ctx, cc))) {
+ DEBUG(3, ("ads_kdestroy: krb5_cc_destroy failed: %s\n",
+ error_message(code)));
+ }
+
+ krb5_free_context (ctx);
+ return code;
+}
+
+int create_kerberos_key_from_string(krb5_context context,
+ krb5_principal host_princ,
+ krb5_principal salt_princ,
+ krb5_data *password,
+ krb5_keyblock *key,
+ krb5_enctype enctype,
+ bool no_salt)
+{
+ int ret;
+ /*
+ * Check if we've determined that the KDC is salting keys for this
+ * principal/enctype in a non-obvious way. If it is, try to match
+ * its behavior.
+ */
+ if (no_salt) {
+ KRB5_KEY_DATA(key) = (KRB5_KEY_DATA_CAST *)SMB_MALLOC(password->length);
+ if (!KRB5_KEY_DATA(key)) {
+ return ENOMEM;
+ }
+ memcpy(KRB5_KEY_DATA(key), password->data, password->length);
+ KRB5_KEY_LENGTH(key) = password->length;
+ KRB5_KEY_TYPE(key) = enctype;
+ return 0;
+ }
+ ret = smb_krb5_create_key_from_string(context,
+ salt_princ ? salt_princ : host_princ,
+ NULL,
+ password,
+ enctype,
+ key);
+ return ret;
+}
+
+/************************************************************************
+************************************************************************/
+
+int kerberos_kinit_password(const char *principal,
+ const char *password,
+ int time_offset,
+ const char *cache_name)
+{
+ return kerberos_kinit_password_ext(principal,
+ password,
+ time_offset,
+ 0,
+ 0,
+ cache_name,
+ False,
+ False,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+}
+
+/************************************************************************
+************************************************************************/
+
+/************************************************************************
+ Create a string list of available kdc's, possibly searching by sitename.
+ Does DNS queries.
+
+ If "sitename" is given, the DC's in that site are listed first.
+
+************************************************************************/
+
+static void add_sockaddr_unique(struct sockaddr_storage *addrs, size_t *num_addrs,
+ const struct sockaddr_storage *addr)
+{
+ size_t i;
+
+ for (i=0; i<*num_addrs; i++) {
+ if (sockaddr_equal((const struct sockaddr *)&addrs[i],
+ (const struct sockaddr *)addr)) {
+ return;
+ }
+ }
+ addrs[i] = *addr;
+ *num_addrs += 1;
+}
+
+/* print_canonical_sockaddr prints an ipv6 addr in the form of
+* [ipv6.addr]. This string, when put in a generated krb5.conf file is not
+* always properly dealt with by some older krb5 libraries. Adding the hard-coded
+* portnumber workarounds the issue. - gd */
+
+static char *print_canonical_sockaddr_with_port(TALLOC_CTX *mem_ctx,
+ const struct sockaddr_storage *pss)
+{
+ char *str = NULL;
+
+ str = print_canonical_sockaddr(mem_ctx, pss);
+ if (str == NULL) {
+ return NULL;
+ }
+
+ if (pss->ss_family != AF_INET6) {
+ return str;
+ }
+
+#if defined(HAVE_IPV6)
+ str = talloc_asprintf_append(str, ":88");
+#endif
+ return str;
+}
+
+static char *get_kdc_ip_string(char *mem_ctx,
+ const char *realm,
+ const char *sitename,
+ const struct sockaddr_storage *pss)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ size_t i;
+ struct samba_sockaddr *ip_sa_site = NULL;
+ struct samba_sockaddr *ip_sa_nonsite = NULL;
+ struct samba_sockaddr sa = {0};
+ size_t count_site = 0;
+ size_t count_nonsite;
+ size_t num_dcs;
+ struct sockaddr_storage *dc_addrs = NULL;
+ struct tsocket_address **dc_addrs2 = NULL;
+ const struct tsocket_address * const *dc_addrs3 = NULL;
+ char *result = NULL;
+ struct netlogon_samlogon_response **responses = NULL;
+ NTSTATUS status;
+ bool ok;
+ char *kdc_str = NULL;
+ char *canon_sockaddr = NULL;
+
+ SMB_ASSERT(pss != NULL);
+
+ canon_sockaddr = print_canonical_sockaddr_with_port(frame, pss);
+ if (canon_sockaddr == NULL) {
+ goto out;
+ }
+
+ kdc_str = talloc_asprintf(frame,
+ "\t\tkdc = %s\n",
+ canon_sockaddr);
+ if (kdc_str == NULL) {
+ goto out;
+ }
+
+ ok = sockaddr_storage_to_samba_sockaddr(&sa, pss);
+ if (!ok) {
+ goto out;
+ }
+
+ /*
+ * First get the KDC's only in this site, the rest will be
+ * appended later
+ */
+
+ if (sitename) {
+ status = get_kdc_list(frame,
+ realm,
+ sitename,
+ &ip_sa_site,
+ &count_site);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("get_kdc_list fail %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+ DBG_DEBUG("got %zu addresses from site %s search\n",
+ count_site,
+ sitename);
+ }
+
+ /* Get all KDC's. */
+
+ status = get_kdc_list(frame,
+ realm,
+ NULL,
+ &ip_sa_nonsite,
+ &count_nonsite);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("get_kdc_list (site-less) fail %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+ DBG_DEBUG("got %zu addresses from site-less search\n", count_nonsite);
+
+ if (count_site + count_nonsite < count_site) {
+ /* Wrap check. */
+ DBG_ERR("get_kdc_list_talloc (site-less) fail wrap error\n");
+ goto out;
+ }
+
+
+ dc_addrs = talloc_array(talloc_tos(), struct sockaddr_storage,
+ count_site + count_nonsite);
+ if (dc_addrs == NULL) {
+ goto out;
+ }
+
+ num_dcs = 0;
+
+ for (i = 0; i < count_site; i++) {
+ if (!sockaddr_equal(&sa.u.sa, &ip_sa_site[i].u.sa)) {
+ add_sockaddr_unique(dc_addrs, &num_dcs,
+ &ip_sa_site[i].u.ss);
+ }
+ }
+
+ for (i = 0; i < count_nonsite; i++) {
+ if (!sockaddr_equal(&sa.u.sa, &ip_sa_nonsite[i].u.sa)) {
+ add_sockaddr_unique(dc_addrs, &num_dcs,
+ &ip_sa_nonsite[i].u.ss);
+ }
+ }
+
+ DBG_DEBUG("%zu additional KDCs to test\n", num_dcs);
+ if (num_dcs == 0) {
+ /*
+ * We do not have additional KDCs, but we have the one passed
+ * in via `pss`. So just use that one and leave.
+ */
+ result = talloc_move(mem_ctx, &kdc_str);
+ goto out;
+ }
+
+ dc_addrs2 = talloc_zero_array(talloc_tos(),
+ struct tsocket_address *,
+ num_dcs);
+ if (dc_addrs2 == NULL) {
+ goto out;
+ }
+
+ for (i=0; i<num_dcs; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ int ret;
+
+ print_sockaddr(addr, sizeof(addr), &dc_addrs[i]);
+
+ ret = tsocket_address_inet_from_strings(dc_addrs2, "ip",
+ addr, LDAP_PORT,
+ &dc_addrs2[i]);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(2,("Failed to create tsocket_address for %s - %s\n",
+ addr, nt_errstr(status)));
+ goto out;
+ }
+ }
+
+ dc_addrs3 = (const struct tsocket_address * const *)dc_addrs2;
+
+ status = cldap_multi_netlogon(talloc_tos(),
+ dc_addrs3, num_dcs,
+ realm, lp_netbios_name(),
+ NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX,
+ MIN(num_dcs, 3), timeval_current_ofs(3, 0), &responses);
+ TALLOC_FREE(dc_addrs2);
+ dc_addrs3 = NULL;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("get_kdc_ip_string: cldap_multi_netlogon failed: "
+ "%s\n", nt_errstr(status)));
+ goto out;
+ }
+
+ for (i=0; i<num_dcs; i++) {
+ char *new_kdc_str;
+
+ if (responses[i] == NULL) {
+ continue;
+ }
+
+ /* Append to the string - inefficient but not done often. */
+ new_kdc_str = talloc_asprintf_append(
+ kdc_str,
+ "\t\tkdc = %s\n",
+ print_canonical_sockaddr_with_port(
+ mem_ctx, &dc_addrs[i]));
+ if (new_kdc_str == NULL) {
+ goto out;
+ }
+ kdc_str = new_kdc_str;
+ }
+
+ result = talloc_move(mem_ctx, &kdc_str);
+out:
+ if (result != NULL) {
+ DBG_DEBUG("Returning\n%s\n", result);
+ } else {
+ DBG_NOTICE("Failed to get KDC ip address\n");
+ }
+
+ TALLOC_FREE(frame);
+ return result;
+}
+
+/************************************************************************
+ Create a specific krb5.conf file in the private directory pointing
+ at a specific kdc for a realm. Keyed off domain name. Sets
+ KRB5_CONFIG environment variable to point to this file. Must be
+ run as root or will fail (which is a good thing :-).
+************************************************************************/
+
+#if !defined(SAMBA4_USES_HEIMDAL) /* MIT version */
+static char *get_enctypes(TALLOC_CTX *mem_ctx)
+{
+ char *aes_enctypes = NULL;
+ const char *legacy_enctypes = "";
+ char *enctypes = NULL;
+
+ aes_enctypes = talloc_strdup(mem_ctx, "");
+ if (aes_enctypes == NULL) {
+ goto done;
+ }
+
+ if (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+ lp_kerberos_encryption_types() == KERBEROS_ETYPES_STRONG) {
+ aes_enctypes = talloc_asprintf_append(
+ aes_enctypes, "%s", "aes256-cts-hmac-sha1-96 ");
+ if (aes_enctypes == NULL) {
+ goto done;
+ }
+ aes_enctypes = talloc_asprintf_append(
+ aes_enctypes, "%s", "aes128-cts-hmac-sha1-96");
+ if (aes_enctypes == NULL) {
+ goto done;
+ }
+ }
+
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_ALLOWED &&
+ (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+ lp_kerberos_encryption_types() == KERBEROS_ETYPES_LEGACY)) {
+ legacy_enctypes = "RC4-HMAC";
+ }
+
+ enctypes =
+ talloc_asprintf(mem_ctx, "\tdefault_tgs_enctypes = %s %s\n"
+ "\tdefault_tkt_enctypes = %s %s\n"
+ "\tpreferred_enctypes = %s %s\n",
+ aes_enctypes, legacy_enctypes, aes_enctypes,
+ legacy_enctypes, aes_enctypes, legacy_enctypes);
+done:
+ TALLOC_FREE(aes_enctypes);
+ return enctypes;
+}
+#else /* Heimdal version */
+static char *get_enctypes(TALLOC_CTX *mem_ctx)
+{
+ const char *aes_enctypes = "";
+ const char *legacy_enctypes = "";
+ char *enctypes = NULL;
+
+ if (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+ lp_kerberos_encryption_types() == KERBEROS_ETYPES_STRONG) {
+ aes_enctypes =
+ "aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96";
+ }
+
+ if (lp_kerberos_encryption_types() == KERBEROS_ETYPES_ALL ||
+ lp_kerberos_encryption_types() == KERBEROS_ETYPES_LEGACY) {
+ legacy_enctypes = "arcfour-hmac-md5";
+ }
+
+ enctypes = talloc_asprintf(mem_ctx, "\tdefault_etypes = %s %s\n",
+ aes_enctypes, legacy_enctypes);
+
+ return enctypes;
+}
+#endif
+
+bool create_local_private_krb5_conf_for_domain(const char *realm,
+ const char *domain,
+ const char *sitename,
+ const struct sockaddr_storage *pss)
+{
+ char *dname;
+ char *tmpname = NULL;
+ char *fname = NULL;
+ char *file_contents = NULL;
+ char *kdc_ip_string = NULL;
+ size_t flen = 0;
+ ssize_t ret;
+ int fd;
+ char *realm_upper = NULL;
+ bool result = false;
+ char *enctypes = NULL;
+ const char *include_system_krb5 = "";
+ mode_t mask;
+
+ if (!lp_create_krb5_conf()) {
+ return false;
+ }
+
+ if (realm == NULL) {
+ DEBUG(0, ("No realm has been specified! Do you really want to "
+ "join an Active Directory server?\n"));
+ return false;
+ }
+
+ if (domain == NULL || pss == NULL) {
+ return false;
+ }
+
+ dname = lock_path(talloc_tos(), "smb_krb5");
+ if (!dname) {
+ return false;
+ }
+ if ((mkdir(dname, 0755)==-1) && (errno != EEXIST)) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: "
+ "failed to create directory %s. Error was %s\n",
+ dname, strerror(errno) ));
+ goto done;
+ }
+
+ tmpname = lock_path(talloc_tos(), "smb_tmp_krb5.XXXXXX");
+ if (!tmpname) {
+ goto done;
+ }
+
+ fname = talloc_asprintf(dname, "%s/krb5.conf.%s", dname, domain);
+ if (!fname) {
+ goto done;
+ }
+
+ DEBUG(10,("create_local_private_krb5_conf_for_domain: fname = %s, realm = %s, domain = %s\n",
+ fname, realm, domain ));
+
+ realm_upper = talloc_strdup(fname, realm);
+ if (!strupper_m(realm_upper)) {
+ goto done;
+ }
+
+ kdc_ip_string = get_kdc_ip_string(dname, realm, sitename, pss);
+ if (!kdc_ip_string) {
+ goto done;
+ }
+
+ enctypes = get_enctypes(fname);
+ if (enctypes == NULL) {
+ goto done;
+ }
+
+#if !defined(SAMBA4_USES_HEIMDAL)
+ if (lp_include_system_krb5_conf()) {
+ include_system_krb5 = "include /etc/krb5.conf";
+ }
+#endif
+
+ /*
+ * We are setting 'dns_lookup_kdc' to true, because we want to lookup
+ * KDCs which are not configured via DNS SRV records, eg. if we do:
+ *
+ * net ads join -Uadmin@otherdomain
+ */
+ file_contents =
+ talloc_asprintf(fname,
+ "[libdefaults]\n"
+ "\tdefault_realm = %s\n"
+ "%s"
+ "\tdns_lookup_realm = false\n"
+ "\tdns_lookup_kdc = true\n\n"
+ "[realms]\n\t%s = {\n"
+ "%s\t}\n"
+ "\t%s = {\n"
+ "%s\t}\n"
+ "%s\n",
+ realm_upper,
+ enctypes,
+ realm_upper,
+ kdc_ip_string,
+ domain,
+ kdc_ip_string,
+ include_system_krb5);
+
+ if (!file_contents) {
+ goto done;
+ }
+
+ flen = strlen(file_contents);
+
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(tmpname);
+ umask(mask);
+ if (fd == -1) {
+ DBG_ERR("mkstemp failed, for file %s. Errno %s\n",
+ tmpname,
+ strerror(errno));
+ goto done;
+ }
+
+ if (fchmod(fd, 0644)==-1) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: fchmod failed for %s."
+ " Errno %s\n",
+ tmpname, strerror(errno) ));
+ unlink(tmpname);
+ close(fd);
+ goto done;
+ }
+
+ ret = write(fd, file_contents, flen);
+ if (flen != ret) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: write failed,"
+ " returned %d (should be %u). Errno %s\n",
+ (int)ret, (unsigned int)flen, strerror(errno) ));
+ unlink(tmpname);
+ close(fd);
+ goto done;
+ }
+ if (close(fd)==-1) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: close failed."
+ " Errno %s\n", strerror(errno) ));
+ unlink(tmpname);
+ goto done;
+ }
+
+ if (rename(tmpname, fname) == -1) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: rename "
+ "of %s to %s failed. Errno %s\n",
+ tmpname, fname, strerror(errno) ));
+ unlink(tmpname);
+ goto done;
+ }
+
+ DBG_INFO("wrote file %s with realm %s KDC list:\n%s\n",
+ fname, realm_upper, kdc_ip_string);
+
+ /* Set the environment variable to this file. */
+ setenv("KRB5_CONFIG", fname, 1);
+
+ result = true;
+
+#if defined(OVERWRITE_SYSTEM_KRB5_CONF)
+
+#define SYSTEM_KRB5_CONF_PATH "/etc/krb5.conf"
+ /* Insanity, sheer insanity..... */
+
+ if (strequal(realm, lp_realm())) {
+ SMB_STRUCT_STAT sbuf;
+
+ if (sys_lstat(SYSTEM_KRB5_CONF_PATH, &sbuf, false) == 0) {
+ if (S_ISLNK(sbuf.st_ex_mode) && sbuf.st_ex_size) {
+ int lret;
+ size_t alloc_size = sbuf.st_ex_size + 1;
+ char *linkpath = talloc_array(talloc_tos(), char,
+ alloc_size);
+ if (!linkpath) {
+ goto done;
+ }
+ lret = readlink(SYSTEM_KRB5_CONF_PATH, linkpath,
+ alloc_size - 1);
+ if (lret == -1) {
+ TALLOC_FREE(linkpath);
+ goto done;
+ }
+ linkpath[lret] = '\0';
+
+ if (strcmp(linkpath, fname) == 0) {
+ /* Symlink already exists. */
+ TALLOC_FREE(linkpath);
+ goto done;
+ }
+ TALLOC_FREE(linkpath);
+ }
+ }
+
+ /* Try and replace with a symlink. */
+ if (symlink(fname, SYSTEM_KRB5_CONF_PATH) == -1) {
+ const char *newpath = SYSTEM_KRB5_CONF_PATH ".saved";
+ if (errno != EEXIST) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: symlink "
+ "of %s to %s failed. Errno %s\n",
+ fname, SYSTEM_KRB5_CONF_PATH, strerror(errno) ));
+ goto done; /* Not a fatal error. */
+ }
+
+ /* Yes, this is a race condition... too bad. */
+ if (rename(SYSTEM_KRB5_CONF_PATH, newpath) == -1) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: rename "
+ "of %s to %s failed. Errno %s\n",
+ SYSTEM_KRB5_CONF_PATH, newpath,
+ strerror(errno) ));
+ goto done; /* Not a fatal error. */
+ }
+
+ if (symlink(fname, SYSTEM_KRB5_CONF_PATH) == -1) {
+ DEBUG(0,("create_local_private_krb5_conf_for_domain: "
+ "forced symlink of %s to /etc/krb5.conf failed. Errno %s\n",
+ fname, strerror(errno) ));
+ goto done; /* Not a fatal error. */
+ }
+ }
+ }
+#endif
+
+done:
+ TALLOC_FREE(tmpname);
+ TALLOC_FREE(dname);
+
+ return result;
+}
+#endif
diff --git a/source3/libads/kerberos_keytab.c b/source3/libads/kerberos_keytab.c
new file mode 100644
index 0000000..466211a
--- /dev/null
+++ b/source3/libads/kerberos_keytab.c
@@ -0,0 +1,1026 @@
+/*
+ Unix SMB/CIFS implementation.
+ kerberos keytab utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Luke Howard 2003
+ Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
+ Copyright (C) Guenther Deschner 2003
+ Copyright (C) Rakesh Patel 2004
+ Copyright (C) Dan Perry 2004
+ Copyright (C) Jeremy Allison 2004
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smb_krb5.h"
+#include "ads.h"
+#include "secrets.h"
+
+#ifdef HAVE_KRB5
+
+#ifdef HAVE_ADS
+
+/* This MAX_NAME_LEN is a constant defined in krb5.h */
+#ifndef MAX_KEYTAB_NAME_LEN
+#define MAX_KEYTAB_NAME_LEN 1100
+#endif
+
+static krb5_error_code ads_keytab_open(krb5_context context,
+ krb5_keytab *keytab)
+{
+ char keytab_str[MAX_KEYTAB_NAME_LEN] = {0};
+ const char *keytab_name = NULL;
+ krb5_error_code ret = 0;
+
+ switch (lp_kerberos_method()) {
+ case KERBEROS_VERIFY_SYSTEM_KEYTAB:
+ case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
+ ret = krb5_kt_default_name(context,
+ keytab_str,
+ sizeof(keytab_str) - 2);
+ if (ret != 0) {
+ DBG_WARNING("Failed to get default keytab name\n");
+ goto out;
+ }
+ keytab_name = keytab_str;
+ break;
+ case KERBEROS_VERIFY_DEDICATED_KEYTAB:
+ keytab_name = lp_dedicated_keytab_file();
+ break;
+ default:
+ DBG_ERR("Invalid kerberos method set (%d)\n",
+ lp_kerberos_method());
+ ret = KRB5_KT_BADNAME;
+ goto out;
+ }
+
+ if (keytab_name == NULL || keytab_name[0] == '\0') {
+ DBG_ERR("Invalid keytab name\n");
+ ret = KRB5_KT_BADNAME;
+ goto out;
+ }
+
+ ret = smb_krb5_kt_open(context, keytab_name, true, keytab);
+ if (ret != 0) {
+ DBG_WARNING("smb_krb5_kt_open failed (%s)\n",
+ error_message(ret));
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static bool fill_default_spns(TALLOC_CTX *ctx, const char *machine_name,
+ const char *my_fqdn, const char *spn,
+ const char ***spns)
+{
+ char *psp1, *psp2;
+
+ if (*spns == NULL) {
+ *spns = talloc_zero_array(ctx, const char*, 3);
+ if (*spns == NULL) {
+ return false;
+ }
+ }
+
+ psp1 = talloc_asprintf(ctx,
+ "%s/%s",
+ spn,
+ machine_name);
+ if (psp1 == NULL) {
+ return false;
+ }
+
+ if (!strlower_m(&psp1[strlen(spn) + 1])) {
+ return false;
+ }
+ (*spns)[0] = psp1;
+
+ psp2 = talloc_asprintf(ctx,
+ "%s/%s",
+ spn,
+ my_fqdn);
+ if (psp2 == NULL) {
+ return false;
+ }
+
+ if (!strlower_m(&psp2[strlen(spn) + 1])) {
+ return false;
+ }
+
+ (*spns)[1] = psp2;
+
+ return true;
+}
+
+static bool ads_set_machine_account_spns(TALLOC_CTX *ctx,
+ ADS_STRUCT *ads,
+ const char *service_or_spn,
+ const char *my_fqdn)
+{
+ const char **spn_names = NULL;
+ ADS_STATUS aderr;
+ struct spn_struct* spn_struct = NULL;
+ char *tmp = NULL;
+
+ /* SPN should have '/' */
+ tmp = strchr_m(service_or_spn, '/');
+ if (tmp != NULL) {
+ spn_struct = parse_spn(ctx, service_or_spn);
+ if (spn_struct == NULL) {
+ return false;
+ }
+ }
+
+ DBG_INFO("Attempting to add/update '%s'\n", service_or_spn);
+
+ if (spn_struct != NULL) {
+ spn_names = talloc_zero_array(ctx, const char*, 2);
+ spn_names[0] = service_or_spn;
+ } else {
+ bool ok;
+
+ ok = fill_default_spns(ctx,
+ lp_netbios_name(),
+ my_fqdn,
+ service_or_spn,
+ &spn_names);
+ if (!ok) {
+ return false;
+ }
+ }
+ aderr = ads_add_service_principal_names(ads,
+ lp_netbios_name(),
+ spn_names);
+ if (!ADS_ERR_OK(aderr)) {
+ DBG_WARNING("Failed to add service principal name.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Create kerberos principal(s) from SPN or service name.
+ */
+static bool service_or_spn_to_kerberos_princ(TALLOC_CTX *ctx,
+ const char *service_or_spn,
+ const char *my_fqdn,
+ char **p_princ_s,
+ char **p_short_princ_s)
+{
+ char *princ_s = NULL;
+ char *short_princ_s = NULL;
+ const char *service = service_or_spn;
+ const char *host = my_fqdn;
+ struct spn_struct* spn_struct = NULL;
+ char *tmp = NULL;
+ bool ok = true;
+
+ /* SPN should have '/' */
+ tmp = strchr_m(service_or_spn, '/');
+ if (tmp != NULL) {
+ spn_struct = parse_spn(ctx, service_or_spn);
+ if (spn_struct == NULL) {
+ ok = false;
+ goto out;
+ }
+ }
+ if (spn_struct != NULL) {
+ service = spn_struct->serviceclass;
+ host = spn_struct->host;
+ }
+ princ_s = talloc_asprintf(ctx, "%s/%s@%s",
+ service,
+ host, lp_realm());
+ if (princ_s == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ if (spn_struct == NULL) {
+ short_princ_s = talloc_asprintf(ctx, "%s/%s@%s",
+ service, lp_netbios_name(),
+ lp_realm());
+ if (short_princ_s == NULL) {
+ ok = false;
+ goto out;
+ }
+ }
+ *p_princ_s = princ_s;
+ *p_short_princ_s = short_princ_s;
+out:
+ return ok;
+}
+
+static int add_kt_entry_etypes(krb5_context context, TALLOC_CTX *tmpctx,
+ ADS_STRUCT *ads, const char *salt_princ_s,
+ krb5_keytab keytab, krb5_kvno kvno,
+ const char *srvPrinc, const char *my_fqdn,
+ krb5_data *password, bool update_ads)
+{
+ krb5_error_code ret = 0;
+ char *princ_s = NULL;
+ char *short_princ_s = NULL;
+ krb5_enctype enctypes[4] = {
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ ENCTYPE_ARCFOUR_HMAC,
+ 0
+ };
+ size_t i;
+
+ /* Construct our principal */
+ if (strchr_m(srvPrinc, '@')) {
+ /* It's a fully-named principal. */
+ princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
+ if (!princ_s) {
+ ret = -1;
+ goto out;
+ }
+ } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
+ /* It's the machine account, as used by smbclient clients. */
+ princ_s = talloc_asprintf(tmpctx, "%s@%s",
+ srvPrinc, lp_realm());
+ if (!princ_s) {
+ ret = -1;
+ goto out;
+ }
+ } else {
+ /* It's a normal service principal. Add the SPN now so that we
+ * can obtain credentials for it and double-check the salt value
+ * used to generate the service's keys. */
+
+ if (!service_or_spn_to_kerberos_princ(tmpctx,
+ srvPrinc,
+ my_fqdn,
+ &princ_s,
+ &short_princ_s)) {
+ ret = -1;
+ goto out;
+ }
+
+ /* According to http://support.microsoft.com/kb/326985/en-us,
+ certain principal names are automatically mapped to the
+ host/... principal in the AD account.
+ So only create these in the keytab, not in AD. --jerry */
+
+ if (update_ads && !strequal(srvPrinc, "cifs") &&
+ !strequal(srvPrinc, "host")) {
+ if (!ads_set_machine_account_spns(tmpctx,
+ ads,
+ srvPrinc,
+ my_fqdn)) {
+ ret = -1;
+ goto out;
+ }
+ }
+ }
+
+ for (i = 0; enctypes[i]; i++) {
+
+ /* add the fqdn principal to the keytab */
+ ret = smb_krb5_kt_add_entry(context,
+ keytab,
+ kvno,
+ princ_s,
+ salt_princ_s,
+ enctypes[i],
+ password,
+ false); /* no_salt */
+ if (ret) {
+ DBG_WARNING("Failed to add entry to keytab\n");
+ goto out;
+ }
+
+ /* add the short principal name if we have one */
+ if (short_princ_s) {
+ ret = smb_krb5_kt_add_entry(context,
+ keytab,
+ kvno,
+ short_princ_s,
+ salt_princ_s,
+ enctypes[i],
+ password,
+ false); /* no_salt */
+ if (ret) {
+ DBG_WARNING("Failed to add short entry to keytab\n");
+ goto out;
+ }
+ }
+ }
+out:
+ return ret;
+}
+
+/**********************************************************************
+ Adds a single service principal, i.e. 'host' to the system keytab
+***********************************************************************/
+
+int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc, bool update_ads)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_data password;
+ krb5_kvno kvno;
+ char *salt_princ_s = NULL;
+ char *password_s = NULL;
+ char *my_fqdn;
+ TALLOC_CTX *tmpctx = NULL;
+ char **hostnames_array = NULL;
+ size_t num_hostnames = 0;
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ return -1;
+ }
+
+ ret = ads_keytab_open(context, &keytab);
+ if (ret != 0) {
+ goto out;
+ }
+
+ /* retrieve the password */
+ if (!secrets_init()) {
+ DBG_WARNING("secrets_init failed\n");
+ ret = -1;
+ goto out;
+ }
+ password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
+ if (!password_s) {
+ DBG_WARNING("failed to fetch machine password\n");
+ ret = -1;
+ goto out;
+ }
+ ZERO_STRUCT(password);
+ password.data = password_s;
+ password.length = strlen(password_s);
+
+ /* we need the dNSHostName value here */
+ tmpctx = talloc_init(__location__);
+ if (!tmpctx) {
+ DBG_ERR("talloc_init() failed!\n");
+ ret = -1;
+ goto out;
+ }
+
+ my_fqdn = ads_get_dnshostname(ads, tmpctx, lp_netbios_name());
+ if (!my_fqdn) {
+ DBG_ERR("unable to determine machine account's dns name in "
+ "AD!\n");
+ ret = -1;
+ goto out;
+ }
+
+ /* make sure we have a single instance of the computer account */
+ if (!ads_has_samaccountname(ads, tmpctx, lp_netbios_name())) {
+ DBG_ERR("unable to determine machine account's short name in "
+ "AD!\n");
+ ret = -1;
+ goto out;
+ }
+
+ kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
+ if (kvno == -1) {
+ /* -1 indicates failure, everything else is OK */
+ DBG_WARNING("ads_get_machine_kvno failed to determine the "
+ "system's kvno.\n");
+ ret = -1;
+ goto out;
+ }
+
+ salt_princ_s = kerberos_secrets_fetch_salt_princ();
+ if (salt_princ_s == NULL) {
+ DBG_WARNING("kerberos_secrets_fetch_salt_princ() failed\n");
+ ret = -1;
+ goto out;
+ }
+
+ ret = add_kt_entry_etypes(context, tmpctx, ads, salt_princ_s, keytab,
+ kvno, srvPrinc, my_fqdn, &password,
+ update_ads);
+ if (ret != 0) {
+ goto out;
+ }
+
+ if (ADS_ERR_OK(ads_get_additional_dns_hostnames(tmpctx, ads,
+ lp_netbios_name(),
+ &hostnames_array,
+ &num_hostnames))) {
+ size_t i;
+
+ for (i = 0; i < num_hostnames; i++) {
+
+ ret = add_kt_entry_etypes(context, tmpctx, ads,
+ salt_princ_s, keytab,
+ kvno, srvPrinc,
+ hostnames_array[i],
+ &password, update_ads);
+ if (ret != 0) {
+ goto out;
+ }
+ }
+ }
+
+out:
+ SAFE_FREE(salt_princ_s);
+ TALLOC_FREE(tmpctx);
+
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ if (context) {
+ krb5_free_context(context);
+ }
+ return (int)ret;
+}
+
+/**********************************************************************
+ Delete a single service principal, i.e. 'host' from the system keytab
+***********************************************************************/
+
+int ads_keytab_delete_entry(ADS_STRUCT *ads, const char *srvPrinc)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ char *princ_s = NULL;
+ krb5_principal princ = NULL;
+ char *short_princ_s = NULL;
+ krb5_principal short_princ = NULL;
+ bool ok;
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ goto out;
+ }
+
+ ret = ads_keytab_open(context, &keytab);
+ if (ret != 0) {
+ goto out;
+ }
+
+ /* Construct our principal */
+ if (strchr_m(srvPrinc, '@')) {
+ /* It's a fully-named principal. */
+ princ_s = talloc_asprintf(frame, "%s", srvPrinc);
+ if (!princ_s) {
+ ret = -1;
+ goto out;
+ }
+ } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
+ /* It's the machine account, as used by smbclient clients. */
+ princ_s = talloc_asprintf(frame, "%s@%s",
+ srvPrinc, lp_realm());
+ if (!princ_s) {
+ ret = -1;
+ goto out;
+ }
+ } else {
+ /*
+ * It's a normal service principal.
+ */
+ char *my_fqdn = NULL;
+ char *tmp = NULL;
+
+ /*
+ * SPN should have '/' otherwise we
+ * need to fallback and find our dnshostname
+ */
+ tmp = strchr_m(srvPrinc, '/');
+ if (tmp == NULL) {
+ my_fqdn = ads_get_dnshostname(ads, frame, lp_netbios_name());
+ if (!my_fqdn) {
+ DBG_ERR("unable to determine machine account's dns name in "
+ "AD!\n");
+ ret = -1;
+ goto out;
+ }
+ }
+
+ ok = service_or_spn_to_kerberos_princ(frame,
+ srvPrinc,
+ my_fqdn,
+ &princ_s,
+ &short_princ_s);
+ if (!ok) {
+ ret = -1;
+ goto out;
+ }
+ }
+
+ ret = smb_krb5_parse_name(context, princ_s, &princ);
+ if (ret) {
+ DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
+ "failed (%s)\n", princ_s, error_message(ret)));
+ goto out;
+ }
+
+ if (short_princ_s != NULL) {
+ ret = smb_krb5_parse_name(context, short_princ_s, &short_princ);
+ if (ret) {
+ DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
+ "failed (%s)\n", short_princ_s, error_message(ret)));
+ goto out;
+ }
+ }
+
+ /* Seek and delete old keytab entries */
+ ret = smb_krb5_kt_seek_and_delete_old_entries(context,
+ keytab,
+ false, /* keep_old_kvno */
+ -1,
+ false, /* enctype_only */
+ ENCTYPE_NULL,
+ princ_s,
+ princ,
+ false); /* flush */
+ if (ret) {
+ goto out;
+ }
+
+ if (short_princ_s == NULL) {
+ goto out;
+ }
+
+ /* Seek and delete old keytab entries */
+ ret = smb_krb5_kt_seek_and_delete_old_entries(context,
+ keytab,
+ false, /* keep_old_kvno */
+ -1,
+ false, /* enctype_only */
+ ENCTYPE_NULL,
+ short_princ_s,
+ short_princ,
+ false); /* flush */
+ if (ret) {
+ goto out;
+ }
+
+out:
+ if (princ) {
+ krb5_free_principal(context, princ);
+ }
+ if (short_princ) {
+ krb5_free_principal(context, short_princ);
+ }
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ if (context) {
+ krb5_free_context(context);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/**********************************************************************
+ Flushes all entries from the system keytab.
+***********************************************************************/
+
+int ads_keytab_flush(ADS_STRUCT *ads)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ ADS_STATUS aderr;
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ return ret;
+ }
+
+ ret = ads_keytab_open(context, &keytab);
+ if (ret != 0) {
+ goto out;
+ }
+
+ /* Seek and delete all old keytab entries */
+ ret = smb_krb5_kt_seek_and_delete_old_entries(context,
+ keytab,
+ false, /* keep_old_kvno */
+ -1,
+ false, /* enctype_only */
+ ENCTYPE_NULL,
+ NULL,
+ NULL,
+ true); /* flush */
+ if (ret) {
+ goto out;
+ }
+
+ aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
+ if (!ADS_ERR_OK(aderr)) {
+ DEBUG(1, (__location__ ": Error while clearing service "
+ "principal listings in LDAP.\n"));
+ ret = -1;
+ goto out;
+ }
+
+out:
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ if (context) {
+ krb5_free_context(context);
+ }
+ return ret;
+}
+
+/**********************************************************************
+ Adds all the required service principals to the system keytab.
+***********************************************************************/
+
+int ads_keytab_create_default(ADS_STRUCT *ads)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor cursor = {0};
+ krb5_keytab_entry kt_entry = {0};
+ krb5_kvno kvno;
+ size_t found = 0;
+ char *sam_account_name, *upn;
+ char **oldEntries = NULL, *princ_s[26];
+ TALLOC_CTX *frame;
+ char *machine_name;
+ char **spn_array;
+ size_t num_spns;
+ size_t i;
+ bool ok = false;
+ ADS_STATUS status;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ frame = talloc_stackframe();
+ if (frame == NULL) {
+ ret = -1;
+ goto done;
+ }
+
+ status = ads_get_service_principal_names(frame,
+ ads,
+ lp_netbios_name(),
+ &spn_array,
+ &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ ret = -1;
+ goto done;
+ }
+
+ for (i = 0; i < num_spns; i++) {
+ char *srv_princ;
+ char *p;
+
+ srv_princ = strlower_talloc(frame, spn_array[i]);
+ if (srv_princ == NULL) {
+ ret = -1;
+ goto done;
+ }
+
+ p = strchr_m(srv_princ, '/');
+ if (p == NULL) {
+ continue;
+ }
+ p[0] = '\0';
+
+ /* Add the SPNs found on the DC */
+ ret = ads_keytab_add_entry(ads, srv_princ, false);
+ if (ret != 0) {
+ DEBUG(1, ("ads_keytab_add_entry failed while "
+ "adding '%s' principal.\n",
+ spn_array[i]));
+ goto done;
+ }
+ }
+
+#if 0 /* don't create the CIFS/... keytab entries since no one except smbd
+ really needs them and we will fall back to verifying against
+ secrets.tdb */
+
+ ret = ads_keytab_add_entry(ads, "cifs", false));
+ if (ret != 0 ) {
+ DEBUG(1, (__location__ ": ads_keytab_add_entry failed while "
+ "adding 'cifs'.\n"));
+ return ret;
+ }
+#endif
+
+ memset(princ_s, '\0', sizeof(princ_s));
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ goto done;
+ }
+
+ machine_name = talloc_strdup(frame, lp_netbios_name());
+ if (!machine_name) {
+ ret = -1;
+ goto done;
+ }
+
+ /* now add the userPrincipalName and sAMAccountName entries */
+ ok = ads_has_samaccountname(ads, frame, machine_name);
+ if (!ok) {
+ DEBUG(0, (__location__ ": unable to determine machine "
+ "account's name in AD!\n"));
+ ret = -1;
+ goto done;
+ }
+
+ /*
+ * append '$' to netbios name so 'ads_keytab_add_entry' recognises
+ * it as a machine account rather than a service or Windows SPN.
+ */
+ sam_account_name = talloc_asprintf(frame, "%s$",machine_name);
+ if (sam_account_name == NULL) {
+ ret = -1;
+ goto done;
+ }
+ /* upper case the sAMAccountName to make it easier for apps to
+ know what case to use in the keytab file */
+ if (!strupper_m(sam_account_name)) {
+ ret = -1;
+ goto done;
+ }
+
+ ret = ads_keytab_add_entry(ads, sam_account_name, false);
+ if (ret != 0) {
+ DEBUG(1, (__location__ ": ads_keytab_add_entry() failed "
+ "while adding sAMAccountName (%s)\n",
+ sam_account_name));
+ goto done;
+ }
+
+ /* remember that not every machine account will have a upn */
+ upn = ads_get_upn(ads, frame, machine_name);
+ if (upn) {
+ ret = ads_keytab_add_entry(ads, upn, false);
+ if (ret != 0) {
+ DEBUG(1, (__location__ ": ads_keytab_add_entry() "
+ "failed while adding UPN (%s)\n", upn));
+ goto done;
+ }
+ }
+
+ /* Now loop through the keytab and update any other existing entries */
+ kvno = (krb5_kvno)ads_get_machine_kvno(ads, machine_name);
+ if (kvno == (krb5_kvno)-1) {
+ DEBUG(1, (__location__ ": ads_get_machine_kvno() failed to "
+ "determine the system's kvno.\n"));
+ goto done;
+ }
+
+ DEBUG(3, (__location__ ": Searching for keytab entries to preserve "
+ "and update.\n"));
+
+ ret = ads_keytab_open(context, &keytab);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret != KRB5_KT_END && ret != ENOENT ) {
+ while ((ret = krb5_kt_next_entry(context, keytab,
+ &kt_entry, &cursor)) == 0) {
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ found++;
+ }
+ }
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+
+ /*
+ * Hmmm. There is no "rewind" function for the keytab. This means we
+ * have a race condition where someone else could add entries after
+ * we've counted them. Re-open asap to minimise the race. JRA.
+ */
+ DEBUG(3, (__location__ ": Found %zd entries in the keytab.\n", found));
+ if (!found) {
+ goto done;
+ }
+
+ oldEntries = talloc_zero_array(frame, char *, found + 1);
+ if (!oldEntries) {
+ DEBUG(1, (__location__ ": Failed to allocate space to store "
+ "the old keytab entries (talloc failed?).\n"));
+ ret = -1;
+ goto done;
+ }
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret == KRB5_KT_END || ret == ENOENT) {
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+ goto done;
+ }
+
+ while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
+ if (kt_entry.vno != kvno) {
+ char *ktprinc = NULL;
+ char *p;
+
+ /* This returns a malloc'ed string in ktprinc. */
+ ret = smb_krb5_unparse_name(oldEntries, context,
+ kt_entry.principal,
+ &ktprinc);
+ if (ret) {
+ DEBUG(1, (__location__
+ ": smb_krb5_unparse_name failed "
+ "(%s)\n", error_message(ret)));
+ goto done;
+ }
+ /*
+ * From looking at the krb5 source they don't seem to
+ * take locale or mb strings into account.
+ * Maybe this is because they assume utf8 ?
+ * In this case we may need to convert from utf8 to
+ * mb charset here ? JRA.
+ */
+ p = strchr_m(ktprinc, '@');
+ if (p) {
+ *p = '\0';
+ }
+
+ p = strchr_m(ktprinc, '/');
+ if (p) {
+ *p = '\0';
+ }
+ for (i = 0; i < found; i++) {
+ if (!oldEntries[i]) {
+ oldEntries[i] = ktprinc;
+ break;
+ }
+ if (!strcmp(oldEntries[i], ktprinc)) {
+ TALLOC_FREE(ktprinc);
+ break;
+ }
+ }
+ if (i == found) {
+ TALLOC_FREE(ktprinc);
+ }
+ }
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ }
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+
+ ret = 0;
+ for (i = 0; oldEntries[i]; i++) {
+ ret |= ads_keytab_add_entry(ads, oldEntries[i], false);
+ TALLOC_FREE(oldEntries[i]);
+ }
+
+done:
+ TALLOC_FREE(oldEntries);
+ TALLOC_FREE(frame);
+
+ if (context) {
+ if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ }
+ if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ }
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ krb5_free_context(context);
+ }
+ return ret;
+}
+
+#endif /* HAVE_ADS */
+
+/**********************************************************************
+ List system keytab.
+***********************************************************************/
+
+int ads_keytab_list(const char *keytab_name)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ return ret;
+ }
+
+ if (keytab_name == NULL) {
+#ifdef HAVE_ADS
+ ret = ads_keytab_open(context, &keytab);
+#else
+ ret = ENOENT;
+#endif
+ } else {
+ ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
+ }
+ if (ret) {
+ DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
+ error_message(ret)));
+ goto out;
+ }
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ ZERO_STRUCT(cursor);
+ goto out;
+ }
+
+ printf("Vno Type Principal\n");
+
+ while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
+
+ char *princ_s = NULL;
+ char *etype_s = NULL;
+ krb5_enctype enctype = 0;
+
+ ret = smb_krb5_unparse_name(talloc_tos(), context,
+ kt_entry.principal, &princ_s);
+ if (ret) {
+ goto out;
+ }
+
+ enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
+
+ ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
+ if (ret &&
+ (asprintf(&etype_s, "UNKNOWN: %d", enctype) == -1)) {
+ TALLOC_FREE(princ_s);
+ goto out;
+ }
+
+ printf("%3d %-43s %s\n", kt_entry.vno, etype_s, princ_s);
+
+ TALLOC_FREE(princ_s);
+ SAFE_FREE(etype_s);
+
+ ret = smb_krb5_kt_free_entry(context, &kt_entry);
+ if (ret) {
+ goto out;
+ }
+ }
+
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ if (ret) {
+ goto out;
+ }
+
+ /* Ensure we don't double free. */
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+out:
+
+ if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ }
+ if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ }
+
+ if (keytab) {
+ krb5_kt_close(context, keytab);
+ }
+ if (context) {
+ krb5_free_context(context);
+ }
+ return ret;
+}
+
+#endif /* HAVE_KRB5 */
diff --git a/source3/libads/kerberos_proto.h b/source3/libads/kerberos_proto.h
new file mode 100644
index 0000000..8073812
--- /dev/null
+++ b/source3/libads/kerberos_proto.h
@@ -0,0 +1,104 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * kerberos utility library
+ *
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Remus Koos (remuskoos@yahoo.com) 2001
+ * Copyright (C) Luke Howard 2002-2003
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ * Copyright (C) Guenther Deschner 2003-2008
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
+ * Copyright (C) Jeremy Allison 2004,2007
+ * Copyright (C) Stefan Metzmacher 2004-2005
+ * Copyright (C) Nalin Dahyabhai <nalin@redhat.com> 2004
+ * Copyright (C) Gerald Carter 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIBADS_KERBEROS_PROTO_H_
+#define _LIBADS_KERBEROS_PROTO_H_
+
+#include "system/kerberos.h"
+
+struct PAC_DATA_CTR;
+
+#define DEFAULT_KRB5_PORT 88
+
+#include "libads/ads_status.h"
+
+/* The following definitions come from libads/kerberos.c */
+
+int kerberos_kinit_password_ext(const char *given_principal,
+ const char *password,
+ int time_offset,
+ time_t *expire_time,
+ time_t *renew_till_time,
+ const char *cache_name,
+ bool request_pac,
+ bool add_netbios_addr,
+ time_t renewable_time,
+ TALLOC_CTX *mem_ctx,
+ char **_canon_principal,
+ char **_canon_realm,
+ NTSTATUS *ntstatus);
+int ads_kdestroy(const char *cc_name);
+
+int kerberos_kinit_password(const char *principal,
+ const char *password,
+ int time_offset,
+ const char *cache_name);
+bool create_local_private_krb5_conf_for_domain(const char *realm,
+ const char *domain,
+ const char *sitename,
+ const struct sockaddr_storage *pss);
+
+/* The following definitions come from libads/authdata.c */
+
+NTSTATUS kerberos_return_pac(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *pass,
+ time_t time_offset,
+ time_t *expire_time,
+ time_t *renew_till_time,
+ const char *cache_name,
+ bool request_pac,
+ bool add_netbios_addr,
+ time_t renewable_time,
+ const char *impersonate_princ_s,
+ const char *local_service,
+ char **_canon_principal,
+ char **_canon_realm,
+ struct PAC_DATA_CTR **pac_data_ctr);
+
+/* The following definitions come from libads/krb5_setpw.c */
+
+ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *princ,
+ const char *newpw, int time_offset);
+ADS_STATUS kerberos_set_password(const char *kpasswd_server,
+ const char *auth_principal, const char *auth_password,
+ const char *target_principal, const char *new_password,
+ int time_offset);
+
+#ifdef HAVE_KRB5
+int create_kerberos_key_from_string(krb5_context context,
+ krb5_principal host_princ,
+ krb5_principal salt_princ,
+ krb5_data *password,
+ krb5_keyblock *key,
+ krb5_enctype enctype,
+ bool no_salt);
+#endif
+
+#endif /* _LIBADS_KERBEROS_PROTO_H_ */
diff --git a/source3/libads/kerberos_util.c b/source3/libads/kerberos_util.c
new file mode 100644
index 0000000..bfe5382
--- /dev/null
+++ b/source3/libads/kerberos_util.c
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ krb5 set password implementation
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001 (remuskoos@yahoo.com)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smb_krb5.h"
+#include "ads.h"
+#include "lib/param/loadparm.h"
+
+#ifdef HAVE_KRB5
+
+/* run kinit to setup our ccache */
+int ads_kinit_password(ADS_STRUCT *ads)
+{
+ char *s;
+ int ret;
+ const char *account_name;
+ fstring acct_name;
+
+ if (ads->auth.password == NULL || ads->auth.password[0] == '\0') {
+ return KRB5_LIBOS_CANTREADPWD;
+ }
+
+ if (ads->auth.flags & ADS_AUTH_USER_CREDS) {
+ account_name = ads->auth.user_name;
+ goto got_accountname;
+ }
+
+ if ( IS_DC ) {
+ /* this will end up getting a ticket for DOMAIN@RUSTED.REA.LM */
+ account_name = lp_workgroup();
+ } else {
+ /* always use the sAMAccountName for security = domain */
+ /* lp_netbios_name()$@REA.LM */
+ if ( lp_security() == SEC_DOMAIN ) {
+ fstr_sprintf( acct_name, "%s$", lp_netbios_name() );
+ account_name = acct_name;
+ }
+ else
+ /* This looks like host/lp_netbios_name()@REA.LM */
+ account_name = ads->auth.user_name;
+ }
+
+ got_accountname:
+ if (asprintf(&s, "%s@%s", account_name, ads->auth.realm) == -1) {
+ return KRB5_CC_NOMEM;
+ }
+
+ ret = kerberos_kinit_password_ext(s, ads->auth.password,
+ ads->auth.time_offset,
+ &ads->auth.tgt_expire, NULL,
+ ads->auth.ccache_name, false, false,
+ ads->auth.renewable,
+ NULL, NULL, NULL, NULL);
+
+ if (ret) {
+ DEBUG(0,("kerberos_kinit_password %s failed: %s\n",
+ s, error_message(ret)));
+ }
+ SAFE_FREE(s);
+ return ret;
+}
+
+#endif
diff --git a/source3/libads/krb5_setpw.c b/source3/libads/krb5_setpw.c
new file mode 100644
index 0000000..dda8857
--- /dev/null
+++ b/source3/libads/krb5_setpw.c
@@ -0,0 +1,329 @@
+/*
+ Unix SMB/CIFS implementation.
+ krb5 set password implementation
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001 (remuskoos@yahoo.com)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smb_krb5.h"
+#include "libads/kerberos_proto.h"
+#include "../lib/util/asn1.h"
+
+#ifdef HAVE_KRB5
+
+/* Those are defined by kerberos-set-passwd-02.txt and are probably
+ * not supported by M$ implementation */
+#define KRB5_KPASSWD_POLICY_REJECT 8
+#define KRB5_KPASSWD_BAD_PRINCIPAL 9
+#define KRB5_KPASSWD_ETYPE_NOSUPP 10
+
+/*
+ * we've got to be able to distinguish KRB_ERRORs from other
+ * requests - valid response for CHPW v2 replies.
+ */
+
+static krb5_error_code kpasswd_err_to_krb5_err(krb5_error_code res_code)
+{
+ switch (res_code) {
+ case KRB5_KPASSWD_ACCESSDENIED:
+ return KRB5KDC_ERR_BADOPTION;
+ case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
+ return KRB5KDC_ERR_BADOPTION;
+ /* return KV5M_ALT_METHOD; MIT-only define */
+ case KRB5_KPASSWD_ETYPE_NOSUPP:
+ return KRB5KDC_ERR_ETYPE_NOSUPP;
+ case KRB5_KPASSWD_BAD_PRINCIPAL:
+ return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
+ case KRB5_KPASSWD_POLICY_REJECT:
+ case KRB5_KPASSWD_SOFTERROR:
+ return KRB5KDC_ERR_POLICY;
+ default:
+ return KRB5KRB_ERR_GENERIC;
+ }
+}
+
+ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *principal,
+ const char *newpw, int time_offset)
+{
+
+ ADS_STATUS aret;
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_principal princ = NULL;
+ krb5_ccache ccache = NULL;
+ int result_code;
+ krb5_data result_code_string = { 0 };
+ krb5_data result_string = { 0 };
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ return ADS_ERROR_KRB5(ret);
+ }
+
+ if (principal) {
+ ret = smb_krb5_parse_name(context, principal, &princ);
+ if (ret) {
+ krb5_free_context(context);
+ DEBUG(1, ("Failed to parse %s (%s)\n", principal,
+ error_message(ret)));
+ return ADS_ERROR_KRB5(ret);
+ }
+ }
+
+ if (time_offset != 0) {
+ krb5_set_real_time(context, time(NULL) + time_offset, 0);
+ }
+
+ ret = krb5_cc_default(context, &ccache);
+ if (ret) {
+ krb5_free_principal(context, princ);
+ krb5_free_context(context);
+ DEBUG(1,("Failed to get default creds (%s)\n", error_message(ret)));
+ return ADS_ERROR_KRB5(ret);
+ }
+
+ ret = krb5_set_password_using_ccache(context,
+ ccache,
+ discard_const_p(char, newpw),
+ princ,
+ &result_code,
+ &result_code_string,
+ &result_string);
+ if (ret) {
+ DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
+ aret = ADS_ERROR_KRB5(ret);
+ goto done;
+ }
+
+ if (result_code != KRB5_KPASSWD_SUCCESS) {
+ ret = kpasswd_err_to_krb5_err(result_code);
+ DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
+ aret = ADS_ERROR_KRB5(ret);
+ goto done;
+ }
+
+ aret = ADS_SUCCESS;
+
+ done:
+ smb_krb5_free_data_contents(context, &result_code_string);
+ smb_krb5_free_data_contents(context, &result_string);
+ krb5_free_principal(context, princ);
+ krb5_cc_close(context, ccache);
+ krb5_free_context(context);
+
+ return aret;
+}
+
+/*
+ we use a prompter to avoid a crash bug in the kerberos libs when
+ dealing with empty passwords
+ this prompter is just a string copy ...
+*/
+static krb5_error_code
+kerb_prompter(krb5_context ctx, void *data,
+ const char *name,
+ const char *banner,
+ int num_prompts,
+ krb5_prompt prompts[])
+{
+ if (num_prompts == 0) return 0;
+
+ memset(prompts[0].reply->data, 0, prompts[0].reply->length);
+ if (prompts[0].reply->length > 0) {
+ if (data) {
+ strncpy((char *)prompts[0].reply->data,
+ (const char *)data,
+ prompts[0].reply->length-1);
+ prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
+ } else {
+ prompts[0].reply->length = 0;
+ }
+ }
+ return 0;
+}
+
+static ADS_STATUS ads_krb5_chg_password(const char *kdc_host,
+ const char *principal,
+ const char *oldpw,
+ const char *newpw,
+ int time_offset)
+{
+ ADS_STATUS aret;
+ krb5_error_code ret;
+ krb5_context context = NULL;
+ krb5_principal princ;
+ krb5_get_init_creds_opt *opts = NULL;
+ krb5_creds creds;
+ char *chpw_princ = NULL, *password;
+ char *realm = NULL;
+ int result_code;
+ krb5_data result_code_string = { 0 };
+ krb5_data result_string = { 0 };
+ smb_krb5_addresses *addr = NULL;
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ return ADS_ERROR_KRB5(ret);
+ }
+
+ if ((ret = smb_krb5_parse_name(context, principal, &princ))) {
+ krb5_free_context(context);
+ DEBUG(1,("Failed to parse %s (%s)\n", principal, error_message(ret)));
+ return ADS_ERROR_KRB5(ret);
+ }
+
+ ret = krb5_get_init_creds_opt_alloc(context, &opts);
+ if (ret != 0) {
+ krb5_free_context(context);
+ DBG_WARNING("krb5_get_init_creds_opt_alloc failed: %s\n",
+ error_message(ret));
+ return ADS_ERROR_KRB5(ret);
+ }
+
+ krb5_get_init_creds_opt_set_tkt_life(opts, 5 * 60);
+ krb5_get_init_creds_opt_set_renew_life(opts, 0);
+ krb5_get_init_creds_opt_set_forwardable(opts, 0);
+ krb5_get_init_creds_opt_set_proxiable(opts, 0);
+#ifdef SAMBA4_USES_HEIMDAL
+ krb5_get_init_creds_opt_set_win2k(context, opts, true);
+ krb5_get_init_creds_opt_set_canonicalize(context, opts, true);
+#else /* MIT */
+#if 0
+ /*
+ * FIXME
+ *
+ * Due to an upstream MIT Kerberos bug, this feature is not
+ * not working. Affection versions (2019-10-09): <= 1.17
+ *
+ * Reproducer:
+ * kinit -C aDmInIsTrAtOr@ACME.COM -S kadmin/changepw@ACME.COM
+ *
+ * This is NOT a problem if the service is a krbtgt.
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=14155
+ */
+ krb5_get_init_creds_opt_set_canonicalize(opts, true);
+#endif
+#endif /* MIT */
+
+ /* note that heimdal will fill in the local addresses if the addresses
+ * in the creds_init_opt are all empty and then later fail with invalid
+ * address, sending our local netbios krb5 address - just like windows
+ * - avoids this - gd */
+ ret = smb_krb5_gen_netbios_krb5_address(&addr, lp_netbios_name());
+ if (ret) {
+ krb5_free_principal(context, princ);
+ krb5_get_init_creds_opt_free(context, opts);
+ krb5_free_context(context);
+ return ADS_ERROR_KRB5(ret);
+ }
+ krb5_get_init_creds_opt_set_address_list(opts, addr->addrs);
+
+ realm = smb_krb5_principal_get_realm(NULL, context, princ);
+
+ /* We have to obtain an INITIAL changepw ticket for changing password */
+ if (asprintf(&chpw_princ, "kadmin/changepw@%s", realm) == -1) {
+ krb5_free_principal(context, princ);
+ krb5_get_init_creds_opt_free(context, opts);
+ smb_krb5_free_addresses(context, addr);
+ krb5_free_context(context);
+ TALLOC_FREE(realm);
+ DEBUG(1, ("ads_krb5_chg_password: asprintf fail\n"));
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ TALLOC_FREE(realm);
+ password = SMB_STRDUP(oldpw);
+ ret = krb5_get_init_creds_password(context, &creds, princ, password,
+ kerb_prompter, NULL,
+ 0, chpw_princ, opts);
+ krb5_get_init_creds_opt_free(context, opts);
+ smb_krb5_free_addresses(context, addr);
+ SAFE_FREE(chpw_princ);
+ SAFE_FREE(password);
+
+ if (ret) {
+ if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
+ DEBUG(1,("Password incorrect while getting initial ticket\n"));
+ } else {
+ DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret)));
+ }
+ krb5_free_principal(context, princ);
+ krb5_free_context(context);
+ return ADS_ERROR_KRB5(ret);
+ }
+
+ ret = krb5_set_password(context,
+ &creds,
+ discard_const_p(char, newpw),
+ NULL,
+ &result_code,
+ &result_code_string,
+ &result_string);
+
+ if (ret) {
+ DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
+ aret = ADS_ERROR_KRB5(ret);
+ goto done;
+ }
+
+ if (result_code != KRB5_KPASSWD_SUCCESS) {
+ ret = kpasswd_err_to_krb5_err(result_code);
+ DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
+ aret = ADS_ERROR_KRB5(ret);
+ goto done;
+ }
+
+ aret = ADS_SUCCESS;
+
+ done:
+ smb_krb5_free_data_contents(context, &result_code_string);
+ smb_krb5_free_data_contents(context, &result_string);
+ krb5_free_principal(context, princ);
+ krb5_free_context(context);
+
+ return aret;
+}
+
+ADS_STATUS kerberos_set_password(const char *kpasswd_server,
+ const char *auth_principal,
+ const char *auth_password,
+ const char *target_principal,
+ const char *new_password, int time_offset)
+{
+ int ret;
+
+ if ((ret = kerberos_kinit_password(auth_principal, auth_password, time_offset, NULL))) {
+ DEBUG(1,("Failed kinit for principal %s (%s)\n", auth_principal, error_message(ret)));
+ return ADS_ERROR_KRB5(ret);
+ }
+
+ if (!strcmp(auth_principal, target_principal)) {
+ return ads_krb5_chg_password(kpasswd_server, target_principal,
+ auth_password, new_password,
+ time_offset);
+ } else {
+ return ads_krb5_set_password(kpasswd_server, target_principal,
+ new_password, time_offset);
+ }
+}
+
+#endif
diff --git a/source3/libads/ldap.c b/source3/libads/ldap.c
new file mode 100644
index 0000000..b5139e5
--- /dev/null
+++ b/source3/libads/ldap.c
@@ -0,0 +1,4684 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Guenther Deschner 2005
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "libads/sitename_cache.h"
+#include "libads/cldap.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/addns/dnsquery.h"
+#include "../libds/common/flags.h"
+#include "smbldap.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "lib/param/loadparm.h"
+#include "libsmb/namequery.h"
+#include "../librpc/gen_ndr/ndr_ads.h"
+
+#ifdef HAVE_LDAP
+
+/**
+ * @file ldap.c
+ * @brief basic ldap client-side routines for ads server communications
+ *
+ * The routines contained here should do the necessary ldap calls for
+ * ads setups.
+ *
+ * Important note: attribute names passed into ads_ routines must
+ * already be in UTF-8 format. We do not convert them because in almost
+ * all cases, they are just ascii (which is represented with the same
+ * codepoints in UTF-8). This may have to change at some point
+ **/
+
+
+#define LDAP_SERVER_TREE_DELETE_OID "1.2.840.113556.1.4.805"
+
+static SIG_ATOMIC_T gotalarm;
+
+/***************************************************************
+ Signal function to tell us we timed out.
+****************************************************************/
+
+static void gotalarm_sig(int signum)
+{
+ gotalarm = 1;
+}
+
+ LDAP *ldap_open_with_timeout(const char *server,
+ struct sockaddr_storage *ss,
+ int port, unsigned int to)
+{
+ LDAP *ldp = NULL;
+ int ldap_err;
+ char *uri;
+
+ DEBUG(10, ("Opening connection to LDAP server '%s:%d', timeout "
+ "%u seconds\n", server, port, to));
+
+ if (to) {
+ /* Setup timeout */
+ gotalarm = 0;
+ CatchSignal(SIGALRM, gotalarm_sig);
+ alarm(to);
+ /* End setup timeout. */
+ }
+
+ if ( strchr_m(server, ':') ) {
+ /* IPv6 URI */
+ uri = talloc_asprintf(talloc_tos(), "ldap://[%s]:%u", server, port);
+ } else {
+ /* IPv4 URI */
+ uri = talloc_asprintf(talloc_tos(), "ldap://%s:%u", server, port);
+ }
+ if (uri == NULL) {
+ return NULL;
+ }
+
+#ifdef HAVE_LDAP_INIT_FD
+ {
+ int fd = -1;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ unsigned timeout_ms = 1000 * to;
+
+ status = open_socket_out(ss, port, timeout_ms, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("open_socket_out: failed to open socket\n"));
+ return NULL;
+ }
+
+/* define LDAP_PROTO_TCP from openldap.h if required */
+#ifndef LDAP_PROTO_TCP
+#define LDAP_PROTO_TCP 1
+#endif
+ ldap_err = ldap_init_fd(fd, LDAP_PROTO_TCP, uri, &ldp);
+ }
+#elif defined(HAVE_LDAP_INITIALIZE)
+ ldap_err = ldap_initialize(&ldp, uri);
+#else
+ ldp = ldap_open(server, port);
+ if (ldp != NULL) {
+ ldap_err = LDAP_SUCCESS;
+ } else {
+ ldap_err = LDAP_OTHER;
+ }
+#endif
+ if (ldap_err != LDAP_SUCCESS) {
+ DEBUG(2,("Could not initialize connection for LDAP server '%s': %s\n",
+ uri, ldap_err2string(ldap_err)));
+ } else {
+ DEBUG(10, ("Initialized connection for LDAP server '%s'\n", uri));
+ }
+
+ if (to) {
+ /* Teardown timeout. */
+ alarm(0);
+ CatchSignal(SIGALRM, SIG_IGN);
+ }
+
+ return ldp;
+}
+
+static int ldap_search_with_timeout(LDAP *ld,
+ LDAP_CONST char *base,
+ int scope,
+ LDAP_CONST char *filter,
+ char **attrs,
+ int attrsonly,
+ LDAPControl **sctrls,
+ LDAPControl **cctrls,
+ int sizelimit,
+ LDAPMessage **res )
+{
+ int to = lp_ldap_timeout();
+ struct timeval timeout;
+ struct timeval *timeout_ptr = NULL;
+ int result;
+
+ /* Setup timeout for the ldap_search_ext_s call - local and remote. */
+ gotalarm = 0;
+
+ if (to) {
+ timeout.tv_sec = to;
+ timeout.tv_usec = 0;
+ timeout_ptr = &timeout;
+
+ /* Setup alarm timeout. */
+ CatchSignal(SIGALRM, gotalarm_sig);
+ /* Make the alarm time one second beyond
+ the timeout we're setting for the
+ remote search timeout, to allow that
+ to fire in preference. */
+ alarm(to+1);
+ /* End setup timeout. */
+ }
+
+
+ result = ldap_search_ext_s(ld, base, scope, filter, attrs,
+ attrsonly, sctrls, cctrls, timeout_ptr,
+ sizelimit, res);
+
+ if (to) {
+ /* Teardown alarm timeout. */
+ CatchSignal(SIGALRM, SIG_IGN);
+ alarm(0);
+ }
+
+ if (gotalarm != 0)
+ return LDAP_TIMELIMIT_EXCEEDED;
+
+ /*
+ * A bug in OpenLDAP means ldap_search_ext_s can return
+ * LDAP_SUCCESS but with a NULL res pointer. Cope with
+ * this. See bug #6279 for details. JRA.
+ */
+
+ if (*res == NULL) {
+ return LDAP_TIMELIMIT_EXCEEDED;
+ }
+
+ return result;
+}
+
+/**********************************************
+ Do client and server sitename match ?
+**********************************************/
+
+bool ads_sitename_match(ADS_STRUCT *ads)
+{
+ if (ads->config.server_site_name == NULL &&
+ ads->config.client_site_name == NULL ) {
+ DEBUG(10,("ads_sitename_match: both null\n"));
+ return True;
+ }
+ if (ads->config.server_site_name &&
+ ads->config.client_site_name &&
+ strequal(ads->config.server_site_name,
+ ads->config.client_site_name)) {
+ DEBUG(10,("ads_sitename_match: name %s match\n", ads->config.server_site_name));
+ return True;
+ }
+ DEBUG(10,("ads_sitename_match: no match between server: %s and client: %s\n",
+ ads->config.server_site_name ? ads->config.server_site_name : "NULL",
+ ads->config.client_site_name ? ads->config.client_site_name : "NULL"));
+ return False;
+}
+
+/**********************************************
+ Is this the closest DC ?
+**********************************************/
+
+bool ads_closest_dc(ADS_STRUCT *ads)
+{
+ if (ads->config.flags & NBT_SERVER_CLOSEST) {
+ DEBUG(10,("ads_closest_dc: NBT_SERVER_CLOSEST flag set\n"));
+ return True;
+ }
+
+ /* not sure if this can ever happen */
+ if (ads_sitename_match(ads)) {
+ DEBUG(10,("ads_closest_dc: NBT_SERVER_CLOSEST flag not set but sites match\n"));
+ return True;
+ }
+
+ if (ads->config.client_site_name == NULL) {
+ DEBUG(10,("ads_closest_dc: client belongs to no site\n"));
+ return True;
+ }
+
+ DEBUG(10,("ads_closest_dc: %s is not the closest DC\n",
+ ads->config.ldap_server_name));
+
+ return False;
+}
+
+static bool ads_fill_cldap_reply(ADS_STRUCT *ads,
+ bool gc,
+ const struct sockaddr_storage *ss,
+ const struct NETLOGON_SAM_LOGON_RESPONSE_EX *cldap_reply)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ret = false;
+ char addr[INET6_ADDRSTRLEN];
+ ADS_STATUS status;
+ char *dn;
+
+ print_sockaddr(addr, sizeof(addr), ss);
+
+ /* Check the CLDAP reply flags */
+
+ if (!(cldap_reply->server_type & NBT_SERVER_LDAP)) {
+ DBG_WARNING("%s's CLDAP reply says it is not an LDAP server!\n",
+ addr);
+ ret = false;
+ goto out;
+ }
+
+ /* Fill in the ads->config values */
+
+ ADS_TALLOC_CONST_FREE(ads->config.realm);
+ ADS_TALLOC_CONST_FREE(ads->config.bind_path);
+ ADS_TALLOC_CONST_FREE(ads->config.ldap_server_name);
+ ADS_TALLOC_CONST_FREE(ads->config.server_site_name);
+ ADS_TALLOC_CONST_FREE(ads->config.client_site_name);
+ ADS_TALLOC_CONST_FREE(ads->server.workgroup);
+
+ if (!check_cldap_reply_required_flags(cldap_reply->server_type,
+ ads->config.flags)) {
+ ret = false;
+ goto out;
+ }
+
+ ads->config.ldap_server_name = talloc_strdup(ads,
+ cldap_reply->pdc_dns_name);
+ if (ads->config.ldap_server_name == NULL) {
+ DBG_WARNING("Out of memory\n");
+ ret = false;
+ goto out;
+ }
+
+ ads->config.realm = talloc_asprintf_strupper_m(ads,
+ "%s",
+ cldap_reply->dns_domain);
+ if (ads->config.realm == NULL) {
+ DBG_WARNING("Out of memory\n");
+ ret = false;
+ goto out;
+ }
+
+ status = ads_build_dn(ads->config.realm, ads, &dn);
+ if (!ADS_ERR_OK(status)) {
+ DBG_DEBUG("Failed to build bind path: %s\n",
+ ads_errstr(status));
+ ret = false;
+ goto out;
+ }
+ ads->config.bind_path = dn;
+
+ if (*cldap_reply->server_site) {
+ ads->config.server_site_name =
+ talloc_strdup(ads, cldap_reply->server_site);
+ if (ads->config.server_site_name == NULL) {
+ DBG_WARNING("Out of memory\n");
+ ret = false;
+ goto out;
+ }
+ }
+
+ if (*cldap_reply->client_site) {
+ ads->config.client_site_name =
+ talloc_strdup(ads, cldap_reply->client_site);
+ if (ads->config.client_site_name == NULL) {
+ DBG_WARNING("Out of memory\n");
+ ret = false;
+ goto out;
+ }
+ }
+
+ ads->server.workgroup = talloc_strdup(ads, cldap_reply->domain_name);
+ if (ads->server.workgroup == NULL) {
+ DBG_WARNING("Out of memory\n");
+ ret = false;
+ goto out;
+ }
+
+ ads->ldap.port = gc ? LDAP_GC_PORT : LDAP_PORT;
+ ads->ldap.ss = *ss;
+
+ /* Store our site name. */
+ sitename_store(cldap_reply->domain_name, cldap_reply->client_site);
+ sitename_store(cldap_reply->dns_domain, cldap_reply->client_site);
+
+ /* Leave this until last so that the flags are not clobbered */
+ ads->config.flags = cldap_reply->server_type;
+
+ ret = true;
+
+ out:
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/*
+ try a connection to a given ldap server, returning True and setting the servers IP
+ in the ads struct if successful
+ */
+static bool ads_try_connect(ADS_STRUCT *ads, bool gc,
+ struct sockaddr_storage *ss)
+{
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX cldap_reply = {};
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ok;
+ char addr[INET6_ADDRSTRLEN] = { 0, };
+
+ if (ss == NULL) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ print_sockaddr(addr, sizeof(addr), ss);
+
+ DBG_INFO("ads_try_connect: sending CLDAP request to %s (realm: %s)\n",
+ addr, ads->server.realm);
+
+ ok = ads_cldap_netlogon_5(frame, ss, ads->server.realm, &cldap_reply);
+ if (!ok) {
+ DBG_NOTICE("ads_cldap_netlogon_5(%s, %s) failed.\n",
+ addr, ads->server.realm);
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ ok = ads_fill_cldap_reply(ads, gc, ss, &cldap_reply);
+ if (!ok) {
+ DBG_NOTICE("ads_fill_cldap_reply(%s, %s) failed.\n",
+ addr, ads->server.realm);
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ TALLOC_FREE(frame);
+ return true;
+}
+
+/**********************************************************************
+ send a cldap ping to list of servers, one at a time, until one of
+ them answers it's an ldap server. Record success in the ADS_STRUCT.
+ Take note of and update negative connection cache.
+**********************************************************************/
+
+static NTSTATUS cldap_ping_list(ADS_STRUCT *ads,
+ const char *domain,
+ struct samba_sockaddr *sa_list,
+ size_t count)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct timeval endtime = timeval_current_ofs(MAX(3,lp_ldap_timeout()/2), 0);
+ uint32_t nt_version = NETLOGON_NT_VERSION_5 | NETLOGON_NT_VERSION_5EX;
+ struct tsocket_address **ts_list = NULL;
+ const struct tsocket_address * const *ts_list_const = NULL;
+ struct samba_sockaddr **req_sa_list = NULL;
+ struct netlogon_samlogon_response **responses = NULL;
+ size_t num_requests = 0;
+ NTSTATUS status;
+ size_t i;
+ bool ok = false;
+ bool retry;
+
+ ts_list = talloc_zero_array(frame,
+ struct tsocket_address *,
+ count);
+ if (ts_list == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ req_sa_list = talloc_zero_array(frame,
+ struct samba_sockaddr *,
+ count);
+ if (req_sa_list == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+again:
+ /*
+ * The retry loop is bound by the timeout
+ */
+ retry = false;
+ num_requests = 0;
+
+ for (i = 0; i < count; i++) {
+ char server[INET6_ADDRSTRLEN];
+ int ret;
+
+ if (is_zero_addr(&sa_list[i].u.ss)) {
+ continue;
+ }
+
+ print_sockaddr(server, sizeof(server), &sa_list[i].u.ss);
+
+ status = check_negative_conn_cache(domain, server);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+
+ ret = tsocket_address_inet_from_strings(ts_list, "ip",
+ server, LDAP_PORT,
+ &ts_list[num_requests]);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_WARNING("Failed to create tsocket_address for %s - %s\n",
+ server, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ req_sa_list[num_requests] = &sa_list[i];
+ num_requests += 1;
+ }
+
+ DBG_DEBUG("Try to create %zu netlogon connections for domain '%s' "
+ "(provided count of addresses was %zu).\n",
+ num_requests,
+ domain,
+ count);
+
+ if (num_requests == 0) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ DBG_WARNING("domain[%s] num_requests[%zu] for count[%zu] - %s\n",
+ domain, num_requests, count, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ ts_list_const = (const struct tsocket_address * const *)ts_list;
+
+ status = cldap_multi_netlogon(frame,
+ ts_list_const, num_requests,
+ ads->server.realm, NULL,
+ nt_version,
+ 1, endtime, &responses);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("cldap_multi_netlogon(realm=%s, num_requests=%zu) "
+ "for count[%zu] - %s\n",
+ ads->server.realm,
+ num_requests, count,
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ for (i = 0; i < num_requests; i++) {
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX *cldap_reply = NULL;
+ char server[INET6_ADDRSTRLEN];
+
+ if (responses[i] == NULL) {
+ continue;
+ }
+
+ print_sockaddr(server, sizeof(server), &req_sa_list[i]->u.ss);
+
+ if (responses[i]->ntver != NETLOGON_NT_VERSION_5EX) {
+ DBG_NOTICE("realm=[%s] nt_version mismatch: 0x%08x for %s\n",
+ ads->server.realm,
+ responses[i]->ntver, server);
+ continue;
+ }
+
+ cldap_reply = &responses[i]->data.nt5_ex;
+
+ /* Returns ok only if it matches the correct server type */
+ ok = ads_fill_cldap_reply(ads,
+ false,
+ &req_sa_list[i]->u.ss,
+ cldap_reply);
+ if (ok) {
+ DBG_DEBUG("realm[%s]: selected %s => %s\n",
+ ads->server.realm,
+ server, cldap_reply->pdc_dns_name);
+ if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(NETLOGON_SAM_LOGON_RESPONSE_EX,
+ cldap_reply);
+ }
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ DBG_NOTICE("realm[%s] server %s %s - not usable\n",
+ ads->server.realm,
+ server, cldap_reply->pdc_dns_name);
+ if (CHECK_DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(NETLOGON_SAM_LOGON_RESPONSE_EX,
+ cldap_reply);
+ }
+ add_failed_connection_entry(domain, server,
+ NT_STATUS_CLIENT_SERVER_PARAMETERS_INVALID);
+ retry = true;
+ }
+
+ if (retry) {
+ bool expired;
+
+ expired = timeval_expired(&endtime);
+ if (!expired) {
+ goto again;
+ }
+ }
+
+ /* keep track of failures as all were not suitable */
+ for (i = 0; i < num_requests; i++) {
+ char server[INET6_ADDRSTRLEN];
+
+ print_sockaddr(server, sizeof(server), &req_sa_list[i]->u.ss);
+
+ add_failed_connection_entry(domain, server,
+ NT_STATUS_UNSUCCESSFUL);
+ }
+
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ DBG_WARNING("realm[%s] no valid response "
+ "num_requests[%zu] for count[%zu] - %s\n",
+ ads->server.realm,
+ num_requests, count, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_LOGON_SERVERS;
+}
+
+/***************************************************************************
+ resolve a name and perform an "ldap ping" using NetBIOS and related methods
+****************************************************************************/
+
+static NTSTATUS resolve_and_ping_netbios(ADS_STRUCT *ads,
+ const char *domain, const char *realm)
+{
+ size_t i;
+ size_t count = 0;
+ struct samba_sockaddr *sa_list = NULL;
+ NTSTATUS status;
+
+ DEBUG(6, ("resolve_and_ping_netbios: (cldap) looking for domain '%s'\n",
+ domain));
+
+ status = get_sorted_dc_list(talloc_tos(),
+ domain,
+ NULL,
+ &sa_list,
+ &count,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* remove servers which are known to be dead based on
+ the corresponding DNS method */
+ if (*realm) {
+ for (i = 0; i < count; ++i) {
+ char server[INET6_ADDRSTRLEN];
+
+ print_sockaddr(server, sizeof(server), &sa_list[i].u.ss);
+
+ if(!NT_STATUS_IS_OK(
+ check_negative_conn_cache(realm, server))) {
+ /* Ensure we add the workgroup name for this
+ IP address as negative too. */
+ add_failed_connection_entry(
+ domain, server,
+ NT_STATUS_UNSUCCESSFUL);
+ }
+ }
+ }
+
+ status = cldap_ping_list(ads, domain, sa_list, count);
+
+ TALLOC_FREE(sa_list);
+
+ return status;
+}
+
+
+/**********************************************************************
+ resolve a name and perform an "ldap ping" using DNS
+**********************************************************************/
+
+static NTSTATUS resolve_and_ping_dns(ADS_STRUCT *ads, const char *sitename,
+ const char *realm)
+{
+ size_t count = 0;
+ struct samba_sockaddr *sa_list = NULL;
+ NTSTATUS status;
+
+ DEBUG(6, ("resolve_and_ping_dns: (cldap) looking for realm '%s'\n",
+ realm));
+
+ status = get_sorted_dc_list(talloc_tos(),
+ realm,
+ sitename,
+ &sa_list,
+ &count,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sa_list);
+ return status;
+ }
+
+ status = cldap_ping_list(ads, realm, sa_list, count);
+
+ TALLOC_FREE(sa_list);
+
+ return status;
+}
+
+/**********************************************************************
+ Try to find an AD dc using our internal name resolution routines
+ Try the realm first and then the workgroup name if netbios is not
+ disabled
+**********************************************************************/
+
+static NTSTATUS ads_find_dc(ADS_STRUCT *ads)
+{
+ const char *c_domain = "";
+ const char *c_realm;
+ bool use_own_domain = False;
+ char *sitename = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ bool ok = false;
+
+ /* if the realm and workgroup are both empty, assume they are ours */
+
+ /* realm */
+ c_realm = ads->server.realm;
+
+ if (c_realm == NULL)
+ c_realm = "";
+
+ if (!*c_realm) {
+ /* special case where no realm and no workgroup means our own */
+ if ( !ads->server.workgroup || !*ads->server.workgroup ) {
+ use_own_domain = True;
+ c_realm = lp_realm();
+ }
+ }
+
+ if (!lp_disable_netbios()) {
+ if (use_own_domain) {
+ c_domain = lp_workgroup();
+ } else {
+ c_domain = ads->server.workgroup;
+ if (!*c_realm && (!c_domain || !*c_domain)) {
+ c_domain = lp_workgroup();
+ }
+ }
+
+ if (!c_domain) {
+ c_domain = "";
+ }
+ }
+
+ if (!*c_realm && !*c_domain) {
+ DEBUG(0, ("ads_find_dc: no realm or workgroup! Don't know "
+ "what to do\n"));
+ return NT_STATUS_INVALID_PARAMETER; /* rather need MISSING_PARAMETER ... */
+ }
+
+ /*
+ * In case of LDAP we use get_dc_name() as that
+ * creates the custom krb5.conf file
+ */
+ if (!(ads->auth.flags & ADS_AUTH_NO_BIND)) {
+ fstring srv_name;
+ struct sockaddr_storage ip_out;
+
+ DEBUG(6, ("ads_find_dc: (ldap) looking for realm '%s'"
+ " and falling back to domain '%s'\n",
+ c_realm, c_domain));
+
+ ok = get_dc_name(c_domain, c_realm, srv_name, &ip_out);
+ if (ok) {
+ if (is_zero_addr(&ip_out)) {
+ return NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ /*
+ * we call ads_try_connect() to fill in the
+ * ads->config details
+ */
+ ok = ads_try_connect(ads, false, &ip_out);
+ if (ok) {
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ if (*c_realm) {
+ sitename = sitename_fetch(talloc_tos(), c_realm);
+ status = resolve_and_ping_dns(ads, sitename, c_realm);
+
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sitename);
+ return status;
+ }
+
+ /* In case we failed to contact one of our closest DC on our
+ * site we
+ * need to try to find another DC, retry with a site-less SRV
+ * DNS query
+ * - Guenther */
+
+ if (sitename) {
+ DEBUG(3, ("ads_find_dc: failed to find a valid DC on "
+ "our site (%s), Trying to find another DC "
+ "for realm '%s' (domain '%s')\n",
+ sitename, c_realm, c_domain));
+ namecache_delete(c_realm, 0x1C);
+ status =
+ resolve_and_ping_dns(ads, NULL, c_realm);
+
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sitename);
+ return status;
+ }
+ }
+
+ TALLOC_FREE(sitename);
+ }
+
+ /* try netbios as fallback - if permitted,
+ or if configuration specifically requests it */
+ if (*c_domain) {
+ if (*c_realm) {
+ DEBUG(3, ("ads_find_dc: falling back to netbios "
+ "name resolution for domain '%s' (realm '%s')\n",
+ c_domain, c_realm));
+ }
+
+ status = resolve_and_ping_netbios(ads, c_domain, c_realm);
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ DEBUG(1, ("ads_find_dc: "
+ "name resolution for realm '%s' (domain '%s') failed: %s\n",
+ c_realm, c_domain, nt_errstr(status)));
+ return status;
+}
+/**
+ * Connect to the LDAP server
+ * @param ads Pointer to an existing ADS_STRUCT
+ * @return status of connection
+ **/
+ADS_STATUS ads_connect(ADS_STRUCT *ads)
+{
+ int version = LDAP_VERSION3;
+ ADS_STATUS status;
+ NTSTATUS ntstatus;
+ char addr[INET6_ADDRSTRLEN];
+ struct sockaddr_storage existing_ss;
+
+ zero_sockaddr(&existing_ss);
+
+ /*
+ * ads_connect can be passed in a reused ADS_STRUCT
+ * with an existing non-zero ads->ldap.ss IP address
+ * that was stored by going through ads_find_dc()
+ * if ads->server.ldap_server was NULL.
+ *
+ * If ads->server.ldap_server is still NULL but
+ * the target address isn't the zero address, then
+ * store that address off off before zeroing out
+ * ads->ldap so we don't keep doing multiple calls
+ * to ads_find_dc() in the reuse case.
+ *
+ * If a caller wants a clean ADS_STRUCT they
+ * will TALLOC_FREE it and allocate a new one
+ * by calling ads_init(), which ensures
+ * ads->ldap.ss is a properly zero'ed out valid IP
+ * address.
+ */
+ if (ads->server.ldap_server == NULL && !is_zero_addr(&ads->ldap.ss)) {
+ /* Save off the address we previously found by ads_find_dc(). */
+ existing_ss = ads->ldap.ss;
+ }
+
+ ads_zero_ldap(ads);
+ ZERO_STRUCT(ads->ldap_wrap_data);
+ ads->ldap.last_attempt = time_mono(NULL);
+ ads->ldap_wrap_data.wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
+
+ /* try with a user specified server */
+
+ if (DEBUGLEVEL >= 11) {
+ char *s = NDR_PRINT_STRUCT_STRING(talloc_tos(), ads_struct, ads);
+ DEBUG(11,("ads_connect: entering\n"));
+ DEBUGADD(11,("%s\n", s));
+ TALLOC_FREE(s);
+ }
+
+ if (ads->server.ldap_server) {
+ bool ok = false;
+ struct sockaddr_storage ss;
+
+ DBG_DEBUG("Resolving name of LDAP server '%s'.\n",
+ ads->server.ldap_server);
+ ok = resolve_name(ads->server.ldap_server, &ss, 0x20, true);
+ if (!ok) {
+ DEBUG(5,("ads_connect: unable to resolve name %s\n",
+ ads->server.ldap_server));
+ status = ADS_ERROR_NT(NT_STATUS_NOT_FOUND);
+ goto out;
+ }
+
+ if (is_zero_addr(&ss)) {
+ status = ADS_ERROR_NT(NT_STATUS_NOT_FOUND);
+ goto out;
+ }
+
+ ok = ads_try_connect(ads, ads->server.gc, &ss);
+ if (ok) {
+ goto got_connection;
+ }
+
+ /* The choice of which GC use is handled one level up in
+ ads_connect_gc(). If we continue on from here with
+ ads_find_dc() we will get GC searches on port 389 which
+ doesn't work. --jerry */
+
+ if (ads->server.gc == true) {
+ return ADS_ERROR(LDAP_OPERATIONS_ERROR);
+ }
+
+ if (ads->server.no_fallback) {
+ status = ADS_ERROR_NT(NT_STATUS_NOT_FOUND);
+ goto out;
+ }
+ }
+
+ if (!is_zero_addr(&existing_ss)) {
+ /* We saved off who we should talk to. */
+ bool ok = ads_try_connect(ads,
+ ads->server.gc,
+ &existing_ss);
+ if (ok) {
+ goto got_connection;
+ }
+ /*
+ * Keep trying to find a server and fall through
+ * into ads_find_dc() again.
+ */
+ DBG_DEBUG("Failed to connect to DC via LDAP server IP address, "
+ "trying to find another DC.\n");
+ }
+
+ ntstatus = ads_find_dc(ads);
+ if (NT_STATUS_IS_OK(ntstatus)) {
+ goto got_connection;
+ }
+
+ status = ADS_ERROR_NT(ntstatus);
+ goto out;
+
+got_connection:
+
+ print_sockaddr(addr, sizeof(addr), &ads->ldap.ss);
+ DEBUG(3,("Successfully contacted LDAP server %s\n", addr));
+
+ if (!ads->auth.user_name) {
+ /* Must use the userPrincipalName value here or sAMAccountName
+ and not servicePrincipalName; found by Guenther Deschner */
+ ads->auth.user_name = talloc_asprintf(ads,
+ "%s$",
+ lp_netbios_name());
+ if (ads->auth.user_name == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (ads->auth.realm == NULL) {
+ ads->auth.realm = talloc_strdup(ads, ads->config.realm);
+ if (ads->auth.realm == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (!ads->auth.kdc_server) {
+ print_sockaddr(addr, sizeof(addr), &ads->ldap.ss);
+ ads->auth.kdc_server = talloc_strdup(ads, addr);
+ if (ads->auth.kdc_server == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ /* If the caller() requested no LDAP bind, then we are done */
+
+ if (ads->auth.flags & ADS_AUTH_NO_BIND) {
+ status = ADS_SUCCESS;
+ goto out;
+ }
+
+ ads->ldap_wrap_data.mem_ctx = talloc_init("ads LDAP connection memory");
+ if (!ads->ldap_wrap_data.mem_ctx) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ /* Otherwise setup the TCP LDAP session */
+
+ ads->ldap.ld = ldap_open_with_timeout(ads->config.ldap_server_name,
+ &ads->ldap.ss,
+ ads->ldap.port, lp_ldap_timeout());
+ if (ads->ldap.ld == NULL) {
+ status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
+ goto out;
+ }
+ DEBUG(3,("Connected to LDAP server %s\n", ads->config.ldap_server_name));
+
+ /* cache the successful connection for workgroup and realm */
+ if (ads_closest_dc(ads)) {
+ saf_store( ads->server.workgroup, ads->config.ldap_server_name);
+ saf_store( ads->server.realm, ads->config.ldap_server_name);
+ }
+
+ ldap_set_option(ads->ldap.ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+
+ /* fill in the current time and offsets */
+
+ status = ads_current_time( ads );
+ if ( !ADS_ERR_OK(status) ) {
+ goto out;
+ }
+
+ /* Now do the bind */
+
+ if (ads->auth.flags & ADS_AUTH_ANON_BIND) {
+ status = ADS_ERROR(ldap_simple_bind_s(ads->ldap.ld, NULL, NULL));
+ goto out;
+ }
+
+ if (ads->auth.flags & ADS_AUTH_SIMPLE_BIND) {
+ status = ADS_ERROR(ldap_simple_bind_s(ads->ldap.ld, ads->auth.user_name, ads->auth.password));
+ goto out;
+ }
+
+ status = ads_sasl_bind(ads);
+
+ out:
+ if (DEBUGLEVEL >= 11) {
+ char *s = NDR_PRINT_STRUCT_STRING(talloc_tos(), ads_struct, ads);
+ DEBUG(11,("ads_connect: leaving with: %s\n",
+ ads_errstr(status)));
+ DEBUGADD(11,("%s\n", s));
+ TALLOC_FREE(s);
+ }
+
+ return status;
+}
+
+/**
+ * Connect to the LDAP server using given credentials
+ * @param ads Pointer to an existing ADS_STRUCT
+ * @return status of connection
+ **/
+ADS_STATUS ads_connect_user_creds(ADS_STRUCT *ads)
+{
+ ads->auth.flags |= ADS_AUTH_USER_CREDS;
+
+ return ads_connect(ads);
+}
+
+/**
+ * Zero out the internal ads->ldap struct and initialize the address to zero IP.
+ * @param ads Pointer to an existing ADS_STRUCT
+ *
+ * Sets the ads->ldap.ss to a valid
+ * zero ip address that can be detected by
+ * our is_zero_addr() function. Otherwise
+ * it is left as AF_UNSPEC (0).
+ **/
+void ads_zero_ldap(ADS_STRUCT *ads)
+{
+ ZERO_STRUCT(ads->ldap);
+ /*
+ * Initialize the sockaddr_storage so we can use
+ * sockaddr test functions against it.
+ */
+ zero_sockaddr(&ads->ldap.ss);
+}
+
+/**
+ * Disconnect the LDAP server
+ * @param ads Pointer to an existing ADS_STRUCT
+ **/
+void ads_disconnect(ADS_STRUCT *ads)
+{
+ if (ads->ldap.ld) {
+ ldap_unbind(ads->ldap.ld);
+ ads->ldap.ld = NULL;
+ }
+ if (ads->ldap_wrap_data.wrap_ops &&
+ ads->ldap_wrap_data.wrap_ops->disconnect) {
+ ads->ldap_wrap_data.wrap_ops->disconnect(&ads->ldap_wrap_data);
+ }
+ if (ads->ldap_wrap_data.mem_ctx) {
+ talloc_free(ads->ldap_wrap_data.mem_ctx);
+ }
+ ads_zero_ldap(ads);
+ ZERO_STRUCT(ads->ldap_wrap_data);
+}
+
+/*
+ Duplicate a struct berval into talloc'ed memory
+ */
+static struct berval *dup_berval(TALLOC_CTX *ctx, const struct berval *in_val)
+{
+ struct berval *value;
+
+ if (!in_val) return NULL;
+
+ value = talloc_zero(ctx, struct berval);
+ if (value == NULL)
+ return NULL;
+ if (in_val->bv_len == 0) return value;
+
+ value->bv_len = in_val->bv_len;
+ value->bv_val = (char *)talloc_memdup(ctx, in_val->bv_val,
+ in_val->bv_len);
+ return value;
+}
+
+/*
+ Make a values list out of an array of (struct berval *)
+ */
+static struct berval **ads_dup_values(TALLOC_CTX *ctx,
+ const struct berval **in_vals)
+{
+ struct berval **values;
+ int i;
+
+ if (!in_vals) return NULL;
+ for (i=0; in_vals[i]; i++)
+ ; /* count values */
+ values = talloc_zero_array(ctx, struct berval *, i+1);
+ if (!values) return NULL;
+
+ for (i=0; in_vals[i]; i++) {
+ values[i] = dup_berval(ctx, in_vals[i]);
+ }
+ return values;
+}
+
+/*
+ UTF8-encode a values list out of an array of (char *)
+ */
+static char **ads_push_strvals(TALLOC_CTX *ctx, const char **in_vals)
+{
+ char **values;
+ int i;
+ size_t size;
+
+ if (!in_vals) return NULL;
+ for (i=0; in_vals[i]; i++)
+ ; /* count values */
+ values = talloc_zero_array(ctx, char *, i+1);
+ if (!values) return NULL;
+
+ for (i=0; in_vals[i]; i++) {
+ if (!push_utf8_talloc(ctx, &values[i], in_vals[i], &size)) {
+ TALLOC_FREE(values);
+ return NULL;
+ }
+ }
+ return values;
+}
+
+/*
+ Pull a (char *) array out of a UTF8-encoded values list
+ */
+static char **ads_pull_strvals(TALLOC_CTX *ctx, const char **in_vals)
+{
+ char **values;
+ int i;
+ size_t converted_size;
+
+ if (!in_vals) return NULL;
+ for (i=0; in_vals[i]; i++)
+ ; /* count values */
+ values = talloc_zero_array(ctx, char *, i+1);
+ if (!values) return NULL;
+
+ for (i=0; in_vals[i]; i++) {
+ if (!pull_utf8_talloc(ctx, &values[i], in_vals[i],
+ &converted_size)) {
+ DEBUG(0,("ads_pull_strvals: pull_utf8_talloc failed: "
+ "%s\n", strerror(errno)));
+ }
+ }
+ return values;
+}
+
+/**
+ * Do a search with paged results. cookie must be null on the first
+ * call, and then returned on each subsequent call. It will be null
+ * again when the entire search is complete
+ * @param ads connection to ads server
+ * @param bind_path Base dn for the search
+ * @param scope Scope of search (LDAP_SCOPE_BASE | LDAP_SCOPE_ONE | LDAP_SCOPE_SUBTREE)
+ * @param expr Search expression - specified in local charset
+ * @param attrs Attributes to retrieve - specified in utf8 or ascii
+ * @param res ** which will contain results - free res* with ads_msgfree()
+ * @param count Number of entries retrieved on this page
+ * @param cookie The paged results cookie to be returned on subsequent calls
+ * @return status of search
+ **/
+static ADS_STATUS ads_do_paged_search_args(ADS_STRUCT *ads,
+ const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, void *args,
+ LDAPMessage **res,
+ int *count, struct berval **cookie)
+{
+ int rc, i, version;
+ char *utf8_expr, *utf8_path, **search_attrs = NULL;
+ size_t converted_size;
+ LDAPControl PagedResults, NoReferrals, ExternalCtrl, *controls[4], **rcontrols;
+ BerElement *cookie_be = NULL;
+ struct berval *cookie_bv= NULL;
+ BerElement *ext_be = NULL;
+ struct berval *ext_bv= NULL;
+
+ TALLOC_CTX *ctx;
+ ads_control *external_control = (ads_control *) args;
+
+ *res = NULL;
+
+ if (!(ctx = talloc_init("ads_do_paged_search_args")))
+ return ADS_ERROR(LDAP_NO_MEMORY);
+
+ /* 0 means the conversion worked but the result was empty
+ so we only fail if it's -1. In any case, it always
+ at least nulls out the dest */
+ if (!push_utf8_talloc(ctx, &utf8_expr, expr, &converted_size) ||
+ !push_utf8_talloc(ctx, &utf8_path, bind_path, &converted_size))
+ {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ if (!attrs || !(*attrs))
+ search_attrs = NULL;
+ else {
+ /* This would be the utf8-encoded version...*/
+ /* if (!(search_attrs = ads_push_strvals(ctx, attrs))) */
+ if (!(search_attrs = str_list_copy(talloc_tos(), attrs))) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ /* Paged results only available on ldap v3 or later */
+ ldap_get_option(ads->ldap.ld, LDAP_OPT_PROTOCOL_VERSION, &version);
+ if (version < LDAP_VERSION3) {
+ rc = LDAP_NOT_SUPPORTED;
+ goto done;
+ }
+
+ cookie_be = ber_alloc_t(LBER_USE_DER);
+ if (*cookie) {
+ ber_printf(cookie_be, "{iO}", (ber_int_t) ads->config.ldap_page_size, *cookie);
+ ber_bvfree(*cookie); /* don't need it from last time */
+ *cookie = NULL;
+ } else {
+ ber_printf(cookie_be, "{io}", (ber_int_t) ads->config.ldap_page_size, "", 0);
+ }
+ ber_flatten(cookie_be, &cookie_bv);
+ PagedResults.ldctl_oid = discard_const_p(char, ADS_PAGE_CTL_OID);
+ PagedResults.ldctl_iscritical = (char) 1;
+ PagedResults.ldctl_value.bv_len = cookie_bv->bv_len;
+ PagedResults.ldctl_value.bv_val = cookie_bv->bv_val;
+
+ NoReferrals.ldctl_oid = discard_const_p(char, ADS_NO_REFERRALS_OID);
+ NoReferrals.ldctl_iscritical = (char) 0;
+ NoReferrals.ldctl_value.bv_len = 0;
+ NoReferrals.ldctl_value.bv_val = discard_const_p(char, "");
+
+ if (external_control &&
+ (strequal(external_control->control, ADS_EXTENDED_DN_OID) ||
+ strequal(external_control->control, ADS_SD_FLAGS_OID))) {
+
+ ExternalCtrl.ldctl_oid = discard_const_p(char, external_control->control);
+ ExternalCtrl.ldctl_iscritical = (char) external_control->critical;
+
+ /* win2k does not accept a ldctl_value being passed in */
+
+ if (external_control->val != 0) {
+
+ if ((ext_be = ber_alloc_t(LBER_USE_DER)) == NULL ) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ if ((ber_printf(ext_be, "{i}", (ber_int_t) external_control->val)) == -1) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+ if ((ber_flatten(ext_be, &ext_bv)) == -1) {
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ ExternalCtrl.ldctl_value.bv_len = ext_bv->bv_len;
+ ExternalCtrl.ldctl_value.bv_val = ext_bv->bv_val;
+
+ } else {
+ ExternalCtrl.ldctl_value.bv_len = 0;
+ ExternalCtrl.ldctl_value.bv_val = NULL;
+ }
+
+ controls[0] = &NoReferrals;
+ controls[1] = &PagedResults;
+ controls[2] = &ExternalCtrl;
+ controls[3] = NULL;
+
+ } else {
+ controls[0] = &NoReferrals;
+ controls[1] = &PagedResults;
+ controls[2] = NULL;
+ }
+
+ /* we need to disable referrals as the openldap libs don't
+ handle them and paged results at the same time. Using them
+ together results in the result record containing the server
+ page control being removed from the result list (tridge/jmcd)
+
+ leaving this in despite the control that says don't generate
+ referrals, in case the server doesn't support it (jmcd)
+ */
+ ldap_set_option(ads->ldap.ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
+
+ rc = ldap_search_with_timeout(ads->ldap.ld, utf8_path, scope, utf8_expr,
+ search_attrs, 0, controls,
+ NULL, LDAP_NO_LIMIT,
+ (LDAPMessage **)res);
+
+ ber_free(cookie_be, 1);
+ ber_bvfree(cookie_bv);
+
+ if (rc) {
+ DEBUG(3,("ads_do_paged_search_args: ldap_search_with_timeout(%s) -> %s\n", expr,
+ ldap_err2string(rc)));
+ if (rc == LDAP_OTHER) {
+ char *ldap_errmsg;
+ int ret;
+
+ ret = ldap_parse_result(ads->ldap.ld,
+ *res,
+ NULL,
+ NULL,
+ &ldap_errmsg,
+ NULL,
+ NULL,
+ 0);
+ if (ret == LDAP_SUCCESS) {
+ DEBUG(3, ("ldap_search_with_timeout(%s) "
+ "error: %s\n", expr, ldap_errmsg));
+ ldap_memfree(ldap_errmsg);
+ }
+ }
+ goto done;
+ }
+
+ rc = ldap_parse_result(ads->ldap.ld, *res, NULL, NULL, NULL,
+ NULL, &rcontrols, 0);
+
+ if (!rcontrols) {
+ goto done;
+ }
+
+ for (i=0; rcontrols[i]; i++) {
+ if (strcmp(ADS_PAGE_CTL_OID, rcontrols[i]->ldctl_oid) == 0) {
+ cookie_be = ber_init(&rcontrols[i]->ldctl_value);
+ ber_scanf(cookie_be,"{iO}", (ber_int_t *) count,
+ &cookie_bv);
+ /* the berval is the cookie, but must be freed when
+ it is all done */
+ if (cookie_bv->bv_len) /* still more to do */
+ *cookie=ber_bvdup(cookie_bv);
+ else
+ *cookie=NULL;
+ ber_bvfree(cookie_bv);
+ ber_free(cookie_be, 1);
+ break;
+ }
+ }
+ ldap_controls_free(rcontrols);
+
+done:
+ talloc_destroy(ctx);
+
+ if (ext_be) {
+ ber_free(ext_be, 1);
+ }
+
+ if (ext_bv) {
+ ber_bvfree(ext_bv);
+ }
+
+ if (rc != LDAP_SUCCESS && *res != NULL) {
+ ads_msgfree(ads, *res);
+ *res = NULL;
+ }
+
+ /* if/when we decide to utf8-encode attrs, take out this next line */
+ TALLOC_FREE(search_attrs);
+
+ return ADS_ERROR(rc);
+}
+
+static ADS_STATUS ads_do_paged_search(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, LDAPMessage **res,
+ int *count, struct berval **cookie)
+{
+ return ads_do_paged_search_args(ads, bind_path, scope, expr, attrs, NULL, res, count, cookie);
+}
+
+
+/**
+ * Get all results for a search. This uses ads_do_paged_search() to return
+ * all entries in a large search.
+ * @param ads connection to ads server
+ * @param bind_path Base dn for the search
+ * @param scope Scope of search (LDAP_SCOPE_BASE | LDAP_SCOPE_ONE | LDAP_SCOPE_SUBTREE)
+ * @param expr Search expression
+ * @param attrs Attributes to retrieve
+ * @param res ** which will contain results - free res* with ads_msgfree()
+ * @return status of search
+ **/
+ ADS_STATUS ads_do_search_all_args(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, void *args,
+ LDAPMessage **res)
+{
+ struct berval *cookie = NULL;
+ int count = 0;
+ ADS_STATUS status;
+
+ *res = NULL;
+ status = ads_do_paged_search_args(ads, bind_path, scope, expr, attrs, args, res,
+ &count, &cookie);
+
+ if (!ADS_ERR_OK(status))
+ return status;
+
+#ifdef HAVE_LDAP_ADD_RESULT_ENTRY
+ while (cookie) {
+ LDAPMessage *res2 = NULL;
+ LDAPMessage *msg, *next;
+
+ status = ads_do_paged_search_args(ads, bind_path, scope, expr,
+ attrs, args, &res2, &count, &cookie);
+ if (!ADS_ERR_OK(status)) {
+ break;
+ }
+
+ /* this relies on the way that ldap_add_result_entry() works internally. I hope
+ that this works on all ldap libs, but I have only tested with openldap */
+ for (msg = ads_first_message(ads, res2); msg; msg = next) {
+ next = ads_next_message(ads, msg);
+ ldap_add_result_entry((LDAPMessage **)res, msg);
+ }
+ /* note that we do not free res2, as the memory is now
+ part of the main returned list */
+ }
+#else
+ DEBUG(0, ("no ldap_add_result_entry() support in LDAP libs!\n"));
+ status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+#endif
+
+ return status;
+}
+
+ ADS_STATUS ads_do_search_all(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, LDAPMessage **res)
+{
+ return ads_do_search_all_args(ads, bind_path, scope, expr, attrs, NULL, res);
+}
+
+ ADS_STATUS ads_do_search_all_sd_flags(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, uint32_t sd_flags,
+ LDAPMessage **res)
+{
+ ads_control args;
+
+ args.control = ADS_SD_FLAGS_OID;
+ args.val = sd_flags;
+ args.critical = True;
+
+ return ads_do_search_all_args(ads, bind_path, scope, expr, attrs, &args, res);
+}
+
+
+/**
+ * Run a function on all results for a search. Uses ads_do_paged_search() and
+ * runs the function as each page is returned, using ads_process_results()
+ * @param ads connection to ads server
+ * @param bind_path Base dn for the search
+ * @param scope Scope of search (LDAP_SCOPE_BASE | LDAP_SCOPE_ONE | LDAP_SCOPE_SUBTREE)
+ * @param expr Search expression - specified in local charset
+ * @param attrs Attributes to retrieve - specified in UTF-8 or ascii
+ * @param fn Function which takes attr name, values list, and data_area
+ * @param data_area Pointer which is passed to function on each call
+ * @return status of search
+ **/
+ADS_STATUS ads_do_search_all_fn(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr, const char **attrs,
+ bool (*fn)(ADS_STRUCT *, char *, void **, void *),
+ void *data_area)
+{
+ struct berval *cookie = NULL;
+ int count = 0;
+ ADS_STATUS status;
+ LDAPMessage *res;
+
+ status = ads_do_paged_search(ads, bind_path, scope, expr, attrs, &res,
+ &count, &cookie);
+
+ if (!ADS_ERR_OK(status)) return status;
+
+ ads_process_results(ads, res, fn, data_area);
+ ads_msgfree(ads, res);
+
+ while (cookie) {
+ status = ads_do_paged_search(ads, bind_path, scope, expr, attrs,
+ &res, &count, &cookie);
+
+ if (!ADS_ERR_OK(status)) break;
+
+ ads_process_results(ads, res, fn, data_area);
+ ads_msgfree(ads, res);
+ }
+
+ return status;
+}
+
+/**
+ * Do a search with a timeout.
+ * @param ads connection to ads server
+ * @param bind_path Base dn for the search
+ * @param scope Scope of search (LDAP_SCOPE_BASE | LDAP_SCOPE_ONE | LDAP_SCOPE_SUBTREE)
+ * @param expr Search expression
+ * @param attrs Attributes to retrieve
+ * @param res ** which will contain results - free res* with ads_msgfree()
+ * @return status of search
+ **/
+ ADS_STATUS ads_do_search(ADS_STRUCT *ads, const char *bind_path, int scope,
+ const char *expr,
+ const char **attrs, LDAPMessage **res)
+{
+ int rc;
+ char *utf8_expr, *utf8_path, **search_attrs = NULL;
+ size_t converted_size;
+ TALLOC_CTX *ctx;
+
+ *res = NULL;
+ if (!(ctx = talloc_init("ads_do_search"))) {
+ DEBUG(1,("ads_do_search: talloc_init() failed!\n"));
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ /* 0 means the conversion worked but the result was empty
+ so we only fail if it's negative. In any case, it always
+ at least nulls out the dest */
+ if (!push_utf8_talloc(ctx, &utf8_expr, expr, &converted_size) ||
+ !push_utf8_talloc(ctx, &utf8_path, bind_path, &converted_size))
+ {
+ DEBUG(1,("ads_do_search: push_utf8_talloc() failed!\n"));
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+
+ if (!attrs || !(*attrs))
+ search_attrs = NULL;
+ else {
+ /* This would be the utf8-encoded version...*/
+ /* if (!(search_attrs = ads_push_strvals(ctx, attrs))) */
+ if (!(search_attrs = str_list_copy(talloc_tos(), attrs)))
+ {
+ DEBUG(1,("ads_do_search: str_list_copy() failed!\n"));
+ rc = LDAP_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ /* see the note in ads_do_paged_search - we *must* disable referrals */
+ ldap_set_option(ads->ldap.ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
+
+ rc = ldap_search_with_timeout(ads->ldap.ld, utf8_path, scope, utf8_expr,
+ search_attrs, 0, NULL, NULL,
+ LDAP_NO_LIMIT,
+ (LDAPMessage **)res);
+
+ if (rc == LDAP_SIZELIMIT_EXCEEDED) {
+ DEBUG(3,("Warning! sizelimit exceeded in ldap. Truncating.\n"));
+ rc = 0;
+ }
+
+ done:
+ talloc_destroy(ctx);
+ /* if/when we decide to utf8-encode attrs, take out this next line */
+ TALLOC_FREE(search_attrs);
+ return ADS_ERROR(rc);
+}
+/**
+ * Do a general ADS search
+ * @param ads connection to ads server
+ * @param res ** which will contain results - free res* with ads_msgfree()
+ * @param expr Search expression
+ * @param attrs Attributes to retrieve
+ * @return status of search
+ **/
+ ADS_STATUS ads_search(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *expr, const char **attrs)
+{
+ return ads_do_search(ads, ads->config.bind_path, LDAP_SCOPE_SUBTREE,
+ expr, attrs, res);
+}
+
+/**
+ * Do a search on a specific DistinguishedName
+ * @param ads connection to ads server
+ * @param res ** which will contain results - free res* with ads_msgfree()
+ * @param dn DistinguishedName to search
+ * @param attrs Attributes to retrieve
+ * @return status of search
+ **/
+ ADS_STATUS ads_search_dn(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *dn, const char **attrs)
+{
+ return ads_do_search(ads, dn, LDAP_SCOPE_BASE, "(objectclass=*)",
+ attrs, res);
+}
+
+/**
+ * Free up memory from a ads_search
+ * @param ads connection to ads server
+ * @param msg Search results to free
+ **/
+ void ads_msgfree(ADS_STRUCT *ads, LDAPMessage *msg)
+{
+ if (!msg) return;
+ ldap_msgfree(msg);
+}
+
+/**
+ * Get a dn from search results
+ * @param ads connection to ads server
+ * @param msg Search result
+ * @return dn string
+ **/
+ char *ads_get_dn(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, LDAPMessage *msg)
+{
+ char *utf8_dn, *unix_dn;
+ size_t converted_size;
+
+ utf8_dn = ldap_get_dn(ads->ldap.ld, msg);
+
+ if (!utf8_dn) {
+ DEBUG (5, ("ads_get_dn: ldap_get_dn failed\n"));
+ return NULL;
+ }
+
+ if (!pull_utf8_talloc(mem_ctx, &unix_dn, utf8_dn, &converted_size)) {
+ DEBUG(0,("ads_get_dn: string conversion failure utf8 [%s]\n",
+ utf8_dn ));
+ return NULL;
+ }
+ ldap_memfree(utf8_dn);
+ return unix_dn;
+}
+
+/**
+ * Get the parent from a dn
+ * @param dn the dn to return the parent from
+ * @return parent dn string
+ **/
+char *ads_parent_dn(const char *dn)
+{
+ char *p;
+
+ if (dn == NULL) {
+ return NULL;
+ }
+
+ p = strchr(dn, ',');
+
+ if (p == NULL) {
+ return NULL;
+ }
+
+ return p+1;
+}
+
+/**
+ * Find a machine account given a hostname
+ * @param ads connection to ads server
+ * @param res ** which will contain results - free res* with ads_msgfree()
+ * @param host Hostname to search for
+ * @return status of search
+ **/
+ ADS_STATUS ads_find_machine_acct(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *machine)
+{
+ ADS_STATUS status;
+ char *expr;
+ const char *attrs[] = {
+ /* This is how Windows checks for machine accounts */
+ "objectClass",
+ "SamAccountName",
+ "userAccountControl",
+ "DnsHostName",
+ "ServicePrincipalName",
+ "userPrincipalName",
+ "unicodePwd",
+
+ /* Additional attributes Samba checks */
+ "msDS-AdditionalDnsHostName",
+ "msDS-SupportedEncryptionTypes",
+ "nTSecurityDescriptor",
+ "objectSid",
+
+ NULL
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *res = NULL;
+
+ /* the easiest way to find a machine account anywhere in the tree
+ is to look for hostname$ */
+ expr = talloc_asprintf(frame, "(samAccountName=%s$)", machine);
+ if (expr == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto done;
+ }
+
+ status = ads_search(ads, res, expr, attrs);
+ if (ADS_ERR_OK(status)) {
+ if (ads_count_replies(ads, *res) != 1) {
+ status = ADS_ERROR_LDAP(LDAP_NO_SUCH_OBJECT);
+ }
+ }
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/**
+ * Initialize a list of mods to be used in a modify request
+ * @param ctx An initialized TALLOC_CTX
+ * @return allocated ADS_MODLIST
+ **/
+ADS_MODLIST ads_init_mods(TALLOC_CTX *ctx)
+{
+#define ADS_MODLIST_ALLOC_SIZE 10
+ LDAPMod **mods;
+
+ if ((mods = talloc_zero_array(ctx, LDAPMod *, ADS_MODLIST_ALLOC_SIZE + 1)))
+ /* -1 is safety to make sure we don't go over the end.
+ need to reset it to NULL before doing ldap modify */
+ mods[ADS_MODLIST_ALLOC_SIZE] = (LDAPMod *) -1;
+
+ return (ADS_MODLIST)mods;
+}
+
+
+/*
+ add an attribute to the list, with values list already constructed
+*/
+static ADS_STATUS ads_modlist_add(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ int mod_op, const char *name,
+ const void *_invals)
+{
+ int curmod;
+ LDAPMod **modlist = (LDAPMod **) *mods;
+ struct berval **ber_values = NULL;
+ char **char_values = NULL;
+
+ if (!_invals) {
+ mod_op = LDAP_MOD_DELETE;
+ } else {
+ if (mod_op & LDAP_MOD_BVALUES) {
+ const struct berval **b;
+ b = discard_const_p(const struct berval *, _invals);
+ ber_values = ads_dup_values(ctx, b);
+ } else {
+ const char **c;
+ c = discard_const_p(const char *, _invals);
+ char_values = ads_push_strvals(ctx, c);
+ }
+ }
+
+ /* find the first empty slot */
+ for (curmod=0; modlist[curmod] && modlist[curmod] != (LDAPMod *) -1;
+ curmod++);
+ if (modlist[curmod] == (LDAPMod *) -1) {
+ if (!(modlist = talloc_realloc(ctx, modlist, LDAPMod *,
+ curmod+ADS_MODLIST_ALLOC_SIZE+1)))
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ memset(&modlist[curmod], 0,
+ ADS_MODLIST_ALLOC_SIZE*sizeof(LDAPMod *));
+ modlist[curmod+ADS_MODLIST_ALLOC_SIZE] = (LDAPMod *) -1;
+ *mods = (ADS_MODLIST)modlist;
+ }
+
+ if (!(modlist[curmod] = talloc_zero(ctx, LDAPMod)))
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ modlist[curmod]->mod_type = talloc_strdup(ctx, name);
+ if (mod_op & LDAP_MOD_BVALUES) {
+ modlist[curmod]->mod_bvalues = ber_values;
+ } else if (mod_op & LDAP_MOD_DELETE) {
+ modlist[curmod]->mod_values = NULL;
+ } else {
+ modlist[curmod]->mod_values = char_values;
+ }
+
+ modlist[curmod]->mod_op = mod_op;
+ return ADS_ERROR(LDAP_SUCCESS);
+}
+
+/**
+ * Add a single string value to a mod list
+ * @param ctx An initialized TALLOC_CTX
+ * @param mods An initialized ADS_MODLIST
+ * @param name The attribute name to add
+ * @param val The value to add - NULL means DELETE
+ * @return ADS STATUS indicating success of add
+ **/
+ADS_STATUS ads_mod_str(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const char *val)
+{
+ const char *values[2];
+
+ values[0] = val;
+ values[1] = NULL;
+
+ if (!val)
+ return ads_modlist_add(ctx, mods, LDAP_MOD_DELETE, name, NULL);
+ return ads_modlist_add(ctx, mods, LDAP_MOD_REPLACE, name, values);
+}
+
+/**
+ * Add an array of string values to a mod list
+ * @param ctx An initialized TALLOC_CTX
+ * @param mods An initialized ADS_MODLIST
+ * @param name The attribute name to add
+ * @param vals The array of string values to add - NULL means DELETE
+ * @return ADS STATUS indicating success of add
+ **/
+ADS_STATUS ads_mod_strlist(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const char **vals)
+{
+ if (!vals)
+ return ads_modlist_add(ctx, mods, LDAP_MOD_DELETE, name, NULL);
+ return ads_modlist_add(ctx, mods, LDAP_MOD_REPLACE,
+ name, (const void **) vals);
+}
+
+/**
+ * Add a single ber-encoded value to a mod list
+ * @param ctx An initialized TALLOC_CTX
+ * @param mods An initialized ADS_MODLIST
+ * @param name The attribute name to add
+ * @param val The value to add - NULL means DELETE
+ * @return ADS STATUS indicating success of add
+ **/
+static ADS_STATUS ads_mod_ber(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const struct berval *val)
+{
+ const struct berval *values[2];
+
+ values[0] = val;
+ values[1] = NULL;
+ if (!val)
+ return ads_modlist_add(ctx, mods, LDAP_MOD_DELETE, name, NULL);
+ return ads_modlist_add(ctx, mods, LDAP_MOD_REPLACE|LDAP_MOD_BVALUES,
+ name, (const void **) values);
+}
+
+static void ads_print_error(int ret, LDAP *ld)
+{
+ if (ret != 0) {
+ char *ld_error = NULL;
+ ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &ld_error);
+ DBG_ERR("AD LDAP ERROR: %d (%s): %s\n",
+ ret,
+ ldap_err2string(ret),
+ ld_error);
+ SAFE_FREE(ld_error);
+ }
+}
+
+/**
+ * Perform an ldap modify
+ * @param ads connection to ads server
+ * @param mod_dn DistinguishedName to modify
+ * @param mods list of modifications to perform
+ * @return status of modify
+ **/
+ADS_STATUS ads_gen_mod(ADS_STRUCT *ads, const char *mod_dn, ADS_MODLIST mods)
+{
+ int ret,i;
+ char *utf8_dn = NULL;
+ size_t converted_size;
+ /*
+ this control is needed to modify that contains a currently
+ non-existent attribute (but allowable for the object) to run
+ */
+ LDAPControl PermitModify = {
+ discard_const_p(char, ADS_PERMIT_MODIFY_OID),
+ {0, NULL},
+ (char) 1};
+ LDAPControl *controls[2];
+
+ DBG_INFO("AD LDAP: Modifying %s\n", mod_dn);
+
+ controls[0] = &PermitModify;
+ controls[1] = NULL;
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_dn, mod_dn, &converted_size)) {
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ /* find the end of the list, marked by NULL or -1 */
+ for(i=0;(mods[i]!=0)&&(mods[i]!=(LDAPMod *) -1);i++);
+ /* make sure the end of the list is NULL */
+ mods[i] = NULL;
+ ret = ldap_modify_ext_s(ads->ldap.ld, utf8_dn,
+ (LDAPMod **) mods, controls, NULL);
+ ads_print_error(ret, ads->ldap.ld);
+ TALLOC_FREE(utf8_dn);
+ return ADS_ERROR(ret);
+}
+
+/**
+ * Perform an ldap add
+ * @param ads connection to ads server
+ * @param new_dn DistinguishedName to add
+ * @param mods list of attributes and values for DN
+ * @return status of add
+ **/
+ADS_STATUS ads_gen_add(ADS_STRUCT *ads, const char *new_dn, ADS_MODLIST mods)
+{
+ int ret, i;
+ char *utf8_dn = NULL;
+ size_t converted_size;
+
+ DBG_INFO("AD LDAP: Adding %s\n", new_dn);
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_dn, new_dn, &converted_size)) {
+ DEBUG(1, ("ads_gen_add: push_utf8_talloc failed!\n"));
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ /* find the end of the list, marked by NULL or -1 */
+ for(i=0;(mods[i]!=0)&&(mods[i]!=(LDAPMod *) -1);i++);
+ /* make sure the end of the list is NULL */
+ mods[i] = NULL;
+
+ ret = ldap_add_ext_s(ads->ldap.ld, utf8_dn, (LDAPMod**)mods, NULL, NULL);
+ ads_print_error(ret, ads->ldap.ld);
+ TALLOC_FREE(utf8_dn);
+ return ADS_ERROR(ret);
+}
+
+/**
+ * Delete a DistinguishedName
+ * @param ads connection to ads server
+ * @param new_dn DistinguishedName to delete
+ * @return status of delete
+ **/
+ADS_STATUS ads_del_dn(ADS_STRUCT *ads, char *del_dn)
+{
+ int ret;
+ char *utf8_dn = NULL;
+ size_t converted_size;
+ if (!push_utf8_talloc(talloc_tos(), &utf8_dn, del_dn, &converted_size)) {
+ DEBUG(1, ("ads_del_dn: push_utf8_talloc failed!\n"));
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ DBG_INFO("AD LDAP: Deleting %s\n", del_dn);
+
+ ret = ldap_delete_s(ads->ldap.ld, utf8_dn);
+ ads_print_error(ret, ads->ldap.ld);
+ TALLOC_FREE(utf8_dn);
+ return ADS_ERROR(ret);
+}
+
+/**
+ * Build an org unit string
+ * if org unit is Computers or blank then assume a container, otherwise
+ * assume a / separated list of organisational units.
+ * jmcd: '\' is now used for escapes so certain chars can be in the ou (e.g. #)
+ * @param ads connection to ads server
+ * @param org_unit Organizational unit
+ * @return org unit string - caller must free
+ **/
+char *ads_ou_string(ADS_STRUCT *ads, const char *org_unit)
+{
+ ADS_STATUS status;
+ char *ret = NULL;
+ char *dn = NULL;
+
+ if (!org_unit || !*org_unit) {
+
+ ret = ads_default_ou_string(ads, DS_GUID_COMPUTERS_CONTAINER);
+
+ /* samba4 might not yet respond to a wellknownobject-query */
+ return ret ? ret : SMB_STRDUP("cn=Computers");
+ }
+
+ if (strequal(org_unit, "Computers")) {
+ return SMB_STRDUP("cn=Computers");
+ }
+
+ /* jmcd: removed "\\" from the separation chars, because it is
+ needed as an escape for chars like '#' which are valid in an
+ OU name */
+ status = ads_build_path(org_unit, "/", "ou=", 1, &dn);
+ if (!ADS_ERR_OK(status)) {
+ return NULL;
+ }
+
+ return dn;
+}
+
+/**
+ * Get a org unit string for a well-known GUID
+ * @param ads connection to ads server
+ * @param wknguid Well known GUID
+ * @return org unit string - caller must free
+ **/
+char *ads_default_ou_string(ADS_STRUCT *ads, const char *wknguid)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ char *base, *wkn_dn = NULL, *ret = NULL, **wkn_dn_exp = NULL,
+ **bind_dn_exp = NULL;
+ const char *attrs[] = {"distinguishedName", NULL};
+ int new_ln, wkn_ln, bind_ln, i;
+
+ if (wknguid == NULL) {
+ return NULL;
+ }
+
+ if (asprintf(&base, "<WKGUID=%s,%s>", wknguid, ads->config.bind_path ) == -1) {
+ DEBUG(1, ("asprintf failed!\n"));
+ return NULL;
+ }
+
+ status = ads_search_dn(ads, &res, base, attrs);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("Failed while searching for: %s\n", base));
+ goto out;
+ }
+
+ if (ads_count_replies(ads, res) != 1) {
+ goto out;
+ }
+
+ /* substitute the bind-path from the well-known-guid-search result */
+ wkn_dn = ads_get_dn(ads, talloc_tos(), res);
+ if (!wkn_dn) {
+ goto out;
+ }
+
+ wkn_dn_exp = ldap_explode_dn(wkn_dn, 0);
+ if (!wkn_dn_exp) {
+ goto out;
+ }
+
+ bind_dn_exp = ldap_explode_dn(ads->config.bind_path, 0);
+ if (!bind_dn_exp) {
+ goto out;
+ }
+
+ for (wkn_ln=0; wkn_dn_exp[wkn_ln]; wkn_ln++)
+ ;
+ for (bind_ln=0; bind_dn_exp[bind_ln]; bind_ln++)
+ ;
+
+ new_ln = wkn_ln - bind_ln;
+
+ ret = SMB_STRDUP(wkn_dn_exp[0]);
+ if (!ret) {
+ goto out;
+ }
+
+ for (i=1; i < new_ln; i++) {
+ char *s = NULL;
+
+ if (asprintf(&s, "%s,%s", ret, wkn_dn_exp[i]) == -1) {
+ SAFE_FREE(ret);
+ goto out;
+ }
+
+ SAFE_FREE(ret);
+ ret = SMB_STRDUP(s);
+ free(s);
+ if (!ret) {
+ goto out;
+ }
+ }
+
+ out:
+ SAFE_FREE(base);
+ ads_msgfree(ads, res);
+ TALLOC_FREE(wkn_dn);
+ if (wkn_dn_exp) {
+ ldap_value_free(wkn_dn_exp);
+ }
+ if (bind_dn_exp) {
+ ldap_value_free(bind_dn_exp);
+ }
+
+ return ret;
+}
+
+/**
+ * Adds (appends) an item to an attribute array, rather then
+ * replacing the whole list
+ * @param ctx An initialized TALLOC_CTX
+ * @param mods An initialized ADS_MODLIST
+ * @param name name of the ldap attribute to append to
+ * @param vals an array of values to add
+ * @return status of addition
+ **/
+
+ADS_STATUS ads_add_strlist(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, const char **vals)
+{
+ return ads_modlist_add(ctx, mods, LDAP_MOD_ADD, name,
+ (const void *) vals);
+}
+
+/**
+ * Determines the an account's current KVNO via an LDAP lookup
+ * @param ads An initialized ADS_STRUCT
+ * @param account_name the NT samaccountname.
+ * @return the kvno for the account, or -1 in case of a failure.
+ **/
+
+uint32_t ads_get_kvno(ADS_STRUCT *ads, const char *account_name)
+{
+ LDAPMessage *res = NULL;
+ uint32_t kvno = (uint32_t)-1; /* -1 indicates a failure */
+ char *filter;
+ const char *attrs[] = {"msDS-KeyVersionNumber", NULL};
+ char *dn_string = NULL;
+ ADS_STATUS ret;
+
+ DEBUG(5,("ads_get_kvno: Searching for account %s\n", account_name));
+ if (asprintf(&filter, "(samAccountName=%s)", account_name) == -1) {
+ return kvno;
+ }
+ ret = ads_search(ads, &res, filter, attrs);
+ SAFE_FREE(filter);
+ if (!ADS_ERR_OK(ret) || (ads_count_replies(ads, res) != 1)) {
+ DEBUG(1,("ads_get_kvno: Account for %s not found.\n", account_name));
+ ads_msgfree(ads, res);
+ return kvno;
+ }
+
+ dn_string = ads_get_dn(ads, talloc_tos(), res);
+ if (!dn_string) {
+ DEBUG(0,("ads_get_kvno: out of memory.\n"));
+ ads_msgfree(ads, res);
+ return kvno;
+ }
+ DEBUG(5,("ads_get_kvno: Using: %s\n", dn_string));
+ TALLOC_FREE(dn_string);
+
+ /* ---------------------------------------------------------
+ * 0 is returned as a default KVNO from this point on...
+ * This is done because Windows 2000 does not support key
+ * version numbers. Chances are that a failure in the next
+ * step is simply due to Windows 2000 being used for a
+ * domain controller. */
+ kvno = 0;
+
+ if (!ads_pull_uint32(ads, res, "msDS-KeyVersionNumber", &kvno)) {
+ DEBUG(3,("ads_get_kvno: Error Determining KVNO!\n"));
+ DEBUG(3,("ads_get_kvno: Windows 2000 does not support KVNO's, so this may be normal.\n"));
+ ads_msgfree(ads, res);
+ return kvno;
+ }
+
+ /* Success */
+ DEBUG(5,("ads_get_kvno: Looked Up KVNO of: %d\n", kvno));
+ ads_msgfree(ads, res);
+ return kvno;
+}
+
+/**
+ * Determines the computer account's current KVNO via an LDAP lookup
+ * @param ads An initialized ADS_STRUCT
+ * @param machine_name the NetBIOS name of the computer, which is used to identify the computer account.
+ * @return the kvno for the computer account, or -1 in case of a failure.
+ **/
+
+uint32_t ads_get_machine_kvno(ADS_STRUCT *ads, const char *machine_name)
+{
+ char *computer_account = NULL;
+ uint32_t kvno = -1;
+
+ if (asprintf(&computer_account, "%s$", machine_name) < 0) {
+ return kvno;
+ }
+
+ kvno = ads_get_kvno(ads, computer_account);
+ free(computer_account);
+
+ return kvno;
+}
+
+/**
+ * This clears out all registered spn's for a given hostname
+ * @param ads An initialized ADS_STRUCT
+ * @param machine_name the NetBIOS name of the computer.
+ * @return 0 upon success, non-zero otherwise.
+ **/
+
+ADS_STATUS ads_clear_service_principal_names(ADS_STRUCT *ads, const char *machine_name)
+{
+ TALLOC_CTX *ctx;
+ LDAPMessage *res = NULL;
+ ADS_MODLIST mods;
+ const char *servicePrincipalName[1] = {NULL};
+ ADS_STATUS ret;
+ char *dn_string = NULL;
+
+ ret = ads_find_machine_acct(ads, &res, machine_name);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(5,("ads_clear_service_principal_names: WARNING: Host Account for %s not found... skipping operation.\n", machine_name));
+ DEBUG(5,("ads_clear_service_principal_names: WARNING: Service Principals for %s have NOT been cleared.\n", machine_name));
+ ads_msgfree(ads, res);
+ return ret;
+ }
+
+ DEBUG(5,("ads_clear_service_principal_names: Host account for %s found\n", machine_name));
+ ctx = talloc_init("ads_clear_service_principal_names");
+ if (!ctx) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (!(mods = ads_init_mods(ctx))) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ ret = ads_mod_strlist(ctx, &mods, "servicePrincipalName", servicePrincipalName);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_clear_service_principal_names: Error creating strlist.\n"));
+ ads_msgfree(ads, res);
+ talloc_destroy(ctx);
+ return ret;
+ }
+ dn_string = ads_get_dn(ads, talloc_tos(), res);
+ if (!dn_string) {
+ talloc_destroy(ctx);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ ret = ads_gen_mod(ads, dn_string, mods);
+ TALLOC_FREE(dn_string);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_clear_service_principal_names: Error: Updating Service Principals for machine %s in LDAP\n",
+ machine_name));
+ ads_msgfree(ads, res);
+ talloc_destroy(ctx);
+ return ret;
+ }
+
+ ads_msgfree(ads, res);
+ talloc_destroy(ctx);
+ return ret;
+}
+
+/**
+ * @brief Search for an element in a string array.
+ *
+ * @param[in] el_array The string array to search.
+ *
+ * @param[in] num_el The number of elements in the string array.
+ *
+ * @param[in] el The string to search.
+ *
+ * @return True if found, false if not.
+ */
+bool ads_element_in_array(const char **el_array, size_t num_el, const char *el)
+{
+ size_t i;
+
+ if (el_array == NULL || num_el == 0 || el == NULL) {
+ return false;
+ }
+
+ for (i = 0; i < num_el && el_array[i] != NULL; i++) {
+ int cmp;
+
+ cmp = strcasecmp_m(el_array[i], el);
+ if (cmp == 0) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * @brief This gets the service principal names of an existing computer account.
+ *
+ * @param[in] mem_ctx The memory context to use to allocate the spn array.
+ *
+ * @param[in] ads The ADS context to use.
+ *
+ * @param[in] machine_name The NetBIOS name of the computer, which is used to
+ * identify the computer account.
+ *
+ * @param[in] spn_array A pointer to store the array for SPNs.
+ *
+ * @param[in] num_spns The number of principals stored in the array.
+ *
+ * @return 0 on success, or a ADS error if a failure occurred.
+ */
+ADS_STATUS ads_get_service_principal_names(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char *machine_name,
+ char ***spn_array,
+ size_t *num_spns)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int count;
+
+ status = ads_find_machine_acct(ads,
+ &res,
+ machine_name);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("Host Account for %s not found... skipping operation.\n",
+ machine_name));
+ return status;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count != 1) {
+ status = ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ goto done;
+ }
+
+ *spn_array = ads_pull_strings(ads,
+ mem_ctx,
+ res,
+ "servicePrincipalName",
+ num_spns);
+ if (*spn_array == NULL) {
+ DEBUG(1, ("Host account for %s does not have service principal "
+ "names.\n",
+ machine_name));
+ status = ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ goto done;
+ }
+
+done:
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/**
+ * This adds a service principal name to an existing computer account
+ * (found by hostname) in AD.
+ * @param ads An initialized ADS_STRUCT
+ * @param machine_name the NetBIOS name of the computer, which is used to identify the computer account.
+ * @param spns An array or strings for the service principals to add,
+ * i.e. 'cifs/machine_name', 'http/machine.full.domain.com' etc.
+ * @return 0 upon success, or non-zero if a failure occurs
+ **/
+
+ADS_STATUS ads_add_service_principal_names(ADS_STRUCT *ads,
+ const char *machine_name,
+ const char **spns)
+{
+ ADS_STATUS ret;
+ TALLOC_CTX *ctx;
+ LDAPMessage *res = NULL;
+ ADS_MODLIST mods;
+ char *dn_string = NULL;
+ const char **servicePrincipalName = spns;
+
+ ret = ads_find_machine_acct(ads, &res, machine_name);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_add_service_principal_name: WARNING: Host Account for %s not found... skipping operation.\n",
+ machine_name));
+ DEBUG(1,("ads_add_service_principal_name: WARNING: Service Principals have NOT been added.\n"));
+ ads_msgfree(ads, res);
+ return ret;
+ }
+
+ DEBUG(1,("ads_add_service_principal_name: Host account for %s found\n", machine_name));
+ if (!(ctx = talloc_init("ads_add_service_principal_name"))) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ DEBUG(5,("ads_add_service_principal_name: INFO: "
+ "Adding %s to host %s\n",
+ spns[0] ? "N/A" : spns[0], machine_name));
+
+
+ DEBUG(5,("ads_add_service_principal_name: INFO: "
+ "Adding %s to host %s\n",
+ spns[1] ? "N/A" : spns[1], machine_name));
+
+ if ( (mods = ads_init_mods(ctx)) == NULL ) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ ret = ads_add_strlist(ctx,
+ &mods,
+ "servicePrincipalName",
+ servicePrincipalName);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_add_service_principal_name: Error: Updating Service Principals in LDAP\n"));
+ goto out;
+ }
+
+ if ( (dn_string = ads_get_dn(ads, ctx, res)) == NULL ) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ ret = ads_gen_mod(ads, dn_string, mods);
+ if (!ADS_ERR_OK(ret)) {
+ DEBUG(1,("ads_add_service_principal_name: Error: Updating Service Principals in LDAP\n"));
+ goto out;
+ }
+
+ out:
+ TALLOC_FREE( ctx );
+ ads_msgfree(ads, res);
+ return ret;
+}
+
+static uint32_t ads_get_acct_ctrl(ADS_STRUCT *ads,
+ LDAPMessage *msg)
+{
+ uint32_t acct_ctrl = 0;
+ bool ok;
+
+ ok = ads_pull_uint32(ads, msg, "userAccountControl", &acct_ctrl);
+ if (!ok) {
+ return 0;
+ }
+
+ return acct_ctrl;
+}
+
+static ADS_STATUS ads_change_machine_acct(ADS_STRUCT *ads,
+ LDAPMessage *msg,
+ const struct berval *machine_pw_val)
+{
+ ADS_MODLIST mods;
+ ADS_STATUS ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint32_t acct_control;
+ char *control_str = NULL;
+ const char *attrs[] = {
+ "objectSid",
+ NULL
+ };
+ LDAPMessage *res = NULL;
+ char *dn = NULL;
+
+ dn = ads_get_dn(ads, frame, msg);
+ if (dn == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ acct_control = ads_get_acct_ctrl(ads, msg);
+ if (acct_control == 0) {
+ ret = ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+ goto done;
+ }
+
+ /*
+ * Changing the password, disables the account. So we need to change the
+ * userAccountControl flags to enable it again.
+ */
+ mods = ads_init_mods(frame);
+ if (mods == NULL) {
+ ret = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ ads_mod_ber(frame, &mods, "unicodePwd", machine_pw_val);
+
+ ret = ads_gen_mod(ads, dn, mods);
+ if (!ADS_ERR_OK(ret)) {
+ goto done;
+ }
+ TALLOC_FREE(mods);
+
+ /*
+ * To activate the account, we need to disable and enable it.
+ */
+ acct_control |= UF_ACCOUNTDISABLE;
+
+ control_str = talloc_asprintf(frame, "%u", acct_control);
+ if (control_str == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ mods = ads_init_mods(frame);
+ if (mods == NULL) {
+ ret = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ ads_mod_str(frame, &mods, "userAccountControl", control_str);
+
+ ret = ads_gen_mod(ads, dn, mods);
+ if (!ADS_ERR_OK(ret)) {
+ goto done;
+ }
+ TALLOC_FREE(mods);
+ TALLOC_FREE(control_str);
+
+ /*
+ * Enable the account again.
+ */
+ acct_control &= ~UF_ACCOUNTDISABLE;
+
+ control_str = talloc_asprintf(frame, "%u", acct_control);
+ if (control_str == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ mods = ads_init_mods(frame);
+ if (mods == NULL) {
+ ret = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ ads_mod_str(frame, &mods, "userAccountControl", control_str);
+
+ ret = ads_gen_mod(ads, dn, mods);
+ if (!ADS_ERR_OK(ret)) {
+ goto done;
+ }
+ TALLOC_FREE(mods);
+ TALLOC_FREE(control_str);
+
+ ret = ads_search_dn(ads, &res, dn, attrs);
+ ads_msgfree(ads, res);
+
+done:
+ talloc_free(frame);
+
+ return ret;
+}
+
+/**
+ * adds a machine account to the ADS server
+ * @param ads An initialized ADS_STRUCT
+ * @param machine_name - the NetBIOS machine name of this account.
+ * @param account_type A number indicating the type of account to create
+ * @param org_unit The LDAP path in which to place this account
+ * @return 0 upon success, or non-zero otherwise
+**/
+
+ADS_STATUS ads_create_machine_acct(ADS_STRUCT *ads,
+ const char *machine_name,
+ const char *machine_password,
+ const char *org_unit,
+ uint32_t etype_list,
+ const char *dns_domain_name)
+{
+ ADS_STATUS ret;
+ char *samAccountName = NULL;
+ char *controlstr = NULL;
+ TALLOC_CTX *ctx = NULL;
+ ADS_MODLIST mods;
+ char *machine_escaped = NULL;
+ char *dns_hostname = NULL;
+ char *new_dn = NULL;
+ char *utf8_pw = NULL;
+ size_t utf8_pw_len = 0;
+ char *utf16_pw = NULL;
+ size_t utf16_pw_len = 0;
+ struct berval machine_pw_val;
+ bool ok;
+ const char **spn_array = NULL;
+ size_t num_spns = 0;
+ const char *spn_prefix[] = {
+ "HOST",
+ "RestrictedKrbHost",
+ };
+ size_t i;
+ LDAPMessage *res = NULL;
+ uint32_t acct_control = UF_WORKSTATION_TRUST_ACCOUNT;
+
+ ctx = talloc_init("ads_add_machine_acct");
+ if (ctx == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ machine_escaped = escape_rdn_val_string_alloc(machine_name);
+ if (machine_escaped == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ utf8_pw = talloc_asprintf(ctx, "\"%s\"", machine_password);
+ if (utf8_pw == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+ utf8_pw_len = strlen(utf8_pw);
+
+ ok = convert_string_talloc(ctx,
+ CH_UTF8, CH_UTF16MUNGED,
+ utf8_pw, utf8_pw_len,
+ (void *)&utf16_pw, &utf16_pw_len);
+ if (!ok) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ machine_pw_val = (struct berval) {
+ .bv_val = utf16_pw,
+ .bv_len = utf16_pw_len,
+ };
+
+ /* Check if the machine account already exists. */
+ ret = ads_find_machine_acct(ads, &res, machine_escaped);
+ if (ADS_ERR_OK(ret)) {
+ /* Change the machine account password */
+ ret = ads_change_machine_acct(ads, res, &machine_pw_val);
+ ads_msgfree(ads, res);
+
+ goto done;
+ }
+ ads_msgfree(ads, res);
+
+ new_dn = talloc_asprintf(ctx, "cn=%s,%s", machine_escaped, org_unit);
+ if (new_dn == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ /* Create machine account */
+
+ samAccountName = talloc_asprintf(ctx, "%s$", machine_name);
+ if (samAccountName == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ dns_hostname = talloc_asprintf(ctx,
+ "%s.%s",
+ machine_name,
+ dns_domain_name);
+ if (dns_hostname == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ /* Add dns_hostname SPNs */
+ for (i = 0; i < ARRAY_SIZE(spn_prefix); i++) {
+ char *spn = talloc_asprintf(ctx,
+ "%s/%s",
+ spn_prefix[i],
+ dns_hostname);
+ if (spn == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ ok = add_string_to_array(ctx,
+ spn,
+ &spn_array,
+ &num_spns);
+ if (!ok) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+ }
+
+ /* Add machine_name SPNs */
+ for (i = 0; i < ARRAY_SIZE(spn_prefix); i++) {
+ char *spn = talloc_asprintf(ctx,
+ "%s/%s",
+ spn_prefix[i],
+ machine_name);
+ if (spn == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ ok = add_string_to_array(ctx,
+ spn,
+ &spn_array,
+ &num_spns);
+ if (!ok) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+ }
+
+ /* Make sure to NULL terminate the array */
+ spn_array = talloc_realloc(ctx, spn_array, const char *, num_spns + 1);
+ if (spn_array == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+ spn_array[num_spns] = NULL;
+
+ controlstr = talloc_asprintf(ctx, "%u", acct_control);
+ if (controlstr == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ mods = ads_init_mods(ctx);
+ if (mods == NULL) {
+ ret = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ ads_mod_str(ctx, &mods, "objectClass", "Computer");
+ ads_mod_str(ctx, &mods, "SamAccountName", samAccountName);
+ ads_mod_str(ctx, &mods, "userAccountControl", controlstr);
+ ads_mod_str(ctx, &mods, "DnsHostName", dns_hostname);
+ ads_mod_strlist(ctx, &mods, "ServicePrincipalName", spn_array);
+ ads_mod_ber(ctx, &mods, "unicodePwd", &machine_pw_val);
+
+ ret = ads_gen_add(ads, new_dn, mods);
+
+done:
+ SAFE_FREE(machine_escaped);
+ talloc_destroy(ctx);
+
+ return ret;
+}
+
+/**
+ * move a machine account to another OU on the ADS server
+ * @param ads - An initialized ADS_STRUCT
+ * @param machine_name - the NetBIOS machine name of this account.
+ * @param org_unit - The LDAP path in which to place this account
+ * @param moved - whether we moved the machine account (optional)
+ * @return 0 upon success, or non-zero otherwise
+**/
+
+ADS_STATUS ads_move_machine_acct(ADS_STRUCT *ads, const char *machine_name,
+ const char *org_unit, bool *moved)
+{
+ ADS_STATUS rc;
+ int ldap_status;
+ LDAPMessage *res = NULL;
+ char *filter = NULL;
+ char *computer_dn = NULL;
+ char *parent_dn;
+ char *computer_rdn = NULL;
+ bool need_move = False;
+
+ if (asprintf(&filter, "(samAccountName=%s$)", machine_name) == -1) {
+ rc = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ /* Find pre-existing machine */
+ rc = ads_search(ads, &res, filter, NULL);
+ if (!ADS_ERR_OK(rc)) {
+ goto done;
+ }
+
+ computer_dn = ads_get_dn(ads, talloc_tos(), res);
+ if (!computer_dn) {
+ rc = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ parent_dn = ads_parent_dn(computer_dn);
+ if (strequal(parent_dn, org_unit)) {
+ goto done;
+ }
+
+ need_move = True;
+
+ if (asprintf(&computer_rdn, "CN=%s", machine_name) == -1) {
+ rc = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ ldap_status = ldap_rename_s(ads->ldap.ld, computer_dn, computer_rdn,
+ org_unit, 1, NULL, NULL);
+ rc = ADS_ERROR(ldap_status);
+
+done:
+ ads_msgfree(ads, res);
+ SAFE_FREE(filter);
+ TALLOC_FREE(computer_dn);
+ SAFE_FREE(computer_rdn);
+
+ if (!ADS_ERR_OK(rc)) {
+ need_move = False;
+ }
+
+ if (moved) {
+ *moved = need_move;
+ }
+
+ return rc;
+}
+
+/*
+ dump a binary result from ldap
+*/
+static void dump_binary(ADS_STRUCT *ads, const char *field, struct berval **values)
+{
+ size_t i;
+ for (i=0; values[i]; i++) {
+ ber_len_t j;
+ printf("%s: ", field);
+ for (j=0; j<values[i]->bv_len; j++) {
+ printf("%02X", (unsigned char)values[i]->bv_val[j]);
+ }
+ printf("\n");
+ }
+}
+
+static void dump_guid(ADS_STRUCT *ads, const char *field, struct berval **values)
+{
+ int i;
+ for (i=0; values[i]; i++) {
+ NTSTATUS status;
+ DATA_BLOB in = data_blob_const(values[i]->bv_val, values[i]->bv_len);
+ struct GUID guid;
+
+ status = GUID_from_ndr_blob(&in, &guid);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("%s: %s\n", field, GUID_string(talloc_tos(), &guid));
+ } else {
+ printf("%s: INVALID GUID\n", field);
+ }
+ }
+}
+
+/*
+ dump a sid result from ldap
+*/
+static void dump_sid(ADS_STRUCT *ads, const char *field, struct berval **values)
+{
+ int i;
+ for (i=0; values[i]; i++) {
+ ssize_t ret;
+ struct dom_sid sid;
+ struct dom_sid_buf tmp;
+ ret = sid_parse((const uint8_t *)values[i]->bv_val,
+ values[i]->bv_len, &sid);
+ if (ret == -1) {
+ return;
+ }
+ printf("%s: %s\n", field, dom_sid_str_buf(&sid, &tmp));
+ }
+}
+
+/*
+ dump ntSecurityDescriptor
+*/
+static void dump_sd(ADS_STRUCT *ads, const char *filed, struct berval **values)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct security_descriptor *psd;
+ NTSTATUS status;
+
+ status = unmarshall_sec_desc(talloc_tos(), (uint8_t *)values[0]->bv_val,
+ values[0]->bv_len, &psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("unmarshall_sec_desc failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ if (psd) {
+ ads_disp_sd(ads, talloc_tos(), psd);
+ }
+
+ TALLOC_FREE(frame);
+}
+
+/*
+ dump a string result from ldap
+*/
+static void dump_string(const char *field, char **values)
+{
+ int i;
+ for (i=0; values[i]; i++) {
+ printf("%s: %s\n", field, values[i]);
+ }
+}
+
+/*
+ dump a field from LDAP on stdout
+ used for debugging
+*/
+
+static bool ads_dump_field(ADS_STRUCT *ads, char *field, void **values, void *data_area)
+{
+ const struct {
+ const char *name;
+ bool string;
+ void (*handler)(ADS_STRUCT *, const char *, struct berval **);
+ } handlers[] = {
+ {"objectGUID", False, dump_guid},
+ {"netbootGUID", False, dump_guid},
+ {"nTSecurityDescriptor", False, dump_sd},
+ {"dnsRecord", False, dump_binary},
+ {"objectSid", False, dump_sid},
+ {"tokenGroups", False, dump_sid},
+ {"tokenGroupsNoGCAcceptable", False, dump_sid},
+ {"tokengroupsGlobalandUniversal", False, dump_sid},
+ {"mS-DS-CreatorSID", False, dump_sid},
+ {"msExchMailboxGuid", False, dump_guid},
+ {NULL, True, NULL}
+ };
+ int i;
+
+ if (!field) { /* must be end of an entry */
+ printf("\n");
+ return False;
+ }
+
+ for (i=0; handlers[i].name; i++) {
+ if (strcasecmp_m(handlers[i].name, field) == 0) {
+ if (!values) /* first time, indicate string or not */
+ return handlers[i].string;
+ handlers[i].handler(ads, field, (struct berval **) values);
+ break;
+ }
+ }
+ if (!handlers[i].name) {
+ if (!values) /* first time, indicate string conversion */
+ return True;
+ dump_string(field, (char **)values);
+ }
+ return False;
+}
+
+/**
+ * Dump a result from LDAP on stdout
+ * used for debugging
+ * @param ads connection to ads server
+ * @param res Results to dump
+ **/
+
+ void ads_dump(ADS_STRUCT *ads, LDAPMessage *res)
+{
+ ads_process_results(ads, res, ads_dump_field, NULL);
+}
+
+/**
+ * Walk through results, calling a function for each entry found.
+ * The function receives a field name, a berval * array of values,
+ * and a data area passed through from the start. The function is
+ * called once with null for field and values at the end of each
+ * entry.
+ * @param ads connection to ads server
+ * @param res Results to process
+ * @param fn Function for processing each result
+ * @param data_area user-defined area to pass to function
+ **/
+ void ads_process_results(ADS_STRUCT *ads, LDAPMessage *res,
+ bool (*fn)(ADS_STRUCT *, char *, void **, void *),
+ void *data_area)
+{
+ LDAPMessage *msg;
+ TALLOC_CTX *ctx;
+ size_t converted_size;
+
+ if (!(ctx = talloc_init("ads_process_results")))
+ return;
+
+ for (msg = ads_first_entry(ads, res); msg;
+ msg = ads_next_entry(ads, msg)) {
+ char *utf8_field;
+ BerElement *b;
+
+ for (utf8_field=ldap_first_attribute(ads->ldap.ld,
+ (LDAPMessage *)msg,&b);
+ utf8_field;
+ utf8_field=ldap_next_attribute(ads->ldap.ld,
+ (LDAPMessage *)msg,b)) {
+ struct berval **ber_vals;
+ char **str_vals;
+ char **utf8_vals;
+ char *field;
+ bool string;
+
+ if (!pull_utf8_talloc(ctx, &field, utf8_field,
+ &converted_size))
+ {
+ DEBUG(0,("ads_process_results: "
+ "pull_utf8_talloc failed: %s\n",
+ strerror(errno)));
+ }
+
+ string = fn(ads, field, NULL, data_area);
+
+ if (string) {
+ const char **p;
+
+ utf8_vals = ldap_get_values(ads->ldap.ld,
+ (LDAPMessage *)msg, field);
+ p = discard_const_p(const char *, utf8_vals);
+ str_vals = ads_pull_strvals(ctx, p);
+ fn(ads, field, (void **) str_vals, data_area);
+ ldap_value_free(utf8_vals);
+ } else {
+ ber_vals = ldap_get_values_len(ads->ldap.ld,
+ (LDAPMessage *)msg, field);
+ fn(ads, field, (void **) ber_vals, data_area);
+
+ ldap_value_free_len(ber_vals);
+ }
+ ldap_memfree(utf8_field);
+ }
+ ber_free(b, 0);
+ talloc_free_children(ctx);
+ fn(ads, NULL, NULL, data_area); /* completed an entry */
+
+ }
+ talloc_destroy(ctx);
+}
+
+/**
+ * count how many replies are in a LDAPMessage
+ * @param ads connection to ads server
+ * @param res Results to count
+ * @return number of replies
+ **/
+int ads_count_replies(ADS_STRUCT *ads, void *res)
+{
+ return ldap_count_entries(ads->ldap.ld, (LDAPMessage *)res);
+}
+
+/**
+ * pull the first entry from a ADS result
+ * @param ads connection to ads server
+ * @param res Results of search
+ * @return first entry from result
+ **/
+ LDAPMessage *ads_first_entry(ADS_STRUCT *ads, LDAPMessage *res)
+{
+ return ldap_first_entry(ads->ldap.ld, res);
+}
+
+/**
+ * pull the next entry from a ADS result
+ * @param ads connection to ads server
+ * @param res Results of search
+ * @return next entry from result
+ **/
+ LDAPMessage *ads_next_entry(ADS_STRUCT *ads, LDAPMessage *res)
+{
+ return ldap_next_entry(ads->ldap.ld, res);
+}
+
+/**
+ * pull the first message from a ADS result
+ * @param ads connection to ads server
+ * @param res Results of search
+ * @return first message from result
+ **/
+ LDAPMessage *ads_first_message(ADS_STRUCT *ads, LDAPMessage *res)
+{
+ return ldap_first_message(ads->ldap.ld, res);
+}
+
+/**
+ * pull the next message from a ADS result
+ * @param ads connection to ads server
+ * @param res Results of search
+ * @return next message from result
+ **/
+ LDAPMessage *ads_next_message(ADS_STRUCT *ads, LDAPMessage *res)
+{
+ return ldap_next_message(ads->ldap.ld, res);
+}
+
+/**
+ * pull a single string from a ADS result
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX to use for allocating result string
+ * @param msg Results of search
+ * @param field Attribute to retrieve
+ * @return Result string in talloc context
+ **/
+ char *ads_pull_string(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, LDAPMessage *msg,
+ const char *field)
+{
+ char **values;
+ char *ret = NULL;
+ char *ux_string;
+ size_t converted_size;
+
+ values = ldap_get_values(ads->ldap.ld, msg, field);
+ if (!values)
+ return NULL;
+
+ if (values[0] && pull_utf8_talloc(mem_ctx, &ux_string, values[0],
+ &converted_size))
+ {
+ ret = ux_string;
+ }
+ ldap_value_free(values);
+ return ret;
+}
+
+/**
+ * pull an array of strings from a ADS result
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX to use for allocating result string
+ * @param msg Results of search
+ * @param field Attribute to retrieve
+ * @return Result strings in talloc context
+ **/
+ char **ads_pull_strings(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field,
+ size_t *num_values)
+{
+ char **values;
+ char **ret = NULL;
+ size_t i, converted_size;
+
+ values = ldap_get_values(ads->ldap.ld, msg, field);
+ if (!values)
+ return NULL;
+
+ *num_values = ldap_count_values(values);
+
+ ret = talloc_array(mem_ctx, char *, *num_values + 1);
+ if (!ret) {
+ ldap_value_free(values);
+ return NULL;
+ }
+
+ for (i=0;i<*num_values;i++) {
+ if (!pull_utf8_talloc(mem_ctx, &ret[i], values[i],
+ &converted_size))
+ {
+ ldap_value_free(values);
+ return NULL;
+ }
+ }
+ ret[i] = NULL;
+
+ ldap_value_free(values);
+ return ret;
+}
+
+/**
+ * pull an array of strings from a ADS result
+ * (handle large multivalue attributes with range retrieval)
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX to use for allocating result string
+ * @param msg Results of search
+ * @param field Attribute to retrieve
+ * @param current_strings strings returned by a previous call to this function
+ * @param next_attribute The next query should ask for this attribute
+ * @param num_values How many values did we get this time?
+ * @param more_values Are there more values to get?
+ * @return Result strings in talloc context
+ **/
+ char **ads_pull_strings_range(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field,
+ char **current_strings,
+ const char **next_attribute,
+ size_t *num_strings,
+ bool *more_strings)
+{
+ char *attr;
+ char *expected_range_attrib, *range_attr = NULL;
+ BerElement *ptr = NULL;
+ char **strings;
+ char **new_strings;
+ size_t num_new_strings;
+ unsigned long int range_start;
+ unsigned long int range_end;
+
+ /* we might have been given the whole lot anyway */
+ if ((strings = ads_pull_strings(ads, mem_ctx, msg, field, num_strings))) {
+ *more_strings = False;
+ return strings;
+ }
+
+ expected_range_attrib = talloc_asprintf(mem_ctx, "%s;Range=", field);
+
+ /* look for Range result */
+ for (attr = ldap_first_attribute(ads->ldap.ld, (LDAPMessage *)msg, &ptr);
+ attr;
+ attr = ldap_next_attribute(ads->ldap.ld, (LDAPMessage *)msg, ptr)) {
+ /* we ignore the fact that this is utf8, as all attributes are ascii... */
+ if (strnequal(attr, expected_range_attrib, strlen(expected_range_attrib))) {
+ range_attr = attr;
+ break;
+ }
+ ldap_memfree(attr);
+ }
+ if (!range_attr) {
+ ber_free(ptr, 0);
+ /* nothing here - this field is just empty */
+ *more_strings = False;
+ return NULL;
+ }
+
+ if (sscanf(&range_attr[strlen(expected_range_attrib)], "%lu-%lu",
+ &range_start, &range_end) == 2) {
+ *more_strings = True;
+ } else {
+ if (sscanf(&range_attr[strlen(expected_range_attrib)], "%lu-*",
+ &range_start) == 1) {
+ *more_strings = False;
+ } else {
+ DEBUG(1, ("ads_pull_strings_range: Cannot parse Range attribute (%s)\n",
+ range_attr));
+ ldap_memfree(range_attr);
+ *more_strings = False;
+ return NULL;
+ }
+ }
+
+ if ((*num_strings) != range_start) {
+ DEBUG(1, ("ads_pull_strings_range: Range attribute (%s) doesn't start at %u, but at %lu"
+ " - aborting range retrieval\n",
+ range_attr, (unsigned int)(*num_strings) + 1, range_start));
+ ldap_memfree(range_attr);
+ *more_strings = False;
+ return NULL;
+ }
+
+ new_strings = ads_pull_strings(ads, mem_ctx, msg, range_attr, &num_new_strings);
+
+ if (*more_strings && ((*num_strings + num_new_strings) != (range_end + 1))) {
+ DEBUG(1, ("ads_pull_strings_range: Range attribute (%s) tells us we have %lu "
+ "strings in this bunch, but we only got %lu - aborting range retrieval\n",
+ range_attr, (unsigned long int)range_end - range_start + 1,
+ (unsigned long int)num_new_strings));
+ ldap_memfree(range_attr);
+ *more_strings = False;
+ return NULL;
+ }
+
+ strings = talloc_realloc(mem_ctx, current_strings, char *,
+ *num_strings + num_new_strings);
+
+ if (strings == NULL) {
+ ldap_memfree(range_attr);
+ *more_strings = False;
+ return NULL;
+ }
+
+ if (new_strings && num_new_strings) {
+ memcpy(&strings[*num_strings], new_strings,
+ sizeof(*new_strings) * num_new_strings);
+ }
+
+ (*num_strings) += num_new_strings;
+
+ if (*more_strings) {
+ *next_attribute = talloc_asprintf(mem_ctx,
+ "%s;range=%d-*",
+ field,
+ (int)*num_strings);
+
+ if (!*next_attribute) {
+ DEBUG(1, ("talloc_asprintf for next attribute failed!\n"));
+ ldap_memfree(range_attr);
+ *more_strings = False;
+ return NULL;
+ }
+ }
+
+ ldap_memfree(range_attr);
+
+ return strings;
+}
+
+/**
+ * pull a single uint32_t from a ADS result
+ * @param ads connection to ads server
+ * @param msg Results of search
+ * @param field Attribute to retrieve
+ * @param v Pointer to int to store result
+ * @return boolean indicating success
+*/
+ bool ads_pull_uint32(ADS_STRUCT *ads, LDAPMessage *msg, const char *field,
+ uint32_t *v)
+{
+ char **values;
+
+ values = ldap_get_values(ads->ldap.ld, msg, field);
+ if (!values)
+ return False;
+ if (!values[0]) {
+ ldap_value_free(values);
+ return False;
+ }
+
+ *v = atoi(values[0]);
+ ldap_value_free(values);
+ return True;
+}
+
+/**
+ * pull a single objectGUID from an ADS result
+ * @param ads connection to ADS server
+ * @param msg results of search
+ * @param guid 37-byte area to receive text guid
+ * @return boolean indicating success
+ **/
+ bool ads_pull_guid(ADS_STRUCT *ads, LDAPMessage *msg, struct GUID *guid)
+{
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ if (!smbldap_talloc_single_blob(talloc_tos(), ads->ldap.ld, msg, "objectGUID",
+ &blob)) {
+ return false;
+ }
+
+ status = GUID_from_ndr_blob(&blob, guid);
+ talloc_free(blob.data);
+ return NT_STATUS_IS_OK(status);
+}
+
+
+/**
+ * pull a single struct dom_sid from a ADS result
+ * @param ads connection to ads server
+ * @param msg Results of search
+ * @param field Attribute to retrieve
+ * @param sid Pointer to sid to store result
+ * @return boolean indicating success
+*/
+ bool ads_pull_sid(ADS_STRUCT *ads, LDAPMessage *msg, const char *field,
+ struct dom_sid *sid)
+{
+ return smbldap_pull_sid(ads->ldap.ld, msg, field, sid);
+}
+
+/**
+ * pull an array of struct dom_sids from a ADS result
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX for allocating sid array
+ * @param msg Results of search
+ * @param field Attribute to retrieve
+ * @param sids pointer to sid array to allocate
+ * @return the count of SIDs pulled
+ **/
+ int ads_pull_sids(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field, struct dom_sid **sids)
+{
+ struct berval **values;
+ int count, i;
+
+ values = ldap_get_values_len(ads->ldap.ld, msg, field);
+
+ if (!values)
+ return 0;
+
+ for (i=0; values[i]; i++)
+ /* nop */ ;
+
+ if (i) {
+ (*sids) = talloc_array(mem_ctx, struct dom_sid, i);
+ if (!(*sids)) {
+ ldap_value_free_len(values);
+ return 0;
+ }
+ } else {
+ (*sids) = NULL;
+ }
+
+ count = 0;
+ for (i=0; values[i]; i++) {
+ ssize_t ret;
+ ret = sid_parse((const uint8_t *)values[i]->bv_val,
+ values[i]->bv_len, &(*sids)[count]);
+ if (ret != -1) {
+ struct dom_sid_buf buf;
+ DBG_DEBUG("pulling SID: %s\n",
+ dom_sid_str_buf(&(*sids)[count], &buf));
+ count++;
+ }
+ }
+
+ ldap_value_free_len(values);
+ return count;
+}
+
+/**
+ * pull a struct security_descriptor from a ADS result
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX for allocating sid array
+ * @param msg Results of search
+ * @param field Attribute to retrieve
+ * @param sd Pointer to *struct security_descriptor to store result (talloc()ed)
+ * @return boolean indicating success
+*/
+ bool ads_pull_sd(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, const char *field,
+ struct security_descriptor **sd)
+{
+ struct berval **values;
+ bool ret = true;
+
+ values = ldap_get_values_len(ads->ldap.ld, msg, field);
+
+ if (!values) return false;
+
+ if (values[0]) {
+ NTSTATUS status;
+ status = unmarshall_sec_desc(mem_ctx,
+ (uint8_t *)values[0]->bv_val,
+ values[0]->bv_len, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("unmarshall_sec_desc failed: %s\n",
+ nt_errstr(status)));
+ ret = false;
+ }
+ }
+
+ ldap_value_free_len(values);
+ return ret;
+}
+
+/*
+ * in order to support usernames longer than 21 characters we need to
+ * use both the sAMAccountName and the userPrincipalName attributes
+ * It seems that not all users have the userPrincipalName attribute set
+ *
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX for allocating sid array
+ * @param msg Results of search
+ * @return the username
+ */
+ char *ads_pull_username(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg)
+{
+#if 0 /* JERRY */
+ char *ret, *p;
+
+ /* lookup_name() only works on the sAMAccountName to
+ returning the username portion of userPrincipalName
+ breaks winbindd_getpwnam() */
+
+ ret = ads_pull_string(ads, mem_ctx, msg, "userPrincipalName");
+ if (ret && (p = strchr_m(ret, '@'))) {
+ *p = 0;
+ return ret;
+ }
+#endif
+ return ads_pull_string(ads, mem_ctx, msg, "sAMAccountName");
+}
+
+
+/**
+ * find the update serial number - this is the core of the ldap cache
+ * @param ads connection to ads server
+ * @param ads connection to ADS server
+ * @param usn Pointer to retrieved update serial number
+ * @return status of search
+ **/
+ADS_STATUS ads_USN(ADS_STRUCT *ads, uint32_t *usn)
+{
+ const char *attrs[] = {"highestCommittedUSN", NULL};
+ ADS_STATUS status;
+ LDAPMessage *res;
+
+ status = ads_do_search_retry(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
+ if (!ADS_ERR_OK(status))
+ return status;
+
+ if (ads_count_replies(ads, res) != 1) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+ }
+
+ if (!ads_pull_uint32(ads, res, "highestCommittedUSN", usn)) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_SUCH_ATTRIBUTE);
+ }
+
+ ads_msgfree(ads, res);
+ return ADS_SUCCESS;
+}
+
+/* parse a ADS timestring - typical string is
+ '20020917091222.0Z0' which means 09:12.22 17th September
+ 2002, timezone 0 */
+static time_t ads_parse_time(const char *str)
+{
+ struct tm tm;
+
+ ZERO_STRUCT(tm);
+
+ if (sscanf(str, "%4d%2d%2d%2d%2d%2d",
+ &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
+ &tm.tm_hour, &tm.tm_min, &tm.tm_sec) != 6) {
+ return 0;
+ }
+ tm.tm_year -= 1900;
+ tm.tm_mon -= 1;
+
+ return timegm(&tm);
+}
+
+/********************************************************************
+********************************************************************/
+
+ADS_STATUS ads_current_time(ADS_STRUCT *ads)
+{
+ const char *attrs[] = {"currentTime", NULL};
+ ADS_STATUS status;
+ LDAPMessage *res;
+ char *timestr;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads_s = ads;
+
+ /* establish a new ldap tcp session if necessary */
+
+ if ( !ads->ldap.ld ) {
+ /*
+ * ADS_STRUCT may be being reused after a
+ * DC lookup, so ads->ldap.ss may already have a
+ * good address. If not, re-initialize the passed-in
+ * ADS_STRUCT with the given server.XXXX parameters.
+ *
+ * Note that this doesn't depend on
+ * ads->server.ldap_server != NULL,
+ * as the case where ads->server.ldap_server==NULL and
+ * ads->ldap.ss != zero_address is precisely the DC
+ * lookup case where ads->ldap.ss was found by going
+ * through ads_find_dc() again we want to avoid repeating.
+ */
+ if (is_zero_addr(&ads->ldap.ss)) {
+ ads_s = ads_init(tmp_ctx,
+ ads->server.realm,
+ ads->server.workgroup,
+ ads->server.ldap_server,
+ ADS_SASL_PLAIN );
+ if (ads_s == NULL) {
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ goto done;
+ }
+ }
+
+ /*
+ * Reset ads->config.flags as it can contain the flags
+ * returned by the previous CLDAP ping when reusing the struct.
+ */
+ ads_s->config.flags = 0;
+
+ ads_s->auth.flags = ADS_AUTH_ANON_BIND;
+ status = ads_connect( ads_s );
+ if ( !ADS_ERR_OK(status))
+ goto done;
+ }
+
+ status = ads_do_search(ads_s, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ timestr = ads_pull_string(ads_s, tmp_ctx, res, "currentTime");
+ if (!timestr) {
+ ads_msgfree(ads_s, res);
+ status = ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+ goto done;
+ }
+
+ /* but save the time and offset in the original ADS_STRUCT */
+
+ ads->config.current_time = ads_parse_time(timestr);
+
+ if (ads->config.current_time != 0) {
+ ads->auth.time_offset = ads->config.current_time - time(NULL);
+ DEBUG(4,("KDC time offset is %d seconds\n", ads->auth.time_offset));
+ }
+
+ ads_msgfree(ads, res);
+
+ status = ADS_SUCCESS;
+
+done:
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+ADS_STATUS ads_domain_func_level(ADS_STRUCT *ads, uint32_t *val)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char *attrs[] = {"domainFunctionality", NULL};
+ ADS_STATUS status;
+ LDAPMessage *res;
+ ADS_STRUCT *ads_s = ads;
+
+ *val = DS_DOMAIN_FUNCTION_2000;
+
+ /* establish a new ldap tcp session if necessary */
+
+ if ( !ads->ldap.ld ) {
+ /*
+ * ADS_STRUCT may be being reused after a
+ * DC lookup, so ads->ldap.ss may already have a
+ * good address. If not, re-initialize the passed-in
+ * ADS_STRUCT with the given server.XXXX parameters.
+ *
+ * Note that this doesn't depend on
+ * ads->server.ldap_server != NULL,
+ * as the case where ads->server.ldap_server==NULL and
+ * ads->ldap.ss != zero_address is precisely the DC
+ * lookup case where ads->ldap.ss was found by going
+ * through ads_find_dc() again we want to avoid repeating.
+ */
+ if (is_zero_addr(&ads->ldap.ss)) {
+ ads_s = ads_init(tmp_ctx,
+ ads->server.realm,
+ ads->server.workgroup,
+ ads->server.ldap_server,
+ ADS_SASL_PLAIN );
+ if (ads_s == NULL ) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto done;
+ }
+ }
+
+ /*
+ * Reset ads->config.flags as it can contain the flags
+ * returned by the previous CLDAP ping when reusing the struct.
+ */
+ ads_s->config.flags = 0;
+
+ ads_s->auth.flags = ADS_AUTH_ANON_BIND;
+ status = ads_connect( ads_s );
+ if ( !ADS_ERR_OK(status))
+ goto done;
+ }
+
+ /* If the attribute does not exist assume it is a Windows 2000
+ functional domain */
+
+ status = ads_do_search(ads_s, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
+ if (!ADS_ERR_OK(status)) {
+ if ( status.err.rc == LDAP_NO_SUCH_ATTRIBUTE ) {
+ status = ADS_SUCCESS;
+ }
+ goto done;
+ }
+
+ if ( !ads_pull_uint32(ads_s, res, "domainFunctionality", val) ) {
+ DEBUG(5,("ads_domain_func_level: Failed to pull the domainFunctionality attribute.\n"));
+ }
+ DEBUG(3,("ads_domain_func_level: %d\n", *val));
+
+
+ ads_msgfree(ads_s, res);
+
+done:
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+/**
+ * find the domain sid for our domain
+ * @param ads connection to ads server
+ * @param sid Pointer to domain sid
+ * @return status of search
+ **/
+ADS_STATUS ads_domain_sid(ADS_STRUCT *ads, struct dom_sid *sid)
+{
+ const char *attrs[] = {"objectSid", NULL};
+ LDAPMessage *res;
+ ADS_STATUS rc;
+
+ rc = ads_do_search_retry(ads, ads->config.bind_path, LDAP_SCOPE_BASE, "(objectclass=*)",
+ attrs, &res);
+ if (!ADS_ERR_OK(rc)) return rc;
+ if (!ads_pull_sid(ads, res, "objectSid", sid)) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR_SYSTEM(ENOENT);
+ }
+ ads_msgfree(ads, res);
+
+ return ADS_SUCCESS;
+}
+
+/**
+ * find our site name
+ * @param ads connection to ads server
+ * @param mem_ctx Pointer to talloc context
+ * @param site_name Pointer to the sitename
+ * @return status of search
+ **/
+ADS_STATUS ads_site_dn(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const char **site_name)
+{
+ ADS_STATUS status;
+ LDAPMessage *res;
+ const char *dn, *service_name;
+ const char *attrs[] = { "dsServiceName", NULL };
+
+ status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ service_name = ads_pull_string(ads, mem_ctx, res, "dsServiceName");
+ if (service_name == NULL) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+ }
+
+ ads_msgfree(ads, res);
+
+ /* go up three levels */
+ dn = ads_parent_dn(ads_parent_dn(ads_parent_dn(service_name)));
+ if (dn == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ *site_name = talloc_strdup(mem_ctx, dn);
+ if (*site_name == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ return status;
+ /*
+ dsServiceName: CN=NTDS Settings,CN=W2K3DC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=ber,DC=suse,DC=de
+ */
+}
+
+/**
+ * find the site dn where a machine resides
+ * @param ads connection to ads server
+ * @param mem_ctx Pointer to talloc context
+ * @param computer_name name of the machine
+ * @param site_name Pointer to the sitename
+ * @return status of search
+ **/
+ADS_STATUS ads_site_dn_for_machine(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, const char *computer_name, const char **site_dn)
+{
+ ADS_STATUS status;
+ LDAPMessage *res;
+ const char *parent, *filter;
+ char *config_context = NULL;
+ char *dn;
+
+ /* shortcut a query */
+ if (strequal(computer_name, ads->config.ldap_server_name)) {
+ return ads_site_dn(ads, mem_ctx, site_dn);
+ }
+
+ status = ads_config_path(ads, mem_ctx, &config_context);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ filter = talloc_asprintf(mem_ctx, "(cn=%s)", computer_name);
+ if (filter == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_do_search(ads, config_context, LDAP_SCOPE_SUBTREE,
+ filter, NULL, &res);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (ads_count_replies(ads, res) != 1) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ }
+
+ dn = ads_get_dn(ads, mem_ctx, res);
+ if (dn == NULL) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ /* go up three levels */
+ parent = ads_parent_dn(ads_parent_dn(ads_parent_dn(dn)));
+ if (parent == NULL) {
+ ads_msgfree(ads, res);
+ TALLOC_FREE(dn);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ *site_dn = talloc_strdup(mem_ctx, parent);
+ if (*site_dn == NULL) {
+ ads_msgfree(ads, res);
+ TALLOC_FREE(dn);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ TALLOC_FREE(dn);
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/**
+ * get the upn suffixes for a domain
+ * @param ads connection to ads server
+ * @param mem_ctx Pointer to talloc context
+ * @param suffixes Pointer to an array of suffixes
+ * @param num_suffixes Pointer to the number of suffixes
+ * @return status of search
+ **/
+ADS_STATUS ads_upn_suffixes(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, char ***suffixes, size_t *num_suffixes)
+{
+ ADS_STATUS status;
+ LDAPMessage *res;
+ const char *base;
+ char *config_context = NULL;
+ const char *attrs[] = { "uPNSuffixes", NULL };
+
+ status = ads_config_path(ads, mem_ctx, &config_context);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ base = talloc_asprintf(mem_ctx, "cn=Partitions,%s", config_context);
+ if (base == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_search_dn(ads, &res, base, attrs);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (ads_count_replies(ads, res) != 1) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ }
+
+ (*suffixes) = ads_pull_strings(ads, mem_ctx, res, "uPNSuffixes", num_suffixes);
+ if ((*suffixes) == NULL) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/**
+ * get the joinable ous for a domain
+ * @param ads connection to ads server
+ * @param mem_ctx Pointer to talloc context
+ * @param ous Pointer to an array of ous
+ * @param num_ous Pointer to the number of ous
+ * @return status of search
+ **/
+ADS_STATUS ads_get_joinable_ous(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ char ***ous,
+ size_t *num_ous)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg = NULL;
+ const char *attrs[] = { "dn", NULL };
+ int count = 0;
+
+ status = ads_search(ads, &res,
+ "(|(objectClass=domain)(objectclass=organizationalUnit))",
+ attrs);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count < 1) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+ }
+
+ for (msg = ads_first_entry(ads, res); msg;
+ msg = ads_next_entry(ads, msg)) {
+ const char **p = discard_const_p(const char *, *ous);
+ char *dn = NULL;
+
+ dn = ads_get_dn(ads, talloc_tos(), msg);
+ if (!dn) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (!add_string_to_array(mem_ctx, dn, &p, num_ous)) {
+ TALLOC_FREE(dn);
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ TALLOC_FREE(dn);
+ *ous = discard_const_p(char *, p);
+ }
+
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+
+/**
+ * pull a struct dom_sid from an extended dn string
+ * @param mem_ctx TALLOC_CTX
+ * @param extended_dn string
+ * @param flags string type of extended_dn
+ * @param sid pointer to a struct dom_sid
+ * @return NT_STATUS_OK on success,
+ * NT_INVALID_PARAMETER on error,
+ * NT_STATUS_NOT_FOUND if no SID present
+ **/
+ADS_STATUS ads_get_sid_from_extended_dn(TALLOC_CTX *mem_ctx,
+ const char *extended_dn,
+ enum ads_extended_dn_flags flags,
+ struct dom_sid *sid)
+{
+ char *p, *q, *dn;
+
+ if (!extended_dn) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+
+ /* otherwise extended_dn gets stripped off */
+ if ((dn = talloc_strdup(mem_ctx, extended_dn)) == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+ /*
+ * ADS_EXTENDED_DN_HEX_STRING:
+ * <GUID=238e1963cb390f4bb032ba0105525a29>;<SID=010500000000000515000000bb68c8fd6b61b427572eb04556040000>;CN=gd,OU=berlin,OU=suse,DC=ber,DC=suse,DC=de
+ *
+ * ADS_EXTENDED_DN_STRING (only with w2k3):
+ * <GUID=63198e23-39cb-4b0f-b032-ba0105525a29>;<SID=S-1-5-21-4257769659-666132843-1169174103-1110>;CN=gd,OU=berlin,OU=suse,DC=ber,DC=suse,DC=de
+ *
+ * Object with no SID, such as an Exchange Public Folder
+ * <GUID=28907fb4bdf6854993e7f0a10b504e7c>;CN=public,CN=Microsoft Exchange System Objects,DC=sd2k3ms,DC=west,DC=isilon,DC=com
+ */
+
+ p = strchr(dn, ';');
+ if (!p) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (strncmp(p, ";<SID=", strlen(";<SID=")) != 0) {
+ DEBUG(5,("No SID present in extended dn\n"));
+ return ADS_ERROR_NT(NT_STATUS_NOT_FOUND);
+ }
+
+ p += strlen(";<SID=");
+
+ q = strchr(p, '>');
+ if (!q) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+
+ *q = '\0';
+
+ DEBUG(100,("ads_get_sid_from_extended_dn: sid string is %s\n", p));
+
+ switch (flags) {
+
+ case ADS_EXTENDED_DN_STRING:
+ if (!string_to_sid(sid, p)) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+ break;
+ case ADS_EXTENDED_DN_HEX_STRING: {
+ ssize_t ret;
+ fstring buf;
+ size_t buf_len;
+
+ buf_len = strhex_to_str(buf, sizeof(buf), p, strlen(p));
+ if (buf_len == 0) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+
+ ret = sid_parse((const uint8_t *)buf, buf_len, sid);
+ if (ret == -1) {
+ DEBUG(10,("failed to parse sid\n"));
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+ break;
+ }
+ default:
+ DEBUG(10,("unknown extended dn format\n"));
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+
+ return ADS_ERROR_NT(NT_STATUS_OK);
+}
+
+/********************************************************************
+********************************************************************/
+
+char* ads_get_dnshostname( ADS_STRUCT *ads, TALLOC_CTX *ctx, const char *machine_name )
+{
+ LDAPMessage *res = NULL;
+ ADS_STATUS status;
+ int count = 0;
+ char *name = NULL;
+
+ status = ads_find_machine_acct(ads, &res, machine_name);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0,("ads_get_dnshostname: Failed to find account for %s\n",
+ lp_netbios_name()));
+ goto out;
+ }
+
+ if ( (count = ads_count_replies(ads, res)) != 1 ) {
+ DEBUG(1,("ads_get_dnshostname: %d entries returned!\n", count));
+ goto out;
+ }
+
+ if ( (name = ads_pull_string(ads, ctx, res, "dNSHostName")) == NULL ) {
+ DEBUG(0,("ads_get_dnshostname: No dNSHostName attribute!\n"));
+ }
+
+out:
+ ads_msgfree(ads, res);
+
+ return name;
+}
+
+/********************************************************************
+********************************************************************/
+
+static char **get_addl_hosts(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ LDAPMessage *msg, size_t *num_values)
+{
+ const char *field = "msDS-AdditionalDnsHostName";
+ struct berval **values = NULL;
+ char **ret = NULL;
+ size_t i, converted_size;
+
+ /*
+ * Windows DC implicitly adds a short name for each FQDN added to
+ * msDS-AdditionalDnsHostName, but it comes with a strange binary
+ * suffix "\0$" which we should ignore (see bug #14406).
+ */
+
+ values = ldap_get_values_len(ads->ldap.ld, msg, field);
+ if (values == NULL) {
+ return NULL;
+ }
+
+ *num_values = ldap_count_values_len(values);
+
+ ret = talloc_array(mem_ctx, char *, *num_values + 1);
+ if (ret == NULL) {
+ ldap_value_free_len(values);
+ return NULL;
+ }
+
+ for (i = 0; i < *num_values; i++) {
+ ret[i] = NULL;
+ if (!convert_string_talloc(mem_ctx, CH_UTF8, CH_UNIX,
+ values[i]->bv_val,
+ strnlen(values[i]->bv_val,
+ values[i]->bv_len),
+ &ret[i], &converted_size)) {
+ ldap_value_free_len(values);
+ return NULL;
+ }
+ }
+ ret[i] = NULL;
+
+ ldap_value_free_len(values);
+ return ret;
+}
+
+ADS_STATUS ads_get_additional_dns_hostnames(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char *machine_name,
+ char ***hostnames_array,
+ size_t *num_hostnames)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int count;
+
+ status = ads_find_machine_acct(ads,
+ &res,
+ machine_name);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("Host Account for %s not found... skipping operation.\n",
+ machine_name));
+ return status;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count != 1) {
+ status = ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ goto done;
+ }
+
+ *hostnames_array = get_addl_hosts(ads, mem_ctx, res, num_hostnames);
+ if (*hostnames_array == NULL) {
+ DEBUG(1, ("Host account for %s does not have msDS-AdditionalDnsHostName.\n",
+ machine_name));
+ status = ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ goto done;
+ }
+
+done:
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+char* ads_get_upn( ADS_STRUCT *ads, TALLOC_CTX *ctx, const char *machine_name )
+{
+ LDAPMessage *res = NULL;
+ ADS_STATUS status;
+ int count = 0;
+ char *name = NULL;
+
+ status = ads_find_machine_acct(ads, &res, machine_name);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0,("ads_get_upn: Failed to find account for %s\n",
+ lp_netbios_name()));
+ goto out;
+ }
+
+ if ( (count = ads_count_replies(ads, res)) != 1 ) {
+ DEBUG(1,("ads_get_upn: %d entries returned!\n", count));
+ goto out;
+ }
+
+ if ( (name = ads_pull_string(ads, ctx, res, "userPrincipalName")) == NULL ) {
+ DEBUG(2,("ads_get_upn: No userPrincipalName attribute!\n"));
+ }
+
+out:
+ ads_msgfree(ads, res);
+
+ return name;
+}
+
+/********************************************************************
+********************************************************************/
+
+bool ads_has_samaccountname( ADS_STRUCT *ads, TALLOC_CTX *ctx, const char *machine_name )
+{
+ LDAPMessage *res = NULL;
+ ADS_STATUS status;
+ int count = 0;
+ char *name = NULL;
+ bool ok = false;
+
+ status = ads_find_machine_acct(ads, &res, machine_name);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0,("ads_has_samaccountname: Failed to find account for %s\n",
+ lp_netbios_name()));
+ goto out;
+ }
+
+ if ( (count = ads_count_replies(ads, res)) != 1 ) {
+ DEBUG(1,("ads_has_samaccountname: %d entries returned!\n", count));
+ goto out;
+ }
+
+ if ( (name = ads_pull_string(ads, ctx, res, "sAMAccountName")) == NULL ) {
+ DEBUG(0,("ads_has_samaccountname: No sAMAccountName attribute!\n"));
+ }
+
+out:
+ ads_msgfree(ads, res);
+ if (name != NULL) {
+ ok = (strlen(name) > 0);
+ }
+ TALLOC_FREE(name);
+ return ok;
+}
+
+#if 0
+
+ SAVED CODE - we used to join via ldap - remember how we did this. JRA.
+
+/**
+ * Join a machine to a realm
+ * Creates the machine account and sets the machine password
+ * @param ads connection to ads server
+ * @param machine name of host to add
+ * @param org_unit Organizational unit to place machine in
+ * @return status of join
+ **/
+ADS_STATUS ads_join_realm(ADS_STRUCT *ads, const char *machine_name,
+ uint32_t account_type, const char *org_unit)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ char *machine;
+
+ /* machine name must be lowercase */
+ machine = SMB_STRDUP(machine_name);
+ strlower_m(machine);
+
+ /*
+ status = ads_find_machine_acct(ads, (void **)&res, machine);
+ if (ADS_ERR_OK(status) && ads_count_replies(ads, res) == 1) {
+ DEBUG(0, ("Host account for %s already exists - deleting old account\n", machine));
+ status = ads_leave_realm(ads, machine);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0, ("Failed to delete host '%s' from the '%s' realm.\n",
+ machine, ads->config.realm));
+ return status;
+ }
+ }
+ */
+ status = ads_add_machine_acct(ads, machine, account_type, org_unit);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0, ("ads_join_realm: ads_add_machine_acct failed (%s): %s\n", machine, ads_errstr(status)));
+ SAFE_FREE(machine);
+ return status;
+ }
+
+ status = ads_find_machine_acct(ads, (void **)(void *)&res, machine);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0, ("ads_join_realm: Host account test failed for machine %s\n", machine));
+ SAFE_FREE(machine);
+ return status;
+ }
+
+ SAFE_FREE(machine);
+ ads_msgfree(ads, res);
+
+ return status;
+}
+#endif
+
+/**
+ * Delete a machine from the realm
+ * @param ads connection to ads server
+ * @param hostname Machine to remove
+ * @return status of delete
+ **/
+ADS_STATUS ads_leave_realm(ADS_STRUCT *ads, const char *hostname)
+{
+ ADS_STATUS status;
+ void *msg;
+ LDAPMessage *res;
+ char *hostnameDN, *host;
+ int rc;
+ LDAPControl ldap_control;
+ LDAPControl * pldap_control[2] = {NULL, NULL};
+
+ pldap_control[0] = &ldap_control;
+ memset(&ldap_control, 0, sizeof(LDAPControl));
+ ldap_control.ldctl_oid = discard_const_p(char, LDAP_SERVER_TREE_DELETE_OID);
+
+ /* hostname must be lowercase */
+ host = SMB_STRDUP(hostname);
+ if (!strlower_m(host)) {
+ SAFE_FREE(host);
+ return ADS_ERROR_SYSTEM(EINVAL);
+ }
+
+ status = ads_find_machine_acct(ads, &res, host);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0, ("Host account for %s does not exist.\n", host));
+ SAFE_FREE(host);
+ return status;
+ }
+
+ msg = ads_first_entry(ads, res);
+ if (!msg) {
+ SAFE_FREE(host);
+ return ADS_ERROR_SYSTEM(ENOENT);
+ }
+
+ hostnameDN = ads_get_dn(ads, talloc_tos(), (LDAPMessage *)msg);
+ if (hostnameDN == NULL) {
+ SAFE_FREE(host);
+ return ADS_ERROR_SYSTEM(ENOENT);
+ }
+
+ rc = ldap_delete_ext_s(ads->ldap.ld, hostnameDN, pldap_control, NULL);
+ if (rc) {
+ DEBUG(3,("ldap_delete_ext_s failed with error code %d\n", rc));
+ }else {
+ DEBUG(3,("ldap_delete_ext_s succeeded with error code %d\n", rc));
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ const char *attrs[] = { "cn", NULL };
+ LDAPMessage *msg_sub;
+
+ /* we only search with scope ONE, we do not expect any further
+ * objects to be created deeper */
+
+ status = ads_do_search_retry(ads, hostnameDN,
+ LDAP_SCOPE_ONELEVEL,
+ "(objectclass=*)", attrs, &res);
+
+ if (!ADS_ERR_OK(status)) {
+ SAFE_FREE(host);
+ TALLOC_FREE(hostnameDN);
+ return status;
+ }
+
+ for (msg_sub = ads_first_entry(ads, res); msg_sub;
+ msg_sub = ads_next_entry(ads, msg_sub)) {
+
+ char *dn = NULL;
+
+ if ((dn = ads_get_dn(ads, talloc_tos(), msg_sub)) == NULL) {
+ SAFE_FREE(host);
+ TALLOC_FREE(hostnameDN);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_del_dn(ads, dn);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(3,("failed to delete dn %s: %s\n", dn, ads_errstr(status)));
+ SAFE_FREE(host);
+ TALLOC_FREE(dn);
+ TALLOC_FREE(hostnameDN);
+ return status;
+ }
+
+ TALLOC_FREE(dn);
+ }
+
+ /* there should be no subordinate objects anymore */
+ status = ads_do_search_retry(ads, hostnameDN,
+ LDAP_SCOPE_ONELEVEL,
+ "(objectclass=*)", attrs, &res);
+
+ if (!ADS_ERR_OK(status) || ( (ads_count_replies(ads, res)) > 0 ) ) {
+ SAFE_FREE(host);
+ TALLOC_FREE(hostnameDN);
+ return status;
+ }
+
+ /* delete hostnameDN now */
+ status = ads_del_dn(ads, hostnameDN);
+ if (!ADS_ERR_OK(status)) {
+ SAFE_FREE(host);
+ DEBUG(3,("failed to delete dn %s: %s\n", hostnameDN, ads_errstr(status)));
+ TALLOC_FREE(hostnameDN);
+ return status;
+ }
+ }
+
+ TALLOC_FREE(hostnameDN);
+
+ status = ads_find_machine_acct(ads, &res, host);
+ if ((status.error_type == ENUM_ADS_ERROR_LDAP) &&
+ (status.err.rc != LDAP_NO_SUCH_OBJECT)) {
+ DEBUG(3, ("Failed to remove host account.\n"));
+ SAFE_FREE(host);
+ return status;
+ }
+
+ SAFE_FREE(host);
+ return ADS_SUCCESS;
+}
+
+/**
+ * pull all token-sids from an LDAP dn
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX for allocating sid array
+ * @param dn of LDAP object
+ * @param user_sid pointer to struct dom_sid (objectSid)
+ * @param primary_group_sid pointer to struct dom_sid (self composed)
+ * @param sids pointer to sid array to allocate
+ * @param num_sids counter of SIDs pulled
+ * @return status of token query
+ **/
+ ADS_STATUS ads_get_tokensids(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ const char *dn,
+ struct dom_sid *user_sid,
+ struct dom_sid *primary_group_sid,
+ struct dom_sid **sids,
+ size_t *num_sids)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int count = 0;
+ size_t tmp_num_sids;
+ struct dom_sid *tmp_sids;
+ struct dom_sid tmp_user_sid;
+ struct dom_sid tmp_primary_group_sid;
+ uint32_t pgid;
+ const char *attrs[] = {
+ "objectSid",
+ "tokenGroups",
+ "primaryGroupID",
+ NULL
+ };
+
+ status = ads_search_retry_dn(ads, &res, dn, attrs);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count != 1) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_SUCH_OBJECT);
+ }
+
+ if (!ads_pull_sid(ads, res, "objectSid", &tmp_user_sid)) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ if (!ads_pull_uint32(ads, res, "primaryGroupID", &pgid)) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ {
+ /* hack to compose the primary group sid without knowing the
+ * domsid */
+
+ struct dom_sid domsid;
+
+ sid_copy(&domsid, &tmp_user_sid);
+
+ if (!sid_split_rid(&domsid, NULL)) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ if (!sid_compose(&tmp_primary_group_sid, &domsid, pgid)) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+ }
+
+ tmp_num_sids = ads_pull_sids(ads, mem_ctx, res, "tokenGroups", &tmp_sids);
+
+ if (tmp_num_sids == 0 || !tmp_sids) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ if (num_sids) {
+ *num_sids = tmp_num_sids;
+ }
+
+ if (sids) {
+ *sids = tmp_sids;
+ }
+
+ if (user_sid) {
+ *user_sid = tmp_user_sid;
+ }
+
+ if (primary_group_sid) {
+ *primary_group_sid = tmp_primary_group_sid;
+ }
+
+ DEBUG(10,("ads_get_tokensids: returned %d sids\n", (int)tmp_num_sids + 2));
+
+ ads_msgfree(ads, res);
+ return ADS_ERROR_LDAP(LDAP_SUCCESS);
+}
+
+/**
+ * Find a sAMAccountName in LDAP
+ * @param ads connection to ads server
+ * @param mem_ctx TALLOC_CTX for allocating sid array
+ * @param samaccountname to search
+ * @param uac_ret uint32_t pointer userAccountControl attribute value
+ * @param dn_ret pointer to dn
+ * @return status of token query
+ **/
+ADS_STATUS ads_find_samaccount(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ const char *samaccountname,
+ uint32_t *uac_ret,
+ const char **dn_ret)
+{
+ ADS_STATUS status;
+ const char *attrs[] = { "userAccountControl", NULL };
+ const char *filter;
+ LDAPMessage *res = NULL;
+ char *dn = NULL;
+ uint32_t uac = 0;
+
+ filter = talloc_asprintf(mem_ctx, "(&(objectclass=user)(sAMAccountName=%s))",
+ samaccountname);
+ if (filter == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ status = ads_do_search_all(ads, ads->config.bind_path,
+ LDAP_SCOPE_SUBTREE,
+ filter, attrs, &res);
+
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (ads_count_replies(ads, res) != 1) {
+ status = ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+ goto out;
+ }
+
+ dn = ads_get_dn(ads, talloc_tos(), res);
+ if (dn == NULL) {
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ if (!ads_pull_uint32(ads, res, "userAccountControl", &uac)) {
+ status = ADS_ERROR(LDAP_NO_SUCH_ATTRIBUTE);
+ goto out;
+ }
+
+ if (uac_ret) {
+ *uac_ret = uac;
+ }
+
+ if (dn_ret) {
+ *dn_ret = talloc_strdup(mem_ctx, dn);
+ if (!*dn_ret) {
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+ }
+ out:
+ TALLOC_FREE(dn);
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/**
+ * find our configuration path
+ * @param ads connection to ads server
+ * @param mem_ctx Pointer to talloc context
+ * @param config_path Pointer to the config path
+ * @return status of search
+ **/
+ADS_STATUS ads_config_path(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ char **config_path)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ const char *config_context = NULL;
+ const char *attrs[] = { "configurationNamingContext", NULL };
+
+ status = ads_do_search(ads, "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, &res);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ config_context = ads_pull_string(ads, mem_ctx, res,
+ "configurationNamingContext");
+ ads_msgfree(ads, res);
+ if (!config_context) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (config_path) {
+ *config_path = talloc_strdup(mem_ctx, config_context);
+ if (!*config_path) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ }
+
+ return ADS_ERROR(LDAP_SUCCESS);
+}
+
+/**
+ * find the displayName of an extended right
+ * @param ads connection to ads server
+ * @param config_path The config path
+ * @param mem_ctx Pointer to talloc context
+ * @param GUID struct of the rightsGUID
+ * @return status of search
+ **/
+const char *ads_get_extended_right_name_by_guid(ADS_STRUCT *ads,
+ const char *config_path,
+ TALLOC_CTX *mem_ctx,
+ const struct GUID *rights_guid)
+{
+ ADS_STATUS rc;
+ LDAPMessage *res = NULL;
+ char *expr = NULL;
+ const char *attrs[] = { "displayName", NULL };
+ const char *result = NULL;
+ const char *path;
+
+ if (!ads || !mem_ctx || !rights_guid) {
+ goto done;
+ }
+
+ expr = talloc_asprintf(mem_ctx, "(rightsGuid=%s)",
+ GUID_string(mem_ctx, rights_guid));
+ if (!expr) {
+ goto done;
+ }
+
+ path = talloc_asprintf(mem_ctx, "cn=Extended-Rights,%s", config_path);
+ if (!path) {
+ goto done;
+ }
+
+ rc = ads_do_search_retry(ads, path, LDAP_SCOPE_SUBTREE,
+ expr, attrs, &res);
+ if (!ADS_ERR_OK(rc)) {
+ goto done;
+ }
+
+ if (ads_count_replies(ads, res) != 1) {
+ goto done;
+ }
+
+ result = ads_pull_string(ads, mem_ctx, res, "displayName");
+
+ done:
+ ads_msgfree(ads, res);
+ return result;
+}
+
+/**
+ * verify or build and verify an account ou
+ * @param mem_ctx Pointer to talloc context
+ * @param ads connection to ads server
+ * @param account_ou
+ * @return status of search
+ **/
+
+ADS_STATUS ads_check_ou_dn(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char **account_ou)
+{
+ char **exploded_dn;
+ const char *name;
+ char *ou_string;
+
+ if (account_ou == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (*account_ou != NULL) {
+ exploded_dn = ldap_explode_dn(*account_ou, 0);
+ if (exploded_dn) {
+ ldap_value_free(exploded_dn);
+ return ADS_SUCCESS;
+ }
+ }
+
+ ou_string = ads_ou_string(ads, *account_ou);
+ if (!ou_string) {
+ return ADS_ERROR_LDAP(LDAP_INVALID_DN_SYNTAX);
+ }
+
+ name = talloc_asprintf(mem_ctx, "%s,%s", ou_string,
+ ads->config.bind_path);
+ SAFE_FREE(ou_string);
+
+ if (!name) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ exploded_dn = ldap_explode_dn(name, 0);
+ if (!exploded_dn) {
+ return ADS_ERROR_LDAP(LDAP_INVALID_DN_SYNTAX);
+ }
+ ldap_value_free(exploded_dn);
+
+ *account_ou = name;
+ return ADS_SUCCESS;
+}
+
+#endif
diff --git a/source3/libads/ldap_printer.c b/source3/libads/ldap_printer.c
new file mode 100644
index 0000000..e610893
--- /dev/null
+++ b/source3/libads/ldap_printer.c
@@ -0,0 +1,361 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) printer utility library
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "registry.h"
+#include "libcli/registry/util_reg.h"
+
+#ifdef HAVE_ADS
+
+/*
+ find a printer given the name and the hostname
+ Note that results "res" may be allocated on return so that the
+ results can be used. It should be freed using ads_msgfree.
+*/
+ ADS_STATUS ads_find_printer_on_server(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *printer,
+ const char *servername)
+{
+ ADS_STATUS status;
+ char *srv_dn, **srv_cn, *s = NULL;
+ const char *attrs[] = {"*", "nTSecurityDescriptor", NULL};
+
+ status = ads_find_machine_acct(ads, res, servername);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1, ("ads_find_printer_on_server: cannot find host %s in ads\n",
+ servername));
+ return status;
+ }
+ if (ads_count_replies(ads, *res) != 1) {
+ ads_msgfree(ads, *res);
+ *res = NULL;
+ return ADS_ERROR(LDAP_NO_SUCH_OBJECT);
+ }
+ srv_dn = ldap_get_dn(ads->ldap.ld, *res);
+ if (srv_dn == NULL) {
+ ads_msgfree(ads, *res);
+ *res = NULL;
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ srv_cn = ldap_explode_dn(srv_dn, 1);
+ if (srv_cn == NULL) {
+ ldap_memfree(srv_dn);
+ ads_msgfree(ads, *res);
+ *res = NULL;
+ return ADS_ERROR(LDAP_INVALID_DN_SYNTAX);
+ }
+ ads_msgfree(ads, *res);
+ *res = NULL;
+
+ if (asprintf(&s, "(cn=%s-%s)", srv_cn[0], printer) == -1) {
+ ldap_memfree(srv_dn);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ status = ads_search(ads, res, s, attrs);
+
+ ldap_memfree(srv_dn);
+ ldap_value_free(srv_cn);
+ SAFE_FREE(s);
+ return status;
+}
+
+ ADS_STATUS ads_find_printers(ADS_STRUCT *ads, LDAPMessage **res)
+{
+ const char *ldap_expr;
+ const char *attrs[] = { "objectClass", "printerName", "location", "driverName",
+ "serverName", "description", NULL };
+
+ /* For the moment only display all printers */
+
+ ldap_expr = "(&(!(showInAdvancedViewOnly=TRUE))(uncName=*)"
+ "(objectCategory=printQueue))";
+
+ return ads_search(ads, res, ldap_expr, attrs);
+}
+
+/*
+ modify a printer entry in the directory
+*/
+ADS_STATUS ads_mod_printer_entry(ADS_STRUCT *ads, char *prt_dn,
+ TALLOC_CTX *ctx, const ADS_MODLIST *mods)
+{
+ return ads_gen_mod(ads, prt_dn, *mods);
+}
+
+/*
+ add a printer to the directory
+*/
+ADS_STATUS ads_add_printer_entry(ADS_STRUCT *ads, char *prt_dn,
+ TALLOC_CTX *ctx, ADS_MODLIST *mods)
+{
+ ads_mod_str(ctx, mods, "objectClass", "printQueue");
+ return ads_gen_add(ads, prt_dn, *mods);
+}
+
+/*
+ map a REG_SZ to an ldap mod
+*/
+static bool map_sz(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, struct registry_value *value)
+{
+ const char *str_value = NULL;
+ ADS_STATUS status;
+
+ if (value->type != REG_SZ)
+ return false;
+
+ if (value->data.length && value->data.data) {
+ if (!pull_reg_sz(ctx, &value->data, &str_value)) {
+ return false;
+ }
+ status = ads_mod_str(ctx, mods, name, str_value);
+ return ADS_ERR_OK(status);
+ }
+ return true;
+}
+
+/*
+ map a REG_DWORD to an ldap mod
+*/
+static bool map_dword(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, struct registry_value *value)
+{
+ char *str_value = NULL;
+ ADS_STATUS status;
+
+ if (value->type != REG_DWORD) {
+ return false;
+ }
+ if (value->data.length != sizeof(uint32_t)) {
+ return false;
+ }
+ str_value = talloc_asprintf(ctx, "%d", IVAL(value->data.data, 0));
+ if (!str_value) {
+ return false;
+ }
+ status = ads_mod_str(ctx, mods, name, str_value);
+ return ADS_ERR_OK(status);
+}
+
+/*
+ map a boolean REG_BINARY to an ldap mod
+*/
+static bool map_bool(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, struct registry_value *value)
+{
+ const char *str_value;
+ ADS_STATUS status;
+
+ if (value->type != REG_BINARY) {
+ return false;
+ }
+ if (value->data.length != 1) {
+ return false;
+ }
+
+ str_value = *value->data.data ? "TRUE" : "FALSE";
+
+ status = ads_mod_str(ctx, mods, name, str_value);
+ return ADS_ERR_OK(status);
+}
+
+/*
+ map a REG_MULTI_SZ to an ldap mod
+*/
+static bool map_multi_sz(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, struct registry_value *value)
+{
+ const char **str_values = NULL;
+ ADS_STATUS status;
+
+ if (value->type != REG_MULTI_SZ) {
+ return false;
+ }
+
+ if (value->data.length && value->data.data) {
+ if (!pull_reg_multi_sz(ctx, &value->data, &str_values)) {
+ return false;
+ }
+ status = ads_mod_strlist(ctx, mods, name, str_values);
+ return ADS_ERR_OK(status);
+ }
+ return true;
+}
+
+struct valmap_to_ads {
+ const char *valname;
+ bool (*fn)(TALLOC_CTX *, ADS_MODLIST *, const char *, struct registry_value *);
+};
+
+/*
+ map a REG_SZ to an ldap mod
+*/
+static void map_regval_to_ads(TALLOC_CTX *ctx, ADS_MODLIST *mods,
+ const char *name, struct registry_value *value)
+{
+ const struct valmap_to_ads map[] = {
+ {SPOOL_REG_ASSETNUMBER, map_sz},
+ {SPOOL_REG_BYTESPERMINUTE, map_dword},
+ {SPOOL_REG_DEFAULTPRIORITY, map_dword},
+ {SPOOL_REG_DESCRIPTION, map_sz},
+ {SPOOL_REG_DRIVERNAME, map_sz},
+ {SPOOL_REG_DRIVERVERSION, map_dword},
+ {SPOOL_REG_FLAGS, map_dword},
+ {SPOOL_REG_LOCATION, map_sz},
+ {SPOOL_REG_OPERATINGSYSTEM, map_sz},
+ {SPOOL_REG_OPERATINGSYSTEMHOTFIX, map_sz},
+ {SPOOL_REG_OPERATINGSYSTEMSERVICEPACK, map_sz},
+ {SPOOL_REG_OPERATINGSYSTEMVERSION, map_sz},
+ {SPOOL_REG_PORTNAME, map_multi_sz},
+ {SPOOL_REG_PRINTATTRIBUTES, map_dword},
+ {SPOOL_REG_PRINTBINNAMES, map_multi_sz},
+ {SPOOL_REG_PRINTCOLLATE, map_bool},
+ {SPOOL_REG_PRINTCOLOR, map_bool},
+ {SPOOL_REG_PRINTDUPLEXSUPPORTED, map_bool},
+ {SPOOL_REG_PRINTENDTIME, map_dword},
+ {SPOOL_REG_PRINTFORMNAME, map_sz},
+ {SPOOL_REG_PRINTKEEPPRINTEDJOBS, map_bool},
+ {SPOOL_REG_PRINTLANGUAGE, map_multi_sz},
+ {SPOOL_REG_PRINTMACADDRESS, map_sz},
+ {SPOOL_REG_PRINTMAXCOPIES, map_sz},
+ {SPOOL_REG_PRINTMAXRESOLUTIONSUPPORTED, map_dword},
+ {SPOOL_REG_PRINTMAXXEXTENT, map_dword},
+ {SPOOL_REG_PRINTMAXYEXTENT, map_dword},
+ {SPOOL_REG_PRINTMEDIAREADY, map_multi_sz},
+ {SPOOL_REG_PRINTMEDIASUPPORTED, map_multi_sz},
+ {SPOOL_REG_PRINTMEMORY, map_dword},
+ {SPOOL_REG_PRINTMINXEXTENT, map_dword},
+ {SPOOL_REG_PRINTMINYEXTENT, map_dword},
+ {SPOOL_REG_PRINTNETWORKADDRESS, map_sz},
+ {SPOOL_REG_PRINTNOTIFY, map_sz},
+ {SPOOL_REG_PRINTNUMBERUP, map_dword},
+ {SPOOL_REG_PRINTORIENTATIONSSUPPORTED, map_multi_sz},
+ {SPOOL_REG_PRINTOWNER, map_sz},
+ {SPOOL_REG_PRINTPAGESPERMINUTE, map_dword},
+ {SPOOL_REG_PRINTRATE, map_dword},
+ {SPOOL_REG_PRINTRATEUNIT, map_sz},
+ {SPOOL_REG_PRINTSEPARATORFILE, map_sz},
+ {SPOOL_REG_PRINTSHARENAME, map_sz},
+ {SPOOL_REG_PRINTSPOOLING, map_sz},
+ {SPOOL_REG_PRINTSTAPLINGSUPPORTED, map_bool},
+ {SPOOL_REG_PRINTSTARTTIME, map_dword},
+ {SPOOL_REG_PRINTSTATUS, map_sz},
+ {SPOOL_REG_PRIORITY, map_dword},
+ {SPOOL_REG_SERVERNAME, map_sz},
+ {SPOOL_REG_SHORTSERVERNAME, map_sz},
+ {SPOOL_REG_UNCNAME, map_sz},
+ {SPOOL_REG_URL, map_sz},
+ {SPOOL_REG_VERSIONNUMBER, map_dword},
+ {NULL, NULL}
+ };
+ int i;
+
+ for (i=0; map[i].valname; i++) {
+ if (strcasecmp_m(map[i].valname, name) == 0) {
+ if (!map[i].fn(ctx, mods, name, value)) {
+ DEBUG(5, ("Add of value %s to modlist failed\n", name));
+ } else {
+ DEBUG(7, ("Mapped value %s\n", name));
+ }
+ }
+ }
+}
+
+
+WERROR get_remote_printer_publishing_data(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ ADS_MODLIST *mods,
+ const char *printer)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ WERROR result;
+ char *printername;
+ struct spoolss_PrinterEnumValues *info;
+ uint32_t count;
+ uint32_t i;
+ struct policy_handle pol;
+ WERROR werr;
+
+ if ((asprintf(&printername, "%s\\%s", cli->srv_name_slash, printer) == -1)) {
+ DEBUG(3, ("Insufficient memory\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3, ("Unable to open printer %s, error is %s.\n",
+ printername, win_errstr(result)));
+ SAFE_FREE(printername);
+ return result;
+ }
+
+ result = rpccli_spoolss_enumprinterdataex(cli, mem_ctx, &pol,
+ SPOOL_DSDRIVER_KEY,
+ 0,
+ &count,
+ &info);
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3, ("Unable to do enumdataex on %s, error is %s.\n",
+ printername, win_errstr(result)));
+ } else {
+ /* Have the data we need now, so start building */
+ for (i=0; i < count; i++) {
+ struct registry_value v;
+ v.type = info[i].type;
+ v.data = *info[i].data;
+
+ map_regval_to_ads(mem_ctx, mods, info[i].value_name, &v);
+ }
+ }
+
+ result = rpccli_spoolss_enumprinterdataex(cli, mem_ctx, &pol,
+ SPOOL_DSSPOOLER_KEY,
+ 0,
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3, ("Unable to do enumdataex on %s, error is %s.\n",
+ printername, win_errstr(result)));
+ } else {
+ for (i=0; i < count; i++) {
+ struct registry_value v;
+ v.type = info[i].type;
+ v.data = *info[i].data;
+
+ map_regval_to_ads(mem_ctx, mods, info[i].value_name, &v);
+ }
+ }
+
+ ads_mod_str(mem_ctx, mods, SPOOL_REG_PRINTERNAME, printer);
+
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &werr);
+ SAFE_FREE(printername);
+
+ return result;
+}
+
+#endif
diff --git a/source3/libads/ldap_schema.c b/source3/libads/ldap_schema.c
new file mode 100644
index 0000000..d2f486e
--- /dev/null
+++ b/source3/libads/ldap_schema.c
@@ -0,0 +1,354 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Guenther Deschner 2005-2007
+ Copyright (C) Gerald (Jerry) Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "libads/ldap_schema.h"
+#include "libads/ldap_schema_oids.h"
+#include "../libcli/ldap/ldap_ndr.h"
+
+#ifdef HAVE_LDAP
+
+static ADS_STATUS ads_get_attrnames_by_oids(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ const char *schema_path,
+ const char **OIDs,
+ size_t num_OIDs,
+ char ***OIDs_out, char ***names,
+ size_t *count)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg;
+ char *expr = NULL;
+ const char *attrs[] = { "lDAPDisplayName", "attributeId", NULL };
+ int i = 0, p = 0;
+
+ if (!ads || !mem_ctx || !names || !count || !OIDs || !OIDs_out) {
+ return ADS_ERROR(LDAP_PARAM_ERROR);
+ }
+
+ if (num_OIDs == 0 || OIDs[0] == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_NONE_MAPPED);
+ }
+
+ expr = talloc_asprintf(mem_ctx, "(|");
+
+ for (i=0; i<num_OIDs; i++) {
+ talloc_asprintf_addbuf(&expr, "(attributeId=%s)", OIDs[i]);
+ }
+
+ talloc_asprintf_addbuf(&expr, ")");
+ if (expr == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_do_search_retry(ads, schema_path,
+ LDAP_SCOPE_SUBTREE, expr, attrs, &res);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ *count = ads_count_replies(ads, res);
+ if (*count == 0 || !res) {
+ status = ADS_ERROR_NT(NT_STATUS_NONE_MAPPED);
+ goto out;
+ }
+
+ if (((*names) = talloc_array(mem_ctx, char *, *count)) == NULL) {
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+ if (((*OIDs_out) = talloc_array(mem_ctx, char *, *count)) == NULL) {
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ for (msg = ads_first_entry(ads, res); msg != NULL;
+ msg = ads_next_entry(ads, msg)) {
+
+ (*names)[p] = ads_pull_string(ads, mem_ctx, msg,
+ "lDAPDisplayName");
+ (*OIDs_out)[p] = ads_pull_string(ads, mem_ctx, msg,
+ "attributeId");
+ if (((*names)[p] == NULL) || ((*OIDs_out)[p] == NULL)) {
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ p++;
+ }
+
+ if (*count < num_OIDs) {
+ status = ADS_ERROR_NT(STATUS_SOME_UNMAPPED);
+ goto out;
+ }
+
+ status = ADS_ERROR(LDAP_SUCCESS);
+out:
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+const char *ads_get_attrname_by_guid(ADS_STRUCT *ads,
+ const char *schema_path,
+ TALLOC_CTX *mem_ctx,
+ const struct GUID *schema_guid)
+{
+ ADS_STATUS rc;
+ LDAPMessage *res = NULL;
+ char *expr = NULL;
+ const char *attrs[] = { "lDAPDisplayName", NULL };
+ const char *result = NULL;
+ char *guid_bin = NULL;
+
+ if (!ads || !mem_ctx || !schema_guid) {
+ goto done;
+ }
+
+ guid_bin = ldap_encode_ndr_GUID(mem_ctx, schema_guid);
+ if (!guid_bin) {
+ goto done;
+ }
+
+ expr = talloc_asprintf(mem_ctx, "(schemaIDGUID=%s)", guid_bin);
+ if (!expr) {
+ goto done;
+ }
+
+ rc = ads_do_search_retry(ads, schema_path, LDAP_SCOPE_SUBTREE,
+ expr, attrs, &res);
+ if (!ADS_ERR_OK(rc)) {
+ goto done;
+ }
+
+ if (ads_count_replies(ads, res) != 1) {
+ goto done;
+ }
+
+ result = ads_pull_string(ads, mem_ctx, res, "lDAPDisplayName");
+
+ done:
+ TALLOC_FREE(guid_bin);
+ ads_msgfree(ads, res);
+ return result;
+
+}
+
+/*********************************************************************
+*********************************************************************/
+
+ADS_STATUS ads_schema_path(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, char **schema_path)
+{
+ ADS_STATUS status;
+ LDAPMessage *res;
+ const char *schema;
+ const char *attrs[] = { "schemaNamingContext", NULL };
+
+ status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if ( (schema = ads_pull_string(ads, mem_ctx, res, "schemaNamingContext")) == NULL ) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_RESULTS_RETURNED);
+ }
+
+ if ( (*schema_path = talloc_strdup(mem_ctx, schema)) == NULL ) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/**
+ * Check for "Services for Unix" or rfc2307 Schema and load some attributes into the ADS_STRUCT
+ * @param ads connection to ads server
+ * @param enum mapping type
+ * @return ADS_STATUS status of search (False if one or more attributes couldn't be
+ * found in Active Directory)
+ **/
+ADS_STATUS ads_check_posix_schema_mapping(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ enum wb_posix_mapping map_type,
+ struct posix_schema **s )
+{
+ TALLOC_CTX *ctx = NULL;
+ ADS_STATUS status;
+ char **oids_out, **names_out;
+ size_t num_names;
+ char *schema_path = NULL;
+ int i;
+ struct posix_schema *schema = NULL;
+
+ const char *oids_sfu[] = { ADS_ATTR_SFU_UIDNUMBER_OID,
+ ADS_ATTR_SFU_GIDNUMBER_OID,
+ ADS_ATTR_SFU_HOMEDIR_OID,
+ ADS_ATTR_SFU_SHELL_OID,
+ ADS_ATTR_SFU_GECOS_OID,
+ ADS_ATTR_SFU_UID_OID };
+
+ const char *oids_sfu20[] = { ADS_ATTR_SFU20_UIDNUMBER_OID,
+ ADS_ATTR_SFU20_GIDNUMBER_OID,
+ ADS_ATTR_SFU20_HOMEDIR_OID,
+ ADS_ATTR_SFU20_SHELL_OID,
+ ADS_ATTR_SFU20_GECOS_OID,
+ ADS_ATTR_SFU20_UID_OID };
+
+ const char *oids_rfc2307[] = { ADS_ATTR_RFC2307_UIDNUMBER_OID,
+ ADS_ATTR_RFC2307_GIDNUMBER_OID,
+ ADS_ATTR_RFC2307_HOMEDIR_OID,
+ ADS_ATTR_RFC2307_SHELL_OID,
+ ADS_ATTR_RFC2307_GECOS_OID,
+ ADS_ATTR_RFC2307_UID_OID };
+
+ DEBUG(10,("ads_check_posix_schema_mapping for schema mode: %d\n", map_type));
+
+ switch (map_type) {
+
+ case WB_POSIX_MAP_TEMPLATE:
+ case WB_POSIX_MAP_UNIXINFO:
+ DEBUG(10,("ads_check_posix_schema_mapping: nothing to do\n"));
+ return ADS_ERROR(LDAP_SUCCESS);
+
+ case WB_POSIX_MAP_SFU:
+ case WB_POSIX_MAP_SFU20:
+ case WB_POSIX_MAP_RFC2307:
+ break;
+
+ default:
+ DEBUG(0,("ads_check_posix_schema_mapping: "
+ "unknown enum %d\n", map_type));
+ return ADS_ERROR(LDAP_PARAM_ERROR);
+ }
+
+ if ( (ctx = talloc_init("ads_check_posix_schema_mapping")) == NULL ) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if ( (schema = talloc(mem_ctx, struct posix_schema)) == NULL ) {
+ TALLOC_FREE( ctx );
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_schema_path(ads, ctx, &schema_path);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(3,("ads_check_posix_mapping: Unable to retrieve schema DN!\n"));
+ goto done;
+ }
+
+ switch (map_type) {
+ case WB_POSIX_MAP_SFU:
+ status = ads_get_attrnames_by_oids(ads, ctx, schema_path, oids_sfu,
+ ARRAY_SIZE(oids_sfu),
+ &oids_out, &names_out, &num_names);
+ break;
+ case WB_POSIX_MAP_SFU20:
+ status = ads_get_attrnames_by_oids(ads, ctx, schema_path, oids_sfu20,
+ ARRAY_SIZE(oids_sfu20),
+ &oids_out, &names_out, &num_names);
+ break;
+ case WB_POSIX_MAP_RFC2307:
+ status = ads_get_attrnames_by_oids(ads, ctx, schema_path, oids_rfc2307,
+ ARRAY_SIZE(oids_rfc2307),
+ &oids_out, &names_out, &num_names);
+ break;
+ default:
+ status = ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ break;
+ }
+
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(3,("ads_check_posix_schema_mapping: failed %s\n",
+ ads_errstr(status)));
+ goto done;
+ }
+
+ for (i=0; i<num_names; i++) {
+
+ DEBUGADD(10,("\tOID %s has name: %s\n", oids_out[i], names_out[i]));
+
+ if (strequal(ADS_ATTR_RFC2307_UIDNUMBER_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU_UIDNUMBER_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU20_UIDNUMBER_OID, oids_out[i])) {
+ schema->posix_uidnumber_attr = talloc_strdup(schema, names_out[i]);
+ continue;
+ }
+
+ if (strequal(ADS_ATTR_RFC2307_GIDNUMBER_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU_GIDNUMBER_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU20_GIDNUMBER_OID, oids_out[i])) {
+ schema->posix_gidnumber_attr = talloc_strdup(schema, names_out[i]);
+ continue;
+ }
+
+ if (strequal(ADS_ATTR_RFC2307_HOMEDIR_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU_HOMEDIR_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU20_HOMEDIR_OID, oids_out[i])) {
+ schema->posix_homedir_attr = talloc_strdup(schema, names_out[i]);
+ continue;
+ }
+
+ if (strequal(ADS_ATTR_RFC2307_SHELL_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU_SHELL_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU20_SHELL_OID, oids_out[i])) {
+ schema->posix_shell_attr = talloc_strdup(schema, names_out[i]);
+ continue;
+ }
+
+ if (strequal(ADS_ATTR_RFC2307_GECOS_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU_GECOS_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU20_GECOS_OID, oids_out[i])) {
+ schema->posix_gecos_attr = talloc_strdup(schema, names_out[i]);
+ }
+
+ if (strequal(ADS_ATTR_RFC2307_UID_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU_UID_OID, oids_out[i]) ||
+ strequal(ADS_ATTR_SFU20_UID_OID, oids_out[i])) {
+ schema->posix_uid_attr = talloc_strdup(schema, names_out[i]);
+ }
+ }
+
+ if (!schema->posix_uidnumber_attr ||
+ !schema->posix_gidnumber_attr ||
+ !schema->posix_homedir_attr ||
+ !schema->posix_shell_attr ||
+ !schema->posix_gecos_attr) {
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ TALLOC_FREE( schema );
+ goto done;
+ }
+
+ *s = schema;
+
+ status = ADS_ERROR(LDAP_SUCCESS);
+
+done:
+ TALLOC_FREE(ctx);
+
+ return status;
+}
+
+#endif
diff --git a/source3/libads/ldap_schema.h b/source3/libads/ldap_schema.h
new file mode 100644
index 0000000..f36d3e0
--- /dev/null
+++ b/source3/libads/ldap_schema.h
@@ -0,0 +1,57 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Guenther Deschner 2005-2007
+ Copyright (C) Gerald (Jerry) Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBADS_LDAP_SCHEMA_H_
+#define _LIBADS_LDAP_SCHEMA_H_
+
+/* used to remember the names of the posix attributes in AD */
+/* see the rfc2307 & sfu nss backends */
+
+struct posix_schema {
+ char *posix_homedir_attr;
+ char *posix_shell_attr;
+ char *posix_uidnumber_attr;
+ char *posix_gidnumber_attr;
+ char *posix_gecos_attr;
+ char *posix_uid_attr;
+};
+
+enum wb_posix_mapping {
+ WB_POSIX_MAP_UNKNOWN = -1,
+ WB_POSIX_MAP_TEMPLATE = 0,
+ WB_POSIX_MAP_SFU = 1,
+ WB_POSIX_MAP_SFU20 = 2,
+ WB_POSIX_MAP_RFC2307 = 3,
+ WB_POSIX_MAP_UNIXINFO = 4
+};
+
+/* The following definitions come from libads/ldap_schema.c */
+
+const char *ads_get_attrname_by_guid(ADS_STRUCT *ads,
+ const char *schema_path,
+ TALLOC_CTX *mem_ctx,
+ const struct GUID *schema_guid);
+ADS_STATUS ads_schema_path(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, char **schema_path);
+ADS_STATUS ads_check_posix_schema_mapping(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ enum wb_posix_mapping map_type,
+ struct posix_schema **s ) ;
+
+#endif /* _LIBADS_LDAP_SCHEMA_H_ */
diff --git a/source3/libads/ldap_schema_oids.h b/source3/libads/ldap_schema_oids.h
new file mode 100644
index 0000000..cb17b3c
--- /dev/null
+++ b/source3/libads/ldap_schema_oids.h
@@ -0,0 +1,49 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Guenther Deschner 2005-2007
+ Copyright (C) Gerald (Jerry) Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBADS_LDAP_SCHEMA_OIDS_H_
+#define _LIBADS_LDAP_SCHEMA_OIDS_H_
+
+/* ldap attribute oids (Services for Unix 3.0, 3.5) */
+#define ADS_ATTR_SFU_UIDNUMBER_OID "1.2.840.113556.1.6.18.1.310"
+#define ADS_ATTR_SFU_GIDNUMBER_OID "1.2.840.113556.1.6.18.1.311"
+#define ADS_ATTR_SFU_HOMEDIR_OID "1.2.840.113556.1.6.18.1.344"
+#define ADS_ATTR_SFU_SHELL_OID "1.2.840.113556.1.6.18.1.312"
+#define ADS_ATTR_SFU_GECOS_OID "1.2.840.113556.1.6.18.1.337"
+#define ADS_ATTR_SFU_UID_OID "1.2.840.113556.1.6.18.1.309"
+
+/* ldap attribute oids (Services for Unix 2.0) */
+#define ADS_ATTR_SFU20_UIDNUMBER_OID "1.2.840.113556.1.4.7000.187.70"
+#define ADS_ATTR_SFU20_GIDNUMBER_OID "1.2.840.113556.1.4.7000.187.71"
+#define ADS_ATTR_SFU20_HOMEDIR_OID "1.2.840.113556.1.4.7000.187.106"
+#define ADS_ATTR_SFU20_SHELL_OID "1.2.840.113556.1.4.7000.187.72"
+#define ADS_ATTR_SFU20_GECOS_OID "1.2.840.113556.1.4.7000.187.97"
+#define ADS_ATTR_SFU20_UID_OID "1.2.840.113556.1.4.7000.187.102"
+
+
+/* ldap attribute oids (RFC2307) */
+#define ADS_ATTR_RFC2307_UIDNUMBER_OID "1.3.6.1.1.1.1.0"
+#define ADS_ATTR_RFC2307_GIDNUMBER_OID "1.3.6.1.1.1.1.1"
+#define ADS_ATTR_RFC2307_HOMEDIR_OID "1.3.6.1.1.1.1.3"
+#define ADS_ATTR_RFC2307_SHELL_OID "1.3.6.1.1.1.1.4"
+#define ADS_ATTR_RFC2307_GECOS_OID "1.3.6.1.1.1.1.2"
+#define ADS_ATTR_RFC2307_UID_OID "0.9.2342.19200300.100.1.1"
+
+#endif
diff --git a/source3/libads/ldap_user.c b/source3/libads/ldap_user.c
new file mode 100644
index 0000000..5542100
--- /dev/null
+++ b/source3/libads/ldap_user.c
@@ -0,0 +1,132 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads (active directory) utility library
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "../libds/common/flags.h"
+
+#ifdef HAVE_ADS
+
+/*
+ find a user account
+*/
+ ADS_STATUS ads_find_user_acct(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *user)
+{
+ ADS_STATUS status;
+ char *ldap_exp;
+ const char *attrs[] = {"*", NULL};
+ char *escaped_user = escape_ldap_string(talloc_tos(), user);
+ if (!escaped_user) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (asprintf(&ldap_exp, "(samAccountName=%s)", escaped_user) == -1) {
+ TALLOC_FREE(escaped_user);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ status = ads_search(ads, res, ldap_exp, attrs);
+ SAFE_FREE(ldap_exp);
+ TALLOC_FREE(escaped_user);
+ return status;
+}
+
+ADS_STATUS ads_add_user_acct(ADS_STRUCT *ads, const char *user,
+ const char *container, const char *fullname)
+{
+ TALLOC_CTX *ctx;
+ ADS_MODLIST mods;
+ ADS_STATUS status;
+ const char *upn, *new_dn, *name, *controlstr;
+ char *name_escaped = NULL;
+ const char *objectClass[] = {"top", "person", "organizationalPerson",
+ "user", NULL};
+
+ if (fullname && *fullname) name = fullname;
+ else name = user;
+
+ if (!(ctx = talloc_init("ads_add_user_acct")))
+ return ADS_ERROR(LDAP_NO_MEMORY);
+
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+
+ if (!(upn = talloc_asprintf(ctx, "%s@%s", user, ads->config.realm)))
+ goto done;
+ if (!(name_escaped = escape_rdn_val_string_alloc(name)))
+ goto done;
+ if (!(new_dn = talloc_asprintf(ctx, "cn=%s,%s,%s", name_escaped, container,
+ ads->config.bind_path)))
+ goto done;
+ if (!(controlstr = talloc_asprintf(ctx, "%u", (UF_NORMAL_ACCOUNT | UF_ACCOUNTDISABLE))))
+ goto done;
+ if (!(mods = ads_init_mods(ctx)))
+ goto done;
+
+ ads_mod_str(ctx, &mods, "cn", name);
+ ads_mod_strlist(ctx, &mods, "objectClass", objectClass);
+ ads_mod_str(ctx, &mods, "userPrincipalName", upn);
+ ads_mod_str(ctx, &mods, "name", name);
+ ads_mod_str(ctx, &mods, "displayName", name);
+ ads_mod_str(ctx, &mods, "sAMAccountName", user);
+ ads_mod_str(ctx, &mods, "userAccountControl", controlstr);
+ status = ads_gen_add(ads, new_dn, mods);
+
+ done:
+ SAFE_FREE(name_escaped);
+ talloc_destroy(ctx);
+ return status;
+}
+
+ADS_STATUS ads_add_group_acct(ADS_STRUCT *ads, const char *group,
+ const char *container, const char *comment)
+{
+ TALLOC_CTX *ctx;
+ ADS_MODLIST mods;
+ ADS_STATUS status;
+ char *new_dn;
+ char *name_escaped = NULL;
+ const char *objectClass[] = {"top", "group", NULL};
+
+ if (!(ctx = talloc_init("ads_add_group_acct")))
+ return ADS_ERROR(LDAP_NO_MEMORY);
+
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+
+ if (!(name_escaped = escape_rdn_val_string_alloc(group)))
+ goto done;
+ if (!(new_dn = talloc_asprintf(ctx, "cn=%s,%s,%s", name_escaped, container,
+ ads->config.bind_path)))
+ goto done;
+ if (!(mods = ads_init_mods(ctx)))
+ goto done;
+
+ ads_mod_str(ctx, &mods, "cn", group);
+ ads_mod_strlist(ctx, &mods, "objectClass",objectClass);
+ ads_mod_str(ctx, &mods, "name", group);
+ if (comment && *comment)
+ ads_mod_str(ctx, &mods, "description", comment);
+ ads_mod_str(ctx, &mods, "sAMAccountName", group);
+ status = ads_gen_add(ads, new_dn, mods);
+
+ done:
+ SAFE_FREE(name_escaped);
+ talloc_destroy(ctx);
+ return status;
+}
+#endif
diff --git a/source3/libads/ldap_utils.c b/source3/libads/ldap_utils.c
new file mode 100644
index 0000000..c08f046
--- /dev/null
+++ b/source3/libads/ldap_utils.c
@@ -0,0 +1,389 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Some Helpful wrappers on LDAP
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Guenther Deschner 2006,2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "lib/param/loadparm.h"
+
+#ifdef HAVE_LDAP
+
+static ADS_STATUS ads_ranged_search_internal(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ int scope,
+ const char *base,
+ const char *filter,
+ const char **attrs,
+ void *args,
+ const char *range_attr,
+ char ***strings,
+ size_t *num_strings,
+ uint32_t *first_usn,
+ int *num_retries,
+ bool *more_values);
+
+/*
+ a wrapper around ldap_search_s that retries depending on the error code
+ this is supposed to catch dropped connections and auto-reconnect
+*/
+static ADS_STATUS ads_do_search_retry_internal(ADS_STRUCT *ads, const char *bind_path, int scope,
+ const char *expr,
+ const char **attrs, void *args,
+ LDAPMessage **res)
+{
+ ADS_STATUS status;
+ int count = 3;
+ char *bp;
+
+ *res = NULL;
+
+ if (!ads->ldap.ld &&
+ time_mono(NULL) - ads->ldap.last_attempt < ADS_RECONNECT_TIME) {
+ return ADS_ERROR(LDAP_SERVER_DOWN);
+ }
+
+ bp = SMB_STRDUP(bind_path);
+
+ if (!bp) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ *res = NULL;
+
+ /* when binding anonymously, we cannot use the paged search LDAP
+ * control - Guenther */
+
+ if (ads->auth.flags & ADS_AUTH_ANON_BIND) {
+ status = ads_do_search(ads, bp, scope, expr, attrs, res);
+ } else {
+ status = ads_do_search_all_args(ads, bp, scope, expr, attrs, args, res);
+ }
+ if (ADS_ERR_OK(status)) {
+ DEBUG(5,("Search for %s in <%s> gave %d replies\n",
+ expr, bp, ads_count_replies(ads, *res)));
+ SAFE_FREE(bp);
+ return status;
+ }
+
+ while (--count) {
+
+ if (NT_STATUS_EQUAL(ads_ntstatus(status), NT_STATUS_IO_TIMEOUT) &&
+ ads->config.ldap_page_size >= (lp_ldap_page_size() / 4) &&
+ lp_ldap_page_size() > 4) {
+ int new_page_size = (ads->config.ldap_page_size / 2);
+ DEBUG(1, ("Reducing LDAP page size from %d to %d due to IO_TIMEOUT\n",
+ ads->config.ldap_page_size, new_page_size));
+ ads->config.ldap_page_size = new_page_size;
+ }
+
+ if (*res)
+ ads_msgfree(ads, *res);
+ *res = NULL;
+
+ DEBUG(3,("Reopening ads connection to realm '%s' after error %s\n",
+ ads->config.realm, ads_errstr(status)));
+
+ ads_disconnect(ads);
+ status = ads_connect(ads);
+
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("ads_search_retry: failed to reconnect (%s)\n",
+ ads_errstr(status)));
+ /*
+ * We need to keep the ads pointer
+ * from being freed here as we don't own it and
+ * callers depend on it being around.
+ */
+ ads_disconnect(ads);
+ SAFE_FREE(bp);
+ return status;
+ }
+
+ *res = NULL;
+
+ /* when binding anonymously, we cannot use the paged search LDAP
+ * control - Guenther */
+
+ if (ads->auth.flags & ADS_AUTH_ANON_BIND) {
+ status = ads_do_search(ads, bp, scope, expr, attrs, res);
+ } else {
+ status = ads_do_search_all_args(ads, bp, scope, expr, attrs, args, res);
+ }
+
+ if (ADS_ERR_OK(status)) {
+ DEBUG(5,("Search for filter: %s, base: %s gave %d replies\n",
+ expr, bp, ads_count_replies(ads, *res)));
+ SAFE_FREE(bp);
+ return status;
+ }
+ }
+ SAFE_FREE(bp);
+
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("ads reopen failed after error %s\n",
+ ads_errstr(status)));
+ }
+ return status;
+}
+
+ ADS_STATUS ads_do_search_retry(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, LDAPMessage **res)
+{
+ return ads_do_search_retry_internal(ads, bind_path, scope, expr, attrs, NULL, res);
+}
+
+static ADS_STATUS ads_do_search_retry_args(ADS_STRUCT *ads, const char *bind_path,
+ int scope, const char *expr,
+ const char **attrs, void *args,
+ LDAPMessage **res)
+{
+ return ads_do_search_retry_internal(ads, bind_path, scope, expr, attrs, args, res);
+}
+
+
+ ADS_STATUS ads_search_retry(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *expr, const char **attrs)
+{
+ return ads_do_search_retry(ads, ads->config.bind_path, LDAP_SCOPE_SUBTREE,
+ expr, attrs, res);
+}
+
+ ADS_STATUS ads_search_retry_dn(ADS_STRUCT *ads, LDAPMessage **res,
+ const char *dn,
+ const char **attrs)
+{
+ return ads_do_search_retry(ads, dn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, res);
+}
+
+ ADS_STATUS ads_search_retry_dn_sd_flags(ADS_STRUCT *ads, LDAPMessage **res,
+ uint32_t sd_flags,
+ const char *dn,
+ const char **attrs)
+{
+ ads_control args;
+
+ args.control = ADS_SD_FLAGS_OID;
+ args.val = sd_flags;
+ args.critical = True;
+
+ return ads_do_search_retry_args(ads, dn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, &args, res);
+}
+
+ ADS_STATUS ads_search_retry_extended_dn_ranged(ADS_STRUCT *ads, TALLOC_CTX *mem_ctx,
+ const char *dn,
+ const char **attrs,
+ enum ads_extended_dn_flags flags,
+ char ***strings,
+ size_t *num_strings)
+{
+ ads_control args;
+
+ args.control = ADS_EXTENDED_DN_OID;
+ args.val = flags;
+ args.critical = True;
+
+ /* we can only range process one attribute */
+ if (!attrs || !attrs[0] || attrs[1]) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+
+ return ads_ranged_search(ads, mem_ctx, LDAP_SCOPE_BASE, dn,
+ "(objectclass=*)", &args, attrs[0],
+ strings, num_strings);
+
+}
+
+ ADS_STATUS ads_search_retry_sid(ADS_STRUCT *ads, LDAPMessage **res,
+ const struct dom_sid *sid,
+ const char **attrs)
+{
+ char *dn, *sid_string;
+ ADS_STATUS status;
+
+ sid_string = sid_binstring_hex_talloc(talloc_tos(), sid);
+ if (sid_string == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (!asprintf(&dn, "<SID=%s>", sid_string)) {
+ TALLOC_FREE(sid_string);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_do_search_retry(ads, dn, LDAP_SCOPE_BASE,
+ "(objectclass=*)", attrs, res);
+ SAFE_FREE(dn);
+ TALLOC_FREE(sid_string);
+ return status;
+}
+
+ADS_STATUS ads_ranged_search(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ int scope,
+ const char *base,
+ const char *filter,
+ void *args,
+ const char *range_attr,
+ char ***strings,
+ size_t *num_strings)
+{
+ ADS_STATUS status;
+ uint32_t first_usn;
+ int num_retries = 0;
+ const char **attrs;
+ bool more_values = False;
+
+ *num_strings = 0;
+ *strings = NULL;
+
+ attrs = talloc_array(mem_ctx, const char *, 3);
+ ADS_ERROR_HAVE_NO_MEMORY(attrs);
+
+ attrs[0] = talloc_strdup(mem_ctx, range_attr);
+ attrs[1] = talloc_strdup(mem_ctx, "usnChanged");
+ attrs[2] = NULL;
+
+ ADS_ERROR_HAVE_NO_MEMORY(attrs[0]);
+ ADS_ERROR_HAVE_NO_MEMORY(attrs[1]);
+
+ do {
+ status = ads_ranged_search_internal(ads, mem_ctx,
+ scope, base, filter,
+ attrs, args, range_attr,
+ strings, num_strings,
+ &first_usn, &num_retries,
+ &more_values);
+
+ if (NT_STATUS_EQUAL(STATUS_MORE_ENTRIES, ads_ntstatus(status))) {
+ continue;
+ }
+
+ if (!ADS_ERR_OK(status)) {
+ *num_strings = 0;
+ strings = NULL;
+ goto done;
+ }
+
+ } while (more_values);
+
+ done:
+ DEBUG(10,("returning with %d strings\n", (int)*num_strings));
+
+ return status;
+}
+
+static ADS_STATUS ads_ranged_search_internal(ADS_STRUCT *ads,
+ TALLOC_CTX *mem_ctx,
+ int scope,
+ const char *base,
+ const char *filter,
+ const char **attrs,
+ void *args,
+ const char *range_attr,
+ char ***strings,
+ size_t *num_strings,
+ uint32_t *first_usn,
+ int *num_retries,
+ bool *more_values)
+{
+ LDAPMessage *res = NULL;
+ ADS_STATUS status;
+ int count;
+ uint32_t current_usn;
+
+ DEBUG(10, ("Searching for attrs[0] = %s, attrs[1] = %s\n", attrs[0], attrs[1]));
+
+ *more_values = False;
+
+ status = ads_do_search_retry_internal(ads, base, scope, filter, attrs, args, &res);
+
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("ads_search: %s\n",
+ ads_errstr(status)));
+ return status;
+ }
+
+ if (!res) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count == 0) {
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_SUCCESS);
+ }
+
+ if (*num_strings == 0) {
+ if (!ads_pull_uint32(ads, res, "usnChanged", first_usn)) {
+ DEBUG(1, ("could not pull first usnChanged!\n"));
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ }
+
+ if (!ads_pull_uint32(ads, res, "usnChanged", &current_usn)) {
+ DEBUG(1, ("could not pull current usnChanged!\n"));
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (*first_usn != current_usn) {
+ DEBUG(5, ("USN on this record changed"
+ " - restarting search\n"));
+ if (*num_retries < 5) {
+ (*num_retries)++;
+ *num_strings = 0;
+ ads_msgfree(ads, res);
+ return ADS_ERROR_NT(STATUS_MORE_ENTRIES);
+ } else {
+ DEBUG(5, ("USN on this record changed"
+ " - restarted search too many times, aborting!\n"));
+ ads_msgfree(ads, res);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ }
+
+ *strings = ads_pull_strings_range(ads, mem_ctx, res,
+ range_attr,
+ *strings,
+ &attrs[0],
+ num_strings,
+ more_values);
+
+ ads_msgfree(ads, res);
+
+ /* paranoia checks */
+ if (*strings == NULL && *more_values) {
+ DEBUG(0,("no strings found but more values???\n"));
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ if (*num_strings == 0 && *more_values) {
+ DEBUG(0,("no strings found but more values???\n"));
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ return (*more_values) ? ADS_ERROR_NT(STATUS_MORE_ENTRIES) : ADS_ERROR(LDAP_SUCCESS);
+}
+
+#endif
diff --git a/source3/libads/net_ads_setspn.c b/source3/libads/net_ads_setspn.c
new file mode 100644
index 0000000..b163184
--- /dev/null
+++ b/source3/libads/net_ads_setspn.c
@@ -0,0 +1,229 @@
+/*
+ Unix SMB/CIFS implementation.
+ net ads setspn routines
+ Copyright (C) Noel Power 2018
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+
+#ifdef HAVE_ADS
+bool ads_setspn_list(ADS_STRUCT *ads, const char *machine_name)
+{
+ size_t i = 0;
+ TALLOC_CTX *frame = NULL;
+ char **spn_array = NULL;
+ size_t num_spns = 0;
+ bool ok = false;
+ ADS_STATUS status;
+
+ frame = talloc_stackframe();
+ status = ads_get_service_principal_names(frame,
+ ads,
+ machine_name,
+ &spn_array,
+ &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ d_printf("Registered SPNs for %s\n", machine_name);
+ for (i = 0; i < num_spns; i++) {
+ d_printf("\t%s\n", spn_array[i]);
+ }
+
+ ok = true;
+done:
+ TALLOC_FREE(frame);
+ return ok;
+}
+
+/* returns true if spn exists in spn_array (match is NOT case-sensitive) */
+static bool find_spn_in_spnlist(TALLOC_CTX *ctx,
+ const char *spn,
+ char **spn_array,
+ size_t num_spns)
+{
+ char *lc_spn = NULL;
+ size_t i = 0;
+
+ lc_spn = strlower_talloc(ctx, spn);
+ if (lc_spn == NULL) {
+ DBG_ERR("Out of memory, lowercasing %s.\n",
+ spn);
+ return false;
+ }
+
+ for (i = 0; i < num_spns; i++) {
+ char *lc_spn_attr = strlower_talloc(ctx, spn_array[i]);
+ if (lc_spn_attr == NULL) {
+ DBG_ERR("Out of memory, lowercasing %s.\n",
+ spn_array[i]);
+ return false;
+ }
+
+ if (strequal(lc_spn, lc_spn_attr)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ads_setspn_add(ADS_STRUCT *ads, const char *machine_name, const char * spn)
+{
+ bool ret = false;
+ TALLOC_CTX *frame = NULL;
+ ADS_STATUS status;
+ struct spn_struct *spn_struct = NULL;
+ const char *spns[2] = {NULL, NULL};
+ char **existing_spns = NULL;
+ size_t num_spns = 0;
+ bool found = false;
+
+ frame = talloc_stackframe();
+ spns[0] = spn;
+ spn_struct = parse_spn(frame, spn);
+ if (spn_struct == NULL) {
+ goto done;
+ }
+
+ status = ads_get_service_principal_names(frame,
+ ads,
+ machine_name,
+ &existing_spns,
+ &num_spns);
+
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ found = find_spn_in_spnlist(frame, spn, existing_spns, num_spns);
+ if (found) {
+ d_printf("Duplicate SPN found, aborting operation.\n");
+ goto done;
+ }
+
+ d_printf("Registering SPN %s for object %s\n", spn, machine_name);
+ status = ads_add_service_principal_names(ads, machine_name, spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ ret = true;
+ d_printf("Updated object\n");
+done:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+bool ads_setspn_delete(ADS_STRUCT *ads,
+ const char *machine_name,
+ const char * spn)
+{
+ size_t i = 0, j = 0;
+ TALLOC_CTX *frame = NULL;
+ char **spn_array = NULL;
+ const char **new_spn_array = NULL;
+ char *lc_spn = NULL;
+ size_t num_spns = 0;
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+ bool ok = false;
+ LDAPMessage *res = NULL;
+
+ frame = talloc_stackframe();
+
+ lc_spn = strlower_talloc(frame, spn);
+ if (lc_spn == NULL) {
+ DBG_ERR("Out of memory, lowercasing %s.\n", spn);
+ goto done;
+ }
+
+ status = ads_find_machine_acct(ads,
+ &res,
+ machine_name);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = ads_get_service_principal_names(frame,
+ ads,
+ machine_name,
+ &spn_array,
+ &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ new_spn_array = talloc_zero_array(frame, const char*, num_spns + 1);
+ if (!new_spn_array) {
+ DBG_ERR("Out of memory, failed to allocate array.\n");
+ goto done;
+ }
+
+ /*
+ * create new spn list to write to object (excluding the spn to
+ * be deleted).
+ */
+ for (i = 0, j = 0; i < num_spns; i++) {
+ /*
+ * windows setspn.exe deletes matching spn in a case
+ * insensitive way.
+ */
+ char *lc_spn_attr = strlower_talloc(frame, spn_array[i]);
+ if (lc_spn_attr == NULL) {
+ DBG_ERR("Out of memory, lowercasing %s.\n",
+ spn_array[i]);
+ goto done;
+ }
+
+ if (!strequal(lc_spn, lc_spn_attr)) {
+ new_spn_array[j++] = spn_array[i];
+ }
+ }
+
+ /* found and removed spn */
+ if (j < num_spns) {
+ char *dn = NULL;
+ mods = ads_init_mods(frame);
+ if (mods == NULL) {
+ goto done;
+ }
+ d_printf("Unregistering SPN %s for %s\n", spn, machine_name);
+ status = ads_mod_strlist(frame, &mods, "servicePrincipalName", new_spn_array);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ dn = ads_get_dn(ads, frame, res);
+ if (dn == NULL ) {
+ goto done;
+ }
+
+ status = ads_gen_mod(ads, dn, mods);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ }
+ d_printf("Updated object\n");
+
+ ok = true;
+done:
+ TALLOC_FREE(frame);
+ return ok;
+}
+
+#endif /* HAVE_ADS */
diff --git a/source3/libads/sasl.c b/source3/libads/sasl.c
new file mode 100644
index 0000000..5ae8b99
--- /dev/null
+++ b/source3/libads/sasl.c
@@ -0,0 +1,855 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads sasl code
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../libcli/auth/spnego.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth_generic.h"
+#include "ads.h"
+#include "smb_krb5.h"
+#include "system/gssapi.h"
+#include "lib/param/loadparm.h"
+#include "krb5_env.h"
+#include "lib/util/asn1.h"
+
+#ifdef HAVE_LDAP
+
+static ADS_STATUS ads_sasl_gensec_wrap(struct ads_saslwrap *wrap,
+ uint8_t *buf, uint32_t len)
+{
+ struct gensec_security *gensec_security =
+ talloc_get_type_abort(wrap->wrap_private_data,
+ struct gensec_security);
+ NTSTATUS nt_status;
+ DATA_BLOB unwrapped, wrapped;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ unwrapped = data_blob_const(buf, len);
+
+ nt_status = gensec_wrap(gensec_security, frame, &unwrapped, &wrapped);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(frame);
+ return ADS_ERROR_NT(nt_status);
+ }
+
+ if ((wrap->out.size - 4) < wrapped.length) {
+ TALLOC_FREE(frame);
+ return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+ }
+
+ /* copy the wrapped blob to the right location */
+ memcpy(wrap->out.buf + 4, wrapped.data, wrapped.length);
+
+ /* set how many bytes must be written to the underlying socket */
+ wrap->out.left = 4 + wrapped.length;
+
+ TALLOC_FREE(frame);
+
+ return ADS_SUCCESS;
+}
+
+static ADS_STATUS ads_sasl_gensec_unwrap(struct ads_saslwrap *wrap)
+{
+ struct gensec_security *gensec_security =
+ talloc_get_type_abort(wrap->wrap_private_data,
+ struct gensec_security);
+ NTSTATUS nt_status;
+ DATA_BLOB unwrapped, wrapped;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ wrapped = data_blob_const(wrap->in.buf + 4, wrap->in.ofs - 4);
+
+ nt_status = gensec_unwrap(gensec_security, frame, &wrapped, &unwrapped);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(frame);
+ return ADS_ERROR_NT(nt_status);
+ }
+
+ if (wrapped.length < unwrapped.length) {
+ TALLOC_FREE(frame);
+ return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+ }
+
+ /* copy the wrapped blob to the right location */
+ memcpy(wrap->in.buf + 4, unwrapped.data, unwrapped.length);
+
+ /* set how many bytes must be written to the underlying socket */
+ wrap->in.left = unwrapped.length;
+ wrap->in.ofs = 4;
+
+ TALLOC_FREE(frame);
+
+ return ADS_SUCCESS;
+}
+
+static void ads_sasl_gensec_disconnect(struct ads_saslwrap *wrap)
+{
+ struct gensec_security *gensec_security =
+ talloc_get_type_abort(wrap->wrap_private_data,
+ struct gensec_security);
+
+ TALLOC_FREE(gensec_security);
+
+ wrap->wrap_ops = NULL;
+ wrap->wrap_private_data = NULL;
+}
+
+static const struct ads_saslwrap_ops ads_sasl_gensec_ops = {
+ .name = "gensec",
+ .wrap = ads_sasl_gensec_wrap,
+ .unwrap = ads_sasl_gensec_unwrap,
+ .disconnect = ads_sasl_gensec_disconnect
+};
+
+/*
+ perform a LDAP/SASL/SPNEGO/{NTLMSSP,KRB5} bind (just how many layers can
+ we fit on one socket??)
+*/
+static ADS_STATUS ads_sasl_spnego_gensec_bind(ADS_STRUCT *ads,
+ const char *sasl,
+ enum credentials_use_kerberos krb5_state,
+ const char *target_service,
+ const char *target_hostname,
+ const DATA_BLOB server_blob)
+{
+ DATA_BLOB blob_in = data_blob_null;
+ DATA_BLOB blob_out = data_blob_null;
+ int rc;
+ NTSTATUS nt_status;
+ ADS_STATUS status;
+ struct auth_generic_state *auth_generic_state;
+ bool use_spnego_principal = lp_client_use_spnego_principal();
+ const char *sasl_list[] = { sasl, NULL };
+ NTTIME end_nt_time;
+ struct ads_saslwrap *wrap = &ads->ldap_wrap_data;
+
+ nt_status = auth_generic_client_prepare(NULL, &auth_generic_state);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return ADS_ERROR_NT(nt_status);
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_username(auth_generic_state, ads->auth.user_name))) {
+ return ADS_ERROR_NT(nt_status);
+ }
+ if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_domain(auth_generic_state, ads->auth.realm))) {
+ return ADS_ERROR_NT(nt_status);
+ }
+ if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_password(auth_generic_state, ads->auth.password))) {
+ return ADS_ERROR_NT(nt_status);
+ }
+
+ if (server_blob.length == 0) {
+ use_spnego_principal = false;
+ }
+
+ if (krb5_state == CRED_USE_KERBEROS_DISABLED) {
+ use_spnego_principal = false;
+ }
+
+ cli_credentials_set_kerberos_state(auth_generic_state->credentials,
+ krb5_state,
+ CRED_SPECIFIED);
+
+ if (target_service != NULL) {
+ nt_status = gensec_set_target_service(
+ auth_generic_state->gensec_security,
+ target_service);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return ADS_ERROR_NT(nt_status);
+ }
+ }
+
+ if (target_hostname != NULL) {
+ nt_status = gensec_set_target_hostname(
+ auth_generic_state->gensec_security,
+ target_hostname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return ADS_ERROR_NT(nt_status);
+ }
+ }
+
+ if (target_service != NULL && target_hostname != NULL) {
+ use_spnego_principal = false;
+ }
+
+ switch (wrap->wrap_type) {
+ case ADS_SASLWRAP_TYPE_SEAL:
+ gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
+ gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SEAL);
+ break;
+ case ADS_SASLWRAP_TYPE_SIGN:
+ if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
+ gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
+ } else {
+ /*
+ * windows servers are broken with sign only,
+ * so we let the NTLMSSP backend to seal here,
+ * via GENSEC_FEATURE_LDAP_STYLE.
+ */
+ gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
+ gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_LDAP_STYLE);
+ }
+ break;
+ case ADS_SASLWRAP_TYPE_PLAIN:
+ break;
+ }
+
+ nt_status = auth_generic_client_start_by_sasl(auth_generic_state,
+ sasl_list);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return ADS_ERROR_NT(nt_status);
+ }
+
+ rc = LDAP_SASL_BIND_IN_PROGRESS;
+ if (use_spnego_principal) {
+ blob_in = data_blob_dup_talloc(talloc_tos(), server_blob);
+ if (blob_in.length == 0) {
+ TALLOC_FREE(auth_generic_state);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+ } else {
+ blob_in = data_blob_null;
+ }
+ blob_out = data_blob_null;
+
+ while (true) {
+ struct berval cred, *scred = NULL;
+
+ nt_status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), blob_in, &blob_out);
+ data_blob_free(&blob_in);
+ if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)
+ && !NT_STATUS_IS_OK(nt_status))
+ {
+ TALLOC_FREE(auth_generic_state);
+ data_blob_free(&blob_out);
+ return ADS_ERROR_NT(nt_status);
+ }
+
+ if (NT_STATUS_IS_OK(nt_status) && rc == 0 && blob_out.length == 0) {
+ break;
+ }
+
+ cred.bv_val = (char *)blob_out.data;
+ cred.bv_len = blob_out.length;
+ scred = NULL;
+ rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, sasl, &cred, NULL, NULL, &scred);
+ data_blob_free(&blob_out);
+ if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) {
+ if (scred) {
+ ber_bvfree(scred);
+ }
+
+ TALLOC_FREE(auth_generic_state);
+ return ADS_ERROR(rc);
+ }
+ if (scred) {
+ blob_in = data_blob_talloc(talloc_tos(),
+ scred->bv_val,
+ scred->bv_len);
+ if (blob_in.length != scred->bv_len) {
+ ber_bvfree(scred);
+ TALLOC_FREE(auth_generic_state);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+ ber_bvfree(scred);
+ } else {
+ blob_in = data_blob_null;
+ }
+ if (NT_STATUS_IS_OK(nt_status) && rc == 0 && blob_in.length == 0) {
+ break;
+ }
+ }
+
+ data_blob_free(&blob_in);
+ data_blob_free(&blob_out);
+
+ if (wrap->wrap_type >= ADS_SASLWRAP_TYPE_SEAL) {
+ bool ok;
+
+ ok = gensec_have_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SEAL);
+ if (!ok) {
+ DEBUG(0,("The gensec feature sealing request, but unavailable\n"));
+ TALLOC_FREE(auth_generic_state);
+ return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+ }
+
+ ok = gensec_have_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SIGN);
+ if (!ok) {
+ DEBUG(0,("The gensec feature signing request, but unavailable\n"));
+ TALLOC_FREE(auth_generic_state);
+ return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+ }
+
+ } else if (wrap->wrap_type >= ADS_SASLWRAP_TYPE_SIGN) {
+ bool ok;
+
+ ok = gensec_have_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SIGN);
+ if (!ok) {
+ DEBUG(0,("The gensec feature signing request, but unavailable\n"));
+ TALLOC_FREE(auth_generic_state);
+ return ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
+ }
+ }
+
+ ads->auth.tgs_expire = LONG_MAX;
+ end_nt_time = gensec_expire_time(auth_generic_state->gensec_security);
+ if (end_nt_time != GENSEC_EXPIRE_TIME_INFINITY) {
+ struct timeval tv;
+ nttime_to_timeval(&tv, end_nt_time);
+ ads->auth.tgs_expire = tv.tv_sec;
+ }
+
+ if (wrap->wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
+ size_t max_wrapped =
+ gensec_max_wrapped_size(auth_generic_state->gensec_security);
+ wrap->out.max_unwrapped =
+ gensec_max_input_size(auth_generic_state->gensec_security);
+
+ wrap->out.sig_size = max_wrapped - wrap->out.max_unwrapped;
+ /*
+ * Note that we have to truncate this to 0x2C
+ * (taken from a capture with LDAP unbind), as the
+ * signature size is not constant for Kerberos with
+ * arcfour-hmac-md5.
+ */
+ wrap->in.min_wrapped = MIN(wrap->out.sig_size, 0x2C);
+ wrap->in.max_wrapped = ADS_SASL_WRAPPING_IN_MAX_WRAPPED;
+ status = ads_setup_sasl_wrapping(wrap, ads->ldap.ld,
+ &ads_sasl_gensec_ops,
+ auth_generic_state->gensec_security);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
+ ads_errstr(status)));
+ TALLOC_FREE(auth_generic_state);
+ return status;
+ }
+ /* Only keep the gensec_security element around long-term */
+ talloc_steal(NULL, auth_generic_state->gensec_security);
+ }
+ TALLOC_FREE(auth_generic_state);
+
+ return ADS_ERROR(rc);
+}
+
+#ifdef HAVE_KRB5
+struct ads_service_principal {
+ char *service;
+ char *hostname;
+ char *string;
+};
+
+static void ads_free_service_principal(struct ads_service_principal *p)
+{
+ SAFE_FREE(p->service);
+ SAFE_FREE(p->hostname);
+ SAFE_FREE(p->string);
+ ZERO_STRUCTP(p);
+}
+
+static ADS_STATUS ads_guess_target(ADS_STRUCT *ads,
+ char **service,
+ char **hostname,
+ char **principal)
+{
+ ADS_STATUS status = ADS_ERROR(LDAP_NO_MEMORY);
+ char *princ = NULL;
+ TALLOC_CTX *frame;
+ char *server = NULL;
+ char *realm = NULL;
+ int rc;
+
+ frame = talloc_stackframe();
+ if (frame == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (ads->server.realm && ads->server.ldap_server) {
+ server = strlower_talloc(frame, ads->server.ldap_server);
+ if (server == NULL) {
+ goto out;
+ }
+
+ realm = strupper_talloc(frame, ads->server.realm);
+ if (realm == NULL) {
+ goto out;
+ }
+
+ /*
+ * If we got a name which is bigger than a NetBIOS name,
+ * but isn't a FQDN, create one.
+ */
+ if (strlen(server) > 15 && strstr(server, ".") == NULL) {
+ char *dnsdomain;
+
+ dnsdomain = strlower_talloc(frame, ads->server.realm);
+ if (dnsdomain == NULL) {
+ goto out;
+ }
+
+ server = talloc_asprintf(frame,
+ "%s.%s",
+ server, dnsdomain);
+ if (server == NULL) {
+ goto out;
+ }
+ }
+ } else if (ads->config.realm && ads->config.ldap_server_name) {
+ server = strlower_talloc(frame, ads->config.ldap_server_name);
+ if (server == NULL) {
+ goto out;
+ }
+
+ realm = strupper_talloc(frame, ads->config.realm);
+ if (realm == NULL) {
+ goto out;
+ }
+
+ /*
+ * If we got a name which is bigger than a NetBIOS name,
+ * but isn't a FQDN, create one.
+ */
+ if (strlen(server) > 15 && strstr(server, ".") == NULL) {
+ char *dnsdomain;
+
+ dnsdomain = strlower_talloc(frame, ads->server.realm);
+ if (dnsdomain == NULL) {
+ goto out;
+ }
+
+ server = talloc_asprintf(frame,
+ "%s.%s",
+ server, dnsdomain);
+ if (server == NULL) {
+ goto out;
+ }
+ }
+ }
+
+ if (server == NULL || realm == NULL) {
+ goto out;
+ }
+
+ *service = SMB_STRDUP("ldap");
+ if (*service == NULL) {
+ status = ADS_ERROR(LDAP_PARAM_ERROR);
+ goto out;
+ }
+ *hostname = SMB_STRDUP(server);
+ if (*hostname == NULL) {
+ SAFE_FREE(*service);
+ status = ADS_ERROR(LDAP_PARAM_ERROR);
+ goto out;
+ }
+ rc = asprintf(&princ, "ldap/%s@%s", server, realm);
+ if (rc == -1 || princ == NULL) {
+ SAFE_FREE(*service);
+ SAFE_FREE(*hostname);
+ status = ADS_ERROR(LDAP_PARAM_ERROR);
+ goto out;
+ }
+
+ *principal = princ;
+
+ status = ADS_SUCCESS;
+out:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads,
+ struct ads_service_principal *p)
+{
+ ADS_STATUS status;
+
+ ZERO_STRUCTP(p);
+
+ status = ads_guess_target(ads,
+ &p->service,
+ &p->hostname,
+ &p->string);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ return ADS_SUCCESS;
+}
+
+#endif /* HAVE_KRB5 */
+
+/*
+ parse a negTokenInit packet giving a GUID, a list of supported
+ OIDs (the mechanisms) and a principal name string
+*/
+static bool spnego_parse_negTokenInit(TALLOC_CTX *ctx,
+ DATA_BLOB blob,
+ char *OIDs[ASN1_MAX_OIDS],
+ char **principal,
+ DATA_BLOB *secblob)
+{
+ int i;
+ bool ret = false;
+ ASN1_DATA *data;
+
+ for (i = 0; i < ASN1_MAX_OIDS; i++) {
+ OIDs[i] = NULL;
+ }
+
+ if (principal) {
+ *principal = NULL;
+ }
+ if (secblob) {
+ *secblob = data_blob_null;
+ }
+
+ data = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH);
+ if (data == NULL) {
+ return false;
+ }
+
+ if (!asn1_load(data, blob)) goto err;
+
+ if (!asn1_start_tag(data,ASN1_APPLICATION(0))) goto err;
+
+ if (!asn1_check_OID(data,OID_SPNEGO)) goto err;
+
+ /* negTokenInit [0] NegTokenInit */
+ if (!asn1_start_tag(data,ASN1_CONTEXT(0))) goto err;
+ if (!asn1_start_tag(data,ASN1_SEQUENCE(0))) goto err;
+
+ /* mechTypes [0] MechTypeList OPTIONAL */
+
+ /* Not really optional, we depend on this to decide
+ * what mechanisms we have to work with. */
+
+ if (!asn1_start_tag(data,ASN1_CONTEXT(0))) goto err;
+ if (!asn1_start_tag(data,ASN1_SEQUENCE(0))) goto err;
+ for (i=0; asn1_tag_remaining(data) > 0 && i < ASN1_MAX_OIDS-1; i++) {
+ if (!asn1_read_OID(data,ctx, &OIDs[i])) {
+ goto err;
+ }
+ if (asn1_has_error(data)) {
+ goto err;
+ }
+ }
+ OIDs[i] = NULL;
+ if (!asn1_end_tag(data)) goto err;
+ if (!asn1_end_tag(data)) goto err;
+
+ /*
+ Win7 + Live Sign-in Assistant attaches a mechToken
+ ASN1_CONTEXT(2) to the negTokenInit packet
+ which breaks our negotiation if we just assume
+ the next tag is ASN1_CONTEXT(3).
+ */
+
+ if (asn1_peek_tag(data, ASN1_CONTEXT(1))) {
+ uint8_t flags;
+
+ /* reqFlags [1] ContextFlags OPTIONAL */
+ if (!asn1_start_tag(data, ASN1_CONTEXT(1))) goto err;
+ if (!asn1_start_tag(data, ASN1_BIT_STRING)) goto err;
+ while (asn1_tag_remaining(data) > 0) {
+ if (!asn1_read_uint8(data, &flags)) goto err;
+ }
+ if (!asn1_end_tag(data)) goto err;
+ if (!asn1_end_tag(data)) goto err;
+ }
+
+ if (asn1_peek_tag(data, ASN1_CONTEXT(2))) {
+ DATA_BLOB sblob = data_blob_null;
+ /* mechToken [2] OCTET STRING OPTIONAL */
+ if (!asn1_start_tag(data, ASN1_CONTEXT(2))) goto err;
+ if (!asn1_read_OctetString(data, ctx, &sblob)) goto err;
+ if (!asn1_end_tag(data)) {
+ data_blob_free(&sblob);
+ goto err;
+ }
+ if (secblob) {
+ *secblob = sblob;
+ } else {
+ data_blob_free(&sblob);
+ }
+ }
+
+ if (asn1_peek_tag(data, ASN1_CONTEXT(3))) {
+ char *princ = NULL;
+ /* mechListMIC [3] OCTET STRING OPTIONAL */
+ if (!asn1_start_tag(data, ASN1_CONTEXT(3))) goto err;
+ if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) goto err;
+ if (!asn1_start_tag(data, ASN1_CONTEXT(0))) goto err;
+ if (!asn1_read_GeneralString(data, ctx, &princ)) goto err;
+ if (!asn1_end_tag(data)) goto err;
+ if (!asn1_end_tag(data)) goto err;
+ if (!asn1_end_tag(data)) goto err;
+ if (principal) {
+ *principal = princ;
+ } else {
+ TALLOC_FREE(princ);
+ }
+ }
+
+ if (!asn1_end_tag(data)) goto err;
+ if (!asn1_end_tag(data)) goto err;
+
+ if (!asn1_end_tag(data)) goto err;
+
+ ret = !asn1_has_error(data);
+
+ err:
+
+ if (asn1_has_error(data)) {
+ int j;
+ if (principal) {
+ TALLOC_FREE(*principal);
+ }
+ if (secblob) {
+ data_blob_free(secblob);
+ }
+ for(j = 0; j < i && j < ASN1_MAX_OIDS-1; j++) {
+ TALLOC_FREE(OIDs[j]);
+ }
+ }
+
+ asn1_free(data);
+ return ret;
+}
+
+/*
+ this performs a SASL/SPNEGO bind
+*/
+static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct ads_service_principal p = {0};
+ struct berval *scred=NULL;
+ int rc, i;
+ ADS_STATUS status;
+ DATA_BLOB blob = data_blob_null;
+ char *given_principal = NULL;
+ char *OIDs[ASN1_MAX_OIDS];
+#ifdef HAVE_KRB5
+ bool got_kerberos_mechanism = False;
+#endif
+ const char *mech = NULL;
+
+ rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
+
+ if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
+ status = ADS_ERROR(rc);
+ goto done;
+ }
+
+ blob = data_blob(scred->bv_val, scred->bv_len);
+
+ ber_bvfree(scred);
+
+#if 0
+ file_save("sasl_spnego.dat", blob.data, blob.length);
+#endif
+
+ /* the server sent us the first part of the SPNEGO exchange in the negprot
+ reply */
+ if (!spnego_parse_negTokenInit(talloc_tos(), blob, OIDs, &given_principal, NULL) ||
+ OIDs[0] == NULL) {
+ status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
+ goto done;
+ }
+ TALLOC_FREE(given_principal);
+
+ /* make sure the server understands kerberos */
+ for (i=0;OIDs[i];i++) {
+ DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
+#ifdef HAVE_KRB5
+ if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
+ strcmp(OIDs[i], OID_KERBEROS5) == 0) {
+ got_kerberos_mechanism = True;
+ }
+#endif
+ talloc_free(OIDs[i]);
+ }
+
+ status = ads_generate_service_principal(ads, &p);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+#ifdef HAVE_KRB5
+ if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
+ got_kerberos_mechanism)
+ {
+ mech = "KRB5";
+
+ if (ads->auth.password == NULL ||
+ ads->auth.password[0] == '\0')
+ {
+
+ status = ads_sasl_spnego_gensec_bind(ads, "GSS-SPNEGO",
+ CRED_USE_KERBEROS_REQUIRED,
+ p.service, p.hostname,
+ blob);
+ if (ADS_ERR_OK(status)) {
+ ads_free_service_principal(&p);
+ goto done;
+ }
+
+ DEBUG(10,("ads_sasl_spnego_gensec_bind(KRB5) failed with: %s, "
+ "calling kinit\n", ads_errstr(status)));
+ }
+
+ status = ADS_ERROR_KRB5(ads_kinit_password(ads));
+
+ if (ADS_ERR_OK(status)) {
+ status = ads_sasl_spnego_gensec_bind(ads, "GSS-SPNEGO",
+ CRED_USE_KERBEROS_REQUIRED,
+ p.service, p.hostname,
+ blob);
+ if (!ADS_ERR_OK(status)) {
+ DBG_ERR("kinit succeeded but "
+ "SPNEGO bind with Kerberos failed "
+ "for %s/%s - user[%s], realm[%s]: %s\n",
+ p.service, p.hostname,
+ ads->auth.user_name,
+ ads->auth.realm,
+ ads_errstr(status));
+ }
+ }
+
+ /* only fallback to NTLMSSP if allowed */
+ if (ADS_ERR_OK(status) ||
+ !(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
+ goto done;
+ }
+
+ DBG_WARNING("SASL bind with Kerberos failed "
+ "for %s/%s - user[%s], realm[%s]: %s, "
+ "try to fallback to NTLMSSP\n",
+ p.service, p.hostname,
+ ads->auth.user_name,
+ ads->auth.realm,
+ ads_errstr(status));
+ }
+#endif
+
+ /* lets do NTLMSSP ... this has the big advantage that we don't need
+ to sync clocks, and we don't rely on special versions of the krb5
+ library for HMAC_MD4 encryption */
+ mech = "NTLMSSP";
+
+ if (!(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
+ DBG_WARNING("We can't use NTLMSSP, it is not allowed.\n");
+ status = ADS_ERROR_NT(NT_STATUS_NETWORK_CREDENTIAL_CONFLICT);
+ goto done;
+ }
+
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) {
+ DBG_WARNING("We can't fallback to NTLMSSP, weak crypto is"
+ " disallowed.\n");
+ status = ADS_ERROR_NT(NT_STATUS_NETWORK_CREDENTIAL_CONFLICT);
+ goto done;
+ }
+
+ status = ads_sasl_spnego_gensec_bind(ads, "GSS-SPNEGO",
+ CRED_USE_KERBEROS_DISABLED,
+ p.service, p.hostname,
+ data_blob_null);
+done:
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("ads_sasl_spnego_gensec_bind(%s) failed "
+ "for %s/%s with user[%s] realm=[%s]: %s\n", mech,
+ p.service, p.hostname,
+ ads->auth.user_name,
+ ads->auth.realm,
+ ads_errstr(status)));
+ }
+ ads_free_service_principal(&p);
+ TALLOC_FREE(frame);
+ if (blob.data != NULL) {
+ data_blob_free(&blob);
+ }
+ return status;
+}
+
+/* mapping between SASL mechanisms and functions */
+static struct {
+ const char *name;
+ ADS_STATUS (*fn)(ADS_STRUCT *);
+} sasl_mechanisms[] = {
+ {"GSS-SPNEGO", ads_sasl_spnego_bind},
+ {NULL, NULL}
+};
+
+ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
+{
+ const char *attrs[] = {"supportedSASLMechanisms", NULL};
+ char **values;
+ ADS_STATUS status;
+ int i, j;
+ LDAPMessage *res;
+ struct ads_saslwrap *wrap = &ads->ldap_wrap_data;
+
+ /* get a list of supported SASL mechanisms */
+ status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
+ if (!ADS_ERR_OK(status)) return status;
+
+ values = ldap_get_values(ads->ldap.ld, res, "supportedSASLMechanisms");
+
+ if (ads->auth.flags & ADS_AUTH_SASL_SEAL) {
+ wrap->wrap_type = ADS_SASLWRAP_TYPE_SEAL;
+ } else if (ads->auth.flags & ADS_AUTH_SASL_SIGN) {
+ wrap->wrap_type = ADS_SASLWRAP_TYPE_SIGN;
+ } else {
+ wrap->wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
+ }
+
+ /* try our supported mechanisms in order */
+ for (i=0;sasl_mechanisms[i].name;i++) {
+ /* see if the server supports it */
+ for (j=0;values && values[j];j++) {
+ if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
+ DEBUG(4,("Found SASL mechanism %s\n", values[j]));
+retry:
+ status = sasl_mechanisms[i].fn(ads);
+ if (status.error_type == ENUM_ADS_ERROR_LDAP &&
+ status.err.rc == LDAP_STRONG_AUTH_REQUIRED &&
+ wrap->wrap_type == ADS_SASLWRAP_TYPE_PLAIN)
+ {
+ DEBUG(3,("SASL bin got LDAP_STRONG_AUTH_REQUIRED "
+ "retrying with signing enabled\n"));
+ wrap->wrap_type = ADS_SASLWRAP_TYPE_SIGN;
+ goto retry;
+ }
+ ldap_value_free(values);
+ ldap_msgfree(res);
+ return status;
+ }
+ }
+ }
+
+ ldap_value_free(values);
+ ldap_msgfree(res);
+ return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
+}
+
+#endif /* HAVE_LDAP */
+
diff --git a/source3/libads/sasl_wrapping.c b/source3/libads/sasl_wrapping.c
new file mode 100644
index 0000000..7a58765
--- /dev/null
+++ b/source3/libads/sasl_wrapping.c
@@ -0,0 +1,351 @@
+/*
+ Unix SMB/CIFS implementation.
+ ads sasl wrapping code
+ Copyright (C) Stefan Metzmacher 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+
+void ndr_print_ads_saslwrap_struct(struct ndr_print *ndr, const char *name, const struct ads_saslwrap *r)
+{
+ ndr_print_struct(ndr, name, "saslwrap");
+ ndr->depth++;
+ ndr_print_uint16(ndr, "wrap_type", r->wrap_type);
+#ifdef HAVE_LDAP_SASL_WRAPPING
+ ndr_print_ptr(ndr, "sbiod", r->sbiod);
+#endif /* HAVE_LDAP_SASL_WRAPPING */
+ ndr_print_ptr(ndr, "mem_ctx", r->mem_ctx);
+ ndr_print_ptr(ndr, "wrap_ops", r->wrap_ops);
+ ndr_print_ptr(ndr, "wrap_private_data", r->wrap_private_data);
+ ndr_print_struct(ndr, name, "in");
+ ndr->depth++;
+ ndr_print_uint32(ndr, "ofs", r->in.ofs);
+ ndr_print_uint32(ndr, "needed", r->in.needed);
+ ndr_print_uint32(ndr, "left", r->in.left);
+ ndr_print_uint32(ndr, "max_wrapped", r->in.max_wrapped);
+ ndr_print_uint32(ndr, "min_wrapped", r->in.min_wrapped);
+ ndr_print_uint32(ndr, "size", r->in.size);
+ ndr_print_array_uint8(ndr, "buf", r->in.buf, r->in.size);
+ ndr->depth--;
+ ndr_print_struct(ndr, name, "out");
+ ndr->depth++;
+ ndr_print_uint32(ndr, "ofs", r->out.ofs);
+ ndr_print_uint32(ndr, "left", r->out.left);
+ ndr_print_uint32(ndr, "max_unwrapped", r->out.max_unwrapped);
+ ndr_print_uint32(ndr, "sig_size", r->out.sig_size);
+ ndr_print_uint32(ndr, "size", r->out.size);
+ ndr_print_array_uint8(ndr, "buf", r->out.buf, r->out.size);
+ ndr->depth--;
+}
+
+#ifdef HAVE_LDAP_SASL_WRAPPING
+
+static int ads_saslwrap_setup(Sockbuf_IO_Desc *sbiod, void *arg)
+{
+ struct ads_saslwrap *wrap = (struct ads_saslwrap *)arg;
+
+ wrap->sbiod = (struct Sockbuf_IO_Desc *)sbiod;
+
+ sbiod->sbiod_pvt = wrap;
+
+ return 0;
+}
+
+static int ads_saslwrap_remove(Sockbuf_IO_Desc *sbiod)
+{
+ return 0;
+}
+
+static ber_slen_t ads_saslwrap_prepare_inbuf(struct ads_saslwrap *wrap)
+{
+ wrap->in.ofs = 0;
+ wrap->in.needed = 0;
+ wrap->in.left = 0;
+ wrap->in.size = 4 + wrap->in.min_wrapped;
+ wrap->in.buf = talloc_array(wrap->mem_ctx,
+ uint8_t, wrap->in.size);
+ if (!wrap->in.buf) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static ber_slen_t ads_saslwrap_grow_inbuf(struct ads_saslwrap *wrap)
+{
+ if (wrap->in.size == (4 + wrap->in.needed)) {
+ return 0;
+ }
+
+ wrap->in.size = 4 + wrap->in.needed;
+ wrap->in.buf = talloc_realloc(wrap->mem_ctx,
+ wrap->in.buf,
+ uint8_t, wrap->in.size);
+ if (!wrap->in.buf) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ads_saslwrap_shrink_inbuf(struct ads_saslwrap *wrap)
+{
+ talloc_free(wrap->in.buf);
+
+ wrap->in.buf = NULL;
+ wrap->in.size = 0;
+ wrap->in.ofs = 0;
+ wrap->in.needed = 0;
+ wrap->in.left = 0;
+}
+
+static ber_slen_t ads_saslwrap_read(Sockbuf_IO_Desc *sbiod,
+ void *buf, ber_len_t len)
+{
+ struct ads_saslwrap *wrap =
+ (struct ads_saslwrap *)sbiod->sbiod_pvt;
+ ber_slen_t ret;
+
+ /* If ofs < 4 it means we don't have read the length header yet */
+ if (wrap->in.ofs < 4) {
+ ret = ads_saslwrap_prepare_inbuf(wrap);
+ if (ret < 0) return ret;
+
+ ret = LBER_SBIOD_READ_NEXT(sbiod,
+ wrap->in.buf + wrap->in.ofs,
+ 4 - wrap->in.ofs);
+ if (ret <= 0) return ret;
+ wrap->in.ofs += ret;
+
+ if (wrap->in.ofs < 4) goto eagain;
+
+ wrap->in.needed = RIVAL(wrap->in.buf, 0);
+ if (wrap->in.needed > wrap->in.max_wrapped) {
+ errno = EINVAL;
+ return -1;
+ }
+ if (wrap->in.needed < wrap->in.min_wrapped) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = ads_saslwrap_grow_inbuf(wrap);
+ if (ret < 0) return ret;
+ }
+
+ /*
+ * if there's more data needed from the remote end,
+ * we need to read more
+ */
+ if (wrap->in.needed > 0) {
+ ret = LBER_SBIOD_READ_NEXT(sbiod,
+ wrap->in.buf + wrap->in.ofs,
+ wrap->in.needed);
+ if (ret <= 0) return ret;
+ wrap->in.ofs += ret;
+ wrap->in.needed -= ret;
+
+ if (wrap->in.needed > 0) goto eagain;
+ }
+
+ /*
+ * if we have a complete packet and have not yet unwrapped it
+ * we need to call the mech specific unwrap() hook
+ */
+ if (wrap->in.needed == 0 && wrap->in.left == 0) {
+ ADS_STATUS status;
+ status = wrap->wrap_ops->unwrap(wrap);
+ if (!ADS_ERR_OK(status)) {
+ errno = EACCES;
+ return -1;
+ }
+ }
+
+ /*
+ * if we have unwrapped data give it to the caller
+ */
+ if (wrap->in.left > 0) {
+ ret = MIN(wrap->in.left, len);
+ memcpy(buf, wrap->in.buf + wrap->in.ofs, ret);
+ wrap->in.ofs += ret;
+ wrap->in.left -= ret;
+
+ /*
+ * if no more is left shrink the inbuf,
+ * this will trigger reading a new SASL packet
+ * from the remote stream in the next call
+ */
+ if (wrap->in.left == 0) {
+ ads_saslwrap_shrink_inbuf(wrap);
+ }
+
+ return ret;
+ }
+
+ /*
+ * if we don't have anything for the caller yet,
+ * tell him to ask again
+ */
+eagain:
+ errno = EAGAIN;
+ return -1;
+}
+
+static ber_slen_t ads_saslwrap_prepare_outbuf(struct ads_saslwrap *wrap,
+ uint32_t len)
+{
+ wrap->out.ofs = 0;
+ wrap->out.left = 0;
+ wrap->out.size = 4 + wrap->out.sig_size + len;
+ wrap->out.buf = talloc_array(wrap->mem_ctx,
+ uint8_t, wrap->out.size);
+ if (!wrap->out.buf) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ads_saslwrap_shrink_outbuf(struct ads_saslwrap *wrap)
+{
+ talloc_free(wrap->out.buf);
+
+ wrap->out.buf = NULL;
+ wrap->out.size = 0;
+ wrap->out.ofs = 0;
+ wrap->out.left = 0;
+}
+
+static ber_slen_t ads_saslwrap_write(Sockbuf_IO_Desc *sbiod,
+ void *buf, ber_len_t len)
+{
+ struct ads_saslwrap *wrap =
+ (struct ads_saslwrap *)sbiod->sbiod_pvt;
+ ber_slen_t ret, rlen;
+
+ /* if the buffer is empty, we need to wrap in incoming buffer */
+ if (wrap->out.left == 0) {
+ ADS_STATUS status;
+
+ if (len == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ rlen = MIN(len, wrap->out.max_unwrapped);
+
+ ret = ads_saslwrap_prepare_outbuf(wrap, rlen);
+ if (ret < 0) return ret;
+
+ status = wrap->wrap_ops->wrap(wrap, (uint8_t *)buf, rlen);
+ if (!ADS_ERR_OK(status)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ RSIVAL(wrap->out.buf, 0, wrap->out.left - 4);
+ } else {
+ rlen = -1;
+ }
+
+ ret = LBER_SBIOD_WRITE_NEXT(sbiod,
+ wrap->out.buf + wrap->out.ofs,
+ wrap->out.left);
+ if (ret <= 0) return ret;
+ wrap->out.ofs += ret;
+ wrap->out.left -= ret;
+
+ if (wrap->out.left == 0) {
+ ads_saslwrap_shrink_outbuf(wrap);
+ }
+
+ if (rlen > 0) return rlen;
+
+ errno = EAGAIN;
+ return -1;
+}
+
+static int ads_saslwrap_ctrl(Sockbuf_IO_Desc *sbiod, int opt, void *arg)
+{
+ struct ads_saslwrap *wrap =
+ (struct ads_saslwrap *)sbiod->sbiod_pvt;
+ int ret;
+
+ switch (opt) {
+ case LBER_SB_OPT_DATA_READY:
+ if (wrap->in.left > 0) {
+ return 1;
+ }
+ ret = LBER_SBIOD_CTRL_NEXT(sbiod, opt, arg);
+ break;
+ default:
+ ret = LBER_SBIOD_CTRL_NEXT(sbiod, opt, arg);
+ break;
+ }
+
+ return ret;
+}
+
+static int ads_saslwrap_close(Sockbuf_IO_Desc *sbiod)
+{
+ return 0;
+}
+
+static const Sockbuf_IO ads_saslwrap_sockbuf_io = {
+ ads_saslwrap_setup, /* sbi_setup */
+ ads_saslwrap_remove, /* sbi_remove */
+ ads_saslwrap_ctrl, /* sbi_ctrl */
+ ads_saslwrap_read, /* sbi_read */
+ ads_saslwrap_write, /* sbi_write */
+ ads_saslwrap_close /* sbi_close */
+};
+
+ADS_STATUS ads_setup_sasl_wrapping(struct ads_saslwrap *wrap, LDAP *ld,
+ const struct ads_saslwrap_ops *ops,
+ void *private_data)
+{
+ ADS_STATUS status;
+ Sockbuf *sb;
+ Sockbuf_IO *io = discard_const_p(Sockbuf_IO, &ads_saslwrap_sockbuf_io);
+ int rc;
+
+ rc = ldap_get_option(ld, LDAP_OPT_SOCKBUF, &sb);
+ status = ADS_ERROR_LDAP(rc);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ /* setup the real wrapping callbacks */
+ rc = ber_sockbuf_add_io(sb, io, LBER_SBIOD_LEVEL_TRANSPORT, wrap);
+ status = ADS_ERROR_LDAP(rc);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ wrap->wrap_ops = discard_const(ops);
+ wrap->wrap_private_data = private_data;
+
+ return ADS_SUCCESS;
+}
+#else
+ADS_STATUS ads_setup_sasl_wrapping(struct ads_saslwrap *wrap, LDAP *ld,
+ const struct ads_saslwrap_ops *ops,
+ void *private_data)
+{
+ return ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
+}
+#endif /* HAVE_LDAP_SASL_WRAPPING */
diff --git a/source3/libads/sitename_cache.c b/source3/libads/sitename_cache.c
new file mode 100644
index 0000000..4bf2ca7
--- /dev/null
+++ b/source3/libads/sitename_cache.c
@@ -0,0 +1,132 @@
+/*
+ Unix SMB/CIFS implementation.
+ DNS utility library
+ Copyright (C) Gerald (Jerry) Carter 2006.
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libads/sitename_cache.h"
+#include "lib/gencache.h"
+
+/****************************************************************************
+ Store and fetch the AD client sitename.
+****************************************************************************/
+
+#define SITENAME_KEY "AD_SITENAME/DOMAIN/%s"
+
+static char *sitename_key(TALLOC_CTX *mem_ctx, const char *realm)
+{
+ char *keystr = talloc_asprintf_strupper_m(
+ mem_ctx, SITENAME_KEY, realm);
+ return keystr;
+}
+
+
+/****************************************************************************
+ Store the AD client sitename.
+ We store indefinitely as every new CLDAP query will re-write this.
+****************************************************************************/
+
+bool sitename_store(const char *realm, const char *sitename)
+{
+ time_t expire;
+ bool ret = False;
+ char *key;
+
+ if (!realm || (strlen(realm) == 0)) {
+ DEBUG(0,("sitename_store: no realm\n"));
+ return False;
+ }
+
+ key = sitename_key(talloc_tos(), realm);
+
+ if (!sitename || (sitename && !*sitename)) {
+ DEBUG(5,("sitename_store: deleting empty sitename!\n"));
+ ret = gencache_del(key);
+ TALLOC_FREE(key);
+ return ret;
+ }
+
+ expire = get_time_t_max(); /* Store indefinitely. */
+
+ DEBUG(10,("sitename_store: realm = [%s], sitename = [%s], expire = [%u]\n",
+ realm, sitename, (unsigned int)expire ));
+
+ ret = gencache_set( key, sitename, expire );
+ TALLOC_FREE(key);
+ return ret;
+}
+
+/****************************************************************************
+ Fetch the AD client sitename.
+ Caller must free.
+****************************************************************************/
+
+char *sitename_fetch(TALLOC_CTX *mem_ctx, const char *realm)
+{
+ char *sitename = NULL;
+ time_t timeout;
+ bool ret = False;
+ const char *query_realm;
+ char *key;
+
+ if (!realm || (strlen(realm) == 0)) {
+ query_realm = lp_realm();
+ } else {
+ query_realm = realm;
+ }
+
+ key = sitename_key(talloc_tos(), query_realm);
+
+ ret = gencache_get( key, mem_ctx, &sitename, &timeout );
+ TALLOC_FREE(key);
+ if ( !ret ) {
+ DBG_INFO("No stored sitename for realm '%s'\n", query_realm);
+ } else {
+ DBG_INFO("Returning sitename for realm '%s': \"%s\"\n",
+ query_realm, sitename);
+ }
+ return sitename;
+}
+
+/****************************************************************************
+ Did the sitename change ?
+****************************************************************************/
+
+bool stored_sitename_changed(const char *realm, const char *sitename)
+{
+ bool ret = False;
+
+ char *new_sitename;
+
+ if (!realm || (strlen(realm) == 0)) {
+ DEBUG(0,("stored_sitename_changed: no realm\n"));
+ return False;
+ }
+
+ new_sitename = sitename_fetch(talloc_tos(), realm);
+
+ if (sitename && new_sitename && !strequal(sitename, new_sitename)) {
+ ret = True;
+ } else if ((sitename && !new_sitename) ||
+ (!sitename && new_sitename)) {
+ ret = True;
+ }
+ TALLOC_FREE(new_sitename);
+ return ret;
+}
+
diff --git a/source3/libads/sitename_cache.h b/source3/libads/sitename_cache.h
new file mode 100644
index 0000000..9044964
--- /dev/null
+++ b/source3/libads/sitename_cache.h
@@ -0,0 +1,28 @@
+/*
+ Unix SMB/CIFS implementation.
+ DNS utility library
+ Copyright (C) Gerald (Jerry) Carter 2006.
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBADS_SITENAME_CACHE_H_
+#define _LIBADS_SITENAME_CACHE_H_
+
+bool sitename_store(const char *realm, const char *sitename);
+char *sitename_fetch(TALLOC_CTX *mem_ctx, const char *realm);
+bool stored_sitename_changed(const char *realm, const char *sitename);
+
+#endif /* _LIBADS_SITENAME_CACHE_H_ */
diff --git a/source3/libads/util.c b/source3/libads/util.c
new file mode 100644
index 0000000..a1e33fd
--- /dev/null
+++ b/source3/libads/util.c
@@ -0,0 +1,236 @@
+/*
+ Unix SMB/CIFS implementation.
+ krb5 set password implementation
+ Copyright (C) Remus Koos 2001 (remuskoos@yahoo.com)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "secrets.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
+
+#ifdef HAVE_KRB5
+ADS_STATUS ads_change_trust_account_password(ADS_STRUCT *ads, char *host_principal)
+{
+ const char *password = NULL;
+ const char *new_password = NULL;
+ ADS_STATUS ret;
+ const char *domain = lp_workgroup();
+ struct secrets_domain_info1 *info = NULL;
+ struct secrets_domain_info1_change *prev = NULL;
+ const DATA_BLOB *cleartext_blob = NULL;
+ DATA_BLOB pw_blob = data_blob_null;
+ DATA_BLOB new_pw_blob = data_blob_null;
+ NTSTATUS status;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+ int role = lp_server_role();
+ bool ok;
+
+ if (role != ROLE_DOMAIN_MEMBER) {
+ DBG_ERR("Machine account password change only supported on a DOMAIN_MEMBER.\n");
+ return ADS_ERROR_NT(NT_STATUS_INVALID_SERVER_STATE);
+ }
+
+ new_password = trust_pw_new_value(talloc_tos(), SEC_CHAN_WKSTA, SEC_ADS);
+ if (new_password == NULL) {
+ ret = ADS_ERROR_SYSTEM(errno);
+ DEBUG(1,("Failed to generate machine password\n"));
+ return ret;
+ }
+
+ status = secrets_prepare_password_change(domain,
+ ads->auth.kdc_server,
+ new_password,
+ talloc_tos(),
+ &info, &prev);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ADS_ERROR_NT(status);
+ }
+ if (prev != NULL) {
+ status = NT_STATUS_REQUEST_NOT_ACCEPTED;
+ secrets_failed_password_change("localhost",
+ status,
+ NT_STATUS_NOT_COMMITTED,
+ info);
+ return ADS_ERROR_NT(status);
+ }
+
+ cleartext_blob = &info->password->cleartext_blob;
+ ok = convert_string_talloc(talloc_tos(), CH_UTF16MUNGED, CH_UNIX,
+ cleartext_blob->data,
+ cleartext_blob->length,
+ (void **)&pw_blob.data,
+ &pw_blob.length);
+ if (!ok) {
+ status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+ "failed for password of %s - %s\n",
+ domain, nt_errstr(status));
+ return ADS_ERROR_NT(status);
+ }
+ password = (const char *)pw_blob.data;
+
+ cleartext_blob = &info->next_change->password->cleartext_blob;
+ ok = convert_string_talloc(talloc_tos(), CH_UTF16MUNGED, CH_UNIX,
+ cleartext_blob->data,
+ cleartext_blob->length,
+ (void **)&new_pw_blob.data,
+ &new_pw_blob.length);
+ if (!ok) {
+ status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+ "failed for new_password of %s - %s\n",
+ domain, nt_errstr(status));
+ secrets_failed_password_change("localhost",
+ status,
+ NT_STATUS_NOT_COMMITTED,
+ info);
+ return ADS_ERROR_NT(status);
+ }
+ talloc_keep_secret(new_pw_blob.data);
+ new_password = (const char *)new_pw_blob.data;
+
+ ret = kerberos_set_password(ads->auth.kdc_server, host_principal, password, host_principal, new_password, ads->auth.time_offset);
+
+ if (!ADS_ERR_OK(ret)) {
+ status = ads_ntstatus(ret);
+ DBG_ERR("kerberos_set_password(%s, %s) "
+ "failed for new_password of %s - %s\n",
+ ads->auth.kdc_server, host_principal,
+ domain, nt_errstr(status));
+ secrets_failed_password_change(ads->auth.kdc_server,
+ NT_STATUS_NOT_COMMITTED,
+ status,
+ info);
+ return ret;
+ }
+
+ status = secrets_finish_password_change(ads->auth.kdc_server, now, info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("Failed to save machine password\n"));
+ return ADS_ERROR_NT(status);
+ }
+
+ return ADS_SUCCESS;
+}
+#endif
+
+/**
+* @brief Parses windows style SPN service/host:port/servicename
+* serviceclass - A string that identifies the general class of service
+* e.g. 'http'
+* host - A netbios name or fully-qualified DNS name
+* port - An optional TCP or UDP port number
+* servicename - An optional distinguished name, GUID, DNS name or
+* DNS name of an SRV or MX record. (not needed for host
+* based services)
+*
+* @param[in] ctx - Talloc context.
+* @param[in] srvprinc - The service principal
+*
+* @return - struct spn_struct containing the fields parsed or NULL
+* if srvprinc could not be parsed.
+*/
+struct spn_struct *parse_spn(TALLOC_CTX *ctx, const char *srvprinc)
+{
+ struct spn_struct * result = NULL;
+ char *tmp = NULL;
+ char *port_str = NULL;
+ char *host_str = NULL;
+
+ result = talloc_zero(ctx, struct spn_struct);
+ if (result == NULL) {
+ DBG_ERR("Out of memory\n");
+ return NULL;
+ }
+
+ result->serviceclass = talloc_strdup(result, srvprinc);
+ if (result->serviceclass == NULL) {
+ DBG_ERR("Out of memory\n");
+ goto fail;
+ }
+ result->port = -1;
+
+ tmp = strchr_m(result->serviceclass, '/');
+ if (tmp == NULL) {
+ /* illegal */
+ DBG_ERR("Failed to parse spn %s, no host definition\n",
+ srvprinc);
+ goto fail;
+ }
+
+ /* terminate service principal */
+ *tmp = '\0';
+ tmp++;
+ host_str = tmp;
+
+ tmp = strchr_m(host_str, ':');
+ if (tmp != NULL) {
+ *tmp = '\0';
+ tmp++;
+ port_str = tmp;
+ } else {
+ tmp = host_str;
+ }
+
+ tmp = strchr_m(tmp, '/');
+ if (tmp != NULL) {
+ *tmp = '\0';
+ tmp++;
+ result->servicename = tmp;
+ }
+
+ if (strlen(host_str) == 0) {
+ /* illegal */
+ DBG_ERR("Failed to parse spn %s, illegal host definition\n",
+ srvprinc);
+ goto fail;
+ }
+ result->host = host_str;
+
+ if (result->servicename != NULL && (strlen(result->servicename) == 0)) {
+ DBG_ERR("Failed to parse spn %s, empty servicename "
+ "definition\n", srvprinc);
+ goto fail;
+ }
+ if (port_str != NULL) {
+ if (strlen(port_str) == 0) {
+ DBG_ERR("Failed to parse spn %s, empty port "
+ "definition\n", srvprinc);
+ goto fail;
+ }
+ result->port = (int32_t)strtol(port_str, NULL, 10);
+ if (result->port <= 0
+ || result->port > 65535
+ || errno == ERANGE) {
+ DBG_ERR("Failed to parse spn %s, port number "
+ "conversion failed\n", srvprinc);
+ errno = 0;
+ goto fail;
+ }
+ }
+ return result;
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
diff --git a/source3/libgpo/gpext/registry.c b/source3/libgpo/gpext/registry.c
new file mode 100644
index 0000000..ffa1d02
--- /dev/null
+++ b/source3/libgpo/gpext/registry.c
@@ -0,0 +1,440 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Group Policy Support
+ * Copyright (C) Guenther Deschner 2007-2008,2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../libgpo/gpo_ini.h"
+#include "../libgpo/gpo.h"
+#include "libgpo/gpo_proto.h"
+#include "registry.h"
+#include "../librpc/gen_ndr/ndr_preg.h"
+#include "libgpo/gpext/gpext.h"
+
+#define GP_EXT_NAME "registry"
+
+/* more info can be found at:
+ * http://msdn2.microsoft.com/en-us/library/aa374407.aspx */
+
+#define GP_REGPOL_FILE "Registry.pol"
+
+#define GP_REGPOL_FILE_SIGNATURE 0x67655250 /* 'PReg' */
+#define GP_REGPOL_FILE_VERSION 1
+
+static TALLOC_CTX *ctx = NULL;
+
+NTSTATUS gpext_registry_init(TALLOC_CTX *mem_ctx);
+
+/****************************************************************
+****************************************************************/
+
+static bool reg_parse_value(TALLOC_CTX *mem_ctx,
+ const char **value,
+ enum gp_reg_action *action)
+{
+ if (!*value) {
+ *action = GP_REG_ACTION_ADD_KEY;
+ return true;
+ }
+
+ if (strncmp(*value, "**", 2) != 0) {
+ *action = GP_REG_ACTION_ADD_VALUE;
+ return true;
+ }
+
+ if (strnequal(*value, "**DelVals.", 10)) {
+ *action = GP_REG_ACTION_DEL_ALL_VALUES;
+ return true;
+ }
+
+ if (strnequal(*value, "**Del.", 6)) {
+ *value = talloc_strdup(mem_ctx, *value + 6);
+ *action = GP_REG_ACTION_DEL_VALUE;
+ return true;
+ }
+
+ if (strnequal(*value, "**SecureKey", 11)) {
+ if (strnequal(*value, "**SecureKey=1", 13)) {
+ *action = GP_REG_ACTION_SEC_KEY_SET;
+ return true;
+ }
+
+ /*************** not tested from here on ***************/
+ if (strnequal(*value, "**SecureKey=0", 13)) {
+ smb_panic("not supported: **SecureKey=0");
+ *action = GP_REG_ACTION_SEC_KEY_RESET;
+ return true;
+ }
+ DEBUG(0,("unknown: SecureKey: %s\n", *value));
+ smb_panic("not supported SecureKey method");
+ return false;
+ }
+
+ if (strnequal(*value, "**DeleteValues", strlen("**DeleteValues"))) {
+ smb_panic("not supported: **DeleteValues");
+ *action = GP_REG_ACTION_DEL_VALUES;
+ return false;
+ }
+
+ if (strnequal(*value, "**DeleteKeys", strlen("**DeleteKeys"))) {
+ smb_panic("not supported: **DeleteKeys");
+ *action = GP_REG_ACTION_DEL_KEYS;
+ return false;
+ }
+
+ DEBUG(0,("unknown value: %s\n", *value));
+ smb_panic(*value);
+ return false;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool gp_reg_entry_from_file_entry(TALLOC_CTX *mem_ctx,
+ struct preg_entry *r,
+ struct gp_registry_entry **reg_entry)
+{
+ struct registry_value *data = NULL;
+ struct gp_registry_entry *entry = NULL;
+ enum gp_reg_action action = GP_REG_ACTION_NONE;
+ enum ndr_err_code ndr_err;
+
+ ZERO_STRUCTP(*reg_entry);
+
+ data = talloc_zero(mem_ctx, struct registry_value);
+ if (!data)
+ return false;
+
+ data->type = r->type;
+
+ ndr_err = ndr_push_union_blob(
+ &data->data,
+ mem_ctx,
+ &r->data,
+ r->type,
+ (ndr_push_flags_fn_t)ndr_push_winreg_Data);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_winreg_Data failed: %s\n",
+ ndr_errstr(ndr_err));
+ return false;
+ }
+
+ entry = talloc_zero(mem_ctx, struct gp_registry_entry);
+ if (!entry)
+ return false;
+
+ if (!reg_parse_value(mem_ctx, &r->valuename, &action))
+ return false;
+
+ entry->key = talloc_strdup(entry, r->keyname);
+ entry->value = talloc_strdup(entry, r->valuename);
+ entry->data = data;
+ entry->action = action;
+
+ *reg_entry = entry;
+
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS reg_parse_registry(TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ const char *filename,
+ struct gp_registry_entry **entries_p,
+ size_t *num_entries_p)
+{
+ DATA_BLOB blob;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ const char *real_filename = NULL;
+ struct preg_file r;
+ struct gp_registry_entry *entries = NULL;
+ size_t num_entries = 0;
+ int i;
+
+ status = gp_find_file(mem_ctx,
+ flags,
+ filename,
+ GP_REGPOL_FILE,
+ &real_filename);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ blob.data = (uint8_t *)file_load(real_filename, &blob.length, 0, NULL);
+ if (!blob.data) {
+ return NT_STATUS_CANNOT_LOAD_REGISTRY_FILE;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_preg_file);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto out;
+ }
+
+ if (flags & GPO_INFO_FLAG_VERBOSE) {
+ NDR_PRINT_DEBUG(preg_file, &r);
+ }
+
+ if (!strequal(r.header.signature, "PReg")) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (r.header.version != GP_REGPOL_FILE_VERSION) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ for (i=0; i < r.num_entries; i++) {
+
+ struct gp_registry_entry *r_entry = NULL;
+
+ if (!gp_reg_entry_from_file_entry(mem_ctx,
+ &r.entries[i],
+ &r_entry)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (!add_gp_registry_entry_to_array(mem_ctx,
+ r_entry,
+ &entries,
+ &num_entries)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ *entries_p = entries;
+ *num_entries_p = num_entries;
+
+ status = NT_STATUS_OK;
+
+ out:
+ data_blob_free(&blob);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR reg_apply_registry(TALLOC_CTX *mem_ctx,
+ const struct security_token *token,
+ struct registry_key *root_key,
+ uint32_t flags,
+ struct gp_registry_entry *entries,
+ size_t num_entries)
+{
+ struct gp_registry_context *reg_ctx = NULL;
+ WERROR werr;
+ size_t i;
+
+ if (num_entries == 0) {
+ return WERR_OK;
+ }
+
+#if 0
+ if (flags & GPO_LIST_FLAG_MACHINE) {
+ werr = gp_init_reg_ctx(mem_ctx, KEY_HKLM, REG_KEY_WRITE,
+ get_system_token(),
+ &reg_ctx);
+ } else {
+ werr = gp_init_reg_ctx(mem_ctx, KEY_HKCU, REG_KEY_WRITE,
+ token,
+ &reg_ctx);
+ }
+ W_ERROR_NOT_OK_RETURN(werr);
+#endif
+ for (i=0; i<num_entries; i++) {
+
+ /* FIXME: maybe we should check here if we attempt to go beyond
+ * the 4 allowed reg keys */
+
+ werr = reg_apply_registry_entry(mem_ctx, root_key,
+ reg_ctx,
+ &(entries)[i],
+ token, flags);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("failed to apply registry: %s\n",
+ win_errstr(werr)));
+ goto done;
+ }
+ }
+
+done:
+ gp_free_reg_ctx(reg_ctx);
+ return werr;
+}
+
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS registry_process_group_policy(TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ struct registry_key *root_key,
+ const struct security_token *token,
+ const struct GROUP_POLICY_OBJECT *deleted_gpo_list,
+ const struct GROUP_POLICY_OBJECT *changed_gpo_list)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct gp_registry_entry *entries = NULL;
+ size_t num_entries = 0;
+ char *unix_path = NULL;
+ const struct GROUP_POLICY_OBJECT *gpo;
+ char *gpo_cache_path = cache_path(talloc_tos(), GPO_CACHE_DIR);
+ if (gpo_cache_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* implementation of the policy callback function, see
+ * http://msdn.microsoft.com/en-us/library/aa373494%28v=vs.85%29.aspx
+ * for details - gd */
+
+ /* for now do not process the list of deleted group policies
+
+ for (gpo = deleted_gpo_list; gpo; gpo = gpo->next) {
+ }
+
+ */
+
+ for (gpo = changed_gpo_list; gpo; gpo = gpo->next) {
+
+ gpext_debug_header(0, "registry_process_group_policy", flags,
+ gpo, GP_EXT_GUID_REGISTRY, NULL);
+
+ status = gpo_get_unix_path(mem_ctx, gpo_cache_path,
+ gpo, &unix_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_cache_path_free;
+ }
+
+ status = reg_parse_registry(mem_ctx,
+ flags,
+ unix_path,
+ &entries,
+ &num_entries);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("failed to parse registry: %s\n",
+ nt_errstr(status)));
+ goto err_cache_path_free;
+ }
+
+ dump_reg_entries(flags, "READ", entries, num_entries);
+
+ werr = reg_apply_registry(mem_ctx, token, root_key, flags,
+ entries, num_entries);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("failed to apply registry: %s\n",
+ win_errstr(werr)));
+ status = werror_to_ntstatus(werr);
+ goto err_cache_path_free;
+ }
+ }
+ status = NT_STATUS_OK;
+
+err_cache_path_free:
+ talloc_free(gpo_cache_path);
+ talloc_free(entries);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS registry_get_reg_config(TALLOC_CTX *mem_ctx,
+ struct gp_extension_reg_info **reg_info)
+{
+ NTSTATUS status;
+ struct gp_extension_reg_info *info = NULL;
+ struct gp_extension_reg_table table[] = {
+ { "ProcessGroupPolicy", REG_SZ, "registry_process_group_policy" },
+ { NULL, REG_NONE, NULL }
+ };
+
+ info = talloc_zero(mem_ctx, struct gp_extension_reg_info);
+ NT_STATUS_HAVE_NO_MEMORY(info);
+
+ status = gpext_info_add_entry(mem_ctx, GP_EXT_NAME,
+ GP_EXT_GUID_REGISTRY,
+ table, info);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ *reg_info = info;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS registry_initialize(TALLOC_CTX *mem_ctx)
+{
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS registry_shutdown(void)
+{
+ NTSTATUS status;
+
+ status = gpext_unregister_gp_extension(GP_EXT_NAME);
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ TALLOC_FREE(ctx);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static struct gp_extension_methods registry_methods = {
+ .initialize = registry_initialize,
+ .process_group_policy = registry_process_group_policy,
+ .get_reg_config = registry_get_reg_config,
+ .shutdown = registry_shutdown
+};
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS gpext_registry_init(TALLOC_CTX *mem_ctx)
+{
+ NTSTATUS status;
+
+ ctx = talloc_init("gpext_registry_init");
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+
+ status = gpext_register_gp_extension(ctx, SMB_GPEXT_INTERFACE_VERSION,
+ GP_EXT_NAME, GP_EXT_GUID_REGISTRY,
+ &registry_methods);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(ctx);
+ }
+
+ return status;
+}
diff --git a/source3/libgpo/gpext/scripts.c b/source3/libgpo/gpext/scripts.c
new file mode 100644
index 0000000..fe0f139
--- /dev/null
+++ b/source3/libgpo/gpext/scripts.c
@@ -0,0 +1,487 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Group Policy Support
+ * Copyright (C) Guenther Deschner 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../libgpo/gpo_ini.h"
+#include "../libgpo/gpo.h"
+#include "libgpo/gpo_proto.h"
+#include "registry.h"
+#include "registry/reg_api.h"
+#include "../libcli/registry/util_reg.h"
+#include "libgpo/gpext/gpext.h"
+
+#define GP_EXT_NAME "scripts"
+
+#define KEY_GP_SCRIPTS "Software\\Policies\\Microsoft\\Windows\\System\\Scripts"
+
+#define GP_SCRIPTS_INI "Scripts/scripts.ini"
+
+#define GP_SCRIPTS_INI_STARTUP "Startup"
+#define GP_SCRIPTS_INI_SHUTDOWN "Shutdown"
+#define GP_SCRIPTS_INI_LOGON "Logon"
+#define GP_SCRIPTS_INI_LOGOFF "Logoff"
+
+#define GP_SCRIPTS_SECTION_CMDLINE "cmdline"
+#define GP_SCRIPTS_SECTION_PARAMETERS "parameters"
+
+#define GP_SCRIPTS_REG_VAL_SCRIPT "Script"
+#define GP_SCRIPTS_REG_VAL_PARAMETERS "Parameters"
+#define GP_SCRIPTS_REG_VAL_EXECTIME "ExecTime"
+
+NTSTATUS gpext_scripts_init(TALLOC_CTX *mem_ctx);
+
+static TALLOC_CTX *ctx = NULL;
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS scripts_get_reg_config(TALLOC_CTX *mem_ctx,
+ struct gp_extension_reg_info **reg_info)
+{
+ NTSTATUS status;
+ struct gp_extension_reg_info *info = NULL;
+
+ struct gp_extension_reg_table table[] = {
+ { "ProcessGroupPolicy", REG_SZ, "scripts_process_group_policy" },
+ { "NoGPOListChanges", REG_DWORD, "1" },
+ { "NoSlowLink", REG_DWORD, "1" },
+ { "NotifyLinkTransition", REG_DWORD, "1" },
+ { NULL, REG_NONE, NULL },
+ };
+
+ info = talloc_zero(mem_ctx, struct gp_extension_reg_info);
+ NT_STATUS_HAVE_NO_MEMORY(info);
+
+ status = gpext_info_add_entry(mem_ctx, GP_EXT_NAME,
+ GP_EXT_GUID_SCRIPTS,
+ table, info);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ *reg_info = info;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS generate_gp_registry_entry(TALLOC_CTX *mem_ctx,
+ const char *key,
+ const char *value,
+ uint32_t data_type,
+ DATA_BLOB *blob,
+ enum gp_reg_action action,
+ struct gp_registry_entry **entry_out)
+{
+ struct gp_registry_entry *entry = NULL;
+ struct registry_value *data = NULL;
+
+ entry = talloc_zero(mem_ctx, struct gp_registry_entry);
+ NT_STATUS_HAVE_NO_MEMORY(entry);
+
+ data = talloc_zero(mem_ctx, struct registry_value);
+ NT_STATUS_HAVE_NO_MEMORY(data);
+
+ data->type = data_type;
+ data->data = *blob;
+
+ entry->key = key;
+ entry->data = data;
+ entry->action = action;
+ entry->value = talloc_strdup(mem_ctx, value);
+ NT_STATUS_HAVE_NO_MEMORY(entry->value);
+
+ *entry_out = entry;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS scripts_parse_ini_section(struct gp_inifile_context *ini_ctx,
+ uint32_t flags,
+ const char *section,
+ struct gp_registry_entry **entries,
+ size_t *num_entries)
+{
+ NTSTATUS status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ NTSTATUS result;
+ int i = 0;
+
+ while (1) {
+
+ const char *key = NULL;
+ const char *script = NULL;
+ const char *count = NULL;
+ const char *parameters = NULL;
+ DATA_BLOB blob;
+ bool ok;
+
+ count = talloc_asprintf(ini_ctx->mem_ctx, "%d", i);
+ NT_STATUS_HAVE_NO_MEMORY(count);
+
+ key = talloc_asprintf(ini_ctx->mem_ctx, "%s:%s%s",
+ section, count,
+ GP_SCRIPTS_SECTION_CMDLINE);
+ NT_STATUS_HAVE_NO_MEMORY(key);
+
+ result = gp_inifile_getstring(ini_ctx, key, &script);
+ if (!NT_STATUS_IS_OK(result)) {
+ break;
+ }
+
+ key = talloc_asprintf(ini_ctx->mem_ctx, "%s:%s%s",
+ section, count,
+ GP_SCRIPTS_SECTION_PARAMETERS);
+ NT_STATUS_HAVE_NO_MEMORY(key);
+
+ result = gp_inifile_getstring(ini_ctx, key, &parameters);
+ if (!NT_STATUS_IS_OK(result)) {
+ break;
+ }
+
+ {
+ struct gp_registry_entry *entry = NULL;
+
+ ok = push_reg_sz(ini_ctx->mem_ctx, &blob, script);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = generate_gp_registry_entry(ini_ctx->mem_ctx,
+ count,
+ GP_SCRIPTS_REG_VAL_SCRIPT,
+ REG_SZ,
+ &blob,
+ GP_REG_ACTION_ADD_VALUE,
+ &entry);
+ NT_STATUS_NOT_OK_RETURN(status);
+ if (!add_gp_registry_entry_to_array(ini_ctx->mem_ctx,
+ entry,
+ entries,
+ num_entries)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ {
+ struct gp_registry_entry *entry = NULL;
+
+ ok = push_reg_sz(ini_ctx->mem_ctx, &blob, parameters);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = generate_gp_registry_entry(ini_ctx->mem_ctx,
+ count,
+ GP_SCRIPTS_REG_VAL_PARAMETERS,
+ REG_SZ,
+ &blob,
+ GP_REG_ACTION_ADD_VALUE,
+ &entry);
+ NT_STATUS_NOT_OK_RETURN(status);
+ if (!add_gp_registry_entry_to_array(ini_ctx->mem_ctx,
+ entry,
+ entries,
+ num_entries)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ {
+ struct gp_registry_entry *entry = NULL;
+
+ blob = data_blob_talloc_zero(ini_ctx->mem_ctx, 8);
+
+ status = generate_gp_registry_entry(ini_ctx->mem_ctx,
+ count,
+ GP_SCRIPTS_REG_VAL_EXECTIME,
+ REG_QWORD,
+ &blob,
+ GP_REG_ACTION_ADD_VALUE,
+ &entry);
+ NT_STATUS_NOT_OK_RETURN(status);
+ if (!add_gp_registry_entry_to_array(ini_ctx->mem_ctx,
+ entry,
+ entries,
+ num_entries)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ status = NT_STATUS_OK;
+ i++;
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR scripts_store_reg_gpovals(TALLOC_CTX *mem_ctx,
+ struct registry_key *key,
+ const struct GROUP_POLICY_OBJECT *gpo)
+{
+ WERROR werr;
+
+ if (!key || !gpo) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = gp_store_reg_val_sz(mem_ctx, key, "DisplayName",
+ gpo->display_name);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ werr = gp_store_reg_val_sz(mem_ctx, key, "FileSysPath",
+ gpo->file_sys_path);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ werr = gp_store_reg_val_sz(mem_ctx, key, "GPO-ID",
+ gpo->ds_path);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ werr = gp_store_reg_val_sz(mem_ctx, key, "GPOName",
+ gpo->name);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ werr = gp_store_reg_val_sz(mem_ctx, key, "SOM-ID",
+ gpo->link);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR scripts_apply(TALLOC_CTX *mem_ctx,
+ const struct security_token *token,
+ struct registry_key *root_key,
+ uint32_t flags,
+ const char *section,
+ const struct GROUP_POLICY_OBJECT *gpo,
+ struct gp_registry_entry *entries,
+ size_t num_entries)
+{
+ struct gp_registry_context *reg_ctx = NULL;
+ WERROR werr;
+ size_t i;
+ const char *keystr = NULL;
+ int count = 0;
+
+ if (num_entries == 0) {
+ return WERR_OK;
+ }
+
+#if 0
+ if (flags & GPO_INFO_FLAG_MACHINE) {
+ struct security_token *tmp_token;
+
+ tmp_token = registry_create_system_token(mem_ctx);
+ W_ERROR_HAVE_NO_MEMORY(tmp_token);
+
+ werr = gp_init_reg_ctx(mem_ctx, KEY_HKLM, REG_KEY_WRITE,
+ tmp_token,
+ &reg_ctx);
+ } else {
+ werr = gp_init_reg_ctx(mem_ctx, KEY_HKCU, REG_KEY_WRITE,
+ token,
+ &reg_ctx);
+ }
+ W_ERROR_NOT_OK_RETURN(werr);
+#endif
+
+ keystr = talloc_asprintf(mem_ctx, "%s\\%s\\%d", KEY_GP_SCRIPTS,
+ section, count++);
+ W_ERROR_HAVE_NO_MEMORY(keystr);
+
+ reg_deletekey_recursive(root_key, keystr);
+
+ werr = gp_store_reg_subkey(mem_ctx, keystr,
+ root_key, &root_key);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = scripts_store_reg_gpovals(mem_ctx, root_key, gpo);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ for (i=0; i<num_entries; i++) {
+
+ werr = reg_apply_registry_entry(mem_ctx, root_key, reg_ctx,
+ &(entries)[i],
+ token, flags);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("failed to apply registry: %s\n",
+ win_errstr(werr)));
+ goto done;
+ }
+ }
+
+ done:
+ gp_free_reg_ctx(reg_ctx);
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS scripts_process_group_policy(TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ struct registry_key *root_key,
+ const struct security_token *token,
+ const struct GROUP_POLICY_OBJECT *deleted_gpo_list,
+ const struct GROUP_POLICY_OBJECT *changed_gpo_list)
+{
+ NTSTATUS status;
+ WERROR werr;
+ int i = 0;
+ char *unix_path = NULL;
+ struct gp_inifile_context *ini_ctx = NULL;
+ struct gp_registry_entry *entries = NULL;
+ size_t num_entries = 0;
+ const char *list[] = {
+ GP_SCRIPTS_INI_STARTUP,
+ GP_SCRIPTS_INI_SHUTDOWN,
+ GP_SCRIPTS_INI_LOGON,
+ GP_SCRIPTS_INI_LOGOFF
+ };
+ const struct GROUP_POLICY_OBJECT *gpo;
+ char *gpo_cache_path = cache_path(talloc_tos(), GPO_CACHE_DIR);
+ if (gpo_cache_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* implementation of the policy callback function, see
+ * http://msdn.microsoft.com/en-us/library/aa373494%28v=vs.85%29.aspx
+ * for details - gd */
+
+ /* for now do not process the list of deleted group policies
+
+ for (gpo = deleted_gpo_list; gpo; gpo = gpo->next) {
+ }
+
+ */
+
+ for (gpo = changed_gpo_list; gpo; gpo = gpo->next) {
+
+ gpext_debug_header(0, "scripts_process_group_policy", flags,
+ gpo, GP_EXT_GUID_SCRIPTS, NULL);
+
+ status = gpo_get_unix_path(mem_ctx, gpo_cache_path,
+ gpo, &unix_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_cache_path_free;
+ }
+
+ status = gp_inifile_init_context(mem_ctx, flags, unix_path,
+ GP_SCRIPTS_INI, &ini_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_cache_path_free;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(list); i++) {
+
+ TALLOC_FREE(entries);
+ num_entries = 0;
+
+ status = scripts_parse_ini_section(ini_ctx, flags, list[i],
+ &entries, &num_entries);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ continue;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(ini_ctx);
+ goto err_cache_path_free;
+ }
+
+ dump_reg_entries(flags, "READ", entries, num_entries);
+
+ werr = scripts_apply(ini_ctx->mem_ctx, token, root_key,
+ flags, list[i], gpo, entries, num_entries);
+ if (!W_ERROR_IS_OK(werr)) {
+ continue; /* FIXME: finally fix storing empty strings and REG_QWORD! */
+ }
+ }
+
+ TALLOC_FREE(ini_ctx);
+ }
+ status = NT_STATUS_OK;
+
+err_cache_path_free:
+ talloc_free(gpo_cache_path);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS scripts_initialize(TALLOC_CTX *mem_ctx)
+{
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS scripts_shutdown(void)
+{
+ NTSTATUS status;
+
+ status = gpext_unregister_gp_extension(GP_EXT_NAME);
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ TALLOC_FREE(ctx);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static struct gp_extension_methods scripts_methods = {
+ .initialize = scripts_initialize,
+ .process_group_policy = scripts_process_group_policy,
+ .get_reg_config = scripts_get_reg_config,
+ .shutdown = scripts_shutdown
+};
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS gpext_scripts_init(TALLOC_CTX *mem_ctx)
+{
+ NTSTATUS status;
+
+ ctx = talloc_init("gpext_scripts_init");
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+
+ status = gpext_register_gp_extension(ctx, SMB_GPEXT_INTERFACE_VERSION,
+ GP_EXT_NAME, GP_EXT_GUID_SCRIPTS,
+ &scripts_methods);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(ctx);
+ }
+
+ return status;
+}
diff --git a/source3/libgpo/gpext/security.c b/source3/libgpo/gpext/security.c
new file mode 100644
index 0000000..a915eec
--- /dev/null
+++ b/source3/libgpo/gpext/security.c
@@ -0,0 +1,297 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Group Policy Support
+ * Copyright (C) Guenther Deschner 2005-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../libgpo/gpo_ini.h"
+#include "../libgpo/gpo.h"
+#include "libgpo/gpo_proto.h"
+#include "libgpo/gpext/gpext.h"
+
+#define GP_EXT_NAME "security"
+
+#define GPTTMPL_UNIX_PATH "Microsoft/Windows NT/SecEdit/GptTmpl.inf"
+
+#define GPTTMPL_SECTION_UNICODE "Unicode"
+#define GPTTMPL_SECTION_VERSION "Version"
+
+#define GPTTMPL_SECTION_REGISTRY_VALUES "Registry Values"
+#define GPTTMPL_SECTION_SYSTEM_ACCESS "System Access"
+#define GPTTMPL_SECTION_KERBEROS_POLICY "Kerberos Policy"
+#define GPTTMPL_SECTION_EVENT_AUDIT "Event Audit"
+#define GPTTMPL_SECTION_PRIVILEGE_RIGHTS "Privilege Rights"
+#define GPTTMPL_SECTION_APPLICATION_LOG "Application Log"
+#define GPTTMPL_SECTION_SECURITY_LOG "Security Log"
+#define GPTTMPL_SECTION_SYSTEM_LOG "System Log"
+#define GPTTMPL_SECTION_GROUP_MEMBERSHIP "Group Membership"
+#define GPTTMPL_SECTION_FILE_SECURITY "File Security"
+#define GPTTMPL_SECTION_SERVICE_GENERAL_SETTING "Service General Setting"
+
+NTSTATUS gpext_security_init(TALLOC_CTX *mem_ctx);
+
+static TALLOC_CTX *ctx = NULL;
+
+struct gpttmpl_table {
+ const char *section;
+ const char *parameter;
+ enum winreg_Type type;
+};
+
+/****************************************************************
+ parse the Version section from gpttmpl file
+****************************************************************/
+
+#define GPTTMPL_PARAMETER_REVISION "Revision"
+#define GPTTMPL_PARAMETER_SIGNATURE "signature"
+#define GPTTMPL_VALUE_CHICAGO "\"$CHICAGO$\"" /* whatever this is good for... */
+#define GPTTMPL_PARAMETER_UNICODE "Unicode"
+
+static NTSTATUS gpttmpl_parse_header(struct gp_inifile_context *ini_ctx,
+ uint32_t *version_out)
+{
+ const char *signature = NULL;
+ NTSTATUS result;
+ int version;
+ bool is_unicode = false;
+
+ if (!ini_ctx) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ result = gp_inifile_getstring(ini_ctx, GPTTMPL_SECTION_VERSION
+ ":"GPTTMPL_PARAMETER_SIGNATURE, &signature);
+ if (!NT_STATUS_IS_OK(result)) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (!strequal(signature, GPTTMPL_VALUE_CHICAGO)) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ result = gp_inifile_getint(ini_ctx, GPTTMPL_SECTION_VERSION
+ ":"GPTTMPL_PARAMETER_REVISION, &version);
+ if (!NT_STATUS_IS_OK(result)) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (version_out) {
+ *version_out = version;
+ }
+
+ result = gp_inifile_getbool(ini_ctx, GPTTMPL_SECTION_UNICODE
+ ":"GPTTMPL_PARAMETER_UNICODE, &is_unicode);
+ if (!NT_STATUS_IS_OK(result) || !is_unicode) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS gpttmpl_init_context(TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ const char *unix_path,
+ struct gp_inifile_context **ini_ctx)
+{
+ NTSTATUS status;
+ uint32_t version;
+ struct gp_inifile_context *tmp_ctx = NULL;
+
+ status = gp_inifile_init_context(mem_ctx, flags, unix_path,
+ GPTTMPL_UNIX_PATH, &tmp_ctx);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ status = gpttmpl_parse_header(tmp_ctx, &version);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("gpttmpl_init_context: failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ *ini_ctx = tmp_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS gpttmpl_process(struct gp_inifile_context *ini_ctx,
+ struct registry_key *root_key,
+ uint32_t flags)
+{
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS security_process_group_policy(TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ struct registry_key *root_key,
+ const struct security_token *token,
+ const struct GROUP_POLICY_OBJECT *deleted_gpo_list,
+ const struct GROUP_POLICY_OBJECT *changed_gpo_list)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ char *unix_path = NULL;
+ struct gp_inifile_context *ini_ctx = NULL;
+ const struct GROUP_POLICY_OBJECT *gpo;
+ char *gpo_cache_path = cache_path(talloc_tos(), GPO_CACHE_DIR);
+ if (gpo_cache_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* implementation of the policy callback function, see
+ * http://msdn.microsoft.com/en-us/library/aa373494%28v=vs.85%29.aspx
+ * for details - gd */
+
+ /* for now do not process the list of deleted group policies
+
+ for (gpo = deleted_gpo_list; gpo; gpo = gpo->next) {
+ }
+
+ */
+
+ for (gpo = changed_gpo_list; gpo; gpo = gpo->next) {
+
+ gpext_debug_header(0, "security_process_group_policy", flags,
+ gpo, GP_EXT_GUID_SECURITY, NULL);
+
+ /* this handler processes the gpttmpl files and merge output to the
+ * registry */
+
+ status = gpo_get_unix_path(mem_ctx, gpo_cache_path,
+ gpo, &unix_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = gpttmpl_init_context(mem_ctx, flags, unix_path,
+ &ini_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = gpttmpl_process(ini_ctx, root_key, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ TALLOC_FREE(ini_ctx);
+ }
+
+ out:
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("security_process_group_policy: %s\n",
+ nt_errstr(status)));
+ }
+ TALLOC_FREE(ini_ctx);
+ talloc_free(gpo_cache_path);
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS security_get_reg_config(TALLOC_CTX *mem_ctx,
+ struct gp_extension_reg_info **reg_info)
+{
+ NTSTATUS status;
+ struct gp_extension_reg_info *info = NULL;
+
+ struct gp_extension_reg_table table[] = {
+ /* FIXME: how can we store the "(Default)" value ??? */
+ /* { "", REG_SZ, "Security" }, */
+ { "ProcessGroupPolicy", REG_SZ, "security_process_group_policy" },
+ { "NoUserPolicy", REG_DWORD, "1" },
+ { "ExtensionDebugLevel", REG_DWORD, "1" },
+ { NULL, REG_NONE, NULL }
+ };
+
+ info = talloc_zero(mem_ctx, struct gp_extension_reg_info);
+ NT_STATUS_HAVE_NO_MEMORY(info);
+
+ status = gpext_info_add_entry(mem_ctx, GP_EXT_NAME,
+ GP_EXT_GUID_SECURITY,
+ table, info);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ *reg_info = info;
+
+ return NT_STATUS_OK;
+}
+
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS security_initialize(TALLOC_CTX *mem_ctx)
+{
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS security_shutdown(void)
+{
+ NTSTATUS status;
+
+ status = gpext_unregister_gp_extension(GP_EXT_NAME);
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ TALLOC_FREE(ctx);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static struct gp_extension_methods security_methods = {
+ .initialize = security_initialize,
+ .process_group_policy = security_process_group_policy,
+ .get_reg_config = security_get_reg_config,
+ .shutdown = security_shutdown
+};
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS gpext_security_init(TALLOC_CTX *mem_ctx)
+{
+ NTSTATUS status;
+
+ ctx = talloc_init("gpext_security_init");
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+
+ status = gpext_register_gp_extension(ctx, SMB_GPEXT_INTERFACE_VERSION,
+ GP_EXT_NAME, GP_EXT_GUID_SECURITY,
+ &security_methods);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(ctx);
+ }
+
+ return status;
+}
diff --git a/source3/libgpo/gpext/wscript_build b/source3/libgpo/gpext/wscript_build
new file mode 100644
index 0000000..365b420
--- /dev/null
+++ b/source3/libgpo/gpext/wscript_build
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_MODULE('gpext_registry',
+ subsystem='gpext',
+ source='registry.c',
+ deps='NDR_PREG',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('gpext_registry'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('gpext_registry'))
+
+bld.SAMBA3_MODULE('gpext_scripts',
+ subsystem='gpext',
+ source='scripts.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('gpext_scripts'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('gpext_scripts'))
+
+bld.SAMBA3_MODULE('gpext_security',
+ subsystem='gpext',
+ source='security.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('gpext_security'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('gpext_security'))
diff --git a/source3/libnet/libnet_dssync.c b/source3/libnet/libnet_dssync.c
new file mode 100644
index 0000000..7d5d836
--- /dev/null
+++ b/source3/libnet/libnet_dssync.c
@@ -0,0 +1,724 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan (metze) Metzmacher 2005
+ Copyright (C) Guenther Deschner 2008
+ Copyright (C) Michael Adam 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "libnet/libnet_dssync.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/drsuapi/drsuapi.h"
+#include "../librpc/gen_ndr/ndr_drsuapi_c.h"
+
+/****************************************************************
+****************************************************************/
+
+static int libnet_dssync_free_context(struct dssync_context *ctx)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+
+ if (!ctx) {
+ return 0;
+ }
+
+ if (is_valid_policy_hnd(&ctx->bind_handle) && ctx->cli) {
+ b = ctx->cli->binding_handle;
+ dcerpc_drsuapi_DsUnbind(b, ctx, &ctx->bind_handle, &result);
+ }
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS libnet_dssync_init_context(TALLOC_CTX *mem_ctx,
+ struct dssync_context **ctx_p)
+{
+ struct dssync_context *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct dssync_context);
+ NT_STATUS_HAVE_NO_MEMORY(ctx);
+
+ talloc_set_destructor(ctx, libnet_dssync_free_context);
+ ctx->clean_old_entries = false;
+
+ *ctx_p = ctx;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static void parse_obj_identifier(struct drsuapi_DsReplicaObjectIdentifier *id,
+ uint32_t *rid)
+{
+ if (!id || !rid) {
+ return;
+ }
+
+ *rid = 0;
+
+ if (id->sid.num_auths > 0) {
+ *rid = id->sid.sub_auths[id->sid.num_auths - 1];
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_dssync_decrypt_attributes(TALLOC_CTX *mem_ctx,
+ DATA_BLOB *session_key,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ for (; cur; cur = cur->next_object) {
+
+ uint32_t i;
+ uint32_t rid = 0;
+
+ parse_obj_identifier(cur->object.identifier, &rid);
+
+ for (i=0; i < cur->object.attribute_ctr.num_attributes; i++) {
+
+ struct drsuapi_DsReplicaAttribute *attr;
+
+ attr = &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->value_ctr.num_values < 1) {
+ continue;
+ }
+
+ if (!attr->value_ctr.values[0].blob) {
+ continue;
+ }
+
+ drsuapi_decrypt_attribute(mem_ctx,
+ session_key,
+ rid,
+ 0,
+ attr);
+ }
+ }
+}
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_bind(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+ WERROR werr;
+
+ struct GUID bind_guid;
+ struct drsuapi_DsBindInfoCtr bind_info;
+ struct drsuapi_DsBindInfo28 info28;
+ struct dcerpc_binding_handle *b = ctx->cli->binding_handle;
+
+ ZERO_STRUCT(info28);
+
+ GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid);
+
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT;
+ info28.site_guid = GUID_zero();
+ info28.pid = 508;
+ info28.repl_epoch = 0;
+
+ bind_info.length = 28;
+ bind_info.info.info28 = info28;
+
+ status = dcerpc_drsuapi_DsBind(b, mem_ctx,
+ &bind_guid,
+ &bind_info,
+ &ctx->bind_handle,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+
+ ZERO_STRUCT(ctx->remote_info28);
+ switch (bind_info.length) {
+ case 24: {
+ struct drsuapi_DsBindInfo24 *info24;
+ info24 = &bind_info.info.info24;
+ ctx->remote_info28.site_guid = info24->site_guid;
+ ctx->remote_info28.supported_extensions = info24->supported_extensions;
+ ctx->remote_info28.pid = info24->pid;
+ ctx->remote_info28.repl_epoch = 0;
+ break;
+ }
+ case 28: {
+ ctx->remote_info28 = bind_info.info.info28;
+ break;
+ }
+ case 32: {
+ struct drsuapi_DsBindInfo32 *info32;
+ info32 = &bind_info.info.info32;
+ ctx->remote_info28.site_guid = info32->site_guid;
+ ctx->remote_info28.supported_extensions = info32->supported_extensions;
+ ctx->remote_info28.pid = info32->pid;
+ ctx->remote_info28.repl_epoch = info32->repl_epoch;
+ break;
+ }
+ case 48: {
+ struct drsuapi_DsBindInfo48 *info48;
+ info48 = &bind_info.info.info48;
+ ctx->remote_info28.site_guid = info48->site_guid;
+ ctx->remote_info28.supported_extensions = info48->supported_extensions;
+ ctx->remote_info28.pid = info48->pid;
+ ctx->remote_info28.repl_epoch = info48->repl_epoch;
+ break;
+ }
+ case 52: {
+ struct drsuapi_DsBindInfo52 *info52;
+ info52 = &bind_info.info.info52;
+ ctx->remote_info28.site_guid = info52->site_guid;
+ ctx->remote_info28.supported_extensions = info52->supported_extensions;
+ ctx->remote_info28.pid = info52->pid;
+ ctx->remote_info28.repl_epoch = info52->repl_epoch;
+ break;
+ }
+ default:
+ DEBUG(1, ("Warning: invalid info length in bind info: %d\n",
+ bind_info.length));
+ break;
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_lookup_nc(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t level = 1;
+ union drsuapi_DsNameRequest req;
+ uint32_t level_out;
+ struct drsuapi_DsNameString names[1];
+ union drsuapi_DsNameCtr ctr;
+ struct dcerpc_binding_handle *b = ctx->cli->binding_handle;
+
+ names[0].str = talloc_asprintf(mem_ctx, "%s\\", ctx->domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(names[0].str);
+
+ req.req1.codepage = 1252; /* german */
+ req.req1.language = 0x00000407; /* german */
+ req.req1.count = 1;
+ req.req1.names = names;
+ req.req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS;
+ req.req1.format_offered = DRSUAPI_DS_NAME_FORMAT_UNKNOWN;
+ req.req1.format_desired = DRSUAPI_DS_NAME_FORMAT_FQDN_1779;
+
+ status = dcerpc_drsuapi_DsCrackNames(b, mem_ctx,
+ &ctx->bind_handle,
+ level,
+ &req,
+ &level_out,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to lookup DN for domain name: %s",
+ get_friendly_nt_error_msg(status));
+ return status;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to lookup DN for domain name: %s",
+ get_friendly_werror_msg(werr));
+ return werror_to_ntstatus(werr);
+ }
+
+ if (ctr.ctr1->count != 1) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (ctr.ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx->nc_dn = talloc_strdup(mem_ctx, ctr.ctr1->array[0].result_name);
+ NT_STATUS_HAVE_NO_MEMORY(ctx->nc_dn);
+
+ if (!ctx->dns_domain_name) {
+ ctx->dns_domain_name = talloc_strdup_upper(mem_ctx,
+ ctr.ctr1->array[0].dns_domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(ctx->dns_domain_name);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_init(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+
+ status = libnet_dssync_bind(mem_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!ctx->nc_dn) {
+ status = libnet_dssync_lookup_nc(mem_ctx, ctx);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_dssync_build_request(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx,
+ const char *dn,
+ struct replUpToDateVectorBlob *utdv,
+ uint32_t *plevel,
+ union drsuapi_DsGetNCChangesRequest *preq)
+{
+ NTSTATUS status;
+ uint32_t count;
+ uint32_t level;
+ union drsuapi_DsGetNCChangesRequest req;
+ enum drsuapi_DsExtendedOperation extended_op;
+ struct drsuapi_DsReplicaObjectIdentifier *nc = NULL;
+ struct drsuapi_DsReplicaCursorCtrEx *cursors = NULL;
+
+ uint32_t replica_flags = DRSUAPI_DRS_WRIT_REP |
+ DRSUAPI_DRS_INIT_SYNC |
+ DRSUAPI_DRS_PER_SYNC |
+ DRSUAPI_DRS_GET_ANC |
+ DRSUAPI_DRS_NEVER_SYNCED;
+
+ ZERO_STRUCT(req);
+
+ if (ctx->remote_info28.supported_extensions
+ & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8)
+ {
+ level = 8;
+ } else {
+ level = 5;
+ }
+
+ nc = talloc_zero(mem_ctx, struct drsuapi_DsReplicaObjectIdentifier);
+ if (!nc) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ nc->dn = dn;
+ nc->guid = GUID_zero();
+ nc->sid = (struct dom_sid) {0};
+
+ if (!ctx->single_object_replication &&
+ !ctx->force_full_replication && utdv)
+ {
+ cursors = talloc_zero(mem_ctx,
+ struct drsuapi_DsReplicaCursorCtrEx);
+ if (!cursors) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ switch (utdv->version) {
+ case 1:
+ cursors->count = utdv->ctr.ctr1.count;
+ cursors->cursors = utdv->ctr.ctr1.cursors;
+ break;
+ case 2:
+ cursors->count = utdv->ctr.ctr2.count;
+ cursors->cursors = talloc_array(cursors,
+ struct drsuapi_DsReplicaCursor,
+ cursors->count);
+ if (!cursors->cursors) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ for (count = 0; count < cursors->count; count++) {
+ cursors->cursors[count].source_dsa_invocation_id =
+ utdv->ctr.ctr2.cursors[count].source_dsa_invocation_id;
+ cursors->cursors[count].highest_usn =
+ utdv->ctr.ctr2.cursors[count].highest_usn;
+ }
+ break;
+ }
+ }
+
+ if (ctx->single_object_replication) {
+ extended_op = DRSUAPI_EXOP_REPL_OBJ;
+ } else {
+ extended_op = DRSUAPI_EXOP_NONE;
+ }
+
+ if (level == 8) {
+ req.req8.naming_context = nc;
+ req.req8.replica_flags = replica_flags;
+ req.req8.max_object_count = 402;
+ req.req8.max_ndr_size = 402116;
+ req.req8.uptodateness_vector = cursors;
+ req.req8.extended_op = extended_op;
+ } else if (level == 5) {
+ req.req5.naming_context = nc;
+ req.req5.replica_flags = replica_flags;
+ req.req5.max_object_count = 402;
+ req.req5.max_ndr_size = 402116;
+ req.req5.uptodateness_vector = cursors;
+ req.req5.extended_op = extended_op;
+ } else {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (plevel) {
+ *plevel = level;
+ }
+
+ if (preq) {
+ *preq = req;
+ }
+
+ return NT_STATUS_OK;
+
+fail:
+ TALLOC_FREE(nc);
+ TALLOC_FREE(cursors);
+ return status;
+}
+
+static NTSTATUS libnet_dssync_getncchanges(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx,
+ uint32_t level,
+ union drsuapi_DsGetNCChangesRequest *req,
+ struct replUpToDateVectorBlob **pnew_utdv)
+{
+ NTSTATUS status;
+ WERROR werr;
+ union drsuapi_DsGetNCChangesCtr ctr;
+ struct drsuapi_DsGetNCChangesCtr1 *ctr1 = NULL;
+ struct drsuapi_DsGetNCChangesCtr6 *ctr6 = NULL;
+ struct replUpToDateVectorBlob *new_utdv = NULL;
+ uint32_t level_out = 0;
+ uint32_t out_level = 0;
+ int y;
+ bool last_query;
+ struct dcerpc_binding_handle *b = ctx->cli->binding_handle;
+
+ if (!ctx->single_object_replication) {
+ new_utdv = talloc_zero(mem_ctx, struct replUpToDateVectorBlob);
+ if (!new_utdv) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ for (y=0, last_query = false; !last_query; y++) {
+ struct drsuapi_DsReplicaObjectListItemEx *first_object = NULL;
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr = NULL;
+ uint32_t linked_attributes_count = 0;
+ struct drsuapi_DsReplicaLinkedAttribute *linked_attributes = NULL;
+
+ if (level == 8) {
+ DEBUG(1,("start[%d] tmp_higest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)req->req8.highwatermark.tmp_highest_usn,
+ (long long)req->req8.highwatermark.highest_usn));
+ } else if (level == 5) {
+ DEBUG(1,("start[%d] tmp_higest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)req->req5.highwatermark.tmp_highest_usn,
+ (long long)req->req5.highwatermark.highest_usn));
+ }
+
+ status = dcerpc_drsuapi_DsGetNCChanges(b, mem_ctx,
+ &ctx->bind_handle,
+ level,
+ req,
+ &level_out,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to get NC Changes: %s",
+ get_friendly_nt_error_msg(status));
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to get NC Changes: %s",
+ get_friendly_werror_msg(werr));
+ goto out;
+ }
+
+ if (level_out == 1) {
+ out_level = 1;
+ ctr1 = &ctr.ctr1;
+ } else if (level_out == 2 && ctr.ctr2.mszip1.ts) {
+ out_level = 1;
+ ctr1 = &ctr.ctr2.mszip1.ts->ctr1;
+ } else if (level_out == 6) {
+ out_level = 6;
+ ctr6 = &ctr.ctr6;
+ } else if (level_out == 7
+ && ctr.ctr7.level == 6
+ && ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_MSZIP
+ && ctr.ctr7.ctr.mszip6.ts) {
+ out_level = 6;
+ ctr6 = &ctr.ctr7.ctr.mszip6.ts->ctr6;
+ } else if (level_out == 7
+ && ctr.ctr7.level == 6
+ && ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_WIN2K3_LZ77_DIRECT2
+ && ctr.ctr7.ctr.xpress6.ts) {
+ out_level = 6;
+ ctr6 = &ctr.ctr7.ctr.xpress6.ts->ctr6;
+ }
+
+ if (out_level == 1) {
+ DEBUG(1,("end[%d] tmp_highest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)ctr1->new_highwatermark.tmp_highest_usn,
+ (long long)ctr1->new_highwatermark.highest_usn));
+
+ first_object = ctr1->first_object;
+ mapping_ctr = &ctr1->mapping_ctr;
+
+ if (ctr1->more_data) {
+ req->req5.highwatermark = ctr1->new_highwatermark;
+ } else {
+ last_query = true;
+ if (ctr1->uptodateness_vector &&
+ !ctx->single_object_replication)
+ {
+ new_utdv->version = 1;
+ new_utdv->ctr.ctr1.count =
+ ctr1->uptodateness_vector->count;
+ new_utdv->ctr.ctr1.cursors =
+ ctr1->uptodateness_vector->cursors;
+ }
+ }
+ } else if (out_level == 6) {
+ DEBUG(1,("end[%d] tmp_highest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)ctr6->new_highwatermark.tmp_highest_usn,
+ (long long)ctr6->new_highwatermark.highest_usn));
+
+ first_object = ctr6->first_object;
+ mapping_ctr = &ctr6->mapping_ctr;
+
+ linked_attributes = ctr6->linked_attributes;
+ linked_attributes_count = ctr6->linked_attributes_count;
+
+ if (ctr6->more_data) {
+ req->req8.highwatermark = ctr6->new_highwatermark;
+ } else {
+ last_query = true;
+ if (ctr6->uptodateness_vector &&
+ !ctx->single_object_replication)
+ {
+ new_utdv->version = 2;
+ new_utdv->ctr.ctr2.count =
+ ctr6->uptodateness_vector->count;
+ new_utdv->ctr.ctr2.cursors =
+ ctr6->uptodateness_vector->cursors;
+ }
+ }
+ }
+
+ status = cli_get_session_key(mem_ctx, ctx->cli, &ctx->session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to get Session Key: %s",
+ nt_errstr(status));
+ goto out;
+ }
+
+ libnet_dssync_decrypt_attributes(mem_ctx,
+ &ctx->session_key,
+ first_object);
+
+ if (ctx->ops->process_objects) {
+ status = ctx->ops->process_objects(ctx, mem_ctx,
+ first_object,
+ mapping_ctr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call processing function: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ if (linked_attributes_count == 0) {
+ continue;
+ }
+
+ if (ctx->ops->process_links) {
+ status = ctx->ops->process_links(ctx, mem_ctx,
+ linked_attributes_count,
+ linked_attributes,
+ mapping_ctr);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call processing function: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+ }
+
+ *pnew_utdv = new_utdv;
+
+out:
+ return status;
+}
+
+static NTSTATUS libnet_dssync_process(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+
+ uint32_t level = 0;
+ union drsuapi_DsGetNCChangesRequest req;
+ struct replUpToDateVectorBlob *old_utdv = NULL;
+ struct replUpToDateVectorBlob *pnew_utdv = NULL;
+ const char **dns;
+ uint32_t dn_count;
+ uint32_t count;
+
+ if (ctx->ops->startup) {
+ status = ctx->ops->startup(ctx, mem_ctx, &old_utdv);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call startup operation: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ if (ctx->single_object_replication && ctx->object_dns) {
+ dns = ctx->object_dns;
+ dn_count = ctx->object_count;
+ } else {
+ dns = &ctx->nc_dn;
+ dn_count = 1;
+ }
+
+ status = NT_STATUS_OK;
+
+ for (count=0; count < dn_count; count++) {
+ status = libnet_dssync_build_request(mem_ctx, ctx,
+ dns[count],
+ old_utdv, &level,
+ &req);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = libnet_dssync_getncchanges(mem_ctx, ctx, level, &req,
+ &pnew_utdv);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!ctx->error_message) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call DsGetNCCHanges: %s",
+ nt_errstr(status));
+ }
+ goto out;
+ }
+ }
+
+ if (ctx->ops->finish) {
+ status = ctx->ops->finish(ctx, mem_ctx, pnew_utdv);
+ if (!NT_STATUS_IS_OK(status)) {
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to call finishing operation: %s",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS libnet_dssync(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx)
+{
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = libnet_dssync_init(tmp_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = libnet_dssync_process(tmp_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ out:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
diff --git a/source3/libnet/libnet_dssync.h b/source3/libnet/libnet_dssync.h
new file mode 100644
index 0000000..d426d8b
--- /dev/null
+++ b/source3/libnet/libnet_dssync.h
@@ -0,0 +1,73 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Support
+ * Copyright (C) Guenther Deschner 2008
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "../librpc/gen_ndr/drsuapi.h"
+#include "../librpc/gen_ndr/drsblobs.h"
+
+struct dssync_context;
+
+struct dssync_ops {
+ NTSTATUS (*startup)(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv);
+ NTSTATUS (*process_objects)(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *objects,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mappings);
+ NTSTATUS (*process_links)(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ uint32_t count,
+ struct drsuapi_DsReplicaLinkedAttribute *links,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mappings);
+ NTSTATUS (*finish)(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv);
+};
+
+struct dssync_context {
+ const char *domain_name;
+ const char *dns_domain_name;
+ struct rpc_pipe_client *cli;
+ const char *nc_dn;
+ bool single_object_replication;
+ bool force_full_replication;
+ bool clean_old_entries;
+ uint32_t object_count;
+ const char **object_dns;
+ struct policy_handle bind_handle;
+ DATA_BLOB session_key;
+ const char *output_filename;
+ struct drsuapi_DsBindInfo28 remote_info28;
+
+ void *private_data;
+
+ const struct dssync_ops *ops;
+
+ char *result_message;
+ char *error_message;
+};
+
+extern const struct dssync_ops libnet_dssync_keytab_ops;
+extern const struct dssync_ops libnet_dssync_passdb_ops;
+
+/* The following definitions come from libnet/libnet_dssync.c */
+
+NTSTATUS libnet_dssync_init_context(TALLOC_CTX *mem_ctx,
+ struct dssync_context **ctx_p);
+NTSTATUS libnet_dssync(TALLOC_CTX *mem_ctx,
+ struct dssync_context *ctx);
diff --git a/source3/libnet/libnet_dssync_keytab.c b/source3/libnet/libnet_dssync_keytab.c
new file mode 100644
index 0000000..41893b4
--- /dev/null
+++ b/source3/libnet/libnet_dssync_keytab.c
@@ -0,0 +1,609 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2008
+ Copyright (C) Michael Adam 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smb_krb5.h"
+#include "libnet/libnet_dssync.h"
+#include "libnet/libnet_keytab.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+
+#if defined(HAVE_ADS)
+
+static NTSTATUS keytab_startup(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv)
+{
+ krb5_error_code ret = 0;
+ struct libnet_keytab_context *keytab_ctx;
+ struct libnet_keytab_entry *entry;
+ struct replUpToDateVectorBlob *old_utdv = NULL;
+ char *principal;
+
+ ret = libnet_keytab_init(mem_ctx, ctx->output_filename, &keytab_ctx);
+ if (ret) {
+ return krb5_to_nt_status(ret);
+ }
+
+ keytab_ctx->dns_domain_name = ctx->dns_domain_name;
+ keytab_ctx->clean_old_entries = ctx->clean_old_entries;
+ ctx->private_data = keytab_ctx;
+
+ principal = talloc_asprintf(mem_ctx, "UTDV/%s@%s",
+ ctx->nc_dn, ctx->dns_domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(principal);
+
+ entry = libnet_keytab_search(keytab_ctx, principal, 0, ENCTYPE_NULL,
+ mem_ctx);
+ if (entry) {
+ enum ndr_err_code ndr_err;
+ old_utdv = talloc(mem_ctx, struct replUpToDateVectorBlob);
+
+ ndr_err = ndr_pull_struct_blob(&entry->password, old_utdv, old_utdv,
+ (ndr_pull_flags_fn_t)ndr_pull_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to pull UpToDateVector: %s",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(replUpToDateVectorBlob, old_utdv);
+ }
+ }
+
+ if (pold_utdv) {
+ *pold_utdv = old_utdv;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS keytab_finish(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ krb5_error_code ret = 0;
+ struct libnet_keytab_context *keytab_ctx =
+ (struct libnet_keytab_context *)ctx->private_data;
+
+ if (new_utdv) {
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(replUpToDateVectorBlob, new_utdv);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, new_utdv,
+ (ndr_push_flags_fn_t)ndr_push_replUpToDateVectorBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to push UpToDateVector: %s",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, keytab_ctx, 0,
+ ctx->nc_dn, "UTDV",
+ ENCTYPE_NULL,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ ret = libnet_keytab_add(keytab_ctx);
+ if (ret) {
+ status = krb5_to_nt_status(ret);
+ ctx->error_message = talloc_asprintf(ctx,
+ "Failed to add entries to keytab %s: %s",
+ keytab_ctx->keytab_name, error_message(ret));
+ goto done;
+ }
+
+ ctx->result_message = talloc_asprintf(ctx,
+ "Vampired %d accounts to keytab %s",
+ keytab_ctx->count,
+ keytab_ctx->keytab_name);
+
+done:
+ TALLOC_FREE(keytab_ctx);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS parse_supplemental_credentials(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *blob,
+ struct package_PrimaryKerberosCtr3 **pkb3,
+ struct package_PrimaryKerberosCtr4 **pkb4)
+{
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ struct supplementalCredentialsBlob scb;
+ struct supplementalCredentialsPackage *scpk = NULL;
+ DATA_BLOB scpk_blob;
+ struct package_PrimaryKerberosBlob *pkb;
+ bool newer_keys = false;
+ uint32_t j;
+
+ ndr_err = ndr_pull_struct_blob_all(blob, mem_ctx, &scb,
+ (ndr_pull_flags_fn_t)ndr_pull_supplementalCredentialsBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+ if ((scb.sub.signature != SUPPLEMENTAL_CREDENTIALS_SIGNATURE)
+ && (scb.sub.num_packages != 0))
+ {
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(supplementalCredentialsBlob, &scb);
+ }
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+ for (j=0; j < scb.sub.num_packages; j++) {
+ if (strcmp("Primary:Kerberos-Newer-Keys",
+ scb.sub.packages[j].name) == 0)
+ {
+ scpk = &scb.sub.packages[j];
+ if (!scpk->data || !scpk->data[0]) {
+ scpk = NULL;
+ continue;
+ }
+ newer_keys = true;
+ break;
+ } else if (strcmp("Primary:Kerberos",
+ scb.sub.packages[j].name) == 0)
+ {
+ /*
+ * grab this but don't break here:
+ * there might still be newer-keys ...
+ */
+ scpk = &scb.sub.packages[j];
+ if (!scpk->data || !scpk->data[0]) {
+ scpk = NULL;
+ }
+ }
+ }
+
+ if (!scpk) {
+ /* no data */
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ scpk_blob = strhex_to_data_blob(mem_ctx, scpk->data);
+ if (!scpk_blob.data) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ pkb = talloc_zero(mem_ctx, struct package_PrimaryKerberosBlob);
+ if (!pkb) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ ndr_err = ndr_pull_struct_blob(&scpk_blob, mem_ctx, pkb,
+ (ndr_pull_flags_fn_t)ndr_pull_package_PrimaryKerberosBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+
+ if (!newer_keys && pkb->version != 3) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (newer_keys && pkb->version != 4) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (pkb->version == 4 && pkb4) {
+ *pkb4 = &pkb->ctr.ctr4;
+ } else if (pkb->version == 3 && pkb3) {
+ *pkb3 = &pkb->ctr.ctr3;
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ return status;
+}
+
+static NTSTATUS parse_object(TALLOC_CTX *mem_ctx,
+ struct libnet_keytab_context *ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ uchar nt_passwd[16];
+ DATA_BLOB *blob;
+ int i = 0;
+ struct drsuapi_DsReplicaAttribute *attr;
+ bool got_pwd = false;
+
+ struct package_PrimaryKerberosCtr3 *pkb3 = NULL;
+ struct package_PrimaryKerberosCtr4 *pkb4 = NULL;
+
+ char *object_dn = NULL;
+ char *upn = NULL;
+ char **spn = NULL;
+ uint32_t num_spns = 0;
+ char *name = NULL;
+ uint32_t kvno = 0;
+ uint32_t uacc = 0;
+ uint32_t sam_type = 0;
+
+ uint32_t pwd_history_len = 0;
+ uint8_t *pwd_history = NULL;
+
+ ZERO_STRUCT(nt_passwd);
+
+ object_dn = talloc_strdup(mem_ctx, cur->object.identifier->dn);
+ if (!object_dn) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(3, ("parsing object '%s'\n", object_dn));
+
+ for (i=0; i < cur->object.attribute_ctr.num_attributes; i++) {
+
+ attr = &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->attid == DRSUAPI_ATTID_servicePrincipalName) {
+ uint32_t count;
+ num_spns = attr->value_ctr.num_values;
+ spn = talloc_array(mem_ctx, char *, num_spns);
+ for (count = 0; count < num_spns; count++) {
+ blob = attr->value_ctr.values[count].blob;
+ pull_string_talloc(spn, NULL, 0,
+ &spn[count],
+ blob->data, blob->length,
+ STR_UNICODE);
+ }
+ }
+
+ if (attr->value_ctr.num_values != 1) {
+ continue;
+ }
+
+ if (!attr->value_ctr.values[0].blob) {
+ continue;
+ }
+
+ blob = attr->value_ctr.values[0].blob;
+
+ switch (attr->attid) {
+ case DRSUAPI_ATTID_unicodePwd:
+
+ if (blob->length != 16) {
+ break;
+ }
+
+ memcpy(&nt_passwd, blob->data, 16);
+ got_pwd = true;
+
+ /* pick the kvno from the meta_data version,
+ * thanks, metze, for explaining this */
+
+ if (!cur->meta_data_ctr) {
+ break;
+ }
+ if (cur->meta_data_ctr->count !=
+ cur->object.attribute_ctr.num_attributes) {
+ break;
+ }
+ kvno = cur->meta_data_ctr->meta_data[i].version;
+ break;
+ case DRSUAPI_ATTID_ntPwdHistory:
+ pwd_history_len = blob->length / 16;
+ pwd_history = blob->data;
+ break;
+ case DRSUAPI_ATTID_userPrincipalName:
+ pull_string_talloc(mem_ctx, NULL, 0, &upn,
+ blob->data, blob->length,
+ STR_UNICODE);
+ break;
+ case DRSUAPI_ATTID_sAMAccountName:
+ pull_string_talloc(mem_ctx, NULL, 0, &name,
+ blob->data, blob->length,
+ STR_UNICODE);
+ break;
+ case DRSUAPI_ATTID_sAMAccountType:
+ sam_type = IVAL(blob->data, 0);
+ break;
+ case DRSUAPI_ATTID_userAccountControl:
+ uacc = IVAL(blob->data, 0);
+ break;
+ case DRSUAPI_ATTID_supplementalCredentials:
+ status = parse_supplemental_credentials(mem_ctx,
+ blob,
+ &pkb3,
+ &pkb4);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("parsing of supplemental "
+ "credentials failed: %s\n",
+ nt_errstr(status)));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!got_pwd) {
+ DEBUG(10, ("no password (unicodePwd) found - skipping.\n"));
+ return NT_STATUS_OK;
+ }
+
+ if (name) {
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, 0, object_dn,
+ "SAMACCOUNTNAME",
+ ENCTYPE_NULL,
+ data_blob_talloc(mem_ctx, name,
+ strlen(name) + 1));
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ /* look into keytab ... */
+ struct libnet_keytab_entry *entry = NULL;
+ char *principal = NULL;
+
+ DEBUG(10, ("looking for SAMACCOUNTNAME/%s@%s in keytayb...\n",
+ object_dn, ctx->dns_domain_name));
+
+ principal = talloc_asprintf(mem_ctx, "%s/%s@%s",
+ "SAMACCOUNTNAME",
+ object_dn,
+ ctx->dns_domain_name);
+ if (!principal) {
+ DEBUG(1, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry = libnet_keytab_search(ctx, principal, 0, ENCTYPE_NULL,
+ mem_ctx);
+ if (entry) {
+ name = (char *)talloc_memdup(mem_ctx,
+ entry->password.data,
+ entry->password.length);
+ if (!name) {
+ DEBUG(1, ("talloc failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ } else {
+ DEBUG(10, ("found name %s\n", name));
+ }
+ TALLOC_FREE(entry);
+ } else {
+ DEBUG(10, ("entry not found\n"));
+ }
+ TALLOC_FREE(principal);
+ }
+
+ if (!name) {
+ DEBUG(10, ("no name (sAMAccountName) found - skipping.\n"));
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(1,("#%02d: %s:%d, ", ctx->count, name, kvno));
+ DEBUGADD(1,("sAMAccountType: 0x%08x, userAccountControl: 0x%08x",
+ sam_type, uacc));
+ if (upn) {
+ DEBUGADD(1,(", upn: %s", upn));
+ }
+ if (num_spns > 0) {
+ DEBUGADD(1, (", spns: ["));
+ for (i = 0; i < num_spns; i++) {
+ DEBUGADD(1, ("%s%s", spn[i],
+ (i+1 == num_spns)?"]":", "));
+ }
+ }
+ DEBUGADD(1,("\n"));
+
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno, name, NULL,
+ ENCTYPE_ARCFOUR_HMAC,
+ data_blob_talloc(mem_ctx, nt_passwd, 16));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* add kerberos keys (if any) */
+
+ if (pkb4) {
+ for (i=0; i < pkb4->num_keys; i++) {
+ if (!pkb4->keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno,
+ name,
+ NULL,
+ pkb4->keys[i].keytype,
+ *pkb4->keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ for (i=0; i < pkb4->num_old_keys; i++) {
+ if (!pkb4->old_keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno - 1,
+ name,
+ NULL,
+ pkb4->old_keys[i].keytype,
+ *pkb4->old_keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ for (i=0; i < pkb4->num_older_keys; i++) {
+ if (!pkb4->older_keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno - 2,
+ name,
+ NULL,
+ pkb4->older_keys[i].keytype,
+ *pkb4->older_keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ }
+
+ if (pkb3) {
+ for (i=0; i < pkb3->num_keys; i++) {
+ if (!pkb3->keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno, name,
+ NULL,
+ pkb3->keys[i].keytype,
+ *pkb3->keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ for (i=0; i < pkb3->num_old_keys; i++) {
+ if (!pkb3->old_keys[i].value) {
+ continue;
+ }
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno - 1,
+ name,
+ NULL,
+ pkb3->old_keys[i].keytype,
+ *pkb3->old_keys[i].value);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ }
+
+ if (kvno < pwd_history_len) {
+ return status;
+ }
+
+ /* add password history */
+
+ /* skip first entry */
+ if (got_pwd) {
+ kvno--;
+ i = 1;
+ } else {
+ i = 0;
+ }
+
+ for (; i<pwd_history_len; i++) {
+ status = libnet_keytab_add_to_keytab_entries(mem_ctx, ctx, kvno--, name, NULL,
+ ENCTYPE_ARCFOUR_HMAC,
+ data_blob_talloc(mem_ctx, &pwd_history[i*16], 16));
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ }
+
+ return status;
+}
+
+static bool dn_is_in_object_list(struct dssync_context *ctx,
+ const char *dn)
+{
+ uint32_t count;
+
+ if (ctx->object_count == 0) {
+ return true;
+ }
+
+ for (count = 0; count < ctx->object_count; count++) {
+ if (strequal(ctx->object_dns[count], dn)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS keytab_process_objects(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct libnet_keytab_context *keytab_ctx =
+ (struct libnet_keytab_context *)ctx->private_data;
+
+ for (; cur; cur = cur->next_object) {
+ /*
+ * When not in single object replication mode,
+ * the object_dn list is used as a positive write filter.
+ */
+ if (!ctx->single_object_replication &&
+ !dn_is_in_object_list(ctx, cur->object.identifier->dn))
+ {
+ continue;
+ }
+
+ status = parse_object(mem_ctx, keytab_ctx, cur);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+#else
+
+static NTSTATUS keytab_startup(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static NTSTATUS keytab_finish(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static NTSTATUS keytab_process_objects(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+#endif /* defined(HAVE_ADS) */
+
+const struct dssync_ops libnet_dssync_keytab_ops = {
+ .startup = keytab_startup,
+ .process_objects = keytab_process_objects,
+ .finish = keytab_finish,
+};
diff --git a/source3/libnet/libnet_dssync_passdb.c b/source3/libnet/libnet_dssync_passdb.c
new file mode 100644
index 0000000..7d5ef64
--- /dev/null
+++ b/source3/libnet/libnet_dssync_passdb.c
@@ -0,0 +1,1955 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Guenther Deschner <gd@samba.org> 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "libnet/libnet_dssync.h"
+#include "../libcli/security/security.h"
+#include "../libds/common/flags.h"
+#include "../librpc/gen_ndr/ndr_drsuapi.h"
+#include "util_tdb.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "../libds/common/flag_mapping.h"
+#include "passdb.h"
+#include "lib/util/base64.h"
+#include "lib/util/string_wrappers.h"
+
+/****************************************************************
+****************************************************************/
+
+struct dssync_passdb {
+ struct pdb_methods *methods;
+ struct db_context *all;
+ struct db_context *aliases;
+ struct db_context *groups;
+};
+
+struct dssync_passdb_obj {
+ struct dssync_passdb_obj *self;
+ uint32_t type;
+ struct drsuapi_DsReplicaObjectListItemEx *cur;
+ TDB_DATA key;
+ TDB_DATA data;
+ struct db_context *members;
+};
+
+struct dssync_passdb_mem {
+ struct dssync_passdb_mem *self;
+ bool active;
+ struct drsuapi_DsReplicaObjectIdentifier3 *cur;
+ struct dssync_passdb_obj *obj;
+ TDB_DATA key;
+ TDB_DATA data;
+};
+
+static NTSTATUS dssync_insert_obj(struct dssync_passdb *pctx,
+ struct db_context *db,
+ struct dssync_passdb_obj *obj)
+{
+ NTSTATUS status;
+ struct db_record *rec;
+ TDB_DATA value;
+
+ rec = dbwrap_fetch_locked(db, talloc_tos(), obj->key);
+ if (rec == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ value = dbwrap_record_get_value(rec);
+ if (value.dsize != 0) {
+ abort();
+ }
+
+ status = dbwrap_record_store(rec, obj->data, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(rec);
+ return status;
+ }
+ TALLOC_FREE(rec);
+ return NT_STATUS_OK;
+}
+
+static struct dssync_passdb_obj *dssync_parse_obj(const TDB_DATA data)
+{
+ struct dssync_passdb_obj *obj;
+
+ if (data.dsize != sizeof(obj)) {
+ return NULL;
+ }
+
+ /*
+ * we need to copy the pointer to avoid alignment problems
+ * on some systems.
+ */
+ memcpy(&obj, data.dptr, sizeof(obj));
+
+ return talloc_get_type_abort(obj, struct dssync_passdb_obj);
+}
+
+static struct dssync_passdb_obj *dssync_search_obj_by_guid(struct dssync_passdb *pctx,
+ struct db_context *db,
+ const struct GUID *guid)
+{
+ struct dssync_passdb_obj *obj;
+ TDB_DATA key;
+ TDB_DATA data;
+ NTSTATUS status;
+
+ key = make_tdb_data((const uint8_t *)(const void *)guid,
+ sizeof(*guid));
+
+ status = dbwrap_fetch(db, talloc_tos(), key, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ obj = dssync_parse_obj(data);
+ return obj;
+}
+
+static NTSTATUS dssync_create_obj(struct dssync_passdb *pctx,
+ struct db_context *db,
+ uint32_t type,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct dssync_passdb_obj **_obj)
+{
+ NTSTATUS status;
+ struct dssync_passdb_obj *obj;
+
+ obj = talloc_zero(pctx, struct dssync_passdb_obj);
+ if (obj == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ obj->self = obj;
+ obj->cur = cur;
+ obj->type = type;
+ obj->key = make_tdb_data((const uint8_t *)(void *)&cur->object.identifier->guid,
+ sizeof(cur->object.identifier->guid));
+ obj->data = make_tdb_data((const uint8_t *)(void *)&obj->self,
+ sizeof(obj->self));
+
+ obj->members = db_open_rbt(obj);
+ if (obj->members == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dssync_insert_obj(pctx, db, obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(obj);
+ return status;
+ }
+ *_obj = obj;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dssync_insert_mem(struct dssync_passdb *pctx,
+ struct dssync_passdb_obj *obj,
+ struct dssync_passdb_mem *mem)
+{
+ NTSTATUS status;
+ struct db_record *rec;
+ TDB_DATA value;
+
+ rec = dbwrap_fetch_locked(obj->members, talloc_tos(), mem->key);
+ if (rec == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ value = dbwrap_record_get_value(rec);
+ if (value.dsize != 0) {
+ abort();
+ }
+
+ status = dbwrap_record_store(rec, mem->data, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(rec);
+ return status;
+ }
+ TALLOC_FREE(rec);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dssync_create_mem(struct dssync_passdb *pctx,
+ struct dssync_passdb_obj *obj,
+ bool active,
+ struct drsuapi_DsReplicaObjectIdentifier3 *cur,
+ struct dssync_passdb_mem **_mem)
+{
+ NTSTATUS status;
+ struct dssync_passdb_mem *mem;
+
+ mem = talloc_zero(pctx, struct dssync_passdb_mem);
+ if (mem == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mem->self = mem;
+ mem->cur = cur;
+ mem->active = active;
+ mem->obj = NULL;
+ mem->key = make_tdb_data((const uint8_t *)(void *)&cur->guid,
+ sizeof(cur->guid));
+ mem->data = make_tdb_data((const uint8_t *)(void *)&mem->self,
+ sizeof(mem->self));
+
+ status = dssync_insert_mem(pctx, obj, mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(obj);
+ return status;
+ }
+ *_mem = mem;
+ return NT_STATUS_OK;
+}
+
+static struct dssync_passdb_mem *dssync_parse_mem(const TDB_DATA data)
+{
+ struct dssync_passdb_mem *mem;
+
+ if (data.dsize != sizeof(mem)) {
+ return NULL;
+ }
+
+ /*
+ * we need to copy the pointer to avoid alignment problems
+ * on some systems.
+ */
+ memcpy(&mem, data.dptr, sizeof(mem));
+
+ return talloc_get_type_abort(mem, struct dssync_passdb_mem);
+}
+
+static NTSTATUS passdb_startup(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob **pold_utdv)
+{
+ NTSTATUS status;
+ struct dssync_passdb *pctx;
+
+ pctx = talloc_zero(mem_ctx, struct dssync_passdb);
+ if (pctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ctx->output_filename) {
+ status = make_pdb_method_name(&pctx->methods, ctx->output_filename);
+ } else {
+ status = make_pdb_method_name(&pctx->methods, lp_passdb_backend());
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ pctx->all = db_open_rbt(pctx);
+ if (pctx->all == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pctx->aliases = db_open_rbt(pctx);
+ if (pctx->aliases == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pctx->groups = db_open_rbt(pctx);
+ if (pctx->groups == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ctx->private_data = pctx;
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+struct dssync_passdb_traverse_amembers {
+ struct dssync_context *ctx;
+ struct dssync_passdb_obj *obj;
+ const char *name;
+ uint32_t idx;
+};
+
+struct dssync_passdb_traverse_aliases {
+ struct dssync_context *ctx;
+ const char *name;
+ uint32_t idx;
+};
+
+static int dssync_passdb_traverse_amembers(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_amembers *state =
+ (struct dssync_passdb_traverse_amembers *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_mem *mem;
+ NTSTATUS status;
+ struct dom_sid alias_sid;
+ struct dom_sid member_sid;
+ struct dom_sid_buf buf1, buf2;
+ const char *member_dn;
+ size_t num_members;
+ size_t i;
+ struct dom_sid *members;
+ bool is_member = false;
+ const char *action;
+ TDB_DATA value;
+
+ state->idx++;
+
+ alias_sid = state->obj->cur->object.identifier->sid;
+
+ value = dbwrap_record_get_value(rec);
+ mem = dssync_parse_mem(value);
+ if (mem == NULL) {
+ return -1;
+ }
+
+ member_sid = mem->cur->sid;
+ member_dn = mem->cur->dn;
+
+ mem->obj = dssync_search_obj_by_guid(pctx, pctx->all, &mem->cur->guid);
+ if (mem->obj == NULL) {
+ DEBUG(0,("alias[%s] member[%s] can't resolve member - ignoring\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ is_null_sid(&member_sid)?
+ dom_sid_str_buf(&member_sid, &buf2):
+ member_dn));
+ return 0;
+ }
+
+ switch (mem->obj->type) {
+ case ATYPE_DISTRIBUTION_LOCAL_GROUP:
+ case ATYPE_DISTRIBUTION_GLOBAL_GROUP:
+ DEBUG(0, ("alias[%s] ignore distribution group [%s]\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ member_dn));
+ return 0;
+ default:
+ break;
+ }
+
+ DEBUG(0,("alias[%s] member[%s]\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ dom_sid_str_buf(&member_sid, &buf2)));
+
+ status = pdb_enum_aliasmem(&alias_sid, talloc_tos(),
+ &members, &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not find current alias members %s - %s\n",
+ dom_sid_str_buf(&alias_sid, &buf1),
+ nt_errstr(status)));
+ return -1;
+ }
+
+ for (i=0; i < num_members; i++) {
+ bool match;
+
+ match = dom_sid_equal(&members[i], &member_sid);
+ if (match) {
+ is_member = true;
+ break;
+ }
+ }
+
+ status = NT_STATUS_OK;
+ action = "none";
+ if (!is_member && mem->active) {
+ action = "add";
+ pdb_add_aliasmem(&alias_sid, &member_sid);
+ } else if (is_member && !mem->active) {
+ action = "delete";
+ pdb_del_aliasmem(&alias_sid, &member_sid);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not %s %s as alias members of %s - %s\n",
+ action,
+ dom_sid_str_buf(&member_sid, &buf1),
+ dom_sid_str_buf(&alias_sid, &buf2),
+ nt_errstr(status)));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dssync_passdb_traverse_aliases(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_aliases *state =
+ (struct dssync_passdb_traverse_aliases *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_traverse_amembers mstate;
+ struct dssync_passdb_obj *obj;
+ TDB_DATA value;
+ NTSTATUS status;
+
+ state->idx++;
+ if (pctx->methods == NULL) {
+ return -1;
+ }
+
+ value = dbwrap_record_get_value(rec);
+ obj = dssync_parse_obj(value);
+ if (obj == NULL) {
+ return -1;
+ }
+
+ ZERO_STRUCT(mstate);
+ mstate.ctx = state->ctx;
+ mstate.name = "members";
+ mstate.obj = obj;
+ status = dbwrap_traverse_read(obj->members,
+ dssync_passdb_traverse_amembers,
+ &mstate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+struct dssync_passdb_traverse_gmembers {
+ struct dssync_context *ctx;
+ struct dssync_passdb_obj *obj;
+ const char *name;
+ uint32_t idx;
+};
+
+struct dssync_passdb_traverse_groups {
+ struct dssync_context *ctx;
+ const char *name;
+ uint32_t idx;
+};
+
+static int dssync_passdb_traverse_gmembers(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_gmembers *state =
+ (struct dssync_passdb_traverse_gmembers *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_mem *mem;
+ NTSTATUS status;
+ char *nt_member = NULL;
+ char **unix_members;
+ struct dom_sid group_sid;
+ struct dom_sid member_sid;
+ struct dom_sid_buf buf1, buf2;
+ struct samu *member = NULL;
+ const char *member_dn = NULL;
+ GROUP_MAP *map;
+ struct group *grp;
+ uint32_t rid;
+ bool is_unix_member = false;
+ TDB_DATA value;
+
+ state->idx++;
+
+ group_sid = state->obj->cur->object.identifier->sid;
+
+ status = dom_sid_split_rid(talloc_tos(), &group_sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ mem = dssync_parse_mem(value);
+ if (mem == NULL) {
+ return -1;
+ }
+
+ member_sid = mem->cur->sid;
+ member_dn = mem->cur->dn;
+
+ mem->obj = dssync_search_obj_by_guid(pctx, pctx->all, &mem->cur->guid);
+ if (mem->obj == NULL) {
+ DEBUG(0,("group[%s] member[%s] can't resolve member - ignoring\n",
+ dom_sid_str_buf(&group_sid, &buf1),
+ is_null_sid(&member_sid)?
+ dom_sid_str_buf(&member_sid, &buf2):
+ member_dn));
+ return 0;
+ }
+
+ member_sid = mem->obj->cur->object.identifier->sid;
+ member_dn = mem->obj->cur->object.identifier->dn;
+
+ switch (mem->obj->type) {
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ case ATYPE_SECURITY_GLOBAL_GROUP:
+ DEBUG(0, ("Group[%s] ignore member group [%s]\n",
+ dom_sid_str_buf(&group_sid, &buf1),
+ dom_sid_str_buf(&member_sid, &buf2)));
+ return 0;
+
+ case ATYPE_DISTRIBUTION_LOCAL_GROUP:
+ case ATYPE_DISTRIBUTION_GLOBAL_GROUP:
+ DEBUG(0, ("Group[%s] ignore distribution group [%s]\n",
+ dom_sid_str_buf(&group_sid, &buf1),
+ member_dn));
+ return 0;
+ default:
+ break;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return -1;
+ }
+
+ if (!get_domain_group_from_sid(group_sid, map)) {
+ DEBUG(0, ("Could not find global group %s\n",
+ dom_sid_str_buf(&group_sid, &buf1)));
+ //return NT_STATUS_NO_SUCH_GROUP;
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ if (!(grp = getgrgid(map->gid))) {
+ DEBUG(0, ("Could not find unix group %lu\n",
+ (unsigned long)map->gid));
+ //return NT_STATUS_NO_SUCH_GROUP;
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ TALLOC_FREE(map);
+
+ DEBUG(0,("Group members of %s: ", grp->gr_name));
+
+ if ( !(member = samu_new(talloc_tos())) ) {
+ //return NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ if (!pdb_getsampwsid(member, &member_sid)) {
+ DEBUG(1, ("Found bogus group member: (member_sid=%s group=%s)\n",
+ dom_sid_str_buf(&member_sid, &buf1),
+ grp->gr_name));
+ TALLOC_FREE(member);
+ return -1;
+ }
+
+ if (pdb_get_group_rid(member) == rid) {
+ DEBUGADD(0,("%s(primary),", pdb_get_username(member)));
+ TALLOC_FREE(member);
+ return -1;
+ }
+
+ DEBUGADD(0,("%s,", pdb_get_username(member)));
+ nt_member = talloc_strdup(talloc_tos(), pdb_get_username(member));
+ TALLOC_FREE(member);
+
+ DEBUGADD(0,("\n"));
+
+ unix_members = grp->gr_mem;
+
+ while (*unix_members) {
+ if (strcmp(*unix_members, nt_member) == 0) {
+ is_unix_member = true;
+ break;
+ }
+ unix_members += 1;
+ }
+
+ if (!is_unix_member && mem->active) {
+ smb_add_user_group(grp->gr_name, nt_member);
+ } else if (is_unix_member && !mem->active) {
+ smb_delete_user_group(grp->gr_name, nt_member);
+ }
+
+ return 0;
+}
+
+static int dssync_passdb_traverse_groups(struct db_record *rec,
+ void *private_data)
+{
+ struct dssync_passdb_traverse_groups *state =
+ (struct dssync_passdb_traverse_groups *)private_data;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(state->ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_traverse_gmembers mstate;
+ struct dssync_passdb_obj *obj;
+ TDB_DATA value;
+ NTSTATUS status;
+
+ state->idx++;
+ if (pctx->methods == NULL) {
+ return -1;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ obj = dssync_parse_obj(value);
+ if (obj == NULL) {
+ return -1;
+ }
+
+ ZERO_STRUCT(mstate);
+ mstate.ctx = state->ctx;
+ mstate.name = "members";
+ mstate.obj = obj;
+ status = dbwrap_traverse_read(obj->members,
+ dssync_passdb_traverse_gmembers,
+ &mstate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static NTSTATUS passdb_finish(struct dssync_context *ctx, TALLOC_CTX *mem_ctx,
+ struct replUpToDateVectorBlob *new_utdv)
+{
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(ctx->private_data,
+ struct dssync_passdb);
+ struct dssync_passdb_traverse_aliases astate;
+ struct dssync_passdb_traverse_groups gstate;
+ NTSTATUS status;
+
+ ZERO_STRUCT(astate);
+ astate.ctx = ctx;
+ astate.name = "aliases";
+ status = dbwrap_traverse_read(pctx->aliases,
+ dssync_passdb_traverse_aliases,
+ &astate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ZERO_STRUCT(gstate);
+ gstate.ctx = ctx;
+ gstate.name = "groups";
+ status = dbwrap_traverse_read(pctx->groups,
+ dssync_passdb_traverse_groups,
+ &gstate, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ TALLOC_FREE(pctx->methods);
+ TALLOC_FREE(pctx);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS smb_create_user(TALLOC_CTX *mem_ctx,
+ uint32_t acct_flags,
+ const char *account,
+ struct passwd **passwd_p)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct passwd *passwd;
+ char *add_script = NULL;
+
+ passwd = Get_Pwnam_alloc(mem_ctx, account);
+ if (passwd) {
+ *passwd_p = passwd;
+ return NT_STATUS_OK;
+ }
+
+ /* Create appropriate user */
+ if (acct_flags & ACB_NORMAL) {
+ add_script = lp_add_user_script(mem_ctx, lp_sub);
+ } else if ( (acct_flags & ACB_WSTRUST) ||
+ (acct_flags & ACB_SVRTRUST) ||
+ (acct_flags & ACB_DOMTRUST) ) {
+ add_script = lp_add_machine_script(mem_ctx, lp_sub);
+ } else {
+ DEBUG(1, ("Unknown user type: %s\n",
+ pdb_encode_acct_ctrl(acct_flags, NEW_PW_FORMAT_SPACE_PADDED_LEN)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (!add_script) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (*add_script) {
+ int add_ret;
+ add_script = talloc_all_string_sub(mem_ctx, add_script,
+ "%u", account);
+ if (!add_script) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ add_ret = smbrun(add_script, NULL, NULL);
+ DEBUG(add_ret ? 0 : 1,("fetch_account: Running the command `%s' "
+ "gave %d\n", add_script, add_ret));
+ if (add_ret == 0) {
+ smb_nscd_flush_user_cache();
+ }
+ }
+
+ /* try and find the possible unix account again */
+ passwd = Get_Pwnam_alloc(mem_ctx, account);
+ if (!passwd) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ *passwd_p = passwd;
+
+ return NT_STATUS_OK;
+}
+
+static struct drsuapi_DsReplicaAttribute *find_drsuapi_attr(
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid)
+{
+ uint32_t i = 0;
+
+ for (i = 0; i < cur->object.attribute_ctr.num_attributes; i++) {
+ struct drsuapi_DsReplicaAttribute *attr;
+
+ attr = &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->attid == attid) {
+ return attr;
+ }
+ }
+
+ return NULL;
+}
+
+static NTSTATUS find_drsuapi_attr_string(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ char ***_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ char **array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, char *, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+ ssize_t ret;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ ret = pull_string_talloc(array, NULL, 0, &array[a],
+ blob->data, blob->length,
+ STR_UNICODE);
+ if (ret == -1) {
+ //TODO
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_int32(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ int32_t **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ int32_t *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, int32_t, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (blob->length != 4) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ array[a] = IVAL(blob->data, 0);
+ }
+
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_blob(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ DATA_BLOB **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ DATA_BLOB *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, DATA_BLOB, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ array[a] = data_blob_talloc(array, blob->data, blob->length);
+ if (array[a].length != blob->length) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_int64(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ int64_t **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ int64_t *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx, int64_t, attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (blob->length != 8) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ array[a] = BVAL(blob->data, 0);
+ }
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_drsuapi_attr_dn(TALLOC_CTX *mem_ctx,
+ const struct drsuapi_DsReplicaObjectListItemEx *cur,
+ uint32_t attid,
+ uint32_t *_count,
+ struct drsuapi_DsReplicaObjectIdentifier3 **_array)
+{
+ struct drsuapi_DsReplicaAttribute *attr;
+ struct drsuapi_DsReplicaObjectIdentifier3 *array;
+ uint32_t a;
+
+ attr = find_drsuapi_attr(cur, attid);
+ if (attr == NULL) {
+ return NT_STATUS_PROPSET_NOT_FOUND;
+ }
+
+ array = talloc_array(mem_ctx,
+ struct drsuapi_DsReplicaObjectIdentifier3,
+ attr->value_ctr.num_values);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (a = 0; a < attr->value_ctr.num_values; a++) {
+ const DATA_BLOB *blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ blob = attr->value_ctr.values[a].blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* windows sometimes sends an extra two pad bytes here */
+ ndr_err = ndr_pull_struct_blob(blob, array, &array[a],
+ (ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+ }
+ *_count = attr->value_ctr.num_values;
+ *_array = array;
+ return NT_STATUS_OK;
+}
+
+#define GET_BLOB_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ DATA_BLOB *_vals = NULL; \
+ attr = data_blob_null; \
+ _status = find_drsuapi_attr_blob(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = _vals[0]; \
+ (void)talloc_steal(mem_ctx, _vals[0].data); \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_STRING_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ char **_vals = NULL; \
+ attr = NULL; \
+ _status = find_drsuapi_attr_string(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = talloc_move(mem_ctx, &_vals[0]); \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_UINT32_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ int32_t*_vals = NULL; \
+ attr = 0; \
+ _status = find_drsuapi_attr_int32(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = (uint32_t)_vals[0]; \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_UINT64_EX(attr, needed) do { \
+ NTSTATUS _status; \
+ uint32_t _cnt; \
+ int64_t *_vals = NULL; \
+ attr = 0; \
+ _status = find_drsuapi_attr_int64(mem_ctx, cur, \
+ DRSUAPI_ATTID_ ## attr, \
+ &_cnt, &_vals); \
+ if (NT_STATUS_EQUAL(_status, NT_STATUS_PROPSET_NOT_FOUND)) { \
+ if (!needed) { \
+ _status = NT_STATUS_OK; \
+ _cnt = 0; \
+ } \
+ } \
+ if (!NT_STATUS_IS_OK(_status)) { \
+ DEBUG(0,(__location__ "attr[%s] %s\n", \
+ #attr, nt_errstr(_status))); \
+ return _status; \
+ } \
+ if (_cnt == 0) { \
+ if (needed) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND; \
+ } \
+ } else if (_cnt > 1) { \
+ talloc_free(_vals); \
+ DEBUG(0,(__location__ "attr[%s] count[%u]\n", #attr, _cnt)); \
+ return NT_STATUS_INTERNAL_DB_CORRUPTION; \
+ } else { \
+ attr = (uint64_t)_vals[0]; \
+ } \
+ talloc_free(_vals); \
+} while(0)
+
+#define GET_BLOB(attr) GET_BLOB_EX(attr, false)
+#define GET_STRING(attr) GET_STRING_EX(attr, false)
+#define GET_UINT32(attr) GET_UINT32_EX(attr, false)
+#define GET_UINT64(attr) GET_UINT64_EX(attr, false)
+
+/* Convert a struct samu_DELTA to a struct samu. */
+#define STRING_CHANGED (old_string && !new_string) ||\
+ (!old_string && new_string) ||\
+ (old_string && new_string && (strcmp(old_string, new_string) != 0))
+
+#define STRING_CHANGED_NC(s1,s2) ((s1) && !(s2)) ||\
+ (!(s1) && (s2)) ||\
+ ((s1) && (s2) && (strcmp((s1), (s2)) != 0))
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS sam_account_from_object(struct samu *account,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ TALLOC_CTX *mem_ctx = account;
+ const char *old_string, *new_string;
+ struct dom_sid_buf buf;
+ time_t unix_time, stored_time;
+ NTSTATUS status;
+
+ NTTIME lastLogon;
+ NTTIME lastLogoff;
+ NTTIME pwdLastSet;
+ NTTIME accountExpires;
+ const char *sAMAccountName;
+ const char *displayName;
+ const char *homeDirectory;
+ const char *homeDrive;
+ const char *scriptPath;
+ const char *profilePath;
+ const char *description;
+ const char *userWorkstations;
+ DATA_BLOB userParameters;
+ struct dom_sid objectSid;
+ uint32_t primaryGroupID;
+ uint32_t userAccountControl;
+ DATA_BLOB logonHours;
+ uint32_t badPwdCount;
+ uint32_t logonCount;
+ DATA_BLOB unicodePwd;
+ DATA_BLOB dBCSPwd;
+
+ uint32_t rid = 0;
+ uint32_t acct_flags;
+ uint32_t units_per_week;
+
+ objectSid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ DEBUG(0,("sam_account_from_object(%s, %s) start\n",
+ sAMAccountName,
+ dom_sid_str_buf(&objectSid, &buf)));
+ GET_UINT64(lastLogon);
+ GET_UINT64(lastLogoff);
+ GET_UINT64(pwdLastSet);
+ GET_UINT64(accountExpires);
+ GET_STRING(displayName);
+ GET_STRING(homeDirectory);
+ GET_STRING(homeDrive);
+ GET_STRING(scriptPath);
+ GET_STRING(profilePath);
+ GET_STRING(description);
+ GET_STRING(userWorkstations);
+ GET_BLOB(userParameters);
+ GET_UINT32(primaryGroupID);
+ GET_UINT32(userAccountControl);
+ GET_BLOB(logonHours);
+ GET_UINT32(badPwdCount);
+ GET_UINT32(logonCount);
+ GET_BLOB(unicodePwd);
+ GET_BLOB(dBCSPwd);
+
+ status = dom_sid_split_rid(mem_ctx, &objectSid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ acct_flags = ds_uf2acb(userAccountControl);
+
+ /* Username, fullname, home dir, dir drive, logon script, acct
+ desc, workstations, profile. */
+
+ if (sAMAccountName) {
+ old_string = pdb_get_nt_username(account);
+ new_string = sAMAccountName;
+
+ if (STRING_CHANGED) {
+ pdb_set_nt_username(account, new_string, PDB_CHANGED);
+ }
+
+ /* Unix username is the same - for sanity */
+ old_string = pdb_get_username( account );
+ if (STRING_CHANGED) {
+ pdb_set_username(account, new_string, PDB_CHANGED);
+ }
+ }
+
+ if (displayName) {
+ old_string = pdb_get_fullname(account);
+ new_string = displayName;
+
+ if (STRING_CHANGED)
+ pdb_set_fullname(account, new_string, PDB_CHANGED);
+ }
+
+ if (homeDirectory) {
+ old_string = pdb_get_homedir(account);
+ new_string = homeDirectory;
+
+ if (STRING_CHANGED)
+ pdb_set_homedir(account, new_string, PDB_CHANGED);
+ }
+
+ if (homeDrive) {
+ old_string = pdb_get_dir_drive(account);
+ new_string = homeDrive;
+
+ if (STRING_CHANGED)
+ pdb_set_dir_drive(account, new_string, PDB_CHANGED);
+ }
+
+ if (scriptPath) {
+ old_string = pdb_get_logon_script(account);
+ new_string = scriptPath;
+
+ if (STRING_CHANGED)
+ pdb_set_logon_script(account, new_string, PDB_CHANGED);
+ }
+
+ if (description) {
+ old_string = pdb_get_acct_desc(account);
+ new_string = description;
+
+ if (STRING_CHANGED)
+ pdb_set_acct_desc(account, new_string, PDB_CHANGED);
+ }
+
+ if (userWorkstations) {
+ old_string = pdb_get_workstations(account);
+ new_string = userWorkstations;
+
+ if (STRING_CHANGED)
+ pdb_set_workstations(account, new_string, PDB_CHANGED);
+ }
+
+ if (profilePath) {
+ old_string = pdb_get_profile_path(account);
+ new_string = profilePath;
+
+ if (STRING_CHANGED)
+ pdb_set_profile_path(account, new_string, PDB_CHANGED);
+ }
+
+ if (userParameters.data) {
+ char *newstr = NULL;
+ old_string = pdb_get_munged_dial(account);
+
+ if (userParameters.length != 0) {
+ newstr = base64_encode_data_blob(talloc_tos(),
+ userParameters);
+ SMB_ASSERT(newstr != NULL);
+ }
+
+ if (STRING_CHANGED_NC(old_string, newstr))
+ pdb_set_munged_dial(account, newstr, PDB_CHANGED);
+ TALLOC_FREE(newstr);
+ }
+
+ /* User and group sid */
+ if (rid != 0 && pdb_get_user_rid(account) != rid) {
+ pdb_set_user_sid_from_rid(account, rid, PDB_CHANGED);
+ }
+ if (primaryGroupID != 0 && pdb_get_group_rid(account) != primaryGroupID) {
+ pdb_set_group_sid_from_rid(account, primaryGroupID, PDB_CHANGED);
+ }
+
+ /* Logon and password information */
+ if (!nt_time_is_zero(&lastLogon)) {
+ unix_time = nt_time_to_unix(lastLogon);
+ stored_time = pdb_get_logon_time(account);
+ if (stored_time != unix_time)
+ pdb_set_logon_time(account, unix_time, PDB_CHANGED);
+ }
+
+ if (!nt_time_is_zero(&lastLogoff)) {
+ unix_time = nt_time_to_unix(lastLogoff);
+ stored_time = pdb_get_logoff_time(account);
+ if (stored_time != unix_time)
+ pdb_set_logoff_time(account, unix_time,PDB_CHANGED);
+ }
+
+ /* Logon Divs */
+ units_per_week = logonHours.length * 8;
+
+ if (pdb_get_logon_divs(account) != units_per_week) {
+ pdb_set_logon_divs(account, units_per_week, PDB_CHANGED);
+ }
+
+ /* Logon Hours Len */
+ if (units_per_week/8 != pdb_get_hours_len(account)) {
+ pdb_set_hours_len(account, units_per_week/8, PDB_CHANGED);
+ }
+
+ /* Logon Hours */
+ if (logonHours.data) {
+ char oldstr[44], newstr[44];
+ pdb_sethexhours(oldstr, pdb_get_hours(account));
+ pdb_sethexhours(newstr, logonHours.data);
+ if (!strequal(oldstr, newstr)) {
+ pdb_set_hours(account, logonHours.data,
+ logonHours.length, PDB_CHANGED);
+ }
+ }
+
+ if (pdb_get_bad_password_count(account) != badPwdCount)
+ pdb_set_bad_password_count(account, badPwdCount, PDB_CHANGED);
+
+ if (pdb_get_logon_count(account) != logonCount)
+ pdb_set_logon_count(account, logonCount, PDB_CHANGED);
+
+ if (!nt_time_is_zero(&pwdLastSet)) {
+ unix_time = nt_time_to_unix(pwdLastSet);
+ stored_time = pdb_get_pass_last_set_time(account);
+ if (stored_time != unix_time)
+ pdb_set_pass_last_set_time(account, unix_time, PDB_CHANGED);
+ } else {
+ /* no last set time, make it now */
+ pdb_set_pass_last_set_time(account, time(NULL), PDB_CHANGED);
+ }
+
+ if (!nt_time_is_zero(&accountExpires)) {
+ unix_time = nt_time_to_unix(accountExpires);
+ stored_time = pdb_get_kickoff_time(account);
+ if (stored_time != unix_time)
+ pdb_set_kickoff_time(account, unix_time, PDB_CHANGED);
+ }
+
+ /* Decode hashes from password hash
+ Note that win2000 may send us all zeros for the hashes if it doesn't
+ think this channel is secure enough - don't set the passwords at all
+ in that case
+ */
+ if (dBCSPwd.length == 16 && !all_zero(dBCSPwd.data, 16)) {
+ pdb_set_lanman_passwd(account, dBCSPwd.data, PDB_CHANGED);
+ }
+
+ if (unicodePwd.length == 16 && !all_zero(unicodePwd.data, 16)) {
+ pdb_set_nt_passwd(account, unicodePwd.data, PDB_CHANGED);
+ }
+
+ /* TODO: history */
+
+ /* TODO: account expiry time */
+
+ pdb_set_acct_ctrl(account, acct_flags, PDB_CHANGED);
+
+ pdb_set_domain(account, lp_workgroup(), PDB_CHANGED);
+
+ DEBUG(0,("sam_account_from_object(%s, %s) done\n",
+ sAMAccountName,
+ dom_sid_str_buf(&objectSid, &buf)));
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_account_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ NTSTATUS status;
+ fstring account;
+ struct samu *sam_account=NULL;
+ GROUP_MAP *map;
+ struct group *grp;
+ struct dom_sid user_sid;
+ struct dom_sid group_sid;
+ struct dom_sid_buf buf;
+ struct passwd *passwd = NULL;
+ uint32_t acct_flags;
+ uint32_t rid;
+
+ const char *sAMAccountName;
+ uint32_t userAccountControl;
+
+ user_sid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ GET_UINT32_EX(userAccountControl, true);
+
+ status = dom_sid_split_rid(mem_ctx, &user_sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fstrcpy(account, sAMAccountName);
+ if (rid == DOMAIN_RID_GUEST) {
+ /*
+ * pdb_getsampwsid() has special handling for DOMAIN_RID_GUEST
+ * that's why we need to ignore it here.
+ *
+ * pdb_smbpasswd.c also has some DOMAIN_RID_GUEST related
+ * code...
+ */
+ DEBUG(0,("Ignore %s - %s\n",
+ account,
+ dom_sid_str_buf(&user_sid, &buf)));
+ return NT_STATUS_OK;
+ }
+ DEBUG(0,("Creating account: %s\n", account));
+
+ if ( !(sam_account = samu_new(mem_ctx)) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ acct_flags = ds_uf2acb(userAccountControl);
+ status = smb_create_user(sam_account, acct_flags, account, &passwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Could not create posix account info for '%s'- %s\n",
+ account, nt_errstr(status)));
+ TALLOC_FREE(sam_account);
+ return status;
+ }
+
+ DEBUG(3, ("Attempting to find SID %s for user %s in the passdb\n",
+ dom_sid_str_buf(&user_sid, &buf),
+ account));
+ if (!pdb_getsampwsid(sam_account, &user_sid)) {
+ sam_account_from_object(sam_account, cur);
+ DEBUG(3, ("Attempting to add user SID %s for user %s in the passdb\n",
+ dom_sid_str_buf(&user_sid, &buf),
+ pdb_get_username(sam_account)));
+ if (!NT_STATUS_IS_OK(pdb_add_sam_account(sam_account))) {
+ DEBUG(1, ("SAM Account for %s failed to be added to the passdb!\n",
+ account));
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ sam_account_from_object(sam_account, cur);
+ DEBUG(3, ("Attempting to update user SID %s for user %s in the passdb\n",
+ dom_sid_str_buf(&user_sid, &buf),
+ pdb_get_username(sam_account)));
+ if (!NT_STATUS_IS_OK(pdb_update_sam_account(sam_account))) {
+ DEBUG(1, ("SAM Account for %s failed to be updated in the passdb!\n",
+ account));
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ if (pdb_get_group_sid(sam_account) == NULL) {
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ group_sid = *pdb_get_group_sid(sam_account);
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_getgrsid(map, group_sid)) {
+ DEBUG(0, ("Primary group of %s has no mapping!\n",
+ pdb_get_username(sam_account)));
+ } else {
+ if (map->gid != passwd->pw_gid) {
+ if (!(grp = getgrgid(map->gid))) {
+ DEBUG(0, ("Could not find unix group %lu for user %s (group SID=%s)\n",
+ (unsigned long)map->gid, pdb_get_username(sam_account),
+ dom_sid_str_buf(&group_sid, &buf)));
+ } else {
+ smb_set_primary_group(grp->gr_name, pdb_get_username(sam_account));
+ }
+ }
+ }
+
+ TALLOC_FREE(map);
+
+ if ( !passwd ) {
+ DEBUG(1, ("No unix user for this account (%s), cannot adjust mappings\n",
+ pdb_get_username(sam_account)));
+ }
+
+ TALLOC_FREE(sam_account);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_alias_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ NTSTATUS status;
+ struct group *grp = NULL;
+ struct dom_sid group_sid;
+ uint32_t rid = 0;
+ struct dom_sid *dom_sid = NULL;
+ struct dom_sid_buf sid_str;
+ GROUP_MAP *map;
+ bool insert = true;
+
+ const char *sAMAccountName;
+ const char *description;
+ uint32_t i;
+ uint32_t num_members = 0;
+ struct drsuapi_DsReplicaObjectIdentifier3 *members = NULL;
+
+ group_sid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ GET_STRING(description);
+
+ status = find_drsuapi_attr_dn(obj, cur, DRSUAPI_ATTID_member,
+ &num_members, &members);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROPSET_NOT_FOUND)) {
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dom_sid_split_rid(mem_ctx, &group_sid, &dom_sid, &rid);
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (map == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ map->nt_name = talloc_strdup(map, sAMAccountName);
+ if (map->nt_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (description) {
+ map->comment = talloc_strdup(map, description);
+ } else {
+ map->comment = talloc_strdup(map, "");
+ }
+ if (map->comment == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(0,("Creating alias[%s] - %s members[%u]\n",
+ map->nt_name,
+ dom_sid_str_buf(&group_sid, &sid_str),
+ num_members));
+
+ status = dssync_insert_obj(pctx, pctx->aliases, obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pdb_getgrsid(map, group_sid)) {
+ if (map->gid != -1) {
+ grp = getgrgid(map->gid);
+ }
+ insert = false;
+ }
+
+ if (grp == NULL) {
+ gid_t gid;
+
+ /* No group found from mapping, find it from its name. */
+ if ((grp = getgrnam(map->nt_name)) == NULL) {
+
+ /* No appropriate group found, create one */
+
+ DEBUG(0, ("Creating unix group: '%s'\n",
+ map->nt_name));
+
+ if (smb_create_group(map->nt_name, &gid) != 0) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ if ((grp = getgrgid(gid)) == NULL) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ }
+ }
+
+ map->gid = grp->gr_gid;
+ map->sid = group_sid;
+
+ if (dom_sid_equal(dom_sid, &global_sid_Builtin)) {
+ /*
+ * pdb_ldap does not like SID_NAME_WKN_GRP...
+ *
+ * map.sid_name_use = SID_NAME_WKN_GRP;
+ */
+ map->sid_name_use = SID_NAME_ALIAS;
+ } else {
+ map->sid_name_use = SID_NAME_ALIAS;
+ }
+
+ if (insert) {
+ pdb_add_group_mapping_entry(map);
+ } else {
+ pdb_update_group_mapping_entry(map);
+ }
+
+ for (i=0; i < num_members; i++) {
+ struct dssync_passdb_mem *mem;
+
+ status = dssync_create_mem(pctx, obj,
+ true /* active */,
+ &members[i], &mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_group_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ NTSTATUS status;
+ struct group *grp = NULL;
+ struct dom_sid group_sid;
+ struct dom_sid_buf sid_str;
+ GROUP_MAP *map;
+ bool insert = true;
+
+ const char *sAMAccountName;
+ const char *description;
+ uint32_t i;
+ uint32_t num_members = 0;
+ struct drsuapi_DsReplicaObjectIdentifier3 *members = NULL;
+
+ group_sid = cur->object.identifier->sid;
+ GET_STRING_EX(sAMAccountName, true);
+ GET_STRING(description);
+
+ status = find_drsuapi_attr_dn(obj, cur, DRSUAPI_ATTID_member,
+ &num_members, &members);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROPSET_NOT_FOUND)) {
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ map->nt_name = talloc_strdup(map, sAMAccountName);
+ if (!map->nt_name) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ if (description) {
+ map->comment = talloc_strdup(map, description);
+ } else {
+ map->comment = talloc_strdup(map, "");
+ }
+ if (!map->comment) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(0,("Creating group[%s] - %s members [%u]\n",
+ map->nt_name,
+ dom_sid_str_buf(&group_sid, &sid_str),
+ num_members));
+
+ status = dssync_insert_obj(pctx, pctx->groups, obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pdb_getgrsid(map, group_sid)) {
+ if (map->gid != -1) {
+ grp = getgrgid(map->gid);
+ }
+ insert = false;
+ }
+
+ if (grp == NULL) {
+ gid_t gid;
+
+ /* No group found from mapping, find it from its name. */
+ if ((grp = getgrnam(map->nt_name)) == NULL) {
+
+ /* No appropriate group found, create one */
+
+ DEBUG(0, ("Creating unix group: '%s'\n",
+ map->nt_name));
+
+ if (smb_create_group(map->nt_name, &gid) != 0) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ if ((grp = getgrnam(map->nt_name)) == NULL) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ }
+ }
+
+ map->gid = grp->gr_gid;
+ map->sid = group_sid;
+ map->sid_name_use = SID_NAME_DOM_GRP;
+
+ if (insert) {
+ pdb_add_group_mapping_entry(map);
+ } else {
+ pdb_update_group_mapping_entry(map);
+ }
+
+ for (i=0; i < num_members; i++) {
+ struct dssync_passdb_mem *mem;
+
+ status = dssync_create_mem(pctx, obj,
+ true /* active */,
+ &members[i], &mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(map);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS handle_interdomain_trust_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj)
+{
+ struct drsuapi_DsReplicaObjectListItemEx *cur = obj->cur;
+ DEBUG(0,("trust: %s\n", cur->object.identifier->dn));
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+struct dssync_object_table_t {
+ uint32_t type;
+ NTSTATUS (*fn) (struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct dssync_passdb_obj *obj);
+};
+
+static const struct dssync_object_table_t dssync_object_table[] = {
+ { ATYPE_NORMAL_ACCOUNT, handle_account_object },
+ { ATYPE_WORKSTATION_TRUST, handle_account_object },
+ { ATYPE_SECURITY_LOCAL_GROUP, handle_alias_object },
+ { ATYPE_SECURITY_GLOBAL_GROUP, handle_group_object },
+ { ATYPE_INTERDOMAIN_TRUST, handle_interdomain_trust_object },
+};
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS parse_object(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ DATA_BLOB *blob = NULL;
+ uint32_t i = 0;
+ size_t a = 0;
+
+ char *name = NULL;
+ uint32_t sam_type = 0;
+
+ DEBUG(3, ("parsing object '%s'\n", cur->object.identifier->dn));
+
+ for (i=0; i < cur->object.attribute_ctr.num_attributes; i++) {
+ struct drsuapi_DsReplicaAttribute *attr =
+ &cur->object.attribute_ctr.attributes[i];
+
+ if (attr->value_ctr.num_values != 1) {
+ continue;
+ }
+
+ if (!attr->value_ctr.values[0].blob) {
+ continue;
+ }
+
+ blob = attr->value_ctr.values[0].blob;
+
+ switch (attr->attid) {
+ case DRSUAPI_ATTID_sAMAccountName:
+ pull_string_talloc(mem_ctx, NULL, 0, &name,
+ blob->data, blob->length,
+ STR_UNICODE);
+ break;
+ case DRSUAPI_ATTID_sAMAccountType:
+ sam_type = IVAL(blob->data, 0);
+ break;
+ default:
+ break;
+ }
+ }
+
+ for (a=0; a < ARRAY_SIZE(dssync_object_table); a++) {
+ if (sam_type == dssync_object_table[a].type) {
+ if (dssync_object_table[a].fn) {
+ struct dssync_passdb_obj *obj = NULL;
+ status = dssync_create_obj(pctx, pctx->all,
+ sam_type, cur, &obj);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ status = dssync_object_table[a].fn(pctx,
+ mem_ctx,
+ obj);
+ break;
+ }
+ }
+ }
+
+ return status;
+}
+
+static NTSTATUS parse_link(struct dssync_passdb *pctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaLinkedAttribute *cur)
+{
+ struct drsuapi_DsReplicaObjectIdentifier3 *id3;
+ const DATA_BLOB *blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ bool active = false;
+ struct dssync_passdb_mem *mem;
+ struct dssync_passdb_obj *obj;
+
+ if (cur->attid != DRSUAPI_ATTID_member) {
+ return NT_STATUS_OK;
+ }
+
+ if (cur->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE) {
+ active = true;
+ }
+
+ DEBUG(3, ("parsing link '%s' - %s\n",
+ cur->identifier->dn, active?"adding":"deleting"));
+
+ blob = cur->value.blob;
+
+ if (blob == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ obj = dssync_search_obj_by_guid(pctx, pctx->all, &cur->identifier->guid);
+ if (obj == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ id3 = talloc_zero(obj, struct drsuapi_DsReplicaObjectIdentifier3);
+ if (id3 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* windows sometimes sends an extra two pad bytes here */
+ ndr_err = ndr_pull_struct_blob(blob, id3, id3,
+ (ndr_pull_flags_fn_t)ndr_pull_drsuapi_DsReplicaObjectIdentifier3);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ status = dssync_create_mem(pctx, obj,
+ active,
+ id3, &mem);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS passdb_process_objects(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct drsuapi_DsReplicaObjectListItemEx *cur,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(ctx->private_data,
+ struct dssync_passdb);
+
+ for (; cur; cur = cur->next_object) {
+ status = parse_object(pctx, mem_ctx, cur);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS passdb_process_links(struct dssync_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ uint32_t count,
+ struct drsuapi_DsReplicaLinkedAttribute *links,
+ struct drsuapi_DsReplicaOIDMapping_Ctr *mapping_ctr)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dssync_passdb *pctx =
+ talloc_get_type_abort(ctx->private_data,
+ struct dssync_passdb);
+ uint32_t i;
+
+ for (i = 0; i < count; i++) {
+ status = parse_link(pctx, mem_ctx, &links[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ out:
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+const struct dssync_ops libnet_dssync_passdb_ops = {
+ .startup = passdb_startup,
+ .process_objects = passdb_process_objects,
+ .process_links = passdb_process_links,
+ .finish = passdb_finish,
+};
diff --git a/source3/libnet/libnet_join.c b/source3/libnet/libnet_join.c
new file mode 100644
index 0000000..d3194bd
--- /dev/null
+++ b/source3/libnet/libnet_join.c
@@ -0,0 +1,3289 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join Support
+ * Copyright (C) Gerald (Jerry) Carter 2006
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ads.h"
+#include "libsmb/namequery.h"
+#include "librpc/gen_ndr/ndr_libnet_join.h"
+#include "libnet/libnet_join.h"
+#include "libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/init_samr.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "rpc_client/cli_netlogon.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "../libds/common/flags.h"
+#include "secrets.h"
+#include "rpc_client/init_lsa.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/param/loadparm.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "auth/credentials/credentials.h"
+#include "krb5_env.h"
+#include "libsmb/dsgetdcname.h"
+#include "rpc_client/util_netlogon.h"
+#include "libnet/libnet_join_offline.h"
+
+/****************************************************************
+****************************************************************/
+
+#define LIBNET_JOIN_DUMP_CTX(ctx, r, f) \
+ do { \
+ char *str = NULL; \
+ str = NDR_PRINT_FUNCTION_STRING(ctx, libnet_JoinCtx, f, r); \
+ DEBUG(1,("libnet_Join:\n%s", str)); \
+ TALLOC_FREE(str); \
+ } while (0)
+
+#define LIBNET_JOIN_IN_DUMP_CTX(ctx, r) \
+ LIBNET_JOIN_DUMP_CTX(ctx, r, NDR_IN | NDR_SET_VALUES)
+#define LIBNET_JOIN_OUT_DUMP_CTX(ctx, r) \
+ LIBNET_JOIN_DUMP_CTX(ctx, r, NDR_OUT)
+
+#define LIBNET_UNJOIN_DUMP_CTX(ctx, r, f) \
+ do { \
+ char *str = NULL; \
+ str = NDR_PRINT_FUNCTION_STRING(ctx, libnet_UnjoinCtx, f, r); \
+ DEBUG(1,("libnet_Unjoin:\n%s", str)); \
+ TALLOC_FREE(str); \
+ } while (0)
+
+#define LIBNET_UNJOIN_IN_DUMP_CTX(ctx, r) \
+ LIBNET_UNJOIN_DUMP_CTX(ctx, r, NDR_IN | NDR_SET_VALUES)
+#define LIBNET_UNJOIN_OUT_DUMP_CTX(ctx, r) \
+ LIBNET_UNJOIN_DUMP_CTX(ctx, r, NDR_OUT)
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_join_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ const char *format, ...)
+ PRINTF_ATTRIBUTE(3,4);
+
+static void libnet_join_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ const char *format, ...)
+{
+ va_list args;
+
+ if (r->out.error_string) {
+ return;
+ }
+
+ va_start(args, format);
+ r->out.error_string = talloc_vasprintf(mem_ctx, format, args);
+ va_end(args);
+}
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_unjoin_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r,
+ const char *format, ...)
+ PRINTF_ATTRIBUTE(3,4);
+
+static void libnet_unjoin_set_error_string(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r,
+ const char *format, ...)
+{
+ va_list args;
+
+ if (r->out.error_string) {
+ return;
+ }
+
+ va_start(args, format);
+ r->out.error_string = talloc_vasprintf(mem_ctx, format, args);
+ va_end(args);
+}
+
+#ifdef HAVE_ADS
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_connect_ads(const char *dns_domain_name,
+ const char *netbios_domain_name,
+ const char *dc_name,
+ const char *user_name,
+ const char *password,
+ const char *ccname,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **ads)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STATUS status;
+ ADS_STRUCT *my_ads = NULL;
+ char *cp;
+ enum credentials_use_kerberos krb5_state;
+
+ my_ads = ads_init(tmp_ctx,
+ dns_domain_name,
+ netbios_domain_name,
+ dc_name,
+ ADS_SASL_SEAL);
+ if (!my_ads) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ /* In FIPS mode, client use kerberos is forced to required. */
+ krb5_state = lp_client_use_kerberos();
+ switch (krb5_state) {
+ case CRED_USE_KERBEROS_REQUIRED:
+ my_ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ my_ads->auth.flags &= ~ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DESIRED:
+ my_ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ my_ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DISABLED:
+ my_ads->auth.flags |= ADS_AUTH_DISABLE_KERBEROS;
+ my_ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ }
+
+ if (user_name) {
+ ADS_TALLOC_CONST_FREE(my_ads->auth.user_name);
+ my_ads->auth.user_name = talloc_strdup(my_ads, user_name);
+ if (my_ads->auth.user_name == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ if ((cp = strchr_m(my_ads->auth.user_name, '@'))!=0) {
+ *cp++ = '\0';
+ ADS_TALLOC_CONST_FREE(my_ads->auth.realm);
+ my_ads->auth.realm = talloc_asprintf_strupper_m(my_ads, "%s", cp);
+ if (my_ads->auth.realm == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto out;
+ }
+ }
+ }
+
+ if (password) {
+ ADS_TALLOC_CONST_FREE(my_ads->auth.password);
+ my_ads->auth.password = talloc_strdup(my_ads, password);
+ if (my_ads->auth.password == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (ccname != NULL) {
+ ADS_TALLOC_CONST_FREE(my_ads->auth.ccache_name);
+ my_ads->auth.ccache_name = talloc_strdup(my_ads, ccname);
+ if (my_ads->auth.ccache_name == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ setenv(KRB5_ENV_CCNAME, my_ads->auth.ccache_name, 1);
+ }
+
+ status = ads_connect_user_creds(my_ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ *ads = talloc_move(mem_ctx, &my_ads);
+
+ status = ADS_SUCCESS;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_connect_ads(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ bool use_machine_creds)
+{
+ ADS_STATUS status;
+ const char *username;
+ const char *password;
+ const char *ccname = NULL;
+
+ if (use_machine_creds) {
+ if (r->in.machine_name == NULL ||
+ r->in.machine_password == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
+ }
+ username = talloc_asprintf(mem_ctx, "%s$",
+ r->in.machine_name);
+ if (username == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ password = r->in.machine_password;
+ ccname = "MEMORY:libnet_join_machine_creds";
+ } else {
+ char *p = NULL;
+
+ username = r->in.admin_account;
+
+ p = strchr(r->in.admin_account, '@');
+ if (p == NULL) {
+ username = talloc_asprintf(mem_ctx, "%s@%s",
+ r->in.admin_account,
+ r->in.admin_domain);
+ }
+ if (username == NULL) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ password = r->in.admin_password;
+
+ /*
+ * when r->in.use_kerberos is set to allow "net ads join -k" we
+ * may not override the provided credential cache - gd
+ */
+
+ if (!r->in.use_kerberos) {
+ ccname = "MEMORY:libnet_join_user_creds";
+ }
+ }
+
+ status = libnet_connect_ads(r->out.dns_domain_name,
+ r->out.netbios_domain_name,
+ r->in.dc_name,
+ username,
+ password,
+ ccname,
+ r,
+ &r->in.ads);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ if (!r->out.netbios_domain_name) {
+ r->out.netbios_domain_name = talloc_strdup(mem_ctx,
+ r->in.ads->server.workgroup);
+ ADS_ERROR_HAVE_NO_MEMORY(r->out.netbios_domain_name);
+ }
+
+ if (!r->out.dns_domain_name) {
+ r->out.dns_domain_name = talloc_strdup(mem_ctx,
+ r->in.ads->config.realm);
+ ADS_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name);
+ }
+
+ r->out.domain_is_ad = true;
+
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_connect_ads_user(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ return libnet_join_connect_ads(mem_ctx, r, false);
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_connect_ads_machine(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ return libnet_join_connect_ads(mem_ctx, r, true);
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_unjoin_connect_ads(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ ADS_STATUS status;
+
+ status = libnet_connect_ads(r->in.domain_name,
+ r->in.domain_name,
+ r->in.dc_name,
+ r->in.admin_account,
+ r->in.admin_password,
+ NULL,
+ r,
+ &r->in.ads);
+ if (!ADS_ERR_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(status));
+ }
+
+ return status;
+}
+
+/****************************************************************
+ join a domain using ADS (LDAP mods)
+****************************************************************/
+
+static ADS_STATUS libnet_join_precreate_machine_acct(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ const char *attrs[] = { "dn", NULL };
+ bool moved = false;
+
+ status = ads_check_ou_dn(mem_ctx, r->in.ads, &r->in.account_ou);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_search_dn(r->in.ads, &res, r->in.account_ou, attrs);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (ads_count_replies(r->in.ads, res) != 1) {
+ ads_msgfree(r->in.ads, res);
+ return ADS_ERROR_LDAP(LDAP_NO_SUCH_OBJECT);
+ }
+
+ ads_msgfree(r->in.ads, res);
+
+ /* Attempt to create the machine account and bail if this fails.
+ Assume that the admin wants exactly what they requested */
+
+ if (r->in.machine_password == NULL) {
+ r->in.machine_password =
+ trust_pw_new_value(mem_ctx,
+ r->in.secure_channel_type,
+ SEC_ADS);
+ if (r->in.machine_password == NULL) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+ }
+
+ status = ads_create_machine_acct(r->in.ads,
+ r->in.machine_name,
+ r->in.machine_password,
+ r->in.account_ou,
+ r->in.desired_encryption_types,
+ r->out.dns_domain_name);
+
+ if (ADS_ERR_OK(status)) {
+ DBG_WARNING("Machine account successfully created\n");
+ return status;
+ } else if ((status.error_type == ENUM_ADS_ERROR_LDAP) &&
+ (status.err.rc == LDAP_ALREADY_EXISTS)) {
+ status = ADS_SUCCESS;
+ }
+
+ if (!ADS_ERR_OK(status)) {
+ DBG_WARNING("Failed to create machine account\n");
+ return status;
+ }
+
+ status = ads_move_machine_acct(r->in.ads,
+ r->in.machine_name,
+ r->in.account_ou,
+ &moved);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("failure to locate/move pre-existing "
+ "machine account\n"));
+ return status;
+ }
+
+ DEBUG(1,("The machine account %s the specified OU.\n",
+ moved ? "was moved into" : "already exists in"));
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_unjoin_remove_machine_acct(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ ADS_STATUS status;
+
+ if (!r->in.ads) {
+ status = libnet_unjoin_connect_ads(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(status));
+ return status;
+ }
+ }
+
+ status = ads_leave_realm(r->in.ads, r->in.machine_name);
+ if (!ADS_ERR_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to leave realm: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_find_machine_acct(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ char *dn = NULL;
+ struct dom_sid sid;
+
+ if (!r->in.machine_name) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_find_machine_acct(r->in.ads,
+ &res,
+ r->in.machine_name);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (ads_count_replies(r->in.ads, res) != 1) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ dn = ads_get_dn(r->in.ads, mem_ctx, res);
+ if (!dn) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ r->out.dn = talloc_strdup(mem_ctx, dn);
+ if (!r->out.dn) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ if (!ads_pull_uint32(r->in.ads, res, "msDS-SupportedEncryptionTypes",
+ &r->out.set_encryption_types)) {
+ r->out.set_encryption_types = 0;
+ }
+
+ if (!ads_pull_sid(r->in.ads, res, "objectSid", &sid)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ dom_sid_split_rid(mem_ctx, &sid, NULL, &r->out.account_rid);
+ done:
+ ads_msgfree(r->in.ads, res);
+ TALLOC_FREE(dn);
+
+ return status;
+}
+
+static ADS_STATUS libnet_join_get_machine_spns(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ char ***spn_array,
+ size_t *num_spns)
+{
+ ADS_STATUS status;
+
+ if (r->in.machine_name == NULL) {
+ return ADS_ERROR_SYSTEM(EINVAL);
+ }
+
+ status = ads_get_service_principal_names(mem_ctx,
+ r->in.ads,
+ r->in.machine_name,
+ spn_array,
+ num_spns);
+
+ return status;
+}
+
+static ADS_STATUS add_uniq_spn(TALLOC_CTX *mem_ctx, const char *spn,
+ const char ***array, size_t *num)
+{
+ bool ok = ads_element_in_array(*array, *num, spn);
+ if (!ok) {
+ ok = add_string_to_array(mem_ctx, spn, array, num);
+ if (!ok) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+ }
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+ Set a machines dNSHostName and servicePrincipalName attributes
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_machine_spn(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+ fstring my_fqdn;
+ fstring my_alias;
+ const char **spn_array = NULL;
+ size_t num_spns = 0;
+ char *spn = NULL;
+ const char **netbios_aliases = NULL;
+ const char **addl_hostnames = NULL;
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = libnet_join_get_machine_spns(frame,
+ r,
+ discard_const_p(char **, &spn_array),
+ &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(5, ("Retrieving the servicePrincipalNames failed.\n"));
+ }
+
+ /* Windows only creates HOST/shortname & HOST/fqdn. */
+
+ spn = talloc_asprintf(frame, "HOST/%s", r->in.machine_name);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+ if (!strupper_m(spn)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ if (r->in.dnshostname != NULL) {
+ fstr_sprintf(my_fqdn, "%s", r->in.dnshostname);
+ } else {
+ fstr_sprintf(my_fqdn, "%s.%s", r->in.machine_name,
+ lp_dnsdomain());
+ }
+
+ if (!strlower_m(my_fqdn)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ spn = talloc_asprintf(frame, "HOST/%s", my_fqdn);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ for (netbios_aliases = lp_netbios_aliases();
+ netbios_aliases != NULL && *netbios_aliases != NULL;
+ netbios_aliases++) {
+ /*
+ * Add HOST/NETBIOSNAME
+ */
+ spn = talloc_asprintf(frame, "HOST/%s", *netbios_aliases);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+ if (!strupper_m(spn)) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ /*
+ * Add HOST/netbiosname.domainname
+ */
+ fstr_sprintf(my_alias, "%s.%s",
+ *netbios_aliases,
+ lp_dnsdomain());
+
+ spn = talloc_asprintf(frame, "HOST/%s", my_alias);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ }
+
+ for (addl_hostnames = lp_additional_dns_hostnames();
+ addl_hostnames != NULL && *addl_hostnames != NULL;
+ addl_hostnames++) {
+
+ spn = talloc_asprintf(frame, "HOST/%s", *addl_hostnames);
+ if (spn == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ status = add_uniq_spn(frame, spn, &spn_array, &num_spns);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ }
+
+ /* make sure to NULL terminate the array */
+ spn_array = talloc_realloc(frame, spn_array, const char *, num_spns + 1);
+ if (spn_array == NULL) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+ spn_array[num_spns] = NULL;
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ status = ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ goto done;
+ }
+
+ /* fields of primary importance */
+
+ status = ads_mod_str(mem_ctx, &mods, "dNSHostName", my_fqdn);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = ads_mod_strlist(mem_ctx, &mods, "servicePrincipalName",
+ spn_array);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ addl_hostnames = lp_additional_dns_hostnames();
+ if (addl_hostnames != NULL && *addl_hostnames != NULL) {
+ status = ads_mod_strlist(mem_ctx, &mods,
+ "msDS-AdditionalDnsHostName",
+ addl_hostnames);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+ }
+
+ status = ads_gen_mod(r->in.ads, r->out.dn, mods);
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_machine_upn(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+
+ if (!r->in.create_upn) {
+ return ADS_SUCCESS;
+ }
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (!r->in.upn) {
+ const char *realm = r->out.dns_domain_name;
+
+ /* in case we are about to generate a keytab during the join
+ * make sure the default upn we create is usable with kinit -k.
+ * gd */
+
+ if (USE_KERBEROS_KEYTAB) {
+ realm = talloc_strdup_upper(mem_ctx,
+ r->out.dns_domain_name);
+ }
+
+ if (!realm) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ r->in.upn = talloc_asprintf(mem_ctx,
+ "host/%s@%s",
+ r->in.machine_name,
+ realm);
+ if (!r->in.upn) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ }
+
+ /* now do the mods */
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ /* fields of primary importance */
+
+ status = ads_mod_str(mem_ctx, &mods, "userPrincipalName", r->in.upn);
+ if (!ADS_ERR_OK(status)) {
+ return ADS_ERROR_LDAP(LDAP_NO_MEMORY);
+ }
+
+ return ads_gen_mod(r->in.ads, r->out.dn, mods);
+}
+
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_os_attributes(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+ char *os_sp = NULL;
+
+ if (!r->in.os_name || !r->in.os_version ) {
+ return ADS_SUCCESS;
+ }
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ /* now do the mods */
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ if (r->in.os_servicepack) {
+ /*
+ * if blank string then leave os_sp equal to NULL to force
+ * attribute delete (LDAP_MOD_DELETE)
+ */
+ if (!strequal(r->in.os_servicepack,"")) {
+ os_sp = talloc_strdup(mem_ctx, r->in.os_servicepack);
+ }
+ } else {
+ os_sp = talloc_asprintf(mem_ctx, "Samba %s",
+ samba_version_string());
+ }
+ if (!os_sp && !strequal(r->in.os_servicepack,"")) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ /* fields of primary importance */
+
+ status = ads_mod_str(mem_ctx, &mods, "operatingSystem",
+ r->in.os_name);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_mod_str(mem_ctx, &mods, "operatingSystemVersion",
+ r->in.os_version);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_mod_str(mem_ctx, &mods, "operatingSystemServicePack",
+ os_sp);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ return ads_gen_mod(r->in.ads, r->out.dn, mods);
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_set_etypes(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ ADS_MODLIST mods;
+ const char *etype_list_str;
+
+ etype_list_str = talloc_asprintf(mem_ctx, "%d",
+ r->in.desired_encryption_types);
+ if (!etype_list_str) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ /* Find our DN */
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (r->in.desired_encryption_types == r->out.set_encryption_types) {
+ return ADS_SUCCESS;
+ }
+
+ /* now do the mods */
+
+ mods = ads_init_mods(mem_ctx);
+ if (!mods) {
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+
+ status = ads_mod_str(mem_ctx, &mods, "msDS-SupportedEncryptionTypes",
+ etype_list_str);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ status = ads_gen_mod(r->in.ads, r->out.dn, mods);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ r->out.set_encryption_types = r->in.desired_encryption_types;
+
+ return ADS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_join_create_keytab(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ if (!USE_SYSTEM_KEYTAB) {
+ return true;
+ }
+
+ if (ads_keytab_create_default(r->in.ads) != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_join_derive_salting_principal(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ uint32_t domain_func;
+ ADS_STATUS status;
+ const char *salt = NULL;
+ char *std_salt = NULL;
+
+ status = ads_domain_func_level(r->in.ads, &domain_func);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to determine domain functional level: %s",
+ ads_errstr(status));
+ return false;
+ }
+
+ /* go ahead and setup the default salt */
+
+ std_salt = kerberos_standard_des_salt();
+ if (!std_salt) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to obtain standard DES salt");
+ return false;
+ }
+
+ salt = talloc_strdup(mem_ctx, std_salt);
+ if (!salt) {
+ return false;
+ }
+
+ SAFE_FREE(std_salt);
+
+ /* if it's a Windows functional domain, we have to look for the UPN */
+
+ if (domain_func == DS_DOMAIN_FUNCTION_2000) {
+ char *upn;
+
+ upn = ads_get_upn(r->in.ads, mem_ctx,
+ r->in.machine_name);
+ if (upn) {
+ salt = talloc_strdup(mem_ctx, upn);
+ if (!salt) {
+ return false;
+ }
+ }
+ }
+
+ r->out.krb5_salt = salt;
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static ADS_STATUS libnet_join_post_processing_ads_modify(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ ADS_STATUS status;
+ bool need_etype_update = false;
+
+ if (r->in.request_offline_join) {
+ /*
+ * When in the "request offline join" path we can no longer
+ * modify the AD account as we are operating w/o network - gd
+ */
+ return ADS_SUCCESS;
+ }
+
+ if (!r->in.ads) {
+ status = libnet_join_connect_ads_user(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+ }
+
+ status = libnet_join_set_machine_spn(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set machine spn: %s\n"
+ "Do you have sufficient permissions to create machine "
+ "accounts?",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_set_os_attributes(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to set machine os attributes: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_set_machine_upn(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to set machine upn: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_find_machine_acct(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ if (r->in.desired_encryption_types != r->out.set_encryption_types) {
+ uint32_t func_level = 0;
+
+ status = ads_domain_func_level(r->in.ads, &func_level);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to query domain controller functional level: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ if (func_level >= DS_DOMAIN_FUNCTION_2008) {
+ need_etype_update = true;
+ }
+ }
+
+ if (need_etype_update) {
+ /*
+ * We need to reconnect as machine account in order
+ * to update msDS-SupportedEncryptionTypes reliable
+ */
+
+ if (r->in.ads->auth.ccache_name != NULL) {
+ ads_kdestroy(r->in.ads->auth.ccache_name);
+ ADS_TALLOC_CONST_FREE(r->in.ads->auth.ccache_name);
+ }
+
+ TALLOC_FREE(r->in.ads);
+
+ status = libnet_join_connect_ads_machine(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to connect as machine account: %s",
+ ads_errstr(status));
+ return status;
+ }
+
+ status = libnet_join_set_etypes(mem_ctx, r);
+ if (!ADS_ERR_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to set machine kerberos encryption types: %s",
+ ads_errstr(status));
+ return status;
+ }
+ }
+
+ if (!libnet_join_derive_salting_principal(mem_ctx, r)) {
+ return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ }
+
+ return ADS_SUCCESS;
+}
+
+static ADS_STATUS libnet_join_post_processing_ads_sync(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ if (!libnet_join_create_keytab(mem_ctx, r)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to create kerberos keytab");
+ return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ }
+
+ return ADS_SUCCESS;
+}
+#endif /* HAVE_ADS */
+
+/****************************************************************
+ Store the machine password and domain SID
+****************************************************************/
+
+static bool libnet_join_joindomain_store_secrets(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+
+ status = secrets_store_JoinCtx(r);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_JoinCtx() failed %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************
+ Connect dc's IPC$ share
+****************************************************************/
+
+static NTSTATUS libnet_join_connect_dc_ipc(const char *dc,
+ const char *user,
+ const char *domain,
+ const char *pass,
+ bool use_kerberos,
+ struct cli_state **cli)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool fallback_after_kerberos = false;
+ bool use_ccache = false;
+ bool pw_nt_hash = false;
+ struct cli_credentials *creds = NULL;
+ int flags = CLI_FULL_CONNECTION_IPC;
+ NTSTATUS status;
+
+ if (use_kerberos && pass) {
+ fallback_after_kerberos = true;
+ }
+
+ creds = cli_session_creds_init(frame,
+ user,
+ domain,
+ NULL, /* realm (use default) */
+ pass,
+ use_kerberos,
+ fallback_after_kerberos,
+ use_ccache,
+ pw_nt_hash);
+ if (creds == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_full_connection_creds(cli,
+ NULL,
+ dc,
+ NULL, 0,
+ "IPC$", "IPC",
+ creds,
+ flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+ Lookup domain dc's info
+****************************************************************/
+
+static NTSTATUS libnet_join_lookup_dc_rpc(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ struct cli_state **cli)
+{
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle lsa_pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ struct dcerpc_binding_handle *b;
+ const char *account = r->in.admin_account;
+ const char *domain = r->in.admin_domain;
+ const char *password = r->in.admin_password;
+ bool use_kerberos = r->in.use_kerberos;
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE) {
+ account = "";
+ domain = "";
+ password = NULL;
+ use_kerberos = false;
+ }
+
+ status = libnet_join_connect_dc_ipc(r->in.dc_name,
+ account,
+ domain,
+ password,
+ use_kerberos,
+ cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = cli_rpc_pipe_open_noauth(*cli, &ndr_table_lsarpc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error connecting to LSA pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED, &lsa_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy2(b, mem_ctx,
+ &lsa_pol,
+ LSA_POLICY_INFO_DNS,
+ &info,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ r->out.domain_is_ad = true;
+ r->out.netbios_domain_name = info->dns.name.string;
+ r->out.dns_domain_name = info->dns.dns_domain.string;
+ r->out.forest_name = info->dns.dns_forest.string;
+ r->out.domain_guid = info->dns.domain_guid;
+ r->out.domain_sid = dom_sid_dup(mem_ctx, info->dns.sid);
+ NT_STATUS_HAVE_NO_MEMORY(r->out.domain_sid);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &lsa_pol,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ r->out.netbios_domain_name = info->account_domain.name.string;
+ r->out.domain_sid = dom_sid_dup(mem_ctx, info->account_domain.sid);
+ NT_STATUS_HAVE_NO_MEMORY(r->out.domain_sid);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &lsa_pol, &result);
+ TALLOC_FREE(pipe_hnd);
+
+ done:
+ return status;
+}
+
+/****************************************************************
+ Do the domain join unsecure
+****************************************************************/
+
+static NTSTATUS libnet_join_joindomain_rpc_unsecure(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ struct cli_state *cli)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct rpc_pipe_client *authenticate_pipe = NULL;
+ struct rpc_pipe_client *passwordset_pipe = NULL;
+ struct cli_credentials *cli_creds;
+ struct netlogon_creds_cli_context *netlogon_creds = NULL;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ uint32_t netlogon_flags = 0;
+ size_t len = 0;
+ bool ok;
+ DATA_BLOB new_trust_blob = data_blob_null;
+ NTSTATUS status;
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_netlogon,
+ &authenticate_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (!r->in.machine_password) {
+ int security = r->in.ads ? SEC_ADS : SEC_DOMAIN;
+
+ r->in.machine_password = trust_pw_new_value(mem_ctx,
+ r->in.secure_channel_type,
+ security);
+ if (r->in.machine_password == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ cli_creds = cli_credentials_init(talloc_tos());
+ if (cli_creds == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cli_credentials_set_username(cli_creds, r->out.account_name,
+ CRED_SPECIFIED);
+ cli_credentials_set_domain(cli_creds, r->in.domain_name,
+ CRED_SPECIFIED);
+ cli_credentials_set_realm(cli_creds, "", CRED_SPECIFIED);
+ cli_credentials_set_secure_channel_type(cli_creds,
+ r->in.secure_channel_type);
+
+ /* according to WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED */
+ cli_credentials_set_password(cli_creds, r->in.admin_password,
+ CRED_SPECIFIED);
+
+ status = rpccli_create_netlogon_creds_ctx(
+ cli_creds, authenticate_pipe->desthost, r->in.msg_ctx,
+ frame, &netlogon_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_setup_netlogon_creds(
+ cli, NCACN_NP, netlogon_creds, true /* force_reauth */,
+ cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_get(netlogon_creds, frame, &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ netlogon_flags = creds->negotiate_flags;
+ TALLOC_FREE(creds);
+
+ if (netlogon_flags & NETLOGON_NEG_AUTHENTICATED_RPC) {
+ const char *remote_name = smbXcli_conn_remote_name(cli->conn);
+ const struct sockaddr_storage *remote_sockaddr =
+ smbXcli_conn_remote_sockaddr(cli->conn);
+
+ status = cli_rpc_pipe_open_schannel_with_creds(
+ cli,
+ &ndr_table_netlogon,
+ NCACN_NP,
+ netlogon_creds,
+ remote_name,
+ remote_sockaddr,
+ &passwordset_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ } else {
+ passwordset_pipe = authenticate_pipe;
+ }
+
+ len = strlen(r->in.machine_password);
+ ok = convert_string_talloc(frame, CH_UNIX, CH_UTF16,
+ r->in.machine_password, len,
+ &new_trust_blob.data,
+ &new_trust_blob.length);
+ if (!ok) {
+ status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_ServerPasswordSet(netlogon_creds,
+ passwordset_pipe->binding_handle,
+ &new_trust_blob,
+ NULL); /* new_version */
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+ Do the domain join
+****************************************************************/
+
+static NTSTATUS libnet_join_joindomain_rpc(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r,
+ struct cli_state *cli)
+{
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle sam_pol, domain_pol, user_pol;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL, result;
+ char *acct_name;
+ struct lsa_String lsa_acct_name;
+ uint32_t acct_flags = ACB_WSTRUST;
+ struct samr_Ids user_rids;
+ struct samr_Ids name_types;
+ union samr_UserInfo user_info;
+ struct dcerpc_binding_handle *b = NULL;
+ unsigned int old_timeout = 0;
+
+ DATA_BLOB session_key = data_blob_null;
+ struct samr_CryptPassword crypt_pwd;
+ struct samr_CryptPasswordEx crypt_pwd_ex;
+
+ ZERO_STRUCT(sam_pol);
+ ZERO_STRUCT(domain_pol);
+ ZERO_STRUCT(user_pol);
+
+ switch (r->in.secure_channel_type) {
+ case SEC_CHAN_WKSTA:
+ acct_flags = ACB_WSTRUST;
+ break;
+ case SEC_CHAN_BDC:
+ acct_flags = ACB_SVRTRUST;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!r->in.machine_password) {
+ int security = r->in.ads ? SEC_ADS : SEC_DOMAIN;
+
+ r->in.machine_password = trust_pw_new_value(mem_ctx,
+ r->in.secure_channel_type,
+ security);
+ NT_STATUS_HAVE_NO_MEMORY(r->in.machine_password);
+ }
+
+ /* Open the domain */
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error connecting to SAM pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = cli_get_session_key(mem_ctx, pipe_hnd, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error getting session_key of SAM pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ SAMR_ACCESS_ENUM_DOMAINS
+ | SAMR_ACCESS_LOOKUP_DOMAIN,
+ &sam_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &sam_pol,
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1
+ | SAMR_DOMAIN_ACCESS_CREATE_USER
+ | SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ r->out.domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Create domain user */
+
+ acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name);
+ if (!strlower_m(acct_name)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, acct_name);
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE) {
+ uint32_t access_desired =
+ SEC_GENERIC_READ | SEC_GENERIC_WRITE | SEC_GENERIC_EXECUTE |
+ SEC_STD_WRITE_DAC | SEC_STD_DELETE |
+ SAMR_USER_ACCESS_SET_PASSWORD |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ uint32_t access_granted = 0;
+
+ DEBUG(10,("Creating account with desired access mask: %d\n",
+ access_desired));
+
+ status = dcerpc_samr_CreateUser2(b, mem_ctx,
+ &domain_pol,
+ &lsa_acct_name,
+ acct_flags,
+ access_desired,
+ &user_pol,
+ &access_granted,
+ &r->out.account_rid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) {
+
+ DEBUG(10,("Creation of workstation account failed: %s\n",
+ nt_errstr(status)));
+
+ /* If NT_STATUS_ACCESS_DENIED then we have a valid
+ username/password combo but the user does not have
+ administrator access. */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "User specified does not have "
+ "administrator privileges");
+ }
+
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) {
+ if (!(r->in.join_flags &
+ WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED)) {
+ goto done;
+ }
+ }
+
+ /* We *must* do this.... don't ask... */
+
+ if (NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ }
+ }
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ if (name_types.ids[0] != SID_NAME_USER) {
+ DEBUG(0,("%s is not a user account (type=%d)\n",
+ acct_name, name_types.ids[0]));
+ status = NT_STATUS_INVALID_WORKSTATION;
+ goto done;
+ }
+
+ r->out.account_rid = user_rids.ids[0];
+
+ /* Open handle on user */
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ r->out.account_rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Fill in the additional account flags now */
+
+ acct_flags |= ACB_PWNOEXP;
+
+ /* Set account flags on machine account */
+ ZERO_STRUCT(user_info.info16);
+ user_info.info16.acct_flags = acct_flags;
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ &user_pol,
+ UserControlInformation,
+ &user_info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set account flags for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set account flags for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ /* Set password on machine account - first try level 26 */
+
+ /*
+ * increase the timeout as password filter modules on the DC
+ * might delay the operation for a significant amount of time
+ */
+ old_timeout = rpccli_set_timeout(pipe_hnd, 600000);
+
+ status = init_samr_CryptPasswordEx(r->in.machine_password,
+ &session_key,
+ &crypt_pwd_ex);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ user_info.info26.password = crypt_pwd_ex;
+ user_info.info26.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON;
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ &user_pol,
+ UserInternal5InformationNew,
+ &user_info,
+ &result);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_ENUM_VALUE_OUT_OF_RANGE)) {
+
+ /* retry with level 24 */
+
+ status = init_samr_CryptPassword(r->in.machine_password,
+ &session_key,
+ &crypt_pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ user_info.info24.password = crypt_pwd;
+ user_info.info24.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON;
+
+ status = dcerpc_samr_SetUserInfo2(b, mem_ctx,
+ &user_pol,
+ UserInternal5Information,
+ &user_info,
+ &result);
+ }
+
+error:
+ old_timeout = rpccli_set_timeout(pipe_hnd, old_timeout);
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set password for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+
+ dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to set password for machine account (%s)\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+ done:
+ if (!pipe_hnd) {
+ return status;
+ }
+
+ data_blob_clear_free(&session_key);
+
+ if (is_valid_policy_hnd(&sam_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &sam_pol, &result);
+ }
+ if (is_valid_policy_hnd(&domain_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ }
+ if (is_valid_policy_hnd(&user_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ }
+ TALLOC_FREE(pipe_hnd);
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS libnet_join_ok(struct messaging_context *msg_ctx,
+ const char *netbios_domain_name,
+ const char *dc_name,
+ const bool use_kerberos)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct cli_credentials *cli_creds = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds = NULL;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ uint32_t netlogon_flags = 0;
+ NTSTATUS status;
+ int flags = CLI_FULL_CONNECTION_IPC;
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+
+ if (!dc_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!secrets_init()) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ status = pdb_get_trust_credentials(netbios_domain_name, NULL,
+ frame, &cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* we don't want any old password */
+ cli_credentials_set_old_password(cli_creds, NULL, CRED_SPECIFIED);
+
+ if (use_kerberos) {
+ cli_credentials_set_kerberos_state(cli_creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+ }
+
+ status = cli_full_connection_creds(&cli, NULL,
+ dc_name,
+ NULL, 0,
+ "IPC$", "IPC",
+ cli_creds,
+ flags);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ struct cli_credentials *anon_creds = NULL;
+
+ anon_creds = cli_credentials_init_anon(frame);
+ if (anon_creds == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_full_connection_creds(&cli,
+ NULL,
+ dc_name,
+ NULL, 0,
+ "IPC$", "IPC",
+ anon_creds,
+ flags);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_create_netlogon_creds_ctx(cli_creds,
+ dc_name,
+ msg_ctx,
+ frame,
+ &netlogon_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_setup_netlogon_creds(cli, NCACN_NP,
+ netlogon_creds,
+ true, /* force_reauth */
+ cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("connect_to_domain_password_server: "
+ "unable to open the domain client session to "
+ "machine %s. Flags[0x%08X] Error was : %s.\n",
+ dc_name, (unsigned)netlogon_flags,
+ nt_errstr(status)));
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_get(netlogon_creds,
+ talloc_tos(),
+ &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ netlogon_flags = creds->negotiate_flags;
+ TALLOC_FREE(creds);
+
+ if (!(netlogon_flags & NETLOGON_NEG_AUTHENTICATED_RPC)) {
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ remote_name = smbXcli_conn_remote_name(cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(cli->conn);
+
+ status = cli_rpc_pipe_open_schannel_with_creds(
+ cli, &ndr_table_netlogon, NCACN_NP,
+ netlogon_creds,
+ remote_name,
+ remote_sockaddr,
+ &netlogon_pipe);
+
+ TALLOC_FREE(netlogon_pipe);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("libnet_join_ok: failed to open schannel session "
+ "on netlogon pipe to server %s for domain %s. "
+ "Error was %s\n",
+ remote_name,
+ netbios_domain_name, nt_errstr(status)));
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ cli_shutdown(cli);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_post_verify(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+
+ status = libnet_join_ok(r->in.msg_ctx,
+ r->out.netbios_domain_name,
+ r->in.dc_name,
+ r->in.use_kerberos);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to verify domain membership after joining: %s",
+ get_friendly_nt_error_msg(status));
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_join_unjoindomain_remove_secrets(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ /*
+ * TODO: use values from 'struct libnet_UnjoinCtx' ?
+ */
+ return secrets_delete_machine_password_ex(lp_workgroup(), lp_realm());
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS libnet_join_unjoindomain_rpc(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle sam_pol, domain_pol, user_pol;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL, result;
+ char *acct_name;
+ uint32_t user_rid;
+ struct lsa_String lsa_acct_name;
+ struct samr_Ids user_rids;
+ struct samr_Ids name_types;
+ union samr_UserInfo *info = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ ZERO_STRUCT(sam_pol);
+ ZERO_STRUCT(domain_pol);
+ ZERO_STRUCT(user_pol);
+
+ status = libnet_join_connect_dc_ipc(r->in.dc_name,
+ r->in.admin_account,
+ r->in.admin_domain,
+ r->in.admin_password,
+ r->in.use_kerberos,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open the domain */
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error connecting to SAM pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &sam_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &sam_pol,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ r->in.domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Create domain user */
+
+ acct_name = talloc_asprintf(mem_ctx, "%s$", r->in.machine_name);
+ if (!strlower_m(acct_name)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, acct_name);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &user_rids,
+ &name_types,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ if (name_types.ids[0] != SID_NAME_USER) {
+ DEBUG(0, ("%s is not a user account (type=%d)\n", acct_name,
+ name_types.ids[0]));
+ status = NT_STATUS_INVALID_WORKSTATION;
+ goto done;
+ }
+
+ user_rid = user_rids.ids[0];
+
+ /* Open handle on user */
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ user_rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get user info */
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ &user_pol,
+ 16,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+
+ /* now disable and setuser info */
+
+ info->info16.acct_flags |= ACB_DISABLED;
+
+ status = dcerpc_samr_SetUserInfo(b, mem_ctx,
+ &user_pol,
+ 16,
+ info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ goto done;
+ }
+ status = result;
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+
+done:
+ if (pipe_hnd && b) {
+ if (is_valid_policy_hnd(&domain_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ }
+ if (is_valid_policy_hnd(&sam_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &sam_pol, &result);
+ }
+ TALLOC_FREE(pipe_hnd);
+ }
+
+ if (cli) {
+ cli_shutdown(cli);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR do_join_modify_vals_config(struct libnet_JoinCtx *r)
+{
+ WERROR werr = WERR_OK;
+ sbcErr err;
+ struct smbconf_ctx *ctx;
+
+ err = smbconf_init_reg(r, &ctx, NULL);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "netbios name",
+ r->in.machine_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE)) {
+
+ err = smbconf_set_global_parameter(ctx, "security", "user");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "workgroup",
+ r->in.domain_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ smbconf_delete_global_parameter(ctx, "realm");
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "security", "domain");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "workgroup",
+ r->out.netbios_domain_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ if (r->out.domain_is_ad) {
+ err = smbconf_set_global_parameter(ctx, "security", "ads");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_set_global_parameter(ctx, "realm",
+ r->out.dns_domain_name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+ }
+
+ done:
+ smbconf_shutdown(ctx);
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR do_unjoin_modify_vals_config(struct libnet_UnjoinCtx *r)
+{
+ WERROR werr = WERR_OK;
+ sbcErr err;
+ struct smbconf_ctx *ctx;
+
+ err = smbconf_init_reg(r, &ctx, NULL);
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+
+ err = smbconf_set_global_parameter(ctx, "security", "user");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ err = smbconf_delete_global_parameter(ctx, "workgroup");
+ if (!SBC_ERROR_IS_OK(err)) {
+ werr = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ smbconf_delete_global_parameter(ctx, "realm");
+ }
+
+ done:
+ smbconf_shutdown(ctx);
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR do_JoinConfig(struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+
+ if (!W_ERROR_IS_OK(r->out.result)) {
+ return r->out.result;
+ }
+
+ if (!r->in.modify_config) {
+ return WERR_OK;
+ }
+
+ werr = do_join_modify_vals_config(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ r->out.modified_config = true;
+ r->out.result = werr;
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_unjoin_config(struct libnet_UnjoinCtx *r)
+{
+ WERROR werr;
+
+ if (!W_ERROR_IS_OK(r->out.result)) {
+ return r->out.result;
+ }
+
+ if (!r->in.modify_config) {
+ return WERR_OK;
+ }
+
+ werr = do_unjoin_modify_vals_config(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ r->out.modified_config = true;
+ r->out.result = werr;
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool libnet_parse_domain_dc(TALLOC_CTX *mem_ctx,
+ const char *domain_str,
+ const char **domain_p,
+ const char **dc_p)
+{
+ char *domain = NULL;
+ char *dc = NULL;
+ const char *p = NULL;
+
+ if (!domain_str || !domain_p || !dc_p) {
+ return false;
+ }
+
+ p = strchr_m(domain_str, '\\');
+
+ if (p != NULL) {
+ domain = talloc_strndup(mem_ctx, domain_str,
+ PTR_DIFF(p, domain_str));
+ dc = talloc_strdup(mem_ctx, p+1);
+ if (!dc) {
+ return false;
+ }
+ } else {
+ domain = talloc_strdup(mem_ctx, domain_str);
+ dc = NULL;
+ }
+ if (!domain) {
+ return false;
+ }
+
+ *domain_p = domain;
+
+ if (!*dc_p && dc) {
+ *dc_p = dc;
+ }
+
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_pre_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ if (!r->in.domain_name) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "No domain name defined");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (strlen(r->in.machine_name) > 15) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Our netbios name can be at most 15 chars long, "
+ "\"%s\" is %u chars long\n",
+ r->in.machine_name,
+ (unsigned int)strlen(r->in.machine_name));
+ return WERR_INVALID_PARAMETER;
+ }
+
+ r->out.account_name = talloc_asprintf(mem_ctx, "%s$",
+ r->in.machine_name);
+ if (r->out.account_name == NULL) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Unable to construct r->out.account_name");
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!libnet_parse_domain_dc(mem_ctx, r->in.domain_name,
+ &r->in.domain_name,
+ &r->in.dc_name)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Failed to parse domain name");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (r->in.request_offline_join) {
+ /*
+ * When in the "request offline join" path we do not have admin
+ * credentials available so we can skip the next steps - gd
+ */
+ return WERR_OK;
+ }
+
+ if (!r->in.admin_domain) {
+ char *admin_domain = NULL;
+ char *admin_account = NULL;
+ bool ok;
+
+ ok = split_domain_user(mem_ctx,
+ r->in.admin_account,
+ &admin_domain,
+ &admin_account);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (admin_domain != NULL) {
+ r->in.admin_domain = admin_domain;
+ } else {
+ r->in.admin_domain = r->in.domain_name;
+ }
+ r->in.admin_account = admin_account;
+ }
+
+ if (r->in.provision_computer_account_only) {
+ /*
+ * When in the "provision_computer_account_only" path we do not
+ * need to have access to secrets.tdb at all - gd
+ */
+ return WERR_OK;
+ }
+
+ if (!secrets_init()) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Unable to open secrets database");
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static void libnet_join_add_dom_rids_to_builtins(struct dom_sid *domain_sid)
+{
+ NTSTATUS status;
+
+ /* Try adding dom admins to builtin\admins. Only log failures. */
+ status = create_builtin_administrators(domain_sid);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE)) {
+ DEBUG(10,("Unable to auto-add domain administrators to "
+ "BUILTIN\\Administrators during join because "
+ "winbindd must be running.\n"));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Failed to auto-add domain administrators to "
+ "BUILTIN\\Administrators during join: %s\n",
+ nt_errstr(status)));
+ }
+
+ /* Try adding dom users to builtin\users. Only log failures. */
+ status = create_builtin_users(domain_sid);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE)) {
+ DEBUG(10,("Unable to auto-add domain users to BUILTIN\\users "
+ "during join because winbindd must be running.\n"));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Failed to auto-add domain administrators to "
+ "BUILTIN\\Administrators during join: %s\n",
+ nt_errstr(status)));
+ }
+
+ /* Try adding dom guests to builtin\guests. Only log failures. */
+ status = create_builtin_guests(domain_sid);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PROTOCOL_UNREACHABLE)) {
+ DEBUG(10,("Unable to auto-add domain guests to "
+ "BUILTIN\\Guests during join because "
+ "winbindd must be running.\n"));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Failed to auto-add domain guests to "
+ "BUILTIN\\Guests during join: %s\n",
+ nt_errstr(status)));
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_post_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+
+ if (!W_ERROR_IS_OK(r->out.result)) {
+ return r->out.result;
+ }
+
+ if (!(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE)) {
+ werr = do_JoinConfig(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+ }
+
+#ifdef HAVE_ADS
+ if (r->out.domain_is_ad &&
+ !(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) {
+ ADS_STATUS ads_status;
+
+ ads_status = libnet_join_post_processing_ads_modify(mem_ctx, r);
+ if (!ADS_ERR_OK(ads_status)) {
+ return WERR_GEN_FAILURE;
+ }
+ }
+#endif /* HAVE_ADS */
+
+ if (r->in.provision_computer_account_only) {
+ /*
+ * When we only provision a computer account we are done here - gd.
+ */
+ return WERR_OK;
+ }
+
+ saf_join_store(r->out.netbios_domain_name, r->in.dc_name);
+ if (r->out.dns_domain_name) {
+ saf_join_store(r->out.dns_domain_name, r->in.dc_name);
+ }
+
+ if (!libnet_join_joindomain_store_secrets(mem_ctx, r)) {
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+
+ werr = do_JoinConfig(r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+#ifdef HAVE_ADS
+ if (r->out.domain_is_ad &&
+ !(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) {
+ ADS_STATUS ads_status;
+
+ ads_status = libnet_join_post_processing_ads_sync(mem_ctx, r);
+ if (!ADS_ERR_OK(ads_status)) {
+ return WERR_GEN_FAILURE;
+ }
+ }
+#endif /* HAVE_ADS */
+
+ libnet_join_add_dom_rids_to_builtins(r->out.domain_sid);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static int libnet_destroy_JoinCtx(struct libnet_JoinCtx *r)
+{
+ TALLOC_FREE(r->in.ads);
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+static int libnet_destroy_UnjoinCtx(struct libnet_UnjoinCtx *r)
+{
+ TALLOC_FREE(r->in.ads);
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_init_JoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx **r)
+{
+ struct libnet_JoinCtx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct libnet_JoinCtx);
+ if (!ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ talloc_set_destructor(ctx, libnet_destroy_JoinCtx);
+
+ ctx->in.machine_name = talloc_strdup(ctx, lp_netbios_name());
+ W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name);
+
+ ctx->in.secure_channel_type = SEC_CHAN_WKSTA;
+
+ ctx->in.desired_encryption_types = 0;
+ ctx->in.desired_encryption_types |= ENC_RC4_HMAC_MD5;
+ ctx->in.desired_encryption_types |= ENC_HMAC_SHA1_96_AES128;
+ ctx->in.desired_encryption_types |= ENC_HMAC_SHA1_96_AES256;
+
+ *r = ctx;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_init_UnjoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx **r)
+{
+ struct libnet_UnjoinCtx *ctx;
+
+ ctx = talloc_zero(mem_ctx, struct libnet_UnjoinCtx);
+ if (!ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ talloc_set_destructor(ctx, libnet_destroy_UnjoinCtx);
+
+ ctx->in.machine_name = talloc_strdup(ctx, lp_netbios_name());
+ W_ERROR_HAVE_NO_MEMORY(ctx->in.machine_name);
+
+ *r = ctx;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_check_config(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ bool valid_security = false;
+ bool valid_workgroup = false;
+ bool valid_realm = false;
+ bool valid_hostname = false;
+ bool ignored_realm = false;
+
+ /* check if configuration is already set correctly */
+
+ valid_workgroup = strequal(lp_workgroup(), r->out.netbios_domain_name);
+ valid_hostname = strequal(lp_netbios_name(), r->in.machine_name);
+
+ switch (r->out.domain_is_ad) {
+ case false:
+ valid_security = (lp_security() == SEC_DOMAIN)
+ || (lp_server_role() == ROLE_DOMAIN_PDC)
+ || (lp_server_role() == ROLE_DOMAIN_BDC);
+ if (valid_workgroup && valid_security) {
+ /* nothing to be done */
+ return WERR_OK;
+ }
+ break;
+ case true:
+ valid_realm = strequal(lp_realm(), r->out.dns_domain_name);
+ switch (lp_security()) {
+ case SEC_DOMAIN:
+ if (!valid_realm && lp_winbind_rpc_only()) {
+ valid_realm = true;
+ ignored_realm = true;
+ }
+
+ FALL_THROUGH;
+ case SEC_ADS:
+ valid_security = true;
+ }
+
+ if (valid_workgroup && valid_realm && valid_security &&
+ valid_hostname) {
+ if (ignored_realm && !r->in.modify_config)
+ {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Warning: ignoring realm when "
+ "joining AD domain with "
+ "'security=domain' and "
+ "'winbind rpc only = yes'. "
+ "(realm set to '%s', "
+ "should be '%s').", lp_realm(),
+ r->out.dns_domain_name);
+ }
+ /* nothing to be done */
+ return WERR_OK;
+ }
+ break;
+ }
+
+ /* check if we are supposed to manipulate configuration */
+
+ if (!r->in.modify_config) {
+
+ char *wrong_conf = talloc_strdup(mem_ctx, "");
+
+ if (!valid_hostname) {
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"netbios name\" set to '%s', should be '%s'",
+ lp_netbios_name(), r->in.machine_name);
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ if (!valid_workgroup) {
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"workgroup\" set to '%s', should be '%s'",
+ lp_workgroup(), r->out.netbios_domain_name);
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ if (!valid_realm) {
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"realm\" set to '%s', should be '%s'",
+ lp_realm(), r->out.dns_domain_name);
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ if (!valid_security) {
+ const char *sec = NULL;
+ switch (lp_security()) {
+ case SEC_USER: sec = "user"; break;
+ case SEC_DOMAIN: sec = "domain"; break;
+ case SEC_ADS: sec = "ads"; break;
+ }
+ wrong_conf = talloc_asprintf_append(wrong_conf,
+ "\"security\" set to '%s', should be %s",
+ sec, r->out.domain_is_ad ?
+ "either 'domain' or 'ads'" : "'domain'");
+ W_ERROR_HAVE_NO_MEMORY(wrong_conf);
+ }
+
+ libnet_join_set_error_string(mem_ctx, r,
+ "Invalid configuration (%s) and configuration modification "
+ "was not requested", wrong_conf);
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ /* check if we are able to manipulate configuration */
+
+ if (!lp_config_backend_is_registry()) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "Configuration manipulation requested but not "
+ "supported by backend");
+ return WERR_NOT_SUPPORTED;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_DomainJoin(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct cli_state *cli = NULL;
+#ifdef HAVE_ADS
+ ADS_STATUS ads_status;
+#endif /* HAVE_ADS */
+ const char *pre_connect_realm = NULL;
+ const char *sitename = NULL;
+ struct netr_DsRGetDCNameInfo *info;
+ const char *dc;
+ uint32_t name_type_flags = 0;
+
+ /* Before contacting a DC, we can securely know
+ * the realm only if the user specifies it.
+ */
+ if (r->in.use_kerberos &&
+ r->in.domain_name_type == JoinDomNameTypeDNS) {
+ pre_connect_realm = r->in.domain_name;
+ }
+
+ if (r->in.domain_name_type == JoinDomNameTypeDNS) {
+ name_type_flags = DS_IS_DNS_NAME;
+ } else if (r->in.domain_name_type == JoinDomNameTypeNBT) {
+ name_type_flags = DS_IS_FLAT_NAME;
+ }
+
+ if (r->in.dc_name) {
+ status = dsgetonedcname(mem_ctx,
+ r->in.msg_ctx,
+ r->in.domain_name,
+ r->in.dc_name,
+ DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME |
+ name_type_flags,
+ &info);
+ } else {
+ status = dsgetdcname(mem_ctx,
+ r->in.msg_ctx,
+ r->in.domain_name,
+ NULL,
+ NULL,
+ DS_FORCE_REDISCOVERY |
+ DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME |
+ name_type_flags,
+ &info);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to find DC for domain %s - %s",
+ r->in.domain_name,
+ get_friendly_nt_error_msg(status));
+ return WERR_NERR_DCNOTFOUND;
+ }
+
+ dc = strip_hostname(info->dc_unc);
+ r->in.dc_name = talloc_strdup(mem_ctx, dc);
+ W_ERROR_HAVE_NO_MEMORY(r->in.dc_name);
+
+ if (info->dc_address == NULL || info->dc_address[0] != '\\' ||
+ info->dc_address[1] != '\\') {
+ DBG_ERR("ill-formed DC address '%s'\n",
+ info->dc_address);
+ return WERR_NERR_DCNOTFOUND;
+ }
+
+ sitename = info->dc_site_name;
+ /* info goes out of scope but the memory stays
+ allocated on the talloc context */
+
+ /* return the allocated netr_DsRGetDCNameInfo struct */
+ r->out.dcinfo = info;
+
+ if (pre_connect_realm != NULL) {
+ struct sockaddr_storage ss = {0};
+ const char *numeric_dcip = info->dc_address + 2;
+
+ if (numeric_dcip[0] == '\0') {
+ if (!interpret_string_addr(&ss, numeric_dcip,
+ AI_NUMERICHOST)) {
+ DBG_ERR(
+ "cannot parse IP address '%s' of DC '%s'\n",
+ numeric_dcip, r->in.dc_name);
+ return WERR_NERR_DCNOTFOUND;
+ }
+ } else {
+ if (!interpret_string_addr(&ss, r->in.dc_name, 0)) {
+ DBG_WARNING(
+ "cannot resolve IP address of DC '%s'\n",
+ r->in.dc_name);
+ return WERR_NERR_DCNOTFOUND;
+ }
+ }
+
+ /* The domain parameter is only used as modifier
+ * to krb5.conf file name. _JOIN_ is not a valid
+ * NetBIOS name so it cannot clash with another domain
+ * -- Uri.
+ */
+ create_local_private_krb5_conf_for_domain(pre_connect_realm,
+ "_JOIN_",
+ sitename,
+ &ss);
+ }
+
+ status = libnet_join_lookup_dc_rpc(mem_ctx, r, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to lookup DC info for domain '%s' over rpc: %s",
+ r->in.domain_name, get_friendly_nt_error_msg(status));
+ return ntstatus_to_werror(status);
+ }
+
+ werr = libnet_join_check_config(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ if (!r->in.provision_computer_account_only) {
+ goto done;
+ }
+ /* do not fail when only provisioning */
+ }
+
+#ifdef HAVE_ADS
+
+ if (r->out.domain_is_ad) {
+ create_local_private_krb5_conf_for_domain(
+ r->out.dns_domain_name, r->out.netbios_domain_name,
+ sitename, smbXcli_conn_remote_sockaddr(cli->conn));
+ }
+
+ if (r->out.domain_is_ad &&
+ !(r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) {
+
+ const char *initial_account_ou = r->in.account_ou;
+
+ /*
+ * we want to create the msDS-SupportedEncryptionTypes attribute
+ * as early as possible so always try an LDAP create as the user
+ * first. We copy r->in.account_ou because it may be changed
+ * during the machine pre-creation.
+ */
+
+ ads_status = libnet_join_connect_ads_user(mem_ctx, r);
+ if (!ADS_ERR_OK(ads_status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to connect to AD: %s",
+ ads_errstr(ads_status));
+ return WERR_NERR_DEFAULTJOINREQUIRED;
+ }
+
+ ads_status = libnet_join_precreate_machine_acct(mem_ctx, r);
+ if (ADS_ERR_OK(ads_status)) {
+
+ /*
+ * LDAP object creation succeeded.
+ */
+ r->in.join_flags &= ~WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE;
+
+ return WERR_OK;
+ }
+
+ if (initial_account_ou != NULL) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to precreate account in ou %s: %s",
+ r->in.account_ou,
+ ads_errstr(ads_status));
+ return WERR_NERR_DEFAULTJOINREQUIRED;
+ }
+
+ DBG_INFO("Failed to pre-create account in OU %s: %s\n",
+ r->in.account_ou, ads_errstr(ads_status));
+ }
+#endif /* HAVE_ADS */
+
+ if ((r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE) &&
+ (r->in.join_flags & WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED)) {
+ status = libnet_join_joindomain_rpc_unsecure(mem_ctx, r, cli);
+ } else {
+ status = libnet_join_joindomain_rpc(mem_ctx, r, cli);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_join_set_error_string(mem_ctx, r,
+ "failed to join domain '%s' over rpc: %s",
+ r->in.domain_name, get_friendly_nt_error_msg(status));
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_EXISTS)) {
+ return WERR_NERR_SETUPALREADYJOINED;
+ }
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+ done:
+ if (cli) {
+ cli_shutdown(cli);
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_DomainOfflineJoin(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct ODJ_WIN7BLOB win7blob;
+ struct OP_JOINPROV3_PART joinprov3;
+ const char *dc_name;
+
+ if (!r->in.request_offline_join) {
+ return WERR_NERR_DEFAULTJOINREQUIRED;
+ }
+
+ if (r->in.odj_provision_data == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = libnet_odj_find_win7blob(r->in.odj_provision_data, &win7blob);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ r->out.netbios_domain_name = talloc_strdup(mem_ctx,
+ win7blob.DnsDomainInfo.Name.string);
+ W_ERROR_HAVE_NO_MEMORY(r->out.netbios_domain_name);
+
+ r->out.dns_domain_name = talloc_strdup(mem_ctx,
+ win7blob.DnsDomainInfo.DnsDomainName.string);
+ W_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name);
+
+ r->out.forest_name = talloc_strdup(mem_ctx,
+ win7blob.DnsDomainInfo.DnsForestName.string);
+ W_ERROR_HAVE_NO_MEMORY(r->out.forest_name);
+
+ r->out.domain_guid = win7blob.DnsDomainInfo.DomainGuid;
+ r->out.domain_sid = dom_sid_dup(mem_ctx,
+ win7blob.DnsDomainInfo.Sid);
+ W_ERROR_HAVE_NO_MEMORY(r->out.domain_sid);
+
+ werr = libnet_odj_find_joinprov3(r->in.odj_provision_data, &joinprov3);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ r->out.account_rid = joinprov3.Rid;
+
+ dc_name = strip_hostname(win7blob.DcInfo.dc_address);
+ if (dc_name == NULL) {
+ return WERR_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+ r->in.dc_name = talloc_strdup(mem_ctx, dc_name);
+ W_ERROR_HAVE_NO_MEMORY(r->in.dc_name);
+
+ r->out.domain_is_ad = true;
+
+ /* we cannot use talloc_steal but have to deep copy the struct here */
+ status = copy_netr_DsRGetDCNameInfo(mem_ctx, &win7blob.DcInfo,
+ &r->out.dcinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ werr = libnet_join_check_config(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+#if 0
+ /* the following fields are currently not filled in */
+
+ const char * dn;
+ uint32_t set_encryption_types;
+ const char * krb5_salt;
+#endif
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_join_rollback(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+ struct libnet_UnjoinCtx *u = NULL;
+
+ werr = libnet_init_UnjoinCtx(mem_ctx, &u);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ u->in.debug = r->in.debug;
+ u->in.dc_name = r->in.dc_name;
+ u->in.domain_name = r->in.domain_name;
+ u->in.admin_account = r->in.admin_account;
+ u->in.admin_password = r->in.admin_password;
+ u->in.modify_config = r->in.modify_config;
+ u->in.use_kerberos = r->in.use_kerberos;
+ u->in.unjoin_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE;
+
+ werr = libnet_Unjoin(mem_ctx, u);
+ TALLOC_FREE(u);
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_Join(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r)
+{
+ WERROR werr;
+
+ if (r->in.debug) {
+ LIBNET_JOIN_IN_DUMP_CTX(mem_ctx, r);
+ }
+
+ ZERO_STRUCT(r->out);
+
+ werr = libnet_join_pre_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+ if (r->in.request_offline_join) {
+ werr = libnet_DomainOfflineJoin(mem_ctx, r);
+ } else {
+ werr = libnet_DomainJoin(mem_ctx, r);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ }
+
+ werr = libnet_join_post_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (r->in.provision_computer_account_only) {
+ /*
+ * When we only provision a computer account we are done here - gd.
+ */
+ goto done;
+ }
+
+ if (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+ if (r->in.request_offline_join) {
+ /*
+ * When we are serving an offline domain join request we
+ * have no network so we are done here - gd.
+ */
+ goto done;
+ }
+
+ werr = libnet_join_post_verify(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ libnet_join_rollback(mem_ctx, r);
+ }
+ }
+
+ done:
+ r->out.result = werr;
+
+ if (r->in.debug) {
+ LIBNET_JOIN_OUT_DUMP_CTX(mem_ctx, r);
+ }
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_DomainUnjoin(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ NTSTATUS status;
+
+ if (!r->in.domain_sid) {
+ struct dom_sid sid;
+ if (!secrets_fetch_domain_sid(lp_workgroup(), &sid)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "Unable to fetch domain sid: are we joined?");
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+ r->in.domain_sid = dom_sid_dup(mem_ctx, &sid);
+ W_ERROR_HAVE_NO_MEMORY(r->in.domain_sid);
+ }
+
+ if (!(r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE) &&
+ !r->in.delete_machine_account) {
+ libnet_join_unjoindomain_remove_secrets(mem_ctx, r);
+ return WERR_OK;
+ }
+
+ if (!r->in.dc_name) {
+ struct netr_DsRGetDCNameInfo *info;
+ const char *dc;
+ status = dsgetdcname(mem_ctx,
+ r->in.msg_ctx,
+ r->in.domain_name,
+ NULL,
+ NULL,
+ DS_DIRECTORY_SERVICE_REQUIRED |
+ DS_WRITABLE_REQUIRED |
+ DS_RETURN_DNS_NAME,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to find DC for domain %s - %s",
+ r->in.domain_name,
+ get_friendly_nt_error_msg(status));
+ return WERR_NERR_DCNOTFOUND;
+ }
+
+ dc = strip_hostname(info->dc_unc);
+ r->in.dc_name = talloc_strdup(mem_ctx, dc);
+ W_ERROR_HAVE_NO_MEMORY(r->in.dc_name);
+ }
+
+#ifdef HAVE_ADS
+ /* for net ads leave, try to delete the account. If it works,
+ no sense in disabling. If it fails, we can still try to
+ disable it. jmcd */
+
+ if (r->in.delete_machine_account) {
+ ADS_STATUS ads_status;
+ ads_status = libnet_unjoin_connect_ads(mem_ctx, r);
+ if (ADS_ERR_OK(ads_status)) {
+ /* dirty hack */
+ r->out.dns_domain_name =
+ talloc_strdup(mem_ctx,
+ r->in.ads->server.realm);
+ ads_status =
+ libnet_unjoin_remove_machine_acct(mem_ctx, r);
+ }
+ if (!ADS_ERR_OK(ads_status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to remove machine account from AD: %s",
+ ads_errstr(ads_status));
+ } else {
+ r->out.deleted_machine_account = true;
+ W_ERROR_HAVE_NO_MEMORY(r->out.dns_domain_name);
+ libnet_join_unjoindomain_remove_secrets(mem_ctx, r);
+ return WERR_OK;
+ }
+ }
+#endif /* HAVE_ADS */
+
+ /* The WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE flag really means
+ "disable". */
+ if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE) {
+ status = libnet_join_unjoindomain_rpc(mem_ctx, r);
+ if (!NT_STATUS_IS_OK(status)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "failed to disable machine account via rpc: %s",
+ get_friendly_nt_error_msg(status));
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return WERR_NERR_SETUPNOTJOINED;
+ }
+ return ntstatus_to_werror(status);
+ }
+
+ r->out.dns_domain_name = talloc_strdup(mem_ctx,
+ r->in.domain_name);
+ r->out.disabled_machine_account = true;
+ }
+
+ /* If disable succeeded or was not requested at all, we
+ should be getting rid of our end of things */
+
+ libnet_join_unjoindomain_remove_secrets(mem_ctx, r);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_unjoin_pre_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ if (!r->in.domain_name) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "No domain name defined");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!libnet_parse_domain_dc(mem_ctx, r->in.domain_name,
+ &r->in.domain_name,
+ &r->in.dc_name)) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "Failed to parse domain name");
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (IS_DC) {
+ return WERR_NERR_SETUPDOMAINCONTROLLER;
+ }
+
+ if (!r->in.admin_domain) {
+ char *admin_domain = NULL;
+ char *admin_account = NULL;
+ bool ok;
+
+ ok = split_domain_user(mem_ctx,
+ r->in.admin_account,
+ &admin_domain,
+ &admin_account);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (admin_domain != NULL) {
+ r->in.admin_domain = admin_domain;
+ } else {
+ r->in.admin_domain = r->in.domain_name;
+ }
+ r->in.admin_account = admin_account;
+ }
+
+ if (!secrets_init()) {
+ libnet_unjoin_set_error_string(mem_ctx, r,
+ "Unable to open secrets database");
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR libnet_unjoin_post_processing(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ saf_delete(r->out.netbios_domain_name);
+ saf_delete(r->out.dns_domain_name);
+
+ return libnet_unjoin_config(r);
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR libnet_Unjoin(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r)
+{
+ WERROR werr;
+
+ if (r->in.debug) {
+ LIBNET_UNJOIN_IN_DUMP_CTX(mem_ctx, r);
+ }
+
+ werr = libnet_unjoin_pre_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (r->in.unjoin_flags & WKSSVC_JOIN_FLAGS_JOIN_TYPE) {
+ werr = libnet_DomainUnjoin(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ libnet_unjoin_config(r);
+ goto done;
+ }
+ }
+
+ werr = libnet_unjoin_post_processing(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ done:
+ r->out.result = werr;
+
+ if (r->in.debug) {
+ LIBNET_UNJOIN_OUT_DUMP_CTX(mem_ctx, r);
+ }
+
+ return werr;
+}
diff --git a/source3/libnet/libnet_join.h b/source3/libnet/libnet_join.h
new file mode 100644
index 0000000..b7e2f0b
--- /dev/null
+++ b/source3/libnet/libnet_join.h
@@ -0,0 +1,40 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join Support
+ * Copyright (C) Gerald (Jerry) Carter 2006
+ * Copyright (C) Guenther Deschner 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIBNET_LIBNET_JOIN_H_
+#define _LIBNET_LIBNET_JOIN_H_
+
+/* The following definitions come from libnet/libnet_join.c */
+
+struct messaging_context;
+NTSTATUS libnet_join_ok(struct messaging_context *msg_ctx,
+ const char *netbios_domain_name,
+ const char *dc_name,
+ const bool use_kerberos);
+WERROR libnet_init_JoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx **r);
+WERROR libnet_init_UnjoinCtx(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx **r);
+WERROR libnet_Join(TALLOC_CTX *mem_ctx,
+ struct libnet_JoinCtx *r);
+WERROR libnet_Unjoin(TALLOC_CTX *mem_ctx,
+ struct libnet_UnjoinCtx *r);
+
+#endif /* _LIBNET_LIBNET_JOIN_H_ */
diff --git a/source3/libnet/libnet_join_offline.c b/source3/libnet/libnet_join_offline.c
new file mode 100644
index 0000000..d1317dd
--- /dev/null
+++ b/source3/libnet/libnet_join_offline.c
@@ -0,0 +1,441 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join offline support
+ * Copyright (C) Guenther Deschner 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_libnet_join.h"
+#include "../librpc/gen_ndr/ndr_ODJ.h"
+#include "libnet/libnet_join_offline.h"
+#include "libcli/security/dom_sid.h"
+#include "rpc_client/util_netlogon.h"
+
+static WERROR libnet_odj_compose_ODJ_WIN7BLOB(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct ODJ_WIN7BLOB *b)
+{
+ char *samaccount;
+ uint32_t len;
+ struct ODJ_POLICY_DNS_DOMAIN_INFO i = {
+ .Sid = NULL,
+ };
+
+ ZERO_STRUCTP(b);
+
+ b->lpDomain = talloc_strdup(mem_ctx, r->out.dns_domain_name);
+ if (b->lpDomain == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ samaccount = talloc_strdup(mem_ctx, r->out.account_name);
+ if (samaccount == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ len = strlen(samaccount);
+ if (samaccount[len-1] == '$') {
+ samaccount[len-1] = '\0';
+ }
+ b->lpMachineName = samaccount;
+
+ b->lpMachinePassword = talloc_strdup(mem_ctx, r->in.machine_password);
+ if (b->lpMachinePassword == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* fill up ODJ_POLICY_DNS_DOMAIN_INFO */
+
+ i.Name.string = talloc_strdup(mem_ctx, r->out.netbios_domain_name);
+ if (i.Name.string == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ i.DnsDomainName.string = talloc_strdup(mem_ctx, r->out.dns_domain_name);
+ if (i.DnsDomainName.string == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ i.DnsForestName.string = talloc_strdup(mem_ctx, r->out.forest_name);
+ if (i.DnsForestName.string == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ i.DomainGuid = r->out.domain_guid;
+ i.Sid = dom_sid_dup(mem_ctx, r->out.domain_sid);
+ if (i.Sid == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->DnsDomainInfo = i;
+
+ if (r->out.dcinfo) {
+ struct netr_DsRGetDCNameInfo *p;
+
+ p = talloc_steal(mem_ctx, r->out.dcinfo);
+ if (p == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->DcInfo = *p;
+ }
+
+ /*
+ * According to
+ * https://docs.microsoft.com/en-us/windows/win32/netmgmt/odj-odj_win7blob
+ * it should be 0 but Windows 2019 always sets 6 - gd.
+ */
+ b->Options = 6;
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_JOINPROV2_PART(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct OP_JOINPROV2_PART **p)
+{
+ struct OP_JOINPROV2_PART *b;
+
+ b = talloc_zero(mem_ctx, struct OP_JOINPROV2_PART);
+ if (b == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* TODO */
+
+ *p = b;
+
+ return WERR_INVALID_LEVEL;
+}
+
+static WERROR libnet_odj_compose_OP_JOINPROV3_PART(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct OP_JOINPROV3_PART **p)
+{
+ struct OP_JOINPROV3_PART *b;
+ struct dom_sid *sid;
+
+ b = talloc_zero(mem_ctx, struct OP_JOINPROV3_PART);
+ if (b == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->Rid = r->out.account_rid;
+ sid = dom_sid_add_rid(mem_ctx, r->out.domain_sid, r->out.account_rid);
+ if (sid == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->lpSid = dom_sid_string(mem_ctx, sid);
+ if (b->lpSid == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *p = b;
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_PACKAGE_PART(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ const struct ODJ_WIN7BLOB *win7,
+ const char *join_provider_guid,
+ uint32_t flags,
+ struct OP_PACKAGE_PART *p)
+{
+ struct GUID guid;
+ uint32_t level;
+ WERROR werr;
+
+ if (!NT_STATUS_IS_OK(GUID_from_string(join_provider_guid, &guid))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ level = odj_switch_level_from_guid(&guid);
+
+ p->PartType = guid;
+ p->ulFlags = flags;
+ p->part_len = 0; /* autogenerated */
+ p->Part = talloc_zero(mem_ctx, union OP_PACKAGE_PART_u);
+ if (p->Part == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ switch (level) {
+ case 1: /* ODJ_GUID_JOIN_PROVIDER */
+ if (win7 == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+ p->Part->win7blob = *win7;
+ break;
+ case 2: /* ODJ_GUID_JOIN_PROVIDER2 */
+ werr = libnet_odj_compose_OP_JOINPROV2_PART(mem_ctx, r,
+ &p->Part->join_prov2.p);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ break;
+ case 3: /* ODJ_GUID_JOIN_PROVIDER3 */
+ werr = libnet_odj_compose_OP_JOINPROV3_PART(mem_ctx, r,
+ &p->Part->join_prov3.p);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_PACKAGE_PART_COLLECTION(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ const struct ODJ_WIN7BLOB *win7,
+ struct OP_PACKAGE_PART_COLLECTION **pp)
+{
+ WERROR werr;
+ struct OP_PACKAGE_PART_COLLECTION *p;
+
+ p = talloc_zero(mem_ctx, struct OP_PACKAGE_PART_COLLECTION);
+ if (p == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p->cParts = 2;
+ p->pParts = talloc_zero_array(p, struct OP_PACKAGE_PART, p->cParts);
+ if (p->pParts == NULL) {
+ talloc_free(p);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE_PART(p, r, win7,
+ ODJ_GUID_JOIN_PROVIDER,
+ OPSPI_PACKAGE_PART_ESSENTIAL,
+ &p->pParts[0]);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(p);
+ return werr;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE_PART(p, r, NULL,
+ ODJ_GUID_JOIN_PROVIDER3,
+ 0,
+ &p->pParts[1]);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(p);
+ return werr;
+ }
+
+ *pp = p;
+
+ return WERR_OK;
+}
+
+static WERROR libnet_odj_compose_OP_PACKAGE(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ const struct ODJ_WIN7BLOB *win7,
+ struct OP_PACKAGE **pp)
+{
+ WERROR werr;
+ struct OP_PACKAGE_PART_COLLECTION *c;
+ struct OP_PACKAGE *p;
+
+ p = talloc_zero(mem_ctx, struct OP_PACKAGE);
+ if (p == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE_PART_COLLECTION(p, r, win7, &c);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(p);
+ return werr;
+ }
+
+ p->EncryptionType = GUID_zero();
+
+ p->WrappedPartCollection.cbBlob = 0; /* autogenerated */
+ p->WrappedPartCollection.w = talloc_zero(p,
+ struct OP_PACKAGE_PART_COLLECTION_serialized_ptr);
+ if (p->WrappedPartCollection.w == NULL) {
+ talloc_free(p);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p->WrappedPartCollection.w->s.p = c;
+
+ *pp = p;
+
+ return WERR_OK;
+}
+
+WERROR libnet_odj_compose_ODJ_PROVISION_DATA(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct ODJ_PROVISION_DATA **b_p)
+{
+ WERROR werr;
+ struct ODJ_PROVISION_DATA *b;
+ struct ODJ_WIN7BLOB win7;
+ struct OP_PACKAGE *package;
+
+ b = talloc_zero(mem_ctx, struct ODJ_PROVISION_DATA);
+ if (b == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ b->ulVersion = 1;
+ b->ulcBlobs = 2;
+ b->pBlobs = talloc_zero_array(b, struct ODJ_BLOB, b->ulcBlobs);
+ if (b->pBlobs == NULL) {
+ talloc_free(b);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_odj_compose_ODJ_WIN7BLOB(b, r, &win7);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(b);
+ return werr;
+ }
+
+ werr = libnet_odj_compose_OP_PACKAGE(b, r, &win7, &package);
+ if (!W_ERROR_IS_OK(werr)) {
+ talloc_free(b);
+ return werr;
+ }
+
+ b->pBlobs[0].ulODJFormat = ODJ_WIN7_FORMAT;
+ b->pBlobs[0].cbBlob = 0; /* autogenerated */
+ b->pBlobs[0].pBlob = talloc_zero(b, union ODJ_BLOB_u);
+ if (b->pBlobs[0].pBlob == NULL) {
+ talloc_free(b);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ b->pBlobs[0].pBlob->odj_win7blob = win7;
+
+ b->pBlobs[1].ulODJFormat = ODJ_WIN8_FORMAT;
+ b->pBlobs[1].cbBlob = 0; /* autogenerated */
+ b->pBlobs[1].pBlob = talloc_zero(b, union ODJ_BLOB_u);
+ if (b->pBlobs[1].pBlob == NULL) {
+ talloc_free(b);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ b->pBlobs[1].pBlob->op_package.p = package;
+
+ *b_p = b;
+
+ return WERR_OK;
+}
+
+WERROR libnet_odj_find_win7blob(const struct ODJ_PROVISION_DATA *r,
+ struct ODJ_WIN7BLOB *win7blob)
+{
+ int i;
+
+ if (r == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < r->ulcBlobs; i++) {
+
+ struct ODJ_BLOB b = r->pBlobs[i];
+
+ switch (b.ulODJFormat) {
+ case ODJ_WIN7_FORMAT:
+ *win7blob = b.pBlob->odj_win7blob;
+ return WERR_OK;
+
+ case ODJ_WIN8_FORMAT: {
+ NTSTATUS status;
+ struct OP_PACKAGE_PART_COLLECTION *col;
+ struct GUID guid;
+ int k;
+
+ if (b.pBlob->op_package.p->WrappedPartCollection.w == NULL) {
+ return WERR_BAD_FORMAT;
+ }
+
+ col = b.pBlob->op_package.p->WrappedPartCollection.w->s.p;
+
+ status = GUID_from_string(ODJ_GUID_JOIN_PROVIDER, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (k = 0; k < col->cParts; k++) {
+ if (GUID_equal(&guid, &col->pParts[k].PartType)) {
+ *win7blob = col->pParts[k].Part->win7blob;
+ return WERR_OK;
+ }
+ }
+ break;
+ }
+ default:
+ return WERR_BAD_FORMAT;
+ }
+ }
+
+ return WERR_BAD_FORMAT;
+}
+
+
+WERROR libnet_odj_find_joinprov3(const struct ODJ_PROVISION_DATA *r,
+ struct OP_JOINPROV3_PART *joinprov3)
+{
+ int i;
+
+ if (r == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < r->ulcBlobs; i++) {
+
+ struct ODJ_BLOB b = r->pBlobs[i];
+
+ switch (b.ulODJFormat) {
+ case ODJ_WIN7_FORMAT:
+ continue;
+
+ case ODJ_WIN8_FORMAT: {
+ NTSTATUS status;
+ struct OP_PACKAGE_PART_COLLECTION *col;
+ struct GUID guid;
+ int k;
+
+ if (b.pBlob->op_package.p->WrappedPartCollection.w == NULL) {
+ return WERR_BAD_FORMAT;
+ }
+
+ col = b.pBlob->op_package.p->WrappedPartCollection.w->s.p;
+
+ status = GUID_from_string(ODJ_GUID_JOIN_PROVIDER3, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (k = 0; k < col->cParts; k++) {
+ if (GUID_equal(&guid, &col->pParts[k].PartType)) {
+ *joinprov3 = *col->pParts[k].Part->join_prov3.p;
+ return WERR_OK;
+ }
+ }
+ break;
+ }
+ default:
+ return WERR_BAD_FORMAT;
+ }
+ }
+
+ return WERR_BAD_FORMAT;
+}
diff --git a/source3/libnet/libnet_join_offline.h b/source3/libnet/libnet_join_offline.h
new file mode 100644
index 0000000..7507c58
--- /dev/null
+++ b/source3/libnet/libnet_join_offline.h
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Join offline support
+ * Copyright (C) Guenther Deschner 2021
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+WERROR libnet_odj_compose_ODJ_PROVISION_DATA(TALLOC_CTX *mem_ctx,
+ const struct libnet_JoinCtx *r,
+ struct ODJ_PROVISION_DATA **b_p);
+WERROR libnet_odj_find_win7blob(const struct ODJ_PROVISION_DATA *r,
+ struct ODJ_WIN7BLOB *win7blob);
+WERROR libnet_odj_find_joinprov3(const struct ODJ_PROVISION_DATA *r,
+ struct OP_JOINPROV3_PART *joinprov3);
diff --git a/source3/libnet/libnet_keytab.c b/source3/libnet/libnet_keytab.c
new file mode 100644
index 0000000..ea4fd5e
--- /dev/null
+++ b/source3/libnet/libnet_keytab.c
@@ -0,0 +1,457 @@
+/*
+ Unix SMB/CIFS implementation.
+ dump the remote SAM using rpc samsync operations
+
+ Copyright (C) Guenther Deschner 2008.
+ Copyright (C) Michael Adam 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smb_krb5.h"
+#include "ads.h"
+#include "secrets.h"
+#include "libnet/libnet_keytab.h"
+
+#ifdef HAVE_KRB5
+
+/****************************************************************
+****************************************************************/
+
+static int keytab_close(struct libnet_keytab_context *ctx)
+{
+ if (!ctx) {
+ return 0;
+ }
+
+ if (ctx->keytab && ctx->context) {
+ krb5_kt_close(ctx->context, ctx->keytab);
+ }
+
+ if (ctx->context) {
+ krb5_free_context(ctx->context);
+ }
+
+ TALLOC_FREE(ctx->ads);
+
+ TALLOC_FREE(ctx);
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+krb5_error_code libnet_keytab_init(TALLOC_CTX *mem_ctx,
+ const char *keytab_name,
+ struct libnet_keytab_context **ctx)
+{
+ krb5_error_code ret = 0;
+ krb5_context context = NULL;
+ krb5_keytab keytab = NULL;
+ const char *keytab_string = NULL;
+
+ struct libnet_keytab_context *r;
+
+ r = talloc_zero(mem_ctx, struct libnet_keytab_context);
+ if (!r) {
+ return ENOMEM;
+ }
+
+ talloc_set_destructor(r, keytab_close);
+
+ ret = smb_krb5_init_context_common(&context);
+ if (ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(ret));
+ return ret;
+ }
+
+ ret = smb_krb5_kt_open_relative(context,
+ keytab_name,
+ true, /* write_access */
+ &keytab);
+ if (ret) {
+ DBG_WARNING("smb_krb5_kt_open_relative failed (%s)\n",
+ error_message(ret));
+ krb5_free_context(context);
+ return ret;
+ }
+
+ ret = smb_krb5_kt_get_name(mem_ctx, context, keytab, &keytab_string);
+ if (ret) {
+ krb5_kt_close(context, keytab);
+ krb5_free_context(context);
+ return ret;
+ }
+
+ r->context = context;
+ r->keytab = keytab;
+ r->keytab_name = keytab_string;
+ r->clean_old_entries = false;
+
+ *ctx = r;
+
+ return 0;
+}
+
+/****************************************************************
+****************************************************************/
+
+/**
+ * Remove all entries that have the given principal, kvno and enctype.
+ */
+static krb5_error_code libnet_keytab_remove_entries(krb5_context context,
+ krb5_keytab keytab,
+ const char *principal,
+ int kvno,
+ const krb5_enctype enctype,
+ bool ignore_kvno)
+{
+ krb5_error_code ret;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ return 0;
+ }
+
+ while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0)
+ {
+ krb5_keyblock *keyp;
+ char *princ_s = NULL;
+
+ if (kt_entry.vno != kvno && !ignore_kvno) {
+ goto cont;
+ }
+
+ keyp = KRB5_KT_KEY(&kt_entry);
+
+ if (KRB5_KEY_TYPE(keyp) != enctype) {
+ goto cont;
+ }
+
+ ret = smb_krb5_unparse_name(talloc_tos(), context, kt_entry.principal,
+ &princ_s);
+ if (ret) {
+ DEBUG(5, ("smb_krb5_unparse_name failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+
+ if (strcmp(principal, princ_s) != 0) {
+ goto cont;
+ }
+
+ /* match found - remove */
+
+ DEBUG(10, ("found entry for principal %s, kvno %d, "
+ "enctype %d - trying to remove it\n",
+ princ_s, kt_entry.vno, KRB5_KEY_TYPE(keyp)));
+
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ ZERO_STRUCT(cursor);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_end_seq_get failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+
+ ret = krb5_kt_remove_entry(context, keytab,
+ &kt_entry);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_remove_entry failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+ DEBUG(10, ("removed entry for principal %s, kvno %d, "
+ "enctype %d\n", princ_s, kt_entry.vno,
+ KRB5_KEY_TYPE(keyp)));
+
+ ret = krb5_kt_start_seq_get(context, keytab, &cursor);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_start_seq_get failed (%s)\n",
+ error_message(ret)));
+ goto cont;
+ }
+
+cont:
+ smb_krb5_kt_free_entry(context, &kt_entry);
+ TALLOC_FREE(princ_s);
+ }
+
+ ret = krb5_kt_end_seq_get(context, keytab, &cursor);
+ if (ret) {
+ DEBUG(5, ("krb5_kt_end_seq_get failed (%s)\n",
+ error_message(ret)));
+ }
+
+ return ret;
+}
+
+static krb5_error_code libnet_keytab_add_entry(krb5_context context,
+ krb5_keytab keytab,
+ krb5_kvno kvno,
+ const char *princ_s,
+ krb5_enctype enctype,
+ krb5_data password)
+{
+ krb5_keyblock *keyp;
+ krb5_keytab_entry kt_entry;
+ krb5_error_code ret;
+ krb5_principal salt_princ = NULL;
+ char *salt_princ_s;
+
+ /* remove duplicates first ... */
+ ret = libnet_keytab_remove_entries(context, keytab, princ_s, kvno,
+ enctype, false);
+ if (ret) {
+ DEBUG(1, ("libnet_keytab_remove_entries failed: %s\n",
+ error_message(ret)));
+ }
+
+ ZERO_STRUCT(kt_entry);
+
+ kt_entry.vno = kvno;
+
+ ret = smb_krb5_parse_name(context, princ_s, &kt_entry.principal);
+ if (ret) {
+ DEBUG(1, ("smb_krb5_parse_name(%s) failed (%s)\n",
+ princ_s, error_message(ret)));
+ return ret;
+ }
+
+ keyp = KRB5_KT_KEY(&kt_entry);
+
+ salt_princ_s = kerberos_secrets_fetch_salt_princ();
+ if (salt_princ_s == NULL) {
+ ret = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ ret = krb5_parse_name(context, salt_princ_s, &salt_princ);
+ SAFE_FREE(salt_princ_s);
+ if (ret != 0) {
+ ret = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ ret = create_kerberos_key_from_string(context,
+ kt_entry.principal,
+ salt_princ,
+ &password,
+ keyp,
+ enctype,
+ true);
+ krb5_free_principal(context, salt_princ);
+ if (ret != 0) {
+ ret = KRB5KRB_ERR_GENERIC;
+ goto done;
+ }
+
+ ret = krb5_kt_add_entry(context, keytab, &kt_entry);
+ if (ret) {
+ DEBUG(1, ("adding entry to keytab failed (%s)\n",
+ error_message(ret)));
+ }
+
+done:
+ krb5_free_keyblock_contents(context, keyp);
+ krb5_free_principal(context, kt_entry.principal);
+ ZERO_STRUCT(kt_entry);
+ smb_krb5_kt_free_entry(context, &kt_entry);
+
+ return ret;
+}
+
+krb5_error_code libnet_keytab_add(struct libnet_keytab_context *ctx)
+{
+ krb5_error_code ret = 0;
+ uint32_t i;
+
+
+ if (ctx->clean_old_entries) {
+ DEBUG(0, ("cleaning old entries...\n"));
+ for (i=0; i < ctx->count; i++) {
+ struct libnet_keytab_entry *entry = &ctx->entries[i];
+
+ ret = libnet_keytab_remove_entries(ctx->context,
+ ctx->keytab,
+ entry->principal,
+ 0,
+ entry->enctype,
+ true);
+ if (ret) {
+ DEBUG(1,("libnet_keytab_add: Failed to remove "
+ "old entries for %s (enctype %u): %s\n",
+ entry->principal, entry->enctype,
+ error_message(ret)));
+ return ret;
+ }
+ }
+ }
+
+ for (i=0; i<ctx->count; i++) {
+
+ struct libnet_keytab_entry *entry = &ctx->entries[i];
+ krb5_data password;
+
+ ZERO_STRUCT(password);
+ password.data = (char *)entry->password.data;
+ password.length = entry->password.length;
+
+ ret = libnet_keytab_add_entry(ctx->context,
+ ctx->keytab,
+ entry->kvno,
+ entry->principal,
+ entry->enctype,
+ password);
+ if (ret) {
+ DEBUG(1,("libnet_keytab_add: "
+ "Failed to add entry to keytab file\n"));
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+struct libnet_keytab_entry *libnet_keytab_search(struct libnet_keytab_context *ctx,
+ const char *principal,
+ int kvno,
+ const krb5_enctype enctype,
+ TALLOC_CTX *mem_ctx)
+{
+ krb5_error_code ret = 0;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry kt_entry;
+ struct libnet_keytab_entry *entry = NULL;
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(cursor);
+
+ ret = krb5_kt_start_seq_get(ctx->context, ctx->keytab, &cursor);
+ if (ret) {
+ DEBUG(10, ("krb5_kt_start_seq_get failed: %s\n",
+ error_message(ret)));
+ return NULL;
+ }
+
+ while (krb5_kt_next_entry(ctx->context, ctx->keytab, &kt_entry, &cursor) == 0)
+ {
+ krb5_keyblock *keyp;
+ char *princ_s = NULL;
+
+ entry = NULL;
+
+ if (kt_entry.vno != kvno) {
+ goto cont;
+ }
+
+ keyp = KRB5_KT_KEY(&kt_entry);
+
+ if (KRB5_KEY_TYPE(keyp) != enctype) {
+ goto cont;
+ }
+
+ entry = talloc_zero(mem_ctx, struct libnet_keytab_entry);
+ if (!entry) {
+ DEBUG(3, ("talloc failed\n"));
+ goto fail;
+ }
+
+ ret = smb_krb5_unparse_name(entry, ctx->context, kt_entry.principal,
+ &princ_s);
+ if (ret) {
+ goto cont;
+ }
+
+ if (strcmp(principal, princ_s) != 0) {
+ goto cont;
+ }
+
+ entry->principal = talloc_strdup(entry, princ_s);
+ if (!entry->principal) {
+ DEBUG(3, ("talloc_strdup_failed\n"));
+ goto fail;
+ }
+
+ entry->name = talloc_move(entry, &princ_s);
+
+ entry->password = data_blob_talloc(entry, KRB5_KEY_DATA(keyp),
+ KRB5_KEY_LENGTH(keyp));
+ if (!entry->password.data) {
+ DEBUG(3, ("data_blob_talloc failed\n"));
+ goto fail;
+ }
+
+ DEBUG(10, ("found entry\n"));
+
+ smb_krb5_kt_free_entry(ctx->context, &kt_entry);
+ break;
+
+fail:
+ smb_krb5_kt_free_entry(ctx->context, &kt_entry);
+ TALLOC_FREE(entry);
+ break;
+
+cont:
+ smb_krb5_kt_free_entry(ctx->context, &kt_entry);
+ TALLOC_FREE(entry);
+ continue;
+ }
+
+ krb5_kt_end_seq_get(ctx->context, ctx->keytab, &cursor);
+ return entry;
+}
+
+/**
+ * Helper function to add data to the list
+ * of keytab entries. It builds the prefix from the input.
+ */
+NTSTATUS libnet_keytab_add_to_keytab_entries(TALLOC_CTX *mem_ctx,
+ struct libnet_keytab_context *ctx,
+ uint32_t kvno,
+ const char *name,
+ const char *prefix,
+ const krb5_enctype enctype,
+ DATA_BLOB blob)
+{
+ struct libnet_keytab_entry entry;
+
+ entry.kvno = kvno;
+ entry.name = talloc_strdup(mem_ctx, name);
+ entry.principal = talloc_asprintf(mem_ctx, "%s%s%s@%s",
+ prefix ? prefix : "",
+ prefix ? "/" : "",
+ name, ctx->dns_domain_name);
+ entry.enctype = enctype;
+ entry.password = blob;
+ NT_STATUS_HAVE_NO_MEMORY(entry.name);
+ NT_STATUS_HAVE_NO_MEMORY(entry.principal);
+ NT_STATUS_HAVE_NO_MEMORY(entry.password.data);
+
+ ADD_TO_ARRAY(mem_ctx, struct libnet_keytab_entry, entry,
+ &ctx->entries, &ctx->count);
+ NT_STATUS_HAVE_NO_MEMORY(ctx->entries);
+
+ return NT_STATUS_OK;
+}
+
+#endif /* HAVE_KRB5 */
diff --git a/source3/libnet/libnet_keytab.h b/source3/libnet/libnet_keytab.h
new file mode 100644
index 0000000..df6e957
--- /dev/null
+++ b/source3/libnet/libnet_keytab.h
@@ -0,0 +1,61 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * libnet Support
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_KRB5
+
+struct libnet_keytab_entry {
+ const char *name;
+ const char *principal;
+ DATA_BLOB password;
+ uint32_t kvno;
+ krb5_enctype enctype;
+};
+
+struct ads_struct;
+
+struct libnet_keytab_context {
+ krb5_context context;
+ krb5_keytab keytab;
+ const char *keytab_name;
+ struct ads_struct *ads;
+ const char *dns_domain_name;
+ uint32_t count;
+ struct libnet_keytab_entry *entries;
+ bool clean_old_entries;
+};
+
+/* The following definitions come from libnet/libnet_keytab.c */
+
+krb5_error_code libnet_keytab_init(TALLOC_CTX *mem_ctx,
+ const char *keytab_name,
+ struct libnet_keytab_context **ctx);
+krb5_error_code libnet_keytab_add(struct libnet_keytab_context *ctx);
+
+struct libnet_keytab_entry *libnet_keytab_search(struct libnet_keytab_context *ctx,
+ const char *principal, int kvno,
+ const krb5_enctype enctype,
+ TALLOC_CTX *mem_ctx);
+NTSTATUS libnet_keytab_add_to_keytab_entries(TALLOC_CTX *mem_ctx,
+ struct libnet_keytab_context *ctx,
+ uint32_t kvno,
+ const char *name,
+ const char *prefix,
+ const krb5_enctype enctype,
+ DATA_BLOB blob);
+#endif /* HAVE_KRB5 */
diff --git a/source3/libnet/netapi.pc.in b/source3/libnet/netapi.pc.in
new file mode 100644
index 0000000..a699027
--- /dev/null
+++ b/source3/libnet/netapi.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Samba libnetapi
+Description: A library to control CIFS servers
+Version: @PACKAGE_VERSION@
+Libs: @LIB_RPATH@ -L${libdir} -lnetapi
+Cflags: -I${includedir}
+URL: http://www.samba.org/
diff --git a/source3/librpc/crypto/gse.c b/source3/librpc/crypto/gse.c
new file mode 100644
index 0000000..ddda18e
--- /dev/null
+++ b/source3/librpc/crypto/gse.c
@@ -0,0 +1,1438 @@
+/*
+ * GSSAPI Security Extensions
+ * RPC Pipe client and server routines
+ * Copyright (C) Simo Sorce 2010.
+ * Copyright (C) Andrew Bartlett 2004-2011.
+ * Copyright (C) Stefan Metzmacher <metze@samba.org> 2004-2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* We support only GSSAPI/KRB5 here */
+
+#include "includes.h"
+#include <tevent.h>
+#include "lib/util/tevent_ntstatus.h"
+#include "gse.h"
+#include "libads/kerberos_proto.h"
+#include "auth/common_auth.h"
+#include "auth/gensec/gensec.h"
+#include "auth/gensec/gensec_internal.h"
+#include "auth/credentials/credentials.h"
+#include "../librpc/gen_ndr/dcerpc.h"
+#include "param/param.h"
+
+#if defined(HAVE_KRB5)
+
+#include "auth/kerberos/pac_utils.h"
+#include "auth/kerberos/gssapi_helper.h"
+#include "gse_krb5.h"
+
+static char *gse_errstr(TALLOC_CTX *mem_ctx, OM_uint32 maj, OM_uint32 min);
+static size_t gensec_gse_sig_size(struct gensec_security *gensec_security,
+ size_t data_size);
+
+struct gse_context {
+ gss_ctx_id_t gssapi_context;
+ gss_name_t server_name;
+ gss_name_t client_name;
+ OM_uint32 gss_want_flags, gss_got_flags;
+ size_t max_wrap_buf_size;
+ size_t sig_size;
+
+ gss_cred_id_t delegated_cred_handle;
+
+ NTTIME expire_time;
+
+ /* gensec_gse only */
+ krb5_context k5ctx;
+ krb5_ccache ccache;
+ krb5_keytab keytab;
+
+ gss_OID_desc gss_mech;
+ gss_cred_id_t creds;
+
+ gss_OID ret_mech;
+};
+
+/* free non talloc dependent contexts */
+static int gse_context_destructor(void *ptr)
+{
+ struct gse_context *gse_ctx;
+ OM_uint32 gss_min;
+
+ gse_ctx = talloc_get_type_abort(ptr, struct gse_context);
+ if (gse_ctx->k5ctx) {
+ if (gse_ctx->ccache) {
+ krb5_cc_close(gse_ctx->k5ctx, gse_ctx->ccache);
+ gse_ctx->ccache = NULL;
+ }
+ if (gse_ctx->keytab) {
+ krb5_kt_close(gse_ctx->k5ctx, gse_ctx->keytab);
+ gse_ctx->keytab = NULL;
+ }
+ krb5_free_context(gse_ctx->k5ctx);
+ gse_ctx->k5ctx = NULL;
+ }
+ if (gse_ctx->gssapi_context != GSS_C_NO_CONTEXT) {
+ (void)gss_delete_sec_context(&gss_min,
+ &gse_ctx->gssapi_context,
+ GSS_C_NO_BUFFER);
+ }
+ if (gse_ctx->server_name) {
+ (void)gss_release_name(&gss_min,
+ &gse_ctx->server_name);
+ }
+ if (gse_ctx->client_name) {
+ (void)gss_release_name(&gss_min,
+ &gse_ctx->client_name);
+ }
+ if (gse_ctx->creds) {
+ (void)gss_release_cred(&gss_min,
+ &gse_ctx->creds);
+ }
+ if (gse_ctx->delegated_cred_handle) {
+ (void)gss_release_cred(&gss_min,
+ &gse_ctx->delegated_cred_handle);
+ }
+
+ /* MIT and Heimdal differ as to if you can call
+ * gss_release_oid() on this OID, generated by
+ * gss_{accept,init}_sec_context(). However, as long as the
+ * oid is gss_mech_krb5 (which it always is at the moment),
+ * then this is a moot point, as both declare this particular
+ * OID static, and so no memory is lost. This assert is in
+ * place to ensure that the programmer who wishes to extend
+ * this code to EAP or other GSS mechanisms determines an
+ * implementation-dependent way of releasing any dynamically
+ * allocated OID */
+ SMB_ASSERT(smb_gss_oid_equal(&gse_ctx->gss_mech, GSS_C_NO_OID) ||
+ smb_gss_oid_equal(&gse_ctx->gss_mech, gss_mech_krb5));
+
+ return 0;
+}
+
+static NTSTATUS gse_setup_server_principal(TALLOC_CTX *mem_ctx,
+ const char *target_principal,
+ const char *service,
+ const char *hostname,
+ const char *realm,
+ char **pserver_principal,
+ gss_name_t *pserver_name)
+{
+ char *server_principal = NULL;
+ gss_buffer_desc name_token;
+ gss_OID name_type;
+ OM_uint32 maj_stat, min_stat = 0;
+
+ if (target_principal != NULL) {
+ server_principal = talloc_strdup(mem_ctx, target_principal);
+ name_type = GSS_C_NULL_OID;
+ } else {
+ server_principal = talloc_asprintf(mem_ctx,
+ "%s/%s@%s",
+ service,
+ hostname,
+ realm);
+ name_type = GSS_C_NT_USER_NAME;
+ }
+ if (server_principal == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ name_token.value = (uint8_t *)server_principal;
+ name_token.length = strlen(server_principal);
+
+ maj_stat = gss_import_name(&min_stat,
+ &name_token,
+ name_type,
+ pserver_name);
+ if (maj_stat) {
+ DBG_WARNING("GSS Import name of %s failed: %s\n",
+ server_principal,
+ gse_errstr(mem_ctx, maj_stat, min_stat));
+ TALLOC_FREE(server_principal);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *pserver_principal = server_principal;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gse_context_init(TALLOC_CTX *mem_ctx,
+ bool do_sign, bool do_seal,
+ const char *ccache_name,
+ uint32_t add_gss_c_flags,
+ struct gse_context **_gse_ctx)
+{
+ struct gse_context *gse_ctx;
+ krb5_error_code k5ret;
+ NTSTATUS status;
+
+ gse_ctx = talloc_zero(mem_ctx, struct gse_context);
+ if (!gse_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor((TALLOC_CTX *)gse_ctx, gse_context_destructor);
+
+ gse_ctx->expire_time = GENSEC_EXPIRE_TIME_INFINITY;
+ gse_ctx->max_wrap_buf_size = UINT16_MAX;
+
+ memcpy(&gse_ctx->gss_mech, gss_mech_krb5, sizeof(gss_OID_desc));
+
+ gse_ctx->gss_want_flags = GSS_C_MUTUAL_FLAG |
+ GSS_C_DELEG_POLICY_FLAG |
+ GSS_C_REPLAY_FLAG |
+ GSS_C_SEQUENCE_FLAG;
+ if (do_sign) {
+ gse_ctx->gss_want_flags |= GSS_C_INTEG_FLAG;
+ }
+ if (do_seal) {
+ gse_ctx->gss_want_flags |= GSS_C_INTEG_FLAG;
+ gse_ctx->gss_want_flags |= GSS_C_CONF_FLAG;
+ }
+
+ gse_ctx->gss_want_flags |= add_gss_c_flags;
+
+ /* Initialize Kerberos Context */
+ k5ret = smb_krb5_init_context_common(&gse_ctx->k5ctx);
+ if (k5ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(k5ret));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+#ifdef SAMBA4_USES_HEIMDAL
+ k5ret = gsskrb5_set_dns_canonicalize(false);
+ if (k5ret) {
+ DBG_ERR("gsskrb5_set_dns_canonicalize() failed (%s)\n",
+ error_message(k5ret));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto err_out;
+ }
+#endif
+
+ if (!ccache_name) {
+ ccache_name = krb5_cc_default_name(gse_ctx->k5ctx);
+ }
+ k5ret = krb5_cc_resolve(gse_ctx->k5ctx, ccache_name,
+ &gse_ctx->ccache);
+ if (k5ret) {
+ DEBUG(1, ("Failed to resolve credential cache '%s'! (%s)\n",
+ ccache_name, error_message(k5ret)));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+ /* TODO: Should we enforce a enc_types list ?
+ ret = krb5_set_default_tgs_ktypes(gse_ctx->k5ctx, enc_types);
+ */
+
+ *_gse_ctx = gse_ctx;
+ return NT_STATUS_OK;
+
+err_out:
+ TALLOC_FREE(gse_ctx);
+ return status;
+}
+
+static NTSTATUS gse_init_client(struct gensec_security *gensec_security,
+ bool do_sign, bool do_seal,
+ const char *ccache_name,
+ const char *server,
+ const char *service,
+ const char *realm,
+ const char *username,
+ const char *password,
+ uint32_t add_gss_c_flags,
+ struct gse_context **_gse_ctx)
+{
+ struct gse_context *gse_ctx;
+ OM_uint32 gss_maj, gss_min;
+#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
+ gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER;
+ gss_OID oid = discard_const(GSS_KRB5_CRED_NO_CI_FLAGS_X);
+#endif
+ NTSTATUS status;
+
+ if (!server || !service) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = gse_context_init(gensec_security, do_sign, do_seal,
+ ccache_name, add_gss_c_flags,
+ &gse_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+#ifdef SAMBA4_USES_HEIMDAL
+ {
+ int ret;
+ bool set_dns_canon = gensec_setting_bool(
+ gensec_security->settings,
+ "krb5", "set_dns_canonicalize",
+ false);
+ const char *server_realm = lpcfg_realm(
+ gensec_security->settings->lp_ctx);
+ if (server_realm != NULL) {
+ ret = gsskrb5_set_default_realm(server_realm);
+ if (ret) {
+ DBG_ERR("gsskrb5_set_default_realm failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ /*
+ * don't do DNS lookups of any kind, it might/will
+ * fail for a netbios name
+ */
+ ret = gsskrb5_set_dns_canonicalize(set_dns_canon);
+ if (ret != GSS_S_COMPLETE) {
+ DBG_ERR("gsskrb5_set_dns_canonicalize failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+#endif
+
+ /* TODO: get krb5 ticket using username/password, if no valid
+ * one already available in ccache */
+
+ gss_maj = smb_gss_krb5_import_cred(&gss_min,
+ gse_ctx->k5ctx,
+ gse_ctx->ccache,
+ NULL, /* keytab_principal */
+ NULL, /* keytab */
+ &gse_ctx->creds);
+ if (gss_maj) {
+ char *ccache = NULL;
+ int kret;
+
+ kret = krb5_cc_get_full_name(gse_ctx->k5ctx,
+ gse_ctx->ccache,
+ &ccache);
+ if (kret != 0) {
+ ccache = NULL;
+ }
+
+ DEBUG(5, ("smb_gss_krb5_import_cred ccache[%s] failed with [%s] -"
+ "the caller may retry after a kinit.\n",
+ ccache, gse_errstr(gse_ctx, gss_maj, gss_min)));
+ krb5_free_string(gse_ctx->k5ctx, ccache);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto err_out;
+ }
+
+#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X
+ /*
+ * Don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG.
+ *
+ * This allows us to disable SIGN and SEAL for
+ * AUTH_LEVEL_CONNECT and AUTH_LEVEL_INTEGRITY.
+ *
+ * https://groups.yahoo.com/neo/groups/cat-ietf/conversations/topics/575
+ * http://krbdev.mit.edu/rt/Ticket/Display.html?id=6938
+ */
+ gss_maj = gss_set_cred_option(&gss_min, &gse_ctx->creds,
+ oid,
+ &empty_buffer);
+ if (gss_maj) {
+ DEBUG(0, ("gss_set_cred_option(GSS_KRB5_CRED_NO_CI_FLAGS_X), "
+ "failed with [%s]\n",
+ gse_errstr(gse_ctx, gss_maj, gss_min)));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto err_out;
+ }
+#endif
+
+ *_gse_ctx = gse_ctx;
+ return NT_STATUS_OK;
+
+err_out:
+ TALLOC_FREE(gse_ctx);
+ return status;
+}
+
+static NTSTATUS gse_get_client_auth_token(TALLOC_CTX *mem_ctx,
+ struct gensec_security *gensec_security,
+ const DATA_BLOB *token_in,
+ DATA_BLOB *token_out)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ OM_uint32 gss_maj = 0;
+ OM_uint32 gss_min;
+ gss_buffer_desc in_data;
+ gss_buffer_desc out_data;
+ DATA_BLOB blob = data_blob_null;
+ NTSTATUS status;
+ OM_uint32 time_rec = 0;
+ struct timeval tv;
+ struct cli_credentials *cli_creds = gensec_get_credentials(gensec_security);
+ const char *target_principal = gensec_get_target_principal(gensec_security);
+ const char *hostname = gensec_get_target_hostname(gensec_security);
+ const char *service = gensec_get_target_service(gensec_security);
+ const char *client_realm = cli_credentials_get_realm(cli_creds);
+ char *server_principal = NULL;
+ char *server_realm = NULL;
+ bool fallback = false;
+ OM_uint32 time_req = 0;
+
+ time_req = gensec_setting_int(gensec_security->settings,
+ "gensec_gssapi",
+ "requested_life_time",
+ time_req);
+
+ in_data.value = token_in->data;
+ in_data.length = token_in->length;
+
+ /*
+ * With credentials for administrator@FOREST1.EXAMPLE.COM this patch
+ * changes the target_principal for the ldap service of host
+ * dc2.forest2.example.com from
+ *
+ * ldap/dc2.forest2.example.com@FOREST1.EXAMPLE.COM
+ *
+ * to
+ *
+ * ldap/dc2.forest2.example.com@FOREST2.EXAMPLE.COM
+ *
+ * Typically ldap/dc2.forest2.example.com@FOREST1.EXAMPLE.COM should be
+ * used in order to allow the KDC of FOREST1.EXAMPLE.COM to generate a
+ * referral ticket for krbtgt/FOREST2.EXAMPLE.COM@FOREST1.EXAMPLE.COM.
+ *
+ * The problem is that KDCs only return such referral tickets if
+ * there's a forest trust between FOREST1.EXAMPLE.COM and
+ * FOREST2.EXAMPLE.COM. If there's only an external domain trust
+ * between FOREST1.EXAMPLE.COM and FOREST2.EXAMPLE.COM the KDC of
+ * FOREST1.EXAMPLE.COM will respond with S_PRINCIPAL_UNKNOWN when being
+ * asked for ldap/dc2.forest2.example.com@FOREST1.EXAMPLE.COM.
+ *
+ * In the case of an external trust the client can still ask explicitly
+ * for krbtgt/FOREST2.EXAMPLE.COM@FOREST1.EXAMPLE.COM and the KDC of
+ * FOREST1.EXAMPLE.COM will generate it.
+ *
+ * From there the client can use the
+ * krbtgt/FOREST2.EXAMPLE.COM@FOREST1.EXAMPLE.COM ticket and ask a KDC
+ * of FOREST2.EXAMPLE.COM for a service ticket for
+ * ldap/dc2.forest2.example.com@FOREST2.EXAMPLE.COM.
+ *
+ * With Heimdal we'll get the fallback on S_PRINCIPAL_UNKNOWN behavior
+ * when we pass ldap/dc2.forest2.example.com@FOREST2.EXAMPLE.COM as
+ * target principal. As _krb5_get_cred_kdc_any() first calls
+ * get_cred_kdc_referral() (which always starts with the client realm)
+ * and falls back to get_cred_kdc_capath() (which starts with the given
+ * realm).
+ *
+ * MIT krb5 only tries the given realm of the target principal, if we
+ * want to autodetect support for transitive forest trusts, would have
+ * to do the fallback ourself.
+ */
+#ifndef SAMBA4_USES_HEIMDAL
+ if (gse_ctx->server_name == NULL) {
+ OM_uint32 gss_min2 = 0;
+
+ status = gse_setup_server_principal(mem_ctx,
+ target_principal,
+ service,
+ hostname,
+ client_realm,
+ &server_principal,
+ &gse_ctx->server_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ gss_maj = gss_init_sec_context(&gss_min,
+ gse_ctx->creds,
+ &gse_ctx->gssapi_context,
+ gse_ctx->server_name,
+ &gse_ctx->gss_mech,
+ gse_ctx->gss_want_flags,
+ time_req,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &in_data,
+ NULL,
+ &out_data,
+ &gse_ctx->gss_got_flags,
+ &time_rec);
+ if (gss_maj != GSS_S_FAILURE) {
+ goto init_sec_context_done;
+ }
+ if (gss_min != (OM_uint32)KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) {
+ goto init_sec_context_done;
+ }
+ if (target_principal != NULL) {
+ goto init_sec_context_done;
+ }
+
+ fallback = true;
+ TALLOC_FREE(server_principal);
+ gss_release_name(&gss_min2, &gse_ctx->server_name);
+ }
+#endif /* !SAMBA4_USES_HEIMDAL */
+
+ if (gse_ctx->server_name == NULL) {
+ server_realm = smb_krb5_get_realm_from_hostname(mem_ctx,
+ hostname,
+ client_realm);
+ if (server_realm == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (fallback &&
+ strequal(client_realm, server_realm)) {
+ goto init_sec_context_done;
+ }
+
+ status = gse_setup_server_principal(mem_ctx,
+ target_principal,
+ service,
+ hostname,
+ server_realm,
+ &server_principal,
+ &gse_ctx->server_name);
+ TALLOC_FREE(server_realm);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ TALLOC_FREE(server_principal);
+ }
+
+ gss_maj = gss_init_sec_context(&gss_min,
+ gse_ctx->creds,
+ &gse_ctx->gssapi_context,
+ gse_ctx->server_name,
+ &gse_ctx->gss_mech,
+ gse_ctx->gss_want_flags,
+ time_req, GSS_C_NO_CHANNEL_BINDINGS,
+ &in_data, NULL, &out_data,
+ &gse_ctx->gss_got_flags, &time_rec);
+ goto init_sec_context_done;
+ /* JUMP! */
+init_sec_context_done:
+
+ switch (gss_maj) {
+ case GSS_S_COMPLETE:
+ /* we are done with it */
+ tv = timeval_current_ofs(time_rec, 0);
+ gse_ctx->expire_time = timeval_to_nttime(&tv);
+
+ status = NT_STATUS_OK;
+ break;
+ case GSS_S_CONTINUE_NEEDED:
+ /* we will need a third leg */
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ break;
+ case GSS_S_CONTEXT_EXPIRED:
+ /* Make SPNEGO ignore us, we can't go any further here */
+ DBG_NOTICE("Context expired\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ case GSS_S_FAILURE:
+ switch (gss_min) {
+ case (OM_uint32)KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN: {
+ gss_buffer_desc name_token = {
+ .length = 0,
+ };
+
+ gss_maj = gss_display_name(&gss_min,
+ gse_ctx->server_name,
+ &name_token,
+ NULL);
+ if (gss_maj == GSS_S_COMPLETE) {
+ DBG_NOTICE("Server principal %.*s not found\n",
+ (int)name_token.length,
+ (char *)name_token.value);
+ gss_release_buffer(&gss_maj, &name_token);
+ } else {
+ DBG_NOTICE("Server principal not found\n");
+ }
+
+ /* Make SPNEGO ignore us, we can't go any further here */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+ case (OM_uint32)KRB5KRB_AP_ERR_TKT_EXPIRED:
+ DBG_NOTICE("Ticket expired\n");
+ /* Make SPNEGO ignore us, we can't go any further here */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ case (OM_uint32)KRB5KRB_AP_ERR_TKT_NYV:
+ DBG_NOTICE("Clockskew\n");
+ /* Make SPNEGO ignore us, we can't go any further here */
+ status = NT_STATUS_TIME_DIFFERENCE_AT_DC;
+ goto done;
+ case (OM_uint32)KRB5_KDC_UNREACH:
+ DBG_NOTICE("KDC unreachable\n");
+ /* Make SPNEGO ignore us, we can't go any further here */
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ goto done;
+ case (OM_uint32)KRB5KRB_AP_ERR_MSG_TYPE:
+ /* Garbage input, possibly from the auto-mech detection */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ case (OM_uint32)KRB5KDC_ERR_ETYPE_NOSUPP:
+ status = NT_STATUS_KDC_UNKNOWN_ETYPE;
+ goto done;
+ default:
+ DBG_ERR("gss_init_sec_context failed with [%s](%u)\n",
+ gse_errstr(talloc_tos(), gss_maj, gss_min),
+ gss_min);
+ status = NT_STATUS_LOGON_FAILURE;
+ goto done;
+ }
+ break;
+ default:
+ DBG_ERR("gss_init_sec_context failed with [%s]\n",
+ gse_errstr(talloc_tos(), gss_maj, gss_min));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /* we may be told to return nothing */
+ if (out_data.length) {
+ blob = data_blob_talloc(mem_ctx, out_data.value, out_data.length);
+ if (!blob.data) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+
+ gss_release_buffer(&gss_min, &out_data);
+ }
+
+done:
+ *token_out = blob;
+ return status;
+}
+
+static NTSTATUS gse_init_server(TALLOC_CTX *mem_ctx,
+ bool do_sign, bool do_seal,
+ uint32_t add_gss_c_flags,
+ struct gse_context **_gse_ctx)
+{
+ struct gse_context *gse_ctx;
+ OM_uint32 gss_maj, gss_min;
+ krb5_error_code ret;
+ NTSTATUS status;
+
+ status = gse_context_init(mem_ctx, do_sign, do_seal,
+ NULL, add_gss_c_flags, &gse_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = gse_krb5_get_server_keytab(gse_ctx->k5ctx,
+ &gse_ctx->keytab);
+ if (ret) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /* This creates a GSSAPI cred_id_t with the keytab set */
+ gss_maj = smb_gss_krb5_import_cred(&gss_min, gse_ctx->k5ctx,
+ NULL, NULL, gse_ctx->keytab,
+ &gse_ctx->creds);
+
+ if (gss_maj != 0) {
+ DEBUG(0, ("smb_gss_krb5_import_cred failed with [%s]\n",
+ gse_errstr(gse_ctx, gss_maj, gss_min)));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(gse_ctx);
+ }
+
+ *_gse_ctx = gse_ctx;
+ return status;
+}
+
+static NTSTATUS gse_get_server_auth_token(TALLOC_CTX *mem_ctx,
+ struct gensec_security *gensec_security,
+ const DATA_BLOB *token_in,
+ DATA_BLOB *token_out)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ OM_uint32 gss_maj, gss_min;
+ gss_buffer_desc in_data;
+ gss_buffer_desc out_data;
+ DATA_BLOB blob = data_blob_null;
+ NTSTATUS status;
+ OM_uint32 time_rec = 0;
+ struct timeval tv;
+
+ in_data.value = token_in->data;
+ in_data.length = token_in->length;
+
+ gss_maj = gss_accept_sec_context(&gss_min,
+ &gse_ctx->gssapi_context,
+ gse_ctx->creds,
+ &in_data,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &gse_ctx->client_name,
+ &gse_ctx->ret_mech,
+ &out_data,
+ &gse_ctx->gss_got_flags,
+ &time_rec,
+ &gse_ctx->delegated_cred_handle);
+ switch (gss_maj) {
+ case GSS_S_COMPLETE:
+ /* we are done with it */
+ tv = timeval_current_ofs(time_rec, 0);
+ gse_ctx->expire_time = timeval_to_nttime(&tv);
+
+ status = NT_STATUS_OK;
+ break;
+ case GSS_S_CONTINUE_NEEDED:
+ /* we will need a third leg */
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ break;
+ default:
+ DEBUG(1, ("gss_accept_sec_context failed with [%s]\n",
+ gse_errstr(talloc_tos(), gss_maj, gss_min)));
+
+ if (gse_ctx->gssapi_context) {
+ gss_delete_sec_context(&gss_min,
+ &gse_ctx->gssapi_context,
+ GSS_C_NO_BUFFER);
+ }
+
+ /*
+ * If we got an output token, make Windows aware of it
+ * by telling it that more processing is needed
+ */
+ if (out_data.length > 0) {
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ /* Fall through to handle the out token */
+ } else {
+ status = NT_STATUS_LOGON_FAILURE;
+ goto done;
+ }
+ }
+
+ /* we may be told to return nothing */
+ if (out_data.length) {
+ blob = data_blob_talloc(mem_ctx, out_data.value, out_data.length);
+ if (!blob.data) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ gss_release_buffer(&gss_min, &out_data);
+ }
+
+
+done:
+ *token_out = blob;
+ return status;
+}
+
+static char *gse_errstr(TALLOC_CTX *mem_ctx, OM_uint32 maj, OM_uint32 min)
+{
+ OM_uint32 gss_min, gss_maj;
+ gss_buffer_desc msg_min;
+ gss_buffer_desc msg_maj;
+ OM_uint32 msg_ctx = 0;
+
+ char *errstr = NULL;
+
+ ZERO_STRUCT(msg_min);
+ ZERO_STRUCT(msg_maj);
+
+ gss_maj = gss_display_status(&gss_min, maj, GSS_C_GSS_CODE,
+ GSS_C_NO_OID, &msg_ctx, &msg_maj);
+ if (gss_maj) {
+ goto done;
+ }
+ errstr = talloc_strndup(mem_ctx,
+ (char *)msg_maj.value,
+ msg_maj.length);
+ if (!errstr) {
+ goto done;
+ }
+ gss_maj = gss_display_status(&gss_min, min, GSS_C_MECH_CODE,
+ (gss_OID)discard_const(gss_mech_krb5),
+ &msg_ctx, &msg_min);
+ if (gss_maj) {
+ goto done;
+ }
+
+ errstr = talloc_strdup_append_buffer(errstr, ": ");
+ if (!errstr) {
+ goto done;
+ }
+ errstr = talloc_strndup_append_buffer(errstr,
+ (char *)msg_min.value,
+ msg_min.length);
+ if (!errstr) {
+ goto done;
+ }
+
+done:
+ if (msg_min.value) {
+ gss_release_buffer(&gss_min, &msg_min);
+ }
+ if (msg_maj.value) {
+ gss_release_buffer(&gss_min, &msg_maj);
+ }
+ return errstr;
+}
+
+static NTSTATUS gensec_gse_client_start(struct gensec_security *gensec_security)
+{
+ struct gse_context *gse_ctx;
+ struct cli_credentials *creds = gensec_get_credentials(gensec_security);
+ NTSTATUS nt_status;
+ OM_uint32 want_flags = 0;
+ bool do_sign = false, do_seal = false;
+ const char *hostname = gensec_get_target_hostname(gensec_security);
+ const char *service = gensec_get_target_service(gensec_security);
+ const char *username = cli_credentials_get_username(creds);
+ const char *password = cli_credentials_get_password(creds);
+ const char *realm = cli_credentials_get_realm(creds);
+
+ if (!hostname) {
+ DEBUG(1, ("Could not determine hostname for target computer, cannot use kerberos\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (is_ipaddress(hostname)) {
+ DEBUG(2, ("Cannot do GSE to an IP address\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (strcmp(hostname, "localhost") == 0) {
+ DEBUG(2, ("GSE to 'localhost' does not make sense\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) {
+ do_sign = true;
+ }
+ if (gensec_security->want_features & GENSEC_FEATURE_SIGN) {
+ do_sign = true;
+ }
+ if (gensec_security->want_features & GENSEC_FEATURE_SEAL) {
+ do_seal = true;
+ }
+ if (gensec_security->want_features & GENSEC_FEATURE_DCE_STYLE) {
+ want_flags |= GSS_C_DCE_STYLE;
+ }
+
+ nt_status = gse_init_client(gensec_security, do_sign, do_seal, NULL,
+ hostname, service, realm,
+ username, password, want_flags,
+ &gse_ctx);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ gensec_security->private_data = gse_ctx;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_gse_server_start(struct gensec_security *gensec_security)
+{
+ struct gse_context *gse_ctx;
+ NTSTATUS nt_status;
+ OM_uint32 want_flags = 0;
+ bool do_sign = false, do_seal = false;
+
+ if (gensec_security->want_features & GENSEC_FEATURE_SIGN) {
+ do_sign = true;
+ }
+ if (gensec_security->want_features & GENSEC_FEATURE_SEAL) {
+ do_seal = true;
+ }
+ if (gensec_security->want_features & GENSEC_FEATURE_DCE_STYLE) {
+ want_flags |= GSS_C_DCE_STYLE;
+ }
+
+ nt_status = gse_init_server(gensec_security, do_sign, do_seal, want_flags,
+ &gse_ctx);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ gensec_security->private_data = gse_ctx;
+ return NT_STATUS_OK;
+}
+
+struct gensec_gse_update_state {
+ NTSTATUS status;
+ DATA_BLOB out;
+};
+
+static NTSTATUS gensec_gse_update_internal(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ const DATA_BLOB in,
+ DATA_BLOB *out);
+
+static struct tevent_req *gensec_gse_update_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct gensec_security *gensec_security,
+ const DATA_BLOB in)
+{
+ struct tevent_req *req = NULL;
+ struct gensec_gse_update_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct gensec_gse_update_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = gensec_gse_update_internal(gensec_security,
+ state, in,
+ &state->out);
+ state->status = status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS gensec_gse_update_internal(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ const DATA_BLOB in,
+ DATA_BLOB *out)
+{
+ NTSTATUS status;
+
+ switch (gensec_security->gensec_role) {
+ case GENSEC_CLIENT:
+ status = gse_get_client_auth_token(mem_ctx,
+ gensec_security,
+ &in, out);
+ break;
+ case GENSEC_SERVER:
+ status = gse_get_server_auth_token(mem_ctx,
+ gensec_security,
+ &in, out);
+ break;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_gse_update_recv(struct tevent_req *req,
+ TALLOC_CTX *out_mem_ctx,
+ DATA_BLOB *out)
+{
+ struct gensec_gse_update_state *state =
+ tevent_req_data(req,
+ struct gensec_gse_update_state);
+ NTSTATUS status;
+
+ *out = data_blob_null;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out = state->out;
+ talloc_steal(out_mem_ctx, state->out.data);
+ status = state->status;
+ tevent_req_received(req);
+ return status;
+}
+
+static NTSTATUS gensec_gse_wrap(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *in,
+ DATA_BLOB *out)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ OM_uint32 maj_stat, min_stat;
+ gss_buffer_desc input_token, output_token;
+ int conf_state;
+ input_token.length = in->length;
+ input_token.value = in->data;
+
+ maj_stat = gss_wrap(&min_stat,
+ gse_ctx->gssapi_context,
+ gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL),
+ GSS_C_QOP_DEFAULT,
+ &input_token,
+ &conf_state,
+ &output_token);
+ if (GSS_ERROR(maj_stat)) {
+ DEBUG(0, ("gensec_gse_wrap: GSS Wrap failed: %s\n",
+ gse_errstr(talloc_tos(), maj_stat, min_stat)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length);
+ gss_release_buffer(&min_stat, &output_token);
+
+ if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)
+ && !conf_state) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_gse_unwrap(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *in,
+ DATA_BLOB *out)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ OM_uint32 maj_stat, min_stat;
+ gss_buffer_desc input_token, output_token;
+ int conf_state;
+ gss_qop_t qop_state;
+ input_token.length = in->length;
+ input_token.value = in->data;
+
+ maj_stat = gss_unwrap(&min_stat,
+ gse_ctx->gssapi_context,
+ &input_token,
+ &output_token,
+ &conf_state,
+ &qop_state);
+ if (GSS_ERROR(maj_stat)) {
+ DEBUG(0, ("gensec_gse_unwrap: GSS UnWrap failed: %s\n",
+ gse_errstr(talloc_tos(), maj_stat, min_stat)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ *out = data_blob_talloc(mem_ctx, output_token.value, output_token.length);
+ gss_release_buffer(&min_stat, &output_token);
+
+ if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)
+ && !conf_state) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_gse_seal_packet(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *data, size_t length,
+ const uint8_t *whole_pdu, size_t pdu_length,
+ DATA_BLOB *sig)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ bool hdr_signing = false;
+ size_t sig_size = 0;
+ NTSTATUS status;
+
+ if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) {
+ hdr_signing = true;
+ }
+
+ sig_size = gensec_gse_sig_size(gensec_security, length);
+
+ status = gssapi_seal_packet(gse_ctx->gssapi_context,
+ &gse_ctx->gss_mech,
+ hdr_signing, sig_size,
+ data, length,
+ whole_pdu, pdu_length,
+ mem_ctx, sig);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("gssapi_seal_packet(hdr_signing=%u,sig_size=%zu,"
+ "data=%zu,pdu=%zu) failed: %s\n",
+ hdr_signing, sig_size, length, pdu_length,
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_gse_unseal_packet(struct gensec_security *gensec_security,
+ uint8_t *data, size_t length,
+ const uint8_t *whole_pdu, size_t pdu_length,
+ const DATA_BLOB *sig)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ bool hdr_signing = false;
+ NTSTATUS status;
+
+ if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) {
+ hdr_signing = true;
+ }
+
+ status = gssapi_unseal_packet(gse_ctx->gssapi_context,
+ &gse_ctx->gss_mech,
+ hdr_signing,
+ data, length,
+ whole_pdu, pdu_length,
+ sig);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("gssapi_unseal_packet(hdr_signing=%u,sig_size=%zu,"
+ "data=%zu,pdu=%zu) failed: %s\n",
+ hdr_signing, sig->length, length, pdu_length,
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_gse_sign_packet(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ const uint8_t *data, size_t length,
+ const uint8_t *whole_pdu, size_t pdu_length,
+ DATA_BLOB *sig)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ bool hdr_signing = false;
+ NTSTATUS status;
+
+ if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) {
+ hdr_signing = true;
+ }
+
+ status = gssapi_sign_packet(gse_ctx->gssapi_context,
+ &gse_ctx->gss_mech,
+ hdr_signing,
+ data, length,
+ whole_pdu, pdu_length,
+ mem_ctx, sig);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("gssapi_sign_packet(hdr_signing=%u,"
+ "data=%zu,pdu=%zu) failed: %s\n",
+ hdr_signing, length, pdu_length,
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_gse_check_packet(struct gensec_security *gensec_security,
+ const uint8_t *data, size_t length,
+ const uint8_t *whole_pdu, size_t pdu_length,
+ const DATA_BLOB *sig)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ bool hdr_signing = false;
+ NTSTATUS status;
+
+ if (gensec_security->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) {
+ hdr_signing = true;
+ }
+
+ status = gssapi_check_packet(gse_ctx->gssapi_context,
+ &gse_ctx->gss_mech,
+ hdr_signing,
+ data, length,
+ whole_pdu, pdu_length,
+ sig);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("gssapi_check_packet(hdr_signing=%u,sig_size=%zu"
+ "data=%zu,pdu=%zu) failed: %s\n",
+ hdr_signing, sig->length, length, pdu_length,
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* Try to figure out what features we actually got on the connection */
+static bool gensec_gse_have_feature(struct gensec_security *gensec_security,
+ uint32_t feature)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+
+ if (feature & GENSEC_FEATURE_SESSION_KEY) {
+ return gse_ctx->gss_got_flags & GSS_C_INTEG_FLAG;
+ }
+ if (feature & GENSEC_FEATURE_SIGN) {
+ return gse_ctx->gss_got_flags & GSS_C_INTEG_FLAG;
+ }
+ if (feature & GENSEC_FEATURE_SEAL) {
+ return gse_ctx->gss_got_flags & GSS_C_CONF_FLAG;
+ }
+ if (feature & GENSEC_FEATURE_DCE_STYLE) {
+ return gse_ctx->gss_got_flags & GSS_C_DCE_STYLE;
+ }
+ if (feature & GENSEC_FEATURE_NEW_SPNEGO) {
+ NTSTATUS status;
+ uint32_t keytype;
+
+ if (!(gse_ctx->gss_got_flags & GSS_C_INTEG_FLAG)) {
+ return false;
+ }
+
+ status = gssapi_get_session_key(talloc_tos(),
+ gse_ctx->gssapi_context, NULL, &keytype);
+ /*
+ * We should do a proper sig on the mechListMic unless
+ * we know we have to be backwards compatible with
+ * earlier windows versions.
+ *
+ * Negotiating a non-krb5
+ * mech for example should be regarded as having
+ * NEW_SPNEGO
+ */
+ if (NT_STATUS_IS_OK(status)) {
+ switch (keytype) {
+ case ENCTYPE_DES_CBC_CRC:
+ case ENCTYPE_DES_CBC_MD5:
+ case ENCTYPE_ARCFOUR_HMAC:
+ case ENCTYPE_DES3_CBC_SHA1:
+ return false;
+ }
+ }
+ return true;
+ }
+ /* We can always do async (rather than strict request/reply) packets. */
+ if (feature & GENSEC_FEATURE_ASYNC_REPLIES) {
+ return true;
+ }
+ if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) {
+ return true;
+ }
+ return false;
+}
+
+static NTTIME gensec_gse_expire_time(struct gensec_security *gensec_security)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+
+ return gse_ctx->expire_time;
+}
+
+/*
+ * Extract the 'session key' needed by SMB signing and ncacn_np
+ * (for encrypting some passwords).
+ *
+ * This breaks all the abstractions, but what do you expect...
+ */
+static NTSTATUS gensec_gse_session_key(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *session_key)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+
+ return gssapi_get_session_key(mem_ctx, gse_ctx->gssapi_context, session_key, NULL);
+}
+
+/* Get some basic (and authorization) information about the user on
+ * this session. This uses either the PAC (if present) or a local
+ * database lookup */
+static NTSTATUS gensec_gse_session_info(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
+ struct auth_session_info **_session_info)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ NTSTATUS nt_status;
+ TALLOC_CTX *tmp_ctx;
+ struct auth_session_info *session_info = NULL;
+ OM_uint32 maj_stat, min_stat;
+ DATA_BLOB pac_blob, *pac_blob_ptr = NULL;
+
+ gss_buffer_desc name_token;
+ char *principal_string;
+
+ tmp_ctx = talloc_named(mem_ctx, 0, "gensec_gse_session_info context");
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ maj_stat = gss_display_name(&min_stat,
+ gse_ctx->client_name,
+ &name_token,
+ NULL);
+ if (GSS_ERROR(maj_stat)) {
+ DEBUG(1, ("GSS display_name failed: %s\n",
+ gse_errstr(talloc_tos(), maj_stat, min_stat)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_FOOBAR;
+ }
+
+ principal_string = talloc_strndup(tmp_ctx,
+ (const char *)name_token.value,
+ name_token.length);
+
+ gss_release_buffer(&min_stat, &name_token);
+
+ if (!principal_string) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = gssapi_obtain_pac_blob(tmp_ctx, gse_ctx->gssapi_context,
+ gse_ctx->client_name,
+ &pac_blob);
+
+ /* IF we have the PAC - otherwise we need to get this
+ * data from elsewhere
+ */
+ if (NT_STATUS_IS_OK(nt_status)) {
+ pac_blob_ptr = &pac_blob;
+ }
+ nt_status = gensec_generate_session_info_pac(tmp_ctx,
+ gensec_security,
+ NULL,
+ pac_blob_ptr, principal_string,
+ gensec_get_remote_address(gensec_security),
+ &session_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ nt_status = gensec_gse_session_key(gensec_security, session_info,
+ &session_info->session_key);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ talloc_free(tmp_ctx);
+ return nt_status;
+ }
+
+ *_session_info = talloc_move(mem_ctx, &session_info);
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+static size_t gensec_gse_max_input_size(struct gensec_security *gensec_security)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ OM_uint32 maj_stat, min_stat;
+ OM_uint32 max_input_size;
+
+ maj_stat = gss_wrap_size_limit(&min_stat,
+ gse_ctx->gssapi_context,
+ gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL),
+ GSS_C_QOP_DEFAULT,
+ gse_ctx->max_wrap_buf_size,
+ &max_input_size);
+ if (GSS_ERROR(maj_stat)) {
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ DEBUG(1, ("gensec_gssapi_max_input_size: determining signature size with gss_wrap_size_limit failed: %s\n",
+ gse_errstr(mem_ctx, maj_stat, min_stat)));
+ talloc_free(mem_ctx);
+ return 0;
+ }
+
+ return max_input_size;
+}
+
+/* Find out the maximum output size negotiated on this connection */
+static size_t gensec_gse_max_wrapped_size(struct gensec_security *gensec_security)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+ return gse_ctx->max_wrap_buf_size;
+}
+
+static size_t gensec_gse_sig_size(struct gensec_security *gensec_security,
+ size_t data_size)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+
+ if (gse_ctx->sig_size > 0) {
+ return gse_ctx->sig_size;
+ }
+
+ gse_ctx->sig_size = gssapi_get_sig_size(gse_ctx->gssapi_context,
+ &gse_ctx->gss_mech,
+ gse_ctx->gss_got_flags,
+ data_size);
+ return gse_ctx->sig_size;
+}
+
+static const char *gensec_gse_final_auth_type(struct gensec_security *gensec_security)
+{
+ struct gse_context *gse_ctx =
+ talloc_get_type_abort(gensec_security->private_data,
+ struct gse_context);
+
+ /* Only return the string for GSSAPI/Krb5 */
+ if (smb_gss_oid_equal(&gse_ctx->gss_mech,
+ gss_mech_krb5)) {
+ return GENSEC_FINAL_AUTH_TYPE_KRB5;
+ } else {
+ return "gensec_gse: UNKNOWN MECH";
+ }
+}
+
+static const char *gensec_gse_krb5_oids[] = {
+ GENSEC_OID_KERBEROS5_OLD,
+ GENSEC_OID_KERBEROS5,
+ NULL
+};
+
+const struct gensec_security_ops gensec_gse_krb5_security_ops = {
+ .name = "gse_krb5",
+ .auth_type = DCERPC_AUTH_TYPE_KRB5,
+ .oid = gensec_gse_krb5_oids,
+ .client_start = gensec_gse_client_start,
+ .server_start = gensec_gse_server_start,
+ .magic = gensec_magic_check_krb5_oid,
+ .update_send = gensec_gse_update_send,
+ .update_recv = gensec_gse_update_recv,
+ .session_key = gensec_gse_session_key,
+ .session_info = gensec_gse_session_info,
+ .sig_size = gensec_gse_sig_size,
+ .sign_packet = gensec_gse_sign_packet,
+ .check_packet = gensec_gse_check_packet,
+ .seal_packet = gensec_gse_seal_packet,
+ .unseal_packet = gensec_gse_unseal_packet,
+ .max_input_size = gensec_gse_max_input_size,
+ .max_wrapped_size = gensec_gse_max_wrapped_size,
+ .wrap = gensec_gse_wrap,
+ .unwrap = gensec_gse_unwrap,
+ .have_feature = gensec_gse_have_feature,
+ .expire_time = gensec_gse_expire_time,
+ .final_auth_type = gensec_gse_final_auth_type,
+ .enabled = true,
+ .kerberos = true,
+ .priority = GENSEC_GSSAPI
+};
+
+#endif /* HAVE_KRB5 */
diff --git a/source3/librpc/crypto/gse.h b/source3/librpc/crypto/gse.h
new file mode 100644
index 0000000..8618573
--- /dev/null
+++ b/source3/librpc/crypto/gse.h
@@ -0,0 +1,26 @@
+/*
+ * GSSAPI Security Extensions
+ * Copyright (C) Simo Sorce 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GSE_H_
+#define _GSE_H_
+
+struct gse_context;
+
+extern const struct gensec_security_ops gensec_gse_krb5_security_ops;
+
+#endif /* _GSE_H_ */
diff --git a/source3/librpc/crypto/gse_krb5.c b/source3/librpc/crypto/gse_krb5.c
new file mode 100644
index 0000000..f1e0e51
--- /dev/null
+++ b/source3/librpc/crypto/gse_krb5.c
@@ -0,0 +1,609 @@
+/*
+ * GSSAPI Security Extensions
+ * Krb5 helpers
+ * Copyright (C) Simo Sorce 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smb_krb5.h"
+#include "secrets.h"
+#include "librpc/gen_ndr/secrets.h"
+#include "gse_krb5.h"
+#include "lib/param/loadparm.h"
+#include "libads/kerberos_proto.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_KRB5
+
+static krb5_error_code flush_keytab(krb5_context krbctx, krb5_keytab keytab)
+{
+ krb5_error_code ret;
+ krb5_kt_cursor kt_cursor;
+ krb5_keytab_entry kt_entry;
+
+ ZERO_STRUCT(kt_entry);
+
+ ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = krb5_kt_next_entry(krbctx, keytab, &kt_entry, &kt_cursor);
+ while (ret == 0) {
+
+ /* we need to close and reopen enumeration because we modify
+ * the keytab */
+ ret = krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
+ if (ret != 0) {
+ DEBUG(1, (__location__ ": krb5_kt_end_seq_get() "
+ "failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ /* remove the entry */
+ ret = krb5_kt_remove_entry(krbctx, keytab, &kt_entry);
+ if (ret != 0) {
+ DEBUG(1, (__location__ ": krb5_kt_remove_entry() "
+ "failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ smb_krb5_kt_free_entry(krbctx, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+
+ /* now reopen */
+ ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
+ if (ret != 0) {
+ DEBUG(1, (__location__ ": krb5_kt_start_seq() failed "
+ "(%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ ret = krb5_kt_next_entry(krbctx, keytab,
+ &kt_entry, &kt_cursor);
+ }
+
+ if (ret != KRB5_KT_END && ret != ENOENT) {
+ DEBUG(1, (__location__ ": flushing keytab we got [%s]!\n",
+ error_message(ret)));
+ }
+
+ ret = krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
+ if (ret != 0) {
+ DEBUG(1, (__location__ ": krb5_kt_end_seq_get() "
+ "failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static krb5_error_code fill_keytab_from_password(krb5_context krbctx,
+ krb5_keytab keytab,
+ krb5_principal princ,
+ krb5_kvno vno,
+ struct secrets_domain_info1_password *pw)
+{
+ krb5_error_code ret;
+ krb5_enctype *enctypes;
+ uint16_t i;
+
+ ret = smb_krb5_get_allowed_etypes(krbctx, &enctypes);
+ if (ret) {
+ DEBUG(1, (__location__
+ ": Can't determine permitted enctypes!\n"));
+ return ret;
+ }
+
+ for (i = 0; i < pw->num_keys; i++) {
+ krb5_keytab_entry kt_entry;
+ krb5_keyblock *key = NULL;
+ unsigned int ei;
+ bool found_etype = false;
+
+ for (ei=0; enctypes[ei] != 0; ei++) {
+ if ((uint32_t)enctypes[ei] != pw->keys[i].keytype) {
+ continue;
+ }
+
+ found_etype = true;
+ break;
+ }
+
+ if (!found_etype) {
+ continue;
+ }
+
+ ZERO_STRUCT(kt_entry);
+ kt_entry.principal = princ;
+ kt_entry.vno = vno;
+
+ key = KRB5_KT_KEY(&kt_entry);
+ KRB5_KEY_TYPE(key) = pw->keys[i].keytype;
+ KRB5_KEY_DATA(key) = pw->keys[i].value.data;
+ KRB5_KEY_LENGTH(key) = pw->keys[i].value.length;
+
+ ret = krb5_kt_add_entry(krbctx, keytab, &kt_entry);
+ if (ret) {
+ DEBUG(1, (__location__ ": Failed to add entry to "
+ "keytab for enctype %d (error: %s)\n",
+ (unsigned)pw->keys[i].keytype,
+ error_message(ret)));
+ goto out;
+ }
+ }
+
+ ret = 0;
+
+out:
+ krb5_free_enctypes(krbctx, enctypes);
+ return ret;
+}
+
+#define SRV_MEM_KEYTAB_NAME "MEMORY:cifs_srv_keytab"
+#define CLEARTEXT_PRIV_ENCTYPE -99
+
+static krb5_error_code fill_mem_keytab_from_secrets(krb5_context krbctx,
+ krb5_keytab *keytab)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ krb5_error_code ret, ret2;
+ const char *domain = lp_workgroup();
+ struct secrets_domain_info1 *info = NULL;
+ const char *realm = NULL;
+ const DATA_BLOB *ct = NULL;
+ krb5_kt_cursor kt_cursor;
+ krb5_keytab_entry kt_entry;
+ krb5_principal princ = NULL;
+ krb5_kvno kvno = 0; /* FIXME: fetch current vno from KDC ? */
+ NTSTATUS status;
+
+ if (!secrets_init()) {
+ DEBUG(1, (__location__ ": secrets_init failed\n"));
+ TALLOC_FREE(frame);
+ return KRB5_CONFIG_CANTOPEN;
+ }
+
+ status = secrets_fetch_or_upgrade_domain_info(domain,
+ frame,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("secrets_fetch_or_upgrade_domain_info(%s) - %s\n",
+ domain, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return KRB5_LIBOS_CANTREADPWD;
+ }
+ ct = &info->password->cleartext_blob;
+
+ if (info->domain_info.dns_domain.string != NULL) {
+ realm = strupper_talloc(frame,
+ info->domain_info.dns_domain.string);
+ if (realm == NULL) {
+ TALLOC_FREE(frame);
+ return ENOMEM;
+ }
+ }
+
+ ZERO_STRUCT(kt_entry);
+ ZERO_STRUCT(kt_cursor);
+
+ /* check if the keytab already has any entry */
+ ret = krb5_kt_start_seq_get(krbctx, *keytab, &kt_cursor);
+ if (ret != 0) {
+ goto out;
+ }
+
+ /* check if we have our special enctype used to hold
+ * the clear text password. If so, check it out so that
+ * we can verify if the keytab needs to be upgraded */
+ while ((ret = krb5_kt_next_entry(krbctx, *keytab,
+ &kt_entry, &kt_cursor)) == 0) {
+ if (smb_krb5_kt_get_enctype_from_entry(&kt_entry) ==
+ CLEARTEXT_PRIV_ENCTYPE) {
+ break;
+ }
+ smb_krb5_kt_free_entry(krbctx, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ }
+
+ ret2 = krb5_kt_end_seq_get(krbctx, *keytab, &kt_cursor);
+ if (ret2 != 0) {
+ ret = ret2;
+ DEBUG(1, (__location__ ": krb5_kt_end_seq_get() "
+ "failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ if (ret != 0 && ret != KRB5_KT_END && ret != ENOENT ) {
+ /* Error parsing keytab */
+ DEBUG(1, (__location__ ": Failed to parse memory "
+ "keytab!\n"));
+ goto out;
+ }
+
+ if (ret == 0) {
+ /* found private entry,
+ * check if keytab is up to date */
+
+ if ((ct->length == KRB5_KEY_LENGTH(KRB5_KT_KEY(&kt_entry))) &&
+ (mem_equal_const_time(KRB5_KEY_DATA(KRB5_KT_KEY(&kt_entry)),
+ ct->data, ct->length))) {
+ /* keytab is already up to date, return */
+ smb_krb5_kt_free_entry(krbctx, &kt_entry);
+ goto out;
+ }
+
+ smb_krb5_kt_free_entry(krbctx, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+
+
+ /* flush keytab, we need to regen it */
+ ret = flush_keytab(krbctx, *keytab);
+ if (ret) {
+ DEBUG(1, (__location__ ": Failed to flush "
+ "memory keytab!\n"));
+ goto out;
+ }
+ }
+
+ /* keytab is not up to date, fill it up */
+
+ ret = smb_krb5_make_principal(krbctx, &princ, realm,
+ info->account_name, NULL);
+ if (ret) {
+ DEBUG(1, (__location__ ": Failed to get host principal!\n"));
+ goto out;
+ }
+
+ ret = fill_keytab_from_password(krbctx, *keytab,
+ princ, kvno,
+ info->password);
+ if (ret) {
+ DBG_WARNING("fill_keytab_from_password() failed for "
+ "info->password.\n.");
+ goto out;
+ }
+
+ if (info->old_password != NULL) {
+ ret = fill_keytab_from_password(krbctx, *keytab,
+ princ, kvno - 1,
+ info->old_password);
+ if (ret) {
+ DBG_WARNING("fill_keytab_from_password() failed for "
+ "info->old_password.\n.");
+ goto out;
+ }
+ }
+
+ if (info->older_password != NULL) {
+ ret = fill_keytab_from_password(krbctx, *keytab,
+ princ, kvno - 2,
+ info->older_password);
+ if (ret) {
+ DBG_WARNING("fill_keytab_from_password() failed for "
+ "info->older_password.\n.");
+ goto out;
+ }
+ }
+
+ if (info->next_change != NULL) {
+ ret = fill_keytab_from_password(krbctx, *keytab,
+ princ, kvno - 3,
+ info->next_change->password);
+ if (ret) {
+ DBG_WARNING("fill_keytab_from_password() failed for "
+ "info->next_change->password.\n.");
+ goto out;
+ }
+ }
+
+ /* add our private enctype + cleartext password so that we can
+ * update the keytab if secrets change later on */
+ ZERO_STRUCT(kt_entry);
+ kt_entry.principal = princ;
+ kt_entry.vno = 0;
+
+ KRB5_KEY_TYPE(KRB5_KT_KEY(&kt_entry)) = CLEARTEXT_PRIV_ENCTYPE;
+ KRB5_KEY_LENGTH(KRB5_KT_KEY(&kt_entry)) = ct->length;
+ KRB5_KEY_DATA(KRB5_KT_KEY(&kt_entry)) = ct->data;
+
+ ret = krb5_kt_add_entry(krbctx, *keytab, &kt_entry);
+ if (ret) {
+ DEBUG(1, (__location__ ": Failed to add entry to "
+ "keytab for private enctype (%d) (error: %s)\n",
+ CLEARTEXT_PRIV_ENCTYPE, error_message(ret)));
+ goto out;
+ }
+
+ ret = 0;
+
+out:
+
+ if (princ) {
+ krb5_free_principal(krbctx, princ);
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static krb5_error_code fill_mem_keytab_from_system_keytab(krb5_context krbctx,
+ krb5_keytab *mkeytab)
+{
+ krb5_error_code ret = 0;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor kt_cursor = { 0, };
+ krb5_keytab_entry kt_entry = { 0, };
+ char *valid_princ_formats[7] = { NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL };
+ char *entry_princ_s = NULL;
+ fstring my_name, my_fqdn;
+ unsigned i;
+ int err;
+
+ /* Generate the list of principal names which we expect
+ * clients might want to use for authenticating to the file
+ * service. We allow name$,{host,cifs}/{name,fqdn,name.REALM}. */
+
+ fstrcpy(my_name, lp_netbios_name());
+
+ my_fqdn[0] = '\0';
+ name_to_fqdn(my_fqdn, lp_netbios_name());
+
+ err = asprintf(&valid_princ_formats[0],
+ "%s$@%s", my_name, lp_realm());
+ if (err == -1) {
+ ret = ENOMEM;
+ goto out;
+ }
+ err = asprintf(&valid_princ_formats[1],
+ "host/%s@%s", my_name, lp_realm());
+ if (err == -1) {
+ ret = ENOMEM;
+ goto out;
+ }
+ err = asprintf(&valid_princ_formats[2],
+ "host/%s@%s", my_fqdn, lp_realm());
+ if (err == -1) {
+ ret = ENOMEM;
+ goto out;
+ }
+ err = asprintf(&valid_princ_formats[3],
+ "host/%s.%s@%s", my_name, lp_realm(), lp_realm());
+ if (err == -1) {
+ ret = ENOMEM;
+ goto out;
+ }
+ err = asprintf(&valid_princ_formats[4],
+ "cifs/%s@%s", my_name, lp_realm());
+ if (err == -1) {
+ ret = ENOMEM;
+ goto out;
+ }
+ err = asprintf(&valid_princ_formats[5],
+ "cifs/%s@%s", my_fqdn, lp_realm());
+ if (err == -1) {
+ ret = ENOMEM;
+ goto out;
+ }
+ err = asprintf(&valid_princ_formats[6],
+ "cifs/%s.%s@%s", my_name, lp_realm(), lp_realm());
+ if (err == -1) {
+ ret = ENOMEM;
+ goto out;
+ }
+
+ ret = smb_krb5_kt_open_relative(krbctx, NULL, false, &keytab);
+ if (ret) {
+ DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
+ error_message(ret)));
+ goto out;
+ }
+
+ /*
+ * Iterate through the keytab. For each key, if the principal
+ * name case-insensitively matches one of the allowed formats,
+ * copy it to the memory keytab.
+ */
+
+ ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
+ if (ret) {
+ DEBUG(1, (__location__ ": krb5_kt_start_seq_get failed (%s)\n",
+ error_message(ret)));
+ /*
+ * krb5_kt_start_seq_get() may leaves bogus data
+ * in kt_cursor. And we want to use the all_zero()
+ * logic below.
+ *
+ * See bug #10490
+ */
+ ZERO_STRUCT(kt_cursor);
+ goto out;
+ }
+
+ while ((krb5_kt_next_entry(krbctx, keytab,
+ &kt_entry, &kt_cursor) == 0)) {
+ ret = smb_krb5_unparse_name(talloc_tos(), krbctx,
+ kt_entry.principal,
+ &entry_princ_s);
+ if (ret) {
+ DEBUG(1, (__location__ ": smb_krb5_unparse_name "
+ "failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
+
+ if (!strequal(entry_princ_s, valid_princ_formats[i])) {
+ continue;
+ }
+
+ ret = krb5_kt_add_entry(krbctx, *mkeytab, &kt_entry);
+ if (ret) {
+ DEBUG(1, (__location__ ": smb_krb5_unparse_name "
+ "failed (%s)\n", error_message(ret)));
+ goto out;
+ }
+ }
+
+ /* Free the name we parsed. */
+ TALLOC_FREE(entry_princ_s);
+
+ /* Free the entry we just read. */
+ smb_krb5_kt_free_entry(krbctx, &kt_entry);
+ ZERO_STRUCT(kt_entry);
+ }
+ krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
+
+ ZERO_STRUCT(kt_cursor);
+
+out:
+
+ for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
+ SAFE_FREE(valid_princ_formats[i]);
+ }
+
+ TALLOC_FREE(entry_princ_s);
+
+ if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
+ smb_krb5_kt_free_entry(krbctx, &kt_entry);
+ }
+
+ if (!all_zero((uint8_t *)&kt_cursor, sizeof(kt_cursor)) && keytab) {
+ krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
+ }
+
+ if (keytab) {
+ krb5_kt_close(krbctx, keytab);
+ }
+
+ return ret;
+}
+
+static krb5_error_code fill_mem_keytab_from_dedicated_keytab(krb5_context krbctx,
+ krb5_keytab *mkeytab)
+{
+ krb5_error_code ret = 0;
+ krb5_keytab keytab = NULL;
+ krb5_kt_cursor kt_cursor;
+ krb5_keytab_entry kt_entry;
+
+ ret = smb_krb5_kt_open(krbctx, lp_dedicated_keytab_file(),
+ false, &keytab);
+ if (ret) {
+ DEBUG(1, ("smb_krb5_kt_open of %s failed (%s)\n",
+ lp_dedicated_keytab_file(),
+ error_message(ret)));
+ return ret;
+ }
+
+ /*
+ * Copy the dedicated keyab to our in-memory keytab.
+ */
+
+ ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
+ if (ret) {
+ DEBUG(1, (__location__ ": krb5_kt_start_seq_get on %s "
+ "failed (%s)\n",
+ lp_dedicated_keytab_file(),
+ error_message(ret)));
+ goto out;
+ }
+
+ while ((krb5_kt_next_entry(krbctx, keytab,
+ &kt_entry, &kt_cursor) == 0)) {
+
+ ret = krb5_kt_add_entry(krbctx, *mkeytab, &kt_entry);
+
+ /* Free the entry we just read. */
+ smb_krb5_kt_free_entry(krbctx, &kt_entry);
+
+ if (ret) {
+ DEBUG(1, (__location__ ": smb_krb5_unparse_name "
+ "failed (%s)\n", error_message(ret)));
+ break;
+ }
+ }
+ krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
+
+out:
+
+ krb5_kt_close(krbctx, keytab);
+
+ return ret;
+}
+
+krb5_error_code gse_krb5_get_server_keytab(krb5_context krbctx,
+ krb5_keytab *keytab)
+{
+ krb5_error_code ret = 0;
+ krb5_error_code ret1 = 0;
+ krb5_error_code ret2 = 0;
+
+ *keytab = NULL;
+
+ /* create memory keytab */
+ ret = krb5_kt_resolve(krbctx, SRV_MEM_KEYTAB_NAME, keytab);
+ if (ret) {
+ DEBUG(1, (__location__ ": Failed to get memory "
+ "keytab!\n"));
+ return ret;
+ }
+
+ switch (lp_kerberos_method()) {
+ default:
+ case KERBEROS_VERIFY_SECRETS:
+ ret = fill_mem_keytab_from_secrets(krbctx, keytab);
+ break;
+ case KERBEROS_VERIFY_SYSTEM_KEYTAB:
+ ret = fill_mem_keytab_from_system_keytab(krbctx, keytab);
+ break;
+ case KERBEROS_VERIFY_DEDICATED_KEYTAB:
+ /* just use whatever keytab is configured */
+ ret = fill_mem_keytab_from_dedicated_keytab(krbctx, keytab);
+ break;
+ case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
+ ret1 = fill_mem_keytab_from_secrets(krbctx, keytab);
+ if (ret1) {
+ DEBUG(3, (__location__ ": Warning! Unable to set mem "
+ "keytab from secrets!\n"));
+ }
+ /* Now append system keytab keys too */
+ ret2 = fill_mem_keytab_from_system_keytab(krbctx, keytab);
+ if (ret2) {
+ DEBUG(3, (__location__ ": Warning! Unable to set mem "
+ "keytab from system keytab!\n"));
+ }
+ if (ret1 == 0 || ret2 == 0) {
+ ret = 0;
+ } else {
+ ret = ret1;
+ }
+ break;
+ }
+
+ if (ret) {
+ krb5_kt_close(krbctx, *keytab);
+ *keytab = NULL;
+ DEBUG(1,("%s: Error! Unable to set mem keytab - %d\n",
+ __location__, ret));
+ }
+
+ return ret;
+}
+
+#endif /* HAVE_KRB5 */
diff --git a/source3/librpc/crypto/gse_krb5.h b/source3/librpc/crypto/gse_krb5.h
new file mode 100644
index 0000000..ea789c9
--- /dev/null
+++ b/source3/librpc/crypto/gse_krb5.h
@@ -0,0 +1,30 @@
+/*
+ * GSSAPI Security Extensions
+ * Krb5 helpers
+ * Copyright (C) Simo Sorce 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _GSE_KRB5_H_
+#define _GSE_KRB5_H_
+
+#ifdef HAVE_KRB5
+
+krb5_error_code gse_krb5_get_server_keytab(krb5_context krbctx,
+ krb5_keytab *keytab);
+
+#endif /* HAVE_KRB5 */
+
+#endif /* _GSE_KRB5_H_ */
diff --git a/source3/librpc/gen_ndr/README b/source3/librpc/gen_ndr/README
new file mode 100644
index 0000000..5ccb89d
--- /dev/null
+++ b/source3/librpc/gen_ndr/README
@@ -0,0 +1,4 @@
+This contains the generated files from PIDL for the IDL files in ../idl/*.idl
+
+DO NOT REMOVE THIS FILE. The waf 1.5 build relies on this directory
+existing in the source tree.
diff --git a/source3/librpc/idl/IDL_LICENSE.txt b/source3/librpc/idl/IDL_LICENSE.txt
new file mode 100644
index 0000000..01ae670
--- /dev/null
+++ b/source3/librpc/idl/IDL_LICENSE.txt
@@ -0,0 +1,9 @@
+The IDL files in this directory are made available by the Samba Team
+under the following license:
+
+ Permission to use, copy, modify, and distribute these interface
+ definitions for any purpose is hereby granted without fee.
+
+ This work is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/source3/librpc/idl/ads.idl b/source3/librpc/idl/ads.idl
new file mode 100644
index 0000000..4f3a387
--- /dev/null
+++ b/source3/librpc/idl/ads.idl
@@ -0,0 +1,130 @@
+#include "idl_types.h"
+#include "config.h"
+
+/*
+ ads interface definition
+*/
+
+import "nbt.idl";
+
+cpp_quote("#include <system/network.h>")
+
+[
+ pointer_default(unique)
+]
+interface ads
+{
+ typedef [public] enum {
+ ADS_SASLWRAP_TYPE_PLAIN = 1,
+ ADS_SASLWRAP_TYPE_SIGN = 2,
+ ADS_SASLWRAP_TYPE_SEAL = 4
+ } ads_saslwrap_type;
+
+ /* ads auth control flags */
+ typedef [public] bitmap {
+ ADS_AUTH_DISABLE_KERBEROS = 0x0001,
+ ADS_AUTH_NO_BIND = 0x0002,
+ ADS_AUTH_ANON_BIND = 0x0004,
+ ADS_AUTH_SIMPLE_BIND = 0x0008,
+ ADS_AUTH_ALLOW_NTLMSSP = 0x0010,
+ ADS_AUTH_SASL_SIGN = 0x0020,
+ ADS_AUTH_SASL_SEAL = 0x0040,
+ ADS_AUTH_SASL_FORCE = 0x0080,
+ ADS_AUTH_USER_CREDS = 0x0100
+ } ads_auth_flags;
+
+ const int ADS_SASL_WRAPPING_IN_MAX_WRAPPED = 0x0FFFFFFF;
+ const int ADS_SASL_WRAPPING_OUT_MAX_WRAPPED = 0x00A00000;
+
+ typedef [nopull,nopush] struct {
+ string realm;
+ string workgroup;
+ string ldap_server;
+ boolean8 gc; /* Is this a global catalog server? */
+ boolean8 no_fallback; /* Bail if the ldap_server is not available */
+ } ads_server;
+
+ typedef [nopull,nopush] struct {
+ string realm;
+ string password;
+ string user_name;
+ string kdc_server;
+ ads_auth_flags flags;
+ int time_offset;
+ string ccache_name;
+ time_t tgt_expire;
+ time_t tgs_expire;
+ time_t renewable;
+ } ads_auth;
+
+ typedef [nopull,nopush] struct {
+ nbt_server_type flags; /* cldap flags identifying the services. */
+ string realm;
+ string bind_path;
+ string ldap_server_name;
+ string server_site_name;
+ string client_site_name;
+ time_t current_time;
+ string schema_path;
+ string config_path;
+ int ldap_page_size;
+ } ads_config;
+
+ typedef [nopull,nopush] struct {
+ uint32 ofs;
+ uint32 needed;
+ uint32 left;
+ uint32 max_wrapped;
+ uint32 min_wrapped;
+ uint32 size;
+ uint8 *buf;
+ } ads_ldap_buf_in;
+
+ typedef [nopull,nopush] struct {
+ uint32 ofs;
+ uint32 left;
+ uint32 max_unwrapped;
+ uint32 sig_size;
+ uint32 size;
+ uint8 *buf;
+ } ads_ldap_buf_out;
+
+ typedef [nopull,nopush] struct {
+ ads_saslwrap_type wrap_type;
+ [ignore] ads_saslwrap_ops *wrap_ops;
+#ifdef HAVE_LDAP_SASL_WRAPPING
+ [ignore] Sockbuf_IO_Desc *sbiod; /* lowlevel state for LDAP wrapping */
+#endif /* HAVE_LDAP_SASL_WRAPPING */
+ [ignore] TALLOC_CTX *mem_ctx;
+ [ignore] void *wrap_private_data;
+ ads_ldap_buf_in in;
+ ads_ldap_buf_out out;
+ } ads_saslwrap;
+
+ typedef [nopull,nopush] struct {
+ [ignore] ldap *ld;
+ sockaddr_storage ss; /* the ip of the active connection, if any */
+ time_t last_attempt; /* last attempt to reconnect, monotonic clock */
+ int port;
+ } ads_ldap;
+
+ typedef [public,nopull,nopush] struct {
+ /* info needed to find the server */
+ ads_server server;
+
+ /* info needed to authenticate */
+ ads_auth auth;
+
+ /* info derived from the servers config */
+ ads_config config;
+
+#ifdef HAVE_LDAP
+ ads_saslwrap ldap_wrap_data;
+ /* info about the current LDAP connection */
+ ads_ldap ldap;
+#endif /* HAVE_LDAP */
+
+ } ads_struct;
+}
+
+
diff --git a/source3/librpc/idl/leases_db.idl b/source3/librpc/idl/leases_db.idl
new file mode 100644
index 0000000..2551dfc
--- /dev/null
+++ b/source3/librpc/idl/leases_db.idl
@@ -0,0 +1,54 @@
+#include "idl_types.h"
+
+import "misc.idl";
+import "smb2_lease_struct.idl";
+import "file_id.idl";
+
+[
+ pointer_default(unique)
+]
+interface leases_db
+{
+ typedef [public] struct {
+ GUID client_guid;
+ smb2_lease_key lease_key;
+ } leases_db_key;
+
+ typedef [public] struct {
+ file_id id;
+ [string,charset(UTF8)] char *servicepath;
+ [string,charset(UTF8)] char *base_name;
+ [string,charset(UTF8)] char *stream_name;
+ } leases_db_file;
+
+ typedef [public] struct {
+ smb2_lease_state current_state;
+ /*
+ * 'breaking' indicates that we're waiting
+ * for a lease break ack from the client
+ * and breaking_to_requested and breaking_to_required
+ * have a meaning.
+ *
+ * breaking_to_requested is the value already sent to
+ * the client, the client needs to ack to this (or less).
+ *
+ * breaking_to_required is the internal value that needs to
+ * be reached before we can reset breaking = false, this
+ * may requires multiple roundtrips to the client, e.g.
+ * when the lease broken to a more reduced value, while
+ * the lease break is still in progress.
+ *
+ * The following can be assumed (if breaking == true):
+ *
+ * current_state>breaking_to_requested>=breaking_to_required
+ */
+ boolean8 breaking;
+ smb2_lease_state breaking_to_requested;
+ smb2_lease_state breaking_to_required;
+ uint16 lease_version;
+ uint16 epoch;
+
+ uint32 num_files;
+ leases_db_file files[num_files];
+ } leases_db_value;
+}
diff --git a/source3/librpc/idl/libnet_join.idl b/source3/librpc/idl/libnet_join.idl
new file mode 100644
index 0000000..4c8e658
--- /dev/null
+++ b/source3/librpc/idl/libnet_join.idl
@@ -0,0 +1,91 @@
+#include "idl_types.h"
+
+import "wkssvc.idl", "security.idl", "misc.idl", "netlogon.idl", "ODJ.idl", "ads.idl";
+
+/*
+ libnetjoin interface definition
+*/
+
+[
+ pointer_default(unique)
+]
+interface libnetjoin
+{
+ typedef bitmap wkssvc_joinflags wkssvc_joinflags;
+ typedef enum netr_SchannelType netr_SchannelType;
+
+ typedef [public] enum {
+ JoinDomNameTypeUnknown = 0,
+ JoinDomNameTypeDNS = 1,
+ JoinDomNameTypeNBT = 2
+ } libnetjoin_JoinDomNameType;
+
+ [nopush,nopull,noopnum] WERROR libnet_JoinCtx(
+ [in] string dc_name,
+ [in] string machine_name,
+ [in,ref] string *domain_name,
+ [in] libnetjoin_JoinDomNameType domain_name_type,
+ [in] string account_ou,
+ [in] string admin_account,
+ [in] string admin_domain,
+ [in,noprint] string admin_password,
+ [in] string machine_password,
+ [in] wkssvc_joinflags join_flags,
+ [in] string os_version,
+ [in] string os_name,
+ [in] string os_servicepack,
+ [in] boolean8 create_upn,
+ [in] string upn,
+ [in] string dnshostname,
+ [in] boolean8 modify_config,
+ [in,unique] ads_struct *ads,
+ [in] boolean8 debug,
+ [in] boolean8 use_kerberos,
+ [in] netr_SchannelType secure_channel_type,
+ [in,noprint] messaging_context *msg_ctx,
+ [in] uint32 desired_encryption_types,
+ [in] boolean8 provision_computer_account_only,
+ [in,out,unique] ODJ_PROVISION_DATA *odj_provision_data,
+ [in] boolean8 request_offline_join,
+ [out] string account_name,
+ [out] string netbios_domain_name,
+ [out] string dns_domain_name,
+ [out] string forest_name,
+ [out] string dn,
+ [out] GUID domain_guid,
+ [out] dom_sid *domain_sid,
+ [out] boolean8 modified_config,
+ [out] string error_string,
+ [out] boolean8 domain_is_ad,
+ [out] uint32 set_encryption_types,
+ [out] string krb5_salt,
+ [out,unique] netr_DsRGetDCNameInfo *dcinfo,
+ [out] uint32 account_rid
+ );
+
+ [nopush,nopull,noopnum] WERROR libnet_UnjoinCtx(
+ [in] string dc_name,
+ [in] string machine_name,
+ [in] string domain_name,
+ [in] string account_ou,
+ [in] string admin_account,
+ [in] string admin_domain,
+ [in,noprint] string admin_password,
+ [in] string machine_password,
+ [in] wkssvc_joinflags unjoin_flags,
+ [in] boolean8 delete_machine_account,
+ [in] boolean8 modify_config,
+ [in] dom_sid *domain_sid,
+ [in,unique] ads_struct *ads,
+ [in] boolean8 debug,
+ [in] boolean8 use_kerberos,
+ [in,noprint] messaging_context *msg_ctx,
+ [out] string netbios_domain_name,
+ [out] string dns_domain_name,
+ [out] string forest_name,
+ [out] boolean8 modified_config,
+ [out] string error_string,
+ [out] boolean8 disabled_machine_account,
+ [out] boolean8 deleted_machine_account
+ );
+}
diff --git a/source3/librpc/idl/libnetapi.idl b/source3/librpc/idl/libnetapi.idl
new file mode 100644
index 0000000..4675af6
--- /dev/null
+++ b/source3/librpc/idl/libnetapi.idl
@@ -0,0 +1,2070 @@
+/*
+ libnetapi interface definition
+*/
+
+cpp_quote("#define LIBNETAPI_LOCAL_SERVER(x) (!x || is_myname_or_ipaddr(x))")
+cpp_quote("#ifndef MAXSUBAUTHS")
+cpp_quote("#define MAXSUBAUTHS 15 /* max sub authorities in a SID */")
+cpp_quote("#endif")
+
+import "misc.idl";
+
+[
+ pointer_default(unique)
+]
+interface libnetapi
+{
+ const int ERROR_MORE_DATA = 234L;
+
+ [public] typedef [v1_enum] enum {
+ NERR_Success = 0,
+ NERR_NoOfflineJoinInfo = 2709,
+ NERR_BadOfflineJoinInfo = 2710,
+ NERR_CantCreateJoinInfo = 2711,
+ NERR_BadDomainJoinInfo = 2712,
+ NERR_JoinPerformedMustRestart = 2713,
+ NERR_NoJoinPending = 2714,
+ NERR_ValuesNotSet = 2715,
+ NERR_CantVerifyHostname = 2716,
+ NERR_CantLoadOfflineHive = 2717,
+ NERR_Connectionsecure = 2718,
+ NERR_ProvisioningBlobUnsupported = 2719
+ } NET_API_STATUS;
+
+ [public] typedef struct {
+ uint8 sid_rev_num;
+ uint8 num_auths;
+ uint8 id_auth[6];
+ uint32 sub_auths[MAXSUBAUTHS];
+ } domsid;
+
+ /*******************************************/
+ /* NetJoinDomain */
+ /*******************************************/
+
+ typedef [public,bitmap32bit] bitmap {
+ NETSETUP_JOIN_DOMAIN = 0x00000001,
+ NETSETUP_ACCT_CREATE = 0x00000002,
+ NETSETUP_ACCT_DELETE = 0x00000004,
+ NETSETUP_WIN9X_UPGRADE = 0x00000010,
+ NETSETUP_DOMAIN_JOIN_IF_JOINED = 0x00000020,
+ NETSETUP_JOIN_UNSECURE = 0x00000040,
+ NETSETUP_MACHINE_PWD_PASSED = 0x00000080,
+ NETSETUP_DEFER_SPN_SET = 0x00000100,
+ NETSETUP_JOIN_DC_ACCOUNT = 0x00000200,
+ NETSETUP_JOIN_WITH_NEW_NAME = 0x00000400,
+ NETSETUP_JOIN_READONLY = 0x00000800,
+ NETSETUP_AMBIGUOUS_DC = 0x00001000,
+ NETSETUP_NO_NETLOGON_CACHE = 0x00002000,
+ NETSETUP_DONT_CONTROL_SERVICES = 0x00004000,
+ NETSETUP_SET_MACHINE_NAME = 0x00008000,
+ NETSETUP_FORCE_SPN_SET = 0x00010000,
+ NETSETUP_NO_ACCT_REUSE = 0x00020000,
+ NETSETUP_INSTALL_INVOCATION = 0x00040000,
+ NETSETUP_IGNORE_UNSUPPORTED_FLAGS = 0x10000000
+ } NetJoinFlags;
+
+ [nopush,nopull] NET_API_STATUS NetJoinDomain(
+ [in,unique] string *server,
+ [in,ref] string *domain,
+ [in,unique] string *account_ou,
+ [in,unique] string *account,
+ [in,unique] string *password,
+ [in] NetJoinFlags join_flags
+ );
+
+ /*******************************************/
+ /* NetUnjoinDomain */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetUnjoinDomain(
+ [in,unique] string *server_name,
+ [in,unique] string *account,
+ [in,unique] string *password,
+ [in] NetJoinFlags unjoin_flags
+ );
+
+ /*******************************************/
+ /* NetGetJoinInformation */
+ /*******************************************/
+
+ typedef enum {
+ NetSetupUnknownStatus = 0,
+ NetSetupUnjoined = 1,
+ NetSetupWorkgroupName = 2,
+ NetSetupDomainName = 3
+ } NETSETUP_JOIN_STATUS;
+
+ [nopush,nopull] NET_API_STATUS NetGetJoinInformation(
+ [in,unique] string *server_name,
+ [out] string **name_buffer,
+ [out] uint16 *name_type
+ );
+
+ /*******************************************/
+ /* NetGetJoinableOUs */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGetJoinableOUs(
+ [in,unique] string *server_name,
+ [in,ref] string *domain,
+ [in,unique] string *account,
+ [in,unique] string *password,
+ [out] uint32 *ou_count,
+ [out] string ***ous
+ );
+
+ /*******************************************/
+ /* NetRenameMachineInDomain */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetRenameMachineInDomain(
+ [in] string server_name,
+ [in] string new_machine_name,
+ [in] string account,
+ [in] string password,
+ [in] uint32 rename_options
+ );
+
+ /*******************************************/
+ /* NetProvisionComputerAccount */
+ /*******************************************/
+
+ typedef [public,bitmap32bit] bitmap {
+ NETSETUP_PROVISION_DOWNLEVEL_PRIV_SUPPORT = 0x00000001,
+ NETSETUP_PROVISION_REUSE_ACCOUNT = 0x00000002,
+ NETSETUP_PROVISION_USE_DEFAULT_PASSWORD = 0x00000004,
+ NETSETUP_PROVISION_SKIP_ACCOUNT_SEARCH = 0x00000008,
+ NETSETUP_PROVISION_ROOT_CA_CERTS = 0x00000010
+ } NetProvisionFlags;
+
+ [nopush,nopull] NET_API_STATUS NetProvisionComputerAccount(
+ [in,ref] string *domain,
+ [in,ref] string *machine_name,
+ [in,unique] string *machine_account_ou,
+ [in,unique] string *dcname,
+ [in] NetProvisionFlags options,
+ [in,out,unique] uint8 **provision_bin_data,
+ [in,out,unique] uint32 *provision_bin_data_size,
+ [in,out,unique] string **provision_text_data
+ );
+
+ /*******************************************/
+ /* NetRequestOfflineDomainJoin */
+ /*******************************************/
+
+ typedef [public,bitmap32bit] bitmap {
+ NETSETUP_PROVISION_ONLINE_CALLER = 0x40000000
+ } NetProvisionJoinFlags;
+
+ [nopush,nopull] NET_API_STATUS NetRequestOfflineDomainJoin(
+ [in,unique] uint8 *provision_bin_data,
+ [in] uint32 provision_bin_data_size,
+ [in] NetProvisionJoinFlags options,
+ [in,unique] string *windows_path
+ );
+
+ /*******************************************/
+ /* NetComposeOfflineDomainJoin */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetComposeOfflineDomainJoin(
+ [in,ref] string *dns_domain_name,
+ [in,ref] string *netbios_domain_name,
+ [in,ref] domsid *domain_sid,
+ [in,ref] GUID *domain_guid,
+ [in,ref] string *forest_name,
+ [in,ref] string *machine_account_name,
+ [in,ref] string *machine_account_password,
+ [in,unique] string *dc_name,
+ [in,unique] string *dc_address,
+ [in] boolean8 domain_is_ad,
+ [in,out,unique] uint8 **compose_bin_data,
+ [in,out,unique] uint32 *compose_bin_data_size,
+ [in,out,unique] string **compose_text_data
+ );
+
+ /*******************************************/
+ /* NetServerGetInfo */
+ /*******************************************/
+
+ [public] typedef struct {
+ uint32 sv100_platform_id;
+ string sv100_name;
+ } SERVER_INFO_100;
+
+ [public] typedef struct {
+ uint32 sv101_platform_id;
+ string sv101_name;
+ uint32 sv101_version_major;
+ uint32 sv101_version_minor;
+ uint32 sv101_type;
+ string sv101_comment;
+ } SERVER_INFO_101;
+
+ [public] typedef struct {
+ uint32 sv102_platform_id;
+ string sv102_name;
+ uint32 sv102_version_major;
+ uint32 sv102_version_minor;
+ uint32 sv102_type;
+ string sv102_comment;
+ uint32 sv102_users;
+ uint32 sv102_disc;
+ boolean8 sv102_hidden;
+ uint32 sv102_announce;
+ uint32 sv102_anndelta;
+ uint32 sv102_licenses;
+ string sv102_userpath;
+ } SERVER_INFO_102;
+
+ [public] typedef struct {
+ uint32 sv402_ulist_mtime;
+ uint32 sv402_glist_mtime;
+ uint32 sv402_alist_mtime;
+ string sv402_alerts;
+ uint32 sv402_security;
+ uint32 sv402_numadmin;
+ uint32 sv402_lanmask;
+ string sv402_guestacct;
+ uint32 sv402_chdevs;
+ uint32 sv402_chdevq;
+ uint32 sv402_chdevjobs;
+ uint32 sv402_connections;
+ uint32 sv402_shares;
+ uint32 sv402_openfiles;
+ uint32 sv402_sessopens;
+ uint32 sv402_sessvcs;
+ uint32 sv402_sessreqs;
+ uint32 sv402_opensearch;
+ uint32 sv402_activelocks;
+ uint32 sv402_numreqbuf;
+ uint32 sv402_sizreqbuf;
+ uint32 sv402_numbigbuf;
+ uint32 sv402_numfiletasks;
+ uint32 sv402_alertsched;
+ uint32 sv402_erroralert;
+ uint32 sv402_logonalert;
+ uint32 sv402_accessalert;
+ uint32 sv402_diskalert;
+ uint32 sv402_netioalert;
+ uint32 sv402_maxauditsz;
+ string sv402_srvheuristics;
+ } SERVER_INFO_402;
+
+ [public] typedef struct {
+ uint32 sv403_ulist_mtime;
+ uint32 sv403_glist_mtime;
+ uint32 sv403_alist_mtime;
+ string sv403_alerts;
+ uint32 sv403_security;
+ uint32 sv403_numadmin;
+ uint32 sv403_lanmask;
+ string sv403_guestacct;
+ uint32 sv403_chdevs;
+ uint32 sv403_chdevq;
+ uint32 sv403_chdevjobs;
+ uint32 sv403_connections;
+ uint32 sv403_shares;
+ uint32 sv403_openfiles;
+ uint32 sv403_sessopens;
+ uint32 sv403_sessvcs;
+ uint32 sv403_sessreqs;
+ uint32 sv403_opensearch;
+ uint32 sv403_activelocks;
+ uint32 sv403_numreqbuf;
+ uint32 sv403_sizreqbuf;
+ uint32 sv403_numbigbuf;
+ uint32 sv403_numfiletasks;
+ uint32 sv403_alertsched;
+ uint32 sv403_erroralert;
+ uint32 sv403_logonalert;
+ uint32 sv403_accessalert;
+ uint32 sv403_diskalert;
+ uint32 sv403_netioalert;
+ uint32 sv403_maxauditsz;
+ string sv403_srvheuristics;
+ uint32 sv403_auditedevents;
+ uint32 sv403_autoprofile;
+ string sv403_autopath;
+ } SERVER_INFO_403;
+
+ [public] typedef struct {
+ uint32 sv502_sessopens;
+ uint32 sv502_sessvcs;
+ uint32 sv502_opensearch;
+ uint32 sv502_sizreqbuf;
+ uint32 sv502_initworkitems;
+ uint32 sv502_maxworkitems;
+ uint32 sv502_rawworkitems;
+ uint32 sv502_irpstacksize;
+ uint32 sv502_maxrawbuflen;
+ uint32 sv502_sessusers;
+ uint32 sv502_sessconns;
+ uint32 sv502_maxpagedmemoryusage;
+ uint32 sv502_maxnonpagedmemoryusage;
+ boolean8 sv502_enablesoftcompat;
+ boolean8 sv502_enableforcedlogoff;
+ boolean8 sv502_timesource;
+ boolean8 sv502_acceptdownlevelapis;
+ boolean8 sv502_lmannounce;
+ } SERVER_INFO_502;
+
+ [public] typedef struct {
+ uint32 sv503_sessopens;
+ uint32 sv503_sessvcs;
+ uint32 sv503_opensearch;
+ uint32 sv503_sizreqbuf;
+ uint32 sv503_initworkitems;
+ uint32 sv503_maxworkitems;
+ uint32 sv503_rawworkitems;
+ uint32 sv503_irpstacksize;
+ uint32 sv503_maxrawbuflen;
+ uint32 sv503_sessusers;
+ uint32 sv503_sessconns;
+ uint32 sv503_maxpagedmemoryusage;
+ uint32 sv503_maxnonpagedmemoryusage;
+ boolean8 sv503_enablesoftcompat;
+ boolean8 sv503_enableforcedlogoff;
+ boolean8 sv503_timesource;
+ boolean8 sv503_acceptdownlevelapis;
+ boolean8 sv503_lmannounce;
+ string sv503_domain;
+ uint32 sv503_maxcopyreadlen;
+ uint32 sv503_maxcopywritelen;
+ uint32 sv503_minkeepsearch;
+ uint32 sv503_maxkeepsearch;
+ uint32 sv503_minkeepcomplsearch;
+ uint32 sv503_maxkeepcomplsearch;
+ uint32 sv503_threadcountadd;
+ uint32 sv503_numblockthreads;
+ uint32 sv503_scavtimeout;
+ uint32 sv503_minrcvqueue;
+ uint32 sv503_minfreeworkitems;
+ uint32 sv503_xactmemsize;
+ uint32 sv503_threadpriority;
+ uint32 sv503_maxmpxct;
+ uint32 sv503_oplockbreakwait;
+ uint32 sv503_oplockbreakresponsewait;
+ boolean8 sv503_enableoplocks;
+ boolean8 sv503_enableoplockforceclose;
+ boolean8 sv503_enablefcbopens;
+ boolean8 sv503_enableraw;
+ boolean8 sv503_enablesharednetdrives;
+ uint32 sv503_minfreeconnections;
+ uint32 sv503_maxfreeconnections;
+ } SERVER_INFO_503;
+
+ [public] typedef struct {
+ uint32 sv599_sessopens;
+ uint32 sv599_sessvcs;
+ uint32 sv599_opensearch;
+ uint32 sv599_sizreqbuf;
+ uint32 sv599_initworkitems;
+ uint32 sv599_maxworkitems;
+ uint32 sv599_rawworkitems;
+ uint32 sv599_irpstacksize;
+ uint32 sv599_maxrawbuflen;
+ uint32 sv599_sessusers;
+ uint32 sv599_sessconns;
+ uint32 sv599_maxpagedmemoryusage;
+ uint32 sv599_maxnonpagedmemoryusage;
+ boolean8 sv599_enablesoftcompat;
+ boolean8 sv599_enableforcedlogoff;
+ boolean8 sv599_timesource;
+ boolean8 sv599_acceptdownlevelapis;
+ boolean8 sv599_lmannounce;
+ string sv599_domain;
+ uint32 sv599_maxcopyreadlen;
+ uint32 sv599_maxcopywritelen;
+ uint32 sv599_minkeepsearch;
+ uint32 sv599_maxkeepsearch;
+ uint32 sv599_minkeepcomplsearch;
+ uint32 sv599_maxkeepcomplsearch;
+ uint32 sv599_threadcountadd;
+ uint32 sv599_numblockthreads;
+ uint32 sv599_scavtimeout;
+ uint32 sv599_minrcvqueue;
+ uint32 sv599_minfreeworkitems;
+ uint32 sv599_xactmemsize;
+ uint32 sv599_threadpriority;
+ uint32 sv599_maxmpxct;
+ uint32 sv599_oplockbreakwait;
+ uint32 sv599_oplockbreakresponsewait;
+ boolean8 sv599_enableoplocks;
+ boolean8 sv599_enableoplockforceclose;
+ boolean8 sv599_enablefcbopens;
+ boolean8 sv599_enableraw;
+ boolean8 sv599_enablesharednetdrives;
+ uint32 sv599_minfreeconnections;
+ uint32 sv599_maxfreeconnections;
+ uint32 sv599_initsesstable;
+ uint32 sv599_initconntable;
+ uint32 sv599_initfiletable;
+ uint32 sv599_initsearchtable;
+ uint32 sv599_alertschedule;
+ uint32 sv599_errorthreshold;
+ uint32 sv599_networkerrorthreshold;
+ uint32 sv599_diskspacethreshold;
+ uint32 sv599_reserved;
+ uint32 sv599_maxlinkdelay;
+ uint32 sv599_minlinkthroughput;
+ uint32 sv599_linkinfovalidtime;
+ uint32 sv599_scavqosinfoupdatetime;
+ uint32 sv599_maxworkitemidletime;
+ } SERVER_INFO_599;
+
+ [public] typedef struct {
+ uint32 sv598_maxrawworkitems;
+ uint32 sv598_maxthreadsperqueue;
+ uint32 sv598_producttype;
+ uint32 sv598_serversize;
+ uint32 sv598_connectionlessautodisc;
+ uint32 sv598_sharingviolationretries;
+ uint32 sv598_sharingviolationdelay;
+ uint32 sv598_maxglobalopensearch;
+ uint32 sv598_removeduplicatesearches;
+ uint32 sv598_lockviolationoffset;
+ uint32 sv598_lockviolationdelay;
+ uint32 sv598_mdlreadswitchover;
+ uint32 sv598_cachedopenlimit;
+ uint32 sv598_otherqueueaffinity;
+ boolean8 sv598_restrictnullsessaccess;
+ boolean8 sv598_enablewfw311directipx;
+ uint32 sv598_queuesamplesecs;
+ uint32 sv598_balancecount;
+ uint32 sv598_preferredaffinity;
+ uint32 sv598_maxfreerfcbs;
+ uint32 sv598_maxfreemfcbs;
+ uint32 sv598_maxfreelfcbs;
+ uint32 sv598_maxfreepagedpoolchunks;
+ uint32 sv598_minpagedpoolchunksize;
+ uint32 sv598_maxpagedpoolchunksize;
+ boolean8 sv598_sendsfrompreferredprocessor;
+ uint32 sv598_cacheddirectorylimit;
+ uint32 sv598_maxcopylength;
+ boolean8 sv598_enablecompression;
+ boolean8 sv598_autosharewks;
+ boolean8 sv598_autoshareserver;
+ boolean8 sv598_enablesecuritysignature;
+ boolean8 sv598_requiresecuritysignature;
+ uint32 sv598_minclientbuffersize;
+ GUID sv598_serverguid;
+ uint32 sv598_ConnectionNoSessionsTimeout;
+ uint32 sv598_IdleThreadTimeOut;
+ boolean8 sv598_enableW9xsecuritysignature;
+ boolean8 sv598_enforcekerberosreauthentication;
+ boolean8 sv598_disabledos;
+ uint32 sv598_lowdiskspaceminimum;
+ boolean8 sv598_disablestrictnamechecking;
+ } SERVER_INFO_598;
+
+ [public] typedef struct {
+ string sv1005_comment;
+ } SERVER_INFO_1005;
+
+ [public] typedef struct {
+ uint32 sv1107_users;
+ } SERVER_INFO_1107;
+
+ [public] typedef struct {
+ int32 sv1010_disc;
+ } SERVER_INFO_1010;
+
+ [public] typedef struct {
+ boolean8 sv1016_hidden;
+ } SERVER_INFO_1016;
+
+ [public] typedef struct {
+ uint32 sv1017_announce;
+ } SERVER_INFO_1017;
+
+ [public] typedef struct {
+ uint32 sv1018_anndelta;
+ } SERVER_INFO_1018;
+
+ [public] typedef struct {
+ uint32 sv1501_sessopens;
+ } SERVER_INFO_1501;
+
+ [public] typedef struct {
+ uint32 sv1502_sessvcs;
+ } SERVER_INFO_1502;
+
+ [public] typedef struct {
+ uint32 sv1503_opensearch;
+ } SERVER_INFO_1503;
+
+ [public] typedef struct {
+ uint32 sv1506_maxworkitems;
+ } SERVER_INFO_1506;
+
+ [public] typedef struct {
+ uint32 sv1509_maxrawbuflen;
+ } SERVER_INFO_1509;
+
+ [public] typedef struct {
+ uint32 sv1510_sessusers;
+ } SERVER_INFO_1510;
+
+ [public] typedef struct {
+ uint32 sv1511_sessconns;
+ } SERVER_INFO_1511;
+
+ [public] typedef struct {
+ uint32 sv1512_maxnonpagedmemoryusage;
+ } SERVER_INFO_1512;
+
+ [public] typedef struct {
+ uint32 sv1513_maxpagedmemoryusage;
+ } SERVER_INFO_1513;
+
+ [public] typedef struct {
+ boolean8 sv1514_enablesoftcompat;
+ } SERVER_INFO_1514;
+
+ [public] typedef struct {
+ boolean8 sv1515_enableforcedlogoff;
+ } SERVER_INFO_1515;
+
+ [public] typedef struct {
+ boolean8 sv1516_timesource;
+ } SERVER_INFO_1516;
+
+ [public] typedef struct {
+ boolean8 sv1518_lmannounce;
+ } SERVER_INFO_1518;
+
+ [public] typedef struct {
+ uint32 sv1520_maxcopyreadlen;
+ } SERVER_INFO_1520;
+
+ [public] typedef struct {
+ uint32 sv1521_maxcopywritelen;
+ } SERVER_INFO_1521;
+
+ [public] typedef struct {
+ uint32 sv1522_minkeepsearch;
+ } SERVER_INFO_1522;
+
+ [public] typedef struct {
+ uint32 sv1523_maxkeepsearch;
+ } SERVER_INFO_1523;
+
+ [public] typedef struct {
+ uint32 sv1524_minkeepcomplsearch;
+ } SERVER_INFO_1524;
+
+ [public] typedef struct {
+ uint32 sv1525_maxkeepcomplsearch;
+ } SERVER_INFO_1525;
+
+ [public] typedef struct {
+ uint32 sv1528_scavtimeout;
+ } SERVER_INFO_1528;
+
+ [public] typedef struct {
+ uint32 sv1529_minrcvqueue;
+ } SERVER_INFO_1529;
+
+ [public] typedef struct {
+ uint32 sv1530_minfreeworkitems;
+ } SERVER_INFO_1530;
+
+ [public] typedef struct {
+ uint32 sv1533_maxmpxct;
+ } SERVER_INFO_1533;
+
+ [public] typedef struct {
+ uint32 sv1534_oplockbreakwait;
+ } SERVER_INFO_1534;
+
+ [public] typedef struct {
+ uint32 sv1535_oplockbreakresponsewait;
+ } SERVER_INFO_1535;
+
+ [public] typedef struct {
+ boolean8 sv1536_enableoplocks;
+ } SERVER_INFO_1536;
+
+ [public] typedef struct {
+ boolean8 sv1537_enableoplockforceclose;
+ } SERVER_INFO_1537;
+
+ [public] typedef struct {
+ boolean8 sv1538_enablefcbopens;
+ } SERVER_INFO_1538;
+
+ [public] typedef struct {
+ boolean8 sv1539_enableraw;
+ } SERVER_INFO_1539;
+
+ [public] typedef struct {
+ boolean8 sv1540_enablesharednetdrives;
+ } SERVER_INFO_1540;
+
+ [public] typedef struct {
+ boolean8 sv1541_minfreeconnections;
+ } SERVER_INFO_1541;
+
+ [public] typedef struct {
+ boolean8 sv1542_maxfreeconnections;
+ } SERVER_INFO_1542;
+
+ [public] typedef struct {
+ uint32 sv1543_initsesstable;
+ } SERVER_INFO_1543;
+
+ [public] typedef struct {
+ uint32 sv1544_initconntable;
+ } SERVER_INFO_1544;
+
+ [public] typedef struct {
+ uint32 sv1545_initfiletable;
+ } SERVER_INFO_1545;
+
+ [public] typedef struct {
+ uint32 sv1546_initsearchtable;
+ } SERVER_INFO_1546;
+
+ [public] typedef struct {
+ uint32 sv1547_alertschedule;
+ } SERVER_INFO_1547;
+
+ [public] typedef struct {
+ uint32 sv1548_errorthreshold;
+ } SERVER_INFO_1548;
+
+ [public] typedef struct {
+ uint32 sv1549_networkerrorthreshold;
+ } SERVER_INFO_1549;
+
+ [public] typedef struct {
+ uint32 sv1550_diskspacethreshold;
+ } SERVER_INFO_1550;
+
+ [public] typedef struct {
+ uint32 sv1552_maxlinkdelay;
+ } SERVER_INFO_1552;
+
+ [public] typedef struct {
+ uint32 sv1553_minlinkthroughput;
+ } SERVER_INFO_1553;
+
+ [public] typedef struct {
+ uint32 sv1554_linkinfovalidtime;
+ } SERVER_INFO_1554;
+
+ [public] typedef struct {
+ uint32 sv1555_scavqosinfoupdatetime;
+ } SERVER_INFO_1555;
+
+ [public] typedef struct {
+ uint32 sv1556_maxworkitemidletime;
+ } SERVER_INFO_1556;
+
+ [public] typedef struct {
+ uint32 sv1557_maxrawworkitems;
+ } SERVER_INFO_1557;
+
+ [public] typedef struct {
+ uint32 sv1560_producttype;
+ } SERVER_INFO_1560;
+
+ [public] typedef struct {
+ uint32 sv1561_serversize;
+ } SERVER_INFO_1561;
+
+ [public] typedef struct {
+ uint32 sv1562_connectionlessautodisc;
+ } SERVER_INFO_1562;
+
+ [public] typedef struct {
+ uint32 sv1563_sharingviolationretries;
+ } SERVER_INFO_1563;
+
+ [public] typedef struct {
+ uint32 sv1564_sharingviolationdelay;
+ } SERVER_INFO_1564;
+
+ [public] typedef struct {
+ uint32 sv1565_maxglobalopensearch;
+ } SERVER_INFO_1565;
+
+ [public] typedef struct {
+ boolean8 sv1566_removeduplicatesearches;
+ } SERVER_INFO_1566;
+
+ [public] typedef struct {
+ uint32 sv1567_lockviolationretries;
+ } SERVER_INFO_1567;
+
+ [public] typedef struct {
+ uint32 sv1568_lockviolationoffset;
+ } SERVER_INFO_1568;
+
+ [public] typedef struct {
+ uint32 sv1569_lockviolationdelay;
+ } SERVER_INFO_1569;
+
+ [public] typedef struct {
+ uint32 sv1570_mdlreadswitchover;
+ } SERVER_INFO_1570;
+
+ [public] typedef struct {
+ uint32 sv1571_cachedopenlimit;
+ } SERVER_INFO_1571;
+
+ [public] typedef struct {
+ uint32 sv1572_criticalthreads;
+ } SERVER_INFO_1572;
+
+ [public] typedef struct {
+ uint32 sv1573_restrictnullsessaccess;
+ } SERVER_INFO_1573;
+
+ [public] typedef struct {
+ uint32 sv1574_enablewfw311directipx;
+ } SERVER_INFO_1574;
+
+ [public] typedef struct {
+ uint32 sv1575_otherqueueaffinity;
+ } SERVER_INFO_1575;
+
+ [public] typedef struct {
+ uint32 sv1576_queuesamplesecs;
+ } SERVER_INFO_1576;
+
+ [public] typedef struct {
+ uint32 sv1577_balancecount;
+ } SERVER_INFO_1577;
+
+ [public] typedef struct {
+ uint32 sv1578_preferredaffinity;
+ } SERVER_INFO_1578;
+
+ [public] typedef struct {
+ uint32 sv1579_maxfreerfcbs;
+ } SERVER_INFO_1579;
+
+ [public] typedef struct {
+ uint32 sv1580_maxfreemfcbs;
+ } SERVER_INFO_1580;
+
+ [public] typedef struct {
+ uint32 sv1581_maxfreemlcbs;
+ } SERVER_INFO_1581;
+
+ [public] typedef struct {
+ uint32 sv1582_maxfreepagedpoolchunks;
+ } SERVER_INFO_1582;
+
+ [public] typedef struct {
+ uint32 sv1583_minpagedpoolchunksize;
+ } SERVER_INFO_1583;
+
+ [public] typedef struct {
+ uint32 sv1584_maxpagedpoolchunksize;
+ } SERVER_INFO_1584;
+
+ [public] typedef struct {
+ boolean8 sv1585_sendsfrompreferredprocessor;
+ } SERVER_INFO_1585;
+
+ [public] typedef struct {
+ uint32 sv1586_maxthreadsperqueue;
+ } SERVER_INFO_1586;
+
+ [public] typedef struct {
+ uint32 sv1587_cacheddirectorylimit;
+ } SERVER_INFO_1587;
+
+ [public] typedef struct {
+ uint32 sv1588_maxcopylength;
+ } SERVER_INFO_1588;
+
+ [public] typedef struct {
+ uint32 sv1590_enablecompression;
+ } SERVER_INFO_1590;
+
+ [public] typedef struct {
+ uint32 sv1591_autosharewks;
+ } SERVER_INFO_1591;
+
+ [public] typedef struct {
+ uint32 sv1592_autosharewks;
+ } SERVER_INFO_1592;
+
+ [public] typedef struct {
+ uint32 sv1593_enablesecuritysignature;
+ } SERVER_INFO_1593;
+
+ [public] typedef struct {
+ uint32 sv1594_requiresecuritysignature;
+ } SERVER_INFO_1594;
+
+ [public] typedef struct {
+ uint32 sv1595_minclientbuffersize;
+ } SERVER_INFO_1595;
+
+ [public] typedef struct {
+ uint32 sv1596_ConnectionNoSessionsTimeout;
+ } SERVER_INFO_1596;
+
+ [public] typedef struct {
+ uint32 sv1597_IdleThreadTimeOut;
+ } SERVER_INFO_1597;
+
+ [public] typedef struct {
+ uint32 sv1598_enableW9xsecuritysignature;
+ } SERVER_INFO_1598;
+
+ [public] typedef struct {
+ boolean8 sv1598_enforcekerberosreauthentication;
+ } SERVER_INFO_1599;
+
+ [public] typedef struct {
+ boolean8 sv1598_disabledos;
+ } SERVER_INFO_1600;
+
+ [public] typedef struct {
+ uint32 sv1598_lowdiskspaceminimum;
+ } SERVER_INFO_1601;
+
+ [public] typedef struct {
+ boolean8 sv_1598_disablestrictnamechecking;
+ } SERVER_INFO_1602;
+
+ [nopush,nopull] NET_API_STATUS NetServerGetInfo(
+ [in,unique] string *server_name,
+ [in] uint32 level,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetServerSetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetServerSetInfo(
+ [in,unique] string *server_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out] uint32 *parm_error
+ );
+
+ /*******************************************/
+ /* NetWkstaGetInfo */
+ /*******************************************/
+
+ [public] typedef struct {
+ uint32 wki100_platform_id;
+ string wki100_computername;
+ string wki100_langroup;
+ uint32 wki100_ver_major;
+ uint32 wki100_ver_minor;
+ } WKSTA_INFO_100;
+
+ [public] typedef struct {
+ uint32 wki101_platform_id;
+ string wki101_computername;
+ string wki101_langroup;
+ uint32 wki101_ver_major;
+ uint32 wki101_ver_minor;
+ string wki101_lanroot;
+ } WKSTA_INFO_101;
+
+ [public] typedef struct {
+ uint32 wki102_platform_id;
+ string wki102_computername;
+ string wki102_langroup;
+ uint32 wki102_ver_major;
+ uint32 wki102_ver_minor;
+ string wki102_lanroot;
+ uint32 wki102_logged_on_users;
+ } WKSTA_INFO_102;
+
+ [nopush,nopull] NET_API_STATUS NetWkstaGetInfo(
+ [in,unique] string *server_name,
+ [in] uint32 level,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetGetDCName */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGetDCName(
+ [in,unique] string *server_name,
+ [in,unique] string *domain_name,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetGetAnyDCName */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGetAnyDCName(
+ [in,unique] string *server_name,
+ [in,unique] string *domain_name,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* DsGetDcName */
+ /*******************************************/
+
+ [public] typedef [bitmap32bit] bitmap {
+ DS_PDC_FLAG = 0x00000001,
+ DS_GC_FLAG = 0x00000004,
+ DS_LDAP_FLAG = 0x00000008,
+ DS_DS_FLAG = 0x00000010,
+ DS_KDC_FLAG = 0x00000020,
+ DS_TIMESERV_FLAG = 0x00000040,
+ DS_CLOSEST_FLAG = 0x00000080,
+ DS_WRITABLE_FLAG = 0x00000100,
+ DS_GOOD_TIMESERV_FLAG = 0x00000200,
+ DS_NDNC_FLAG = 0x00000400,
+ DS_SELECT_SECRET_DOMAIN_6_FLAG = 0x00000800,
+ DS_FULL_SECRET_DOMAIN_6_FLAG = 0x00001000,
+ DS_WS_FLAG = 0x00002000,
+ DS_DS_8_FLAG = 0x00004000,
+ DS_DNS_CONTROLLER_FLAG = 0x20000000,
+ DS_DNS_DOMAIN_FLAG = 0x40000000,
+ DS_DNS_FOREST_FLAG = 0x80000000
+ } DOMAIN_CONTROLLER_INFO_FLAGS;
+
+ [public] typedef struct {
+ string domain_controller_name;
+ string domain_controller_address;
+ uint32 domain_controller_address_type;
+ GUID domain_guid;
+ string domain_name;
+ string dns_forest_name;
+ DOMAIN_CONTROLLER_INFO_FLAGS flags;
+ string dc_site_name;
+ string client_site_name;
+ } DOMAIN_CONTROLLER_INFO;
+
+ [nopush,nopull] NET_API_STATUS DsGetDcName(
+ [in,unique] string *server_name,
+ [in,ref] string *domain_name,
+ [in,unique] GUID *domain_guid,
+ [in,unique] string *site_name,
+ [in] uint32 flags,
+ [out,ref] DOMAIN_CONTROLLER_INFO **dc_info
+ );
+
+ /*******************************************/
+ /* NetUserAdd */
+ /*******************************************/
+
+ [public] typedef struct {
+ string usri0_name;
+ } USER_INFO_0;
+
+ /* priv */
+ const int USER_PRIV_GUEST = 0;
+ const int USER_PRIV_USER = 1;
+ const int USER_PRIV_ADMIN = 2;
+
+ [public] typedef struct {
+ string usri1_name;
+ string usri1_password;
+ uint32 usri1_password_age;
+ uint32 usri1_priv;
+ string usri1_home_dir;
+ string usri1_comment;
+ uint32 usri1_flags;
+ string usri1_script_path;
+ } USER_INFO_1;
+
+ /* auth_flags in USER_INFO_2 */
+
+ const int AF_OP_PRINT = 0x1;
+ const int AF_OP_COMM = 0x2;
+ const int AF_OP_SERVER = 0x4;
+ const int AF_OP_ACCOUNTS = 0x8;
+ const int AF_SETTABLE_BITS = (AF_OP_PRINT | AF_OP_COMM | AF_OP_SERVER | AF_OP_ACCOUNTS);
+
+ const int USER_MAXSTORAGE_UNLIMITED = (uint32_t)-1L;
+
+ [public] typedef struct {
+ string usri2_name;
+ string usri2_password;
+ uint32 usri2_password_age;
+ uint32 usri2_priv;
+ string usri2_home_dir;
+ string usri2_comment;
+ uint32 usri2_flags;
+ string usri2_script_path;
+ uint32 usri2_auth_flags;
+ string usri2_full_name;
+ string usri2_usr_comment;
+ string usri2_parms;
+ string usri2_workstations;
+ uint32 usri2_last_logon;
+ uint32 usri2_last_logoff;
+ uint32 usri2_acct_expires;
+ uint32 usri2_max_storage;
+ uint32 usri2_units_per_week;
+ uint8 *usri2_logon_hours;
+ uint32 usri2_bad_pw_count;
+ uint32 usri2_num_logons;
+ string usri2_logon_server;
+ uint32 usri2_country_code;
+ uint32 usri2_code_page;
+ } USER_INFO_2;
+
+ [public] typedef struct {
+ string usri3_name;
+ uint32 usri3_password_age;
+ uint32 usri3_priv;
+ string usri3_home_dir;
+ string usri3_comment;
+ uint32 usri3_flags;
+ string usri3_script_path;
+ uint32 usri3_auth_flags;
+ string usri3_full_name;
+ string usri3_usr_comment;
+ string usri3_parms;
+ string usri3_workstations;
+ uint32 usri3_last_logon;
+ uint32 usri3_last_logoff;
+ uint32 usri3_acct_expires;
+ uint32 usri3_max_storage;
+ uint32 usri3_units_per_week;
+ uint8 *usri3_logon_hours;
+ uint32 usri3_bad_pw_count;
+ uint32 usri3_num_logons;
+ string usri3_logon_server;
+ uint32 usri3_country_code;
+ uint32 usri3_code_page;
+ uint32 usri3_user_id;
+ uint32 usri3_primary_group_id;
+ string usri3_profile;
+ string usri3_home_dir_drive;
+ uint32 usri3_password_expired;
+ } USER_INFO_3;
+
+ [public] typedef struct {
+ string usri4_name;
+ string usri4_password;
+ uint32 usri4_password_age;
+ uint32 usri4_priv;
+ string usri4_home_dir;
+ string usri4_comment;
+ uint32 usri4_flags;
+ string usri4_script_path;
+ uint32 usri4_auth_flags;
+ string usri4_full_name;
+ string usri4_usr_comment;
+ string usri4_parms;
+ string usri4_workstations;
+ uint32 usri4_last_logon;
+ uint32 usri4_last_logoff;
+ uint32 usri4_acct_expires;
+ uint32 usri4_max_storage;
+ uint32 usri4_units_per_week;
+ uint8 *usri4_logon_hours;
+ uint32 usri4_bad_pw_count;
+ uint32 usri4_num_logons;
+ string usri4_logon_server;
+ uint32 usri4_country_code;
+ uint32 usri4_code_page;
+ domsid *usri4_user_sid;
+ uint32 usri4_primary_group_id;
+ string usri4_profile;
+ string usri4_home_dir_drive;
+ uint32 usri4_password_expired;
+ } USER_INFO_4;
+
+ [public] typedef struct {
+ string usri10_name;
+ string usri10_comment;
+ string usri10_usr_comment;
+ string usri10_full_name;
+ } USER_INFO_10;
+
+ [public] typedef struct {
+ string usri11_name;
+ string usri11_comment;
+ string usri11_usr_comment;
+ string usri11_full_name;
+ uint32 usri11_priv;
+ uint32 usri11_auth_flags;
+ uint32 usri11_password_age;
+ string usri11_home_dir;
+ string usri11_parms;
+ uint32 usri11_last_logon;
+ uint32 usri11_last_logoff;
+ uint32 usri11_bad_pw_count;
+ uint32 usri11_num_logons;
+ string usri11_logon_server;
+ uint32 usri11_country_code;
+ string usri11_workstations;
+ uint32 usri11_max_storage;
+ uint32 usri11_units_per_week;
+ uint8 *usri11_logon_hours;
+ uint32 usri11_code_page;
+ } USER_INFO_11;
+
+ [public] typedef struct {
+ string usri20_name;
+ string usri20_full_name;
+ string usri20_comment;
+ uint32 usri20_flags;
+ uint32 usri20_user_id;
+ } USER_INFO_20;
+
+ const int ENCRYPTED_PWLEN = 16;
+
+ [public] typedef struct {
+ uint8 usri21_password[ENCRYPTED_PWLEN];
+ } USER_INFO_21;
+
+ [public] typedef struct {
+ string usri22_name;
+ uint8 usri22_password[ENCRYPTED_PWLEN];
+ uint32 usri22_password_age;
+ uint32 usri22_priv;
+ string usri22_home_dir;
+ string usri22_comment;
+ uint32 usri22_flags;
+ uint32 usri22_script_path;
+ uint32 usri22_auth_flags;
+ string usri22_full_name;
+ string usri22_usr_comment;
+ string usri22_parms;
+ string usri22_workstations;
+ uint32 usri22_last_logon;
+ uint32 usri22_last_logoff;
+ uint32 usri22_acct_expires;
+ uint32 usri22_max_storage;
+ uint32 usri22_units_per_week;
+ uint8 *usri22_logon_hours;
+ uint32 usri22_bad_pw_count;
+ uint32 usri22_num_logons;
+ string usri22_logon_server;
+ uint32 usri22_country_code;
+ uint32 usri22_code_page;
+ } USER_INFO_22;
+
+ [public] typedef struct {
+ string usri23_name;
+ string usri23_full_name;
+ string usri23_comment;
+ uint32 usri23_flags;
+ domsid *usri23_user_sid;
+ } USER_INFO_23;
+
+ [public] typedef struct {
+ string usri1003_password;
+ } USER_INFO_1003;
+
+ [public] typedef struct {
+ uint32 usri1005_priv;
+ } USER_INFO_1005;
+
+ [public] typedef struct {
+ string usri1006_home_dir;
+ } USER_INFO_1006;
+
+ [public] typedef struct {
+ string usri1007_comment;
+ } USER_INFO_1007;
+
+ [public] typedef struct {
+ uint32 usri1008_flags;
+ } USER_INFO_1008;
+
+ [public] typedef struct {
+ string usri1009_script_path;
+ } USER_INFO_1009;
+
+ [public] typedef struct {
+ uint32 usri1010_auth_flags;
+ } USER_INFO_1010;
+
+ [public] typedef struct {
+ string usri1011_full_name;
+ } USER_INFO_1011;
+
+ [public] typedef struct {
+ string usri1012_usr_comment;
+ } USER_INFO_1012;
+
+ [public] typedef struct {
+ string usri1013_parms;
+ } USER_INFO_1013;
+
+ [public] typedef struct {
+ string usri1014_workstations;
+ } USER_INFO_1014;
+
+ [public] typedef struct {
+ uint32 usri1017_acct_expires;
+ } USER_INFO_1017;
+
+ [public] typedef struct {
+ uint32 usri1018_max_storage;
+ } USER_INFO_1018;
+
+ [public] typedef struct {
+ uint32 usri1020_units_per_week;
+ uint8 *usri1020_logon_hours;
+ } USER_INFO_1020;
+
+ [public] typedef struct {
+ string usri1023_logon_server;
+ } USER_INFO_1023;
+
+ [public] typedef struct {
+ uint32 usri1024_country_code;
+ } USER_INFO_1024;
+
+ [public] typedef struct {
+ uint32 usri1025_code_page;
+ } USER_INFO_1025;
+
+ [public] typedef struct {
+ uint32 usri1051_primary_group_id;
+ } USER_INFO_1051;
+
+ [public] typedef struct {
+ string usri1052_profile;
+ } USER_INFO_1052;
+
+ [public] typedef struct {
+ string usri1053_home_dir_drive;
+ } USER_INFO_1053;
+
+ [public] typedef struct {
+ string usriX_name;
+ string usriX_password;
+ uint32 usriX_password_age;
+ uint32 usriX_priv;
+ string usriX_home_dir;
+ string usriX_comment;
+ uint32 usriX_flags;
+ string usriX_script_path;
+ uint32 usriX_auth_flags;
+ string usriX_full_name;
+ string usriX_usr_comment;
+ string usriX_parms;
+ string usriX_workstations;
+ uint32 usriX_last_logon;
+ uint32 usriX_last_logoff;
+ uint32 usriX_acct_expires;
+ uint32 usriX_max_storage;
+ uint32 usriX_units_per_week;
+ uint8 *usriX_logon_hours;
+ uint32 usriX_bad_pw_count;
+ uint32 usriX_num_logons;
+ string usriX_logon_server;
+ uint32 usriX_country_code;
+ uint32 usriX_code_page;
+ string usriX_profile;
+ string usriX_home_dir_drive;
+ uint32 usriX_user_id;
+ uint32 usriX_primary_group_id;
+ uint32 usriX_password_expired;
+ } USER_INFO_X;
+
+ [nopush,nopull] NET_API_STATUS NetUserAdd(
+ [in,unique] string *server_name,
+ [in] uint32 level,
+ [in,ref] uint8 *buffer,
+ [out,ref] uint32 *parm_error
+ );
+
+ /*******************************************/
+ /* NetUserDel */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetUserDel(
+ [in,unique] string *server_name,
+ [in,ref] string *user_name
+ );
+
+ /*******************************************/
+ /* NetUserEnum */
+ /*******************************************/
+
+ const int FILTER_TEMP_DUPLICATE_ACCOUNT = 0x0001;
+ const int FILTER_NORMAL_ACCOUNT = 0x0002;
+ const int FILTER_INTERDOMAIN_TRUST_ACCOUNT = 0x0008;
+ const int FILTER_WORKSTATION_TRUST_ACCOUNT = 0x0010;
+ const int FILTER_SERVER_TRUST_ACCOUNT = 0x0020;
+
+ [nopush,nopull] NET_API_STATUS NetUserEnum(
+ [in,unique] string *server_name,
+ [in] uint32 level,
+ [in] uint32 filter,
+ [out,ref] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out,ref] uint32 *entries_read,
+ [out,ref] uint32 *total_entries,
+ [in,out,ref] uint32 *resume_handle
+ );
+
+ /*******************************************/
+ /* NetUserChangePassword */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetUserChangePassword(
+ [in] string domain_name,
+ [in] string user_name,
+ [in] string old_password,
+ [in] string new_password
+ );
+
+ /*******************************************/
+ /* NetUserGetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetUserGetInfo(
+ [in] string server_name,
+ [in] string user_name,
+ [in] uint32 level,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetUserSetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetUserSetInfo(
+ [in] string server_name,
+ [in] string user_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetUserGetGroups */
+ /*******************************************/
+
+ [public] typedef struct {
+ string grui0_name;
+ } GROUP_USERS_INFO_0;
+
+ [public] typedef struct {
+ string grui1_name;
+ uint32 grui1_attributes;
+ } GROUP_USERS_INFO_1;
+
+ [nopush,nopull] NET_API_STATUS NetUserGetGroups(
+ [in] string server_name,
+ [in] string user_name,
+ [in] uint32 level,
+ [out] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out,ref] uint32 *entries_read,
+ [out,ref] uint32 *total_entries
+ );
+
+ /*******************************************/
+ /* NetUserSetGroups */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetUserSetGroups(
+ [in] string server_name,
+ [in] string user_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [in] uint32 num_entries
+ );
+
+ /*******************************************/
+ /* NetUserGetLocalGroups */
+ /*******************************************/
+
+ const int LG_INCLUDE_INDIRECT = 0x0001;
+
+ typedef struct {
+ string lgrui0_name;
+ } LOCALGROUP_USERS_INFO_0;
+
+ [nopush,nopull] NET_API_STATUS NetUserGetLocalGroups(
+ [in] string server_name,
+ [in] string user_name,
+ [in] uint32 level,
+ [in] uint32 flags,
+ [out] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out,ref] uint32 *entries_read,
+ [out,ref] uint32 *total_entries
+ );
+
+ /*******************************************/
+ /* NetUserModalsGet */
+ /*******************************************/
+
+ const int TIMEQ_FOREVER = (uint32_t)-1L;
+
+ typedef struct {
+ uint32 usrmod0_min_passwd_len;
+ uint32 usrmod0_max_passwd_age;
+ uint32 usrmod0_min_passwd_age;
+ uint32 usrmod0_force_logoff;
+ uint32 usrmod0_password_hist_len;
+ } USER_MODALS_INFO_0;
+
+ typedef struct {
+ uint32 usrmod1_role;
+ string usrmod1_primary;
+ } USER_MODALS_INFO_1;
+
+ typedef struct {
+ string usrmod2_domain_name;
+ domsid *usrmod2_domain_id;
+ } USER_MODALS_INFO_2;
+
+ typedef struct {
+ uint32 usrmod3_lockout_duration;
+ uint32 usrmod3_lockout_observation_window;
+ uint32 usrmod3_lockout_threshold;
+ } USER_MODALS_INFO_3;
+
+ typedef struct {
+ uint32 usrmod1001_min_passwd_len;
+ } USER_MODALS_INFO_1001;
+
+ typedef struct {
+ uint32 usrmod1002_max_passwd_age;
+ } USER_MODALS_INFO_1002;
+
+ typedef struct {
+ uint32 usrmod1003_min_passwd_age;
+ } USER_MODALS_INFO_1003;
+
+ typedef struct {
+ uint32 usrmod1004_force_logoff;
+ } USER_MODALS_INFO_1004;
+
+ typedef struct {
+ uint32 usrmod1005_password_hist_len;
+ } USER_MODALS_INFO_1005;
+
+ typedef struct {
+ uint32 usrmod1006_role;
+ } USER_MODALS_INFO_1006;
+
+ typedef struct {
+ string usrmod1007_primary;
+ } USER_MODALS_INFO_1007;
+
+ [nopush,nopull] NET_API_STATUS NetUserModalsGet(
+ [in] string server_name,
+ [in] uint32 level,
+ [out,ref] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetUserModalsSet */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetUserModalsSet(
+ [in] string server_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out,ref] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetQueryDisplayInformation */
+ /*******************************************/
+
+ [public] typedef struct {
+ string usri1_name;
+ string usri1_comment;
+ uint32 usri1_flags;
+ string usri1_full_name;
+ uint32 usri1_user_id;
+ uint32 usri1_next_index;
+ } NET_DISPLAY_USER;
+
+ [public] typedef struct {
+ string usri2_name;
+ string usri2_comment;
+ uint32 usri2_flags;
+ uint32 usri2_user_id;
+ uint32 usri2_next_index;
+ } NET_DISPLAY_MACHINE;
+
+ [public] typedef struct {
+ string grpi3_name;
+ string grpi3_comment;
+ uint32 grpi3_group_id;
+ uint32 grpi3_attributes;
+ uint32 grpi3_next_index;
+ } NET_DISPLAY_GROUP;
+
+ [nopush,nopull] NET_API_STATUS NetQueryDisplayInformation(
+ [in,unique] string *server_name,
+ [in] uint32 level,
+ [in] uint32 idx,
+ [in] uint32 entries_requested,
+ [in] uint32 prefmaxlen,
+ [out,ref] uint32 *entries_read,
+ [out,ref,noprint] void **buffer
+ );
+
+ /*******************************************/
+ /* NetGroupAdd */
+ /*******************************************/
+
+ typedef struct {
+ string grpi0_name;
+ } GROUP_INFO_0;
+
+ typedef struct {
+ string grpi1_name;
+ string grpi1_comment;
+ } GROUP_INFO_1;
+
+ typedef struct {
+ string grpi2_name;
+ string grpi2_comment;
+ uint32 grpi2_group_id;
+ uint32 grpi2_attributes;
+ } GROUP_INFO_2;
+
+ typedef struct {
+ string grpi3_name;
+ string grpi3_comment;
+ domsid *grpi3_group_sid;
+ uint32 grpi3_attributes;
+ } GROUP_INFO_3;
+
+ typedef struct {
+ string grpi1002_comment;
+ } GROUP_INFO_1002;
+
+ typedef struct {
+ uint32 grpi1005_attributes;
+ } GROUP_INFO_1005;
+
+ [nopush,nopull] NET_API_STATUS NetGroupAdd(
+ [in] string server_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetGroupDel */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupDel(
+ [in] string server_name,
+ [in] string group_name
+ );
+
+ /*******************************************/
+ /* NetGroupEnum */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupEnum(
+ [in] string server_name,
+ [in] uint32 level,
+ [out,ref] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out,ref] uint32 *entries_read,
+ [out,ref] uint32 *total_entries,
+ [in,out,ref] uint32 *resume_handle
+ );
+
+ /*******************************************/
+ /* NetGroupSetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupSetInfo(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetGroupGetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupGetInfo(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetGroupAddUser */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupAddUser(
+ [in] string server_name,
+ [in] string group_name,
+ [in] string user_name
+ );
+
+ /*******************************************/
+ /* NetGroupDelUser */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupDelUser(
+ [in] string server_name,
+ [in] string group_name,
+ [in] string user_name
+ );
+
+ /*******************************************/
+ /* NetGroupGetUsers */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupGetUsers(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [out] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out,ref] uint32 *entries_read,
+ [out,ref] uint32 *total_entries,
+ [in,out,ref] uint32 *resume_handle
+ );
+
+ /*******************************************/
+ /* NetGroupSetUsers */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetGroupSetUsers(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [in] uint32 num_entries
+ );
+
+ /*******************************************/
+ /* NetLocalGroupAdd */
+ /*******************************************/
+
+ typedef struct {
+ string lgrpi0_name;
+ } LOCALGROUP_INFO_0;
+
+ typedef struct {
+ string lgrpi1_name;
+ string lgrpi1_comment;
+ } LOCALGROUP_INFO_1;
+
+ typedef struct {
+ string lgrpi1002_comment;
+ } LOCALGROUP_INFO_1002;
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupAdd(
+ [in] string server_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out,ref] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetLocalGroupDel */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupDel(
+ [in] string server_name,
+ [in] string group_name
+ );
+
+ /*******************************************/
+ /* NetLocalGroupGetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupGetInfo(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [out,ref] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetLocalGroupSetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupSetInfo(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [in,ref] uint8 *buffer,
+ [out,ref] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetLocalGroupEnum */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupEnum(
+ [in] string server_name,
+ [in] uint32 level,
+ [out,ref] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out,ref] uint32 *entries_read,
+ [out,ref] uint32 *total_entries,
+ [in,out,ref] uint32 *resume_handle
+ );
+
+ /*******************************************/
+ /* NetLocalGroupAddMembers */
+ /*******************************************/
+
+ typedef enum {
+ SidTypeUser = 1,
+ SidTypeGroup = 2,
+ SidTypeDomain = 3,
+ SidTypeAlias = 4,
+ SidTypeWellKnownGroup = 5,
+ SidTypeDeletedAccount = 6,
+ SidTypeInvalid = 7,
+ SidTypeUnknown = 8,
+ SidTypeComputer = 9,
+ SidTypeLabel = 10
+ } SID_NAME_USE;
+
+ typedef struct {
+ domsid *lgrmi0_sid;
+ } LOCALGROUP_MEMBERS_INFO_0;
+
+ typedef struct {
+ domsid *lgrmi1_sid;
+ SID_NAME_USE lgrmi1_sidusage;
+ string lgrmi1_name;
+ } LOCALGROUP_MEMBERS_INFO_1;
+
+ typedef struct {
+ domsid *lgrmi2_sid;
+ SID_NAME_USE lgrmi2_sidusage;
+ string lgrmi2_domainandname;
+ } LOCALGROUP_MEMBERS_INFO_2;
+
+ typedef struct {
+ string lgrmi3_domainandname;
+ } LOCALGROUP_MEMBERS_INFO_3;
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupAddMembers(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [in] uint32 total_entries
+ );
+
+ /*******************************************/
+ /* NetLocalGroupDelMembers */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupDelMembers(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [in] uint32 total_entries
+ );
+
+ /*******************************************/
+ /* NetLocalGroupGetMembers */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupGetMembers(
+ [in] string server_name,
+ [in] string local_group_name,
+ [in] uint32 level,
+ [out] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out] uint32 *entries_read,
+ [out] uint32 *total_entries,
+ [in,out] uint32 *resume_handle
+ );
+
+ /*******************************************/
+ /* NetLocalGroupSetMembers */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetLocalGroupSetMembers(
+ [in] string server_name,
+ [in] string group_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [in] uint32 total_entries
+ );
+
+ /*******************************************/
+ /* NetRemoteTOD */
+ /*******************************************/
+
+ typedef struct {
+ uint32 tod_elapsedt;
+ uint32 tod_msecs;
+ uint32 tod_hours;
+ uint32 tod_mins;
+ uint32 tod_secs;
+ uint32 tod_hunds;
+ int32 tod_timezone;
+ uint32 tod_tinterval;
+ uint32 tod_day;
+ uint32 tod_month;
+ uint32 tod_year;
+ uint32 tod_weekday;
+ } TIME_OF_DAY_INFO;
+
+ [nopush,nopull] NET_API_STATUS NetRemoteTOD(
+ [in] string server_name,
+ [out,ref] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetShareAdd */
+ /*******************************************/
+
+ typedef struct {
+ string shi0_netname;
+ } SHARE_INFO_0;
+
+ typedef struct {
+ string shi1_netname;
+ uint32 shi1_type;
+ string shi1_remark;
+ } SHARE_INFO_1;
+
+ typedef struct {
+ string shi2_netname;
+ uint32 shi2_type;
+ string shi2_remark;
+ uint32 shi2_permissions;
+ uint32 shi2_max_uses;
+ uint32 shi2_current_uses;
+ string shi2_path;
+ string shi2_passwd;
+ } SHARE_INFO_2;
+
+ typedef struct {
+ string shi501_netname;
+ uint32 shi501_type;
+ string shi501_remark;
+ uint32 shi501_flags;
+ } SHARE_INFO_501;
+
+ typedef struct {
+ string shi502_netname;
+ uint32 shi502_type;
+ string shi502_remark;
+ uint32 shi502_permissions;
+ uint32 shi502_max_uses;
+ uint32 shi502_current_uses;
+ string shi502_path;
+ string shi502_passwd;
+ uint32 shi502_reserved;
+ security_descriptor *shi502_security_descriptor;
+ } SHARE_INFO_502;
+
+ typedef struct {
+ string shi1004_remark;
+ } SHARE_INFO_1004;
+
+ const int CSC_MASK = 0x30;
+
+ typedef [public,bitmap32bit] bitmap {
+ SHI1005_FLAGS_DFS = 0x01,
+ SHI1005_FLAGS_DFS_ROOT = 0x02,
+ CSC_CACHE_MANUAL_REINT = 0x00,
+ CSC_CACHE_AUTO_REINT = 0x10,
+ CSC_CACHE_VDO = 0x20,
+ CSC_CACHE_NONE = 0x30,
+ SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS = 0x0100,
+ SHI1005_FLAGS_FORCE_SHARED_DELETE = 0x0200,
+ SHI1005_FLAGS_ALLOW_NAMESPACE_CACHING = 0x0400,
+ SHI1005_FLAGS_ACCESS_BASED_DIRECTORY_ENUM = 0x0800
+ } SHARE_INFO_1005_FLAGS;
+
+ typedef struct {
+ SHARE_INFO_1005_FLAGS shi1005_flags;
+ } SHARE_INFO_1005;
+
+ typedef struct {
+ uint32 shi1006_max_uses;
+ } SHARE_INFO_1006;
+
+ [nopush,nopull] NET_API_STATUS NetShareAdd(
+ [in] string server_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetShareDel */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetShareDel(
+ [in] string server_name,
+ [in] string net_name,
+ [in] uint32 reserved
+ );
+
+ /*******************************************/
+ /* NetShareEnum */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetShareEnum(
+ [in] string server_name,
+ [in] uint32 level,
+ [out] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out] uint32 *entries_read,
+ [out] uint32 *total_entries,
+ [in,out] uint32 *resume_handle
+ );
+
+ /*******************************************/
+ /* NetShareGetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetShareGetInfo(
+ [in] string server_name,
+ [in] string net_name,
+ [in] uint32 level,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetShareSetInfo */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetShareSetInfo(
+ [in] string server_name,
+ [in] string net_name,
+ [in] uint32 level,
+ [in] uint8 *buffer,
+ [out] uint32 *parm_err
+ );
+
+ /*******************************************/
+ /* NetFileClose */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetFileClose(
+ [in] string server_name,
+ [in] uint32 fileid
+ );
+
+ /*******************************************/
+ /* NetFileGetInfo */
+ /*******************************************/
+
+ typedef struct {
+ uint32 fi2_id;
+ } FILE_INFO_2;
+
+ typedef struct {
+ uint32 fi3_id;
+ uint32 fi3_permissions;
+ uint32 fi3_num_locks;
+ string fi3_pathname;
+ string fi3_username;
+ } FILE_INFO_3;
+
+ [nopush,nopull] NET_API_STATUS NetFileGetInfo(
+ [in] string server_name,
+ [in] uint32 fileid,
+ [in] uint32 level,
+ [out] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* NetFileEnum */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetFileEnum(
+ [in] string server_name,
+ [in] string base_path,
+ [in] string user_name,
+ [in] uint32 level,
+ [out] uint8 **buffer,
+ [in] uint32 prefmaxlen,
+ [out] uint32 *entries_read,
+ [out] uint32 *total_entries,
+ [in,out] uint32 *resume_handle
+ );
+
+ /*******************************************/
+ /* NetShutdownInit */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetShutdownInit(
+ [in] string server_name,
+ [in] string message,
+ [in] uint32 timeout,
+ [in] uint8 force_apps,
+ [in] uint8 do_reboot
+ );
+
+ /*******************************************/
+ /* NetShutdownAbort */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS NetShutdownAbort(
+ [in] string server_name
+ );
+
+ /*******************************************/
+ /* I_NetLogonControl */
+ /*******************************************/
+
+ typedef struct {
+ uint32 netlog1_flags;
+ NET_API_STATUS netlog1_pdc_connection_status;
+ } NETLOGON_INFO_1;
+
+ typedef struct {
+ uint32 netlog2_flags;
+ NET_API_STATUS netlog2_pdc_connection_status;
+ string netlog2_trusted_dc_name;
+ NET_API_STATUS netlog2_tc_connection_status;
+ } NETLOGON_INFO_2;
+
+ typedef struct {
+ uint32 netlog1_flags;
+ uint32 netlog3_logon_attempts;
+ uint32 netlog3_reserved1;
+ uint32 netlog3_reserved2;
+ uint32 netlog3_reserved3;
+ uint32 netlog3_reserved4;
+ uint32 netlog3_reserved5;
+ } NETLOGON_INFO_3;
+
+ typedef struct {
+ string netlog4_trusted_dc_name;
+ string netlog4_trusted_domain_name;
+ } NETLOGON_INFO_4;
+
+ [nopush,nopull] NET_API_STATUS I_NetLogonControl(
+ [in] string server_name,
+ [in] uint32 function_code,
+ [in] uint32 query_level,
+ [out,ref] uint8 **buffer
+ );
+
+ /*******************************************/
+ /* I_NetLogonControl2 */
+ /*******************************************/
+
+ [nopush,nopull] NET_API_STATUS I_NetLogonControl2(
+ [in] string server_name,
+ [in] uint32 function_code,
+ [in] uint32 query_level,
+ [in,unique] uint8 *data,
+ [out,ref] uint8 **buffer
+ );
+}
diff --git a/source3/librpc/idl/open_files.idl b/source3/librpc/idl/open_files.idl
new file mode 100644
index 0000000..5dea021
--- /dev/null
+++ b/source3/librpc/idl/open_files.idl
@@ -0,0 +1,133 @@
+#include "idl_types.h"
+
+import "server_id.idl";
+import "security.idl";
+import "file_id.idl";
+import "smb2_lease_struct.idl";
+import "misc.idl";
+
+[
+ pointer_default(unique)
+]
+
+interface open_files
+{
+ typedef [enum16bit] enum {
+ SHARE_MODE_ENTRY_OP_TYPE_NONE = 0,
+ SHARE_MODE_ENTRY_OP_TYPE_EXCLUSIVE = 1,
+ SHARE_MODE_ENTRY_OP_TYPE_BATCH_WITHOUT_EXCLUSIVE = 2,
+ SHARE_MODE_ENTRY_OP_TYPE_BATCH = 3,
+ SHARE_MODE_ENTRY_OP_TYPE_LEVEL_II = 4,
+ SHARE_MODE_ENTRY_OP_TYPE_LEASE = 0x100
+ } share_mode_entry_op_type;
+
+ typedef [public] struct {
+ server_id pid;
+ hyper op_mid;
+ share_mode_entry_op_type op_type;
+ GUID client_guid;
+ smb2_lease_key lease_key;
+ uint32 access_mask;
+ uint32 share_access;
+ uint32 private_options;
+ timeval time;
+ udlong share_file_id;
+ uint32 uid;
+ uint16 flags;
+ uint32 name_hash;
+
+ /*
+ * In-memory flag indicating a non-existing pid. We don't want
+ * to store this share_mode_entry on disk.
+ */
+ [skip] boolean8 stale;
+ } share_mode_entry;
+
+ typedef [public] struct {
+ uint32 name_hash;
+ security_token *delete_nt_token;
+ security_unix_token *delete_token;
+ } delete_token;
+
+ typedef [public,bitmap16bit] bitmap {
+ SHARE_MODE_SHARE_DELETE = 0x100,
+ SHARE_MODE_SHARE_WRITE = 0x080,
+ SHARE_MODE_SHARE_READ = 0x040,
+ SHARE_MODE_ACCESS_DELETE = 0x020,
+ SHARE_MODE_ACCESS_WRITE = 0x010,
+ SHARE_MODE_ACCESS_READ = 0x008,
+ SHARE_MODE_LEASE_HANDLE = 0x004,
+ SHARE_MODE_LEASE_WRITE = 0x002,
+ SHARE_MODE_LEASE_READ = 0x001
+ } share_mode_flags;
+
+ typedef [public] struct {
+ hyper unique_content_epoch;
+ share_mode_flags flags;
+ [string,charset(UTF8)] char *servicepath;
+ [string,charset(UTF8)] char *base_name;
+ [string,charset(UTF8)] char *stream_name;
+ uint32 num_delete_tokens;
+ [size_is(num_delete_tokens)] delete_token delete_tokens[];
+ NTTIME old_write_time;
+ NTTIME changed_write_time;
+ [skip] boolean8 not_stored;
+ [skip] boolean8 modified;
+ [ignore] file_id id; /* In memory key used to lookup cache. */
+ } share_mode_data;
+
+ /* these are 0x30 (48) characters */
+ const string VFS_DEFAULT_DURABLE_COOKIE_MAGIC =
+ "VFS_DEFAULT_DURABLE_COOKIE_MAGIC ";
+ const uint32 VFS_DEFAULT_DURABLE_COOKIE_VERSION = 0;
+
+ /* this corresponds to struct stat_ex (SMB_STRUCT_STAT) */
+ typedef struct {
+ hyper st_ex_dev;
+ hyper st_ex_ino;
+ hyper st_ex_mode;
+ hyper st_ex_nlink;
+ hyper st_ex_uid;
+ hyper st_ex_gid;
+ hyper st_ex_rdev;
+ hyper st_ex_size;
+ timespec st_ex_atime;
+ timespec st_ex_mtime;
+ timespec st_ex_ctime;
+ timespec st_ex_btime;
+ hyper st_ex_blksize;
+ hyper st_ex_blocks;
+ uint32 st_ex_flags;
+ uint32 st_ex_iflags;
+ } vfs_default_durable_stat;
+
+ typedef [public] struct {
+ [value(VFS_DEFAULT_DURABLE_COOKIE_MAGIC),charset(DOS)] uint8 magic[0x30];
+ [value(VFS_DEFAULT_DURABLE_COOKIE_VERSION)] uint32 version;
+ boolean8 allow_reconnect;
+ file_id id;
+ [string,charset(UTF8)] char *servicepath;
+ [string,charset(UTF8)] char *base_name;
+ hyper initial_allocation_size;
+ hyper position_information;
+ boolean8 update_write_time_triggered;
+ boolean8 update_write_time_on_close;
+ boolean8 write_time_forced;
+ NTTIME close_write_time;
+ vfs_default_durable_stat stat_info;
+ } vfs_default_durable_cookie;
+
+ typedef [public] struct {
+ file_id id;
+ udlong share_file_id;
+ uint8 break_to;
+ } oplock_break_message;
+
+ typedef [public] struct {
+ file_id id;
+ udlong share_file_id;
+ [string,charset(UTF8)] char *servicepath;
+ [string,charset(UTF8)] char *base_name;
+ [string,charset(UTF8)] char *stream_name;
+ } file_rename_message;
+}
diff --git a/source3/librpc/idl/perfcount.idl b/source3/librpc/idl/perfcount.idl
new file mode 100644
index 0000000..e797e01
--- /dev/null
+++ b/source3/librpc/idl/perfcount.idl
@@ -0,0 +1,172 @@
+#include "idl_types.h"
+
+/*
+ IDL structures for perfcount code
+*/
+
+[
+ pointer_default(unique)
+]
+ interface perfcount
+{
+ const int PERF_NO_INSTANCES = -1;
+ const int PERF_NO_UNIQUE_ID = -1;
+
+ /* These determine the data size */
+ const int PERF_SIZE_DWORD = 0x00000000;
+ const int PERF_SIZE_LARGE = 0x00000100;
+ const int PERF_SIZE_ZERO = 0x00000200;
+ const int PERF_SIZE_VARIABLE_LEN = 0x00000300;
+
+ /* These determine the usage of the counter */
+ const int PERF_TYPE_NUMBER = 0x00000000;
+ const int PERF_TYPE_COUNTER = 0x00000400;
+ const int PERF_TYPE_TEXT = 0x00000800;
+ const int PERF_TYPE_ZERO = 0x00000C00;
+
+ /* If PERF_TYPE_NUMBER was selected, these provide display information */
+ const int PERF_NUMBER_HEX = 0x00000000;
+ const int PERF_NUMBER_DECIMAL = 0x00010000;
+ const int PERF_NUMBER_DEC_1000 = 0x00020000;
+
+ /* If PERF_TYPE_COUNTER was selected, these provide display information */
+ const int PERF_COUNTER_VALUE = 0x00000000;
+ const int PERF_COUNTER_RATE = 0x00010000;
+ const int PERF_COUNTER_FRACTION = 0x00020000;
+ const int PERF_COUNTER_BASE = 0x00030000;
+ const int PERF_COUNTER_ELAPSED = 0x00040000;
+ const int PERF_COUNTER_QUEUELEN = 0x00050000;
+ const int PERF_COUNTER_HISTOGRAM = 0x00060000;
+ const int PERF_COUNTER_PRECISION = 0x00070000;
+
+ /* If PERF_TYPE_TEXT was selected, these provide display information */
+ const int PERF_TEXT_UNICODE = 0x00000000;
+ const int PERF_TEXT_ASCII = 0x00010000;
+
+ /* These provide information for which tick count to use when computing elapsed interval */
+ const int PERF_TIMER_TICK = 0x00000000;
+ const int PERF_TIMER_100NS = 0x00100000;
+ const int PERF_OBJECT_TIMER = 0x00200000;
+
+ /* These affect how the data is manipulated prior to being displayed */
+ const int PERF_DELTA_COUNTER = 0x00400000;
+ const int PERF_DELTA_BASE = 0x00800000;
+ const int PERF_INVERSE_COUNTER = 0x01000000;
+ const int PERF_MULTI_COUNTER = 0x02000000;
+
+ /* These determine if any text gets added when the value is displayed */
+ const int PERF_DISPLAY_NO_SUFFIX = 0x00000000;
+ const int PERF_DISPLAY_PER_SEC = 0x10000000;
+ const int PERF_DISPLAY_PERCENT = 0x20000000;
+ const int PERF_DISPLAY_SECONDS = 0x30000000;
+ const int PERF_DISPLAY_NOSHOW = 0x40000000;
+
+ /* These determine the DetailLevel of the counter */
+ const int PERF_DETAIL_NOVICE = 100;
+ const int PERF_DETAIL_ADVANCED = 200;
+ const int PERF_DETAIL_EXPERT = 300;
+ const int PERF_DETAIL_WIZARD = 400;
+
+ typedef struct {
+ uint16 year;
+ uint16 month;
+ uint16 dayofweek;
+ uint16 day;
+ uint16 hour;
+ uint16 minute;
+ uint16 second;
+ uint16 milliseconds;
+ } SYSTEMTIME;
+
+ typedef [public] struct {
+ /* sizeof(PERF_COUNTER_DEFINITION) */
+ uint32 ByteLength;
+ uint32 CounterNameTitleIndex;
+ uint32 CounterNameTitlePointer;
+ uint32 CounterHelpTitleIndex;
+ uint32 CounterHelpTitlePointer;
+ uint32 DefaultScale;
+ uint32 DetailLevel;
+ uint32 CounterType;
+ uint32 CounterSize;
+ uint32 CounterOffset;
+ } PERF_COUNTER_DEFINITION;
+
+ typedef [public] struct {
+ /* Total size of the data block, including all data plus this header */
+ uint32 ByteLength;
+ uint8 data[ByteLength];
+ } PERF_COUNTER_BLOCK;
+
+ typedef [public] struct {
+ /* Total size of the instance definition, including the length of the terminated Name string */
+ uint32 ByteLength;
+ uint32 ParentObjectTitleIndex;
+ uint32 ParentObjectTitlePointer;
+ uint32 UniqueID;
+ /* From the start of the PERF_INSTANCE_DEFINITION, the byte offset to the start of the Name string */
+ uint32 NameOffset;
+ uint32 NameLength;
+ /* Unicode string containing the name for the instance */
+ uint8 *data;
+ PERF_COUNTER_BLOCK counter_data;
+ } PERF_INSTANCE_DEFINITION;
+
+ typedef [public] struct {
+ /* Total size of the object block, including all PERF_INSTANCE_DEFINITIONs,
+ PERF_COUNTER_DEFINITIONs and PERF_COUNTER_BLOCKs in bytes */
+ uint32 TotalByteLength;
+ /* Size of this PERF_OBJECT_TYPE plus all PERF_COUNTER_DEFINITIONs in bytes */
+ uint32 DefinitionLength;
+ /* Size of this PERF_OBJECT_TYPE */
+ uint32 HeaderLength;
+ uint32 ObjectNameTitleIndex;
+ uint32 ObjectNameTitlePointer;
+ uint32 ObjectHelpTitleIndex;
+ uint32 ObjectHelpTitlePointer;
+ uint32 DetailLevel;
+ uint32 NumCounters;
+ uint32 DefaultCounter;
+ uint32 NumInstances;
+ uint32 CodePage;
+ hyper PerfTime;
+ hyper PerfFreq;
+ PERF_COUNTER_DEFINITION counters[NumCounters];
+ PERF_INSTANCE_DEFINITION instances[NumInstances];
+ PERF_COUNTER_BLOCK counter_data;
+ } PERF_OBJECT_TYPE;
+
+ /* PerfCounter Inner Buffer structs */
+ typedef [public] struct {
+ /* hardcoded to read "P.E.R.F" */
+ uint16 Signature[4];
+ uint32 LittleEndian;
+ /* both currently hardcoded to 1 */
+ uint32 Version;
+ uint32 Revision;
+ /* bytes of PERF_OBJECT_TYPE data, does NOT include the PERF_DATA_BLOCK */
+ uint32 TotalByteLength;
+ /* size of PERF_DATA_BLOCK including the uint8 *data */
+ uint32 HeaderLength;
+ /* number of PERF_OBJECT_TYPE structures encoded */
+ uint32 NumObjectTypes;
+ uint32 DefaultObject;
+ SYSTEMTIME SystemTime;
+ /* This will guarantee that we're on a 64-bit boundary before we encode
+ PerfTime, and having it there will make my offset math much easier. */
+ uint32 Padding;
+ /* Now when I'm marshalling this, I'll need to call prs_align_uint64()
+ before I start encoding the uint64 structs */
+ /* clock rate * seconds uptime */
+ hyper PerfTime;
+ /* The clock rate of the CPU */
+ hyper PerfFreq;
+ /* used for high-res timers -- for now PerfTime * 10e7 */
+ hyper PerfTime100nSec;
+ uint32 SystemNameLength;
+ uint32 SystemNameOffset;
+ /* The SystemName, in unicode, terminated */
+ uint8* data;
+ PERF_OBJECT_TYPE objects[NumObjectTypes];
+ } PERF_DATA_BLOCK;
+}
diff --git a/source3/librpc/idl/rpc_host.idl b/source3/librpc/idl/rpc_host.idl
new file mode 100644
index 0000000..9eb48d3
--- /dev/null
+++ b/source3/librpc/idl/rpc_host.idl
@@ -0,0 +1,93 @@
+/**
+ * @file rpc_host.idl
+ *
+ * Definitions for communication between samba-dcerpcd and rpc helper
+ * daemons
+ */
+#include "idl_types.h"
+
+import "named_pipe_auth.idl";
+import "dcerpc.idl";
+
+[
+ pointer_default(unique)
+]
+
+interface rpc_host_msg
+{
+ /**
+ * @brief MSG_RPC_HOST_NEW_CLIENT: samba-dcerpcd->rpcd
+ *
+ * samba-dcerpcd to rpcd msg announcing a new client. This
+ * will carry a file descriptor representing the new
+ * connection
+ */
+ typedef [public] struct {
+ /**
+ * @brief The binding the client wants to connect to
+ */
+ [string,charset(UTF8)] char *binding;
+
+ /**
+ * @brief Auth info inherited from SMB
+ */
+ named_pipe_auth_req_info8 *npa_info8;
+
+ /**
+ * @brief Raw bind PDU
+ *
+ * Already read from the socket by samba-dcerpcd
+ */
+ DATA_BLOB bind_packet;
+ } rpc_host_client;
+
+ /**
+ * @brief MSG_RPC_WORKER_STATUS: rpcd->samba-dcerpcd
+ *
+ * Once a client has been handed over from samba-dcerpcd to an
+ * rpcd, samba-dcerpc has no control over this socket
+ * anymore. So it can't know when the client exits and the
+ * rpcd helper process has a slot free. Thus whenever a client
+ * exits, rpcd sends this message up to samba-dcerpc for a
+ * status update.
+ */
+ typedef [flag(NDR_NOALIGN),public] struct {
+ /**
+ * @brief Which rpc service is this message from
+ *
+ * samba-dcerpcd will only have the source pid of this
+ * message. To avoid a walk through all arrays with
+ * all rpcd helpers, give samba-dcerpcd a hint where
+ * in its arrays to find the rpcd that sent this
+ * message.
+ */
+ uint32 server_index;
+
+ /**
+ * @brief Which of the processes of a helper prog is this from
+ *
+ * @note while this is uint32, we currently only support 16-bit
+ * values, as we use it in the high 16-bits of the 32-bit
+ * association group id.
+ */
+ uint32 worker_index;
+
+
+ /**
+ * @brief How many association groups this process serves right now
+ *
+ * @note while this is uint32, we currently only support 16-bit
+ * values, as we use the worker_index in the high 16-bits of
+ * so we only have 16-bit per worker in order to form the 32-bit
+ * association group id.
+ */
+ uint32 num_association_groups;
+
+ /**
+ * @brief How many client connections this process serves right now
+ *
+ * @note might be greater or equal to num_association_groups.
+ */
+ uint32 num_connections;
+ } rpc_worker_status;
+}
diff --git a/source3/librpc/idl/rpcd_witness.idl b/source3/librpc/idl/rpcd_witness.idl
new file mode 100644
index 0000000..80852d2
--- /dev/null
+++ b/source3/librpc/idl/rpcd_witness.idl
@@ -0,0 +1,109 @@
+#include "idl_types.h"
+
+import "misc.idl";
+import "server_id.idl";
+import "security.idl";
+import "witness.idl";
+
+[
+ uuid("d320d080-a007-11ee-9cb4-37fa942d84d2"),
+ version(0.0),
+ pointer_default(unique),
+ helpstring("rpcd_witness structures")
+]
+interface rpcd_witness
+{
+ /*
+ * This the content of records stored in
+ * rpcd_witness_registration.tdb.
+ */
+ typedef [public] struct {
+ witness_version version;
+ [string,charset(UTF8)] char net_name[];
+ [string,charset(UTF8)] char *share_name;
+ [string,charset(UTF8)] char ip_address[];
+ [string,charset(UTF8)] char client_computer_name[];
+ witness_RegisterEx_flags flags;
+ uint32 timeout;
+ policy_handle context_handle;
+ server_id server_id;
+ [charset(UTF8),string] char account_name[];
+ [charset(UTF8),string] char domain_name[];
+ dom_sid account_sid;
+ [charset(UTF8),string] char local_address[];
+ [charset(UTF8),string] char remote_address[];
+ NTTIME registration_time;
+ } rpcd_witness_registration;
+
+ void rpcd_witness_registration_decode(
+ [in] rpcd_witness_registration reg
+ );
+
+ typedef [enum16bit] enum {
+ RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE = 0x0001,
+ RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV4 = 0x0002,
+ RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV6 = 0x0003,
+ RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE = 0x0004,
+ RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV4 = 0x0005,
+ RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV6 = 0x0006,
+ RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_UNREGISTER = 0x0007,
+ RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_RESPONSE = 0x0008
+ } rpcd_witness_registration_update_type;
+
+ typedef struct {
+ uint32 new_node;
+ } rpcd_witness_registration_update_move_to_node;
+
+ typedef struct {
+ [flag(NDR_BIG_ENDIAN)] ipv4address new_ipv4;
+ } rpcd_witness_registration_update_move_to_ipv4;
+
+ typedef struct {
+ [flag(NDR_BIG_ENDIAN)] ipv6address new_ipv6;
+ } rpcd_witness_registration_update_move_to_ipv6;
+
+ typedef struct {
+ witness_notifyResponse *response;
+ WERROR result;
+ } rpcd_witness_registration_update_force_response;
+
+ typedef [switch_type(rpcd_witness_registration_update_type)] union {
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE)]
+ rpcd_witness_registration_update_move_to_node client_move_to_node;
+
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV4)]
+ rpcd_witness_registration_update_move_to_ipv4 client_move_to_ipv4;
+
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV6)]
+ rpcd_witness_registration_update_move_to_ipv6 client_move_to_ipv6;
+
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE)]
+ rpcd_witness_registration_update_move_to_node share_move_to_node;
+
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV4)]
+ rpcd_witness_registration_update_move_to_ipv4 share_move_to_ipv4;
+
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV6)]
+ rpcd_witness_registration_update_move_to_ipv6 share_move_to_ipv6;
+
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_UNREGISTER)];
+ /* empty */
+
+ [case(RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_RESPONSE)]
+ rpcd_witness_registration_update_force_response force_response;
+ } rpcd_witness_registration_updateU;
+
+ /*
+ * This is the content of
+ * MSG_RPCD_WITNESS_REGISTRATION_UPDATE messages
+ */
+ typedef [public] struct {
+ policy_handle context_handle;
+ rpcd_witness_registration_update_type type;
+ [switch_is(type)] rpcd_witness_registration_updateU update;
+ } rpcd_witness_registration_updateB;
+
+ void rpcd_witness_registration_update_decode(
+ [in] rpcd_witness_registration_updateB update
+ );
+}
diff --git a/source3/librpc/idl/secrets.idl b/source3/librpc/idl/secrets.idl
new file mode 100644
index 0000000..2c06fa6
--- /dev/null
+++ b/source3/librpc/idl/secrets.idl
@@ -0,0 +1,131 @@
+#include "idl_types.h"
+
+import "misc.idl", "samr.idl", "lsa.idl", "netlogon.idl", "security.idl";
+
+/*
+ IDL structures for secrets code
+*/
+
+[
+ pointer_default(unique)
+]
+ interface secrets
+{
+
+ /*
+ * s3 on-disc storage structure for trusted domains, do not change !
+ */
+
+ typedef [flag(NDR_NOALIGN),public] struct {
+ [value(strlen_m_term(uni_name))] uint32 uni_name_len;
+ [charset(UTF16)] uint16 uni_name[32]; /* unicode domain name */
+ [value(strlen(pass))] uint32 pass_len;
+ astring pass; /* trust relationship's password */
+ time_t mod_time;
+ dom_sid domain_sid; /* remote domain's sid */
+ } TRUSTED_DOM_PASS;
+
+ /*
+ * s3 on-disc storage structure for lsa secrets, do not change !
+ */
+
+ typedef [public] struct {
+ DATA_BLOB *secret_current;
+ NTTIME secret_current_lastchange;
+ DATA_BLOB *secret_old;
+ NTTIME secret_old_lastchange;
+ security_descriptor *sd;
+ } lsa_secret;
+
+ /*
+ * This is the on-disc format the workstation trust.
+ *
+ * DO NOT CHANGE
+ * without changing secrets_domain_info_version
+ * and adding glue code. Discuss on samba-technical
+ * first!
+ */
+ typedef struct {
+ uint32 keytype;
+ uint32 iteration_count;
+ [flag(NDR_SECRET)] DATA_BLOB value;
+ } secrets_domain_info1_kerberos_key;
+
+ typedef struct {
+ NTTIME change_time;
+ [string,charset(UTF16)] uint16 change_server[];
+
+ [flag(NDR_SECRET)] DATA_BLOB cleartext_blob;
+ [flag(NDR_SECRET)] samr_Password nt_hash;
+
+ [string,charset(UTF16)] uint16 *salt_data;
+ uint32 default_iteration_count;
+ uint16 num_keys;
+ secrets_domain_info1_kerberos_key keys[num_keys];
+ } secrets_domain_info1_password;
+
+ typedef struct {
+ NTSTATUS local_status;
+ NTSTATUS remote_status;
+ NTTIME change_time;
+ [string,charset(UTF16)] uint16 change_server[];
+ [ref] secrets_domain_info1_password *password;
+ } secrets_domain_info1_change;
+
+ typedef [public] struct {
+ [value(0)] hyper reserved_flags;
+
+ NTTIME join_time;
+
+ [string,charset(UTF16)] uint16 computer_name[];
+ [string,charset(UTF16)] uint16 account_name[];
+ netr_SchannelType secure_channel_type;
+
+ lsa_DnsDomainInfo domain_info;
+ netr_TrustFlags trust_flags;
+ lsa_TrustType trust_type;
+ lsa_TrustAttributes trust_attributes;
+
+ /*
+ * This is unused currently, it might
+ * be useful to implement multi-tenancy (joining multiple domains)
+ * in future.
+ *
+ * Or we could use it to do other filtering of domains.
+ */
+ [value(NULL)] lsa_ForestTrustInformation *reserved_routing;
+
+ kerb_EncTypes supported_enc_types;
+ [string,charset(UTF16)] uint16 *salt_principal;
+
+ NTTIME password_last_change;
+ hyper password_changes;
+ secrets_domain_info1_change *next_change;
+
+ [ref] secrets_domain_info1_password *password;
+ secrets_domain_info1_password *old_password;
+ secrets_domain_info1_password *older_password;
+ } secrets_domain_info1;
+
+ typedef [v1_enum] enum {
+ SECRETS_DOMAIN_INFO_VERSION_1 = 0x00000001
+ } secrets_domain_info_version;
+
+ /*
+ * If we ever need to change this we need to
+ * change secrets_domain_info into
+ * secrets_domain_info_v1
+ */
+ typedef union {
+ [case(SECRETS_DOMAIN_INFO_VERSION_1)]
+ secrets_domain_info1 *info1;
+ [default];
+ } secrets_domain_infoU;
+
+ typedef [public] struct {
+ secrets_domain_info_version version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] secrets_domain_infoU info;
+ } secrets_domain_infoB;
+}
+
diff --git a/source3/librpc/idl/smbXsrv.idl b/source3/librpc/idl/smbXsrv.idl
new file mode 100644
index 0000000..4eb43e6
--- /dev/null
+++ b/source3/librpc/idl/smbXsrv.idl
@@ -0,0 +1,549 @@
+#include "idl_types.h"
+
+import "misc.idl";
+import "server_id.idl";
+import "security.idl";
+import "auth.idl";
+
+[
+ uuid("07408340-ae31-11e1-97dc-539f7fddc06f"),
+ version(0.0),
+ pointer_default(unique),
+ helpstring("smbXsrv structures")
+]
+interface smbXsrv
+{
+ /*
+ * smbXsrv_version* is designed to allow
+ * rolling code upgrades in future (within a cluster).
+ *
+ * This just adds the infrastructure,
+ * but it does not implement it yet!
+ *
+ * Currently it only prevents that
+ * nodes with a different version numbers
+ * cannot run at the same time.
+ *
+ * Each node checks at startup, if the version
+ * matches the version of all other nodes.
+ * And it exits if the version does not match
+ * to avoid corruption.
+ *
+ * While it would be possible to add versioning
+ * to each of our internal databases it is easier
+ * use a dedicated database "smbXsrv_version_global.tdb"
+ * to hold the global version information.
+ *
+ * This removes extra complexity from the individual
+ * databases and allows that we add/remove databases
+ * or use different indexing keys.
+ *
+ */
+ typedef [v1_enum] enum {
+ /*
+ * NOTE: Version 0 is designed to be unstable and the format
+ * may change during development.
+ */
+ SMBXSRV_VERSION_0 = 0x00000000
+ } smbXsrv_version_values;
+
+ const uint32 SMBXSRV_VERSION_CURRENT = SMBXSRV_VERSION_0;
+
+ typedef struct {
+ server_id server_id;
+ smbXsrv_version_values min_version;
+ smbXsrv_version_values max_version;
+ smbXsrv_version_values current_version;
+ } smbXsrv_version_node0;
+
+ typedef struct {
+ [ignore] db_record *db_rec;
+ [range(1, 1024)] uint32 num_nodes;
+ smbXsrv_version_node0 nodes[num_nodes];
+ } smbXsrv_version_global0;
+
+ typedef union {
+ [case(0)] smbXsrv_version_global0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_version_globalU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ uint32 seqnum;
+ [switch_is(version)] smbXsrv_version_globalU info;
+ } smbXsrv_version_globalB;
+
+ void smbXsrv_version_global_decode(
+ [in] smbXsrv_version_globalB blob
+ );
+
+ /* client */
+
+ typedef struct {
+ [ignore] db_record *db_rec;
+ server_id server_id;
+ [charset(UTF8),string] char local_address[];
+ [charset(UTF8),string] char remote_address[];
+ [charset(UTF8),string] char remote_name[];
+ NTTIME initial_connect_time;
+ GUID client_guid;
+ boolean8 stored;
+ } smbXsrv_client_global0;
+
+ typedef union {
+ [case(0)] smbXsrv_client_global0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_client_globalU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ uint32 seqnum;
+ [switch_is(version)] smbXsrv_client_globalU info;
+ } smbXsrv_client_globalB;
+
+ void smbXsrv_client_global_decode(
+ [in] smbXsrv_client_globalB blob
+ );
+
+ typedef [public] struct {
+ [ignore] smbXsrv_client_table *table;
+ [ignore] struct tevent_context *raw_ev_ctx;
+ [ignore] struct messaging_context *msg_ctx;
+
+ [ref] smbXsrv_client_global0 *global;
+
+ /*
+ * There's just one 'sconn' per client.
+ * It holds the FSA layer details, which are global
+ * per client (process).
+ */
+ [ignore] struct smbd_server_connection *sconn;
+
+ /*
+ * this session_table is used for SMB1 and SMB2,
+ */
+ [ignore] struct smbXsrv_session_table *session_table;
+ /*
+ * this tcon_table is only used for SMB1.
+ */
+ [ignore] struct smbXsrv_tcon_table *tcon_table;
+ /*
+ * this open_table is used for SMB1 and SMB2,
+ * because we have a global sconn->real_max_open_files
+ * limit.
+ */
+ [ignore] struct smbXsrv_open_table *open_table;
+
+ /*
+ * For now this is only one connection!
+ * With multi-channel support we'll get more than
+ * one in future.
+ */
+ [ignore] struct smbXsrv_connection *connections;
+ boolean8 server_multi_channel_enabled;
+ hyper next_channel_id;
+ [ignore] struct tevent_req *connection_pass_subreq;
+ [ignore] struct tevent_req *connection_drop_subreq;
+
+ /*
+ * A List of pending breaks.
+ */
+ [ignore] struct smbXsrv_pending_break *pending_breaks;
+ } smbXsrv_client;
+
+ typedef union {
+ [case(0)] smbXsrv_client *info0;
+ [default] hyper *dummy;
+ } smbXsrv_clientU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] smbXsrv_clientU info;
+ } smbXsrv_clientB;
+
+ void smbXsrv_client_decode(
+ [in] smbXsrv_clientB blob
+ );
+
+ /*
+ * smbXsrv_connection_pass is used in the MSG_SMBXSRV_CONNECTION_PASS
+ * message and echo'ed as MSG_SMBXSRV_CONNECTION_PASSED message with
+ * negotiate_request.length = 0.
+ */
+ typedef struct {
+ GUID client_guid;
+ server_id src_server_id;
+ NTTIME xconn_connect_time;
+ server_id dst_server_id;
+ NTTIME client_connect_time;
+ DATA_BLOB negotiate_request;
+ } smbXsrv_connection_pass0;
+
+ typedef union {
+ [case(0)] smbXsrv_connection_pass0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_connection_passU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] smbXsrv_connection_passU info;
+ } smbXsrv_connection_passB;
+
+ void smbXsrv_connection_pass_decode(
+ [in] smbXsrv_connection_passB blob
+ );
+
+ /*
+ * smbXsrv_connection_drop is used in the MSG_SMBXSRV_CONNECTION_DROP
+ * message as reaction the record is deleted.
+ */
+ typedef struct {
+ GUID client_guid;
+ server_id src_server_id;
+ NTTIME xconn_connect_time;
+ server_id dst_server_id;
+ NTTIME client_connect_time;
+ } smbXsrv_connection_drop0;
+
+ typedef union {
+ [case(0)] smbXsrv_connection_drop0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_connection_dropU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] smbXsrv_connection_dropU info;
+ } smbXsrv_connection_dropB;
+
+ void smbXsrv_connection_drop_decode(
+ [in] smbXsrv_connection_dropB blob
+ );
+
+ /* sessions */
+
+ typedef [public,bitmap8bit] bitmap {
+ SMBXSRV_ENCRYPTION_REQUIRED = 0x01,
+ SMBXSRV_ENCRYPTION_DESIRED = 0x02,
+ SMBXSRV_PROCESSED_ENCRYPTED_PACKET = 0x04,
+ SMBXSRV_PROCESSED_UNENCRYPTED_PACKET = 0x08
+ } smbXsrv_encrpytion_flags;
+
+ typedef [public,bitmap8bit] bitmap {
+ SMBXSRV_SIGNING_REQUIRED = 0x01,
+ SMBXSRV_PROCESSED_SIGNED_PACKET = 0x02,
+ SMBXSRV_PROCESSED_UNSIGNED_PACKET = 0x04
+ } smbXsrv_signing_flags;
+
+ typedef struct {
+ server_id server_id;
+ hyper channel_id;
+ NTTIME creation_time;
+ [charset(UTF8),string] char local_address[];
+ [charset(UTF8),string] char remote_address[];
+ [charset(UTF8),string] char remote_name[];
+ [noprint] DATA_BLOB signing_key_blob;
+ [ignore] smb2_signing_key *signing_key;
+ uint32 auth_session_info_seqnum;
+ [ignore] smbXsrv_connection *connection;
+ uint16 signing_algo;
+ uint16 encryption_cipher;
+ } smbXsrv_channel_global0;
+
+ typedef struct {
+ [ignore] db_record *db_rec;
+ uint32 session_global_id;
+ hyper session_wire_id;
+ NTTIME creation_time;
+ NTTIME expiration_time;
+ /*
+ * auth_session is NULL until the
+ * session is valid for the first time.
+ */
+ NTTIME auth_time;
+ uint32 auth_session_info_seqnum;
+ auth_session_info *auth_session_info;
+ uint16 connection_dialect;
+ GUID client_guid;
+ smbXsrv_signing_flags signing_flags;
+ uint16 signing_algo;
+ smbXsrv_encrpytion_flags encryption_flags;
+ uint16 encryption_cipher;
+ [noprint] DATA_BLOB signing_key_blob;
+ [ignore] smb2_signing_key *signing_key;
+ [noprint] DATA_BLOB encryption_key_blob;
+ [ignore] smb2_signing_key *encryption_key;
+ [noprint] DATA_BLOB decryption_key_blob;
+ [ignore] smb2_signing_key *decryption_key;
+ [noprint] DATA_BLOB application_key_blob;
+ [ignore] smb2_signing_key *application_key;
+ [range(1, 1024)] uint32 num_channels;
+ smbXsrv_channel_global0 channels[num_channels];
+ } smbXsrv_session_global0;
+
+ typedef union {
+ [case(0)] smbXsrv_session_global0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_session_globalU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ uint32 seqnum;
+ [switch_is(version)] smbXsrv_session_globalU info;
+ } smbXsrv_session_globalB;
+
+ void smbXsrv_session_global_decode(
+ [in] smbXsrv_session_globalB blob
+ );
+
+ /*
+ * The main server code should just work with
+ * 'struct smbXsrv_session' and never use
+ * smbXsrv_session0, smbXsrv_sessionU
+ * and smbXsrv_sessionB directly.
+ *
+ * If we need to change the smbXsrv_session,
+ * we can just rename smbXsrv_session
+ * to smbXsrv_session0 and add a new
+ * smbXsrv_session for version 1
+ * and could implement transparent mapping.
+ */
+
+ typedef struct {
+ [ignore] smbXsrv_session_auth0 *prev;
+ [max_recursion(20000)] smbXsrv_session_auth0 *next;
+ [ignore] smbXsrv_session *session;
+ [ignore] smbXsrv_connection *connection;
+ [ignore] gensec_security *gensec;
+ [ignore] smbXsrv_preauth *preauth;
+ uint8 in_flags;
+ uint8 in_security_mode;
+ NTTIME creation_time;
+ NTTIME idle_time;
+ hyper channel_id;
+ } smbXsrv_session_auth0;
+
+ typedef struct {
+ [ignore] smbXsrv_session_table *table;
+ [ignore] db_record *db_rec;
+ [ignore] smbXsrv_client *client;
+ uint32 local_id;
+ [ref] smbXsrv_session_global0 *global;
+ NTSTATUS status;
+ NTTIME idle_time;
+ hyper nonce_high_random;
+ hyper nonce_high_max;
+ hyper nonce_high;
+ hyper nonce_low;
+ [ignore] smbXsrv_tcon_table *tcon_table;
+ [ignore] uint32 homes_snum;
+ smbXsrv_session_auth0 *pending_auth;
+ } smbXsrv_session;
+
+ typedef union {
+ [case(0)] smbXsrv_session *info0;
+ [default] hyper *dummy;
+ } smbXsrv_sessionU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] smbXsrv_sessionU info;
+ } smbXsrv_sessionB;
+
+ void smbXsrv_session_decode(
+ [in] smbXsrv_sessionB blob
+ );
+
+ /*
+ * smbXsrv_session_close is use in the MSG_SMBXSRV_SESSION_CLOSE
+ * message
+ */
+ typedef struct {
+ uint32 old_session_global_id;
+ hyper old_session_wire_id;
+ NTTIME old_creation_time;
+ hyper new_session_wire_id;
+ } smbXsrv_session_close0;
+
+ typedef union {
+ [case(0)] smbXsrv_session_close0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_session_closeU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] smbXsrv_session_closeU info;
+ } smbXsrv_session_closeB;
+
+ void smbXsrv_session_close_decode(
+ [in] smbXsrv_session_closeB blob
+ );
+
+ /* tree connects */
+
+ typedef struct {
+ [ignore] db_record *db_rec;
+ uint32 tcon_global_id;
+ uint32 tcon_wire_id;
+ server_id server_id;
+ NTTIME creation_time;
+ [charset(UTF8),string] char share_name[];
+ smbXsrv_encrpytion_flags encryption_flags;
+ /*
+ * for SMB1 this is the session that the tcon was opened on
+ */
+ uint32 session_global_id;
+ smbXsrv_signing_flags signing_flags;
+ } smbXsrv_tcon_global0;
+
+ typedef union {
+ [case(0)] smbXsrv_tcon_global0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_tcon_globalU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ uint32 seqnum;
+ [switch_is(version)] smbXsrv_tcon_globalU info;
+ } smbXsrv_tcon_globalB;
+
+ void smbXsrv_tcon_global_decode(
+ [in] smbXsrv_tcon_globalB blob
+ );
+
+ /*
+ * The main server code should just work with
+ * 'struct smbXsrv_tcon' and never use
+ * smbXsrv_tcon0, smbXsrv_tconU
+ * and smbXsrv_tconB directly.
+ *
+ * If we need to change the smbXsrv_tcon,
+ * we can just rename smbXsrv_tcon
+ * to smbXsrv_tcon0 and add a new
+ * smbXsrv_tcon for version 1
+ * and could implement transparent mapping.
+ */
+ typedef struct {
+ [ignore] smbXsrv_tcon_table *table;
+ [ignore] db_record *db_rec;
+ uint32 local_id;
+ [ref] smbXsrv_tcon_global0 *global;
+ NTSTATUS status;
+ NTTIME idle_time;
+ [ignore] connection_struct *compat;
+ } smbXsrv_tcon;
+
+ typedef union {
+ [case(0)] smbXsrv_tcon *info0;
+ [default] hyper *dummy;
+ } smbXsrv_tconU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] smbXsrv_tconU info;
+ } smbXsrv_tconB;
+
+ void smbXsrv_tcon_decode(
+ [in] smbXsrv_tconB blob
+ );
+
+ /* open files */
+
+ typedef [public,bitmap8bit] bitmap {
+ SMBXSRV_OPEN_NEED_REPLAY_CACHE = 0x01,
+ SMBXSRV_OPEN_HAVE_REPLAY_CACHE = 0x02
+ } smbXsrv_open_flags;
+
+ typedef struct {
+ server_id server_id;
+ uint32 open_global_id;
+ hyper open_persistent_id;
+ hyper open_volatile_id;
+ dom_sid open_owner;
+ NTTIME open_time;
+ GUID create_guid;
+ GUID client_guid;
+ GUID app_instance_id;
+ /*
+ * TODO: for durable/resilient/persistent handles we need more
+ * things here. See [MS-SMB2] 3.3.1.10 Per Open
+ *
+ * NOTE: this is still version 0, which is not a stable format!
+ */
+ NTTIME disconnect_time;
+ uint32 durable_timeout_msec;
+ boolean8 durable;
+ DATA_BLOB backend_cookie;
+ uint16 channel_sequence;
+ hyper channel_generation;
+ [flag(NDR_PAHEX)] uint8 lock_sequence_array[64];
+ } smbXsrv_open_global0;
+
+ typedef union {
+ [case(0)] smbXsrv_open_global0 *info0;
+ [default] hyper *dummy;
+ } smbXsrv_open_globalU;
+
+ typedef [public] struct {
+
+ smbXsrv_version_values version;
+ uint32 seqnum;
+ [switch_is(version)] smbXsrv_open_globalU info;
+ } smbXsrv_open_globalB;
+
+ void smbXsrv_open_global_decode(
+ [in] smbXsrv_open_globalB blob
+ );
+
+ /*
+ * The main server code should just work with
+ * 'struct smbXsrv_open' and never use
+ * smbXsrv_open0, smbXsrv_openU
+ * and smbXsrv_openB directly.
+ *
+ * If we need to change the smbXsrv_open,
+ * we can just rename smbXsrv_open
+ * to smbXsrv_open0 and add a new
+ * smbXsrv_open for version 1
+ * and could implement transparent mapping.
+ */
+ typedef struct {
+ [ignore] smbXsrv_open_table *table;
+ uint32 local_id;
+ [ref] smbXsrv_open_global0 *global;
+ NTSTATUS status;
+ NTTIME idle_time;
+ [ignore] files_struct *compat;
+ smbXsrv_open_flags flags;
+ uint32 create_action;
+ hyper request_count;
+ hyper pre_request_count;
+ } smbXsrv_open;
+
+ typedef union {
+ [case(0)] smbXsrv_open *info0;
+ [default] hyper *dummy;
+ } smbXsrv_openU;
+
+ typedef [public] struct {
+ smbXsrv_version_values version;
+ [value(0)] uint32 reserved;
+ [switch_is(version)] smbXsrv_openU info;
+ } smbXsrv_openB;
+
+ void smbXsrv_open_decode(
+ [in] smbXsrv_openB blob
+ );
+
+ const uint32 SMBXSRV_OPEN_REPLAY_CACHE_FIXED_SIZE = 28;
+ typedef [public] struct {
+ GUID holder_req_guid;
+ NTTIME idle_time;
+ uint32 local_id;
+ } smbXsrv_open_replay_cache;
+}
diff --git a/source3/librpc/idl/wscript_build b/source3/librpc/idl/wscript_build
new file mode 100644
index 0000000..f46c498
--- /dev/null
+++ b/source3/librpc/idl/wscript_build
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+import os
+
+topinclude=os.path.join(bld.srcnode.abspath(), 'librpc/idl')
+
+bld.SAMBA_PIDL_LIST('PIDL',
+ '''open_files.idl
+ perfcount.idl secrets.idl
+ smbXsrv.idl
+ leases_db.idl
+ rpcd_witness.idl
+ ''',
+ options='--includedir=%s --header --ndr-parser --client --python' % topinclude,
+ output_dir='../gen_ndr')
+
+bld.SAMBA_PIDL_LIST('PIDL',
+ '''
+ libnetapi.idl
+ rpc_host.idl
+ ''',
+ options='--includedir=%s --header --ndr-parser' % topinclude,
+ output_dir='../gen_ndr',
+ generate_tables=False)
+
+absinclude=os.path.join(bld.srcnode.abspath(), 'bin/default/include')
+
+bld.SAMBA_PIDL_LIST('PIDL',
+ '''
+ libnet_join.idl
+ ''',
+ options='--includedir=%s --includedir=%s --header --ndr-parser' % (topinclude, absinclude),
+ output_dir='../gen_ndr',
+ generate_tables=False)
+
+bld.SAMBA_PIDL_LIST('PIDL',
+ '''
+ ads.idl
+ ''',
+ options='--includedir=%s --includedir=%s --header --ndr-parser' % (topinclude, absinclude),
+ output_dir='../gen_ndr')
diff --git a/source3/librpc/ndr/ndr_ads.c b/source3/librpc/ndr/ndr_ads.c
new file mode 100644
index 0000000..6b6e11a
--- /dev/null
+++ b/source3/librpc/ndr/ndr_ads.c
@@ -0,0 +1,32 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ routines for marshalling/unmarshalling ads structures
+
+ Copyright (C) Guenther Deschner 2020
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_ads.h"
+
+enum ndr_err_code ndr_pull_ads_struct(struct ndr_pull *ndr, ndr_flags_type ndr_flags, struct ads_struct *r)
+{
+ return NDR_ERR_SUCCESS;
+}
+enum ndr_err_code ndr_push_ads_struct(struct ndr_push *ndr, ndr_flags_type ndr_flags, const struct ads_struct *r)
+{
+ return NDR_ERR_SUCCESS;
+}
diff --git a/source3/librpc/rpc/dcerpc.h b/source3/librpc/rpc/dcerpc.h
new file mode 100644
index 0000000..4b76c5c
--- /dev/null
+++ b/source3/librpc/rpc/dcerpc.h
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ DCERPC client side interface structures
+
+ Copyright (C) 2008 Jelmer Vernooij
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* This is a public header file that is installed as part of Samba.
+ * If you remove any functions or change their signature, update
+ * the so version number. */
+
+#ifndef _S3_DCERPC_H__
+#define _S3_DCERPC_H__
+
+#include "librpc/gen_ndr/dcerpc.h"
+#include "../librpc/ndr/libndr.h"
+#include "../librpc/rpc/rpc_common.h"
+
+struct NL_AUTH_MESSAGE;
+struct gensec_security;
+
+/* auth state for all bind types. */
+
+struct pipe_auth_data {
+ enum dcerpc_AuthType auth_type;
+ enum dcerpc_AuthLevel auth_level;
+ uint32_t auth_context_id;
+ bool client_hdr_signing;
+ bool hdr_signing;
+ bool verified_bitmask1;
+
+ struct gensec_security *auth_ctx;
+
+ /* Only the client code uses this for now */
+ DATA_BLOB transport_session_key;
+};
+
+/* The following definitions come from librpc/rpc/dcerpc_helpers.c */
+NTSTATUS dcerpc_push_ncacn_packet(TALLOC_CTX *mem_ctx,
+ enum dcerpc_pkt_type ptype,
+ uint8_t pfc_flags,
+ uint16_t auth_length,
+ uint32_t call_id,
+ union dcerpc_payload *u,
+ DATA_BLOB *blob);
+NTSTATUS dcerpc_push_dcerpc_auth(TALLOC_CTX *mem_ctx,
+ enum dcerpc_AuthType auth_type,
+ enum dcerpc_AuthLevel auth_level,
+ uint8_t auth_pad_length,
+ uint32_t auth_context_id,
+ const DATA_BLOB *credentials,
+ DATA_BLOB *blob);
+NTSTATUS dcerpc_guess_sizes(struct pipe_auth_data *auth,
+ size_t header_len, size_t data_left,
+ size_t max_xmit_frag,
+ size_t *data_to_send, size_t *frag_len,
+ size_t *auth_len, size_t *pad_len);
+NTSTATUS dcerpc_add_auth_footer(struct pipe_auth_data *auth,
+ size_t pad_len, DATA_BLOB *rpc_out);
+NTSTATUS dcerpc_check_auth(struct pipe_auth_data *auth,
+ struct ncacn_packet *pkt,
+ DATA_BLOB *pkt_trailer,
+ uint8_t header_size,
+ DATA_BLOB *raw_pkt);
+
+#endif /* __S3_DCERPC_H__ */
diff --git a/source3/librpc/rpc/dcerpc_helpers.c b/source3/librpc/rpc/dcerpc_helpers.c
new file mode 100644
index 0000000..2b99eb7
--- /dev/null
+++ b/source3/librpc/rpc/dcerpc_helpers.c
@@ -0,0 +1,520 @@
+/*
+ * DCERPC Helper routines
+ * Günther Deschner <gd@samba.org> 2010.
+ * Simo Sorce <idra@samba.org> 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "librpc/rpc/dcerpc.h"
+#include "librpc/rpc/dcerpc_util.h"
+#include "librpc/gen_ndr/ndr_dcerpc.h"
+#include "librpc/crypto/gse.h"
+#include "auth/gensec/gensec.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_PARSE
+
+/**
+* @brief NDR Encodes a ncacn_packet
+*
+* @param mem_ctx The memory context the blob will be allocated on
+* @param ptype The DCERPC packet type
+* @param pfc_flags The DCERPC PFC Flags
+* @param auth_length The length of the trailing auth blob
+* @param call_id The call ID
+* @param u The payload of the packet
+* @param blob [out] The encoded blob if successful
+*
+* @return an NTSTATUS error code
+*/
+NTSTATUS dcerpc_push_ncacn_packet(TALLOC_CTX *mem_ctx,
+ enum dcerpc_pkt_type ptype,
+ uint8_t pfc_flags,
+ uint16_t auth_length,
+ uint32_t call_id,
+ union dcerpc_payload *u,
+ DATA_BLOB *blob)
+{
+ struct ncacn_packet r;
+ enum ndr_err_code ndr_err;
+
+ r.rpc_vers = 5;
+ r.rpc_vers_minor = 0;
+ r.ptype = ptype;
+ r.pfc_flags = pfc_flags;
+ r.drep[0] = DCERPC_DREP_LE;
+ r.drep[1] = 0;
+ r.drep[2] = 0;
+ r.drep[3] = 0;
+ r.auth_length = auth_length;
+ r.call_id = call_id;
+ r.u = *u;
+
+ ndr_err = ndr_push_struct_blob(blob, mem_ctx, &r,
+ (ndr_push_flags_fn_t)ndr_push_ncacn_packet);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ dcerpc_set_frag_length(blob, blob->length);
+
+
+ if (DEBUGLEVEL >= 10) {
+ /* set frag len for print function */
+ r.frag_length = blob->length;
+ NDR_PRINT_DEBUG(ncacn_packet, &r);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+* @brief NDR Encodes a dcerpc_auth structure
+*
+* @param mem_ctx The memory context the blob will be allocated on
+* @param auth_type The DCERPC Authentication Type
+* @param auth_level The DCERPC Authentication Level
+* @param auth_pad_length The padding added to the packet this blob will be
+* appended to.
+* @param auth_context_id The context id
+* @param credentials The authentication credentials blob (signature)
+* @param blob [out] The encoded blob if successful
+*
+* @return a NTSTATUS error code
+*/
+NTSTATUS dcerpc_push_dcerpc_auth(TALLOC_CTX *mem_ctx,
+ enum dcerpc_AuthType auth_type,
+ enum dcerpc_AuthLevel auth_level,
+ uint8_t auth_pad_length,
+ uint32_t auth_context_id,
+ const DATA_BLOB *credentials,
+ DATA_BLOB *blob)
+{
+ struct dcerpc_auth r;
+ enum ndr_err_code ndr_err;
+
+ r.auth_type = auth_type;
+ r.auth_level = auth_level;
+ r.auth_pad_length = auth_pad_length;
+ r.auth_reserved = 0;
+ r.auth_context_id = auth_context_id;
+ r.credentials = *credentials;
+
+ ndr_err = ndr_push_struct_blob(blob, mem_ctx, &r,
+ (ndr_push_flags_fn_t)ndr_push_dcerpc_auth);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(dcerpc_auth, &r);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+* @brief Calculate how much data we can in a packet, including calculating
+* auth token and pad lengths.
+*
+* @param auth The pipe_auth_data structure for this pipe.
+* @param header_len The length of the packet header
+* @param data_left The data left in the send buffer
+* @param max_xmit_frag The max fragment size.
+* @param data_to_send [out] The max data we will send in the pdu
+* @param frag_len [out] The total length of the fragment
+* @param auth_len [out] The length of the auth trailer
+* @param pad_len [out] The padding to be applied
+*
+* @return A NT Error status code.
+*/
+NTSTATUS dcerpc_guess_sizes(struct pipe_auth_data *auth,
+ size_t header_len, size_t data_left,
+ size_t max_xmit_frag,
+ size_t *data_to_send, size_t *frag_len,
+ size_t *auth_len, size_t *pad_len)
+{
+ size_t max_len;
+ size_t mod_len;
+ struct gensec_security *gensec_security;
+
+ /* no auth token cases first */
+ switch (auth->auth_level) {
+ case DCERPC_AUTH_LEVEL_NONE:
+ case DCERPC_AUTH_LEVEL_CONNECT:
+ max_len = max_xmit_frag - header_len;
+ *data_to_send = MIN(max_len, data_left);
+ *pad_len = 0;
+ *auth_len = 0;
+ *frag_len = header_len + *data_to_send;
+ return NT_STATUS_OK;
+
+ case DCERPC_AUTH_LEVEL_PRIVACY:
+ break;
+
+ case DCERPC_AUTH_LEVEL_INTEGRITY:
+ break;
+
+ case DCERPC_AUTH_LEVEL_PACKET:
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+
+ /* Sign/seal case, calculate auth and pad lengths */
+
+ max_len = max_xmit_frag - header_len - DCERPC_AUTH_TRAILER_LENGTH;
+
+ /* Treat the same for all authenticated rpc requests. */
+ switch (auth->auth_type) {
+ case DCERPC_AUTH_TYPE_SPNEGO:
+ case DCERPC_AUTH_TYPE_NTLMSSP:
+ case DCERPC_AUTH_TYPE_KRB5:
+ case DCERPC_AUTH_TYPE_SCHANNEL:
+ gensec_security = auth->auth_ctx;
+ mod_len = (max_len % DCERPC_AUTH_PAD_ALIGNMENT);
+ *auth_len = gensec_sig_size(gensec_security, max_len - mod_len);
+ if (*auth_len == 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ max_len -= *auth_len;
+ mod_len = (max_len % DCERPC_AUTH_PAD_ALIGNMENT);
+ max_len -= mod_len;
+
+ *data_to_send = MIN(max_len, data_left);
+
+ *pad_len = DCERPC_AUTH_PAD_LENGTH(*data_to_send);
+
+ *frag_len = header_len + *data_to_send + *pad_len
+ + DCERPC_AUTH_TRAILER_LENGTH + *auth_len;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Create and add the NTLMSSP sign/seal auth data.
+ ********************************************************************/
+
+static NTSTATUS add_generic_auth_footer(struct gensec_security *gensec_security,
+ enum dcerpc_AuthLevel auth_level,
+ DATA_BLOB *rpc_out)
+{
+ uint16_t data_and_pad_len = rpc_out->length
+ - DCERPC_RESPONSE_LENGTH
+ - DCERPC_AUTH_TRAILER_LENGTH;
+ DATA_BLOB auth_blob;
+ NTSTATUS status;
+
+ if (!gensec_security) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (auth_level) {
+ case DCERPC_AUTH_LEVEL_PRIVACY:
+ /* Data portion is encrypted. */
+ status = gensec_seal_packet(gensec_security,
+ rpc_out->data,
+ rpc_out->data
+ + DCERPC_RESPONSE_LENGTH,
+ data_and_pad_len,
+ rpc_out->data,
+ rpc_out->length,
+ &auth_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+
+ case DCERPC_AUTH_LEVEL_INTEGRITY:
+ case DCERPC_AUTH_LEVEL_PACKET:
+ /* Data is signed. */
+ status = gensec_sign_packet(gensec_security,
+ rpc_out->data,
+ rpc_out->data
+ + DCERPC_RESPONSE_LENGTH,
+ data_and_pad_len,
+ rpc_out->data,
+ rpc_out->length,
+ &auth_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+
+ default:
+ /* Can't happen. */
+ smb_panic("bad auth level");
+ /* Notreached. */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Finally attach the blob. */
+ if (!data_blob_append(NULL, rpc_out,
+ auth_blob.data, auth_blob.length)) {
+ DEBUG(0, ("Failed to add %u bytes auth blob.\n",
+ (unsigned int)auth_blob.length));
+ return NT_STATUS_NO_MEMORY;
+ }
+ data_blob_free(&auth_blob);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Check/unseal the NTLMSSP auth data. (Unseal in place).
+ ********************************************************************/
+
+static NTSTATUS get_generic_auth_footer(struct gensec_security *gensec_security,
+ enum dcerpc_AuthLevel auth_level,
+ DATA_BLOB *data, DATA_BLOB *full_pkt,
+ DATA_BLOB *auth_token)
+{
+ if (gensec_security == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (auth_level) {
+ case DCERPC_AUTH_LEVEL_PRIVACY:
+ /* Data portion is encrypted. */
+ return gensec_unseal_packet(gensec_security,
+ data->data,
+ data->length,
+ full_pkt->data,
+ full_pkt->length,
+ auth_token);
+
+ case DCERPC_AUTH_LEVEL_INTEGRITY:
+ case DCERPC_AUTH_LEVEL_PACKET:
+ /* Data is signed. */
+ return gensec_check_packet(gensec_security,
+ data->data,
+ data->length,
+ full_pkt->data,
+ full_pkt->length,
+ auth_token);
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+}
+
+/**
+* @brief Append an auth footer according to what is the current mechanism
+*
+* @param auth The pipe_auth_data associated with the connection
+* @param pad_len The padding used in the packet
+* @param rpc_out Packet blob up to and including the auth header
+*
+* @return A NTSTATUS error code.
+*/
+NTSTATUS dcerpc_add_auth_footer(struct pipe_auth_data *auth,
+ size_t pad_len, DATA_BLOB *rpc_out)
+{
+ struct gensec_security *gensec_security;
+ const char pad[DCERPC_AUTH_PAD_ALIGNMENT] = { 0, };
+ DATA_BLOB auth_info;
+ DATA_BLOB auth_blob;
+ NTSTATUS status;
+
+ if (auth->auth_type == DCERPC_AUTH_TYPE_NONE) {
+ return NT_STATUS_OK;
+ }
+
+ if (pad_len) {
+ SMB_ASSERT(pad_len <= ARRAY_SIZE(pad));
+
+ /* Copy the sign/seal padding data. */
+ if (!data_blob_append(NULL, rpc_out, pad, pad_len)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* marshall the dcerpc_auth with an actually empty auth_blob.
+ * This is needed because the ntmlssp signature includes the
+ * auth header. We will append the actual blob later. */
+ auth_blob = data_blob_null;
+ status = dcerpc_push_dcerpc_auth(rpc_out->data,
+ auth->auth_type,
+ auth->auth_level,
+ pad_len,
+ auth->auth_context_id,
+ &auth_blob,
+ &auth_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* append the header */
+ if (!data_blob_append(NULL, rpc_out,
+ auth_info.data, auth_info.length)) {
+ DEBUG(0, ("Failed to add %u bytes auth blob.\n",
+ (unsigned int)auth_info.length));
+ return NT_STATUS_NO_MEMORY;
+ }
+ data_blob_free(&auth_info);
+
+ /* Generate any auth sign/seal and add the auth footer. */
+ switch (auth->auth_type) {
+ case DCERPC_AUTH_TYPE_NONE:
+ status = NT_STATUS_OK;
+ break;
+ default:
+ gensec_security = auth->auth_ctx;
+ status = add_generic_auth_footer(gensec_security,
+ auth->auth_level,
+ rpc_out);
+ break;
+ }
+
+ return status;
+}
+
+/**
+* @brief Check authentication for request/response packets
+*
+* @param auth The auth data for the connection
+* @param pkt The actual ncacn_packet
+* @param pkt_trailer [in][out] The stub_and_verifier part of the packet,
+* the auth_trailer and padding will be removed.
+* @param header_size The header size
+* @param raw_pkt The whole raw packet data blob
+*
+* @return A NTSTATUS error code
+*/
+NTSTATUS dcerpc_check_auth(struct pipe_auth_data *auth,
+ struct ncacn_packet *pkt,
+ DATA_BLOB *pkt_trailer,
+ uint8_t header_size,
+ DATA_BLOB *raw_pkt)
+{
+ struct gensec_security *gensec_security;
+ NTSTATUS status;
+ struct dcerpc_auth auth_info;
+ uint32_t auth_length;
+ DATA_BLOB full_pkt;
+ DATA_BLOB data;
+
+ /*
+ * These check should be done in the caller.
+ */
+ SMB_ASSERT(raw_pkt->length == pkt->frag_length);
+ SMB_ASSERT(header_size <= pkt->frag_length);
+ SMB_ASSERT(pkt_trailer->length < pkt->frag_length);
+ SMB_ASSERT((pkt_trailer->length + header_size) <= pkt->frag_length);
+
+ switch (auth->auth_level) {
+ case DCERPC_AUTH_LEVEL_PRIVACY:
+ DEBUG(10, ("Requested Privacy.\n"));
+ break;
+
+ case DCERPC_AUTH_LEVEL_INTEGRITY:
+ DEBUG(10, ("Requested Integrity.\n"));
+ break;
+
+ case DCERPC_AUTH_LEVEL_PACKET:
+ DEBUG(10, ("Requested packet.\n"));
+ break;
+
+ case DCERPC_AUTH_LEVEL_CONNECT:
+ if (pkt->auth_length != 0) {
+ break;
+ }
+ return NT_STATUS_OK;
+
+ case DCERPC_AUTH_LEVEL_NONE:
+ if (pkt->auth_length != 0) {
+ DEBUG(3, ("Got non-zero auth len on non "
+ "authenticated connection!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+
+ default:
+ DEBUG(3, ("Unimplemented Auth Level %d\n",
+ auth->auth_level));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (pkt->auth_length == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_pull_auth_trailer(pkt, pkt, pkt_trailer,
+ &auth_info, &auth_length, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (auth_info.auth_type != auth->auth_type) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (auth_info.auth_level != auth->auth_level) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (auth_info.auth_context_id != auth->auth_context_id) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ pkt_trailer->length -= auth_length;
+ data = data_blob_const(raw_pkt->data + header_size,
+ pkt_trailer->length);
+ full_pkt = data_blob_const(raw_pkt->data, raw_pkt->length);
+ full_pkt.length -= auth_info.credentials.length;
+
+ switch (auth->auth_type) {
+ case DCERPC_AUTH_TYPE_NONE:
+ return NT_STATUS_OK;
+
+ default:
+ DEBUG(10, ("GENSEC auth\n"));
+
+ gensec_security = auth->auth_ctx;
+ status = get_generic_auth_footer(gensec_security,
+ auth->auth_level,
+ &data, &full_pkt,
+ &auth_info.credentials);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ }
+
+ /* TODO: remove later
+ * this is still needed because in the server code the
+ * pkt_trailer actually has a copy of the raw data, and they
+ * are still both used in later calls */
+ if (auth->auth_level == DCERPC_AUTH_LEVEL_PRIVACY) {
+ if (pkt_trailer->length != data.length) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ memcpy(pkt_trailer->data, data.data, data.length);
+ }
+
+ pkt_trailer->length -= auth_info.auth_pad_length;
+ data_blob_free(&auth_info.credentials);
+ return NT_STATUS_OK;
+}
+
diff --git a/source3/librpc/wscript_build b/source3/librpc/wscript_build
new file mode 100644
index 0000000..d228db7
--- /dev/null
+++ b/source3/librpc/wscript_build
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_SUBSYSTEM('NDR_LIBNETAPI',
+ cflags="-D SKIP_NDR_TABLE_libnetapi",
+ source='gen_ndr/ndr_libnetapi.c',
+ public_deps='ndr',
+ allow_warnings=True
+ )
+
+bld.SAMBA3_SUBSYSTEM('NDR_LIBNET_JOIN',
+ source='gen_ndr/ndr_libnet_join.c',
+ public_deps='ndr krb5samba NDR_ODJ NDR_ADS'
+ )
+
+bld.SAMBA3_SUBSYSTEM("NDR_RPC_HOST",
+ source='gen_ndr/ndr_rpc_host.c',
+ public_deps='ndr')
+
+bld.SAMBA3_SUBSYSTEM('NDR_OPEN_FILES',
+ source='gen_ndr/ndr_open_files.c',
+ public_deps='ndr NDR_SERVER_ID NDR_FILE_ID NDR_SECURITY NDR_SMB2_LEASE_STRUCT'
+ )
+
+bld.SAMBA3_SUBSYSTEM('NDR_SMBXSRV',
+ source='gen_ndr/ndr_smbXsrv.c',
+ public_deps='ndr NDR_SERVER_ID NDR_SECURITY NDR_AUTH'
+ )
+
+bld.SAMBA3_SUBSYSTEM('NDR_LEASES_DB',
+ source='gen_ndr/ndr_leases_db.c',
+ public_deps='ndr NDR_SMB2_LEASE_STRUCT NDR_FILE_ID'
+ )
+
+bld.SAMBA3_SUBSYSTEM('NDR_RPCD_WITNESS',
+ source='gen_ndr/ndr_rpcd_witness.c',
+ public_deps='ndr NDR_SERVER_ID NDR_SECURITY NDR_WITNESS'
+ )
+
+bld.SAMBA3_SUBSYSTEM('NDR_SECRETS',
+ source='gen_ndr/ndr_secrets.c',
+ public_deps='ndr NDR_SAMR NDR_LSA NDR_NETLOGON NDR_SECURITY'
+ )
+
+bld.SAMBA3_SUBSYSTEM('NDR_PERFCOUNT',
+ source='gen_ndr/ndr_perfcount.c',
+ public_deps='ndr'
+ )
+
+bld.SAMBA3_SUBSYSTEM('NDR_ADS',
+ source='gen_ndr/ndr_ads.c ndr/ndr_ads.c',
+ public_deps='ndr NDR_NBT'
+ )
+
diff --git a/source3/libsmb/ABI/smbclient-0.1.0.sigs b/source3/libsmb/ABI/smbclient-0.1.0.sigs
new file mode 100644
index 0000000..3dea59c
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.1.0.sigs
@@ -0,0 +1,172 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setUser: void (SMBCCTX *, char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.2.0.sigs b/source3/libsmb/ABI/smbclient-0.2.0.sigs
new file mode 100644
index 0000000..aa85859
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.2.0.sigs
@@ -0,0 +1,172 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.2.1.sigs b/source3/libsmb/ABI/smbclient-0.2.1.sigs
new file mode 100644
index 0000000..1b7f6ce
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.2.1.sigs
@@ -0,0 +1,174 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.2.2.sigs b/source3/libsmb/ABI/smbclient-0.2.2.sigs
new file mode 100644
index 0000000..fdafdd0
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.2.2.sigs
@@ -0,0 +1,176 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.2.3.sigs b/source3/libsmb/ABI/smbclient-0.2.3.sigs
new file mode 100644
index 0000000..cda537c
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.2.3.sigs
@@ -0,0 +1,179 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.3.0.sigs b/source3/libsmb/ABI/smbclient-0.3.0.sigs
new file mode 100644
index 0000000..736f32a
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.3.0.sigs
@@ -0,0 +1,179 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.3.1.sigs b/source3/libsmb/ABI/smbclient-0.3.1.sigs
new file mode 100644
index 0000000..d41274d
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.3.1.sigs
@@ -0,0 +1,180 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.3.2.sigs b/source3/libsmb/ABI/smbclient-0.3.2.sigs
new file mode 100644
index 0000000..2f089d8
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.3.2.sigs
@@ -0,0 +1,181 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setConfiguration: int (SMBCCTX *, const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.3.3.sigs b/source3/libsmb/ABI/smbclient-0.3.3.sigs
new file mode 100644
index 0000000..833d0df
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.3.3.sigs
@@ -0,0 +1,184 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus: smbc_readdirplus_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: char *(SMBCCTX *)
+smbc_getWorkgroup: char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_readdirplus: const struct libsmb_file_info *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setConfiguration: int (SMBCCTX *, const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionReaddirPlus: void (SMBCCTX *, smbc_readdirplus_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.4.0.sigs b/source3/libsmb/ABI/smbclient-0.4.0.sigs
new file mode 100644
index 0000000..170cb52
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.4.0.sigs
@@ -0,0 +1,184 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus: smbc_readdirplus_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: const char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: const char *(SMBCCTX *)
+smbc_getWorkgroup: const char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_readdirplus: const struct libsmb_file_info *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setConfiguration: int (SMBCCTX *, const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionReaddirPlus: void (SMBCCTX *, smbc_readdirplus_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, const char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, const char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.5.0.sigs b/source3/libsmb/ABI/smbclient-0.5.0.sigs
new file mode 100644
index 0000000..b424597
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.5.0.sigs
@@ -0,0 +1,185 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus: smbc_readdirplus_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: const char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: const char *(SMBCCTX *)
+smbc_getWorkgroup: const char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_readdirplus: const struct libsmb_file_info *(unsigned int)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setConfiguration: int (SMBCCTX *, const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionReaddirPlus: void (SMBCCTX *, smbc_readdirplus_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, const char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionProtocols: smbc_bool (SMBCCTX *, const char *, const char *)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, const char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.6.0.sigs b/source3/libsmb/ABI/smbclient-0.6.0.sigs
new file mode 100644
index 0000000..ee758e2
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.6.0.sigs
@@ -0,0 +1,188 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus: smbc_readdirplus_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus2: smbc_readdirplus2_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: const char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: const char *(SMBCCTX *)
+smbc_getWorkgroup: const char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_readdirplus: const struct libsmb_file_info *(unsigned int)
+smbc_readdirplus2: const struct libsmb_file_info *(unsigned int, struct stat *)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setConfiguration: int (SMBCCTX *, const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionReaddirPlus: void (SMBCCTX *, smbc_readdirplus_fn)
+smbc_setFunctionReaddirPlus2: void (SMBCCTX *, smbc_readdirplus2_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, const char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionProtocols: smbc_bool (SMBCCTX *, const char *, const char *)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, const char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.7.0.sigs b/source3/libsmb/ABI/smbclient-0.7.0.sigs
new file mode 100644
index 0000000..ee758e2
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.7.0.sigs
@@ -0,0 +1,188 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus: smbc_readdirplus_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus2: smbc_readdirplus2_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: const char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: const char *(SMBCCTX *)
+smbc_getWorkgroup: const char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_readdirplus: const struct libsmb_file_info *(unsigned int)
+smbc_readdirplus2: const struct libsmb_file_info *(unsigned int, struct stat *)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setConfiguration: int (SMBCCTX *, const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionReaddirPlus: void (SMBCCTX *, smbc_readdirplus_fn)
+smbc_setFunctionReaddirPlus2: void (SMBCCTX *, smbc_readdirplus2_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, const char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionProtocols: smbc_bool (SMBCCTX *, const char *, const char *)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, const char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/ABI/smbclient-0.8.0.sigs b/source3/libsmb/ABI/smbclient-0.8.0.sigs
new file mode 100644
index 0000000..fc7e79f
--- /dev/null
+++ b/source3/libsmb/ABI/smbclient-0.8.0.sigs
@@ -0,0 +1,190 @@
+smbc_chmod: int (const char *, mode_t)
+smbc_close: int (int)
+smbc_closedir: int (int)
+smbc_creat: int (const char *, mode_t)
+smbc_fgetxattr: int (int, const char *, const void *, size_t)
+smbc_flistxattr: int (int, char *, size_t)
+smbc_free_context: int (SMBCCTX *, int)
+smbc_fremovexattr: int (int, const char *)
+smbc_fsetxattr: int (int, const char *, const void *, size_t, int)
+smbc_fstat: int (int, struct stat *)
+smbc_fstatvfs: int (int, struct statvfs *)
+smbc_ftruncate: int (int, off_t)
+smbc_getDebug: int (SMBCCTX *)
+smbc_getFunctionAddCachedServer: smbc_add_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionAuthData: smbc_get_auth_data_fn (SMBCCTX *)
+smbc_getFunctionAuthDataWithContext: smbc_get_auth_data_with_context_fn (SMBCCTX *)
+smbc_getFunctionCheckServer: smbc_check_server_fn (SMBCCTX *)
+smbc_getFunctionChmod: smbc_chmod_fn (SMBCCTX *)
+smbc_getFunctionClose: smbc_close_fn (SMBCCTX *)
+smbc_getFunctionClosedir: smbc_closedir_fn (SMBCCTX *)
+smbc_getFunctionCreat: smbc_creat_fn (SMBCCTX *)
+smbc_getFunctionFstat: smbc_fstat_fn (SMBCCTX *)
+smbc_getFunctionFstatVFS: smbc_fstatvfs_fn (SMBCCTX *)
+smbc_getFunctionFstatdir: smbc_fstatdir_fn (SMBCCTX *)
+smbc_getFunctionFtruncate: smbc_ftruncate_fn (SMBCCTX *)
+smbc_getFunctionGetCachedServer: smbc_get_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionGetdents: smbc_getdents_fn (SMBCCTX *)
+smbc_getFunctionGetxattr: smbc_getxattr_fn (SMBCCTX *)
+smbc_getFunctionListPrintJobs: smbc_list_print_jobs_fn (SMBCCTX *)
+smbc_getFunctionListxattr: smbc_listxattr_fn (SMBCCTX *)
+smbc_getFunctionLseek: smbc_lseek_fn (SMBCCTX *)
+smbc_getFunctionLseekdir: smbc_lseekdir_fn (SMBCCTX *)
+smbc_getFunctionMkdir: smbc_mkdir_fn (SMBCCTX *)
+smbc_getFunctionNotify: smbc_notify_fn (SMBCCTX *)
+smbc_getFunctionOpen: smbc_open_fn (SMBCCTX *)
+smbc_getFunctionOpenPrintJob: smbc_open_print_job_fn (SMBCCTX *)
+smbc_getFunctionOpendir: smbc_opendir_fn (SMBCCTX *)
+smbc_getFunctionPrintFile: smbc_print_file_fn (SMBCCTX *)
+smbc_getFunctionPurgeCachedServers: smbc_purge_cached_fn (SMBCCTX *)
+smbc_getFunctionRead: smbc_read_fn (SMBCCTX *)
+smbc_getFunctionReaddir: smbc_readdir_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus: smbc_readdirplus_fn (SMBCCTX *)
+smbc_getFunctionReaddirPlus2: smbc_readdirplus2_fn (SMBCCTX *)
+smbc_getFunctionRemoveCachedServer: smbc_remove_cached_srv_fn (SMBCCTX *)
+smbc_getFunctionRemoveUnusedServer: smbc_remove_unused_server_fn (SMBCCTX *)
+smbc_getFunctionRemovexattr: smbc_removexattr_fn (SMBCCTX *)
+smbc_getFunctionRename: smbc_rename_fn (SMBCCTX *)
+smbc_getFunctionRmdir: smbc_rmdir_fn (SMBCCTX *)
+smbc_getFunctionSetxattr: smbc_setxattr_fn (SMBCCTX *)
+smbc_getFunctionSplice: smbc_splice_fn (SMBCCTX *)
+smbc_getFunctionStat: smbc_stat_fn (SMBCCTX *)
+smbc_getFunctionStatVFS: smbc_statvfs_fn (SMBCCTX *)
+smbc_getFunctionTelldir: smbc_telldir_fn (SMBCCTX *)
+smbc_getFunctionUnlink: smbc_unlink_fn (SMBCCTX *)
+smbc_getFunctionUnlinkPrintJob: smbc_unlink_print_job_fn (SMBCCTX *)
+smbc_getFunctionUtimes: smbc_utimes_fn (SMBCCTX *)
+smbc_getFunctionWrite: smbc_write_fn (SMBCCTX *)
+smbc_getNetbiosName: const char *(SMBCCTX *)
+smbc_getOptionBrowseMaxLmbCount: int (SMBCCTX *)
+smbc_getOptionCaseSensitive: smbc_bool (SMBCCTX *)
+smbc_getOptionDebugToStderr: smbc_bool (SMBCCTX *)
+smbc_getOptionFallbackAfterKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionFullTimeNames: smbc_bool (SMBCCTX *)
+smbc_getOptionNoAutoAnonymousLogin: smbc_bool (SMBCCTX *)
+smbc_getOptionOneSharePerServer: smbc_bool (SMBCCTX *)
+smbc_getOptionOpenShareMode: smbc_share_mode (SMBCCTX *)
+smbc_getOptionPosixExtensions: smbc_bool (SMBCCTX *)
+smbc_getOptionSmbEncryptionLevel: smbc_smb_encrypt_level (SMBCCTX *)
+smbc_getOptionUrlEncodeReaddirEntries: smbc_bool (SMBCCTX *)
+smbc_getOptionUseCCache: smbc_bool (SMBCCTX *)
+smbc_getOptionUseKerberos: smbc_bool (SMBCCTX *)
+smbc_getOptionUseNTHash: smbc_bool (SMBCCTX *)
+smbc_getOptionUserData: void *(SMBCCTX *)
+smbc_getPort: uint16_t (SMBCCTX *)
+smbc_getServerCacheData: struct smbc_server_cache *(SMBCCTX *)
+smbc_getTimeout: int (SMBCCTX *)
+smbc_getUser: const char *(SMBCCTX *)
+smbc_getWorkgroup: const char *(SMBCCTX *)
+smbc_getdents: int (unsigned int, struct smbc_dirent *, int)
+smbc_getxattr: int (const char *, const char *, const void *, size_t)
+smbc_init: int (smbc_get_auth_data_fn, int)
+smbc_init_context: SMBCCTX *(SMBCCTX *)
+smbc_lgetxattr: int (const char *, const char *, const void *, size_t)
+smbc_list_print_jobs: int (const char *, smbc_list_print_job_fn)
+smbc_listxattr: int (const char *, char *, size_t)
+smbc_llistxattr: int (const char *, char *, size_t)
+smbc_lremovexattr: int (const char *, const char *)
+smbc_lseek: off_t (int, off_t, int)
+smbc_lseekdir: int (int, off_t)
+smbc_lsetxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_mkdir: int (const char *, mode_t)
+smbc_new_context: SMBCCTX *(void)
+smbc_notify: int (int, smbc_bool, uint32_t, unsigned int, smbc_notify_callback_fn, void *)
+smbc_open: int (const char *, int, mode_t)
+smbc_open_print_job: int (const char *)
+smbc_opendir: int (const char *)
+smbc_option_get: void *(SMBCCTX *, char *)
+smbc_option_set: void (SMBCCTX *, char *, ...)
+smbc_print_file: int (const char *, const char *)
+smbc_read: ssize_t (int, void *, size_t)
+smbc_readdir: struct smbc_dirent *(unsigned int)
+smbc_readdirplus: const struct libsmb_file_info *(unsigned int)
+smbc_readdirplus2: const struct libsmb_file_info *(unsigned int, struct stat *)
+smbc_removexattr: int (const char *, const char *)
+smbc_rename: int (const char *, const char *)
+smbc_rmdir: int (const char *)
+smbc_setConfiguration: int (SMBCCTX *, const char *)
+smbc_setDebug: void (SMBCCTX *, int)
+smbc_setFunctionAddCachedServer: void (SMBCCTX *, smbc_add_cached_srv_fn)
+smbc_setFunctionAuthData: void (SMBCCTX *, smbc_get_auth_data_fn)
+smbc_setFunctionAuthDataWithContext: void (SMBCCTX *, smbc_get_auth_data_with_context_fn)
+smbc_setFunctionCheckServer: void (SMBCCTX *, smbc_check_server_fn)
+smbc_setFunctionChmod: void (SMBCCTX *, smbc_chmod_fn)
+smbc_setFunctionClose: void (SMBCCTX *, smbc_close_fn)
+smbc_setFunctionClosedir: void (SMBCCTX *, smbc_closedir_fn)
+smbc_setFunctionCreat: void (SMBCCTX *, smbc_creat_fn)
+smbc_setFunctionFstat: void (SMBCCTX *, smbc_fstat_fn)
+smbc_setFunctionFstatVFS: void (SMBCCTX *, smbc_fstatvfs_fn)
+smbc_setFunctionFstatdir: void (SMBCCTX *, smbc_fstatdir_fn)
+smbc_setFunctionFtruncate: void (SMBCCTX *, smbc_ftruncate_fn)
+smbc_setFunctionGetCachedServer: void (SMBCCTX *, smbc_get_cached_srv_fn)
+smbc_setFunctionGetdents: void (SMBCCTX *, smbc_getdents_fn)
+smbc_setFunctionGetxattr: void (SMBCCTX *, smbc_getxattr_fn)
+smbc_setFunctionListPrintJobs: void (SMBCCTX *, smbc_list_print_jobs_fn)
+smbc_setFunctionListxattr: void (SMBCCTX *, smbc_listxattr_fn)
+smbc_setFunctionLseek: void (SMBCCTX *, smbc_lseek_fn)
+smbc_setFunctionLseekdir: void (SMBCCTX *, smbc_lseekdir_fn)
+smbc_setFunctionMkdir: void (SMBCCTX *, smbc_mkdir_fn)
+smbc_setFunctionNotify: void (SMBCCTX *, smbc_notify_fn)
+smbc_setFunctionOpen: void (SMBCCTX *, smbc_open_fn)
+smbc_setFunctionOpenPrintJob: void (SMBCCTX *, smbc_open_print_job_fn)
+smbc_setFunctionOpendir: void (SMBCCTX *, smbc_opendir_fn)
+smbc_setFunctionPrintFile: void (SMBCCTX *, smbc_print_file_fn)
+smbc_setFunctionPurgeCachedServers: void (SMBCCTX *, smbc_purge_cached_fn)
+smbc_setFunctionRead: void (SMBCCTX *, smbc_read_fn)
+smbc_setFunctionReaddir: void (SMBCCTX *, smbc_readdir_fn)
+smbc_setFunctionReaddirPlus: void (SMBCCTX *, smbc_readdirplus_fn)
+smbc_setFunctionReaddirPlus2: void (SMBCCTX *, smbc_readdirplus2_fn)
+smbc_setFunctionRemoveCachedServer: void (SMBCCTX *, smbc_remove_cached_srv_fn)
+smbc_setFunctionRemoveUnusedServer: void (SMBCCTX *, smbc_remove_unused_server_fn)
+smbc_setFunctionRemovexattr: void (SMBCCTX *, smbc_removexattr_fn)
+smbc_setFunctionRename: void (SMBCCTX *, smbc_rename_fn)
+smbc_setFunctionRmdir: void (SMBCCTX *, smbc_rmdir_fn)
+smbc_setFunctionSetxattr: void (SMBCCTX *, smbc_setxattr_fn)
+smbc_setFunctionSplice: void (SMBCCTX *, smbc_splice_fn)
+smbc_setFunctionStat: void (SMBCCTX *, smbc_stat_fn)
+smbc_setFunctionStatVFS: void (SMBCCTX *, smbc_statvfs_fn)
+smbc_setFunctionTelldir: void (SMBCCTX *, smbc_telldir_fn)
+smbc_setFunctionUnlink: void (SMBCCTX *, smbc_unlink_fn)
+smbc_setFunctionUnlinkPrintJob: void (SMBCCTX *, smbc_unlink_print_job_fn)
+smbc_setFunctionUtimes: void (SMBCCTX *, smbc_utimes_fn)
+smbc_setFunctionWrite: void (SMBCCTX *, smbc_write_fn)
+smbc_setLogCallback: void (SMBCCTX *, void *, smbc_debug_callback_fn)
+smbc_setNetbiosName: void (SMBCCTX *, const char *)
+smbc_setOptionBrowseMaxLmbCount: void (SMBCCTX *, int)
+smbc_setOptionCaseSensitive: void (SMBCCTX *, smbc_bool)
+smbc_setOptionDebugToStderr: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFallbackAfterKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionFullTimeNames: void (SMBCCTX *, smbc_bool)
+smbc_setOptionNoAutoAnonymousLogin: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOneSharePerServer: void (SMBCCTX *, smbc_bool)
+smbc_setOptionOpenShareMode: void (SMBCCTX *, smbc_share_mode)
+smbc_setOptionPosixExtensions: void (SMBCCTX *, smbc_bool)
+smbc_setOptionProtocols: smbc_bool (SMBCCTX *, const char *, const char *)
+smbc_setOptionSmbEncryptionLevel: void (SMBCCTX *, smbc_smb_encrypt_level)
+smbc_setOptionUrlEncodeReaddirEntries: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseCCache: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseKerberos: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUseNTHash: void (SMBCCTX *, smbc_bool)
+smbc_setOptionUserData: void (SMBCCTX *, void *)
+smbc_setPort: void (SMBCCTX *, uint16_t)
+smbc_setServerCacheData: void (SMBCCTX *, struct smbc_server_cache *)
+smbc_setTimeout: void (SMBCCTX *, int)
+smbc_setUser: void (SMBCCTX *, const char *)
+smbc_setWorkgroup: void (SMBCCTX *, const char *)
+smbc_set_context: SMBCCTX *(SMBCCTX *)
+smbc_set_credentials: void (const char *, const char *, const char *, smbc_bool, const char *)
+smbc_set_credentials_with_fallback: void (SMBCCTX *, const char *, const char *, const char *)
+smbc_setxattr: int (const char *, const char *, const void *, size_t, int)
+smbc_stat: int (const char *, struct stat *)
+smbc_statvfs: int (char *, struct statvfs *)
+smbc_telldir: off_t (int)
+smbc_unlink: int (const char *)
+smbc_unlink_print_job: int (const char *, int)
+smbc_urldecode: int (char *, char *, size_t)
+smbc_urlencode: int (char *, char *, int)
+smbc_utime: int (const char *, struct utimbuf *)
+smbc_utimes: int (const char *, struct timeval *)
+smbc_version: const char *(void)
+smbc_write: ssize_t (int, const void *, size_t)
diff --git a/source3/libsmb/async_smb.c b/source3/libsmb/async_smb.c
new file mode 100644
index 0000000..38e0bb9
--- /dev/null
+++ b/source3/libsmb/async_smb.c
@@ -0,0 +1,269 @@
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for async SMB client requests
+ Copyright (C) Volker Lendecke 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+struct cli_smb_req_state {
+ struct cli_state *cli;
+ uint8_t smb_command;
+ struct tevent_req *req;
+ struct cli_smb_req_state **ptr;
+};
+
+static int cli_smb_req_state_destructor(struct cli_smb_req_state *state)
+{
+ talloc_set_destructor(state->ptr, NULL);
+ talloc_free(state->ptr);
+ return 0;
+}
+
+static int cli_smb_req_state_ptr_destructor(struct cli_smb_req_state **ptr)
+{
+ struct cli_smb_req_state *state = *ptr;
+ void *parent = talloc_parent(state);
+
+ talloc_set_destructor(state, NULL);
+
+ talloc_reparent(state, parent, state->req);
+ talloc_free(state);
+ return 0;
+}
+
+struct tevent_req *cli_smb_req_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint8_t smb_command,
+ uint8_t additional_flags,
+ uint16_t additional_flags2,
+ uint8_t wct, uint16_t *vwv,
+ int iov_count,
+ struct iovec *bytes_iov)
+{
+ struct cli_smb_req_state *state;
+ uint8_t clear_flags = 0;
+ uint16_t clear_flags2 = 0;
+
+ state = talloc_zero(mem_ctx, struct cli_smb_req_state);
+ if (state == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->smb_command = smb_command;
+ state->ptr = talloc(state, struct cli_smb_req_state *);
+ if (state->ptr == NULL) {
+ talloc_free(state);
+ return NULL;
+ }
+ *state->ptr = state;
+
+ state->req = smb1cli_req_create(state, ev, cli->conn, smb_command,
+ additional_flags, clear_flags,
+ additional_flags2, clear_flags2,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ wct, vwv, iov_count, bytes_iov);
+ if (state->req == NULL) {
+ talloc_free(state);
+ return NULL;
+ }
+
+ talloc_reparent(state, state->req, state->ptr);
+ talloc_set_destructor(state, cli_smb_req_state_destructor);
+ talloc_set_destructor(state->ptr, cli_smb_req_state_ptr_destructor);
+
+ return state->req;
+}
+
+struct tevent_req *cli_smb_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint8_t smb_command,
+ uint8_t additional_flags,
+ uint16_t additional_flags2,
+ uint8_t wct, uint16_t *vwv,
+ uint32_t num_bytes,
+ const uint8_t *bytes)
+{
+ struct cli_smb_req_state *state;
+ uint8_t clear_flags = 0;
+ uint16_t clear_flags2 = 0;
+
+ state = talloc_zero(mem_ctx, struct cli_smb_req_state);
+ if (state == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->smb_command = smb_command;
+ state->ptr = talloc(state, struct cli_smb_req_state *);
+ if (state->ptr == NULL) {
+ talloc_free(state);
+ return NULL;
+ }
+ *state->ptr = state;
+
+ state->req = smb1cli_req_send(state, ev, cli->conn, smb_command,
+ additional_flags, clear_flags,
+ additional_flags2, clear_flags2,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ wct, vwv, num_bytes, bytes);
+ if (state->req == NULL) {
+ talloc_free(state);
+ return NULL;
+ }
+
+ talloc_reparent(state, state->req, state->ptr);
+ talloc_set_destructor(state, cli_smb_req_state_destructor);
+ talloc_set_destructor(state->ptr, cli_smb_req_state_ptr_destructor);
+
+ return state->req;
+}
+
+NTSTATUS cli_smb_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx, uint8_t **pinbuf,
+ uint8_t min_wct, uint8_t *pwct, uint16_t **pvwv,
+ uint32_t *pnum_bytes, uint8_t **pbytes)
+{
+ NTSTATUS status;
+ void *parent = talloc_parent(req);
+ struct cli_smb_req_state *state =
+ talloc_get_type(parent,
+ struct cli_smb_req_state);
+ struct iovec *recv_iov = NULL;
+ uint8_t wct = 0;
+ uint16_t *vwv = NULL;
+ uint32_t num_bytes;
+ uint8_t *bytes = NULL;
+ uint8_t *inbuf;
+ bool is_expected = false;
+ bool map_dos_errors = true;
+
+ if (pinbuf != NULL) {
+ *pinbuf = NULL;
+ }
+ if (pwct != NULL) {
+ *pwct = 0;
+ }
+ if (pvwv != NULL) {
+ *pvwv = NULL;
+ }
+ if (pnum_bytes != NULL) {
+ *pnum_bytes = 0;
+ }
+ if (pbytes != NULL) {
+ *pbytes = NULL;
+ }
+
+ status = smb1cli_req_recv(req, req,
+ &recv_iov,
+ NULL, /* phdr */
+ &wct,
+ &vwv,
+ NULL, /* pvwv_offset */
+ &num_bytes,
+ &bytes,
+ NULL, /* pbytes_offset */
+ &inbuf,
+ NULL, 0); /* expected */
+
+ if (state) {
+ if ((state->smb_command == SMBsesssetupX) &&
+ NT_STATUS_EQUAL(status,
+ NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ /*
+ * NT_STATUS_MORE_PROCESSING_REQUIRED is a
+ * valid return code for session setup
+ */
+ is_expected = true;
+ }
+
+ if ((state->smb_command == SMBntcreateX) &&
+ NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ min_wct = 0;
+ }
+
+ map_dos_errors = state->cli->map_dos_errors;
+ state->cli->raw_status = status;
+ talloc_free(state->ptr);
+ state = NULL;
+ }
+
+ if (NT_STATUS_IS_DOS(status) && map_dos_errors) {
+ uint8_t eclass = NT_STATUS_DOS_CLASS(status);
+ uint16_t ecode = NT_STATUS_DOS_CODE(status);
+ /*
+ * TODO: is it really a good idea to do a mapping here?
+ *
+ * The old cli_pull_error() also does it, so I do not change
+ * the behavior yet.
+ */
+ status = dos_to_ntstatus(eclass, ecode);
+ }
+
+ if (!NT_STATUS_IS_ERR(status)) {
+ is_expected = true;
+ }
+
+ if (!is_expected) {
+ TALLOC_FREE(recv_iov);
+ return status;
+ }
+
+ if (wct < min_wct) {
+ TALLOC_FREE(recv_iov);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ if (pwct != NULL) {
+ *pwct = wct;
+ }
+ if (pvwv != NULL) {
+ *pvwv = vwv;
+ }
+ if (pnum_bytes != NULL) {
+ *pnum_bytes = num_bytes;
+ }
+ if (pbytes != NULL) {
+ *pbytes = bytes;
+ }
+
+ if (pinbuf != NULL && mem_ctx != NULL) {
+ if (talloc_reference_count(inbuf) == 0) {
+ *pinbuf = talloc_move(mem_ctx, &inbuf);
+ TALLOC_FREE(recv_iov);
+ } else {
+ *pinbuf = inbuf;
+ }
+ } else if (mem_ctx != NULL) {
+ if (talloc_reference_count(inbuf) == 0) {
+ (void)talloc_move(mem_ctx, &inbuf);
+ TALLOC_FREE(recv_iov);
+ }
+ }
+
+ return status;
+}
diff --git a/source3/libsmb/auth_generic.c b/source3/libsmb/auth_generic.c
new file mode 100644
index 0000000..e5120a0
--- /dev/null
+++ b/source3/libsmb/auth_generic.c
@@ -0,0 +1,229 @@
+/*
+ NLTMSSP wrappers
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett 2001-2003,2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "auth/ntlmssp/ntlmssp.h"
+#include "auth_generic.h"
+#include "auth/gensec/gensec.h"
+#include "auth/credentials/credentials.h"
+#include "librpc/rpc/dcerpc.h"
+#include "lib/param/param.h"
+#include "librpc/crypto/gse.h"
+
+NTSTATUS auth_generic_set_username(struct auth_generic_state *ans,
+ const char *user)
+{
+ cli_credentials_set_username(ans->credentials, user, CRED_SPECIFIED);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_set_domain(struct auth_generic_state *ans,
+ const char *domain)
+{
+ cli_credentials_set_domain(ans->credentials, domain, CRED_SPECIFIED);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_set_password(struct auth_generic_state *ans,
+ const char *password)
+{
+ cli_credentials_set_password(ans->credentials, password, CRED_SPECIFIED);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_set_creds(struct auth_generic_state *ans,
+ struct cli_credentials *creds)
+{
+ talloc_unlink(ans->credentials, creds);
+ ans->credentials = creds;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_client_prepare(TALLOC_CTX *mem_ctx, struct auth_generic_state **auth_generic_state)
+{
+ struct auth_generic_state *ans;
+ NTSTATUS nt_status;
+ size_t idx = 0;
+ struct gensec_settings *gensec_settings;
+ const struct gensec_security_ops **backends = NULL;
+ struct loadparm_context *lp_ctx;
+ bool ok;
+
+ ans = talloc_zero(mem_ctx, struct auth_generic_state);
+ if (!ans) {
+ DEBUG(0,("auth_generic_start: talloc failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ lp_ctx = loadparm_init_s3(ans, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(10, ("loadparm_init_s3 failed\n"));
+ TALLOC_FREE(ans);
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+
+ gensec_settings = lpcfg_gensec_settings(ans, lp_ctx);
+ if (lp_ctx == NULL) {
+ DEBUG(10, ("lpcfg_gensec_settings failed\n"));
+ TALLOC_FREE(ans);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ backends = talloc_zero_array(gensec_settings,
+ const struct gensec_security_ops *, 7);
+ if (backends == NULL) {
+ TALLOC_FREE(ans);
+ return NT_STATUS_NO_MEMORY;
+ }
+ gensec_settings->backends = backends;
+
+ gensec_init();
+
+ /* These need to be in priority order, krb5 before NTLMSSP */
+#if defined(HAVE_KRB5)
+ backends[idx++] = &gensec_gse_krb5_security_ops;
+#endif
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_NTLMSSP);
+ backends[idx++] = gensec_security_by_name(NULL, "ntlmssp_resume_ccache");
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_SPNEGO);
+ backends[idx++] = gensec_security_by_auth_type(NULL, DCERPC_AUTH_TYPE_SCHANNEL);
+ backends[idx++] = gensec_security_by_auth_type(NULL, DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM);
+
+ nt_status = gensec_client_start(ans, &ans->gensec_security, gensec_settings);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(ans);
+ return nt_status;
+ }
+
+ ans->credentials = cli_credentials_init(ans);
+ if (!ans->credentials) {
+ TALLOC_FREE(ans);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = cli_credentials_guess(ans->credentials, lp_ctx);
+ if (!ok) {
+ TALLOC_FREE(ans);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ talloc_unlink(ans, lp_ctx);
+ talloc_unlink(ans, gensec_settings);
+
+ *auth_generic_state = ans;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_client_start(struct auth_generic_state *ans, const char *oid)
+{
+ NTSTATUS status;
+
+ /* Transfer the credentials to gensec */
+ status = gensec_set_credentials(ans->gensec_security, ans->credentials);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set GENSEC credentials: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ talloc_unlink(ans, ans->credentials);
+ ans->credentials = NULL;
+
+ status = gensec_start_mech_by_oid(ans->gensec_security,
+ oid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_client_start_by_name(struct auth_generic_state *ans,
+ const char *name)
+{
+ NTSTATUS status;
+
+ /* Transfer the credentials to gensec */
+ status = gensec_set_credentials(ans->gensec_security, ans->credentials);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set GENSEC credentials: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ talloc_unlink(ans, ans->credentials);
+ ans->credentials = NULL;
+
+ status = gensec_start_mech_by_name(ans->gensec_security, name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_client_start_by_authtype(struct auth_generic_state *ans,
+ uint8_t auth_type,
+ uint8_t auth_level)
+{
+ NTSTATUS status;
+
+ /* Transfer the credentials to gensec */
+ status = gensec_set_credentials(ans->gensec_security, ans->credentials);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set GENSEC credentials: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ talloc_unlink(ans, ans->credentials);
+ ans->credentials = NULL;
+
+ status = gensec_start_mech_by_authtype(ans->gensec_security,
+ auth_type, auth_level);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS auth_generic_client_start_by_sasl(struct auth_generic_state *ans,
+ const char **sasl_list)
+{
+ NTSTATUS status;
+
+ /* Transfer the credentials to gensec */
+ status = gensec_set_credentials(ans->gensec_security, ans->credentials);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set GENSEC credentials: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ talloc_unlink(ans, ans->credentials);
+ ans->credentials = NULL;
+
+ status = gensec_start_mech_by_sasl_list(ans->gensec_security, sasl_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/cli_smb2_fnum.c b/source3/libsmb/cli_smb2_fnum.c
new file mode 100644
index 0000000..08d95cf
--- /dev/null
+++ b/source3/libsmb/cli_smb2_fnum.c
@@ -0,0 +1,5040 @@
+/*
+ Unix SMB/CIFS implementation.
+ smb2 lib
+ Copyright (C) Jeremy Allison 2013
+ Copyright (C) Volker Lendecke 2013
+ Copyright (C) Stefan Metzmacher 2013
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ This code is a thin wrapper around the existing
+ cli_smb2_XXXX() functions in libcli/smb/smb2cli_XXXXX.c,
+ but allows the handles to be mapped to uint16_t fnums,
+ which are easier for smbclient to use.
+*/
+
+#include "includes.h"
+#include "client.h"
+#include "async_smb.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "cli_smb2_fnum.h"
+#include "trans2.h"
+#include "clirap.h"
+#include "../libcli/smb/smb2_create_blob.h"
+#include "libsmb/proto.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "lib/util_ea.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "ntioctl.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_smb3posix.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/util/idtree.h"
+
+struct smb2_hnd {
+ uint64_t fid_persistent;
+ uint64_t fid_volatile;
+};
+
+/*
+ * Handle mapping code.
+ */
+
+/***************************************************************
+ Allocate a new fnum between 1 and 0xFFFE from an smb2_hnd.
+ Ensures handle is owned by cli struct.
+***************************************************************/
+
+static NTSTATUS map_smb2_handle_to_fnum(struct cli_state *cli,
+ const struct smb2_hnd *ph, /* In */
+ uint16_t *pfnum) /* Out */
+{
+ int ret;
+ struct idr_context *idp = cli->smb2.open_handles;
+ struct smb2_hnd *owned_h = talloc_memdup(cli,
+ ph,
+ sizeof(struct smb2_hnd));
+
+ if (owned_h == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (idp == NULL) {
+ /* Lazy init */
+ cli->smb2.open_handles = idr_init(cli);
+ if (cli->smb2.open_handles == NULL) {
+ TALLOC_FREE(owned_h);
+ return NT_STATUS_NO_MEMORY;
+ }
+ idp = cli->smb2.open_handles;
+ }
+
+ ret = idr_get_new_above(idp, owned_h, 1, 0xFFFE);
+ if (ret == -1) {
+ TALLOC_FREE(owned_h);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *pfnum = (uint16_t)ret;
+ return NT_STATUS_OK;
+}
+
+/***************************************************************
+ Return the smb2_hnd pointer associated with the given fnum.
+***************************************************************/
+
+static NTSTATUS map_fnum_to_smb2_handle(struct cli_state *cli,
+ uint16_t fnum, /* In */
+ struct smb2_hnd **pph) /* Out */
+{
+ struct idr_context *idp = cli->smb2.open_handles;
+
+ if (idp == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ *pph = (struct smb2_hnd *)idr_find(idp, fnum);
+ if (*pph == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ return NT_STATUS_OK;
+}
+
+/***************************************************************
+ Delete the fnum to smb2_hnd mapping. Zeros out handle on
+ successful return.
+***************************************************************/
+
+static NTSTATUS delete_smb2_handle_mapping(struct cli_state *cli,
+ struct smb2_hnd **pph, /* In */
+ uint16_t fnum) /* In */
+{
+ struct idr_context *idp = cli->smb2.open_handles;
+ struct smb2_hnd *ph;
+
+ if (idp == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ph = (struct smb2_hnd *)idr_find(idp, fnum);
+ if (ph != *pph) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ idr_remove(idp, fnum);
+ TALLOC_FREE(*pph);
+ return NT_STATUS_OK;
+}
+
+/***************************************************************
+ Oplock mapping code.
+***************************************************************/
+
+static uint8_t flags_to_smb2_oplock(struct cli_smb2_create_flags create_flags)
+{
+ if (create_flags.batch_oplock) {
+ return SMB2_OPLOCK_LEVEL_BATCH;
+ } else if (create_flags.exclusive_oplock) {
+ return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ }
+
+ /* create_flags doesn't do a level2 request. */
+ return SMB2_OPLOCK_LEVEL_NONE;
+}
+
+/***************************************************************
+ If we're on a DFS share, ensure we convert to a full DFS path
+ if this hasn't already been done.
+***************************************************************/
+
+static char *smb2_dfs_share_path(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ char *path)
+{
+ bool is_dfs = smbXcli_conn_dfs_supported(cli->conn) &&
+ smbXcli_tcon_is_dfs_share(cli->smb2.tcon);
+ bool is_already_dfs_path = false;
+
+ if (!is_dfs) {
+ return path;
+ }
+ is_already_dfs_path = cli_dfs_is_already_full_path(cli, path);
+ if (is_already_dfs_path) {
+ return path;
+ }
+ if (path[0] == '\0') {
+ return talloc_asprintf(ctx,
+ "%s\\%s",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share);
+ }
+ while (*path == '\\') {
+ path++;
+ }
+ return talloc_asprintf(ctx,
+ "%s\\%s\\%s",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share,
+ path);
+}
+
+/***************************************************************
+ Small wrapper that allows SMB2 create to return a uint16_t fnum.
+***************************************************************/
+
+struct cli_smb2_create_fnum_state {
+ struct cli_state *cli;
+ struct smb2_create_blobs in_cblobs;
+ struct smb2_create_blobs out_cblobs;
+ struct smb_create_returns cr;
+ struct symlink_reparse_struct *symlink;
+ uint16_t fnum;
+ struct tevent_req *subreq;
+};
+
+static void cli_smb2_create_fnum_done(struct tevent_req *subreq);
+static bool cli_smb2_create_fnum_cancel(struct tevent_req *req);
+
+struct tevent_req *cli_smb2_create_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_in,
+ struct cli_smb2_create_flags create_flags,
+ uint32_t impersonation_level,
+ uint32_t desired_access,
+ uint32_t file_attributes,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ const struct smb2_create_blobs *in_cblobs)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_smb2_create_fnum_state *state;
+ char *fname = NULL;
+ size_t fname_len = 0;
+ bool have_twrp;
+ NTTIME ntt;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb2_create_fnum_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ fname = talloc_strdup(state, fname_in);
+ if (tevent_req_nomem(fname, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (cli->backup_intent) {
+ create_options |= FILE_OPEN_FOR_BACKUP_INTENT;
+ }
+
+ if (cli->smb2.client_smb311_posix) {
+ uint8_t modebuf[4] = {
+ 0,
+ };
+
+ status =
+ smb2_create_blob_add(state,
+ &state->in_cblobs,
+ SMB2_CREATE_TAG_POSIX,
+ (DATA_BLOB){
+ .data = modebuf,
+ .length = sizeof(modebuf),
+ });
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ /* Check for @GMT- paths. Remove the @GMT and turn into TWrp if so. */
+ have_twrp = clistr_smb2_extract_snapshot_token(fname, &ntt);
+ if (have_twrp) {
+ status = smb2_create_blob_add(
+ state,
+ &state->in_cblobs,
+ SMB2_CREATE_TAG_TWRP,
+ (DATA_BLOB) {
+ .data = (uint8_t *)&ntt,
+ .length = sizeof(ntt),
+ });
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (in_cblobs != NULL) {
+ uint32_t i;
+ for (i=0; i<in_cblobs->num_blobs; i++) {
+ struct smb2_create_blob *b = &in_cblobs->blobs[i];
+ status = smb2_create_blob_add(
+ state, &state->in_cblobs, b->tag, b->data);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ fname = smb2_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname, req)) {
+ return tevent_req_post(req, ev);
+ }
+ fname_len = strlen(fname);
+
+ /* SMB2 is pickier about pathnames. Ensure it doesn't
+ start in a '\' */
+ if (*fname == '\\') {
+ fname++;
+ fname_len--;
+ }
+
+ /* Or end in a '\' */
+ if (fname_len > 0 && fname[fname_len-1] == '\\') {
+ fname[fname_len-1] = '\0';
+ }
+
+ subreq = smb2cli_create_send(state, ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ fname,
+ flags_to_smb2_oplock(create_flags),
+ impersonation_level,
+ desired_access,
+ file_attributes,
+ share_access,
+ create_disposition,
+ create_options,
+ &state->in_cblobs);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_create_fnum_done, req);
+
+ state->subreq = subreq;
+ tevent_req_set_cancel_fn(req, cli_smb2_create_fnum_cancel);
+
+ return req;
+}
+
+static void cli_smb2_create_fnum_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_create_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_create_fnum_state);
+ struct smb2_hnd h;
+ NTSTATUS status;
+
+ status = smb2cli_create_recv(
+ subreq,
+ &h.fid_persistent,
+ &h.fid_volatile, &state->cr,
+ state,
+ &state->out_cblobs,
+ &state->symlink);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = map_smb2_handle_to_fnum(state->cli, &h, &state->fnum);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static bool cli_smb2_create_fnum_cancel(struct tevent_req *req)
+{
+ struct cli_smb2_create_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_create_fnum_state);
+ return tevent_req_cancel(state->subreq);
+}
+
+NTSTATUS cli_smb2_create_fnum_recv(
+ struct tevent_req *req,
+ uint16_t *pfnum,
+ struct smb_create_returns *cr,
+ TALLOC_CTX *mem_ctx,
+ struct smb2_create_blobs *out_cblobs,
+ struct symlink_reparse_struct **symlink)
+{
+ struct cli_smb2_create_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_create_fnum_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) &&
+ (symlink != NULL)) {
+ *symlink = talloc_move(mem_ctx, &state->symlink);
+ }
+ state->cli->raw_status = status;
+ return status;
+ }
+ if (pfnum != NULL) {
+ *pfnum = state->fnum;
+ }
+ if (cr != NULL) {
+ *cr = state->cr;
+ }
+ if (out_cblobs != NULL) {
+ *out_cblobs = (struct smb2_create_blobs) {
+ .num_blobs = state->out_cblobs.num_blobs,
+ .blobs = talloc_move(
+ mem_ctx, &state->out_cblobs.blobs),
+ };
+ }
+ state->cli->raw_status = NT_STATUS_OK;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_smb2_create_fnum(
+ struct cli_state *cli,
+ const char *fname,
+ struct cli_smb2_create_flags create_flags,
+ uint32_t impersonation_level,
+ uint32_t desired_access,
+ uint32_t file_attributes,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ const struct smb2_create_blobs *in_cblobs,
+ uint16_t *pfid,
+ struct smb_create_returns *cr,
+ TALLOC_CTX *mem_ctx,
+ struct smb2_create_blobs *out_cblobs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_create_fnum_send(
+ frame,
+ ev,
+ cli,
+ fname,
+ create_flags,
+ impersonation_level,
+ desired_access,
+ file_attributes,
+ share_access,
+ create_disposition,
+ create_options,
+ in_cblobs);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb2_create_fnum_recv(
+ req, pfid, cr, mem_ctx, out_cblobs, NULL);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Small wrapper that allows SMB2 close to use a uint16_t fnum.
+***************************************************************/
+
+struct cli_smb2_close_fnum_state {
+ struct cli_state *cli;
+ uint16_t fnum;
+ struct smb2_hnd *ph;
+};
+
+static void cli_smb2_close_fnum_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_close_fnum_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t flags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_smb2_close_fnum_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb2_close_fnum_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->fnum = fnum;
+
+ status = map_fnum_to_smb2_handle(cli, fnum, &state->ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_close_send(state,
+ ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ flags,
+ state->ph->fid_persistent,
+ state->ph->fid_volatile);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_close_fnum_done, req);
+ return req;
+}
+
+static void cli_smb2_close_fnum_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_close_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_close_fnum_state);
+ NTSTATUS status;
+
+ status = smb2cli_close_recv(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* Delete the fnum -> handle mapping. */
+ status = delete_smb2_handle_mapping(state->cli, &state->ph,
+ state->fnum);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_close_fnum_recv(struct tevent_req *req)
+{
+ struct cli_smb2_close_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_close_fnum_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->cli->raw_status = status;
+ }
+ tevent_req_received(req);
+ return status;
+}
+
+NTSTATUS cli_smb2_close_fnum(struct cli_state *cli, uint16_t fnum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_close_fnum_send(frame, ev, cli, fnum, 0);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb2_close_fnum_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_set_info_fnum_state {
+ uint8_t dummy;
+};
+
+static void cli_smb2_set_info_fnum_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_set_info_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_set_info_fnum_state *state = NULL;
+ struct smb2_hnd *ph = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_smb2_set_info_fnum_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = map_fnum_to_smb2_handle(cli, fnum, &ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_set_info_send(
+ state,
+ ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ in_info_type,
+ in_info_class,
+ in_input_buffer,
+ in_additional_info,
+ ph->fid_persistent,
+ ph->fid_volatile);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_set_info_fnum_done, req);
+ return req;
+}
+
+static void cli_smb2_set_info_fnum_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = smb2cli_set_info_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_smb2_set_info_fnum_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_smb2_set_info_fnum(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ bool ok;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_set_info_fnum_send(
+ frame,
+ ev,
+ cli,
+ fnum,
+ in_info_type,
+ in_info_class,
+ in_input_buffer,
+ in_additional_info);
+ if (req == NULL) {
+ goto fail;
+ }
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ goto fail;
+ }
+ status = cli_smb2_set_info_fnum_recv(req);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_delete_on_close_state {
+ struct cli_state *cli;
+ uint8_t data[1];
+ DATA_BLOB inbuf;
+};
+
+static void cli_smb2_delete_on_close_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_delete_on_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool flag)
+{
+ struct tevent_req *req = NULL;
+ struct cli_smb2_delete_on_close_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ uint8_t in_info_type;
+ uint8_t in_file_info_class;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb2_delete_on_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ /*
+ * setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
+ * level 13 (SMB_FILE_DISPOSITION_INFORMATION - 1000).
+ */
+ in_info_type = 1;
+ in_file_info_class = SMB_FILE_DISPOSITION_INFORMATION - 1000;
+ /* Setup data array. */
+ SCVAL(&state->data[0], 0, flag ? 1 : 0);
+ state->inbuf.data = &state->data[0];
+ state->inbuf.length = 1;
+
+ subreq = cli_smb2_set_info_fnum_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ in_info_type,
+ in_file_info_class,
+ &state->inbuf,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_smb2_delete_on_close_done,
+ req);
+ return req;
+}
+
+static void cli_smb2_delete_on_close_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_set_info_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_smb2_delete_on_close_recv(struct tevent_req *req)
+{
+ struct cli_smb2_delete_on_close_state *state =
+ tevent_req_data(req,
+ struct cli_smb2_delete_on_close_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->cli->raw_status = status;
+ tevent_req_received(req);
+ return status;
+ }
+
+ state->cli->raw_status = NT_STATUS_OK;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_smb2_delete_on_close(struct cli_state *cli, uint16_t fnum, bool flag)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_delete_on_close_send(frame, ev, cli, fnum, flag);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb2_delete_on_close_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_mkdir_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+};
+
+static void cli_smb2_mkdir_opened(struct tevent_req *subreq);
+static void cli_smb2_mkdir_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_mkdir_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *dname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_mkdir_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_smb2_mkdir_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ /* Ensure this is a directory. */
+ subreq = cli_smb2_create_fnum_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ dname, /* fname */
+ (struct cli_smb2_create_flags){0}, /* create_flags */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level */
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* share_access */
+ FILE_CREATE, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ NULL); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_mkdir_opened, req);
+ return req;
+}
+
+static void cli_smb2_mkdir_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_mkdir_state *state = tevent_req_data(
+ req, struct cli_smb2_mkdir_state);
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq =
+ cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_mkdir_closed, req);
+}
+
+static void cli_smb2_mkdir_closed(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_smb2_mkdir_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct cli_smb2_rmdir_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *dname;
+ const struct smb2_create_blobs *in_cblobs;
+ uint16_t fnum;
+ NTSTATUS status;
+};
+
+static void cli_smb2_rmdir_opened1(struct tevent_req *subreq);
+static void cli_smb2_rmdir_opened2(struct tevent_req *subreq);
+static void cli_smb2_rmdir_disp_set(struct tevent_req *subreq);
+static void cli_smb2_rmdir_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_rmdir_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *dname,
+ const struct smb2_create_blobs *in_cblobs)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_rmdir_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_rmdir_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->dname = dname;
+ state->in_cblobs = in_cblobs;
+
+ subreq = cli_smb2_create_fnum_send(
+ state,
+ state->ev,
+ state->cli,
+ state->dname,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ DELETE_ACCESS, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ state->in_cblobs); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rmdir_opened1, req);
+ return req;
+}
+
+static void cli_smb2_rmdir_opened1(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_rmdir_state *state = tevent_req_data(
+ req, struct cli_smb2_rmdir_state);
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &state->fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ /*
+ * Naive option to match our SMB1 code. Assume the
+ * symlink path that tripped us up was the last
+ * component and try again. Eventually we will have to
+ * deal with the returned path unprocessed component. JRA.
+ */
+ subreq = cli_smb2_create_fnum_send(
+ state,
+ state->ev,
+ state->cli,
+ state->dname,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ DELETE_ACCESS, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE|
+ FILE_DELETE_ON_CLOSE|
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ state->in_cblobs); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rmdir_opened2, req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_smb2_delete_on_close_send(
+ state, state->ev, state->cli, state->fnum, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rmdir_disp_set, req);
+}
+
+static void cli_smb2_rmdir_opened2(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_rmdir_state *state = tevent_req_data(
+ req, struct cli_smb2_rmdir_state);
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &state->fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_smb2_delete_on_close_send(
+ state, state->ev, state->cli, state->fnum, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rmdir_disp_set, req);
+}
+
+static void cli_smb2_rmdir_disp_set(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_rmdir_state *state = tevent_req_data(
+ req, struct cli_smb2_rmdir_state);
+
+ state->status = cli_smb2_delete_on_close_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ /*
+ * Close the fd even if the set_disp failed
+ */
+
+ subreq = cli_smb2_close_fnum_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rmdir_closed, req);
+}
+
+static void cli_smb2_rmdir_closed(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_smb2_rmdir_recv(struct tevent_req *req)
+{
+ struct cli_smb2_rmdir_state *state = tevent_req_data(
+ req, struct cli_smb2_rmdir_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ return state->status;
+}
+
+/***************************************************************
+ Small wrapper that allows SMB2 to unlink a pathname.
+***************************************************************/
+
+struct cli_smb2_unlink_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *fname;
+ const struct smb2_create_blobs *in_cblobs;
+};
+
+static void cli_smb2_unlink_opened1(struct tevent_req *subreq);
+static void cli_smb2_unlink_opened2(struct tevent_req *subreq);
+static void cli_smb2_unlink_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_unlink_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ const struct smb2_create_blobs *in_cblobs)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_unlink_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_unlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fname = fname;
+ state->in_cblobs = in_cblobs;
+
+ subreq = cli_smb2_create_fnum_send(
+ state, /* mem_ctx */
+ state->ev, /* tevent_context */
+ state->cli, /* cli_struct */
+ state->fname, /* filename */
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ DELETE_ACCESS, /* desired_access */
+ FILE_ATTRIBUTE_NORMAL, /* file attributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DELETE_ON_CLOSE, /* create_options */
+ state->in_cblobs); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_unlink_opened1, req);
+ return req;
+}
+
+static void cli_smb2_unlink_opened1(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_unlink_state *state = tevent_req_data(
+ req, struct cli_smb2_unlink_state);
+ uint16_t fnum = 0xffff;
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED)) {
+ /*
+ * Naive option to match our SMB1 code. Assume the
+ * symlink path that tripped us up was the last
+ * component and try again. Eventually we will have to
+ * deal with the returned path unprocessed component. JRA.
+ */
+ subreq = cli_smb2_create_fnum_send(
+ state, /* mem_ctx */
+ state->ev, /* tevent_context */
+ state->cli, /* cli_struct */
+ state->fname, /* filename */
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ DELETE_ACCESS, /* desired_access */
+ FILE_ATTRIBUTE_NORMAL, /* file attributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DELETE_ON_CLOSE|
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ state->in_cblobs); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_unlink_opened2, req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq =
+ cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_unlink_closed, req);
+}
+
+static void cli_smb2_unlink_opened2(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_unlink_state *state = tevent_req_data(
+ req, struct cli_smb2_unlink_state);
+ uint16_t fnum = 0xffff;
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq =
+ cli_smb2_close_fnum_send(state, state->ev, state->cli, fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_unlink_closed, req);
+}
+
+static void cli_smb2_unlink_closed(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_smb2_unlink_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/***************************************************************
+ Utility function to parse a SMB2_FIND_POSIX_INFORMATION reply.
+***************************************************************/
+
+static NTSTATUS parse_finfo_posix_info(const uint8_t *dir_data,
+ uint32_t dir_data_length,
+ struct file_info *finfo,
+ uint32_t *next_offset)
+{
+ struct smb3_file_posix_information info = {};
+ size_t consumed;
+ enum ndr_err_code ndr_err;
+ size_t namelen = 0;
+ size_t ret = 0;
+ uint32_t _next_offset = 0;
+
+ if (dir_data_length < 4) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ _next_offset = IVAL(dir_data, 0);
+
+ if (_next_offset > dir_data_length) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ if (_next_offset != 0) {
+ /* Ensure we only read what in this record. */
+ dir_data_length = _next_offset;
+ }
+
+ /*
+ * Skip NextEntryOffset and FileIndex
+ */
+ if (dir_data_length < 8) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+ dir_data += 8;
+ dir_data_length -= 8;
+
+ ndr_err = ndr_pull_struct_blob_noalloc(
+ dir_data,
+ dir_data_length,
+ &info,
+ (ndr_pull_flags_fn_t)ndr_pull_smb3_file_posix_information,
+ &consumed);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+ if (consumed > dir_data_length) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+ dir_data += consumed;
+ dir_data_length -= consumed;
+
+ finfo->btime_ts = interpret_long_date(info.creation_time);
+ finfo->atime_ts = interpret_long_date(info.last_access_time);
+ finfo->mtime_ts = interpret_long_date(info.last_write_time);
+ finfo->ctime_ts = interpret_long_date(info.change_time);
+ finfo->allocated_size = info.allocation_size;
+ finfo->size = info.end_of_file;
+ finfo->attr = info.file_attributes;
+ finfo->ino = info.inode;
+ finfo->st_ex_dev = info.device;
+ finfo->st_ex_nlink = info.cc.nlinks;
+ finfo->reparse_tag = info.cc.reparse_tag;
+ finfo->st_ex_mode = wire_perms_to_unix(info.cc.posix_perms);
+ sid_copy(&finfo->owner_sid, &info.cc.owner);
+ sid_copy(&finfo->group_sid, &info.cc.group);
+
+ if (dir_data_length < 4) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+ namelen = PULL_LE_U32(dir_data, 0);
+
+ dir_data += 4;
+ dir_data_length -= 4;
+
+ if (namelen > dir_data_length) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ ret = pull_string_talloc(finfo,
+ dir_data,
+ FLAGS2_UNICODE_STRINGS,
+ &finfo->name,
+ dir_data,
+ namelen,
+ STR_UNICODE);
+ if (ret == (size_t)-1) {
+ /* Bad conversion. */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ if (finfo->name == NULL) {
+ /* Bad conversion. */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ *next_offset = _next_offset;
+ return NT_STATUS_OK;
+}
+
+/***************************************************************
+ Utility function to parse a SMB2_FIND_ID_BOTH_DIRECTORY_INFO reply.
+***************************************************************/
+
+static NTSTATUS parse_finfo_id_both_directory_info(const uint8_t *dir_data,
+ uint32_t dir_data_length,
+ struct file_info *finfo,
+ uint32_t *next_offset)
+{
+ size_t namelen = 0;
+ size_t slen = 0;
+ size_t ret = 0;
+
+ if (dir_data_length < 4) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ *next_offset = IVAL(dir_data, 0);
+
+ if (*next_offset > dir_data_length) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ if (*next_offset != 0) {
+ /* Ensure we only read what in this record. */
+ dir_data_length = *next_offset;
+ }
+
+ if (dir_data_length < 105) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ finfo->btime_ts = interpret_long_date(BVAL(dir_data, 8));
+ finfo->atime_ts = interpret_long_date(BVAL(dir_data, 16));
+ finfo->mtime_ts = interpret_long_date(BVAL(dir_data, 24));
+ finfo->ctime_ts = interpret_long_date(BVAL(dir_data, 32));
+ finfo->size = IVAL2_TO_SMB_BIG_UINT(dir_data + 40, 0);
+ finfo->allocated_size = IVAL2_TO_SMB_BIG_UINT(dir_data + 48, 0);
+ finfo->attr = IVAL(dir_data + 56, 0);
+ finfo->ino = IVAL2_TO_SMB_BIG_UINT(dir_data + 96, 0);
+ namelen = IVAL(dir_data + 60,0);
+ if (namelen > (dir_data_length - 104)) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+ finfo->reparse_tag = IVAL(dir_data + 64, 0);
+ slen = CVAL(dir_data + 68, 0);
+ if (slen > 24) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+ ret = pull_string_talloc(finfo,
+ dir_data,
+ FLAGS2_UNICODE_STRINGS,
+ &finfo->short_name,
+ dir_data + 70,
+ slen,
+ STR_UNICODE);
+ if (ret == (size_t)-1) {
+ /* Bad conversion. */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ ret = pull_string_talloc(finfo,
+ dir_data,
+ FLAGS2_UNICODE_STRINGS,
+ &finfo->name,
+ dir_data + 104,
+ namelen,
+ STR_UNICODE);
+ if (ret == (size_t)-1) {
+ /* Bad conversion. */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ if (finfo->name == NULL) {
+ /* Bad conversion. */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Given a filename - get its directory name
+********************************************************************/
+
+static bool windows_parent_dirname(TALLOC_CTX *mem_ctx,
+ const char *dir,
+ char **parent,
+ const char **name)
+{
+ char *p;
+ ptrdiff_t len;
+
+ p = strrchr_m(dir, '\\'); /* Find final '\\', if any */
+
+ if (p == NULL) {
+ if (!(*parent = talloc_strdup(mem_ctx, "\\"))) {
+ return false;
+ }
+ if (name) {
+ *name = dir;
+ }
+ return true;
+ }
+
+ len = p-dir;
+
+ if (!(*parent = (char *)talloc_memdup(mem_ctx, dir, len+1))) {
+ return false;
+ }
+ (*parent)[len] = '\0';
+
+ if (name) {
+ *name = p+1;
+ }
+ return true;
+}
+
+struct cli_smb2_list_dir_data {
+ uint8_t *data;
+ uint32_t length;
+};
+
+struct cli_smb2_list_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *mask;
+
+ uint16_t fnum;
+
+ NTSTATUS status;
+ struct cli_smb2_list_dir_data *response;
+ uint32_t offset;
+ unsigned int info_level;
+};
+
+static void cli_smb2_list_opened(struct tevent_req *subreq);
+static void cli_smb2_list_done(struct tevent_req *subreq);
+static void cli_smb2_list_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_list_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *pathname,
+ unsigned int info_level)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_list_state *state = NULL;
+ char *parent = NULL;
+ bool ok;
+ struct smb2_create_blobs *in_cblobs = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_list_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->status = NT_STATUS_OK;
+ state->info_level = info_level;
+
+ ok = windows_parent_dirname(state, pathname, &parent, &state->mask);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_have_posix(cli->conn) &&
+ info_level == SMB2_FIND_POSIX_INFORMATION)
+ {
+ NTSTATUS status;
+
+ /* The mode MUST be 0 when opening an existing file/dir, and
+ * will be ignored by the server.
+ */
+ uint8_t linear_mode[4] = { 0 };
+ DATA_BLOB blob = { .data=linear_mode,
+ .length=sizeof(linear_mode) };
+
+ in_cblobs = talloc_zero(mem_ctx, struct smb2_create_blobs);
+ if (in_cblobs == NULL) {
+ return NULL;
+ }
+
+ status = smb2_create_blob_add(in_cblobs, in_cblobs,
+ SMB2_CREATE_TAG_POSIX, blob);
+ if (tevent_req_nterror(req, status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = cli_smb2_create_fnum_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ parent, /* fname */
+ (struct cli_smb2_create_flags){0}, /* create_flags */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level */
+ SEC_DIR_LIST|SEC_DIR_READ_ATTRIBUTE, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ in_cblobs); /* in_cblobs */
+ TALLOC_FREE(in_cblobs);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_list_opened, req);
+ return req;
+}
+
+static void cli_smb2_list_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_list_state *state = tevent_req_data(
+ req, struct cli_smb2_list_state);
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &state->fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * Make our caller get back to us via cli_smb2_list_recv(),
+ * triggering the smb2_query_directory_send()
+ */
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+}
+
+static void cli_smb2_list_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_list_state *state = tevent_req_data(
+ req, struct cli_smb2_list_state);
+ struct cli_smb2_list_dir_data *response = NULL;
+
+ response = talloc(state, struct cli_smb2_list_dir_data);
+ if (tevent_req_nomem(response, req)) {
+ return;
+ }
+
+ state->status = smb2cli_query_directory_recv(
+ subreq, response, &response->data, &response->length);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(state->status)) {
+ state->response = response;
+ state->offset = 0;
+
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+ return;
+ }
+
+ TALLOC_FREE(response);
+
+ subreq = cli_smb2_close_fnum_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_list_closed, req);
+}
+
+static void cli_smb2_list_closed(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+/*
+ * Return the next finfo directory.
+ *
+ * This parses the blob returned from QUERY_DIRECTORY step by step. If
+ * the blob ends, this triggers a fresh QUERY_DIRECTORY and returns
+ * NT_STATUS_RETRY, which will then trigger the caller again when the
+ * QUERY_DIRECTORY has returned with another buffer. This way we
+ * guarantee that no asynchronous request is open after this call
+ * returns an entry, so that other synchronous requests can be issued
+ * on the same connection while the directory listing proceeds.
+ */
+NTSTATUS cli_smb2_list_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct file_info **pfinfo)
+{
+ struct cli_smb2_list_state *state = tevent_req_data(
+ req, struct cli_smb2_list_state);
+ struct cli_smb2_list_dir_data *response = NULL;
+ struct file_info *finfo = NULL;
+ NTSTATUS status;
+ uint32_t next_offset = 0;
+ bool in_progress;
+
+ in_progress = tevent_req_is_in_progress(req);
+
+ if (!in_progress) {
+ if (!tevent_req_is_nterror(req, &status)) {
+ status = NT_STATUS_NO_MORE_FILES;
+ }
+ goto fail;
+ }
+
+ response = state->response;
+ if (response == NULL) {
+ struct tevent_req *subreq = NULL;
+ struct cli_state *cli = state->cli;
+ struct smb2_hnd *ph = NULL;
+ uint32_t max_trans, max_avail_len;
+ bool ok;
+
+ if (!NT_STATUS_IS_OK(state->status)) {
+ status = state->status;
+ goto fail;
+ }
+
+ status = map_fnum_to_smb2_handle(cli, state->fnum, &ph);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ max_trans = smb2cli_conn_max_trans_size(cli->conn);
+ ok = smb2cli_conn_req_possible(cli->conn, &max_avail_len);
+ if (ok) {
+ max_trans = MIN(max_trans, max_avail_len);
+ }
+
+ subreq = smb2cli_query_directory_send(
+ state, /* mem_ctx */
+ state->ev, /* ev */
+ cli->conn, /* conn */
+ cli->timeout, /* timeout_msec */
+ cli->smb2.session, /* session */
+ cli->smb2.tcon, /* tcon */
+ state->info_level, /* level */
+ 0, /* flags */
+ 0, /* file_index */
+ ph->fid_persistent, /* fid_persistent */
+ ph->fid_volatile, /* fid_volatile */
+ state->mask, /* mask */
+ max_trans); /* outbuf_len */
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_list_done, req);
+ return NT_STATUS_RETRY;
+ }
+
+ SMB_ASSERT(response->length > state->offset);
+
+ finfo = talloc_zero(mem_ctx, struct file_info);
+ if (finfo == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (state->info_level == SMB2_FIND_POSIX_INFORMATION) {
+ status = parse_finfo_posix_info(
+ response->data + state->offset,
+ response->length - state->offset,
+ finfo,
+ &next_offset);
+ } else {
+ status = parse_finfo_id_both_directory_info(
+ response->data + state->offset,
+ response->length - state->offset,
+ finfo,
+ &next_offset);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = is_bad_finfo_name(state->cli, finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /*
+ * parse_finfo_id_both_directory_info() checks for overflow,
+ * no need to check again here.
+ */
+ state->offset += next_offset;
+
+ if (next_offset == 0) {
+ TALLOC_FREE(state->response);
+ }
+
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+
+ *pfinfo = finfo;
+ return NT_STATUS_OK;
+
+fail:
+ TALLOC_FREE(finfo);
+ tevent_req_received(req);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to query a path info (basic level).
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
+ const char *name,
+ SMB_STRUCT_STAT *sbuf,
+ uint32_t *attributes)
+{
+ NTSTATUS status;
+ struct smb_create_returns cr;
+ uint16_t fnum = 0xffff;
+ size_t namelen = strlen(name);
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* SMB2 is pickier about pathnames. Ensure it doesn't
+ end in a '\' */
+ if (namelen > 0 && name[namelen-1] == '\\') {
+ char *modname = talloc_strndup(talloc_tos(), name, namelen-1);
+ if (modname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ name = modname;
+ }
+
+ /* This is commonly used as a 'cd'. Try qpathinfo on
+ a directory handle first. */
+
+ status = cli_smb2_create_fnum(cli,
+ name,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ NULL,
+ &fnum,
+ &cr,
+ NULL,
+ NULL);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY)) {
+ /* Maybe a file ? */
+ status = cli_smb2_create_fnum(cli,
+ name,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ 0, /* file attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ 0, /* create_options */
+ NULL,
+ &fnum,
+ &cr,
+ NULL,
+ NULL);
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ /* Maybe a reparse point ? */
+ status = cli_smb2_create_fnum(cli,
+ name,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ 0, /* file attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ NULL,
+ &fnum,
+ &cr,
+ NULL,
+ NULL);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = cli_smb2_close_fnum(cli, fnum);
+
+ ZERO_STRUCTP(sbuf);
+
+ sbuf->st_ex_atime = nt_time_to_unix_timespec(cr.last_access_time);
+ sbuf->st_ex_mtime = nt_time_to_unix_timespec(cr.last_write_time);
+ sbuf->st_ex_ctime = nt_time_to_unix_timespec(cr.change_time);
+ sbuf->st_ex_size = cr.end_of_file;
+ *attributes = cr.file_attributes;
+
+ return status;
+}
+
+struct cli_smb2_query_info_fnum_state {
+ DATA_BLOB outbuf;
+};
+
+static void cli_smb2_query_info_fnum_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_query_info_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ uint32_t in_max_output_length,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info,
+ uint32_t in_flags)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_query_info_fnum_state *state = NULL;
+ struct smb2_hnd *ph = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_smb2_query_info_fnum_state);
+ if (req == NULL) {
+ return req;
+ }
+
+ status = map_fnum_to_smb2_handle(cli, fnum, &ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_query_info_send(
+ state,
+ ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ in_info_type,
+ in_info_class,
+ in_max_output_length,
+ in_input_buffer,
+ in_additional_info,
+ in_flags,
+ ph->fid_persistent,
+ ph->fid_volatile);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_query_info_fnum_done, req);
+ return req;
+}
+
+static void cli_smb2_query_info_fnum_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_query_info_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_query_info_fnum_state);
+ DATA_BLOB outbuf;
+ NTSTATUS status;
+
+ status = smb2cli_query_info_recv(subreq, state, &outbuf);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * We have to dup the memory here because outbuf.data is not
+ * returned as a talloc object by smb2cli_query_info_recv.
+ * It's a pointer into the received buffer.
+ */
+ state->outbuf = data_blob_dup_talloc(state, outbuf);
+
+ if ((outbuf.length != 0) &&
+ tevent_req_nomem(state->outbuf.data, req)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_query_info_fnum_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *outbuf)
+{
+ struct cli_smb2_query_info_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_query_info_fnum_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *outbuf = (DATA_BLOB) {
+ .data = talloc_move(mem_ctx, &state->outbuf.data),
+ .length = state->outbuf.length,
+ };
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_smb2_query_info_fnum(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ uint32_t in_max_output_length,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info,
+ uint32_t in_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *outbuf)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ bool ok;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_query_info_fnum_send(
+ frame,
+ ev,
+ cli,
+ fnum,
+ in_info_type,
+ in_info_class,
+ in_max_output_length,
+ in_input_buffer,
+ in_additional_info,
+ in_flags);
+ if (req == NULL) {
+ goto fail;
+ }
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ goto fail;
+ }
+ status = cli_smb2_query_info_fnum_recv(req, mem_ctx, outbuf);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Helper function for pathname operations.
+***************************************************************/
+
+struct get_fnum_from_path_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *name;
+ uint32_t desired_access;
+ uint16_t fnum;
+};
+
+static void get_fnum_from_path_opened_file(struct tevent_req *subreq);
+static void get_fnum_from_path_opened_reparse(struct tevent_req *subreq);
+static void get_fnum_from_path_opened_dir(struct tevent_req *subreq);
+
+static struct tevent_req *get_fnum_from_path_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *name,
+ uint32_t desired_access)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct get_fnum_from_path_state *state = NULL;
+ size_t namelen = strlen(name);
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct get_fnum_from_path_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->name = name;
+ state->desired_access = desired_access;
+
+ /*
+ * SMB2 is pickier about pathnames. Ensure it doesn't end in a
+ * '\'
+ */
+ if (namelen > 0 && name[namelen-1] == '\\') {
+ state->name = talloc_strndup(state, name, namelen-1);
+ if (tevent_req_nomem(state->name, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = cli_smb2_create_fnum_send(
+ state, /* mem_ctx, */
+ ev, /* ev */
+ cli, /* cli */
+ state->name, /* fname */
+ (struct cli_smb2_create_flags){0}, /* create_flags */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level */
+ desired_access, /* desired_access */
+ 0, /* file_attributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ 0, /* create_options */
+ NULL); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, get_fnum_from_path_opened_file, req);
+ return req;
+}
+
+static void get_fnum_from_path_opened_file(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct get_fnum_from_path_state *state = tevent_req_data(
+ req, struct get_fnum_from_path_state);
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &state->fnum, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED)) {
+ /*
+ * Naive option to match our SMB1 code. Assume the
+ * symlink path that tripped us up was the last
+ * component and try again. Eventually we will have to
+ * deal with the returned path unprocessed component. JRA.
+ */
+ subreq = cli_smb2_create_fnum_send(
+ state, /* mem_ctx, */
+ state->ev, /* ev */
+ state->cli, /* cli */
+ state->name, /* fname */
+ (struct cli_smb2_create_flags){0}, /* create_flags */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation */
+ state->desired_access, /* desired_access */
+ 0, /* file_attributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ NULL); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(
+ subreq, get_fnum_from_path_opened_reparse, req);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ subreq = cli_smb2_create_fnum_send(
+ state, /* mem_ctx, */
+ state->ev, /* ev */
+ state->cli, /* cli */
+ state->name, /* fname */
+ (struct cli_smb2_create_flags){0}, /* create_flags */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation */
+ state->desired_access, /* desired_access */
+ 0, /* file_attributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ NULL); /* in_cblobs */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(
+ subreq, get_fnum_from_path_opened_dir, req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void get_fnum_from_path_opened_reparse(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct get_fnum_from_path_state *state = tevent_req_data(
+ req, struct get_fnum_from_path_state);
+ NTSTATUS status = cli_smb2_create_fnum_recv(
+ subreq, &state->fnum, NULL, NULL, NULL, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void get_fnum_from_path_opened_dir(struct tevent_req *subreq)
+{
+ /* Abstraction violation, but these two are just the same... */
+ get_fnum_from_path_opened_reparse(subreq);
+}
+
+static NTSTATUS get_fnum_from_path_recv(
+ struct tevent_req *req, uint16_t *pfnum)
+{
+ struct get_fnum_from_path_state *state = tevent_req_data(
+ req, struct get_fnum_from_path_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (!tevent_req_is_nterror(req, &status)) {
+ *pfnum = state->fnum;
+ }
+ tevent_req_received(req);
+ return status;
+}
+
+static NTSTATUS get_fnum_from_path(struct cli_state *cli,
+ const char *name,
+ uint32_t desired_access,
+ uint16_t *pfnum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = get_fnum_from_path_send(frame, ev, cli, name, desired_access);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = get_fnum_from_path_recv(req, pfnum);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_qpathinfo_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *fname;
+ uint16_t fnum;
+ uint16_t level;
+ uint32_t min_rdata;
+ uint32_t max_rdata;
+
+ NTSTATUS status;
+ DATA_BLOB out;
+};
+
+static void cli_smb2_qpathinfo_opened(struct tevent_req *subreq);
+static void cli_smb2_qpathinfo_done(struct tevent_req *subreq);
+static void cli_smb2_qpathinfo_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_qpathinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint16_t level,
+ uint32_t min_rdata,
+ uint32_t max_rdata)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_qpathinfo_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct cli_smb2_qpathinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->level = level;
+ state->min_rdata = min_rdata;
+ state->max_rdata = max_rdata;
+
+ subreq = get_fnum_from_path_send(state,
+ ev,
+ cli,
+ fname,
+ FILE_READ_ATTRIBUTES);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_qpathinfo_opened, req);
+ return req;
+}
+
+static void cli_smb2_qpathinfo_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_smb2_qpathinfo_state *state =
+ tevent_req_data(req, struct cli_smb2_qpathinfo_state);
+ NTSTATUS status;
+
+ status = get_fnum_from_path_recv(subreq, &state->fnum);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_smb2_query_info_fnum_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 1, /* in_info_type */
+ state->level,
+ state->max_rdata,
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0); /* in_flags */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_qpathinfo_done, req);
+}
+
+static void cli_smb2_qpathinfo_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_smb2_qpathinfo_state *state =
+ tevent_req_data(req, struct cli_smb2_qpathinfo_state);
+
+ state->status =
+ cli_smb2_query_info_fnum_recv(subreq, state, &state->out);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(state->status) &&
+ (state->out.length < state->min_rdata)) {
+ state->status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ subreq = cli_smb2_close_fnum_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_qpathinfo_closed, req);
+}
+
+static void cli_smb2_qpathinfo_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_smb2_qpathinfo_state *state =
+ tevent_req_data(req, struct cli_smb2_qpathinfo_state);
+ NTSTATUS status;
+
+ status = cli_smb2_close_fnum_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (tevent_req_nterror(req, state->status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_qpathinfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **rdata,
+ uint32_t *num_rdata)
+{
+ struct cli_smb2_qpathinfo_state *state =
+ tevent_req_data(req, struct cli_smb2_qpathinfo_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *rdata = talloc_move(mem_ctx, &state->out.data);
+ *num_rdata = state->out.length;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to set SMB_FILE_BASIC_INFORMATION on
+ a pathname.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_setpathinfo(struct cli_state *cli,
+ const char *name,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ const DATA_BLOB *p_in_data)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ status = get_fnum_from_path(cli,
+ name,
+ FILE_WRITE_ATTRIBUTES,
+ &fnum);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = cli_smb2_set_info_fnum(
+ cli,
+ fnum,
+ in_info_type,
+ in_file_info_class,
+ p_in_data, /* in_input_buffer */
+ 0); /* in_additional_info */
+ fail:
+
+ if (fnum != 0xffff) {
+ cli_smb2_close_fnum(cli, fnum);
+ }
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+
+/***************************************************************
+ Wrapper that allows SMB2 to set pathname attributes.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_setatr(struct cli_state *cli,
+ const char *name,
+ uint32_t attr,
+ time_t mtime)
+{
+ uint8_t inbuf_store[40];
+ DATA_BLOB inbuf = data_blob_null;
+
+ /* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
+ level 4 (SMB_FILE_BASIC_INFORMATION - 1000). */
+
+ inbuf.data = inbuf_store;
+ inbuf.length = sizeof(inbuf_store);
+ data_blob_clear(&inbuf);
+
+ /*
+ * SMB1 uses attr == 0 to clear all attributes
+ * on a file (end up with FILE_ATTRIBUTE_NORMAL),
+ * and attr == FILE_ATTRIBUTE_NORMAL to mean ignore
+ * request attribute change.
+ *
+ * SMB2 uses exactly the reverse. Unfortunately as the
+ * cli_setatr() ABI is exposed inside libsmbclient,
+ * we must make the SMB2 cli_smb2_setatr() call
+ * export the same ABI as the SMB1 cli_setatr()
+ * which calls it. This means reversing the sense
+ * of the requested attr argument if it's zero
+ * or FILE_ATTRIBUTE_NORMAL.
+ *
+ * See BUG: https://bugzilla.samba.org/show_bug.cgi?id=12899
+ */
+
+ if (attr == 0) {
+ attr = FILE_ATTRIBUTE_NORMAL;
+ } else if (attr == FILE_ATTRIBUTE_NORMAL) {
+ attr = 0;
+ }
+
+ SIVAL(inbuf.data, 32, attr);
+ if (mtime != 0) {
+ put_long_date((char *)inbuf.data + 16,mtime);
+ }
+ /* Set all the other times to -1. */
+ SBVAL(inbuf.data, 0, 0xFFFFFFFFFFFFFFFFLL);
+ SBVAL(inbuf.data, 8, 0xFFFFFFFFFFFFFFFFLL);
+ SBVAL(inbuf.data, 24, 0xFFFFFFFFFFFFFFFFLL);
+
+ return cli_smb2_setpathinfo(cli,
+ name,
+ 1, /* in_info_type */
+ /* in_file_info_class */
+ SMB_FILE_BASIC_INFORMATION - 1000,
+ &inbuf);
+}
+
+
+/***************************************************************
+ Wrapper that allows SMB2 to set file handle times.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_setattrE(struct cli_state *cli,
+ uint16_t fnum,
+ time_t change_time,
+ time_t access_time,
+ time_t write_time)
+{
+ uint8_t inbuf_store[40];
+ DATA_BLOB inbuf = data_blob_null;
+ NTSTATUS status;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
+ level 4 (SMB_FILE_BASIC_INFORMATION - 1000). */
+
+ inbuf.data = inbuf_store;
+ inbuf.length = sizeof(inbuf_store);
+ data_blob_clear(&inbuf);
+
+ SBVAL(inbuf.data, 0, 0xFFFFFFFFFFFFFFFFLL);
+ if (change_time != 0) {
+ put_long_date((char *)inbuf.data + 24, change_time);
+ }
+ if (access_time != 0) {
+ put_long_date((char *)inbuf.data + 8, access_time);
+ }
+ if (write_time != 0) {
+ put_long_date((char *)inbuf.data + 16, write_time);
+ }
+
+ status = cli_smb2_set_info_fnum(cli,
+ fnum,
+ 1, /* in_info_type */
+ SMB_FILE_BASIC_INFORMATION -
+ 1000, /* in_file_info_class */
+ &inbuf, /* in_input_buffer */
+ 0); /* in_additional_info */
+ cli->raw_status = status;
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to query disk attributes (size).
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_dskattr(struct cli_state *cli, const char *path,
+ uint64_t *bsize, uint64_t *total, uint64_t *avail)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+ DATA_BLOB outbuf = data_blob_null;
+ uint32_t sectors_per_unit = 0;
+ uint32_t bytes_per_sector = 0;
+ uint64_t total_size = 0;
+ uint64_t size_free = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* First open the top level directory. */
+ status = cli_smb2_create_fnum(cli,
+ path,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ NULL,
+ &fnum,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2),
+ level 3 (SMB_FS_SIZE_INFORMATION). */
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ fnum,
+ 2, /* in_info_type */
+ 3, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* Parse the reply. */
+ if (outbuf.length != 24) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto fail;
+ }
+
+ total_size = BVAL(outbuf.data, 0);
+ size_free = BVAL(outbuf.data, 8);
+ sectors_per_unit = IVAL(outbuf.data, 16);
+ bytes_per_sector = IVAL(outbuf.data, 20);
+
+ if (bsize) {
+ *bsize = (uint64_t)sectors_per_unit * (uint64_t)bytes_per_sector;
+ }
+ if (total) {
+ *total = total_size;
+ }
+ if (avail) {
+ *avail = size_free;
+ }
+
+ status = NT_STATUS_OK;
+
+ fail:
+
+ if (fnum != 0xffff) {
+ cli_smb2_close_fnum(cli, fnum);
+ }
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to query file system sizes.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_get_fs_full_size_info(struct cli_state *cli,
+ uint64_t *total_allocation_units,
+ uint64_t *caller_allocation_units,
+ uint64_t *actual_allocation_units,
+ uint64_t *sectors_per_allocation_unit,
+ uint64_t *bytes_per_sector)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+ DATA_BLOB outbuf = data_blob_null;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* First open the top level directory. */
+ status =
+ cli_smb2_create_fnum(cli, "",
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
+ FILE_SHARE_READ | FILE_SHARE_WRITE |
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ NULL,
+ &fnum,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2),
+ level 7 (SMB_FS_FULL_SIZE_INFORMATION). */
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ fnum,
+ SMB2_0_INFO_FILESYSTEM, /* in_info_type */
+ SMB_FS_FULL_SIZE_INFORMATION - 1000, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (outbuf.length < 32) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto fail;
+ }
+
+ *total_allocation_units = BIG_UINT(outbuf.data, 0);
+ *caller_allocation_units = BIG_UINT(outbuf.data, 8);
+ *actual_allocation_units = BIG_UINT(outbuf.data, 16);
+ *sectors_per_allocation_unit = (uint64_t)IVAL(outbuf.data, 24);
+ *bytes_per_sector = (uint64_t)IVAL(outbuf.data, 28);
+
+fail:
+
+ if (fnum != 0xffff) {
+ cli_smb2_close_fnum(cli, fnum);
+ }
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to query file system attributes.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_get_fs_attr_info(struct cli_state *cli, uint32_t *fs_attr)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+ DATA_BLOB outbuf = data_blob_null;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* First open the top level directory. */
+ status =
+ cli_smb2_create_fnum(cli, "",
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
+ FILE_SHARE_READ | FILE_SHARE_WRITE |
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ NULL,
+ &fnum,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ fnum,
+ 2, /* in_info_type */
+ 5, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (outbuf.length < 12) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto fail;
+ }
+
+ *fs_attr = IVAL(outbuf.data, 0);
+
+fail:
+
+ if (fnum != 0xffff) {
+ cli_smb2_close_fnum(cli, fnum);
+ }
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to query file system volume info.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_get_fs_volume_info(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ char **_volume_name,
+ uint32_t *pserial_number,
+ time_t *pdate)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+ DATA_BLOB outbuf = data_blob_null;
+ uint32_t nlen;
+ char *volume_name = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* First open the top level directory. */
+ status =
+ cli_smb2_create_fnum(cli, "",
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* file attributes */
+ FILE_SHARE_READ | FILE_SHARE_WRITE |
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_DIRECTORY_FILE, /* create_options */
+ NULL,
+ &fnum,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* getinfo on the returned handle with info_type SMB2_GETINFO_FS (2),
+ level 1 (SMB_FS_VOLUME_INFORMATION). */
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ fnum,
+ SMB2_0_INFO_FILESYSTEM, /* in_info_type */
+ /* in_file_info_class */
+ SMB_FS_VOLUME_INFORMATION - 1000,
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (outbuf.length < 24) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto fail;
+ }
+
+ if (pdate) {
+ struct timespec ts;
+ ts = interpret_long_date(BVAL(outbuf.data, 0));
+ *pdate = ts.tv_sec;
+ }
+ if (pserial_number) {
+ *pserial_number = IVAL(outbuf.data,8);
+ }
+ nlen = IVAL(outbuf.data,12);
+ if (nlen + 18 < 18) {
+ /* Integer wrap. */
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto fail;
+ }
+ /*
+ * The next check is safe as we know outbuf.length >= 24
+ * from above.
+ */
+ if (nlen > (outbuf.length - 18)) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto fail;
+ }
+
+ pull_string_talloc(mem_ctx,
+ (const char *)outbuf.data,
+ 0,
+ &volume_name,
+ outbuf.data + 18,
+ nlen,
+ STR_UNICODE);
+ if (volume_name == NULL) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ *_volume_name = volume_name;
+
+fail:
+
+ if (fnum != 0xffff) {
+ cli_smb2_close_fnum(cli, fnum);
+ }
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_mxac_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *fname;
+ struct smb2_create_blobs in_cblobs;
+ uint16_t fnum;
+ NTSTATUS status;
+ uint32_t mxac;
+};
+
+static void cli_smb2_mxac_opened(struct tevent_req *subreq);
+static void cli_smb2_mxac_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_query_mxac_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_mxac_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_mxac_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct cli_smb2_mxac_state) {
+ .ev = ev,
+ .cli = cli,
+ .fname = fname,
+ };
+
+ status = smb2_create_blob_add(state,
+ &state->in_cblobs,
+ SMB2_CREATE_TAG_MXAC,
+ data_blob(NULL, 0));
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb2_create_fnum_send(
+ state,
+ state->ev,
+ state->cli,
+ state->fname,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_READ_ATTRIBUTES,
+ 0, /* file attributes */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0, /* create_options */
+ &state->in_cblobs);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_mxac_opened, req);
+ return req;
+}
+
+static void cli_smb2_mxac_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_mxac_state *state = tevent_req_data(
+ req, struct cli_smb2_mxac_state);
+ struct smb2_create_blobs out_cblobs = {0};
+ struct smb2_create_blob *mxac_blob = NULL;
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &state->fnum, NULL, state, &out_cblobs, NULL);
+ TALLOC_FREE(subreq);
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ mxac_blob = smb2_create_blob_find(&out_cblobs, SMB2_CREATE_TAG_MXAC);
+ if (mxac_blob == NULL) {
+ state->status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto close;
+ }
+ if (mxac_blob->data.length != 8) {
+ state->status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto close;
+ }
+
+ state->status = NT_STATUS(IVAL(mxac_blob->data.data, 0));
+ state->mxac = IVAL(mxac_blob->data.data, 4);
+
+close:
+ subreq = cli_smb2_close_fnum_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_mxac_closed, req);
+
+ return;
+}
+
+static void cli_smb2_mxac_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb2_close_fnum_recv(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_query_mxac_recv(struct tevent_req *req, uint32_t *mxac)
+{
+ struct cli_smb2_mxac_state *state = tevent_req_data(
+ req, struct cli_smb2_mxac_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return state->status;
+ }
+
+ *mxac = state->mxac;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_smb2_query_mxac(struct cli_state *cli,
+ const char *fname,
+ uint32_t *_mxac)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ bool ok;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_query_mxac_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ goto fail;
+ }
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ goto fail;
+ }
+ status = cli_smb2_query_mxac_recv(req, _mxac);
+
+fail:
+ cli->raw_status = status;
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_rename_fnum_state {
+ DATA_BLOB inbuf;
+};
+
+static void cli_smb2_rename_fnum_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb2_rename_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ const char *fname_dst,
+ bool replace)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_rename_fnum_state *state = NULL;
+ size_t namelen = strlen(fname_dst);
+ smb_ucs2_t *converted_str = NULL;
+ size_t converted_size_bytes = 0;
+ size_t inbuf_size;
+ bool ok;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_smb2_rename_fnum_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /*
+ * SMB2 is pickier about pathnames. Ensure it doesn't start in
+ * a '\'
+ */
+ if (*fname_dst == '\\') {
+ fname_dst++;
+ }
+
+ /*
+ * SMB2 is pickier about pathnames. Ensure it doesn't end in a
+ * '\'
+ */
+ if (namelen > 0 && fname_dst[namelen-1] == '\\') {
+ fname_dst = talloc_strndup(state, fname_dst, namelen-1);
+ if (tevent_req_nomem(fname_dst, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ ok = push_ucs2_talloc(
+ state, &converted_str, fname_dst, &converted_size_bytes);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * W2K8 insists the dest name is not null terminated. Remove
+ * the last 2 zero bytes and reduce the name length.
+ */
+ if (converted_size_bytes < 2) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ converted_size_bytes -= 2;
+
+ inbuf_size = 20 + converted_size_bytes;
+ if (inbuf_size < 20) {
+ /* Integer wrap check. */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * The Windows 10 SMB2 server has a minimum length
+ * for a SMB2_FILE_RENAME_INFORMATION buffer of
+ * 24 bytes. It returns NT_STATUS_INFO_LENGTH_MISMATCH
+ * if the length is less. This isn't an alignment
+ * issue as Windows client accepts happily 2-byte align
+ * for larger target name sizes. Also the Windows 10
+ * SMB1 server doesn't have this restriction.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14403
+ */
+ inbuf_size = MAX(inbuf_size, 24);
+
+ state->inbuf = data_blob_talloc_zero(state, inbuf_size);
+ if (tevent_req_nomem(state->inbuf.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (replace) {
+ SCVAL(state->inbuf.data, 0, 1);
+ }
+
+ SIVAL(state->inbuf.data, 16, converted_size_bytes);
+ memcpy(state->inbuf.data + 20, converted_str, converted_size_bytes);
+
+ TALLOC_FREE(converted_str);
+
+ /* setinfo on the returned handle with info_type SMB2_GETINFO_FILE (1),
+ level SMB2_FILE_RENAME_INFORMATION (SMB_FILE_RENAME_INFORMATION - 1000) */
+
+ subreq = cli_smb2_set_info_fnum_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ fnum, /* fnum */
+ 1, /* in_info_type */
+ SMB_FILE_RENAME_INFORMATION - 1000, /* in_file_info_class */
+ &state->inbuf, /* in_input_buffer */
+ 0); /* in_additional_info */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rename_fnum_done, req);
+ return req;
+}
+
+static void cli_smb2_rename_fnum_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_set_info_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_smb2_rename_fnum_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to rename a file.
+***************************************************************/
+
+struct cli_smb2_rename_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *fname_dst;
+ bool replace;
+ uint16_t fnum;
+
+ NTSTATUS rename_status;
+};
+
+static void cli_smb2_rename_opened(struct tevent_req *subreq);
+static void cli_smb2_rename_renamed(struct tevent_req *subreq);
+static void cli_smb2_rename_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_rename_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_rename_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_smb2_rename_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Strip a MSDFS path from fname_dst if we were given one.
+ */
+ status = cli_dfs_target_check(state,
+ cli,
+ fname_dst,
+ &fname_dst);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->ev = ev;
+ state->cli = cli;
+ state->fname_dst = fname_dst;
+ state->replace = replace;
+
+ subreq = get_fnum_from_path_send(
+ state, ev, cli, fname_src, DELETE_ACCESS);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rename_opened, req);
+ return req;
+}
+
+static void cli_smb2_rename_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_rename_state *state = tevent_req_data(
+ req, struct cli_smb2_rename_state);
+ NTSTATUS status;
+
+ status = get_fnum_from_path_recv(subreq, &state->fnum);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_smb2_rename_fnum_send(
+ state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ state->fname_dst,
+ state->replace);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rename_renamed, req);
+}
+
+static void cli_smb2_rename_renamed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_rename_state *state = tevent_req_data(
+ req, struct cli_smb2_rename_state);
+
+ state->rename_status = cli_smb2_rename_fnum_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ subreq = cli_smb2_close_fnum_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_rename_closed, req);
+}
+
+static void cli_smb2_rename_closed(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_smb2_rename_recv(struct tevent_req *req)
+{
+ struct cli_smb2_rename_state *state = tevent_req_data(
+ req, struct cli_smb2_rename_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (!tevent_req_is_nterror(req, &status)) {
+ status = state->rename_status;
+ }
+ tevent_req_received(req);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to set an EA on a fnum.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_set_ea_fnum(struct cli_state *cli,
+ uint16_t fnum,
+ const char *ea_name,
+ const char *ea_val,
+ size_t ea_len)
+{
+ NTSTATUS status;
+ DATA_BLOB inbuf = data_blob_null;
+ size_t bloblen = 0;
+ char *ea_name_ascii = NULL;
+ size_t namelen = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* Marshall the SMB2 EA data. */
+ if (ea_len > 0xFFFF) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (!push_ascii_talloc(frame,
+ &ea_name_ascii,
+ ea_name,
+ &namelen)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (namelen < 2 || namelen > 0xFF) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ bloblen = 8 + ea_len + namelen;
+ /* Round up to a 4 byte boundary. */
+ bloblen = ((bloblen + 3)&~3);
+
+ inbuf = data_blob_talloc_zero(frame, bloblen);
+ if (inbuf.data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ /* namelen doesn't include the NULL byte. */
+ SCVAL(inbuf.data, 5, namelen - 1);
+ SSVAL(inbuf.data, 6, ea_len);
+ memcpy(inbuf.data + 8, ea_name_ascii, namelen);
+ memcpy(inbuf.data + 8 + namelen, ea_val, ea_len);
+
+ /* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
+ level 15 (SMB_FILE_FULL_EA_INFORMATION - 1000). */
+
+ status = cli_smb2_set_info_fnum(
+ cli,
+ fnum,
+ 1, /* in_info_type */
+ SMB_FILE_FULL_EA_INFORMATION - 1000, /* in_file_info_class */
+ &inbuf, /* in_input_buffer */
+ 0); /* in_additional_info */
+
+ fail:
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to set an EA on a pathname.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_set_ea_path(struct cli_state *cli,
+ const char *name,
+ const char *ea_name,
+ const char *ea_val,
+ size_t ea_len)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ status = get_fnum_from_path(cli,
+ name,
+ FILE_WRITE_EA,
+ &fnum);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = cli_set_ea_fnum(cli,
+ fnum,
+ ea_name,
+ ea_val,
+ ea_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ fail:
+
+ if (fnum != 0xffff) {
+ cli_smb2_close_fnum(cli, fnum);
+ }
+
+ cli->raw_status = status;
+
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to get an EA list on a pathname.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_get_ea_list_path(struct cli_state *cli,
+ const char *name,
+ TALLOC_CTX *ctx,
+ size_t *pnum_eas,
+ struct ea_struct **pea_array)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0xffff;
+ DATA_BLOB outbuf = data_blob_null;
+ struct ea_list *ea_list = NULL;
+ struct ea_list *eal = NULL;
+ size_t ea_count = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *pnum_eas = 0;
+ *pea_array = NULL;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ status = get_fnum_from_path(cli,
+ name,
+ FILE_READ_EA,
+ &fnum);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* getinfo on the handle with info_type SMB2_GETINFO_FILE (1),
+ level 15 (SMB_FILE_FULL_EA_INFORMATION - 1000). */
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ fnum,
+ 1, /* in_info_type */
+ SMB_FILE_FULL_EA_INFORMATION - 1000, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* Parse the reply. */
+ ea_list = read_nttrans_ea_list(ctx,
+ (const char *)outbuf.data,
+ outbuf.length);
+ if (ea_list == NULL) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto fail;
+ }
+
+ /* Convert to an array. */
+ for (eal = ea_list; eal; eal = eal->next) {
+ ea_count++;
+ }
+
+ if (ea_count) {
+ *pea_array = talloc_array(ctx, struct ea_struct, ea_count);
+ if (*pea_array == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ ea_count = 0;
+ for (eal = ea_list; eal; eal = eal->next) {
+ (*pea_array)[ea_count++] = eal->ea;
+ }
+ *pnum_eas = ea_count;
+ }
+
+ fail:
+
+ if (fnum != 0xffff) {
+ cli_smb2_close_fnum(cli, fnum);
+ }
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to get user quota.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_get_user_quota(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ NTSTATUS status;
+ DATA_BLOB inbuf = data_blob_null;
+ DATA_BLOB info_blob = data_blob_null;
+ DATA_BLOB outbuf = data_blob_null;
+ TALLOC_CTX *frame = talloc_stackframe();
+ unsigned sid_len;
+ unsigned int offset;
+ struct smb2_query_quota_info query = {0};
+ struct file_get_quota_info info = {0};
+ enum ndr_err_code err;
+ struct ndr_push *ndr_push = NULL;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ sid_len = ndr_size_dom_sid(&pqt->sid, 0);
+
+ query.return_single = 1;
+
+ info.next_entry_offset = 0;
+ info.sid_length = sid_len;
+ info.sid = pqt->sid;
+
+ err = ndr_push_struct_blob(
+ &info_blob,
+ frame,
+ &info,
+ (ndr_push_flags_fn_t)ndr_push_file_get_quota_info);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ query.sid_list_length = info_blob.length;
+ ndr_push = ndr_push_init_ctx(frame);
+ if (!ndr_push) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ err = ndr_push_smb2_query_quota_info(ndr_push,
+ NDR_SCALARS | NDR_BUFFERS,
+ &query);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ err = ndr_push_array_uint8(ndr_push, NDR_SCALARS, info_blob.data,
+ info_blob.length);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+ inbuf.data = ndr_push->data;
+ inbuf.length = ndr_push->offset;
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ quota_fnum,
+ 4, /* in_info_type */
+ 0, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ &inbuf, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (!parse_user_quota_record(outbuf.data, outbuf.length, &offset,
+ pqt)) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0, ("Got invalid FILE_QUOTA_INFORMATION in reply.\n"));
+ }
+
+fail:
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to list user quota.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_list_user_quota_step(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ int quota_fnum,
+ SMB_NTQUOTA_LIST **pqt_list,
+ bool first)
+{
+ NTSTATUS status;
+ DATA_BLOB inbuf = data_blob_null;
+ DATA_BLOB outbuf = data_blob_null;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smb2_query_quota_info info = {0};
+ enum ndr_err_code err;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto cleanup;
+ }
+
+ info.restart_scan = first ? 1 : 0;
+
+ err = ndr_push_struct_blob(
+ &inbuf,
+ frame,
+ &info,
+ (ndr_push_flags_fn_t)ndr_push_smb2_query_quota_info);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto cleanup;
+ }
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ quota_fnum,
+ 4, /* in_info_type */
+ 0, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ &inbuf, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+
+ /*
+ * safeguard against panic from calling parse_user_quota_list with
+ * NULL buffer
+ */
+ if (NT_STATUS_IS_OK(status) && outbuf.length == 0) {
+ status = NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup;
+ }
+
+ status = parse_user_quota_list(outbuf.data, outbuf.length, mem_ctx,
+ pqt_list);
+
+cleanup:
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to get file system quota.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_get_fs_quota_info(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ NTSTATUS status;
+ DATA_BLOB outbuf = data_blob_null;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto cleanup;
+ }
+
+ status = cli_smb2_query_info_fnum(
+ cli,
+ quota_fnum,
+ 2, /* in_info_type */
+ SMB_FS_QUOTA_INFORMATION - 1000, /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ frame,
+ &outbuf);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup;
+ }
+
+ status = parse_fs_quota_buffer(outbuf.data, outbuf.length, pqt);
+
+cleanup:
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to set user quota.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_set_user_quota(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_LIST *qtl)
+{
+ NTSTATUS status;
+ DATA_BLOB inbuf = data_blob_null;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto cleanup;
+ }
+
+ status = build_user_quota_buffer(qtl, 0, talloc_tos(), &inbuf, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup;
+ }
+
+ status = cli_smb2_set_info_fnum(
+ cli,
+ quota_fnum,
+ 4, /* in_info_type */
+ 0, /* in_file_info_class */
+ &inbuf, /* in_input_buffer */
+ 0); /* in_additional_info */
+cleanup:
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+NTSTATUS cli_smb2_set_fs_quota_info(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ NTSTATUS status;
+ DATA_BLOB inbuf = data_blob_null;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto cleanup;
+ }
+
+ status = build_fs_quota_buffer(talloc_tos(), pqt, &inbuf, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup;
+ }
+
+ status = cli_smb2_set_info_fnum(
+ cli,
+ quota_fnum,
+ 2, /* in_info_type */
+ SMB_FS_QUOTA_INFORMATION - 1000, /* in_file_info_class */
+ &inbuf, /* in_input_buffer */
+ 0); /* in_additional_info */
+cleanup:
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_read_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct smb2_hnd *ph;
+ uint64_t start_offset;
+ uint32_t size;
+ uint32_t received;
+ uint8_t *buf;
+};
+
+static void cli_smb2_read_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ off_t offset,
+ size_t size)
+{
+ NTSTATUS status;
+ struct tevent_req *req, *subreq;
+ struct cli_smb2_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->start_offset = (uint64_t)offset;
+ state->size = (uint32_t)size;
+ state->received = 0;
+ state->buf = NULL;
+
+ status = map_fnum_to_smb2_handle(cli,
+ fnum,
+ &state->ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_read_send(state,
+ state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ state->cli->smb2.tcon,
+ state->size,
+ state->start_offset,
+ state->ph->fid_persistent,
+ state->ph->fid_volatile,
+ 0, /* minimum_count */
+ 0); /* remaining_bytes */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_read_done, req);
+ return req;
+}
+
+static void cli_smb2_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_read_state *state = tevent_req_data(
+ req, struct cli_smb2_read_state);
+ NTSTATUS status;
+
+ status = smb2cli_read_recv(subreq, state,
+ &state->buf, &state->received);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (state->received > state->size) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_read_recv(struct tevent_req *req,
+ ssize_t *received,
+ uint8_t **rcvbuf)
+{
+ NTSTATUS status;
+ struct cli_smb2_read_state *state = tevent_req_data(
+ req, struct cli_smb2_read_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->cli->raw_status = status;
+ return status;
+ }
+ /*
+ * As in cli_read_andx_recv() rcvbuf is talloced from the request, so
+ * better make sure that you copy it away before you talloc_free(req).
+ * "rcvbuf" is NOT a talloc_ctx of its own, so do not talloc_move it!
+ */
+ *received = (ssize_t)state->received;
+ *rcvbuf = state->buf;
+ state->cli->raw_status = NT_STATUS_OK;
+ return NT_STATUS_OK;
+}
+
+struct cli_smb2_write_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct smb2_hnd *ph;
+ uint32_t flags;
+ const uint8_t *buf;
+ uint64_t offset;
+ uint32_t size;
+ uint32_t written;
+};
+
+static void cli_smb2_write_written(struct tevent_req *req);
+
+struct tevent_req *cli_smb2_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t mode,
+ const uint8_t *buf,
+ off_t offset,
+ size_t size)
+{
+ NTSTATUS status;
+ struct tevent_req *req, *subreq = NULL;
+ struct cli_smb2_write_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ /* Both SMB1 and SMB2 use 1 in the following meaning write-through. */
+ state->flags = (uint32_t)mode;
+ state->buf = buf;
+ state->offset = (uint64_t)offset;
+ state->size = (uint32_t)size;
+ state->written = 0;
+
+ status = map_fnum_to_smb2_handle(cli,
+ fnum,
+ &state->ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_write_send(state,
+ state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ state->cli->smb2.tcon,
+ state->size,
+ state->offset,
+ state->ph->fid_persistent,
+ state->ph->fid_volatile,
+ 0, /* remaining_bytes */
+ state->flags, /* flags */
+ state->buf);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_write_written, req);
+ return req;
+}
+
+static void cli_smb2_write_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_write_state *state = tevent_req_data(
+ req, struct cli_smb2_write_state);
+ NTSTATUS status;
+ uint32_t written;
+
+ status = smb2cli_write_recv(subreq, &written);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->written = written;
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_write_recv(struct tevent_req *req,
+ size_t *pwritten)
+{
+ struct cli_smb2_write_state *state = tevent_req_data(
+ req, struct cli_smb2_write_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->cli->raw_status = status;
+ tevent_req_received(req);
+ return status;
+ }
+
+ if (pwritten != NULL) {
+ *pwritten = (size_t)state->written;
+ }
+ state->cli->raw_status = NT_STATUS_OK;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 async write using an fnum.
+ This is mostly cut-and-paste from Volker's code inside
+ source3/libsmb/clireadwrite.c, adapted for SMB2.
+
+ Done this way so I can reuse all the logic inside cli_push()
+ for free :-).
+***************************************************************/
+
+struct cli_smb2_writeall_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct smb2_hnd *ph;
+ uint32_t flags;
+ const uint8_t *buf;
+ uint64_t offset;
+ uint32_t size;
+ uint32_t written;
+};
+
+static void cli_smb2_writeall_written(struct tevent_req *req);
+
+struct tevent_req *cli_smb2_writeall_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t mode,
+ const uint8_t *buf,
+ off_t offset,
+ size_t size)
+{
+ NTSTATUS status;
+ struct tevent_req *req, *subreq = NULL;
+ struct cli_smb2_writeall_state *state = NULL;
+ uint32_t to_write;
+ uint32_t max_size;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_writeall_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ /* Both SMB1 and SMB2 use 1 in the following meaning write-through. */
+ state->flags = (uint32_t)mode;
+ state->buf = buf;
+ state->offset = (uint64_t)offset;
+ state->size = (uint32_t)size;
+ state->written = 0;
+
+ status = map_fnum_to_smb2_handle(cli,
+ fnum,
+ &state->ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ to_write = state->size;
+ max_size = smb2cli_conn_max_write_size(state->cli->conn);
+ to_write = MIN(max_size, to_write);
+ ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+ if (ok) {
+ to_write = MIN(max_size, to_write);
+ }
+
+ subreq = smb2cli_write_send(state,
+ state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ state->cli->smb2.tcon,
+ to_write,
+ state->offset,
+ state->ph->fid_persistent,
+ state->ph->fid_volatile,
+ 0, /* remaining_bytes */
+ state->flags, /* flags */
+ state->buf + state->written);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_writeall_written, req);
+ return req;
+}
+
+static void cli_smb2_writeall_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_writeall_state *state = tevent_req_data(
+ req, struct cli_smb2_writeall_state);
+ NTSTATUS status;
+ uint32_t written, to_write;
+ uint32_t max_size;
+ bool ok;
+
+ status = smb2cli_write_recv(subreq, &written);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->written += written;
+
+ if (state->written > state->size) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ to_write = state->size - state->written;
+
+ if (to_write == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ max_size = smb2cli_conn_max_write_size(state->cli->conn);
+ to_write = MIN(max_size, to_write);
+ ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+ if (ok) {
+ to_write = MIN(max_size, to_write);
+ }
+
+ subreq = smb2cli_write_send(state,
+ state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ state->cli->smb2.tcon,
+ to_write,
+ state->offset + state->written,
+ state->ph->fid_persistent,
+ state->ph->fid_volatile,
+ 0, /* remaining_bytes */
+ state->flags, /* flags */
+ state->buf + state->written);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_writeall_written, req);
+}
+
+NTSTATUS cli_smb2_writeall_recv(struct tevent_req *req,
+ size_t *pwritten)
+{
+ struct cli_smb2_writeall_state *state = tevent_req_data(
+ req, struct cli_smb2_writeall_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->cli->raw_status = status;
+ return status;
+ }
+ if (pwritten != NULL) {
+ *pwritten = (size_t)state->written;
+ }
+ state->cli->raw_status = NT_STATUS_OK;
+ return NT_STATUS_OK;
+}
+
+struct cli_smb2_splice_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct smb2_hnd *src_ph;
+ struct smb2_hnd *dst_ph;
+ int (*splice_cb)(off_t n, void *priv);
+ void *priv;
+ off_t written;
+ off_t size;
+ off_t src_offset;
+ off_t dst_offset;
+ bool resized;
+ struct req_resume_key_rsp resume_rsp;
+ struct srv_copychunk_copy cc_copy;
+};
+
+static void cli_splice_copychunk_send(struct cli_smb2_splice_state *state,
+ struct tevent_req *req);
+
+static void cli_splice_copychunk_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_splice_state *state =
+ tevent_req_data(req,
+ struct cli_smb2_splice_state);
+ struct smbXcli_conn *conn = state->cli->conn;
+ DATA_BLOB out_input_buffer = data_blob_null;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ struct srv_copychunk_rsp cc_copy_rsp;
+ enum ndr_err_code ndr_ret;
+ NTSTATUS status;
+
+ status = smb2cli_ioctl_recv(subreq, state,
+ &out_input_buffer,
+ &out_output_buffer);
+ TALLOC_FREE(subreq);
+ if ((!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) ||
+ state->resized) && tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(&out_output_buffer, state, &cc_copy_rsp,
+ (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall copy chunk rsp\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ uint32_t max_chunks = MIN(cc_copy_rsp.chunks_written,
+ cc_copy_rsp.total_bytes_written / cc_copy_rsp.chunk_bytes_written);
+ if ((cc_copy_rsp.chunk_bytes_written > smb2cli_conn_cc_chunk_len(conn) ||
+ max_chunks > smb2cli_conn_cc_max_chunks(conn)) &&
+ tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->resized = true;
+ smb2cli_conn_set_cc_chunk_len(conn, cc_copy_rsp.chunk_bytes_written);
+ smb2cli_conn_set_cc_max_chunks(conn, max_chunks);
+ } else {
+ if ((state->src_offset > INT64_MAX - cc_copy_rsp.total_bytes_written) ||
+ (state->dst_offset > INT64_MAX - cc_copy_rsp.total_bytes_written) ||
+ (state->written > INT64_MAX - cc_copy_rsp.total_bytes_written)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_TOO_LARGE);
+ return;
+ }
+ state->src_offset += cc_copy_rsp.total_bytes_written;
+ state->dst_offset += cc_copy_rsp.total_bytes_written;
+ state->written += cc_copy_rsp.total_bytes_written;
+ if (!state->splice_cb(state->written, state->priv)) {
+ tevent_req_nterror(req, NT_STATUS_CANCELLED);
+ return;
+ }
+ }
+
+ cli_splice_copychunk_send(state, req);
+}
+
+static void cli_splice_copychunk_send(struct cli_smb2_splice_state *state,
+ struct tevent_req *req)
+{
+ struct tevent_req *subreq;
+ enum ndr_err_code ndr_ret;
+ struct smbXcli_conn *conn = state->cli->conn;
+ struct srv_copychunk_copy *cc_copy = &state->cc_copy;
+ off_t src_offset = state->src_offset;
+ off_t dst_offset = state->dst_offset;
+ uint32_t req_len = MIN(smb2cli_conn_cc_chunk_len(conn) * smb2cli_conn_cc_max_chunks(conn),
+ state->size - state->written);
+ DATA_BLOB in_input_buffer = data_blob_null;
+ DATA_BLOB in_output_buffer = data_blob_null;
+
+ if (state->size - state->written == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ cc_copy->chunk_count = 0;
+ while (req_len) {
+ cc_copy->chunks[cc_copy->chunk_count].source_off = src_offset;
+ cc_copy->chunks[cc_copy->chunk_count].target_off = dst_offset;
+ cc_copy->chunks[cc_copy->chunk_count].length = MIN(req_len,
+ smb2cli_conn_cc_chunk_len(conn));
+ if (req_len < cc_copy->chunks[cc_copy->chunk_count].length) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ req_len -= cc_copy->chunks[cc_copy->chunk_count].length;
+ if ((src_offset > INT64_MAX - cc_copy->chunks[cc_copy->chunk_count].length) ||
+ (dst_offset > INT64_MAX - cc_copy->chunks[cc_copy->chunk_count].length)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_TOO_LARGE);
+ return;
+ }
+ src_offset += cc_copy->chunks[cc_copy->chunk_count].length;
+ dst_offset += cc_copy->chunks[cc_copy->chunk_count].length;
+ cc_copy->chunk_count++;
+ }
+
+ ndr_ret = ndr_push_struct_blob(&in_input_buffer, state, cc_copy,
+ (ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to marshall copy chunk req\n"));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ subreq = smb2cli_ioctl_send(state, state->ev, state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ state->cli->smb2.tcon,
+ state->dst_ph->fid_persistent, /* in_fid_persistent */
+ state->dst_ph->fid_volatile, /* in_fid_volatile */
+ FSCTL_SRV_COPYCHUNK_WRITE,
+ 0, /* in_max_input_length */
+ &in_input_buffer,
+ 12, /* in_max_output_length */
+ &in_output_buffer,
+ SMB2_IOCTL_FLAG_IS_FSCTL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_splice_copychunk_done,
+ req);
+}
+
+static void cli_splice_key_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_splice_state *state =
+ tevent_req_data(req,
+ struct cli_smb2_splice_state);
+ enum ndr_err_code ndr_ret;
+ NTSTATUS status;
+
+ DATA_BLOB out_input_buffer = data_blob_null;
+ DATA_BLOB out_output_buffer = data_blob_null;
+
+ status = smb2cli_ioctl_recv(subreq, state,
+ &out_input_buffer,
+ &out_output_buffer);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(&out_output_buffer,
+ state, &state->resume_rsp,
+ (ndr_pull_flags_fn_t)ndr_pull_req_resume_key_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall resume key rsp\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ memcpy(&state->cc_copy.source_key,
+ &state->resume_rsp.resume_key,
+ sizeof state->resume_rsp.resume_key);
+
+ cli_splice_copychunk_send(state, req);
+}
+
+struct tevent_req *cli_smb2_splice_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t src_fnum, uint16_t dst_fnum,
+ off_t size, off_t src_offset, off_t dst_offset,
+ int (*splice_cb)(off_t n, void *priv),
+ void *priv)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct cli_smb2_splice_state *state;
+ NTSTATUS status;
+ DATA_BLOB in_input_buffer = data_blob_null;
+ DATA_BLOB in_output_buffer = data_blob_null;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_splice_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->ev = ev;
+ state->splice_cb = splice_cb;
+ state->priv = priv;
+ state->size = size;
+ state->written = 0;
+ state->src_offset = src_offset;
+ state->dst_offset = dst_offset;
+ state->cc_copy.chunks = talloc_array(state,
+ struct srv_copychunk,
+ smb2cli_conn_cc_max_chunks(cli->conn));
+ if (state->cc_copy.chunks == NULL) {
+ return NULL;
+ }
+
+ status = map_fnum_to_smb2_handle(cli, src_fnum, &state->src_ph);
+ if (tevent_req_nterror(req, status))
+ return tevent_req_post(req, ev);
+
+ status = map_fnum_to_smb2_handle(cli, dst_fnum, &state->dst_ph);
+ if (tevent_req_nterror(req, status))
+ return tevent_req_post(req, ev);
+
+ subreq = smb2cli_ioctl_send(state, ev, cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ state->src_ph->fid_persistent, /* in_fid_persistent */
+ state->src_ph->fid_volatile, /* in_fid_volatile */
+ FSCTL_SRV_REQUEST_RESUME_KEY,
+ 0, /* in_max_input_length */
+ &in_input_buffer,
+ 32, /* in_max_output_length */
+ &in_output_buffer,
+ SMB2_IOCTL_FLAG_IS_FSCTL);
+ if (tevent_req_nomem(subreq, req)) {
+ return NULL;
+ }
+ tevent_req_set_callback(subreq,
+ cli_splice_key_done,
+ req);
+
+ return req;
+}
+
+NTSTATUS cli_smb2_splice_recv(struct tevent_req *req, off_t *written)
+{
+ struct cli_smb2_splice_state *state = tevent_req_data(
+ req, struct cli_smb2_splice_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->cli->raw_status = status;
+ tevent_req_received(req);
+ return status;
+ }
+ if (written != NULL) {
+ *written = state->written;
+ }
+ state->cli->raw_status = NT_STATUS_OK;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/***************************************************************
+ SMB2 enum shadow copy data.
+***************************************************************/
+
+struct cli_smb2_shadow_copy_data_fnum_state {
+ struct cli_state *cli;
+ uint16_t fnum;
+ struct smb2_hnd *ph;
+ DATA_BLOB out_input_buffer;
+ DATA_BLOB out_output_buffer;
+};
+
+static void cli_smb2_shadow_copy_data_fnum_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb2_shadow_copy_data_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool get_names)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_smb2_shadow_copy_data_fnum_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb2_shadow_copy_data_fnum_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->cli = cli;
+ state->fnum = fnum;
+
+ status = map_fnum_to_smb2_handle(cli, fnum, &state->ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * TODO. Under SMB2 we should send a zero max_output_length
+ * ioctl to get the required size, then send another ioctl
+ * to get the data, but the current SMB1 implementation just
+ * does one roundtrip with a 64K buffer size. Do the same
+ * for now. JRA.
+ */
+
+ subreq = smb2cli_ioctl_send(state, ev, state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ state->cli->smb2.tcon,
+ state->ph->fid_persistent, /* in_fid_persistent */
+ state->ph->fid_volatile, /* in_fid_volatile */
+ FSCTL_GET_SHADOW_COPY_DATA,
+ 0, /* in_max_input_length */
+ NULL, /* in_input_buffer */
+ get_names ?
+ CLI_BUFFER_SIZE : 16, /* in_max_output_length */
+ NULL, /* in_output_buffer */
+ SMB2_IOCTL_FLAG_IS_FSCTL);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_smb2_shadow_copy_data_fnum_done,
+ req);
+
+ return req;
+}
+
+static void cli_smb2_shadow_copy_data_fnum_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_shadow_copy_data_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_shadow_copy_data_fnum_state);
+ NTSTATUS status;
+
+ status = smb2cli_ioctl_recv(subreq, state,
+ &state->out_input_buffer,
+ &state->out_output_buffer);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_smb2_shadow_copy_data_fnum_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ bool get_names,
+ char ***pnames,
+ int *pnum_names)
+{
+ struct cli_smb2_shadow_copy_data_fnum_state *state = tevent_req_data(
+ req, struct cli_smb2_shadow_copy_data_fnum_state);
+ char **names = NULL;
+ uint32_t num_names = 0;
+ uint32_t num_names_returned = 0;
+ uint32_t dlength = 0;
+ uint32_t i;
+ uint8_t *endp = NULL;
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (state->out_output_buffer.length < 16) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ num_names = IVAL(state->out_output_buffer.data, 0);
+ num_names_returned = IVAL(state->out_output_buffer.data, 4);
+ dlength = IVAL(state->out_output_buffer.data, 8);
+
+ if (num_names > 0x7FFFFFFF) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ if (get_names == false) {
+ *pnum_names = (int)num_names;
+ return NT_STATUS_OK;
+ }
+ if (num_names != num_names_returned) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (dlength + 12 < 12) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ /*
+ * NB. The below is an allowable return if there are
+ * more snapshots than the buffer size we told the
+ * server we can receive. We currently don't support
+ * this.
+ */
+ if (dlength + 12 > state->out_output_buffer.length) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (state->out_output_buffer.length +
+ (2 * sizeof(SHADOW_COPY_LABEL)) <
+ state->out_output_buffer.length) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ names = talloc_array(mem_ctx, char *, num_names_returned);
+ if (names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ endp = state->out_output_buffer.data +
+ state->out_output_buffer.length;
+
+ for (i=0; i<num_names_returned; i++) {
+ bool ret;
+ uint8_t *src;
+ size_t converted_size;
+
+ src = state->out_output_buffer.data + 12 +
+ (i * 2 * sizeof(SHADOW_COPY_LABEL));
+
+ if (src + (2 * sizeof(SHADOW_COPY_LABEL)) > endp) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ ret = convert_string_talloc(
+ names, CH_UTF16LE, CH_UNIX,
+ src, 2 * sizeof(SHADOW_COPY_LABEL),
+ &names[i], &converted_size);
+ if (!ret) {
+ TALLOC_FREE(names);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ }
+ *pnum_names = num_names;
+ *pnames = names;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_smb2_shadow_copy_data(TALLOC_CTX *mem_ctx,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool get_names,
+ char ***pnames,
+ int *pnum_names)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_shadow_copy_data_fnum_send(frame,
+ ev,
+ cli,
+ fnum,
+ get_names);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb2_shadow_copy_data_fnum_recv(req,
+ mem_ctx,
+ get_names,
+ pnames,
+ pnum_names);
+ fail:
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/***************************************************************
+ Wrapper that allows SMB2 to truncate a file.
+ Synchronous only.
+***************************************************************/
+
+NTSTATUS cli_smb2_ftruncate(struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t newsize)
+{
+ NTSTATUS status;
+ uint8_t buf[8] = {0};
+ DATA_BLOB inbuf = { .data = buf, .length = sizeof(buf) };
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ SBVAL(buf, 0, newsize);
+
+ /* setinfo on the handle with info_type SMB2_SETINFO_FILE (1),
+ level 20 (SMB_FILE_END_OF_FILE_INFORMATION - 1000). */
+
+ status = cli_smb2_set_info_fnum(
+ cli,
+ fnum,
+ 1, /* in_info_type */
+ SMB_FILE_END_OF_FILE_INFORMATION-1000, /* in_file_info_class */
+ &inbuf, /* in_input_buffer */
+ 0);
+
+ fail:
+
+ cli->raw_status = status;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_notify_state {
+ struct tevent_req *subreq;
+ struct notify_change *changes;
+ size_t num_changes;
+};
+
+static void cli_smb2_notify_done(struct tevent_req *subreq);
+static bool cli_smb2_notify_cancel(struct tevent_req *req);
+
+struct tevent_req *cli_smb2_notify_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t buffer_size,
+ uint32_t completion_filter,
+ bool recursive)
+{
+ struct tevent_req *req = NULL;
+ struct cli_smb2_notify_state *state = NULL;
+ struct smb2_hnd *ph = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb2_notify_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = map_fnum_to_smb2_handle(cli, fnum, &ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->subreq = smb2cli_notify_send(
+ state,
+ ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ buffer_size,
+ ph->fid_persistent,
+ ph->fid_volatile,
+ completion_filter,
+ recursive);
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreq, cli_smb2_notify_done, req);
+ tevent_req_set_cancel_fn(req, cli_smb2_notify_cancel);
+ return req;
+}
+
+static bool cli_smb2_notify_cancel(struct tevent_req *req)
+{
+ struct cli_smb2_notify_state *state = tevent_req_data(
+ req, struct cli_smb2_notify_state);
+ bool ok;
+
+ ok = tevent_req_cancel(state->subreq);
+ return ok;
+}
+
+static void cli_smb2_notify_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_notify_state *state = tevent_req_data(
+ req, struct cli_smb2_notify_state);
+ uint8_t *base;
+ uint32_t len;
+ uint32_t ofs;
+ NTSTATUS status;
+
+ status = smb2cli_notify_recv(subreq, state, &base, &len);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ofs = 0;
+
+ while (len - ofs >= 12) {
+ struct notify_change *tmp;
+ struct notify_change *c;
+ uint32_t next_ofs = IVAL(base, ofs);
+ uint32_t file_name_length = IVAL(base, ofs+8);
+ size_t namelen;
+ bool ok;
+
+ tmp = talloc_realloc(
+ state,
+ state->changes,
+ struct notify_change,
+ state->num_changes + 1);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ state->changes = tmp;
+ c = &state->changes[state->num_changes];
+ state->num_changes += 1;
+
+ if (smb_buffer_oob(len, ofs, next_ofs) ||
+ smb_buffer_oob(len, ofs+12, file_name_length)) {
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ c->action = IVAL(base, ofs+4);
+
+ ok = convert_string_talloc(
+ state->changes,
+ CH_UTF16LE,
+ CH_UNIX,
+ base + ofs + 12,
+ file_name_length,
+ &c->name,
+ &namelen);
+ if (!ok) {
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (next_ofs == 0) {
+ break;
+ }
+ ofs += next_ofs;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb2_notify_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct notify_change **pchanges,
+ uint32_t *pnum_changes)
+{
+ struct cli_smb2_notify_state *state = tevent_req_data(
+ req, struct cli_smb2_notify_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pchanges = talloc_move(mem_ctx, &state->changes);
+ *pnum_changes = state->num_changes;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_smb2_notify(struct cli_state *cli, uint16_t fnum,
+ uint32_t buffer_size, uint32_t completion_filter,
+ bool recursive, TALLOC_CTX *mem_ctx,
+ struct notify_change **pchanges,
+ uint32_t *pnum_changes)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb2_notify_send(
+ frame,
+ ev,
+ cli,
+ fnum,
+ buffer_size,
+ completion_filter,
+ recursive);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb2_notify_recv(req, mem_ctx, pchanges, pnum_changes);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb2_fsctl_state {
+ DATA_BLOB out;
+};
+
+static void cli_smb2_fsctl_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb2_fsctl_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t ctl_code,
+ const DATA_BLOB *in,
+ uint32_t max_out)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_fsctl_state *state = NULL;
+ struct smb2_hnd *ph = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb2_fsctl_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = map_fnum_to_smb2_handle(cli, fnum, &ph);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_ioctl_send(
+ state,
+ ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ ph->fid_persistent,
+ ph->fid_volatile,
+ ctl_code,
+ 0, /* in_max_input_length */
+ in,
+ max_out,
+ NULL,
+ SMB2_IOCTL_FLAG_IS_FSCTL);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_fsctl_done, req);
+ return req;
+}
+
+static void cli_smb2_fsctl_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_fsctl_state *state = tevent_req_data(
+ req, struct cli_smb2_fsctl_state);
+ NTSTATUS status;
+
+ status = smb2cli_ioctl_recv(subreq, state, NULL, &state->out);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_smb2_fsctl_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *out)
+{
+ struct cli_smb2_fsctl_state *state = tevent_req_data(
+ req, struct cli_smb2_fsctl_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ if (state->out.length == 0) {
+ *out = (DATA_BLOB) { .data = NULL, };
+ } else {
+ /*
+ * Can't use talloc_move() here, the outblobs from
+ * smb2cli_ioctl_recv() are not standalone talloc
+ * objects but just peek into the larger buffers
+ * received, hanging off "state".
+ */
+ *out = data_blob_talloc(
+ mem_ctx, state->out.data, state->out.length);
+ if (out->data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/cli_smb2_fnum.h b/source3/libsmb/cli_smb2_fnum.h
new file mode 100644
index 0000000..abac569
--- /dev/null
+++ b/source3/libsmb/cli_smb2_fnum.h
@@ -0,0 +1,324 @@
+/*
+ Unix SMB/CIFS implementation.
+ smb2 wrapper client routines
+ Copyright (C) Jeremy Allison 2013
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SMB2CLI_FNUM_H__
+#define __SMB2CLI_FNUM_H__
+
+struct smbXcli_conn;
+struct smbXcli_session;
+struct cli_state;
+struct file_info;
+struct symlink_reparse_struct;
+
+struct cli_smb2_create_flags {
+ bool batch_oplock:1;
+ bool exclusive_oplock:1;
+};
+
+struct tevent_req *cli_smb2_create_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ struct cli_smb2_create_flags create_flags,
+ uint32_t impersonation_level,
+ uint32_t desired_access,
+ uint32_t file_attributes,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ const struct smb2_create_blobs *in_cblobs);
+NTSTATUS cli_smb2_create_fnum_recv(
+ struct tevent_req *req,
+ uint16_t *pfnum,
+ struct smb_create_returns *cr,
+ TALLOC_CTX *mem_ctx,
+ struct smb2_create_blobs *out_cblobs,
+ struct symlink_reparse_struct **symlink);
+NTSTATUS cli_smb2_create_fnum(
+ struct cli_state *cli,
+ const char *fname,
+ struct cli_smb2_create_flags create_flags,
+ uint32_t impersonation_level,
+ uint32_t desired_access,
+ uint32_t file_attributes,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ const struct smb2_create_blobs *in_cblobs,
+ uint16_t *pfid,
+ struct smb_create_returns *cr,
+ TALLOC_CTX *mem_ctx,
+ struct smb2_create_blobs *out_cblobs);
+
+struct tevent_req *cli_smb2_close_fnum_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t flags);
+NTSTATUS cli_smb2_close_fnum_recv(struct tevent_req *req);
+NTSTATUS cli_smb2_close_fnum(struct cli_state *cli, uint16_t fnum);
+struct tevent_req *cli_smb2_delete_on_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool flag);
+NTSTATUS cli_smb2_delete_on_close_recv(struct tevent_req *req);
+NTSTATUS cli_smb2_delete_on_close(struct cli_state *cli, uint16_t fnum, bool flag);
+struct tevent_req *cli_smb2_mkdir_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *name);
+NTSTATUS cli_smb2_mkdir_recv(struct tevent_req *req);
+struct tevent_req *cli_smb2_rmdir_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *dname,
+ const struct smb2_create_blobs *in_cblobs);
+NTSTATUS cli_smb2_rmdir_recv(struct tevent_req *req);
+struct tevent_req *cli_smb2_unlink_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ const struct smb2_create_blobs *in_cblobs);
+NTSTATUS cli_smb2_unlink_recv(struct tevent_req *req);
+struct tevent_req *cli_smb2_list_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *pathname,
+ unsigned int info_level);
+NTSTATUS cli_smb2_list_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct file_info **pfinfo);
+NTSTATUS cli_smb2_qpathinfo_basic(struct cli_state *cli,
+ const char *name,
+ SMB_STRUCT_STAT *sbuf,
+ uint32_t *attributes);
+struct tevent_req *cli_smb2_qpathinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint16_t level,
+ uint32_t min_rdata,
+ uint32_t max_rdata);
+NTSTATUS cli_smb2_qpathinfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **rdata,
+ uint32_t *num_rdata);
+struct tevent_req *cli_smb2_query_info_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ uint32_t in_max_output_length,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info,
+ uint32_t in_flags);
+NTSTATUS cli_smb2_query_info_fnum_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *outbuf);
+struct tevent_req *cli_smb2_set_info_fnum_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info);
+NTSTATUS cli_smb2_set_info_fnum_recv(struct tevent_req *req);
+NTSTATUS cli_smb2_set_info_fnum(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info);
+NTSTATUS cli_smb2_query_info_fnum(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t in_info_type,
+ uint8_t in_info_class,
+ uint32_t in_max_output_length,
+ const DATA_BLOB *in_input_buffer,
+ uint32_t in_additional_info,
+ uint32_t in_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *outbuf);
+NTSTATUS cli_smb2_setpathinfo(struct cli_state *cli,
+ const char *name,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ const DATA_BLOB *p_in_data);
+NTSTATUS cli_smb2_setatr(struct cli_state *cli,
+ const char *fname,
+ uint32_t attr,
+ time_t mtime);
+NTSTATUS cli_smb2_setattrE(struct cli_state *cli,
+ uint16_t fnum,
+ time_t change_time,
+ time_t access_time,
+ time_t write_time);
+NTSTATUS cli_smb2_dskattr(struct cli_state *cli,
+ const char *path,
+ uint64_t *bsize,
+ uint64_t *total,
+ uint64_t *avail);
+NTSTATUS cli_smb2_get_fs_attr_info(struct cli_state *cli, uint32_t *fs_attr);
+NTSTATUS cli_smb2_get_fs_full_size_info(struct cli_state *cli,
+ uint64_t *total_allocation_units,
+ uint64_t *caller_allocation_units,
+ uint64_t *actual_allocation_units,
+ uint64_t *sectors_per_allocation_unit,
+ uint64_t *bytes_per_sector);
+NTSTATUS cli_smb2_get_fs_volume_info(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ char **_volume_name,
+ uint32_t *pserial_number,
+ time_t *pdate);
+struct tevent_req *cli_smb2_query_mxac_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_smb2_query_mxac_recv(struct tevent_req *req,
+ uint32_t *mxac);
+NTSTATUS cli_smb2_query_mxac(struct cli_state *cli,
+ const char *fname,
+ uint32_t *mxac);
+struct tevent_req *cli_smb2_rename_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace);
+NTSTATUS cli_smb2_rename_recv(struct tevent_req *req);
+NTSTATUS cli_smb2_set_ea_fnum(struct cli_state *cli,
+ uint16_t fnum,
+ const char *ea_name,
+ const char *ea_val,
+ size_t ea_len);
+NTSTATUS cli_smb2_get_ea_list_path(struct cli_state *cli,
+ const char *name,
+ TALLOC_CTX *ctx,
+ size_t *pnum_eas,
+ struct ea_struct **pea_list);
+NTSTATUS cli_smb2_set_ea_path(struct cli_state *cli,
+ const char *name,
+ const char *ea_name,
+ const char *ea_val,
+ size_t ea_len);
+NTSTATUS cli_smb2_get_user_quota(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt);
+NTSTATUS cli_smb2_list_user_quota_step(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ int quota_fnum,
+ SMB_NTQUOTA_LIST **pqt_list,
+ bool first);
+NTSTATUS cli_smb2_get_fs_quota_info(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt);
+NTSTATUS cli_smb2_set_user_quota(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_LIST *qtl);
+NTSTATUS cli_smb2_set_fs_quota_info(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt);
+struct tevent_req *cli_smb2_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ off_t offset,
+ size_t size);
+NTSTATUS cli_smb2_read_recv(struct tevent_req *req,
+ ssize_t *received,
+ uint8_t **rcvbuf);
+struct tevent_req *cli_smb2_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t mode,
+ const uint8_t *buf,
+ off_t offset,
+ size_t size);
+NTSTATUS cli_smb2_write_recv(struct tevent_req *req,
+ size_t *pwritten);
+struct tevent_req *cli_smb2_writeall_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t mode,
+ const uint8_t *buf,
+ off_t offset,
+ size_t size);
+NTSTATUS cli_smb2_writeall_recv(struct tevent_req *req,
+ size_t *pwritten);
+struct tevent_req *cli_smb2_splice_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t src_fnum, uint16_t dst_fnum,
+ off_t size, off_t src_offset, off_t dst_offset,
+ int (*splice_cb)(off_t n, void *priv), void *priv);
+NTSTATUS cli_smb2_splice_recv(struct tevent_req *req, off_t *written);
+NTSTATUS cli_smb2_shadow_copy_data(TALLOC_CTX *mem_ctx,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool get_names,
+ char ***pnames,
+ int *pnum_names);
+NTSTATUS cli_smb2_ftruncate(struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t newsize);
+struct tevent_req *cli_smb2_notify_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t buffer_size,
+ uint32_t completion_filter,
+ bool recursive);
+NTSTATUS cli_smb2_notify_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct notify_change **pchanges,
+ uint32_t *pnum_changes);
+NTSTATUS cli_smb2_notify(struct cli_state *cli, uint16_t fnum,
+ uint32_t buffer_size, uint32_t completion_filter,
+ bool recursive, TALLOC_CTX *mem_ctx,
+ struct notify_change **pchanges,
+ uint32_t *pnum_changes);
+
+struct tevent_req *cli_smb2_fsctl_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t ctl_code,
+ const DATA_BLOB *in,
+ uint32_t max_out);
+NTSTATUS cli_smb2_fsctl_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *out);
+
+#endif /* __SMB2CLI_FNUM_H__ */
diff --git a/source3/libsmb/cliconnect.c b/source3/libsmb/cliconnect.c
new file mode 100644
index 0000000..61e3504
--- /dev/null
+++ b/source3/libsmb/cliconnect.c
@@ -0,0 +1,4026 @@
+/*
+ Unix SMB/CIFS implementation.
+ client connect/disconnect routines
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Andrew Bartlett 2001-2003
+ Copyright (C) Volker Lendecke 2011
+ Copyright (C) Jeremy Allison 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/namequery.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../libcli/auth/spnego.h"
+#include "smb_krb5.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth/ntlmssp/ntlmssp.h"
+#include "auth_generic.h"
+#include "libads/kerberos_proto.h"
+#include "krb5_env.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "libsmb/nmblib.h"
+#include "librpc/ndr/libndr.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "../libcli/smb/smb_seal.h"
+#include "lib/param/param.h"
+#include "../libcli/smb/smb2_negotiate_context.h"
+
+#define STAR_SMBSERVER "*SMBSERVER"
+
+static char *cli_session_setup_get_account(TALLOC_CTX *mem_ctx,
+ const char *principal);
+
+struct cli_credentials *cli_session_creds_init(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *domain,
+ const char *realm,
+ const char *password,
+ bool use_kerberos,
+ bool fallback_after_kerberos,
+ bool use_ccache,
+ bool password_is_nt_hash)
+{
+ struct loadparm_context *lp_ctx = NULL;
+ struct cli_credentials *creds = NULL;
+ const char *principal = NULL;
+ char *tmp = NULL;
+ char *p = NULL;
+ bool ok;
+
+ creds = cli_credentials_init(mem_ctx);
+ if (creds == NULL) {
+ return NULL;
+ }
+
+ lp_ctx = loadparm_init_s3(creds, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ goto fail;
+ }
+ ok = cli_credentials_set_conf(creds, lp_ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (username == NULL) {
+ username = "";
+ }
+
+ if (strlen(username) == 0) {
+ if (password != NULL && strlen(password) == 0) {
+ /*
+ * some callers pass "" as no password
+ *
+ * gensec only handles NULL as no password.
+ */
+ password = NULL;
+ }
+ if (password == NULL) {
+ cli_credentials_set_anonymous(creds);
+ return creds;
+ }
+ }
+
+ tmp = talloc_strdup(creds, username);
+ if (tmp == NULL) {
+ goto fail;
+ }
+ username = tmp;
+
+ /* allow for workgroups as part of the username */
+ if ((p = strchr_m(tmp, '\\')) ||
+ (p = strchr_m(tmp, '/')) ||
+ (p = strchr_m(tmp, *lp_winbind_separator()))) {
+ *p = 0;
+ username = p + 1;
+ domain = tmp;
+ }
+
+ principal = username;
+ username = cli_session_setup_get_account(creds, principal);
+ if (username == NULL) {
+ goto fail;
+ }
+ ok = strequal(username, principal);
+ if (ok) {
+ /*
+ * Ok still the same, so it's not a principal
+ */
+ principal = NULL;
+ }
+
+ if (use_kerberos && fallback_after_kerberos) {
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DESIRED,
+ CRED_SPECIFIED);
+ } else if (use_kerberos) {
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+ } else {
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+ }
+
+ if (use_ccache) {
+ uint32_t features;
+
+ features = cli_credentials_get_gensec_features(creds);
+ features |= GENSEC_FEATURE_NTLM_CCACHE;
+ cli_credentials_set_gensec_features(creds,
+ features,
+ CRED_SPECIFIED);
+
+ if (password != NULL && strlen(password) == 0) {
+ /*
+ * some callers pass "" as no password
+ *
+ * GENSEC_FEATURE_NTLM_CCACHE only handles
+ * NULL as no password.
+ */
+ password = NULL;
+ }
+ }
+
+ ok = cli_credentials_set_username(creds,
+ username,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (domain != NULL) {
+ ok = cli_credentials_set_domain(creds,
+ domain,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ if (principal != NULL) {
+ ok = cli_credentials_set_principal(creds,
+ principal,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ if (realm != NULL) {
+ ok = cli_credentials_set_realm(creds,
+ realm,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ if (password != NULL && strlen(password) > 0) {
+ if (password_is_nt_hash) {
+ struct samr_Password nt_hash;
+ size_t converted;
+
+ converted = strhex_to_str((char *)nt_hash.hash,
+ sizeof(nt_hash.hash),
+ password,
+ strlen(password));
+ if (converted != sizeof(nt_hash.hash)) {
+ goto fail;
+ }
+
+ ok = cli_credentials_set_nt_hash(creds,
+ &nt_hash,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ } else {
+ ok = cli_credentials_set_password(creds,
+ password,
+ CRED_SPECIFIED);
+ if (!ok) {
+ goto fail;
+ }
+ }
+ }
+
+ return creds;
+fail:
+ TALLOC_FREE(creds);
+ return NULL;
+}
+
+NTSTATUS cli_session_creds_prepare_krb5(struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *user_principal = NULL;
+ const char *user_account = NULL;
+ const char *user_domain = NULL;
+ const char *pass = NULL;
+ char *canon_principal = NULL;
+ char *canon_realm = NULL;
+ const char *target_hostname = NULL;
+ enum credentials_use_kerberos krb5_state;
+ bool try_kerberos = false;
+ bool need_kinit = false;
+ bool auth_requested = true;
+ int ret;
+ bool ok;
+
+ target_hostname = smbXcli_conn_remote_name(cli->conn);
+
+ auth_requested = cli_credentials_authentication_requested(creds);
+ if (auth_requested) {
+ errno = 0;
+ user_principal = cli_credentials_get_principal(creds, frame);
+ if (errno != 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ user_account = cli_credentials_get_username(creds);
+ user_domain = cli_credentials_get_domain(creds);
+ pass = cli_credentials_get_password(creds);
+
+ krb5_state = cli_credentials_get_kerberos_state(creds);
+
+ if (krb5_state != CRED_USE_KERBEROS_DISABLED) {
+ try_kerberos = true;
+ }
+
+ if (user_principal == NULL) {
+ try_kerberos = false;
+ }
+
+ if (target_hostname == NULL) {
+ try_kerberos = false;
+ } else if (is_ipaddress(target_hostname)) {
+ try_kerberos = false;
+ } else if (strequal(target_hostname, "localhost")) {
+ try_kerberos = false;
+ } else if (strequal(target_hostname, STAR_SMBSERVER)) {
+ try_kerberos = false;
+ } else if (!auth_requested) {
+ try_kerberos = false;
+ }
+
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED && !try_kerberos) {
+ DEBUG(0, ("Kerberos auth with '%s' (%s\\%s) to access "
+ "'%s' not possible\n",
+ user_principal, user_domain, user_account,
+ target_hostname));
+ TALLOC_FREE(frame);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (pass == NULL || strlen(pass) == 0) {
+ need_kinit = false;
+ } else if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ need_kinit = try_kerberos;
+ } else {
+ need_kinit = try_kerberos;
+ }
+
+ if (!need_kinit) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ DBG_INFO("Doing kinit for %s to access %s\n",
+ user_principal, target_hostname);
+
+ /*
+ * TODO: This should be done within the gensec layer
+ * only if required!
+ */
+ setenv(KRB5_ENV_CCNAME, "MEMORY:cliconnect", 1);
+ ret = kerberos_kinit_password_ext(user_principal,
+ pass,
+ 0,
+ 0,
+ 0,
+ NULL,
+ false,
+ false,
+ 0,
+ frame,
+ &canon_principal,
+ &canon_realm,
+ NULL);
+ if (ret != 0) {
+ int dbglvl = DBGLVL_NOTICE;
+
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ dbglvl = DBGLVL_ERR;
+ }
+
+ DEBUG(dbglvl, ("Kinit for %s to access %s failed: %s\n",
+ user_principal, target_hostname,
+ error_message(ret)));
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ TALLOC_FREE(frame);
+ return krb5_to_nt_status(ret);
+ }
+
+ /*
+ * Ignore the error and hope that NTLM will work
+ */
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ ok = cli_credentials_set_principal(creds,
+ canon_principal,
+ CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = cli_credentials_set_realm(creds,
+ canon_realm,
+ CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("Successfully authenticated as %s (%s) to access %s using "
+ "Kerberos\n",
+ user_principal,
+ canon_principal,
+ target_hostname);
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cli_state_update_after_sesssetup(struct cli_state *cli,
+ const char *native_os,
+ const char *native_lm,
+ const char *primary_domain)
+{
+#define _VALID_STR(p) ((p) != NULL && (p)[0] != '\0')
+
+ if (!_VALID_STR(cli->server_os) && _VALID_STR(native_os)) {
+ cli->server_os = talloc_strdup(cli, native_os);
+ if (cli->server_os == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!_VALID_STR(cli->server_type) && _VALID_STR(native_lm)) {
+ cli->server_type = talloc_strdup(cli, native_lm);
+ if (cli->server_type == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!_VALID_STR(cli->server_domain) && _VALID_STR(primary_domain)) {
+ cli->server_domain = talloc_strdup(cli, primary_domain);
+ if (cli->server_domain == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+#undef _VALID_STRING
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Utility function to ensure we always return at least
+ a valid char * pointer to an empty string for the
+ cli->server_os, cli->server_type and cli->server_domain
+ strings.
+*******************************************************/
+
+static NTSTATUS smb_bytes_talloc_string(TALLOC_CTX *mem_ctx,
+ const uint8_t *hdr,
+ char **dest,
+ uint8_t *src,
+ size_t srclen,
+ ssize_t *destlen)
+{
+ *destlen = pull_string_talloc(mem_ctx,
+ (const char *)hdr,
+ SVAL(hdr, HDR_FLG2),
+ dest,
+ (char *)src,
+ srclen,
+ STR_TERMINATE);
+ if (*destlen == -1) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (*dest == NULL) {
+ *dest = talloc_strdup(mem_ctx, "");
+ if (*dest == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Work out suitable capabilities to offer the server.
+****************************************************************************/
+
+static uint32_t cli_session_setup_capabilities(struct cli_state *cli,
+ uint32_t sesssetup_capabilities)
+{
+ uint32_t client_capabilities = smb1cli_conn_capabilities(cli->conn);
+
+ /*
+ * We only send capabilities based on the mask for:
+ * - client only flags
+ * - flags used in both directions
+ *
+ * We do not echo the server only flags, except some legacy flags.
+ *
+ * SMB_CAP_LEGACY_CLIENT_MASK contains CAP_LARGE_READX and
+ * CAP_LARGE_WRITEX in order to allow us to do large reads
+ * against old Samba releases (<= 3.6.x).
+ */
+ client_capabilities &= (SMB_CAP_BOTH_MASK | SMB_CAP_LEGACY_CLIENT_MASK);
+
+ /*
+ * Session Setup specific flags CAP_DYNAMIC_REAUTH
+ * and CAP_EXTENDED_SECURITY are passed by the caller.
+ * We need that in order to do guest logins even if
+ * CAP_EXTENDED_SECURITY is negotiated.
+ */
+ client_capabilities &= ~(CAP_DYNAMIC_REAUTH|CAP_EXTENDED_SECURITY);
+ sesssetup_capabilities &= (CAP_DYNAMIC_REAUTH|CAP_EXTENDED_SECURITY);
+ client_capabilities |= sesssetup_capabilities;
+
+ return client_capabilities;
+}
+
+/****************************************************************************
+ Do a NT1 guest session setup.
+****************************************************************************/
+
+struct cli_session_setup_guest_state {
+ struct cli_state *cli;
+ uint16_t vwv[13];
+ struct iovec bytes;
+};
+
+static void cli_session_setup_guest_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_session_setup_guest_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_session_setup_guest_state *state;
+ uint16_t *vwv;
+ uint8_t *bytes;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_guest_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ vwv = state->vwv;
+
+ SCVAL(vwv+0, 0, 0xFF);
+ SCVAL(vwv+0, 1, 0);
+ SSVAL(vwv+1, 0, 0);
+ SSVAL(vwv+2, 0, CLI_BUFFER_SIZE);
+ SSVAL(vwv+3, 0, 2);
+ SSVAL(vwv+4, 0, cli_state_get_vc_num(cli));
+ SIVAL(vwv+5, 0, smb1cli_conn_server_session_key(cli->conn));
+ SSVAL(vwv+7, 0, 0);
+ SSVAL(vwv+8, 0, 0);
+ SSVAL(vwv+9, 0, 0);
+ SSVAL(vwv+10, 0, 0);
+ SIVAL(vwv+11, 0, cli_session_setup_capabilities(cli, 0));
+
+ bytes = talloc_array(state, uint8_t, 0);
+
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "", 1, /* username */
+ NULL);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "", 1, /* workgroup */
+ NULL);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "Unix", 5, NULL);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "Samba", 6, NULL);
+
+ if (bytes == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ state->bytes.iov_base = (void *)bytes;
+ state->bytes.iov_len = talloc_get_size(bytes);
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBsesssetupX, 0, 0, 13,
+ vwv, 1, &state->bytes);
+ if (subreq == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_guest_done, req);
+ *psmbreq = subreq;
+ return req;
+}
+
+struct tevent_req *cli_session_setup_guest_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ NTSTATUS status;
+
+ req = cli_session_setup_guest_create(mem_ctx, ev, cli, &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void cli_session_setup_guest_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_setup_guest_state *state = tevent_req_data(
+ req, struct cli_session_setup_guest_state);
+ struct cli_state *cli = state->cli;
+ uint32_t num_bytes;
+ uint8_t *in;
+ uint8_t *inhdr;
+ uint8_t *bytes;
+ uint8_t *p;
+ NTSTATUS status;
+ ssize_t ret;
+ uint8_t wct;
+ uint16_t *vwv;
+
+ status = cli_smb_recv(subreq, state, &in, 3, &wct, &vwv,
+ &num_bytes, &bytes);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ inhdr = in + NBT_HDR_SIZE;
+ p = bytes;
+
+ cli_state_set_uid(state->cli, SVAL(inhdr, HDR_UID));
+ smb1cli_session_set_action(cli->smb1.session, SVAL(vwv+2, 0));
+
+ status = smb_bytes_talloc_string(cli,
+ inhdr,
+ &cli->server_os,
+ p,
+ bytes+num_bytes-p,
+ &ret);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ p += ret;
+
+ status = smb_bytes_talloc_string(cli,
+ inhdr,
+ &cli->server_type,
+ p,
+ bytes+num_bytes-p,
+ &ret);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ p += ret;
+
+ status = smb_bytes_talloc_string(cli,
+ inhdr,
+ &cli->server_domain,
+ p,
+ bytes+num_bytes-p,
+ &ret);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_session_setup_guest_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/* The following is calculated from :
+ * (smb_size-4) = 35
+ * (smb_wcnt * 2) = 24 (smb_wcnt == 12 in cli_session_setup_blob_send() )
+ * (strlen("Unix") + 1 + strlen("Samba") + 1) * 2 = 22 (unicode strings at
+ * end of packet.
+ */
+
+#define BASE_SESSSETUP_BLOB_PACKET_SIZE (35 + 24 + 22)
+
+struct cli_sesssetup_blob_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ DATA_BLOB blob;
+ uint16_t max_blob_size;
+
+ DATA_BLOB this_blob;
+ struct iovec *recv_iov;
+
+ NTSTATUS status;
+ const uint8_t *inbuf;
+ DATA_BLOB ret_blob;
+
+ char *out_native_os;
+ char *out_native_lm;
+};
+
+static bool cli_sesssetup_blob_next(struct cli_sesssetup_blob_state *state,
+ struct tevent_req **psubreq);
+static void cli_sesssetup_blob_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_sesssetup_blob_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ DATA_BLOB blob)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_sesssetup_blob_state *state;
+ uint32_t usable_space;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_sesssetup_blob_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->blob = blob;
+ state->cli = cli;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ usable_space = UINT16_MAX;
+ } else {
+ usable_space = cli_state_available_size(cli,
+ BASE_SESSSETUP_BLOB_PACKET_SIZE);
+ }
+
+ if (usable_space == 0) {
+ DEBUG(1, ("cli_session_setup_blob: cli->max_xmit too small "
+ "(not possible to send %u bytes)\n",
+ BASE_SESSSETUP_BLOB_PACKET_SIZE + 1));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ state->max_blob_size = MIN(usable_space, 0xFFFF);
+
+ if (!cli_sesssetup_blob_next(state, &subreq)) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_sesssetup_blob_done, req);
+ return req;
+}
+
+static bool cli_sesssetup_blob_next(struct cli_sesssetup_blob_state *state,
+ struct tevent_req **psubreq)
+{
+ struct tevent_req *subreq;
+ uint16_t thistime;
+
+ thistime = MIN(state->blob.length, state->max_blob_size);
+
+ state->this_blob.data = state->blob.data;
+ state->this_blob.length = thistime;
+
+ state->blob.data += thistime;
+ state->blob.length -= thistime;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = smb2cli_session_setup_send(state, state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb2.session,
+ 0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &state->this_blob);
+ if (subreq == NULL) {
+ return false;
+ }
+ } else {
+ uint16_t in_buf_size = 0;
+ uint16_t in_mpx_max = 0;
+ uint16_t in_vc_num = 0;
+ uint32_t in_sess_key = 0;
+ uint32_t in_capabilities = 0;
+ const char *in_native_os = NULL;
+ const char *in_native_lm = NULL;
+
+ in_buf_size = CLI_BUFFER_SIZE;
+ in_mpx_max = smbXcli_conn_max_requests(state->cli->conn);
+ in_vc_num = cli_state_get_vc_num(state->cli);
+ in_sess_key = smb1cli_conn_server_session_key(state->cli->conn);
+ in_capabilities = cli_session_setup_capabilities(state->cli,
+ CAP_EXTENDED_SECURITY);
+ in_native_os = "Unix";
+ in_native_lm = "Samba";
+
+ /*
+ * For now we keep the same values as before,
+ * we may remove these in a separate commit later.
+ */
+ in_mpx_max = 2;
+ in_vc_num = 1;
+ in_sess_key = 0;
+
+ subreq = smb1cli_session_setup_ext_send(state, state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->cli->smb1.pid,
+ state->cli->smb1.session,
+ in_buf_size,
+ in_mpx_max,
+ in_vc_num,
+ in_sess_key,
+ state->this_blob,
+ in_capabilities,
+ in_native_os,
+ in_native_lm);
+ if (subreq == NULL) {
+ return false;
+ }
+ }
+ *psubreq = subreq;
+ return true;
+}
+
+static void cli_sesssetup_blob_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_sesssetup_blob_state *state = tevent_req_data(
+ req, struct cli_sesssetup_blob_state);
+ NTSTATUS status;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_session_setup_recv(subreq, state,
+ &state->recv_iov,
+ &state->ret_blob);
+ } else {
+ status = smb1cli_session_setup_ext_recv(subreq, state,
+ &state->recv_iov,
+ &state->inbuf,
+ &state->ret_blob,
+ &state->out_native_os,
+ &state->out_native_lm);
+ }
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)
+ && !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->status = status;
+
+ status = cli_state_update_after_sesssetup(state->cli,
+ state->out_native_os,
+ state->out_native_lm,
+ NULL);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (state->blob.length != 0) {
+ /*
+ * More to send
+ */
+ if (!cli_sesssetup_blob_next(state, &subreq)) {
+ tevent_req_oom(req);
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_sesssetup_blob_done, req);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_sesssetup_blob_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *pblob,
+ const uint8_t **pinbuf,
+ struct iovec **precv_iov)
+{
+ struct cli_sesssetup_blob_state *state = tevent_req_data(
+ req, struct cli_sesssetup_blob_state);
+ NTSTATUS status;
+ struct iovec *recv_iov;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ TALLOC_FREE(state->cli->smb2.session);
+ cli_state_set_uid(state->cli, UID_FIELD_INVALID);
+ tevent_req_received(req);
+ return status;
+ }
+
+ recv_iov = talloc_move(mem_ctx, &state->recv_iov);
+ if (pblob != NULL) {
+ *pblob = state->ret_blob;
+ }
+ if (pinbuf != NULL) {
+ *pinbuf = state->inbuf;
+ }
+ if (precv_iov != NULL) {
+ *precv_iov = recv_iov;
+ }
+ /* could be NT_STATUS_MORE_PROCESSING_REQUIRED */
+ status = state->status;
+ tevent_req_received(req);
+ return status;
+}
+
+/****************************************************************************
+ Do a spnego/NTLMSSP encrypted session setup.
+****************************************************************************/
+
+struct cli_session_setup_gensec_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct auth_generic_state *auth_generic;
+ bool is_anonymous;
+ DATA_BLOB blob_in;
+ const uint8_t *inbuf;
+ struct iovec *recv_iov;
+ DATA_BLOB blob_out;
+ bool local_ready;
+ bool remote_ready;
+ DATA_BLOB session_key;
+};
+
+static int cli_session_setup_gensec_state_destructor(
+ struct cli_session_setup_gensec_state *state)
+{
+ TALLOC_FREE(state->auth_generic);
+ data_blob_clear_free(&state->session_key);
+ return 0;
+}
+
+static void cli_session_setup_gensec_local_next(struct tevent_req *req);
+static void cli_session_setup_gensec_local_done(struct tevent_req *subreq);
+static void cli_session_setup_gensec_remote_next(struct tevent_req *req);
+static void cli_session_setup_gensec_remote_done(struct tevent_req *subreq);
+static void cli_session_setup_gensec_ready(struct tevent_req *req);
+
+static struct tevent_req *cli_session_setup_gensec_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ struct cli_credentials *creds,
+ const char *target_service,
+ const char *target_hostname)
+{
+ struct tevent_req *req;
+ struct cli_session_setup_gensec_state *state;
+ NTSTATUS status;
+ const DATA_BLOB *b = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_gensec_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ talloc_set_destructor(
+ state, cli_session_setup_gensec_state_destructor);
+
+ status = auth_generic_client_prepare(state, &state->auth_generic);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = auth_generic_set_creds(state->auth_generic, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ gensec_want_feature(state->auth_generic->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+
+ if (target_service != NULL) {
+ status = gensec_set_target_service(
+ state->auth_generic->gensec_security,
+ target_service);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (target_hostname != NULL) {
+ status = gensec_set_target_hostname(
+ state->auth_generic->gensec_security,
+ target_hostname);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ b = smbXcli_conn_server_gss_blob(cli->conn);
+ if (b != NULL) {
+ state->blob_in = *b;
+ }
+
+ state->is_anonymous = cli_credentials_is_anonymous(state->auth_generic->credentials);
+
+ status = auth_generic_client_start(state->auth_generic,
+ GENSEC_OID_SPNEGO);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ state->cli->smb2.session = smbXcli_session_create(cli,
+ cli->conn);
+ if (tevent_req_nomem(state->cli->smb2.session, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ cli_session_setup_gensec_local_next(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void cli_session_setup_gensec_local_next(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->local_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = gensec_update_send(state, state->ev,
+ state->auth_generic->gensec_security,
+ state->blob_in);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_gensec_local_done, req);
+}
+
+static void cli_session_setup_gensec_local_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ NTSTATUS status;
+
+ status = gensec_update_recv(subreq, state, &state->blob_out);
+ TALLOC_FREE(subreq);
+ state->blob_in = data_blob_null;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ state->local_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_session_setup_gensec_ready(req);
+ return;
+ }
+
+ cli_session_setup_gensec_remote_next(req);
+}
+
+static void cli_session_setup_gensec_remote_next(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->remote_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = cli_sesssetup_blob_send(state, state->ev,
+ state->cli, state->blob_out);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_session_setup_gensec_remote_done,
+ req);
+}
+
+static void cli_session_setup_gensec_remote_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ NTSTATUS status;
+
+ state->inbuf = NULL;
+ TALLOC_FREE(state->recv_iov);
+
+ status = cli_sesssetup_blob_recv(subreq, state, &state->blob_in,
+ &state->inbuf, &state->recv_iov);
+ TALLOC_FREE(subreq);
+ data_blob_free(&state->blob_out);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ struct smbXcli_session *session = NULL;
+ bool is_guest = false;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ session = state->cli->smb2.session;
+ } else {
+ session = state->cli->smb1.session;
+ }
+
+ is_guest = smbXcli_session_is_guest(session);
+ if (is_guest) {
+ /*
+ * We can't finish the gensec handshake, we don't
+ * have a negotiated session key.
+ *
+ * So just pretend we are completely done,
+ * we need to continue as anonymous from this point,
+ * as we can't get a session key.
+ *
+ * Note that smbXcli_session_is_guest()
+ * always returns false if we require signing.
+ */
+ state->blob_in = data_blob_null;
+ state->local_ready = true;
+ state->is_anonymous = true;
+ }
+
+ state->remote_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_session_setup_gensec_ready(req);
+ return;
+ }
+
+ cli_session_setup_gensec_local_next(req);
+}
+
+static void cli_session_dump_keys(TALLOC_CTX *mem_ctx,
+ struct smbXcli_session *session,
+ DATA_BLOB session_key)
+{
+ NTSTATUS status;
+ DATA_BLOB sig = data_blob_null;
+ DATA_BLOB app = data_blob_null;
+ DATA_BLOB enc = data_blob_null;
+ DATA_BLOB dec = data_blob_null;
+ uint64_t sid = smb2cli_session_current_id(session);
+
+ status = smb2cli_session_signing_key(session, mem_ctx, &sig);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = smbXcli_session_application_key(session, mem_ctx, &app);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = smb2cli_session_encryption_key(session, mem_ctx, &enc);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = smb2cli_session_decryption_key(session, mem_ctx, &dec);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ DEBUG(0, ("debug encryption: dumping generated session keys\n"));
+ DEBUGADD(0, ("Session Id "));
+ dump_data(0, (uint8_t*)&sid, sizeof(sid));
+ DEBUGADD(0, ("Session Key "));
+ dump_data(0, session_key.data, session_key.length);
+ DEBUGADD(0, ("Signing Key "));
+ dump_data(0, sig.data, sig.length);
+ DEBUGADD(0, ("App Key "));
+ dump_data(0, app.data, app.length);
+
+ /* In client code, ServerIn is the encryption key */
+
+ DEBUGADD(0, ("ServerIn Key "));
+ dump_data(0, enc.data, enc.length);
+ DEBUGADD(0, ("ServerOut Key "));
+ dump_data(0, dec.data, dec.length);
+
+out:
+ data_blob_clear_free(&sig);
+ data_blob_clear_free(&app);
+ data_blob_clear_free(&enc);
+ data_blob_clear_free(&dec);
+}
+
+static void cli_session_setup_gensec_ready(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ const char *server_domain = NULL;
+ NTSTATUS status;
+
+ if (state->blob_in.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (state->blob_out.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ /*
+ * gensec_ntlmssp_server_domain() returns NULL
+ * if NTLMSSP is not used.
+ *
+ * We can remove this later
+ * and leave the server domain empty for SMB2 and above
+ * in future releases.
+ */
+ server_domain = gensec_ntlmssp_server_domain(
+ state->auth_generic->gensec_security);
+
+ if (state->cli->server_domain[0] == '\0' && server_domain != NULL) {
+ TALLOC_FREE(state->cli->server_domain);
+ state->cli->server_domain = talloc_strdup(state->cli,
+ server_domain);
+ if (state->cli->server_domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ }
+
+ if (state->is_anonymous) {
+ /*
+ * Windows server does not set the
+ * SMB2_SESSION_FLAG_IS_NULL flag.
+ *
+ * This fix makes sure we do not try
+ * to verify a signature on the final
+ * session setup response.
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ status = gensec_session_key(state->auth_generic->gensec_security,
+ state, &state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ struct smbXcli_session *session = state->cli->smb2.session;
+
+ status = smb2cli_session_set_session_key(session,
+ state->session_key,
+ state->recv_iov);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB3_00
+ && lp_debug_encryption())
+ {
+ cli_session_dump_keys(state, session, state->session_key);
+ }
+ } else {
+ struct smbXcli_session *session = state->cli->smb1.session;
+ bool active;
+
+ status = smb1cli_session_set_session_key(session,
+ state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ active = smb1cli_conn_activate_signing(state->cli->conn,
+ state->session_key,
+ data_blob_null);
+ if (active) {
+ bool ok;
+
+ ok = smb1cli_conn_check_signing(state->cli->conn,
+ state->inbuf, 1);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_session_setup_gensec_recv(struct tevent_req *req)
+{
+ struct cli_session_setup_gensec_state *state =
+ tevent_req_data(req,
+ struct cli_session_setup_gensec_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ cli_state_set_uid(state->cli, UID_FIELD_INVALID);
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+static char *cli_session_setup_get_account(TALLOC_CTX *mem_ctx,
+ const char *principal)
+{
+ char *account, *p;
+
+ account = talloc_strdup(mem_ctx, principal);
+ if (account == NULL) {
+ return NULL;
+ }
+ p = strchr_m(account, '@');
+ if (p != NULL) {
+ *p = '\0';
+ }
+ return account;
+}
+
+/****************************************************************************
+ Do a spnego encrypted session setup.
+
+ user_domain: The shortname of the domain the user/machine is a member of.
+ dest_realm: The realm we're connecting to, if NULL we use our default realm.
+****************************************************************************/
+
+struct cli_session_setup_spnego_state {
+ ADS_STATUS result;
+};
+
+static void cli_session_setup_spnego_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_session_setup_spnego_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_session_setup_spnego_state *state;
+ const char *target_service = NULL;
+ const char *target_hostname = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_spnego_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ target_service = "cifs";
+ target_hostname = smbXcli_conn_remote_name(cli->conn);
+
+ status = cli_session_creds_prepare_krb5(cli, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_INFO("Connect to %s as %s using SPNEGO\n",
+ target_hostname,
+ cli_credentials_get_principal(creds, talloc_tos()));
+
+ subreq = cli_session_setup_gensec_send(state, ev, cli, creds,
+ target_service, target_hostname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_session_setup_spnego_done, req);
+ return req;
+}
+
+static void cli_session_setup_spnego_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_session_setup_gensec_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static ADS_STATUS cli_session_setup_spnego_recv(struct tevent_req *req)
+{
+ struct cli_session_setup_spnego_state *state = tevent_req_data(
+ req, struct cli_session_setup_spnego_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ state->result = ADS_ERROR_NT(status);
+ }
+
+ return state->result;
+}
+
+struct cli_session_setup_creds_state {
+ struct cli_state *cli;
+ DATA_BLOB apassword_blob;
+ DATA_BLOB upassword_blob;
+ DATA_BLOB lm_session_key;
+ DATA_BLOB session_key;
+ char *out_native_os;
+ char *out_native_lm;
+ char *out_primary_domain;
+};
+
+static void cli_session_setup_creds_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct cli_session_setup_creds_state *state = tevent_req_data(
+ req, struct cli_session_setup_creds_state);
+
+ if (req_state != TEVENT_REQ_RECEIVED) {
+ return;
+ }
+
+ /*
+ * We only call data_blob_clear() as
+ * some of the blobs point to the same memory.
+ *
+ * We let the talloc hierarchy free the memory.
+ */
+ data_blob_clear(&state->apassword_blob);
+ data_blob_clear(&state->upassword_blob);
+ data_blob_clear(&state->lm_session_key);
+ data_blob_clear(&state->session_key);
+ ZERO_STRUCTP(state);
+}
+
+static void cli_session_setup_creds_done_spnego(struct tevent_req *subreq);
+static void cli_session_setup_creds_done_nt1(struct tevent_req *subreq);
+static void cli_session_setup_creds_done_lm21(struct tevent_req *subreq);
+
+/****************************************************************************
+ Send a session setup. The username and workgroup is in UNIX character
+ format and must be converted to DOS codepage format before sending. If the
+ password is in plaintext, the same should be done.
+****************************************************************************/
+
+struct tevent_req *cli_session_setup_creds_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_session_setup_creds_state *state;
+ uint16_t sec_mode = smb1cli_conn_server_security_mode(cli->conn);
+ bool use_spnego = false;
+ int flags = 0;
+ const char *username = "";
+ const char *domain = "";
+ DATA_BLOB target_info = data_blob_null;
+ DATA_BLOB challenge = data_blob_null;
+ uint16_t in_buf_size = 0;
+ uint16_t in_mpx_max = 0;
+ uint16_t in_vc_num = 0;
+ uint32_t in_sess_key = 0;
+ const char *in_native_os = NULL;
+ const char *in_native_lm = NULL;
+ enum credentials_use_kerberos krb5_state =
+ cli_credentials_get_kerberos_state(creds);
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_setup_creds_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ tevent_req_set_cleanup_fn(req, cli_session_setup_creds_cleanup);
+
+ /*
+ * Now work out what sort of session setup we are going to
+ * do. I have split this into separate functions to make the flow a bit
+ * easier to understand (tridge).
+ */
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_NT1) {
+ use_spnego = false;
+ } else if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ use_spnego = true;
+ } else if (smb1cli_conn_capabilities(cli->conn) & CAP_EXTENDED_SECURITY) {
+ /*
+ * if the server supports extended security then use SPNEGO
+ * even for anonymous connections.
+ */
+ use_spnego = true;
+ } else {
+ use_spnego = false;
+ }
+
+ if (use_spnego) {
+ subreq = cli_session_setup_spnego_send(
+ state, ev, cli, creds);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_creds_done_spnego,
+ req);
+ return req;
+ }
+
+ if (krb5_state == CRED_USE_KERBEROS_REQUIRED) {
+ DBG_WARNING("Kerberos authentication requested, but "
+ "the server does not support SPNEGO authentication\n");
+ tevent_req_nterror(req, NT_STATUS_NETWORK_CREDENTIAL_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_LANMAN1) {
+ /*
+ * SessionSetupAndX was introduced by LANMAN 1.0. So we skip
+ * this step against older servers.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (cli_credentials_is_anonymous(creds)) {
+ /*
+ * Do an anonymous session setup
+ */
+ goto non_spnego_creds_done;
+ }
+
+ if ((sec_mode & NEGOTIATE_SECURITY_USER_LEVEL) == 0) {
+ /*
+ * Do an anonymous session setup,
+ * the password is passed via the tree connect.
+ */
+ goto non_spnego_creds_done;
+ }
+
+ cli_credentials_get_ntlm_username_domain(creds, state,
+ &username,
+ &domain);
+ if (tevent_req_nomem(username, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (tevent_req_nomem(domain, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_INFO("Connect to %s as %s using NTLM\n", domain, username);
+
+ if ((sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) == 0) {
+ bool use_unicode = smbXcli_conn_use_unicode(cli->conn);
+ uint8_t *bytes = NULL;
+ size_t bytes_len = 0;
+ const char *pw = cli_credentials_get_password(creds);
+ size_t pw_len = 0;
+
+ if (pw == NULL) {
+ pw = "";
+ }
+ pw_len = strlen(pw) + 1;
+
+ if (!lp_client_plaintext_auth()) {
+ DEBUG(1, ("Server requested PLAINTEXT password but "
+ "'client plaintext auth = no'\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ bytes = talloc_array(state, uint8_t, 0);
+ bytes = trans2_bytes_push_str(bytes, use_unicode,
+ pw, pw_len, &bytes_len);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (use_unicode) {
+ /*
+ * CAP_UNICODE, can only be negotiated by NT1.
+ */
+ state->upassword_blob = data_blob_const(bytes,
+ bytes_len);
+ } else {
+ state->apassword_blob = data_blob_const(bytes,
+ bytes_len);
+ }
+
+ goto non_spnego_creds_done;
+ }
+
+ challenge = data_blob_const(smb1cli_conn_server_challenge(cli->conn), 8);
+
+ if (smbXcli_conn_protocol(cli->conn) == PROTOCOL_NT1) {
+ if (lp_client_ntlmv2_auth() && lp_client_use_spnego()) {
+ /*
+ * Don't send an NTLMv2 response without NTLMSSP if we
+ * want to use spnego support.
+ */
+ DEBUG(1, ("Server does not support EXTENDED_SECURITY "
+ " but 'client use spnego = yes'"
+ " and 'client ntlmv2 auth = yes' is set\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (lp_client_ntlmv2_auth()) {
+ flags |= CLI_CRED_NTLMv2_AUTH;
+
+ /*
+ * note that the 'domain' here is a best
+ * guess - we don't know the server's domain
+ * at this point. Windows clients also don't
+ * use hostname...
+ */
+ target_info = NTLMv2_generate_names_blob(state,
+ NULL,
+ domain);
+ if (tevent_req_nomem(target_info.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ flags |= CLI_CRED_NTLM_AUTH;
+ if (lp_client_lanman_auth()) {
+ flags |= CLI_CRED_LANMAN_AUTH;
+ }
+ }
+ } else {
+ if (!lp_client_lanman_auth()) {
+ DEBUG(1, ("Server requested user level LM password but "
+ "'client lanman auth = no' is set.\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ flags |= CLI_CRED_LANMAN_AUTH;
+ }
+
+ status = cli_credentials_get_ntlm_response(creds, state, &flags,
+ challenge, NULL,
+ target_info,
+ &state->apassword_blob,
+ &state->upassword_blob,
+ &state->lm_session_key,
+ &state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+non_spnego_creds_done:
+
+ in_buf_size = CLI_BUFFER_SIZE;
+ in_mpx_max = smbXcli_conn_max_requests(cli->conn);
+ in_vc_num = cli_state_get_vc_num(cli);
+ in_sess_key = smb1cli_conn_server_session_key(cli->conn);
+ in_native_os = "Unix";
+ in_native_lm = "Samba";
+
+ if (smbXcli_conn_protocol(cli->conn) == PROTOCOL_NT1) {
+ uint32_t in_capabilities = 0;
+
+ in_capabilities = cli_session_setup_capabilities(cli, 0);
+
+ /*
+ * For now we keep the same values as before,
+ * we may remove these in a separate commit later.
+ */
+ in_mpx_max = 2;
+
+ subreq = smb1cli_session_setup_nt1_send(state, ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.session,
+ in_buf_size,
+ in_mpx_max,
+ in_vc_num,
+ in_sess_key,
+ username,
+ domain,
+ state->apassword_blob,
+ state->upassword_blob,
+ in_capabilities,
+ in_native_os,
+ in_native_lm);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_creds_done_nt1,
+ req);
+ return req;
+ }
+
+ /*
+ * For now we keep the same values as before,
+ * we may remove these in a separate commit later.
+ */
+ in_mpx_max = 2;
+ in_vc_num = 1;
+
+ subreq = smb1cli_session_setup_lm21_send(state, ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.session,
+ in_buf_size,
+ in_mpx_max,
+ in_vc_num,
+ in_sess_key,
+ username,
+ domain,
+ state->apassword_blob,
+ in_native_os,
+ in_native_lm);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_session_setup_creds_done_lm21,
+ req);
+ return req;
+}
+
+static void cli_session_setup_creds_done_spnego(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ ADS_STATUS status;
+
+ status = cli_session_setup_spnego_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(3, ("SPNEGO login failed: %s\n", ads_errstr(status)));
+ tevent_req_nterror(req, ads_ntstatus(status));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_session_setup_creds_done_nt1(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_setup_creds_state *state = tevent_req_data(
+ req, struct cli_session_setup_creds_state);
+ struct cli_state *cli = state->cli;
+ NTSTATUS status;
+ struct iovec *recv_iov = NULL;
+ const uint8_t *inbuf = NULL;
+ bool ok;
+
+ status = smb1cli_session_setup_nt1_recv(subreq, state,
+ &recv_iov,
+ &inbuf,
+ &state->out_native_os,
+ &state->out_native_lm,
+ &state->out_primary_domain);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(3, ("NT1 login failed: %s\n", nt_errstr(status)));
+ return;
+ }
+
+ status = cli_state_update_after_sesssetup(state->cli,
+ state->out_native_os,
+ state->out_native_lm,
+ state->out_primary_domain);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ok = smb1cli_conn_activate_signing(cli->conn,
+ state->session_key,
+ state->upassword_blob);
+ if (ok) {
+ ok = smb1cli_conn_check_signing(cli->conn, inbuf, 1);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ if (state->session_key.data) {
+ struct smbXcli_session *session = cli->smb1.session;
+
+ status = smb1cli_session_set_session_key(session,
+ state->session_key);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+static void cli_session_setup_creds_done_lm21(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_setup_creds_state *state = tevent_req_data(
+ req, struct cli_session_setup_creds_state);
+ NTSTATUS status;
+
+ status = smb1cli_session_setup_lm21_recv(subreq, state,
+ &state->out_native_os,
+ &state->out_native_lm);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(3, ("LM21 login failed: %s\n", nt_errstr(status)));
+ return;
+ }
+
+ status = cli_state_update_after_sesssetup(state->cli,
+ state->out_native_os,
+ state->out_native_lm,
+ NULL);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_session_setup_creds_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_session_setup_creds(struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_session_setup_creds_send(ev, ev, cli, creds);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_session_setup_creds_recv(req);
+ fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+NTSTATUS cli_session_setup_anon(struct cli_state *cli)
+{
+ NTSTATUS status;
+ struct cli_credentials *creds = NULL;
+
+ creds = cli_credentials_init_anon(cli);
+ if (creds == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_session_setup_creds(cli, creds);
+ TALLOC_FREE(creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Send a uloggoff.
+*****************************************************************************/
+
+struct cli_ulogoff_state {
+ struct cli_state *cli;
+ uint16_t vwv[3];
+};
+
+static void cli_ulogoff_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_ulogoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_ulogoff_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_ulogoff_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ SCVAL(state->vwv+0, 0, 0xFF);
+ SCVAL(state->vwv+1, 0, 0);
+ SSVAL(state->vwv+2, 0, 0);
+
+ subreq = cli_smb_send(state, ev, cli, SMBulogoffX, 0, 0, 2, state->vwv,
+ 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ulogoff_done, req);
+ return req;
+}
+
+static void cli_ulogoff_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_ulogoff_state *state = tevent_req_data(
+ req, struct cli_ulogoff_state);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ cli_state_set_uid(state->cli, UID_FIELD_INVALID);
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_ulogoff_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_ulogoff(struct cli_state *cli)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_logoff(cli->conn,
+ cli->timeout,
+ cli->smb2.session);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ smb2cli_session_set_id_and_flags(cli->smb2.session,
+ UINT64_MAX, 0);
+ return NT_STATUS_OK;
+ }
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_ulogoff_send(ev, ev, cli);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_ulogoff_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/****************************************************************************
+ Send a tconX.
+****************************************************************************/
+
+struct cli_tcon_andx_state {
+ struct cli_state *cli;
+ uint16_t vwv[4];
+ struct iovec bytes;
+};
+
+static void cli_tcon_andx_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_tcon_andx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *share, const char *dev,
+ const char *pass, int passlen,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_tcon_andx_state *state;
+ uint8_t p24[24];
+ uint16_t *vwv;
+ char *tmp = NULL;
+ uint8_t *bytes;
+ uint16_t sec_mode = smb1cli_conn_server_security_mode(cli->conn);
+ uint16_t tcon_flags = 0;
+
+ *psmbreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_tcon_andx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ vwv = state->vwv;
+
+ TALLOC_FREE(cli->smb1.tcon);
+ cli->smb1.tcon = smbXcli_tcon_create(cli);
+ if (tevent_req_nomem(cli->smb1.tcon, req)) {
+ return tevent_req_post(req, ev);
+ }
+ smb1cli_tcon_set_id(cli->smb1.tcon, UINT16_MAX);
+
+ cli->share = talloc_strdup(cli, share);
+ if (!cli->share) {
+ return NULL;
+ }
+
+ /* in user level security don't send a password now */
+ if (sec_mode & NEGOTIATE_SECURITY_USER_LEVEL) {
+ passlen = 1;
+ pass = "";
+ } else if (pass == NULL) {
+ DEBUG(1, ("Server not using user level security and no "
+ "password supplied.\n"));
+ goto access_denied;
+ }
+
+ if ((sec_mode & NEGOTIATE_SECURITY_CHALLENGE_RESPONSE) &&
+ *pass && passlen != 24) {
+ if (!lp_client_lanman_auth()) {
+ DEBUG(1, ("Server requested LANMAN password "
+ "(share-level security) but "
+ "'client lanman auth = no' or 'client ntlmv2 auth = yes'\n"));
+ goto access_denied;
+ }
+
+ /*
+ * Non-encrypted passwords - convert to DOS codepage before
+ * encryption.
+ */
+ SMBencrypt(pass, smb1cli_conn_server_challenge(cli->conn), p24);
+ passlen = 24;
+ pass = (const char *)p24;
+ } else {
+ if((sec_mode & (NEGOTIATE_SECURITY_USER_LEVEL
+ |NEGOTIATE_SECURITY_CHALLENGE_RESPONSE))
+ == 0) {
+ uint8_t *tmp_pass;
+
+ if (!lp_client_plaintext_auth() && (*pass)) {
+ DEBUG(1, ("Server requested PLAINTEXT "
+ "password but "
+ "'client plaintext auth = no' or 'client ntlmv2 auth = yes'\n"));
+ goto access_denied;
+ }
+
+ /*
+ * Non-encrypted passwords - convert to DOS codepage
+ * before using.
+ */
+ tmp_pass = talloc_array(talloc_tos(), uint8_t, 0);
+ if (tevent_req_nomem(tmp_pass, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tmp_pass = trans2_bytes_push_str(tmp_pass,
+ false, /* always DOS */
+ pass,
+ passlen,
+ NULL);
+ if (tevent_req_nomem(tmp_pass, req)) {
+ return tevent_req_post(req, ev);
+ }
+ pass = (const char *)tmp_pass;
+ passlen = talloc_get_size(tmp_pass);
+ }
+ }
+
+ tcon_flags |= TCONX_FLAG_EXTENDED_RESPONSE;
+ tcon_flags |= TCONX_FLAG_EXTENDED_SIGNATURES;
+
+ SCVAL(vwv+0, 0, 0xFF);
+ SCVAL(vwv+0, 1, 0);
+ SSVAL(vwv+1, 0, 0);
+ SSVAL(vwv+2, 0, tcon_flags);
+ SSVAL(vwv+3, 0, passlen);
+
+ if (passlen && pass) {
+ bytes = (uint8_t *)talloc_memdup(state, pass, passlen);
+ } else {
+ bytes = talloc_array(state, uint8_t, 0);
+ }
+
+ /*
+ * Add the sharename
+ */
+ tmp = talloc_asprintf_strupper_m(talloc_tos(), "\\\\%s\\%s",
+ smbXcli_conn_remote_name(cli->conn), share);
+ if (tmp == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), tmp, strlen(tmp)+1,
+ NULL);
+ TALLOC_FREE(tmp);
+
+ /*
+ * Add the devicetype
+ */
+ tmp = talloc_strdup_upper(talloc_tos(), dev);
+ if (tmp == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ bytes = smb_bytes_push_str(bytes, false, tmp, strlen(tmp)+1, NULL);
+ TALLOC_FREE(tmp);
+
+ if (bytes == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ state->bytes.iov_base = (void *)bytes;
+ state->bytes.iov_len = talloc_get_size(bytes);
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBtconX, 0, 0, 4, vwv,
+ 1, &state->bytes);
+ if (subreq == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cli_tcon_andx_done, req);
+ *psmbreq = subreq;
+ return req;
+
+ access_denied:
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+}
+
+struct tevent_req *cli_tcon_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *share, const char *dev,
+ const char *pass, int passlen)
+{
+ struct tevent_req *req, *subreq;
+ NTSTATUS status;
+
+ req = cli_tcon_andx_create(mem_ctx, ev, cli, share, dev, pass, passlen,
+ &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+ if (subreq == NULL) {
+ return req;
+ }
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void cli_tcon_andx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_tcon_andx_state *state = tevent_req_data(
+ req, struct cli_tcon_andx_state);
+ struct cli_state *cli = state->cli;
+ uint8_t *in;
+ uint8_t *inhdr;
+ uint8_t wct;
+ uint16_t *vwv;
+ uint32_t num_bytes;
+ uint8_t *bytes;
+ NTSTATUS status;
+ uint16_t optional_support = 0;
+
+ status = cli_smb_recv(subreq, state, &in, 0, &wct, &vwv,
+ &num_bytes, &bytes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ inhdr = in + NBT_HDR_SIZE;
+
+ if (num_bytes) {
+ if (pull_string_talloc(cli,
+ (const char *)inhdr,
+ SVAL(inhdr, HDR_FLG2),
+ &cli->dev,
+ bytes,
+ num_bytes,
+ STR_TERMINATE|STR_ASCII) == -1) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ } else {
+ cli->dev = talloc_strdup(cli, "");
+ if (cli->dev == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ }
+
+ if ((smbXcli_conn_protocol(cli->conn) >= PROTOCOL_NT1) && (num_bytes == 3)) {
+ /* almost certainly win95 - enable bug fixes */
+ cli->win95 = True;
+ }
+
+ /*
+ * Make sure that we have the optional support 16-bit field. WCT > 2.
+ * Avoids issues when connecting to Win9x boxes sharing files
+ */
+
+ if ((wct > 2) && (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_LANMAN2)) {
+ optional_support = SVAL(vwv+2, 0);
+ }
+
+ if (optional_support & SMB_EXTENDED_SIGNATURES) {
+ smb1cli_session_protect_session_key(cli->smb1.session);
+ }
+
+ smb1cli_tcon_set_values(state->cli->smb1.tcon,
+ SVAL(inhdr, HDR_TID),
+ optional_support,
+ 0, /* maximal_access */
+ 0, /* guest_maximal_access */
+ NULL, /* service */
+ NULL); /* fs_type */
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_tcon_andx_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_tcon_andx(struct cli_state *cli, const char *share,
+ const char *dev, const char *pass, int passlen)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = cli_tcon_andx_send(frame, ev, cli, share, dev, pass, passlen);
+ if (req == NULL) {
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_tcon_andx_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_tree_connect_state {
+ struct cli_state *cli;
+};
+
+static struct tevent_req *cli_raw_tcon_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *service, const char *pass, const char *dev);
+static NTSTATUS cli_raw_tcon_recv(struct tevent_req *req,
+ uint16_t *max_xmit, uint16_t *tid);
+
+static void cli_tree_connect_smb2_done(struct tevent_req *subreq);
+static void cli_tree_connect_andx_done(struct tevent_req *subreq);
+static void cli_tree_connect_raw_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_tree_connect_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *share, const char *dev, const char *pass)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_tree_connect_state *state;
+ int passlen;
+
+ if (pass == NULL) {
+ pass = "";
+ }
+ passlen = strlen(pass) + 1;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_tree_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ cli->share = talloc_strdup(cli, share);
+ if (tevent_req_nomem(cli->share, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ char *unc;
+
+ TALLOC_FREE(cli->smb2.tcon);
+ cli->smb2.tcon = smbXcli_tcon_create(cli);
+ if (tevent_req_nomem(cli->smb2.tcon, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ unc = talloc_asprintf(state, "\\\\%s\\%s",
+ smbXcli_conn_remote_name(cli->conn),
+ share);
+ if (tevent_req_nomem(unc, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smb2cli_tcon_send(state, ev, cli->conn, cli->timeout,
+ cli->smb2.session, cli->smb2.tcon,
+ 0, /* flags */
+ unc);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tree_connect_smb2_done,
+ req);
+ return req;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_LANMAN1) {
+ subreq = cli_tcon_andx_send(state, ev, cli, share, dev,
+ pass, passlen);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tree_connect_andx_done,
+ req);
+ return req;
+ }
+
+ subreq = cli_raw_tcon_send(state, ev, cli, share, pass, dev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tree_connect_raw_done, req);
+
+ return req;
+}
+
+static void cli_tree_connect_smb2_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = smb2cli_tcon_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_tree_connect_andx_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_tcon_andx_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_tree_connect_raw_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_tree_connect_state *state = tevent_req_data(
+ req, struct cli_tree_connect_state);
+ NTSTATUS status;
+ uint16_t max_xmit = 0;
+ uint16_t tid = 0;
+
+ status = cli_raw_tcon_recv(subreq, &max_xmit, &tid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smb1cli_tcon_set_values(state->cli->smb1.tcon,
+ tid,
+ 0, /* optional_support */
+ 0, /* maximal_access */
+ 0, /* guest_maximal_access */
+ NULL, /* service */
+ NULL); /* fs_type */
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_tree_connect_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_tree_connect(struct cli_state *cli, const char *share,
+ const char *dev, const char *pass)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_tree_connect_send(ev, ev, cli, share, dev, pass);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_tree_connect_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+NTSTATUS cli_tree_connect_creds(struct cli_state *cli,
+ const char *share, const char *dev,
+ struct cli_credentials *creds)
+{
+ const char *pw = NULL;
+
+ if (creds != NULL) {
+ pw = cli_credentials_get_password(creds);
+ }
+
+ return cli_tree_connect(cli, share, dev, pw);
+}
+
+/****************************************************************************
+ Send a tree disconnect.
+****************************************************************************/
+
+struct cli_tdis_state {
+ struct cli_state *cli;
+};
+
+static void cli_tdis_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_tdis_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_tdis_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ subreq = cli_smb_send(state, ev, cli, SMBtdis, 0, 0, 0, NULL, 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_tdis_done, req);
+ return req;
+}
+
+static void cli_tdis_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_tdis_state *state = tevent_req_data(
+ req, struct cli_tdis_state);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ TALLOC_FREE(state->cli->smb1.tcon);
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_tdis(struct cli_state *cli)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_tdis(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon);
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(cli->smb2.tcon);
+ }
+ return status;
+ }
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_tdis_send(ev, ev, cli);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_tdis_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+struct cli_connect_sock_state {
+ const char **called_names;
+ const char **calling_names;
+ int *called_types;
+ int fd;
+ uint16_t port;
+};
+
+static void cli_connect_sock_done(struct tevent_req *subreq);
+
+/*
+ * Async only if we don't have to look up the name, i.e. "pss" is set with a
+ * nonzero address.
+ */
+
+static struct tevent_req *cli_connect_sock_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *host, int name_type, const struct sockaddr_storage *pss,
+ const char *myname, uint16_t port)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_connect_sock_state *state;
+ struct sockaddr_storage *addrs = NULL;
+ unsigned i;
+ unsigned num_addrs = 0;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_connect_sock_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if ((pss == NULL) || is_zero_addr(pss)) {
+
+ /*
+ * Here we cheat. resolve_name_list is not async at all. So
+ * this call will only be really async if the name lookup has
+ * been done externally.
+ */
+
+ status = resolve_name_list(state, host, name_type,
+ &addrs, &num_addrs);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ addrs = talloc_array(state, struct sockaddr_storage, 1);
+ if (tevent_req_nomem(addrs, req)) {
+ return tevent_req_post(req, ev);
+ }
+ addrs[0] = *pss;
+ num_addrs = 1;
+ }
+
+ state->called_names = talloc_array(state, const char *, num_addrs);
+ if (tevent_req_nomem(state->called_names, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->called_types = talloc_array(state, int, num_addrs);
+ if (tevent_req_nomem(state->called_types, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->calling_names = talloc_array(state, const char *, num_addrs);
+ if (tevent_req_nomem(state->calling_names, req)) {
+ return tevent_req_post(req, ev);
+ }
+ for (i=0; i<num_addrs; i++) {
+ state->called_names[i] = host;
+ state->called_types[i] = name_type;
+ state->calling_names[i] = myname;
+ }
+
+ subreq = smbsock_any_connect_send(
+ state, ev, addrs, state->called_names, state->called_types,
+ state->calling_names, NULL, num_addrs, port);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_connect_sock_done, req);
+ return req;
+}
+
+static void cli_connect_sock_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_connect_sock_state *state = tevent_req_data(
+ req, struct cli_connect_sock_state);
+ NTSTATUS status;
+
+ status = smbsock_any_connect_recv(subreq, &state->fd, NULL,
+ &state->port);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ set_socket_options(state->fd, lp_socket_options());
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_connect_sock_recv(struct tevent_req *req,
+ int *pfd, uint16_t *pport)
+{
+ struct cli_connect_sock_state *state = tevent_req_data(
+ req, struct cli_connect_sock_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfd = state->fd;
+ *pport = state->port;
+ return NT_STATUS_OK;
+}
+
+struct cli_connect_nb_state {
+ const char *desthost;
+ enum smb_signing_setting signing_state;
+ int flags;
+ struct cli_state *cli;
+};
+
+static void cli_connect_nb_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_connect_nb_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *host, const struct sockaddr_storage *dest_ss,
+ uint16_t port, int name_type, const char *myname,
+ enum smb_signing_setting signing_state, int flags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_connect_nb_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_connect_nb_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->signing_state = signing_state;
+ state->flags = flags;
+
+ if (host != NULL) {
+ char *p = strchr(host, '#');
+
+ if (p != NULL) {
+ name_type = strtol(p+1, NULL, 16);
+ host = talloc_strndup(state, host, p - host);
+ if (tevent_req_nomem(host, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ state->desthost = host;
+ } else if (dest_ss != NULL) {
+ state->desthost = print_canonical_sockaddr(state, dest_ss);
+ if (tevent_req_nomem(state->desthost, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ /* No host or dest_ss given. Error out. */
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_connect_sock_send(state, ev, host, name_type, dest_ss,
+ myname, port);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_connect_nb_done, req);
+ return req;
+}
+
+static void cli_connect_nb_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_connect_nb_state *state = tevent_req_data(
+ req, struct cli_connect_nb_state);
+ NTSTATUS status;
+ int fd = 0;
+ uint16_t port;
+
+ status = cli_connect_sock_recv(subreq, &fd, &port);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->cli = cli_state_create(state, fd, state->desthost,
+ state->signing_state, state->flags);
+ if (tevent_req_nomem(state->cli, req)) {
+ close(fd);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_connect_nb_recv(struct tevent_req *req,
+ struct cli_state **pcli)
+{
+ struct cli_connect_nb_state *state = tevent_req_data(
+ req, struct cli_connect_nb_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pcli = talloc_move(NULL, &state->cli);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_connect_nb(const char *host, const struct sockaddr_storage *dest_ss,
+ uint16_t port, int name_type, const char *myname,
+ enum smb_signing_setting signing_state, int flags, struct cli_state **pcli)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_connect_nb_send(ev, ev, host, dest_ss, port, name_type,
+ myname, signing_state, flags);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_set_endtime(req, ev, timeval_current_ofs(20, 0))) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_connect_nb_recv(req, pcli);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+struct cli_start_connection_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ int min_protocol;
+ int max_protocol;
+ struct smb2_negotiate_contexts *negotiate_contexts;
+};
+
+static void cli_start_connection_connected(struct tevent_req *subreq);
+static void cli_start_connection_done(struct tevent_req *subreq);
+
+/**
+ establishes a connection to after the negprot.
+ @param output_cli A fully initialised cli structure, non-null only on success
+ @param dest_host The netbios name of the remote host
+ @param dest_ss (optional) The destination IP, NULL for name based lookup
+ @param port (optional) The destination port (0 for default)
+*/
+
+static struct tevent_req *cli_start_connection_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *my_name, const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ enum smb_signing_setting signing_state, int flags,
+ struct smb2_negotiate_contexts *negotiate_contexts)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_start_connection_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_start_connection_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ if (flags & CLI_FULL_CONNECTION_IPC) {
+ state->min_protocol = lp_client_ipc_min_protocol();
+ state->max_protocol = lp_client_ipc_max_protocol();
+ } else {
+ state->min_protocol = lp_client_min_protocol();
+ state->max_protocol = lp_client_max_protocol();
+ }
+
+ if (flags & CLI_FULL_CONNECTION_FORCE_SMB1) {
+ state->max_protocol = MIN(state->max_protocol,
+ PROTOCOL_NT1);
+ state->min_protocol = MIN(state->min_protocol,
+ state->max_protocol);
+ }
+
+ if (flags & CLI_FULL_CONNECTION_DISABLE_SMB1) {
+ state->min_protocol = MAX(state->min_protocol,
+ PROTOCOL_SMB2_02);
+ state->max_protocol = MAX(state->max_protocol,
+ state->min_protocol);
+ }
+
+ state->negotiate_contexts = talloc_zero(
+ state, struct smb2_negotiate_contexts);
+ if (tevent_req_nomem(state->negotiate_contexts, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (flags & CLI_FULL_CONNECTION_REQUEST_POSIX) {
+ NTSTATUS status;
+
+ status = smb2_negotiate_context_add(
+ state->negotiate_contexts,
+ state->negotiate_contexts,
+ SMB2_POSIX_EXTENSIONS_AVAILABLE,
+ (const uint8_t *)SMB2_CREATE_TAG_POSIX,
+ strlen(SMB2_CREATE_TAG_POSIX));
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (negotiate_contexts != NULL) {
+ uint16_t i;
+
+ for (i=0; i<negotiate_contexts->num_contexts; i++) {
+ struct smb2_negotiate_context *ctx =
+ &negotiate_contexts->contexts[i];
+ NTSTATUS status;
+
+ status = smb2_negotiate_context_add(
+ state->negotiate_contexts,
+ state->negotiate_contexts,
+ ctx->type,
+ ctx->data.data,
+ ctx->data.length);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ subreq = cli_connect_nb_send(state, ev, dest_host, dest_ss, port,
+ 0x20, my_name, signing_state, flags);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_start_connection_connected, req);
+ return req;
+}
+
+static void cli_start_connection_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_start_connection_state *state = tevent_req_data(
+ req, struct cli_start_connection_state);
+ NTSTATUS status;
+
+ status = cli_connect_nb_recv(subreq, &state->cli);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = smbXcli_negprot_send(
+ state,
+ state->ev,
+ state->cli->conn,
+ state->cli->timeout,
+ state->min_protocol,
+ state->max_protocol,
+ WINDOWS_CLIENT_PURE_SMB2_NEGPROT_INITIAL_CREDIT_ASK,
+ state->negotiate_contexts);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_start_connection_done, req);
+}
+
+static void cli_start_connection_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_start_connection_state *state = tevent_req_data(
+ req, struct cli_start_connection_state);
+ NTSTATUS status;
+
+ status = smbXcli_negprot_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ /* Ensure we ask for some initial credits. */
+ smb2cli_conn_set_max_credits(state->cli->conn,
+ DEFAULT_SMB2_MAX_CREDITS);
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_start_connection_recv(struct tevent_req *req,
+ struct cli_state **output_cli)
+{
+ struct cli_start_connection_state *state = tevent_req_data(
+ req, struct cli_start_connection_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *output_cli = state->cli;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_start_connection(struct cli_state **output_cli,
+ const char *my_name,
+ const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ enum smb_signing_setting signing_state, int flags)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_start_connection_send(ev, ev, my_name, dest_host, dest_ss,
+ port, signing_state, flags, NULL);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_start_connection_recv(req, output_cli);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+struct cli_smb1_setup_encryption_blob_state {
+ uint16_t setup[1];
+ uint8_t param[4];
+ NTSTATUS status;
+ DATA_BLOB out;
+ uint16_t enc_ctx_id;
+};
+
+static void cli_smb1_setup_encryption_blob_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb1_setup_encryption_blob_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const DATA_BLOB in)
+{
+ struct tevent_req *req = NULL;
+ struct cli_smb1_setup_encryption_blob_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb1_setup_encryption_blob_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (in.length > CLI_BUFFER_SIZE) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ SSVAL(state->setup+0, 0, TRANSACT2_SETFSINFO);
+ SSVAL(state->param, 0, 0);
+ SSVAL(state->param, 2, SMB_REQUEST_TRANSPORT_ENCRYPTION);
+
+ subreq = smb1cli_trans_send(state, ev, cli->conn,
+ SMBtrans2,
+ 0, 0, /* _flags */
+ 0, 0, /* _flags2 */
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ NULL, /* pipe_name */
+ 0, /* fid */
+ 0, /* function */
+ 0, /* flags */
+ state->setup, 1, 0,
+ state->param, 4, 2,
+ in.data, in.length, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_smb1_setup_encryption_blob_done,
+ req);
+
+ return req;
+}
+
+static void cli_smb1_setup_encryption_blob_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_smb1_setup_encryption_blob_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_blob_state);
+ uint8_t *rparam=NULL, *rdata=NULL;
+ uint32_t num_rparam, num_rdata;
+ NTSTATUS status;
+
+ status = smb1cli_trans_recv(subreq, state,
+ NULL, /* recv_flags */
+ NULL, 0, NULL, /* rsetup */
+ &rparam, 0, &num_rparam,
+ &rdata, 0, &num_rdata);
+ TALLOC_FREE(subreq);
+ state->status = status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (num_rparam == 2) {
+ state->enc_ctx_id = SVAL(rparam, 0);
+ }
+ TALLOC_FREE(rparam);
+
+ state->out = data_blob_const(rdata, num_rdata);
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_smb1_setup_encryption_blob_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out,
+ uint16_t *enc_ctx_id)
+{
+ struct cli_smb1_setup_encryption_blob_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_blob_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ status = state->status;
+
+ *out = state->out;
+ talloc_steal(mem_ctx, out->data);
+
+ *enc_ctx_id = state->enc_ctx_id;
+
+ tevent_req_received(req);
+ return status;
+}
+
+struct cli_smb1_setup_encryption_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ struct smb_trans_enc_state *es;
+ DATA_BLOB blob_in;
+ DATA_BLOB blob_out;
+ bool local_ready;
+ bool remote_ready;
+};
+
+static void cli_smb1_setup_encryption_local_next(struct tevent_req *req);
+static void cli_smb1_setup_encryption_local_done(struct tevent_req *subreq);
+static void cli_smb1_setup_encryption_remote_next(struct tevent_req *req);
+static void cli_smb1_setup_encryption_remote_done(struct tevent_req *subreq);
+static void cli_smb1_setup_encryption_ready(struct tevent_req *req);
+
+static struct tevent_req *cli_smb1_setup_encryption_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_req *req = NULL;
+ struct cli_smb1_setup_encryption_state *state = NULL;
+ struct auth_generic_state *ags = NULL;
+ const DATA_BLOB *b = NULL;
+ bool auth_requested = false;
+ const char *target_service = NULL;
+ const char *target_hostname = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb1_setup_encryption_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ auth_requested = cli_credentials_authentication_requested(creds);
+ if (!auth_requested) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ target_service = "cifs";
+ target_hostname = smbXcli_conn_remote_name(cli->conn);
+
+ status = cli_session_creds_prepare_krb5(cli, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->es = talloc_zero(state, struct smb_trans_enc_state);
+ if (tevent_req_nomem(state->es, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = auth_generic_client_prepare(state->es, &ags);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ gensec_want_feature(ags->gensec_security,
+ GENSEC_FEATURE_SIGN);
+ gensec_want_feature(ags->gensec_security,
+ GENSEC_FEATURE_SEAL);
+
+ status = auth_generic_set_creds(ags, creds);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (target_service != NULL) {
+ status = gensec_set_target_service(ags->gensec_security,
+ target_service);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (target_hostname != NULL) {
+ status = gensec_set_target_hostname(ags->gensec_security,
+ target_hostname);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ gensec_set_max_update_size(ags->gensec_security,
+ CLI_BUFFER_SIZE);
+
+ b = smbXcli_conn_server_gss_blob(state->cli->conn);
+ if (b != NULL) {
+ state->blob_in = *b;
+ }
+
+ status = auth_generic_client_start(ags, GENSEC_OID_SPNEGO);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * We only need the gensec_security part from here.
+ */
+ state->es->gensec_security = talloc_move(state->es,
+ &ags->gensec_security);
+ TALLOC_FREE(ags);
+
+ cli_smb1_setup_encryption_local_next(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void cli_smb1_setup_encryption_local_next(struct tevent_req *req)
+{
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->local_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = gensec_update_send(state, state->ev,
+ state->es->gensec_security,
+ state->blob_in);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb1_setup_encryption_local_done, req);
+}
+
+static void cli_smb1_setup_encryption_local_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ NTSTATUS status;
+
+ status = gensec_update_recv(subreq, state, &state->blob_out);
+ TALLOC_FREE(subreq);
+ state->blob_in = data_blob_null;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ state->local_ready = true;
+ }
+
+ /*
+ * We always get NT_STATUS_OK from the server even if it is not ready.
+ * So guess the server is ready when we are ready and already sent
+ * our last blob to the server.
+ */
+ if (state->local_ready && state->blob_out.length == 0) {
+ state->remote_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_smb1_setup_encryption_ready(req);
+ return;
+ }
+
+ cli_smb1_setup_encryption_remote_next(req);
+}
+
+static void cli_smb1_setup_encryption_remote_next(struct tevent_req *req)
+{
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ struct tevent_req *subreq = NULL;
+
+ if (state->remote_ready) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ subreq = cli_smb1_setup_encryption_blob_send(state, state->ev,
+ state->cli, state->blob_out);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_smb1_setup_encryption_remote_done,
+ req);
+}
+
+static void cli_smb1_setup_encryption_remote_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ NTSTATUS status;
+
+ status = cli_smb1_setup_encryption_blob_recv(subreq, state,
+ &state->blob_in,
+ &state->es->enc_ctx_num);
+ TALLOC_FREE(subreq);
+ data_blob_free(&state->blob_out);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ /*
+ * We always get NT_STATUS_OK even if the server is not ready.
+ * So guess the server is ready when we are ready and sent
+ * our last blob to the server.
+ */
+ if (state->local_ready) {
+ state->remote_ready = true;
+ }
+
+ if (state->local_ready && state->remote_ready) {
+ cli_smb1_setup_encryption_ready(req);
+ return;
+ }
+
+ cli_smb1_setup_encryption_local_next(req);
+}
+
+static void cli_smb1_setup_encryption_ready(struct tevent_req *req)
+{
+ struct cli_smb1_setup_encryption_state *state =
+ tevent_req_data(req,
+ struct cli_smb1_setup_encryption_state);
+ struct smb_trans_enc_state *es = NULL;
+
+ if (state->blob_in.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (state->blob_out.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ es = talloc_move(state->cli->conn, &state->es);
+ es->enc_on = true;
+ smb1cli_conn_set_encryption(state->cli->conn, es);
+ es = NULL;
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_smb1_setup_encryption_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_smb1_setup_encryption(struct cli_state *cli,
+ struct cli_credentials *creds)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb1_setup_encryption_send(ev, ev, cli, creds);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb1_setup_encryption_recv(req);
+ fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/**
+ establishes a connection right up to doing tconX, password specified.
+ @param output_cli A fully initialised cli structure, non-null only on success
+ @param dest_host The netbios name of the remote host
+ @param dest_ip (optional) The the destination IP, NULL for name based lookup
+ @param port (optional) The destination port (0 for default)
+ @param service (optional) The share to make the connection to. Should be 'unqualified' in any way.
+ @param service_type The 'type' of service.
+ @param creds The used user credentials
+*/
+
+struct cli_full_connection_creds_state {
+ struct tevent_context *ev;
+ const char *service;
+ const char *service_type;
+ struct cli_credentials *creds;
+ int flags;
+ struct cli_state *cli;
+};
+
+static int cli_full_connection_creds_state_destructor(
+ struct cli_full_connection_creds_state *s)
+{
+ if (s->cli != NULL) {
+ cli_shutdown(s->cli);
+ s->cli = NULL;
+ }
+ return 0;
+}
+
+static void cli_full_connection_creds_conn_done(struct tevent_req *subreq);
+static void cli_full_connection_creds_sess_done(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_start(struct tevent_req *req);
+static void cli_full_connection_creds_enc_tcon(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_ver(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_done(struct tevent_req *subreq);
+static void cli_full_connection_creds_enc_tdis(struct tevent_req *req);
+static void cli_full_connection_creds_enc_finished(struct tevent_req *subreq);
+static void cli_full_connection_creds_tcon_start(struct tevent_req *req);
+static void cli_full_connection_creds_tcon_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_full_connection_creds_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *my_name, const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ const char *service, const char *service_type,
+ struct cli_credentials *creds,
+ int flags,
+ struct smb2_negotiate_contexts *negotiate_contexts)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_full_connection_creds_state *state;
+ enum smb_signing_setting signing_state;
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(creds);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_full_connection_creds_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(state, cli_full_connection_creds_state_destructor);
+
+ state->ev = ev;
+ state->service = service;
+ state->service_type = service_type;
+ state->creds = creds;
+ state->flags = flags;
+
+ if (flags & CLI_FULL_CONNECTION_IPC) {
+ signing_state = cli_credentials_get_smb_ipc_signing(creds);
+ } else {
+ signing_state = cli_credentials_get_smb_signing(creds);
+ }
+
+ if (encryption_state == SMB_ENCRYPTION_REQUIRED) {
+ if (flags & CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK) {
+ encryption_state = SMB_ENCRYPTION_DESIRED;
+ }
+ }
+
+ if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
+ signing_state = SMB_SIGNING_REQUIRED;
+ }
+
+ subreq = cli_start_connection_send(
+ state, ev, my_name, dest_host, dest_ss, port,
+ signing_state, flags,
+ negotiate_contexts);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_conn_done,
+ req);
+ return req;
+}
+
+static void cli_full_connection_creds_conn_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ status = cli_start_connection_recv(subreq, &state->cli);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_session_setup_creds_send(
+ state, state->ev, state->cli, state->creds);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_sess_done,
+ req);
+}
+
+static void cli_full_connection_creds_sess_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ status = cli_session_setup_creds_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ (state->flags & CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK)) {
+
+ state->flags &= ~CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK;
+
+ state->creds = cli_credentials_init_anon(state);
+ if (tevent_req_nomem(state->creds, req)) {
+ return;
+ }
+
+ subreq = cli_session_setup_creds_send(
+ state, state->ev, state->cli, state->creds);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_sess_done,
+ req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ cli_full_connection_creds_enc_start(req);
+}
+
+static void cli_full_connection_creds_enc_start(struct tevent_req *req)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(state->creds);
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ if (encryption_state < SMB_ENCRYPTION_DESIRED) {
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_session_encryption_on(state->cli->smb2.session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+ d_printf("Encryption required and "
+ "server doesn't support "
+ "SMB3 encryption - failing connect\n");
+ tevent_req_nterror(req, status);
+ return;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "setup failed with error %s.\n",
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(state->cli)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ cli_full_connection_creds_tcon_start(req);
+ return;
+ }
+
+ status = NT_STATUS_NOT_SUPPORTED;
+ d_printf("Encryption required and "
+ "server doesn't support "
+ "SMB1 Unix Extensions - failing connect\n");
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ /*
+ * We do a tcon on IPC$ just to setup the encryption,
+ * the real tcon will be encrypted then.
+ */
+ subreq = cli_tree_connect_send(state, state->ev, state->cli,
+ "IPC$", "IPC", NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_full_connection_creds_enc_tcon, req);
+}
+
+static void cli_full_connection_creds_enc_tcon(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ status = cli_tree_connect_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_unix_extensions_version_send(state, state->ev, state->cli);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_full_connection_creds_enc_ver, req);
+}
+
+static void cli_full_connection_creds_enc_ver(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(state->creds);
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ NTSTATUS status;
+
+ status = cli_unix_extensions_version_recv(subreq,
+ &major, &minor,
+ &caplow,
+ &caphigh);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ /* disconnect ipc$ followed by the real tree connect */
+ cli_full_connection_creds_enc_tdis(req);
+ return;
+ }
+ DEBUG(10, ("%s: cli_unix_extensions_version "
+ "returned %s\n", __func__, nt_errstr(status)));
+ tevent_req_nterror(req, NT_STATUS_UNKNOWN_REVISION);
+ return;
+ }
+
+ if (!(caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP)) {
+ if (encryption_state < SMB_ENCRYPTION_REQUIRED) {
+ /* disconnect ipc$ followed by the real tree connect */
+ cli_full_connection_creds_enc_tdis(req);
+ return;
+ }
+ DEBUG(10, ("%s: CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP "
+ "not supported\n", __func__));
+ tevent_req_nterror(req, NT_STATUS_UNSUPPORTED_COMPRESSION);
+ return;
+ }
+
+ subreq = cli_smb1_setup_encryption_send(state, state->ev,
+ state->cli,
+ state->creds);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_enc_done,
+ req);
+}
+
+static void cli_full_connection_creds_enc_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb1_setup_encryption_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* disconnect ipc$ followed by the real tree connect */
+ cli_full_connection_creds_enc_tdis(req);
+}
+
+static void cli_full_connection_creds_enc_tdis(struct tevent_req *req)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ struct tevent_req *subreq = NULL;
+
+ subreq = cli_tdis_send(state, state->ev, state->cli);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_enc_finished,
+ req);
+}
+
+static void cli_full_connection_creds_enc_finished(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_tdis_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ cli_full_connection_creds_tcon_start(req);
+}
+
+static void cli_full_connection_creds_tcon_start(struct tevent_req *req)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ struct tevent_req *subreq = NULL;
+ const char *password = NULL;
+
+ if (state->service == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ password = cli_credentials_get_password(state->creds);
+
+ subreq = cli_tree_connect_send(state, state->ev,
+ state->cli,
+ state->service,
+ state->service_type,
+ password);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_full_connection_creds_tcon_done,
+ req);
+}
+
+static void cli_full_connection_creds_tcon_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_tree_connect_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_full_connection_creds_recv(struct tevent_req *req,
+ struct cli_state **output_cli)
+{
+ struct cli_full_connection_creds_state *state = tevent_req_data(
+ req, struct cli_full_connection_creds_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *output_cli = state->cli;
+ talloc_set_destructor(state, NULL);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_full_connection_creds(struct cli_state **output_cli,
+ const char *my_name,
+ const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ const char *service, const char *service_type,
+ struct cli_credentials *creds,
+ int flags)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_full_connection_creds_send(
+ ev, ev, my_name, dest_host, dest_ss, port, service,
+ service_type, creds, flags,
+ NULL);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_full_connection_creds_recv(req, output_cli);
+ fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/****************************************************************************
+ Send an old style tcon.
+****************************************************************************/
+struct cli_raw_tcon_state {
+ uint16_t *ret_vwv;
+};
+
+static void cli_raw_tcon_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_raw_tcon_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *service, const char *pass, const char *dev)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_raw_tcon_state *state;
+ uint8_t *bytes;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_raw_tcon_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!lp_client_plaintext_auth() && (*pass)) {
+ DEBUG(1, ("Server requested PLAINTEXT password but 'client plaintext auth = no'"
+ " or 'client ntlmv2 auth = yes'\n"));
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ TALLOC_FREE(cli->smb1.tcon);
+ cli->smb1.tcon = smbXcli_tcon_create(cli);
+ if (tevent_req_nomem(cli->smb1.tcon, req)) {
+ return tevent_req_post(req, ev);
+ }
+ smb1cli_tcon_set_id(cli->smb1.tcon, UINT16_MAX);
+
+ bytes = talloc_array(state, uint8_t, 0);
+ bytes = smb_bytes_push_bytes(bytes, 4, NULL, 0);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
+ service, strlen(service)+1, NULL);
+ bytes = smb_bytes_push_bytes(bytes, 4, NULL, 0);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
+ pass, strlen(pass)+1, NULL);
+ bytes = smb_bytes_push_bytes(bytes, 4, NULL, 0);
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn),
+ dev, strlen(dev)+1, NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBtcon, 0, 0, 0, NULL,
+ talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_raw_tcon_done, req);
+ return req;
+}
+
+static void cli_raw_tcon_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_raw_tcon_state *state = tevent_req_data(
+ req, struct cli_raw_tcon_state);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 2, NULL, &state->ret_vwv,
+ NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_raw_tcon_recv(struct tevent_req *req,
+ uint16_t *max_xmit, uint16_t *tid)
+{
+ struct cli_raw_tcon_state *state = tevent_req_data(
+ req, struct cli_raw_tcon_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *max_xmit = SVAL(state->ret_vwv + 0, 0);
+ *tid = SVAL(state->ret_vwv + 1, 0);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_raw_tcon(struct cli_state *cli,
+ const char *service, const char *pass, const char *dev,
+ uint16_t *max_xmit, uint16_t *tid)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_raw_tcon_send(ev, ev, cli, service, pass, dev);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_raw_tcon_recv(req, max_xmit, tid);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/* Return a cli_state pointing at the IPC$ share for the given server */
+
+struct cli_state *get_ipc_connect(char *server,
+ struct sockaddr_storage *server_ss,
+ struct cli_credentials *creds)
+{
+ struct cli_state *cli;
+ NTSTATUS nt_status;
+ uint32_t flags = CLI_FULL_CONNECTION_ANONYMOUS_FALLBACK;
+
+ flags |= CLI_FULL_CONNECTION_FORCE_SMB1;
+ flags |= CLI_FULL_CONNECTION_IPC;
+
+ nt_status = cli_full_connection_creds(&cli, NULL, server, server_ss, 0, "IPC$", "IPC",
+ creds,
+ flags);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ return cli;
+ }
+ if (is_ipaddress(server)) {
+ /* windows 9* needs a correct NMB name for connections */
+ fstring remote_name;
+
+ if (name_status_find("*", 0, 0, server_ss, remote_name)) {
+ cli = get_ipc_connect(remote_name, server_ss, creds);
+ if (cli)
+ return cli;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Given the IP address of a master browser on the network, return its
+ * workgroup and connect to it.
+ *
+ * This function is provided to allow additional processing beyond what
+ * get_ipc_connect_master_ip_bcast() does, e.g. to retrieve the list of master
+ * browsers and obtain each master browsers' list of domains (in case the
+ * first master browser is recently on the network and has not yet
+ * synchronized with other master browsers and therefore does not yet have the
+ * entire network browse list)
+ */
+
+struct cli_state *get_ipc_connect_master_ip(TALLOC_CTX *ctx,
+ struct sockaddr_storage *mb_ip,
+ struct cli_credentials *creds,
+ char **pp_workgroup_out)
+{
+ char addr[INET6_ADDRSTRLEN];
+ fstring name;
+ struct cli_state *cli;
+ struct sockaddr_storage server_ss;
+
+ *pp_workgroup_out = NULL;
+
+ print_sockaddr(addr, sizeof(addr), mb_ip);
+ DEBUG(99, ("Looking up name of master browser %s\n",
+ addr));
+
+ /*
+ * Do a name status query to find out the name of the master browser.
+ * We use <01><02>__MSBROWSE__<02>#01 if *#00 fails because a domain
+ * master browser will not respond to a wildcard query (or, at least,
+ * an NT4 server acting as the domain master browser will not).
+ *
+ * We might be able to use ONLY the query on MSBROWSE, but that's not
+ * yet been tested with all Windows versions, so until it is, leave
+ * the original wildcard query as the first choice and fall back to
+ * MSBROWSE if the wildcard query fails.
+ */
+ if (!name_status_find("*", 0, 0x1d, mb_ip, name) &&
+ !name_status_find(MSBROWSE, 1, 0x1d, mb_ip, name)) {
+
+ DEBUG(99, ("Could not retrieve name status for %s\n",
+ addr));
+ return NULL;
+ }
+
+ if (!find_master_ip(name, &server_ss)) {
+ DEBUG(99, ("Could not find master ip for %s\n", name));
+ return NULL;
+ }
+
+ *pp_workgroup_out = talloc_strdup(ctx, name);
+
+ DEBUG(4, ("found master browser %s, %s\n", name, addr));
+
+ print_sockaddr(addr, sizeof(addr), &server_ss);
+ cli = get_ipc_connect(addr, &server_ss, creds);
+
+ return cli;
+}
diff --git a/source3/libsmb/clidfs.c b/source3/libsmb/clidfs.c
new file mode 100644
index 0000000..939b3b0
--- /dev/null
+++ b/source3/libsmb/clidfs.c
@@ -0,0 +1,1451 @@
+/*
+ Unix SMB/CIFS implementation.
+ client connect/disconnect routines
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Gerald (Jerry) Carter 2004
+ Copyright (C) Jeremy Allison 2007-2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "msdfs.h"
+#include "trans2.h"
+#include "libsmb/nmblib.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "auth/credentials/credentials.h"
+#include "lib/param/param.h"
+#include "libcli/smb/smb2_negotiate_context.h"
+
+/********************************************************************
+ Important point.
+
+ DFS paths are *always* of the form \server\share\<pathname> (the \ characters
+ are not C escaped here).
+
+ - but if we're using POSIX paths then <pathname> may contain
+ '/' separators, not '\\' separators. So cope with '\\' or '/'
+ as a separator when looking at the pathname part.... JRA.
+********************************************************************/
+
+/********************************************************************
+ Ensure a connection is encrypted.
+********************************************************************/
+
+static NTSTATUS cli_cm_force_encryption_creds(struct cli_state *c,
+ struct cli_credentials *creds,
+ const char *sharename)
+{
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ NTSTATUS status;
+ bool temp_ipc = false;
+
+ if (smbXcli_conn_protocol(c->conn) >= PROTOCOL_SMB2_02) {
+ status = smb2cli_session_encryption_on(c->smb2.session);
+ if (NT_STATUS_EQUAL(status,NT_STATUS_NOT_SUPPORTED)) {
+ d_printf("Encryption required and "
+ "server doesn't support "
+ "SMB3 encryption - failing connect\n");
+ } else if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "setup failed with error %s.\n",
+ nt_errstr(status));
+ }
+ return status;
+ }
+
+ if (!SERVER_HAS_UNIX_CIFS(c)) {
+ d_printf("Encryption required and "
+ "server that doesn't support "
+ "UNIX extensions - failing connect\n");
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (c->smb1.tcon == NULL) {
+ status = cli_tree_connect_creds(c, "IPC$", "IPC", creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "can't connect to IPC$ to check "
+ "UNIX CIFS extensions.\n");
+ return NT_STATUS_UNKNOWN_REVISION;
+ }
+ temp_ipc = true;
+ }
+
+ status = cli_unix_extensions_version(c, &major, &minor, &caplow,
+ &caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "can't get UNIX CIFS extensions "
+ "version from server.\n");
+ if (temp_ipc) {
+ cli_tdis(c);
+ }
+ return NT_STATUS_UNKNOWN_REVISION;
+ }
+
+ if (!(caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP)) {
+ d_printf("Encryption required and "
+ "share %s doesn't support "
+ "encryption.\n", sharename);
+ if (temp_ipc) {
+ cli_tdis(c);
+ }
+ return NT_STATUS_UNSUPPORTED_COMPRESSION;
+ }
+
+ status = cli_smb1_setup_encryption(c, creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "setup failed with error %s.\n",
+ nt_errstr(status));
+ if (temp_ipc) {
+ cli_tdis(c);
+ }
+ return status;
+ }
+
+ if (temp_ipc) {
+ cli_tdis(c);
+ }
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Return a connection to a server.
+********************************************************************/
+
+static NTSTATUS do_connect(TALLOC_CTX *ctx,
+ const char *server,
+ const char *share,
+ struct cli_credentials *creds,
+ const struct sockaddr_storage *dest_ss,
+ int port,
+ int name_type,
+ struct cli_state **pcli)
+{
+ struct cli_state *c = NULL;
+ char *servicename;
+ char *sharename;
+ char *newserver, *newshare;
+ NTSTATUS status;
+ int flags = 0;
+ enum protocol_types protocol = PROTOCOL_NONE;
+ enum smb_signing_setting signing_state =
+ cli_credentials_get_smb_signing(creds);
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(creds);
+ struct smb2_negotiate_contexts *in_contexts = NULL;
+ struct smb2_negotiate_contexts *out_contexts = NULL;
+
+ if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
+ signing_state = SMB_SIGNING_REQUIRED;
+ }
+
+ /* make a copy so we don't modify the global string 'service' */
+ servicename = talloc_strdup(ctx,share);
+ if (!servicename) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ sharename = servicename;
+ if (*sharename == '\\') {
+ sharename += 2;
+ if (server == NULL) {
+ server = sharename;
+ }
+ sharename = strchr_m(sharename,'\\');
+ if (!sharename) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *sharename = 0;
+ sharename++;
+ }
+ if (server == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = cli_connect_nb(
+ server, dest_ss, port, name_type, NULL,
+ signing_state,
+ flags, &c);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ DBG_ERR("NetBIOS support disabled, unable to connect\n");
+ }
+
+ DBG_WARNING("Connection to %s failed (Error %s)\n",
+ server,
+ nt_errstr(status));
+ return status;
+ }
+
+ DEBUG(4,(" session request ok\n"));
+
+ in_contexts = talloc_zero(ctx, struct smb2_negotiate_contexts);
+ if (in_contexts == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = smb2_negotiate_context_add(
+ in_contexts,
+ in_contexts,
+ SMB2_POSIX_EXTENSIONS_AVAILABLE,
+ (const uint8_t *)SMB2_CREATE_TAG_POSIX,
+ strlen(SMB2_CREATE_TAG_POSIX));
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbXcli_negprot(c->conn,
+ c->timeout,
+ lp_client_min_protocol(),
+ lp_client_max_protocol(),
+ in_contexts,
+ ctx,
+ &out_contexts);
+ TALLOC_FREE(in_contexts);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ d_printf("Protocol negotiation (with timeout %d ms) timed out against server %s\n",
+ c->timeout,
+ smbXcli_conn_remote_name(c->conn));
+ cli_shutdown(c);
+ return status;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Protocol negotiation to server %s (for a protocol between %s and %s) failed: %s\n",
+ smbXcli_conn_remote_name(c->conn),
+ lpcfg_get_smb_protocol(lp_client_min_protocol()),
+ lpcfg_get_smb_protocol(lp_client_max_protocol()),
+ nt_errstr(status));
+ cli_shutdown(c);
+ return status;
+ }
+ protocol = smbXcli_conn_protocol(c->conn);
+ DEBUG(4,(" negotiated dialect[%s] against server[%s]\n",
+ smb_protocol_types_string(protocol),
+ smbXcli_conn_remote_name(c->conn)));
+
+ if (protocol >= PROTOCOL_SMB2_02) {
+ /* Ensure we ask for some initial credits. */
+ smb2cli_conn_set_max_credits(c->conn, DEFAULT_SMB2_MAX_CREDITS);
+ }
+
+ status = cli_session_setup_creds(c, creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* If a password was not supplied then
+ * try again with a null username. */
+ if (encryption_state == SMB_ENCRYPTION_REQUIRED ||
+ smbXcli_conn_signing_mandatory(c->conn) ||
+ cli_credentials_authentication_requested(creds) ||
+ cli_credentials_is_anonymous(creds) ||
+ !NT_STATUS_IS_OK(status = cli_session_setup_anon(c)))
+ {
+ d_printf("session setup failed: %s\n",
+ nt_errstr(status));
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_MORE_PROCESSING_REQUIRED))
+ d_printf("did you forget to run kinit?\n");
+ cli_shutdown(c);
+ return status;
+ }
+ d_printf("Anonymous login successful\n");
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("cli_init_creds() failed: %s\n", nt_errstr(status)));
+ cli_shutdown(c);
+ return status;
+ }
+
+ DEBUG(4,(" session setup ok\n"));
+
+ if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
+ status = cli_cm_force_encryption_creds(c,
+ creds,
+ sharename);
+ if (!NT_STATUS_IS_OK(status)) {
+ switch (encryption_state) {
+ case SMB_ENCRYPTION_DESIRED:
+ break;
+ case SMB_ENCRYPTION_REQUIRED:
+ default:
+ cli_shutdown(c);
+ return status;
+ }
+ }
+ }
+
+ /* here's the fun part....to support 'msdfs proxy' shares
+ (on Samba or windows) we have to issues a TRANS_GET_DFS_REFERRAL
+ here before trying to connect to the original share.
+ cli_check_msdfs_proxy() will fail if it is a normal share. */
+
+ if (smbXcli_conn_dfs_supported(c->conn) &&
+ cli_check_msdfs_proxy(ctx, c, sharename,
+ &newserver, &newshare,
+ creds)) {
+ cli_shutdown(c);
+ return do_connect(ctx, newserver,
+ newshare, creds,
+ NULL, port, name_type, pcli);
+ }
+
+ /* must be a normal share */
+
+ status = cli_tree_connect_creds(c, sharename, "?????", creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("tree connect failed: %s\n", nt_errstr(status));
+ cli_shutdown(c);
+ return status;
+ }
+
+ DEBUG(4,(" tconx ok\n"));
+ *pcli = c;
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Add a new connection to the list.
+ referring_cli == NULL means a new initial connection.
+********************************************************************/
+
+static NTSTATUS cli_cm_connect(TALLOC_CTX *ctx,
+ struct cli_state *referring_cli,
+ const char *server,
+ const char *share,
+ struct cli_credentials *creds,
+ const struct sockaddr_storage *dest_ss,
+ int port,
+ int name_type,
+ struct cli_state **pcli)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+
+ status = do_connect(ctx, server, share,
+ creds,
+ dest_ss, port, name_type, &cli);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * This can't happen, this test is to satisfy static
+ * checkers (clang)
+ */
+ if (cli == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Enter into the list. */
+ if (referring_cli) {
+ DLIST_ADD_END(referring_cli, cli);
+ }
+
+ if (referring_cli && referring_cli->requested_posix_capabilities) {
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ status = cli_unix_extensions_version(cli, &major, &minor,
+ &caplow, &caphigh);
+ if (NT_STATUS_IS_OK(status)) {
+ cli_set_unix_extensions_capabilities(cli,
+ major, minor,
+ caplow, caphigh);
+ }
+ }
+
+ *pcli = cli;
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Return a connection to a server on a particular share.
+********************************************************************/
+
+static struct cli_state *cli_cm_find(struct cli_state *cli,
+ const char *server,
+ const char *share)
+{
+ struct cli_state *p;
+
+ if (cli == NULL) {
+ return NULL;
+ }
+
+ /* Search to the start of the list. */
+ for (p = cli; p; p = DLIST_PREV(p)) {
+ const char *remote_name =
+ smbXcli_conn_remote_name(p->conn);
+
+ if (strequal(server, remote_name) &&
+ strequal(share,p->share)) {
+ return p;
+ }
+ }
+
+ /* Search to the end of the list. */
+ for (p = cli->next; p; p = p->next) {
+ const char *remote_name =
+ smbXcli_conn_remote_name(p->conn);
+
+ if (strequal(server, remote_name) &&
+ strequal(share,p->share)) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Open a client connection to a \\server\share.
+****************************************************************************/
+
+NTSTATUS cli_cm_open(TALLOC_CTX *ctx,
+ struct cli_state *referring_cli,
+ const char *server,
+ const char *share,
+ struct cli_credentials *creds,
+ const struct sockaddr_storage *dest_ss,
+ int port,
+ int name_type,
+ struct cli_state **pcli)
+{
+ /* Try to reuse an existing connection in this list. */
+ struct cli_state *c = cli_cm_find(referring_cli, server, share);
+ NTSTATUS status;
+
+ if (c) {
+ *pcli = c;
+ return NT_STATUS_OK;
+ }
+
+ if (creds == NULL) {
+ /* Can't do a new connection
+ * without auth info. */
+ d_printf("cli_cm_open() Unable to open connection [\\%s\\%s] "
+ "without client credentials\n",
+ server, share );
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = cli_cm_connect(ctx,
+ referring_cli,
+ server,
+ share,
+ creds,
+ dest_ss,
+ port,
+ name_type,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *pcli = c;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+void cli_cm_display(struct cli_state *cli)
+{
+ int i;
+
+ for (i=0; cli; cli = cli->next,i++ ) {
+ d_printf("%d:\tserver=%s, share=%s\n",
+ i, smbXcli_conn_remote_name(cli->conn), cli->share);
+ }
+}
+
+/**********************************************************************
+ split a dfs path into the server, share name, and extrapath components
+**********************************************************************/
+
+static bool split_dfs_path(TALLOC_CTX *ctx,
+ const char *nodepath,
+ char **pp_server,
+ char **pp_share,
+ char **pp_extrapath)
+{
+ char *p, *q;
+ char *path;
+
+ *pp_server = NULL;
+ *pp_share = NULL;
+ *pp_extrapath = NULL;
+
+ path = talloc_strdup(ctx, nodepath);
+ if (!path) {
+ goto fail;
+ }
+
+ if ( path[0] != '\\' ) {
+ goto fail;
+ }
+
+ p = strchr_m( path + 1, '\\' );
+ if ( !p ) {
+ goto fail;
+ }
+
+ *p = '\0';
+ p++;
+
+ /* Look for any extra/deep path */
+ q = strchr_m(p, '\\');
+ if (q != NULL) {
+ *q = '\0';
+ q++;
+ *pp_extrapath = talloc_strdup(ctx, q);
+ } else {
+ *pp_extrapath = talloc_strdup(ctx, "");
+ }
+ if (*pp_extrapath == NULL) {
+ goto fail;
+ }
+
+ *pp_share = talloc_strdup(ctx, p);
+ if (*pp_share == NULL) {
+ goto fail;
+ }
+
+ *pp_server = talloc_strdup(ctx, &path[1]);
+ if (*pp_server == NULL) {
+ goto fail;
+ }
+
+ TALLOC_FREE(path);
+ return true;
+
+fail:
+ TALLOC_FREE(*pp_share);
+ TALLOC_FREE(*pp_extrapath);
+ TALLOC_FREE(path);
+ return false;
+}
+
+/****************************************************************************
+ Return the original path truncated at the directory component before
+ the first wildcard character. Trust the caller to provide a NULL
+ terminated string
+****************************************************************************/
+
+static char *clean_path(TALLOC_CTX *ctx, const char *path)
+{
+ size_t len;
+ char *p1, *p2, *p;
+ char *path_out;
+
+ /* No absolute paths. */
+ while (IS_DIRECTORY_SEP(*path)) {
+ path++;
+ }
+
+ path_out = talloc_strdup(ctx, path);
+ if (!path_out) {
+ return NULL;
+ }
+
+ p1 = strchr_m(path_out, '*');
+ p2 = strchr_m(path_out, '?');
+
+ if (p1 || p2) {
+ if (p1 && p2) {
+ p = MIN(p1,p2);
+ } else if (!p1) {
+ p = p2;
+ } else {
+ p = p1;
+ }
+ *p = '\0';
+
+ /* Now go back to the start of this component. */
+ p1 = strrchr_m(path_out, '/');
+ p2 = strrchr_m(path_out, '\\');
+ p = MAX(p1,p2);
+ if (p) {
+ *p = '\0';
+ }
+ }
+
+ /* Strip any trailing separator */
+
+ len = strlen(path_out);
+ if ( (len > 0) && IS_DIRECTORY_SEP(path_out[len-1])) {
+ path_out[len-1] = '\0';
+ }
+
+ return path_out;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static char *cli_dfs_make_full_path(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *dir)
+{
+ char path_sep = '\\';
+
+ /* Ensure the extrapath doesn't start with a separator. */
+ while (IS_DIRECTORY_SEP(*dir)) {
+ dir++;
+ }
+
+ if (cli->requested_posix_capabilities & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
+ path_sep = '/';
+ }
+ return talloc_asprintf(ctx, "%c%s%c%s%c%s",
+ path_sep,
+ smbXcli_conn_remote_name(cli->conn),
+ path_sep,
+ cli->share,
+ path_sep,
+ dir);
+}
+
+/********************************************************************
+ Check if a path has already been converted to DFS.
+********************************************************************/
+
+bool cli_dfs_is_already_full_path(struct cli_state *cli, const char *path)
+{
+ const char *server = smbXcli_conn_remote_name(cli->conn);
+ size_t server_len = strlen(server);
+ bool found_server = false;
+ const char *share = cli->share;
+ size_t share_len = strlen(share);
+ bool found_share = false;
+
+ if (!IS_DIRECTORY_SEP(path[0])) {
+ return false;
+ }
+ path++;
+ found_server = (strncasecmp_m(path, server, server_len) == 0);
+ if (!found_server) {
+ return false;
+ }
+ path += server_len;
+ if (!IS_DIRECTORY_SEP(path[0])) {
+ return false;
+ }
+ path++;
+ found_share = (strncasecmp_m(path, share, share_len) == 0);
+ if (!found_share) {
+ return false;
+ }
+ path += share_len;
+ if (path[0] == '\0') {
+ return true;
+ }
+ if (IS_DIRECTORY_SEP(path[0])) {
+ return true;
+ }
+ return false;
+}
+
+/********************************************************************
+ Get the dfs referral link.
+********************************************************************/
+
+NTSTATUS cli_dfs_get_referral_ex(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *path,
+ uint16_t max_referral_level,
+ struct client_dfs_referral **refs,
+ size_t *num_refs,
+ size_t *consumed)
+{
+ unsigned int param_len = 0;
+ uint16_t recv_flags2;
+ uint8_t *param = NULL;
+ uint8_t *rdata = NULL;
+ char *p;
+ char *endp;
+ smb_ucs2_t *path_ucs;
+ char *consumed_path = NULL;
+ uint16_t consumed_ucs;
+ uint16_t num_referrals;
+ struct client_dfs_referral *referrals = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *num_refs = 0;
+ *refs = NULL;
+
+ param = talloc_array(talloc_tos(), uint8_t, 2);
+ if (!param) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ SSVAL(param, 0, max_referral_level);
+
+ param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
+ path, strlen(path)+1,
+ NULL);
+ if (!param) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ param_len = talloc_get_size(param);
+ path_ucs = (smb_ucs2_t *)&param[2];
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ DATA_BLOB in_input_buffer;
+ DATA_BLOB in_output_buffer = data_blob_null;
+ DATA_BLOB out_input_buffer = data_blob_null;
+ DATA_BLOB out_output_buffer = data_blob_null;
+
+ in_input_buffer.data = param;
+ in_input_buffer.length = param_len;
+
+ status = smb2cli_ioctl(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ UINT64_MAX, /* in_fid_persistent */
+ UINT64_MAX, /* in_fid_volatile */
+ FSCTL_DFS_GET_REFERRALS,
+ 0, /* in_max_input_length */
+ &in_input_buffer,
+ CLI_BUFFER_SIZE, /* in_max_output_length */
+ &in_output_buffer,
+ SMB2_IOCTL_FLAG_IS_FSCTL,
+ talloc_tos(),
+ &out_input_buffer,
+ &out_output_buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (out_output_buffer.length < 4) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto out;
+ }
+
+ recv_flags2 = FLAGS2_UNICODE_STRINGS;
+ rdata = out_output_buffer.data;
+ endp = (char *)rdata + out_output_buffer.length;
+ } else {
+ unsigned int data_len = 0;
+ uint16_t setup[1];
+
+ SSVAL(setup, 0, TRANSACT2_GET_DFS_REFERRAL);
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2,
+ NULL, 0xffff, 0, 0,
+ setup, 1, 0,
+ param, param_len, 2,
+ NULL, 0, CLI_BUFFER_SIZE,
+ &recv_flags2,
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL,
+ &rdata, 4, &data_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ endp = (char *)rdata + data_len;
+ }
+
+ consumed_ucs = SVAL(rdata, 0);
+ num_referrals = SVAL(rdata, 2);
+
+ /* consumed_ucs is the number of bytes
+ * of the UCS2 path consumed not counting any
+ * terminating null. We need to convert
+ * back to unix charset and count again
+ * to get the number of bytes consumed from
+ * the incoming path. */
+
+ errno = 0;
+ if (pull_string_talloc(talloc_tos(),
+ NULL,
+ 0,
+ &consumed_path,
+ path_ucs,
+ consumed_ucs,
+ STR_UNICODE) == 0) {
+ if (errno != 0) {
+ status = map_nt_error_from_unix(errno);
+ } else {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ goto out;
+ }
+ if (consumed_path == NULL) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+ *consumed = strlen(consumed_path);
+
+ if (num_referrals != 0) {
+ uint16_t ref_version;
+ uint16_t ref_size;
+ int i;
+ uint16_t node_offset;
+
+ referrals = talloc_array(ctx, struct client_dfs_referral,
+ num_referrals);
+
+ if (!referrals) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /* start at the referrals array */
+
+ p = (char *)rdata+8;
+ for (i=0; i<num_referrals && p < endp; i++) {
+ if (p + 18 > endp) {
+ goto out;
+ }
+ ref_version = SVAL(p, 0);
+ ref_size = SVAL(p, 2);
+ node_offset = SVAL(p, 16);
+
+ if (ref_version != 3) {
+ p += ref_size;
+ continue;
+ }
+
+ referrals[i].proximity = SVAL(p, 8);
+ referrals[i].ttl = SVAL(p, 10);
+
+ if (p + node_offset > endp) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto out;
+ }
+ pull_string_talloc(referrals,
+ (const char *)rdata,
+ recv_flags2,
+ &referrals[i].dfspath,
+ p+node_offset,
+ PTR_DIFF(endp, p+node_offset),
+ STR_TERMINATE|STR_UNICODE);
+
+ if (!referrals[i].dfspath) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+ p += ref_size;
+ }
+ if (i < num_referrals) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto out;
+ }
+ }
+
+ *num_refs = num_referrals;
+ *refs = referrals;
+
+ out:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS cli_dfs_get_referral(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *path,
+ struct client_dfs_referral **refs,
+ size_t *num_refs,
+ size_t *consumed)
+{
+ return cli_dfs_get_referral_ex(ctx,
+ cli,
+ path,
+ 3,
+ refs, /* Max referral level we want */
+ num_refs,
+ consumed);
+}
+
+static bool cli_conn_have_dfs(struct cli_state *cli)
+{
+ struct smbXcli_conn *conn = cli->conn;
+ struct smbXcli_tcon *tcon = NULL;
+ bool ok;
+
+ if (smbXcli_conn_protocol(conn) < PROTOCOL_SMB2_02) {
+ uint32_t capabilities = smb1cli_conn_capabilities(conn);
+
+ if ((capabilities & CAP_STATUS32) == 0) {
+ return false;
+ }
+ if ((capabilities & CAP_UNICODE) == 0) {
+ return false;
+ }
+
+ tcon = cli->smb1.tcon;
+ } else {
+ tcon = cli->smb2.tcon;
+ }
+
+ ok = smbXcli_tcon_is_dfs_share(tcon);
+ return ok;
+}
+
+/********************************************************************
+********************************************************************/
+struct cli_dfs_path_split {
+ char *server;
+ char *share;
+ char *extrapath;
+};
+
+NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
+ const char *mountpt,
+ struct cli_credentials *creds,
+ struct cli_state *rootcli,
+ const char *path,
+ struct cli_state **targetcli,
+ char **pp_targetpath)
+{
+ struct client_dfs_referral *refs = NULL;
+ size_t num_refs = 0;
+ size_t consumed = 0;
+ struct cli_state *cli_ipc = NULL;
+ char *dfs_path = NULL;
+ char *cleanpath = NULL;
+ char *extrapath = NULL;
+ int pathlen;
+ struct cli_state *newcli = NULL;
+ struct cli_state *ccli = NULL;
+ size_t count = 0;
+ char *newpath = NULL;
+ char *newmount = NULL;
+ char *ppath = NULL;
+ SMB_STRUCT_STAT sbuf;
+ uint32_t attributes;
+ NTSTATUS status;
+ struct smbXcli_tcon *target_tcon = NULL;
+ struct cli_dfs_path_split *dfs_refs = NULL;
+ bool ok;
+ bool is_already_dfs = false;
+
+ if ( !rootcli || !path || !targetcli ) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Avoid more than one leading directory separator
+ */
+ while (IS_DIRECTORY_SEP(path[0]) && IS_DIRECTORY_SEP(path[1])) {
+ path++;
+ }
+
+ ok = cli_conn_have_dfs(rootcli);
+ if (!ok) {
+ *targetcli = rootcli;
+ *pp_targetpath = talloc_strdup(ctx, path);
+ if (!*pp_targetpath) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+
+ *targetcli = NULL;
+
+ is_already_dfs = cli_dfs_is_already_full_path(rootcli, path);
+ if (is_already_dfs) {
+ const char *localpath = NULL;
+ /*
+ * Given path is already converted to DFS.
+ * Convert to a local path so clean_path()
+ * can correctly strip any wildcards.
+ */
+ status = cli_dfs_target_check(ctx,
+ rootcli,
+ path,
+ &localpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ path = localpath;
+ }
+
+ /* Send a trans2_query_path_info to check for a referral. */
+
+ cleanpath = clean_path(ctx, path);
+ if (!cleanpath) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dfs_path = cli_dfs_make_full_path(ctx, rootcli, cleanpath);
+ if (!dfs_path) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_qpathinfo_basic( rootcli, dfs_path, &sbuf, &attributes);
+ if (NT_STATUS_IS_OK(status)) {
+ /* This is an ordinary path, just return it. */
+ *targetcli = rootcli;
+ *pp_targetpath = talloc_strdup(ctx, path);
+ if (!*pp_targetpath) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ goto done;
+ }
+
+ /* Special case where client asked for a path that does not exist */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ *targetcli = rootcli;
+ *pp_targetpath = talloc_strdup(ctx, path);
+ if (!*pp_targetpath) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ goto done;
+ }
+
+ /* We got an error, check for DFS referral. */
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_PATH_NOT_COVERED)) {
+ return status;
+ }
+
+ /* Check for the referral. */
+
+ status = cli_cm_open(ctx,
+ rootcli,
+ smbXcli_conn_remote_name(rootcli->conn),
+ "IPC$",
+ creds,
+ NULL, /* dest_ss not needed, we reuse the transport */
+ 0,
+ 0x20,
+ &cli_ipc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = cli_dfs_get_referral(ctx, cli_ipc, dfs_path, &refs,
+ &num_refs, &consumed);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!num_refs || !refs[0].dfspath) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /*
+ * Bug#10123 - DFS referral entries can be provided in a random order,
+ * so check the connection cache for each item to avoid unnecessary
+ * reconnections.
+ */
+ dfs_refs = talloc_array(ctx, struct cli_dfs_path_split, num_refs);
+ if (dfs_refs == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (count = 0; count < num_refs; count++) {
+ if (!split_dfs_path(dfs_refs, refs[count].dfspath,
+ &dfs_refs[count].server,
+ &dfs_refs[count].share,
+ &dfs_refs[count].extrapath)) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ ccli = cli_cm_find(rootcli, dfs_refs[count].server,
+ dfs_refs[count].share);
+ if (ccli != NULL) {
+ extrapath = dfs_refs[count].extrapath;
+ *targetcli = ccli;
+ break;
+ }
+ }
+
+ /*
+ * If no cached connection was found, then connect to the first live
+ * referral server in the list.
+ */
+ for (count = 0; (ccli == NULL) && (count < num_refs); count++) {
+ /* Connect to the target server & share */
+ status = cli_cm_connect(ctx, rootcli,
+ dfs_refs[count].server,
+ dfs_refs[count].share,
+ creds,
+ NULL, /* dest_ss */
+ 0, /* port */
+ 0x20,
+ targetcli);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Unable to follow dfs referral [\\%s\\%s]\n",
+ dfs_refs[count].server,
+ dfs_refs[count].share);
+ continue;
+ } else {
+ extrapath = dfs_refs[count].extrapath;
+ break;
+ }
+ }
+
+ /* No available referral server for the connection */
+ if (*targetcli == NULL) {
+ TALLOC_FREE(dfs_refs);
+ return status;
+ }
+
+ /* Make sure to recreate the original string including any wildcards. */
+
+ dfs_path = cli_dfs_make_full_path(ctx, rootcli, path);
+ if (!dfs_path) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NO_MEMORY;
+ }
+ pathlen = strlen(dfs_path);
+ consumed = MIN(pathlen, consumed);
+ *pp_targetpath = talloc_strdup(ctx, &dfs_path[consumed]);
+ if (!*pp_targetpath) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NO_MEMORY;
+ }
+ dfs_path[consumed] = '\0';
+
+ /*
+ * *pp_targetpath is now the unconsumed part of the path.
+ * dfs_path is now the consumed part of the path
+ * (in \server\share\path format).
+ */
+
+ if (extrapath && strlen(extrapath) > 0) {
+ /* EMC Celerra NAS version 5.6.50 (at least) doesn't appear to */
+ /* put the trailing \ on the path, so to be safe we put one in if needed */
+ if (extrapath[strlen(extrapath)-1] != '\\' && **pp_targetpath != '\\') {
+ *pp_targetpath = talloc_asprintf(ctx,
+ "%s\\%s",
+ extrapath,
+ *pp_targetpath);
+ } else {
+ *pp_targetpath = talloc_asprintf(ctx,
+ "%s%s",
+ extrapath,
+ *pp_targetpath);
+ }
+ if (!*pp_targetpath) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* parse out the consumed mount path */
+ /* trim off the \server\share\ */
+
+ ppath = dfs_path;
+
+ if (*ppath != '\\') {
+ d_printf("cli_resolve_path: "
+ "dfs_path (%s) not in correct format.\n",
+ dfs_path );
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ ppath++; /* Now pointing at start of server name. */
+
+ if ((ppath = strchr_m( dfs_path, '\\' )) == NULL) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ ppath++; /* Now pointing at start of share name. */
+
+ if ((ppath = strchr_m( ppath+1, '\\' )) == NULL) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ ppath++; /* Now pointing at path component. */
+
+ newmount = talloc_asprintf(ctx, "%s\\%s", mountpt, ppath );
+ if (!newmount) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /* Check for another dfs referral, note that we are not
+ checking for loops here. */
+
+ if (!strequal(*pp_targetpath, "\\") && !strequal(*pp_targetpath, "/")) {
+ status = cli_resolve_path(ctx,
+ newmount,
+ creds,
+ *targetcli,
+ *pp_targetpath,
+ &newcli,
+ &newpath);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * When cli_resolve_path returns true here it's always
+ * returning the complete path in newpath, so we're done
+ * here.
+ */
+ *targetcli = newcli;
+ *pp_targetpath = newpath;
+ TALLOC_FREE(dfs_refs);
+ return status;
+ }
+ }
+
+ done:
+
+ if (smbXcli_conn_protocol((*targetcli)->conn) >= PROTOCOL_SMB2_02) {
+ target_tcon = (*targetcli)->smb2.tcon;
+ } else {
+ target_tcon = (*targetcli)->smb1.tcon;
+ }
+
+ /* If returning true ensure we return a dfs root full path. */
+ if (smbXcli_tcon_is_dfs_share(target_tcon)) {
+ dfs_path = talloc_strdup(ctx, *pp_targetpath);
+ if (!dfs_path) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *pp_targetpath = cli_dfs_make_full_path(ctx, *targetcli, dfs_path);
+ if (*pp_targetpath == NULL) {
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ TALLOC_FREE(dfs_refs);
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *sharename,
+ char **pp_newserver,
+ char **pp_newshare,
+ struct cli_credentials *creds)
+{
+ struct client_dfs_referral *refs = NULL;
+ size_t num_refs = 0;
+ size_t consumed = 0;
+ char *fullpath = NULL;
+ bool res;
+ struct smbXcli_tcon *orig_tcon = NULL;
+ char *orig_share = NULL;
+ char *newextrapath = NULL;
+ NTSTATUS status;
+ const char *remote_name;
+ enum smb_encryption_setting encryption_state =
+ cli_credentials_get_smb_encryption(creds);
+
+ if (!cli || !sharename) {
+ return false;
+ }
+
+ remote_name = smbXcli_conn_remote_name(cli->conn);
+
+ /* special case. never check for a referral on the IPC$ share */
+
+ if (strequal(sharename, "IPC$")) {
+ return false;
+ }
+
+ /* send a trans2_query_path_info to check for a referral */
+
+ fullpath = talloc_asprintf(ctx, "\\%s\\%s", remote_name, sharename);
+ if (!fullpath) {
+ return false;
+ }
+
+ /* Store tcon state. */
+ if (cli_state_has_tcon(cli)) {
+ cli_state_save_tcon_share(cli, &orig_tcon, &orig_share);
+ }
+
+ /* check for the referral */
+
+ if (!NT_STATUS_IS_OK(cli_tree_connect(cli, "IPC$", "IPC", NULL))) {
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ return false;
+ }
+
+ if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
+ status = cli_cm_force_encryption_creds(cli, creds, "IPC$");
+ if (!NT_STATUS_IS_OK(status)) {
+ switch (encryption_state) {
+ case SMB_ENCRYPTION_DESIRED:
+ break;
+ case SMB_ENCRYPTION_REQUIRED:
+ default:
+ /*
+ * Failed to set up encryption.
+ * Disconnect the temporary IPC$
+ * tcon before restoring the original
+ * tcon so we don't leak it.
+ */
+ cli_tdis(cli);
+ cli_state_restore_tcon_share(cli,
+ orig_tcon,
+ orig_share);
+ return false;
+ }
+ }
+ }
+
+ status = cli_dfs_get_referral(ctx, cli, fullpath, &refs,
+ &num_refs, &consumed);
+ res = NT_STATUS_IS_OK(status);
+
+ status = cli_tdis(cli);
+
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (!res || !num_refs) {
+ return false;
+ }
+
+ if (!refs[0].dfspath) {
+ return false;
+ }
+
+ if (!split_dfs_path(ctx, refs[0].dfspath, pp_newserver,
+ pp_newshare, &newextrapath)) {
+ return false;
+ }
+
+ /* check that this is not a self-referral */
+
+ if (strequal(remote_name, *pp_newserver) &&
+ strequal(sharename, *pp_newshare)) {
+ return false;
+ }
+
+ return true;
+}
+
+/********************************************************************
+ Windows and NetApp (and arguably the SMB1/2/3 specs) expect a non-DFS
+ path for the targets of rename and hardlink. If we have been given
+ a DFS path for these calls, convert it back into a local path by
+ stripping off the DFS prefix.
+********************************************************************/
+
+NTSTATUS cli_dfs_target_check(TALLOC_CTX *mem_ctx,
+ struct cli_state *cli,
+ const char *fname_dst,
+ const char **fname_dst_out)
+{
+ char *dfs_prefix = NULL;
+ size_t prefix_len = 0;
+ struct smbXcli_tcon *tcon = NULL;
+
+ if (!smbXcli_conn_dfs_supported(cli->conn)) {
+ goto copy_fname_out;
+ }
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ tcon = cli->smb2.tcon;
+ } else {
+ tcon = cli->smb1.tcon;
+ }
+ if (!smbXcli_tcon_is_dfs_share(tcon)) {
+ goto copy_fname_out;
+ }
+ dfs_prefix = cli_dfs_make_full_path(mem_ctx, cli, "");
+ if (dfs_prefix == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ prefix_len = strlen(dfs_prefix);
+ if (strncmp(fname_dst, dfs_prefix, prefix_len) != 0) {
+ /*
+ * Prefix doesn't match. Assume it was
+ * already stripped or not added in the
+ * first place.
+ */
+ goto copy_fname_out;
+ }
+ /* Return the trailing name after the prefix. */
+ *fname_dst_out = &fname_dst[prefix_len];
+ TALLOC_FREE(dfs_prefix);
+ return NT_STATUS_OK;
+
+ copy_fname_out:
+
+ /*
+ * No change to the destination name. Just
+ * point it at the incoming destination name.
+ */
+ *fname_dst_out = fname_dst;
+ TALLOC_FREE(dfs_prefix);
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Convert a pathname into a DFS path if it hasn't already been converted.
+ Always returns a talloc'ed path, makes it easy to pass const paths in.
+********************************************************************/
+
+char *smb1_dfs_share_path(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *path)
+{
+ bool is_dfs = smbXcli_conn_dfs_supported(cli->conn) &&
+ smbXcli_tcon_is_dfs_share(cli->smb1.tcon);
+ bool is_already_dfs_path = false;
+ bool posix = (cli->requested_posix_capabilities &
+ CIFS_UNIX_POSIX_PATHNAMES_CAP);
+ char sepchar = (posix ? '/' : '\\');
+
+ if (!is_dfs) {
+ return talloc_strdup(ctx, path);
+ }
+ is_already_dfs_path = cli_dfs_is_already_full_path(cli, path);
+ if (is_already_dfs_path) {
+ return talloc_strdup(ctx, path);
+ }
+ /*
+ * We don't use cli_dfs_make_full_path() as,
+ * when given a null path, cli_dfs_make_full_path
+ * deliberately adds a trailing '\\' (this is by
+ * design to check for an existing DFS prefix match).
+ */
+ if (path[0] == '\0') {
+ return talloc_asprintf(ctx,
+ "%c%s%c%s",
+ sepchar,
+ smbXcli_conn_remote_name(cli->conn),
+ sepchar,
+ cli->share);
+ }
+ while (*path == sepchar) {
+ path++;
+ }
+ return talloc_asprintf(ctx,
+ "%c%s%c%s%c%s",
+ sepchar,
+ smbXcli_conn_remote_name(cli->conn),
+ sepchar,
+ cli->share,
+ sepchar,
+ path);
+}
diff --git a/source3/libsmb/clidgram.c b/source3/libsmb/clidgram.c
new file mode 100644
index 0000000..a45bdac
--- /dev/null
+++ b/source3/libsmb/clidgram.c
@@ -0,0 +1,482 @@
+/*
+ Unix SMB/CIFS implementation.
+ client dgram calls
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Richard Sharpe 2001
+ Copyright (C) John Terpstra 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "libsmb/clidgram.h"
+#include "libsmb/nmblib.h"
+#include "libsmb/unexpected.h"
+#include "messages.h"
+#include "librpc/gen_ndr/samr.h"
+#include "../lib/util/pidfile.h"
+#include "lib/util/string_wrappers.h"
+
+/*
+ * cli_send_mailslot, send a mailslot for client code ...
+ */
+
+static bool cli_prep_mailslot(bool unique, const char *mailslot,
+ uint16_t priority,
+ char *buf, int len,
+ const char *srcname, int src_type,
+ const char *dstname, int dest_type,
+ const struct sockaddr_storage *dest_ss,
+ int dgm_id,
+ struct packet_struct *p)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ char *ptr, *p2;
+ char tmp[4];
+ char addr[INET6_ADDRSTRLEN];
+
+ ZERO_STRUCTP(p);
+
+ /*
+ * Next, build the DGRAM ...
+ */
+
+ /* DIRECT GROUP or UNIQUE datagram. */
+ dgram->header.msg_type = unique ? 0x10 : 0x11;
+ dgram->header.flags.node_type = M_NODE;
+ dgram->header.flags.first = True;
+ dgram->header.flags.more = False;
+ dgram->header.dgm_id = dgm_id;
+ /* source ip is filled by nmbd */
+ dgram->header.dgm_length = 0; /* Let build_dgram() handle this. */
+ dgram->header.packet_offset = 0;
+
+ make_nmb_name(&dgram->source_name,srcname,src_type);
+ make_nmb_name(&dgram->dest_name,dstname,dest_type);
+
+ ptr = &dgram->data[0];
+
+ /* Setup the smb part. */
+ ptr -= 4; /* XXX Ugliness because of handling of tcp SMB length. */
+ memcpy(tmp,ptr,4);
+
+ if (smb_size + 17*2 + strlen(mailslot) + 1 + len > MAX_DGRAM_SIZE) {
+ DEBUG(0, ("cli_send_mailslot: Cannot write beyond end of packet\n"));
+ return False;
+ }
+
+ cli_set_message(ptr,17,strlen(mailslot) + 1 + len,True);
+ memcpy(ptr,tmp,4);
+
+ SCVAL(ptr,smb_com,SMBtrans);
+ SSVAL(ptr,smb_vwv1,len);
+ SSVAL(ptr,smb_vwv11,len);
+ SSVAL(ptr,smb_vwv12,70 + strlen(mailslot));
+ SSVAL(ptr,smb_vwv13,3);
+ SSVAL(ptr,smb_vwv14,1);
+ SSVAL(ptr,smb_vwv15,priority);
+ SSVAL(ptr,smb_vwv16,2);
+ p2 = smb_buf(ptr);
+ fstrcpy(p2,mailslot);
+ p2 = skip_string(ptr,MAX_DGRAM_SIZE,p2);
+ if (!p2) {
+ return False;
+ }
+
+ memcpy(p2,buf,len);
+ p2 += len;
+
+ dgram->datasize = PTR_DIFF(p2,ptr+4); /* +4 for tcp length. */
+
+ p->packet_type = DGRAM_PACKET;
+ p->ip = ((const struct sockaddr_in *)dest_ss)->sin_addr;
+ p->timestamp = time(NULL);
+
+ DEBUG(4,("send_mailslot: Sending to mailslot %s from %s ",
+ mailslot, nmb_namestr(&dgram->source_name)));
+ print_sockaddr(addr, sizeof(addr), dest_ss);
+
+ DEBUGADD(4,("to %s IP %s\n", nmb_namestr(&dgram->dest_name), addr));
+
+ return true;
+}
+
+static char *mailslot_name(TALLOC_CTX *mem_ctx, struct in_addr dc_ip)
+{
+ return talloc_asprintf(mem_ctx, "%s%X",
+ NBT_MAILSLOT_GETDC, dc_ip.s_addr);
+}
+
+static bool prep_getdc_request(const struct sockaddr_storage *dc_ss,
+ const char *account_name,
+ uint32_t account_flags,
+ const char *domain_name,
+ const struct dom_sid *sid,
+ uint32_t nt_version,
+ const char *my_mailslot,
+ int dgm_id,
+ struct packet_struct *p)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct nbt_netlogon_packet packet;
+ struct NETLOGON_SAM_LOGON_REQUEST *s;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = data_blob_null;
+ struct dom_sid my_sid;
+ bool ret = false;
+
+ ZERO_STRUCT(packet);
+ ZERO_STRUCT(my_sid);
+
+ if (sid != NULL) {
+ my_sid = *sid;
+ }
+
+ packet.command = LOGON_SAM_LOGON_REQUEST;
+ s = &packet.req.logon;
+
+ s->request_count = 0;
+ s->computer_name = lp_netbios_name();
+ s->user_name = account_name;
+ s->mailslot_name = my_mailslot;
+ s->acct_control = account_flags;
+ s->sid = my_sid;
+ s->nt_version = nt_version;
+ s->lmnt_token = 0xffff;
+ s->lm20_token = 0xffff;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(nbt_netlogon_packet, &packet);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &packet,
+ (ndr_push_flags_fn_t)ndr_push_nbt_netlogon_packet);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ goto fail;
+ }
+
+ ret = cli_prep_mailslot(false, NBT_MAILSLOT_NTLOGON, 0,
+ (char *)blob.data, blob.length,
+ lp_netbios_name(), 0, domain_name, 0x1c,
+ dc_ss, dgm_id, p);
+fail:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static bool parse_getdc_response(
+ struct packet_struct *packet,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ uint32_t *nt_version,
+ const char **dc_name,
+ struct netlogon_samlogon_response **samlogon_response)
+{
+ DATA_BLOB blob;
+ struct netlogon_samlogon_response *r;
+ union dgram_message_body p;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ const char *returned_dc = NULL;
+ const char *returned_domain = NULL;
+
+ blob = data_blob_const(packet->packet.dgram.data,
+ packet->packet.dgram.datasize);
+ if (blob.length < 4) {
+ DEBUG(1, ("invalid length: %d\n", (int)blob.length));
+ return false;
+ }
+
+ if (RIVAL(blob.data,0) != DGRAM_SMB) {
+ DEBUG(1, ("invalid packet\n"));
+ return false;
+ }
+
+ blob.data += 4;
+ blob.length -= 4;
+
+ ndr_err = ndr_pull_union_blob_all(&blob, mem_ctx, &p, DGRAM_SMB,
+ (ndr_pull_flags_fn_t)ndr_pull_dgram_smb_packet);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1, ("failed to parse packet\n"));
+ return false;
+ }
+
+ if (p.smb.smb_command != SMB_TRANSACTION) {
+ DEBUG(1, ("invalid smb_command: %d\n", p.smb.smb_command));
+ return false;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(dgram_smb_packet, &p);
+ }
+
+ blob = p.smb.body.trans.data;
+
+ r = talloc_zero(mem_ctx, struct netlogon_samlogon_response);
+ if (!r) {
+ return false;
+ }
+
+ status = pull_netlogon_samlogon_response(&blob, r, r);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(r);
+ return false;
+ }
+
+ map_netlogon_samlogon_response(r);
+
+ /* do we still need this ? */
+ *nt_version = r->ntver;
+
+ returned_domain = r->data.nt5_ex.domain_name;
+ returned_dc = r->data.nt5_ex.pdc_name;
+
+ if (!strequal(returned_domain, domain_name)) {
+ DEBUG(3, ("GetDC: Expected domain %s, got %s\n",
+ domain_name, returned_domain));
+ TALLOC_FREE(r);
+ return false;
+ }
+
+ if (*returned_dc == '\\') returned_dc += 1;
+ if (*returned_dc == '\\') returned_dc += 1;
+
+ *dc_name = talloc_strdup(mem_ctx, returned_dc);
+ if (!*dc_name) {
+ TALLOC_FREE(r);
+ return false;
+ }
+
+ if (samlogon_response) {
+ *samlogon_response = r;
+ } else {
+ TALLOC_FREE(r);
+ }
+
+ DEBUG(10, ("GetDC gave name %s for domain %s\n",
+ *dc_name, returned_domain));
+
+ return True;
+}
+
+struct nbt_getdc_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct nb_packet_reader *reader;
+ const char *my_mailslot;
+ pid_t nmbd_pid;
+
+ const struct sockaddr_storage *dc_addr;
+ const char *domain_name;
+ const struct dom_sid *sid;
+ uint32_t nt_version;
+ const char *dc_name;
+ struct netlogon_samlogon_response *samlogon_response;
+
+ struct packet_struct p;
+};
+
+static void nbt_getdc_got_reader(struct tevent_req *subreq);
+static void nbt_getdc_got_response(struct tevent_req *subreq);
+
+struct tevent_req *nbt_getdc_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const struct sockaddr_storage *dc_addr,
+ const char *domain_name,
+ const struct dom_sid *sid,
+ const char *account_name,
+ uint32_t account_flags,
+ uint32_t nt_version)
+{
+ struct tevent_req *req, *subreq;
+ struct nbt_getdc_state *state;
+ uint16_t dgm_id;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct nbt_getdc_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->dc_addr = dc_addr;
+ state->domain_name = domain_name;
+ state->sid = sid;
+ state->nt_version = nt_version;
+
+ if (dc_addr->ss_family != AF_INET) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+ state->my_mailslot = mailslot_name(
+ state, ((const struct sockaddr_in *)dc_addr)->sin_addr);
+ if (tevent_req_nomem(state->my_mailslot, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->nmbd_pid = pidfile_pid(lp_pid_directory(), "nmbd");
+ if (state->nmbd_pid == 0) {
+ DEBUG(3, ("No nmbd found\n"));
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ generate_random_buffer((uint8_t *)(void *)&dgm_id, sizeof(dgm_id));
+
+ ok = prep_getdc_request(dc_addr, account_name, account_flags,
+ domain_name, sid, nt_version,
+ state->my_mailslot, dgm_id & 0x7fff,
+ &state->p);
+
+ if (!ok) {
+ DEBUG(3, ("prep_getdc_request failed\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = nb_packet_reader_send(state, ev, DGRAM_PACKET, -1,
+ state->my_mailslot);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, nbt_getdc_got_reader, req);
+ return req;
+}
+
+static void nbt_getdc_got_reader(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nbt_getdc_state *state = tevent_req_data(
+ req, struct nbt_getdc_state);
+ NTSTATUS status;
+
+ status = nb_packet_reader_recv(subreq, state, &state->reader);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("nb_packet_reader_recv returned %s\n",
+ nt_errstr(status)));
+ return;
+ }
+
+ status = messaging_send_buf(
+ state->msg_ctx, pid_to_procid(state->nmbd_pid),
+ MSG_SEND_PACKET, (uint8_t *)&state->p, sizeof(state->p));
+
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("messaging_send_buf returned %s\n",
+ nt_errstr(status)));
+ return;
+ }
+ subreq = nb_packet_read_send(state, state->ev, state->reader);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nbt_getdc_got_response, req);
+}
+
+static void nbt_getdc_got_response(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nbt_getdc_state *state = tevent_req_data(
+ req, struct nbt_getdc_state);
+ struct packet_struct *p;
+ NTSTATUS status;
+ bool ret;
+
+ status = nb_packet_read_recv(subreq, state, &p);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ret = parse_getdc_response(p, state, state->domain_name,
+ &state->nt_version, &state->dc_name,
+ &state->samlogon_response);
+ if (!ret) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS nbt_getdc_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *nt_version, const char **dc_name,
+ struct netlogon_samlogon_response **samlogon_response)
+{
+ struct nbt_getdc_state *state = tevent_req_data(
+ req, struct nbt_getdc_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (nt_version != NULL) {
+ *nt_version = state->nt_version;
+ }
+ if (dc_name != NULL) {
+ *dc_name = talloc_move(mem_ctx, &state->dc_name);
+ }
+ if (samlogon_response != NULL) {
+ *samlogon_response = talloc_move(
+ mem_ctx, &state->samlogon_response);
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS nbt_getdc(struct messaging_context *msg_ctx,
+ uint32_t timeout_in_seconds,
+ const struct sockaddr_storage *dc_addr,
+ const char *domain_name,
+ const struct dom_sid *sid,
+ const char *account_name,
+ uint32_t account_flags,
+ uint32_t nt_version,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *pnt_version,
+ const char **dc_name,
+ struct netlogon_samlogon_response **samlogon_response)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = nbt_getdc_send(ev, ev, msg_ctx, dc_addr, domain_name,
+ sid, account_name, account_flags, nt_version);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_set_endtime(req, ev,
+ timeval_current_ofs(timeout_in_seconds, 0))) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = nbt_getdc_recv(req, mem_ctx, pnt_version, dc_name,
+ samlogon_response);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/clidgram.h b/source3/libsmb/clidgram.h
new file mode 100644
index 0000000..46e11f4
--- /dev/null
+++ b/source3/libsmb/clidgram.h
@@ -0,0 +1,54 @@
+/*
+ Unix SMB/CIFS implementation.
+ client dgram calls
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Richard Sharpe 2001
+ Copyright (C) John Terpstra 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBSMB_CLIDGRAM_H_
+#define _LIBSMB_CLIDGRAM_H_
+
+#include "../libcli/netlogon/netlogon.h"
+
+/* The following definitions come from libsmb/clidgram.c */
+
+struct tevent_req *nbt_getdc_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const struct sockaddr_storage *dc_addr,
+ const char *domain_name,
+ const struct dom_sid *sid,
+ const char *account_name,
+ uint32_t account_flags,
+ uint32_t nt_version);
+NTSTATUS nbt_getdc_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *nt_version, const char **dc_name,
+ struct netlogon_samlogon_response **samlogon_response);
+NTSTATUS nbt_getdc(struct messaging_context *msg_ctx,
+ uint32_t timeout_in_seconds,
+ const struct sockaddr_storage *dc_addr,
+ const char *domain_name,
+ const struct dom_sid *sid,
+ const char *account_name,
+ uint32_t account_flags,
+ uint32_t nt_version,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *pnt_version,
+ const char **dc_name,
+ struct netlogon_samlogon_response **samlogon_response);
+
+#endif /* _LIBSMB_CLIDGRAM_H_ */
diff --git a/source3/libsmb/clientgen.c b/source3/libsmb/clientgen.c
new file mode 100644
index 0000000..bec1184
--- /dev/null
+++ b/source3/libsmb/clientgen.c
@@ -0,0 +1,650 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB client generic functions
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../libcli/smb/smb_signing.h"
+#include "../libcli/smb/smb_seal.h"
+#include "async_smb.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "../libcli/smb/smb2_negotiate_context.h"
+#include "../librpc/ndr/libndr.h"
+#include "../include/client.h"
+
+/****************************************************************************
+ Change the timeout (in milliseconds).
+****************************************************************************/
+
+unsigned int cli_set_timeout(struct cli_state *cli, unsigned int timeout)
+{
+ unsigned int old_timeout = cli->timeout;
+ DBG_DEBUG("Changing connection timeout for server '%s' from %d (ms) to "
+ "%d (ms).\n",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->timeout,
+ timeout);
+ cli->timeout = timeout;
+ return old_timeout;
+}
+
+/****************************************************************************
+ Set the 'backup_intent' flag.
+****************************************************************************/
+
+bool cli_set_backup_intent(struct cli_state *cli, bool flag)
+{
+ bool old_state = cli->backup_intent;
+ cli->backup_intent = flag;
+ return old_state;
+}
+
+/****************************************************************************
+ Initialise a client structure. Always returns a talloc'ed struct.
+ Set the signing state (used from the command line).
+****************************************************************************/
+
+struct GUID cli_state_client_guid;
+
+struct cli_state *cli_state_create(TALLOC_CTX *mem_ctx,
+ int fd,
+ const char *remote_name,
+ enum smb_signing_setting signing_state,
+ int flags)
+{
+ struct cli_state *cli = NULL;
+ bool use_spnego = lp_client_use_spnego();
+ bool force_dos_errors = false;
+ bool force_ascii = false;
+ bool use_level_II_oplocks = false;
+ uint32_t smb1_capabilities = 0;
+ uint32_t smb2_capabilities = 0;
+ struct smb311_capabilities smb3_capabilities =
+ smb311_capabilities_parse("client",
+ lp_client_smb3_signing_algorithms(),
+ lp_client_smb3_encryption_algorithms());
+ struct GUID client_guid;
+
+ if (!GUID_all_zero(&cli_state_client_guid)) {
+ client_guid = cli_state_client_guid;
+ } else {
+ const char *str = NULL;
+
+ str = lp_parm_const_string(-1, "libsmb", "client_guid", NULL);
+ if (str != NULL) {
+ GUID_from_string(str, &client_guid);
+ } else {
+ client_guid = GUID_random();
+ }
+ }
+
+ /* Check the effective uid - make sure we are not setuid */
+ if (is_setuid_root()) {
+ DEBUG(0,("libsmb based programs must *NOT* be setuid root.\n"));
+ return NULL;
+ }
+
+ cli = talloc_zero(mem_ctx, struct cli_state);
+ if (!cli) {
+ return NULL;
+ }
+
+ cli->server_domain = talloc_strdup(cli, "");
+ if (!cli->server_domain) {
+ goto error;
+ }
+ cli->server_os = talloc_strdup(cli, "");
+ if (!cli->server_os) {
+ goto error;
+ }
+ cli->server_type = talloc_strdup(cli, "");
+ if (!cli->server_type) {
+ goto error;
+ }
+
+ cli->raw_status = NT_STATUS_INTERNAL_ERROR;
+ cli->map_dos_errors = true; /* remove this */
+ cli->timeout = CLIENT_TIMEOUT;
+
+ /* Set the CLI_FORCE_DOSERR environment variable to test
+ client routines using DOS errors instead of STATUS32
+ ones. This intended only as a temporary hack. */
+ if (getenv("CLI_FORCE_DOSERR")) {
+ force_dos_errors = true;
+ }
+ if (flags & CLI_FULL_CONNECTION_FORCE_DOS_ERRORS) {
+ force_dos_errors = true;
+ }
+
+ if (getenv("CLI_FORCE_ASCII")) {
+ force_ascii = true;
+ }
+ if (!lp_unicode()) {
+ force_ascii = true;
+ }
+ if (flags & CLI_FULL_CONNECTION_FORCE_ASCII) {
+ force_ascii = true;
+ }
+
+ if (flags & CLI_FULL_CONNECTION_DONT_SPNEGO) {
+ use_spnego = false;
+ }
+
+ if (flags & CLI_FULL_CONNECTION_OPLOCKS) {
+ cli->use_oplocks = true;
+ }
+ if (flags & CLI_FULL_CONNECTION_LEVEL_II_OPLOCKS) {
+ use_level_II_oplocks = true;
+ }
+
+ if (signing_state == SMB_SIGNING_IPC_DEFAULT) {
+ /*
+ * Ensure for IPC/RPC the default is to require
+ * signing unless explicitly turned off by the
+ * administrator.
+ */
+ signing_state = lp_client_ipc_signing();
+ }
+
+ if (signing_state == SMB_SIGNING_DEFAULT) {
+ signing_state = lp_client_signing();
+ }
+
+ smb1_capabilities = 0;
+ smb1_capabilities |= CAP_LARGE_FILES;
+ smb1_capabilities |= CAP_NT_SMBS | CAP_RPC_REMOTE_APIS;
+ smb1_capabilities |= CAP_LOCK_AND_READ | CAP_NT_FIND;
+ smb1_capabilities |= CAP_DFS | CAP_W2K_SMBS;
+ smb1_capabilities |= CAP_LARGE_READX|CAP_LARGE_WRITEX;
+ smb1_capabilities |= CAP_LWIO;
+
+ if (!force_dos_errors) {
+ smb1_capabilities |= CAP_STATUS32;
+ }
+
+ if (!force_ascii) {
+ smb1_capabilities |= CAP_UNICODE;
+ }
+
+ if (use_spnego) {
+ smb1_capabilities |= CAP_EXTENDED_SECURITY;
+ }
+
+ if (use_level_II_oplocks) {
+ smb1_capabilities |= CAP_LEVEL_II_OPLOCKS;
+ }
+
+ smb2_capabilities = SMB2_CAP_ALL;
+
+ cli->conn = smbXcli_conn_create(cli, fd, remote_name,
+ signing_state,
+ smb1_capabilities,
+ &client_guid,
+ smb2_capabilities,
+ &smb3_capabilities);
+ if (cli->conn == NULL) {
+ goto error;
+ }
+
+ cli->smb1.pid = (uint32_t)getpid();
+ cli->smb1.vc_num = cli->smb1.pid;
+ cli->smb1.session = smbXcli_session_create(cli, cli->conn);
+ if (cli->smb1.session == NULL) {
+ goto error;
+ }
+
+ cli->initialised = 1;
+ return cli;
+
+ /* Clean up after malloc() error */
+
+ error:
+
+ TALLOC_FREE(cli);
+ return NULL;
+}
+
+/****************************************************************************
+ Close all pipes open on this session.
+****************************************************************************/
+
+static void cli_nt_pipes_close(struct cli_state *cli)
+{
+ while (cli->pipe_list != NULL) {
+ /*
+ * No TALLOC_FREE here!
+ */
+ talloc_free(cli->pipe_list);
+ }
+}
+
+/****************************************************************************
+ Shutdown a client structure.
+****************************************************************************/
+
+static void _cli_shutdown(struct cli_state *cli)
+{
+ cli_nt_pipes_close(cli);
+
+ /*
+ * tell our peer to free his resources. Without this, when an
+ * application attempts to do a graceful shutdown and calls
+ * smbc_free_context() to clean up all connections, some connections
+ * can remain active on the peer end, until some (long) timeout period
+ * later. This tree disconnect forces the peer to clean up, since the
+ * connection will be going away.
+ */
+ if (cli_state_has_tcon(cli)) {
+ cli_tdis(cli);
+ }
+
+ smbXcli_conn_disconnect(cli->conn, NT_STATUS_OK);
+
+ TALLOC_FREE(cli);
+}
+
+void cli_shutdown(struct cli_state *cli)
+{
+ struct cli_state *cli_head;
+ if (cli == NULL) {
+ return;
+ }
+ DLIST_HEAD(cli, cli_head);
+ if (cli_head == cli) {
+ /*
+ * head of a DFS list, shutdown all subsidiary DFS
+ * connections.
+ */
+ struct cli_state *p, *next;
+
+ for (p = cli_head->next; p; p = next) {
+ next = p->next;
+ DLIST_REMOVE(cli_head, p);
+ _cli_shutdown(p);
+ }
+ } else {
+ DLIST_REMOVE(cli_head, cli);
+ }
+
+ _cli_shutdown(cli);
+}
+
+uint16_t cli_state_get_vc_num(struct cli_state *cli)
+{
+ return cli->smb1.vc_num;
+}
+
+/****************************************************************************
+ Set the PID to use for smb messages. Return the old pid.
+****************************************************************************/
+
+uint32_t cli_setpid(struct cli_state *cli, uint32_t pid)
+{
+ uint32_t ret = cli->smb1.pid;
+ cli->smb1.pid = pid;
+ return ret;
+}
+
+uint32_t cli_getpid(struct cli_state *cli)
+{
+ return cli->smb1.pid;
+}
+
+bool cli_state_is_encryption_on(struct cli_state *cli)
+{
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
+ return smb1cli_conn_encryption_on(cli->conn);
+ }
+
+ if (cli->smb2.tcon == NULL) {
+ return false;
+ }
+
+ return smb2cli_tcon_is_encryption_on(cli->smb2.tcon);
+}
+
+bool cli_state_has_tcon(struct cli_state *cli)
+{
+ uint32_t tid;
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ if (cli->smb2.tcon == NULL) {
+ return false;
+ }
+ tid = cli_state_get_tid(cli);
+ if (tid == UINT32_MAX) {
+ return false;
+ }
+ } else {
+ if (cli->smb1.tcon == NULL) {
+ return false;
+ }
+ tid = cli_state_get_tid(cli);
+ if (tid == UINT16_MAX) {
+ return false;
+ }
+ }
+ return true;
+}
+
+uint32_t cli_state_get_tid(struct cli_state *cli)
+{
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return smb2cli_tcon_current_id(cli->smb2.tcon);
+ } else {
+ return (uint32_t)smb1cli_tcon_current_id(cli->smb1.tcon);
+ }
+}
+
+uint32_t cli_state_set_tid(struct cli_state *cli, uint32_t tid)
+{
+ uint32_t ret;
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ ret = smb2cli_tcon_current_id(cli->smb2.tcon);
+ smb2cli_tcon_set_id(cli->smb2.tcon, tid);
+ } else {
+ ret = smb1cli_tcon_current_id(cli->smb1.tcon);
+ smb1cli_tcon_set_id(cli->smb1.tcon, tid);
+ }
+ return ret;
+}
+
+static struct smbXcli_tcon *cli_state_save_tcon(struct cli_state *cli)
+{
+ /*
+ * Note. This used to make a deep copy of either
+ * cli->smb2.tcon or cli->smb1.tcon, but this leaves
+ * the original pointer in place which will then get
+ * TALLOC_FREE()'d when the new connection is made on
+ * this cli_state.
+ *
+ * As there may be pipes open on the old connection with
+ * talloc'ed state allocated using the tcon pointer as a
+ * parent we can't deep copy and then free this as that
+ * closes the open pipes.
+ *
+ * This call is used to temporarily swap out a tcon pointer
+ * to allow a new tcon on the same cli_state.
+ *
+ * Just return the raw pointer and set the old value to NULL.
+ * We know we MUST be calling cli_state_restore_tcon() below
+ * to restore before closing the session.
+ *
+ * See BUG: https://bugzilla.samba.org/show_bug.cgi?id=13992
+ */
+ struct smbXcli_tcon *tcon_ret = NULL;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ tcon_ret = cli->smb2.tcon;
+ cli->smb2.tcon = NULL; /* *Not* TALLOC_FREE(). */
+ } else {
+ tcon_ret = cli->smb1.tcon;
+ cli->smb1.tcon = NULL; /* *Not* TALLOC_FREE(). */
+ }
+ return tcon_ret;
+}
+
+void cli_state_save_tcon_share(struct cli_state *cli,
+ struct smbXcli_tcon **_tcon_ret,
+ char **_sharename_ret)
+{
+ *_tcon_ret = cli_state_save_tcon(cli);
+ /*
+ * No talloc_copy as cli->share is already
+ * allocated off cli.
+ */
+ *_sharename_ret = cli->share;
+ cli->share = NULL;
+}
+
+static void cli_state_restore_tcon(struct cli_state *cli,
+ struct smbXcli_tcon *tcon)
+{
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ TALLOC_FREE(cli->smb2.tcon);
+ cli->smb2.tcon = tcon;
+ } else {
+ TALLOC_FREE(cli->smb1.tcon);
+ cli->smb1.tcon = tcon;
+ }
+}
+
+void cli_state_restore_tcon_share(struct cli_state *cli,
+ struct smbXcli_tcon *tcon,
+ char *share)
+{
+ /* cli->share will have been replaced by a cli_tree_connect() call. */
+ TALLOC_FREE(cli->share);
+ cli->share = share;
+ cli_state_restore_tcon(cli, tcon);
+}
+
+uint16_t cli_state_get_uid(struct cli_state *cli)
+{
+ return smb1cli_session_current_id(cli->smb1.session);
+}
+
+uint16_t cli_state_set_uid(struct cli_state *cli, uint16_t uid)
+{
+ uint16_t ret = smb1cli_session_current_id(cli->smb1.session);
+ smb1cli_session_set_id(cli->smb1.session, uid);
+ return ret;
+}
+
+/****************************************************************************
+ Set the case sensitivity flag on the packets. Returns old state.
+****************************************************************************/
+
+bool cli_set_case_sensitive(struct cli_state *cli, bool case_sensitive)
+{
+ bool ret;
+ uint32_t fs_attrs;
+ struct smbXcli_tcon *tcon;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ tcon = cli->smb2.tcon;
+ } else {
+ tcon = cli->smb1.tcon;
+ }
+
+ fs_attrs = smbXcli_tcon_get_fs_attributes(tcon);
+ if (fs_attrs & FILE_CASE_SENSITIVE_SEARCH) {
+ ret = true;
+ } else {
+ ret = false;
+ }
+ if (case_sensitive) {
+ fs_attrs |= FILE_CASE_SENSITIVE_SEARCH;
+ } else {
+ fs_attrs &= ~FILE_CASE_SENSITIVE_SEARCH;
+ }
+ smbXcli_tcon_set_fs_attributes(tcon, fs_attrs);
+
+ return ret;
+}
+
+uint32_t cli_state_available_size(struct cli_state *cli, uint32_t ofs)
+{
+ uint32_t ret = smb1cli_conn_max_xmit(cli->conn);
+
+ if (ofs >= ret) {
+ return 0;
+ }
+
+ ret -= ofs;
+
+ return ret;
+}
+
+time_t cli_state_server_time(struct cli_state *cli)
+{
+ NTTIME nt;
+ time_t t;
+
+ nt = smbXcli_conn_server_system_time(cli->conn);
+ t = nt_time_to_unix(nt);
+
+ return t;
+}
+
+struct cli_echo_state {
+ uint8_t dummy;
+};
+
+static void cli_echo_done1(struct tevent_req *subreq);
+static void cli_echo_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_echo_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, uint16_t num_echos,
+ DATA_BLOB data)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_echo_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_echo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = smb2cli_echo_send(
+ state, ev, cli->conn, cli->timeout);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_echo_done2, req);
+ return req;
+ }
+
+ subreq = smb1cli_echo_send(
+ state, ev, cli->conn, cli->timeout, num_echos, data);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_echo_done1, req);
+
+ return req;
+}
+
+static void cli_echo_done1(struct tevent_req *subreq)
+{
+ NTSTATUS status = smb1cli_echo_recv(subreq);
+ return tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_echo_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = smb2cli_echo_recv(subreq);
+ return tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+/**
+ * Get the result out from an echo request
+ * @param[in] req The async_req from cli_echo_send
+ * @retval Did the server reply correctly?
+ */
+
+NTSTATUS cli_echo_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/**
+ * @brief Send/Receive SMBEcho requests
+ * @param[in] mem_ctx The memory context to put the async_req on
+ * @param[in] ev The event context that will call us back
+ * @param[in] cli The connection to send the echo to
+ * @param[in] num_echos How many times do we want to get the reply?
+ * @param[in] data The data we want to get back
+ * @retval Did the server reply correctly?
+ */
+
+NTSTATUS cli_echo(struct cli_state *cli, uint16_t num_echos, DATA_BLOB data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_echo_send(frame, ev, cli, num_echos, data);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_echo_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS cli_smb(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint8_t smb_command, uint8_t additional_flags,
+ uint8_t wct, uint16_t *vwv,
+ uint32_t num_bytes, const uint8_t *bytes,
+ struct tevent_req **result_parent,
+ uint8_t min_wct, uint8_t *pwct, uint16_t **pvwv,
+ uint32_t *pnum_bytes, uint8_t **pbytes)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_smb_send(mem_ctx, ev, cli, smb_command, additional_flags, 0,
+ wct, vwv, num_bytes, bytes);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_smb_recv(req, NULL, NULL, min_wct, pwct, pvwv,
+ pnum_bytes, pbytes);
+fail:
+ TALLOC_FREE(ev);
+ if (NT_STATUS_IS_OK(status) && (result_parent != NULL)) {
+ *result_parent = req;
+ }
+ return status;
+}
diff --git a/source3/libsmb/clierror.c b/source3/libsmb/clierror.c
new file mode 100644
index 0000000..afbec47
--- /dev/null
+++ b/source3/libsmb/clierror.c
@@ -0,0 +1,119 @@
+/*
+ Unix SMB/CIFS implementation.
+ client error handling routines
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Jelmer Vernooij 2003
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/****************************************************************************
+ Return the 32-bit NT status code from the last packet.
+****************************************************************************/
+
+NTSTATUS cli_nt_error(struct cli_state *cli)
+{
+ /* Deal with socket errors first. */
+ if (!cli_state_is_connected(cli)) {
+ return NT_STATUS_CONNECTION_DISCONNECTED;
+ }
+
+ if (NT_STATUS_IS_DOS(cli->raw_status)) {
+ int e_class = NT_STATUS_DOS_CLASS(cli->raw_status);
+ int code = NT_STATUS_DOS_CODE(cli->raw_status);
+ return dos_to_ntstatus(e_class, code);
+ }
+
+ return cli->raw_status;
+}
+
+int cli_status_to_errno(NTSTATUS status)
+{
+ int err;
+
+ if (NT_STATUS_IS_DOS(status)) {
+ uint8_t eclass = NT_STATUS_DOS_CLASS(status);
+ uint32_t ecode = NT_STATUS_DOS_CODE(status);
+ status = dos_to_ntstatus(eclass, ecode);
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ /*
+ * Legacy code from cli_errno, see Samba up to 4.13: A
+ * special case for this Vista error. Since its
+ * high-order byte isn't 0xc0, it won't match
+ * correctly in map_errno_from_nt_status().
+ */
+ err = EACCES;
+ } else {
+ err = map_errno_from_nt_status(status);
+ }
+
+ DBG_NOTICE("0x%"PRIx32" -> %d\n", NT_STATUS_V(status), err);
+
+ return err;
+}
+
+/* Return a UNIX errno appropriate for the error received in the last
+ packet. */
+
+int cli_errno(struct cli_state *cli)
+{
+ bool connected;
+ int err;
+
+ connected = cli_state_is_connected(cli);
+ if (!connected) {
+ return EPIPE;
+ }
+
+ err = cli_status_to_errno(cli->raw_status);
+ return err;
+}
+
+/* Return true if the last packet was in error */
+
+bool cli_is_error(struct cli_state *cli)
+{
+ /* A socket error is always an error. */
+ if (!cli_state_is_connected(cli)) {
+ return true;
+ }
+
+ if (NT_STATUS_IS_DOS(cli->raw_status)) {
+ /* Return error if error class in non-zero */
+ uint8_t rcls = NT_STATUS_DOS_CLASS(cli->raw_status);
+ return rcls != 0;
+ }
+
+ return NT_STATUS_IS_ERR(cli->raw_status);
+}
+
+bool cli_state_is_connected(struct cli_state *cli)
+{
+ if (cli == NULL) {
+ return false;
+ }
+
+ if (!cli->initialised) {
+ return false;
+ }
+
+ return smbXcli_conn_is_connected(cli->conn);
+}
diff --git a/source3/libsmb/clifile.c b/source3/libsmb/clifile.c
new file mode 100644
index 0000000..cc1b4d4
--- /dev/null
+++ b/source3/libsmb/clifile.c
@@ -0,0 +1,7486 @@
+/*
+ Unix SMB/CIFS implementation.
+ client file operations
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Jeremy Allison 2001-2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "libsmb/clirap.h"
+#include "trans2.h"
+#include "ntioctl.h"
+#include "libcli/security/security.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/smb/reparse.h"
+
+struct cli_setpathinfo_state {
+ uint16_t setup;
+ uint8_t *param;
+};
+
+static void cli_setpathinfo_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_setpathinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t level,
+ const char *path,
+ uint8_t *data,
+ size_t data_len)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_setpathinfo_state *state;
+ uint16_t additional_flags2 = 0;
+ char *path_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_setpathinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Setup setup word. */
+ SSVAL(&state->setup, 0, TRANSACT2_SETPATHINFO);
+
+ /* Setup param array. */
+ state->param = talloc_zero_array(state, uint8_t, 6);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+ SSVAL(state->param, 0, level);
+
+ /* Check for DFS. */
+ path_cp = smb1_dfs_share_path(state, cli, path);
+ if (tevent_req_nomem(path_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->param = trans2_bytes_push_str(state->param,
+ smbXcli_conn_use_unicode(cli->conn),
+ path_cp,
+ strlen(path_cp)+1,
+ NULL);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(path) &&
+ !INFO_LEVEL_IS_UNIX(level)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_trans_send(
+ state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ additional_flags2, /* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ 0, /* function. */
+ 0, /* flags. */
+ &state->setup, /* setup. */
+ 1, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ state->param, /* param. */
+ talloc_get_size(state->param), /* num param. */
+ 2, /* max returned param. */
+ data, /* data. */
+ data_len, /* num data. */
+ 0); /* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_setpathinfo_done, req);
+ return req;
+}
+
+static void cli_setpathinfo_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_trans_recv(subreq, NULL, NULL, NULL, 0, NULL,
+ NULL, 0, NULL, NULL, 0, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_setpathinfo_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_setpathinfo(struct cli_state *cli,
+ uint16_t level,
+ const char *path,
+ uint8_t *data,
+ size_t data_len)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_setpathinfo_send(ev, ev, cli, level, path, data, data_len);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_setpathinfo_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_setfileinfo_state {
+ uint16_t setup;
+ uint8_t param[6];
+};
+
+static void cli_setfileinfo_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_setfileinfo_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t level,
+ uint8_t *data,
+ size_t data_len)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_setfileinfo_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_setfileinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ PUSH_LE_U16(&state->setup, 0, TRANSACT2_SETFILEINFO);
+
+ PUSH_LE_U16(state->param, 0, fnum);
+ PUSH_LE_U16(state->param, 2, level);
+
+ subreq = cli_trans_send(state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ 0, /* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ 0, /* function. */
+ 0, /* flags. */
+ &state->setup, /* setup. */
+ 1, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ state->param, /* param. */
+ 6, /* num param. */
+ 2, /* max returned param. */
+ data, /* data. */
+ data_len, /* num data. */
+ 0); /* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_setfileinfo_done, req);
+ return req;
+}
+
+static void cli_setfileinfo_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_trans_recv(
+ subreq, /* req */
+ NULL, /* mem_ctx */
+ NULL, /* recv_flags2 */
+ NULL, /* setup */
+ 0, /* min_setup */
+ NULL, /* num_setup */
+ NULL, /* param */
+ 0, /* min_param */
+ NULL, /* num_param */
+ NULL, /* data */
+ 0, /* min_data */
+ NULL); /* num_data */
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_setfileinfo_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************************
+ Hard/Symlink a file (UNIX extensions).
+ Creates new name (sym)linked to link_target.
+****************************************************************************/
+
+struct cli_posix_link_internal_state {
+ uint8_t *data;
+};
+
+static void cli_posix_link_internal_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_posix_link_internal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t level,
+ const char *link_target,
+ const char *newname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_link_internal_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_posix_link_internal_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Setup data array. */
+ state->data = talloc_array(state, uint8_t, 0);
+ if (tevent_req_nomem(state->data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->data = trans2_bytes_push_str(
+ state->data, smbXcli_conn_use_unicode(cli->conn),
+ link_target, strlen(link_target)+1, NULL);
+
+ subreq = cli_setpathinfo_send(
+ state, ev, cli, level, newname,
+ state->data, talloc_get_size(state->data));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_link_internal_done, req);
+ return req;
+}
+
+static void cli_posix_link_internal_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setpathinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_posix_link_internal_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************************
+ Symlink a file (UNIX extensions).
+****************************************************************************/
+
+struct cli_posix_symlink_state {
+ uint8_t dummy;
+};
+
+static void cli_posix_symlink_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_symlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *link_target,
+ const char *newname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_symlink_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_posix_symlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_posix_link_internal_send(
+ mem_ctx, ev, cli, SMB_SET_FILE_UNIX_LINK, link_target, newname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_symlink_done, req);
+ return req;
+}
+
+static void cli_posix_symlink_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_posix_link_internal_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_symlink_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_symlink(struct cli_state *cli,
+ const char *link_target,
+ const char *newname)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_symlink_send(frame,
+ ev,
+ cli,
+ link_target,
+ newname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_symlink_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Read a POSIX symlink.
+****************************************************************************/
+
+struct cli_posix_readlink_state {
+ struct cli_state *cli;
+ char *converted;
+};
+
+static void cli_posix_readlink_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_readlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_readlink_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_posix_readlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ subreq = cli_qpathinfo_send(
+ state,
+ ev,
+ cli,
+ fname,
+ SMB_QUERY_FILE_UNIX_LINK,
+ 1,
+ UINT16_MAX);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_readlink_done, req);
+ return req;
+}
+
+static void cli_posix_readlink_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_posix_readlink_state *state = tevent_req_data(
+ req, struct cli_posix_readlink_state);
+ NTSTATUS status;
+ uint8_t *data = NULL;
+ uint32_t num_data = 0;
+ charset_t charset;
+ size_t converted_size;
+ bool ok;
+
+ status = cli_qpathinfo_recv(subreq, state, &data, &num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ /*
+ * num_data is > 1, we've given 1 as minimum to cli_qpathinfo_send
+ */
+ if (data == NULL || data[num_data-1] != '\0') {
+ tevent_req_nterror(req, NT_STATUS_DATA_ERROR);
+ return;
+ }
+
+ charset = smbXcli_conn_use_unicode(state->cli->conn) ?
+ CH_UTF16LE : CH_DOS;
+
+ /* The returned data is a pushed string, not raw data. */
+ ok = convert_string_talloc(
+ state,
+ charset,
+ CH_UNIX,
+ data,
+ num_data,
+ &state->converted,
+ &converted_size);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_posix_readlink_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, char **target)
+{
+ struct cli_posix_readlink_state *state = tevent_req_data(
+ req, struct cli_posix_readlink_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *target = talloc_move(mem_ctx, &state->converted);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Hard link a file (UNIX extensions).
+****************************************************************************/
+
+struct cli_posix_hardlink_state {
+ uint8_t dummy;
+};
+
+static void cli_posix_hardlink_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_hardlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *oldname,
+ const char *newname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_hardlink_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_posix_hardlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_posix_link_internal_send(
+ state, ev, cli, SMB_SET_FILE_UNIX_HLINK, oldname, newname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_hardlink_done, req);
+ return req;
+}
+
+static void cli_posix_hardlink_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_posix_link_internal_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_hardlink_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_hardlink(struct cli_state *cli,
+ const char *oldname,
+ const char *newname)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_hardlink_send(frame,
+ ev,
+ cli,
+ oldname,
+ newname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_hardlink_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Do a POSIX getacl - pathname based ACL get (UNIX extensions).
+****************************************************************************/
+
+struct getacl_state {
+ uint32_t num_data;
+ uint8_t *data;
+};
+
+static void cli_posix_getacl_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_getacl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct getacl_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct getacl_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = cli_qpathinfo_send(state, ev, cli, fname, SMB_QUERY_POSIX_ACL,
+ 0, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_getacl_done, req);
+ return req;
+}
+
+static void cli_posix_getacl_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct getacl_state *state = tevent_req_data(
+ req, struct getacl_state);
+ NTSTATUS status;
+
+ status = cli_qpathinfo_recv(subreq, state, &state->data,
+ &state->num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_posix_getacl_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *prb_size,
+ char **retbuf)
+{
+ struct getacl_state *state = tevent_req_data(req, struct getacl_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *prb_size = (size_t)state->num_data;
+ *retbuf = (char *)talloc_move(mem_ctx, &state->data);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_posix_getacl(struct cli_state *cli,
+ const char *fname,
+ TALLOC_CTX *mem_ctx,
+ size_t *prb_size,
+ char **retbuf)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_getacl_send(frame,
+ ev,
+ cli,
+ fname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_getacl_recv(req, mem_ctx, prb_size, retbuf);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Do a POSIX setacl - pathname based ACL set (UNIX extensions).
+****************************************************************************/
+
+struct setacl_state {
+ uint8_t *data;
+};
+
+static void cli_posix_setacl_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_setacl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ const void *data,
+ size_t num_data)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct setacl_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct setacl_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->data = talloc_memdup(state, data, num_data);
+ if (tevent_req_nomem(state->data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_setpathinfo_send(state,
+ ev,
+ cli,
+ SMB_SET_POSIX_ACL,
+ fname,
+ state->data,
+ num_data);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_setacl_done, req);
+ return req;
+}
+
+static void cli_posix_setacl_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setpathinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_setacl_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_setacl(struct cli_state *cli,
+ const char *fname,
+ const void *acl_buf,
+ size_t acl_buf_size)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_setacl_send(frame,
+ ev,
+ cli,
+ fname,
+ acl_buf,
+ acl_buf_size);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_setacl_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Stat a file (UNIX extensions).
+****************************************************************************/
+
+struct cli_posix_stat_state {
+ struct stat_ex sbuf;
+};
+
+static void cli_posix_stat_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_stat_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct stat_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_posix_stat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_qpathinfo_send(state, ev, cli, fname,
+ SMB_QUERY_FILE_UNIX_BASIC, 100, 100);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_stat_done, req);
+ return req;
+}
+
+static void cli_posix_stat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_posix_stat_state *state = tevent_req_data(
+ req, struct cli_posix_stat_state);
+ SMB_STRUCT_STAT *sbuf = &state->sbuf;
+ uint8_t *data;
+ uint32_t num_data = 0;
+ NTSTATUS status;
+
+ status = cli_qpathinfo_recv(subreq, state, &data, &num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (num_data != 100) {
+ /*
+ * Paranoia, cli_qpathinfo should have guaranteed
+ * this, but you never know...
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ /* total size, in bytes */
+ sbuf->st_ex_size = IVAL2_TO_SMB_BIG_UINT(data, 0);
+
+ /* number of blocks allocated */
+ sbuf->st_ex_blocks = IVAL2_TO_SMB_BIG_UINT(data,8);
+#if defined (HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE)
+ sbuf->st_ex_blocks /= STAT_ST_BLOCKSIZE;
+#else
+ /* assume 512 byte blocks */
+ sbuf->st_ex_blocks /= 512;
+#endif
+ /* time of last change */
+ sbuf->st_ex_ctime = interpret_long_date(BVAL(data, 16));
+
+ /* time of last access */
+ sbuf->st_ex_atime = interpret_long_date(BVAL(data, 24));
+
+ /* time of last modification */
+ sbuf->st_ex_mtime = interpret_long_date(BVAL(data, 32));
+
+ sbuf->st_ex_uid = (uid_t) IVAL(data, 40); /* user ID of owner */
+ sbuf->st_ex_gid = (gid_t) IVAL(data, 48); /* group ID of owner */
+ sbuf->st_ex_mode = unix_filetype_from_wire(IVAL(data, 56));
+
+#if defined(HAVE_MAKEDEV)
+ {
+ uint32_t dev_major = IVAL(data,60);
+ uint32_t dev_minor = IVAL(data,68);
+ sbuf->st_ex_rdev = makedev(dev_major, dev_minor);
+ }
+#endif
+ /* inode */
+ sbuf->st_ex_ino = (SMB_INO_T)IVAL2_TO_SMB_BIG_UINT(data, 76);
+
+ /* protection */
+ sbuf->st_ex_mode |= wire_perms_to_unix(IVAL(data, 84));
+
+ /* number of hard links */
+ sbuf->st_ex_nlink = BIG_UINT(data, 92);
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_posix_stat_recv(struct tevent_req *req, struct stat_ex *sbuf)
+{
+ struct cli_posix_stat_state *state = tevent_req_data(
+ req, struct cli_posix_stat_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *sbuf = state->sbuf;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_posix_stat(struct cli_state *cli,
+ const char *fname,
+ struct stat_ex *sbuf)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_stat_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_stat_recv(req, sbuf);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Chmod or chown a file internal (UNIX extensions).
+****************************************************************************/
+
+struct cli_posix_chown_chmod_internal_state {
+ uint8_t data[100];
+};
+
+static void cli_posix_chown_chmod_internal_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_posix_chown_chmod_internal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t mode,
+ uint32_t uid,
+ uint32_t gid)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_chown_chmod_internal_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_posix_chown_chmod_internal_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ memset(state->data, 0xff, 40); /* Set all sizes/times to no change. */
+ SIVAL(state->data,40,uid);
+ SIVAL(state->data,48,gid);
+ SIVAL(state->data,84,mode);
+
+ subreq = cli_setpathinfo_send(state, ev, cli, SMB_SET_FILE_UNIX_BASIC,
+ fname, state->data, sizeof(state->data));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_chown_chmod_internal_done,
+ req);
+ return req;
+}
+
+static void cli_posix_chown_chmod_internal_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setpathinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_posix_chown_chmod_internal_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************************
+ chmod a file (UNIX extensions).
+****************************************************************************/
+
+struct cli_posix_chmod_state {
+ uint8_t dummy;
+};
+
+static void cli_posix_chmod_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_chmod_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ mode_t mode)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_chmod_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_posix_chmod_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_posix_chown_chmod_internal_send(
+ state,
+ ev,
+ cli,
+ fname,
+ unix_perms_to_wire(mode),
+ SMB_UID_NO_CHANGE,
+ SMB_GID_NO_CHANGE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_chmod_done, req);
+ return req;
+}
+
+static void cli_posix_chmod_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_posix_chown_chmod_internal_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_chmod_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_chmod(struct cli_state *cli, const char *fname, mode_t mode)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_chmod_send(frame,
+ ev,
+ cli,
+ fname,
+ mode);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_chmod_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ chown a file (UNIX extensions).
+****************************************************************************/
+
+struct cli_posix_chown_state {
+ uint8_t dummy;
+};
+
+static void cli_posix_chown_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_chown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uid_t uid,
+ gid_t gid)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_chown_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_posix_chown_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_posix_chown_chmod_internal_send(
+ state,
+ ev,
+ cli,
+ fname,
+ SMB_MODE_NO_CHANGE,
+ (uint32_t)uid,
+ (uint32_t)gid);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_chown_done, req);
+ return req;
+}
+
+static void cli_posix_chown_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_posix_chown_chmod_internal_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_chown_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_chown(struct cli_state *cli,
+ const char *fname,
+ uid_t uid,
+ gid_t gid)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_chown_send(frame,
+ ev,
+ cli,
+ fname,
+ uid,
+ gid);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_chown_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_smb1_posix_mknod_state {
+ uint8_t data[100];
+};
+
+static void cli_smb1_posix_mknod_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb1_posix_mknod_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ mode_t mode,
+ dev_t dev)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb1_posix_mknod_state *state = NULL;
+ mode_t type = mode & S_IFMT;
+ uint32_t smb_unix_type = 0xFFFFFFFF;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_smb1_posix_mknod_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ /*
+ * Set all sizes/times/ids to no change.
+ */
+ memset(state->data, 0xff, 56);
+
+ switch (type) {
+ case S_IFREG:
+ smb_unix_type = UNIX_TYPE_FILE;
+ break;
+ case S_IFDIR:
+ smb_unix_type = UNIX_TYPE_DIR;
+ break;
+ case S_IFLNK:
+ smb_unix_type = UNIX_TYPE_SYMLINK;
+ break;
+ case S_IFCHR:
+ smb_unix_type = UNIX_TYPE_CHARDEV;
+ break;
+ case S_IFBLK:
+ smb_unix_type = UNIX_TYPE_BLKDEV;
+ break;
+ case S_IFIFO:
+ smb_unix_type = UNIX_TYPE_FIFO;
+ break;
+ case S_IFSOCK:
+ smb_unix_type = UNIX_TYPE_SOCKET;
+ break;
+ }
+ PUSH_LE_U32(state->data, 56, smb_unix_type);
+
+ if ((type == S_IFCHR) || (type == S_IFBLK)) {
+ PUSH_LE_U64(state->data, 60, unix_dev_major(dev));
+ PUSH_LE_U64(state->data, 68, unix_dev_minor(dev));
+ }
+
+ PUSH_LE_U32(state->data, 84, unix_perms_to_wire(mode));
+
+ subreq = cli_setpathinfo_send(
+ state,
+ ev,
+ cli,
+ SMB_SET_FILE_UNIX_BASIC,
+ fname,
+ state->data,
+ sizeof(state->data));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb1_posix_mknod_done, req);
+ return req;
+}
+
+static void cli_smb1_posix_mknod_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setpathinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_smb1_posix_mknod_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct cli_mknod_state {
+ uint8_t buf[24];
+};
+
+static void cli_mknod_done1(struct tevent_req *subreq);
+static void cli_mknod_reparse_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_mknod_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ mode_t mode,
+ dev_t dev)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_mknod_state *state = NULL;
+ struct reparse_data_buffer reparse_buf = {
+ .tag = IO_REPARSE_TAG_NFS,
+ };
+ struct nfs_reparse_data_buffer *nfs = &reparse_buf.parsed.nfs;
+ ssize_t buflen;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_mknod_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (cli->requested_posix_capabilities != 0) {
+ subreq = cli_smb1_posix_mknod_send(
+ state, ev, cli, fname, mode, dev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_mknod_done1, req);
+ return req;
+ }
+
+ /*
+ * Ignored for all but BLK and CHR
+ */
+ nfs->data.dev.major = major(dev);
+ nfs->data.dev.minor = minor(dev);
+
+ switch (mode & S_IFMT) {
+ case S_IFIFO:
+ nfs->type = NFS_SPECFILE_FIFO;
+ break;
+ case S_IFSOCK:
+ nfs->type = NFS_SPECFILE_SOCK;
+ break;
+ case S_IFCHR:
+ nfs->type = NFS_SPECFILE_CHR;
+ break;
+ case S_IFBLK:
+ nfs->type = NFS_SPECFILE_BLK;
+ break;
+ }
+
+ buflen = reparse_data_buffer_marshall(&reparse_buf,
+ state->buf,
+ sizeof(state->buf));
+ if ((buflen == -1) || (buflen > sizeof(state->buf))) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_create_reparse_point_send(state,
+ ev,
+ cli,
+ fname,
+ (DATA_BLOB){
+ .data = state->buf,
+ .length = buflen,
+ });
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_mknod_reparse_done, req);
+ return req;
+}
+
+static void cli_mknod_done1(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb1_posix_mknod_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_mknod_reparse_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_create_reparse_point_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_mknod_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS
+cli_mknod(struct cli_state *cli, const char *fname, mode_t mode, dev_t dev)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_mknod_send(ev, ev, cli, fname, mode, dev);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_mknod_recv(req);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Rename a file.
+****************************************************************************/
+
+struct cli_smb1_rename_state {
+ uint8_t *data;
+};
+
+static void cli_smb1_rename_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb1_rename_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb1_rename_state *state = NULL;
+ smb_ucs2_t *converted_str = NULL;
+ size_t converted_size_bytes = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb1_rename_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Strip a MSDFS path from fname_dst if we were given one.
+ */
+ status = cli_dfs_target_check(state,
+ cli,
+ fname_dst,
+ &fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (!push_ucs2_talloc(talloc_tos(), &converted_str, fname_dst,
+ &converted_size_bytes)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* W2K8 insists the dest name is not null
+ terminated. Remove the last 2 zero bytes
+ and reduce the name length. */
+
+ if (converted_size_bytes < 2) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ converted_size_bytes -= 2;
+
+ state->data =
+ talloc_zero_array(state, uint8_t, 12 + converted_size_bytes);
+ if (state->data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (replace) {
+ SCVAL(state->data, 0, 1);
+ }
+
+ SIVAL(state->data, 8, converted_size_bytes);
+ memcpy(state->data + 12, converted_str, converted_size_bytes);
+
+ TALLOC_FREE(converted_str);
+
+ subreq = cli_setpathinfo_send(
+ state, ev, cli, SMB_FILE_RENAME_INFORMATION, fname_src, state->data,
+ talloc_get_size(state->data));
+ if (tevent_req_nomem(subreq, req)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, cli_smb1_rename_done, req);
+ return req;
+
+fail:
+ TALLOC_FREE(converted_str);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+}
+
+static void cli_smb1_rename_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setpathinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_smb1_rename_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void cli_cifs_rename_done(struct tevent_req *subreq);
+
+struct cli_cifs_rename_state {
+ uint16_t vwv[1];
+};
+
+static struct tevent_req *cli_cifs_rename_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_cifs_rename_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *fname_src_cp = NULL;
+ char *fname_dst_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_cifs_rename_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (replace) {
+ /*
+ * CIFS doesn't support replace
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ SSVAL(state->vwv+0, 0, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * SMBmv on a DFS share uses DFS names for src and dst.
+ * See smbtorture3: SMB1-DFS-PATHS: test_smb1_mv().
+ */
+
+ fname_src_cp = smb1_dfs_share_path(state, cli, fname_src);
+ if (tevent_req_nomem(fname_src_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_src_cp,
+ strlen(fname_src_cp)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname_src)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ bytes = talloc_realloc(state, bytes, uint8_t,
+ talloc_get_size(bytes)+1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * SMBmv on a DFS share uses DFS names for src and dst.
+ * See smbtorture3: SMB1-DFS-PATHS: test_smb1_mv().
+ */
+
+ fname_dst_cp = smb1_dfs_share_path(state, cli, fname_dst);
+ if (tevent_req_nomem(fname_dst_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[talloc_get_size(bytes)-1] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_dst_cp,
+ strlen(fname_dst_cp)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBmv, additional_flags,
+ additional_flags2,
+ 1, state->vwv, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_cifs_rename_done, req);
+ return req;
+}
+
+static void cli_cifs_rename_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(
+ subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_cifs_rename_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct cli_rename_state {
+ uint8_t dummy;
+};
+
+static void cli_rename_done1(struct tevent_req *subreq);
+static void cli_rename_done_cifs(struct tevent_req *subreq);
+static void cli_rename_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_rename_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_rename_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_rename_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_rename_send(
+ state, ev, cli, fname_src, fname_dst, replace);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_rename_done2, req);
+ return req;
+ }
+
+ if (replace && smbXcli_conn_support_passthrough(cli->conn)) {
+ subreq = cli_smb1_rename_send(
+ state, ev, cli, fname_src, fname_dst, replace);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_rename_done1, req);
+ return req;
+ }
+
+ subreq = cli_cifs_rename_send(
+ state, ev, cli, fname_src,fname_dst, replace);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_rename_done_cifs, req);
+ return req;
+}
+
+static void cli_rename_done1(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb1_rename_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_rename_done_cifs(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_cifs_rename_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_rename_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_rename_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_rename_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_rename(struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_rename_send(frame, ev, cli, fname_src, fname_dst, replace);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_rename_recv(req);
+ cli->raw_status = status; /* cli_smb2_rename_recv doesn't set this */
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ NT Rename a file.
+****************************************************************************/
+
+static void cli_ntrename_internal_done(struct tevent_req *subreq);
+
+struct cli_ntrename_internal_state {
+ uint16_t vwv[4];
+};
+
+static struct tevent_req *cli_ntrename_internal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ uint16_t rename_flag)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_ntrename_internal_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *fname_src_cp = NULL;
+ char *fname_dst_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_ntrename_internal_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ SSVAL(state->vwv+0, 0 ,FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY);
+ SSVAL(state->vwv+1, 0, rename_flag);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBntrename on a DFS share uses DFS names for src and dst.
+ * See smbtorture3: SMB1-DFS-PATHS: test_smb1_ntrename_rename().
+ */
+ fname_src_cp = smb1_dfs_share_path(state, cli, fname_src);
+ if (tevent_req_nomem(fname_src_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_src_cp,
+ strlen(fname_src_cp)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname_src)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ bytes = talloc_realloc(state, bytes, uint8_t,
+ talloc_get_size(bytes)+1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * SMBntrename on a DFS share uses DFS names for src and dst.
+ * See smbtorture3: SMB1-DFS-PATHS: test_smb1_ntrename_rename().
+ * and smbtorture3: SMB1-DFS-PATHS: test_smb1_ntrename_hardlink()
+ */
+ fname_dst_cp = smb1_dfs_share_path(state, cli, fname_dst);
+ if (tevent_req_nomem(fname_dst_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[talloc_get_size(bytes)-1] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_dst_cp,
+ strlen(fname_dst_cp)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBntrename, additional_flags,
+ additional_flags2,
+ 4, state->vwv, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ntrename_internal_done, req);
+ return req;
+}
+
+static void cli_ntrename_internal_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(
+ subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_ntrename_internal_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct tevent_req *cli_ntrename_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst)
+{
+ return cli_ntrename_internal_send(mem_ctx,
+ ev,
+ cli,
+ fname_src,
+ fname_dst,
+ RENAME_FLAG_RENAME);
+}
+
+NTSTATUS cli_ntrename_recv(struct tevent_req *req)
+{
+ return cli_ntrename_internal_recv(req);
+}
+
+NTSTATUS cli_ntrename(struct cli_state *cli, const char *fname_src, const char *fname_dst)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_ntrename_send(frame, ev, cli, fname_src, fname_dst);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_ntrename_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ NT hardlink a file.
+****************************************************************************/
+
+static struct tevent_req *cli_nt_hardlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst)
+{
+ return cli_ntrename_internal_send(mem_ctx,
+ ev,
+ cli,
+ fname_src,
+ fname_dst,
+ RENAME_FLAG_HARD_LINK);
+}
+
+static NTSTATUS cli_nt_hardlink_recv(struct tevent_req *req)
+{
+ return cli_ntrename_internal_recv(req);
+}
+
+struct cli_smb2_hardlink_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum_src;
+ const char *fname_dst;
+ bool overwrite;
+ NTSTATUS status;
+};
+
+static void cli_smb2_hardlink_opened(struct tevent_req *subreq);
+static void cli_smb2_hardlink_info_set(struct tevent_req *subreq);
+static void cli_smb2_hardlink_closed(struct tevent_req *subreq);
+
+static struct tevent_req *cli_smb2_hardlink_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool overwrite,
+ struct smb2_create_blobs *in_cblobs)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_smb2_hardlink_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_smb2_hardlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Strip a MSDFS path from fname_dst if we were given one.
+ */
+ status = cli_dfs_target_check(state,
+ cli,
+ fname_dst,
+ &fname_dst);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->ev = ev;
+ state->cli = cli;
+ state->fname_dst = fname_dst;
+ state->overwrite = overwrite;
+
+ subreq = cli_smb2_create_fnum_send(
+ state,
+ ev,
+ cli,
+ fname_src,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ FILE_WRITE_ATTRIBUTES,
+ 0, /* file attributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ FILE_NON_DIRECTORY_FILE, /* no hardlinks on directories */
+ in_cblobs);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb2_hardlink_opened, req);
+ return req;
+}
+
+static void cli_smb2_hardlink_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_hardlink_state *state = tevent_req_data(
+ req, struct cli_smb2_hardlink_state);
+ NTSTATUS status;
+ smb_ucs2_t *ucs2_dst;
+ size_t ucs2_len;
+ DATA_BLOB inbuf;
+ bool ok;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq, &state->fnum_src, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ ok = push_ucs2_talloc(state, &ucs2_dst, state->fname_dst, &ucs2_len);
+ if (!ok || (ucs2_len < 2)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ /* Don't 0-terminate the name */
+ ucs2_len -= 2;
+
+ inbuf = data_blob_talloc_zero(state, ucs2_len + 20);
+ if (tevent_req_nomem(inbuf.data, req)) {
+ return;
+ }
+
+ if (state->overwrite) {
+ SCVAL(inbuf.data, 0, 1);
+ }
+ SIVAL(inbuf.data, 16, ucs2_len);
+ memcpy(inbuf.data + 20, ucs2_dst, ucs2_len);
+ TALLOC_FREE(ucs2_dst);
+
+ subreq = cli_smb2_set_info_fnum_send(
+ state,
+ state->ev,
+ state->cli,
+ state->fnum_src,
+ 1, /* in_info_type */
+ SMB_FILE_LINK_INFORMATION - 1000, /* in_file_info_class */
+ &inbuf,
+ 0); /* in_additional_info */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_hardlink_info_set, req);
+}
+
+static void cli_smb2_hardlink_info_set(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb2_hardlink_state *state = tevent_req_data(
+ req, struct cli_smb2_hardlink_state);
+
+ state->status = cli_smb2_set_info_fnum_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ /* ignore error here, we need to close the file */
+
+ subreq = cli_smb2_close_fnum_send(state,
+ state->ev,
+ state->cli,
+ state->fnum_src,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb2_hardlink_closed, req);
+}
+
+static void cli_smb2_hardlink_closed(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_close_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_smb2_hardlink_recv(struct tevent_req *req)
+{
+ struct cli_smb2_hardlink_state *state = tevent_req_data(
+ req, struct cli_smb2_hardlink_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ return state->status;
+}
+
+struct cli_hardlink_state {
+ uint8_t dummy;
+};
+
+static void cli_hardlink_done(struct tevent_req *subreq);
+static void cli_hardlink_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_hardlink_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_hardlink_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_hardlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_hardlink_send(
+ state, ev, cli, fname_src, fname_dst, false, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_hardlink_done2, req);
+ return req;
+ }
+
+ subreq = cli_nt_hardlink_send(state, ev, cli, fname_src, fname_dst);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_hardlink_done, req);
+ return req;
+}
+
+static void cli_hardlink_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_nt_hardlink_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_hardlink_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_hardlink_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_hardlink_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_hardlink(
+ struct cli_state *cli, const char *fname_src, const char *fname_dst)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_hardlink_send(frame, ev, cli, fname_src, fname_dst);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_hardlink_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Delete a file.
+****************************************************************************/
+
+static void cli_unlink_done(struct tevent_req *subreq);
+static void cli_unlink_done2(struct tevent_req *subreq);
+
+struct cli_unlink_state {
+ uint16_t vwv[1];
+};
+
+struct tevent_req *cli_unlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t mayhave_attrs)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_unlink_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_unlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_unlink_send(state, ev, cli, fname, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_unlink_done2, req);
+ return req;
+ }
+
+ if (mayhave_attrs & 0xFFFF0000) {
+ /*
+ * Don't allow attributes greater than
+ * 16-bits for a 16-bit protocol value.
+ */
+ if (tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ SSVAL(state->vwv+0, 0, mayhave_attrs);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBunlink on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBunlink, additional_flags,
+ additional_flags2,
+ 1, state->vwv, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_unlink_done, req);
+ return req;
+}
+
+static void cli_unlink_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(
+ subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_unlink_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_unlink_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_unlink_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_unlink(struct cli_state *cli, const char *fname, uint32_t mayhave_attrs)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_unlink_send(frame, ev, cli, fname, mayhave_attrs);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_unlink_recv(req);
+ cli->raw_status = status; /* cli_smb2_unlink_recv doesn't set this */
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Create a directory.
+****************************************************************************/
+
+static void cli_mkdir_done(struct tevent_req *subreq);
+static void cli_mkdir_done2(struct tevent_req *subreq);
+
+struct cli_mkdir_state {
+ int dummy;
+};
+
+struct tevent_req *cli_mkdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *dname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_mkdir_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *dname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_mkdir_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_mkdir_send(state, ev, cli, dname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_mkdir_done2, req);
+ return req;
+ }
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBmkdir on a DFS share must use DFS names.
+ */
+ dname_cp = smb1_dfs_share_path(state, cli, dname);
+ if (tevent_req_nomem(dname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ dname_cp,
+ strlen(dname_cp)+1,
+ NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(dname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBmkdir, additional_flags,
+ additional_flags2,
+ 0, NULL, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_mkdir_done, req);
+ return req;
+}
+
+static void cli_mkdir_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_mkdir_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_mkdir_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_mkdir_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_mkdir(struct cli_state *cli, const char *dname)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_mkdir_send(frame, ev, cli, dname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_mkdir_recv(req);
+ cli->raw_status = status; /* cli_smb2_mkdir_recv doesn't set this */
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Remove a directory.
+****************************************************************************/
+
+static void cli_rmdir_done(struct tevent_req *subreq);
+static void cli_rmdir_done2(struct tevent_req *subreq);
+
+struct cli_rmdir_state {
+ int dummy;
+};
+
+struct tevent_req *cli_rmdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *dname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_rmdir_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *dname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_rmdir_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_rmdir_send(state, ev, cli, dname, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_rmdir_done2, req);
+ return req;
+ }
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBrmdir on a DFS share must use DFS names.
+ */
+ dname_cp = smb1_dfs_share_path(state, cli, dname);
+ if (tevent_req_nomem(dname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ dname_cp,
+ strlen(dname_cp)+1,
+ NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(dname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBrmdir, additional_flags,
+ additional_flags2,
+ 0, NULL, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_rmdir_done, req);
+ return req;
+}
+
+static void cli_rmdir_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(
+ subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_rmdir_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_rmdir_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_rmdir_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_rmdir(struct cli_state *cli, const char *dname)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_rmdir_send(frame, ev, cli, dname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_rmdir_recv(req);
+ cli->raw_status = status; /* cli_smb2_rmdir_recv doesn't set this */
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Set or clear the delete on close flag.
+****************************************************************************/
+
+struct doc_state {
+ uint8_t data[1];
+};
+
+static void cli_nt_delete_on_close_smb1_done(struct tevent_req *subreq);
+static void cli_nt_delete_on_close_smb2_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_nt_delete_on_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool flag)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct doc_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct doc_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_delete_on_close_send(state, ev, cli,
+ fnum, flag);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_nt_delete_on_close_smb2_done,
+ req);
+ return req;
+ }
+
+ /* Setup data array. */
+ SCVAL(&state->data[0], 0, flag ? 1 : 0);
+
+ subreq = cli_setfileinfo_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ SMB_SET_FILE_DISPOSITION_INFO,
+ state->data,
+ sizeof(state->data));
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_nt_delete_on_close_smb1_done,
+ req);
+ return req;
+}
+
+static void cli_nt_delete_on_close_smb1_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setfileinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_nt_delete_on_close_smb2_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_delete_on_close_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_nt_delete_on_close_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_nt_delete_on_close(struct cli_state *cli, uint16_t fnum, bool flag)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_nt_delete_on_close_send(frame,
+ ev,
+ cli,
+ fnum,
+ flag);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_ntcreate1_state {
+ uint16_t vwv[24];
+ uint16_t fnum;
+ struct smb_create_returns cr;
+ struct tevent_req *subreq;
+};
+
+static void cli_ntcreate1_done(struct tevent_req *subreq);
+static bool cli_ntcreate1_cancel(struct tevent_req *req);
+
+static struct tevent_req *cli_ntcreate1_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint32_t ImpersonationLevel,
+ uint8_t SecurityFlags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_ntcreate1_state *state;
+ uint16_t *vwv;
+ uint8_t *bytes;
+ size_t converted_len;
+ uint16_t additional_flags2 = 0;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_ntcreate1_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ vwv = state->vwv;
+
+ SCVAL(vwv+0, 0, 0xFF);
+ SCVAL(vwv+0, 1, 0);
+ SSVAL(vwv+1, 0, 0);
+ SCVAL(vwv+2, 0, 0);
+
+ if (cli->use_oplocks) {
+ CreatFlags |= (REQUEST_OPLOCK|REQUEST_BATCH_OPLOCK);
+ }
+ SIVAL(vwv+3, 1, CreatFlags);
+ SIVAL(vwv+5, 1, 0x0); /* RootDirectoryFid */
+ SIVAL(vwv+7, 1, DesiredAccess);
+ SIVAL(vwv+9, 1, 0x0); /* AllocationSize */
+ SIVAL(vwv+11, 1, 0x0); /* AllocationSize */
+ SIVAL(vwv+13, 1, FileAttributes);
+ SIVAL(vwv+15, 1, ShareAccess);
+ SIVAL(vwv+17, 1, CreateDisposition);
+ SIVAL(vwv+19, 1, CreateOptions |
+ (cli->backup_intent ? FILE_OPEN_FOR_BACKUP_INTENT : 0));
+ SIVAL(vwv+21, 1, ImpersonationLevel);
+ SCVAL(vwv+23, 1, SecurityFlags);
+
+ bytes = talloc_array(state, uint8_t, 0);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBntcreateX on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ &converted_len);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ /* sigh. this copes with broken netapp filer behaviour */
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "", 1, NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ SSVAL(vwv+2, 1, converted_len);
+
+ subreq = cli_smb_send(state, ev, cli, SMBntcreateX, 0,
+ additional_flags2, 24, vwv,
+ talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ntcreate1_done, req);
+
+ state->subreq = subreq;
+ tevent_req_set_cancel_fn(req, cli_ntcreate1_cancel);
+
+ return req;
+}
+
+static void cli_ntcreate1_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_ntcreate1_state *state = tevent_req_data(
+ req, struct cli_ntcreate1_state);
+ uint8_t wct;
+ uint16_t *vwv;
+ uint32_t num_bytes;
+ uint8_t *bytes;
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 34, &wct, &vwv,
+ &num_bytes, &bytes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->cr.oplock_level = CVAL(vwv+2, 0);
+ state->fnum = SVAL(vwv+2, 1);
+ state->cr.create_action = IVAL(vwv+3, 1);
+ state->cr.creation_time = BVAL(vwv+5, 1);
+ state->cr.last_access_time = BVAL(vwv+9, 1);
+ state->cr.last_write_time = BVAL(vwv+13, 1);
+ state->cr.change_time = BVAL(vwv+17, 1);
+ state->cr.file_attributes = IVAL(vwv+21, 1);
+ state->cr.allocation_size = BVAL(vwv+23, 1);
+ state->cr.end_of_file = BVAL(vwv+27, 1);
+
+ tevent_req_done(req);
+}
+
+static bool cli_ntcreate1_cancel(struct tevent_req *req)
+{
+ struct cli_ntcreate1_state *state = tevent_req_data(
+ req, struct cli_ntcreate1_state);
+ return tevent_req_cancel(state->subreq);
+}
+
+static NTSTATUS cli_ntcreate1_recv(struct tevent_req *req,
+ uint16_t *pfnum,
+ struct smb_create_returns *cr)
+{
+ struct cli_ntcreate1_state *state = tevent_req_data(
+ req, struct cli_ntcreate1_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfnum = state->fnum;
+ if (cr != NULL) {
+ *cr = state->cr;
+ }
+ return NT_STATUS_OK;
+}
+
+struct cli_ntcreate_state {
+ struct smb_create_returns cr;
+ uint16_t fnum;
+ struct tevent_req *subreq;
+};
+
+static void cli_ntcreate_done_nt1(struct tevent_req *subreq);
+static void cli_ntcreate_done_smb2(struct tevent_req *subreq);
+static bool cli_ntcreate_cancel(struct tevent_req *req);
+
+struct tevent_req *cli_ntcreate_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t create_flags,
+ uint32_t desired_access,
+ uint32_t file_attributes,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t impersonation_level,
+ uint8_t security_flags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_ntcreate_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_ntcreate_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ struct cli_smb2_create_flags cflags = {0};
+
+ if (cli->use_oplocks) {
+ create_flags |= REQUEST_OPLOCK|REQUEST_BATCH_OPLOCK;
+ }
+
+ cflags = (struct cli_smb2_create_flags) {
+ .batch_oplock = (create_flags & REQUEST_BATCH_OPLOCK),
+ .exclusive_oplock = (create_flags & REQUEST_OPLOCK),
+ };
+
+ subreq = cli_smb2_create_fnum_send(
+ state,
+ ev,
+ cli,
+ fname,
+ cflags,
+ impersonation_level,
+ desired_access,
+ file_attributes,
+ share_access,
+ create_disposition,
+ create_options,
+ NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ntcreate_done_smb2, req);
+ } else {
+ subreq = cli_ntcreate1_send(
+ state, ev, cli, fname, create_flags, desired_access,
+ file_attributes, share_access, create_disposition,
+ create_options, impersonation_level, security_flags);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ntcreate_done_nt1, req);
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_cancel_fn(req, cli_ntcreate_cancel);
+
+ return req;
+}
+
+static void cli_ntcreate_done_nt1(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_ntcreate_state *state = tevent_req_data(
+ req, struct cli_ntcreate_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate1_recv(subreq, &state->fnum, &state->cr);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_ntcreate_done_smb2(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_ntcreate_state *state = tevent_req_data(
+ req, struct cli_ntcreate_state);
+ NTSTATUS status;
+
+ status = cli_smb2_create_fnum_recv(
+ subreq,
+ &state->fnum,
+ &state->cr,
+ NULL,
+ NULL,
+ NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static bool cli_ntcreate_cancel(struct tevent_req *req)
+{
+ struct cli_ntcreate_state *state = tevent_req_data(
+ req, struct cli_ntcreate_state);
+ return tevent_req_cancel(state->subreq);
+}
+
+NTSTATUS cli_ntcreate_recv(struct tevent_req *req, uint16_t *fnum,
+ struct smb_create_returns *cr)
+{
+ struct cli_ntcreate_state *state = tevent_req_data(
+ req, struct cli_ntcreate_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (fnum != NULL) {
+ *fnum = state->fnum;
+ }
+ if (cr != NULL) {
+ *cr = state->cr;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_ntcreate(struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint8_t SecurityFlags,
+ uint16_t *pfid,
+ struct smb_create_returns *cr)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ uint32_t ImpersonationLevel = SMB2_IMPERSONATION_IMPERSONATION;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = cli_ntcreate_send(frame, ev, cli, fname, CreatFlags,
+ DesiredAccess, FileAttributes, ShareAccess,
+ CreateDisposition, CreateOptions,
+ ImpersonationLevel, SecurityFlags);
+ if (req == NULL) {
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_ntcreate_recv(req, pfid, cr);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_nttrans_create_state {
+ uint16_t fnum;
+ struct smb_create_returns cr;
+};
+
+static void cli_nttrans_create_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_nttrans_create_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint8_t SecurityFlags,
+ struct security_descriptor *secdesc,
+ struct ea_struct *eas,
+ int num_eas)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_nttrans_create_state *state;
+ uint8_t *param;
+ uint8_t *secdesc_buf;
+ size_t secdesc_len;
+ NTSTATUS status;
+ size_t converted_len;
+ uint16_t additional_flags2 = 0;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state, struct cli_nttrans_create_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (secdesc != NULL) {
+ status = marshall_sec_desc(talloc_tos(), secdesc,
+ &secdesc_buf, &secdesc_len);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("marshall_sec_desc failed: %s\n",
+ nt_errstr(status)));
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ secdesc_buf = NULL;
+ secdesc_len = 0;
+ }
+
+ if (num_eas != 0) {
+ /*
+ * TODO ;-)
+ */
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+ }
+
+ param = talloc_array(state, uint8_t, 53);
+ if (tevent_req_nomem(param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * SMBntcreateX on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ param = trans2_bytes_push_str(param,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp),
+ &converted_len);
+ if (tevent_req_nomem(param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ SIVAL(param, 0, CreatFlags);
+ SIVAL(param, 4, 0x0); /* RootDirectoryFid */
+ SIVAL(param, 8, DesiredAccess);
+ SIVAL(param, 12, 0x0); /* AllocationSize */
+ SIVAL(param, 16, 0x0); /* AllocationSize */
+ SIVAL(param, 20, FileAttributes);
+ SIVAL(param, 24, ShareAccess);
+ SIVAL(param, 28, CreateDisposition);
+ SIVAL(param, 32, CreateOptions |
+ (cli->backup_intent ? FILE_OPEN_FOR_BACKUP_INTENT : 0));
+ SIVAL(param, 36, secdesc_len);
+ SIVAL(param, 40, 0); /* EA length*/
+ SIVAL(param, 44, converted_len);
+ SIVAL(param, 48, 0x02); /* ImpersonationLevel */
+ SCVAL(param, 52, SecurityFlags);
+
+ subreq = cli_trans_send(state, ev, cli,
+ additional_flags2, /* additional_flags2 */
+ SMBnttrans,
+ NULL, -1, /* name, fid */
+ NT_TRANSACT_CREATE, 0,
+ NULL, 0, 0, /* setup */
+ param, talloc_get_size(param), 128, /* param */
+ secdesc_buf, secdesc_len, 0); /* data */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_nttrans_create_done, req);
+ return req;
+}
+
+static void cli_nttrans_create_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_nttrans_create_state *state = tevent_req_data(
+ req, struct cli_nttrans_create_state);
+ uint8_t *param;
+ uint32_t num_param;
+ NTSTATUS status;
+
+ status = cli_trans_recv(subreq, talloc_tos(), NULL,
+ NULL, 0, NULL, /* rsetup */
+ &param, 69, &num_param,
+ NULL, 0, NULL);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->cr.oplock_level = CVAL(param, 0);
+ state->fnum = SVAL(param, 2);
+ state->cr.create_action = IVAL(param, 4);
+ state->cr.creation_time = BVAL(param, 12);
+ state->cr.last_access_time = BVAL(param, 20);
+ state->cr.last_write_time = BVAL(param, 28);
+ state->cr.change_time = BVAL(param, 36);
+ state->cr.file_attributes = IVAL(param, 44);
+ state->cr.allocation_size = BVAL(param, 48);
+ state->cr.end_of_file = BVAL(param, 56);
+
+ TALLOC_FREE(param);
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_nttrans_create_recv(struct tevent_req *req,
+ uint16_t *fnum,
+ struct smb_create_returns *cr)
+{
+ struct cli_nttrans_create_state *state = tevent_req_data(
+ req, struct cli_nttrans_create_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *fnum = state->fnum;
+ if (cr != NULL) {
+ *cr = state->cr;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_nttrans_create(struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint8_t SecurityFlags,
+ struct security_descriptor *secdesc,
+ struct ea_struct *eas,
+ int num_eas,
+ uint16_t *pfid,
+ struct smb_create_returns *cr)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_nttrans_create_send(frame, ev, cli, fname, CreatFlags,
+ DesiredAccess, FileAttributes,
+ ShareAccess, CreateDisposition,
+ CreateOptions, SecurityFlags,
+ secdesc, eas, num_eas);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_nttrans_create_recv(req, pfid, cr);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Open a file
+ WARNING: if you open with O_WRONLY then getattrE won't work!
+****************************************************************************/
+
+struct cli_openx_state {
+ const char *fname;
+ uint16_t vwv[15];
+ uint16_t fnum;
+ struct iovec bytes;
+};
+
+static void cli_openx_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_openx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, const char *fname,
+ int flags, int share_mode,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_openx_state *state;
+ unsigned openfn;
+ unsigned accessmode;
+ uint8_t additional_flags;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_openx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ openfn = 0;
+ if (flags & O_CREAT) {
+ openfn |= (1<<4);
+ }
+ if (!(flags & O_EXCL)) {
+ if (flags & O_TRUNC)
+ openfn |= (1<<1);
+ else
+ openfn |= (1<<0);
+ }
+
+ accessmode = (share_mode<<4);
+
+ if ((flags & O_ACCMODE) == O_RDWR) {
+ accessmode |= 2;
+ } else if ((flags & O_ACCMODE) == O_WRONLY) {
+ accessmode |= 1;
+ }
+
+#if defined(O_SYNC)
+ if ((flags & O_SYNC) == O_SYNC) {
+ accessmode |= (1<<14);
+ }
+#endif /* O_SYNC */
+
+ if (share_mode == DENY_FCB) {
+ accessmode = 0xFF;
+ }
+
+ SCVAL(state->vwv + 0, 0, 0xFF);
+ SCVAL(state->vwv + 0, 1, 0);
+ SSVAL(state->vwv + 1, 0, 0);
+ SSVAL(state->vwv + 2, 0, 0); /* no additional info */
+ SSVAL(state->vwv + 3, 0, accessmode);
+ SSVAL(state->vwv + 4, 0, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ SSVAL(state->vwv + 5, 0, 0);
+ SIVAL(state->vwv + 6, 0, 0);
+ SSVAL(state->vwv + 8, 0, openfn);
+ SIVAL(state->vwv + 9, 0, 0);
+ SIVAL(state->vwv + 11, 0, 0);
+ SIVAL(state->vwv + 13, 0, 0);
+
+ additional_flags = 0;
+
+ if (cli->use_oplocks) {
+ /* if using oplocks then ask for a batch oplock via
+ core and extended methods */
+ additional_flags =
+ FLAG_REQUEST_OPLOCK|FLAG_REQUEST_BATCH_OPLOCK;
+ SSVAL(state->vwv+2, 0, SVAL(state->vwv+2, 0) | 6);
+ }
+
+ bytes = talloc_array(state, uint8_t, 0);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBopenX on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ state->bytes.iov_base = (void *)bytes;
+ state->bytes.iov_len = talloc_get_size(bytes);
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBopenX, additional_flags,
+ additional_flags2, 15, state->vwv, 1, &state->bytes);
+ if (subreq == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cli_openx_done, req);
+ *psmbreq = subreq;
+ return req;
+}
+
+struct tevent_req *cli_openx_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, const char *fname,
+ int flags, int share_mode)
+{
+ struct tevent_req *req, *subreq;
+ NTSTATUS status;
+
+ req = cli_openx_create(mem_ctx, ev, cli, fname, flags, share_mode,
+ &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void cli_openx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_openx_state *state = tevent_req_data(
+ req, struct cli_openx_state);
+ uint8_t wct;
+ uint16_t *vwv;
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 3, &wct, &vwv, NULL,
+ NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->fnum = SVAL(vwv+2, 0);
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_openx_recv(struct tevent_req *req, uint16_t *pfnum)
+{
+ struct cli_openx_state *state = tevent_req_data(
+ req, struct cli_openx_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfnum = state->fnum;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_openx(struct cli_state *cli, const char *fname, int flags,
+ int share_mode, uint16_t *pfnum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = cli_openx_send(frame, ev, cli, fname, flags, share_mode);
+ if (req == NULL) {
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_openx_recv(req, pfnum);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+/****************************************************************************
+ Synchronous wrapper function that does an NtCreateX open by preference
+ and falls back to openX if this fails.
+****************************************************************************/
+
+NTSTATUS cli_open(struct cli_state *cli, const char *fname, int flags,
+ int share_mode_in, uint16_t *pfnum)
+{
+ NTSTATUS status;
+ unsigned int openfn = 0;
+ unsigned int dos_deny = 0;
+ uint32_t access_mask, share_mode, create_disposition, create_options;
+ struct smb_create_returns cr = {0};
+
+ /* Do the initial mapping into OpenX parameters. */
+ if (flags & O_CREAT) {
+ openfn |= (1<<4);
+ }
+ if (!(flags & O_EXCL)) {
+ if (flags & O_TRUNC)
+ openfn |= (1<<1);
+ else
+ openfn |= (1<<0);
+ }
+
+ dos_deny = (share_mode_in<<4);
+
+ if ((flags & O_ACCMODE) == O_RDWR) {
+ dos_deny |= 2;
+ } else if ((flags & O_ACCMODE) == O_WRONLY) {
+ dos_deny |= 1;
+ }
+
+#if defined(O_SYNC)
+ if ((flags & O_SYNC) == O_SYNC) {
+ dos_deny |= (1<<14);
+ }
+#endif /* O_SYNC */
+
+ if (share_mode_in == DENY_FCB) {
+ dos_deny = 0xFF;
+ }
+
+ if (!map_open_params_to_ntcreate(fname, dos_deny,
+ openfn, &access_mask,
+ &share_mode, &create_disposition,
+ &create_options, NULL)) {
+ goto try_openx;
+ }
+
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ access_mask,
+ 0,
+ share_mode,
+ create_disposition,
+ create_options,
+ 0,
+ pfnum,
+ &cr);
+
+ /* Try and cope will all variants of "we don't do this call"
+ and fall back to openX. */
+
+ if (NT_STATUS_EQUAL(status,NT_STATUS_NOT_IMPLEMENTED) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_INFO_CLASS) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_PROCEDURE_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_LEVEL) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_PARAMETER) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_DEVICE_REQUEST) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_DEVICE_STATE) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_CTL_FILE_NOT_SUPPORTED) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_UNSUCCESSFUL)) {
+ goto try_openx;
+ }
+
+ if (NT_STATUS_IS_OK(status) &&
+ (create_options & FILE_NON_DIRECTORY_FILE) &&
+ (cr.file_attributes & FILE_ATTRIBUTE_DIRECTORY))
+ {
+ /*
+ * Some (broken) servers return a valid handle
+ * for directories even if FILE_NON_DIRECTORY_FILE
+ * is set. Just close the handle and set the
+ * error explicitly to NT_STATUS_FILE_IS_A_DIRECTORY.
+ */
+ status = cli_close(cli, *pfnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ /* Set this so libsmbclient can retrieve it. */
+ cli->raw_status = status;
+ }
+
+ return status;
+
+ try_openx:
+
+ return cli_openx(cli, fname, flags, share_mode_in, pfnum);
+}
+
+/****************************************************************************
+ Close a file.
+****************************************************************************/
+
+struct cli_smb1_close_state {
+ uint16_t vwv[3];
+};
+
+static void cli_smb1_close_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb1_close_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ struct tevent_req **psubreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_smb1_close_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_smb1_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ SSVAL(state->vwv+0, 0, fnum);
+ SIVALS(state->vwv+1, 0, -1);
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBclose, 0, 0,
+ 3, state->vwv, 0, NULL);
+ if (subreq == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cli_smb1_close_done, req);
+ *psubreq = subreq;
+ return req;
+}
+
+static void cli_smb1_close_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+struct cli_close_state {
+ int dummy;
+};
+
+static void cli_close_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t flags)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_close_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_close_fnum_send(state, ev, cli, fnum, flags);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ struct tevent_req *ch_req = NULL;
+ subreq = cli_smb1_close_create(state, ev, cli, fnum, &ch_req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ status = smb1cli_req_chain_submit(&ch_req, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ tevent_req_set_callback(subreq, cli_close_done, req);
+ return req;
+}
+
+static void cli_close_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status = NT_STATUS_OK;
+ bool err = tevent_req_is_nterror(subreq, &status);
+
+ TALLOC_FREE(subreq);
+ if (err) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_close_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_close(struct cli_state *cli, uint16_t fnum)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_close_send(frame, ev, cli, fnum, 0);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_close_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Truncate a file to a specified size
+****************************************************************************/
+
+struct ftrunc_state {
+ uint8_t data[8];
+};
+
+static void cli_ftruncate_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setfileinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+struct tevent_req *cli_ftruncate_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t size)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct ftrunc_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct ftrunc_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Setup data array. */
+ SBVAL(state->data, 0, size);
+
+ subreq = cli_setfileinfo_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ SMB_SET_FILE_END_OF_FILE_INFO,
+ state->data,
+ sizeof(state->data));
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ftruncate_done, req);
+ return req;
+}
+
+NTSTATUS cli_ftruncate_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_ftruncate(struct cli_state *cli, uint16_t fnum, uint64_t size)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_ftruncate(cli, fnum, size);
+ }
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_ftruncate_send(frame,
+ ev,
+ cli,
+ fnum,
+ size);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_ftruncate_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static uint8_t *cli_lockingx_put_locks(
+ uint8_t *buf,
+ bool large,
+ uint16_t num_locks,
+ const struct smb1_lock_element *locks)
+{
+ uint16_t i;
+
+ for (i=0; i<num_locks; i++) {
+ const struct smb1_lock_element *e = &locks[i];
+ if (large) {
+ SSVAL(buf, 0, e->pid);
+ SSVAL(buf, 2, 0);
+ SOFF_T_R(buf, 4, e->offset);
+ SOFF_T_R(buf, 12, e->length);
+ buf += 20;
+ } else {
+ SSVAL(buf, 0, e->pid);
+ SIVAL(buf, 2, e->offset);
+ SIVAL(buf, 6, e->length);
+ buf += 10;
+ }
+ }
+ return buf;
+}
+
+struct cli_lockingx_state {
+ uint16_t vwv[8];
+ struct iovec bytes;
+ struct tevent_req *subreq;
+};
+
+static void cli_lockingx_done(struct tevent_req *subreq);
+static bool cli_lockingx_cancel(struct tevent_req *req);
+
+struct tevent_req *cli_lockingx_create(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t typeoflock,
+ uint8_t newoplocklevel,
+ int32_t timeout,
+ uint16_t num_unlocks,
+ const struct smb1_lock_element *unlocks,
+ uint16_t num_locks,
+ const struct smb1_lock_element *locks,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_lockingx_state *state = NULL;
+ uint16_t *vwv;
+ uint8_t *p;
+ const bool large = (typeoflock & LOCKING_ANDX_LARGE_FILES);
+ const size_t element_len = large ? 20 : 10;
+
+ /* uint16->size_t, no overflow */
+ const size_t num_elements = (size_t)num_locks + (size_t)num_unlocks;
+
+ /* at most 20*2*65535 = 2621400, no overflow */
+ const size_t num_bytes = num_elements * element_len;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_lockingx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ vwv = state->vwv;
+
+ SCVAL(vwv + 0, 0, 0xFF);
+ SCVAL(vwv + 0, 1, 0);
+ SSVAL(vwv + 1, 0, 0);
+ SSVAL(vwv + 2, 0, fnum);
+ SCVAL(vwv + 3, 0, typeoflock);
+ SCVAL(vwv + 3, 1, newoplocklevel);
+ SIVALS(vwv + 4, 0, timeout);
+ SSVAL(vwv + 6, 0, num_unlocks);
+ SSVAL(vwv + 7, 0, num_locks);
+
+ state->bytes.iov_len = num_bytes;
+ state->bytes.iov_base = talloc_array(state, uint8_t, num_bytes);
+ if (tevent_req_nomem(state->bytes.iov_base, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ p = cli_lockingx_put_locks(
+ state->bytes.iov_base, large, num_unlocks, unlocks);
+ cli_lockingx_put_locks(p, large, num_locks, locks);
+
+ subreq = cli_smb_req_create(
+ state, ev, cli, SMBlockingX, 0, 0, 8, vwv, 1, &state->bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_lockingx_done, req);
+ *psmbreq = subreq;
+ return req;
+}
+
+struct tevent_req *cli_lockingx_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t typeoflock,
+ uint8_t newoplocklevel,
+ int32_t timeout,
+ uint16_t num_unlocks,
+ const struct smb1_lock_element *unlocks,
+ uint16_t num_locks,
+ const struct smb1_lock_element *locks)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_lockingx_state *state = NULL;
+ NTSTATUS status;
+
+ req = cli_lockingx_create(
+ mem_ctx,
+ ev,
+ cli,
+ fnum,
+ typeoflock,
+ newoplocklevel,
+ timeout,
+ num_unlocks,
+ unlocks,
+ num_locks,
+ locks,
+ &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+ state = tevent_req_data(req, struct cli_lockingx_state);
+ state->subreq = subreq;
+
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_cancel_fn(req, cli_lockingx_cancel);
+ return req;
+}
+
+static void cli_lockingx_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(
+ subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static bool cli_lockingx_cancel(struct tevent_req *req)
+{
+ struct cli_lockingx_state *state = tevent_req_data(
+ req, struct cli_lockingx_state);
+ if (state->subreq == NULL) {
+ return false;
+ }
+ return tevent_req_cancel(state->subreq);
+}
+
+NTSTATUS cli_lockingx_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_lockingx(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t typeoflock,
+ uint8_t newoplocklevel,
+ int32_t timeout,
+ uint16_t num_unlocks,
+ const struct smb1_lock_element *unlocks,
+ uint16_t num_locks,
+ const struct smb1_lock_element *locks)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ unsigned int set_timeout = 0;
+ unsigned int saved_timeout = 0;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ if (timeout != 0) {
+ if (timeout == -1) {
+ set_timeout = 0x7FFFFFFF;
+ } else {
+ set_timeout = timeout + 2*1000;
+ }
+ saved_timeout = cli_set_timeout(cli, set_timeout);
+ }
+
+ req = cli_lockingx_send(
+ frame,
+ ev,
+ cli,
+ fnum,
+ typeoflock,
+ newoplocklevel,
+ timeout,
+ num_unlocks,
+ unlocks,
+ num_locks,
+ locks);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_lockingx_recv(req);
+
+ if (saved_timeout != 0) {
+ cli_set_timeout(cli, saved_timeout);
+ }
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ send a lock with a specified locktype
+ this is used for testing LOCKING_ANDX_CANCEL_LOCK
+****************************************************************************/
+
+NTSTATUS cli_locktype(struct cli_state *cli, uint16_t fnum,
+ uint32_t offset, uint32_t len,
+ int timeout, unsigned char locktype)
+{
+ struct smb1_lock_element lck = {
+ .pid = cli_getpid(cli),
+ .offset = offset,
+ .length = len,
+ };
+ NTSTATUS status;
+
+ status = cli_lockingx(
+ cli, /* cli */
+ fnum, /* fnum */
+ locktype, /* typeoflock */
+ 0, /* newoplocklevel */
+ timeout, /* timeout */
+ 0, /* num_unlocks */
+ NULL, /* unlocks */
+ 1, /* num_locks */
+ &lck); /* locks */
+ return status;
+}
+
+/****************************************************************************
+ Lock a file.
+ note that timeout is in units of 2 milliseconds
+****************************************************************************/
+
+NTSTATUS cli_lock32(struct cli_state *cli, uint16_t fnum,
+ uint32_t offset, uint32_t len, int timeout,
+ enum brl_type lock_type)
+{
+ NTSTATUS status;
+
+ status = cli_locktype(cli, fnum, offset, len, timeout,
+ (lock_type == READ_LOCK? 1 : 0));
+ return status;
+}
+
+/****************************************************************************
+ Unlock a file.
+****************************************************************************/
+
+struct cli_unlock_state {
+ struct smb1_lock_element lck;
+};
+
+static void cli_unlock_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_unlock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t offset,
+ uint64_t len)
+
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_unlock_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_unlock_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->lck = (struct smb1_lock_element) {
+ .pid = cli_getpid(cli),
+ .offset = offset,
+ .length = len,
+ };
+
+ subreq = cli_lockingx_send(
+ state, /* mem_ctx */
+ ev, /* tevent_context */
+ cli, /* cli */
+ fnum, /* fnum */
+ 0, /* typeoflock */
+ 0, /* newoplocklevel */
+ 0, /* timeout */
+ 1, /* num_unlocks */
+ &state->lck, /* unlocks */
+ 0, /* num_locks */
+ NULL); /* locks */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_unlock_done, req);
+ return req;
+}
+
+static void cli_unlock_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_lockingx_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_unlock_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_unlock(struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t offset,
+ uint32_t len)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_unlock_send(frame, ev, cli,
+ fnum, offset, len);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_unlock_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Get/unlock a POSIX lock on a file - internal function.
+****************************************************************************/
+
+struct posix_lock_state {
+ uint16_t setup;
+ uint8_t param[4];
+ uint8_t data[POSIX_LOCK_DATA_SIZE];
+};
+
+static void cli_posix_unlock_internal_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_trans_recv(subreq, NULL, NULL, NULL, 0, NULL,
+ NULL, 0, NULL, NULL, 0, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static struct tevent_req *cli_posix_lock_internal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t offset,
+ uint64_t len,
+ bool wait_lock,
+ enum brl_type lock_type)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct posix_lock_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct posix_lock_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Setup setup word. */
+ SSVAL(&state->setup, 0, TRANSACT2_SETFILEINFO);
+
+ /* Setup param array. */
+ SSVAL(&state->param, 0, fnum);
+ SSVAL(&state->param, 2, SMB_SET_POSIX_LOCK);
+
+ /* Setup data array. */
+ switch (lock_type) {
+ case READ_LOCK:
+ SSVAL(&state->data, POSIX_LOCK_TYPE_OFFSET,
+ POSIX_LOCK_TYPE_READ);
+ break;
+ case WRITE_LOCK:
+ SSVAL(&state->data, POSIX_LOCK_TYPE_OFFSET,
+ POSIX_LOCK_TYPE_WRITE);
+ break;
+ case UNLOCK_LOCK:
+ SSVAL(&state->data, POSIX_LOCK_TYPE_OFFSET,
+ POSIX_LOCK_TYPE_UNLOCK);
+ break;
+ default:
+ return NULL;
+ }
+
+ if (wait_lock) {
+ SSVAL(&state->data, POSIX_LOCK_FLAGS_OFFSET,
+ POSIX_LOCK_FLAG_WAIT);
+ } else {
+ SSVAL(state->data, POSIX_LOCK_FLAGS_OFFSET,
+ POSIX_LOCK_FLAG_NOWAIT);
+ }
+
+ SIVAL(&state->data, POSIX_LOCK_PID_OFFSET, cli_getpid(cli));
+ SOFF_T(&state->data, POSIX_LOCK_START_OFFSET, offset);
+ SOFF_T(&state->data, POSIX_LOCK_LEN_OFFSET, len);
+
+ subreq = cli_trans_send(state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ 0, /* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ 0, /* function. */
+ 0, /* flags. */
+ &state->setup, /* setup. */
+ 1, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ state->param, /* param. */
+ 4, /* num param. */
+ 2, /* max returned param. */
+ state->data, /* data. */
+ POSIX_LOCK_DATA_SIZE, /* num data. */
+ 0); /* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_unlock_internal_done, req);
+ return req;
+}
+
+/****************************************************************************
+ POSIX Lock a file.
+****************************************************************************/
+
+struct tevent_req *cli_posix_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t offset,
+ uint64_t len,
+ bool wait_lock,
+ enum brl_type lock_type)
+{
+ return cli_posix_lock_internal_send(mem_ctx, ev, cli, fnum, offset, len,
+ wait_lock, lock_type);
+}
+
+NTSTATUS cli_posix_lock_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_lock(struct cli_state *cli, uint16_t fnum,
+ uint64_t offset, uint64_t len,
+ bool wait_lock, enum brl_type lock_type)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (lock_type != READ_LOCK && lock_type != WRITE_LOCK) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_lock_send(frame,
+ ev,
+ cli,
+ fnum,
+ offset,
+ len,
+ wait_lock,
+ lock_type);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_lock_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ POSIX Unlock a file.
+****************************************************************************/
+
+struct tevent_req *cli_posix_unlock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t offset,
+ uint64_t len)
+{
+ return cli_posix_lock_internal_send(mem_ctx, ev, cli, fnum, offset, len,
+ false, UNLOCK_LOCK);
+}
+
+NTSTATUS cli_posix_unlock_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_unlock(struct cli_state *cli, uint16_t fnum, uint64_t offset, uint64_t len)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_unlock_send(frame,
+ ev,
+ cli,
+ fnum,
+ offset,
+ len);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_unlock_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Do a SMBgetattrE call.
+****************************************************************************/
+
+static void cli_getattrE_done(struct tevent_req *subreq);
+
+struct cli_getattrE_state {
+ uint16_t vwv[1];
+ int zone_offset;
+ uint32_t attr;
+ off_t size;
+ time_t change_time;
+ time_t access_time;
+ time_t write_time;
+};
+
+struct tevent_req *cli_getattrE_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_getattrE_state *state = NULL;
+ uint8_t additional_flags = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_getattrE_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->zone_offset = smb1cli_conn_server_time_zone(cli->conn);
+ SSVAL(state->vwv+0,0,fnum);
+
+ subreq = cli_smb_send(state, ev, cli, SMBgetattrE, additional_flags, 0,
+ 1, state->vwv, 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_getattrE_done, req);
+ return req;
+}
+
+static void cli_getattrE_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_getattrE_state *state = tevent_req_data(
+ req, struct cli_getattrE_state);
+ uint8_t wct;
+ uint16_t *vwv = NULL;
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 11, &wct, &vwv,
+ NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->size = (off_t)IVAL(vwv+6,0);
+ state->attr = SVAL(vwv+10,0);
+ state->change_time = make_unix_date2(vwv+0, state->zone_offset);
+ state->access_time = make_unix_date2(vwv+2, state->zone_offset);
+ state->write_time = make_unix_date2(vwv+4, state->zone_offset);
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_getattrE_recv(struct tevent_req *req,
+ uint32_t *pattr,
+ off_t *size,
+ time_t *change_time,
+ time_t *access_time,
+ time_t *write_time)
+{
+ struct cli_getattrE_state *state = tevent_req_data(
+ req, struct cli_getattrE_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (pattr) {
+ *pattr = state->attr;
+ }
+ if (size) {
+ *size = state->size;
+ }
+ if (change_time) {
+ *change_time = state->change_time;
+ }
+ if (access_time) {
+ *access_time = state->access_time;
+ }
+ if (write_time) {
+ *write_time = state->write_time;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Do a SMBgetatr call
+****************************************************************************/
+
+static void cli_getatr_done(struct tevent_req *subreq);
+
+struct cli_getatr_state {
+ int zone_offset;
+ uint32_t attr;
+ off_t size;
+ time_t write_time;
+};
+
+struct tevent_req *cli_getatr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_getatr_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_getatr_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->zone_offset = smb1cli_conn_server_time_zone(cli->conn);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBgetatr on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBgetatr, additional_flags,
+ additional_flags2,
+ 0, NULL, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_getatr_done, req);
+ return req;
+}
+
+static void cli_getatr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_getatr_state *state = tevent_req_data(
+ req, struct cli_getatr_state);
+ uint8_t wct;
+ uint16_t *vwv = NULL;
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 4, &wct, &vwv, NULL,
+ NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->attr = SVAL(vwv+0,0);
+ state->size = (off_t)IVAL(vwv+3,0);
+ state->write_time = make_unix_date3(vwv+1, state->zone_offset);
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_getatr_recv(struct tevent_req *req,
+ uint32_t *pattr,
+ off_t *size,
+ time_t *write_time)
+{
+ struct cli_getatr_state *state = tevent_req_data(
+ req, struct cli_getatr_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (pattr) {
+ *pattr = state->attr;
+ }
+ if (size) {
+ *size = state->size;
+ }
+ if (write_time) {
+ *write_time = state->write_time;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_getatr(struct cli_state *cli,
+ const char *fname,
+ uint32_t *pattr,
+ off_t *size,
+ time_t *write_time)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ struct stat_ex sbuf = {
+ .st_ex_nlink = 0,
+ };
+ uint32_t attr;
+
+ status = cli_smb2_qpathinfo_basic(cli, fname, &sbuf, &attr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (pattr != NULL) {
+ *pattr = attr;
+ }
+ if (size != NULL) {
+ *size = sbuf.st_ex_size;
+ }
+ if (write_time != NULL) {
+ *write_time = sbuf.st_ex_mtime.tv_sec;
+ }
+ return NT_STATUS_OK;
+ }
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_getatr_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_getatr_recv(req,
+ pattr,
+ size,
+ write_time);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Do a SMBsetattrE call.
+****************************************************************************/
+
+static void cli_setattrE_done(struct tevent_req *subreq);
+
+struct cli_setattrE_state {
+ uint16_t vwv[7];
+};
+
+struct tevent_req *cli_setattrE_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ time_t change_time,
+ time_t access_time,
+ time_t write_time)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_setattrE_state *state = NULL;
+ uint8_t additional_flags = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_setattrE_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ SSVAL(state->vwv+0, 0, fnum);
+ push_dos_date2((uint8_t *)&state->vwv[1], 0, change_time,
+ smb1cli_conn_server_time_zone(cli->conn));
+ push_dos_date2((uint8_t *)&state->vwv[3], 0, access_time,
+ smb1cli_conn_server_time_zone(cli->conn));
+ push_dos_date2((uint8_t *)&state->vwv[5], 0, write_time,
+ smb1cli_conn_server_time_zone(cli->conn));
+
+ subreq = cli_smb_send(state, ev, cli, SMBsetattrE, additional_flags, 0,
+ 7, state->vwv, 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_setattrE_done, req);
+ return req;
+}
+
+static void cli_setattrE_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_setattrE_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_setattrE(struct cli_state *cli,
+ uint16_t fnum,
+ time_t change_time,
+ time_t access_time,
+ time_t write_time)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_setattrE(cli,
+ fnum,
+ change_time,
+ access_time,
+ write_time);
+ }
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_setattrE_send(frame, ev,
+ cli,
+ fnum,
+ change_time,
+ access_time,
+ write_time);
+
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_setattrE_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Do a SMBsetatr call.
+****************************************************************************/
+
+static void cli_setatr_done(struct tevent_req *subreq);
+
+struct cli_setatr_state {
+ uint16_t vwv[8];
+};
+
+struct tevent_req *cli_setatr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t attr,
+ time_t mtime)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_setatr_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_setatr_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (attr & 0xFFFF0000) {
+ /*
+ * Don't allow attributes greater than
+ * 16-bits for a 16-bit protocol value.
+ */
+ if (tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ SSVAL(state->vwv+0, 0, attr);
+ push_dos_date3((uint8_t *)&state->vwv[1], 0, mtime, smb1cli_conn_server_time_zone(cli->conn));
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBsetatr on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes = talloc_realloc(state, bytes, uint8_t,
+ talloc_get_size(bytes)+1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ bytes[talloc_get_size(bytes)-1] = 4;
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(cli->conn), "",
+ 1, NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBsetatr, additional_flags,
+ additional_flags2,
+ 8, state->vwv, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_setatr_done, req);
+ return req;
+}
+
+static void cli_setatr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_setatr_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_setatr(struct cli_state *cli,
+ const char *fname,
+ uint32_t attr,
+ time_t mtime)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_setatr(cli,
+ fname,
+ attr,
+ mtime);
+ }
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_setatr_send(frame, ev, cli, fname, attr, mtime);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_setatr_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Check for existence of a dir.
+****************************************************************************/
+
+static void cli_chkpath_done(struct tevent_req *subreq);
+static void cli_chkpath_opened(struct tevent_req *subreq);
+static void cli_chkpath_closed(struct tevent_req *subreq);
+
+struct cli_chkpath_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+};
+
+struct tevent_req *cli_chkpath_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_chkpath_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_chkpath_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_NT1) {
+ subreq = cli_ntcreate_send(
+ state, /* mem_ctx */
+ state->ev, /* ev */
+ state->cli, /* cli */
+ fname, /* fname */
+ 0, /* create_flags */
+ FILE_READ_ATTRIBUTES, /* desired_access */
+ FILE_ATTRIBUTE_DIRECTORY, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access */
+ FILE_OPEN, /* CreateDisposition */
+ FILE_DIRECTORY_FILE, /* CreateOptions */
+ SMB2_IMPERSONATION_IMPERSONATION,
+ 0); /* SecurityFlags */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_chkpath_opened, req);
+ return req;
+ }
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBcheckpath on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ NULL);
+
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBcheckpath, additional_flags,
+ additional_flags2,
+ 0, NULL, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_chkpath_done, req);
+ return req;
+}
+
+static void cli_chkpath_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(
+ subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_chkpath_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_chkpath_state *state = tevent_req_data(
+ req, struct cli_chkpath_state);
+ NTSTATUS status;
+ uint16_t fnum;
+
+ status = cli_ntcreate_recv(subreq, &fnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_close_send(state, state->ev, state->cli, fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_chkpath_closed, req);
+}
+
+static void cli_chkpath_closed(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_close_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_chkpath_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_chkpath(struct cli_state *cli, const char *path)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ char *path2 = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ path2 = talloc_strdup(frame, path);
+ if (!path2) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ trim_char(path2,'\0','\\');
+ if (!*path2) {
+ path2 = talloc_strdup(frame, "\\");
+ if (!path2) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_chkpath_send(frame, ev, cli, path2);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_chkpath_recv(req);
+ cli->raw_status = status; /* cli_smb2_chkpath_recv doesn't set this */
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Query disk space.
+****************************************************************************/
+
+static void cli_dskattr_done(struct tevent_req *subreq);
+
+struct cli_dskattr_state {
+ int bsize;
+ int total;
+ int avail;
+};
+
+struct tevent_req *cli_dskattr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_dskattr_state *state = NULL;
+ uint8_t additional_flags = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_dskattr_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBdskattr, additional_flags, 0,
+ 0, NULL, 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_dskattr_done, req);
+ return req;
+}
+
+static void cli_dskattr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_dskattr_state *state = tevent_req_data(
+ req, struct cli_dskattr_state);
+ uint8_t wct;
+ uint16_t *vwv = NULL;
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 4, &wct, &vwv, NULL,
+ NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->bsize = SVAL(vwv+1, 0)*SVAL(vwv+2,0);
+ state->total = SVAL(vwv+0, 0);
+ state->avail = SVAL(vwv+3, 0);
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_dskattr_recv(struct tevent_req *req, int *bsize, int *total, int *avail)
+{
+ struct cli_dskattr_state *state = tevent_req_data(
+ req, struct cli_dskattr_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *bsize = state->bsize;
+ *total = state->total;
+ *avail = state->avail;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_dskattr(struct cli_state *cli, int *bsize, int *total, int *avail)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_dskattr_send(frame, ev, cli);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_dskattr_recv(req, bsize, total, avail);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS cli_disk_size(struct cli_state *cli, const char *path, uint64_t *bsize,
+ uint64_t *total, uint64_t *avail)
+{
+ uint64_t sectors_per_block;
+ uint64_t bytes_per_sector;
+ int old_bsize = 0, old_total = 0, old_avail = 0;
+ NTSTATUS status;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_dskattr(cli, path, bsize, total, avail);
+ }
+
+ /*
+ * Try the trans2 disk full size info call first.
+ * We already use this in SMBC_fstatvfs_ctx().
+ * Ignore 'actual_available_units' as we only
+ * care about the quota for the caller.
+ */
+
+ status = cli_get_fs_full_size_info(cli,
+ total,
+ avail,
+ NULL,
+ &sectors_per_block,
+ &bytes_per_sector);
+
+ /* Try and cope will all variants of "we don't do this call"
+ and fall back to cli_dskattr. */
+
+ if (NT_STATUS_EQUAL(status,NT_STATUS_NOT_IMPLEMENTED) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_NOT_SUPPORTED) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_INFO_CLASS) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_PROCEDURE_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_LEVEL) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_PARAMETER) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_DEVICE_REQUEST) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_INVALID_DEVICE_STATE) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_CTL_FILE_NOT_SUPPORTED) ||
+ NT_STATUS_EQUAL(status,NT_STATUS_UNSUCCESSFUL)) {
+ goto try_dskattr;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (bsize) {
+ *bsize = sectors_per_block *
+ bytes_per_sector;
+ }
+
+ return NT_STATUS_OK;
+
+ try_dskattr:
+
+ /* Old SMB1 core protocol fallback. */
+ status = cli_dskattr(cli, &old_bsize, &old_total, &old_avail);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (bsize) {
+ *bsize = (uint64_t)old_bsize;
+ }
+ if (total) {
+ *total = (uint64_t)old_total;
+ }
+ if (avail) {
+ *avail = (uint64_t)old_avail;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Create and open a temporary file.
+****************************************************************************/
+
+static void cli_ctemp_done(struct tevent_req *subreq);
+
+struct ctemp_state {
+ uint16_t vwv[3];
+ char *ret_path;
+ uint16_t fnum;
+};
+
+struct tevent_req *cli_ctemp_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *path)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct ctemp_state *state = NULL;
+ uint8_t additional_flags = 0;
+ uint16_t additional_flags2 = 0;
+ uint8_t *bytes = NULL;
+ char *path_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct ctemp_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ SSVAL(state->vwv,0,0);
+ SIVALS(state->vwv+1,0,-1);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * SMBctemp on a DFS share must use DFS names.
+ */
+ path_cp = smb1_dfs_share_path(state, cli, path);
+ if (tevent_req_nomem(path_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path_cp,
+ strlen(path_cp)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(path)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_smb_send(state, ev, cli, SMBctemp, additional_flags,
+ additional_flags2,
+ 3, state->vwv, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_ctemp_done, req);
+ return req;
+}
+
+static void cli_ctemp_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct ctemp_state *state = tevent_req_data(
+ req, struct ctemp_state);
+ NTSTATUS status;
+ uint8_t wcnt;
+ uint16_t *vwv;
+ uint32_t num_bytes = 0;
+ uint8_t *bytes = NULL;
+
+ status = cli_smb_recv(subreq, state, NULL, 1, &wcnt, &vwv,
+ &num_bytes, &bytes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->fnum = SVAL(vwv+0, 0);
+
+ /* From W2K3, the result is just the ASCII name */
+ if (num_bytes < 2) {
+ tevent_req_nterror(req, NT_STATUS_DATA_ERROR);
+ return;
+ }
+
+ if (pull_string_talloc(state,
+ NULL,
+ 0,
+ &state->ret_path,
+ bytes,
+ num_bytes,
+ STR_ASCII) == 0) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_ctemp_recv(struct tevent_req *req,
+ TALLOC_CTX *ctx,
+ uint16_t *pfnum,
+ char **outfile)
+{
+ struct ctemp_state *state = tevent_req_data(req,
+ struct ctemp_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfnum = state->fnum;
+ *outfile = talloc_strdup(ctx, state->ret_path);
+ if (!*outfile) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_ctemp(struct cli_state *cli,
+ TALLOC_CTX *ctx,
+ const char *path,
+ uint16_t *pfnum,
+ char **out_path)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_ctemp_send(frame, ev, cli, path);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_ctemp_recv(req, ctx, pfnum, out_path);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*********************************************************
+ Set an extended attribute utility fn.
+*********************************************************/
+
+static NTSTATUS cli_set_ea(struct cli_state *cli, uint16_t setup_val,
+ uint8_t *param, unsigned int param_len,
+ const char *ea_name,
+ const char *ea_val, size_t ea_len)
+{
+ uint16_t setup[1];
+ unsigned int data_len = 0;
+ uint8_t *data = NULL;
+ char *p;
+ size_t ea_namelen = strlen(ea_name);
+ NTSTATUS status;
+
+ SSVAL(setup, 0, setup_val);
+
+ if (ea_namelen == 0 && ea_len == 0) {
+ data_len = 4;
+ data = talloc_array(talloc_tos(),
+ uint8_t,
+ data_len);
+ if (!data) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = (char *)data;
+ SIVAL(p,0,data_len);
+ } else {
+ data_len = 4 + 4 + ea_namelen + 1 + ea_len;
+ data = talloc_array(talloc_tos(),
+ uint8_t,
+ data_len);
+ if (!data) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = (char *)data;
+ SIVAL(p,0,data_len);
+ p += 4;
+ SCVAL(p, 0, 0); /* EA flags. */
+ SCVAL(p, 1, ea_namelen);
+ SSVAL(p, 2, ea_len);
+ memcpy(p+4, ea_name, ea_namelen+1); /* Copy in the name. */
+ memcpy(p+4+ea_namelen+1, ea_val, ea_len);
+ }
+
+ /*
+ * FIXME - if we want to do previous version path
+ * processing on an EA set call we need to turn this
+ * into calls to cli_trans_send()/cli_trans_recv()
+ * with a temporary event context, as cli_trans_send()
+ * have access to the additional_flags2 needed to
+ * send @GMT- paths. JRA.
+ */
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2, NULL, -1, 0, 0,
+ setup, 1, 0,
+ param, param_len, 2,
+ data, data_len, 0,
+ NULL,
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL, /* rparam */
+ NULL, 0, NULL); /* rdata */
+ talloc_free(data);
+ return status;
+}
+
+/*********************************************************
+ Set an extended attribute on a pathname.
+*********************************************************/
+
+NTSTATUS cli_set_ea_path(struct cli_state *cli, const char *path,
+ const char *ea_name, const char *ea_val,
+ size_t ea_len)
+{
+ unsigned int param_len = 0;
+ uint8_t *param;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+ char *path_cp = NULL;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_set_ea_path(cli,
+ path,
+ ea_name,
+ ea_val,
+ ea_len);
+ }
+
+ frame = talloc_stackframe();
+
+ param = talloc_array(frame, uint8_t, 6);
+ if (!param) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ SSVAL(param,0,SMB_INFO_SET_EA);
+ SSVAL(param,2,0);
+ SSVAL(param,4,0);
+
+ /*
+ * TRANSACT2_SETPATHINFO on a DFS share must use DFS names.
+ */
+ path_cp = smb1_dfs_share_path(frame, cli, path);
+ if (path_cp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ param = trans2_bytes_push_str(param,
+ smbXcli_conn_use_unicode(cli->conn),
+ path_cp,
+ strlen(path_cp)+1,
+ NULL);
+ param_len = talloc_get_size(param);
+
+ status = cli_set_ea(cli, TRANSACT2_SETPATHINFO, param, param_len,
+ ea_name, ea_val, ea_len);
+
+ fail:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*********************************************************
+ Set an extended attribute on an fnum.
+*********************************************************/
+
+NTSTATUS cli_set_ea_fnum(struct cli_state *cli, uint16_t fnum,
+ const char *ea_name, const char *ea_val,
+ size_t ea_len)
+{
+ uint8_t param[6] = { 0, };
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_set_ea_fnum(cli,
+ fnum,
+ ea_name,
+ ea_val,
+ ea_len);
+ }
+
+ SSVAL(param,0,fnum);
+ SSVAL(param,2,SMB_INFO_SET_EA);
+
+ return cli_set_ea(cli, TRANSACT2_SETFILEINFO, param, 6,
+ ea_name, ea_val, ea_len);
+}
+
+/*********************************************************
+ Get an extended attribute list utility fn.
+*********************************************************/
+
+static bool parse_ea_blob(TALLOC_CTX *ctx, const uint8_t *rdata,
+ size_t rdata_len,
+ size_t *pnum_eas, struct ea_struct **pea_list)
+{
+ struct ea_struct *ea_list = NULL;
+ size_t num_eas;
+ size_t ea_size;
+ const uint8_t *p;
+
+ if (rdata_len < 4) {
+ return false;
+ }
+
+ ea_size = (size_t)IVAL(rdata,0);
+ if (ea_size > rdata_len) {
+ return false;
+ }
+
+ if (ea_size == 0) {
+ /* No EA's present. */
+ *pnum_eas = 0;
+ *pea_list = NULL;
+ return true;
+ }
+
+ p = rdata + 4;
+ ea_size -= 4;
+
+ /* Validate the EA list and count it. */
+ for (num_eas = 0; ea_size >= 4; num_eas++) {
+ unsigned int ea_namelen = CVAL(p,1);
+ unsigned int ea_valuelen = SVAL(p,2);
+ if (ea_namelen == 0) {
+ return false;
+ }
+ if (4 + ea_namelen + 1 + ea_valuelen > ea_size) {
+ return false;
+ }
+ ea_size -= 4 + ea_namelen + 1 + ea_valuelen;
+ p += 4 + ea_namelen + 1 + ea_valuelen;
+ }
+
+ if (num_eas == 0) {
+ *pnum_eas = 0;
+ *pea_list = NULL;
+ return true;
+ }
+
+ *pnum_eas = num_eas;
+ if (!pea_list) {
+ /* Caller only wants number of EA's. */
+ return true;
+ }
+
+ ea_list = talloc_array(ctx, struct ea_struct, num_eas);
+ if (!ea_list) {
+ return false;
+ }
+
+ p = rdata + 4;
+
+ for (num_eas = 0; num_eas < *pnum_eas; num_eas++ ) {
+ struct ea_struct *ea = &ea_list[num_eas];
+ fstring unix_ea_name;
+ unsigned int ea_namelen = CVAL(p,1);
+ unsigned int ea_valuelen = SVAL(p,2);
+
+ ea->flags = CVAL(p,0);
+ unix_ea_name[0] = '\0';
+ pull_ascii(unix_ea_name, p + 4, sizeof(unix_ea_name), rdata_len - PTR_DIFF(p+4, rdata), STR_TERMINATE);
+ ea->name = talloc_strdup(ea_list, unix_ea_name);
+ if (!ea->name) {
+ goto fail;
+ }
+ /* Ensure the value is null terminated (in case it's a string). */
+ ea->value = data_blob_talloc(ea_list, NULL, ea_valuelen + 1);
+ if (!ea->value.data) {
+ goto fail;
+ }
+ if (ea_valuelen) {
+ memcpy(ea->value.data, p+4+ea_namelen+1, ea_valuelen);
+ }
+ ea->value.data[ea_valuelen] = 0;
+ ea->value.length--;
+ p += 4 + ea_namelen + 1 + ea_valuelen;
+ }
+
+ *pea_list = ea_list;
+ return true;
+
+fail:
+ TALLOC_FREE(ea_list);
+ return false;
+}
+
+/*********************************************************
+ Get an extended attribute list from a pathname.
+*********************************************************/
+
+struct cli_get_ea_list_path_state {
+ uint32_t num_data;
+ uint8_t *data;
+};
+
+static void cli_get_ea_list_path_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_get_ea_list_path_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_get_ea_list_path_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_get_ea_list_path_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = cli_qpathinfo_send(state, ev, cli, fname,
+ SMB_INFO_QUERY_ALL_EAS, 4,
+ CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_get_ea_list_path_done, req);
+ return req;
+}
+
+static void cli_get_ea_list_path_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_get_ea_list_path_state *state = tevent_req_data(
+ req, struct cli_get_ea_list_path_state);
+ NTSTATUS status;
+
+ status = cli_qpathinfo_recv(subreq, state, &state->data,
+ &state->num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_get_ea_list_path_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *pnum_eas, struct ea_struct **peas)
+{
+ struct cli_get_ea_list_path_state *state = tevent_req_data(
+ req, struct cli_get_ea_list_path_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (!parse_ea_blob(mem_ctx, state->data, state->num_data,
+ pnum_eas, peas)) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_get_ea_list_path(struct cli_state *cli, const char *path,
+ TALLOC_CTX *ctx,
+ size_t *pnum_eas,
+ struct ea_struct **pea_list)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_get_ea_list_path(cli,
+ path,
+ ctx,
+ pnum_eas,
+ pea_list);
+ }
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_get_ea_list_path_send(frame, ev, cli, path);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_get_ea_list_path_recv(req, ctx, pnum_eas, pea_list);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Convert open "flags" arg to uint32_t on wire.
+****************************************************************************/
+
+static uint32_t open_flags_to_wire(int flags)
+{
+ int open_mode = flags & O_ACCMODE;
+ uint32_t ret = 0;
+
+ switch (open_mode) {
+ case O_WRONLY:
+ ret |= SMB_O_WRONLY;
+ break;
+ case O_RDWR:
+ ret |= SMB_O_RDWR;
+ break;
+ default:
+ case O_RDONLY:
+ ret |= SMB_O_RDONLY;
+ break;
+ }
+
+ if (flags & O_CREAT) {
+ ret |= SMB_O_CREAT;
+ }
+ if (flags & O_EXCL) {
+ ret |= SMB_O_EXCL;
+ }
+ if (flags & O_TRUNC) {
+ ret |= SMB_O_TRUNC;
+ }
+#if defined(O_SYNC)
+ if (flags & O_SYNC) {
+ ret |= SMB_O_SYNC;
+ }
+#endif /* O_SYNC */
+ if (flags & O_APPEND) {
+ ret |= SMB_O_APPEND;
+ }
+#if defined(O_DIRECT)
+ if (flags & O_DIRECT) {
+ ret |= SMB_O_DIRECT;
+ }
+#endif
+#if defined(O_DIRECTORY)
+ if (flags & O_DIRECTORY) {
+ ret |= SMB_O_DIRECTORY;
+ }
+#endif
+ return ret;
+}
+
+/****************************************************************************
+ Open a file - POSIX semantics. Returns fnum. Doesn't request oplock.
+****************************************************************************/
+
+struct cli_posix_open_internal_state {
+ uint16_t setup;
+ uint8_t *param;
+ uint8_t data[18];
+ uint16_t fnum; /* Out */
+};
+
+static void cli_posix_open_internal_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_posix_open_internal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t wire_flags,
+ mode_t mode)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_open_internal_state *state = NULL;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_posix_open_internal_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Setup setup word. */
+ SSVAL(&state->setup, 0, TRANSACT2_SETPATHINFO);
+
+ /* Setup param array. */
+ state->param = talloc_zero_array(state, uint8_t, 6);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+ SSVAL(state->param, 0, SMB_POSIX_PATH_OPEN);
+
+ /*
+ * TRANSACT2_SETPATHINFO on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->param = trans2_bytes_push_str(
+ state->param,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ NULL);
+
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ SIVAL(state->data,0,0); /* No oplock. */
+ SIVAL(state->data,4,wire_flags);
+ SIVAL(state->data,8,unix_perms_to_wire(mode));
+ SIVAL(state->data,12,0); /* Top bits of perms currently undefined. */
+ SSVAL(state->data,16,SMB_NO_INFO_LEVEL_RETURNED); /* No info level returned. */
+
+ subreq = cli_trans_send(state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ 0, /* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ 0, /* function. */
+ 0, /* flags. */
+ &state->setup, /* setup. */
+ 1, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ state->param, /* param. */
+ talloc_get_size(state->param),/* num param. */
+ 2, /* max returned param. */
+ state->data, /* data. */
+ 18, /* num data. */
+ 12); /* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_open_internal_done, req);
+ return req;
+}
+
+static void cli_posix_open_internal_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_posix_open_internal_state *state = tevent_req_data(
+ req, struct cli_posix_open_internal_state);
+ NTSTATUS status;
+ uint8_t *data;
+ uint32_t num_data;
+
+ status = cli_trans_recv(
+ subreq,
+ state,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ &data,
+ 12,
+ &num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->fnum = SVAL(data,2);
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_posix_open_internal_recv(struct tevent_req *req,
+ uint16_t *pfnum)
+{
+ struct cli_posix_open_internal_state *state = tevent_req_data(
+ req, struct cli_posix_open_internal_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfnum = state->fnum;
+ return NT_STATUS_OK;
+}
+
+struct cli_posix_open_state {
+ uint16_t fnum;
+};
+
+static void cli_posix_open_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_open_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ int flags,
+ mode_t mode)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_open_state *state = NULL;
+ uint32_t wire_flags;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_posix_open_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ wire_flags = open_flags_to_wire(flags);
+
+ subreq = cli_posix_open_internal_send(
+ mem_ctx, ev, cli, fname, wire_flags, mode);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_open_done, req);
+ return req;
+}
+
+static void cli_posix_open_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_posix_open_state *state = tevent_req_data(
+ req, struct cli_posix_open_state);
+ NTSTATUS status;
+
+ status = cli_posix_open_internal_recv(subreq, &state->fnum);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_open_recv(struct tevent_req *req, uint16_t *pfnum)
+{
+ struct cli_posix_open_state *state = tevent_req_data(
+ req, struct cli_posix_open_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfnum = state->fnum;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Open - POSIX semantics. Doesn't request oplock.
+****************************************************************************/
+
+NTSTATUS cli_posix_open(struct cli_state *cli, const char *fname,
+ int flags, mode_t mode, uint16_t *pfnum)
+{
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_posix_open_send(
+ frame, ev, cli, fname, flags, mode);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_posix_open_recv(req, pfnum);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_posix_mkdir_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+};
+
+static void cli_posix_mkdir_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_mkdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ mode_t mode)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_mkdir_state *state = NULL;
+ uint32_t wire_flags;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_posix_mkdir_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ wire_flags = SMB_O_CREAT | SMB_O_DIRECTORY;
+
+ subreq = cli_posix_open_internal_send(
+ mem_ctx, ev, cli, fname, wire_flags, mode);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_mkdir_done, req);
+ return req;
+}
+
+static void cli_posix_mkdir_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+ uint16_t fnum;
+
+ status = cli_posix_open_internal_recv(subreq, &fnum);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_posix_mkdir_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_mkdir(struct cli_state *cli, const char *fname, mode_t mode)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_posix_mkdir_send(
+ frame, ev, cli, fname, mode);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_posix_mkdir_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ unlink or rmdir - POSIX semantics.
+****************************************************************************/
+
+struct cli_posix_unlink_internal_state {
+ uint8_t data[2];
+};
+
+static void cli_posix_unlink_internal_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_posix_unlink_internal_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint16_t level)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_unlink_internal_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_posix_unlink_internal_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Setup data word. */
+ SSVAL(state->data, 0, level);
+
+ subreq = cli_setpathinfo_send(state, ev, cli,
+ SMB_POSIX_PATH_UNLINK,
+ fname,
+ state->data, sizeof(state->data));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_unlink_internal_done, req);
+ return req;
+}
+
+static void cli_posix_unlink_internal_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setpathinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS cli_posix_unlink_internal_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct cli_posix_unlink_state {
+ uint8_t dummy;
+};
+
+static void cli_posix_unlink_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_unlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_unlink_state *state;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_posix_unlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = cli_posix_unlink_internal_send(
+ mem_ctx, ev, cli, fname, SMB_POSIX_UNLINK_FILE_TARGET);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_unlink_done, req);
+ return req;
+}
+
+static void cli_posix_unlink_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_posix_unlink_internal_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_unlink_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************************
+ unlink - POSIX semantics.
+****************************************************************************/
+
+NTSTATUS cli_posix_unlink(struct cli_state *cli, const char *fname)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_unlink_send(frame,
+ ev,
+ cli,
+ fname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_unlink_recv(req);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ rmdir - POSIX semantics.
+****************************************************************************/
+
+struct cli_posix_rmdir_state {
+ uint8_t dummy;
+};
+
+static void cli_posix_rmdir_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_posix_rmdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_posix_rmdir_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_posix_rmdir_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = cli_posix_unlink_internal_send(
+ mem_ctx, ev, cli, fname, SMB_POSIX_UNLINK_DIRECTORY_TARGET);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_rmdir_done, req);
+ return req;
+}
+
+static void cli_posix_rmdir_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_posix_unlink_internal_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_posix_rmdir_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_posix_rmdir(struct cli_state *cli, const char *fname)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_rmdir_send(frame,
+ ev,
+ cli,
+ fname);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_rmdir_recv(req, frame);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ filechangenotify
+****************************************************************************/
+
+struct cli_notify_state {
+ struct tevent_req *subreq;
+ uint8_t setup[8];
+ uint32_t num_changes;
+ struct notify_change *changes;
+};
+
+static void cli_notify_done(struct tevent_req *subreq);
+static void cli_notify_done_smb2(struct tevent_req *subreq);
+static bool cli_notify_cancel(struct tevent_req *req);
+
+struct tevent_req *cli_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint32_t buffer_size,
+ uint32_t completion_filter, bool recursive)
+{
+ struct tevent_req *req;
+ struct cli_notify_state *state;
+ unsigned old_timeout;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_notify_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ /*
+ * Notifies should not time out
+ */
+ old_timeout = cli_set_timeout(cli, 0);
+
+ state->subreq = cli_smb2_notify_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ buffer_size,
+ completion_filter,
+ recursive);
+
+ cli_set_timeout(cli, old_timeout);
+
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->subreq, cli_notify_done_smb2, req);
+ goto done;
+ }
+
+ SIVAL(state->setup, 0, completion_filter);
+ SSVAL(state->setup, 4, fnum);
+ SSVAL(state->setup, 6, recursive);
+
+ /*
+ * Notifies should not time out
+ */
+ old_timeout = cli_set_timeout(cli, 0);
+
+ state->subreq = cli_trans_send(
+ state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ 0, /* additional_flags2 */
+ SMBnttrans, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ NT_TRANSACT_NOTIFY_CHANGE, /* function. */
+ 0, /* flags. */
+ (uint16_t *)state->setup, /* setup. */
+ 4, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ NULL, /* param. */
+ 0, /* num param. */
+ buffer_size, /* max returned param. */
+ NULL, /* data. */
+ 0, /* num data. */
+ 0); /* max returned data. */
+
+ cli_set_timeout(cli, old_timeout);
+
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreq, cli_notify_done, req);
+done:
+ tevent_req_set_cancel_fn(req, cli_notify_cancel);
+ return req;
+}
+
+static bool cli_notify_cancel(struct tevent_req *req)
+{
+ struct cli_notify_state *state = tevent_req_data(
+ req, struct cli_notify_state);
+ bool ok;
+
+ ok = tevent_req_cancel(state->subreq);
+ return ok;
+}
+
+static void cli_notify_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_notify_state *state = tevent_req_data(
+ req, struct cli_notify_state);
+ NTSTATUS status;
+ uint8_t *params;
+ uint32_t i, ofs, num_params;
+ uint16_t flags2;
+
+ status = cli_trans_recv(subreq, talloc_tos(), &flags2, NULL, 0, NULL,
+ &params, 0, &num_params, NULL, 0, NULL);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("cli_trans_recv returned %s\n", nt_errstr(status)));
+ return;
+ }
+
+ state->num_changes = 0;
+ ofs = 0;
+
+ while (num_params - ofs > 12) {
+ uint32_t next = IVAL(params, ofs);
+ state->num_changes += 1;
+
+ if ((next == 0) || (ofs+next >= num_params)) {
+ break;
+ }
+ ofs += next;
+ }
+
+ state->changes = talloc_array(state, struct notify_change,
+ state->num_changes);
+ if (tevent_req_nomem(state->changes, req)) {
+ TALLOC_FREE(params);
+ return;
+ }
+
+ ofs = 0;
+
+ for (i=0; i<state->num_changes; i++) {
+ uint32_t next = IVAL(params, ofs);
+ uint32_t len = IVAL(params, ofs+8);
+ ssize_t ret;
+ char *name;
+
+ if (smb_buffer_oob(num_params, ofs + 12, len)) {
+ TALLOC_FREE(params);
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ state->changes[i].action = IVAL(params, ofs+4);
+ ret = pull_string_talloc(state->changes,
+ (char *)params,
+ flags2,
+ &name,
+ params+ofs+12,
+ len,
+ STR_TERMINATE|STR_UNICODE);
+ if (ret == -1) {
+ TALLOC_FREE(params);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ state->changes[i].name = name;
+ ofs += next;
+ }
+
+ TALLOC_FREE(params);
+ tevent_req_done(req);
+}
+
+static void cli_notify_done_smb2(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_notify_state *state = tevent_req_data(
+ req, struct cli_notify_state);
+ NTSTATUS status;
+
+ status = cli_smb2_notify_recv(
+ subreq,
+ state,
+ &state->changes,
+ &state->num_changes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_notify_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_changes,
+ struct notify_change **pchanges)
+{
+ struct cli_notify_state *state = tevent_req_data(
+ req, struct cli_notify_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *pnum_changes = state->num_changes;
+ *pchanges = talloc_move(mem_ctx, &state->changes);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_notify(struct cli_state *cli, uint16_t fnum, uint32_t buffer_size,
+ uint32_t completion_filter, bool recursive,
+ TALLOC_CTX *mem_ctx, uint32_t *pnum_changes,
+ struct notify_change **pchanges)
+{
+ TALLOC_CTX *frame;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_notify_send(ev, ev, cli, fnum, buffer_size,
+ completion_filter, recursive);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_notify_recv(req, mem_ctx, pnum_changes, pchanges);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_qpathinfo_state {
+ uint8_t *param;
+ uint8_t *data;
+ uint16_t setup[1];
+ uint32_t min_rdata;
+ uint8_t *rdata;
+ uint32_t num_rdata;
+};
+
+static void cli_qpathinfo_done(struct tevent_req *subreq);
+static void cli_qpathinfo_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_qpathinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, const char *fname,
+ uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_qpathinfo_state *state;
+ uint16_t additional_flags2 = 0;
+ char *fname_cp = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_qpathinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ uint16_t smb2_level = 0;
+
+ switch (level) {
+ case SMB_QUERY_FILE_ALT_NAME_INFO:
+ smb2_level = FSCC_FILE_ALTERNATE_NAME_INFORMATION;
+ break;
+ default:
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb2_qpathinfo_send(state,
+ ev,
+ cli,
+ fname,
+ smb2_level,
+ min_rdata,
+ max_rdata);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qpathinfo_done2, req);
+ return req;
+ }
+
+ state->min_rdata = min_rdata;
+ SSVAL(state->setup, 0, TRANSACT2_QPATHINFO);
+
+ state->param = talloc_zero_array(state, uint8_t, 6);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+ SSVAL(state->param, 0, level);
+ /*
+ * qpathinfo on a DFS share must use DFS names.
+ */
+ fname_cp = smb1_dfs_share_path(state, cli, fname);
+ if (tevent_req_nomem(fname_cp, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->param = trans2_bytes_push_str(state->param,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname_cp,
+ strlen(fname_cp)+1,
+ NULL);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(fname) &&
+ !INFO_LEVEL_IS_UNIX(level)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_trans_send(
+ state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ additional_flags2, /* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ 0, /* function. */
+ 0, /* flags. */
+ state->setup, /* setup. */
+ 1, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ state->param, /* param. */
+ talloc_get_size(state->param), /* num param. */
+ 2, /* max returned param. */
+ NULL, /* data. */
+ 0, /* num data. */
+ max_rdata); /* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qpathinfo_done, req);
+ return req;
+}
+
+static void cli_qpathinfo_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qpathinfo_state *state = tevent_req_data(
+ req, struct cli_qpathinfo_state);
+ NTSTATUS status;
+
+ status = cli_trans_recv(subreq, state, NULL, NULL, 0, NULL,
+ NULL, 0, NULL,
+ &state->rdata, state->min_rdata,
+ &state->num_rdata);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_qpathinfo_done2(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_qpathinfo_state *state =
+ tevent_req_data(req, struct cli_qpathinfo_state);
+ NTSTATUS status;
+
+ status = cli_smb2_qpathinfo_recv(subreq,
+ state,
+ &state->rdata,
+ &state->num_rdata);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_qpathinfo_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **rdata, uint32_t *num_rdata)
+{
+ struct cli_qpathinfo_state *state = tevent_req_data(
+ req, struct cli_qpathinfo_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (rdata != NULL) {
+ *rdata = talloc_move(mem_ctx, &state->rdata);
+ } else {
+ TALLOC_FREE(state->rdata);
+ }
+ if (num_rdata != NULL) {
+ *num_rdata = state->num_rdata;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_qpathinfo(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ const char *fname, uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata,
+ uint8_t **rdata, uint32_t *num_rdata)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_qpathinfo_send(frame, ev, cli, fname, level, min_rdata,
+ max_rdata);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_qpathinfo_recv(req, mem_ctx, rdata, num_rdata);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_qfileinfo_state {
+ uint16_t setup[1];
+ uint8_t param[4];
+ uint8_t *data;
+ uint16_t recv_flags2;
+ uint32_t min_rdata;
+ uint8_t *rdata;
+ uint32_t num_rdata;
+};
+
+static void cli_qfileinfo_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_qfileinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_qfileinfo_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_qfileinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->min_rdata = min_rdata;
+ SSVAL(state->param, 0, fnum);
+ SSVAL(state->param, 2, level);
+ SSVAL(state->setup, 0, TRANSACT2_QFILEINFO);
+
+ subreq = cli_trans_send(
+ state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ 0, /* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ 0, /* function. */
+ 0, /* flags. */
+ state->setup, /* setup. */
+ 1, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ state->param, /* param. */
+ sizeof(state->param), /* num param. */
+ 2, /* max returned param. */
+ NULL, /* data. */
+ 0, /* num data. */
+ max_rdata); /* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qfileinfo_done, req);
+ return req;
+}
+
+static void cli_qfileinfo_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qfileinfo_state *state = tevent_req_data(
+ req, struct cli_qfileinfo_state);
+ NTSTATUS status;
+
+ status = cli_trans_recv(subreq, state,
+ &state->recv_flags2,
+ NULL, 0, NULL,
+ NULL, 0, NULL,
+ &state->rdata, state->min_rdata,
+ &state->num_rdata);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_qfileinfo_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint16_t *recv_flags2,
+ uint8_t **rdata, uint32_t *num_rdata)
+{
+ struct cli_qfileinfo_state *state = tevent_req_data(
+ req, struct cli_qfileinfo_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (recv_flags2 != NULL) {
+ *recv_flags2 = state->recv_flags2;
+ }
+ if (rdata != NULL) {
+ *rdata = talloc_move(mem_ctx, &state->rdata);
+ }
+ if (num_rdata != NULL) {
+ *num_rdata = state->num_rdata;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_qfileinfo(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint16_t fnum, uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata, uint16_t *recv_flags2,
+ uint8_t **rdata, uint32_t *num_rdata)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_qfileinfo_send(frame, ev, cli, fnum, level, min_rdata,
+ max_rdata);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_qfileinfo_recv(req, mem_ctx, recv_flags2, rdata, num_rdata);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_flush_state {
+ uint16_t vwv[1];
+};
+
+static void cli_flush_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_flush_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_flush_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_flush_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ SSVAL(state->vwv + 0, 0, fnum);
+
+ subreq = cli_smb_send(state, ev, cli, SMBflush, 0, 0, 1, state->vwv,
+ 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_flush_done, req);
+ return req;
+}
+
+static void cli_flush_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_flush_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_flush(TALLOC_CTX *mem_ctx, struct cli_state *cli, uint16_t fnum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_flush_send(frame, ev, cli, fnum);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_flush_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_shadow_copy_data_state {
+ uint16_t setup[4];
+ uint8_t *data;
+ uint32_t num_data;
+ bool get_names;
+};
+
+static void cli_shadow_copy_data_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_shadow_copy_data_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool get_names)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_shadow_copy_data_state *state;
+ uint32_t ret_size;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_shadow_copy_data_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->get_names = get_names;
+ ret_size = get_names ? CLI_BUFFER_SIZE : 16;
+
+ SIVAL(state->setup + 0, 0, FSCTL_GET_SHADOW_COPY_DATA);
+ SSVAL(state->setup + 2, 0, fnum);
+ SCVAL(state->setup + 3, 0, 1); /* isFsctl */
+ SCVAL(state->setup + 3, 1, 0); /* compfilter, isFlags (WSSP) */
+
+ subreq = cli_trans_send(
+ state, ev, cli, 0, SMBnttrans, NULL, 0, NT_TRANSACT_IOCTL, 0,
+ state->setup, ARRAY_SIZE(state->setup),
+ ARRAY_SIZE(state->setup),
+ NULL, 0, 0,
+ NULL, 0, ret_size);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_shadow_copy_data_done, req);
+ return req;
+}
+
+static void cli_shadow_copy_data_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_shadow_copy_data_state *state = tevent_req_data(
+ req, struct cli_shadow_copy_data_state);
+ NTSTATUS status;
+
+ status = cli_trans_recv(subreq, state, NULL,
+ NULL, 0, NULL, /* setup */
+ NULL, 0, NULL, /* param */
+ &state->data, 12, &state->num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_shadow_copy_data_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char ***pnames, int *pnum_names)
+{
+ struct cli_shadow_copy_data_state *state = tevent_req_data(
+ req, struct cli_shadow_copy_data_state);
+ char **names = NULL;
+ uint32_t i, num_names;
+ uint32_t dlength;
+ uint8_t *endp = NULL;
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (state->num_data < 16) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ num_names = IVAL(state->data, 4);
+ dlength = IVAL(state->data, 8);
+
+ if (num_names > 0x7FFFFFFF) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ if (!state->get_names) {
+ *pnum_names = (int)num_names;
+ return NT_STATUS_OK;
+ }
+
+ if (dlength + 12 < 12) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (dlength + 12 > state->num_data) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (state->num_data + (2 * sizeof(SHADOW_COPY_LABEL)) <
+ state->num_data) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ names = talloc_array(mem_ctx, char *, num_names);
+ if (names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ endp = state->data + state->num_data;
+
+ for (i=0; i<num_names; i++) {
+ bool ret;
+ uint8_t *src;
+ size_t converted_size;
+
+ src = state->data + 12 + i * 2 * sizeof(SHADOW_COPY_LABEL);
+
+ if (src + (2 * sizeof(SHADOW_COPY_LABEL)) > endp) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ ret = convert_string_talloc(
+ names, CH_UTF16LE, CH_UNIX,
+ src, 2 * sizeof(SHADOW_COPY_LABEL),
+ &names[i], &converted_size);
+ if (!ret) {
+ TALLOC_FREE(names);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ }
+ *pnum_names = (int)num_names;
+ *pnames = names;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_shadow_copy_data(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint16_t fnum, bool get_names,
+ char ***pnames, int *pnum_names)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_shadow_copy_data(mem_ctx,
+ cli,
+ fnum,
+ get_names,
+ pnames,
+ pnum_names);
+ }
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_shadow_copy_data_send(frame, ev, cli, fnum, get_names);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_shadow_copy_data_recv(req, mem_ctx, pnames, pnum_names);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_fsctl_state {
+ DATA_BLOB out;
+};
+
+static void cli_fsctl_smb1_done(struct tevent_req *subreq);
+static void cli_fsctl_smb2_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_fsctl_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t ctl_code,
+ const DATA_BLOB *in,
+ uint32_t max_out)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_fsctl_state *state = NULL;
+ uint16_t *setup = NULL;
+ uint8_t *data = NULL;
+ uint32_t num_data = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_fsctl_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_fsctl_send(
+ state, ev, cli, fnum, ctl_code, in, max_out);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_fsctl_smb2_done, req);
+ return req;
+ }
+
+ setup = talloc_array(state, uint16_t, 4);
+ if (tevent_req_nomem(setup, req)) {
+ return tevent_req_post(req, ev);
+ }
+ SIVAL(setup, 0, ctl_code);
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 1); /* IsFcntl */
+ SCVAL(setup, 7, 0); /* IsFlags */
+
+ if (in) {
+ data = in->data;
+ num_data = in->length;
+ }
+
+ subreq = cli_trans_send(state,
+ ev,
+ cli,
+ 0, /* additional_flags2 */
+ SMBnttrans, /* cmd */
+ NULL, /* name */
+ -1, /* fid */
+ NT_TRANSACT_IOCTL, /* function */
+ 0, /* flags */
+ setup,
+ 4,
+ 0, /* setup */
+ NULL,
+ 0,
+ 0, /* param */
+ data,
+ num_data,
+ max_out); /* data */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_fsctl_smb1_done, req);
+ return req;
+}
+
+static void cli_fsctl_smb2_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_fsctl_state *state = tevent_req_data(
+ req, struct cli_fsctl_state);
+ NTSTATUS status;
+
+ status = cli_smb2_fsctl_recv(subreq, state, &state->out);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_fsctl_smb1_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_fsctl_state *state = tevent_req_data(
+ req, struct cli_fsctl_state);
+ uint8_t *out = NULL;
+ uint32_t out_len;
+ NTSTATUS status;
+
+ status = cli_trans_recv(
+ subreq, state, NULL,
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL, /* rparam */
+ &out, 0, &out_len);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->out = (DATA_BLOB) {
+ .data = out, .length = out_len,
+ };
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_fsctl_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *out)
+{
+ struct cli_fsctl_state *state = tevent_req_data(
+ req, struct cli_fsctl_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (out != NULL) {
+ *out = (DATA_BLOB) {
+ .data = talloc_move(mem_ctx, &state->out.data),
+ .length = state->out.length,
+ };
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/clifsinfo.c b/source3/libsmb/clifsinfo.c
new file mode 100644
index 0000000..cc95f47
--- /dev/null
+++ b/source3/libsmb/clifsinfo.c
@@ -0,0 +1,815 @@
+/*
+ Unix SMB/CIFS implementation.
+ FS info functions
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Jeremy Allison 2007
+ Copyright (C) Andrew Bartlett 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "trans2.h"
+#include "auth_generic.h"
+#include "auth/gensec/gensec.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "auth/credentials/credentials.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+
+/****************************************************************************
+ Get UNIX extensions version info.
+****************************************************************************/
+
+struct cli_unix_extensions_version_state {
+ struct cli_state *cli;
+ uint16_t setup[1];
+ uint8_t param[2];
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+};
+
+static void cli_unix_extensions_version_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_unix_extensions_version_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_unix_extensions_version_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_unix_extensions_version_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ SSVAL(state->setup, 0, TRANSACT2_QFSINFO);
+ SSVAL(state->param, 0, SMB_QUERY_CIFS_UNIX_INFO);
+
+ subreq = cli_trans_send(state, ev, cli, 0, SMBtrans2,
+ NULL, 0, 0, 0,
+ state->setup, 1, 0,
+ state->param, 2, 0,
+ NULL, 0, 560);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_unix_extensions_version_done, req);
+ return req;
+}
+
+static void cli_unix_extensions_version_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_unix_extensions_version_state *state = tevent_req_data(
+ req, struct cli_unix_extensions_version_state);
+ uint8_t *data;
+ uint32_t num_data;
+ NTSTATUS status;
+
+ status = cli_trans_recv(subreq, state, NULL, NULL, 0, NULL,
+ NULL, 0, NULL, &data, 12, &num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->major = SVAL(data, 0);
+ state->minor = SVAL(data, 2);
+ state->caplow = IVAL(data, 4);
+ state->caphigh = IVAL(data, 8);
+ TALLOC_FREE(data);
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_unix_extensions_version_recv(struct tevent_req *req,
+ uint16_t *pmajor, uint16_t *pminor,
+ uint32_t *pcaplow,
+ uint32_t *pcaphigh)
+{
+ struct cli_unix_extensions_version_state *state = tevent_req_data(
+ req, struct cli_unix_extensions_version_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pmajor = state->major;
+ *pminor = state->minor;
+ *pcaplow = state->caplow;
+ *pcaphigh = state->caphigh;
+ state->cli->server_posix_capabilities = *pcaplow;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_unix_extensions_version(struct cli_state *cli, uint16_t *pmajor,
+ uint16_t *pminor, uint32_t *pcaplow,
+ uint32_t *pcaphigh)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_unix_extensions_version_send(frame, ev, cli);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_unix_extensions_version_recv(req, pmajor, pminor, pcaplow,
+ pcaphigh);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Set UNIX extensions capabilities.
+****************************************************************************/
+
+struct cli_set_unix_extensions_capabilities_state {
+ struct cli_state *cli;
+ uint16_t setup[1];
+ uint8_t param[4];
+ uint8_t data[12];
+};
+
+static void cli_set_unix_extensions_capabilities_done(
+ struct tevent_req *subreq);
+
+struct tevent_req *cli_set_unix_extensions_capabilities_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ uint16_t major, uint16_t minor, uint32_t caplow, uint32_t caphigh)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_set_unix_extensions_capabilities_state *state;
+
+ req = tevent_req_create(
+ mem_ctx, &state,
+ struct cli_set_unix_extensions_capabilities_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->cli = cli;
+ SSVAL(state->setup+0, 0, TRANSACT2_SETFSINFO);
+
+ SSVAL(state->param, 0, 0);
+ SSVAL(state->param, 2, SMB_SET_CIFS_UNIX_INFO);
+
+ SSVAL(state->data, 0, major);
+ SSVAL(state->data, 2, minor);
+ SIVAL(state->data, 4, caplow);
+ SIVAL(state->data, 8, caphigh);
+
+ subreq = cli_trans_send(state, ev, cli, 0, SMBtrans2,
+ NULL, 0, 0, 0,
+ state->setup, 1, 0,
+ state->param, 4, 0,
+ state->data, 12, 560);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_set_unix_extensions_capabilities_done, req);
+ return req;
+}
+
+static void cli_set_unix_extensions_capabilities_done(
+ struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_set_unix_extensions_capabilities_state *state = tevent_req_data(
+ req, struct cli_set_unix_extensions_capabilities_state);
+
+ NTSTATUS status = cli_trans_recv(subreq, NULL, NULL, NULL, 0, NULL,
+ NULL, 0, NULL, NULL, 0, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ state->cli->requested_posix_capabilities = IVAL(state->data, 4);
+ }
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_set_unix_extensions_capabilities_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_set_unix_extensions_capabilities(struct cli_state *cli,
+ uint16_t major, uint16_t minor,
+ uint32_t caplow, uint32_t caphigh)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_set_unix_extensions_capabilities_send(
+ ev, ev, cli, major, minor, caplow, caphigh);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_set_unix_extensions_capabilities_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+struct cli_get_fs_attr_info_state {
+ uint16_t setup[1];
+ uint8_t param[2];
+ uint32_t fs_attr;
+};
+
+static void cli_get_fs_attr_info_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_get_fs_attr_info_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *subreq, *req;
+ struct cli_get_fs_attr_info_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_get_fs_attr_info_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ SSVAL(state->setup+0, 0, TRANSACT2_QFSINFO);
+ SSVAL(state->param+0, 0, SMB_QUERY_FS_ATTRIBUTE_INFO);
+
+ subreq = cli_trans_send(state, ev, cli, 0, SMBtrans2,
+ NULL, 0, 0, 0,
+ state->setup, 1, 0,
+ state->param, 2, 0,
+ NULL, 0, 560);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_get_fs_attr_info_done, req);
+ return req;
+}
+
+static void cli_get_fs_attr_info_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_get_fs_attr_info_state *state = tevent_req_data(
+ req, struct cli_get_fs_attr_info_state);
+ uint8_t *data;
+ uint32_t num_data;
+ NTSTATUS status;
+
+ status = cli_trans_recv(subreq, talloc_tos(), NULL, NULL, 0, NULL,
+ NULL, 0, NULL, &data, 12, &num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->fs_attr = IVAL(data, 0);
+ TALLOC_FREE(data);
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_get_fs_attr_info_recv(struct tevent_req *req, uint32_t *fs_attr)
+{
+ struct cli_get_fs_attr_info_state *state = tevent_req_data(
+ req, struct cli_get_fs_attr_info_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *fs_attr = state->fs_attr;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_get_fs_attr_info(struct cli_state *cli, uint32_t *fs_attr)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_get_fs_attr_info(cli, fs_attr);
+ }
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_get_fs_attr_info_send(ev, ev, cli);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_get_fs_attr_info_recv(req, fs_attr);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+NTSTATUS cli_get_fs_volume_info(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ char **_volume_name,
+ uint32_t *pserial_number,
+ time_t *pdate)
+{
+ NTSTATUS status;
+ uint16_t recv_flags2;
+ uint16_t setup[1];
+ uint8_t param[2];
+ uint8_t *rdata;
+ uint32_t rdata_count;
+ unsigned int nlen;
+ char *volume_name = NULL;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_get_fs_volume_info(cli,
+ mem_ctx,
+ _volume_name,
+ pserial_number,
+ pdate);
+ }
+
+ SSVAL(setup, 0, TRANSACT2_QFSINFO);
+ SSVAL(param,0,SMB_QUERY_FS_VOLUME_INFO);
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2,
+ NULL, 0, 0, 0,
+ setup, 1, 0,
+ param, 2, 0,
+ NULL, 0, 560,
+ &recv_flags2,
+ NULL, 0, NULL,
+ NULL, 0, NULL,
+ &rdata, 18, &rdata_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (pdate) {
+ struct timespec ts;
+ ts = interpret_long_date(BVAL(rdata, 0));
+ *pdate = ts.tv_sec;
+ }
+ if (pserial_number) {
+ *pserial_number = IVAL(rdata,8);
+ }
+ nlen = IVAL(rdata,12);
+ if (nlen > (rdata_count - 18)) {
+ TALLOC_FREE(rdata);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ pull_string_talloc(mem_ctx,
+ (const char *)rdata,
+ recv_flags2,
+ &volume_name,
+ rdata + 18,
+ nlen, STR_UNICODE);
+ if (volume_name == NULL) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(rdata);
+ return status;
+ }
+
+ /* todo: but not yet needed
+ * return the other stuff
+ */
+
+ *_volume_name = volume_name;
+ TALLOC_FREE(rdata);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_get_fs_full_size_info(struct cli_state *cli,
+ uint64_t *total_allocation_units,
+ uint64_t *caller_allocation_units,
+ uint64_t *actual_allocation_units,
+ uint64_t *sectors_per_allocation_unit,
+ uint64_t *bytes_per_sector)
+{
+ uint16_t setup[1];
+ uint8_t param[2];
+ uint8_t *rdata = NULL;
+ uint32_t rdata_count;
+ NTSTATUS status;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_get_fs_full_size_info(cli,
+ total_allocation_units,
+ caller_allocation_units,
+ actual_allocation_units,
+ sectors_per_allocation_unit,
+ bytes_per_sector);
+ }
+
+ SSVAL(setup, 0, TRANSACT2_QFSINFO);
+ SSVAL(param, 0, SMB_FS_FULL_SIZE_INFORMATION);
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2,
+ NULL, 0, 0, 0,
+ setup, 1, 0, /* setup */
+ param, 2, 0, /* param */
+ NULL, 0, 560, /* data */
+ NULL,
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL, /* rparam */
+ &rdata, 32, &rdata_count); /* rdata */
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (total_allocation_units) {
+ *total_allocation_units = BIG_UINT(rdata, 0);
+ }
+ if (caller_allocation_units) {
+ *caller_allocation_units = BIG_UINT(rdata,8);
+ }
+ if (actual_allocation_units) {
+ *actual_allocation_units = BIG_UINT(rdata,16);
+ }
+ if (sectors_per_allocation_unit) {
+ *sectors_per_allocation_unit = IVAL(rdata,24);
+ }
+ if (bytes_per_sector) {
+ *bytes_per_sector = IVAL(rdata,28);
+ }
+
+fail:
+ TALLOC_FREE(rdata);
+ return status;
+}
+
+NTSTATUS cli_get_posix_fs_info(struct cli_state *cli,
+ uint32_t *optimal_transfer_size,
+ uint32_t *block_size,
+ uint64_t *total_blocks,
+ uint64_t *blocks_available,
+ uint64_t *user_blocks_available,
+ uint64_t *total_file_nodes,
+ uint64_t *free_file_nodes,
+ uint64_t *fs_identifier)
+{
+ uint16_t setup[1];
+ uint8_t param[2];
+ uint8_t *rdata = NULL;
+ uint32_t rdata_count;
+ NTSTATUS status;
+
+ SSVAL(setup, 0, TRANSACT2_QFSINFO);
+ SSVAL(param,0,SMB_QUERY_POSIX_FS_INFO);
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2, NULL, 0, 0, 0,
+ setup, 1, 0,
+ param, 2, 0,
+ NULL, 0, 560,
+ NULL,
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL, /* rparam */
+ &rdata, 56, &rdata_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (optimal_transfer_size) {
+ *optimal_transfer_size = IVAL(rdata, 0);
+ }
+ if (block_size) {
+ *block_size = IVAL(rdata,4);
+ }
+ if (total_blocks) {
+ *total_blocks = BIG_UINT(rdata,8);
+ }
+ if (blocks_available) {
+ *blocks_available = BIG_UINT(rdata,16);
+ }
+ if (user_blocks_available) {
+ *user_blocks_available = BIG_UINT(rdata,24);
+ }
+ if (total_file_nodes) {
+ *total_file_nodes = BIG_UINT(rdata,32);
+ }
+ if (free_file_nodes) {
+ *free_file_nodes = BIG_UINT(rdata,40);
+ }
+ if (fs_identifier) {
+ *fs_identifier = BIG_UINT(rdata,48);
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Do a UNIX extensions SMB_QUERY_POSIX_WHOAMI call.
+****************************************************************************/
+
+struct posix_whoami_state {
+ uint16_t setup[1];
+ uint8_t param[2];
+ uint32_t max_rdata;
+ bool guest;
+ uint64_t uid;
+ uint64_t gid;
+ uint32_t num_gids;
+ uint64_t *gids;
+ uint32_t num_sids;
+ struct dom_sid *sids;
+};
+
+static void cli_posix_whoami_done(struct tevent_req *subreq);
+
+static const uint32_t posix_whoami_max_rdata = 62*1024;
+
+struct tevent_req *cli_posix_whoami_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct posix_whoami_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct posix_whoami_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Setup setup word. */
+ SSVAL(state->setup, 0, TRANSACT2_QFSINFO);
+ SSVAL(state->param, 0, SMB_QUERY_POSIX_WHOAMI);
+
+ state->max_rdata = posix_whoami_max_rdata;
+
+ subreq = cli_trans_send(state, /* mem ctx. */
+ ev, /* event ctx. */
+ cli, /* cli_state. */
+ 0, /* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL, /* pipe name. */
+ -1, /* fid. */
+ 0, /* function. */
+ 0, /* flags. */
+ state->setup, /* setup. */
+ 1, /* num setup uint16_t words. */
+ 0, /* max returned setup. */
+ state->param, /* param. */
+ 2, /* num param. */
+ 0, /* max returned param. */
+ NULL, /* data. */
+ 0, /* num data. */
+ state->max_rdata); /* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_posix_whoami_done, req);
+ return req;
+}
+
+static void cli_posix_whoami_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct posix_whoami_state *state = tevent_req_data(
+ req, struct posix_whoami_state);
+ uint8_t *rdata = NULL;
+ uint8_t *p = NULL;
+ uint32_t num_rdata = 0;
+ uint32_t i;
+ NTSTATUS status;
+
+ status = cli_trans_recv(subreq,
+ state,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ &rdata,
+ 40,
+ &num_rdata);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * Not strictly needed - cli_trans_recv()
+ * will ensure at least 40 bytes here. Added
+ * as more of a reminder to be careful when
+ * parsing network packets in C.
+ */
+
+ if (num_rdata < 40 || num_rdata > posix_whoami_max_rdata) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ state->guest = (IVAL(rdata, 0) & SMB_WHOAMI_GUEST);
+ state->uid = BVAL(rdata, 8);
+ state->gid = BVAL(rdata, 16);
+ state->num_gids = IVAL(rdata, 24);
+ state->num_sids = IVAL(rdata, 28);
+
+ /* Ensure the gid array doesn't overflow */
+ if (state->num_gids > (num_rdata - 40) / sizeof(uint64_t)) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ state->gids = talloc_array(state, uint64_t, state->num_gids);
+ if (tevent_req_nomem(state->gids, req)) {
+ return;
+ }
+ state->sids = talloc_array(state, struct dom_sid, state->num_sids);
+ if (tevent_req_nomem(state->sids, req)) {
+ return;
+ }
+
+ p = rdata + 40;
+
+ for (i = 0; i < state->num_gids; i++) {
+ state->gids[i] = BVAL(p, 0);
+ p += 8;
+ }
+
+ num_rdata -= (p - rdata);
+
+ for (i = 0; i < state->num_sids; i++) {
+ size_t sid_size;
+ DATA_BLOB in = data_blob_const(p, num_rdata);
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_pull_struct_blob(&in,
+ state,
+ &state->sids[i],
+ (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ sid_size = ndr_size_dom_sid(&state->sids[i], 0);
+
+ if (sid_size > num_rdata) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ p += sid_size;
+ num_rdata -= sid_size;
+ }
+
+ if (num_rdata != 0) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_posix_whoami_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint64_t *puid,
+ uint64_t *pgid,
+ uint32_t *pnum_gids,
+ uint64_t **pgids,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids,
+ bool *pguest)
+{
+ NTSTATUS status;
+ struct posix_whoami_state *state = tevent_req_data(
+ req, struct posix_whoami_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (puid) {
+ *puid = state->uid;
+ }
+ if (pgid) {
+ *pgid = state->gid;
+ }
+ if (pnum_gids) {
+ *pnum_gids = state->num_gids;
+ }
+ if (pgids) {
+ *pgids = talloc_move(mem_ctx, &state->gids);
+ }
+ if (pnum_sids) {
+ *pnum_sids = state->num_sids;
+ }
+ if (psids) {
+ *psids = talloc_move(mem_ctx, &state->sids);
+ }
+ if (pguest) {
+ *pguest = state->guest;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_posix_whoami(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ uint64_t *puid,
+ uint64_t *pgid,
+ uint32_t *num_gids,
+ uint64_t **gids,
+ uint32_t *num_sids,
+ struct dom_sid **sids,
+ bool *pguest)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_posix_whoami_send(frame,
+ ev,
+ cli);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_posix_whoami_recv(req,
+ mem_ctx,
+ puid,
+ pgid,
+ num_gids,
+ gids,
+ num_sids,
+ sids,
+ pguest);
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/clilist.c b/source3/libsmb/clilist.c
new file mode 100644
index 0000000..54b46b0
--- /dev/null
+++ b/source3/libsmb/clilist.c
@@ -0,0 +1,1255 @@
+/*
+ Unix SMB/CIFS implementation.
+ client directory list routines
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "trans2.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/****************************************************************************
+ Check if a returned directory name is safe.
+****************************************************************************/
+
+static NTSTATUS is_bad_name(bool windows_names, const char *name)
+{
+ const char *bad_name_p = NULL;
+
+ bad_name_p = strchr(name, '/');
+ if (bad_name_p != NULL) {
+ /*
+ * Windows and POSIX names can't have '/'.
+ * Server is attacking us.
+ */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (windows_names) {
+ bad_name_p = strchr(name, '\\');
+ if (bad_name_p != NULL) {
+ /*
+ * Windows names can't have '\\'.
+ * Server is attacking us.
+ */
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check if a returned directory name is safe. Disconnect if server is
+ sending bad names.
+****************************************************************************/
+
+NTSTATUS is_bad_finfo_name(const struct cli_state *cli,
+ const struct file_info *finfo)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ bool windows_names = true;
+
+ if (cli->requested_posix_capabilities & CIFS_UNIX_POSIX_PATHNAMES_CAP) {
+ windows_names = false;
+ }
+ if (finfo->name != NULL) {
+ status = is_bad_name(windows_names, finfo->name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("bad finfo->name\n");
+ return status;
+ }
+ }
+ if (finfo->short_name != NULL) {
+ status = is_bad_name(windows_names, finfo->short_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("bad finfo->short_name\n");
+ return status;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Calculate a safe next_entry_offset.
+****************************************************************************/
+
+static size_t calc_next_entry_offset(const char *base, const char *pdata_end)
+{
+ size_t next_entry_offset = (size_t)IVAL(base,0);
+
+ if (next_entry_offset == 0 ||
+ base + next_entry_offset < base ||
+ base + next_entry_offset > pdata_end) {
+ next_entry_offset = pdata_end - base;
+ }
+ return next_entry_offset;
+}
+
+/****************************************************************************
+ Interpret a long filename structure - this is mostly guesses at the moment.
+ The length of the structure is returned
+ The structure of a long filename depends on the info level.
+ SMB_FIND_FILE_BOTH_DIRECTORY_INFO is used
+ by NT and SMB_FIND_EA_SIZE is used by OS/2
+****************************************************************************/
+
+static size_t interpret_long_filename(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ int level,
+ const char *base_ptr,
+ uint16_t recv_flags2,
+ const char *p,
+ const char *pdata_end,
+ struct file_info *finfo,
+ uint32_t *p_resume_key,
+ DATA_BLOB *p_last_name_raw)
+{
+ int len;
+ size_t ret;
+ const char *base = p;
+
+ data_blob_free(p_last_name_raw);
+
+ if (p_resume_key) {
+ *p_resume_key = 0;
+ }
+ ZERO_STRUCTP(finfo);
+
+ switch (level) {
+ case SMB_FIND_INFO_STANDARD: /* OS/2 understands this */
+ /* these dates are converted to GMT by
+ make_unix_date */
+ if (pdata_end - base < 27) {
+ return pdata_end - base;
+ }
+ /*
+ * What we're returning here as ctime_ts is
+ * actually the server create time.
+ */
+ finfo->btime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+4,
+ smb1cli_conn_server_time_zone(
+ cli->conn)));
+ finfo->ctime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+4, smb1cli_conn_server_time_zone(cli->conn)));
+ finfo->atime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+8, smb1cli_conn_server_time_zone(cli->conn)));
+ finfo->mtime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+12, smb1cli_conn_server_time_zone(cli->conn)));
+ finfo->size = IVAL(p,16);
+ finfo->attr = SVAL(p,24);
+ len = CVAL(p, 26);
+ p += 27;
+ if (recv_flags2 & FLAGS2_UNICODE_STRINGS) {
+ p += ucs2_align(base_ptr, p, STR_UNICODE);
+ }
+
+ /* We can safely use len here (which is required by OS/2)
+ * and the NAS-BASIC server instead of +2 or +1 as the
+ * STR_TERMINATE flag below is
+ * actually used as the length calculation.
+ * The len is merely an upper bound.
+ * Due to the explicit 2 byte null termination
+ * in cli_receive_trans/cli_receive_nt_trans
+ * we know this is safe. JRA + kukks
+ */
+
+ if (p + len > pdata_end) {
+ return pdata_end - base;
+ }
+
+ /* the len+2 below looks strange but it is
+ important to cope with the differences
+ between win2000 and win9x for this call
+ (tridge) */
+ ret = pull_string_talloc(ctx,
+ base_ptr,
+ recv_flags2,
+ &finfo->name,
+ p,
+ len+2,
+ STR_TERMINATE);
+ if (ret == (size_t)-1) {
+ return pdata_end - base;
+ }
+ p += ret;
+ return PTR_DIFF(p, base);
+
+ case SMB_FIND_EA_SIZE: /* this is what OS/2 uses mostly */
+ /* these dates are converted to GMT by
+ make_unix_date */
+ if (pdata_end - base < 31) {
+ return pdata_end - base;
+ }
+ /*
+ * What we're returning here as ctime_ts is
+ * actually the server create time.
+ */
+ finfo->btime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+4,
+ smb1cli_conn_server_time_zone(
+ cli->conn)));
+ finfo->ctime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+4, smb1cli_conn_server_time_zone(cli->conn)));
+ finfo->atime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+8, smb1cli_conn_server_time_zone(cli->conn)));
+ finfo->mtime_ts = convert_time_t_to_timespec(
+ make_unix_date2(p+12, smb1cli_conn_server_time_zone(cli->conn)));
+ finfo->size = IVAL(p,16);
+ finfo->attr = SVAL(p,24);
+ len = CVAL(p, 30);
+ p += 31;
+ /* check for unisys! */
+ if (p + len + 1 > pdata_end) {
+ return pdata_end - base;
+ }
+ ret = pull_string_talloc(ctx,
+ base_ptr,
+ recv_flags2,
+ &finfo->name,
+ p,
+ len,
+ STR_NOALIGN);
+ if (ret == (size_t)-1) {
+ return pdata_end - base;
+ }
+ p += ret;
+ return PTR_DIFF(p, base) + 1;
+
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO: /* NT uses this, but also accepts 2 */
+ {
+ size_t namelen, slen;
+
+ if (pdata_end - base < 94) {
+ return pdata_end - base;
+ }
+
+ p += 4; /* next entry offset */
+
+ if (p_resume_key) {
+ *p_resume_key = IVAL(p,0);
+ }
+ p += 4; /* fileindex */
+
+ /* Offset zero is "create time", not "change time". */
+ p += 8;
+ finfo->atime_ts = interpret_long_date(BVAL(p, 0));
+ p += 8;
+ finfo->mtime_ts = interpret_long_date(BVAL(p, 0));
+ p += 8;
+ finfo->ctime_ts = interpret_long_date(BVAL(p, 0));
+ p += 8;
+ finfo->size = IVAL2_TO_SMB_BIG_UINT(p,0);
+ p += 8;
+ p += 8; /* alloc size */
+ finfo->attr = IVAL(p,0);
+ p += 4;
+ namelen = IVAL(p,0);
+ p += 4;
+ p += 4; /* EA size */
+ slen = CVAL(p, 0);
+ if (slen > 24) {
+ /* Bad short name length. */
+ return pdata_end - base;
+ }
+ p += 2;
+ ret = pull_string_talloc(ctx,
+ base_ptr,
+ recv_flags2,
+ &finfo->short_name,
+ p,
+ slen,
+ STR_UNICODE);
+ if (ret == (size_t)-1) {
+ return pdata_end - base;
+ }
+ p += 24; /* short name? */
+ if (p + namelen < p || p + namelen > pdata_end) {
+ return pdata_end - base;
+ }
+ ret = pull_string_talloc(ctx,
+ base_ptr,
+ recv_flags2,
+ &finfo->name,
+ p,
+ namelen,
+ 0);
+ if (ret == (size_t)-1) {
+ return pdata_end - base;
+ }
+
+ /* To be robust in the face of unicode conversion failures
+ we need to copy the raw bytes of the last name seen here.
+ Namelen doesn't include the terminating unicode null, so
+ copy it here. */
+
+ if (p_last_name_raw) {
+ *p_last_name_raw = data_blob(NULL, namelen+2);
+ memcpy(p_last_name_raw->data, p, namelen);
+ SSVAL(p_last_name_raw->data, namelen, 0);
+ }
+ return calc_next_entry_offset(base, pdata_end);
+ }
+ }
+
+ DEBUG(1,("Unknown long filename format %d\n",level));
+ return calc_next_entry_offset(base, pdata_end);
+}
+
+/****************************************************************************
+ Interpret a short filename structure.
+ The length of the structure is returned.
+****************************************************************************/
+
+static bool interpret_short_filename(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ char *p,
+ struct file_info *finfo)
+{
+ size_t ret;
+ ZERO_STRUCTP(finfo);
+
+ finfo->attr = CVAL(p,21);
+
+ /* We don't get birth time. */
+ finfo->btime_ts.tv_sec = 0;
+ finfo->btime_ts.tv_nsec = 0;
+ /* this date is converted to GMT by make_unix_date */
+ finfo->ctime_ts.tv_sec = make_unix_date(p+22, smb1cli_conn_server_time_zone(cli->conn));
+ finfo->ctime_ts.tv_nsec = 0;
+ finfo->mtime_ts.tv_sec = finfo->atime_ts.tv_sec = finfo->ctime_ts.tv_sec;
+ finfo->mtime_ts.tv_nsec = finfo->atime_ts.tv_nsec = 0;
+ finfo->size = IVAL(p,26);
+ ret = pull_string_talloc(ctx,
+ NULL,
+ 0,
+ &finfo->name,
+ p+30,
+ 12,
+ STR_ASCII);
+ if (ret == (size_t)-1) {
+ return false;
+ }
+
+ if (finfo->name) {
+ finfo->short_name = talloc_strdup(ctx, finfo->name);
+ if (finfo->short_name == NULL) {
+ return false;
+ }
+ }
+ return true;
+}
+
+struct cli_list_old_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t vwv[2];
+ char *mask;
+ int num_asked;
+ uint32_t attribute;
+ uint8_t search_status[23];
+ bool first;
+ bool done;
+ uint8_t *dirlist;
+};
+
+static void cli_list_old_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_list_old_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *mask,
+ uint32_t attribute)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_list_old_state *state;
+ uint8_t *bytes;
+ static const uint16_t zero = 0;
+ uint32_t usable_space;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_list_old_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->attribute = attribute;
+ state->first = true;
+ state->mask = talloc_strdup(state, mask);
+ if (tevent_req_nomem(state->mask, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->mask = smb1_dfs_share_path(state, cli, state->mask);
+ if (tevent_req_nomem(state->mask, req)) {
+ return tevent_req_post(req, ev);
+ }
+ usable_space = cli_state_available_size(cli, 100);
+ state->num_asked = usable_space / DIR_STRUCT_SIZE;
+
+ SSVAL(state->vwv + 0, 0, state->num_asked);
+ SSVAL(state->vwv + 1, 0, state->attribute);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ state->mask,
+ strlen(state->mask)+1,
+ NULL);
+
+ bytes = smb_bytes_push_bytes(bytes, 5, (const uint8_t *)&zero, 2);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_send(state, state->ev, state->cli, SMBsearch, 0, 0,
+ 2, state->vwv, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_list_old_done, req);
+ return req;
+}
+
+static void cli_list_old_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_list_old_state *state = tevent_req_data(
+ req, struct cli_list_old_state);
+ NTSTATUS status;
+ uint8_t cmd;
+ uint8_t wct;
+ uint16_t *vwv;
+ uint32_t num_bytes;
+ uint8_t *bytes;
+ uint16_t received;
+ size_t dirlist_len;
+ uint8_t *tmp;
+
+ status = cli_smb_recv(subreq, state, NULL, 0, &wct, &vwv, &num_bytes,
+ &bytes);
+ if (!NT_STATUS_IS_OK(status)
+ && !NT_STATUS_EQUAL(status, NT_STATUS_DOS(ERRDOS, ERRnofiles))
+ && !NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) {
+ TALLOC_FREE(subreq);
+ tevent_req_nterror(req, status);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DOS(ERRDOS, ERRnofiles))
+ || NT_STATUS_EQUAL(status, STATUS_NO_MORE_FILES)) {
+ received = 0;
+ } else {
+ if (wct < 1) {
+ TALLOC_FREE(subreq);
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+ received = SVAL(vwv + 0, 0);
+ }
+
+ if (received > 0) {
+ /*
+ * I don't think this can wrap. received is
+ * initialized from a 16-bit value.
+ */
+ if (num_bytes < ((uint32_t)received * DIR_STRUCT_SIZE + 3)) {
+ TALLOC_FREE(subreq);
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ dirlist_len = talloc_get_size(state->dirlist);
+
+ tmp = talloc_realloc(
+ state, state->dirlist, uint8_t,
+ dirlist_len + received * DIR_STRUCT_SIZE);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ state->dirlist = tmp;
+ memcpy(state->dirlist + dirlist_len, bytes + 3,
+ received * DIR_STRUCT_SIZE);
+
+ SSVAL(state->search_status, 0, 21);
+ memcpy(state->search_status + 2,
+ bytes + 3 + (received-1)*DIR_STRUCT_SIZE, 21);
+ cmd = SMBsearch;
+ } else {
+ if (state->first || state->done) {
+ tevent_req_done(req);
+ return;
+ }
+ state->done = true;
+ state->num_asked = 0;
+ cmd = SMBfclose;
+ }
+ TALLOC_FREE(subreq);
+
+ state->first = false;
+
+ SSVAL(state->vwv + 0, 0, state->num_asked);
+ SSVAL(state->vwv + 1, 0, state->attribute);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes, smbXcli_conn_use_unicode(state->cli->conn), "",
+ 1, NULL);
+ bytes = smb_bytes_push_bytes(bytes, 5, state->search_status,
+ sizeof(state->search_status));
+ if (tevent_req_nomem(bytes, req)) {
+ return;
+ }
+ subreq = cli_smb_send(state, state->ev, state->cli, cmd, 0, 0,
+ 2, state->vwv, talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_list_old_done, req);
+}
+
+static NTSTATUS cli_list_old_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct file_info **pfinfo)
+{
+ struct cli_list_old_state *state = tevent_req_data(
+ req, struct cli_list_old_state);
+ NTSTATUS status;
+ size_t i, num_received;
+ struct file_info *finfo;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (state->dirlist == NULL) {
+ *pfinfo = NULL;
+ return NT_STATUS_OK;
+ }
+
+ num_received = talloc_array_length(state->dirlist) / DIR_STRUCT_SIZE;
+
+ finfo = talloc_array(mem_ctx, struct file_info, num_received);
+ if (finfo == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_received; i++) {
+ if (!interpret_short_filename(
+ finfo, state->cli,
+ (char *)state->dirlist + i * DIR_STRUCT_SIZE,
+ &finfo[i])) {
+ TALLOC_FREE(finfo);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (finfo->name == NULL) {
+ TALLOC_FREE(finfo);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ status = is_bad_finfo_name(state->cli, finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbXcli_conn_disconnect(state->cli->conn, status);
+ TALLOC_FREE(finfo);
+ return status;
+ }
+ }
+ TALLOC_FREE(state->dirlist);
+ *pfinfo = finfo;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_list_old(struct cli_state *cli, const char *mask,
+ uint32_t attribute,
+ NTSTATUS (*fn)(struct file_info *,
+ const char *, void *), void *state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ struct file_info *finfo = NULL;
+ size_t i, num_finfo;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_list_old_send(frame, ev, cli, mask, attribute);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_list_old_recv(req, frame, &finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ num_finfo = talloc_array_length(finfo);
+ for (i=0; i<num_finfo; i++) {
+ status = fn(&finfo[i], mask, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_list_trans_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ char *mask;
+ uint32_t attribute;
+ uint16_t info_level;
+
+ int loop_count;
+ int total_received;
+ uint16_t max_matches;
+ bool first;
+
+ int ff_eos;
+ int ff_dir_handle;
+
+ uint16_t setup[1];
+ uint8_t *param;
+
+ struct file_info *finfo;
+};
+
+static void cli_list_trans_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_list_trans_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *mask,
+ uint32_t attribute,
+ uint16_t info_level)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_list_trans_state *state;
+ size_t param_len;
+ uint16_t additional_flags2 = 0;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_list_trans_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->mask = talloc_strdup(state, mask);
+ if (tevent_req_nomem(state->mask, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->mask = smb1_dfs_share_path(state, cli, state->mask);
+ if (tevent_req_nomem(state->mask, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->attribute = attribute;
+ state->info_level = info_level;
+ state->loop_count = 0;
+ state->first = true;
+
+ state->max_matches = 1366; /* Match W2k */
+
+ SSVAL(&state->setup[0], 0, TRANSACT2_FINDFIRST);
+
+ state->param = talloc_array(state, uint8_t, 12);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ SSVAL(state->param, 0, state->attribute);
+ SSVAL(state->param, 2, state->max_matches);
+ SSVAL(state->param, 4,
+ FLAG_TRANS2_FIND_REQUIRE_RESUME
+ |FLAG_TRANS2_FIND_CLOSE_IF_END
+ |(cli->backup_intent ? FLAG_TRANS2_FIND_BACKUP_INTENT : 0));
+ SSVAL(state->param, 6, state->info_level);
+ SIVAL(state->param, 8, 0);
+
+ state->param = trans2_bytes_push_str(state->param, smbXcli_conn_use_unicode(cli->conn),
+ state->mask, strlen(state->mask)+1,
+ NULL);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (clistr_is_previous_version_path(state->mask)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ param_len = talloc_get_size(state->param);
+
+ subreq = cli_trans_send(state, state->ev, state->cli, additional_flags2,
+ SMBtrans2, NULL, -1, 0, 0,
+ state->setup, 1, 0,
+ state->param, param_len, 10,
+ NULL, 0, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_list_trans_done, req);
+ return req;
+}
+
+static void cli_list_trans_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_list_trans_state *state = tevent_req_data(
+ req, struct cli_list_trans_state);
+ NTSTATUS status;
+ uint8_t *param;
+ uint32_t num_param;
+ uint8_t *data;
+ char *data_end;
+ uint32_t num_data;
+ uint32_t min_param;
+ struct file_info *tmp;
+ size_t old_num_finfo;
+ uint16_t recv_flags2;
+ int ff_searchcount;
+ bool ff_eos;
+ char *p, *p2;
+ uint32_t resume_key = 0;
+ int i;
+ DATA_BLOB last_name_raw;
+ struct file_info *finfo = NULL;
+ size_t param_len;
+ uint16_t additional_flags2 = 0;
+
+ min_param = (state->first ? 6 : 4);
+
+ status = cli_trans_recv(subreq, talloc_tos(), &recv_flags2,
+ NULL, 0, NULL,
+ &param, min_param, &num_param,
+ &data, 0, &num_data);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * TODO: retry, OS/2 nofiles
+ */
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (state->first) {
+ state->ff_dir_handle = SVAL(param, 0);
+ ff_searchcount = SVAL(param, 2);
+ ff_eos = SVAL(param, 4) != 0;
+ } else {
+ ff_searchcount = SVAL(param, 0);
+ ff_eos = SVAL(param, 2) != 0;
+ }
+
+ old_num_finfo = talloc_array_length(state->finfo);
+
+ tmp = talloc_realloc(state, state->finfo, struct file_info,
+ old_num_finfo + ff_searchcount);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ state->finfo = tmp;
+
+ p2 = p = (char *)data;
+ data_end = (char *)data + num_data;
+ last_name_raw = data_blob_null;
+
+ for (i=0; i<ff_searchcount; i++) {
+ if (p2 >= data_end) {
+ ff_eos = true;
+ break;
+ }
+ if ((state->info_level == SMB_FIND_FILE_BOTH_DIRECTORY_INFO)
+ && (i == ff_searchcount-1)) {
+ /* Last entry - fixup the last offset length. */
+ SIVAL(p2, 0, PTR_DIFF((data + num_data), p2));
+ }
+
+ data_blob_free(&last_name_raw);
+
+ finfo = &state->finfo[old_num_finfo + i];
+
+ p2 += interpret_long_filename(
+ state->finfo, /* Stick fname to the array as such */
+ state->cli, state->info_level,
+ (char *)data, recv_flags2, p2,
+ data_end, finfo, &resume_key, &last_name_raw);
+
+ if (finfo->name == NULL) {
+ DEBUG(1, ("cli_list: Error: unable to parse name from "
+ "info level %d\n", state->info_level));
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ status = is_bad_finfo_name(state->cli, finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbXcli_conn_disconnect(state->cli->conn, status);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (!state->first && (state->mask[0] != '\0') &&
+ strcsequal(finfo->name, state->mask)) {
+ DEBUG(1, ("Error: Looping in FIND_NEXT as name %s has "
+ "already been seen?\n", finfo->name));
+ ff_eos = true;
+ break;
+ }
+ }
+
+ if (ff_searchcount == 0) {
+ ff_eos = true;
+ }
+
+ TALLOC_FREE(param);
+ TALLOC_FREE(data);
+
+ /*
+ * Shrink state->finfo to the real length we received
+ */
+ tmp = talloc_realloc(state, state->finfo, struct file_info,
+ old_num_finfo + i);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ state->finfo = tmp;
+
+ state->first = false;
+
+ if (ff_eos) {
+ data_blob_free(&last_name_raw);
+ tevent_req_done(req);
+ return;
+ }
+
+ TALLOC_FREE(state->mask);
+ state->mask = talloc_strdup(state, finfo->name);
+ if (tevent_req_nomem(state->mask, req)) {
+ return;
+ }
+
+ SSVAL(&state->setup[0], 0, TRANSACT2_FINDNEXT);
+
+ param = talloc_realloc(state, state->param, uint8_t, 12);
+ if (tevent_req_nomem(param, req)) {
+ return;
+ }
+ state->param = param;
+
+ SSVAL(param, 0, state->ff_dir_handle);
+ SSVAL(param, 2, state->max_matches); /* max count */
+ SSVAL(param, 4, state->info_level);
+ /*
+ * For W2K servers serving out FAT filesystems we *must* set
+ * the resume key. If it's not FAT then it's returned as zero.
+ */
+ SIVAL(param, 6, resume_key); /* ff_resume_key */
+ /*
+ * NB. *DON'T* use continue here. If you do it seems that W2K
+ * and brethren can miss filenames. Use last filename
+ * continue instead. JRA
+ */
+ SSVAL(param, 10, (FLAG_TRANS2_FIND_REQUIRE_RESUME
+ |FLAG_TRANS2_FIND_CLOSE_IF_END
+ |(state->cli->backup_intent ? FLAG_TRANS2_FIND_BACKUP_INTENT : 0)));
+ if (last_name_raw.length) {
+ state->param = trans2_bytes_push_bytes(state->param,
+ last_name_raw.data,
+ last_name_raw.length);
+ if (tevent_req_nomem(state->param, req)) {
+ return;
+ }
+ data_blob_free(&last_name_raw);
+ } else {
+ state->param = trans2_bytes_push_str(state->param,
+ smbXcli_conn_use_unicode(state->cli->conn),
+ state->mask,
+ strlen(state->mask)+1,
+ NULL);
+ if (tevent_req_nomem(state->param, req)) {
+ return;
+ }
+ }
+ param_len = talloc_get_size(state->param);
+
+ if (clistr_is_previous_version_path(state->mask)) {
+ additional_flags2 = FLAGS2_REPARSE_PATH;
+ }
+
+ subreq = cli_trans_send(state, state->ev, state->cli, additional_flags2,
+ SMBtrans2, NULL, -1, 0, 0,
+ state->setup, 1, 0,
+ state->param, param_len, 10,
+ NULL, 0, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_list_trans_done, req);
+}
+
+static NTSTATUS cli_list_trans_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct file_info **finfo)
+{
+ struct cli_list_trans_state *state = tevent_req_data(
+ req, struct cli_list_trans_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *finfo = talloc_move(mem_ctx, &state->finfo);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_list_trans(struct cli_state *cli, const char *mask,
+ uint32_t attribute, int info_level,
+ NTSTATUS (*fn)(
+ struct file_info *finfo,
+ const char *mask,
+ void *private_data),
+ void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ int i, num_finfo;
+ struct file_info *finfo = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_list_trans_send(frame, ev, cli, mask, attribute, info_level);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_list_trans_recv(req, frame, &finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ num_finfo = talloc_array_length(finfo);
+ for (i=0; i<num_finfo; i++) {
+ status = fn(&finfo[i], mask, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_list_state {
+ struct tevent_context *ev;
+ struct tevent_req *subreq;
+ NTSTATUS (*recv_fn)(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct file_info **finfo);
+ struct file_info *finfo;
+ size_t num_received;
+};
+
+static void cli_list_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *mask,
+ uint32_t attribute,
+ uint16_t info_level)
+{
+ struct tevent_req *req = NULL;
+ struct cli_list_state *state;
+ enum protocol_types proto = smbXcli_conn_protocol(cli->conn);
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_list_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ if (proto >= PROTOCOL_SMB2_02) {
+ state->subreq = cli_smb2_list_send(state, ev, cli, mask,
+ info_level);
+ state->recv_fn = cli_smb2_list_recv;
+ } else if (proto >= PROTOCOL_LANMAN2) {
+ state->subreq = cli_list_trans_send(
+ state, ev, cli, mask, attribute, info_level);
+ state->recv_fn = cli_list_trans_recv;
+ } else {
+ state->subreq = cli_list_old_send(
+ state, ev, cli, mask, attribute);
+ state->recv_fn = cli_list_old_recv;
+ }
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreq, cli_list_done, req);
+ return req;
+}
+
+static void cli_list_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_list_state *state = tevent_req_data(
+ req, struct cli_list_state);
+ NTSTATUS status;
+
+ SMB_ASSERT(subreq == state->subreq);
+
+ /*
+ * We don't want to be called by the lowerlevel routines
+ * from within state->recv_fn()
+ */
+ tevent_req_set_callback(subreq, NULL, NULL);
+
+ status = state->recv_fn(subreq, state, &state->finfo);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* We'll get back here */
+ tevent_req_set_callback(subreq, cli_list_done, req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_notify_callback(req);
+}
+
+NTSTATUS cli_list_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct file_info **pfinfo)
+{
+ struct cli_list_state *state = tevent_req_data(
+ req, struct cli_list_state);
+ size_t num_results;
+ struct file_info *finfo = NULL;
+ NTSTATUS status;
+ bool in_progress;
+
+ in_progress = tevent_req_is_in_progress(req);
+
+ if (!in_progress) {
+ if (!tevent_req_is_nterror(req, &status)) {
+ status = NT_STATUS_NO_MORE_FILES;
+ }
+ return status;
+ }
+
+ if (state->finfo == NULL) {
+ status = state->recv_fn(state->subreq, state, &state->finfo);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ tevent_req_set_callback(
+ state->subreq, cli_list_done, req);
+ return NT_STATUS_RETRY;
+ }
+
+ if (NT_STATUS_IS_OK(status) && (state->finfo == NULL)) {
+ status = NT_STATUS_NO_MORE_FILES;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return status;
+ }
+
+ state->num_received = 0;
+ }
+
+ num_results = talloc_array_length(state->finfo);
+
+ if (num_results == 1) {
+ finfo = talloc_move(mem_ctx, &state->finfo);
+ } else {
+ struct file_info *src_finfo =
+ &state->finfo[state->num_received];
+
+ finfo = talloc(mem_ctx, struct file_info);
+ if (finfo == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *finfo = *src_finfo;
+ finfo->name = talloc_move(finfo, &src_finfo->name);
+ finfo->short_name = talloc_move(finfo, &src_finfo->short_name);
+ }
+
+ state->num_received += 1;
+
+ if (state->num_received == num_results) {
+ TALLOC_FREE(state->finfo);
+ }
+
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+
+ *pfinfo = finfo;
+ return NT_STATUS_OK;
+}
+
+struct cli_list_sync_state {
+ const char *mask;
+ uint32_t attribute;
+ NTSTATUS (*fn)(struct file_info *finfo,
+ const char *mask,
+ void *private_data);
+ void *private_data;
+ NTSTATUS status;
+ bool processed_file;
+};
+
+static void cli_list_sync_cb(struct tevent_req *subreq)
+{
+ struct cli_list_sync_state *state =
+ tevent_req_callback_data_void(subreq);
+ struct file_info *finfo;
+ bool ok;
+
+ state->status = cli_list_recv(subreq, talloc_tos(), &finfo);
+ /* No TALLOC_FREE(subreq), we get here more than once */
+
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_RETRY)) {
+ /*
+ * The lowlevel SMB call was rearmed, we'll get back
+ * here when it's done.
+ */
+ state->status = NT_STATUS_OK;
+ return;
+ }
+
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return;
+ }
+
+ ok = dir_check_ftype(finfo->attr, state->attribute);
+ if (!ok) {
+ /*
+ * Only process if attributes match. On SMB1 server
+ * does this, so on SMB2 we need to emulate in the
+ * client.
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=10260
+ */
+ return;
+ }
+
+ state->status = state->fn(finfo, state->mask, state->private_data);
+
+ state->processed_file = true;
+
+ TALLOC_FREE(finfo);
+}
+
+NTSTATUS cli_list(struct cli_state *cli,
+ const char *mask,
+ uint32_t attribute,
+ NTSTATUS (*fn)(struct file_info *finfo,
+ const char *mask,
+ void *private_data),
+ void *private_data)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_list_sync_state state = {
+ .mask = mask,
+ .attribute = attribute,
+ .fn = fn,
+ .private_data = private_data,
+ };
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ uint16_t info_level;
+ enum protocol_types proto = smbXcli_conn_protocol(cli->conn);
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ if (proto >= PROTOCOL_SMB2_02) {
+ info_level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO;
+ } else {
+ info_level = (smb1cli_conn_capabilities(cli->conn) & CAP_NT_SMBS)
+ ? SMB_FIND_FILE_BOTH_DIRECTORY_INFO : SMB_FIND_INFO_STANDARD;
+ }
+
+ req = cli_list_send(frame, ev, cli, mask, attribute, info_level);
+ if (req == NULL) {
+ goto fail;
+ }
+ tevent_req_set_callback(req, cli_list_sync_cb, &state);
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = state.status;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_FILES)) {
+ status = NT_STATUS_OK;
+ }
+
+ if (NT_STATUS_IS_OK(status) && !state.processed_file) {
+ status = NT_STATUS_NO_SUCH_FILE;
+ }
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/climessage.c b/source3/libsmb/climessage.c
new file mode 100644
index 0000000..8563615
--- /dev/null
+++ b/source3/libsmb/climessage.c
@@ -0,0 +1,411 @@
+/*
+ Unix SMB/CIFS implementation.
+ client message handling routines
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+struct cli_message_start_state {
+ uint16_t grp;
+};
+
+static void cli_message_start_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_message_start_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *host,
+ const char *username)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_message_start_state *state;
+ char *htmp = NULL;
+ char *utmp = NULL;
+ size_t hlen, ulen;
+ uint8_t *bytes, *p;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_message_start_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!convert_string_talloc(talloc_tos(), CH_UNIX, CH_DOS,
+ username, strlen(username)+1,
+ &utmp, &ulen)) {
+ goto fail;
+ }
+ if (!convert_string_talloc(talloc_tos(), CH_UNIX, CH_DOS,
+ host, strlen(host)+1,
+ &htmp, &hlen)) {
+ goto fail;
+ }
+
+ bytes = talloc_array(state, uint8_t, ulen+hlen+2);
+ if (bytes == NULL) {
+ goto fail;
+ }
+ p = bytes;
+
+ *p++ = 4;
+ memcpy(p, utmp, ulen);
+ p += ulen;
+ *p++ = 4;
+ memcpy(p, htmp, hlen);
+ TALLOC_FREE(htmp);
+ TALLOC_FREE(utmp);
+
+ subreq = cli_smb_send(state, ev, cli, SMBsendstrt, 0, 0, 0, NULL,
+ talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_message_start_done, req);
+ return req;
+fail:
+ TALLOC_FREE(htmp);
+ TALLOC_FREE(utmp);
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+}
+
+static void cli_message_start_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_message_start_state *state = tevent_req_data(
+ req, struct cli_message_start_state);
+ NTSTATUS status;
+ uint8_t wct;
+ uint16_t *vwv;
+
+ status = cli_smb_recv(subreq, state, NULL, 0, &wct, &vwv,
+ NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (wct >= 1) {
+ state->grp = SVAL(vwv+0, 0);
+ } else {
+ state->grp = 0;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_message_start_recv(struct tevent_req *req,
+ uint16_t *pgrp)
+{
+ struct cli_message_start_state *state = tevent_req_data(
+ req, struct cli_message_start_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pgrp = state->grp;
+ return NT_STATUS_OK;
+}
+
+struct cli_message_text_state {
+ uint16_t vwv;
+};
+
+static void cli_message_text_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_message_text_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t grp,
+ const char *msg,
+ int msglen)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_message_text_state *state;
+ char *tmp;
+ size_t tmplen;
+ uint8_t *bytes;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_message_text_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ SSVAL(&state->vwv, 0, grp);
+
+ if (convert_string_talloc(talloc_tos(), CH_UNIX, CH_DOS, msg, msglen,
+ &tmp, &tmplen)) {
+ msg = tmp;
+ msglen = tmplen;
+ } else {
+ DEBUG(3, ("Conversion failed, sending message in UNIX "
+ "charset\n"));
+ tmp = NULL;
+ }
+
+ bytes = talloc_array(state, uint8_t, msglen+3);
+ if (tevent_req_nomem(bytes, req)) {
+ TALLOC_FREE(tmp);
+ return tevent_req_post(req, ev);
+ }
+ SCVAL(bytes, 0, 1); /* pad */
+ SSVAL(bytes+1, 0, msglen);
+ memcpy(bytes+3, msg, msglen);
+ TALLOC_FREE(tmp);
+
+ subreq = cli_smb_send(state, ev, cli, SMBsendtxt, 0, 0, 1, &state->vwv,
+ talloc_get_size(bytes), bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_message_text_done, req);
+ return req;
+}
+
+static void cli_message_text_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_message_text_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct cli_message_end_state {
+ uint16_t vwv;
+};
+
+static void cli_message_end_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_message_end_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t grp)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_message_end_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_message_end_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ SSVAL(&state->vwv, 0, grp);
+
+ subreq = cli_smb_send(state, ev, cli, SMBsendend, 0, 0, 1, &state->vwv,
+ 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_message_end_done, req);
+ return req;
+}
+
+static void cli_message_end_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, NULL, NULL, 0, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_message_end_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct cli_message_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ size_t sent;
+ const char *message;
+ uint16_t grp;
+};
+
+static void cli_message_started(struct tevent_req *subreq);
+static void cli_message_sent(struct tevent_req *subreq);
+static void cli_message_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_message_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *host, const char *username,
+ const char *message)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_message_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_message_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->sent = 0;
+ state->message = message;
+
+ subreq = cli_message_start_send(state, ev, cli, host, username);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_message_started, req);
+ return req;
+}
+
+static void cli_message_started(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_message_state *state = tevent_req_data(
+ req, struct cli_message_state);
+ NTSTATUS status;
+ size_t thistime;
+
+ status = cli_message_start_recv(subreq, &state->grp);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ thistime = MIN(127, strlen(state->message));
+
+ subreq = cli_message_text_send(state, state->ev, state->cli,
+ state->grp, state->message, thistime);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ state->sent += thistime;
+ tevent_req_set_callback(subreq, cli_message_sent, req);
+}
+
+static void cli_message_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_message_state *state = tevent_req_data(
+ req, struct cli_message_state);
+ NTSTATUS status;
+ size_t left, thistime;
+
+ status = cli_message_text_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (state->sent >= strlen(state->message)) {
+ subreq = cli_message_end_send(state, state->ev, state->cli,
+ state->grp);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_message_done, req);
+ return;
+ }
+
+ left = strlen(state->message) - state->sent;
+ thistime = MIN(127, left);
+
+ subreq = cli_message_text_send(state, state->ev, state->cli,
+ state->grp,
+ state->message + state->sent,
+ thistime);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ state->sent += thistime;
+ tevent_req_set_callback(subreq, cli_message_sent, req);
+}
+
+static void cli_message_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_message_end_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_message_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_message(struct cli_state *cli, const char *host,
+ const char *username, const char *message)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_message_send(frame, ev, cli, host, username, message);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_message_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/clioplock.c b/source3/libsmb/clioplock.c
new file mode 100644
index 0000000..aee09da
--- /dev/null
+++ b/source3/libsmb/clioplock.c
@@ -0,0 +1,169 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB client oplock functions
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+struct cli_smb_oplock_break_waiter_state {
+ uint16_t fnum;
+ uint8_t level;
+};
+
+static void cli_smb_oplock_break_waiter_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_smb_oplock_break_waiter_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_smb_oplock_break_waiter_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb_oplock_break_waiter_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Create a fake SMB request that we will never send out. This is only
+ * used to be set into the pending queue with the right mid.
+ */
+ subreq = smb1cli_req_create(mem_ctx, ev, cli->conn, 0, 0, 0, 0, 0, 0,
+ 0, NULL, NULL, 0, NULL, 0, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ smb1cli_req_set_mid(subreq, 0xffff);
+
+ if (!smbXcli_req_set_pending(subreq)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb_oplock_break_waiter_done, req);
+ return req;
+}
+
+static void cli_smb_oplock_break_waiter_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb_oplock_break_waiter_state *state = tevent_req_data(
+ req, struct cli_smb_oplock_break_waiter_state);
+ struct iovec *iov;
+ uint8_t wct;
+ uint16_t *vwv;
+ NTSTATUS status;
+
+ status = smb1cli_req_recv(subreq, state,
+ &iov, /* piov */
+ NULL, /* phdr */
+ &wct,
+ &vwv,
+ NULL, /* pvwv_offset */
+ NULL, /* pnum_bytes */
+ NULL, /* pbytes */
+ NULL, /* pbytes_offset */
+ NULL, /* pinbuf */
+ NULL, 0); /* expected */
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (wct < 8) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+ state->fnum = SVAL(vwv+2, 0);
+ state->level = CVAL(vwv+3, 1);
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_smb_oplock_break_waiter_recv(struct tevent_req *req,
+ uint16_t *pfnum,
+ uint8_t *plevel)
+{
+ struct cli_smb_oplock_break_waiter_state *state = tevent_req_data(
+ req, struct cli_smb_oplock_break_waiter_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfnum = state->fnum;
+ *plevel = state->level;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+send an ack for an oplock break request
+****************************************************************************/
+
+struct cli_oplock_ack_state {
+ uint8_t dummy;
+};
+
+static void cli_oplock_ack_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_oplock_ack_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum, uint8_t level)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_oplock_ack_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_oplock_ack_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_lockingx_send(
+ state, /* mem_ctx */
+ ev, /* tevent_context */
+ cli, /* cli */
+ fnum, /* fnum */
+ LOCKING_ANDX_OPLOCK_RELEASE, /* typeoflock */
+ level, /* newoplocklevel */
+ 0, /* timeout */
+ 0, /* num_unlocks */
+ NULL, /* unlocks */
+ 0, /* num_locks */
+ NULL); /* locks */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_oplock_ack_done, req);
+ return req;
+}
+
+static void cli_oplock_ack_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_lockingx_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_oplock_ack_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
diff --git a/source3/libsmb/cliprint.c b/source3/libsmb/cliprint.c
new file mode 100644
index 0000000..b354990
--- /dev/null
+++ b/source3/libsmb/cliprint.c
@@ -0,0 +1,219 @@
+/*
+ Unix SMB/CIFS implementation.
+ client print routines
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/util/string_wrappers.h"
+
+/*****************************************************************************
+ Convert a character pointer in a cli_call_api() response to a form we can use.
+ This function contains code to prevent core dumps if the server returns
+ invalid data.
+*****************************************************************************/
+static const char *fix_char_ptr(unsigned int datap, unsigned int converter,
+ char *rdata, int rdrcnt)
+{
+ unsigned int offset;
+
+ if (datap == 0) {
+ /* turn NULL pointers into zero length strings */
+ return "";
+ }
+
+ offset = datap - converter;
+
+ if (offset >= rdrcnt) {
+ DEBUG(1,("bad char ptr: datap=%u, converter=%u rdrcnt=%d>\n",
+ datap, converter, rdrcnt));
+ return "<ERROR>";
+ }
+ return &rdata[offset];
+}
+
+/****************************************************************************
+call fn() on each entry in a print queue
+****************************************************************************/
+
+NTSTATUS cli_print_queue(struct cli_state *cli,
+ void (*fn)(struct print_job_info *))
+{
+ uint8_t *rparam = NULL;
+ uint8_t *rdata = NULL;
+ char *p = NULL;
+ uint32_t rdrcnt, rprcnt;
+ char param[1024];
+ int converter;
+ int result_code=0;
+ int i = -1;
+ NTSTATUS status;
+
+ memset(param,'\0',sizeof(param));
+
+ p = param;
+ SSVAL(p,0,76); /* API function number 76 (DosPrintJobEnum) */
+ p += 2;
+ strlcpy_base(p,"zWrLeh", param, sizeof(param)); /* parameter description? */
+ p = skip_string(param,sizeof(param),p);
+ strlcpy_base(p,"WWzWWDDzz", param, sizeof(param)); /* returned data format */
+ p = skip_string(param,sizeof(param),p);
+ strlcpy_base(p,cli->share, param, sizeof(param)); /* name of queue */
+ p = skip_string(param,sizeof(param),p);
+ SSVAL(p,0,2); /* API function level 2, PRJINFO_2 data structure */
+ SSVAL(p,2,1000); /* size of bytes of returned data buffer */
+ p += 4;
+ strlcpy_base(p,"", param,sizeof(param)); /* subformat */
+ p = skip_string(param,sizeof(param),p);
+
+ DEBUG(4,("doing cli_print_queue for %s\n", cli->share));
+
+ status = cli_trans(
+ talloc_tos(),
+ cli,
+ SMBtrans, /* trans_cmd */
+ "\\PIPE\\LANMAN", /* name */
+ 0, /* fid */
+ 0, /* function */
+ 0, /* flags */
+ NULL, /* setup */
+ 0, /* num_setup */
+ 0, /* max_setup */
+ (uint8_t *)param, /* param */
+ PTR_DIFF(p,param), /* num_param */
+ 1024, /* max_param */
+ NULL, /* data */
+ 0, /* num_data */
+ CLI_BUFFER_SIZE, /* max_data */
+ NULL, /* recv_flags2 */
+ NULL, /* rsetup */
+ 0, /* min_rsetup */
+ NULL, /* num_rsetup */
+ &rparam, /* rparam */
+ 8, /* min_rparam */
+ &rprcnt, /* num_rparam */
+ &rdata, /* rdata */
+ 0, /* min_rdata */
+ &rdrcnt); /* num_rdata */
+ if (!NT_STATUS_IS_OK(status)) {
+ cli->raw_status = status;
+ return status;
+ }
+
+ result_code = SVAL(rparam,0);
+ converter = SVAL(rparam,2); /* conversion factor */
+
+ if (result_code == 0) {
+ struct print_job_info job;
+
+ p = (char *)rdata;
+
+ for (i = 0; i < SVAL(rparam,4); ++i) {
+ job.id = SVAL(p,0);
+ job.priority = SVAL(p,2);
+ fstrcpy(job.user,
+ fix_char_ptr(SVAL(p,4), converter,
+ (char *)rdata, rdrcnt));
+ job.t = make_unix_date3(
+ p + 12, smb1cli_conn_server_time_zone(cli->conn));
+ job.size = IVAL(p,16);
+ fstrcpy(job.name,fix_char_ptr(SVAL(p,24),
+ converter,
+ (char *)rdata, rdrcnt));
+ fn(&job);
+ p += 28;
+ }
+ }
+
+ /* If any parameters or data were returned, free the storage. */
+ TALLOC_FREE(rparam);
+ TALLOC_FREE(rdata);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ cancel a print job
+ ****************************************************************************/
+
+NTSTATUS cli_printjob_del(struct cli_state *cli, int job)
+{
+ uint8_t *rparam = NULL;
+ uint8_t *rdata = NULL;
+ char *p = NULL;
+ uint32_t rdrcnt, rprcnt;
+ int result_code;
+ char param[1024];
+ NTSTATUS status = NT_STATUS_OK;
+
+ memset(param,'\0',sizeof(param));
+
+ p = param;
+ SSVAL(p,0,81); /* DosPrintJobDel() */
+ p += 2;
+ strlcpy_base(p,"W", param,sizeof(param));
+ p = skip_string(param,sizeof(param),p);
+ strlcpy_base(p,"", param,sizeof(param));
+ p = skip_string(param,sizeof(param),p);
+ SSVAL(p,0,job);
+ p += 2;
+
+ status = cli_trans(talloc_tos(),
+ cli,
+ SMBtrans, /* trans_cmd */
+ "\\PIPE\\LANMAN", /* name */
+ 0, /* fid */
+ 0, /* function */
+ 0, /* flags */
+ NULL, /* setup */
+ 0, /* num_setup */
+ 0, /* max_setup */
+ (uint8_t *)param, /* param */
+ PTR_DIFF(p, param), /* num_param */
+ 1024, /* max_param */
+ NULL, /* data */
+ 0, /* num_data */
+ CLI_BUFFER_SIZE, /* max_data */
+ NULL, /* recv_flags2 */
+ NULL, /* rsetup */
+ 0, /* min_rsetup */
+ NULL, /* num_rsetup */
+ &rparam, /* rparam */
+ 8, /* min_rparam */
+ &rprcnt, /* num_rparam */
+ &rdata, /* rdata */
+ 0, /* min_rdata */
+ &rdrcnt); /* num_rdata */
+ if (!NT_STATUS_IS_OK(status)) {
+ cli->raw_status = status;
+ return status;
+ }
+
+ result_code = SVAL(rparam, 0);
+
+ TALLOC_FREE(rparam);
+ TALLOC_FREE(rdata);
+
+ if (result_code == ERRnosuchprintjob) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ cli->raw_status = NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return status;
+}
diff --git a/source3/libsmb/cliquota.c b/source3/libsmb/cliquota.c
new file mode 100644
index 0000000..0c41272
--- /dev/null
+++ b/source3/libsmb/cliquota.c
@@ -0,0 +1,696 @@
+/*
+ Unix SMB/CIFS implementation.
+ client quota functions
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "trans2.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+
+NTSTATUS cli_get_quota_handle(struct cli_state *cli, uint16_t *quota_fnum)
+{
+ return cli_ntcreate(cli, FAKE_FILE_NAME_QUOTA_WIN32,
+ 0x00000016, DESIRED_ACCESS_PIPE,
+ 0x00000000, FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0x00000000, 0x03, quota_fnum, NULL);
+}
+
+void free_ntquota_list(SMB_NTQUOTA_LIST **qt_list)
+{
+ if (!qt_list || !*qt_list) {
+ return;
+ }
+
+ if ((*qt_list)->mem_ctx)
+ talloc_destroy((*qt_list)->mem_ctx);
+
+ (*qt_list) = NULL;
+
+ return;
+}
+
+bool add_record_to_ntquota_list(TALLOC_CTX *mem_ctx,
+ SMB_NTQUOTA_STRUCT *pqt,
+ SMB_NTQUOTA_LIST **pqt_list)
+{
+ SMB_NTQUOTA_LIST *tmp_list_ent;
+
+ if ((tmp_list_ent = talloc_zero(mem_ctx, SMB_NTQUOTA_LIST)) == NULL) {
+ return false;
+ }
+
+ if ((tmp_list_ent->quotas = talloc_zero(mem_ctx, SMB_NTQUOTA_STRUCT)) ==
+ NULL) {
+ return false;
+ }
+
+ *tmp_list_ent->quotas = *pqt;
+ tmp_list_ent->mem_ctx = mem_ctx;
+
+ DLIST_ADD((*pqt_list), tmp_list_ent);
+
+ return true;
+}
+
+bool parse_user_quota_record(const uint8_t *rdata,
+ unsigned int rdata_count,
+ unsigned int *offset,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ struct file_quota_information info = {0};
+ TALLOC_CTX *frame = talloc_stackframe();
+ DATA_BLOB blob;
+ enum ndr_err_code err;
+ bool result = false;
+
+ blob.data = discard_const_p(uint8_t, rdata);
+ blob.length = rdata_count;
+ err = ndr_pull_struct_blob(
+ &blob,
+ frame,
+ &info,
+ (ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ goto out;
+ }
+
+ *offset = info.next_entry_offset;
+
+ ZERO_STRUCTP(pqt);
+ pqt->usedspace = info.quota_used;
+
+ pqt->softlim = info.quota_threshold;
+
+ pqt->hardlim = info.quota_limit;
+
+ pqt->qtype = SMB_USER_QUOTA_TYPE;
+ pqt->sid = info.sid;
+ result = true;
+out:
+ TALLOC_FREE(frame);
+ return result;
+}
+
+NTSTATUS parse_user_quota_list(const uint8_t *curdata,
+ uint32_t curdata_count,
+ TALLOC_CTX *mem_ctx,
+ SMB_NTQUOTA_LIST **pqt_list)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ unsigned offset;
+ SMB_NTQUOTA_STRUCT qt;
+
+ while (true) {
+ ZERO_STRUCT(qt);
+ if (!parse_user_quota_record(curdata, curdata_count, &offset,
+ &qt)) {
+ DEBUG(1, ("Failed to parse the quota record\n"));
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ break;
+ }
+
+ if (offset > curdata_count) {
+ DEBUG(1, ("out of bounds offset in quota record\n"));
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ break;
+ }
+
+ if (curdata + offset < curdata) {
+ DEBUG(1, ("Pointer overflow in quota record\n"));
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ break;
+ }
+
+ if (!add_record_to_ntquota_list(mem_ctx, &qt, pqt_list)) {
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ curdata += offset;
+ curdata_count -= offset;
+
+ if (offset == 0) {
+ break;
+ }
+ }
+
+ return status;
+}
+
+NTSTATUS parse_fs_quota_buffer(const uint8_t *rdata,
+ unsigned int rdata_count,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ SMB_NTQUOTA_STRUCT qt;
+
+ ZERO_STRUCT(qt);
+
+ if (rdata_count < 48) {
+ /* minimum length is not enforced by SMB2 client.
+ */
+ DEBUG(1, ("small returned fs quota buffer\n"));
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ /* unknown_1 24 NULL bytes in pdata*/
+
+ /* the soft quotas 8 bytes (uint64_t)*/
+ qt.softlim = BVAL(rdata, 24);
+
+ /* the hard quotas 8 bytes (uint64_t)*/
+ qt.hardlim = BVAL(rdata, 32);
+
+ /* quota_flags 2 bytes **/
+ qt.qflags = SVAL(rdata, 40);
+
+ qt.qtype = SMB_USER_FS_QUOTA_TYPE;
+
+ *pqt = qt;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS build_user_quota_buffer(SMB_NTQUOTA_LIST *qt_list,
+ uint32_t maxlen,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *outbuf,
+ SMB_NTQUOTA_LIST **end_ptr)
+{
+ return fill_quota_buffer(mem_ctx,
+ qt_list,
+ false,
+ maxlen,
+ outbuf,
+ end_ptr);
+}
+
+NTSTATUS build_fs_quota_buffer(TALLOC_CTX *mem_ctx,
+ const SMB_NTQUOTA_STRUCT *pqt,
+ DATA_BLOB *blob,
+ uint32_t maxlen)
+{
+ uint8_t *buf;
+
+ if (maxlen > 0 && maxlen < 48) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ *blob = data_blob_talloc_zero(mem_ctx, 48);
+
+ if (!blob->data) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ buf = blob->data;
+
+ /* Unknown1 24 NULL bytes*/
+ SBIG_UINT(buf, 0, (uint64_t)0);
+ SBIG_UINT(buf, 8, (uint64_t)0);
+ SBIG_UINT(buf, 16, (uint64_t)0);
+
+ /* Default Soft Quota 8 bytes */
+ SBIG_UINT(buf, 24, pqt->softlim);
+
+ /* Default Hard Quota 8 bytes */
+ SBIG_UINT(buf, 32, pqt->hardlim);
+
+ /* Quota flag 4 bytes */
+ SIVAL(buf, 40, pqt->qflags);
+
+ /* 4 padding bytes */
+ SIVAL(buf, 44, 0);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_get_user_quota(struct cli_state *cli, int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ uint16_t setup[1];
+ uint8_t *rparam = NULL, *rdata = NULL;
+ uint32_t rparam_count, rdata_count;
+ unsigned int sid_len;
+ unsigned int offset;
+ struct nttrans_query_quota_params get_quota = {0};
+ struct file_get_quota_info info = {0};
+ enum ndr_err_code err;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ DATA_BLOB data_blob = data_blob_null;
+ DATA_BLOB param_blob = data_blob_null;
+
+ if (!cli||!pqt) {
+ smb_panic("cli_get_user_quota() called with NULL Pointer!");
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ TALLOC_FREE(frame);
+ return cli_smb2_get_user_quota(cli, quota_fnum, pqt);
+ }
+
+ get_quota.fid = quota_fnum;
+ get_quota.return_single_entry = 1;
+ get_quota.restart_scan = 0;
+
+ sid_len = ndr_size_dom_sid(&pqt->sid, 0);
+
+ info.next_entry_offset = 0;
+ info.sid_length = sid_len;
+ info.sid = pqt->sid;
+
+ err = ndr_push_struct_blob(
+ &data_blob,
+ frame,
+ &info,
+ (ndr_push_flags_fn_t)ndr_push_file_get_quota_info);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ get_quota.sid_list_length = data_blob.length;
+ get_quota.start_sid_offset = data_blob.length;
+
+ err = ndr_push_struct_blob(
+ &param_blob,
+ frame,
+ &get_quota,
+ (ndr_push_flags_fn_t)ndr_push_nttrans_query_quota_params);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, -1, /* name, fid */
+ NT_TRANSACT_GET_USER_QUOTA, 0,
+ setup, 1, 0, /* setup */
+ param_blob.data, param_blob.length, 4, /* params */
+ data_blob.data, data_blob.length, 112, /* data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ &rparam, 4, &rparam_count,
+ &rdata, 8, &rdata_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("NT_TRANSACT_GET_USER_QUOTA failed: %s\n",
+ nt_errstr(status)));
+ goto out;
+ }
+
+ if (!parse_user_quota_record(rdata, rdata_count, &offset, pqt)) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ DEBUG(0,("Got INVALID NT_TRANSACT_GET_USER_QUOTA reply.\n"));
+ }
+
+out:
+ TALLOC_FREE(rparam);
+ TALLOC_FREE(rdata);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS
+cli_set_user_quota(struct cli_state *cli, int quota_fnum, SMB_NTQUOTA_LIST *qtl)
+{
+ uint16_t setup[1];
+ uint8_t params[2];
+ DATA_BLOB data = data_blob_null;
+ NTSTATUS status;
+
+ if (!cli || !qtl) {
+ smb_panic("cli_set_user_quota() called with NULL Pointer!");
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_set_user_quota(cli, quota_fnum, qtl);
+ }
+
+ status = build_user_quota_buffer(qtl, 0, talloc_tos(), &data, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * smb1 doesn't send NT_STATUS_NO_MORE_ENTRIES so swallow
+ * this status.
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) {
+ goto cleanup;
+ }
+ }
+
+ SSVAL(setup + 0, 0, NT_TRANSACT_SET_USER_QUOTA);
+
+ SSVAL(params,0,quota_fnum);
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, -1, /* name, fid */
+ NT_TRANSACT_SET_USER_QUOTA, 0,
+ setup, 1, 0, /* setup */
+ params, 2, 0, /* params */
+ data.data, data.length, 0, /* data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL, /* rparams */
+ NULL, 0, NULL); /* rdata */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("NT_TRANSACT_SET_USER_QUOTA failed: %s\n",
+ nt_errstr(status)));
+ }
+
+cleanup:
+ data_blob_free(&data);
+ return status;
+}
+
+static NTSTATUS cli_list_user_quota_step(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ int quota_fnum,
+ SMB_NTQUOTA_LIST **pqt_list,
+ bool first)
+{
+ uint16_t setup[1];
+ DATA_BLOB params_blob = data_blob_null;
+ uint8_t *rparam=NULL, *rdata=NULL;
+ uint32_t rparam_count=0, rdata_count=0;
+ NTSTATUS status;
+ struct nttrans_query_quota_params quota_params = {0};
+ enum ndr_err_code err;
+
+ TALLOC_CTX *frame = NULL;
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_list_user_quota_step(cli, mem_ctx, quota_fnum,
+ pqt_list, first);
+ }
+ frame = talloc_stackframe();
+
+ SSVAL(setup + 0, 0, NT_TRANSACT_GET_USER_QUOTA);
+
+ quota_params.fid = quota_fnum;
+ if (first) {
+ quota_params.restart_scan = 1;
+ }
+ err = ndr_push_struct_blob(
+ &params_blob,
+ frame,
+ &quota_params,
+ (ndr_push_flags_fn_t)ndr_push_nttrans_query_quota_params);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto cleanup;
+ }
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, -1, /* name, fid */
+ NT_TRANSACT_GET_USER_QUOTA, 0,
+ setup, 1, 0, /* setup */
+ params_blob.data, params_blob.length, 4, /* params */
+ NULL, 0, 2048, /* data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ &rparam, 0, &rparam_count,
+ &rdata, 0, &rdata_count);
+
+ /* compat. with smbd + safeguard against
+ * endless loop
+ */
+ if (NT_STATUS_IS_OK(status) && rdata_count == 0) {
+ status = NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto cleanup;
+ }
+
+ status = parse_user_quota_list(rdata, rdata_count, mem_ctx, pqt_list);
+
+cleanup:
+ TALLOC_FREE(rparam);
+ TALLOC_FREE(rdata);
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+NTSTATUS cli_list_user_quota(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_LIST **pqt_list)
+{
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx = NULL;
+ bool first = true;
+
+ if (!cli || !pqt_list) {
+ smb_panic("cli_list_user_quota() called with NULL Pointer!");
+ }
+
+ *pqt_list = NULL;
+
+ if ((mem_ctx = talloc_init("SMB_USER_QUOTA_LIST")) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ do {
+ status = cli_list_user_quota_step(cli, mem_ctx, quota_fnum,
+ pqt_list, first);
+ first = false;
+ } while (NT_STATUS_IS_OK(status));
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) {
+ status = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status) || *pqt_list == NULL) {
+ TALLOC_FREE(mem_ctx);
+ }
+
+ return status;
+}
+
+NTSTATUS cli_get_fs_quota_info(struct cli_state *cli, int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ uint16_t setup[1];
+ uint8_t param[2];
+ uint8_t *rdata=NULL;
+ uint32_t rdata_count=0;
+ NTSTATUS status;
+
+ if (!cli||!pqt) {
+ smb_panic("cli_get_fs_quota_info() called with NULL Pointer!");
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_get_fs_quota_info(cli, quota_fnum, pqt);
+ }
+
+ SSVAL(setup + 0, 0, TRANSACT2_QFSINFO);
+
+ SSVAL(param,0,SMB_FS_QUOTA_INFORMATION);
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2,
+ NULL, -1, /* name, fid */
+ 0, 0, /* function, flags */
+ setup, 1, 0, /* setup */
+ param, 2, 0, /* param */
+ NULL, 0, 560, /* data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL, /* rparam */
+ &rdata, 48, &rdata_count);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("SMB_FS_QUOTA_INFORMATION failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = parse_fs_quota_buffer(rdata, rdata_count, pqt);
+
+ TALLOC_FREE(rdata);
+ return status;
+}
+
+NTSTATUS cli_set_fs_quota_info(struct cli_state *cli, int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ uint16_t setup[1];
+ uint8_t param[4];
+ DATA_BLOB data = data_blob_null;
+ NTSTATUS status;
+
+ if (!cli||!pqt) {
+ smb_panic("cli_set_fs_quota_info() called with NULL Pointer!");
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_set_fs_quota_info(cli, quota_fnum, pqt);
+ }
+
+ status = build_fs_quota_buffer(talloc_tos(), pqt, &data, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ SSVAL(setup + 0, 0,TRANSACT2_SETFSINFO);
+
+ SSVAL(param,0,quota_fnum);
+ SSVAL(param,2,SMB_FS_QUOTA_INFORMATION);
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2,
+ NULL, -1, /* name, fid */
+ 0, 0, /* function, flags */
+ setup, 1, 0, /* setup */
+ param, 4, 0, /* param */
+ data.data, data.length, 0, /* data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ NULL, 0, NULL, /* rparam */
+ NULL, 0, NULL); /* rdata */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("SMB_FS_QUOTA_INFORMATION failed: %s\n",
+ nt_errstr(status)));
+ }
+
+ return status;
+}
+
+NTSTATUS fill_quota_buffer(TALLOC_CTX *mem_ctx,
+ SMB_NTQUOTA_LIST *qlist,
+ bool return_single,
+ uint32_t max_data,
+ DATA_BLOB *blob,
+ SMB_NTQUOTA_LIST **end_ptr)
+{
+ ndr_flags_type ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+ struct ndr_push *qndr = NULL;
+ uint32_t start_offset = 0;
+ uint32_t padding = 0;
+ if (qlist == NULL) {
+ /* We must push at least one. */
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ qndr = ndr_push_init_ctx(mem_ctx);
+ if (qndr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (;qlist != NULL; qlist = qlist->next) {
+ struct file_quota_information info = {0};
+ enum ndr_err_code err;
+ uint32_t dsize = sizeof(info.next_entry_offset)
+ + sizeof(info.sid_length)
+ + sizeof(info.change_time)
+ + sizeof(info.quota_used)
+ + sizeof(info.quota_threshold)
+ + sizeof(info.quota_limit);
+
+
+ info.sid_length = ndr_size_dom_sid(&qlist->quotas->sid, 0);
+
+ if (max_data) {
+ uint32_t curr_pos_no_padding = qndr->offset - padding;
+ uint32_t payload = dsize + info.sid_length;
+ uint32_t new_pos = (curr_pos_no_padding + payload);
+ if (new_pos < curr_pos_no_padding) {
+ /* Detect unlikely integer wrap */
+ DBG_ERR("Integer wrap while adjusting pos "
+ "0x%x by offset 0x%x\n",
+ curr_pos_no_padding, payload);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (new_pos > max_data) {
+ DBG_WARNING("Max data will be exceeded "
+ "writing next query info. "
+ "cur_pos 0x%x, sid_length 0x%x, "
+ "dsize 0x%x, max_data 0x%x\n",
+ curr_pos_no_padding,
+ info.sid_length,
+ dsize,
+ max_data);
+ break;
+ }
+ }
+
+ start_offset = qndr->offset;
+ info.sid = qlist->quotas->sid;
+ info.quota_used = qlist->quotas->usedspace;
+ info.quota_threshold = qlist->quotas->softlim;
+ info.quota_limit = qlist->quotas->hardlim;
+
+ err = ndr_push_file_quota_information(qndr,
+ ndr_flags,
+ &info);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DBG_DEBUG("Failed to push the quota sid\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* pidl will align to 8 bytes due to 8 byte members*/
+ /* Remember how much align padding we've used. */
+ padding = qndr->offset;
+
+ err = ndr_push_align(qndr, 8);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DBG_DEBUG("ndr_push_align returned %s\n",
+ ndr_map_error2string(err));
+ return ndr_map_error2ntstatus(err);
+ }
+
+ padding = qndr->offset - padding;
+
+ /*
+ * Overwrite next_entry_offset for this entry now
+ * we know what it should be. We know we're using
+ * LIBNDR_FLAG_LITTLE_ENDIAN here so we can use
+ * SIVAL.
+ */
+ info.next_entry_offset = qndr->offset - start_offset;
+ SIVAL(qndr->data, start_offset, info.next_entry_offset);
+
+ if (return_single) {
+ break;
+ }
+ }
+
+ if (end_ptr != NULL) {
+ *end_ptr = qlist;
+ }
+
+ /* Remove the padding alignment on the last element pushed. */
+ blob->length = qndr->offset - padding;
+ blob->data = qndr->data;
+
+ /*
+ * Terminate the pushed array by setting next_entry_offset
+ * for the last element to zero.
+ */
+ if (blob->length >= sizeof(uint32_t)) {
+ SIVAL(qndr->data, start_offset, 0);
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/clirap.c b/source3/libsmb/clirap.c
new file mode 100644
index 0000000..21ee346
--- /dev/null
+++ b/source3/libsmb/clirap.c
@@ -0,0 +1,1853 @@
+/*
+ Unix SMB/CIFS implementation.
+ client RAP calls
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Gerald (Jerry) Carter 2004
+ Copyright (C) James Peach 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/rap.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "trans2.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/smb/reparse.h"
+#include "cli_smb2_fnum.h"
+#include "lib/util/string_wrappers.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#define PIPE_LANMAN "\\PIPE\\LANMAN"
+
+/****************************************************************************
+ Call a remote api
+****************************************************************************/
+
+bool cli_api(struct cli_state *cli,
+ char *param, int prcnt, int mprcnt,
+ char *data, int drcnt, int mdrcnt,
+ char **rparam, unsigned int *rprcnt,
+ char **rdata, unsigned int *rdrcnt)
+{
+ NTSTATUS status;
+
+ uint8_t *my_rparam, *my_rdata;
+ uint32_t num_my_rparam, num_my_rdata;
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans,
+ PIPE_LANMAN, 0, /* name, fid */
+ 0, 0, /* function, flags */
+ NULL, 0, 0, /* setup */
+ (uint8_t *)param, prcnt, mprcnt, /* Params, length, max */
+ (uint8_t *)data, drcnt, mdrcnt, /* Data, length, max */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ &my_rparam, 0, &num_my_rparam,
+ &my_rdata, 0, &num_my_rdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ /*
+ * I know this memcpy massively hurts, but there are just tons
+ * of callers of cli_api that eventually need changing to
+ * talloc
+ */
+
+ *rparam = (char *)smb_memdup(my_rparam, num_my_rparam);
+ if (*rparam == NULL) {
+ goto fail;
+ }
+ *rprcnt = num_my_rparam;
+ TALLOC_FREE(my_rparam);
+
+ *rdata = (char *)smb_memdup(my_rdata, num_my_rdata);
+ if (*rdata == NULL) {
+ goto fail;
+ }
+ *rdrcnt = num_my_rdata;
+ TALLOC_FREE(my_rdata);
+
+ return true;
+fail:
+ TALLOC_FREE(my_rdata);
+ TALLOC_FREE(my_rparam);
+ *rparam = NULL;
+ *rprcnt = 0;
+ *rdata = NULL;
+ *rdrcnt = 0;
+ return false;
+}
+
+/****************************************************************************
+ Call a NetShareEnum - try and browse available connections on a host.
+****************************************************************************/
+
+int cli_RNetShareEnum(struct cli_state *cli, void (*fn)(const char *, uint32_t, const char *, void *), void *state)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ char param[1024];
+ int count = -1;
+ bool ok;
+ int res;
+
+ /* now send a SMBtrans command with api RNetShareEnum */
+ p = param;
+ SSVAL(p,0,0); /* api number */
+ p += 2;
+ strlcpy(p,"WrLeh",sizeof(param)-PTR_DIFF(p,param));
+ p = skip_string(param,sizeof(param),p);
+ strlcpy(p,"B13BWz",sizeof(param)-PTR_DIFF(p,param));
+ p = skip_string(param,sizeof(param),p);
+ SSVAL(p,0,1);
+ /*
+ * Win2k needs a *smaller* buffer than 0xFFFF here -
+ * it returns "out of server memory" with 0xFFFF !!! JRA.
+ */
+ SSVAL(p,2,0xFFE0);
+ p += 4;
+
+ ok = cli_api(
+ cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 0xFFE0, /* data, length, maxlen - Win2k needs a small buffer here too ! */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt); /* return data, length */
+ if (!ok) {
+ DEBUG(4,("NetShareEnum failed\n"));
+ goto done;
+ }
+
+ if (rprcnt < 6) {
+ DBG_ERR("Got invalid result: rprcnt=%u\n", rprcnt);
+ goto done;
+ }
+
+ res = rparam? SVAL(rparam,0) : -1;
+
+ if (res == 0 || res == ERRmoredata) {
+ int converter=SVAL(rparam,2);
+ int i;
+ char *rdata_end = rdata + rdrcnt;
+
+ count=SVAL(rparam,4);
+ p = rdata;
+
+ for (i=0;i<count;i++,p+=20) {
+ char *sname;
+ int type;
+ int comment_offset;
+ const char *cmnt;
+ const char *p1;
+ char *s1, *s2;
+ size_t len;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (p + 20 > rdata_end) {
+ TALLOC_FREE(frame);
+ break;
+ }
+
+ sname = p;
+ type = SVAL(p,14);
+ comment_offset = (IVAL(p,16) & 0xFFFF) - converter;
+ if (comment_offset < 0 ||
+ comment_offset > (int)rdrcnt) {
+ TALLOC_FREE(frame);
+ break;
+ }
+ cmnt = comment_offset?(rdata+comment_offset):"";
+
+ /* Work out the comment length. */
+ for (p1 = cmnt, len = 0; *p1 &&
+ p1 < rdata_end; len++)
+ p1++;
+ if (!*p1) {
+ len++;
+ }
+ pull_string_talloc(frame,rdata,0,
+ &s1,sname,14,STR_ASCII);
+ pull_string_talloc(frame,rdata,0,
+ &s2,cmnt,len,STR_ASCII);
+ if (!s1 || !s2) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ fn(s1, type, s2, state);
+
+ TALLOC_FREE(frame);
+ }
+ } else {
+ DEBUG(4,("NetShareEnum res=%d\n", res));
+ }
+
+done:
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return count;
+}
+
+/****************************************************************************
+ Call a NetServerEnum for the specified workgroup and servertype mask. This
+ function then calls the specified callback function for each name returned.
+
+ The callback function takes 4 arguments: the machine name, the server type,
+ the comment and a state pointer.
+****************************************************************************/
+
+bool cli_NetServerEnum(struct cli_state *cli, char *workgroup, uint32_t stype,
+ void (*fn)(const char *, uint32_t, const char *, void *),
+ void *state)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *rdata_end = NULL;
+ unsigned int rdrcnt,rprcnt;
+ char *p;
+ char param[1024];
+ int uLevel = 1;
+ size_t len;
+ uint32_t func = RAP_NetServerEnum2;
+ char *last_entry = NULL;
+ int total_cnt = 0;
+ int return_cnt = 0;
+ int res;
+
+ errno = 0; /* reset */
+
+ /*
+ * This may take more than one transaction, so we should loop until
+ * we no longer get a more data to process or we have all of the
+ * items.
+ */
+ do {
+ /* send a SMBtrans command with api NetServerEnum */
+ p = param;
+ SIVAL(p,0,func); /* api number */
+ p += 2;
+
+ if (func == RAP_NetServerEnum3) {
+ strlcpy(p,"WrLehDzz", sizeof(param)-PTR_DIFF(p,param));
+ } else {
+ strlcpy(p,"WrLehDz", sizeof(param)-PTR_DIFF(p,param));
+ }
+
+ p = skip_string(param, sizeof(param), p);
+ strlcpy(p,"B16BBDz", sizeof(param)-PTR_DIFF(p,param));
+
+ p = skip_string(param, sizeof(param), p);
+ SSVAL(p,0,uLevel);
+ SSVAL(p,2,CLI_BUFFER_SIZE);
+ p += 4;
+ SIVAL(p,0,stype);
+ p += 4;
+
+ /* If we have more data, tell the server where
+ * to continue from.
+ */
+ len = push_ascii(p,
+ workgroup,
+ sizeof(param) - PTR_DIFF(p,param) - 1,
+ STR_TERMINATE|STR_UPPER);
+
+ if (len == 0) {
+ SAFE_FREE(last_entry);
+ return false;
+ }
+ p += len;
+
+ if (func == RAP_NetServerEnum3) {
+ len = push_ascii(p,
+ last_entry ? last_entry : "",
+ sizeof(param) - PTR_DIFF(p,param) - 1,
+ STR_TERMINATE);
+
+ if (len == 0) {
+ SAFE_FREE(last_entry);
+ return false;
+ }
+ p += len;
+ }
+
+ /* Next time through we need to use the continue api */
+ func = RAP_NetServerEnum3;
+
+ if (!cli_api(cli,
+ param, PTR_DIFF(p,param), 8, /* params, length, max */
+ NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */
+ &rparam, &rprcnt, /* return params, return size */
+ &rdata, &rdrcnt)) { /* return data, return size */
+
+ /* break out of the loop on error */
+ res = -1;
+ break;
+ }
+
+ rdata_end = rdata + rdrcnt;
+
+ if (rprcnt < 6) {
+ DBG_ERR("Got invalid result: rprcnt=%u\n", rprcnt);
+ res = -1;
+ break;
+ }
+
+ res = rparam ? SVAL(rparam,0) : -1;
+
+ if (res == 0 || res == ERRmoredata ||
+ (res != -1 && cli_errno(cli) == 0)) {
+ char *sname = NULL;
+ int i, count;
+ int converter=SVAL(rparam,2);
+
+ /* Get the number of items returned in this buffer */
+ count = SVAL(rparam, 4);
+
+ /* The next field contains the number of items left,
+ * including those returned in this buffer. So the
+ * first time through this should contain all of the
+ * entries.
+ */
+ if (total_cnt == 0) {
+ total_cnt = SVAL(rparam, 6);
+ }
+
+ /* Keep track of how many we have read */
+ return_cnt += count;
+ p = rdata;
+
+ /* The last name in the previous NetServerEnum reply is
+ * sent back to server in the NetServerEnum3 request
+ * (last_entry). The next reply should repeat this entry
+ * as the first element. We have no proof that this is
+ * always true, but from traces that seems to be the
+ * behavior from Window Servers. So first lets do a lot
+ * of checking, just being paranoid. If the string
+ * matches then we already saw this entry so skip it.
+ *
+ * NOTE: sv1_name field must be null terminated and has
+ * a max size of 16 (NetBIOS Name).
+ */
+ if (last_entry && count && p &&
+ (strncmp(last_entry, p, 16) == 0)) {
+ count -= 1; /* Skip this entry */
+ return_cnt = -1; /* Not part of total, so don't count. */
+ p = rdata + 26; /* Skip the whole record */
+ }
+
+ for (i = 0; i < count; i++, p += 26) {
+ int comment_offset;
+ const char *cmnt;
+ const char *p1;
+ char *s1, *s2;
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint32_t entry_stype;
+
+ if (p + 26 > rdata_end) {
+ TALLOC_FREE(frame);
+ break;
+ }
+
+ sname = p;
+ comment_offset = (IVAL(p,22) & 0xFFFF)-converter;
+ cmnt = comment_offset?(rdata+comment_offset):"";
+
+ if (comment_offset < 0 || comment_offset >= (int)rdrcnt) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ /* Work out the comment length. */
+ for (p1 = cmnt, len = 0; *p1 &&
+ p1 < rdata_end; len++)
+ p1++;
+ if (!*p1) {
+ len++;
+ }
+
+ entry_stype = IVAL(p,18) & ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ pull_string_talloc(frame,rdata,0,
+ &s1,sname,16,STR_ASCII);
+ pull_string_talloc(frame,rdata,0,
+ &s2,cmnt,len,STR_ASCII);
+
+ if (!s1 || !s2) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ fn(s1, entry_stype, s2, state);
+ TALLOC_FREE(frame);
+ }
+
+ /* We are done with the old last entry, so now we can free it */
+ if (last_entry) {
+ SAFE_FREE(last_entry); /* This will set it to null */
+ }
+
+ /* We always make a copy of the last entry if we have one */
+ if (sname) {
+ last_entry = smb_xstrdup(sname);
+ }
+
+ /* If we have more data, but no last entry then error out */
+ if (!last_entry && (res == ERRmoredata)) {
+ errno = EINVAL;
+ res = 0;
+ }
+
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+ } while ((res == ERRmoredata) && (total_cnt > return_cnt));
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+ SAFE_FREE(last_entry);
+
+ if (res == -1) {
+ errno = cli_errno(cli);
+ } else {
+ if (!return_cnt) {
+ /* this is a very special case, when the domain master for the
+ work group isn't part of the work group itself, there is something
+ wild going on */
+ errno = ENOENT;
+ }
+ }
+
+ return(return_cnt > 0);
+}
+
+/****************************************************************************
+ Send a SamOEMChangePassword command.
+****************************************************************************/
+
+bool cli_oem_change_password(struct cli_state *cli, const char *user, const char *new_password,
+ const char *old_password)
+{
+ char param[1024];
+ unsigned char data[532];
+ char *p = param;
+ unsigned char old_pw_hash[16];
+ unsigned char new_pw_hash[16];
+ unsigned int data_len;
+ unsigned int param_len = 0;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ gnutls_cipher_hd_t cipher_hnd = NULL;
+ gnutls_datum_t old_pw_key = {
+ .data = old_pw_hash,
+ .size = sizeof(old_pw_hash),
+ };
+ int rc;
+
+ if (strlen(user) >= sizeof(fstring)-1) {
+ DEBUG(0,("cli_oem_change_password: user name %s is too long.\n", user));
+ return False;
+ }
+
+ SSVAL(p,0,214); /* SamOEMChangePassword command. */
+ p += 2;
+ strlcpy(p, "zsT", sizeof(param)-PTR_DIFF(p,param));
+ p = skip_string(param,sizeof(param),p);
+ strlcpy(p, "B516B16", sizeof(param)-PTR_DIFF(p,param));
+ p = skip_string(param,sizeof(param),p);
+ strlcpy(p,user, sizeof(param)-PTR_DIFF(p,param));
+ p = skip_string(param,sizeof(param),p);
+ SSVAL(p,0,532);
+ p += 2;
+
+ param_len = PTR_DIFF(p,param);
+
+ /*
+ * Get the Lanman hash of the old password, we
+ * use this as the key to make_oem_passwd_hash().
+ */
+ E_deshash(old_password, old_pw_hash);
+
+ encode_pw_buffer(data, new_password, STR_ASCII);
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("make_oem_passwd_hash\n"));
+ dump_data(100, data, 516);
+#endif
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &old_pw_key,
+ NULL);
+ if (rc < 0) {
+ DBG_ERR("gnutls_cipher_init failed: %s\n",
+ gnutls_strerror(rc));
+ return false;
+ }
+ rc = gnutls_cipher_encrypt(cipher_hnd,
+ data,
+ 516);
+ gnutls_cipher_deinit(cipher_hnd);
+ if (rc < 0) {
+ return false;
+ }
+
+ /*
+ * Now place the old password hash in the data.
+ */
+ E_deshash(new_password, new_pw_hash);
+
+ rc = E_old_pw_hash( new_pw_hash, old_pw_hash, (uchar *)&data[516]);
+ if (rc != 0) {
+ DBG_ERR("E_old_pw_hash failed: %s\n", gnutls_strerror(rc));
+ return false;
+ }
+
+ data_len = 532;
+
+ if (!cli_api(cli,
+ param, param_len, 4, /* param, length, max */
+ (char *)data, data_len, 0, /* data, length, max */
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ DEBUG(0,("cli_oem_change_password: Failed to send password change for user %s\n",
+ user ));
+ return False;
+ }
+
+ if (rdrcnt < 2) {
+ cli->rap_error = ERRbadformat;
+ goto done;
+ }
+
+ if (rparam) {
+ cli->rap_error = SVAL(rparam,0);
+ }
+
+done:
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return (cli->rap_error == 0);
+}
+
+static void prep_basic_information_buf(
+ uint8_t buf[40],
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint32_t attr)
+{
+ char *p = (char *)buf;
+ /*
+ * Add the create, last access, modification, and status change times
+ */
+ put_long_date_full_timespec(
+ TIMESTAMP_SET_NT_OR_BETTER, p, &create_time);
+ p += 8;
+
+ put_long_date_full_timespec(
+ TIMESTAMP_SET_NT_OR_BETTER, p, &access_time);
+ p += 8;
+
+ put_long_date_full_timespec(
+ TIMESTAMP_SET_NT_OR_BETTER, p, &write_time);
+ p += 8;
+
+ put_long_date_full_timespec(
+ TIMESTAMP_SET_NT_OR_BETTER, p, &change_time);
+ p += 8;
+
+ if (attr == (uint32_t)-1 || attr == FILE_ATTRIBUTE_NORMAL) {
+ /* No change. */
+ attr = 0;
+ } else if (attr == 0) {
+ /* Clear all existing attributes. */
+ attr = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* Add attributes */
+ SIVAL(p, 0, attr);
+
+ p += 4;
+
+ /* Add padding */
+ SIVAL(p, 0, 0);
+ p += 4;
+
+ SMB_ASSERT(PTR_DIFF(p, buf) == 40);
+}
+
+NTSTATUS cli_setpathinfo_ext(struct cli_state *cli, const char *fname,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint32_t attr)
+{
+ uint8_t buf[40];
+
+ prep_basic_information_buf(
+ buf,
+ create_time,
+ access_time,
+ write_time,
+ change_time,
+ attr);
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ DATA_BLOB in_data = data_blob_const(buf, sizeof(buf));
+ /*
+ * Split out SMB2 here as we need to select
+ * the correct info type and level.
+ */
+ return cli_smb2_setpathinfo(cli,
+ fname,
+ 1, /* SMB2_SETINFO_FILE */
+ SMB_FILE_BASIC_INFORMATION - 1000,
+ &in_data);
+ }
+
+ return cli_setpathinfo(
+ cli, SMB_FILE_BASIC_INFORMATION, fname, buf, sizeof(buf));
+}
+
+struct cli_setfileinfo_ext_state {
+ uint8_t data[40];
+ DATA_BLOB in_data;
+};
+
+static void cli_setfileinfo_ext_done(struct tevent_req *subreq);
+static void cli_setfileinfo_ext_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_setfileinfo_ext_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint32_t attr)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_setfileinfo_ext_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_setfileinfo_ext_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ prep_basic_information_buf(
+ state->data,
+ create_time,
+ access_time,
+ write_time,
+ change_time,
+ attr);
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ state->in_data = (DATA_BLOB) {
+ .data = state->data, .length = sizeof(state->data),
+ };
+
+ subreq = cli_smb2_set_info_fnum_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ SMB2_0_INFO_FILE,
+ SMB_FILE_BASIC_INFORMATION - 1000,
+ &state->in_data,
+ 0); /* in_additional_info */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_setfileinfo_ext_done2, req);
+ return req;
+ }
+
+ subreq = cli_setfileinfo_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ SMB_FILE_BASIC_INFORMATION,
+ state->data,
+ sizeof(state->data));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_setfileinfo_ext_done, req);
+ return req;
+}
+
+static void cli_setfileinfo_ext_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_setfileinfo_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_setfileinfo_ext_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_set_info_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_setfileinfo_ext_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_setfileinfo_ext(
+ struct cli_state *cli,
+ uint16_t fnum,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint32_t attr)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ frame = talloc_stackframe();
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_setfileinfo_ext_send(
+ ev,
+ ev,
+ cli,
+ fnum,
+ create_time,
+ access_time,
+ write_time,
+ change_time,
+ attr);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_setfileinfo_ext_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Send a qpathinfo call with the SMB_QUERY_FILE_ALL_INFO info level.
+****************************************************************************/
+
+struct cli_qpathinfo2_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *fname;
+ struct timespec create_time;
+ struct timespec access_time;
+ struct timespec write_time;
+ struct timespec change_time;
+ off_t size;
+ uint32_t attr;
+ SMB_INO_T ino;
+ mode_t mode;
+};
+
+static void cli_qpathinfo2_done2(struct tevent_req *subreq);
+static void cli_qpathinfo2_done(struct tevent_req *subreq);
+static void cli_qpathinfo2_got_reparse(struct tevent_req *subreq);
+
+struct tevent_req *cli_qpathinfo2_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_qpathinfo2_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_qpathinfo2_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fname = fname;
+
+ state->mode = S_IFREG;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_qpathinfo_send(state,
+ ev,
+ cli,
+ fname,
+ FSCC_FILE_ALL_INFORMATION,
+ 0x60,
+ UINT16_MAX);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qpathinfo2_done2, req);
+ return req;
+ }
+ subreq = cli_qpathinfo_send(state, ev, cli, fname,
+ SMB_QUERY_FILE_ALL_INFO,
+ 68, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qpathinfo2_done, req);
+ return req;
+}
+
+static void cli_qpathinfo2_done2(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_qpathinfo2_state *state =
+ tevent_req_data(req, struct cli_qpathinfo2_state);
+ uint8_t *rdata = NULL;
+ uint32_t num_rdata;
+ NTSTATUS status;
+
+ status = cli_smb2_qpathinfo_recv(subreq, state, &rdata, &num_rdata);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->create_time = interpret_long_date(BVAL(rdata, 0x0));
+ state->access_time = interpret_long_date(BVAL(rdata, 0x8));
+ state->write_time = interpret_long_date(BVAL(rdata, 0x10));
+ state->change_time = interpret_long_date(BVAL(rdata, 0x18));
+ state->attr = PULL_LE_U32(rdata, 0x20);
+ state->size = PULL_LE_U64(rdata, 0x30);
+ state->ino = PULL_LE_U64(rdata, 0x40);
+
+ if (state->attr & FILE_ATTRIBUTE_REPARSE_POINT) {
+ subreq = cli_get_reparse_data_send(state,
+ state->ev,
+ state->cli,
+ state->fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_qpathinfo2_got_reparse,
+ req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void cli_qpathinfo2_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qpathinfo2_state *state = tevent_req_data(
+ req, struct cli_qpathinfo2_state);
+ uint8_t *data = NULL;
+ uint32_t num_data;
+ NTSTATUS status;
+
+ status = cli_qpathinfo_recv(subreq, state, &data, &num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->create_time = interpret_long_date(BVAL(data, 0));
+ state->access_time = interpret_long_date(BVAL(data, 8));
+ state->write_time = interpret_long_date(BVAL(data, 16));
+ state->change_time = interpret_long_date(BVAL(data, 24));
+ state->attr = PULL_LE_U32(data, 32);
+ state->size = PULL_LE_U64(data, 48);
+
+ /*
+ * SMB1 qpathinfo2 uses SMB_QUERY_FILE_ALL_INFO which doesn't
+ * return an inode number (fileid). We can't change this to
+ * one of the FILE_ID info levels as only Win2003 and above
+ * support these [MS-SMB: 2.2.2.3.1] and the SMB1 code needs
+ * to support older servers.
+ */
+ state->ino = 0;
+
+ TALLOC_FREE(data);
+
+ if (state->attr & FILE_ATTRIBUTE_REPARSE_POINT) {
+ subreq = cli_get_reparse_data_send(state,
+ state->ev,
+ state->cli,
+ state->fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_qpathinfo2_got_reparse,
+ req);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void cli_qpathinfo2_got_reparse(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_qpathinfo2_state *state =
+ tevent_req_data(req, struct cli_qpathinfo2_state);
+ uint8_t *data = NULL;
+ uint32_t num_data;
+ struct reparse_data_buffer reparse = {
+ .tag = 0,
+ };
+ NTSTATUS status;
+
+ status = cli_get_reparse_data_recv(subreq, state, &data, &num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = reparse_data_buffer_parse(state, &reparse, data, num_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Ignoring unknown reparse data\n");
+ goto done;
+ }
+
+ switch (reparse.tag) {
+ case IO_REPARSE_TAG_SYMLINK:
+ state->mode = S_IFLNK;
+ break;
+ case IO_REPARSE_TAG_NFS:
+ switch (reparse.parsed.nfs.type) {
+ case NFS_SPECFILE_LNK:
+ state->mode = S_IFLNK;
+ break;
+ case NFS_SPECFILE_CHR:
+ state->mode = S_IFCHR;
+ break;
+ case NFS_SPECFILE_BLK:
+ state->mode = S_IFBLK;
+ break;
+ case NFS_SPECFILE_FIFO:
+ state->mode = S_IFIFO;
+ break;
+ case NFS_SPECFILE_SOCK:
+ state->mode = S_IFSOCK;
+ break;
+ }
+ break;
+ }
+done:
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_qpathinfo2_recv(struct tevent_req *req,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ off_t *size,
+ uint32_t *pattr,
+ SMB_INO_T *ino,
+ mode_t *mode)
+{
+ struct cli_qpathinfo2_state *state = tevent_req_data(
+ req, struct cli_qpathinfo2_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (create_time) {
+ *create_time = state->create_time;
+ }
+ if (access_time) {
+ *access_time = state->access_time;
+ }
+ if (write_time) {
+ *write_time = state->write_time;
+ }
+ if (change_time) {
+ *change_time = state->change_time;
+ }
+ if (pattr) {
+ *pattr = state->attr;
+ }
+ if (size) {
+ *size = state->size;
+ }
+ if (ino) {
+ *ino = state->ino;
+ }
+ if (mode != NULL) {
+ *mode = state->mode;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_qpathinfo2(struct cli_state *cli,
+ const char *fname,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ off_t *size,
+ uint32_t *pattr,
+ SMB_INO_T *ino,
+ mode_t *mode)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_qpathinfo2_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_qpathinfo2_recv(req,
+ create_time,
+ access_time,
+ write_time,
+ change_time,
+ size,
+ pattr,
+ ino,
+ mode);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Get the stream info
+****************************************************************************/
+
+struct cli_qpathinfo_streams_state {
+ uint32_t num_data;
+ uint8_t *data;
+};
+
+static void cli_qpathinfo_streams_done(struct tevent_req *subreq);
+static void cli_qpathinfo_streams_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_qpathinfo_streams_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_qpathinfo_streams_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_qpathinfo_streams_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_qpathinfo_send(state,
+ ev,
+ cli,
+ fname,
+ FSCC_FILE_STREAM_INFORMATION,
+ 0,
+ CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ cli_qpathinfo_streams_done2,
+ req);
+ return req;
+ }
+ subreq = cli_qpathinfo_send(state, ev, cli, fname,
+ SMB_FILE_STREAM_INFORMATION,
+ 0, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qpathinfo_streams_done, req);
+ return req;
+}
+
+static void cli_qpathinfo_streams_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qpathinfo_streams_state *state = tevent_req_data(
+ req, struct cli_qpathinfo_streams_state);
+ NTSTATUS status;
+
+ status = cli_qpathinfo_recv(subreq, state, &state->data,
+ &state->num_data);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_qpathinfo_streams_done2(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_qpathinfo_streams_state *state =
+ tevent_req_data(req, struct cli_qpathinfo_streams_state);
+ NTSTATUS status;
+
+ status = cli_smb2_qpathinfo_recv(subreq,
+ state,
+ &state->data,
+ &state->num_data);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_qpathinfo_streams_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct cli_qpathinfo_streams_state *state = tevent_req_data(
+ req, struct cli_qpathinfo_streams_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (!parse_streams_blob(mem_ctx, state->data, state->num_data,
+ pnum_streams, pstreams)) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_qpathinfo_streams(struct cli_state *cli, const char *fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_qpathinfo_streams_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_qpathinfo_streams_recv(req, mem_ctx, pnum_streams,
+ pstreams);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+bool parse_streams_blob(TALLOC_CTX *mem_ctx, const uint8_t *rdata,
+ size_t data_len,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ unsigned int num_streams;
+ struct stream_struct *streams;
+ unsigned int ofs;
+
+ num_streams = 0;
+ streams = NULL;
+ ofs = 0;
+
+ while ((data_len > ofs) && (data_len - ofs >= 24)) {
+ uint32_t nlen, len;
+ size_t size;
+ void *vstr;
+ struct stream_struct *tmp;
+ uint8_t *tmp_buf;
+
+ tmp = talloc_realloc(mem_ctx, streams,
+ struct stream_struct,
+ num_streams+1);
+
+ if (tmp == NULL) {
+ goto fail;
+ }
+ streams = tmp;
+
+ nlen = IVAL(rdata, ofs + 0x04);
+
+ streams[num_streams].size = IVAL_TO_SMB_OFF_T(
+ rdata, ofs + 0x08);
+ streams[num_streams].alloc_size = IVAL_TO_SMB_OFF_T(
+ rdata, ofs + 0x10);
+
+ if (nlen > data_len - (ofs + 24)) {
+ goto fail;
+ }
+
+ /*
+ * We need to null-terminate src, how do I do this with
+ * convert_string_talloc??
+ */
+
+ tmp_buf = talloc_array(streams, uint8_t, nlen+2);
+ if (tmp_buf == NULL) {
+ goto fail;
+ }
+
+ memcpy(tmp_buf, rdata+ofs+24, nlen);
+ tmp_buf[nlen] = 0;
+ tmp_buf[nlen+1] = 0;
+
+ if (!convert_string_talloc(streams, CH_UTF16, CH_UNIX, tmp_buf,
+ nlen+2, &vstr, &size))
+ {
+ TALLOC_FREE(tmp_buf);
+ goto fail;
+ }
+
+ TALLOC_FREE(tmp_buf);
+ streams[num_streams].name = (char *)vstr;
+ num_streams++;
+
+ len = IVAL(rdata, ofs);
+ if (len > data_len - ofs) {
+ goto fail;
+ }
+ if (len == 0) break;
+ ofs += len;
+ }
+
+ *pnum_streams = num_streams;
+ *pstreams = streams;
+ return true;
+
+ fail:
+ TALLOC_FREE(streams);
+ return false;
+}
+
+/****************************************************************************
+ Send a qfileinfo QUERY_FILE_NAME_INFO call.
+****************************************************************************/
+
+struct cli_qfileinfo_basic_state {
+ uint32_t attr;
+ off_t size;
+ struct timespec create_time;
+ struct timespec access_time;
+ struct timespec write_time;
+ struct timespec change_time;
+ SMB_INO_T ino;
+};
+
+static void cli_qfileinfo_basic_done(struct tevent_req *subreq);
+static void cli_qfileinfo_basic_doneE(struct tevent_req *subreq);
+static void cli_qfileinfo_basic_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_qfileinfo_basic_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_qfileinfo_basic_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_qfileinfo_basic_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if ((smbXcli_conn_protocol(cli->conn) < PROTOCOL_LANMAN2) ||
+ cli->win95) {
+ /*
+ * According to
+ * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cifs/3d9d8f3e-dc70-410d-a3fc-6f4a881e8cab
+ * SMB_COM_TRANSACTION2 used in cli_qfileinfo_send()
+ * further down was introduced with the LAN Manager
+ * 1.2 dialect, which we encode as PROTOCOL_LANMAN2.
+ *
+ * The "win95" check was introduced with commit
+ * 27e5850fd3e1c8 in 1998. Hard to check these days,
+ * but leave it in.
+ *
+ * Use a lowerlevel fallback in both cases.
+ */
+
+ subreq = cli_getattrE_send(state, ev, cli, fnum);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_qfileinfo_basic_doneE, req);
+ return req;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_query_info_fnum_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ fnum, /* fnum */
+ 1, /* in_info_type */
+ (SMB_FILE_ALL_INFORMATION - 1000), /* in_file_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0); /* in_flags */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_qfileinfo_basic_done2, req);
+ return req;
+ }
+
+ subreq = cli_qfileinfo_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ SMB_QUERY_FILE_ALL_INFO, /* level */
+ 68, /* min_rdata */
+ CLI_BUFFER_SIZE); /* max_rdata */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qfileinfo_basic_done, req);
+ return req;
+}
+
+static void cli_qfileinfo_basic_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qfileinfo_basic_state *state = tevent_req_data(
+ req, struct cli_qfileinfo_basic_state);
+ uint8_t *rdata;
+ uint32_t num_rdata;
+ NTSTATUS status;
+
+ status = cli_qfileinfo_recv(
+ subreq, state, NULL, &rdata, &num_rdata);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->create_time = interpret_long_date(BVAL(rdata, 0));
+ state->access_time = interpret_long_date(BVAL(rdata, 8));
+ state->write_time = interpret_long_date(BVAL(rdata, 16));
+ state->change_time = interpret_long_date(BVAL(rdata, 24));
+ state->attr = PULL_LE_U32(rdata, 32);
+ state->size = PULL_LE_U64(rdata,48);
+ state->ino = PULL_LE_U32(rdata, 64);
+ TALLOC_FREE(rdata);
+
+ tevent_req_done(req);
+}
+
+static void cli_qfileinfo_basic_doneE(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qfileinfo_basic_state *state = tevent_req_data(
+ req, struct cli_qfileinfo_basic_state);
+ NTSTATUS status;
+
+ status = cli_getattrE_recv(
+ subreq,
+ &state->attr,
+ &state->size,
+ &state->change_time.tv_sec,
+ &state->access_time.tv_sec,
+ &state->write_time.tv_sec);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_qfileinfo_basic_done2(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qfileinfo_basic_state *state = tevent_req_data(
+ req, struct cli_qfileinfo_basic_state);
+ DATA_BLOB outbuf = {0};
+ NTSTATUS status;
+
+ status = cli_smb2_query_info_fnum_recv(subreq, state, &outbuf);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* Parse the reply. */
+ if (outbuf.length < 0x60) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ state->create_time = interpret_long_date(BVAL(outbuf.data, 0x0));
+ state->access_time = interpret_long_date(BVAL(outbuf.data, 0x8));
+ state->write_time = interpret_long_date(BVAL(outbuf.data, 0x10));
+ state->change_time = interpret_long_date(BVAL(outbuf.data, 0x18));
+ state->attr = IVAL(outbuf.data, 0x20);
+ state->size = BVAL(outbuf.data, 0x30);
+ state->ino = BVAL(outbuf.data, 0x40);
+
+ data_blob_free(&outbuf);
+
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_qfileinfo_basic_recv(
+ struct tevent_req *req,
+ uint32_t *attr,
+ off_t *size,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ SMB_INO_T *ino)
+{
+ struct cli_qfileinfo_basic_state *state = tevent_req_data(
+ req, struct cli_qfileinfo_basic_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (create_time != NULL) {
+ *create_time = state->create_time;
+ }
+ if (access_time != NULL) {
+ *access_time = state->access_time;
+ }
+ if (write_time != NULL) {
+ *write_time = state->write_time;
+ }
+ if (change_time != NULL) {
+ *change_time = state->change_time;
+ }
+ if (attr != NULL) {
+ *attr = state->attr;
+ }
+ if (size != NULL) {
+ *size = state->size;
+ }
+ if (ino) {
+ *ino = state->ino;
+ }
+
+ return NT_STATUS_OK;
+}
+/****************************************************************************
+ Send a qfileinfo call.
+****************************************************************************/
+
+NTSTATUS cli_qfileinfo_basic(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t *attr,
+ off_t *size,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ SMB_INO_T *ino)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_qfileinfo_basic_send(frame, ev, cli, fnum);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_qfileinfo_basic_recv(
+ req,
+ attr,
+ size,
+ create_time,
+ access_time,
+ write_time,
+ change_time,
+ ino);
+
+ /* cli_smb2_query_info_fnum_recv doesn't set this */
+ cli->raw_status = status;
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Send a qpathinfo BASIC_INFO call.
+****************************************************************************/
+
+struct cli_qpathinfo_basic_state {
+ uint32_t num_data;
+ uint8_t *data;
+};
+
+static void cli_qpathinfo_basic_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_qpathinfo_basic_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_qpathinfo_basic_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_qpathinfo_basic_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = cli_qpathinfo_send(state, ev, cli, fname,
+ SMB_QUERY_FILE_BASIC_INFO,
+ 36, CLI_BUFFER_SIZE);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_qpathinfo_basic_done, req);
+ return req;
+}
+
+static void cli_qpathinfo_basic_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_qpathinfo_basic_state *state = tevent_req_data(
+ req, struct cli_qpathinfo_basic_state);
+ NTSTATUS status;
+
+ status = cli_qpathinfo_recv(subreq, state, &state->data,
+ &state->num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_qpathinfo_basic_recv(struct tevent_req *req,
+ SMB_STRUCT_STAT *sbuf, uint32_t *attributes)
+{
+ struct cli_qpathinfo_basic_state *state = tevent_req_data(
+ req, struct cli_qpathinfo_basic_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ sbuf->st_ex_btime = interpret_long_date(BVAL(state->data, 0));
+ sbuf->st_ex_atime = interpret_long_date(BVAL(state->data, 8));
+ sbuf->st_ex_mtime = interpret_long_date(BVAL(state->data, 16));
+ sbuf->st_ex_ctime = interpret_long_date(BVAL(state->data, 24));
+ *attributes = IVAL(state->data, 32);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_qpathinfo_basic(struct cli_state *cli, const char *name,
+ SMB_STRUCT_STAT *sbuf, uint32_t *attributes)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return cli_smb2_qpathinfo_basic(cli,
+ name,
+ sbuf,
+ attributes);
+ }
+
+ frame = talloc_stackframe();
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_qpathinfo_basic_send(frame, ev, cli, name);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_qpathinfo_basic_recv(req, sbuf, attributes);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Send a qpathinfo SMB_QUERY_FILE_ALT_NAME_INFO call.
+****************************************************************************/
+
+NTSTATUS cli_qpathinfo_alt_name(struct cli_state *cli, const char *fname, fstring alt_name)
+{
+ uint8_t *rdata;
+ uint32_t num_rdata;
+ unsigned int len;
+ char *converted = NULL;
+ size_t converted_size = 0;
+ NTSTATUS status;
+
+ status = cli_qpathinfo(talloc_tos(), cli, fname,
+ SMB_QUERY_FILE_ALT_NAME_INFO,
+ 4, CLI_BUFFER_SIZE, &rdata, &num_rdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ len = IVAL(rdata, 0);
+
+ if (len > num_rdata - 4) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ /* The returned data is a pushed string, not raw data. */
+ if (!convert_string_talloc(talloc_tos(),
+ smbXcli_conn_use_unicode(cli->conn) ? CH_UTF16LE : CH_DOS,
+ CH_UNIX,
+ rdata + 4,
+ len,
+ &converted,
+ &converted_size)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ fstrcpy(alt_name, converted);
+
+ TALLOC_FREE(converted);
+ TALLOC_FREE(rdata);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Send a qpathinfo SMB_QUERY_FILE_STANDARD_INFO call.
+****************************************************************************/
+
+NTSTATUS cli_qpathinfo_standard(struct cli_state *cli, const char *fname,
+ uint64_t *allocated, uint64_t *size,
+ uint32_t *nlinks,
+ bool *is_del_pending, bool *is_dir)
+{
+ uint8_t *rdata;
+ uint32_t num_rdata;
+ NTSTATUS status;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ status = cli_qpathinfo(talloc_tos(), cli, fname,
+ SMB_QUERY_FILE_STANDARD_INFO,
+ 24, CLI_BUFFER_SIZE, &rdata, &num_rdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (allocated) {
+ *allocated = BVAL(rdata, 0);
+ }
+
+ if (size) {
+ *size = BVAL(rdata, 8);
+ }
+
+ if (nlinks) {
+ *nlinks = IVAL(rdata, 16);
+ }
+
+ if (is_del_pending) {
+ *is_del_pending = CVAL(rdata, 20);
+ }
+
+ if (is_dir) {
+ *is_dir = CVAL(rdata, 20);
+ }
+
+ TALLOC_FREE(rdata);
+
+ return NT_STATUS_OK;
+}
+
+
+/* like cli_qpathinfo2 but do not use SMB_QUERY_FILE_ALL_INFO with smb1 */
+NTSTATUS cli_qpathinfo3(struct cli_state *cli, const char *fname,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ off_t *size, uint32_t *pattr,
+ SMB_INO_T *ino)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ SMB_STRUCT_STAT st = { 0 };
+ uint32_t attr = 0;
+ uint64_t pos;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ /*
+ * NB. cli_qpathinfo2() checks pattr is valid before
+ * storing a value into it, so we don't need to use
+ * an intermediate attr variable as below but can
+ * pass pattr directly.
+ */
+ return cli_qpathinfo2(cli,
+ fname,
+ create_time,
+ access_time,
+ write_time,
+ change_time,
+ size,
+ pattr,
+ ino,
+ NULL);
+ }
+
+ if (create_time || access_time || write_time || change_time || pattr) {
+ /*
+ * cli_qpathinfo_basic() always indirects the passed
+ * in pointers so we use intermediate variables to
+ * collect all of them before assigning any requested
+ * below.
+ */
+ status = cli_qpathinfo_basic(cli, fname, &st, &attr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (size) {
+ status = cli_qpathinfo_standard(cli, fname,
+ NULL, &pos, NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *size = pos;
+ }
+
+ if (create_time) {
+ *create_time = st.st_ex_btime;
+ }
+ if (access_time) {
+ *access_time = st.st_ex_atime;
+ }
+ if (write_time) {
+ *write_time = st.st_ex_mtime;
+ }
+ if (change_time) {
+ *change_time = st.st_ex_ctime;
+ }
+ if (pattr) {
+ *pattr = attr;
+ }
+ if (ino) {
+ *ino = 0;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/clirap.h b/source3/libsmb/clirap.h
new file mode 100644
index 0000000..4dc0441
--- /dev/null
+++ b/source3/libsmb/clirap.h
@@ -0,0 +1,206 @@
+/*
+ Samba Unix/Linux SMB client library
+ More client RAP (SMB Remote Procedure Calls) functions
+ Copyright (C) 2001 Steve French (sfrench@us.ibm.com)
+ Copyright (C) 2001 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2007 Jeremy Allison. jra@samba.org
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Gerald (Jerry) Carter 2004
+ Copyright (C) James Peach 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBSMB_CLIRAP_H
+#define _LIBSMB_CLIRAP_H
+
+struct cli_state;
+
+/* The following definitions come from libsmb/clirap.c */
+
+bool cli_api(struct cli_state *cli,
+ char *param, int prcnt, int mprcnt,
+ char *data, int drcnt, int mdrcnt,
+ char **rparam, unsigned int *rprcnt,
+ char **rdata, unsigned int *rdrcnt);
+int cli_RNetShareEnum(struct cli_state *cli, void (*fn)(const char *, uint32_t, const char *, void *), void *state);
+bool cli_NetServerEnum(struct cli_state *cli, char *workgroup, uint32_t stype,
+ void (*fn)(const char *, uint32_t, const char *, void *),
+ void *state);
+bool cli_oem_change_password(struct cli_state *cli, const char *user, const char *new_password,
+ const char *old_password);
+NTSTATUS cli_setpathinfo_ext(struct cli_state *cli, const char *fname,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint32_t attr);
+struct tevent_req *cli_setfileinfo_ext_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint32_t attr);
+NTSTATUS cli_setfileinfo_ext_recv(struct tevent_req *req);
+NTSTATUS cli_setfileinfo_ext(
+ struct cli_state *cli,
+ uint16_t fnum,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint32_t attr);
+struct tevent_req *cli_qpathinfo2_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_qpathinfo2_recv(struct tevent_req *req,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ off_t *size,
+ uint32_t *pattr,
+ SMB_INO_T *ino,
+ mode_t *mode);
+NTSTATUS cli_qpathinfo2(struct cli_state *cli,
+ const char *fname,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ off_t *size,
+ uint32_t *pattr,
+ SMB_INO_T *ino,
+ mode_t *mode);
+NTSTATUS cli_qpathinfo3(struct cli_state *cli, const char *fname,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ off_t *size, uint32_t *pattr,
+ SMB_INO_T *ino);
+struct tevent_req *cli_qpathinfo_streams_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_qpathinfo_streams_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams);
+NTSTATUS cli_qpathinfo_streams(struct cli_state *cli, const char *fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams);
+bool parse_streams_blob(TALLOC_CTX *mem_ctx, const uint8_t *rdata,
+ size_t data_len,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams);
+struct tevent_req *cli_qfileinfo_basic_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum);
+NTSTATUS cli_qfileinfo_basic_recv(
+ struct tevent_req *req,
+ uint32_t *attr,
+ off_t *size,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ SMB_INO_T *ino);
+NTSTATUS cli_qfileinfo_basic(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t *attr,
+ off_t *size,
+ struct timespec *create_time,
+ struct timespec *access_time,
+ struct timespec *write_time,
+ struct timespec *change_time,
+ SMB_INO_T *ino);
+struct tevent_req *cli_qpathinfo_basic_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_qpathinfo_basic_recv(struct tevent_req *req,
+ SMB_STRUCT_STAT *sbuf, uint32_t *attributes);
+NTSTATUS cli_qpathinfo_basic(struct cli_state *cli, const char *name,
+ SMB_STRUCT_STAT *sbuf, uint32_t *attributes);
+NTSTATUS cli_qpathinfo_standard(struct cli_state *cli, const char *fname,
+ uint64_t *allocated, uint64_t *size,
+ uint32_t *nlinks,
+ bool *is_del_pending, bool *is_dir);
+NTSTATUS cli_qpathinfo_alt_name(struct cli_state *cli, const char *fname, fstring alt_name);
+struct tevent_req *cli_qpathinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, const char *fname,
+ uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata);
+NTSTATUS cli_qpathinfo_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **rdata, uint32_t *num_rdata);
+NTSTATUS cli_qpathinfo(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ const char *fname, uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata,
+ uint8_t **rdata, uint32_t *num_rdata);
+
+struct tevent_req *cli_qfileinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata);
+NTSTATUS cli_qfileinfo_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint16_t *recv_flags2,
+ uint8_t **rdata, uint32_t *num_rdata);
+NTSTATUS cli_qfileinfo(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint16_t fnum, uint16_t level, uint32_t min_rdata,
+ uint32_t max_rdata, uint16_t *recv_flags2,
+ uint8_t **rdata, uint32_t *num_rdata);
+
+struct tevent_req *cli_flush_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum);
+NTSTATUS cli_flush_recv(struct tevent_req *req);
+NTSTATUS cli_flush(TALLOC_CTX *mem_ctx, struct cli_state *cli, uint16_t fnum);
+
+struct tevent_req *cli_shadow_copy_data_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool get_names);
+NTSTATUS cli_shadow_copy_data_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char ***pnames, int *pnum_names);
+NTSTATUS cli_shadow_copy_data(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint16_t fnum, bool get_names,
+ char ***pnames, int *pnum_names);
+
+struct tevent_req *cli_fsctl_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t ctl_code,
+ const DATA_BLOB *in,
+ uint32_t max_out);
+NTSTATUS cli_fsctl_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, DATA_BLOB *out);
+
+
+#endif /* _LIBSMB_CLIRAP_H */
diff --git a/source3/libsmb/clireadwrite.c b/source3/libsmb/clireadwrite.c
new file mode 100644
index 0000000..1641905
--- /dev/null
+++ b/source3/libsmb/clireadwrite.c
@@ -0,0 +1,1907 @@
+/*
+ Unix SMB/CIFS implementation.
+ client file read/write routines
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "trans2.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/****************************************************************************
+ Calculate the recommended read buffer size
+****************************************************************************/
+static size_t cli_read_max_bufsize(struct cli_state *cli)
+{
+ uint8_t wct = 12;
+ uint32_t min_space;
+ uint32_t data_offset;
+ uint32_t useable_space = 0;
+
+ data_offset = HDR_VWV;
+ data_offset += wct * sizeof(uint16_t);
+ data_offset += sizeof(uint16_t); /* byte count */
+ data_offset += 1; /* pad */
+
+ min_space = cli_state_available_size(cli, data_offset);
+
+ if (cli->server_posix_capabilities & CIFS_UNIX_LARGE_READ_CAP) {
+ useable_space = 0xFFFFFF - data_offset;
+
+ if (smb1cli_conn_signing_is_active(cli->conn)) {
+ return min_space;
+ }
+
+ if (smb1cli_conn_encryption_on(cli->conn)) {
+ return min_space;
+ }
+
+ return useable_space;
+ } else if (smb1cli_conn_capabilities(cli->conn) & CAP_LARGE_READX) {
+ /*
+ * Note: CAP_LARGE_READX also works with signing
+ */
+ useable_space = 0x1FFFF - data_offset;
+
+ useable_space = MIN(useable_space, UINT16_MAX);
+
+ return useable_space;
+ }
+
+ return min_space;
+}
+
+/****************************************************************************
+ Calculate the recommended write buffer size
+****************************************************************************/
+static size_t cli_write_max_bufsize(struct cli_state *cli,
+ uint16_t write_mode,
+ uint8_t wct)
+{
+ uint32_t min_space;
+ uint32_t data_offset;
+ uint32_t useable_space = 0;
+
+ data_offset = HDR_VWV;
+ data_offset += wct * sizeof(uint16_t);
+ data_offset += sizeof(uint16_t); /* byte count */
+ data_offset += 1; /* pad */
+
+ min_space = cli_state_available_size(cli, data_offset);
+
+ if (cli->server_posix_capabilities & CIFS_UNIX_LARGE_WRITE_CAP) {
+ useable_space = 0xFFFFFF - data_offset;
+ } else if (smb1cli_conn_capabilities(cli->conn) & CAP_LARGE_WRITEX) {
+ useable_space = 0x1FFFF - data_offset;
+ } else {
+ return min_space;
+ }
+
+ if (write_mode != 0) {
+ return min_space;
+ }
+
+ if (smb1cli_conn_signing_is_active(cli->conn)) {
+ return min_space;
+ }
+
+ if (smb1cli_conn_encryption_on(cli->conn)) {
+ return min_space;
+ }
+
+ if (strequal(cli->dev, "LPT1:")) {
+ return min_space;
+ }
+
+ return useable_space;
+}
+
+struct cli_read_andx_state {
+ size_t size;
+ uint16_t vwv[12];
+ NTSTATUS status;
+ size_t received;
+ uint8_t *buf;
+};
+
+static void cli_read_andx_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_read_andx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ off_t offset, size_t size,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_read_andx_state *state;
+ uint8_t wct = 10;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_read_andx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->size = size;
+
+ SCVAL(state->vwv + 0, 0, 0xFF);
+ SCVAL(state->vwv + 0, 1, 0);
+ SSVAL(state->vwv + 1, 0, 0);
+ SSVAL(state->vwv + 2, 0, fnum);
+ SIVAL(state->vwv + 3, 0, offset);
+ SSVAL(state->vwv + 5, 0, size);
+ SSVAL(state->vwv + 6, 0, size);
+ SSVAL(state->vwv + 7, 0, (size >> 16));
+ SSVAL(state->vwv + 8, 0, 0);
+ SSVAL(state->vwv + 9, 0, 0);
+
+ if (smb1cli_conn_capabilities(cli->conn) & CAP_LARGE_FILES) {
+ SIVAL(state->vwv + 10, 0,
+ (((uint64_t)offset)>>32) & 0xffffffff);
+ wct = 12;
+ } else {
+ if ((((uint64_t)offset) & 0xffffffff00000000LL) != 0) {
+ DEBUG(10, ("cli_read_andx_send got large offset where "
+ "the server does not support it\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBreadX, 0, 0, wct,
+ state->vwv, 0, NULL);
+ if (subreq == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ tevent_req_set_callback(subreq, cli_read_andx_done, req);
+ *psmbreq = subreq;
+ return req;
+}
+
+struct tevent_req *cli_read_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ off_t offset, size_t size)
+{
+ struct tevent_req *req, *subreq;
+ NTSTATUS status;
+
+ req = cli_read_andx_create(mem_ctx, ev, cli, fnum, offset, size,
+ &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void cli_read_andx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_read_andx_state *state = tevent_req_data(
+ req, struct cli_read_andx_state);
+ uint8_t *inbuf;
+ uint8_t wct;
+ uint16_t *vwv;
+ uint32_t num_bytes;
+ uint8_t *bytes;
+
+ state->status = cli_smb_recv(subreq, state, &inbuf, 12, &wct, &vwv,
+ &num_bytes, &bytes);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_IS_ERR(state->status)) {
+ tevent_req_nterror(req, state->status);
+ return;
+ }
+
+ /* size is the number of bytes the server returned.
+ * Might be zero. */
+ state->received = SVAL(vwv + 5, 0);
+ state->received |= (((unsigned int)SVAL(vwv + 7, 0)) << 16);
+
+ if (state->received > state->size) {
+ DEBUG(5,("server returned more than we wanted!\n"));
+ tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR);
+ return;
+ }
+
+ /*
+ * bcc field must be valid for small reads, for large reads the 16-bit
+ * bcc field can't be correct.
+ */
+
+ if ((state->received < 0xffff) && (state->received > num_bytes)) {
+ DEBUG(5, ("server announced more bytes than sent\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ state->buf = discard_const_p(uint8_t, smb_base(inbuf)) + SVAL(vwv+6, 0);
+
+ if (smb_buffer_oob(smb_len_tcp(inbuf), SVAL(vwv+6, 0), state->received)
+ || ((state->received != 0) && (state->buf < bytes))) {
+ DEBUG(5, ("server returned invalid read&x data offset\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+/*
+ * Pull the data out of a finished async read_and_x request. rcvbuf is
+ * talloced from the request, so better make sure that you copy it away before
+ * you talloc_free(req). "rcvbuf" is NOT a talloc_ctx of its own, so do not
+ * talloc_move it!
+ */
+
+NTSTATUS cli_read_andx_recv(struct tevent_req *req, ssize_t *received,
+ uint8_t **rcvbuf)
+{
+ struct cli_read_andx_state *state = tevent_req_data(
+ req, struct cli_read_andx_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *received = state->received;
+ *rcvbuf = state->buf;
+ return NT_STATUS_OK;
+}
+
+struct cli_pull_chunk;
+
+struct cli_pull_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+ off_t start_offset;
+ off_t size;
+
+ NTSTATUS (*sink)(char *buf, size_t n, void *priv);
+ void *priv;
+
+ size_t chunk_size;
+ off_t next_offset;
+ off_t remaining;
+
+ /*
+ * How many bytes did we push into "sink"?
+ */
+ off_t pushed;
+
+ /*
+ * Outstanding requests
+ *
+ * The maximum is 256:
+ * - which would be a window of 256 MByte
+ * for SMB2 with multi-credit
+ * or smb1 unix extensions.
+ */
+ uint16_t max_chunks;
+ uint16_t num_chunks;
+ uint16_t num_waiting;
+ struct cli_pull_chunk *chunks;
+};
+
+struct cli_pull_chunk {
+ struct cli_pull_chunk *prev, *next;
+ struct tevent_req *req;/* This is the main request! Not the subreq */
+ struct tevent_req *subreq;
+ off_t ofs;
+ uint8_t *buf;
+ size_t total_size;
+ size_t tmp_size;
+ bool done;
+};
+
+static void cli_pull_setup_chunks(struct tevent_req *req);
+static void cli_pull_chunk_ship(struct cli_pull_chunk *chunk);
+static void cli_pull_chunk_done(struct tevent_req *subreq);
+
+/*
+ * Parallel read support.
+ *
+ * cli_pull sends as many read&x requests as the server would allow via
+ * max_mux at a time. When replies flow back in, the data is written into
+ * the callback function "sink" in the right order.
+ */
+
+struct tevent_req *cli_pull_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum, off_t start_offset,
+ off_t size, size_t window_size,
+ NTSTATUS (*sink)(char *buf, size_t n,
+ void *priv),
+ void *priv)
+{
+ struct tevent_req *req;
+ struct cli_pull_state *state;
+ size_t page_size = 1024;
+ uint64_t tmp64;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_pull_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->ev = ev;
+ state->fnum = fnum;
+ state->start_offset = start_offset;
+ state->size = size;
+ state->sink = sink;
+ state->priv = priv;
+ state->next_offset = start_offset;
+ state->remaining = size;
+
+ if (size == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ state->chunk_size = smb2cli_conn_max_read_size(cli->conn);
+ } else {
+ state->chunk_size = cli_read_max_bufsize(cli);
+ }
+ if (state->chunk_size > page_size) {
+ state->chunk_size &= ~(page_size - 1);
+ }
+
+ if (window_size == 0) {
+ /*
+ * We use 16 MByte as default window size.
+ */
+ window_size = 16 * 1024 * 1024;
+ }
+
+ tmp64 = window_size/state->chunk_size;
+ if ((window_size % state->chunk_size) > 0) {
+ tmp64 += 1;
+ }
+ tmp64 = MAX(tmp64, 1);
+ tmp64 = MIN(tmp64, 256);
+ state->max_chunks = tmp64;
+
+ /*
+ * We defer the callback because of the complex
+ * substate/subfunction logic
+ */
+ tevent_req_defer_callback(req, ev);
+
+ cli_pull_setup_chunks(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void cli_pull_setup_chunks(struct tevent_req *req)
+{
+ struct cli_pull_state *state =
+ tevent_req_data(req,
+ struct cli_pull_state);
+ struct cli_pull_chunk *chunk, *next = NULL;
+ size_t i;
+
+ for (chunk = state->chunks; chunk; chunk = next) {
+ /*
+ * Note that chunk might be removed from this call.
+ */
+ next = chunk->next;
+ cli_pull_chunk_ship(chunk);
+ if (!tevent_req_is_in_progress(req)) {
+ return;
+ }
+ }
+
+ for (i = state->num_chunks; i < state->max_chunks; i++) {
+
+ if (state->num_waiting > 0) {
+ return;
+ }
+
+ if (state->remaining == 0) {
+ break;
+ }
+
+ chunk = talloc_zero(state, struct cli_pull_chunk);
+ if (tevent_req_nomem(chunk, req)) {
+ return;
+ }
+ chunk->req = req;
+ chunk->ofs = state->next_offset;
+ chunk->total_size = MIN(state->remaining, state->chunk_size);
+ state->next_offset += chunk->total_size;
+ state->remaining -= chunk->total_size;
+
+ DLIST_ADD_END(state->chunks, chunk);
+ state->num_chunks++;
+ state->num_waiting++;
+
+ cli_pull_chunk_ship(chunk);
+ if (!tevent_req_is_in_progress(req)) {
+ return;
+ }
+ }
+
+ if (state->remaining > 0) {
+ return;
+ }
+
+ if (state->num_chunks > 0) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void cli_pull_chunk_ship(struct cli_pull_chunk *chunk)
+{
+ struct tevent_req *req = chunk->req;
+ struct cli_pull_state *state =
+ tevent_req_data(req,
+ struct cli_pull_state);
+ bool ok;
+ off_t ofs;
+ size_t size;
+
+ if (chunk->done) {
+ NTSTATUS status;
+
+ if (chunk != state->chunks) {
+ /*
+ * this chunk is not the
+ * first one in the list.
+ *
+ * which means we should not
+ * push it into the sink yet.
+ */
+ return;
+ }
+
+ if (chunk->tmp_size == 0) {
+ /*
+ * we got a short read, we're done
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ status = state->sink((char *)chunk->buf,
+ chunk->tmp_size,
+ state->priv);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->pushed += chunk->tmp_size;
+
+ if (chunk->tmp_size < chunk->total_size) {
+ /*
+ * we got a short read, we're done
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ DLIST_REMOVE(state->chunks, chunk);
+ SMB_ASSERT(state->num_chunks > 0);
+ state->num_chunks--;
+ TALLOC_FREE(chunk);
+
+ return;
+ }
+
+ if (chunk->subreq != NULL) {
+ return;
+ }
+
+ SMB_ASSERT(state->num_waiting > 0);
+
+ ofs = chunk->ofs + chunk->tmp_size;
+ size = chunk->total_size - chunk->tmp_size;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ uint32_t max_size;
+
+ ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+ if (!ok) {
+ return;
+ }
+
+ /*
+ * downgrade depending on the available credits
+ */
+ size = MIN(max_size, size);
+
+ chunk->subreq = cli_smb2_read_send(chunk,
+ state->ev,
+ state->cli,
+ state->fnum,
+ ofs,
+ size);
+ if (tevent_req_nomem(chunk->subreq, req)) {
+ return;
+ }
+ } else {
+ ok = smb1cli_conn_req_possible(state->cli->conn);
+ if (!ok) {
+ return;
+ }
+
+ chunk->subreq = cli_read_andx_send(chunk,
+ state->ev,
+ state->cli,
+ state->fnum,
+ ofs,
+ size);
+ if (tevent_req_nomem(chunk->subreq, req)) {
+ return;
+ }
+ }
+ tevent_req_set_callback(chunk->subreq,
+ cli_pull_chunk_done,
+ chunk);
+
+ state->num_waiting--;
+ return;
+}
+
+static void cli_pull_chunk_done(struct tevent_req *subreq)
+{
+ struct cli_pull_chunk *chunk =
+ tevent_req_callback_data(subreq,
+ struct cli_pull_chunk);
+ struct tevent_req *req = chunk->req;
+ struct cli_pull_state *state =
+ tevent_req_data(req,
+ struct cli_pull_state);
+ NTSTATUS status;
+ size_t expected = chunk->total_size - chunk->tmp_size;
+ ssize_t received = 0;
+ uint8_t *buf = NULL;
+
+ chunk->subreq = NULL;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = cli_smb2_read_recv(subreq, &received, &buf);
+ } else {
+ status = cli_read_andx_recv(subreq, &received, &buf);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) {
+ received = 0;
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (received > expected) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (received == 0) {
+ /*
+ * We got EOF we're done
+ */
+ chunk->done = true;
+ cli_pull_setup_chunks(req);
+ return;
+ }
+
+ if (received == chunk->total_size) {
+ /*
+ * We got it in the first run.
+ *
+ * We don't call TALLOC_FREE(subreq)
+ * here and keep the returned buffer.
+ */
+ chunk->buf = buf;
+ } else if (chunk->buf == NULL) {
+ chunk->buf = talloc_array(chunk, uint8_t, chunk->total_size);
+ if (tevent_req_nomem(chunk->buf, req)) {
+ return;
+ }
+ }
+
+ if (received != chunk->total_size) {
+ uint8_t *p = chunk->buf + chunk->tmp_size;
+ memcpy(p, buf, received);
+ TALLOC_FREE(subreq);
+ }
+
+ chunk->tmp_size += received;
+
+ if (chunk->tmp_size == chunk->total_size) {
+ chunk->done = true;
+ } else {
+ state->num_waiting++;
+ }
+
+ cli_pull_setup_chunks(req);
+}
+
+NTSTATUS cli_pull_recv(struct tevent_req *req, off_t *received)
+{
+ struct cli_pull_state *state = tevent_req_data(
+ req, struct cli_pull_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *received = state->pushed;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_pull(struct cli_state *cli, uint16_t fnum,
+ off_t start_offset, off_t size, size_t window_size,
+ NTSTATUS (*sink)(char *buf, size_t n, void *priv),
+ void *priv, off_t *received)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_pull_send(frame, ev, cli, fnum, start_offset, size,
+ window_size, sink, priv);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_pull_recv(req, received);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_read_state {
+ struct cli_state *cli;
+ char *buf;
+ size_t buflen;
+ size_t received;
+};
+
+static void cli_read_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ char *buf,
+ off_t offset,
+ size_t size)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->buf = buf;
+ state->buflen = size;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ uint32_t max_size;
+ bool ok;
+
+ ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+ if (!ok) {
+ tevent_req_nterror(
+ req,
+ NT_STATUS_INSUFFICIENT_RESOURCES);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * downgrade depending on the available credits
+ */
+ size = MIN(max_size, size);
+
+ subreq = cli_smb2_read_send(
+ state, ev, cli, fnum, offset, size);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ bool ok;
+ ok = smb1cli_conn_req_possible(state->cli->conn);
+ if (!ok) {
+ tevent_req_nterror(
+ req,
+ NT_STATUS_INSUFFICIENT_RESOURCES);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_read_andx_send(
+ state, ev, cli, fnum, offset, size);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ tevent_req_set_callback(subreq, cli_read_done, req);
+
+ return req;
+}
+
+static void cli_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_read_state *state = tevent_req_data(
+ req, struct cli_read_state);
+ NTSTATUS status;
+ ssize_t received;
+ uint8_t *buf = NULL;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = cli_smb2_read_recv(subreq, &received, &buf);
+ } else {
+ status = cli_read_andx_recv(subreq, &received, &buf);
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) {
+ received = 0;
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if ((buf == NULL) || (received < 0) || (received > state->buflen)) {
+ state->received = 0;
+ tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR);
+ return;
+ }
+
+ memcpy(state->buf, buf, received);
+ state->received = received;
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_read_recv(struct tevent_req *req, size_t *received)
+{
+ struct cli_read_state *state = tevent_req_data(
+ req, struct cli_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (received != NULL) {
+ *received = state->received;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Helper function for cli_pull(). This takes a chunk of data (buf) read from
+ * a remote file and copies it into the return buffer (priv).
+ */
+NTSTATUS cli_read_sink(char *buf, size_t n, void *priv)
+{
+ char **pbuf = (char **)priv;
+ memcpy(*pbuf, buf, n);
+ *pbuf += n;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_read(struct cli_state *cli, uint16_t fnum,
+ char *buf, off_t offset, size_t size,
+ size_t *nread)
+{
+ NTSTATUS status;
+ off_t ret = 0;
+
+ status = cli_pull(cli, fnum, offset, size, size,
+ cli_read_sink, &buf, &ret);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (nread) {
+ *nread = ret;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ write to a file using a SMBwrite and not bypassing 0 byte writes
+****************************************************************************/
+
+NTSTATUS cli_smbwrite(struct cli_state *cli, uint16_t fnum, char *buf,
+ off_t offset, size_t size1, size_t *ptotal)
+{
+ uint8_t *bytes;
+ ssize_t total = 0;
+
+ /*
+ * 3 bytes prefix
+ */
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 3);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 1;
+
+ do {
+ uint32_t usable_space = cli_state_available_size(cli, 48);
+ size_t size = MIN(size1, usable_space);
+ struct tevent_req *req;
+ uint16_t vwv[5];
+ uint16_t *ret_vwv;
+ NTSTATUS status;
+
+ SSVAL(vwv+0, 0, fnum);
+ SSVAL(vwv+1, 0, size);
+ SIVAL(vwv+2, 0, offset);
+ SSVAL(vwv+4, 0, 0);
+
+ bytes = talloc_realloc(talloc_tos(), bytes, uint8_t,
+ size+3);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SSVAL(bytes, 1, size);
+ memcpy(bytes + 3, buf + total, size);
+
+ status = cli_smb(talloc_tos(), cli, SMBwrite, 0, 5, vwv,
+ size+3, bytes, &req, 1, NULL, &ret_vwv,
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(bytes);
+ return status;
+ }
+
+ size = SVAL(ret_vwv+0, 0);
+ TALLOC_FREE(req);
+ if (size == 0) {
+ break;
+ }
+ size1 -= size;
+ total += size;
+ offset += size;
+
+ } while (size1);
+
+ TALLOC_FREE(bytes);
+
+ if (ptotal != NULL) {
+ *ptotal = total;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Send a write&x request
+ */
+
+struct cli_write_andx_state {
+ size_t size;
+ uint16_t vwv[14];
+ size_t written;
+ uint8_t pad;
+ struct iovec iov[2];
+};
+
+static void cli_write_andx_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_write_andx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t mode, const uint8_t *buf,
+ off_t offset, size_t size,
+ struct tevent_req **reqs_before,
+ int num_reqs_before,
+ struct tevent_req **psmbreq)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_write_andx_state *state;
+ bool bigoffset = ((smb1cli_conn_capabilities(cli->conn) & CAP_LARGE_FILES) != 0);
+ uint8_t wct = bigoffset ? 14 : 12;
+ size_t max_write = cli_write_max_bufsize(cli, mode, wct);
+ uint16_t *vwv;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_write_andx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->size = MIN(size, max_write);
+
+ vwv = state->vwv;
+
+ SCVAL(vwv+0, 0, 0xFF);
+ SCVAL(vwv+0, 1, 0);
+ SSVAL(vwv+1, 0, 0);
+ SSVAL(vwv+2, 0, fnum);
+ SIVAL(vwv+3, 0, offset);
+ SIVAL(vwv+5, 0, 0);
+ SSVAL(vwv+7, 0, mode);
+ SSVAL(vwv+8, 0, 0);
+ SSVAL(vwv+9, 0, (state->size>>16));
+ SSVAL(vwv+10, 0, state->size);
+
+ SSVAL(vwv+11, 0,
+ smb1cli_req_wct_ofs(reqs_before, num_reqs_before)
+ + 1 /* the wct field */
+ + wct * 2 /* vwv */
+ + 2 /* num_bytes field */
+ + 1 /* pad */);
+
+ if (bigoffset) {
+ SIVAL(vwv+12, 0, (((uint64_t)offset)>>32) & 0xffffffff);
+ }
+
+ state->pad = 0;
+ state->iov[0].iov_base = (void *)&state->pad;
+ state->iov[0].iov_len = 1;
+ state->iov[1].iov_base = discard_const_p(void, buf);
+ state->iov[1].iov_len = state->size;
+
+ subreq = cli_smb_req_create(state, ev, cli, SMBwriteX, 0, 0, wct, vwv,
+ 2, state->iov);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_write_andx_done, req);
+ *psmbreq = subreq;
+ return req;
+}
+
+struct tevent_req *cli_write_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t mode, const uint8_t *buf,
+ off_t offset, size_t size)
+{
+ struct tevent_req *req, *subreq;
+ NTSTATUS status;
+
+ req = cli_write_andx_create(mem_ctx, ev, cli, fnum, mode, buf, offset,
+ size, NULL, 0, &subreq);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void cli_write_andx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_write_andx_state *state = tevent_req_data(
+ req, struct cli_write_andx_state);
+ uint8_t wct;
+ uint16_t *vwv;
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq, state, NULL, 6, &wct, &vwv,
+ NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_IS_ERR(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ state->written = SVAL(vwv+2, 0);
+ if (state->size > UINT16_MAX) {
+ /*
+ * It is important that we only set the
+ * high bits only if we asked for a large write.
+ *
+ * OS/2 print shares get this wrong and may send
+ * invalid values.
+ *
+ * See bug #5326.
+ */
+ state->written |= SVAL(vwv+4, 0)<<16;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_write_andx_recv(struct tevent_req *req, size_t *pwritten)
+{
+ struct cli_write_andx_state *state = tevent_req_data(
+ req, struct cli_write_andx_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (pwritten != 0) {
+ *pwritten = state->written;
+ }
+ return NT_STATUS_OK;
+}
+
+struct cli_write_state {
+ struct cli_state *cli;
+ size_t written;
+};
+
+static void cli_write_done(struct tevent_req *subreq);
+
+/*
+ * Used to write to a file remotely.
+ * This is similar in functionality to cli_push_send(), except this is a more
+ * finer-grain API. For example, if the data we want to write exceeds the max
+ * write size of the underlying connection, then it's the caller's
+ * responsibility to handle this.
+ * For writing a small amount of data to file, this is a simpler API to use.
+ */
+struct tevent_req *cli_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t mode, const uint8_t *buf,
+ off_t offset, size_t size)
+{
+ struct tevent_req *req = NULL;
+ struct cli_write_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ uint32_t max_size;
+ bool ok;
+
+ ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+ if (!ok) {
+ tevent_req_nterror(
+ req,
+ NT_STATUS_INSUFFICIENT_RESOURCES);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * downgrade depending on the available credits
+ */
+ size = MIN(max_size, size);
+
+ subreq = cli_smb2_write_send(state,
+ ev,
+ cli,
+ fnum,
+ mode,
+ buf,
+ offset,
+ size);
+ } else {
+ bool ok;
+
+ ok = smb1cli_conn_req_possible(state->cli->conn);
+ if (!ok) {
+ tevent_req_nterror(
+ req,
+ NT_STATUS_INSUFFICIENT_RESOURCES);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_write_andx_send(state,
+ ev,
+ cli,
+ fnum,
+ mode,
+ buf,
+ offset,
+ size);
+ }
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_write_done, req);
+
+ return req;
+}
+
+static void cli_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct cli_write_state *state =
+ tevent_req_data(req,
+ struct cli_write_state);
+ NTSTATUS status;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = cli_smb2_write_recv(subreq, &state->written);
+ } else {
+ status = cli_write_andx_recv(subreq, &state->written);
+ }
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_write_recv(struct tevent_req *req, size_t *pwritten)
+{
+ struct cli_write_state *state =
+ tevent_req_data(req,
+ struct cli_write_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ if (pwritten != NULL) {
+ *pwritten = state->written;
+ }
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct cli_smb1_writeall_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+ uint16_t mode;
+ const uint8_t *buf;
+ off_t offset;
+ size_t size;
+ size_t written;
+};
+
+static void cli_smb1_writeall_written(struct tevent_req *req);
+
+static struct tevent_req *cli_smb1_writeall_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t mode,
+ const uint8_t *buf,
+ off_t offset, size_t size)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_smb1_writeall_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_smb1_writeall_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fnum = fnum;
+ state->mode = mode;
+ state->buf = buf;
+ state->offset = offset;
+ state->size = size;
+ state->written = 0;
+
+ subreq = cli_write_andx_send(state, state->ev, state->cli, state->fnum,
+ state->mode, state->buf, state->offset,
+ state->size);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_smb1_writeall_written, req);
+ return req;
+}
+
+static void cli_smb1_writeall_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_smb1_writeall_state *state = tevent_req_data(
+ req, struct cli_smb1_writeall_state);
+ NTSTATUS status;
+ size_t written = 0, to_write;
+
+ status = cli_write_andx_recv(subreq, &written);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->written += written;
+
+ if (state->written > state->size) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ to_write = state->size - state->written;
+
+ if (to_write == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = cli_write_andx_send(state, state->ev, state->cli, state->fnum,
+ state->mode,
+ state->buf + state->written,
+ state->offset + state->written, to_write);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_smb1_writeall_written, req);
+}
+
+static NTSTATUS cli_smb1_writeall_recv(struct tevent_req *req,
+ size_t *pwritten)
+{
+ struct cli_smb1_writeall_state *state = tevent_req_data(
+ req, struct cli_smb1_writeall_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (pwritten != NULL) {
+ *pwritten = state->written;
+ }
+ return NT_STATUS_OK;
+}
+
+struct cli_writeall_state {
+ struct cli_state *cli;
+ size_t written;
+};
+
+static void cli_writeall_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_writeall_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t mode,
+ const uint8_t *buf,
+ off_t offset,
+ size_t size)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_writeall_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_writeall_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_writeall_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ mode,
+ buf,
+ offset,
+ size);
+ } else {
+ subreq = cli_smb1_writeall_send(
+ state,
+ ev,
+ cli,
+ fnum,
+ mode,
+ buf,
+ offset,
+ size);
+ }
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_writeall_done, req);
+
+ return req;
+}
+
+static void cli_writeall_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_writeall_state *state = tevent_req_data(
+ req, struct cli_writeall_state);
+ NTSTATUS status;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = cli_smb2_writeall_recv(subreq, &state->written);
+ } else {
+ status = cli_smb1_writeall_recv(subreq, &state->written);
+ }
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_writeall_recv(struct tevent_req *req, size_t *pwritten)
+{
+ struct cli_writeall_state *state = tevent_req_data(
+ req, struct cli_writeall_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (pwritten != NULL) {
+ *pwritten = state->written;
+ }
+ return NT_STATUS_OK;
+}
+
+
+NTSTATUS cli_writeall(struct cli_state *cli, uint16_t fnum, uint16_t mode,
+ const uint8_t *buf, off_t offset, size_t size,
+ size_t *pwritten)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_writeall_send(frame, ev, cli, fnum, mode, buf, offset, size);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_writeall_recv(req, pwritten);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_push_chunk;
+
+struct cli_push_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+ uint16_t mode;
+ off_t start_offset;
+
+ size_t (*source)(uint8_t *buf, size_t n, void *priv);
+ void *priv;
+
+ bool eof;
+
+ size_t chunk_size;
+ off_t next_offset;
+
+ /*
+ * Outstanding requests
+ *
+ * The maximum is 256:
+ * - which would be a window of 256 MByte
+ * for SMB2 with multi-credit
+ * or smb1 unix extensions.
+ */
+ uint16_t max_chunks;
+ uint16_t num_chunks;
+ uint16_t num_waiting;
+ struct cli_push_chunk *chunks;
+};
+
+struct cli_push_chunk {
+ struct cli_push_chunk *prev, *next;
+ struct tevent_req *req;/* This is the main request! Not the subreq */
+ struct tevent_req *subreq;
+ off_t ofs;
+ uint8_t *buf;
+ size_t total_size;
+ size_t tmp_size;
+ bool done;
+};
+
+static void cli_push_setup_chunks(struct tevent_req *req);
+static void cli_push_chunk_ship(struct cli_push_chunk *chunk);
+static void cli_push_chunk_done(struct tevent_req *subreq);
+
+/*
+ * Used to write to a file remotely.
+ * This is similar in functionality to cli_write_send(), except this API
+ * handles writing a large file by breaking the data into chunks (so we don't
+ * exceed the max write size of the underlying connection). To do this, the
+ * (*source) callback handles copying the underlying file data into a message
+ * buffer, one chunk at a time.
+ * This API is recommended when writing a potentially large amount of data,
+ * e.g. when copying a file (or doing a 'put').
+ */
+struct tevent_req *cli_push_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum, uint16_t mode,
+ off_t start_offset, size_t window_size,
+ size_t (*source)(uint8_t *buf, size_t n,
+ void *priv),
+ void *priv)
+{
+ struct tevent_req *req;
+ struct cli_push_state *state;
+ size_t page_size = 1024;
+ uint64_t tmp64;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_push_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->ev = ev;
+ state->fnum = fnum;
+ state->start_offset = start_offset;
+ state->mode = mode;
+ state->source = source;
+ state->priv = priv;
+ state->next_offset = start_offset;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ state->chunk_size = smb2cli_conn_max_write_size(cli->conn);
+ } else {
+ state->chunk_size = cli_write_max_bufsize(cli, mode, 14);
+ }
+ if (state->chunk_size > page_size) {
+ state->chunk_size &= ~(page_size - 1);
+ }
+
+ if (window_size == 0) {
+ /*
+ * We use 16 MByte as default window size.
+ */
+ window_size = 16 * 1024 * 1024;
+ }
+
+ tmp64 = window_size/state->chunk_size;
+ if ((window_size % state->chunk_size) > 0) {
+ tmp64 += 1;
+ }
+ tmp64 = MAX(tmp64, 1);
+ tmp64 = MIN(tmp64, 256);
+ state->max_chunks = tmp64;
+
+ /*
+ * We defer the callback because of the complex
+ * substate/subfunction logic
+ */
+ tevent_req_defer_callback(req, ev);
+
+ cli_push_setup_chunks(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void cli_push_setup_chunks(struct tevent_req *req)
+{
+ struct cli_push_state *state =
+ tevent_req_data(req,
+ struct cli_push_state);
+ struct cli_push_chunk *chunk, *next = NULL;
+ size_t i;
+
+ for (chunk = state->chunks; chunk; chunk = next) {
+ /*
+ * Note that chunk might be removed from this call.
+ */
+ next = chunk->next;
+ cli_push_chunk_ship(chunk);
+ if (!tevent_req_is_in_progress(req)) {
+ return;
+ }
+ }
+
+ for (i = state->num_chunks; i < state->max_chunks; i++) {
+
+ if (state->num_waiting > 0) {
+ return;
+ }
+
+ if (state->eof) {
+ break;
+ }
+
+ chunk = talloc_zero(state, struct cli_push_chunk);
+ if (tevent_req_nomem(chunk, req)) {
+ return;
+ }
+ chunk->req = req;
+ chunk->ofs = state->next_offset;
+ chunk->buf = talloc_array(chunk,
+ uint8_t,
+ state->chunk_size);
+ if (tevent_req_nomem(chunk->buf, req)) {
+ return;
+ }
+ chunk->total_size = state->source(chunk->buf,
+ state->chunk_size,
+ state->priv);
+ if (chunk->total_size == 0) {
+ /* nothing to send */
+ talloc_free(chunk);
+ state->eof = true;
+ break;
+ }
+ state->next_offset += chunk->total_size;
+
+ DLIST_ADD_END(state->chunks, chunk);
+ state->num_chunks++;
+ state->num_waiting++;
+
+ cli_push_chunk_ship(chunk);
+ if (!tevent_req_is_in_progress(req)) {
+ return;
+ }
+ }
+
+ if (!state->eof) {
+ return;
+ }
+
+ if (state->num_chunks > 0) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static void cli_push_chunk_ship(struct cli_push_chunk *chunk)
+{
+ struct tevent_req *req = chunk->req;
+ struct cli_push_state *state =
+ tevent_req_data(req,
+ struct cli_push_state);
+ bool ok;
+ const uint8_t *buf;
+ off_t ofs;
+ size_t size;
+
+ if (chunk->done) {
+ DLIST_REMOVE(state->chunks, chunk);
+ SMB_ASSERT(state->num_chunks > 0);
+ state->num_chunks--;
+ TALLOC_FREE(chunk);
+
+ return;
+ }
+
+ if (chunk->subreq != NULL) {
+ return;
+ }
+
+ SMB_ASSERT(state->num_waiting > 0);
+
+ buf = chunk->buf + chunk->tmp_size;
+ ofs = chunk->ofs + chunk->tmp_size;
+ size = chunk->total_size - chunk->tmp_size;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ uint32_t max_size;
+
+ ok = smb2cli_conn_req_possible(state->cli->conn, &max_size);
+ if (!ok) {
+ return;
+ }
+
+ /*
+ * downgrade depending on the available credits
+ */
+ size = MIN(max_size, size);
+
+ chunk->subreq = cli_smb2_write_send(chunk,
+ state->ev,
+ state->cli,
+ state->fnum,
+ state->mode,
+ buf,
+ ofs,
+ size);
+ if (tevent_req_nomem(chunk->subreq, req)) {
+ return;
+ }
+ } else {
+ ok = smb1cli_conn_req_possible(state->cli->conn);
+ if (!ok) {
+ return;
+ }
+
+ chunk->subreq = cli_write_andx_send(chunk,
+ state->ev,
+ state->cli,
+ state->fnum,
+ state->mode,
+ buf,
+ ofs,
+ size);
+ if (tevent_req_nomem(chunk->subreq, req)) {
+ return;
+ }
+ }
+ tevent_req_set_callback(chunk->subreq,
+ cli_push_chunk_done,
+ chunk);
+
+ state->num_waiting--;
+ return;
+}
+
+static void cli_push_chunk_done(struct tevent_req *subreq)
+{
+ struct cli_push_chunk *chunk =
+ tevent_req_callback_data(subreq,
+ struct cli_push_chunk);
+ struct tevent_req *req = chunk->req;
+ struct cli_push_state *state =
+ tevent_req_data(req,
+ struct cli_push_state);
+ NTSTATUS status;
+ size_t expected = chunk->total_size - chunk->tmp_size;
+ size_t written;
+
+ chunk->subreq = NULL;
+
+ if (smbXcli_conn_protocol(state->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = cli_smb2_write_recv(subreq, &written);
+ } else {
+ status = cli_write_andx_recv(subreq, &written);
+ }
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (written > expected) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (written == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ chunk->tmp_size += written;
+
+ if (chunk->tmp_size == chunk->total_size) {
+ chunk->done = true;
+ } else {
+ state->num_waiting++;
+ }
+
+ cli_push_setup_chunks(req);
+}
+
+NTSTATUS cli_push_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_push(struct cli_state *cli, uint16_t fnum, uint16_t mode,
+ off_t start_offset, size_t window_size,
+ size_t (*source)(uint8_t *buf, size_t n, void *priv),
+ void *priv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = cli_push_send(frame, ev, cli, fnum, mode, start_offset,
+ window_size, source, priv);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = cli_push_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+#define SPLICE_BLOCK_SIZE 1024 * 1024
+
+static NTSTATUS cli_splice_fallback(TALLOC_CTX *frame,
+ struct cli_state *srccli,
+ struct cli_state *dstcli,
+ uint16_t src_fnum, uint16_t dst_fnum,
+ off_t initial_size,
+ off_t src_offset, off_t dst_offset,
+ off_t *written,
+ int (*splice_cb)(off_t n, void *priv),
+ void *priv)
+{
+ NTSTATUS status;
+ uint8_t *buf = talloc_size(frame, SPLICE_BLOCK_SIZE);
+ size_t nread;
+ off_t remaining = initial_size;
+ *written = 0;
+
+ while (remaining) {
+ size_t to_read = MIN(remaining, SPLICE_BLOCK_SIZE);
+
+ status = cli_read(srccli, src_fnum,
+ (char *)buf, src_offset, to_read,
+ &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = cli_writeall(dstcli, dst_fnum, 0,
+ buf, dst_offset, nread, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if ((src_offset > INT64_MAX - nread) ||
+ (dst_offset > INT64_MAX - nread)) {
+ return NT_STATUS_FILE_TOO_LARGE;
+ }
+ src_offset += nread;
+ dst_offset += nread;
+ *written += nread;
+ if (remaining < nread) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ remaining -= nread;
+ if (!splice_cb(initial_size - remaining, priv)) {
+ return NT_STATUS_CANCELLED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_splice(struct cli_state *srccli, struct cli_state *dstcli,
+ uint16_t src_fnum, uint16_t dst_fnum,
+ off_t size,
+ off_t src_offset, off_t dst_offset,
+ off_t *written,
+ int (*splice_cb)(off_t n, void *priv), void *priv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ bool retry_fallback = false;
+
+ if (smbXcli_conn_has_async_calls(srccli->conn) ||
+ smbXcli_conn_has_async_calls(dstcli->conn))
+ {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ do {
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto out;
+ }
+ if (srccli == dstcli &&
+ smbXcli_conn_protocol(srccli->conn) >= PROTOCOL_SMB2_02 &&
+ !retry_fallback)
+ {
+ req = cli_smb2_splice_send(frame, ev,
+ srccli, src_fnum, dst_fnum,
+ size, src_offset, dst_offset,
+ splice_cb, priv);
+ } else {
+ status = cli_splice_fallback(frame,
+ srccli, dstcli,
+ src_fnum, dst_fnum,
+ size,
+ src_offset, dst_offset,
+ written,
+ splice_cb, priv);
+ goto out;
+ }
+ if (req == NULL) {
+ goto out;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+ status = cli_smb2_splice_recv(req, written);
+
+ /*
+ * Older versions of Samba don't support
+ * FSCTL_SRV_COPYCHUNK_WRITE so use the fallback.
+ */
+ retry_fallback = NT_STATUS_EQUAL(status, NT_STATUS_INVALID_DEVICE_REQUEST);
+ } while (retry_fallback);
+
+ out:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/clisecdesc.c b/source3/libsmb/clisecdesc.c
new file mode 100644
index 0000000..d6a2661
--- /dev/null
+++ b/source3/libsmb/clisecdesc.c
@@ -0,0 +1,372 @@
+/*
+ Unix SMB/CIFS implementation.
+ client security descriptor functions
+ Copyright (C) Andrew Tridgell 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/security/secdesc.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/util/tevent_ntstatus.h"
+
+struct cli_query_security_descriptor_state {
+ uint8_t param[8];
+ DATA_BLOB outbuf;
+};
+
+static void cli_query_security_descriptor_done1(struct tevent_req *subreq);
+static void cli_query_security_descriptor_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_query_security_descriptor_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_query_security_descriptor_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_query_security_descriptor_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_query_info_fnum_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ fnum, /* fnum */
+ 3, /* in_info_type */
+ 0, /* in_info_class */
+ 0xFFFF, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ sec_info, /* in_additional_info */
+ 0); /* in_flags */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_query_security_descriptor_done2, req);
+ return req;
+ }
+
+ PUSH_LE_U32(state->param, 0, fnum);
+ PUSH_LE_U32(state->param, 4, sec_info);
+
+ subreq = cli_trans_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ 0, /* additional_flags2 */
+ SMBnttrans, /* cmd */
+ NULL, /* pipe_name */
+ -1, /* fid */
+ NT_TRANSACT_QUERY_SECURITY_DESC, /* function */
+ 0, /* flags */
+ NULL, /* setup */
+ 0, /* num_setup */
+ 0, /* max_setup */
+ state->param, /* param */
+ 8, /* num_param */
+ 4, /* max_param */
+ NULL, /* data */
+ 0, /* num_data */
+ 0x10000); /* max_data */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_query_security_descriptor_done1, req);
+ return req;
+}
+
+static void cli_query_security_descriptor_done1(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_query_security_descriptor_state *state = tevent_req_data(
+ req, struct cli_query_security_descriptor_state);
+ NTSTATUS status;
+ uint32_t len;
+
+ status = cli_trans_recv(
+ subreq, /* req */
+ state, /* mem_ctx */
+ NULL, /* recv_flags2 */
+ NULL, /* setup */
+ 0, /* min_setup */
+ NULL, /* num_setup */
+ NULL, /* param */
+ 0, /* min_param */
+ NULL, /* num_param */
+ &state->outbuf.data, /* data */
+ 0, /* min_data */
+ &len); /* num_data */
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->outbuf.length = len; /* uint32_t -> size_t */
+ tevent_req_done(req);
+}
+
+static void cli_query_security_descriptor_done2(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_query_security_descriptor_state *state = tevent_req_data(
+ req, struct cli_query_security_descriptor_state);
+ NTSTATUS status;
+
+ status = cli_smb2_query_info_fnum_recv(subreq, state, &state->outbuf);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_query_security_descriptor_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **sd)
+{
+ struct cli_query_security_descriptor_state *state = tevent_req_data(
+ req, struct cli_query_security_descriptor_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ goto done;
+ }
+ if (sd != NULL) {
+ status = unmarshall_sec_desc(
+ mem_ctx, state->outbuf.data, state->outbuf.length, sd);
+ }
+done:
+ tevent_req_received(req);
+ return status;
+}
+
+NTSTATUS cli_query_security_descriptor(struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **sd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_query_security_descriptor_send(
+ frame, ev, cli, fnum, sec_info);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_query_security_descriptor_recv(req, mem_ctx, sd);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS cli_query_secdesc(struct cli_state *cli, uint16_t fnum,
+ TALLOC_CTX *mem_ctx, struct security_descriptor **sd)
+{
+ uint32_t sec_info = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL;
+
+ return cli_query_security_descriptor(cli, fnum, sec_info, mem_ctx, sd);
+}
+
+NTSTATUS cli_query_mxac(struct cli_state *cli,
+ const char *filename,
+ uint32_t *mxac)
+{
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_SMB2_02) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return cli_smb2_query_mxac(cli, filename, mxac);
+}
+
+struct cli_set_security_descriptor_state {
+ uint8_t param[8];
+ DATA_BLOB buf;
+};
+
+static void cli_set_security_descriptor_done1(struct tevent_req *subreq);
+static void cli_set_security_descriptor_done2(struct tevent_req *subreq);
+
+struct tevent_req *cli_set_security_descriptor_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info,
+ const struct security_descriptor *sd)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_set_security_descriptor_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct cli_set_security_descriptor_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = marshall_sec_desc(
+ state, sd, &state->buf.data, &state->buf.length);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ subreq = cli_smb2_set_info_fnum_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ fnum, /* fnum */
+ 3, /* in_info_type */
+ 0, /* in_file_info_class */
+ &state->buf, /* in_input_buffer */
+ sec_info); /* in_additional_info */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_set_security_descriptor_done2, req);
+ return req;
+ }
+
+ SIVAL(state->param, 0, fnum);
+ SIVAL(state->param, 4, sec_info);
+
+ subreq = cli_trans_send(
+ state, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ 0, /* additional_flags2 */
+ SMBnttrans, /* cmd */
+ NULL, /* pipe_name */
+ -1, /* fid */
+ NT_TRANSACT_SET_SECURITY_DESC, /* function */
+ 0, /* flags */
+ NULL, /* setup */
+ 0, /* num_setup */
+ 0, /* max_setup */
+ state->param, /* param */
+ 8, /* num_param */
+ 0, /* max_param */
+ state->buf.data, /* data */
+ state->buf.length, /* num_data */
+ 0); /* max_data */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, cli_set_security_descriptor_done1, req);
+ return req;
+}
+
+static void cli_set_security_descriptor_done1(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_trans_recv(
+ subreq, NULL, NULL, NULL, 0, NULL, NULL, 0, NULL,
+ NULL, 0, NULL);
+ return tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void cli_set_security_descriptor_done2(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb2_set_info_fnum_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_set_security_descriptor_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************************
+ set the security descriptor for a open file
+ ****************************************************************************/
+NTSTATUS cli_set_security_descriptor(struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info,
+ const struct security_descriptor *sd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_set_security_descriptor_send(
+ frame, ev, cli, fnum, sec_info, sd);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_set_security_descriptor_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS cli_set_secdesc(struct cli_state *cli, uint16_t fnum,
+ const struct security_descriptor *sd)
+{
+ uint32_t sec_info = 0;
+
+ if (sd->dacl || (sd->type & SEC_DESC_DACL_PRESENT)) {
+ sec_info |= SECINFO_DACL;
+ }
+ if (sd->sacl || (sd->type & SEC_DESC_SACL_PRESENT)) {
+ sec_info |= SECINFO_SACL;
+ }
+ if (sd->owner_sid) {
+ sec_info |= SECINFO_OWNER;
+ }
+ if (sd->group_sid) {
+ sec_info |= SECINFO_GROUP;
+ }
+
+ return cli_set_security_descriptor(cli, fnum, sec_info, sd);
+}
diff --git a/source3/libsmb/clisymlink.c b/source3/libsmb/clisymlink.c
new file mode 100644
index 0000000..d7ecf54
--- /dev/null
+++ b/source3/libsmb/clisymlink.c
@@ -0,0 +1,650 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Client implementation of setting symlinks using reparse points
+ * Copyright (C) Volker Lendecke 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "libsmb/clirap.h"
+#include "trans2.h"
+#include "libcli/security/secdesc.h"
+#include "libcli/security/security.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/smb/reparse.h"
+
+struct cli_create_reparse_point_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ DATA_BLOB reparse_blob;
+ uint16_t fnum;
+ NTSTATUS set_reparse_status;
+};
+
+static void cli_create_reparse_point_opened(struct tevent_req *subreq);
+static void cli_create_reparse_point_done(struct tevent_req *subreq);
+static void cli_create_reparse_point_doc_done(struct tevent_req *subreq);
+static void cli_create_reparse_point_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_create_reparse_point_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ DATA_BLOB reparse_blob)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_create_reparse_point_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct cli_create_reparse_point_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->reparse_blob = reparse_blob;
+
+ /*
+ * The create arguments were taken from a Windows->Windows
+ * symlink create call.
+ */
+ subreq = cli_ntcreate_send(
+ state,
+ ev,
+ cli,
+ fname,
+ 0,
+ SYNCHRONIZE_ACCESS | DELETE_ACCESS | FILE_READ_ATTRIBUTES |
+ FILE_WRITE_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_CREATE,
+ FILE_OPEN_REPARSE_POINT | FILE_SYNCHRONOUS_IO_NONALERT |
+ FILE_NON_DIRECTORY_FILE,
+ SMB2_IMPERSONATION_IMPERSONATION,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_create_reparse_point_opened, req);
+ return req;
+}
+
+static void cli_create_reparse_point_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_create_reparse_point_state *state =
+ tevent_req_data(req, struct cli_create_reparse_point_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->fnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_fsctl_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ FSCTL_SET_REPARSE_POINT,
+ &state->reparse_blob,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_create_reparse_point_done, req);
+}
+
+static void cli_create_reparse_point_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_create_reparse_point_state *state =
+ tevent_req_data(req, struct cli_create_reparse_point_state);
+
+ state->set_reparse_status = cli_fsctl_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(state->set_reparse_status)) {
+ subreq = cli_close_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_create_reparse_point_closed,
+ req);
+ return;
+ }
+ subreq = cli_nt_delete_on_close_send(
+ state, state->ev, state->cli, state->fnum, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ cli_create_reparse_point_doc_done,
+ req);
+}
+
+static void cli_create_reparse_point_doc_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_create_reparse_point_state *state =
+ tevent_req_data(req, struct cli_create_reparse_point_state);
+
+ /*
+ * Ignore status, we can't do much anyway in case of failure
+ */
+
+ (void)cli_nt_delete_on_close_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ subreq = cli_close_send(state, state->ev, state->cli, state->fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_create_reparse_point_closed, req);
+}
+
+static void cli_create_reparse_point_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_create_reparse_point_state *state =
+ tevent_req_data(req, struct cli_create_reparse_point_state);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (tevent_req_nterror(req, state->set_reparse_status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_create_reparse_point_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct cli_symlink_state {
+ uint8_t dummy;
+};
+
+static void cli_symlink_done(struct tevent_req *subreq);
+
+struct tevent_req *cli_symlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *link_target,
+ const char *newpath,
+ uint32_t flags)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_symlink_state *state = NULL;
+ struct reparse_data_buffer reparse_buf = {
+ .tag = IO_REPARSE_TAG_SYMLINK,
+ .parsed.lnk.substitute_name =
+ discard_const_p(char, link_target),
+ .parsed.lnk.print_name = discard_const_p(char, link_target),
+ .parsed.lnk.flags = flags,
+ };
+ uint8_t *buf;
+ ssize_t buflen;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_symlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ buflen = reparse_data_buffer_marshall(&reparse_buf, NULL, 0);
+ if (buflen == -1) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ buf = talloc_array(state, uint8_t, buflen);
+ if (tevent_req_nomem(buf, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ buflen = reparse_data_buffer_marshall(&reparse_buf, buf, buflen);
+ if (buflen != talloc_array_length(buf)) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_create_reparse_point_send(state,
+ ev,
+ cli,
+ newpath,
+ (DATA_BLOB){
+ .data = buf,
+ .length = buflen,
+ });
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_symlink_done, req);
+ return req;
+}
+
+static void cli_symlink_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_symlink_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+NTSTATUS cli_symlink_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS cli_symlink(struct cli_state *cli, const char *link_target,
+ const char *newname, uint32_t flags)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_symlink_send(frame, ev, cli, link_target, newname, flags);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_symlink_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_get_reparse_data_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+
+ NTSTATUS get_reparse_status;
+ uint8_t *data;
+ uint32_t datalen;
+};
+
+static void cli_get_reparse_data_opened(struct tevent_req *subreq);
+static void cli_get_reparse_data_done(struct tevent_req *subreq);
+static void cli_get_reparse_data_closed(struct tevent_req *subreq);
+
+struct tevent_req *cli_get_reparse_data_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct cli_get_reparse_data_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct cli_get_reparse_data_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ subreq = cli_ntcreate_send(state,
+ ev,
+ cli,
+ fname,
+ 0,
+ FILE_READ_ATTRIBUTES | FILE_READ_EA,
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE |
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ FILE_OPEN_REPARSE_POINT,
+ SMB2_IMPERSONATION_IMPERSONATION,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_get_reparse_data_opened, req);
+ return req;
+}
+
+static void cli_get_reparse_data_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_get_reparse_data_state *state =
+ tevent_req_data(req, struct cli_get_reparse_data_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->fnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_fsctl_send(state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ FSCTL_GET_REPARSE_POINT,
+ NULL,
+ 65536);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_get_reparse_data_done, req);
+}
+
+static void cli_get_reparse_data_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_get_reparse_data_state *state =
+ tevent_req_data(req, struct cli_get_reparse_data_state);
+ DATA_BLOB out = {
+ .data = NULL,
+ };
+
+ state->get_reparse_status = cli_fsctl_recv(subreq, state, &out);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(state->get_reparse_status)) {
+ state->data = out.data;
+ state->datalen = out.length;
+ }
+
+ subreq = cli_close_send(state, state->ev, state->cli, state->fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_get_reparse_data_closed, req);
+}
+
+static void cli_get_reparse_data_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct cli_get_reparse_data_state *state =
+ tevent_req_data(req, struct cli_get_reparse_data_state);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (tevent_req_nterror(req, state->get_reparse_status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_get_reparse_data_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **_data,
+ uint32_t *_datalen)
+{
+ struct cli_get_reparse_data_state *state =
+ tevent_req_data(req, struct cli_get_reparse_data_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *_data = talloc_move(mem_ctx, &state->data);
+ *_datalen = state->datalen;
+
+ tevent_req_received(req);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_get_reparse_data(struct cli_state *cli,
+ const char *fname,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **_data,
+ uint32_t *_datalen)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_get_reparse_data_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_get_reparse_data_recv(req, mem_ctx, _data, _datalen);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct cli_readlink_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+
+ uint16_t setup[4];
+ uint8_t *data;
+ uint32_t num_data;
+ char *target;
+};
+
+static void cli_readlink_posix1_done(struct tevent_req *subreq);
+static void cli_readlink_got_reparse_data(struct tevent_req *subreq);
+
+struct tevent_req *cli_readlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_readlink_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_readlink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ if (cli->requested_posix_capabilities != 0) {
+ /*
+ * Only happens for negotiated SMB1 posix caps
+ */
+ subreq = cli_posix_readlink_send(state, ev, cli, fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_readlink_posix1_done, req);
+ return req;
+ }
+
+ subreq = cli_get_reparse_data_send(state, ev, cli, fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_readlink_got_reparse_data, req);
+ return req;
+}
+
+static void cli_readlink_posix1_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_readlink_state *state = tevent_req_data(
+ req, struct cli_readlink_state);
+ NTSTATUS status;
+
+ status = cli_posix_readlink_recv(subreq, state, &state->target);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_readlink_got_reparse_data(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_readlink_state *state = tevent_req_data(
+ req, struct cli_readlink_state);
+ NTSTATUS status;
+
+ status = cli_get_reparse_data_recv(subreq,
+ state,
+ &state->data,
+ &state->num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_readlink_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **psubstitute_name, char **pprint_name,
+ uint32_t *pflags)
+{
+ struct cli_readlink_state *state = tevent_req_data(
+ req, struct cli_readlink_state);
+ struct reparse_data_buffer buf = {
+ .tag = 0,
+ };
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (state->target != NULL) {
+ /*
+ * SMB1 posix version
+ */
+ if (psubstitute_name != NULL) {
+ *psubstitute_name = talloc_move(
+ mem_ctx, &state->target);
+ }
+ if (pprint_name != NULL) {
+ *pprint_name = NULL;
+ }
+ if (pflags != NULL) {
+ *pflags = 0;
+ }
+ return NT_STATUS_OK;
+ }
+
+ status = reparse_data_buffer_parse(state,
+ &buf,
+ state->data,
+ state->num_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (buf.tag != IO_REPARSE_TAG_SYMLINK) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ if (psubstitute_name != NULL) {
+ *psubstitute_name =
+ talloc_move(mem_ctx, &buf.parsed.lnk.substitute_name);
+ }
+
+ if (pprint_name != NULL) {
+ *pprint_name =
+ talloc_move(mem_ctx, &buf.parsed.lnk.print_name);
+ }
+
+ if (pflags != NULL) {
+ *pflags = buf.parsed.lnk.flags;
+ }
+
+ tevent_req_received(req);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_readlink(struct cli_state *cli, const char *fname,
+ TALLOC_CTX *mem_ctx, char **psubstitute_name,
+ char **pprint_name, uint32_t *pflags)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_readlink_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_readlink_recv(req, mem_ctx, psubstitute_name,
+ pprint_name, pflags);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/clitrans.c b/source3/libsmb/clitrans.c
new file mode 100644
index 0000000..e5b4c4a
--- /dev/null
+++ b/source3/libsmb/clitrans.c
@@ -0,0 +1,224 @@
+/*
+ Unix SMB/CIFS implementation.
+ client transaction calls
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "async_smb.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+struct cli_trans_state {
+ struct cli_state *cli;
+ struct tevent_req *subreq;
+ uint16_t recv_flags2;
+ uint16_t *setup;
+ uint8_t num_setup;
+ uint8_t *param;
+ uint32_t num_param;
+ uint8_t *data;
+ uint32_t num_data;
+};
+
+static void cli_trans_done(struct tevent_req *subreq);
+static bool cli_trans_cancel(struct tevent_req *req);
+
+struct tevent_req *cli_trans_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, uint16_t additional_flags2, uint8_t cmd,
+ const char *pipe_name, uint16_t fid, uint16_t function, int flags,
+ uint16_t *setup, uint8_t num_setup, uint8_t max_setup,
+ uint8_t *param, uint32_t num_param, uint32_t max_param,
+ uint8_t *data, uint32_t num_data, uint32_t max_data)
+{
+ struct tevent_req *req;
+ struct cli_trans_state *state;
+ uint8_t additional_flags = 0;
+ uint8_t clear_flags = 0;
+ uint16_t clear_flags2 = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_trans_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+
+ state->subreq = smb1cli_trans_send(state, ev,
+ cli->conn, cmd,
+ additional_flags, clear_flags,
+ additional_flags2, clear_flags2,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ pipe_name, fid, function, flags,
+ setup, num_setup, max_setup,
+ param, num_param, max_param,
+ data, num_data, max_data);
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreq, cli_trans_done, req);
+ tevent_req_set_cancel_fn(req, cli_trans_cancel);
+ return req;
+}
+
+static bool cli_trans_cancel(struct tevent_req *req)
+{
+ struct cli_trans_state *state = tevent_req_data(
+ req, struct cli_trans_state);
+ bool ok;
+
+ ok = tevent_req_cancel(state->subreq);
+ return ok;
+}
+
+static void cli_trans_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_trans_state *state = tevent_req_data(
+ req, struct cli_trans_state);
+ NTSTATUS status;
+
+ status = smb1cli_trans_recv(
+ subreq,
+ state,
+ &state->recv_flags2,
+ &state->setup, 0, &state->num_setup,
+ &state->param, 0, &state->num_param,
+ &state->data, 0, &state->num_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS cli_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint16_t *recv_flags2,
+ uint16_t **setup, uint8_t min_setup,
+ uint8_t *num_setup,
+ uint8_t **param, uint32_t min_param,
+ uint32_t *num_param,
+ uint8_t **data, uint32_t min_data,
+ uint32_t *num_data)
+{
+ struct cli_trans_state *state = tevent_req_data(
+ req, struct cli_trans_state);
+ NTSTATUS status = NT_STATUS_OK;
+ bool map_dos_errors = true;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ goto map_error;
+ }
+
+ if ((state->num_setup < min_setup) ||
+ (state->num_param < min_param) ||
+ (state->num_data < min_data)) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ if (recv_flags2 != NULL) {
+ *recv_flags2 = state->recv_flags2;
+ }
+ if (setup != NULL) {
+ *setup = talloc_move(mem_ctx, &state->setup);
+ *num_setup = state->num_setup;
+ }
+ if (param != NULL) {
+ *param = talloc_move(mem_ctx, &state->param);
+ *num_param = state->num_param;
+ }
+ if (data != NULL) {
+ *data = talloc_move(mem_ctx, &state->data);
+ *num_data = state->num_data;
+ }
+
+map_error:
+ map_dos_errors = state->cli->map_dos_errors;
+ state->cli->raw_status = status;
+
+ if (NT_STATUS_IS_DOS(status) && map_dos_errors) {
+ uint8_t eclass = NT_STATUS_DOS_CLASS(status);
+ uint16_t ecode = NT_STATUS_DOS_CODE(status);
+ /*
+ * TODO: is it really a good idea to do a mapping here?
+ *
+ * The old cli_pull_error() also does it, so I do not change
+ * the behavior yet.
+ */
+ status = dos_to_ntstatus(eclass, ecode);
+ }
+
+ return status;
+}
+
+NTSTATUS cli_trans(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint8_t trans_cmd,
+ const char *pipe_name, uint16_t fid, uint16_t function,
+ int flags,
+ uint16_t *setup, uint8_t num_setup, uint8_t max_setup,
+ uint8_t *param, uint32_t num_param, uint32_t max_param,
+ uint8_t *data, uint32_t num_data, uint32_t max_data,
+ uint16_t *recv_flags2,
+ uint16_t **rsetup, uint8_t min_rsetup, uint8_t *num_rsetup,
+ uint8_t **rparam, uint32_t min_rparam, uint32_t *num_rparam,
+ uint8_t **rdata, uint32_t min_rdata, uint32_t *num_rdata)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (smbXcli_conn_has_async_calls(cli->conn)) {
+ /*
+ * Can't use sync call while an async call is in flight
+ */
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = cli_trans_send(
+ frame, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ 0, /* additional_flags2 */
+ trans_cmd, /* cmd */
+ pipe_name, fid, function, flags,
+ setup, num_setup, max_setup,
+ param, num_param, max_param,
+ data, num_data, max_data);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = cli_trans_recv(
+ req, mem_ctx, recv_flags2,
+ rsetup, min_rsetup, num_rsetup,
+ rparam, min_rparam, num_rparam,
+ rdata, min_rdata, num_rdata);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/conncache.c b/source3/libsmb/conncache.c
new file mode 100644
index 0000000..7310b50
--- /dev/null
+++ b/source3/libsmb/conncache.c
@@ -0,0 +1,227 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon connection manager
+
+ Copyright (C) Tim Potter 2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Gerald (Jerry) Carter 2003
+ Copyright (C) Marc VanHeyningen 2008
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "lib/gencache.h"
+
+/**
+ * @file
+ * Negative connection cache implemented in terms of gencache API
+ *
+ * The negative connection cache stores names of servers which have
+ * been unresponsive so that we don't waste time repeatedly trying
+ * to contact them. It used to use an in-memory linked list, but
+ * this limited its utility to a single process
+ */
+
+
+/**
+ * Marshalls the domain and server name into the key for the gencache
+ * record
+ *
+ * @param[in] domain required
+ * @param[in] server may be a FQDN or an IP address
+ * @return the resulting string, which the caller is responsible for
+ * SAFE_FREE()ing
+ * @retval NULL returned on error
+ */
+static char *negative_conn_cache_keystr(const char *domain, const char *server)
+{
+ char *keystr = NULL;
+
+ if (domain == NULL) {
+ return NULL;
+ }
+ if (server == NULL)
+ server = "";
+
+ keystr = talloc_asprintf(talloc_tos(), "NEG_CONN_CACHE/%s,%s",
+ domain, server);
+ if (keystr == NULL) {
+ DEBUG(0, ("negative_conn_cache_keystr: malloc error\n"));
+ }
+
+ return keystr;
+}
+
+/**
+ * Marshalls the NT status into a printable value field for the gencache
+ * record
+ *
+ * @param[in] status
+ * @return the resulting string, which the caller is responsible for
+ * SAFE_FREE()ing
+ * @retval NULL returned on error
+ */
+static char *negative_conn_cache_valuestr(NTSTATUS status)
+{
+ char *valuestr = NULL;
+
+ valuestr = talloc_asprintf(talloc_tos(), "%x", NT_STATUS_V(status));
+ if (valuestr == NULL) {
+ DEBUG(0, ("negative_conn_cache_valuestr: malloc error\n"));
+ }
+
+ return valuestr;
+}
+
+/**
+ * Un-marshalls the NT status from a printable field for the gencache
+ * record
+ *
+ * @param[in] value The value field from the record
+ * @return the decoded NT status
+ * @retval NT_STATUS_OK returned on error
+ */
+static NTSTATUS negative_conn_cache_valuedecode(const char *value)
+{
+ unsigned int v = NT_STATUS_V(NT_STATUS_INTERNAL_ERROR);
+
+ if (value == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (sscanf(value, "%x", &v) != 1) {
+ DEBUG(0, ("negative_conn_cache_valuedecode: unable to parse "
+ "value field '%s'\n", value));
+ }
+ return NT_STATUS(v);
+}
+
+/**
+ * Function passed to gencache_iterate to remove any matching items
+ * from the list
+ *
+ * @param[in] key Key to the record found and to be deleted
+ * @param[in] value Value to the record (ignored)
+ * @param[in] timeout Timeout remaining for the record (ignored)
+ * @param[in] dptr Handle for passing additional data (ignored)
+ */
+static void delete_matches(const char *key, const char *value,
+ time_t timeout, void *dptr)
+{
+ gencache_del(key);
+}
+
+
+/**
+ * Checks for a given domain/server record in the negative cache
+ *
+ * @param[in] domain
+ * @param[in] server may be either a FQDN or an IP address
+ * @return The cached failure status
+ * @retval NT_STATUS_OK returned if no record is found or an error occurs
+ */
+NTSTATUS check_negative_conn_cache( const char *domain, const char *server)
+{
+ NTSTATUS result = NT_STATUS_OK;
+ char *key = NULL;
+ char *value = NULL;
+
+ key = negative_conn_cache_keystr(domain, server);
+ if (key == NULL)
+ goto done;
+
+ if (gencache_get(key, talloc_tos(), &value, NULL))
+ result = negative_conn_cache_valuedecode(value);
+ done:
+ DEBUG(9,("check_negative_conn_cache returning result %d for domain %s "
+ "server %s\n", NT_STATUS_V(result), domain, server));
+ TALLOC_FREE(key);
+ TALLOC_FREE(value);
+ return result;
+}
+
+/**
+ * Add an entry to the failed connection cache
+ *
+ * @param[in] domain
+ * @param[in] server may be a FQDN or an IP addr in printable form
+ * @param[in] result error to cache; must not be NT_STATUS_OK
+ */
+void add_failed_connection_entry(const char *domain, const char *server,
+ NTSTATUS result)
+{
+ char *key = NULL;
+ char *value = NULL;
+
+ if (NT_STATUS_IS_OK(result)) {
+ /* Nothing failed here */
+ return;
+ }
+
+ key = negative_conn_cache_keystr(domain, server);
+ if (key == NULL) {
+ DEBUG(0, ("add_failed_connection_entry: key creation error\n"));
+ goto done;
+ }
+
+ value = negative_conn_cache_valuestr(result);
+ if (value == NULL) {
+ DEBUG(0, ("add_failed_connection_entry: value creation error\n"));
+ goto done;
+ }
+
+ if (gencache_set(key, value,
+ time(NULL) + FAILED_CONNECTION_CACHE_TIMEOUT))
+ DEBUG(9,("add_failed_connection_entry: added domain %s (%s) "
+ "to failed conn cache\n", domain, server ));
+ else
+ DEBUG(1,("add_failed_connection_entry: failed to add "
+ "domain %s (%s) to failed conn cache\n",
+ domain, server));
+
+ done:
+ TALLOC_FREE(key);
+ TALLOC_FREE(value);
+ return;
+}
+
+/**
+ * Deletes all records for a specified domain from the negative connection
+ * cache
+ *
+ * @param[in] domain String to match against domain portion of keys, or "*"
+ * to match all domains
+ */
+void flush_negative_conn_cache_for_domain(const char *domain)
+{
+ char *key_pattern = NULL;
+
+ key_pattern = negative_conn_cache_keystr(domain,"*");
+ if (key_pattern == NULL) {
+ DEBUG(0, ("flush_negative_conn_cache_for_domain: "
+ "key creation error\n"));
+ goto done;
+ }
+
+ gencache_iterate(delete_matches, NULL, key_pattern);
+ DEBUG(8, ("flush_negative_conn_cache_for_domain: flushed domain %s\n",
+ domain));
+
+ done:
+ TALLOC_FREE(key_pattern);
+ return;
+}
diff --git a/source3/libsmb/dsgetdcname.c b/source3/libsmb/dsgetdcname.c
new file mode 100644
index 0000000..09a6e66
--- /dev/null
+++ b/source3/libsmb/dsgetdcname.c
@@ -0,0 +1,1274 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ dsgetdcname
+
+ Copyright (C) Gerald Carter 2006
+ Copyright (C) Guenther Deschner 2007-2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/dsgetdcname.h"
+#include "libsmb/namequery.h"
+#include "libads/sitename_cache.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "libads/cldap.h"
+#include "lib/addns/dnsquery_srv.h"
+#include "libsmb/clidgram.h"
+#include "lib/gencache.h"
+#include "lib/util/util_net.h"
+
+/* 15 minutes */
+#define DSGETDCNAME_CACHE_TTL 60*15
+
+struct ip_service_name {
+ struct samba_sockaddr sa;
+ const char *hostname;
+};
+
+static NTSTATUS make_dc_info_from_cldap_reply(
+ TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ const struct samba_sockaddr *sa,
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX *r,
+ struct netr_DsRGetDCNameInfo **info);
+
+/****************************************************************
+****************************************************************/
+
+static void debug_dsdcinfo_flags(int lvl, uint32_t flags)
+{
+ DEBUG(lvl,("debug_dsdcinfo_flags: 0x%08x\n\t", flags));
+
+ if (flags & DS_FORCE_REDISCOVERY)
+ DEBUGADD(lvl,("DS_FORCE_REDISCOVERY "));
+ if (flags & 0x00000002)
+ DEBUGADD(lvl,("0x00000002 "));
+ if (flags & 0x00000004)
+ DEBUGADD(lvl,("0x00000004 "));
+ if (flags & 0x00000008)
+ DEBUGADD(lvl,("0x00000008 "));
+ if (flags & DS_DIRECTORY_SERVICE_REQUIRED)
+ DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_REQUIRED "));
+ if (flags & DS_DIRECTORY_SERVICE_PREFERRED)
+ DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_PREFERRED "));
+ if (flags & DS_GC_SERVER_REQUIRED)
+ DEBUGADD(lvl,("DS_GC_SERVER_REQUIRED "));
+ if (flags & DS_PDC_REQUIRED)
+ DEBUGADD(lvl,("DS_PDC_REQUIRED "));
+ if (flags & DS_BACKGROUND_ONLY)
+ DEBUGADD(lvl,("DS_BACKGROUND_ONLY "));
+ if (flags & DS_IP_REQUIRED)
+ DEBUGADD(lvl,("DS_IP_REQUIRED "));
+ if (flags & DS_KDC_REQUIRED)
+ DEBUGADD(lvl,("DS_KDC_REQUIRED "));
+ if (flags & DS_TIMESERV_REQUIRED)
+ DEBUGADD(lvl,("DS_TIMESERV_REQUIRED "));
+ if (flags & DS_WRITABLE_REQUIRED)
+ DEBUGADD(lvl,("DS_WRITABLE_REQUIRED "));
+ if (flags & DS_GOOD_TIMESERV_PREFERRED)
+ DEBUGADD(lvl,("DS_GOOD_TIMESERV_PREFERRED "));
+ if (flags & DS_AVOID_SELF)
+ DEBUGADD(lvl,("DS_AVOID_SELF "));
+ if (flags & DS_ONLY_LDAP_NEEDED)
+ DEBUGADD(lvl,("DS_ONLY_LDAP_NEEDED "));
+ if (flags & DS_IS_FLAT_NAME)
+ DEBUGADD(lvl,("DS_IS_FLAT_NAME "));
+ if (flags & DS_IS_DNS_NAME)
+ DEBUGADD(lvl,("DS_IS_DNS_NAME "));
+ if (flags & DS_TRY_NEXTCLOSEST_SITE)
+ DEBUGADD(lvl,("DS_TRY_NEXTCLOSEST_SITE "));
+ if (flags & DS_DIRECTORY_SERVICE_6_REQUIRED)
+ DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_6_REQUIRED "));
+ if (flags & DS_WEB_SERVICE_REQUIRED)
+ DEBUGADD(lvl,("DS_WEB_SERVICE_REQUIRED "));
+ if (flags & DS_DIRECTORY_SERVICE_8_REQUIRED)
+ DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_8_REQUIRED "));
+ if (flags & DS_DIRECTORY_SERVICE_9_REQUIRED)
+ DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_9_REQUIRED "));
+ if (flags & DS_DIRECTORY_SERVICE_10_REQUIRED)
+ DEBUGADD(lvl,("DS_DIRECTORY_SERVICE_10_REQUIRED "));
+ if (flags & 0x01000000)
+ DEBUGADD(lvl,("0x01000000 "));
+ if (flags & 0x02000000)
+ DEBUGADD(lvl,("0x02000000 "));
+ if (flags & 0x04000000)
+ DEBUGADD(lvl,("0x04000000 "));
+ if (flags & 0x08000000)
+ DEBUGADD(lvl,("0x08000000 "));
+ if (flags & 0x10000000)
+ DEBUGADD(lvl,("0x10000000 "));
+ if (flags & 0x20000000)
+ DEBUGADD(lvl,("0x20000000 "));
+ if (flags & DS_RETURN_DNS_NAME)
+ DEBUGADD(lvl,("DS_RETURN_DNS_NAME "));
+ if (flags & DS_RETURN_FLAT_NAME)
+ DEBUGADD(lvl,("DS_RETURN_FLAT_NAME "));
+ if (flags)
+ DEBUGADD(lvl,("\n"));
+}
+
+/****************************************************************
+****************************************************************/
+
+static char *dsgetdcname_cache_key(TALLOC_CTX *mem_ctx, const char *domain)
+{
+ if (!domain) {
+ return NULL;
+ }
+
+ return talloc_asprintf_strupper_m(mem_ctx, "DSGETDCNAME/DOMAIN/%s",
+ domain);
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS dsgetdcname_cache_delete(TALLOC_CTX *mem_ctx,
+ const char *domain_name)
+{
+ char *key;
+
+ key = dsgetdcname_cache_key(mem_ctx, domain_name);
+ if (!key) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!gencache_del(key)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS dsgetdcname_cache_store(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ DATA_BLOB blob)
+{
+ time_t expire_time;
+ char *key;
+ bool ret = false;
+
+ key = dsgetdcname_cache_key(mem_ctx, domain_name);
+ if (!key) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ expire_time = time(NULL) + DSGETDCNAME_CACHE_TTL;
+
+ ret = gencache_set_data_blob(key, blob, expire_time);
+
+ return ret ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS store_cldap_reply(TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ struct samba_sockaddr *sa,
+ uint32_t nt_version,
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX *r)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), &sa->u.ss);
+
+ /* FIXME */
+ r->sockaddr_size = 0x10; /* the w32 winsock addr size */
+ r->sockaddr.sockaddr_family = 2; /* AF_INET */
+ r->sockaddr.pdc_ip = talloc_strdup(mem_ctx, addr);
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, r,
+ (ndr_push_flags_fn_t)ndr_push_NETLOGON_SAM_LOGON_RESPONSE_EX);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (r->domain_name) {
+ status = dsgetdcname_cache_store(mem_ctx, r->domain_name,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (r->client_site) {
+ sitename_store(r->domain_name, r->client_site);
+ }
+ }
+ if (r->dns_domain) {
+ status = dsgetdcname_cache_store(mem_ctx, r->dns_domain, blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (r->client_site) {
+ sitename_store(r->dns_domain, r->client_site);
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+ done:
+ data_blob_free(&blob);
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static uint32_t get_cldap_reply_server_flags(struct netlogon_samlogon_response *r,
+ uint32_t nt_version)
+{
+ switch (nt_version & 0x0000001f) {
+ case 0:
+ case 1:
+ case 16:
+ case 17:
+ return 0;
+ case 2:
+ case 3:
+ case 18:
+ case 19:
+ return r->data.nt5.server_type;
+ case 4:
+ case 5:
+ case 6:
+ case 7:
+ return r->data.nt5_ex.server_type;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ return r->data.nt5_ex.server_type;
+ case 20:
+ case 21:
+ case 22:
+ case 23:
+ case 24:
+ case 25:
+ case 26:
+ case 27:
+ case 28:
+ return r->data.nt5_ex.server_type;
+ case 29:
+ case 30:
+ case 31:
+ return r->data.nt5_ex.server_type;
+ default:
+ return 0;
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS dsgetdcname_cache_fetch(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ uint32_t flags,
+ struct netr_DsRGetDCNameInfo **info_p)
+{
+ char *key;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ struct netr_DsRGetDCNameInfo *info;
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX r;
+ NTSTATUS status;
+
+ key = dsgetdcname_cache_key(mem_ctx, domain_name);
+ if (!key) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!gencache_get_data_blob(key, NULL, &blob, NULL, NULL)) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ info = talloc_zero(mem_ctx, struct netr_DsRGetDCNameInfo);
+ if (!info) {
+ data_blob_free(&blob);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_NETLOGON_SAM_LOGON_RESPONSE_EX);
+
+ data_blob_free(&blob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ dsgetdcname_cache_delete(mem_ctx, domain_name);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ status = make_dc_info_from_cldap_reply(mem_ctx, flags, NULL,
+ &r, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(netr_DsRGetDCNameInfo, info);
+ }
+
+ /* check flags */
+ if (!check_cldap_reply_required_flags(info->dc_flags, flags)) {
+ DEBUG(10,("invalid flags\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ((flags & DS_IP_REQUIRED) &&
+ (info->dc_address_type != DS_ADDRESS_TYPE_INET)) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ *info_p = info;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS dsgetdcname_cached(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ uint32_t flags,
+ const char *site_name,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ NTSTATUS status;
+
+ status = dsgetdcname_cache_fetch(mem_ctx, domain_name, domain_guid,
+ flags, info);
+ if (!NT_STATUS_IS_OK(status)
+ && !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(10,("dsgetdcname_cached: cache fetch failed with: %s\n",
+ nt_errstr(status)));
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ if (flags & DS_BACKGROUND_ONLY) {
+ return status;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ struct netr_DsRGetDCNameInfo *dc_info;
+
+ status = dsgetdcname(mem_ctx, msg_ctx, domain_name,
+ domain_guid, site_name,
+ flags | DS_FORCE_REDISCOVERY,
+ &dc_info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *info = dc_info;
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool check_allowed_required_flags(uint32_t flags,
+ const char *site_name)
+{
+ uint32_t return_type = flags & (DS_RETURN_FLAT_NAME|DS_RETURN_DNS_NAME);
+ uint32_t offered_type = flags & (DS_IS_FLAT_NAME|DS_IS_DNS_NAME);
+ uint32_t query_type = flags & (DS_BACKGROUND_ONLY|DS_FORCE_REDISCOVERY);
+
+ /* FIXME: check for DSGETDC_VALID_FLAGS and check for exclusive bits
+ * (DS_PDC_REQUIRED, DS_KDC_REQUIRED, DS_GC_SERVER_REQUIRED) */
+
+ debug_dsdcinfo_flags(10, flags);
+
+ if ((flags & DS_TRY_NEXTCLOSEST_SITE) && site_name) {
+ return false;
+ }
+
+ if (return_type == (DS_RETURN_FLAT_NAME|DS_RETURN_DNS_NAME)) {
+ return false;
+ }
+
+ if (offered_type == (DS_IS_DNS_NAME|DS_IS_FLAT_NAME)) {
+ return false;
+ }
+
+ if (query_type == (DS_BACKGROUND_ONLY|DS_FORCE_REDISCOVERY)) {
+ return false;
+ }
+
+#if 0
+ if ((flags & DS_RETURN_DNS_NAME) && (!(flags & DS_IP_REQUIRED))) {
+ printf("gd: here5 \n");
+ return false;
+ }
+#endif
+ return true;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS discover_dc_netbios(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ uint32_t flags,
+ struct ip_service_name **returned_dclist,
+ size_t *returned_count)
+{
+ NTSTATUS status;
+ enum nbt_name_type name_type = NBT_NAME_LOGON;
+ struct samba_sockaddr *salist = NULL;
+ size_t i;
+ struct ip_service_name *dclist = NULL;
+ size_t count = 0;
+ static const char *resolve_order[] = { "lmhosts", "wins", "bcast", NULL };
+
+ if (flags & DS_PDC_REQUIRED) {
+ name_type = NBT_NAME_PDC;
+ }
+
+ status = internal_resolve_name(mem_ctx,
+ domain_name,
+ name_type,
+ NULL,
+ &salist,
+ &count,
+ resolve_order);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("discover_dc_netbios: failed to find DC\n"));
+ return status;
+ }
+
+ dclist = talloc_zero_array(mem_ctx, struct ip_service_name, count);
+ if (!dclist) {
+ TALLOC_FREE(salist);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<count; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ struct ip_service_name *r = &dclist[i];
+
+ print_sockaddr(addr, sizeof(addr),
+ &salist[i].u.ss);
+
+ r->sa = salist[i];
+ r->hostname = talloc_strdup(mem_ctx, addr);
+ if (!r->hostname) {
+ TALLOC_FREE(salist);
+ TALLOC_FREE(dclist);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ }
+
+ TALLOC_FREE(salist);
+
+ *returned_dclist = dclist;
+ *returned_count = count;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS discover_dc_dns(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ uint32_t flags,
+ const char *site_name,
+ struct ip_service_name **returned_dclist,
+ size_t *return_count)
+{
+ size_t i;
+ NTSTATUS status;
+ struct dns_rr_srv *dcs = NULL;
+ size_t numdcs = 0;
+ struct ip_service_name *dclist = NULL;
+ size_t ret_count = 0;
+ char *query = NULL;
+
+ if (flags & DS_PDC_REQUIRED) {
+ query = ads_dns_query_string_pdc(mem_ctx, domain_name);
+ } else if (flags & DS_GC_SERVER_REQUIRED) {
+ query = ads_dns_query_string_gcs(mem_ctx, domain_name);
+ } else if (flags & DS_KDC_REQUIRED) {
+ query = ads_dns_query_string_kdcs(mem_ctx, domain_name);
+ } else if (flags & DS_DIRECTORY_SERVICE_REQUIRED) {
+ query = ads_dns_query_string_dcs(mem_ctx, domain_name);
+ } else if (domain_guid) {
+ query = ads_dns_query_string_dcs_guid(
+ mem_ctx, domain_guid, domain_name);
+ } else {
+ query = ads_dns_query_string_dcs(mem_ctx, domain_name);
+ }
+
+ if (query == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = ads_dns_query_srv(
+ mem_ctx,
+ lp_get_async_dns_timeout(),
+ site_name,
+ query,
+ &dcs,
+ &numdcs);
+ TALLOC_FREE(query);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (numdcs == 0) {
+ TALLOC_FREE(dcs);
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ /* Check for integer wrap. */
+ if (numdcs + numdcs < numdcs) {
+ TALLOC_FREE(dcs);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * We're only returning up to 2 addresses per
+ * DC name, so just allocate size numdcs x 2.
+ */
+
+ dclist = talloc_zero_array(mem_ctx,
+ struct ip_service_name,
+ numdcs * 2);
+ if (!dclist) {
+ TALLOC_FREE(dcs);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * First, copy the SRV record replies that
+ * have IP addresses returned with them.
+ */
+ ret_count = 0;
+ for (i = 0; i < numdcs; i++) {
+ size_t j;
+ bool have_v4_addr = false;
+ bool have_v6_addr = false;
+
+ if (dcs[i].num_ips == 0) {
+ continue;
+ }
+
+ /*
+ * Pick up to 1 address from each address
+ * family (IPv4, IPv6).
+ *
+ * This is different from the previous
+ * code which picked a 'next ip' address
+ * each time, incrementing an index.
+ * Too complex to maintain :-(.
+ */
+ for (j = 0; j < dcs[i].num_ips; j++) {
+ if ((dcs[i].ss_s[j].ss_family == AF_INET && !have_v4_addr) ||
+ (dcs[i].ss_s[j].ss_family == AF_INET6 && !have_v6_addr)) {
+ bool ok;
+ dclist[ret_count].hostname =
+ talloc_strdup(dclist, dcs[i].hostname);
+ ok = sockaddr_storage_to_samba_sockaddr(
+ &dclist[ret_count].sa,
+ &dcs[i].ss_s[j]);
+ if (!ok) {
+ TALLOC_FREE(dcs);
+ TALLOC_FREE(dclist);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ret_count++;
+ if (dcs[i].ss_s[j].ss_family == AF_INET) {
+ have_v4_addr = true;
+ } else {
+ have_v6_addr = true;
+ }
+ if (have_v4_addr && have_v6_addr) {
+ break;
+ }
+ }
+ }
+ }
+
+ TALLOC_FREE(dcs);
+
+ if (ret_count == 0) {
+ TALLOC_FREE(dclist);
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ *returned_dclist = dclist;
+ *return_count = ret_count;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS make_domain_controller_info(TALLOC_CTX *mem_ctx,
+ const char *dc_unc,
+ const char *dc_address,
+ uint32_t dc_address_type,
+ const struct GUID *domain_guid,
+ const char *domain_name,
+ const char *forest_name,
+ uint32_t flags,
+ const char *dc_site_name,
+ const char *client_site_name,
+ struct netr_DsRGetDCNameInfo **info_out)
+{
+ struct netr_DsRGetDCNameInfo *info;
+
+ info = talloc_zero(mem_ctx, struct netr_DsRGetDCNameInfo);
+ NT_STATUS_HAVE_NO_MEMORY(info);
+
+ if (dc_unc) {
+ if (!(dc_unc[0] == '\\' && dc_unc[1] == '\\')) {
+ info->dc_unc = talloc_asprintf(mem_ctx, "\\\\%s",
+ dc_unc);
+ } else {
+ info->dc_unc = talloc_strdup(mem_ctx, dc_unc);
+ }
+ NT_STATUS_HAVE_NO_MEMORY(info->dc_unc);
+ }
+
+ if (dc_address) {
+ if (!(dc_address[0] == '\\' && dc_address[1] == '\\')) {
+ info->dc_address = talloc_asprintf(mem_ctx, "\\\\%s",
+ dc_address);
+ } else {
+ info->dc_address = talloc_strdup(mem_ctx, dc_address);
+ }
+ NT_STATUS_HAVE_NO_MEMORY(info->dc_address);
+ }
+
+ info->dc_address_type = dc_address_type;
+
+ if (domain_guid) {
+ info->domain_guid = *domain_guid;
+ }
+
+ if (domain_name) {
+ info->domain_name = talloc_strdup(mem_ctx, domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->domain_name);
+ }
+
+ if (forest_name && *forest_name) {
+ info->forest_name = talloc_strdup(mem_ctx, forest_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->forest_name);
+ flags |= DS_DNS_FOREST_ROOT;
+ }
+
+ info->dc_flags = flags;
+
+ if (dc_site_name) {
+ info->dc_site_name = talloc_strdup(mem_ctx, dc_site_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->dc_site_name);
+ }
+
+ if (client_site_name) {
+ info->client_site_name = talloc_strdup(mem_ctx,
+ client_site_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->client_site_name);
+ }
+
+ *info_out = info;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static void map_dc_and_domain_names(uint32_t flags,
+ const char *dc_name,
+ const char *domain_name,
+ const char *dns_dc_name,
+ const char *dns_domain_name,
+ uint32_t *dc_flags,
+ const char **hostname_p,
+ const char **domain_p)
+{
+ switch (flags & 0xf0000000) {
+ case DS_RETURN_FLAT_NAME:
+ if (dc_name && domain_name &&
+ *dc_name && *domain_name) {
+ *hostname_p = dc_name;
+ *domain_p = domain_name;
+ break;
+ }
+
+ FALL_THROUGH;
+ case DS_RETURN_DNS_NAME:
+ default:
+ if (dns_dc_name && dns_domain_name &&
+ *dns_dc_name && *dns_domain_name) {
+ *hostname_p = dns_dc_name;
+ *domain_p = dns_domain_name;
+ *dc_flags |= DS_DNS_DOMAIN | DS_DNS_CONTROLLER;
+ break;
+ }
+ if (dc_name && domain_name &&
+ *dc_name && *domain_name) {
+ *hostname_p = dc_name;
+ *domain_p = domain_name;
+ break;
+ }
+ }
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS make_dc_info_from_cldap_reply(
+ TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ const struct samba_sockaddr *sa,
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX *r,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ const char *dc_hostname = NULL;
+ const char *dc_domain_name = NULL;
+ const char *dc_address = NULL;
+ const char *dc_forest = NULL;
+ uint32_t dc_address_type = 0;
+ uint32_t dc_flags = 0;
+ struct GUID *dc_domain_guid = NULL;
+ const char *dc_server_site = NULL;
+ const char *dc_client_site = NULL;
+
+ char addr[INET6_ADDRSTRLEN];
+
+ if (sa != NULL) {
+ print_sockaddr(addr, sizeof(addr), &sa->u.ss);
+ dc_address = addr;
+ dc_address_type = DS_ADDRESS_TYPE_INET;
+ } else {
+ if (r->sockaddr.pdc_ip) {
+ dc_address = r->sockaddr.pdc_ip;
+ dc_address_type = DS_ADDRESS_TYPE_INET;
+ } else {
+ dc_address = r->pdc_name;
+ dc_address_type = DS_ADDRESS_TYPE_NETBIOS;
+ }
+ }
+
+ map_dc_and_domain_names(flags,
+ r->pdc_name,
+ r->domain_name,
+ r->pdc_dns_name,
+ r->dns_domain,
+ &dc_flags,
+ &dc_hostname,
+ &dc_domain_name);
+
+ dc_flags |= r->server_type;
+ dc_forest = r->forest;
+ dc_domain_guid = &r->domain_uuid;
+ dc_server_site = r->server_site;
+ dc_client_site = r->client_site;
+
+ return make_domain_controller_info(mem_ctx,
+ dc_hostname,
+ dc_address,
+ dc_address_type,
+ dc_domain_guid,
+ dc_domain_name,
+ dc_forest,
+ dc_flags,
+ dc_server_site,
+ dc_client_site,
+ info);
+}
+
+/****************************************************************
+****************************************************************/
+
+static uint32_t map_ds_flags_to_nt_version(uint32_t flags)
+{
+ uint32_t nt_version = 0;
+
+ if (flags & DS_PDC_REQUIRED) {
+ nt_version |= NETLOGON_NT_VERSION_PDC;
+ }
+
+ if (flags & DS_GC_SERVER_REQUIRED) {
+ nt_version |= NETLOGON_NT_VERSION_GC;
+ }
+
+ if (flags & DS_TRY_NEXTCLOSEST_SITE) {
+ nt_version |= NETLOGON_NT_VERSION_WITH_CLOSEST_SITE;
+ }
+
+ if (flags & DS_IP_REQUIRED) {
+ nt_version |= NETLOGON_NT_VERSION_IP;
+ }
+
+ return nt_version;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS process_dc_dns(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ uint32_t flags,
+ struct ip_service_name *dclist,
+ size_t num_dcs,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ size_t i = 0;
+ bool valid_dc = false;
+ struct netlogon_samlogon_response *r = NULL;
+ uint32_t nt_version = NETLOGON_NT_VERSION_5 |
+ NETLOGON_NT_VERSION_5EX;
+ uint32_t ret_flags = 0;
+ NTSTATUS status;
+
+ nt_version |= map_ds_flags_to_nt_version(flags);
+
+ for (i=0; i<num_dcs; i++) {
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), &dclist[i].sa.u.ss);
+
+ DEBUG(10,("LDAP ping to %s (%s)\n", dclist[i].hostname, addr));
+
+ if (ads_cldap_netlogon(mem_ctx, &dclist[i].sa.u.ss,
+ domain_name,
+ nt_version,
+ &r))
+ {
+ nt_version = r->ntver;
+ ret_flags = get_cldap_reply_server_flags(r, nt_version);
+
+ if (check_cldap_reply_required_flags(ret_flags, flags)) {
+ valid_dc = true;
+ break;
+ }
+ }
+
+ continue;
+ }
+
+ if (!valid_dc) {
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ status = make_dc_info_from_cldap_reply(mem_ctx, flags, &dclist[i].sa,
+ &r->data.nt5_ex, info);
+ if (NT_STATUS_IS_OK(status)) {
+ return store_cldap_reply(mem_ctx, flags, &dclist[i].sa,
+ nt_version, &r->data.nt5_ex);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS process_dc_netbios(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ uint32_t flags,
+ struct ip_service_name *dclist,
+ size_t num_dcs,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ enum nbt_name_type name_type = NBT_NAME_LOGON;
+ NTSTATUS status;
+ size_t i;
+ const char *dc_name = NULL;
+ fstring tmp_dc_name;
+ struct netlogon_samlogon_response *r = NULL;
+ bool store_cache = false;
+ uint32_t nt_version = NETLOGON_NT_VERSION_1 |
+ NETLOGON_NT_VERSION_5 |
+ NETLOGON_NT_VERSION_5EX_WITH_IP;
+ size_t len = strlen(lp_netbios_name());
+ char my_acct_name[len+2];
+
+ if (msg_ctx == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (flags & DS_PDC_REQUIRED) {
+ name_type = NBT_NAME_PDC;
+ }
+
+ nt_version |= map_ds_flags_to_nt_version(flags);
+
+ snprintf(my_acct_name,
+ sizeof(my_acct_name),
+ "%s$",
+ lp_netbios_name());
+
+ DEBUG(10,("process_dc_netbios\n"));
+
+ for (i=0; i<num_dcs; i++) {
+ uint16_t val;
+
+ generate_random_buffer((uint8_t *)&val, 2);
+
+ status = nbt_getdc(msg_ctx, 10, &dclist[i].sa.u.ss, domain_name,
+ NULL, my_acct_name, ACB_WSTRUST, nt_version,
+ mem_ctx, &nt_version, &dc_name, &r);
+ if (NT_STATUS_IS_OK(status)) {
+ store_cache = true;
+ namecache_store(dc_name,
+ NBT_NAME_SERVER,
+ 1,
+ &dclist[i].sa);
+ goto make_reply;
+ }
+
+ if (name_status_find(domain_name,
+ name_type,
+ NBT_NAME_SERVER,
+ &dclist[i].sa.u.ss,
+ tmp_dc_name))
+ {
+ struct NETLOGON_SAM_LOGON_RESPONSE_NT40 logon1;
+
+ r = talloc_zero(mem_ctx, struct netlogon_samlogon_response);
+ NT_STATUS_HAVE_NO_MEMORY(r);
+
+ ZERO_STRUCT(logon1);
+
+ nt_version = NETLOGON_NT_VERSION_1;
+
+ logon1.nt_version = nt_version;
+ logon1.pdc_name = tmp_dc_name;
+ logon1.domain_name = talloc_strdup_upper(mem_ctx, domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(logon1.domain_name);
+
+ r->data.nt4 = logon1;
+ r->ntver = nt_version;
+
+ map_netlogon_samlogon_response(r);
+
+ namecache_store(tmp_dc_name,
+ NBT_NAME_SERVER,
+ 1,
+ &dclist[i].sa);
+
+ goto make_reply;
+ }
+ }
+
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+
+ make_reply:
+
+ status = make_dc_info_from_cldap_reply(mem_ctx, flags, &dclist[i].sa,
+ &r->data.nt5_ex, info);
+ if (NT_STATUS_IS_OK(status) && store_cache) {
+ return store_cldap_reply(mem_ctx, flags, &dclist[i].sa,
+ nt_version, &r->data.nt5_ex);
+ }
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS dsgetdcname_rediscover(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ uint32_t flags,
+ const char *site_name,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ NTSTATUS status;
+ struct ip_service_name *dclist = NULL;
+ size_t num_dcs = 0;
+
+ DEBUG(10,("dsgetdcname_rediscover\n"));
+
+ if (flags & DS_IS_FLAT_NAME) {
+
+ if (lp_disable_netbios()) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = discover_dc_netbios(mem_ctx, domain_name, flags,
+ &dclist, &num_dcs);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ return process_dc_netbios(mem_ctx, msg_ctx, domain_name, flags,
+ dclist, num_dcs, info);
+ }
+
+ if (flags & DS_IS_DNS_NAME) {
+
+ status = discover_dc_dns(mem_ctx, domain_name, domain_guid,
+ flags, site_name, &dclist, &num_dcs);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ return process_dc_dns(mem_ctx, domain_name, flags,
+ dclist, num_dcs, info);
+ }
+
+ status = discover_dc_dns(mem_ctx, domain_name, domain_guid, flags,
+ site_name, &dclist, &num_dcs);
+
+ if (NT_STATUS_IS_OK(status) && num_dcs != 0) {
+
+ status = process_dc_dns(mem_ctx, domain_name, flags, dclist,
+ num_dcs, info);
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (lp_disable_netbios()) {
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ status = discover_dc_netbios(mem_ctx, domain_name, flags, &dclist,
+ &num_dcs);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ return process_dc_netbios(mem_ctx, msg_ctx, domain_name, flags, dclist,
+ num_dcs, info);
+}
+
+static bool is_closest_site(struct netr_DsRGetDCNameInfo *info)
+{
+ if (info->dc_flags & DS_SERVER_CLOSEST) {
+ return true;
+ }
+
+ if (!info->client_site_name) {
+ return true;
+ }
+
+ if (!info->dc_site_name) {
+ return false;
+ }
+
+ if (strcmp(info->client_site_name, info->dc_site_name) == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+/********************************************************************
+ Internal dsgetdcname.
+********************************************************************/
+
+static NTSTATUS dsgetdcname_internal(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ const char *site_name,
+ uint32_t flags,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ NTSTATUS status;
+ struct netr_DsRGetDCNameInfo *myinfo = NULL;
+ bool first = true;
+ struct netr_DsRGetDCNameInfo *first_info = NULL;
+
+ DEBUG(10,("dsgetdcname_internal: domain_name: %s, "
+ "domain_guid: %s, site_name: %s, flags: 0x%08x\n",
+ domain_name,
+ domain_guid ? GUID_string(mem_ctx, domain_guid) : "(null)",
+ site_name ? site_name : "(null)", flags));
+
+ *info = NULL;
+
+ if (!check_allowed_required_flags(flags, site_name)) {
+ DEBUG(0,("invalid flags specified\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (flags & DS_FORCE_REDISCOVERY) {
+ goto rediscover;
+ }
+
+ status = dsgetdcname_cached(mem_ctx, msg_ctx, domain_name, domain_guid,
+ flags, site_name, &myinfo);
+ if (NT_STATUS_IS_OK(status)) {
+ *info = myinfo;
+ goto done;
+ }
+
+ if (flags & DS_BACKGROUND_ONLY) {
+ goto done;
+ }
+
+ rediscover:
+ status = dsgetdcname_rediscover(mem_ctx, msg_ctx, domain_name,
+ domain_guid, flags, site_name,
+ &myinfo);
+
+ done:
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!first) {
+ *info = first_info;
+ return NT_STATUS_OK;
+ }
+ return status;
+ }
+
+ if (!first) {
+ TALLOC_FREE(first_info);
+ } else if (!is_closest_site(myinfo)) {
+ first = false;
+ first_info = myinfo;
+ /* TODO: may use the next_closest_site here */
+ site_name = myinfo->client_site_name;
+ goto rediscover;
+ }
+
+ *info = myinfo;
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ dsgetdcname.
+
+ This will be the only public function here.
+********************************************************************/
+
+NTSTATUS dsgetdcname(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ const char *site_name,
+ uint32_t flags,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ NTSTATUS status;
+ const char *query_site = NULL;
+ char *ptr_to_free = NULL;
+ bool retry_query_with_null = false;
+
+ if ((site_name == NULL) || (site_name[0] == '\0')) {
+ ptr_to_free = sitename_fetch(mem_ctx, domain_name);
+ if (ptr_to_free != NULL) {
+ retry_query_with_null = true;
+ }
+ query_site = ptr_to_free;
+ } else {
+ query_site = site_name;
+ }
+
+ status = dsgetdcname_internal(mem_ctx,
+ msg_ctx,
+ domain_name,
+ domain_guid,
+ query_site,
+ flags,
+ info);
+
+ TALLOC_FREE(ptr_to_free);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ return status;
+ }
+
+ /* Should we try again with site_name == NULL ? */
+ if (retry_query_with_null) {
+ status = dsgetdcname_internal(mem_ctx,
+ msg_ctx,
+ domain_name,
+ domain_guid,
+ NULL,
+ flags,
+ info);
+ }
+
+ return status;
+}
+
+NTSTATUS dsgetonedcname(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ const char *dcname,
+ uint32_t flags,
+ struct netr_DsRGetDCNameInfo **info)
+{
+ NTSTATUS status;
+ struct sockaddr_storage *addrs;
+ unsigned int num_addrs, i;
+ const char *hostname = strip_hostname(dcname);
+
+ status = resolve_name_list(mem_ctx, hostname, 0x20,
+ &addrs, &num_addrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (i = 0; i < num_addrs; i++) {
+
+ bool ok;
+ struct ip_service_name dclist;
+
+ dclist.hostname = hostname;
+ ok = sockaddr_storage_to_samba_sockaddr(&dclist.sa, &addrs[i]);
+ if (!ok) {
+ TALLOC_FREE(addrs);
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ status = process_dc_dns(mem_ctx, domain_name, flags,
+ &dclist, 1, info);
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(addrs);
+ return NT_STATUS_OK;
+ }
+
+ if (lp_disable_netbios()) {
+ continue;
+ }
+
+ status = process_dc_netbios(mem_ctx, msg_ctx, domain_name, flags,
+ &dclist, 1, info);
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(addrs);
+ return NT_STATUS_OK;
+ }
+ }
+
+ TALLOC_FREE(addrs);
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+}
diff --git a/source3/libsmb/dsgetdcname.h b/source3/libsmb/dsgetdcname.h
new file mode 100644
index 0000000..241b721
--- /dev/null
+++ b/source3/libsmb/dsgetdcname.h
@@ -0,0 +1,43 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Copyright (C) Volker Lendecke 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBSMB_DSGETDCNAME_H__
+#define __LIBSMB_DSGETDCNAME_H__
+
+#include "replace.h"
+#include <talloc.h>
+#include "librpc/gen_ndr/misc.h"
+
+struct netr_DsRGetDCNameInfo;
+struct messaging_context;
+
+NTSTATUS dsgetdcname(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ const char *site_name,
+ uint32_t flags,
+ struct netr_DsRGetDCNameInfo **info);
+
+NTSTATUS dsgetonedcname(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *domain_name,
+ const char *dcname,
+ uint32_t flags,
+ struct netr_DsRGetDCNameInfo **info);
+#endif
diff --git a/source3/libsmb/errormap.c b/source3/libsmb/errormap.c
new file mode 100644
index 0000000..fd7d6a1
--- /dev/null
+++ b/source3/libsmb/errormap.c
@@ -0,0 +1,295 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * error mapping functions
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Tim Potter 2000
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+/* dos -> nt status error map */
+static const struct {
+ uint8_t dos_class;
+ uint32_t dos_code;
+ NTSTATUS ntstatus;
+} dos_to_ntstatus_map[] = {
+ {ERRDOS, ERRbadfunc, NT_STATUS_NOT_IMPLEMENTED},
+ {ERRDOS, ERRbadfile, NT_STATUS_NO_SUCH_FILE},
+ {ERRDOS, ERRbadpath, NT_STATUS_OBJECT_PATH_NOT_FOUND},
+ {ERRDOS, ERRnofids, NT_STATUS_TOO_MANY_OPENED_FILES},
+ {ERRDOS, ERRnoaccess, NT_STATUS_ACCESS_DENIED},
+ {ERRDOS, ERRbadfid, NT_STATUS_INVALID_HANDLE},
+ {ERRDOS, ERRnomem, NT_STATUS_INSUFFICIENT_RESOURCES},
+ {ERRDOS, ERRbadaccess, NT_STATUS_ACCESS_DENIED},
+ {ERRDOS, ERRbaddata, NT_STATUS_DATA_ERROR},
+ {ERRDOS, 14, NT_STATUS_SECTION_NOT_EXTENDED},
+ {ERRDOS, ERRremcd, NT_STATUS_DIRECTORY_NOT_EMPTY},
+ {ERRDOS, ERRdiffdevice, NT_STATUS_NOT_SAME_DEVICE},
+ {ERRDOS, ERRnofiles, STATUS_NO_MORE_FILES},
+ {ERRDOS, 19, NT_STATUS_MEDIA_WRITE_PROTECTED},
+ {ERRDOS, 21, NT_STATUS_NO_MEDIA_IN_DEVICE},
+ {ERRDOS, 22, NT_STATUS_INVALID_DEVICE_STATE},
+ {ERRDOS, 23, NT_STATUS_DATA_ERROR},
+ {ERRDOS, 24, NT_STATUS_DATA_ERROR},
+ {ERRDOS, 26, NT_STATUS_DISK_CORRUPT_ERROR},
+ {ERRDOS, 27, NT_STATUS_NONEXISTENT_SECTOR},
+ {ERRDOS, 28, NT_STATUS(0x8000000e)},
+ {ERRDOS, 31, NT_STATUS_UNSUCCESSFUL},
+ {ERRDOS, ERRbadshare, NT_STATUS_SHARING_VIOLATION},
+ {ERRDOS, ERRlock, NT_STATUS_FILE_LOCK_CONFLICT},
+ {ERRDOS, 34, NT_STATUS_WRONG_VOLUME},
+ {ERRDOS, 38, NT_STATUS_END_OF_FILE},
+ {ERRDOS, ERRunsup, NT_STATUS_CTL_FILE_NOT_SUPPORTED},
+ {ERRDOS, 51, NT_STATUS_REMOTE_NOT_LISTENING},
+ {ERRDOS, 52, NT_STATUS_DUPLICATE_NAME},
+ {ERRDOS, 53, NT_STATUS_BAD_NETWORK_PATH},
+ {ERRDOS, 54, NT_STATUS_NETWORK_BUSY},
+ {ERRDOS, 55, NT_STATUS_DEVICE_DOES_NOT_EXIST},
+ {ERRDOS, 56, NT_STATUS_TOO_MANY_COMMANDS},
+ {ERRDOS, 57, NT_STATUS_ADAPTER_HARDWARE_ERROR},
+ {ERRDOS, 58, NT_STATUS_INVALID_NETWORK_RESPONSE},
+ {ERRDOS, 59, NT_STATUS_UNEXPECTED_NETWORK_ERROR},
+ {ERRDOS, 60, NT_STATUS_BAD_REMOTE_ADAPTER},
+ {ERRDOS, 61, NT_STATUS_PRINT_QUEUE_FULL},
+ {ERRDOS, 62, NT_STATUS_NO_SPOOL_SPACE},
+ {ERRDOS, 63, NT_STATUS_PRINT_CANCELLED},
+ {ERRDOS, 64, NT_STATUS_NETWORK_NAME_DELETED},
+ {ERRDOS, 65, NT_STATUS_NETWORK_ACCESS_DENIED},
+ {ERRDOS, 66, NT_STATUS_BAD_DEVICE_TYPE},
+ {ERRDOS, ERRnosuchshare, NT_STATUS_BAD_NETWORK_NAME},
+ {ERRDOS, 68, NT_STATUS_TOO_MANY_GUIDS_REQUESTED},
+ {ERRDOS, 69, NT_STATUS_TOO_MANY_SESSIONS},
+ {ERRDOS, 70, NT_STATUS_SHARING_PAUSED},
+ {ERRDOS, 71, NT_STATUS_REQUEST_NOT_ACCEPTED},
+ {ERRDOS, 72, NT_STATUS_REDIRECTOR_PAUSED},
+ {ERRDOS, ERRfilexists, NT_STATUS_OBJECT_NAME_COLLISION},
+ {ERRDOS, 86, NT_STATUS_WRONG_PASSWORD},
+ {ERRDOS, 87, NT_STATUS_INVALID_INFO_CLASS},
+ {ERRDOS, 88, NT_STATUS_NET_WRITE_FAULT},
+ {ERRDOS, 109, NT_STATUS_PIPE_BROKEN},
+ {ERRDOS, 111, STATUS_MORE_ENTRIES},
+ {ERRDOS, 112, NT_STATUS_DISK_FULL},
+ {ERRDOS, 121, NT_STATUS_IO_TIMEOUT},
+ {ERRDOS, 122, NT_STATUS_BUFFER_TOO_SMALL},
+ {ERRDOS, ERRinvalidname, NT_STATUS_OBJECT_NAME_INVALID},
+ {ERRDOS, ERRunknownlevel, NT_STATUS_INVALID_LEVEL},
+ {ERRDOS, 126, NT_STATUS_DLL_NOT_FOUND},
+ {ERRDOS, 127, NT_STATUS_PROCEDURE_NOT_FOUND},
+ {ERRDOS, 145, NT_STATUS_DIRECTORY_NOT_EMPTY},
+ {ERRDOS, 154, NT_STATUS_INVALID_VOLUME_LABEL},
+ {ERRDOS, 156, NT_STATUS_SUSPEND_COUNT_EXCEEDED},
+ {ERRDOS, 158, NT_STATUS_NOT_LOCKED},
+ {ERRDOS, 161, NT_STATUS_OBJECT_PATH_INVALID},
+ {ERRDOS, 170, NT_STATUS(0x80000011)},
+ {ERRDOS, 182, NT_STATUS_ORDINAL_NOT_FOUND},
+ {ERRDOS, 183, NT_STATUS_OBJECT_NAME_COLLISION},
+ {ERRDOS, 193, NT_STATUS_BAD_INITIAL_PC},
+ {ERRDOS, 203, NT_STATUS(0xc0000100)},
+ {ERRDOS, 206, NT_STATUS_NAME_TOO_LONG},
+ {ERRDOS, ERRbadpipe, NT_STATUS_INVALID_INFO_CLASS},
+ {ERRDOS, ERRpipebusy, NT_STATUS_INSTANCE_NOT_AVAILABLE},
+ {ERRDOS, ERRpipeclosing, NT_STATUS_PIPE_CLOSING},
+ {ERRDOS, ERRnotconnected, NT_STATUS_PIPE_DISCONNECTED},
+ {ERRDOS, ERRmoredata, NT_STATUS_MORE_PROCESSING_REQUIRED},
+ {ERRDOS, 240, NT_STATUS_VIRTUAL_CIRCUIT_CLOSED},
+ {ERRDOS, 254, NT_STATUS(0x80000013)},
+ {ERRDOS, 255, NT_STATUS_EA_TOO_LARGE},
+ {ERRDOS, 259, NT_STATUS_GUIDS_EXHAUSTED},
+ {ERRDOS, 267, NT_STATUS_NOT_A_DIRECTORY},
+ {ERRDOS, 275, NT_STATUS_EA_TOO_LARGE},
+ {ERRDOS, 276, NT_STATUS_NONEXISTENT_EA_ENTRY},
+ {ERRDOS, 277, NT_STATUS_NONEXISTENT_EA_ENTRY},
+ {ERRDOS, 278, NT_STATUS_NONEXISTENT_EA_ENTRY},
+ {ERRDOS, ERReasnotsupported, NT_STATUS_EAS_NOT_SUPPORTED},
+ {ERRDOS, 288, NT_STATUS_MUTANT_NOT_OWNED},
+ {ERRDOS, 298, NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED},
+ {ERRDOS, 299, NT_STATUS(0x8000000d)},
+ {ERRDOS, 300, NT_STATUS_OPLOCK_NOT_GRANTED},
+ {ERRDOS, 301, NT_STATUS_INVALID_OPLOCK_PROTOCOL},
+ {ERRDOS, 487, NT_STATUS_CONFLICTING_ADDRESSES},
+ {ERRDOS, 534, NT_STATUS_INTEGER_OVERFLOW},
+ {ERRDOS, 535, NT_STATUS_PIPE_CONNECTED},
+ {ERRDOS, 536, NT_STATUS_PIPE_LISTENING},
+ {ERRDOS, 995, NT_STATUS_CANCELLED},
+ {ERRDOS, 997, NT_STATUS(0x00000103)},
+ {ERRDOS, 998, NT_STATUS_ACCESS_VIOLATION},
+ {ERRDOS, 999, NT_STATUS_IN_PAGE_ERROR},
+ {ERRDOS, 1001, NT_STATUS_BAD_INITIAL_STACK},
+ {ERRDOS, 1005, NT_STATUS_UNRECOGNIZED_VOLUME},
+ {ERRDOS, 1006, NT_STATUS_FILE_INVALID},
+ {ERRDOS, 1007, NT_STATUS_FULLSCREEN_MODE},
+ {ERRDOS, 1008, NT_STATUS_NO_TOKEN},
+ {ERRDOS, 1009, NT_STATUS_REGISTRY_CORRUPT},
+ {ERRDOS, 1016, NT_STATUS_REGISTRY_IO_FAILED},
+ {ERRDOS, 1017, NT_STATUS_NOT_REGISTRY_FILE},
+ {ERRDOS, 1018, NT_STATUS_KEY_DELETED},
+ {ERRDOS, 1019, NT_STATUS_NO_LOG_SPACE},
+ {ERRDOS, 1020, NT_STATUS_KEY_HAS_CHILDREN},
+ {ERRDOS, 1021, NT_STATUS_CHILD_MUST_BE_VOLATILE},
+ {ERRDOS, 1022, NT_STATUS(0x0000010c)},
+ {ERRSRV, ERRbadpw, NT_STATUS_WRONG_PASSWORD},
+ {ERRSRV, ERRbaduid, NT_STATUS_USER_SESSION_DELETED},
+ {ERRSRV, ERRbadtype, NT_STATUS_BAD_DEVICE_TYPE},
+ {ERRSRV, ERRaccess, NT_STATUS_NETWORK_ACCESS_DENIED},
+ {ERRSRV, ERRinvnid, NT_STATUS_NETWORK_NAME_DELETED},
+ {ERRSRV, ERRinvnetname, NT_STATUS_BAD_NETWORK_NAME},
+ {ERRSRV, ERRinvdevice, NT_STATUS_BAD_DEVICE_TYPE},
+ {ERRSRV, ERRqfull, NT_STATUS_PRINT_QUEUE_FULL},
+ {ERRSRV, ERRqtoobig, NT_STATUS_NO_SPOOL_SPACE},
+ {ERRSRV, ERRinvpfid, NT_STATUS_PRINT_CANCELLED},
+ {ERRSRV, ERRsmbcmd, NT_STATUS_NOT_IMPLEMENTED},
+ {ERRSRV, ERRbadpermits, NT_STATUS_NETWORK_ACCESS_DENIED},
+ {ERRSRV, ERRpaused, NT_STATUS_SHARING_PAUSED},
+ {ERRSRV, ERRmsgoff, NT_STATUS_REQUEST_NOT_ACCEPTED},
+ {ERRSRV, ERRnoroom, NT_STATUS_DISK_FULL},
+ {ERRSRV, ERRnoresource, NT_STATUS_REQUEST_NOT_ACCEPTED},
+ {ERRSRV, ERRtoomanyuids, NT_STATUS_TOO_MANY_SESSIONS},
+ {ERRSRV, ERRunknownsmb, NT_STATUS_NOT_IMPLEMENTED},
+ {ERRSRV, 123, NT_STATUS_OBJECT_NAME_INVALID},
+ {ERRSRV, 206, NT_STATUS_OBJECT_NAME_INVALID},
+ {ERRHRD, 1, NT_STATUS_NOT_IMPLEMENTED},
+ {ERRHRD, 2, NT_STATUS_NO_SUCH_DEVICE},
+ {ERRHRD, 3, NT_STATUS_OBJECT_PATH_NOT_FOUND},
+ {ERRHRD, 4, NT_STATUS_TOO_MANY_OPENED_FILES},
+ {ERRHRD, 5, NT_STATUS_INVALID_LOCK_SEQUENCE},
+ {ERRHRD, 6, NT_STATUS_INVALID_HANDLE},
+ {ERRHRD, 8, NT_STATUS_INSUFFICIENT_RESOURCES},
+ {ERRHRD, 12, NT_STATUS_INVALID_LOCK_SEQUENCE},
+ {ERRHRD, 13, NT_STATUS_DATA_ERROR},
+ {ERRHRD, 14, NT_STATUS_SECTION_NOT_EXTENDED},
+ {ERRHRD, 16, NT_STATUS_DIRECTORY_NOT_EMPTY},
+ {ERRHRD, 17, NT_STATUS_NOT_SAME_DEVICE},
+ {ERRHRD, 18, NT_STATUS(0x80000006)},
+ {ERRHRD, ERRnowrite, NT_STATUS_MEDIA_WRITE_PROTECTED},
+ {ERRHRD, ERRnotready, NT_STATUS_NO_MEDIA_IN_DEVICE},
+ {ERRHRD, ERRbadcmd, NT_STATUS_INVALID_DEVICE_STATE},
+ {ERRHRD, ERRdata, NT_STATUS_DATA_ERROR},
+ {ERRHRD, ERRbadreq, NT_STATUS_DATA_ERROR},
+ {ERRHRD, ERRbadmedia, NT_STATUS_DISK_CORRUPT_ERROR},
+ {ERRHRD, ERRbadsector, NT_STATUS_NONEXISTENT_SECTOR},
+ {ERRHRD, ERRnopaper, NT_STATUS(0x8000000e)},
+ {ERRHRD, ERRgeneral, NT_STATUS_UNSUCCESSFUL},
+ {ERRHRD, ERRbadshare, NT_STATUS_SHARING_VIOLATION},
+ {ERRHRD, ERRlock, NT_STATUS_FILE_LOCK_CONFLICT},
+ {ERRHRD, ERRwrongdisk, NT_STATUS_WRONG_VOLUME},
+ {ERRHRD, 38, NT_STATUS_END_OF_FILE},
+ {ERRHRD, ERRdiskfull, NT_STATUS_DISK_FULL},
+ {ERRHRD, 50, NT_STATUS_CTL_FILE_NOT_SUPPORTED},
+ {ERRHRD, 51, NT_STATUS_REMOTE_NOT_LISTENING},
+ {ERRHRD, 52, NT_STATUS_DUPLICATE_NAME},
+ {ERRHRD, 53, NT_STATUS_BAD_NETWORK_PATH},
+ {ERRHRD, 54, NT_STATUS_NETWORK_BUSY},
+ {ERRHRD, 55, NT_STATUS_DEVICE_DOES_NOT_EXIST},
+ {ERRHRD, 56, NT_STATUS_TOO_MANY_COMMANDS},
+ {ERRHRD, 57, NT_STATUS_ADAPTER_HARDWARE_ERROR},
+ {ERRHRD, 58, NT_STATUS_INVALID_NETWORK_RESPONSE},
+ {ERRHRD, 59, NT_STATUS_UNEXPECTED_NETWORK_ERROR},
+ {ERRHRD, 60, NT_STATUS_BAD_REMOTE_ADAPTER},
+ {ERRHRD, 61, NT_STATUS_PRINT_QUEUE_FULL},
+ {ERRHRD, 62, NT_STATUS_NO_SPOOL_SPACE},
+ {ERRHRD, 63, NT_STATUS_PRINT_CANCELLED},
+ {ERRHRD, 64, NT_STATUS_NETWORK_NAME_DELETED},
+ {ERRHRD, 65, NT_STATUS_NETWORK_ACCESS_DENIED},
+ {ERRHRD, 66, NT_STATUS_BAD_DEVICE_TYPE},
+ {ERRHRD, 67, NT_STATUS_BAD_NETWORK_NAME},
+ {ERRHRD, 68, NT_STATUS_TOO_MANY_GUIDS_REQUESTED},
+ {ERRHRD, 69, NT_STATUS_TOO_MANY_SESSIONS},
+ {ERRHRD, 70, NT_STATUS_SHARING_PAUSED},
+ {ERRHRD, 71, NT_STATUS_REQUEST_NOT_ACCEPTED},
+ {ERRHRD, 72, NT_STATUS_REDIRECTOR_PAUSED},
+ {ERRHRD, 80, NT_STATUS_OBJECT_NAME_COLLISION},
+ {ERRHRD, 86, NT_STATUS_WRONG_PASSWORD},
+ {ERRHRD, 87, NT_STATUS_INVALID_INFO_CLASS},
+ {ERRHRD, 88, NT_STATUS_NET_WRITE_FAULT},
+ {ERRHRD, 109, NT_STATUS_PIPE_BROKEN},
+ {ERRHRD, 111, STATUS_MORE_ENTRIES},
+ {ERRHRD, 112, NT_STATUS_DISK_FULL},
+ {ERRHRD, 121, NT_STATUS_IO_TIMEOUT},
+ {ERRHRD, 122, NT_STATUS_BUFFER_TOO_SMALL},
+ {ERRHRD, 123, NT_STATUS_OBJECT_NAME_INVALID},
+ {ERRHRD, 124, NT_STATUS_INVALID_LEVEL},
+ {ERRHRD, 126, NT_STATUS_DLL_NOT_FOUND},
+ {ERRHRD, 127, NT_STATUS_PROCEDURE_NOT_FOUND},
+ {ERRHRD, 145, NT_STATUS_DIRECTORY_NOT_EMPTY},
+ {ERRHRD, 154, NT_STATUS_INVALID_VOLUME_LABEL},
+ {ERRHRD, 156, NT_STATUS_SUSPEND_COUNT_EXCEEDED},
+ {ERRHRD, 158, NT_STATUS_NOT_LOCKED},
+ {ERRHRD, 161, NT_STATUS_OBJECT_PATH_INVALID},
+ {ERRHRD, 170, NT_STATUS(0x80000011)},
+ {ERRHRD, 182, NT_STATUS_ORDINAL_NOT_FOUND},
+ {ERRHRD, 183, NT_STATUS_OBJECT_NAME_COLLISION},
+ {ERRHRD, 193, NT_STATUS_BAD_INITIAL_PC},
+ {ERRHRD, 203, NT_STATUS(0xc0000100)},
+ {ERRHRD, 206, NT_STATUS_NAME_TOO_LONG},
+ {ERRHRD, 230, NT_STATUS_INVALID_INFO_CLASS},
+ {ERRHRD, 231, NT_STATUS_INSTANCE_NOT_AVAILABLE},
+ {ERRHRD, 232, NT_STATUS_PIPE_CLOSING},
+ {ERRHRD, 233, NT_STATUS_PIPE_DISCONNECTED},
+ {ERRHRD, 234, STATUS_MORE_ENTRIES},
+ {ERRHRD, 240, NT_STATUS_VIRTUAL_CIRCUIT_CLOSED},
+ {ERRHRD, 254, NT_STATUS(0x80000013)},
+ {ERRHRD, 255, NT_STATUS_EA_TOO_LARGE},
+ {ERRHRD, 259, NT_STATUS_GUIDS_EXHAUSTED},
+ {ERRHRD, 267, NT_STATUS_NOT_A_DIRECTORY},
+ {ERRHRD, 275, NT_STATUS_EA_TOO_LARGE},
+ {ERRHRD, 276, NT_STATUS_NONEXISTENT_EA_ENTRY},
+ {ERRHRD, 277, NT_STATUS_NONEXISTENT_EA_ENTRY},
+ {ERRHRD, 278, NT_STATUS_NONEXISTENT_EA_ENTRY},
+ {ERRHRD, ERReasnotsupported, NT_STATUS_EAS_NOT_SUPPORTED},
+ {ERRHRD, 288, NT_STATUS_MUTANT_NOT_OWNED},
+ {ERRHRD, 298, NT_STATUS_SEMAPHORE_LIMIT_EXCEEDED},
+ {ERRHRD, 299, NT_STATUS(0x8000000d)},
+ {ERRHRD, 300, NT_STATUS_OPLOCK_NOT_GRANTED},
+ {ERRHRD, 301, NT_STATUS_INVALID_OPLOCK_PROTOCOL},
+ {ERRHRD, 487, NT_STATUS_CONFLICTING_ADDRESSES},
+ {ERRHRD, 534, NT_STATUS_INTEGER_OVERFLOW},
+ {ERRHRD, 535, NT_STATUS_PIPE_CONNECTED},
+ {ERRHRD, 536, NT_STATUS_PIPE_LISTENING},
+ {ERRHRD, 995, NT_STATUS_CANCELLED},
+ {ERRHRD, 997, NT_STATUS(0x00000103)},
+ {ERRHRD, 998, NT_STATUS_ACCESS_VIOLATION},
+ {ERRHRD, 999, NT_STATUS_IN_PAGE_ERROR},
+ {ERRHRD, 1001, NT_STATUS_BAD_INITIAL_STACK},
+ {ERRHRD, 1005, NT_STATUS_UNRECOGNIZED_VOLUME},
+ {ERRHRD, 1006, NT_STATUS_FILE_INVALID},
+ {ERRHRD, 1007, NT_STATUS_FULLSCREEN_MODE},
+ {ERRHRD, 1008, NT_STATUS_NO_TOKEN},
+ {ERRHRD, 1009, NT_STATUS_REGISTRY_CORRUPT},
+ {ERRHRD, 1016, NT_STATUS_REGISTRY_IO_FAILED},
+ {ERRHRD, 1017, NT_STATUS_NOT_REGISTRY_FILE},
+ {ERRHRD, 1018, NT_STATUS_KEY_DELETED},
+ {ERRHRD, 1019, NT_STATUS_NO_LOG_SPACE},
+ {ERRHRD, 1020, NT_STATUS_KEY_HAS_CHILDREN},
+ {ERRHRD, 1021, NT_STATUS_CHILD_MUST_BE_VOLATILE},
+ {ERRHRD, 1022, NT_STATUS(0x0000010c)},
+};
+
+/*****************************************************************************
+convert a dos eclas/ecode to a NT status32 code
+ *****************************************************************************/
+NTSTATUS dos_to_ntstatus(uint8_t eclass, uint32_t ecode)
+{
+ size_t i;
+ if (eclass == 0) return NT_STATUS_OK;
+ for (i=0; i < ARRAY_SIZE(dos_to_ntstatus_map); i++) {
+ if (eclass == dos_to_ntstatus_map[i].dos_class &&
+ ecode == dos_to_ntstatus_map[i].dos_code) {
+ return dos_to_ntstatus_map[i].ntstatus;
+ }
+ }
+ return NT_STATUS_UNSUCCESSFUL;
+}
diff --git a/source3/libsmb/errormap_wbc.c b/source3/libsmb/errormap_wbc.c
new file mode 100644
index 0000000..ad523d6
--- /dev/null
+++ b/source3/libsmb/errormap_wbc.c
@@ -0,0 +1,62 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * error mapping functions
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Tim Potter 2000
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "nsswitch/libwbclient/wbclient.h"
+
+/*******************************************************************************
+ Map between wbcErr and NT status.
+*******************************************************************************/
+
+static const struct {
+ wbcErr wbc_err;
+ NTSTATUS nt_status;
+} wbcErr_ntstatus_map[] = {
+ { WBC_ERR_SUCCESS, NT_STATUS_OK },
+ { WBC_ERR_NOT_IMPLEMENTED, NT_STATUS_NOT_IMPLEMENTED },
+ { WBC_ERR_UNKNOWN_FAILURE, NT_STATUS_UNSUCCESSFUL },
+ { WBC_ERR_NO_MEMORY, NT_STATUS_NO_MEMORY },
+ { WBC_ERR_INVALID_SID, NT_STATUS_INVALID_SID },
+ { WBC_ERR_INVALID_PARAM, NT_STATUS_INVALID_PARAMETER },
+ { WBC_ERR_WINBIND_NOT_AVAILABLE, NT_STATUS_SERVER_DISABLED },
+ { WBC_ERR_DOMAIN_NOT_FOUND, NT_STATUS_NO_SUCH_DOMAIN },
+ { WBC_ERR_INVALID_RESPONSE, NT_STATUS_INVALID_NETWORK_RESPONSE },
+ { WBC_ERR_NSS_ERROR, NT_STATUS_INTERNAL_ERROR },
+ { WBC_ERR_AUTH_ERROR, NT_STATUS_LOGON_FAILURE },
+ { WBC_ERR_UNKNOWN_USER, NT_STATUS_NO_SUCH_USER },
+ { WBC_ERR_UNKNOWN_GROUP, NT_STATUS_NO_SUCH_GROUP },
+ { WBC_ERR_PWD_CHANGE_FAILED, NT_STATUS_PASSWORD_RESTRICTION }
+};
+
+NTSTATUS map_nt_error_from_wbcErr(wbcErr wbc_err)
+{
+ int i;
+
+ /* Look through list */
+ for (i=0;i<ARRAY_SIZE(wbcErr_ntstatus_map);i++) {
+ if (wbcErr_ntstatus_map[i].wbc_err == wbc_err) {
+ return wbcErr_ntstatus_map[i].nt_status;
+ }
+ }
+
+ /* Default return */
+ return NT_STATUS_UNSUCCESSFUL;
+}
diff --git a/source3/libsmb/errormap_wbc.h b/source3/libsmb/errormap_wbc.h
new file mode 100644
index 0000000..6323dee
--- /dev/null
+++ b/source3/libsmb/errormap_wbc.h
@@ -0,0 +1,29 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * error mapping functions
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Tim Potter 2000
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LIBSMB_ERRORMAP_WBC_H_
+#define _LIBSMB_ERRORMAP_WBC_H_
+
+/* The following definitions come from libsmb/errormap_wbc.c */
+
+NTSTATUS map_nt_error_from_wbcErr(wbcErr wbc_err);
+
+#endif /* _LIBSMB_ERRORMAP_WBC_H_ */
diff --git a/source3/libsmb/libsmb.h b/source3/libsmb/libsmb.h
new file mode 100644
index 0000000..6df06ae
--- /dev/null
+++ b/source3/libsmb/libsmb.h
@@ -0,0 +1,31 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 1992-1998,2001
+ Copyright (C) Jeremy Allison 1998
+ Copyright (C) Remus Koos 2001
+ Copyright (C) Andrew Bartlett 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBSMB_LIBSMB_H
+#define _LIBSMB_LIBSMB_H
+
+#include "client.h"
+#include "libads/ads_status.h"
+#include "libsmb/proto.h"
+#include "libsmb/cli_smb2_fnum.h"
+
+#endif /* _LIBSMB_LIBSMB_H */
diff --git a/source3/libsmb/libsmb_cache.c b/source3/libsmb/libsmb_cache.c
new file mode 100644
index 0000000..91292b1
--- /dev/null
+++ b/source3/libsmb/libsmb_cache.c
@@ -0,0 +1,242 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB client library implementation (server cache)
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+
+/*
+ * Structure we use if internal caching mechanism is used
+ * nothing fancy here.
+ */
+struct smbc_server_cache {
+ char *server_name;
+ char *share_name;
+ char *workgroup;
+ char *username;
+ SMBCSRV *server;
+
+ struct smbc_server_cache *next, *prev;
+};
+
+
+
+/*
+ * Add a new connection to the server cache.
+ * This function is only used if the external cache is not enabled
+ */
+int
+SMBC_add_cached_server(SMBCCTX * context,
+ SMBCSRV * newsrv,
+ const char * server,
+ const char * share,
+ const char * workgroup,
+ const char * username)
+{
+ struct smbc_server_cache * srvcache = NULL;
+
+ srvcache = SMB_CALLOC_ARRAY(struct smbc_server_cache, 1);
+ if (srvcache == NULL) {
+ DEBUG(3, ("Not enough space for server cache allocation\n"));
+ errno = ENOMEM;
+ return 1;
+ }
+
+ srvcache->server = newsrv;
+
+ srvcache->server_name = SMB_STRDUP(server);
+ if (!srvcache->server_name) {
+ goto nomem;
+ }
+
+ srvcache->share_name = SMB_STRDUP(share);
+ if (!srvcache->share_name) {
+ goto nomem;
+ }
+
+ srvcache->workgroup = SMB_STRDUP(workgroup);
+ if (!srvcache->workgroup) {
+ goto nomem;
+ }
+
+ srvcache->username = SMB_STRDUP(username);
+ if (!srvcache->username) {
+ goto nomem;
+ }
+
+ DLIST_ADD(context->internal->server_cache, srvcache);
+ return 0;
+
+nomem:
+ SAFE_FREE(srvcache->server_name);
+ SAFE_FREE(srvcache->share_name);
+ SAFE_FREE(srvcache->workgroup);
+ SAFE_FREE(srvcache->username);
+ SAFE_FREE(srvcache);
+ errno = ENOMEM;
+
+ return 1;
+}
+
+
+
+/*
+ * Search the server cache for a server
+ * returns server handle on success, NULL on error (not found)
+ * This function is only used if the external cache is not enabled
+ */
+SMBCSRV *
+SMBC_get_cached_server(SMBCCTX * context,
+ const char * server,
+ const char * share,
+ const char * workgroup,
+ const char * user)
+{
+ struct smbc_server_cache * srv = NULL;
+
+ /* Search the cache lines */
+ for (srv = context->internal->server_cache; srv; srv = srv->next) {
+
+ if (strcmp(server,srv->server_name) == 0 &&
+ strcmp(workgroup,srv->workgroup) == 0 &&
+ strcmp(user, srv->username) == 0) {
+
+ /* If the share name matches, we're cool */
+ if (strcmp(share, srv->share_name) == 0) {
+ return srv->server;
+ }
+
+ /*
+ * We only return an empty share name or the attribute
+ * server on an exact match (which would have been
+ * caught above).
+ */
+ if (*share == '\0' || strcmp(share, "*IPC$") == 0)
+ continue;
+
+ /*
+ * Never return an empty share name or the attribute
+ * server if it wasn't what was requested.
+ */
+ if (*srv->share_name == '\0' ||
+ strcmp(srv->share_name, "*IPC$") == 0)
+ continue;
+
+ /*
+ * If we're only allowing one share per server, then
+ * a connection to the server (other than the
+ * attribute server connection) is cool.
+ */
+ if (smbc_getOptionOneSharePerServer(context)) {
+ NTSTATUS status;
+ /*
+ * The currently connected share name
+ * doesn't match the requested share, so
+ * disconnect from the current share.
+ */
+ status = cli_tdis(srv->server->cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Sigh. Couldn't disconnect. */
+ cli_shutdown(srv->server->cli);
+ srv->server->cli = NULL;
+ smbc_getFunctionRemoveCachedServer(context)(context, srv->server);
+ continue;
+ }
+
+ /*
+ * Save the new share name. We've
+ * disconnected from the old share, and are
+ * about to connect to the new one.
+ */
+ SAFE_FREE(srv->share_name);
+ srv->share_name = SMB_STRDUP(share);
+ if (!srv->share_name) {
+ /* Out of memory. */
+ cli_shutdown(srv->server->cli);
+ srv->server->cli = NULL;
+ smbc_getFunctionRemoveCachedServer(context)(context, srv->server);
+ continue;
+ }
+
+ return srv->server;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Search the server cache for a server and remove it
+ * returns 0 on success
+ * This function is only used if the external cache is not enabled
+ */
+int
+SMBC_remove_cached_server(SMBCCTX * context,
+ SMBCSRV * server)
+{
+ struct smbc_server_cache * srv = NULL;
+
+ for (srv = context->internal->server_cache; srv; srv = srv->next) {
+ if (server == srv->server) {
+
+ /* remove this sucker */
+ DLIST_REMOVE(context->internal->server_cache, srv);
+ SAFE_FREE(srv->server_name);
+ SAFE_FREE(srv->share_name);
+ SAFE_FREE(srv->workgroup);
+ SAFE_FREE(srv->username);
+ SAFE_FREE(srv);
+ return 0;
+ }
+ }
+ /* server not found */
+ return 1;
+}
+
+
+/*
+ * Try to remove all the servers in cache
+ * returns 1 on failure and 0 if all servers could be removed.
+ */
+int
+SMBC_purge_cached_servers(SMBCCTX * context)
+{
+ struct smbc_server_cache * srv;
+ struct smbc_server_cache * next;
+ int could_not_purge_all = 0;
+
+ for (srv = context->internal->server_cache,
+ next = (srv ? srv->next :NULL);
+ srv;
+ srv = next,
+ next = (srv ? srv->next : NULL)) {
+
+ if (SMBC_remove_unused_server(context, srv->server)) {
+ /* could not be removed */
+ could_not_purge_all = 1;
+ }
+ }
+ return could_not_purge_all;
+}
diff --git a/source3/libsmb/libsmb_compat.c b/source3/libsmb/libsmb_compat.c
new file mode 100644
index 0000000..465bb3c
--- /dev/null
+++ b/source3/libsmb/libsmb_compat.c
@@ -0,0 +1,581 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB client library implementation (Old interface compatibility)
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "libsmb_internal.h"
+
+struct smbc_compat_fdlist {
+ SMBCFILE * file;
+ int fd;
+ struct smbc_compat_fdlist *next, *prev;
+};
+
+static SMBCCTX * statcont = NULL;
+static int smbc_compat_initialized = 0;
+static int smbc_compat_nextfd = 0;
+static struct smbc_compat_fdlist * smbc_compat_fd_in_use = NULL;
+static struct smbc_compat_fdlist * smbc_compat_fd_avail = NULL;
+
+/* Find an fd and return the SMBCFILE * or NULL on failure */
+static SMBCFILE *
+find_fd(int fd)
+{
+ struct smbc_compat_fdlist * f = smbc_compat_fd_in_use;
+ while (f) {
+ if (f->fd == fd)
+ return f->file;
+ f = f->next;
+ }
+ return NULL;
+}
+
+/* Add an fd, returns 0 on success, -1 on error with errno set */
+static int
+add_fd(SMBCFILE * file)
+{
+ struct smbc_compat_fdlist * f = smbc_compat_fd_avail;
+
+ if (f) {
+ /* We found one that's available */
+ DLIST_REMOVE(smbc_compat_fd_avail, f);
+ } else {
+ /*
+ * None were available, so allocate one. Keep the number of
+ * file descriptors determinate. This allows the application
+ * to allocate bitmaps or mapping of file descriptors based on
+ * a known maximum number of file descriptors that will ever
+ * be returned.
+ */
+ if (smbc_compat_nextfd >= FD_SETSIZE) {
+ errno = EMFILE;
+ return -1;
+ }
+
+ f = SMB_MALLOC_P(struct smbc_compat_fdlist);
+ if (!f) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ f->fd = SMBC_BASE_FD + smbc_compat_nextfd++;
+ }
+
+ f->file = file;
+ DLIST_ADD(smbc_compat_fd_in_use, f);
+
+ return f->fd;
+}
+
+
+
+/* Delete an fd, returns 0 on success */
+static int
+del_fd(int fd)
+{
+ struct smbc_compat_fdlist * f = smbc_compat_fd_in_use;
+
+ while (f) {
+ if (f->fd == fd)
+ break;
+ f = f->next;
+ }
+
+ if (f) {
+ /* found */
+ DLIST_REMOVE(smbc_compat_fd_in_use, f);
+ f->file = NULL;
+ DLIST_ADD(smbc_compat_fd_avail, f);
+ return 0;
+ }
+ return 1;
+}
+
+
+
+int
+smbc_init(smbc_get_auth_data_fn fn,
+ int debug)
+{
+ if (smbc_compat_initialized) {
+ return 0;
+ }
+
+ statcont = smbc_new_context();
+ if (!statcont)
+ return -1;
+
+ smbc_setDebug(statcont, debug);
+ smbc_setFunctionAuthData(statcont, fn);
+
+ if (!smbc_init_context(statcont)) {
+ smbc_free_context(statcont, False);
+ return -1;
+ }
+
+ smbc_compat_initialized = 1;
+
+ return 0;
+}
+
+
+SMBCCTX *
+smbc_set_context(SMBCCTX * context)
+{
+ SMBCCTX *old_context = statcont;
+
+ if (context) {
+ /* Save provided context. It must have been initialized! */
+ statcont = context;
+
+ /* You'd better know what you're doing. We won't help you. */
+ smbc_compat_initialized = 1;
+ }
+
+ return old_context;
+}
+
+
+int
+smbc_open(const char *furl,
+ int flags,
+ mode_t mode)
+{
+ SMBCFILE * file;
+ int fd;
+
+ file = smbc_getFunctionOpen(statcont)(statcont, furl, flags, mode);
+ if (!file)
+ return -1;
+
+ fd = add_fd(file);
+ if (fd == -1)
+ smbc_getFunctionClose(statcont)(statcont, file);
+ return fd;
+}
+
+
+int
+smbc_creat(const char *furl,
+ mode_t mode)
+{
+ SMBCFILE * file;
+ int fd;
+
+ file = smbc_getFunctionCreat(statcont)(statcont, furl, mode);
+ if (!file)
+ return -1;
+
+ fd = add_fd(file);
+ if (fd == -1) {
+ /* Hmm... should we delete the file too ? I guess we could try */
+ smbc_getFunctionClose(statcont)(statcont, file);
+ smbc_getFunctionUnlink(statcont)(statcont, furl);
+ }
+ return fd;
+}
+
+
+ssize_t
+smbc_read(int fd,
+ void *buf,
+ size_t bufsize)
+{
+ SMBCFILE * file = find_fd(fd);
+ return smbc_getFunctionRead(statcont)(statcont, file, buf, bufsize);
+}
+
+ssize_t
+smbc_write(int fd,
+ const void *buf,
+ size_t bufsize)
+{
+ SMBCFILE * file = find_fd(fd);
+ return smbc_getFunctionWrite(statcont)(statcont, file, buf, bufsize);
+}
+
+off_t
+smbc_lseek(int fd,
+ off_t offset,
+ int whence)
+{
+ SMBCFILE * file = find_fd(fd);
+ return smbc_getFunctionLseek(statcont)(statcont, file, offset, whence);
+}
+
+int
+smbc_close(int fd)
+{
+ SMBCFILE * file = find_fd(fd);
+ del_fd(fd);
+ return smbc_getFunctionClose(statcont)(statcont, file);
+}
+
+int
+smbc_unlink(const char *fname)
+{
+ return smbc_getFunctionUnlink(statcont)(statcont, fname);
+}
+
+int
+smbc_rename(const char *ourl,
+ const char *nurl)
+{
+ return smbc_getFunctionRename(statcont)(statcont, ourl,
+ statcont, nurl);
+}
+
+int
+smbc_opendir(const char *durl)
+{
+ SMBCFILE * file;
+ int fd;
+
+ file = smbc_getFunctionOpendir(statcont)(statcont, durl);
+ if (!file)
+ return -1;
+
+ fd = add_fd(file);
+ if (fd == -1)
+ smbc_getFunctionClosedir(statcont)(statcont, file);
+
+ return fd;
+}
+
+int
+smbc_closedir(int dh)
+{
+ SMBCFILE * file = find_fd(dh);
+ del_fd(dh);
+ return smbc_getFunctionClosedir(statcont)(statcont, file);
+}
+
+int
+smbc_getdents(unsigned int dh,
+ struct smbc_dirent *dirp,
+ int count)
+{
+ SMBCFILE * file = find_fd(dh);
+ return smbc_getFunctionGetdents(statcont)(statcont, file, dirp, count);
+}
+
+struct smbc_dirent *
+smbc_readdir(unsigned int dh)
+{
+ SMBCFILE * file = find_fd(dh);
+ return smbc_getFunctionReaddir(statcont)(statcont, file);
+}
+
+const struct libsmb_file_info *smbc_readdirplus(unsigned int dh)
+{
+ SMBCFILE * file = find_fd(dh);
+ return smbc_getFunctionReaddirPlus(statcont)(statcont, file);
+}
+
+const struct libsmb_file_info *smbc_readdirplus2(unsigned int dh,
+ struct stat *st)
+{
+ SMBCFILE *file = find_fd(dh);
+ return smbc_getFunctionReaddirPlus2(statcont)(statcont, file, st);
+}
+
+off_t
+smbc_telldir(int dh)
+{
+ SMBCFILE * file = find_fd(dh);
+ return smbc_getFunctionTelldir(statcont)(statcont, file);
+}
+
+int
+smbc_lseekdir(int fd,
+ off_t offset)
+{
+ SMBCFILE * file = find_fd(fd);
+ return smbc_getFunctionLseekdir(statcont)(statcont, file, offset);
+}
+
+int
+smbc_mkdir(const char *durl,
+ mode_t mode)
+{
+ return smbc_getFunctionMkdir(statcont)(statcont, durl, mode);
+}
+
+int
+smbc_rmdir(const char *durl)
+{
+ return smbc_getFunctionRmdir(statcont)(statcont, durl);
+}
+
+int
+smbc_notify(int dh, smbc_bool recursive, uint32_t completion_filter,
+ unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data)
+{
+ SMBCFILE *dir = find_fd(dh);
+ return smbc_getFunctionNotify(statcont)(
+ statcont, dir, recursive, completion_filter,
+ callback_timeout_ms, cb, private_data);
+}
+
+int
+smbc_stat(const char *url,
+ struct stat *st)
+{
+ return smbc_getFunctionStat(statcont)(statcont, url, st);
+}
+
+int
+smbc_fstat(int fd,
+ struct stat *st)
+{
+ SMBCFILE * file = find_fd(fd);
+ return smbc_getFunctionFstat(statcont)(statcont, file, st);
+}
+
+int
+smbc_statvfs(char *path,
+ struct statvfs *st)
+{
+ return smbc_getFunctionStatVFS(statcont)(statcont, path, st);
+}
+
+int
+smbc_fstatvfs(int fd,
+ struct statvfs *st)
+{
+ SMBCFILE * file = find_fd(fd);
+ return smbc_getFunctionFstatVFS(statcont)(statcont, file, st);
+}
+
+int
+smbc_ftruncate(int fd,
+ off_t size)
+{
+ SMBCFILE * file = find_fd(fd);
+ return smbc_getFunctionFtruncate(statcont)(statcont, file, size);
+}
+
+int
+smbc_chmod(const char *url,
+ mode_t mode)
+{
+ return smbc_getFunctionChmod(statcont)(statcont, url, mode);
+}
+
+int
+smbc_utimes(const char *fname,
+ struct timeval *tbuf)
+{
+ return smbc_getFunctionUtimes(statcont)(statcont, fname, tbuf);
+}
+
+#ifdef HAVE_UTIME_H
+int
+smbc_utime(const char *fname,
+ struct utimbuf *utbuf)
+{
+ struct timeval tv[2];
+
+ if (utbuf == NULL)
+ return smbc_getFunctionUtimes(statcont)(statcont, fname, NULL);
+
+ tv[0].tv_sec = utbuf->actime;
+ tv[1].tv_sec = utbuf->modtime;
+ tv[0].tv_usec = tv[1].tv_usec = 0;
+
+ return smbc_getFunctionUtimes(statcont)(statcont, fname, tv);
+}
+#endif
+
+int
+smbc_setxattr(const char *fname,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ return smbc_getFunctionSetxattr(statcont)(statcont,
+ fname, name,
+ value, size, flags);
+}
+
+int
+smbc_lsetxattr(const char *fname,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ return smbc_getFunctionSetxattr(statcont)(statcont,
+ fname, name,
+ value, size, flags);
+}
+
+int
+smbc_fsetxattr(int fd,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ SMBCFILE * file = find_fd(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+ return smbc_getFunctionSetxattr(statcont)(statcont,
+ file->fname, name,
+ value, size, flags);
+}
+
+int
+smbc_getxattr(const char *fname,
+ const char *name,
+ const void *value,
+ size_t size)
+{
+ return smbc_getFunctionGetxattr(statcont)(statcont,
+ fname, name,
+ value, size);
+}
+
+int
+smbc_lgetxattr(const char *fname,
+ const char *name,
+ const void *value,
+ size_t size)
+{
+ return smbc_getFunctionGetxattr(statcont)(statcont,
+ fname, name,
+ value, size);
+}
+
+int
+smbc_fgetxattr(int fd,
+ const char *name,
+ const void *value,
+ size_t size)
+{
+ SMBCFILE * file = find_fd(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+ return smbc_getFunctionGetxattr(statcont)(statcont,
+ file->fname, name,
+ value, size);
+}
+
+int
+smbc_removexattr(const char *fname,
+ const char *name)
+{
+ return smbc_getFunctionRemovexattr(statcont)(statcont, fname, name);
+}
+
+int
+smbc_lremovexattr(const char *fname,
+ const char *name)
+{
+ return smbc_getFunctionRemovexattr(statcont)(statcont, fname, name);
+}
+
+int
+smbc_fremovexattr(int fd,
+ const char *name)
+{
+ SMBCFILE * file = find_fd(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+ return smbc_getFunctionRemovexattr(statcont)(statcont,
+ file->fname, name);
+}
+
+int
+smbc_listxattr(const char *fname,
+ char *list,
+ size_t size)
+{
+ return smbc_getFunctionListxattr(statcont)(statcont,
+ fname, list, size);
+}
+
+int
+smbc_llistxattr(const char *fname,
+ char *list,
+ size_t size)
+{
+ return smbc_getFunctionListxattr(statcont)(statcont,
+ fname, list, size);
+}
+
+int
+smbc_flistxattr(int fd,
+ char *list,
+ size_t size)
+{
+ SMBCFILE * file = find_fd(fd);
+ if (file == NULL) {
+ errno = EBADF;
+ return -1;
+ }
+ return smbc_getFunctionListxattr(statcont)(statcont,
+ file->fname, list, size);
+}
+
+int
+smbc_print_file(const char *fname,
+ const char *printq)
+{
+ return smbc_getFunctionPrintFile(statcont)(statcont, fname,
+ statcont, printq);
+}
+
+int
+smbc_open_print_job(const char *fname)
+{
+ SMBCFILE * file;
+
+ file = smbc_getFunctionOpenPrintJob(statcont)(statcont, fname);
+ if (!file) return -1;
+ return file->cli_fd;
+}
+
+int
+smbc_list_print_jobs(const char *purl,
+ smbc_list_print_job_fn fn)
+{
+ return smbc_getFunctionListPrintJobs(statcont)(statcont, purl, fn);
+}
+
+int
+smbc_unlink_print_job(const char *purl,
+ int id)
+{
+ return smbc_getFunctionUnlinkPrintJob(statcont)(statcont, purl, id);
+}
+
+
diff --git a/source3/libsmb/libsmb_context.c b/source3/libsmb/libsmb_context.c
new file mode 100644
index 0000000..441dec1
--- /dev/null
+++ b/source3/libsmb/libsmb_context.c
@@ -0,0 +1,807 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "secrets.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "lib/param/param.h"
+#include "../lib/util/smb_threads.h"
+#include "../lib/util/smb_threads_internal.h"
+
+/*
+ * Is the logging working / configfile read ?
+ */
+static bool SMBC_initialized = false;
+static unsigned int initialized_ctx_count = 0;
+static void *initialized_ctx_count_mutex = NULL;
+
+/*
+ * Do some module- and library-wide initializations
+ */
+static void
+SMBC_module_init(void * punused)
+{
+ bool conf_loaded = False;
+ char *home = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ setup_logging("libsmbclient", DEBUG_STDOUT);
+
+ /* Here we would open the smb.conf file if needed ... */
+
+ home = getenv("HOME");
+ if (home) {
+ char *conf = NULL;
+ if (asprintf(&conf, "%s/.smb/smb.conf", home) > 0) {
+ if (lp_load_client(conf)) {
+ conf_loaded = True;
+ } else {
+ DEBUG(5, ("Could not load config file: %s\n",
+ conf));
+ }
+ SAFE_FREE(conf);
+ }
+ }
+
+ if (!conf_loaded) {
+ /*
+ * Well, if that failed, try the get_dyn_CONFIGFILE
+ * Which points to the standard locn, and if that
+ * fails, silently ignore it and use the internal
+ * defaults ...
+ */
+
+ if (!lp_load_client(get_dyn_CONFIGFILE())) {
+ DEBUG(5, ("Could not load config file: %s\n",
+ get_dyn_CONFIGFILE()));
+ } else if (home) {
+ char *conf;
+ /*
+ * We loaded the global config file. Now lets
+ * load user-specific modifications to the
+ * global config.
+ */
+ if (asprintf(&conf,
+ "%s/.smb/smb.conf.append",
+ home) > 0) {
+ if (!lp_load_client_no_reinit(conf)) {
+ DEBUG(10,
+ ("Could not append config file: "
+ "%s\n",
+ conf));
+ }
+ SAFE_FREE(conf);
+ }
+ }
+ }
+
+ load_interfaces(); /* Load the list of interfaces ... */
+
+ reopen_logs(); /* Get logging working ... */
+
+ /*
+ * Block SIGPIPE (from lib/util_sock.c: write())
+ * It is not needed and should not stop execution
+ */
+ BlockSignals(True, SIGPIPE);
+
+ /* Create the mutex we'll use to protect initialized_ctx_count */
+ if (SMB_THREAD_CREATE_MUTEX("initialized_ctx_count_mutex",
+ initialized_ctx_count_mutex) != 0) {
+ smb_panic("SMBC_module_init: "
+ "failed to create 'initialized_ctx_count' mutex");
+ }
+
+ TALLOC_FREE(frame);
+}
+
+
+static void
+SMBC_module_terminate(void)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ secrets_shutdown();
+ gfree_all();
+ SMBC_initialized = false;
+ TALLOC_FREE(frame);
+}
+
+
+/*
+ * Get a new empty handle to fill in with your own info
+ */
+SMBCCTX *
+smbc_new_context(void)
+{
+ SMBCCTX *context;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* The first call to this function should initialize the module */
+ SMB_THREAD_ONCE(&SMBC_initialized, SMBC_module_init, NULL);
+
+ /*
+ * All newly added context fields should be placed in
+ * SMBC_internal_data, not directly in SMBCCTX.
+ */
+ context = SMB_MALLOC_P(SMBCCTX);
+ if (!context) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ZERO_STRUCTP(context);
+
+ context->internal = SMB_MALLOC_P(struct SMBC_internal_data);
+ if (!context->internal) {
+ TALLOC_FREE(frame);
+ SAFE_FREE(context);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ /* Initialize the context and establish reasonable defaults */
+ ZERO_STRUCTP(context->internal);
+
+ context->internal->lp_ctx = loadparm_init_s3(NULL,
+ loadparm_s3_helpers());
+ if (context->internal->lp_ctx == NULL) {
+ SAFE_FREE(context->internal);
+ SAFE_FREE(context);
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ smbc_setDebug(context, 0);
+ smbc_setTimeout(context, 20000);
+ smbc_setPort(context, 0);
+
+ smbc_setOptionFullTimeNames(context, False);
+ smbc_setOptionOpenShareMode(context, SMBC_SHAREMODE_DENY_NONE);
+ smbc_setOptionSmbEncryptionLevel(context, SMBC_ENCRYPTLEVEL_DEFAULT);
+ smbc_setOptionUseCCache(context, True);
+ smbc_setOptionCaseSensitive(context, False);
+ smbc_setOptionBrowseMaxLmbCount(context, 3); /* # LMBs to query */
+ smbc_setOptionUrlEncodeReaddirEntries(context, False);
+ smbc_setOptionOneSharePerServer(context, False);
+ smbc_setOptionPosixExtensions(context, false);
+ if (getenv("LIBSMBCLIENT_NO_CCACHE") != NULL) {
+ smbc_setOptionUseCCache(context, false);
+ }
+
+ smbc_setFunctionAuthData(context, SMBC_get_auth_data);
+ smbc_setFunctionCheckServer(context, SMBC_check_server);
+ smbc_setFunctionRemoveUnusedServer(context, SMBC_remove_unused_server);
+
+ smbc_setOptionUserData(context, NULL);
+ smbc_setFunctionAddCachedServer(context, SMBC_add_cached_server);
+ smbc_setFunctionGetCachedServer(context, SMBC_get_cached_server);
+ smbc_setFunctionRemoveCachedServer(context, SMBC_remove_cached_server);
+ smbc_setFunctionPurgeCachedServers(context, SMBC_purge_cached_servers);
+
+ smbc_setFunctionOpen(context, SMBC_open_ctx);
+ smbc_setFunctionCreat(context, SMBC_creat_ctx);
+ smbc_setFunctionRead(context, SMBC_read_ctx);
+ smbc_setFunctionSplice(context, SMBC_splice_ctx);
+ smbc_setFunctionWrite(context, SMBC_write_ctx);
+ smbc_setFunctionClose(context, SMBC_close_ctx);
+ smbc_setFunctionUnlink(context, SMBC_unlink_ctx);
+ smbc_setFunctionRename(context, SMBC_rename_ctx);
+ smbc_setFunctionLseek(context, SMBC_lseek_ctx);
+ smbc_setFunctionFtruncate(context, SMBC_ftruncate_ctx);
+ smbc_setFunctionStat(context, SMBC_stat_ctx);
+ smbc_setFunctionStatVFS(context, SMBC_statvfs_ctx);
+ smbc_setFunctionFstatVFS(context, SMBC_fstatvfs_ctx);
+ smbc_setFunctionFstat(context, SMBC_fstat_ctx);
+ smbc_setFunctionOpendir(context, SMBC_opendir_ctx);
+ smbc_setFunctionClosedir(context, SMBC_closedir_ctx);
+ smbc_setFunctionReaddir(context, SMBC_readdir_ctx);
+ smbc_setFunctionReaddirPlus(context, SMBC_readdirplus_ctx);
+ smbc_setFunctionReaddirPlus2(context, SMBC_readdirplus2_ctx);
+ smbc_setFunctionGetdents(context, SMBC_getdents_ctx);
+ smbc_setFunctionMkdir(context, SMBC_mkdir_ctx);
+ smbc_setFunctionRmdir(context, SMBC_rmdir_ctx);
+ smbc_setFunctionTelldir(context, SMBC_telldir_ctx);
+ smbc_setFunctionLseekdir(context, SMBC_lseekdir_ctx);
+ smbc_setFunctionFstatdir(context, SMBC_fstatdir_ctx);
+ smbc_setFunctionNotify(context, SMBC_notify_ctx);
+ smbc_setFunctionChmod(context, SMBC_chmod_ctx);
+ smbc_setFunctionUtimes(context, SMBC_utimes_ctx);
+ smbc_setFunctionSetxattr(context, SMBC_setxattr_ctx);
+ smbc_setFunctionGetxattr(context, SMBC_getxattr_ctx);
+ smbc_setFunctionRemovexattr(context, SMBC_removexattr_ctx);
+ smbc_setFunctionListxattr(context, SMBC_listxattr_ctx);
+
+ smbc_setFunctionOpenPrintJob(context, SMBC_open_print_job_ctx);
+ smbc_setFunctionPrintFile(context, SMBC_print_file_ctx);
+ smbc_setFunctionListPrintJobs(context, SMBC_list_print_jobs_ctx);
+ smbc_setFunctionUnlinkPrintJob(context, SMBC_unlink_print_job_ctx);
+
+ TALLOC_FREE(frame);
+ return context;
+}
+
+/*
+ * Free a context
+ *
+ * Returns 0 on success. Otherwise returns 1, the SMBCCTX is _not_ freed
+ * and thus you'll be leaking memory if not handled properly.
+ *
+ */
+int
+smbc_free_context(SMBCCTX *context,
+ int shutdown_ctx)
+{
+ TALLOC_CTX *frame;
+ if (!context) {
+ errno = EBADF;
+ return 1;
+ }
+
+ frame = talloc_stackframe();
+
+ if (shutdown_ctx) {
+ SMBCFILE * f;
+ DEBUG(1,("Performing aggressive shutdown.\n"));
+
+ f = context->internal->files;
+ while (f) {
+ SMBCFILE *next = f->next;
+ smbc_getFunctionClose(context)(context, f);
+ f = next;
+ }
+ context->internal->files = NULL;
+
+ /* First try to remove the servers the nice way. */
+ if (smbc_getFunctionPurgeCachedServers(context)(context)) {
+ SMBCSRV * s;
+ SMBCSRV * next;
+ DEBUG(1, ("Could not purge all servers, "
+ "Nice way shutdown failed.\n"));
+ s = context->internal->servers;
+ while (s) {
+ DEBUG(1, ("Forced shutdown: %p (cli=%p)\n",
+ s, s->cli));
+ cli_shutdown(s->cli);
+ smbc_getFunctionRemoveCachedServer(context)(context,
+ s);
+ next = s->next;
+ DLIST_REMOVE(context->internal->servers, s);
+ SAFE_FREE(s);
+ s = next;
+ }
+ context->internal->servers = NULL;
+ }
+ }
+ else {
+ /* This is the polite way */
+ if (smbc_getFunctionPurgeCachedServers(context)(context)) {
+ DEBUG(1, ("Could not purge all servers, "
+ "free_context failed.\n"));
+ errno = EBUSY;
+ TALLOC_FREE(frame);
+ return 1;
+ }
+ if (context->internal->servers) {
+ DEBUG(1, ("Active servers in context, "
+ "free_context failed.\n"));
+ errno = EBUSY;
+ TALLOC_FREE(frame);
+ return 1;
+ }
+ if (context->internal->files) {
+ DEBUG(1, ("Active files in context, "
+ "free_context failed.\n"));
+ errno = EBUSY;
+ TALLOC_FREE(frame);
+ return 1;
+ }
+ }
+
+ /* Things we have to clean up */
+ smbc_setWorkgroup(context, NULL);
+ smbc_setNetbiosName(context, NULL);
+ smbc_setUser(context, NULL);
+
+ DEBUG(3, ("Context %p successfully freed\n", context));
+
+ /* Free any DFS auth context. */
+ TALLOC_FREE(context->internal->creds);
+
+ TALLOC_FREE(context->internal->lp_ctx);
+ SAFE_FREE(context->internal);
+ SAFE_FREE(context);
+
+ /* Protect access to the count of contexts in use */
+ if (SMB_THREAD_LOCK(initialized_ctx_count_mutex) != 0) {
+ smb_panic("error locking 'initialized_ctx_count'");
+ }
+
+ if (initialized_ctx_count) {
+ initialized_ctx_count--;
+ }
+
+ if (initialized_ctx_count == 0) {
+ SMBC_module_terminate();
+ }
+
+ /* Unlock the mutex */
+ if (SMB_THREAD_UNLOCK(initialized_ctx_count_mutex) != 0) {
+ smb_panic("error unlocking 'initialized_ctx_count'");
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+
+/**
+ * Deprecated interface. Do not use. Instead, use the various
+ * smbc_setOption*() functions or smbc_setFunctionAuthDataWithContext().
+ */
+void
+smbc_option_set(SMBCCTX *context,
+ char *option_name,
+ ... /* option_value */)
+{
+ va_list ap;
+ union {
+ int i;
+ bool b;
+ smbc_get_auth_data_with_context_fn auth_fn;
+ void *v;
+ const char *s;
+ } option_value;
+
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ va_start(ap, option_name);
+
+ if (strcmp(option_name, "debug_to_stderr") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionDebugToStderr(context, option_value.b);
+
+ } else if (strcmp(option_name, "full_time_names") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionFullTimeNames(context, option_value.b);
+
+ } else if (strcmp(option_name, "open_share_mode") == 0) {
+ option_value.i = va_arg(ap, int);
+ smbc_setOptionOpenShareMode(context, option_value.i);
+
+ } else if (strcmp(option_name, "auth_function") == 0) {
+ option_value.auth_fn =
+ va_arg(ap, smbc_get_auth_data_with_context_fn);
+ smbc_setFunctionAuthDataWithContext(context, option_value.auth_fn);
+
+ } else if (strcmp(option_name, "user_data") == 0) {
+ option_value.v = va_arg(ap, void *);
+ smbc_setOptionUserData(context, option_value.v);
+
+ } else if (strcmp(option_name, "smb_encrypt_level") == 0) {
+ option_value.s = va_arg(ap, const char *);
+ if (strcmp(option_value.s, "none") == 0) {
+ smbc_setOptionSmbEncryptionLevel(context,
+ SMBC_ENCRYPTLEVEL_NONE);
+ } else if (strcmp(option_value.s, "request") == 0) {
+ smbc_setOptionSmbEncryptionLevel(context,
+ SMBC_ENCRYPTLEVEL_REQUEST);
+ } else if (strcmp(option_value.s, "require") == 0) {
+ smbc_setOptionSmbEncryptionLevel(context,
+ SMBC_ENCRYPTLEVEL_REQUIRE);
+ }
+
+ } else if (strcmp(option_name, "browse_max_lmb_count") == 0) {
+ option_value.i = va_arg(ap, int);
+ smbc_setOptionBrowseMaxLmbCount(context, option_value.i);
+
+ } else if (strcmp(option_name, "urlencode_readdir_entries") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionUrlEncodeReaddirEntries(context, option_value.b);
+
+ } else if (strcmp(option_name, "one_share_per_server") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionOneSharePerServer(context, option_value.b);
+
+ } else if (strcmp(option_name, "use_kerberos") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionUseKerberos(context, option_value.b);
+
+ } else if (strcmp(option_name, "fallback_after_kerberos") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionFallbackAfterKerberos(context, option_value.b);
+
+ } else if (strcmp(option_name, "use_ccache") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionUseCCache(context, option_value.b);
+
+ } else if (strcmp(option_name, "no_auto_anonymous_login") == 0) {
+ option_value.b = (bool) va_arg(ap, int);
+ smbc_setOptionNoAutoAnonymousLogin(context, option_value.b);
+ }
+
+ va_end(ap);
+ TALLOC_FREE(frame);
+}
+
+
+/*
+ * Deprecated interface. Do not use. Instead, use the various
+ * smbc_getOption*() functions.
+ */
+void *
+smbc_option_get(SMBCCTX *context,
+ char *option_name)
+{
+ if (strcmp(option_name, "debug_stderr") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionDebugToStderr(context);
+#else
+ return (void *) smbc_getOptionDebugToStderr(context);
+#endif
+
+ } else if (strcmp(option_name, "full_time_names") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionFullTimeNames(context);
+#else
+ return (void *) smbc_getOptionFullTimeNames(context);
+#endif
+
+ } else if (strcmp(option_name, "open_share_mode") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionOpenShareMode(context);
+#else
+ return (void *) smbc_getOptionOpenShareMode(context);
+#endif
+
+ } else if (strcmp(option_name, "auth_function") == 0) {
+ return (void *) smbc_getFunctionAuthDataWithContext(context);
+
+ } else if (strcmp(option_name, "user_data") == 0) {
+ return smbc_getOptionUserData(context);
+
+ } else if (strcmp(option_name, "smb_encrypt_level") == 0) {
+ switch(smbc_getOptionSmbEncryptionLevel(context))
+ {
+ case SMBC_ENCRYPTLEVEL_DEFAULT:
+ return discard_const_p(void, "default");
+ case 0:
+ return discard_const_p(void, "none");
+ case 1:
+ return discard_const_p(void, "request");
+ case 2:
+ return discard_const_p(void, "require");
+ }
+
+ } else if (strcmp(option_name, "smb_encrypt_on") == 0) {
+ SMBCSRV *s;
+ unsigned int num_servers = 0;
+
+ for (s = context->internal->servers; s; s = s->next) {
+ num_servers++;
+ if (!cli_state_is_encryption_on(s->cli)) {
+ return (void *)false;
+ }
+ }
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) (bool) (num_servers > 0);
+#else
+ return (void *) (bool) (num_servers > 0);
+#endif
+
+ } else if (strcmp(option_name, "browse_max_lmb_count") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionBrowseMaxLmbCount(context);
+#else
+ return (void *) smbc_getOptionBrowseMaxLmbCount(context);
+#endif
+
+ } else if (strcmp(option_name, "urlencode_readdir_entries") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *)(intptr_t) smbc_getOptionUrlEncodeReaddirEntries(context);
+#else
+ return (void *) (bool) smbc_getOptionUrlEncodeReaddirEntries(context);
+#endif
+
+ } else if (strcmp(option_name, "one_share_per_server") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionOneSharePerServer(context);
+#else
+ return (void *) (bool) smbc_getOptionOneSharePerServer(context);
+#endif
+
+ } else if (strcmp(option_name, "use_kerberos") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionUseKerberos(context);
+#else
+ return (void *) (bool) smbc_getOptionUseKerberos(context);
+#endif
+
+ } else if (strcmp(option_name, "fallback_after_kerberos") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *)(intptr_t) smbc_getOptionFallbackAfterKerberos(context);
+#else
+ return (void *) (bool) smbc_getOptionFallbackAfterKerberos(context);
+#endif
+
+ } else if (strcmp(option_name, "use_ccache") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionUseCCache(context);
+#else
+ return (void *) (bool) smbc_getOptionUseCCache(context);
+#endif
+
+ } else if (strcmp(option_name, "no_auto_anonymous_login") == 0) {
+#if defined(__intptr_t_defined) || defined(HAVE_INTPTR_T)
+ return (void *) (intptr_t) smbc_getOptionNoAutoAnonymousLogin(context);
+#else
+ return (void *) (bool) smbc_getOptionNoAutoAnonymousLogin(context);
+#endif
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Initialize the library, etc.
+ *
+ * We accept a struct containing handle information.
+ * valid values for info->debug from 0 to 100,
+ * and insist that info->fn must be non-null.
+ */
+SMBCCTX *
+smbc_init_context(SMBCCTX *context)
+{
+ int pid;
+ TALLOC_CTX *frame;
+
+ if (!context) {
+ errno = EBADF;
+ return NULL;
+ }
+
+ /* Do not initialise the same client twice */
+ if (context->internal->initialized) {
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+
+ if ((!smbc_getFunctionAuthData(context) &&
+ !smbc_getFunctionAuthDataWithContext(context)) ||
+ smbc_getDebug(context) < 0 ||
+ smbc_getDebug(context) > 100) {
+
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return NULL;
+
+ }
+
+ if (!smbc_getUser(context)) {
+ /*
+ * FIXME: Is this the best way to get the user info?
+ */
+ char *user = getenv("USER");
+ /* walk around as "guest" if no username can be found */
+ if (!user) {
+ user = SMB_STRDUP("guest");
+ } else {
+ user = SMB_STRDUP(user);
+ }
+
+ if (!user) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ smbc_setUser(context, user);
+ SAFE_FREE(user);
+
+ if (!smbc_getUser(context)) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ if (!smbc_getNetbiosName(context)) {
+ /*
+ * We try to get our netbios name from the config. If that
+ * fails we fall back on constructing our netbios name from
+ * our hostname etc
+ */
+ char *netbios_name;
+ if (lp_netbios_name()) {
+ netbios_name = SMB_STRDUP(lp_netbios_name());
+ } else {
+ /*
+ * Hmmm, I want to get hostname as well, but I am too
+ * lazy for the moment
+ */
+ pid = getpid();
+ netbios_name = (char *)SMB_MALLOC(17);
+ if (!netbios_name) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ slprintf(netbios_name, 16,
+ "smbc%s%d", smbc_getUser(context), pid);
+ }
+
+ if (!netbios_name) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ smbc_setNetbiosName(context, netbios_name);
+ SAFE_FREE(netbios_name);
+
+ if (!smbc_getNetbiosName(context)) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ DEBUG(1, ("Using netbios name %s.\n", smbc_getNetbiosName(context)));
+
+ if (!smbc_getWorkgroup(context)) {
+ const char *workgroup;
+
+ if (lp_workgroup()) {
+ workgroup = lp_workgroup();
+ } else {
+ /* TODO: Think about a decent default workgroup */
+ workgroup = "samba";
+ }
+
+ smbc_setWorkgroup(context, workgroup);
+
+ if (!smbc_getWorkgroup(context)) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ DEBUG(1, ("Using workgroup %s.\n", smbc_getWorkgroup(context)));
+
+ /* shortest timeout is 1 second */
+ if (smbc_getTimeout(context) > 0 && smbc_getTimeout(context) < 1000)
+ smbc_setTimeout(context, 1000);
+
+ context->internal->initialized = True;
+
+ /* Protect access to the count of contexts in use */
+ if (SMB_THREAD_LOCK(initialized_ctx_count_mutex) != 0) {
+ smb_panic("error locking 'initialized_ctx_count'");
+ }
+
+ initialized_ctx_count++;
+
+ /* Unlock the mutex */
+ if (SMB_THREAD_UNLOCK(initialized_ctx_count_mutex) != 0) {
+ smb_panic("error unlocking 'initialized_ctx_count'");
+ }
+
+ TALLOC_FREE(frame);
+ return context;
+}
+
+
+/* Return the version of samba, and thus libsmbclient */
+const char *
+smbc_version(void)
+{
+ return samba_version_string();
+}
+
+/*
+ * Set the credentials so DFS will work when following referrals.
+ * This function is broken and must be removed. No SMBCCTX arg...
+ * JRA.
+ */
+
+void
+smbc_set_credentials(const char *workgroup,
+ const char *user,
+ const char *password,
+ smbc_bool use_kerberos,
+ const char *signing_state)
+{
+ d_printf("smbc_set_credentials is obsolete. Replace with smbc_set_credentials_with_fallback().\n");
+}
+
+void smbc_set_credentials_with_fallback(SMBCCTX *context,
+ const char *workgroup,
+ const char *user,
+ const char *password)
+{
+ struct cli_credentials *creds = NULL;
+ enum credentials_use_kerberos kerberos_state =
+ CRED_USE_KERBEROS_DISABLED;
+
+ if (! context) {
+
+ return;
+ }
+
+ if (! workgroup || ! *workgroup) {
+ workgroup = smbc_getWorkgroup(context);
+ }
+
+ if (! user) {
+ user = smbc_getUser(context);
+ }
+
+ if (! password) {
+ password = "";
+ }
+
+ creds = cli_credentials_init(NULL);
+ if (creds == NULL) {
+ DEBUG(0, ("smbc_set_credentials_with_fallback: allocation fail\n"));
+ return;
+ }
+
+ cli_credentials_set_conf(creds, context->internal->lp_ctx);
+
+ if (smbc_getOptionUseKerberos(context)) {
+ kerberos_state = CRED_USE_KERBEROS_REQUIRED;
+
+ if (smbc_getOptionFallbackAfterKerberos(context)) {
+ kerberos_state = CRED_USE_KERBEROS_DESIRED;
+ }
+ }
+
+ cli_credentials_set_username(creds, user, CRED_SPECIFIED);
+ cli_credentials_set_password(creds, password, CRED_SPECIFIED);
+ cli_credentials_set_domain(creds, workgroup, CRED_SPECIFIED);
+ cli_credentials_set_kerberos_state(creds,
+ kerberos_state,
+ CRED_SPECIFIED);
+ if (smbc_getOptionUseCCache(context)) {
+ uint32_t gensec_features;
+
+ gensec_features = cli_credentials_get_gensec_features(creds);
+ gensec_features |= GENSEC_FEATURE_NTLM_CCACHE;
+ cli_credentials_set_gensec_features(creds,
+ gensec_features,
+ CRED_SPECIFIED);
+ }
+
+ TALLOC_FREE(context->internal->creds);
+ context->internal->creds = creds;
+}
diff --git a/source3/libsmb/libsmb_dir.c b/source3/libsmb/libsmb_dir.c
new file mode 100644
index 0000000..34d6731
--- /dev/null
+++ b/source3/libsmb/libsmb_dir.c
@@ -0,0 +1,2726 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "libsmb/nmblib.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "../libcli/security/security.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/time_basic.h"
+#include "lib/util/string_wrappers.h"
+
+/*
+ * Routine to open a directory
+ * We accept the URL syntax explained in SMBC_parse_path(), above.
+ */
+
+static void remove_dirplus(SMBCFILE *dir)
+{
+ struct smbc_dirplus_list *d = NULL;
+
+ d = dir->dirplus_list;
+ while (d != NULL) {
+ struct smbc_dirplus_list *f = d;
+ d = d->next;
+
+ SAFE_FREE(f->smb_finfo->short_name);
+ SAFE_FREE(f->smb_finfo->name);
+ SAFE_FREE(f->smb_finfo);
+ SAFE_FREE(f);
+ }
+
+ dir->dirplus_list = NULL;
+ dir->dirplus_end = NULL;
+ dir->dirplus_next = NULL;
+}
+
+static void
+remove_dir(SMBCFILE *dir)
+{
+ struct smbc_dir_list *d,*f;
+
+ d = dir->dir_list;
+ while (d) {
+
+ f = d; d = d->next;
+
+ SAFE_FREE(f->dirent);
+ SAFE_FREE(f);
+
+ }
+
+ dir->dir_list = dir->dir_end = dir->dir_next = NULL;
+
+}
+
+static int
+add_dirent(SMBCFILE *dir,
+ const char *name,
+ const char *comment,
+ uint32_t type)
+{
+ struct smbc_dirent *dirent;
+ int size;
+ int name_length = (name == NULL ? 0 : strlen(name));
+ int comment_len = (comment == NULL ? 0 : strlen(comment));
+
+ /*
+ * Allocate space for the dirent, which must be increased by the
+ * size of the name and the comment and 1 each for the null terminator.
+ */
+
+ size = sizeof(struct smbc_dirent) + name_length + comment_len + 2;
+
+ dirent = (struct smbc_dirent *)SMB_MALLOC(size);
+
+ if (!dirent) {
+
+ dir->dir_error = ENOMEM;
+ return -1;
+
+ }
+
+ ZERO_STRUCTP(dirent);
+
+ if (dir->dir_list == NULL) {
+
+ dir->dir_list = SMB_MALLOC_P(struct smbc_dir_list);
+ if (!dir->dir_list) {
+
+ SAFE_FREE(dirent);
+ dir->dir_error = ENOMEM;
+ return -1;
+
+ }
+ ZERO_STRUCTP(dir->dir_list);
+
+ dir->dir_end = dir->dir_next = dir->dir_list;
+ }
+ else {
+
+ dir->dir_end->next = SMB_MALLOC_P(struct smbc_dir_list);
+
+ if (!dir->dir_end->next) {
+
+ SAFE_FREE(dirent);
+ dir->dir_error = ENOMEM;
+ return -1;
+
+ }
+ ZERO_STRUCTP(dir->dir_end->next);
+
+ dir->dir_end = dir->dir_end->next;
+ }
+
+ dir->dir_end->next = NULL;
+ dir->dir_end->dirent = dirent;
+
+ dirent->smbc_type = type;
+ dirent->namelen = name_length;
+ dirent->commentlen = comment_len;
+ dirent->dirlen = size;
+
+ /*
+ * dirent->namelen + 1 includes the null (no null termination needed)
+ * Ditto for dirent->commentlen.
+ * The space for the two null bytes was allocated.
+ */
+ strncpy(dirent->name, (name?name:""), dirent->namelen + 1);
+ dirent->comment = (char *)(&dirent->name + dirent->namelen + 1);
+ strncpy(dirent->comment, (comment?comment:""), dirent->commentlen + 1);
+
+ return 0;
+
+}
+
+static int add_dirplus(SMBCFILE *dir, struct file_info *finfo)
+{
+ struct smbc_dirplus_list *new_entry = NULL;
+ struct libsmb_file_info *info = NULL;
+
+ new_entry = SMB_MALLOC_P(struct smbc_dirplus_list);
+ if (new_entry == NULL) {
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+ ZERO_STRUCTP(new_entry);
+ new_entry->ino = finfo->ino;
+
+ info = SMB_MALLOC_P(struct libsmb_file_info);
+ if (info == NULL) {
+ SAFE_FREE(new_entry);
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+
+ ZERO_STRUCTP(info);
+
+ info->btime_ts = finfo->btime_ts;
+ info->atime_ts = finfo->atime_ts;
+ info->ctime_ts = finfo->ctime_ts;
+ info->mtime_ts = finfo->mtime_ts;
+ info->gid = finfo->gid;
+ info->attrs = finfo->attr;
+ info->size = finfo->size;
+ info->uid = finfo->uid;
+ info->name = SMB_STRDUP(finfo->name);
+ if (info->name == NULL) {
+ SAFE_FREE(info);
+ SAFE_FREE(new_entry);
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+
+ if (finfo->short_name) {
+ info->short_name = SMB_STRDUP(finfo->short_name);
+ } else {
+ info->short_name = SMB_STRDUP("");
+ }
+
+ if (info->short_name == NULL) {
+ SAFE_FREE(info->name);
+ SAFE_FREE(info);
+ SAFE_FREE(new_entry);
+ dir->dir_error = ENOMEM;
+ return -1;
+ }
+ new_entry->smb_finfo = info;
+
+ /* Now add to the list. */
+ if (dir->dirplus_list == NULL) {
+ /* Empty list - point everything at new_entry. */
+ dir->dirplus_list = new_entry;
+ dir->dirplus_end = new_entry;
+ dir->dirplus_next = new_entry;
+ } else {
+ /* Append to list but leave the ->next cursor alone. */
+ dir->dirplus_end->next = new_entry;
+ dir->dirplus_end = new_entry;
+ }
+
+ return 0;
+}
+
+static void
+list_unique_wg_fn(const char *name,
+ uint32_t type,
+ const char *comment,
+ void *state)
+{
+ SMBCFILE *dir = (SMBCFILE *)state;
+ struct smbc_dir_list *dir_list;
+ struct smbc_dirent *dirent;
+ int dirent_type;
+ int do_remove = 0;
+
+ dirent_type = dir->dir_type;
+
+ if (add_dirent(dir, name, comment, dirent_type) < 0) {
+ /* An error occurred, what do we do? */
+ /* FIXME: Add some code here */
+ /* Change cli_NetServerEnum to take a fn
+ returning NTSTATUS... JRA. */
+ }
+
+ /* Point to the one just added */
+ dirent = dir->dir_end->dirent;
+
+ /* See if this was a duplicate */
+ for (dir_list = dir->dir_list;
+ dir_list != dir->dir_end;
+ dir_list = dir_list->next) {
+ if (! do_remove &&
+ strcmp(dir_list->dirent->name, dirent->name) == 0) {
+ /* Duplicate. End end of list need to be removed. */
+ do_remove = 1;
+ }
+
+ if (do_remove && dir_list->next == dir->dir_end) {
+ /* Found the end of the list. Remove it. */
+ dir->dir_end = dir_list;
+ free(dir_list->next);
+ free(dirent);
+ dir_list->next = NULL;
+ break;
+ }
+ }
+}
+
+static void
+list_fn(const char *name,
+ uint32_t type,
+ const char *comment,
+ void *state)
+{
+ SMBCFILE *dir = (SMBCFILE *)state;
+ int dirent_type;
+
+ /*
+ * We need to process the type a little ...
+ *
+ * Disk share = 0x00000000
+ * Print share = 0x00000001
+ * Comms share = 0x00000002 (obsolete?)
+ * IPC$ share = 0x00000003
+ *
+ * administrative shares:
+ * ADMIN$, IPC$, C$, D$, E$ ... are type |= 0x80000000
+ */
+
+ if (dir->dir_type == SMBC_FILE_SHARE) {
+ switch (type) {
+ case 0 | 0x80000000:
+ case 0:
+ dirent_type = SMBC_FILE_SHARE;
+ break;
+
+ case 1:
+ dirent_type = SMBC_PRINTER_SHARE;
+ break;
+
+ case 2:
+ dirent_type = SMBC_COMMS_SHARE;
+ break;
+
+ case 3 | 0x80000000:
+ case 3:
+ dirent_type = SMBC_IPC_SHARE;
+ break;
+
+ default:
+ dirent_type = SMBC_FILE_SHARE; /* FIXME, error? */
+ break;
+ }
+ }
+ else {
+ dirent_type = dir->dir_type;
+ }
+
+ if (add_dirent(dir, name, comment, dirent_type) < 0) {
+ /* An error occurred, what do we do? */
+ /* FIXME: Add some code here */
+ /* Change cli_NetServerEnum to take a fn
+ returning NTSTATUS... JRA. */
+ }
+}
+
+static NTSTATUS
+dir_list_fn(struct file_info *finfo,
+ const char *mask,
+ void *state)
+{
+ SMBCFILE *dirp = (SMBCFILE *)state;
+ int ret;
+
+ if (add_dirent((SMBCFILE *)state, finfo->name, "",
+ (finfo->attr&FILE_ATTRIBUTE_DIRECTORY?SMBC_DIR:SMBC_FILE)) < 0) {
+ SMBCFILE *dir = (SMBCFILE *)state;
+ return map_nt_error_from_unix(dir->dir_error);
+ }
+ ret = add_dirplus(dirp, finfo);
+ if (ret < 0) {
+ return map_nt_error_from_unix(dirp->dir_error);
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS
+net_share_enum_rpc(struct cli_state *cli,
+ void (*fn)(const char *name,
+ uint32_t type,
+ const char *comment,
+ void *state),
+ void *state)
+{
+ uint32_t i;
+ WERROR result;
+ uint32_t preferred_len = 0xffffffff;
+ uint32_t type;
+ struct srvsvc_NetShareInfoCtr info_ctr;
+ struct srvsvc_NetShareCtr1 ctr1;
+ fstring name = "";
+ fstring comment = "";
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ NTSTATUS nt_status;
+ uint32_t resume_handle = 0;
+ uint32_t total_entries = 0;
+ struct dcerpc_binding_handle *b;
+
+ /* Open the server service pipe */
+ nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_srvsvc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("net_share_enum_rpc pipe open fail!\n"));
+ goto done;
+ }
+
+ ZERO_STRUCT(info_ctr);
+ ZERO_STRUCT(ctr1);
+
+ info_ctr.level = 1;
+ info_ctr.ctr.ctr1 = &ctr1;
+
+ b = pipe_hnd->binding_handle;
+
+ /* Issue the NetShareEnum RPC call and retrieve the response */
+ nt_status = dcerpc_srvsvc_NetShareEnumAll(b, talloc_tos(),
+ pipe_hnd->desthost,
+ &info_ctr,
+ preferred_len,
+ &total_entries,
+ &resume_handle,
+ &result);
+
+ /* Was it successful? */
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /* Nope. Go clean up. */
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ /* Nope. Go clean up. */
+ nt_status = werror_to_ntstatus(result);
+ goto done;
+ }
+
+ if (total_entries == 0) {
+ /* Nope. Go clean up. */
+ nt_status = NT_STATUS_NOT_FOUND;
+ goto done;
+ }
+
+ /* For each returned entry... */
+ for (i = 0; i < info_ctr.ctr.ctr1->count; i++) {
+
+ /* pull out the share name */
+ fstrcpy(name, info_ctr.ctr.ctr1->array[i].name);
+
+ /* pull out the share's comment */
+ fstrcpy(comment, info_ctr.ctr.ctr1->array[i].comment);
+
+ /* Get the type value */
+ type = info_ctr.ctr.ctr1->array[i].type;
+
+ /* Add this share to the list */
+ (*fn)(name, type, comment, state);
+ }
+
+done:
+ /* Close the server service pipe */
+ TALLOC_FREE(pipe_hnd);
+
+ /* Tell 'em if it worked */
+ return nt_status;
+}
+
+
+/*
+ * Verify that the options specified in a URL are valid
+ */
+int
+SMBC_check_options(char *server,
+ char *share,
+ char *path,
+ char *options)
+{
+ DEBUG(4, ("SMBC_check_options(): server='%s' share='%s' "
+ "path='%s' options='%s'\n",
+ server, share, path, options));
+
+ /* No options at all is always ok */
+ if (! *options) return 0;
+
+ /* Currently, we don't support any options. */
+ return -1;
+}
+
+
+SMBCFILE *
+SMBC_opendir_ctx(SMBCCTX *context,
+ const char *fname)
+{
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *options = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ size_t path_len = 0;
+ uint16_t port = 0;
+ SMBCSRV *srv = NULL;
+ SMBCFILE *dir = NULL;
+ struct sockaddr_storage rem_ss;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ DEBUG(4, ("no valid context\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8192;
+ return NULL;
+
+ }
+
+ if (!fname) {
+ DEBUG(4, ("no valid fname\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8193;
+ return NULL;
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ &options)) {
+ DEBUG(4, ("no valid path\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8194;
+ return NULL;
+ }
+
+ DEBUG(4, ("parsed path: fname='%s' server='%s' share='%s' "
+ "path='%s' options='%s'\n",
+ fname, server, share, path, options));
+
+ /* Ensure the options are valid */
+ if (SMBC_check_options(server, share, path, options)) {
+ DEBUG(4, ("unacceptable options (%s)\n", options));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8195;
+ return NULL;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ dir = SMB_MALLOC_P(SMBCFILE);
+
+ if (!dir) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ZERO_STRUCTP(dir);
+
+ dir->cli_fd = 0;
+ dir->fname = SMB_STRDUP(fname);
+ if (dir->fname == NULL) {
+ SAFE_FREE(dir);
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ dir->srv = NULL;
+ dir->offset = 0;
+ dir->file = False;
+ dir->dir_list = dir->dir_next = dir->dir_end = NULL;
+
+ if (server[0] == (char)0) {
+
+ size_t i;
+ size_t count = 0;
+ size_t max_lmb_count;
+ struct sockaddr_storage *ip_list;
+ struct sockaddr_storage server_addr;
+ struct cli_credentials *creds = NULL;
+ NTSTATUS status;
+
+ if (share[0] != (char)0 || path[0] != (char)0) {
+
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8196;
+ return NULL;
+ }
+
+ /* Determine how many local master browsers to query */
+ max_lmb_count = (smbc_getOptionBrowseMaxLmbCount(context) == 0
+ ? INT_MAX
+ : smbc_getOptionBrowseMaxLmbCount(context));
+
+ creds = cli_credentials_init(frame);
+ if (creds == NULL) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ (void)cli_credentials_set_username(creds, user, CRED_SPECIFIED);
+ (void)cli_credentials_set_password(creds, password, CRED_SPECIFIED);
+
+ /*
+ * We have server and share and path empty but options
+ * requesting that we scan all master browsers for their list
+ * of workgroups/domains. This implies that we must first try
+ * broadcast queries to find all master browsers, and if that
+ * doesn't work, then try our other methods which return only
+ * a single master browser.
+ */
+
+ ip_list = NULL;
+ status = name_resolve_bcast(talloc_tos(),
+ MSBROWSE,
+ 1,
+ &ip_list,
+ &count);
+ if (!NT_STATUS_IS_OK(status))
+ {
+
+ TALLOC_FREE(ip_list);
+
+ if (!find_master_ip(workgroup, &server_addr)) {
+
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ ip_list = (struct sockaddr_storage *)talloc_memdup(
+ talloc_tos(), &server_addr,
+ sizeof(server_addr));
+ if (ip_list == NULL) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+ count = 1;
+ }
+
+ for (i = 0; i < count && i < max_lmb_count; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ char *wg_ptr = NULL;
+ struct cli_state *cli = NULL;
+
+ print_sockaddr(addr, sizeof(addr), &ip_list[i]);
+ DEBUG(99, ("Found master browser %zu of %zu: %s\n",
+ i+1, MAX(count, max_lmb_count),
+ addr));
+
+ cli = get_ipc_connect_master_ip(talloc_tos(),
+ &ip_list[i],
+ creds,
+ &wg_ptr);
+ /* cli == NULL is the master browser refused to talk or
+ could not be found */
+ if (!cli) {
+ continue;
+ }
+
+ workgroup = talloc_strdup(frame, wg_ptr);
+ server = talloc_strdup(frame, smbXcli_conn_remote_name(cli->conn));
+
+ cli_shutdown(cli);
+
+ if (!workgroup || !server) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ DEBUG(4, ("using workgroup %s %s\n",
+ workgroup, server));
+
+ /*
+ * For each returned master browser IP address, get a
+ * connection to IPC$ on the server if we do not
+ * already have one, and determine the
+ * workgroups/domains that it knows about.
+ */
+
+ srv = SMBC_server(frame, context, True, server, port, "IPC$",
+ &workgroup, &user, &password);
+ if (!srv) {
+ continue;
+ }
+
+ if (smbXcli_conn_protocol(srv->cli->conn) > PROTOCOL_NT1) {
+ continue;
+ }
+
+ dir->srv = srv;
+ dir->dir_type = SMBC_WORKGROUP;
+
+ /* Now, list the stuff ... */
+
+ if (!cli_NetServerEnum(srv->cli,
+ workgroup,
+ SV_TYPE_DOMAIN_ENUM,
+ list_unique_wg_fn,
+ (void *)dir)) {
+ continue;
+ }
+ }
+
+ TALLOC_FREE(ip_list);
+ } else {
+ /*
+ * Server not an empty string ... Check the rest and see what
+ * gives
+ */
+ if (*share == '\0') {
+ if (*path != '\0') {
+
+ /* Should not have empty share with path */
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8197;
+ return NULL;
+
+ }
+
+ /*
+ * We don't know if <server> is really a server name
+ * or is a workgroup/domain name. If we already have
+ * a server structure for it, we'll use it.
+ * Otherwise, check to see if <server><1D>,
+ * <server><1B>, or <server><20> translates. We check
+ * to see if <server> is an IP address first.
+ */
+
+ /*
+ * See if we have an existing server. Do not
+ * establish a connection if one does not already
+ * exist.
+ */
+ srv = SMBC_server(frame, context, False,
+ server, port, "IPC$",
+ &workgroup, &user, &password);
+
+ /*
+ * If no existing server and not an IP addr, look for
+ * LMB or DMB
+ */
+ if (!srv &&
+ !is_ipaddress(server) &&
+ (resolve_name(server, &rem_ss, 0x1d, false) || /* LMB */
+ resolve_name(server, &rem_ss, 0x1b, false) )) { /* DMB */
+ /*
+ * "server" is actually a workgroup name,
+ * not a server. Make this clear.
+ */
+ char *wgroup = server;
+ fstring buserver;
+
+ dir->dir_type = SMBC_SERVER;
+
+ /*
+ * Get the backup list ...
+ */
+ if (!name_status_find(wgroup, 0, 0,
+ &rem_ss, buserver)) {
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), &rem_ss);
+ DEBUG(0,("Could not get name of "
+ "local/domain master browser "
+ "for workgroup %s from "
+ "address %s\n",
+ wgroup,
+ addr));
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = EPERM;
+ return NULL;
+
+ }
+
+ /*
+ * Get a connection to IPC$ on the server if
+ * we do not already have one
+ */
+ srv = SMBC_server(frame, context, True,
+ buserver, port, "IPC$",
+ &workgroup,
+ &user, &password);
+ if (!srv) {
+ DEBUG(0, ("got no contact to IPC$\n"));
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ dir->srv = srv;
+
+ if (smbXcli_conn_protocol(srv->cli->conn) > PROTOCOL_NT1) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ /* Now, list the servers ... */
+ if (!cli_NetServerEnum(srv->cli, wgroup,
+ 0x0000FFFE, list_fn,
+ (void *)dir)) {
+
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ } else if (srv ||
+ (resolve_name(server, &rem_ss, 0x20, false))) {
+ NTSTATUS status;
+
+ /*
+ * If we hadn't found the server, get one now
+ */
+ if (!srv) {
+ srv = SMBC_server(frame, context, True,
+ server, port, "IPC$",
+ &workgroup,
+ &user, &password);
+ }
+
+ if (!srv) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ dir->dir_type = SMBC_FILE_SHARE;
+ dir->srv = srv;
+
+ /* List the shares ... */
+
+ status = net_share_enum_rpc(srv->cli,
+ list_fn,
+ (void *)dir);
+ if (!NT_STATUS_IS_OK(status) &&
+ smbXcli_conn_protocol(srv->cli->conn) <=
+ PROTOCOL_NT1) {
+ /*
+ * Only call cli_RNetShareEnum()
+ * on SMB1 connections, not SMB2+.
+ */
+ int rc = cli_RNetShareEnum(srv->cli,
+ list_fn,
+ (void *)dir);
+ if (rc != 0) {
+ status = cli_nt_error(srv->cli);
+ } else {
+ status = NT_STATUS_OK;
+ }
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Set cli->raw_status so SMBC_errno()
+ * will correctly return the error.
+ */
+ srv->cli->raw_status = status;
+ if (dir != NULL) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ errno = map_errno_from_nt_status(
+ status);
+ return NULL;
+ }
+ } else {
+ /* Neither the workgroup nor server exists */
+ errno = ECONNREFUSED;
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ }
+ else {
+ /*
+ * The server and share are specified ... work from
+ * there ...
+ */
+ char *targetpath;
+ struct cli_state *targetcli;
+ struct cli_credentials *creds = NULL;
+ NTSTATUS status;
+
+ /* We connect to the server and list the directory */
+ dir->dir_type = SMBC_FILE_SHARE;
+
+ srv = SMBC_server(frame, context, True, server, port, share,
+ &workgroup, &user, &password);
+
+ if (!srv) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ dir->srv = srv;
+
+ /* Now, list the files ... */
+
+ path_len = strlen(path);
+ path = talloc_asprintf_append(path, "\\*");
+ if (!path) {
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ creds = context->internal->creds;
+
+ status = cli_resolve_path(
+ frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = cli_list(targetcli, targetpath,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
+ dir_list_fn, (void *)dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ int saved_errno;
+ if (dir) {
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir);
+ }
+ saved_errno = cli_status_to_errno(status);
+
+ if (saved_errno == EINVAL) {
+ struct stat sb = {0};
+ /*
+ * See if they asked to opendir
+ * something other than a directory.
+ * If so, the converted error value we
+ * got would have been EINVAL rather
+ * than ENOTDIR.
+ */
+ path[path_len] = '\0'; /* restore original path */
+
+ status = SMBC_getatr(
+ context,
+ srv,
+ path,
+ &sb);
+ if (NT_STATUS_IS_OK(status) &&
+ !S_ISDIR(sb.st_mode)) {
+
+ /* It is. Correct the error value */
+ saved_errno = ENOTDIR;
+ }
+ }
+
+ /*
+ * If there was an error and the server is no
+ * good any more...
+ */
+ if (cli_is_error(targetcli) &&
+ smbc_getFunctionCheckServer(context)(context, srv)) {
+
+ /* ... then remove it. */
+ if (smbc_getFunctionRemoveUnusedServer(context)(context,
+ srv)) {
+ /*
+ * We could not remove the
+ * server completely, remove
+ * it from the cache so we
+ * will not get it again. It
+ * will be removed when the
+ * last file/dir is closed.
+ */
+ smbc_getFunctionRemoveCachedServer(context)(context, srv);
+ }
+ }
+
+ TALLOC_FREE(frame);
+ errno = saved_errno;
+ return NULL;
+ }
+ }
+
+ }
+
+ DLIST_ADD(context->internal->files, dir);
+ TALLOC_FREE(frame);
+ return dir;
+
+}
+
+/*
+ * Routine to close a directory
+ */
+
+int
+SMBC_closedir_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ TALLOC_CTX *frame = NULL;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (dir == NULL) {
+ return 0;
+ }
+
+ frame = talloc_stackframe();
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ remove_dir(dir); /* Clean it up */
+ remove_dirplus(dir);
+
+ DLIST_REMOVE(context->internal->files, dir);
+
+ SAFE_FREE(dir->fname);
+ SAFE_FREE(dir); /* Free the space too */
+
+ TALLOC_FREE(frame);
+ return 0;
+
+}
+
+static int
+smbc_readdir_internal(SMBCCTX * context,
+ struct smbc_dirent *dest,
+ struct smbc_dirent *src,
+ int max_namebuf_len)
+{
+ if (smbc_getOptionUrlEncodeReaddirEntries(context)) {
+ int remaining_len;
+
+ /* url-encode the name. get back remaining buffer space */
+ remaining_len =
+ smbc_urlencode(dest->name, src->name, max_namebuf_len);
+
+ /* -1 means no null termination. */
+ if (remaining_len < 0) {
+ return -1;
+ }
+
+ /* We now know the name length */
+ dest->namelen = strlen(dest->name);
+
+ if (dest->namelen + 1 < 1) {
+ /* Integer wrap. */
+ return -1;
+ }
+
+ if (dest->namelen + 1 >= max_namebuf_len) {
+ /* Out of space for comment. */
+ return -1;
+ }
+
+ /* Save the pointer to the beginning of the comment */
+ dest->comment = dest->name + dest->namelen + 1;
+
+ if (remaining_len < 1) {
+ /* No room for comment null termination. */
+ return -1;
+ }
+
+ /* Copy the comment */
+ strlcpy(dest->comment, src->comment, remaining_len);
+
+ /* Save other fields */
+ dest->smbc_type = src->smbc_type;
+ dest->commentlen = strlen(dest->comment);
+ dest->dirlen = ((dest->comment + dest->commentlen + 1) -
+ (char *) dest);
+ } else {
+
+ /* No encoding. Just copy the entry as is. */
+ if (src->dirlen > max_namebuf_len) {
+ return -1;
+ }
+ memcpy(dest, src, src->dirlen);
+ if (src->namelen + 1 < 1) {
+ /* Integer wrap */
+ return -1;
+ }
+ if (src->namelen + 1 >= max_namebuf_len) {
+ /* Comment off the end. */
+ return -1;
+ }
+ dest->comment = (char *)(&dest->name + src->namelen + 1);
+ }
+ return 0;
+}
+
+/*
+ * Routine to get a directory entry
+ */
+
+struct smbc_dirent *
+SMBC_readdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ int maxlen;
+ int ret;
+ struct smbc_dirent *dirp, *dirent;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ DEBUG(0, ("Invalid context in SMBC_readdir_ctx()\n"));
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+
+ errno = EBADF;
+ DEBUG(0, ("Invalid dir in SMBC_readdir_ctx()\n"));
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ DEBUG(0, ("Found file vs directory in SMBC_readdir_ctx()\n"));
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ if (!dir->dir_next) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ dirent = dir->dir_next->dirent;
+ if (!dirent) {
+
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return NULL;
+
+ }
+
+ dirp = &context->internal->dirent;
+ maxlen = sizeof(context->internal->_dirent_name);
+
+ ret = smbc_readdir_internal(context, dirp, dirent, maxlen);
+ if (ret == -1) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ dir->dir_next = dir->dir_next->next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dirplus.
+ *
+ * Update dirplus_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dirplus_list != NULL) {
+ dir->dirplus_next = dir->dirplus_next->next;
+ }
+
+ TALLOC_FREE(frame);
+ return dirp;
+}
+
+/*
+ * Routine to get a directory entry with all attributes
+ */
+
+const struct libsmb_file_info *
+SMBC_readdirplus_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ struct libsmb_file_info *smb_finfo = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+
+ if (context == NULL || !context->internal->initialized) {
+ DBG_ERR("Invalid context in SMBC_readdirplus_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ DBG_ERR("Invalid dir in SMBC_readdirplus_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EBADF;
+ return NULL;
+ }
+
+ if (dir->dirplus_next == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_finfo = dir->dirplus_next->smb_finfo;
+ if (smb_finfo == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+ dir->dirplus_next = dir->dirplus_next->next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dir_list
+ *
+ * Update dir_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dir_list) {
+ dir->dir_next = dir->dir_next->next;
+ }
+
+ TALLOC_FREE(frame);
+ return smb_finfo;
+}
+
+/*
+ * Routine to get a directory entry plus a filled in stat structure if
+ * requested.
+ */
+
+const struct libsmb_file_info *SMBC_readdirplus2_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct stat *st)
+{
+ struct libsmb_file_info *smb_finfo = NULL;
+ struct smbc_dirplus_list *dp_list = NULL;
+ ino_t ino;
+ char *full_pathname = NULL;
+ char *workgroup = NULL;
+ char *server = NULL;
+ uint16_t port = 0;
+ char *share = NULL;
+ char *path = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *options = NULL;
+ int rc;
+ TALLOC_CTX *frame = NULL;
+
+ /*
+ * Allow caller to pass in NULL for stat pointer if
+ * required. This makes this call identical to
+ * smbc_readdirplus().
+ */
+
+ if (st == NULL) {
+ return SMBC_readdirplus_ctx(context, dir);
+ }
+
+ frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+ if (context == NULL || !context->internal->initialized) {
+ DBG_ERR("Invalid context in SMBC_readdirplus2_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ DBG_ERR("Invalid dir in SMBC_readdirplus2_ctx()\n");
+ TALLOC_FREE(frame);
+ errno = EBADF;
+ return NULL;
+ }
+
+ dp_list = dir->dirplus_next;
+ if (dp_list == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ ino = (ino_t)dp_list->ino;
+
+ smb_finfo = dp_list->smb_finfo;
+ if (smb_finfo == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ full_pathname = talloc_asprintf(frame,
+ "%s/%s",
+ dir->fname,
+ smb_finfo->name);
+ if (full_pathname == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ rc = SMBC_parse_path(frame,
+ context,
+ full_pathname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ &options);
+ if (rc != 0) {
+ TALLOC_FREE(frame);
+ errno = ENOENT;
+ return NULL;
+ }
+
+ setup_stat(st,
+ path,
+ smb_finfo->size,
+ smb_finfo->attrs,
+ ino,
+ dir->srv->dev,
+ smb_finfo->atime_ts,
+ smb_finfo->ctime_ts,
+ smb_finfo->mtime_ts);
+
+ TALLOC_FREE(full_pathname);
+
+ dir->dirplus_next = dir->dirplus_next->next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dir_list
+ *
+ * Update dir_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dir_list) {
+ dir->dir_next = dir->dir_next->next;
+ }
+
+ TALLOC_FREE(frame);
+ return smb_finfo;
+}
+
+/*
+ * Routine to get directory entries
+ */
+
+int
+SMBC_getdents_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct smbc_dirent *dirp,
+ int count)
+{
+ int rem = count;
+ int reqd;
+ int maxlen;
+ char *ndir = (char *)dirp;
+ struct smbc_dir_list *dirlist;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Check that all is ok first ... */
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /*
+ * Now, retrieve the number of entries that will fit in what was passed
+ * We have to figure out if the info is in the list, or we need to
+ * send a request to the server to get the info.
+ */
+
+ while ((dirlist = dir->dir_next)) {
+ int ret;
+ struct smbc_dirent *dirent;
+ struct smbc_dirent *currentEntry = (struct smbc_dirent *)ndir;
+
+ if (!dirlist->dirent) {
+
+ errno = ENOENT; /* Bad error */
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* Do urlencoding of next entry, if so selected */
+ dirent = &context->internal->dirent;
+ maxlen = sizeof(context->internal->_dirent_name);
+ ret = smbc_readdir_internal(context, dirent,
+ dirlist->dirent, maxlen);
+ if (ret == -1) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ reqd = dirent->dirlen;
+
+ if (rem < reqd) {
+
+ if (rem < count) { /* We managed to copy something */
+
+ errno = 0;
+ TALLOC_FREE(frame);
+ return count - rem;
+
+ }
+ else { /* Nothing copied ... */
+
+ errno = EINVAL; /* Not enough space ... */
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ }
+
+ memcpy(currentEntry, dirent, reqd); /* Copy the data in ... */
+
+ currentEntry->comment = &currentEntry->name[0] +
+ dirent->namelen + 1;
+
+ ndir += reqd;
+ rem -= reqd;
+
+ /* Try and align the struct for the next entry
+ on a valid pointer boundary by appending zeros */
+ while((rem > 0) && ((uintptr_t)ndir & (sizeof(void*) - 1))) {
+ *ndir = '\0';
+ rem--;
+ ndir++;
+ currentEntry->dirlen++;
+ }
+
+ dir->dir_next = dirlist = dirlist -> next;
+
+ /*
+ * If we are returning file entries, we
+ * have a duplicate list in dirplus.
+ *
+ * Update dirplus_next also so readdir and
+ * readdirplus are kept in sync.
+ */
+ if (dir->dirplus_list != NULL) {
+ dir->dirplus_next = dir->dirplus_next->next;
+ }
+ }
+
+ TALLOC_FREE(frame);
+
+ if (rem == count)
+ return 0;
+ else
+ return count - rem;
+
+}
+
+/*
+ * Routine to create a directory ...
+ */
+
+int
+SMBC_mkdir_ctx(SMBCCTX *context,
+ const char *fname,
+ mode_t mode)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ uint16_t port = 0;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_mkdir(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>mkdir: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>mkdir: resolved path as %s\n", targetpath);*/
+
+ status = cli_mkdir(targetcli, targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+
+}
+
+/*
+ * Our list function simply checks to see if a directory is not empty
+ */
+
+static NTSTATUS
+rmdir_list_fn(struct file_info *finfo,
+ const char *mask,
+ void *state)
+{
+ if (strncmp(finfo->name, ".", 1) != 0 &&
+ strncmp(finfo->name, "..", 2) != 0) {
+ bool *smbc_rmdir_dirempty = (bool *)state;
+ *smbc_rmdir_dirempty = false;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Routine to remove a directory
+ */
+
+int
+SMBC_rmdir_ctx(SMBCCTX *context,
+ const char *fname)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ uint16_t port = 0;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_rmdir(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>rmdir: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>rmdir: resolved path as %s\n", targetpath);*/
+
+ status = cli_rmdir(targetcli, targetpath);
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ errno = cli_status_to_errno(status);
+
+ if (errno == EACCES) { /* Check if the dir empty or not */
+
+ /* Local storage to avoid buffer overflows */
+ char *lpath;
+ bool smbc_rmdir_dirempty = true;
+
+ lpath = talloc_asprintf(frame, "%s\\*",
+ targetpath);
+ if (!lpath) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = cli_list(targetcli, lpath,
+ FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN,
+ rmdir_list_fn,
+ &smbc_rmdir_dirempty);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Fix errno to ignore latest error ... */
+ DBG_INFO("cli_list returned an error: %s\n",
+ nt_errstr(status));
+ errno = EACCES;
+
+ }
+
+ if (smbc_rmdir_dirempty)
+ errno = EACCES;
+ else
+ errno = ENOTEMPTY;
+
+ }
+
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+
+}
+
+/*
+ * Routine to return the current directory position
+ */
+
+off_t
+SMBC_telldir_ctx(SMBCCTX *context,
+ SMBCFILE *dir)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* See if we're already at the end. */
+ if (dir->dir_next == NULL) {
+ /* We are. */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /*
+ * We return the pointer here as the offset
+ */
+ TALLOC_FREE(frame);
+ return (off_t)(long)dir->dir_next->dirent;
+}
+
+/*
+ * A routine to run down the list and see if the entry is OK
+ * Modifies the dir list and the dirplus list (if it exists)
+ * to point at the correct next entry on success.
+ */
+
+static bool update_dir_ents(SMBCFILE *dir, struct smbc_dirent *dirent)
+{
+ struct smbc_dir_list *tmp_dir = dir->dir_list;
+ struct smbc_dirplus_list *tmp_dirplus = dir->dirplus_list;
+
+ /*
+ * Run down the list looking for what we want.
+ * If we're enumerating files both dir_list
+ * and dirplus_list contain the same entry
+ * list, as they were seeded from the same
+ * cli_list callback.
+ *
+ * If we're enumerating servers then
+ * dirplus_list will be NULL, so don't
+ * update in that case.
+ */
+
+ while (tmp_dir != NULL) {
+ if (tmp_dir->dirent == dirent) {
+ dir->dir_next = tmp_dir;
+ if (tmp_dirplus != NULL) {
+ dir->dirplus_next = tmp_dirplus;
+ }
+ return true;
+ }
+ tmp_dir = tmp_dir->next;
+ if (tmp_dirplus != NULL) {
+ tmp_dirplus = tmp_dirplus->next;
+ }
+ }
+ return false;
+}
+
+/*
+ * Routine to seek on a directory
+ */
+
+int
+SMBC_lseekdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ off_t offset)
+{
+ long int l_offset = offset; /* Handle problems of size */
+ struct smbc_dirent *dirent = (struct smbc_dirent *)l_offset;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ok;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (dir->file != False) { /* FIXME, should be dir, perhaps */
+
+ errno = ENOTDIR;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* Now, check what we were passed and see if it is OK ... */
+
+ if (dirent == NULL) { /* Seek to the beginning of the list */
+
+ dir->dir_next = dir->dir_list;
+
+ /* Do the same for dirplus. */
+ dir->dirplus_next = dir->dirplus_list;
+
+ TALLOC_FREE(frame);
+ return 0;
+
+ }
+
+ if (offset == -1) { /* Seek to the end of the list */
+ dir->dir_next = NULL;
+
+ /* Do the same for dirplus. */
+ dir->dirplus_next = NULL;
+
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /*
+ * Run down the list and make sure that the entry is OK.
+ * Update the position of both dir and dirplus lists.
+ */
+
+ ok = update_dir_ents(dir, dirent);
+ if (!ok) {
+ errno = EINVAL; /* Bad entry */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Routine to fstat a dir
+ */
+
+int
+SMBC_fstatdir_ctx(SMBCCTX *context,
+ SMBCFILE *dir,
+ struct stat *st)
+{
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL;
+ return -1;
+ }
+
+ /* No code yet ... */
+ return 0;
+}
+
+int
+SMBC_chmod_ctx(SMBCCTX *context,
+ const char *fname,
+ mode_t newmode)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ char *path = NULL;
+ uint32_t attr;
+ uint16_t port = 0;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_chmod(%s, 0%3o)\n", fname, (unsigned int)newmode));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>unlink: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ attr = 0;
+
+ if (!(newmode & (S_IWUSR | S_IWGRP | S_IWOTH))) attr |= FILE_ATTRIBUTE_READONLY;
+ if ((newmode & S_IXUSR) && lp_map_archive(-1)) attr |= FILE_ATTRIBUTE_ARCHIVE;
+ if ((newmode & S_IXGRP) && lp_map_system(-1)) attr |= FILE_ATTRIBUTE_SYSTEM;
+ if ((newmode & S_IXOTH) && lp_map_hidden(-1)) attr |= FILE_ATTRIBUTE_HIDDEN;
+
+ status = cli_setatr(targetcli, targetpath, attr, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+int
+SMBC_utimes_ctx(SMBCCTX *context,
+ const char *fname,
+ struct timeval *tbuf)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ struct timespec access_time, write_time;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ok;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (tbuf == NULL) {
+ access_time = write_time = timespec_current();
+ } else {
+ access_time = convert_timeval_to_timespec(tbuf[0]);
+ write_time = convert_timeval_to_timespec(tbuf[1]);
+ }
+
+ if (DEBUGLVL(4)) {
+ struct timeval_buf abuf, wbuf;
+
+ dbgtext("smbc_utimes(%s, atime = %s mtime = %s)\n",
+ fname,
+ timespec_string_buf(&access_time, false, &abuf),
+ timespec_string_buf(&write_time, false, &wbuf));
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ ok = SMBC_setatr(
+ context,
+ srv,
+ path,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT },
+ access_time,
+ write_time,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT },
+ 0);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_setatr */
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Routine to unlink() a file
+ */
+
+int
+SMBC_unlink_ctx(SMBCCTX *context,
+ const char *fname)
+{
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ uint16_t port = 0;
+ struct cli_state *targetcli = NULL;
+ SMBCSRV *srv = NULL;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* SMBC_server sets errno */
+
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>unlink: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>unlink: resolved path as %s\n", targetpath);*/
+
+ status = cli_unlink(
+ targetcli,
+ targetpath,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ errno = cli_status_to_errno(status);
+
+ if (errno == EACCES) { /* Check if the file is a directory */
+
+ int saverr = errno;
+ struct stat sb = {0};
+
+ status = SMBC_getatr(context, srv, path, &sb);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Hmmm, bad error ... What? */
+
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+
+ }
+ else {
+
+ if (S_ISDIR(sb.st_mode))
+ errno = EISDIR;
+ else
+ errno = saverr; /* Restore this */
+
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ TALLOC_FREE(frame);
+ return 0; /* Success ... */
+
+}
+
+/*
+ * Routine to rename() a file
+ */
+
+int
+SMBC_rename_ctx(SMBCCTX *ocontext,
+ const char *oname,
+ SMBCCTX *ncontext,
+ const char *nname)
+{
+ char *server1 = NULL;
+ char *share1 = NULL;
+ char *server2 = NULL;
+ char *share2 = NULL;
+ char *user1 = NULL;
+ char *user2 = NULL;
+ char *password1 = NULL;
+ char *password2 = NULL;
+ char *workgroup = NULL;
+ char *path1 = NULL;
+ char *path2 = NULL;
+ char *targetpath1 = NULL;
+ char *targetpath2 = NULL;
+ struct cli_state *targetcli1 = NULL;
+ struct cli_state *targetcli2 = NULL;
+ SMBCSRV *srv = NULL;
+ uint16_t port1 = 0;
+ uint16_t port2 = 0;
+ struct cli_credentials *ocreds = NULL;
+ struct cli_credentials *ncreds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!ocontext || !ncontext ||
+ !ocontext->internal->initialized ||
+ !ncontext->internal->initialized) {
+
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!oname || !nname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_rename(%s,%s)\n", oname, nname));
+
+ if (SMBC_parse_path(frame,
+ ocontext,
+ oname,
+ &workgroup,
+ &server1,
+ &port1,
+ &share1,
+ &path1,
+ &user1,
+ &password1,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user1 || user1[0] == (char)0) {
+ user1 = talloc_strdup(frame, smbc_getUser(ocontext));
+ if (!user1) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ if (SMBC_parse_path(frame,
+ ncontext,
+ nname,
+ NULL,
+ &server2,
+ &port2,
+ &share2,
+ &path2,
+ &user2,
+ &password2,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user2 || user2[0] == (char)0) {
+ user2 = talloc_strdup(frame, smbc_getUser(ncontext));
+ if (!user2) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ if (strcmp(server1, server2) || strcmp(share1, share2) ||
+ strcmp(user1, user2)) {
+ /* Can't rename across file systems, or users?? */
+ errno = EXDEV;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ srv = SMBC_server(frame, ocontext, True,
+ server1, port1, share1, &workgroup, &user1, &password1);
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+
+ /* set the credentials to make DFS work */
+ smbc_set_credentials_with_fallback(ocontext,
+ workgroup,
+ user1,
+ password1);
+
+ /*d_printf(">>>rename: resolving %s\n", path1);*/
+ ocreds = ocontext->internal->creds;
+
+ status = cli_resolve_path(frame, "",
+ ocreds,
+ srv->cli, path1, &targetcli1, &targetpath1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path1);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /* set the credentials to make DFS work */
+ smbc_set_credentials_with_fallback(ncontext,
+ workgroup,
+ user2,
+ password2);
+
+ /*d_printf(">>>rename: resolved path as %s\n", targetpath1);*/
+ /*d_printf(">>>rename: resolving %s\n", path2);*/
+ ncreds = ncontext->internal->creds;
+
+ status = cli_resolve_path(frame, "",
+ ncreds,
+ srv->cli, path2, &targetcli2, &targetpath2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path2);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>rename: resolved path as %s\n", targetpath2);*/
+
+ if (strcmp(smbXcli_conn_remote_name(targetcli1->conn), smbXcli_conn_remote_name(targetcli2->conn)) ||
+ strcmp(targetcli1->share, targetcli2->share))
+ {
+ /* can't rename across file systems */
+ errno = EXDEV;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = cli_rename(targetcli1, targetpath1, targetpath2, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ int eno = cli_status_to_errno(status);
+
+ if (eno != EEXIST ||
+ !NT_STATUS_IS_OK(cli_unlink(targetcli1, targetpath2,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN)) ||
+ !NT_STATUS_IS_OK(cli_rename(targetcli1, targetpath1,
+ targetpath2, false))) {
+
+ errno = eno;
+ TALLOC_FREE(frame);
+ return -1;
+
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return 0; /* Success */
+}
+
+struct smbc_notify_cb_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+ bool recursive;
+ uint32_t completion_filter;
+ unsigned callback_timeout_ms;
+ smbc_notify_callback_fn cb;
+ void *private_data;
+};
+
+static void smbc_notify_cb_got_changes(struct tevent_req *subreq);
+static void smbc_notify_cb_timedout(struct tevent_req *subreq);
+
+static struct tevent_req *smbc_notify_cb_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ uint16_t fnum, bool recursive, uint32_t completion_filter,
+ unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data)
+{
+ struct tevent_req *req, *subreq;
+ struct smbc_notify_cb_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct smbc_notify_cb_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fnum = fnum;
+ state->recursive = recursive;
+ state->completion_filter = completion_filter;
+ state->callback_timeout_ms = callback_timeout_ms;
+ state->cb = cb;
+ state->private_data = private_data;
+
+ subreq = cli_notify_send(
+ state, state->ev, state->cli, state->fnum, 1000,
+ state->completion_filter, state->recursive);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_got_changes, req);
+
+ if (state->callback_timeout_ms == 0) {
+ return req;
+ }
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ tevent_timeval_current_ofs(state->callback_timeout_ms/1000,
+ state->callback_timeout_ms*1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_timedout, req);
+
+ return req;
+}
+
+static void smbc_notify_cb_got_changes(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbc_notify_cb_state *state = tevent_req_data(
+ req, struct smbc_notify_cb_state);
+ uint32_t num_changes;
+ struct notify_change *changes;
+ NTSTATUS status;
+ int cb_ret;
+
+ status = cli_notify_recv(subreq, state, &num_changes, &changes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ {
+ struct smbc_notify_callback_action actions[num_changes];
+ uint32_t i;
+
+ for (i=0; i<num_changes; i++) {
+ actions[i].action = changes[i].action;
+ actions[i].filename = changes[i].name;
+ }
+
+ cb_ret = state->cb(actions, num_changes, state->private_data);
+ }
+
+ TALLOC_FREE(changes);
+
+ if (cb_ret != 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = cli_notify_send(
+ state, state->ev, state->cli, state->fnum, 1000,
+ state->completion_filter, state->recursive);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_got_changes, req);
+}
+
+static void smbc_notify_cb_timedout(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbc_notify_cb_state *state = tevent_req_data(
+ req, struct smbc_notify_cb_state);
+ int cb_ret;
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ cb_ret = state->cb(NULL, 0, state->private_data);
+ if (cb_ret != 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ tevent_timeval_current_ofs(state->callback_timeout_ms/1000,
+ state->callback_timeout_ms*1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbc_notify_cb_timedout, req);
+}
+
+static NTSTATUS smbc_notify_cb_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS smbc_notify_cb(struct cli_state *cli, uint16_t fnum,
+ bool recursive, uint32_t completion_filter,
+ unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = smbc_notify_cb_send(frame, ev, cli, fnum, recursive,
+ completion_filter,
+ callback_timeout_ms, cb, private_data);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = smbc_notify_cb_recv(req);
+ TALLOC_FREE(req);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+int
+SMBC_notify_ctx(SMBCCTX *context, SMBCFILE *dir, smbc_bool recursive,
+ uint32_t completion_filter, unsigned callback_timeout_ms,
+ smbc_notify_callback_fn cb, void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cli_state *cli;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *options = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ uint16_t port;
+ NTSTATUS status;
+ uint16_t fnum;
+
+ if ((context == NULL) || !context->internal->initialized) {
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return -1;
+ }
+ if (!SMBC_dlist_contains(context->internal->files, dir)) {
+ TALLOC_FREE(frame);
+ errno = EBADF;
+ return -1;
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ dir->fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ &options)) {
+ DEBUG(4, ("no valid path\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL + 8194;
+ return -1;
+ }
+
+ DEBUG(4, ("parsed path: fname='%s' server='%s' share='%s' "
+ "path='%s' options='%s'\n",
+ dir->fname, server, share, path, options));
+
+ DEBUG(4, ("%s(%p, %d, %"PRIu32")\n", __func__, dir,
+ (int)recursive, completion_filter));
+
+ cli = dir->srv->cli;
+ status = cli_ntcreate(
+ cli, path, 0, FILE_READ_DATA, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ status = smbc_notify_cb(cli, fnum, recursive != 0, completion_filter,
+ callback_timeout_ms, cb, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_close(cli, fnum);
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ cli_close(cli, fnum);
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/libsmb/libsmb_file.c b/source3/libsmb/libsmb_file.c
new file mode 100644
index 0000000..ff18d56
--- /dev/null
+++ b/source3/libsmb/libsmb_file.c
@@ -0,0 +1,803 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/*
+ * Routine to open() a file ...
+ */
+
+SMBCFILE *
+SMBC_open_ctx(SMBCCTX *context,
+ const char *fname,
+ int flags,
+ mode_t mode)
+{
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ SMBCSRV *srv = NULL;
+ SMBCFILE *file = NULL;
+ uint16_t fd;
+ uint16_t port = 0;
+ NTSTATUS status = NT_STATUS_OBJECT_PATH_INVALID;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+ if (!srv) {
+ if (errno == EPERM) errno = EACCES;
+ TALLOC_FREE(frame);
+ return NULL; /* SMBC_server sets errno */
+ }
+
+ /* Hmmm, the test for a directory is suspect here ... FIXME */
+
+ if (strlen(path) > 0 && path[strlen(path) - 1] == '\\') {
+ status = NT_STATUS_OBJECT_PATH_INVALID;
+ } else {
+ struct cli_credentials *creds = NULL;
+
+ file = SMB_MALLOC_P(SMBCFILE);
+ if (!file) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ ZERO_STRUCTP(file);
+
+ creds = context->internal->creds;
+ /*d_printf(">>>open: resolving %s\n", path);*/
+ status = cli_resolve_path(
+ frame, "",
+ creds,
+ srv->cli, path, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ SAFE_FREE(file);
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ /*d_printf(">>>open: resolved %s as %s\n", path, targetpath);*/
+
+ status = cli_open(targetcli, targetpath, flags,
+ context->internal->share_mode, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ /* Handle the error ... */
+
+ SAFE_FREE(file);
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return NULL;
+ }
+
+ /* Fill in file struct */
+
+ file->cli_fd = fd;
+ file->fname = SMB_STRDUP(fname);
+ file->srv = srv;
+ file->offset = 0;
+ file->file = True;
+ /*
+ * targetcli is either equal to srv->cli or
+ * is a subsidiary DFS connection. Either way
+ * file->cli_fd belongs to it so we must cache
+ * it for read/write/close, not re-resolve each time.
+ * Re-resolving is both slow and incorrect.
+ */
+ file->targetcli = targetcli;
+
+ DLIST_ADD(context->internal->files, file);
+
+ /*
+ * If the file was opened in O_APPEND mode, all write
+ * operations should be appended to the file. To do that,
+ * though, using this protocol, would require a getattrE()
+ * call for each and every write, to determine where the end
+ * of the file is. (There does not appear to be an append flag
+ * in the protocol.) Rather than add all of that overhead of
+ * retrieving the current end-of-file offset prior to each
+ * write operation, we'll assume that most append operations
+ * will continuously write, so we'll just set the offset to
+ * the end of the file now and hope that's adequate.
+ *
+ * Note to self: If this proves inadequate, and O_APPEND
+ * should, in some cases, be forced for each write, add a
+ * field in the context options structure, for
+ * "strict_append_mode" which would select between the current
+ * behavior (if FALSE) or issuing a getattrE() prior to each
+ * write and forcing the write to the end of the file (if
+ * TRUE). Adding that capability will likely require adding
+ * an "append" flag into the _SMBCFILE structure to track
+ * whether a file was opened in O_APPEND mode. -- djl
+ */
+ if (flags & O_APPEND) {
+ if (SMBC_lseek_ctx(context, file, 0, SEEK_END) < 0) {
+ (void) SMBC_close_ctx(context, file);
+ errno = ENXIO;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return file;
+ }
+
+ /* Check if opendir needed ... */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ file = smbc_getFunctionOpendir(context)(context, fname);
+ TALLOC_FREE(frame);
+ if (file == NULL) {
+ errno = cli_status_to_errno(status);
+ }
+ return file;
+ }
+
+ errno = EINVAL; /* FIXME, correct errno ? */
+ TALLOC_FREE(frame);
+ return NULL;
+}
+
+/*
+ * Routine to create a file
+ */
+
+SMBCFILE *
+SMBC_creat_ctx(SMBCCTX *context,
+ const char *path,
+ mode_t mode)
+{
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ return SMBC_open_ctx(context, path,
+ O_WRONLY | O_CREAT | O_TRUNC, mode);
+}
+
+/*
+ * Routine to read() a file ...
+ */
+
+ssize_t
+SMBC_read_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ void *buf,
+ size_t count)
+{
+ size_t ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ /*
+ * offset:
+ *
+ * Compiler bug (possibly) -- gcc (GCC) 3.3.5 (Debian 1:3.3.5-2) --
+ * appears to pass file->offset (which is type off_t) differently than
+ * a local variable of type off_t. Using local variable "offset" in
+ * the call to cli_read() instead of file->offset fixes a problem
+ * retrieving data at an offset greater than 4GB.
+ */
+ off_t offset;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_read(%p, %zu)\n", file, count));
+
+ if (!SMBC_dlist_contains(context->internal->files, file)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ offset = file->offset;
+
+ /* Check that the buffer exists ... */
+
+ if (buf == NULL) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = cli_read(file->targetcli, file->cli_fd, (char *)buf, offset,
+ count, &ret);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ file->offset += ret;
+
+ DEBUG(4, (" --> %zu\n", ret));
+
+ TALLOC_FREE(frame);
+ return ret; /* Success, ret bytes of data ... */
+}
+
+off_t
+SMBC_splice_ctx(SMBCCTX *context,
+ SMBCFILE *srcfile,
+ SMBCFILE *dstfile,
+ off_t count,
+ int (*splice_cb)(off_t n, void *priv),
+ void *priv)
+{
+ off_t written = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, srcfile)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, dstfile)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = cli_splice(srcfile->targetcli, dstfile->targetcli,
+ srcfile->cli_fd, dstfile->cli_fd,
+ count, srcfile->offset, dstfile->offset, &written,
+ splice_cb, priv);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ srcfile->offset += written;
+ dstfile->offset += written;
+
+ TALLOC_FREE(frame);
+ return written;
+}
+
+/*
+ * Routine to write() a file ...
+ */
+
+ssize_t
+SMBC_write_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ const void *buf,
+ size_t count)
+{
+ off_t offset;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ /* First check all pointers before dereferencing them */
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, file)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /* Check that the buffer exists ... */
+
+ if (buf == NULL) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ offset = file->offset; /* See "offset" comment in SMBC_read_ctx() */
+
+ status = cli_writeall(file->targetcli, file->cli_fd,
+ 0, (const uint8_t *)buf, offset, count, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ file->offset += count;
+
+ TALLOC_FREE(frame);
+ return count; /* Success, 0 bytes of data ... */
+}
+
+/*
+ * Routine to close() a file ...
+ */
+
+int
+SMBC_close_ctx(SMBCCTX *context,
+ SMBCFILE *file)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, file)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /* IS a dir ... */
+ if (!file->file) {
+ TALLOC_FREE(frame);
+ return smbc_getFunctionClosedir(context)(context, file);
+ }
+
+ status = cli_close(file->targetcli, file->cli_fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ SMBCSRV *srv;
+ DEBUG(3, ("cli_close failed on %s. purging server.\n",
+ file->fname));
+ /* Deallocate slot and remove the server
+ * from the server cache if unused */
+ srv = file->srv;
+ DLIST_REMOVE(context->internal->files, file);
+ SAFE_FREE(file->fname);
+ SAFE_FREE(file);
+ smbc_getFunctionRemoveUnusedServer(context)(context, srv);
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ DLIST_REMOVE(context->internal->files, file);
+ SAFE_FREE(file->fname);
+ SAFE_FREE(file);
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Get info from an SMB server on a file. Use a qpathinfo call first
+ * and if that fails, use getatr, as Win95 sometimes refuses qpathinfo
+ */
+NTSTATUS
+SMBC_getatr(SMBCCTX * context,
+ SMBCSRV *srv,
+ const char *path,
+ struct stat *sb)
+{
+ char *fixedpath = NULL;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ uint32_t attr = 0;
+ off_t size = 0;
+ struct timespec create_time_ts = {0};
+ struct timespec access_time_ts = {0};
+ struct timespec write_time_ts = {0};
+ struct timespec change_time_ts = {0};
+ struct timespec w_time_ts = {0};
+ time_t write_time = 0;
+ SMB_INO_T ino = 0;
+ mode_t mode = S_IFREG;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* path fixup for . and .. */
+ if (ISDOT(path) || ISDOTDOT(path)) {
+ fixedpath = talloc_strdup(frame, "\\");
+ if (!fixedpath) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ fixedpath = talloc_strdup(frame, path);
+ if (!fixedpath) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ trim_string(fixedpath, NULL, "\\..");
+ trim_string(fixedpath, NULL, "\\.");
+ }
+ DEBUG(4,("SMBC_getatr: sending qpathinfo\n"));
+
+ creds = context->internal->creds;
+
+ status = cli_resolve_path(frame, "",
+ creds,
+ srv->cli, fixedpath,
+ &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Couldn't resolve %s\n", path);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!srv->no_pathinfo2) {
+ bool not_supported_error = false;
+ status = cli_qpathinfo2(targetcli,
+ targetpath,
+ &create_time_ts,
+ &access_time_ts,
+ &write_time_ts,
+ &change_time_ts,
+ &size,
+ &attr,
+ &ino,
+ &mode);
+ if (NT_STATUS_IS_OK(status)) {
+ goto setup_stat;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ not_supported_error = true;
+ }
+ if (!not_supported_error) {
+ /* "Normal error". Just return it to caller. */
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ srv->no_pathinfo2 = True;
+
+ if (!srv->no_pathinfo3) {
+ bool not_supported_error = false;
+ status = cli_qpathinfo3(targetcli,
+ targetpath,
+ &create_time_ts,
+ &access_time_ts,
+ &write_time_ts,
+ &change_time_ts,
+ &size,
+ &attr,
+ &ino);
+ if (NT_STATUS_IS_OK(status)) {
+ goto setup_stat;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ not_supported_error = true;
+ }
+ if (!not_supported_error) {
+ /* "Normal error". Just return it to caller. */
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ srv->no_pathinfo3 = True;
+
+ /* if this is NT then don't bother with the getatr */
+ if (smb1cli_conn_capabilities(targetcli->conn) & CAP_NT_SMBS) {
+ goto all_failed;
+ }
+
+ status = cli_getatr(targetcli, targetpath, &attr, &size, &write_time);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto all_failed;
+ }
+ w_time_ts = convert_time_t_to_timespec(write_time);
+ access_time_ts = change_time_ts = write_time_ts = w_time_ts;
+
+setup_stat:
+ setup_stat(sb,
+ path,
+ size,
+ attr,
+ ino,
+ srv->dev,
+ access_time_ts,
+ change_time_ts,
+ write_time_ts);
+
+ if ((context->internal->posix_extensions) && (mode != S_IFREG)) {
+ sb->st_mode = (sb->st_mode & ~S_IFMT) | mode;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+
+all_failed:
+ srv->no_pathinfo2 = False;
+ srv->no_pathinfo3 = False;
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * Set file info on an SMB server. Use setpathinfo call first. If that
+ * fails, use setattrE..
+ *
+ * Access and modification time parameters are always used and must be
+ * provided. Create time, if zero, will be determined from the actual create
+ * time of the file. If non-zero, the create time will be set as well.
+ *
+ * "attr" (attributes) parameter may be set to -1 if it is not to be set.
+ */
+bool
+SMBC_setatr(SMBCCTX * context, SMBCSRV *srv, char *path,
+ struct timespec create_time,
+ struct timespec access_time,
+ struct timespec write_time,
+ struct timespec change_time,
+ uint16_t attr)
+{
+ uint16_t fd;
+ uint32_t lattr = (uint32_t)attr;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (attr == (uint16_t)-1) {
+ /*
+ * External ABI only passes in
+ * 16-bits of attribute. Make
+ * sure we correctly map to
+ * (uint32_t)-1 meaning don't
+ * change attributes if attr was
+ * passed in as 16-bit -1.
+ */
+ lattr = (uint32_t)-1;
+ }
+
+
+ /*
+ * First, try setpathinfo (if qpathinfo succeeded), for it is the
+ * modern function for "new code" to be using, and it works given a
+ * filename rather than requiring that the file be opened to have its
+ * attributes manipulated.
+ */
+ if (srv->no_pathinfo ||
+ !NT_STATUS_IS_OK(cli_setpathinfo_ext(srv->cli, path,
+ create_time,
+ access_time,
+ write_time,
+ change_time,
+ lattr))) {
+
+ /*
+ * setpathinfo is not supported; go to plan B.
+ *
+ * cli_setatr() does not work on win98, and it also doesn't
+ * support setting the access time (only the modification
+ * time), so in all cases, we open the specified file and use
+ * cli_setattrE() which should work on all OS versions, and
+ * supports both times.
+ */
+
+ /* Don't try {q,set}pathinfo() again, with this server */
+ srv->no_pathinfo = True;
+
+ /* Open the file */
+ status = cli_open(srv->cli, path, O_RDWR, DENY_NONE, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return False;
+ }
+
+ /* Set the new attributes */
+ status = cli_setattrE(
+ srv->cli,
+ fd,
+ change_time.tv_sec,
+ access_time.tv_sec,
+ write_time.tv_sec);
+
+ /* Close the file */
+ cli_close(srv->cli, fd);
+
+ /*
+ * Unfortunately, setattrE() doesn't have a provision for
+ * setting the access attr (attributes). We'll have to try
+ * cli_setatr() for that, and with only this parameter, it
+ * seems to work on win98.
+ */
+ if (NT_STATUS_IS_OK(status) && attr != (uint16_t) -1) {
+ status = cli_setatr(srv->cli, path, (uint32_t)attr, 0);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return False;
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/*
+ * A routine to lseek() a file
+ */
+
+off_t
+SMBC_lseek_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ off_t offset,
+ int whence)
+{
+ off_t size;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, file)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!file->file) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1; /* Can't lseek a dir ... */
+ }
+
+ switch (whence) {
+ case SEEK_SET:
+ file->offset = offset;
+ break;
+ case SEEK_CUR:
+ file->offset += offset;
+ break;
+ case SEEK_END:
+ if (!NT_STATUS_IS_OK(cli_qfileinfo_basic(
+ file->targetcli, file->cli_fd, NULL,
+ &size, NULL, NULL, NULL, NULL,
+ NULL))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ file->offset = size + offset;
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
+
+ TALLOC_FREE(frame);
+ return file->offset;
+}
+
+
+/*
+ * Routine to truncate a file given by its file descriptor, to a specified size
+ */
+
+int
+SMBC_ftruncate_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ off_t length)
+{
+ off_t size = length;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, file)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!file->file) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(cli_ftruncate(file->targetcli, file->cli_fd, (uint64_t)size))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/libsmb/libsmb_misc.c b/source3/libsmb/libsmb_misc.c
new file mode 100644
index 0000000..4509895
--- /dev/null
+++ b/source3/libsmb/libsmb_misc.c
@@ -0,0 +1,46 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+
+
+/*
+ * check if an element is part of the list.
+ */
+bool SMBC_dlist_contains(SMBCFILE * list, SMBCFILE *p)
+{
+ if ((p == NULL) || (list == NULL)) {
+ return false;
+ }
+ do {
+ if (p == list) {
+ return true;
+ }
+ list = list->next;
+ } while (list != NULL);
+ return false;
+}
diff --git a/source3/libsmb/libsmb_path.c b/source3/libsmb/libsmb_path.c
new file mode 100644
index 0000000..615de1c
--- /dev/null
+++ b/source3/libsmb/libsmb_path.c
@@ -0,0 +1,416 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+
+
+/*
+ * smbc_urldecode()
+ * and urldecode_talloc() (internal fn.)
+ *
+ * Convert strings of %xx to their single character equivalent. Each 'x' must
+ * be a valid hexadecimal digit, or that % sequence is left undecoded.
+ *
+ * dest may, but need not be, the same pointer as src.
+ *
+ * Returns the number of % sequences which could not be converted due to lack
+ * of two following hexadecimal digits.
+ */
+static int
+urldecode_talloc(TALLOC_CTX *ctx, char **pp_dest, const char *src)
+{
+ int old_length = strlen(src);
+ int i = 0;
+ int err_count = 0;
+ size_t newlen = 1;
+ char *p, *dest;
+
+ if (old_length == 0) {
+ return 0;
+ }
+
+ *pp_dest = NULL;
+ for (i = 0; i < old_length; ) {
+ unsigned char character = src[i++];
+
+ if (character == '%') {
+ uint8_t v;
+ bool ok = hex_byte(&src[i], &v);
+ if (ok) {
+ character = v;
+ if (character == '\0') {
+ break; /* Stop at %00 */
+ }
+ i += 2;
+ } else {
+ err_count++;
+ }
+ }
+ newlen++;
+ }
+
+ dest = talloc_array(ctx, char, newlen);
+ if (!dest) {
+ return err_count;
+ }
+
+ err_count = 0;
+ for (p = dest, i = 0; i < old_length; ) {
+ unsigned char character = src[i++];
+
+ if (character == '%') {
+ uint8_t v;
+ bool ok = hex_byte(&src[i], &v);
+ if (ok) {
+ character = v;
+ if (character == '\0') {
+ break; /* Stop at %00 */
+ }
+ i += 2;
+ } else {
+ err_count++;
+ }
+ }
+ *p++ = character;
+ }
+
+ *p = '\0';
+ *pp_dest = dest;
+ return err_count;
+}
+
+int
+smbc_urldecode(char *dest,
+ char *src,
+ size_t max_dest_len)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *pdest;
+ int ret = urldecode_talloc(frame, &pdest, src);
+
+ if (pdest) {
+ strlcpy(dest, pdest, max_dest_len);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/*
+ * smbc_urlencode()
+ *
+ * Convert any characters not specifically allowed in a URL into their %xx
+ * equivalent.
+ *
+ * Returns the remaining buffer length.
+ */
+int
+smbc_urlencode(char *dest,
+ char *src,
+ int max_dest_len)
+{
+ char hex[] = "0123456789ABCDEF";
+
+ for (; *src != '\0' && max_dest_len >= 3; src++) {
+
+ if ((*src < '0' &&
+ *src != '-' &&
+ *src != '.') ||
+ (*src > '9' &&
+ *src < 'A') ||
+ (*src > 'Z' &&
+ *src < 'a' &&
+ *src != '_') ||
+ (*src > 'z')) {
+ *dest++ = '%';
+ *dest++ = hex[(*src >> 4) & 0x0f];
+ *dest++ = hex[*src & 0x0f];
+ max_dest_len -= 3;
+ } else {
+ *dest++ = *src;
+ max_dest_len--;
+ }
+ }
+
+ if (max_dest_len <= 0) {
+ /* Ensure we return -1 if no null termination. */
+ return -1;
+ }
+
+ *dest++ = '\0';
+ max_dest_len--;
+
+ return max_dest_len;
+}
+
+/*
+ * Function to parse a path and turn it into components
+ *
+ * The general format of an SMB URI is explain in Christopher Hertel's CIFS
+ * book, at http://ubiqx.org/cifs/Appendix-D.html. We accept a subset of the
+ * general format ("smb:" only; we do not look for "cifs:").
+ *
+ *
+ * We accept:
+ * smb://[[[domain;]user[:password]@]server[:port][/share[/path[/file]]]]
+ * [?options]
+ *
+ * Meaning of URLs:
+ *
+ * smb:// Show all workgroups.
+ *
+ * The method of locating the list of workgroups varies
+ * depending upon the setting of the context variable
+ * context->options.browse_max_lmb_count. This value
+ * determines the maximum number of local master browsers to
+ * query for the list of workgroups. In order to ensure that
+ * a complete list of workgroups is obtained, all master
+ * browsers must be queried, but if there are many
+ * workgroups, the time spent querying can begin to add up.
+ * For small networks (not many workgroups), it is suggested
+ * that this variable be set to 0, indicating query all local
+ * master browsers. When the network has many workgroups, a
+ * reasonable setting for this variable might be around 3.
+ *
+ * smb://name/ if name<1D> or name<1B> exists, list servers in
+ * workgroup, else, if name<20> exists, list all shares
+ * for server ...
+ *
+ * If "options" are provided, this function returns the entire option list as a
+ * string, for later parsing by the caller. Note that currently, no options
+ * are supported.
+ */
+
+#define SMBC_PREFIX "smb:"
+
+int
+SMBC_parse_path(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *fname,
+ char **pp_workgroup,
+ char **pp_server,
+ uint16_t *p_port,
+ char **pp_share,
+ char **pp_path,
+ char **pp_user,
+ char **pp_password,
+ char **pp_options)
+{
+ char *s;
+ const char *p;
+ char *q, *r;
+ char *workgroup = NULL;
+ int len;
+
+ /* Ensure these returns are at least valid pointers. */
+ *pp_server = talloc_strdup(ctx, "");
+ *p_port = smbc_getPort(context);
+ *pp_share = talloc_strdup(ctx, "");
+ *pp_path = talloc_strdup(ctx, "");
+ *pp_user = talloc_strdup(ctx, "");
+ *pp_password = talloc_strdup(ctx, "");
+
+ if (!*pp_server || !*pp_share || !*pp_path ||
+ !*pp_user || !*pp_password) {
+ return -1;
+ }
+
+ /*
+ * Assume we won't find an authentication domain to parse, so default
+ * to the workgroup in the provided context.
+ */
+ if (pp_workgroup != NULL) {
+ *pp_workgroup =
+ talloc_strdup(ctx, smbc_getWorkgroup(context));
+ }
+
+ if (pp_options) {
+ *pp_options = talloc_strdup(ctx, "");
+ }
+ s = talloc_strdup(ctx, fname);
+
+ /* see if it has the right prefix */
+ len = strlen(SMBC_PREFIX);
+ if (strncmp(s,SMBC_PREFIX,len) || (s[len] != '/' && s[len] != 0)) {
+ return -1; /* What about no smb: ? */
+ }
+
+ p = s + len;
+
+ /* Watch the test below, we are testing to see if we should exit */
+
+ if (strncmp(p, "//", 2) && strncmp(p, "\\\\", 2)) {
+ DEBUG(1, ("Invalid path (does not begin with smb://)\n"));
+ return -1;
+ }
+
+ p += 2; /* Skip the double slash */
+
+ /* See if any options were specified */
+ if ((q = strrchr(p, '?')) != NULL ) {
+ /* There are options. Null terminate here and point to them */
+ *q++ = '\0';
+
+ DEBUG(4, ("Found options '%s'\n", q));
+
+ /* Copy the options */
+ if (pp_options && *pp_options != NULL) {
+ TALLOC_FREE(*pp_options);
+ *pp_options = talloc_strdup(ctx, q);
+ }
+ }
+
+ if (*p == '\0') {
+ goto decoding;
+ }
+
+ if (*p == '/') {
+ *pp_server = talloc_strndup(
+ ctx, smbc_getWorkgroup(context), 16);
+ if (!*pp_server) {
+ return -1;
+ }
+ return 0;
+ }
+
+ /*
+ * ok, its for us. Now parse out the server, share etc.
+ *
+ * However, we want to parse out [[domain;]user[:password]@] if it
+ * exists ...
+ */
+
+ /* check that '@' occurs before '/', if '/' exists at all */
+ q = strchr_m(p, '@');
+ r = strchr_m(p, '/');
+ if (q && (!r || q < r)) {
+ char *userinfo = NULL;
+ const char *u;
+
+ next_token_no_ltrim_talloc(ctx, &p, &userinfo, "@");
+ if (!userinfo) {
+ return -1;
+ }
+ u = userinfo;
+
+ if (strchr_m(u, ';')) {
+ next_token_no_ltrim_talloc(ctx, &u, &workgroup, ";");
+ if (!workgroup) {
+ return -1;
+ }
+ if (pp_workgroup) {
+ *pp_workgroup = workgroup;
+ }
+ }
+
+ if (strchr_m(u, ':')) {
+ next_token_no_ltrim_talloc(ctx, &u, pp_user, ":");
+ if (!*pp_user) {
+ return -1;
+ }
+ *pp_password = talloc_strdup(ctx, u);
+ if (!*pp_password) {
+ return -1;
+ }
+ } else {
+ *pp_user = talloc_strdup(ctx, u);
+ if (!*pp_user) {
+ return -1;
+ }
+ }
+ }
+
+ if (!next_token_talloc(ctx, &p, pp_server, "/")) {
+ return -1;
+ }
+
+ /*
+ * Does *pp_server contain a ':' ? If so
+ * this denotes the port.
+ */
+ q = strchr_m(*pp_server, ':');
+ if (q != NULL) {
+ long int port;
+ char *endptr = NULL;
+ *q = '\0';
+ q++;
+ if (*q == '\0') {
+ /* Bad port. */
+ return -1;
+ }
+ port = strtol(q, &endptr, 10);
+ if (*endptr != '\0') {
+ /* Bad port. */
+ return -1;
+ }
+ *p_port = (uint16_t)port;
+ }
+
+ if (*p == (char)0) {
+ goto decoding; /* That's it ... */
+ }
+
+ if (!next_token_talloc(ctx, &p, pp_share, "/")) {
+ return -1;
+ }
+
+ /*
+ * Prepend a leading slash if there's a file path, as required by
+ * NetApp filers.
+ */
+ if (*p != '\0') {
+ *pp_path = talloc_asprintf(ctx,
+ "\\%s",
+ p);
+ } else {
+ *pp_path = talloc_strdup(ctx, "");
+ }
+ if (!*pp_path) {
+ return -1;
+ }
+ string_replace(*pp_path, '/', '\\');
+
+decoding:
+ (void) urldecode_talloc(ctx, pp_path, *pp_path);
+ (void) urldecode_talloc(ctx, pp_server, *pp_server);
+ (void) urldecode_talloc(ctx, pp_share, *pp_share);
+ (void) urldecode_talloc(ctx, pp_user, *pp_user);
+ (void) urldecode_talloc(ctx, pp_password, *pp_password);
+
+ if (!workgroup) {
+ workgroup = talloc_strdup(ctx, smbc_getWorkgroup(context));
+ }
+ if (!workgroup) {
+ return -1;
+ }
+
+ /* set the credentials to make DFS work */
+ smbc_set_credentials_with_fallback(context,
+ workgroup,
+ *pp_user,
+ *pp_password);
+ return 0;
+}
+
diff --git a/source3/libsmb/libsmb_printjob.c b/source3/libsmb/libsmb_printjob.c
new file mode 100644
index 0000000..2b05dd0
--- /dev/null
+++ b/source3/libsmb/libsmb_printjob.c
@@ -0,0 +1,335 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+
+
+/*
+ * Open a print file to be written to by other calls
+ */
+
+SMBCFILE *
+SMBC_open_print_job_ctx(SMBCCTX *context,
+ const char *fname)
+{
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *path = NULL;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ DEBUG(4, ("SMBC_open_print_job_ctx(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ NULL,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ /* What if the path is empty, or the file exists? */
+
+ TALLOC_FREE(frame);
+ return smbc_getFunctionOpen(context)(context, fname, O_WRONLY, 666);
+}
+
+/*
+ * Routine to print a file on a remote server ...
+ *
+ * We open the file, which we assume to be on a remote server, and then
+ * copy it to a print file on the share specified by printq.
+ */
+
+int
+SMBC_print_file_ctx(SMBCCTX *c_file,
+ const char *fname,
+ SMBCCTX *c_print,
+ const char *printq)
+{
+ SMBCFILE *fid1;
+ SMBCFILE *fid2;
+ smbc_open_fn f_open1;
+ smbc_open_print_job_fn f_open_pj2;
+ int bytes;
+ int saverr;
+ int tot_bytes = 0;
+ char buf[4096];
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!c_file || !c_file->internal->initialized ||
+ !c_print || !c_print->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname && !printq) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /* Try to open the file for reading ... */
+ f_open1 = smbc_getFunctionOpen(c_file);
+ if (f_open1 == NULL) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ fid1 = f_open1(c_file, fname, O_RDONLY, 0666);
+ if (fid1 == NULL) {
+ DEBUG(3, ("Error, fname=%s, errno=%i\n", fname, errno));
+ TALLOC_FREE(frame);
+ return -1; /* smbc_open sets errno */
+ }
+
+ /* Now, try to open the printer file for writing */
+ f_open_pj2 = smbc_getFunctionOpenPrintJob(c_print);
+ if (f_open_pj2 == NULL) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ fid2 = f_open_pj2(c_print, printq);
+ if (fid2 == NULL) {
+ saverr = errno; /* Save errno */
+ smbc_getFunctionClose(c_file)(c_file, fid1);
+ errno = saverr;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ while ((bytes = smbc_getFunctionRead(c_file)(c_file, fid1,
+ buf, sizeof(buf))) > 0) {
+ tot_bytes += bytes;
+
+ if ((smbc_getFunctionWrite(c_print)(c_print, fid2,
+ buf, bytes)) < 0) {
+ saverr = errno;
+ smbc_getFunctionClose(c_file)(c_file, fid1);
+ smbc_getFunctionClose(c_print)(c_print, fid2);
+ errno = saverr;
+ }
+ }
+
+ saverr = errno;
+
+ smbc_getFunctionClose(c_file)(c_file, fid1);
+ smbc_getFunctionClose(c_print)(c_print, fid2);
+
+ if (bytes < 0) {
+ errno = saverr;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return tot_bytes;
+}
+
+/*
+ * Routine to list print jobs on a printer share ...
+ */
+
+int
+SMBC_list_print_jobs_ctx(SMBCCTX *context,
+ const char *fname,
+ smbc_list_print_job_fn fn)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_list_print_jobs(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ status = cli_print_queue(srv->cli,
+ (void (*)(struct print_job_info *))fn);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Delete a print job from a remote printer share
+ */
+
+int
+SMBC_unlink_print_job_ctx(SMBCCTX *context,
+ const char *fname,
+ int id)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_unlink_print_job(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ status = cli_printjob_del(srv->cli, id);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = cli_status_to_errno(status);
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
diff --git a/source3/libsmb/libsmb_server.c b/source3/libsmb/libsmb_server.c
new file mode 100644
index 0000000..69b51b8
--- /dev/null
+++ b/source3/libsmb/libsmb_server.c
@@ -0,0 +1,910 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+ Copyright (C) SATOH Fumiyasu <fumiyas@osstech.co.jp> 2009.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "libcli/security/security.h"
+#include "libsmb/nmblib.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/*
+ * Check a server for being alive and well.
+ * returns 0 if the server is in shape. Returns 1 on error
+ *
+ * Also usable outside libsmbclient to enable external cache
+ * to do some checks too.
+ */
+int
+SMBC_check_server(SMBCCTX * context,
+ SMBCSRV * server)
+{
+ time_t now;
+
+ if (!cli_state_is_connected(server->cli)) {
+ return 1;
+ }
+
+ now = time_mono(NULL);
+
+ if (server->last_echo_time == (time_t)0 ||
+ now > server->last_echo_time +
+ (server->cli->timeout/1000)) {
+ unsigned char data[16] = {0};
+ NTSTATUS status = cli_echo(server->cli,
+ 1,
+ data_blob_const(data, sizeof(data)));
+ if (!NT_STATUS_IS_OK(status)) {
+ bool ok = false;
+ /*
+ * Some SMB2 servers (not Samba or Windows)
+ * check the session status on SMB2_ECHO and return
+ * NT_STATUS_USER_SESSION_DELETED
+ * if the session was not set. That's OK, they still
+ * replied.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13218
+ */
+ if (smbXcli_conn_protocol(server->cli->conn) >=
+ PROTOCOL_SMB2_02) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_USER_SESSION_DELETED)) {
+ ok = true;
+ }
+ }
+ /*
+ * Some NetApp servers return
+ * NT_STATUS_INVALID_PARAMETER.That's OK, they still
+ * replied.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13007
+ */
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_INVALID_PARAMETER)) {
+ ok = true;
+ }
+ if (!ok) {
+ return 1;
+ }
+ }
+ server->last_echo_time = now;
+ }
+ return 0;
+}
+
+/*
+ * Remove a server from the cached server list it's unused.
+ * On success, 0 is returned. 1 is returned if the server could not be removed.
+ *
+ * Also usable outside libsmbclient
+ */
+int
+SMBC_remove_unused_server(SMBCCTX * context,
+ SMBCSRV * srv)
+{
+ SMBCFILE * file;
+
+ /* are we being fooled ? */
+ if (!context || !context->internal->initialized || !srv) {
+ return 1;
+ }
+
+ /* Check all open files/directories for a relation with this server */
+ for (file = context->internal->files; file; file = file->next) {
+ if (file->srv == srv) {
+ /* Still used */
+ DEBUG(3, ("smbc_remove_usused_server: "
+ "%p still used by %p.\n",
+ srv, file));
+ return 1;
+ }
+ }
+
+ DLIST_REMOVE(context->internal->servers, srv);
+
+ cli_shutdown(srv->cli);
+ srv->cli = NULL;
+
+ DEBUG(3, ("smbc_remove_usused_server: %p removed.\n", srv));
+
+ smbc_getFunctionRemoveCachedServer(context)(context, srv);
+
+ SAFE_FREE(srv);
+ return 0;
+}
+
+/****************************************************************
+ * Call the auth_fn with fixed size (fstring) buffers.
+ ***************************************************************/
+static void
+SMBC_call_auth_fn(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ fstring workgroup = { 0 };
+ fstring username = { 0 };
+ fstring password = { 0 };
+ smbc_get_auth_data_with_context_fn auth_with_context_fn;
+
+ if (*pp_workgroup != NULL) {
+ strlcpy(workgroup, *pp_workgroup, sizeof(workgroup));
+ }
+ if (*pp_username != NULL) {
+ strlcpy(username, *pp_username, sizeof(username));
+ }
+ if (*pp_password != NULL) {
+ strlcpy(password, *pp_password, sizeof(password));
+ }
+
+ /* See if there's an authentication with context function provided */
+ auth_with_context_fn = smbc_getFunctionAuthDataWithContext(context);
+ if (auth_with_context_fn)
+ {
+ (* auth_with_context_fn)(context,
+ server, share,
+ workgroup, sizeof(workgroup),
+ username, sizeof(username),
+ password, sizeof(password));
+ }
+ else
+ {
+ smbc_getFunctionAuthData(context)(server, share,
+ workgroup, sizeof(workgroup),
+ username, sizeof(username),
+ password, sizeof(password));
+ }
+
+ TALLOC_FREE(*pp_workgroup);
+ TALLOC_FREE(*pp_username);
+ TALLOC_FREE(*pp_password);
+
+ *pp_workgroup = talloc_strdup(ctx, workgroup);
+ *pp_username = talloc_strdup(ctx, username);
+ *pp_password = talloc_strdup(ctx, password);
+}
+
+
+void
+SMBC_get_auth_data(const char *server, const char *share,
+ char *workgroup_buf, int workgroup_buf_len,
+ char *username_buf, int username_buf_len,
+ char *password_buf, int password_buf_len)
+{
+ /* Default function just uses provided data. Nothing to do. */
+}
+
+
+
+SMBCSRV *
+SMBC_find_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ SMBCSRV *srv;
+ int auth_called = 0;
+
+ if (!pp_workgroup || !pp_username || !pp_password) {
+ return NULL;
+ }
+
+check_server_cache:
+
+ srv = smbc_getFunctionGetCachedServer(context)(context,
+ server, share,
+ *pp_workgroup,
+ *pp_username);
+
+ if (!auth_called && !srv && (!*pp_username || !(*pp_username)[0] ||
+ !*pp_password || !(*pp_password)[0])) {
+ SMBC_call_auth_fn(ctx, context, server, share,
+ pp_workgroup, pp_username, pp_password);
+
+ /*
+ * However, smbc_auth_fn may have picked up info relating to
+ * an existing connection, so try for an existing connection
+ * again ...
+ */
+ auth_called = 1;
+ goto check_server_cache;
+
+ }
+
+ if (srv) {
+ if (smbc_getFunctionCheckServer(context)(context, srv)) {
+ /*
+ * This server is no good anymore
+ * Try to remove it and check for more possible
+ * servers in the cache
+ */
+ if (smbc_getFunctionRemoveUnusedServer(context)(context,
+ srv)) {
+ /*
+ * We could not remove the server completely,
+ * remove it from the cache so we will not get
+ * it again. It will be removed when the last
+ * file/dir is closed.
+ */
+ smbc_getFunctionRemoveCachedServer(context)(context,
+ srv);
+ }
+
+ /*
+ * Maybe there are more cached connections to this
+ * server
+ */
+ goto check_server_cache;
+ }
+
+ return srv;
+ }
+
+ return NULL;
+}
+
+static struct cli_credentials *SMBC_auth_credentials(TALLOC_CTX *mem_ctx,
+ SMBCCTX *context,
+ const char *domain,
+ const char *username,
+ const char *password)
+{
+ struct cli_credentials *creds = NULL;
+ bool use_kerberos = false;
+ bool fallback_after_kerberos = false;
+ bool use_ccache = false;
+ bool pw_nt_hash = false;
+
+ use_kerberos = smbc_getOptionUseKerberos(context);
+ fallback_after_kerberos = smbc_getOptionFallbackAfterKerberos(context);
+ use_ccache = smbc_getOptionUseCCache(context);
+ pw_nt_hash = smbc_getOptionUseNTHash(context);
+
+ creds = cli_session_creds_init(mem_ctx,
+ username,
+ domain,
+ NULL, /* realm */
+ password,
+ use_kerberos,
+ fallback_after_kerberos,
+ use_ccache,
+ pw_nt_hash);
+ if (creds == NULL) {
+ return NULL;
+ }
+
+ switch (context->internal->smb_encryption_level) {
+ case SMBC_ENCRYPTLEVEL_DEFAULT:
+ /* Use the config option */
+ break;
+ case SMBC_ENCRYPTLEVEL_NONE:
+ (void)cli_credentials_set_smb_encryption(
+ creds,
+ SMB_ENCRYPTION_OFF,
+ CRED_SPECIFIED);
+ break;
+ case SMBC_ENCRYPTLEVEL_REQUEST:
+ (void)cli_credentials_set_smb_encryption(
+ creds,
+ SMB_ENCRYPTION_DESIRED,
+ CRED_SPECIFIED);
+ break;
+ case SMBC_ENCRYPTLEVEL_REQUIRE:
+ default:
+ (void)cli_credentials_set_smb_encryption(
+ creds,
+ SMB_ENCRYPTION_REQUIRED,
+ CRED_SPECIFIED);
+ break;
+ }
+
+
+ return creds;
+}
+
+/*
+ * Connect to a server, possibly on an existing connection
+ *
+ * Here, what we want to do is: If the server and username
+ * match an existing connection, reuse that, otherwise, establish a
+ * new connection.
+ *
+ * If we have to create a new connection, call the auth_fn to get the
+ * info we need, unless the username and password were passed in.
+ */
+
+static SMBCSRV *
+SMBC_server_internal(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ bool connect_if_not_found,
+ const char *server,
+ uint16_t port,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password,
+ bool *in_cache)
+{
+ SMBCSRV *srv=NULL;
+ char *workgroup = NULL;
+ struct cli_state *c = NULL;
+ const char *server_n = server;
+ int is_ipc = (share != NULL && strcmp(share, "IPC$") == 0);
+ uint32_t fs_attrs = 0;
+ const char *username_used = NULL;
+ const char *password_used = NULL;
+ NTSTATUS status;
+ char *newserver, *newshare;
+ int flags = 0;
+ struct smbXcli_tcon *tcon = NULL;
+ int signing_state = SMB_SIGNING_DEFAULT;
+ struct cli_credentials *creds = NULL;
+
+ *in_cache = false;
+
+ if (server[0] == 0) {
+ errno = EPERM;
+ return NULL;
+ }
+
+ /* Look for a cached connection */
+ srv = SMBC_find_server(ctx, context, server, share,
+ pp_workgroup, pp_username, pp_password);
+
+ /*
+ * If we found a connection and we're only allowed one share per
+ * server...
+ */
+ if (srv &&
+ share != NULL && *share != '\0' &&
+ smbc_getOptionOneSharePerServer(context)) {
+
+ /*
+ * ... then if there's no current connection to the share,
+ * connect to it. SMBC_find_server(), or rather the function
+ * pointed to by context->get_cached_srv_fn which
+ * was called by SMBC_find_server(), will have issued a tree
+ * disconnect if the requested share is not the same as the
+ * one that was already connected.
+ */
+
+ /*
+ * Use srv->cli->desthost and srv->cli->share instead of
+ * server and share below to connect to the actual share,
+ * i.e., a normal share or a referred share from
+ * 'msdfs proxy' share.
+ */
+ if (!cli_state_has_tcon(srv->cli)) {
+ /* Ensure we have accurate auth info */
+ SMBC_call_auth_fn(ctx, context,
+ smbXcli_conn_remote_name(srv->cli->conn),
+ srv->cli->share,
+ pp_workgroup,
+ pp_username,
+ pp_password);
+
+ if (!*pp_workgroup || !*pp_username || !*pp_password) {
+ errno = ENOMEM;
+ cli_shutdown(srv->cli);
+ srv->cli = NULL;
+ smbc_getFunctionRemoveCachedServer(context)(context,
+ srv);
+ return NULL;
+ }
+
+ /*
+ * We don't need to renegotiate encryption
+ * here as the encryption context is not per
+ * tid.
+ */
+
+ status = cli_tree_connect(srv->cli,
+ srv->cli->share,
+ "?????",
+ *pp_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(srv->cli);
+ errno = map_errno_from_nt_status(status);
+ srv->cli = NULL;
+ smbc_getFunctionRemoveCachedServer(context)(context,
+ srv);
+ srv = NULL;
+ goto not_found;
+ }
+
+ /* Determine if this share supports case sensitivity */
+ if (is_ipc) {
+ DEBUG(4,
+ ("IPC$ so ignore case sensitivity\n"));
+ status = NT_STATUS_OK;
+ } else {
+ status = cli_get_fs_attr_info(srv->cli, &fs_attrs);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(4, ("Could not retrieve "
+ "case sensitivity flag: %s.\n",
+ nt_errstr(status)));
+
+ /*
+ * We can't determine the case sensitivity of
+ * the share. We have no choice but to use the
+ * user-specified case sensitivity setting.
+ */
+ if (smbc_getOptionCaseSensitive(context)) {
+ cli_set_case_sensitive(srv->cli, true);
+ } else {
+ cli_set_case_sensitive(srv->cli, false);
+ }
+ } else if (!is_ipc) {
+ DEBUG(4,
+ ("Case sensitive: %s\n",
+ (fs_attrs & FILE_CASE_SENSITIVE_SEARCH
+ ? "True"
+ : "False")));
+ cli_set_case_sensitive(
+ srv->cli,
+ (fs_attrs & FILE_CASE_SENSITIVE_SEARCH
+ ? True
+ : False));
+ }
+
+ /*
+ * Regenerate the dev value since it's based on both
+ * server and share
+ */
+ if (srv) {
+ const char *remote_name =
+ smbXcli_conn_remote_name(srv->cli->conn);
+
+ srv->dev = (dev_t)(str_checksum(remote_name) ^
+ str_checksum(srv->cli->share));
+ }
+ }
+ }
+
+ not_found:
+
+ /* If we have a connection... */
+ if (srv) {
+
+ /* ... then we're done here. Give 'em what they came for. */
+ *in_cache = true;
+ goto done;
+ }
+
+ /* If we're not asked to connect when a connection doesn't exist... */
+ if (! connect_if_not_found) {
+ /* ... then we're done here. */
+ return NULL;
+ }
+
+ if (!*pp_workgroup || !*pp_username || !*pp_password) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ DEBUG(4,("SMBC_server: server_n=[%s] server=[%s]\n", server_n, server));
+
+ DEBUG(4,(" -> server_n=[%s] server=[%s]\n", server_n, server));
+
+ status = NT_STATUS_UNSUCCESSFUL;
+
+ if (context->internal->smb_encryption_level > SMBC_ENCRYPTLEVEL_NONE) {
+ signing_state = SMB_SIGNING_REQUIRED;
+ }
+
+ if (port == 0) {
+ if (share == NULL || *share == '\0' || is_ipc) {
+ /*
+ * Try 139 first for IPC$
+ */
+ status = cli_connect_nb(server_n, NULL, NBT_SMB_PORT, 0x20,
+ smbc_getNetbiosName(context),
+ signing_state, flags, &c);
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * No IPC$ or 139 did not work
+ */
+ status = cli_connect_nb(server_n, NULL, port, 0x20,
+ smbc_getNetbiosName(context),
+ signing_state, flags, &c);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ DBG_ERR("NetBIOS support disabled, unable to connect\n");
+ }
+
+ errno = map_errno_from_nt_status(status);
+ return NULL;
+ }
+
+ cli_set_timeout(c, smbc_getTimeout(context));
+
+ status = smbXcli_negprot(c->conn,
+ c->timeout,
+ lp_client_min_protocol(),
+ lp_client_max_protocol(),
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(c);
+ errno = map_errno_from_nt_status(status);
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(c->conn) >= PROTOCOL_SMB2_02) {
+ /* Ensure we ask for some initial credits. */
+ smb2cli_conn_set_max_credits(c->conn, DEFAULT_SMB2_MAX_CREDITS);
+ }
+
+ username_used = *pp_username;
+ password_used = *pp_password;
+
+ creds = SMBC_auth_credentials(c,
+ context,
+ *pp_workgroup,
+ username_used,
+ password_used);
+ if (creds == NULL) {
+ cli_shutdown(c);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ status = cli_session_setup_creds(c, creds);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ /* Failed. Try an anonymous login, if allowed by flags. */
+ username_used = "";
+ password_used = "";
+
+ if (smbc_getOptionNoAutoAnonymousLogin(context) ||
+ !NT_STATUS_IS_OK(cli_session_setup_anon(c))) {
+
+ cli_shutdown(c);
+ errno = map_errno_from_nt_status(status);
+ return NULL;
+ }
+ }
+
+ DEBUG(4,(" session setup ok\n"));
+
+ /* here's the fun part....to support 'msdfs proxy' shares
+ (on Samba or windows) we have to issues a TRANS_GET_DFS_REFERRAL
+ here before trying to connect to the original share.
+ cli_check_msdfs_proxy() will fail if it is a normal share. */
+
+ if (smbXcli_conn_dfs_supported(c->conn) &&
+ cli_check_msdfs_proxy(ctx, c, share,
+ &newserver, &newshare,
+ creds)) {
+ cli_shutdown(c);
+ srv = SMBC_server_internal(ctx, context, connect_if_not_found,
+ newserver, port, newshare, pp_workgroup,
+ pp_username, pp_password, in_cache);
+ TALLOC_FREE(newserver);
+ TALLOC_FREE(newshare);
+ return srv;
+ }
+
+ /* must be a normal share */
+
+ status = cli_tree_connect_creds(c, share, "?????", creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(c);
+ errno = map_errno_from_nt_status(status);
+ return NULL;
+ }
+
+ DEBUG(4,(" tconx ok\n"));
+
+ if (smbXcli_conn_protocol(c->conn) >= PROTOCOL_SMB2_02) {
+ tcon = c->smb2.tcon;
+ } else {
+ tcon = c->smb1.tcon;
+ }
+
+ /* Determine if this share supports case sensitivity */
+ if (is_ipc) {
+ DEBUG(4, ("IPC$ so ignore case sensitivity\n"));
+ status = NT_STATUS_OK;
+ } else {
+ status = cli_get_fs_attr_info(c, &fs_attrs);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(4, ("Could not retrieve case sensitivity flag: %s.\n",
+ nt_errstr(status)));
+
+ /*
+ * We can't determine the case sensitivity of the share. We
+ * have no choice but to use the user-specified case
+ * sensitivity setting.
+ */
+ if (smbc_getOptionCaseSensitive(context)) {
+ cli_set_case_sensitive(c, True);
+ } else {
+ cli_set_case_sensitive(c, False);
+ }
+ } else if (!is_ipc) {
+ DEBUG(4, ("Case sensitive: %s\n",
+ (fs_attrs & FILE_CASE_SENSITIVE_SEARCH
+ ? "True"
+ : "False")));
+ smbXcli_tcon_set_fs_attributes(tcon, fs_attrs);
+ }
+
+ /*
+ * Ok, we have got a nice connection
+ * Let's allocate a server structure.
+ */
+
+ srv = SMB_MALLOC_P(SMBCSRV);
+ if (!srv) {
+ cli_shutdown(c);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ZERO_STRUCTP(srv);
+ DLIST_ADD(srv->cli, c);
+ srv->dev = (dev_t)(str_checksum(server) ^ str_checksum(share));
+ srv->no_pathinfo = False;
+ srv->no_pathinfo2 = False;
+ srv->no_pathinfo3 = False;
+ srv->no_nt_session = False;
+
+done:
+ if (!pp_workgroup || !*pp_workgroup || !**pp_workgroup) {
+ workgroup = talloc_strdup(ctx, smbc_getWorkgroup(context));
+ } else {
+ workgroup = *pp_workgroup;
+ }
+ if(!workgroup) {
+ if (c != NULL) {
+ cli_shutdown(c);
+ }
+ SAFE_FREE(srv);
+ return NULL;
+ }
+
+ /* set the credentials to make DFS work */
+ smbc_set_credentials_with_fallback(context,
+ workgroup,
+ *pp_username,
+ *pp_password);
+
+ return srv;
+}
+
+SMBCSRV *
+SMBC_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ bool connect_if_not_found,
+ const char *server,
+ uint16_t port,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ SMBCSRV *srv=NULL;
+ bool in_cache = false;
+
+ srv = SMBC_server_internal(ctx, context, connect_if_not_found,
+ server, port, share, pp_workgroup,
+ pp_username, pp_password, &in_cache);
+
+ if (!srv) {
+ return NULL;
+ }
+ if (in_cache) {
+ return srv;
+ }
+
+ /* Now add it to the cache (internal or external) */
+ /* Let the cache function set errno if it wants to */
+ errno = 0;
+ if (smbc_getFunctionAddCachedServer(context)(context, srv,
+ server, share,
+ *pp_workgroup,
+ *pp_username)) {
+ int saved_errno = errno;
+ DEBUG(3, (" Failed to add server to cache\n"));
+ errno = saved_errno;
+ if (errno == 0) {
+ errno = ENOMEM;
+ }
+ SAFE_FREE(srv);
+ return NULL;
+ }
+
+ DEBUG(2, ("Server connect ok: //%s/%s: %p\n",
+ server, share, srv));
+
+ DLIST_ADD(context->internal->servers, srv);
+ return srv;
+}
+
+/*
+ * Connect to a server for getting/setting attributes, possibly on an existing
+ * connection. This works similarly to SMBC_server().
+ */
+SMBCSRV *
+SMBC_attr_server(TALLOC_CTX *ctx,
+ SMBCCTX *context,
+ const char *server,
+ uint16_t port,
+ const char *share,
+ char **pp_workgroup,
+ char **pp_username,
+ char **pp_password)
+{
+ int flags;
+ struct cli_state *ipc_cli = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ NTSTATUS nt_status;
+ SMBCSRV *srv=NULL;
+ SMBCSRV *ipc_srv=NULL;
+
+ /*
+ * Use srv->cli->desthost and srv->cli->share instead of
+ * server and share below to connect to the actual share,
+ * i.e., a normal share or a referred share from
+ * 'msdfs proxy' share.
+ */
+ srv = SMBC_server(ctx, context, true, server, port, share,
+ pp_workgroup, pp_username, pp_password);
+ if (!srv) {
+ return NULL;
+ }
+ server = smbXcli_conn_remote_name(srv->cli->conn);
+ share = srv->cli->share;
+
+ /*
+ * See if we've already created this special connection. Reference
+ * our "special" share name '*IPC$', which is an impossible real share
+ * name due to the leading asterisk.
+ */
+ ipc_srv = SMBC_find_server(ctx, context, server, "*IPC$",
+ pp_workgroup, pp_username, pp_password);
+ if (!ipc_srv) {
+ struct cli_credentials *creds = NULL;
+
+ /* We didn't find a cached connection. Get the password */
+ if (!*pp_password || (*pp_password)[0] == '\0') {
+ /* ... then retrieve it now. */
+ SMBC_call_auth_fn(ctx, context, server, share,
+ pp_workgroup,
+ pp_username,
+ pp_password);
+ if (!*pp_workgroup || !*pp_username || !*pp_password) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ }
+
+ flags = 0;
+
+ creds = SMBC_auth_credentials(NULL,
+ context,
+ *pp_workgroup,
+ *pp_username,
+ *pp_password);
+ if (creds == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ nt_status = cli_full_connection_creds(&ipc_cli,
+ lp_netbios_name(), server,
+ NULL, 0, "IPC$", "?????",
+ creds,
+ flags);
+ if (! NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(creds);
+ DEBUG(1,("cli_full_connection failed! (%s)\n",
+ nt_errstr(nt_status)));
+ errno = ENOTSUP;
+ return NULL;
+ }
+ talloc_steal(ipc_cli, creds);
+
+ ipc_srv = SMB_MALLOC_P(SMBCSRV);
+ if (!ipc_srv) {
+ errno = ENOMEM;
+ cli_shutdown(ipc_cli);
+ return NULL;
+ }
+
+ ZERO_STRUCTP(ipc_srv);
+ DLIST_ADD(ipc_srv->cli, ipc_cli);
+
+ nt_status = cli_rpc_pipe_open_noauth(
+ ipc_srv->cli, &ndr_table_lsarpc, &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("cli_nt_session_open fail!\n"));
+ errno = ENOTSUP;
+ cli_shutdown(ipc_srv->cli);
+ free(ipc_srv);
+ return NULL;
+ }
+
+ /*
+ * Some systems don't support
+ * SEC_FLAG_MAXIMUM_ALLOWED, but NT sends 0x2000000
+ * so we might as well do it too.
+ */
+
+ nt_status = rpccli_lsa_open_policy(
+ pipe_hnd,
+ talloc_tos(),
+ True,
+ GENERIC_EXECUTE_ACCESS,
+ &ipc_srv->pol);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ cli_shutdown(ipc_srv->cli);
+ free(ipc_srv);
+ errno = cli_status_to_errno(nt_status);
+ return NULL;
+ }
+
+ /* now add it to the cache (internal or external) */
+
+ errno = 0; /* let cache function set errno if it likes */
+ if (smbc_getFunctionAddCachedServer(context)(context, ipc_srv,
+ server,
+ "*IPC$",
+ *pp_workgroup,
+ *pp_username)) {
+ DEBUG(3, (" Failed to add server to cache\n"));
+ if (errno == 0) {
+ errno = ENOMEM;
+ }
+ cli_shutdown(ipc_srv->cli);
+ free(ipc_srv);
+ return NULL;
+ }
+
+ DLIST_ADD(context->internal->servers, ipc_srv);
+ }
+
+ return ipc_srv;
+}
diff --git a/source3/libsmb/libsmb_setget.c b/source3/libsmb/libsmb_setget.c
new file mode 100644
index 0000000..7b140d6
--- /dev/null
+++ b/source3/libsmb/libsmb_setget.c
@@ -0,0 +1,1155 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#define __LIBSMBCLIENT_INTERNAL__
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "lib/param/param.h"
+
+
+/** Get the netbios name used for making connections */
+const char *
+smbc_getNetbiosName(SMBCCTX *c)
+{
+ return c->netbios_name;
+}
+
+/** Set the netbios name used for making connections */
+void
+smbc_setNetbiosName(SMBCCTX *c, const char *netbios_name)
+{
+ SAFE_FREE(c->netbios_name);
+ if (netbios_name) {
+ c->netbios_name = SMB_STRDUP(netbios_name);
+ }
+}
+
+/** Get the workgroup used for making connections */
+const char *
+smbc_getWorkgroup(SMBCCTX *c)
+{
+ return c->workgroup;
+}
+
+/** Set the workgroup used for making connections */
+void
+smbc_setWorkgroup(SMBCCTX *c, const char *workgroup)
+{
+ SAFE_FREE(c->workgroup);
+ if (workgroup) {
+ c->workgroup = SMB_STRDUP(workgroup);
+ }
+}
+
+/** Get the username used for making connections */
+const char *
+smbc_getUser(SMBCCTX *c)
+{
+ return c->user;
+}
+
+/** Set the username used for making connections */
+void
+smbc_setUser(SMBCCTX *c, const char *user)
+{
+ SAFE_FREE(c->user);
+ if (user) {
+ c->user = SMB_STRDUP(user);
+ }
+}
+
+/** Get the debug level */
+int
+smbc_getDebug(SMBCCTX *c)
+{
+ return c->debug;
+}
+
+/** Set the debug level */
+void
+smbc_setDebug(SMBCCTX *c, int debug)
+{
+ char buf[32];
+ TALLOC_CTX *frame = talloc_stackframe();
+ snprintf(buf, sizeof(buf), "%d", debug);
+ c->debug = debug;
+ lpcfg_set_cmdline(c->internal->lp_ctx, "log level", buf);
+ TALLOC_FREE(frame);
+}
+
+/** set callback function which will be called for logging */
+void
+smbc_setLogCallback(SMBCCTX *c, void *private_ptr,
+ smbc_debug_callback_fn fn)
+{
+ debug_set_callback(private_ptr, fn);
+}
+
+/** set configuration file */
+int smbc_setConfiguration(SMBCCTX *c, const char *file)
+{
+ bool ok;
+
+ ok = lp_load_client_no_reinit(file);
+ if (!ok) {
+ DBG_WARNING("Could not load config file: %s\n", file);
+ errno = ENOENT;
+ return -1;
+ }
+
+ DBG_NOTICE("Configuration loaded successfully: %s\n", file);
+ return 0;
+}
+/**
+ * Get the timeout used for waiting on connections and response data
+ * (in milliseconds)
+ */
+int
+smbc_getTimeout(SMBCCTX *c)
+{
+ return c->timeout;
+}
+
+/**
+ * Set the timeout used for waiting on connections and response data
+ * (in milliseconds)
+ */
+void
+smbc_setTimeout(SMBCCTX *c, int timeout)
+{
+ c->timeout = timeout;
+}
+
+/**
+ * Get the TCP port used to connect.
+ */
+uint16_t
+smbc_getPort(SMBCCTX *c)
+{
+ return c->internal->port;
+}
+
+/**
+ * Set the TCP port used to connect.
+ */
+void
+smbc_setPort(SMBCCTX *c, uint16_t port)
+{
+ c->internal->port = port;
+}
+
+
+/** Get whether to log to standard error instead of standard output */
+smbc_bool
+smbc_getOptionDebugToStderr(SMBCCTX *c)
+{
+ smbc_bool ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Because this is a global concept, it is better to check
+ * what is really set, rather than what we wanted set
+ * (particularly as you cannot go back to stdout). */
+ ret = debug_get_output_is_stderr();
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/** Set whether to log to standard error instead of standard output.
+ * This option is 'sticky' - once set to true, it cannot be set to
+ * false again, as it is global to the process, as once we have been
+ * told that it is not safe to safe to write to stdout, we shouldn't
+ * go back as we don't know it was this context that set it that way.
+ */
+void
+smbc_setOptionDebugToStderr(SMBCCTX *c, smbc_bool b)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ if (b) {
+ /*
+ * We do not have a unique per-thread debug state? For
+ * now, we'll just leave it up to the user. If any one
+ * context specifies debug to stderr then all will be (and
+ * will stay that way, as it is unsafe to flip back if
+ * stdout is in use for other things)
+ */
+ setup_logging("libsmbclient", DEBUG_STDERR);
+ }
+ TALLOC_FREE(frame);
+}
+
+/**
+ * Get whether to use new-style time attribute names, e.g. WRITE_TIME rather
+ * than the old-style names such as M_TIME. This allows also setting/getting
+ * CREATE_TIME which was previously unimplemented. (Note that the old C_TIME
+ * was supposed to be CHANGE_TIME but was confused and sometimes referred to
+ * CREATE_TIME.)
+ */
+smbc_bool
+smbc_getOptionFullTimeNames(SMBCCTX *c)
+{
+ return c->internal->full_time_names;
+}
+
+/**
+ * Set whether to use new-style time attribute names, e.g. WRITE_TIME rather
+ * than the old-style names such as M_TIME. This allows also setting/getting
+ * CREATE_TIME which was previously unimplemented. (Note that the old C_TIME
+ * was supposed to be CHANGE_TIME but was confused and sometimes referred to
+ * CREATE_TIME.)
+ */
+void
+smbc_setOptionFullTimeNames(SMBCCTX *c, smbc_bool b)
+{
+ c->internal->full_time_names = b;
+}
+
+/**
+ * Get the share mode to use for files opened with SMBC_open_ctx(). The
+ * default is SMBC_SHAREMODE_DENY_NONE.
+ */
+smbc_share_mode
+smbc_getOptionOpenShareMode(SMBCCTX *c)
+{
+ return c->internal->share_mode;
+}
+
+/**
+ * Set the share mode to use for files opened with SMBC_open_ctx(). The
+ * default is SMBC_SHAREMODE_DENY_NONE.
+ */
+void
+smbc_setOptionOpenShareMode(SMBCCTX *c, smbc_share_mode share_mode)
+{
+ c->internal->share_mode = share_mode;
+}
+
+/** Retrieve a previously set user data handle */
+void *
+smbc_getOptionUserData(SMBCCTX *c)
+{
+ return c->internal->user_data;
+}
+
+/** Save a user data handle */
+void
+smbc_setOptionUserData(SMBCCTX *c, void *user_data)
+{
+ c->internal->user_data = user_data;
+}
+
+/** Get the encoded value for encryption level. */
+smbc_smb_encrypt_level
+smbc_getOptionSmbEncryptionLevel(SMBCCTX *c)
+{
+ return c->internal->smb_encryption_level;
+}
+
+/** Set the encoded value for encryption level. */
+void
+smbc_setOptionSmbEncryptionLevel(SMBCCTX *c, smbc_smb_encrypt_level level)
+{
+ c->internal->smb_encryption_level = level;
+}
+
+/**
+ * Get whether to treat file names as case-sensitive if we can't determine
+ * when connecting to the remote share whether the file system is case
+ * sensitive. This defaults to FALSE since it's most likely that if we can't
+ * retrieve the file system attributes, it's a very old file system that does
+ * not support case sensitivity.
+ */
+smbc_bool
+smbc_getOptionCaseSensitive(SMBCCTX *c)
+{
+ return c->internal->case_sensitive;
+}
+
+/**
+ * Set whether to treat file names as case-sensitive if we can't determine
+ * when connecting to the remote share whether the file system is case
+ * sensitive. This defaults to FALSE since it's most likely that if we can't
+ * retrieve the file system attributes, it's a very old file system that does
+ * not support case sensitivity.
+ */
+void
+smbc_setOptionCaseSensitive(SMBCCTX *c, smbc_bool b)
+{
+ c->internal->case_sensitive = b;
+}
+
+/**
+ * Get from how many local master browsers should the list of workgroups be
+ * retrieved. It can take up to 12 minutes or longer after a server becomes a
+ * local master browser, for it to have the entire browse list (the list of
+ * workgroups/domains) from an entire network. Since a client never knows
+ * which local master browser will be found first, the one which is found
+ * first and used to retrieve a browse list may have an incomplete or empty
+ * browse list. By requesting the browse list from multiple local master
+ * browsers, a more complete list can be generated. For small networks (few
+ * workgroups), it is recommended that this value be set to 0, causing the
+ * browse lists from all found local master browsers to be retrieved and
+ * merged. For networks with many workgroups, a suitable value for this
+ * variable is probably somewhere around 3. (Default: 3).
+ */
+int
+smbc_getOptionBrowseMaxLmbCount(SMBCCTX *c)
+{
+ return c->options.browse_max_lmb_count;
+}
+
+/**
+ * Set from how many local master browsers should the list of workgroups be
+ * retrieved. It can take up to 12 minutes or longer after a server becomes a
+ * local master browser, for it to have the entire browse list (the list of
+ * workgroups/domains) from an entire network. Since a client never knows
+ * which local master browser will be found first, the one which is found
+ * first and used to retrieve a browse list may have an incomplete or empty
+ * browse list. By requesting the browse list from multiple local master
+ * browsers, a more complete list can be generated. For small networks (few
+ * workgroups), it is recommended that this value be set to 0, causing the
+ * browse lists from all found local master browsers to be retrieved and
+ * merged. For networks with many workgroups, a suitable value for this
+ * variable is probably somewhere around 3. (Default: 3).
+ */
+void
+smbc_setOptionBrowseMaxLmbCount(SMBCCTX *c, int count)
+{
+ c->options.browse_max_lmb_count = count;
+}
+
+/**
+ * Get whether to url-encode readdir entries.
+ *
+ * There is a difference in the desired return strings from
+ * smbc_readdir() depending upon whether the filenames are to
+ * be displayed to the user, or whether they are to be
+ * appended to the path name passed to smbc_opendir() to call
+ * a further smbc_ function (e.g. open the file with
+ * smbc_open()). In the former case, the filename should be
+ * in "human readable" form. In the latter case, the smbc_
+ * functions expect a URL which must be url-encoded. Those
+ * functions decode the URL. If, for example, smbc_readdir()
+ * returned a file name of "abc%20def.txt", passing a path
+ * with this file name attached to smbc_open() would cause
+ * smbc_open to attempt to open the file "abc def.txt" since
+ * the %20 is decoded into a space.
+ *
+ * Set this option to True if the names returned by
+ * smbc_readdir() should be url-encoded such that they can be
+ * passed back to another smbc_ call. Set it to False if the
+ * names returned by smbc_readdir() are to be presented to the
+ * user.
+ *
+ * For backwards compatibility, this option defaults to False.
+ */
+smbc_bool
+smbc_getOptionUrlEncodeReaddirEntries(SMBCCTX *c)
+{
+ return c->options.urlencode_readdir_entries;
+}
+
+/**
+ * Set whether to url-encode readdir entries.
+ *
+ * There is a difference in the desired return strings from
+ * smbc_readdir() depending upon whether the filenames are to
+ * be displayed to the user, or whether they are to be
+ * appended to the path name passed to smbc_opendir() to call
+ * a further smbc_ function (e.g. open the file with
+ * smbc_open()). In the former case, the filename should be
+ * in "human readable" form. In the latter case, the smbc_
+ * functions expect a URL which must be url-encoded. Those
+ * functions decode the URL. If, for example, smbc_readdir()
+ * returned a file name of "abc%20def.txt", passing a path
+ * with this file name attached to smbc_open() would cause
+ * smbc_open to attempt to open the file "abc def.txt" since
+ * the %20 is decoded into a space.
+ *
+ * Set this option to True if the names returned by
+ * smbc_readdir() should be url-encoded such that they can be
+ * passed back to another smbc_ call. Set it to False if the
+ * names returned by smbc_readdir() are to be presented to the
+ * user.
+ *
+ * For backwards compatibility, this option defaults to False.
+ */
+void
+smbc_setOptionUrlEncodeReaddirEntries(SMBCCTX *c, smbc_bool b)
+{
+ c->options.urlencode_readdir_entries = b;
+}
+
+/**
+ * Get whether to use the same connection for all shares on a server.
+ *
+ * Some Windows versions appear to have a limit to the number
+ * of concurrent SESSIONs and/or TREE CONNECTions. In
+ * one-shot programs (i.e. the program runs and then quickly
+ * ends, thereby shutting down all connections), it is
+ * probably reasonable to establish a new connection for each
+ * share. In long-running applications, the limitation can be
+ * avoided by using only a single connection to each server,
+ * and issuing a new TREE CONNECT when the share is accessed.
+ */
+smbc_bool
+smbc_getOptionOneSharePerServer(SMBCCTX *c)
+{
+ return c->options.one_share_per_server;
+}
+
+/**
+ * Set whether to use the same connection for all shares on a server.
+ *
+ * Some Windows versions appear to have a limit to the number
+ * of concurrent SESSIONs and/or TREE CONNECTions. In
+ * one-shot programs (i.e. the program runs and then quickly
+ * ends, thereby shutting down all connections), it is
+ * probably reasonable to establish a new connection for each
+ * share. In long-running applications, the limitation can be
+ * avoided by using only a single connection to each server,
+ * and issuing a new TREE CONNECT when the share is accessed.
+ */
+void
+smbc_setOptionOneSharePerServer(SMBCCTX *c, smbc_bool b)
+{
+ c->options.one_share_per_server = b;
+}
+
+/** Get whether to enable use of kerberos */
+smbc_bool
+smbc_getOptionUseKerberos(SMBCCTX *c)
+{
+ return c->flags & SMB_CTX_FLAG_USE_KERBEROS ? True : False;
+}
+
+/** Set whether to enable use of kerberos */
+void
+smbc_setOptionUseKerberos(SMBCCTX *c, smbc_bool b)
+{
+ if (b) {
+ c->flags |= SMB_CTX_FLAG_USE_KERBEROS;
+ } else {
+ c->flags &= ~SMB_CTX_FLAG_USE_KERBEROS;
+ }
+}
+
+/** Get whether to fallback after kerberos */
+smbc_bool
+smbc_getOptionFallbackAfterKerberos(SMBCCTX *c)
+{
+ return c->flags & SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS ? True : False;
+}
+
+/** Set whether to fallback after kerberos */
+void
+smbc_setOptionFallbackAfterKerberos(SMBCCTX *c, smbc_bool b)
+{
+ if (b) {
+ c->flags |= SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS;
+ } else {
+ c->flags &= ~SMB_CTX_FLAG_FALLBACK_AFTER_KERBEROS;
+ }
+}
+
+/** Get whether to automatically select anonymous login */
+smbc_bool
+smbc_getOptionNoAutoAnonymousLogin(SMBCCTX *c)
+{
+ return c->flags & SMBCCTX_FLAG_NO_AUTO_ANONYMOUS_LOGON ? True : False;
+}
+
+/** Set whether to automatically select anonymous login */
+void
+smbc_setOptionNoAutoAnonymousLogin(SMBCCTX *c, smbc_bool b)
+{
+ if (b) {
+ c->flags |= SMBCCTX_FLAG_NO_AUTO_ANONYMOUS_LOGON;
+ } else {
+ c->flags &= ~SMBCCTX_FLAG_NO_AUTO_ANONYMOUS_LOGON;
+ }
+}
+
+/** Get whether to enable use of the winbind ccache */
+smbc_bool
+smbc_getOptionUseCCache(SMBCCTX *c)
+{
+ return c->flags & SMB_CTX_FLAG_USE_CCACHE ? True : False;
+}
+
+/** Set whether to enable use of the winbind ccache */
+void
+smbc_setOptionUseCCache(SMBCCTX *c, smbc_bool b)
+{
+ if (b) {
+ c->flags |= SMB_CTX_FLAG_USE_CCACHE;
+ } else {
+ c->flags &= ~SMB_CTX_FLAG_USE_CCACHE;
+ }
+}
+
+/** Get indication whether the password supplied is the NT hash */
+smbc_bool
+smbc_getOptionUseNTHash(SMBCCTX *c)
+{
+ return (c->flags & SMB_CTX_FLAG_USE_NT_HASH) != 0;
+}
+
+/** Set indication that the password supplied is the NT hash */
+void
+smbc_setOptionUseNTHash(SMBCCTX *c, smbc_bool b)
+{
+ if (b) {
+ c->flags |= SMB_CTX_FLAG_USE_NT_HASH;
+ } else {
+ c->flags &= ~SMB_CTX_FLAG_USE_NT_HASH;
+ }
+}
+
+smbc_bool
+smbc_setOptionProtocols(SMBCCTX *c,
+ const char *min_proto,
+ const char *max_proto)
+{
+ bool ok = true;
+
+ if (min_proto != NULL) {
+ ok = lpcfg_set_cmdline(c->internal->lp_ctx,
+ "client min protocol",
+ min_proto);
+ }
+
+ if (max_proto != NULL) {
+ ok &= lpcfg_set_cmdline(c->internal->lp_ctx,
+ "client max protocol",
+ max_proto);
+ }
+
+ return ok;
+}
+
+/** Get whether to enable POSIX extensions if available */
+smbc_bool
+smbc_getOptionPosixExtensions(SMBCCTX *c)
+{
+ return c->internal->posix_extensions;
+}
+
+/** Set whether to enable POSIX extensions if available */
+void
+smbc_setOptionPosixExtensions(SMBCCTX *c, smbc_bool b)
+{
+ c->internal->posix_extensions = b;
+}
+
+/** Get the function for obtaining authentication data */
+smbc_get_auth_data_fn
+smbc_getFunctionAuthData(SMBCCTX *c)
+{
+ smbc_get_auth_data_fn ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+ ret = c->callbacks.auth_fn;
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/** Set the function for obtaining authentication data */
+void
+smbc_setFunctionAuthData(SMBCCTX *c, smbc_get_auth_data_fn fn)
+{
+ c->internal->auth_fn_with_context = NULL;
+ c->callbacks.auth_fn = fn;
+}
+
+/** Get the new-style authentication function which includes the context. */
+smbc_get_auth_data_with_context_fn
+smbc_getFunctionAuthDataWithContext(SMBCCTX *c)
+{
+ return c->internal->auth_fn_with_context;
+}
+
+/** Set the new-style authentication function which includes the context. */
+void
+smbc_setFunctionAuthDataWithContext(SMBCCTX *c,
+ smbc_get_auth_data_with_context_fn fn)
+{
+ c->callbacks.auth_fn = NULL;
+ c->internal->auth_fn_with_context = fn;
+}
+
+/** Get the function for checking if a server is still good */
+smbc_check_server_fn
+smbc_getFunctionCheckServer(SMBCCTX *c)
+{
+ return c->callbacks.check_server_fn;
+}
+
+/** Set the function for checking if a server is still good */
+void
+smbc_setFunctionCheckServer(SMBCCTX *c, smbc_check_server_fn fn)
+{
+ c->callbacks.check_server_fn = fn;
+}
+
+/** Get the function for removing a server if unused */
+smbc_remove_unused_server_fn
+smbc_getFunctionRemoveUnusedServer(SMBCCTX *c)
+{
+ return c->callbacks.remove_unused_server_fn;
+}
+
+/** Set the function for removing a server if unused */
+void
+smbc_setFunctionRemoveUnusedServer(SMBCCTX *c,
+ smbc_remove_unused_server_fn fn)
+{
+ c->callbacks.remove_unused_server_fn = fn;
+}
+
+/** Get the function for adding a cached server */
+smbc_add_cached_srv_fn
+smbc_getFunctionAddCachedServer(SMBCCTX *c)
+{
+ return c->callbacks.add_cached_srv_fn;
+}
+
+/** Set the function for adding a cached server */
+void
+smbc_setFunctionAddCachedServer(SMBCCTX *c, smbc_add_cached_srv_fn fn)
+{
+ c->callbacks.add_cached_srv_fn = fn;
+}
+
+/** Get the function for server cache lookup */
+smbc_get_cached_srv_fn
+smbc_getFunctionGetCachedServer(SMBCCTX *c)
+{
+ return c->callbacks.get_cached_srv_fn;
+}
+
+/** Set the function for server cache lookup */
+void
+smbc_setFunctionGetCachedServer(SMBCCTX *c, smbc_get_cached_srv_fn fn)
+{
+ c->callbacks.get_cached_srv_fn = fn;
+}
+
+/** Get the function for server cache removal */
+smbc_remove_cached_srv_fn
+smbc_getFunctionRemoveCachedServer(SMBCCTX *c)
+{
+ return c->callbacks.remove_cached_srv_fn;
+}
+
+/** Set the function for server cache removal */
+void
+smbc_setFunctionRemoveCachedServer(SMBCCTX *c,
+ smbc_remove_cached_srv_fn fn)
+{
+ c->callbacks.remove_cached_srv_fn = fn;
+}
+
+/**
+ * Get the function for server cache purging. This function tries to
+ * remove all cached servers (e.g. on disconnect)
+ */
+smbc_purge_cached_fn
+smbc_getFunctionPurgeCachedServers(SMBCCTX *c)
+{
+ return c->callbacks.purge_cached_fn;
+}
+
+/** Set the function to store private data of the server cache */
+void smbc_setServerCacheData(SMBCCTX *c, struct smbc_server_cache * cache)
+{
+ c->internal->server_cache = cache;
+}
+
+/** Get the function to store private data of the server cache */
+struct smbc_server_cache * smbc_getServerCacheData(SMBCCTX *c)
+{
+ return c->internal->server_cache;
+}
+
+
+/**
+ * Set the function for server cache purging. This function tries to
+ * remove all cached servers (e.g. on disconnect)
+ */
+void
+smbc_setFunctionPurgeCachedServers(SMBCCTX *c, smbc_purge_cached_fn fn)
+{
+ c->callbacks.purge_cached_fn = fn;
+}
+
+/**
+ * Callable functions for files.
+ */
+
+smbc_open_fn
+smbc_getFunctionOpen(SMBCCTX *c)
+{
+ return c->open;
+}
+
+void
+smbc_setFunctionOpen(SMBCCTX *c, smbc_open_fn fn)
+{
+ c->open = fn;
+}
+
+smbc_creat_fn
+smbc_getFunctionCreat(SMBCCTX *c)
+{
+ return c->creat;
+}
+
+void
+smbc_setFunctionCreat(SMBCCTX *c, smbc_creat_fn fn)
+{
+ c->creat = fn;
+}
+
+smbc_read_fn
+smbc_getFunctionRead(SMBCCTX *c)
+{
+ return c->read;
+}
+
+void
+smbc_setFunctionRead(SMBCCTX *c, smbc_read_fn fn)
+{
+ c->read = fn;
+}
+
+smbc_write_fn
+smbc_getFunctionWrite(SMBCCTX *c)
+{
+ return c->write;
+}
+
+void
+smbc_setFunctionWrite(SMBCCTX *c, smbc_write_fn fn)
+{
+ c->write = fn;
+}
+
+smbc_splice_fn
+smbc_getFunctionSplice(SMBCCTX *c)
+{
+ return c->internal->smb.splice_fn;
+}
+
+void
+smbc_setFunctionSplice(SMBCCTX *c, smbc_splice_fn fn)
+{
+ c->internal->smb.splice_fn = fn;
+}
+
+smbc_unlink_fn
+smbc_getFunctionUnlink(SMBCCTX *c)
+{
+ return c->unlink;
+}
+
+void
+smbc_setFunctionUnlink(SMBCCTX *c, smbc_unlink_fn fn)
+{
+ c->unlink = fn;
+}
+
+smbc_rename_fn
+smbc_getFunctionRename(SMBCCTX *c)
+{
+ return c->rename;
+}
+
+void
+smbc_setFunctionRename(SMBCCTX *c, smbc_rename_fn fn)
+{
+ c->rename = fn;
+}
+
+smbc_lseek_fn
+smbc_getFunctionLseek(SMBCCTX *c)
+{
+ return c->lseek;
+}
+
+void
+smbc_setFunctionLseek(SMBCCTX *c, smbc_lseek_fn fn)
+{
+ c->lseek = fn;
+}
+
+smbc_stat_fn
+smbc_getFunctionStat(SMBCCTX *c)
+{
+ return c->stat;
+}
+
+void
+smbc_setFunctionStat(SMBCCTX *c, smbc_stat_fn fn)
+{
+ c->stat = fn;
+}
+
+smbc_fstat_fn
+smbc_getFunctionFstat(SMBCCTX *c)
+{
+ return c->fstat;
+}
+
+void
+smbc_setFunctionFstat(SMBCCTX *c, smbc_fstat_fn fn)
+{
+ c->fstat = fn;
+}
+
+smbc_statvfs_fn
+smbc_getFunctionStatVFS(SMBCCTX *c)
+{
+ return c->internal->posix_emu.statvfs_fn;
+}
+
+void
+smbc_setFunctionStatVFS(SMBCCTX *c, smbc_statvfs_fn fn)
+{
+ c->internal->posix_emu.statvfs_fn = fn;
+}
+
+smbc_fstatvfs_fn
+smbc_getFunctionFstatVFS(SMBCCTX *c)
+{
+ return c->internal->posix_emu.fstatvfs_fn;
+}
+
+void
+smbc_setFunctionFstatVFS(SMBCCTX *c, smbc_fstatvfs_fn fn)
+{
+ c->internal->posix_emu.fstatvfs_fn = fn;
+}
+
+smbc_ftruncate_fn
+smbc_getFunctionFtruncate(SMBCCTX *c)
+{
+ return c->internal->posix_emu.ftruncate_fn;
+}
+
+void
+smbc_setFunctionFtruncate(SMBCCTX *c, smbc_ftruncate_fn fn)
+{
+ c->internal->posix_emu.ftruncate_fn = fn;
+}
+
+smbc_close_fn
+smbc_getFunctionClose(SMBCCTX *c)
+{
+ return c->close_fn;
+}
+
+void
+smbc_setFunctionClose(SMBCCTX *c, smbc_close_fn fn)
+{
+ c->close_fn = fn;
+}
+
+
+/**
+ * Callable functions for directories.
+ */
+
+smbc_opendir_fn
+smbc_getFunctionOpendir(SMBCCTX *c)
+{
+ return c->opendir;
+}
+
+void
+smbc_setFunctionOpendir(SMBCCTX *c, smbc_opendir_fn fn)
+{
+ c->opendir = fn;
+}
+
+smbc_closedir_fn
+smbc_getFunctionClosedir(SMBCCTX *c)
+{
+ return c->closedir;
+}
+
+void
+smbc_setFunctionClosedir(SMBCCTX *c, smbc_closedir_fn fn)
+{
+ c->closedir = fn;
+}
+
+smbc_readdir_fn
+smbc_getFunctionReaddir(SMBCCTX *c)
+{
+ return c->readdir;
+}
+
+void
+smbc_setFunctionReaddir(SMBCCTX *c, smbc_readdir_fn fn)
+{
+ c->readdir = fn;
+}
+
+smbc_readdirplus_fn smbc_getFunctionReaddirPlus(SMBCCTX *c)
+{
+ return c->readdirplus;
+}
+
+void smbc_setFunctionReaddirPlus(SMBCCTX *c, smbc_readdirplus_fn fn)
+{
+ c->readdirplus = fn;
+}
+
+smbc_readdirplus2_fn smbc_getFunctionReaddirPlus2(SMBCCTX *c)
+{
+ return c->readdirplus2;
+}
+
+void smbc_setFunctionReaddirPlus2(SMBCCTX *c, smbc_readdirplus2_fn fn)
+{
+ c->readdirplus2 = fn;
+}
+
+smbc_getdents_fn
+smbc_getFunctionGetdents(SMBCCTX *c)
+{
+ return c->getdents;
+}
+
+void
+smbc_setFunctionGetdents(SMBCCTX *c, smbc_getdents_fn fn)
+{
+ c->getdents = fn;
+}
+
+smbc_mkdir_fn
+smbc_getFunctionMkdir(SMBCCTX *c)
+{
+ return c->mkdir;
+}
+
+void
+smbc_setFunctionMkdir(SMBCCTX *c, smbc_mkdir_fn fn)
+{
+ c->mkdir = fn;
+}
+
+smbc_rmdir_fn
+smbc_getFunctionRmdir(SMBCCTX *c)
+{
+ return c->rmdir;
+}
+
+void
+smbc_setFunctionRmdir(SMBCCTX *c, smbc_rmdir_fn fn)
+{
+ c->rmdir = fn;
+}
+
+smbc_telldir_fn
+smbc_getFunctionTelldir(SMBCCTX *c)
+{
+ return c->telldir;
+}
+
+void
+smbc_setFunctionTelldir(SMBCCTX *c, smbc_telldir_fn fn)
+{
+ c->telldir = fn;
+}
+
+smbc_lseekdir_fn
+smbc_getFunctionLseekdir(SMBCCTX *c)
+{
+ return c->lseekdir;
+}
+
+void
+smbc_setFunctionLseekdir(SMBCCTX *c, smbc_lseekdir_fn fn)
+{
+ c->lseekdir = fn;
+}
+
+smbc_fstatdir_fn
+smbc_getFunctionFstatdir(SMBCCTX *c)
+{
+ return c->fstatdir;
+}
+
+void
+smbc_setFunctionFstatdir(SMBCCTX *c, smbc_fstatdir_fn fn)
+{
+ c->fstatdir = fn;
+}
+
+smbc_notify_fn
+smbc_getFunctionNotify(SMBCCTX *c)
+{
+ return c->internal->smb.notify_fn;
+}
+
+void
+smbc_setFunctionNotify(SMBCCTX *c, smbc_notify_fn fn)
+{
+ c->internal->smb.notify_fn = fn;
+}
+
+
+/**
+ * Callable functions applicable to both files and directories.
+ */
+
+smbc_chmod_fn
+smbc_getFunctionChmod(SMBCCTX *c)
+{
+ return c->chmod;
+}
+
+void
+smbc_setFunctionChmod(SMBCCTX *c, smbc_chmod_fn fn)
+{
+ c->chmod = fn;
+}
+
+smbc_utimes_fn
+smbc_getFunctionUtimes(SMBCCTX *c)
+{
+ return c->utimes;
+}
+
+void
+smbc_setFunctionUtimes(SMBCCTX *c, smbc_utimes_fn fn)
+{
+ c->utimes = fn;
+}
+
+smbc_setxattr_fn
+smbc_getFunctionSetxattr(SMBCCTX *c)
+{
+ return c->setxattr;
+}
+
+void
+smbc_setFunctionSetxattr(SMBCCTX *c, smbc_setxattr_fn fn)
+{
+ c->setxattr = fn;
+}
+
+smbc_getxattr_fn
+smbc_getFunctionGetxattr(SMBCCTX *c)
+{
+ return c->getxattr;
+}
+
+void
+smbc_setFunctionGetxattr(SMBCCTX *c, smbc_getxattr_fn fn)
+{
+ c->getxattr = fn;
+}
+
+smbc_removexattr_fn
+smbc_getFunctionRemovexattr(SMBCCTX *c)
+{
+ return c->removexattr;
+}
+
+void
+smbc_setFunctionRemovexattr(SMBCCTX *c, smbc_removexattr_fn fn)
+{
+ c->removexattr = fn;
+}
+
+smbc_listxattr_fn
+smbc_getFunctionListxattr(SMBCCTX *c)
+{
+ return c->listxattr;
+}
+
+void
+smbc_setFunctionListxattr(SMBCCTX *c, smbc_listxattr_fn fn)
+{
+ c->listxattr = fn;
+}
+
+
+/**
+ * Callable functions related to printing
+ */
+
+smbc_print_file_fn
+smbc_getFunctionPrintFile(SMBCCTX *c)
+{
+ return c->print_file;
+}
+
+void
+smbc_setFunctionPrintFile(SMBCCTX *c, smbc_print_file_fn fn)
+{
+ c->print_file = fn;
+}
+
+smbc_open_print_job_fn
+smbc_getFunctionOpenPrintJob(SMBCCTX *c)
+{
+ return c->open_print_job;
+}
+
+void
+smbc_setFunctionOpenPrintJob(SMBCCTX *c,
+ smbc_open_print_job_fn fn)
+{
+ c->open_print_job = fn;
+}
+
+smbc_list_print_jobs_fn
+smbc_getFunctionListPrintJobs(SMBCCTX *c)
+{
+ return c->list_print_jobs;
+}
+
+void
+smbc_setFunctionListPrintJobs(SMBCCTX *c,
+ smbc_list_print_jobs_fn fn)
+{
+ c->list_print_jobs = fn;
+}
+
+smbc_unlink_print_job_fn
+smbc_getFunctionUnlinkPrintJob(SMBCCTX *c)
+{
+ return c->unlink_print_job;
+}
+
+void
+smbc_setFunctionUnlinkPrintJob(SMBCCTX *c,
+ smbc_unlink_print_job_fn fn)
+{
+ c->unlink_print_job = fn;
+}
+
diff --git a/source3/libsmb/libsmb_stat.c b/source3/libsmb/libsmb_stat.c
new file mode 100644
index 0000000..ae18f59
--- /dev/null
+++ b/source3/libsmb/libsmb_stat.c
@@ -0,0 +1,538 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/util/time.h"
+
+/*
+ * Generate an inode number from file name for those things that need it
+ */
+
+static ino_t generate_inode(const char *name)
+{
+ if (name == NULL) {
+ return (ino_t)-1;
+ }
+ return (ino_t)str_checksum(name);
+}
+
+/*
+ * Routine to put basic stat info into a stat structure ... Used by stat and
+ * fstat below.
+ */
+
+void setup_stat(struct stat *st,
+ const char *fname,
+ off_t size,
+ int attr,
+ ino_t ino,
+ dev_t dev,
+ struct timespec access_time_ts,
+ struct timespec change_time_ts,
+ struct timespec write_time_ts)
+{
+ st->st_mode = 0;
+
+ if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+ st->st_mode = (S_IFDIR | 0555);
+ } else {
+ st->st_mode = (S_IFREG | 0444);
+ }
+
+ if (attr & FILE_ATTRIBUTE_ARCHIVE) {
+ st->st_mode |= S_IXUSR;
+ }
+ if (attr & FILE_ATTRIBUTE_SYSTEM) {
+ st->st_mode |= S_IXGRP;
+ }
+ if (attr & FILE_ATTRIBUTE_HIDDEN) {
+ st->st_mode |= S_IXOTH;
+ }
+ if (!(attr & FILE_ATTRIBUTE_READONLY)) {
+ st->st_mode |= S_IWUSR;
+ }
+
+ st->st_size = size;
+#ifdef HAVE_STAT_ST_BLKSIZE
+ st->st_blksize = 512;
+#endif
+#ifdef HAVE_STAT_ST_BLOCKS
+ st->st_blocks = (size+511)/512;
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st->st_rdev = 0;
+#endif
+ st->st_uid = getuid();
+ st->st_gid = getgid();
+
+ if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+ st->st_nlink = 2;
+ } else {
+ st->st_nlink = 1;
+ }
+
+ if (ino != 0) {
+ st->st_ino = ino;
+ } else {
+ st->st_ino = generate_inode(fname);
+ }
+
+ st->st_dev = dev;
+
+ st->st_atime = access_time_ts.tv_sec;
+ set_atimensec(st, access_time_ts.tv_nsec);
+
+ st->st_ctime = change_time_ts.tv_sec;
+ set_ctimensec(st, change_time_ts.tv_nsec);
+
+ st->st_mtime = write_time_ts.tv_sec;
+ set_mtimensec(st, write_time_ts.tv_nsec);
+}
+
+void setup_stat_from_stat_ex(const struct stat_ex *stex,
+ const char *fname,
+ struct stat *st)
+{
+ st->st_atime = stex->st_ex_atime.tv_sec;
+ set_atimensec(st, stex->st_ex_atime.tv_nsec);
+
+ st->st_ctime = stex->st_ex_ctime.tv_sec;
+ set_ctimensec(st, stex->st_ex_ctime.tv_nsec);
+
+ st->st_mtime = stex->st_ex_mtime.tv_sec;
+ set_mtimensec(st, stex->st_ex_mtime.tv_nsec);
+
+ st->st_mode = stex->st_ex_mode;
+ st->st_size = stex->st_ex_size;
+#ifdef HAVE_STAT_ST_BLKSIZE
+ st->st_blksize = 512;
+#endif
+#ifdef HAVE_STAT_ST_BLOCKS
+ st->st_blocks = (st->st_size + 511) / 512;
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_RDEV
+ st->st_rdev = 0;
+#endif
+ st->st_uid = stex->st_ex_uid;
+ st->st_gid = stex->st_ex_gid;
+
+ st->st_nlink = stex->st_ex_nlink;
+
+ if (stex->st_ex_ino == 0) {
+ st->st_ino = 0;
+ if (fname != NULL) {
+ st->st_ino = generate_inode(fname);
+ }
+ } else {
+ st->st_ino = stex->st_ex_ino;
+ }
+
+ st->st_dev = stex->st_ex_dev;
+
+}
+
+/*
+ * Routine to stat a file given a name
+ */
+
+int
+SMBC_stat_ctx(SMBCCTX *context,
+ const char *fname,
+ struct stat *st)
+{
+ SMBCSRV *srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ uint16_t port = 0;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_stat(%s)\n", fname));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ status = SMBC_getatr(context, srv, path, st);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+/*
+ * Routine to stat a file given an fd
+ */
+
+int
+SMBC_fstat_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ struct stat *st)
+{
+ struct timespec change_time_ts;
+ struct timespec access_time_ts;
+ struct timespec write_time_ts;
+ off_t size;
+ uint32_t attr;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *path = NULL;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ SMB_INO_T ino = 0;
+ uint16_t port = 0;
+ struct cli_credentials *creds = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!SMBC_dlist_contains(context->internal->files, file)) {
+ errno = EBADF;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!file->file) {
+ TALLOC_FREE(frame);
+ return smbc_getFunctionFstatdir(context)(context, file, st);
+ }
+
+ /*d_printf(">>>fstat: parsing %s\n", file->fname);*/
+ if (SMBC_parse_path(frame,
+ context,
+ file->fname,
+ NULL,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ creds = context->internal->creds;
+
+ /*d_printf(">>>fstat: resolving %s\n", path);*/
+ status = cli_resolve_path(frame, "",
+ creds,
+ file->srv->cli, path,
+ &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not resolve %s\n", path);
+ errno = ENOENT;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /*d_printf(">>>fstat: resolved path as %s\n", targetpath);*/
+
+ if (!NT_STATUS_IS_OK(cli_qfileinfo_basic(
+ targetcli, file->cli_fd, &attr, &size,
+ NULL,
+ &access_time_ts,
+ &write_time_ts,
+ &change_time_ts,
+ &ino))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ setup_stat(st,
+ path,
+ size,
+ attr,
+ ino,
+ file->srv->dev,
+ access_time_ts,
+ change_time_ts,
+ write_time_ts);
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+
+/*
+ * Routine to obtain file system information given a path
+ */
+int
+SMBC_statvfs_ctx(SMBCCTX *context,
+ char *path,
+ struct statvfs *st)
+{
+ int ret;
+ bool bIsDir;
+ struct stat statbuf;
+ SMBCFILE * pFile;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Determine if the provided path is a file or a folder */
+ if (SMBC_stat_ctx(context, path, &statbuf) < 0) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ /* Is it a file or a directory? */
+ if (S_ISDIR(statbuf.st_mode)) {
+ /* It's a directory. */
+ if ((pFile = SMBC_opendir_ctx(context, path)) == NULL) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ bIsDir = true;
+ } else if (S_ISREG(statbuf.st_mode)) {
+ /* It's a file. */
+ if ((pFile = SMBC_open_ctx(context, path,
+ O_RDONLY, 0)) == NULL) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ bIsDir = false;
+ } else {
+ /* It's neither a file nor a directory. Not supported. */
+ TALLOC_FREE(frame);
+ errno = ENOSYS;
+ return -1;
+ }
+
+ /* Now we have an open file handle, so just use SMBC_fstatvfs */
+ ret = SMBC_fstatvfs_ctx(context, pFile, st);
+
+ /* Close the file or directory */
+ if (bIsDir) {
+ SMBC_closedir_ctx(context, pFile);
+ } else {
+ SMBC_close_ctx(context, pFile);
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * Routine to obtain file system information given an fd
+ */
+
+int
+SMBC_fstatvfs_ctx(SMBCCTX *context,
+ SMBCFILE *file,
+ struct statvfs *st)
+{
+ unsigned long flags = 0;
+ uint32_t fs_attrs = 0;
+ struct cli_state *cli = file->srv->cli;
+ struct smbXcli_tcon *tcon;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ tcon = cli->smb2.tcon;
+ } else {
+ tcon = cli->smb1.tcon;
+ }
+
+ /* Initialize all fields (at least until we actually use them) */
+ ZERO_STRUCTP(st);
+
+ /*
+ * The state of each flag is such that the same bits are unset as
+ * would typically be unset on a local file system on a POSIX OS. Thus
+ * the bit is on, for example, only for case-insensitive file systems
+ * since most POSIX file systems are case sensitive and fstatvfs()
+ * would typically return zero in these bits on such a local file
+ * system.
+ */
+
+ /* See if the server has UNIX CIFS support */
+ if (! SERVER_HAS_UNIX_CIFS(cli)) {
+ uint64_t total_allocation_units;
+ uint64_t caller_allocation_units;
+ uint64_t actual_allocation_units;
+ uint64_t sectors_per_allocation_unit;
+ uint64_t bytes_per_sector;
+ NTSTATUS status;
+
+ /* Nope. If size data is available... */
+ status = cli_get_fs_full_size_info(cli,
+ &total_allocation_units,
+ &caller_allocation_units,
+ &actual_allocation_units,
+ &sectors_per_allocation_unit,
+ &bytes_per_sector);
+ if (NT_STATUS_IS_OK(status)) {
+
+ /* ... then provide it */
+ st->f_bsize =
+ (unsigned long) bytes_per_sector;
+#ifdef HAVE_FRSIZE
+ st->f_frsize =
+ (unsigned long) sectors_per_allocation_unit;
+#endif
+ st->f_blocks =
+ (fsblkcnt_t) total_allocation_units;
+ st->f_bfree =
+ (fsblkcnt_t) actual_allocation_units;
+ st->f_bavail =
+ (fsblkcnt_t) caller_allocation_units;
+ }
+
+ flags |= SMBC_VFS_FEATURE_NO_UNIXCIFS;
+ } else {
+ uint32_t optimal_transfer_size;
+ uint32_t block_size;
+ uint64_t total_blocks;
+ uint64_t blocks_available;
+ uint64_t user_blocks_available;
+ uint64_t total_file_nodes;
+ uint64_t free_file_nodes;
+ uint64_t fs_identifier;
+ NTSTATUS status;
+
+ /* Has UNIXCIFS. If POSIX filesystem info is available... */
+ status = cli_get_posix_fs_info(cli,
+ &optimal_transfer_size,
+ &block_size,
+ &total_blocks,
+ &blocks_available,
+ &user_blocks_available,
+ &total_file_nodes,
+ &free_file_nodes,
+ &fs_identifier);
+ if (NT_STATUS_IS_OK(status)) {
+
+ /* ... then what's provided here takes precedence. */
+ st->f_bsize =
+ (unsigned long) block_size;
+ st->f_blocks =
+ (fsblkcnt_t) total_blocks;
+ st->f_bfree =
+ (fsblkcnt_t) blocks_available;
+ st->f_bavail =
+ (fsblkcnt_t) user_blocks_available;
+ st->f_files =
+ (fsfilcnt_t) total_file_nodes;
+ st->f_ffree =
+ (fsfilcnt_t) free_file_nodes;
+#ifdef HAVE_FSID_INT
+ st->f_fsid =
+ (unsigned long) fs_identifier;
+#endif
+ }
+ }
+
+ /* See if the share is case sensitive */
+ if (!NT_STATUS_IS_OK(cli_get_fs_attr_info(cli, &fs_attrs))) {
+ /*
+ * We can't determine the case sensitivity of
+ * the share. We have no choice but to use the
+ * user-specified case sensitivity setting.
+ */
+ if (! smbc_getOptionCaseSensitive(context)) {
+ flags |= SMBC_VFS_FEATURE_CASE_INSENSITIVE;
+ }
+ } else {
+ if (! (fs_attrs & FILE_CASE_SENSITIVE_SEARCH)) {
+ flags |= SMBC_VFS_FEATURE_CASE_INSENSITIVE;
+ }
+ }
+
+ /* See if DFS is supported */
+ if (smbXcli_conn_dfs_supported(cli->conn) &&
+ smbXcli_tcon_is_dfs_share(tcon))
+ {
+ flags |= SMBC_VFS_FEATURE_DFS;
+ }
+
+#if defined(HAVE_STATVFS_F_FLAG)
+ st->f_flag = flags;
+#elif defined(HAVE_STATVFS_F_FLAGS)
+ st->f_flags = flags;
+#endif
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/libsmb/libsmb_thread_impl.c b/source3/libsmb/libsmb_thread_impl.c
new file mode 100644
index 0000000..8b5d859
--- /dev/null
+++ b/source3/libsmb/libsmb_thread_impl.c
@@ -0,0 +1,127 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Derrell Lipman 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+
+
+/**
+ * Initialize for an arbitrary thread implementation. The caller should
+ * provide, as parameters, pointers to functions to implement the requisite
+ * low-level thread functionality. A function must be provided for each
+ * parameter; none may be null.
+ *
+ * If the thread implementation is POSIX Threads (pthreads), then the much
+ * simpler smbc_thread_pthread() function may be used instead of this one.
+ *
+ * @param create_mutex
+ * Create a mutex. This function should expect three parameters: lockname,
+ * pplock, and location. It should create a unique mutex for each unique
+ * lockname it is provided, and return the mutex identifier in *pplock. The
+ * location parameter can be used for debugging, as it contains the
+ * compiler-provided __location__ of the call.
+ *
+ * @param destroy_mutex
+ * Destroy a mutex. This function should expect two parameters: plock and
+ * location. It should destroy the mutex associated with the identifier
+ * plock. The location parameter can be used for debugging, as it contains
+ * the compiler-provided __location__ of the call.
+ *
+ * @param lock_mutex
+ * Lock a mutex. This function should expect three parameters: plock,
+ * lock_type, and location. The mutex associated with identifier plock
+ * should be locked if lock_type is 1, and unlocked if lock_type is 2. The
+ * location parameter can be used for debugging, as it contains the
+ * compiler-provided __location__ of the call.
+ *
+ * @param create_tls
+ * Create thread local storage. This function should expect three
+ * parameters: keyname, ppkey, and location. It should allocate an
+ * implementation-specific amount of memory and assign the pointer to that
+ * allocated memory to *ppkey. The location parameter can be used for
+ * debugging, as it contains the compiler-provided __location__ of the
+ * call. This function should return 0 upon success, non-zero upon failure.
+ *
+ * @param destroy_tls
+ * Destroy thread local storage. This function should expect two parameters:
+ * ppkey and location. The ppkey parameter points to a variable containing a
+ * thread local storage key previously provided by the create_tls
+ * function. The location parameter can be used for debugging, as it
+ * contains the compiler-provided __location__ of the call.
+ *
+ * @param set_tls
+ * Set a thread local storage variable's value. This function should expect
+ * three parameters: pkey, pval, and location. The pkey parameter is a
+ * thread local storage key previously provided by the create_tls
+ * function. The (void *) pval parameter contains the value to be placed in
+ * the thread local storage variable identified by pkey. The location
+ * parameter can be used for debugging, as it contains the compiler-provided
+ * __location__ of the call. This function should return 0 upon success;
+ * non-zero otherwise.
+ *
+ * @param get_tls
+ * Retrieve a thread local storage variable's value. This function should
+ * expect two parameters: pkey and location. The pkey parameter is a thread
+ * local storage key previously provided by the create_tls function, and
+ * which has previously been used in a call to the set_tls function to
+ * initialize a thread local storage variable. The location parameter can be
+ * used for debugging, as it contains the compiler-provided __location__ of
+ * the call. This function should return the (void *) value stored in the
+ * variable identified by pkey.
+ *
+ * @return {void}
+ */
+void
+smbc_thread_impl(
+ /* Mutex functions. */
+ int (*create_mutex)(const char *lockname,
+ void **pplock,
+ const char *location),
+ void (*destroy_mutex)(void *plock,
+ const char *location),
+ int (*lock_mutex)(void *plock,
+ int lock_type,
+ const char *location),
+
+ /* Thread local storage. */
+ int (*create_tls)(const char *keyname,
+ void **ppkey,
+ const char *location),
+ void (*destroy_tls)(void **ppkey,
+ const char *location),
+ int (*set_tls)(void *pkey,
+ const void *pval,
+ const char *location),
+ void *(*get_tls)(void *pkey,
+ const char *location)
+ )
+{
+ static struct smb_thread_functions tf;
+
+ tf.create_mutex = create_mutex;
+ tf.destroy_mutex = destroy_mutex;
+ tf.lock_mutex = lock_mutex;
+ tf.create_tls = create_tls;
+ tf.destroy_tls = destroy_tls;
+ tf.set_tls = set_tls;
+ tf.get_tls = get_tls;
+
+ smb_thread_set_functions(&tf);
+}
diff --git a/source3/libsmb/libsmb_thread_posix.c b/source3/libsmb/libsmb_thread_posix.c
new file mode 100644
index 0000000..36afc02
--- /dev/null
+++ b/source3/libsmb/libsmb_thread_posix.c
@@ -0,0 +1,52 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Derrell Lipman 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#ifdef HAVE_PTHREAD_H
+#include <pthread.h>
+#endif
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+
+
+/* Get rid of the malloc checker */
+#ifdef malloc
+#undef malloc
+#endif
+
+/*
+ * Define the functions which implement the pthread interface
+ */
+SMB_THREADS_DEF_PTHREAD_IMPLEMENTATION(tf);
+
+
+/**
+ * Initialize for threads using the Posix Threads (pthread)
+ * implementation. This is a built-in implementation, avoiding the need to
+ * implement the component functions of the thread interface. If this function
+ * is used, it is not necessary to call smbc_thread_impl().
+ *
+ * @return {void}
+ */
+void
+smbc_thread_posix(void)
+{
+ smb_thread_set_functions(&tf);
+}
diff --git a/source3/libsmb/libsmb_xattr.c b/source3/libsmb/libsmb_xattr.c
new file mode 100644
index 0000000..dcb2f9e
--- /dev/null
+++ b/source3/libsmb/libsmb_xattr.c
@@ -0,0 +1,2406 @@
+/*
+ Unix SMB/Netbios implementation.
+ SMB client library implementation
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Richard Sharpe 2000, 2002
+ Copyright (C) John Terpstra 2000
+ Copyright (C) Tom Jansen (Ninja ISD) 2002
+ Copyright (C) Derrell Lipman 2003-2008
+ Copyright (C) Jeremy Allison 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "libsmbclient.h"
+#include "libsmb_internal.h"
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "rpc_client/rpc_client.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../libcli/security/security.h"
+#include "lib/util/string_wrappers.h"
+
+/*
+ * Find an lsa pipe handle associated with a cli struct.
+ */
+static struct rpc_pipe_client *
+find_lsa_pipe_hnd(struct cli_state *ipc_cli)
+{
+ struct rpc_pipe_client *pipe_hnd;
+
+ for (pipe_hnd = ipc_cli->pipe_list;
+ pipe_hnd;
+ pipe_hnd = pipe_hnd->next) {
+ if (ndr_syntax_id_equal(&pipe_hnd->abstract_syntax,
+ &ndr_table_lsarpc.syntax_id)) {
+ return pipe_hnd;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Sort ACEs according to the documentation at
+ * http://support.microsoft.com/kb/269175, at least as far as it defines the
+ * order.
+ */
+
+static int
+ace_compare(struct security_ace *ace1,
+ struct security_ace *ace2)
+{
+ bool b1;
+ bool b2;
+
+ /* If the ACEs are equal, we have nothing more to do. */
+ if (security_ace_equal(ace1, ace2)) {
+ return 0;
+ }
+
+ /* Inherited follow non-inherited */
+ b1 = ((ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) != 0);
+ b2 = ((ace2->flags & SEC_ACE_FLAG_INHERITED_ACE) != 0);
+ if (b1 != b2) {
+ return (b1 ? 1 : -1);
+ }
+
+ /*
+ * What shall we do with AUDITs and ALARMs? It's undefined. We'll
+ * sort them after DENY and ALLOW.
+ */
+ b1 = (ace1->type != SEC_ACE_TYPE_ACCESS_ALLOWED &&
+ ace1->type != SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT &&
+ ace1->type != SEC_ACE_TYPE_ACCESS_DENIED &&
+ ace1->type != SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
+ b2 = (ace2->type != SEC_ACE_TYPE_ACCESS_ALLOWED &&
+ ace2->type != SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT &&
+ ace2->type != SEC_ACE_TYPE_ACCESS_DENIED &&
+ ace2->type != SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
+ if (b1 != b2) {
+ return (b1 ? 1 : -1);
+ }
+
+ /* Allowed ACEs follow denied ACEs */
+ b1 = (ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED ||
+ ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT);
+ b2 = (ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED ||
+ ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT);
+ if (b1 != b2) {
+ return (b1 ? 1 : -1);
+ }
+
+ /*
+ * ACEs applying to an entity's object follow those applying to the
+ * entity itself
+ */
+ b1 = (ace1->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT ||
+ ace1->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
+ b2 = (ace2->type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT ||
+ ace2->type == SEC_ACE_TYPE_ACCESS_DENIED_OBJECT);
+ if (b1 != b2) {
+ return (b1 ? 1 : -1);
+ }
+
+ /*
+ * If we get this far, the ACEs are similar as far as the
+ * characteristics we typically care about (those defined by the
+ * referenced MS document). We'll now sort by characteristics that
+ * just seems reasonable.
+ */
+
+ if (ace1->type != ace2->type) {
+ return ace2->type - ace1->type;
+ }
+
+ if (dom_sid_compare(&ace1->trustee, &ace2->trustee)) {
+ return dom_sid_compare(&ace1->trustee, &ace2->trustee);
+ }
+
+ if (ace1->flags != ace2->flags) {
+ return ace1->flags - ace2->flags;
+ }
+
+ if (ace1->access_mask != ace2->access_mask) {
+ return ace1->access_mask - ace2->access_mask;
+ }
+
+ if (ace1->size != ace2->size) {
+ return ace1->size - ace2->size;
+ }
+
+ return memcmp(ace1, ace2, sizeof(struct security_ace));
+}
+
+
+static void
+sort_acl(struct security_acl *the_acl)
+{
+ uint32_t i;
+ if (!the_acl) return;
+
+ TYPESAFE_QSORT(the_acl->aces, the_acl->num_aces, ace_compare);
+
+ for (i=1;i<the_acl->num_aces;) {
+ if (security_ace_equal(&the_acl->aces[i-1],
+ &the_acl->aces[i])) {
+ ARRAY_DEL_ELEMENT(
+ the_acl->aces, i, the_acl->num_aces);
+ the_acl->num_aces--;
+ } else {
+ i++;
+ }
+ }
+}
+
+/* convert a SID to a string, either numeric or username/group */
+static void
+convert_sid_to_string(struct cli_state *ipc_cli,
+ struct policy_handle *pol,
+ fstring str,
+ bool numeric,
+ struct dom_sid *sid)
+{
+ char **domains = NULL;
+ char **names = NULL;
+ enum lsa_SidType *types = NULL;
+ struct rpc_pipe_client *pipe_hnd = find_lsa_pipe_hnd(ipc_cli);
+ TALLOC_CTX *ctx;
+
+ sid_to_fstring(str, sid);
+
+ if (numeric) {
+ return; /* no lookup desired */
+ }
+
+ if (!pipe_hnd) {
+ return;
+ }
+
+ /* Ask LSA to convert the sid to a name */
+
+ ctx = talloc_stackframe();
+
+ if (!NT_STATUS_IS_OK(rpccli_lsa_lookup_sids(pipe_hnd, ctx,
+ pol, 1, sid, &domains,
+ &names, &types)) ||
+ !domains || !domains[0] || !names || !names[0]) {
+ TALLOC_FREE(ctx);
+ return;
+ }
+
+ /* Converted OK */
+
+ fstr_sprintf(str, "%s%s%s",
+ domains[0], lp_winbind_separator(), names[0]);
+
+ TALLOC_FREE(ctx);
+}
+
+/* convert a string to a SID, either numeric or username/group */
+static bool
+convert_string_to_sid(struct cli_state *ipc_cli,
+ struct policy_handle *pol,
+ bool numeric,
+ struct dom_sid *sid,
+ const char *str)
+{
+ enum lsa_SidType *types = NULL;
+ struct dom_sid *sids = NULL;
+ bool result = True;
+ TALLOC_CTX *ctx = NULL;
+ struct rpc_pipe_client *pipe_hnd = find_lsa_pipe_hnd(ipc_cli);
+
+ if (!pipe_hnd) {
+ return False;
+ }
+
+ if (numeric) {
+ if (strncmp(str, "S-", 2) == 0) {
+ return string_to_sid(sid, str);
+ }
+
+ result = False;
+ goto done;
+ }
+
+ ctx = talloc_stackframe();
+ if (!NT_STATUS_IS_OK(rpccli_lsa_lookup_names(pipe_hnd, ctx,
+ pol, 1, &str,
+ NULL, 1, &sids,
+ &types))) {
+ result = False;
+ goto done;
+ }
+
+ sid_copy(sid, &sids[0]);
+done:
+ TALLOC_FREE(ctx);
+ return result;
+}
+
+
+/* parse an struct security_ace in the same format as print_ace() */
+static bool
+parse_ace(struct cli_state *ipc_cli,
+ struct policy_handle *pol,
+ struct security_ace *ace,
+ bool numeric,
+ char *str)
+{
+ char *p;
+ const char *cp;
+ char *tok;
+ unsigned int atype;
+ unsigned int aflags;
+ unsigned int amask;
+ struct dom_sid sid;
+ uint32_t mask;
+ struct perm_value {
+ const char perm[7];
+ uint32_t mask;
+ };
+ size_t i;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* These values discovered by inspection */
+ static const struct perm_value special_values[] = {
+ { "R", 0x00120089 },
+ { "W", 0x00120116 },
+ { "X", 0x001200a0 },
+ { "D", 0x00010000 },
+ { "P", 0x00040000 },
+ { "O", 0x00080000 },
+ };
+
+ static const struct perm_value standard_values[] = {
+ { "READ", 0x001200a9 },
+ { "CHANGE", 0x001301bf },
+ { "FULL", 0x001f01ff },
+ };
+
+ ZERO_STRUCTP(ace);
+ p = strchr_m(str,':');
+ if (!p) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ *p = '\0';
+ p++;
+ /* Try to parse numeric form */
+
+ if (sscanf(p, "%u/%u/%u", &atype, &aflags, &amask) == 3 &&
+ convert_string_to_sid(ipc_cli, pol, numeric, &sid, str)) {
+ goto done;
+ }
+
+ /* Try to parse text form */
+
+ if (!convert_string_to_sid(ipc_cli, pol, numeric, &sid, str)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cp = p;
+ if (!next_token_talloc(frame, &cp, &tok, "/")) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (strncasecmp_m(tok, "ALLOWED", strlen("ALLOWED")) == 0) {
+ atype = SEC_ACE_TYPE_ACCESS_ALLOWED;
+ } else if (strncasecmp_m(tok, "DENIED", strlen("DENIED")) == 0) {
+ atype = SEC_ACE_TYPE_ACCESS_DENIED;
+ } else {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Only numeric form accepted for flags at present */
+
+ if (!(next_token_talloc(frame, &cp, &tok, "/") &&
+ sscanf(tok, "%u", &aflags))) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (!next_token_talloc(frame, &cp, &tok, "/")) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (strncmp(tok, "0x", 2) == 0) {
+ if (sscanf(tok, "%u", &amask) != 1) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ goto done;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(standard_values); i++) {
+ const struct perm_value *v = &standard_values[i];
+ if (strcmp(tok, v->perm) == 0) {
+ amask = v->mask;
+ goto done;
+ }
+ }
+
+ p = tok;
+
+ while(*p) {
+ bool found = False;
+
+ for (i = 0; i < ARRAY_SIZE(special_values); i++) {
+ const struct perm_value *v = &special_values[i];
+ if (v->perm[0] == *p) {
+ amask |= v->mask;
+ found = True;
+ }
+ }
+
+ if (!found) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ p++;
+ }
+
+ if (*p) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+done:
+ mask = amask;
+ init_sec_ace(ace, &sid, atype, mask, aflags);
+ TALLOC_FREE(frame);
+ return true;
+}
+
+/* add an struct security_ace to a list of struct security_aces in a struct security_acl */
+static bool
+add_ace(struct security_acl **the_acl,
+ const struct security_ace *ace,
+ TALLOC_CTX *ctx)
+{
+ struct security_acl *acl = *the_acl;
+
+ if (acl == NULL) {
+ acl = make_sec_acl(ctx, 3, 0, NULL);
+ if (acl == NULL) {
+ return false;
+ }
+ }
+
+ if (acl->num_aces == UINT32_MAX) {
+ return false;
+ }
+ ADD_TO_ARRAY(
+ acl, struct security_ace, *ace, &acl->aces, &acl->num_aces);
+ *the_acl = acl;
+ return True;
+}
+
+
+/* parse a ascii version of a security descriptor */
+static struct security_descriptor *
+sec_desc_parse(TALLOC_CTX *ctx,
+ struct cli_state *ipc_cli,
+ struct policy_handle *pol,
+ bool numeric,
+ const char *str)
+{
+ const char *p = str;
+ char *tok;
+ struct security_descriptor *ret = NULL;
+ size_t sd_size;
+ struct dom_sid owner_sid = { .num_auths = 0 };
+ struct dom_sid group_sid = { .num_auths = 0 };
+ bool have_owner = false, have_group = false;
+ struct security_acl *dacl=NULL;
+ int revision=1;
+
+ while (next_token_talloc(ctx, &p, &tok, "\t,\r\n")) {
+
+ if (strncasecmp_m(tok,"REVISION:", 9) == 0) {
+ revision = strtol(tok+9, NULL, 16);
+ continue;
+ }
+
+ if (strncasecmp_m(tok,"OWNER:", 6) == 0) {
+ if (have_owner) {
+ DEBUG(5,("OWNER specified more than once!\n"));
+ goto done;
+ }
+ if (!convert_string_to_sid(ipc_cli, pol,
+ numeric,
+ &owner_sid, tok+6)) {
+ DEBUG(5, ("Failed to parse owner sid\n"));
+ goto done;
+ }
+ have_owner = true;
+ continue;
+ }
+
+ if (strncasecmp_m(tok,"OWNER+:", 7) == 0) {
+ if (have_owner) {
+ DEBUG(5,("OWNER specified more than once!\n"));
+ goto done;
+ }
+ if (!convert_string_to_sid(ipc_cli, pol,
+ False,
+ &owner_sid, tok+7)) {
+ DEBUG(5, ("Failed to parse owner sid\n"));
+ goto done;
+ }
+ have_owner = true;
+ continue;
+ }
+
+ if (strncasecmp_m(tok,"GROUP:", 6) == 0) {
+ if (have_group) {
+ DEBUG(5,("GROUP specified more than once!\n"));
+ goto done;
+ }
+ if (!convert_string_to_sid(ipc_cli, pol,
+ numeric,
+ &group_sid, tok+6)) {
+ DEBUG(5, ("Failed to parse group sid\n"));
+ goto done;
+ }
+ have_group = true;
+ continue;
+ }
+
+ if (strncasecmp_m(tok,"GROUP+:", 7) == 0) {
+ if (have_group) {
+ DEBUG(5,("GROUP specified more than once!\n"));
+ goto done;
+ }
+ if (!convert_string_to_sid(ipc_cli, pol,
+ False,
+ &group_sid, tok+6)) {
+ DEBUG(5, ("Failed to parse group sid\n"));
+ goto done;
+ }
+ have_group = true;
+ continue;
+ }
+
+ if (strncasecmp_m(tok,"ACL:", 4) == 0) {
+ struct security_ace ace;
+ if (!parse_ace(ipc_cli, pol, &ace, numeric, tok+4)) {
+ DEBUG(5, ("Failed to parse ACL %s\n", tok));
+ goto done;
+ }
+ if(!add_ace(&dacl, &ace, ctx)) {
+ DEBUG(5, ("Failed to add ACL %s\n", tok));
+ goto done;
+ }
+ continue;
+ }
+
+ if (strncasecmp_m(tok,"ACL+:", 5) == 0) {
+ struct security_ace ace;
+ if (!parse_ace(ipc_cli, pol, &ace, False, tok+5)) {
+ DEBUG(5, ("Failed to parse ACL %s\n", tok));
+ goto done;
+ }
+ if(!add_ace(&dacl, &ace, ctx)) {
+ DEBUG(5, ("Failed to add ACL %s\n", tok));
+ goto done;
+ }
+ continue;
+ }
+
+ DEBUG(5, ("Failed to parse security descriptor\n"));
+ goto done;
+ }
+
+ ret = make_sec_desc(
+ ctx,
+ revision,
+ SEC_DESC_SELF_RELATIVE,
+ have_owner ? &owner_sid : NULL,
+ have_group ? &group_sid : NULL,
+ NULL,
+ dacl,
+ &sd_size);
+
+done:
+ return ret;
+}
+
+
+/* Obtain the current dos attributes */
+static struct DOS_ATTR_DESC *
+dos_attr_query(SMBCCTX *context,
+ TALLOC_CTX *ctx,
+ const char *filename,
+ SMBCSRV *srv)
+{
+ struct stat sb = {0};
+ struct DOS_ATTR_DESC *ret = NULL;
+ NTSTATUS status;
+
+ ret = talloc(ctx, struct DOS_ATTR_DESC);
+ if (!ret) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ /* Obtain the DOS attributes */
+ status = SMBC_getatr(context, srv, filename, &sb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("dos_attr_query Failed to query old attributes\n"));
+ TALLOC_FREE(ret);
+ errno = cli_status_to_errno(status);
+ return NULL;
+ }
+
+ ret->mode = sb.st_mode;
+ ret->size = sb.st_size;
+ ret->create_time = sb.st_ctime;
+ ret->access_time = sb.st_atime;
+ ret->write_time = sb.st_mtime;
+ ret->change_time = sb.st_mtime;
+ ret->inode = sb.st_ino;
+
+ return ret;
+}
+
+
+/* parse a ascii version of a security descriptor */
+static void
+dos_attr_parse(SMBCCTX *context,
+ struct DOS_ATTR_DESC *dad,
+ SMBCSRV *srv,
+ char *str)
+{
+ int n;
+ const char *p = str;
+ char *tok = NULL;
+ TALLOC_CTX *frame = NULL;
+ struct {
+ const char * create_time_attr;
+ const char * access_time_attr;
+ const char * write_time_attr;
+ const char * change_time_attr;
+ } attr_strings;
+
+ /* Determine whether to use old-style or new-style attribute names */
+ if (context->internal->full_time_names) {
+ /* new-style names */
+ attr_strings.create_time_attr = "CREATE_TIME";
+ attr_strings.access_time_attr = "ACCESS_TIME";
+ attr_strings.write_time_attr = "WRITE_TIME";
+ attr_strings.change_time_attr = "CHANGE_TIME";
+ } else {
+ /* old-style names */
+ attr_strings.create_time_attr = NULL;
+ attr_strings.access_time_attr = "A_TIME";
+ attr_strings.write_time_attr = "M_TIME";
+ attr_strings.change_time_attr = "C_TIME";
+ }
+
+ /* if this is to set the entire ACL... */
+ if (*str == '*') {
+ /* ... then increment past the first colon if there is one */
+ if ((p = strchr(str, ':')) != NULL) {
+ ++p;
+ } else {
+ p = str;
+ }
+ }
+
+ frame = talloc_stackframe();
+ while (next_token_talloc(frame, &p, &tok, "\t,\r\n")) {
+ if (strncasecmp_m(tok, "MODE:", 5) == 0) {
+ long request = strtol(tok+5, NULL, 16);
+ if (request == 0) {
+ dad->mode =
+ (dad->mode & FILE_ATTRIBUTE_DIRECTORY)
+ ? FILE_ATTRIBUTE_DIRECTORY
+ : FILE_ATTRIBUTE_NORMAL;
+ } else {
+ dad->mode = request;
+ }
+ continue;
+ }
+
+ if (strncasecmp_m(tok, "SIZE:", 5) == 0) {
+ dad->size = (off_t)atof(tok+5);
+ continue;
+ }
+
+ n = strlen(attr_strings.access_time_attr);
+ if (strncasecmp_m(tok, attr_strings.access_time_attr, n) == 0) {
+ dad->access_time = (time_t)strtol(tok+n+1, NULL, 10);
+ continue;
+ }
+
+ n = strlen(attr_strings.change_time_attr);
+ if (strncasecmp_m(tok, attr_strings.change_time_attr, n) == 0) {
+ dad->change_time = (time_t)strtol(tok+n+1, NULL, 10);
+ continue;
+ }
+
+ n = strlen(attr_strings.write_time_attr);
+ if (strncasecmp_m(tok, attr_strings.write_time_attr, n) == 0) {
+ dad->write_time = (time_t)strtol(tok+n+1, NULL, 10);
+ continue;
+ }
+
+ if (attr_strings.create_time_attr != NULL) {
+ n = strlen(attr_strings.create_time_attr);
+ if (strncasecmp_m(tok, attr_strings.create_time_attr,
+ n) == 0) {
+ dad->create_time = (time_t)strtol(tok+n+1,
+ NULL, 10);
+ continue;
+ }
+ }
+
+ if (strncasecmp_m(tok, "INODE:", 6) == 0) {
+ dad->inode = (SMB_INO_T)atof(tok+6);
+ continue;
+ }
+ }
+ TALLOC_FREE(frame);
+}
+
+/*****************************************************
+ Retrieve the acls for a file.
+*******************************************************/
+
+static int
+cacl_get(SMBCCTX *context,
+ TALLOC_CTX *ctx,
+ SMBCSRV *srv,
+ struct cli_state *ipc_cli,
+ struct policy_handle *pol,
+ const char *filename,
+ const char *attr_name,
+ char *buf,
+ int bufsize)
+{
+ uint32_t i;
+ int n = 0;
+ int n_used;
+ bool all;
+ bool all_nt;
+ bool all_nt_acls;
+ bool all_dos;
+ bool some_nt;
+ bool some_dos;
+ bool exclude_nt_revision = False;
+ bool exclude_nt_owner = False;
+ bool exclude_nt_group = False;
+ bool exclude_nt_acl = False;
+ bool exclude_dos_mode = False;
+ bool exclude_dos_size = False;
+ bool exclude_dos_create_time = False;
+ bool exclude_dos_access_time = False;
+ bool exclude_dos_write_time = False;
+ bool exclude_dos_change_time = False;
+ bool exclude_dos_inode = False;
+ bool numeric = True;
+ bool determine_size = (bufsize == 0);
+ uint16_t fnum;
+ struct security_descriptor *sd;
+ fstring sidstr;
+ fstring name_sandbox;
+ char *name;
+ char *pExclude;
+ char *p;
+ struct cli_state *cli = srv->cli;
+ struct {
+ const char * create_time_attr;
+ const char * access_time_attr;
+ const char * write_time_attr;
+ const char * change_time_attr;
+ } attr_strings;
+ struct {
+ const char * create_time_attr;
+ const char * access_time_attr;
+ const char * write_time_attr;
+ const char * change_time_attr;
+ } excl_attr_strings;
+
+ /* Determine whether to use old-style or new-style attribute names */
+ if (context->internal->full_time_names) {
+ /* new-style names */
+ attr_strings.create_time_attr = "CREATE_TIME";
+ attr_strings.access_time_attr = "ACCESS_TIME";
+ attr_strings.write_time_attr = "WRITE_TIME";
+ attr_strings.change_time_attr = "CHANGE_TIME";
+
+ excl_attr_strings.create_time_attr = "CREATE_TIME";
+ excl_attr_strings.access_time_attr = "ACCESS_TIME";
+ excl_attr_strings.write_time_attr = "WRITE_TIME";
+ excl_attr_strings.change_time_attr = "CHANGE_TIME";
+ } else {
+ /* old-style names */
+ attr_strings.create_time_attr = NULL;
+ attr_strings.access_time_attr = "A_TIME";
+ attr_strings.write_time_attr = "M_TIME";
+ attr_strings.change_time_attr = "C_TIME";
+
+ excl_attr_strings.create_time_attr = NULL;
+ excl_attr_strings.access_time_attr = "dos_attr.A_TIME";
+ excl_attr_strings.write_time_attr = "dos_attr.M_TIME";
+ excl_attr_strings.change_time_attr = "dos_attr.C_TIME";
+ }
+
+ /* Copy name so we can strip off exclusions (if any are specified) */
+ fstrcpy(name_sandbox, attr_name);
+
+ /* Ensure name is null terminated */
+ name_sandbox[sizeof(name_sandbox) - 1] = '\0';
+
+ /* Play in the sandbox */
+ name = name_sandbox;
+
+ /* If there are any exclusions, point to them and mask them from name */
+ if ((pExclude = strchr(name, '!')) != NULL)
+ {
+ *pExclude++ = '\0';
+ }
+
+ all = (strncasecmp_m(name, "system.*", 8) == 0);
+ all_nt = (strncasecmp_m(name, "system.nt_sec_desc.*", 20) == 0);
+ all_nt_acls = (strncasecmp_m(name, "system.nt_sec_desc.acl.*", 24) == 0);
+ all_dos = (strncasecmp_m(name, "system.dos_attr.*", 17) == 0);
+ some_nt = (strncasecmp_m(name, "system.nt_sec_desc.", 19) == 0);
+ some_dos = (strncasecmp_m(name, "system.dos_attr.", 16) == 0);
+ numeric = (* (name + strlen(name) - 1) != '+');
+
+ /* Look for exclusions from "all" requests */
+ if (all || all_nt || all_dos) {
+ /* Exclusions are delimited by '!' */
+ for (;
+ pExclude != NULL;
+ pExclude = (p == NULL ? NULL : p + 1)) {
+
+ /* Find end of this exclusion name */
+ if ((p = strchr(pExclude, '!')) != NULL)
+ {
+ *p = '\0';
+ }
+
+ /* Which exclusion name is this? */
+ if (strcasecmp_m(pExclude,
+ "nt_sec_desc.revision") == 0) {
+ exclude_nt_revision = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ "nt_sec_desc.owner") == 0) {
+ exclude_nt_owner = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ "nt_sec_desc.group") == 0) {
+ exclude_nt_group = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ "nt_sec_desc.acl") == 0) {
+ exclude_nt_acl = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ "dos_attr.mode") == 0) {
+ exclude_dos_mode = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ "dos_attr.size") == 0) {
+ exclude_dos_size = True;
+ }
+ else if (excl_attr_strings.create_time_attr != NULL &&
+ strcasecmp_m(pExclude,
+ excl_attr_strings.change_time_attr) == 0) {
+ exclude_dos_create_time = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ excl_attr_strings.access_time_attr) == 0) {
+ exclude_dos_access_time = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ excl_attr_strings.write_time_attr) == 0) {
+ exclude_dos_write_time = True;
+ }
+ else if (strcasecmp_m(pExclude,
+ excl_attr_strings.change_time_attr) == 0) {
+ exclude_dos_change_time = True;
+ }
+ else if (strcasecmp_m(pExclude, "dos_attr.inode") == 0) {
+ exclude_dos_inode = True;
+ }
+ else {
+ DEBUG(5, ("cacl_get received unknown exclusion: %s\n",
+ pExclude));
+ errno = ENOATTR;
+ return -1;
+ }
+ }
+ }
+
+ n_used = 0;
+
+ /*
+ * If we are (possibly) talking to an NT or new system and some NT
+ * attributes have been requested...
+ */
+ if (ipc_cli && (all || some_nt || all_nt_acls)) {
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = NULL;
+ NTSTATUS status;
+
+ /* Point to the portion after "system.nt_sec_desc." */
+ name += 19; /* if (all) this will be invalid but unused */
+
+ creds = context->internal->creds;
+
+ status = cli_resolve_path(
+ ctx, "",
+ creds,
+ cli, filename, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("cacl_get Could not resolve %s\n",
+ filename));
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* ... then obtain any NT attributes which were requested */
+ status = cli_ntcreate(
+ targetcli, /* cli */
+ targetpath, /* fname */
+ 0, /* CreatFlags */
+ READ_CONTROL_ACCESS, /* DesiredAccess */
+ 0, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0x0, /* CreateOptions */
+ 0x0, /* SecurityFlags */
+ &fnum, /* pfid */
+ NULL); /* cr */
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("cacl_get failed to open %s: %s\n",
+ targetpath, nt_errstr(status)));
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ status = cli_query_secdesc(targetcli, fnum, ctx, &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("cacl_get Failed to query old descriptor "
+ "of %s: %s\n",
+ targetpath, nt_errstr(status)));
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ cli_close(targetcli, fnum);
+
+ if (! exclude_nt_revision) {
+ if (all || all_nt) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx,
+ "REVISION:%d",
+ sd->revision);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "REVISION:%d",
+ sd->revision);
+ }
+ } else if (strcasecmp_m(name, "revision") == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "%d",
+ sd->revision);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize, "%d",
+ sd->revision);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_nt_owner) {
+ /* Get owner and group sid */
+ if (sd->owner_sid) {
+ convert_sid_to_string(ipc_cli, pol,
+ sidstr,
+ numeric,
+ sd->owner_sid);
+ } else {
+ fstrcpy(sidstr, "");
+ }
+
+ if (all || all_nt) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, ",OWNER:%s",
+ sidstr);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else if (sidstr[0] != '\0') {
+ n = snprintf(buf, bufsize,
+ ",OWNER:%s", sidstr);
+ }
+ } else if (strncasecmp_m(name, "owner", 5) == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "%s", sidstr);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize, "%s",
+ sidstr);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_nt_group) {
+ if (sd->group_sid) {
+ convert_sid_to_string(ipc_cli, pol,
+ sidstr, numeric,
+ sd->group_sid);
+ } else {
+ fstrcpy(sidstr, "");
+ }
+
+ if (all || all_nt) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, ",GROUP:%s",
+ sidstr);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else if (sidstr[0] != '\0') {
+ n = snprintf(buf, bufsize,
+ ",GROUP:%s", sidstr);
+ }
+ } else if (strncasecmp_m(name, "group", 5) == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "%s", sidstr);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%s", sidstr);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_nt_acl) {
+ /* Add aces to value buffer */
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+
+ struct security_ace *ace = &sd->dacl->aces[i];
+ convert_sid_to_string(ipc_cli, pol,
+ sidstr, numeric,
+ &ace->trustee);
+
+ if (all || all_nt) {
+ if (determine_size) {
+ p = talloc_asprintf(
+ ctx,
+ ",ACL:"
+ "%s:%d/%d/0x%08x",
+ sidstr,
+ ace->type,
+ ace->flags,
+ ace->access_mask);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(
+ buf, bufsize,
+ ",ACL:%s:%d/%d/0x%08x",
+ sidstr,
+ ace->type,
+ ace->flags,
+ ace->access_mask);
+ }
+ } else if ((strncasecmp_m(name, "acl", 3) == 0 &&
+ strcasecmp_m(name+3, sidstr) == 0) ||
+ (strncasecmp_m(name, "acl+", 4) == 0 &&
+ strcasecmp_m(name+4, sidstr) == 0)) {
+ if (determine_size) {
+ p = talloc_asprintf(
+ ctx,
+ "%d/%d/0x%08x",
+ ace->type,
+ ace->flags,
+ ace->access_mask);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%d/%d/0x%08x",
+ ace->type,
+ ace->flags,
+ ace->access_mask);
+ }
+ } else if (all_nt_acls) {
+ if (determine_size) {
+ p = talloc_asprintf(
+ ctx,
+ "%s%s:%d/%d/0x%08x",
+ i ? "," : "",
+ sidstr,
+ ace->type,
+ ace->flags,
+ ace->access_mask);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%s%s:%d/%d/0x%08x",
+ i ? "," : "",
+ sidstr,
+ ace->type,
+ ace->flags,
+ ace->access_mask);
+ }
+ }
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+ }
+
+ /* Restore name pointer to its original value */
+ name -= 19;
+ }
+
+ if (all || some_dos) {
+ struct stat sb = {0};
+ time_t create_time = (time_t)0;
+ time_t write_time = (time_t)0;
+ time_t access_time = (time_t)0;
+ time_t change_time = (time_t)0;
+ off_t size = 0;
+ uint16_t mode = 0;
+ SMB_INO_T ino = 0;
+ NTSTATUS status;
+
+ /* Point to the portion after "system.dos_attr." */
+ name += 16; /* if (all) this will be invalid but unused */
+
+ /* Obtain the DOS attributes */
+ status = SMBC_getatr(context, srv, filename, &sb);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = cli_status_to_errno(status);
+ return -1;
+ }
+
+ create_time = sb.st_ctime;
+ access_time = sb.st_atime;
+ write_time = sb.st_mtime;
+ change_time = sb.st_mtime;
+ size = sb.st_size;
+ mode = sb.st_mode;
+ ino = sb.st_ino;
+
+ if (! exclude_dos_mode) {
+ if (all || all_dos) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx,
+ "%sMODE:0x%x",
+ (ipc_cli &&
+ (all || some_nt)
+ ? ","
+ : ""),
+ mode);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%sMODE:0x%x",
+ (ipc_cli &&
+ (all || some_nt)
+ ? ","
+ : ""),
+ mode);
+ }
+ } else if (strcasecmp_m(name, "mode") == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "0x%x", mode);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "0x%x", mode);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_dos_size) {
+ if (all || all_dos) {
+ if (determine_size) {
+ p = talloc_asprintf(
+ ctx,
+ ",SIZE:%.0f",
+ (double)size);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ ",SIZE:%.0f",
+ (double)size);
+ }
+ } else if (strcasecmp_m(name, "size") == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(
+ ctx,
+ "%.0f",
+ (double)size);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%.0f",
+ (double)size);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_dos_create_time &&
+ attr_strings.create_time_attr != NULL) {
+ if (all || all_dos) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx,
+ ",%s:%lu",
+ attr_strings.create_time_attr,
+ (unsigned long) create_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ ",%s:%lu",
+ attr_strings.create_time_attr,
+ (unsigned long) create_time);
+ }
+ } else if (strcasecmp_m(name, attr_strings.create_time_attr) == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "%lu", (unsigned long) create_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%lu", (unsigned long) create_time);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_dos_access_time) {
+ if (all || all_dos) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx,
+ ",%s:%lu",
+ attr_strings.access_time_attr,
+ (unsigned long) access_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ ",%s:%lu",
+ attr_strings.access_time_attr,
+ (unsigned long) access_time);
+ }
+ } else if (strcasecmp_m(name, attr_strings.access_time_attr) == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "%lu", (unsigned long) access_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%lu", (unsigned long) access_time);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_dos_write_time) {
+ if (all || all_dos) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx,
+ ",%s:%lu",
+ attr_strings.write_time_attr,
+ (unsigned long) write_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ ",%s:%lu",
+ attr_strings.write_time_attr,
+ (unsigned long) write_time);
+ }
+ } else if (strcasecmp_m(name, attr_strings.write_time_attr) == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "%lu", (unsigned long) write_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%lu", (unsigned long) write_time);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_dos_change_time) {
+ if (all || all_dos) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx,
+ ",%s:%lu",
+ attr_strings.change_time_attr,
+ (unsigned long) change_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ ",%s:%lu",
+ attr_strings.change_time_attr,
+ (unsigned long) change_time);
+ }
+ } else if (strcasecmp_m(name, attr_strings.change_time_attr) == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(ctx, "%lu", (unsigned long) change_time);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%lu", (unsigned long) change_time);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ if (! exclude_dos_inode) {
+ if (all || all_dos) {
+ if (determine_size) {
+ p = talloc_asprintf(
+ ctx,
+ ",INODE:%.0f",
+ (double)ino);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ ",INODE:%.0f",
+ (double) ino);
+ }
+ } else if (strcasecmp_m(name, "inode") == 0) {
+ if (determine_size) {
+ p = talloc_asprintf(
+ ctx,
+ "%.0f",
+ (double) ino);
+ if (!p) {
+ errno = ENOMEM;
+ return -1;
+ }
+ n = strlen(p);
+ } else {
+ n = snprintf(buf, bufsize,
+ "%.0f",
+ (double) ino);
+ }
+ }
+
+ if (!determine_size && n > bufsize) {
+ errno = ERANGE;
+ return -1;
+ }
+ buf += n;
+ n_used += n;
+ bufsize -= n;
+ n = 0;
+ }
+
+ /* Restore name pointer to its original value */
+ name -= 16;
+ }
+
+ if (n_used == 0) {
+ errno = ENOATTR;
+ return -1;
+ }
+
+ return n_used;
+}
+
+/*****************************************************
+set the ACLs on a file given an ascii description
+*******************************************************/
+static int
+cacl_set(SMBCCTX *context,
+ TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ struct cli_state *ipc_cli,
+ struct policy_handle *pol,
+ const char *filename,
+ char *the_acl,
+ int mode,
+ int flags)
+{
+ uint16_t fnum = (uint16_t)-1;
+ int err = 0;
+ struct security_descriptor *sd = NULL, *old;
+ struct security_acl *dacl = NULL;
+ struct dom_sid *owner_sid = NULL;
+ struct dom_sid *group_sid = NULL;
+ uint32_t i, j;
+ size_t sd_size;
+ int ret = 0;
+ char *p;
+ bool numeric = True;
+ char *targetpath = NULL;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = NULL;
+ NTSTATUS status;
+
+ /* the_acl will be null for REMOVE_ALL operations */
+ if (the_acl) {
+ numeric = ((p = strchr(the_acl, ':')) != NULL &&
+ p > the_acl &&
+ p[-1] != '+');
+
+ /* if this is to set the entire ACL... */
+ if (*the_acl == '*') {
+ /* ... then increment past the first colon */
+ the_acl = p + 1;
+ }
+
+ sd = sec_desc_parse(ctx, ipc_cli, pol, numeric, the_acl);
+ if (!sd) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ /* SMBC_XATTR_MODE_REMOVE_ALL is the only caller
+ that doesn't deref sd */
+
+ if (!sd && (mode != SMBC_XATTR_MODE_REMOVE_ALL)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ creds = context->internal->creds;
+
+ status = cli_resolve_path(ctx, "",
+ creds,
+ cli, filename, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("cacl_set: Could not resolve %s\n", filename));
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* The desired access below is the only one I could find that works
+ with NT4, W2KP and Samba */
+
+ status = cli_ntcreate(
+ targetcli, /* cli */
+ targetpath, /* fname */
+ 0, /* CreatFlags */
+ READ_CONTROL_ACCESS, /* DesiredAccess */
+ 0, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0x0, /* CreateOptions */
+ 0x0, /* SecurityFlags */
+ &fnum, /* pfid */
+ NULL); /* cr */
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("cacl_set failed to open %s: %s\n",
+ targetpath, nt_errstr(status)));
+ errno = 0;
+ return -1;
+ }
+
+ status = cli_query_secdesc(targetcli, fnum, ctx, &old);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("cacl_set Failed to query old descriptor of %s: %s\n",
+ targetpath, nt_errstr(status)));
+ errno = 0;
+ return -1;
+ }
+
+ cli_close(targetcli, fnum);
+
+ switch (mode) {
+ case SMBC_XATTR_MODE_REMOVE_ALL:
+ old->dacl->num_aces = 0;
+ dacl = old->dacl;
+ break;
+
+ case SMBC_XATTR_MODE_REMOVE:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ bool found = False;
+
+ for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+ if (security_ace_equal(&sd->dacl->aces[i],
+ &old->dacl->aces[j])) {
+ uint32_t k;
+ for (k=j; k<old->dacl->num_aces-1;k++) {
+ old->dacl->aces[k] =
+ old->dacl->aces[k+1];
+ }
+ old->dacl->num_aces--;
+ found = True;
+ dacl = old->dacl;
+ break;
+ }
+ }
+
+ if (!found) {
+ err = ENOATTR;
+ ret = -1;
+ goto failed;
+ }
+ }
+ break;
+
+ case SMBC_XATTR_MODE_ADD:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ bool found = False;
+
+ for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+ if (dom_sid_equal(&sd->dacl->aces[i].trustee,
+ &old->dacl->aces[j].trustee)) {
+ if (!(flags & SMBC_XATTR_FLAG_CREATE)) {
+ err = EEXIST;
+ ret = -1;
+ goto failed;
+ }
+ old->dacl->aces[j] = sd->dacl->aces[i];
+ ret = -1;
+ found = True;
+ }
+ }
+
+ if (!found && (flags & SMBC_XATTR_FLAG_REPLACE)) {
+ err = ENOATTR;
+ ret = -1;
+ goto failed;
+ }
+
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ add_ace(&old->dacl, &sd->dacl->aces[i], ctx);
+ }
+ }
+ dacl = old->dacl;
+ break;
+
+ case SMBC_XATTR_MODE_SET:
+ old = sd;
+ owner_sid = old->owner_sid;
+ group_sid = old->group_sid;
+ dacl = old->dacl;
+ break;
+
+ case SMBC_XATTR_MODE_CHOWN:
+ owner_sid = sd->owner_sid;
+ break;
+
+ case SMBC_XATTR_MODE_CHGRP:
+ group_sid = sd->group_sid;
+ break;
+ }
+
+ /* Denied ACE entries must come before allowed ones */
+ sort_acl(old->dacl);
+
+ /* Create new security descriptor and set it */
+ sd = make_sec_desc(ctx, old->revision, SEC_DESC_SELF_RELATIVE,
+ owner_sid, group_sid, NULL, dacl, &sd_size);
+
+ status = cli_ntcreate(targetcli, targetpath, 0,
+ WRITE_DAC_ACCESS | WRITE_OWNER_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN,
+ 0x0, 0x0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("cacl_set failed to open %s: %s\n",
+ targetpath, nt_errstr(status)));
+ errno = 0;
+ return -1;
+ }
+
+ status = cli_set_secdesc(targetcli, fnum, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("ERROR: secdesc set failed: %s\n",
+ nt_errstr(status)));
+ ret = -1;
+ }
+
+ /* Clean up */
+
+failed:
+ cli_close(targetcli, fnum);
+
+ if (err != 0) {
+ errno = err;
+ }
+
+ return ret;
+}
+
+
+int
+SMBC_setxattr_ctx(SMBCCTX *context,
+ const char *fname,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ int ret;
+ int ret2;
+ SMBCSRV *srv = NULL;
+ SMBCSRV *ipc_srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ struct DOS_ATTR_DESC *dad = NULL;
+ struct {
+ const char * create_time_attr;
+ const char * access_time_attr;
+ const char * write_time_attr;
+ const char * change_time_attr;
+ } attr_strings;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_setxattr(%s, %s, %.*s)\n",
+ fname, name, (int) size, (const char*)value));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ if (! srv->no_nt_session) {
+ ipc_srv = SMBC_attr_server(frame, context, server, port, share,
+ &workgroup, &user, &password);
+ if (! ipc_srv) {
+ srv->no_nt_session = True;
+ }
+ } else {
+ ipc_srv = NULL;
+ }
+
+ /*
+ * Are they asking to set the entire set of known attributes?
+ */
+ if (strcasecmp_m(name, "system.*") == 0 ||
+ strcasecmp_m(name, "system.*+") == 0) {
+ /* Yup. */
+ char *namevalue =
+ talloc_asprintf(talloc_tos(), "%s:%s",
+ name+7, (const char *) value);
+ if (! namevalue) {
+ errno = ENOMEM;
+ ret = -1;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (ipc_srv) {
+ ret = cacl_set(context, talloc_tos(), srv->cli,
+ ipc_srv->cli, &ipc_srv->pol, path,
+ namevalue,
+ (*namevalue == '*'
+ ? SMBC_XATTR_MODE_SET
+ : SMBC_XATTR_MODE_ADD),
+ flags);
+ } else {
+ ret = 0;
+ }
+
+ /* get a DOS Attribute Descriptor with current attributes */
+ dad = dos_attr_query(context, talloc_tos(), path, srv);
+ if (dad) {
+ bool ok;
+
+ /* Overwrite old with new, using what was provided */
+ dos_attr_parse(context, dad, srv, namevalue);
+
+ /* Set the new DOS attributes */
+ ok = SMBC_setatr(
+ context,
+ srv,
+ path,
+ (struct timespec) {
+ .tv_sec = dad->create_time },
+ (struct timespec) {
+ .tv_sec = dad->access_time },
+ (struct timespec) {
+ .tv_sec = dad->write_time },
+ (struct timespec) {
+ .tv_sec = dad->change_time },
+ dad->mode);
+ if (!ok) {
+ /* cause failure if NT failed too */
+ dad = NULL;
+ }
+ }
+
+ /* we only fail if both NT and DOS sets failed */
+ if (ret < 0 && ! dad) {
+ ret = -1; /* in case dad was null */
+ }
+ else {
+ ret = 0;
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ /*
+ * Are they asking to set an access control element or to set
+ * the entire access control list?
+ */
+ if (strcasecmp_m(name, "system.nt_sec_desc.*") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.*+") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.revision") == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.acl", 22) == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.acl+", 23) == 0) {
+
+ /* Yup. */
+ char *namevalue =
+ talloc_asprintf(talloc_tos(), "%s:%s",
+ name+19, (const char *) value);
+
+ if (! ipc_srv) {
+ ret = -1; /* errno set by SMBC_server() */
+ }
+ else if (! namevalue) {
+ errno = ENOMEM;
+ ret = -1;
+ } else {
+ ret = cacl_set(context, talloc_tos(), srv->cli,
+ ipc_srv->cli, &ipc_srv->pol, path,
+ namevalue,
+ (*namevalue == '*'
+ ? SMBC_XATTR_MODE_SET
+ : SMBC_XATTR_MODE_ADD),
+ flags);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ /*
+ * Are they asking to set the owner?
+ */
+ if (strcasecmp_m(name, "system.nt_sec_desc.owner") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.owner+") == 0) {
+
+ /* Yup. */
+ char *namevalue =
+ talloc_asprintf(talloc_tos(), "%s:%s",
+ name+19, (const char *) value);
+
+ if (! ipc_srv) {
+ ret = -1; /* errno set by SMBC_server() */
+ }
+ else if (! namevalue) {
+ errno = ENOMEM;
+ ret = -1;
+ } else {
+ ret = cacl_set(context, talloc_tos(), srv->cli,
+ ipc_srv->cli, &ipc_srv->pol, path,
+ namevalue, SMBC_XATTR_MODE_CHOWN, 0);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ /*
+ * Are they asking to set the group?
+ */
+ if (strcasecmp_m(name, "system.nt_sec_desc.group") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.group+") == 0) {
+
+ /* Yup. */
+ char *namevalue =
+ talloc_asprintf(talloc_tos(), "%s:%s",
+ name+19, (const char *) value);
+
+ if (! ipc_srv) {
+ /* errno set by SMBC_server() */
+ ret = -1;
+ }
+ else if (! namevalue) {
+ errno = ENOMEM;
+ ret = -1;
+ } else {
+ ret = cacl_set(context, talloc_tos(), srv->cli,
+ ipc_srv->cli, &ipc_srv->pol, path,
+ namevalue, SMBC_XATTR_MODE_CHGRP, 0);
+ }
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ /* Determine whether to use old-style or new-style attribute names */
+ if (context->internal->full_time_names) {
+ /* new-style names */
+ attr_strings.create_time_attr = "system.dos_attr.CREATE_TIME";
+ attr_strings.access_time_attr = "system.dos_attr.ACCESS_TIME";
+ attr_strings.write_time_attr = "system.dos_attr.WRITE_TIME";
+ attr_strings.change_time_attr = "system.dos_attr.CHANGE_TIME";
+ } else {
+ /* old-style names */
+ attr_strings.create_time_attr = NULL;
+ attr_strings.access_time_attr = "system.dos_attr.A_TIME";
+ attr_strings.write_time_attr = "system.dos_attr.M_TIME";
+ attr_strings.change_time_attr = "system.dos_attr.C_TIME";
+ }
+
+ /*
+ * Are they asking to set a DOS attribute?
+ */
+ if (strcasecmp_m(name, "system.dos_attr.*") == 0 ||
+ strcasecmp_m(name, "system.dos_attr.mode") == 0 ||
+ (attr_strings.create_time_attr != NULL &&
+ strcasecmp_m(name, attr_strings.create_time_attr) == 0) ||
+ strcasecmp_m(name, attr_strings.access_time_attr) == 0 ||
+ strcasecmp_m(name, attr_strings.write_time_attr) == 0 ||
+ strcasecmp_m(name, attr_strings.change_time_attr) == 0) {
+
+ /* get a DOS Attribute Descriptor with current attributes */
+ dad = dos_attr_query(context, talloc_tos(), path, srv);
+ if (dad) {
+ char *namevalue =
+ talloc_asprintf(talloc_tos(), "%s:%s",
+ name+16, (const char *) value);
+ if (! namevalue) {
+ errno = ENOMEM;
+ ret = -1;
+ } else {
+ /* Overwrite old with provided new params */
+ dos_attr_parse(context, dad, srv, namevalue);
+
+ /* Set the new DOS attributes */
+ ret2 = SMBC_setatr(
+ context,
+ srv,
+ path,
+ (struct timespec) {
+ .tv_sec = dad->create_time },
+ (struct timespec) {
+ .tv_sec = dad->access_time },
+ (struct timespec) {
+ .tv_sec = dad->write_time },
+ (struct timespec) {
+ .tv_sec = dad->change_time },
+ dad->mode);
+
+ /* ret2 has True (success) / False (failure) */
+ if (ret2) {
+ ret = 0;
+ } else {
+ ret = -1;
+ }
+ }
+ } else {
+ ret = -1;
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ /* Unsupported attribute name */
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+}
+
+int
+SMBC_getxattr_ctx(SMBCCTX *context,
+ const char *fname,
+ const char *name,
+ const void *value,
+ size_t size)
+{
+ int ret;
+ SMBCSRV *srv = NULL;
+ SMBCSRV *ipc_srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ struct {
+ const char * create_time_attr;
+ const char * access_time_attr;
+ const char * write_time_attr;
+ const char * change_time_attr;
+ } attr_strings;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_getxattr(%s, %s)\n", fname, name));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == '\0') {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ if (! srv->no_nt_session) {
+ ipc_srv = SMBC_attr_server(frame, context, server, port, share,
+ &workgroup, &user, &password);
+ /*
+ * SMBC_attr_server() can cause the original
+ * server to be removed from the cache.
+ * If so we must error out here as the srv
+ * pointer has been freed.
+ */
+ if (smbc_getFunctionGetCachedServer(context)(context,
+ server,
+ share,
+ workgroup,
+ user) != srv) {
+#if defined(ECONNRESET)
+ errno = ECONNRESET;
+#else
+ errno = ETIMEDOUT;
+#endif
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ if (! ipc_srv) {
+ srv->no_nt_session = True;
+ }
+ } else {
+ ipc_srv = NULL;
+ }
+
+ /* Determine whether to use old-style or new-style attribute names */
+ if (context->internal->full_time_names) {
+ /* new-style names */
+ attr_strings.create_time_attr = "system.dos_attr.CREATE_TIME";
+ attr_strings.access_time_attr = "system.dos_attr.ACCESS_TIME";
+ attr_strings.write_time_attr = "system.dos_attr.WRITE_TIME";
+ attr_strings.change_time_attr = "system.dos_attr.CHANGE_TIME";
+ } else {
+ /* old-style names */
+ attr_strings.create_time_attr = NULL;
+ attr_strings.access_time_attr = "system.dos_attr.A_TIME";
+ attr_strings.write_time_attr = "system.dos_attr.M_TIME";
+ attr_strings.change_time_attr = "system.dos_attr.C_TIME";
+ }
+
+ /* Are they requesting a supported attribute? */
+ if (strcasecmp_m(name, "system.*") == 0 ||
+ strncasecmp_m(name, "system.*!", 9) == 0 ||
+ strcasecmp_m(name, "system.*+") == 0 ||
+ strncasecmp_m(name, "system.*+!", 10) == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.*") == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.*!", 21) == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.*+") == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.*+!", 22) == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.revision") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.owner") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.owner+") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.group") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.group+") == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.acl", 22) == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.acl+", 23) == 0 ||
+ strcasecmp_m(name, "system.dos_attr.*") == 0 ||
+ strncasecmp_m(name, "system.dos_attr.*!", 18) == 0 ||
+ strcasecmp_m(name, "system.dos_attr.mode") == 0 ||
+ strcasecmp_m(name, "system.dos_attr.size") == 0 ||
+ (attr_strings.create_time_attr != NULL &&
+ strcasecmp_m(name, attr_strings.create_time_attr) == 0) ||
+ strcasecmp_m(name, attr_strings.access_time_attr) == 0 ||
+ strcasecmp_m(name, attr_strings.write_time_attr) == 0 ||
+ strcasecmp_m(name, attr_strings.change_time_attr) == 0 ||
+ strcasecmp_m(name, "system.dos_attr.inode") == 0) {
+
+ /* Yup. */
+ const char *filename = name;
+ ret = cacl_get(context, talloc_tos(), srv,
+ ipc_srv == NULL ? NULL : ipc_srv->cli,
+ &ipc_srv->pol, path,
+ filename,
+ discard_const_p(char, value),
+ size);
+ TALLOC_FREE(frame);
+ /*
+ * static function cacl_get returns a value greater than zero
+ * which is needed buffer size needed when size_t is 0.
+ */
+ return ret;
+ }
+
+ /* Unsupported attribute name */
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+}
+
+
+int
+SMBC_removexattr_ctx(SMBCCTX *context,
+ const char *fname,
+ const char *name)
+{
+ int ret;
+ SMBCSRV *srv = NULL;
+ SMBCSRV *ipc_srv = NULL;
+ char *server = NULL;
+ char *share = NULL;
+ char *user = NULL;
+ char *password = NULL;
+ char *workgroup = NULL;
+ char *path = NULL;
+ uint16_t port = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!context || !context->internal->initialized) {
+ errno = EINVAL; /* Best I can think of ... */
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!fname) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ DEBUG(4, ("smbc_removexattr(%s, %s)\n", fname, name));
+
+ if (SMBC_parse_path(frame,
+ context,
+ fname,
+ &workgroup,
+ &server,
+ &port,
+ &share,
+ &path,
+ &user,
+ &password,
+ NULL)) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (!user || user[0] == (char)0) {
+ user = talloc_strdup(frame, smbc_getUser(context));
+ if (!user) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ srv = SMBC_server(frame, context, True,
+ server, port, share, &workgroup, &user, &password);
+ if (!srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_server */
+ }
+
+ if (! srv->no_nt_session) {
+ int saved_errno;
+ ipc_srv = SMBC_attr_server(frame, context, server, port, share,
+ &workgroup, &user, &password);
+ saved_errno = errno;
+ /*
+ * SMBC_attr_server() can cause the original
+ * server to be removed from the cache.
+ * If so we must error out here as the srv
+ * pointer has been freed.
+ */
+ if (smbc_getFunctionGetCachedServer(context)(context,
+ server,
+ share,
+ workgroup,
+ user) != srv) {
+#if defined(ECONNRESET)
+ errno = ECONNRESET;
+#else
+ errno = ETIMEDOUT;
+#endif
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ if (! ipc_srv) {
+ errno = saved_errno;
+ srv->no_nt_session = True;
+ }
+ } else {
+ ipc_srv = NULL;
+ }
+
+ if (! ipc_srv) {
+ TALLOC_FREE(frame);
+ return -1; /* errno set by SMBC_attr_server */
+ }
+
+ /* Are they asking to set the entire ACL? */
+ if (strcasecmp_m(name, "system.nt_sec_desc.*") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.*+") == 0) {
+
+ /* Yup. */
+ ret = cacl_set(context, talloc_tos(), srv->cli,
+ ipc_srv->cli, &ipc_srv->pol, path,
+ NULL, SMBC_XATTR_MODE_REMOVE_ALL, 0);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ /*
+ * Are they asking to remove one or more specific security descriptor
+ * attributes?
+ */
+ if (strcasecmp_m(name, "system.nt_sec_desc.revision") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.owner") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.owner+") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.group") == 0 ||
+ strcasecmp_m(name, "system.nt_sec_desc.group+") == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.acl", 22) == 0 ||
+ strncasecmp_m(name, "system.nt_sec_desc.acl+", 23) == 0) {
+
+ /* Yup. */
+ ret = cacl_set(context, talloc_tos(), srv->cli,
+ ipc_srv->cli, &ipc_srv->pol, path,
+ discard_const_p(char, name) + 19,
+ SMBC_XATTR_MODE_REMOVE, 0);
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ /* Unsupported attribute name */
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+}
+
+int
+SMBC_listxattr_ctx(SMBCCTX *context,
+ const char *fname,
+ char *list,
+ size_t size)
+{
+ /*
+ * This isn't quite what listxattr() is supposed to do. This returns
+ * the complete set of attribute names, always, rather than only those
+ * attribute names which actually exist for a file. Hmmm...
+ */
+ size_t retsize;
+ static const char supported_old[] =
+ "system.*\0"
+ "system.*+\0"
+ "system.nt_sec_desc.revision\0"
+ "system.nt_sec_desc.owner\0"
+ "system.nt_sec_desc.owner+\0"
+ "system.nt_sec_desc.group\0"
+ "system.nt_sec_desc.group+\0"
+ "system.nt_sec_desc.acl.*\0"
+ "system.nt_sec_desc.acl\0"
+ "system.nt_sec_desc.acl+\0"
+ "system.nt_sec_desc.*\0"
+ "system.nt_sec_desc.*+\0"
+ "system.dos_attr.*\0"
+ "system.dos_attr.mode\0"
+ "system.dos_attr.c_time\0"
+ "system.dos_attr.a_time\0"
+ "system.dos_attr.m_time\0"
+ ;
+ static const char supported_new[] =
+ "system.*\0"
+ "system.*+\0"
+ "system.nt_sec_desc.revision\0"
+ "system.nt_sec_desc.owner\0"
+ "system.nt_sec_desc.owner+\0"
+ "system.nt_sec_desc.group\0"
+ "system.nt_sec_desc.group+\0"
+ "system.nt_sec_desc.acl.*\0"
+ "system.nt_sec_desc.acl\0"
+ "system.nt_sec_desc.acl+\0"
+ "system.nt_sec_desc.*\0"
+ "system.nt_sec_desc.*+\0"
+ "system.dos_attr.*\0"
+ "system.dos_attr.mode\0"
+ "system.dos_attr.create_time\0"
+ "system.dos_attr.access_time\0"
+ "system.dos_attr.write_time\0"
+ "system.dos_attr.change_time\0"
+ ;
+ const char * supported;
+
+ if (context->internal->full_time_names) {
+ supported = supported_new;
+ retsize = sizeof(supported_new);
+ } else {
+ supported = supported_old;
+ retsize = sizeof(supported_old);
+ }
+
+ if (size == 0) {
+ return retsize;
+ }
+
+ if (retsize > size) {
+ errno = ERANGE;
+ return -1;
+ }
+
+ /* this can't be strcpy() because there are embedded null characters */
+ memcpy(list, supported, retsize);
+ return retsize;
+}
diff --git a/source3/libsmb/namecache.c b/source3/libsmb/namecache.c
new file mode 100644
index 0000000..eb59b9e
--- /dev/null
+++ b/source3/libsmb/namecache.c
@@ -0,0 +1,480 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ NetBIOS name cache module on top of gencache mechanism.
+
+ Copyright (C) Tim Potter 2002
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/gencache.h"
+#include "libsmb/namequery.h"
+
+#define IPSTR_LIST_SEP ","
+#define IPSTR_LIST_CHAR ','
+
+/**
+ * Allocate and initialise an ipstr list using samba_sockaddr ip addresses
+ * passed as arguments.
+ *
+ * @param ctx TALLOC_CTX to use
+ * @param ip_list array of ip addresses to place in the list
+ * @param ip_count number of addresses stored in ip_list
+ * @return pointer to allocated ip string
+ **/
+
+static char *ipstr_list_make_sa(TALLOC_CTX *ctx,
+ const struct samba_sockaddr *sa_list,
+ size_t ip_count)
+{
+ char *ipstr_list = NULL;
+ size_t i;
+
+ /* arguments checking */
+ if (sa_list == NULL) {
+ return NULL;
+ }
+
+ /* process ip addresses given as arguments */
+ for (i = 0; i < ip_count; i++) {
+ char addr_buf[INET6_ADDRSTRLEN];
+ char *new_str = NULL;
+
+ print_sockaddr(addr_buf,
+ sizeof(addr_buf),
+ &sa_list[i].u.ss);
+
+ if (sa_list[i].u.ss.ss_family == AF_INET) {
+ /* IPv4 - port no longer used, store 0 */
+ new_str = talloc_asprintf(ctx,
+ "%s:%d",
+ addr_buf,
+ 0);
+ } else {
+ /* IPv6 - port no longer used, store 0 */
+ new_str = talloc_asprintf(ctx,
+ "[%s]:%d",
+ addr_buf,
+ 0);
+ }
+ if (new_str == NULL) {
+ TALLOC_FREE(ipstr_list);
+ return NULL;
+ }
+
+ if (ipstr_list == NULL) {
+ /* First ip address. */
+ ipstr_list = new_str;
+ } else {
+ /*
+ * Append the separator "," and then the new
+ * ip address to the existing list.
+ *
+ * The efficiency here is horrible, but
+ * ip_count should be small enough we can
+ * live with it.
+ */
+ char *tmp = talloc_asprintf(ctx,
+ "%s%s%s",
+ ipstr_list,
+ IPSTR_LIST_SEP,
+ new_str);
+ if (tmp == NULL) {
+ TALLOC_FREE(new_str);
+ TALLOC_FREE(ipstr_list);
+ return NULL;
+ }
+ TALLOC_FREE(new_str);
+ TALLOC_FREE(ipstr_list);
+ ipstr_list = tmp;
+ }
+ }
+
+ return ipstr_list;
+}
+
+/**
+ * Parse given ip string list into array of ip addresses
+ * (as ip_service structures)
+ * e.g. [IPv6]:port,192.168.1.100:389,192.168.1.78, ...
+ *
+ * @param ipstr ip string list to be parsed
+ * @param ip_list pointer to array of ip addresses which is
+ * talloced by this function and must be freed by caller
+ * @return number of successfully parsed addresses
+ **/
+
+static int ipstr_list_parse(TALLOC_CTX *ctx,
+ const char *ipstr_list,
+ struct samba_sockaddr **sa_list_out)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samba_sockaddr *sa_list = NULL;
+ char *token_str = NULL;
+ size_t count;
+ size_t array_size;
+
+ *sa_list_out = NULL;
+
+ array_size = count_chars(ipstr_list, IPSTR_LIST_CHAR) + 1;
+ sa_list = talloc_zero_array(frame,
+ struct samba_sockaddr,
+ array_size);
+ if (sa_list == NULL) {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ count = 0;
+ while (next_token_talloc(frame,
+ &ipstr_list,
+ &token_str,
+ IPSTR_LIST_SEP)) {
+ bool ok;
+ char *s = token_str;
+ char *p = strrchr(token_str, ':');
+ struct sockaddr_storage ss;
+
+ /* Ensure we don't overrun. */
+ if (count >= array_size) {
+ break;
+ }
+
+ if (p) {
+ *p = 0;
+ /* We now ignore the port. */
+ }
+
+ /* convert single token to ip address */
+ if (token_str[0] == '[') {
+ /* IPv6 address. */
+ s++;
+ p = strchr(token_str, ']');
+ if (!p) {
+ continue;
+ }
+ *p = '\0';
+ }
+ ok = interpret_string_addr(&ss, s, AI_NUMERICHOST);
+ if (!ok) {
+ continue;
+ }
+ ok = sockaddr_storage_to_samba_sockaddr(&sa_list[count],
+ &ss);
+ if (!ok) {
+ continue;
+ }
+ count++;
+ }
+ if (count > 0) {
+ *sa_list_out = talloc_move(ctx, &sa_list);
+ }
+ TALLOC_FREE(frame);
+ return count;
+}
+
+#define NBTKEY_FMT "NBT/%s#%02X"
+
+/**
+ * Generates a key for netbios name lookups on basis of
+ * netbios name and type.
+ * The caller must free returned key string when finished.
+ *
+ * @param name netbios name string (case insensitive)
+ * @param name_type netbios type of the name being looked up
+ *
+ * @return string consisted of uppercased name and appended
+ * type number
+ */
+
+static char *namecache_key(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type)
+{
+ return talloc_asprintf_strupper_m(ctx,
+ NBTKEY_FMT,
+ name,
+ name_type);
+}
+
+/**
+ * Store a name(s) in the name cache - samba_sockaddr version.
+ *
+ * @param name netbios names array
+ * @param name_type integer netbios name type
+ * @param num_names number of names being stored
+ * @param ip_list array of in_addr structures containing
+ * ip addresses being stored
+ **/
+
+bool namecache_store(const char *name,
+ int name_type,
+ size_t num_names,
+ struct samba_sockaddr *sa_list)
+{
+ time_t expiry;
+ char *key = NULL;
+ char *value_string = NULL;
+ size_t i;
+ bool ret = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (name_type > 255) {
+ /* Don't store non-real name types. */
+ goto out;
+ }
+
+ if ( DEBUGLEVEL >= 5 ) {
+ char *addr = NULL;
+
+ DBG_INFO("storing %zu address%s for %s#%02x: ",
+ num_names, num_names == 1 ? "": "es", name, name_type);
+
+ for (i = 0; i < num_names; i++) {
+ addr = print_canonical_sockaddr(frame,
+ &sa_list[i].u.ss);
+ if (!addr) {
+ continue;
+ }
+ DEBUGADD(5, ("%s%s", addr,
+ (i == (num_names - 1) ? "" : ",")));
+
+ }
+ DEBUGADD(5, ("\n"));
+ }
+
+ key = namecache_key(frame, name, name_type);
+ if (!key) {
+ goto out;
+ }
+
+ expiry = time(NULL) + lp_name_cache_timeout();
+
+ /*
+ * Generate string representation of ip addresses list
+ */
+ value_string = ipstr_list_make_sa(frame, sa_list, num_names);
+ if (value_string == NULL) {
+ goto out;
+ }
+
+ /* set the entry */
+ ret = gencache_set(key, value_string, expiry);
+
+ out:
+
+ TALLOC_FREE(key);
+ TALLOC_FREE(value_string);
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/**
+ * Look up a name in the cache.
+ *
+ * @param name netbios name to look up for
+ * @param name_type netbios name type of @param name
+ * @param ip_list talloced list of IP addresses if found in the cache,
+ * NULL otherwise
+ * @param num_names number of entries found
+ *
+ * @return true upon successful fetch or
+ * false if name isn't found in the cache or has expired
+ **/
+
+bool namecache_fetch(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type,
+ struct samba_sockaddr **sa_list,
+ size_t *num_names)
+{
+ char *key, *value;
+ time_t timeout;
+
+ if (name_type > 255) {
+ return false; /* Don't fetch non-real name types. */
+ }
+
+ *num_names = 0;
+
+ /*
+ * Use gencache interface - lookup the key
+ */
+ key = namecache_key(talloc_tos(), name, name_type);
+ if (!key) {
+ return false;
+ }
+
+ if (!gencache_get(key, talloc_tos(), &value, &timeout)) {
+ DBG_INFO("no entry for %s#%02X found.\n", name, name_type);
+ TALLOC_FREE(key);
+ return false;
+ }
+
+ DBG_INFO("name %s#%02X found.\n", name, name_type);
+
+ /*
+ * Split up the stored value into the list of IP addresses
+ */
+ *num_names = ipstr_list_parse(ctx, value, sa_list);
+
+ TALLOC_FREE(key);
+ TALLOC_FREE(value);
+
+ return *num_names > 0; /* true only if some ip has been fetched */
+}
+
+/**
+ * Remove a namecache entry. Needed for site support.
+ *
+ **/
+
+bool namecache_delete(const char *name, int name_type)
+{
+ bool ret;
+ char *key;
+
+ if (name_type > 255) {
+ return false; /* Don't fetch non-real name types. */
+ }
+
+ key = namecache_key(talloc_tos(), name, name_type);
+ if (!key) {
+ return false;
+ }
+ ret = gencache_del(key);
+ TALLOC_FREE(key);
+ return ret;
+}
+
+/**
+ * Delete single namecache entry. Look at the
+ * gencache_iterate definition.
+ *
+ **/
+
+static void flush_netbios_name(const char *key,
+ const char *value,
+ time_t timeout,
+ void *dptr)
+{
+ gencache_del(key);
+ DBG_INFO("Deleting entry %s\n", key);
+}
+
+/**
+ * Flush all names from the name cache.
+ * It's done by gencache_iterate()
+ *
+ * @return true upon successful deletion or
+ * false in case of an error
+ **/
+
+void namecache_flush(void)
+{
+ /*
+ * iterate through each NBT cache's entry and flush it
+ * by flush_netbios_name function
+ */
+ gencache_iterate(flush_netbios_name, NULL, "NBT/*");
+ DBG_INFO("Namecache flushed\n");
+}
+
+/* Construct a name status record key. */
+
+static char *namecache_status_record_key(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type1,
+ int name_type2,
+ const struct sockaddr_storage *keyip)
+{
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), keyip);
+ return talloc_asprintf_strupper_m(ctx,
+ "NBT/%s#%02X.%02X.%s",
+ name,
+ name_type1,
+ name_type2,
+ addr);
+}
+
+/* Store a name status record. */
+
+bool namecache_status_store(const char *keyname, int keyname_type,
+ int name_type, const struct sockaddr_storage *keyip,
+ const char *srvname)
+{
+ char *key;
+ time_t expiry;
+ bool ret;
+
+ key = namecache_status_record_key(talloc_tos(),
+ keyname,
+ keyname_type,
+ name_type,
+ keyip);
+ if (!key)
+ return false;
+
+ expiry = time(NULL) + lp_name_cache_timeout();
+ ret = gencache_set(key, srvname, expiry);
+
+ if (ret) {
+ DBG_INFO("entry %s -> %s\n", key, srvname);
+ } else {
+ DBG_INFO("entry %s store failed.\n", key);
+ }
+
+ TALLOC_FREE(key);
+ return ret;
+}
+
+/* Fetch a name status record. */
+
+bool namecache_status_fetch(const char *keyname,
+ int keyname_type,
+ int name_type,
+ const struct sockaddr_storage *keyip,
+ char *srvname_out)
+{
+ char *key = NULL;
+ char *value = NULL;
+ time_t timeout;
+
+ key = namecache_status_record_key(talloc_tos(),
+ keyname,
+ keyname_type,
+ name_type,
+ keyip);
+ if (!key)
+ return false;
+
+ if (!gencache_get(key, talloc_tos(), &value, &timeout)) {
+ DBG_INFO("no entry for %s found.\n", key);
+ TALLOC_FREE(key);
+ return false;
+ } else {
+ DBG_INFO("key %s -> %s\n", key, value);
+ }
+
+ strlcpy(srvname_out, value, 16);
+ TALLOC_FREE(key);
+ TALLOC_FREE(value);
+ return true;
+}
diff --git a/source3/libsmb/namequery.c b/source3/libsmb/namequery.c
new file mode 100644
index 0000000..e6c0c7d
--- /dev/null
+++ b/source3/libsmb/namequery.c
@@ -0,0 +1,3538 @@
+/*
+ Unix SMB/CIFS implementation.
+ name query routines
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "libads/sitename_cache.h"
+#include "../lib/addns/dnsquery.h"
+#include "../lib/addns/dnsquery_srv.h"
+#include "../libcli/netlogon/netlogon.h"
+#include "lib/async_req/async_sock.h"
+#include "lib/tsocket/tsocket.h"
+#include "libsmb/nmblib.h"
+#include "libsmb/unexpected.h"
+#include "../libcli/nbt/libnbt.h"
+#include "libads/kerberos_proto.h"
+#include "lib/gencache.h"
+#include "librpc/gen_ndr/dns.h"
+#include "lib/util/util_net.h"
+#include "lib/util/string_wrappers.h"
+
+/* nmbd.c sets this to True. */
+bool global_in_nmbd = False;
+
+/*
+ * Utility function to convert from a sockaddr_storage
+ * array to a struct samba_sockaddr array.
+ */
+
+static NTSTATUS sockaddr_array_to_samba_sockaddr_array(
+ TALLOC_CTX *ctx,
+ struct samba_sockaddr **sa_out,
+ size_t *count_out,
+ const struct sockaddr_storage *ss_in,
+ size_t count_in)
+{
+ struct samba_sockaddr *sa = NULL;
+ size_t i;
+ size_t count = 0;
+
+ if (count_in == 0) {
+ /*
+ * Zero length arrays are returned as NULL.
+ * in the name resolution code.
+ */
+ *count_out = 0;
+ *sa_out = NULL;
+ return NT_STATUS_OK;
+ }
+ sa = talloc_zero_array(ctx,
+ struct samba_sockaddr,
+ count_in);
+ if (sa == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ count = 0;
+ for (i = 0; i < count_in; i++) {
+ bool ok;
+
+ /* Filter out zero addresses. */
+ if (is_zero_addr(&ss_in[i])) {
+ continue;
+ }
+ ok = sockaddr_storage_to_samba_sockaddr(&sa[count],
+ &ss_in[i]);
+ if (!ok) {
+ continue;
+ }
+ count++;
+ }
+ if (count == 0) {
+ /*
+ * Zero length arrays are returned as NULL.
+ * in the name resolution code.
+ */
+ TALLOC_FREE(sa);
+ }
+ *count_out = count;
+ *sa_out = sa;
+ return NT_STATUS_OK;
+}
+
+/****************************
+ * SERVER AFFINITY ROUTINES *
+ ****************************/
+
+ /* Server affinity is the concept of preferring the last domain
+ controller with whom you had a successful conversation */
+
+/****************************************************************************
+****************************************************************************/
+#define SAFKEY_FMT "SAF/DOMAIN/%s"
+#define SAF_TTL 900
+#define SAFJOINKEY_FMT "SAFJOIN/DOMAIN/%s"
+#define SAFJOIN_TTL 3600
+
+static char *saf_key(TALLOC_CTX *mem_ctx, const char *domain)
+{
+ return talloc_asprintf_strupper_m(mem_ctx, SAFKEY_FMT, domain);
+}
+
+static char *saf_join_key(TALLOC_CTX *mem_ctx, const char *domain)
+{
+ return talloc_asprintf_strupper_m(mem_ctx, SAFJOINKEY_FMT, domain);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+bool saf_store( const char *domain, const char *servername )
+{
+ char *key;
+ time_t expire;
+ bool ret = False;
+
+ if ( !domain || !servername ) {
+ DEBUG(2,("saf_store: "
+ "Refusing to store empty domain or servername!\n"));
+ return False;
+ }
+
+ if ( (strlen(domain) == 0) || (strlen(servername) == 0) ) {
+ DEBUG(0,("saf_store: "
+ "refusing to store 0 length domain or servername!\n"));
+ return False;
+ }
+
+ key = saf_key(talloc_tos(), domain);
+ if (key == NULL) {
+ DEBUG(1, ("saf_key() failed\n"));
+ return false;
+ }
+ expire = time( NULL ) + lp_parm_int(-1, "saf","ttl", SAF_TTL);
+
+ DEBUG(10,("saf_store: domain = [%s], server = [%s], expire = [%u]\n",
+ domain, servername, (unsigned int)expire ));
+
+ ret = gencache_set( key, servername, expire );
+
+ TALLOC_FREE( key );
+
+ return ret;
+}
+
+bool saf_join_store( const char *domain, const char *servername )
+{
+ char *key;
+ time_t expire;
+ bool ret = False;
+
+ if ( !domain || !servername ) {
+ DEBUG(2,("saf_join_store: Refusing to store empty domain or servername!\n"));
+ return False;
+ }
+
+ if ( (strlen(domain) == 0) || (strlen(servername) == 0) ) {
+ DEBUG(0,("saf_join_store: refusing to store 0 length domain or servername!\n"));
+ return False;
+ }
+
+ key = saf_join_key(talloc_tos(), domain);
+ if (key == NULL) {
+ DEBUG(1, ("saf_join_key() failed\n"));
+ return false;
+ }
+ expire = time( NULL ) + lp_parm_int(-1, "saf","join ttl", SAFJOIN_TTL);
+
+ DEBUG(10,("saf_join_store: domain = [%s], server = [%s], expire = [%u]\n",
+ domain, servername, (unsigned int)expire ));
+
+ ret = gencache_set( key, servername, expire );
+
+ TALLOC_FREE( key );
+
+ return ret;
+}
+
+bool saf_delete( const char *domain )
+{
+ char *key;
+ bool ret = False;
+
+ if ( !domain ) {
+ DEBUG(2,("saf_delete: Refusing to delete empty domain\n"));
+ return False;
+ }
+
+ key = saf_join_key(talloc_tos(), domain);
+ if (key == NULL) {
+ DEBUG(1, ("saf_join_key() failed\n"));
+ return false;
+ }
+ ret = gencache_del(key);
+ TALLOC_FREE(key);
+
+ if (ret) {
+ DEBUG(10,("saf_delete[join]: domain = [%s]\n", domain ));
+ }
+
+ key = saf_key(talloc_tos(), domain);
+ if (key == NULL) {
+ DEBUG(1, ("saf_key() failed\n"));
+ return false;
+ }
+ ret = gencache_del(key);
+ TALLOC_FREE(key);
+
+ if (ret) {
+ DEBUG(10,("saf_delete: domain = [%s]\n", domain ));
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+char *saf_fetch(TALLOC_CTX *mem_ctx, const char *domain )
+{
+ char *server = NULL;
+ time_t timeout;
+ bool ret = False;
+ char *key = NULL;
+
+ if ( !domain || strlen(domain) == 0) {
+ DEBUG(2,("saf_fetch: Empty domain name!\n"));
+ return NULL;
+ }
+
+ key = saf_join_key(talloc_tos(), domain);
+ if (key == NULL) {
+ DEBUG(1, ("saf_join_key() failed\n"));
+ return NULL;
+ }
+
+ ret = gencache_get( key, mem_ctx, &server, &timeout );
+
+ TALLOC_FREE( key );
+
+ if ( ret ) {
+ DEBUG(5,("saf_fetch[join]: Returning \"%s\" for \"%s\" domain\n",
+ server, domain ));
+ return server;
+ }
+
+ key = saf_key(talloc_tos(), domain);
+ if (key == NULL) {
+ DEBUG(1, ("saf_key() failed\n"));
+ return NULL;
+ }
+
+ ret = gencache_get( key, mem_ctx, &server, &timeout );
+
+ TALLOC_FREE( key );
+
+ if ( !ret ) {
+ DEBUG(5,("saf_fetch: failed to find server for \"%s\" domain\n",
+ domain ));
+ } else {
+ DEBUG(5,("saf_fetch: Returning \"%s\" for \"%s\" domain\n",
+ server, domain ));
+ }
+
+ return server;
+}
+
+static void set_socket_addr_v4(struct samba_sockaddr *addr)
+{
+ if (!interpret_string_addr(&addr->u.ss, lp_nbt_client_socket_address(),
+ AI_NUMERICHOST|AI_PASSIVE)) {
+ zero_sockaddr(&addr->u.ss);
+ /* zero_sockaddr sets family to AF_INET. */
+ addr->sa_socklen = sizeof(struct sockaddr_in);
+ }
+ if (addr->u.ss.ss_family != AF_INET) {
+ zero_sockaddr(&addr->u.ss);
+ /* zero_sockaddr sets family to AF_INET. */
+ addr->sa_socklen = sizeof(struct sockaddr_in);
+ }
+}
+
+static struct in_addr my_socket_addr_v4(void)
+{
+ struct samba_sockaddr my_addr = {0};
+
+ set_socket_addr_v4(&my_addr);
+ return my_addr.u.in.sin_addr;
+}
+
+/****************************************************************************
+ Generate a random trn_id.
+****************************************************************************/
+
+static int generate_trn_id(void)
+{
+ uint16_t id;
+
+ generate_random_buffer((uint8_t *)&id, sizeof(id));
+
+ return id % (unsigned)0x7FFF;
+}
+
+/****************************************************************************
+ Parse a node status response into an array of structures.
+****************************************************************************/
+
+static struct node_status *parse_node_status(TALLOC_CTX *mem_ctx, char *p,
+ size_t *num_names,
+ struct node_status_extra *extra)
+{
+ struct node_status *ret;
+ size_t i;
+ size_t result_count = 0;
+
+ result_count = CVAL(p,0);
+
+ if (result_count == 0)
+ return NULL;
+
+ ret = talloc_array(mem_ctx, struct node_status,result_count);
+ if (!ret)
+ return NULL;
+
+ p++;
+ for (i=0;i< result_count;i++) {
+ strlcpy(ret[i].name,p,16);
+ trim_char(ret[i].name,'\0',' ');
+ ret[i].type = CVAL(p,15);
+ ret[i].flags = p[16];
+ p += 18;
+ DEBUG(10, ("%s#%02x: flags = 0x%02x\n", ret[i].name,
+ ret[i].type, ret[i].flags));
+ }
+ /*
+ * Also, pick up the MAC address ...
+ */
+ if (extra) {
+ memcpy(&extra->mac_addr, p, 6); /* Fill in the mac addr */
+ }
+ *num_names = result_count;
+ return ret;
+}
+
+struct sock_packet_read_state {
+ struct tevent_context *ev;
+ enum packet_type type;
+ int trn_id;
+
+ struct nb_packet_reader *reader;
+ struct tevent_req *reader_req;
+
+ struct tdgram_context *sock;
+ struct tevent_req *socket_req;
+ uint8_t *buf;
+ struct tsocket_address *addr;
+
+ bool (*validator)(struct packet_struct *p,
+ void *private_data);
+ void *private_data;
+
+ struct packet_struct *packet;
+};
+
+static void sock_packet_read_got_packet(struct tevent_req *subreq);
+static void sock_packet_read_got_socket(struct tevent_req *subreq);
+
+static struct tevent_req *sock_packet_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct tdgram_context *sock,
+ struct nb_packet_reader *reader,
+ enum packet_type type,
+ int trn_id,
+ bool (*validator)(struct packet_struct *p, void *private_data),
+ void *private_data)
+{
+ struct tevent_req *req;
+ struct sock_packet_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct sock_packet_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->reader = reader;
+ state->sock = sock;
+ state->type = type;
+ state->trn_id = trn_id;
+ state->validator = validator;
+ state->private_data = private_data;
+
+ if (reader != NULL) {
+ state->reader_req = nb_packet_read_send(state, ev, reader);
+ if (tevent_req_nomem(state->reader_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->reader_req, sock_packet_read_got_packet, req);
+ }
+
+ state->socket_req = tdgram_recvfrom_send(state, ev, state->sock);
+ if (tevent_req_nomem(state->socket_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->socket_req, sock_packet_read_got_socket,
+ req);
+
+ return req;
+}
+
+static void sock_packet_read_got_packet(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct sock_packet_read_state *state = tevent_req_data(
+ req, struct sock_packet_read_state);
+ NTSTATUS status;
+
+ status = nb_packet_read_recv(subreq, state, &state->packet);
+
+ TALLOC_FREE(state->reader_req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (state->socket_req != NULL) {
+ /*
+ * Still waiting for socket
+ */
+ return;
+ }
+ /*
+ * Both socket and packet reader failed
+ */
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if ((state->validator != NULL) &&
+ !state->validator(state->packet, state->private_data)) {
+ DEBUG(10, ("validator failed\n"));
+
+ TALLOC_FREE(state->packet);
+
+ state->reader_req = nb_packet_read_send(state, state->ev,
+ state->reader);
+ if (tevent_req_nomem(state->reader_req, req)) {
+ return;
+ }
+ tevent_req_set_callback(
+ state->reader_req, sock_packet_read_got_packet, req);
+ return;
+ }
+
+ TALLOC_FREE(state->socket_req);
+ tevent_req_done(req);
+}
+
+static void sock_packet_read_got_socket(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct sock_packet_read_state *state = tevent_req_data(
+ req, struct sock_packet_read_state);
+ struct samba_sockaddr addr = {0};
+ ssize_t ret;
+ ssize_t received;
+ int err;
+ bool ok;
+
+ received = tdgram_recvfrom_recv(subreq, &err, state,
+ &state->buf, &state->addr);
+
+ TALLOC_FREE(state->socket_req);
+
+ if (received == -1) {
+ if (state->reader_req != NULL) {
+ /*
+ * Still waiting for reader
+ */
+ return;
+ }
+ /*
+ * Both socket and reader failed
+ */
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ ok = tsocket_address_is_inet(state->addr, "ipv4");
+ if (!ok) {
+ goto retry;
+ }
+ ret = tsocket_address_bsd_sockaddr(state->addr,
+ &addr.u.sa,
+ sizeof(addr.u.in));
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return;
+ }
+
+ state->packet = parse_packet_talloc(
+ state, (char *)state->buf, received, state->type,
+ addr.u.in.sin_addr, addr.u.in.sin_port);
+ if (state->packet == NULL) {
+ DEBUG(10, ("parse_packet failed\n"));
+ goto retry;
+ }
+ if ((state->trn_id != -1) &&
+ (state->trn_id != packet_trn_id(state->packet))) {
+ DEBUG(10, ("Expected transaction id %d, got %d\n",
+ state->trn_id, packet_trn_id(state->packet)));
+ goto retry;
+ }
+
+ if ((state->validator != NULL) &&
+ !state->validator(state->packet, state->private_data)) {
+ DEBUG(10, ("validator failed\n"));
+ goto retry;
+ }
+
+ tevent_req_done(req);
+ return;
+
+retry:
+ TALLOC_FREE(state->packet);
+ TALLOC_FREE(state->buf);
+ TALLOC_FREE(state->addr);
+
+ state->socket_req = tdgram_recvfrom_send(state, state->ev, state->sock);
+ if (tevent_req_nomem(state->socket_req, req)) {
+ return;
+ }
+ tevent_req_set_callback(state->socket_req, sock_packet_read_got_socket,
+ req);
+}
+
+static NTSTATUS sock_packet_read_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct packet_struct **ppacket)
+{
+ struct sock_packet_read_state *state = tevent_req_data(
+ req, struct sock_packet_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *ppacket = talloc_move(mem_ctx, &state->packet);
+ return NT_STATUS_OK;
+}
+
+struct nb_trans_state {
+ struct tevent_context *ev;
+ struct tdgram_context *sock;
+ struct nb_packet_reader *reader;
+
+ struct tsocket_address *src_addr;
+ struct tsocket_address *dst_addr;
+ uint8_t *buf;
+ size_t buflen;
+ enum packet_type type;
+ int trn_id;
+
+ bool (*validator)(struct packet_struct *p,
+ void *private_data);
+ void *private_data;
+
+ struct packet_struct *packet;
+};
+
+static void nb_trans_got_reader(struct tevent_req *subreq);
+static void nb_trans_done(struct tevent_req *subreq);
+static void nb_trans_sent(struct tevent_req *subreq);
+static void nb_trans_send_next(struct tevent_req *subreq);
+
+static struct tevent_req *nb_trans_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct samba_sockaddr *_my_addr,
+ const struct samba_sockaddr *_dst_addr,
+ bool bcast,
+ uint8_t *buf, size_t buflen,
+ enum packet_type type, int trn_id,
+ bool (*validator)(struct packet_struct *p,
+ void *private_data),
+ void *private_data)
+{
+ const struct sockaddr *my_addr = &_my_addr->u.sa;
+ size_t my_addr_len = sizeof(_my_addr->u.in); /*We know it's AF_INET.*/
+ const struct sockaddr *dst_addr = &_dst_addr->u.sa;
+ size_t dst_addr_len = sizeof(_dst_addr->u.in); /*We know it's AF_INET.*/
+ struct tevent_req *req, *subreq;
+ struct nb_trans_state *state;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct nb_trans_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->buf = buf;
+ state->buflen = buflen;
+ state->type = type;
+ state->trn_id = trn_id;
+ state->validator = validator;
+ state->private_data = private_data;
+
+ ret = tsocket_address_bsd_from_sockaddr(state,
+ my_addr, my_addr_len,
+ &state->src_addr);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return tevent_req_post(req, ev);
+ }
+
+ ret = tsocket_address_bsd_from_sockaddr(state,
+ dst_addr, dst_addr_len,
+ &state->dst_addr);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return tevent_req_post(req, ev);
+ }
+
+ ret = tdgram_inet_udp_broadcast_socket(state->src_addr, state,
+ &state->sock);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = nb_packet_reader_send(state, ev, type, state->trn_id, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, nb_trans_got_reader, req);
+ return req;
+}
+
+static void nb_trans_got_reader(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_trans_state *state = tevent_req_data(
+ req, struct nb_trans_state);
+ NTSTATUS status;
+
+ status = nb_packet_reader_recv(subreq, state, &state->reader);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("nmbd not around\n"));
+ state->reader = NULL;
+ }
+
+ subreq = sock_packet_read_send(
+ state, state->ev, state->sock,
+ state->reader, state->type, state->trn_id,
+ state->validator, state->private_data);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_trans_done, req);
+
+ subreq = tdgram_sendto_send(state, state->ev,
+ state->sock,
+ state->buf, state->buflen,
+ state->dst_addr);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_trans_sent, req);
+}
+
+static void nb_trans_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_trans_state *state = tevent_req_data(
+ req, struct nb_trans_state);
+ ssize_t sent;
+ int err;
+
+ sent = tdgram_sendto_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (sent == -1) {
+ DEBUG(10, ("sendto failed: %s\n", strerror(err)));
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_trans_send_next, req);
+}
+
+static void nb_trans_send_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_trans_state *state = tevent_req_data(
+ req, struct nb_trans_state);
+ bool ret;
+
+ ret = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ subreq = tdgram_sendto_send(state, state->ev,
+ state->sock,
+ state->buf, state->buflen,
+ state->dst_addr);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_trans_sent, req);
+}
+
+static void nb_trans_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_trans_state *state = tevent_req_data(
+ req, struct nb_trans_state);
+ NTSTATUS status;
+
+ status = sock_packet_read_recv(subreq, state, &state->packet);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS nb_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct packet_struct **ppacket)
+{
+ struct nb_trans_state *state = tevent_req_data(
+ req, struct nb_trans_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *ppacket = talloc_move(mem_ctx, &state->packet);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Do a NBT node status query on an open socket and return an array of
+ structures holding the returned names or NULL if the query failed.
+**************************************************************************/
+
+struct node_status_query_state {
+ struct samba_sockaddr my_addr;
+ struct samba_sockaddr addr;
+ uint8_t buf[1024];
+ ssize_t buflen;
+ struct packet_struct *packet;
+};
+
+static bool node_status_query_validator(struct packet_struct *p,
+ void *private_data);
+static void node_status_query_done(struct tevent_req *subreq);
+
+struct tevent_req *node_status_query_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct nmb_name *name,
+ const struct sockaddr_storage *addr)
+{
+ struct tevent_req *req, *subreq;
+ struct node_status_query_state *state;
+ struct packet_struct p;
+ struct nmb_packet *nmb = &p.packet.nmb;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct node_status_query_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (addr->ss_family != AF_INET) {
+ /* Can't do node status to IPv6 */
+ tevent_req_nterror(req, NT_STATUS_INVALID_ADDRESS);
+ return tevent_req_post(req, ev);
+ }
+
+ ok = sockaddr_storage_to_samba_sockaddr(&state->addr, addr);
+ if (!ok) {
+ /* node status must be IPv4 */
+ tevent_req_nterror(req, NT_STATUS_INVALID_ADDRESS);
+ return tevent_req_post(req, ev);
+ }
+ state->addr.u.in.sin_port = htons(NMB_PORT);
+
+ set_socket_addr_v4(&state->my_addr);
+
+ ZERO_STRUCT(p);
+ nmb->header.name_trn_id = generate_trn_id();
+ nmb->header.opcode = 0;
+ nmb->header.response = false;
+ nmb->header.nm_flags.bcast = false;
+ nmb->header.nm_flags.recursion_available = false;
+ nmb->header.nm_flags.recursion_desired = false;
+ nmb->header.nm_flags.trunc = false;
+ nmb->header.nm_flags.authoritative = false;
+ nmb->header.rcode = 0;
+ nmb->header.qdcount = 1;
+ nmb->header.ancount = 0;
+ nmb->header.nscount = 0;
+ nmb->header.arcount = 0;
+ nmb->question.question_name = *name;
+ nmb->question.question_type = 0x21;
+ nmb->question.question_class = 0x1;
+
+ state->buflen = build_packet((char *)state->buf, sizeof(state->buf),
+ &p);
+ if (state->buflen == 0) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ DEBUG(10, ("build_packet failed\n"));
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = nb_trans_send(state,
+ ev,
+ &state->my_addr,
+ &state->addr,
+ false,
+ state->buf,
+ state->buflen,
+ NMB_PACKET,
+ nmb->header.name_trn_id,
+ node_status_query_validator,
+ NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ DEBUG(10, ("nb_trans_send failed\n"));
+ return tevent_req_post(req, ev);
+ }
+ if (!tevent_req_set_endtime(req, ev, timeval_current_ofs(10, 0))) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, node_status_query_done, req);
+ return req;
+}
+
+static bool node_status_query_validator(struct packet_struct *p,
+ void *private_data)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ debug_nmb_packet(p);
+
+ if (nmb->header.opcode != 0 ||
+ nmb->header.nm_flags.bcast ||
+ nmb->header.rcode ||
+ !nmb->header.ancount ||
+ nmb->answers->rr_type != 0x21) {
+ /*
+ * XXXX what do we do with this? could be a redirect,
+ * but we'll discard it for the moment
+ */
+ return false;
+ }
+ return true;
+}
+
+static void node_status_query_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct node_status_query_state *state = tevent_req_data(
+ req, struct node_status_query_state);
+ NTSTATUS status;
+
+ status = nb_trans_recv(subreq, state, &state->packet);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS node_status_query_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct node_status **pnode_status,
+ size_t *pnum_names,
+ struct node_status_extra *extra)
+{
+ struct node_status_query_state *state = tevent_req_data(
+ req, struct node_status_query_state);
+ struct node_status *node_status;
+ size_t num_names = 0;
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ node_status = parse_node_status(
+ mem_ctx, &state->packet->packet.nmb.answers->rdata[0],
+ &num_names, extra);
+ if (node_status == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *pnode_status = node_status;
+ *pnum_names = num_names;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS node_status_query(TALLOC_CTX *mem_ctx, struct nmb_name *name,
+ const struct sockaddr_storage *addr,
+ struct node_status **pnode_status,
+ size_t *pnum_names,
+ struct node_status_extra *extra)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = node_status_query_send(ev, ev, name, addr);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = node_status_query_recv(req, mem_ctx, pnode_status,
+ pnum_names, extra);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static bool name_status_lmhosts(const struct sockaddr_storage *paddr,
+ int qname_type, fstring pname)
+{
+ FILE *f;
+ char *name;
+ int name_type;
+ struct samba_sockaddr addr_in = {0};
+ struct samba_sockaddr addr = {0};
+ bool ok;
+
+ ok = sockaddr_storage_to_samba_sockaddr(&addr_in, paddr);
+ if (!ok) {
+ return false;
+ }
+ if (addr_in.u.ss.ss_family != AF_INET) {
+ return false;
+ }
+
+ f = startlmhosts(get_dyn_LMHOSTSFILE());
+ if (f == NULL) {
+ return false;
+ }
+
+ while (getlmhostsent(talloc_tos(), f, &name, &name_type, &addr.u.ss)) {
+ if (addr.u.ss.ss_family != AF_INET) {
+ continue;
+ }
+ if (name_type != qname_type) {
+ continue;
+ }
+ if (sockaddr_equal(&addr_in.u.sa, &addr.u.sa)) {
+ fstrcpy(pname, name);
+ endlmhosts(f);
+ return true;
+ }
+ }
+ endlmhosts(f);
+ return false;
+}
+
+/****************************************************************************
+ Find the first type XX name in a node status reply - used for finding
+ a servers name given its IP. Return the matched name in *name.
+**************************************************************************/
+
+bool name_status_find(const char *q_name,
+ int q_type,
+ int type,
+ const struct sockaddr_storage *to_ss,
+ fstring name)
+{
+ char addr[INET6_ADDRSTRLEN];
+ struct node_status *addrs = NULL;
+ struct nmb_name nname;
+ size_t count = 0, i;
+ bool result = false;
+ NTSTATUS status;
+
+ if (lp_disable_netbios()) {
+ DEBUG(5,("name_status_find(%s#%02x): netbios is disabled\n",
+ q_name, q_type));
+ return False;
+ }
+
+ print_sockaddr(addr, sizeof(addr), to_ss);
+
+ DEBUG(10, ("name_status_find: looking up %s#%02x at %s\n", q_name,
+ q_type, addr));
+
+ /* Check the cache first. */
+
+ if (namecache_status_fetch(q_name, q_type, type, to_ss, name)) {
+ return True;
+ }
+
+ if (to_ss->ss_family != AF_INET) {
+ /* Can't do node status to IPv6 */
+ return false;
+ }
+
+ result = name_status_lmhosts(to_ss, type, name);
+ if (result) {
+ DBG_DEBUG("Found name %s in lmhosts\n", name);
+ namecache_status_store(q_name, q_type, type, to_ss, name);
+ return true;
+ }
+
+ /* W2K PDC's seem not to respond to '*'#0. JRA */
+ make_nmb_name(&nname, q_name, q_type);
+ status = node_status_query(talloc_tos(), &nname, to_ss,
+ &addrs, &count, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ for (i=0;i<count;i++) {
+ /* Find first one of the requested type that's not a GROUP. */
+ if (addrs[i].type == type && ! (addrs[i].flags & 0x80))
+ break;
+ }
+ if (i == count)
+ goto done;
+
+ pull_ascii_nstring(name, sizeof(fstring), addrs[i].name);
+
+ /* Store the result in the cache. */
+ /* but don't store an entry for 0x1c names here. Here we have
+ a single host and DOMAIN<0x1c> names should be a list of hosts */
+
+ if ( q_type != 0x1c ) {
+ namecache_status_store(q_name, q_type, type, to_ss, name);
+ }
+
+ result = true;
+
+ done:
+ TALLOC_FREE(addrs);
+
+ DEBUG(10, ("name_status_find: name %sfound", result ? "" : "not "));
+
+ if (result)
+ DEBUGADD(10, (", name %s ip address is %s", name, addr));
+
+ DEBUG(10, ("\n"));
+
+ return result;
+}
+
+/*
+ comparison function used by sort_addr_list
+*/
+
+static int addr_compare(const struct sockaddr_storage *ss1,
+ const struct sockaddr_storage *ss2)
+{
+ int max_bits1=0, max_bits2=0;
+ int num_interfaces = iface_count();
+ int i;
+ struct samba_sockaddr sa1;
+ struct samba_sockaddr sa2;
+ bool ok;
+
+ ok = sockaddr_storage_to_samba_sockaddr(&sa1, ss1);
+ if (!ok) {
+ return 0; /* No change. */
+ }
+
+ ok = sockaddr_storage_to_samba_sockaddr(&sa2, ss2);
+ if (!ok) {
+ return 0; /* No change. */
+ }
+
+ /* Sort IPv4 addresses first. */
+ if (sa1.u.ss.ss_family != sa2.u.ss.ss_family) {
+ if (sa2.u.ss.ss_family == AF_INET) {
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+
+ /* Here we know both addresses are of the same
+ * family. */
+
+ for (i=0;i<num_interfaces;i++) {
+ struct samba_sockaddr sif = {0};
+ const unsigned char *p_ss1 = NULL;
+ const unsigned char *p_ss2 = NULL;
+ const unsigned char *p_if = NULL;
+ size_t len = 0;
+ int bits1, bits2;
+
+ ok = sockaddr_storage_to_samba_sockaddr(&sif, iface_n_bcast(i));
+ if (!ok) {
+ return 0; /* No change. */
+ }
+ if (sif.u.ss.ss_family != sa1.u.ss.ss_family) {
+ /* Ignore interfaces of the wrong type. */
+ continue;
+ }
+ if (sif.u.ss.ss_family == AF_INET) {
+ p_if = (const unsigned char *)&sif.u.in.sin_addr;
+ p_ss1 = (const unsigned char *)&sa1.u.in.sin_addr;
+ p_ss2 = (const unsigned char *)&sa2.u.in.sin_addr;
+ len = 4;
+ }
+#if defined(HAVE_IPV6)
+ if (sif.u.ss.ss_family == AF_INET6) {
+ p_if = (const unsigned char *)&sif.u.in6.sin6_addr;
+ p_ss1 = (const unsigned char *)&sa1.u.in6.sin6_addr;
+ p_ss2 = (const unsigned char *)&sa2.u.in6.sin6_addr;
+ len = 16;
+ }
+#endif
+ if (!p_ss1 || !p_ss2 || !p_if || len == 0) {
+ continue;
+ }
+ bits1 = matching_len_bits(p_ss1, p_if, len);
+ bits2 = matching_len_bits(p_ss2, p_if, len);
+ max_bits1 = MAX(bits1, max_bits1);
+ max_bits2 = MAX(bits2, max_bits2);
+ }
+
+ /* Bias towards directly reachable IPs */
+ if (iface_local(&sa1.u.sa)) {
+ if (sa1.u.ss.ss_family == AF_INET) {
+ max_bits1 += 32;
+ } else {
+ max_bits1 += 128;
+ }
+ }
+ if (iface_local(&sa2.u.sa)) {
+ if (sa2.u.ss.ss_family == AF_INET) {
+ max_bits2 += 32;
+ } else {
+ max_bits2 += 128;
+ }
+ }
+ return max_bits2 - max_bits1;
+}
+
+/*
+ sort an IP list so that names that are close to one of our interfaces
+ are at the top. This prevents the problem where a WINS server returns an IP
+ that is not reachable from our subnet as the first match
+*/
+
+static void sort_addr_list(struct sockaddr_storage *sslist, size_t count)
+{
+ if (count <= 1) {
+ return;
+ }
+
+ TYPESAFE_QSORT(sslist, count, addr_compare);
+}
+
+static int samba_sockaddr_compare(struct samba_sockaddr *sa1,
+ struct samba_sockaddr *sa2)
+{
+ return addr_compare(&sa1->u.ss, &sa2->u.ss);
+}
+
+static void sort_sa_list(struct samba_sockaddr *salist, size_t count)
+{
+ if (count <= 1) {
+ return;
+ }
+
+ TYPESAFE_QSORT(salist, count, samba_sockaddr_compare);
+}
+
+/**********************************************************************
+ Remove any duplicate address/port pairs in the samba_sockaddr array.
+ *********************************************************************/
+
+size_t remove_duplicate_addrs2(struct samba_sockaddr *salist, size_t count )
+{
+ size_t i, j;
+
+ DBG_DEBUG("looking for duplicate address/port pairs\n");
+
+ /* One loop to set duplicates to a zero addr. */
+ for (i=0; i < count; i++) {
+ if (is_zero_addr(&salist[i].u.ss)) {
+ continue;
+ }
+
+ for (j=i+1; j<count; j++) {
+ if (sockaddr_equal(&salist[i].u.sa, &salist[j].u.sa)) {
+ zero_sockaddr(&salist[j].u.ss);
+ }
+ }
+ }
+
+ /* Now remove any addresses set to zero above. */
+ for (i = 0; i < count; i++) {
+ while (i < count &&
+ is_zero_addr(&salist[i].u.ss)) {
+ ARRAY_DEL_ELEMENT(salist, i, count);
+ count--;
+ }
+ }
+
+ return count;
+}
+
+static bool prioritize_ipv4_list(struct samba_sockaddr *salist, size_t count)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samba_sockaddr *salist_new = talloc_array(frame,
+ struct samba_sockaddr,
+ count);
+ size_t i, j;
+
+ if (salist_new == NULL) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ j = 0;
+
+ /* Copy IPv4 first. */
+ for (i = 0; i < count; i++) {
+ if (salist[i].u.ss.ss_family == AF_INET) {
+ salist_new[j++] = salist[i];
+ }
+ }
+
+ /* Copy IPv6. */
+ for (i = 0; i < count; i++) {
+ if (salist[i].u.ss.ss_family != AF_INET) {
+ salist_new[j++] = salist[i];
+ }
+ }
+
+ memcpy(salist, salist_new, sizeof(struct samba_sockaddr)*count);
+ TALLOC_FREE(frame);
+ return true;
+}
+
+/****************************************************************************
+ Do a netbios name query to find someones IP.
+ Returns an array of IP addresses or NULL if none.
+ *count will be set to the number of addresses returned.
+ *timed_out is set if we failed by timing out
+****************************************************************************/
+
+struct name_query_state {
+ struct samba_sockaddr my_addr;
+ struct samba_sockaddr addr;
+ bool bcast;
+ bool bcast_star_query;
+
+
+ uint8_t buf[1024];
+ ssize_t buflen;
+
+ NTSTATUS validate_error;
+ uint8_t flags;
+
+ struct sockaddr_storage *addrs;
+ size_t num_addrs;
+};
+
+static bool name_query_validator(struct packet_struct *p, void *private_data);
+static void name_query_done(struct tevent_req *subreq);
+
+struct tevent_req *name_query_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *name, int name_type,
+ bool bcast, bool recurse,
+ const struct sockaddr_storage *addr)
+{
+ struct tevent_req *req, *subreq;
+ struct name_query_state *state;
+ struct packet_struct p;
+ struct nmb_packet *nmb = &p.packet.nmb;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct name_query_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->bcast = bcast;
+
+ if (addr->ss_family != AF_INET) {
+ /* Can't do node status to IPv6 */
+ tevent_req_nterror(req, NT_STATUS_INVALID_ADDRESS);
+ return tevent_req_post(req, ev);
+ }
+
+ if (lp_disable_netbios()) {
+ DEBUG(5,("name_query(%s#%02x): netbios is disabled\n",
+ name, name_type));
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ ok = sockaddr_storage_to_samba_sockaddr(&state->addr, addr);
+ if (!ok) {
+ /* Node status must be IPv4 */
+ tevent_req_nterror(req, NT_STATUS_INVALID_ADDRESS);
+ return tevent_req_post(req, ev);
+ }
+ state->addr.u.in.sin_port = htons(NMB_PORT);
+
+ set_socket_addr_v4(&state->my_addr);
+
+ ZERO_STRUCT(p);
+ nmb->header.name_trn_id = generate_trn_id();
+ nmb->header.opcode = 0;
+ nmb->header.response = false;
+ nmb->header.nm_flags.bcast = bcast;
+ nmb->header.nm_flags.recursion_available = false;
+ nmb->header.nm_flags.recursion_desired = recurse;
+ nmb->header.nm_flags.trunc = false;
+ nmb->header.nm_flags.authoritative = false;
+ nmb->header.rcode = 0;
+ nmb->header.qdcount = 1;
+ nmb->header.ancount = 0;
+ nmb->header.nscount = 0;
+ nmb->header.arcount = 0;
+
+ if (bcast && (strcmp(name, "*")==0)) {
+ /*
+ * We're doing a broadcast query for all
+ * names in the area. Remember this so
+ * we will wait for all names within
+ * the timeout period.
+ */
+ state->bcast_star_query = true;
+ }
+
+ make_nmb_name(&nmb->question.question_name,name,name_type);
+
+ nmb->question.question_type = 0x20;
+ nmb->question.question_class = 0x1;
+
+ state->buflen = build_packet((char *)state->buf, sizeof(state->buf),
+ &p);
+ if (state->buflen == 0) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ DEBUG(10, ("build_packet failed\n"));
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = nb_trans_send(state,
+ ev,
+ &state->my_addr,
+ &state->addr,
+ bcast,
+ state->buf,
+ state->buflen,
+ NMB_PACKET,
+ nmb->header.name_trn_id,
+ name_query_validator,
+ state);
+ if (tevent_req_nomem(subreq, req)) {
+ DEBUG(10, ("nb_trans_send failed\n"));
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, name_query_done, req);
+ return req;
+}
+
+static bool name_query_validator(struct packet_struct *p, void *private_data)
+{
+ struct name_query_state *state = talloc_get_type_abort(
+ private_data, struct name_query_state);
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct sockaddr_storage *tmp_addrs;
+ bool got_unique_netbios_name = false;
+ int i;
+
+ debug_nmb_packet(p);
+
+ /*
+ * If we get a Negative Name Query Response from a WINS
+ * server, we should report it and give up.
+ */
+ if( 0 == nmb->header.opcode /* A query response */
+ && !state->bcast /* from a WINS server */
+ && nmb->header.rcode /* Error returned */
+ ) {
+
+ if( DEBUGLVL( 3 ) ) {
+ /* Only executed if DEBUGLEVEL >= 3 */
+ dbgtext( "Negative name query "
+ "response, rcode 0x%02x: ",
+ nmb->header.rcode );
+ switch( nmb->header.rcode ) {
+ case 0x01:
+ dbgtext("Request was invalidly formatted.\n");
+ break;
+ case 0x02:
+ dbgtext("Problem with NBNS, cannot process "
+ "name.\n");
+ break;
+ case 0x03:
+ dbgtext("The name requested does not "
+ "exist.\n");
+ break;
+ case 0x04:
+ dbgtext("Unsupported request error.\n");
+ break;
+ case 0x05:
+ dbgtext("Query refused error.\n");
+ break;
+ default:
+ dbgtext("Unrecognized error code.\n" );
+ break;
+ }
+ }
+
+ /*
+ * We accept this packet as valid, but tell the upper
+ * layers that it's a negative response.
+ */
+ state->validate_error = NT_STATUS_NOT_FOUND;
+ return true;
+ }
+
+ if (nmb->header.opcode != 0 ||
+ nmb->header.nm_flags.bcast ||
+ nmb->header.rcode ||
+ !nmb->header.ancount) {
+ /*
+ * XXXX what do we do with this? Could be a redirect,
+ * but we'll discard it for the moment.
+ */
+ return false;
+ }
+
+ tmp_addrs = talloc_realloc(
+ state, state->addrs, struct sockaddr_storage,
+ state->num_addrs + nmb->answers->rdlength/6);
+ if (tmp_addrs == NULL) {
+ state->validate_error = NT_STATUS_NO_MEMORY;
+ return true;
+ }
+ state->addrs = tmp_addrs;
+
+ DEBUG(2,("Got a positive name query response "
+ "from %s ( ", inet_ntoa(p->ip)));
+
+ for (i=0; i<nmb->answers->rdlength/6; i++) {
+ uint16_t flags;
+ struct in_addr ip;
+ struct sockaddr_storage addr;
+ struct samba_sockaddr sa = {0};
+ bool ok;
+ size_t j;
+
+ flags = RSVAL(&nmb->answers->rdata[i*6], 0);
+ got_unique_netbios_name |= ((flags & 0x8000) == 0);
+
+ putip((char *)&ip,&nmb->answers->rdata[2+i*6]);
+ in_addr_to_sockaddr_storage(&addr, ip);
+
+ ok = sockaddr_storage_to_samba_sockaddr(&sa, &addr);
+ if (!ok) {
+ continue;
+ }
+
+ if (is_zero_addr(&sa.u.ss)) {
+ continue;
+ }
+
+ for (j=0; j<state->num_addrs; j++) {
+ struct samba_sockaddr sa_j = {0};
+
+ ok = sockaddr_storage_to_samba_sockaddr(&sa_j,
+ &state->addrs[j]);
+ if (!ok) {
+ continue;
+ }
+ if (sockaddr_equal(&sa.u.sa, &sa_j.u.sa)) {
+ break;
+ }
+ }
+ if (j < state->num_addrs) {
+ /* Already got it */
+ continue;
+ }
+
+ DEBUGADD(2,("%s ",inet_ntoa(ip)));
+
+ state->addrs[state->num_addrs] = addr;
+ /* wrap check. */
+ if (state->num_addrs + 1 < state->num_addrs) {
+ return false;
+ }
+ state->num_addrs += 1;
+ }
+ DEBUGADD(2,(")\n"));
+
+ /* We add the flags back ... */
+ if (nmb->header.response)
+ state->flags |= NM_FLAGS_RS;
+ if (nmb->header.nm_flags.authoritative)
+ state->flags |= NM_FLAGS_AA;
+ if (nmb->header.nm_flags.trunc)
+ state->flags |= NM_FLAGS_TC;
+ if (nmb->header.nm_flags.recursion_desired)
+ state->flags |= NM_FLAGS_RD;
+ if (nmb->header.nm_flags.recursion_available)
+ state->flags |= NM_FLAGS_RA;
+ if (nmb->header.nm_flags.bcast)
+ state->flags |= NM_FLAGS_B;
+
+ if (state->bcast) {
+ /*
+ * We have to collect all entries coming in from broadcast
+ * queries. If we got a unique name and we are not querying
+ * all names registered within broadcast area (query
+ * for the name '*', so state->bcast_star_query is set),
+ * we're done.
+ */
+ return (got_unique_netbios_name && !state->bcast_star_query);
+ }
+ /*
+ * WINS responses are accepted when they are received
+ */
+ return true;
+}
+
+static void name_query_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct name_query_state *state = tevent_req_data(
+ req, struct name_query_state);
+ NTSTATUS status;
+ struct packet_struct *p = NULL;
+
+ status = nb_trans_recv(subreq, state, &p);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (!NT_STATUS_IS_OK(state->validate_error)) {
+ tevent_req_nterror(req, state->validate_error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS name_query_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs, size_t *num_addrs,
+ uint8_t *flags)
+{
+ struct name_query_state *state = tevent_req_data(
+ req, struct name_query_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ if (state->bcast &&
+ NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ /*
+ * In the broadcast case we collect replies until the
+ * timeout.
+ */
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ if (state->num_addrs == 0) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ *addrs = talloc_move(mem_ctx, &state->addrs);
+ sort_addr_list(*addrs, state->num_addrs);
+ *num_addrs = state->num_addrs;
+ if (flags != NULL) {
+ *flags = state->flags;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS name_query(const char *name, int name_type,
+ bool bcast, bool recurse,
+ const struct sockaddr_storage *to_ss,
+ TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs,
+ size_t *num_addrs, uint8_t *flags)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ struct timeval timeout;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = name_query_send(ev, ev, name, name_type, bcast, recurse, to_ss);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (bcast) {
+ timeout = timeval_current_ofs(0, 250000);
+ } else {
+ timeout = timeval_current_ofs(2, 0);
+ }
+ if (!tevent_req_set_endtime(req, ev, timeout)) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = name_query_recv(req, mem_ctx, addrs, num_addrs, flags);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct name_queries_state {
+ struct tevent_context *ev;
+ const char *name;
+ int name_type;
+ bool bcast;
+ bool recurse;
+ const struct sockaddr_storage *addrs;
+ size_t num_addrs;
+ int wait_msec;
+ int timeout_msec;
+
+ struct tevent_req **subreqs;
+ size_t num_received;
+ size_t num_sent;
+
+ size_t received_index;
+ struct sockaddr_storage *result_addrs;
+ size_t num_result_addrs;
+ uint8_t flags;
+};
+
+static void name_queries_done(struct tevent_req *subreq);
+static void name_queries_next(struct tevent_req *subreq);
+
+/*
+ * Send a name query to multiple destinations with a wait time in between
+ */
+
+static struct tevent_req *name_queries_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *name, int name_type,
+ bool bcast, bool recurse,
+ const struct sockaddr_storage *addrs,
+ size_t num_addrs, int wait_msec, int timeout_msec)
+{
+ struct tevent_req *req, *subreq;
+ struct name_queries_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct name_queries_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->name = name;
+ state->name_type = name_type;
+ state->bcast = bcast;
+ state->recurse = recurse;
+ state->addrs = addrs;
+ state->num_addrs = num_addrs;
+ state->wait_msec = wait_msec;
+ state->timeout_msec = timeout_msec;
+
+ state->subreqs = talloc_zero_array(
+ state, struct tevent_req *, num_addrs);
+ if (tevent_req_nomem(state->subreqs, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->num_sent = 0;
+
+ subreq = name_query_send(
+ state->subreqs, state->ev, name, name_type, bcast, recurse,
+ &state->addrs[state->num_sent]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (!tevent_req_set_endtime(
+ subreq, state->ev,
+ timeval_current_ofs(0, state->timeout_msec * 1000))) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, name_queries_done, req);
+
+ state->subreqs[state->num_sent] = subreq;
+ state->num_sent += 1;
+
+ if (state->num_sent < state->num_addrs) {
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ timeval_current_ofs(0, state->wait_msec * 1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, name_queries_next, req);
+ }
+ return req;
+}
+
+static void name_queries_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct name_queries_state *state = tevent_req_data(
+ req, struct name_queries_state);
+ size_t i;
+ NTSTATUS status;
+
+ status = name_query_recv(subreq, state, &state->result_addrs,
+ &state->num_result_addrs, &state->flags);
+
+ for (i=0; i<state->num_sent; i++) {
+ if (state->subreqs[i] == subreq) {
+ break;
+ }
+ }
+ if (i == state->num_sent) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ TALLOC_FREE(state->subreqs[i]);
+
+ /* wrap check. */
+ if (state->num_received + 1 < state->num_received) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ state->num_received += 1;
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ if (state->num_received >= state->num_addrs) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ /*
+ * Still outstanding requests, just wait
+ */
+ return;
+ }
+ state->received_index = i;
+ tevent_req_done(req);
+}
+
+static void name_queries_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct name_queries_state *state = tevent_req_data(
+ req, struct name_queries_state);
+
+ if (!tevent_wakeup_recv(subreq)) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ subreq = name_query_send(
+ state->subreqs, state->ev,
+ state->name, state->name_type, state->bcast, state->recurse,
+ &state->addrs[state->num_sent]);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, name_queries_done, req);
+ if (!tevent_req_set_endtime(
+ subreq, state->ev,
+ timeval_current_ofs(0, state->timeout_msec * 1000))) {
+ return;
+ }
+ state->subreqs[state->num_sent] = subreq;
+ state->num_sent += 1;
+
+ if (state->num_sent < state->num_addrs) {
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ timeval_current_ofs(0, state->wait_msec * 1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, name_queries_next, req);
+ }
+}
+
+static NTSTATUS name_queries_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **result_addrs,
+ size_t *num_result_addrs, uint8_t *flags,
+ size_t *received_index)
+{
+ struct name_queries_state *state = tevent_req_data(
+ req, struct name_queries_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (result_addrs != NULL) {
+ *result_addrs = talloc_move(mem_ctx, &state->result_addrs);
+ }
+ if (num_result_addrs != NULL) {
+ *num_result_addrs = state->num_result_addrs;
+ }
+ if (flags != NULL) {
+ *flags = state->flags;
+ }
+ if (received_index != NULL) {
+ *received_index = state->received_index;
+ }
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Resolve via "bcast" method.
+*********************************************************/
+
+struct name_resolve_bcast_state {
+ struct sockaddr_storage *addrs;
+ size_t num_addrs;
+};
+
+static void name_resolve_bcast_done(struct tevent_req *subreq);
+
+struct tevent_req *name_resolve_bcast_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *name,
+ int name_type)
+{
+ struct tevent_req *req, *subreq;
+ struct name_resolve_bcast_state *state;
+ struct sockaddr_storage *bcast_addrs;
+ size_t i, num_addrs, num_bcast_addrs;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct name_resolve_bcast_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (lp_disable_netbios()) {
+ DEBUG(5, ("name_resolve_bcast(%s#%02x): netbios is disabled\n",
+ name, name_type));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * "bcast" means do a broadcast lookup on all the local interfaces.
+ */
+
+ DEBUG(3, ("name_resolve_bcast: Attempting broadcast lookup "
+ "for name %s<0x%x>\n", name, name_type));
+
+ num_addrs = iface_count();
+ bcast_addrs = talloc_array(state, struct sockaddr_storage, num_addrs);
+ if (tevent_req_nomem(bcast_addrs, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Lookup the name on all the interfaces, return on
+ * the first successful match.
+ */
+ num_bcast_addrs = 0;
+
+ for (i=0; i<num_addrs; i++) {
+ const struct sockaddr_storage *pss = iface_n_bcast(i);
+
+ if (pss->ss_family != AF_INET) {
+ continue;
+ }
+ bcast_addrs[num_bcast_addrs] = *pss;
+ num_bcast_addrs += 1;
+ }
+
+ subreq = name_queries_send(state, ev, name, name_type, true, true,
+ bcast_addrs, num_bcast_addrs, 0, 250);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, name_resolve_bcast_done, req);
+ return req;
+}
+
+static void name_resolve_bcast_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct name_resolve_bcast_state *state = tevent_req_data(
+ req, struct name_resolve_bcast_state);
+ NTSTATUS status;
+
+ status = name_queries_recv(subreq, state,
+ &state->addrs, &state->num_addrs,
+ NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS name_resolve_bcast_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs,
+ size_t *num_addrs)
+{
+ struct name_resolve_bcast_state *state = tevent_req_data(
+ req, struct name_resolve_bcast_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *addrs = talloc_move(mem_ctx, &state->addrs);
+ *num_addrs = state->num_addrs;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS name_resolve_bcast(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int name_type,
+ struct sockaddr_storage **return_iplist,
+ size_t *return_count)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = name_resolve_bcast_send(frame, ev, name, name_type);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = name_resolve_bcast_recv(req, mem_ctx, return_iplist,
+ return_count);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct query_wins_list_state {
+ struct tevent_context *ev;
+ const char *name;
+ uint8_t name_type;
+ struct in_addr *servers;
+ size_t num_servers;
+ struct sockaddr_storage server;
+ size_t num_sent;
+
+ struct sockaddr_storage *addrs;
+ size_t num_addrs;
+ uint8_t flags;
+};
+
+static void query_wins_list_done(struct tevent_req *subreq);
+
+/*
+ * Query a list of (replicating) wins servers in sequence, call them
+ * dead if they don't reply
+ */
+
+static struct tevent_req *query_wins_list_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct in_addr src_ip, const char *name, uint8_t name_type,
+ struct in_addr *servers, size_t num_servers)
+{
+ struct tevent_req *req, *subreq;
+ struct query_wins_list_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct query_wins_list_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->name = name;
+ state->name_type = name_type;
+ state->servers = servers;
+ state->num_servers = num_servers;
+
+ if (state->num_servers == 0) {
+ tevent_req_nterror(req, NT_STATUS_NOT_FOUND);
+ return tevent_req_post(req, ev);
+ }
+
+ in_addr_to_sockaddr_storage(
+ &state->server, state->servers[state->num_sent]);
+
+ subreq = name_query_send(state, state->ev,
+ state->name, state->name_type,
+ false, true, &state->server);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /* wrap check */
+ if (state->num_sent + 1 < state->num_sent) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->num_sent += 1;
+ if (!tevent_req_set_endtime(subreq, state->ev,
+ timeval_current_ofs(2, 0))) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, query_wins_list_done, req);
+ return req;
+}
+
+static void query_wins_list_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct query_wins_list_state *state = tevent_req_data(
+ req, struct query_wins_list_state);
+ NTSTATUS status;
+
+ status = name_query_recv(subreq, state,
+ &state->addrs, &state->num_addrs,
+ &state->flags);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ wins_srv_died(state->servers[state->num_sent-1],
+ my_socket_addr_v4());
+
+ if (state->num_sent == state->num_servers) {
+ tevent_req_nterror(req, NT_STATUS_NOT_FOUND);
+ return;
+ }
+
+ in_addr_to_sockaddr_storage(
+ &state->server, state->servers[state->num_sent]);
+
+ subreq = name_query_send(state, state->ev,
+ state->name, state->name_type,
+ false, true, &state->server);
+ state->num_sent += 1;
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ if (!tevent_req_set_endtime(subreq, state->ev,
+ timeval_current_ofs(2, 0))) {
+ return;
+ }
+ tevent_req_set_callback(subreq, query_wins_list_done, req);
+}
+
+static NTSTATUS query_wins_list_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs,
+ size_t *num_addrs,
+ uint8_t *flags)
+{
+ struct query_wins_list_state *state = tevent_req_data(
+ req, struct query_wins_list_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (addrs != NULL) {
+ *addrs = talloc_move(mem_ctx, &state->addrs);
+ }
+ if (num_addrs != NULL) {
+ *num_addrs = state->num_addrs;
+ }
+ if (flags != NULL) {
+ *flags = state->flags;
+ }
+ return NT_STATUS_OK;
+}
+
+struct resolve_wins_state {
+ size_t num_sent;
+ size_t num_received;
+
+ struct sockaddr_storage *addrs;
+ size_t num_addrs;
+ uint8_t flags;
+};
+
+static void resolve_wins_done(struct tevent_req *subreq);
+
+struct tevent_req *resolve_wins_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *name,
+ int name_type)
+{
+ struct tevent_req *req, *subreq;
+ struct resolve_wins_state *state;
+ char **wins_tags = NULL;
+ struct sockaddr_storage src_ss;
+ struct samba_sockaddr src_sa = {0};
+ struct in_addr src_ip;
+ size_t i, num_wins_tags;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct resolve_wins_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (wins_srv_count() < 1) {
+ DEBUG(3,("resolve_wins: WINS server resolution selected "
+ "and no WINS servers listed.\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto fail;
+ }
+
+ /* the address we will be sending from */
+ if (!interpret_string_addr(&src_ss, lp_nbt_client_socket_address(),
+ AI_NUMERICHOST|AI_PASSIVE)) {
+ zero_sockaddr(&src_ss);
+ }
+
+ ok = sockaddr_storage_to_samba_sockaddr(&src_sa, &src_ss);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto fail;
+ }
+
+ if (src_sa.u.ss.ss_family != AF_INET) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr), &src_sa.u.ss);
+ DEBUG(3,("resolve_wins: cannot receive WINS replies "
+ "on IPv6 address %s\n",
+ addr));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto fail;
+ }
+
+ src_ip = src_sa.u.in.sin_addr;
+
+ wins_tags = wins_srv_tags();
+ if (wins_tags == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto fail;
+ }
+
+ num_wins_tags = 0;
+ while (wins_tags[num_wins_tags] != NULL) {
+ /* wrap check. */
+ if (num_wins_tags + 1 < num_wins_tags) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto fail;
+ }
+ num_wins_tags += 1;
+ }
+
+ for (i=0; i<num_wins_tags; i++) {
+ size_t num_servers, num_alive;
+ struct in_addr *servers, *alive;
+ size_t j;
+
+ if (!wins_server_tag_ips(wins_tags[i], talloc_tos(),
+ &servers, &num_servers)) {
+ DEBUG(10, ("wins_server_tag_ips failed for tag %s\n",
+ wins_tags[i]));
+ continue;
+ }
+
+ alive = talloc_array(state, struct in_addr, num_servers);
+ if (tevent_req_nomem(alive, req)) {
+ goto fail;
+ }
+
+ num_alive = 0;
+ for (j=0; j<num_servers; j++) {
+ struct in_addr wins_ip = servers[j];
+
+ if (global_in_nmbd && ismyip_v4(wins_ip)) {
+ /* yikes! we'll loop forever */
+ continue;
+ }
+ /* skip any that have been unresponsive lately */
+ if (wins_srv_is_dead(wins_ip, src_ip)) {
+ continue;
+ }
+ DEBUG(3, ("resolve_wins: using WINS server %s "
+ "and tag '%s'\n",
+ inet_ntoa(wins_ip), wins_tags[i]));
+ alive[num_alive] = wins_ip;
+ num_alive += 1;
+ }
+ TALLOC_FREE(servers);
+
+ if (num_alive == 0) {
+ continue;
+ }
+
+ subreq = query_wins_list_send(
+ state, ev, src_ip, name, name_type,
+ alive, num_alive);
+ if (tevent_req_nomem(subreq, req)) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, resolve_wins_done, req);
+ state->num_sent += 1;
+ }
+
+ if (state->num_sent == 0) {
+ tevent_req_nterror(req, NT_STATUS_NOT_FOUND);
+ goto fail;
+ }
+
+ wins_srv_tags_free(wins_tags);
+ return req;
+fail:
+ wins_srv_tags_free(wins_tags);
+ return tevent_req_post(req, ev);
+}
+
+static void resolve_wins_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct resolve_wins_state *state = tevent_req_data(
+ req, struct resolve_wins_state);
+ NTSTATUS status;
+
+ status = query_wins_list_recv(subreq, state, &state->addrs,
+ &state->num_addrs, &state->flags);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /* wrap check. */
+ if (state->num_received + 1 < state->num_received) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ state->num_received += 1;
+
+ if (state->num_received < state->num_sent) {
+ /*
+ * Wait for the others
+ */
+ return;
+ }
+ tevent_req_nterror(req, status);
+}
+
+NTSTATUS resolve_wins_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs,
+ size_t *num_addrs, uint8_t *flags)
+{
+ struct resolve_wins_state *state = tevent_req_data(
+ req, struct resolve_wins_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (addrs != NULL) {
+ *addrs = talloc_move(mem_ctx, &state->addrs);
+ }
+ if (num_addrs != NULL) {
+ *num_addrs = state->num_addrs;
+ }
+ if (flags != NULL) {
+ *flags = state->flags;
+ }
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Resolve via "wins" method.
+*********************************************************/
+
+NTSTATUS resolve_wins(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int name_type,
+ struct sockaddr_storage **return_iplist,
+ size_t *return_count)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = resolve_wins_send(ev, ev, name, name_type);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = resolve_wins_recv(req, mem_ctx, return_iplist, return_count,
+ NULL);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+
+/********************************************************
+ Resolve via "hosts" method.
+*********************************************************/
+
+static NTSTATUS resolve_hosts(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int name_type,
+ struct sockaddr_storage **return_iplist,
+ size_t *return_count)
+{
+ /*
+ * "host" means do a localhost, or dns lookup.
+ */
+ struct addrinfo hints;
+ struct addrinfo *ailist = NULL;
+ struct addrinfo *res = NULL;
+ int ret = -1;
+ size_t i = 0;
+ size_t ret_count = 0;
+ struct sockaddr_storage *iplist = NULL;
+
+ if ( name_type != 0x20 && name_type != 0x0) {
+ DEBUG(5, ("resolve_hosts: not appropriate "
+ "for name type <0x%x>\n",
+ name_type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(3,("resolve_hosts: Attempting host lookup for name %s<0x%x>\n",
+ name, name_type));
+
+ ZERO_STRUCT(hints);
+ /* By default make sure it supports TCP. */
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_ADDRCONFIG;
+
+#if !defined(HAVE_IPV6)
+ /* Unless we have IPv6, we really only want IPv4 addresses back. */
+ hints.ai_family = AF_INET;
+#endif
+
+ ret = getaddrinfo(name,
+ NULL,
+ &hints,
+ &ailist);
+ if (ret) {
+ DEBUG(3,("resolve_hosts: getaddrinfo failed for name %s [%s]\n",
+ name,
+ gai_strerror(ret) ));
+ }
+
+ for (res = ailist; res; res = res->ai_next) {
+ struct sockaddr_storage ss = {0};
+ struct sockaddr_storage *tmp = NULL;
+
+ if ((res->ai_addr == NULL) ||
+ (res->ai_addrlen == 0) ||
+ (res->ai_addrlen > sizeof(ss))) {
+ continue;
+ }
+
+ memcpy(&ss, res->ai_addr, res->ai_addrlen);
+
+ if (is_zero_addr(&ss)) {
+ continue;
+ }
+
+ /* wrap check. */
+ if (ret_count + 1 < ret_count) {
+ freeaddrinfo(ailist);
+ TALLOC_FREE(iplist);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ret_count += 1;
+
+ tmp = talloc_realloc(
+ mem_ctx, iplist, struct sockaddr_storage,
+ ret_count);
+ if (tmp == NULL) {
+ DEBUG(3,("resolve_hosts: malloc fail !\n"));
+ freeaddrinfo(ailist);
+ TALLOC_FREE(iplist);
+ return NT_STATUS_NO_MEMORY;
+ }
+ iplist = tmp;
+ iplist[i] = ss;
+ i++;
+ }
+ if (ailist) {
+ freeaddrinfo(ailist);
+ }
+ if (ret_count == 0) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ *return_count = ret_count;
+ *return_iplist = iplist;
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Resolve via "ADS" method.
+*********************************************************/
+
+/* Special name type used to cause a _kerberos DNS lookup. */
+#define KDC_NAME_TYPE 0xDCDC
+
+static NTSTATUS resolve_ads(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type,
+ const char *sitename,
+ struct sockaddr_storage **return_addrs,
+ size_t *return_count)
+{
+ size_t i;
+ NTSTATUS status;
+ struct dns_rr_srv *dcs = NULL;
+ size_t numdcs = 0;
+ size_t num_srv_addrs = 0;
+ struct sockaddr_storage *srv_addrs = NULL;
+ char *query = NULL;
+
+ if ((name_type != 0x1c) && (name_type != KDC_NAME_TYPE) &&
+ (name_type != 0x1b)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = NT_STATUS_OK;
+
+ switch (name_type) {
+ case 0x1b:
+ DEBUG(5,("resolve_ads: Attempting to resolve "
+ "PDC for %s using DNS\n", name));
+ query = ads_dns_query_string_pdc(ctx, name);
+ break;
+
+ case 0x1c:
+ DEBUG(5,("resolve_ads: Attempting to resolve "
+ "DCs for %s using DNS\n", name));
+ query = ads_dns_query_string_dcs(ctx, name);
+ break;
+ case KDC_NAME_TYPE:
+ DEBUG(5,("resolve_ads: Attempting to resolve "
+ "KDCs for %s using DNS\n", name));
+ query = ads_dns_query_string_kdcs(ctx, name);
+ break;
+ default:
+ status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (query == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("SRV query for %s\n", query);
+
+ status = ads_dns_query_srv(
+ ctx,
+ lp_get_async_dns_timeout(),
+ sitename,
+ query,
+ &dcs,
+ &numdcs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (numdcs == 0) {
+ *return_addrs = NULL;
+ *return_count = 0;
+ TALLOC_FREE(dcs);
+ return NT_STATUS_OK;
+ }
+
+ /* First count the sizes of each array. */
+ for(i = 0; i < numdcs; i++) {
+ if (dcs[i].ss_s == NULL) {
+ /*
+ * Nothing received or timeout in A/AAAA reqs
+ */
+ continue;
+ }
+
+ if (num_srv_addrs + dcs[i].num_ips < num_srv_addrs) {
+ /* Wrap check. */
+ TALLOC_FREE(dcs);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ /* Add in the number of addresses we got. */
+ num_srv_addrs += dcs[i].num_ips;
+ }
+
+ /* Allocate the list of IP addresses we already have. */
+ srv_addrs = talloc_zero_array(ctx,
+ struct sockaddr_storage,
+ num_srv_addrs);
+ if (srv_addrs == NULL) {
+ TALLOC_FREE(dcs);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_srv_addrs = 0;
+ for(i = 0; i < numdcs; i++) {
+ /* Copy all the IP addresses from the SRV response */
+ size_t j;
+ for (j = 0; j < dcs[i].num_ips; j++) {
+ char addr[INET6_ADDRSTRLEN];
+
+ srv_addrs[num_srv_addrs] = dcs[i].ss_s[j];
+ if (is_zero_addr(&srv_addrs[num_srv_addrs])) {
+ continue;
+ }
+
+ DBG_DEBUG("SRV lookup %s got IP[%zu] %s\n",
+ name,
+ j,
+ print_sockaddr(addr,
+ sizeof(addr),
+ &srv_addrs[num_srv_addrs]));
+
+ num_srv_addrs++;
+ }
+ }
+
+ TALLOC_FREE(dcs);
+
+ *return_addrs = srv_addrs;
+ *return_count = num_srv_addrs;
+ return NT_STATUS_OK;
+}
+
+static const char **filter_out_nbt_lookup(TALLOC_CTX *mem_ctx,
+ const char **resolve_order)
+{
+ size_t i, len, result_idx;
+ const char **result;
+
+ len = 0;
+ while (resolve_order[len] != NULL) {
+ len += 1;
+ }
+
+ result = talloc_array(mem_ctx, const char *, len+1);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ result_idx = 0;
+
+ for (i=0; i<len; i++) {
+ const char *tok = resolve_order[i];
+
+ if (strequal(tok, "lmhosts") || strequal(tok, "wins") ||
+ strequal(tok, "bcast")) {
+ continue;
+ }
+ result[result_idx++] = tok;
+ }
+ result[result_idx] = NULL;
+
+ return result;
+}
+
+/*******************************************************************
+ Samba interface to resolve a name into an IP address.
+ Use this function if the string is either an IP address, DNS
+ or host name or NetBIOS name. This uses the name switch in the
+ smb.conf to determine the order of name resolution.
+
+ Added support for ip addr/port to support ADS ldap servers.
+ the only place we currently care about the port is in the
+ resolve_hosts() when looking up DC's via SRV RR entries in DNS
+**********************************************************************/
+
+NTSTATUS internal_resolve_name(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type,
+ const char *sitename,
+ struct samba_sockaddr **return_salist,
+ size_t *return_count,
+ const char **resolve_order)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ size_t i;
+ size_t nc_count = 0;
+ size_t ret_count = 0;
+ bool ok;
+ struct sockaddr_storage *ss_list = NULL;
+ struct samba_sockaddr *sa_list = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ DBG_DEBUG("looking up %s#%x (sitename %s)\n",
+ name, name_type, sitename ? sitename : "(null)");
+
+ if (is_ipaddress(name)) {
+ struct sockaddr_storage ss;
+
+ /* if it's in the form of an IP address then get the lib to interpret it */
+ ok = interpret_string_addr(&ss, name, AI_NUMERICHOST);
+ if (!ok) {
+ DBG_WARNING("interpret_string_addr failed on %s\n",
+ name);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (is_zero_addr(&ss)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = sockaddr_array_to_samba_sockaddr_array(frame,
+ &sa_list,
+ &ret_count,
+ &ss,
+ 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ *return_salist = talloc_move(ctx, &sa_list);
+ *return_count = 1;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ /* Check name cache */
+
+ ok = namecache_fetch(frame,
+ name,
+ name_type,
+ &sa_list,
+ &nc_count);
+ if (ok) {
+ /*
+ * remove_duplicate_addrs2() has the
+ * side effect of removing zero addresses,
+ * so use it here.
+ */
+ nc_count = remove_duplicate_addrs2(sa_list, nc_count);
+ if (nc_count == 0) {
+ TALLOC_FREE(sa_list);
+ TALLOC_FREE(frame);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ *return_count = nc_count;
+ *return_salist = talloc_move(ctx, &sa_list);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ /* set the name resolution order */
+
+ if (resolve_order && strcmp(resolve_order[0], "NULL") == 0) {
+ DBG_DEBUG("all lookups disabled\n");
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!resolve_order || !resolve_order[0]) {
+ static const char *host_order[] = { "host", NULL };
+ resolve_order = host_order;
+ }
+
+ if ((strlen(name) > MAX_NETBIOSNAME_LEN - 1) ||
+ (strchr(name, '.') != NULL)) {
+ /*
+ * Don't do NBT lookup, the name would not fit anyway
+ */
+ resolve_order = filter_out_nbt_lookup(frame, resolve_order);
+ if (resolve_order == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* iterate through the name resolution backends */
+
+ for (i=0; resolve_order[i]; i++) {
+ const char *tok = resolve_order[i];
+
+ if ((strequal(tok, "host") || strequal(tok, "hosts"))) {
+ status = resolve_hosts(talloc_tos(),
+ name,
+ name_type,
+ &ss_list,
+ &ret_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ goto done;
+ }
+
+ if (strequal(tok, "kdc")) {
+ /* deal with KDC_NAME_TYPE names here.
+ * This will result in a SRV record lookup */
+ status = resolve_ads(talloc_tos(),
+ name,
+ KDC_NAME_TYPE,
+ sitename,
+ &ss_list,
+ &ret_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ /* Ensure we don't namecache
+ * this with the KDC port. */
+ name_type = KDC_NAME_TYPE;
+ goto done;
+ }
+
+ if (strequal(tok, "ads")) {
+ /* deal with 0x1c and 0x1b names here.
+ * This will result in a SRV record lookup */
+ status = resolve_ads(talloc_tos(),
+ name,
+ name_type,
+ sitename,
+ &ss_list,
+ &ret_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ goto done;
+ }
+
+ if (strequal(tok, "lmhosts")) {
+ status = resolve_lmhosts_file_as_sockaddr(
+ talloc_tos(),
+ get_dyn_LMHOSTSFILE(),
+ name,
+ name_type,
+ &ss_list,
+ &ret_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ goto done;
+ }
+
+ if (strequal(tok, "wins")) {
+ /* don't resolve 1D via WINS */
+ if (name_type == 0x1D) {
+ continue;
+ }
+ status = resolve_wins(talloc_tos(),
+ name,
+ name_type,
+ &ss_list,
+ &ret_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ goto done;
+ }
+
+ if (strequal(tok, "bcast")) {
+ status = name_resolve_bcast(
+ talloc_tos(),
+ name,
+ name_type,
+ &ss_list,
+ &ret_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ goto done;
+ }
+
+ DBG_ERR("unknown name switch type %s\n", tok);
+ }
+
+ /* All of the resolve_* functions above have returned false. */
+
+ TALLOC_FREE(frame);
+ *return_count = 0;
+
+ return status;
+
+ done:
+
+ status = sockaddr_array_to_samba_sockaddr_array(frame,
+ &sa_list,
+ &ret_count,
+ ss_list,
+ ret_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Remove duplicate entries. Some queries, notably #1c (domain
+ controllers) return the PDC in iplist[0] and then all domain
+ controllers including the PDC in iplist[1..n]. Iterating over
+ the iplist when the PDC is down will cause two sets of timeouts. */
+
+ ret_count = remove_duplicate_addrs2(sa_list, ret_count);
+
+ /* Save in name cache */
+ if ( DEBUGLEVEL >= 100 ) {
+ for (i = 0; i < ret_count && DEBUGLEVEL == 100; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr),
+ &sa_list[i].u.ss);
+ DEBUG(100, ("Storing name %s of type %d (%s:0)\n",
+ name,
+ name_type,
+ addr));
+ }
+ }
+
+ if (ret_count) {
+ namecache_store(name,
+ name_type,
+ ret_count,
+ sa_list);
+ }
+
+ /* Display some debugging info */
+
+ if ( DEBUGLEVEL >= 10 ) {
+ DBG_DEBUG("returning %zu addresses: ",
+ ret_count);
+
+ for (i = 0; i < ret_count; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr),
+ &sa_list[i].u.ss);
+ DEBUGADD(10, ("%s ", addr));
+ }
+ DEBUG(10, ("\n"));
+ }
+
+ *return_count = ret_count;
+ *return_salist = talloc_move(ctx, &sa_list);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/********************************************************
+ Internal interface to resolve a name into one IP address.
+ Use this function if the string is either an IP address, DNS
+ or host name or NetBIOS name. This uses the name switch in the
+ smb.conf to determine the order of name resolution.
+*********************************************************/
+
+bool resolve_name(const char *name,
+ struct sockaddr_storage *return_ss,
+ int name_type,
+ bool prefer_ipv4)
+{
+ struct samba_sockaddr *sa_list = NULL;
+ char *sitename = NULL;
+ size_t count = 0;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+
+ if (is_ipaddress(name)) {
+ return interpret_string_addr(return_ss, name, AI_NUMERICHOST);
+ }
+
+ frame = talloc_stackframe();
+
+ sitename = sitename_fetch(frame, lp_realm()); /* wild guess */
+
+ status = internal_resolve_name(frame,
+ name,
+ name_type,
+ sitename,
+ &sa_list,
+ &count,
+ lp_name_resolve_order());
+ if (NT_STATUS_IS_OK(status)) {
+ size_t i;
+
+ if (prefer_ipv4) {
+ for (i=0; i<count; i++) {
+ if (!is_broadcast_addr(&sa_list[i].u.sa) &&
+ (sa_list[i].u.ss.ss_family == AF_INET)) {
+ *return_ss = sa_list[i].u.ss;
+ TALLOC_FREE(sa_list);
+ TALLOC_FREE(frame);
+ return True;
+ }
+ }
+ }
+
+ /* only return valid addresses for TCP connections */
+ for (i=0; i<count; i++) {
+ if (!is_broadcast_addr(&sa_list[i].u.sa)) {
+ *return_ss = sa_list[i].u.ss;
+ TALLOC_FREE(sa_list);
+ TALLOC_FREE(frame);
+ return True;
+ }
+ }
+ }
+
+ TALLOC_FREE(sa_list);
+ TALLOC_FREE(frame);
+ return False;
+}
+
+/********************************************************
+ Internal interface to resolve a name into a list of IP addresses.
+ Use this function if the string is either an IP address, DNS
+ or host name or NetBIOS name. This uses the name switch in the
+ smb.conf to determine the order of name resolution.
+*********************************************************/
+
+NTSTATUS resolve_name_list(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type,
+ struct sockaddr_storage **return_ss_arr,
+ unsigned int *p_num_entries)
+{
+ struct samba_sockaddr *sa_list = NULL;
+ char *sitename = NULL;
+ size_t count = 0;
+ size_t i;
+ unsigned int num_entries = 0;
+ struct sockaddr_storage *result_arr = NULL;
+ NTSTATUS status;
+
+ if (is_ipaddress(name)) {
+ result_arr = talloc(ctx, struct sockaddr_storage);
+ if (result_arr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!interpret_string_addr(result_arr, name, AI_NUMERICHOST)) {
+ TALLOC_FREE(result_arr);
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+ *p_num_entries = 1;
+ *return_ss_arr = result_arr;
+ return NT_STATUS_OK;
+ }
+
+ sitename = sitename_fetch(ctx, lp_realm()); /* wild guess */
+
+ status = internal_resolve_name(ctx,
+ name,
+ name_type,
+ sitename,
+ &sa_list,
+ &count,
+ lp_name_resolve_order());
+ TALLOC_FREE(sitename);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* only return valid addresses for TCP connections */
+ for (i=0, num_entries = 0; i<count; i++) {
+ if (!is_zero_addr(&sa_list[i].u.ss) &&
+ !is_broadcast_addr(&sa_list[i].u.sa)) {
+ num_entries++;
+ }
+ }
+ if (num_entries == 0) {
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto done;
+ }
+
+ result_arr = talloc_array(ctx,
+ struct sockaddr_storage,
+ num_entries);
+ if (result_arr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0, num_entries = 0; i<count; i++) {
+ if (!is_zero_addr(&sa_list[i].u.ss) &&
+ !is_broadcast_addr(&sa_list[i].u.sa)) {
+ result_arr[num_entries++] = sa_list[i].u.ss;
+ }
+ }
+
+ if (num_entries == 0) {
+ TALLOC_FREE(result_arr);
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+ *p_num_entries = num_entries;
+ *return_ss_arr = result_arr;
+done:
+ TALLOC_FREE(sa_list);
+ return status;
+}
+
+/********************************************************
+ Find the IP address of the master browser or DMB for a workgroup.
+*********************************************************/
+
+bool find_master_ip(const char *group, struct sockaddr_storage *master_ss)
+{
+ struct samba_sockaddr *sa_list = NULL;
+ size_t count = 0;
+ NTSTATUS status;
+
+ if (lp_disable_netbios()) {
+ DEBUG(5,("find_master_ip(%s): netbios is disabled\n", group));
+ return false;
+ }
+
+ status = internal_resolve_name(talloc_tos(),
+ group,
+ 0x1D,
+ NULL,
+ &sa_list,
+ &count,
+ lp_name_resolve_order());
+ if (NT_STATUS_IS_OK(status)) {
+ *master_ss = sa_list[0].u.ss;
+ TALLOC_FREE(sa_list);
+ return true;
+ }
+
+ TALLOC_FREE(sa_list);
+
+ status = internal_resolve_name(talloc_tos(),
+ group,
+ 0x1B,
+ NULL,
+ &sa_list,
+ &count,
+ lp_name_resolve_order());
+ if (NT_STATUS_IS_OK(status)) {
+ *master_ss = sa_list[0].u.ss;
+ TALLOC_FREE(sa_list);
+ return true;
+ }
+
+ TALLOC_FREE(sa_list);
+ return false;
+}
+
+/********************************************************
+ Get the IP address list of the primary domain controller
+ for a domain.
+*********************************************************/
+
+bool get_pdc_ip(const char *domain, struct sockaddr_storage *pss)
+{
+ struct samba_sockaddr *sa_list = NULL;
+ size_t count = 0;
+ NTSTATUS status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ static const char *ads_order[] = { "ads", NULL };
+ /* Look up #1B name */
+
+ if (lp_security() == SEC_ADS) {
+ status = internal_resolve_name(talloc_tos(),
+ domain,
+ 0x1b,
+ NULL,
+ &sa_list,
+ &count,
+ ads_order);
+ }
+
+ if (!NT_STATUS_IS_OK(status) || count == 0) {
+ TALLOC_FREE(sa_list);
+ status = internal_resolve_name(talloc_tos(),
+ domain,
+ 0x1b,
+ NULL,
+ &sa_list,
+ &count,
+ lp_name_resolve_order());
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sa_list);
+ return false;
+ }
+ }
+
+ /* if we get more than 1 IP back we have to assume it is a
+ multi-homed PDC and not a mess up */
+
+ if ( count > 1 ) {
+ DBG_INFO("PDC has %zu IP addresses!\n", count);
+ sort_sa_list(sa_list, count);
+ }
+
+ *pss = sa_list[0].u.ss;
+ TALLOC_FREE(sa_list);
+ return true;
+}
+
+/* Private enum type for lookups. */
+
+enum dc_lookup_type { DC_NORMAL_LOOKUP, DC_ADS_ONLY, DC_KDC_ONLY };
+
+/********************************************************
+ Get the IP address list of the domain controllers for
+ a domain.
+*********************************************************/
+
+static NTSTATUS get_dc_list(TALLOC_CTX *ctx,
+ const char *domain,
+ const char *sitename,
+ struct samba_sockaddr **sa_list_ret,
+ size_t *ret_count,
+ enum dc_lookup_type lookup_type,
+ bool *ordered)
+{
+ const char **resolve_order = NULL;
+ char *saf_servername = NULL;
+ char *pserver = NULL;
+ const char *p;
+ char *name;
+ size_t num_addresses = 0;
+ size_t local_count = 0;
+ size_t i;
+ struct samba_sockaddr *auto_sa_list = NULL;
+ struct samba_sockaddr *return_salist = NULL;
+ bool done_auto_lookup = false;
+ size_t auto_count = 0;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int auto_name_type = 0x1C;
+
+ *ordered = False;
+
+ /* if we are restricted to solely using DNS for looking
+ up a domain controller, make sure that host lookups
+ are enabled for the 'name resolve order'. If host lookups
+ are disabled and ads_only is True, then set the string to
+ NULL. */
+
+ resolve_order = lp_name_resolve_order();
+ if (!resolve_order) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ if (lookup_type == DC_ADS_ONLY) {
+ if (str_list_check_ci(resolve_order, "host")) {
+ static const char *ads_order[] = { "ads", NULL };
+ resolve_order = ads_order;
+
+ /* DNS SRV lookups used by the ads resolver
+ are already sorted by priority and weight */
+ *ordered = true;
+ } else {
+ /* this is quite bizarre! */
+ static const char *null_order[] = { "NULL", NULL };
+ resolve_order = null_order;
+ }
+ } else if (lookup_type == DC_KDC_ONLY) {
+ static const char *kdc_order[] = { "kdc", NULL };
+ /* DNS SRV lookups used by the ads/kdc resolver
+ are already sorted by priority and weight */
+ *ordered = true;
+ resolve_order = kdc_order;
+ auto_name_type = KDC_NAME_TYPE;
+ }
+
+ /* fetch the server we have affinity for. Add the
+ 'password server' list to a search for our domain controllers */
+
+ saf_servername = saf_fetch(frame, domain);
+
+ if (strequal(domain, lp_workgroup()) || strequal(domain, lp_realm())) {
+ pserver = talloc_asprintf(frame, "%s, %s",
+ saf_servername ? saf_servername : "",
+ lp_password_server());
+ } else {
+ pserver = talloc_asprintf(frame, "%s, *",
+ saf_servername ? saf_servername : "");
+ }
+
+ TALLOC_FREE(saf_servername);
+ if (!pserver) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ DEBUG(3,("get_dc_list: preferred server list: \"%s\"\n", pserver ));
+
+ /*
+ * if '*' appears in the "password server" list then add
+ * an auto lookup to the list of manually configured
+ * DC's. If any DC is listed by name, then the list should be
+ * considered to be ordered
+ */
+
+ p = pserver;
+ while (next_token_talloc(frame, &p, &name, LIST_SEP)) {
+ if (!done_auto_lookup && strequal(name, "*")) {
+ done_auto_lookup = true;
+
+ status = internal_resolve_name(frame,
+ domain,
+ auto_name_type,
+ sitename,
+ &auto_sa_list,
+ &auto_count,
+ resolve_order);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ /* Wrap check. */
+ if (num_addresses + auto_count < num_addresses) {
+ TALLOC_FREE(auto_sa_list);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ num_addresses += auto_count;
+ DBG_DEBUG("Adding %zu DC's from auto lookup\n",
+ auto_count);
+ } else {
+ /* Wrap check. */
+ if (num_addresses + 1 < num_addresses) {
+ TALLOC_FREE(auto_sa_list);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ num_addresses++;
+ }
+ }
+
+ /* if we have no addresses and haven't done the auto lookup, then
+ just return the list of DC's. Or maybe we just failed. */
+
+ if (num_addresses == 0) {
+ struct samba_sockaddr *dc_salist = NULL;
+ size_t dc_count = 0;
+
+ if (done_auto_lookup) {
+ DEBUG(4,("get_dc_list: no servers found\n"));
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ goto out;
+ }
+ /* talloc off frame, only move to ctx on success. */
+ status = internal_resolve_name(frame,
+ domain,
+ auto_name_type,
+ sitename,
+ &dc_salist,
+ &dc_count,
+ resolve_order);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ return_salist = dc_salist;
+ local_count = dc_count;
+ goto out;
+ }
+
+ return_salist = talloc_zero_array(frame,
+ struct samba_sockaddr,
+ num_addresses);
+ if (return_salist == NULL) {
+ DEBUG(3,("get_dc_list: malloc fail !\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ p = pserver;
+ local_count = 0;
+
+ /* fill in the return list now with real IP's */
+
+ while ((local_count<num_addresses) &&
+ next_token_talloc(frame, &p, &name, LIST_SEP)) {
+ struct samba_sockaddr name_sa = {0};
+
+ /* copy any addresses from the auto lookup */
+
+ if (strequal(name, "*")) {
+ size_t j;
+ for (j=0; j<auto_count; j++) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr,
+ sizeof(addr),
+ &auto_sa_list[j].u.ss);
+ /* Check for and don't copy any
+ * known bad DC IP's. */
+ if(!NT_STATUS_IS_OK(check_negative_conn_cache(
+ domain,
+ addr))) {
+ DEBUG(5,("get_dc_list: "
+ "negative entry %s removed "
+ "from DC list\n",
+ addr));
+ continue;
+ }
+ return_salist[local_count] = auto_sa_list[j];
+ local_count++;
+ }
+ continue;
+ }
+
+ /* explicit lookup; resolve_name() will
+ * handle names & IP addresses */
+ if (resolve_name(name, &name_sa.u.ss, 0x20, true)) {
+ char addr[INET6_ADDRSTRLEN];
+ bool ok;
+
+ /*
+ * Ensure we set sa_socklen correctly.
+ * Doesn't matter now, but eventually we
+ * will remove ip_service and return samba_sockaddr
+ * arrays directly.
+ */
+ ok = sockaddr_storage_to_samba_sockaddr(
+ &name_sa,
+ &name_sa.u.ss);
+ if (!ok) {
+ status = NT_STATUS_INVALID_ADDRESS;
+ goto out;
+ }
+
+ print_sockaddr(addr,
+ sizeof(addr),
+ &name_sa.u.ss);
+
+ /* Check for and don't copy any known bad DC IP's. */
+ if( !NT_STATUS_IS_OK(check_negative_conn_cache(domain,
+ addr)) ) {
+ DEBUG(5,("get_dc_list: negative entry %s "
+ "removed from DC list\n",
+ name ));
+ continue;
+ }
+
+ return_salist[local_count] = name_sa;
+ local_count++;
+ *ordered = true;
+ }
+ }
+
+ /* need to remove duplicates in the list if we have any
+ explicit password servers */
+
+ local_count = remove_duplicate_addrs2(return_salist, local_count );
+
+ /* For DC's we always prioritize IPv4 due to W2K3 not
+ * supporting LDAP, KRB5 or CLDAP over IPv6. */
+
+ if (local_count && return_salist != NULL) {
+ prioritize_ipv4_list(return_salist, local_count);
+ }
+
+ if ( DEBUGLEVEL >= 4 ) {
+ DEBUG(4,("get_dc_list: returning %zu ip addresses "
+ "in an %sordered list\n",
+ local_count,
+ *ordered ? "":"un"));
+ DEBUG(4,("get_dc_list: "));
+ for ( i=0; i<local_count; i++ ) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr,
+ sizeof(addr),
+ &return_salist[i].u.ss);
+ DEBUGADD(4,("%s ", addr));
+ }
+ DEBUGADD(4,("\n"));
+ }
+
+ status = (local_count != 0 ? NT_STATUS_OK : NT_STATUS_NO_LOGON_SERVERS);
+
+ out:
+
+ if (NT_STATUS_IS_OK(status)) {
+ *sa_list_ret = talloc_move(ctx, &return_salist);
+ *ret_count = local_count;
+ }
+ TALLOC_FREE(return_salist);
+ TALLOC_FREE(auto_sa_list);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*********************************************************************
+ Small wrapper function to get the DC list and sort it if necessary.
+ Returns a samba_sockaddr array.
+*********************************************************************/
+
+NTSTATUS get_sorted_dc_list(TALLOC_CTX *ctx,
+ const char *domain,
+ const char *sitename,
+ struct samba_sockaddr **sa_list_ret,
+ size_t *ret_count,
+ bool ads_only)
+{
+ bool ordered = false;
+ NTSTATUS status;
+ enum dc_lookup_type lookup_type = DC_NORMAL_LOOKUP;
+ struct samba_sockaddr *sa_list = NULL;
+ size_t count = 0;
+
+ DBG_INFO("attempting lookup for name %s (sitename %s)\n",
+ domain,
+ sitename ? sitename : "NULL");
+
+ if (ads_only) {
+ lookup_type = DC_ADS_ONLY;
+ }
+
+ status = get_dc_list(ctx,
+ domain,
+ sitename,
+ &sa_list,
+ &count,
+ lookup_type,
+ &ordered);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_LOGON_SERVERS)
+ && sitename) {
+ DBG_WARNING("No server for domain '%s' available"
+ " in site '%s', fallback to all servers\n",
+ domain,
+ sitename);
+ status = get_dc_list(ctx,
+ domain,
+ NULL,
+ &sa_list,
+ &count,
+ lookup_type,
+ &ordered);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* only sort if we don't already have an ordered list */
+ if (!ordered) {
+ sort_sa_list(sa_list, count);
+ }
+
+ *ret_count = count;
+ *sa_list_ret = sa_list;
+ return status;
+}
+
+/*********************************************************************
+ Get the KDC list - re-use all the logic in get_dc_list.
+ Returns a samba_sockaddr array.
+*********************************************************************/
+
+NTSTATUS get_kdc_list(TALLOC_CTX *ctx,
+ const char *realm,
+ const char *sitename,
+ struct samba_sockaddr **sa_list_ret,
+ size_t *ret_count)
+{
+ size_t count = 0;
+ struct samba_sockaddr *sa_list = NULL;
+ bool ordered = false;
+ NTSTATUS status;
+
+ status = get_dc_list(ctx,
+ realm,
+ sitename,
+ &sa_list,
+ &count,
+ DC_KDC_ONLY,
+ &ordered);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* only sort if we don't already have an ordered list */
+ if (!ordered ) {
+ sort_sa_list(sa_list, count);
+ }
+
+ *ret_count = count;
+ *sa_list_ret = sa_list;
+ return status;
+}
diff --git a/source3/libsmb/namequery.h b/source3/libsmb/namequery.h
new file mode 100644
index 0000000..16559f9
--- /dev/null
+++ b/source3/libsmb/namequery.h
@@ -0,0 +1,116 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Copyright (C) Volker Lendecke 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBSMB_NAMEQUERY_H__
+#define __LIBSMB_NAMEQUERY_H__
+
+#include "includes.h"
+#include <tevent.h>
+
+/* The following definitions come from libsmb/namequery.c */
+bool saf_store( const char *domain, const char *servername );
+bool saf_join_store( const char *domain, const char *servername );
+bool saf_delete( const char *domain );
+char *saf_fetch(TALLOC_CTX *mem_ctx, const char *domain );
+struct tevent_req *node_status_query_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct nmb_name *name,
+ const struct sockaddr_storage *addr);
+NTSTATUS node_status_query_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct node_status **pnode_status,
+ size_t *pnum_names,
+ struct node_status_extra *extra);
+NTSTATUS node_status_query(TALLOC_CTX *mem_ctx, struct nmb_name *name,
+ const struct sockaddr_storage *addr,
+ struct node_status **pnode_status,
+ size_t *pnum_names,
+ struct node_status_extra *extra);
+bool name_status_find(const char *q_name,
+ int q_type,
+ int type,
+ const struct sockaddr_storage *to_ss,
+ fstring name);
+size_t remove_duplicate_addrs2(struct samba_sockaddr *salist, size_t count);
+struct tevent_req *name_query_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *name, int name_type,
+ bool bcast, bool recurse,
+ const struct sockaddr_storage *addr);
+NTSTATUS name_query_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs, size_t *num_addrs,
+ uint8_t *flags);
+NTSTATUS name_query(const char *name, int name_type,
+ bool bcast, bool recurse,
+ const struct sockaddr_storage *to_ss,
+ TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs,
+ size_t *num_addrs, uint8_t *flags);
+struct tevent_req *name_resolve_bcast_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *name,
+ int name_type);
+NTSTATUS name_resolve_bcast_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs,
+ size_t *num_addrs);
+NTSTATUS name_resolve_bcast(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int name_type,
+ struct sockaddr_storage **return_iplist,
+ size_t *return_count);
+struct tevent_req *resolve_wins_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *name,
+ int name_type);
+NTSTATUS resolve_wins_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage **addrs,
+ size_t *num_addrs, uint8_t *flags);
+NTSTATUS resolve_wins(TALLOC_CTX *mem_ctx,
+ const char *name,
+ int name_type,
+ struct sockaddr_storage **return_iplist,
+ size_t *return_count);
+NTSTATUS internal_resolve_name(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type,
+ const char *sitename,
+ struct samba_sockaddr **return_salist,
+ size_t *ret_count,
+ const char **resolve_order);
+bool resolve_name(const char *name,
+ struct sockaddr_storage *return_ss,
+ int name_type,
+ bool prefer_ipv4);
+NTSTATUS resolve_name_list(TALLOC_CTX *ctx,
+ const char *name,
+ int name_type,
+ struct sockaddr_storage **return_ss_arr,
+ unsigned int *p_num_entries);
+bool find_master_ip(const char *group, struct sockaddr_storage *master_ss);
+bool get_pdc_ip(const char *domain, struct sockaddr_storage *pss);
+NTSTATUS get_sorted_dc_list(TALLOC_CTX *ctx,
+ const char *domain,
+ const char *sitename,
+ struct samba_sockaddr **sa_list_ret,
+ size_t *ret_count,
+ bool ads_only);
+NTSTATUS get_kdc_list(TALLOC_CTX *ctx,
+ const char *realm,
+ const char *sitename,
+ struct samba_sockaddr **sa_list_ret,
+ size_t *ret_count);
+#endif
diff --git a/source3/libsmb/namequery_dc.c b/source3/libsmb/namequery_dc.c
new file mode 100644
index 0000000..06e2c50
--- /dev/null
+++ b/source3/libsmb/namequery_dc.c
@@ -0,0 +1,262 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon connection manager
+
+ Copyright (C) Tim Potter 2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Gerald Carter 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "libads/sitename_cache.h"
+#include "ads.h"
+#include "../librpc/gen_ndr/nbt.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/string_wrappers.h"
+
+/**********************************************************************
+ Is this our primary domain ?
+**********************************************************************/
+
+#ifdef HAVE_ADS
+static bool is_our_primary_domain(const char *domain)
+{
+ int role = lp_server_role();
+
+ if ((role == ROLE_DOMAIN_MEMBER) && strequal(lp_workgroup(), domain)) {
+ return True;
+ } else if (strequal(get_global_sam_name(), domain)) {
+ return True;
+ }
+ return False;
+}
+#endif
+
+/**************************************************************************
+ Find the name and IP address for a server in the realm/domain
+ *************************************************************************/
+
+static bool ads_dc_name(const char *domain,
+ const char *realm,
+ struct sockaddr_storage *dc_ss,
+ fstring srv_name)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ bool ok = false;
+ ADS_STRUCT *ads;
+ char *sitename;
+ int i;
+ char addr[INET6_ADDRSTRLEN];
+
+ if (!realm && strequal(domain, lp_workgroup())) {
+ realm = lp_realm();
+ }
+
+ sitename = sitename_fetch(tmp_ctx, realm);
+
+ /* Try this 3 times then give up. */
+ for( i =0 ; i < 3; i++) {
+ ads = ads_init(tmp_ctx, realm, domain, NULL, ADS_SASL_PLAIN);
+ if (!ads) {
+ ok = false;
+ goto out;
+ }
+
+ DEBUG(4,("ads_dc_name: domain=%s\n", domain));
+
+#ifdef HAVE_ADS
+ /* we don't need to bind, just connect */
+ ads->auth.flags |= ADS_AUTH_NO_BIND;
+ ads_connect(ads);
+#endif
+
+ if (!ads->config.realm) {
+ ok = false;
+ goto out;
+ }
+
+ /* Now we've found a server, see if our sitename
+ has changed. If so, we need to re-do the DNS query
+ to ensure we only find servers in our site. */
+
+ if (stored_sitename_changed(realm, sitename)) {
+ sitename = sitename_fetch(tmp_ctx, realm);
+ TALLOC_FREE(ads);
+ /* Ensure we don't cache the DC we just connected to. */
+ namecache_delete(realm, 0x1C);
+ namecache_delete(domain, 0x1C);
+ continue;
+ }
+
+#ifdef HAVE_ADS
+ if (is_our_primary_domain(domain) && (ads->config.flags & NBT_SERVER_KDC)) {
+ if (ads_closest_dc(ads)) {
+ /* We're going to use this KDC for this realm/domain.
+ If we are using sites, then force the krb5 libs
+ to use this KDC. */
+
+ create_local_private_krb5_conf_for_domain(realm,
+ domain,
+ sitename,
+ &ads->ldap.ss);
+ } else {
+ create_local_private_krb5_conf_for_domain(realm,
+ domain,
+ NULL,
+ &ads->ldap.ss);
+ }
+ }
+#endif
+ break;
+ }
+
+ if (i == 3) {
+ DEBUG(1,("ads_dc_name: sitename (now \"%s\") keeps changing ???\n",
+ sitename ? sitename : ""));
+ ok = false;
+ goto out;
+ }
+
+ fstrcpy(srv_name, ads->config.ldap_server_name);
+ if (!strupper_m(srv_name)) {
+ ok = false;
+ goto out;
+ }
+#ifdef HAVE_ADS
+ *dc_ss = ads->ldap.ss;
+#else
+ zero_sockaddr(dc_ss);
+#endif
+ print_sockaddr(addr, sizeof(addr), dc_ss);
+ DEBUG(4,("ads_dc_name: using server='%s' IP=%s\n",
+ srv_name, addr));
+
+ ok = true;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return ok;
+}
+
+/****************************************************************************
+ Utility function to return the name of a DC. The name is guaranteed to be
+ valid since we have already done a name_status_find on it
+ ***************************************************************************/
+
+static bool rpc_dc_name(const char *domain,
+ fstring srv_name,
+ struct sockaddr_storage *ss_out)
+{
+ struct samba_sockaddr *sa_list = NULL;
+ size_t count = 0;
+ struct sockaddr_storage dc_ss;
+ size_t i;
+ NTSTATUS result;
+ char addr[INET6_ADDRSTRLEN];
+
+ /* get a list of all domain controllers */
+
+ result = get_sorted_dc_list(talloc_tos(),
+ domain,
+ NULL,
+ &sa_list,
+ &count,
+ false);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3, ("Could not look up dc's for domain %s\n", domain));
+ return False;
+ }
+
+ /* Remove the entry we've already failed with (should be the PDC). */
+
+ for (i = 0; i < count; i++) {
+ if (is_zero_addr(&sa_list[i].u.ss))
+ continue;
+
+ if (name_status_find(domain, 0x1c, 0x20, &sa_list[i].u.ss, srv_name)) {
+ result = check_negative_conn_cache( domain, srv_name );
+ if ( NT_STATUS_IS_OK(result) ) {
+ dc_ss = sa_list[i].u.ss;
+ goto done;
+ }
+ }
+ }
+
+ TALLOC_FREE(sa_list);
+
+ /* No-one to talk to )-: */
+ return False; /* Boo-hoo */
+
+ done:
+ /* We have the netbios name and IP address of a domain controller.
+ Ideally we should sent a SAMLOGON request to determine whether
+ the DC is alive and kicking. If we can catch a dead DC before
+ performing a cli_connect() we can avoid a 30-second timeout. */
+
+ print_sockaddr(addr, sizeof(addr), &dc_ss);
+ DEBUG(3, ("rpc_dc_name: Returning DC %s (%s) for domain %s\n", srv_name,
+ addr, domain));
+
+ *ss_out = dc_ss;
+ TALLOC_FREE(sa_list);
+
+ return True;
+}
+
+/**********************************************************************
+ wrapper around ads and rpc methods of finds DC's
+**********************************************************************/
+
+bool get_dc_name(const char *domain,
+ const char *realm,
+ fstring srv_name,
+ struct sockaddr_storage *ss_out)
+{
+ struct sockaddr_storage dc_ss;
+ bool ret;
+ bool our_domain = False;
+
+ zero_sockaddr(&dc_ss);
+
+ ret = False;
+
+ if ( strequal(lp_workgroup(), domain) || strequal(lp_realm(), realm) )
+ our_domain = True;
+
+ /* always try to obey what the admin specified in smb.conf
+ (for the local domain) */
+
+ if ( (our_domain && lp_security()==SEC_ADS) || realm ) {
+ ret = ads_dc_name(domain, realm, &dc_ss, srv_name);
+ }
+
+ if (!domain) {
+ /* if we have only the realm we can't do anything else */
+ return False;
+ }
+
+ if (!ret) {
+ /* fall back on rpc methods if the ADS methods fail */
+ ret = rpc_dc_name(domain, srv_name, &dc_ss);
+ }
+
+ *ss_out = dc_ss;
+
+ return ret;
+}
diff --git a/source3/libsmb/nmblib.c b/source3/libsmb/nmblib.c
new file mode 100644
index 0000000..c90e92e
--- /dev/null
+++ b/source3/libsmb/nmblib.c
@@ -0,0 +1,1466 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios library routines
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "libsmb/nmblib.h"
+#include "lib/util/string_wrappers.h"
+
+static const struct opcode_names {
+ const char *nmb_opcode_name;
+ int opcode;
+} nmb_header_opcode_names[] = {
+ {"Query", 0 },
+ {"Registration", 5 },
+ {"Release", 6 },
+ {"WACK", 7 },
+ {"Refresh", 8 },
+ {"Refresh(altcode)", 9 },
+ {"Multi-homed Registration", 15 },
+ {0, -1 }
+};
+
+/****************************************************************************
+ Lookup a nmb opcode name.
+****************************************************************************/
+
+static const char *lookup_opcode_name( int opcode )
+{
+ const struct opcode_names *op_namep;
+ int i;
+
+ for(i = 0; nmb_header_opcode_names[i].nmb_opcode_name != 0; i++) {
+ op_namep = &nmb_header_opcode_names[i];
+ if(opcode == op_namep->opcode)
+ return op_namep->nmb_opcode_name;
+ }
+ return "<unknown opcode>";
+}
+
+/****************************************************************************
+ Print out a res_rec structure.
+****************************************************************************/
+
+static void debug_nmb_res_rec(struct res_rec *res, const char *hdr)
+{
+ int i, j;
+
+ DEBUGADD( 4, ( " %s: nmb_name=%s rr_type=%d rr_class=%d ttl=%d\n",
+ hdr,
+ nmb_namestr(&res->rr_name),
+ res->rr_type,
+ res->rr_class,
+ res->ttl ) );
+
+ if (res->rdlength == 0) {
+ return;
+ }
+
+ for (i = 0; i < res->rdlength; i+= MAX_NETBIOSNAME_LEN) {
+ DEBUGADD(4, (" %s %3x char ", hdr, i));
+
+ for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) {
+ unsigned char x = res->rdata[i+j];
+ if (x < 32 || x > 127)
+ x = '.';
+
+ if (i+j >= res->rdlength)
+ break;
+ DEBUGADD(4, ("%c", x));
+ }
+
+ DEBUGADD(4, (" hex "));
+
+ for (j = 0; j < MAX_NETBIOSNAME_LEN; j++) {
+ if (i+j >= res->rdlength)
+ break;
+ DEBUGADD(4, ("%02X", (unsigned char)res->rdata[i+j]));
+ }
+
+ DEBUGADD(4, ("\n"));
+ }
+}
+
+/****************************************************************************
+ Process a nmb packet.
+****************************************************************************/
+
+void debug_nmb_packet(struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+
+ if( DEBUGLVL( 4 ) ) {
+ dbgtext( "nmb packet from %s(%d) header: id=%d "
+ "opcode=%s(%d) response=%s\n",
+ inet_ntoa(p->ip), p->port,
+ nmb->header.name_trn_id,
+ lookup_opcode_name(nmb->header.opcode),
+ nmb->header.opcode,
+ BOOLSTR(nmb->header.response) );
+ dbgtext( " header: flags: bcast=%s rec_avail=%s "
+ "rec_des=%s trunc=%s auth=%s\n",
+ BOOLSTR(nmb->header.nm_flags.bcast),
+ BOOLSTR(nmb->header.nm_flags.recursion_available),
+ BOOLSTR(nmb->header.nm_flags.recursion_desired),
+ BOOLSTR(nmb->header.nm_flags.trunc),
+ BOOLSTR(nmb->header.nm_flags.authoritative) );
+ dbgtext( " header: rcode=%d qdcount=%d ancount=%d "
+ "nscount=%d arcount=%d\n",
+ nmb->header.rcode,
+ nmb->header.qdcount,
+ nmb->header.ancount,
+ nmb->header.nscount,
+ nmb->header.arcount );
+ }
+
+ if (nmb->header.qdcount) {
+ DEBUGADD( 4, ( " question: q_name=%s q_type=%d q_class=%d\n",
+ nmb_namestr(&nmb->question.question_name),
+ nmb->question.question_type,
+ nmb->question.question_class) );
+ }
+
+ if (nmb->answers && nmb->header.ancount) {
+ debug_nmb_res_rec(nmb->answers,"answers");
+ }
+ if (nmb->nsrecs && nmb->header.nscount) {
+ debug_nmb_res_rec(nmb->nsrecs,"nsrecs");
+ }
+ if (nmb->additional && nmb->header.arcount) {
+ debug_nmb_res_rec(nmb->additional,"additional");
+ }
+}
+
+/*******************************************************************
+ Handle "compressed" name pointers.
+******************************************************************/
+
+static bool handle_name_ptrs(unsigned char *ubuf,int *offset,int length,
+ bool *got_pointer,int *ret)
+{
+ int loop_count=0;
+
+ while ((ubuf[*offset] & 0xC0) == 0xC0) {
+ if (!*got_pointer)
+ (*ret) += 2;
+ (*got_pointer)=True;
+ if (*offset > length - 2) {
+ return False;
+ }
+ (*offset) = ((ubuf[*offset] & ~0xC0)<<8) | ubuf[(*offset)+1];
+ if (loop_count++ == 10 ||
+ (*offset) < 0 || (*offset)>(length-2)) {
+ return False;
+ }
+ }
+ return True;
+}
+
+/*******************************************************************
+ Parse a nmb name from "compressed" format to something readable
+ return the space taken by the name, or 0 if the name is invalid
+******************************************************************/
+
+static int parse_nmb_name(char *inbuf,int ofs,int length, struct nmb_name *name)
+{
+ size_t m,n=0;
+ unsigned char *ubuf = (unsigned char *)inbuf;
+ int ret = 0;
+ bool got_pointer=False;
+ size_t loop_count=0;
+ int offset = ofs;
+
+ if (length - offset < 2)
+ return(0);
+
+ /* handle initial name pointers */
+ if (!handle_name_ptrs(ubuf,&offset,length,&got_pointer,&ret))
+ return(0);
+
+ m = ubuf[offset];
+
+ /* m must be 32 to exactly fill in the 16 bytes of the netbios name */
+ if (m != 32) {
+ return 0;
+ }
+ /* Cannot go past length. */
+ if (offset+m+2 > length) {
+ return 0;
+ }
+
+ memset((char *)name,'\0',sizeof(*name));
+
+ /* the "compressed" part */
+ if (!got_pointer)
+ ret += m + 2;
+ offset++;
+ while (m > 0) {
+ unsigned char c1,c2;
+ c1 = ubuf[offset++]-'A';
+ c2 = ubuf[offset++]-'A';
+ if ((c1 & 0xF0) || (c2 & 0xF0)) {
+ return(0);
+ }
+ if (n >= sizeof(name->name)) {
+ return 0;
+ }
+ name->name[n++] = (c1<<4) | c2;
+ m -= 2;
+ }
+ /*
+ * RFC1002: For a valid NetBIOS name, exiting from the above,
+ * n *must* be MAX_NETBIOSNAME_LEN (16).
+ */
+ if (n != MAX_NETBIOSNAME_LEN) {
+ return 0;
+ }
+
+ /* parse out the name type, its always
+ * in the 16th byte of the name */
+ name->name_type = ((unsigned char)name->name[15]) & 0xff;
+
+ /* remove trailing spaces */
+ name->name[15] = 0;
+ n = 14;
+ while (n && name->name[n]==' ')
+ name->name[n--] = 0;
+
+ /* now the domain parts (if any) */
+ n = 0;
+ while (ubuf[offset]) {
+ /* we can have pointers within the domain part as well */
+ if (!handle_name_ptrs(ubuf,&offset,length,&got_pointer,&ret))
+ return(0);
+
+ m = ubuf[offset];
+ /*
+ * Don't allow null domain parts.
+ */
+ if (!m)
+ return(0);
+ if (!got_pointer)
+ ret += m+1;
+ if (n)
+ name->scope[n++] = '.';
+ if (m+2+offset>length || n+m+1>sizeof(name->scope))
+ return(0);
+ offset++;
+ while (m--)
+ name->scope[n++] = (char)ubuf[offset++];
+
+ /*
+ * Watch for malicious loops.
+ */
+ if (loop_count++ == 10)
+ return 0;
+ }
+ name->scope[n++] = 0;
+
+ return(ret);
+}
+
+/****************************************************************************
+ Put a netbios name, padding(s) and a name type into a 16 character buffer.
+ name is already in DOS charset.
+ [15 bytes name + padding][1 byte name type].
+****************************************************************************/
+
+void put_name(char *dest, const char *name, int pad, unsigned int name_type)
+{
+ size_t len = strlen(name);
+
+ memcpy(dest, name, (len < MAX_NETBIOSNAME_LEN) ?
+ len : MAX_NETBIOSNAME_LEN - 1);
+ if (len < MAX_NETBIOSNAME_LEN - 1) {
+ memset(dest + len, pad, MAX_NETBIOSNAME_LEN - 1 - len);
+ }
+ dest[MAX_NETBIOSNAME_LEN - 1] = name_type;
+}
+
+/*******************************************************************
+ Put a compressed nmb name into a buffer. Return the length of the
+ compressed name.
+
+ Compressed names are really weird. The "compression" doubles the
+ size. The idea is that it also means that compressed names conform
+ to the domain name system. See RFC1002.
+
+ If buf == NULL this is a length calculation.
+******************************************************************/
+
+static int put_nmb_name(char *buf, size_t buflen, int offset,struct nmb_name *name)
+{
+ int ret,m;
+ nstring buf1;
+ char *p;
+
+ if (strcmp(name->name,"*") == 0) {
+ /* special case for wildcard name */
+ put_name(buf1, "*", '\0', name->name_type);
+ } else {
+ put_name(buf1, name->name, ' ', name->name_type);
+ }
+
+ if (buf) {
+ if (offset >= buflen) {
+ return 0;
+ }
+ buf[offset] = 0x20;
+ }
+
+ ret = 34;
+
+ for (m=0;m<MAX_NETBIOSNAME_LEN;m++) {
+ if (buf) {
+ if (offset+2+2*m >= buflen) {
+ return 0;
+ }
+ buf[offset+1+2*m] = 'A' + ((buf1[m]>>4)&0xF);
+ buf[offset+2+2*m] = 'A' + (buf1[m]&0xF);
+ }
+ }
+ offset += 33;
+
+ if (buf) {
+ if (offset >= buflen) {
+ return 0;
+ }
+ buf[offset] = 0;
+ }
+
+ if (name->scope[0]) {
+ /* XXXX this scope handling needs testing */
+ size_t scopenamelen = strlen(name->scope) + 1;
+ ret += scopenamelen;
+ if (buf) {
+ if (offset+1+scopenamelen >= buflen) {
+ return 0;
+ }
+ strlcpy(&buf[offset+1],name->scope,
+ buflen - (offset+1));
+
+ p = &buf[offset+1];
+ while ((p = strchr_m(p,'.'))) {
+ buf[offset] = PTR_DIFF(p,&buf[offset+1]);
+ offset += (buf[offset] + 1);
+ if (offset+1 >= buflen) {
+ return 0;
+ }
+ p = &buf[offset+1];
+ }
+ buf[offset] = strlen(&buf[offset+1]);
+ }
+ }
+
+ return ret;
+}
+
+/*******************************************************************
+ Useful for debugging messages.
+******************************************************************/
+
+char *nmb_namestr(const struct nmb_name *n)
+{
+ fstring name;
+ char *result;
+
+ pull_ascii_fstring(name, n->name);
+ if (!n->scope[0])
+ result = talloc_asprintf(talloc_tos(), "%s<%02x>", name,
+ n->name_type);
+ else
+ result = talloc_asprintf(talloc_tos(), "%s<%02x>.%s", name,
+ n->name_type, n->scope);
+
+ SMB_ASSERT(result != NULL);
+ return result;
+}
+
+/*******************************************************************
+ Allocate and parse some resource records.
+******************************************************************/
+
+static bool parse_alloc_res_rec(char *inbuf,int *offset,int length,
+ struct res_rec **recs, int count)
+{
+ int i;
+
+ *recs = SMB_MALLOC_ARRAY(struct res_rec, count);
+ if (!*recs)
+ return(False);
+
+ memset((char *)*recs,'\0',sizeof(**recs)*count);
+
+ for (i=0;i<count;i++) {
+ int l = parse_nmb_name(inbuf,*offset,length,
+ &(*recs)[i].rr_name);
+ (*offset) += l;
+ if (!l || (*offset)+10 > length) {
+ SAFE_FREE(*recs);
+ return(False);
+ }
+ (*recs)[i].rr_type = RSVAL(inbuf,(*offset));
+ (*recs)[i].rr_class = RSVAL(inbuf,(*offset)+2);
+ (*recs)[i].ttl = RIVAL(inbuf,(*offset)+4);
+ (*recs)[i].rdlength = RSVAL(inbuf,(*offset)+8);
+ (*offset) += 10;
+ if ((*recs)[i].rdlength>sizeof((*recs)[i].rdata) ||
+ (*offset)+(*recs)[i].rdlength > length) {
+ SAFE_FREE(*recs);
+ return(False);
+ }
+ memcpy((*recs)[i].rdata,inbuf+(*offset),(*recs)[i].rdlength);
+ (*offset) += (*recs)[i].rdlength;
+ }
+ return(True);
+}
+
+/*******************************************************************
+ Put a resource record into a packet.
+ If buf == NULL this is a length calculation.
+******************************************************************/
+
+static int put_res_rec(char *buf, size_t buflen, int offset,struct res_rec *recs,int count)
+{
+ int ret=0;
+ int i;
+
+ for (i=0;i<count;i++) {
+ int l = put_nmb_name(buf,buflen,offset,&recs[i].rr_name);
+ offset += l;
+ ret += l;
+ if (buf) {
+ RSSVAL(buf,offset,recs[i].rr_type);
+ RSSVAL(buf,offset+2,recs[i].rr_class);
+ RSIVAL(buf,offset+4,(unsigned int)recs[i].ttl);
+ RSSVAL(buf,offset+8,recs[i].rdlength);
+ memcpy(buf+offset+10,recs[i].rdata,recs[i].rdlength);
+ }
+ offset += 10+recs[i].rdlength;
+ ret += 10+recs[i].rdlength;
+ }
+
+ return ret;
+}
+
+/*******************************************************************
+ Put a compressed name pointer record into a packet.
+ If buf == NULL this is a length calculation.
+******************************************************************/
+
+static int put_compressed_name_ptr(unsigned char *buf,
+ int offset,
+ struct res_rec *rec,
+ int ptr_offset)
+{
+ int ret=offset;
+ if (buf) {
+ buf[offset] = (0xC0 | ((ptr_offset >> 8) & 0xFF));
+ buf[offset+1] = (ptr_offset & 0xFF);
+ }
+ offset += 2;
+ if (buf) {
+ RSSVAL(buf,offset,rec->rr_type);
+ RSSVAL(buf,offset+2,rec->rr_class);
+ RSIVAL(buf,offset+4,rec->ttl);
+ RSSVAL(buf,offset+8,rec->rdlength);
+ memcpy(buf+offset+10,rec->rdata,rec->rdlength);
+ }
+ offset += 10+rec->rdlength;
+ ret = (offset - ret);
+
+ return ret;
+}
+
+/*******************************************************************
+ Parse a dgram packet. Return False if the packet can't be parsed
+ or is invalid for some reason, True otherwise.
+
+ This is documented in section 4.4.1 of RFC1002.
+******************************************************************/
+
+static bool parse_dgram(char *inbuf,int length,struct dgram_packet *dgram)
+{
+ size_t offset;
+ int flags;
+
+ memset((char *)dgram,'\0',sizeof(*dgram));
+
+ if (length < 14)
+ return(False);
+
+ dgram->header.msg_type = CVAL(inbuf,0);
+ flags = CVAL(inbuf,1);
+ dgram->header.flags.node_type = (enum node_type)((flags>>2)&3);
+ if (flags & 1)
+ dgram->header.flags.more = True;
+ if (flags & 2)
+ dgram->header.flags.first = True;
+ dgram->header.dgm_id = RSVAL(inbuf,2);
+ putip((char *)&dgram->header.source_ip,inbuf+4);
+ dgram->header.source_port = RSVAL(inbuf,8);
+ dgram->header.dgm_length = RSVAL(inbuf,10);
+ dgram->header.packet_offset = RSVAL(inbuf,12);
+
+ offset = 14;
+
+ if (dgram->header.msg_type == 0x10 ||
+ dgram->header.msg_type == 0x11 ||
+ dgram->header.msg_type == 0x12) {
+ offset += parse_nmb_name(inbuf,offset,length,
+ &dgram->source_name);
+ offset += parse_nmb_name(inbuf,offset,length,
+ &dgram->dest_name);
+ }
+
+ if (offset >= length || (length-offset > sizeof(dgram->data)))
+ return(False);
+
+ dgram->datasize = length-offset;
+ memcpy(dgram->data,inbuf+offset,dgram->datasize);
+
+ /* Paranioa. Ensure the last 2 bytes in the dgram buffer are
+ zero. This should be true anyway, just enforce it for
+ paranioa sake. JRA. */
+ SMB_ASSERT(dgram->datasize <= (sizeof(dgram->data)-2));
+ memset(&dgram->data[sizeof(dgram->data)-2], '\0', 2);
+
+ return(True);
+}
+
+/*******************************************************************
+ Parse a nmb packet. Return False if the packet can't be parsed
+ or is invalid for some reason, True otherwise.
+******************************************************************/
+
+static bool parse_nmb(char *inbuf,int length,struct nmb_packet *nmb)
+{
+ int nm_flags,offset;
+
+ memset((char *)nmb,'\0',sizeof(*nmb));
+
+ if (length < 12)
+ return(False);
+
+ /* parse the header */
+ nmb->header.name_trn_id = RSVAL(inbuf,0);
+
+ DEBUG(10,("parse_nmb: packet id = %d\n", nmb->header.name_trn_id));
+
+ nmb->header.opcode = (CVAL(inbuf,2) >> 3) & 0xF;
+ nmb->header.response = ((CVAL(inbuf,2)>>7)&1)?True:False;
+ nm_flags = ((CVAL(inbuf,2) & 0x7) << 4) + (CVAL(inbuf,3)>>4);
+ nmb->header.nm_flags.bcast = (nm_flags&1)?True:False;
+ nmb->header.nm_flags.recursion_available = (nm_flags&8)?True:False;
+ nmb->header.nm_flags.recursion_desired = (nm_flags&0x10)?True:False;
+ nmb->header.nm_flags.trunc = (nm_flags&0x20)?True:False;
+ nmb->header.nm_flags.authoritative = (nm_flags&0x40)?True:False;
+ nmb->header.rcode = CVAL(inbuf,3) & 0xF;
+ nmb->header.qdcount = RSVAL(inbuf,4);
+ nmb->header.ancount = RSVAL(inbuf,6);
+ nmb->header.nscount = RSVAL(inbuf,8);
+ nmb->header.arcount = RSVAL(inbuf,10);
+
+ if (nmb->header.qdcount) {
+ offset = parse_nmb_name(inbuf,12,length,
+ &nmb->question.question_name);
+ if (!offset)
+ return(False);
+
+ if (length - (12+offset) < 4)
+ return(False);
+ nmb->question.question_type = RSVAL(inbuf,12+offset);
+ nmb->question.question_class = RSVAL(inbuf,12+offset+2);
+
+ offset += 12+4;
+ } else {
+ offset = 12;
+ }
+
+ /* and any resource records */
+ if (nmb->header.ancount &&
+ !parse_alloc_res_rec(inbuf,&offset,length,&nmb->answers,
+ nmb->header.ancount))
+ return(False);
+
+ if (nmb->header.nscount &&
+ !parse_alloc_res_rec(inbuf,&offset,length,&nmb->nsrecs,
+ nmb->header.nscount))
+ return(False);
+
+ if (nmb->header.arcount &&
+ !parse_alloc_res_rec(inbuf,&offset,length,
+ &nmb->additional, nmb->header.arcount))
+ return(False);
+
+ return(True);
+}
+
+/*******************************************************************
+ 'Copy constructor' for an nmb packet.
+******************************************************************/
+
+static struct packet_struct *copy_nmb_packet(struct packet_struct *packet)
+{
+ struct nmb_packet *nmb;
+ struct nmb_packet *copy_nmb;
+ struct packet_struct *pkt_copy;
+
+ if(( pkt_copy = SMB_MALLOC_P(struct packet_struct)) == NULL) {
+ DEBUG(0,("copy_nmb_packet: malloc fail.\n"));
+ return NULL;
+ }
+
+ /* Structure copy of entire thing. */
+
+ *pkt_copy = *packet;
+
+ /* Ensure this copy is not locked. */
+ pkt_copy->locked = False;
+ pkt_copy->recv_fd = -1;
+ pkt_copy->send_fd = -1;
+
+ /* Ensure this copy has no resource records. */
+ nmb = &packet->packet.nmb;
+ copy_nmb = &pkt_copy->packet.nmb;
+
+ copy_nmb->answers = NULL;
+ copy_nmb->nsrecs = NULL;
+ copy_nmb->additional = NULL;
+
+ /* Now copy any resource records. */
+
+ if (nmb->answers) {
+ if((copy_nmb->answers = SMB_MALLOC_ARRAY(
+ struct res_rec,nmb->header.ancount)) == NULL)
+ goto free_and_exit;
+ memcpy((char *)copy_nmb->answers, (char *)nmb->answers,
+ nmb->header.ancount * sizeof(struct res_rec));
+ }
+ if (nmb->nsrecs) {
+ if((copy_nmb->nsrecs = SMB_MALLOC_ARRAY(
+ struct res_rec, nmb->header.nscount)) == NULL)
+ goto free_and_exit;
+ memcpy((char *)copy_nmb->nsrecs, (char *)nmb->nsrecs,
+ nmb->header.nscount * sizeof(struct res_rec));
+ }
+ if (nmb->additional) {
+ if((copy_nmb->additional = SMB_MALLOC_ARRAY(
+ struct res_rec, nmb->header.arcount)) == NULL)
+ goto free_and_exit;
+ memcpy((char *)copy_nmb->additional, (char *)nmb->additional,
+ nmb->header.arcount * sizeof(struct res_rec));
+ }
+
+ return pkt_copy;
+
+ free_and_exit:
+
+ SAFE_FREE(copy_nmb->answers);
+ SAFE_FREE(copy_nmb->nsrecs);
+ SAFE_FREE(copy_nmb->additional);
+ SAFE_FREE(pkt_copy);
+
+ DEBUG(0,("copy_nmb_packet: malloc fail in resource records.\n"));
+ return NULL;
+}
+
+/*******************************************************************
+ 'Copy constructor' for a dgram packet.
+******************************************************************/
+
+static struct packet_struct *copy_dgram_packet(struct packet_struct *packet)
+{
+ struct packet_struct *pkt_copy;
+
+ if(( pkt_copy = SMB_MALLOC_P(struct packet_struct)) == NULL) {
+ DEBUG(0,("copy_dgram_packet: malloc fail.\n"));
+ return NULL;
+ }
+
+ /* Structure copy of entire thing. */
+
+ *pkt_copy = *packet;
+
+ /* Ensure this copy is not locked. */
+ pkt_copy->locked = False;
+ pkt_copy->recv_fd = -1;
+ pkt_copy->send_fd = -1;
+
+ /* There are no additional pointers in a dgram packet,
+ we are finished. */
+ return pkt_copy;
+}
+
+/*******************************************************************
+ 'Copy constructor' for a generic packet.
+******************************************************************/
+
+struct packet_struct *copy_packet(struct packet_struct *packet)
+{
+ if(packet->packet_type == NMB_PACKET)
+ return copy_nmb_packet(packet);
+ else if (packet->packet_type == DGRAM_PACKET)
+ return copy_dgram_packet(packet);
+ return NULL;
+}
+
+/*******************************************************************
+ Free up any resources associated with an nmb packet.
+******************************************************************/
+
+static void free_nmb_packet(struct nmb_packet *nmb)
+{
+ SAFE_FREE(nmb->answers);
+ SAFE_FREE(nmb->nsrecs);
+ SAFE_FREE(nmb->additional);
+}
+
+/*******************************************************************
+ Free up any resources associated with a dgram packet.
+******************************************************************/
+
+static void free_dgram_packet(struct dgram_packet *nmb)
+{
+ /* We have nothing to do for a dgram packet. */
+}
+
+/*******************************************************************
+ Free up any resources associated with a packet.
+******************************************************************/
+
+void free_packet(struct packet_struct *packet)
+{
+ if (packet->locked)
+ return;
+ if (packet->packet_type == NMB_PACKET)
+ free_nmb_packet(&packet->packet.nmb);
+ else if (packet->packet_type == DGRAM_PACKET)
+ free_dgram_packet(&packet->packet.dgram);
+ ZERO_STRUCTPN(packet);
+ SAFE_FREE(packet);
+}
+
+int packet_trn_id(struct packet_struct *p)
+{
+ int result;
+ switch (p->packet_type) {
+ case NMB_PACKET:
+ result = p->packet.nmb.header.name_trn_id;
+ break;
+ case DGRAM_PACKET:
+ result = p->packet.dgram.header.dgm_id;
+ break;
+ default:
+ result = -1;
+ }
+ return result;
+}
+
+/*******************************************************************
+ Parse a packet buffer into a packet structure.
+******************************************************************/
+
+struct packet_struct *parse_packet(char *buf,int length,
+ enum packet_type packet_type,
+ struct in_addr ip,
+ int port)
+{
+ struct packet_struct *p;
+ bool ok=False;
+
+ p = SMB_MALLOC_P(struct packet_struct);
+ if (!p)
+ return(NULL);
+
+ ZERO_STRUCTP(p); /* initialize for possible padding */
+
+ p->next = NULL;
+ p->prev = NULL;
+ p->ip = ip;
+ p->port = port;
+ p->locked = False;
+ p->timestamp = time(NULL);
+ p->packet_type = packet_type;
+
+ switch (packet_type) {
+ case NMB_PACKET:
+ ok = parse_nmb(buf,length,&p->packet.nmb);
+ break;
+
+ case DGRAM_PACKET:
+ ok = parse_dgram(buf,length,&p->packet.dgram);
+ break;
+ }
+
+ if (!ok) {
+ free_packet(p);
+ return NULL;
+ }
+
+ return p;
+}
+
+static struct packet_struct *copy_packet_talloc(
+ TALLOC_CTX *mem_ctx, const struct packet_struct *src)
+{
+ struct packet_struct *pkt;
+
+ pkt = talloc_memdup(mem_ctx, src, sizeof(struct packet_struct));
+ if (pkt == NULL) {
+ return NULL;
+ }
+ pkt->locked = false;
+ pkt->recv_fd = -1;
+ pkt->send_fd = -1;
+
+ if (src->packet_type == NMB_PACKET) {
+ const struct nmb_packet *nsrc = &src->packet.nmb;
+ struct nmb_packet *ndst = &pkt->packet.nmb;
+
+ if (nsrc->answers != NULL) {
+ ndst->answers = talloc_memdup(
+ pkt, nsrc->answers,
+ sizeof(struct res_rec) * nsrc->header.ancount);
+ if (ndst->answers == NULL) {
+ goto fail;
+ }
+ }
+ if (nsrc->nsrecs != NULL) {
+ ndst->nsrecs = talloc_memdup(
+ pkt, nsrc->nsrecs,
+ sizeof(struct res_rec) * nsrc->header.nscount);
+ if (ndst->nsrecs == NULL) {
+ goto fail;
+ }
+ }
+ if (nsrc->additional != NULL) {
+ ndst->additional = talloc_memdup(
+ pkt, nsrc->additional,
+ sizeof(struct res_rec) * nsrc->header.arcount);
+ if (ndst->additional == NULL) {
+ goto fail;
+ }
+ }
+ }
+
+ return pkt;
+
+ /*
+ * DGRAM packets have no substructures
+ */
+
+fail:
+ TALLOC_FREE(pkt);
+ return NULL;
+}
+
+struct packet_struct *parse_packet_talloc(TALLOC_CTX *mem_ctx,
+ char *buf,int length,
+ enum packet_type packet_type,
+ struct in_addr ip,
+ int port)
+{
+ struct packet_struct *pkt, *result;
+
+ pkt = parse_packet(buf, length, packet_type, ip, port);
+ if (pkt == NULL) {
+ return NULL;
+ }
+ result = copy_packet_talloc(mem_ctx, pkt);
+ free_packet(pkt);
+ return result;
+}
+
+/*******************************************************************
+ Send a udp packet on a already open socket.
+******************************************************************/
+
+static bool send_udp(int fd,char *buf,int len,struct in_addr ip,int port)
+{
+ bool ret = False;
+ int i;
+ struct sockaddr_in sock_out;
+
+ /* set the address and port */
+ memset((char *)&sock_out,'\0',sizeof(sock_out));
+ putip((char *)&sock_out.sin_addr,(char *)&ip);
+ sock_out.sin_port = htons( port );
+ sock_out.sin_family = AF_INET;
+
+ DEBUG( 5, ( "Sending a packet of len %d to (%s) on port %d\n",
+ len, inet_ntoa(ip), port ) );
+
+ /*
+ * Patch to fix asynch error notifications from Linux kernel.
+ */
+
+ for (i = 0; i < 5; i++) {
+ ret = (sendto(fd,buf,len,0,(struct sockaddr *)&sock_out,
+ sizeof(sock_out)) >= 0);
+ if (ret || errno != ECONNREFUSED)
+ break;
+ }
+
+ if (!ret)
+ DEBUG(0,("Packet send failed to %s(%d) ERRNO=%s\n",
+ inet_ntoa(ip),port,strerror(errno)));
+
+ return(ret);
+}
+
+/*******************************************************************
+ Build a dgram packet ready for sending.
+ If buf == NULL this is a length calculation.
+******************************************************************/
+
+static int build_dgram(char *buf, size_t len, struct dgram_packet *dgram)
+{
+ unsigned char *ubuf = (unsigned char *)buf;
+ int offset=0;
+
+ /* put in the header */
+ if (buf) {
+ ubuf[0] = dgram->header.msg_type;
+ ubuf[1] = (((int)dgram->header.flags.node_type)<<2);
+ if (dgram->header.flags.more)
+ ubuf[1] |= 1;
+ if (dgram->header.flags.first)
+ ubuf[1] |= 2;
+ RSSVAL(ubuf,2,dgram->header.dgm_id);
+ putip(ubuf+4,(char *)&dgram->header.source_ip);
+ RSSVAL(ubuf,8,dgram->header.source_port);
+ RSSVAL(ubuf,12,dgram->header.packet_offset);
+ }
+
+ offset = 14;
+
+ if (dgram->header.msg_type == 0x10 ||
+ dgram->header.msg_type == 0x11 ||
+ dgram->header.msg_type == 0x12) {
+ offset += put_nmb_name((char *)ubuf,len,offset,&dgram->source_name);
+ offset += put_nmb_name((char *)ubuf,len,offset,&dgram->dest_name);
+ }
+
+ if (buf) {
+ memcpy(ubuf+offset,dgram->data,dgram->datasize);
+ }
+ offset += dgram->datasize;
+
+ /* automatically set the dgm_length
+ * NOTE: RFC1002 says the dgm_length does *not*
+ * include the fourteen-byte header. crh
+ */
+ dgram->header.dgm_length = (offset - 14);
+ if (buf) {
+ RSSVAL(ubuf,10,dgram->header.dgm_length);
+ }
+
+ return offset;
+}
+
+/*******************************************************************
+ Build a nmb name
+*******************************************************************/
+
+void make_nmb_name( struct nmb_name *n, const char *name, int type)
+{
+ fstring unix_name;
+ memset( (char *)n, '\0', sizeof(struct nmb_name) );
+ fstrcpy(unix_name, name);
+ (void)strupper_m(unix_name);
+ push_ascii(n->name, unix_name, sizeof(n->name), STR_TERMINATE);
+ n->name_type = (unsigned int)type & 0xFF;
+ push_ascii(n->scope, lp_netbios_scope(), 64, STR_TERMINATE);
+}
+
+/*******************************************************************
+ Compare two nmb names
+******************************************************************/
+
+bool nmb_name_equal(struct nmb_name *n1, struct nmb_name *n2)
+{
+ return ((n1->name_type == n2->name_type) &&
+ strequal(n1->name ,n2->name ) &&
+ strequal(n1->scope,n2->scope));
+}
+
+/*******************************************************************
+ Build a nmb packet ready for sending.
+ If buf == NULL this is a length calculation.
+******************************************************************/
+
+static int build_nmb(char *buf, size_t len, struct nmb_packet *nmb)
+{
+ unsigned char *ubuf = (unsigned char *)buf;
+ int offset=0;
+
+ if (len && len < 12) {
+ return 0;
+ }
+
+ /* put in the header */
+ if (buf) {
+ RSSVAL(ubuf,offset,nmb->header.name_trn_id);
+ ubuf[offset+2] = (nmb->header.opcode & 0xF) << 3;
+ if (nmb->header.response)
+ ubuf[offset+2] |= (1<<7);
+ if (nmb->header.nm_flags.authoritative &&
+ nmb->header.response)
+ ubuf[offset+2] |= 0x4;
+ if (nmb->header.nm_flags.trunc)
+ ubuf[offset+2] |= 0x2;
+ if (nmb->header.nm_flags.recursion_desired)
+ ubuf[offset+2] |= 0x1;
+ if (nmb->header.nm_flags.recursion_available &&
+ nmb->header.response)
+ ubuf[offset+3] |= 0x80;
+ if (nmb->header.nm_flags.bcast)
+ ubuf[offset+3] |= 0x10;
+ ubuf[offset+3] |= (nmb->header.rcode & 0xF);
+
+ RSSVAL(ubuf,offset+4,nmb->header.qdcount);
+ RSSVAL(ubuf,offset+6,nmb->header.ancount);
+ RSSVAL(ubuf,offset+8,nmb->header.nscount);
+ RSSVAL(ubuf,offset+10,nmb->header.arcount);
+ }
+
+ offset += 12;
+ if (nmb->header.qdcount) {
+ /* XXXX this doesn't handle a qdcount of > 1 */
+ if (len) {
+ /* Length check. */
+ int extra = put_nmb_name(NULL,0,offset,
+ &nmb->question.question_name);
+ if (offset + extra > len) {
+ return 0;
+ }
+ }
+ offset += put_nmb_name((char *)ubuf,len,offset,
+ &nmb->question.question_name);
+ if (buf) {
+ RSSVAL(ubuf,offset,nmb->question.question_type);
+ RSSVAL(ubuf,offset+2,nmb->question.question_class);
+ }
+ offset += 4;
+ }
+
+ if (nmb->header.ancount) {
+ if (len) {
+ /* Length check. */
+ int extra = put_res_rec(NULL,0,offset,nmb->answers,
+ nmb->header.ancount);
+ if (offset + extra > len) {
+ return 0;
+ }
+ }
+ offset += put_res_rec((char *)ubuf,len,offset,nmb->answers,
+ nmb->header.ancount);
+ }
+
+ if (nmb->header.nscount) {
+ if (len) {
+ /* Length check. */
+ int extra = put_res_rec(NULL,0,offset,nmb->nsrecs,
+ nmb->header.nscount);
+ if (offset + extra > len) {
+ return 0;
+ }
+ }
+ offset += put_res_rec((char *)ubuf,len,offset,nmb->nsrecs,
+ nmb->header.nscount);
+ }
+
+ /*
+ * The spec says we must put compressed name pointers
+ * in the following outgoing packets :
+ * NAME_REGISTRATION_REQUEST, NAME_REFRESH_REQUEST,
+ * NAME_RELEASE_REQUEST.
+ */
+
+ if((nmb->header.response == False) &&
+ ((nmb->header.opcode == NMB_NAME_REG_OPCODE) ||
+ (nmb->header.opcode == NMB_NAME_RELEASE_OPCODE) ||
+ (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_8) ||
+ (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_9) ||
+ (nmb->header.opcode == NMB_NAME_MULTIHOMED_REG_OPCODE)) &&
+ (nmb->header.arcount == 1)) {
+
+ if (len) {
+ /* Length check. */
+ int extra = put_compressed_name_ptr(NULL,offset,
+ nmb->additional,12);
+ if (offset + extra > len) {
+ return 0;
+ }
+ }
+ offset += put_compressed_name_ptr(ubuf,offset,
+ nmb->additional,12);
+ } else if (nmb->header.arcount) {
+ if (len) {
+ /* Length check. */
+ int extra = put_res_rec(NULL,0,offset,nmb->additional,
+ nmb->header.arcount);
+ if (offset + extra > len) {
+ return 0;
+ }
+ }
+ offset += put_res_rec((char *)ubuf,len,offset,nmb->additional,
+ nmb->header.arcount);
+ }
+ return offset;
+}
+
+/*******************************************************************
+ Linearise a packet.
+******************************************************************/
+
+int build_packet(char *buf, size_t buflen, struct packet_struct *p)
+{
+ int len = 0;
+
+ switch (p->packet_type) {
+ case NMB_PACKET:
+ len = build_nmb(buf,buflen,&p->packet.nmb);
+ break;
+
+ case DGRAM_PACKET:
+ len = build_dgram(buf,buflen,&p->packet.dgram);
+ break;
+ }
+
+ return len;
+}
+
+/*******************************************************************
+ Send a packet_struct.
+******************************************************************/
+
+bool send_packet(struct packet_struct *p)
+{
+ char buf[1024];
+ int len=0;
+
+ memset(buf,'\0',sizeof(buf));
+
+ len = build_packet(buf, sizeof(buf), p);
+
+ if (!len)
+ return(False);
+
+ return(send_udp(p->send_fd,buf,len,p->ip,p->port));
+}
+
+/****************************************************************************
+ Receive a UDP/138 packet either via UDP or from the unexpected packet
+ queue. The packet must be a reply packet and have the specified mailslot name
+ The timeout is in milliseconds.
+***************************************************************************/
+
+/****************************************************************************
+ See if a datagram has the right mailslot name.
+***************************************************************************/
+
+bool match_mailslot_name(struct packet_struct *p, const char *mailslot_name)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ char *buf;
+
+ buf = &dgram->data[0];
+ buf -= 4;
+
+ buf = smb_buf(buf);
+
+ if (memcmp(buf, mailslot_name, strlen(mailslot_name)+1) == 0) {
+ return True;
+ }
+
+ return False;
+}
+
+/****************************************************************************
+ Return the number of bits that match between two len character buffers
+***************************************************************************/
+
+int matching_len_bits(const unsigned char *p1, const unsigned char *p2, size_t len)
+{
+ size_t i, j;
+ int ret = 0;
+ for (i=0; i<len; i++) {
+ if (p1[i] != p2[i])
+ break;
+ ret += 8;
+ }
+
+ if (i==len)
+ return ret;
+
+ for (j=0; j<8; j++) {
+ if ((p1[i] & (1<<(7-j))) != (p2[i] & (1<<(7-j))))
+ break;
+ ret++;
+ }
+
+ return ret;
+}
+
+static unsigned char sort_ip[4];
+
+/****************************************************************************
+ Compare two query reply records.
+***************************************************************************/
+
+static int name_query_comp(unsigned char *p1, unsigned char *p2)
+{
+ return matching_len_bits(p2+2, sort_ip, 4) -
+ matching_len_bits(p1+2, sort_ip, 4);
+}
+
+/****************************************************************************
+ Sort a set of 6 byte name query response records so that the IPs that
+ have the most leading bits in common with the specified address come first.
+***************************************************************************/
+
+void sort_query_replies(char *data, int n, struct in_addr ip)
+{
+ if (n <= 1)
+ return;
+
+ putip(sort_ip, (char *)&ip);
+
+ /* TODO:
+ this can't use TYPESAFE_QSORT() as the types are wrong.
+ It should be fixed to use a real type instead of char*
+ */
+ qsort(data, n, 6, QSORT_CAST name_query_comp);
+}
+
+/****************************************************************************
+ Interpret the weird netbios "name" into a unix fstring. Return the name type.
+ Returns -1 on error.
+****************************************************************************/
+
+static int name_interpret(unsigned char *buf, size_t buf_len,
+ unsigned char *in, fstring name)
+{
+ unsigned char *end_ptr = buf + buf_len;
+ int ret;
+ unsigned int len;
+ fstring out_string;
+ unsigned char *out = (unsigned char *)out_string;
+
+ *out=0;
+
+ if (in >= end_ptr) {
+ return -1;
+ }
+ len = (*in++) / 2;
+
+ if (len<1) {
+ return -1;
+ }
+
+ while (len--) {
+ if (&in[1] >= end_ptr) {
+ return -1;
+ }
+ if (in[0] < 'A' || in[0] > 'P' || in[1] < 'A' || in[1] > 'P') {
+ *out = 0;
+ return(0);
+ }
+ *out = ((in[0]-'A')<<4) + (in[1]-'A');
+ in += 2;
+ out++;
+ if (PTR_DIFF(out,out_string) >= sizeof(fstring)) {
+ return -1;
+ }
+ }
+ ret = out[-1];
+ out[-1] = 0;
+
+ pull_ascii_fstring(name, out_string);
+
+ return(ret);
+}
+
+/****************************************************************************
+ Mangle a name into netbios format.
+ Note: <Out> must be (33 + strlen(scope) + 2) bytes long, at minimum.
+****************************************************************************/
+
+char *name_mangle(TALLOC_CTX *mem_ctx, const char *In, char name_type)
+{
+ int i;
+ int len;
+ nstring buf;
+ char *result;
+ char *p;
+
+ result = talloc_array(mem_ctx, char, 33 + strlen(lp_netbios_scope()) + 2);
+ if (result == NULL) {
+ return NULL;
+ }
+ p = result;
+
+ /* Safely copy the input string, In, into buf[]. */
+ if (strcmp(In,"*") == 0)
+ put_name(buf, "*", '\0', 0x00);
+ else {
+ /* We use an fstring here as mb dos names can expend x3 when
+ going to utf8. */
+ fstring buf_unix;
+ nstring buf_dos;
+
+ pull_ascii_fstring(buf_unix, In);
+ if (!strupper_m(buf_unix)) {
+ return NULL;
+ }
+
+ push_ascii_nstring(buf_dos, buf_unix);
+ put_name(buf, buf_dos, ' ', name_type);
+ }
+
+ /* Place the length of the first field into the output buffer. */
+ p[0] = 32;
+ p++;
+
+ /* Now convert the name to the rfc1001/1002 format. */
+ for( i = 0; i < MAX_NETBIOSNAME_LEN; i++ ) {
+ p[i*2] = ( (buf[i] >> 4) & 0x000F ) + 'A';
+ p[(i*2)+1] = (buf[i] & 0x000F) + 'A';
+ }
+ p += 32;
+ p[0] = '\0';
+
+ /* Add the scope string. */
+ for( i = 0, len = 0; *(lp_netbios_scope()) != '\0'; i++, len++ ) {
+ switch( (lp_netbios_scope())[i] ) {
+ case '\0':
+ p[0] = len;
+ if( len > 0 )
+ p[len+1] = 0;
+ return result;
+ case '.':
+ p[0] = len;
+ p += (len + 1);
+ len = -1;
+ break;
+ default:
+ p[len+1] = (lp_netbios_scope())[i];
+ break;
+ }
+ }
+
+ return result;
+}
+
+/****************************************************************************
+ Find a pointer to a netbios name.
+****************************************************************************/
+
+static unsigned char *name_ptr(unsigned char *buf, size_t buf_len, unsigned int ofs)
+{
+ unsigned char c = 0;
+
+ if (ofs > buf_len || buf_len < 1) {
+ return NULL;
+ }
+
+ c = *(unsigned char *)(buf+ofs);
+ if ((c & 0xC0) == 0xC0) {
+ uint16_t l = 0;
+
+ if (ofs > buf_len - 1) {
+ return NULL;
+ }
+ l = RSVAL(buf, ofs) & 0x3FFF;
+ if (l > buf_len) {
+ return NULL;
+ }
+ DEBUG(5,("name ptr to pos %d from %d is %s\n",l,ofs,buf+l));
+ return(buf + l);
+ } else {
+ return(buf+ofs);
+ }
+}
+
+/****************************************************************************
+ Extract a netbios name from a buf (into a unix string) return name type.
+ Returns -1 on error.
+****************************************************************************/
+
+int name_extract(unsigned char *buf, size_t buf_len, unsigned int ofs, fstring name)
+{
+ unsigned char *p = name_ptr(buf,buf_len,ofs);
+
+ name[0] = '\0';
+ if (p == NULL) {
+ return -1;
+ }
+ return(name_interpret(buf,buf_len,p,name));
+}
+
+/****************************************************************************
+ Return the total storage length of a mangled name.
+ Returns -1 on error.
+****************************************************************************/
+
+int name_len(unsigned char *s1, size_t buf_len)
+{
+ /* NOTE: this argument _must_ be unsigned */
+ unsigned char *s = (unsigned char *)s1;
+ int len = 0;
+
+ if (buf_len < 1) {
+ return -1;
+ }
+ /* If the two high bits of the byte are set, return 2. */
+ if (0xC0 == (*s & 0xC0)) {
+ if (buf_len < 2) {
+ return -1;
+ }
+ return(2);
+ }
+
+ /* Add up the length bytes. */
+ for (len = 1; (*s); s += (*s) + 1) {
+ len += *s + 1;
+ if (len > buf_len) {
+ return -1;
+ }
+ }
+
+ return(len);
+}
+
+/*******************************************************************
+ Setup the word count and byte count for a client smb message.
+********************************************************************/
+
+int cli_set_message(char *buf,int num_words,int num_bytes,bool zero)
+{
+ if (zero && (num_words || num_bytes)) {
+ memset(buf + smb_size,'\0',num_words*2 + num_bytes);
+ }
+ SCVAL(buf,smb_wct,num_words);
+ SSVAL(buf,smb_vwv + num_words*SIZEOFWORD,num_bytes);
+ smb_setlen(buf,smb_size + num_words*2 + num_bytes - 4);
+ return (smb_size + num_words*2 + num_bytes);
+}
diff --git a/source3/libsmb/nmblib.h b/source3/libsmb/nmblib.h
new file mode 100644
index 0000000..52600a4
--- /dev/null
+++ b/source3/libsmb/nmblib.h
@@ -0,0 +1,59 @@
+/*
+ Unix SMB/CIFS implementation.
+ handle unexpected packets
+ NBT netbios library routines
+ Copyright (C) Andrew Tridgell 1994-1998, 2000
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _LIBSMB_NMBLIB_H_
+#define _LIBSMB_NMBLIB_H_
+
+/* The following definitions come from libsmb/unexpected.c */
+
+#include "nameserv.h"
+
+/* The following definitions come from libsmb/nmblib.c */
+
+void debug_nmb_packet(struct packet_struct *p);
+void put_name(char *dest, const char *name, int pad, unsigned int name_type);
+char *nmb_namestr(const struct nmb_name *n);
+struct packet_struct *copy_packet(struct packet_struct *packet);
+void free_packet(struct packet_struct *packet);
+int packet_trn_id(struct packet_struct *p);
+struct packet_struct *parse_packet(char *buf,int length,
+ enum packet_type packet_type,
+ struct in_addr ip,
+ int port);
+struct packet_struct *parse_packet_talloc(TALLOC_CTX *mem_ctx,
+ char *buf,int length,
+ enum packet_type packet_type,
+ struct in_addr ip,
+ int port);
+void make_nmb_name( struct nmb_name *n, const char *name, int type);
+bool nmb_name_equal(struct nmb_name *n1, struct nmb_name *n2);
+int build_packet(char *buf, size_t buflen, struct packet_struct *p);
+bool send_packet(struct packet_struct *p);
+bool match_mailslot_name(struct packet_struct *p, const char *mailslot_name);
+int matching_len_bits(const unsigned char *p1, const unsigned char *p2, size_t len);
+void sort_query_replies(char *data, int n, struct in_addr ip);
+char *name_mangle(TALLOC_CTX *mem_ctx, const char *In, char name_type);
+int name_extract(unsigned char *buf,size_t buf_len, unsigned int ofs, fstring name);
+int name_len(unsigned char *s1, size_t buf_len);
+int cli_set_message(char *buf,int num_words,int num_bytes,bool zero);
+
+#endif /* _LIBSMB_NMBLIB_H_ */
diff --git a/source3/libsmb/passchange.c b/source3/libsmb/passchange.c
new file mode 100644
index 0000000..b3304ed
--- /dev/null
+++ b/source3/libsmb/passchange.c
@@ -0,0 +1,323 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB client password change routine
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/ndr_samr.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_samr.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "libsmb/nmblib.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/*************************************************************
+ Change a password on a remote machine using IPC calls.
+*************************************************************/
+
+NTSTATUS remote_password_change(const char *remote_machine,
+ const char *domain, const char *user_name,
+ const char *old_passwd, const char *new_passwd,
+ char **err_str)
+{
+ struct cli_state *cli = NULL;
+ struct cli_credentials *creds = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ NTSTATUS status;
+ NTSTATUS result;
+ bool pass_must_change = False;
+
+ *err_str = NULL;
+
+ result = cli_connect_nb(remote_machine, NULL, 0, 0x20, NULL,
+ SMB_SIGNING_IPC_DEFAULT, 0, &cli);
+ if (!NT_STATUS_IS_OK(result)) {
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_SUPPORTED)) {
+ if (asprintf(err_str, "Unable to connect to SMB server on "
+ "machine %s. NetBIOS support disabled\n",
+ remote_machine) == -1) {
+ *err_str = NULL;
+ }
+ } else {
+ if (asprintf(err_str, "Unable to connect to SMB server on "
+ "machine %s. Error was : %s.\n",
+ remote_machine, nt_errstr(result))==-1) {
+ *err_str = NULL;
+ }
+ }
+ return result;
+ }
+
+ creds = cli_session_creds_init(cli,
+ user_name,
+ domain,
+ NULL, /* realm */
+ old_passwd,
+ false, /* use_kerberos */
+ false, /* fallback_after_kerberos */
+ false, /* use_ccache */
+ false); /* password_is_nt_hash */
+ SMB_ASSERT(creds != NULL);
+
+ result = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ lp_client_ipc_min_protocol(),
+ lp_client_ipc_max_protocol(),
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ if (asprintf(err_str, "machine %s rejected the negotiate "
+ "protocol. Error was : %s.\n",
+ remote_machine, nt_errstr(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+
+ /* Given things like SMB signing, restrict anonymous and the like,
+ try an authenticated connection first */
+ result = cli_session_setup_creds(cli, creds);
+
+ if (!NT_STATUS_IS_OK(result)) {
+
+ /* Password must change or Password expired are the only valid
+ * error conditions here from where we can proceed, the rest like
+ * account locked out or logon failure will lead to errors later
+ * anyway */
+
+ if (!NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_MUST_CHANGE) &&
+ !NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_EXPIRED)) {
+ if (asprintf(err_str, "Could not connect to machine %s: "
+ "%s\n", remote_machine, nt_errstr(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+
+ pass_must_change = True;
+
+ /*
+ * We should connect as the anonymous user here, in case
+ * the server has "must change password" checked...
+ * Thanks to <Nicholas.S.Jenkins@cdc.com> for this fix.
+ */
+
+ result = cli_session_setup_anon(cli);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ if (asprintf(err_str, "machine %s rejected the session "
+ "setup. Error was : %s.\n",
+ remote_machine, nt_errstr(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+ }
+
+ result = cli_tree_connect(cli, "IPC$", "IPC", NULL);
+ if (!NT_STATUS_IS_OK(result)) {
+ if (asprintf(err_str, "machine %s rejected the tconX on the "
+ "IPC$ share. Error was : %s.\n",
+ remote_machine, nt_errstr(result))) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+
+ /* Try not to give the password away too easily */
+
+ if (!pass_must_change) {
+ const struct sockaddr_storage *remote_sockaddr =
+ smbXcli_conn_remote_sockaddr(cli->conn);
+
+ result = cli_rpc_pipe_open_with_creds(cli,
+ &ndr_table_samr,
+ NCACN_NP,
+ DCERPC_AUTH_TYPE_NTLMSSP,
+ DCERPC_AUTH_LEVEL_PRIVACY,
+ remote_machine,
+ remote_sockaddr,
+ creds,
+ &pipe_hnd);
+ } else {
+ /*
+ * If the user password must be changed the ntlmssp bind will
+ * fail the same way as the session setup above did. The
+ * difference is that with a pipe bind we don't get a good
+ * error message, the result will be that the rpc call below
+ * will just fail. So we do it anonymously, there's no other
+ * way.
+ */
+ result = cli_rpc_pipe_open_noauth(
+ cli, &ndr_table_samr, &pipe_hnd);
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ if (lp_client_lanman_auth()) {
+ /* Use the old RAP method. */
+ if (!cli_oem_change_password(cli, user_name, new_passwd, old_passwd)) {
+ result = cli_nt_error(cli);
+ if (asprintf(err_str, "machine %s rejected the "
+ "password change: Error was : %s.\n",
+ remote_machine, nt_errstr(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+ } else {
+ if (asprintf(err_str, "SAMR connection to machine %s "
+ "failed. Error was %s, but LANMAN password "
+ "changes are disabled\n",
+ remote_machine, nt_errstr(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+ }
+
+ status = dcerpc_samr_chgpasswd_user4(pipe_hnd->binding_handle,
+ talloc_tos(),
+ pipe_hnd->srv_name_slash,
+ user_name,
+ old_passwd,
+ new_passwd,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ /* All good, password successfully changed. */
+ cli_shutdown(cli);
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ /* DO NOT FALLBACK TO RC4 */
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) {
+ cli_shutdown(cli);
+ return NT_STATUS_STRONG_CRYPTO_NOT_SUPPORTED;
+ }
+ }
+ } else {
+ if (!NT_STATUS_IS_OK(result)) {
+ int rc = asprintf(
+ err_str,
+ "machine %s rejected to change the password"
+ "with error: %s",
+ remote_machine,
+ get_friendly_nt_error_msg(result));
+ if (rc <= 0) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+ }
+
+ result = rpccli_samr_chgpasswd_user2(pipe_hnd, talloc_tos(),
+ user_name, new_passwd, old_passwd);
+ if (NT_STATUS_IS_OK(result)) {
+ /* Great - it all worked! */
+ cli_shutdown(cli);
+ return NT_STATUS_OK;
+
+ } else if (!(NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)
+ || NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))) {
+ /* it failed, but for reasons such as wrong password, too short etc ... */
+
+ if (asprintf(err_str, "machine %s rejected the password change: "
+ "Error was : %s.\n",
+ remote_machine, get_friendly_nt_error_msg(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+
+ /* OK, that failed, so try again... */
+ TALLOC_FREE(pipe_hnd);
+
+ /* Try anonymous NTLMSSP... */
+ result = NT_STATUS_UNSUCCESSFUL;
+
+ /* OK, this is ugly, but... try an anonymous pipe. */
+ result = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr,
+ &pipe_hnd);
+
+ if ( NT_STATUS_IS_OK(result) &&
+ (NT_STATUS_IS_OK(result = rpccli_samr_chgpasswd_user2(
+ pipe_hnd, talloc_tos(), user_name,
+ new_passwd, old_passwd)))) {
+ /* Great - it all worked! */
+ cli_shutdown(cli);
+ return NT_STATUS_OK;
+ } else {
+ if (!(NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)
+ || NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL))) {
+ /* it failed, but again it was due to things like new password too short */
+
+ if (asprintf(err_str, "machine %s rejected the "
+ "(anonymous) password change: Error was : "
+ "%s.\n", remote_machine,
+ get_friendly_nt_error_msg(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ }
+
+ /* We have failed to change the user's password, and we think the server
+ just might not support SAMR password changes, so fall back */
+
+ if (lp_client_lanman_auth()) {
+ /* Use the old RAP method. */
+ if (cli_oem_change_password(cli, user_name, new_passwd, old_passwd)) {
+ /* SAMR failed, but the old LanMan protocol worked! */
+
+ cli_shutdown(cli);
+ return NT_STATUS_OK;
+ }
+
+ result = cli_nt_error(cli);
+ if (asprintf(err_str, "machine %s rejected the password "
+ "change: Error was : %s.\n",
+ remote_machine, nt_errstr(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return result;
+ } else {
+ if (asprintf(err_str, "SAMR connection to machine %s "
+ "failed. Error was %s, but LANMAN password "
+ "changes are disabled\n",
+ remote_machine, nt_errstr(result)) == -1) {
+ *err_str = NULL;
+ }
+ cli_shutdown(cli);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+}
diff --git a/source3/libsmb/proto.h b/source3/libsmb/proto.h
new file mode 100644
index 0000000..35b6577
--- /dev/null
+++ b/source3/libsmb/proto.h
@@ -0,0 +1,1080 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Bartlett 2001-2003
+ Copyright (C) Andrew Tridgell 1994-1998,2000-2001
+ Copyright (C) Gerald (Jerry) Carter 2004
+ Copyright (C) Jelmer Vernooij 2003
+ Copyright (C) Jeremy Allison 2001-2009,2011
+ Copyright (C) Stefan Metzmacher 2003,2009
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _LIBSMB_PROTO_H_
+#define _LIBSMB_PROTO_H_
+
+struct smb_trans_enc_state;
+struct cli_credentials;
+struct cli_state;
+struct file_info;
+struct print_job_info;
+
+/* The following definitions come from libsmb/cliconnect.c */
+
+struct cli_credentials *cli_session_creds_init(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *domain,
+ const char *realm,
+ const char *password,
+ bool use_kerberos,
+ bool fallback_after_kerberos,
+ bool use_ccache,
+ bool password_is_nt_hash);
+NTSTATUS cli_session_creds_prepare_krb5(struct cli_state *cli,
+ struct cli_credentials *creds);
+struct tevent_req *cli_session_setup_creds_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct cli_credentials *creds);
+NTSTATUS cli_session_setup_creds_recv(struct tevent_req *req);
+NTSTATUS cli_session_setup_creds(struct cli_state *cli,
+ struct cli_credentials *creds);
+NTSTATUS cli_session_setup_anon(struct cli_state *cli);
+struct tevent_req *cli_session_setup_guest_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ struct tevent_req **psmbreq);
+struct tevent_req *cli_session_setup_guest_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli);
+NTSTATUS cli_session_setup_guest_recv(struct tevent_req *req);
+NTSTATUS cli_ulogoff(struct cli_state *cli);
+struct tevent_req *cli_tcon_andx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *share, const char *dev,
+ const char *pass, int passlen,
+ struct tevent_req **psmbreq);
+struct tevent_req *cli_tcon_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *share, const char *dev,
+ const char *pass, int passlen);
+NTSTATUS cli_tcon_andx_recv(struct tevent_req *req);
+NTSTATUS cli_tcon_andx(struct cli_state *cli, const char *share,
+ const char *dev, const char *pass, int passlen);
+NTSTATUS cli_tree_connect_creds(struct cli_state *cli,
+ const char *share, const char *dev,
+ struct cli_credentials *creds);
+NTSTATUS cli_tree_connect(struct cli_state *cli, const char *share,
+ const char *dev, const char *pass);
+NTSTATUS cli_tdis(struct cli_state *cli);
+NTSTATUS cli_connect_nb(const char *host, const struct sockaddr_storage *dest_ss,
+ uint16_t port, int name_type, const char *myname,
+ enum smb_signing_setting signing_state, int flags, struct cli_state **pcli);
+NTSTATUS cli_start_connection(struct cli_state **output_cli,
+ const char *my_name,
+ const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ enum smb_signing_setting signing_state, int flags);
+NTSTATUS cli_smb1_setup_encryption(struct cli_state *cli,
+ struct cli_credentials *creds);
+
+struct smb2_negotiate_contexts;
+struct tevent_req *cli_full_connection_creds_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const char *my_name, const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ const char *service, const char *service_type,
+ struct cli_credentials *creds,
+ int flags,
+ struct smb2_negotiate_contexts *negotiate_contexts);
+NTSTATUS cli_full_connection_creds_recv(struct tevent_req *req,
+ struct cli_state **output_cli);
+NTSTATUS cli_full_connection_creds(struct cli_state **output_cli,
+ const char *my_name,
+ const char *dest_host,
+ const struct sockaddr_storage *dest_ss, int port,
+ const char *service, const char *service_type,
+ struct cli_credentials *creds,
+ int flags);
+NTSTATUS cli_raw_tcon(struct cli_state *cli,
+ const char *service, const char *pass, const char *dev,
+ uint16_t *max_xmit, uint16_t *tid);
+struct cli_state *get_ipc_connect(char *server,
+ struct sockaddr_storage *server_ss,
+ struct cli_credentials *creds);
+struct cli_state *get_ipc_connect_master_ip(TALLOC_CTX *ctx,
+ struct sockaddr_storage *mb_ip,
+ struct cli_credentials *creds,
+ char **pp_workgroup_out);
+
+/* The following definitions come from libsmb/clidfs.c */
+
+NTSTATUS cli_cm_open(TALLOC_CTX *ctx,
+ struct cli_state *referring_cli,
+ const char *server,
+ const char *share,
+ struct cli_credentials *creds,
+ const struct sockaddr_storage *dest_ss,
+ int port,
+ int name_type,
+ struct cli_state **pcli);
+void cli_cm_display(struct cli_state *c);
+struct client_dfs_referral;
+bool cli_dfs_is_already_full_path(struct cli_state *cli, const char *path);
+NTSTATUS cli_dfs_get_referral_ex(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *path,
+ uint16_t max_referral_level,
+ struct client_dfs_referral **refs,
+ size_t *num_refs,
+ size_t *consumed);
+NTSTATUS cli_dfs_get_referral(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *path,
+ struct client_dfs_referral **refs,
+ size_t *num_refs,
+ size_t *consumed);
+NTSTATUS cli_resolve_path(TALLOC_CTX *ctx,
+ const char *mountpt,
+ struct cli_credentials *creds,
+ struct cli_state *rootcli,
+ const char *path,
+ struct cli_state **targetcli,
+ char **pp_targetpath);
+
+bool cli_check_msdfs_proxy(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *sharename,
+ char **pp_newserver,
+ char **pp_newshare,
+ struct cli_credentials *creds);
+
+NTSTATUS cli_dfs_target_check(TALLOC_CTX *mem_ctx,
+ struct cli_state *cli,
+ const char *fname_dst,
+ const char **fname_dst_out);
+char *smb1_dfs_share_path(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *path);
+
+/* The following definitions come from libsmb/clientgen.c */
+
+unsigned int cli_set_timeout(struct cli_state *cli, unsigned int timeout);
+bool cli_set_backup_intent(struct cli_state *cli, bool flag);
+extern struct GUID cli_state_client_guid;
+struct cli_state *cli_state_create(TALLOC_CTX *mem_ctx,
+ int fd,
+ const char *remote_name,
+ enum smb_signing_setting signing_state,
+ int flags);
+void cli_shutdown(struct cli_state *cli);
+uint16_t cli_state_get_vc_num(struct cli_state *cli);
+uint32_t cli_setpid(struct cli_state *cli, uint32_t pid);
+uint32_t cli_getpid(struct cli_state *cli);
+bool cli_state_is_encryption_on(struct cli_state *cli);
+bool cli_state_has_tcon(struct cli_state *cli);
+uint32_t cli_state_get_tid(struct cli_state *cli);
+uint32_t cli_state_set_tid(struct cli_state *cli, uint32_t tid);
+struct smbXcli_tcon;
+void cli_state_save_tcon_share(struct cli_state *cli,
+ struct smbXcli_tcon **_tcon_ret,
+ char **_share_ret);
+void cli_state_restore_tcon_share(struct cli_state *cli,
+ struct smbXcli_tcon *tcon,
+ char *share);
+uint16_t cli_state_get_uid(struct cli_state *cli);
+uint16_t cli_state_set_uid(struct cli_state *cli, uint16_t uid);
+bool cli_set_case_sensitive(struct cli_state *cli, bool case_sensitive);
+uint32_t cli_state_available_size(struct cli_state *cli, uint32_t ofs);
+time_t cli_state_server_time(struct cli_state *cli);
+struct tevent_req *cli_echo_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, uint16_t num_echos,
+ DATA_BLOB data);
+NTSTATUS cli_echo_recv(struct tevent_req *req);
+NTSTATUS cli_echo(struct cli_state *cli, uint16_t num_echos, DATA_BLOB data);
+NTSTATUS cli_smb(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint8_t smb_command, uint8_t additional_flags,
+ uint8_t wct, uint16_t *vwv,
+ uint32_t num_bytes, const uint8_t *bytes,
+ struct tevent_req **result_parent,
+ uint8_t min_wct, uint8_t *pwct, uint16_t **pvwv,
+ uint32_t *pnum_bytes, uint8_t **pbytes);
+
+/* The following definitions come from libsmb/clierror.c */
+
+NTSTATUS cli_nt_error(struct cli_state *cli);
+int cli_status_to_errno(NTSTATUS status);
+int cli_errno(struct cli_state *cli);
+bool cli_is_error(struct cli_state *cli);
+bool cli_state_is_connected(struct cli_state *cli);
+
+/* The following definitions come from libsmb/clifile.c */
+
+struct tevent_req *cli_setpathinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t level,
+ const char *path,
+ uint8_t *data,
+ size_t data_len);
+NTSTATUS cli_setpathinfo_recv(struct tevent_req *req);
+NTSTATUS cli_setpathinfo(struct cli_state *cli,
+ uint16_t level,
+ const char *path,
+ uint8_t *data,
+ size_t data_len);
+struct tevent_req *cli_setfileinfo_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t level,
+ uint8_t *data,
+ size_t data_len);
+NTSTATUS cli_setfileinfo_recv(struct tevent_req *req);
+
+struct tevent_req *cli_posix_symlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *oldname,
+ const char *newname);
+NTSTATUS cli_posix_symlink_recv(struct tevent_req *req);
+NTSTATUS cli_posix_symlink(struct cli_state *cli,
+ const char *oldname,
+ const char *newname);
+struct tevent_req *cli_posix_readlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_posix_readlink_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, char **target);
+struct tevent_req *cli_posix_hardlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *oldname,
+ const char *newname);
+NTSTATUS cli_posix_hardlink_recv(struct tevent_req *req);
+NTSTATUS cli_posix_hardlink(struct cli_state *cli,
+ const char *oldname,
+ const char *newname);
+uint32_t unix_perms_to_wire(mode_t perms);
+mode_t wire_perms_to_unix(uint32_t perms);
+struct tevent_req *cli_posix_getacl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_posix_getacl_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ size_t *prb_size,
+ char **retbuf);
+NTSTATUS cli_posix_getacl(struct cli_state *cli,
+ const char *fname,
+ TALLOC_CTX *mem_ctx,
+ size_t *prb_size,
+ char **retbuf);
+struct tevent_req *cli_posix_setacl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ const void *acl_buf,
+ size_t acl_buf_size);
+NTSTATUS cli_posix_setacl_recv(struct tevent_req *req);
+NTSTATUS cli_posix_setacl(struct cli_state *cli,
+ const char *fname,
+ const void *acl_buf,
+ size_t acl_buf_size);
+struct tevent_req *cli_posix_stat_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_posix_stat_recv(struct tevent_req *req, struct stat_ex *sbuf);
+NTSTATUS cli_posix_stat(struct cli_state *cli,
+ const char *fname,
+ struct stat_ex *sbuf);
+struct tevent_req *cli_posix_chmod_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ mode_t mode);
+NTSTATUS cli_posix_chmod_recv(struct tevent_req *req);
+NTSTATUS cli_posix_chmod(struct cli_state *cli, const char *fname, mode_t mode);
+struct tevent_req *cli_posix_chown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uid_t uid,
+ gid_t gid);
+NTSTATUS cli_posix_chown_recv(struct tevent_req *req);
+NTSTATUS cli_posix_chown(struct cli_state *cli,
+ const char *fname,
+ uid_t uid,
+ gid_t gid);
+struct tevent_req *cli_mknod_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ mode_t mode,
+ dev_t dev);
+NTSTATUS cli_mknod_recv(struct tevent_req *req);
+NTSTATUS
+cli_mknod(struct cli_state *cli, const char *fname, mode_t mode, dev_t dev);
+struct tevent_req *cli_rename_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace);
+NTSTATUS cli_rename_recv(struct tevent_req *req);
+NTSTATUS cli_rename(struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst,
+ bool replace);
+struct tevent_req *cli_ntrename_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst);
+NTSTATUS cli_ntrename_recv(struct tevent_req *req);
+NTSTATUS cli_ntrename(struct cli_state *cli, const char *fname_src, const char *fname_dst);
+
+struct tevent_req *cli_hardlink_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst);
+NTSTATUS cli_hardlink_recv(struct tevent_req *req);
+NTSTATUS cli_hardlink(
+ struct cli_state *cli,
+ const char *fname_src,
+ const char *fname_dst);
+
+struct tevent_req *cli_unlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t mayhave_attrs);
+NTSTATUS cli_unlink_recv(struct tevent_req *req);
+NTSTATUS cli_unlink(struct cli_state *cli, const char *fname, uint32_t mayhave_attrs);
+
+struct tevent_req *cli_mkdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *dname);
+NTSTATUS cli_mkdir_recv(struct tevent_req *req);
+NTSTATUS cli_mkdir(struct cli_state *cli, const char *dname);
+struct tevent_req *cli_rmdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *dname);
+NTSTATUS cli_rmdir_recv(struct tevent_req *req);
+NTSTATUS cli_rmdir(struct cli_state *cli, const char *dname);
+struct tevent_req *cli_nt_delete_on_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ bool flag);
+NTSTATUS cli_nt_delete_on_close_recv(struct tevent_req *req);
+NTSTATUS cli_nt_delete_on_close(struct cli_state *cli, uint16_t fnum, bool flag);
+struct tevent_req *cli_ntcreate_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint32_t ImpersonationLevel,
+ uint8_t SecurityFlags);
+NTSTATUS cli_ntcreate_recv(struct tevent_req *req,
+ uint16_t *pfnum,
+ struct smb_create_returns *cr);
+NTSTATUS cli_ntcreate(struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint8_t SecurityFlags,
+ uint16_t *pfid,
+ struct smb_create_returns *cr);
+struct tevent_req *cli_openx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, const char *fname,
+ int flags, int share_mode,
+ struct tevent_req **psmbreq);
+struct tevent_req *cli_openx_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, const char *fname,
+ int flags, int share_mode);
+NTSTATUS cli_openx_recv(struct tevent_req *req, uint16_t *fnum);
+NTSTATUS cli_openx(struct cli_state *cli, const char *fname, int flags, int share_mode, uint16_t *pfnum);
+NTSTATUS cli_open(struct cli_state *cli, const char *fname, int flags, int share_mode, uint16_t *pfnum);
+struct tevent_req *cli_smb1_close_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ struct tevent_req **psubreq);
+struct tevent_req *cli_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t flags);
+NTSTATUS cli_close_recv(struct tevent_req *req);
+NTSTATUS cli_close(struct cli_state *cli, uint16_t fnum);
+struct tevent_req *cli_ftruncate_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t size);
+NTSTATUS cli_ftruncate_recv(struct tevent_req *req);
+NTSTATUS cli_ftruncate(struct cli_state *cli, uint16_t fnum, uint64_t size);
+NTSTATUS cli_locktype(struct cli_state *cli, uint16_t fnum,
+ uint32_t offset, uint32_t len,
+ int timeout, unsigned char locktype);
+struct smb1_lock_element {
+ uint16_t pid;
+ uint64_t offset;
+ uint64_t length;
+};
+
+struct tevent_req *cli_lockingx_create(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t typeoflock,
+ uint8_t newoplocklevel,
+ int32_t timeout,
+ uint16_t num_unlocks,
+ const struct smb1_lock_element *unlocks,
+ uint16_t num_locks,
+ const struct smb1_lock_element *locks,
+ struct tevent_req **psmbreq);
+struct tevent_req *cli_lockingx_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t typeoflock,
+ uint8_t newoplocklevel,
+ int32_t timeout,
+ uint16_t num_unlocks,
+ const struct smb1_lock_element *unlocks,
+ uint16_t num_locks,
+ const struct smb1_lock_element *locks);
+NTSTATUS cli_lockingx_recv(struct tevent_req *req);
+NTSTATUS cli_lockingx(
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t typeoflock,
+ uint8_t newoplocklevel,
+ int32_t timeout,
+ uint16_t num_unlocks,
+ const struct smb1_lock_element *unlocks,
+ uint16_t num_locks,
+ const struct smb1_lock_element *locks);
+
+NTSTATUS cli_lock32(struct cli_state *cli, uint16_t fnum, uint32_t offset,
+ uint32_t len, int timeout, enum brl_type lock_type);
+struct tevent_req *cli_unlock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t offset,
+ uint64_t len);
+NTSTATUS cli_unlock_recv(struct tevent_req *req);
+NTSTATUS cli_unlock(struct cli_state *cli, uint16_t fnum, uint32_t offset, uint32_t len);
+struct tevent_req *cli_posix_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t offset,
+ uint64_t len,
+ bool wait_lock,
+ enum brl_type lock_type);
+NTSTATUS cli_posix_lock_recv(struct tevent_req *req);
+NTSTATUS cli_posix_lock(struct cli_state *cli, uint16_t fnum,
+ uint64_t offset, uint64_t len,
+ bool wait_lock, enum brl_type lock_type);
+struct tevent_req *cli_posix_unlock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint64_t offset,
+ uint64_t len);
+NTSTATUS cli_posix_unlock_recv(struct tevent_req *req);
+NTSTATUS cli_posix_unlock(struct cli_state *cli, uint16_t fnum, uint64_t offset, uint64_t len);
+struct tevent_req *cli_getattrE_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum);
+NTSTATUS cli_getattrE_recv(struct tevent_req *req,
+ uint32_t *pattr,
+ off_t *size,
+ time_t *change_time,
+ time_t *access_time,
+ time_t *write_time);
+struct tevent_req *cli_setattrE_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ time_t change_time,
+ time_t access_time,
+ time_t write_time);
+NTSTATUS cli_setattrE_recv(struct tevent_req *req);
+NTSTATUS cli_setattrE(struct cli_state *cli,
+ uint16_t fnum,
+ time_t change_time,
+ time_t access_time,
+ time_t write_time);
+struct tevent_req *cli_getatr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_getatr_recv(struct tevent_req *req,
+ uint32_t *pattr,
+ off_t *size,
+ time_t *write_time);
+NTSTATUS cli_getatr(struct cli_state *cli,
+ const char *fname,
+ uint32_t *pattr,
+ off_t *size,
+ time_t *write_time);
+struct tevent_req *cli_setatr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t attr,
+ time_t mtime);
+NTSTATUS cli_setatr_recv(struct tevent_req *req);
+NTSTATUS cli_setatr(struct cli_state *cli,
+ const char *fname,
+ uint32_t attr,
+ time_t mtime);
+struct tevent_req *cli_chkpath_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_chkpath_recv(struct tevent_req *req);
+NTSTATUS cli_chkpath(struct cli_state *cli, const char *path);
+struct tevent_req *cli_dskattr_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli);
+NTSTATUS cli_dskattr_recv(struct tevent_req *req, int *bsize, int *total,
+ int *avail);
+NTSTATUS cli_dskattr(struct cli_state *cli, int *bsize, int *total, int *avail);
+NTSTATUS cli_disk_size(struct cli_state *cli, const char *path, uint64_t *bsize,
+ uint64_t *total, uint64_t *avail);
+struct tevent_req *cli_ctemp_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *path);
+NTSTATUS cli_ctemp_recv(struct tevent_req *req,
+ TALLOC_CTX *ctx,
+ uint16_t *pfnum,
+ char **outfile);
+NTSTATUS cli_ctemp(struct cli_state *cli,
+ TALLOC_CTX *ctx,
+ const char *path,
+ uint16_t *pfnum,
+ char **out_path);
+NTSTATUS cli_set_ea_path(struct cli_state *cli, const char *path,
+ const char *ea_name, const char *ea_val,
+ size_t ea_len);
+NTSTATUS cli_set_ea_fnum(struct cli_state *cli, uint16_t fnum,
+ const char *ea_name, const char *ea_val,
+ size_t ea_len);
+struct tevent_req *cli_get_ea_list_path_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_get_ea_list_path_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ size_t *pnum_eas, struct ea_struct **peas);
+NTSTATUS cli_get_ea_list_path(struct cli_state *cli, const char *path,
+ TALLOC_CTX *ctx,
+ size_t *pnum_eas,
+ struct ea_struct **pea_list);
+struct tevent_req *cli_posix_open_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ int flags,
+ mode_t mode);
+NTSTATUS cli_posix_open_recv(struct tevent_req *req, uint16_t *pfnum);
+NTSTATUS cli_posix_open(struct cli_state *cli, const char *fname,
+ int flags, mode_t mode, uint16_t *fnum);
+struct tevent_req *cli_posix_mkdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ mode_t mode);
+NTSTATUS cli_posix_mkdir_recv(struct tevent_req *req);
+NTSTATUS cli_posix_mkdir(struct cli_state *cli, const char *fname, mode_t mode);
+
+struct tevent_req *cli_posix_unlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_posix_unlink_recv(struct tevent_req *req);
+NTSTATUS cli_posix_unlink(struct cli_state *cli, const char *fname);
+
+struct tevent_req *cli_posix_rmdir_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_posix_rmdir_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx);
+NTSTATUS cli_posix_rmdir(struct cli_state *cli, const char *fname);
+struct tevent_req *cli_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint32_t buffer_size,
+ uint32_t completion_filter, bool recursive);
+NTSTATUS cli_notify_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_changes,
+ struct notify_change **pchanges);
+NTSTATUS cli_notify(struct cli_state *cli, uint16_t fnum, uint32_t buffer_size,
+ uint32_t completion_filter, bool recursive,
+ TALLOC_CTX *mem_ctx, uint32_t *pnum_changes,
+ struct notify_change **pchanges);
+
+struct tevent_req *cli_nttrans_create_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint8_t SecurityFlags,
+ struct security_descriptor *secdesc,
+ struct ea_struct *eas,
+ int num_eas);
+NTSTATUS cli_nttrans_create_recv(struct tevent_req *req,
+ uint16_t *fnum,
+ struct smb_create_returns *cr);
+NTSTATUS cli_nttrans_create(struct cli_state *cli,
+ const char *fname,
+ uint32_t CreatFlags,
+ uint32_t DesiredAccess,
+ uint32_t FileAttributes,
+ uint32_t ShareAccess,
+ uint32_t CreateDisposition,
+ uint32_t CreateOptions,
+ uint8_t SecurityFlags,
+ struct security_descriptor *secdesc,
+ struct ea_struct *eas,
+ int num_eas,
+ uint16_t *pfid,
+ struct smb_create_returns *cr);
+
+/* The following definitions come from libsmb/clifsinfo.c */
+
+struct tevent_req *cli_unix_extensions_version_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli);
+NTSTATUS cli_unix_extensions_version_recv(struct tevent_req *req,
+ uint16_t *pmajor, uint16_t *pminor,
+ uint32_t *pcaplow,
+ uint32_t *pcaphigh);
+NTSTATUS cli_unix_extensions_version(struct cli_state *cli, uint16_t *pmajor,
+ uint16_t *pminor, uint32_t *pcaplow,
+ uint32_t *pcaphigh);
+struct tevent_req *cli_set_unix_extensions_capabilities_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ uint16_t major, uint16_t minor, uint32_t caplow, uint32_t caphigh);
+NTSTATUS cli_set_unix_extensions_capabilities_recv(struct tevent_req *req);
+NTSTATUS cli_set_unix_extensions_capabilities(struct cli_state *cli,
+ uint16_t major, uint16_t minor,
+ uint32_t caplow, uint32_t caphigh);
+struct tevent_req *cli_get_fs_attr_info_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli);
+NTSTATUS cli_get_fs_attr_info_recv(struct tevent_req *req, uint32_t *fs_attr);
+NTSTATUS cli_get_fs_attr_info(struct cli_state *cli, uint32_t *fs_attr);
+NTSTATUS cli_get_fs_volume_info(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx, char **volume_name,
+ uint32_t *pserial_number, time_t *pdate);
+NTSTATUS cli_get_fs_full_size_info(struct cli_state *cli,
+ uint64_t *total_allocation_units,
+ uint64_t *caller_allocation_units,
+ uint64_t *actual_allocation_units,
+ uint64_t *sectors_per_allocation_unit,
+ uint64_t *bytes_per_sector);
+NTSTATUS cli_get_posix_fs_info(struct cli_state *cli,
+ uint32_t *optimal_transfer_size,
+ uint32_t *block_size,
+ uint64_t *total_blocks,
+ uint64_t *blocks_available,
+ uint64_t *user_blocks_available,
+ uint64_t *total_file_nodes,
+ uint64_t *free_file_nodes,
+ uint64_t *fs_identifier);
+struct tevent_req *cli_posix_whoami_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli);
+NTSTATUS cli_posix_whoami_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint64_t *puid,
+ uint64_t *pgid,
+ uint32_t *pnum_gids,
+ uint64_t **pgids,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids,
+ bool *pguest);
+NTSTATUS cli_posix_whoami(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ uint64_t *puid,
+ uint64_t *pgid,
+ uint32_t *num_gids,
+ uint64_t **gids,
+ uint32_t *num_sids,
+ struct dom_sid **sids,
+ bool *pguest);
+
+/* The following definitions come from libsmb/clilist.c */
+
+NTSTATUS is_bad_finfo_name(const struct cli_state *cli,
+ const struct file_info *finfo);
+
+NTSTATUS cli_list_old(struct cli_state *cli,const char *Mask,uint32_t attribute,
+ NTSTATUS (*fn)(struct file_info *,
+ const char *, void *), void *state);
+NTSTATUS cli_list_trans(struct cli_state *cli, const char *mask,
+ uint32_t attribute, int info_level,
+ NTSTATUS (*fn)(
+ struct file_info *finfo,
+ const char *mask,
+ void *private_data),
+ void *private_data);
+struct tevent_req *cli_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *mask,
+ uint32_t attribute,
+ uint16_t info_level);
+NTSTATUS cli_list_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct file_info **pfinfo);
+NTSTATUS cli_list(struct cli_state *cli,
+ const char *mask,
+ uint32_t attribute,
+ NTSTATUS (*fn)(struct file_info *finfo,
+ const char *mask,
+ void *private_data),
+ void *private_data);
+
+/* The following definitions come from libsmb/climessage.c */
+
+struct tevent_req *cli_message_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *host, const char *username,
+ const char *message);
+NTSTATUS cli_message_recv(struct tevent_req *req);
+NTSTATUS cli_message(struct cli_state *cli, const char *host,
+ const char *username, const char *message);
+
+/* The following definitions come from libsmb/clioplock.c */
+
+struct tevent_req *cli_smb_oplock_break_waiter_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli);
+NTSTATUS cli_smb_oplock_break_waiter_recv(struct tevent_req *req,
+ uint16_t *pfnum,
+ uint8_t *plevel);
+
+struct tevent_req *cli_oplock_ack_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum, uint8_t level);
+NTSTATUS cli_oplock_ack_recv(struct tevent_req *req);
+
+/* The following definitions come from libsmb/cliprint.c */
+
+NTSTATUS cli_print_queue(struct cli_state *cli,
+ void (*fn)(struct print_job_info *));
+NTSTATUS cli_printjob_del(struct cli_state *cli, int job);
+
+/* The following definitions come from libsmb/cliquota.c */
+
+NTSTATUS cli_get_quota_handle(struct cli_state *cli, uint16_t *quota_fnum);
+void free_ntquota_list(SMB_NTQUOTA_LIST **qt_list);
+bool parse_user_quota_record(const uint8_t *rdata,
+ unsigned int rdata_count,
+ unsigned int *offset,
+ SMB_NTQUOTA_STRUCT *pqt);
+bool add_record_to_ntquota_list(TALLOC_CTX *mem_ctx,
+ SMB_NTQUOTA_STRUCT *pqt,
+ SMB_NTQUOTA_LIST **pqt_list);
+NTSTATUS parse_user_quota_list(const uint8_t *curdata,
+ uint32_t curdata_size,
+ TALLOC_CTX *mem_ctx,
+ SMB_NTQUOTA_LIST **pqt_list);
+NTSTATUS parse_fs_quota_buffer(const uint8_t *rdata,
+ unsigned int rdata_count,
+ SMB_NTQUOTA_STRUCT *pqt);
+NTSTATUS build_user_quota_buffer(SMB_NTQUOTA_LIST *qt_list,
+ uint32_t maxlen,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *outbuf,
+ SMB_NTQUOTA_LIST **end_ptr);
+NTSTATUS build_fs_quota_buffer(TALLOC_CTX *mem_ctx,
+ const SMB_NTQUOTA_STRUCT *pqt,
+ DATA_BLOB *blob,
+ uint32_t maxlen);
+NTSTATUS cli_get_user_quota(struct cli_state *cli, int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt);
+NTSTATUS cli_set_user_quota(struct cli_state *cli,
+ int quota_fnum,
+ SMB_NTQUOTA_LIST *qtl);
+NTSTATUS cli_list_user_quota(struct cli_state *cli, int quota_fnum,
+ SMB_NTQUOTA_LIST **pqt_list);
+NTSTATUS cli_get_fs_quota_info(struct cli_state *cli, int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt);
+NTSTATUS cli_set_fs_quota_info(struct cli_state *cli, int quota_fnum,
+ SMB_NTQUOTA_STRUCT *pqt);
+
+/* The following definitions come from libsmb/clireadwrite.c */
+
+struct tevent_req *cli_read_andx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ off_t offset, size_t size,
+ struct tevent_req **psmbreq);
+struct tevent_req *cli_read_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ off_t offset, size_t size);
+NTSTATUS cli_read_andx_recv(struct tevent_req *req, ssize_t *received,
+ uint8_t **rcvbuf);
+struct tevent_req *cli_pull_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum, off_t start_offset,
+ off_t size, size_t window_size,
+ NTSTATUS (*sink)(char *buf, size_t n,
+ void *priv),
+ void *priv);
+NTSTATUS cli_pull_recv(struct tevent_req *req, off_t *received);
+NTSTATUS cli_pull(struct cli_state *cli, uint16_t fnum,
+ off_t start_offset, off_t size, size_t window_size,
+ NTSTATUS (*sink)(char *buf, size_t n, void *priv),
+ void *priv, off_t *received);
+NTSTATUS cli_read_sink(char *buf, size_t n, void *priv);
+struct tevent_req *cli_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ char *buf,
+ off_t offset,
+ size_t size);
+NTSTATUS cli_read_recv(struct tevent_req *req, size_t *received);
+NTSTATUS cli_read(struct cli_state *cli, uint16_t fnum,
+ char *buf, off_t offset, size_t size,
+ size_t *nread);
+NTSTATUS cli_smbwrite(struct cli_state *cli, uint16_t fnum, char *buf,
+ off_t offset, size_t size1, size_t *ptotal);
+struct tevent_req *cli_write_andx_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t mode, const uint8_t *buf,
+ off_t offset, size_t size,
+ struct tevent_req **reqs_before,
+ int num_reqs_before,
+ struct tevent_req **psmbreq);
+struct tevent_req *cli_write_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t mode, const uint8_t *buf,
+ off_t offset, size_t size);
+NTSTATUS cli_write_andx_recv(struct tevent_req *req, size_t *pwritten);
+
+struct tevent_req *cli_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli, uint16_t fnum,
+ uint16_t mode, const uint8_t *buf,
+ off_t offset, size_t size);
+NTSTATUS cli_write_recv(struct tevent_req *req, size_t *pwritten);
+
+struct tevent_req *cli_writeall_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint16_t mode,
+ const uint8_t *buf,
+ off_t offset,
+ size_t size);
+NTSTATUS cli_writeall_recv(struct tevent_req *req, size_t *pwritten);
+NTSTATUS cli_writeall(struct cli_state *cli, uint16_t fnum, uint16_t mode,
+ const uint8_t *buf, off_t offset, size_t size,
+ size_t *pwritten);
+
+struct tevent_req *cli_push_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum, uint16_t mode,
+ off_t start_offset, size_t window_size,
+ size_t (*source)(uint8_t *buf, size_t n,
+ void *priv),
+ void *priv);
+NTSTATUS cli_push_recv(struct tevent_req *req);
+NTSTATUS cli_push(struct cli_state *cli, uint16_t fnum, uint16_t mode,
+ off_t start_offset, size_t window_size,
+ size_t (*source)(uint8_t *buf, size_t n, void *priv),
+ void *priv);
+
+NTSTATUS cli_splice(struct cli_state *srccli, struct cli_state *dstcli,
+ uint16_t src_fnum, uint16_t dst_fnum,
+ off_t size,
+ off_t src_offset, off_t dst_offset,
+ off_t *written,
+ int (*splice_cb)(off_t n, void *priv), void *priv);
+
+/* The following definitions come from libsmb/clisecdesc.c */
+
+struct tevent_req *cli_query_security_descriptor_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info);
+NTSTATUS cli_query_security_descriptor_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **sd);
+NTSTATUS cli_query_security_descriptor(struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **sd);
+NTSTATUS cli_query_secdesc(struct cli_state *cli, uint16_t fnum,
+ TALLOC_CTX *mem_ctx, struct security_descriptor **sd);
+struct tevent_req *cli_set_security_descriptor_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info,
+ const struct security_descriptor *sd);
+NTSTATUS cli_set_security_descriptor_recv(struct tevent_req *req);
+NTSTATUS cli_set_security_descriptor(struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t sec_info,
+ const struct security_descriptor *sd);
+NTSTATUS cli_set_secdesc(struct cli_state *cli, uint16_t fnum,
+ const struct security_descriptor *sd);
+
+NTSTATUS cli_query_mxac(struct cli_state *cli,
+ const char *filename,
+ uint32_t *mxac);
+
+/* The following definitions come from libsmb/clitrans.c */
+
+struct tevent_req *cli_trans_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, uint16_t additional_flags2, uint8_t cmd,
+ const char *pipe_name, uint16_t fid, uint16_t function, int flags,
+ uint16_t *setup, uint8_t num_setup, uint8_t max_setup,
+ uint8_t *param, uint32_t num_param, uint32_t max_param,
+ uint8_t *data, uint32_t num_data, uint32_t max_data);
+NTSTATUS cli_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint16_t *recv_flags2,
+ uint16_t **setup, uint8_t min_setup,
+ uint8_t *num_setup,
+ uint8_t **param, uint32_t min_param,
+ uint32_t *num_param,
+ uint8_t **data, uint32_t min_data,
+ uint32_t *num_data);
+NTSTATUS cli_trans(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ uint8_t trans_cmd,
+ const char *pipe_name, uint16_t fid, uint16_t function,
+ int flags,
+ uint16_t *setup, uint8_t num_setup, uint8_t max_setup,
+ uint8_t *param, uint32_t num_param, uint32_t max_param,
+ uint8_t *data, uint32_t num_data, uint32_t max_data,
+ uint16_t *recv_flags2,
+ uint16_t **rsetup, uint8_t min_rsetup, uint8_t *num_rsetup,
+ uint8_t **rparam, uint32_t min_rparam, uint32_t *num_rparam,
+ uint8_t **rdata, uint32_t min_rdata, uint32_t *num_rdata);
+
+/* The following definitions come from libsmb/clisymlink.c */
+
+struct tevent_req *cli_create_reparse_point_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ DATA_BLOB reparse_blob);
+NTSTATUS cli_create_reparse_point_recv(struct tevent_req *req);
+
+struct tevent_req *cli_symlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *oldpath,
+ const char *newpath,
+ uint32_t flags);
+NTSTATUS cli_symlink_recv(struct tevent_req *req);
+NTSTATUS cli_symlink(struct cli_state *cli, const char *oldname,
+ const char *newname, uint32_t flags);
+
+struct tevent_req *cli_get_reparse_data_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_get_reparse_data_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **_data,
+ uint32_t *_datalen);
+NTSTATUS cli_get_reparse_data(struct cli_state *cli,
+ const char *fname,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **_data,
+ uint32_t *_datalen);
+
+struct tevent_req *cli_readlink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname);
+NTSTATUS cli_readlink_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **psubstitute_name, char **pprint_name,
+ uint32_t *pflags);
+NTSTATUS cli_readlink(struct cli_state *cli, const char *fname,
+ TALLOC_CTX *mem_ctx, char **psubstitute_name,
+ char **pprint_name, uint32_t *pflags);
+
+NTSTATUS fill_quota_buffer(TALLOC_CTX *mem_ctx,
+ SMB_NTQUOTA_LIST *tmp_list,
+ bool return_single,
+ uint32_t max_data,
+ DATA_BLOB *blob,
+ SMB_NTQUOTA_LIST **end_ptr);
+/* The following definitions come from libsmb/passchange.c */
+
+NTSTATUS remote_password_change(const char *remote_machine,
+ const char *domain, const char *user_name,
+ const char *old_passwd, const char *new_passwd,
+ char **err_str);
+
+#endif /* _LIBSMB_PROTO_H_ */
diff --git a/source3/libsmb/pylibsmb.c b/source3/libsmb/pylibsmb.c
new file mode 100644
index 0000000..52ba449
--- /dev/null
+++ b/source3/libsmb/pylibsmb.c
@@ -0,0 +1,3030 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SMB client Python bindings used internally by Samba (for things like
+ * samba-tool). These Python bindings may change without warning, and so
+ * should not be used outside of the Samba codebase.
+ *
+ * Copyright (C) Volker Lendecke 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+Template code to use this library:
+
+-------------------------
+from samba.samba3 import libsmb_samba_internal as libsmb
+from samba.samba3 import param as s3param
+from samba import (credentials,NTSTATUSError)
+
+lp = s3param.get_context()
+lp.load("/etc/samba/smb.conf");
+
+creds = credentials.Credentials()
+creds.guess(lp)
+creds.set_username("administrator")
+creds.set_password("1234")
+
+c = libsmb.Conn("127.0.0.1",
+ "tmp",
+ lp,
+ creds,
+ multi_threaded=True)
+-------------------------
+*/
+
+#include "lib/replace/system/python.h"
+#include "includes.h"
+#include "python/py3compat.h"
+#include "python/modules.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "libcli/smb/smb2_negotiate_context.h"
+#include "libcli/smb/reparse.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/security.h"
+#include "system/select.h"
+#include "source4/libcli/util/pyerrors.h"
+#include "auth/credentials/pycredentials.h"
+#include "trans2.h"
+#include "libsmb/clirap.h"
+#include "librpc/rpc/pyrpc_util.h"
+
+#define LIST_ATTRIBUTE_MASK \
+ (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN)
+
+static PyTypeObject *dom_sid_Type = NULL;
+
+static PyTypeObject *get_pytype(const char *module, const char *type)
+{
+ PyObject *mod;
+ PyTypeObject *result;
+
+ mod = PyImport_ImportModule(module);
+ if (mod == NULL) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Unable to import %s to check type %s",
+ module, type);
+ return NULL;
+ }
+ result = (PyTypeObject *)PyObject_GetAttrString(mod, type);
+ Py_DECREF(mod);
+ if (result == NULL) {
+ PyErr_Format(PyExc_RuntimeError,
+ "Unable to find type %s in module %s",
+ module, type);
+ return NULL;
+ }
+ return result;
+}
+
+/*
+ * We're using "const char * const *" for keywords,
+ * PyArg_ParseTupleAndKeywords expects a "char **". Confine the
+ * inevitable warnings to just one place.
+ */
+static int ParseTupleAndKeywords(PyObject *args, PyObject *kw,
+ const char *format, const char * const *keywords,
+ ...)
+{
+ char **_keywords = discard_const_p(char *, keywords);
+ va_list a;
+ int ret;
+ va_start(a, keywords);
+ ret = PyArg_VaParseTupleAndKeywords(args, kw, format,
+ _keywords, a);
+ va_end(a);
+ return ret;
+}
+
+struct py_cli_thread;
+
+struct py_cli_oplock_break {
+ uint16_t fnum;
+ uint8_t level;
+};
+
+struct py_cli_state {
+ PyObject_HEAD
+ struct cli_state *cli;
+ struct tevent_context *ev;
+ int (*req_wait_fn)(struct tevent_context *ev,
+ struct tevent_req *req);
+ struct py_cli_thread *thread_state;
+
+ struct tevent_req *oplock_waiter;
+ struct py_cli_oplock_break *oplock_breaks;
+ struct py_tevent_cond *oplock_cond;
+};
+
+#ifdef HAVE_PTHREAD
+
+#include <pthread.h>
+
+struct py_cli_thread {
+
+ /*
+ * Pipe to make the poll thread wake up in our destructor, so
+ * that we can exit and join the thread.
+ */
+ int shutdown_pipe[2];
+ struct tevent_fd *shutdown_fde;
+ bool do_shutdown;
+ pthread_t id;
+
+ /*
+ * Thread state to release the GIL during the poll(2) syscall
+ */
+ PyThreadState *py_threadstate;
+};
+
+static void *py_cli_state_poll_thread(void *private_data)
+{
+ struct py_cli_state *self = (struct py_cli_state *)private_data;
+ struct py_cli_thread *t = self->thread_state;
+ PyGILState_STATE gstate;
+
+ gstate = PyGILState_Ensure();
+
+ while (!t->do_shutdown) {
+ int ret;
+ ret = tevent_loop_once(self->ev);
+ assert(ret == 0);
+ }
+ PyGILState_Release(gstate);
+ return NULL;
+}
+
+static void py_cli_state_trace_callback(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct py_cli_state *self = (struct py_cli_state *)private_data;
+ struct py_cli_thread *t = self->thread_state;
+
+ switch(point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ assert(t->py_threadstate == NULL);
+ t->py_threadstate = PyEval_SaveThread();
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ assert(t->py_threadstate != NULL);
+ PyEval_RestoreThread(t->py_threadstate);
+ t->py_threadstate = NULL;
+ break;
+ default:
+ break;
+ }
+}
+
+static void py_cli_state_shutdown_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct py_cli_state *self = (struct py_cli_state *)private_data;
+ struct py_cli_thread *t = self->thread_state;
+
+ if ((flags & TEVENT_FD_READ) == 0) {
+ return;
+ }
+ TALLOC_FREE(t->shutdown_fde);
+ t->do_shutdown = true;
+}
+
+static int py_cli_thread_destructor(struct py_cli_thread *t)
+{
+ char c = 0;
+ ssize_t written;
+ int ret;
+
+ do {
+ /*
+ * This will wake the poll thread from the poll(2)
+ */
+ written = write(t->shutdown_pipe[1], &c, 1);
+ } while ((written == -1) && (errno == EINTR));
+
+ /*
+ * Allow the poll thread to do its own cleanup under the GIL
+ */
+ Py_BEGIN_ALLOW_THREADS
+ ret = pthread_join(t->id, NULL);
+ Py_END_ALLOW_THREADS
+ assert(ret == 0);
+
+ if (t->shutdown_pipe[0] != -1) {
+ close(t->shutdown_pipe[0]);
+ t->shutdown_pipe[0] = -1;
+ }
+ if (t->shutdown_pipe[1] != -1) {
+ close(t->shutdown_pipe[1]);
+ t->shutdown_pipe[1] = -1;
+ }
+ return 0;
+}
+
+static int py_tevent_cond_req_wait(struct tevent_context *ev,
+ struct tevent_req *req);
+
+static bool py_cli_state_setup_mt_ev(struct py_cli_state *self)
+{
+ struct py_cli_thread *t = NULL;
+ int ret;
+
+ self->ev = tevent_context_init_byname(NULL, "poll_mt");
+ if (self->ev == NULL) {
+ goto fail;
+ }
+ samba_tevent_set_debug(self->ev, "pylibsmb_tevent_mt");
+ tevent_set_trace_callback(self->ev, py_cli_state_trace_callback, self);
+
+ self->req_wait_fn = py_tevent_cond_req_wait;
+
+ self->thread_state = talloc_zero(NULL, struct py_cli_thread);
+ if (self->thread_state == NULL) {
+ goto fail;
+ }
+ t = self->thread_state;
+
+ ret = pipe(t->shutdown_pipe);
+ if (ret == -1) {
+ goto fail;
+ }
+ t->shutdown_fde = tevent_add_fd(
+ self->ev, self->ev, t->shutdown_pipe[0], TEVENT_FD_READ,
+ py_cli_state_shutdown_handler, self);
+ if (t->shutdown_fde == NULL) {
+ goto fail;
+ }
+
+ PyEval_InitThreads();
+
+ ret = pthread_create(&t->id, NULL, py_cli_state_poll_thread, self);
+ if (ret != 0) {
+ goto fail;
+ }
+ talloc_set_destructor(self->thread_state, py_cli_thread_destructor);
+ return true;
+
+fail:
+ if (t != NULL) {
+ TALLOC_FREE(t->shutdown_fde);
+
+ if (t->shutdown_pipe[0] != -1) {
+ close(t->shutdown_pipe[0]);
+ t->shutdown_pipe[0] = -1;
+ }
+ if (t->shutdown_pipe[1] != -1) {
+ close(t->shutdown_pipe[1]);
+ t->shutdown_pipe[1] = -1;
+ }
+ }
+
+ TALLOC_FREE(self->thread_state);
+ TALLOC_FREE(self->ev);
+ return false;
+}
+
+struct py_tevent_cond {
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ bool is_done;
+};
+
+static void py_tevent_signalme(struct tevent_req *req);
+
+static int py_tevent_cond_wait(struct py_tevent_cond *cond)
+{
+ int ret, result;
+
+ result = pthread_mutex_init(&cond->mutex, NULL);
+ if (result != 0) {
+ goto fail;
+ }
+ result = pthread_cond_init(&cond->cond, NULL);
+ if (result != 0) {
+ goto fail_mutex;
+ }
+
+ result = pthread_mutex_lock(&cond->mutex);
+ if (result != 0) {
+ goto fail_cond;
+ }
+
+ cond->is_done = false;
+
+ while (!cond->is_done) {
+
+ Py_BEGIN_ALLOW_THREADS
+ result = pthread_cond_wait(&cond->cond, &cond->mutex);
+ Py_END_ALLOW_THREADS
+
+ if (result != 0) {
+ goto fail_unlock;
+ }
+ }
+
+fail_unlock:
+ ret = pthread_mutex_unlock(&cond->mutex);
+ assert(ret == 0);
+fail_cond:
+ ret = pthread_cond_destroy(&cond->cond);
+ assert(ret == 0);
+fail_mutex:
+ ret = pthread_mutex_destroy(&cond->mutex);
+ assert(ret == 0);
+fail:
+ return result;
+}
+
+static int py_tevent_cond_req_wait(struct tevent_context *ev,
+ struct tevent_req *req)
+{
+ struct py_tevent_cond cond;
+ tevent_req_set_callback(req, py_tevent_signalme, &cond);
+ return py_tevent_cond_wait(&cond);
+}
+
+static void py_tevent_cond_signal(struct py_tevent_cond *cond)
+{
+ int ret;
+
+ ret = pthread_mutex_lock(&cond->mutex);
+ assert(ret == 0);
+
+ cond->is_done = true;
+
+ ret = pthread_cond_signal(&cond->cond);
+ assert(ret == 0);
+ ret = pthread_mutex_unlock(&cond->mutex);
+ assert(ret == 0);
+}
+
+static void py_tevent_signalme(struct tevent_req *req)
+{
+ struct py_tevent_cond *cond = (struct py_tevent_cond *)
+ tevent_req_callback_data_void(req);
+
+ py_tevent_cond_signal(cond);
+}
+
+#endif
+
+static int py_tevent_req_wait(struct tevent_context *ev,
+ struct tevent_req *req);
+
+static bool py_cli_state_setup_ev(struct py_cli_state *self)
+{
+ self->ev = tevent_context_init(NULL);
+ if (self->ev == NULL) {
+ return false;
+ }
+
+ samba_tevent_set_debug(self->ev, "pylibsmb_tevent");
+
+ self->req_wait_fn = py_tevent_req_wait;
+
+ return true;
+}
+
+static int py_tevent_req_wait(struct tevent_context *ev,
+ struct tevent_req *req)
+{
+ while (tevent_req_is_in_progress(req)) {
+ int ret;
+
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static bool py_tevent_req_wait_exc(struct py_cli_state *self,
+ struct tevent_req *req)
+{
+ int ret;
+
+ if (req == NULL) {
+ PyErr_NoMemory();
+ return false;
+ }
+ ret = self->req_wait_fn(self->ev, req);
+ if (ret != 0) {
+ TALLOC_FREE(req);
+ errno = ret;
+ PyErr_SetFromErrno(PyExc_RuntimeError);
+ return false;
+ }
+ return true;
+}
+
+static PyObject *py_cli_state_new(PyTypeObject *type, PyObject *args,
+ PyObject *kwds)
+{
+ struct py_cli_state *self;
+
+ self = (struct py_cli_state *)type->tp_alloc(type, 0);
+ if (self == NULL) {
+ return NULL;
+ }
+ self->cli = NULL;
+ self->ev = NULL;
+ self->thread_state = NULL;
+ self->oplock_waiter = NULL;
+ self->oplock_cond = NULL;
+ self->oplock_breaks = NULL;
+ return (PyObject *)self;
+}
+
+static struct smb2_negotiate_contexts *py_cli_get_negotiate_contexts(
+ TALLOC_CTX *mem_ctx, PyObject *list)
+{
+ struct smb2_negotiate_contexts *ctxs = NULL;
+ Py_ssize_t i, len;
+ int ret;
+
+ ret = PyList_Check(list);
+ if (!ret) {
+ goto fail;
+ }
+
+ len = PyList_Size(list);
+ if (len == 0) {
+ goto fail;
+ }
+
+ ctxs = talloc_zero(mem_ctx, struct smb2_negotiate_contexts);
+ if (ctxs == NULL) {
+ goto fail;
+ }
+
+ for (i=0; i<len; i++) {
+ NTSTATUS status;
+
+ PyObject *t = PyList_GetItem(list, i);
+ Py_ssize_t tlen;
+
+ PyObject *ptype = NULL;
+ long type;
+
+ PyObject *pdata = NULL;
+ DATA_BLOB data = { .data = NULL, };
+
+ if (t == NULL) {
+ goto fail;
+ }
+
+ ret = PyTuple_Check(t);
+ if (!ret) {
+ goto fail;
+ }
+
+ tlen = PyTuple_Size(t);
+ if (tlen != 2) {
+ goto fail;
+ }
+
+ ptype = PyTuple_GetItem(t, 0);
+ if (ptype == NULL) {
+ goto fail;
+ }
+ type = PyLong_AsLong(ptype);
+ if ((type < 0) || (type > UINT16_MAX)) {
+ goto fail;
+ }
+
+ pdata = PyTuple_GetItem(t, 1);
+
+ ret = PyBytes_Check(pdata);
+ if (!ret) {
+ goto fail;
+ }
+
+ data.data = (uint8_t *)PyBytes_AsString(pdata);
+ data.length = PyBytes_Size(pdata);
+
+ status = smb2_negotiate_context_add(
+ ctxs, ctxs, type, data.data, data.length);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+ return ctxs;
+
+fail:
+ TALLOC_FREE(ctxs);
+ return NULL;
+}
+
+static void py_cli_got_oplock_break(struct tevent_req *req);
+
+static int py_cli_state_init(struct py_cli_state *self, PyObject *args,
+ PyObject *kwds)
+{
+ NTSTATUS status;
+ char *host, *share;
+ PyObject *creds = NULL;
+ struct cli_credentials *cli_creds;
+ PyObject *py_lp = Py_None;
+ PyObject *py_multi_threaded = Py_False;
+ bool multi_threaded = false;
+ PyObject *py_force_smb1 = Py_False;
+ bool force_smb1 = false;
+ PyObject *py_ipc = Py_False;
+ PyObject *py_posix = Py_False;
+ PyObject *py_negotiate_contexts = NULL;
+ struct smb2_negotiate_contexts *negotiate_contexts = NULL;
+ bool use_ipc = false;
+ bool request_posix = false;
+ struct tevent_req *req;
+ bool ret;
+ int flags = 0;
+
+ static const char *kwlist[] = {
+ "host", "share", "lp", "creds",
+ "multi_threaded", "force_smb1",
+ "ipc",
+ "posix",
+ "negotiate_contexts",
+ NULL
+ };
+
+ PyTypeObject *py_type_Credentials = get_pytype(
+ "samba.credentials", "Credentials");
+ if (py_type_Credentials == NULL) {
+ return -1;
+ }
+
+ ret = ParseTupleAndKeywords(
+ args, kwds, "ssO|O!OOOOO", kwlist,
+ &host, &share, &py_lp,
+ py_type_Credentials, &creds,
+ &py_multi_threaded,
+ &py_force_smb1,
+ &py_ipc,
+ &py_posix,
+ &py_negotiate_contexts);
+
+ Py_DECREF(py_type_Credentials);
+
+ if (!ret) {
+ return -1;
+ }
+
+ multi_threaded = PyObject_IsTrue(py_multi_threaded);
+ force_smb1 = PyObject_IsTrue(py_force_smb1);
+
+ if (force_smb1) {
+ /*
+ * As most of the cli_*_send() function
+ * don't support SMB2 (it's only plugged
+ * into the sync wrapper functions currently)
+ * we have a way to force SMB1.
+ */
+ flags = CLI_FULL_CONNECTION_FORCE_SMB1;
+ }
+
+ use_ipc = PyObject_IsTrue(py_ipc);
+ if (use_ipc) {
+ flags |= CLI_FULL_CONNECTION_IPC;
+ }
+
+ request_posix = PyObject_IsTrue(py_posix);
+ if (request_posix) {
+ flags |= CLI_FULL_CONNECTION_REQUEST_POSIX;
+ }
+
+ if (py_negotiate_contexts != NULL) {
+ negotiate_contexts = py_cli_get_negotiate_contexts(
+ talloc_tos(), py_negotiate_contexts);
+ if (negotiate_contexts == NULL) {
+ return -1;
+ }
+ }
+
+ if (multi_threaded) {
+#ifdef HAVE_PTHREAD
+ ret = py_cli_state_setup_mt_ev(self);
+ if (!ret) {
+ return -1;
+ }
+#else
+ PyErr_SetString(PyExc_RuntimeError,
+ "No PTHREAD support available");
+ return -1;
+#endif
+ } else {
+ ret = py_cli_state_setup_ev(self);
+ if (!ret) {
+ return -1;
+ }
+ }
+
+ if (creds == NULL) {
+ cli_creds = cli_credentials_init_anon(NULL);
+ } else {
+ cli_creds = PyCredentials_AsCliCredentials(creds);
+ }
+
+ req = cli_full_connection_creds_send(
+ NULL, self->ev, "myname", host, NULL, 0, share, "?????",
+ cli_creds, flags,
+ negotiate_contexts);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return -1;
+ }
+ status = cli_full_connection_creds_recv(req, &self->cli);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return -1;
+ }
+
+ /*
+ * Oplocks require a multi threaded connection
+ */
+ if (self->thread_state == NULL) {
+ return 0;
+ }
+
+ self->oplock_waiter = cli_smb_oplock_break_waiter_send(
+ self->ev, self->ev, self->cli);
+ if (self->oplock_waiter == NULL) {
+ PyErr_NoMemory();
+ return -1;
+ }
+ tevent_req_set_callback(self->oplock_waiter, py_cli_got_oplock_break,
+ self);
+ return 0;
+}
+
+static void py_cli_got_oplock_break(struct tevent_req *req)
+{
+ struct py_cli_state *self = (struct py_cli_state *)
+ tevent_req_callback_data_void(req);
+ struct py_cli_oplock_break b;
+ struct py_cli_oplock_break *tmp;
+ size_t num_breaks;
+ NTSTATUS status;
+
+ status = cli_smb_oplock_break_waiter_recv(req, &b.fnum, &b.level);
+ TALLOC_FREE(req);
+ self->oplock_waiter = NULL;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ num_breaks = talloc_array_length(self->oplock_breaks);
+ tmp = talloc_realloc(self->ev, self->oplock_breaks,
+ struct py_cli_oplock_break, num_breaks+1);
+ if (tmp == NULL) {
+ return;
+ }
+ self->oplock_breaks = tmp;
+ self->oplock_breaks[num_breaks] = b;
+
+ if (self->oplock_cond != NULL) {
+ py_tevent_cond_signal(self->oplock_cond);
+ }
+
+ self->oplock_waiter = cli_smb_oplock_break_waiter_send(
+ self->ev, self->ev, self->cli);
+ if (self->oplock_waiter == NULL) {
+ return;
+ }
+ tevent_req_set_callback(self->oplock_waiter, py_cli_got_oplock_break,
+ self);
+}
+
+static PyObject *py_cli_get_oplock_break(struct py_cli_state *self,
+ PyObject *args)
+{
+ size_t num_oplock_breaks;
+
+ if (!PyArg_ParseTuple(args, "")) {
+ return NULL;
+ }
+
+ if (self->thread_state == NULL) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "get_oplock_break() only possible on "
+ "a multi_threaded connection");
+ return NULL;
+ }
+
+ if (self->oplock_cond != NULL) {
+ errno = EBUSY;
+ PyErr_SetFromErrno(PyExc_RuntimeError);
+ return NULL;
+ }
+
+ num_oplock_breaks = talloc_array_length(self->oplock_breaks);
+
+ if (num_oplock_breaks == 0) {
+ struct py_tevent_cond cond;
+ int ret;
+
+ self->oplock_cond = &cond;
+ ret = py_tevent_cond_wait(&cond);
+ self->oplock_cond = NULL;
+
+ if (ret != 0) {
+ errno = ret;
+ PyErr_SetFromErrno(PyExc_RuntimeError);
+ return NULL;
+ }
+ }
+
+ num_oplock_breaks = talloc_array_length(self->oplock_breaks);
+ if (num_oplock_breaks > 0) {
+ PyObject *result;
+
+ result = Py_BuildValue(
+ "{s:i,s:i}",
+ "fnum", self->oplock_breaks[0].fnum,
+ "level", self->oplock_breaks[0].level);
+
+ memmove(&self->oplock_breaks[0], &self->oplock_breaks[1],
+ sizeof(self->oplock_breaks[0]) *
+ (num_oplock_breaks - 1));
+ self->oplock_breaks = talloc_realloc(
+ NULL, self->oplock_breaks, struct py_cli_oplock_break,
+ num_oplock_breaks - 1);
+
+ return result;
+ }
+ Py_RETURN_NONE;
+}
+
+static void py_cli_state_dealloc(struct py_cli_state *self)
+{
+ TALLOC_FREE(self->thread_state);
+ TALLOC_FREE(self->oplock_waiter);
+ TALLOC_FREE(self->ev);
+
+ if (self->cli != NULL) {
+ cli_shutdown(self->cli);
+ self->cli = NULL;
+ }
+ Py_TYPE(self)->tp_free((PyObject *)self);
+}
+
+static PyObject *py_cli_settimeout(struct py_cli_state *self, PyObject *args)
+{
+ unsigned int nmsecs = 0;
+ unsigned int omsecs = 0;
+
+ if (!PyArg_ParseTuple(args, "I", &nmsecs)) {
+ return NULL;
+ }
+
+ omsecs = cli_set_timeout(self->cli, nmsecs);
+
+ return PyLong_FromLong(omsecs);
+}
+
+static PyObject *py_cli_echo(struct py_cli_state *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ DATA_BLOB data = data_blob_string_const("keepalive");
+ struct tevent_req *req = NULL;
+ NTSTATUS status;
+
+ req = cli_echo_send(NULL, self->ev, self->cli, 1, data);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_echo_recv(req);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_cli_create(struct py_cli_state *self, PyObject *args,
+ PyObject *kwds)
+{
+ char *fname;
+ unsigned CreateFlags = 0;
+ unsigned DesiredAccess = FILE_GENERIC_READ;
+ unsigned FileAttributes = 0;
+ unsigned ShareAccess = 0;
+ unsigned CreateDisposition = FILE_OPEN;
+ unsigned CreateOptions = 0;
+ unsigned ImpersonationLevel = SMB2_IMPERSONATION_IMPERSONATION;
+ unsigned SecurityFlags = 0;
+ uint16_t fnum;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ static const char *kwlist[] = {
+ "Name", "CreateFlags", "DesiredAccess", "FileAttributes",
+ "ShareAccess", "CreateDisposition", "CreateOptions",
+ "ImpersonationLevel", "SecurityFlags", NULL };
+
+ if (!ParseTupleAndKeywords(
+ args, kwds, "s|IIIIIIII", kwlist,
+ &fname, &CreateFlags, &DesiredAccess, &FileAttributes,
+ &ShareAccess, &CreateDisposition, &CreateOptions,
+ &ImpersonationLevel, &SecurityFlags)) {
+ return NULL;
+ }
+
+ req = cli_ntcreate_send(NULL, self->ev, self->cli, fname, CreateFlags,
+ DesiredAccess, FileAttributes, ShareAccess,
+ CreateDisposition, CreateOptions,
+ ImpersonationLevel, SecurityFlags);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_ntcreate_recv(req, &fnum, NULL);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+ return Py_BuildValue("I", (unsigned)fnum);
+}
+
+static struct smb2_create_blobs *py_cli_get_create_contexts(
+ TALLOC_CTX *mem_ctx, PyObject *list)
+{
+ struct smb2_create_blobs *ctxs = NULL;
+ Py_ssize_t i, len;
+ int ret;
+
+ ret = PyList_Check(list);
+ if (!ret) {
+ goto fail;
+ }
+
+ len = PyList_Size(list);
+ if (len == 0) {
+ goto fail;
+ }
+
+ ctxs = talloc_zero(mem_ctx, struct smb2_create_blobs);
+ if (ctxs == NULL) {
+ goto fail;
+ }
+
+ for (i=0; i<len; i++) {
+ NTSTATUS status;
+
+ PyObject *t = NULL;
+ Py_ssize_t tlen;
+
+ PyObject *pname = NULL;
+ char *name = NULL;
+
+ PyObject *pdata = NULL;
+ DATA_BLOB data = { .data = NULL, };
+
+ t = PyList_GetItem(list, i);
+ if (t == NULL) {
+ goto fail;
+ }
+
+ ret = PyTuple_Check(t);
+ if (!ret) {
+ goto fail;
+ }
+
+ tlen = PyTuple_Size(t);
+ if (tlen != 2) {
+ goto fail;
+ }
+
+ pname = PyTuple_GetItem(t, 0);
+ if (pname == NULL) {
+ goto fail;
+ }
+ ret = PyBytes_Check(pname);
+ if (!ret) {
+ goto fail;
+ }
+ name = PyBytes_AsString(pname);
+
+ pdata = PyTuple_GetItem(t, 1);
+ if (pdata == NULL) {
+ goto fail;
+ }
+ ret = PyBytes_Check(pdata);
+ if (!ret) {
+ goto fail;
+ }
+ data = (DATA_BLOB) {
+ .data = (uint8_t *)PyBytes_AsString(pdata),
+ .length = PyBytes_Size(pdata),
+ };
+ status = smb2_create_blob_add(ctxs, ctxs, name, data);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+ return ctxs;
+
+fail:
+ TALLOC_FREE(ctxs);
+ return NULL;
+}
+
+static PyObject *py_cli_create_contexts(const struct smb2_create_blobs *blobs)
+{
+ PyObject *py_blobs = NULL;
+ uint32_t i;
+
+ if (blobs == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_blobs = PyList_New(blobs->num_blobs);
+ if (py_blobs == NULL) {
+ return NULL;
+ }
+
+ for (i=0; i<blobs->num_blobs; i++) {
+ struct smb2_create_blob *blob = &blobs->blobs[i];
+ PyObject *py_blob = NULL;
+ int ret;
+
+ py_blob = Py_BuildValue(
+ "(yy#)",
+ blob->tag,
+ blob->data.data,
+ (int)blob->data.length);
+ if (py_blob == NULL) {
+ goto fail;
+ }
+
+ ret = PyList_SetItem(py_blobs, i, py_blob);
+ if (ret == -1) {
+ Py_XDECREF(py_blob);
+ goto fail;
+ }
+ }
+ return py_blobs;
+
+fail:
+ Py_XDECREF(py_blobs);
+ return NULL;
+}
+
+static PyObject *py_cli_create_returns(const struct smb_create_returns *r)
+{
+ PyObject *v = NULL;
+
+ v = Py_BuildValue(
+ "{sLsLsLsLsLsLsLsLsLsL}",
+ "oplock_level",
+ (unsigned long long)r->oplock_level,
+ "flags",
+ (unsigned long long)r->flags,
+ "create_action",
+ (unsigned long long)r->create_action,
+ "creation_time",
+ (unsigned long long)r->creation_time,
+ "last_access_time",
+ (unsigned long long)r->last_access_time,
+ "last_write_time",
+ (unsigned long long)r->last_write_time,
+ "change_time",
+ (unsigned long long)r->change_time,
+ "allocation_size",
+ (unsigned long long)r->allocation_size,
+ "end_of_file",
+ (unsigned long long)r->end_of_file,
+ "file_attributes",
+ (unsigned long long)r->file_attributes);
+ return v;
+}
+
+static PyObject *py_cli_symlink_error(const struct symlink_reparse_struct *s)
+{
+ char *subst_utf8 = NULL, *print_utf8 = NULL;
+ size_t subst_utf8_len, print_utf8_len;
+ PyObject *v = NULL;
+ bool ok = true;
+
+ /*
+ * Python wants utf-8, regardless of our unix charset (which
+ * most likely is utf-8 these days, but you never know).
+ */
+
+ ok = convert_string_talloc(
+ talloc_tos(),
+ CH_UNIX,
+ CH_UTF8,
+ s->substitute_name,
+ strlen(s->substitute_name),
+ &subst_utf8,
+ &subst_utf8_len);
+ if (!ok) {
+ goto fail;
+ }
+
+ ok = convert_string_talloc(
+ talloc_tos(),
+ CH_UNIX,
+ CH_UTF8,
+ s->print_name,
+ strlen(s->print_name),
+ &print_utf8,
+ &print_utf8_len);
+ if (!ok) {
+ goto fail;
+ }
+
+ v = Py_BuildValue(
+ "{sLsssssL}",
+ "unparsed_path_length",
+ (unsigned long long)s->unparsed_path_length,
+ "substitute_name",
+ subst_utf8,
+ "print_name",
+ print_utf8,
+ "flags",
+ (unsigned long long)s->flags);
+
+fail:
+ TALLOC_FREE(subst_utf8);
+ TALLOC_FREE(print_utf8);
+ return v;
+}
+
+static PyObject *py_cli_create_ex(
+ struct py_cli_state *self, PyObject *args, PyObject *kwds)
+{
+ char *fname = NULL;
+ unsigned CreateFlags = 0;
+ unsigned DesiredAccess = FILE_GENERIC_READ;
+ unsigned FileAttributes = 0;
+ unsigned ShareAccess = 0;
+ unsigned CreateDisposition = FILE_OPEN;
+ unsigned CreateOptions = 0;
+ unsigned ImpersonationLevel = SMB2_IMPERSONATION_IMPERSONATION;
+ unsigned SecurityFlags = 0;
+ PyObject *py_create_contexts_in = NULL;
+ PyObject *py_create_contexts_out = NULL;
+ struct smb2_create_blobs *create_contexts_in = NULL;
+ struct smb2_create_blobs create_contexts_out = { .num_blobs = 0 };
+ struct smb_create_returns cr = { .create_action = 0, };
+ struct symlink_reparse_struct *symlink = NULL;
+ PyObject *py_cr = NULL;
+ uint16_t fnum;
+ struct tevent_req *req;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+ PyObject *v = NULL;
+
+ static const char *kwlist[] = {
+ "Name",
+ "CreateFlags",
+ "DesiredAccess",
+ "FileAttributes",
+ "ShareAccess",
+ "CreateDisposition",
+ "CreateOptions",
+ "ImpersonationLevel",
+ "SecurityFlags",
+ "CreateContexts",
+ NULL };
+
+ ret = ParseTupleAndKeywords(
+ args,
+ kwds,
+ "s|IIIIIIIIO",
+ kwlist,
+ &fname,
+ &CreateFlags,
+ &DesiredAccess,
+ &FileAttributes,
+ &ShareAccess,
+ &CreateDisposition,
+ &CreateOptions,
+ &ImpersonationLevel,
+ &SecurityFlags,
+ &py_create_contexts_in);
+ if (!ret) {
+ return NULL;
+ }
+
+ if (py_create_contexts_in != NULL) {
+ create_contexts_in = py_cli_get_create_contexts(
+ NULL, py_create_contexts_in);
+ if (create_contexts_in == NULL) {
+ errno = EINVAL;
+ PyErr_SetFromErrno(PyExc_RuntimeError);
+ return NULL;
+ }
+ }
+
+ if (smbXcli_conn_protocol(self->cli->conn) >= PROTOCOL_SMB2_02) {
+ struct cli_smb2_create_flags cflags = {
+ .batch_oplock = (CreateFlags & REQUEST_BATCH_OPLOCK),
+ .exclusive_oplock = (CreateFlags & REQUEST_OPLOCK),
+ };
+
+ req = cli_smb2_create_fnum_send(
+ NULL,
+ self->ev,
+ self->cli,
+ fname,
+ cflags,
+ ImpersonationLevel,
+ DesiredAccess,
+ FileAttributes,
+ ShareAccess,
+ CreateDisposition,
+ CreateOptions,
+ create_contexts_in);
+ } else {
+ req = cli_ntcreate_send(
+ NULL,
+ self->ev,
+ self->cli,
+ fname,
+ CreateFlags,
+ DesiredAccess,
+ FileAttributes,
+ ShareAccess,
+ CreateDisposition,
+ CreateOptions,
+ ImpersonationLevel,
+ SecurityFlags);
+ }
+
+ TALLOC_FREE(create_contexts_in);
+
+ ok = py_tevent_req_wait_exc(self, req);
+ if (!ok) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(self->cli->conn) >= PROTOCOL_SMB2_02) {
+ status = cli_smb2_create_fnum_recv(
+ req,
+ &fnum,
+ &cr,
+ NULL,
+ &create_contexts_out,
+ &symlink);
+ } else {
+ status = cli_ntcreate_recv(req, &fnum, &cr);
+ }
+
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ SMB_ASSERT(symlink == NULL);
+
+ py_create_contexts_out = py_cli_create_contexts(&create_contexts_out);
+ TALLOC_FREE(create_contexts_out.blobs);
+ if (py_create_contexts_out == NULL) {
+ goto nomem;
+ }
+
+ py_cr = py_cli_create_returns(&cr);
+ if (py_cr == NULL) {
+ goto nomem;
+ }
+
+ v = Py_BuildValue("(IOO)",
+ (unsigned)fnum,
+ py_cr,
+ py_create_contexts_out);
+ return v;
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ Py_XDECREF(py_create_contexts_out);
+ Py_XDECREF(py_cr);
+ Py_XDECREF(v);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK) &&
+ (symlink != NULL)) {
+ PyErr_SetObject(
+ PyObject_GetAttrString(
+ PyImport_ImportModule("samba"),
+ "NTSTATUSError"),
+ Py_BuildValue(
+ "I,s,O",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status),
+ py_cli_symlink_error(symlink)));
+ } else {
+ PyErr_SetNTSTATUS(status);
+ }
+ return NULL;
+}
+
+static PyObject *py_cli_close(struct py_cli_state *self, PyObject *args)
+{
+ struct tevent_req *req;
+ int fnum;
+ int flags = 0;
+ NTSTATUS status;
+
+ if (!PyArg_ParseTuple(args, "i|i", &fnum, &flags)) {
+ return NULL;
+ }
+
+ req = cli_close_send(NULL, self->ev, self->cli, fnum, flags);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_close_recv(req);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_cli_rename(
+ struct py_cli_state *self, PyObject *args, PyObject *kwds)
+{
+ char *fname_src = NULL, *fname_dst = NULL;
+ int replace = false;
+ struct tevent_req *req = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ static const char *kwlist[] = { "src", "dst", "replace", NULL };
+
+ ok = ParseTupleAndKeywords(
+ args, kwds, "ss|p", kwlist, &fname_src, &fname_dst, &replace);
+ if (!ok) {
+ return NULL;
+ }
+
+ req = cli_rename_send(
+ NULL, self->ev, self->cli, fname_src, fname_dst, replace);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_rename_recv(req);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+
+struct push_state {
+ char *data;
+ off_t nread;
+ off_t total_data;
+};
+
+/*
+ * cli_push() helper to write a chunk of data to a remote file
+ */
+static size_t push_data(uint8_t *buf, size_t n, void *priv)
+{
+ struct push_state *state = (struct push_state *)priv;
+ char *curr_ptr = NULL;
+ off_t remaining;
+ size_t copied_bytes;
+
+ if (state->nread >= state->total_data) {
+ return 0;
+ }
+
+ curr_ptr = state->data + state->nread;
+ remaining = state->total_data - state->nread;
+ copied_bytes = MIN(remaining, n);
+
+ memcpy(buf, curr_ptr, copied_bytes);
+ state->nread += copied_bytes;
+ return copied_bytes;
+}
+
+/*
+ * Writes a file with the contents specified
+ */
+static PyObject *py_smb_savefile(struct py_cli_state *self, PyObject *args)
+{
+ uint16_t fnum;
+ const char *filename = NULL;
+ char *data = NULL;
+ Py_ssize_t size = 0;
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ struct push_state state;
+
+ if (!PyArg_ParseTuple(args, "s"PYARG_BYTES_LEN":savefile", &filename,
+ &data, &size)) {
+ return NULL;
+ }
+
+ /* create a new file handle for writing to */
+ req = cli_ntcreate_send(NULL, self->ev, self->cli, filename, 0,
+ FILE_WRITE_DATA, FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OVERWRITE_IF, FILE_NON_DIRECTORY_FILE,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_ntcreate_recv(req, &fnum, NULL);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ /* write the new file contents */
+ state.data = data;
+ state.nread = 0;
+ state.total_data = size;
+
+ req = cli_push_send(NULL, self->ev, self->cli, fnum, 0, 0, 0,
+ push_data, &state);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_push_recv(req);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ /* close the file handle */
+ req = cli_close_send(NULL, self->ev, self->cli, fnum, 0);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_close_recv(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_cli_write(struct py_cli_state *self, PyObject *args,
+ PyObject *kwds)
+{
+ int fnum;
+ unsigned mode = 0;
+ char *buf;
+ Py_ssize_t buflen;
+ unsigned long long offset;
+ struct tevent_req *req;
+ NTSTATUS status;
+ size_t written;
+
+ static const char *kwlist[] = {
+ "fnum", "buffer", "offset", "mode", NULL };
+
+ if (!ParseTupleAndKeywords(
+ args, kwds, "i" PYARG_BYTES_LEN "K|I", kwlist,
+ &fnum, &buf, &buflen, &offset, &mode)) {
+ return NULL;
+ }
+
+ req = cli_write_send(NULL, self->ev, self->cli, fnum, mode,
+ (uint8_t *)buf, offset, buflen);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_write_recv(req, &written);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+ return Py_BuildValue("K", (unsigned long long)written);
+}
+
+/*
+ * Returns the size of the given file
+ */
+static NTSTATUS py_smb_filesize(struct py_cli_state *self, uint16_t fnum,
+ off_t *size)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+
+ req = cli_qfileinfo_basic_send(NULL, self->ev, self->cli, fnum);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ status = cli_qfileinfo_basic_recv(
+ req, NULL, size, NULL, NULL, NULL, NULL, NULL);
+ TALLOC_FREE(req);
+ return status;
+}
+
+/*
+ * Loads the specified file's contents and returns it
+ */
+static PyObject *py_smb_loadfile(struct py_cli_state *self, PyObject *args)
+{
+ NTSTATUS status;
+ const char *filename = NULL;
+ struct tevent_req *req = NULL;
+ uint16_t fnum;
+ off_t size;
+ char *buf = NULL;
+ off_t nread = 0;
+ PyObject *result = NULL;
+
+ if (!PyArg_ParseTuple(args, "s:loadfile", &filename)) {
+ return NULL;
+ }
+
+ /* get a read file handle */
+ req = cli_ntcreate_send(NULL, self->ev, self->cli, filename, 0,
+ FILE_READ_DATA | FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ, FILE_OPEN, 0,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_ntcreate_recv(req, &fnum, NULL);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ /* get a buffer to hold the file contents */
+ status = py_smb_filesize(self, fnum, &size);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ result = PyBytes_FromStringAndSize(NULL, size);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ /* read the file contents */
+ buf = PyBytes_AS_STRING(result);
+ req = cli_pull_send(NULL, self->ev, self->cli, fnum, 0, size,
+ size, cli_read_sink, &buf);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ Py_XDECREF(result);
+ return NULL;
+ }
+ status = cli_pull_recv(req, &nread);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ Py_XDECREF(result);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ /* close the file handle */
+ req = cli_close_send(NULL, self->ev, self->cli, fnum, 0);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ Py_XDECREF(result);
+ return NULL;
+ }
+ status = cli_close_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ Py_XDECREF(result);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ /* sanity-check we read the expected number of bytes */
+ if (nread > size) {
+ Py_XDECREF(result);
+ PyErr_Format(PyExc_IOError,
+ "read invalid - got %zu requested %zu",
+ nread, size);
+ return NULL;
+ }
+
+ if (nread < size) {
+ if (_PyBytes_Resize(&result, nread) < 0) {
+ return NULL;
+ }
+ }
+
+ return result;
+}
+
+static PyObject *py_cli_read(struct py_cli_state *self, PyObject *args,
+ PyObject *kwds)
+{
+ int fnum;
+ unsigned long long offset;
+ unsigned size;
+ struct tevent_req *req;
+ NTSTATUS status;
+ char *buf;
+ size_t received;
+ PyObject *result;
+
+ static const char *kwlist[] = {
+ "fnum", "offset", "size", NULL };
+
+ if (!ParseTupleAndKeywords(
+ args, kwds, "iKI", kwlist, &fnum, &offset,
+ &size)) {
+ return NULL;
+ }
+
+ result = PyBytes_FromStringAndSize(NULL, size);
+ if (result == NULL) {
+ return NULL;
+ }
+ buf = PyBytes_AS_STRING(result);
+
+ req = cli_read_send(NULL, self->ev, self->cli, fnum,
+ buf, offset, size);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ Py_XDECREF(result);
+ return NULL;
+ }
+ status = cli_read_recv(req, &received);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ Py_XDECREF(result);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ if (received > size) {
+ Py_XDECREF(result);
+ PyErr_Format(PyExc_IOError,
+ "read invalid - got %zu requested %u",
+ received, size);
+ return NULL;
+ }
+
+ if (received < size) {
+ if (_PyBytes_Resize(&result, received) < 0) {
+ return NULL;
+ }
+ }
+
+ return result;
+}
+
+static PyObject *py_cli_ftruncate(struct py_cli_state *self, PyObject *args,
+ PyObject *kwds)
+{
+ int fnum;
+ unsigned long long size;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ static const char *kwlist[] = {
+ "fnum", "size", NULL };
+
+ if (!ParseTupleAndKeywords(
+ args, kwds, "IK", kwlist, &fnum, &size)) {
+ return NULL;
+ }
+
+ req = cli_ftruncate_send(NULL, self->ev, self->cli, fnum, size);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_ftruncate_recv(req);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_cli_delete_on_close(struct py_cli_state *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ unsigned fnum, flag;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ static const char *kwlist[] = {
+ "fnum", "flag", NULL };
+
+ if (!ParseTupleAndKeywords(
+ args, kwds, "II", kwlist, &fnum, &flag)) {
+ return NULL;
+ }
+
+ req = cli_nt_delete_on_close_send(NULL, self->ev, self->cli, fnum,
+ flag);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_nt_delete_on_close_recv(req);
+ TALLOC_FREE(req);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+struct py_cli_notify_state {
+ PyObject_HEAD
+ struct py_cli_state *py_cli_state;
+ struct tevent_req *req;
+};
+
+static void py_cli_notify_state_dealloc(struct py_cli_notify_state *self)
+{
+ TALLOC_FREE(self->req);
+ Py_CLEAR(self->py_cli_state);
+ Py_TYPE(self)->tp_free(self);
+}
+
+static PyTypeObject py_cli_notify_state_type;
+
+static PyObject *py_cli_notify(struct py_cli_state *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ static const char *kwlist[] = {
+ "fnum",
+ "buffer_size",
+ "completion_filter",
+ "recursive",
+ NULL
+ };
+ unsigned fnum = 0;
+ unsigned buffer_size = 0;
+ unsigned completion_filter = 0;
+ PyObject *py_recursive = Py_False;
+ bool recursive = false;
+ struct tevent_req *req = NULL;
+ struct tevent_queue *send_queue = NULL;
+ struct tevent_req *flush_req = NULL;
+ bool ok;
+ struct py_cli_notify_state *py_notify_state = NULL;
+ struct timeval endtime;
+
+ ok = ParseTupleAndKeywords(args,
+ kwds,
+ "IIIO",
+ kwlist,
+ &fnum,
+ &buffer_size,
+ &completion_filter,
+ &py_recursive);
+ if (!ok) {
+ return NULL;
+ }
+
+ recursive = PyObject_IsTrue(py_recursive);
+
+ req = cli_notify_send(NULL,
+ self->ev,
+ self->cli,
+ fnum,
+ buffer_size,
+ completion_filter,
+ recursive);
+ if (req == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ /*
+ * Just wait for the request being submitted to
+ * the kernel/socket/wire.
+ */
+ send_queue = smbXcli_conn_send_queue(self->cli->conn);
+ flush_req = tevent_queue_wait_send(req,
+ self->ev,
+ send_queue);
+ endtime = timeval_current_ofs_msec(self->cli->timeout);
+ ok = tevent_req_set_endtime(flush_req,
+ self->ev,
+ endtime);
+ if (!ok) {
+ TALLOC_FREE(req);
+ PyErr_NoMemory();
+ return NULL;
+ }
+ ok = py_tevent_req_wait_exc(self, flush_req);
+ if (!ok) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ TALLOC_FREE(flush_req);
+
+ py_notify_state = (struct py_cli_notify_state *)
+ py_cli_notify_state_type.tp_alloc(&py_cli_notify_state_type, 0);
+ if (py_notify_state == NULL) {
+ TALLOC_FREE(req);
+ PyErr_NoMemory();
+ return NULL;
+ }
+ Py_INCREF(self);
+ py_notify_state->py_cli_state = self;
+ py_notify_state->req = req;
+
+ return (PyObject *)py_notify_state;
+}
+
+static PyObject *py_cli_notify_get_changes(struct py_cli_notify_state *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ struct py_cli_state *py_cli_state = self->py_cli_state;
+ struct tevent_req *req = self->req;
+ uint32_t i;
+ uint32_t num_changes = 0;
+ struct notify_change *changes = NULL;
+ PyObject *result = NULL;
+ NTSTATUS status;
+ bool ok;
+ static const char *kwlist[] = {
+ "wait",
+ NULL
+ };
+ PyObject *py_wait = Py_False;
+ bool wait = false;
+ bool pending;
+
+ ok = ParseTupleAndKeywords(args,
+ kwds,
+ "O",
+ kwlist,
+ &py_wait);
+ if (!ok) {
+ return NULL;
+ }
+
+ wait = PyObject_IsTrue(py_wait);
+
+ if (req == NULL) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "TODO req == NULL "
+ "- missing change notify request?");
+ return NULL;
+ }
+
+ pending = tevent_req_is_in_progress(req);
+ if (pending && !wait) {
+ Py_RETURN_NONE;
+ }
+
+ if (pending) {
+ struct timeval endtime;
+
+ endtime = timeval_current_ofs_msec(py_cli_state->cli->timeout);
+ ok = tevent_req_set_endtime(req,
+ py_cli_state->ev,
+ endtime);
+ if (!ok) {
+ TALLOC_FREE(req);
+ PyErr_NoMemory();
+ return NULL;
+ }
+ }
+
+ ok = py_tevent_req_wait_exc(py_cli_state, req);
+ self->req = NULL;
+ Py_CLEAR(self->py_cli_state);
+ if (!ok) {
+ return NULL;
+ }
+
+ status = cli_notify_recv(req, req, &num_changes, &changes);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(req);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ result = Py_BuildValue("[]");
+ if (result == NULL) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ for (i = 0; i < num_changes; i++) {
+ PyObject *change = NULL;
+ int ret;
+
+ change = Py_BuildValue("{s:s,s:I}",
+ "name", changes[i].name,
+ "action", changes[i].action);
+ if (change == NULL) {
+ Py_XDECREF(result);
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ ret = PyList_Append(result, change);
+ Py_DECREF(change);
+ if (ret == -1) {
+ Py_XDECREF(result);
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+
+ TALLOC_FREE(req);
+ return result;
+}
+
+static PyMethodDef py_cli_notify_state_methods[] = {
+ {
+ .ml_name = "get_changes",
+ .ml_meth = (PyCFunction)py_cli_notify_get_changes,
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = "Wait for change notifications: \n"
+ "N.get_changes(wait=BOOLEAN) -> "
+ "change notifications as a dictionary\n"
+ "\t\tList contents of a directory. The keys are, \n"
+ "\t\t\tname: name of changed object\n"
+ "\t\t\taction: type of the change\n"
+ "None is returned if there's no response yet and "
+ "wait=False is passed"
+ },
+ {
+ .ml_name = NULL
+ }
+};
+
+static PyTypeObject py_cli_notify_state_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "libsmb_samba_cwrapper.Notify",
+ .tp_basicsize = sizeof(struct py_cli_notify_state),
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = "notify request",
+ .tp_dealloc = (destructor)py_cli_notify_state_dealloc,
+ .tp_methods = py_cli_notify_state_methods,
+};
+
+/*
+ * Helper to add posix directory listing entries to an overall Python list
+ */
+static NTSTATUS list_posix_helper(struct file_info *finfo,
+ const char *mask, void *state)
+{
+ PyObject *result = (PyObject *)state;
+ PyObject *file = NULL;
+ int ret;
+
+ /*
+ * Build a dictionary representing the file info.
+ */
+ file = Py_BuildValue("{s:s,s:I,"
+ "s:K,s:K,"
+ "s:l,s:l,s:l,s:l,"
+ "s:i,s:K,s:i,s:i,s:I,"
+ "s:s,s:s}",
+ "name", finfo->name,
+ "attrib", finfo->attr,
+
+ "size", finfo->size,
+ "allocaction_size", finfo->allocated_size,
+
+ "btime",
+ convert_timespec_to_time_t(finfo->btime_ts),
+ "atime",
+ convert_timespec_to_time_t(finfo->atime_ts),
+ "mtime",
+ convert_timespec_to_time_t(finfo->mtime_ts),
+ "ctime",
+ convert_timespec_to_time_t(finfo->ctime_ts),
+
+ "perms", finfo->st_ex_mode,
+ "ino", finfo->ino,
+ "dev", finfo->st_ex_dev,
+ "nlink", finfo->st_ex_nlink,
+ "reparse_tag", finfo->reparse_tag,
+
+ "owner_sid",
+ dom_sid_string(finfo, &finfo->owner_sid),
+ "group_sid",
+ dom_sid_string(finfo, &finfo->group_sid));
+ if (file == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = PyList_Append(result, file);
+ Py_CLEAR(file);
+ if (ret == -1) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Helper to add directory listing entries to an overall Python list
+ */
+static NTSTATUS list_helper(struct file_info *finfo,
+ const char *mask, void *state)
+{
+ PyObject *result = (PyObject *)state;
+ PyObject *file = NULL;
+ PyObject *size = NULL;
+ int ret;
+
+ /* suppress '.' and '..' in the results we return */
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
+ return NT_STATUS_OK;
+ }
+ size = PyLong_FromUnsignedLongLong(finfo->size);
+ /*
+ * Build a dictionary representing the file info.
+ * Note: Windows does not always return short_name (so it may be None)
+ */
+ file = Py_BuildValue("{s:s,s:i,s:s,s:O,s:l}",
+ "name", finfo->name,
+ "attrib", (int)finfo->attr,
+ "short_name", finfo->short_name,
+ "size", size,
+ "mtime",
+ convert_timespec_to_time_t(finfo->mtime_ts));
+
+ Py_CLEAR(size);
+
+ if (file == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (finfo->attr & FILE_ATTRIBUTE_REPARSE_POINT) {
+ unsigned long tag = finfo->reparse_tag;
+
+ ret = PyDict_SetItemString(
+ file,
+ "reparse_tag",
+ PyLong_FromUnsignedLong(tag));
+ if (ret == -1) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ ret = PyList_Append(result, file);
+ Py_CLEAR(file);
+ if (ret == -1) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct do_listing_state {
+ const char *mask;
+ NTSTATUS (*callback_fn)(
+ struct file_info *finfo,
+ const char *mask,
+ void *private_data);
+ void *private_data;
+ NTSTATUS status;
+};
+
+static void do_listing_cb(struct tevent_req *subreq)
+{
+ struct do_listing_state *state = tevent_req_callback_data_void(subreq);
+ struct file_info *finfo = NULL;
+
+ state->status = cli_list_recv(subreq, NULL, &finfo);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return;
+ }
+ state->callback_fn(finfo, state->mask, state->private_data);
+ TALLOC_FREE(finfo);
+}
+
+static NTSTATUS do_listing(struct py_cli_state *self,
+ const char *base_dir, const char *user_mask,
+ uint16_t attribute,
+ unsigned int info_level,
+ NTSTATUS (*callback_fn)(struct file_info *,
+ const char *, void *),
+ void *priv)
+{
+ char *mask = NULL;
+ struct do_listing_state state = {
+ .mask = mask,
+ .callback_fn = callback_fn,
+ .private_data = priv,
+ };
+ struct tevent_req *req = NULL;
+ NTSTATUS status;
+
+ if (user_mask == NULL) {
+ mask = talloc_asprintf(NULL, "%s\\*", base_dir);
+ } else {
+ mask = talloc_asprintf(NULL, "%s\\%s", base_dir, user_mask);
+ }
+
+ if (mask == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ dos_format(mask);
+
+ req = cli_list_send(NULL, self->ev, self->cli, mask, attribute,
+ info_level);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ tevent_req_set_callback(req, do_listing_cb, &state);
+
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ TALLOC_FREE(req);
+
+ status = state.status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_FILES)) {
+ status = NT_STATUS_OK;
+ }
+
+done:
+ TALLOC_FREE(mask);
+ return status;
+}
+
+static PyObject *py_cli_list(struct py_cli_state *self,
+ PyObject *args,
+ PyObject *kwds)
+{
+ char *base_dir;
+ char *user_mask = NULL;
+ unsigned int attribute = LIST_ATTRIBUTE_MASK;
+ unsigned int info_level = 0;
+ NTSTATUS status;
+ enum protocol_types proto = smbXcli_conn_protocol(self->cli->conn);
+ PyObject *result = NULL;
+ const char *kwlist[] = { "directory", "mask", "attribs",
+ "info_level", NULL };
+ NTSTATUS (*callback_fn)(struct file_info *, const char *, void *) =
+ &list_helper;
+
+ if (!ParseTupleAndKeywords(args, kwds, "z|sII:list", kwlist,
+ &base_dir, &user_mask, &attribute,
+ &info_level)) {
+ return NULL;
+ }
+
+ result = Py_BuildValue("[]");
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (!info_level) {
+ if (proto >= PROTOCOL_SMB2_02) {
+ info_level = SMB2_FIND_ID_BOTH_DIRECTORY_INFO;
+ } else {
+ info_level = SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
+ }
+ }
+
+ if (info_level == SMB2_FIND_POSIX_INFORMATION) {
+ callback_fn = &list_posix_helper;
+ }
+ status = do_listing(self, base_dir, user_mask, attribute,
+ info_level, callback_fn, result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ Py_XDECREF(result);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ return result;
+}
+
+static PyObject *py_smb_unlink(struct py_cli_state *self, PyObject *args)
+{
+ NTSTATUS status;
+ const char *filename = NULL;
+ struct tevent_req *req = NULL;
+ const uint32_t attrs = (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!PyArg_ParseTuple(args, "s:unlink", &filename)) {
+ return NULL;
+ }
+
+ req = cli_unlink_send(NULL, self->ev, self->cli, filename, attrs);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_unlink_recv(req);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_smb_rmdir(struct py_cli_state *self, PyObject *args)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ const char *dirname = NULL;
+
+ if (!PyArg_ParseTuple(args, "s:rmdir", &dirname)) {
+ return NULL;
+ }
+
+ req = cli_rmdir_send(NULL, self->ev, self->cli, dirname);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_rmdir_recv(req);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ * Create a directory
+ */
+static PyObject *py_smb_mkdir(struct py_cli_state *self, PyObject *args)
+{
+ NTSTATUS status;
+ const char *dirname = NULL;
+ struct tevent_req *req = NULL;
+
+ if (!PyArg_ParseTuple(args, "s:mkdir", &dirname)) {
+ return NULL;
+ }
+
+ req = cli_mkdir_send(NULL, self->ev, self->cli, dirname);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_mkdir_recv(req);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ * Does a whoami call
+ */
+static PyObject *py_smb_posix_whoami(struct py_cli_state *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ uint64_t uid;
+ uint64_t gid;
+ uint32_t num_gids;
+ uint64_t *gids = NULL;
+ uint32_t num_sids;
+ struct dom_sid *sids = NULL;
+ bool guest;
+ PyObject *py_gids = NULL;
+ PyObject *py_sids = NULL;
+ PyObject *py_guest = NULL;
+ PyObject *py_ret = NULL;
+ Py_ssize_t i;
+
+ req = cli_posix_whoami_send(frame, self->ev, self->cli);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ goto fail;
+ }
+ status = cli_posix_whoami_recv(req,
+ frame,
+ &uid,
+ &gid,
+ &num_gids,
+ &gids,
+ &num_sids,
+ &sids,
+ &guest);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ goto fail;
+ }
+ if (num_gids > PY_SSIZE_T_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "posix_whoami: Too many GIDs");
+ goto fail;
+ }
+ if (num_sids > PY_SSIZE_T_MAX) {
+ PyErr_SetString(PyExc_OverflowError, "posix_whoami: Too many SIDs");
+ goto fail;
+ }
+
+ py_gids = PyList_New(num_gids);
+ if (!py_gids) {
+ goto fail;
+ }
+ for (i = 0; i < num_gids; ++i) {
+ int ret;
+ PyObject *py_item = PyLong_FromUnsignedLongLong(gids[i]);
+ if (!py_item) {
+ goto fail2;
+ }
+
+ ret = PyList_SetItem(py_gids, i, py_item);
+ if (ret) {
+ goto fail2;
+ }
+ }
+ py_sids = PyList_New(num_sids);
+ if (!py_sids) {
+ goto fail2;
+ }
+ for (i = 0; i < num_sids; ++i) {
+ int ret;
+ struct dom_sid *sid;
+ PyObject *py_item;
+
+ sid = dom_sid_dup(frame, &sids[i]);
+ if (!sid) {
+ PyErr_NoMemory();
+ goto fail3;
+ }
+
+ py_item = pytalloc_steal(dom_sid_Type, sid);
+ if (!py_item) {
+ PyErr_NoMemory();
+ goto fail3;
+ }
+
+ ret = PyList_SetItem(py_sids, i, py_item);
+ if (ret) {
+ goto fail3;
+ }
+ }
+
+ py_guest = guest ? Py_True : Py_False;
+
+ py_ret = Py_BuildValue("KKNNO",
+ uid,
+ gid,
+ py_gids,
+ py_sids,
+ py_guest);
+ if (!py_ret) {
+ goto fail3;
+ }
+
+ TALLOC_FREE(frame);
+ return py_ret;
+
+fail3:
+ Py_CLEAR(py_sids);
+
+fail2:
+ Py_CLEAR(py_gids);
+
+fail:
+ TALLOC_FREE(frame);
+ return NULL;
+}
+
+/*
+ * Checks existence of a directory
+ */
+static bool check_dir_path(struct py_cli_state *self, const char *path)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+
+ req = cli_chkpath_send(NULL, self->ev, self->cli, path);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return false;
+ }
+ status = cli_chkpath_recv(req);
+ TALLOC_FREE(req);
+
+ return NT_STATUS_IS_OK(status);
+}
+
+static PyObject *py_smb_chkpath(struct py_cli_state *self, PyObject *args)
+{
+ const char *path = NULL;
+ bool dir_exists;
+
+ if (!PyArg_ParseTuple(args, "s:chkpath", &path)) {
+ return NULL;
+ }
+
+ dir_exists = check_dir_path(self, path);
+ return PyBool_FromLong(dir_exists);
+}
+
+static PyObject *py_smb_have_posix(struct py_cli_state *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ bool posix = smbXcli_conn_have_posix(self->cli->conn);
+
+ if (posix) {
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+}
+
+static PyObject *py_smb_protocol(struct py_cli_state *self,
+ PyObject *Py_UNUSED(ignored))
+{
+ enum protocol_types proto = smbXcli_conn_protocol(self->cli->conn);
+ PyObject *result = PyLong_FromLong(proto);
+ return result;
+}
+
+static PyObject *py_smb_get_sd(struct py_cli_state *self, PyObject *args)
+{
+ int fnum;
+ unsigned sinfo;
+ struct tevent_req *req = NULL;
+ struct security_descriptor *sd = NULL;
+ NTSTATUS status;
+
+ if (!PyArg_ParseTuple(args, "iI:get_acl", &fnum, &sinfo)) {
+ return NULL;
+ }
+
+ req = cli_query_security_descriptor_send(
+ NULL, self->ev, self->cli, fnum, sinfo);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_query_security_descriptor_recv(req, NULL, &sd);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ return py_return_ndr_struct(
+ "samba.dcerpc.security", "descriptor", sd, sd);
+}
+
+static PyObject *py_smb_set_sd(struct py_cli_state *self, PyObject *args)
+{
+ PyObject *py_sd = NULL;
+ struct tevent_req *req = NULL;
+ struct security_descriptor *sd = NULL;
+ uint16_t fnum;
+ unsigned int sinfo;
+ NTSTATUS status;
+
+ if (!PyArg_ParseTuple(args, "iOI:set_sd", &fnum, &py_sd, &sinfo)) {
+ return NULL;
+ }
+
+ sd = pytalloc_get_type(py_sd, struct security_descriptor);
+ if (!sd) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected dcerpc.security.descriptor as argument, got %s",
+ pytalloc_get_name(py_sd));
+ return NULL;
+ }
+
+ req = cli_set_security_descriptor_send(
+ NULL, self->ev, self->cli, fnum, sinfo, sd);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+
+ status = cli_set_security_descriptor_recv(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_smb_smb1_posix(
+ struct py_cli_state *self, PyObject *Py_UNUSED(ignored))
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ PyObject *result = NULL;
+
+ req = cli_unix_extensions_version_send(NULL, self->ev, self->cli);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_unix_extensions_version_recv(
+ req, &major, &minor, &caplow, &caphigh);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ req = cli_set_unix_extensions_capabilities_send(
+ NULL, self->ev, self->cli, major, minor, caplow, caphigh);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_set_unix_extensions_capabilities_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ result = Py_BuildValue(
+ "[IIII]",
+ (unsigned)minor,
+ (unsigned)major,
+ (unsigned)caplow,
+ (unsigned)caphigh);
+ return result;
+}
+
+static PyObject *py_smb_smb1_readlink(
+ struct py_cli_state *self, PyObject *args)
+{
+ NTSTATUS status;
+ const char *filename = NULL;
+ struct tevent_req *req = NULL;
+ char *target = NULL;
+ PyObject *result = NULL;
+
+ if (!PyArg_ParseTuple(args, "s:smb1_readlink", &filename)) {
+ return NULL;
+ }
+
+ req = cli_posix_readlink_send(NULL, self->ev, self->cli, filename);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_posix_readlink_recv(req, NULL, &target);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ result = PyBytes_FromString(target);
+ TALLOC_FREE(target);
+ return result;
+}
+
+static PyObject *py_smb_smb1_symlink(
+ struct py_cli_state *self, PyObject *args)
+{
+ NTSTATUS status;
+ const char *target = NULL, *newname = NULL;
+ struct tevent_req *req = NULL;
+
+ if (!PyArg_ParseTuple(args, "ss:smb1_symlink", &target, &newname)) {
+ return NULL;
+ }
+
+ req = cli_posix_symlink_send(
+ NULL, self->ev, self->cli, target, newname);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_posix_symlink_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_smb_smb1_stat(
+ struct py_cli_state *self, PyObject *args)
+{
+ NTSTATUS status;
+ const char *fname = NULL;
+ struct tevent_req *req = NULL;
+ struct stat_ex sbuf = { .st_ex_nlink = 0, };
+
+ if (!PyArg_ParseTuple(args, "s:smb1_stat", &fname)) {
+ return NULL;
+ }
+
+ req = cli_posix_stat_send(NULL, self->ev, self->cli, fname);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_posix_stat_recv(req, &sbuf);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ return Py_BuildValue(
+ "{sLsLsLsLsLsLsLsLsLsLsLsLsLsLsLsLsLsLsLsL}",
+ "dev",
+ (unsigned long long)sbuf.st_ex_dev,
+ "ino",
+ (unsigned long long)sbuf.st_ex_ino,
+ "mode",
+ (unsigned long long)sbuf.st_ex_mode,
+ "nlink",
+ (unsigned long long)sbuf.st_ex_nlink,
+ "uid",
+ (unsigned long long)sbuf.st_ex_uid,
+ "gid",
+ (unsigned long long)sbuf.st_ex_gid,
+ "rdev",
+ (unsigned long long)sbuf.st_ex_size,
+ "atime_sec",
+ (unsigned long long)sbuf.st_ex_atime.tv_sec,
+ "atime_nsec",
+ (unsigned long long)sbuf.st_ex_atime.tv_nsec,
+ "mtime_sec",
+ (unsigned long long)sbuf.st_ex_mtime.tv_sec,
+ "mtime_nsec",
+ (unsigned long long)sbuf.st_ex_mtime.tv_nsec,
+ "ctime_sec",
+ (unsigned long long)sbuf.st_ex_ctime.tv_sec,
+ "ctime_nsec",
+ (unsigned long long)sbuf.st_ex_ctime.tv_nsec,
+ "btime_sec",
+ (unsigned long long)sbuf.st_ex_btime.tv_sec,
+ "btime_nsec",
+ (unsigned long long)sbuf.st_ex_btime.tv_nsec,
+ "cached_dos_attributes",
+ (unsigned long long)sbuf.cached_dos_attributes,
+ "blksize",
+ (unsigned long long)sbuf.st_ex_blksize,
+ "blocks",
+ (unsigned long long)sbuf.st_ex_blocks,
+ "flags",
+ (unsigned long long)sbuf.st_ex_flags,
+ "iflags",
+ (unsigned long long)sbuf.st_ex_iflags);
+}
+
+static PyObject *py_cli_mknod(
+ struct py_cli_state *self, PyObject *args, PyObject *kwds)
+{
+ char *fname = NULL;
+ int mode = 0, major = 0, minor = 0, dev = 0;
+ struct tevent_req *req = NULL;
+ static const char *kwlist[] = {
+ "fname", "mode", "major", "minor", NULL,
+ };
+ NTSTATUS status;
+ bool ok;
+
+ ok = ParseTupleAndKeywords(
+ args,
+ kwds,
+ "sI|II:mknod",
+ kwlist,
+ &fname,
+ &mode,
+ &major,
+ &minor);
+ if (!ok) {
+ return NULL;
+ }
+
+#if defined(HAVE_MAKEDEV)
+ dev = makedev(major, minor);
+#endif
+
+ req = cli_mknod_send(
+ NULL, self->ev, self->cli, fname, mode, dev);
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+ status = cli_mknod_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_cli_fsctl(
+ struct py_cli_state *self, PyObject *args, PyObject *kwds)
+{
+ int fnum, ctl_code;
+ int max_out = 0;
+ char *buf = NULL;
+ Py_ssize_t buflen;
+ DATA_BLOB in = { .data = NULL, };
+ DATA_BLOB out = { .data = NULL, };
+ struct tevent_req *req = NULL;
+ PyObject *result = NULL;
+ static const char *kwlist[] = {
+ "fnum", "ctl_code", "in", "max_out", NULL,
+ };
+ NTSTATUS status;
+ bool ok;
+
+ ok = ParseTupleAndKeywords(
+ args,
+ kwds,
+ "ii" PYARG_BYTES_LEN "i",
+ kwlist,
+ &fnum,
+ &ctl_code,
+ &buf,
+ &buflen,
+ &max_out);
+ if (!ok) {
+ return NULL;
+ }
+
+ in = (DATA_BLOB) { .data = (uint8_t *)buf, .length = buflen, };
+
+ req = cli_fsctl_send(
+ NULL, self->ev, self->cli, fnum, ctl_code, &in, max_out);
+
+ if (!py_tevent_req_wait_exc(self, req)) {
+ return NULL;
+ }
+
+ status = cli_fsctl_recv(req, NULL, &out);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ result = PyBytes_FromStringAndSize((char *)out.data, out.length);
+ data_blob_free(&out);
+ return result;
+}
+
+static PyMethodDef py_cli_state_methods[] = {
+ { "settimeout", (PyCFunction)py_cli_settimeout, METH_VARARGS,
+ "settimeout(new_timeout_msecs) => return old_timeout_msecs" },
+ { "echo", (PyCFunction)py_cli_echo, METH_NOARGS,
+ "Ping the server connection" },
+ { "create", PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_create),
+ METH_VARARGS|METH_KEYWORDS,
+ "Open a file" },
+ { "create_ex",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_create_ex),
+ METH_VARARGS|METH_KEYWORDS,
+ "Open a file, SMB2 version returning create contexts" },
+ { "close", (PyCFunction)py_cli_close, METH_VARARGS,
+ "Close a file handle" },
+ { "write", PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_write),
+ METH_VARARGS|METH_KEYWORDS,
+ "Write to a file handle" },
+ { "read", PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_read),
+ METH_VARARGS|METH_KEYWORDS,
+ "Read from a file handle" },
+ { "truncate", PY_DISCARD_FUNC_SIG(PyCFunction,
+ py_cli_ftruncate),
+ METH_VARARGS|METH_KEYWORDS,
+ "Truncate a file" },
+ { "delete_on_close", PY_DISCARD_FUNC_SIG(PyCFunction,
+ py_cli_delete_on_close),
+ METH_VARARGS|METH_KEYWORDS,
+ "Set/Reset the delete on close flag" },
+ { "notify", PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_notify),
+ METH_VARARGS|METH_KEYWORDS,
+ "Wait for change notifications: \n"
+ "notify(fnum, buffer_size, completion_filter...) -> "
+ "libsmb_samba_internal.Notify request handle\n" },
+ { "list", PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_list),
+ METH_VARARGS|METH_KEYWORDS,
+ "list(directory, mask='*', attribs=DEFAULT_ATTRS) -> "
+ "directory contents as a dictionary\n"
+ "\t\tDEFAULT_ATTRS: FILE_ATTRIBUTE_SYSTEM | "
+ "FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_ARCHIVE\n\n"
+ "\t\tList contents of a directory. The keys are, \n"
+ "\t\t\tname: Long name of the directory item\n"
+ "\t\t\tshort_name: Short name of the directory item\n"
+ "\t\t\tsize: File size in bytes\n"
+ "\t\t\tattrib: Attributes\n"
+ "\t\t\tmtime: Modification time\n" },
+ { "get_oplock_break", (PyCFunction)py_cli_get_oplock_break,
+ METH_VARARGS, "Wait for an oplock break" },
+ { "unlink", (PyCFunction)py_smb_unlink,
+ METH_VARARGS,
+ "unlink(path) -> None\n\n \t\tDelete a file." },
+ { "mkdir", (PyCFunction)py_smb_mkdir, METH_VARARGS,
+ "mkdir(path) -> None\n\n \t\tCreate a directory." },
+ { "posix_whoami", (PyCFunction)py_smb_posix_whoami, METH_NOARGS,
+ "posix_whoami() -> (uid, gid, gids, sids, guest)" },
+ { "rmdir", (PyCFunction)py_smb_rmdir, METH_VARARGS,
+ "rmdir(path) -> None\n\n \t\tDelete a directory." },
+ { "rename",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_rename),
+ METH_VARARGS|METH_KEYWORDS,
+ "rename(src,dst) -> None\n\n \t\tRename a file." },
+ { "chkpath", (PyCFunction)py_smb_chkpath, METH_VARARGS,
+ "chkpath(dir_path) -> True or False\n\n"
+ "\t\tReturn true if directory exists, false otherwise." },
+ { "savefile", (PyCFunction)py_smb_savefile, METH_VARARGS,
+ "savefile(path, bytes) -> None\n\n"
+ "\t\tWrite bytes to file." },
+ { "loadfile", (PyCFunction)py_smb_loadfile, METH_VARARGS,
+ "loadfile(path) -> file contents as a bytes object"
+ "\n\n\t\tRead contents of a file." },
+ { "get_sd", (PyCFunction)py_smb_get_sd, METH_VARARGS,
+ "get_sd(fnum[, security_info=0]) -> security_descriptor object\n\n"
+ "\t\tGet security descriptor for opened file." },
+ { "set_sd", (PyCFunction)py_smb_set_sd, METH_VARARGS,
+ "set_sd(fnum, security_descriptor[, security_info=0]) -> None\n\n"
+ "\t\tSet security descriptor for opened file." },
+ { "protocol",
+ (PyCFunction)py_smb_protocol,
+ METH_NOARGS,
+ "protocol() -> Number"
+ },
+ { "have_posix",
+ (PyCFunction)py_smb_have_posix,
+ METH_NOARGS,
+ "have_posix() -> True/False\n\n"
+ "\t\tReturn if the server has posix extensions"
+ },
+ { "smb1_posix",
+ (PyCFunction)py_smb_smb1_posix,
+ METH_NOARGS,
+ "Negotiate SMB1 posix extensions",
+ },
+ { "smb1_readlink",
+ (PyCFunction)py_smb_smb1_readlink,
+ METH_VARARGS,
+ "smb1_readlink(path) -> link target",
+ },
+ { "smb1_symlink",
+ (PyCFunction)py_smb_smb1_symlink,
+ METH_VARARGS,
+ "smb1_symlink(target, newname) -> None",
+ },
+ { "smb1_stat",
+ (PyCFunction)py_smb_smb1_stat,
+ METH_VARARGS,
+ "smb1_stat(path) -> stat info",
+ },
+ { "fsctl",
+ (PyCFunction)py_cli_fsctl,
+ METH_VARARGS|METH_KEYWORDS,
+ "fsctl(fnum, ctl_code, in_bytes, max_out) -> out_bytes",
+ },
+ { "mknod",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_cli_mknod),
+ METH_VARARGS|METH_KEYWORDS,
+ "mknod(path, mode | major, minor)",
+ },
+ { NULL, NULL, 0, NULL }
+};
+
+static PyTypeObject py_cli_state_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "libsmb_samba_cwrapper.LibsmbCConn",
+ .tp_basicsize = sizeof(struct py_cli_state),
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = "libsmb cwrapper connection",
+ .tp_new = py_cli_state_new,
+ .tp_init = (initproc)py_cli_state_init,
+ .tp_dealloc = (destructor)py_cli_state_dealloc,
+ .tp_methods = py_cli_state_methods,
+};
+
+static PyMethodDef py_libsmb_methods[] = {
+ {0},
+};
+
+void initlibsmb_samba_cwrapper(void);
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "libsmb_samba_cwrapper",
+ .m_doc = "libsmb wrapper",
+ .m_size = -1,
+ .m_methods = py_libsmb_methods,
+};
+
+MODULE_INIT_FUNC(libsmb_samba_cwrapper)
+{
+ PyObject *m = NULL;
+ PyObject *mod = NULL;
+
+ talloc_stackframe();
+
+ if (PyType_Ready(&py_cli_state_type) < 0) {
+ return NULL;
+ }
+ if (PyType_Ready(&py_cli_notify_state_type) < 0) {
+ return NULL;
+ }
+
+ m = PyModule_Create(&moduledef);
+ if (m == NULL) {
+ return m;
+ }
+
+ /* Import dom_sid type from dcerpc.security */
+ mod = PyImport_ImportModule("samba.dcerpc.security");
+ if (mod == NULL) {
+ return NULL;
+ }
+
+ dom_sid_Type = (PyTypeObject *)PyObject_GetAttrString(mod, "dom_sid");
+ if (dom_sid_Type == NULL) {
+ Py_DECREF(mod);
+ return NULL;
+ }
+
+ Py_INCREF(&py_cli_state_type);
+ PyModule_AddObject(m, "LibsmbCConn", (PyObject *)&py_cli_state_type);
+
+#define ADD_FLAGS(val) PyModule_AddObject(m, #val, PyLong_FromLong(val))
+
+ ADD_FLAGS(PROTOCOL_NONE);
+ ADD_FLAGS(PROTOCOL_CORE);
+ ADD_FLAGS(PROTOCOL_COREPLUS);
+ ADD_FLAGS(PROTOCOL_LANMAN1);
+ ADD_FLAGS(PROTOCOL_LANMAN2);
+ ADD_FLAGS(PROTOCOL_NT1);
+ ADD_FLAGS(PROTOCOL_SMB2_02);
+ ADD_FLAGS(PROTOCOL_SMB2_10);
+ ADD_FLAGS(PROTOCOL_SMB3_00);
+ ADD_FLAGS(PROTOCOL_SMB3_02);
+ ADD_FLAGS(PROTOCOL_SMB3_11);
+
+ ADD_FLAGS(FILE_ATTRIBUTE_READONLY);
+ ADD_FLAGS(FILE_ATTRIBUTE_HIDDEN);
+ ADD_FLAGS(FILE_ATTRIBUTE_SYSTEM);
+ ADD_FLAGS(FILE_ATTRIBUTE_VOLUME);
+ ADD_FLAGS(FILE_ATTRIBUTE_DIRECTORY);
+ ADD_FLAGS(FILE_ATTRIBUTE_ARCHIVE);
+ ADD_FLAGS(FILE_ATTRIBUTE_DEVICE);
+ ADD_FLAGS(FILE_ATTRIBUTE_NORMAL);
+ ADD_FLAGS(FILE_ATTRIBUTE_TEMPORARY);
+ ADD_FLAGS(FILE_ATTRIBUTE_SPARSE);
+ ADD_FLAGS(FILE_ATTRIBUTE_REPARSE_POINT);
+ ADD_FLAGS(FILE_ATTRIBUTE_COMPRESSED);
+ ADD_FLAGS(FILE_ATTRIBUTE_OFFLINE);
+ ADD_FLAGS(FILE_ATTRIBUTE_NONINDEXED);
+ ADD_FLAGS(FILE_ATTRIBUTE_ENCRYPTED);
+ ADD_FLAGS(FILE_ATTRIBUTE_ALL_MASK);
+
+ ADD_FLAGS(FILE_DIRECTORY_FILE);
+ ADD_FLAGS(FILE_WRITE_THROUGH);
+ ADD_FLAGS(FILE_SEQUENTIAL_ONLY);
+ ADD_FLAGS(FILE_NO_INTERMEDIATE_BUFFERING);
+ ADD_FLAGS(FILE_SYNCHRONOUS_IO_ALERT);
+ ADD_FLAGS(FILE_SYNCHRONOUS_IO_NONALERT);
+ ADD_FLAGS(FILE_NON_DIRECTORY_FILE);
+ ADD_FLAGS(FILE_CREATE_TREE_CONNECTION);
+ ADD_FLAGS(FILE_COMPLETE_IF_OPLOCKED);
+ ADD_FLAGS(FILE_NO_EA_KNOWLEDGE);
+ ADD_FLAGS(FILE_EIGHT_DOT_THREE_ONLY);
+ ADD_FLAGS(FILE_RANDOM_ACCESS);
+ ADD_FLAGS(FILE_DELETE_ON_CLOSE);
+ ADD_FLAGS(FILE_OPEN_BY_FILE_ID);
+ ADD_FLAGS(FILE_OPEN_FOR_BACKUP_INTENT);
+ ADD_FLAGS(FILE_NO_COMPRESSION);
+ ADD_FLAGS(FILE_RESERVER_OPFILTER);
+ ADD_FLAGS(FILE_OPEN_REPARSE_POINT);
+ ADD_FLAGS(FILE_OPEN_NO_RECALL);
+ ADD_FLAGS(FILE_OPEN_FOR_FREE_SPACE_QUERY);
+
+ ADD_FLAGS(FILE_SHARE_READ);
+ ADD_FLAGS(FILE_SHARE_WRITE);
+ ADD_FLAGS(FILE_SHARE_DELETE);
+
+ /* change notify completion filter flags */
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_FILE_NAME);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_DIR_NAME);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_ATTRIBUTES);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_SIZE);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_LAST_WRITE);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_LAST_ACCESS);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_CREATION);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_EA);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_SECURITY);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_STREAM_NAME);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_STREAM_SIZE);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_STREAM_WRITE);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_NAME);
+ ADD_FLAGS(FILE_NOTIFY_CHANGE_ALL);
+
+ /* change notify action results */
+ ADD_FLAGS(NOTIFY_ACTION_ADDED);
+ ADD_FLAGS(NOTIFY_ACTION_REMOVED);
+ ADD_FLAGS(NOTIFY_ACTION_MODIFIED);
+ ADD_FLAGS(NOTIFY_ACTION_OLD_NAME);
+ ADD_FLAGS(NOTIFY_ACTION_NEW_NAME);
+ ADD_FLAGS(NOTIFY_ACTION_ADDED_STREAM);
+ ADD_FLAGS(NOTIFY_ACTION_REMOVED_STREAM);
+ ADD_FLAGS(NOTIFY_ACTION_MODIFIED_STREAM);
+
+ /* CreateDisposition values */
+ ADD_FLAGS(FILE_SUPERSEDE);
+ ADD_FLAGS(FILE_OPEN);
+ ADD_FLAGS(FILE_CREATE);
+ ADD_FLAGS(FILE_OPEN_IF);
+ ADD_FLAGS(FILE_OVERWRITE);
+ ADD_FLAGS(FILE_OVERWRITE_IF);
+
+ ADD_FLAGS(FSCTL_DFS_GET_REFERRALS);
+ ADD_FLAGS(FSCTL_DFS_GET_REFERRALS_EX);
+ ADD_FLAGS(FSCTL_REQUEST_OPLOCK_LEVEL_1);
+ ADD_FLAGS(FSCTL_REQUEST_OPLOCK_LEVEL_2);
+ ADD_FLAGS(FSCTL_REQUEST_BATCH_OPLOCK);
+ ADD_FLAGS(FSCTL_OPLOCK_BREAK_ACKNOWLEDGE);
+ ADD_FLAGS(FSCTL_OPBATCH_ACK_CLOSE_PENDING);
+ ADD_FLAGS(FSCTL_OPLOCK_BREAK_NOTIFY);
+ ADD_FLAGS(FSCTL_GET_COMPRESSION);
+ ADD_FLAGS(FSCTL_FILESYS_GET_STATISTICS);
+ ADD_FLAGS(FSCTL_GET_NTFS_VOLUME_DATA);
+ ADD_FLAGS(FSCTL_IS_VOLUME_DIRTY);
+ ADD_FLAGS(FSCTL_FIND_FILES_BY_SID);
+ ADD_FLAGS(FSCTL_SET_OBJECT_ID);
+ ADD_FLAGS(FSCTL_GET_OBJECT_ID);
+ ADD_FLAGS(FSCTL_DELETE_OBJECT_ID);
+ ADD_FLAGS(FSCTL_SET_REPARSE_POINT);
+ ADD_FLAGS(FSCTL_GET_REPARSE_POINT);
+ ADD_FLAGS(FSCTL_DELETE_REPARSE_POINT);
+ ADD_FLAGS(FSCTL_SET_OBJECT_ID_EXTENDED);
+ ADD_FLAGS(FSCTL_CREATE_OR_GET_OBJECT_ID);
+ ADD_FLAGS(FSCTL_SET_SPARSE);
+ ADD_FLAGS(FSCTL_SET_ZERO_DATA);
+ ADD_FLAGS(FSCTL_SET_ZERO_ON_DEALLOCATION);
+ ADD_FLAGS(FSCTL_READ_FILE_USN_DATA);
+ ADD_FLAGS(FSCTL_WRITE_USN_CLOSE_RECORD);
+ ADD_FLAGS(FSCTL_QUERY_ALLOCATED_RANGES);
+ ADD_FLAGS(FSCTL_QUERY_ON_DISK_VOLUME_INFO);
+ ADD_FLAGS(FSCTL_QUERY_SPARING_INFO);
+ ADD_FLAGS(FSCTL_FILE_LEVEL_TRIM);
+ ADD_FLAGS(FSCTL_OFFLOAD_READ);
+ ADD_FLAGS(FSCTL_OFFLOAD_WRITE);
+ ADD_FLAGS(FSCTL_SET_INTEGRITY_INFORMATION);
+ ADD_FLAGS(FSCTL_DUP_EXTENTS_TO_FILE);
+ ADD_FLAGS(FSCTL_DUPLICATE_EXTENTS_TO_FILE_EX);
+ ADD_FLAGS(FSCTL_STORAGE_QOS_CONTROL);
+ ADD_FLAGS(FSCTL_SVHDX_SYNC_TUNNEL_REQUEST);
+ ADD_FLAGS(FSCTL_QUERY_SHARED_VIRTUAL_DISK_SUPPORT);
+ ADD_FLAGS(FSCTL_PIPE_PEEK);
+ ADD_FLAGS(FSCTL_NAMED_PIPE_READ_WRITE);
+ ADD_FLAGS(FSCTL_PIPE_TRANSCEIVE);
+ ADD_FLAGS(FSCTL_PIPE_WAIT);
+ ADD_FLAGS(FSCTL_GET_SHADOW_COPY_DATA);
+ ADD_FLAGS(FSCTL_SRV_ENUM_SNAPS);
+ ADD_FLAGS(FSCTL_SRV_REQUEST_RESUME_KEY);
+ ADD_FLAGS(FSCTL_SRV_COPYCHUNK);
+ ADD_FLAGS(FSCTL_SRV_COPYCHUNK_WRITE);
+ ADD_FLAGS(FSCTL_SRV_READ_HASH);
+ ADD_FLAGS(FSCTL_LMR_REQ_RESILIENCY);
+ ADD_FLAGS(FSCTL_LMR_SET_LINK_TRACKING_INFORMATION);
+ ADD_FLAGS(FSCTL_QUERY_NETWORK_INTERFACE_INFO);
+
+ ADD_FLAGS(SYMLINK_ERROR_TAG);
+ ADD_FLAGS(SYMLINK_FLAG_RELATIVE);
+ ADD_FLAGS(SYMLINK_ADMIN);
+ ADD_FLAGS(SYMLINK_UNTRUSTED);
+ ADD_FLAGS(SYMLINK_TRUST_UNKNOWN);
+ ADD_FLAGS(SYMLINK_TRUST_MASK);
+
+ ADD_FLAGS(IO_REPARSE_TAG_RESERVED_ZERO);
+ ADD_FLAGS(IO_REPARSE_TAG_SYMLINK);
+ ADD_FLAGS(IO_REPARSE_TAG_MOUNT_POINT);
+ ADD_FLAGS(IO_REPARSE_TAG_HSM);
+ ADD_FLAGS(IO_REPARSE_TAG_SIS);
+ ADD_FLAGS(IO_REPARSE_TAG_DFS);
+ ADD_FLAGS(IO_REPARSE_TAG_NFS);
+
+#define ADD_STRING(val) PyModule_AddObject(m, #val, PyBytes_FromString(val))
+
+ ADD_STRING(SMB2_CREATE_TAG_EXTA);
+ ADD_STRING(SMB2_CREATE_TAG_MXAC);
+ ADD_STRING(SMB2_CREATE_TAG_SECD);
+ ADD_STRING(SMB2_CREATE_TAG_DHNQ);
+ ADD_STRING(SMB2_CREATE_TAG_DHNC);
+ ADD_STRING(SMB2_CREATE_TAG_ALSI);
+ ADD_STRING(SMB2_CREATE_TAG_TWRP);
+ ADD_STRING(SMB2_CREATE_TAG_QFID);
+ ADD_STRING(SMB2_CREATE_TAG_RQLS);
+ ADD_STRING(SMB2_CREATE_TAG_DH2Q);
+ ADD_STRING(SMB2_CREATE_TAG_DH2C);
+ ADD_STRING(SMB2_CREATE_TAG_AAPL);
+ ADD_STRING(SMB2_CREATE_TAG_APP_INSTANCE_ID);
+ ADD_STRING(SVHDX_OPEN_DEVICE_CONTEXT);
+ ADD_STRING(SMB2_CREATE_TAG_POSIX);
+ ADD_FLAGS(SMB2_FIND_POSIX_INFORMATION);
+ ADD_FLAGS(FILE_SUPERSEDE);
+ ADD_FLAGS(FILE_OPEN);
+ ADD_FLAGS(FILE_CREATE);
+ ADD_FLAGS(FILE_OPEN_IF);
+ ADD_FLAGS(FILE_OVERWRITE);
+ ADD_FLAGS(FILE_OVERWRITE_IF);
+ ADD_FLAGS(FILE_DIRECTORY_FILE);
+
+ ADD_FLAGS(SMB2_CLOSE_FLAGS_FULL_INFORMATION);
+
+ return m;
+}
diff --git a/source3/libsmb/samlogon_cache.c b/source3/libsmb/samlogon_cache.c
new file mode 100644
index 0000000..ab81b43
--- /dev/null
+++ b/source3/libsmb/samlogon_cache.c
@@ -0,0 +1,405 @@
+/*
+ Unix SMB/CIFS implementation.
+ Net_sam_logon info3 helpers
+ Copyright (C) Alexander Bokovoy 2002.
+ Copyright (C) Andrew Bartlett 2002.
+ Copyright (C) Gerald Carter 2003.
+ Copyright (C) Tim Potter 2003.
+ Copyright (C) Guenther Deschner 2008.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "samlogon_cache.h"
+#include "system/filesys.h"
+#include "system/time.h"
+#include "lib/util/debug.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/memory.h" /* for SAFE_FREE() */
+#include "source3/lib/util_path.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "../libcli/security/security.h"
+#include "util_tdb.h"
+
+#define NETSAMLOGON_TDB "netsamlogon_cache.tdb"
+
+static TDB_CONTEXT *netsamlogon_tdb = NULL;
+
+/***********************************************************************
+ open the tdb
+ ***********************************************************************/
+
+bool netsamlogon_cache_init(void)
+{
+ bool first_try = true;
+ char *path = NULL;
+ int ret;
+ struct tdb_context *tdb;
+
+ if (netsamlogon_tdb) {
+ return true;
+ }
+
+ path = cache_path(talloc_tos(), NETSAMLOGON_TDB);
+ if (path == NULL) {
+ return false;
+ }
+again:
+ tdb = tdb_open_log(path, 0, TDB_DEFAULT|TDB_INCOMPATIBLE_HASH,
+ O_RDWR | O_CREAT, 0600);
+ if (tdb == NULL) {
+ DEBUG(0,("tdb_open_log('%s') - failed\n", path));
+ goto clear;
+ }
+
+ ret = tdb_check(tdb, NULL, NULL);
+ if (ret != 0) {
+ tdb_close(tdb);
+ DEBUG(0,("tdb_check('%s') - failed\n", path));
+ goto clear;
+ }
+
+ netsamlogon_tdb = tdb;
+ talloc_free(path);
+ return true;
+
+clear:
+ if (!first_try) {
+ talloc_free(path);
+ return false;
+ }
+ first_try = false;
+
+ DEBUG(0,("retry after truncate for '%s'\n", path));
+ ret = truncate(path, 0);
+ if (ret == -1) {
+ DBG_ERR("truncate failed: %s\n", strerror(errno));
+ talloc_free(path);
+ return false;
+ }
+
+ goto again;
+}
+
+/***********************************************************************
+ Clear cache getpwnam and getgroups entries from the winbindd cache
+***********************************************************************/
+
+void netsamlogon_clear_cached_user(const struct dom_sid *user_sid)
+{
+ struct dom_sid_buf keystr;
+
+ if (!netsamlogon_cache_init()) {
+ DEBUG(0,("netsamlogon_clear_cached_user: cannot open "
+ "%s for write!\n",
+ NETSAMLOGON_TDB));
+ return;
+ }
+
+ /* Prepare key as DOMAIN-SID/USER-RID string */
+ dom_sid_str_buf(user_sid, &keystr);
+
+ DBG_DEBUG("SID [%s]\n", keystr.buf);
+
+ tdb_delete_bystring(netsamlogon_tdb, keystr.buf);
+}
+
+/***********************************************************************
+ Store a netr_SamInfo3 structure in a tdb for later user
+ username should be in UTF-8 format
+***********************************************************************/
+
+bool netsamlogon_cache_store(const char *username, struct netr_SamInfo3 *info3)
+{
+ uint8_t dummy = 0;
+ TDB_DATA data = { .dptr = &dummy, .dsize = sizeof(dummy) };
+ struct dom_sid_buf keystr;
+ bool result = false;
+ struct dom_sid user_sid;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ struct netsamlogoncache_entry r;
+ int ret;
+
+ if (!info3) {
+ goto fail;
+ }
+
+ if (!netsamlogon_cache_init()) {
+ D_WARNING("netsamlogon_cache_store: cannot open %s for write!\n",
+ NETSAMLOGON_TDB);
+ goto fail;
+ }
+
+ /*
+ * First write a record with just the domain sid for
+ * netsamlogon_cache_domain_known. Use TDB_INSERT to avoid
+ * overwriting potentially other data. We're just interested
+ * in the existence of that record.
+ */
+ dom_sid_str_buf(info3->base.domain_sid, &keystr);
+
+ ret = tdb_store_bystring(netsamlogon_tdb, keystr.buf, data, TDB_INSERT);
+
+ if ((ret == -1) && (tdb_error(netsamlogon_tdb) != TDB_ERR_EXISTS)) {
+ D_WARNING("Could not store domain marker for %s: %s\n",
+ keystr.buf, tdb_errorstr(netsamlogon_tdb));
+ goto fail;
+ }
+
+ sid_compose(&user_sid, info3->base.domain_sid, info3->base.rid);
+
+ /* Prepare key as DOMAIN-SID/USER-RID string */
+ dom_sid_str_buf(&user_sid, &keystr);
+
+ DBG_DEBUG("SID [%s]\n", keystr.buf);
+
+ /* Prepare data */
+
+ if (info3->base.full_name.string == NULL) {
+ struct netr_SamInfo3 *cached_info3;
+ const char *full_name = NULL;
+
+ cached_info3 = netsamlogon_cache_get(tmp_ctx, &user_sid);
+ if (cached_info3 != NULL) {
+ full_name = cached_info3->base.full_name.string;
+ }
+
+ if (full_name != NULL) {
+ info3->base.full_name.string = talloc_strdup(info3, full_name);
+ if (info3->base.full_name.string == NULL) {
+ goto fail;
+ }
+ }
+ }
+
+ /* only Samba fills in the username, not sure why NT doesn't */
+ /* so we fill it in since winbindd_getpwnam() makes use of it */
+
+ if (!info3->base.account_name.string) {
+ info3->base.account_name.string = talloc_strdup(info3, username);
+ if (info3->base.account_name.string == NULL) {
+ goto fail;
+ }
+ }
+
+ r.timestamp = time(NULL);
+ r.info3 = *info3;
+
+ /* avoid storing secret information */
+ ZERO_STRUCT(r.info3.base.key);
+ ZERO_STRUCT(r.info3.base.LMSessKey);
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(netsamlogoncache_entry, &r);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, tmp_ctx, &r,
+ (ndr_push_flags_fn_t)ndr_push_netsamlogoncache_entry);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("failed to push entry to cache: %s\n",
+ ndr_errstr(ndr_err));
+ goto fail;
+ }
+
+ data.dsize = blob.length;
+ data.dptr = blob.data;
+
+ if (tdb_store_bystring(netsamlogon_tdb, keystr.buf, data, TDB_REPLACE) == 0) {
+ result = true;
+ }
+
+fail:
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/***********************************************************************
+ Retrieves a netr_SamInfo3 structure from a tdb. Caller must
+ free the user_info struct (talloced memory)
+***********************************************************************/
+
+struct netr_SamInfo3 *netsamlogon_cache_get(TALLOC_CTX *mem_ctx, const struct dom_sid *user_sid)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ TDB_DATA data;
+ struct dom_sid_buf keystr;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ struct netsamlogoncache_entry r;
+
+ if (!netsamlogon_cache_init()) {
+ DEBUG(0,("netsamlogon_cache_get: cannot open %s for write!\n",
+ NETSAMLOGON_TDB));
+ return NULL;
+ }
+
+ /* Prepare key as DOMAIN-SID/USER-RID string */
+ dom_sid_str_buf(user_sid, &keystr);
+ DBG_DEBUG("SID [%s]\n", keystr.buf);
+ data = tdb_fetch_bystring( netsamlogon_tdb, keystr.buf );
+
+ if (!data.dptr) {
+ D_DEBUG("tdb fetch for %s is empty\n", keystr.buf);
+ return NULL;
+ }
+
+ info3 = talloc_zero(mem_ctx, struct netr_SamInfo3);
+ if (!info3) {
+ goto done;
+ }
+
+ blob = data_blob_const(data.dptr, data.dsize);
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_netsamlogoncache_entry);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ D_WARNING("netsamlogon_cache_get: failed to pull entry from cache\n");
+ tdb_delete_bystring(netsamlogon_tdb, keystr.buf);
+ TALLOC_FREE(info3);
+ goto done;
+ }
+
+ NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, netsamlogoncache_entry, &r);
+
+ info3 = (struct netr_SamInfo3 *)talloc_memdup(mem_ctx, &r.info3,
+ sizeof(r.info3));
+
+ done:
+ SAFE_FREE(data.dptr);
+
+ return info3;
+}
+
+bool netsamlogon_cache_have(const struct dom_sid *sid)
+{
+ struct dom_sid_buf keystr;
+ bool ok;
+
+ if (!netsamlogon_cache_init()) {
+ DBG_WARNING("Cannot open %s\n", NETSAMLOGON_TDB);
+ return false;
+ }
+
+ dom_sid_str_buf(sid, &keystr);
+
+ ok = tdb_exists(netsamlogon_tdb, string_term_tdb_data(keystr.buf));
+ return ok;
+}
+
+struct netsamlog_cache_forall_state {
+ TALLOC_CTX *mem_ctx;
+ int (*cb)(const char *sid_str,
+ time_t when_cached,
+ struct netr_SamInfo3 *,
+ void *private_data);
+ void *private_data;
+};
+
+static int netsamlog_cache_traverse_cb(struct tdb_context *tdb,
+ TDB_DATA key,
+ TDB_DATA data,
+ void *private_data)
+{
+ struct netsamlog_cache_forall_state *state =
+ (struct netsamlog_cache_forall_state *)private_data;
+ TALLOC_CTX *mem_ctx = NULL;
+ DATA_BLOB blob;
+ const char *sid_str = NULL;
+ struct dom_sid sid;
+ struct netsamlogoncache_entry r;
+ enum ndr_err_code ndr_err;
+ int ret;
+ bool ok;
+
+ if (key.dsize == 0) {
+ return 0;
+ }
+ if (key.dptr[key.dsize - 1] != '\0') {
+ return 0;
+ }
+ if (data.dptr == NULL) {
+ return 0;
+ }
+ sid_str = (char *)key.dptr;
+
+ ok = string_to_sid(&sid, sid_str);
+ if (!ok) {
+ DBG_ERR("String to SID failed for %s\n", sid_str);
+ return -1;
+ }
+
+ if (sid.num_auths != 5) {
+ return 0;
+ }
+
+ mem_ctx = talloc_new(state->mem_ctx);
+ if (mem_ctx == NULL) {
+ return -1;
+ }
+
+ blob = data_blob_const(data.dptr, data.dsize);
+
+ ndr_err = ndr_pull_struct_blob(
+ &blob, state->mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_netsamlogoncache_entry);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("failed to pull entry from cache\n");
+ return -1;
+ }
+
+ ret = state->cb(sid_str, r.timestamp, &r.info3, state->private_data);
+
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+int netsamlog_cache_for_all(int (*cb)(const char *sid_str,
+ time_t when_cached,
+ struct netr_SamInfo3 *,
+ void *private_data),
+ void *private_data)
+{
+ int ret;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct netsamlog_cache_forall_state state;
+
+ if (!netsamlogon_cache_init()) {
+ DBG_ERR("Cannot open %s\n", NETSAMLOGON_TDB);
+ return -1;
+ }
+
+ mem_ctx = talloc_init("netsamlog_cache_for_all");
+ if (mem_ctx == NULL) {
+ return -1;
+ }
+
+ state = (struct netsamlog_cache_forall_state) {
+ .mem_ctx = mem_ctx,
+ .cb = cb,
+ .private_data = private_data,
+ };
+
+ ret = tdb_traverse_read(netsamlogon_tdb,
+ netsamlog_cache_traverse_cb,
+ &state);
+
+ TALLOC_FREE(state.mem_ctx);
+ return ret;
+}
diff --git a/source3/libsmb/samlogon_cache.h b/source3/libsmb/samlogon_cache.h
new file mode 100644
index 0000000..29e0cea
--- /dev/null
+++ b/source3/libsmb/samlogon_cache.h
@@ -0,0 +1,46 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Net_sam_logon info3 helpers
+ * Copyright (C) Alexander Bokovoy 2002.
+ * Copyright (C) Andrew Bartlett 2002.
+ * Copyright (C) Gerald Carter 2003.
+ * Copyright (C) Tim Potter 2003.
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBSMB_SAMLOGON_CACHE_H__
+#define __LIBSMB_SAMLOGON_CACHE_H__
+
+#include "replace.h"
+#include <talloc.h>
+
+struct dom_sid;
+struct netr_SamInfo3;
+
+bool netsamlogon_cache_init(void);
+void netsamlogon_clear_cached_user(const struct dom_sid *user_sid);
+bool netsamlogon_cache_store(const char *username,
+ struct netr_SamInfo3 *info3);
+struct netr_SamInfo3 *netsamlogon_cache_get(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid);
+bool netsamlogon_cache_have(const struct dom_sid *sid);
+int netsamlog_cache_for_all(int (*cb)(const char *sid_str,
+ time_t when_cached,
+ struct netr_SamInfo3 *,
+ void *private_data),
+ void *private_data);
+
+#endif
diff --git a/source3/libsmb/smbclient.pc.in b/source3/libsmb/smbclient.pc.in
new file mode 100644
index 0000000..bcef2f2
--- /dev/null
+++ b/source3/libsmb/smbclient.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: smbclient
+Description: A SMB library interface
+Version: @PACKAGE_VERSION@
+Libs: @LIB_RPATH@ -L${libdir} -lsmbclient
+Cflags: -I${includedir}
+URL: http://www.samba.org/
diff --git a/source3/libsmb/smberr.c b/source3/libsmb/smberr.c
new file mode 100644
index 0000000..167c796
--- /dev/null
+++ b/source3/libsmb/smberr.c
@@ -0,0 +1,224 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Andrew Tridgell 1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+
+/* error code stuff - put together by Merik Karman
+ merik@blackadder.dsh.oz.au */
+
+
+/* There is a big list of error codes and their meanings at:
+
+ http://msdn.microsoft.com/library/default.asp?url=/library/en-us/debug/errlist_7oz7.asp
+
+ and if you don't like MSDN try:
+
+ http://www.siris.gr/computers/library/error.htm
+
+*/
+
+typedef const struct
+{
+ const char *name;
+ int code;
+ const char *message;
+} err_code_struct;
+
+/* Dos Error Messages */
+static err_code_struct dos_msgs[] = {
+ {"ERRbadfunc",ERRbadfunc,"Invalid function."},
+ {"ERRbadfile",ERRbadfile,"File not found."},
+ {"ERRbadpath",ERRbadpath,"Directory invalid."},
+ {"ERRnofids",ERRnofids,"No file descriptors available"},
+ {"ERRnoaccess",ERRnoaccess,"Access denied."},
+ {"ERRbadfid",ERRbadfid,"Invalid file handle."},
+ {"ERRbadmcb",ERRbadmcb,"Memory control blocks destroyed."},
+ {"ERRnomem",ERRnomem,"Insufficient server memory to perform the requested function."},
+ {"ERRbadmem",ERRbadmem,"Invalid memory block address."},
+ {"ERRbadenv",ERRbadenv,"Invalid environment."},
+ {"ERRbadformat",11,"Invalid format."},
+ {"ERRbadaccess",ERRbadaccess,"Invalid open mode."},
+ {"ERRbaddata",ERRbaddata,"Invalid data."},
+ {"ERRres",ERRres,"reserved."},
+ {"ERRbaddrive",ERRbaddrive,"Invalid drive specified."},
+ {"ERRremcd",ERRremcd,"A Delete Directory request attempted to remove the server's current directory."},
+ {"ERRdiffdevice",ERRdiffdevice,"Not same device."},
+ {"ERRnofiles",ERRnofiles,"A File Search command can find no more files matching the specified criteria."},
+ {"ERRbadshare",ERRbadshare,"The sharing mode specified for an Open conflicts with existing FIDs on the file."},
+ {"ERRlock",ERRlock,"A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."},
+ {"ERRunsup", ERRunsup, "The operation is unsupported"},
+ {"ERRnosuchshare", ERRnosuchshare, "You specified an invalid share name"},
+ {"ERRfilexists",ERRfilexists,"The file named in a Create Directory, Make New File or Link request already exists."},
+ {"ERRinvalidname",ERRinvalidname, "Invalid name"},
+ {"ERRbadpipe",ERRbadpipe,"Pipe invalid."},
+ {"ERRpipebusy",ERRpipebusy,"All instances of the requested pipe are busy."},
+ {"ERRpipeclosing",ERRpipeclosing,"Pipe close in progress."},
+ {"ERRnotconnected",ERRnotconnected,"No process on other end of pipe."},
+ {"ERRmoredata",ERRmoredata,"There is more data to be returned."},
+ {"ERRinvgroup",ERRinvgroup,"Invalid workgroup (try the -W option)"},
+ {"ERRlogonfailure",ERRlogonfailure,"Logon failure"},
+ {"ERRdiskfull",ERRdiskfull,"Disk full"},
+ {"ERRgeneral",ERRgeneral, "General failure"},
+ {"ERRbaddirectory", ERRbaddirectory, "Bad directory name"},
+ {"ERRunknownlevel",ERRunknownlevel, "Unknown info level"},
+ {NULL,-1,NULL}};
+
+/* Server Error Messages */
+static err_code_struct server_msgs[] = {
+ {"ERRerror",1,"Non-specific error code."},
+ {"ERRbadpw",2,"Bad password - name/password pair in a Tree Connect or Session Setup are invalid."},
+ {"ERRbadtype",3,"reserved."},
+ {"ERRaccess",4,"The requester does not have the necessary access rights within the specified context for the requested function. The context is defined by the TID or the UID."},
+ {"ERRinvnid",5,"The tree ID (TID) specified in a command was invalid."},
+ {"ERRinvnetname",6,"Invalid network name in tree connect."},
+ {"ERRinvdevice",7,"Invalid device - printer request made to non-printer connection or non-printer request made to printer connection."},
+ {"ERRqfull",49,"Print queue full (files) -- returned by open print file."},
+ {"ERRqtoobig",50,"Print queue full -- no space."},
+ {"ERRqeof",51,"EOF on print queue dump."},
+ {"ERRinvpfid",52,"Invalid print file FID."},
+ {"ERRsmbcmd",64,"The server did not recognize the command received."},
+ {"ERRsrverror",65,"The server encountered an internal error, e.g., system file unavailable."},
+ {"ERRfilespecs",67,"The file handle (FID) and pathname parameters contained an invalid combination of values."},
+ {"ERRreserved",68,"reserved."},
+ {"ERRbadpermits",69,"The access permissions specified for a file or directory are not a valid combination. The server cannot set the requested attribute."},
+ {"ERRreserved",70,"reserved."},
+ {"ERRsetattrmode",71,"The attribute mode in the Set File Attribute request is invalid."},
+ {"ERRpaused",81,"Server is paused."},
+ {"ERRmsgoff",82,"Not receiving messages."},
+ {"ERRnoroom",83,"No room to buffer message."},
+ {"ERRrmuns",87,"Too many remote user names."},
+ {"ERRtimeout",88,"Operation timed out."},
+ {"ERRnoresource",89,"No resources currently available for request."},
+ {"ERRtoomanyuids",90,"Too many UIDs active on this session."},
+ {"ERRbaduid",91,"The UID is not known as a valid ID on this session."},
+ {"ERRusempx",250,"Temp unable to support Raw, use MPX mode."},
+ {"ERRusestd",251,"Temp unable to support Raw, use standard read/write."},
+ {"ERRcontmpx",252,"Continue in MPX mode."},
+ {"ERRreserved",253,"reserved."},
+ {"ERRreserved",254,"reserved."},
+ {"ERRnosupport",0xFFFF,"Function not supported."},
+ {NULL,-1,NULL}};
+
+/* Hard Error Messages */
+static err_code_struct hard_msgs[] = {
+ {"ERRnowrite",19,"Attempt to write on write-protected diskette."},
+ {"ERRbadunit",20,"Unknown unit."},
+ {"ERRnotready",21,"Drive not ready."},
+ {"ERRbadcmd",22,"Unknown command."},
+ {"ERRdata",23,"Data error (CRC)."},
+ {"ERRbadreq",24,"Bad request structure length."},
+ {"ERRseek",25 ,"Seek error."},
+ {"ERRbadmedia",26,"Unknown media type."},
+ {"ERRbadsector",27,"Sector not found."},
+ {"ERRnopaper",28,"Printer out of paper."},
+ {"ERRwrite",29,"Write fault."},
+ {"ERRread",30,"Read fault."},
+ {"ERRgeneral",31,"General failure."},
+ {"ERRbadshare",32,"An open conflicts with an existing open."},
+ {"ERRlock",33,"A Lock request conflicted with an existing lock or specified an invalid mode, or an Unlock requested attempted to remove a lock held by another process."},
+ {"ERRwrongdisk",34,"The wrong disk was found in a drive."},
+ {"ERRFCBUnavail",35,"No FCBs are available to process request."},
+ {"ERRsharebufexc",36,"A sharing buffer has been exceeded."},
+ {NULL,-1,NULL}};
+
+
+static const struct
+{
+ int code;
+ const char *e_class;
+ err_code_struct *err_msgs;
+} err_classes[] = {
+ {0,"SUCCESS",NULL},
+ {0x01,"ERRDOS",dos_msgs},
+ {0x02,"ERRSRV",server_msgs},
+ {0x03,"ERRHRD",hard_msgs},
+ {0x04,"ERRXOS",NULL},
+ {0xE1,"ERRRMX1",NULL},
+ {0xE2,"ERRRMX2",NULL},
+ {0xE3,"ERRRMX3",NULL},
+ {0xFF,"ERRCMD",NULL},
+ {-1,NULL,NULL}};
+
+
+/****************************************************************************
+return a SMB error name from a class and code
+****************************************************************************/
+const char *smb_dos_err_name(uint8_t e_class, uint16_t num)
+{
+ char *result;
+ int i,j;
+
+ for (i=0;err_classes[i].e_class;i++)
+ if (err_classes[i].code == e_class) {
+ if (err_classes[i].err_msgs) {
+ err_code_struct *err = err_classes[i].err_msgs;
+ for (j=0;err[j].name;j++)
+ if (num == err[j].code) {
+ return err[j].name;
+ }
+ }
+ result = talloc_asprintf(talloc_tos(), "%d", num);
+ SMB_ASSERT(result != NULL);
+ return result;
+ }
+
+ result = talloc_asprintf(talloc_tos(), "Error: Unknown error class "
+ "(%d,%d)", e_class,num);
+ SMB_ASSERT(result != NULL);
+ return result;
+}
+
+/* Return a string for a DOS error */
+
+const char *get_dos_error_msg(WERROR result)
+{
+ uint16_t errnum;
+
+ errnum = W_ERROR_V(result);
+
+ return smb_dos_err_name(ERRDOS, errnum);
+}
+
+/****************************************************************************
+return a SMB error class name as a string.
+****************************************************************************/
+const char *smb_dos_err_class(uint8_t e_class)
+{
+ char *result;
+ int i;
+
+ for (i=0;err_classes[i].e_class;i++) {
+ if (err_classes[i].code == e_class) {
+ return err_classes[i].e_class;
+ }
+ }
+
+ result = talloc_asprintf(talloc_tos(), "Error: Unknown class (%d)",
+ e_class);
+ SMB_ASSERT(result != NULL);
+ return result;
+}
+
+/*****************************************************************************
+map a unix errno to a win32 error
+ *****************************************************************************/
+WERROR map_werror_from_unix(int error)
+{
+ NTSTATUS status = map_nt_error_from_unix(error);
+ return ntstatus_to_werror(status);
+}
diff --git a/source3/libsmb/smbsock_connect.c b/source3/libsmb/smbsock_connect.c
new file mode 100644
index 0000000..7f0baad
--- /dev/null
+++ b/source3/libsmb/smbsock_connect.c
@@ -0,0 +1,870 @@
+/*
+ Unix SMB/CIFS implementation.
+ Connect to 445 and 139/nbsesssetup
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../lib/async_req/async_sock.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../lib/util/tevent_unix.h"
+#include "client.h"
+#include "async_smb.h"
+#include "../libcli/smb/read_smb.h"
+#include "libsmb/nmblib.h"
+
+struct cli_session_request_state {
+ struct tevent_context *ev;
+ int sock;
+ uint32_t len_hdr;
+ struct iovec iov[3];
+ uint8_t nb_session_response;
+};
+
+static void cli_session_request_sent(struct tevent_req *subreq);
+static void cli_session_request_recvd(struct tevent_req *subreq);
+
+static struct tevent_req *cli_session_request_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int sock,
+ const struct nmb_name *called,
+ const struct nmb_name *calling)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_session_request_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct cli_session_request_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->sock = sock;
+
+ state->iov[1].iov_base = name_mangle(
+ state, called->name, called->name_type);
+ if (tevent_req_nomem(state->iov[1].iov_base, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->iov[1].iov_len = name_len(
+ (unsigned char *)state->iov[1].iov_base,
+ talloc_get_size(state->iov[1].iov_base));
+
+ state->iov[2].iov_base = name_mangle(
+ state, calling->name, calling->name_type);
+ if (tevent_req_nomem(state->iov[2].iov_base, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->iov[2].iov_len = name_len(
+ (unsigned char *)state->iov[2].iov_base,
+ talloc_get_size(state->iov[2].iov_base));
+
+ _smb_setlen(((char *)&state->len_hdr),
+ state->iov[1].iov_len + state->iov[2].iov_len);
+ SCVAL((char *)&state->len_hdr, 0, 0x81);
+
+ state->iov[0].iov_base = &state->len_hdr;
+ state->iov[0].iov_len = sizeof(state->len_hdr);
+
+ subreq = writev_send(state, ev, NULL, sock, true, state->iov, 3);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_session_request_sent, req);
+ return req;
+}
+
+static void cli_session_request_sent(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_request_state *state = tevent_req_data(
+ req, struct cli_session_request_state);
+ ssize_t ret;
+ int err;
+
+ ret = writev_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+ subreq = read_smb_send(state, state->ev, state->sock);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_session_request_recvd, req);
+}
+
+static void cli_session_request_recvd(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_session_request_state *state = tevent_req_data(
+ req, struct cli_session_request_state);
+ uint8_t *buf;
+ ssize_t ret;
+ int err;
+
+ ret = read_smb_recv(subreq, talloc_tos(), &buf, &err);
+ TALLOC_FREE(subreq);
+
+ if (ret < 4) {
+ ret = -1;
+ err = EIO;
+ }
+ if (ret == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+ /*
+ * In case of an error there is more information in the data
+ * portion according to RFC1002. We're not subtle enough to
+ * respond to the different error conditions, so drop the
+ * error info here.
+ */
+ state->nb_session_response = CVAL(buf, 0);
+ tevent_req_done(req);
+}
+
+static bool cli_session_request_recv(struct tevent_req *req, int *err, uint8_t *resp)
+{
+ struct cli_session_request_state *state = tevent_req_data(
+ req, struct cli_session_request_state);
+
+ if (tevent_req_is_unix_error(req, err)) {
+ return false;
+ }
+ *resp = state->nb_session_response;
+ return true;
+}
+
+struct nb_connect_state {
+ struct tevent_context *ev;
+ const struct sockaddr_storage *addr;
+ const char *called_name;
+ int sock;
+ struct tevent_req *session_subreq;
+ struct nmb_name called;
+ struct nmb_name calling;
+};
+
+static void nb_connect_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
+static void nb_connect_connected(struct tevent_req *subreq);
+static void nb_connect_done(struct tevent_req *subreq);
+
+static struct tevent_req *nb_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct sockaddr_storage *addr,
+ const char *called_name,
+ int called_type,
+ const char *calling_name,
+ int calling_type)
+{
+ struct tevent_req *req, *subreq;
+ struct nb_connect_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct nb_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->called_name = called_name;
+ state->addr = addr;
+
+ state->sock = -1;
+ make_nmb_name(&state->called, called_name, called_type);
+ make_nmb_name(&state->calling, calling_name, calling_type);
+
+ tevent_req_set_cleanup_fn(req, nb_connect_cleanup);
+
+ subreq = open_socket_out_send(state, ev, addr, NBT_SMB_PORT, 5000);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, nb_connect_connected, req);
+ return req;
+}
+
+static void nb_connect_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct nb_connect_state *state = tevent_req_data(
+ req, struct nb_connect_state);
+
+ /*
+ * we need to free a pending request before closing the
+ * socket, see bug #11141
+ */
+ TALLOC_FREE(state->session_subreq);
+
+ if (req_state == TEVENT_REQ_DONE) {
+ /*
+ * we keep the socket open for the caller to use
+ */
+ return;
+ }
+
+ if (state->sock != -1) {
+ close(state->sock);
+ state->sock = -1;
+ }
+
+ return;
+}
+
+static void nb_connect_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_connect_state *state = tevent_req_data(
+ req, struct nb_connect_state);
+ NTSTATUS status;
+
+ status = open_socket_out_recv(subreq, &state->sock);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_session_request_send(state, state->ev, state->sock,
+ &state->called, &state->calling);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_connect_done, req);
+ state->session_subreq = subreq;
+}
+
+static void nb_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_connect_state *state = tevent_req_data(
+ req, struct nb_connect_state);
+ bool ret;
+ int err;
+ uint8_t resp;
+
+ state->session_subreq = NULL;
+
+ ret = cli_session_request_recv(subreq, &err, &resp);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ /*
+ * RFC1002: 0x82 - POSITIVE SESSION RESPONSE
+ */
+
+ if (resp != 0x82) {
+ /*
+ * The server did not like our session request
+ */
+ close(state->sock);
+ state->sock = -1;
+
+ if (strequal(state->called_name, "*SMBSERVER")) {
+ /*
+ * Here we could try a name status request and
+ * use the first 0x20 type name.
+ */
+ tevent_req_nterror(
+ req, NT_STATUS_RESOURCE_NAME_NOT_FOUND);
+ return;
+ }
+
+ /*
+ * We could be subtle and distinguish between
+ * different failure modes, but what we do here
+ * instead is just retry with *SMBSERVER type 0x20.
+ */
+ state->called_name = "*SMBSERVER";
+ make_nmb_name(&state->called, state->called_name, 0x20);
+
+ subreq = open_socket_out_send(state, state->ev, state->addr,
+ NBT_SMB_PORT, 5000);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_connect_connected, req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS nb_connect_recv(struct tevent_req *req, int *sock)
+{
+ struct nb_connect_state *state = tevent_req_data(
+ req, struct nb_connect_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *sock = state->sock;
+ state->sock = -1;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct smbsock_connect_state {
+ struct tevent_context *ev;
+ const struct sockaddr_storage *addr;
+ const char *called_name;
+ uint8_t called_type;
+ const char *calling_name;
+ uint8_t calling_type;
+ struct tevent_req *req_139;
+ struct tevent_req *req_445;
+ int sock;
+ uint16_t port;
+};
+
+static void smbsock_connect_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
+static void smbsock_connect_connected(struct tevent_req *subreq);
+static void smbsock_connect_do_139(struct tevent_req *subreq);
+
+struct tevent_req *smbsock_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct sockaddr_storage *addr,
+ uint16_t port,
+ const char *called_name,
+ int called_type,
+ const char *calling_name,
+ int calling_type)
+{
+ struct tevent_req *req;
+ struct smbsock_connect_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct smbsock_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->addr = addr;
+ state->sock = -1;
+ state->called_name =
+ (called_name != NULL) ? called_name : "*SMBSERVER";
+ state->called_type =
+ (called_type != -1) ? called_type : 0x20;
+ state->calling_name =
+ (calling_name != NULL) ? calling_name : lp_netbios_name();
+ state->calling_type =
+ (calling_type != -1) ? calling_type : 0x00;
+
+ tevent_req_set_cleanup_fn(req, smbsock_connect_cleanup);
+
+ if (port == NBT_SMB_PORT) {
+ if (lp_disable_netbios()) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ state->req_139 = nb_connect_send(state, state->ev, state->addr,
+ state->called_name,
+ state->called_type,
+ state->calling_name,
+ state->calling_type);
+ if (tevent_req_nomem(state->req_139, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->req_139, smbsock_connect_connected, req);
+ return req;
+ }
+ if (port != 0) {
+ state->req_445 = open_socket_out_send(state, ev, addr, port,
+ 5000);
+ if (tevent_req_nomem(state->req_445, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->req_445, smbsock_connect_connected, req);
+ return req;
+ }
+
+ /*
+ * port==0, try both
+ */
+
+ state->req_445 = open_socket_out_send(state, ev, addr, TCP_SMB_PORT, 5000);
+ if (tevent_req_nomem(state->req_445, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->req_445, smbsock_connect_connected,
+ req);
+
+ /*
+ * Check for disable_netbios
+ */
+ if (lp_disable_netbios()) {
+ return req;
+ }
+
+ /*
+ * After 5 msecs, fire the 139 (NBT) request
+ */
+ state->req_139 = tevent_wakeup_send(
+ state, ev, timeval_current_ofs(0, 5000));
+ if (tevent_req_nomem(state->req_139, req)) {
+ TALLOC_FREE(state->req_445);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->req_139, smbsock_connect_do_139,
+ req);
+ return req;
+}
+
+static void smbsock_connect_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smbsock_connect_state *state = tevent_req_data(
+ req, struct smbsock_connect_state);
+
+ /*
+ * we need to free a pending request before closing the
+ * socket, see bug #11141
+ */
+ TALLOC_FREE(state->req_445);
+ TALLOC_FREE(state->req_139);
+
+ if (req_state == TEVENT_REQ_DONE) {
+ /*
+ * we keep the socket open for the caller to use
+ */
+ return;
+ }
+
+ if (state->sock != -1) {
+ close(state->sock);
+ state->sock = -1;
+ }
+
+ return;
+}
+
+static void smbsock_connect_do_139(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbsock_connect_state *state = tevent_req_data(
+ req, struct smbsock_connect_state);
+ bool ret;
+
+ ret = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ state->req_139 = nb_connect_send(state, state->ev, state->addr,
+ state->called_name,
+ state->called_type,
+ state->calling_name,
+ state->calling_type);
+ if (tevent_req_nomem(state->req_139, req)) {
+ return;
+ }
+ tevent_req_set_callback(state->req_139, smbsock_connect_connected,
+ req);
+}
+
+static void smbsock_connect_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbsock_connect_state *state = tevent_req_data(
+ req, struct smbsock_connect_state);
+ struct tevent_req *unfinished_req;
+ NTSTATUS status;
+
+ if (subreq == state->req_445) {
+
+ status = open_socket_out_recv(subreq, &state->sock);
+ TALLOC_FREE(state->req_445);
+ unfinished_req = state->req_139;
+ state->port = TCP_SMB_PORT;
+
+ } else if (subreq == state->req_139) {
+
+ status = nb_connect_recv(subreq, &state->sock);
+ TALLOC_FREE(state->req_139);
+ unfinished_req = state->req_445;
+ state->port = NBT_SMB_PORT;
+
+ } else {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(unfinished_req);
+ state->req_139 = NULL;
+ state->req_445 = NULL;
+ tevent_req_done(req);
+ return;
+ }
+ if (unfinished_req == NULL) {
+ /*
+ * Both requests failed
+ */
+ tevent_req_nterror(req, status);
+ return;
+ }
+ /*
+ * Do nothing, wait for the second request to come here.
+ */
+}
+
+NTSTATUS smbsock_connect_recv(struct tevent_req *req, int *sock,
+ uint16_t *ret_port)
+{
+ struct smbsock_connect_state *state = tevent_req_data(
+ req, struct smbsock_connect_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *sock = state->sock;
+ state->sock = -1;
+ if (ret_port != NULL) {
+ *ret_port = state->port;
+ }
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbsock_connect(const struct sockaddr_storage *addr, uint16_t port,
+ const char *called_name, int called_type,
+ const char *calling_name, int calling_type,
+ int *pfd, uint16_t *ret_port, int sec_timeout)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = smbsock_connect_send(frame, ev, addr, port,
+ called_name, called_type,
+ calling_name, calling_type);
+ if (req == NULL) {
+ goto fail;
+ }
+ if ((sec_timeout != 0) &&
+ !tevent_req_set_endtime(
+ req, ev, timeval_current_ofs(sec_timeout, 0))) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = smbsock_connect_recv(req, pfd, ret_port);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct smbsock_any_connect_state {
+ struct tevent_context *ev;
+ const struct sockaddr_storage *addrs;
+ const char **called_names;
+ int *called_types;
+ const char **calling_names;
+ int *calling_types;
+ size_t num_addrs;
+ uint16_t port;
+
+ struct tevent_req **requests;
+ size_t num_sent;
+ size_t num_received;
+
+ int fd;
+ uint16_t chosen_port;
+ size_t chosen_index;
+};
+
+static void smbsock_any_connect_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
+static bool smbsock_any_connect_send_next(
+ struct tevent_req *req, struct smbsock_any_connect_state *state);
+static void smbsock_any_connect_trynext(struct tevent_req *subreq);
+static void smbsock_any_connect_connected(struct tevent_req *subreq);
+
+struct tevent_req *smbsock_any_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct sockaddr_storage *addrs,
+ const char **called_names,
+ int *called_types,
+ const char **calling_names,
+ int *calling_types,
+ size_t num_addrs, uint16_t port)
+{
+ struct tevent_req *req, *subreq;
+ struct smbsock_any_connect_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbsock_any_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->addrs = addrs;
+ state->num_addrs = num_addrs;
+ state->called_names = called_names;
+ state->called_types = called_types;
+ state->calling_names = calling_names;
+ state->calling_types = calling_types;
+ state->port = port;
+ state->fd = -1;
+
+ tevent_req_set_cleanup_fn(req, smbsock_any_connect_cleanup);
+
+ if (num_addrs == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->requests = talloc_zero_array(state, struct tevent_req *,
+ num_addrs);
+ if (tevent_req_nomem(state->requests, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (!smbsock_any_connect_send_next(req, state)) {
+ return tevent_req_post(req, ev);
+ }
+ if (state->num_sent >= state->num_addrs) {
+ return req;
+ }
+ subreq = tevent_wakeup_send(state, ev, timeval_current_ofs(0, 10000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbsock_any_connect_trynext, req);
+ return req;
+}
+
+static void smbsock_any_connect_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smbsock_any_connect_state *state = tevent_req_data(
+ req, struct smbsock_any_connect_state);
+
+ TALLOC_FREE(state->requests);
+
+ if (req_state == TEVENT_REQ_DONE) {
+ /*
+ * Keep the socket open for the caller.
+ */
+ return;
+ }
+
+ if (state->fd != -1) {
+ close(state->fd);
+ state->fd = -1;
+ }
+}
+
+static void smbsock_any_connect_trynext(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbsock_any_connect_state *state = tevent_req_data(
+ req, struct smbsock_any_connect_state);
+ bool ret;
+
+ ret = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ if (!smbsock_any_connect_send_next(req, state)) {
+ return;
+ }
+ if (state->num_sent >= state->num_addrs) {
+ return;
+ }
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_set(0, 10000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbsock_any_connect_trynext, req);
+}
+
+static bool smbsock_any_connect_send_next(
+ struct tevent_req *req, struct smbsock_any_connect_state *state)
+{
+ struct tevent_req *subreq;
+
+ if (state->num_sent >= state->num_addrs) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return false;
+ }
+ subreq = smbsock_connect_send(
+ state->requests, state->ev, &state->addrs[state->num_sent],
+ state->port,
+ (state->called_names != NULL)
+ ? state->called_names[state->num_sent] : NULL,
+ (state->called_types != NULL)
+ ? state->called_types[state->num_sent] : -1,
+ (state->calling_names != NULL)
+ ? state->calling_names[state->num_sent] : NULL,
+ (state->calling_types != NULL)
+ ? state->calling_types[state->num_sent] : -1);
+ if (tevent_req_nomem(subreq, req)) {
+ return false;
+ }
+ tevent_req_set_callback(subreq, smbsock_any_connect_connected, req);
+
+ state->requests[state->num_sent] = subreq;
+ state->num_sent += 1;
+
+ return true;
+}
+
+static void smbsock_any_connect_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbsock_any_connect_state *state = tevent_req_data(
+ req, struct smbsock_any_connect_state);
+ NTSTATUS status;
+ int fd = 0;
+ uint16_t chosen_port = 0;
+ size_t i;
+ size_t chosen_index = 0;
+
+ for (i=0; i<state->num_sent; i++) {
+ if (state->requests[i] == subreq) {
+ chosen_index = i;
+ break;
+ }
+ }
+ if (i == state->num_sent) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ status = smbsock_connect_recv(subreq, &fd, &chosen_port);
+
+ TALLOC_FREE(subreq);
+ state->requests[chosen_index] = NULL;
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * tevent_req_done() will kill all the other requests
+ * via smbsock_any_connect_cleanup().
+ */
+ state->fd = fd;
+ state->chosen_port = chosen_port;
+ state->chosen_index = chosen_index;
+ tevent_req_done(req);
+ return;
+ }
+
+ state->num_received += 1;
+ if (state->num_received < state->num_addrs) {
+ /*
+ * More addrs pending, wait for the others
+ */
+ return;
+ }
+
+ /*
+ * This is the last response, none succeeded.
+ */
+ tevent_req_nterror(req, status);
+ return;
+}
+
+NTSTATUS smbsock_any_connect_recv(struct tevent_req *req, int *pfd,
+ size_t *chosen_index,
+ uint16_t *chosen_port)
+{
+ struct smbsock_any_connect_state *state = tevent_req_data(
+ req, struct smbsock_any_connect_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *pfd = state->fd;
+ state->fd = -1;
+ if (chosen_index != NULL) {
+ *chosen_index = state->chosen_index;
+ }
+ if (chosen_port != NULL) {
+ *chosen_port = state->chosen_port;
+ }
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbsock_any_connect(const struct sockaddr_storage *addrs,
+ const char **called_names,
+ int *called_types,
+ const char **calling_names,
+ int *calling_types,
+ size_t num_addrs,
+ uint16_t port,
+ int sec_timeout,
+ int *pfd, size_t *chosen_index,
+ uint16_t *chosen_port)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = smbsock_any_connect_send(frame, ev, addrs,
+ called_names, called_types,
+ calling_names, calling_types,
+ num_addrs, port);
+ if (req == NULL) {
+ goto fail;
+ }
+ if ((sec_timeout != 0) &&
+ !tevent_req_set_endtime(
+ req, ev, timeval_current_ofs(sec_timeout, 0))) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = smbsock_any_connect_recv(req, pfd, chosen_index, chosen_port);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/libsmb/trusts_util.c b/source3/libsmb/trusts_util.c
new file mode 100644
index 0000000..c40eb79
--- /dev/null
+++ b/source3/libsmb/trusts_util.c
@@ -0,0 +1,630 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Routines to operate on various trust relationships
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Rafal Szczesniak 2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../libcli/auth/netlogon_creds_cli.h"
+#include "rpc_client/cli_netlogon.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "librpc/gen_ndr/secrets.h"
+#include "secrets.h"
+#include "passdb.h"
+#include "libsmb/libsmb.h"
+#include "source3/include/messages.h"
+#include "source3/include/g_lock.h"
+#include "lib/util/util_tdb.h"
+
+/*********************************************************
+ Change the domain password on the PDC.
+ Do most of the legwork ourselves. Caller must have
+ already setup the connection to the NETLOGON pipe
+**********************************************************/
+
+struct trust_pw_change_state {
+ struct g_lock_ctx *g_ctx;
+ char *g_lock_key;
+};
+
+static int trust_pw_change_state_destructor(struct trust_pw_change_state *state)
+{
+ g_lock_unlock(state->g_ctx,
+ string_term_tdb_data(state->g_lock_key));
+ return 0;
+}
+
+char *trust_pw_new_value(TALLOC_CTX *mem_ctx,
+ enum netr_SchannelType sec_channel_type,
+ int security)
+{
+ /*
+ * use secure defaults, which match
+ * what windows uses for computer passwords.
+ *
+ * We used to have min=128 and max=255 here, but
+ * it's a bad idea because of bugs in the Windows
+ * RODC/RWDC PasswordUpdateForward handling via
+ * NetrLogonSendToSam.
+ *
+ * See https://bugzilla.samba.org/show_bug.cgi?id=14984
+ */
+ size_t min = 120;
+ size_t max = 120;
+
+ switch (sec_channel_type) {
+ case SEC_CHAN_WKSTA:
+ case SEC_CHAN_BDC:
+ if (security == SEC_DOMAIN) {
+ /*
+ * The maximum length of a trust account password.
+ * Used when we randomly create it, 15 char passwords
+ * exceed NT4's max password length.
+ */
+ min = 14;
+ max = 14;
+ }
+ break;
+ case SEC_CHAN_DNS_DOMAIN:
+ /*
+ * new_len * 2 = 498 bytes is the largest possible length
+ * NL_PASSWORD_VERSION consumes the rest of the possible 512 bytes
+ * and a confounder with at least 2 bytes is required.
+ *
+ * Windows uses new_len = 120 => 240 bytes (utf16)
+ */
+ min = 120;
+ max = 120;
+ break;
+ case SEC_CHAN_DOMAIN:
+ /*
+ * The maximum length of a trust account password.
+ * Used when we randomly create it, 15 char passwords
+ * exceed NT4's max password length.
+ */
+ min = 14;
+ max = 14;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Create a random machine account password
+ * We create a random buffer and convert that to utf8.
+ * This is similar to what windows is doing.
+ */
+ return generate_random_machine_password(mem_ctx, min, max);
+}
+
+/*
+ * Temporary function to wrap cli_auth in a lck
+ */
+
+static NTSTATUS netlogon_creds_cli_lck_auth(
+ struct netlogon_creds_cli_context *context,
+ struct dcerpc_binding_handle *b,
+ uint8_t num_nt_hashes,
+ const struct samr_Password * const *nt_hashes,
+ uint8_t *idx_nt_hashes)
+{
+ struct netlogon_creds_cli_lck *lck;
+ NTSTATUS status;
+
+ status = netlogon_creds_cli_lck(
+ context, NETLOGON_CREDS_CLI_LCK_EXCLUSIVE,
+ talloc_tos(), &lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("netlogon_creds_cli_lck failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = netlogon_creds_cli_auth(context, b, num_nt_hashes, nt_hashes,
+ idx_nt_hashes);
+ TALLOC_FREE(lck);
+
+ return status;
+}
+
+NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *domain,
+ const char *dcname,
+ bool force)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *context_name = NULL;
+ struct trust_pw_change_state *state;
+ struct cli_credentials *creds = NULL;
+ struct secrets_domain_info1 *info = NULL;
+ struct secrets_domain_info1_change *prev = NULL;
+ const struct samr_Password *current_nt_hash = NULL;
+ const struct samr_Password *previous_nt_hash = NULL;
+ uint8_t num_nt_hashes = 0;
+ uint8_t idx = 0;
+ const struct samr_Password *nt_hashes[1+3] = { NULL, };
+ uint8_t idx_nt_hashes = 0;
+ uint8_t idx_current = UINT8_MAX;
+ enum netr_SchannelType sec_channel_type = SEC_CHAN_NULL;
+ time_t pass_last_set_time;
+ uint32_t old_version = 0;
+ struct pdb_trusted_domain *td = NULL;
+ struct timeval g_timeout = { 0, };
+ int timeout = 0;
+ struct timeval tv = { 0, };
+ char *new_trust_pw_str = NULL;
+ size_t len = 0;
+ DATA_BLOB new_trust_pw_blob = data_blob_null;
+ uint32_t new_version = 0;
+ uint32_t *new_trust_version = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ state = talloc_zero(frame, struct trust_pw_change_state);
+ if (state == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->g_ctx = g_lock_ctx_init(state, msg_ctx);
+ if (state->g_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->g_lock_key = talloc_asprintf(state,
+ "trust_password_change_%s",
+ domain);
+ if (state->g_lock_key == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ g_timeout = timeval_current_ofs(10, 0);
+ status = g_lock_lock(state->g_ctx,
+ string_term_tdb_data(state->g_lock_key),
+ G_LOCK_WRITE, g_timeout, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("could not get g_lock on [%s]!\n",
+ state->g_lock_key));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ talloc_set_destructor(state, trust_pw_change_state_destructor);
+
+ status = pdb_get_trust_credentials(domain, NULL, frame, &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("could not fetch domain creds for domain %s - %s!\n",
+ domain, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+ }
+
+ current_nt_hash = cli_credentials_get_nt_hash(creds, frame);
+ if (current_nt_hash == NULL) {
+ DEBUG(0, ("cli_credentials_get_nt_hash failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+ }
+ previous_nt_hash = cli_credentials_get_old_nt_hash(creds, frame);
+
+ old_version = cli_credentials_get_kvno(creds);
+ pass_last_set_time = cli_credentials_get_password_last_changed_time(creds);
+ sec_channel_type = cli_credentials_get_secure_channel_type(creds);
+
+ new_version = old_version + 1;
+
+ switch (sec_channel_type) {
+ case SEC_CHAN_WKSTA:
+ case SEC_CHAN_BDC:
+ break;
+ case SEC_CHAN_DNS_DOMAIN:
+ case SEC_CHAN_DOMAIN:
+ status = pdb_get_trusted_domain(frame, domain, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("pdb_get_trusted_domain() failed for domain %s - %s!\n",
+ domain, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ new_trust_version = &new_version;
+ break;
+ default:
+ TALLOC_FREE(frame);
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ timeout = lp_machine_password_timeout();
+ if (timeout == 0) {
+ if (!force) {
+ DEBUG(10,("machine password never expires\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+ }
+
+ tv.tv_sec = pass_last_set_time;
+ DEBUG(10, ("password last changed %s\n",
+ timeval_string(talloc_tos(), &tv, false)));
+ tv.tv_sec += timeout;
+ DEBUGADD(10, ("password valid until %s\n",
+ timeval_string(talloc_tos(), &tv, false)));
+
+ if (!force && !timeval_expired(&tv)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ context_name = netlogon_creds_cli_debug_string(context, talloc_tos());
+ if (context_name == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Create a random machine account password
+ * We create a random buffer and convert that to utf8.
+ * This is similar to what windows is doing.
+ */
+ new_trust_pw_str = trust_pw_new_value(frame, sec_channel_type,
+ lp_security());
+ if (new_trust_pw_str == NULL) {
+ DEBUG(0, ("trust_pw_new_value() failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ len = strlen(new_trust_pw_str);
+ ok = convert_string_talloc(frame, CH_UNIX, CH_UTF16,
+ new_trust_pw_str, len,
+ (void **)&new_trust_pw_blob.data,
+ &new_trust_pw_blob.length);
+ if (!ok) {
+ status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+ "failed for of %s - %s\n",
+ domain, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+ talloc_keep_secret(new_trust_pw_blob.data);
+
+ switch (sec_channel_type) {
+
+ case SEC_CHAN_WKSTA:
+ case SEC_CHAN_BDC:
+ status = secrets_prepare_password_change(domain, dcname,
+ new_trust_pw_str,
+ frame, &info, &prev);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("secrets_prepare_password_change() failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ TALLOC_FREE(new_trust_pw_str);
+
+ if (prev != NULL) {
+ /*
+ * We had a failure before we changed the password.
+ */
+ nt_hashes[idx++] = &prev->password->nt_hash;
+
+ DEBUG(0,("%s : %s(%s): A password change was already "
+ "started against '%s' at %s. Trying to "
+ "recover...\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain,
+ prev->password->change_server,
+ nt_time_string(talloc_tos(),
+ prev->password->change_time)));
+ DEBUG(0,("%s : %s(%s): Last failure local[%s] remote[%s] "
+ "against '%s' at %s.\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain,
+ nt_errstr(prev->local_status),
+ nt_errstr(prev->remote_status),
+ prev->change_server,
+ nt_time_string(talloc_tos(),
+ prev->change_time)));
+ }
+
+ idx_current = idx;
+ nt_hashes[idx++] = &info->password->nt_hash;
+ if (info->old_password != NULL) {
+ nt_hashes[idx++] = &info->old_password->nt_hash;
+ }
+ if (info->older_password != NULL) {
+ nt_hashes[idx++] = &info->older_password->nt_hash;
+ }
+
+ /*
+ * We use the password that's already persistent in
+ * our database in order to handle failures.
+ */
+ data_blob_free(&new_trust_pw_blob);
+ new_trust_pw_blob = info->next_change->password->cleartext_blob;
+ break;
+
+ case SEC_CHAN_DNS_DOMAIN:
+ case SEC_CHAN_DOMAIN:
+ idx_current = idx;
+ nt_hashes[idx++] = current_nt_hash;
+ if (previous_nt_hash != NULL) {
+ nt_hashes[idx++] = previous_nt_hash;
+ }
+ break;
+
+ default:
+ smb_panic("Unsupported secure channel type");
+ break;
+ }
+ num_nt_hashes = idx;
+
+ DEBUG(0,("%s : %s(%s): Verifying passwords remotely %s.\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain, context_name));
+
+ /*
+ * Check which password the dc knows about.
+ *
+ * TODO:
+ * If the previous password is the only password in common with the dc,
+ * we better skip the password change, or use something like
+ * ServerTrustPasswordsGet() or netr_ServerGetTrustInfo() to fix our
+ * local secrets before doing the change.
+ */
+ status = netlogon_creds_cli_lck_auth(context, b,
+ num_nt_hashes,
+ nt_hashes,
+ &idx_nt_hashes);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("netlogon_creds_cli_auth(%s) failed for old passwords (%u) - %s!\n",
+ context_name, num_nt_hashes, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (prev != NULL && idx_nt_hashes == 0) {
+ DEBUG(0,("%s : %s(%s): Verified new password remotely "
+ "without changing %s\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain, context_name));
+
+ status = secrets_finish_password_change(prev->password->change_server,
+ prev->password->change_time,
+ info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("secrets_prepare_password_change() failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ DEBUG(0,("%s : %s(%s): Recovered previous password change.\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (idx_nt_hashes != idx_current) {
+ DEBUG(0,("%s : %s(%s): Verified older password remotely "
+ "skip changing %s\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain, context_name));
+
+ if (info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+ }
+
+ status = secrets_defer_password_change(dcname,
+ NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE,
+ NT_STATUS_NOT_COMMITTED,
+ info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("secrets_defer_password_change() failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ TALLOC_FREE(frame);
+ return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+ }
+
+ DEBUG(0,("%s : %s(%s): Verified old password remotely using %s\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain, context_name));
+
+ /*
+ * Return the result of trying to write the new password
+ * back into the trust account file.
+ */
+
+ switch (sec_channel_type) {
+
+ case SEC_CHAN_WKSTA:
+ case SEC_CHAN_BDC:
+ /*
+ * we called secrets_prepare_password_change() above.
+ */
+ break;
+
+ case SEC_CHAN_DNS_DOMAIN:
+ case SEC_CHAN_DOMAIN:
+ /*
+ * we need to get the sid first for the
+ * pdb_set_trusteddom_pw call
+ */
+ ok = pdb_set_trusteddom_pw(domain, new_trust_pw_str,
+ &td->security_identifier);
+ if (!ok) {
+ DEBUG(0, ("pdb_set_trusteddom_pw() failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ TALLOC_FREE(new_trust_pw_str);
+ break;
+
+ default:
+ smb_panic("Unsupported secure channel type");
+ break;
+ }
+
+ DEBUG(0,("%s : %s(%s): Changed password locally\n",
+ current_timestring(talloc_tos(), false), __func__, domain));
+
+ status = netlogon_creds_cli_ServerPasswordSet(context, b,
+ &new_trust_pw_blob,
+ new_trust_version);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS status2;
+ const char *fn = NULL;
+
+ ok = dcerpc_binding_handle_is_connected(b);
+
+ DEBUG(0,("%s : %s(%s) remote password change with %s failed "
+ "- %s (%s)\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain, context_name,
+ nt_errstr(status),
+ ok ? "connected": "disconnected"));
+
+ if (!ok) {
+ /*
+ * The connection is broken, we don't
+ * know if the password was changed,
+ * we hope to have more luck next time.
+ */
+ status2 = secrets_failed_password_change(dcname,
+ NT_STATUS_NOT_COMMITTED,
+ status,
+ info);
+ fn = "secrets_failed_password_change";
+ } else {
+ /*
+ * The server rejected the change, we don't
+ * retry and defer the change to the next
+ * "machine password timeout" interval.
+ */
+ status2 = secrets_defer_password_change(dcname,
+ NT_STATUS_NOT_COMMITTED,
+ status,
+ info);
+ fn = "secrets_defer_password_change";
+ }
+ if (!NT_STATUS_IS_OK(status2)) {
+ DEBUG(0, ("%s() failed for domain %s!\n",
+ fn, domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ DEBUG(0,("%s : %s(%s): Changed password remotely using %s\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain, context_name));
+
+ switch (sec_channel_type) {
+
+ case SEC_CHAN_WKSTA:
+ case SEC_CHAN_BDC:
+ status = secrets_finish_password_change(
+ info->next_change->change_server,
+ info->next_change->change_time,
+ info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("secrets_finish_password_change() failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ DEBUG(0,("%s : %s(%s): Finished password change.\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain));
+ break;
+
+ case SEC_CHAN_DNS_DOMAIN:
+ case SEC_CHAN_DOMAIN:
+ /*
+ * we used pdb_set_trusteddom_pw().
+ */
+ break;
+
+ default:
+ smb_panic("Unsupported secure channel type");
+ break;
+ }
+
+ ok = cli_credentials_set_utf16_password(creds,
+ &new_trust_pw_blob,
+ CRED_SPECIFIED);
+ if (!ok) {
+ DEBUG(0, ("cli_credentials_set_password failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ current_nt_hash = cli_credentials_get_nt_hash(creds, frame);
+ if (current_nt_hash == NULL) {
+ DEBUG(0, ("cli_credentials_get_nt_hash failed for domain %s!\n",
+ domain));
+ TALLOC_FREE(frame);
+ return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
+ }
+
+ /*
+ * Now we verify the new password.
+ */
+ idx = 0;
+ nt_hashes[idx++] = current_nt_hash;
+ num_nt_hashes = idx;
+ status = netlogon_creds_cli_lck_auth(context, b,
+ num_nt_hashes,
+ nt_hashes,
+ &idx_nt_hashes);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("netlogon_creds_cli_auth(%s) failed for new password - %s!\n",
+ context_name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ DEBUG(0,("%s : %s(%s): Verified new password remotely using %s\n",
+ current_timestring(talloc_tos(), false),
+ __func__, domain, context_name));
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/unexpected.c b/source3/libsmb/unexpected.c
new file mode 100644
index 0000000..b81d379
--- /dev/null
+++ b/source3/libsmb/unexpected.c
@@ -0,0 +1,751 @@
+/*
+ Unix SMB/CIFS implementation.
+ handle unexpected packets
+ Copyright (C) Andrew Tridgell 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "libsmb/unexpected.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "lib/util_tsock.h"
+#include "libsmb/nmblib.h"
+#include "lib/tsocket/tsocket.h"
+#include "lib/util/sys_rw.h"
+
+static const char *nmbd_socket_dir(void)
+{
+ return lp_parm_const_string(-1, "nmbd", "socket dir",
+ get_dyn_NMBDSOCKETDIR());
+}
+
+struct nb_packet_query {
+ enum packet_type type;
+ size_t mailslot_namelen;
+ int trn_id;
+};
+
+struct nb_packet_client;
+
+struct nb_packet_server {
+ struct tevent_context *ev;
+ int listen_sock;
+ struct tevent_fd *listen_fde;
+ int max_clients;
+ int num_clients;
+ struct nb_packet_client *clients;
+};
+
+struct nb_packet_client {
+ struct nb_packet_client *prev, *next;
+ struct nb_packet_server *server;
+
+ enum packet_type type;
+ int trn_id;
+ char *mailslot_name;
+
+ struct {
+ uint8_t byte;
+ struct iovec iov[1];
+ } ack;
+
+ struct tstream_context *sock;
+ struct tevent_queue *out_queue;
+};
+
+static int nb_packet_server_destructor(struct nb_packet_server *s);
+static void nb_packet_server_listener(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+NTSTATUS nb_packet_server_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int max_clients,
+ struct nb_packet_server **presult)
+{
+ struct nb_packet_server *result;
+ NTSTATUS status;
+ int rc;
+
+ result = talloc_zero(mem_ctx, struct nb_packet_server);
+ if (result == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ result->ev = ev;
+ result->max_clients = max_clients;
+
+ result->listen_sock = create_pipe_sock(
+ nmbd_socket_dir(), "unexpected", 0755);
+ if (result->listen_sock == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+ rc = listen(result->listen_sock, 5);
+ if (rc < 0) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+ talloc_set_destructor(result, nb_packet_server_destructor);
+
+ result->listen_fde = tevent_add_fd(ev, result,
+ result->listen_sock,
+ TEVENT_FD_READ,
+ nb_packet_server_listener,
+ result);
+ if (result->listen_fde == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+fail:
+ TALLOC_FREE(result);
+ return status;
+}
+
+static int nb_packet_server_destructor(struct nb_packet_server *s)
+{
+ TALLOC_FREE(s->listen_fde);
+
+ if (s->listen_sock != -1) {
+ close(s->listen_sock);
+ s->listen_sock = -1;
+ }
+ return 0;
+}
+
+static int nb_packet_client_destructor(struct nb_packet_client *c);
+static ssize_t nb_packet_client_more(uint8_t *buf, size_t buflen,
+ void *private_data);
+static void nb_packet_got_query(struct tevent_req *req);
+static void nb_packet_client_ack_done(struct tevent_req *req);
+static void nb_packet_client_read_done(struct tevent_req *req);
+
+static void nb_packet_server_listener(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct nb_packet_server *server = talloc_get_type_abort(
+ private_data, struct nb_packet_server);
+ struct nb_packet_client *client;
+ struct tevent_req *req;
+ struct sockaddr_un sunaddr;
+ socklen_t len;
+ int sock;
+ int ret;
+
+ len = sizeof(sunaddr);
+
+ sock = accept(server->listen_sock, (struct sockaddr *)(void *)&sunaddr,
+ &len);
+ if (sock == -1) {
+ return;
+ }
+ smb_set_close_on_exec(sock);
+ DEBUG(6,("accepted socket %d\n", sock));
+
+ client = talloc_zero(server, struct nb_packet_client);
+ if (client == NULL) {
+ DEBUG(10, ("talloc failed\n"));
+ close(sock);
+ return;
+ }
+ ret = tstream_bsd_existing_socket(client, sock, &client->sock);
+ if (ret != 0) {
+ DEBUG(10, ("tstream_bsd_existing_socket failed\n"));
+ TALLOC_FREE(client);
+ close(sock);
+ return;
+ }
+ /* as server we want to fail early */
+ tstream_bsd_fail_readv_first_error(client->sock, true);
+
+ client->server = server;
+
+ client->out_queue = tevent_queue_create(
+ client, "unexpected packet output");
+ if (client->out_queue == NULL) {
+ DEBUG(10, ("tevent_queue_create failed\n"));
+ TALLOC_FREE(client);
+ return;
+ }
+
+ req = tstream_read_packet_send(client, ev, client->sock,
+ sizeof(struct nb_packet_query),
+ nb_packet_client_more, NULL);
+ if (req == NULL) {
+ DEBUG(10, ("tstream_read_packet_send failed\n"));
+ TALLOC_FREE(client);
+ return;
+ }
+ tevent_req_set_callback(req, nb_packet_got_query, client);
+
+ DLIST_ADD(server->clients, client);
+ server->num_clients += 1;
+
+ talloc_set_destructor(client, nb_packet_client_destructor);
+
+ if (server->num_clients > server->max_clients) {
+ DEBUG(10, ("Too many clients, dropping oldest\n"));
+
+ /*
+ * no TALLOC_FREE here, don't mess with the list structs
+ */
+ talloc_free(server->clients->prev);
+ }
+}
+
+static ssize_t nb_packet_client_more(uint8_t *buf, size_t buflen,
+ void *private_data)
+{
+ struct nb_packet_query q;
+ if (buflen > sizeof(struct nb_packet_query)) {
+ return 0;
+ }
+ /* Take care of alignment */
+ memcpy(&q, buf, sizeof(q));
+ if (q.mailslot_namelen > 1024) {
+ DEBUG(10, ("Got invalid mailslot namelen %d\n",
+ (int)q.mailslot_namelen));
+ return -1;
+ }
+ return q.mailslot_namelen;
+}
+
+static int nb_packet_client_destructor(struct nb_packet_client *c)
+{
+ tevent_queue_stop(c->out_queue);
+ TALLOC_FREE(c->sock);
+
+ DLIST_REMOVE(c->server->clients, c);
+ c->server->num_clients -= 1;
+ return 0;
+}
+
+static void nb_packet_got_query(struct tevent_req *req)
+{
+ struct nb_packet_client *client = tevent_req_callback_data(
+ req, struct nb_packet_client);
+ struct nb_packet_query q;
+ uint8_t *buf;
+ ssize_t nread;
+ int err;
+
+ nread = tstream_read_packet_recv(req, talloc_tos(), &buf, &err);
+ TALLOC_FREE(req);
+ if (nread < (ssize_t)sizeof(struct nb_packet_query)) {
+ DEBUG(10, ("read_packet_recv returned %d (%s)\n",
+ (int)nread,
+ (nread == -1) ? strerror(err) : "wrong length"));
+ TALLOC_FREE(client);
+ return;
+ }
+
+ /* Take care of alignment */
+ memcpy(&q, buf, sizeof(q));
+
+ if ((size_t)nread !=
+ sizeof(struct nb_packet_query) + q.mailslot_namelen) {
+ DEBUG(10, ("nb_packet_got_query: Invalid mailslot namelength\n"));
+ TALLOC_FREE(client);
+ return;
+ }
+
+ client->trn_id = q.trn_id;
+ client->type = q.type;
+ if (q.mailslot_namelen > 0) {
+ client->mailslot_name = talloc_strndup(
+ client, (char *)buf + sizeof(q),
+ q.mailslot_namelen);
+ if (client->mailslot_name == NULL) {
+ TALLOC_FREE(client);
+ return;
+ }
+ }
+
+ client->ack.byte = 0;
+ client->ack.iov[0].iov_base = &client->ack.byte;
+ client->ack.iov[0].iov_len = 1;
+ req = tstream_writev_queue_send(client, client->server->ev,
+ client->sock,
+ client->out_queue,
+ client->ack.iov, 1);
+ if (req == NULL) {
+ DEBUG(10, ("tstream_writev_queue_send failed\n"));
+ TALLOC_FREE(client);
+ return;
+ }
+ tevent_req_set_callback(req, nb_packet_client_ack_done, client);
+
+ req = tstream_read_packet_send(client, client->server->ev,
+ client->sock, 1, NULL, NULL);
+ if (req == NULL) {
+ DEBUG(10, ("Could not activate reader for client exit "
+ "detection\n"));
+ TALLOC_FREE(client);
+ return;
+ }
+ tevent_req_set_callback(req, nb_packet_client_read_done,
+ client);
+}
+
+static void nb_packet_client_ack_done(struct tevent_req *req)
+{
+ struct nb_packet_client *client = tevent_req_callback_data(
+ req, struct nb_packet_client);
+ ssize_t nwritten;
+ int err;
+
+ nwritten = tstream_writev_queue_recv(req, &err);
+
+ TALLOC_FREE(req);
+
+ if (nwritten == -1) {
+ DEBUG(10, ("tstream_writev_queue_recv failed: %s\n",
+ strerror(err)));
+ TALLOC_FREE(client);
+ return;
+ }
+}
+
+static void nb_packet_client_read_done(struct tevent_req *req)
+{
+ struct nb_packet_client *client = tevent_req_callback_data(
+ req, struct nb_packet_client);
+ ssize_t nread;
+ uint8_t *buf;
+ int err;
+
+ nread = tstream_read_packet_recv(req, talloc_tos(), &buf, &err);
+ TALLOC_FREE(req);
+ if (nread == 1) {
+ DEBUG(10, ("Protocol error, received data on write-only "
+ "unexpected socket: 0x%2.2x\n", (*buf)));
+ }
+ TALLOC_FREE(client);
+}
+
+static void nb_packet_client_send(struct nb_packet_client *client,
+ struct packet_struct *p);
+
+void nb_packet_dispatch(struct nb_packet_server *server,
+ struct packet_struct *p)
+{
+ struct nb_packet_client *c;
+ uint16_t trn_id;
+
+ switch (p->packet_type) {
+ case NMB_PACKET:
+ trn_id = p->packet.nmb.header.name_trn_id;
+ break;
+ case DGRAM_PACKET:
+ trn_id = p->packet.dgram.header.dgm_id;
+ break;
+ default:
+ DEBUG(10, ("Got invalid packet type %d\n",
+ (int)p->packet_type));
+ return;
+ }
+ for (c = server->clients; c != NULL; c = c->next) {
+
+ if (c->type != p->packet_type) {
+ DEBUG(10, ("client expects packet %d, got %d\n",
+ c->type, p->packet_type));
+ continue;
+ }
+
+ if (p->packet_type == NMB_PACKET) {
+ /*
+ * See if the client specified transaction
+ * ID. Filter if it did.
+ */
+ if ((c->trn_id != -1) &&
+ (c->trn_id != trn_id)) {
+ DEBUG(10, ("client expects trn %d, got %d\n",
+ c->trn_id, trn_id));
+ continue;
+ }
+ } else {
+ /*
+ * See if the client specified a mailslot
+ * name. Filter if it did.
+ */
+ if ((c->mailslot_name != NULL) &&
+ !match_mailslot_name(p, c->mailslot_name)) {
+ continue;
+ }
+ }
+ nb_packet_client_send(c, p);
+ }
+}
+
+struct nb_packet_client_header {
+ size_t len;
+ enum packet_type type;
+ time_t timestamp;
+ struct in_addr ip;
+ int port;
+};
+
+struct nb_packet_client_state {
+ struct nb_packet_client *client;
+ struct iovec iov[2];
+ struct nb_packet_client_header hdr;
+ char buf[1024];
+};
+
+static void nb_packet_client_send_done(struct tevent_req *req);
+
+static void nb_packet_client_send(struct nb_packet_client *client,
+ struct packet_struct *p)
+{
+ struct nb_packet_client_state *state;
+ struct tevent_req *req;
+
+ if (tevent_queue_length(client->out_queue) > 10) {
+ /*
+ * Skip clients that don't listen anyway, some form of DoS
+ * protection
+ */
+ return;
+ }
+
+ state = talloc_zero(client, struct nb_packet_client_state);
+ if (state == NULL) {
+ DEBUG(10, ("talloc failed\n"));
+ return;
+ }
+
+ state->client = client;
+
+ state->hdr.ip = p->ip;
+ state->hdr.port = p->port;
+ state->hdr.timestamp = p->timestamp;
+ state->hdr.type = p->packet_type;
+ state->hdr.len = build_packet(state->buf, sizeof(state->buf), p);
+
+ state->iov[0].iov_base = (char *)&state->hdr;
+ state->iov[0].iov_len = sizeof(state->hdr);
+ state->iov[1].iov_base = state->buf;
+ state->iov[1].iov_len = state->hdr.len;
+
+ req = tstream_writev_queue_send(state, client->server->ev,
+ client->sock,
+ client->out_queue,
+ state->iov, 2);
+ if (req == NULL) {
+ DEBUG(10, ("tstream_writev_queue_send failed\n"));
+ return;
+ }
+ tevent_req_set_callback(req, nb_packet_client_send_done, state);
+}
+
+static void nb_packet_client_send_done(struct tevent_req *req)
+{
+ struct nb_packet_client_state *state = tevent_req_callback_data(
+ req, struct nb_packet_client_state);
+ struct nb_packet_client *client = state->client;
+ ssize_t nwritten;
+ int err;
+
+ nwritten = tstream_writev_queue_recv(req, &err);
+
+ TALLOC_FREE(req);
+ TALLOC_FREE(state);
+
+ if (nwritten == -1) {
+ DEBUG(10, ("tstream_writev_queue failed: %s\n", strerror(err)));
+ TALLOC_FREE(client);
+ return;
+ }
+}
+
+struct nb_packet_reader {
+ struct tstream_context *sock;
+};
+
+struct nb_packet_reader_state {
+ struct tevent_context *ev;
+ struct nb_packet_query query;
+ const char *mailslot_name;
+ struct iovec iov[2];
+ struct nb_packet_reader *reader;
+};
+
+static void nb_packet_reader_connected(struct tevent_req *subreq);
+static void nb_packet_reader_sent_query(struct tevent_req *subreq);
+static void nb_packet_reader_got_ack(struct tevent_req *subreq);
+
+struct tevent_req *nb_packet_reader_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ enum packet_type type,
+ int trn_id,
+ const char *mailslot_name)
+{
+ struct tevent_req *req, *subreq;
+ struct nb_packet_reader_state *state;
+ struct tsocket_address *laddr;
+ char *rpath;
+ struct tsocket_address *raddr;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct nb_packet_reader_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->query.trn_id = trn_id;
+ state->query.type = type;
+ state->mailslot_name = mailslot_name;
+
+ if (mailslot_name != NULL) {
+ state->query.mailslot_namelen = strlen(mailslot_name);
+ }
+
+ state->reader = talloc_zero(state, struct nb_packet_reader);
+ if (tevent_req_nomem(state->reader, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ret = tsocket_address_unix_from_path(state, NULL, &laddr);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return tevent_req_post(req, ev);
+ }
+ rpath = talloc_asprintf(state, "%s/%s", nmbd_socket_dir(),
+ "unexpected");
+ if (tevent_req_nomem(rpath, req)) {
+ return tevent_req_post(req, ev);
+ }
+ ret = tsocket_address_unix_from_path(state, rpath, &raddr);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = tstream_unix_connect_send(state, ev, laddr, raddr);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, nb_packet_reader_connected, req);
+ return req;
+}
+
+static void nb_packet_reader_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_packet_reader_state *state = tevent_req_data(
+ req, struct nb_packet_reader_state);
+ int res, err;
+ int num_iovecs = 1;
+
+ res = tstream_unix_connect_recv(subreq, &err, state->reader,
+ &state->reader->sock);
+ TALLOC_FREE(subreq);
+ if (res == -1) {
+ DEBUG(10, ("tstream_unix_connect failed: %s\n", strerror(err)));
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ state->iov[0].iov_base = (char *)&state->query;
+ state->iov[0].iov_len = sizeof(state->query);
+
+ if (state->mailslot_name != NULL) {
+ num_iovecs = 2;
+ state->iov[1].iov_base = discard_const_p(
+ char, state->mailslot_name);
+ state->iov[1].iov_len = state->query.mailslot_namelen;
+ }
+
+ subreq = tstream_writev_send(state, state->ev, state->reader->sock,
+ state->iov, num_iovecs);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_packet_reader_sent_query, req);
+}
+
+static void nb_packet_reader_sent_query(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_packet_reader_state *state = tevent_req_data(
+ req, struct nb_packet_reader_state);
+ ssize_t written;
+ int err;
+
+ written = tstream_writev_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (written == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ if ((size_t)written !=
+ sizeof(state->query) + state->query.mailslot_namelen) {
+ tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR);
+ return;
+ }
+ subreq = tstream_read_packet_send(state, state->ev,
+ state->reader->sock,
+ 1, NULL, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nb_packet_reader_got_ack, req);
+}
+
+static void nb_packet_reader_got_ack(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_packet_reader_state *state = tevent_req_data(
+ req, struct nb_packet_reader_state);
+ ssize_t nread;
+ int err;
+ uint8_t *buf;
+
+ nread = tstream_read_packet_recv(subreq, state, &buf, &err);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ DEBUG(10, ("read_packet_recv returned %s\n",
+ strerror(err)));
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ if (nread != 1) {
+ DBG_DEBUG("read = %zd, expected 1\n", nread);
+ tevent_req_nterror(req, NT_STATUS_UNEXPECTED_IO_ERROR);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS nb_packet_reader_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct nb_packet_reader **preader)
+{
+ struct nb_packet_reader_state *state = tevent_req_data(
+ req, struct nb_packet_reader_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+ *preader = talloc_move(mem_ctx, &state->reader);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct nb_packet_read_state {
+ struct nb_packet_client_header hdr;
+ uint8_t *buf;
+ size_t buflen;
+};
+
+static ssize_t nb_packet_read_more(uint8_t *buf, size_t buflen, void *p);
+static void nb_packet_read_done(struct tevent_req *subreq);
+
+struct tevent_req *nb_packet_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct nb_packet_reader *reader)
+{
+ struct tevent_req *req, *subreq;
+ struct nb_packet_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct nb_packet_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = tstream_read_packet_send(state, ev, reader->sock,
+ sizeof(struct nb_packet_client_header),
+ nb_packet_read_more, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, nb_packet_read_done, req);
+ return req;
+}
+
+static ssize_t nb_packet_read_more(uint8_t *buf, size_t buflen, void *p)
+{
+ struct nb_packet_read_state *state = talloc_get_type_abort(
+ p, struct nb_packet_read_state);
+
+ if (buflen > sizeof(struct nb_packet_client_header)) {
+ /*
+ * Been here, done
+ */
+ return 0;
+ }
+ memcpy(&state->hdr, buf, sizeof(struct nb_packet_client_header));
+ return state->hdr.len;
+}
+
+static void nb_packet_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nb_packet_read_state *state = tevent_req_data(
+ req, struct nb_packet_read_state);
+ ssize_t nread;
+ int err;
+
+ nread = tstream_read_packet_recv(subreq, state, &state->buf, &err);
+ if (nread == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ state->buflen = nread;
+ tevent_req_done(req);
+}
+
+NTSTATUS nb_packet_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct packet_struct **ppacket)
+{
+ struct nb_packet_read_state *state = tevent_req_data(
+ req, struct nb_packet_read_state);
+ struct nb_packet_client_header hdr;
+ struct packet_struct *packet;
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ memcpy(&hdr, state->buf, sizeof(hdr));
+
+ packet = parse_packet_talloc(
+ mem_ctx,
+ (char *)state->buf + sizeof(struct nb_packet_client_header),
+ state->buflen - sizeof(struct nb_packet_client_header),
+ state->hdr.type, state->hdr.ip, state->hdr.port);
+ if (packet == NULL) {
+ tevent_req_received(req);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ *ppacket = packet;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/libsmb/unexpected.h b/source3/libsmb/unexpected.h
new file mode 100644
index 0000000..270976b
--- /dev/null
+++ b/source3/libsmb/unexpected.h
@@ -0,0 +1,49 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Copyright (C) Volker Lendecke 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LIBSMB_UNEXPECTED_H__
+#define __LIBSMB_UNEXPECTED_H__
+
+#include "replace.h"
+#include <tevent.h>
+#include "libcli/util/ntstatus.h"
+#include "nameserv.h"
+
+struct nb_packet_server;
+struct nb_packet_reader;
+
+NTSTATUS nb_packet_server_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int max_clients,
+ struct nb_packet_server **presult);
+void nb_packet_dispatch(struct nb_packet_server *server,
+ struct packet_struct *p);
+struct tevent_req *nb_packet_reader_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ enum packet_type type,
+ int trn_id,
+ const char *mailslot_name);
+NTSTATUS nb_packet_reader_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct nb_packet_reader **preader);
+struct tevent_req *nb_packet_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct nb_packet_reader *reader);
+NTSTATUS nb_packet_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct packet_struct **ppacket);
+
+#endif
diff --git a/source3/libsmb/wscript b/source3/libsmb/wscript
new file mode 100644
index 0000000..8ffdec6
--- /dev/null
+++ b/source3/libsmb/wscript
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+def build(bld):
+ bld.SAMBA3_LIBRARY('smbclient',
+ source='''
+ libsmb_cache.c
+ libsmb_compat.c
+ libsmb_context.c
+ libsmb_dir.c
+ libsmb_file.c
+ libsmb_misc.c
+ libsmb_path.c
+ libsmb_printjob.c
+ libsmb_server.c
+ libsmb_stat.c
+ libsmb_xattr.c
+ libsmb_setget.c''',
+ public_deps='''
+ pthread
+ talloc
+ smbconf
+ libsmb
+ KRBCLIENT
+ msrpc3
+ libcli_lsa3''',
+ public_headers='../include/libsmbclient.h',
+ abi_directory='ABI',
+ abi_match='smbc_*',
+ vnum='0.8.0',
+ pc_files='smbclient.pc')
diff --git a/source3/locale/net/de.po b/source3/locale/net/de.po
new file mode 100644
index 0000000..1f460b2
--- /dev/null
+++ b/source3/locale/net/de.po
@@ -0,0 +1,8852 @@
+# net message translation (german).
+# Copyright (C) 2009, 2010 Kai Blin <kai@samba.org>
+# Copyright (C) 2009, 2010 André Hentschel <nerv@dawncrow.de>
+# This file is distributed under the same license as the samba package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: @PACKAGE@\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-01-19 14:40+0100\n"
+"PO-Revision-Date: 2010-01-14 18:55+0100\n"
+"Last-Translator: André Hentschel <nerv@dawncrow.de>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../utils/net.c:103
+msgid "Enter machine password: "
+msgstr "Bitte Maschinenpasswort eingeben: "
+
+#: ../../utils/net.c:107
+msgid "Unable to write the machine account password in the secrets database"
+msgstr ""
+
+#: ../../utils/net.c:111
+msgid "Modified trust account password in secrets database\n"
+msgstr ""
+
+#: ../../utils/net.c:115
+msgid ""
+"Machine account password change requires the -f flag.\n"
+"Do NOT use this function unless you know what it does!\n"
+"This function will change the ADS Domain member machine account password in the secrets.tdb file!\n"
+msgstr ""
+
+#: ../../utils/net.c:132
+msgid "Failed to open secrets.tdb.\n"
+msgstr ""
+
+#: ../../utils/net.c:139 ../../utils/net.c:157 ../../utils/net_ads.c:1435 ../../utils/net_ads.c:1487 ../../utils/net_conf.c:1156 ../../utils/net_groupmap.c:195 ../../utils/net_groupmap.c:271 ../../utils/net_groupmap.c:355 ../../utils/net_groupmap.c:411
+#: ../../utils/net_groupmap.c:494 ../../utils/net_groupmap.c:521 ../../utils/net_help.c:36 ../../utils/net_rap.c:161 ../../utils/net_rap.c:302 ../../utils/net_rap.c:467 ../../utils/net_rap.c:752 ../../utils/net_rap.c:893 ../../utils/net_rap.c:1004
+#: ../../utils/net_rap.c:1195 ../../utils/net_rpc.c:2730 ../../utils/net_rpc.c:2845 ../../utils/net_rpc.c:4961 ../../utils/net_rpc.c:6953 ../../utils/net_rpc.c:7043 ../../utils/net_rpc.c:7148 ../../utils/net_util.c:586
+msgid "Usage:\n"
+msgstr "Verwendung:\n"
+
+#: ../../utils/net.c:141
+#, c-format
+msgid ""
+" net setauthuser -U user[%%password] \n"
+" Set the auth user account to userpassword. Prompt for password if not specified.\n"
+msgstr ""
+
+#: ../../utils/net.c:146 ../../utils/net.c:164
+msgid ""
+" net setauthuser delete\n"
+" Delete the auth user setting.\n"
+msgstr ""
+
+#: ../../utils/net.c:159
+#, c-format
+msgid ""
+" net setauthuser -U user[%%password]\n"
+" Set the auth user account to userpassword. Prompt for password if not specified.\n"
+msgstr ""
+
+#: ../../utils/net.c:169
+msgid "the auth user"
+msgstr ""
+
+#: ../../utils/net.c:171
+msgid "Failed to get the auth users password.\n"
+msgstr ""
+
+#: ../../utils/net.c:177
+msgid "error storing auth user name\n"
+msgstr ""
+
+#: ../../utils/net.c:183
+msgid "error storing auth user domain\n"
+msgstr ""
+
+#: ../../utils/net.c:189
+msgid "error storing auth user password\n"
+msgstr ""
+
+#: ../../utils/net.c:213
+msgid "No authorised user configured\n"
+msgstr ""
+
+#: ../../utils/net.c:255 ../../utils/net.c:336
+#, c-format
+msgid "Unable to open secrets.tdb. Can't fetch domain SID for name: %s\n"
+msgstr ""
+
+#: ../../utils/net.c:268 ../../utils/net.c:359
+#, c-format
+msgid "SID for domain %s is: %s\n"
+msgstr "SID der Domäne %s ist: %s\n"
+
+# c-format
+#: ../../utils/net.c:280 ../../utils/net.c:301 ../../utils/net.c:320 ../../utils/net.c:405 ../../utils/net_ads.c:134 ../../utils/net_ads.c:166 ../../utils/net_ads.c:382 ../../utils/net_ads.c:709 ../../utils/net_ads.c:862 ../../utils/net_ads.c:898
+#: ../../utils/net_ads.c:942 ../../utils/net_ads.c:1057 ../../utils/net_ads.c:1560 ../../utils/net_ads.c:1600 ../../utils/net_ads.c:1665 ../../utils/net_ads.c:1792 ../../utils/net_ads.c:1896 ../../utils/net_ads.c:1982 ../../utils/net_ads.c:2225
+#: ../../utils/net_ads.c:2246 ../../utils/net_ads.c:2274 ../../utils/net_ads.c:2293 ../../utils/net_ads.c:2362 ../../utils/net_ads.c:2388 ../../utils/net_ads.c:2445 ../../utils/net_ads_gpo.c:41 ../../utils/net_ads_gpo.c:230 ../../utils/net_ads_gpo.c:304
+#: ../../utils/net_ads_gpo.c:456 ../../utils/net_ads_gpo.c:498 ../../utils/net_ads_gpo.c:586 ../../utils/net_afs.c:38 ../../utils/net_afs.c:71 ../../utils/net_cache.c:159 ../../utils/net_cache.c:198 ../../utils/net_cache.c:228 ../../utils/net_cache.c:257
+#: ../../utils/net_cache.c:283 ../../utils/net_cache.c:306 ../../utils/net_cache.c:321 ../../utils/net_conf.c:42 ../../utils/net_conf.c:50 ../../utils/net_conf.c:63 ../../utils/net_conf.c:70 ../../utils/net_conf.c:78 ../../utils/net_conf.c:87
+#: ../../utils/net_conf.c:104 ../../utils/net_conf.c:113 ../../utils/net_conf.c:122 ../../utils/net_conf.c:131 ../../utils/net_conf.c:140 ../../utils/net_conf.c:149 ../../utils/net_conf.c:158 ../../utils/net_dom.c:27 ../../utils/net_dom.c:32
+#: ../../utils/net_dom.c:37 ../../utils/net_eventlog.c:47 ../../utils/net_eventlog.c:104 ../../utils/net_eventlog.c:200 ../../utils/net_groupmap.c:119 ../../utils/net_groupmap.c:557 ../../utils/net_groupmap.c:677 ../../utils/net_groupmap.c:715
+#: ../../utils/net_groupmap.c:738 ../../utils/net_groupmap.c:762 ../../utils/net_groupmap.c:822 ../../utils/net_idmap.c:65 ../../utils/net_idmap.c:97 ../../utils/net_idmap.c:244 ../../utils/net_idmap.c:344 ../../utils/net_lookup.c:327
+#: ../../utils/net_lookup.c:351 ../../utils/net_lookup.c:384 ../../utils/net_rap.c:507 ../../utils/net_rap.c:537 ../../utils/net_registry.c:134 ../../utils/net_registry.c:189 ../../utils/net_registry.c:246 ../../utils/net_registry.c:291
+#: ../../utils/net_registry.c:341 ../../utils/net_registry.c:398 ../../utils/net_registry.c:443 ../../utils/net_rpc.c:280 ../../utils/net_rpc.c:413 ../../utils/net_rpc.c:443 ../../utils/net_rpc.c:561 ../../utils/net_rpc.c:629 ../../utils/net_rpc.c:999
+#: ../../utils/net_rpc.c:1049 ../../utils/net_rpc.c:1131 ../../utils/net_rpc.c:1186 ../../utils/net_rpc.c:1933 ../../utils/net_rpc.c:2136 ../../utils/net_rpc.c:2221 ../../utils/net_rpc.c:3072 ../../utils/net_rpc.c:3258 ../../utils/net_rpc.c:3593
+#: ../../utils/net_rpc.c:3720 ../../utils/net_rpc.c:3755 ../../utils/net_rpc.c:4560 ../../utils/net_rpc.c:4686 ../../utils/net_rpc.c:4750 ../../utils/net_rpc.c:4768 ../../utils/net_rpc.c:5071 ../../utils/net_rpc.c:5220 ../../utils/net_rpc.c:5279
+#: ../../utils/net_rpc.c:5394 ../../utils/net_rpc.c:5436 ../../utils/net_rpc.c:5554 ../../utils/net_rpc.c:5638 ../../utils/net_rpc.c:5803 ../../utils/net_rpc.c:5937 ../../utils/net_rpc.c:6096 ../../utils/net_rpc.c:6471 ../../utils/net_rpc.c:6520
+#: ../../utils/net_rpc.c:6558 ../../utils/net_rpc.c:6614 ../../utils/net_rpc.c:6646 ../../utils/net_rpc.c:6678 ../../utils/net_rpc.c:6710 ../../utils/net_rpc.c:6742 ../../utils/net_rpc.c:6846 ../../utils/net_rpc.c:6873 ../../utils/net_rpc.c:6900
+#: ../../utils/net_rpc.c:6926 ../../utils/net_rpc.c:6980 ../../utils/net_rpc_audit.c:370 ../../utils/net_rpc_audit.c:388 ../../utils/net_rpc_audit.c:406 ../../utils/net_rpc_audit.c:424 ../../utils/net_rpc_audit.c:442 ../../utils/net_rpc_registry.c:475
+#: ../../utils/net_rpc_registry.c:530 ../../utils/net_rpc_registry.c:646 ../../utils/net_rpc_registry.c:674 ../../utils/net_rpc_registry.c:748 ../../utils/net_rpc_registry.c:799 ../../utils/net_rpc_registry.c:831 ../../utils/net_rpc_registry.c:905
+#: ../../utils/net_rpc_registry.c:1094 ../../utils/net_rpc_registry.c:1140 ../../utils/net_rpc_registry.c:1213 ../../utils/net_rpc_rights.c:417 ../../utils/net_rpc_rights.c:455 ../../utils/net_rpc_rights.c:526 ../../utils/net_rpc_rights.c:583
+#: ../../utils/net_rpc_rights.c:601 ../../utils/net_rpc_rights.c:623 ../../utils/net_rpc_samsync.c:258 ../../utils/net_rpc_samsync.c:341 ../../utils/net_rpc_samsync.c:485 ../../utils/net_rpc_service.c:218 ../../utils/net_rpc_service.c:327
+#: ../../utils/net_rpc_service.c:459 ../../utils/net_rpc_service.c:505 ../../utils/net_rpc_service.c:551 ../../utils/net_rpc_service.c:597 ../../utils/net_rpc_service.c:677 ../../utils/net_rpc_service.c:756 ../../utils/net_rpc_service.c:827
+#: ../../utils/net_rpc_service.c:845 ../../utils/net_rpc_service.c:863 ../../utils/net_rpc_service.c:881 ../../utils/net_rpc_service.c:899 ../../utils/net_rpc_service.c:917 ../../utils/net_rpc_service.c:935 ../../utils/net_rpc_service.c:953
+#: ../../utils/net_rpc_sh_acct.c:158 ../../utils/net_rpc_sh_acct.c:229 ../../utils/net_rpc_shell.c:217 ../../utils/net_sam.c:40 ../../utils/net_sam.c:146 ../../utils/net_sam.c:239 ../../utils/net_sam.c:303 ../../utils/net_sam.c:468
+#: ../../utils/net_sam.c:535 ../../utils/net_sam.c:583 ../../utils/net_sam.c:642 ../../utils/net_sam.c:699 ../../utils/net_sam.c:738 ../../utils/net_sam.c:866 ../../utils/net_sam.c:930 ../../utils/net_sam.c:967 ../../utils/net_sam.c:1000
+#: ../../utils/net_sam.c:1043 ../../utils/net_sam.c:1080 ../../utils/net_sam.c:1124 ../../utils/net_sam.c:1178 ../../utils/net_sam.c:1267 ../../utils/net_sam.c:1341 ../../utils/net_sam.c:1416 ../../utils/net_sam.c:1551 ../../utils/net_sam.c:1593
+#: ../../utils/net_status.c:69 ../../utils/net_status.c:221 ../../utils/net_time.c:139 ../../utils/net_time.c:165 ../../utils/net_time.c:227
+#, c-format
+msgid "Usage:"
+msgstr "Verwendung:"
+
+#: ../../utils/net.c:346
+msgid "Could not fetch local SID\n"
+msgstr "Lokale SID nicht abrufbar\n"
+
+#: ../../utils/net.c:350
+#, c-format
+msgid "SID for local machine %s is: %s\n"
+msgstr "SID des lokalen Rechners %s ist: %s\n"
+
+#: ../../utils/net.c:354
+msgid "Could not fetch domain SID\n"
+msgstr "SID der Domäne nicht abrufbar\n"
+
+#: ../../utils/net.c:371
+#, c-format
+msgid "get_maxrid: Could not search %s\n"
+msgstr "get_maxrid: Konnte %s nicht suchen\n"
+
+#: ../../utils/net.c:410
+msgid "can't get current maximum rid\n"
+msgstr ""
+
+#: ../../utils/net.c:414
+#, c-format
+msgid "Currently used maximum rid: %d\n"
+msgstr ""
+
+#: ../../utils/net.c:425
+msgid "Run functions using RPC transport"
+msgstr "RPC Protokoll nutzen"
+
+#: ../../utils/net.c:426
+msgid " Use 'net help rpc' to get more extensive information about 'net rpc' commands."
+msgstr ""
+
+#: ../../utils/net.c:433
+msgid "Run functions using RAP transport"
+msgstr "RAP Protokoll nutzen"
+
+#: ../../utils/net.c:434
+msgid " Use 'net help rap' to get more extensive information about 'net rap' commands."
+msgstr ""
+
+#: ../../utils/net.c:441
+msgid "Run functions using ADS transport"
+msgstr "ADS Protokoll nutzen"
+
+#: ../../utils/net.c:442
+msgid " Use 'net help ads' to get more extensive information about 'net ads' commands."
+msgstr ""
+
+#: ../../utils/net.c:451
+msgid "Functions on remote opened files"
+msgstr "Freigegebene Dateien verwalten"
+
+#: ../../utils/net.c:452
+msgid " Use 'net help file' to get more information about 'net file' commands."
+msgstr ""
+
+#: ../../utils/net.c:459
+msgid "Functions on shares"
+msgstr "Freigaben verwalten"
+
+#: ../../utils/net.c:460
+msgid " Use 'net help share' to get more information about 'net share' commands."
+msgstr ""
+
+#: ../../utils/net.c:467
+msgid "Manage sessions"
+msgstr "Sitzungen verwalten"
+
+#: ../../utils/net.c:468
+msgid " Use 'net help session' to get more information about 'net session' commands."
+msgstr ""
+
+#: ../../utils/net.c:475 ../../utils/net_rap.c:1293
+msgid "List servers in workgroup"
+msgstr "Server der Arbeitsgruppe auflisten"
+
+#: ../../utils/net.c:476
+msgid " Use 'net help server' to get more information about 'net server' commands."
+msgstr ""
+
+#: ../../utils/net.c:483
+msgid "List domains/workgroups on network"
+msgstr "Domänen/Arbeitsgruppen im Netzwerk auflisten"
+
+#: ../../utils/net.c:484
+msgid " Use 'net help domain' to get more information about 'net domain' commands."
+msgstr ""
+
+#: ../../utils/net.c:491
+msgid "Modify printer queue"
+msgstr "Druckerwarteschlange ändern"
+
+#: ../../utils/net.c:492
+msgid " Use 'net help printq' to get more information about 'net printq' commands."
+msgstr ""
+
+#: ../../utils/net.c:499
+msgid "Manage users"
+msgstr "Benutzer verwalten"
+
+#: ../../utils/net.c:500
+msgid " Use 'net help user' to get more information about 'net user' commands."
+msgstr ""
+
+#: ../../utils/net.c:507
+msgid "Manage groups"
+msgstr "Gruppen verwalten"
+
+#: ../../utils/net.c:508
+msgid " Use 'net help group' to get more information about 'net group' commands."
+msgstr ""
+
+#: ../../utils/net.c:515
+msgid "Manage group mappings"
+msgstr "Gruppenzuweisungen verwalten"
+
+#: ../../utils/net.c:516
+msgid " Use 'net help groupmap' to get more information about 'net groupmap' commands."
+msgstr ""
+
+#: ../../utils/net.c:523
+msgid "Functions on the SAM database"
+msgstr "SAM Datenbank nutzen"
+
+#: ../../utils/net.c:524
+msgid " Use 'net help sam' to get more information about 'net sam' commands."
+msgstr ""
+
+#: ../../utils/net.c:531
+msgid "Validate username and password"
+msgstr "Benutzername und Passwort prüfen"
+
+#: ../../utils/net.c:532
+msgid " Use 'net help validate' to get more information about 'net validate' commands."
+msgstr ""
+
+#: ../../utils/net.c:539
+msgid "Modify group memberships"
+msgstr "Gruppenzugehörigkeiten verwalten"
+
+#: ../../utils/net.c:540
+msgid " Use 'net help groupmember' to get more information about 'net groupmember' commands."
+msgstr ""
+
+#: ../../utils/net.c:546
+msgid "Execute remote command on a remote OS/2 server"
+msgstr "Befehl auf einem entfernten OS/2 Server ausführen"
+
+#: ../../utils/net.c:547
+msgid " Use 'net help admin' to get more information about 'net admin' commands."
+msgstr ""
+
+#: ../../utils/net.c:553
+msgid "List/modify running services"
+msgstr "Zeige/Ändere laufende Dienste"
+
+#: ../../utils/net.c:554
+msgid " Use 'net help service' to get more information about 'net service' commands."
+msgstr ""
+
+#: ../../utils/net.c:561
+msgid "Change user password on target server"
+msgstr "Benutzerpasswort auf Zielserver ändern"
+
+#: ../../utils/net.c:562
+msgid " Use 'net help password' to get more information about 'net password' commands."
+msgstr ""
+
+#: ../../utils/net.c:568
+msgid "Change the trust password"
+msgstr "Passwort von Vertrauenskonto ändern"
+
+#: ../../utils/net.c:569
+msgid " Use 'net help changetrustpw' to get more information about 'net changetrustpw'."
+msgstr ""
+
+#: ../../utils/net.c:575
+msgid "Change the secret password"
+msgstr "Das geheime Passwort ändern"
+
+#: ../../utils/net.c:576
+msgid ""
+" net [options] changesecretpw\n"
+" Change the ADS domain member machine account password in secrets.tdb.\n"
+" Do NOT use this function unless you know what it does.\n"
+" Requires the -f flag to work."
+msgstr ""
+
+#: ../../utils/net.c:586
+msgid "Set the winbind auth user"
+msgstr "Winbind Authentfikationseinstellungen setzten"
+
+#: ../../utils/net.c:587
+#, c-format
+msgid ""
+" net -U user[%%password] [-W domain] setauthuser\n"
+" Set the auth user, password (and optionally domain\n"
+" Will prompt for password if not given.\n"
+" net setauthuser delete\n"
+" Delete the existing auth user settings."
+msgstr ""
+
+#: ../../utils/net.c:597
+msgid "Get the winbind auth user settings"
+msgstr "Winbind Authentfikationseinstellungen lesen"
+
+#: ../../utils/net.c:598
+msgid ""
+" net getauthuser\n"
+" Get the current winbind auth user settings."
+msgstr ""
+
+#: ../../utils/net.c:604
+msgid "Show/set time"
+msgstr "Zeigt/Setzt die Systemzeit"
+
+#: ../../utils/net.c:605
+msgid " Use 'net help time' to get more information about 'net time' commands."
+msgstr ""
+
+#: ../../utils/net.c:611
+msgid "Look up host names/IP addresses"
+msgstr "Hostname/IP-Adresse nachschlagen"
+
+#: ../../utils/net.c:612
+msgid " Use 'net help lookup' to get more information about 'net lookup' commands."
+msgstr ""
+
+#: ../../utils/net.c:618
+msgid "Join a domain/AD"
+msgstr "Einer Domäne/AD beitreten"
+
+#: ../../utils/net.c:619
+msgid " Use 'net help join' to get more information about 'net join'."
+msgstr ""
+
+#: ../../utils/net.c:625
+msgid "Join/unjoin (remote) machines to/from a domain/AD"
+msgstr "Domäne/AD betreten/verlassen"
+
+#: ../../utils/net.c:626
+msgid " Use 'net help dom' to get more information about 'net dom' commands."
+msgstr ""
+
+#: ../../utils/net.c:632
+msgid "Operate on the cache tdb file"
+msgstr "Cache tdb Datei nutzen"
+
+#: ../../utils/net.c:633
+msgid " Use 'net help cache' to get more information about 'net cache' commands."
+msgstr ""
+
+#: ../../utils/net.c:639
+msgid "Get the SID for the local domain"
+msgstr "SID der lokalen Domäne anzeigen"
+
+#: ../../utils/net.c:640
+msgid " net getlocalsid"
+msgstr ""
+
+#: ../../utils/net.c:645
+msgid "Set the SID for the local domain"
+msgstr "SID der lokalen Domäne ändern"
+
+#: ../../utils/net.c:646
+msgid " net setlocalsid S-1-5-21-x-y-z"
+msgstr ""
+
+#: ../../utils/net.c:651
+msgid "Set domain SID on member servers"
+msgstr "SID der Domäne ändern (wenn zugehörig)"
+
+#: ../../utils/net.c:652
+msgid " net setdomainsid S-1-5-21-x-y-z"
+msgstr ""
+
+#: ../../utils/net.c:657
+msgid "Get domain SID on member servers"
+msgstr "SID der Domäne anzeigen (wenn zugehörig)"
+
+#: ../../utils/net.c:658
+msgid " net getdomainsid"
+msgstr ""
+
+#: ../../utils/net.c:663
+msgid "Display the maximum RID currently used"
+msgstr "Die höchste verwendete RID anzeigen"
+
+#: ../../utils/net.c:664
+msgid " net maxrid"
+msgstr ""
+
+#: ../../utils/net.c:669
+msgid "IDmap functions"
+msgstr "IDmap nutzen"
+
+#: ../../utils/net.c:670
+msgid " Use 'net help idmap to get more information about 'net idmap' commands."
+msgstr ""
+
+#: ../../utils/net.c:676
+msgid "Display server status"
+msgstr "Zeigt den Server Status"
+
+#: ../../utils/net.c:677
+msgid " Use 'net help status' to get more information about 'net status' commands."
+msgstr ""
+
+#: ../../utils/net.c:683
+msgid "Manage user-modifiable shares"
+msgstr "Benutzerfreigaben verwalten"
+
+#: ../../utils/net.c:684
+msgid " Use 'net help usershare to get more information about 'net usershare' commands."
+msgstr ""
+
+#: ../../utils/net.c:690
+msgid "Display list of all users with SID"
+msgstr "Zeigt eine Liste aller SID-Benutzer"
+
+#: ../../utils/net.c:691
+msgid " Use 'net help usersidlist' to get more information about 'net usersidlist'."
+msgstr ""
+
+#: ../../utils/net.c:697
+msgid "Manage Samba registry based configuration"
+msgstr "Konfiguration ändern"
+
+#: ../../utils/net.c:698
+msgid " Use 'net help conf' to get more information about 'net conf' commands."
+msgstr ""
+
+#: ../../utils/net.c:704
+msgid "Manage the Samba registry"
+msgstr "Die Sambaregistrierung verwalten"
+
+#: ../../utils/net.c:705
+msgid " Use 'net help registry' to get more information about 'net registry' commands."
+msgstr ""
+
+#: ../../utils/net.c:711
+msgid "Process Win32 *.evt eventlog files"
+msgstr "Arbeitet mit Win32 *.evt Eventlog Dateien"
+
+#: ../../utils/net.c:712
+msgid " Use 'net help eventlog' to get more information about 'net eventlog' commands."
+msgstr ""
+
+#: ../../utils/net.c:720
+msgid "Manage AFS tokens"
+msgstr "AFS Tickets verwalten"
+
+#: ../../utils/net.c:721
+msgid " Use 'net help afs' to get more information about 'net afs' commands."
+msgstr ""
+
+#: ../../utils/net.c:729
+msgid "Print usage information"
+msgstr "Zeigt die Hilfe an"
+
+#: ../../utils/net.c:730
+msgid " Use 'net help help' to list usage information for 'net' commands."
+msgstr ""
+
+#: ../../utils/net.c:759
+msgid "Encrypt SMB transport"
+msgstr "SMB Übertragung verschlüsseln"
+
+#: ../../utils/net.c:827
+msgid ""
+"\n"
+"Invalid ip address specified\n"
+msgstr ""
+
+#: ../../utils/net.c:842
+#, c-format
+msgid ""
+"\n"
+"Invalid option %s: %s\n"
+msgstr ""
+"\n"
+"Ungültige Option %s: %s\n"
+
+#: ../../utils/net_ads.c:53 ../../utils/net_ads.c:399
+msgid "CLDAP query failed!\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:57
+#, c-format
+msgid ""
+"Information for Domain Controller: %s\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:60
+msgid "Response Type: "
+msgstr ""
+
+#: ../../utils/net_ads.c:73
+#, c-format
+msgid "GUID: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:75
+#, c-format
+msgid ""
+"Flags:\n"
+"\tIs a PDC: %s\n"
+"\tIs a GC of the forest: %s\n"
+"\tIs an LDAP server: %s\n"
+"\tSupports DS: %s\n"
+"\tIs running a KDC: %s\n"
+"\tIs running time services: %s\n"
+"\tIs the closest DC: %s\n"
+"\tIs writable: %s\n"
+"\tHas a hardware clock: %s\n"
+"\tIs a non-domain NC serviced by LDAP server: %s\n"
+"\tIs NT6 DC that has some secrets: %s\n"
+"\tIs NT6 DC that has all secrets: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:88 ../../utils/net_ads.c:89 ../../utils/net_ads.c:90 ../../utils/net_ads.c:91 ../../utils/net_ads.c:92 ../../utils/net_ads.c:93 ../../utils/net_ads.c:94 ../../utils/net_ads.c:95 ../../utils/net_ads.c:96 ../../utils/net_ads.c:97
+#: ../../utils/net_ads.c:98 ../../utils/net_ads.c:99 ../../utils/net_rap.c:376 ../../utils/net_rpc_sh_acct.c:204 ../../utils/net_rpc_sh_acct.c:207
+msgid "yes"
+msgstr "Ja"
+
+#: ../../utils/net_ads.c:88 ../../utils/net_ads.c:89 ../../utils/net_ads.c:90 ../../utils/net_ads.c:91 ../../utils/net_ads.c:92 ../../utils/net_ads.c:93 ../../utils/net_ads.c:94 ../../utils/net_ads.c:95 ../../utils/net_ads.c:96 ../../utils/net_ads.c:97
+#: ../../utils/net_ads.c:98 ../../utils/net_ads.c:99 ../../utils/net_rap.c:376 ../../utils/net_rpc_sh_acct.c:204 ../../utils/net_rpc_sh_acct.c:207
+msgid "no"
+msgstr "Nein"
+
+#: ../../utils/net_ads.c:102
+#, c-format
+msgid "Forest:\t\t\t%s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:103
+#, c-format
+msgid "Domain:\t\t\t%s\n"
+msgstr "Domäne:\t\t\t%s\n"
+
+#: ../../utils/net_ads.c:104
+#, c-format
+msgid "Domain Controller:\t%s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:106
+#, c-format
+msgid "Pre-Win2k Domain:\t%s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:107
+#, c-format
+msgid "Pre-Win2k Hostname:\t%s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:109
+#, c-format
+msgid "User name:\t%s\n"
+msgstr "Benutzername:\t%s\n"
+
+#: ../../utils/net_ads.c:111
+#, c-format
+msgid "Server Site Name :\t\t%s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:112
+#, c-format
+msgid "Client Site Name :\t\t%s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:114
+#, c-format
+msgid "NT Version: %d\n"
+msgstr "NT Version: %d\n"
+
+#: ../../utils/net_ads.c:115
+#, c-format
+msgid "LMNT Token: %.2x\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:116
+#, c-format
+msgid "LM20 Token: %.2x\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:135
+msgid "Find the ADS DC using CLDAP lookup.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:140 ../../utils/net_ads.c:388
+msgid "Didn't find the cldap server!\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:167
+msgid "Display information about an Active Directory server.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:173 ../../utils/net_ads.c:178
+msgid "Didn't find the ldap server!\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:187
+msgid "Failed to get server's current time!\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:192
+#, c-format
+msgid "LDAP server: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:193
+#, c-format
+msgid "LDAP server name: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:194
+#, c-format
+msgid "Realm: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:195
+#, c-format
+msgid "Bind Path: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:196
+#, c-format
+msgid "LDAP port: %d\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:197
+#, c-format
+msgid "Server time: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:200
+#, c-format
+msgid "KDC server: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:201
+#, c-format
+msgid "Server time offset: %d\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:383
+msgid "Print the workgroup name"
+msgstr ""
+
+#: ../../utils/net_ads.c:404
+#, c-format
+msgid "Workgroup: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:465
+#, c-format
+msgid "ads_user_add: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:470
+#, c-format
+msgid "ads_user_add: User %s already exists\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:484
+#, c-format
+msgid "Could not add user %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:491 ../../utils/net_ads.c:504
+#, c-format
+msgid "User %s added\n"
+msgstr ""
+
+#. password didn't set, delete account
+#: ../../utils/net_ads.c:510
+#, c-format
+msgid "Could not add user %s. Error setting password %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:558
+#, c-format
+msgid "ads_user_info: failed to escape user %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:576
+#, c-format
+msgid "ads_search: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:582
+msgid "ads_pull_uint32 failed\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:589
+#, c-format
+msgid "ads_domain_sid: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:649
+#, c-format
+msgid "User %s does not exist.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:659
+#, c-format
+msgid "User %s deleted\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:663
+#, c-format
+msgid "Error deleting user %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:676
+msgid "Add an AD user"
+msgstr ""
+
+#: ../../utils/net_ads.c:677
+msgid ""
+"net ads user add\n"
+" Add an AD user"
+msgstr ""
+
+#: ../../utils/net_ads.c:684
+msgid "Display information about an AD user"
+msgstr ""
+
+#: ../../utils/net_ads.c:685
+msgid ""
+"net ads user info\n"
+" Display information about an AD user"
+msgstr ""
+
+#: ../../utils/net_ads.c:692
+msgid "Delete an AD user"
+msgstr ""
+
+#: ../../utils/net_ads.c:693
+msgid ""
+"net ads user delete\n"
+" Delete an AD user"
+msgstr ""
+
+#: ../../utils/net_ads.c:710
+msgid "List AD users"
+msgstr "AD Benutzer auflisten"
+
+#: ../../utils/net_ads.c:720 ../../utils/net_rap.c:903 ../../utils/net_rpc.c:888
+msgid ""
+"\n"
+"User name Comment\n"
+"-----------------------------\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:760
+#, c-format
+msgid "ads_group_add: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:765
+#, c-format
+msgid "ads_group_add: Group %s already exists\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:778
+#, c-format
+msgid "Group %s added\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:781
+#, c-format
+msgid "Could not add group %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:810
+#, c-format
+msgid "Group %s does not exist.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:820
+#, c-format
+msgid "Group %s deleted\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:824
+#, c-format
+msgid "Error deleting group %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:837
+msgid "Add an AD group"
+msgstr "AD Gruppe hinzufügen"
+
+#: ../../utils/net_ads.c:838
+msgid ""
+"net ads group add\n"
+" Add an AD group"
+msgstr ""
+"net ads group add\n"
+" AD Gruppe hinzufügen"
+
+#: ../../utils/net_ads.c:845
+msgid "Delete an AD group"
+msgstr "AD Gruppe entfernen"
+
+#: ../../utils/net_ads.c:846
+msgid ""
+"net ads group delete\n"
+" Delete an AD group"
+msgstr ""
+"net ads group delete\n"
+" AD Gruppe entfernen"
+
+#: ../../utils/net_ads.c:863
+msgid "List AD groups"
+msgstr "AD Gruppen auflisten"
+
+#: ../../utils/net_ads.c:873 ../../utils/net_rpc.c:2273
+msgid ""
+"\n"
+"Group name Comment\n"
+"-----------------------------\n"
+msgstr ""
+"\n"
+"Gruppenname Kommentar\n"
+"-----------------------------\n"
+
+#: ../../utils/net_ads.c:899 ../../utils/net_ads.c:2548
+msgid "Display machine account details"
+msgstr ""
+
+#: ../../utils/net_ads.c:909
+#, c-format
+msgid "ads_find_machine_acct: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:915
+#, c-format
+msgid "No machine account for '%s' found\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:943
+msgid "Leave an AD domain"
+msgstr ""
+
+#: ../../utils/net_ads.c:948
+msgid "No realm set, are we joined ?\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:953 ../../utils/net_ads.c:1281
+msgid "Could not initialise talloc context.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:963
+msgid "Could not initialise unjoin context.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:983
+#, c-format
+msgid "Failed to leave domain: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:990
+#, c-format
+msgid "Deleted account for '%s' in realm '%s'\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:997
+#, c-format
+msgid "Disabled account for '%s' in realm '%s'\n"
+msgstr ""
+
+#. Based on what we requested, we shouldn't get here, but if
+#. we did, it means the secrets were removed, and therefore
+#. we have left the domain
+#: ../../utils/net_ads.c:1006
+#, c-format
+msgid "Machine '%s' Left domain '%s'\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1058
+msgid "Test if the existing join is ok"
+msgstr ""
+
+#: ../../utils/net_ads.c:1065
+#, c-format
+msgid "Join to domain is not valid: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1070
+#, c-format
+msgid "Join is OK\n"
+msgstr "Mitgliedschaft ist OK\n"
+
+#: ../../utils/net_ads.c:1081
+msgid "Host is not configured as a member server.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1086 ../../utils/net_rpc.c:462
+#, c-format
+msgid "Our netbios name can be at most 15 chars long, \"%s\" is %u chars long\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1093
+#, c-format
+msgid "realm must be set in %s for ADS join to succeed.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1126
+#, c-format
+msgid "No DNS domain configured for %s. Unable to perform DNS Update.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1233
+msgid ""
+"net ads join [options]\n"
+"Valid options:\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1235
+msgid ""
+" createupn[=UPN] Set the userPrincipalName attribute during the join.\n"
+" The deault UPN is in the form host/netbiosname@REALM.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1237
+msgid ""
+" createcomputer=OU Precreate the computer account in a specific OU.\n"
+" The OU string read from top to bottom without RDNs and delimited by a '/'.\n"
+" E.g. \"createcomputer=Computers/Servers/Unix\"\n"
+" NB: A backslash '\\' is used as escape at multiple levels and may\n"
+" need to be doubled or even quadrupled. It is not used as a separator.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1242
+msgid " osName=string Set the operatingSystem attribute during the join.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1243
+msgid ""
+" osVer=string Set the operatingSystemVersion attribute during the join.\n"
+" NB: osName and osVer must be specified together for either to take effect.\n"
+" Also, the operatingSystemService attribute is also set when along with\n"
+" the two other attributes.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1275
+msgid "Invalid configuration. Exiting....\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1304
+msgid "Please supply a valid OU path.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1311
+msgid "Please supply a operating system name.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1318
+msgid "Please supply a valid operating system version.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1329
+msgid "Please supply a valid domain name\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1360
+#, c-format
+msgid ""
+"The workgroup in %s does not match the short\n"
+"domain name obtained from the server.\n"
+"Using the name [%s] from the server.\n"
+"You should set \"workgroup = %s\" in %s.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1368
+#, c-format
+msgid "Using short domain name -- %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1371
+#, c-format
+msgid "Joined '%s' to realm '%s'\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1374
+#, c-format
+msgid "Joined '%s' to domain '%s'\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1398 ../../utils/net_ads.c:1454
+msgid "DNS update failed!\n"
+msgstr ""
+
+#. issue an overall failure message at the end.
+#: ../../utils/net_ads.c:1412 ../../utils/net_dom.c:205
+#, c-format
+msgid "Failed to join domain: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1437
+msgid "Register hostname with DNS\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1442
+msgid "Could not initialise talloc context\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1460
+msgid "Successfully registered hostname with DNS\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1468
+msgid "DNS update support not enabled at compile time!\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1488
+msgid "net ads dns gethostbyname <server> <name>\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1489
+msgid ""
+" Look up hostname from the AD\n"
+" server\tName server to use\n"
+" name\tName to look up\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1497
+#, c-format
+msgid "do_gethostbyname returned %d\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1509
+msgid "Add host dns entry to AD"
+msgstr ""
+
+#: ../../utils/net_ads.c:1510
+msgid ""
+"net ads dns register\n"
+" Add host dns entry to AD"
+msgstr ""
+
+#: ../../utils/net_ads.c:1517
+msgid "Look up host"
+msgstr ""
+
+#: ../../utils/net_ads.c:1518
+msgid ""
+"net ads dns gethostbyname\n"
+" Look up host"
+msgstr ""
+
+#: ../../utils/net_ads.c:1533
+msgid ""
+"\n"
+"net ads printer search <printer>\n"
+"\tsearch for a printer in the directory\n"
+"\n"
+"net ads printer info <printer> <server>\n"
+"\tlookup info in directory for printer on server\n"
+"\t(note: printer defaults to \"*\", server defaults to local)\n"
+"\n"
+"net ads printer publish <printername>\n"
+"\tpublish printer in directory\n"
+"\t(note: printer name is required)\n"
+"\n"
+"net ads printer remove <printername>\n"
+"\tremove printer from directory\n"
+"\t(note: printer name is required)\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1561
+msgid "List printers in the AD"
+msgstr ""
+
+#: ../../utils/net_ads.c:1572
+#, c-format
+msgid "ads_find_printer: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1579
+msgid "No results found\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1601
+msgid ""
+"net ads printer info [printername [servername]]\n"
+" Display printer info from AD\n"
+" printername\tPrinter name or wildcard\n"
+" servername\tName of the print server\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1627
+#, c-format
+msgid "Server '%s' not found: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1635 ../../utils/net_ads.c:1820
+#, c-format
+msgid "Printer '%s' not found\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1666
+msgid ""
+"net ads printer publish <printername> [servername]\n"
+" Publish printer in AD\n"
+" printername\tName of the printer\n"
+" servername\tName of the print server\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1700
+#, c-format
+msgid "Unable to open a connnection to %s to obtain data for %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1713
+#, c-format
+msgid "Could not find machine account for server %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1729 ../../utils/net_ads.c:1738
+msgid "Internal error, out of memory!"
+msgstr ""
+
+#: ../../utils/net_ads.c:1749
+#, c-format
+msgid "Unable to open a connnection to the spoolss pipe on %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1793
+msgid ""
+"net ads printer remove <printername> [servername]\n"
+" Remove a printer from the AD\n"
+" printername\tName of the printer\n"
+" servername\tName of the print server\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1813
+#, c-format
+msgid "ads_find_printer_on_server: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1832
+#, c-format
+msgid "ads_del_dn: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1848
+msgid "Search for a printer"
+msgstr "Drucker suchen"
+
+#: ../../utils/net_ads.c:1849
+msgid ""
+"net ads printer search\n"
+" Search for a printer"
+msgstr ""
+
+#: ../../utils/net_ads.c:1856
+msgid "Display printer information"
+msgstr ""
+
+#: ../../utils/net_ads.c:1857
+msgid ""
+"net ads printer info\n"
+" Display printer information"
+msgstr ""
+
+#: ../../utils/net_ads.c:1864
+msgid "Publish a printer"
+msgstr "Drucker freigeben"
+
+#: ../../utils/net_ads.c:1865
+msgid ""
+"net ads printer publish\n"
+" Publish a printer"
+msgstr ""
+
+#: ../../utils/net_ads.c:1872
+msgid "Delete a printer"
+msgstr "Drucker löschen"
+
+#: ../../utils/net_ads.c:1873
+msgid ""
+"net ads printer remove\n"
+" Delete a printer"
+msgstr ""
+
+#: ../../utils/net_ads.c:1897
+msgid ""
+"net ads password <username>\n"
+" Change password for user\n"
+" username\tName of user to change password for\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1904
+msgid "You must supply an administrator username/password\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1910
+msgid "ERROR: You must say which username to change password for\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1942
+msgid "Didn't find the kerberos server!\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1950 ../../utils/net_rpc.c:792
+#, c-format
+msgid "Enter new password for %s:"
+msgstr "Bitte neues Passwort für %s eingeben: "
+
+#: ../../utils/net_ads.c:1960 ../../utils/net_ads.c:2011
+#, c-format
+msgid "Password change failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1965
+#, c-format
+msgid "Password change for %s completed.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:1983
+msgid "Change the machine account's trust password"
+msgstr "Passwort von Vertrauenskonto ändern"
+
+#: ../../utils/net_ads.c:2006
+#, c-format
+msgid "Changing password for principal: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2017
+#, c-format
+msgid "Password change for principal %s succeeded.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2020
+msgid "Attempting to update system keytab with new password.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2022
+msgid "Failed to update system keytab.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2038
+msgid ""
+"\n"
+"net ads search <expression> <attributes...>\n"
+"\n"
+"Perform a raw LDAP search on a ADS server and dump the results.\n"
+"The expression is a standard LDAP search expression, and the\n"
+"attributes are a list of LDAP fields to show in the results.\n"
+"\n"
+"Example: net ads search '(objectCategory=group)' sAMAccountName\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2075 ../../utils/net_ads.c:2136 ../../utils/net_ads.c:2200 ../../utils/net_ads_gpo.c:253
+#, c-format
+msgid "search failed: %s\n"
+msgstr "Suche fehlgeschlagen: %s\n"
+
+#: ../../utils/net_ads.c:2080 ../../utils/net_ads.c:2205 ../../utils/net_ads_gpo.c:259
+#, c-format
+msgid ""
+"Got %d replies\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2098
+msgid ""
+"\n"
+"net ads dn <dn> <attributes...>\n"
+"\n"
+"perform a raw LDAP search on a ADS server and dump the results\n"
+"The DN standard LDAP DN, and the attributes are a list of LDAP fields \n"
+"to show in the results\n"
+"\n"
+"Example: net ads dn 'CN=administrator,CN=Users,DC=my,DC=domain' sAMAccountName\n"
+"\n"
+"Note: the DN must be provided properly escaped. See RFC 4514 for details\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2158
+msgid ""
+"\n"
+"net ads sid <sid> <attributes...>\n"
+"\n"
+"perform a raw LDAP search on a ADS server and dump the results\n"
+"The SID is in string format, and the attributes are a list of LDAP fields \n"
+"to show in the results\n"
+"\n"
+"Example: net ads sid 'S-1-5-32' distinguishedName\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2193
+msgid "could not convert sid\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2226
+msgid "Delete the whole keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2247
+msgid ""
+"net ads keytab add <principal> [principal ...]\n"
+" Add principals to local keytab\n"
+" principal\tKerberos principal to add to keytab\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2254
+msgid "Processing principals to add...\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2275
+msgid "Create new default keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2294
+msgid ""
+"net ads keytab list [keytab]\n"
+" List a local keytab\n"
+" keytab\tKeytab to list\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2315
+msgid "Add a service principal"
+msgstr ""
+
+#: ../../utils/net_ads.c:2316
+msgid ""
+"net ads keytab add\n"
+" Add a service principal"
+msgstr ""
+
+#: ../../utils/net_ads.c:2323
+msgid "Create a fresh keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2324
+msgid ""
+"net ads keytab create\n"
+" Create a fresh keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2331
+msgid "Remove all keytab entries"
+msgstr ""
+
+#: ../../utils/net_ads.c:2332
+msgid ""
+"net ads keytab flush\n"
+" Remove all keytab entries"
+msgstr ""
+
+#: ../../utils/net_ads.c:2339
+msgid "List a keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2340
+msgid ""
+"net ads keytab list\n"
+" List a keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2347
+msgid ""
+"\n"
+"Warning: \"kerberos method\" must be set to a keytab method to use keytab functions.\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2363
+msgid "Renew TGT from existing credential cache"
+msgstr ""
+
+#: ../../utils/net_ads.c:2369
+#, c-format
+msgid "failed to renew kerberos ticket: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2389
+msgid "Dump the Kerberos PAC"
+msgstr ""
+
+#: ../../utils/net_ads.c:2417
+#, c-format
+msgid "failed to query kerberos PAC: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2426
+#, c-format
+msgid "The Pac: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2446
+msgid "Get Ticket Granting Ticket (TGT) for the user"
+msgstr ""
+
+#: ../../utils/net_ads.c:2468
+#, c-format
+msgid "failed to kinit password: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads.c:2482
+msgid "Retrieve Ticket Granting Ticket (TGT)"
+msgstr ""
+
+#: ../../utils/net_ads.c:2483
+msgid ""
+"net ads kerberos kinit\n"
+" Receive Ticket Granting Ticket (TGT)"
+msgstr ""
+
+#: ../../utils/net_ads.c:2490
+msgid "Renew Ticket Granting Ticket from credential cache"
+msgstr ""
+
+#: ../../utils/net_ads.c:2491
+msgid ""
+"net ads kerberos renew\n"
+" Renew Ticket Granting Ticket (TGT) from credential cache"
+msgstr ""
+
+#: ../../utils/net_ads.c:2499
+msgid "Dump Kerberos PAC"
+msgstr ""
+
+#: ../../utils/net_ads.c:2500
+msgid ""
+"net ads kerberos pac\n"
+" Dump Kerberos PAC"
+msgstr ""
+
+#: ../../utils/net_ads.c:2516
+msgid "Display details on remote ADS server"
+msgstr ""
+
+#: ../../utils/net_ads.c:2517
+msgid ""
+"net ads info\n"
+" Display details on remote ADS server"
+msgstr ""
+
+#: ../../utils/net_ads.c:2524
+msgid "Join the local machine to ADS realm"
+msgstr ""
+
+#: ../../utils/net_ads.c:2525
+msgid ""
+"net ads join\n"
+" Join the local machine to ADS realm"
+msgstr ""
+
+#: ../../utils/net_ads.c:2532
+msgid "Validate machine account"
+msgstr ""
+
+#: ../../utils/net_ads.c:2533
+msgid ""
+"net ads testjoin\n"
+" Validate machine account"
+msgstr ""
+
+#: ../../utils/net_ads.c:2540
+msgid "Remove the local machine from ADS"
+msgstr ""
+
+#: ../../utils/net_ads.c:2541
+msgid ""
+"net ads leave\n"
+" Remove the local machine from ADS"
+msgstr ""
+
+#: ../../utils/net_ads.c:2549
+msgid ""
+"net ads status\n"
+" Display machine account details"
+msgstr ""
+
+#: ../../utils/net_ads.c:2556 ../../utils/net_rpc.c:7220
+msgid "List/modify users"
+msgstr ""
+
+#: ../../utils/net_ads.c:2557
+msgid ""
+"net ads user\n"
+" List/modify users"
+msgstr ""
+
+#: ../../utils/net_ads.c:2564 ../../utils/net_rpc.c:7237
+msgid "List/modify groups"
+msgstr ""
+
+#: ../../utils/net_ads.c:2565
+msgid ""
+"net ads group\n"
+" List/modify groups"
+msgstr ""
+
+#: ../../utils/net_ads.c:2572
+msgid "Issue dynamic DNS update"
+msgstr ""
+
+#: ../../utils/net_ads.c:2573
+msgid ""
+"net ads dns\n"
+" Issue dynamic DNS update"
+msgstr ""
+
+#: ../../utils/net_ads.c:2580
+msgid "Change user passwords"
+msgstr ""
+
+#: ../../utils/net_ads.c:2581
+msgid ""
+"net ads password\n"
+" Change user passwords"
+msgstr ""
+
+#: ../../utils/net_ads.c:2588 ../../utils/net_rpc.c:7269
+msgid "Change trust account password"
+msgstr "Trust account Passwort ändern"
+
+#: ../../utils/net_ads.c:2589
+msgid ""
+"net ads changetrustpw\n"
+" Change trust account password"
+msgstr ""
+
+#: ../../utils/net_ads.c:2596
+msgid "List/modify printer entries"
+msgstr ""
+
+#: ../../utils/net_ads.c:2597
+msgid ""
+"net ads printer\n"
+" List/modify printer entries"
+msgstr ""
+
+#: ../../utils/net_ads.c:2604
+msgid "Issue LDAP search using filter"
+msgstr ""
+
+#: ../../utils/net_ads.c:2605
+msgid ""
+"net ads search\n"
+" Issue LDAP search using filter"
+msgstr ""
+
+#: ../../utils/net_ads.c:2612
+msgid "Issue LDAP search by DN"
+msgstr ""
+
+#: ../../utils/net_ads.c:2613
+msgid ""
+"net ads dn\n"
+" Issue LDAP search by DN"
+msgstr ""
+
+#: ../../utils/net_ads.c:2620
+msgid "Issue LDAP search by SID"
+msgstr ""
+
+#: ../../utils/net_ads.c:2621
+msgid ""
+"net ads sid\n"
+" Issue LDAP search by SID"
+msgstr ""
+
+#: ../../utils/net_ads.c:2628
+msgid "Display workgroup name"
+msgstr ""
+
+#: ../../utils/net_ads.c:2629
+msgid ""
+"net ads workgroup\n"
+" Display the workgroup name"
+msgstr ""
+
+#: ../../utils/net_ads.c:2636
+msgid "Perform CLDAP query on DC"
+msgstr ""
+
+#: ../../utils/net_ads.c:2637
+msgid ""
+"net ads lookup\n"
+" Find the ADS DC using CLDAP lookups"
+msgstr ""
+
+#: ../../utils/net_ads.c:2644
+msgid "Manage local keytab file"
+msgstr ""
+
+#: ../../utils/net_ads.c:2645
+msgid ""
+"net ads keytab\n"
+" Manage local keytab file"
+msgstr ""
+
+#: ../../utils/net_ads.c:2652
+msgid "Manage group policy objects"
+msgstr ""
+
+#: ../../utils/net_ads.c:2653
+msgid ""
+"net ads gpo\n"
+" Manage group policy objects"
+msgstr ""
+
+#: ../../utils/net_ads.c:2660
+msgid "Manage kerberos keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2661
+msgid ""
+"net ads kerberos\n"
+" Manage kerberos keytab"
+msgstr ""
+
+#: ../../utils/net_ads.c:2674
+msgid "ADS support not compiled in\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:42
+msgid "net ads gpo refresh <username|machinename>"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:43
+msgid ""
+" Lists all GPOs assigned to an account and downloads them\n"
+" username\tUser to refresh GPOs for\n"
+" machinename\tMachine to refresh GPOs for\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:57
+#, c-format
+msgid "failed to connect AD server: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:63
+#, c-format
+msgid "failed to find samaccount for %s\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:71
+#, c-format
+msgid ""
+"\n"
+"%s: '%s' has dn: '%s'\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:72 ../../utils/net_ads_gpo.c:332
+msgid "machine"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:72 ../../utils/net_ads_gpo.c:332
+msgid "user"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:75
+msgid "* fetching token "
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:83 ../../utils/net_ads_gpo.c:91 ../../utils/net_ads_gpo.c:103 ../../utils/net_ads_gpo.c:114 ../../utils/net_ads_gpo.c:159
+#, c-format
+msgid "failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:86 ../../utils/net_ads_gpo.c:95 ../../utils/net_ads_gpo.c:106 ../../utils/net_ads_gpo.c:119 ../../utils/net_ads_gpo.c:164
+msgid "finished\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:88
+msgid "* fetching GPO List "
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:97
+msgid "* Refreshing Group Policy Data "
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:108
+msgid "* storing GPO list to registry "
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:123
+msgid "* dumping GPO list\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:152
+msgid "* re-reading GPO list from registry "
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:168
+msgid "* dumping GPO list from registry\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:231
+msgid "List all GPOs on the DC"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:273
+#, c-format
+msgid "ads_parse_gpo failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:305
+msgid "net ads gpo list <username|machinename>"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:306
+msgid ""
+" Lists all GPOs for machine/user\n"
+" username\tUser to list GPOs for\n"
+" machinename\tMachine to list GPOs for\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:331
+#, c-format
+msgid "%s: '%s' has dn: '%s'\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:457
+msgid "net ads gpo linkget <container>"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:458
+msgid ""
+" Lists gPLink of a container\n"
+" container\tContainer to get link for\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:475
+#, c-format
+msgid "get link for %s failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:499
+msgid "net ads gpo linkadd <linkdn> <gpodn> [options]"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:500
+msgid ""
+" Link a container to a GPO\n"
+" linkdn\tContainer to link to a GPO\n"
+" gpodn\tGPO to link container to\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:503
+msgid ""
+"note: DNs must be provided properly escaped.\n"
+"See RFC 4514 for details\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:524
+#, c-format
+msgid "link add failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:587
+msgid "net ads gpo getgpo <gpo>"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:588
+msgid ""
+" List speciefied GPO\n"
+" gpo\t\tGPO to list\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:610
+#, c-format
+msgid "get gpo for [%s] failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:641
+msgid "List specified GPO"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:642
+msgid ""
+"net ads gpo getgpo\n"
+" List specified GPO"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:649
+msgid "Link a container to a GPO"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:650
+msgid ""
+"net ads gpo linkadd\n"
+" Link a container to a GPO"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:667
+msgid "Lists gPLink of container"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:668
+msgid ""
+"net ads gpo linkget\n"
+" Lists gPLink of container"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:675
+msgid "Lists all GPOs for machine/user"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:676
+msgid ""
+"net ads gpo list\n"
+" Lists all GPOs for machine/user"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:683
+msgid "Lists all GPOs on a DC"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:684
+msgid ""
+"net ads gpo listall\n"
+" Lists all GPOs on a DC"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:691
+msgid "Lists all GPOs assigned to an account and downloads them"
+msgstr ""
+
+#: ../../utils/net_ads_gpo.c:693
+msgid ""
+"net ads gpo refresh\n"
+" Lists all GPOs assigned to an account and downloads them"
+msgstr ""
+
+#: ../../utils/net_afs.c:25
+msgid ""
+" net afs key filename\n"
+"\tImports a OpenAFS KeyFile into our secrets.tdb\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:27
+msgid ""
+" net afs impersonate <user> <cell>\n"
+"\tCreates a token for user@cell\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:43
+msgid "Could not open secrets.tdb\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:48
+#, c-format
+msgid "Could not open %s\n"
+msgstr "Konnte %s nicht öffnen\n"
+
+#: ../../utils/net_afs.c:53
+msgid "Could not read keyfile\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:58
+msgid "Could not write keyfile to secrets.tdb\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:78
+#, c-format
+msgid "Could not create token\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:83
+#, c-format
+msgid "Could not set token into kernel\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:87
+#, c-format
+msgid "Success: %s@%s\n"
+msgstr ""
+
+#: ../../utils/net_afs.c:98
+msgid "Import an OpenAFS keyfile"
+msgstr ""
+
+#: ../../utils/net_afs.c:99
+msgid ""
+"net afs key <filename>\n"
+" Import kefile from <filename>."
+msgstr ""
+
+#: ../../utils/net_afs.c:106
+msgid "Get a user token"
+msgstr ""
+
+#: ../../utils/net_afs.c:107
+msgid ""
+"net afs impersonate <user> <cell>\n"
+" Create token for user@cell"
+msgstr ""
+
+#: ../../utils/net_cache.c:74
+#, c-format
+msgid "Key: %s\t Timeout: %s\t Value: %s %s\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:75
+msgid "(expired)"
+msgstr ""
+
+#: ../../utils/net_cache.c:84
+#, c-format
+msgid "Couldn't delete entry! key = %s\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:160
+msgid "net cache add <key string> <data string> <timeout>\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:172
+msgid "Invalid timeout argument.\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:177
+msgid "New cache entry stored successfully.\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:181
+msgid "Entry couldn't be added. Perhaps there's already such a key.\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:199
+msgid " net cache del <key string>\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:204
+msgid "Entry deleted.\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:208
+msgid "Couldn't delete specified entry\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:229
+msgid " net cache get <key>\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:239
+msgid "Failed to find entry\n"
+msgstr ""
+
+#: ../../utils/net_cache.c:258
+msgid " net cache search <pattern>\n"
+msgstr " net cache search <Muster>\n"
+
+#: ../../utils/net_cache.c:284
+msgid "List all cache entries."
+msgstr ""
+
+#: ../../utils/net_cache.c:307
+msgid "Delete all cache entries."
+msgstr ""
+
+#: ../../utils/net_cache.c:322 ../../utils/net_cache.c:400
+msgid "Move transient cache content to stable storage"
+msgstr ""
+
+#: ../../utils/net_cache.c:345
+msgid "Add new cache entry"
+msgstr ""
+
+#: ../../utils/net_cache.c:346
+msgid ""
+"net cache add <key string> <data string> <timeout>\n"
+" Add new cache entry.\n"
+" key string\tKey string to add cache data under.\n"
+" data string\tData to store under given key.\n"
+" timeout\tTimeout for cache data."
+msgstr ""
+
+#: ../../utils/net_cache.c:356
+msgid "Delete existing cache entry by key"
+msgstr ""
+
+#: ../../utils/net_cache.c:357
+msgid ""
+"net cache del <key string>\n"
+" Delete existing cache entry by key.\n"
+" key string\tKey string to delete."
+msgstr ""
+
+#: ../../utils/net_cache.c:365
+msgid "Get cache entry by key"
+msgstr ""
+
+#: ../../utils/net_cache.c:366
+msgid ""
+"net cache get <key string>\n"
+" Get cache entry by key.\n"
+" key string\tKey string to look up cache entry for."
+msgstr ""
+
+#: ../../utils/net_cache.c:375
+msgid "Search entry by pattern"
+msgstr ""
+
+#: ../../utils/net_cache.c:376
+msgid ""
+"net cache search <pattern>\n"
+" Search entry by pattern.\n"
+" pattern\tPattern to search for in cache."
+msgstr ""
+
+#: ../../utils/net_cache.c:384
+msgid "List all cache entries"
+msgstr ""
+
+#: ../../utils/net_cache.c:385
+msgid ""
+"net cache list\n"
+" List all cache entries"
+msgstr ""
+
+#: ../../utils/net_cache.c:392
+msgid "Delete all cache entries"
+msgstr ""
+
+#: ../../utils/net_cache.c:393
+msgid ""
+"net cache flush\n"
+" Delete all cache entries"
+msgstr ""
+
+#: ../../utils/net_cache.c:401
+msgid ""
+"net cache stabilize\n"
+" Move transient cache content to stable storage"
+msgstr ""
+
+#: ../../utils/net_conf.c:51
+msgid ""
+" net conf import [--test|-T] <filename> [<servicename>]\n"
+"\t[--test|-T] testmode - do not act, just print what would be done\n"
+"\t<servicename> only import service <servicename>, ignore the rest\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:79
+msgid "net conf showshare <sharename>\n"
+msgstr "net conf showshare <sharename>\n"
+
+#: ../../utils/net_conf.c:88
+msgid ""
+" net conf addshare <sharename> <path> [writeable={y|N} [guest_ok={y|N} [<comment>]]\n"
+"\t<sharename> the new share name.\n"
+"\t<path> the path on the filesystem to export.\n"
+"\twriteable={y|N} set \"writeable to \"yes\" or \"no\" (default) on this share.\n"
+"\tguest_ok={y|N} set \"guest ok\" to \"yes\" or \"no\" (default) on this share.\n"
+"\t<comment> optional comment for the new share.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:105
+msgid "net conf delshare <sharename>\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:114
+msgid " net conf setparm <section> <param> <value>\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:123
+msgid " net conf getparm <section> <param>\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:132
+msgid " net conf delparm <section> <param>\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:141
+msgid " net conf getincludes <section>\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:150
+msgid " net conf setincludes <section> [<filename>]*\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:159
+msgid " net conf delincludes <section>\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:271
+#, c-format
+msgid "Error getting config: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:325 ../../utils/net_conf.c:338 ../../utils/net_conf.c:634 ../../utils/net_conf.c:762 ../../utils/net_conf.c:800 ../../utils/net_conf.c:806 ../../utils/net_conf.c:880 ../../utils/net_conf.c:886 ../../utils/net_conf.c:936
+#: ../../utils/net_conf.c:990 ../../utils/net_conf.c:1030 ../../utils/net_conf.c:1070
+msgid "error: out of memory!\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:344
+#, c-format
+msgid "error loading file '%s': %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:350
+msgid ""
+"\n"
+"TEST MODE - would import the following configuration:\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:366 ../../utils/net_conf.c:402 ../../utils/net_conf.c:427 ../../utils/net_conf.c:813
+#, c-format
+msgid "error starting transaction: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:420 ../../utils/net_conf.c:436 ../../utils/net_conf.c:837
+#, c-format
+msgid "error committing transaction: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:447 ../../utils/net_conf.c:848
+#, c-format
+msgid "error cancelling transaction: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:504
+#, c-format
+msgid "Error deleting configuration: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:541
+#, c-format
+msgid "error getting share parameters: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:650
+#, c-format
+msgid "ERROR: share name %s contains invalid characters (any of %s)\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:658
+msgid "ERROR: 'global' is not a valid share name.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:663
+#, c-format
+msgid "ERROR: share %s already exists.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:672
+#, c-format
+msgid "Error: path '%s' is not an absolute path.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:679
+#, c-format
+msgid ""
+"ERROR: cannot stat path '%s' to ensure this is a directory.\n"
+"Error was '%s'.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:688
+#, c-format
+msgid "ERROR: path '%s' is not a directory.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:699
+#, c-format
+msgid "Error creating share %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:710 ../../utils/net_conf.c:719 ../../utils/net_conf.c:727 ../../utils/net_conf.c:735
+#, c-format
+msgid "Error setting parameter %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:768
+#, c-format
+msgid "Error deleting share %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:821
+#, c-format
+msgid "Error creating share '%s': %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:830
+#, c-format
+msgid "Error setting value '%s': %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:894 ../../utils/net_conf.c:950
+#, c-format
+msgid "Error: given service '%s' does not exist.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:899 ../../utils/net_conf.c:955
+#, c-format
+msgid "Error: given parameter '%s' is not set.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:903
+#, c-format
+msgid "Error getting value '%s': %s.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:959
+#, c-format
+msgid "Error deleting value '%s': %s.\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:997
+#, c-format
+msgid "error getting includes: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:1043
+#, c-format
+msgid "error setting includes: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:1076
+#, c-format
+msgid "error deleting includes: %s\n"
+msgstr ""
+
+#: ../../utils/net_conf.c:1180
+msgid "Dump the complete configuration in smb.conf like format."
+msgstr ""
+
+#: ../../utils/net_conf.c:1182
+msgid ""
+"net conf list\n"
+" Dump the complete configuration in smb.conf like format."
+msgstr ""
+
+#: ../../utils/net_conf.c:1191
+msgid "Import configuration from file in smb.conf format."
+msgstr ""
+
+#: ../../utils/net_conf.c:1193
+msgid ""
+"net conf import\n"
+" Import configuration from file in smb.conf format."
+msgstr ""
+
+#: ../../utils/net_conf.c:1201
+msgid "List the share names."
+msgstr ""
+
+#: ../../utils/net_conf.c:1202
+msgid ""
+"net conf listshares\n"
+" List the share names."
+msgstr ""
+
+#: ../../utils/net_conf.c:1209
+msgid "Delete the complete configuration."
+msgstr ""
+
+#: ../../utils/net_conf.c:1210
+msgid ""
+"net conf drop\n"
+" Delete the complete configuration."
+msgstr ""
+
+#: ../../utils/net_conf.c:1217
+msgid "Show the definition of a share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1218
+msgid ""
+"net conf showshare\n"
+" Show the definition of a share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1225
+msgid "Create a new share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1226
+msgid ""
+"net conf addshare\n"
+" Create a new share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1233
+msgid "Delete a share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1234
+msgid ""
+"net conf delshare\n"
+" Delete a share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1241
+msgid "Store a parameter."
+msgstr ""
+
+#: ../../utils/net_conf.c:1242
+msgid ""
+"net conf setparm\n"
+" Store a parameter."
+msgstr ""
+
+#: ../../utils/net_conf.c:1249
+msgid "Retrieve the value of a parameter."
+msgstr ""
+
+#: ../../utils/net_conf.c:1250
+msgid ""
+"net conf getparm\n"
+" Retrieve the value of a parameter."
+msgstr ""
+
+#: ../../utils/net_conf.c:1257
+msgid "Delete a parameter."
+msgstr ""
+
+#: ../../utils/net_conf.c:1258
+msgid ""
+"net conf delparm\n"
+" Delete a parameter."
+msgstr ""
+
+#: ../../utils/net_conf.c:1265
+msgid "Show the includes of a share definition."
+msgstr ""
+
+#: ../../utils/net_conf.c:1266
+msgid ""
+"net conf getincludes\n"
+" Show the includes of a share definition."
+msgstr ""
+
+#: ../../utils/net_conf.c:1273
+msgid "Set includes for a share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1274
+msgid ""
+"net conf setincludes\n"
+" Set includes for a share."
+msgstr ""
+
+#: ../../utils/net_conf.c:1281
+msgid "Delete includes from a share definition."
+msgstr ""
+
+#: ../../utils/net_conf.c:1282
+msgid ""
+"net conf setincludes\n"
+" Delete includes from a share definition."
+msgstr ""
+
+#: ../../utils/net_dom.c:28
+msgid ""
+"net dom join <domain=DOMAIN> <ou=OU> <account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+" Join a remote machine\n"
+msgstr ""
+
+#: ../../utils/net_dom.c:33
+msgid ""
+"net dom unjoin <account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+" Unjoin a remote machine\n"
+msgstr ""
+
+#: ../../utils/net_dom.c:38
+msgid ""
+"net dom renamecomputer <newname=NEWNAME> <account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+" Rename joined computer\n"
+msgstr ""
+
+#: ../../utils/net_dom.c:98
+#, c-format
+msgid "Failed to unjoin domain: %s\n"
+msgstr ""
+
+#: ../../utils/net_dom.c:104 ../../utils/net_dom.c:211
+msgid "Shutting down due to a domain membership change"
+msgstr ""
+
+#: ../../utils/net_dom.c:297
+#, c-format
+msgid "Failed to rename machine: "
+msgstr ""
+
+#: ../../utils/net_dom.c:299
+#, c-format
+msgid "Computer is not joined to a Domain\n"
+msgstr ""
+
+#: ../../utils/net_dom.c:308
+msgid "Shutting down due to a computer rename"
+msgstr ""
+
+#: ../../utils/net_dom.c:345
+msgid "Join a remote machine"
+msgstr ""
+
+#: ../../utils/net_dom.c:346
+msgid ""
+"net dom join <domain=DOMAIN> <ou=OU> <account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+" Join a remote machine"
+msgstr ""
+
+#: ../../utils/net_dom.c:354
+msgid "Unjoin a remote machine"
+msgstr ""
+
+#: ../../utils/net_dom.c:355
+msgid ""
+"net dom unjoin <account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+" Unjoin a remote machine"
+msgstr ""
+
+#: ../../utils/net_dom.c:363
+msgid "Rename a computer that is joined to a domain"
+msgstr ""
+
+#: ../../utils/net_dom.c:364
+msgid ""
+"net dom renamecomputer <newname=NEWNAME> <account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+" Rename joined computer"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:53 ../../utils/net_eventlog.c:110
+#, c-format
+msgid "failed to load evt file: %s\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:60 ../../utils/net_eventlog.c:131
+#, c-format
+msgid "evt pull failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:118
+#, c-format
+msgid "evt header pull failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:124
+msgid "input file is wrapped, cannot proceed\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:140 ../../utils/net_eventlog.c:206
+#, c-format
+msgid "can't open the eventlog TDB (%s)\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:160
+#, c-format
+msgid "can't write to the eventlog: %s\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:166
+#, c-format
+msgid "wrote %d entries to tdb\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:217
+#, c-format
+msgid "failed to save evt file: %s\n"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:246
+msgid "Dump eventlog"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:247
+msgid ""
+"net eventlog dump\n"
+" Dump win32 *.evt eventlog file"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:254
+msgid "Import eventlog"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:255
+msgid ""
+"net eventlog import\n"
+" Import win32 *.evt eventlog file"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:262
+msgid "Export eventlog"
+msgstr ""
+
+#: ../../utils/net_eventlog.c:263
+msgid ""
+"net eventlog export\n"
+" Export win32 *.evt eventlog file"
+msgstr ""
+
+#: ../../utils/net_file.c:27
+msgid ""
+"net [<method>] file [misc. options] [targets]\n"
+"\tlists all open files on file server\n"
+msgstr ""
+
+#: ../../utils/net_file.c:29
+msgid ""
+"net [<method>] file USER <username> [misc. options] [targets]\n"
+"\tlists all files opened by username on file server\n"
+msgstr ""
+
+#: ../../utils/net_file.c:32
+msgid ""
+"net [<method>] file CLOSE <id> [misc. options] [targets]\n"
+"\tcloses specified file on target server\n"
+msgstr ""
+
+#: ../../utils/net_file.c:34
+msgid ""
+"net [rap] file INFO <id> [misc. options] [targets]\n"
+"\tdisplays information about the specified open file\n"
+msgstr ""
+
+#: ../../utils/net_group.c:27
+msgid ""
+"net [<method>] group [misc. options] [targets]\n"
+"\tList user groups\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_group.c:29
+msgid ""
+"net rpc group LIST [global|local|builtin]* [misc. options]\n"
+"\tList specific user groups\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_group.c:31
+msgid ""
+"net [<method>] group DELETE <name> [misc. options] [targets]\n"
+"\tDelete specified group\n"
+msgstr ""
+
+#: ../../utils/net_group.c:34
+msgid ""
+"\n"
+"net [<method>] group ADD <name> [-C comment] [-c container] [misc. options] [targets]\n"
+"\tCreate specified group\n"
+msgstr ""
+
+#: ../../utils/net_group.c:37
+msgid ""
+"\n"
+"net rpc group MEMBERS <name>\n"
+"\tList Group Members\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_group.c:38
+msgid ""
+"\n"
+"net rpc group ADDMEM <group> <member>\n"
+"\tAdd Group Members\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_group.c:40
+msgid ""
+"\n"
+"net rpc group DELMEM <group> <member>\n"
+"\tDelete Group Members\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_group.c:44 ../../utils/net_user.c:41
+msgid "\t-C or --comment=<comment>\tdescriptive comment (for add only)\n"
+msgstr ""
+
+#: ../../utils/net_group.c:46 ../../utils/net_user.c:43
+msgid "\t-c or --container=<container>\tLDAP container, defaults to cn=Users (for add in ADS only)\n"
+msgstr ""
+
+#: ../../utils/net_group.c:48
+msgid "\t-L or --localgroup\t\tWhen adding groups, create a local group (alias)\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:38
+#, c-format
+msgid "NT Group %s doesn't exist in mapping DB\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:46
+#, c-format
+msgid "converting sid %s from a string failed!\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:65
+#, c-format
+msgid "\tSID : %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:66
+#, c-format
+msgid "\tUnix gid : %u\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:67
+#, c-format
+msgid "\tUnix group: %s\n"
+msgstr "\tUnix Gruppe: %s\n"
+
+#: ../../utils/net_groupmap.c:68
+#, c-format
+msgid "\tGroup type: %s\n"
+msgstr "\tGruppentyp: %s\n"
+
+#: ../../utils/net_groupmap.c:70
+#, c-format
+msgid "\tComment : %s\n"
+msgstr "\tKommentar : %s\n"
+
+#: ../../utils/net_groupmap.c:84
+msgid ""
+"net groupmap list [verbose] [ntgroup=NT group] [sid=SID]\n"
+" verbose\tPrint verbose list\n"
+" ntgroup\tNT group to list\n"
+" sid\tSID of group to list"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:91
+msgid "Usage: "
+msgstr "Verwendung: "
+
+#: ../../utils/net_groupmap.c:106 ../../utils/net_groupmap.c:213 ../../utils/net_groupmap.c:220 ../../utils/net_groupmap.c:364 ../../utils/net_groupmap.c:371 ../../utils/net_groupmap.c:503
+msgid "must supply a name\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:113 ../../utils/net_groupmap.c:227 ../../utils/net_groupmap.c:510
+msgid "must supply a SID\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:118 ../../utils/net_groupmap.c:265 ../../utils/net_groupmap.c:405 ../../utils/net_groupmap.c:515
+#, c-format
+msgid "Bad option: %s\n"
+msgstr "Ungültige Option: %s\n"
+
+#: ../../utils/net_groupmap.c:139
+msgid "Failure to local group SID in the database\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:181
+msgid "net groupmap add {rid=<int>|sid=<string>} unixgroup=<string> [type=<domain|local|builtin>] [ntgroup=<string>] [comment=<string>]"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:205
+#, c-format
+msgid "RID must be greater than %d\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:235 ../../utils/net_groupmap.c:379
+msgid "must supply a comment string\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:259
+#, c-format
+msgid "unknown group type %s\n"
+msgstr "Unbekannter Gruppentyp %s\n"
+
+#: ../../utils/net_groupmap.c:276
+#, c-format
+msgid "Can't lookup UNIX group %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:282
+#, c-format
+msgid "Unix group %s already mapped to SID %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:289
+msgid "No rid or sid specified, choosing a RID\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:292
+msgid "Could not get new RID\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:297
+#, c-format
+msgid "Got RID %d\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:327
+#, c-format
+msgid "adding entry for group %s failed!\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:331
+#, c-format
+msgid "Successfully added group %s to the mapping db as a %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:348
+msgid "net groupmap modify {ntgroup=<string>|sid=<SID>} [comment=<string>] [unixgroup=<string>] [type=<domain|local>]"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:387
+msgid "must supply a group name\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:433
+msgid "Failed to find local group SID in the database\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:442
+msgid "Can't map to an unknown group type.\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:448
+msgid "You can only change between domain and local groups.\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:465
+#, c-format
+msgid "Unable to lookup UNIX group %s. Make sure the group exists.\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:475
+msgid "Could not update group database\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:479
+#, c-format
+msgid "Updated mapping entry for %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:490
+msgid "net groupmap delete {ntgroup=<string>|sid=<SID>}"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:531
+#, c-format
+msgid "Unable to resolve group %s to a SID\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:538
+#, c-format
+msgid "Failed to remove group %s from the mapping db!\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:543
+#, c-format
+msgid "Sucessfully removed %s from the mapping db\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:558
+msgid " net groupmap set \"NT Group\" [\"unix group\"] [-C \"comment\"] [-L] [-D]\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:564
+msgid "Can only specify -L or -D, not both\n"
+msgstr "Entweder -L oder -D angeben, aber nicht beides\n"
+
+#: ../../utils/net_groupmap.c:574
+#, c-format
+msgid "Could not find unix group %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:595
+#, c-format
+msgid "Could not find group mapping for %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:606
+msgid "Could not allocate new RID\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:622
+#, c-format
+msgid "Could not add mapping entry for %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:633
+#, c-format
+msgid "Can't change type of the BUILTIN group %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:660
+#, c-format
+msgid "Could not update group mapping for %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:678
+msgid "Delete all group mappings"
+msgstr "Alle Gruppenzuweisungen löschen"
+
+#: ../../utils/net_groupmap.c:684
+msgid "Could not list group mappings\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:691
+#, c-format
+msgid "Group %s is not mapped\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:694
+#, c-format
+msgid "Deleting mapping for NT Group %s, sid %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:716
+msgid "net groupmap addmem alias-sid member-sid\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:721
+#, c-format
+msgid "Could not add sid %s to alias %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:739
+msgid "net groupmap delmem alias-sid member-sid\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:744
+#, c-format
+msgid "Could not delete sid %s from alias %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:763
+msgid "net groupmap listmem alias-sid\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:772
+#, c-format
+msgid "Could not list members for sid %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:799
+#, c-format
+msgid "Could not list memberships for sid %s\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:823
+msgid "net groupmap memberof sid\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:829 ../../utils/net_idmap.c:349
+msgid "talloc_init failed\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:836
+msgid "Could not get domain sid\n"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:859
+msgid "Create a new group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:860
+msgid ""
+"net groupmap add\n"
+" Create a new group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:867
+msgid "Update a group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:868
+msgid ""
+"net groupmap modify\n"
+" Modify an existing group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:875
+msgid "Remove a group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:876
+msgid ""
+"net groupmap delete\n"
+" Remove a group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:883
+msgid "Set group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:884
+msgid ""
+"net groupmap set\n"
+" Set a group mapping"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:891
+msgid "Remove foreign group mapping entries"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:892
+msgid ""
+"net groupmap cleanup\n"
+" Remove foreign group mapping entries"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:899
+msgid "Add a foreign alias member"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:900
+msgid ""
+"net groupmap addmem\n"
+" Add a foreign alias member"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:907
+msgid "Delete foreign alias member"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:908
+msgid ""
+"net groupmap delmem\n"
+" Delete foreign alias member"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:915
+msgid "List foreign group members"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:916
+msgid ""
+"net groupmap listmem\n"
+" List foreign alias members"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:923
+msgid "List foreign group memberships"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:924
+msgid ""
+"net groupmap memberships\n"
+" List foreign group memberships"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:931
+msgid "List current group map"
+msgstr ""
+
+#: ../../utils/net_groupmap.c:932
+msgid ""
+"net groupmap list\n"
+" List current group map"
+msgstr ""
+
+#: ../../utils/net_help.c:39
+#, c-format
+msgid "net %s usage:\n"
+msgstr ""
+
+#: ../../utils/net_help_common.c:25 ../../utils/net_join.c:28
+msgid "Valid methods: (auto-detected if not specified)\n"
+msgstr ""
+
+#: ../../utils/net_help_common.c:26 ../../utils/net_join.c:29
+msgid "\tads\t\t\t\tActive Directory (LDAP/Kerberos)\n"
+msgstr ""
+
+#: ../../utils/net_help_common.c:27 ../../utils/net_join.c:30
+msgid "\trpc\t\t\t\tDCE-RPC\n"
+msgstr ""
+
+#: ../../utils/net_help_common.c:28
+msgid "\trap\t\t\t\tRAP (older systems)\n"
+msgstr ""
+
+#: ../../utils/net_help_common.c:35
+msgid "Valid targets: choose one (none defaults to localhost)\n"
+msgstr "Mögliche Zielangaben: (ohne Angabe wird localhost angenommen)\n"
+
+#: ../../utils/net_help_common.c:36
+msgid "\t-S or --server=<server>\t\tserver name\n"
+msgstr "\t-S oder --server=<server>\t\tServer Name\n"
+
+#: ../../utils/net_help_common.c:37
+msgid "\t-I or --ipaddress=<ipaddr>\taddress of target server\n"
+msgstr "\t-I oder --ipaddress=<ipaddr>\tAdresse des Zielservers\n"
+
+#: ../../utils/net_help_common.c:38
+msgid "\t-w or --workgroup=<wg>\t\ttarget workgroup or domain\n"
+msgstr "\t-w oder --workgroup=<wg>\t\tZielarbeitsgruppe oder -domäne\n"
+
+#: ../../utils/net_help_common.c:41
+msgid "Valid miscellaneous options are:\n"
+msgstr "Diverse Optionen:\n"
+
+#. misc options
+#: ../../utils/net_help_common.c:42
+msgid "\t-p or --port=<port>\t\tconnection port on target\n"
+msgstr "\t-p oder --port=<port>\t\tPortnummer\n"
+
+#: ../../utils/net_help_common.c:43
+msgid "\t-W or --myworkgroup=<wg>\tclient workgroup\n"
+msgstr "\t-W oder --myworkgroup=<wg>\tzu nutzende Arbeitsgruppe\n"
+
+#: ../../utils/net_help_common.c:44
+msgid "\t-d or --debuglevel=<level>\tdebug level (0-10)\n"
+msgstr "\t-d oder --debuglevel=<level>\tDebug Level (0-10)\n"
+
+#: ../../utils/net_help_common.c:45
+msgid "\t-n or --myname=<name>\t\tclient name\n"
+msgstr "\t-n oder --myname=<name>\t\tzu nutzender Name\n"
+
+#: ../../utils/net_help_common.c:46
+msgid "\t-U or --user=<name>\t\tuser name\n"
+msgstr "\t-U oder --user=<name>\t\tBenutzername\n"
+
+#: ../../utils/net_help_common.c:47
+msgid "\t-s or --configfile=<path>\tpathname of smb.conf file\n"
+msgstr "\t-s oder --configfile=<path>\tPfad zur smb.conf Datei\n"
+
+#: ../../utils/net_help_common.c:48
+msgid "\t-l or --long\t\t\tDisplay full information\n"
+msgstr "\t-l oder --long\t\t\tAlle Informationen anzeigen\n"
+
+#: ../../utils/net_help_common.c:49
+msgid "\t-V or --version\t\t\tPrint samba version information\n"
+msgstr "\t-V oder --version\t\t\tSamba Versionsinformationen ausgeben\n"
+
+#: ../../utils/net_help_common.c:50
+msgid "\t-P or --machine-pass\t\tAuthenticate as machine account\n"
+msgstr "\t-P oder --machine-pass\t\tMit Rechnerkonto authentifizieren\n"
+
+#: ../../utils/net_help_common.c:52
+msgid "\t-e or --encrypt\t\t\tEncrypt SMB transport\n"
+msgstr "\t-e oder --encrypt\t\t\tSMB Übertragung verschlüsseln\n"
+
+#: ../../utils/net_help_common.c:54
+msgid "\t-k or --kerberos\t\tUse kerberos (active directory) authentication\n"
+msgstr "\t-k oder --kerberos\t\tKerberos Authentifizierung benutzen (Active Directory)\n"
+
+#: ../../utils/net_idmap.c:26
+msgid "Out of memory!\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:40
+#, c-format
+msgid "USER HWM %d\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:45
+#, c-format
+msgid "GROUP HWM %d\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:66
+msgid ""
+"net idmap dump <inputfile>\n"
+" Dump current ID mapping.\n"
+" inputfile\tTDB file to read mappings from.\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:75
+#, c-format
+msgid "Could not open idmap: %s\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:98
+msgid ""
+"net idmap restore [inputfile]\n"
+" Restore ID mappings from file\n"
+" inputfile\tFile to load ID mappings from. If not given, load data from stdin.\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:107
+msgid "To use net idmap Winbindd must be running.\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:145
+#, c-format
+msgid "Could not set USER HWM: %s\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:154
+#, c-format
+msgid "Could not set GROUP HWM: %s\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:159
+#, c-format
+msgid "ignoring invalid line [%s]\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:166
+#, c-format
+msgid "ignoring invalid sid [%s]: %s\n"
+msgstr "ignoriere ungültige sid [%s]: %s\n"
+
+#: ../../utils/net_idmap.c:178
+#, c-format
+msgid "Could not set mapping of %s %lu to sid %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:199 ../../utils/net_idmap.c:205 ../../utils/net_idmap.c:422 ../../utils/net_idmap.c:430
+msgid "Not implemented yet"
+msgstr "Noch nicht implementiert"
+
+#: ../../utils/net_idmap.c:245
+msgid ""
+"net idmap secret {<DOMAIN>|alloc} <secret>\n"
+" Set the secret for the specified domain (or alloc module)\n"
+" DOMAIN\tDomain to set secret for.\n"
+" alloc\tSet secret for the alloc module\n"
+" secret\tNew secret to set.\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:275
+msgid "The only currently supported backend is LDAP\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:285
+#, c-format
+msgid "Missing ldap_user_dn option for domain %s\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:296
+msgid "Missing ldap_user_dn option for alloc backend\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:306
+msgid "Failed to store secret\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:311
+msgid "Secret stored\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:317
+msgid ""
+"net idmap dump <inputfile>\n"
+" Dump current id mapping\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:320
+msgid ""
+"net idmap restore\n"
+" Restore entries from stdin\n"
+msgstr ""
+
+#. Deliberately *not* document net idmap delete
+#: ../../utils/net_idmap.c:325
+msgid ""
+"net idmap secret <DOMAIN>|alloc <secret>\n"
+" Set the secret for the specified DOMAIN (or the alloc module)\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:355
+#, c-format
+msgid "db_open failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:360 ../../utils/net_idmap.c:365
+#, c-format
+msgid "%s is not a valid sid\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:371
+msgid "talloc_strdup failed\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:377
+msgid "could not fetch db record\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:385
+#, c-format
+msgid "could not store record: %s\n"
+msgstr ""
+
+#: ../../utils/net_idmap.c:406
+msgid "Dump the current ID mappings"
+msgstr ""
+
+#: ../../utils/net_idmap.c:407
+msgid ""
+"net idmap dump\n"
+" Dump the current ID mappings"
+msgstr ""
+
+#: ../../utils/net_idmap.c:414
+msgid "Restore entries from stdin"
+msgstr ""
+
+#: ../../utils/net_idmap.c:415
+msgid ""
+"net idmap restore\n"
+" Restore entries from stdin"
+msgstr ""
+
+#: ../../utils/net_idmap.c:423
+msgid ""
+"net idmap setmap\n"
+" Not implemented yet"
+msgstr ""
+
+#: ../../utils/net_idmap.c:431
+msgid ""
+"net idmap delete\n"
+" Not implemented yet"
+msgstr ""
+
+#: ../../utils/net_idmap.c:438
+msgid "Set secret for specified domain"
+msgstr ""
+
+#: ../../utils/net_idmap.c:439
+msgid ""
+"net idmap secret {<DOMAIN>|alloc} <secret>\n"
+" Set secret for specified domain or alloc module"
+msgstr ""
+
+#: ../../utils/net_idmap.c:446
+msgid "Set acl map"
+msgstr ""
+
+#: ../../utils/net_idmap.c:447
+msgid ""
+"net idmap aclmapset\n"
+" Set acl map"
+msgstr ""
+
+#: ../../utils/net_join.c:26
+msgid ""
+"\n"
+"net [<method>] join [misc. options]\n"
+"\tjoins this server to a domain\n"
+msgstr ""
+
+#: ../../utils/net_join.c:47
+msgid "ADS join did not work, falling back to RPC...\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:25
+msgid ""
+" net lookup [host] HOSTNAME[#<type>]\n"
+"\tgives IP for a hostname\n"
+"\n"
+" net lookup ldap [domain]\n"
+"\tgives IP of domain's ldap server\n"
+"\n"
+" net lookup kdc [realm]\n"
+"\tgives IP of realm's kerberos KDC\n"
+"\n"
+" net lookup pdc [domain|realm]\n"
+"\tgives IP of realm's kerberos KDC\n"
+"\n"
+" net lookup dc [domain]\n"
+"\tgives IP of domains Domain Controllers\n"
+"\n"
+" net lookup master [domain|wg]\n"
+"\tgive IP of master browser\n"
+"\n"
+" net lookup name [name]\n"
+"\tLookup name's sid and type\n"
+"\n"
+" net lookup sid [sid]\n"
+"\tGive sid's name and type\n"
+"\n"
+" net lookup dsgetdcname [name] [flags] [sitename]\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:113 ../../utils/net_registry.c:261 ../../utils/net_registry.c:268 ../../utils/net_rpc.c:82 ../../utils/net_rpc.c:93 ../../utils/net_rpc.c:1589 ../../utils/net_rpc.c:4051 ../../utils/net_util.c:53
+msgid "failed"
+msgstr ""
+
+#: ../../utils/net_lookup.c:328
+msgid " net lookup name <name>\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:334 ../../utils/net_lookup.c:363
+#, c-format
+msgid "Could not lookup name %s\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:352
+msgid " net lookup sid <sid>\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:357
+#, c-format
+msgid "Could not convert %s to SID\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:385
+msgid " net lookup dsgetdcname <name> <flags> <sitename>\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:411
+#, c-format
+msgid "failed with: %s\n"
+msgstr ""
+
+#: ../../utils/net_lookup.c:444
+msgid ""
+"\n"
+"Usage: \n"
+msgstr ""
+
+#: ../../utils/net_rap.c:39
+msgid ""
+"\n"
+"Not implemented\n"
+msgstr ""
+"\n"
+"Nicht implementiert\n"
+
+#: ../../utils/net_rap.c:61
+#, c-format
+msgid ""
+"File ID %d\n"
+"User name %s\n"
+"Locks 0x%-4.2x\n"
+"Path %s\n"
+"Permissions 0x%x\n"
+msgstr ""
+
+#. list open files
+#: ../../utils/net_rap.c:114
+msgid ""
+"\n"
+"Enumerating open files on remote server:\n"
+"\n"
+"\n"
+"FileId Opened by Perms Locks Path \n"
+"------ --------- ----- ----- ---- \n"
+msgstr ""
+
+#: ../../utils/net_rap.c:120 ../../utils/net_rap.c:180
+msgid ""
+"\n"
+"Operation not supported by server!\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:133
+msgid "Close specified file on server"
+msgstr ""
+
+#: ../../utils/net_rap.c:134
+msgid ""
+"net rap file close\n"
+" Close specified file on server"
+msgstr ""
+
+#: ../../utils/net_rap.c:141
+msgid "List all files opened by username"
+msgstr ""
+
+#: ../../utils/net_rap.c:142
+msgid ""
+"net rap file user\n"
+" List all files opened by username"
+msgstr ""
+
+#: ../../utils/net_rap.c:149
+msgid "Display info about an opened file"
+msgstr ""
+
+#: ../../utils/net_rap.c:150
+msgid ""
+"net rap file info\n"
+" Display info about an opened file"
+msgstr ""
+
+#: ../../utils/net_rap.c:162
+msgid ""
+"net rap file\n"
+" List all open files on rempte server\n"
+msgstr ""
+
+#. list open files
+#: ../../utils/net_rap.c:174
+msgid ""
+"\n"
+"Enumerating open files on remote server:\n"
+"\n"
+"\n"
+"FileId Opened by Perms Locks Path\n"
+"------ --------- ----- ----- ----\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:243
+msgid "Server path not specified\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:273 ../../utils/net_rap.c:281
+msgid "Delete a share from server"
+msgstr ""
+
+#: ../../utils/net_rap.c:274
+msgid ""
+"net rap share delete\n"
+" Delete a share from server"
+msgstr ""
+
+#: ../../utils/net_rap.c:282
+msgid ""
+"net rap share close\n"
+" Delete a share from server\n"
+" Alias for net rap share delete"
+msgstr ""
+
+#: ../../utils/net_rap.c:290
+msgid "Add a share to server"
+msgstr ""
+
+#: ../../utils/net_rap.c:291
+msgid ""
+"net rap share add\n"
+" Add a share to server"
+msgstr ""
+
+#: ../../utils/net_rap.c:303
+msgid ""
+"net rap share\n"
+" List all shares on remote server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:314 ../../utils/net_rpc.c:3092
+msgid ""
+"\n"
+"Enumerating shared resources (exports) on remote server:\n"
+"\n"
+"\n"
+"Share name Type Description\n"
+"---------- ---- -----------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:331
+msgid ""
+"\n"
+"net rap session [misc. options] [targets]\n"
+"\tenumerates all active SMB/CIFS sessions on target server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:334
+msgid ""
+"\n"
+"net rap session DELETE <client_name> [misc. options] [targets] \n"
+"\tor\n"
+"net rap session CLOSE <client_name> [misc. options] [targets]\n"
+"\tDeletes (closes) a session from specified client to server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:339
+msgid ""
+"\n"
+"net rap session INFO <client_name>\n"
+"\tEnumerates all open files in specified session\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:369
+#, c-format
+msgid ""
+"User name %-20.20s\n"
+"Computer %-20.20s\n"
+"Guest logon %-20.20s\n"
+"Client Type %-40.40s\n"
+"Sess time %2.2d:%2.2d:%2.2d\n"
+"Idle time %2.2d:%2.2d:%2.2d\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:408
+msgid ""
+"Share name Type # Opens\n"
+"------------------------------------------------------------------------------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:438
+msgid "Display information about session"
+msgstr ""
+
+#: ../../utils/net_rap.c:439
+msgid ""
+"net rap session info\n"
+" Display information about session"
+msgstr ""
+
+#: ../../utils/net_rap.c:446 ../../utils/net_rap.c:455
+msgid "Close specified session"
+msgstr ""
+
+#: ../../utils/net_rap.c:447
+msgid ""
+"net rap session delete\n"
+" Close specified session\n"
+" Alias for net rap session close"
+msgstr ""
+
+#: ../../utils/net_rap.c:456
+msgid ""
+"net rap session close\n"
+" Close specified session"
+msgstr ""
+
+#: ../../utils/net_rap.c:468
+msgid ""
+"net rap session\n"
+" List all open sessions on remote server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:478
+msgid ""
+"Computer User name Client Type Opens Idle time\n"
+"------------------------------------------------------------------------------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:508
+msgid ""
+"net rap server name\n"
+" Get the name of the server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:517
+msgid "cli_get_server_name failed\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:522
+#, c-format
+msgid "Server name = %s\n"
+msgstr "Servername = %s\n"
+
+#: ../../utils/net_rap.c:538
+msgid ""
+"net rap server domain\n"
+" Enumerate servers in this domain/workgroup\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:546
+msgid ""
+"\n"
+"Enumerating servers in this domain or workgroup: \n"
+"\n"
+"\tServer name Server description\n"
+"\t------------- ----------------------------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:563
+msgid "Get the name of the server"
+msgstr ""
+
+#: ../../utils/net_rap.c:564
+msgid ""
+"net rap server name\n"
+" Get the name of the server"
+msgstr ""
+
+#: ../../utils/net_rap.c:571
+msgid "Get the servers in this domain/workgroup"
+msgstr ""
+
+#: ../../utils/net_rap.c:572
+msgid ""
+"net rap server domain\n"
+" Get the servers in this domain/workgroup"
+msgstr ""
+
+#: ../../utils/net_rap.c:586
+msgid ""
+"net rap domain [misc. options] [target]\n"
+"\tlists the domains or workgroups visible on the current network\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:604
+msgid ""
+"\n"
+"Enumerating domains:\n"
+"\n"
+"\tDomain name Server name of Browse Master\n"
+"\t------------- ----------------------------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:617
+msgid ""
+"net rap printq [misc. options] [targets]\n"
+"\tor\n"
+"net rap printq info [<queue_name>] [misc. options] [targets]\n"
+"\tlists the specified queue and jobs on the target server.\n"
+"\tIf the queue name is not specified, all queues are listed.\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:623
+msgid ""
+"net rap printq delete [<queue name>] [misc. options] [targets]\n"
+"\tdeletes the specified job number on the target server, or the\n"
+"\tprinter queue if no job number is specified\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:637
+#, c-format
+msgid "%-17.17s Queue %5d jobs "
+msgstr ""
+
+#: ../../utils/net_rap.c:642
+msgid "*Printer Active*\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:645
+msgid "*Printer Paused*\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:648
+msgid "*Printer error*\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:651
+msgid "*Delete Pending*\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:654 ../../utils/net_rap.c:680
+msgid "**UNKNOWN STATUS**\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:668
+msgid "Waiting\n"
+msgstr "Wartend\n"
+
+#: ../../utils/net_rap.c:671
+msgid "Held in queue\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:674
+msgid "Spooling\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:677
+msgid "Printing\n"
+msgstr "Druckend\n"
+
+#: ../../utils/net_rap.c:685
+#, c-format
+msgid ""
+"Print queues at \\\\%s\n"
+"\n"
+"Name Job # Size Status\n"
+"\n"
+"-------------------------------------------------------------------------------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:733
+msgid "Display info about print queues and jobs"
+msgstr ""
+
+#: ../../utils/net_rap.c:734
+msgid ""
+"net rap printq info [queue]\n"
+" Display info about print jobs in queue.\n"
+" If queue is not specified, all queues are listed"
+msgstr ""
+
+#: ../../utils/net_rap.c:743
+msgid "Delete print job(s)"
+msgstr ""
+
+#: ../../utils/net_rap.c:744
+msgid ""
+"net rap printq delete\n"
+" Delete print job(s)"
+msgstr ""
+
+#: ../../utils/net_rap.c:753
+msgid ""
+"net rap printq\n"
+" List the print queue\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:866 ../../utils/net_rpc.c:945
+msgid "Add specified user"
+msgstr ""
+
+#: ../../utils/net_rap.c:867
+msgid ""
+"net rap user add\n"
+" Add specified user"
+msgstr ""
+
+#: ../../utils/net_rap.c:874
+msgid "List domain groups of specified user"
+msgstr ""
+
+#: ../../utils/net_rap.c:875
+msgid ""
+"net rap user info\n"
+" List domain groups of specified user"
+msgstr ""
+
+#: ../../utils/net_rap.c:883 ../../utils/net_rpc.c:961
+msgid "Remove specified user"
+msgstr ""
+
+#: ../../utils/net_rap.c:884
+msgid ""
+"net rap user delete\n"
+" Remove specified user"
+msgstr ""
+
+#: ../../utils/net_rap.c:894
+msgid ""
+"net rap user\n"
+" List all users\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:917
+#, c-format
+msgid "Net user returned: %d\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:985
+msgid "Add specified group"
+msgstr ""
+
+#: ../../utils/net_rap.c:986
+msgid ""
+"net rap group add\n"
+" Add specified group"
+msgstr ""
+
+#: ../../utils/net_rap.c:993 ../../utils/net_rpc.c:2786
+msgid "Delete specified group"
+msgstr ""
+
+#: ../../utils/net_rap.c:994
+msgid ""
+"net rap group delete\n"
+" Delete specified group"
+msgstr ""
+
+#: ../../utils/net_rap.c:1005
+msgid ""
+"net rap group\n"
+" List all groups\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1014
+msgid ""
+"Group name Comment\n"
+"-----------------------------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1031
+msgid ""
+"net rap groupmember LIST <group> [misc. options] [targets]\n"
+"\t Enumerate users in a group\n"
+"\n"
+"net rap groupmember DELETE <group> <user> [misc. options] [targets]\n"
+"\t Delete specified user from specified group\n"
+"\n"
+"net rap groupmember ADD <group> <user> [misc. options] [targets]\n"
+"\t Add specified user to specified group\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1098
+msgid "Add specified user to group"
+msgstr ""
+
+#: ../../utils/net_rap.c:1099
+msgid ""
+"net rap groupmember add\n"
+" Add specified user to group"
+msgstr ""
+
+#: ../../utils/net_rap.c:1106
+msgid "List users in group"
+msgstr ""
+
+#: ../../utils/net_rap.c:1107
+msgid ""
+"net rap groupmember list\n"
+" List users in group"
+msgstr ""
+
+#: ../../utils/net_rap.c:1114
+msgid "Remove user from group"
+msgstr ""
+
+#: ../../utils/net_rap.c:1115
+msgid ""
+"net rap groupmember delete\n"
+" Remove user from group"
+msgstr ""
+
+#: ../../utils/net_rap.c:1126
+msgid ""
+"net rap validate <username> [password]\n"
+"\tValidate user and password to check whether they can access target server or domain\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1141
+msgid ""
+"net rap service [misc. options] [targets] \n"
+"\tlists all running service daemons on target server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1143
+msgid ""
+"\n"
+"net rap service START <name> [service startup arguments] [misc. options] [targets]\n"
+"\tStart named service on remote server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1146
+msgid ""
+"\n"
+"net rap service STOP <name> [misc. options] [targets]\n"
+"\n"
+"\tStop named service on remote server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1176
+msgid "Start service on remote server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1177
+msgid ""
+"net rap service start\n"
+" Start service on remote server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1184
+msgid "Stop named serve on remote server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1185
+msgid ""
+"net rap service stop\n"
+" Stop named serve on remote server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1196
+msgid ""
+"net rap service\n"
+" List services on remote server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1206
+msgid ""
+"Service name Comment\n"
+"-----------------------------\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1221
+msgid ""
+"net rap password <user> <oldpwo> <newpw> [misc. options] [target]\n"
+"\tchanges the password for the specified user at target\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1248
+msgid ""
+"net rap admin <remote command> [cmd args [env]] [misc. options] [targets]\n"
+"\texecutes a remote command on an os/2 target server\n"
+msgstr ""
+
+#: ../../utils/net_rap.c:1269 ../../utils/net_rpc.c:7253
+msgid "List open files"
+msgstr ""
+
+#: ../../utils/net_rap.c:1270
+msgid ""
+"net rap file\n"
+" List open files"
+msgstr ""
+
+#: ../../utils/net_rap.c:1277
+msgid "List shares exported by server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1278
+msgid ""
+"net rap share\n"
+" List shares exported by server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1285
+msgid "List open sessions"
+msgstr ""
+
+#: ../../utils/net_rap.c:1286
+msgid ""
+"net rap session\n"
+" List open sessions"
+msgstr ""
+
+#: ../../utils/net_rap.c:1294
+msgid ""
+"net rap server\n"
+" List servers in domain/workgroup"
+msgstr ""
+
+#: ../../utils/net_rap.c:1301
+msgid "List domains in network"
+msgstr ""
+
+#: ../../utils/net_rap.c:1302
+msgid ""
+"net rap domain\n"
+" List domains in network"
+msgstr ""
+
+#: ../../utils/net_rap.c:1309
+msgid "List printer queues on server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1310
+msgid ""
+"net rap printq\n"
+" List printer queues on server"
+msgstr ""
+
+#: ../../utils/net_rap.c:1317
+msgid "List users"
+msgstr ""
+
+#: ../../utils/net_rap.c:1318
+msgid ""
+"net rap user\n"
+" List users"
+msgstr ""
+
+#: ../../utils/net_rap.c:1325
+msgid "List user groups"
+msgstr ""
+
+#: ../../utils/net_rap.c:1326
+msgid ""
+"net rap group\n"
+" List user groups"
+msgstr ""
+
+#: ../../utils/net_rap.c:1333
+msgid "Check username/password"
+msgstr ""
+
+#: ../../utils/net_rap.c:1334
+msgid ""
+"net rap validate\n"
+" Check username/password"
+msgstr ""
+
+#: ../../utils/net_rap.c:1341
+msgid "List/modify group memberships"
+msgstr ""
+
+#: ../../utils/net_rap.c:1342
+msgid ""
+"net rap groupmember\n"
+" List/modify group memberships"
+msgstr ""
+
+#: ../../utils/net_rap.c:1349
+msgid "Execute commands on remote OS/2"
+msgstr ""
+
+#: ../../utils/net_rap.c:1350
+msgid ""
+"net rap admin\n"
+" Execute commands on remote OS/2"
+msgstr ""
+
+#: ../../utils/net_rap.c:1357
+msgid "Start/stop remote service"
+msgstr ""
+
+#: ../../utils/net_rap.c:1358
+msgid ""
+"net rap service\n"
+" Start/stop remote service"
+msgstr ""
+
+#: ../../utils/net_rap.c:1365 ../../utils/net_rpc.c:969
+msgid "Change user password"
+msgstr ""
+
+#: ../../utils/net_rap.c:1366
+msgid ""
+"net rap password\n"
+" Change user password"
+msgstr ""
+
+#: ../../utils/net_registry.c:94 ../../utils/net_registry.c:204
+#, c-format
+msgid "open_hive failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:101
+#, c-format
+msgid "reg_openkey failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:135
+msgid "net registry enumerate <path>\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:137 ../../utils/net_registry.c:192 ../../utils/net_registry.c:249 ../../utils/net_registry.c:446 ../../utils/net_rpc_registry.c:834 ../../utils/net_rpc_registry.c:1216
+msgid "Example:"
+msgstr ""
+
+#: ../../utils/net_registry.c:138
+msgid "net registry enumerate 'HKLM\\Software\\Samba'\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:144 ../../utils/net_registry.c:298 ../../utils/net_registry.c:370 ../../utils/net_registry.c:405 ../../utils/net_registry.c:457
+#, c-format
+msgid "open_key failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:190
+msgid "net registry createkey <path>\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:193
+msgid "net registry createkey 'HKLM\\Software\\Samba\\smbconf.127.0.0.1'\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:198 ../../utils/net_registry.c:255
+msgid "error: zero length key name given\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:212
+#, c-format
+msgid "reg_createkey failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:218 ../../utils/net_rpc_registry.c:727
+msgid "createkey did nothing -- huh?\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:221 ../../utils/net_rpc_registry.c:730
+#, c-format
+msgid "createkey created %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:224 ../../utils/net_rpc_registry.c:733
+#, c-format
+msgid "createkey opened existing %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:247
+msgid "net registry deletekey <path>\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:250
+msgid "net registry deletekey 'HKLM\\Software\\Samba\\smbconf.127.0.0.1'\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:292 ../../utils/net_rpc_registry.c:647 ../../utils/net_rpc_registry.c:675
+msgid "net rpc registry getvalue <key> <valuename>\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:304
+#, c-format
+msgid "reg_queryvalue failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:342 ../../utils/net_rpc_registry.c:476
+msgid "net rpc registry setvalue <key> <valuename> <type> [<val>]+\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:348 ../../utils/net_rpc_registry.c:436
+#, c-format
+msgid "Too many args for type %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:364 ../../utils/net_rpc_registry.c:450
+#, c-format
+msgid "type \"%s\" not implemented\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:376
+#, c-format
+msgid "reg_setvalue failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:399 ../../utils/net_rpc_registry.c:531
+msgid "net rpc registry deletevalue <key> <valuename>\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:411
+#, c-format
+msgid "reg_deletekey failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:444
+msgid "net registry getsd <path>\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:447
+msgid "net registry getsd 'HKLM\\Software\\Samba'\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:463
+#, c-format
+msgid "reg_getkeysecurity failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_registry.c:486 ../../utils/net_rpc_registry.c:1287
+msgid "Enumerate registry keys and values"
+msgstr ""
+
+#: ../../utils/net_registry.c:487
+msgid ""
+"net registry enumerate\n"
+" Enumerate registry keys and values"
+msgstr ""
+
+#: ../../utils/net_registry.c:494 ../../utils/net_rpc_registry.c:1295
+msgid "Create a new registry key"
+msgstr ""
+
+#: ../../utils/net_registry.c:495
+msgid ""
+"net registry createkey\n"
+" Create a new registry key"
+msgstr ""
+
+#: ../../utils/net_registry.c:502 ../../utils/net_rpc_registry.c:1303
+msgid "Delete a registry key"
+msgstr ""
+
+#: ../../utils/net_registry.c:503
+msgid ""
+"net registry deletekey\n"
+" Delete a registry key"
+msgstr ""
+
+#: ../../utils/net_registry.c:510 ../../utils/net_rpc_registry.c:1311 ../../utils/net_rpc_registry.c:1319
+msgid "Print a registry value"
+msgstr ""
+
+#: ../../utils/net_registry.c:511
+msgid ""
+"net registry getvalue\n"
+" Print a registry value"
+msgstr ""
+
+#: ../../utils/net_registry.c:518
+msgid "Print a registry value (raw format)"
+msgstr ""
+
+#: ../../utils/net_registry.c:519
+msgid ""
+"net registry getvalueraw\n"
+" Print a registry value (raw format)"
+msgstr ""
+
+#: ../../utils/net_registry.c:526 ../../utils/net_rpc_registry.c:1327
+msgid "Set a new registry value"
+msgstr ""
+
+#: ../../utils/net_registry.c:527
+msgid ""
+"net registry setvalue\n"
+" Set a new registry value"
+msgstr ""
+
+#: ../../utils/net_registry.c:534 ../../utils/net_rpc_registry.c:1335
+msgid "Delete a registry value"
+msgstr ""
+
+#: ../../utils/net_registry.c:535
+msgid ""
+"net registry deletevalue\n"
+" Delete a registry value"
+msgstr ""
+
+#: ../../utils/net_registry.c:542 ../../utils/net_rpc_registry.c:1367
+msgid "Get security descriptor"
+msgstr ""
+
+#: ../../utils/net_registry.c:543
+msgid ""
+"net registry getsd\n"
+" Get security descriptor"
+msgstr ""
+
+#: ../../utils/net_registry_util.c:28
+#, c-format
+msgid "Keyname = %s\n"
+msgstr ""
+
+#: ../../utils/net_registry_util.c:29
+#, c-format
+msgid "Modtime = %s\n"
+msgstr ""
+
+#: ../../utils/net_registry_util.c:32
+msgid "None"
+msgstr "Kein"
+
+#: ../../utils/net_registry_util.c:39
+#, c-format
+msgid "Type = %s\n"
+msgstr "Typ = %s\n"
+
+#: ../../utils/net_registry_util.c:45 ../../utils/net_registry_util.c:76 ../../utils/net_registry_util.c:82
+msgid "Value = "
+msgstr "Wert = "
+
+#: ../../utils/net_registry_util.c:52
+msgid "Value = \""
+msgstr "Wert = \""
+
+#: ../../utils/net_registry_util.c:64
+#, c-format
+msgid "Value[%3.3d] = \""
+msgstr ""
+
+#: ../../utils/net_registry_util.c:78
+#, c-format
+msgid "%d bytes\n"
+msgstr "%d Bytes\n"
+
+#: ../../utils/net_registry_util.c:84
+msgid "<unprintable>\n"
+msgstr "<nicht druckbar>\n"
+
+#: ../../utils/net_registry_util.c:92
+#, c-format
+msgid "Valuename = %s\n"
+msgstr "Wertname = %s\n"
+
+#: ../../utils/net_rpc.c:73 ../../utils/net_util.c:45
+msgid "Could not initialise lsa pipe\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:256
+#, c-format
+msgid "Failed to change machine account password: %s\n"
+msgstr "Konnte Passwort für Vertrauenskonto nicht ändern: %s\n"
+
+#: ../../utils/net_rpc.c:281
+msgid "Change the machine trust password"
+msgstr "Passwort von Vertrauenskonto ändern"
+
+#: ../../utils/net_rpc.c:364 ../../utils/net_rpc_join.c:477
+#, c-format
+msgid "Joined domain %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:414
+msgid "Join a domain the old way"
+msgstr "Einer Domäne auf altem Weg beitreten"
+
+#: ../../utils/net_rpc.c:421
+msgid "Failed to join domain\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:444
+#, c-format
+msgid ""
+"net rpc join -U <username>[%%password] <type>\n"
+" Join a domain\n"
+" username\tName of the admin user password\tPassword of the admin user, will prompt if not specified\n"
+" type\tCan be one of the following:\n"
+"\t\tMEMBER\tJoin as member server (default)\n"
+"\t\tBDC\tJoin as BDC\n"
+"\t\tPDC\tJoin as PDC\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:457
+msgid "cannot join as standalone machine\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:512
+#, c-format
+msgid "Could not connect to SAM: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:524
+#, c-format
+msgid "Could not open domain: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:534
+#, c-format
+msgid "Domain Name: %s\n"
+msgstr "Domänenname: %s\n"
+
+#: ../../utils/net_rpc.c:536
+#, c-format
+msgid "Domain SID: %s\n"
+msgstr "Domänen SID: %s\n"
+
+#: ../../utils/net_rpc.c:537
+#, c-format
+msgid "Sequence number: %llu\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:539
+#, c-format
+msgid "Num users: %u\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:540
+#, c-format
+msgid "Num domain groups: %u\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:541
+#, c-format
+msgid "Num local groups: %u\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:562
+msgid "Display information about the domain"
+msgstr ""
+
+#: ../../utils/net_rpc.c:599
+#, c-format
+msgid "Storing SID %s for Domain %s in secrets.tdb\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:630
+msgid "Fetch domain SID into local secrets.tdb"
+msgstr "SID der Domäne nach secrets.tdb übertragen"
+
+#: ../../utils/net_rpc.c:685
+#, c-format
+msgid "Failed to add user '%s' with error: %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:690
+#, c-format
+msgid "Added user '%s'.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:723
+#, c-format
+msgid "Failed to rename user from %s to %s - %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:727
+#, c-format
+msgid "Renamed user from %s to %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:755
+#, c-format
+msgid "Failed to delete user '%s' with: %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:760
+#, c-format
+msgid "Deleted user '%s'.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:806
+#, c-format
+msgid "Failed to set password for '%s' with error: %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:849
+#, c-format
+msgid "Failed to get groups for '%s' with error: %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:946
+msgid ""
+"net rpc user add\n"
+" Add specified user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:953
+msgid "List domain groups of user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:954
+msgid ""
+"net rpc user info\n"
+" List domain groups of user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:962
+msgid ""
+"net rpc user delete\n"
+" Remove specified user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:970
+msgid ""
+"net rpc user password\n"
+" Change user password"
+msgstr ""
+
+#: ../../utils/net_rpc.c:977
+msgid "Rename specified user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:978
+msgid ""
+"net rpc user rename\n"
+" Rename specified user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1000
+msgid "List all users"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1061
+#, c-format
+msgid "Could not lookup %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1067 ../../utils/net_sam.c:53 ../../utils/net_sam.c:159 ../../utils/net_sam.c:251
+#, c-format
+msgid "%s is a %s, not a user\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1074
+#, c-format
+msgid "%s is not in our domain\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1144
+#, c-format
+msgid "user rid: %d, group rid: %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1208
+#, c-format
+msgid "%s's %s: [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1230
+#, c-format
+msgid "Set %s's %s from [%s] to [%s]\n"
+msgstr ""
+
+#. TRANSATORS: The yes|no here are program keywords. Please do
+#. not translate.
+#: ../../utils/net_rpc.c:1276
+#, c-format
+msgid "Usage: %s <username> [yes|no]\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1301
+#, c-format
+msgid "%s's %s flag: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1317
+#, c-format
+msgid "Set %s's %s flag from [%s] to [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1343
+msgid "Show/Set a user's full name"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1346
+msgid "Show/Set a user's home directory"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1349
+msgid "Show/Set a user's home drive"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1352
+msgid "Show/Set a user's logon script"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1355
+msgid "Show/Set a user's profile path"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1358
+msgid "Show/Set a user's description"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1361
+msgid "Show/Set whether a user is disabled"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1364
+msgid "Show/Set whether a user locked out"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1367
+msgid "Show/Set whether a user does not need a password"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1370
+msgid "Show/Set whether a user's password does not expire"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1385
+msgid "List available users"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1388
+msgid "List the domain groups a user is member of"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1391
+msgid "Show info about a user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1394
+msgid "Show/Modify a user's fields"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1465
+msgid "Request samr_Connect2 failed\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1476
+msgid "Request open_domain failed\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1489
+#, c-format
+msgid "Lookup of '%s' failed\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1502
+msgid "Request open_group failed"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1514
+#, c-format
+msgid "Unable to query group members of %s"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1521
+#, c-format
+msgid "Domain Group %s (rid: %d) has %d members\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1536
+#, c-format
+msgid "Unable to open group member %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1548
+#, c-format
+msgid "Unable to lookup userinfo for group member %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1556
+#, c-format
+msgid "Group is primary group of %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1567
+msgid "Unable to delete group because some of it's members have it as primary group\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1578
+#, c-format
+msgid "Remove group member %d..."
+msgstr ""
+
+#: ../../utils/net_rpc.c:1586 ../../utils/net_rpc_registry.c:1104 ../../utils/net_rpc_registry.c:1124 ../../utils/net_rpc_registry.c:1150 ../../utils/net_rpc_registry.c:1157 ../../utils/net_rpc_registry.c:1177 ../../utils/net_rpc_registry.c:1183
+msgid "ok\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1607
+msgid "Request open_alias failed\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1615
+#, c-format
+msgid "%s is of type %s. This command is only for deleting local or global groups\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1624
+#, c-format
+msgid "Deleted %s '%s'\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1627
+#, c-format
+msgid "Deleting of %s failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1664
+#, c-format
+msgid "Failed to add group '%s' with error: %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1669
+#, c-format
+msgid "Added group '%s'.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1697
+#, c-format
+msgid "Failed to add alias '%s' with error: %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1702
+#, c-format
+msgid "Added alias '%s'.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1822 ../../utils/net_rpc.c:1872 ../../utils/net_rpc.c:2030 ../../utils/net_rpc.c:2077
+#, c-format
+msgid "Could not lookup up group member %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1934
+msgid ""
+"net rpc group addmem <group> <member>\n"
+" Add a member to a group\n"
+" group\tGroup to add member to\n"
+" member\tMember to add to group\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1943 ../../utils/net_rpc.c:2146
+#, c-format
+msgid "Could not lookup group name %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1953 ../../utils/net_rpc.c:1964
+#, c-format
+msgid "Could not add %s to %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:1970
+#, c-format
+msgid "Can only add members to global or local groups which %s is not\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2137
+msgid ""
+"net rpc group delmem <group> <member>\n"
+" Delete a member from a group\n"
+" group\tGroup to delete member from\n"
+" member\tMember to delete from group\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2156 ../../utils/net_rpc.c:2167
+#, c-format
+msgid "Could not del %s from %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2173
+#, c-format
+msgid "Can only delete members from global or local groups which %s is not\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2222
+msgid ""
+"net rpc group list [global] [local] [builtin]\n"
+" List groups on RPC server\n"
+" global\tList global groups\n"
+" local\tList local groups\n"
+" builtin\tList builtin groups\n"
+" If none of global, local or builtin is specified, all three options are considered set\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2542
+msgid "Couldn't list alias members\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2556
+#, c-format
+msgid "Couldn't open LSA pipe. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2565
+msgid "Couldn't open LSA policy handle\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2572
+msgid "Out of memory\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2587
+msgid "Couldn't lookup SIDs\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2598 ../../utils/net_rpc.c:2599
+msgid "*unknown*"
+msgstr "*unbekannt*"
+
+#: ../../utils/net_rpc.c:2673 ../../utils/net_rpc.c:2686 ../../utils/net_rpc.c:2693
+#, c-format
+msgid "Couldn't find group %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2744
+#, c-format
+msgid "Renaming group %s failed with: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2778
+msgid "Create specified group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2779
+msgid ""
+"net rpc group add\n"
+" Create specified group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2787
+msgid ""
+"net rpc group delete\n"
+" Delete specified group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2794
+msgid "Add member to group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2795
+msgid ""
+"net rpc group addmem\n"
+" Add member to group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2802
+msgid "Remove member from group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2803
+msgid ""
+"net rpc group delmem\n"
+" Remove member from group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2810
+msgid "List groups"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2811
+msgid ""
+"net rpc group list\n"
+" List groups"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2818 ../../utils/net_sam.c:2085
+msgid "List group members"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2819
+msgid ""
+"net rpc group members\n"
+" List group members"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2826
+msgid "Rename group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2827
+msgid ""
+"net rpc group rename\n"
+" Rename group"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2846
+msgid ""
+"net rpc group\n"
+" Alias for net rpc group list global local builtin\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:2918
+#, c-format
+msgid "NetShareAdd failed with: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3073
+msgid "List shares on remote server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3108
+#, c-format
+msgid "skipping [%s]: not a file share.\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3114
+#, c-format
+msgid "cli_tdis returned %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3126
+#, c-format
+msgid "share [%s] is not a diskshare (type: %x)\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3138
+#, c-format
+msgid "excluding [%s]\n"
+msgstr ""
+
+#. finally add the share on the dst server
+#: ../../utils/net_rpc.c:3205
+#, c-format
+msgid "migrating: [%s], path: %s, comment: %s, without share-ACLs\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3219
+#, c-format
+msgid " [%s] does already exist\n"
+msgstr " [%s] existiert bereits\n"
+
+#: ../../utils/net_rpc.c:3225
+#, c-format
+msgid "cannot add share: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3259
+msgid "Migrate shares to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3264 ../../utils/net_rpc.c:3599 ../../utils/net_rpc.c:3726 ../../utils/net_rpc.c:3761 ../../utils/net_rpc.c:6564 ../../utils/net_rpc.c:6620 ../../utils/net_rpc.c:6652 ../../utils/net_rpc.c:6684 ../../utils/net_rpc.c:6716
+#: ../../utils/net_rpc.c:6749
+#, c-format
+msgid "no server to migrate\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3324 ../../utils/net_rpc.c:3436 ../../utils/net_rpc.c:3519
+#, c-format
+msgid "Unsupported mode %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3329
+#, c-format
+msgid "could not handle dir %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3339
+#, c-format
+msgid "could not handle files\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3366
+#, c-format
+msgid "Unsupported file mode %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3372
+#, c-format
+msgid "could not handle file %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3395
+#, c-format
+msgid "cli_resolve_path %s failed with error: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3402
+#, c-format
+msgid "listing %s failed with error: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3441
+#, c-format
+msgid "Could handle directory attributes for top level directory of share %s. Error %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3508
+#, c-format
+msgid "skipping [%s]: builtin/hidden share\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3523
+#, c-format
+msgid " [%s] files and directories %s ACLs, %s DOS Attributes %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3526 ../../utils/net_rpc.c:3527
+msgid "including"
+msgstr "inklusive"
+
+#: ../../utils/net_rpc.c:3526 ../../utils/net_rpc.c:3527 ../../utils/net_rpc_printer.c:370 ../../utils/net_rpc_printer.c:371
+msgid "without"
+msgstr "ohne"
+
+#: ../../utils/net_rpc.c:3528 ../../utils/net_rpc_printer.c:372
+msgid "(preserving timestamps)"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3557
+#, c-format
+msgid "Could not handle the top level directory permissions for the share: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3565
+#, c-format
+msgid "could not handle files for share: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3594
+msgid "Migrate files to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3667
+#, c-format
+msgid "migrating: [%s], path: %s, comment: %s, including share-ACLs\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3686
+#, c-format
+msgid "cannot set share-acl: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3721
+msgid "Migrate share-acls to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3756
+msgid "Migrates shares including all share settings"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3798 ../../utils/net_rpc.c:3822
+msgid "Migrate shares from remote to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3799
+msgid ""
+"net rpc share migrate all\n"
+" Migrate shares from remote to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3806
+msgid "Migrate files from remote to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3807
+msgid ""
+"net rpc share migrate files\n"
+" Migrate files from remote to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3814
+msgid "Migrate share-ACLs from remote to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3815
+msgid ""
+"net rpc share migrate security\n"
+" Migrate share-ACLs from remote to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:3823
+msgid ""
+"net rpc share migrate shares\n"
+" Migrate shares from remote to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4238
+msgid "winbind use default domain = yes set, please specify a workgroup\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4247
+#, c-format
+msgid "winbind could not list users: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4561
+msgid "List allowed users"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4609
+msgid ""
+"net usersidlist\n"
+"\tprints out a list of all users the running winbind knows\n"
+"\tabout, together with all their SIDs. This is used as\n"
+"\tinput to the 'net rpc share allowedusers' command.\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4634
+msgid "Add share"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4635
+msgid ""
+"net rpc share add\n"
+" Add share"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4642
+msgid "Remove share"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4643
+msgid ""
+"net rpc share delete\n"
+" Remove share"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4650
+msgid "Modify allowed users"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4651
+msgid ""
+"net rpc share allowedusers\n"
+" Modify allowed users"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4658
+msgid "Migrate share to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4659
+msgid ""
+"net rpc share migrate\n"
+" Migrate share to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4666
+msgid "List shares"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4667
+msgid ""
+"net rpc share list\n"
+" List shares"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4687
+msgid ""
+"net rpc share\n"
+" List shares\n"
+" Alias for net rpc share list\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4721
+#, c-format
+msgid "Usage: %s <share> <path> [comment]\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4782
+#, c-format
+msgid "Name: %s\n"
+msgstr "Name: %s\n"
+
+#: ../../utils/net_rpc.c:4783
+#, c-format
+msgid "Comment: %s\n"
+msgstr "Kommentar: %s\n"
+
+#: ../../utils/net_rpc.c:4784
+#, c-format
+msgid "Path: %s\n"
+msgstr "Pfad: %s\n"
+
+#: ../../utils/net_rpc.c:4785
+#, c-format
+msgid "Password: %s\n"
+msgstr "Passwort: %s\n"
+
+#: ../../utils/net_rpc.c:4797
+msgid "List available shares"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4800
+msgid "Add a share"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4803
+msgid "Delete a share"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4806
+msgid "Get information about a share"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4898
+msgid ""
+"\n"
+"Enumerating open files on remote server:\n"
+"\n"
+"\n"
+"FileId Opened by Perms Locks Path\n"
+"------ --------- ----- ----- ---- \n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4924
+msgid "Close opened file"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4925
+msgid ""
+"net rpc file close\n"
+" Close opened file"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4932
+msgid "List files opened by user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4933
+msgid ""
+"net rpc file user\n"
+" List files opened by user"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4941
+msgid "Display information about opened file"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4942
+msgid ""
+"net rpc file info\n"
+" Display information about opened file"
+msgstr ""
+
+#: ../../utils/net_rpc.c:4962
+msgid ""
+"net rpc file\n"
+" List opened files\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5005 ../../utils/net_rpc.c:5044
+msgid ""
+"\n"
+"Shutdown successfully aborted\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5072
+msgid "Abort a scheduled shutdown"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5116 ../../utils/net_rpc.c:5169
+msgid "This machine will be shutdown shortly"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5135 ../../utils/net_rpc.c:5190
+msgid ""
+"\n"
+"Shutdown of remote machine succeeded\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5221
+msgid "Shut down a remote RPC server"
+msgstr "Einen entfernten RPC server herunterfahren"
+
+#: ../../utils/net_rpc.c:5280
+msgid " net rpc trustdom add <domain_name> <trust password>\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5342
+#, c-format
+msgid "net rpc trustdom add: create user %s failed %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5395
+msgid "net rpc trustdom add <domain_name> <trust password>\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5437
+msgid " net rpc trustdom del <domain_name>\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5480
+#, c-format
+msgid "net rpc trustdom del: LookupNames on user %s failed %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5493
+#, c-format
+msgid "net rpc trustdom del: OpenUser on user %s failed %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5510
+#, c-format
+msgid "net rpc trustdom del: RemoveMemberFromForeignDomain on user %s failed %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5522
+#, c-format
+msgid "net rpc trustdom del: DeleteUser on user %s failed %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5529
+#, c-format
+msgid "Could not set trust account password: %s\n"
+msgstr "Konnte Passwort für Vertrauenskonto nicht stetzen: %s\n"
+
+#: ../../utils/net_rpc.c:5555
+msgid "net rpc trustdom del <domain>\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5639
+msgid "net rpc trustdom establish <domain_name>\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5781
+#, c-format
+msgid "Trust to domain %s established\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5804
+msgid ""
+"net rpc trustdom revoke <domain_name>\n"
+" Revoke trust relationship\n"
+" domain_name\tName of domain to revoke trust\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:5938
+msgid "Vampire trust relationship from remote server"
+msgstr ""
+
+#.
+#. * Keep calling LsaEnumTrustdom over opened pipe until
+#. * the end of enumeration is reached
+#.
+#: ../../utils/net_rpc.c:6012
+msgid ""
+"Vampire trusted domains:\n"
+"\n"
+msgstr ""
+
+#.
+#. * in case of no trusted domains say something rather
+#. * than just display blank line
+#.
+#: ../../utils/net_rpc.c:6047 ../../utils/net_rpc.c:6203
+msgid "none\n"
+msgstr "kein\n"
+
+#: ../../utils/net_rpc.c:6097
+msgid "List incoming and outgoing trust relationships"
+msgstr ""
+
+#.
+#. * Keep calling LsaEnumTrustdom over opened pipe until
+#. * the end of enumeration is reached
+#.
+#: ../../utils/net_rpc.c:6171
+msgid ""
+"Trusted domains list:\n"
+"\n"
+msgstr ""
+
+#.
+#. * Listing trusting domains (stored in passdb backend, if local)
+#.
+#: ../../utils/net_rpc.c:6222
+msgid ""
+"\n"
+"Trusting domains list:\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6323
+msgid "strange - couldn't get domain's sid\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6328
+#, c-format
+msgid "domain controller is not responding: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6331
+msgid "couldn't get domain's sid\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6375
+msgid "Add trusting domain's account"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6376
+msgid ""
+"net rpc trustdom add\n"
+" Add trusting domain's account"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6383
+msgid "Remove trusting domain's account"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6384
+msgid ""
+"net rpc trustdom del\n"
+" Remove trusting domain's account"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6391
+msgid "Establish outgoing trust relationship"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6392
+msgid ""
+"net rpc trustdom establish\n"
+" Establish outgoing trust relationship"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6399
+msgid "Revoke outgoing trust relationship"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6400
+msgid ""
+"net rpc trustdom revoke\n"
+" Revoke outgoing trust relationship"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6407
+msgid "List in- and outgoing domain trusts"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6408
+msgid ""
+"net rpc trustdom list\n"
+" List in- and outgoing domain trusts"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6415
+msgid "Vampire trusts from remote server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6416
+msgid ""
+"net rpc trustdom vampire\n"
+" Vampire trusts from remote server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6472
+msgid "Dump remote SAM database"
+msgstr "Entfernte SAM Datenbank ausgeben"
+
+#: ../../utils/net_rpc.c:6489
+msgid "Dump remote SAM database to ldif"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6490
+msgid ""
+"net rpc vampire ldif\n"
+" Dump remote SAM database to LDIF file or stdout"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6498
+msgid "Dump remote SAM database to Kerberos Keytab"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6499
+msgid ""
+"net rpc vampire keytab\n"
+" Dump remote SAM database to Kerberos keytab file"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6507 ../../utils/net_rpc_samsync.c:259
+msgid "Dump remote SAM database to passdb"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6508
+msgid ""
+"net rpc vampire passdb\n"
+" Dump remote SAM database to passdb"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6521
+msgid "Vampire remote SAM database"
+msgstr "Entfernte SAM Datenbank übernehmen"
+
+#: ../../utils/net_rpc.c:6559
+msgid "Migrate everything from a print server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6615
+msgid "Migrate print-drivers from a print-server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6647
+msgid "Migrate print-forms from a print-server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6679
+msgid "Migrate printers from a print-server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6711
+msgid "Migrate printer-ACLs from a print-server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6743
+msgid "Migrate printer-settings from a print-server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6779
+msgid "Migrate all from remote to local print server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6780
+msgid ""
+"net rpc printer migrate all\n"
+" Migrate all from remote to local print server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6787
+msgid "Migrate drivers to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6788
+msgid ""
+"net rpc printer migrate drivers\n"
+" Migrate drivers to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6795
+msgid "Migrate froms to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6796
+msgid ""
+"net rpc printer migrate forms\n"
+" Migrate froms to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6803
+msgid "Migrate printers to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6804
+msgid ""
+"net rpc printer migrate printers\n"
+" Migrate printers to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6811
+msgid "Migrate printer ACLs to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6812
+msgid ""
+"net rpc printer migrate security\n"
+" Migrate printer ACLs to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6819
+msgid "Migrate printer settings to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6820
+msgid ""
+"net rpc printer migrate settings\n"
+" Migrate printer settings to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6847
+msgid "List printers on a remote RPC server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6874
+msgid "List printer-drivers on a remote RPC server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:6901
+msgid "Publish printer in ADS via MSRPC"
+msgstr "Drucker in AD über MSRPC freigeben"
+
+#: ../../utils/net_rpc.c:6927
+msgid "Update printer in ADS via MSRPC"
+msgstr "Drucker in AD über MSRPC aktualisieren"
+
+#: ../../utils/net_rpc.c:6954
+msgid "UnPublish printer in ADS via MSRPC"
+msgstr "Freigabe eines Druckers in AD über MSRPC entfernen"
+
+#: ../../utils/net_rpc.c:6981
+msgid "List published printers via MSRPC"
+msgstr "Freigegebene Drucker in AD über MSRPC auflisten"
+
+#: ../../utils/net_rpc.c:7010 ../../utils/net_rpc.c:7139
+msgid "Publish printer in AD"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7011
+msgid ""
+"net rpc printer publish publish\n"
+" Publish printer in AD"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7018
+msgid "Update printer in AD"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7019
+msgid ""
+"net rpc printer publish update\n"
+" Update printer in AD"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7026
+msgid "Unpublish printer"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7027
+msgid ""
+"net rpc printer publish unpublish\n"
+" Unpublish printer"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7034
+msgid "List published printers"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7035
+msgid ""
+"net rpc printer publish list\n"
+" List published printers"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7044
+msgid ""
+"net rpc printer publish\n"
+" List published printers\n"
+" Alias of net rpc printer publish list\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7071
+msgid ""
+"net rpc printer LIST [printer] [misc. options] [targets]\n"
+"\tlists all printers on print-server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7073
+msgid ""
+"net rpc printer DRIVER [printer] [misc. options] [targets]\n"
+"\tlists all printer-drivers on print-server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7075
+msgid ""
+"net rpc printer PUBLISH action [printer] [misc. options] [targets]\n"
+"\tpublishes printer settings in Active Directory\n"
+"\taction can be one of PUBLISH, UPDATE, UNPUBLISH or LIST\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7078
+msgid ""
+"net rpc printer MIGRATE PRINTERS [printer] [misc. options] [targets]\n"
+"\tmigrates printers from remote to local server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7080
+msgid ""
+"net rpc printer MIGRATE SETTINGS [printer] [misc. options] [targets]\n"
+"\tmigrates printer-settings from remote to local server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7082
+msgid ""
+"net rpc printer MIGRATE DRIVERS [printer] [misc. options] [targets]\n"
+"\tmigrates printer-drivers from remote to local server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7084
+msgid ""
+"net rpc printer MIGRATE FORMS [printer] [misc. options] [targets]\n"
+"\tmigrates printer-forms from remote to local server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7086
+msgid ""
+"net rpc printer MIGRATE SECURITY [printer] [misc. options] [targets]\n"
+"\tmigrates printer-ACLs from remote to local server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7088
+msgid ""
+"net rpc printer MIGRATE ALL [printer] [misc. options] [targets]\n"
+"\tmigrates drivers, forms, queues, settings and acls from\n"
+"\tremote to local print-server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7094
+msgid ""
+"\t-v or --verbose\t\t\tgive verbose output\n"
+"\t --destination\t\tmigration target server (default: localhost)\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7115
+msgid "List all printers on print server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7116
+msgid ""
+"net rpc printer list\n"
+" List all printers on print server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7123
+msgid "Migrate printer to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7124
+msgid ""
+"net rpc printer migrate\n"
+" Migrate printer to local server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7131
+msgid "List printer drivers"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7132
+msgid ""
+"net rpc printer driver\n"
+" List printer drivers"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7140
+msgid ""
+"net rpc printer publish\n"
+" Publish printer in AD"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7149
+msgid ""
+"net rpc printer\n"
+" List printers\n"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7180
+msgid "Modify global audit settings"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7181
+msgid ""
+"net rpc audit\n"
+" Modify global audit settings"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7188
+msgid "Show basic info about a domain"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7189
+msgid ""
+"net rpc info\n"
+" Show basic info about a domain"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7196
+msgid "Join a domain"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7197
+msgid ""
+"net rpc join\n"
+" Join a domain"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7204
+msgid "Join a domain created in server manager"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7205
+msgid ""
+"net rpc oldjoin\n"
+" Join a domain created in server manager"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7212
+msgid "Test that a join is valid"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7213
+msgid ""
+"net rpc testjoin\n"
+" Test that a join is valid"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7221
+msgid ""
+"net rpc user\n"
+" List/modify users"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7228
+msgid "Change a user password"
+msgstr "Benutzerpasswort ändern"
+
+#: ../../utils/net_rpc.c:7229
+msgid ""
+"net rpc password\n"
+" Change a user password\n"
+" Alias for net rpc user password"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7238
+msgid ""
+"net rpc group\n"
+" List/modify groups"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7245
+msgid "List/modify shares"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7246
+msgid ""
+"net rpc share\n"
+" List/modify shares"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7254
+msgid ""
+"net rpc file\n"
+" List open files"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7261
+msgid "List/modify printers"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7262
+msgid ""
+"net rpc printer\n"
+" List/modify printers"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7270
+msgid ""
+"net rpc changetrustpw\n"
+" Change trust account password"
+msgstr ""
+"net rpc changetrustpw\n"
+" trust account Passwort ändern"
+
+#: ../../utils/net_rpc.c:7277
+msgid "Modify domain trusts"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7278
+msgid ""
+"net rpc trustdom\n"
+" Modify domain trusts"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7285
+msgid "Abort a remote shutdown"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7286
+msgid ""
+"net rpc abortshutdown\n"
+" Abort a remote shutdown"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7293
+msgid "Shutdown a remote server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7294
+msgid ""
+"net rpc shutdown\n"
+" Shutdown a remote server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7301
+msgid "Dump SAM data of remote NT PDC"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7302
+msgid ""
+"net rpc samdump\n"
+" Dump SAM data of remote NT PDC"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7309
+msgid "Sync a remote NT PDC's data into local passdb"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7310
+msgid ""
+"net rpc vampire\n"
+" Sync a remote NT PDC's data into local passdb"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7317
+msgid "Fetch the domain sid into local secrets.tdb"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7318
+msgid ""
+"net rpc getsid\n"
+" Fetch the domain sid into local secrets.tdb"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7325
+msgid "Manage privileges assigned to SID"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7326
+msgid ""
+"net rpc rights\n"
+" Manage privileges assigned to SID"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7333
+msgid "Start/stop/query remote services"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7334
+msgid ""
+"net rpc service\n"
+" Start/stop/query remote services"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7341
+msgid "Manage registry hives"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7342
+msgid ""
+"net rpc registry\n"
+" Manage registry hives"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7349
+msgid "Open interactive shell on remote server"
+msgstr ""
+
+#: ../../utils/net_rpc.c:7350
+msgid ""
+"net rpc shell\n"
+" Open interactive shell on remote server"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:28
+msgid "net rpc audit list View configured Auditing policies\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:29
+msgid "net rpc audit enable Enable Auditing\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:30
+msgid "net rpc audit disable Disable Auditing\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:31
+msgid "net rpc audit get <category> View configured Auditing policy setting\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:32
+msgid ""
+"net rpc audit set <category> <policy> Set Auditing policies\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:33
+msgid "\tcategory can be one of: SYSTEM, LOGON, OBJECT, PRIVILEGE, PROCESS, POLICY, SAM, DIRECTORY or ACCOUNT\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:34
+msgid ""
+"\tpolicy can be one of: SUCCESS, FAILURE, ALL or NONE\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:45 ../../utils/net_util.c:613
+msgid "Unknown"
+msgstr "Unbekannt"
+
+#: ../../utils/net_rpc_audit.c:48
+msgid "Invalid"
+msgstr "Ungültig"
+
+#: ../../utils/net_rpc_audit.c:51
+#, c-format
+msgid "\t%-30s%s\n"
+msgstr "\t%-30s%s\n"
+
+#: ../../utils/net_rpc_audit.c:73 ../../utils/net_rpc_audit.c:140
+msgid "insufficient arguments\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:79 ../../utils/net_rpc_audit.c:146
+#, c-format
+msgid "invalid auditing category: %s\n"
+msgstr "Ungültige Audit-Kategorie: %s\n"
+
+#: ../../utils/net_rpc_audit.c:115
+#, c-format
+msgid "failed to get auditing policy: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:161
+#, c-format
+msgid "invalid auditing policy: %s\n"
+msgstr "Ungültige Audit-Regel: %s\n"
+
+#: ../../utils/net_rpc_audit.c:205
+#, c-format
+msgid "failed to set audit policy: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:254
+#, c-format
+msgid "%s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:255
+msgid "failed to enable audit policy"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:256
+msgid "failed to disable audit policy"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:328
+#, c-format
+msgid "Auditing:\t\t"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:331
+#, c-format
+msgid "Enabled"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:334
+#, c-format
+msgid "Disabled"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:337
+#, c-format
+msgid "unknown (%d)"
+msgstr "unbekannt (%d)"
+
+#: ../../utils/net_rpc_audit.c:343
+#, c-format
+msgid "Auditing categories:\t%d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:344
+#, c-format
+msgid "Auditing settings:\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:354
+#, c-format
+msgid "failed to list auditing policies: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:371
+msgid "View configured audit setting"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:389
+msgid "Set audit policies"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:407 ../../utils/net_rpc_audit.c:477
+msgid "Enable auditing"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:425 ../../utils/net_rpc_audit.c:485
+msgid "Disable auditing"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:443
+msgid "List auditing settings"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:461
+msgid "View configured auditing settings"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:462
+msgid ""
+"net rpc audit get\n"
+" View configured auditing settings"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:469
+msgid "Set auditing policies"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:470
+msgid ""
+"net rpc audit set\n"
+" Set auditing policies"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:478
+msgid ""
+"net rpc audit enable\n"
+" Enable auditing"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:486
+msgid ""
+"net rpc audit disable\n"
+" Disable auditing"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:493
+msgid "List configured auditing settings"
+msgstr ""
+
+#: ../../utils/net_rpc_audit.c:494
+msgid ""
+"net rpc audit list\n"
+" List configured auditing settings"
+msgstr ""
+
+#: ../../utils/net_rpc_join.c:297
+msgid "Creation of workstation account failed\n"
+msgstr ""
+
+#: ../../utils/net_rpc_join.c:304
+msgid "User specified does not have administrator privileges\n"
+msgstr ""
+
+#: ../../utils/net_rpc_join.c:410 ../../utils/net_rpc_join.c:438
+#, c-format
+msgid ""
+"Please make sure that no computer account\n"
+"named like this machine (%s) exists in the domain\n"
+msgstr ""
+
+#: ../../utils/net_rpc_join.c:475
+#, c-format
+msgid "Unable to join domain %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_join.c:499
+msgid ""
+"Usage\n"
+"net rpc testjoin\n"
+" Test if a join is OK\n"
+msgstr ""
+
+#: ../../utils/net_rpc_join.c:508
+#, c-format
+msgid "Join to domain '%s' is not valid: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_join.c:513
+#, c-format
+msgid "Join to '%s' is OK\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:55
+#, c-format
+msgid "Printer Driver Info 3:\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:56
+#, c-format
+msgid "\tVersion: [%x]\n"
+msgstr "\tVersion: [%x]\n"
+
+#: ../../utils/net_rpc_printer.c:57
+#, c-format
+msgid "\tDriver Name: [%s]\n"
+msgstr "\tTreibername: [%s]\n"
+
+#: ../../utils/net_rpc_printer.c:58
+#, c-format
+msgid "\tArchitecture: [%s]\n"
+msgstr "\tArchitektur: [%s]\n"
+
+#: ../../utils/net_rpc_printer.c:59
+#, c-format
+msgid "\tDriver Path: [%s]\n"
+msgstr "\tTreiberpfad: [%s]\n"
+
+#: ../../utils/net_rpc_printer.c:60
+#, c-format
+msgid "\tDatafile: [%s]\n"
+msgstr "\tDatendatei: [%s]\n"
+
+#: ../../utils/net_rpc_printer.c:61
+#, c-format
+msgid ""
+"\tConfigfile: [%s]\n"
+"\n"
+msgstr ""
+"\tKonfigurationsdatei: [%s]\n"
+"\n"
+
+#: ../../utils/net_rpc_printer.c:62
+#, c-format
+msgid ""
+"\tHelpfile: [%s]\n"
+"\n"
+msgstr ""
+"\tHilfedatei: [%s]\n"
+"\n"
+
+#: ../../utils/net_rpc_printer.c:65
+#, c-format
+msgid "\tDependentfiles: [%s]\n"
+msgstr "\tAbhängigkeiten: [%s]\n"
+
+#: ../../utils/net_rpc_printer.c:70
+#, c-format
+msgid "\tMonitorname: [%s]\n"
+msgstr "\tMonitorname: [%s]\n"
+
+#: ../../utils/net_rpc_printer.c:71
+#, c-format
+msgid ""
+"\tDefaultdatatype: [%s]\n"
+"\n"
+msgstr ""
+"\tStandard Datentyp: [%s]\n"
+"\n"
+
+#: ../../utils/net_rpc_printer.c:81
+#, c-format
+msgid "\t[%s:%s]: REG_DWORD: 0x%08x\n"
+msgstr "\t[%s:%s]: REG_DWORD: 0x%08x\n"
+
+#: ../../utils/net_rpc_printer.c:91
+#, c-format
+msgid "\t[%s:%s]: REG_SZ: %s\n"
+msgstr "\t[%s:%s]: REG_SZ: %s\n"
+
+#: ../../utils/net_rpc_printer.c:96
+#, c-format
+msgid "\t[%s:%s]: REG_BINARY: unknown length value not displayed\n"
+msgstr "\t[%s:%s]: REG_BINARY: unbekannte Länge, Daten werden nicht angezeigt\n"
+
+#: ../../utils/net_rpc_printer.c:120
+#, c-format
+msgid "\t%s: unknown type %d\n"
+msgstr "\t%s: unbekannter Typ %d\n"
+
+#: ../../utils/net_rpc_printer.c:254
+#, c-format
+msgid "could not close %s on originating server: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:262
+#, c-format
+msgid "could not close %s on destination server: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:355
+#, c-format
+msgid "malloc fail for size %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:366
+#, c-format
+msgid "copying [\\\\%s\\%s%s] => [\\\\%s\\%s%s] %s ACLs and %s DOS Attributes %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:370 ../../utils/net_rpc_printer.c:371
+msgid "with"
+msgstr "mit"
+
+#: ../../utils/net_rpc_printer.c:390
+#, c-format
+msgid "Error writing file: %s\n"
+msgstr "Datei konnte nicht gespeichert werden: %s\n"
+
+#: ../../utils/net_rpc_printer.c:414
+#, c-format
+msgid "cannot check for directory %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:424
+#, c-format
+msgid "could not close file on originating server: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:432
+#, c-format
+msgid "could not close file on destination server: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:565
+#, c-format
+msgid "cannot check %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:606
+#, c-format
+msgid "copying driver: [%s], for architecture: [%s], version: [%d]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:675
+#, c-format
+msgid "cannot enum printers: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:708
+#, c-format
+msgid "no access to printer [%s] on [%s] for user [%s] granted\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:715
+#, c-format
+msgid "cannot open printer %s on server %s: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:741
+#, c-format
+msgid "cannot get printer-info: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:822 ../../utils/net_rpc_printer.c:1326
+#, c-format
+msgid "cannot set printer-info: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:850
+#, c-format
+msgid "unable to set printerdata: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:871
+#, c-format
+msgid "enumprinterkey failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:897
+#, c-format
+msgid "enumprinterdataex failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:934
+#, c-format
+msgid "could not set printerdataex: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:959
+#, c-format
+msgid "could not enum forms: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:983
+#, c-format
+msgid "cannot enum drivers: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1016
+#, c-format
+msgid "cannot get driver: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1046
+#, c-format
+msgid "unsupported info level: %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1057
+#, c-format
+msgid "You are not allowed to add drivers\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1061
+#, c-format
+msgid "cannot add driver: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1161
+#, c-format
+msgid "printer %d: %s, shared as: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1201
+#, c-format
+msgid "listing printer-drivers\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1216
+#, c-format
+msgid "no drivers found on server for architecture: [%s].\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1222
+#, c-format
+msgid "got %d printer-drivers for architecture: [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1295
+msgid "published"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1298
+msgid "updated"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1301
+msgid "unpublished"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1304
+msgid "unknown action"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1305
+#, c-format
+msgid "unknown action: %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1331
+#, c-format
+msgid "successfully %s printer %s in Active Directory\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1443
+#, c-format
+msgid "printer [%s] is published"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1446
+#, c-format
+msgid ", guid: %s"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1450
+#, c-format
+msgid "printer [%s] is unpublished\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1454
+#, c-format
+msgid "printer [%s] is currently updating\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1458
+#, c-format
+msgid "unknown state: %d\n"
+msgstr "unbekannter Status: %d\n"
+
+#: ../../utils/net_rpc_printer.c:1528 ../../utils/net_rpc_printer.c:1675 ../../utils/net_rpc_printer.c:1858 ../../utils/net_rpc_printer.c:2047 ../../utils/net_rpc_printer.c:2214
+#, c-format
+msgid "no printers found on server.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1549
+#, c-format
+msgid "migrating printer ACLs for: [%s] / [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1695
+#, c-format
+msgid "migrating printer forms for: [%s] / [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1731
+#, c-format
+msgid "\tmigrating form # %d [%s] of type [%d]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1747
+#, c-format
+msgid "\tAddForm form %d: [%s] refused.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:1880
+#, c-format
+msgid "migrating printer driver for: [%s] / [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:2067
+#, c-format
+msgid "migrating printer queue for: [%s] / [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:2079
+#, c-format
+msgid "could not get printer, creating printer.\n"
+msgstr ""
+
+#. copy each src printer to a dst printer 1:1,
+#. maybe some values have to be changed though
+#: ../../utils/net_rpc_printer.c:2103
+#, c-format
+msgid "creating printer: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:2114
+#, c-format
+msgid "printer [%s] successfully added.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:2117
+#, c-format
+msgid "printer [%s] already exists.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:2120
+#, c-format
+msgid "could not create printer [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:2248
+#, c-format
+msgid "migrating printer settings for: [%s] / [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_printer.c:2401
+#, c-format
+msgid "got no key-data\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:430 ../../utils/net_rpc_registry.c:504 ../../utils/net_rpc_registry.c:566 ../../utils/net_rpc_registry.c:841 ../../utils/net_rpc_registry.c:913 ../../utils/net_rpc_registry.c:1224
+#, c-format
+msgid "registry_openkey failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:459
+#, c-format
+msgid "registry_setvalue failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:515
+#, c-format
+msgid "registry_deletevalue failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:587 ../../utils/net_rpc_registry.c:604
+#, c-format
+msgid "registry_queryvalue failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:719
+#, c-format
+msgid "createkey returned %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:749
+msgid "net rpc registry createkey <key>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:788
+#, c-format
+msgid "deletekey returned %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:800
+msgid "net rpc registry deletekey <key>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:832
+msgid "net rpc registry enumerate <path>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:849
+#, c-format
+msgid "enumerating keys failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:861
+#, c-format
+msgid "enumerating values failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:906
+msgid "net rpc registry backup <path> <file> \n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:921
+#, c-format
+msgid "Unable to save [%s] to %s:%s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:986
+msgid "unknown"
+msgstr "unbekannt"
+
+#: ../../utils/net_rpc_registry.c:1095
+msgid "net rpc registry dump <file> \n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1099 ../../utils/net_rpc_registry.c:1145 ../../utils/net_rpc_registry.c:1152
+#, c-format
+msgid "Opening %s...."
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1101 ../../utils/net_rpc_registry.c:1147
+#, c-format
+msgid "Failed to open %s for reading\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1109 ../../utils/net_rpc_registry.c:1162
+msgid "Could not get rootkey\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1122
+msgid "Closing registry..."
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1141
+msgid "net rpc registry copy <srcfile> <newfile>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1154
+#, c-format
+msgid "Failed to open %s for writing\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1165
+#, c-format
+msgid "RootKey: [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1173 ../../utils/net_rpc_registry.c:1179
+#, c-format
+msgid "Closing %s..."
+msgstr "Schließe %s..."
+
+#: ../../utils/net_rpc_registry.c:1214
+msgid "net rpc registry getsd <path> <secinfo>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1245
+#, c-format
+msgid "getting sd failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1288
+msgid ""
+"net rpc registry enumerate\n"
+" Enumerate registry keys and values"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1296
+msgid ""
+"net rpc registry createkey\n"
+" Create a new registry key"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1304
+msgid ""
+"net rpc registry deletekey\n"
+" Delete a registry key"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1312
+msgid ""
+"net rpc registry getvalue\n"
+" Print a registry value"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1320
+msgid ""
+"net rpc registry getvalueraw\n"
+" Print a registry value (raw version)"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1328
+msgid ""
+"net rpc registry setvalue\n"
+" Set a new registry value"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1336
+msgid ""
+"net rpc registry deletevalue\n"
+" Delete a registry value"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1343
+msgid "Save a registry file"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1344
+msgid ""
+"net rpc registry save\n"
+" Save a registry file"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1351
+msgid "Dump a registry file"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1352
+msgid ""
+"net rpc registry dump\n"
+" Dump a registry file"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1359
+msgid "Copy a registry file"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1360
+msgid ""
+"net rpc registry copy\n"
+" Copy a registry file"
+msgstr ""
+
+#: ../../utils/net_rpc_registry.c:1368
+msgid ""
+"net rpc registry getsd\n"
+" Get security descriptor"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:203
+msgid "No privileges assigned\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:368
+#, c-format
+msgid "No such privilege exists: %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:371
+#, c-format
+msgid "Error resolving privilege display name [%s].\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:380
+#, c-format
+msgid "Error enumerating accounts for privilege %s [%s].\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:456
+msgid " net rpc rights grant <name|SID> <rights...>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:493
+msgid "Successfully granted rights.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:497
+#, c-format
+msgid "Failed to grant privileges for %s (%s)\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:527
+msgid " net rpc rights revoke <name|SID> <rights...>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:562
+msgid "Successfully revoked rights.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:566
+#, c-format
+msgid "Failed to revoke privileges for %s (%s)\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:584
+msgid ""
+"net rpc rights list [{accounts|privileges} [name|SID]]\n"
+" View available/assigned privileges\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:602
+msgid ""
+"net rpc rights grant <name|SID> <right>\n"
+" Assign privilege[s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:604
+msgid ""
+"For example:\n"
+" net rpc rights grant 'VALE\\biddle' SePrintOperatorPrivilege SeDiskOperatorPrivilege\n"
+" would grant the printer admin and disk manager rights to the user 'VALE\\biddle'\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:624
+msgid ""
+"net rpc rights revoke <name|SID> <right>\n"
+" Revoke privilege[s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:626
+msgid ""
+"For example:\n"
+" net rpc rights revoke 'VALE\\biddle' SePrintOperatorPrivilege SeDiskOperatorPrivilege\n"
+" would revoke the printer admin and disk manager rights from the user 'VALE\\biddle'\n"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:648
+msgid "View available/assigned privileges"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:649
+msgid ""
+"net rpc rights list\n"
+" View available/assigned privileges"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:656 ../../utils/net_rpc_rights.c:715
+msgid "Assign privilege[s]"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:657
+msgid ""
+"net rpc rights grant\n"
+" Assign privilege[s]"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:664 ../../utils/net_rpc_rights.c:718
+msgid "Revoke privilege[s]"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:665
+msgid ""
+"net rpc rights revoke\n"
+" Revoke privilege[s]"
+msgstr ""
+
+#: ../../utils/net_rpc_rights.c:712
+msgid "View available or assigned privileges"
+msgstr ""
+
+#: ../../utils/net_rpc_samsync.c:159
+msgid ""
+"net rpc vampire ([ldif [<ldif-filename>] | [keytab] [<keytab-filename]) [options]\n"
+"\t to pull accounts from a remote PDC where we are a BDC\n"
+"\t\t no args puts accounts in local passdb from smb.conf\n"
+"\t\t ldif - put accounts in ldif format (file defaults to /tmp/tmp.ldif)\n"
+"\t\t keytab - put account passwords in krb5 keytab (defaults to system keytab)\n"
+msgstr ""
+
+#: ../../utils/net_rpc_samsync.c:187
+#, c-format
+msgid ""
+"Cannot import users from %s at this time, as the current domain:\n"
+"\t%s: %s\n"
+"conflicts with the remote domain\n"
+"\t%s: %s\n"
+"Perhaps you need to set: \n"
+"\n"
+"\tsecurity=user\n"
+"\tworkgroup=%s\n"
+"\n"
+" in your smb.conf?\n"
+msgstr ""
+
+#: ../../utils/net_rpc_samsync.c:342
+msgid "Dump remote SAM database to LDIF file or stdout"
+msgstr "Entfernte SAM Datenbank in LDIF-Datei oder Standardausgabe ausgeben"
+
+#: ../../utils/net_rpc_samsync.c:486
+msgid ""
+"net rpc vampire keytab <keytabfile>\n"
+" Dump remote SAM database to Kerberos keytab file\n"
+msgstr ""
+
+#: ../../utils/net_rpc_samsync.c:503
+#, c-format
+msgid "DC is not running Active Directory\n"
+msgstr ""
+
+#: ../../utils/net_rpc_samsync.c:513
+#, c-format
+msgid "Fallback to NT4 vampire on Mixed-Mode AD Domain\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:29
+msgid "stopped"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:30
+msgid "start pending"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:31
+msgid "stop pending"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:32
+msgid "running"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:33
+msgid "resume pending"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:34
+msgid "pause pending"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:35
+msgid "paused"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:47
+#, c-format
+msgid "Unknown State [%d]"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:82 ../../utils/net_rpc_service.c:162 ../../utils/net_rpc_service.c:355 ../../utils/net_rpc_service.c:625 ../../utils/net_rpc_service.c:705
+#, c-format
+msgid "Failed to open service. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:176
+#, c-format
+msgid "Control service request failed. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:185 ../../utils/net_rpc_service.c:373
+#, c-format
+msgid "%s service is %s.\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:230 ../../utils/net_rpc_service.c:340
+#, c-format
+msgid "Failed to open Service Control Manager. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:249
+#, c-format
+msgid "Failed to enumerate services. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:261
+msgid "No services returned\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:368 ../../utils/net_rpc_service.c:639
+#, c-format
+msgid "Query status request failed. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:395
+#, c-format
+msgid "Query config request failed. [%s]\n"
+msgstr ""
+
+#. print out the configuration information for the service
+#: ../../utils/net_rpc_service.c:402
+msgid "Configuration details:\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:403
+#, c-format
+msgid "\tControls Accepted = 0x%x\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:405
+#, c-format
+msgid "\tService Type = 0x%x\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:406
+#, c-format
+msgid "\tStart Type = 0x%x\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:407
+#, c-format
+msgid "\tError Control = 0x%x\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:408
+#, c-format
+msgid "\tTag ID = 0x%x\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:411
+#, c-format
+msgid "\tExecutable Path = %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:416
+#, c-format
+msgid "\tLoad Order Group = %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:421
+#, c-format
+msgid "\tDependencies = %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:426
+#, c-format
+msgid "\tStart Name = %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:430
+#, c-format
+msgid "\tDisplay Name = %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:474 ../../utils/net_rpc_service.c:520 ../../utils/net_rpc_service.c:566 ../../utils/net_rpc_service.c:610 ../../utils/net_rpc_service.c:690 ../../utils/net_rpc_service.c:769
+#, c-format
+msgid "Failed to open Service Control Manager. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:647
+#, c-format
+msgid "Successfully started service: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:650
+#, c-format
+msgid "Failed to start service: %s [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:717
+#, c-format
+msgid "Delete service request failed. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:722
+#, c-format
+msgid "Successfully deleted Service: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:800
+#, c-format
+msgid "Create service request failed. [%s]\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:805
+#, c-format
+msgid "Successfully created Service: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:828 ../../utils/net_rpc_service.c:972
+msgid "View configured Win32 services"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:846
+msgid "Start a Win32 service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:864
+msgid "Stop a Win32 service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:882
+msgid "Resume a Win32 service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:900
+msgid "Pause a Win32 service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:918
+msgid "Show the current status of a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:936
+msgid "Delete a Win32 service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:954
+msgid "Create a Win32 service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:973
+msgid ""
+"net rpc service list\n"
+" View configured Win32 services"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:980
+msgid "Start a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:981
+msgid ""
+"net rpc service start\n"
+" Start a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:988
+msgid "Stop a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:989
+msgid ""
+"net rpc service stop\n"
+" Stop a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:996
+msgid "Pause a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:997
+msgid ""
+"net rpc service pause\n"
+" Pause a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1004
+msgid "Resume a paused service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1005
+msgid ""
+"net rpc service resume\n"
+" Resume a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1012
+msgid "View current status of a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1013
+msgid ""
+"net rpc service status\n"
+" View current status of a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1020
+msgid "Delete a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1021
+msgid ""
+"net rpc service delete\n"
+" Deletes a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1028
+msgid "Create a service"
+msgstr ""
+
+#: ../../utils/net_rpc_service.c:1029
+msgid ""
+"net rpc service create\n"
+" Creates a service"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:79
+#, c-format
+msgid "query_domain_info level 1 failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:90
+#, c-format
+msgid "query_domain_info level 3 failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:101
+#, c-format
+msgid "query_domain_info level 12 failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:134
+#, c-format
+msgid "Got unexpected info level %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:162
+#, c-format
+msgid "Minimum password length: %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:163
+#, c-format
+msgid "Password history length: %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:166
+msgid "Minimum password age: "
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:169 ../../utils/net_rpc_sh_acct.c:177 ../../utils/net_rpc_sh_acct.c:189 ../../utils/net_rpc_sh_acct.c:197
+#, c-format
+msgid "%d seconds\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:171 ../../utils/net_rpc_sh_acct.c:179 ../../utils/net_rpc_sh_acct.c:191 ../../utils/net_rpc_sh_acct.c:199
+msgid "not set\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:174
+msgid "Maximum password age: "
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:182
+#, c-format
+msgid "Bad logon attempts: %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:186
+msgid "Account lockout duration: "
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:194
+msgid "Bad password count reset after: "
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:203
+#, c-format
+msgid "Disconnect users when logon hours expire: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:206
+#, c-format
+msgid "User must logon to change password: %s\n"
+msgstr "Benutzer muss sich anmelden um Passwort zu ändern: %s\n"
+
+#: ../../utils/net_rpc_sh_acct.c:234
+#, c-format
+msgid "Setting bad password count to %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:259 ../../utils/net_rpc_sh_acct.c:289 ../../utils/net_rpc_sh_acct.c:319 ../../utils/net_rpc_sh_acct.c:349 ../../utils/net_rpc_sh_acct.c:379 ../../utils/net_rpc_sh_acct.c:409
+#, c-format
+msgid "Usage: %s <count>\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:264
+#, c-format
+msgid "Setting lockout duration to %d seconds\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:294
+#, c-format
+msgid "Setting bad password reset duration to %d seconds\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:324
+#, c-format
+msgid "Setting minimum password age to %d seconds\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:354
+#, c-format
+msgid "Setting maximum password age to %d seconds\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:384
+#, c-format
+msgid "Setting minimum password length to %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:414
+#, c-format
+msgid "Setting password history length to %d\n"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:435
+msgid "Show current account policy settings"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:437
+msgid "Set bad password count before lockout"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:439
+msgid "Set account lockout duration"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:442
+msgid "Set bad password count reset duration"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:444
+msgid "Set minimum password age"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:446
+msgid "Set maximum password age"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:448
+msgid "Set minimum password length"
+msgstr ""
+
+#: ../../utils/net_rpc_sh_acct.c:450
+msgid "Set the password history length"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:77
+msgid "talloc_new failed\n"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:84
+#, c-format
+msgid "Could not open pipe: %s\n"
+msgstr ""
+
+#. None found
+#: ../../utils/net_rpc_shell.c:146
+#, c-format
+msgid "%s: unknown cmd\n"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:152 ../../utils/net_rpc_shell.c:232 ../../utils/net_sam.c:1605 ../../utils/net_sam.c:1867
+msgid "talloc failed\n"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:184
+#, c-format
+msgid "%s failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:194
+msgid "Print information about the domain connected to"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:197
+msgid "List/Grant/Revoke user rights"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:200
+msgid "List/Add/Remove etc shares"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:203
+msgid "List/Add/Remove user info"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:206
+msgid "Show/Change account policy settings"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:238
+#, c-format
+msgid "Could not open connection: %s\n"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:253
+#, c-format
+msgid "Talking to domain %s (%s)\n"
+msgstr ""
+
+#: ../../utils/net_rpc_shell.c:280
+#, c-format
+msgid "cmdline invalid: %s\n"
+msgstr "Kommandozeile ungültig: %s\n"
+
+#: ../../utils/net_sam.c:41
+#, c-format
+msgid "net sam set %s <user> <value>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:48 ../../utils/net_sam.c:154 ../../utils/net_sam.c:246 ../../utils/net_sam.c:310 ../../utils/net_sam.c:706 ../../utils/net_sam.c:745 ../../utils/net_sam.c:1558
+#, c-format
+msgid "Could not find name %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:59 ../../utils/net_sam.c:69 ../../utils/net_sam.c:165 ../../utils/net_sam.c:257
+msgid "Internal error\n"
+msgstr "interner Fehler\n"
+
+#: ../../utils/net_sam.c:64 ../../utils/net_sam.c:170 ../../utils/net_sam.c:262
+#, c-format
+msgid "Loading user %s failed\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:75 ../../utils/net_sam.c:186 ../../utils/net_sam.c:274
+#, c-format
+msgid "Updating sam account %s failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:82
+#, c-format
+msgid "Updated %s for %s\\%s to %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:147
+#, c-format
+msgid "net sam set %s <user> [yes|no]\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:193
+#, c-format
+msgid "Updated flag %s for %s\\%s to %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:240
+msgid "net sam set pwdmustchangenow <user> [yes|no]\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:281
+#, c-format
+msgid "Updated 'user must change password at next logon' for %s\\%s to %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:304
+msgid "net sam set comment <name> <comment>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:321
+#, c-format
+msgid "%s is a %s, not a group\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:327
+#, c-format
+msgid "Could not load group %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:336
+#, c-format
+msgid "Updating group mapping entry failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:354
+msgid "Change a user's home directory"
+msgstr ""
+
+#: ../../utils/net_sam.c:355
+msgid ""
+"net sam set homedir\n"
+" Change a user's home directory"
+msgstr ""
+
+#: ../../utils/net_sam.c:362
+msgid "Change a user's profile path"
+msgstr ""
+
+#: ../../utils/net_sam.c:363
+msgid ""
+"net sam set profilepath\n"
+" Change a user's profile path"
+msgstr ""
+
+#: ../../utils/net_sam.c:370
+msgid "Change a users or groups description"
+msgstr ""
+
+#: ../../utils/net_sam.c:371
+msgid ""
+"net sam set comment\n"
+" Change a users or groups description"
+msgstr ""
+
+#: ../../utils/net_sam.c:378
+msgid "Change a user's full name"
+msgstr ""
+
+#: ../../utils/net_sam.c:379
+msgid ""
+"net sam set fullname\n"
+" Change a user's full name"
+msgstr ""
+
+#: ../../utils/net_sam.c:386
+msgid "Change a user's logon script"
+msgstr ""
+
+#: ../../utils/net_sam.c:387
+msgid ""
+"net sam set logonscript\n"
+" Change a user's logon script"
+msgstr ""
+
+#: ../../utils/net_sam.c:394
+msgid "Change a user's home drive"
+msgstr ""
+
+#: ../../utils/net_sam.c:395
+msgid ""
+"net sam set homedrive\n"
+" Change a user's home drive"
+msgstr ""
+
+#: ../../utils/net_sam.c:402
+msgid "Change a user's allowed workstations"
+msgstr ""
+
+#: ../../utils/net_sam.c:403
+msgid ""
+"net sam set workstations\n"
+" Change a user's allowed workstations"
+msgstr ""
+
+#: ../../utils/net_sam.c:410
+msgid "Disable/Enable a user"
+msgstr ""
+
+#: ../../utils/net_sam.c:411
+msgid ""
+"net sam set disable\n"
+" Disable/Enable a user"
+msgstr ""
+
+#: ../../utils/net_sam.c:418
+msgid "Disable/Enable the password not required flag"
+msgstr ""
+
+#: ../../utils/net_sam.c:419
+msgid ""
+"net sam set pwnotreq\n"
+" Disable/Enable the password not required flag"
+msgstr ""
+
+#: ../../utils/net_sam.c:426
+msgid "Disable/Enable a user's lockout flag"
+msgstr ""
+
+#: ../../utils/net_sam.c:427
+msgid ""
+"net sam set autolock\n"
+" Disable/Enable a user's lockout flag"
+msgstr ""
+
+#: ../../utils/net_sam.c:434
+msgid "Disable/Enable whether a user's pw does not expire"
+msgstr ""
+
+#: ../../utils/net_sam.c:436
+msgid ""
+"net sam set pwnoexp\n"
+" Disable/Enable whether a user's pw does not expire"
+msgstr ""
+
+#: ../../utils/net_sam.c:444
+msgid "Force users password must change at next logon"
+msgstr ""
+
+#: ../../utils/net_sam.c:445
+msgid ""
+"net sam set pwdmustchangenow\n"
+" Force users password must change at next logon"
+msgstr ""
+
+#: ../../utils/net_sam.c:469
+msgid "net sam policy set \"<account policy>\" <value>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:484
+#, c-format
+msgid "Unable to set policy \"%s\"! Invalid value \"%s\".\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:496
+#, c-format
+msgid ""
+"No account policy \"%s\"!\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:497 ../../utils/net_sam.c:550 ../../utils/net_sam.c:590
+msgid "Valid account policies are:\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:508 ../../utils/net_sam.c:561
+#, c-format
+msgid "Valid account policy, but unable to fetch value!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:511
+#, c-format
+msgid "Account policy \"%s\" value was: %d\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:516
+msgid "Valid account policy, but unable to set value!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:520
+#, c-format
+msgid "Account policy \"%s\" value is now: %d\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:536
+msgid "net sam policy show \"<account policy>\"\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:548
+msgid "No account policy by that name!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:566
+#, c-format
+msgid "Account policy \"%s\" description: %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:568
+#, c-format
+msgid "Account policy \"%s\" value is: %d\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:584 ../../utils/net_sam.c:607
+msgid "List account policies"
+msgstr ""
+
+#: ../../utils/net_sam.c:608
+msgid ""
+"net sam policy list\n"
+" List account policies"
+msgstr ""
+
+#: ../../utils/net_sam.c:615
+msgid "Show account policies"
+msgstr ""
+
+#: ../../utils/net_sam.c:616
+msgid ""
+"net sam policy show\n"
+" Show account policies"
+msgstr ""
+
+#: ../../utils/net_sam.c:623
+msgid "Change account policies"
+msgstr "Konto-Regeln ändern"
+
+#: ../../utils/net_sam.c:624
+msgid ""
+"net sam policy set\n"
+" Change account policies"
+msgstr ""
+
+#: ../../utils/net_sam.c:643
+msgid "net sam rights list [privilege name]\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:665
+#, c-format
+msgid "Could not list rights: %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:700
+msgid "net sam rights grant <name> <rights> ...\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:712 ../../utils/net_sam.c:752
+#, c-format
+msgid "%s unknown\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:717
+msgid "Could not grant privilege\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:721
+#, c-format
+msgid "Granted %s to %s\\%s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:739
+msgid "net sam rights revoke <name> <rights>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:757
+msgid "Could not revoke privilege\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:761
+#, c-format
+msgid "Revoked %s from %s\\%s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:774
+msgid "List possible user rights"
+msgstr ""
+
+#: ../../utils/net_sam.c:775
+msgid ""
+"net sam rights list\n"
+" List possible user rights"
+msgstr ""
+
+#: ../../utils/net_sam.c:782
+msgid "Grant right(s)"
+msgstr ""
+
+#: ../../utils/net_sam.c:783
+msgid ""
+"net sam rights grant\n"
+" Grant right(s)"
+msgstr ""
+
+#: ../../utils/net_sam.c:790
+msgid "Revoke right(s)"
+msgstr ""
+
+#: ../../utils/net_sam.c:791
+msgid ""
+"net sam rights revoke\n"
+" Revoke right(s)"
+msgstr ""
+
+#: ../../utils/net_sam.c:867
+msgid "net sam mapunixgroup <name>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:873 ../../utils/net_sam.c:1185 ../../utils/net_sam.c:1274 ../../utils/net_sam.c:1348
+#, c-format
+msgid "Could not find group %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:880
+#, c-format
+msgid "Mapping group %s failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:885
+#, c-format
+msgid "Mapped unix group %s to SID %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:931
+msgid "net sam unmapunixgroup <name>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:937
+#, c-format
+msgid "Could not find mapping for group %s.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:945
+#, c-format
+msgid "Unmapping group %s failed with %s.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:950
+#, c-format
+msgid "Unmapped unix group %s.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:968
+msgid "net sam createdomaingroup <name>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:975 ../../utils/net_sam.c:1057 ../../utils/net_sam.c:1155
+#, c-format
+msgid "Creating %s failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:980
+#, c-format
+msgid "Created domain group %s with RID %d\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1001 ../../utils/net_sam.c:1081
+msgid "net sam deletelocalgroup <name>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1007 ../../utils/net_sam.c:1087
+#, c-format
+msgid "Could not find %s.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1012
+#, c-format
+msgid "%s is a %s, not a domain group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1022
+#, c-format
+msgid "Deleting domain group %s failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1027
+#, c-format
+msgid "Deleted domain group %s.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1044
+msgid "net sam createlocalgroup <name>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1049
+msgid "winbind seems not to run. createlocalgroup only works when winbind runs.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1062
+#, c-format
+msgid "Created local group %s with RID %d\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1092
+#, c-format
+msgid "%s is a %s, not a local group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1100
+#, c-format
+msgid "Deleting local group %s failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1105
+#, c-format
+msgid "Deleted local group %s.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1125
+msgid "net sam createbuiltingroup <name>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1130
+msgid "winbind seems not to run. createbuiltingroup only works when winbind runs.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1143
+#, c-format
+msgid "%s is not a BUILTIN group\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1148
+#, c-format
+msgid "Failed to get RID for %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1160
+#, c-format
+msgid "Created BUILTIN group %s with RID %d\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1179
+msgid "net sam addmem <group> <member>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1197 ../../utils/net_sam.c:1281
+#, c-format
+msgid "Could not find member %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1205
+#, c-format
+msgid "Could not resolve SID %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1214
+#, c-format
+msgid ""
+"%s is a local group, only users and domain groups can be added.\n"
+"%s is a %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1223
+#, c-format
+msgid "Adding local group member failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1235
+#, c-format
+msgid "Adding domain group member failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1240
+#, c-format
+msgid "Can only add members to local groups so far, %s is a %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1246
+#, c-format
+msgid "Added %s\\%s to %s\\%s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1268
+msgid "net sam delmem <group> <member>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1292
+#, c-format
+msgid "Deleting local group member failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1304
+#, c-format
+msgid "Deleting domain group member failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1309
+#, c-format
+msgid "Can only delete members from local groups so far, %s is a %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1316
+#, c-format
+msgid "Deleted %s\\%s from %s\\%s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1319
+#, c-format
+msgid "Deleted %s from %s\\%s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1342
+msgid "net sam listmem <group>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1357 ../../utils/net_sam.c:1367
+#, c-format
+msgid "Listing group members failed with %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1385
+#, c-format
+msgid ""
+"Can only list local group members so far.\n"
+"%s is a %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1390
+#, c-format
+msgid "%s\\%s has %u members\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1417
+#, c-format
+msgid "net sam list %s [verbose]\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1422
+msgid "Could not start search\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1497
+msgid "List SAM users"
+msgstr ""
+
+#: ../../utils/net_sam.c:1498
+msgid ""
+"net sam list users\n"
+" List SAM users"
+msgstr ""
+
+#: ../../utils/net_sam.c:1505
+msgid "List SAM groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:1506
+msgid ""
+"net sam list groups\n"
+" List SAM groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:1513
+msgid "List SAM local groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:1514
+msgid ""
+"net sam list localgroups\n"
+" List SAM local groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:1521
+msgid "List builtin groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:1522
+msgid ""
+"net sam list builtin\n"
+" List builtin groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:1529
+msgid "List domain member workstations"
+msgstr ""
+
+#: ../../utils/net_sam.c:1530
+msgid ""
+"net sam list workstations\n"
+" List domain member workstations"
+msgstr ""
+
+#: ../../utils/net_sam.c:1552
+msgid "net sam show <name>\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1562
+#, c-format
+msgid "%s\\%s is a %s with SID %s\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1594
+msgid "Init an LDAP tree with default users/groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:1600 ../../utils/net_sam.c:1729 ../../utils/net_sam.c:1761 ../../utils/net_sam.c:1805 ../../utils/net_sam.c:1843 ../../utils/net_sam.c:1881 ../../utils/net_sam.c:1892 ../../utils/net_sam.c:1961
+msgid "Out of Memory!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1620
+msgid "Provisioning works only with ldapsam backend\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1627
+msgid "Provisioning works only if ldapsam:trusted and ldapsam:editposix are enabled.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1633
+msgid "winbind seems not to run. Provisioning LDAP only works when winbind runs.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1639
+msgid "Unable to connect to the LDAP server.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1643
+msgid "Checking for Domain Users group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1656
+msgid "Adding the Domain Users group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1660
+msgid "Unable to allocate a new gid to create Domain Users group!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1690
+msgid "Failed to add Domain Users group to ldap directory\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1695 ../../utils/net_sam.c:1752 ../../utils/net_sam.c:1836 ../../utils/net_sam.c:1925 ../../utils/net_sam.c:1939 ../../utils/net_sam.c:1986
+msgid "found!\n"
+msgstr "gefunden!\n"
+
+#: ../../utils/net_sam.c:1700
+msgid "Checking for Domain Admins group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1713
+msgid "Adding the Domain Admins group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1717
+msgid "Unable to allocate a new gid to create Domain Admins group!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1747
+msgid "Failed to add Domain Admins group to ldap directory\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1757
+msgid "Check for Administrator account.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1777
+msgid "Adding the Administrator user.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1781
+msgid "Can't create Administrator user, Domain Admins group not available!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1787
+msgid "Unable to allocate a new uid to create the Administrator user!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1832
+msgid "Failed to add Administrator user to ldap directory\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1839
+msgid "Checking for Guest user.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1855
+msgid "Adding the Guest user.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1862
+msgid "Can't create Guest user, Domain Users group not available!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1873
+msgid "Unable to allocate a new uid to create the Guest user!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1921
+msgid "Failed to add Guest user to ldap directory\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1928
+msgid "Checking Guest's group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1933
+msgid ""
+"Failed to find just created Guest account!\n"
+" Is nss properly configured?!\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1952
+msgid "Adding the Domain Guests group.\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:1982
+msgid "Failed to add Domain Guests group to ldap directory\n"
+msgstr ""
+
+#: ../../utils/net_sam.c:2011
+msgid "Create a new BUILTIN group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2012
+msgid ""
+"net sam createbuiltingroup\n"
+" Create a new BUILTIN group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2019
+msgid "Create a new local group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2020
+msgid ""
+"net sam createlocalgroup\n"
+" Create a new local group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2027
+msgid "Create a new group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2028
+msgid ""
+"net sam createdomaingroup\n"
+" Create a new group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2035
+msgid "Delete an existing local group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2036
+msgid ""
+"net sam deletelocalgroup\n"
+" Delete an existing local group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2043
+msgid "Delete a domain group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2044
+msgid ""
+"net sam deletedomaingroup\n"
+" Delete a group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2051
+msgid "Map a unix group to a domain group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2052
+msgid ""
+"net sam mapunixgroup\n"
+" Map a unix group to a domain group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2059
+msgid "Remove a group mapping of an unix group to a domain group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2061
+msgid ""
+"net sam unmapunixgroup\n"
+" Remove a group mapping of an unix group to a domain group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2069
+msgid "Add a member to a group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2070
+msgid ""
+"net sam addmem\n"
+" Add a member to a group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2077
+msgid "Delete a member from a group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2078
+msgid ""
+"net sam delmem\n"
+" Delete a member from a group"
+msgstr ""
+
+#: ../../utils/net_sam.c:2086
+msgid ""
+"net sam listmem\n"
+" List group members"
+msgstr ""
+
+#: ../../utils/net_sam.c:2093
+msgid "List users, groups and local groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:2094
+msgid ""
+"net sam list\n"
+" List users, groups and local groups"
+msgstr ""
+
+#: ../../utils/net_sam.c:2101
+msgid "Show details of a SAM entry"
+msgstr ""
+
+#: ../../utils/net_sam.c:2102
+msgid ""
+"net sam show\n"
+" Show details of a SAM entry"
+msgstr ""
+
+#: ../../utils/net_sam.c:2109
+msgid "Set details of a SAM account"
+msgstr ""
+
+#: ../../utils/net_sam.c:2110
+msgid ""
+"net sam set\n"
+" Set details of a SAM account"
+msgstr ""
+
+#: ../../utils/net_sam.c:2117
+msgid "Set account policies"
+msgstr ""
+
+#: ../../utils/net_sam.c:2118
+msgid ""
+"net sam policy\n"
+" Set account policies"
+msgstr ""
+
+#: ../../utils/net_sam.c:2125
+msgid "Manipulate user privileges"
+msgstr ""
+
+#: ../../utils/net_sam.c:2126
+msgid ""
+"net sam rights\n"
+" Manipulate user privileges"
+msgstr ""
+
+#: ../../utils/net_sam.c:2134
+msgid "Provision a clean user database"
+msgstr ""
+
+#: ../../utils/net_sam.c:2135
+msgid ""
+"net sam privison\n"
+" Provision a clear user database"
+msgstr ""
+
+#: ../../utils/net_sam.c:2143
+msgid "You are not root, most things won't work\n"
+msgstr ""
+
+#: ../../utils/net_share.c:27
+msgid ""
+"\n"
+"net [<method>] share [misc. options] [targets] \n"
+"\tenumerates all exported resources (network shares) on target server\n"
+"\n"
+"net [<method>] share ADD <name=serverpath> [misc. options] [targets]\n"
+"\tadds a share from a server (makes the export active)\n"
+"\n"
+"net [<method>] share DELETE <sharename> [misc. options] [targets]\n"
+"\tdeletes a share from a server (makes the export inactive)\n"
+"\n"
+"net [<method>] share ALLOWEDUSERS [<filename>] [misc. options] [targets]\n"
+"\tshows a list of all shares together with all users allowed to\n"
+"\taccess them. This needs the output of 'net usersidlist' on\n"
+"\tstdin or in <filename>.\n"
+"\n"
+"net [<method>] share MIGRATE FILES <sharename> [misc. options] [targets]\n"
+"\tMigrates files from remote to local server\n"
+"\n"
+"net [<method>] share MIGRATE SHARES <sharename> [misc. options] [targets]\n"
+"\tMigrates shares from remote to local server\n"
+"\n"
+"net [<method>] share MIGRATE SECURITY <sharename> [misc. options] [targets]\n"
+"\tMigrates share-ACLs from remote to local server\n"
+"\n"
+"net [<method>] share MIGRATE ALL <sharename> [misc. options] [targets]\n"
+"\tMigrates shares (including directories, files) from remote\n"
+"\tto local server\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_share.c:52
+msgid ""
+"\t-C or --comment=<comment>\tdescriptive comment (for add only)\n"
+"\t-M or --maxusers=<num>\t\tmax users allowed for share\n"
+"\t --acls\t\t\tcopies ACLs as well\n"
+"\t --attrs\t\t\tcopies DOS Attributes as well\n"
+"\t --timestamps\t\tpreserve timestamps while copying files\n"
+"\t --destination\t\tmigration target server (default: localhost)\n"
+"\t-e or --exclude\t\t\tlist of shares to be excluded from mirroring\n"
+"\t-v or --verbose\t\t\tgive verbose output\n"
+msgstr ""
+
+#: ../../utils/net_status.c:24
+msgid " net status sessions [parseable] Show list of open sessions\n"
+msgstr ""
+
+#: ../../utils/net_status.c:26
+msgid " net status shares [parseable] Show list of open shares\n"
+msgstr ""
+
+#: ../../utils/net_status.c:70
+msgid ""
+"Display open user sessions.\n"
+" If parseable is specified, output is machine-readable."
+msgstr ""
+
+#: ../../utils/net_status.c:85
+msgid ""
+"PID Username Group Machine \n"
+"-------------------------------------------------------------------\n"
+msgstr ""
+
+#: ../../utils/net_status.c:94 ../../utils/net_status.c:200
+#, c-format
+msgid "%s not initialised\n"
+msgstr ""
+
+#: ../../utils/net_status.c:222
+msgid ""
+"Display open user shares.\n"
+" If parseable is specified, output is machine-readable."
+msgstr ""
+
+#: ../../utils/net_status.c:230
+msgid ""
+"\n"
+"Service pid machine Connected at\n"
+"-------------------------------------------------------\n"
+msgstr ""
+
+#: ../../utils/net_status.c:254
+msgid "Show list of open sessions"
+msgstr ""
+
+#: ../../utils/net_status.c:255
+msgid ""
+"net status sessions [parseable]\n"
+" If parseable is specified, output is presented in a machine-parseable fashion."
+msgstr ""
+
+#: ../../utils/net_status.c:263
+msgid "Show list of open shares"
+msgstr ""
+
+#: ../../utils/net_status.c:264
+msgid ""
+"net status shares [parseable]\n"
+" If parseable is specified, output is presented in a machine-parseable fashion."
+msgstr ""
+
+#: ../../utils/net_time.c:39
+#, c-format
+msgid "Can't contact server %s. Error %s\n"
+msgstr ""
+
+#: ../../utils/net_time.c:52
+#, c-format
+msgid "Session request failed\n"
+msgstr ""
+
+#: ../../utils/net_time.c:57
+#, c-format
+msgid "Protocol negotiation failed: %s\n"
+msgstr ""
+
+#: ../../utils/net_time.c:97
+msgid ""
+"net time\n"
+"\tdisplays time on a server\n"
+"\n"
+"net time system\n"
+"\tdisplays time on a server in a format ready for /bin/date\n"
+"\n"
+"net time set\n"
+"\truns /bin/date with the time from the server\n"
+"\n"
+"net time zone\n"
+"\tdisplays the timezone in hours from GMT on the remote computer\n"
+"\n"
+"\n"
+msgstr ""
+"net time\n"
+"\tZeigt die Zeit eines Servers an\n"
+"\n"
+"net time system\n"
+"\tZeigt die Zeit eines Servers im /bin/date Format an\n"
+"\n"
+"net time set\n"
+"\tFührt /bin/date mit der Serverzeit aus\n"
+"\n"
+"net time zone\n"
+"\tZeigt die Zeitzone in Stunden zur GMT auf dem entfernten Computer\n"
+"\n"
+
+#: ../../utils/net_time.c:123
+#, c-format
+msgid "%s failed. Error was (%s)\n"
+msgstr ""
+
+#: ../../utils/net_time.c:140
+msgid "Output remote time server time in a format ready for /bin/date"
+msgstr ""
+
+#: ../../utils/net_time.c:166
+msgid "Display the remote time server's offset to UTC"
+msgstr ""
+
+#: ../../utils/net_time.c:196
+msgid "Display time ready for /bin/date"
+msgstr ""
+
+#: ../../utils/net_time.c:197
+msgid ""
+"net time system\n"
+" Display time ready for /bin/date"
+msgstr ""
+
+#: ../../utils/net_time.c:204
+msgid "Set the system time from time server"
+msgstr ""
+
+#: ../../utils/net_time.c:205
+msgid ""
+"net time set\n"
+" Set the system time from time server"
+msgstr ""
+
+#: ../../utils/net_time.c:212
+msgid "Display timezone offset from UTC"
+msgstr ""
+
+#: ../../utils/net_time.c:213
+msgid ""
+"net time zone\n"
+" Display timezone offset from UTC"
+msgstr ""
+
+#: ../../utils/net_time.c:228
+msgid "Display the remote time server's time"
+msgstr ""
+
+#: ../../utils/net_time.c:235
+msgid "Could not locate a time server. Try specifying a target host.\n"
+msgstr ""
+
+#: ../../utils/net_user.c:27
+msgid ""
+"\n"
+"net [<method>] user [misc. options] [targets]\n"
+"\tList users\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_user.c:29
+msgid ""
+"net [<method>] user DELETE <name> [misc. options] [targets]\n"
+"\tDelete specified user\n"
+msgstr ""
+
+#: ../../utils/net_user.c:31
+msgid ""
+"\n"
+"net [<method>] user INFO <name> [misc. options] [targets]\n"
+"\tList the domain groups of the specified user\n"
+msgstr ""
+
+#: ../../utils/net_user.c:33
+msgid ""
+"\n"
+"net [<method>] user ADD <name> [password] [-c container] [-F user flags] [misc. options] [targets]\n"
+"\tAdd specified user\n"
+msgstr ""
+
+#: ../../utils/net_user.c:36
+msgid ""
+"\n"
+"net [<method>] user RENAME <oldusername> <newusername> [targets]\n"
+"\tRename specified user\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:29
+msgid "Malformed usershare file"
+msgstr ""
+
+#: ../../utils/net_usershare.c:30
+msgid "Bad version number"
+msgstr ""
+
+#: ../../utils/net_usershare.c:31
+msgid "Malformed path entry"
+msgstr ""
+
+#: ../../utils/net_usershare.c:32
+msgid "Malformed comment entryfile"
+msgstr ""
+
+#: ../../utils/net_usershare.c:33
+msgid "Malformed acl definition"
+msgstr ""
+
+#: ../../utils/net_usershare.c:34
+msgid "Acl parse error"
+msgstr ""
+
+#: ../../utils/net_usershare.c:35
+msgid "Path not absolute"
+msgstr ""
+
+#: ../../utils/net_usershare.c:36
+msgid "Path is denied"
+msgstr ""
+
+#: ../../utils/net_usershare.c:37
+msgid "Path not allowed"
+msgstr ""
+
+#: ../../utils/net_usershare.c:38
+msgid "Path is not a directory"
+msgstr ""
+
+#: ../../utils/net_usershare.c:39
+msgid "System error"
+msgstr "System Fehler"
+
+#: ../../utils/net_usershare.c:55
+#, c-format
+msgid "Usershare error code (0x%x)"
+msgstr ""
+
+#: ../../utils/net_usershare.c:67
+#, c-format
+msgid ""
+"net usershare add [-l|--long] <sharename> <path> [<comment>] [<acl>] [<guest_ok=[y|n]>]\n"
+"\tAdds the specified share name for this user.\n"
+"\t<sharename> is the new share name.\n"
+"\t<path> is the path on the filesystem to export.\n"
+"\t<comment> is the optional comment for the new share.\n"
+"\t<acl> is an optional share acl in the format \"DOMAIN%cname:X,DOMAIN%cname:X,....\"\n"
+"\t<guest_ok=y> if present sets \"guest ok = yes\" on this usershare.\n"
+"\t\t\"X\" represents a permission and can be any one of the characters f, r or d\n"
+"\t\twhere \"f\" means full control, \"r\" means read-only, \"d\" means deny access.\n"
+"\t\tname may be a domain user or group. For local users use the local server name instead of \"DOMAIN\"\n"
+"\t\tThe default acl is \"Everyone:r\" which allows everyone read-only access.\n"
+"\tAdd -l or --long to print the info on the newly added share.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:87
+msgid ""
+"net usershare delete <sharename>\n"
+"\tdeletes the specified share name for this user.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:95
+msgid ""
+"net usershare info [-l|--long] [wildcard sharename]\n"
+"\tPrints out the path, comment and acl elements of shares that match the wildcard.\n"
+"\tBy default only gives info on shares owned by the current user\n"
+"\tAdd -l or --long to apply this to all shares\n"
+"\tOmit the sharename or use a wildcard of '*' to see all shares\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:106
+msgid ""
+"net usershare list [-l|--long] [wildcard sharename]\n"
+"\tLists the names of all shares that match the wildcard.\n"
+"\tBy default only lists shares owned by the current user\n"
+"\tAdd -l or --long to apply this to all shares\n"
+"\tOmit the sharename or use a wildcard of '*' to see all shares\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:116
+msgid ""
+"net usershare add <sharename> <path> [<comment>] [<acl>] [<guest_ok=[y|n]>] to add or change a user defined share.\n"
+"net usershare delete <sharename> to delete a user defined share.\n"
+"net usershare info [-l|--long] [wildcard sharename] to print info about a user defined share.\n"
+"net usershare list [-l|--long] [wildcard sharename] to list user defined shares.\n"
+"net usershare help\n"
+"\n"
+"Type \"net usershare help <option>\" to get more information on that option\n"
+"\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:158
+msgid "strlower_talloc failed\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:163
+#, c-format
+msgid "net usershare delete: share name %s contains invalid characters (any of %s)\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:180
+#, c-format
+msgid "net usershare delete: unable to remove usershare %s. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:218
+#, c-format
+msgid "get_share_list: cannot open usershare directory %s. Error %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:238
+#, c-format
+msgid "get_share_list: ignoring bad share name %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:253
+#, c-format
+msgid "get_share_list: can't lstat file %s. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:261
+#, c-format
+msgid "get_share_list: file %s is not a regular file. Ignoring.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:362
+#, c-format
+msgid "info_fn: unable to open %s. %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:370
+#, c-format
+msgid "info_fn: can't fstat file %s. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:378
+#, c-format
+msgid "info_fn: file %s is not a regular file. Ignoring.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:402
+#, c-format
+msgid "info_fn: file %s is not a well formed usershare file.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:405
+#, c-format
+msgid "info_fn: Error was %s.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:555
+#, c-format
+msgid "count_num_usershares: cannot open usershare directory %s. Error %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:575
+#, c-format
+msgid "count_num_usershares: ignoring bad share name %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:590
+#, c-format
+msgid "count_num_usershares: can't lstat file %s. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:598
+#, c-format
+msgid "count_num_usershares: file %s is not a regular file. Ignoring.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:694
+#, c-format
+msgid "net usershare add: maximum number of allowed usershares (%d) reached\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:702
+#, c-format
+msgid "net usershare add: share name %s contains invalid characters (any of %s)\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:712
+#, c-format
+msgid "net usershare add: share name %s is already a valid system user name\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:744
+#, c-format
+msgid "net usershare add: path %s is not an absolute path.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:754
+#, c-format
+msgid "net usershare add: cannot stat path %s to ensure this is a directory. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:763
+#, c-format
+msgid "net usershare add: path %s is not a directory.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:773
+#, c-format
+msgid ""
+"net usershare add: cannot share path %s as we are restricted to only sharing directories we own.\n"
+"\tAsk the administrator to add the line \"usershare owner only = false\" \n"
+"\tto the [global] section of the smb.conf to allow this.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:806
+#, c-format
+msgid "net usershare add: malformed acl %s (missing ':').\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:822
+#, c-format
+msgid "net usershare add: malformed acl %s (access control must be 'r', 'f', or 'd')\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:832
+#, c-format
+msgid "net usershare add: malformed terminating character for acl %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:841
+msgid "talloc_strndup failed\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:850
+#, c-format
+msgid "net usershare add: cannot convert name \"%s\" to a SID. %s."
+msgstr ""
+
+#: ../../utils/net_usershare.c:855
+msgid " Maybe smbd is not running.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:876
+msgid "net usershare add: guest_ok=y requested but the \"usershare allow guests\" parameter is not enabled by this server.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:888
+#, c-format
+msgid "net usershare add: cannot create tmp file %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:897
+#, c-format
+msgid "net usershare add: cannot lstat tmp file %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:906
+#, c-format
+msgid "net usershare add: cannot fstat tmp file %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:914
+#, c-format
+msgid "net usershare add: tmp file %s is not a regular file ?\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:923
+#, c-format
+msgid "net usershare add: failed to fchmod tmp file %s to 0644n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:939
+#, c-format
+msgid "net usershare add: failed to write %u bytes to file %s. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:951
+#, c-format
+msgid "net usershare add: failed to add share %s. Error was %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1046
+msgid "Add/modify user defined share"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1047
+msgid ""
+"net usershare add\n"
+" Add/modify user defined share"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1054
+msgid "Delete user defined share"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1055
+msgid ""
+"net usershare delete\n"
+" Delete user defined share"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1062
+msgid "Display information about a user defined share"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1063
+msgid ""
+"net usershare info\n"
+" Display information about a user defined share"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1070
+msgid "List user defined shares"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1071
+msgid ""
+"net usershare list\n"
+" List user defined shares"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1079
+msgid "net usershare: usershares are currently disabled\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1088
+#, c-format
+msgid "net usershare: cannot open usershare directory %s. Error %s\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1093
+msgid "You do not have permission to create a usershare. Ask your administrator to grant you permissions to create a share.\n"
+msgstr ""
+
+#: ../../utils/net_usershare.c:1098
+msgid "Please ask your system administrator to enable user sharing.\n"
+msgstr ""
+
+#: ../../utils/net_util.c:118
+#, c-format
+msgid "Could not connect to server %s\n"
+msgstr "Verbindung mit Server %s gescheitert\n"
+
+#: ../../utils/net_util.c:126
+msgid "The username or password was not correct.\n"
+msgstr "Benutzername oder Passwort nicht korrekt.\n"
+
+#: ../../utils/net_util.c:131
+msgid "The account was locked out.\n"
+msgstr ""
+
+#: ../../utils/net_util.c:135
+msgid "The account was disabled.\n"
+msgstr ""
+
+#: ../../utils/net_util.c:146
+msgid "Encryption required and server that doesn't support UNIX extensions - failing connect\n"
+msgstr ""
+
+#: ../../utils/net_util.c:150
+msgid "Encryption required and can't get UNIX CIFS extensions version from server.\n"
+msgstr ""
+
+#: ../../utils/net_util.c:154
+#, c-format
+msgid "Encryption required and share %s doesn't support encryption.\n"
+msgstr ""
+
+#: ../../utils/net_util.c:158
+#, c-format
+msgid "Encryption required and setup failed with error %s.\n"
+msgstr ""
+
+#: ../../utils/net_util.c:345 ../../utils/net_util.c:367
+msgid "ERROR: Unable to open secrets database\n"
+msgstr ""
+
+#: ../../utils/net_util.c:501
+#, c-format
+msgid "Unable to find a suitable server for domain %s\n"
+msgstr ""
+
+#: ../../utils/net_util.c:526
+#, c-format
+msgid "Connection failed: %s\n"
+msgstr "Verbindung fehlgeschlagen: %s\n"
+
+#: ../../utils/net_util.c:560
+#, c-format
+msgid "Enter %s's password:"
+msgstr "Bitte Passwort für %s eingeben: "
+
+#: ../../utils/net_util.c:583
+#, c-format
+msgid "Invalid command: %s %s\n"
+msgstr "Ungültiges Kommando: %s %s\n"
+
+#: ../../utils/net_util.c:609
+msgid "Disk"
+msgstr "Festplatte"
+
+#: ../../utils/net_util.c:610
+msgid "Print"
+msgstr "Drucker"
+
+#: ../../utils/net_util.c:611
+msgid "Dev"
+msgstr "Gerät"
+
+#: ../../utils/net_util.c:612
+msgid "IPC"
+msgstr "IPC"
+
+#~ msgid "Usage\n"
+#~ msgstr "Verwendung\n"
+
+#~ msgid "usage: net setlocalsid S-1-5-21-x-y-z\n"
+#~ msgstr "Aufruf: net setlocalsid S-1-5-21-x-y-z\n"
+
+#~ msgid "usage: net setdomainsid S-1-5-21-x-y-z\n"
+#~ msgstr "Aufruf: net setdomainsid S-1-5-21-x-y-z\n"
+
+#~ msgid "usage: net getdomainsid\n"
+#~ msgstr "Aufruf: net getdomainsid\n"
+
+#~ msgid "usage: net maxrid\n"
+#~ msgstr "Aufruf: net maxrid\n"
+
+#~ msgid "usage: 'net afs key <keyfile> cell'\n"
+#~ msgstr "Aufruf: 'net afs key <keyfile> cell'\n"
+
+#~ msgid "Usage: net afs impersonate <user> <cell>\n"
+#~ msgstr "Aufruf: net afs impersonate <user> <cell>\n"
+
+#~ msgid "USAGE: net conf list\n"
+#~ msgstr "Aufruf: net conf list\n"
+
+#~ msgid "USAGE: net conf listshares\n"
+#~ msgstr "Aufruf: net conf listshares\n"
+
+#~ msgid "USAGE: net conf drop\n"
+#~ msgstr "Aufruf: net conf drop\n"
+
+#~ msgid "Not Implemented yet\n"
+#~ msgstr "Noch nicht implementiert\n"
diff --git a/source3/locale/net/genmsg b/source3/locale/net/genmsg
new file mode 100755
index 0000000..f9a0e27
--- /dev/null
+++ b/source3/locale/net/genmsg
@@ -0,0 +1,61 @@
+#!/bin/sh
+# Copyright (C) 2003 TAKAHASHI Motonobu <monyo@samba.org>
+# Copyright (C) 2009 Kai Blin <kai@samba.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+#
+add_basedir_to_filelist()
+{
+ BASEDIR="$1"
+ shift
+ FILELIST="$@"
+ FULL_LIST=""
+ for file in ${FILELIST}; do
+ FULL_LIST="${FULL_LIST} $BASEDIR/${file}"
+ done
+ echo ${FULL_LIST}
+}
+
+FILES=$(add_basedir_to_filelist ../../utils net.c net_ads.c net_ads_gpo.c \
+ net_afs.c net_cache.c net_conf.c net_dom.c net_eventlog.c net_file.c \
+ net_group.c net_groupmap.c net_help.c net_help_common.c net_idmap.c \
+ net_join.c net_lookup.c net_rap.c net_registry.c \
+ net_registry_util.c net_rpc.c net_rpc_audit.c net_rpc_join.c \
+ net_rpc_printer.c net_rpc_registry.c net_rpc_rights.c net_rpc_samsync.c \
+ net_rpc_service.c net_rpc_sh_acct.c net_rpc_shell.c net_sam.c \
+ net_share.c net_status.c net_time.c net_user.c net_usershare.c net_util.c)
+
+LANGS="af ar bg bn bs ca cs cy da de el en_GB en_US es et fi fr gl gu he hi hr
+ hu id it ja ka km ko lo lt mk mr nb nl pa pl pt_BR pt ro ru si sk sl sr
+ sv ta th tr uk vi wa xh zh_CN zh_TW zu"
+XGETTEXT=xgettext
+MSGMERGE=msgmerge
+
+WIDTH=256
+
+${XGETTEXT} --default-domain="net" \
+ --add-comments \
+ --keyword=_ --keyword=N_ \
+ --width=${WIDTH} \
+ ${FILES}
+
+for lang in ${LANGS}; do
+ printf "%s " ${lang}
+ touch ${lang}.po
+ mv ${lang}.po ${lang}.po.old
+ ${MSGMERGE} --width=${WIDTH} ${lang}.po.old net.po -o ${lang}.po
+ rm -rf ${lang}.po.old
+done
+
+rm -rf net.po
diff --git a/source3/locale/pam_winbind/ar.po b/source3/locale/pam_winbind/ar.po
new file mode 100644
index 0000000..16aa191
--- /dev/null
+++ b/source3/locale/pam_winbind/ar.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-06 08:04\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "نجاح"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "جهاز التحكم في المجال الرئيسي غير متوفر"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "لم يتم العثور على أجهزة تحكم في المجال"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "لا توجد خوادم تسجيل الدخول"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "كلمة السر قصيرة جدًا"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "كلمة سر هذا المستخدم حديثة جدًا على التغيير"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "كلمة السر موجودة بالفعل في سجل كلمات السر"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "انتهت صلاحية كلمة السر الخاصة بك"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "يلزم تغيير كلمة السر الآن"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "غير مسموح لك بتسجيل الدخول من محطة العمل هذه"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "غير مسموح لك بتسجيل الدخول في هذا الوقت"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "انتهت صلاحية حسابك. الرجاء الاتصال بمسؤول النظام"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "تم تعطيل حسابك. الرجاء الاتصال بمسؤول النظام"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "تم قفل حسابك. الرجاء الاتصال بمسؤول النظام"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "حساب ثقة غير صالح"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "الوصول مرفوض"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "يلزم تغيير كلمة السر الآن"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "انتهت صلاحية كلمة السر الخاصة بك"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "انتهت صلاحية كلمة السر الخاصة بك"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "أيام"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "اليوم"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "تسجيل الدخول في فترة السماح. الرجاء تغيير كلمة السر الخاصة بك عند الاتصال بالإنترنت مرة أخرى."
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "تعذر الوصول إلى جهاز التحكم في المجال، قم باستخدام بيانات تعريف مؤقتة بدلاً من ذلك. ربما لا تتوفر مصادر الشبكة"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "كلمة السر الخاصة بك "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "يجب أن تكون %d من الأحرف على الأقل؛ "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "لا يمكنك تكرار أي من كلمات السر %dالسابقة الخاصة بك؛"
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "يجب أن تحتوي على حروف كبيرة أو أرقام أو علامات ترقيم؛ وألا تحتوي على اسم الحساب الخاص بك أو الاسم الكامل؛"
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "الرجاء كتابة كلمة سر مختلفة. قم بكتابة كلمة سر تتوافق مع هذه المتطلبات الموجودة في مربعي النص."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "كلمة السر لا تتوافق مع المتطلبات المعقدة"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "كلمة السر: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "تغيير كلمة السر لـ"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "كلمة سر NT (الحالية) "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "إدخال كلمة سر NT: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "إعادة كتابة كلمة سر NT الجديدة: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "الوصول مرفوض"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "يلزم تغيير كلمة السر الآن"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "كلمة السر قصيرة جدًا"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "كلمة السر قصيرة جدًا"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "كلمة السر قصيرة جدًا"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "كلمة السر: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "كلمة السر الخاصة بك "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "كلمة السر قصيرة جدًا"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "حساب ثقة غير صالح"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "انتهت صلاحية كلمة السر الخاصة بك"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "لم يتم العثور على أجهزة تحكم في المجال"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "لا توجد خوادم تسجيل الدخول"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "جهاز التحكم في المجال الرئيسي غير متوفر"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "حساب ثقة غير صالح"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "لا توجد خوادم تسجيل الدخول"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/cs.po b/source3/locale/pam_winbind/cs.po
new file mode 100644
index 0000000..dada4d7
--- /dev/null
+++ b/source3/locale/pam_winbind/cs.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-05 14:25\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "úspěch"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Žádný primární doménový kontrolér není dostupný."
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Nenalezeny žádné doménové kontroléry"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Žádné přihlašovací servery"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Heslo je příliš krátké"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Heslo tohoto uživatele nelze změnit, protože je příliš nové"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Heslo je již v historii hesel"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Vaše heslo vypršelo"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Je potřeba změnit Vaše heslo"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Nemáte práva pro přihlášení z této stanice."
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Nyní nemáte povolení k přihlášení."
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Platnost vašeho účtu vypršela. Kontaktujte svého správce systému."
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Váš účet je zablokován. Kontaktujte svého správce systému"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Váš účet byl uzamčen. Prosím kontaktujte svého správce systému."
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Neplatný účet vztahu důvěryhodnosti"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Přístup zamítnut"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Je potřeba změnit Vaše heslo"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Vaše heslo vypršelo"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Vaše heslo vypršelo"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "Dní"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "Den"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Výjimečně přidělený login. Změňte své heslo, jakmile budete znovu online"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Doménový kontrolér je nedostupný, místo toho použity přihlašovací údaje. Síťové zdroje jsou možná nedostupné"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Vaše heslo:"
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "musí mít nejméně %d znaků;"
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "nelze zopakovat žádné z vašich %d hesel;"
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "musí obsahovat velká písmena, číslice nebo speciální znaky;nesmí obsahovat název vašeho účtu nebo celé jméno;"
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Prosím napište jiné heslo. Napište heslo, které bude splňovat požadavky uvedené v obou textových rámečcích."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Heslo nesplňuje požadavky na složitost."
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Heslo:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Měním heslo pro"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "NT heslo (aktuální):"
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Zadejte nové NT heslo:"
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Zopakujte nové NT heslo: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Přístup zamítnut"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Je potřeba změnit Vaše heslo"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Heslo je příliš krátké"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Heslo je příliš krátké"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Heslo je příliš krátké"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Heslo:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Vaše heslo:"
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Heslo je příliš krátké"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Neplatný účet vztahu důvěryhodnosti"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Vaše heslo vypršelo"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Nenalezeny žádné doménové kontroléry"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Žádné přihlašovací servery"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Žádný primární doménový kontrolér není dostupný."
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Neplatný účet vztahu důvěryhodnosti"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Žádné přihlašovací servery"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/da.po b/source3/locale/pam_winbind/da.po
new file mode 100644
index 0000000..4f4f651
--- /dev/null
+++ b/source3/locale/pam_winbind/da.po
@@ -0,0 +1,561 @@
+# Martin Schlander <mschlander@opensuse.org>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: @PACKAGE@\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2008-11-23 15:44+0100\n"
+"Last-Translator: Martin Schlander <mschlander@opensuse.org>\n"
+"Language-Team: Danish <opensuse-translation@opensuse.org>\n"
+"Language: da\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Lokalize 0.2\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Gennemført"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Ingen primær domæne-controller tilgængelig"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Ingen domæne-controllere fundet"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Ingen log på-servere"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "For kort adgangskode"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Denne brugers adgangskode er for ny til at ændre"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Adgangskoden er allerede i adgangskode-historikken"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Din adgangskode er udløbet"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Du skal ændre din adgangskode nu"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Du har ikke tilladelse til at logge på fra denne arbejdsstation"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Du har ikke tilladelse til at logge på, på nuværende tidspunkt"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Din konto er udløbet. Kontakt din systemadministrator"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Din konto er deaktiveret. Kontakt din systemadministrator"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Din konto er blevet låst. Kontakt din systemadministrator"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Ugyldig tillidskonto"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Adgang nægtet"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Du skal ændre din adgangskode nu"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Din adgangskode er udløbet"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Din adgangskode er udløbet"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "dage"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "dag"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Nåde-login. Ændr venligst din adgangskode, så snart du er online igen"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Domæne-controller kan ikke nås, bruger cachede akkreditiver i stedet. Netværksressourcer kan være utilgængelige"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"Kunne ikke etablere din Kerberos-billet-cache pga. tidsforskelle\n"
+"med domæne-controlleren. Verificér venligst systemtiden.\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Din adgangskode"
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "skal have mindst %d tegn;"
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "kan ikke gentage nogen af dine forrige %d adgangskoder;"
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "skal indeholde store bogstaver, tal eller skilletegn og må ikke indholde dit kontonavn eller fulde navn;"
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Indtast venligst en anden adgangskode. Indtast en adgangskode, som opfylder disse krav, i begge tekstfelter."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Adgangskoden opfylder ikke kravene til kompleksitet"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "Brugernavn: "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Adgangskode: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Ændrer adgangskode for"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(nuværende) NT-adgangskode: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Indtast ny NT-adgangskode: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Indtast ny NT-adgangskode igen: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "Beklager, adgangskoderne stemmer ikke overens"
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Adgang nægtet"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Du skal ændre din adgangskode nu"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "For kort adgangskode"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "For kort adgangskode"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "For kort adgangskode"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Adgangskode: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Din adgangskode"
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "For kort adgangskode"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Ugyldig tillidskonto"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Din adgangskode er udløbet"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Ingen domæne-controllere fundet"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Ingen log på-servere"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Ingen primær domæne-controller tilgængelig"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Ugyldig tillidskonto"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Ingen log på-servere"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/de.po b/source3/locale/pam_winbind/de.po
new file mode 100644
index 0000000..b725cb1
--- /dev/null
+++ b/source3/locale/pam_winbind/de.po
@@ -0,0 +1,562 @@
+# pam_winbind message translations (german)
+# Copyright (C) 2008 Guenther Deschner <gd@samba.org>
+# This file is distributed under the same license as the pam_winbind package.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: @PACKAGE@\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2008-11-13 14:29+0100\n"
+"Last-Translator: Guenther Deschner <gd@samba.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Erfolgreich"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Kein primärer Domänen-Controller verfügbar"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Keine Domänen-Controller gefunden"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Keine Anmeldeserver verfügbar"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Passwort ist zu kurz"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Das Passwort des Benutzers kann noch nicht erneut geändert werden"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Das neue Passwort ist bereits in der Passwort-Historie enthalten"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Ihr Passwort ist abgelaufen"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Sie müssen Ihr Passwort jetzt ändern"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Sie können sich nicht von diesem Arbeitsplatz aus anmelden"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Sie können sich zum jetzigen Zeitpunkt nicht anmelden"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Ihr Benutzerkonto ist abgelaufen. Bitte kontaktieren Sie ihren System-Administrator"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Ihr Benutzerkonto ist deaktiviert. Bitte kontaktieren Sie ihren System-Administrator"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Ihr Benutzerkonto wurde gesperrt. Bitte kontaktieren Sie ihren System-Administrator"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Ungültiges Maschinen-Konto"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Zugriff verweigert"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Sie müssen Ihr Passwort jetzt ändern"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Ihr Passwort ist abgelaufen"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Ihr Passwort ist abgelaufen"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "Tagen"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "Tag"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Kulanzanmeldung. Bitte ändern sie ihr Passwort sobald sie wieder online sind"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Domänen-Controller unerreichbar. Anmeldung erfolgte mit gespeicherten Anmeldedaten. Netzwerk-Ressourcen können momentan nicht verfügbar sein"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"Anforderung eines Kerberos-Tickets wegen Zeitunterschied zum \n"
+"Domänen-Controller fehlgeschlagen. Bitte überprüfen Sie die Systemzeit.\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Ihr Passwort "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "muss mindestens %d Zeichen lang sein; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "kann keines der %d vorherigen Passwörter enthalten; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "muss Großbuchstaben, Ziffern oder Punktzeichen enthalten; kann nicht den Benutzer- oder vollen Namen enthalten; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Bitte wählen Sie ein anderes Passwort. Geben Sie ein geeignetes Passwort in beide Textfelder ein."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr "Anlegen des Verzeichnisses: %s fehlgeschlagen: %s"
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Passwort genügt nicht den Komplexitätsanforderungen"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "Benuzername: "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Passwort: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(aktuelles) NT Passwort: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Neues NT Passwort eingeben: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Neues NT Passwort wiederholen: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "Passwörter stimmen leider nicht überein"
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Zugriff verweigert"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Sie müssen Ihr Passwort jetzt ändern"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Passwort ist zu kurz"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Passwort ist zu kurz"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Passwort ist zu kurz"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Passwort: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Ihr Passwort "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Passwort ist zu kurz"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Ungültiges Maschinen-Konto"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Ihr Passwort ist abgelaufen"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Keine Domänen-Controller gefunden"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Keine Anmeldeserver verfügbar"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Kein primärer Domänen-Controller verfügbar"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Ungültiges Maschinen-Konto"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Keine Anmeldeserver verfügbar"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/es.po b/source3/locale/pam_winbind/es.po
new file mode 100644
index 0000000..055c7a8
--- /dev/null
+++ b/source3/locale/pam_winbind/es.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-04 14:32\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Correcto"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "No hay un controlador de dominio principal disponible"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "No se han encontrado controladores de dominio"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "No hay servidores de inicio de sesión"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "La contraseña es demasiado corta"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "La contraseña de este usuario es demasiado reciente como para cambiarla"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "La contraseña ya se encuentra en el historial de contraseñas"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Su contraseña ha caducado"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Debe cambiar su contraseña ahora"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "No dispone de la autorización necesaria para iniciar sesión desde esta estación de trabajo"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "No dispone de la autorización necesaria para iniciar sesión en este momento"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Su cuenta ha caducado. Póngase en contacto con el administrador del sistema."
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Su cuenta está inhabilitada. Póngase en contacto con el administrador del sistema."
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Su cuenta ha sido bloqueada. Póngase en contacto con el administrador del sistema."
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Cuenta de confianza no válida"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Acceso denegado"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Debe cambiar su contraseña ahora"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Su contraseña ha caducado"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Su contraseña ha caducado"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "días"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "día"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Inicio de sesión en periodo de gracia. Cambie su contraseña tan pronto como vuelva a estar en línea."
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "No se encuentra el controlador de dominio, por lo que se utilizarán las credenciales del caché en su lugar. Es posible que los recursos de red no estén disponibles."
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Su contraseña "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "debe tener al menos %d caracteres; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "no puede repetir ninguna de sus %d contraseñas anteriores; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "debe contener letras en mayúscula, números o signos de puntuación y no puede contener su nombre de cuenta ni su nombre completo; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Introduzca una contraseña diferente. Escriba una contraseña que cumpla estos requisitos en ambos campos de texto."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "La contraseña no cumple con los requisitos de complejidad solicitados"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Contraseña:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Cambiando la contraseña para"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "Contraseña de NT (actual): "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Introduzca la nueva contraseña de NT: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Vuelva a introducir la nueva contraseña de NT: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Acceso denegado"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Debe cambiar su contraseña ahora"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "La contraseña es demasiado corta"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "La contraseña es demasiado corta"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "La contraseña es demasiado corta"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Contraseña:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Su contraseña "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "La contraseña es demasiado corta"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Cuenta de confianza no válida"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Su contraseña ha caducado"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "No se han encontrado controladores de dominio"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "No hay servidores de inicio de sesión"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "No hay un controlador de dominio principal disponible"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Cuenta de confianza no válida"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "No hay servidores de inicio de sesión"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/fi.po b/source3/locale/pam_winbind/fi.po
new file mode 100644
index 0000000..8691b5c
--- /dev/null
+++ b/source3/locale/pam_winbind/fi.po
@@ -0,0 +1,564 @@
+# translation of pam_winbind.fi.po to suomi
+# Jyri Palokangas <jmp@opensuse.org>, 2008.
+# Jyri Palokangas <jyri.palokangas@opensuse.org>, 2008.
+# Mikko Piippo <mikko.piippo@opensuse.fi>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind.fi\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2008-11-23 17:53+0200\n"
+"Last-Translator: Jyri Palokangas <jyri.palokangas@opensuse.org>\n"
+"Language-Team: suomi <fi@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Onnistui"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Ensisijaista toimialueen ohjainta ei käytettävissä"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Toimialueen ohjaimia ei löytynyt"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Ei kirjautumispalvelimia"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Liian lyhyt salasana"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Tämän käyttäjän salasana on liian uusi muutettavaksi"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Salasana löytyy jo salasana historiasta"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Salasanasi on vanhentunut"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Salasana täytyy vaihtaa nyt"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Sinulla ei ole oikeutta kirjautua tältä työasemalta"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Sinulla ei ole oikeutta kirjautua tähän aikaan."
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Tilisi on vanhentunut. Ota yhteyttä järjestelmän ylläpitäjään"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Tilisi ei ole käytössä. Ota yhteyttä järjestelmän ylläpitäjään"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Tilisi on lukittu. Ota yhteyttä järjestelmän ylläpitäjään"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Virheellinen luottotili"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Pääsy estetty"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Salasana täytyy vaihtaa nyt"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Salasanasi on vanhentunut"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Salasanasi on vanhentunut"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "päivää"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "päivä"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Ok. Mutta vain tämän kerran. Muuta salasana heti kun mahdollista."
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Toimialueen ohjaimeen ei saatu yhteyttä. Käytetään välimuistin tietoja. Verkkopalveluita ei kenties voida käyttää."
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"Kerberos lippuvälimuistin todentaminen epäonnistui johtuen aika eroavaisuuksista\n"
+"toimialueohjaimen kanssa. Tarkista jäjestelmän aika asetukset.\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Salasanasi "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "täytyy olla vähintään %d merkkiä; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "ei voi toistaa mitään edellisistä %d salasanasta; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "täytyy sisältää suuraakkosia, numeroita tai välimerkkejä eikä se saa sisältää käyttäjän tilin nimeä tai koko nimeä; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Anna erilainen salasana. Anna molempiin laatikoihin tarpeeksi monimutkainen salasana."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Salasana ei ole tarpeeksi monimutkainen."
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "Käyttäjänimi: "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Salasana: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Vaihdetaan salasana käyttäjälle"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(nykyinen) NT-salasana: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Anna uusi NT-salasana: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Anna uusi NT-salasana uudestaan: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "Salasanat eivät täsmää"
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Pääsy estetty"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Salasana täytyy vaihtaa nyt"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Liian lyhyt salasana"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Liian lyhyt salasana"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Liian lyhyt salasana"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Salasana: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Salasanasi "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Liian lyhyt salasana"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Virheellinen luottotili"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Salasanasi on vanhentunut"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Toimialueen ohjaimia ei löytynyt"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Ei kirjautumispalvelimia"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Ensisijaista toimialueen ohjainta ei käytettävissä"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Virheellinen luottotili"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Ei kirjautumispalvelimia"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/fr.po b/source3/locale/pam_winbind/fr.po
new file mode 100644
index 0000000..a1bbb69
--- /dev/null
+++ b/source3/locale/pam_winbind/fr.po
@@ -0,0 +1,547 @@
+# This file is distributed under the same license as the pam_winbind package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+# Copyright (C) 2010 Christian Perrier <bubulle@debian.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2010-01-15 19:18+0100\n"
+"Last-Translator: Christian Perrier <bubulle@debian.org>\n"
+"Language-Team: French <debian-l10n-french@lists.debian.org>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Lokalize 1.0\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Succès"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Aucun contrôleur de domaine principal disponible"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Aucun contrôleur de domaine trouvé"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Aucun serveur d'authentification"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Mot de passe trop court"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Le mot de passe de cet utilisateur est trop récent pour être modifié."
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Ce mot de passe est dans l'historique des mots de passe utilisés."
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Votre mot de passe a expiré."
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Vous devez changer votre mot de passe maintenant."
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Vous n'avez pas l'autorisation de vous connecter depuis cet ordinateur."
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Vous n'avez pas l'autorisation de vous connecter à cette heure."
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Votre compte a expiré. Contactez votre administrateur système."
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Votre compte a été désactivé. Contactez votre administrateur système."
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Votre compte a été verrouillé. Contactez votre administrateur système."
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Compte d'approbation non valable"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Accès refusé"
+
+#: ../../../nsswitch/pam_winbind.c:882
+msgid "Do you want to change your password now?"
+msgstr "Voulez-vous changer votre mot de passe maintenant ?"
+
+#: ../../../nsswitch/pam_winbind.c:963
+msgid "Your password expires today.\n"
+msgstr "Votre mot de passe expire aujourd'hui.\n"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Votre mot de passe expirera dans %d %s.\n"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "jours"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "jour"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr "Impossible de convertir le groupe %s en sid, veuillez contacter votre administrateur pour voir si le groupe %s est valide."
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Délai de grâce. Veuillez changer votre mot de passe à votre prochaine connexion."
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Le contrôleur de domaine est injoignable. Les donnés d'identification en cache seront utilisées. Certaines ressources réseaux seront indisponibles."
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"Impossible d'établir votre cache Kerberos Ticket en raison d'une différence\n"
+"de temps avec le contrôleur de domaine. Veuillez vérifier l'heure système.\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Votre mot de passe "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "doit contenir au moins %d caractères ; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "ne peut être identique à un de vos %d précédents mots de passe ; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "doit contenir des majuscules, des chiffres ou des signes de ponctuation et ne doit pas contenir votre nom ou votre identifiant ; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Veuillez choisir un autre mot de passe qui satisfasse les différents critères."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr "La création du répertoire %s a échoué : %s"
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Le mot de passe n'est pas suffisamment complexe."
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "Nom d'utilisateur : "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Mot de passe : "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Changement du mot de passe pour"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "Mot de passe NT actuel : "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Nouveau mot de passe NT : "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Confirmation du nouveau mot de passe NT : "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "Désolé, les mots de passe ne correspondent pas."
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr "Erreur indéterminée"
+
+#: ../../libsmb/nterr.c:560
+msgid "Access denied"
+msgstr "Accès refusé"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr "Compte bloqué"
+
+#: ../../libsmb/nterr.c:562
+msgid "Must change password"
+msgstr "Doit changer son mot de passe"
+
+#: ../../libsmb/nterr.c:563
+msgid "Password is too short"
+msgstr "Mot de passe trop court"
+
+#: ../../libsmb/nterr.c:564
+msgid "Password is too recent"
+msgstr "Mot de passe trop récent"
+
+#: ../../libsmb/nterr.c:565
+msgid "Password history conflict"
+msgstr "Mot de passe en conflit avec l'historique"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr "Nom de compte incorrectement formé"
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr "L'utilisateur existe"
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr "Pas de tel utilisateur"
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr "Le groupe existe"
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr "Pas de tel groupe"
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr "Pas membre de ce groupe"
+
+#: ../../libsmb/nterr.c:573
+msgid "Wrong Password"
+msgstr "Mot de passe incorrect"
+
+#: ../../libsmb/nterr.c:574
+msgid "Ill formed password"
+msgstr "Mot de passe mal formé"
+
+#: ../../libsmb/nterr.c:575
+msgid "Password restriction"
+msgstr "Restriction relative au mot de passe"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr "Echec de connexion"
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr "Restriction de compte"
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr "Heures de connexion non valides"
+
+#: ../../libsmb/nterr.c:579
+msgid "Invalid workstation"
+msgstr "Ordinateur non valide"
+
+#: ../../libsmb/nterr.c:580
+msgid "Password expired"
+msgstr "Mot de passe expiré"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr "Compte désactivé"
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr "Erreur d'allocation mémoire"
+
+#: ../../libsmb/nterr.c:583
+msgid "No domain controllers located"
+msgstr "Aucun contrôleur de domaine trouvé"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr "Relais nommé indisponible"
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr "Pas implémenté"
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr "Classe d'information non valide"
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr "Longueur d'information incohérente"
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr "Violation d'accès"
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr "Référence non valide"
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr "Paramètre non valide"
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr "Pas de mémoire"
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr "Tampon trop petit"
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr "Révisions incompatibles"
+
+#: ../../libsmb/nterr.c:594
+msgid "No such logon session"
+msgstr "Pas de telle session de connexion"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr "Pas de tel privilège"
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr "Procédure non trouvée"
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr "Serveur désactivé"
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr "Etat du relais non valide"
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr "Relais nommé occupé"
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr "Fonction illégale"
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr "Relais nommé déconnecté"
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr "Le relais nommé va fermer"
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr "Le serveur distant n'écoute pas"
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr "Nom en double sur le réseau"
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr "La file d'impression est pleine"
+
+#: ../../libsmb/nterr.c:606
+msgid "No print spool space available"
+msgstr "Aucun espace d'impression disponible"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr "Le nom de réseau ne peut être trouvé"
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr "La connexion a été refusée"
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr "Trop de noms"
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr "Trop de sessions"
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr "Etat du serveur non valide"
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr "Etat du domaine non valide"
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr "Rôle du domaine non valide"
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr "Pas de tel domaine"
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr "Le domain existe"
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr "Limite de domaine dépassée"
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr "Mauvais état de la session de connexion"
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr "Collision de la session de connexion"
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr "Type de connexion non valide"
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr "Annulé"
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr "Nom d'ordinateur non valide"
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr "Conflit de serveurs d'authentification"
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr "Différence de temps au niveau du contrôleur de domaine"
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr "Relais brisé"
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr "Base de registre corrompue"
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr "Trop de secrets"
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr "Trop de SIDs"
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr "Chiffrement croisé Lanmanager nécessaire"
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr "Journal plein"
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr "Pas de secret LSA approuvé"
+
+#: ../../libsmb/nterr.c:631
+msgid "No trusted SAM account"
+msgstr "Pas de compte SAM approuvé"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr "Erreur de domaine approuvé"
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr "Erreur de relation d'approbation"
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr "Erreur d'approbation"
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr "Service Netlogon non démarré"
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr "Compte expiré"
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr "Conflit d'identifiants réseau"
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr "Limite de sessions distantes"
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr "Impossible de se connecter avec un compte de confiance inter-domaine"
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr "Impossible de se connecter avec un compte de confiance ordinateur"
+
+#: ../../libsmb/nterr.c:641
+msgid "No logon server trust account"
+msgstr "Impossible de se connecter avec un compte de confiance serveur"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr "Approbation du domaine incohérente"
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr "Pas de clé de session utilisateur disponible"
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr "Session utilisateur supprimée"
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr "Ressources serveur insuffisantes"
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr "Informations de connexion insuffisantes"
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr "Quota de licenses dépassé"
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr "Plus de fichiers"
diff --git a/source3/locale/pam_winbind/genmsg b/source3/locale/pam_winbind/genmsg
new file mode 100755
index 0000000..29fa9cb
--- /dev/null
+++ b/source3/locale/pam_winbind/genmsg
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+FILES="../../../nsswitch/pam_winbind.c ../../../nsswitch/pam_winbind.h ../../../libcli/util/nterr.c"
+LANGS="ar cs da de es fi fr hu it ja ko nb nl pl pt_BR ru sv tr zh_CN zh_TW"
+
+XGETTEXT=xgettext
+MSGMERGE=msgmerge
+
+WIDTH=256
+
+[ -f pam_winbind.po ] || touch pam_winbind.po
+
+$XGETTEXT --default-domain="pam_winbind" \
+ --add-comments \
+ --keyword=_ --keyword=N_ \
+ --width=${WIDTH} \
+ ${FILES}
+
+for lang in ${LANGS}; do
+ echo -n $lang
+ touch ${lang}.po
+ mv ${lang}.po ${lang}.po.old
+ ${MSGMERGE} --width=${WIDTH} ${lang}.po.old pam_winbind.po -o ${lang}.po
+ rm -fr ${lang}.po.old
+done
+
+rm -fr pam_winbind.po
diff --git a/source3/locale/pam_winbind/hu.po b/source3/locale/pam_winbind/hu.po
new file mode 100644
index 0000000..b93e05c
--- /dev/null
+++ b/source3/locale/pam_winbind/hu.po
@@ -0,0 +1,560 @@
+# translation of pam_winbind.hu.po to
+# Kalman Kemenczy <kkemenczy@novell.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind.hu\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2008-10-26 10:27+0100\n"
+"Last-Translator: Kalman Kemenczy <kkemenczy@novell.com>\n"
+"Language-Team: <hu@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Sikeres"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Az elsődleges tartományvezérlő nem érhető el"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Nem található tartományvezérlő"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Nincsenek Logon kiszolgálók"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Túl rövid jelszó"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "A felhasználó jelszava új"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "A jelszó már szerepel az előzményekben"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "A jelszava lejárt"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Meg kell most változtatnia a jelszavát"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Nem jogosult belépni erről a munkaállomásról"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Most nem jogosult most belépni"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "A felhasználói fiókja lejárt. Lépjen kapcsolatba a rendszergazdával"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "A felhasználói fiókja le van tiltva. Lépjen kapcsolatba a rendszergazdával"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "A felhasználói fiókja zárolt. Lépjen kapcsolatba a rendszergazdával"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Érvénytelen Trust Account"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Hozzáférés megtagadva"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Meg kell most változtatnia a jelszavát"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "A jelszava lejárt"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "A jelszava lejárt"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "nap múlva"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "nap múlva"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Türelmi belépés. Cserélje le a jelszavát a következő alkalommal"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "A tartomány nem érhető el, a gyorstározott hitelesítő adat kerül felhasználásra. A hálózati erőforrások valószínűleg nem érhatők el "
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "A jelszavának"
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "legalább %d karakterből kell állnia; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "nem ismétlődhet az előző %d jelszó; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "tartalmaznia kell betűket, számokat vagy írásjeleket és nem tartalmazhatja a felhasználói vagy a teljes nevét; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Adjon meg egy másik jelszót. Írjon be mindkét szövegmezőbe a feltételeknek megfelelő jelszót."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "A jelszó nem felel meg a követelményeknek"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Jelszó: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Jelszómódosítás"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(jelenlegi) NT jelszó: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Új NT jelszó: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Új NT jelszó ismét: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Hozzáférés megtagadva"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Meg kell most változtatnia a jelszavát"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Túl rövid jelszó"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Túl rövid jelszó"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Túl rövid jelszó"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Jelszó: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "A jelszavának"
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Túl rövid jelszó"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Érvénytelen Trust Account"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "A jelszava lejárt"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Nem található tartományvezérlő"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Nincsenek Logon kiszolgálók"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Az elsődleges tartományvezérlő nem érhető el"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Érvénytelen Trust Account"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Nincsenek Logon kiszolgálók"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/it.po b/source3/locale/pam_winbind/it.po
new file mode 100644
index 0000000..731cb45
--- /dev/null
+++ b/source3/locale/pam_winbind/it.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-05 10:11\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Successo"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Nessun controller di dominio primario disponibile"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Nessun controller di dominio trovato"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Nessun server d'autenticazione"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Password troppo breve"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "La password per questo utente è troppo recente per essere cambiata"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "La password è già presente nella cronologia delle password"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "La password è scaduta"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "È necessario modificare la password ora"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Impossibile effettuare il login da questa workstation"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Impossibile effettuare il login in questo momento"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "L'account è scaduto. Contattare l'amministratore di sistema"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "L'account è disabilitato. Contattare l'amministratore di sistema"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "L'account è stato bloccato. Contattare l'amministratore di sistema"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Account attendibile non valido"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Accesso negato"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "È necessario modificare la password ora"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "La password è scaduta"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "La password è scaduta"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "giorni"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "giorno"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Login extra. Modificare la password poiché a breve verrà ristabilita la connessione"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Controller del dominio non raggiungibile; uso delle credenziali memorizzate al suo posto. Le risorse di rete potrebbero non essere disponibili"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "La tua password "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "deve essere composta almeno da %d caratteri; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "non puoi ripetere nessuna delle tue %d password precedenti; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "deve contenere lettere maiuscole, cifre e punteggiatura; e non può contenere il nome del tuo account o il tuo nome completo; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Inserire una password diversa. Digita in entrambe le caselle una password che soddisfa i requisiti."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "La password non soddisfa i requisiti di complessità"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Password: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Modifica la password per"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "Password NT (corrente): "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Immetti la nuova password NT: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Reinserisci la nuova password NT: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Accesso negato"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "È necessario modificare la password ora"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Password troppo breve"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Password troppo breve"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Password troppo breve"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Password: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "La tua password "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Password troppo breve"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Account attendibile non valido"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "La password è scaduta"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Nessun controller di dominio trovato"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Nessun server d'autenticazione"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Nessun controller di dominio primario disponibile"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Account attendibile non valido"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Nessun server d'autenticazione"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/ja.po b/source3/locale/pam_winbind/ja.po
new file mode 100644
index 0000000..319d04a
--- /dev/null
+++ b/source3/locale/pam_winbind/ja.po
@@ -0,0 +1,552 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2010-03-06 02:55+0900\n"
+"Last-Translator: victory <victory.deb@gmail.com>\n"
+"Language-Team: Japanese <debian-japanese@lists.debian.org>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "成功しました"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "プライマリドメインコントローラーが利用できません"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "ドメインコントローラーが見つかりません"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "ログオンサーバーがありません"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "パスワードが短すぎます"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "このユーザーに対するパスワードは変更するには新しすぎます"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "指定したパスワードはパスワード履歴内に存在しています"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "お使いのパスワードの期限が切れました"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "パスワードを今すぐ変更する必要があります"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "このワークステーションからログオンすることが許可されていません"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "この時間にログオンすることが許可されていません"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "お使いのアカウントの期限が切れました。システム管理者に連絡してください"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "アカウントは無効です。システム管理者にご連絡ください。"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "お使いのアカウントはロック (施錠) されています。システム管理者に連絡してください"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "信頼アカウントが正しくありません"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "アクセスが拒否されました"
+
+#: ../../../nsswitch/pam_winbind.c:882
+msgid "Do you want to change your password now?"
+msgstr "パスワードを今すぐ変更しますか?"
+
+#: ../../../nsswitch/pam_winbind.c:963
+msgid "Your password expires today.\n"
+msgstr "お使いのパスワードは本日期限切れになります。\n"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "お使いのパスワードは、あと %d %s で有効期限が切れます。\n"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "日"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "日"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr "グループ %s を sid に変換できません。管理者と連絡を取ってグループ %s が合っているか確認してください。"
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "猶予ログイン中です。次回ログオン時にすぐにパスワードを変更してください"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "ドメインコントローラーにアクセスできないため、記憶済みの認証を使用しました。ネットワーク資源が利用できないかもしれません"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"ドメインコントローラーと期限が合っていないため Kerberos Ticket の\n"
+"キャッシュの構成に失敗しました。システム時刻を確認してください。\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "お使いのパスワードは "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "少なくとも %d 文字以上でなければなりません; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "過去 %d 個分のパスワードはいずれも再度利用することはできません; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "アルファベット大文字や数字、句読点 (カンマやピリオド) のいずれかを含まなければなりません; さらにアカウント名や本名 (フルネーム) を含めることはできません; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "別のパスワードを入力してください。両方のテキストボックス内にこれらの要件を満たすものを入力してください。"
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr "ディレクトリ %s の作成に失敗しました: %s"
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "パスワードは複雑さの要件を満たしていません"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "ユーザー名: "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "パスワード:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "下記に対するパスワードを変更しています"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "現在の NT パスワード: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "新しい NT パスワード: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "新しい NT パスワードの再入力: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "申し訳ありませんがパスワードが一致しません"
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr "不明瞭なエラー"
+
+#: ../../libsmb/nterr.c:560
+msgid "Access denied"
+msgstr "アクセスが拒否されました"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr "アカウントロックアウト"
+
+#: ../../libsmb/nterr.c:562
+msgid "Must change password"
+msgstr "パスワードを変更する必要があります"
+
+#: ../../libsmb/nterr.c:563
+msgid "Password is too short"
+msgstr "パスワードが短すぎます"
+
+#: ../../libsmb/nterr.c:564
+msgid "Password is too recent"
+msgstr "パスワードがごく最近利用されています"
+
+#: ../../libsmb/nterr.c:565
+msgid "Password history conflict"
+msgstr "パスワード履歴と競合しています"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr "アカウント名の形式が誤っています"
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr "ユーザーが存在します"
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr "ユーザーが存在しません"
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr "グループが存在します"
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr "グループが存在しません"
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr "グループのメンバーではありません"
+
+#: ../../libsmb/nterr.c:573
+msgid "Wrong Password"
+msgstr "パスワードが誤っています"
+
+#: ../../libsmb/nterr.c:574
+msgid "Ill formed password"
+msgstr "パスワードの形式が不正です"
+
+#: ../../libsmb/nterr.c:575
+msgid "Password restriction"
+msgstr "パスワード制限"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr "ログオン失敗"
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr "アカウント制限"
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr "ログオン時間が不正です"
+
+#: ../../libsmb/nterr.c:579
+msgid "Invalid workstation"
+msgstr "ワークステーションが不正です"
+
+#: ../../libsmb/nterr.c:580
+msgid "Password expired"
+msgstr "お使いのパスワードの期限が切れました"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr "アカウントが無効になっています"
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr "メモリ割り当てエラー"
+
+#: ../../libsmb/nterr.c:583
+msgid "No domain controllers located"
+msgstr "ドメインコントローラーが見つかりません"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr "名前付きパイプは利用できません"
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr "実装されていません"
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr "情報クラスが不正です"
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr "情報の長さが合いません"
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr "アクセス違反"
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr "処理が不正です"
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr "パラメーターが不正です"
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr "メモリがありません"
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr "バッファーが小さすぎます"
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr "リビジョンが一致しません"
+
+#: ../../libsmb/nterr.c:594
+msgid "No such logon session"
+msgstr "ログオンセッションがありません"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr "権限がありません"
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr "手順が見つかりませ"
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr "サーバーは無効にされています"
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr "パイプの状態が不正です"
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr "名前付きパイプが使用中です"
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr "関数が不正です"
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr "名前付きパイプを切断しました"
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr "名前付きパイプを閉じています"
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr "リモートホストが待ち受けしていません"
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr "ネットワーク上で名前が重複しています"
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr "印刷キューがいっぱいです"
+
+#: ../../libsmb/nterr.c:606
+msgid "No print spool space available"
+msgstr "利用可能な印刷スプールスペースがありません"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr "ネットワーク名を見つけられませんでした"
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr "接続が拒否されました"
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr "名前が多すぎます"
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr "セッションが多すぎます"
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr "サーバーの状態が不正です"
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr "ドメインの状態が不正です"
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr "ドメインロールが不正です"
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr "ドメインがありません"
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr "ドメインが存在します"
+
+# Translator's NOTE: 何の限度か不明
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr "ドメインの限度を超えています"
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr "ログオンセッションの状態が不正です"
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr "ログオンセッション衝突"
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr "ログオンの種類が不正です"
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr "取り消しました"
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr "コンピューター名が不正です"
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr "ログオンサーバーが競合しています"
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr "ドメインコントローラーに時差があります"
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr "パイプが壊れています"
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr "レジストリが破損しています"
+
+# Translator's NOTE: is 'secret' MS term?
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr "秘密が多すぎます"
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr "SID が多すぎます"
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr "LanManager のクロス暗号化が必要です"
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr "ログファイルがいっぱいです"
+
+# Translator's NOTE: is 'Trust' MS term?
+# Translator's NOTE: is 'secret' MS term?
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr "信頼している LSA シークレットがありません"
+
+# Translator's NOTE: is 'Trust' MS term?
+#: ../../libsmb/nterr.c:631
+msgid "No trusted SAM account"
+msgstr "信頼している SAM アカウントがありません"
+
+# Translator's NOTE: is 'Trust' MS term?
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr "信頼しているドメインに故障が発生しました"
+
+# Translator's NOTE: is 'Trust' MS term?
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr "関係の信頼に失敗しました"
+
+# Translator's NOTE: is 'Trust' MS term?
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr "信頼に失敗しました"
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr "Netlogon サービスが起動していません"
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr "アカウントの有効期限が切れました"
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr "ネットワークの資格情報が競合しています"
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr "リモートセッション数の上限です"
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr "ログオンドメイン間信頼アカウントがありません"
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr "ログオンワークステーションの信頼アカウントがありません"
+
+#: ../../libsmb/nterr.c:641
+msgid "No logon server trust account"
+msgstr "ログオンサーバー信頼アカウントがありません"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr "ドメインの信頼に不整合があります"
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr "利用できるユーザーセッションキーがありません"
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr "ユーザーセッションを削除しました"
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr "サーバーリソースが不足しています"
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr "ログオン情報が不足しています"
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr "ライセンス数の制限を超えました"
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr "もうファイルがありません"
diff --git a/source3/locale/pam_winbind/ko.po b/source3/locale/pam_winbind/ko.po
new file mode 100644
index 0000000..1e3129c
--- /dev/null
+++ b/source3/locale/pam_winbind/ko.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-04 15:02\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "성공"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "사용할 수 있는 기본 도메인 컨트롤러가 없습니다."
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "도메인 컨트롤러가 없습니다."
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "서버에 로그온하지 않았습니다."
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "비밀번호가 너무 짧습니다."
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "이 사용자의 비밀번호는 바꾼지 얼마 되지 않아 지금 바꿀 수 없습니다."
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "비밀번호 이력에 이미 있는 비밀번호입니다."
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "비밀번호가 만료되었습니다."
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "지금 비밀번호를 변경해야 합니다."
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "이 워크스테이션에서 로그온할 수 없습니다."
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "현재 로그온이 허용되지 않습니다."
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "계정이 만료되었습니다. 시스템 관리자에게 문의하십시오."
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "계정이 비활성화되었습니다. 시스템 관리자에게 문의하십시오."
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "계정이 잠겼습니다. 시스템 관리자에게 문의하십시오."
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "잘못된 트러스트 계정입니다."
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "액세스가 거부되었습니다."
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "지금 비밀번호를 변경해야 합니다."
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "비밀번호가 만료되었습니다."
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "비밀번호가 만료되었습니다."
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "일"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "일"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "정상적으로 로그온되었습니다. 다시 접속할 때 비밀번호를 변경해 주십시오. "
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "캐시된 자격인증을 사용하여 도메인 컨트롤러에 연결할 수 없습니다. 네트워크 리소스를 사용할 수 없습니다. "
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "비밀번호는"
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "최소 %d자 이상이어야 합니다."
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "이전에 사용한 %d개의 비밀번호를 다시 사용할 수 없습니다."
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "대문자, 숫자 또는 문장 부호를 포함해야 하며, 본인의 계정이나 이름을 비밀번호에 사용할 수 없습니다."
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "다른 비밀번호를 입력하십시오. 비밀번호 요구사항을 준수하여 두 텍스트 상자에 입력해 주십시오. "
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "비밀번호 복잡성 요구사항에 맞지 않습니다."
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "비밀번호:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "비밀번호 변경"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(현재) NT 비밀번호:"
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "새 NT 비밀번호 입력:"
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "새 NT 비밀번호 재입력:"
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "액세스가 거부되었습니다."
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "지금 비밀번호를 변경해야 합니다."
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "비밀번호가 너무 짧습니다."
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "비밀번호가 너무 짧습니다."
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "비밀번호가 너무 짧습니다."
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "비밀번호:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "비밀번호는"
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "비밀번호가 너무 짧습니다."
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "잘못된 트러스트 계정입니다."
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "비밀번호가 만료되었습니다."
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "도메인 컨트롤러가 없습니다."
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "서버에 로그온하지 않았습니다."
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "사용할 수 있는 기본 도메인 컨트롤러가 없습니다."
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "잘못된 트러스트 계정입니다."
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "서버에 로그온하지 않았습니다."
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/nb.po b/source3/locale/pam_winbind/nb.po
new file mode 100644
index 0000000..78fd9f7
--- /dev/null
+++ b/source3/locale/pam_winbind/nb.po
@@ -0,0 +1,566 @@
+# translation of pam_winbind.po to norsk bokmål
+# @TITLE@
+# This file is distributed under the same license as @PACKAGE@ package. FIRST
+#
+# Olav Pettershagen <olav.pet@gmail.com>, 2008.
+# Olav P. <olav.pet@gmail.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2008-11-22 22:33+0100\n"
+"Last-Translator: Olav P. <olav.pet@gmail.com>\n"
+"Language-Team: Norwegian Bokmål <>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Lokalize 0.2\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Vellykket"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Ingen primær domenekontroller tilgjengelig"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Ingen domenekontrollere funnet"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Ingen innloggingsservere"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Passordet er for kort"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Passordet for denne brukeren er for nytt til å endre"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Passordet finnes allerede i passordloggen"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Passordet er utløpt"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Du må endre passordet nå"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Du er ikke autorisert for å logge inn fra denne arbeidsstasjonen"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Du er ikke autorisert for å logge inn på dette tidspunktet"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Din konto er utløpt. Kontakt systemadministratoren"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Din konto er deaktivert. Kontakt systemadministratoren"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Din konto er låst. Kontakt systemadministratoren"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Ugyldig pålitelig konto"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Tilgang avvist"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Du må endre passordet nå"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Passordet er utløpt"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Passordet er utløpt"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "dager"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "dag"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Midlertidig innlogging. Du må endre passordet straks du er tilkoblet igjen"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Domenekontrolleren er utilgjengelig, bruker mellomlagrede rettigheter i stedet. Nettverksressurser Kan være utilgjengelige"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"Kunne ikke opprette mellomlager for Kerberos-billetter på grunn av tidsdifferanse\n"
+"i forhold til domenekontroller. Kontroller systemtiden.\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Passordet "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "må inneholde minst %d tegn; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "kan ikke være likt noen av dine %d tidligere passord; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "må inneholde store bokstaver, tall eller skilletegn; og kan ikke inneholde ditt kontonavn eller fulle navn; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Angi et annet passord. Skriv inn et passord som oppfyller kriteriene i begge tekstboksene."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Passordet er for enkelt"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "Brukernavn: "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Passord: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Endrer passord for"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(gjeldende) NT-passord: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Angi nytt NT-passord: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Bekreft det nye NT-passordet: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "Beklager, passordene samsvarer ikke"
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Tilgang avvist"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Du må endre passordet nå"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Passordet er for kort"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Passordet er for kort"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Passordet er for kort"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Passord: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Passordet "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Passordet er for kort"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Ugyldig pålitelig konto"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Passordet er utløpt"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Ingen domenekontrollere funnet"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Ingen innloggingsservere"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Ingen primær domenekontroller tilgjengelig"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Ugyldig pålitelig konto"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Ingen innloggingsservere"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/nl.po b/source3/locale/pam_winbind/nl.po
new file mode 100644
index 0000000..768ace0
--- /dev/null
+++ b/source3/locale/pam_winbind/nl.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-05 08:00\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Succes"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Geen primaire domeincontroller beschikbaar"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Geen domeincontroller gevonden"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Geen aanmeldservers"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Wachtwoord te kort"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Het wachtwoord van deze gebruiker is te recent om te wijzigen"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Wachtwoord zit al in de wachtwoordgeschiedenis"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Uw wachtwoord is verlopen"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "U moet uw wachtwoord nu wijzigen"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "U mag u vanaf dit werkstation niet aanmelden"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "U mag u op dit tijdstip niet aanmelden"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Uw account is verlopen. Neem contact op met uw systeembeheerder"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Uw account is uitgeschakeld. Neem contact op met de systeembeheerder"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Uw account is vergrendeld. Meen contact op met uw systeembeheerder"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Ongeldige vertrouwde account"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Toegang geweigerd"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "U moet uw wachtwoord nu wijzigen"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Uw wachtwoord is verlopen"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Uw wachtwoord is verlopen"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "dagen"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "dag"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Dispensatie-aanmelding. Wijzig uw wachtwoord zodra u weer online bent"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Domeincontroller is onbereikbaar. Uw gegevens uit de cache worden gebruikt. Netwerkrecources kunnen onbereikbaar zijn"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Uw wachtwoord "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "moet uit minstens %d tekens bestaan; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "mag niet hetzelfde zijn als een van uw voorgaande %d wachtwoorden; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "moet hoofdletters, cijfers of bijzondere tekens bevatten; en mag niet uw accountnaam of volledige naam bevatten; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Geef een ander wachtwoord op. Typ een wachtwoord dat voldoet aan de vereisten in beide tekstblokken."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Wachtwoord voldoet niet aan de complexiteitseisen"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Wachtwoord:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Wachtwoord wijzigen voor"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(huidig) NT-wachtwoord: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Geef nieuw NT-wachtwoord: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Nieuw NT-wachtwoord herhalen: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Toegang geweigerd"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "U moet uw wachtwoord nu wijzigen"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Wachtwoord te kort"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Wachtwoord te kort"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Wachtwoord te kort"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Wachtwoord:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Uw wachtwoord "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Wachtwoord te kort"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Ongeldige vertrouwde account"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Uw wachtwoord is verlopen"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Geen domeincontroller gevonden"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Geen aanmeldservers"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Geen primaire domeincontroller beschikbaar"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Ongeldige vertrouwde account"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Geen aanmeldservers"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/pl.po b/source3/locale/pam_winbind/pl.po
new file mode 100644
index 0000000..25b4008
--- /dev/null
+++ b/source3/locale/pam_winbind/pl.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-13 18:27\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Sukces"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Brak podstawowego kontolera domeny"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Nie odnaleziono kontrolerów domen"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Brak serwerów logowania"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Zbyt krótkie hasło"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Hasło tego użytkownika jest zbyt nowe, aby je zmienić"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Hasło znajduje się już w historii haseł."
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Hasło użytkownika wygasło"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Zmiana hasła jest wymagana w tej chwili"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Brak zezwolenia na logowanie się z tej stacji roboczej"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Brak zezwolenia na logowanie się w tej chwili"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "To konto wygasło. Proszę skontaktować się z administratorem systemu."
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "To konto zostało wyłączone. Proszę skontaktować się z administratorem systemu."
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "To konto zostało zablokowane. Proszę skontaktować się z administratorem systemu."
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Nieprawidłowe zaufane konto"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Brak dostępu"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Zmiana hasła jest wymagana w tej chwili"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Hasło użytkownika wygasło"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Hasło użytkownika wygasło"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "dni"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "dzień"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Logowanie dodatkowe. Proszę zmienić swoje hasło przy następnym wejściu w tryb online."
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Kontroler domeny jest nieosiągalny, zamiast tego używane są zapisane wcześniej dane. Zasoby sieciowe mogą być niedostępne."
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Hasło użytkownika "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "musi mieć co najmniej %d znaków; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "nie można powtarzać żadnego z poprzednich %d haseł, "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "musi zawierać litery, cyfry lub znaki interpunkcyjne i nie może zawierać nazwy konta lub nazwiska; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Proszę wprowadzić inne hasło. Proszę wpisać w oba pola tekstowe hasło, które spełnia te wymagania."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Hasło nie spełnia wymagań złożoności"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Hasło: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Zmiana hasła dla"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(aktualne) hasło NT: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Prosze podać nowe hasło NT: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Powtórzenie nowego hasła NT: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Brak dostępu"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Zmiana hasła jest wymagana w tej chwili"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Zbyt krótkie hasło"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Zbyt krótkie hasło"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Zbyt krótkie hasło"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Hasło: "
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Hasło użytkownika "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Zbyt krótkie hasło"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Nieprawidłowe zaufane konto"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Hasło użytkownika wygasło"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Nie odnaleziono kontrolerów domen"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Brak serwerów logowania"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Brak podstawowego kontolera domeny"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Nieprawidłowe zaufane konto"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Brak serwerów logowania"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/pt_BR.po b/source3/locale/pam_winbind/pt_BR.po
new file mode 100644
index 0000000..797846d
--- /dev/null
+++ b/source3/locale/pam_winbind/pt_BR.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-05 09:28\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Sucesso"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Nenhum Controlador de Domínio primário disponível"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Nenhum controlador de domínio encontrado"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Nenhum servidor de logon"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Senha muito curta"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "A senha deste usuário é muito recente para ser alterada"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "A senha já está no histórico de senhas"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Sua senha expirou"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Você precisa alterar sua senha agora"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Você não tem autorização para efetuar login a partir desta estação de trabalho"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Você não tem autorização para efetuar login agora"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Sua conta expirou. Entre em contato com o administrador do sistema"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Sua conta está desabilitada. Entre em contato com o administrador do sistema"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Sua conta foi bloqueada. Entre em contato com o administrador do sistema"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Conta Confiável Inválida"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "O acesso foi negado"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Você precisa alterar sua senha agora"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Sua senha expirou"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Sua senha expirou"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "dias"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "dia"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Login extra. Altere sua senha assim que você ficar online novamente"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Controlador de domínio inacessível, usando no lugar dele credencias em cache. Os recursos de rede podem estar indisponíveis"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Sua senha"
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "deve ter no mínimo %d caracteres;"
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "não é permitido repetir quaisquer das suas %d senhas anteriores;"
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "deve conter maiúsculas, números ou pontuação e não pode conter o nome de sua conta nem seu nome completo;"
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Digite uma senha diferente. Digite uma senha que atenda a estes requisitos em ambas as caixas de texto."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "A senha não atende aos requisitos de complexidade"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Senha:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Alterando a senha para"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(atual) senha NT:"
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Digite a nova senha NT:"
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Redigite a nova senha NT:"
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "O acesso foi negado"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Você precisa alterar sua senha agora"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Senha muito curta"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Senha muito curta"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Senha muito curta"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Senha:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Sua senha"
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Senha muito curta"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Conta Confiável Inválida"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Sua senha expirou"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Nenhum controlador de domínio encontrado"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Nenhum servidor de logon"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Nenhum Controlador de Domínio primário disponível"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Conta Confiável Inválida"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Nenhum servidor de logon"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/ru.po b/source3/locale/pam_winbind/ru.po
new file mode 100644
index 0000000..1c2ff90
--- /dev/null
+++ b/source3/locale/pam_winbind/ru.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:26+0300\n"
+"PO-Revision-Date: 2009-02-05 08:14\n"
+"Last-Translator: Mikhail Novosyolov <m.novosyolov@rosalinux.ru>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "успешно"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Нет доступных основных контроллеров домена"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Контроллеры домена не найдены"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Нет серверов входа"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Пароль слишком короткий"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Пароль этого пользователя слишком новый, чтобы его менять"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Пароль уже в истории паролей"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Срок действия Вашего пароля истёк"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Вам необходимо изменить Ваш пароль сейчас"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Вам не разрешается входить с этой рабочей станции"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Вам не разрешается входить в это время"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Срок действия Вашей учетной записи истёк. Пожалуйста, обратитесь к системному администратору"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Ваша учетная запись заблокирована. Обратитесь к системному администратору"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Ваша учётная запись заблокирована. Пожалуйста, обратитесь к системному администратору"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Неверная доверенная учётная запись"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Доступ запрещён"
+
+#: ../../../nsswitch/pam_winbind.c:882
+msgid "Do you want to change your password now?"
+msgstr "Хотите изменить свой пароль сейчас?"
+
+#: ../../../nsswitch/pam_winbind.c:963
+msgid "Your password expires today.\n"
+msgstr "Срок действия Вашего пароля истекает сегодня.\n"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Срок действия Вашего пароля истечет через %d %s.\n"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "Дней"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "День"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr "Невозможно конвертировать группу %s в sid, пожалуйста, свяжитесь со своим администратором, чтобы проверить, верна ли группа %s."
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Вход разрешён. Пожалуйста, измените Ваш пароль, как только Вы снова будете в сети"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Контроллер домена недоступен, используются кэшированные учетные данные входа. Сетевые ресурсы могут быть недоступны"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"Не удалось создать кеш билетов Kerberos из-за разницы во времени\n"
+"с контроллером домена. Пожалуйста, проверьте системное время.\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Ваш пароль "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "должен иметь хотя бы %d символов;"
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "не может повторять любой из ваших предыдущих %d паролей;"
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "должен содержать заглавные буквы, цифры или знаки препинания; и не может содержать Вашу учётную запись или полное имя; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Пожалуйста, введите другой пароль. Введите пароль, соответствующий этим требованиям, в оба текстовых поля."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr "Ошибка создания директории %s: %s"
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Пароль не соответствует требованиям сложности"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "Имя пользователя: "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Пароль:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Изменение пароля для"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(текущий) пароль NT: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Введите новый пароль NT: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Повторите новый пароль NT: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "Извините, пароли не совпадают"
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Доступ запрещён"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Вам необходимо изменить Ваш пароль сейчас"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Пароль слишком короткий"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Пароль слишком короткий"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Пароль слишком короткий"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Пароль:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Ваш пароль "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Пароль слишком короткий"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Неверная доверенная учётная запись"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Срок действия Вашего пароля истёк"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Контроллеры домена не найдены"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Нет серверов входа"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Нет доступных основных контроллеров домена"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Неверная доверенная учётная запись"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Нет серверов входа"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/sv.po b/source3/locale/pam_winbind/sv.po
new file mode 100644
index 0000000..0e6652a
--- /dev/null
+++ b/source3/locale/pam_winbind/sv.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-05 07:23\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Lyckades"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "Det finns ingen tillgänglig primär domänkontrollant"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Inga domänkontrollanter hittades"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Inga inloggningsservrar"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Lösenordet är för kort"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Lösenordet för den här användaren är för nytt för att kunna ändras"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Lösenordet finns redan i lösenordshistoriken"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Ditt lösenord gäller inte längre"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Du måste ändra ditt lösenord nu"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Du kan inte logga in från den här arbetsstationen"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Du kan inte logga in just nu"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Ditt konto gäller inte längre. Kontakta systemadministratören"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Ditt konto har inaktiverats. Kontakta systemadministratören."
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Ditt konto har låsts. Kontakta systemadministratören"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Ogiltigt förtroendekonto"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Åtkomst nekades"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "Du måste ändra ditt lösenord nu"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "Ditt lösenord gäller inte längre"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Ditt lösenord gäller inte längre"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "dagar"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "dag"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Inloggningsräknaren har aktiverats. Ändra ditt lösenord så fort du ansluter igen"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Det gick inte att ansluta till domänkontrollanten. Cachelagrade inloggningsuppgifter används i stället. Nätverksresurserna kanske inte är tillgängliga"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Ditt lösenord"
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "måste vara minst %d tecken långt, "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "får inte vara identiskt med något av dina %d senaste lösenord "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "måste innehålla versaler, siffror eller skiljetecken och får inte innehålla ditt kontonamn eller ditt fullständiga namn,"
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Skriv ett annat lösenord. Skriv ett lösenord som uppfyller kraven i båda textrutorna."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Lösenordet uppfyller inte kraven på komplexitet"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Lösenord:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Ändrar lösenord för"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(aktuellt) NT-lösenord:"
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Ange det nya NT-lösenordet: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Skriv det nya NT-lösenordet igen: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "Åtkomst nekades"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "Du måste ändra ditt lösenord nu"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "Lösenordet är för kort"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "Lösenordet är för kort"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "Lösenordet är för kort"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "Lösenord:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "Ditt lösenord"
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "Lösenordet är för kort"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "Ogiltigt förtroendekonto"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "Ditt lösenord gäller inte längre"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "Inga domänkontrollanter hittades"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "Inga inloggningsservrar"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "Det finns ingen tillgänglig primär domänkontrollant"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "Ogiltigt förtroendekonto"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "Inga inloggningsservrar"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/tr.po b/source3/locale/pam_winbind/tr.po
new file mode 100644
index 0000000..ae27bb8
--- /dev/null
+++ b/source3/locale/pam_winbind/tr.po
@@ -0,0 +1,548 @@
+# This file is distributed under the same license as the pam_winbind package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+# Copyright (C) 2010 Christian Perrier <bubulle@debian.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2017-07-02 15:23+0300\n"
+"Last-Translator: Caglar Ulkuderner <caglar@profelis.com.tr>\n"
+"Language-Team: French <debian-l10n-french@lists.debian.org>\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.2\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "Başarılı"
+
+#: ../../../nsswitch/pam_winbind.c:632
+#, fuzzy
+msgid "No primary Domain Controller available"
+msgstr "Hiçbir Etki Alanı Denetleyicisi bulunamadı"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "Hiçbir etki alanı denetleyicileri bulunamadı."
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "Hiçbir oturum açma sunucusu bulunamadı."
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "Parola çok kısa."
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "Kullanıcının parolası değiştirmek için çok yeni."
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "Girilen parola, parola geçmişinde mevcut."
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "Parolanızın süresi dolmuş."
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "Parolanızı değiştirmeniz gerekiyor."
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "Bu iş istasyonunda oturum açmanıza izin verilmiyor."
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "Bu saatte oturum açmanıza izin verilmiyor."
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "Hesabınızın süresi dolmuş. Sistem yöneticinize başvurunuz."
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "Hesabınız devre dışı bırakılmış. Sistem yöneticinize başvurunuz."
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "Hesabınız kilitlenmiş. Sistem yöneticinize başvurunuz."
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "Geçersiz Güven Hesabı."
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "Erişim engellendi."
+
+#: ../../../nsswitch/pam_winbind.c:882
+msgid "Do you want to change your password now?"
+msgstr "Parolanızı şimdi değiştirmek istiyor musunuz?"
+
+#: ../../../nsswitch/pam_winbind.c:963
+msgid "Your password expires today.\n"
+msgstr "Parolanızın süresi bugün sona eriyor.\n"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "Parolanız %d %s içerisinde geçersiz olacak.\n"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "gün"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "gün"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr "%s grubu, sid türüne dönüştürülemiyor, yöneticiniz ile başlantı kurup %s grubunun geçerliliğini kontrol ediniz."
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "Çevrimiçi olduğunuz en kısa süre içerisinde parolanızı değiştiriniz."
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "Etki Alanı Denetleyicisine ulaşılamadığı için kimlik bilgileri önbellekten kullanılıyor. Ağ kaynakları kullanılamıyor olabilir."
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+"Etki Alanı Denetleyicisi ile aradaki zaman farkından dolayı\n"
+"Kerberos Bileti Önbelleği oluşturulamıyor. Sistem zamanını kontrol ediniz.\n"
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "Parolanız "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "en az %d karakter olmalıdır; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "Son %d parola tekrar edilemez; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "büyük harfler, sayılar ve noktalama işaretleri içermelidir; ve tam hesap adınızı içeremez;"
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "Başka bir parola giriniz. Her iki kutucuğa da parola kriterlerine uygun parolanızı giriniz."
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr "Klasör oluşturuluyor: %s başarısız: %s"
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "Parola karmaşıklık gereksinimlerini karşılamıyor."
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr "Kullanıcı Adı: "
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "Parola: "
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "Parolası değiştirilen kullanıcı: "
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(geçerli) Parola: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "Yeni parola: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "Yeni parola (tekrar): "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr "Parolalar uyuşmuyor"
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr "Belirsiz hata"
+
+#: ../../libsmb/nterr.c:560
+msgid "Access denied"
+msgstr "Erişim engellendi"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr "Hesap kilitlendi"
+
+#: ../../libsmb/nterr.c:562
+msgid "Must change password"
+msgstr "Parola değiştirilmelidir"
+
+#: ../../libsmb/nterr.c:563
+msgid "Password is too short"
+msgstr "Parola çok kısa"
+
+#: ../../libsmb/nterr.c:564
+msgid "Password is too recent"
+msgstr "Parola çok yeni"
+
+#: ../../libsmb/nterr.c:565
+msgid "Password history conflict"
+msgstr "Parola geçmişi çakışması"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr "Yanlış oluşturulmuş hesap adı"
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr "Kullanıcı mevcut"
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr "Kullanıcı bulunamadı"
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr "Grup mevcut"
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr "Grup bulunamadı"
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr "Üye bu grupta değil"
+
+#: ../../libsmb/nterr.c:573
+msgid "Wrong Password"
+msgstr "Yanlış Parola"
+
+#: ../../libsmb/nterr.c:574
+msgid "Ill formed password"
+msgstr "Kötü parola"
+
+#: ../../libsmb/nterr.c:575
+msgid "Password restriction"
+msgstr "Parola kısıtlaması"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr "Oturum açma hatası"
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr "Hesap kısıtlaması"
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr "Geçersiz oturum açma saati"
+
+#: ../../libsmb/nterr.c:579
+msgid "Invalid workstation"
+msgstr "Geçersiz Çalışma İstasyonu"
+
+#: ../../libsmb/nterr.c:580
+msgid "Password expired"
+msgstr "Parolanın süresi doldu"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr "Hesap devre dışı"
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr "Bellek ayarlama hatası"
+
+#: ../../libsmb/nterr.c:583
+msgid "No domain controllers located"
+msgstr "Hiçbir Etki Alanı Denetleyicisi bulunamadı"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr "Named pipe uygun değil"
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr "Henüz geliştirilmedi"
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr "Geçersiz bilgi sınıfı"
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr "Bilgi uzunluğu uyumsuz"
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr "Erişim ihlali"
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr "Geçersiz yönetim"
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr "Geçersiz parametre"
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr "Bellek yok"
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr "Tampon bölge çok küçük"
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr "Revizyon uyumsuzluğu"
+
+#: ../../libsmb/nterr.c:594
+msgid "No such logon session"
+msgstr "Böyle bir oturum yok"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr "Böyle bir yetki yok"
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr "Prosedür bulunamadı"
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr "Sunucu devre dışı"
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr "Geçersiz pipe durumu"
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr "Named pipe meşgul"
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr "Geçersiz fonksiyon"
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr "Named pipe çevirim dışı"
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr "Named pipe kapatılıyor"
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr "Uzak sunucu dinlemiyor"
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr "Ağ üzerinde çift isim"
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr "Yazırma kuyruğu dolu"
+
+#: ../../libsmb/nterr.c:606
+msgid "No print spool space available"
+msgstr "Yazdırma biriktirme alanı dolu"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr "Ağ adı bulunamıyor"
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr "Bağlantı reddedildi"
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr "Çok fazla isim"
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr "Çok fazla oturum"
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr "Geçersiz sunucu durumu"
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr "Geçersiz etki alanı durumu"
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr "Geçersiz etki alanı rolü"
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr "Böyle bir etki alanı yok"
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr "Etki alanı mevcut"
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr "Etki alanı limiti aşıldı"
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr "Kötü oturum açma oturumu durumu"
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr "Oturum açma oturum çakışması"
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr "Hatalı oturum açma tipi"
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr "İptal edildi"
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr "Geçersiz bilgisayar adı"
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr "Oturum açma sunucusu çakışması"
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr "Etki alanı saati farklı"
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr "Pipe bozuk"
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr "Kayıt defteri bozuk"
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr "Çok fazla secrets var"
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr "Çok fazla SID var"
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr "Lanmanager cross encryption gerekli"
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr "Kayıt dosyası dolu"
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr "No trusted LSA secret"
+
+#: ../../libsmb/nterr.c:631
+msgid "No trusted SAM account"
+msgstr "No trusted SAM account"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr "Güvenlien etki alanı hatası"
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr "Güven ilişkisi hatası"
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr "Güven hatası"
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr "Netlogon servisi başlamadı"
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr "Hesap süresi dolmuş"
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr "Ağ kimliği çakışması"
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr "Uzak oturum limiti"
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr "No logon interdomain trust account"
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr "No logon workstation trust account"
+
+#: ../../libsmb/nterr.c:641
+msgid "No logon server trust account"
+msgstr "No logon server trust account"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr "Domain trust inconsistent"
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr "No user session key available"
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr "Kullanıcı oturumu silindi"
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr "Yetersiz sunucu kaynağı"
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr "Yetersiz oturum açma bilgileri"
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr "Lisans limiti aşıldı"
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr "Başka dosya yok"
diff --git a/source3/locale/pam_winbind/zh_CN.po b/source3/locale/pam_winbind/zh_CN.po
new file mode 100644
index 0000000..d1049f5
--- /dev/null
+++ b/source3/locale/pam_winbind/zh_CN.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-05 09:53\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "成功"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "没有可用的主域控制器"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "未发现任何域控制器"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "无登录服务器"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "密码太短"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "此用户的密码很新,无法更改"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "密码已在密码历史记录中"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "您的密码已失效"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "您需要立即更改密码"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "不允许您从此工作站登录"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "此时不允许您登录"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "您的帐户已失效。请与系统管理员联系"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "您的帐户已禁用。请与系统管理员联系"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "您的帐户已锁定。请与系统管理员联系"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "无效的信任帐户"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "访问被拒绝"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "您需要立即更改密码"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "您的密码已失效"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "您的密码已失效"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "天"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "天"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "宽限登录。请在您再次联机时立即更改密码"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "域控制器无法访问,改为使用超速缓存的身份凭证。网络资源可能不可用"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "您的密码 "
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "必须至少为 %d 个字符; "
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "不能重复先前 %d 个密码中的任何一个; "
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "必须包含大写字母、数字或标点;且不得包含您的帐户或全名; "
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "请键入其他密码。在两个文本框中键入符合这些要求的密码。"
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "密码不符合复杂度要求"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "密码:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "更改密码"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(当前)NT 密码: "
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "输入新 NT 密码: "
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "重键入新 NT 密码: "
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "访问被拒绝"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "您需要立即更改密码"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "密码太短"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "密码太短"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "密码太短"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "密码:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "您的密码 "
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "密码太短"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "无效的信任帐户"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "您的密码已失效"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "未发现任何域控制器"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "无登录服务器"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "没有可用的主域控制器"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "无效的信任帐户"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "无登录服务器"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locale/pam_winbind/zh_TW.po b/source3/locale/pam_winbind/zh_TW.po
new file mode 100644
index 0000000..ca05b87
--- /dev/null
+++ b/source3/locale/pam_winbind/zh_TW.po
@@ -0,0 +1,559 @@
+# This file is distributed under the same license as the package.
+#
+# Copyright (C) 2009 Lars Mueller <lars@samba.org>
+msgid ""
+msgstr ""
+"Project-Id-Version: pam_winbind\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-15 02:10+0300\n"
+"PO-Revision-Date: 2009-02-04 15:44\n"
+"Last-Translator: Novell Language <language@novell.com>\n"
+"Language-Team: Novell Language <language@novell.com>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../../nsswitch/pam_winbind.c:630
+msgid "Success"
+msgstr "成功"
+
+#: ../../../nsswitch/pam_winbind.c:632
+msgid "No primary Domain Controller available"
+msgstr "無可用的主要領域控制器"
+
+#: ../../../nsswitch/pam_winbind.c:634
+msgid "No domain controllers found"
+msgstr "找不到領域控制器"
+
+#: ../../../nsswitch/pam_winbind.c:636
+msgid "No logon servers"
+msgstr "沒有登入伺服器"
+
+#: ../../../nsswitch/pam_winbind.c:638
+msgid "Password too short"
+msgstr "密碼太短"
+
+#: ../../../nsswitch/pam_winbind.c:640
+msgid "The password of this user is too recent to change"
+msgstr "此使用者的密碼太新,無法變更"
+
+#: ../../../nsswitch/pam_winbind.c:642
+msgid "Password is already in password history"
+msgstr "密碼歷程記錄中已存在此密碼"
+
+#: ../../../nsswitch/pam_winbind.c:644
+msgid "Your password has expired"
+msgstr "您的密碼已過期"
+
+#: ../../../nsswitch/pam_winbind.c:646
+msgid "You need to change your password now"
+msgstr "您需要立即變更密碼"
+
+#: ../../../nsswitch/pam_winbind.c:648
+msgid "You are not allowed to logon from this workstation"
+msgstr "您不允許從此工作站登入"
+
+#: ../../../nsswitch/pam_winbind.c:650
+msgid "You are not allowed to logon at this time"
+msgstr "目前您不允許登入"
+
+#: ../../../nsswitch/pam_winbind.c:652
+msgid "Your account has expired. Please contact your System administrator"
+msgstr "您的帳戶已經過期,請聯絡您的系統管理員"
+
+#: ../../../nsswitch/pam_winbind.c:655
+msgid "Your account is disabled. Please contact your System administrator"
+msgstr "您的帳戶已經停用,請聯絡您的系統管理員"
+
+#: ../../../nsswitch/pam_winbind.c:658
+msgid "Your account has been locked. Please contact your System administrator"
+msgstr "您的帳戶已經鎖定,請聯絡您的系統管理員"
+
+#: ../../../nsswitch/pam_winbind.c:661 ../../../nsswitch/pam_winbind.c:663 ../../../nsswitch/pam_winbind.c:665
+msgid "Invalid Trust Account"
+msgstr "無效的信任帳戶"
+
+#: ../../../nsswitch/pam_winbind.c:667
+msgid "Access is denied"
+msgstr "存取遭拒"
+
+#: ../../../nsswitch/pam_winbind.c:882
+#, fuzzy
+msgid "Do you want to change your password now?"
+msgstr "您需要立即變更密碼"
+
+#: ../../../nsswitch/pam_winbind.c:963
+#, fuzzy
+msgid "Your password expires today.\n"
+msgstr "您的密碼已過期"
+
+#: ../../../nsswitch/pam_winbind.c:993
+#, fuzzy, c-format
+msgid "Your password will expire in %d %s.\n"
+msgstr "您的密碼已過期"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "days"
+msgstr "日"
+
+#: ../../../nsswitch/pam_winbind.c:994
+msgid "day"
+msgstr "日"
+
+#: ../../../nsswitch/pam_winbind.c:1201 ../../../nsswitch/pam_winbind.c:1225
+#, c-format
+msgid "Cannot convert group %s to sid, please contact your administrator to see if group %s is valid."
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1428
+msgid "Grace login. Please change your password as soon you're online again"
+msgstr "寬限登入。請於您再次上線時立即變更密碼"
+
+#: ../../../nsswitch/pam_winbind.c:1438
+msgid "Domain Controller unreachable, using cached credentials instead. Network resources may be unavailable"
+msgstr "領域控制器無法連線,改用快取的身份證明。網路資源可能不可用"
+
+#: ../../../nsswitch/pam_winbind.c:1463
+msgid ""
+"Failed to establish your Kerberos Ticket cache due time differences\n"
+"with the domain controller. Please verify the system time.\n"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:1531
+msgid "Your password "
+msgstr "您的密碼"
+
+#: ../../../nsswitch/pam_winbind.c:1538
+#, c-format
+msgid "must be at least %d characters; "
+msgstr "必須至少包含 %d 個字元;"
+
+#: ../../../nsswitch/pam_winbind.c:1547
+#, c-format
+msgid "cannot repeat any of your previous %d passwords; "
+msgstr "不能重複使用前 %d 個密碼;"
+
+#: ../../../nsswitch/pam_winbind.c:1557
+msgid "must contain capitals, numerals or punctuation; and cannot contain your account or full name; "
+msgstr "必須包含大寫字母、數字或標點符號;並且不能包含您的帳戶或全名;"
+
+#: ../../../nsswitch/pam_winbind.c:1567
+msgid "Please type a different password. Type a password which meets these requirements in both text boxes."
+msgstr "請輸入另一個密碼。請在兩個文字方塊中輸入符合這些要求的密碼。"
+
+#: ../../../nsswitch/pam_winbind.c:1603
+#, c-format
+msgid "Creating directory: %s failed: %s"
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2066
+msgid "Password does not meet complexity requirements"
+msgstr "密碼不符合複雜性要求"
+
+#.
+#. * First get the name of a user
+#.
+#: ../../../nsswitch/pam_winbind.c:2552 ../../../nsswitch/pam_winbind.c:3177
+msgid "Username: "
+msgstr ""
+
+#: ../../../nsswitch/pam_winbind.c:2803
+msgid "Password: "
+msgstr "密碼:"
+
+#. instruct user what is happening
+#: ../../../nsswitch/pam_winbind.c:3215
+msgid "Changing password for"
+msgstr "變更密碼 -"
+
+#: ../../../nsswitch/pam_winbind.c:3228
+msgid "(current) NT password: "
+msgstr "(目前) NT 密碼:"
+
+#: ../../../nsswitch/pam_winbind.c:3301
+msgid "Enter new NT password: "
+msgstr "輸入新 NT 密碼︰"
+
+#: ../../../nsswitch/pam_winbind.c:3302
+msgid "Retype new NT password: "
+msgstr "重新輸入新 NT 密碼:"
+
+#.
+#. * here is the string to inform the user that the new passwords they
+#. * typed were not the same.
+#.
+#: ../../../nsswitch/pam_winbind.h:128
+msgid "Sorry, passwords do not match"
+msgstr ""
+
+#: ../../libsmb/nterr.c:559
+msgid "Undetermined error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:560
+#, fuzzy
+msgid "Access denied"
+msgstr "存取遭拒"
+
+#: ../../libsmb/nterr.c:561
+msgid "Account locked out"
+msgstr ""
+
+#: ../../libsmb/nterr.c:562
+#, fuzzy
+msgid "Must change password"
+msgstr "您需要立即變更密碼"
+
+#: ../../libsmb/nterr.c:563
+#, fuzzy
+msgid "Password is too short"
+msgstr "密碼太短"
+
+#: ../../libsmb/nterr.c:564
+#, fuzzy
+msgid "Password is too recent"
+msgstr "密碼太短"
+
+#: ../../libsmb/nterr.c:565
+#, fuzzy
+msgid "Password history conflict"
+msgstr "密碼太短"
+
+#: ../../libsmb/nterr.c:567
+msgid "Improperly formed account name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:568
+msgid "User exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:569
+msgid "No such user"
+msgstr ""
+
+#: ../../libsmb/nterr.c:570
+msgid "Group exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:571
+msgid "No such group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:572
+msgid "Member not in group"
+msgstr ""
+
+#: ../../libsmb/nterr.c:573
+#, fuzzy
+msgid "Wrong Password"
+msgstr "密碼:"
+
+#: ../../libsmb/nterr.c:574
+#, fuzzy
+msgid "Ill formed password"
+msgstr "您的密碼"
+
+#: ../../libsmb/nterr.c:575
+#, fuzzy
+msgid "Password restriction"
+msgstr "密碼太短"
+
+#: ../../libsmb/nterr.c:576
+msgid "Logon failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:577
+msgid "Account restriction"
+msgstr ""
+
+#: ../../libsmb/nterr.c:578
+msgid "Invalid logon hours"
+msgstr ""
+
+#: ../../libsmb/nterr.c:579
+#, fuzzy
+msgid "Invalid workstation"
+msgstr "無效的信任帳戶"
+
+#: ../../libsmb/nterr.c:580
+#, fuzzy
+msgid "Password expired"
+msgstr "您的密碼已過期"
+
+#: ../../libsmb/nterr.c:581
+msgid "Account disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:582
+msgid "Memory allocation error"
+msgstr ""
+
+#: ../../libsmb/nterr.c:583
+#, fuzzy
+msgid "No domain controllers located"
+msgstr "找不到領域控制器"
+
+#: ../../libsmb/nterr.c:584
+msgid "Named pipe not available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:585
+msgid "Not implemented"
+msgstr ""
+
+#: ../../libsmb/nterr.c:586
+msgid "Invalid information class"
+msgstr ""
+
+#: ../../libsmb/nterr.c:587
+msgid "Information length mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:588
+msgid "Access violation"
+msgstr ""
+
+#: ../../libsmb/nterr.c:589
+msgid "Invalid handle"
+msgstr ""
+
+#: ../../libsmb/nterr.c:590
+msgid "Invalid parameter"
+msgstr ""
+
+#: ../../libsmb/nterr.c:591
+msgid "No memory"
+msgstr ""
+
+#: ../../libsmb/nterr.c:592
+msgid "Buffer too small"
+msgstr ""
+
+#: ../../libsmb/nterr.c:593
+msgid "Revision mismatch"
+msgstr ""
+
+#: ../../libsmb/nterr.c:594
+#, fuzzy
+msgid "No such logon session"
+msgstr "沒有登入伺服器"
+
+#: ../../libsmb/nterr.c:595
+msgid "No such privilege"
+msgstr ""
+
+#: ../../libsmb/nterr.c:596
+msgid "Procedure not found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:597
+msgid "Server disabled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:598
+msgid "Invalid pipe state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:599
+msgid "Named pipe busy"
+msgstr ""
+
+#: ../../libsmb/nterr.c:600
+msgid "Illegal function"
+msgstr ""
+
+#: ../../libsmb/nterr.c:601
+msgid "Named pipe disconnected"
+msgstr ""
+
+#: ../../libsmb/nterr.c:602
+msgid "Named pipe closing"
+msgstr ""
+
+#: ../../libsmb/nterr.c:603
+msgid "Remote host not listening"
+msgstr ""
+
+#: ../../libsmb/nterr.c:604
+msgid "Duplicate name on network"
+msgstr ""
+
+#: ../../libsmb/nterr.c:605
+msgid "Print queue is full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:606
+#, fuzzy
+msgid "No print spool space available"
+msgstr "無可用的主要領域控制器"
+
+#: ../../libsmb/nterr.c:607
+msgid "The network name cannot be found"
+msgstr ""
+
+#: ../../libsmb/nterr.c:608
+msgid "The connection was refused"
+msgstr ""
+
+#: ../../libsmb/nterr.c:609
+msgid "Too many names"
+msgstr ""
+
+#: ../../libsmb/nterr.c:610
+msgid "Too many sessions"
+msgstr ""
+
+#: ../../libsmb/nterr.c:611
+msgid "Invalid server state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:612
+msgid "Invalid domain state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:613
+msgid "Invalid domain role"
+msgstr ""
+
+#: ../../libsmb/nterr.c:614
+msgid "No such domain"
+msgstr ""
+
+#: ../../libsmb/nterr.c:615
+msgid "Domain exists"
+msgstr ""
+
+#: ../../libsmb/nterr.c:616
+msgid "Domain limit exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:617
+msgid "Bad logon session state"
+msgstr ""
+
+#: ../../libsmb/nterr.c:618
+msgid "Logon session collision"
+msgstr ""
+
+#: ../../libsmb/nterr.c:619
+msgid "Invalid logon type"
+msgstr ""
+
+#: ../../libsmb/nterr.c:620
+msgid "Cancelled"
+msgstr ""
+
+#: ../../libsmb/nterr.c:621
+msgid "Invalid computer name"
+msgstr ""
+
+#: ../../libsmb/nterr.c:622
+msgid "Logon server conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:623
+msgid "Time difference at domain controller"
+msgstr ""
+
+#: ../../libsmb/nterr.c:624
+msgid "Pipe broken"
+msgstr ""
+
+#: ../../libsmb/nterr.c:625
+msgid "Registry corrupt"
+msgstr ""
+
+#: ../../libsmb/nterr.c:626
+msgid "Too many secrets"
+msgstr ""
+
+#: ../../libsmb/nterr.c:627
+msgid "Too many SIDs"
+msgstr ""
+
+#: ../../libsmb/nterr.c:628
+msgid "Lanmanager cross encryption required"
+msgstr ""
+
+#: ../../libsmb/nterr.c:629
+msgid "Log file full"
+msgstr ""
+
+#: ../../libsmb/nterr.c:630
+msgid "No trusted LSA secret"
+msgstr ""
+
+#: ../../libsmb/nterr.c:631
+#, fuzzy
+msgid "No trusted SAM account"
+msgstr "無效的信任帳戶"
+
+#: ../../libsmb/nterr.c:632
+msgid "Trusted domain failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:633
+msgid "Trust relationship failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:634
+msgid "Trust failure"
+msgstr ""
+
+#: ../../libsmb/nterr.c:635
+msgid "Netlogon service not started"
+msgstr ""
+
+#: ../../libsmb/nterr.c:636
+msgid "Account expired"
+msgstr ""
+
+#: ../../libsmb/nterr.c:637
+msgid "Network credential conflict"
+msgstr ""
+
+#: ../../libsmb/nterr.c:638
+msgid "Remote session limit"
+msgstr ""
+
+#: ../../libsmb/nterr.c:639
+msgid "No logon interdomain trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:640
+msgid "No logon workstation trust account"
+msgstr ""
+
+#: ../../libsmb/nterr.c:641
+#, fuzzy
+msgid "No logon server trust account"
+msgstr "沒有登入伺服器"
+
+#: ../../libsmb/nterr.c:642
+msgid "Domain trust inconsistent"
+msgstr ""
+
+#: ../../libsmb/nterr.c:643
+msgid "No user session key available"
+msgstr ""
+
+#: ../../libsmb/nterr.c:644
+msgid "User session deleted"
+msgstr ""
+
+#: ../../libsmb/nterr.c:645
+msgid "Insufficient server resources"
+msgstr ""
+
+#: ../../libsmb/nterr.c:646
+msgid "Insufficient logon information"
+msgstr ""
+
+#: ../../libsmb/nterr.c:648
+msgid "License quota exceeded"
+msgstr ""
+
+#: ../../libsmb/nterr.c:649
+msgid "No more files"
+msgstr ""
diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
new file mode 100644
index 0000000..905da04
--- /dev/null
+++ b/source3/locking/brlock.c
@@ -0,0 +1,1998 @@
+/*
+ Unix SMB/CIFS implementation.
+ byte range locking code
+ Updated to handle range splits/merges.
+
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* This module implements a tdb based byte range locking service,
+ replacing the fcntl() based byte range locking previously
+ used. This allows us to provide the same semantics as NT */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "locking/proto.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "serverid.h"
+#include "messages.h"
+#include "util_tdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+#define ZERO_ZERO 0
+
+/* The open brlock.tdb database. */
+
+static struct db_context *brlock_db;
+
+struct byte_range_lock {
+ struct files_struct *fsp;
+ TALLOC_CTX *req_mem_ctx;
+ const struct GUID *req_guid;
+ unsigned int num_locks;
+ bool modified;
+ struct lock_struct *lock_data;
+ struct db_record *record;
+};
+
+/****************************************************************************
+ Debug info at level 10 for lock struct.
+****************************************************************************/
+
+static void print_lock_struct(unsigned int i, const struct lock_struct *pls)
+{
+ struct server_id_buf tmp;
+
+ DBG_DEBUG("[%u]: smblctx = %"PRIu64", tid = %"PRIu32", pid = %s, "
+ "start = %"PRIu64", size = %"PRIu64", fnum = %"PRIu64", "
+ "%s %s\n",
+ i,
+ pls->context.smblctx,
+ pls->context.tid,
+ server_id_str_buf(pls->context.pid, &tmp),
+ pls->start,
+ pls->size,
+ pls->fnum,
+ lock_type_name(pls->lock_type),
+ lock_flav_name(pls->lock_flav));
+}
+
+unsigned int brl_num_locks(const struct byte_range_lock *brl)
+{
+ return brl->num_locks;
+}
+
+struct files_struct *brl_fsp(struct byte_range_lock *brl)
+{
+ return brl->fsp;
+}
+
+TALLOC_CTX *brl_req_mem_ctx(const struct byte_range_lock *brl)
+{
+ if (brl->req_mem_ctx == NULL) {
+ return talloc_get_type_abort(brl, struct byte_range_lock);
+ }
+
+ return brl->req_mem_ctx;
+}
+
+const struct GUID *brl_req_guid(const struct byte_range_lock *brl)
+{
+ if (brl->req_guid == NULL) {
+ static const struct GUID brl_zero_req_guid;
+ return &brl_zero_req_guid;
+ }
+
+ return brl->req_guid;
+}
+
+/****************************************************************************
+ See if two locking contexts are equal.
+****************************************************************************/
+
+static bool brl_same_context(const struct lock_context *ctx1,
+ const struct lock_context *ctx2)
+{
+ return (server_id_equal(&ctx1->pid, &ctx2->pid) &&
+ (ctx1->smblctx == ctx2->smblctx) &&
+ (ctx1->tid == ctx2->tid));
+}
+
+bool byte_range_valid(uint64_t ofs, uint64_t len)
+{
+ uint64_t max_len = UINT64_MAX - ofs;
+ uint64_t effective_len;
+
+ /*
+ * [MS-FSA] specifies this:
+ *
+ * If (((FileOffset + Length - 1) < FileOffset) && Length != 0) {
+ * return STATUS_INVALID_LOCK_RANGE
+ * }
+ *
+ * We avoid integer wrapping and calculate
+ * max and effective len instead.
+ */
+
+ if (len == 0) {
+ return true;
+ }
+
+ effective_len = len - 1;
+ if (effective_len <= max_len) {
+ return true;
+ }
+
+ return false;
+}
+
+bool byte_range_overlap(uint64_t ofs1,
+ uint64_t len1,
+ uint64_t ofs2,
+ uint64_t len2)
+{
+ uint64_t last1;
+ uint64_t last2;
+ bool valid;
+
+ /*
+ * This is based on [MS-FSA] 2.1.4.10
+ * Algorithm for Determining If a Range Access
+ * Conflicts with Byte-Range Locks
+ */
+
+ /*
+ * The {0, 0} range doesn't conflict with any byte-range lock
+ */
+ if (ofs1 == 0 && len1 == 0) {
+ return false;
+ }
+ if (ofs2 == 0 && len2 == 0) {
+ return false;
+ }
+
+ /*
+ * The caller should have checked that the ranges are
+ * valid. But currently we gracefully handle
+ * the overflow of a read/write check.
+ */
+ valid = byte_range_valid(ofs1, len1);
+ if (valid) {
+ last1 = ofs1 + len1 - 1;
+ } else {
+ last1 = UINT64_MAX;
+ }
+ valid = byte_range_valid(ofs2, len2);
+ if (valid) {
+ last2 = ofs2 + len2 - 1;
+ } else {
+ last2 = UINT64_MAX;
+ }
+
+ /*
+ * If one range starts after the last
+ * byte of the other range there's
+ * no conflict.
+ */
+ if (ofs1 > last2) {
+ return false;
+ }
+ if (ofs2 > last1) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ See if lck1 and lck2 overlap.
+****************************************************************************/
+
+static bool brl_overlap(const struct lock_struct *lck1,
+ const struct lock_struct *lck2)
+{
+ return byte_range_overlap(lck1->start,
+ lck1->size,
+ lck2->start,
+ lck2->size);
+}
+
+/****************************************************************************
+ See if lock2 can be added when lock1 is in place.
+****************************************************************************/
+
+static bool brl_conflict(const struct lock_struct *lck1,
+ const struct lock_struct *lck2)
+{
+ /* Read locks never conflict. */
+ if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) {
+ return False;
+ }
+
+ /* A READ lock can stack on top of a WRITE lock if they have the same
+ * context & fnum. */
+ if (lck1->lock_type == WRITE_LOCK && lck2->lock_type == READ_LOCK &&
+ brl_same_context(&lck1->context, &lck2->context) &&
+ lck1->fnum == lck2->fnum) {
+ return False;
+ }
+
+ return brl_overlap(lck1, lck2);
+}
+
+/****************************************************************************
+ See if lock2 can be added when lock1 is in place - when both locks are POSIX
+ flavour. POSIX locks ignore fnum - they only care about dev/ino which we
+ know already match.
+****************************************************************************/
+
+static bool brl_conflict_posix(const struct lock_struct *lck1,
+ const struct lock_struct *lck2)
+{
+#if defined(DEVELOPER)
+ SMB_ASSERT(lck1->lock_flav == POSIX_LOCK);
+ SMB_ASSERT(lck2->lock_flav == POSIX_LOCK);
+#endif
+
+ /* Read locks never conflict. */
+ if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) {
+ return False;
+ }
+
+ /* Locks on the same context don't conflict. Ignore fnum. */
+ if (brl_same_context(&lck1->context, &lck2->context)) {
+ return False;
+ }
+
+ /* One is read, the other write, or the context is different,
+ do they overlap ? */
+ return brl_overlap(lck1, lck2);
+}
+
+#if ZERO_ZERO
+static bool brl_conflict1(const struct lock_struct *lck1,
+ const struct lock_struct *lck2)
+{
+ if (lck1->lock_type == READ_LOCK && lck2->lock_type == READ_LOCK) {
+ return False;
+ }
+
+ if (brl_same_context(&lck1->context, &lck2->context) &&
+ lck2->lock_type == READ_LOCK && lck1->fnum == lck2->fnum) {
+ return False;
+ }
+
+ if (lck2->start == 0 && lck2->size == 0 && lck1->size != 0) {
+ return True;
+ }
+
+ if (lck1->start >= (lck2->start + lck2->size) ||
+ lck2->start >= (lck1->start + lck1->size)) {
+ return False;
+ }
+
+ return True;
+}
+#endif
+
+/****************************************************************************
+ Check to see if this lock conflicts, but ignore our own locks on the
+ same fnum only. This is the read/write lock check code path.
+ This is never used in the POSIX lock case.
+****************************************************************************/
+
+static bool brl_conflict_other(const struct lock_struct *lock,
+ const struct lock_struct *rw_probe)
+{
+ if (lock->lock_type == READ_LOCK && rw_probe->lock_type == READ_LOCK) {
+ return False;
+ }
+
+ if (lock->lock_flav == POSIX_LOCK &&
+ rw_probe->lock_flav == POSIX_LOCK) {
+ /*
+ * POSIX flavour locks never conflict here - this is only called
+ * in the read/write path.
+ */
+ return False;
+ }
+
+ if (!brl_overlap(lock, rw_probe)) {
+ /*
+ * I/O can only conflict when overlapping a lock, thus let it
+ * pass
+ */
+ return false;
+ }
+
+ if (!brl_same_context(&lock->context, &rw_probe->context)) {
+ /*
+ * Different process, conflict
+ */
+ return true;
+ }
+
+ if (lock->fnum != rw_probe->fnum) {
+ /*
+ * Different file handle, conflict
+ */
+ return true;
+ }
+
+ if ((lock->lock_type == READ_LOCK) &&
+ (rw_probe->lock_type == WRITE_LOCK)) {
+ /*
+ * Incoming WRITE locks conflict with existing READ locks even
+ * if the context is the same. JRA. See LOCKTEST7 in
+ * smbtorture.
+ */
+ return true;
+ }
+
+ /*
+ * I/O request compatible with existing lock, let it pass without
+ * conflict
+ */
+
+ return false;
+}
+
+/****************************************************************************
+ Open up the brlock.tdb database.
+****************************************************************************/
+
+void brl_init(bool read_only)
+{
+ int tdb_flags;
+ char *db_path;
+
+ if (brlock_db) {
+ return;
+ }
+
+ tdb_flags = SMBD_VOLATILE_TDB_FLAGS | TDB_SEQNUM;
+
+ db_path = lock_path(talloc_tos(), "brlock.tdb");
+ if (db_path == NULL) {
+ DEBUG(0, ("out of memory!\n"));
+ return;
+ }
+
+ brlock_db = db_open(NULL, db_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE, tdb_flags,
+ read_only?O_RDONLY:(O_RDWR|O_CREAT), 0644,
+ DBWRAP_LOCK_ORDER_2, DBWRAP_FLAG_NONE);
+ if (!brlock_db) {
+ DEBUG(0,("Failed to open byte range locking database %s\n",
+ db_path));
+ TALLOC_FREE(db_path);
+ return;
+ }
+ TALLOC_FREE(db_path);
+}
+
+/****************************************************************************
+ Close down the brlock.tdb database.
+****************************************************************************/
+
+void brl_shutdown(void)
+{
+ TALLOC_FREE(brlock_db);
+}
+
+#if ZERO_ZERO
+/****************************************************************************
+ Compare two locks for sorting.
+****************************************************************************/
+
+static int lock_compare(const struct lock_struct *lck1,
+ const struct lock_struct *lck2)
+{
+ if (lck1->start != lck2->start) {
+ return (lck1->start - lck2->start);
+ }
+ if (lck2->size != lck1->size) {
+ return ((int)lck1->size - (int)lck2->size);
+ }
+ return 0;
+}
+#endif
+
+/****************************************************************************
+ Lock a range of bytes - Windows lock semantics.
+****************************************************************************/
+
+NTSTATUS brl_lock_windows_default(struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ unsigned int i;
+ files_struct *fsp = br_lck->fsp;
+ struct lock_struct *locks = br_lck->lock_data;
+ NTSTATUS status;
+ bool valid;
+
+ SMB_ASSERT(plock->lock_type != UNLOCK_LOCK);
+
+ valid = byte_range_valid(plock->start, plock->size);
+ if (!valid) {
+ return NT_STATUS_INVALID_LOCK_RANGE;
+ }
+
+ for (i=0; i < br_lck->num_locks; i++) {
+ /* Do any Windows or POSIX locks conflict ? */
+ if (brl_conflict(&locks[i], plock)) {
+ if (!serverid_exists(&locks[i].context.pid)) {
+ locks[i].context.pid.pid = 0;
+ br_lck->modified = true;
+ continue;
+ }
+ /* Remember who blocked us. */
+ plock->context.smblctx = locks[i].context.smblctx;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+#if ZERO_ZERO
+ if (plock->start == 0 && plock->size == 0 &&
+ locks[i].size == 0) {
+ break;
+ }
+#endif
+ }
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WINDOWS_BRL);
+
+ /* We can get the Windows lock, now see if it needs to
+ be mapped into a lower level POSIX one, and if so can
+ we get it ? */
+
+ if (lp_posix_locking(fsp->conn->params)) {
+ int errno_ret;
+ if (!set_posix_lock_windows_flavour(fsp,
+ plock->start,
+ plock->size,
+ plock->lock_type,
+ &plock->context,
+ locks,
+ br_lck->num_locks,
+ &errno_ret)) {
+
+ /* We don't know who blocked us. */
+ plock->context.smblctx = 0xFFFFFFFFFFFFFFFFLL;
+
+ if (errno_ret == EACCES || errno_ret == EAGAIN) {
+ status = NT_STATUS_LOCK_NOT_GRANTED;
+ goto fail;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+ }
+ }
+
+ /* no conflicts - add it to the list of locks */
+ locks = talloc_realloc(br_lck, locks, struct lock_struct,
+ (br_lck->num_locks + 1));
+ if (!locks) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ memcpy(&locks[br_lck->num_locks], plock, sizeof(struct lock_struct));
+ br_lck->num_locks += 1;
+ br_lck->lock_data = locks;
+ br_lck->modified = True;
+
+ return NT_STATUS_OK;
+ fail:
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WINDOWS_BRL);
+ return status;
+}
+
+/****************************************************************************
+ Cope with POSIX range splits and merges.
+****************************************************************************/
+
+static unsigned int brlock_posix_split_merge(struct lock_struct *lck_arr, /* Output array. */
+ struct lock_struct *ex, /* existing lock. */
+ struct lock_struct *plock) /* proposed lock. */
+{
+ bool lock_types_differ = (ex->lock_type != plock->lock_type);
+
+ /* We can't merge non-conflicting locks on different context - ignore fnum. */
+
+ if (!brl_same_context(&ex->context, &plock->context)) {
+ /* Just copy. */
+ memcpy(&lck_arr[0], ex, sizeof(struct lock_struct));
+ return 1;
+ }
+
+ /* We now know we have the same context. */
+
+ /* Did we overlap ? */
+
+/*********************************************
+ +---------+
+ | ex |
+ +---------+
+ +-------+
+ | plock |
+ +-------+
+OR....
+ +---------+
+ | ex |
+ +---------+
+**********************************************/
+
+ if ( (ex->start > (plock->start + plock->size)) ||
+ (plock->start > (ex->start + ex->size))) {
+
+ /* No overlap with this lock - copy existing. */
+
+ memcpy(&lck_arr[0], ex, sizeof(struct lock_struct));
+ return 1;
+ }
+
+/*********************************************
+ +---------------------------+
+ | ex |
+ +---------------------------+
+ +---------------------------+
+ | plock | -> replace with plock.
+ +---------------------------+
+OR
+ +---------------+
+ | ex |
+ +---------------+
+ +---------------------------+
+ | plock | -> replace with plock.
+ +---------------------------+
+
+**********************************************/
+
+ if ( (ex->start >= plock->start) &&
+ (ex->start + ex->size <= plock->start + plock->size) ) {
+
+ /* Replace - discard existing lock. */
+
+ return 0;
+ }
+
+/*********************************************
+Adjacent after.
+ +-------+
+ | ex |
+ +-------+
+ +---------------+
+ | plock |
+ +---------------+
+
+BECOMES....
+ +---------------+-------+
+ | plock | ex | - different lock types.
+ +---------------+-------+
+OR.... (merge)
+ +-----------------------+
+ | plock | - same lock type.
+ +-----------------------+
+**********************************************/
+
+ if (plock->start + plock->size == ex->start) {
+
+ /* If the lock types are the same, we merge, if different, we
+ add the remainder of the old lock. */
+
+ if (lock_types_differ) {
+ /* Add existing. */
+ memcpy(&lck_arr[0], ex, sizeof(struct lock_struct));
+ return 1;
+ } else {
+ /* Merge - adjust incoming lock as we may have more
+ * merging to come. */
+ plock->size += ex->size;
+ return 0;
+ }
+ }
+
+/*********************************************
+Adjacent before.
+ +-------+
+ | ex |
+ +-------+
+ +---------------+
+ | plock |
+ +---------------+
+BECOMES....
+ +-------+---------------+
+ | ex | plock | - different lock types
+ +-------+---------------+
+
+OR.... (merge)
+ +-----------------------+
+ | plock | - same lock type.
+ +-----------------------+
+
+**********************************************/
+
+ if (ex->start + ex->size == plock->start) {
+
+ /* If the lock types are the same, we merge, if different, we
+ add the existing lock. */
+
+ if (lock_types_differ) {
+ memcpy(&lck_arr[0], ex, sizeof(struct lock_struct));
+ return 1;
+ } else {
+ /* Merge - adjust incoming lock as we may have more
+ * merging to come. */
+ plock->start = ex->start;
+ plock->size += ex->size;
+ return 0;
+ }
+ }
+
+/*********************************************
+Overlap after.
+ +-----------------------+
+ | ex |
+ +-----------------------+
+ +---------------+
+ | plock |
+ +---------------+
+OR
+ +----------------+
+ | ex |
+ +----------------+
+ +---------------+
+ | plock |
+ +---------------+
+
+BECOMES....
+ +---------------+-------+
+ | plock | ex | - different lock types.
+ +---------------+-------+
+OR.... (merge)
+ +-----------------------+
+ | plock | - same lock type.
+ +-----------------------+
+**********************************************/
+
+ if ( (ex->start >= plock->start) &&
+ (ex->start <= plock->start + plock->size) &&
+ (ex->start + ex->size > plock->start + plock->size) ) {
+
+ /* If the lock types are the same, we merge, if different, we
+ add the remainder of the old lock. */
+
+ if (lock_types_differ) {
+ /* Add remaining existing. */
+ memcpy(&lck_arr[0], ex, sizeof(struct lock_struct));
+ /* Adjust existing start and size. */
+ lck_arr[0].start = plock->start + plock->size;
+ lck_arr[0].size = (ex->start + ex->size) - (plock->start + plock->size);
+ return 1;
+ } else {
+ /* Merge - adjust incoming lock as we may have more
+ * merging to come. */
+ plock->size += (ex->start + ex->size) - (plock->start + plock->size);
+ return 0;
+ }
+ }
+
+/*********************************************
+Overlap before.
+ +-----------------------+
+ | ex |
+ +-----------------------+
+ +---------------+
+ | plock |
+ +---------------+
+OR
+ +-------------+
+ | ex |
+ +-------------+
+ +---------------+
+ | plock |
+ +---------------+
+
+BECOMES....
+ +-------+---------------+
+ | ex | plock | - different lock types
+ +-------+---------------+
+
+OR.... (merge)
+ +-----------------------+
+ | plock | - same lock type.
+ +-----------------------+
+
+**********************************************/
+
+ if ( (ex->start < plock->start) &&
+ (ex->start + ex->size >= plock->start) &&
+ (ex->start + ex->size <= plock->start + plock->size) ) {
+
+ /* If the lock types are the same, we merge, if different, we
+ add the truncated old lock. */
+
+ if (lock_types_differ) {
+ memcpy(&lck_arr[0], ex, sizeof(struct lock_struct));
+ /* Adjust existing size. */
+ lck_arr[0].size = plock->start - ex->start;
+ return 1;
+ } else {
+ /* Merge - adjust incoming lock as we may have more
+ * merging to come. MUST ADJUST plock SIZE FIRST ! */
+ plock->size += (plock->start - ex->start);
+ plock->start = ex->start;
+ return 0;
+ }
+ }
+
+/*********************************************
+Complete overlap.
+ +---------------------------+
+ | ex |
+ +---------------------------+
+ +---------+
+ | plock |
+ +---------+
+BECOMES.....
+ +-------+---------+---------+
+ | ex | plock | ex | - different lock types.
+ +-------+---------+---------+
+OR
+ +---------------------------+
+ | plock | - same lock type.
+ +---------------------------+
+**********************************************/
+
+ if ( (ex->start < plock->start) && (ex->start + ex->size > plock->start + plock->size) ) {
+
+ if (lock_types_differ) {
+
+ /* We have to split ex into two locks here. */
+
+ memcpy(&lck_arr[0], ex, sizeof(struct lock_struct));
+ memcpy(&lck_arr[1], ex, sizeof(struct lock_struct));
+
+ /* Adjust first existing size. */
+ lck_arr[0].size = plock->start - ex->start;
+
+ /* Adjust second existing start and size. */
+ lck_arr[1].start = plock->start + plock->size;
+ lck_arr[1].size = (ex->start + ex->size) - (plock->start + plock->size);
+ return 2;
+ } else {
+ /* Just eat the existing locks, merge them into plock. */
+ plock->start = ex->start;
+ plock->size = ex->size;
+ return 0;
+ }
+ }
+
+ /* Never get here. */
+ smb_panic("brlock_posix_split_merge");
+ /* Notreached. */
+
+ /* Keep some compilers happy. */
+ return 0;
+}
+
+/****************************************************************************
+ Lock a range of bytes - POSIX lock semantics.
+ We must cope with range splits and merges.
+****************************************************************************/
+
+static NTSTATUS brl_lock_posix(struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ unsigned int i, count, posix_count;
+ struct lock_struct *locks = br_lck->lock_data;
+ struct lock_struct *tp;
+ bool break_oplocks = false;
+ NTSTATUS status;
+
+ /* No zero-zero locks for POSIX. */
+ if (plock->start == 0 && plock->size == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Don't allow 64-bit lock wrap. */
+ if (plock->start + plock->size - 1 < plock->start) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* The worst case scenario here is we have to split an
+ existing POSIX lock range into two, and add our lock,
+ so we need at most 2 more entries. */
+
+ tp = talloc_array(br_lck, struct lock_struct, br_lck->num_locks + 2);
+ if (!tp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ count = posix_count = 0;
+
+ for (i=0; i < br_lck->num_locks; i++) {
+ struct lock_struct *curr_lock = &locks[i];
+
+ if (curr_lock->lock_flav == WINDOWS_LOCK) {
+ /* Do any Windows flavour locks conflict ? */
+ if (brl_conflict(curr_lock, plock)) {
+ if (!serverid_exists(&curr_lock->context.pid)) {
+ curr_lock->context.pid.pid = 0;
+ br_lck->modified = true;
+ continue;
+ }
+ /* No games with error messages. */
+ TALLOC_FREE(tp);
+ /* Remember who blocked us. */
+ plock->context.smblctx = curr_lock->context.smblctx;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+ /* Just copy the Windows lock into the new array. */
+ memcpy(&tp[count], curr_lock, sizeof(struct lock_struct));
+ count++;
+ } else {
+ unsigned int tmp_count = 0;
+
+ /* POSIX conflict semantics are different. */
+ if (brl_conflict_posix(curr_lock, plock)) {
+ if (!serverid_exists(&curr_lock->context.pid)) {
+ curr_lock->context.pid.pid = 0;
+ br_lck->modified = true;
+ continue;
+ }
+ /* Can't block ourselves with POSIX locks. */
+ /* No games with error messages. */
+ TALLOC_FREE(tp);
+ /* Remember who blocked us. */
+ plock->context.smblctx = curr_lock->context.smblctx;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ /* Work out overlaps. */
+ tmp_count += brlock_posix_split_merge(&tp[count], curr_lock, plock);
+ posix_count += tmp_count;
+ count += tmp_count;
+ }
+ }
+
+ /*
+ * Break oplocks while we hold a brl. Since lock() and unlock() calls
+ * are not symmetric with POSIX semantics, we cannot guarantee our
+ * contend_level2_oplocks_begin/end calls will be acquired and
+ * released one-for-one as with Windows semantics. Therefore we only
+ * call contend_level2_oplocks_begin if this is the first POSIX brl on
+ * the file.
+ */
+ break_oplocks = (posix_count == 0);
+ if (break_oplocks) {
+ contend_level2_oplocks_begin(br_lck->fsp,
+ LEVEL2_CONTEND_POSIX_BRL);
+ }
+
+ /* Try and add the lock in order, sorted by lock start. */
+ for (i=0; i < count; i++) {
+ struct lock_struct *curr_lock = &tp[i];
+
+ if (curr_lock->start <= plock->start) {
+ continue;
+ }
+ }
+
+ if (i < count) {
+ memmove(&tp[i+1], &tp[i],
+ (count - i)*sizeof(struct lock_struct));
+ }
+ memcpy(&tp[i], plock, sizeof(struct lock_struct));
+ count++;
+
+ /* We can get the POSIX lock, now see if it needs to
+ be mapped into a lower level POSIX one, and if so can
+ we get it ? */
+
+ if (lp_posix_locking(br_lck->fsp->conn->params)) {
+ int errno_ret;
+
+ /* The lower layer just needs to attempt to
+ get the system POSIX lock. We've weeded out
+ any conflicts above. */
+
+ if (!set_posix_lock_posix_flavour(br_lck->fsp,
+ plock->start,
+ plock->size,
+ plock->lock_type,
+ &plock->context,
+ &errno_ret)) {
+
+ /* We don't know who blocked us. */
+ plock->context.smblctx = 0xFFFFFFFFFFFFFFFFLL;
+
+ if (errno_ret == EACCES || errno_ret == EAGAIN) {
+ TALLOC_FREE(tp);
+ status = NT_STATUS_LOCK_NOT_GRANTED;
+ goto fail;
+ } else {
+ TALLOC_FREE(tp);
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+ }
+ }
+
+ /* If we didn't use all the allocated size,
+ * Realloc so we don't leak entries per lock call. */
+ if (count < br_lck->num_locks + 2) {
+ tp = talloc_realloc(br_lck, tp, struct lock_struct, count);
+ if (!tp) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ br_lck->num_locks = count;
+ TALLOC_FREE(br_lck->lock_data);
+ br_lck->lock_data = tp;
+ locks = tp;
+ br_lck->modified = True;
+
+ /* A successful downgrade from write to read lock can trigger a lock
+ re-evalutation where waiting readers can now proceed. */
+
+ return NT_STATUS_OK;
+ fail:
+ if (break_oplocks) {
+ contend_level2_oplocks_end(br_lck->fsp,
+ LEVEL2_CONTEND_POSIX_BRL);
+ }
+ return status;
+}
+
+/****************************************************************************
+ Lock a range of bytes.
+****************************************************************************/
+
+NTSTATUS brl_lock(
+ struct byte_range_lock *br_lck,
+ uint64_t smblctx,
+ struct server_id pid,
+ br_off start,
+ br_off size,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ struct server_id *blocker_pid,
+ uint64_t *psmblctx)
+{
+ NTSTATUS ret;
+ struct lock_struct lock;
+
+ ZERO_STRUCT(lock);
+
+#if !ZERO_ZERO
+ if (start == 0 && size == 0) {
+ DEBUG(0,("client sent 0/0 lock - please report this\n"));
+ }
+#endif
+
+ lock = (struct lock_struct) {
+ .context.smblctx = smblctx,
+ .context.pid = pid,
+ .context.tid = br_lck->fsp->conn->cnum,
+ .start = start,
+ .size = size,
+ .fnum = br_lck->fsp->fnum,
+ .lock_type = lock_type,
+ .lock_flav = lock_flav
+ };
+
+ if (lock_flav == WINDOWS_LOCK) {
+ ret = SMB_VFS_BRL_LOCK_WINDOWS(
+ br_lck->fsp->conn, br_lck, &lock);
+ } else {
+ ret = brl_lock_posix(br_lck, &lock);
+ }
+
+#if ZERO_ZERO
+ /* sort the lock list */
+ TYPESAFE_QSORT(br_lck->lock_data, (size_t)br_lck->num_locks, lock_compare);
+#endif
+ /* If we're returning an error, return who blocked us. */
+ if (!NT_STATUS_IS_OK(ret) && psmblctx) {
+ *blocker_pid = lock.context.pid;
+ *psmblctx = lock.context.smblctx;
+ }
+ return ret;
+}
+
+/****************************************************************************
+ Unlock a range of bytes - Windows semantics.
+****************************************************************************/
+
+bool brl_unlock_windows_default(struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ unsigned int i;
+ struct lock_struct *locks = br_lck->lock_data;
+ enum brl_type deleted_lock_type = READ_LOCK; /* shut the compiler up.... */
+
+ SMB_ASSERT(plock->lock_type == UNLOCK_LOCK);
+
+#if ZERO_ZERO
+ /* Delete write locks by preference... The lock list
+ is sorted in the zero zero case. */
+
+ for (i = 0; i < br_lck->num_locks; i++) {
+ struct lock_struct *lock = &locks[i];
+
+ if (lock->lock_type == WRITE_LOCK &&
+ brl_same_context(&lock->context, &plock->context) &&
+ lock->fnum == plock->fnum &&
+ lock->lock_flav == WINDOWS_LOCK &&
+ lock->start == plock->start &&
+ lock->size == plock->size) {
+
+ /* found it - delete it */
+ deleted_lock_type = lock->lock_type;
+ break;
+ }
+ }
+
+ if (i != br_lck->num_locks) {
+ /* We found it - don't search again. */
+ goto unlock_continue;
+ }
+#endif
+
+ for (i = 0; i < br_lck->num_locks; i++) {
+ struct lock_struct *lock = &locks[i];
+
+ /* Only remove our own locks that match in start, size, and flavour. */
+ if (brl_same_context(&lock->context, &plock->context) &&
+ lock->fnum == plock->fnum &&
+ lock->lock_flav == WINDOWS_LOCK &&
+ lock->start == plock->start &&
+ lock->size == plock->size ) {
+ deleted_lock_type = lock->lock_type;
+ break;
+ }
+ }
+
+ if (i == br_lck->num_locks) {
+ /* we didn't find it */
+ return False;
+ }
+
+#if ZERO_ZERO
+ unlock_continue:
+#endif
+
+ ARRAY_DEL_ELEMENT(locks, i, br_lck->num_locks);
+ br_lck->num_locks -= 1;
+ br_lck->modified = True;
+
+ /* Unlock the underlying POSIX regions. */
+ if(lp_posix_locking(br_lck->fsp->conn->params)) {
+ release_posix_lock_windows_flavour(br_lck->fsp,
+ plock->start,
+ plock->size,
+ deleted_lock_type,
+ &plock->context,
+ locks,
+ br_lck->num_locks);
+ }
+
+ contend_level2_oplocks_end(br_lck->fsp, LEVEL2_CONTEND_WINDOWS_BRL);
+ return True;
+}
+
+/****************************************************************************
+ Unlock a range of bytes - POSIX semantics.
+****************************************************************************/
+
+static bool brl_unlock_posix(struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ unsigned int i, count;
+ struct lock_struct *tp;
+ struct lock_struct *locks = br_lck->lock_data;
+ bool overlap_found = False;
+
+ /* No zero-zero locks for POSIX. */
+ if (plock->start == 0 && plock->size == 0) {
+ return False;
+ }
+
+ /* Don't allow 64-bit lock wrap. */
+ if (plock->start + plock->size < plock->start ||
+ plock->start + plock->size < plock->size) {
+ DEBUG(10,("brl_unlock_posix: lock wrap\n"));
+ return False;
+ }
+
+ /* The worst case scenario here is we have to split an
+ existing POSIX lock range into two, so we need at most
+ 1 more entry. */
+
+ tp = talloc_array(br_lck, struct lock_struct, br_lck->num_locks + 1);
+ if (!tp) {
+ DEBUG(10,("brl_unlock_posix: malloc fail\n"));
+ return False;
+ }
+
+ count = 0;
+ for (i = 0; i < br_lck->num_locks; i++) {
+ struct lock_struct *lock = &locks[i];
+ unsigned int tmp_count;
+
+ /* Only remove our own locks - ignore fnum. */
+ if (!brl_same_context(&lock->context, &plock->context)) {
+ memcpy(&tp[count], lock, sizeof(struct lock_struct));
+ count++;
+ continue;
+ }
+
+ if (lock->lock_flav == WINDOWS_LOCK) {
+ /* Do any Windows flavour locks conflict ? */
+ if (brl_conflict(lock, plock)) {
+ TALLOC_FREE(tp);
+ return false;
+ }
+ /* Just copy the Windows lock into the new array. */
+ memcpy(&tp[count], lock, sizeof(struct lock_struct));
+ count++;
+ continue;
+ }
+
+ /* Work out overlaps. */
+ tmp_count = brlock_posix_split_merge(&tp[count], lock, plock);
+
+ if (tmp_count == 0) {
+ /* plock overlapped the existing lock completely,
+ or replaced it. Don't copy the existing lock. */
+ overlap_found = true;
+ } else if (tmp_count == 1) {
+ /* Either no overlap, (simple copy of existing lock) or
+ * an overlap of an existing lock. */
+ /* If the lock changed size, we had an overlap. */
+ if (tp[count].size != lock->size) {
+ overlap_found = true;
+ }
+ count += tmp_count;
+ } else if (tmp_count == 2) {
+ /* We split a lock range in two. */
+ overlap_found = true;
+ count += tmp_count;
+
+ /* Optimisation... */
+ /* We know we're finished here as we can't overlap any
+ more POSIX locks. Copy the rest of the lock array. */
+
+ if (i < br_lck->num_locks - 1) {
+ memcpy(&tp[count], &locks[i+1],
+ sizeof(*locks)*((br_lck->num_locks-1) - i));
+ count += ((br_lck->num_locks-1) - i);
+ }
+ break;
+ }
+
+ }
+
+ if (!overlap_found) {
+ /* Just ignore - no change. */
+ TALLOC_FREE(tp);
+ DEBUG(10,("brl_unlock_posix: No overlap - unlocked.\n"));
+ return True;
+ }
+
+ /* Unlock any POSIX regions. */
+ if(lp_posix_locking(br_lck->fsp->conn->params)) {
+ release_posix_lock_posix_flavour(br_lck->fsp,
+ plock->start,
+ plock->size,
+ &plock->context,
+ tp,
+ count);
+ }
+
+ /* Realloc so we don't leak entries per unlock call. */
+ if (count) {
+ tp = talloc_realloc(br_lck, tp, struct lock_struct, count);
+ if (!tp) {
+ DEBUG(10,("brl_unlock_posix: realloc fail\n"));
+ return False;
+ }
+ } else {
+ /* We deleted the last lock. */
+ TALLOC_FREE(tp);
+ tp = NULL;
+ }
+
+ contend_level2_oplocks_end(br_lck->fsp,
+ LEVEL2_CONTEND_POSIX_BRL);
+
+ br_lck->num_locks = count;
+ TALLOC_FREE(br_lck->lock_data);
+ locks = tp;
+ br_lck->lock_data = tp;
+ br_lck->modified = True;
+
+ return True;
+}
+
+/****************************************************************************
+ Unlock a range of bytes.
+****************************************************************************/
+
+bool brl_unlock(struct byte_range_lock *br_lck,
+ uint64_t smblctx,
+ struct server_id pid,
+ br_off start,
+ br_off size,
+ enum brl_flavour lock_flav)
+{
+ struct lock_struct lock;
+
+ lock.context.smblctx = smblctx;
+ lock.context.pid = pid;
+ lock.context.tid = br_lck->fsp->conn->cnum;
+ lock.start = start;
+ lock.size = size;
+ lock.fnum = br_lck->fsp->fnum;
+ lock.lock_type = UNLOCK_LOCK;
+ lock.lock_flav = lock_flav;
+
+ if (lock_flav == WINDOWS_LOCK) {
+ return SMB_VFS_BRL_UNLOCK_WINDOWS(
+ br_lck->fsp->conn, br_lck, &lock);
+ } else {
+ return brl_unlock_posix(br_lck, &lock);
+ }
+}
+
+/****************************************************************************
+ Test if we could add a lock if we wanted to.
+ Returns True if the region required is currently unlocked, False if locked.
+****************************************************************************/
+
+bool brl_locktest(struct byte_range_lock *br_lck,
+ const struct lock_struct *rw_probe)
+{
+ bool ret = True;
+ unsigned int i;
+ struct lock_struct *locks = br_lck->lock_data;
+ files_struct *fsp = br_lck->fsp;
+
+ /* Make sure existing locks don't conflict */
+ for (i=0; i < br_lck->num_locks; i++) {
+ /*
+ * Our own locks don't conflict.
+ */
+ if (brl_conflict_other(&locks[i], rw_probe)) {
+ if (br_lck->record == NULL) {
+ /* readonly */
+ return false;
+ }
+
+ if (!serverid_exists(&locks[i].context.pid)) {
+ locks[i].context.pid.pid = 0;
+ br_lck->modified = true;
+ continue;
+ }
+
+ return False;
+ }
+ }
+
+ /*
+ * There is no lock held by an SMB daemon, check to
+ * see if there is a POSIX lock from a UNIX or NFS process.
+ * This only conflicts with Windows locks, not POSIX locks.
+ */
+
+ if(lp_posix_locking(fsp->conn->params) &&
+ (rw_probe->lock_flav == WINDOWS_LOCK)) {
+ /*
+ * Make copies -- is_posix_locked might modify the values
+ */
+
+ br_off start = rw_probe->start;
+ br_off size = rw_probe->size;
+ enum brl_type lock_type = rw_probe->lock_type;
+
+ ret = is_posix_locked(fsp, &start, &size, &lock_type, WINDOWS_LOCK);
+
+ DEBUG(10, ("brl_locktest: posix start=%ju len=%ju %s for %s "
+ "file %s\n", (uintmax_t)start, (uintmax_t)size,
+ ret ? "locked" : "unlocked",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp)));
+
+ /* We need to return the inverse of is_posix_locked. */
+ ret = !ret;
+ }
+
+ /* no conflicts - we could have added it */
+ return ret;
+}
+
+/****************************************************************************
+ Query for existing locks.
+****************************************************************************/
+
+NTSTATUS brl_lockquery(struct byte_range_lock *br_lck,
+ uint64_t *psmblctx,
+ struct server_id pid,
+ br_off *pstart,
+ br_off *psize,
+ enum brl_type *plock_type,
+ enum brl_flavour lock_flav)
+{
+ unsigned int i;
+ struct lock_struct lock;
+ const struct lock_struct *locks = br_lck->lock_data;
+ files_struct *fsp = br_lck->fsp;
+
+ lock.context.smblctx = *psmblctx;
+ lock.context.pid = pid;
+ lock.context.tid = br_lck->fsp->conn->cnum;
+ lock.start = *pstart;
+ lock.size = *psize;
+ lock.fnum = fsp->fnum;
+ lock.lock_type = *plock_type;
+ lock.lock_flav = lock_flav;
+
+ /* Make sure existing locks don't conflict */
+ for (i=0; i < br_lck->num_locks; i++) {
+ const struct lock_struct *exlock = &locks[i];
+ bool conflict = False;
+
+ if (exlock->lock_flav == WINDOWS_LOCK) {
+ conflict = brl_conflict(exlock, &lock);
+ } else {
+ conflict = brl_conflict_posix(exlock, &lock);
+ }
+
+ if (conflict) {
+ *psmblctx = exlock->context.smblctx;
+ *pstart = exlock->start;
+ *psize = exlock->size;
+ *plock_type = exlock->lock_type;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+ }
+
+ /*
+ * There is no lock held by an SMB daemon, check to
+ * see if there is a POSIX lock from a UNIX or NFS process.
+ */
+
+ if(lp_posix_locking(fsp->conn->params)) {
+ bool ret = is_posix_locked(fsp, pstart, psize, plock_type, POSIX_LOCK);
+
+ DEBUG(10, ("brl_lockquery: posix start=%ju len=%ju %s for %s "
+ "file %s\n", (uintmax_t)*pstart,
+ (uintmax_t)*psize, ret ? "locked" : "unlocked",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp)));
+
+ if (ret) {
+ /* Hmmm. No clue what to set smblctx to - use -1. */
+ *psmblctx = 0xFFFFFFFFFFFFFFFFLL;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/****************************************************************************
+ Remove any locks associated with a open file.
+ We return True if this process owns any other Windows locks on this
+ fd and so we should not immediately close the fd.
+****************************************************************************/
+
+void brl_close_fnum(struct byte_range_lock *br_lck)
+{
+ files_struct *fsp = br_lck->fsp;
+ uint32_t tid = fsp->conn->cnum;
+ uint64_t fnum = fsp->fnum;
+ unsigned int i;
+ struct lock_struct *locks = br_lck->lock_data;
+ struct server_id pid = messaging_server_id(fsp->conn->sconn->msg_ctx);
+ struct lock_struct *locks_copy;
+ unsigned int num_locks_copy;
+
+ /* Copy the current lock array. */
+ if (br_lck->num_locks) {
+ locks_copy = (struct lock_struct *)talloc_memdup(br_lck, locks, br_lck->num_locks * sizeof(struct lock_struct));
+ if (!locks_copy) {
+ smb_panic("brl_close_fnum: talloc failed");
+ }
+ } else {
+ locks_copy = NULL;
+ }
+
+ num_locks_copy = br_lck->num_locks;
+
+ for (i=0; i < num_locks_copy; i++) {
+ struct lock_struct *lock = &locks_copy[i];
+
+ if (lock->context.tid == tid &&
+ server_id_equal(&lock->context.pid, &pid) &&
+ (lock->fnum == fnum)) {
+ brl_unlock(
+ br_lck,
+ lock->context.smblctx,
+ pid,
+ lock->start,
+ lock->size,
+ lock->lock_flav);
+ }
+ }
+}
+
+bool brl_mark_disconnected(struct files_struct *fsp)
+{
+ uint32_t tid = fsp->conn->cnum;
+ uint64_t smblctx;
+ uint64_t fnum = fsp->fnum;
+ unsigned int i;
+ struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx);
+ struct byte_range_lock *br_lck = NULL;
+
+ if (fsp->op == NULL) {
+ return false;
+ }
+
+ smblctx = fsp->op->global->open_persistent_id;
+
+ if (!fsp->op->global->durable) {
+ return false;
+ }
+
+ if (fsp->current_lock_count == 0) {
+ return true;
+ }
+
+ br_lck = brl_get_locks(talloc_tos(), fsp);
+ if (br_lck == NULL) {
+ return false;
+ }
+
+ for (i=0; i < br_lck->num_locks; i++) {
+ struct lock_struct *lock = &br_lck->lock_data[i];
+
+ /*
+ * as this is a durable handle, we only expect locks
+ * of the current file handle!
+ */
+
+ if (lock->context.smblctx != smblctx) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ if (lock->context.tid != tid) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ if (!server_id_equal(&lock->context.pid, &self)) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ if (lock->fnum != fnum) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ server_id_set_disconnected(&lock->context.pid);
+ lock->context.tid = TID_FIELD_INVALID;
+ lock->fnum = FNUM_FIELD_INVALID;
+ }
+
+ br_lck->modified = true;
+ TALLOC_FREE(br_lck);
+ return true;
+}
+
+bool brl_reconnect_disconnected(struct files_struct *fsp)
+{
+ uint32_t tid = fsp->conn->cnum;
+ uint64_t smblctx;
+ uint64_t fnum = fsp->fnum;
+ unsigned int i;
+ struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx);
+ struct byte_range_lock *br_lck = NULL;
+
+ if (fsp->op == NULL) {
+ return false;
+ }
+
+ smblctx = fsp->op->global->open_persistent_id;
+
+ if (!fsp->op->global->durable) {
+ return false;
+ }
+
+ /*
+ * When reconnecting, we do not want to validate the brlock entries
+ * and thereby remove our own (disconnected) entries but reactivate
+ * them instead.
+ */
+
+ br_lck = brl_get_locks(talloc_tos(), fsp);
+ if (br_lck == NULL) {
+ return false;
+ }
+
+ if (br_lck->num_locks == 0) {
+ TALLOC_FREE(br_lck);
+ return true;
+ }
+
+ for (i=0; i < br_lck->num_locks; i++) {
+ struct lock_struct *lock = &br_lck->lock_data[i];
+
+ /*
+ * as this is a durable handle we only expect locks
+ * of the current file handle!
+ */
+
+ if (lock->context.smblctx != smblctx) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ if (lock->context.tid != TID_FIELD_INVALID) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ if (!server_id_is_disconnected(&lock->context.pid)) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ if (lock->fnum != FNUM_FIELD_INVALID) {
+ TALLOC_FREE(br_lck);
+ return false;
+ }
+
+ lock->context.pid = self;
+ lock->context.tid = tid;
+ lock->fnum = fnum;
+ }
+
+ fsp->current_lock_count = br_lck->num_locks;
+ br_lck->modified = true;
+ TALLOC_FREE(br_lck);
+ return true;
+}
+
+struct brl_forall_cb {
+ void (*fn)(struct file_id id, struct server_id pid,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ br_off start, br_off size,
+ void *private_data);
+ void *private_data;
+};
+
+/****************************************************************************
+ Traverse the whole database with this function, calling traverse_callback
+ on each lock.
+****************************************************************************/
+
+static int brl_traverse_fn(struct db_record *rec, void *state)
+{
+ struct brl_forall_cb *cb = (struct brl_forall_cb *)state;
+ struct lock_struct *locks;
+ struct file_id *key;
+ unsigned int i;
+ unsigned int num_locks = 0;
+ TDB_DATA dbkey;
+ TDB_DATA value;
+
+ dbkey = dbwrap_record_get_key(rec);
+ value = dbwrap_record_get_value(rec);
+
+ /* In a traverse function we must make a copy of
+ dbuf before modifying it. */
+
+ locks = (struct lock_struct *)talloc_memdup(
+ talloc_tos(), value.dptr, value.dsize);
+ if (!locks) {
+ return -1; /* Terminate traversal. */
+ }
+
+ key = (struct file_id *)dbkey.dptr;
+ num_locks = value.dsize/sizeof(*locks);
+
+ if (cb->fn) {
+ for ( i=0; i<num_locks; i++) {
+ cb->fn(*key,
+ locks[i].context.pid,
+ locks[i].lock_type,
+ locks[i].lock_flav,
+ locks[i].start,
+ locks[i].size,
+ cb->private_data);
+ }
+ }
+
+ TALLOC_FREE(locks);
+ return 0;
+}
+
+/*******************************************************************
+ Call the specified function on each lock in the database.
+********************************************************************/
+
+int brl_forall(void (*fn)(struct file_id id, struct server_id pid,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ br_off start, br_off size,
+ void *private_data),
+ void *private_data)
+{
+ struct brl_forall_cb cb;
+ NTSTATUS status;
+ int count = 0;
+
+ if (!brlock_db) {
+ return 0;
+ }
+ cb.fn = fn;
+ cb.private_data = private_data;
+ status = dbwrap_traverse(brlock_db, brl_traverse_fn, &cb, &count);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ } else {
+ return count;
+ }
+}
+
+/*******************************************************************
+ Store a potentially modified set of byte range lock data back into
+ the database.
+ Unlock the record.
+********************************************************************/
+
+static void byte_range_lock_flush(struct byte_range_lock *br_lck)
+{
+ unsigned i;
+ struct lock_struct *locks = br_lck->lock_data;
+
+ if (!br_lck->modified) {
+ DEBUG(10, ("br_lck not modified\n"));
+ goto done;
+ }
+
+ i = 0;
+
+ while (i < br_lck->num_locks) {
+ if (locks[i].context.pid.pid == 0) {
+ /*
+ * Autocleanup, the process conflicted and does not
+ * exist anymore.
+ */
+ locks[i] = locks[br_lck->num_locks-1];
+ br_lck->num_locks -= 1;
+ } else {
+ i += 1;
+ }
+ }
+
+ if (br_lck->num_locks == 0) {
+ /* No locks - delete this entry. */
+ NTSTATUS status = dbwrap_record_delete(br_lck->record);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("delete_rec returned %s\n",
+ nt_errstr(status)));
+ smb_panic("Could not delete byte range lock entry");
+ }
+ } else {
+ TDB_DATA data = {
+ .dsize = br_lck->num_locks * sizeof(struct lock_struct),
+ .dptr = (uint8_t *)br_lck->lock_data,
+ };
+ NTSTATUS status;
+
+ status = dbwrap_record_store(br_lck->record, data, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("store returned %s\n", nt_errstr(status)));
+ smb_panic("Could not store byte range mode entry");
+ }
+ }
+
+ DEBUG(10, ("seqnum=%d\n", dbwrap_get_seqnum(brlock_db)));
+
+ done:
+ br_lck->modified = false;
+ TALLOC_FREE(br_lck->record);
+}
+
+static int byte_range_lock_destructor(struct byte_range_lock *br_lck)
+{
+ byte_range_lock_flush(br_lck);
+ return 0;
+}
+
+static bool brl_parse_data(struct byte_range_lock *br_lck, TDB_DATA data)
+{
+ size_t data_len;
+
+ if (data.dsize == 0) {
+ return true;
+ }
+ if (data.dsize % sizeof(struct lock_struct) != 0) {
+ DEBUG(1, ("Invalid data size: %u\n", (unsigned)data.dsize));
+ return false;
+ }
+
+ br_lck->num_locks = data.dsize / sizeof(struct lock_struct);
+ data_len = br_lck->num_locks * sizeof(struct lock_struct);
+
+ br_lck->lock_data = talloc_memdup(br_lck, data.dptr, data_len);
+ if (br_lck->lock_data == NULL) {
+ DEBUG(1, ("talloc_memdup failed\n"));
+ return false;
+ }
+ return true;
+}
+
+/*******************************************************************
+ Fetch a set of byte range lock data from the database.
+ Leave the record locked.
+ TALLOC_FREE(brl) will release the lock in the destructor.
+********************************************************************/
+
+struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx, files_struct *fsp)
+{
+ TDB_DATA key, data;
+ struct byte_range_lock *br_lck;
+
+ br_lck = talloc_zero(mem_ctx, struct byte_range_lock);
+ if (br_lck == NULL) {
+ return NULL;
+ }
+
+ br_lck->fsp = fsp;
+
+ key.dptr = (uint8_t *)&fsp->file_id;
+ key.dsize = sizeof(struct file_id);
+
+ br_lck->record = dbwrap_fetch_locked(brlock_db, br_lck, key);
+
+ if (br_lck->record == NULL) {
+ DEBUG(3, ("Could not lock byte range lock entry\n"));
+ TALLOC_FREE(br_lck);
+ return NULL;
+ }
+
+ data = dbwrap_record_get_value(br_lck->record);
+
+ if (!brl_parse_data(br_lck, data)) {
+ TALLOC_FREE(br_lck);
+ return NULL;
+ }
+
+ talloc_set_destructor(br_lck, byte_range_lock_destructor);
+
+ if (DEBUGLEVEL >= 10) {
+ unsigned int i;
+ struct file_id_buf buf;
+ struct lock_struct *locks = br_lck->lock_data;
+ DBG_DEBUG("%u current locks on file_id %s\n",
+ br_lck->num_locks,
+ file_id_str_buf(fsp->file_id, &buf));
+ for( i = 0; i < br_lck->num_locks; i++) {
+ print_lock_struct(i, &locks[i]);
+ }
+ }
+
+ return br_lck;
+}
+
+struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ TALLOC_CTX *req_mem_ctx,
+ const struct GUID *req_guid)
+{
+ struct byte_range_lock *br_lck = NULL;
+
+ br_lck = brl_get_locks(mem_ctx, fsp);
+ if (br_lck == NULL) {
+ return NULL;
+ }
+ SMB_ASSERT(req_mem_ctx != NULL);
+ br_lck->req_mem_ctx = req_mem_ctx;
+ SMB_ASSERT(req_guid != NULL);
+ br_lck->req_guid = req_guid;
+
+ return br_lck;
+}
+
+struct brl_get_locks_readonly_state {
+ TALLOC_CTX *mem_ctx;
+ struct byte_range_lock **br_lock;
+};
+
+static void brl_get_locks_readonly_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct brl_get_locks_readonly_state *state =
+ (struct brl_get_locks_readonly_state *)private_data;
+ struct byte_range_lock *br_lck;
+
+ br_lck = talloc_pooled_object(
+ state->mem_ctx, struct byte_range_lock, 1, data.dsize);
+ if (br_lck == NULL) {
+ *state->br_lock = NULL;
+ return;
+ }
+ *br_lck = (struct byte_range_lock) { 0 };
+ if (!brl_parse_data(br_lck, data)) {
+ *state->br_lock = NULL;
+ return;
+ }
+ *state->br_lock = br_lck;
+}
+
+struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp)
+{
+ struct byte_range_lock *br_lock = NULL;
+ struct brl_get_locks_readonly_state state;
+ NTSTATUS status;
+
+ DEBUG(10, ("seqnum=%d, fsp->brlock_seqnum=%d\n",
+ dbwrap_get_seqnum(brlock_db), fsp->brlock_seqnum));
+
+ if ((fsp->brlock_rec != NULL)
+ && (dbwrap_get_seqnum(brlock_db) == fsp->brlock_seqnum)) {
+ /*
+ * We have cached the brlock_rec and the database did not
+ * change.
+ */
+ return fsp->brlock_rec;
+ }
+
+ /*
+ * Parse the record fresh from the database
+ */
+
+ state.mem_ctx = fsp;
+ state.br_lock = &br_lock;
+
+ status = dbwrap_parse_record(
+ brlock_db,
+ make_tdb_data((uint8_t *)&fsp->file_id,
+ sizeof(fsp->file_id)),
+ brl_get_locks_readonly_parser, &state);
+
+ if (NT_STATUS_EQUAL(status,NT_STATUS_NOT_FOUND)) {
+ /*
+ * No locks on this file. Return an empty br_lock.
+ */
+ br_lock = talloc_zero(fsp, struct byte_range_lock);
+ if (br_lock == NULL) {
+ return NULL;
+ }
+
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not parse byte range lock record: "
+ "%s\n", nt_errstr(status)));
+ return NULL;
+ }
+ if (br_lock == NULL) {
+ return NULL;
+ }
+
+ br_lock->fsp = fsp;
+ br_lock->modified = false;
+ br_lock->record = NULL;
+
+ /*
+ * Cache the brlock struct, invalidated when the dbwrap_seqnum
+ * changes. See beginning of this routine.
+ */
+ TALLOC_FREE(fsp->brlock_rec);
+ fsp->brlock_rec = br_lock;
+ fsp->brlock_seqnum = dbwrap_get_seqnum(brlock_db);
+
+ return br_lock;
+}
+
+bool brl_cleanup_disconnected(struct file_id fid, uint64_t open_persistent_id)
+{
+ bool ret = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+ TDB_DATA key, val;
+ struct db_record *rec;
+ struct lock_struct *lock;
+ unsigned n, num;
+ struct file_id_buf buf;
+ NTSTATUS status;
+
+ key = make_tdb_data((void*)&fid, sizeof(fid));
+
+ rec = dbwrap_fetch_locked(brlock_db, frame, key);
+ if (rec == NULL) {
+ DBG_INFO("failed to fetch record for file %s\n",
+ file_id_str_buf(fid, &buf));
+ goto done;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ lock = (struct lock_struct*)val.dptr;
+ num = val.dsize / sizeof(struct lock_struct);
+ if (lock == NULL) {
+ DBG_DEBUG("no byte range locks for file %s\n",
+ file_id_str_buf(fid, &buf));
+ ret = true;
+ goto done;
+ }
+
+ for (n=0; n<num; n++) {
+ struct lock_context *ctx = &lock[n].context;
+
+ if (!server_id_is_disconnected(&ctx->pid)) {
+ struct server_id_buf tmp;
+ DBG_INFO("byte range lock "
+ "%s used by server %s, do not cleanup\n",
+ file_id_str_buf(fid, &buf),
+ server_id_str_buf(ctx->pid, &tmp));
+ goto done;
+ }
+
+ if (ctx->smblctx != open_persistent_id) {
+ DBG_INFO("byte range lock %s expected smblctx %"PRIu64" "
+ "but found %"PRIu64", do not cleanup\n",
+ file_id_str_buf(fid, &buf),
+ open_persistent_id,
+ ctx->smblctx);
+ goto done;
+ }
+ }
+
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("failed to delete record "
+ "for file %s from %s, open %"PRIu64": %s\n",
+ file_id_str_buf(fid, &buf),
+ dbwrap_name(brlock_db),
+ open_persistent_id,
+ nt_errstr(status));
+ goto done;
+ }
+
+ DBG_DEBUG("file %s cleaned up %u entries from open %"PRIu64"\n",
+ file_id_str_buf(fid, &buf),
+ num,
+ open_persistent_id);
+
+ ret = true;
+done:
+ talloc_free(frame);
+ return ret;
+}
diff --git a/source3/locking/leases_db.c b/source3/locking/leases_db.c
new file mode 100644
index 0000000..eae58f5
--- /dev/null
+++ b/source3/locking/leases_db.c
@@ -0,0 +1,724 @@
+/*
+ Unix SMB/CIFS implementation.
+ Map lease keys to file ids
+ Copyright (C) Volker Lendecke 2013
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "locking/leases_db.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "util_tdb.h"
+#include "ndr.h"
+#include "librpc/gen_ndr/ndr_leases_db.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/* the leases database handle */
+static struct db_context *leases_db;
+
+bool leases_db_init(bool read_only)
+{
+ char *db_path;
+
+ if (leases_db) {
+ return true;
+ }
+
+ db_path = lock_path(talloc_tos(), "leases.tdb");
+ if (db_path == NULL) {
+ return false;
+ }
+
+ leases_db = db_open(NULL, db_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS |
+ TDB_SEQNUM,
+ read_only ? O_RDONLY : O_RDWR|O_CREAT, 0644,
+ DBWRAP_LOCK_ORDER_4, DBWRAP_FLAG_NONE);
+ TALLOC_FREE(db_path);
+ if (leases_db == NULL) {
+ DEBUG(1, ("ERROR: Failed to initialise leases database\n"));
+ return false;
+ }
+
+ return true;
+}
+
+struct leases_db_key_buf {
+ uint8_t buf[32];
+};
+
+static TDB_DATA leases_db_key(struct leases_db_key_buf *buf,
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key)
+{
+ struct leases_db_key db_key = {
+ .client_guid = *client_guid,
+ .lease_key = *lease_key };
+ DATA_BLOB blob = { .data = buf->buf, .length = sizeof(buf->buf) };
+ enum ndr_err_code ndr_err;
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("\n");
+ NDR_PRINT_DEBUG(leases_db_key, &db_key);
+ }
+
+ ndr_err = ndr_push_struct_into_fixed_blob(
+ &blob, &db_key, (ndr_push_flags_fn_t)ndr_push_leases_db_key);
+ SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+
+ return (TDB_DATA) { .dptr = buf->buf, .dsize = sizeof(buf->buf) };
+}
+
+struct leases_db_do_locked_state {
+ void (*fn)(struct leases_db_value *value,
+ bool *modified,
+ void *private_data);
+ void *private_data;
+ NTSTATUS status;
+};
+
+static void leases_db_do_locked_fn(
+ struct db_record *rec,
+ TDB_DATA db_value,
+ void *private_data)
+{
+ struct leases_db_do_locked_state *state = private_data;
+ DATA_BLOB blob = { .data = db_value.dptr, .length = db_value.dsize };
+ struct leases_db_value *value = NULL;
+ enum ndr_err_code ndr_err;
+ bool modified = false;
+
+ value = talloc_zero(talloc_tos(), struct leases_db_value);
+ if (value == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (blob.length != 0) {
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob,
+ value,
+ value,
+ (ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_pull_struct_blob_failed: %s\n",
+ ndr_errstr(ndr_err));
+ state->status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+ }
+
+ state->fn(value, &modified, state->private_data);
+
+ if (!modified) {
+ goto done;
+ }
+
+ if (value->num_files == 0) {
+ state->status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("dbwrap_record_delete returned %s\n",
+ nt_errstr(state->status));
+ }
+ goto done;
+ }
+
+ ndr_err = ndr_push_struct_blob(
+ &blob,
+ value,
+ value,
+ (ndr_push_flags_fn_t)ndr_push_leases_db_value);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_push_struct_blob_failed: %s\n",
+ ndr_errstr(ndr_err));
+ state->status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("\n");
+ NDR_PRINT_DEBUG(leases_db_value, value);
+ }
+
+ db_value = make_tdb_data(blob.data, blob.length);
+
+ state->status = dbwrap_record_store(rec, db_value, 0);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("dbwrap_record_store returned %s\n",
+ nt_errstr(state->status));
+ }
+
+done:
+ TALLOC_FREE(value);
+}
+
+static NTSTATUS leases_db_do_locked(
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ void (*fn)(struct leases_db_value *value,
+ bool *modified,
+ void *private_data),
+ void *private_data)
+{
+ struct leases_db_key_buf keybuf;
+ TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key);
+ struct leases_db_do_locked_state state = {
+ .fn = fn, .private_data = private_data,
+ };
+ NTSTATUS status;
+
+ if (!leases_db_init(false)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = dbwrap_do_locked(
+ leases_db, db_key, leases_db_do_locked_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return state.status;
+}
+
+struct leases_db_add_state {
+ const struct file_id *id;
+ uint32_t current_state;
+ uint16_t lease_version;
+ uint16_t epoch;
+ const char *servicepath;
+ const char *base_name;
+ const char *stream_name;
+ NTSTATUS status;
+};
+
+static void leases_db_add_fn(
+ struct leases_db_value *value, bool *modified, void *private_data)
+{
+ struct leases_db_add_state *state = private_data;
+ struct leases_db_file *tmp = NULL;
+ uint32_t i;
+
+ /* id must be unique. */
+ for (i = 0; i < value->num_files; i++) {
+ if (file_id_equal(state->id, &value->files[i].id)) {
+ state->status = NT_STATUS_OBJECT_NAME_COLLISION;
+ return;
+ }
+ }
+
+ if (value->num_files == 0) {
+ /* new record */
+ value->current_state = state->current_state;
+ value->lease_version = state->lease_version;
+ value->epoch = state->epoch;
+ }
+
+ tmp = talloc_realloc(
+ value,
+ value->files,
+ struct leases_db_file,
+ value->num_files + 1);
+ if (tmp == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ value->files = tmp;
+
+ value->files[value->num_files] = (struct leases_db_file) {
+ .id = *state->id,
+ .servicepath = state->servicepath,
+ .base_name = state->base_name,
+ .stream_name = state->stream_name,
+ };
+ value->num_files += 1;
+
+ *modified = true;
+}
+
+NTSTATUS leases_db_add(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *id,
+ uint32_t current_state,
+ uint16_t lease_version,
+ uint16_t epoch,
+ const char *servicepath,
+ const char *base_name,
+ const char *stream_name)
+{
+ struct leases_db_add_state state = {
+ .id = id,
+ .current_state = current_state,
+ .lease_version = lease_version,
+ .epoch = epoch,
+ .servicepath = servicepath,
+ .base_name = base_name,
+ .stream_name = stream_name,
+ };
+ NTSTATUS status;
+
+ status = leases_db_do_locked(
+ client_guid, lease_key, leases_db_add_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("leases_db_do_locked failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ return state.status;
+}
+
+struct leases_db_del_state {
+ const struct file_id *id;
+ NTSTATUS status;
+};
+
+static void leases_db_del_fn(
+ struct leases_db_value *value, bool *modified, void *private_data)
+{
+ struct leases_db_del_state *state = private_data;
+ uint32_t i;
+
+ for (i = 0; i < value->num_files; i++) {
+ if (file_id_equal(state->id, &value->files[i].id)) {
+ break;
+ }
+ }
+ if (i == value->num_files) {
+ state->status = NT_STATUS_NOT_FOUND;
+ return;
+ }
+
+ value->files[i] = value->files[value->num_files-1];
+ value->num_files -= 1;
+
+ *modified = true;
+}
+
+NTSTATUS leases_db_del(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *id)
+{
+ struct leases_db_del_state state = { .id = id };
+ NTSTATUS status;
+
+ status = leases_db_do_locked(
+ client_guid, lease_key, leases_db_del_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("leases_db_do_locked failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ return state.status;
+}
+
+struct leases_db_fetch_state {
+ void (*parser)(uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data);
+ void *private_data;
+ NTSTATUS status;
+};
+
+static void leases_db_parser(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct leases_db_fetch_state *state =
+ (struct leases_db_fetch_state *)private_data;
+ DATA_BLOB blob = { .data = data.dptr, .length = data.dsize };
+ enum ndr_err_code ndr_err;
+ struct leases_db_value *value;
+
+ value = talloc(talloc_tos(), struct leases_db_value);
+ if (value == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob, value, value,
+ (ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("%s: ndr_pull_struct_blob_failed: %s\n",
+ __func__, ndr_errstr(ndr_err)));
+ TALLOC_FREE(value);
+ state->status = ndr_map_error2ntstatus(ndr_err);
+ return;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10, ("%s:\n", __func__));
+ NDR_PRINT_DEBUG(leases_db_value, value);
+ }
+
+ state->parser(value->num_files,
+ value->files,
+ state->private_data);
+
+ TALLOC_FREE(value);
+ state->status = NT_STATUS_OK;
+}
+
+NTSTATUS leases_db_parse(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ void (*parser)(uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data),
+ void *private_data)
+{
+ struct leases_db_key_buf keybuf;
+ TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key);
+ struct leases_db_fetch_state state;
+ NTSTATUS status;
+
+ if (!leases_db_init(true)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ state = (struct leases_db_fetch_state) {
+ .parser = parser,
+ .private_data = private_data,
+ .status = NT_STATUS_OK
+ };
+
+ status = dbwrap_parse_record(leases_db, db_key, leases_db_parser,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return state.status;
+}
+
+struct leases_db_rename_state {
+ const struct file_id *id;
+ const char *servicename_new;
+ const char *filename_new;
+ const char *stream_name_new;
+ NTSTATUS status;
+};
+
+static void leases_db_rename_fn(
+ struct leases_db_value *value, bool *modified, void *private_data)
+{
+ struct leases_db_rename_state *state = private_data;
+ struct leases_db_file *file = NULL;
+ uint32_t i;
+
+ /* id must exist. */
+ for (i = 0; i < value->num_files; i++) {
+ if (file_id_equal(state->id, &value->files[i].id)) {
+ break;
+ }
+ }
+ if (i == value->num_files) {
+ state->status = NT_STATUS_NOT_FOUND;
+ return;
+ }
+
+ file = &value->files[i];
+ file->servicepath = state->servicename_new;
+ file->base_name = state->filename_new;
+ file->stream_name = state->stream_name_new;
+
+ *modified = true;
+}
+
+NTSTATUS leases_db_rename(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *id,
+ const char *servicename_new,
+ const char *filename_new,
+ const char *stream_name_new)
+{
+ struct leases_db_rename_state state = {
+ .id = id,
+ .servicename_new = servicename_new,
+ .filename_new = filename_new,
+ .stream_name_new = stream_name_new,
+ };
+ NTSTATUS status;
+
+ status = leases_db_do_locked(
+ client_guid, lease_key, leases_db_rename_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("leases_db_do_locked failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ return state.status;
+}
+
+struct leases_db_set_state {
+ uint32_t current_state;
+ bool breaking;
+ uint32_t breaking_to_requested;
+ uint32_t breaking_to_required;
+ uint16_t lease_version;
+ uint16_t epoch;
+};
+
+static void leases_db_set_fn(
+ struct leases_db_value *value, bool *modified, void *private_data)
+{
+ struct leases_db_set_state *state = private_data;
+
+ if (value->num_files == 0) {
+ DBG_WARNING("leases_db_set on new entry\n");
+ return;
+ }
+ value->current_state = state->current_state;
+ value->breaking = state->breaking;
+ value->breaking_to_requested = state->breaking_to_requested;
+ value->breaking_to_required = state->breaking_to_required;
+ value->lease_version = state->lease_version;
+ value->epoch = state->epoch;
+ *modified = true;
+}
+
+NTSTATUS leases_db_set(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ uint32_t current_state,
+ bool breaking,
+ uint32_t breaking_to_requested,
+ uint32_t breaking_to_required,
+ uint16_t lease_version,
+ uint16_t epoch)
+{
+ struct leases_db_set_state state = {
+ .current_state = current_state,
+ .breaking = breaking,
+ .breaking_to_requested = breaking_to_requested,
+ .breaking_to_required = breaking_to_required,
+ .lease_version = lease_version,
+ .epoch = epoch,
+ };
+ NTSTATUS status;
+
+ status = leases_db_do_locked(
+ client_guid, lease_key, leases_db_set_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("leases_db_do_locked failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+struct leases_db_get_state {
+ const struct file_id *file_id;
+ uint32_t *current_state;
+ bool *breaking;
+ uint32_t *breaking_to_requested;
+ uint32_t *breaking_to_required;
+ uint16_t *lease_version;
+ uint16_t *epoch;
+ NTSTATUS status;
+};
+
+static void leases_db_get_fn(TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct leases_db_get_state *state = private_data;
+ DATA_BLOB blob = { .data = data.dptr, .length = data.dsize };
+ enum ndr_err_code ndr_err;
+ struct leases_db_value *value;
+ uint32_t i;
+
+ value = talloc(talloc_tos(), struct leases_db_value);
+ if (value == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob, value, value,
+ (ndr_pull_flags_fn_t)ndr_pull_leases_db_value);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_pull_struct_blob_failed: %s\n",
+ ndr_errstr(ndr_err));
+ TALLOC_FREE(value);
+ state->status = ndr_map_error2ntstatus(ndr_err);
+ return;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("\n");
+ NDR_PRINT_DEBUG(leases_db_value, value);
+ }
+
+ /* id must exist. */
+ for (i = 0; i < value->num_files; i++) {
+ if (file_id_equal(state->file_id, &value->files[i].id)) {
+ break;
+ }
+ }
+
+ if (i == value->num_files) {
+ state->status = NT_STATUS_NOT_FOUND;
+ TALLOC_FREE(value);
+ return;
+ }
+
+ if (state->current_state != NULL) {
+ *state->current_state = value->current_state;
+ };
+ if (state->breaking != NULL) {
+ *state->breaking = value->breaking;
+ };
+ if (state->breaking_to_requested != NULL) {
+ *state->breaking_to_requested = value->breaking_to_requested;
+ };
+ if (state->breaking_to_required != NULL) {
+ *state->breaking_to_required = value->breaking_to_required;
+ };
+ if (state->lease_version != NULL) {
+ *state->lease_version = value->lease_version;
+ };
+ if (state->epoch != NULL) {
+ *state->epoch = value->epoch;
+ };
+
+ TALLOC_FREE(value);
+ state->status = NT_STATUS_OK;
+}
+
+NTSTATUS leases_db_get(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *file_id,
+ uint32_t *current_state,
+ bool *breaking,
+ uint32_t *breaking_to_requested,
+ uint32_t *breaking_to_required,
+ uint16_t *lease_version,
+ uint16_t *epoch)
+{
+ struct leases_db_get_state state = {
+ .file_id = file_id,
+ .current_state = current_state,
+ .breaking = breaking,
+ .breaking_to_requested = breaking_to_requested,
+ .breaking_to_required = breaking_to_required,
+ .lease_version = lease_version,
+ .epoch = epoch,
+ };
+ struct leases_db_key_buf keybuf;
+ TDB_DATA db_key = leases_db_key(&keybuf, client_guid, lease_key);
+ NTSTATUS status;
+
+ if (!leases_db_init(true)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = dbwrap_parse_record(
+ leases_db, db_key, leases_db_get_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return state.status;
+}
+
+struct leases_db_get_current_state_state {
+ int seqnum;
+ uint32_t current_state;
+ NTSTATUS status;
+};
+
+/*
+ * This function is an optimization that
+ * relies on the fact that the
+ * smb2_lease_state current_state
+ * (which is a uint32_t size)
+ * from struct leases_db_value is the first
+ * entry in the ndr-encoded struct leases_db_value.
+ * Read it without having to ndr decode all
+ * the values in struct leases_db_value.
+ */
+
+static void leases_db_get_current_state_fn(
+ TDB_DATA key, TDB_DATA data, void *private_data)
+{
+ struct leases_db_get_current_state_state *state = private_data;
+ struct ndr_pull ndr;
+ enum ndr_err_code ndr_err;
+
+ if (data.dsize < sizeof(uint32_t)) {
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+
+ state->seqnum = dbwrap_get_seqnum(leases_db);
+
+ ndr = (struct ndr_pull) {
+ .data = data.dptr, .data_size = data.dsize,
+ };
+ ndr_err = ndr_pull_uint32(&ndr, NDR_SCALARS, &state->current_state);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ state->status = ndr_map_error2ntstatus(ndr_err);
+ }
+}
+
+NTSTATUS leases_db_get_current_state(
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ int *database_seqnum,
+ uint32_t *current_state)
+{
+ struct leases_db_get_current_state_state state = { 0 };
+ struct leases_db_key_buf keybuf;
+ TDB_DATA db_key = { 0 };
+ NTSTATUS status;
+
+ if (!leases_db_init(true)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ state.seqnum = dbwrap_get_seqnum(leases_db);
+ if (*database_seqnum == state.seqnum) {
+ return NT_STATUS_OK;
+ }
+
+ db_key = leases_db_key(&keybuf, client_guid, lease_key);
+
+ status = dbwrap_parse_record(
+ leases_db, db_key, leases_db_get_current_state_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *database_seqnum = state.seqnum;
+ *current_state = state.current_state;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS leases_db_copy_file_ids(TALLOC_CTX *mem_ctx,
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ struct file_id **pp_ids)
+{
+ uint32_t i;
+ struct file_id *ids = talloc_array(mem_ctx,
+ struct file_id,
+ num_files);
+ if (ids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_files; i++) {
+ ids[i] = files[i].id;
+ }
+ *pp_ids = ids;
+ return NT_STATUS_OK;
+}
diff --git a/source3/locking/leases_db.h b/source3/locking/leases_db.h
new file mode 100644
index 0000000..9c149c1
--- /dev/null
+++ b/source3/locking/leases_db.h
@@ -0,0 +1,80 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * leases.tdb functions
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LEASES_DB_H_
+#define _LEASES_DB_H_
+
+struct GUID;
+struct smb2_lease_key;
+struct file_id;
+struct leases_db_file;
+
+bool leases_db_init(bool read_only);
+NTSTATUS leases_db_add(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *id,
+ uint32_t current_state,
+ uint16_t lease_version,
+ uint16_t epoch,
+ const char *servicepath,
+ const char *filename,
+ const char *stream_name);
+NTSTATUS leases_db_del(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *id);
+NTSTATUS leases_db_parse(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ void (*parser)(uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data),
+ void *private_data);
+NTSTATUS leases_db_rename(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *id,
+ const char *servicepath_new,
+ const char *filename_new,
+ const char *stream_name_new);
+NTSTATUS leases_db_set(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ uint32_t current_state,
+ bool breaking,
+ uint32_t breaking_to_requested,
+ uint32_t breaking_to_required,
+ uint16_t lease_version,
+ uint16_t epoch);
+NTSTATUS leases_db_get(const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ const struct file_id *file_id,
+ uint32_t *current_state,
+ bool *breaking,
+ uint32_t *breaking_to_requested,
+ uint32_t *breaking_to_required,
+ uint16_t *lease_version,
+ uint16_t *epoch);
+NTSTATUS leases_db_get_current_state(
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key,
+ int *database_seqnum,
+ uint32_t *current_state);
+NTSTATUS leases_db_copy_file_ids(TALLOC_CTX *mem_ctx,
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ struct file_id **pp_ids);
+#endif /* _LEASES_DB_H_ */
diff --git a/source3/locking/leases_util.c b/source3/locking/leases_util.c
new file mode 100644
index 0000000..9ae4081
--- /dev/null
+++ b/source3/locking/leases_util.c
@@ -0,0 +1,77 @@
+/*
+ Unix SMB/CIFS implementation.
+ Lease utility functions
+
+ Copyright (C) Jeremy Allison 2017.
+ Copyright (C) Stefan (metze) Metzmacher 2017.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "locking/proto.h"
+#include "smbd/globals.h"
+#include "locking/leases_db.h"
+
+uint32_t map_oplock_to_lease_type(uint16_t op_type)
+{
+ uint32_t ret;
+
+ switch(op_type) {
+ case BATCH_OPLOCK:
+ case BATCH_OPLOCK|EXCLUSIVE_OPLOCK:
+ ret = SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE;
+ break;
+ case EXCLUSIVE_OPLOCK:
+ ret = SMB2_LEASE_READ|SMB2_LEASE_WRITE;
+ break;
+ case LEVEL_II_OPLOCK:
+ ret = SMB2_LEASE_READ;
+ break;
+ default:
+ ret = SMB2_LEASE_NONE;
+ break;
+ }
+ return ret;
+}
+
+uint32_t fsp_lease_type(struct files_struct *fsp)
+{
+ NTSTATUS status;
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ uint32_t type = map_oplock_to_lease_type(fsp->oplock_type);
+ return type;
+ }
+
+ status = leases_db_get_current_state(
+ fsp_client_guid(fsp),
+ &fsp->lease->lease.lease_key,
+ &fsp->leases_db_seqnum,
+ &fsp->lease_type);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("leases_db_get_current_state failed: %s\n",
+ nt_errstr(status));
+ fsp->lease_type = 0; /* no lease */
+ }
+
+ return fsp->lease_type;
+}
+
+const struct GUID *fsp_client_guid(const files_struct *fsp)
+{
+ return &fsp->conn->sconn->client->global->client_guid;
+}
diff --git a/source3/locking/locking.c b/source3/locking/locking.c
new file mode 100644
index 0000000..fa1c85a
--- /dev/null
+++ b/source3/locking/locking.c
@@ -0,0 +1,1359 @@
+/*
+ Unix SMB/CIFS implementation.
+ Locking functions
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 1992-2006
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Revision History:
+
+ 12 aug 96: Erik.Devriendt@te6.siemens.be
+ added support for shared memory implementation of share mode locking
+
+ May 1997. Jeremy Allison (jallison@whistle.com). Modified share mode
+ locking to deal with multiple share modes per open file.
+
+ September 1997. Jeremy Allison (jallison@whistle.com). Added oplock
+ support.
+
+ rewritten completely to use new tdb code. Tridge, Dec '99
+
+ Added POSIX locking support. Jeremy Allison (jeremy@valinux.com), Apr. 2000.
+ Added Unix Extensions POSIX locking support. Jeremy Allison Mar 2006.
+*/
+
+#include "includes.h"
+#include "lib/util/time_basic.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "share_mode_lock.h"
+#include "share_mode_lock_private.h"
+#include "locking/proto.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "serverid.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+#include "librpc/gen_ndr/ndr_file_id.h"
+#include "librpc/gen_ndr/ndr_leases_db.h"
+#include "locking/leases_db.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+#define NO_LOCKING_COUNT (-1)
+
+/****************************************************************************
+ Debugging aids :-).
+****************************************************************************/
+
+const char *lock_type_name(enum brl_type lock_type)
+{
+ switch (lock_type) {
+ case READ_LOCK:
+ return "READ";
+ case WRITE_LOCK:
+ return "WRITE";
+ default:
+ return "other";
+ }
+}
+
+const char *lock_flav_name(enum brl_flavour lock_flav)
+{
+ return (lock_flav == WINDOWS_LOCK) ? "WINDOWS_LOCK" : "POSIX_LOCK";
+}
+
+/****************************************************************************
+ Utility function called to see if a file region is locked.
+ Called in the read/write codepath.
+****************************************************************************/
+
+void init_strict_lock_struct(files_struct *fsp,
+ uint64_t smblctx,
+ br_off start,
+ br_off size,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ struct lock_struct *plock)
+{
+ SMB_ASSERT(lock_type == READ_LOCK || lock_type == WRITE_LOCK);
+
+ plock->context.smblctx = smblctx;
+ plock->context.tid = fsp->conn->cnum;
+ plock->context.pid = messaging_server_id(fsp->conn->sconn->msg_ctx);
+ plock->start = start;
+ plock->size = size;
+ plock->fnum = fsp->fnum;
+ plock->lock_type = lock_type;
+ plock->lock_flav = lp_posix_cifsu_locktype(fsp);
+}
+
+bool strict_lock_check_default(files_struct *fsp, struct lock_struct *plock)
+{
+ struct byte_range_lock *br_lck;
+ int strict_locking = lp_strict_locking(fsp->conn->params);
+ bool ret = False;
+
+ if (plock->size == 0) {
+ return True;
+ }
+
+ if (!lp_locking(fsp->conn->params) || !strict_locking) {
+ return True;
+ }
+
+ if (strict_locking == Auto) {
+ uint32_t lease_type = fsp_lease_type(fsp);
+
+ if ((lease_type & SMB2_LEASE_READ) &&
+ (plock->lock_type == READ_LOCK))
+ {
+ DBG_DEBUG("optimisation - read lease on file %s\n",
+ fsp_str_dbg(fsp));
+ return true;
+ }
+
+ if ((lease_type & SMB2_LEASE_WRITE) &&
+ (plock->lock_type == WRITE_LOCK))
+ {
+ DBG_DEBUG("optimisation - write lease on file %s\n",
+ fsp_str_dbg(fsp));
+ return true;
+ }
+ }
+
+ br_lck = brl_get_locks_readonly(fsp);
+ if (!br_lck) {
+ return true;
+ }
+ ret = brl_locktest(br_lck, plock);
+
+ if (!ret) {
+ /*
+ * We got a lock conflict. Retry with rw locks to enable
+ * autocleanup. This is the slow path anyway.
+ */
+ br_lck = brl_get_locks(talloc_tos(), fsp);
+ if (br_lck == NULL) {
+ return true;
+ }
+ ret = brl_locktest(br_lck, plock);
+ TALLOC_FREE(br_lck);
+ }
+
+ DEBUG(10, ("strict_lock_default: flavour = %s brl start=%ju "
+ "len=%ju %s for fnum %ju file %s\n",
+ lock_flav_name(plock->lock_flav),
+ (uintmax_t)plock->start, (uintmax_t)plock->size,
+ ret ? "unlocked" : "locked",
+ (uintmax_t)plock->fnum, fsp_str_dbg(fsp)));
+
+ return ret;
+}
+
+/****************************************************************************
+ Find out if a lock could be granted - return who is blocking us if we can't.
+****************************************************************************/
+
+NTSTATUS query_lock(files_struct *fsp,
+ uint64_t *psmblctx,
+ uint64_t *pcount,
+ uint64_t *poffset,
+ enum brl_type *plock_type,
+ enum brl_flavour lock_flav)
+{
+ struct byte_range_lock *br_lck = NULL;
+
+ if (!fsp->fsp_flags.can_lock) {
+ return fsp->fsp_flags.is_directory ?
+ NT_STATUS_INVALID_DEVICE_REQUEST :
+ NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!lp_locking(fsp->conn->params)) {
+ return NT_STATUS_OK;
+ }
+
+ br_lck = brl_get_locks_readonly(fsp);
+ if (!br_lck) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return brl_lockquery(br_lck,
+ psmblctx,
+ messaging_server_id(fsp->conn->sconn->msg_ctx),
+ poffset,
+ pcount,
+ plock_type,
+ lock_flav);
+}
+
+static void increment_current_lock_count(files_struct *fsp,
+ enum brl_flavour lock_flav)
+{
+ if (lock_flav == WINDOWS_LOCK &&
+ fsp->current_lock_count != NO_LOCKING_COUNT) {
+ /* blocking ie. pending, locks also count here,
+ * as this is an efficiency counter to avoid checking
+ * the lock db. on close. JRA. */
+
+ fsp->current_lock_count++;
+ } else {
+ /* Notice that this has had a POSIX lock request.
+ * We can't count locks after this so forget them.
+ */
+ fsp->current_lock_count = NO_LOCKING_COUNT;
+ }
+}
+
+static void decrement_current_lock_count(files_struct *fsp,
+ enum brl_flavour lock_flav)
+{
+ if (lock_flav == WINDOWS_LOCK &&
+ fsp->current_lock_count != NO_LOCKING_COUNT) {
+ SMB_ASSERT(fsp->current_lock_count > 0);
+ fsp->current_lock_count--;
+ }
+}
+
+/****************************************************************************
+ Utility function called by locking requests.
+****************************************************************************/
+
+struct do_lock_state {
+ struct files_struct *fsp;
+ TALLOC_CTX *req_mem_ctx;
+ const struct GUID *req_guid;
+ uint64_t smblctx;
+ uint64_t count;
+ uint64_t offset;
+ enum brl_type lock_type;
+ enum brl_flavour lock_flav;
+
+ struct server_id blocker_pid;
+ uint64_t blocker_smblctx;
+ NTSTATUS status;
+};
+
+static void do_lock_fn(
+ struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct do_lock_state *state = private_data;
+ struct byte_range_lock *br_lck = NULL;
+
+ br_lck = brl_get_locks_for_locking(talloc_tos(),
+ state->fsp,
+ state->req_mem_ctx,
+ state->req_guid);
+ if (br_lck == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+
+ state->status = brl_lock(
+ br_lck,
+ state->smblctx,
+ messaging_server_id(state->fsp->conn->sconn->msg_ctx),
+ state->offset,
+ state->count,
+ state->lock_type,
+ state->lock_flav,
+ &state->blocker_pid,
+ &state->blocker_smblctx);
+
+ TALLOC_FREE(br_lck);
+}
+
+NTSTATUS do_lock(files_struct *fsp,
+ TALLOC_CTX *req_mem_ctx,
+ const struct GUID *req_guid,
+ uint64_t smblctx,
+ uint64_t count,
+ uint64_t offset,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ struct server_id *pblocker_pid,
+ uint64_t *psmblctx)
+{
+ struct do_lock_state state = {
+ .fsp = fsp,
+ .req_mem_ctx = req_mem_ctx,
+ .req_guid = req_guid,
+ .smblctx = smblctx,
+ .count = count,
+ .offset = offset,
+ .lock_type = lock_type,
+ .lock_flav = lock_flav,
+ };
+ NTSTATUS status;
+
+ /* silently return ok on print files as we don't do locking there */
+ if (fsp->print_file) {
+ return NT_STATUS_OK;
+ }
+
+ if (!fsp->fsp_flags.can_lock) {
+ if (fsp->fsp_flags.is_directory) {
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!lp_locking(fsp->conn->params)) {
+ return NT_STATUS_OK;
+ }
+
+ /* NOTE! 0 byte long ranges ARE allowed and should be stored */
+
+ DBG_DEBUG("lock flavour %s lock type %s start=%"PRIu64" len=%"PRIu64" "
+ "requested for %s file %s\n",
+ lock_flav_name(lock_flav),
+ lock_type_name(lock_type),
+ offset,
+ count,
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp));
+
+ status = share_mode_do_locked_vfs_allowed(fsp->file_id,
+ do_lock_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("share_mode_do_locked returned %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (psmblctx != NULL) {
+ *psmblctx = state.blocker_smblctx;
+ }
+ if (pblocker_pid != NULL) {
+ *pblocker_pid = state.blocker_pid;
+ }
+
+ DBG_DEBUG("returning status=%s\n", nt_errstr(state.status));
+
+ increment_current_lock_count(fsp, lock_flav);
+
+ return state.status;
+}
+
+/****************************************************************************
+ Utility function called by unlocking requests.
+****************************************************************************/
+
+NTSTATUS do_unlock(files_struct *fsp,
+ uint64_t smblctx,
+ uint64_t count,
+ uint64_t offset,
+ enum brl_flavour lock_flav)
+{
+ bool ok = False;
+ struct byte_range_lock *br_lck = NULL;
+
+ if (!fsp->fsp_flags.can_lock) {
+ return fsp->fsp_flags.is_directory ?
+ NT_STATUS_INVALID_DEVICE_REQUEST :
+ NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!lp_locking(fsp->conn->params)) {
+ return NT_STATUS_OK;
+ }
+
+ DBG_DEBUG("unlock start=%"PRIu64" len=%"PRIu64" requested for %s file "
+ "%s\n",
+ offset,
+ count,
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp));
+
+ br_lck = brl_get_locks(talloc_tos(), fsp);
+ if (!br_lck) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = brl_unlock(br_lck,
+ smblctx,
+ messaging_server_id(fsp->conn->sconn->msg_ctx),
+ offset,
+ count,
+ lock_flav);
+
+ TALLOC_FREE(br_lck);
+
+ if (!ok) {
+ DEBUG(10,("do_unlock: returning ERRlock.\n" ));
+ return NT_STATUS_RANGE_NOT_LOCKED;
+ }
+
+ decrement_current_lock_count(fsp, lock_flav);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Remove any locks on this fd. Called from file_close().
+****************************************************************************/
+
+void locking_close_file(files_struct *fsp,
+ enum file_close_type close_type)
+{
+ struct byte_range_lock *br_lck;
+
+ if (!lp_locking(fsp->conn->params)) {
+ return;
+ }
+
+ /* If we have no outstanding locks or pending
+ * locks then we don't need to look in the lock db.
+ */
+
+ if (fsp->current_lock_count == 0) {
+ return;
+ }
+
+ br_lck = brl_get_locks(talloc_tos(),fsp);
+
+ if (br_lck) {
+ /*
+ * Unlocks must trigger dbwrap_watch watchers,
+ * normally in smbd_do_unlocking. Here it's done
+ * implicitly, we're closing the file and thus remove a
+ * share mode. This will wake the waiters.
+ */
+ brl_close_fnum(br_lck);
+ TALLOC_FREE(br_lck);
+ }
+}
+
+/*******************************************************************
+ Print out a share mode.
+********************************************************************/
+
+char *share_mode_str(TALLOC_CTX *ctx, int num,
+ const struct file_id *id,
+ const struct share_mode_entry *e)
+{
+ struct server_id_buf tmp;
+ struct file_id_buf ftmp;
+
+ return talloc_asprintf(ctx, "share_mode_entry[%d]: "
+ "pid = %s, share_access = 0x%x, private_options = 0x%x, "
+ "access_mask = 0x%x, mid = 0x%llx, type= 0x%x, gen_id = %llu, "
+ "uid = %u, flags = %u, file_id %s, name_hash = 0x%x",
+ num,
+ server_id_str_buf(e->pid, &tmp),
+ e->share_access, e->private_options,
+ e->access_mask, (unsigned long long)e->op_mid,
+ e->op_type, (unsigned long long)e->share_file_id,
+ (unsigned int)e->uid, (unsigned int)e->flags,
+ file_id_str_buf(*id, &ftmp),
+ (unsigned int)e->name_hash);
+}
+
+struct rename_share_filename_state {
+ struct share_mode_data *data;
+ struct messaging_context *msg_ctx;
+ struct server_id self;
+ uint32_t orig_name_hash;
+ uint32_t new_name_hash;
+ struct file_rename_message msg;
+};
+
+static bool rename_lease_fn(struct share_mode_entry *e,
+ void *private_data)
+{
+ struct rename_share_filename_state *state = private_data;
+ struct share_mode_data *d = state->data;
+ NTSTATUS status;
+
+ status = leases_db_rename(&e->client_guid,
+ &e->lease_key,
+ &d->id,
+ d->servicepath,
+ d->base_name,
+ d->stream_name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ DBG_WARNING("Failed to rename lease key for "
+ "renamed file %s:%s. %s\n",
+ d->base_name,
+ d->stream_name,
+ nt_errstr(status));
+ }
+
+ return false;
+}
+
+/*******************************************************************
+ Sets the service name and filename for rename.
+ At this point we emit "file renamed" messages to all
+ process id's that have this file open.
+ Based on an initial code idea from SATOH Fumiyasu <fumiya@samba.gr.jp>
+********************************************************************/
+
+static bool rename_share_filename_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct rename_share_filename_state *state = private_data;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ bool ok;
+
+ /*
+ * If this is a hardlink to the inode with a different name,
+ * skip this.
+ */
+ if (e->name_hash != state->orig_name_hash) {
+ return false;
+ }
+ e->name_hash = state->new_name_hash;
+ *modified = true;
+
+ ok = server_id_equal(&e->pid, &state->self);
+ if (ok) {
+ return false;
+ }
+
+ state->msg.share_file_id = e->share_file_id;
+
+ ndr_err = ndr_push_struct_blob(
+ &blob,
+ talloc_tos(),
+ &state->msg,
+ (ndr_push_flags_fn_t)ndr_push_file_rename_message);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_push_file_rename_message failed: %s\n",
+ ndr_errstr(ndr_err));
+ return false;
+ }
+ if (DEBUGLEVEL >= 10) {
+ struct server_id_buf tmp;
+ DBG_DEBUG("sending rename message to %s\n",
+ server_id_str_buf(e->pid, &tmp));
+ NDR_PRINT_DEBUG(file_rename_message, &state->msg);
+ }
+
+ messaging_send(state->msg_ctx, e->pid, MSG_SMB_FILE_RENAME, &blob);
+
+ TALLOC_FREE(blob.data);
+
+ return false;
+}
+
+bool rename_share_filename(struct messaging_context *msg_ctx,
+ struct share_mode_lock *lck,
+ struct file_id id,
+ const char *servicepath,
+ uint32_t orig_name_hash,
+ uint32_t new_name_hash,
+ const struct smb_filename *smb_fname_dst)
+{
+ struct rename_share_filename_state state = {
+ .msg_ctx = msg_ctx,
+ .self = messaging_server_id(msg_ctx),
+ .orig_name_hash = orig_name_hash,
+ .new_name_hash = new_name_hash,
+ .msg.id = id,
+ .msg.servicepath = servicepath,
+ .msg.base_name = smb_fname_dst->base_name,
+ .msg.stream_name = smb_fname_dst->stream_name,
+ };
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ DEBUG(10, ("rename_share_filename: servicepath %s newname %s\n",
+ servicepath, smb_fname_dst->base_name));
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "servicepath %s newname %s - %s\n",
+ servicepath, smb_fname_dst->base_name,
+ nt_errstr(status));
+ return false;
+ }
+ state.data = d;
+
+ /*
+ * rename_internal_fsp() and rename_internals() add './' to
+ * head of newname if newname does not contain a '/'.
+ */
+
+ if (strncmp(state.msg.base_name, "./", 2) == 0) {
+ state.msg.base_name += 2;
+ }
+
+ d->servicepath = talloc_strdup(d, state.msg.servicepath);
+ d->base_name = talloc_strdup(d, state.msg.base_name);
+ d->stream_name = talloc_strdup(d, state.msg.stream_name);
+ if ((d->servicepath == NULL) ||
+ (d->base_name == NULL) ||
+ ((state.msg.stream_name != NULL) && (d->stream_name == NULL))) {
+ DBG_WARNING("talloc failed\n");
+ return false;
+ }
+ d->modified = True;
+
+ ok = share_mode_forall_entries(
+ lck, rename_share_filename_fn, &state);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_entries failed\n");
+ }
+
+ ok = share_mode_forall_leases(lck, rename_lease_fn, &state);
+ if (!ok) {
+ /*
+ * Ignore error here. Not sure what to do..
+ */
+ DBG_WARNING("share_mode_forall_leases failed\n");
+ }
+
+ return True;
+}
+
+void get_file_infos(struct file_id id,
+ uint32_t name_hash,
+ bool *delete_on_close,
+ struct timespec *write_time)
+{
+ struct share_mode_lock *lck;
+
+ if (delete_on_close) {
+ *delete_on_close = false;
+ }
+
+ if (write_time) {
+ *write_time = make_omit_timespec();
+ }
+
+ if (!(lck = fetch_share_mode_unlocked(talloc_tos(), id))) {
+ return;
+ }
+
+ if (delete_on_close) {
+ *delete_on_close = is_delete_on_close_set(lck, name_hash);
+ }
+
+ if (write_time) {
+ *write_time = get_share_mode_write_time(lck);
+ }
+
+ TALLOC_FREE(lck);
+}
+
+bool is_valid_share_mode_entry(const struct share_mode_entry *e)
+{
+ int num_props = 0;
+
+ if (e->stale) {
+ return false;
+ }
+
+ num_props += ((e->op_type == NO_OPLOCK) ? 1 : 0);
+ num_props += (EXCLUSIVE_OPLOCK_TYPE(e->op_type) ? 1 : 0);
+ num_props += (LEVEL_II_OPLOCK_TYPE(e->op_type) ? 1 : 0);
+ num_props += (e->op_type == LEASE_OPLOCK);
+
+ if ((num_props > 1) && serverid_exists(&e->pid)) {
+ smb_panic("Invalid share mode entry");
+ }
+ return (num_props != 0);
+}
+
+struct find_lease_ref_state {
+ const struct GUID *client_guid;
+ const struct smb2_lease_key *lease_key;
+ bool found_same;
+};
+
+static bool find_lease_ref_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct find_lease_ref_state *state = private_data;
+
+ if (e->stale) {
+ return false;
+ }
+ if (e->op_type != LEASE_OPLOCK) {
+ return false;
+ }
+
+ state->found_same = smb2_lease_equal(
+ &e->client_guid,
+ &e->lease_key,
+ state->client_guid,
+ state->lease_key);
+ /*
+ * If we found a lease reference, look no further (i.e. return true)
+ */
+ return state->found_same;
+}
+
+NTSTATUS remove_lease_if_stale(struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key)
+{
+ struct find_lease_ref_state state = {
+ .client_guid = client_guid, .lease_key = lease_key,
+ };
+ struct file_id id = share_mode_lock_file_id(lck);
+ NTSTATUS status;
+ bool ok;
+
+ ok = share_mode_forall_entries(lck, find_lease_ref_fn, &state);
+ if (!ok) {
+ DBG_ERR("share_mode_forall_entries failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (state.found_same) {
+ return NT_STATUS_RESOURCE_IN_USE;
+ }
+
+ status = leases_db_del(client_guid, lease_key, &id);
+ if (!NT_STATUS_IS_OK(status)) {
+ int level = DBGLVL_DEBUG;
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ level = DBGLVL_ERR;
+ }
+ DBG_PREFIX(level, ("leases_db_del failed: %s\n",
+ nt_errstr(status)));
+ }
+ return status;
+}
+
+bool share_entry_stale_pid(struct share_mode_entry *e)
+{
+ struct server_id_buf buf;
+ bool exists;
+
+ if (e->stale) {
+ return true;
+ }
+
+ exists = serverid_exists(&e->pid);
+ if (exists) {
+ DBG_DEBUG("PID %s still exists\n",
+ server_id_str_buf(e->pid, &buf));
+ return false;
+ }
+
+ DBG_DEBUG("PID %s does not exist anymore\n",
+ server_id_str_buf(e->pid, &buf));
+
+ e->stale = true;
+
+ return true;
+}
+
+/****************************************************************************
+ Adds a delete on close token.
+****************************************************************************/
+
+static bool add_delete_on_close_token(struct share_mode_data *d,
+ uint32_t name_hash,
+ const struct security_token *nt_tok,
+ const struct security_unix_token *tok)
+{
+ struct delete_token *tmp, *dtl;
+
+ tmp = talloc_realloc(d, d->delete_tokens, struct delete_token,
+ d->num_delete_tokens+1);
+ if (tmp == NULL) {
+ return false;
+ }
+ d->delete_tokens = tmp;
+ dtl = &d->delete_tokens[d->num_delete_tokens];
+
+ dtl->name_hash = name_hash;
+ dtl->delete_nt_token = security_token_duplicate(d->delete_tokens, nt_tok);
+ if (dtl->delete_nt_token == NULL) {
+ return false;
+ }
+ dtl->delete_token = copy_unix_token(d->delete_tokens, tok);
+ if (dtl->delete_token == NULL) {
+ return false;
+ }
+ d->num_delete_tokens += 1;
+ d->modified = true;
+ return true;
+}
+
+void reset_delete_on_close_lck(files_struct *fsp,
+ struct share_mode_lock *lck)
+{
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+ uint32_t i;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s - %s\n", fsp_str_dbg(fsp), nt_errstr(status));
+ smb_panic(__location__);
+ return;
+ }
+
+ for (i=0; i<d->num_delete_tokens; i++) {
+ struct delete_token *dt = &d->delete_tokens[i];
+
+ if (dt->name_hash == fsp->name_hash) {
+ d->modified = true;
+
+ /* Delete this entry. */
+ TALLOC_FREE(dt->delete_nt_token);
+ TALLOC_FREE(dt->delete_token);
+ *dt = d->delete_tokens[d->num_delete_tokens-1];
+ d->num_delete_tokens -= 1;
+ }
+ }
+}
+
+struct set_delete_on_close_state {
+ struct messaging_context *msg_ctx;
+ DATA_BLOB blob;
+};
+
+static bool set_delete_on_close_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct set_delete_on_close_state *state = private_data;
+ NTSTATUS status;
+
+ status = messaging_send(
+ state->msg_ctx,
+ e->pid,
+ MSG_SMB_NOTIFY_CANCEL_DELETED,
+ &state->blob);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ struct server_id_buf tmp;
+ DBG_DEBUG("messaging_send to %s returned %s\n",
+ server_id_str_buf(e->pid, &tmp),
+ nt_errstr(status));
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ Sets the delete on close flag over all share modes on this file.
+ Modify the share mode entry for all files open
+ on this device and inode to tell other smbds we have
+ changed the delete on close flag. This will be noticed
+ in the close code, the last closer will delete the file
+ if flag is set.
+ This makes a copy of any struct security_unix_token into the
+ lck entry. This function is used when the lock is already granted.
+****************************************************************************/
+
+void set_delete_on_close_lck(files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct security_token *nt_tok,
+ const struct security_unix_token *tok)
+{
+ struct share_mode_data *d = NULL;
+ struct set_delete_on_close_state state = {
+ .msg_ctx = fsp->conn->sconn->msg_ctx
+ };
+ uint32_t i;
+ bool ret;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s - %s\n", fsp_str_dbg(fsp), nt_errstr(status));
+ smb_panic(__location__);
+ return;
+ }
+
+ SMB_ASSERT(nt_tok != NULL);
+ SMB_ASSERT(tok != NULL);
+
+ for (i=0; i<d->num_delete_tokens; i++) {
+ struct delete_token *dt = &d->delete_tokens[i];
+ if (dt->name_hash == fsp->name_hash) {
+ d->modified = true;
+
+ /* Replace this token with the given tok. */
+ TALLOC_FREE(dt->delete_nt_token);
+ dt->delete_nt_token = security_token_duplicate(dt, nt_tok);
+ SMB_ASSERT(dt->delete_nt_token != NULL);
+ TALLOC_FREE(dt->delete_token);
+ dt->delete_token = copy_unix_token(dt, tok);
+ SMB_ASSERT(dt->delete_token != NULL);
+
+ return;
+ }
+ }
+
+ ret = add_delete_on_close_token(d, fsp->name_hash, nt_tok, tok);
+ SMB_ASSERT(ret);
+
+ ndr_err = ndr_push_struct_blob(
+ &state.blob,
+ talloc_tos(),
+ &fsp->file_id,
+ (ndr_push_flags_fn_t)ndr_push_file_id);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_push_file_id failed: %s\n",
+ ndr_errstr(ndr_err));
+ smb_panic(__location__);
+ }
+
+ ret = share_mode_forall_entries(
+ lck, set_delete_on_close_fn, &state);
+ if (!ret) {
+ DBG_ERR("share_mode_forall_entries failed\n");
+ smb_panic(__location__);
+ }
+
+ TALLOC_FREE(state.blob.data);
+}
+
+struct set_delete_on_close_locked_state {
+ struct files_struct *fsp;
+ bool delete_on_close;
+ const struct security_token *nt_tok;
+ const struct security_unix_token *tok;
+};
+
+static void set_delete_on_close_locked(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct set_delete_on_close_locked_state *state =
+ (struct set_delete_on_close_locked_state *)private_data;
+
+ if (state->delete_on_close) {
+ set_delete_on_close_lck(state->fsp,
+ lck,
+ state->nt_tok,
+ state->tok);
+ } else {
+ reset_delete_on_close_lck(state->fsp, lck);
+ }
+
+ state->fsp->fsp_flags.delete_on_close = state->delete_on_close;
+}
+
+bool set_delete_on_close(files_struct *fsp, bool delete_on_close,
+ const struct security_token *nt_tok,
+ const struct security_unix_token *tok)
+{
+ struct set_delete_on_close_locked_state state = {
+ .fsp = fsp,
+ .delete_on_close = delete_on_close,
+ .nt_tok = nt_tok,
+ .tok = tok,
+ };
+ NTSTATUS status;
+
+ DEBUG(10,("set_delete_on_close: %s delete on close flag for "
+ "%s, file %s\n",
+ delete_on_close ? "Adding" : "Removing", fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp)));
+
+ if (fsp->fsp_flags.is_directory) {
+ SMB_ASSERT(!is_ntfs_stream_smb_fname(fsp->fsp_name));
+ }
+
+ status = share_mode_do_locked_vfs_denied(fsp->file_id,
+ set_delete_on_close_locked,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ return True;
+}
+
+static struct delete_token *find_delete_on_close_token(
+ struct share_mode_data *d, uint32_t name_hash)
+{
+ uint32_t i;
+
+ DBG_DEBUG("name_hash = 0x%"PRIx32"\n", name_hash);
+
+ for (i=0; i<d->num_delete_tokens; i++) {
+ struct delete_token *dt = &d->delete_tokens[i];
+
+ DBG_DEBUG("dt->name_hash = 0x%"PRIx32"\n",
+ dt->name_hash);
+ if (dt->name_hash == name_hash) {
+ return dt;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Return the NT token and UNIX token if there's a match. Return true if
+ found, false if not.
+****************************************************************************/
+
+bool get_delete_on_close_token(struct share_mode_lock *lck,
+ uint32_t name_hash,
+ const struct security_token **pp_nt_tok,
+ const struct security_unix_token **pp_tok)
+{
+ struct share_mode_data *d = NULL;
+ struct delete_token *dt;
+ NTSTATUS status;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct file_id_buf id_buf;
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s name_hash=%"PRIu32" - %s\n",
+ file_id_str_buf(id, &id_buf), name_hash,
+ nt_errstr(status));
+ return false;
+ }
+
+ dt = find_delete_on_close_token(d, name_hash);
+ if (dt == NULL) {
+ return false;
+ }
+ *pp_nt_tok = dt->delete_nt_token;
+ *pp_tok = dt->delete_token;
+ return true;
+}
+
+bool is_delete_on_close_set(struct share_mode_lock *lck, uint32_t name_hash)
+{
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct file_id_buf id_buf;
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s name_hash=%"PRIu32" - %s\n",
+ file_id_str_buf(id, &id_buf), name_hash,
+ nt_errstr(status));
+ return false;
+ }
+
+ return find_delete_on_close_token(d, name_hash) != NULL;
+}
+
+struct set_sticky_write_time_state {
+ struct file_id fileid;
+ struct timespec write_time;
+ bool ok;
+};
+
+static void set_sticky_write_time_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct set_sticky_write_time_state *state = private_data;
+ struct share_mode_data *d = NULL;
+ struct file_id_buf ftmp;
+ struct timeval_buf tbuf;
+ NTSTATUS status;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s id=%s - %s\n",
+ timespec_string_buf(&state->write_time, true, &tbuf),
+ file_id_str_buf(state->fileid, &ftmp),
+ nt_errstr(status));
+ return;
+ }
+
+ share_mode_set_changed_write_time(lck, state->write_time);
+
+ state->ok = true;
+}
+
+bool set_sticky_write_time(struct file_id fileid, struct timespec write_time)
+{
+ struct set_sticky_write_time_state state = {
+ .fileid = fileid,
+ .write_time = write_time,
+ };
+ struct file_id_buf ftmp;
+ struct timeval_buf tbuf;
+ NTSTATUS status;
+
+ status = share_mode_do_locked_vfs_denied(fileid,
+ set_sticky_write_time_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_do_locked_vfs_denied() failed for "
+ "%s id=%s - %s\n",
+ timespec_string_buf(&write_time, true, &tbuf),
+ file_id_str_buf(fileid, &ftmp),
+ nt_errstr(status));
+ return false;
+ }
+
+ return state.ok;
+}
+
+struct set_write_time_state {
+ struct file_id fileid;
+ struct timespec write_time;
+ bool ok;
+};
+
+static void set_write_time_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct set_write_time_state *state = private_data;
+ struct share_mode_data *d = NULL;
+ struct file_id_buf idbuf;
+ struct timeval_buf tbuf;
+ NTSTATUS status;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s id=%s - %s\n",
+ timespec_string_buf(&state->write_time, true, &tbuf),
+ file_id_str_buf(state->fileid, &idbuf),
+ nt_errstr(status));
+ return;
+ }
+
+ share_mode_set_old_write_time(lck, state->write_time);
+
+ state->ok = true;
+}
+
+bool set_write_time(struct file_id fileid, struct timespec write_time)
+{
+ struct set_write_time_state state = {
+ .fileid = fileid,
+ .write_time = write_time,
+ };
+ struct file_id_buf idbuf;
+ struct timeval_buf tbuf;
+ NTSTATUS status;
+
+ status = share_mode_do_locked_vfs_denied(fileid,
+ set_write_time_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_do_locked_vfs_denied() failed for "
+ "%s id=%s - %s\n",
+ timespec_string_buf(&write_time, true, &tbuf),
+ file_id_str_buf(fileid, &idbuf),
+ nt_errstr(status));
+ return false;
+ }
+
+ return state.ok;
+}
+
+struct timespec get_share_mode_write_time(struct share_mode_lock *lck)
+{
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct file_id_buf id_buf;
+ struct timespec ts_zero = {};
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s - %s\n",
+ file_id_str_buf(id, &id_buf),
+ nt_errstr(status));
+ smb_panic(__location__);
+ return ts_zero;
+ }
+
+ if (!null_nttime(d->changed_write_time)) {
+ return nt_time_to_full_timespec(d->changed_write_time);
+ }
+ return nt_time_to_full_timespec(d->old_write_time);
+}
+
+struct file_has_open_streams_state {
+ bool found_one;
+ bool ok;
+};
+
+static bool file_has_open_streams_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct file_has_open_streams_state *state = private_data;
+
+ if ((e->private_options &
+ NTCREATEX_FLAG_STREAM_BASEOPEN) == 0) {
+ return false;
+ }
+
+ if (share_entry_stale_pid(e)) {
+ return false;
+ }
+
+ state->found_one = true;
+ return true;
+}
+
+static void file_has_open_streams_locked(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct file_has_open_streams_state *state = private_data;
+
+ state->ok = share_mode_forall_entries(lck,
+ file_has_open_streams_fn,
+ private_data);
+}
+
+bool file_has_open_streams(files_struct *fsp)
+{
+ struct file_has_open_streams_state state = { .found_one = false };
+ NTSTATUS status;
+
+ status = share_mode_do_locked_vfs_denied(fsp->file_id,
+ file_has_open_streams_locked,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("share_mode_do_locked_vfs_denied() failed - %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (!state.ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ return false;
+ }
+
+ return state.found_one;
+}
+
+/*
+ * Walk share mode entries, looking at every lease only once
+ */
+
+struct share_mode_forall_leases_state {
+ TALLOC_CTX *mem_ctx;
+ struct leases_db_key *leases;
+ bool (*fn)(struct share_mode_entry *e,
+ void *private_data);
+ void *private_data;
+ NTSTATUS status;
+};
+
+static bool share_mode_forall_leases_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct share_mode_forall_leases_state *state = private_data;
+ struct leases_db_key *leases = state->leases;
+ size_t i, num_leases;
+ bool stop;
+
+ if (e->op_type != LEASE_OPLOCK) {
+ return false;
+ }
+
+ num_leases = talloc_array_length(leases);
+
+ for (i=0; i<num_leases; i++) {
+ struct leases_db_key *l = &leases[i];
+ bool same = smb2_lease_equal(
+ &e->client_guid,
+ &e->lease_key,
+ &l->client_guid,
+ &l->lease_key);
+ if (same) {
+ return false;
+ }
+ }
+
+ leases = talloc_realloc(
+ state->mem_ctx,
+ leases,
+ struct leases_db_key,
+ num_leases+1);
+ if (leases == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return true;
+ }
+ leases[num_leases] = (struct leases_db_key) {
+ .client_guid = e->client_guid,
+ .lease_key = e->lease_key,
+ };
+ state->leases = leases;
+
+ stop = state->fn(e, state->private_data);
+ return stop;
+}
+
+bool share_mode_forall_leases(
+ struct share_mode_lock *lck,
+ bool (*fn)(struct share_mode_entry *e,
+ void *private_data),
+ void *private_data)
+{
+ struct share_mode_forall_leases_state state = {
+ .mem_ctx = talloc_tos(),
+ .fn = fn,
+ .private_data = private_data
+ };
+ bool ok;
+
+ ok = share_mode_forall_entries(
+ lck, share_mode_forall_leases_fn, &state);
+ TALLOC_FREE(state.leases);
+ if (!ok) {
+ DBG_ERR("share_mode_forall_entries failed\n");
+ return false;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_ERR("share_mode_forall_leases_fn returned %s\n",
+ nt_errstr(state.status));
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/locking/posix.c b/source3/locking/posix.c
new file mode 100644
index 0000000..eedbbc1
--- /dev/null
+++ b/source3/locking/posix.c
@@ -0,0 +1,1371 @@
+/*
+ Unix SMB/CIFS implementation.
+ Locking functions
+ Copyright (C) Jeremy Allison 1992-2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Revision History:
+
+ POSIX locking support. Jeremy Allison (jeremy@valinux.com), Apr. 2000.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "locking/proto.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "util_tdb.h"
+#include "smbd/fd_handle.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/*
+ * The pending close database handle.
+ */
+
+static struct db_context *posix_pending_close_db;
+
+/****************************************************************************
+ First - the functions that deal with the underlying system locks - these
+ functions are used no matter if we're mapping CIFS Windows locks or CIFS
+ POSIX locks onto POSIX.
+****************************************************************************/
+
+/****************************************************************************
+ Utility function to map a lock type correctly depending on the open
+ mode of a file.
+****************************************************************************/
+
+static int map_posix_lock_type( files_struct *fsp, enum brl_type lock_type)
+{
+ if ((lock_type == WRITE_LOCK) && !fsp->fsp_flags.can_write) {
+ /*
+ * Many UNIX's cannot get a write lock on a file opened read-only.
+ * Win32 locking semantics allow this.
+ * Do the best we can and attempt a read-only lock.
+ */
+ DEBUG(10,("map_posix_lock_type: Downgrading write lock to read due to read-only file.\n"));
+ return F_RDLCK;
+ }
+
+ /*
+ * This return should be the most normal, as we attempt
+ * to always open files read/write.
+ */
+
+ return (lock_type == READ_LOCK) ? F_RDLCK : F_WRLCK;
+}
+
+/****************************************************************************
+ Debugging aid :-).
+****************************************************************************/
+
+static const char *posix_lock_type_name(int lock_type)
+{
+ return (lock_type == F_RDLCK) ? "READ" : "WRITE";
+}
+
+/****************************************************************************
+ Check to see if the given unsigned lock range is within the possible POSIX
+ range. Modifies the given args to be in range if possible, just returns
+ False if not.
+****************************************************************************/
+
+#define SMB_OFF_T_BITS (sizeof(off_t)*8)
+
+static bool posix_lock_in_range(off_t *offset_out, off_t *count_out,
+ uint64_t u_offset, uint64_t u_count)
+{
+ off_t offset = (off_t)u_offset;
+ off_t count = (off_t)u_count;
+
+ /*
+ * For the type of system we are, attempt to
+ * find the maximum positive lock offset as an off_t.
+ */
+
+#if defined(MAX_POSITIVE_LOCK_OFFSET) /* Some systems have arbitrary limits. */
+
+ off_t max_positive_lock_offset = (MAX_POSITIVE_LOCK_OFFSET);
+#else
+ /*
+ * In this case off_t is 64 bits,
+ * and the underlying system can handle 64 bit signed locks.
+ */
+
+ off_t mask2 = ((off_t)0x4) << (SMB_OFF_T_BITS-4);
+ off_t mask = (mask2<<1);
+ off_t max_positive_lock_offset = ~mask;
+
+#endif
+ /*
+ * POSIX locks of length zero mean lock to end-of-file.
+ * Win32 locks of length zero are point probes. Ignore
+ * any Win32 locks of length zero. JRA.
+ */
+
+ if (count == 0) {
+ DEBUG(10,("posix_lock_in_range: count = 0, ignoring.\n"));
+ return False;
+ }
+
+ /*
+ * If the given offset was > max_positive_lock_offset then we cannot map this at all
+ * ignore this lock.
+ */
+
+ if (u_offset & ~((uint64_t)max_positive_lock_offset)) {
+ DEBUG(10, ("posix_lock_in_range: (offset = %ju) offset > %ju "
+ "and we cannot handle this. Ignoring lock.\n",
+ (uintmax_t)u_offset,
+ (uintmax_t)max_positive_lock_offset));
+ return False;
+ }
+
+ /*
+ * We must truncate the count to less than max_positive_lock_offset.
+ */
+
+ if (u_count & ~((uint64_t)max_positive_lock_offset)) {
+ count = max_positive_lock_offset;
+ }
+
+ /*
+ * Truncate count to end at max lock offset.
+ */
+
+ if (offset > INT64_MAX - count ||
+ offset + count > max_positive_lock_offset) {
+ count = max_positive_lock_offset - offset;
+ }
+
+ /*
+ * If we ate all the count, ignore this lock.
+ */
+
+ if (count == 0) {
+ DEBUG(10, ("posix_lock_in_range: Count = 0. Ignoring lock "
+ "u_offset = %ju, u_count = %ju\n",
+ (uintmax_t)u_offset,
+ (uintmax_t)u_count));
+ return False;
+ }
+
+ /*
+ * The mapping was successful.
+ */
+
+ DEBUG(10, ("posix_lock_in_range: offset_out = %ju, "
+ "count_out = %ju\n",
+ (uintmax_t)offset, (uintmax_t)count));
+
+ *offset_out = offset;
+ *count_out = count;
+
+ return True;
+}
+
+/****************************************************************************
+ Actual function that does POSIX locks. Copes with 64 -> 32 bit cruft and
+ broken NFS implementations.
+****************************************************************************/
+
+static bool posix_fcntl_lock(files_struct *fsp, int op, off_t offset, off_t count, int type)
+{
+ bool ret;
+
+ DEBUG(8,("posix_fcntl_lock %d %d %jd %jd %d\n",
+ fsp_get_io_fd(fsp),op,(intmax_t)offset,(intmax_t)count,type));
+
+ ret = SMB_VFS_LOCK(fsp, op, offset, count, type);
+
+ if (!ret && ((errno == EFBIG) || (errno == ENOLCK) || (errno == EINVAL))) {
+
+ if ((errno == EINVAL) &&
+ (op != F_GETLK &&
+ op != F_SETLK &&
+ op != F_SETLKW)) {
+ DEBUG(0,("WARNING: OFD locks in use and no kernel "
+ "support. Try setting "
+ "'smbd:force process locks = true' "
+ "in smb.conf\n"));
+ } else {
+ DEBUG(0, ("WARNING: lock request at offset "
+ "%ju, length %ju returned\n",
+ (uintmax_t)offset, (uintmax_t)count));
+ DEBUGADD(0, ("an %s error. This can happen when using 64 bit "
+ "lock offsets\n", strerror(errno)));
+ DEBUGADD(0, ("on 32 bit NFS mounted file systems.\n"));
+ }
+
+ /*
+ * If the offset is > 0x7FFFFFFF then this will cause problems on
+ * 32 bit NFS mounted filesystems. Just ignore it.
+ */
+
+ if (offset & ~((off_t)0x7fffffff)) {
+ DEBUG(0,("Offset greater than 31 bits. Returning success.\n"));
+ return True;
+ }
+
+ if (count & ~((off_t)0x7fffffff)) {
+ /* 32 bit NFS file system, retry with smaller offset */
+ DEBUG(0,("Count greater than 31 bits - retrying with 31 bit truncated length.\n"));
+ errno = 0;
+ count &= 0x7fffffff;
+ ret = SMB_VFS_LOCK(fsp, op, offset, count, type);
+ }
+ }
+
+ DEBUG(8,("posix_fcntl_lock: Lock call %s\n", ret ? "successful" : "failed"));
+ return ret;
+}
+
+/****************************************************************************
+ Actual function that gets POSIX locks. Copes with 64 -> 32 bit cruft and
+ broken NFS implementations.
+****************************************************************************/
+
+static bool posix_fcntl_getlock(files_struct *fsp, off_t *poffset, off_t *pcount, int *ptype)
+{
+ pid_t pid;
+ bool ret;
+
+ DEBUG(8, ("posix_fcntl_getlock %d %ju %ju %d\n",
+ fsp_get_io_fd(fsp), (uintmax_t)*poffset, (uintmax_t)*pcount,
+ *ptype));
+
+ ret = SMB_VFS_GETLOCK(fsp, poffset, pcount, ptype, &pid);
+
+ if (!ret && ((errno == EFBIG) || (errno == ENOLCK) || (errno == EINVAL))) {
+
+ DEBUG(0, ("posix_fcntl_getlock: WARNING: lock request at "
+ "offset %ju, length %ju returned\n",
+ (uintmax_t)*poffset, (uintmax_t)*pcount));
+ DEBUGADD(0, ("an %s error. This can happen when using 64 bit "
+ "lock offsets\n", strerror(errno)));
+ DEBUGADD(0, ("on 32 bit NFS mounted file systems.\n"));
+
+ /*
+ * If the offset is > 0x7FFFFFFF then this will cause problems on
+ * 32 bit NFS mounted filesystems. Just ignore it.
+ */
+
+ if (*poffset & ~((off_t)0x7fffffff)) {
+ DEBUG(0,("Offset greater than 31 bits. Returning success.\n"));
+ return True;
+ }
+
+ if (*pcount & ~((off_t)0x7fffffff)) {
+ /* 32 bit NFS file system, retry with smaller offset */
+ DEBUG(0,("Count greater than 31 bits - retrying with 31 bit truncated length.\n"));
+ errno = 0;
+ *pcount &= 0x7fffffff;
+ ret = SMB_VFS_GETLOCK(fsp,poffset,pcount,ptype,&pid);
+ }
+ }
+
+ DEBUG(8,("posix_fcntl_getlock: Lock query call %s\n", ret ? "successful" : "failed"));
+ return ret;
+}
+
+/****************************************************************************
+ POSIX function to see if a file region is locked. Returns True if the
+ region is locked, False otherwise.
+****************************************************************************/
+
+bool is_posix_locked(files_struct *fsp,
+ uint64_t *pu_offset,
+ uint64_t *pu_count,
+ enum brl_type *plock_type,
+ enum brl_flavour lock_flav)
+{
+ off_t offset;
+ off_t count;
+ int posix_lock_type = map_posix_lock_type(fsp,*plock_type);
+
+ DEBUG(10, ("is_posix_locked: File %s, offset = %ju, count = %ju, "
+ "type = %s\n", fsp_str_dbg(fsp), (uintmax_t)*pu_offset,
+ (uintmax_t)*pu_count, posix_lock_type_name(*plock_type)));
+
+ /*
+ * If the requested lock won't fit in the POSIX range, we will
+ * never set it, so presume it is not locked.
+ */
+
+ if(!posix_lock_in_range(&offset, &count, *pu_offset, *pu_count)) {
+ return False;
+ }
+
+ if (!posix_fcntl_getlock(fsp,&offset,&count,&posix_lock_type)) {
+ return False;
+ }
+
+ if (posix_lock_type == F_UNLCK) {
+ return False;
+ }
+
+ if (lock_flav == POSIX_LOCK) {
+ /* Only POSIX lock queries need to know the details. */
+ *pu_offset = (uint64_t)offset;
+ *pu_count = (uint64_t)count;
+ *plock_type = (posix_lock_type == F_RDLCK) ? READ_LOCK : WRITE_LOCK;
+ }
+ return True;
+}
+
+/****************************************************************************
+ Next - the functions that deal with in memory database storing representations
+ of either Windows CIFS locks or POSIX CIFS locks.
+****************************************************************************/
+
+/* The key used in the in-memory POSIX databases. */
+
+struct lock_ref_count_key {
+ struct file_id id;
+ char r;
+};
+
+/*******************************************************************
+ Form a static locking key for a dev/inode pair for the lock ref count
+******************************************************************/
+
+static TDB_DATA locking_ref_count_key_fsp(const files_struct *fsp,
+ struct lock_ref_count_key *tmp)
+{
+ ZERO_STRUCTP(tmp);
+ tmp->id = fsp->file_id;
+ tmp->r = 'r';
+ return make_tdb_data((uint8_t *)tmp, sizeof(*tmp));
+}
+
+/*******************************************************************
+ Convenience function to get an fd_array key from an fsp.
+******************************************************************/
+
+static TDB_DATA fd_array_key_fsp(const files_struct *fsp)
+{
+ return make_tdb_data((const uint8_t *)&fsp->file_id, sizeof(fsp->file_id));
+}
+
+/*******************************************************************
+ Create the in-memory POSIX lock databases.
+********************************************************************/
+
+bool posix_locking_init(bool read_only)
+{
+ if (posix_pending_close_db != NULL) {
+ return true;
+ }
+
+ posix_pending_close_db = db_open_rbt(NULL);
+
+ if (posix_pending_close_db == NULL) {
+ DEBUG(0,("Failed to open POSIX pending close database.\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/*******************************************************************
+ Delete the in-memory POSIX lock databases.
+********************************************************************/
+
+bool posix_locking_end(void)
+{
+ /*
+ * Shouldn't we close all fd's here?
+ */
+ TALLOC_FREE(posix_pending_close_db);
+ return true;
+}
+
+/****************************************************************************
+ Next - the functions that deal with reference count of number of locks open
+ on a dev/ino pair.
+****************************************************************************/
+
+/****************************************************************************
+ Increase the lock ref count. Creates lock_ref_count entry if it doesn't exist.
+****************************************************************************/
+
+static void increment_lock_ref_count(const files_struct *fsp)
+{
+ struct lock_ref_count_key tmp;
+ int32_t lock_ref_count = 0;
+ NTSTATUS status;
+
+ status = dbwrap_change_int32_atomic(
+ posix_pending_close_db, locking_ref_count_key_fsp(fsp, &tmp),
+ &lock_ref_count, 1);
+
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+ SMB_ASSERT(lock_ref_count < INT32_MAX);
+
+ DEBUG(10,("lock_ref_count for file %s = %d\n",
+ fsp_str_dbg(fsp), (int)(lock_ref_count + 1)));
+}
+
+/****************************************************************************
+ Reduce the lock ref count.
+****************************************************************************/
+
+static void decrement_lock_ref_count(const files_struct *fsp)
+{
+ struct lock_ref_count_key tmp;
+ int32_t lock_ref_count = 0;
+ NTSTATUS status;
+
+ status = dbwrap_change_int32_atomic(
+ posix_pending_close_db, locking_ref_count_key_fsp(fsp, &tmp),
+ &lock_ref_count, -1);
+
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+ SMB_ASSERT(lock_ref_count > 0);
+
+ DEBUG(10,("lock_ref_count for file %s = %d\n",
+ fsp_str_dbg(fsp), (int)(lock_ref_count - 1)));
+}
+
+/****************************************************************************
+ Fetch the lock ref count.
+****************************************************************************/
+
+static int32_t get_lock_ref_count(const files_struct *fsp)
+{
+ struct lock_ref_count_key tmp;
+ NTSTATUS status;
+ int32_t lock_ref_count = 0;
+
+ status = dbwrap_fetch_int32(
+ posix_pending_close_db, locking_ref_count_key_fsp(fsp, &tmp),
+ &lock_ref_count);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(0, ("Error fetching "
+ "lock ref count for file %s: %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ }
+ return lock_ref_count;
+}
+
+/****************************************************************************
+ Delete a lock_ref_count entry.
+****************************************************************************/
+
+static void delete_lock_ref_count(const files_struct *fsp)
+{
+ struct lock_ref_count_key tmp;
+
+ /* Not a bug if it doesn't exist - no locks were ever granted. */
+
+ dbwrap_delete(posix_pending_close_db,
+ locking_ref_count_key_fsp(fsp, &tmp));
+
+ DEBUG(10,("delete_lock_ref_count for file %s\n",
+ fsp_str_dbg(fsp)));
+}
+
+/****************************************************************************
+ Next - the functions that deal with storing fd's that have outstanding
+ POSIX locks when closed.
+****************************************************************************/
+
+/****************************************************************************
+ The records in posix_pending_close_db are composed of an array of
+ ints keyed by dev/ino pair. Those ints are the fd's that were open on
+ this dev/ino pair that should have been closed, but can't as the lock
+ ref count is non zero.
+****************************************************************************/
+
+struct add_fd_to_close_entry_state {
+ const struct files_struct *fsp;
+};
+
+static void add_fd_to_close_entry_fn(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct add_fd_to_close_entry_state *state = private_data;
+ int fd = fsp_get_pathref_fd(state->fsp);
+ TDB_DATA values[] = {
+ value,
+ { .dptr = (uint8_t *)&fd,
+ .dsize = sizeof(fd) },
+ };
+ NTSTATUS status;
+
+ SMB_ASSERT((values[0].dsize % sizeof(int)) == 0);
+
+ status = dbwrap_record_storev(rec, values, ARRAY_SIZE(values), 0);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+}
+
+/****************************************************************************
+ Add an fd to the pending close db.
+****************************************************************************/
+
+static void add_fd_to_close_entry(const files_struct *fsp)
+{
+ struct add_fd_to_close_entry_state state = { .fsp = fsp };
+ NTSTATUS status;
+
+ status = dbwrap_do_locked(
+ posix_pending_close_db,
+ fd_array_key_fsp(fsp),
+ add_fd_to_close_entry_fn,
+ &state);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+ DBG_DEBUG("added fd %d file %s\n",
+ fsp_get_pathref_fd(fsp),
+ fsp_str_dbg(fsp));
+}
+
+static void fd_close_posix_fn(
+ struct db_record *rec,
+ TDB_DATA data,
+ void *private_data)
+{
+ int *saved_errno = (int *)private_data;
+ size_t num_fds, i;
+
+ SMB_ASSERT((data.dsize % sizeof(int)) == 0);
+ num_fds = data.dsize / sizeof(int);
+
+ for (i=0; i<num_fds; i++) {
+ int fd;
+ int ret;
+ memcpy(&fd, data.dptr, sizeof(int));
+ ret = close(fd);
+ if (ret == -1) {
+ *saved_errno = errno;
+ }
+ data.dptr += sizeof(int);
+ }
+ dbwrap_record_delete(rec);
+}
+
+/****************************************************************************
+ Deal with pending closes needed by POSIX locking support.
+ Note that locking_close_file() is expected to have been called
+ to delete all locks on this fsp before this function is called.
+****************************************************************************/
+
+int fd_close_posix(const struct files_struct *fsp)
+{
+ int saved_errno = 0;
+ int ret;
+ NTSTATUS status;
+
+ if (!lp_locking(fsp->conn->params) ||
+ !lp_posix_locking(fsp->conn->params) ||
+ fsp->fsp_flags.use_ofd_locks)
+ {
+ /*
+ * No locking or POSIX to worry about or we are using POSIX
+ * open file description lock semantics which only removes
+ * locks on the file descriptor we're closing. Just close.
+ */
+ return close(fsp_get_pathref_fd(fsp));
+ }
+
+ if (get_lock_ref_count(fsp)) {
+
+ /*
+ * There are outstanding locks on this dev/inode pair on
+ * other fds. Add our fd to the pending close db. We also
+ * set fsp_get_io_fd(fsp) to -1 inside fd_close() after returning
+ * from VFS layer.
+ */
+
+ add_fd_to_close_entry(fsp);
+ return 0;
+ }
+
+ status = dbwrap_do_locked(
+ posix_pending_close_db,
+ fd_array_key_fsp(fsp),
+ fd_close_posix_fn,
+ &saved_errno);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ }
+
+ /* Don't need a lock ref count on this dev/ino anymore. */
+ delete_lock_ref_count(fsp);
+
+ /*
+ * Finally close the fd associated with this fsp.
+ */
+
+ ret = close(fsp_get_pathref_fd(fsp));
+
+ if (ret == 0 && saved_errno != 0) {
+ errno = saved_errno;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Next - the functions that deal with the mapping CIFS Windows locks onto
+ the underlying system POSIX locks.
+****************************************************************************/
+
+/*
+ * Structure used when splitting a lock range
+ * into a POSIX lock range. Doubly linked list.
+ */
+
+struct lock_list {
+ struct lock_list *next;
+ struct lock_list *prev;
+ off_t start;
+ off_t size;
+};
+
+/****************************************************************************
+ Create a list of lock ranges that don't overlap a given range. Used in calculating
+ POSIX locks and unlocks. This is a difficult function that requires ASCII art to
+ understand it :-).
+****************************************************************************/
+
+static struct lock_list *posix_lock_list(TALLOC_CTX *ctx,
+ struct lock_list *lhead,
+ const struct lock_context *lock_ctx, /* Lock context lhead belongs to. */
+ const struct lock_struct *plocks,
+ int num_locks)
+{
+ int i;
+
+ /*
+ * Check the current lock list on this dev/inode pair.
+ * Quit if the list is deleted.
+ */
+
+ DEBUG(10, ("posix_lock_list: curr: start=%ju,size=%ju\n",
+ (uintmax_t)lhead->start, (uintmax_t)lhead->size ));
+
+ for (i=0; i<num_locks && lhead; i++) {
+ const struct lock_struct *lock = &plocks[i];
+ struct lock_list *l_curr;
+
+ /* Ignore all but read/write locks. */
+ if (lock->lock_type != READ_LOCK && lock->lock_type != WRITE_LOCK) {
+ continue;
+ }
+
+ /* Ignore locks not owned by this process. */
+ if (!server_id_equal(&lock->context.pid, &lock_ctx->pid)) {
+ continue;
+ }
+
+ /*
+ * Walk the lock list, checking for overlaps. Note that
+ * the lock list can expand within this loop if the current
+ * range being examined needs to be split.
+ */
+
+ for (l_curr = lhead; l_curr;) {
+
+ DEBUG(10, ("posix_lock_list: lock: fnum=%ju: "
+ "start=%ju,size=%ju:type=%s",
+ (uintmax_t)lock->fnum,
+ (uintmax_t)lock->start,
+ (uintmax_t)lock->size,
+ posix_lock_type_name(lock->lock_type) ));
+
+ if ( (l_curr->start >= (lock->start + lock->size)) ||
+ (lock->start >= (l_curr->start + l_curr->size))) {
+
+ /* No overlap with existing lock - leave this range alone. */
+/*********************************************
+ +---------+
+ | l_curr |
+ +---------+
+ +-------+
+ | lock |
+ +-------+
+OR....
+ +---------+
+ | l_curr |
+ +---------+
+**********************************************/
+
+ DEBUG(10,(" no overlap case.\n" ));
+
+ l_curr = l_curr->next;
+
+ } else if ( (l_curr->start >= lock->start) &&
+ (l_curr->start + l_curr->size <= lock->start + lock->size) ) {
+
+ /*
+ * This range is completely overlapped by this existing lock range
+ * and thus should have no effect. Delete it from the list.
+ */
+/*********************************************
+ +---------+
+ | l_curr |
+ +---------+
+ +---------------------------+
+ | lock |
+ +---------------------------+
+**********************************************/
+ /* Save the next pointer */
+ struct lock_list *ul_next = l_curr->next;
+
+ DEBUG(10,(" delete case.\n" ));
+
+ DLIST_REMOVE(lhead, l_curr);
+ if(lhead == NULL) {
+ break; /* No more list... */
+ }
+
+ l_curr = ul_next;
+
+ } else if ( (l_curr->start >= lock->start) &&
+ (l_curr->start < lock->start + lock->size) &&
+ (l_curr->start + l_curr->size > lock->start + lock->size) ) {
+
+ /*
+ * This range overlaps the existing lock range at the high end.
+ * Truncate by moving start to existing range end and reducing size.
+ */
+/*********************************************
+ +---------------+
+ | l_curr |
+ +---------------+
+ +---------------+
+ | lock |
+ +---------------+
+BECOMES....
+ +-------+
+ | l_curr|
+ +-------+
+**********************************************/
+
+ l_curr->size = (l_curr->start + l_curr->size) - (lock->start + lock->size);
+ l_curr->start = lock->start + lock->size;
+
+ DEBUG(10, (" truncate high case: start=%ju,"
+ "size=%ju\n",
+ (uintmax_t)l_curr->start,
+ (uintmax_t)l_curr->size ));
+
+ l_curr = l_curr->next;
+
+ } else if ( (l_curr->start < lock->start) &&
+ (l_curr->start + l_curr->size > lock->start) &&
+ (l_curr->start + l_curr->size <= lock->start + lock->size) ) {
+
+ /*
+ * This range overlaps the existing lock range at the low end.
+ * Truncate by reducing size.
+ */
+/*********************************************
+ +---------------+
+ | l_curr |
+ +---------------+
+ +---------------+
+ | lock |
+ +---------------+
+BECOMES....
+ +-------+
+ | l_curr|
+ +-------+
+**********************************************/
+
+ l_curr->size = lock->start - l_curr->start;
+
+ DEBUG(10, (" truncate low case: start=%ju,"
+ "size=%ju\n",
+ (uintmax_t)l_curr->start,
+ (uintmax_t)l_curr->size ));
+
+ l_curr = l_curr->next;
+
+ } else if ( (l_curr->start < lock->start) &&
+ (l_curr->start + l_curr->size > lock->start + lock->size) ) {
+ /*
+ * Worst case scenario. Range completely overlaps an existing
+ * lock range. Split the request into two, push the new (upper) request
+ * into the dlink list, and continue with the entry after l_new (as we
+ * know that l_new will not overlap with this lock).
+ */
+/*********************************************
+ +---------------------------+
+ | l_curr |
+ +---------------------------+
+ +---------+
+ | lock |
+ +---------+
+BECOMES.....
+ +-------+ +---------+
+ | l_curr| | l_new |
+ +-------+ +---------+
+**********************************************/
+ struct lock_list *l_new = talloc(ctx, struct lock_list);
+
+ if(l_new == NULL) {
+ DEBUG(0,("posix_lock_list: talloc fail.\n"));
+ return NULL; /* The talloc_destroy takes care of cleanup. */
+ }
+
+ ZERO_STRUCTP(l_new);
+ l_new->start = lock->start + lock->size;
+ l_new->size = l_curr->start + l_curr->size - l_new->start;
+
+ /* Truncate the l_curr. */
+ l_curr->size = lock->start - l_curr->start;
+
+ DEBUG(10, (" split case: curr: start=%ju,"
+ "size=%ju new: start=%ju,"
+ "size=%ju\n",
+ (uintmax_t)l_curr->start,
+ (uintmax_t)l_curr->size,
+ (uintmax_t)l_new->start,
+ (uintmax_t)l_new->size ));
+
+ /*
+ * Add into the dlink list after the l_curr point - NOT at lhead.
+ */
+ DLIST_ADD_AFTER(lhead, l_new, l_curr);
+
+ /* And move after the link we added. */
+ l_curr = l_new->next;
+
+ } else {
+
+ /*
+ * This logic case should never happen. Ensure this is the
+ * case by forcing an abort.... Remove in production.
+ */
+ char *msg = NULL;
+
+ if (asprintf(&msg, "logic flaw in cases: "
+ "l_curr: start = %ju, "
+ "size = %ju : lock: "
+ "start = %ju, size = %ju",
+ (uintmax_t)l_curr->start,
+ (uintmax_t)l_curr->size,
+ (uintmax_t)lock->start,
+ (uintmax_t)lock->size ) != -1) {
+ smb_panic(msg);
+ } else {
+ smb_panic("posix_lock_list");
+ }
+ }
+ } /* end for ( l_curr = lhead; l_curr;) */
+ } /* end for (i=0; i<num_locks && ul_head; i++) */
+
+ return lhead;
+}
+
+/****************************************************************************
+ POSIX function to acquire a lock. Returns True if the
+ lock could be granted, False if not.
+****************************************************************************/
+
+bool set_posix_lock_windows_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ enum brl_type lock_type,
+ const struct lock_context *lock_ctx,
+ const struct lock_struct *plocks,
+ int num_locks,
+ int *errno_ret)
+{
+ off_t offset;
+ off_t count;
+ int posix_lock_type = map_posix_lock_type(fsp,lock_type);
+ bool ret = True;
+ size_t lock_count;
+ TALLOC_CTX *l_ctx = NULL;
+ struct lock_list *llist = NULL;
+ struct lock_list *ll = NULL;
+
+ DEBUG(5, ("set_posix_lock_windows_flavour: File %s, offset = %ju, "
+ "count = %ju, type = %s\n", fsp_str_dbg(fsp),
+ (uintmax_t)u_offset, (uintmax_t)u_count,
+ posix_lock_type_name(lock_type)));
+
+ /*
+ * If the requested lock won't fit in the POSIX range, we will
+ * pretend it was successful.
+ */
+
+ if(!posix_lock_in_range(&offset, &count, u_offset, u_count)) {
+ increment_lock_ref_count(fsp);
+ return True;
+ }
+
+ /*
+ * Windows is very strange. It allows read locks to be overlaid
+ * (even over a write lock), but leaves the write lock in force until the first
+ * unlock. It also reference counts the locks. This means the following sequence :
+ *
+ * process1 process2
+ * ------------------------------------------------------------------------
+ * WRITE LOCK : start = 2, len = 10
+ * READ LOCK: start =0, len = 10 - FAIL
+ * READ LOCK : start = 0, len = 14
+ * READ LOCK: start =0, len = 10 - FAIL
+ * UNLOCK : start = 2, len = 10
+ * READ LOCK: start =0, len = 10 - OK
+ *
+ * Under POSIX, the same sequence in steps 1 and 2 would not be reference counted, but
+ * would leave a single read lock over the 0-14 region.
+ */
+
+ if ((l_ctx = talloc_init("set_posix_lock")) == NULL) {
+ DEBUG(0,("set_posix_lock_windows_flavour: unable to init talloc context.\n"));
+ return False;
+ }
+
+ if ((ll = talloc(l_ctx, struct lock_list)) == NULL) {
+ DEBUG(0,("set_posix_lock_windows_flavour: unable to talloc unlock list.\n"));
+ talloc_destroy(l_ctx);
+ return False;
+ }
+
+ /*
+ * Create the initial list entry containing the
+ * lock we want to add.
+ */
+
+ ZERO_STRUCTP(ll);
+ ll->start = offset;
+ ll->size = count;
+
+ DLIST_ADD(llist, ll);
+
+ /*
+ * The following call calculates if there are any
+ * overlapping locks held by this process on
+ * fd's open on the same file and splits this list
+ * into a list of lock ranges that do not overlap with existing
+ * POSIX locks.
+ */
+
+ llist = posix_lock_list(l_ctx,
+ llist,
+ lock_ctx, /* Lock context llist belongs to. */
+ plocks,
+ num_locks);
+
+ /*
+ * Add the POSIX locks on the list of ranges returned.
+ * As the lock is supposed to be added atomically, we need to
+ * back out all the locks if any one of these calls fail.
+ */
+
+ for (lock_count = 0, ll = llist; ll; ll = ll->next, lock_count++) {
+ offset = ll->start;
+ count = ll->size;
+
+ DEBUG(5, ("set_posix_lock_windows_flavour: Real lock: "
+ "Type = %s: offset = %ju, count = %ju\n",
+ posix_lock_type_name(posix_lock_type),
+ (uintmax_t)offset, (uintmax_t)count ));
+
+ if (!posix_fcntl_lock(fsp,F_SETLK,offset,count,posix_lock_type)) {
+ *errno_ret = errno;
+ DEBUG(5, ("set_posix_lock_windows_flavour: Lock "
+ "fail !: Type = %s: offset = %ju, "
+ "count = %ju. Errno = %s\n",
+ posix_lock_type_name(posix_lock_type),
+ (uintmax_t)offset, (uintmax_t)count,
+ strerror(errno) ));
+ ret = False;
+ break;
+ }
+ }
+
+ if (!ret) {
+
+ /*
+ * Back out all the POSIX locks we have on fail.
+ */
+
+ for (ll = llist; lock_count; ll = ll->next, lock_count--) {
+ offset = ll->start;
+ count = ll->size;
+
+ DEBUG(5, ("set_posix_lock_windows_flavour: Backing "
+ "out locks: Type = %s: offset = %ju, "
+ "count = %ju\n",
+ posix_lock_type_name(posix_lock_type),
+ (uintmax_t)offset, (uintmax_t)count ));
+
+ posix_fcntl_lock(fsp,F_SETLK,offset,count,F_UNLCK);
+ }
+ } else {
+ /* Remember the number of locks we have on this dev/ino pair. */
+ increment_lock_ref_count(fsp);
+ }
+
+ talloc_destroy(l_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ POSIX function to release a lock. Returns True if the
+ lock could be released, False if not.
+****************************************************************************/
+
+bool release_posix_lock_windows_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ enum brl_type deleted_lock_type,
+ const struct lock_context *lock_ctx,
+ const struct lock_struct *plocks,
+ int num_locks)
+{
+ off_t offset;
+ off_t count;
+ bool ret = True;
+ TALLOC_CTX *ul_ctx = NULL;
+ struct lock_list *ulist = NULL;
+ struct lock_list *ul = NULL;
+
+ DEBUG(5, ("release_posix_lock_windows_flavour: File %s, offset = %ju, "
+ "count = %ju\n", fsp_str_dbg(fsp),
+ (uintmax_t)u_offset, (uintmax_t)u_count));
+
+ /* Remember the number of locks we have on this dev/ino pair. */
+ decrement_lock_ref_count(fsp);
+
+ /*
+ * If the requested lock won't fit in the POSIX range, we will
+ * pretend it was successful.
+ */
+
+ if(!posix_lock_in_range(&offset, &count, u_offset, u_count)) {
+ return True;
+ }
+
+ if ((ul_ctx = talloc_init("release_posix_lock")) == NULL) {
+ DEBUG(0,("release_posix_lock_windows_flavour: unable to init talloc context.\n"));
+ return False;
+ }
+
+ if ((ul = talloc(ul_ctx, struct lock_list)) == NULL) {
+ DEBUG(0,("release_posix_lock_windows_flavour: unable to talloc unlock list.\n"));
+ talloc_destroy(ul_ctx);
+ return False;
+ }
+
+ /*
+ * Create the initial list entry containing the
+ * lock we want to remove.
+ */
+
+ ZERO_STRUCTP(ul);
+ ul->start = offset;
+ ul->size = count;
+
+ DLIST_ADD(ulist, ul);
+
+ /*
+ * The following call calculates if there are any
+ * overlapping locks held by this process on
+ * fd's open on the same file and creates a
+ * list of unlock ranges that will allow
+ * POSIX lock ranges to remain on the file whilst the
+ * unlocks are performed.
+ */
+
+ ulist = posix_lock_list(ul_ctx,
+ ulist,
+ lock_ctx, /* Lock context ulist belongs to. */
+ plocks,
+ num_locks);
+
+ /*
+ * If there were any overlapped entries (list is > 1 or size or start have changed),
+ * and the lock_type we just deleted from
+ * the upper layer tdb was a write lock, then before doing the unlock we need to downgrade
+ * the POSIX lock to a read lock. This allows any overlapping read locks
+ * to be atomically maintained.
+ */
+
+ if (deleted_lock_type == WRITE_LOCK &&
+ (!ulist || ulist->next != NULL || ulist->start != offset || ulist->size != count)) {
+
+ DEBUG(5, ("release_posix_lock_windows_flavour: downgrading "
+ "lock to READ: offset = %ju, count = %ju\n",
+ (uintmax_t)offset, (uintmax_t)count ));
+
+ if (!posix_fcntl_lock(fsp,F_SETLK,offset,count,F_RDLCK)) {
+ DEBUG(0,("release_posix_lock_windows_flavour: downgrade of lock failed with error %s !\n", strerror(errno) ));
+ talloc_destroy(ul_ctx);
+ return False;
+ }
+ }
+
+ /*
+ * Release the POSIX locks on the list of ranges returned.
+ */
+
+ for(; ulist; ulist = ulist->next) {
+ offset = ulist->start;
+ count = ulist->size;
+
+ DEBUG(5, ("release_posix_lock_windows_flavour: Real unlock: "
+ "offset = %ju, count = %ju\n",
+ (uintmax_t)offset, (uintmax_t)count ));
+
+ if (!posix_fcntl_lock(fsp,F_SETLK,offset,count,F_UNLCK)) {
+ ret = False;
+ }
+ }
+
+ talloc_destroy(ul_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Next - the functions that deal with mapping CIFS POSIX locks onto
+ the underlying system POSIX locks.
+****************************************************************************/
+
+/****************************************************************************
+ We only increment the lock ref count when we see a POSIX lock on a context
+ that doesn't already have them.
+****************************************************************************/
+
+static void increment_posix_lock_count(const files_struct *fsp,
+ uint64_t smblctx)
+{
+ NTSTATUS status;
+ TDB_DATA ctx_key;
+ TDB_DATA val = { 0 };
+
+ ctx_key.dptr = (uint8_t *)&smblctx;
+ ctx_key.dsize = sizeof(smblctx);
+
+ /*
+ * Don't increment if we already have any POSIX flavor
+ * locks on this context.
+ */
+ if (dbwrap_exists(posix_pending_close_db, ctx_key)) {
+ return;
+ }
+
+ /* Remember that we have POSIX flavor locks on this context. */
+ status = dbwrap_store(posix_pending_close_db, ctx_key, val, 0);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+ increment_lock_ref_count(fsp);
+
+ DEBUG(10,("posix_locks set for file %s\n",
+ fsp_str_dbg(fsp)));
+}
+
+static void decrement_posix_lock_count(const files_struct *fsp, uint64_t smblctx)
+{
+ NTSTATUS status;
+ TDB_DATA ctx_key;
+
+ ctx_key.dptr = (uint8_t *)&smblctx;
+ ctx_key.dsize = sizeof(smblctx);
+
+ status = dbwrap_delete(posix_pending_close_db, ctx_key);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+ decrement_lock_ref_count(fsp);
+
+ DEBUG(10,("posix_locks deleted for file %s\n",
+ fsp_str_dbg(fsp)));
+}
+
+/****************************************************************************
+ Return true if any locks exist on the given lock context.
+****************************************************************************/
+
+static bool locks_exist_on_context(const struct lock_struct *plocks,
+ int num_locks,
+ const struct lock_context *lock_ctx)
+{
+ int i;
+
+ for (i=0; i < num_locks; i++) {
+ const struct lock_struct *lock = &plocks[i];
+
+ /* Ignore all but read/write locks. */
+ if (lock->lock_type != READ_LOCK && lock->lock_type != WRITE_LOCK) {
+ continue;
+ }
+
+ /* Ignore locks not owned by this process. */
+ if (!server_id_equal(&lock->context.pid, &lock_ctx->pid)) {
+ continue;
+ }
+
+ if (lock_ctx->smblctx == lock->context.smblctx) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/****************************************************************************
+ POSIX function to acquire a lock. Returns True if the
+ lock could be granted, False if not.
+ As POSIX locks don't stack or conflict (they just overwrite)
+ we can map the requested lock directly onto a system one. We
+ know it doesn't conflict with locks on other contexts as the
+ upper layer would have refused it.
+****************************************************************************/
+
+bool set_posix_lock_posix_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ enum brl_type lock_type,
+ const struct lock_context *lock_ctx,
+ int *errno_ret)
+{
+ off_t offset;
+ off_t count;
+ int posix_lock_type = map_posix_lock_type(fsp,lock_type);
+
+ DEBUG(5,("set_posix_lock_posix_flavour: File %s, offset = %ju, count "
+ "= %ju, type = %s\n", fsp_str_dbg(fsp),
+ (uintmax_t)u_offset, (uintmax_t)u_count,
+ posix_lock_type_name(lock_type)));
+
+ /*
+ * If the requested lock won't fit in the POSIX range, we will
+ * pretend it was successful.
+ */
+
+ if(!posix_lock_in_range(&offset, &count, u_offset, u_count)) {
+ increment_posix_lock_count(fsp, lock_ctx->smblctx);
+ return True;
+ }
+
+ if (!posix_fcntl_lock(fsp,F_SETLK,offset,count,posix_lock_type)) {
+ *errno_ret = errno;
+ DEBUG(5,("set_posix_lock_posix_flavour: Lock fail !: Type = %s: offset = %ju, count = %ju. Errno = %s\n",
+ posix_lock_type_name(posix_lock_type), (intmax_t)offset, (intmax_t)count, strerror(errno) ));
+ return False;
+ }
+ increment_posix_lock_count(fsp, lock_ctx->smblctx);
+ return True;
+}
+
+/****************************************************************************
+ POSIX function to release a lock. Returns True if the
+ lock could be released, False if not.
+ We are given a complete lock state from the upper layer which is what the lock
+ state should be after the unlock has already been done, so what
+ we do is punch out holes in the unlock range where locks owned by this process
+ have a different lock context.
+****************************************************************************/
+
+bool release_posix_lock_posix_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ const struct lock_context *lock_ctx,
+ const struct lock_struct *plocks,
+ int num_locks)
+{
+ bool ret = True;
+ off_t offset;
+ off_t count;
+ TALLOC_CTX *ul_ctx = NULL;
+ struct lock_list *ulist = NULL;
+ struct lock_list *ul = NULL;
+
+ DEBUG(5, ("release_posix_lock_posix_flavour: File %s, offset = %ju, "
+ "count = %ju\n", fsp_str_dbg(fsp),
+ (uintmax_t)u_offset, (uintmax_t)u_count));
+
+ /*
+ * If the requested lock won't fit in the POSIX range, we will
+ * pretend it was successful.
+ */
+
+ if(!posix_lock_in_range(&offset, &count, u_offset, u_count)) {
+ if (!locks_exist_on_context(plocks, num_locks, lock_ctx)) {
+ decrement_posix_lock_count(fsp, lock_ctx->smblctx);
+ }
+ return True;
+ }
+
+ if ((ul_ctx = talloc_init("release_posix_lock")) == NULL) {
+ DEBUG(0,("release_posix_lock_windows_flavour: unable to init talloc context.\n"));
+ return False;
+ }
+
+ if ((ul = talloc(ul_ctx, struct lock_list)) == NULL) {
+ DEBUG(0,("release_posix_lock_windows_flavour: unable to talloc unlock list.\n"));
+ talloc_destroy(ul_ctx);
+ return False;
+ }
+
+ /*
+ * Create the initial list entry containing the
+ * lock we want to remove.
+ */
+
+ ZERO_STRUCTP(ul);
+ ul->start = offset;
+ ul->size = count;
+
+ DLIST_ADD(ulist, ul);
+
+ /*
+ * Walk the given array creating a linked list
+ * of unlock requests.
+ */
+
+ ulist = posix_lock_list(ul_ctx,
+ ulist,
+ lock_ctx, /* Lock context ulist belongs to. */
+ plocks,
+ num_locks);
+
+ /*
+ * Release the POSIX locks on the list of ranges returned.
+ */
+
+ for(; ulist; ulist = ulist->next) {
+ offset = ulist->start;
+ count = ulist->size;
+
+ DEBUG(5, ("release_posix_lock_posix_flavour: Real unlock: "
+ "offset = %ju, count = %ju\n",
+ (uintmax_t)offset, (uintmax_t)count ));
+
+ if (!posix_fcntl_lock(fsp,F_SETLK,offset,count,F_UNLCK)) {
+ ret = False;
+ }
+ }
+
+ if (!locks_exist_on_context(plocks, num_locks, lock_ctx)) {
+ decrement_posix_lock_count(fsp, lock_ctx->smblctx);
+ }
+ talloc_destroy(ul_ctx);
+ return ret;
+}
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
new file mode 100644
index 0000000..7fc177d
--- /dev/null
+++ b/source3/locking/proto.h
@@ -0,0 +1,215 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Locking functions
+ *
+ * Copyright (C) Andrew Tridgell 1992-2000
+ * Copyright (C) Jeremy Allison 1992-2006
+ * Copyright (C) Volker Lendecke 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LOCKING_PROTO_H_
+#define _LOCKING_PROTO_H_
+
+#include <tdb.h>
+
+/* The following definitions come from locking/brlock.c */
+
+void brl_init(bool read_only);
+void brl_shutdown(void);
+
+unsigned int brl_num_locks(const struct byte_range_lock *brl);
+struct files_struct *brl_fsp(struct byte_range_lock *brl);
+TALLOC_CTX *brl_req_mem_ctx(const struct byte_range_lock *brl);
+const struct GUID *brl_req_guid(const struct byte_range_lock *brl);
+
+bool byte_range_valid(uint64_t ofs, uint64_t len);
+bool byte_range_overlap(uint64_t ofs1,
+ uint64_t len1,
+ uint64_t ofs2,
+ uint64_t len2);
+
+NTSTATUS brl_lock_windows_default(struct byte_range_lock *br_lck,
+ struct lock_struct *plock);
+
+NTSTATUS brl_lock(
+ struct byte_range_lock *br_lck,
+ uint64_t smblctx,
+ struct server_id pid,
+ br_off start,
+ br_off size,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ struct server_id *blocker_pid,
+ uint64_t *psmblctx);
+bool brl_unlock(struct byte_range_lock *br_lck,
+ uint64_t smblctx,
+ struct server_id pid,
+ br_off start,
+ br_off size,
+ enum brl_flavour lock_flav);
+bool brl_unlock_windows_default(struct byte_range_lock *br_lck,
+ const struct lock_struct *plock);
+bool brl_locktest(struct byte_range_lock *br_lck,
+ const struct lock_struct *rw_probe);
+NTSTATUS brl_lockquery(struct byte_range_lock *br_lck,
+ uint64_t *psmblctx,
+ struct server_id pid,
+ br_off *pstart,
+ br_off *psize,
+ enum brl_type *plock_type,
+ enum brl_flavour lock_flav);
+bool brl_mark_disconnected(struct files_struct *fsp);
+bool brl_reconnect_disconnected(struct files_struct *fsp);
+void brl_close_fnum(struct byte_range_lock *br_lck);
+int brl_forall(void (*fn)(struct file_id id, struct server_id pid,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ br_off start, br_off size,
+ void *private_data),
+ void *private_data);
+struct byte_range_lock *brl_get_locks_for_locking(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ TALLOC_CTX *req_mem_ctx,
+ const struct GUID *req_guid);
+struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx,
+ files_struct *fsp);
+struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp);
+bool brl_cleanup_disconnected(struct file_id fid, uint64_t open_persistent_id);
+
+/* The following definitions come from locking/locking.c */
+
+const char *lock_type_name(enum brl_type lock_type);
+const char *lock_flav_name(enum brl_flavour lock_flav);
+void init_strict_lock_struct(files_struct *fsp,
+ uint64_t smblctx,
+ br_off start,
+ br_off size,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ struct lock_struct *plock);
+bool strict_lock_check_default(files_struct *fsp,
+ struct lock_struct *plock);
+NTSTATUS query_lock(files_struct *fsp,
+ uint64_t *psmblctx,
+ uint64_t *pcount,
+ uint64_t *poffset,
+ enum brl_type *plock_type,
+ enum brl_flavour lock_flav);
+NTSTATUS do_lock(files_struct *fsp,
+ TALLOC_CTX *req_mem_ctx,
+ const struct GUID *req_guid,
+ uint64_t smblctx,
+ uint64_t count,
+ uint64_t offset,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ struct server_id *pblocker_pid,
+ uint64_t *psmblctx);
+NTSTATUS do_unlock(files_struct *fsp,
+ uint64_t smblctx,
+ uint64_t count,
+ uint64_t offset,
+ enum brl_flavour lock_flav);
+void locking_close_file(files_struct *fsp,
+ enum file_close_type close_type);
+char *share_mode_str(TALLOC_CTX *ctx, int num,
+ const struct file_id *id,
+ const struct share_mode_entry *e);
+
+bool rename_share_filename(struct messaging_context *msg_ctx,
+ struct share_mode_lock *lck,
+ struct file_id id,
+ const char *servicepath,
+ uint32_t orig_name_hash,
+ uint32_t new_name_hash,
+ const struct smb_filename *smb_fname);
+void get_file_infos(struct file_id id,
+ uint32_t name_hash,
+ bool *delete_on_close,
+ struct timespec *write_time);
+bool is_valid_share_mode_entry(const struct share_mode_entry *e);
+bool share_entry_stale_pid(struct share_mode_entry *e);
+NTSTATUS remove_lease_if_stale(struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease_key *lease_key);
+bool get_delete_on_close_token(struct share_mode_lock *lck,
+ uint32_t name_hash,
+ const struct security_token **pp_nt_tok,
+ const struct security_unix_token **pp_tok);
+void reset_delete_on_close_lck(files_struct *fsp,
+ struct share_mode_lock *lck);
+void set_delete_on_close_lck(files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct security_token *nt_tok,
+ const struct security_unix_token *tok);
+bool set_delete_on_close(files_struct *fsp, bool delete_on_close,
+ const struct security_token *nt_tok,
+ const struct security_unix_token *tok);
+bool is_delete_on_close_set(struct share_mode_lock *lck, uint32_t name_hash);
+bool set_sticky_write_time(struct file_id fileid, struct timespec write_time);
+bool set_write_time(struct file_id fileid, struct timespec write_time);
+struct timespec get_share_mode_write_time(struct share_mode_lock *lck);
+bool file_has_open_streams(files_struct *fsp);
+bool share_mode_forall_leases(
+ struct share_mode_lock *lck,
+ bool (*fn)(struct share_mode_entry *e,
+ void *private_data),
+ void *private_data);
+
+/* The following definitions come from locking/posix.c */
+
+bool is_posix_locked(files_struct *fsp,
+ uint64_t *pu_offset,
+ uint64_t *pu_count,
+ enum brl_type *plock_type,
+ enum brl_flavour lock_flav);
+bool posix_locking_init(bool read_only);
+bool posix_locking_end(void);
+int fd_close_posix(const struct files_struct *fsp);
+bool set_posix_lock_windows_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ enum brl_type lock_type,
+ const struct lock_context *lock_ctx,
+ const struct lock_struct *plocks,
+ int num_locks,
+ int *errno_ret);
+bool release_posix_lock_windows_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ enum brl_type deleted_lock_type,
+ const struct lock_context *lock_ctx,
+ const struct lock_struct *plocks,
+ int num_locks);
+bool set_posix_lock_posix_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ enum brl_type lock_type,
+ const struct lock_context *lock_ctx,
+ int *errno_ret);
+bool release_posix_lock_posix_flavour(files_struct *fsp,
+ uint64_t u_offset,
+ uint64_t u_count,
+ const struct lock_context *lock_ctx,
+ const struct lock_struct *plocks,
+ int num_locks);
+
+/* The following definitions come from locking/leases_util.c */
+uint32_t map_oplock_to_lease_type(uint16_t op_type);
+uint32_t fsp_lease_type(struct files_struct *fsp);
+const struct GUID *fsp_client_guid(const files_struct *fsp);
+
+#endif /* _LOCKING_PROTO_H_ */
diff --git a/source3/locking/share_mode_lock.c b/source3/locking/share_mode_lock.c
new file mode 100644
index 0000000..3fc7d56
--- /dev/null
+++ b/source3/locking/share_mode_lock.c
@@ -0,0 +1,3193 @@
+/*
+ Unix SMB/CIFS implementation.
+ Locking functions
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 1992-2006
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Revision History:
+
+ 12 aug 96: Erik.Devriendt@te6.siemens.be
+ added support for shared memory implementation of share mode locking
+
+ May 1997. Jeremy Allison (jallison@whistle.com). Modified share mode
+ locking to deal with multiple share modes per open file.
+
+ September 1997. Jeremy Allison (jallison@whistle.com). Added oplock
+ support.
+
+ rewritten completely to use new tdb code. Tridge, Dec '99
+
+ Added POSIX locking support. Jeremy Allison (jeremy@valinux.com), Apr. 2000.
+ Added Unix Extensions POSIX locking support. Jeremy Allison Mar 2006.
+*/
+
+#include "includes.h"
+#include "lib/util/time_basic.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "share_mode_lock_private.h"
+struct share_mode_lock {
+ struct file_id id;
+ struct share_mode_data *cached_data;
+};
+#define SHARE_MODE_ENTRY_PREPARE_STATE_LCK_SPACE 1
+#include "share_mode_lock.h"
+#include "locking/proto.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_private.h"
+#include "../libcli/security/security.h"
+#include "serverid.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+#include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "locking/leases_db.h"
+#include "../lib/util/memcache.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "g_lock.h"
+#include "smbd/fd_handle.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+#define DBG_GET_SHARE_MODE_LOCK(__status, ...) \
+ DBG_PREFIX( \
+ NT_STATUS_EQUAL(__status, NT_STATUS_NOT_FOUND) ? \
+ DBGLVL_DEBUG : DBGLVL_ERR, \
+ (__VA_ARGS__))
+
+/* the locking database handle */
+static struct g_lock_ctx *lock_ctx;
+static struct g_lock_lock_cb_state *current_share_mode_glck = NULL;
+
+static bool share_mode_g_lock_within_cb(TDB_DATA key);
+
+static NTSTATUS share_mode_g_lock_dump(TDB_DATA key,
+ void (*fn)(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data),
+ void *private_data)
+{
+ if (share_mode_g_lock_within_cb(key)) {
+ return g_lock_lock_cb_dump(current_share_mode_glck,
+ fn, private_data);
+ }
+
+ return g_lock_dump(lock_ctx, key, fn, private_data);
+}
+
+static NTSTATUS share_mode_g_lock_writev(TDB_DATA key,
+ const TDB_DATA *dbufs,
+ size_t num_dbufs)
+{
+ if (share_mode_g_lock_within_cb(key)) {
+ return g_lock_lock_cb_writev(current_share_mode_glck,
+ dbufs, num_dbufs);
+ }
+
+ return g_lock_writev_data(lock_ctx, key, dbufs, num_dbufs);
+}
+
+static bool locking_init_internal(bool read_only)
+{
+ struct db_context *backend;
+ char *db_path;
+
+ brl_init(read_only);
+
+ if (lock_ctx != NULL) {
+ return True;
+ }
+
+ db_path = lock_path(talloc_tos(), "locking.tdb");
+ if (db_path == NULL) {
+ return false;
+ }
+
+ backend = db_open(NULL, db_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS |
+ TDB_SEQNUM,
+ read_only?O_RDONLY:O_RDWR|O_CREAT, 0644,
+ DBWRAP_LOCK_ORDER_NONE,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(db_path);
+ if (!backend) {
+ DEBUG(0,("ERROR: Failed to initialise locking database\n"));
+ return False;
+ }
+
+ lock_ctx = g_lock_ctx_init_backend(
+ NULL, global_messaging_context(), &backend);
+ if (lock_ctx == NULL) {
+ TALLOC_FREE(backend);
+ return false;
+ }
+ g_lock_set_lock_order(lock_ctx, DBWRAP_LOCK_ORDER_1);
+
+ if (!posix_locking_init(read_only)) {
+ TALLOC_FREE(lock_ctx);
+ return False;
+ }
+
+ return True;
+}
+
+bool locking_init(void)
+{
+ return locking_init_internal(false);
+}
+
+bool locking_init_readonly(void)
+{
+ return locking_init_internal(true);
+}
+
+/*******************************************************************
+ Deinitialize the share_mode management.
+******************************************************************/
+
+bool locking_end(void)
+{
+ brl_shutdown();
+ TALLOC_FREE(lock_ctx);
+ return true;
+}
+
+/*******************************************************************
+ Form a static locking key for a dev/inode pair.
+******************************************************************/
+
+static TDB_DATA locking_key(const struct file_id *id)
+{
+ return make_tdb_data((const uint8_t *)id, sizeof(*id));
+}
+
+/*******************************************************************
+ Share mode cache utility functions that store/delete/retrieve
+ entries from memcache.
+
+ For now share the statcache (global cache) memory space. If
+ a lock record gets orphaned (which shouldn't happen as we're
+ using the same locking_key data as lookup) it will eventually
+ fall out of the cache via the normal LRU trim mechanism. If
+ necessary we can always make this a separate (smaller) cache.
+******************************************************************/
+
+static DATA_BLOB memcache_key(const struct file_id *id)
+{
+ return data_blob_const((const void *)id, sizeof(*id));
+}
+
+static void share_mode_memcache_store(struct share_mode_data *d)
+{
+ const DATA_BLOB key = memcache_key(&d->id);
+ struct file_id_buf idbuf;
+
+ DBG_DEBUG("stored entry for file %s epoch %"PRIx64" key %s\n",
+ d->base_name,
+ d->unique_content_epoch,
+ file_id_str_buf(d->id, &idbuf));
+
+ /* Ensure everything stored in the cache is pristine. */
+ SMB_ASSERT(!d->modified);
+ SMB_ASSERT(!d->not_stored);
+
+ /*
+ * Ensure the memory going into the cache
+ * doesn't have a destructor so it can be
+ * cleanly evicted by the memcache LRU
+ * mechanism.
+ */
+ talloc_set_destructor(d, NULL);
+
+ /* Cache will own d after this call. */
+ memcache_add_talloc(NULL,
+ SHARE_MODE_LOCK_CACHE,
+ key,
+ &d);
+}
+
+/*
+ * NB. We use ndr_pull_hyper on a stack-created
+ * struct ndr_pull with no talloc allowed, as we
+ * need this to be really fast as an ndr-peek into
+ * the first 10 bytes of the blob.
+ */
+
+static enum ndr_err_code get_share_mode_blob_header(
+ const uint8_t *buf, size_t buflen, uint64_t *pepoch, uint16_t *pflags)
+{
+ struct ndr_pull ndr = {
+ .data = discard_const_p(uint8_t, buf),
+ .data_size = buflen,
+ };
+ NDR_CHECK(ndr_pull_hyper(&ndr, NDR_SCALARS, pepoch));
+ NDR_CHECK(ndr_pull_uint16(&ndr, NDR_SCALARS, pflags));
+ return NDR_ERR_SUCCESS;
+}
+
+static int share_mode_data_nofree_destructor(struct share_mode_data *d)
+{
+ return -1;
+}
+
+static struct share_mode_data *share_mode_memcache_fetch(
+ TALLOC_CTX *mem_ctx,
+ struct file_id id,
+ const uint8_t *buf,
+ size_t buflen)
+{
+ const DATA_BLOB key = memcache_key(&id);
+ enum ndr_err_code ndr_err;
+ struct share_mode_data *d;
+ uint64_t unique_content_epoch;
+ uint16_t flags;
+ void *ptr;
+ struct file_id_buf idbuf;
+
+ ptr = memcache_lookup_talloc(NULL,
+ SHARE_MODE_LOCK_CACHE,
+ key);
+ if (ptr == NULL) {
+ DBG_DEBUG("failed to find entry for key %s\n",
+ file_id_str_buf(id, &idbuf));
+ return NULL;
+ }
+ /* sequence number key is at start of blob. */
+ ndr_err = get_share_mode_blob_header(
+ buf, buflen, &unique_content_epoch, &flags);
+ if (ndr_err != NDR_ERR_SUCCESS) {
+ /* Bad blob. Remove entry. */
+ DBG_DEBUG("bad blob %u key %s\n",
+ (unsigned int)ndr_err,
+ file_id_str_buf(id, &idbuf));
+ memcache_delete(NULL,
+ SHARE_MODE_LOCK_CACHE,
+ key);
+ return NULL;
+ }
+
+ d = (struct share_mode_data *)ptr;
+ if (d->unique_content_epoch != unique_content_epoch) {
+ DBG_DEBUG("epoch changed (cached %"PRIx64") (new %"PRIx64") "
+ "for key %s\n",
+ d->unique_content_epoch,
+ unique_content_epoch,
+ file_id_str_buf(id, &idbuf));
+ /* Cache out of date. Remove entry. */
+ memcache_delete(NULL,
+ SHARE_MODE_LOCK_CACHE,
+ key);
+ return NULL;
+ }
+
+ /* Move onto mem_ctx. */
+ d = talloc_move(mem_ctx, &ptr);
+
+ /*
+ * Now we own d, prevent the cache from freeing it
+ * when we delete the entry.
+ */
+ talloc_set_destructor(d, share_mode_data_nofree_destructor);
+
+ /* Remove from the cache. We own it now. */
+ memcache_delete(NULL,
+ SHARE_MODE_LOCK_CACHE,
+ key);
+
+ /* And reset the destructor to none. */
+ talloc_set_destructor(d, NULL);
+
+ DBG_DEBUG("fetched entry for file %s epoch %"PRIx64" key %s\n",
+ d->base_name,
+ d->unique_content_epoch,
+ file_id_str_buf(id, &idbuf));
+
+ return d;
+}
+
+/*
+ * 132 is the sizeof an ndr-encoded struct share_mode_entry_buf.
+ * Reading/writing entries will immediately error out if this
+ * size differs (push/pull is done without allocs).
+ */
+
+struct share_mode_entry_buf {
+ uint8_t buf[132];
+};
+#define SHARE_MODE_ENTRY_SIZE (sizeof(struct share_mode_entry_buf))
+
+static bool share_mode_entry_put(
+ const struct share_mode_entry *e,
+ struct share_mode_entry_buf *dst)
+{
+ DATA_BLOB blob = { .data = dst->buf, .length = sizeof(dst->buf) };
+ enum ndr_err_code ndr_err;
+
+ if (DEBUGLEVEL>=10) {
+ DBG_DEBUG("share_mode_entry:\n");
+ NDR_PRINT_DEBUG(share_mode_entry, discard_const_p(void, e));
+ }
+
+ ndr_err = ndr_push_struct_into_fixed_blob(
+ &blob,
+ e,
+ (ndr_push_flags_fn_t)ndr_push_share_mode_entry);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_share_mode_entry failed: %s\n",
+ ndr_errstr(ndr_err));
+ return false;
+ }
+
+ return true;
+}
+
+static bool share_mode_entry_get(
+ const uint8_t ptr[SHARE_MODE_ENTRY_SIZE], struct share_mode_entry *e)
+{
+ enum ndr_err_code ndr_err = NDR_ERR_SUCCESS;
+ DATA_BLOB blob = {
+ .data = discard_const_p(uint8_t, ptr),
+ .length = SHARE_MODE_ENTRY_SIZE,
+ };
+
+ ndr_err = ndr_pull_struct_blob_all_noalloc(
+ &blob, e, (ndr_pull_flags_fn_t)ndr_pull_share_mode_entry);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_pull_share_mode_entry failed\n");
+ return false;
+ }
+ return true;
+}
+
+/*
+ * locking.tdb records consist of
+ *
+ * uint32_t share_mode_data_len
+ * uint8_t [share_mode_data] This is struct share_mode_data in NDR
+ *
+ * 0 [SHARE_MODE_ENTRY_SIZE] Sorted array of share modes,
+ * 1 [SHARE_MODE_ENTRY_SIZE] filling up the rest of the data in the
+ * 2 [SHARE_MODE_ENTRY_SIZE] g_lock.c maintained record in locking.tdb
+ */
+
+struct locking_tdb_data {
+ const uint8_t *share_mode_data_buf;
+ size_t share_mode_data_len;
+ const uint8_t *share_entries;
+ size_t num_share_entries;
+};
+
+static bool locking_tdb_data_get(
+ struct locking_tdb_data *data, const uint8_t *buf, size_t buflen)
+{
+ uint32_t share_mode_data_len, share_entries_len;
+
+ if (buflen == 0) {
+ *data = (struct locking_tdb_data) { 0 };
+ return true;
+ }
+ if (buflen < sizeof(uint32_t)) {
+ return false;
+ }
+
+ share_mode_data_len = PULL_LE_U32(buf, 0);
+
+ buf += sizeof(uint32_t);
+ buflen -= sizeof(uint32_t);
+
+ if (buflen < share_mode_data_len) {
+ return false;
+ }
+
+ share_entries_len = buflen - share_mode_data_len;
+
+ if ((share_entries_len % SHARE_MODE_ENTRY_SIZE) != 0) {
+ return false;
+ }
+
+ *data = (struct locking_tdb_data) {
+ .share_mode_data_buf = buf,
+ .share_mode_data_len = share_mode_data_len,
+ .share_entries = buf + share_mode_data_len,
+ .num_share_entries = share_entries_len / SHARE_MODE_ENTRY_SIZE,
+ };
+
+ return true;
+}
+
+struct locking_tdb_data_fetch_state {
+ TALLOC_CTX *mem_ctx;
+ uint8_t *data;
+ size_t datalen;
+};
+
+static void locking_tdb_data_fetch_fn(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct locking_tdb_data_fetch_state *state = private_data;
+ state->datalen = datalen;
+ state->data = talloc_memdup(state->mem_ctx, data, datalen);
+}
+
+static NTSTATUS locking_tdb_data_fetch(
+ TDB_DATA key, TALLOC_CTX *mem_ctx, struct locking_tdb_data **ltdb)
+{
+ struct locking_tdb_data_fetch_state state = { 0 };
+ struct locking_tdb_data *result = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ result = talloc_zero(mem_ctx, struct locking_tdb_data);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state.mem_ctx = result;
+
+ status = share_mode_g_lock_dump(key, locking_tdb_data_fetch_fn, &state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ /*
+ * Just return an empty record
+ */
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_g_lock_dump failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (state.datalen == 0) {
+ goto done;
+ }
+
+ ok = locking_tdb_data_get(result, state.data, state.datalen);
+ if (!ok) {
+ DBG_ERR("locking_tdb_data_get failed for %zu bytes\n",
+ state.datalen);
+ TALLOC_FREE(result);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+done:
+ *ltdb = result;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS locking_tdb_data_store(
+ TDB_DATA key,
+ const struct locking_tdb_data *ltdb,
+ const TDB_DATA *share_mode_dbufs,
+ size_t num_share_mode_dbufs)
+{
+ uint8_t share_mode_data_len_buf[4];
+ TDB_DATA dbufs[num_share_mode_dbufs+3];
+ NTSTATUS status;
+
+ if ((ltdb->share_mode_data_len == 0) &&
+ (ltdb->num_share_entries == 0) &&
+ (num_share_mode_dbufs == 0)) {
+ /*
+ * Nothing to write
+ */
+ status = share_mode_g_lock_writev(key, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_g_lock_writev(NULL) failed: %s\n",
+ nt_errstr(status));
+ }
+ return status;
+ }
+
+ PUSH_LE_U32(share_mode_data_len_buf, 0, ltdb->share_mode_data_len);
+
+ dbufs[0] = (TDB_DATA) {
+ .dptr = share_mode_data_len_buf,
+ .dsize = sizeof(share_mode_data_len_buf),
+ };
+ dbufs[1] = (TDB_DATA) {
+ .dptr = discard_const_p(uint8_t, ltdb->share_mode_data_buf),
+ .dsize = ltdb->share_mode_data_len,
+ };
+
+ if (ltdb->num_share_entries > SIZE_MAX/SHARE_MODE_ENTRY_SIZE) {
+ /* overflow */
+ return NT_STATUS_BUFFER_OVERFLOW;
+ }
+ dbufs[2] = (TDB_DATA) {
+ .dptr = discard_const_p(uint8_t, ltdb->share_entries),
+ .dsize = ltdb->num_share_entries * SHARE_MODE_ENTRY_SIZE,
+ };
+
+ if (num_share_mode_dbufs != 0) {
+ memcpy(&dbufs[3],
+ share_mode_dbufs,
+ num_share_mode_dbufs * sizeof(TDB_DATA));
+ }
+
+ status = share_mode_g_lock_writev(key, dbufs, ARRAY_SIZE(dbufs));
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_g_lock_writev() failed: %s\n",
+ nt_errstr(status));
+ }
+ return status;
+}
+
+/*******************************************************************
+ Get all share mode entries for a dev/inode pair.
+********************************************************************/
+
+static struct share_mode_data *parse_share_modes(
+ TALLOC_CTX *mem_ctx,
+ struct file_id id,
+ const uint8_t *buf,
+ size_t buflen)
+{
+ struct share_mode_data *d;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ /* See if we already have a cached copy of this key. */
+ d = share_mode_memcache_fetch(mem_ctx, id, buf, buflen);
+ if (d != NULL) {
+ return d;
+ }
+
+ d = talloc(mem_ctx, struct share_mode_data);
+ if (d == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ goto fail;
+ }
+
+ blob = (DATA_BLOB) {
+ .data = discard_const_p(uint8_t, buf),
+ .length = buflen,
+ };
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob, d, d, (ndr_pull_flags_fn_t)ndr_pull_share_mode_data);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_pull_share_mode_data failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto fail;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10, ("parse_share_modes:\n"));
+ NDR_PRINT_DEBUG(share_mode_data, d);
+ }
+
+ return d;
+fail:
+ TALLOC_FREE(d);
+ return NULL;
+}
+
+static NTSTATUS share_mode_data_ltdb_store(struct share_mode_data *d,
+ TDB_DATA key,
+ struct locking_tdb_data *ltdb,
+ const TDB_DATA *share_mode_dbufs,
+ size_t num_share_mode_dbufs)
+{
+ DATA_BLOB blob = { 0 };
+ NTSTATUS status;
+
+ if (!d->modified) {
+ DBG_DEBUG("share_mode_data not modified\n");
+ goto store;
+ }
+
+ d->unique_content_epoch = generate_unique_u64(d->unique_content_epoch);
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("\n");
+ NDR_PRINT_DEBUG(share_mode_data, d);
+ }
+
+ if (ltdb->num_share_entries != 0 || num_share_mode_dbufs != 0) {
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_struct_blob(
+ &blob,
+ ltdb,
+ d,
+ (ndr_push_flags_fn_t)ndr_push_share_mode_data);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_push_share_mode_data failed: %s\n",
+ ndr_errstr(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+ }
+
+ ltdb->share_mode_data_buf = blob.data;
+ ltdb->share_mode_data_len = blob.length;
+
+store:
+ status = locking_tdb_data_store(key,
+ ltdb,
+ share_mode_dbufs,
+ num_share_mode_dbufs);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("locking_tdb_data_store failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ d->modified = false;
+ d->not_stored = (ltdb->share_mode_data_len == 0);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ If modified, store the share_mode_data back into the database.
+********************************************************************/
+
+static NTSTATUS share_mode_data_store(struct share_mode_data *d)
+{
+ TDB_DATA key = locking_key(&d->id);
+ struct locking_tdb_data *ltdb = NULL;
+ NTSTATUS status;
+
+ if (!d->modified) {
+ DBG_DEBUG("not modified\n");
+ return NT_STATUS_OK;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("\n");
+ NDR_PRINT_DEBUG(share_mode_data, d);
+ }
+
+ status = locking_tdb_data_fetch(key, d, &ltdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("locking_tdb_data_fetch failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = share_mode_data_ltdb_store(d, key, ltdb, NULL, 0);
+ TALLOC_FREE(ltdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_data_ltdb_store failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Allocate a new share_mode_data struct, mark it unmodified.
+ fresh is set to note that currently there is no database entry.
+********************************************************************/
+
+static struct share_mode_data *fresh_share_mode_lock(
+ TALLOC_CTX *mem_ctx, const char *servicepath,
+ const struct smb_filename *smb_fname,
+ const struct timespec *old_write_time)
+{
+ struct share_mode_data *d;
+
+ if ((servicepath == NULL) || (smb_fname == NULL) ||
+ (old_write_time == NULL)) {
+ return NULL;
+ }
+
+ d = talloc_zero(mem_ctx, struct share_mode_data);
+ if (d == NULL) {
+ goto fail;
+ }
+ d->unique_content_epoch = generate_unique_u64(0);
+
+ d->base_name = talloc_strdup(d, smb_fname->base_name);
+ if (d->base_name == NULL) {
+ goto fail;
+ }
+ if (smb_fname->stream_name != NULL) {
+ d->stream_name = talloc_strdup(d, smb_fname->stream_name);
+ if (d->stream_name == NULL) {
+ goto fail;
+ }
+ }
+ d->servicepath = talloc_strdup(d, servicepath);
+ if (d->servicepath == NULL) {
+ goto fail;
+ }
+ d->old_write_time = full_timespec_to_nt_time(old_write_time);
+ d->flags = SHARE_MODE_SHARE_DELETE |
+ SHARE_MODE_SHARE_WRITE |
+ SHARE_MODE_SHARE_READ;
+ d->modified = false;
+ d->not_stored = true;
+ return d;
+fail:
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(d);
+ return NULL;
+}
+
+/*
+ * Key that's locked with g_lock
+ */
+static struct file_id share_mode_lock_key_id = {};
+static TDB_DATA share_mode_lock_key = {
+ .dptr = (uint8_t *)&share_mode_lock_key_id,
+ .dsize = sizeof(share_mode_lock_key_id),
+};
+static size_t share_mode_lock_key_refcount = 0;
+
+static bool share_mode_g_lock_within_cb(TDB_DATA key)
+{
+ int cmp;
+
+ if (current_share_mode_glck == NULL) {
+ return false;
+ }
+
+ cmp = tdb_data_cmp(share_mode_lock_key, key);
+ if (cmp != 0) {
+ struct file_id_buf existing;
+
+ DBG_ERR("Can not lock two share modes "
+ "simultaneously: existing %s requested %s\n",
+ file_id_str_buf(share_mode_lock_key_id, &existing),
+ tdb_data_dbg(key));
+ smb_panic(__location__);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * We can only ever have one share mode locked. Use a static
+ * share_mode_data pointer that is shared by multiple nested
+ * share_mode_lock structures, explicitly refcounted.
+ */
+static struct share_mode_data *static_share_mode_data = NULL;
+
+/*******************************************************************
+ Either fetch a share mode from the database, or allocate a fresh
+ one if the record doesn't exist.
+********************************************************************/
+
+struct get_static_share_mode_data_state {
+ TALLOC_CTX *mem_ctx;
+ struct file_id id;
+ const char *servicepath;
+ const struct smb_filename *smb_fname;
+ const struct timespec *old_write_time;
+ NTSTATUS status;
+};
+
+static void get_static_share_mode_data_fn(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct get_static_share_mode_data_state *state = private_data;
+ struct share_mode_data *d = NULL;
+ struct locking_tdb_data ltdb = { 0 };
+
+ if (datalen != 0) {
+ bool ok;
+
+ ok = locking_tdb_data_get(&ltdb, data, datalen);
+ if (!ok) {
+ DBG_ERR("locking_tdb_data_get failed\n");
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+ }
+
+ if (ltdb.share_mode_data_len == 0) {
+ if (state->smb_fname == NULL) {
+ state->status = NT_STATUS_NOT_FOUND;
+ return;
+ }
+ d = fresh_share_mode_lock(
+ state->mem_ctx,
+ state->servicepath,
+ state->smb_fname,
+ state->old_write_time);
+ if (d == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ } else {
+ d = parse_share_modes(
+ lock_ctx,
+ state->id,
+ ltdb.share_mode_data_buf,
+ ltdb.share_mode_data_len);
+ if (d == NULL) {
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+ }
+
+ d->id = state->id;
+ static_share_mode_data = d;
+}
+
+static NTSTATUS get_static_share_mode_data(
+ struct file_id id,
+ const char *servicepath,
+ const struct smb_filename *smb_fname,
+ const struct timespec *old_write_time)
+{
+ struct get_static_share_mode_data_state state = {
+ .mem_ctx = lock_ctx,
+ .id = id,
+ .servicepath = servicepath,
+ .smb_fname = smb_fname,
+ .old_write_time = old_write_time,
+ };
+ NTSTATUS status;
+
+ SMB_ASSERT(static_share_mode_data == NULL);
+
+ status = share_mode_g_lock_dump(
+ share_mode_lock_key,
+ get_static_share_mode_data_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_GET_SHARE_MODE_LOCK(status,
+ "share_mode_g_lock_dump failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_GET_SHARE_MODE_LOCK(state.status,
+ "get_static_share_mode_data_fn failed: %s\n",
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct file_id share_mode_lock_file_id(const struct share_mode_lock *lck)
+{
+ return lck->id;
+}
+
+NTSTATUS share_mode_lock_access_private_data(struct share_mode_lock *lck,
+ struct share_mode_data **data)
+{
+ /*
+ * For now we always have lck->cached_data,
+ * but we may change that in future.
+ */
+ SMB_ASSERT(lck->cached_data != NULL);
+ *data = lck->cached_data;
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Get a share_mode_lock, Reference counted to allow nested calls.
+********************************************************************/
+
+static int share_mode_lock_destructor(struct share_mode_lock *lck);
+
+static bool share_mode_lock_skip_g_lock;
+
+static NTSTATUS get_share_mode_lock_internal(
+ struct file_id id,
+ const char *servicepath,
+ const struct smb_filename *smb_fname,
+ const struct timespec *old_write_time,
+ struct share_mode_lock *lck)
+{
+ NTSTATUS status;
+
+ *lck = (struct share_mode_lock) {
+ .id = id,
+ };
+
+ if (share_mode_lock_key_refcount == 0) {
+ if (!share_mode_lock_skip_g_lock) {
+ TDB_DATA key = locking_key(&id);
+
+ status = g_lock_lock(
+ lock_ctx,
+ key,
+ G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 3600 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ }
+ share_mode_lock_key_id = id;
+ }
+
+ if (!file_id_equal(&share_mode_lock_key_id, &id)) {
+ struct file_id_buf existing;
+ struct file_id_buf requested;
+
+ DBG_ERR("Can not lock two share modes "
+ "simultaneously: existing %s requested %s\n",
+ file_id_str_buf(share_mode_lock_key_id, &existing),
+ file_id_str_buf(id, &requested));
+ smb_panic(__location__);
+ goto fail;
+ }
+
+ SMB_ASSERT(share_mode_lock_key_refcount < SIZE_MAX);
+ share_mode_lock_key_refcount += 1;
+
+ if (static_share_mode_data != NULL) {
+ goto done;
+ }
+
+ status = get_static_share_mode_data(
+ id,
+ servicepath,
+ smb_fname,
+ old_write_time);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("get_static_share_mode_data failed: %s\n",
+ nt_errstr(status));
+ share_mode_lock_key_refcount -= 1;
+ goto fail;
+ }
+done:
+ lck->cached_data = static_share_mode_data;
+
+ if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) {
+ struct file_id_buf returned;
+
+ DBG_DEBUG("Returning %s (data_cached=%u key_refcount=%zu)\n",
+ file_id_str_buf(id, &returned),
+ static_share_mode_data != NULL,
+ share_mode_lock_key_refcount);
+ }
+
+ return NT_STATUS_OK;
+fail:
+ if (share_mode_lock_key_refcount == 0) {
+ if (!share_mode_lock_skip_g_lock) {
+ NTSTATUS ulstatus = g_lock_unlock(lock_ctx, share_mode_lock_key);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("g_lock_unlock failed: %s\n",
+ nt_errstr(ulstatus));
+ }
+ }
+ }
+ return status;
+}
+
+static NTSTATUS put_share_mode_lock_internal(struct share_mode_lock *lck)
+{
+ NTSTATUS status;
+
+ SMB_ASSERT(share_mode_lock_key_refcount > 0);
+ share_mode_lock_key_refcount -= 1;
+
+ if (share_mode_lock_key_refcount > 0) {
+ return NT_STATUS_OK;
+ }
+
+ status = share_mode_data_store(static_share_mode_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_data_store failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!share_mode_lock_skip_g_lock) {
+ status = g_lock_unlock(lock_ctx, share_mode_lock_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("g_lock_unlock failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ }
+
+ if (!static_share_mode_data->not_stored) {
+ /*
+ * This is worth keeping. Without share modes,
+ * share_mode_data_store above has left nothing in the
+ * database.
+ */
+ share_mode_memcache_store(static_share_mode_data);
+ static_share_mode_data = NULL;
+ }
+
+ TALLOC_FREE(static_share_mode_data);
+ return NT_STATUS_OK;
+}
+
+static int share_mode_lock_destructor(struct share_mode_lock *lck)
+{
+ NTSTATUS status;
+
+ status = put_share_mode_lock_internal(lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("put_share_mode_lock_internal failed: %s\n",
+ nt_errstr(status));
+ smb_panic("put_share_mode_lock_internal failed\n");
+ }
+
+ return 0;
+}
+
+/*******************************************************************
+ Fetch a share mode where we know one MUST exist. This call reference
+ counts it internally to allow for nested lock fetches.
+********************************************************************/
+
+struct share_mode_lock *get_existing_share_mode_lock(TALLOC_CTX *mem_ctx,
+ const struct file_id id)
+{
+ struct share_mode_lock *lck = NULL;
+ NTSTATUS status;
+
+ lck = talloc(mem_ctx, struct share_mode_lock);
+ if (lck == NULL) {
+ return NULL;
+ }
+
+ status = get_share_mode_lock_internal(id,
+ NULL, /* servicepath */
+ NULL, /* smb_fname */
+ NULL, /* old_write_time */
+ lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_GET_SHARE_MODE_LOCK(status,
+ "get_share_mode_lock_internal() failed - %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(lck);
+ return NULL;
+ }
+
+ talloc_set_destructor(lck, share_mode_lock_destructor);
+ return lck;
+}
+
+static void share_mode_wakeup_waiters_fn(
+ struct share_mode_lock *lck,
+ void *private_data)
+{
+ if (share_mode_g_lock_within_cb(share_mode_lock_key)) {
+ g_lock_lock_cb_wake_watchers(current_share_mode_glck);
+ return;
+ }
+
+ g_lock_wake_watchers(lock_ctx, share_mode_lock_key);
+}
+
+NTSTATUS share_mode_wakeup_waiters(struct file_id id)
+{
+ return share_mode_do_locked_vfs_denied(id,
+ share_mode_wakeup_waiters_fn,
+ NULL);
+}
+
+struct fsp_update_share_mode_flags_state {
+ struct files_struct *fsp;
+ enum ndr_err_code ndr_err;
+ uint64_t share_mode_epoch;
+ uint16_t share_mode_flags;
+};
+
+static void fsp_update_share_mode_flags_fn(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct fsp_update_share_mode_flags_state *state = private_data;
+ struct locking_tdb_data ltdb = { 0 };
+
+ if (datalen != 0) {
+ bool ok = locking_tdb_data_get(&ltdb, data, datalen);
+ if (!ok) {
+ DBG_DEBUG("locking_tdb_data_get failed\n");
+ return;
+ }
+ }
+
+ if (ltdb.share_mode_data_len == 0) {
+ /* Likely a ctdb tombstone record, ignore it */
+ return;
+ }
+
+ if (exclusive.pid != 0) {
+ struct server_id self =
+ messaging_server_id(state->fsp->conn->sconn->msg_ctx);
+ bool is_self = server_id_equal(&self, &exclusive);
+
+ if (!is_self) {
+ /*
+ * If someone else is holding an exclusive
+ * lock, pretend there's a read lease
+ */
+ state->share_mode_flags = SHARE_MODE_LEASE_READ;
+ return;
+ }
+ }
+
+ state->ndr_err = get_share_mode_blob_header(ltdb.share_mode_data_buf,
+ ltdb.share_mode_data_len,
+ &state->share_mode_epoch,
+ &state->share_mode_flags);
+}
+
+static NTSTATUS fsp_update_share_mode_flags(struct files_struct *fsp)
+{
+ struct fsp_update_share_mode_flags_state state = { .fsp = fsp, };
+ int seqnum = g_lock_seqnum(lock_ctx);
+ TDB_DATA key = {0};
+ NTSTATUS status;
+
+ if (seqnum == fsp->share_mode_flags_seqnum) {
+ return NT_STATUS_OK;
+ }
+
+ key = locking_key(&fsp->file_id);
+ status = share_mode_g_lock_dump(key,
+ fsp_update_share_mode_flags_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* no DBG_GET_SHARE_MODE_LOCK here! */
+ DBG_ERR("share_mode_g_lock_dump returned %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(state.ndr_err)) {
+ DBG_ERR("get_share_mode_blob_header returned %s\n",
+ ndr_errstr(state.ndr_err));
+ return ndr_map_error2ntstatus(state.ndr_err);
+ }
+
+ fsp->share_mode_flags_seqnum = seqnum;
+ fsp->share_mode_flags = state.share_mode_flags;
+
+ return NT_STATUS_OK;
+}
+
+bool file_has_read_lease(struct files_struct *fsp)
+{
+ NTSTATUS status;
+
+ status = fsp_update_share_mode_flags(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Safe default for leases */
+ return true;
+ }
+
+ return (fsp->share_mode_flags & SHARE_MODE_LEASE_READ) != 0;
+}
+
+#define share_mode_lock_assert_private_data(__lck) \
+ _share_mode_lock_assert_private_data(__lck, __func__, __location__)
+static struct share_mode_data *_share_mode_lock_assert_private_data(
+ struct share_mode_lock *lck,
+ const char *caller_function,
+ const char *caller_location)
+{
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct file_id_buf id_buf;
+ /* Any error recovery possible here ? */
+ D_ERR("%s:%s(): share_mode_lock_access_private_data() "
+ "failed for id=%s - %s\n",
+ caller_location, caller_function,
+ file_id_str_buf(id, &id_buf),
+ nt_errstr(status));
+ smb_panic(caller_location);
+ return NULL;
+ }
+
+ return d;
+}
+
+NTTIME share_mode_changed_write_time(struct share_mode_lock *lck)
+{
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ return d->changed_write_time;
+}
+
+void share_mode_set_changed_write_time(struct share_mode_lock *lck, struct timespec write_time)
+{
+ struct file_id fileid = share_mode_lock_file_id(lck);
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ struct file_id_buf ftmp;
+ struct timeval_buf tbuf;
+ NTTIME nt = full_timespec_to_nt_time(&write_time);
+
+ DBG_INFO("%s id=%s\n",
+ timespec_string_buf(&write_time, true, &tbuf),
+ file_id_str_buf(fileid, &ftmp));
+
+ if (d->changed_write_time != nt) {
+ d->modified = true;
+ d->changed_write_time = nt;
+ }
+}
+
+void share_mode_set_old_write_time(struct share_mode_lock *lck, struct timespec write_time)
+{
+ struct file_id fileid = share_mode_lock_file_id(lck);
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ struct file_id_buf ftmp;
+ struct timeval_buf tbuf;
+ NTTIME nt = full_timespec_to_nt_time(&write_time);
+
+ DBG_INFO("%s id=%s\n",
+ timespec_string_buf(&write_time, true, &tbuf),
+ file_id_str_buf(fileid, &ftmp));
+
+ if (d->changed_write_time != nt) {
+ d->modified = true;
+ d->old_write_time = nt;
+ }
+}
+
+const char *share_mode_servicepath(struct share_mode_lock *lck)
+{
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ return d->servicepath;
+}
+
+char *share_mode_filename(TALLOC_CTX *mem_ctx, struct share_mode_lock *lck)
+{
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ bool has_stream = (d->stream_name != NULL);
+ char *fname = NULL;
+
+ fname = talloc_asprintf(
+ mem_ctx,
+ "%s%s%s",
+ d->base_name,
+ has_stream ? ":" : "",
+ has_stream ? d->stream_name : "");
+ return fname;
+}
+
+char *share_mode_data_dump(
+ TALLOC_CTX *mem_ctx, struct share_mode_lock *lck)
+{
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ struct ndr_print *p = talloc(mem_ctx, struct ndr_print);
+ char *ret = NULL;
+
+ if (p == NULL) {
+ return NULL;
+ }
+
+ *p = (struct ndr_print) {
+ .print = ndr_print_string_helper,
+ .depth = 1,
+ .private_data = talloc_strdup(mem_ctx, ""),
+ };
+
+ if (p->private_data == NULL) {
+ TALLOC_FREE(p);
+ return NULL;
+ }
+
+ ndr_print_share_mode_data(p, "SHARE_MODE_DATA", d);
+
+ ret = p->private_data;
+
+ TALLOC_FREE(p);
+
+ return ret;
+}
+
+void share_mode_flags_get(
+ struct share_mode_lock *lck,
+ uint32_t *access_mask,
+ uint32_t *share_mode,
+ uint32_t *lease_type)
+{
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ uint16_t flags = d->flags;
+
+ if (access_mask != NULL) {
+ *access_mask =
+ ((flags & SHARE_MODE_ACCESS_READ) ?
+ FILE_READ_DATA : 0) |
+ ((flags & SHARE_MODE_ACCESS_WRITE) ?
+ FILE_WRITE_DATA : 0) |
+ ((flags & SHARE_MODE_ACCESS_DELETE) ?
+ DELETE_ACCESS : 0);
+ }
+ if (share_mode != NULL) {
+ *share_mode =
+ ((flags & SHARE_MODE_SHARE_READ) ?
+ FILE_SHARE_READ : 0) |
+ ((flags & SHARE_MODE_SHARE_WRITE) ?
+ FILE_SHARE_WRITE : 0) |
+ ((flags & SHARE_MODE_SHARE_DELETE) ?
+ FILE_SHARE_DELETE : 0);
+ }
+ if (lease_type != NULL) {
+ *lease_type =
+ ((flags & SHARE_MODE_LEASE_READ) ?
+ SMB2_LEASE_READ : 0) |
+ ((flags & SHARE_MODE_LEASE_WRITE) ?
+ SMB2_LEASE_WRITE : 0) |
+ ((flags & SHARE_MODE_LEASE_HANDLE) ?
+ SMB2_LEASE_HANDLE : 0);
+ }
+}
+
+void share_mode_flags_set(
+ struct share_mode_lock *lck,
+ uint32_t access_mask,
+ uint32_t share_mode,
+ uint32_t lease_type,
+ bool *modified)
+{
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ uint16_t flags = 0;
+
+ flags |= (access_mask & (FILE_READ_DATA | FILE_EXECUTE)) ?
+ SHARE_MODE_ACCESS_READ : 0;
+ flags |= (access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) ?
+ SHARE_MODE_ACCESS_WRITE : 0;
+ flags |= (access_mask & (DELETE_ACCESS)) ?
+ SHARE_MODE_ACCESS_DELETE : 0;
+
+ flags |= (share_mode & FILE_SHARE_READ) ?
+ SHARE_MODE_SHARE_READ : 0;
+ flags |= (share_mode & FILE_SHARE_WRITE) ?
+ SHARE_MODE_SHARE_WRITE : 0;
+ flags |= (share_mode & FILE_SHARE_DELETE) ?
+ SHARE_MODE_SHARE_DELETE : 0;
+
+ flags |= (lease_type & SMB2_LEASE_READ) ?
+ SHARE_MODE_LEASE_READ : 0;
+ flags |= (lease_type & SMB2_LEASE_WRITE) ?
+ SHARE_MODE_LEASE_WRITE : 0;
+ flags |= (lease_type & SMB2_LEASE_HANDLE) ?
+ SHARE_MODE_LEASE_HANDLE : 0;
+
+ if (d->flags == flags) {
+ return;
+ }
+
+ if (modified != NULL) {
+ *modified = true;
+ }
+ d->flags = flags;
+ d->modified = true;
+}
+
+struct share_mode_watch_state {
+ bool blockerdead;
+ struct server_id blocker;
+ bool within_cb;
+};
+
+static void share_mode_watch_done(struct tevent_req *subreq);
+
+struct tevent_req *share_mode_watch_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct share_mode_lock *lck,
+ struct server_id blocker)
+{
+ struct file_id id = share_mode_lock_file_id(lck);
+ TDB_DATA key = locking_key(&id);
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct share_mode_watch_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct share_mode_watch_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (share_mode_g_lock_within_cb(key)) {
+ state->within_cb = true;
+ subreq = g_lock_lock_cb_watch_data_send(state, ev,
+ current_share_mode_glck,
+ blocker);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ subreq = g_lock_watch_data_send(state, ev, lock_ctx, key, blocker);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ tevent_req_set_callback(subreq, share_mode_watch_done, req);
+ return req;
+}
+
+static void share_mode_watch_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct share_mode_watch_state *state = tevent_req_data(
+ req, struct share_mode_watch_state);
+ NTSTATUS status;
+
+ if (state->within_cb) {
+ status = g_lock_lock_cb_watch_data_recv(
+ subreq, &state->blockerdead, &state->blocker);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ } else {
+ status = g_lock_watch_data_recv(
+ subreq, &state->blockerdead, &state->blocker);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS share_mode_watch_recv(
+ struct tevent_req *req, bool *blockerdead, struct server_id *blocker)
+{
+ struct share_mode_watch_state *state = tevent_req_data(
+ req, struct share_mode_watch_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (blockerdead != NULL) {
+ *blockerdead = state->blockerdead;
+ }
+ if (blocker != NULL) {
+ *blocker = state->blocker;
+ }
+ return NT_STATUS_OK;
+}
+
+struct fetch_share_mode_unlocked_state {
+ TALLOC_CTX *mem_ctx;
+ struct file_id id;
+ struct share_mode_lock *lck;
+};
+
+static void fetch_share_mode_unlocked_parser(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct fetch_share_mode_unlocked_state *state = private_data;
+ struct locking_tdb_data ltdb = { 0 };
+
+ if (datalen != 0) {
+ bool ok = locking_tdb_data_get(&ltdb, data, datalen);
+ if (!ok) {
+ DBG_DEBUG("locking_tdb_data_get failed\n");
+ return;
+ }
+ }
+
+ if (ltdb.share_mode_data_len == 0) {
+ /* Likely a ctdb tombstone record, ignore it */
+ return;
+ }
+
+ state->lck = talloc(state->mem_ctx, struct share_mode_lock);
+ if (state->lck == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return;
+ }
+ state->lck->id = state->id;
+
+ state->lck->cached_data = parse_share_modes(
+ state->lck,
+ state->id,
+ ltdb.share_mode_data_buf,
+ ltdb.share_mode_data_len);
+ if (state->lck->cached_data == NULL) {
+ DBG_DEBUG("parse_share_modes failed\n");
+ TALLOC_FREE(state->lck);
+ }
+}
+
+/*******************************************************************
+ Get a share_mode_lock without locking the database or reference
+ counting. Used by smbstatus to display existing share modes.
+********************************************************************/
+
+struct share_mode_lock *fetch_share_mode_unlocked(TALLOC_CTX *mem_ctx,
+ struct file_id id)
+{
+ struct fetch_share_mode_unlocked_state state = {
+ .mem_ctx = mem_ctx,
+ .id = id,
+ };
+ TDB_DATA key = locking_key(&id);
+ NTSTATUS status;
+
+ status = g_lock_dump(
+ lock_ctx, key, fetch_share_mode_unlocked_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("g_lock_dump failed: %s\n", nt_errstr(status));
+ return NULL;
+ }
+ return state.lck;
+}
+
+struct fetch_share_mode_state {
+ struct file_id id;
+ struct share_mode_lock *lck;
+ NTSTATUS status;
+};
+
+static void fetch_share_mode_fn(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data);
+static void fetch_share_mode_done(struct tevent_req *subreq);
+
+/**
+ * @brief Get a share_mode_lock without locking or refcounting
+ *
+ * This can be used in a clustered Samba environment where the async dbwrap
+ * request is sent over a socket to the local ctdbd. If the send queue is full
+ * and the caller was issuing multiple async dbwrap requests in a loop, the
+ * caller knows it's probably time to stop sending requests for now and try
+ * again later.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] ev The event context to work on.
+ *
+ * @param[in] id The file id for the locking.tdb key
+ *
+ * @param[out] queued This boolean out parameter tells the caller whether the
+ * async request is blocked in a full send queue:
+ *
+ * false := request is dispatched
+ *
+ * true := send queue is full, request waiting to be
+ * dispatched
+ *
+ * @return The new async request, NULL on error.
+ **/
+struct tevent_req *fetch_share_mode_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct file_id id,
+ bool *queued)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct fetch_share_mode_state *state = NULL;
+
+ *queued = false;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fetch_share_mode_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->id = id;
+
+ subreq = g_lock_dump_send(
+ state,
+ ev,
+ lock_ctx,
+ locking_key(&id),
+ fetch_share_mode_fn,
+ state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_share_mode_done, req);
+ return req;
+}
+
+static void fetch_share_mode_fn(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct fetch_share_mode_state *state = talloc_get_type_abort(
+ private_data, struct fetch_share_mode_state);
+ struct locking_tdb_data ltdb = { 0 };
+
+ if (datalen != 0) {
+ bool ok = locking_tdb_data_get(&ltdb, data, datalen);
+ if (!ok) {
+ DBG_DEBUG("locking_tdb_data_get failed\n");
+ return;
+ }
+ }
+
+ if (ltdb.share_mode_data_len == 0) {
+ /* Likely a ctdb tombstone record, ignore it */
+ return;
+ }
+
+ state->lck = talloc(state, struct share_mode_lock);
+ if (state->lck == NULL) {
+ DBG_WARNING("talloc failed\n");
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+ state->lck->id = state->id,
+
+ state->lck->cached_data = parse_share_modes(
+ state->lck,
+ state->id,
+ ltdb.share_mode_data_buf,
+ ltdb.share_mode_data_len);
+ if (state->lck->cached_data == NULL) {
+ DBG_DEBUG("parse_share_modes failed\n");
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ TALLOC_FREE(state->lck);
+ return;
+ }
+}
+
+static void fetch_share_mode_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_share_mode_state *state = tevent_req_data(
+ req, struct fetch_share_mode_state);
+ NTSTATUS status;
+
+ status = g_lock_dump_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (tevent_req_nterror(req, state->status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS fetch_share_mode_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct share_mode_lock **_lck)
+{
+ struct fetch_share_mode_state *state = tevent_req_data(
+ req, struct fetch_share_mode_state);
+ struct share_mode_lock *lck = NULL;
+
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ if (state->lck == NULL) {
+ tevent_req_received(req);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ lck = talloc_move(mem_ctx, &state->lck);
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("share_mode_data:\n");
+ NDR_PRINT_DEBUG(share_mode_data, lck->cached_data);
+ }
+
+ *_lck = lck;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct share_mode_forall_state {
+ TDB_DATA key;
+ int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data);
+ void *private_data;
+};
+
+static void share_mode_forall_dump_fn(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct share_mode_forall_state *state = private_data;
+ struct file_id fid;
+ struct locking_tdb_data ltdb = { 0 };
+ bool ok;
+ struct share_mode_data *d;
+
+ if (state->key.dsize != sizeof(fid)) {
+ DBG_DEBUG("Got invalid key length %zu\n", state->key.dsize);
+ return;
+ }
+ memcpy(&fid, state->key.dptr, sizeof(fid));
+
+ ok = locking_tdb_data_get(&ltdb, data, datalen);
+ if (!ok) {
+ DBG_DEBUG("locking_tdb_data_get() failed\n");
+ return;
+ }
+
+ d = parse_share_modes(
+ talloc_tos(),
+ fid,
+ ltdb.share_mode_data_buf,
+ ltdb.share_mode_data_len);
+ if (d == NULL) {
+ DBG_DEBUG("parse_share_modes() failed\n");
+ return;
+ }
+
+ state->fn(fid, d, state->private_data);
+ TALLOC_FREE(d);
+}
+
+static int share_mode_forall_fn(TDB_DATA key, void *private_data)
+{
+ struct share_mode_forall_state *state = private_data;
+ NTSTATUS status;
+
+ state->key = key;
+
+ status = share_mode_g_lock_dump(
+ key, share_mode_forall_dump_fn, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_GET_SHARE_MODE_LOCK(status,
+ "g_lock_dump failed: %s\n",
+ nt_errstr(status));
+ }
+ return 0;
+}
+
+int share_mode_forall(int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data),
+ void *private_data)
+{
+ struct share_mode_forall_state state = {
+ .fn = fn,
+ .private_data = private_data
+ };
+ int ret;
+
+ if (lock_ctx == NULL) {
+ return 0;
+ }
+
+ ret = g_lock_locks(
+ lock_ctx, share_mode_forall_fn, &state);
+ if (ret < 0) {
+ DBG_ERR("g_lock_locks failed\n");
+ }
+ return ret;
+}
+
+struct share_entry_forall_state {
+ struct file_id fid;
+ const struct share_mode_data *data;
+ int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ const struct share_mode_entry *entry,
+ void *private_data);
+ void *private_data;
+ int ret;
+};
+
+static bool share_entry_traverse_walker(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct share_entry_forall_state *state = private_data;
+
+ state->ret = state->fn(
+ state->fid, state->data, e, state->private_data);
+ return (state->ret != 0);
+}
+
+static int share_entry_traverse_fn(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data)
+{
+ struct share_entry_forall_state *state = private_data;
+ struct share_mode_lock lck = {
+ .id = fid,
+ .cached_data = discard_const_p(struct share_mode_data, data)
+ };
+ bool ok;
+
+ state->fid = fid;
+ state->data = data;
+
+ ok = share_mode_forall_entries(
+ &lck, share_entry_traverse_walker, state);
+ if (!ok) {
+ DBG_ERR("share_mode_forall_entries failed\n");
+ return false;
+ }
+
+ return state->ret;
+}
+
+/*******************************************************************
+ Call the specified function on each entry under management by the
+ share mode system.
+********************************************************************/
+
+int share_entry_forall(int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ const struct share_mode_entry *entry,
+ void *private_data),
+ void *private_data)
+{
+ struct share_entry_forall_state state = {
+ .fn = fn, .private_data = private_data };
+
+ return share_mode_forall(share_entry_traverse_fn, &state);
+}
+
+static int share_mode_entry_cmp(
+ struct server_id pid1,
+ uint64_t share_file_id1,
+ struct server_id pid2,
+ uint64_t share_file_id2)
+{
+ int cmp;
+
+ cmp = server_id_cmp(&pid1, &pid2);
+ if (cmp != 0) {
+ return cmp;
+ }
+ if (share_file_id1 != share_file_id2) {
+ return (share_file_id1 < share_file_id2) ? -1 : 1;
+ }
+ return 0;
+}
+
+static size_t share_mode_entry_find(
+ const uint8_t *data,
+ size_t num_share_modes,
+ struct server_id pid,
+ uint64_t share_file_id,
+ struct share_mode_entry *e,
+ bool *match)
+{
+ ssize_t left, right, middle;
+
+ *match = false;
+
+ if (num_share_modes == 0) {
+ return 0;
+ }
+
+ left = 0;
+ right = (num_share_modes-1);
+
+ while (left <= right) {
+ const uint8_t *middle_ptr = NULL;
+ int cmp;
+ bool ok;
+
+ middle = left + ((right - left) / 2);
+ middle_ptr = data + middle * SHARE_MODE_ENTRY_SIZE;
+
+ DBG_DEBUG("left=%zu, right=%zu, middle=%zu, middle_ptr=%p\n",
+ left,
+ right,
+ middle,
+ middle_ptr);
+
+ ok = share_mode_entry_get(middle_ptr, e);
+ if (!ok) {
+ DBG_DEBUG("share_mode_entry_get failed\n");
+ return 0;
+ }
+
+ cmp = share_mode_entry_cmp(
+ e->pid, e->share_file_id, pid, share_file_id);
+ if (cmp == 0) {
+ *match = true;
+ return middle;
+ }
+
+ if (cmp < 0) {
+ right = middle-1;
+ } else {
+ left = middle+1;
+ }
+ }
+
+ return left;
+}
+
+bool set_share_mode(struct share_mode_lock *lck,
+ struct files_struct *fsp,
+ uid_t uid,
+ uint64_t mid,
+ uint16_t op_type,
+ const struct smb2_lease_key *lease_key,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ struct share_mode_data *d = share_mode_lock_assert_private_data(lck);
+ TDB_DATA key = locking_key(&d->id);
+ struct server_id my_pid = messaging_server_id(
+ fsp->conn->sconn->msg_ctx);
+ struct locking_tdb_data *ltdb = NULL;
+ size_t idx;
+ struct share_mode_entry e = { .pid.pid = 0 };
+ struct share_mode_entry_buf e_buf;
+ NTSTATUS status;
+ bool ok, found;
+
+ TDB_DATA dbufs[3];
+ size_t num_dbufs = 0;
+
+ status = locking_tdb_data_fetch(key, talloc_tos(), &ltdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("locking_tdb_data_fetch failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ DBG_DEBUG("num_share_modes=%zu\n", ltdb->num_share_entries);
+
+ idx = share_mode_entry_find(
+ ltdb->share_entries,
+ ltdb->num_share_entries,
+ my_pid,
+ fh_get_gen_id(fsp->fh),
+ &e,
+ &found);
+ if (found) {
+ DBG_WARNING("Found duplicate share mode\n");
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ e = (struct share_mode_entry) {
+ .pid = my_pid,
+ .share_access = share_access,
+ .private_options = fh_get_private_options(fsp->fh),
+ .access_mask = access_mask,
+ .op_mid = mid,
+ .op_type = op_type,
+ .time.tv_sec = fsp->open_time.tv_sec,
+ .time.tv_usec = fsp->open_time.tv_usec,
+ .share_file_id = fh_get_gen_id(fsp->fh),
+ .uid = (uint32_t)uid,
+ .flags = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
+ SHARE_MODE_FLAG_POSIX_OPEN : 0,
+ .name_hash = fsp->name_hash,
+ };
+
+ if (op_type == LEASE_OPLOCK) {
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ e.client_guid = *client_guid;
+ e.lease_key = *lease_key;
+ }
+
+ ok = share_mode_entry_put(&e, &e_buf);
+ if (!ok) {
+ DBG_DEBUG("share_mode_entry_put failed\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ DBG_DEBUG("idx=%zu, found=%d\n", idx, (int)found);
+
+ if (idx > 0) {
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = discard_const_p(uint8_t, ltdb->share_entries),
+ .dsize = idx * SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+ }
+
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = e_buf.buf, .dsize = SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+
+ if (idx < ltdb->num_share_entries) {
+ size_t num_after_idx = (ltdb->num_share_entries-idx);
+ dbufs[num_dbufs] = (TDB_DATA) {
+ .dptr = discard_const_p(uint8_t, ltdb->share_entries) +
+ idx * SHARE_MODE_ENTRY_SIZE,
+ .dsize = num_after_idx * SHARE_MODE_ENTRY_SIZE,
+ };
+ num_dbufs += 1;
+ }
+
+ {
+ size_t i;
+ for (i=0; i<num_dbufs; i++) {
+ DBG_DEBUG("dbufs[%zu]=(%p, %zu)\n",
+ i,
+ dbufs[i].dptr,
+ dbufs[i].dsize);
+ }
+ }
+
+ if (num_dbufs == 1) {
+ /*
+ * Storing a fresh record with just one share entry
+ */
+ d->modified = true;
+ }
+
+ /*
+ * If there was any existing data in
+ * ltdb->share_entries, it's now been
+ * moved and we've split it into:
+ *
+ * num_dbufs = 3
+ * dbufs[0] -> old sorted data less than new_entry
+ * dbufs[1] -> new_share_mode_entry
+ * dbufs[2] -> old sorted_data greater than new entry.
+ *
+ * So the old data inside ltdb->share_entries is
+ * no longer valid.
+ *
+ * If we're storing a brand new entry the
+ * dbufs look like:
+ *
+ * num_dbufs = 1
+ * dbufs[0] -> new_share_mode_entry
+ *
+ * Either way we must set ltdb->share_entries = NULL
+ * and ltdb->num_share_entries = 0 so that
+ * locking_tdb_data_store() doesn't use it to
+ * store any data. It's no longer there.
+ */
+
+ ltdb->share_entries = NULL;
+ ltdb->num_share_entries = 0;
+
+ status = share_mode_data_ltdb_store(d, key, ltdb, dbufs, num_dbufs);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_data_ltdb_store failed: %s\n",
+ nt_errstr(status));
+ }
+done:
+ TALLOC_FREE(ltdb);
+ return NT_STATUS_IS_OK(status);
+}
+
+static bool share_mode_for_one_entry(
+ bool (*fn)(struct share_mode_entry *e,
+ bool *modified,
+ void *private_data),
+ void *private_data,
+ size_t *i,
+ uint8_t *data,
+ size_t *num_share_modes,
+ bool *writeback)
+{
+ DATA_BLOB blob = {
+ .data = data + (*i) * SHARE_MODE_ENTRY_SIZE,
+ .length = SHARE_MODE_ENTRY_SIZE,
+ };
+ struct share_mode_entry e = {.pid.pid=0};
+ enum ndr_err_code ndr_err = NDR_ERR_SUCCESS;
+ bool modified = false;
+ bool stop = false;
+ struct server_id e_pid;
+ uint64_t e_share_file_id;
+
+ ndr_err = ndr_pull_struct_blob_all_noalloc(
+ &blob,
+ &e,
+ (ndr_pull_flags_fn_t)ndr_pull_share_mode_entry);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_pull_share_mode_entry failed\n");
+ *i += 1;
+ return false;
+ }
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("entry[%zu]:\n", *i);
+ NDR_PRINT_DEBUG(share_mode_entry, &e);
+ }
+
+ e_pid = e.pid;
+ e_share_file_id = e.share_file_id;
+
+ stop = fn(&e, &modified, private_data);
+
+ DBG_DEBUG("entry[%zu]: modified=%d, e.stale=%d\n",
+ *i,
+ (int)modified,
+ (int)e.stale);
+
+ if (e.stale) {
+ if (DEBUGLEVEL>=10) {
+ DBG_DEBUG("share_mode_entry:\n");
+ NDR_PRINT_DEBUG(share_mode_entry, &e);
+ }
+
+ if (*i < *num_share_modes) {
+ memmove(blob.data,
+ blob.data + SHARE_MODE_ENTRY_SIZE,
+ (*num_share_modes - *i - 1) *
+ SHARE_MODE_ENTRY_SIZE);
+ }
+ *num_share_modes -= 1;
+ *writeback = true;
+ return stop;
+ }
+
+ if (modified) {
+ if (DEBUGLEVEL>=10) {
+ DBG_DEBUG("share_mode_entry:\n");
+ NDR_PRINT_DEBUG(share_mode_entry, &e);
+ }
+
+ /*
+ * Make sure sorting order is kept intact
+ */
+ SMB_ASSERT(server_id_equal(&e_pid, &e.pid));
+ SMB_ASSERT(e_share_file_id == e.share_file_id);
+
+ ndr_err = ndr_push_struct_into_fixed_blob(
+ &blob,
+ &e,
+ (ndr_push_flags_fn_t)
+ ndr_push_share_mode_entry);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_share_mode_entry "
+ "failed: %s\n",
+ ndr_errstr(ndr_err));
+ /*
+ * Not much we can do, just ignore it
+ */
+ }
+ *i += 1;
+ *writeback = true;
+ return stop;
+ }
+
+ if (stop) {
+ return true;
+ }
+
+ *i += 1;
+ return false;
+}
+
+bool share_mode_forall_entries(
+ struct share_mode_lock *lck,
+ bool (*fn)(struct share_mode_entry *e,
+ bool *modified,
+ void *private_data),
+ void *private_data)
+{
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct share_mode_data *d = NULL;
+ TDB_DATA key = locking_key(&id);
+ struct locking_tdb_data *ltdb = NULL;
+ uint8_t *share_entries = NULL;
+ size_t num_share_entries;
+ bool writeback = false;
+ NTSTATUS status;
+ bool stop = false;
+ size_t i;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id_buf id_buf;
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s - %s\n",
+ file_id_str_buf(id, &id_buf),
+ nt_errstr(status));
+ return false;
+ }
+
+ status = locking_tdb_data_fetch(key, talloc_tos(), &ltdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("locking_tdb_data_fetch failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ DBG_DEBUG("num_share_modes=%zu\n", ltdb->num_share_entries);
+
+ num_share_entries = ltdb->num_share_entries;
+ share_entries = discard_const_p(uint8_t, ltdb->share_entries);
+
+ i = 0;
+ while (i<num_share_entries) {
+ stop = share_mode_for_one_entry(
+ fn,
+ private_data,
+ &i,
+ share_entries,
+ &num_share_entries,
+ &writeback);
+ if (stop) {
+ break;
+ }
+ }
+
+ DBG_DEBUG("num_share_entries=%zu, writeback=%d\n",
+ num_share_entries,
+ (int)writeback);
+
+ if (!writeback) {
+ TALLOC_FREE(ltdb);
+ return true;
+ }
+
+ if ((ltdb->num_share_entries != 0 ) && (num_share_entries == 0)) {
+ /*
+ * This routine wiped all share entries, let
+ * share_mode_data_store() delete the record
+ */
+ d->modified = true;
+ }
+
+ ltdb->num_share_entries = num_share_entries;
+ ltdb->share_entries = share_entries;
+
+ status = share_mode_data_ltdb_store(d, key, ltdb, NULL, 0);
+ TALLOC_FREE(ltdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_data_ltdb_store failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+struct share_mode_count_entries_state {
+ size_t num_share_modes;
+ NTSTATUS status;
+};
+
+static void share_mode_count_entries_fn(
+ struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct share_mode_count_entries_state *state = private_data;
+ struct locking_tdb_data ltdb = { 0 };
+ bool ok;
+
+ ok = locking_tdb_data_get(&ltdb, data, datalen);
+ if (!ok) {
+ DBG_WARNING("locking_tdb_data_get failed for %zu\n", datalen);
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+ state->num_share_modes = ltdb.num_share_entries;
+ state->status = NT_STATUS_OK;
+}
+
+NTSTATUS share_mode_count_entries(struct file_id fid, size_t *num_share_modes)
+{
+ struct share_mode_count_entries_state state = {
+ .status = NT_STATUS_NOT_FOUND,
+ };
+ NTSTATUS status;
+
+ status = g_lock_dump(
+ lock_ctx,
+ locking_key(&fid),
+ share_mode_count_entries_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("g_lock_dump failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_DEBUG("share_mode_count_entries_fn failed: %s\n",
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ *num_share_modes = state.num_share_modes;
+ return NT_STATUS_OK;
+}
+
+static bool share_mode_entry_do(
+ struct share_mode_data *d,
+ struct server_id pid,
+ uint64_t share_file_id,
+ void (*fn)(struct share_mode_entry *e,
+ size_t num_share_modes,
+ bool *modified,
+ void *private_data),
+ void *private_data)
+{
+ TDB_DATA key = locking_key(&d->id);
+ struct locking_tdb_data *ltdb = NULL;
+ size_t idx;
+ bool found = false;
+ bool modified = false;
+ struct share_mode_entry e;
+ uint8_t *e_ptr = NULL;
+ NTSTATUS status;
+ bool ret = false;
+
+ status = locking_tdb_data_fetch(key, talloc_tos(), &ltdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("locking_tdb_data_fetch failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ DBG_DEBUG("num_share_modes=%zu\n", ltdb->num_share_entries);
+
+ idx = share_mode_entry_find(
+ ltdb->share_entries,
+ ltdb->num_share_entries,
+ pid,
+ share_file_id,
+ &e,
+ &found);
+ if (!found) {
+ DBG_WARNING("Did not find share mode entry for %"PRIu64"\n",
+ share_file_id);
+ goto done;
+ }
+
+ if (DEBUGLEVEL>=10) {
+ DBG_DEBUG("entry[%zu]:\n", idx);
+ NDR_PRINT_DEBUG(share_mode_entry, &e);
+ }
+
+ fn(&e, ltdb->num_share_entries, &modified, private_data);
+
+ DBG_DEBUG("entry[%zu]: modified=%d, e.stale=%d\n",
+ idx,
+ (int)modified,
+ (int)e.stale);
+
+ if (!e.stale && !modified) {
+ ret = true;
+ goto done;
+ }
+
+ e_ptr = discard_const_p(uint8_t, ltdb->share_entries) +
+ idx * SHARE_MODE_ENTRY_SIZE;
+
+ if (e.stale) {
+ /*
+ * Move the rest down one entry
+ */
+ size_t behind = ltdb->num_share_entries - idx - 1;
+ if (behind != 0) {
+ memmove(e_ptr,
+ e_ptr + SHARE_MODE_ENTRY_SIZE,
+ behind * SHARE_MODE_ENTRY_SIZE);
+ }
+ ltdb->num_share_entries -= 1;
+
+ if (ltdb->num_share_entries == 0) {
+ /*
+ * Tell share_mode_lock_destructor() to delete
+ * the whole record
+ */
+ d->modified = true;
+ }
+
+ if (DEBUGLEVEL>=10) {
+ DBG_DEBUG("share_mode_entry:\n");
+ NDR_PRINT_DEBUG(share_mode_entry, &e);
+ }
+ } else {
+ struct share_mode_entry_buf buf;
+ bool ok;
+
+ if (ltdb->num_share_entries != 1) {
+ /*
+ * Make sure the sorting order stays intact
+ */
+ SMB_ASSERT(server_id_equal(&e.pid, &pid));
+ SMB_ASSERT(e.share_file_id == share_file_id);
+ }
+
+ ok = share_mode_entry_put(&e, &buf);
+ if (!ok) {
+ DBG_DEBUG("share_mode_entry_put failed\n");
+ goto done;
+ }
+ memcpy(e_ptr, buf.buf, SHARE_MODE_ENTRY_SIZE);
+ }
+
+ status = share_mode_data_ltdb_store(d, key, ltdb, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_data_ltdb_store failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = true;
+done:
+ TALLOC_FREE(ltdb);
+ return ret;
+}
+
+struct del_share_mode_state {
+ bool ok;
+};
+
+static void del_share_mode_fn(
+ struct share_mode_entry *e,
+ size_t num_share_modes,
+ bool *modified,
+ void *private_data)
+{
+ struct del_share_mode_state *state = private_data;
+ e->stale = true;
+ state->ok = true;
+}
+
+bool del_share_mode_open_id(struct share_mode_lock *lck,
+ struct server_id open_pid,
+ uint64_t open_file_id)
+{
+ struct del_share_mode_state state = { .ok = false };
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Any error recovery possible here ? */
+ return false;
+ }
+
+ ok = share_mode_entry_do(
+ d,
+ open_pid,
+ open_file_id,
+ del_share_mode_fn,
+ &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_entry_do failed\n");
+ return false;
+ }
+ if (!state.ok) {
+ DBG_DEBUG("del_share_mode_fn failed\n");
+ return false;
+ }
+ return true;
+}
+
+bool del_share_mode(struct share_mode_lock *lck, files_struct *fsp)
+{
+ struct server_id pid =
+ messaging_server_id(fsp->conn->sconn->msg_ctx);
+ bool ok;
+
+ ok = del_share_mode_open_id(lck, pid, fh_get_gen_id(fsp->fh));
+ if (!ok) {
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct file_id_buf id_buf;
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s %s\n",
+ file_id_str_buf(id, &id_buf),
+ fsp_str_dbg(fsp));
+ return false;
+ }
+ return true;
+}
+
+struct remove_share_oplock_state {
+ bool ok;
+};
+
+static void remove_share_oplock_fn(
+ struct share_mode_entry *e,
+ size_t num_share_modes,
+ bool *modified,
+ void *private_data)
+{
+ struct remove_share_oplock_state *state = private_data;
+
+ e->op_type = NO_OPLOCK;
+ *modified = true;
+ state->ok = true;
+}
+
+bool remove_share_oplock(struct share_mode_lock *lck, files_struct *fsp)
+{
+ struct remove_share_oplock_state state = { .ok = false };
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct file_id_buf id_buf;
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s %s - %s\n",
+ file_id_str_buf(id, &id_buf),
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return false;
+ }
+
+ ok = share_mode_entry_do(
+ d,
+ messaging_server_id(fsp->conn->sconn->msg_ctx),
+ fh_get_gen_id(fsp->fh),
+ remove_share_oplock_fn,
+ &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_entry_do failed\n");
+ return false;
+ }
+ if (!state.ok) {
+ DBG_DEBUG("remove_share_oplock_fn failed\n");
+ return false;
+ }
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ remove_lease_if_stale(
+ lck,
+ fsp_client_guid(fsp),
+ &fsp->lease->lease.lease_key);
+ }
+
+ share_mode_wakeup_waiters(fsp->file_id);
+
+ return true;
+}
+
+struct downgrade_share_oplock_state {
+ bool ok;
+};
+
+static void downgrade_share_oplock_fn(
+ struct share_mode_entry *e,
+ size_t num_share_modes,
+ bool *modified,
+ void *private_data)
+{
+ struct downgrade_share_oplock_state *state = private_data;
+
+ e->op_type = LEVEL_II_OPLOCK;
+ *modified = true;
+ state->ok = true;
+}
+
+bool downgrade_share_oplock(struct share_mode_lock *lck, files_struct *fsp)
+{
+ struct downgrade_share_oplock_state state = { .ok = false };
+ struct share_mode_data *d = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct file_id_buf id_buf;
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s %s - %s\n",
+ file_id_str_buf(id, &id_buf),
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return false;
+ }
+
+ ok = share_mode_entry_do(
+ d,
+ messaging_server_id(fsp->conn->sconn->msg_ctx),
+ fh_get_gen_id(fsp->fh),
+ downgrade_share_oplock_fn,
+ &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_entry_do failed\n");
+ return false;
+ }
+ if (!state.ok) {
+ DBG_DEBUG("downgrade_share_oplock_fn failed\n");
+ return false;
+ }
+
+ d->flags |= SHARE_MODE_LEASE_READ;
+ d->modified = true;
+
+ return true;
+}
+
+bool mark_share_mode_disconnected(struct share_mode_lock *lck,
+ struct files_struct *fsp)
+{
+ struct server_id disconnected_pid = { .pid = 0 };
+ bool ok;
+
+ if (fsp->op == NULL) {
+ return false;
+ }
+ if (!fsp->op->global->durable) {
+ return false;
+ }
+
+ server_id_set_disconnected(&disconnected_pid);
+
+ ok = reset_share_mode_entry(
+ lck,
+ messaging_server_id(fsp->conn->sconn->msg_ctx),
+ fh_get_gen_id(fsp->fh),
+ disconnected_pid,
+ UINT64_MAX,
+ fsp->op->global->open_persistent_id);
+
+ return ok;
+}
+
+bool reset_share_mode_entry(
+ struct share_mode_lock *lck,
+ struct server_id old_pid,
+ uint64_t old_share_file_id,
+ struct server_id new_pid,
+ uint64_t new_mid,
+ uint64_t new_share_file_id)
+{
+ struct file_id id = share_mode_lock_file_id(lck);
+ struct share_mode_data *d = NULL;
+ TDB_DATA key = locking_key(&id);
+ struct locking_tdb_data *ltdb = NULL;
+ struct share_mode_entry e;
+ struct share_mode_entry_buf e_buf;
+ NTSTATUS status;
+ int cmp;
+ bool ret = false;
+ bool ok;
+
+ status = share_mode_lock_access_private_data(lck, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id_buf id_buf;
+ /* Any error recovery possible here ? */
+ DBG_ERR("share_mode_lock_access_private_data() failed for "
+ "%s - %s\n",
+ file_id_str_buf(id, &id_buf),
+ nt_errstr(status));
+ return false;
+ }
+
+ status = locking_tdb_data_fetch(key, talloc_tos(), &ltdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("locking_tdb_data_fetch failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (ltdb->num_share_entries != 1) {
+ DBG_DEBUG("num_share_modes=%zu\n", ltdb->num_share_entries);
+ goto done;
+ }
+
+ ok = share_mode_entry_get(ltdb->share_entries, &e);
+ if (!ok) {
+ DBG_WARNING("share_mode_entry_get failed\n");
+ goto done;
+ }
+
+ cmp = share_mode_entry_cmp(
+ old_pid, old_share_file_id, e.pid, e.share_file_id);
+ if (cmp != 0) {
+ struct server_id_buf tmp1, tmp2;
+ DBG_WARNING("Expected pid=%s, file_id=%"PRIu64", "
+ "got pid=%s, file_id=%"PRIu64"\n",
+ server_id_str_buf(old_pid, &tmp1),
+ old_share_file_id,
+ server_id_str_buf(e.pid, &tmp2),
+ e.share_file_id);
+ goto done;
+ }
+
+ e.pid = new_pid;
+ if (new_mid != UINT64_MAX) {
+ e.op_mid = new_mid;
+ }
+ e.share_file_id = new_share_file_id;
+
+ ok = share_mode_entry_put(&e, &e_buf);
+ if (!ok) {
+ DBG_WARNING("share_mode_entry_put failed\n");
+ goto done;
+ }
+
+ ltdb->share_entries = e_buf.buf;
+
+ d->modified = true;
+
+ status = share_mode_data_ltdb_store(d, key, ltdb, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_data_ltdb_store failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = true;
+done:
+ TALLOC_FREE(ltdb);
+ return ret;
+}
+
+struct share_mode_do_locked_vfs_denied_state {
+ struct file_id id;
+ share_mode_do_locked_vfs_fn_t fn;
+ void *private_data;
+ const char *location;
+ NTSTATUS status;
+};
+
+static void share_mode_do_locked_vfs_denied_fn(struct g_lock_lock_cb_state *glck,
+ void *cb_private)
+{
+ struct share_mode_do_locked_vfs_denied_state *state =
+ (struct share_mode_do_locked_vfs_denied_state *)cb_private;
+ struct smb_vfs_deny_state vfs_deny = {};
+ struct share_mode_lock lck;
+
+ if (glck != NULL) {
+ current_share_mode_glck = glck;
+ }
+
+ state->status = get_share_mode_lock_internal(state->id,
+ NULL, /* servicepath */
+ NULL, /* smb_fname */
+ NULL, /* old_write_time */
+ &lck);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_GET_SHARE_MODE_LOCK(state->status,
+ "get_share_mode_lock_internal failed: %s\n",
+ nt_errstr(state->status));
+ if (glck != NULL) {
+ g_lock_lock_cb_unlock(glck);
+ current_share_mode_glck = NULL;
+ }
+ return;
+ }
+
+ _smb_vfs_deny_push(&vfs_deny, state->location);
+ state->fn(&lck, state->private_data);
+ _smb_vfs_deny_pop(&vfs_deny, state->location);
+
+ state->status = put_share_mode_lock_internal(&lck);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("put_share_mode_lock_internal failed: %s\n",
+ nt_errstr(state->status));
+ smb_panic("put_share_mode_lock_internal failed\n");
+ return;
+ }
+
+ if (glck != NULL) {
+ g_lock_lock_cb_unlock(glck);
+ current_share_mode_glck = NULL;
+ }
+ return;
+}
+
+/**
+ * @brief Run @fn protected with G_LOCK_WRITE in the given file_id
+ *
+ * @fn is NOT allowed to call SMB_VFS_* or similar functions,
+ * which may block for some time in the kernel.
+ *
+ * There must be at least one share_mode_entry, otherwise
+ * NT_STATUS_NOT_FOUND is returned.
+ *
+ * @param[in] id The key for the share_mode record.
+ * @param[in] fn The function to run under the g_lock.
+ * @param[in] private_date A private pointer passed to @fn.
+ */
+NTSTATUS _share_mode_do_locked_vfs_denied(
+ struct file_id id,
+ share_mode_do_locked_vfs_fn_t fn,
+ void *private_data,
+ const char *location)
+{
+ struct share_mode_do_locked_vfs_denied_state state = {
+ .id = id,
+ .fn = fn,
+ .private_data = private_data,
+ .location = location,
+ };
+
+ if (share_mode_lock_key_refcount == 0) {
+ TDB_DATA key = locking_key(&id);
+ NTSTATUS status;
+
+ share_mode_lock_skip_g_lock = true;
+ status = g_lock_lock(
+ lock_ctx,
+ key,
+ G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 3600 },
+ share_mode_do_locked_vfs_denied_fn,
+ &state);
+ share_mode_lock_skip_g_lock = false;
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ return state.status;
+ }
+
+ share_mode_do_locked_vfs_denied_fn(NULL, &state);
+
+ return state.status;
+}
+
+/**
+ * @brief Run @fn protected with G_LOCK_WRITE in the given file_id
+ *
+ * @fn is allowed to call SMB_VFS_* or similar functions,
+ * which may block for some time in the kernel.
+ *
+ * There must be at least one share_mode_entry, otherwise
+ * NT_STATUS_NOT_FOUND is returned.
+ *
+ * @param[in] id The key for the share_mode record.
+ * @param[in] fn The function to run under the g_lock.
+ * @param[in] private_date A private pointer passed to @fn.
+ */
+NTSTATUS _share_mode_do_locked_vfs_allowed(
+ struct file_id id,
+ share_mode_do_locked_vfs_fn_t fn,
+ void *private_data,
+ const char *location)
+{
+ struct share_mode_lock lck;
+ NTSTATUS status;
+
+ smb_vfs_assert_allowed();
+
+ status = get_share_mode_lock_internal(id,
+ NULL, /* servicepath */
+ NULL, /* smb_fname */
+ NULL, /* old_write_time */
+ &lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_GET_SHARE_MODE_LOCK(status,
+ "get_share_mode_lock_internal failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ fn(&lck, private_data);
+
+ status = put_share_mode_lock_internal(&lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("put_share_mode_lock_internal failed: %s\n",
+ nt_errstr(status));
+ smb_panic("put_share_mode_lock_internal failed\n");
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct share_mode_entry_prepare_lock_state {
+ struct file_id id;
+ const char *servicepath;
+ const struct smb_filename *smb_fname;
+ const struct timespec *old_write_time;
+ share_mode_entry_prepare_lock_fn_t fn;
+ void *private_data;
+ const char *location;
+ bool keep_locked;
+ struct share_mode_lock *lck;
+ NTSTATUS status;
+};
+
+static void share_mode_entry_prepare_lock_fn(struct g_lock_lock_cb_state *glck,
+ void *cb_private)
+{
+ struct share_mode_entry_prepare_lock_state *state =
+ (struct share_mode_entry_prepare_lock_state *)cb_private;
+ struct smb_vfs_deny_state vfs_deny = {};
+
+ SMB_ASSERT(glck != NULL);
+ current_share_mode_glck = glck;
+
+ state->status = get_share_mode_lock_internal(state->id,
+ state->servicepath,
+ state->smb_fname,
+ state->old_write_time,
+ state->lck);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ /* no DBG_GET_SHARE_MODE_LOCK here! */
+ DBG_ERR("get_share_mode_lock_internal failed: %s\n",
+ nt_errstr(state->status));
+ g_lock_lock_cb_unlock(glck);
+ current_share_mode_glck = NULL;
+ return;
+ }
+
+ _smb_vfs_deny_push(&vfs_deny, state->location);
+ state->fn(state->lck, &state->keep_locked, state->private_data);
+ _smb_vfs_deny_pop(&vfs_deny, state->location);
+
+ if (state->keep_locked) {
+ current_share_mode_glck = NULL;
+ return;
+ }
+
+ state->status = put_share_mode_lock_internal(state->lck);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("put_share_mode_lock_internal failed: %s\n",
+ nt_errstr(state->status));
+ smb_panic("put_share_mode_lock_internal failed\n");
+ return;
+ }
+
+ g_lock_lock_cb_unlock(glck);
+ current_share_mode_glck = NULL;
+ return;
+}
+
+NTSTATUS _share_mode_entry_prepare_lock(
+ struct share_mode_entry_prepare_state *prepare_state,
+ struct file_id id,
+ const char *servicepath,
+ const struct smb_filename *smb_fname,
+ const struct timespec *old_write_time,
+ share_mode_entry_prepare_lock_fn_t fn,
+ void *private_data,
+ const char *location)
+{
+ struct share_mode_entry_prepare_lock_state state = {
+ .id = id,
+ .servicepath = servicepath,
+ .smb_fname = smb_fname,
+ .old_write_time = old_write_time,
+ .fn = fn,
+ .private_data = private_data,
+ .location = location,
+ };
+ TDB_DATA key = locking_key(&id);
+ NTSTATUS status;
+
+ SMB_ASSERT(share_mode_lock_key_refcount == 0);
+
+ SMB_ASSERT(__SHARE_MODE_LOCK_SPACE >= sizeof(struct share_mode_lock));
+
+ *prepare_state = (struct share_mode_entry_prepare_state) {
+ .__fid = id,
+ .__lck_ptr = &prepare_state->__lck_space,
+ };
+
+ state.lck = prepare_state->__lck_ptr;
+
+ share_mode_lock_skip_g_lock = true;
+ status = g_lock_lock(
+ lock_ctx,
+ key,
+ G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 3600 },
+ share_mode_entry_prepare_lock_fn,
+ &state);
+ share_mode_lock_skip_g_lock = false;
+ if (!state.keep_locked) {
+ prepare_state->__lck_ptr = NULL;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ return state.status;
+}
+
+struct share_mode_entry_prepare_unlock_state {
+ struct file_id id;
+ share_mode_entry_prepare_unlock_fn_t fn;
+ void *private_data;
+ const char *location;
+ struct share_mode_lock *lck;
+ NTSTATUS status;
+};
+
+static void share_mode_entry_prepare_unlock_existing_fn(
+ struct share_mode_entry_prepare_unlock_state *state)
+{
+ if (state->fn != NULL) {
+ struct smb_vfs_deny_state vfs_deny = {};
+
+ _smb_vfs_deny_push(&vfs_deny, state->location);
+ state->fn(state->lck, state->private_data);
+ _smb_vfs_deny_pop(&vfs_deny, state->location);
+ }
+
+ state->status = put_share_mode_lock_internal(state->lck);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("put_share_mode_lock_internal failed: %s\n",
+ nt_errstr(state->status));
+ smb_panic("put_share_mode_lock_internal failed\n");
+ return;
+ }
+
+ return;
+}
+
+static void share_mode_entry_prepare_unlock_relock_fn(struct g_lock_lock_cb_state *glck,
+ void *cb_private)
+{
+ struct share_mode_entry_prepare_unlock_state *state =
+ (struct share_mode_entry_prepare_unlock_state *)cb_private;
+ struct smb_vfs_deny_state vfs_deny = {};
+
+ SMB_ASSERT(glck != NULL);
+ current_share_mode_glck = glck;
+
+ state->status = get_share_mode_lock_internal(state->id,
+ NULL, /* servicepath */
+ NULL, /* smb_fname */
+ NULL, /* old_write_time */
+ state->lck);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ /* no DBG_GET_SHARE_MODE_LOCK here! */
+ DBG_ERR("get_share_mode_lock_internal failed: %s\n",
+ nt_errstr(state->status));
+ g_lock_lock_cb_unlock(glck);
+ current_share_mode_glck = NULL;
+ return;
+ }
+
+ _smb_vfs_deny_push(&vfs_deny, state->location);
+ state->fn(state->lck, state->private_data);
+ _smb_vfs_deny_pop(&vfs_deny, state->location);
+
+ state->status = put_share_mode_lock_internal(state->lck);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_ERR("put_share_mode_lock_internal failed: %s\n",
+ nt_errstr(state->status));
+ smb_panic("put_share_mode_lock_internal failed\n");
+ return;
+ }
+
+ g_lock_lock_cb_unlock(glck);
+ current_share_mode_glck = NULL;
+ return;
+}
+
+NTSTATUS _share_mode_entry_prepare_unlock(
+ struct share_mode_entry_prepare_state *prepare_state,
+ share_mode_entry_prepare_unlock_fn_t fn,
+ void *private_data,
+ const char *location)
+{
+ struct share_mode_entry_prepare_unlock_state state = {
+ .id = prepare_state->__fid,
+ .fn = fn,
+ .private_data = private_data,
+ .location = location,
+ };
+ TDB_DATA key = locking_key(&prepare_state->__fid);
+ NTSTATUS status;
+
+ if (prepare_state->__lck_ptr != NULL) {
+ /*
+ * With an existing lock, we just run the unlock prepare
+ * function following by the unlock.
+ */
+
+ SMB_ASSERT(share_mode_lock_key_refcount == 1);
+
+ state.lck = prepare_state->__lck_ptr;
+ prepare_state->__lck_ptr = NULL;
+
+ share_mode_entry_prepare_unlock_existing_fn(&state);
+ return state.status;
+ }
+
+ /*
+ * No existing lock, which means
+ * _share_mode_entry_prepare_lock() didn't steal
+ * the lock...
+ */
+ SMB_ASSERT(share_mode_lock_key_refcount == 0);
+
+ if (fn == NULL) {
+ /*
+ * Without an existing lock and without
+ * a prepare function there's nothing to
+ * do...
+ */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * In order to run the unlock prepare function
+ * we need to relock the entry.
+ */
+ state.lck = &prepare_state->__lck_space;
+
+ share_mode_lock_skip_g_lock = true;
+ status = g_lock_lock(
+ lock_ctx,
+ key,
+ G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 3600 },
+ share_mode_entry_prepare_unlock_relock_fn,
+ &state);
+ share_mode_lock_skip_g_lock = false;
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ return state.status;
+}
diff --git a/source3/locking/share_mode_lock.h b/source3/locking/share_mode_lock.h
new file mode 100644
index 0000000..eb74144
--- /dev/null
+++ b/source3/locking/share_mode_lock.h
@@ -0,0 +1,202 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LOCKING_SHARE_MODE_LOCK_H__
+#define __LOCKING_SHARE_MODE_LOCK_H__
+
+#include "replace.h"
+#include <tevent.h>
+#include "librpc/gen_ndr/file_id.h"
+#include "lib/util/time.h"
+
+struct share_mode_data;
+struct share_mode_lock;
+struct share_mode_entry;
+struct smb_filename;
+struct files_struct;
+struct smb2_lease_key;
+
+bool locking_init(void);
+bool locking_init_readonly(void);
+bool locking_end(void);
+
+struct file_id share_mode_lock_file_id(const struct share_mode_lock *lck);
+
+struct share_mode_lock *get_existing_share_mode_lock(TALLOC_CTX *mem_ctx,
+ struct file_id id);
+
+bool del_share_mode_open_id(struct share_mode_lock *lck,
+ struct server_id open_pid,
+ uint64_t open_file_id);
+bool del_share_mode(struct share_mode_lock *lck,
+ struct files_struct *fsp);
+bool downgrade_share_oplock(struct share_mode_lock *lck,
+ struct files_struct *fsp);
+bool remove_share_oplock(struct share_mode_lock *lck,
+ struct files_struct *fsp);
+bool file_has_read_lease(struct files_struct *fsp);
+
+bool set_share_mode(
+ struct share_mode_lock *lck,
+ struct files_struct *fsp,
+ uid_t uid,
+ uint64_t mid,
+ uint16_t op_type,
+ const struct smb2_lease_key *lease_key,
+ uint32_t share_access,
+ uint32_t access_mask);
+bool reset_share_mode_entry(
+ struct share_mode_lock *lck,
+ struct server_id old_pid,
+ uint64_t old_share_file_id,
+ struct server_id new_pid,
+ uint64_t new_mid,
+ uint64_t new_share_file_id);
+
+bool mark_share_mode_disconnected(
+ struct share_mode_lock *lck, struct files_struct *fsp);
+
+struct share_mode_lock *fetch_share_mode_unlocked(
+ TALLOC_CTX *mem_ctx,
+ struct file_id id);
+
+struct tevent_req *fetch_share_mode_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct file_id id,
+ bool *queued);
+NTSTATUS fetch_share_mode_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct share_mode_lock **_lck);
+
+int share_entry_forall(
+ int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ const struct share_mode_entry *entry,
+ void *private_data),
+ void *private_data);
+
+NTSTATUS share_mode_count_entries(struct file_id fid, size_t *num_share_modes);
+int share_mode_forall(
+ int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data),
+ void *private_data);
+bool share_mode_forall_entries(
+ struct share_mode_lock *lck,
+ bool (*fn)(struct share_mode_entry *e,
+ bool *modified,
+ void *private_data),
+ void *private_data);
+
+NTTIME share_mode_changed_write_time(struct share_mode_lock *lck);
+void share_mode_set_changed_write_time(struct share_mode_lock *lck, struct timespec write_time);
+void share_mode_set_old_write_time(struct share_mode_lock *lck, struct timespec write_time);
+const char *share_mode_servicepath(struct share_mode_lock *lck);
+char *share_mode_filename(TALLOC_CTX *mem_ctx, struct share_mode_lock *lck);
+char *share_mode_data_dump(
+ TALLOC_CTX *mem_ctx, struct share_mode_lock *lck);
+
+void share_mode_flags_get(
+ struct share_mode_lock *lck,
+ uint32_t *access_mask,
+ uint32_t *share_mode,
+ uint32_t *lease_type);
+void share_mode_flags_set(
+ struct share_mode_lock *lck,
+ uint32_t access_mask,
+ uint32_t share_mode,
+ uint32_t lease_type,
+ bool *modified);
+
+struct tevent_req *share_mode_watch_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct share_mode_lock *lck,
+ struct server_id blocker);
+NTSTATUS share_mode_watch_recv(
+ struct tevent_req *req, bool *blockerdead, struct server_id *blocker);
+NTSTATUS share_mode_wakeup_waiters(struct file_id id);
+
+typedef void (*share_mode_do_locked_vfs_fn_t)(
+ struct share_mode_lock *lck,
+ void *private_data);
+NTSTATUS _share_mode_do_locked_vfs_denied(
+ struct file_id id,
+ share_mode_do_locked_vfs_fn_t fn,
+ void *private_data,
+ const char *location);
+#define share_mode_do_locked_vfs_denied(__id, __fn, __private_data) \
+ _share_mode_do_locked_vfs_denied(__id, __fn, __private_data, __location__)
+NTSTATUS _share_mode_do_locked_vfs_allowed(
+ struct file_id id,
+ share_mode_do_locked_vfs_fn_t fn,
+ void *private_data,
+ const char *location);
+#define share_mode_do_locked_vfs_allowed(__id, __fn, __private_data) \
+ _share_mode_do_locked_vfs_allowed(__id, __fn, __private_data, __location__)
+
+struct share_mode_entry_prepare_state {
+ struct file_id __fid;
+ struct share_mode_lock *__lck_ptr;
+ union {
+#define __SHARE_MODE_LOCK_SPACE 32
+ uint8_t __u8_space[__SHARE_MODE_LOCK_SPACE];
+#ifdef SHARE_MODE_ENTRY_PREPARE_STATE_LCK_SPACE
+ struct share_mode_lock __lck_space;
+#endif
+ };
+};
+
+typedef void (*share_mode_entry_prepare_lock_fn_t)(
+ struct share_mode_lock *lck,
+ bool *keep_locked,
+ void *private_data);
+NTSTATUS _share_mode_entry_prepare_lock(
+ struct share_mode_entry_prepare_state *prepare_state,
+ struct file_id id,
+ const char *servicepath,
+ const struct smb_filename *smb_fname,
+ const struct timespec *old_write_time,
+ share_mode_entry_prepare_lock_fn_t fn,
+ void *private_data,
+ const char *location);
+#define share_mode_entry_prepare_lock_add(__prepare_state, __id, \
+ __servicepath, __smb_fname, __old_write_time, \
+ __fn, __private_data) \
+ _share_mode_entry_prepare_lock(__prepare_state, __id, \
+ __servicepath, __smb_fname, __old_write_time, \
+ __fn, __private_data, __location__);
+#define share_mode_entry_prepare_lock_del(__prepare_state, __id, \
+ __fn, __private_data) \
+ _share_mode_entry_prepare_lock(__prepare_state, __id, \
+ NULL, NULL, NULL, \
+ __fn, __private_data, __location__);
+
+typedef void (*share_mode_entry_prepare_unlock_fn_t)(
+ struct share_mode_lock *lck,
+ void *private_data);
+NTSTATUS _share_mode_entry_prepare_unlock(
+ struct share_mode_entry_prepare_state *prepare_state,
+ share_mode_entry_prepare_unlock_fn_t fn,
+ void *private_data,
+ const char *location);
+#define share_mode_entry_prepare_unlock(__prepare_state, \
+ __fn, __private_data) \
+ _share_mode_entry_prepare_unlock(__prepare_state, \
+ __fn, __private_data, __location__);
+
+#endif
diff --git a/source3/locking/share_mode_lock_private.h b/source3/locking/share_mode_lock_private.h
new file mode 100644
index 0000000..a89a8fb
--- /dev/null
+++ b/source3/locking/share_mode_lock_private.h
@@ -0,0 +1,25 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LOCKING_SHARE_MODE_LOCK_PRIVATE_H__
+#define __LOCKING_SHARE_MODE_LOCK_PRIVATE_H__
+
+struct share_mode_lock;
+struct share_mode_data;
+
+NTSTATUS share_mode_lock_access_private_data(struct share_mode_lock *lck,
+ struct share_mode_data **data);
+
+#endif
diff --git a/source3/mainpage.dox b/source3/mainpage.dox
new file mode 100644
index 0000000..8b72f80
--- /dev/null
+++ b/source3/mainpage.dox
@@ -0,0 +1,7 @@
+/**
+
+@mainpage
+
+@li \ref CodingSuggestions
+
+**/
diff --git a/source3/modules/README-gpfs-acl.txt b/source3/modules/README-gpfs-acl.txt
new file mode 100644
index 0000000..49f3259
--- /dev/null
+++ b/source3/modules/README-gpfs-acl.txt
@@ -0,0 +1,82 @@
+This patch has been taken against SAMBA_3_0 Release 20028.
+
+The patch basically moves the GPFS-ACL functionalities into the new GPFS VFS module( vfs_gpfs ).
+
+Please read SAMBA_3_0/source/modules/README.nfs4acls.txt - generalised README file on Samba support for NFS4-ACLS.
+This README file is specific for GPFS only.
+
+Configuring GPFS ACL support
+===============================
+
+Binary: (default install path is [samba]/lib/vfs/)
+- gpfs.so
+
+Its compiled by default, no additional configure option needed.
+
+To enable/use/load this module, include "vfs objects = gpfs" in the smb.conf file under concerned share-name.
+
+Example of smb.conf:
+
+[smbtest]
+path = /gpfs-test
+vfs objects = gpfs
+nfs4: mode = special
+nfs4: chown = yes
+nfs4: acedup = merge
+
+Adding "vfs objects = gpfs" to a share should be done only in case when NFS4 is really supported by the filesystem.
+(Otherwise you may get performance loss.)
+
+==================================================
+Below are some limitations listed for this module:
+==================================================
+1. When a child file or child directory is created, the results are a bit different from windows as specified below:
+
+Eg: Prent directory is set to have 2 ACES - full access for owner and everyone
+
+Default ACL for Windows: 2 aces: allow ACE for owner and everyone
+Default ACL for GPFS: 6 aces: allow and deny ACEs for owner, group and everyone
+
+The below mentioned inheritance flags and its combinations are applied only to the owner ACE and not to everyone ACE
+
+"fi"------>File Inherit
+"di"------>Directory Inherit
+"oi"------>Inherit Only
+
+
+Parent dir: no inheritance flag set
+ Windows: index=0: GPFS(special mode): index=0: GPFS(simple mode): index=0:
+child File: default acl: 2 aces child file: default acl: 6 aces child file: default acl: 6 aces
+child dir: default acl: 2 aces child dir: default acl: 6 aces child dir: default acl: 6 aces
+
+
+Parent dir: "fi" flag set
+ Windows: index=1: GPFS(special mode): index=1: GPFS(simple mode): index=1:
+child file: no flag set child file: "fi" flag set child file: default acl: 6 aces
+child dir: "fioi" flag set child dir: "fi" flag set child dir: "fi" flag set
+
+
+Parent dir: "di" flag set
+ Windows: index=2: GPFS(special mode): index=2: GPFS(simple mode): index=2:
+child file: default acl: 2 aces child file: default acl: 6 aces child file: default acl: 6 aces
+
+
+Parent dir: "fidi" flag set
+ Windows: index=3: GPFS(special mode): index=3: GPFS(simple mode): index=3:
+child file: no flag set child file: "fidi" flag set child file: default acl: 6 aces
+
+
+Parent dir: "fioi" flag set
+ Windows: index=4: GPFS(special mode): index=4: GPFS(simple mode): index=4:
+child file: no flag set child file: "fi" flag set child file: default acl: 6 aces
+child dir: "fioi" flag set child dir: "fi" flag set child dir: "fi" flag set
+
+
+Parent dir: "dioi" flag set
+ Windows: index=5: GPFS(special mode): index=5: GPFS(simple mode): index=5:
+child file: default acl: 2 aces child file: default acl: 6 aces child file: default acl: 6 aces
+
+
+Parent dir: "fidioi" flag set
+ Windows: index=6: GPFS(special mode): index=6: GPFS(simple mode): index=6:
+child file: no flag set child file: "fidi" flag set child file: default acl: 6 aces
diff --git a/source3/modules/README.nfs4acls.txt b/source3/modules/README.nfs4acls.txt
new file mode 100644
index 0000000..6de87a1
--- /dev/null
+++ b/source3/modules/README.nfs4acls.txt
@@ -0,0 +1,89 @@
+Configuring NFS4 ACLs in Samba3
+===============================
+Created: Peter Somogyi, 2006-JUN-06
+Last modified: Alexander Werth, 2013-MAY-02
+Revision no.: 4
+-------------------------------
+
+
+Parameters in smb.conf:
+=======================
+
+Each parameter must have a prefix "nfs4:".
+Each one affects the behaviour only when _setting_ an acl on a file/dir:
+
+mode = [simple|special]
+- simple: Use OWNER@ and GROUP@ special IDs for non inheriting ACEs only.
+ This mode is the default.
+- special: use OWNER@ and GROUP@ special IDs in ACEs instead of simple
+ user&group ids. This mode is deprecated.
+
+Note1: EVERYONE@ is always processed (if found such an ACE).
+Note2: There is a side effect when _only_ chown is performed.
+ Later this may be worked out.
+Note3: Mode special inherits incorrect ACL entries when the user creating
+ a file is different from the owner of the caurrent folder.
+Note4: Mode simple uses inheriting OWNER@ and GROUP@ special IDs to
+ support Creator Owner and Creator Group.
+
+It's strongly advised to set "store dos attributes = yes" in smb.conf.
+
+chown = [true|false]
+- true => enable changing owner and group - default.
+- false => disable support for changing owner or group
+
+acedup = [dontcare|reject|ignore|merge]
+- dontcare: copy ACEs as they come, don't care with "duplicate" records.
+- reject: stop operation, exit acl setter operation with an error. (deprecated)
+- ignore: don't include the second matching ACE. (deprecated)
+- merge: OR 2 ace.flag fields and 2 ace.mask fields of the 2 duplicate ACEs into 1 ACE (default)
+
+Two ACEs are considered here "duplicate" when their type and id fields are matching.
+
+Example:
+
+[smbtest]
+path = /tests/psomogyi/smbtest
+writable = yes
+vfs objects = aixacl2
+nfs4: mode = special
+nfs4: chown = yes
+nfs4: acedup = merge
+
+Configuring AIX ACL support
+==============================
+
+Binaries: (default install path is [samba]/lib/vfs/)
+- aixacl.so: provides AIXC ACL support only, can be compiled and works on all AIX platforms
+- aixacl2.so: provides AIXC and JFS2-NFS4 ACL support, can be compiled and works only under AIX 5.3 and newer.
+NFS4 acl currently has support only under JFS2 (ext. attr. format must be set to v2).
+aixacl2.so always detects support for NFS4 acls and redirects to POSIX ACL handling automatically when NFS4 is not supported for a path.
+
+Adding "vfs objects = aixacl2" to a share should be done only in case when NFS4 is really supported by the filesystem.
+(Otherwise you may get performance loss.)
+
+For configuration see also the example above.
+
+General notes
+=============
+
+NFS4 handling logic is separated from AIX/jfs2 ACL parsing.
+
+Samba and its VFS modules don't reorder ACEs. Windows clients do that (and the smbcacl tool). MSDN also says deny ACEs must come first.
+NFS4 ACL's validity is checked by the system API, not by Samba.
+NFS4 ACL rights are enforced by the OS or filesystem, not by Samba.
+
+The flag INHERITED_ACE is never set (not required, as doesn't do WinNT/98/me, only since Win2k).
+Win2k GUI behaves strangely when detecting inheritance (sometimes it doesn't detect,
+but after adding an ace it shows that - it's some GUI error).
+
+Unknown (unmappable) SIDs are not accepted.
+
+TODOs
+=====
+- Creator Owner & Group SID handling (same way as posix)
+- the 4 generic rights bits support (GENERIC_RIGHT_READ_ACCESS, WRITE, EXEC, ALL)
+- chown & no ACL, but we have OWNER@ and GROUP@
+- DIALUP, ANONYMOUS, ... builtin SIDs
+- audit & alarm support - in theory it's forwarded so it should work, but currently there's no platform which supports them to test
+- support for a real NFS4 client (we don't have an accepted API yet)
diff --git a/source3/modules/The_New_VFS.org b/source3/modules/The_New_VFS.org
new file mode 100644
index 0000000..2f6a84c
--- /dev/null
+++ b/source3/modules/The_New_VFS.org
@@ -0,0 +1,469 @@
+#+TITLE: The New Samba VFS
+#+AUTHOR: Ralph Böhme, SerNet, Samba Team
+#+DATE: {{{modification-time(%Y-%m-%d)}}}
+* The new VFS
+** Summary
+The effort to modernize Samba's VFS interface has reached a major milestone with
+the next release Samba 4.14.
+
+Starting with version 4.14 Samba provides core infrastructure code that allows
+basing all access to the server's filesystem on file handles and not on
+paths. An example of this is using =fstat()= instead of =stat()=, or
+=SMB_VFS_FSTAT()= instead of =SMB_VFS_STAT()= in Samba parlance.
+
+Historically Samba's fileserver code had to deal a lot with processing path
+based SMB requests. While the SMB protocol itself has been streamlined to be
+purely handle based starting with SMB2, large parts of infrastructure code
+remains in place that will "degrade" handle based SMB2 requests to path based
+filesystem access.
+
+In order to fully leverage the handle based nature of the SMB2 protocol we came
+up with a straight forward way to convert this infrastructure code.
+
+At the core, we introduced a helper function that opens a file handle that only
+serves as a path reference and hence can not be used for any sort of access to
+file data.
+
+Samba's internal file handle structure is of type =struct files_struct= and all
+variable pointing to objects of such type are typically called =fsp=. Until very
+recently the only function that would open such a file handle and return an fsp
+was =SMB_VFS_CREATE_FILE()=.
+
+Internally =SMB_VFS_CREATE_FILE()= consisted of processing through Samba's VFS
+open function to open the low level file and then going through Samba's Windows
+NTFS emulation code.
+
+The key point of the new helper function which is called =openat_pathref_fsp()=
+is that it skips the NTFS emulation logic. Additionally, the handle is
+restricted internally to be only usable as a path reference but not for any sort
+of IO. On Linux this is achieved by using the =O_PATH= =open()= flag, on systems
+without =O_PATH= support other mechanisms are used described in more detail
+below.
+
+Path processing in Samba typically means processing client supplied paths by
+Samba's core path processing function =filename_convert()= which returns a
+pointer to an object of type =struct smb_filename=. Pointers to such objects are
+then passed around, often passing many layers of code.
+
+By attaching an =fsp= file handle returned from =openat_pathref_fsp()= to all
+=struct smb_filename= objects returned from =filename_convert()=, the whole
+infrastructure code has immediate access to a file handle and so the large
+infrastructure codebase can be converted to use handle based VFS functions
+whenever VFS access is done in a piecemeal fashion.
+** Samba and O_PATH
+*** Background
+ On Linux the =O_PATH= flag to =open()= can be used to open a filehandle on a
+ file or directory with interesting properties: [fn:manpage]
+
+ - the file-handle indicates a location in the filesystem tree,
+
+ - no permission checks are done by the kernel on the filesystem object and
+
+ - only operations that act purely at the file descriptor level are allowed.
+
+ The file itself is not opened, and other file operations (e.g., ~read(2)~,
+ ~write(2)~, ~fchmod(2)~, ~fchown(2)~, ~fgetxattr(2)~, ~ioctl(2)~, ~mmap(2)~) fail
+ with the error ~EBADF~.
+
+ The following subset of operations that is relevant to Samba is allowed:
+
+ - ~close(2)~,
+
+ - ~fchdir(2)~, if the file descriptor refers to a directory,
+
+ - ~fstat(2)~,
+
+ - ~fstatfs(2)~ and
+
+ - passing the file descriptor as the dirfd argument of ~openat()~ and the other
+ "*at()" system calls. This includes ~linkat(2)~ with AT_EMPTY_PATH (or via
+ procfs using AT_SYMLINK_FOLLOW) even if the file is not a directory.
+
+ Opening a file or directory with the ~O_PATH~ flag requires no permissions
+ on the object itself (but does require execute permission on the
+ directories in the path prefix). By contrast, obtaining a reference to a
+ filesystem object by opening it with the ~O_RDONLY~ flag requires that the
+ caller have read permission on the object, even when the subsequent
+ operation (e.g., ~fchdir(2)~, ~fstat(2)~) does not require read permis‐
+ sion on the object.
+
+ If for example Samba receives an SMB request to open a file requesting
+ ~SEC_FILE_READ_ATTRIBUTE~ access rights because the client wants to read the
+ file's metadata from the handle, Samba will have to call ~open()~ with at least
+ ~O_RDONLY~ access rights.
+*** Usecases for O_PATH in Samba
+ The ~O_PATH~ flag is currently not used in Samba. By leveraging this Linux
+ specific flags we can avoid permission mismatches as described above.
+
+ Additionally ~O_PATH~ allows basing all filesystem accesses done by the
+ fileserver on handle based syscalls by opening all client pathnames with
+ ~O_PATH~ and consistently using for example ~fstat()~ instead of ~stat()~
+ throughout the codebase.
+
+ Subsequent parts of this document will call such file-handles opened with O_PATH
+ *path referencing file-handles* or *pathref*s for short.
+
+*** When to open with O_PATH
+ In Samba the decision whether to call POSIX ~open()~ on a client pathname or
+ whether to leave the low-level handle at -1 (what we call a stat-open) is based
+ on the client requested SMB access mask.
+
+ The set of access rights that trigger an ~open()~ includes
+ ~READ_CONTROL_ACCESS~. As a result, the open() will be done with at least
+ ~O_RDONLY~. If the filesystem supports NT style ACLs natively (like GPFS or ZFS),
+ the filesystem may grant the user requested right ~READ_CONTROL_ACCESS~, but it
+ may not grant ~READ_DATA~ (~O_RDONLY~).
+
+ Currently the full set of access rights that trigger opening a file is:
+
+ - FILE_READ_DATA
+ - FILE_WRITE_DATA
+ - FILE_APPEND_DATA
+ - FILE_EXECUTE
+ - WRITE_DAC_ACCESS
+ - WRITE_OWNER_ACCESS
+ - SEC_FLAG_SYSTEM_SECURITY
+ - READ_CONTROL_ACCESS
+
+ In the future we can remove the following rights from the list on systems that
+ support O_PATH:
+
+ - WRITE_DAC_ACCESS
+ - WRITE_OWNER_ACCESS
+ - SEC_FLAG_SYSTEM_SECURITY
+ - READ_CONTROL_ACCESS
+*** Fallback on systems without O_PATH support
+ The code of higher level file-handle consumers must be kept simple and
+ streamlined, avoiding special casing the handling of the file-handles opened
+ with or without ~O_PATH~. To achieve this, a fallback that allows opening a
+ file-handle with the same higher level semantics even if the system doesn't
+ support ~O_PATH~ is needed.
+
+ The way this is implemented on such systems is impersonating the root user for
+ the ~open()~ syscall. In order to avoid privilege escalations security issues,
+ we must carefully control the use these file-handles.
+
+ The low level filehandle is stored in a public struct ~struct file_handle~ that
+ is part of the widely used ~struct files_struct~. Consumers used to simply
+ access the fd directly by dereferencing pointers to ~struct files_struct~.
+
+ In order to guard access to such file-handles we do two things:
+
+ - tag the pathref file-handles and
+
+ - control access to the file-handle by making the structure ~struct
+ file_handle~ private, only allowing access with accessor functions that
+ implement a security boundary.
+
+ In order to avoid bypassing restrictive permissions on intermediate directories
+ of a client path, the root user is only impersonated after changing directory
+ to the parent directory of the client requested pathname.
+
+ Two functions can then be used to fetch the low-level system file-handle from a
+ ~struct files_struct~:
+
+ - ~fsp_get_io_fd(fsp)~: enforces fsp is NOT a pathref file-handle and
+
+ - ~fsp_get_pathref_fd(fsp)~: allows fsp to be either a pathref file-handle or a
+ traditional POSIX file-handle opened with O_RDONLY or any other POSIX open
+ flag.
+
+ Note that the name ~fsp_get_pathref_fd()~ may sound confusing at first given
+ that the fsp can be either a pathref fsp or a "normal/full" fsp, but as any
+ full file-handle can be used for IO and as path reference, the name
+ correctly reflects the intended usage of the caller.
+*** When to use fsp_get_io_fd() or fsp_get_pathref_fd()
+
+ The general guideline is:
+
+ - if you do something like ~fstat(fd)~, use ~fsp_get_pathref_fd()~,
+
+ - if you do something like ~*at(dirfd, ...)~, use ~fsp_get_pathref_fd()~,
+
+ - if you want to print the fd for example in =DEBUG= messages, use ~fsp_get_pathref_fd()~,
+
+ - if you want to call ~close(fd)~, use ~fsp_get_pathref_fd()~,
+
+ - if you're doing a logical comparison of fd values, use ~fsp_get_pathref_fd()~.
+
+ In any other case use ~fsp_get_io_fd()~.
+
+[fn:manpage] parts of the following sections copied from man open(2)
+[fn:gitlab] https://gitlab.com/samba-team/devel/samba/-/commits/slow-pathref-wip
+
+* VFS status quo and remaining work
+** VFS Functions Tables [fn:VFS_API]
+*** Existing VFS Functions
+#+ATTR_HTML: :border 1 :rules all :frame border
+| VFS Function | Group | Status |
+|-----------------------------------+----------+--------|
+| SMB_VFS_AIO_FORCE() | [[fsp][fsp]] | - |
+| SMB_VFS_AUDIT_FILE() | [[Special][Special]] | - |
+| SMB_VFS_BRL_LOCK_WINDOWS() | [[fsp][fsp]] | - |
+| SMB_VFS_BRL_UNLOCK_WINDOWS() | [[fsp][fsp]] | - |
+| SMB_VFS_CHDIR() | [[Path][Path]] | Todo |
+| SMB_VFS_CHFLAGS() | [[Path][Path]] | - |
+| SMB_VFS_CHMOD() | [[Path][Path]] | - |
+| SMB_VFS_CLOSE() | [[fsp][fsp]] | - |
+| SMB_VFS_CLOSEDIR() | [[fsp][fsp]] | - |
+| SMB_VFS_CONNECT() | [[Disk][Disk]] | - |
+| SMB_VFS_CONNECTPATH() | [[P2px][P2px]] | - |
+| SMB_VFS_CREATE_DFS_PATHAT() | [[NsC][NsC]] | - |
+| SMB_VFS_CREATE_FILE() | [[NsC][NsC]] | - |
+| SMB_VFS_DISCONNECT() | [[Disk][Disk]] | - |
+| SMB_VFS_DISK_FREE() | [[Disk][Disk]] | - |
+| SMB_VFS_DURABLE_COOKIE() | [[fsp][fsp]] | - |
+| SMB_VFS_DURABLE_DISCONNECT() | [[fsp][fsp]] | - |
+| SMB_VFS_DURABLE_RECONNECT() | [[fsp][fsp]] | - |
+| SMB_VFS_FALLOCATE() | [[fsp][fsp]] | - |
+| SMB_VFS_FCHMOD() | [[fsp][fsp]] | - |
+| SMB_VFS_FCHOWN() | [[fsp][fsp]] | - |
+| SMB_VFS_FCNTL() | [[fsp][fsp]] | - |
+| SMB_VFS_FDOPENDIR() | [[fsp][fsp]] | - |
+| SMB_VFS_FGET_COMPRESSION() | [[fsp][fsp]] | - |
+| SMB_VFS_FGET_DOS_ATTRIBUTES() | [[fsp][fsp]] | - |
+| SMB_VFS_FGET_NT_ACL() | [[fsp][fsp]] | - |
+| SMB_VFS_FGETXATTR() | [[xpathref][xpathref]] | - |
+| SMB_VFS_FILE_ID_CREATE() | [[Special][Special]] | - |
+| SMB_VFS_FLISTXATTR() | [[xpathref][xpathref]] | - |
+| SMB_VFS_FREMOVEXATTR() | [[xpathref][xpathref]] | - |
+| SMB_VFS_FS_CAPABILITIES() | [[Disk][Disk]] | - |
+| SMB_VFS_FSCTL() | [[fsp][fsp]] | - |
+| SMB_VFS_FSET_DOS_ATTRIBUTES() | [[fsp][fsp]] | - |
+| SMB_VFS_FSET_NT_ACL() | [[fsp][fsp]] | - |
+| SMB_VFS_FSETXATTR() | [[xpathref][xpathref]] | - |
+| SMB_VFS_FS_FILE_ID() | [[Special][Special]] | - |
+| SMB_VFS_FSTAT() | [[fsp][fsp]] | - |
+| SMB_VFS_FSYNC() | [[fsp][fsp]] | - |
+| SMB_VFS_FSYNC_SEND() | [[fsp][fsp]] | - |
+| SMB_VFS_FTRUNCATE() | [[fsp][fsp]] | - |
+| SMB_VFS_GET_ALLOC_SIZE() | [[fsp][fsp]] | - |
+| SMB_VFS_GET_DFS_REFERRALS() | [[Disk][Disk]] | - |
+| SMB_VFS_GET_DOS_ATTRIBUTES_RECV() | [[Enum][Enum]] | - |
+| SMB_VFS_GET_DOS_ATTRIBUTES_SEND() | [[Enum][Enum]] | - |
+| SMB_VFS_GETLOCK() | [[fsp][fsp]] | - |
+| SMB_VFS_GET_NT_ACL_AT() | [[Path][Path]] | - |
+| SMB_VFS_GET_QUOTA() | [[Special][Special]] | - |
+| SMB_VFS_GET_REAL_FILENAME() | [[P2px][P2px]] | - |
+| SMB_VFS_GET_SHADOW_COPY_DATA() | [[fsp][fsp]] | - |
+| SMB_VFS_GETWD() | [[Special][Special]] | - |
+| SMB_VFS_GETXATTR() | [[Path][Path]] | - |
+| SMB_VFS_GETXATTRAT_RECV() | [[Enum][Enum]] | - |
+| SMB_VFS_GETXATTRAT_SEND() | [[Enum][Enum]] | - |
+| SMB_VFS_FILESYSTEM_SHAREMODE() | [[fsp][fsp]] | - |
+| SMB_VFS_LCHOWN() | [[Path][Path]] | Todo |
+| SMB_VFS_LINKAT() | [[NsC][NsC]] | - |
+| SMB_VFS_LINUX_SETLEASE() | [[fsp][fsp]] | - |
+| SMB_VFS_LISTXATTR() | [[Path][Path]] | - |
+| SMB_VFS_LOCK() | [[fsp][fsp]] | - |
+| SMB_VFS_LSEEK() | [[fsp][fsp]] | - |
+| SMB_VFS_LSTAT() | [[Path][Path]] | Todo |
+| SMB_VFS_MKDIRAT() | [[NsC][NsC]] | - |
+| SMB_VFS_MKNODAT() | [[NsC][NsC]] | - |
+| SMB_VFS_NTIMES() | [[Path][Path]] | - |
+| SMB_VFS_OFFLOAD_READ_RECV() | [[fsp][fsp]] | - |
+| SMB_VFS_OFFLOAD_READ_SEND() | [[fsp][fsp]] | - |
+| SMB_VFS_OFFLOAD_WRITE_RECV() | [[fsp][fsp]] | - |
+| SMB_VFS_OFFLOAD_WRITE_SEND() | [[fsp][fsp]] | - |
+| SMB_VFS_OPENAT() | [[NsC][NsC]] | - |
+| SMB_VFS_PREAD() | [[fsp][fsp]] | - |
+| SMB_VFS_PREAD_SEND() | [[fsp][fsp]] | - |
+| SMB_VFS_PWRITE() | [[fsp][fsp]] | - |
+| SMB_VFS_PWRITE_SEND() | [[fsp][fsp]] | - |
+| SMB_VFS_READ_DFS_PATHAT() | [[Symlink][Symlink]] | - |
+| SMB_VFS_READDIR() | [[fsp][fsp]] | - |
+| SMB_VFS_READDIR_ATTR() | [[Path][Path]] | - |
+| SMB_VFS_READLINKAT() | [[Symlink][Symlink]] | - |
+| SMB_VFS_REALPATH() | [[P2px][P2px]] | - |
+| SMB_VFS_RECVFILE() | [[fsp][fsp]] | - |
+| SMB_VFS_REMOVEXATTR() | [[Path][Path]] | - |
+| SMB_VFS_RENAMEAT() | [[Path][Path]] | ---- |
+| SMB_VFS_REWINDDIR() | [[fsp][fsp]] | - |
+| SMB_VFS_SENDFILE() | [[fsp][fsp]] | - |
+| SMB_VFS_SET_COMPRESSION() | [[fsp][fsp]] | - |
+| SMB_VFS_SET_DOS_ATTRIBUTES() | [[Path][Path]] | - |
+| SMB_VFS_SET_QUOTA() | [[Special][Special]] | - |
+| SMB_VFS_SETXATTR() | [[Path][Path]] | - |
+| SMB_VFS_SNAP_CHECK_PATH() | [[Disk][Disk]] | - |
+| SMB_VFS_SNAP_CREATE() | [[Disk][Disk]] | - |
+| SMB_VFS_SNAP_DELETE() | [[Disk][Disk]] | - |
+| SMB_VFS_STAT() | [[Path][Path]] | Todo |
+| SMB_VFS_STATVFS() | [[Disk][Disk]] | - |
+| SMB_VFS_STREAMINFO() | [[Path][Path]] | - |
+| SMB_VFS_STRICT_LOCK_CHECK() | [[fsp][fsp]] | - |
+| SMB_VFS_SYMLINKAT() | [[NsC][NsC]] | - |
+| SMB_VFS_SYS_ACL_BLOB_GET_FD() | [[xpathref][xpathref]] | - |
+| SMB_VFS_SYS_ACL_BLOB_GET_FILE() | [[Path][Path]] | - |
+| SMB_VFS_SYS_ACL_DELETE_DEF_FILE() | [[Path][Path]] | - |
+| SMB_VFS_SYS_ACL_GET_FD() | [[xpathref][xpathref]] | - |
+| SMB_VFS_SYS_ACL_GET_FILE() | [[Path][Path]] | - |
+| SMB_VFS_SYS_ACL_SET_FD() | [[xpathref][xpathref]] | - |
+| SMB_VFS_TRANSLATE_NAME() | [[P2px][P2px]] | - |
+| SMB_VFS_UNLINKAT() | [[NsC][NsC]] | - |
+|-----------------------------------+----------+--------|
+
+*** New VFS Functions
+#+ATTR_HTML: :border 1 :rules all :frame border
+| VFS Function | Group | Status |
+|---------------------------------+----------+--------|
+| SMB_VFS_SYS_ACL_DELETE_DEF_FD() | [[xpathref][xpathref]] | - |
+| SMB_VFS_FNTIMENS() | [[fsp][fsp]] | - |
+|---------------------------------+----------+--------|
+
+** VFS functions by category
+*** Disk operations <<Disk>>
+ - SMB_VFS_CONNECT()
+ - SMB_VFS_DISCONNECT()
+ - SMB_VFS_DISK_FREE()
+ - SMB_VFS_FS_CAPABILITIES()
+ - SMB_VFS_GET_DFS_REFERRALS()
+ - SMB_VFS_SNAP_CHECK_PATH()
+ - SMB_VFS_SNAP_CREATE()
+ - SMB_VFS_SNAP_DELETE()
+ - SMB_VFS_STATVFS()
+
+ No changes needed.
+*** Handle based VFS functions <<fsp>>
+ - SMB_VFS_AIO_FORCE()
+ - SMB_VFS_BRL_LOCK_WINDOWS()
+ - SMB_VFS_BRL_UNLOCK_WINDOWS()
+ - SMB_VFS_CLOSE()
+ - SMB_VFS_CLOSEDIR()
+ - SMB_VFS_DURABLE_COOKIE()
+ - SMB_VFS_DURABLE_DISCONNECT()
+ - SMB_VFS_FALLOCATE()
+ - SMB_VFS_FCHMOD()
+ - SMB_VFS_FCHOWN()
+ - SMB_VFS_FCNTL()
+ - SMB_VFS_FDOPENDIR()
+ - SMB_VFS_FGET_DOS_ATTRIBUTES()
+ - SMB_VFS_FGET_NT_ACL()
+ - SMB_VFS_FSCTL()
+ - SMB_VFS_FSET_DOS_ATTRIBUTES()
+ - SMB_VFS_FSET_NT_ACL()
+ - SMB_VFS_FSTAT()
+ - SMB_VFS_FSYNC()
+ - SMB_VFS_FSYNC_SEND()
+ - SMB_VFS_FTRUNCATE()
+ - SMB_VFS_GETLOCK()
+ - SMB_VFS_GET_ALLOC_SIZE()
+ - SMB_VFS_GET_SHADOW_COPY_DATA()
+ - SMB_VFS_FILESYSTEM_SHAREMODE()
+ - SMB_VFS_LINUX_SETLEASE()
+ - SMB_VFS_LOCK()
+ - SMB_VFS_LSEEK()
+ - SMB_VFS_OFFLOAD_READ_SEND()
+ - SMB_VFS_OFFLOAD_WRITE_SEND()
+ - SMB_VFS_PREAD()
+ - SMB_VFS_PREAD_SEND()
+ - SMB_VFS_PWRITE()
+ - SMB_VFS_PWRITE_SEND()
+ - SMB_VFS_READDIR()
+ - SMB_VFS_RECVFILE()
+ - SMB_VFS_REWINDDIR()
+ - SMB_VFS_SENDFILE()
+ - SMB_VFS_SET_COMPRESSION()
+ - SMB_VFS_STRICT_LOCK_CHECK()
+
+ If an fsp is provided by the SMB layer we use that, otherwise we use the
+ pathref fsp =smb_fname->fsp= provided by =filename_convert()=.
+*** Namespace changing VFS functions <<NsC>>
+
+ - SMB_VFS_CREATE_FILE()
+
+ All intermediate VFS calls within =SMB_VFS_CREATE_FILE()= will be based on
+ =smb_fname->fsp= if the requested path exists. When creating a file we rely on
+ =non_widelink_open()= which doesn't depend on a dirfsp.
+
+ - SMB_VFS_MKDIRAT()
+
+ Needs a real dirfsp (done).
+
+ - SMB_VFS_OPENAT()
+
+ Is only called from within =non_widelink_open()= with a dirfsp equivalent of
+ =AT_FDCWD= and so doesn't need a real dirfsp.
+
+ The following operations need a real dirfsp:
+
+ - SMB_VFS_LINKAT()
+ - SMB_VFS_MKNODAT()
+ - SMB_VFS_RENAMEAT()
+ - SMB_VFS_SYMLINKAT()
+ - SMB_VFS_UNLINKAT()
+
+ Callers use =openat_pathref_fsp()= to open a fsp on the parent directory.
+
+*** Path based VFS functions <<Path>>
+ All path based VFS functions will be replaced by handle based variants using the
+ =smb_fname->fsp= provided by =filename_convert()=.
+
+ - SMB_VFS_CHDIR()
+ - SMB_VFS_CHFLAGS()
+ - SMB_VFS_CHMOD()
+ - SMB_VFS_DURABLE_RECONNECT()
+ - SMB_VFS_GETXATTR()
+ - SMB_VFS_GET_COMPRESSION()
+ - SMB_VFS_GET_DOS_ATTRIBUTES()
+ - SMB_VFS_GET_NT_ACL_AT()
+ - SMB_VFS_LCHOWN()
+ - SMB_VFS_LISTXATTR()
+ - SMB_VFS_LSTAT()
+ - SMB_VFS_NTIMES()
+ - SMB_VFS_REMOVEXATTR()
+ - SMB_VFS_SETXATTR()
+ - SMB_VFS_SET_DOS_ATTRIBUTES()
+ - SMB_VFS_STAT()
+ - SMB_VFS_STREAMINFO()
+ - SMB_VFS_SYS_ACL_BLOB_GET_FILE()
+ - SMB_VFS_SYS_ACL_DELETE_DEF_FILE()
+ - SMB_VFS_SYS_ACL_GET_FILE()
+ - SMB_VFS_SYS_ACL_SET_FILE()
+
+ Replace with corresponding handle based VFS calls.
+*** AT VFS functions that can't be based on handles <<Symlink>>
+
+ - SMB_VFS_CREATE_DFS_PATHAT()
+ - SMB_VFS_READ_DFS_PATHAT()
+ - SMB_VFS_READLINKAT()
+
+ As the DFS link implementation is based on symlinks, we have to use *AT based
+ functions with real dirfsps.
+
+*** AT VFS functions needed for directory enumeration <<Enum>>
+ - SMB_VFS_GET_DOS_ATTRIBUTES_SEND()
+ - SMB_VFS_GETXATTRAT_SEND()
+*** Handle based VFS functions not allowed on O_PATH opened handles <<xpathref>>
+ - SMB_VFS_FGETXATTR()
+ - SMB_VFS_FLISTXATTR()
+ - SMB_VFS_FREMOVEXATTR()
+ - SMB_VFS_FSETXATTR()
+ - SMB_VFS_SYS_ACL_BLOB_GET_FD()
+ - SMB_VFS_SYS_ACL_GET_FD()
+ - SMB_VFS_SYS_ACL_DELETE_DEF_FD() (NEW)
+ - SMB_VFS_SYS_ACL_SET_FD()
+
+ Based upon securely opening a full fd based on =/proc/self/fd/%d= as in the case
+ of xattrs, pathref handles can't be used for xattr IO, and in the case of ACLs
+ pathref handles can't be used to access default ACEs.
+*** Pure path to path translation <<P2px>>
+ - SMB_VFS_CONNECTPATH()
+ - SMB_VFS_GET_REAL_FILENAME()
+ - SMB_VFS_REALPATH()
+ - SMB_VFS_TRANSLATE_NAME()
+
+ No changes needed.
+*** Special cases <<Special>>
+ - SMB_VFS_FILE_ID_CREATE()
+ - SMB_VFS_FS_FILE_ID()
+ - SMB_VFS_GET_QUOTA()
+ - SMB_VFS_GETWD()
+ - SMB_VFS_SET_QUOTA()
+
+ No changes needed.
+
+ - SMB_VFS_AUDIT_FILE()
+
+ This is currently unused.
+
+[fn:VFS_API] ~grep 'SMB_VFS_*' source3/include/vfs_macros.h | grep -v NEXT_ | sed 's|.*\(SMB_VFS_.*\)(.*|\1()|' | sort~
diff --git a/source3/modules/The_New_VFS.txt b/source3/modules/The_New_VFS.txt
new file mode 100644
index 0000000..af50723
--- /dev/null
+++ b/source3/modules/The_New_VFS.txt
@@ -0,0 +1,607 @@
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ THE NEW SAMBA VFS
+
+ Ralph Böhme, SerNet, Samba Team
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+
+ 2021-01-14
+
+
+Table of Contents
+─────────────────
+
+1. The new VFS
+.. 1. Summary
+.. 2. Samba and O_PATH
+..... 1. Background
+..... 2. Usecases for O_PATH in Samba
+..... 3. When to open with O_PATH
+..... 4. Fallback on systems without O_PATH support
+..... 5. When to use fsp_get_io_fd() or fsp_get_pathref_fd()
+2. VFS status quo and remaining work
+.. 1. VFS Functions Tables [2]
+..... 1. Existing VFS Functions
+..... 2. New VFS Functions
+.. 2. VFS functions by category
+..... 1. Disk operations
+..... 2. Handle based VFS functions
+..... 3. Namespace changing VFS functions
+..... 4. Path based VFS functions
+..... 5. AT VFS functions that can't be based on handles
+..... 6. AT VFS functions needed for directory enumeration
+..... 7. Handle based VFS functions not allowed on O_PATH opened handles
+..... 8. Pure path to path translation
+..... 9. Special cases
+
+
+1 The new VFS
+═════════════
+
+1.1 Summary
+───────────
+
+ The effort to modernize Samba's VFS interface has reached a major
+ milestone with the next release Samba 4.14.
+
+ Starting with version 4.14 Samba provides core infrastructure code that
+ allows basing all access to the server's filesystem on file handles and
+ not on paths. An example of this is using `fstat()' instead of `stat()',
+ or `SMB_VFS_FSTAT()' instead of `SMB_VFS_STAT()' in Samba parlance.
+
+ Historically Samba's fileserver code had to deal a lot with processing
+ path based SMB requests. While the SMB protocol itself has been
+ streamlined to be purely handle based starting with SMB2, large parts of
+ infrastructure code remains in place that will "degrade" handle based SMB2
+ requests to path based filesystem access.
+
+ In order to fully leverage the handle based nature of the SMB2 protocol we
+ came up with a straight forward way to convert this infrastructure code.
+
+ At the core, we introduced a helper function that opens a file handle that
+ only serves as a path reference and hence can not be used for any sort of
+ access to file data.
+
+ Samba's internal file handle structure is of type `struct files_struct'
+ and all variable pointing to objects of such type are typically called
+ `fsp'. Until very recently the only function that would open such a file
+ handle and return an fsp was `SMB_VFS_CREATE_FILE()'.
+
+ Internally `SMB_VFS_CREATE_FILE()' consisted of processing through Samba's
+ VFS open function to open the low level file and then going through
+ Samba's Windows NTFS emulation code.
+
+ The key point of the new helper function which is called
+ `openat_pathref_fsp()' is that it skips the NTFS emulation
+ logic. Additionally, the handle is restricted internally to be only usable
+ as a path reference but not for any sort of IO. On Linux this is achieved
+ by using the `O_PATH' `open()' flag, on systems without `O_PATH' support
+ other mechanisms are used described in more detail below.
+
+ Path processing in Samba typically means processing client supplied paths
+ by Samba's core path processing function `filename_convert()' which returns
+ a pointer to an object of type `struct smb_filename'. Pointers to such
+ objects are then passed around, often passing many layers of code.
+
+ By attaching an `fsp' file handle returned from `openat_pathref_fsp()' to
+ all `struct smb_filename' objects returned from `filename_convert()', the
+ whole infrastructure code has immediate access to a file handle and so the
+ large infrastructure codebase can be converted to use handle based VFS
+ functions whenever VFS access is done in a piecemeal fashion.
+
+
+1.2 Samba and O_PATH
+────────────────────
+
+1.2.1 Background
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ On Linux the `O_PATH' flag to `open()' can be used to open a filehandle on
+ a file or directory with interesting properties: [1]
+
+ • the file-handle indicates a location in the filesystem tree,
+
+ • no permission checks are done by the kernel on the filesystem object and
+
+ • only operations that act purely at the file descriptor level are
+ allowed.
+
+ The file itself is not opened, and other file operations (e.g., `read(2)',
+ `write(2)', `fchmod(2)', `fchown(2)', `fgetxattr(2)', `ioctl(2)',
+ `mmap(2)') fail with the error `EBADF'.
+
+ The following subset of operations that is relevant to Samba is allowed:
+
+ • `close(2)',
+
+ • `fchdir(2)', if the file descriptor refers to a directory,
+
+ • `fstat(2)',
+
+ • `fstatfs(2)' and
+
+ • passing the file descriptor as the dirfd argument of `openat()' and the
+ other "*at()" system calls. This includes `linkat(2)' with AT_EMPTY_PATH
+ (or via procfs using AT_SYMLINK_FOLLOW) even if the file is not a
+ directory.
+
+ Opening a file or directory with the `O_PATH' flag requires no permissions
+ on the object itself (but does require execute permission on the
+ directories in the path prefix). By contrast, obtaining a reference to a
+ filesystem object by opening it with the `O_RDONLY' flag requires that the
+ caller have read permission on the object, even when the subsequent
+ operation (e.g., `fchdir(2)', `fstat(2)') does not require read permis‐
+ sion on the object.
+
+ If for example Samba receives an SMB request to open a file requesting
+ `SEC_FILE_READ_ATTRIBUTE' access rights because the client wants to read
+ the file's metadata from the handle, Samba will have to call `open()' with
+ at least `O_RDONLY' access rights.
+
+
+1.2.2 Usecases for O_PATH in Samba
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ The `O_PATH' flag is currently not used in Samba. By leveraging this Linux
+ specific flags we can avoid permission mismatches as described above.
+
+ Additionally `O_PATH' allows basing all filesystem accesses done by the
+ fileserver on handle based syscalls by opening all client pathnames with
+ `O_PATH' and consistently using for example `fstat()' instead of `stat()'
+ throughout the codebase.
+
+ Subsequent parts of this document will call such file-handles opened with
+ O_PATH *path referencing file-handles* or *pathref*s for short.
+
+
+1.2.3 When to open with O_PATH
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ In Samba the decision whether to call POSIX `open()' on a client pathname
+ or whether to leave the low-level handle at -1 (what we call a stat-open)
+ is based on the client requested SMB access mask.
+
+ The set of access rights that trigger an `open()' includes
+ `READ_CONTROL_ACCESS'. As a result, the open() will be done with at least
+ `O_RDONLY'. If the filesystem supports NT style ACLs natively (like GPFS
+ or ZFS), the filesystem may grant the user requested right
+ `READ_CONTROL_ACCESS', but it may not grant `READ_DATA' (`O_RDONLY').
+
+ Currently the full set of access rights that trigger opening a file is:
+
+ • FILE_READ_DATA
+ • FILE_WRITE_DATA
+ • FILE_APPEND_DATA
+ • FILE_EXECUTE
+ • WRITE_DAC_ACCESS
+ • WRITE_OWNER_ACCESS
+ • SEC_FLAG_SYSTEM_SECURITY
+ • READ_CONTROL_ACCESS
+
+ In the future we can remove the following rights from the list on systems
+ that support O_PATH:
+
+ • WRITE_DAC_ACCESS
+ • WRITE_OWNER_ACCESS
+ • SEC_FLAG_SYSTEM_SECURITY
+ • READ_CONTROL_ACCESS
+
+
+1.2.4 Fallback on systems without O_PATH support
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ The code of higher level file-handle consumers must be kept simple and
+ streamlined, avoiding special casing the handling of the file-handles
+ opened with or without `O_PATH'. To achieve this, a fallback that allows
+ opening a file-handle with the same higher level semantics even if the
+ system doesn't support `O_PATH' is needed.
+
+ The way this is implemented on such systems is impersonating the root user
+ for the `open()' syscall. In order to avoid privilege escalations security
+ issues, we must carefully control the use these file-handles.
+
+ The low level filehandle is stored in a public struct `struct file_handle'
+ that is part of the widely used `struct files_struct'. Consumers used to
+ simply access the fd directly by dereferencing pointers to `struct
+ files_struct'.
+
+ In order to guard access to such file-handles we do two things:
+
+ • tag the pathref file-handles and
+
+ • control access to the file-handle by making the structure `struct
+ file_handle' private, only allowing access with accessor functions
+ that implement a security boundary.
+
+ In order to avoid bypassing restrictive permissions on intermediate
+ directories of a client path, the root user is only impersonated after
+ changing directory to the parent directory of the client requested
+ pathname.
+
+ Two functions can then be used to fetch the low-level system file-handle
+ from a `struct files_struct':
+
+ • `fsp_get_io_fd(fsp)': enforces fsp is NOT a pathref file-handle and
+
+ • `fsp_get_pathref_fd(fsp)': allows fsp to be either a pathref file-handle
+ or a traditional POSIX file-handle opened with O_RDONLY or any other
+ POSIX open flag.
+
+ Note that the name `fsp_get_pathref_fd()' may sound confusing at first
+ given that the fsp can be either a pathref fsp or a "normal/full" fsp, but
+ as any full file-handle can be used for IO and as path reference, the name
+ correctly reflects the intended usage of the caller.
+
+
+1.2.5 When to use fsp_get_io_fd() or fsp_get_pathref_fd()
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ The general guideline is:
+
+ • if you do something like `fstat(fd)', use `fsp_get_pathref_fd()',
+
+ • if you do something like `*at(dirfd, ...)', use `fsp_get_pathref_fd()',
+
+ • if you want to print the fd for example in `DEBUG' messages, use
+ `fsp_get_pathref_fd()',
+
+ • if you want to call `close(fd)', use `fsp_get_pathref_fd()',
+
+ • if you're doing a logical comparison of fd values, use
+ `fsp_get_pathref_fd()'.
+
+ In any other case use `fsp_get_io_fd()'.
+
+
+2 VFS status quo and remaining work
+═══════════════════════════════════
+
+2.1 VFS Functions Tables [2]
+────────────────────────────
+
+2.1.1 Existing VFS Functions
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ VFS Function Group Status
+ ───────────────────────────────────────────────────────
+ SMB_VFS_AIO_FORCE() [fsp] -
+ SMB_VFS_AUDIT_FILE() [Special] -
+ SMB_VFS_BRL_LOCK_WINDOWS() [fsp] -
+ SMB_VFS_BRL_UNLOCK_WINDOWS() [fsp] -
+ SMB_VFS_CHDIR() [Path] Todo
+ SMB_VFS_CHFLAGS() [Path] -
+ SMB_VFS_CHMOD() [Path] -
+ SMB_VFS_CLOSE() [fsp] -
+ SMB_VFS_CLOSEDIR() [fsp] -
+ SMB_VFS_CONNECT() [Disk] -
+ SMB_VFS_CONNECTPATH() [P2px] -
+ SMB_VFS_CREATE_DFS_PATHAT() [NsC] -
+ SMB_VFS_CREATE_FILE() [NsC] -
+ SMB_VFS_DISCONNECT() [Disk] -
+ SMB_VFS_DISK_FREE() [Disk] -
+ SMB_VFS_DURABLE_COOKIE() [fsp] -
+ SMB_VFS_DURABLE_DISCONNECT() [fsp] -
+ SMB_VFS_DURABLE_RECONNECT() [fsp] -
+ SMB_VFS_FALLOCATE() [fsp] -
+ SMB_VFS_FCHMOD() [fsp] -
+ SMB_VFS_FCHOWN() [fsp] -
+ SMB_VFS_FCNTL() [fsp] -
+ SMB_VFS_FDOPENDIR() [fsp] -
+ SMB_VFS_FGET_COMPRESSION() [fsp] -
+ SMB_VFS_FGET_DOS_ATTRIBUTES() [fsp] -
+ SMB_VFS_FGET_NT_ACL() [fsp] -
+ SMB_VFS_FGETXATTR() [xpathref] -
+ SMB_VFS_FILE_ID_CREATE() [Special] -
+ SMB_VFS_FLISTXATTR() [xpathref] -
+ SMB_VFS_FREMOVEXATTR() [xpathref] -
+ SMB_VFS_FS_CAPABILITIES() [Disk] -
+ SMB_VFS_FSCTL() [fsp] -
+ SMB_VFS_FSET_DOS_ATTRIBUTES() [fsp] -
+ SMB_VFS_FSET_NT_ACL() [fsp] -
+ SMB_VFS_FSETXATTR() [xpathref] -
+ SMB_VFS_FS_FILE_ID() [Special] -
+ SMB_VFS_FSTAT() [fsp] -
+ SMB_VFS_FSYNC() [fsp] -
+ SMB_VFS_FSYNC_SEND() [fsp] -
+ SMB_VFS_FTRUNCATE() [fsp] -
+ SMB_VFS_GET_ALLOC_SIZE() [fsp] -
+ SMB_VFS_GET_DFS_REFERRALS() [Disk] -
+ SMB_VFS_GET_DOS_ATTRIBUTES_RECV() [Enum] -
+ SMB_VFS_GET_DOS_ATTRIBUTES_SEND() [Enum] -
+ SMB_VFS_GETLOCK() [fsp] -
+ SMB_VFS_GET_NT_ACL_AT() [Path] -
+ SMB_VFS_GET_QUOTA() [Special] -
+ SMB_VFS_GET_REAL_FILENAME() [P2px] -
+ SMB_VFS_GET_SHADOW_COPY_DATA() [fsp] -
+ SMB_VFS_GETWD() [Special] -
+ SMB_VFS_GETXATTR() [Path] -
+ SMB_VFS_GETXATTRAT_RECV() [Enum] -
+ SMB_VFS_GETXATTRAT_SEND() [Enum] -
+ SMB_VFS_FILESYSTEM_SHAREMODE() [fsp] -
+ SMB_VFS_LCHOWN() [Path] Todo
+ SMB_VFS_LINKAT() [NsC] -
+ SMB_VFS_LINUX_SETLEASE() [fsp] -
+ SMB_VFS_LISTXATTR() [Path] -
+ SMB_VFS_LOCK() [fsp] -
+ SMB_VFS_LSEEK() [fsp] -
+ SMB_VFS_LSTAT() [Path] Todo
+ SMB_VFS_MKDIRAT() [NsC] -
+ SMB_VFS_MKNODAT() [NsC] -
+ SMB_VFS_NTIMES() [Path] -
+ SMB_VFS_OFFLOAD_READ_RECV() [fsp] -
+ SMB_VFS_OFFLOAD_READ_SEND() [fsp] -
+ SMB_VFS_OFFLOAD_WRITE_RECV() [fsp] -
+ SMB_VFS_OFFLOAD_WRITE_SEND() [fsp] -
+ SMB_VFS_OPENAT() [NsC] -
+ SMB_VFS_PREAD() [fsp] -
+ SMB_VFS_PREAD_SEND() [fsp] -
+ SMB_VFS_PWRITE() [fsp] -
+ SMB_VFS_PWRITE_SEND() [fsp] -
+ SMB_VFS_READ_DFS_PATHAT() [Symlink] -
+ SMB_VFS_READDIR() [fsp] -
+ SMB_VFS_READDIR_ATTR() [Path] -
+ SMB_VFS_READLINKAT() [Symlink] -
+ SMB_VFS_REALPATH() [P2px] -
+ SMB_VFS_RECVFILE() [fsp] -
+ SMB_VFS_REMOVEXATTR() [Path] -
+ SMB_VFS_RENAMEAT() [Path] -
+ SMB_VFS_REWINDDIR() [fsp] -
+ SMB_VFS_SENDFILE() [fsp] -
+ SMB_VFS_SET_COMPRESSION() [fsp] -
+ SMB_VFS_SET_DOS_ATTRIBUTES() [Path] -
+ SMB_VFS_SET_QUOTA() [Special] -
+ SMB_VFS_SETXATTR() [Path] -
+ SMB_VFS_SNAP_CHECK_PATH() [Disk] -
+ SMB_VFS_SNAP_CREATE() [Disk] -
+ SMB_VFS_SNAP_DELETE() [Disk] -
+ SMB_VFS_STAT() [Path] Todo
+ SMB_VFS_STATVFS() [Disk] -
+ SMB_VFS_STREAMINFO() [Path] -
+ SMB_VFS_STRICT_LOCK_CHECK() [fsp] -
+ SMB_VFS_SYMLINKAT() [NsC] -
+ SMB_VFS_SYS_ACL_BLOB_GET_FD() [xpathref] -
+ SMB_VFS_SYS_ACL_BLOB_GET_FILE() [Path] -
+ SMB_VFS_SYS_ACL_DELETE_DEF_FILE() [Path] -
+ SMB_VFS_SYS_ACL_GET_FD() [xpathref] -
+ SMB_VFS_SYS_ACL_GET_FILE() [Path] -
+ SMB_VFS_SYS_ACL_SET_FD() [xpathref] -
+ SMB_VFS_TRANSLATE_NAME() [P2px] -
+ SMB_VFS_UNLINKAT() [NsC] -
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+
+[fsp] See section 2.2.2
+
+[Special] See section 2.2.9
+
+[Path] See section 2.2.4
+
+[Disk] See section 2.2.1
+
+[P2px] See section 2.2.8
+
+[NsC] See section 2.2.3
+
+[xpathref] See section 2.2.7
+
+[Enum] See section 2.2.6
+
+[Symlink] See section 2.2.5
+
+
+2.1.2 New VFS Functions
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ VFS Function Group Status
+ ─────────────────────────────────────────────────────
+ SMB_VFS_SYS_ACL_DELETE_DEF_FD() [xpathref] -
+ SMB_VFS_FNTIMENS() [fsp] -
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+
+[xpathref] See section 2.2.7
+
+[Enum] See section 2.2.6
+
+[fsp] See section 2.2.2
+
+
+2.2 VFS functions by category
+─────────────────────────────
+
+2.2.1 Disk operations
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_CONNECT()
+ • SMB_VFS_DISCONNECT()
+ • SMB_VFS_DISK_FREE()
+ • SMB_VFS_FS_CAPABILITIES()
+ • SMB_VFS_GET_DFS_REFERRALS()
+ • SMB_VFS_SNAP_CHECK_PATH()
+ • SMB_VFS_SNAP_CREATE()
+ • SMB_VFS_SNAP_DELETE()
+ • SMB_VFS_STATVFS()
+
+ No changes needed.
+
+
+2.2.2 Handle based VFS functions
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_AIO_FORCE()
+ • SMB_VFS_BRL_LOCK_WINDOWS()
+ • SMB_VFS_BRL_UNLOCK_WINDOWS()
+ • SMB_VFS_CLOSE()
+ • SMB_VFS_CLOSEDIR()
+ • SMB_VFS_DURABLE_COOKIE()
+ • SMB_VFS_DURABLE_DISCONNECT()
+ • SMB_VFS_FALLOCATE()
+ • SMB_VFS_FCHMOD()
+ • SMB_VFS_FCHOWN()
+ • SMB_VFS_FCNTL()
+ • SMB_VFS_FDOPENDIR()
+ • SMB_VFS_FGET_DOS_ATTRIBUTES()
+ • SMB_VFS_FGET_NT_ACL()
+ • SMB_VFS_FSCTL()
+ • SMB_VFS_FSET_DOS_ATTRIBUTES()
+ • SMB_VFS_FSET_NT_ACL()
+ • SMB_VFS_FSTAT()
+ • SMB_VFS_FSYNC()
+ • SMB_VFS_FSYNC_SEND()
+ • SMB_VFS_FTRUNCATE()
+ • SMB_VFS_GETLOCK()
+ • SMB_VFS_GET_ALLOC_SIZE()
+ • SMB_VFS_GET_SHADOW_COPY_DATA()
+ • SMB_VFS_FILESYSTEM_SHAREMODE()
+ • SMB_VFS_LINUX_SETLEASE()
+ • SMB_VFS_LOCK()
+ • SMB_VFS_LSEEK()
+ • SMB_VFS_OFFLOAD_READ_SEND()
+ • SMB_VFS_OFFLOAD_WRITE_SEND()
+ • SMB_VFS_PREAD()
+ • SMB_VFS_PREAD_SEND()
+ • SMB_VFS_PWRITE()
+ • SMB_VFS_PWRITE_SEND()
+ • SMB_VFS_READDIR()
+ • SMB_VFS_RECVFILE()
+ • SMB_VFS_REWINDDIR()
+ • SMB_VFS_SENDFILE()
+ • SMB_VFS_SET_COMPRESSION()
+ • SMB_VFS_STRICT_LOCK_CHECK()
+
+ If an fsp is provided by the SMB layer we use that, otherwise we use the
+ pathref fsp `smb_fname->fsp' provided by `filename_convert()'.
+
+
+2.2.3 Namespace changing VFS functions
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_CREATE_FILE()
+
+ All intermediate VFS calls within `SMB_VFS_CREATE_FILE()' will be based on
+ `smb_fname->fsp' if the requested path exists. When creating a file we
+ rely on `non_widelink_open()' which doesn't depend on a dirfsp.
+
+ • SMB_VFS_MKDIRAT()
+
+ Needs a real dirfsp (done).
+
+ • SMB_VFS_OPENAT()
+
+ Is only called from within `non_widelink_open()' with a dirfsp equivalent
+ of `AT_FDCWD' and so doesn't need a real dirfsp.
+
+ The following operations need a real dirfsp:
+
+ • SMB_VFS_LINKAT()
+ • SMB_VFS_MKNODAT()
+ • SMB_VFS_RENAMEAT()
+ • SMB_VFS_SYMLINKAT()
+ • SMB_VFS_UNLINKAT()
+
+ Callers use `openat_pathref_fsp()' to open a fsp on the parent directory.
+
+
+2.2.4 Path based VFS functions
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ All path based VFS functions will be replaced by handle based variants
+ using the `smb_fname->fsp' provided by `filename_convert()'.
+
+ • SMB_VFS_CHDIR()
+ • SMB_VFS_CHFLAGS()
+ • SMB_VFS_CHMOD()
+ • SMB_VFS_DURABLE_RECONNECT()
+ • SMB_VFS_GETXATTR()
+ • SMB_VFS_GET_COMPRESSION()
+ • SMB_VFS_GET_DOS_ATTRIBUTES()
+ • SMB_VFS_GET_NT_ACL_AT()
+ • SMB_VFS_LCHOWN()
+ • SMB_VFS_LISTXATTR()
+ • SMB_VFS_LSTAT()
+ • SMB_VFS_NTIMES()
+ • SMB_VFS_REMOVEXATTR()
+ • SMB_VFS_SETXATTR()
+ • SMB_VFS_SET_DOS_ATTRIBUTES()
+ • SMB_VFS_STAT()
+ • SMB_VFS_STREAMINFO()
+ • SMB_VFS_SYS_ACL_BLOB_GET_FILE()
+ • SMB_VFS_SYS_ACL_DELETE_DEF_FILE()
+ • SMB_VFS_SYS_ACL_GET_FILE()
+ • SMB_VFS_SYS_ACL_SET_FILE()
+
+ Replace with corresponding handle based VFS calls.
+
+
+2.2.5 AT VFS functions that can't be based on handles
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_CREATE_DFS_PATHAT()
+ • SMB_VFS_READ_DFS_PATHAT()
+ • SMB_VFS_READLINKAT()
+
+ As the DFS link implementation is based on symlinks, we have to use *AT
+ based functions with real dirfsps.
+
+
+2.2.6 AT VFS functions needed for directory enumeration
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_GET_DOS_ATTRIBUTES_SEND()
+ • SMB_VFS_GETXATTRAT_SEND()
+
+
+2.2.7 Handle based VFS functions not allowed on O_PATH opened handles
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_FGETXATTR()
+ • SMB_VFS_FLISTXATTR()
+ • SMB_VFS_FREMOVEXATTR()
+ • SMB_VFS_FSETXATTR()
+ • SMB_VFS_SYS_ACL_BLOB_GET_FD()
+ • SMB_VFS_SYS_ACL_GET_FD()
+ • SMB_VFS_SYS_ACL_DELETE_DEF_FD() (NEW)
+ • SMB_VFS_SYS_ACL_SET_FD()
+
+ Based upon securely opening a full fd based on `/proc/self/fd/%d' as in
+ the case of xattrs, pathref handles can't be used for xattr IO, and in the
+ case of ACLs pathref handles can't be used to access default ACEs.
+
+
+2.2.8 Pure path to path translation
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_CONNECTPATH()
+ • SMB_VFS_GET_REAL_FILENAME()
+ • SMB_VFS_REALPATH()
+ • SMB_VFS_TRANSLATE_NAME()
+
+ No changes needed.
+
+
+2.2.9 Special cases
+╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+
+ • SMB_VFS_FILE_ID_CREATE()
+ • SMB_VFS_FS_FILE_ID()
+ • SMB_VFS_GET_QUOTA()
+ • SMB_VFS_GETWD()
+ • SMB_VFS_SET_QUOTA()
+
+ No changes needed.
+
+ • SMB_VFS_AUDIT_FILE()
+
+ This is currently unused.
+
+
+
+Footnotes
+─────────
+
+[1] parts of the following sections copied from man open(2)
+
+[2] `grep 'SMB_VFS_*' source3/include/vfs_macros.h | grep -v NEXT_ | sed
+'s|.*\(SMB_VFS_.*\)(.*|\1()|' | sort'
diff --git a/source3/modules/getdate.c b/source3/modules/getdate.c
new file mode 100644
index 0000000..763ff93
--- /dev/null
+++ b/source3/modules/getdate.c
@@ -0,0 +1,2742 @@
+/* A Bison parser, made by GNU Bison 3.0.4. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output. */
+#define YYBISON 1
+
+/* Bison version. */
+#define YYBISON_VERSION "3.0.4"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 1
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+
+
+/* Copy the first part of user declarations. */
+#line 1 "source3/modules/getdate.y" /* yacc.c:339 */
+
+/* Parse a string into an internal time stamp.
+ Copyright (C) 1999, 2000, 2002 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+/* Originally written by Steven M. Bellovin <smb@research.att.com> while
+ at the University of North Carolina at Chapel Hill. Later tweaked by
+ a couple of people on Usenet. Completely overhauled by Rich $alz
+ <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+
+ Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ the right thing about local DST. Unlike previous versions, this
+ version is reentrant. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+# ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+# endif
+#endif
+
+/* Since the code of getdate.y is not included in the Emacs executable
+ itself, there is no need to #define static in this file. Even if
+ the code were included in the Emacs executable, it probably
+ wouldn't do any harm to #undef it here; this will only cause
+ problems if we try to write to a static variable, which I don't
+ think this code needs to do. */
+#ifdef emacs
+# undef static
+#endif
+
+#include <ctype.h>
+#include <string.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h> /* for `free'; used by Bison 1.27 */
+#endif
+
+#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
+# define IN_CTYPE_DOMAIN(c) 1
+#else
+# define IN_CTYPE_DOMAIN(c) isascii (c)
+#endif
+
+#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
+#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
+#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
+#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
+
+/* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char.
+ - It's guaranteed to evaluate its argument exactly once.
+ - It's typically faster.
+ POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
+ ISDIGIT_LOCALE unless it's important to use the locale's definition
+ of `digit' even when the host does not conform to POSIX. */
+#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
+
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+#endif
+
+#ifndef HAVE___ATTRIBUTE__
+# define __attribute__(x)
+#endif
+
+#ifndef ATTRIBUTE_UNUSED
+# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+#define EPOCH_YEAR 1970
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+/* An integer value, and the number of digits in its textual
+ representation. */
+typedef struct
+{
+ int value;
+ int digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct
+{
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+/* Information passed to and from the parser. */
+struct parser_control
+{
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ int day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, and seconds. */
+ textint year;
+ int month;
+ int day;
+ int hour;
+ int minutes;
+ int seconds;
+
+ /* Relative year, month, day, hour, minutes, and seconds. */
+ int rel_year;
+ int rel_month;
+ int rel_day;
+ int rel_hour;
+ int rel_minutes;
+ int rel_seconds;
+
+ /* Counts of nonterminals of various flavors parsed so far. */
+ int dates_seen;
+ int days_seen;
+ int local_zones_seen;
+ int rels_seen;
+ int times_seen;
+ int zones_seen;
+
+ /* Table of local time zone abbreviations, terminated by a null entry. */
+ table local_time_zone_table[3];
+};
+
+
+#line 223 "source3/modules/getdate.c" /* yacc.c:339 */
+
+# ifndef YY_NULLPTR
+# if defined __cplusplus && 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# endif
+
+/* Enabling verbose error messages. */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE 0
+#endif
+
+
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int yydebug;
+#endif
+
+/* Token type. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ tAGO = 258,
+ tDST = 259,
+ tDAY = 260,
+ tDAY_UNIT = 261,
+ tDAYZONE = 262,
+ tHOUR_UNIT = 263,
+ tLOCAL_ZONE = 264,
+ tMERIDIAN = 265,
+ tMINUTE_UNIT = 266,
+ tMONTH = 267,
+ tMONTH_UNIT = 268,
+ tSEC_UNIT = 269,
+ tYEAR_UNIT = 270,
+ tZONE = 271,
+ tSNUMBER = 272,
+ tUNUMBER = 273
+ };
+#endif
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+
+union YYSTYPE
+{
+#line 168 "source3/modules/getdate.y" /* yacc.c:355 */
+
+ int intval;
+ textint textintval;
+
+#line 284 "source3/modules/getdate.c" /* yacc.c:355 */
+};
+
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+
+
+int yyparse (struct parser_control *pc);
+
+
+
+/* Copy the second part of user declarations. */
+#line 173 "source3/modules/getdate.y" /* yacc.c:358 */
+
+
+static int yyerror(struct parser_control *, const char *);
+static int yylex(YYSTYPE *, struct parser_control *);
+
+
+#line 306 "source3/modules/getdate.c" /* yacc.c:358 */
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif ! defined YYSIZE_T
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE
+# if (defined __GNUC__ \
+ && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \
+ || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C
+# define YY_ATTRIBUTE(Spec) __attribute__(Spec)
+# else
+# define YY_ATTRIBUTE(Spec) /* empty */
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_PURE
+# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__))
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__))
+#endif
+
+#if !defined _Noreturn \
+ && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112)
+# if defined _MSC_VER && 1200 <= _MSC_VER
+# define _Noreturn __declspec (noreturn)
+# else
+# define _Noreturn YY_ATTRIBUTE ((__noreturn__))
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(E) ((void) (E))
+#else
+# define YYUSE(E) /* empty */
+#endif
+
+#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yytype_int16 yyss_alloc;
+ YYSTYPE yyvs_alloc;
+};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+# define YYSTACK_BYTES(N) \
+ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYSIZE_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / sizeof (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYSIZE_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 2
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 52
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 22
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 12
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 54
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 64
+
+/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned
+ by yylex, with out-of-bounds checking. */
+#define YYUNDEFTOK 2
+#define YYMAXUTOK 273
+
+#define YYTRANSLATE(YYX) \
+ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM
+ as returned by yylex, without out-of-bounds checking. */
+static const yytype_uint8 yytranslate[] =
+{
+ 0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 20, 2, 2, 21, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 19, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18
+};
+
+#if YYDEBUG
+ /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_uint16 yyrline[] =
+{
+ 0, 191, 191, 193, 197, 199, 201, 203, 205, 207,
+ 209, 213, 220, 227, 235, 242, 254, 256, 261, 263,
+ 265, 270, 275, 280, 288, 293, 313, 320, 328, 333,
+ 339, 344, 353, 362, 366, 368, 370, 372, 374, 376,
+ 378, 380, 382, 384, 386, 388, 390, 392, 394, 396,
+ 398, 400, 405, 442, 443
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || 0
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ "$end", "error", "$undefined", "tAGO", "tDST", "tDAY", "tDAY_UNIT",
+ "tDAYZONE", "tHOUR_UNIT", "tLOCAL_ZONE", "tMERIDIAN", "tMINUTE_UNIT",
+ "tMONTH", "tMONTH_UNIT", "tSEC_UNIT", "tYEAR_UNIT", "tZONE", "tSNUMBER",
+ "tUNUMBER", "':'", "','", "'/'", "$accept", "spec", "item", "time",
+ "local_zone", "zone", "day", "date", "rel", "relunit", "number",
+ "o_merid", YY_NULLPTR
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[NUM] -- (External) token number corresponding to the
+ (internal) symbol number NUM (which must be that of a token). */
+static const yytype_uint16 yytoknum[] =
+{
+ 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 267, 268, 269, 270, 271, 272, 273, 58,
+ 44, 47
+};
+# endif
+
+#define YYPACT_NINF -17
+
+#define yypact_value_is_default(Yystate) \
+ (!!((Yystate) == (-17)))
+
+#define YYTABLE_NINF -1
+
+#define yytable_value_is_error(Yytable_value) \
+ 0
+
+ /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int8 yypact[] =
+{
+ -17, 0, -17, 1, -17, -17, -17, 19, -17, -14,
+ -17, -17, -17, 32, 26, 14, -17, -17, -17, -17,
+ -17, -17, -17, 27, -17, -17, -17, 22, -17, -17,
+ -17, -17, -17, -17, -17, -17, -17, -17, -17, -17,
+ -16, -17, -17, -17, 29, 25, 30, -17, 31, -17,
+ -17, -17, 28, 23, -17, -17, -17, 33, -17, 34,
+ -7, -17, -17, -17
+};
+
+ /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM.
+ Performed when YYTABLE does not specify something else to do. Zero
+ means the default is an error. */
+static const yytype_uint8 yydefact[] =
+{
+ 2, 0, 1, 21, 42, 19, 45, 16, 48, 0,
+ 39, 51, 36, 18, 0, 52, 3, 4, 5, 6,
+ 8, 7, 9, 33, 10, 22, 17, 28, 20, 41,
+ 44, 47, 38, 50, 35, 23, 40, 43, 11, 46,
+ 30, 37, 49, 34, 0, 0, 0, 32, 0, 27,
+ 31, 26, 53, 24, 29, 54, 13, 0, 12, 0,
+ 53, 25, 15, 14
+};
+
+ /* YYPGOTO[NTERM-NUM]. */
+static const yytype_int8 yypgoto[] =
+{
+ -17, -17, -17, -17, -17, -17, -17, -17, -17, -17,
+ -17, -10
+};
+
+ /* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int8 yydefgoto[] =
+{
+ -1, 1, 16, 17, 18, 19, 20, 21, 22, 23,
+ 24, 58
+};
+
+ /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule whose
+ number is the opposite. If YYTABLE_NINF, syntax error. */
+static const yytype_uint8 yytable[] =
+{
+ 2, 49, 50, 55, 27, 3, 4, 5, 6, 7,
+ 62, 8, 9, 10, 11, 12, 13, 14, 15, 35,
+ 36, 25, 37, 26, 38, 39, 40, 41, 42, 43,
+ 47, 44, 29, 45, 30, 46, 28, 31, 55, 32,
+ 33, 34, 48, 52, 59, 56, 51, 57, 53, 54,
+ 63, 60, 61
+};
+
+static const yytype_uint8 yycheck[] =
+{
+ 0, 17, 18, 10, 18, 5, 6, 7, 8, 9,
+ 17, 11, 12, 13, 14, 15, 16, 17, 18, 5,
+ 6, 20, 8, 4, 10, 11, 12, 13, 14, 15,
+ 3, 17, 6, 19, 8, 21, 4, 11, 10, 13,
+ 14, 15, 20, 18, 21, 17, 17, 19, 18, 18,
+ 60, 18, 18
+};
+
+ /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const yytype_uint8 yystos[] =
+{
+ 0, 23, 0, 5, 6, 7, 8, 9, 11, 12,
+ 13, 14, 15, 16, 17, 18, 24, 25, 26, 27,
+ 28, 29, 30, 31, 32, 20, 4, 18, 4, 6,
+ 8, 11, 13, 14, 15, 5, 6, 8, 10, 11,
+ 12, 13, 14, 15, 17, 19, 21, 3, 20, 17,
+ 18, 17, 18, 18, 18, 10, 17, 19, 33, 21,
+ 18, 18, 17, 33
+};
+
+ /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const yytype_uint8 yyr1[] =
+{
+ 0, 22, 23, 23, 24, 24, 24, 24, 24, 24,
+ 24, 25, 25, 25, 25, 25, 26, 26, 27, 27,
+ 27, 28, 28, 28, 29, 29, 29, 29, 29, 29,
+ 29, 29, 30, 30, 31, 31, 31, 31, 31, 31,
+ 31, 31, 31, 31, 31, 31, 31, 31, 31, 31,
+ 31, 31, 32, 33, 33
+};
+
+ /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */
+static const yytype_uint8 yyr2[] =
+{
+ 0, 2, 0, 2, 1, 1, 1, 1, 1, 1,
+ 1, 2, 4, 4, 6, 6, 1, 2, 1, 1,
+ 2, 1, 2, 2, 3, 5, 3, 3, 2, 4,
+ 2, 3, 2, 1, 2, 2, 1, 2, 2, 1,
+ 2, 2, 1, 2, 2, 1, 2, 2, 1, 2,
+ 2, 1, 1, 0, 1
+};
+
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+#define YYEMPTY (-2)
+#define YYEOF 0
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (pc, YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+while (0)
+
+/* Error token number */
+#define YYTERROR 1
+#define YYERRCODE 256
+
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+/* This macro is provided for backward compatibility. */
+#ifndef YY_LOCATION_PRINT
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+#endif
+
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Type, Value, pc); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (0)
+
+
+/*----------------------------------------.
+| Print this symbol's value on YYOUTPUT. |
+`----------------------------------------*/
+
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, struct parser_control *pc)
+{
+ FILE *yyo = yyoutput;
+ YYUSE (yyo);
+ YYUSE (pc);
+ if (!yyvaluep)
+ return;
+# ifdef YYPRINT
+ if (yytype < YYNTOKENS)
+ YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# endif
+ YYUSE (yytype);
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT. |
+`--------------------------------*/
+
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, struct parser_control *pc)
+{
+ YYFPRINTF (yyoutput, "%s %s (",
+ yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]);
+
+ yy_symbol_value_print (yyoutput, yytype, yyvaluep, pc);
+ YYFPRINTF (yyoutput, ")");
+}
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+static void
+yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop)
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; yybottom <= yytop; yybottom++)
+ {
+ int yybot = *yybottom;
+ YYFPRINTF (stderr, " %d", yybot);
+ }
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (0)
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+static void
+yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, int yyrule, struct parser_control *pc)
+{
+ unsigned long int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ yystos[yyssp[yyi + 1 - yynrhs]],
+ &(yyvsp[(yyi + 1) - (yynrhs)])
+ , pc);
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, Rule, pc); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+# if defined __GLIBC__ && defined _STRING_H
+# define yystrlen strlen
+# else
+/* Return the length of YYSTR. */
+static YYSIZE_T
+yystrlen (const char *yystr)
+{
+ YYSIZE_T yylen;
+ for (yylen = 0; yystr[yylen]; yylen++)
+ continue;
+ return yylen;
+}
+# endif
+# endif
+
+# ifndef yystpcpy
+# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+# define yystpcpy stpcpy
+# else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+ YYDEST. */
+static char *
+yystpcpy (char *yydest, const char *yysrc)
+{
+ char *yyd = yydest;
+ const char *yys = yysrc;
+
+ while ((*yyd++ = *yys++) != '\0')
+ continue;
+
+ return yyd - 1;
+}
+# endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+ quotes and backslashes, so that it's suitable for yyerror. The
+ heuristic is that double-quoting is unnecessary unless the string
+ contains an apostrophe, a comma, or backslash (other than
+ backslash-backslash). YYSTR is taken from yytname. If YYRES is
+ null, do not copy; instead, return the length of what the result
+ would have been. */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+ if (*yystr == '"')
+ {
+ YYSIZE_T yyn = 0;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ /* Fall through. */
+ default:
+ if (yyres)
+ yyres[yyn] = *yyp;
+ yyn++;
+ break;
+
+ case '"':
+ if (yyres)
+ yyres[yyn] = '\0';
+ return yyn;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ if (! yyres)
+ return yystrlen (yystr);
+
+ return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message
+ about the unexpected token YYTOKEN for the state stack whose top is
+ YYSSP.
+
+ Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is
+ not large enough to hold the message. In that case, also set
+ *YYMSG_ALLOC to the required number of bytes. Return 2 if the
+ required number of bytes is too large to store. */
+static int
+yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg,
+ yytype_int16 *yyssp, int yytoken)
+{
+ YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]);
+ YYSIZE_T yysize = yysize0;
+ enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+ /* Internationalized format string. */
+ const char *yyformat = YY_NULLPTR;
+ /* Arguments of yyformat. */
+ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+ /* Number of reported tokens (one for the "unexpected", one per
+ "expected"). */
+ int yycount = 0;
+
+ /* There are many possibilities here to consider:
+ - If this state is a consistent state with a default action, then
+ the only way this function was invoked is if the default action
+ is an error action. In that case, don't check for expected
+ tokens because there are none.
+ - The only way there can be no lookahead present (in yychar) is if
+ this state is a consistent state with a default action. Thus,
+ detecting the absence of a lookahead is sufficient to determine
+ that there is no unexpected or expected token to report. In that
+ case, just report a simple "syntax error".
+ - Don't assume there isn't a lookahead just because this state is a
+ consistent state with a default action. There might have been a
+ previous inconsistent state, consistent state with a non-default
+ action, or user semantic action that manipulated yychar.
+ - Of course, the expected token list depends on states to have
+ correct lookahead information, and it depends on the parser not
+ to perform extra reductions after fetching a lookahead from the
+ scanner and before detecting a syntax error. Thus, state merging
+ (from LALR or IELR) and default reductions corrupt the expected
+ token list. However, the list is correct for canonical LR with
+ one exception: it will still contain any token that will not be
+ accepted due to an error action in a later state.
+ */
+ if (yytoken != YYEMPTY)
+ {
+ int yyn = yypact[*yyssp];
+ yyarg[yycount++] = yytname[yytoken];
+ if (!yypact_value_is_default (yyn))
+ {
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. In other words, skip the first -YYN actions for
+ this state because they are default actions. */
+ int yyxbegin = yyn < 0 ? -yyn : 0;
+ /* Stay within bounds of both yycheck and yytname. */
+ int yychecklim = YYLAST - yyn + 1;
+ int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ int yyx;
+
+ for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR
+ && !yytable_value_is_error (yytable[yyx + yyn]))
+ {
+ if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+ {
+ yycount = 1;
+ yysize = yysize0;
+ break;
+ }
+ yyarg[yycount++] = yytname[yyx];
+ {
+ YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]);
+ if (! (yysize <= yysize1
+ && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+ return 2;
+ yysize = yysize1;
+ }
+ }
+ }
+ }
+
+ switch (yycount)
+ {
+# define YYCASE_(N, S) \
+ case N: \
+ yyformat = S; \
+ break
+ YYCASE_(0, YY_("syntax error"));
+ YYCASE_(1, YY_("syntax error, unexpected %s"));
+ YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s"));
+ YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s"));
+ YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s"));
+ YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s"));
+# undef YYCASE_
+ }
+
+ {
+ YYSIZE_T yysize1 = yysize + yystrlen (yyformat);
+ if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM))
+ return 2;
+ yysize = yysize1;
+ }
+
+ if (*yymsg_alloc < yysize)
+ {
+ *yymsg_alloc = 2 * yysize;
+ if (! (yysize <= *yymsg_alloc
+ && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM))
+ *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM;
+ return 1;
+ }
+
+ /* Avoid sprintf, as that infringes on the user's name space.
+ Don't have undefined behavior even if the translation
+ produced a string with the wrong number of "%s"s. */
+ {
+ char *yyp = *yymsg;
+ int yyi = 0;
+ while ((*yyp = *yyformat) != '\0')
+ if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount)
+ {
+ yyp += yytnamerr (yyp, yyarg[yyi++]);
+ yyformat += 2;
+ }
+ else
+ {
+ yyp++;
+ yyformat++;
+ }
+ }
+ return 0;
+}
+#endif /* YYERROR_VERBOSE */
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, struct parser_control *pc)
+{
+ YYUSE (yyvaluep);
+ YYUSE (pc);
+ if (!yymsg)
+ yymsg = "Deleting";
+ YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp);
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ YYUSE (yytype);
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+}
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (struct parser_control *pc)
+{
+/* The lookahead symbol. */
+int yychar;
+
+
+/* The semantic value of the lookahead symbol. */
+/* Default value used for initialization, for pacifying older GCCs
+ or non-GCC compilers. */
+YY_INITIAL_VALUE (static YYSTYPE yyval_default;)
+YYSTYPE yylval YY_INITIAL_VALUE (= yyval_default);
+
+ /* Number of syntax errors so far. */
+ int yynerrs;
+
+ int yystate;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus;
+
+ /* The stacks and their tools:
+ 'yyss': related to states.
+ 'yyvs': related to semantic values.
+
+ Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* The state stack. */
+ yytype_int16 yyssa[YYINITDEPTH];
+ yytype_int16 *yyss;
+ yytype_int16 *yyssp;
+
+ /* The semantic value stack. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs;
+ YYSTYPE *yyvsp;
+
+ YYSIZE_T yystacksize;
+
+ int yyn;
+ int yyresult;
+ /* Lookahead token as an internal (translated) token number. */
+ int yytoken = 0;
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+
+#if YYERROR_VERBOSE
+ /* Buffer for error messages, and its allocated size. */
+ char yymsgbuf[128];
+ char *yymsg = yymsgbuf;
+ YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N))
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ yyssp = yyss = yyssa;
+ yyvsp = yyvs = yyvsa;
+ yystacksize = YYINITDEPTH;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yystate = 0;
+ yyerrstatus = 0;
+ yynerrs = 0;
+ yychar = YYEMPTY; /* Cause a token to be read. */
+ goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+ yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+ yysetstate:
+ *yyssp = yystate;
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ YYSTYPE *yyvs1 = yyvs;
+ yytype_int16 *yyss1 = yyss;
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * sizeof (*yyssp),
+ &yyvs1, yysize * sizeof (*yyvsp),
+ &yystacksize);
+
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+ goto yyexhaustedlab;
+# else
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ goto yyexhaustedlab;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yytype_int16 *yyss1 = yyss;
+ union yyalloc *yyptr =
+ (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+ if (! yyptr)
+ goto yyexhaustedlab;
+ YYSTACK_RELOCATE (yyss_alloc, yyss);
+ YYSTACK_RELOCATE (yyvs_alloc, yyvs);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+#endif /* no yyoverflow */
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+
+ YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+ (unsigned long int) yystacksize));
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+ if (yystate == YYFINAL)
+ YYACCEPT;
+
+ goto yybackup;
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+
+ /* Do appropriate processing given the current state. Read a
+ lookahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to lookahead token. */
+ yyn = yypact[yystate];
+ if (yypact_value_is_default (yyn))
+ goto yydefault;
+
+ /* Not known => get a lookahead token if don't already have one. */
+
+ /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token: "));
+ yychar = yylex (&yylval, pc);
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = yytoken = YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yytable_value_is_error (yyn))
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the lookahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+ /* Discard the shifted token. */
+ yychar = YYEMPTY;
+
+ yystate = yyn;
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ '$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 4:
+#line 198 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->times_seen++; }
+#line 1430 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 5:
+#line 200 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->local_zones_seen++; }
+#line 1436 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 6:
+#line 202 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->zones_seen++; }
+#line 1442 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 7:
+#line 204 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->dates_seen++; }
+#line 1448 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 8:
+#line 206 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->days_seen++; }
+#line 1454 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 9:
+#line 208 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rels_seen++; }
+#line 1460 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 11:
+#line 214 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->hour = (yyvsp[-1].textintval).value;
+ pc->minutes = 0;
+ pc->seconds = 0;
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1471 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 12:
+#line 221 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->hour = (yyvsp[-3].textintval).value;
+ pc->minutes = (yyvsp[-1].textintval).value;
+ pc->seconds = 0;
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1482 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 13:
+#line 228 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->hour = (yyvsp[-3].textintval).value;
+ pc->minutes = (yyvsp[-1].textintval).value;
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = (yyvsp[0].textintval).value % 100 + ((yyvsp[0].textintval).value / 100) * 60;
+ }
+#line 1494 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 14:
+#line 236 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->hour = (yyvsp[-5].textintval).value;
+ pc->minutes = (yyvsp[-3].textintval).value;
+ pc->seconds = (yyvsp[-1].textintval).value;
+ pc->meridian = (yyvsp[0].intval);
+ }
+#line 1505 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 15:
+#line 243 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->hour = (yyvsp[-5].textintval).value;
+ pc->minutes = (yyvsp[-3].textintval).value;
+ pc->seconds = (yyvsp[-1].textintval).value;
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = (yyvsp[0].textintval).value % 100 + ((yyvsp[0].textintval).value / 100) * 60;
+ }
+#line 1518 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 16:
+#line 255 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->local_isdst = (yyvsp[0].intval); }
+#line 1524 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 17:
+#line 257 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->local_isdst = (yyvsp[-1].intval) < 0 ? 1 : (yyvsp[-1].intval) + 1; }
+#line 1530 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 18:
+#line 262 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->time_zone = (yyvsp[0].intval); }
+#line 1536 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 19:
+#line 264 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->time_zone = (yyvsp[0].intval) + 60; }
+#line 1542 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 20:
+#line 266 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->time_zone = (yyvsp[-1].intval) + 60; }
+#line 1548 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 21:
+#line 271 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->day_ordinal = 1;
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1557 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 22:
+#line 276 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->day_ordinal = 1;
+ pc->day_number = (yyvsp[-1].intval);
+ }
+#line 1566 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 23:
+#line 281 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->day_ordinal = (yyvsp[-1].textintval).value;
+ pc->day_number = (yyvsp[0].intval);
+ }
+#line 1575 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 24:
+#line 289 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->month = (yyvsp[-2].textintval).value;
+ pc->day = (yyvsp[0].textintval).value;
+ }
+#line 1584 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 25:
+#line 294 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ otherwise as MM/DD/YY.
+ The goal in recognizing YYYY/MM/DD is solely to support legacy
+ machine-generated dates like those in an RCS log listing. If
+ you want portability, use the ISO 8601 format. */
+ if (4 <= (yyvsp[-4].textintval).digits)
+ {
+ pc->year = (yyvsp[-4].textintval);
+ pc->month = (yyvsp[-2].textintval).value;
+ pc->day = (yyvsp[0].textintval).value;
+ }
+ else
+ {
+ pc->month = (yyvsp[-4].textintval).value;
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->year = (yyvsp[0].textintval);
+ }
+ }
+#line 1608 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 26:
+#line 314 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ /* ISO 8601 format. YYYY-MM-DD. */
+ pc->year = (yyvsp[-2].textintval);
+ pc->month = -(yyvsp[-1].textintval).value;
+ pc->day = -(yyvsp[0].textintval).value;
+ }
+#line 1619 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 27:
+#line 321 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ /* e.g. 17-JUN-1992. */
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->month = (yyvsp[-1].intval);
+ pc->year.value = -(yyvsp[0].textintval).value;
+ pc->year.digits = (yyvsp[0].textintval).digits;
+ }
+#line 1631 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 28:
+#line 329 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->month = (yyvsp[-1].intval);
+ pc->day = (yyvsp[0].textintval).value;
+ }
+#line 1640 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 29:
+#line 334 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->month = (yyvsp[-3].intval);
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->year = (yyvsp[0].textintval);
+ }
+#line 1650 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 30:
+#line 340 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->day = (yyvsp[-1].textintval).value;
+ pc->month = (yyvsp[0].intval);
+ }
+#line 1659 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 31:
+#line 345 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->day = (yyvsp[-2].textintval).value;
+ pc->month = (yyvsp[-1].intval);
+ pc->year = (yyvsp[0].textintval);
+ }
+#line 1669 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 32:
+#line 354 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ pc->rel_seconds = -pc->rel_seconds;
+ pc->rel_minutes = -pc->rel_minutes;
+ pc->rel_hour = -pc->rel_hour;
+ pc->rel_day = -pc->rel_day;
+ pc->rel_month = -pc->rel_month;
+ pc->rel_year = -pc->rel_year;
+ }
+#line 1682 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 34:
+#line 367 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_year += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1688 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 35:
+#line 369 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_year += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1694 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 36:
+#line 371 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_year += (yyvsp[0].intval); }
+#line 1700 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 37:
+#line 373 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_month += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1706 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 38:
+#line 375 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_month += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1712 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 39:
+#line 377 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_month += (yyvsp[0].intval); }
+#line 1718 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 40:
+#line 379 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_day += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1724 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 41:
+#line 381 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_day += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1730 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 42:
+#line 383 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_day += (yyvsp[0].intval); }
+#line 1736 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 43:
+#line 385 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_hour += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1742 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 44:
+#line 387 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_hour += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1748 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 45:
+#line 389 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_hour += (yyvsp[0].intval); }
+#line 1754 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 46:
+#line 391 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_minutes += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1760 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 47:
+#line 393 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_minutes += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1766 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 48:
+#line 395 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_minutes += (yyvsp[0].intval); }
+#line 1772 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 49:
+#line 397 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_seconds += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1778 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 50:
+#line 399 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_seconds += (yyvsp[-1].textintval).value * (yyvsp[0].intval); }
+#line 1784 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 51:
+#line 401 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { pc->rel_seconds += (yyvsp[0].intval); }
+#line 1790 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 52:
+#line 406 "source3/modules/getdate.y" /* yacc.c:1646 */
+ {
+ if (pc->dates_seen
+ && ! pc->rels_seen && (pc->times_seen || 2 < (yyvsp[0].textintval).digits))
+ pc->year = (yyvsp[0].textintval);
+ else
+ {
+ if (4 < (yyvsp[0].textintval).digits)
+ {
+ pc->dates_seen++;
+ pc->day = (yyvsp[0].textintval).value % 100;
+ pc->month = ((yyvsp[0].textintval).value / 100) % 100;
+ pc->year.value = (yyvsp[0].textintval).value / 10000;
+ pc->year.digits = (yyvsp[0].textintval).digits - 4;
+ }
+ else
+ {
+ pc->times_seen++;
+ if ((yyvsp[0].textintval).digits <= 2)
+ {
+ pc->hour = (yyvsp[0].textintval).value;
+ pc->minutes = 0;
+ }
+ else
+ {
+ pc->hour = (yyvsp[0].textintval).value / 100;
+ pc->minutes = (yyvsp[0].textintval).value % 100;
+ }
+ pc->seconds = 0;
+ pc->meridian = MER24;
+ }
+ }
+ }
+#line 1827 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 53:
+#line 442 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { (yyval.intval) = MER24; }
+#line 1833 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+ case 54:
+#line 444 "source3/modules/getdate.y" /* yacc.c:1646 */
+ { (yyval.intval) = (yyvsp[0].intval); }
+#line 1839 "source3/modules/getdate.c" /* yacc.c:1646 */
+ break;
+
+
+#line 1843 "source3/modules/getdate.c" /* yacc.c:1646 */
+ default: break;
+ }
+ /* User semantic actions sometimes alter yychar, and that requires
+ that yytoken be updated with the new translation. We take the
+ approach of translating immediately before every use of yytoken.
+ One alternative is translating here after every semantic action,
+ but that translation would be missed if the semantic action invokes
+ YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or
+ if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an
+ incorrect destructor might then be invoked immediately. In the
+ case of YYERROR or YYBACKUP, subsequent parser actions might lead
+ to an incorrect destructor call or verbose syntax error message
+ before the lookahead is translated. */
+ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+
+ *++yyvsp = yyval;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+
+ yyn = yyr1[yyn];
+
+ yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+ if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+ yystate = yytable[yystate];
+ else
+ yystate = yydefgoto[yyn - YYNTOKENS];
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar);
+
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+#if ! YYERROR_VERBOSE
+ yyerror (pc, YY_("syntax error"));
+#else
+# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \
+ yyssp, yytoken)
+ {
+ char const *yymsgp = YY_("syntax error");
+ int yysyntax_error_status;
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ if (yysyntax_error_status == 0)
+ yymsgp = yymsg;
+ else if (yysyntax_error_status == 1)
+ {
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc);
+ if (!yymsg)
+ {
+ yymsg = yymsgbuf;
+ yymsg_alloc = sizeof yymsgbuf;
+ yysyntax_error_status = 2;
+ }
+ else
+ {
+ yysyntax_error_status = YYSYNTAX_ERROR;
+ yymsgp = yymsg;
+ }
+ }
+ yyerror (pc, yymsgp);
+ if (yysyntax_error_status == 2)
+ goto yyexhaustedlab;
+ }
+# undef YYSYNTAX_ERROR
+#endif
+ }
+
+
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse lookahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval, pc);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse lookahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+
+ /* Pacify compilers like GCC when the user code never invokes
+ YYERROR and the label yyerrorlab therefore never appears in user
+ code. */
+ if (/*CONSTCOND*/ 0)
+ goto yyerrorlab;
+
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (!yypact_value_is_default (yyn))
+ {
+ yyn += YYTERROR;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+
+ yydestruct ("Error: popping",
+ yystos[yystate], yyvsp, pc);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+#if !defined yyoverflow || YYERROR_VERBOSE
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (pc, YY_("memory exhausted"));
+ yyresult = 2;
+ /* Fall through. */
+#endif
+
+yyreturn:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval, pc);
+ }
+ /* Do not reclaim the symbols of the rule whose action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ yystos[*yyssp], yyvsp, pc);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+#endif
+ return yyresult;
+}
+#line 447 "source3/modules/getdate.y" /* yacc.c:1906 */
+
+
+/* Include this file down here because bison inserts code above which
+ may define-away `const'. We want the prototype for get_date to have
+ the same signature as the function definition. */
+#include "modules/getdate.h"
+
+#ifndef gmtime
+struct tm *gmtime (const time_t *);
+#endif
+#ifndef localtime
+struct tm *localtime (const time_t *);
+#endif
+#ifndef mktime
+time_t mktime (struct tm *);
+#endif
+
+static table const meridian_table[] =
+{
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { 0, 0, 0 }
+};
+
+static table const dst_table[] =
+{
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] =
+{
+ { "JANUARY", tMONTH, 1 },
+ { "FEBRUARY", tMONTH, 2 },
+ { "MARCH", tMONTH, 3 },
+ { "APRIL", tMONTH, 4 },
+ { "MAY", tMONTH, 5 },
+ { "JUNE", tMONTH, 6 },
+ { "JULY", tMONTH, 7 },
+ { "AUGUST", tMONTH, 8 },
+ { "SEPTEMBER",tMONTH, 9 },
+ { "SEPT", tMONTH, 9 },
+ { "OCTOBER", tMONTH, 10 },
+ { "NOVEMBER", tMONTH, 11 },
+ { "DECEMBER", tMONTH, 12 },
+ { "SUNDAY", tDAY, 0 },
+ { "MONDAY", tDAY, 1 },
+ { "TUESDAY", tDAY, 2 },
+ { "TUES", tDAY, 2 },
+ { "WEDNESDAY",tDAY, 3 },
+ { "WEDNES", tDAY, 3 },
+ { "THURSDAY", tDAY, 4 },
+ { "THUR", tDAY, 4 },
+ { "THURS", tDAY, 4 },
+ { "FRIDAY", tDAY, 5 },
+ { "SATURDAY", tDAY, 6 },
+ { 0, 0, 0 }
+};
+
+static table const time_units_table[] =
+{
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "MINUTE", tMINUTE_UNIT, 1 },
+ { "MIN", tMINUTE_UNIT, 1 },
+ { "SECOND", tSEC_UNIT, 1 },
+ { "SEC", tSEC_UNIT, 1 },
+ { 0, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static table const relative_time_table[] =
+{
+ { "TOMORROW", tMINUTE_UNIT, 24 * 60 },
+ { "YESTERDAY",tMINUTE_UNIT, - (24 * 60) },
+ { "TODAY", tMINUTE_UNIT, 0 },
+ { "NOW", tMINUTE_UNIT, 0 },
+ { "LAST", tUNUMBER, -1 },
+ { "THIS", tUNUMBER, 0 },
+ { "NEXT", tUNUMBER, 1 },
+ { "FIRST", tUNUMBER, 1 },
+/*{ "SECOND", tUNUMBER, 2 }, */
+ { "THIRD", tUNUMBER, 3 },
+ { "FOURTH", tUNUMBER, 4 },
+ { "FIFTH", tUNUMBER, 5 },
+ { "SIXTH", tUNUMBER, 6 },
+ { "SEVENTH", tUNUMBER, 7 },
+ { "EIGHTH", tUNUMBER, 8 },
+ { "NINTH", tUNUMBER, 9 },
+ { "TENTH", tUNUMBER, 10 },
+ { "ELEVENTH", tUNUMBER, 11 },
+ { "TWELFTH", tUNUMBER, 12 },
+ { "AGO", tAGO, 1 },
+ { 0, 0, 0 }
+};
+
+/* The time zone table. This table is necessarily incomplete, as time
+ zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ as Eastern time in Australia, not as US Eastern Standard Time.
+ You cannot rely on getdate to handle arbitrary time zone
+ abbreviations; use numeric abbreviations like `-0500' instead. */
+static table const time_zone_table[] =
+{
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
+ { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
+ { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
+ { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
+ { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
+ { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
+ { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
+ { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { 0, 0, 0 }
+};
+
+/* Military time zone table. */
+static table const military_table[] =
+{
+ { "A", tZONE, -HOUR ( 1) },
+ { "B", tZONE, -HOUR ( 2) },
+ { "C", tZONE, -HOUR ( 3) },
+ { "D", tZONE, -HOUR ( 4) },
+ { "E", tZONE, -HOUR ( 5) },
+ { "F", tZONE, -HOUR ( 6) },
+ { "G", tZONE, -HOUR ( 7) },
+ { "H", tZONE, -HOUR ( 8) },
+ { "I", tZONE, -HOUR ( 9) },
+ { "K", tZONE, -HOUR (10) },
+ { "L", tZONE, -HOUR (11) },
+ { "M", tZONE, -HOUR (12) },
+ { "N", tZONE, HOUR ( 1) },
+ { "O", tZONE, HOUR ( 2) },
+ { "P", tZONE, HOUR ( 3) },
+ { "Q", tZONE, HOUR ( 4) },
+ { "R", tZONE, HOUR ( 5) },
+ { "S", tZONE, HOUR ( 6) },
+ { "T", tZONE, HOUR ( 7) },
+ { "U", tZONE, HOUR ( 8) },
+ { "V", tZONE, HOUR ( 9) },
+ { "W", tZONE, HOUR (10) },
+ { "X", tZONE, HOUR (11) },
+ { "Y", tZONE, HOUR (12) },
+ { "Z", tZONE, HOUR ( 0) },
+ { 0, 0, 0 }
+};
+
+
+
+static int
+to_hour (int hours, int meridian)
+{
+ switch (meridian)
+ {
+ case MER24:
+ return 0 <= hours && hours < 24 ? hours : -1;
+ case MERam:
+ return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
+ case MERpm:
+ return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
+ default:
+ abort ();
+ }
+ /* NOTREACHED */
+ return 0;
+}
+
+static int
+to_year (textint textyear)
+{
+ int year = textyear.value;
+
+ if (year < 0)
+ year = -year;
+
+ /* XPG4 suggests that years 00-68 map to 2000-2068, and
+ years 69-99 map to 1969-1999. */
+ if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const *
+lookup_zone (struct parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ /* Try local zone abbreviations first; they're more likely to be right. */
+ for (tp = pc->local_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ return 0;
+}
+
+#if ! HAVE_TM_GMTOFF
+/* Yield the difference between *A and *B,
+ measured in seconds, ignoring leap seconds.
+ The body of this function is taken directly from the GNU C Library;
+ see src/strftime.c. */
+static int
+tm_diff (struct tm const *a, struct tm const *b)
+{
+ /* Compute intervening leap days correctly even if year is negative.
+ Take care to avoid int overflow in leap day calculations,
+ but it's OK to assume that A and B are close to each other. */
+ int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
+ int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = a100 >> 2;
+ int b400 = b100 >> 2;
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ int years = a->tm_year - b->tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+#endif /* ! HAVE_TM_GMTOFF */
+
+static table const *
+lookup_word (struct parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ int i;
+ int abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++)
+ if (ISLOWER ((unsigned char) *p))
+ *p = toupper ((unsigned char) *p);
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen (word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone (pc, word)))
+ return tp;
+
+ if (strcmp (word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S')
+ {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (i = 0, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ i = 1;
+ else
+ p++;
+ if (i && (tp = lookup_zone (pc, word)))
+ return tp;
+
+ return 0;
+}
+
+static int
+yylex (YYSTYPE *lvalp, struct parser_control *pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;)
+ {
+ while (c = *pc->input, ISSPACE (c))
+ pc->input++;
+
+ if (ISDIGIT (c) || c == '-' || c == '+')
+ {
+ char const *p;
+ int sign;
+ int value;
+ if (c == '-' || c == '+')
+ {
+ sign = c == '-' ? -1 : 1;
+ c = *++pc->input;
+ if (! ISDIGIT (c))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ p = pc->input;
+ value = 0;
+ do
+ {
+ value = 10 * value + c - '0';
+ c = *++p;
+ }
+ while (ISDIGIT (c));
+ lvalp->textintval.value = sign < 0 ? -value : value;
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ if (ISALPHA (c))
+ {
+ char buff[20];
+ size_t i = 0;
+ table const *tp;
+
+ do
+ {
+ if (i < 20)
+ buff[i++] = c;
+ c = *++pc->input;
+ }
+ while (ISALPHA (c) || c == '.');
+
+ buff[i] = '\0';
+ tp = lookup_word (pc, buff);
+ if (! tp)
+ return '?';
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return *pc->input++;
+ count = 0;
+ do
+ {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count > 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int
+yyerror (struct parser_control *pc ATTRIBUTE_UNUSED, const char *s ATTRIBUTE_UNUSED)
+{
+ return 0;
+}
+
+/* Parse a date/time string P. Return the corresponding time_t value,
+ or (time_t) -1 if there is an error. P can be an incomplete or
+ relative time specification; if so, use *NOW as the basis for the
+ returned time. */
+time_t
+get_date (const char *p, const time_t *now)
+{
+ time_t Start = now ? *now : time (0);
+ struct tm *tmp = localtime (&Start);
+ struct tm tm;
+ struct tm tm0;
+ struct parser_control pc;
+
+ if (! tmp)
+ return -1;
+
+ pc.input = p;
+ pc.year.value = tmp->tm_year + TM_YEAR_BASE;
+ pc.year.digits = 4;
+ pc.month = tmp->tm_mon + 1;
+ pc.day = tmp->tm_mday;
+ pc.hour = tmp->tm_hour;
+ pc.minutes = tmp->tm_min;
+ pc.seconds = tmp->tm_sec;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel_seconds = 0;
+ pc.rel_minutes = 0;
+ pc.rel_hour = 0;
+ pc.rel_day = 0;
+ pc.rel_month = 0;
+ pc.rel_year = 0;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.rels_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.zones_seen = 0;
+
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ pc.local_time_zone_table[0].name = tmp->tm_zone;
+ pc.local_time_zone_table[0].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[0].value = tmp->tm_isdst;
+ pc.local_time_zone_table[1].name = 0;
+
+ /* Probe the names used in the next three calendar quarters, looking
+ for a tm_isdst different from the one we already have. */
+ {
+ int quarter;
+ for (quarter = 1; quarter <= 3; quarter++)
+ {
+ time_t probe = Start + quarter * (90 * 24 * 60 * 60);
+ struct tm *probe_tm = localtime (&probe);
+ if (probe_tm && probe_tm->tm_zone
+ && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
+ {
+ {
+ pc.local_time_zone_table[1].name = probe_tm->tm_zone;
+ pc.local_time_zone_table[1].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
+ pc.local_time_zone_table[2].name = 0;
+ }
+ break;
+ }
+ }
+ }
+#else
+#ifdef HAVE_TZNAME
+ {
+# ifndef tzname
+ extern char *tzname[];
+# endif
+ int i;
+ for (i = 0; i < 2; i++)
+ {
+ pc.local_time_zone_table[i].name = tzname[i];
+ pc.local_time_zone_table[i].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[i].value = i;
+ }
+ pc.local_time_zone_table[i].name = 0;
+ }
+#else
+ pc.local_time_zone_table[0].name = 0;
+#endif
+#endif
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && ! strcmp (pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name))
+ {
+ /* This locale uses the same abbreviation for standard and
+ daylight times. So if we see that abbreviation, we don't
+ know whether it's daylight time. */
+ pc.local_time_zone_table[0].value = -1;
+ pc.local_time_zone_table[1].name = 0;
+ }
+
+ if (yyparse (&pc) != 0
+ || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
+ || 1 < (pc.local_zones_seen + pc.zones_seen)
+ || (pc.local_zones_seen && 1 < pc.local_isdst))
+ return -1;
+
+ tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
+ tm.tm_mon = pc.month - 1 + pc.rel_month;
+ tm.tm_mday = pc.day + pc.rel_day;
+ if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
+ {
+ tm.tm_hour = to_hour (pc.hour, pc.meridian);
+ if (tm.tm_hour < 0)
+ return -1;
+ tm.tm_min = pc.minutes;
+ tm.tm_sec = pc.seconds;
+ }
+ else
+ {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ }
+
+ /* Let mktime deduce tm_isdst if we have an absolute time stamp,
+ or if the relative time stamp mentions days, months, or years. */
+ if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
+ | pc.rel_month | pc.rel_year)
+ tm.tm_isdst = -1;
+
+ /* But if the input explicitly specifies local time with or without
+ DST, give mktime that information. */
+ if (pc.local_zones_seen)
+ tm.tm_isdst = pc.local_isdst;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (Start == (time_t) -1)
+ {
+
+ /* Guard against falsely reporting errors near the time_t boundaries
+ when parsing times in other time zones. For example, if the min
+ time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
+ of UTC, then the min localtime value is 1970-01-01 08:00:00; if
+ we apply mktime to 1970-01-01 00:00:00 we will get an error, so
+ we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
+ zone by 24 hours to compensate. This algorithm assumes that
+ there is no DST transition within a day of the time_t boundaries. */
+ if (pc.zones_seen)
+ {
+ tm = tm0;
+ if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
+ {
+ tm.tm_mday++;
+ pc.time_zone += 24 * 60;
+ }
+ else
+ {
+ tm.tm_mday--;
+ pc.time_zone -= 24 * 60;
+ }
+ Start = mktime (&tm);
+ }
+
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (pc.days_seen && ! pc.dates_seen)
+ {
+ tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
+ + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
+ tm.tm_isdst = -1;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (pc.zones_seen)
+ {
+ int delta = pc.time_zone * 60;
+#ifdef HAVE_TM_GMTOFF
+ delta -= tm.tm_gmtoff;
+#else
+ struct tm *gmt = gmtime (&Start);
+ if (! gmt)
+ return -1;
+ delta -= tm_diff (&tm, gmt);
+#endif
+ if ((Start < Start - delta) != (delta < 0))
+ return -1; /* time_t overflow */
+ Start -= delta;
+ }
+
+ /* Add relative hours, minutes, and seconds. Ignore leap seconds;
+ i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
+ leap second. Typically this is not what the user wants, but it's
+ too hard to do it the other way, because the time zone indicator
+ must be applied before relative times, and if mktime is applied
+ again the time zone will be lost. */
+ {
+ time_t t0 = Start;
+ long d1 = 60 * 60 * (long) pc.rel_hour;
+ time_t t1 = t0 + d1;
+ long d2 = 60 * (long) pc.rel_minutes;
+ time_t t2 = t1 + d2;
+ int d3 = pc.rel_seconds;
+ time_t t3 = t2 + d3;
+ if ((d1 / (60 * 60) ^ pc.rel_hour)
+ | (d2 / 60 ^ pc.rel_minutes)
+ | ((t0 + d1 < t0) ^ (d1 < 0))
+ | ((t1 + d2 < t1) ^ (d2 < 0))
+ | ((t2 + d3 < t2) ^ (d3 < 0)))
+ return -1;
+ Start = t3;
+ }
+
+ return Start;
+}
+
+#if TEST
+
+#include <stdio.h>
+
+int
+main (int ac, char **av)
+{
+ char buff[BUFSIZ];
+ time_t d;
+
+ printf ("Enter date, or blank line to exit.\n\t> ");
+ fflush (stdout);
+
+ buff[BUFSIZ - 1] = 0;
+ while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
+ {
+ d = get_date (buff, 0);
+ if (d == (time_t) -1)
+ printf ("Bad format - couldn't convert.\n");
+ else
+ printf ("%s", ctime (&d));
+ printf ("\t> ");
+ fflush (stdout);
+ }
+ return 0;
+}
+#endif /* defined TEST */
diff --git a/source3/modules/getdate.h b/source3/modules/getdate.h
new file mode 100644
index 0000000..80b4a98
--- /dev/null
+++ b/source3/modules/getdate.h
@@ -0,0 +1,45 @@
+/* Copyright (C) 1995, 1997, 1998 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifndef PARAMS
+# if defined PROTOTYPES || (defined __STDC__ && __STDC__)
+# define PARAMS(Args) Args
+# else
+# define PARAMS(Args) ()
+# endif
+#endif
+
+#ifdef vms
+# include <types.h>
+# include <time.h>
+#else
+# include <sys/types.h>
+# if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+# else
+# if HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+# endif
+#endif /* defined (vms) */
+
+time_t get_date PARAMS ((const char *p, const time_t *now));
diff --git a/source3/modules/getdate.y b/source3/modules/getdate.y
new file mode 100644
index 0000000..8e349ba
--- /dev/null
+++ b/source3/modules/getdate.y
@@ -0,0 +1,1118 @@
+%{
+/* Parse a string into an internal time stamp.
+ Copyright (C) 1999, 2000, 2002 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+/* Originally written by Steven M. Bellovin <smb@research.att.com> while
+ at the University of North Carolina at Chapel Hill. Later tweaked by
+ a couple of people on Usenet. Completely overhauled by Rich $alz
+ <rsalz@bbn.com> and Jim Berets <jberets@bbn.com> in August, 1990.
+
+ Modified by Paul Eggert <eggert@twinsun.com> in August 1999 to do
+ the right thing about local DST. Unlike previous versions, this
+ version is reentrant. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+# ifdef HAVE_ALLOCA_H
+# include <alloca.h>
+# endif
+#endif
+
+/* Since the code of getdate.y is not included in the Emacs executable
+ itself, there is no need to #define static in this file. Even if
+ the code were included in the Emacs executable, it probably
+ wouldn't do any harm to #undef it here; this will only cause
+ problems if we try to write to a static variable, which I don't
+ think this code needs to do. */
+#ifdef emacs
+# undef static
+#endif
+
+#include <ctype.h>
+#include <string.h>
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h> /* for `free'; used by Bison 1.27 */
+#endif
+
+#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII)
+# define IN_CTYPE_DOMAIN(c) 1
+#else
+# define IN_CTYPE_DOMAIN(c) isascii (c)
+#endif
+
+#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace (c))
+#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha (c))
+#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower (c))
+#define ISDIGIT_LOCALE(c) (IN_CTYPE_DOMAIN (c) && isdigit (c))
+
+/* ISDIGIT differs from ISDIGIT_LOCALE, as follows:
+ - Its arg may be any int or unsigned int; it need not be an unsigned char.
+ - It's guaranteed to evaluate its argument exactly once.
+ - It's typically faster.
+ POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
+ ISDIGIT_LOCALE unless it's important to use the locale's definition
+ of `digit' even when the host does not conform to POSIX. */
+#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
+
+#if STDC_HEADERS || HAVE_STRING_H
+# include <string.h>
+#endif
+
+#ifndef HAVE___ATTRIBUTE__
+# define __attribute__(x)
+#endif
+
+#ifndef ATTRIBUTE_UNUSED
+# define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+#endif
+
+#define EPOCH_YEAR 1970
+#define TM_YEAR_BASE 1900
+
+#define HOUR(x) ((x) * 60)
+
+/* An integer value, and the number of digits in its textual
+ representation. */
+typedef struct
+{
+ int value;
+ int digits;
+} textint;
+
+/* An entry in the lexical lookup table. */
+typedef struct
+{
+ char const *name;
+ int type;
+ int value;
+} table;
+
+/* Meridian: am, pm, or 24-hour style. */
+enum { MERam, MERpm, MER24 };
+
+/* Information passed to and from the parser. */
+struct parser_control
+{
+ /* The input string remaining to be parsed. */
+ const char *input;
+
+ /* N, if this is the Nth Tuesday. */
+ int day_ordinal;
+
+ /* Day of week; Sunday is 0. */
+ int day_number;
+
+ /* tm_isdst flag for the local zone. */
+ int local_isdst;
+
+ /* Time zone, in minutes east of UTC. */
+ int time_zone;
+
+ /* Style used for time. */
+ int meridian;
+
+ /* Gregorian year, month, day, hour, minutes, and seconds. */
+ textint year;
+ int month;
+ int day;
+ int hour;
+ int minutes;
+ int seconds;
+
+ /* Relative year, month, day, hour, minutes, and seconds. */
+ int rel_year;
+ int rel_month;
+ int rel_day;
+ int rel_hour;
+ int rel_minutes;
+ int rel_seconds;
+
+ /* Counts of nonterminals of various flavors parsed so far. */
+ int dates_seen;
+ int days_seen;
+ int local_zones_seen;
+ int rels_seen;
+ int times_seen;
+ int zones_seen;
+
+ /* Table of local time zone abbreviations, terminated by a null entry. */
+ table local_time_zone_table[3];
+};
+
+%}
+
+%lex-param {struct parser_control *pc}
+%parse-param {struct parser_control *pc}
+
+/* We want a reentrant parser. */
+%pure-parser
+
+/* This grammar has 13 shift/reduce conflicts. */
+%expect 13
+
+%union
+{
+ int intval;
+ textint textintval;
+}
+
+%{
+
+static int yyerror(struct parser_control *, const char *);
+static int yylex(YYSTYPE *, struct parser_control *);
+
+%}
+
+%token tAGO tDST
+
+%token <intval> tDAY tDAY_UNIT tDAYZONE tHOUR_UNIT tLOCAL_ZONE tMERIDIAN
+%token <intval> tMINUTE_UNIT tMONTH tMONTH_UNIT tSEC_UNIT tYEAR_UNIT tZONE
+
+%token <textintval> tSNUMBER tUNUMBER
+
+%type <intval> o_merid
+
+%%
+
+spec:
+ /* empty */
+ | spec item
+ ;
+
+item:
+ time
+ { pc->times_seen++; }
+ | local_zone
+ { pc->local_zones_seen++; }
+ | zone
+ { pc->zones_seen++; }
+ | date
+ { pc->dates_seen++; }
+ | day
+ { pc->days_seen++; }
+ | rel
+ { pc->rels_seen++; }
+ | number
+ ;
+
+time:
+ tUNUMBER tMERIDIAN
+ {
+ pc->hour = $1.value;
+ pc->minutes = 0;
+ pc->seconds = 0;
+ pc->meridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds = 0;
+ pc->meridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER tSNUMBER
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = $4.value % 100 + ($4.value / 100) * 60;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds = $5.value;
+ pc->meridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER tSNUMBER
+ {
+ pc->hour = $1.value;
+ pc->minutes = $3.value;
+ pc->seconds = $5.value;
+ pc->meridian = MER24;
+ pc->zones_seen++;
+ pc->time_zone = $6.value % 100 + ($6.value / 100) * 60;
+ }
+ ;
+
+local_zone:
+ tLOCAL_ZONE
+ { pc->local_isdst = $1; }
+ | tLOCAL_ZONE tDST
+ { pc->local_isdst = $1 < 0 ? 1 : $1 + 1; }
+ ;
+
+zone:
+ tZONE
+ { pc->time_zone = $1; }
+ | tDAYZONE
+ { pc->time_zone = $1 + 60; }
+ | tZONE tDST
+ { pc->time_zone = $1 + 60; }
+ ;
+
+day:
+ tDAY
+ {
+ pc->day_ordinal = 1;
+ pc->day_number = $1;
+ }
+ | tDAY ','
+ {
+ pc->day_ordinal = 1;
+ pc->day_number = $1;
+ }
+ | tUNUMBER tDAY
+ {
+ pc->day_ordinal = $1.value;
+ pc->day_number = $2;
+ }
+ ;
+
+date:
+ tUNUMBER '/' tUNUMBER
+ {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER
+ {
+ /* Interpret as YYYY/MM/DD if the first value has 4 or more digits,
+ otherwise as MM/DD/YY.
+ The goal in recognizing YYYY/MM/DD is solely to support legacy
+ machine-generated dates like those in an RCS log listing. If
+ you want portability, use the ISO 8601 format. */
+ if (4 <= $1.digits)
+ {
+ pc->year = $1;
+ pc->month = $3.value;
+ pc->day = $5.value;
+ }
+ else
+ {
+ pc->month = $1.value;
+ pc->day = $3.value;
+ pc->year = $5;
+ }
+ }
+ | tUNUMBER tSNUMBER tSNUMBER
+ {
+ /* ISO 8601 format. YYYY-MM-DD. */
+ pc->year = $1;
+ pc->month = -$2.value;
+ pc->day = -$3.value;
+ }
+ | tUNUMBER tMONTH tSNUMBER
+ {
+ /* e.g. 17-JUN-1992. */
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year.value = -$3.value;
+ pc->year.digits = $3.digits;
+ }
+ | tMONTH tUNUMBER
+ {
+ pc->month = $1;
+ pc->day = $2.value;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER
+ {
+ pc->month = $1;
+ pc->day = $2.value;
+ pc->year = $4;
+ }
+ | tUNUMBER tMONTH
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER
+ {
+ pc->day = $1.value;
+ pc->month = $2;
+ pc->year = $3;
+ }
+ ;
+
+rel:
+ relunit tAGO
+ {
+ pc->rel_seconds = -pc->rel_seconds;
+ pc->rel_minutes = -pc->rel_minutes;
+ pc->rel_hour = -pc->rel_hour;
+ pc->rel_day = -pc->rel_day;
+ pc->rel_month = -pc->rel_month;
+ pc->rel_year = -pc->rel_year;
+ }
+ | relunit
+ ;
+
+relunit:
+ tUNUMBER tYEAR_UNIT
+ { pc->rel_year += $1.value * $2; }
+ | tSNUMBER tYEAR_UNIT
+ { pc->rel_year += $1.value * $2; }
+ | tYEAR_UNIT
+ { pc->rel_year += $1; }
+ | tUNUMBER tMONTH_UNIT
+ { pc->rel_month += $1.value * $2; }
+ | tSNUMBER tMONTH_UNIT
+ { pc->rel_month += $1.value * $2; }
+ | tMONTH_UNIT
+ { pc->rel_month += $1; }
+ | tUNUMBER tDAY_UNIT
+ { pc->rel_day += $1.value * $2; }
+ | tSNUMBER tDAY_UNIT
+ { pc->rel_day += $1.value * $2; }
+ | tDAY_UNIT
+ { pc->rel_day += $1; }
+ | tUNUMBER tHOUR_UNIT
+ { pc->rel_hour += $1.value * $2; }
+ | tSNUMBER tHOUR_UNIT
+ { pc->rel_hour += $1.value * $2; }
+ | tHOUR_UNIT
+ { pc->rel_hour += $1; }
+ | tUNUMBER tMINUTE_UNIT
+ { pc->rel_minutes += $1.value * $2; }
+ | tSNUMBER tMINUTE_UNIT
+ { pc->rel_minutes += $1.value * $2; }
+ | tMINUTE_UNIT
+ { pc->rel_minutes += $1; }
+ | tUNUMBER tSEC_UNIT
+ { pc->rel_seconds += $1.value * $2; }
+ | tSNUMBER tSEC_UNIT
+ { pc->rel_seconds += $1.value * $2; }
+ | tSEC_UNIT
+ { pc->rel_seconds += $1; }
+ ;
+
+number:
+ tUNUMBER
+ {
+ if (pc->dates_seen
+ && ! pc->rels_seen && (pc->times_seen || 2 < $1.digits))
+ pc->year = $1;
+ else
+ {
+ if (4 < $1.digits)
+ {
+ pc->dates_seen++;
+ pc->day = $1.value % 100;
+ pc->month = ($1.value / 100) % 100;
+ pc->year.value = $1.value / 10000;
+ pc->year.digits = $1.digits - 4;
+ }
+ else
+ {
+ pc->times_seen++;
+ if ($1.digits <= 2)
+ {
+ pc->hour = $1.value;
+ pc->minutes = 0;
+ }
+ else
+ {
+ pc->hour = $1.value / 100;
+ pc->minutes = $1.value % 100;
+ }
+ pc->seconds = 0;
+ pc->meridian = MER24;
+ }
+ }
+ }
+ ;
+
+o_merid:
+ /* empty */
+ { $$ = MER24; }
+ | tMERIDIAN
+ { $$ = $1; }
+ ;
+
+%%
+
+/* Include this file down here because bison inserts code above which
+ may define-away `const'. We want the prototype for get_date to have
+ the same signature as the function definition. */
+#include "modules/getdate.h"
+
+#ifndef gmtime
+struct tm *gmtime (const time_t *);
+#endif
+#ifndef localtime
+struct tm *localtime (const time_t *);
+#endif
+#ifndef mktime
+time_t mktime (struct tm *);
+#endif
+
+static table const meridian_table[] =
+{
+ { "AM", tMERIDIAN, MERam },
+ { "A.M.", tMERIDIAN, MERam },
+ { "PM", tMERIDIAN, MERpm },
+ { "P.M.", tMERIDIAN, MERpm },
+ { 0, 0, 0 }
+};
+
+static table const dst_table[] =
+{
+ { "DST", tDST, 0 }
+};
+
+static table const month_and_day_table[] =
+{
+ { "JANUARY", tMONTH, 1 },
+ { "FEBRUARY", tMONTH, 2 },
+ { "MARCH", tMONTH, 3 },
+ { "APRIL", tMONTH, 4 },
+ { "MAY", tMONTH, 5 },
+ { "JUNE", tMONTH, 6 },
+ { "JULY", tMONTH, 7 },
+ { "AUGUST", tMONTH, 8 },
+ { "SEPTEMBER",tMONTH, 9 },
+ { "SEPT", tMONTH, 9 },
+ { "OCTOBER", tMONTH, 10 },
+ { "NOVEMBER", tMONTH, 11 },
+ { "DECEMBER", tMONTH, 12 },
+ { "SUNDAY", tDAY, 0 },
+ { "MONDAY", tDAY, 1 },
+ { "TUESDAY", tDAY, 2 },
+ { "TUES", tDAY, 2 },
+ { "WEDNESDAY",tDAY, 3 },
+ { "WEDNES", tDAY, 3 },
+ { "THURSDAY", tDAY, 4 },
+ { "THUR", tDAY, 4 },
+ { "THURS", tDAY, 4 },
+ { "FRIDAY", tDAY, 5 },
+ { "SATURDAY", tDAY, 6 },
+ { 0, 0, 0 }
+};
+
+static table const time_units_table[] =
+{
+ { "YEAR", tYEAR_UNIT, 1 },
+ { "MONTH", tMONTH_UNIT, 1 },
+ { "FORTNIGHT",tDAY_UNIT, 14 },
+ { "WEEK", tDAY_UNIT, 7 },
+ { "DAY", tDAY_UNIT, 1 },
+ { "HOUR", tHOUR_UNIT, 1 },
+ { "MINUTE", tMINUTE_UNIT, 1 },
+ { "MIN", tMINUTE_UNIT, 1 },
+ { "SECOND", tSEC_UNIT, 1 },
+ { "SEC", tSEC_UNIT, 1 },
+ { 0, 0, 0 }
+};
+
+/* Assorted relative-time words. */
+static table const relative_time_table[] =
+{
+ { "TOMORROW", tMINUTE_UNIT, 24 * 60 },
+ { "YESTERDAY",tMINUTE_UNIT, - (24 * 60) },
+ { "TODAY", tMINUTE_UNIT, 0 },
+ { "NOW", tMINUTE_UNIT, 0 },
+ { "LAST", tUNUMBER, -1 },
+ { "THIS", tUNUMBER, 0 },
+ { "NEXT", tUNUMBER, 1 },
+ { "FIRST", tUNUMBER, 1 },
+/*{ "SECOND", tUNUMBER, 2 }, */
+ { "THIRD", tUNUMBER, 3 },
+ { "FOURTH", tUNUMBER, 4 },
+ { "FIFTH", tUNUMBER, 5 },
+ { "SIXTH", tUNUMBER, 6 },
+ { "SEVENTH", tUNUMBER, 7 },
+ { "EIGHTH", tUNUMBER, 8 },
+ { "NINTH", tUNUMBER, 9 },
+ { "TENTH", tUNUMBER, 10 },
+ { "ELEVENTH", tUNUMBER, 11 },
+ { "TWELFTH", tUNUMBER, 12 },
+ { "AGO", tAGO, 1 },
+ { 0, 0, 0 }
+};
+
+/* The time zone table. This table is necessarily incomplete, as time
+ zone abbreviations are ambiguous; e.g. Australians interpret "EST"
+ as Eastern time in Australia, not as US Eastern Standard Time.
+ You cannot rely on getdate to handle arbitrary time zone
+ abbreviations; use numeric abbreviations like `-0500' instead. */
+static table const time_zone_table[] =
+{
+ { "GMT", tZONE, HOUR ( 0) }, /* Greenwich Mean */
+ { "UT", tZONE, HOUR ( 0) }, /* Universal (Coordinated) */
+ { "UTC", tZONE, HOUR ( 0) },
+ { "WET", tZONE, HOUR ( 0) }, /* Western European */
+ { "WEST", tDAYZONE, HOUR ( 0) }, /* Western European Summer */
+ { "BST", tDAYZONE, HOUR ( 0) }, /* British Summer */
+ { "ART", tZONE, -HOUR ( 3) }, /* Argentina */
+ { "BRT", tZONE, -HOUR ( 3) }, /* Brazil */
+ { "BRST", tDAYZONE, -HOUR ( 3) }, /* Brazil Summer */
+ { "NST", tZONE, -(HOUR ( 3) + 30) }, /* Newfoundland Standard */
+ { "NDT", tDAYZONE,-(HOUR ( 3) + 30) }, /* Newfoundland Daylight */
+ { "AST", tZONE, -HOUR ( 4) }, /* Atlantic Standard */
+ { "ADT", tDAYZONE, -HOUR ( 4) }, /* Atlantic Daylight */
+ { "CLT", tZONE, -HOUR ( 4) }, /* Chile */
+ { "CLST", tDAYZONE, -HOUR ( 4) }, /* Chile Summer */
+ { "EST", tZONE, -HOUR ( 5) }, /* Eastern Standard */
+ { "EDT", tDAYZONE, -HOUR ( 5) }, /* Eastern Daylight */
+ { "CST", tZONE, -HOUR ( 6) }, /* Central Standard */
+ { "CDT", tDAYZONE, -HOUR ( 6) }, /* Central Daylight */
+ { "MST", tZONE, -HOUR ( 7) }, /* Mountain Standard */
+ { "MDT", tDAYZONE, -HOUR ( 7) }, /* Mountain Daylight */
+ { "PST", tZONE, -HOUR ( 8) }, /* Pacific Standard */
+ { "PDT", tDAYZONE, -HOUR ( 8) }, /* Pacific Daylight */
+ { "AKST", tZONE, -HOUR ( 9) }, /* Alaska Standard */
+ { "AKDT", tDAYZONE, -HOUR ( 9) }, /* Alaska Daylight */
+ { "HST", tZONE, -HOUR (10) }, /* Hawaii Standard */
+ { "HAST", tZONE, -HOUR (10) }, /* Hawaii-Aleutian Standard */
+ { "HADT", tDAYZONE, -HOUR (10) }, /* Hawaii-Aleutian Daylight */
+ { "SST", tZONE, -HOUR (12) }, /* Samoa Standard */
+ { "WAT", tZONE, HOUR ( 1) }, /* West Africa */
+ { "CET", tZONE, HOUR ( 1) }, /* Central European */
+ { "CEST", tDAYZONE, HOUR ( 1) }, /* Central European Summer */
+ { "MET", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEZ", tZONE, HOUR ( 1) }, /* Middle European */
+ { "MEST", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "MESZ", tDAYZONE, HOUR ( 1) }, /* Middle European Summer */
+ { "EET", tZONE, HOUR ( 2) }, /* Eastern European */
+ { "EEST", tDAYZONE, HOUR ( 2) }, /* Eastern European Summer */
+ { "CAT", tZONE, HOUR ( 2) }, /* Central Africa */
+ { "SAST", tZONE, HOUR ( 2) }, /* South Africa Standard */
+ { "EAT", tZONE, HOUR ( 3) }, /* East Africa */
+ { "MSK", tZONE, HOUR ( 3) }, /* Moscow */
+ { "MSD", tDAYZONE, HOUR ( 3) }, /* Moscow Daylight */
+ { "IST", tZONE, (HOUR ( 5) + 30) }, /* India Standard */
+ { "SGT", tZONE, HOUR ( 8) }, /* Singapore */
+ { "KST", tZONE, HOUR ( 9) }, /* Korea Standard */
+ { "JST", tZONE, HOUR ( 9) }, /* Japan Standard */
+ { "GST", tZONE, HOUR (10) }, /* Guam Standard */
+ { "NZST", tZONE, HOUR (12) }, /* New Zealand Standard */
+ { "NZDT", tDAYZONE, HOUR (12) }, /* New Zealand Daylight */
+ { 0, 0, 0 }
+};
+
+/* Military time zone table. */
+static table const military_table[] =
+{
+ { "A", tZONE, -HOUR ( 1) },
+ { "B", tZONE, -HOUR ( 2) },
+ { "C", tZONE, -HOUR ( 3) },
+ { "D", tZONE, -HOUR ( 4) },
+ { "E", tZONE, -HOUR ( 5) },
+ { "F", tZONE, -HOUR ( 6) },
+ { "G", tZONE, -HOUR ( 7) },
+ { "H", tZONE, -HOUR ( 8) },
+ { "I", tZONE, -HOUR ( 9) },
+ { "K", tZONE, -HOUR (10) },
+ { "L", tZONE, -HOUR (11) },
+ { "M", tZONE, -HOUR (12) },
+ { "N", tZONE, HOUR ( 1) },
+ { "O", tZONE, HOUR ( 2) },
+ { "P", tZONE, HOUR ( 3) },
+ { "Q", tZONE, HOUR ( 4) },
+ { "R", tZONE, HOUR ( 5) },
+ { "S", tZONE, HOUR ( 6) },
+ { "T", tZONE, HOUR ( 7) },
+ { "U", tZONE, HOUR ( 8) },
+ { "V", tZONE, HOUR ( 9) },
+ { "W", tZONE, HOUR (10) },
+ { "X", tZONE, HOUR (11) },
+ { "Y", tZONE, HOUR (12) },
+ { "Z", tZONE, HOUR ( 0) },
+ { 0, 0, 0 }
+};
+
+
+
+static int
+to_hour (int hours, int meridian)
+{
+ switch (meridian)
+ {
+ case MER24:
+ return 0 <= hours && hours < 24 ? hours : -1;
+ case MERam:
+ return 0 < hours && hours < 12 ? hours : hours == 12 ? 0 : -1;
+ case MERpm:
+ return 0 < hours && hours < 12 ? hours + 12 : hours == 12 ? 12 : -1;
+ default:
+ abort ();
+ }
+ /* NOTREACHED */
+ return 0;
+}
+
+static int
+to_year (textint textyear)
+{
+ int year = textyear.value;
+
+ if (year < 0)
+ year = -year;
+
+ /* XPG4 suggests that years 00-68 map to 2000-2068, and
+ years 69-99 map to 1969-1999. */
+ if (textyear.digits == 2)
+ year += year < 69 ? 2000 : 1900;
+
+ return year;
+}
+
+static table const *
+lookup_zone (struct parser_control const *pc, char const *name)
+{
+ table const *tp;
+
+ /* Try local zone abbreviations first; they're more likely to be right. */
+ for (tp = pc->local_time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ for (tp = time_zone_table; tp->name; tp++)
+ if (strcmp (name, tp->name) == 0)
+ return tp;
+
+ return 0;
+}
+
+#if ! HAVE_TM_GMTOFF
+/* Yield the difference between *A and *B,
+ measured in seconds, ignoring leap seconds.
+ The body of this function is taken directly from the GNU C Library;
+ see src/strftime.c. */
+static int
+tm_diff (struct tm const *a, struct tm const *b)
+{
+ /* Compute intervening leap days correctly even if year is negative.
+ Take care to avoid int overflow in leap day calculations,
+ but it's OK to assume that A and B are close to each other. */
+ int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
+ int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+ int a100 = a4 / 25 - (a4 % 25 < 0);
+ int b100 = b4 / 25 - (b4 % 25 < 0);
+ int a400 = a100 >> 2;
+ int b400 = b100 >> 2;
+ int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+ int years = a->tm_year - b->tm_year;
+ int days = (365 * years + intervening_leap_days
+ + (a->tm_yday - b->tm_yday));
+ return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+ + (a->tm_min - b->tm_min))
+ + (a->tm_sec - b->tm_sec));
+}
+#endif /* ! HAVE_TM_GMTOFF */
+
+static table const *
+lookup_word (struct parser_control const *pc, char *word)
+{
+ char *p;
+ char *q;
+ size_t wordlen;
+ table const *tp;
+ int i;
+ int abbrev;
+
+ /* Make it uppercase. */
+ for (p = word; *p; p++)
+ if (ISLOWER ((unsigned char) *p))
+ *p = toupper ((unsigned char) *p);
+
+ for (tp = meridian_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* See if we have an abbreviation for a month. */
+ wordlen = strlen (word);
+ abbrev = wordlen == 3 || (wordlen == 4 && word[3] == '.');
+
+ for (tp = month_and_day_table; tp->name; tp++)
+ if ((abbrev ? strncmp (word, tp->name, 3) : strcmp (word, tp->name)) == 0)
+ return tp;
+
+ if ((tp = lookup_zone (pc, word)))
+ return tp;
+
+ if (strcmp (word, dst_table[0].name) == 0)
+ return dst_table;
+
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Strip off any plural and try the units table again. */
+ if (word[wordlen - 1] == 'S')
+ {
+ word[wordlen - 1] = '\0';
+ for (tp = time_units_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+ word[wordlen - 1] = 'S'; /* For "this" in relative_time_table. */
+ }
+
+ for (tp = relative_time_table; tp->name; tp++)
+ if (strcmp (word, tp->name) == 0)
+ return tp;
+
+ /* Military time zones. */
+ if (wordlen == 1)
+ for (tp = military_table; tp->name; tp++)
+ if (word[0] == tp->name[0])
+ return tp;
+
+ /* Drop out any periods and try the time zone table again. */
+ for (i = 0, p = q = word; (*p = *q); q++)
+ if (*q == '.')
+ i = 1;
+ else
+ p++;
+ if (i && (tp = lookup_zone (pc, word)))
+ return tp;
+
+ return 0;
+}
+
+static int
+yylex (YYSTYPE *lvalp, struct parser_control *pc)
+{
+ unsigned char c;
+ size_t count;
+
+ for (;;)
+ {
+ while (c = *pc->input, ISSPACE (c))
+ pc->input++;
+
+ if (ISDIGIT (c) || c == '-' || c == '+')
+ {
+ char const *p;
+ int sign;
+ int value;
+ if (c == '-' || c == '+')
+ {
+ sign = c == '-' ? -1 : 1;
+ c = *++pc->input;
+ if (! ISDIGIT (c))
+ /* skip the '-' sign */
+ continue;
+ }
+ else
+ sign = 0;
+ p = pc->input;
+ value = 0;
+ do
+ {
+ value = 10 * value + c - '0';
+ c = *++p;
+ }
+ while (ISDIGIT (c));
+ lvalp->textintval.value = sign < 0 ? -value : value;
+ lvalp->textintval.digits = p - pc->input;
+ pc->input = p;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ if (ISALPHA (c))
+ {
+ char buff[20];
+ size_t i = 0;
+ table const *tp;
+
+ do
+ {
+ if (i < 20)
+ buff[i++] = c;
+ c = *++pc->input;
+ }
+ while (ISALPHA (c) || c == '.');
+
+ buff[i] = '\0';
+ tp = lookup_word (pc, buff);
+ if (! tp)
+ return '?';
+ lvalp->intval = tp->value;
+ return tp->type;
+ }
+
+ if (c != '(')
+ return *pc->input++;
+ count = 0;
+ do
+ {
+ c = *pc->input++;
+ if (c == '\0')
+ return c;
+ if (c == '(')
+ count++;
+ else if (c == ')')
+ count--;
+ }
+ while (count > 0);
+ }
+}
+
+/* Do nothing if the parser reports an error. */
+static int
+yyerror (struct parser_control *pc ATTRIBUTE_UNUSED, const char *s ATTRIBUTE_UNUSED)
+{
+ return 0;
+}
+
+/* Parse a date/time string P. Return the corresponding time_t value,
+ or (time_t) -1 if there is an error. P can be an incomplete or
+ relative time specification; if so, use *NOW as the basis for the
+ returned time. */
+time_t
+get_date (const char *p, const time_t *now)
+{
+ time_t Start = now ? *now : time (0);
+ struct tm *tmp = localtime (&Start);
+ struct tm tm;
+ struct tm tm0;
+ struct parser_control pc;
+
+ if (! tmp)
+ return -1;
+
+ pc.input = p;
+ pc.year.value = tmp->tm_year + TM_YEAR_BASE;
+ pc.year.digits = 4;
+ pc.month = tmp->tm_mon + 1;
+ pc.day = tmp->tm_mday;
+ pc.hour = tmp->tm_hour;
+ pc.minutes = tmp->tm_min;
+ pc.seconds = tmp->tm_sec;
+ tm.tm_isdst = tmp->tm_isdst;
+
+ pc.meridian = MER24;
+ pc.rel_seconds = 0;
+ pc.rel_minutes = 0;
+ pc.rel_hour = 0;
+ pc.rel_day = 0;
+ pc.rel_month = 0;
+ pc.rel_year = 0;
+ pc.dates_seen = 0;
+ pc.days_seen = 0;
+ pc.rels_seen = 0;
+ pc.times_seen = 0;
+ pc.local_zones_seen = 0;
+ pc.zones_seen = 0;
+
+#ifdef HAVE_STRUCT_TM_TM_ZONE
+ pc.local_time_zone_table[0].name = tmp->tm_zone;
+ pc.local_time_zone_table[0].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[0].value = tmp->tm_isdst;
+ pc.local_time_zone_table[1].name = 0;
+
+ /* Probe the names used in the next three calendar quarters, looking
+ for a tm_isdst different from the one we already have. */
+ {
+ int quarter;
+ for (quarter = 1; quarter <= 3; quarter++)
+ {
+ time_t probe = Start + quarter * (90 * 24 * 60 * 60);
+ struct tm *probe_tm = localtime (&probe);
+ if (probe_tm && probe_tm->tm_zone
+ && probe_tm->tm_isdst != pc.local_time_zone_table[0].value)
+ {
+ {
+ pc.local_time_zone_table[1].name = probe_tm->tm_zone;
+ pc.local_time_zone_table[1].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[1].value = probe_tm->tm_isdst;
+ pc.local_time_zone_table[2].name = 0;
+ }
+ break;
+ }
+ }
+ }
+#else
+#ifdef HAVE_TZNAME
+ {
+# ifndef tzname
+ extern char *tzname[];
+# endif
+ int i;
+ for (i = 0; i < 2; i++)
+ {
+ pc.local_time_zone_table[i].name = tzname[i];
+ pc.local_time_zone_table[i].type = tLOCAL_ZONE;
+ pc.local_time_zone_table[i].value = i;
+ }
+ pc.local_time_zone_table[i].name = 0;
+ }
+#else
+ pc.local_time_zone_table[0].name = 0;
+#endif
+#endif
+
+ if (pc.local_time_zone_table[0].name && pc.local_time_zone_table[1].name
+ && ! strcmp (pc.local_time_zone_table[0].name,
+ pc.local_time_zone_table[1].name))
+ {
+ /* This locale uses the same abbreviation for standard and
+ daylight times. So if we see that abbreviation, we don't
+ know whether it's daylight time. */
+ pc.local_time_zone_table[0].value = -1;
+ pc.local_time_zone_table[1].name = 0;
+ }
+
+ if (yyparse (&pc) != 0
+ || 1 < pc.times_seen || 1 < pc.dates_seen || 1 < pc.days_seen
+ || 1 < (pc.local_zones_seen + pc.zones_seen)
+ || (pc.local_zones_seen && 1 < pc.local_isdst))
+ return -1;
+
+ tm.tm_year = to_year (pc.year) - TM_YEAR_BASE + pc.rel_year;
+ tm.tm_mon = pc.month - 1 + pc.rel_month;
+ tm.tm_mday = pc.day + pc.rel_day;
+ if (pc.times_seen || (pc.rels_seen && ! pc.dates_seen && ! pc.days_seen))
+ {
+ tm.tm_hour = to_hour (pc.hour, pc.meridian);
+ if (tm.tm_hour < 0)
+ return -1;
+ tm.tm_min = pc.minutes;
+ tm.tm_sec = pc.seconds;
+ }
+ else
+ {
+ tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
+ }
+
+ /* Let mktime deduce tm_isdst if we have an absolute time stamp,
+ or if the relative time stamp mentions days, months, or years. */
+ if (pc.dates_seen | pc.days_seen | pc.times_seen | pc.rel_day
+ | pc.rel_month | pc.rel_year)
+ tm.tm_isdst = -1;
+
+ /* But if the input explicitly specifies local time with or without
+ DST, give mktime that information. */
+ if (pc.local_zones_seen)
+ tm.tm_isdst = pc.local_isdst;
+
+ tm0 = tm;
+
+ Start = mktime (&tm);
+
+ if (Start == (time_t) -1)
+ {
+
+ /* Guard against falsely reporting errors near the time_t boundaries
+ when parsing times in other time zones. For example, if the min
+ time_t value is 1970-01-01 00:00:00 UTC and we are 8 hours ahead
+ of UTC, then the min localtime value is 1970-01-01 08:00:00; if
+ we apply mktime to 1970-01-01 00:00:00 we will get an error, so
+ we apply mktime to 1970-01-02 08:00:00 instead and adjust the time
+ zone by 24 hours to compensate. This algorithm assumes that
+ there is no DST transition within a day of the time_t boundaries. */
+ if (pc.zones_seen)
+ {
+ tm = tm0;
+ if (tm.tm_year <= EPOCH_YEAR - TM_YEAR_BASE)
+ {
+ tm.tm_mday++;
+ pc.time_zone += 24 * 60;
+ }
+ else
+ {
+ tm.tm_mday--;
+ pc.time_zone -= 24 * 60;
+ }
+ Start = mktime (&tm);
+ }
+
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (pc.days_seen && ! pc.dates_seen)
+ {
+ tm.tm_mday += ((pc.day_number - tm.tm_wday + 7) % 7
+ + 7 * (pc.day_ordinal - (0 < pc.day_ordinal)));
+ tm.tm_isdst = -1;
+ Start = mktime (&tm);
+ if (Start == (time_t) -1)
+ return Start;
+ }
+
+ if (pc.zones_seen)
+ {
+ int delta = pc.time_zone * 60;
+#ifdef HAVE_TM_GMTOFF
+ delta -= tm.tm_gmtoff;
+#else
+ struct tm *gmt = gmtime (&Start);
+ if (! gmt)
+ return -1;
+ delta -= tm_diff (&tm, gmt);
+#endif
+ if ((Start < Start - delta) != (delta < 0))
+ return -1; /* time_t overflow */
+ Start -= delta;
+ }
+
+ /* Add relative hours, minutes, and seconds. Ignore leap seconds;
+ i.e. "+ 10 minutes" means 600 seconds, even if one of them is a
+ leap second. Typically this is not what the user wants, but it's
+ too hard to do it the other way, because the time zone indicator
+ must be applied before relative times, and if mktime is applied
+ again the time zone will be lost. */
+ {
+ time_t t0 = Start;
+ long d1 = 60 * 60 * (long) pc.rel_hour;
+ time_t t1 = t0 + d1;
+ long d2 = 60 * (long) pc.rel_minutes;
+ time_t t2 = t1 + d2;
+ int d3 = pc.rel_seconds;
+ time_t t3 = t2 + d3;
+ if ((d1 / (60 * 60) ^ pc.rel_hour)
+ | (d2 / 60 ^ pc.rel_minutes)
+ | ((t0 + d1 < t0) ^ (d1 < 0))
+ | ((t1 + d2 < t1) ^ (d2 < 0))
+ | ((t2 + d3 < t2) ^ (d3 < 0)))
+ return -1;
+ Start = t3;
+ }
+
+ return Start;
+}
+
+#if TEST
+
+#include <stdio.h>
+
+int
+main (int ac, char **av)
+{
+ char buff[BUFSIZ];
+ time_t d;
+
+ printf ("Enter date, or blank line to exit.\n\t> ");
+ fflush (stdout);
+
+ buff[BUFSIZ - 1] = 0;
+ while (fgets (buff, BUFSIZ - 1, stdin) && buff[0])
+ {
+ d = get_date (buff, 0);
+ if (d == (time_t) -1)
+ printf ("Bad format - couldn't convert.\n");
+ else
+ printf ("%s", ctime (&d));
+ printf ("\t> ");
+ fflush (stdout);
+ }
+ return 0;
+}
+#endif /* defined TEST */
diff --git a/source3/modules/hash_inode.c b/source3/modules/hash_inode.c
new file mode 100644
index 0000000..a914462
--- /dev/null
+++ b/source3/modules/hash_inode.c
@@ -0,0 +1,87 @@
+/*
+ * Unix SMB/Netbios implementation.
+ *
+ * Copyright (c) 2019 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "hash_inode.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include "lib/crypto/gnutls_helpers.h"
+
+SMB_INO_T hash_inode(const SMB_STRUCT_STAT *sbuf, const char *sname)
+{
+ gnutls_hash_hd_t hash_hnd = NULL;
+ uint8_t digest[gnutls_hash_get_len(GNUTLS_DIG_SHA1)];
+ char *upper_sname = NULL;
+ SMB_INO_T result = 0;
+ int rc;
+
+ DBG_DEBUG("hash_inode called for %ju/%ju [%s]\n",
+ (uintmax_t)sbuf->st_ex_dev,
+ (uintmax_t)sbuf->st_ex_ino,
+ sname);
+
+ upper_sname = talloc_strdup_upper(talloc_tos(), sname);
+ SMB_ASSERT(upper_sname != NULL);
+
+ GNUTLS_FIPS140_SET_LAX_MODE();
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA1);
+ if (rc < 0) {
+ goto out;
+ }
+
+ rc = gnutls_hash(hash_hnd,
+ &(sbuf->st_ex_dev),
+ sizeof(sbuf->st_ex_dev));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ goto out;
+ }
+ rc = gnutls_hash(hash_hnd,
+ &(sbuf->st_ex_ino),
+ sizeof(sbuf->st_ex_ino));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ goto out;
+ }
+ rc = gnutls_hash(hash_hnd,
+ upper_sname,
+ talloc_get_size(upper_sname) - 1);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ goto out;
+ }
+
+ gnutls_hash_deinit(hash_hnd, digest);
+
+ memcpy(&result, digest, sizeof(result));
+ DBG_DEBUG("fruit_inode \"%s\": ino=%ju\n",
+ sname, (uintmax_t)result);
+
+out:
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ TALLOC_FREE(upper_sname);
+
+ DBG_DEBUG("hash_inode '%s': ino=%ju\n",
+ sname,
+ (uintmax_t)result);
+
+ return result;
+}
diff --git a/source3/modules/hash_inode.h b/source3/modules/hash_inode.h
new file mode 100644
index 0000000..e08fc48
--- /dev/null
+++ b/source3/modules/hash_inode.h
@@ -0,0 +1,25 @@
+/*
+ * Unix SMB/Netbios implementation.
+ *
+ * Copyright (c) 2019 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _HASH_INODE_H
+#define _HASH_INODE_H
+
+SMB_INO_T hash_inode(const SMB_STRUCT_STAT *sbuf, const char *sname);
+
+#endif /* _HASH_INODE_H */
diff --git a/source3/modules/lib_vxfs.c b/source3/modules/lib_vxfs.c
new file mode 100644
index 0000000..e030cc3
--- /dev/null
+++ b/source3/modules/lib_vxfs.c
@@ -0,0 +1,279 @@
+/*
+ Unix SMB/CIFS implementation.
+ Wrap VxFS xattr calls.
+
+ Copyright (C) Veritas Technologies LLC <www.veritas.com> 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "string.h"
+#include "vfs_vxfs.h"
+
+/*
+ * Available under GPL at
+ * http://www.veritas.com/community/downloads/vxfsmisc-library
+ */
+#define LIBVXFS "/usr/lib64/vxfsmisc.so"
+
+
+static int (*vxfs_setxattr_fd_func) (int fd, const char *name,
+ const void *value, size_t len, int flags);
+static int (*vxfs_getxattr_fd_func) (int fd, const char *name, void *value,
+ size_t *len);
+static int (*vxfs_removexattr_fd_func) (int fd, const char *name);
+static int (*vxfs_listxattr_fd_func) (int fd, void *value, size_t *len);
+static int (*vxfs_setwxattr_fd_func) (int fd);
+static int (*vxfs_checkwxattr_fd_func) (int fd);
+
+int vxfs_setxattr_fd(int fd, const char *name, const void *value,
+ size_t len, int flags)
+{
+ int ret = -1;
+
+ DBG_DEBUG("In vxfs_setxattr_fd fd %d name %s len %zu flags %d\n",
+ fd, name, len, flags);
+ if (vxfs_setxattr_fd_func == NULL) {
+ errno = ENOSYS;
+ return ret;
+ }
+
+ DBG_DEBUG("Calling vxfs_setxattr_fd\n");
+ ret = vxfs_setxattr_fd_func(fd, name, value, len, flags);
+ DBG_DEBUG("vxfs_setxattr_fd ret = %d \n", ret);
+ if (ret) {
+ errno = ret;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int vxfs_getxattr_fd(int fd, const char *name, void *value, size_t len)
+{
+ int ret;
+ size_t size = len;
+ DBG_DEBUG("In vxfs_getxattr_fd fd %d name %s len %zu\n",
+ fd, name, len);
+
+ if (vxfs_getxattr_fd_func == NULL) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ DBG_DEBUG("Calling vxfs_getxattr_fd with %s\n", name);
+ ret = vxfs_getxattr_fd_func(fd, name, value, &size);
+ DBG_DEBUG("vxfs_getxattr_fd ret = %d\n", ret);
+ if (ret) {
+ errno = ret;
+ if (ret == EFBIG) {
+ errno = ERANGE;
+ }
+ return -1;
+ }
+ DBG_DEBUG("vxfs_getxattr_fd done with size %zu\n", size);
+
+ return size;
+}
+
+int vxfs_getxattr_path(const char *path, const char *name, void *value,
+ size_t len)
+{
+ int ret, fd = -1;
+ DBG_DEBUG("In vxfs_getxattr_path path %s name %s len %zu\n",
+ path, name, len);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ DBG_DEBUG("file not opened: vxfs_getxattr_path for %s\n",
+ path);
+ return -1;
+ }
+
+ ret = vxfs_getxattr_fd(fd, name, value, len);
+ close(fd);
+
+ return ret;
+}
+
+int vxfs_removexattr_fd(int fd, const char *name)
+{
+ int ret = 0;
+ DBG_DEBUG("In vxfs_removexattr_fd fd %d name %s\n", fd, name);
+
+ if (vxfs_removexattr_fd_func == NULL) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ DBG_DEBUG("Calling vxfs_removexattr_fd with %s\n", name);
+ ret = vxfs_removexattr_fd_func(fd, name);
+ if (ret) {
+ errno = ret;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int vxfs_listxattr_fd(int fd, char *list, size_t size)
+{
+ int ret;
+ size_t len = size;
+ DBG_DEBUG("In vxfs_listxattr_fd fd %d list %s size %zu\n", fd, list, size);
+
+ if (vxfs_listxattr_fd_func == NULL) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ ret = vxfs_listxattr_fd_func(fd, list, &len);
+ DBG_DEBUG("vxfs_listxattr_fd: returned ret = %d\n", ret);
+ DBG_DEBUG("In vxfs_listxattr_fd done with len %zu\n", len);
+ if (ret) {
+ errno = ret;
+ if (ret == EFBIG) {
+ errno = ERANGE;
+ }
+ return -1;
+ }
+
+ return len;
+}
+
+int vxfs_setwxattr_fd(int fd)
+{
+ int ret = 0;
+ DBG_DEBUG("In vxfs_setwxattr_fd fd %d\n", fd);
+
+ if (vxfs_setwxattr_fd_func == NULL) {
+ errno = ENOSYS;
+ return -1;
+ }
+ ret = vxfs_setwxattr_fd_func(fd);
+ DBG_DEBUG("ret = %d\n", ret);
+ if (ret != 0) {
+ errno = ret;
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int vxfs_setwxattr_path(const char *path, bool is_dir)
+{
+ int ret, fd = -1;
+ DBG_DEBUG("In vxfs_setwxattr_path path %s is_dir %d\n", path, is_dir);
+
+ if (is_dir) {
+ fd = open(path, O_RDONLY|O_DIRECTORY);
+ } else {
+ fd = open(path, O_WRONLY);
+ }
+ if (fd == -1) {
+ DBG_DEBUG("file %s not opened, errno:%s\n",
+ path, strerror(errno));
+ return -1;
+ }
+
+ ret = vxfs_setwxattr_fd(fd);
+ DBG_DEBUG("ret = %d\n", ret);
+ close(fd);
+
+ return ret;
+}
+
+int vxfs_checkwxattr_fd(int fd)
+{
+ int ret;
+ DBG_DEBUG("In vxfs_checkwxattr_fd fd %d\n", fd);
+
+ if (vxfs_checkwxattr_fd_func == NULL) {
+ errno = ENOSYS;
+ return -1;
+ }
+ ret = vxfs_checkwxattr_fd_func(fd);
+ DBG_DEBUG("ret = %d\n", ret);
+ if (ret != 0) {
+ errno = ret;
+ ret = -1;
+ }
+ return ret;
+}
+
+int vxfs_checkwxattr_path(const char *path)
+{
+ int ret, fd = -1;
+ DBG_DEBUG("In vxfs_checkwxattr_path path %s\n", path);
+
+ fd = open(path, O_RDONLY);
+
+ if (fd == -1) {
+ DBG_DEBUG("file %s not opened, errno:%s\n",
+ path, strerror(errno));
+ return -1;
+ }
+ ret = vxfs_checkwxattr_fd(fd);
+ close(fd);
+
+ return ret;
+}
+
+static bool load_lib_vxfs_function(void *lib_handle, void *fn_ptr,
+ const char *fnc_name)
+{
+ void **vlib_handle = (void **)lib_handle;
+ void **fn_pointer = (void **)fn_ptr;
+
+ *fn_pointer = dlsym(*vlib_handle, fnc_name);
+ if (*fn_pointer == NULL) {
+ DEBUG(10, ("Cannot find symbol for %s\n", fnc_name));
+ return true;
+ }
+
+ return false;
+}
+
+void vxfs_init()
+{
+ static void *lib_handle = NULL;
+
+ if (lib_handle != NULL ) {
+ return;
+ }
+
+ lib_handle = dlopen(LIBVXFS, RTLD_LAZY);
+ if (lib_handle == NULL) {
+ DEBUG(10, ("Cannot get lib handle\n"));
+ return;
+ }
+
+ DEBUG(10, ("Calling vxfs_init\n"));
+ load_lib_vxfs_function(&lib_handle, &vxfs_setxattr_fd_func,
+ "vxfs_nxattr_set");
+ load_lib_vxfs_function(&lib_handle, &vxfs_getxattr_fd_func,
+ "vxfs_nxattr_get");
+ load_lib_vxfs_function(&lib_handle, &vxfs_removexattr_fd_func,
+ "vxfs_nxattr_remove");
+ load_lib_vxfs_function(&lib_handle, &vxfs_listxattr_fd_func,
+ "vxfs_nxattr_list");
+ load_lib_vxfs_function(&lib_handle, &vxfs_setwxattr_fd_func,
+ "vxfs_wattr_set");
+ load_lib_vxfs_function(&lib_handle, &vxfs_checkwxattr_fd_func,
+ "vxfs_wattr_check");
+
+}
diff --git a/source3/modules/nfs41acl.x b/source3/modules/nfs41acl.x
new file mode 100644
index 0000000..9b3681d
--- /dev/null
+++ b/source3/modules/nfs41acl.x
@@ -0,0 +1,111 @@
+typedef opaque utf8string<>;
+typedef utf8string utf8str_mixed;
+
+const ACE4_ACCESS_ALLOWED_ACE_TYPE = 0x00000000;
+const ACE4_ACCESS_DENIED_ACE_TYPE = 0x00000001;
+const ACE4_SYSTEM_AUDIT_ACE_TYPE = 0x00000002;
+const ACE4_SYSTEM_ALARM_ACE_TYPE = 0x00000003;
+
+typedef u_int acetype4;
+
+const ACE4_FILE_INHERIT_ACE = 0x00000001;
+const ACE4_DIRECTORY_INHERIT_ACE = 0x00000002;
+const ACE4_NO_PROPAGATE_INHERIT_ACE = 0x00000004;
+const ACE4_INHERIT_ONLY_ACE = 0x00000008;
+const ACE4_SUCCESSFUL_ACCESS_ACE_FLAG = 0x00000010;
+const ACE4_FAILED_ACCESS_ACE_FLAG = 0x00000020;
+const ACE4_IDENTIFIER_GROUP = 0x00000040;
+const ACE4_INHERITED_ACE = 0x00000080;
+
+typedef u_int aceflag4;
+
+/*
+ * The following aceiflag4 is extensions for RFC 5661 that deals with storing
+ * identifiers as numerical ids instead UTF8 strings in order to avoid wasting
+ * CPU cycles for the costly conversion.
+ *
+ * Placed in a separate field to avoid ever running into conflicts with newly
+ * defined NFSv4 flags.
+ */
+
+const ACEI4_SPECIAL_WHO = 0x00000001;
+
+typedef u_int aceiflag4;
+
+/*
+ * Numerical representation of special identifiers from 6.2.1.5.
+ * ACEI4_SPECIAL_WHO MUST be set in nfsace4.aceiflag4.
+ */
+const ACE4_SPECIAL_OWNER = 1;
+const ACE4_SPECIAL_GROUP = 2;
+const ACE4_SPECIAL_EVERYONE = 3;
+const ACE4_SPECIAL_INTERACTIVE = 4;
+const ACE4_SPECIAL_NETWORK = 5;
+const ACE4_SPECIAL_DIALUP = 6;
+const ACE4_SPECIAL_BATCH = 7;
+const ACE4_SPECIAL_ANONYMOUS = 8;
+const ACE4_SPECIAL_AUTHENTICATED = 9;
+const ACE4_SPECIAL_SERVICE = 10;
+
+const ACE4_READ_DATA = 0x00000001;
+const ACE4_LIST_DIRECTORY = 0x00000001;
+const ACE4_WRITE_DATA = 0x00000002;
+const ACE4_ADD_FILE = 0x00000002;
+const ACE4_APPEND_DATA = 0x00000004;
+const ACE4_ADD_SUBDIRECTORY = 0x00000004;
+const ACE4_READ_NAMED_ATTRS = 0x00000008;
+const ACE4_WRITE_NAMED_ATTRS = 0x00000010;
+const ACE4_EXECUTE = 0x00000020;
+const ACE4_DELETE_CHILD = 0x00000040;
+const ACE4_READ_ATTRIBUTES = 0x00000080;
+const ACE4_WRITE_ATTRIBUTES = 0x00000100;
+const ACE4_WRITE_RETENTION = 0x00000200;
+const ACE4_WRITE_RETENTION_HOLD = 0x00000400;
+
+const ACE4_DELETE = 0x00010000;
+const ACE4_READ_ACL = 0x00020000;
+const ACE4_WRITE_ACL = 0x00040000;
+const ACE4_WRITE_OWNER = 0x00080000;
+const ACE4_SYNCHRONIZE = 0x00100000;
+
+typedef u_int acemask4;
+
+/* ACL structure definition as per RFC 7530 Section-6.2.1 */
+struct nfsace4 {
+ acetype4 type;
+ aceflag4 flag;
+ acemask4 access_mask;
+ utf8str_mixed who;
+};
+
+struct nfsace4i {
+ acetype4 type;
+ aceflag4 flag;
+ aceiflag4 iflag;
+ acemask4 access_mask;
+ u_int who;
+};
+
+const ACL4_XATTR_VERSION_40 = 0;
+const ACL4_XATTR_VERSION_41 = 1;
+const ACL4_XATTR_VERSION_DEFAULT = ACL4_XATTR_VERSION_40;
+
+const ACL4_AUTO_INHERIT = 0x00000001;
+const ACL4_PROTECTED = 0x00000002;
+const ACL4_DEFAULTED = 0x00000004;
+
+typedef u_int aclflag4;
+
+struct nfsacl40 {
+ nfsace4 na40_aces<>;
+};
+
+struct nfsacl41 {
+ aclflag4 na41_flag;
+ nfsace4 na41_aces<>;
+};
+
+struct nfsacl41i {
+ aclflag4 na41_flag;
+ nfsace4i na41_aces<>;
+};
diff --git a/source3/modules/nfs4_acls.c b/source3/modules/nfs4_acls.c
new file mode 100644
index 0000000..c80f839
--- /dev/null
+++ b/source3/modules/nfs4_acls.c
@@ -0,0 +1,1223 @@
+/*
+ * NFS4 ACL handling
+ *
+ * Copyright (C) Jim McDonough, 2006
+ * Copyright (C) Christof Schmitt 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "nfs4_acls.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/idmap.h"
+#include "../libcli/security/dom_sid.h"
+#include "../libcli/security/security.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "system/filesys.h"
+#include "passdb/lookup_sid.h"
+#include "util_tdb.h"
+#include "lib/param/loadparm.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+#define SMBACL4_PARAM_TYPE_NAME "nfs4"
+
+extern const struct generic_mapping file_generic_mapping;
+
+struct SMB4ACE_T
+{
+ SMB_ACE4PROP_T prop;
+ struct SMB4ACE_T *next;
+};
+
+struct SMB4ACL_T
+{
+ uint16_t controlflags;
+ uint32_t naces;
+ struct SMB4ACE_T *first;
+ struct SMB4ACE_T *last;
+};
+
+/*
+ * Gather special parameters for NFS4 ACL handling
+ */
+int smbacl4_get_vfs_params(struct connection_struct *conn,
+ struct smbacl4_vfs_params *params)
+{
+ static const struct enum_list enum_smbacl4_modes[] = {
+ { e_simple, "simple" },
+ { e_special, "special" },
+ { -1 , NULL }
+ };
+ static const struct enum_list enum_smbacl4_acedups[] = {
+ { e_dontcare, "dontcare" },
+ { e_reject, "reject" },
+ { e_ignore, "ignore" },
+ { e_merge, "merge" },
+ { -1 , NULL }
+ };
+ int enumval;
+
+ *params = (struct smbacl4_vfs_params) { 0 };
+
+ enumval = lp_parm_enum(SNUM(conn), SMBACL4_PARAM_TYPE_NAME, "mode",
+ enum_smbacl4_modes, e_simple);
+ if (enumval == -1) {
+ DEBUG(10, ("value for %s:mode unknown\n",
+ SMBACL4_PARAM_TYPE_NAME));
+ return -1;
+ }
+ params->mode = (enum smbacl4_mode_enum)enumval;
+ if (params->mode == e_special) {
+ DBG_WARNING("nfs4:mode special is deprecated.\n");
+ }
+
+ params->do_chown = lp_parm_bool(SNUM(conn), SMBACL4_PARAM_TYPE_NAME,
+ "chown", true);
+
+ enumval = lp_parm_enum(SNUM(conn), SMBACL4_PARAM_TYPE_NAME, "acedup",
+ enum_smbacl4_acedups, e_merge);
+ if (enumval == -1) {
+ DEBUG(10, ("value for %s:acedup unknown\n",
+ SMBACL4_PARAM_TYPE_NAME));
+ return -1;
+ }
+ params->acedup = (enum smbacl4_acedup_enum)enumval;
+ if (params->acedup == e_ignore) {
+ DBG_WARNING("nfs4:acedup ignore is deprecated.\n");
+ }
+ if (params->acedup == e_reject) {
+ DBG_WARNING("nfs4:acedup ignore is deprecated.\n");
+ }
+
+ params->map_full_control = lp_acl_map_full_control(SNUM(conn));
+
+ DEBUG(10, ("mode:%s, do_chown:%s, acedup: %s map full control:%s\n",
+ enum_smbacl4_modes[params->mode].name,
+ params->do_chown ? "true" : "false",
+ enum_smbacl4_acedups[params->acedup].name,
+ params->map_full_control ? "true" : "false"));
+
+ return 0;
+}
+
+static int fstatat_with_cap_dac_override(int fd,
+ const char *pathname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags,
+ bool fake_dir_create_times)
+{
+ int ret;
+
+ set_effective_capability(DAC_OVERRIDE_CAPABILITY);
+ ret = sys_fstatat(fd,
+ pathname,
+ sbuf,
+ flags,
+ fake_dir_create_times);
+ drop_effective_capability(DAC_OVERRIDE_CAPABILITY);
+
+ return ret;
+}
+
+static int stat_with_cap_dac_override(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname, int flag)
+{
+ bool fake_dctime = lp_fake_directory_create_times(SNUM(handle->conn));
+ int fd = -1;
+ NTSTATUS status;
+ struct smb_filename *dir_name = NULL;
+ struct smb_filename *rel_name = NULL;
+ int ret = -1;
+#ifdef O_PATH
+ int open_flags = O_PATH;
+#else
+ int open_flags = O_RDONLY;
+#endif
+
+ status = SMB_VFS_PARENT_PATHNAME(handle->conn,
+ talloc_tos(),
+ smb_fname,
+ &dir_name,
+ &rel_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ fd = open(dir_name->base_name, open_flags, 0);
+ if (fd == -1) {
+ TALLOC_FREE(dir_name);
+ return -1;
+ }
+
+ ret = fstatat_with_cap_dac_override(fd,
+ rel_name->base_name,
+ &smb_fname->st,
+ flag,
+ fake_dctime);
+
+ TALLOC_FREE(dir_name);
+ close(fd);
+
+ return ret;
+}
+
+int nfs4_acl_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (ret == -1 && errno == EACCES) {
+ DEBUG(10, ("Trying stat with capability for %s\n",
+ smb_fname->base_name));
+ ret = stat_with_cap_dac_override(handle, smb_fname, 0);
+ }
+ return ret;
+}
+
+static int fstat_with_cap_dac_override(int fd, SMB_STRUCT_STAT *sbuf,
+ bool fake_dir_create_times)
+{
+ int ret;
+
+ set_effective_capability(DAC_OVERRIDE_CAPABILITY);
+ ret = sys_fstat(fd, sbuf, fake_dir_create_times);
+ drop_effective_capability(DAC_OVERRIDE_CAPABILITY);
+
+ return ret;
+}
+
+int nfs4_acl_fstat(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (ret == -1 && errno == EACCES) {
+ bool fake_dctime =
+ lp_fake_directory_create_times(SNUM(handle->conn));
+
+ DBG_DEBUG("fstat for %s failed with EACCES. Trying with "
+ "CAP_DAC_OVERRIDE.\n", fsp->fsp_name->base_name);
+ ret = fstat_with_cap_dac_override(fsp_get_pathref_fd(fsp),
+ sbuf,
+ fake_dctime);
+ }
+
+ return ret;
+}
+
+int nfs4_acl_lstat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (ret == -1 && errno == EACCES) {
+ DEBUG(10, ("Trying lstat with capability for %s\n",
+ smb_fname->base_name));
+ ret = stat_with_cap_dac_override(handle, smb_fname,
+ AT_SYMLINK_NOFOLLOW);
+ }
+ return ret;
+}
+
+int nfs4_acl_fstatat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_FSTATAT(handle, dirfsp, smb_fname, sbuf, flags);
+ if (ret == -1 && errno == EACCES) {
+ bool fake_dctime =
+ lp_fake_directory_create_times(SNUM(handle->conn));
+
+ DBG_DEBUG("fstatat for %s failed with EACCES. Trying with "
+ "CAP_DAC_OVERRIDE.\n", dirfsp->fsp_name->base_name);
+ ret = fstatat_with_cap_dac_override(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ sbuf,
+ flags,
+ fake_dctime);
+ }
+
+ return ret;
+}
+
+/************************************************
+ Split the ACE flag mapping between nfs4 and Windows
+ into two separate functions rather than trying to do
+ it inline. Allows us to carefully control what flags
+ are mapped to what in one place.
+************************************************/
+
+static uint32_t map_nfs4_ace_flags_to_windows_ace_flags(
+ uint32_t nfs4_ace_flags)
+{
+ uint32_t win_ace_flags = 0;
+
+ /* The nfs4 flags <= 0xf map perfectly. */
+ win_ace_flags = nfs4_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+
+ /* flags greater than 0xf have diverged :-(. */
+ /* See the nfs4 ace flag definitions here:
+ http://www.ietf.org/rfc/rfc3530.txt.
+ And the Windows ace flag definitions here:
+ librpc/idl/security.idl. */
+ if (nfs4_ace_flags & SMB_ACE4_INHERITED_ACE) {
+ win_ace_flags |= SEC_ACE_FLAG_INHERITED_ACE;
+ }
+
+ return win_ace_flags;
+}
+
+static uint32_t map_windows_ace_flags_to_nfs4_ace_flags(uint32_t win_ace_flags)
+{
+ uint32_t nfs4_ace_flags = 0;
+
+ /* The windows flags <= 0xf map perfectly. */
+ nfs4_ace_flags = win_ace_flags & (SMB_ACE4_FILE_INHERIT_ACE|
+ SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_NO_PROPAGATE_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE);
+
+ /* flags greater than 0xf have diverged :-(. */
+ /* See the nfs4 ace flag definitions here:
+ http://www.ietf.org/rfc/rfc3530.txt.
+ And the Windows ace flag definitions here:
+ librpc/idl/security.idl. */
+ if (win_ace_flags & SEC_ACE_FLAG_INHERITED_ACE) {
+ nfs4_ace_flags |= SMB_ACE4_INHERITED_ACE;
+ }
+
+ return nfs4_ace_flags;
+}
+
+struct SMB4ACL_T *smb_create_smb4acl(TALLOC_CTX *mem_ctx)
+{
+ struct SMB4ACL_T *theacl;
+
+ theacl = talloc_zero(mem_ctx, struct SMB4ACL_T);
+ if (theacl==NULL)
+ {
+ DEBUG(0, ("TALLOC_SIZE failed\n"));
+ errno = ENOMEM;
+ return NULL;
+ }
+ theacl->controlflags = SEC_DESC_SELF_RELATIVE;
+ /* theacl->first, last = NULL not needed */
+ return theacl;
+}
+
+struct SMB4ACE_T *smb_add_ace4(struct SMB4ACL_T *acl, SMB_ACE4PROP_T *prop)
+{
+ struct SMB4ACE_T *ace;
+
+ ace = talloc_zero(acl, struct SMB4ACE_T);
+ if (ace==NULL)
+ {
+ DBG_ERR("talloc_zero failed\n");
+ errno = ENOMEM;
+ return NULL;
+ }
+ ace->prop = *prop;
+
+ if (acl->first==NULL)
+ {
+ acl->first = ace;
+ acl->last = ace;
+ } else {
+ acl->last->next = ace;
+ acl->last = ace;
+ }
+ acl->naces++;
+
+ return ace;
+}
+
+SMB_ACE4PROP_T *smb_get_ace4(struct SMB4ACE_T *ace)
+{
+ if (ace == NULL) {
+ return NULL;
+ }
+
+ return &ace->prop;
+}
+
+struct SMB4ACE_T *smb_next_ace4(struct SMB4ACE_T *ace)
+{
+ if (ace == NULL) {
+ return NULL;
+ }
+
+ return ace->next;
+}
+
+struct SMB4ACE_T *smb_first_ace4(struct SMB4ACL_T *acl)
+{
+ if (acl == NULL) {
+ return NULL;
+ }
+
+ return acl->first;
+}
+
+uint32_t smb_get_naces(struct SMB4ACL_T *acl)
+{
+ if (acl == NULL) {
+ return 0;
+ }
+
+ return acl->naces;
+}
+
+uint16_t smbacl4_get_controlflags(struct SMB4ACL_T *acl)
+{
+ if (acl == NULL) {
+ return 0;
+ }
+
+ return acl->controlflags;
+}
+
+bool smbacl4_set_controlflags(struct SMB4ACL_T *acl, uint16_t controlflags)
+{
+ if (acl == NULL) {
+ return false;
+ }
+
+ acl->controlflags = controlflags;
+ return true;
+}
+
+bool nfs_ace_is_inherit(SMB_ACE4PROP_T *ace)
+{
+ return ace->aceFlags & (SMB_ACE4_INHERIT_ONLY_ACE|
+ SMB_ACE4_FILE_INHERIT_ACE|
+ SMB_ACE4_DIRECTORY_INHERIT_ACE);
+}
+
+static int smbacl4_GetFileOwner(struct connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *psbuf)
+{
+ ZERO_STRUCTP(psbuf);
+
+ /* Get the stat struct for the owner info. */
+ if (vfs_stat_smb_basename(conn, smb_fname, psbuf) != 0)
+ {
+ DEBUG(8, ("vfs_stat_smb_basename failed with error %s\n",
+ strerror(errno)));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void check_for_duplicate_sec_ace(struct security_ace *nt_ace_list,
+ int *good_aces)
+{
+ struct security_ace *last = NULL;
+ int i;
+
+ if (*good_aces < 2) {
+ return;
+ }
+
+ last = &nt_ace_list[(*good_aces) - 1];
+
+ for (i = 0; i < (*good_aces) - 1; i++) {
+ struct security_ace *cur = &nt_ace_list[i];
+
+ if (cur->type == last->type &&
+ cur->flags == last->flags &&
+ cur->access_mask == last->access_mask &&
+ dom_sid_equal(&cur->trustee, &last->trustee))
+ {
+ struct dom_sid_buf sid_buf;
+
+ DBG_INFO("Removing duplicate entry for SID %s.\n",
+ dom_sid_str_buf(&last->trustee, &sid_buf));
+ (*good_aces)--;
+ }
+ }
+}
+
+static bool smbacl4_nfs42win(TALLOC_CTX *mem_ctx,
+ const struct smbacl4_vfs_params *params,
+ struct SMB4ACL_T *acl, /* in */
+ struct dom_sid *psid_owner, /* in */
+ struct dom_sid *psid_group, /* in */
+ bool is_directory, /* in */
+ struct security_ace **ppnt_ace_list, /* out */
+ int *pgood_aces /* out */
+)
+{
+ struct SMB4ACE_T *aceint;
+ struct security_ace *nt_ace_list = NULL;
+ int good_aces = 0;
+
+ DEBUG(10, ("%s entered\n", __func__));
+
+ nt_ace_list = talloc_zero_array(mem_ctx, struct security_ace,
+ 2 * acl->naces);
+ if (nt_ace_list==NULL)
+ {
+ DEBUG(10, ("talloc error with %d aces\n", acl->naces));
+ errno = ENOMEM;
+ return false;
+ }
+
+ for (aceint = acl->first; aceint != NULL; aceint = aceint->next) {
+ uint32_t mask;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ SMB_ACE4PROP_T *ace = &aceint->prop;
+ uint32_t win_ace_flags;
+
+ DEBUG(10, ("type: %d, iflags: %x, flags: %x, "
+ "mask: %x, who: %d\n",
+ ace->aceType, ace->flags,
+ ace->aceFlags, ace->aceMask, ace->who.id));
+
+ if (ace->flags & SMB_ACE4_ID_SPECIAL) {
+ switch (ace->who.special_id) {
+ case SMB_ACE4_WHO_OWNER:
+ sid_copy(&sid, psid_owner);
+ break;
+ case SMB_ACE4_WHO_GROUP:
+ sid_copy(&sid, psid_group);
+ break;
+ case SMB_ACE4_WHO_EVERYONE:
+ sid_copy(&sid, &global_sid_World);
+ break;
+ default:
+ DEBUG(8, ("invalid special who id %d "
+ "ignored\n", ace->who.special_id));
+ continue;
+ }
+ } else {
+ if (ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) {
+ gid_to_sid(&sid, ace->who.gid);
+ } else {
+ uid_to_sid(&sid, ace->who.uid);
+ }
+ }
+ DEBUG(10, ("mapped %d to %s\n", ace->who.id,
+ dom_sid_str_buf(&sid, &buf)));
+
+ if (!is_directory && params->map_full_control) {
+ /*
+ * Do we have all access except DELETE_CHILD
+ * (not caring about the delete bit).
+ */
+ uint32_t test_mask = ((ace->aceMask|SMB_ACE4_DELETE|SMB_ACE4_DELETE_CHILD) &
+ SMB_ACE4_ALL_MASKS);
+ if (test_mask == SMB_ACE4_ALL_MASKS) {
+ ace->aceMask |= SMB_ACE4_DELETE_CHILD;
+ }
+ }
+
+ win_ace_flags = map_nfs4_ace_flags_to_windows_ace_flags(
+ ace->aceFlags);
+ if (!is_directory &&
+ (win_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT))) {
+ /*
+ * GPFS sets inherits dir_inherit and file_inherit flags
+ * to files, too, which confuses windows, and seems to
+ * be wrong anyways. ==> Map these bits away for files.
+ */
+ DEBUG(10, ("removing inherit flags from nfs4 ace\n"));
+ win_ace_flags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT);
+ }
+ DEBUG(10, ("Windows mapped ace flags: 0x%x => 0x%x\n",
+ ace->aceFlags, win_ace_flags));
+
+ mask = ace->aceMask;
+
+ /* Mapping of owner@ and group@ to creator owner and
+ creator group. Keep old behavior in mode special. */
+ if (params->mode != e_special &&
+ ace->flags & SMB_ACE4_ID_SPECIAL &&
+ (ace->who.special_id == SMB_ACE4_WHO_OWNER ||
+ ace->who.special_id == SMB_ACE4_WHO_GROUP)) {
+ DEBUG(10, ("Map special entry\n"));
+ if (!(win_ace_flags & SEC_ACE_FLAG_INHERIT_ONLY)) {
+ uint32_t win_ace_flags_current;
+ DEBUG(10, ("Map current sid\n"));
+ win_ace_flags_current = win_ace_flags &
+ ~(SEC_ACE_FLAG_OBJECT_INHERIT |
+ SEC_ACE_FLAG_CONTAINER_INHERIT);
+ init_sec_ace(&nt_ace_list[good_aces++], &sid,
+ ace->aceType, mask,
+ win_ace_flags_current);
+ }
+ if (ace->who.special_id == SMB_ACE4_WHO_OWNER &&
+ win_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT |
+ SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ uint32_t win_ace_flags_creator;
+ DEBUG(10, ("Map creator owner\n"));
+ win_ace_flags_creator = win_ace_flags |
+ SMB_ACE4_INHERIT_ONLY_ACE;
+ init_sec_ace(&nt_ace_list[good_aces++],
+ &global_sid_Creator_Owner,
+ ace->aceType, mask,
+ win_ace_flags_creator);
+ }
+ if (ace->who.special_id == SMB_ACE4_WHO_GROUP &&
+ win_ace_flags & (SEC_ACE_FLAG_OBJECT_INHERIT |
+ SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ uint32_t win_ace_flags_creator;
+ DEBUG(10, ("Map creator owner group\n"));
+ win_ace_flags_creator = win_ace_flags |
+ SMB_ACE4_INHERIT_ONLY_ACE;
+ init_sec_ace(&nt_ace_list[good_aces++],
+ &global_sid_Creator_Group,
+ ace->aceType, mask,
+ win_ace_flags_creator);
+ }
+ } else {
+ DEBUG(10, ("Map normal sid\n"));
+ init_sec_ace(&nt_ace_list[good_aces++], &sid,
+ ace->aceType, mask,
+ win_ace_flags);
+ }
+
+ check_for_duplicate_sec_ace(nt_ace_list, &good_aces);
+ }
+
+ nt_ace_list = talloc_realloc(mem_ctx, nt_ace_list, struct security_ace,
+ good_aces);
+
+ /* returns a NULL ace list when good_aces is zero. */
+ if (good_aces && nt_ace_list == NULL) {
+ DEBUG(10, ("realloc error with %d aces\n", good_aces));
+ errno = ENOMEM;
+ return false;
+ }
+
+ *ppnt_ace_list = nt_ace_list;
+ *pgood_aces = good_aces;
+
+ return true;
+}
+
+static NTSTATUS smb_get_nt_acl_nfs4_common(const SMB_STRUCT_STAT *sbuf,
+ const struct smbacl4_vfs_params *params,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc,
+ struct SMB4ACL_T *theacl)
+{
+ int good_aces = 0;
+ struct dom_sid sid_owner, sid_group;
+ size_t sd_size = 0;
+ struct security_ace *nt_ace_list = NULL;
+ struct security_acl *psa = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ok;
+
+ if (theacl==NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_ACCESS_DENIED; /* special because we
+ * need to think through
+ * the null case.*/
+ }
+
+ uid_to_sid(&sid_owner, sbuf->st_ex_uid);
+ gid_to_sid(&sid_group, sbuf->st_ex_gid);
+
+ ok = smbacl4_nfs42win(frame, params, theacl, &sid_owner, &sid_group,
+ S_ISDIR(sbuf->st_ex_mode),
+ &nt_ace_list, &good_aces);
+ if (!ok) {
+ DEBUG(8,("smbacl4_nfs42win failed\n"));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix(errno);
+ }
+
+ psa = make_sec_acl(frame, NT4_ACL_REVISION, good_aces, nt_ace_list);
+ if (psa == NULL) {
+ DEBUG(2,("make_sec_acl failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10,("after make sec_acl\n"));
+ *ppdesc = make_sec_desc(
+ mem_ctx, SD_REVISION, smbacl4_get_controlflags(theacl),
+ (security_info & SECINFO_OWNER) ? &sid_owner : NULL,
+ (security_info & SECINFO_GROUP) ? &sid_group : NULL,
+ NULL, psa, &sd_size);
+ if (*ppdesc==NULL) {
+ DEBUG(2,("make_sec_desc failed\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10, ("smb_get_nt_acl_nfs4_common successfully exited with "
+ "sd_size %d\n",
+ (int)ndr_size_security_descriptor(*ppdesc, 0)));
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb_fget_nt_acl_nfs4(files_struct *fsp,
+ const struct smbacl4_vfs_params *pparams,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc,
+ struct SMB4ACL_T *theacl)
+{
+ struct smbacl4_vfs_params params;
+
+ DEBUG(10, ("smb_fget_nt_acl_nfs4 invoked for %s\n", fsp_str_dbg(fsp)));
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ NTSTATUS status;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (pparams == NULL) {
+ /* Special behaviours */
+ if (smbacl4_get_vfs_params(fsp->conn, &params)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pparams = &params;
+ }
+
+ return smb_get_nt_acl_nfs4_common(&fsp->fsp_name->st, pparams,
+ security_info,
+ mem_ctx, ppdesc, theacl);
+}
+
+NTSTATUS smb_get_nt_acl_nfs4(struct connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ const struct smbacl4_vfs_params *pparams,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc,
+ struct SMB4ACL_T *theacl)
+{
+ SMB_STRUCT_STAT sbuf;
+ struct smbacl4_vfs_params params;
+ const SMB_STRUCT_STAT *psbuf = NULL;
+
+ DEBUG(10, ("smb_get_nt_acl_nfs4 invoked for %s\n",
+ smb_fname->base_name));
+
+ if (VALID_STAT(smb_fname->st)) {
+ psbuf = &smb_fname->st;
+ }
+
+ if (psbuf == NULL) {
+ if (smbacl4_GetFileOwner(conn, smb_fname, &sbuf)) {
+ return map_nt_error_from_unix(errno);
+ }
+ psbuf = &sbuf;
+ }
+
+ if (pparams == NULL) {
+ /* Special behaviours */
+ if (smbacl4_get_vfs_params(conn, &params)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pparams = &params;
+ }
+
+ return smb_get_nt_acl_nfs4_common(psbuf, pparams, security_info,
+ mem_ctx, ppdesc, theacl);
+}
+
+static void smbacl4_dump_nfs4acl(int level, struct SMB4ACL_T *acl)
+{
+ struct SMB4ACE_T *aceint;
+
+ DEBUG(level, ("NFS4ACL: size=%d\n", acl->naces));
+
+ for (aceint = acl->first; aceint != NULL; aceint = aceint->next) {
+ SMB_ACE4PROP_T *ace = &aceint->prop;
+
+ DEBUG(level, ("\tACE: type=%d, flags=0x%x, fflags=0x%x, "
+ "mask=0x%x, id=%d\n",
+ ace->aceType,
+ ace->aceFlags, ace->flags,
+ ace->aceMask,
+ ace->who.id));
+ }
+}
+
+/*
+ * Find 2 NFS4 who-special ACE property (non-copy!!!)
+ * match nonzero if "special" and who is equal
+ * return ace if found matching; otherwise NULL
+ */
+static SMB_ACE4PROP_T *smbacl4_find_equal_special(
+ struct SMB4ACL_T *acl,
+ SMB_ACE4PROP_T *aceNew)
+{
+ struct SMB4ACE_T *aceint;
+
+ for (aceint = acl->first; aceint != NULL; aceint = aceint->next) {
+ SMB_ACE4PROP_T *ace = &aceint->prop;
+
+ DEBUG(10,("ace type:0x%x flags:0x%x aceFlags:0x%x "
+ "new type:0x%x flags:0x%x aceFlags:0x%x\n",
+ ace->aceType, ace->flags, ace->aceFlags,
+ aceNew->aceType, aceNew->flags,aceNew->aceFlags));
+
+ if (ace->flags == aceNew->flags &&
+ ace->aceType==aceNew->aceType &&
+ ace->aceFlags==aceNew->aceFlags)
+ {
+ /* keep type safety; e.g. gid is an u.short */
+ if (ace->flags & SMB_ACE4_ID_SPECIAL)
+ {
+ if (ace->who.special_id ==
+ aceNew->who.special_id)
+ return ace;
+ } else {
+ if (ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP)
+ {
+ if (ace->who.gid==aceNew->who.gid)
+ return ace;
+ } else {
+ if (ace->who.uid==aceNew->who.uid)
+ return ace;
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static int smbacl4_MergeIgnoreReject(enum smbacl4_acedup_enum acedup,
+ struct SMB4ACL_T *theacl,
+ SMB_ACE4PROP_T *ace,
+ bool *paddNewACE)
+{
+ int result = 0;
+ SMB_ACE4PROP_T *ace4found = smbacl4_find_equal_special(theacl, ace);
+ if (ace4found)
+ {
+ switch(acedup)
+ {
+ case e_merge: /* "merge" flags */
+ *paddNewACE = false;
+ ace4found->aceFlags |= ace->aceFlags;
+ ace4found->aceMask |= ace->aceMask;
+ break;
+ case e_ignore: /* leave out this record */
+ *paddNewACE = false;
+ break;
+ case e_reject: /* do an error */
+ DBG_INFO("ACL rejected by duplicate nt ace.\n");
+ errno = EINVAL; /* SHOULD be set on any _real_ error */
+ result = -1;
+ break;
+ default:
+ break;
+ }
+ }
+ return result;
+}
+
+static int nfs4_acl_add_ace(enum smbacl4_acedup_enum acedup,
+ struct SMB4ACL_T *nfs4_acl,
+ SMB_ACE4PROP_T *nfs4_ace)
+{
+ bool add_ace = true;
+
+ if (acedup != e_dontcare) {
+ int ret;
+
+ ret = smbacl4_MergeIgnoreReject(acedup, nfs4_acl,
+ nfs4_ace, &add_ace);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ if (add_ace) {
+ smb_add_ace4(nfs4_acl, nfs4_ace);
+ }
+
+ return 0;
+}
+
+static int nfs4_acl_add_sec_ace(bool is_directory,
+ const struct smbacl4_vfs_params *params,
+ uid_t ownerUID,
+ gid_t ownerGID,
+ const struct security_ace *ace_nt,
+ struct SMB4ACL_T *nfs4_acl)
+{
+ struct dom_sid_buf buf;
+ SMB_ACE4PROP_T nfs4_ace = { 0 };
+ SMB_ACE4PROP_T nfs4_ace_2 = { 0 };
+ bool add_ace2 = false;
+ int ret;
+
+ DEBUG(10, ("got ace for %s\n",
+ dom_sid_str_buf(&ace_nt->trustee, &buf)));
+
+ /* only ACCESS|DENY supported right now */
+ nfs4_ace.aceType = ace_nt->type;
+
+ nfs4_ace.aceFlags =
+ map_windows_ace_flags_to_nfs4_ace_flags(ace_nt->flags);
+
+ /* remove inheritance flags on files */
+ if (!is_directory) {
+ DEBUG(10, ("Removing inheritance flags from a file\n"));
+ nfs4_ace.aceFlags &= ~(SMB_ACE4_FILE_INHERIT_ACE|
+ SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_NO_PROPAGATE_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE);
+ }
+
+ nfs4_ace.aceMask = ace_nt->access_mask & (SEC_STD_ALL | SEC_FILE_ALL);
+
+ se_map_generic(&nfs4_ace.aceMask, &file_generic_mapping);
+
+ if (dom_sid_equal(&ace_nt->trustee, &global_sid_World)) {
+ nfs4_ace.who.special_id = SMB_ACE4_WHO_EVERYONE;
+ nfs4_ace.flags |= SMB_ACE4_ID_SPECIAL;
+ } else if (params->mode!=e_special &&
+ dom_sid_equal(&ace_nt->trustee,
+ &global_sid_Creator_Owner)) {
+ DEBUG(10, ("Map creator owner\n"));
+ nfs4_ace.who.special_id = SMB_ACE4_WHO_OWNER;
+ nfs4_ace.flags |= SMB_ACE4_ID_SPECIAL;
+ /* A non inheriting creator owner entry has no effect. */
+ nfs4_ace.aceFlags |= SMB_ACE4_INHERIT_ONLY_ACE;
+ if (!(nfs4_ace.aceFlags & SMB_ACE4_DIRECTORY_INHERIT_ACE)
+ && !(nfs4_ace.aceFlags & SMB_ACE4_FILE_INHERIT_ACE)) {
+ return 0;
+ }
+ } else if (params->mode!=e_special &&
+ dom_sid_equal(&ace_nt->trustee,
+ &global_sid_Creator_Group)) {
+ DEBUG(10, ("Map creator owner group\n"));
+ nfs4_ace.who.special_id = SMB_ACE4_WHO_GROUP;
+ nfs4_ace.flags |= SMB_ACE4_ID_SPECIAL;
+ /* A non inheriting creator group entry has no effect. */
+ nfs4_ace.aceFlags |= SMB_ACE4_INHERIT_ONLY_ACE;
+ if (!(nfs4_ace.aceFlags & SMB_ACE4_DIRECTORY_INHERIT_ACE)
+ && !(nfs4_ace.aceFlags & SMB_ACE4_FILE_INHERIT_ACE)) {
+ return 0;
+ }
+ } else {
+ struct unixid unixid;
+ bool ok;
+
+ ok = sids_to_unixids(&ace_nt->trustee, 1, &unixid);
+ if (!ok) {
+ DBG_WARNING("Could not convert %s to uid or gid.\n",
+ dom_sid_str_buf(&ace_nt->trustee, &buf));
+ return 0;
+ }
+
+ if (dom_sid_compare_domain(&ace_nt->trustee,
+ &global_sid_Unix_NFS) == 0) {
+ return 0;
+ }
+
+ switch (unixid.type) {
+ case ID_TYPE_BOTH:
+ nfs4_ace.aceFlags |= SMB_ACE4_IDENTIFIER_GROUP;
+ nfs4_ace.who.gid = unixid.id;
+
+ if (ownerUID == unixid.id &&
+ !nfs_ace_is_inherit(&nfs4_ace))
+ {
+ /*
+ * IDMAP_TYPE_BOTH for owner. Add
+ * additional user entry, which can be
+ * mapped to special:owner to reflect
+ * the permissions in the modebits.
+ *
+ * This only applies to non-inheriting
+ * entries as only these are replaced
+ * with SPECIAL_OWNER in nfs4:mode=simple.
+ */
+ nfs4_ace_2 = (SMB_ACE4PROP_T) {
+ .who.uid = unixid.id,
+ .aceFlags = (nfs4_ace.aceFlags &
+ ~SMB_ACE4_IDENTIFIER_GROUP),
+ .aceMask = nfs4_ace.aceMask,
+ .aceType = nfs4_ace.aceType,
+ };
+ add_ace2 = true;
+ }
+ break;
+ case ID_TYPE_GID:
+ nfs4_ace.aceFlags |= SMB_ACE4_IDENTIFIER_GROUP;
+ nfs4_ace.who.gid = unixid.id;
+ break;
+ case ID_TYPE_UID:
+ nfs4_ace.who.uid = unixid.id;
+ break;
+ case ID_TYPE_NOT_SPECIFIED:
+ default:
+ DBG_WARNING("Could not convert %s to uid or gid.\n",
+ dom_sid_str_buf(&ace_nt->trustee, &buf));
+ return 0;
+ }
+ }
+
+ ret = nfs4_acl_add_ace(params->acedup, nfs4_acl, &nfs4_ace);
+ if (ret != 0) {
+ return -1;
+ }
+
+ if (!add_ace2) {
+ return 0;
+ }
+
+ return nfs4_acl_add_ace(params->acedup, nfs4_acl, &nfs4_ace_2);
+}
+
+static void smbacl4_substitute_special(struct SMB4ACL_T *acl,
+ uid_t ownerUID,
+ gid_t ownerGID)
+{
+ struct SMB4ACE_T *aceint;
+
+ for (aceint = acl->first; aceint != NULL; aceint = aceint->next) {
+ SMB_ACE4PROP_T *ace = &aceint->prop;
+
+ DEBUG(10,("ace type: %d, iflags: %x, flags: %x, "
+ "mask: %x, who: %d\n",
+ ace->aceType, ace->flags, ace->aceFlags,
+ ace->aceMask, ace->who.id));
+
+ if (!(ace->flags & SMB_ACE4_ID_SPECIAL) &&
+ !(ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) &&
+ ace->who.uid == ownerUID) {
+ ace->flags |= SMB_ACE4_ID_SPECIAL;
+ ace->who.special_id = SMB_ACE4_WHO_OWNER;
+ DEBUG(10,("replaced with special owner ace\n"));
+ }
+
+ if (!(ace->flags & SMB_ACE4_ID_SPECIAL) &&
+ ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP &&
+ ace->who.uid == ownerGID) {
+ ace->flags |= SMB_ACE4_ID_SPECIAL;
+ ace->who.special_id = SMB_ACE4_WHO_GROUP;
+ DEBUG(10,("replaced with special group ace\n"));
+ }
+ }
+}
+
+static void smbacl4_substitute_simple(struct SMB4ACL_T *acl,
+ uid_t ownerUID,
+ gid_t ownerGID)
+{
+ struct SMB4ACE_T *aceint;
+
+ for (aceint = acl->first; aceint != NULL; aceint = aceint->next) {
+ SMB_ACE4PROP_T *ace = &aceint->prop;
+
+ DEBUG(10,("ace type: %d, iflags: %x, flags: %x, "
+ "mask: %x, who: %d\n",
+ ace->aceType, ace->flags, ace->aceFlags,
+ ace->aceMask, ace->who.id));
+
+ if (!(ace->flags & SMB_ACE4_ID_SPECIAL) &&
+ !(ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) &&
+ ace->who.uid == ownerUID &&
+ !nfs_ace_is_inherit(ace)) {
+ ace->flags |= SMB_ACE4_ID_SPECIAL;
+ ace->who.special_id = SMB_ACE4_WHO_OWNER;
+ DEBUG(10,("replaced with special owner ace\n"));
+ }
+
+ if (!(ace->flags & SMB_ACE4_ID_SPECIAL) &&
+ ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP &&
+ ace->who.gid == ownerGID &&
+ !nfs_ace_is_inherit(ace)) {
+ ace->flags |= SMB_ACE4_ID_SPECIAL;
+ ace->who.special_id = SMB_ACE4_WHO_GROUP;
+ DEBUG(10,("replaced with special group ace\n"));
+ }
+ }
+}
+
+static struct SMB4ACL_T *smbacl4_win2nfs4(
+ TALLOC_CTX *mem_ctx,
+ bool is_directory,
+ const struct security_acl *dacl,
+ const struct smbacl4_vfs_params *pparams,
+ uid_t ownerUID,
+ gid_t ownerGID
+)
+{
+ struct SMB4ACL_T *theacl;
+ uint32_t i;
+
+ DEBUG(10, ("smbacl4_win2nfs4 invoked\n"));
+
+ theacl = smb_create_smb4acl(mem_ctx);
+ if (theacl==NULL)
+ return NULL;
+
+ for(i=0; i<dacl->num_aces; i++) {
+ int ret;
+
+ ret = nfs4_acl_add_sec_ace(is_directory, pparams,
+ ownerUID, ownerGID,
+ dacl->aces + i, theacl);
+ if (ret == -1) {
+ return NULL;
+ }
+ }
+
+ if (pparams->mode==e_simple) {
+ smbacl4_substitute_simple(theacl, ownerUID, ownerGID);
+ }
+
+ if (pparams->mode==e_special) {
+ smbacl4_substitute_special(theacl, ownerUID, ownerGID);
+ }
+
+ return theacl;
+}
+
+NTSTATUS smb_set_nt_acl_nfs4(vfs_handle_struct *handle, files_struct *fsp,
+ const struct smbacl4_vfs_params *pparams,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd,
+ set_nfs4acl_native_fn_t set_nfs4_native)
+{
+ struct smbacl4_vfs_params params;
+ struct SMB4ACL_T *theacl = NULL;
+ bool result, is_directory;
+
+ bool set_acl_as_root = false;
+ int saved_errno;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ DEBUG(10, ("smb_set_nt_acl_nfs4 invoked for %s\n", fsp_str_dbg(fsp)));
+
+ if ((security_info_sent & (SECINFO_DACL |
+ SECINFO_GROUP | SECINFO_OWNER)) == 0)
+ {
+ DEBUG(9, ("security_info_sent (0x%x) ignored\n",
+ security_info_sent));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK; /* won't show error - later to be
+ * refined... */
+ }
+
+ if (security_descriptor_with_ms_nfs(psd)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (pparams == NULL) {
+ /* Special behaviours */
+ if (smbacl4_get_vfs_params(fsp->conn, &params)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ pparams = &params;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+
+ if (pparams->do_chown) {
+ /*
+ * When the chown succeeds, the special entries in the
+ * file system ACL refer to the new owner. In order to
+ * apply the complete information from the DACL,
+ * setting the ACL then has to succeed. Track this
+ * case with set_acl_as_root and set the ACL as root
+ * accordingly.
+ */
+ status = chown_if_needed(fsp, security_info_sent, psd,
+ &set_acl_as_root);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ if (!(security_info_sent & SECINFO_DACL) || psd->dacl ==NULL) {
+ DEBUG(10, ("no dacl found; security_info_sent = 0x%x\n",
+ security_info_sent));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ theacl = smbacl4_win2nfs4(frame, is_directory, psd->dacl, pparams,
+ fsp->fsp_name->st.st_ex_uid,
+ fsp->fsp_name->st.st_ex_gid);
+ if (!theacl) {
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix(errno);
+ }
+
+ smbacl4_set_controlflags(theacl, psd->type);
+ smbacl4_dump_nfs4acl(10, theacl);
+
+ if (set_acl_as_root) {
+ become_root();
+ }
+ result = set_nfs4_native(handle, fsp, theacl);
+ saved_errno = errno;
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+
+ TALLOC_FREE(frame);
+
+ if (result!=true) {
+ errno = saved_errno;
+ DEBUG(10, ("set_nfs4_native failed with %s\n",
+ strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10, ("smb_set_nt_acl_nfs4 succeeded\n"));
+ return NT_STATUS_OK;
+}
diff --git a/source3/modules/nfs4_acls.h b/source3/modules/nfs4_acls.h
new file mode 100644
index 0000000..011b9da
--- /dev/null
+++ b/source3/modules/nfs4_acls.h
@@ -0,0 +1,184 @@
+/*
+ * NFS4 ACL handling
+ *
+ * Copyright (C) Jim McDonough, 2006
+ * Reused & renamed some parts of AIX 5.3 sys/acl.h structures
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NFS4_ACLS_H__
+#define __NFS4_ACLS_H__
+
+/*
+ * Following union captures the identity as
+ * used in the NFS4 ACL structures.
+ */
+typedef union _SMB_NFS4_ACEWHOID_T {
+ uid_t uid; /* User id */
+ gid_t gid; /* Group id */
+ uint32_t special_id; /* Identifies special identities in NFS4 */
+
+#define SMB_ACE4_WHO_OWNER 0x00000001 /*The owner of the file. */
+#define SMB_ACE4_WHO_GROUP 0x00000002 /*The group associated with the file. */
+#define SMB_ACE4_WHO_EVERYONE 0x00000003 /*The world. */
+#define SMB_ACE4_WHO_INTERACTIVE 0x00000004 /*Accessed from an interactive terminal. */
+#define SMB_ACE4_WHO_NETWORK 0x00000005 /*Accessed via the network. */
+#define SMB_ACE4_WHO_DIALUP 0x00000006 /*Accessed as a dialup user to the server. */
+#define SMB_ACE4_WHO_BATCH 0x00000007 /*Accessed from a batch job. */
+#define SMB_ACE4_WHO_ANONYMOUS 0x00000008 /*Accessed without any authentication. */
+#define SMB_ACE4_WHO_AUTHENTICATED 0x00000009 /*Any authenticated user (opposite of ANONYMOUS) */
+#define SMB_ACE4_WHO_SERVICE 0x0000000A /*Access from a system service. */
+#define SMB_ACE4_WHO_MAX SMB_ACE4_WHO_SERVICE /* largest valid ACE4_WHO */
+ uint32_t id;
+} SMB_NFS4_ACEWHOID_T;
+
+typedef struct _SMB_ACE4PROP_T {
+ uint32_t flags; /* Bit mask defining details of ACE */
+/*The following are constants for flags field */
+/* #define SMB_ACE4_ID_NOT_VALID 0x00000001 - from aix/jfs2 */
+#define SMB_ACE4_ID_SPECIAL 0x00000002
+
+ SMB_NFS4_ACEWHOID_T who; /* Identifies to whom this ACE applies */
+
+ /* The following part of ACE has the same layout as NFSv4 wire format. */
+
+ uint32_t aceType; /* Type of ACE PERMIT/ALLOW etc*/
+/*The constants used for the type field (acetype4) are as follows: */
+#define SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE 0x00000000
+#define SMB_ACE4_ACCESS_DENIED_ACE_TYPE 0x00000001
+#define SMB_ACE4_SYSTEM_AUDIT_ACE_TYPE 0x00000002
+#define SMB_ACE4_SYSTEM_ALARM_ACE_TYPE 0x00000003
+#define SMB_ACE4_MAX_TYPE SMB_ACE4_SYSTEM_ALARM_ACE_TYPE /* largest valid ACE4_TYPE */
+
+ uint32_t aceFlags; /* Controls Inheritance and such */
+/*The bitmask constants used for the flag field are as follows: */
+#define SMB_ACE4_FILE_INHERIT_ACE 0x00000001
+#define SMB_ACE4_DIRECTORY_INHERIT_ACE 0x00000002
+#define SMB_ACE4_NO_PROPAGATE_INHERIT_ACE 0x00000004
+#define SMB_ACE4_INHERIT_ONLY_ACE 0x00000008
+#define SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG 0x00000010
+#define SMB_ACE4_FAILED_ACCESS_ACE_FLAG 0x00000020
+#define SMB_ACE4_IDENTIFIER_GROUP 0x00000040
+#define SMB_ACE4_INHERITED_ACE 0x00000080
+#define SMB_ACE4_ALL_FLAGS ( SMB_ACE4_FILE_INHERIT_ACE | SMB_ACE4_DIRECTORY_INHERIT_ACE \
+| SMB_ACE4_NO_PROPAGATE_INHERIT_ACE | SMB_ACE4_INHERIT_ONLY_ACE | SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG \
+| SMB_ACE4_FAILED_ACCESS_ACE_FLAG | SMB_ACE4_IDENTIFIER_GROUP | SMB_ACE4_INHERITED_ACE)
+
+ uint32_t aceMask; /* Access rights */
+/*The bitmask constants used for the access mask field are as follows: */
+#define SMB_ACE4_READ_DATA 0x00000001
+#define SMB_ACE4_LIST_DIRECTORY 0x00000001
+#define SMB_ACE4_WRITE_DATA 0x00000002
+#define SMB_ACE4_ADD_FILE 0x00000002
+#define SMB_ACE4_APPEND_DATA 0x00000004
+#define SMB_ACE4_ADD_SUBDIRECTORY 0x00000004
+#define SMB_ACE4_READ_NAMED_ATTRS 0x00000008
+#define SMB_ACE4_WRITE_NAMED_ATTRS 0x00000010
+#define SMB_ACE4_EXECUTE 0x00000020
+#define SMB_ACE4_DELETE_CHILD 0x00000040
+#define SMB_ACE4_READ_ATTRIBUTES 0x00000080
+#define SMB_ACE4_WRITE_ATTRIBUTES 0x00000100
+#define SMB_ACE4_DELETE 0x00010000
+#define SMB_ACE4_READ_ACL 0x00020000
+#define SMB_ACE4_WRITE_ACL 0x00040000
+#define SMB_ACE4_WRITE_OWNER 0x00080000
+#define SMB_ACE4_SYNCHRONIZE 0x00100000
+#define SMB_ACE4_ALL_MASKS ( SMB_ACE4_READ_DATA | SMB_ACE4_LIST_DIRECTORY \
+| SMB_ACE4_WRITE_DATA | SMB_ACE4_ADD_FILE | SMB_ACE4_APPEND_DATA | SMB_ACE4_ADD_SUBDIRECTORY \
+| SMB_ACE4_READ_NAMED_ATTRS | SMB_ACE4_WRITE_NAMED_ATTRS | SMB_ACE4_EXECUTE | SMB_ACE4_DELETE_CHILD \
+| SMB_ACE4_READ_ATTRIBUTES | SMB_ACE4_WRITE_ATTRIBUTES | SMB_ACE4_DELETE | SMB_ACE4_READ_ACL \
+| SMB_ACE4_WRITE_ACL | SMB_ACE4_WRITE_OWNER | SMB_ACE4_SYNCHRONIZE )
+} SMB_ACE4PROP_T;
+
+struct SMB4ACL_T;
+struct SMB4ACE_T;
+
+enum smbacl4_mode_enum {e_simple=0, e_special=1};
+enum smbacl4_acedup_enum {e_dontcare=0, e_reject=1, e_ignore=2, e_merge=3};
+
+struct smbacl4_vfs_params {
+ enum smbacl4_mode_enum mode;
+ bool do_chown;
+ enum smbacl4_acedup_enum acedup;
+ bool map_full_control;
+};
+
+int smbacl4_get_vfs_params(struct connection_struct *conn,
+ struct smbacl4_vfs_params *params);
+
+int nfs4_acl_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname);
+
+int nfs4_acl_fstat(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf);
+
+int nfs4_acl_lstat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname);
+
+int nfs4_acl_fstatat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags);
+
+struct SMB4ACL_T *smb_create_smb4acl(TALLOC_CTX *mem_ctx);
+
+/* prop's contents are copied */
+/* it doesn't change the order, appends */
+struct SMB4ACE_T *smb_add_ace4(struct SMB4ACL_T *theacl, SMB_ACE4PROP_T *prop);
+
+SMB_ACE4PROP_T *smb_get_ace4(struct SMB4ACE_T *ace);
+
+/* Returns NULL if none - or error */
+struct SMB4ACE_T *smb_first_ace4(struct SMB4ACL_T *theacl);
+
+/* Returns NULL in the end - or error */
+struct SMB4ACE_T *smb_next_ace4(struct SMB4ACE_T *ace);
+
+uint32_t smb_get_naces(struct SMB4ACL_T *theacl);
+
+uint16_t smbacl4_get_controlflags(struct SMB4ACL_T *theacl);
+
+bool smbacl4_set_controlflags(struct SMB4ACL_T *theacl, uint16_t controlflags);
+
+bool nfs_ace_is_inherit(SMB_ACE4PROP_T *ace);
+
+NTSTATUS smb_fget_nt_acl_nfs4(files_struct *fsp,
+ const struct smbacl4_vfs_params *pparams,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc, struct SMB4ACL_T *theacl);
+
+NTSTATUS smb_get_nt_acl_nfs4(connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ const struct smbacl4_vfs_params *pparams,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc, struct SMB4ACL_T *theacl);
+
+/* Callback function needed to set the native acl
+ * when applicable */
+typedef bool (*set_nfs4acl_native_fn_t)(vfs_handle_struct *handle,
+ files_struct *,
+ struct SMB4ACL_T *);
+
+NTSTATUS smb_set_nt_acl_nfs4(vfs_handle_struct *handle, files_struct *fsp,
+ const struct smbacl4_vfs_params *pparams,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd,
+ set_nfs4acl_native_fn_t set_nfs4_native);
+
+#endif /* __NFS4_ACLS_H__ */
diff --git a/source3/modules/nfs4acl_xattr.h b/source3/modules/nfs4acl_xattr.h
new file mode 100644
index 0000000..0adede1
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) Ralph Boehme 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __NFS4ACL_XATTR_H__
+#define __NFS4ACL_XATTR_H__
+
+#define NFS4ACL_XDR_MAX_ACES 8192
+
+enum nfs4acl_encoding {
+ NFS4ACL_ENCODING_NDR,
+ NFS4ACL_ENCODING_XDR,
+ NFS4ACL_ENCODING_NFS
+};
+
+struct nfs4acl_config {
+ unsigned nfs_version;
+ enum nfs4acl_encoding encoding;
+ char *xattr_name;
+ struct smbacl4_vfs_params nfs4_params;
+ enum default_acl_style default_acl_style;
+ bool nfs4_id_numeric;
+ bool validate_mode;
+};
+
+#endif /* __NFS4ACL_XATTR_H__ */
diff --git a/source3/modules/nfs4acl_xattr_ndr.c b/source3/modules/nfs4acl_xattr_ndr.c
new file mode 100644
index 0000000..ffa3e69
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_ndr.c
@@ -0,0 +1,300 @@
+/*
+ * Convert NFSv4 acls stored per http://www.suse.de/~agruen/nfs4acl/ to NT acls and vice versa.
+ *
+ * Copyright (C) Jiri Sasek, 2007
+ * based on the foobar.c module which is copyrighted by Volker Lendecke
+ * based on pvfs_acl_nfs4.c Copyright (C) Andrew Tridgell 2006
+ *
+ * based on vfs_fake_acls:
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) Andrew Bartlett, 2002,2012
+ * Copyright (C) Ralph Boehme 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "nfs4_acls.h"
+#include "librpc/gen_ndr/ndr_nfs4acl.h"
+#include "nfs4acl_xattr.h"
+#include "nfs4acl_xattr_ndr.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static struct nfs4acl *nfs4acl_blob2acl(DATA_BLOB *blob, TALLOC_CTX *mem_ctx)
+{
+ enum ndr_err_code ndr_err;
+ struct nfs4acl *acl = talloc_zero(mem_ctx, struct nfs4acl);
+
+ if (acl == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ndr_err = ndr_pull_struct_blob(blob, acl, acl,
+ (ndr_pull_flags_fn_t)ndr_pull_nfs4acl);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_pull_acl_t failed: %s\n", ndr_errstr(ndr_err));
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+ return acl;
+}
+
+static DATA_BLOB nfs4acl_acl2blob(TALLOC_CTX *mem_ctx, struct nfs4acl *acl)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, acl,
+ (ndr_push_flags_fn_t)ndr_push_nfs4acl);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_push_acl_t failed: %s\n", ndr_errstr(ndr_err));
+ return data_blob_null;
+ }
+ return blob;
+}
+
+static uint16_t nfs4acl_to_smb4acl_flags(uint8_t nfs4acl_flags)
+{
+ uint16_t smb4acl_flags = SEC_DESC_SELF_RELATIVE;
+
+ if (nfs4acl_flags & ACL4_AUTO_INHERIT) {
+ smb4acl_flags |= SEC_DESC_DACL_AUTO_INHERITED;
+ }
+ if (nfs4acl_flags & ACL4_PROTECTED) {
+ smb4acl_flags |= SEC_DESC_DACL_PROTECTED;
+ }
+ if (nfs4acl_flags & ACL4_DEFAULTED) {
+ smb4acl_flags |= SEC_DESC_DACL_DEFAULTED;
+ }
+
+ return smb4acl_flags;
+}
+
+NTSTATUS nfs4acl_ndr_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl)
+{
+ struct nfs4acl *nfs4acl = NULL;
+ struct SMB4ACL_T *smb4acl = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct nfs4acl_config *config = NULL;
+ int i;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ nfs4acl = nfs4acl_blob2acl(blob, frame);
+ if (nfs4acl == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ smb4acl = smb_create_smb4acl(mem_ctx);
+ if (smb4acl == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (config->nfs_version > ACL4_XATTR_VERSION_40 &&
+ nfs4acl->a_version > ACL4_XATTR_VERSION_40)
+ {
+ uint16_t smb4acl_flags;
+
+ smb4acl_flags = nfs4acl_to_smb4acl_flags(nfs4acl->a_flags);
+ smbacl4_set_controlflags(smb4acl, smb4acl_flags);
+ }
+
+ for (i = 0; i < nfs4acl->a_count; i++) {
+ SMB_ACE4PROP_T aceprop;
+
+ aceprop.aceType = (uint32_t) nfs4acl->ace[i].e_type;
+ aceprop.aceFlags = (uint32_t) nfs4acl->ace[i].e_flags;
+ aceprop.aceMask = (uint32_t) nfs4acl->ace[i].e_mask;
+ aceprop.who.id = (uint32_t) nfs4acl->ace[i].e_id;
+
+ if (!strcmp(nfs4acl->ace[i].e_who,
+ NFS4ACL_XATTR_OWNER_WHO)) {
+ aceprop.flags = SMB_ACE4_ID_SPECIAL;
+ aceprop.who.special_id = SMB_ACE4_WHO_OWNER;
+ } else if (!strcmp(nfs4acl->ace[i].e_who,
+ NFS4ACL_XATTR_GROUP_WHO)) {
+ aceprop.flags = SMB_ACE4_ID_SPECIAL;
+ aceprop.who.special_id = SMB_ACE4_WHO_GROUP;
+ } else if (!strcmp(nfs4acl->ace[i].e_who,
+ NFS4ACL_XATTR_EVERYONE_WHO)) {
+ aceprop.flags = SMB_ACE4_ID_SPECIAL;
+ aceprop.who.special_id = SMB_ACE4_WHO_EVERYONE;
+ } else {
+ aceprop.flags = 0;
+ }
+
+ if (smb_add_ace4(smb4acl, &aceprop) == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_smb4acl = smb4acl;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static uint8_t smb4acl_to_nfs4acl_flags(uint16_t smb4acl_flags)
+{
+ uint8_t flags = 0;
+
+ if (smb4acl_flags & SEC_DESC_DACL_AUTO_INHERITED) {
+ flags |= ACL4_AUTO_INHERIT;
+ }
+ if (smb4acl_flags & SEC_DESC_DACL_PROTECTED) {
+ flags |= ACL4_PROTECTED;
+ }
+ if (smb4acl_flags & SEC_DESC_DACL_DEFAULTED) {
+ flags |= ACL4_DEFAULTED;
+ }
+
+ return flags;
+}
+
+static bool nfs4acl_smb4acl2nfs4acl(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smbacl,
+ struct nfs4acl **_nfs4acl,
+ bool denymissingspecial)
+{
+ struct nfs4acl_config *config = NULL;
+ struct nfs4acl *nfs4acl = NULL;
+ struct SMB4ACE_T *smbace = NULL;
+ bool have_special_id = false;
+ int i;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return false);
+
+ nfs4acl = talloc_zero(mem_ctx, struct nfs4acl);
+ if (nfs4acl == NULL) {
+ errno = ENOMEM;
+ return false;
+ }
+
+ nfs4acl->a_count = smb_get_naces(smbacl);
+
+ nfs4acl->ace = talloc_zero_array(nfs4acl, struct nfs4ace,
+ nfs4acl->a_count);
+ if (nfs4acl->ace == NULL) {
+ TALLOC_FREE(nfs4acl);
+ errno = ENOMEM;
+ return false;
+ }
+
+ nfs4acl->a_version = config->nfs_version;
+ if (nfs4acl->a_version > ACL4_XATTR_VERSION_40) {
+ uint16_t smb4acl_flags;
+ uint8_t flags;
+
+ smb4acl_flags = smbacl4_get_controlflags(smbacl);
+ flags = smb4acl_to_nfs4acl_flags(smb4acl_flags);
+ nfs4acl->a_flags = flags;
+ }
+
+ for (smbace = smb_first_ace4(smbacl), i = 0;
+ smbace != NULL;
+ smbace = smb_next_ace4(smbace), i++)
+ {
+ SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace);
+
+ nfs4acl->ace[i].e_type = aceprop->aceType;
+ nfs4acl->ace[i].e_flags = aceprop->aceFlags;
+ nfs4acl->ace[i].e_mask = aceprop->aceMask;
+ nfs4acl->ace[i].e_id = aceprop->who.id;
+ if(aceprop->flags & SMB_ACE4_ID_SPECIAL) {
+ switch(aceprop->who.special_id) {
+ case SMB_ACE4_WHO_EVERYONE:
+ nfs4acl->ace[i].e_who =
+ NFS4ACL_XATTR_EVERYONE_WHO;
+ break;
+ case SMB_ACE4_WHO_OWNER:
+ nfs4acl->ace[i].e_who =
+ NFS4ACL_XATTR_OWNER_WHO;
+ break;
+ case SMB_ACE4_WHO_GROUP:
+ nfs4acl->ace[i].e_who =
+ NFS4ACL_XATTR_GROUP_WHO;
+ break;
+ default:
+ DBG_DEBUG("unsupported special_id %d\n",
+ aceprop->who.special_id);
+ continue; /* don't add it !!! */
+ }
+ have_special_id = true;
+ } else {
+ nfs4acl->ace[i].e_who = "";
+ }
+ }
+
+ if (!have_special_id && denymissingspecial) {
+ TALLOC_FREE(nfs4acl);
+ errno = EACCES;
+ return false;
+ }
+
+ SMB_ASSERT(i == nfs4acl->a_count);
+
+ *_nfs4acl = nfs4acl;
+ return true;
+}
+
+NTSTATUS nfs4acl_smb4acl_to_ndr_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smb4acl,
+ DATA_BLOB *_blob)
+{
+ struct nfs4acl *nfs4acl = NULL;
+ DATA_BLOB blob;
+ bool denymissingspecial;
+ bool ok;
+
+ denymissingspecial = lp_parm_bool(SNUM(handle->conn),
+ "nfs4acl_xattr",
+ "denymissingspecial", false);
+
+ ok = nfs4acl_smb4acl2nfs4acl(handle, talloc_tos(), smb4acl, &nfs4acl,
+ denymissingspecial);
+ if (!ok) {
+ DBG_ERR("Failed to convert smb ACL to nfs4 ACL.\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ blob = nfs4acl_acl2blob(mem_ctx, nfs4acl);
+ TALLOC_FREE(nfs4acl);
+ if (blob.data == NULL) {
+ DBG_ERR("Failed to convert ACL to linear blob for xattr\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ *_blob = blob;
+ return NT_STATUS_OK;
+}
diff --git a/source3/modules/nfs4acl_xattr_ndr.h b/source3/modules/nfs4acl_xattr_ndr.h
new file mode 100644
index 0000000..17cf9da
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_ndr.h
@@ -0,0 +1,44 @@
+/*
+ * Convert NFSv4 acls stored per http://www.suse.de/~agruen/nfs4acl/ to NT acls and vice versa.
+ *
+ * Copyright (C) Jiri Sasek, 2007
+ * based on the foobar.c module which is copyrighted by Volker Lendecke
+ * based on pvfs_acl_nfs4.c Copyright (C) Andrew Tridgell 2006
+ *
+ * based on vfs_fake_acls:
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) Andrew Bartlett, 2002,2012
+ * Copyright (C) Ralph Boehme 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __NFS4ACL_XATTR_NDR_H__
+#define __NFS4ACL_XATTR_NDR_H__
+
+struct SMB4ACL_T;
+
+NTSTATUS nfs4acl_ndr_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl);
+
+NTSTATUS nfs4acl_smb4acl_to_ndr_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smbacl,
+ DATA_BLOB *blob);
+
+#endif /* _VFS_NFS4ACL_XATTR_NDR_H */
diff --git a/source3/modules/nfs4acl_xattr_nfs.c b/source3/modules/nfs4acl_xattr_nfs.c
new file mode 100644
index 0000000..df3c0f5
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_nfs.c
@@ -0,0 +1,894 @@
+/*
+ * Copyright (C) Ralph Boehme 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "smbd/proto.h"
+#include "system/passwd.h"
+#include "libcli/security/security_descriptor.h"
+#include "libcli/security/security_token.h"
+
+#ifdef HAVE_RPC_XDR_H
+/* <rpc/xdr.h> uses TRUE and FALSE */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#ifdef HAVE_RPC_TYPES_H
+#include <rpc/types.h>
+#endif
+#include <rpc/xdr.h>
+
+#include "nfs4_acls.h"
+#include "nfs41acl.h"
+#include "nfs4acl_xattr.h"
+#include "nfs4acl_xattr_nfs.h"
+#include "nfs4acl_xattr_util.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#define OVERFLOW_CHECK(val1, val2) ((val1) + (val2) < (val1))
+#define XDR_UTF8STR_ALIGNMENT 4
+#define XDR_UTF8STR_ALIGN(l) \
+ (((l) + ((XDR_UTF8STR_ALIGNMENT) - 1)) & ~((XDR_UTF8STR_ALIGNMENT) - 1))
+
+static struct nfs4_to_smb4_id_map {
+ const char *nfs4_id;
+ uint32_t smb4_id;
+} nfs4_to_smb4_id_map[] = {
+ {"OWNER@", SMB_ACE4_WHO_OWNER},
+ {"GROUP@", SMB_ACE4_WHO_GROUP},
+ {"EVERYONE@", SMB_ACE4_WHO_EVERYONE},
+ {"INTERACTIVE@", SMB_ACE4_WHO_INTERACTIVE},
+ {"NETWORK@", SMB_ACE4_WHO_NETWORK},
+ {"DIALUP@", SMB_ACE4_WHO_DIALUP},
+ {"BATCH@", SMB_ACE4_WHO_BATCH},
+ {"ANONYMOUS@", SMB_ACE4_WHO_ANONYMOUS},
+ {"AUTHENTICATED@", SMB_ACE4_WHO_AUTHENTICATED},
+ {"SERVICE@", SMB_ACE4_WHO_SERVICE},
+};
+
+static bool is_special_nfs4_id(const char *nfs4_id)
+{
+ char *at = NULL;
+
+ at = strchr(nfs4_id, '@');
+ if (at == NULL) {
+ return false;
+ }
+ if (at[1] != '\0') {
+ return false;
+ }
+ return true;
+}
+
+static bool map_special_nfs4_to_smb4_id(const char *nfs4_id, uint32_t *smb4_id)
+{
+ size_t i;
+ int cmp;
+
+ for (i = 0; i < ARRAY_SIZE(nfs4_to_smb4_id_map); i++) {
+ cmp = strcmp(nfs4_to_smb4_id_map[i].nfs4_id, nfs4_id);
+ if (cmp != 0) {
+ continue;
+ }
+ *smb4_id = nfs4_to_smb4_id_map[i].smb4_id;
+ return true;
+ }
+ return false;
+}
+
+static bool map_special_smb4_to_nfs4_id(uint32_t smb4_id, const char **nfs4_id)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(nfs4_to_smb4_id_map); i++) {
+ if (nfs4_to_smb4_id_map[i].smb4_id != smb4_id) {
+ continue;
+ }
+ *nfs4_id = nfs4_to_smb4_id_map[i].nfs4_id;
+ return true;
+ }
+ return false;
+}
+
+static unsigned nfs40acl_get_naces(nfsacl40 *nacl)
+{
+ return nacl->na40_aces.na40_aces_len;
+}
+
+static unsigned nfs41acl_get_naces(nfsacl41 *nacl)
+{
+ return nacl->na41_aces.na41_aces_len;
+}
+
+static void nfs40acl_set_naces(nfsacl40 *nacl, unsigned naces)
+{
+ nacl->na40_aces.na40_aces_len = naces;
+}
+
+static void nfs41acl_set_naces(nfsacl41 *nacl, unsigned naces)
+{
+ nacl->na41_aces.na41_aces_len = naces;
+}
+
+static unsigned nfs41acl_get_flags(nfsacl41 *nacl)
+{
+ return nacl->na41_flag;
+}
+
+static void nfs41acl_set_flags(nfsacl41 *nacl, unsigned flags)
+{
+ nacl->na41_flag = flags;
+}
+
+static nfsace4 *nfs40acl_get_ace(nfsacl40 *nacl, size_t n)
+{
+ return &nacl->na40_aces.na40_aces_val[n];
+}
+
+static nfsace4 *nfs41acl_get_ace(nfsacl41 *nacl, size_t n)
+{
+ return &nacl->na41_aces.na41_aces_val[n];
+}
+
+static size_t nfs40acl_get_xdrblob_size(nfsacl40 *nacl)
+{
+ size_t acl_size;
+ size_t aces_size;
+ size_t identifier_size;
+ unsigned i;
+ unsigned naces = nfs40acl_get_naces(nacl);
+
+ /* ACE structure minus actual identifier strings */
+ struct nfsace4_size {
+ acetype4 type;
+ aceflag4 flag;
+ acemask4 access_mask;
+ u_int who_length;
+ };
+
+ /*
+ * acl_size =
+ * sizeof(ace_count) +
+ * (ace_count * (sizeof(nfsace4_size)) +
+ * length of all identifiers strings
+ */
+
+ acl_size = sizeof(unsigned);
+
+ if (naces > NFS4ACL_XDR_MAX_ACES) {
+ DBG_ERR("Too many ACEs: %u\n", naces);
+ return 0;
+ }
+
+ aces_size = naces * sizeof(struct nfsace4_size);
+
+ if (OVERFLOW_CHECK(acl_size, aces_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return 0;
+ }
+ acl_size += aces_size;
+
+ identifier_size = 0;
+ for (i = 0; i < naces; i++) {
+ nfsace4 *nace = nfs40acl_get_ace(nacl, i);
+ size_t string_size = nace->who.utf8string_len;
+ size_t id_size;
+
+ id_size = XDR_UTF8STR_ALIGN(string_size);
+
+ if (OVERFLOW_CHECK(identifier_size, id_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return 0;
+ }
+ identifier_size += id_size;
+ }
+
+ if (OVERFLOW_CHECK(acl_size, identifier_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return 0;
+ }
+ acl_size += identifier_size;
+
+ DBG_DEBUG("acl_size: %zd\n", acl_size);
+ return acl_size;
+}
+
+static size_t nfs41acl_get_xdrblob_size(nfsacl41 *nacl)
+{
+ size_t acl_size;
+ size_t aces_size;
+ size_t identifier_size;
+ unsigned i;
+ unsigned naces = nfs41acl_get_naces(nacl);
+
+ /* ACE structure minus actual identifier strings */
+ struct nfsace4_size {
+ acetype4 type;
+ aceflag4 flag;
+ acemask4 access_mask;
+ u_int who_length;
+ };
+
+ /*
+ * acl_size =
+ * sizeof(acl_flag) +
+ * sizeof(ace_count) +
+ * (ace_count * (sizeof(nfsace4_size)) +
+ * length of all identifiers strings
+ */
+
+ acl_size = 2 * sizeof(unsigned);
+
+ if (naces > NFS4ACL_XDR_MAX_ACES) {
+ DBG_ERR("Too many ACEs: %u\n", naces);
+ return 0;
+ }
+
+ aces_size = naces * sizeof(struct nfsace4_size);
+
+ if (OVERFLOW_CHECK(acl_size, aces_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return 0;
+ }
+ acl_size += aces_size;
+
+ identifier_size = 0;
+ for (i = 0; i < naces; i++) {
+ nfsace4 *nace = nfs41acl_get_ace(nacl, i);
+ size_t string_size = nace->who.utf8string_len;
+ size_t id_size;
+
+ id_size = XDR_UTF8STR_ALIGN(string_size);
+
+ if (OVERFLOW_CHECK(identifier_size, id_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return 0;
+ }
+ identifier_size += id_size;
+ }
+
+ if (OVERFLOW_CHECK(acl_size, identifier_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return 0;
+ }
+ acl_size += identifier_size;
+
+ DBG_DEBUG("acl_size: %zd\n", acl_size);
+ return acl_size;
+}
+
+static nfsacl40 *nfs40acl_alloc(TALLOC_CTX *mem_ctx, unsigned naces)
+{
+ size_t acl_size;
+ size_t aces_size;
+ nfsacl40 *nacl = NULL;
+
+ if (naces > NFS4ACL_XDR_MAX_ACES) {
+ DBG_ERR("Too many ACEs: %d\n", naces);
+ return NULL;
+ }
+
+ acl_size = sizeof(nfsacl40);
+ aces_size = (naces * sizeof(struct nfsace4));
+
+ if (OVERFLOW_CHECK(acl_size, aces_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return NULL;
+ }
+ acl_size += aces_size;
+
+ nacl = talloc_zero_size(mem_ctx, acl_size);
+ if (nacl == NULL) {
+ DBG_ERR("talloc_zero_size failed\n");
+ return NULL;
+ }
+
+ nfs40acl_set_naces(nacl, naces);
+ nacl->na40_aces.na40_aces_val =
+ (nfsace4 *)((uint8_t *)nacl + sizeof(nfsacl40));
+
+ return nacl;
+}
+
+static nfsacl41 *nfs41acl_alloc(TALLOC_CTX *mem_ctx, unsigned naces)
+{
+ size_t acl_size;
+ size_t aces_size;
+ nfsacl41 *nacl = NULL;
+
+ if (naces > NFS4ACL_XDR_MAX_ACES) {
+ DBG_ERR("Too many ACEs: %d\n", naces);
+ return NULL;
+ }
+
+ acl_size = sizeof(nfsacl41);
+ aces_size = (naces * sizeof(struct nfsace4));
+
+ if (OVERFLOW_CHECK(acl_size, aces_size)) {
+ DBG_ERR("Integer Overflow error\n");
+ return NULL;
+ }
+ acl_size += aces_size;
+
+ nacl = talloc_zero_size(mem_ctx, acl_size);
+ if (nacl == NULL) {
+ DBG_ERR("talloc_zero_size failed\n");
+ return NULL;
+ }
+
+ nfs41acl_set_naces(nacl, naces);
+ nacl->na41_aces.na41_aces_val =
+ (nfsace4 *)((uint8_t *)nacl + sizeof(nfsacl41));
+
+ return nacl;
+}
+
+static bool create_special_id(TALLOC_CTX *mem_ctx,
+ nfsace4 *nace,
+ const char *id)
+{
+ char *s = talloc_strdup(mem_ctx, id);
+
+ if (s == NULL) {
+ DBG_ERR("talloc_strdup failed\n");
+ return false;
+ }
+ nace->who.utf8string_val = s;
+ nace->who.utf8string_len = talloc_get_size(s) - 1;
+ return true;
+}
+
+static bool map_smb4_to_nfs4_id(TALLOC_CTX *mem_ctx,
+ struct nfs4acl_config *config,
+ nfsace4 *nace,
+ SMB_ACE4PROP_T *sace)
+{
+ const char *nfs4_id = NULL;
+ const char *name = NULL;
+ char *ace_name = NULL;
+ uid_t id;
+ bool ok;
+
+ if (sace->flags & SMB_ACE4_ID_SPECIAL) {
+ ok = map_special_smb4_to_nfs4_id(sace->who.special_id,
+ &nfs4_id);
+ if (!ok) {
+ DBG_ERR("Unsupported special id [%"PRIu32"]\n",
+ sace->who.special_id);
+ return false;
+ }
+
+ ok = create_special_id(mem_ctx, nace, nfs4_id);
+ if (!ok) {
+ return false;
+ }
+ DBG_DEBUG("Special id [%s]\n", nace->who.utf8string_val);
+ return true;
+ }
+
+ if (sace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) {
+ nace->flag |= ACE4_IDENTIFIER_GROUP;
+ }
+
+ if (config->nfs4_id_numeric) {
+ char *strid = NULL;
+
+ if (sace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) {
+ id = sace->who.gid;
+ } else {
+ id = sace->who.uid;
+ }
+
+ strid = talloc_asprintf(mem_ctx, "%jd", (intmax_t)id);
+ if (strid == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ return false;
+ }
+ nace->who.utf8string_val = strid;
+ nace->who.utf8string_len = talloc_get_size(strid) - 1;
+ DBG_DEBUG("Numeric id [%s]\n", nace->who.utf8string_val);
+ return true;
+ }
+
+ if (sace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) {
+ struct group *grp = NULL;
+
+ grp = getgrgid(sace->who.gid);
+ if (grp == NULL) {
+ DBG_ERR("Unknown gid [%jd]\n", (intmax_t)sace->who.gid);
+ return false;
+ }
+ name = grp->gr_name;
+ } else {
+ struct passwd *pwd = NULL;
+
+ pwd = getpwuid(sace->who.uid);
+ if (pwd == NULL) {
+ DBG_ERR("Unknown uid [%jd]\n", (intmax_t)sace->who.uid);
+ return false;
+ }
+ name = pwd->pw_name;
+ }
+
+ ace_name = talloc_strdup(mem_ctx, name);
+ if (ace_name == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ return false;
+ }
+ nace->who.utf8string_val = ace_name;
+ nace->who.utf8string_len = talloc_get_size(ace_name) - 1;
+
+ DBG_DEBUG("id [%s]\n", nace->who.utf8string_val);
+ return true;
+}
+
+static bool smb4acl_to_nfs40acl(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smb4acl,
+ nfsacl40 **_nacl)
+{
+ struct nfs4acl_config *config = NULL;
+ struct SMB4ACE_T *smb4ace = NULL;
+ nfsacl40 *nacl = NULL;
+ size_t naces = smb_get_naces(smb4acl);
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return false);
+
+ nacl = nfs40acl_alloc(mem_ctx, naces);
+ nfs40acl_set_naces(nacl, 0);
+
+ smb4ace = smb_first_ace4(smb4acl);
+ while (smb4ace != NULL) {
+ SMB_ACE4PROP_T *ace4prop = smb_get_ace4(smb4ace);
+ size_t nace_count = nfs40acl_get_naces(nacl);
+ nfsace4 *nace = nfs40acl_get_ace(nacl, nace_count);
+
+ nace->type = ace4prop->aceType;
+ nace->flag = ace4prop->aceFlags;
+ nace->access_mask = ace4prop->aceMask;
+
+ ok = map_smb4_to_nfs4_id(nacl, config, nace, ace4prop);
+ if (!ok) {
+ smb4ace = smb_next_ace4(smb4ace);
+ continue;
+ }
+
+ nace_count++;
+ nfs40acl_set_naces(nacl, nace_count);
+ smb4ace = smb_next_ace4(smb4ace);
+ }
+
+ *_nacl = nacl;
+ return true;
+}
+
+static bool smb4acl_to_nfs41acl(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smb4acl,
+ nfsacl41 **_nacl)
+{
+ struct nfs4acl_config *config = NULL;
+ struct SMB4ACE_T *smb4ace = NULL;
+ nfsacl41 *nacl = NULL;
+ size_t naces = smb_get_naces(smb4acl);
+ uint16_t smb4acl_flags;
+ unsigned nacl_flags;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return false);
+
+ nacl = nfs41acl_alloc(mem_ctx, naces);
+ nfs41acl_set_naces(nacl, 0);
+
+ smb4acl_flags = smbacl4_get_controlflags(smb4acl);
+ nacl_flags = smb4acl_to_nfs4acl_flags(smb4acl_flags);
+ nfs41acl_set_flags(nacl, nacl_flags);
+
+ smb4ace = smb_first_ace4(smb4acl);
+ while (smb4ace != NULL) {
+ SMB_ACE4PROP_T *ace4prop = smb_get_ace4(smb4ace);
+ size_t nace_count = nfs41acl_get_naces(nacl);
+ nfsace4 *nace = nfs41acl_get_ace(nacl, nace_count);
+
+ nace->type = ace4prop->aceType;
+ nace->flag = ace4prop->aceFlags;
+ nace->access_mask = ace4prop->aceMask;
+
+ ok = map_smb4_to_nfs4_id(nacl, config, nace, ace4prop);
+ if (!ok) {
+ smb4ace = smb_next_ace4(smb4ace);
+ continue;
+ }
+
+ nace_count++;
+ nfs41acl_set_naces(nacl, nace_count);
+ smb4ace = smb_next_ace4(smb4ace);
+ }
+
+ *_nacl = nacl;
+ return true;
+}
+
+NTSTATUS nfs4acl_smb4acl_to_nfs_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smb4acl,
+ DATA_BLOB *_blob)
+{
+ struct nfs4acl_config *config = NULL;
+ nfsacl40 *nacl40 = NULL;
+ nfsacl41 *nacl41 = NULL;
+ XDR xdr = {0};
+ size_t aclblobsize;
+ DATA_BLOB blob;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (config->nfs_version == ACL4_XATTR_VERSION_40) {
+ ok = smb4acl_to_nfs40acl(handle, mem_ctx, smb4acl, &nacl40);
+ if (!ok) {
+ DBG_ERR("smb4acl_to_nfs4acl failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ aclblobsize = nfs40acl_get_xdrblob_size(nacl40);
+ if (aclblobsize == 0) {
+ DBG_ERR("Error calculating XDR blob size\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ } else {
+ ok = smb4acl_to_nfs41acl(handle, mem_ctx, smb4acl, &nacl41);
+ if (!ok) {
+ DBG_ERR("smb4acl_to_nfs4acl failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ aclblobsize = nfs41acl_get_xdrblob_size(nacl41);
+ if (aclblobsize == 0) {
+ DBG_ERR("Error calculating XDR blob size\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ blob = data_blob_talloc(mem_ctx, NULL, aclblobsize);
+ if (blob.data == NULL) {
+ TALLOC_FREE(nacl40);
+ TALLOC_FREE(nacl41);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ xdrmem_create(&xdr, (char *)blob.data, blob.length, XDR_ENCODE);
+
+ if (config->nfs_version == ACL4_XATTR_VERSION_40) {
+ ok = xdr_nfsacl40(&xdr, nacl40);
+ TALLOC_FREE(nacl40);
+ if (!ok) {
+ DBG_ERR("xdr_nfs4acl40 failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ ok = xdr_nfsacl41(&xdr, nacl41);
+ TALLOC_FREE(nacl41);
+ if (!ok) {
+ DBG_ERR("xdr_nfs4acl40 failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_blob = blob;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nfs4acl_nfs_blob_to_nfs40acl(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ nfsacl40 **_nacl)
+{
+ nfsacl40 *nacl = NULL;
+ XDR xdr = {0};
+ bool ok;
+
+ nacl = talloc_zero_size(mem_ctx, sizeof(nfsacl40));
+ if (nacl == NULL) {
+ DBG_ERR("talloc_zero_size failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ xdrmem_create(&xdr, (char *)blob->data, blob->length, XDR_DECODE);
+
+ ok = xdr_nfsacl40(&xdr, nacl);
+ if (!ok) {
+ DBG_ERR("xdr_nfsacl40 failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ DBG_DEBUG("naces = %d \n", nacl->na40_aces.na40_aces_len);
+
+ *_nacl = nacl;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nfs4acl_nfs_blob_to_nfs41acl(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ nfsacl41 **_nacl)
+{
+ nfsacl41 *nacl = NULL;
+ XDR xdr = {0};
+ bool ok;
+
+ nacl = talloc_zero_size(mem_ctx, sizeof(nfsacl41));
+ if (nacl == NULL) {
+ DBG_ERR("talloc_zero_size failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ xdrmem_create(&xdr, (char *)blob->data, blob->length, XDR_DECODE);
+
+ ok = xdr_nfsacl41(&xdr, nacl);
+ if (!ok) {
+ DBG_ERR("xdr_nfsacl40 failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ DBG_DEBUG("naces = %d \n", nacl->na41_aces.na41_aces_len);
+
+ *_nacl = nacl;
+ return NT_STATUS_OK;
+}
+
+static bool map_ace_nfs4_to_smb4(struct nfs4acl_config *config,
+ const nfsace4 *nace,
+ SMB_ACE4PROP_T *sace)
+{
+ char *name = NULL;
+ char *p = NULL;
+ uint32_t smb4_id;
+ bool ok;
+
+ name = talloc_strndup(talloc_tos(),
+ nace->who.utf8string_val,
+ nace->who.utf8string_len);
+ if (name == NULL) {
+ return false;
+ }
+
+ sace->aceType = nace->type;
+ sace->aceFlags = nace->flag;
+ sace->aceMask = nace->access_mask;
+
+ if (is_special_nfs4_id(name)) {
+ ok = map_special_nfs4_to_smb4_id(name, &smb4_id);
+ if (!ok) {
+ DBG_WARNING("Unknown special id [%s]\n", name);
+ return false;
+ }
+ sace->flags |= SMB_ACE4_ID_SPECIAL;
+ sace->who.special_id = smb4_id;
+ return true;
+ }
+
+ p = strtok(name, "@");
+ if (p == NULL && !config->nfs4_id_numeric) {
+ DBG_ERR("Unqualified name [%s]\n", name);
+ TALLOC_FREE(name);
+ return false;
+ }
+
+ /*
+ * nametouid() and nametogid() work with both names and numbers...
+ */
+
+ if (nace->flag & ACE4_IDENTIFIER_GROUP) {
+ sace->who.gid = nametogid(name);
+ if (sace->who.gid == (gid_t)-1) {
+ DBG_ERR("converting id [%s] failed\n", name);
+ TALLOC_FREE(name);
+ return false;
+ }
+ TALLOC_FREE(name);
+ return true;
+ }
+
+ sace->who.uid = nametouid(name);
+ if (sace->who.uid == (gid_t)-1) {
+ DBG_ERR("converting id [%s] failed\n", name);
+ TALLOC_FREE(name);
+ return false;
+ }
+ TALLOC_FREE(name);
+ return true;
+}
+
+static NTSTATUS nfs40acl_to_smb4acl(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ nfsacl40 *nacl,
+ struct SMB4ACL_T **_smb4acl)
+{
+ struct nfs4acl_config *config = NULL;
+ struct SMB4ACL_T *smb4acl = NULL;
+ unsigned naces = nfs40acl_get_naces(nacl);
+ unsigned int i;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ smb4acl = smb_create_smb4acl(mem_ctx);
+ if (smb4acl == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ DBG_DEBUG("nace [%u]\n", naces);
+
+ for (i = 0; i < naces; i++) {
+ nfsace4 *nace = nfs40acl_get_ace(nacl, i);
+ SMB_ACE4PROP_T sace = { 0 };
+
+ DBG_DEBUG("type [%d] flag [%x] mask [%x] who [%*s]\n",
+ nace->type, nace->flag,
+ nace->access_mask,
+ nace->who.utf8string_len,
+ nace->who.utf8string_val);
+
+ ok = map_ace_nfs4_to_smb4(config, nace, &sace);
+ if (!ok) {
+ continue;
+ }
+
+ smb_add_ace4(smb4acl, &sace);
+ }
+
+ *_smb4acl = smb4acl;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nfs41acl_to_smb4acl(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ nfsacl41 *nacl,
+ struct SMB4ACL_T **_smb4acl)
+{
+ struct nfs4acl_config *config = NULL;
+ struct SMB4ACL_T *smb4acl = NULL;
+ unsigned nfsacl41_flag = 0;
+ uint16_t smb4acl_flags = 0;
+ unsigned naces = nfs41acl_get_naces(nacl);
+ unsigned int i;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ smb4acl = smb_create_smb4acl(mem_ctx);
+ if (smb4acl == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ nfsacl41_flag = nfs41acl_get_flags(nacl);
+ smb4acl_flags = nfs4acl_to_smb4acl_flags(nfsacl41_flag);
+ smbacl4_set_controlflags(smb4acl, smb4acl_flags);
+
+ DBG_DEBUG("flags [%x] nace [%u]\n", smb4acl_flags, naces);
+
+ for (i = 0; i < naces; i++) {
+ nfsace4 *nace = nfs41acl_get_ace(nacl, i);
+ SMB_ACE4PROP_T sace = { 0 };
+
+ DBG_DEBUG("type [%d] flag [%x] mask [%x] who [%*s]\n",
+ nace->type, nace->flag,
+ nace->access_mask,
+ nace->who.utf8string_len,
+ nace->who.utf8string_val);
+
+ ok = map_ace_nfs4_to_smb4(config, nace, &sace);
+ if (!ok) {
+ continue;
+ }
+
+ smb_add_ace4(smb4acl, &sace);
+ }
+
+ *_smb4acl = smb4acl;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS nfs4acl_nfs_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl)
+{
+ struct nfs4acl_config *config = NULL;
+ struct SMB4ACL_T *smb4acl = NULL;
+ NTSTATUS status;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (config->nfs_version == ACL4_XATTR_VERSION_40) {
+ nfsacl40 *nacl = NULL;
+
+ status = nfs4acl_nfs_blob_to_nfs40acl(handle,
+ talloc_tos(),
+ blob,
+ &nacl);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = nfs40acl_to_smb4acl(handle, mem_ctx, nacl, &smb4acl);
+ TALLOC_FREE(nacl);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ nfsacl41 *nacl = NULL;
+
+ status = nfs4acl_nfs_blob_to_nfs41acl(handle,
+ talloc_tos(),
+ blob,
+ &nacl);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = nfs41acl_to_smb4acl(handle, mem_ctx, nacl, &smb4acl);
+ TALLOC_FREE(nacl);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ *_smb4acl = smb4acl;
+ return NT_STATUS_OK;
+}
+
+#else /* !HAVE_RPC_XDR_H */
+#include "nfs4_acls.h"
+#include "nfs4acl_xattr_nfs.h"
+NTSTATUS nfs4acl_nfs_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+NTSTATUS nfs4acl_smb4acl_to_nfs_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smbacl,
+ DATA_BLOB *blob)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+#endif /* HAVE_RPC_XDR_H */
diff --git a/source3/modules/nfs4acl_xattr_nfs.h b/source3/modules/nfs4acl_xattr_nfs.h
new file mode 100644
index 0000000..3c4109c
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_nfs.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) Ralph Boehme 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __NFS4ACL_XATTR_NFS_H__
+#define __NFS4ACL_XATTR_NFS_H__
+
+#define NFS4ACL_NFS_XATTR_NAME "system.nfs4_acl"
+
+struct SMB4ACL_T;
+
+NTSTATUS nfs4acl_nfs_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl);
+
+NTSTATUS nfs4acl_smb4acl_to_nfs_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smbacl,
+ DATA_BLOB *blob);
+
+#endif /* __NFS4ACL_XATTR_NFS_H__ */
diff --git a/source3/modules/nfs4acl_xattr_util.c b/source3/modules/nfs4acl_xattr_util.c
new file mode 100644
index 0000000..998dbf2
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_util.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) Ralph Boehme 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "smbd/proto.h"
+#include "libcli/security/security_descriptor.h"
+
+#ifdef HAVE_RPC_XDR_H
+/* <rpc/xdr.h> uses TRUE and FALSE */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#include "nfs4_acls.h"
+#include "nfs41acl.h"
+#include "nfs4acl_xattr_util.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+unsigned smb4acl_to_nfs4acl_flags(uint16_t smb4acl_flags)
+{
+ unsigned nfs4acl_flags = 0;
+
+ if (smb4acl_flags & SEC_DESC_DACL_AUTO_INHERITED) {
+ nfs4acl_flags |= ACL4_AUTO_INHERIT;
+ }
+ if (smb4acl_flags & SEC_DESC_DACL_PROTECTED) {
+ nfs4acl_flags |= ACL4_PROTECTED;
+ }
+ if (smb4acl_flags & SEC_DESC_DACL_DEFAULTED) {
+ nfs4acl_flags |= ACL4_DEFAULTED;
+ }
+
+ return nfs4acl_flags;
+}
+
+uint16_t nfs4acl_to_smb4acl_flags(unsigned nfsacl41_flags)
+{
+ uint16_t smb4acl_flags = SEC_DESC_SELF_RELATIVE;
+
+ if (nfsacl41_flags & ACL4_AUTO_INHERIT) {
+ smb4acl_flags |= SEC_DESC_DACL_AUTO_INHERITED;
+ }
+ if (nfsacl41_flags & ACL4_PROTECTED) {
+ smb4acl_flags |= SEC_DESC_DACL_PROTECTED;
+ }
+ if (nfsacl41_flags & ACL4_DEFAULTED) {
+ smb4acl_flags |= SEC_DESC_DACL_DEFAULTED;
+ }
+
+ return smb4acl_flags;
+}
+#endif /* HAVE_RPC_XDR_H */
diff --git a/source3/modules/nfs4acl_xattr_util.h b/source3/modules/nfs4acl_xattr_util.h
new file mode 100644
index 0000000..2d2c6a1
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_util.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Ralph Boehme 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _NFS4ACL_XATTR_UTIL_H_
+#define _NFS4ACL_XATTR_UTIL_H_
+
+unsigned smb4acl_to_nfs4acl_flags(uint16_t smb4acl_flags);
+uint16_t nfs4acl_to_smb4acl_flags(unsigned nfsacl41_flags);
+
+#endif /* _NFS4ACL_XATTR_UTIL_H_ */
diff --git a/source3/modules/nfs4acl_xattr_xdr.c b/source3/modules/nfs4acl_xattr_xdr.c
new file mode 100644
index 0000000..439378e
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_xdr.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) Ralph Boehme 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "smbd/proto.h"
+#include "libcli/security/security_descriptor.h"
+#include "libcli/security/security_token.h"
+#include "nfs4_acls.h"
+#include "nfs4acl_xattr.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#ifdef HAVE_RPC_XDR_H
+/* <rpc/xdr.h> uses TRUE and FALSE */
+#ifdef TRUE
+#undef TRUE
+#endif
+
+#ifdef FALSE
+#undef FALSE
+#endif
+
+#ifdef HAVE_RPC_TYPES_H
+#include <rpc/types.h>
+#endif
+#include <rpc/xdr.h>
+#include "nfs41acl.h"
+#include "nfs4acl_xattr_xdr.h"
+#include "nfs4acl_xattr_util.h"
+
+static unsigned nfs4acli_get_naces(nfsacl41i *nacl)
+{
+ return nacl->na41_aces.na41_aces_len;
+}
+
+static void nfs4acli_set_naces(nfsacl41i *nacl, unsigned naces)
+{
+ nacl->na41_aces.na41_aces_len = naces;
+}
+
+static unsigned nfs4acli_get_flags(nfsacl41i *nacl)
+{
+ return nacl->na41_flag;
+}
+
+static void nfs4acli_set_flags(nfsacl41i *nacl, unsigned flags)
+{
+ nacl->na41_flag = flags;
+}
+
+static size_t nfs4acli_get_xdrblob_size(nfsacl41i *nacl)
+{
+ size_t acl_size;
+ size_t aces_size;
+ unsigned naces = nfs4acli_get_naces(nacl);
+
+ acl_size = sizeof(aclflag4) + sizeof(unsigned);
+
+ if (naces > NFS4ACL_XDR_MAX_ACES) {
+ DBG_ERR("Too many ACEs: %u\n", naces);
+ return 0;
+ }
+
+ aces_size = naces * sizeof(struct nfsace4i);
+ if (acl_size + aces_size < acl_size) {
+ return 0;
+ }
+ acl_size += aces_size;
+
+ return acl_size;
+}
+
+static size_t nfs4acli_get_xdrblob_naces(size_t _blobsize)
+{
+ size_t blobsize = _blobsize;
+
+ blobsize -= sizeof(aclflag4);
+ blobsize -= sizeof(unsigned);
+ if (blobsize > _blobsize) {
+ return 0;
+ }
+ return (blobsize / sizeof(struct nfsace4i));
+}
+
+static nfsacl41i *nfs4acli_alloc(TALLOC_CTX *mem_ctx, unsigned naces)
+{
+ size_t acl_size = sizeof(nfsacl41i) + (naces * sizeof(struct nfsace4i));
+ nfsacl41i *nacl = NULL;
+
+ if (naces > NFS4ACL_XDR_MAX_ACES) {
+ DBG_ERR("Too many ACEs: %d\n", naces);
+ return NULL;
+ }
+
+ nacl = talloc_zero_size(mem_ctx, acl_size);
+ if (nacl == NULL) {
+ DBG_ERR("talloc_zero_size failed\n");
+ return NULL;
+ }
+
+ nfs4acli_set_naces(nacl, naces);
+ nacl->na41_aces.na41_aces_val =
+ (nfsace4i *)((char *)nacl + sizeof(nfsacl41i));
+
+ return nacl;
+}
+
+static nfsace4i *nfs4acli_get_ace(nfsacl41i *nacl, size_t n)
+{
+ return &nacl->na41_aces.na41_aces_val[n];
+}
+
+static bool smb4acl_to_nfs4acli(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smb4acl,
+ nfsacl41i **_nacl)
+{
+ struct nfs4acl_config *config = NULL;
+ struct SMB4ACE_T *smb4ace = NULL;
+ size_t smb4naces = 0;
+ nfsacl41i *nacl = NULL;
+ uint16_t smb4acl_flags = 0;
+ unsigned nacl_flags = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return false);
+
+ smb4naces = smb_get_naces(smb4acl);
+ nacl = nfs4acli_alloc(mem_ctx, smb4naces);
+ nfs4acli_set_naces(nacl, 0);
+
+ if (config->nfs_version > ACL4_XATTR_VERSION_40) {
+ smb4acl_flags = smbacl4_get_controlflags(smb4acl);
+ nacl_flags = smb4acl_to_nfs4acl_flags(smb4acl_flags);
+ nfs4acli_set_flags(nacl, nacl_flags);
+ }
+
+ smb4ace = smb_first_ace4(smb4acl);
+ while (smb4ace != NULL) {
+ SMB_ACE4PROP_T *ace4prop = smb_get_ace4(smb4ace);
+ size_t nace_count = nfs4acli_get_naces(nacl);
+ nfsace4i *nace = nfs4acli_get_ace(nacl, nace_count);
+
+ nace->type = ace4prop->aceType;
+ nace->flag = ace4prop->aceFlags;
+ nace->access_mask = ace4prop->aceMask;
+
+ if (ace4prop->flags & SMB_ACE4_ID_SPECIAL) {
+ nace->iflag |= ACEI4_SPECIAL_WHO;
+
+ switch (ace4prop->who.special_id) {
+ case SMB_ACE4_WHO_OWNER:
+ nace->who = ACE4_SPECIAL_OWNER;
+ break;
+
+ case SMB_ACE4_WHO_GROUP:
+ nace->who = ACE4_SPECIAL_GROUP;
+ break;
+
+ case SMB_ACE4_WHO_EVERYONE:
+ nace->who = ACE4_SPECIAL_EVERYONE;
+ break;
+
+ default:
+ DBG_ERR("Unsupported special id [%d]\n",
+ ace4prop->who.special_id);
+ continue;
+ }
+ } else {
+ if (ace4prop->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) {
+ nace->flag |= ACE4_IDENTIFIER_GROUP;
+ nace->who = ace4prop->who.gid;
+ } else {
+ nace->who = ace4prop->who.uid;
+ }
+ }
+
+ nace_count++;
+ nfs4acli_set_naces(nacl, nace_count);
+ smb4ace = smb_next_ace4(smb4ace);
+ }
+
+ *_nacl = nacl;
+ return true;
+}
+
+NTSTATUS nfs4acl_smb4acl_to_xdr_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smb4acl,
+ DATA_BLOB *_blob)
+{
+ nfsacl41i *nacl = NULL;
+ XDR xdr = {0};
+ size_t aclblobsize;
+ DATA_BLOB blob;
+ bool ok;
+
+ ok = smb4acl_to_nfs4acli(handle, talloc_tos(), smb4acl, &nacl);
+ if (!ok) {
+ DBG_ERR("smb4acl_to_nfs4acl failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ aclblobsize = nfs4acli_get_xdrblob_size(nacl);
+ if (aclblobsize == 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ blob = data_blob_talloc(mem_ctx, NULL, aclblobsize);
+ if (blob.data == NULL) {
+ TALLOC_FREE(nacl);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ xdrmem_create(&xdr, (char *)blob.data, blob.length, XDR_ENCODE);
+
+ ok = xdr_nfsacl41i(&xdr, nacl);
+ TALLOC_FREE(nacl);
+ if (!ok) {
+ DBG_ERR("xdr_nfs4acl41 failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *_blob = blob;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nfs4acl_xdr_blob_to_nfs4acli(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ nfsacl41i **_nacl)
+{
+ struct nfs4acl_config *config = NULL;
+ nfsacl41i *nacl = NULL;
+ size_t naces;
+ XDR xdr = {0};
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ naces = nfs4acli_get_xdrblob_naces(blob->length);
+ nacl = nfs4acli_alloc(mem_ctx, naces);
+
+ xdrmem_create(&xdr, (char *)blob->data, blob->length, XDR_DECODE);
+
+ ok = xdr_nfsacl41i(&xdr, nacl);
+ if (!ok) {
+ DBG_ERR("xdr_nfs4acl41 failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (config->nfs_version == ACL4_XATTR_VERSION_40) {
+ nacl->na41_flag = 0;
+ }
+
+ *_nacl = nacl;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nfs4acli_to_smb4acl(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ nfsacl41i *nacl,
+ struct SMB4ACL_T **_smb4acl)
+{
+ struct nfs4acl_config *config = NULL;
+ struct SMB4ACL_T *smb4acl = NULL;
+ unsigned nfsacl41_flag = 0;
+ uint16_t smb4acl_flags = 0;
+ unsigned naces = nfs4acli_get_naces(nacl);
+ unsigned i;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ smb4acl = smb_create_smb4acl(mem_ctx);
+ if (smb4acl == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (config->nfs_version > ACL4_XATTR_VERSION_40) {
+ nfsacl41_flag = nfs4acli_get_flags(nacl);
+ smb4acl_flags = nfs4acl_to_smb4acl_flags(nfsacl41_flag);
+ smbacl4_set_controlflags(smb4acl, smb4acl_flags);
+ }
+
+ DBG_DEBUG("flags [%x] nace [%u]\n", smb4acl_flags, naces);
+
+ for (i = 0; i < naces; i++) {
+ nfsace4i *nace = nfs4acli_get_ace(nacl, i);
+ SMB_ACE4PROP_T smbace = { 0 };
+
+ DBG_DEBUG("type [%d] iflag [%x] flag [%x] mask [%x] who [%d]\n",
+ nace->type, nace->iflag, nace->flag,
+ nace->access_mask, nace->who);
+
+ smbace.aceType = nace->type;
+ smbace.aceFlags = nace->flag;
+ smbace.aceMask = nace->access_mask;
+
+ if (nace->iflag & ACEI4_SPECIAL_WHO) {
+ smbace.flags |= SMB_ACE4_ID_SPECIAL;
+
+ switch (nace->who) {
+ case ACE4_SPECIAL_OWNER:
+ smbace.who.special_id = SMB_ACE4_WHO_OWNER;
+ break;
+
+ case ACE4_SPECIAL_GROUP:
+ smbace.who.special_id = SMB_ACE4_WHO_GROUP;
+ break;
+
+ case ACE4_SPECIAL_EVERYONE:
+ smbace.who.special_id = SMB_ACE4_WHO_EVERYONE;
+ break;
+
+ default:
+ DBG_ERR("Unknown special id [%d]\n", nace->who);
+ continue;
+ }
+ } else {
+ if (nace->flag & ACE4_IDENTIFIER_GROUP) {
+ smbace.who.gid = nace->who;
+ } else {
+ smbace.who.uid = nace->who;
+ }
+ }
+
+ smb_add_ace4(smb4acl, &smbace);
+ }
+
+ *_smb4acl = smb4acl;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS nfs4acl_xdr_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl)
+{
+ struct nfs4acl_config *config = NULL;
+ nfsacl41i *nacl = NULL;
+ struct SMB4ACL_T *smb4acl = NULL;
+ NTSTATUS status;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ status = nfs4acl_xdr_blob_to_nfs4acli(handle, talloc_tos(), blob, &nacl);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = nfs4acli_to_smb4acl(handle, mem_ctx, nacl, &smb4acl);
+ TALLOC_FREE(nacl);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *_smb4acl = smb4acl;
+ return NT_STATUS_OK;
+}
+
+#else /* !HAVE_RPC_XDR_H */
+#include "nfs4acl_xattr_xdr.h"
+NTSTATUS nfs4acl_xdr_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+NTSTATUS nfs4acl_smb4acl_to_xdr_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smbacl,
+ DATA_BLOB *blob)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+#endif /* HAVE_RPC_XDR_H */
diff --git a/source3/modules/nfs4acl_xattr_xdr.h b/source3/modules/nfs4acl_xattr_xdr.h
new file mode 100644
index 0000000..4a79a0d
--- /dev/null
+++ b/source3/modules/nfs4acl_xattr_xdr.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) Ralph Boehme 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef __NFS4ACL_XATTR_XDR_H__
+#define __NFS4ACL_XATTR_XDR_H__
+
+#define NFS4ACL_XDR_XATTR_NAME "security.nfs4acl_xdr"
+
+NTSTATUS nfs4acl_xdr_blob_to_smb4(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct SMB4ACL_T **_smb4acl);
+
+NTSTATUS nfs4acl_smb4acl_to_xdr_blob(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T *smbacl,
+ DATA_BLOB *blob);
+
+#endif /* __NFS4ACL_XATTR_XDR_H__ */
diff --git a/source3/modules/non_posix_acls.c b/source3/modules/non_posix_acls.c
new file mode 100644
index 0000000..81e126f
--- /dev/null
+++ b/source3/modules/non_posix_acls.c
@@ -0,0 +1,63 @@
+/*
+ Unix SMB/CIFS implementation.
+ Access Control List handling
+ Copyright (C) Andrew Bartlett 2012.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/ndr_xattr.h"
+#include "modules/non_posix_acls.h"
+
+int non_posix_sys_acl_blob_get_fd_helper(vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB acl_as_blob,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob)
+{
+ SMB_STRUCT_STAT sbuf;
+ TALLOC_CTX *frame;
+ struct xattr_sys_acl_hash_wrapper acl_wrapper;
+ int ret;
+
+ frame = talloc_stackframe();
+
+ acl_wrapper.acl_as_blob = acl_as_blob;
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ ret = smb_vfs_call_fstat(handle, fsp, &sbuf);
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ } else {
+ sbuf = fsp->fsp_name->st;
+ }
+
+ acl_wrapper.owner = sbuf.st_ex_uid;
+ acl_wrapper.group = sbuf.st_ex_gid;
+ acl_wrapper.mode = sbuf.st_ex_mode;
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(blob, mem_ctx,
+ &acl_wrapper,
+ (ndr_push_flags_fn_t)ndr_push_xattr_sys_acl_hash_wrapper))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/modules/non_posix_acls.h b/source3/modules/non_posix_acls.h
new file mode 100644
index 0000000..efa0455
--- /dev/null
+++ b/source3/modules/non_posix_acls.h
@@ -0,0 +1,24 @@
+/*
+ Unix SMB/CIFS implementation.
+ Access Control List handling
+ Copyright (C) Andrew Bartlett 2012.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+int non_posix_sys_acl_blob_get_fd_helper(vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB acl_as_blob,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob);
diff --git a/source3/modules/offload_token.c b/source3/modules/offload_token.c
new file mode 100644
index 0000000..3b71a00
--- /dev/null
+++ b/source3/modules/offload_token.c
@@ -0,0 +1,348 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Ralph Boehme 2017
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/security/security.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "librpc/gen_ndr/ioctl.h"
+#include "offload_token.h"
+
+struct vfs_offload_ctx {
+ bool initialized;
+ struct db_context *db_ctx;
+};
+
+NTSTATUS vfs_offload_token_ctx_init(TALLOC_CTX *mem_ctx,
+ struct vfs_offload_ctx **_ctx)
+{
+ struct vfs_offload_ctx *ctx = *_ctx;
+
+ if (ctx != NULL) {
+ if (!ctx->initialized) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ return NT_STATUS_OK;
+ }
+
+ ctx = talloc_zero(mem_ctx, struct vfs_offload_ctx);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ctx->db_ctx = db_open_rbt(mem_ctx);
+ if (ctx->db_ctx == NULL) {
+ TALLOC_FREE(ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ctx->initialized = true;
+ *_ctx = ctx;
+ return NT_STATUS_OK;
+}
+
+struct fsp_token_link {
+ struct vfs_offload_ctx *ctx;
+ DATA_BLOB token_blob;
+};
+
+static int fsp_token_link_destructor(struct fsp_token_link *link)
+{
+ DATA_BLOB token_blob = link->token_blob;
+ TDB_DATA key = make_tdb_data(token_blob.data, token_blob.length);
+ NTSTATUS status;
+
+ status = dbwrap_delete(link->ctx->db_ctx, key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_delete failed: %s. Token:\n", nt_errstr(status));
+ dump_data(0, token_blob.data, token_blob.length);
+ return -1;
+ }
+
+ return 0;
+}
+
+struct vfs_offload_token_db_store_fsp_state {
+ const struct files_struct *fsp;
+ const DATA_BLOB *token_blob;
+ NTSTATUS status;
+};
+
+static void vfs_offload_token_db_store_fsp_fn(
+ struct db_record *rec, TDB_DATA value, void *private_data)
+{
+ struct vfs_offload_token_db_store_fsp_state *state = private_data;
+ const struct files_struct *fsp = state->fsp;
+ const DATA_BLOB *token_blob = state->token_blob;
+ files_struct *token_db_fsp = NULL;
+ void *ptr = NULL;
+
+ if (value.dsize == 0) {
+ value = make_tdb_data((uint8_t *)&fsp, sizeof(files_struct *));
+ state->status = dbwrap_record_store(rec, value, 0);
+ return;
+ }
+
+ if (value.dsize != sizeof(ptr)) {
+ DBG_ERR("Bad db entry for token:\n");
+ dump_data(1, token_blob->data, token_blob->length);
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ return;
+ }
+ memcpy(&ptr, value.dptr, value.dsize);
+
+ token_db_fsp = talloc_get_type_abort(ptr, struct files_struct);
+ if (token_db_fsp != fsp) {
+ DBG_ERR("token for fsp [%s] matches already known "
+ "but different fsp [%s]:\n",
+ fsp_str_dbg(fsp),
+ fsp_str_dbg(token_db_fsp));
+ dump_data(1, token_blob->data, token_blob->length);
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ return;
+ }
+}
+
+NTSTATUS vfs_offload_token_db_store_fsp(struct vfs_offload_ctx *ctx,
+ const files_struct *fsp,
+ const DATA_BLOB *token_blob)
+{
+ struct vfs_offload_token_db_store_fsp_state state = {
+ .fsp = fsp, .token_blob = token_blob,
+ };
+ struct fsp_token_link *link = NULL;
+ TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length);
+ NTSTATUS status;
+
+ link = talloc(fsp, struct fsp_token_link);
+ if (link == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *link = (struct fsp_token_link) {
+ .ctx = ctx,
+ .token_blob = data_blob_dup_talloc(link, *token_blob),
+ };
+ if (link->token_blob.data == NULL) {
+ TALLOC_FREE(link);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dbwrap_do_locked(
+ ctx->db_ctx,
+ key,
+ vfs_offload_token_db_store_fsp_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(link);
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_DEBUG("vfs_offload_token_db_store_fsp_fn failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(link);
+ return status;
+ }
+
+ talloc_set_destructor(link, fsp_token_link_destructor);
+ return NT_STATUS_OK;
+}
+
+struct vfs_offload_token_db_fetch_fsp_state {
+ struct files_struct **fsp;
+ NTSTATUS status;
+};
+
+static void vfs_offload_token_db_fetch_fsp_fn(
+ TDB_DATA key, TDB_DATA value, void *private_data)
+{
+ struct vfs_offload_token_db_fetch_fsp_state *state = private_data;
+ void *ptr;
+
+ if (value.dsize != sizeof(ptr)) {
+ DBG_ERR("Bad db entry for token:\n");
+ dump_data(1, key.dptr, key.dsize);
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ return;
+ }
+
+ memcpy(&ptr, value.dptr, value.dsize);
+ *state->fsp = talloc_get_type_abort(ptr, struct files_struct);
+}
+
+NTSTATUS vfs_offload_token_db_fetch_fsp(struct vfs_offload_ctx *ctx,
+ const DATA_BLOB *token_blob,
+ files_struct **fsp)
+{
+ struct vfs_offload_token_db_fetch_fsp_state state = { .fsp = fsp };
+ TDB_DATA key = make_tdb_data(token_blob->data, token_blob->length);
+ NTSTATUS status;
+
+ status = dbwrap_parse_record(
+ ctx->db_ctx,
+ key,
+ vfs_offload_token_db_fetch_fsp_fn,
+ &state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Unknown token:\n");
+ dump_data(10, token_blob->data, token_blob->length);
+ return status;
+ }
+ return state.status;
+}
+
+NTSTATUS vfs_offload_token_create_blob(TALLOC_CTX *mem_ctx,
+ const files_struct *fsp,
+ uint32_t fsctl,
+ DATA_BLOB *token_blob)
+{
+ size_t len;
+
+ switch (fsctl) {
+ case FSCTL_DUP_EXTENTS_TO_FILE:
+ len = 20;
+ break;
+ case FSCTL_SRV_REQUEST_RESUME_KEY:
+ len = 24;
+ break;
+ default:
+ DBG_ERR("Invalid fsctl [%" PRIu32 "]\n", fsctl);
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ *token_blob = data_blob_talloc_zero(mem_ctx, len);
+ if (token_blob->length == 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* combine persistent and volatile handles for the resume key */
+ SBVAL(token_blob->data,
+ SMB_VFS_ODX_TOKEN_OFFSET_PFID,
+ fsp->op->global->open_persistent_id);
+ SBVAL(token_blob->data,
+ SMB_VFS_ODX_TOKEN_OFFSET_VFID,
+ fsp->op->global->open_volatile_id);
+ SIVAL(token_blob->data,
+ SMB_VFS_ODX_TOKEN_OFFSET_FSCTL,
+ fsctl);
+
+ return NT_STATUS_OK;
+}
+
+
+NTSTATUS vfs_offload_token_check_handles(uint32_t fsctl,
+ files_struct *src_fsp,
+ files_struct *dst_fsp)
+{
+ NTSTATUS status;
+
+ if (src_fsp->vuid != dst_fsp->vuid) {
+ DBG_INFO("copy chunk handles not in the same session.\n");
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!NT_STATUS_IS_OK(src_fsp->op->status)) {
+ DBG_INFO("copy chunk source handle invalid: %s\n",
+ nt_errstr(src_fsp->op->status));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!NT_STATUS_IS_OK(dst_fsp->op->status)) {
+ DBG_INFO("copy chunk destination handle invalid: %s\n",
+ nt_errstr(dst_fsp->op->status));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (src_fsp->fsp_flags.closing) {
+ DBG_INFO("copy chunk src handle with closing in progress.\n");
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (dst_fsp->fsp_flags.closing) {
+ DBG_INFO("copy chunk dst handle with closing in progress.\n");
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (src_fsp->fsp_flags.is_directory) {
+ DBG_INFO("copy chunk no read on src directory handle (%s).\n",
+ smb_fname_str_dbg(src_fsp->fsp_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (dst_fsp->fsp_flags.is_directory) {
+ DBG_INFO("copy chunk no read on dst directory handle (%s).\n",
+ smb_fname_str_dbg(dst_fsp->fsp_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (IS_IPC(src_fsp->conn) || IS_IPC(dst_fsp->conn)) {
+ DBG_INFO("copy chunk no access on IPC$ handle.\n");
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (IS_PRINT(src_fsp->conn) || IS_PRINT(dst_fsp->conn)) {
+ DBG_INFO("copy chunk no access on PRINT handle.\n");
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request
+ * The server MUST fail the request with STATUS_ACCESS_DENIED if any of
+ * the following are true:
+ * - The Open.GrantedAccess of the destination file does not include
+ * FILE_WRITE_DATA or FILE_APPEND_DATA.
+ *
+ * A non writable dst handle also doesn't make sense for other fsctls.
+ */
+ status = check_any_access_fsp(dst_fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("dest handle not writable (%s).\n",
+ smb_fname_str_dbg(dst_fsp->fsp_name));
+ return status;
+ }
+ /*
+ * - The Open.GrantedAccess of the destination file does not include
+ * FILE_READ_DATA, and the CtlCode is FSCTL_SRV_COPYCHUNK.
+ */
+ if ((fsctl == FSCTL_SRV_COPYCHUNK) && !CHECK_READ_IOCTL(dst_fsp)) {
+ DBG_INFO("copy chunk no read on dest handle (%s).\n",
+ smb_fname_str_dbg(dst_fsp->fsp_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ /*
+ * - The Open.GrantedAccess of the source file does not include
+ * FILE_READ_DATA access.
+ */
+ if (!CHECK_READ_SMB2(src_fsp)) {
+ DBG_INFO("src handle not readable (%s).\n",
+ smb_fname_str_dbg(src_fsp->fsp_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/modules/offload_token.h b/source3/modules/offload_token.h
new file mode 100644
index 0000000..8662d3e
--- /dev/null
+++ b/source3/modules/offload_token.h
@@ -0,0 +1,44 @@
+/*
+ Unix SMB/Netbios implementation.
+ Copyright (c) 2017 Ralph Boehme <slow@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _OFFLOAD_TOKEN_H_
+#define _OFFLOAD_TOKEN_H_
+
+struct vfs_offload_ctx;
+struct req_resume_key_rsp;
+
+#define SMB_VFS_ODX_TOKEN_OFFSET_PFID 0
+#define SMB_VFS_ODX_TOKEN_OFFSET_VFID 8
+#define SMB_VFS_ODX_TOKEN_OFFSET_FSCTL 16
+
+NTSTATUS vfs_offload_token_ctx_init(TALLOC_CTX *mem_ctx,
+ struct vfs_offload_ctx **_ctx);
+NTSTATUS vfs_offload_token_db_store_fsp(struct vfs_offload_ctx *ctx,
+ const files_struct *fsp,
+ const DATA_BLOB *token_blob);
+NTSTATUS vfs_offload_token_db_fetch_fsp(struct vfs_offload_ctx *ctx,
+ const DATA_BLOB *token_blob,
+ files_struct **fsp);
+NTSTATUS vfs_offload_token_create_blob(TALLOC_CTX *mem_ctx,
+ const files_struct *fsp,
+ uint32_t fsctl,
+ DATA_BLOB *token_blob);
+NTSTATUS vfs_offload_token_check_handles(uint32_t fsctl,
+ files_struct *src_fsp,
+ files_struct *dst_fsp);
+#endif
diff --git a/source3/modules/posixacl_xattr.c b/source3/modules/posixacl_xattr.c
new file mode 100644
index 0000000..365cdc7
--- /dev/null
+++ b/source3/modules/posixacl_xattr.c
@@ -0,0 +1,423 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set posix acls through xattr
+ Copyright (c) 2013 Anand Avati <avati@redhat.com>
+ Copyright (c) 2016 Yan, Zheng <zyan@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "modules/posixacl_xattr.h"
+
+/*
+ POSIX ACL Format:
+
+ Size = 4 (header) + N * 8 (entry)
+
+ Offset Size Field (Little Endian)
+ -------------------------------------
+ 0-3 4-byte Version
+
+ 4-5 2-byte Entry-1 tag
+ 6-7 2-byte Entry-1 perm
+ 8-11 4-byte Entry-1 id
+
+ 12-13 2-byte Entry-2 tag
+ 14-15 2-byte Entry-2 perm
+ 16-19 4-byte Entry-2 id
+
+ ...
+
+ */
+
+
+
+/* private functions */
+
+#define ACL_EA_ACCESS "system.posix_acl_access"
+#define ACL_EA_DEFAULT "system.posix_acl_default"
+#define ACL_EA_VERSION 0x0002
+#define ACL_EA_HEADER_SIZE 4
+#define ACL_EA_ENTRY_SIZE 8
+
+#define ACL_EA_SIZE(n) (ACL_EA_HEADER_SIZE + ((n) * ACL_EA_ENTRY_SIZE))
+
+static SMB_ACL_T mode_to_smb_acl(mode_t mode, TALLOC_CTX *mem_ctx)
+{
+ struct smb_acl_t *result;
+ int count;
+
+ count = 3;
+ result = sys_acl_init(mem_ctx);
+ if (!result) {
+ return NULL;
+ }
+
+ result->acl = talloc_array(result, struct smb_acl_entry, count);
+ if (!result->acl) {
+ errno = ENOMEM;
+ talloc_free(result);
+ return NULL;
+ }
+
+ result->count = count;
+
+ result->acl[0].a_type = SMB_ACL_USER_OBJ;
+ result->acl[0].a_perm = (mode & S_IRWXU) >> 6;
+
+ result->acl[1].a_type = SMB_ACL_GROUP_OBJ;
+ result->acl[1].a_perm = (mode & S_IRWXG) >> 3;
+
+ result->acl[2].a_type = SMB_ACL_OTHER;
+ result->acl[2].a_perm = mode & S_IRWXO;
+
+ return result;
+}
+
+static SMB_ACL_T posixacl_xattr_to_smb_acl(const char *buf, size_t xattr_size,
+ TALLOC_CTX *mem_ctx)
+{
+ int count;
+ int size;
+ struct smb_acl_entry *smb_ace;
+ struct smb_acl_t *result;
+ int i;
+ int offset;
+ uint16_t tag;
+ uint16_t perm;
+ uint32_t id;
+
+ size = xattr_size;
+
+ if (size < ACL_EA_HEADER_SIZE) {
+ /* ACL should be at least as big as the header (4 bytes) */
+ errno = EINVAL;
+ return NULL;
+ }
+
+ /* Version is the first 4 bytes of the ACL */
+ if (IVAL(buf, 0) != ACL_EA_VERSION) {
+ DEBUG(0, ("Unknown ACL EA version: %d\n",
+ IVAL(buf, 0)));
+ errno = EINVAL;
+ return NULL;
+ }
+ offset = ACL_EA_HEADER_SIZE;
+
+ size -= ACL_EA_HEADER_SIZE;
+ if (size % ACL_EA_ENTRY_SIZE) {
+ /* Size of entries must strictly be a multiple of
+ size of an ACE (8 bytes)
+ */
+ DEBUG(0, ("Invalid ACL EA size: %d\n", size));
+ errno = EINVAL;
+ return NULL;
+ }
+
+ count = size / ACL_EA_ENTRY_SIZE;
+
+ result = sys_acl_init(mem_ctx);
+ if (!result) {
+ return NULL;
+ }
+
+ result->acl = talloc_array(result, struct smb_acl_entry, count);
+ if (!result->acl) {
+ errno = ENOMEM;
+ talloc_free(result);
+ return NULL;
+ }
+
+ result->count = count;
+
+ smb_ace = result->acl;
+
+ for (i = 0; i < count; i++) {
+ /* TAG is the first 2 bytes of an entry */
+ tag = SVAL(buf, offset);
+ offset += 2;
+
+ /* PERM is the next 2 bytes of an entry */
+ perm = SVAL(buf, offset);
+ offset += 2;
+
+ /* ID is the last 4 bytes of an entry */
+ id = IVAL(buf, offset);
+ offset += 4;
+
+ switch(tag) {
+ case ACL_USER:
+ smb_ace->a_type = SMB_ACL_USER;
+ break;
+ case ACL_USER_OBJ:
+ smb_ace->a_type = SMB_ACL_USER_OBJ;
+ break;
+ case ACL_GROUP:
+ smb_ace->a_type = SMB_ACL_GROUP;
+ break;
+ case ACL_GROUP_OBJ:
+ smb_ace->a_type = SMB_ACL_GROUP_OBJ;
+ break;
+ case ACL_OTHER:
+ smb_ace->a_type = SMB_ACL_OTHER;
+ break;
+ case ACL_MASK:
+ smb_ace->a_type = SMB_ACL_MASK;
+ break;
+ default:
+ DEBUG(0, ("unknown tag type %d\n", (unsigned int) tag));
+ errno = EINVAL;
+ return NULL;
+ }
+
+
+ switch(smb_ace->a_type) {
+ case SMB_ACL_USER:
+ smb_ace->info.user.uid = id;
+ break;
+ case SMB_ACL_GROUP:
+ smb_ace->info.group.gid = id;
+ break;
+ default:
+ break;
+ }
+
+ smb_ace->a_perm = 0;
+ smb_ace->a_perm |= ((perm & ACL_READ) ? SMB_ACL_READ : 0);
+ smb_ace->a_perm |= ((perm & ACL_WRITE) ? SMB_ACL_WRITE : 0);
+ smb_ace->a_perm |= ((perm & ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
+
+ smb_ace++;
+ }
+
+ return result;
+}
+
+
+static int posixacl_xattr_entry_compare(const void *left, const void *right)
+{
+ int ret = 0;
+ uint16_t tag_left, tag_right;
+ uint32_t id_left, id_right;
+
+ /*
+ Sorting precedence:
+ - Smaller TAG values must be earlier.
+ - Within same TAG, smaller identifiers must be earlier, E.g:
+ UID 0 entry must be earlier than UID 200
+ GID 17 entry must be earlier than GID 19
+ */
+
+ /* TAG is the first element in the entry */
+ tag_left = SVAL(left, 0);
+ tag_right = SVAL(right, 0);
+
+ ret = (tag_left - tag_right);
+ if (!ret) {
+ /* ID is the third element in the entry, after two short
+ integers (tag and perm), i.e at offset 4.
+ */
+ id_left = IVAL(left, 4);
+ id_right = IVAL(right, 4);
+ ret = id_left - id_right;
+ }
+
+ return ret;
+}
+
+
+static int smb_acl_to_posixacl_xattr(SMB_ACL_T theacl, char *buf, size_t len)
+{
+ ssize_t size;
+ struct smb_acl_entry *smb_ace;
+ int i;
+ int count;
+ uint16_t tag;
+ uint16_t perm;
+ uint32_t id;
+ int offset;
+
+ count = theacl->count;
+
+ size = ACL_EA_SIZE(count);
+ if (!buf) {
+ return size;
+ }
+ if (len < size) {
+ return -ERANGE;
+ }
+ smb_ace = theacl->acl;
+
+ /* Version is the first 4 bytes of the ACL */
+ SIVAL(buf, 0, ACL_EA_VERSION);
+ offset = ACL_EA_HEADER_SIZE;
+
+ for (i = 0; i < count; i++) {
+ /* Calculate tag */
+ switch(smb_ace->a_type) {
+ case SMB_ACL_USER:
+ tag = ACL_USER;
+ break;
+ case SMB_ACL_USER_OBJ:
+ tag = ACL_USER_OBJ;
+ break;
+ case SMB_ACL_GROUP:
+ tag = ACL_GROUP;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ tag = ACL_GROUP_OBJ;
+ break;
+ case SMB_ACL_OTHER:
+ tag = ACL_OTHER;
+ break;
+ case SMB_ACL_MASK:
+ tag = ACL_MASK;
+ break;
+ default:
+ DEBUG(0, ("Unknown tag value %d\n",
+ smb_ace->a_type));
+ return -EINVAL;
+ }
+
+
+ /* Calculate id */
+ switch(smb_ace->a_type) {
+ case SMB_ACL_USER:
+ id = smb_ace->info.user.uid;
+ break;
+ case SMB_ACL_GROUP:
+ id = smb_ace->info.group.gid;
+ break;
+ default:
+ id = ACL_UNDEFINED_ID;
+ break;
+ }
+
+ /* Calculate perm */
+ perm = 0;
+ perm |= (smb_ace->a_perm & SMB_ACL_READ) ? ACL_READ : 0;
+ perm |= (smb_ace->a_perm & SMB_ACL_WRITE) ? ACL_WRITE : 0;
+ perm |= (smb_ace->a_perm & SMB_ACL_EXECUTE) ? ACL_EXECUTE : 0;
+
+ /* TAG is the first 2 bytes of an entry */
+ SSVAL(buf, offset, tag);
+ offset += 2;
+
+ /* PERM is the next 2 bytes of an entry */
+ SSVAL(buf, offset, perm);
+ offset += 2;
+
+ /* ID is the last 4 bytes of an entry */
+ SIVAL(buf, offset, id);
+ offset += 4;
+
+ smb_ace++;
+ }
+
+ /* Skip the header, sort @count number of 8-byte entries */
+ qsort(buf+ACL_EA_HEADER_SIZE, count, ACL_EA_ENTRY_SIZE,
+ posixacl_xattr_entry_compare);
+
+ return size;
+}
+
+SMB_ACL_T posixacl_xattr_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ int ret;
+ int size = ACL_EA_SIZE(20);
+ char *buf = alloca(size);
+ const char *name;
+
+ if (type == SMB_ACL_TYPE_ACCESS) {
+ name = ACL_EA_ACCESS;
+ } else if (type == SMB_ACL_TYPE_DEFAULT) {
+ name = ACL_EA_DEFAULT;
+ } else {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (!buf) {
+ return NULL;
+ }
+
+ ret = SMB_VFS_FGETXATTR(fsp, name, buf, size);
+ if (ret < 0 && errno == ERANGE) {
+ size = SMB_VFS_FGETXATTR(fsp, name, NULL, 0);
+ if (size > 0) {
+ buf = alloca(size);
+ if (!buf) {
+ return NULL;
+ }
+ ret = SMB_VFS_FGETXATTR(fsp, name, buf, size);
+ }
+ }
+
+ if (ret > 0) {
+ return posixacl_xattr_to_smb_acl(buf, ret, mem_ctx);
+ }
+ if (ret == 0 || errno == ENOATTR) {
+ SMB_STRUCT_STAT sbuf;
+ ret = SMB_VFS_FSTAT(fsp, &sbuf);
+ if (ret == 0)
+ return mode_to_smb_acl(sbuf.st_ex_mode, mem_ctx);
+ }
+ return NULL;
+}
+
+int posixacl_xattr_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ const char *name = NULL;
+ char *buf;
+ ssize_t size;
+ int ret;
+
+ if (type == SMB_ACL_TYPE_ACCESS) {
+ name = ACL_EA_ACCESS;
+ } else if (type == SMB_ACL_TYPE_DEFAULT) {
+ name = ACL_EA_DEFAULT;
+ } else {
+ errno = EINVAL;
+ return -1;
+ }
+
+ size = smb_acl_to_posixacl_xattr(theacl, NULL, 0);
+ buf = alloca(size);
+ if (!buf) {
+ return -1;
+ }
+
+ ret = smb_acl_to_posixacl_xattr(theacl, buf, size);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+
+ return SMB_VFS_FSETXATTR(fsp, name, buf, size, 0);
+}
+
+int posixacl_xattr_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return SMB_VFS_FREMOVEXATTR(fsp, ACL_EA_DEFAULT);
+}
diff --git a/source3/modules/posixacl_xattr.h b/source3/modules/posixacl_xattr.h
new file mode 100644
index 0000000..6721538
--- /dev/null
+++ b/source3/modules/posixacl_xattr.h
@@ -0,0 +1,36 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set posix acl through xattr
+ Copyright (c) 2013 Anand Avati <avati@redhat.com>
+ Copyright (c) 2016 Yan, Zheng <zyan@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __POSIXACL_XATTR_H__
+#define __POSIXACL_XATTR_H__
+
+SMB_ACL_T posixacl_xattr_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+
+int posixacl_xattr_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl);
+
+int posixacl_xattr_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp);
+#endif
diff --git a/source3/modules/test_nfs4_acls.c b/source3/modules/test_nfs4_acls.c
new file mode 100644
index 0000000..f47ff9b
--- /dev/null
+++ b/source3/modules/test_nfs4_acls.c
@@ -0,0 +1,1898 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Unit test for NFS4 ACL handling
+ *
+ * Copyright (C) Christof Schmitt 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nfs4_acls.c"
+#include "librpc/gen_ndr/idmap.h"
+#include "idmap_cache.h"
+#include <cmocka.h>
+
+struct test_sids {
+ const char *sid_str;
+ struct unixid unix_id;
+} test_sids[] = {
+ { "S-1-5-2-123-456-789-100", { 1000, ID_TYPE_UID }},
+ { "S-1-5-2-123-456-789-101", { 1001, ID_TYPE_GID }},
+ { "S-1-5-2-123-456-789-102", { 1002, ID_TYPE_BOTH }},
+ { SID_CREATOR_OWNER, { 1003, ID_TYPE_UID }},
+ { SID_CREATOR_GROUP, { 1004, ID_TYPE_GID }},
+ { "S-1-5-2-123-456-789-103", { 1000, ID_TYPE_GID }},
+ { "S-1-5-2-123-456-789-104", { 1005, ID_TYPE_BOTH }},
+ { "S-1-5-2-123-456-789-105", { 1006, ID_TYPE_BOTH }},
+ { "S-1-5-2-123-456-789-106", { 1007, ID_TYPE_BOTH }},
+};
+
+static int group_setup(void **state)
+{
+ struct dom_sid *sids = NULL;
+ int i;
+
+ sids = talloc_array(NULL, struct dom_sid, ARRAY_SIZE(test_sids));
+ assert_non_null(sids);
+
+ for (i = 0; i < ARRAY_SIZE(test_sids); i++) {
+ assert_true(dom_sid_parse(test_sids[i].sid_str, &sids[i]));
+ idmap_cache_set_sid2unixid(&sids[i], &test_sids[i].unix_id);
+ }
+
+ *state = sids;
+
+ return 0;
+
+}
+
+static int group_teardown(void **state)
+{
+ struct dom_sid *sids = *state;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(test_sids); i++) {
+ assert_true(idmap_cache_del_sid(&sids[i]));
+ }
+
+ TALLOC_FREE(sids);
+ *state = NULL;
+
+ return 0;
+}
+
+/*
+ * Run this as first test to verify that the id mappings used by other
+ * tests are available in the cache.
+ */
+static void test_cached_id_mappings(void **state)
+{
+ struct dom_sid *sids = *state;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(test_sids); i++) {
+ struct dom_sid *sid = &sids[i];
+ struct unixid *unix_id = &test_sids[i].unix_id;
+ uid_t uid;
+ gid_t gid;
+
+ switch(unix_id->type) {
+ case ID_TYPE_UID:
+ assert_true(sid_to_uid(sid, &uid));
+ assert_int_equal(uid, unix_id->id);
+ assert_false(sid_to_gid(sid, &gid));
+ break;
+ case ID_TYPE_GID:
+ assert_false(sid_to_uid(sid, &uid));
+ assert_true(sid_to_gid(sid, &gid));
+ assert_int_equal(gid, unix_id->id);
+ break;
+ case ID_TYPE_BOTH:
+ assert_true(sid_to_uid(sid, &uid));
+ assert_int_equal(uid, unix_id->id);
+ assert_true(sid_to_gid(sid, &gid));
+ assert_int_equal(gid, unix_id->id);
+ break;
+ default:
+ fail_msg("Unknown id type %d\n", unix_id->type);
+ break;
+ }
+ }
+}
+
+static void test_empty_nfs4_to_dacl(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct SMB4ACL_T *nfs4_acl;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[0], &sids[1], false,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 0);
+ assert_null(dacl_aces);
+
+ TALLOC_FREE(frame);
+}
+
+static void test_empty_dacl_to_nfs4(void **state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct SMB4ACL_T *nfs4_acl;
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS, 0, NULL);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, false, dacl, &params, 1001, 1002);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 0);
+ assert_null(smb_first_ace4(nfs4_acl));
+}
+
+struct ace_dacl_type_mapping {
+ uint32_t nfs4_type;
+ enum security_ace_type dacl_type;
+} ace_dacl_type_mapping[] = {
+ { SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE, SEC_ACE_TYPE_ACCESS_ALLOWED },
+ { SMB_ACE4_ACCESS_DENIED_ACE_TYPE, SEC_ACE_TYPE_ACCESS_DENIED },
+};
+
+static void test_acl_type_nfs4_to_dacl(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ace_dacl_type_mapping); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ SMB_ACE4PROP_T nfs4_ace;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.uid = 1000,
+ .aceType = ace_dacl_type_mapping[i].nfs4_type,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_READ_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[2], &sids[3], false,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 1);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type,
+ ace_dacl_type_mapping[i].dacl_type);
+ assert_int_equal(dacl_aces[0].flags, 0);
+ assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0]));
+ }
+
+ TALLOC_FREE(frame);
+}
+
+static void test_acl_type_dacl_to_nfs4(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ace_dacl_type_mapping); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct security_ace dacl_aces[1];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ init_sec_ace(&dacl_aces[0], &sids[0],
+ ace_dacl_type_mapping[i].dacl_type,
+ SEC_FILE_READ_DATA, 0);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, false, dacl, &params,
+ 101, 102);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 1);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags, 0);
+ assert_int_equal(nfs4_ace->aceType,
+ ace_dacl_type_mapping[i].nfs4_type);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct ace_flag_mapping_nfs4_to_dacl {
+ bool is_directory;
+ uint32_t nfs4_flag;
+ uint32_t dacl_flag;
+} ace_flags_nfs4_to_dacl[] = {
+ { true, SMB_ACE4_FILE_INHERIT_ACE,
+ SEC_ACE_FLAG_OBJECT_INHERIT },
+ { false, SMB_ACE4_FILE_INHERIT_ACE,
+ 0 },
+ { true, SMB_ACE4_DIRECTORY_INHERIT_ACE,
+ SEC_ACE_FLAG_CONTAINER_INHERIT },
+ { false, SMB_ACE4_DIRECTORY_INHERIT_ACE,
+ 0 },
+ { true, SMB_ACE4_NO_PROPAGATE_INHERIT_ACE,
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT },
+ { false, SMB_ACE4_NO_PROPAGATE_INHERIT_ACE,
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT },
+ { true, SMB_ACE4_INHERIT_ONLY_ACE,
+ SEC_ACE_FLAG_INHERIT_ONLY },
+ { false, SMB_ACE4_INHERIT_ONLY_ACE,
+ SEC_ACE_FLAG_INHERIT_ONLY },
+ { true, SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG,
+ 0 },
+ { false, SMB_ACE4_SUCCESSFUL_ACCESS_ACE_FLAG,
+ 0 },
+ { true, SMB_ACE4_FAILED_ACCESS_ACE_FLAG,
+ 0 },
+ { false, SMB_ACE4_FAILED_ACCESS_ACE_FLAG,
+ 0 },
+ { true, SMB_ACE4_INHERITED_ACE,
+ SEC_ACE_FLAG_INHERITED_ACE },
+ { false, SMB_ACE4_INHERITED_ACE,
+ SEC_ACE_FLAG_INHERITED_ACE },
+};
+
+static void test_ace_flags_nfs4_to_dacl(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ SMB_ACE4PROP_T nfs4_ace;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ace_flags_nfs4_to_dacl); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ bool is_directory;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.uid = 1000,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = ace_flags_nfs4_to_dacl[i].nfs4_flag,
+ .aceMask = SMB_ACE4_READ_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ is_directory = ace_flags_nfs4_to_dacl[i].is_directory;
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[2], &sids[3], is_directory,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 1);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type,
+ SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags,
+ ace_flags_nfs4_to_dacl[i].dacl_flag);
+ assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0]));
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct ace_flag_mapping_dacl_to_nfs4 {
+ bool is_directory;
+ uint32_t dacl_flag;
+ uint32_t nfs4_flag;
+} ace_flags_dacl_to_nfs4[] = {
+ { true, SEC_ACE_FLAG_OBJECT_INHERIT,
+ SMB_ACE4_FILE_INHERIT_ACE },
+ { false, SEC_ACE_FLAG_OBJECT_INHERIT,
+ 0 },
+ { true, SEC_ACE_FLAG_CONTAINER_INHERIT,
+ SMB_ACE4_DIRECTORY_INHERIT_ACE },
+ { false, SEC_ACE_FLAG_CONTAINER_INHERIT,
+ 0 },
+ { true, SEC_ACE_FLAG_NO_PROPAGATE_INHERIT,
+ SMB_ACE4_NO_PROPAGATE_INHERIT_ACE },
+ { false, SEC_ACE_FLAG_NO_PROPAGATE_INHERIT,
+ 0 },
+ { true, SEC_ACE_FLAG_INHERIT_ONLY,
+ SMB_ACE4_INHERIT_ONLY_ACE },
+ { false, SEC_ACE_FLAG_INHERIT_ONLY,
+ 0 },
+ { true, SEC_ACE_FLAG_INHERITED_ACE,
+ SMB_ACE4_INHERITED_ACE },
+ { false, SEC_ACE_FLAG_INHERITED_ACE,
+ SMB_ACE4_INHERITED_ACE },
+ { true, SEC_ACE_FLAG_SUCCESSFUL_ACCESS,
+ 0 },
+ { false, SEC_ACE_FLAG_SUCCESSFUL_ACCESS,
+ 0 },
+ { true, SEC_ACE_FLAG_FAILED_ACCESS,
+ 0 },
+ { false, SEC_ACE_FLAG_FAILED_ACCESS,
+ 0 },
+};
+
+static void test_ace_flags_dacl_to_nfs4(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ace_flags_dacl_to_nfs4); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ bool is_directory;
+ struct security_ace dacl_aces[1];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ init_sec_ace(&dacl_aces[0], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ ace_flags_dacl_to_nfs4[i].dacl_flag);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ is_directory = ace_flags_dacl_to_nfs4[i].is_directory;
+ nfs4_acl = smbacl4_win2nfs4(frame, is_directory, dacl, &params,
+ 101, 102);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 1);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags,
+ ace_flags_dacl_to_nfs4[i].nfs4_flag);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct ace_perm_mapping {
+ uint32_t nfs4_perm;
+ uint32_t dacl_perm;
+} perm_table_nfs4_to_dacl[] = {
+ { SMB_ACE4_READ_DATA, SEC_FILE_READ_DATA },
+ { SMB_ACE4_LIST_DIRECTORY, SEC_DIR_LIST },
+ { SMB_ACE4_WRITE_DATA, SEC_FILE_WRITE_DATA },
+ { SMB_ACE4_ADD_FILE, SEC_DIR_ADD_FILE },
+ { SMB_ACE4_APPEND_DATA, SEC_FILE_APPEND_DATA },
+ { SMB_ACE4_ADD_SUBDIRECTORY, SEC_DIR_ADD_SUBDIR, },
+ { SMB_ACE4_READ_NAMED_ATTRS, SEC_FILE_READ_EA },
+ { SMB_ACE4_READ_NAMED_ATTRS, SEC_DIR_READ_EA },
+ { SMB_ACE4_WRITE_NAMED_ATTRS, SEC_FILE_WRITE_EA },
+ { SMB_ACE4_WRITE_NAMED_ATTRS, SEC_DIR_WRITE_EA },
+ { SMB_ACE4_EXECUTE, SEC_FILE_EXECUTE },
+ { SMB_ACE4_EXECUTE, SEC_DIR_TRAVERSE },
+ { SMB_ACE4_DELETE_CHILD, SEC_DIR_DELETE_CHILD },
+ { SMB_ACE4_READ_ATTRIBUTES, SEC_FILE_READ_ATTRIBUTE },
+ { SMB_ACE4_READ_ATTRIBUTES, SEC_DIR_READ_ATTRIBUTE },
+ { SMB_ACE4_WRITE_ATTRIBUTES, SEC_FILE_WRITE_ATTRIBUTE },
+ { SMB_ACE4_WRITE_ATTRIBUTES, SEC_DIR_WRITE_ATTRIBUTE },
+ { SMB_ACE4_DELETE, SEC_STD_DELETE },
+ { SMB_ACE4_READ_ACL, SEC_STD_READ_CONTROL },
+ { SMB_ACE4_WRITE_ACL, SEC_STD_WRITE_DAC, },
+ { SMB_ACE4_WRITE_OWNER, SEC_STD_WRITE_OWNER },
+ { SMB_ACE4_SYNCHRONIZE, SEC_STD_SYNCHRONIZE },
+};
+
+static void test_nfs4_permissions_to_dacl(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(perm_table_nfs4_to_dacl); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ SMB_ACE4PROP_T nfs4_ace;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.uid = 1000,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = perm_table_nfs4_to_dacl[i].nfs4_perm,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[0], &sids[1], false,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 1);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type,
+ SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags, 0);
+ assert_int_equal(dacl_aces[0].access_mask,
+ perm_table_nfs4_to_dacl[i].dacl_perm);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0]));
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct ace_perm_mapping_dacl_to_nfs4 {
+ uint32_t dacl_perm;
+ uint32_t nfs4_perm;
+} perm_table_dacl_to_nfs4[] = {
+ { SEC_FILE_READ_DATA, SMB_ACE4_READ_DATA, },
+ { SEC_DIR_LIST, SMB_ACE4_LIST_DIRECTORY, },
+ { SEC_FILE_WRITE_DATA, SMB_ACE4_WRITE_DATA, },
+ { SEC_DIR_ADD_FILE, SMB_ACE4_ADD_FILE, },
+ { SEC_FILE_APPEND_DATA, SMB_ACE4_APPEND_DATA, },
+ { SEC_DIR_ADD_SUBDIR, SMB_ACE4_ADD_SUBDIRECTORY, },
+ { SEC_FILE_READ_EA, SMB_ACE4_READ_NAMED_ATTRS, },
+ { SEC_DIR_READ_EA, SMB_ACE4_READ_NAMED_ATTRS, },
+ { SEC_FILE_WRITE_EA, SMB_ACE4_WRITE_NAMED_ATTRS, },
+ { SEC_DIR_WRITE_EA, SMB_ACE4_WRITE_NAMED_ATTRS, },
+ { SEC_FILE_EXECUTE, SMB_ACE4_EXECUTE, },
+ { SEC_DIR_TRAVERSE, SMB_ACE4_EXECUTE, },
+ { SEC_DIR_DELETE_CHILD, SMB_ACE4_DELETE_CHILD, },
+ { SEC_FILE_READ_ATTRIBUTE, SMB_ACE4_READ_ATTRIBUTES, },
+ { SEC_DIR_READ_ATTRIBUTE, SMB_ACE4_READ_ATTRIBUTES, },
+ { SEC_FILE_WRITE_ATTRIBUTE, SMB_ACE4_WRITE_ATTRIBUTES, },
+ { SEC_DIR_WRITE_ATTRIBUTE, SMB_ACE4_WRITE_ATTRIBUTES, },
+ { SEC_STD_DELETE, SMB_ACE4_DELETE, },
+ { SEC_STD_READ_CONTROL, SMB_ACE4_READ_ACL, },
+ { SEC_STD_WRITE_DAC, SMB_ACE4_WRITE_ACL, },
+ { SEC_STD_WRITE_OWNER, SMB_ACE4_WRITE_OWNER, },
+ { SEC_STD_SYNCHRONIZE, SMB_ACE4_SYNCHRONIZE, },
+ { SEC_GENERIC_READ, SMB_ACE4_READ_ACL|
+ SMB_ACE4_READ_DATA|
+ SMB_ACE4_READ_ATTRIBUTES|
+ SMB_ACE4_READ_NAMED_ATTRS|
+ SMB_ACE4_SYNCHRONIZE },
+ { SEC_GENERIC_WRITE, SMB_ACE4_WRITE_ACL|
+ SMB_ACE4_WRITE_DATA|
+ SMB_ACE4_WRITE_ATTRIBUTES|
+ SMB_ACE4_WRITE_NAMED_ATTRS|
+ SMB_ACE4_SYNCHRONIZE },
+ { SEC_GENERIC_EXECUTE, SMB_ACE4_READ_ACL|
+ SMB_ACE4_READ_ATTRIBUTES|
+ SMB_ACE4_EXECUTE|
+ SMB_ACE4_SYNCHRONIZE },
+ { SEC_GENERIC_ALL, SMB_ACE4_DELETE|
+ SMB_ACE4_READ_ACL|
+ SMB_ACE4_WRITE_ACL|
+ SMB_ACE4_WRITE_OWNER|
+ SMB_ACE4_SYNCHRONIZE|
+ SMB_ACE4_WRITE_ATTRIBUTES|
+ SMB_ACE4_READ_ATTRIBUTES|
+ SMB_ACE4_EXECUTE|
+ SMB_ACE4_READ_NAMED_ATTRS|
+ SMB_ACE4_WRITE_NAMED_ATTRS|
+ SMB_ACE4_WRITE_DATA|
+ SMB_ACE4_APPEND_DATA|
+ SMB_ACE4_READ_DATA|
+ SMB_ACE4_DELETE_CHILD },
+};
+
+static void test_dacl_permissions_to_nfs4(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(perm_table_nfs4_to_dacl); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+ struct security_ace dacl_aces[1];
+ struct security_acl *dacl;
+
+ init_sec_ace(&dacl_aces[0], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ perm_table_dacl_to_nfs4[i].dacl_perm, 0);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, false, dacl, &params,
+ 101, 102);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 1);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags, 0);
+ assert_int_equal(nfs4_ace->aceMask,
+ perm_table_dacl_to_nfs4[i].nfs4_perm);
+ }
+
+ TALLOC_FREE(frame);
+}
+
+/*
+ * Create NFS4 ACL with all possible "special" entries. Verify that
+ * the ones that should be mapped to a DACL are mapped and the other
+ * ones are ignored.
+ */
+static void test_special_nfs4_to_dacl(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct SMB4ACL_T *nfs4_acl;
+ SMB_ACE4PROP_T nfs4_ace;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_OWNER,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_READ_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_GROUP,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_WRITE_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_EVERYONE,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_APPEND_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_INTERACTIVE,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_READ_NAMED_ATTRS,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_NETWORK,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_WRITE_NAMED_ATTRS,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_DIALUP,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_EXECUTE,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_BATCH,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_READ_ATTRIBUTES,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_ANONYMOUS,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_WRITE_ATTRIBUTES,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_AUTHENTICATED,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_READ_ACL,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_SERVICE,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = SMB_ACE4_WRITE_ACL,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[0], &sids[1], false,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 3);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type, SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags, 0);
+ assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0]));
+
+ assert_int_equal(dacl_aces[1].type, SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[1].flags, 0);
+ assert_int_equal(dacl_aces[1].access_mask, SEC_FILE_WRITE_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[1].trustee, &sids[1]));
+
+ assert_int_equal(dacl_aces[2].type, SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[2].flags, 0);
+ assert_int_equal(dacl_aces[2].access_mask, SEC_FILE_APPEND_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[2].trustee, &global_sid_World));
+
+ TALLOC_FREE(frame);
+}
+
+static void test_dacl_to_special_nfs4(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct security_ace dacl_aces[6];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_dontcare,
+ .map_full_control = true,
+ };
+
+ /*
+ * global_Sid_World is mapped to EVERYONE.
+ */
+ init_sec_ace(&dacl_aces[0], &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA, 0);
+ /*
+ * global_sid_Unix_NFS is ignored.
+ */
+ init_sec_ace(&dacl_aces[1], &global_sid_Unix_NFS,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA, 0);
+ /*
+ * Anything that maps to owner or owning group with inheritance flags
+ * is NOT mapped to special owner or special group.
+ */
+ init_sec_ace(&dacl_aces[2], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ init_sec_ace(&dacl_aces[3], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY);
+ init_sec_ace(&dacl_aces[4], &sids[1],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ init_sec_ace(&dacl_aces[5], &sids[1],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, &params, 1000, 1001);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 5);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL);
+ assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_EVERYONE);
+ assert_int_equal(nfs4_ace->aceFlags, 0);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_WRITE_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->who.gid, 1001);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE);
+ assert_int_equal(nfs4_ace->who.gid, 1001);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ TALLOC_FREE(frame);
+}
+
+struct creator_ace_flags {
+ uint32_t dacl_flags;
+ uint32_t nfs4_flags;
+} creator_ace_flags[] = {
+ { 0, 0 },
+
+ { SEC_ACE_FLAG_INHERIT_ONLY, 0 },
+
+ { SEC_ACE_FLAG_CONTAINER_INHERIT, SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE },
+
+ { SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY, SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE },
+
+ { SEC_ACE_FLAG_OBJECT_INHERIT, SMB_ACE4_FILE_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE },
+ { SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY, SMB_ACE4_FILE_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE },
+
+ { SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_OBJECT_INHERIT, SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_FILE_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE },
+
+ { SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY, SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_FILE_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE },
+};
+
+static void test_dacl_creator_to_nfs4(void **state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(creator_ace_flags); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct security_ace dacl_aces[2];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ init_sec_ace(&dacl_aces[0], &global_sid_Creator_Owner,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ creator_ace_flags[i].dacl_flags);
+ init_sec_ace(&dacl_aces[1], &global_sid_Creator_Group,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ creator_ace_flags[i].dacl_flags);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, &params,
+ 101, 102);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+
+ if (creator_ace_flags[i].nfs4_flags == 0) {
+ /*
+ * CREATOR OWNER and CREATOR GROUP not mapped
+ * in this case.
+ */
+ assert_null(smb_first_ace4(nfs4_acl));
+ } else {
+ assert_int_equal(smb_get_naces(nfs4_acl), 2);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace);
+ assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL);
+ assert_int_equal(nfs4_ace->who.special_id,
+ SMB_ACE4_WHO_OWNER);
+ assert_int_equal(nfs4_ace->aceFlags,
+ creator_ace_flags[i].nfs4_flags);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace);
+ assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL);
+ assert_int_equal(nfs4_ace->who.special_id,
+ SMB_ACE4_WHO_GROUP);
+ assert_int_equal(nfs4_ace->aceFlags,
+ creator_ace_flags[i].nfs4_flags);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+ }
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct creator_owner_nfs4_to_dacl {
+ uint32_t special_id;
+ uint32_t nfs4_ace_flags;
+ uint32_t dacl_ace_flags;
+} creator_owner_nfs4_to_dacl[] = {
+ { SMB_ACE4_WHO_OWNER,
+ SMB_ACE4_FILE_INHERIT_ACE,
+ SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY },
+ { SMB_ACE4_WHO_OWNER,
+ SMB_ACE4_DIRECTORY_INHERIT_ACE,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY },
+ { SMB_ACE4_WHO_OWNER,
+ SMB_ACE4_FILE_INHERIT_ACE|SMB_ACE4_DIRECTORY_INHERIT_ACE,
+ SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY },
+ { SMB_ACE4_WHO_GROUP,
+ SMB_ACE4_FILE_INHERIT_ACE,
+ SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY },
+ { SMB_ACE4_WHO_GROUP,
+ SMB_ACE4_DIRECTORY_INHERIT_ACE,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY },
+ { SMB_ACE4_WHO_GROUP,
+ SMB_ACE4_FILE_INHERIT_ACE|SMB_ACE4_DIRECTORY_INHERIT_ACE,
+ SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY },
+};
+
+static void test_nfs4_to_dacl_creator(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(creator_owner_nfs4_to_dacl); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ SMB_ACE4PROP_T nfs4_ace;
+ struct security_ace *dacl_aces, *creator_dacl_ace;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id
+ = creator_owner_nfs4_to_dacl[i].special_id,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags
+ = creator_owner_nfs4_to_dacl[i].nfs4_ace_flags,
+ .aceMask = SMB_ACE4_READ_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[0], &sids[1], true,
+ &dacl_aces, &good_aces));
+ assert_non_null(dacl_aces);
+
+ if (creator_owner_nfs4_to_dacl[i].nfs4_ace_flags &
+ SMB_ACE4_INHERIT_ONLY_ACE) {
+ /*
+ * Only one ACE entry for the CREATOR ACE entry.
+ */
+ assert_int_equal(good_aces, 1);
+ creator_dacl_ace = &dacl_aces[0];
+ } else {
+ /*
+ * This creates an additional ACE entry for
+ * the permissions on the current object.
+ */
+ assert_int_equal(good_aces, 2);
+
+ assert_int_equal(dacl_aces[0].type,
+ SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags, 0);
+ assert_int_equal(dacl_aces[0].access_mask,
+ SEC_FILE_READ_DATA);
+
+ if (creator_owner_nfs4_to_dacl[i].special_id ==
+ SMB_ACE4_WHO_OWNER) {
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee,
+ &sids[0]));
+ }
+
+ if (creator_owner_nfs4_to_dacl[i].special_id ==
+ SMB_ACE4_WHO_GROUP) {
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee,
+ &sids[1]));
+ }
+
+ creator_dacl_ace = &dacl_aces[1];
+ }
+
+ assert_int_equal(creator_dacl_ace->type,
+ SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(creator_dacl_ace->flags,
+ creator_owner_nfs4_to_dacl[i].dacl_ace_flags);
+ assert_int_equal(creator_dacl_ace->access_mask,
+ SEC_FILE_READ_DATA);
+ if (creator_owner_nfs4_to_dacl[i].special_id ==
+ SMB_ACE4_WHO_OWNER) {
+ assert_true(dom_sid_equal(&creator_dacl_ace->trustee,
+ &global_sid_Creator_Owner));
+ }
+
+ if (creator_owner_nfs4_to_dacl[i].special_id ==
+ SMB_ACE4_WHO_GROUP) {
+ assert_true(dom_sid_equal(&creator_dacl_ace->trustee,
+ &global_sid_Creator_Group));
+ }
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct nfs4_to_dacl_map_full_control{
+ bool is_dir;
+ bool config;
+ bool delete_child_added;
+} nfs4_to_dacl_full_control[] = {
+ { true, true, false },
+ { true, false, false },
+ { false, true, true },
+ { false, false, false },
+};
+
+static void test_full_control_nfs4_to_dacl(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(nfs4_to_dacl_full_control); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ SMB_ACE4PROP_T nfs4_ace;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = nfs4_to_dacl_full_control[i].config,
+ };
+ const uint32_t nfs4_ace_mask_except_deletes =
+ SMB_ACE4_READ_DATA|SMB_ACE4_WRITE_DATA|
+ SMB_ACE4_APPEND_DATA|SMB_ACE4_READ_NAMED_ATTRS|
+ SMB_ACE4_WRITE_NAMED_ATTRS|SMB_ACE4_EXECUTE|
+ SMB_ACE4_READ_ATTRIBUTES|SMB_ACE4_WRITE_ATTRIBUTES|
+ SMB_ACE4_READ_ACL|SMB_ACE4_WRITE_ACL|
+ SMB_ACE4_WRITE_OWNER|SMB_ACE4_SYNCHRONIZE;
+ const uint32_t dacl_ace_mask_except_deletes =
+ SEC_FILE_READ_DATA|SEC_FILE_WRITE_DATA|
+ SEC_FILE_APPEND_DATA|SEC_FILE_READ_EA|
+ SEC_FILE_WRITE_EA|SEC_FILE_EXECUTE|
+ SEC_FILE_READ_ATTRIBUTE|SEC_FILE_WRITE_ATTRIBUTE|
+ SEC_STD_READ_CONTROL|SEC_STD_WRITE_DAC|
+ SEC_STD_WRITE_OWNER|SEC_STD_SYNCHRONIZE;
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.uid = 1000,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = 0,
+ .aceMask = nfs4_ace_mask_except_deletes,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(
+ smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[0], &sids[1],
+ nfs4_to_dacl_full_control[i].is_dir,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 1);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type,
+ SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags, 0);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0]));
+ if (nfs4_to_dacl_full_control[i].delete_child_added) {
+ assert_int_equal(dacl_aces[0].access_mask,
+ dacl_ace_mask_except_deletes|
+ SEC_DIR_DELETE_CHILD);
+ } else {
+ assert_int_equal(dacl_aces[0].access_mask,
+ dacl_ace_mask_except_deletes);
+ }
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct acedup_settings {
+ enum smbacl4_acedup_enum setting;
+} acedup_settings[] = {
+ { e_dontcare },
+ { e_reject },
+ { e_ignore },
+ { e_merge },
+};
+
+static void test_dacl_to_nfs4_acedup_settings(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(acedup_settings); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct security_ace dacl_aces[2];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = acedup_settings[i].setting,
+ .map_full_control = true,
+ };
+
+ init_sec_ace(&dacl_aces[0], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ init_sec_ace(&dacl_aces[1], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, &params,
+ 101, 102);
+
+ switch(params.acedup) {
+ case e_dontcare:
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 2);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags,
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags,
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask,
+ SMB_ACE4_WRITE_DATA);
+ break;
+
+ case e_reject:
+ assert_null(nfs4_acl);
+ assert_int_equal(errno, EINVAL);
+ break;
+
+ case e_ignore:
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 1);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags,
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+ break;
+
+ case e_merge:
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 1);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags,
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask,
+ SMB_ACE4_READ_DATA|
+ SMB_ACE4_WRITE_DATA);
+ break;
+
+ default:
+ fail_msg("Unexpected value for acedup: %d\n",
+ params.acedup);
+ };
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct acedup_match {
+ int sid_idx1;
+ enum security_ace_type type1;
+ uint32_t ace_mask1;
+ uint8_t flag1;
+ int sid_idx2;
+ enum security_ace_type type2;
+ uint32_t ace_mask2;
+ uint8_t flag2;
+ bool match;
+} acedup_match[] = {
+ { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ true },
+ { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ 1, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ false },
+ { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ 0, SEC_ACE_TYPE_ACCESS_DENIED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ false },
+ { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ true },
+ { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_CONTAINER_INHERIT,
+ false },
+ { 0, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ 5, SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT,
+ false },
+};
+
+static void test_dacl_to_nfs4_acedup_match(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(acedup_match); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct security_ace dacl_aces[2];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_ignore,
+ .map_full_control = true,
+ };
+
+ init_sec_ace(&dacl_aces[0],
+ &sids[acedup_match[i].sid_idx1],
+ acedup_match[i].type1,
+ acedup_match[i].ace_mask1,
+ acedup_match[i].flag1);
+ init_sec_ace(&dacl_aces[1],
+ &sids[acedup_match[i].sid_idx2],
+ acedup_match[i].type2,
+ acedup_match[i].ace_mask2,
+ acedup_match[i].flag2);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, &params,
+ 101, 102);
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+
+ if (acedup_match[i].match) {
+ assert_int_equal(smb_get_naces(nfs4_acl), 1);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags,
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ } else {
+ assert_int_equal(smb_get_naces(nfs4_acl), 2);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->who.uid, 1000);
+ assert_int_equal(nfs4_ace->aceFlags,
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ }
+ }
+
+ TALLOC_FREE(frame);
+}
+
+static void test_dacl_to_nfs4_config_special(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct security_ace dacl_aces[6];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_special,
+ .do_chown = true,
+ .acedup = e_dontcare,
+ .map_full_control = true,
+ };
+
+ /*
+ * global_sid_Creator_Owner or global_sid_Special_Group is NOT mapped
+ * to SMB_ACE4_ID_SPECIAL.
+ */
+ init_sec_ace(&dacl_aces[0], &global_sid_Creator_Owner,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ init_sec_ace(&dacl_aces[1], &global_sid_Creator_Group,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_WRITE_DATA,
+ SEC_ACE_FLAG_CONTAINER_INHERIT);
+ /*
+ * Anything that maps to owner or owning group with inheritance flags
+ * IS mapped to special owner or special group.
+ */
+ init_sec_ace(&dacl_aces[2], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ init_sec_ace(&dacl_aces[3], &sids[0],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY);
+ init_sec_ace(&dacl_aces[4], &sids[1],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_OBJECT_INHERIT);
+ init_sec_ace(&dacl_aces[5], &sids[1],
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SEC_FILE_READ_DATA,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, &params, 1000, 1001);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl), 6);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->who.uid, 1003);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, 0);
+ assert_int_equal(nfs4_ace->aceFlags,
+ SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_DIRECTORY_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->who.gid, 1004);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_WRITE_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL);
+ assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_OWNER);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE);
+ assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_OWNER);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_FILE_INHERIT_ACE);
+ assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_GROUP);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags, SMB_ACE4_ID_SPECIAL);
+ assert_int_equal(nfs4_ace->aceFlags, SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_DIRECTORY_INHERIT_ACE|
+ SMB_ACE4_INHERIT_ONLY_ACE);
+ assert_int_equal(nfs4_ace->who.special_id, SMB_ACE4_WHO_GROUP);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ assert_null(smb_next_ace4(nfs4_ace_container));
+
+ TALLOC_FREE(frame);
+}
+
+static void test_nfs4_to_dacl_config_special(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct SMB4ACL_T *nfs4_acl;
+ SMB_ACE4PROP_T nfs4_ace;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_special,
+ .do_chown = true,
+ .acedup = e_dontcare,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ /*
+ * In config mode special, this is not mapped to Creator Owner
+ */
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_OWNER,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = SMB_ACE4_FILE_INHERIT_ACE,
+ .aceMask = SMB_ACE4_READ_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ /*
+ * In config mode special, this is not mapped to Creator Group
+ */
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = SMB_ACE4_ID_SPECIAL,
+ .who.special_id = SMB_ACE4_WHO_GROUP,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = SMB_ACE4_DIRECTORY_INHERIT_ACE,
+ .aceMask = SMB_ACE4_WRITE_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[0], &sids[1], true,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 2);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type, SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags, SEC_ACE_FLAG_OBJECT_INHERIT);
+ assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[0]));
+
+ assert_int_equal(dacl_aces[1].type, SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[1].flags, SEC_ACE_FLAG_CONTAINER_INHERIT);
+ assert_int_equal(dacl_aces[1].access_mask, SEC_FILE_WRITE_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[1].trustee, &sids[1]));
+
+ TALLOC_FREE(frame);
+}
+
+struct nfs_to_dacl_idmap_both {
+ uint32_t nfs4_flags;
+ uint32_t nfs4_id;
+ struct dom_sid *sid;
+};
+
+static void test_nfs4_to_dacl_idmap_type_both(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+ struct nfs_to_dacl_idmap_both nfs_to_dacl_idmap_both[] = {
+ { 0, 1002, &sids[2] },
+ { SMB_ACE4_IDENTIFIER_GROUP, 1002, &sids[2] },
+ { 0, 1005, &sids[6] },
+ { SMB_ACE4_IDENTIFIER_GROUP, 1005, &sids[6] },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(nfs_to_dacl_idmap_both); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct security_ace *dacl_aces;
+ SMB_ACE4PROP_T nfs4_ace;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = nfs_to_dacl_idmap_both[i].nfs4_flags,
+ .aceMask = SMB_ACE4_READ_DATA,
+ };
+
+ if (nfs_to_dacl_idmap_both[i].nfs4_flags &
+ SMB_ACE4_IDENTIFIER_GROUP) {
+ nfs4_ace.who.gid = nfs_to_dacl_idmap_both[i].nfs4_id;
+ } else {
+ nfs4_ace.who.uid = nfs_to_dacl_idmap_both[i].nfs4_id;
+ }
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[2], &sids[2],
+ false, &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 1);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type,
+ SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags, 0);
+ assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_READ_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee,
+ nfs_to_dacl_idmap_both[i].sid));
+ }
+
+ TALLOC_FREE(frame);
+}
+
+struct dacl_to_nfs4_idmap_both {
+ struct dom_sid *sid;
+ uint32_t dacl_flags;
+ uint32_t nfs4_flags;
+ uint32_t nfs4_ace_flags;
+ uint32_t nfs4_id;
+ int num_nfs4_aces;
+};
+
+/*
+ * IDMAP_TYPE_BOTH always creates group entries.
+ */
+static void test_dacl_to_nfs4_idmap_type_both(void **state)
+{
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int i;
+
+ struct dacl_to_nfs4_idmap_both dacl_to_nfs4_idmap_both[] = {
+ { &sids[2], 0,
+ SMB_ACE4_ID_SPECIAL, SMB_ACE4_IDENTIFIER_GROUP, SMB_ACE4_WHO_GROUP,
+ 2 },
+ { &sids[2], SEC_ACE_FLAG_OBJECT_INHERIT,
+ 0, SMB_ACE4_IDENTIFIER_GROUP|SMB_ACE4_FILE_INHERIT_ACE, 1002,
+ 1 },
+ { &sids[6], 0,
+ 0, SMB_ACE4_IDENTIFIER_GROUP, 1005,
+ 1 },
+ { &sids[6], SEC_ACE_FLAG_OBJECT_INHERIT,
+ 0, SMB_ACE4_IDENTIFIER_GROUP|SMB_ACE4_FILE_INHERIT_ACE, 1005,
+ 1 },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(dacl_to_nfs4_idmap_both); i++) {
+ struct SMB4ACL_T *nfs4_acl;
+ struct SMB4ACE_T *nfs4_ace_container;
+ SMB_ACE4PROP_T *nfs4_ace;
+ struct security_ace dacl_aces[1];
+ struct security_acl *dacl;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_merge,
+ .map_full_control = true,
+ };
+
+ init_sec_ace(&dacl_aces[0], dacl_to_nfs4_idmap_both[i].sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ SEC_FILE_READ_DATA,
+ dacl_to_nfs4_idmap_both[i].dacl_flags);
+ dacl = make_sec_acl(frame, SECURITY_ACL_REVISION_ADS,
+ ARRAY_SIZE(dacl_aces), dacl_aces);
+ assert_non_null(dacl);
+
+ nfs4_acl = smbacl4_win2nfs4(frame, true, dacl, &params,
+ 1002, 1002);
+
+ assert_non_null(nfs4_acl);
+ assert_int_equal(smbacl4_get_controlflags(nfs4_acl),
+ SEC_DESC_SELF_RELATIVE);
+ assert_int_equal(smb_get_naces(nfs4_acl),
+ dacl_to_nfs4_idmap_both[i].num_nfs4_aces);
+
+ nfs4_ace_container = smb_first_ace4(nfs4_acl);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags,
+ dacl_to_nfs4_idmap_both[i].nfs4_flags);
+ assert_int_equal(nfs4_ace->aceFlags,
+ dacl_to_nfs4_idmap_both[i].nfs4_ace_flags);
+ if (nfs4_ace->flags & SMB_ACE4_ID_SPECIAL) {
+ assert_int_equal(nfs4_ace->who.special_id,
+ dacl_to_nfs4_idmap_both[i].nfs4_id);
+ } else if (nfs4_ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) {
+ assert_int_equal(nfs4_ace->who.gid,
+ dacl_to_nfs4_idmap_both[i].nfs4_id);
+ } else {
+ assert_int_equal(nfs4_ace->who.uid,
+ dacl_to_nfs4_idmap_both[i].nfs4_id);
+ }
+ assert_int_equal(nfs4_ace->aceType,
+ SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+
+ if (dacl_to_nfs4_idmap_both[i].num_nfs4_aces == 2) {
+ nfs4_ace_container = smb_next_ace4(nfs4_ace_container);
+ assert_non_null(nfs4_ace_container);
+
+ nfs4_ace = smb_get_ace4(nfs4_ace_container);
+ assert_int_equal(nfs4_ace->flags,
+ dacl_to_nfs4_idmap_both[i].nfs4_flags);
+ assert_int_equal(nfs4_ace->aceFlags,
+ dacl_to_nfs4_idmap_both[i].nfs4_ace_flags &
+ ~SMB_ACE4_IDENTIFIER_GROUP);
+ if (nfs4_ace->flags & SMB_ACE4_ID_SPECIAL) {
+ assert_int_equal(nfs4_ace->who.special_id,
+ SMB_ACE4_WHO_OWNER);
+ } else {
+ assert_int_equal(nfs4_ace->who.uid,
+ dacl_to_nfs4_idmap_both[i].nfs4_id);
+ }
+ assert_int_equal(nfs4_ace->aceType,
+ SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE);
+ assert_int_equal(nfs4_ace->aceMask, SMB_ACE4_READ_DATA);
+ }
+ }
+
+ TALLOC_FREE(frame);
+}
+
+static void test_nfs4_to_dacl_remove_duplicate(void **state)
+{
+
+ struct dom_sid *sids = *state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct SMB4ACL_T *nfs4_acl;
+ SMB_ACE4PROP_T nfs4_ace;
+ struct security_ace *dacl_aces;
+ int good_aces;
+ struct smbacl4_vfs_params params = {
+ .mode = e_simple,
+ .do_chown = true,
+ .acedup = e_dontcare,
+ .map_full_control = true,
+ };
+
+ nfs4_acl = smb_create_smb4acl(frame);
+ assert_non_null(nfs4_acl);
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.uid = 1002,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = SMB_ACE4_INHERITED_ACE,
+ .aceMask = SMB_ACE4_WRITE_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.gid = 1002,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_INHERITED_ACE,
+ .aceMask = SMB_ACE4_WRITE_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.gid = 1002,
+ .aceType = SMB_ACE4_ACCESS_DENIED_ACE_TYPE,
+ .aceFlags = SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_INHERITED_ACE,
+ .aceMask = SMB_ACE4_WRITE_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ nfs4_ace = (SMB_ACE4PROP_T) {
+ .flags = 0,
+ .who.gid = 1002,
+ .aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE,
+ .aceFlags = SMB_ACE4_IDENTIFIER_GROUP|
+ SMB_ACE4_INHERITED_ACE,
+ .aceMask = SMB_ACE4_WRITE_DATA,
+ };
+ assert_non_null(smb_add_ace4(nfs4_acl, &nfs4_ace));
+
+ assert_true(smbacl4_nfs42win(frame, &params, nfs4_acl,
+ &sids[0], &sids[1], true,
+ &dacl_aces, &good_aces));
+
+ assert_int_equal(good_aces, 2);
+ assert_non_null(dacl_aces);
+
+ assert_int_equal(dacl_aces[0].type, SEC_ACE_TYPE_ACCESS_ALLOWED);
+ assert_int_equal(dacl_aces[0].flags, SEC_ACE_FLAG_INHERITED_ACE);
+ assert_int_equal(dacl_aces[0].access_mask, SEC_FILE_WRITE_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[0].trustee, &sids[2]));
+
+ assert_int_equal(dacl_aces[1].type, SEC_ACE_TYPE_ACCESS_DENIED);
+ assert_int_equal(dacl_aces[1].flags, SEC_ACE_FLAG_INHERITED_ACE);
+ assert_int_equal(dacl_aces[1].access_mask, SEC_FILE_WRITE_DATA);
+ assert_true(dom_sid_equal(&dacl_aces[1].trustee, &sids[2]));
+
+ TALLOC_FREE(frame);
+}
+
+int main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_cached_id_mappings),
+ cmocka_unit_test(test_empty_nfs4_to_dacl),
+ cmocka_unit_test(test_empty_dacl_to_nfs4),
+ cmocka_unit_test(test_acl_type_nfs4_to_dacl),
+ cmocka_unit_test(test_acl_type_dacl_to_nfs4),
+ cmocka_unit_test(test_ace_flags_nfs4_to_dacl),
+ cmocka_unit_test(test_ace_flags_dacl_to_nfs4),
+ cmocka_unit_test(test_nfs4_permissions_to_dacl),
+ cmocka_unit_test(test_dacl_permissions_to_nfs4),
+ cmocka_unit_test(test_special_nfs4_to_dacl),
+ cmocka_unit_test(test_dacl_to_special_nfs4),
+ cmocka_unit_test(test_dacl_creator_to_nfs4),
+ cmocka_unit_test(test_nfs4_to_dacl_creator),
+ cmocka_unit_test(test_full_control_nfs4_to_dacl),
+ cmocka_unit_test(test_dacl_to_nfs4_acedup_settings),
+ cmocka_unit_test(test_dacl_to_nfs4_acedup_match),
+ cmocka_unit_test(test_dacl_to_nfs4_config_special),
+ cmocka_unit_test(test_nfs4_to_dacl_config_special),
+ cmocka_unit_test(test_nfs4_to_dacl_idmap_type_both),
+ cmocka_unit_test(test_dacl_to_nfs4_idmap_type_both),
+ cmocka_unit_test(test_nfs4_to_dacl_remove_duplicate),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ if (argc != 2) {
+ print_error("Usage: %s smb.conf\n", argv[0]);
+ exit(1);
+ }
+
+ /*
+ * Initialize enough of the Samba internals to have the
+ * mappings tests work.
+ */
+ talloc_stackframe();
+ lp_load_global(argv[1]);
+
+ return cmocka_run_group_tests(tests, group_setup, group_teardown);
+}
diff --git a/source3/modules/test_vfs_full_audit.c b/source3/modules/test_vfs_full_audit.c
new file mode 100644
index 0000000..4a12e46
--- /dev/null
+++ b/source3/modules/test_vfs_full_audit.c
@@ -0,0 +1,49 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Unit test for entries in vfs_full_audit arrays.
+ *
+ * Copyright (C) Jeremy Allison 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Needed for static build to complete... */
+#include "includes.h"
+#include "smbd/smbd.h"
+NTSTATUS vfs_full_audit_init(TALLOC_CTX *ctx);
+
+#include "vfs_full_audit.c"
+#include <cmocka.h>
+
+static void test_full_audit_array(void **state)
+{
+ unsigned i;
+
+ for (i=0; i<SMB_VFS_OP_LAST; i++) {
+ assert_non_null(vfs_op_names[i].name);
+ assert_int_equal(vfs_op_names[i].type, i);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_full_audit_array),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source3/modules/test_vfs_gpfs.c b/source3/modules/test_vfs_gpfs.c
new file mode 100644
index 0000000..44454f8
--- /dev/null
+++ b/source3/modules/test_vfs_gpfs.c
@@ -0,0 +1,101 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Unit test for vfs_gpfs module.
+ *
+ * Copyright (C) Christof Schmitt 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_gpfs.c"
+#include <cmocka.h>
+
+static void test_share_deny_mapping(void **state)
+{
+ assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_NONE),
+ GPFS_DENY_READ|GPFS_DENY_WRITE|GPFS_DENY_DELETE);
+ assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_READ),
+ GPFS_DENY_WRITE|GPFS_DENY_DELETE);
+ assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_WRITE),
+ GPFS_DENY_READ|GPFS_DENY_DELETE);
+ assert_int_equal(vfs_gpfs_share_access_to_deny(FILE_SHARE_DELETE),
+ GPFS_DENY_READ|GPFS_DENY_WRITE);
+ assert_int_equal(vfs_gpfs_share_access_to_deny(
+ FILE_SHARE_READ|FILE_SHARE_DELETE),
+ GPFS_DENY_WRITE);
+ assert_int_equal(vfs_gpfs_share_access_to_deny(
+ FILE_SHARE_WRITE|FILE_SHARE_DELETE),
+ GPFS_DENY_READ);
+ assert_int_equal(vfs_gpfs_share_access_to_deny(
+ FILE_SHARE_READ|FILE_SHARE_WRITE),
+ 0); /* GPFS limitation, cannot deny only delete. */
+}
+
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+static void test_gpfs_lease_mapping(void **state)
+{
+ assert_int_equal(lease_type_to_gpfs(F_RDLCK), GPFS_LEASE_READ);
+ assert_int_equal(lease_type_to_gpfs(F_WRLCK), GPFS_LEASE_WRITE);
+ assert_int_equal(lease_type_to_gpfs(F_UNLCK), GPFS_LEASE_NONE);
+}
+#endif /* #ifdef HAVE_KERNEL_OPLOCKS_LINUX */
+
+static void test_gpfs_winattrs_to_dosmode(void **state)
+{
+ assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_ARCHIVE),
+ FILE_ATTRIBUTE_ARCHIVE);
+ assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_READONLY),
+ FILE_ATTRIBUTE_READONLY);
+ assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_HIDDEN),
+ FILE_ATTRIBUTE_HIDDEN);
+ assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_OFFLINE),
+ FILE_ATTRIBUTE_OFFLINE);
+ assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_SPARSE_FILE),
+ FILE_ATTRIBUTE_SPARSE);
+ assert_int_equal(vfs_gpfs_winattrs_to_dosmode(GPFS_WINATTR_SYSTEM),
+ FILE_ATTRIBUTE_SYSTEM);
+}
+
+static void test_dosmode_to_gpfs_winattrs(void **state)
+{
+ assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_ARCHIVE),
+ GPFS_WINATTR_ARCHIVE);
+ assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_HIDDEN),
+ GPFS_WINATTR_HIDDEN);
+ assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_OFFLINE),
+ GPFS_WINATTR_OFFLINE);
+ assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_READONLY),
+ GPFS_WINATTR_READONLY);
+ assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_SPARSE),
+ GPFS_WINATTR_SPARSE_FILE);
+ assert_int_equal(vfs_gpfs_dosmode_to_winattrs(FILE_ATTRIBUTE_SYSTEM),
+ GPFS_WINATTR_SYSTEM);
+}
+
+int main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_share_deny_mapping),
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+ cmocka_unit_test(test_gpfs_lease_mapping),
+#endif /* #ifdef HAVE_KERNEL_OPLOCKS_LINUX */
+ cmocka_unit_test(test_gpfs_winattrs_to_dosmode),
+ cmocka_unit_test(test_dosmode_to_gpfs_winattrs),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source3/modules/test_vfs_posixacl.c b/source3/modules/test_vfs_posixacl.c
new file mode 100644
index 0000000..19e7d98
--- /dev/null
+++ b/source3/modules/test_vfs_posixacl.c
@@ -0,0 +1,171 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Unit test for vfs_posixacl
+ *
+ * Copyright (C) Christof Schmitt 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_posixacl.c"
+#include <cmocka.h>
+
+static void smb_acl_add_entry(struct smb_acl_t * smb_acl,
+ SMB_ACL_TAG_T tag, uint32_t id,
+ bool read, bool write, bool execute)
+{
+ int ret;
+ struct smb_acl_entry *smb_acl_entry = NULL;
+ SMB_ACL_PERMSET_T smb_permset = NULL;
+
+ ret = sys_acl_create_entry(&smb_acl, &smb_acl_entry);
+ assert_int_equal(ret, 0);
+
+ ret = sys_acl_set_tag_type(smb_acl_entry, tag);
+ assert_int_equal(ret, 0);
+
+ if (tag == SMB_ACL_USER || tag == SMB_ACL_GROUP) {
+ ret = sys_acl_set_qualifier(smb_acl_entry, &id);
+ assert_int_equal(ret, 0);
+ }
+
+ ret = sys_acl_get_permset(smb_acl_entry, &smb_permset);
+ assert_int_equal(ret, 0);
+
+ if (read) {
+ ret = sys_acl_add_perm(smb_permset, SMB_ACL_READ);
+ assert_int_equal(ret, 0);
+ }
+
+ if (write) {
+ ret = sys_acl_add_perm(smb_permset, SMB_ACL_WRITE);
+ assert_int_equal(ret, 0);
+ }
+
+ if (execute) {
+ ret = sys_acl_add_perm(smb_permset, SMB_ACL_EXECUTE);
+ assert_int_equal(ret, 0);
+ }
+
+ ret = sys_acl_set_permset(smb_acl_entry, smb_permset);
+ assert_int_equal(ret, 0);
+}
+
+static void acl_check_entry(acl_entry_t acl_entry, SMB_ACL_TAG_T tag,
+ uint32_t id,
+ bool read, bool write, bool execute)
+{
+ int ret;
+ acl_permset_t acl_permset = NULL;
+ acl_tag_t acl_tag;
+
+ ret = acl_get_permset(acl_entry, &acl_permset);
+ assert_int_equal(ret, 0);
+
+ ret = acl_get_tag_type(acl_entry, &acl_tag);
+ assert_int_equal(ret, 0);
+ assert_int_equal(acl_tag, tag);
+
+ if (tag == ACL_USER || tag == ACL_GROUP) {
+ uint32_t *id_p;
+
+ id_p = acl_get_qualifier(acl_entry);
+ assert_non_null(id_p);
+ assert_int_equal(*id_p, id);
+ }
+
+#ifdef HAVE_ACL_GET_PERM_NP
+ ret = acl_get_perm_np(acl_permset, ACL_READ);
+#else
+ ret = acl_get_perm(acl_permset, ACL_READ);
+#endif
+ assert_int_equal(ret, read ? 1 : 0);
+
+#ifdef HAVE_ACL_GET_PERM_NP
+ ret = acl_get_perm_np(acl_permset, ACL_WRITE);
+#else
+ ret = acl_get_perm(acl_permset, ACL_WRITE);
+#endif
+ assert_int_equal(ret, write ? 1 : 0);
+
+#ifdef HAVE_ACL_GET_PERM_NP
+ ret = acl_get_perm_np(acl_permset, ACL_EXECUTE);
+#else
+ ret = acl_get_perm(acl_permset, ACL_EXECUTE);
+#endif
+ assert_int_equal(ret, execute ? 1 : 0);
+}
+
+static void test_smb_acl_to_posix_simple_acl(void **state)
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct smb_acl_t *smb_acl = NULL;
+ acl_t acl = NULL;
+ acl_entry_t acl_entry = NULL;
+ int ret;
+
+ smb_acl = sys_acl_init(mem_ctx);
+ assert_non_null(smb_acl);
+
+ smb_acl_add_entry(smb_acl, SMB_ACL_USER_OBJ, 0, false, true, false);
+ smb_acl_add_entry(smb_acl, SMB_ACL_GROUP_OBJ, 0, true, false, false);
+ smb_acl_add_entry(smb_acl, SMB_ACL_OTHER, 0, false, false, true);
+
+ acl = smb_acl_to_posix(smb_acl);
+ assert_non_null(acl);
+
+ ret = acl_get_entry(acl, ACL_FIRST_ENTRY, &acl_entry);
+ assert_int_equal(ret, 1);
+ acl_check_entry(acl_entry, ACL_USER_OBJ, 0, false, true, false);
+
+ ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
+ assert_int_equal(ret, 1);
+ acl_check_entry(acl_entry, ACL_GROUP_OBJ, 0, true, false, false);
+
+ ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
+ assert_int_equal(ret, 1);
+ acl_check_entry(acl_entry, ACL_OTHER, 0, false, false, true);
+
+ ret = acl_get_entry(acl, ACL_NEXT_ENTRY, &acl_entry);
+ assert_int_equal(ret, 0);
+
+ ret = acl_free(acl);
+ assert_int_equal(ret, 0);
+
+ TALLOC_FREE(mem_ctx);
+}
+
+int main(int argc, char **argv)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_smb_acl_to_posix_simple_acl),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ if (argc != 2) {
+ print_error("Usage: %s smb.conf\n", argv[0]);
+ exit(1);
+ }
+
+ /*
+ * Initialize enough of the Samba internals to have the
+ * mappings tests work.
+ */
+ talloc_stackframe();
+ lp_load_global(argv[1]);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source3/modules/util_reparse.c b/source3/modules/util_reparse.c
new file mode 100644
index 0000000..45cacbd
--- /dev/null
+++ b/source3/modules/util_reparse.c
@@ -0,0 +1,84 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utility functions for reparse points.
+ *
+ * Copyright (C) Jeremy Allison 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "util_reparse.h"
+
+NTSTATUS fsctl_get_reparse_point(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len)
+{
+ DBG_DEBUG("Called on %s\n", fsp_str_dbg(fsp));
+ return NT_STATUS_NOT_A_REPARSE_POINT;
+}
+
+static NTSTATUS check_reparse_data_buffer(
+ const uint8_t *in_data, size_t in_len)
+{
+ uint16_t reparse_data_length;
+
+ if (in_len == 0) {
+ DBG_DEBUG("in_len=0\n");
+ return NT_STATUS_INVALID_BUFFER_SIZE;
+ }
+ if (in_len < 8) {
+ DBG_DEBUG("in_len=%zu\n", in_len);
+ return NT_STATUS_IO_REPARSE_DATA_INVALID;
+ }
+
+ reparse_data_length = PULL_LE_U16(in_data, 4);
+
+ if (reparse_data_length != (in_len - 8)) {
+ DBG_DEBUG("in_len=%zu, reparse_data_length=%"PRIu16"\n",
+ in_len,
+ reparse_data_length);
+ return NT_STATUS_IO_REPARSE_DATA_INVALID;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS fsctl_set_reparse_point(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ const uint8_t *in_data,
+ uint32_t in_len)
+{
+ NTSTATUS status;
+
+ DBG_DEBUG("Called on %s\n", fsp_str_dbg(fsp));
+
+ status = check_reparse_data_buffer(in_data, in_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_NOT_A_REPARSE_POINT;
+}
+
+NTSTATUS fsctl_del_reparse_point(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ const uint8_t *in_data,
+ uint32_t in_len)
+{
+ DBG_DEBUG("Called on %s\n", fsp_str_dbg(fsp));
+ return NT_STATUS_NOT_A_REPARSE_POINT;
+}
diff --git a/source3/modules/util_reparse.h b/source3/modules/util_reparse.h
new file mode 100644
index 0000000..ffdb7b2
--- /dev/null
+++ b/source3/modules/util_reparse.h
@@ -0,0 +1,40 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Utility functions for reparse points.
+ *
+ * Copyright (C) Jeremy Allison 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __UTIL_REPARSE_H__
+#define __UTIL_REPARSE_H__
+
+NTSTATUS fsctl_get_reparse_point(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len);
+
+NTSTATUS fsctl_set_reparse_point(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ const uint8_t *in_data,
+ uint32_t in_len);
+
+NTSTATUS fsctl_del_reparse_point(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ const uint8_t *in_data,
+ uint32_t in_len);
+
+#endif /* __UTIL_REPARSE_H__ */
diff --git a/source3/modules/vfs_acl_common.c b/source3/modules/vfs_acl_common.c
new file mode 100644
index 0000000..e04b672
--- /dev/null
+++ b/source3/modules/vfs_acl_common.c
@@ -0,0 +1,1181 @@
+/*
+ * Store Windows ACLs in data store - common functions.
+ * #included into modules/vfs_acl_xattr.c and modules/vfs_acl_tdb.c
+ *
+ * Copyright (C) Volker Lendecke, 2008
+ * Copyright (C) Jeremy Allison, 2009
+ * Copyright (C) Ralph Böhme, 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "vfs_acl_common.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../lib/util/bitmap.h"
+#include "passdb/lookup_sid.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+static NTSTATUS create_acl_blob(const struct security_descriptor *psd,
+ DATA_BLOB *pblob,
+ uint16_t hash_type,
+ uint8_t hash[XATTR_SD_HASH_SIZE]);
+
+#define HASH_SECURITY_INFO (SECINFO_OWNER | \
+ SECINFO_GROUP | \
+ SECINFO_DACL | \
+ SECINFO_SACL)
+
+bool init_acl_common_config(vfs_handle_struct *handle,
+ const char *module_name)
+{
+ struct acl_common_config *config = NULL;
+ const struct enum_list *default_acl_style_list = NULL;
+
+ default_acl_style_list = get_default_acl_style_list();
+
+ config = talloc_zero(handle->conn, struct acl_common_config);
+ if (config == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return false;
+ }
+
+ config->ignore_system_acls = lp_parm_bool(SNUM(handle->conn),
+ module_name,
+ "ignore system acls",
+ false);
+ config->default_acl_style = lp_parm_enum(SNUM(handle->conn),
+ module_name,
+ "default acl style",
+ default_acl_style_list,
+ DEFAULT_ACL_POSIX);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config, NULL,
+ struct acl_common_config,
+ return false);
+
+ return true;
+}
+
+
+/*******************************************************************
+ Hash a security descriptor.
+*******************************************************************/
+
+static NTSTATUS hash_blob_sha256(DATA_BLOB blob,
+ uint8_t *hash)
+{
+ int rc;
+
+ ZERO_ARRAY_LEN(hash, XATTR_SD_HASH_SIZE);
+
+ rc = gnutls_hash_fast(GNUTLS_DIG_SHA256,
+ blob.data,
+ blob.length,
+ hash);
+ if (rc < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Hash a security descriptor.
+*******************************************************************/
+
+static NTSTATUS hash_sd_sha256(struct security_descriptor *psd,
+ uint8_t *hash)
+{
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ memset(hash, '\0', XATTR_SD_HASH_SIZE);
+ status = create_acl_blob(psd, &blob, XATTR_SD_HASH_TYPE_SHA256, hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return hash_blob_sha256(blob, hash);
+}
+
+/*******************************************************************
+ Parse out a struct security_descriptor from a DATA_BLOB.
+*******************************************************************/
+
+static NTSTATUS parse_acl_blob(const DATA_BLOB *pblob,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc,
+ uint16_t *p_hash_type,
+ uint16_t *p_version,
+ uint8_t hash[XATTR_SD_HASH_SIZE],
+ uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE])
+{
+ struct xattr_NTACL xacl;
+ enum ndr_err_code ndr_err;
+ size_t sd_size;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(pblob, frame, &xacl,
+ (ndr_pull_flags_fn_t)ndr_pull_xattr_NTACL);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_INFO("ndr_pull_xattr_NTACL failed: %s\n",
+ ndr_errstr(ndr_err));
+ TALLOC_FREE(frame);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ *p_version = xacl.version;
+
+ switch (xacl.version) {
+ case 1:
+ *ppdesc = make_sec_desc(mem_ctx, SD_REVISION,
+ xacl.info.sd->type | SEC_DESC_SELF_RELATIVE,
+ xacl.info.sd->owner_sid,
+ xacl.info.sd->group_sid,
+ xacl.info.sd->sacl,
+ xacl.info.sd->dacl,
+ &sd_size);
+ /* No hash - null out. */
+ *p_hash_type = XATTR_SD_HASH_TYPE_NONE;
+ memset(hash, '\0', XATTR_SD_HASH_SIZE);
+ break;
+ case 2:
+ *ppdesc = make_sec_desc(mem_ctx, SD_REVISION,
+ xacl.info.sd_hs2->sd->type | SEC_DESC_SELF_RELATIVE,
+ xacl.info.sd_hs2->sd->owner_sid,
+ xacl.info.sd_hs2->sd->group_sid,
+ xacl.info.sd_hs2->sd->sacl,
+ xacl.info.sd_hs2->sd->dacl,
+ &sd_size);
+ /* No hash - null out. */
+ *p_hash_type = XATTR_SD_HASH_TYPE_NONE;
+ memset(hash, '\0', XATTR_SD_HASH_SIZE);
+ break;
+ case 3:
+ *ppdesc = make_sec_desc(mem_ctx, SD_REVISION,
+ xacl.info.sd_hs3->sd->type | SEC_DESC_SELF_RELATIVE,
+ xacl.info.sd_hs3->sd->owner_sid,
+ xacl.info.sd_hs3->sd->group_sid,
+ xacl.info.sd_hs3->sd->sacl,
+ xacl.info.sd_hs3->sd->dacl,
+ &sd_size);
+ *p_hash_type = xacl.info.sd_hs3->hash_type;
+ /* Current version 3 (if no sys acl hash available). */
+ memcpy(hash, xacl.info.sd_hs3->hash, XATTR_SD_HASH_SIZE);
+ break;
+ case 4:
+ *ppdesc = make_sec_desc(mem_ctx, SD_REVISION,
+ xacl.info.sd_hs4->sd->type | SEC_DESC_SELF_RELATIVE,
+ xacl.info.sd_hs4->sd->owner_sid,
+ xacl.info.sd_hs4->sd->group_sid,
+ xacl.info.sd_hs4->sd->sacl,
+ xacl.info.sd_hs4->sd->dacl,
+ &sd_size);
+ *p_hash_type = xacl.info.sd_hs4->hash_type;
+ /* Current version 4. */
+ memcpy(hash, xacl.info.sd_hs4->hash, XATTR_SD_HASH_SIZE);
+ memcpy(sys_acl_hash, xacl.info.sd_hs4->sys_acl_hash, XATTR_SD_HASH_SIZE);
+ break;
+ default:
+ TALLOC_FREE(frame);
+ return NT_STATUS_REVISION_MISMATCH;
+ }
+
+ TALLOC_FREE(frame);
+
+ return (*ppdesc != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+}
+
+/*******************************************************************
+ Create a DATA_BLOB from a hash of the security descriptor storead at
+ the system layer and the NT ACL we wish to preserve
+*******************************************************************/
+
+static NTSTATUS create_acl_blob(const struct security_descriptor *psd,
+ DATA_BLOB *pblob,
+ uint16_t hash_type,
+ uint8_t hash[XATTR_SD_HASH_SIZE])
+{
+ struct xattr_NTACL xacl;
+ struct security_descriptor_hash_v3 sd_hs3;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ ZERO_STRUCT(xacl);
+ ZERO_STRUCT(sd_hs3);
+
+ xacl.version = 3;
+ xacl.info.sd_hs3 = &sd_hs3;
+ xacl.info.sd_hs3->sd = discard_const_p(struct security_descriptor, psd);
+ xacl.info.sd_hs3->hash_type = hash_type;
+ memcpy(&xacl.info.sd_hs3->hash[0], hash, XATTR_SD_HASH_SIZE);
+
+ ndr_err = ndr_push_struct_blob(
+ pblob, ctx, &xacl,
+ (ndr_push_flags_fn_t)ndr_push_xattr_NTACL);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_INFO("ndr_push_xattr_NTACL failed: %s\n",
+ ndr_errstr(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Create a DATA_BLOB from a hash of the security descriptors
+ (system and NT) stored at the system layer and the NT ACL we wish
+ to preserve.
+*******************************************************************/
+
+static NTSTATUS create_sys_acl_blob(const struct security_descriptor *psd,
+ DATA_BLOB *pblob,
+ uint16_t hash_type,
+ uint8_t hash[XATTR_SD_HASH_SIZE],
+ const char *description,
+ uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE])
+{
+ struct xattr_NTACL xacl;
+ struct security_descriptor_hash_v4 sd_hs4;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ ZERO_STRUCT(xacl);
+ ZERO_STRUCT(sd_hs4);
+
+ xacl.version = 4;
+ xacl.info.sd_hs4 = &sd_hs4;
+ xacl.info.sd_hs4->sd = discard_const_p(struct security_descriptor, psd);
+ xacl.info.sd_hs4->hash_type = hash_type;
+ memcpy(&xacl.info.sd_hs4->hash[0], hash, XATTR_SD_HASH_SIZE);
+ xacl.info.sd_hs4->description = description;
+ memcpy(&xacl.info.sd_hs4->sys_acl_hash[0], sys_acl_hash, XATTR_SD_HASH_SIZE);
+
+ ndr_err = ndr_push_struct_blob(
+ pblob, ctx, &xacl,
+ (ndr_push_flags_fn_t)ndr_push_xattr_NTACL);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_INFO("ndr_push_xattr_NTACL failed: %s\n",
+ ndr_errstr(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Add in 3 inheritable components for a non-inheritable directory ACL.
+ CREATOR_OWNER/CREATOR_GROUP/WORLD.
+*******************************************************************/
+
+static NTSTATUS add_directory_inheritable_components(vfs_handle_struct *handle,
+ const char *name,
+ SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor *psd)
+{
+ struct connection_struct *conn = handle->conn;
+ int num_aces = (psd->dacl ? psd->dacl->num_aces : 0);
+ struct smb_filename smb_fname;
+ enum security_ace_type acltype;
+ uint32_t access_mask;
+ mode_t dir_mode;
+ mode_t file_mode;
+ mode_t mode;
+ struct security_ace *new_ace_list;
+
+ if (psd->dacl) {
+ new_ace_list = talloc_zero_array(psd->dacl,
+ struct security_ace,
+ num_aces + 3);
+ } else {
+ /*
+ * make_sec_acl() at the bottom of this function
+ * duplicates new_ace_list
+ */
+ new_ace_list = talloc_zero_array(talloc_tos(),
+ struct security_ace,
+ num_aces + 3);
+ }
+
+ if (new_ace_list == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Fake a quick smb_filename. */
+ ZERO_STRUCT(smb_fname);
+ smb_fname.st = *psbuf;
+ smb_fname.base_name = discard_const_p(char, name);
+
+ dir_mode = unix_mode(conn,
+ FILE_ATTRIBUTE_DIRECTORY, &smb_fname, NULL);
+ file_mode = unix_mode(conn,
+ FILE_ATTRIBUTE_ARCHIVE, &smb_fname, NULL);
+
+ mode = dir_mode | file_mode;
+
+ DBG_DEBUG("directory %s, mode = 0%o\n", name, (unsigned int)mode);
+
+ if (num_aces) {
+ memcpy(new_ace_list, psd->dacl->aces,
+ num_aces * sizeof(struct security_ace));
+ }
+ access_mask = map_canon_ace_perms(SNUM(conn), &acltype,
+ mode & 0700, false);
+
+ init_sec_ace(&new_ace_list[num_aces],
+ &global_sid_Creator_Owner,
+ acltype,
+ access_mask,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+ access_mask = map_canon_ace_perms(SNUM(conn), &acltype,
+ (mode << 3) & 0700, false);
+ init_sec_ace(&new_ace_list[num_aces+1],
+ &global_sid_Creator_Group,
+ acltype,
+ access_mask,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+ access_mask = map_canon_ace_perms(SNUM(conn), &acltype,
+ (mode << 6) & 0700, false);
+ init_sec_ace(&new_ace_list[num_aces+2],
+ &global_sid_World,
+ acltype,
+ access_mask,
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+ if (psd->dacl) {
+ psd->dacl->aces = new_ace_list;
+ psd->dacl->num_aces += 3;
+ psd->dacl->size += new_ace_list[num_aces].size +
+ new_ace_list[num_aces+1].size +
+ new_ace_list[num_aces+2].size;
+ } else {
+ psd->dacl = make_sec_acl(psd,
+ NT4_ACL_REVISION,
+ 3,
+ new_ace_list);
+ if (psd->dacl == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/**
+ * Validate an ACL blob
+ *
+ * This validates an ACL blob against the underlying filesystem ACL. If this
+ * function returns NT_STATUS_OK ppsd can be
+ *
+ * 1. the ACL from the blob (psd_from_fs=false), or
+ * 2. the ACL from the fs (psd_from_fs=true), or
+ * 3. NULL (!)
+ *
+ * If the return value is anything else then NT_STATUS_OK, ppsd is set to NULL
+ * and psd_from_fs set to false.
+ *
+ * Returning the underlying filesystem ACL in case no. 2 is really just an
+ * optimisation, because some validations have to fetch the filesystem ACL as
+ * part of the validation, so we already have it available and callers might
+ * need it as well.
+ **/
+static NTSTATUS validate_nt_acl_blob(TALLOC_CTX *mem_ctx,
+ vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ const DATA_BLOB *blob,
+ struct security_descriptor **ppsd,
+ bool *psd_is_from_fs)
+{
+ NTSTATUS status;
+ uint16_t hash_type = XATTR_SD_HASH_TYPE_NONE;
+ uint16_t xattr_version = 0;
+ uint8_t hash[XATTR_SD_HASH_SIZE];
+ uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE];
+ uint8_t hash_tmp[XATTR_SD_HASH_SIZE];
+ uint8_t sys_acl_hash_tmp[XATTR_SD_HASH_SIZE];
+ struct security_descriptor *psd = NULL;
+ struct security_descriptor *psd_blob = NULL;
+ struct security_descriptor *psd_fs = NULL;
+ char *sys_acl_blob_description = NULL;
+ DATA_BLOB sys_acl_blob = { 0 };
+ struct acl_common_config *config = NULL;
+
+ *ppsd = NULL;
+ *psd_is_from_fs = false;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ status = parse_acl_blob(blob,
+ mem_ctx,
+ &psd_blob,
+ &hash_type,
+ &xattr_version,
+ &hash[0],
+ &sys_acl_hash[0]);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("parse_acl_blob returned %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* determine which type of xattr we got */
+ switch (xattr_version) {
+ case 1:
+ case 2:
+ /* These xattr types are unilateral, they do not
+ * require confirmation of the hash. In particular,
+ * the NTVFS file server uses version 1, but
+ * 'samba-tool ntacl' can set these as well */
+ *ppsd = psd_blob;
+ return NT_STATUS_OK;
+ case 3:
+ case 4:
+ if (config->ignore_system_acls) {
+ *ppsd = psd_blob;
+ return NT_STATUS_OK;
+ }
+
+ break;
+ default:
+ DBG_DEBUG("ACL blob revision mismatch (%u) for file %s\n",
+ (unsigned int)hash_type, smb_fname->base_name);
+ TALLOC_FREE(psd_blob);
+ return NT_STATUS_OK;
+ }
+
+ /* determine which type of xattr we got */
+ if (hash_type != XATTR_SD_HASH_TYPE_SHA256) {
+ DBG_DEBUG("ACL blob hash type (%u) unexpected for file %s\n",
+ (unsigned int)hash_type, smb_fname->base_name);
+ TALLOC_FREE(psd_blob);
+ return NT_STATUS_OK;
+ }
+
+ /* determine which type of xattr we got */
+ switch (xattr_version) {
+ case 4:
+ {
+ int ret;
+ /* Get the full underlying sd, then hash. */
+ ret = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle,
+ fsp,
+ mem_ctx,
+ &sys_acl_blob_description,
+ &sys_acl_blob);
+ /* If we fail to get the ACL blob (for some reason) then this
+ * is not fatal, we just work based on the NT ACL only */
+ if (ret == 0) {
+ status = hash_blob_sha256(sys_acl_blob, sys_acl_hash_tmp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ TALLOC_FREE(sys_acl_blob_description);
+ TALLOC_FREE(sys_acl_blob.data);
+
+ if (memcmp(&sys_acl_hash[0], &sys_acl_hash_tmp[0],
+ XATTR_SD_HASH_SIZE) == 0) {
+ /* Hash matches, return blob sd. */
+ DBG_DEBUG("blob hash matches for file %s\n",
+ smb_fname->base_name);
+ *ppsd = psd_blob;
+ return NT_STATUS_OK;
+ }
+ }
+
+ /* Otherwise, fall though and see if the NT ACL hash matches */
+ FALL_THROUGH;
+ }
+ case 3:
+ /* Get the full underlying sd for the hash
+ or to return as backup. */
+ status = SMB_VFS_NEXT_FGET_NT_ACL(handle,
+ fsp,
+ HASH_SECURITY_INFO,
+ mem_ctx,
+ &psd_fs);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("get_next_acl for file %s returned %s\n",
+ smb_fname->base_name, nt_errstr(status));
+ goto fail;
+ }
+
+ status = hash_sd_sha256(psd_fs, hash_tmp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(psd_blob);
+ *ppsd = psd_fs;
+ *psd_is_from_fs = true;
+ return NT_STATUS_OK;
+ }
+
+ if (memcmp(&hash[0], &hash_tmp[0], XATTR_SD_HASH_SIZE) == 0) {
+ /* Hash matches, return blob sd. */
+ DBG_DEBUG("blob hash matches for file %s\n",
+ smb_fname->base_name);
+ *ppsd = psd_blob;
+ return NT_STATUS_OK;
+ }
+
+ /* Hash doesn't match, return underlying sd. */
+ DBG_DEBUG("blob hash does not match for file %s - returning "
+ "file system SD mapping.\n",
+ smb_fname->base_name);
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("acl for blob hash for %s is:\n",
+ smb_fname->base_name);
+ NDR_PRINT_DEBUG(security_descriptor, psd_fs);
+ }
+
+ TALLOC_FREE(psd_blob);
+ *ppsd = psd_fs;
+ *psd_is_from_fs = true;
+ }
+
+ return NT_STATUS_OK;
+
+fail:
+ TALLOC_FREE(psd);
+ TALLOC_FREE(psd_blob);
+ TALLOC_FREE(psd_fs);
+ TALLOC_FREE(sys_acl_blob_description);
+ TALLOC_FREE(sys_acl_blob.data);
+ return status;
+}
+
+/*******************************************************************
+ Pull a DATA_BLOB from an xattr given an fsp.
+ If the hash doesn't match, or doesn't exist - return the underlying
+ filesystem sd.
+*******************************************************************/
+
+NTSTATUS fget_nt_acl_common(
+ NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob),
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ DATA_BLOB blob = data_blob_null;
+ NTSTATUS status;
+ struct security_descriptor *psd = NULL;
+ const struct smb_filename *smb_fname = fsp->fsp_name;
+ bool psd_is_from_fs = false;
+ struct acl_common_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ DBG_DEBUG("name=%s\n", smb_fname->base_name);
+
+ status = fget_acl_blob_fn(mem_ctx, handle, fsp, &blob);
+ if (NT_STATUS_IS_OK(status)) {
+ status = validate_nt_acl_blob(mem_ctx,
+ handle,
+ fsp,
+ smb_fname,
+ &blob,
+ &psd,
+ &psd_is_from_fs);
+ TALLOC_FREE(blob.data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("ACL validation for [%s] failed\n",
+ smb_fname->base_name);
+ goto fail;
+ }
+ }
+
+ if (psd == NULL) {
+ /* Get the full underlying sd, as we failed to get the
+ * blob for the hash, or the revision/hash type wasn't
+ * known */
+
+ if (config->ignore_system_acls) {
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = make_default_filesystem_acl(
+ mem_ctx,
+ config->default_acl_style,
+ smb_fname->base_name,
+ &fsp->fsp_name->st,
+ &psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ } else {
+ status = SMB_VFS_NEXT_FGET_NT_ACL(handle,
+ fsp,
+ security_info,
+ mem_ctx,
+ &psd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("get_next_acl for file %s "
+ "returned %s\n",
+ smb_fname->base_name,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ psd_is_from_fs = true;
+ }
+ }
+
+ if (psd_is_from_fs) {
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /*
+ * We're returning the underlying ACL from the
+ * filesystem. If it's a directory, and has no
+ * inheritable ACE entries we have to fake them.
+ */
+
+ if (fsp->fsp_flags.is_directory &&
+ !sd_has_inheritable_components(psd, true)) {
+ status = add_directory_inheritable_components(
+ handle,
+ smb_fname->base_name,
+ &fsp->fsp_name->st,
+ psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ /*
+ * The underlying POSIX module always sets the
+ * ~SEC_DESC_DACL_PROTECTED bit, as ACLs can't be inherited in
+ * this way under POSIX. Remove it for Windows-style ACLs.
+ */
+ psd->type &= ~SEC_DESC_DACL_PROTECTED;
+ }
+
+ if (!(security_info & SECINFO_OWNER)) {
+ psd->owner_sid = NULL;
+ }
+ if (!(security_info & SECINFO_GROUP)) {
+ psd->group_sid = NULL;
+ }
+ if (!(security_info & SECINFO_DACL)) {
+ psd->type &= ~SEC_DESC_DACL_PRESENT;
+ psd->dacl = NULL;
+ }
+ if (!(security_info & SECINFO_SACL)) {
+ psd->type &= ~SEC_DESC_SACL_PRESENT;
+ psd->sacl = NULL;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("returning acl for %s is:\n",
+ smb_fname->base_name);
+ NDR_PRINT_DEBUG(security_descriptor, psd);
+ }
+
+ *ppdesc = psd;
+
+ return NT_STATUS_OK;
+
+fail:
+ TALLOC_FREE(psd);
+ return status;
+}
+
+/*********************************************************************
+ Set the underlying ACL (e.g. POSIX ACLS, POSIX owner, etc)
+*********************************************************************/
+static NTSTATUS set_underlying_acl(vfs_handle_struct *handle, files_struct *fsp,
+ struct security_descriptor *psd,
+ uint32_t security_info_sent,
+ bool chown_needed)
+{
+ NTSTATUS status;
+ const struct security_token *token = NULL;
+ struct dom_sid_buf buf;
+
+ status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ return status;
+ }
+
+ /* We got access denied here. If we're already root,
+ or we didn't need to do a chown, or the fsp isn't
+ open with WRITE_OWNER access, just return. */
+ if (get_current_uid(handle->conn) == 0 || !chown_needed) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Only allow take-ownership, not give-ownership. That's the way Windows
+ * implements SEC_STD_WRITE_OWNER. MS-FSA 2.1.5.16 just states: If
+ * InputBuffer.OwnerSid is not a valid owner SID for a file in the
+ * objectstore, as determined in an implementation specific manner, the
+ * object store MUST return STATUS_INVALID_OWNER.
+ */
+ token = get_current_nttok(fsp->conn);
+ if (!security_token_is_sid(token, psd->owner_sid)) {
+ return NT_STATUS_INVALID_OWNER;
+ }
+
+ DBG_DEBUG("overriding chown on file %s for sid %s\n",
+ fsp_str_dbg(fsp),
+ dom_sid_str_buf(psd->owner_sid, &buf));
+
+ /* Ok, we failed to chown and we have
+ SEC_STD_WRITE_OWNER access - override. */
+ become_root();
+ status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+ unbecome_root();
+
+ return status;
+}
+
+/*********************************************************************
+ Store a v3 security descriptor
+*********************************************************************/
+static NTSTATUS store_v3_blob(
+ NTSTATUS (*store_acl_blob_fsp_fn)(vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob),
+ vfs_handle_struct *handle, files_struct *fsp,
+ struct security_descriptor *psd,
+ struct security_descriptor *pdesc_next,
+ uint8_t hash[XATTR_SD_HASH_SIZE])
+{
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("storing xattr sd for file %s\n",
+ fsp_str_dbg(fsp));
+ NDR_PRINT_DEBUG(
+ security_descriptor,
+ discard_const_p(struct security_descriptor, psd));
+
+ if (pdesc_next != NULL) {
+ DBG_DEBUG("storing xattr sd based on \n");
+ NDR_PRINT_DEBUG(
+ security_descriptor,
+ discard_const_p(struct security_descriptor,
+ pdesc_next));
+ } else {
+ DBG_DEBUG("ignoring underlying sd\n");
+ }
+ }
+ status = create_acl_blob(psd, &blob, XATTR_SD_HASH_TYPE_SHA256, hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("create_acl_blob failed\n");
+ return status;
+ }
+
+ status = store_acl_blob_fsp_fn(handle, fsp, &blob);
+ return status;
+}
+
+/*********************************************************************
+ Store a security descriptor given an fsp.
+*********************************************************************/
+
+NTSTATUS fset_nt_acl_common(
+ NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob),
+ NTSTATUS (*store_acl_blob_fsp_fn)(vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob),
+ const char *module_name,
+ vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *orig_psd)
+{
+ NTSTATUS status;
+ int ret;
+ DATA_BLOB blob, sys_acl_blob;
+ struct security_descriptor *pdesc_next = NULL;
+ struct security_descriptor *psd = NULL;
+ uint8_t hash[XATTR_SD_HASH_SIZE];
+ uint8_t sys_acl_hash[XATTR_SD_HASH_SIZE];
+ bool chown_needed = false;
+ char *sys_acl_description;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ignore_file_system_acl = lp_parm_bool(
+ SNUM(handle->conn), module_name, "ignore system acls", false);
+ struct acl_common_fsp_ext *ext = NULL;
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("incoming sd for file %s\n", fsp_str_dbg(fsp));
+ NDR_PRINT_DEBUG(security_descriptor,
+ discard_const_p(struct security_descriptor, orig_psd));
+ }
+
+ status = fget_nt_acl_common(fget_acl_blob_fn, handle, fsp,
+ SECINFO_OWNER|SECINFO_GROUP|SECINFO_DACL|SECINFO_SACL,
+ frame,
+ &psd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ psd->revision = orig_psd->revision;
+ if (security_info_sent & SECINFO_DACL) {
+ psd->type = orig_psd->type;
+ /* All our SD's are self relative. */
+ psd->type |= SEC_DESC_SELF_RELATIVE;
+ }
+
+ if ((security_info_sent & SECINFO_OWNER) && (orig_psd->owner_sid != NULL)) {
+ if (!dom_sid_equal(orig_psd->owner_sid, psd->owner_sid)) {
+ /* We're changing the owner. */
+ chown_needed = true;
+ }
+ psd->owner_sid = orig_psd->owner_sid;
+ }
+ if ((security_info_sent & SECINFO_GROUP) && (orig_psd->group_sid != NULL)) {
+ if (!dom_sid_equal(orig_psd->group_sid, psd->group_sid)) {
+ /* We're changing the group. */
+ chown_needed = true;
+ }
+ psd->group_sid = orig_psd->group_sid;
+ }
+ if (security_info_sent & SECINFO_DACL) {
+ if (security_descriptor_with_ms_nfs(orig_psd)) {
+ /*
+ * If the sd contains a MS NFS SID, do
+ * nothing, it's a chmod() request from OS X
+ * with AAPL context.
+ */
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+ psd->dacl = orig_psd->dacl;
+ psd->type |= SEC_DESC_DACL_PRESENT;
+ }
+ if (security_info_sent & SECINFO_SACL) {
+ psd->sacl = orig_psd->sacl;
+ psd->type |= SEC_DESC_SACL_PRESENT;
+ }
+
+ ext = VFS_ADD_FSP_EXTENSION(handle,
+ fsp,
+ struct acl_common_fsp_ext,
+ NULL);
+ ext->setting_nt_acl = true;
+
+ if (ignore_file_system_acl) {
+ if (chown_needed) {
+ /* send only ownership stuff to lower layer */
+ security_info_sent &= (SECINFO_OWNER | SECINFO_GROUP);
+ status = set_underlying_acl(handle, fsp, psd,
+ security_info_sent, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+ ZERO_ARRAY(hash);
+ status = store_v3_blob(store_acl_blob_fsp_fn, handle, fsp, psd,
+ NULL, hash);
+ goto done;
+ }
+
+ status = set_underlying_acl(handle, fsp, psd, security_info_sent,
+ chown_needed);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get the full underlying sd, then hash. */
+ status = SMB_VFS_NEXT_FGET_NT_ACL(handle,
+ fsp,
+ HASH_SECURITY_INFO,
+ frame,
+ &pdesc_next);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = hash_sd_sha256(pdesc_next, hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get the full underlying sd, then hash. */
+ ret = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle,
+ fsp,
+ frame,
+ &sys_acl_description,
+ &sys_acl_blob);
+
+ /* If we fail to get the ACL blob (for some reason) then this
+ * is not fatal, we just work based on the NT ACL only */
+ if (ret != 0) {
+ status = store_v3_blob(store_acl_blob_fsp_fn, handle, fsp, psd,
+ pdesc_next, hash);
+
+ goto done;
+ }
+
+ status = hash_blob_sha256(sys_acl_blob, sys_acl_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("storing xattr sd for file %s based on system ACL\n",
+ fsp_str_dbg(fsp));
+ NDR_PRINT_DEBUG(security_descriptor,
+ discard_const_p(struct security_descriptor, psd));
+
+ DBG_DEBUG("storing hash in xattr sd based on system ACL and:\n");
+ NDR_PRINT_DEBUG(security_descriptor,
+ discard_const_p(struct security_descriptor, pdesc_next));
+ }
+
+ /* We store hashes of both the sys ACL blob and the NT
+ * security descriptor mapped from that ACL so as to improve
+ * our chances against some inadvertent change breaking the
+ * hash used */
+ status = create_sys_acl_blob(psd, &blob, XATTR_SD_HASH_TYPE_SHA256, hash,
+ sys_acl_description, sys_acl_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("create_sys_acl_blob failed\n");
+ goto done;
+ }
+
+ status = store_acl_blob_fsp_fn(handle, fsp, &blob);
+
+done:
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static int acl_common_remove_object(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ bool is_directory)
+{
+ connection_struct *conn = handle->conn;
+ struct file_id id;
+ files_struct *fsp = NULL;
+ int ret = 0;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *local_fname = NULL;
+ struct smb_filename *parent_dir_fname = NULL;
+ int saved_errno = 0;
+ struct smb_filename *saved_dir_fname = NULL;
+ NTSTATUS status;
+
+ saved_dir_fname = vfs_GetWd(talloc_tos(),conn);
+ if (saved_dir_fname == NULL) {
+ saved_errno = errno;
+ goto out;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ goto out;
+ }
+
+ status = SMB_VFS_PARENT_PATHNAME(conn,
+ talloc_tos(),
+ full_fname,
+ &parent_dir_fname,
+ &local_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ saved_errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ DBG_DEBUG("removing %s %s\n", is_directory ? "directory" : "file",
+ smb_fname_str_dbg(full_fname));
+
+ /* cd into the parent dir to pin it. */
+ ret = vfs_ChDir(conn, parent_dir_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ goto out;
+ }
+
+ /* Must use lstat here. */
+ ret = SMB_VFS_LSTAT(conn, local_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ goto out;
+ }
+
+ /* Ensure we have this file open with DELETE access. */
+ id = vfs_file_id_from_sbuf(conn, &local_fname->st);
+ for (fsp = file_find_di_first(conn->sconn, id, true); fsp;
+ fsp = file_find_di_next(fsp, true)) {
+ if (fsp->access_mask & DELETE_ACCESS &&
+ fsp->fsp_flags.delete_on_close)
+ {
+ /* We did open this for delete,
+ * allow the delete as root.
+ */
+ break;
+ }
+ }
+
+ if (!fsp) {
+ DBG_DEBUG("%s %s not an open file\n",
+ is_directory ? "directory" : "file",
+ smb_fname_str_dbg(full_fname));
+ saved_errno = EACCES;
+ goto out;
+ }
+
+ become_root();
+ if (is_directory) {
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ AT_REMOVEDIR);
+ } else {
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ 0);
+ }
+ unbecome_root();
+
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+
+ out:
+
+ TALLOC_FREE(parent_dir_fname);
+ TALLOC_FREE(full_fname);
+
+ if (saved_dir_fname) {
+ vfs_ChDir(conn, saved_dir_fname);
+ TALLOC_FREE(saved_dir_fname);
+ }
+ if (saved_errno) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+int rmdir_acl_common(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ int ret;
+
+ /* Try the normal rmdir first. */
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ AT_REMOVEDIR);
+ if (ret == 0) {
+ return 0;
+ }
+ if (errno == EACCES || errno == EPERM) {
+ /* Failed due to access denied,
+ see if we need to root override. */
+ return acl_common_remove_object(handle,
+ dirfsp,
+ smb_fname,
+ true);
+ }
+
+ DBG_DEBUG("unlink of %s failed %s\n",
+ smb_fname->base_name,
+ strerror(errno));
+ return -1;
+}
+
+int unlink_acl_common(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret;
+
+ /* Try the normal unlink first. */
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ if (ret == 0) {
+ return 0;
+ }
+ if (errno == EACCES || errno == EPERM) {
+ /* Failed due to access denied,
+ see if we need to root override. */
+
+ /* Don't do anything fancy for streams. */
+ if (smb_fname->stream_name) {
+ return -1;
+ }
+ return acl_common_remove_object(handle,
+ dirfsp,
+ smb_fname,
+ false);
+ }
+
+ DBG_DEBUG("unlink of %s failed %s\n",
+ smb_fname->base_name,
+ strerror(errno));
+ return -1;
+}
+
+int fchmod_acl_module_common(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, mode_t mode)
+{
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES
+ || fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH) {
+ /* Only allow this on POSIX opens. */
+ return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+ }
+ return 0;
+}
diff --git a/source3/modules/vfs_acl_common.h b/source3/modules/vfs_acl_common.h
new file mode 100644
index 0000000..8d3475f
--- /dev/null
+++ b/source3/modules/vfs_acl_common.h
@@ -0,0 +1,91 @@
+/*
+ * Store Windows ACLs in data store - common functions.
+ *
+ * Copyright (C) Volker Lendecke, 2008
+ * Copyright (C) Jeremy Allison, 2009
+ * Copyright (C) Ralph Böhme, 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VFS_ACL_COMMON_H__
+#define __VFS_ACL_COMMON_H__
+
+#include "smbd/proto.h"
+
+struct acl_common_config {
+ bool ignore_system_acls;
+ enum default_acl_style default_acl_style;
+ char *security_acl_xattr_name;
+};
+
+struct acl_common_fsp_ext {
+ bool setting_nt_acl;
+};
+
+bool init_acl_common_config(vfs_handle_struct *handle,
+ const char *module_name);
+
+int rmdir_acl_common(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname);
+int unlink_acl_common(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags);
+int fchmod_acl_module_common(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, mode_t mode);
+int chmod_acl_acl_module_common(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ mode_t mode);
+NTSTATUS get_nt_acl_common_at(
+ NTSTATUS (*get_acl_blob_at_fn)(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ DATA_BLOB *pblob),
+ vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+
+NTSTATUS fget_nt_acl_common(
+ NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob),
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+
+NTSTATUS fset_nt_acl_common(
+ NTSTATUS (*fget_acl_blob_fn)(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob),
+ NTSTATUS (*store_acl_blob_fsp_fn)(vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob),
+ const char *module_name,
+ vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *orig_psd);
+
+
+
+#endif
diff --git a/source3/modules/vfs_acl_tdb.c b/source3/modules/vfs_acl_tdb.c
new file mode 100644
index 0000000..bccb1ab
--- /dev/null
+++ b/source3/modules/vfs_acl_tdb.c
@@ -0,0 +1,387 @@
+/*
+ * Store Windows ACLs in a tdb.
+ *
+ * Copyright (C) Volker Lendecke, 2008
+ * Copyright (C) Jeremy Allison, 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "librpc/gen_ndr/xattr.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "auth.h"
+#include "util_tdb.h"
+#include "vfs_acl_common.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#define ACL_MODULE_NAME "acl_tdb"
+
+static unsigned int ref_count;
+static struct db_context *acl_db;
+
+/*******************************************************************
+ Open acl_db if not already open, increment ref count.
+*******************************************************************/
+
+static bool acl_tdb_init(void)
+{
+ char *dbname;
+
+ if (acl_db) {
+ ref_count++;
+ return true;
+ }
+
+ dbname = state_path(talloc_tos(), "file_ntacls.tdb");
+
+ if (dbname == NULL) {
+ errno = ENOSYS;
+ return false;
+ }
+
+ become_root();
+ acl_db = db_open(NULL, dbname, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ unbecome_root();
+
+ if (acl_db == NULL) {
+#if defined(ENOTSUP)
+ errno = ENOTSUP;
+#else
+ errno = ENOSYS;
+#endif
+ TALLOC_FREE(dbname);
+ return false;
+ }
+
+ ref_count++;
+ TALLOC_FREE(dbname);
+ return true;
+}
+
+/*******************************************************************
+ Lower ref count and close acl_db if zero.
+*******************************************************************/
+
+static void disconnect_acl_tdb(struct vfs_handle_struct *handle)
+{
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ ref_count--;
+ if (ref_count == 0) {
+ TALLOC_FREE(acl_db);
+ }
+}
+
+/*******************************************************************
+ Delete the tdb acl record for a file
+*******************************************************************/
+
+static NTSTATUS acl_tdb_delete(vfs_handle_struct *handle,
+ struct db_context *db,
+ SMB_STRUCT_STAT *psbuf)
+{
+ NTSTATUS status;
+ struct file_id id = vfs_file_id_from_sbuf(handle->conn, psbuf);
+ uint8_t id_buf[16];
+
+ /* For backwards compatibility only store the dev/inode. */
+ push_file_id_16((char *)id_buf, &id);
+
+ status = dbwrap_delete(db, make_tdb_data(id_buf, sizeof(id_buf)));
+ return status;
+}
+
+/*******************************************************************
+ Pull a security descriptor from an fsp into a DATA_BLOB from a tdb store.
+*******************************************************************/
+
+static NTSTATUS fget_acl_blob(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob)
+{
+ uint8_t id_buf[16];
+ TDB_DATA data;
+ struct file_id id;
+ struct db_context *db = acl_db;
+ NTSTATUS status = NT_STATUS_OK;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ id = vfs_file_id_from_sbuf(handle->conn, &fsp->fsp_name->st);
+
+ /* For backwards compatibility only store the dev/inode. */
+ push_file_id_16((char *)id_buf, &id);
+
+ status = dbwrap_fetch(db,
+ ctx,
+ make_tdb_data(id_buf, sizeof(id_buf)),
+ &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ pblob->data = data.dptr;
+ pblob->length = data.dsize;
+
+ DBG_DEBUG("returned %u bytes from file %s\n",
+ (unsigned int)data.dsize,
+ fsp_str_dbg(fsp));
+
+ if (pblob->length == 0 || pblob->data == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Store a DATA_BLOB into a tdb record given an fsp pointer.
+*******************************************************************/
+
+static NTSTATUS store_acl_blob_fsp(vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob)
+{
+ uint8_t id_buf[16];
+ struct file_id id;
+ TDB_DATA data = { .dptr = pblob->data, .dsize = pblob->length };
+ struct db_context *db = acl_db;
+ NTSTATUS status;
+
+ DEBUG(10,("store_acl_blob_fsp: storing blob length %u on file %s\n",
+ (unsigned int)pblob->length, fsp_str_dbg(fsp)));
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ id = vfs_file_id_from_sbuf(handle->conn, &fsp->fsp_name->st);
+
+ /* For backwards compatibility only store the dev/inode. */
+ push_file_id_16((char *)id_buf, &id);
+
+ status = dbwrap_store(
+ db, make_tdb_data(id_buf, sizeof(id_buf)), data, 0);
+ return status;
+}
+
+/*********************************************************************
+ On unlinkat we need to delete the tdb record (if using tdb).
+*********************************************************************/
+
+static int unlinkat_acl_tdb(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *smb_fname_tmp = NULL;
+ struct db_context *db = acl_db;
+ int ret = -1;
+
+ smb_fname_tmp = cp_smb_filename_nostream(talloc_tos(), smb_fname);
+ if (smb_fname_tmp == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ ret = vfs_stat(handle->conn, smb_fname_tmp);
+ if (ret == -1) {
+ goto out;
+ }
+
+ if (flags & AT_REMOVEDIR) {
+ ret = rmdir_acl_common(handle,
+ dirfsp,
+ smb_fname_tmp);
+ } else {
+ ret = unlink_acl_common(handle,
+ dirfsp,
+ smb_fname_tmp,
+ flags);
+ }
+
+ if (ret == -1) {
+ goto out;
+ }
+
+ acl_tdb_delete(handle, db, &smb_fname_tmp->st);
+ out:
+ return ret;
+}
+
+/*******************************************************************
+ Handle opening the storage tdb if so configured.
+*******************************************************************/
+
+static int connect_acl_tdb(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ bool ok;
+ struct acl_common_config *config = NULL;
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (!acl_tdb_init()) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ ok = init_acl_common_config(handle, ACL_MODULE_NAME);
+ if (!ok) {
+ DBG_ERR("init_acl_common_config failed\n");
+ return -1;
+ }
+
+ /* Ensure we have the parameters correct if we're
+ * using this module. */
+ DEBUG(2,("connect_acl_tdb: setting 'inherit acls = true' "
+ "'dos filemode = true' and "
+ "'force unknown acl user = true' for service %s\n",
+ service ));
+
+ lp_do_parameter(SNUM(handle->conn), "inherit acls", "true");
+ lp_do_parameter(SNUM(handle->conn), "dos filemode", "true");
+ lp_do_parameter(SNUM(handle->conn), "force unknown acl user", "true");
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return -1);
+
+ if (config->ignore_system_acls) {
+ mode_t create_mask = lp_create_mask(SNUM(handle->conn));
+ char *create_mask_str = NULL;
+
+ if ((create_mask & 0666) != 0666) {
+ create_mask |= 0666;
+ create_mask_str = talloc_asprintf(handle, "0%o",
+ create_mask);
+ if (create_mask_str == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ return -1;
+ }
+
+ DBG_NOTICE("setting 'create mask = %s'\n", create_mask_str);
+
+ lp_do_parameter (SNUM(handle->conn),
+ "create mask", create_mask_str);
+
+ TALLOC_FREE(create_mask_str);
+ }
+
+ DBG_NOTICE("setting 'directory mask = 0777', "
+ "'store dos attributes = yes' and all "
+ "'map ...' options to 'no'\n");
+
+ lp_do_parameter(SNUM(handle->conn), "directory mask", "0777");
+ lp_do_parameter(SNUM(handle->conn), "map archive", "no");
+ lp_do_parameter(SNUM(handle->conn), "map hidden", "no");
+ lp_do_parameter(SNUM(handle->conn), "map readonly", "no");
+ lp_do_parameter(SNUM(handle->conn), "map system", "no");
+ lp_do_parameter(SNUM(handle->conn), "store dos attributes",
+ "yes");
+ }
+
+ return 0;
+}
+
+/*********************************************************************
+ Remove a Windows ACL - we're setting the underlying POSIX ACL.
+*********************************************************************/
+
+static int sys_acl_set_fd_tdb(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ struct acl_common_fsp_ext *ext = (struct acl_common_fsp_ext *)
+ VFS_FETCH_FSP_EXTENSION(handle, fsp);
+ struct db_context *db = acl_db;
+ NTSTATUS status;
+ int ret;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle,
+ fsp,
+ type,
+ theacl);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (ext != NULL && ext->setting_nt_acl) {
+ return 0;
+ }
+
+ acl_tdb_delete(handle, db, &fsp->fsp_name->st);
+ return 0;
+}
+
+static NTSTATUS acl_tdb_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS status;
+ status = fget_nt_acl_common(fget_acl_blob, handle, fsp,
+ security_info, mem_ctx, ppdesc);
+ return status;
+}
+
+static NTSTATUS acl_tdb_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ NTSTATUS status;
+ status = fset_nt_acl_common(fget_acl_blob, store_acl_blob_fsp,
+ ACL_MODULE_NAME,
+ handle, fsp, security_info_sent, psd);
+ return status;
+}
+
+static struct vfs_fn_pointers vfs_acl_tdb_fns = {
+ .connect_fn = connect_acl_tdb,
+ .disconnect_fn = disconnect_acl_tdb,
+ .unlinkat_fn = unlinkat_acl_tdb,
+ .fchmod_fn = fchmod_acl_module_common,
+ .fget_nt_acl_fn = acl_tdb_fget_nt_acl,
+ .fset_nt_acl_fn = acl_tdb_fset_nt_acl,
+ .sys_acl_set_fd_fn = sys_acl_set_fd_tdb
+};
+
+static_decl_vfs;
+NTSTATUS vfs_acl_tdb_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "acl_tdb",
+ &vfs_acl_tdb_fns);
+}
diff --git a/source3/modules/vfs_acl_xattr.c b/source3/modules/vfs_acl_xattr.c
new file mode 100644
index 0000000..1a3ab34
--- /dev/null
+++ b/source3/modules/vfs_acl_xattr.c
@@ -0,0 +1,552 @@
+/*
+ * Store Windows ACLs in xattrs.
+ *
+ * Copyright (C) Volker Lendecke, 2008
+ * Copyright (C) Jeremy Allison, 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "librpc/gen_ndr/xattr.h"
+#include "auth.h"
+#include "vfs_acl_common.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/tevent_unix.h"
+
+/* Pull in the common functions. */
+#define ACL_MODULE_NAME "acl_xattr"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+/*******************************************************************
+ Pull a security descriptor into a DATA_BLOB from a xattr.
+*******************************************************************/
+
+static ssize_t getxattr_do(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const char *xattr_name,
+ uint8_t *val,
+ size_t size)
+{
+ ssize_t sizeret;
+ int saved_errno = 0;
+
+ become_root();
+ sizeret = SMB_VFS_FGETXATTR(fsp, xattr_name, val, size);
+ if (sizeret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+
+ return sizeret;
+}
+
+static NTSTATUS fget_acl_blob(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob)
+{
+ size_t size = 4096;
+ uint8_t *val = NULL;
+ uint8_t *tmp;
+ ssize_t sizeret;
+
+ ZERO_STRUCTP(pblob);
+
+ again:
+
+ tmp = talloc_realloc(ctx, val, uint8_t, size);
+ if (tmp == NULL) {
+ TALLOC_FREE(val);
+ return NT_STATUS_NO_MEMORY;
+ }
+ val = tmp;
+
+ sizeret =
+ getxattr_do(handle, fsp, XATTR_NTACL_NAME, val, size);
+
+ if (sizeret >= 0) {
+ pblob->data = val;
+ pblob->length = sizeret;
+ return NT_STATUS_OK;
+ }
+
+ if (errno != ERANGE) {
+ goto err;
+ }
+
+ /* Too small, try again. */
+ sizeret =
+ getxattr_do(handle, fsp, XATTR_NTACL_NAME, NULL, 0);
+ if (sizeret < 0) {
+ goto err;
+ }
+
+ if (size < sizeret) {
+ size = sizeret;
+ }
+
+ if (size > 65536) {
+ /* Max ACL size is 65536 bytes. */
+ errno = ERANGE;
+ goto err;
+ }
+
+ goto again;
+ err:
+ /* Real error - exit here. */
+ TALLOC_FREE(val);
+ return map_nt_error_from_unix(errno);
+}
+
+/*******************************************************************
+ Store a DATA_BLOB into an xattr given an fsp pointer.
+*******************************************************************/
+
+static NTSTATUS store_acl_blob_fsp(vfs_handle_struct *handle,
+ files_struct *fsp,
+ DATA_BLOB *pblob)
+{
+ int ret;
+ int saved_errno = 0;
+
+ DEBUG(10,("store_acl_blob_fsp: storing blob length %u on file %s\n",
+ (unsigned int)pblob->length, fsp_str_dbg(fsp)));
+
+ become_root();
+ ret = SMB_VFS_FSETXATTR(fsp, XATTR_NTACL_NAME,
+ pblob->data, pblob->length, 0);
+ if (ret) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+ if (ret) {
+ DEBUG(5, ("store_acl_blob_fsp: setting attr failed for file %s"
+ "with error %s\n",
+ fsp_str_dbg(fsp),
+ strerror(saved_errno) ));
+ errno = saved_errno;
+ return map_nt_error_from_unix(saved_errno);
+ }
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ Remove a Windows ACL - we're setting the underlying POSIX ACL.
+*********************************************************************/
+
+static int sys_acl_set_fd_xattr(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ struct acl_common_fsp_ext *ext = (struct acl_common_fsp_ext *)
+ VFS_FETCH_FSP_EXTENSION(handle, fsp);
+ int ret;
+
+ ret = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle,
+ fsp,
+ type,
+ theacl);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (ext != NULL && ext->setting_nt_acl) {
+ return 0;
+ }
+
+ become_root();
+ SMB_VFS_FREMOVEXATTR(fsp, XATTR_NTACL_NAME);
+ unbecome_root();
+
+ return 0;
+}
+
+static int connect_acl_xattr(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ const char *security_acl_xattr_name = NULL;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ bool ok;
+ struct acl_common_config *config = NULL;
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ ok = init_acl_common_config(handle, ACL_MODULE_NAME);
+ if (!ok) {
+ DBG_ERR("init_acl_common_config failed\n");
+ return -1;
+ }
+
+ /* Ensure we have the parameters correct if we're
+ * using this module. */
+ DEBUG(2,("connect_acl_xattr: setting 'inherit acls = true' "
+ "'dos filemode = true' and "
+ "'force unknown acl user = true' for service %s\n",
+ service ));
+
+ lp_do_parameter(SNUM(handle->conn), "inherit acls", "true");
+ lp_do_parameter(SNUM(handle->conn), "dos filemode", "true");
+ lp_do_parameter(SNUM(handle->conn), "force unknown acl user", "true");
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return -1);
+
+ if (config->ignore_system_acls) {
+ mode_t create_mask = lp_create_mask(SNUM(handle->conn));
+ char *create_mask_str = NULL;
+
+ if ((create_mask & 0666) != 0666) {
+ create_mask |= 0666;
+ create_mask_str = talloc_asprintf(handle, "0%o",
+ create_mask);
+ if (create_mask_str == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ return -1;
+ }
+
+ DBG_NOTICE("setting 'create mask = %s'\n", create_mask_str);
+
+ lp_do_parameter (SNUM(handle->conn),
+ "create mask", create_mask_str);
+
+ TALLOC_FREE(create_mask_str);
+ }
+
+ DBG_NOTICE("setting 'directory mask = 0777', "
+ "'store dos attributes = yes' and all "
+ "'map ...' options to 'no'\n");
+
+ lp_do_parameter(SNUM(handle->conn), "directory mask", "0777");
+ lp_do_parameter(SNUM(handle->conn), "map archive", "no");
+ lp_do_parameter(SNUM(handle->conn), "map hidden", "no");
+ lp_do_parameter(SNUM(handle->conn), "map readonly", "no");
+ lp_do_parameter(SNUM(handle->conn), "map system", "no");
+ lp_do_parameter(SNUM(handle->conn), "store dos attributes",
+ "yes");
+ }
+
+ security_acl_xattr_name = lp_parm_const_string(SNUM(handle->conn),
+ "acl_xattr",
+ "security_acl_name",
+ NULL);
+ if (security_acl_xattr_name != NULL) {
+ config->security_acl_xattr_name = talloc_strdup(config, security_acl_xattr_name);
+ if (config->security_acl_xattr_name == NULL) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int acl_xattr_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret;
+
+ if (flags & AT_REMOVEDIR) {
+ ret = rmdir_acl_common(handle,
+ dirfsp,
+ smb_fname);
+ } else {
+ ret = unlink_acl_common(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ }
+ return ret;
+}
+
+static NTSTATUS acl_xattr_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS status;
+ status = fget_nt_acl_common(fget_acl_blob, handle, fsp,
+ security_info, mem_ctx, ppdesc);
+ return status;
+}
+
+static NTSTATUS acl_xattr_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ NTSTATUS status;
+ status = fset_nt_acl_common(fget_acl_blob, store_acl_blob_fsp,
+ ACL_MODULE_NAME,
+ handle, fsp, security_info_sent, psd);
+ return status;
+}
+
+struct acl_xattr_getxattrat_state {
+ struct vfs_aio_state aio_state;
+ ssize_t xattr_size;
+ uint8_t *xattr_value;
+};
+
+static void acl_xattr_getxattrat_done(struct tevent_req *subreq);
+
+static struct tevent_req *acl_xattr_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct acl_xattr_getxattrat_state *state = NULL;
+ struct acl_common_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return NULL);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct acl_xattr_getxattrat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (strequal(xattr_name, config->security_acl_xattr_name)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+ if (config->security_acl_xattr_name != NULL &&
+ strequal(xattr_name, XATTR_NTACL_NAME))
+ {
+ xattr_name = config->security_acl_xattr_name;
+ }
+
+ subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state,
+ ev,
+ handle,
+ dirfsp,
+ smb_fname,
+ xattr_name,
+ alloc_hint);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, acl_xattr_getxattrat_done, req);
+
+ return req;
+}
+
+static void acl_xattr_getxattrat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct acl_xattr_getxattrat_state *state = tevent_req_data(
+ req, struct acl_xattr_getxattrat_state);
+
+ state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq,
+ &state->aio_state,
+ state,
+ &state->xattr_value);
+ TALLOC_FREE(subreq);
+ if (state->xattr_size == -1) {
+ tevent_req_error(req, state->aio_state.error);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t acl_xattr_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct acl_xattr_getxattrat_state *state = tevent_req_data(
+ req, struct acl_xattr_getxattrat_state);
+ ssize_t xattr_size;
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *aio_state = state->aio_state;
+ xattr_size = state->xattr_size;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+static ssize_t acl_xattr_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ void *value,
+ size_t size)
+{
+ struct acl_common_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return -1);
+
+ if (strequal(name, config->security_acl_xattr_name)) {
+ errno = EACCES;
+ return -1;
+ }
+ if (config->security_acl_xattr_name != NULL &&
+ strequal(name, XATTR_NTACL_NAME))
+ {
+ name = config->security_acl_xattr_name;
+ }
+
+ return SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size);
+}
+
+static ssize_t acl_xattr_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ char *listbuf,
+ size_t bufsize)
+{
+ struct acl_common_config *config = NULL;
+ ssize_t size;
+ char *p = NULL;
+ size_t nlen, consumed;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return -1);
+
+ size = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, listbuf, bufsize);
+ if (size < 0) {
+ return -1;
+ }
+
+ p = listbuf;
+ while (p - listbuf < size) {
+ nlen = strlen(p) + 1;
+ if (strequal(p, config->security_acl_xattr_name)) {
+ break;
+ }
+ p += nlen;
+ }
+ if (p - listbuf >= size) {
+ /* No match */
+ return size;
+ }
+
+ /*
+ * The consumed helper variable just makes the math
+ * a bit more digestible.
+ */
+ consumed = p - listbuf;
+ if (consumed + nlen < size) {
+ /* If not the last name move, else just skip */
+ memmove(p, p + nlen, size - consumed - nlen);
+ }
+ size -= nlen;
+
+ return size;
+}
+
+static int acl_xattr_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name)
+{
+ struct acl_common_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return -1);
+
+ if (strequal(name, config->security_acl_xattr_name)) {
+ errno = EACCES;
+ return -1;
+ }
+ if (config->security_acl_xattr_name != NULL &&
+ strequal(name, XATTR_NTACL_NAME))
+ {
+ name = config->security_acl_xattr_name;
+ }
+
+ return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
+}
+
+static int acl_xattr_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ struct acl_common_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct acl_common_config,
+ return -1);
+
+ if (strequal(name, config->security_acl_xattr_name)) {
+ errno = EACCES;
+ return -1;
+ }
+ if (config->security_acl_xattr_name != NULL &&
+ strequal(name, XATTR_NTACL_NAME))
+ {
+ name = config->security_acl_xattr_name;
+ }
+
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags);
+}
+
+static struct vfs_fn_pointers vfs_acl_xattr_fns = {
+ .connect_fn = connect_acl_xattr,
+ .unlinkat_fn = acl_xattr_unlinkat,
+ .fchmod_fn = fchmod_acl_module_common,
+ .fget_nt_acl_fn = acl_xattr_fget_nt_acl,
+ .fset_nt_acl_fn = acl_xattr_fset_nt_acl,
+ .sys_acl_set_fd_fn = sys_acl_set_fd_xattr,
+ .getxattrat_send_fn = acl_xattr_getxattrat_send,
+ .getxattrat_recv_fn = acl_xattr_getxattrat_recv,
+ .fgetxattr_fn = acl_xattr_fgetxattr,
+ .flistxattr_fn = acl_xattr_flistxattr,
+ .fremovexattr_fn = acl_xattr_fremovexattr,
+ .fsetxattr_fn = acl_xattr_fsetxattr,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_acl_xattr_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "acl_xattr",
+ &vfs_acl_xattr_fns);
+}
diff --git a/source3/modules/vfs_afsacl.c b/source3/modules/vfs_afsacl.c
new file mode 100644
index 0000000..3dc80d3
--- /dev/null
+++ b/source3/modules/vfs_afsacl.c
@@ -0,0 +1,1085 @@
+/*
+ * Convert AFS acls to NT acls and vice versa.
+ *
+ * Copyright (C) Volker Lendecke, 2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "../librpc/gen_ndr/lsa.h"
+#include "../libcli/security/security.h"
+#include "../libcli/security/dom_sid.h"
+#include "passdb.h"
+#include "lib/afs/afs_settoken.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#include <afs/stds.h>
+#include <afs/afs_args.h>
+#include <afs/venus.h>
+#include <afs/prs_fs.h>
+
+#define MAXSIZE 2049
+
+extern const struct dom_sid global_sid_World;
+extern const struct dom_sid global_sid_Builtin_Administrators;
+extern const struct dom_sid global_sid_Builtin_Backup_Operators;
+extern const struct dom_sid global_sid_Authenticated_Users;
+extern const struct dom_sid global_sid_NULL;
+
+static char space_replacement = '%';
+
+/* Do we expect SIDs as pts names? */
+static bool sidpts;
+
+struct afs_ace {
+ bool positive;
+ char *name;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ uint32_t rights;
+ struct afs_ace *next;
+};
+
+struct afs_acl {
+ TALLOC_CTX *ctx;
+ int type;
+ int num_aces;
+ struct afs_ace *acelist;
+};
+
+struct afs_iob {
+ char *in, *out;
+ uint16_t in_size, out_size;
+};
+
+
+static bool init_afs_acl(struct afs_acl *acl)
+{
+ ZERO_STRUCT(*acl);
+ acl->ctx = talloc_init("afs_acl");
+ if (acl->ctx == NULL) {
+ DEBUG(10, ("Could not init afs_acl\n"));
+ return false;
+ }
+ return true;
+}
+
+static void free_afs_acl(struct afs_acl *acl)
+{
+ if (acl->ctx != NULL)
+ talloc_destroy(acl->ctx);
+ acl->ctx = NULL;
+ acl->num_aces = 0;
+ acl->acelist = NULL;
+}
+
+static struct afs_ace *clone_afs_ace(TALLOC_CTX *mem_ctx, struct afs_ace *ace)
+{
+ struct afs_ace *result = talloc(mem_ctx, struct afs_ace);
+
+ if (result == NULL)
+ return NULL;
+
+ *result = *ace;
+
+ result->next = NULL;
+ result->name = talloc_strdup(mem_ctx, ace->name);
+
+ if (result->name == NULL) {
+ return NULL;
+ }
+
+ return result;
+}
+
+static struct afs_ace *new_afs_ace(TALLOC_CTX *mem_ctx,
+ bool positive,
+ const char *name, uint32_t rights)
+{
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ struct afs_ace *result;
+
+ if (strcmp(name, "system:administrators") == 0) {
+
+ sid_copy(&sid, &global_sid_Builtin_Administrators);
+ type = SID_NAME_ALIAS;
+
+ } else if (strcmp(name, "system:anyuser") == 0) {
+
+ sid_copy(&sid, &global_sid_World);
+ type = SID_NAME_ALIAS;
+
+ } else if (strcmp(name, "system:authuser") == 0) {
+
+ sid_copy(&sid, &global_sid_Authenticated_Users);
+ type = SID_NAME_WKN_GRP;
+
+ } else if (strcmp(name, "system:backup") == 0) {
+
+ sid_copy(&sid, &global_sid_Builtin_Backup_Operators);
+ type = SID_NAME_ALIAS;
+
+ } else if (sidpts) {
+ /* All PTS users/groups are expressed as SIDs */
+
+ sid_copy(&sid, &global_sid_NULL);
+ type = SID_NAME_UNKNOWN;
+
+ if (string_to_sid(&sid, name)) {
+ const char *user, *domain;
+ /* We have to find the type, look up the SID */
+ lookup_sid(talloc_tos(), &sid,
+ &domain, &user, &type);
+ }
+
+ } else {
+
+ const char *domain, *uname;
+ char *p;
+
+ p = strchr_m(name, *lp_winbind_separator());
+ if (p != NULL) {
+ *p = '\\';
+ }
+
+ if (!lookup_name(talloc_tos(), name, LOOKUP_NAME_ALL,
+ &domain, &uname, &sid, &type)) {
+ DEBUG(10, ("Could not find AFS user %s\n", name));
+
+ sid_copy(&sid, &global_sid_NULL);
+ type = SID_NAME_UNKNOWN;
+
+ }
+ }
+
+ result = talloc(mem_ctx, struct afs_ace);
+
+ if (result == NULL) {
+ DEBUG(0, ("Could not talloc AFS ace\n"));
+ return NULL;
+ }
+
+ result->name = talloc_strdup(mem_ctx, name);
+ if (result->name == NULL) {
+ DEBUG(0, ("Could not talloc AFS ace name\n"));
+ return NULL;
+ }
+
+ result->sid = sid;
+ result->type = type;
+
+ result->positive = positive;
+ result->rights = rights;
+
+ return result;
+}
+
+static void add_afs_ace(struct afs_acl *acl,
+ bool positive,
+ const char *name, uint32_t rights)
+{
+ struct afs_ace *ace;
+
+ for (ace = acl->acelist; ace != NULL; ace = ace->next) {
+ if ((ace->positive == positive) &&
+ (strequal(ace->name, name))) {
+ ace->rights |= rights;
+ return;
+ }
+ }
+
+ ace = new_afs_ace(acl->ctx, positive, name, rights);
+
+ ace->next = acl->acelist;
+ acl->acelist = ace;
+
+ acl->num_aces += 1;
+
+ DEBUG(10, ("add_afs_ace: Added %s entry for %s with rights %d\n",
+ ace->positive?"positive":"negative",
+ ace->name, ace->rights));
+}
+
+/* AFS ACLs in string form are a long string of fields delimited with \n.
+ *
+ * First line: Number of positive entries
+ * Second line: Number of negative entries
+ * Third and following lines: The entries themselves
+ *
+ * An ACE is a line of two fields, delimited by \t.
+ *
+ * First field: Name
+ * Second field: Rights
+ */
+
+static bool parse_afs_acl(struct afs_acl *acl, const char *acl_str)
+{
+ int nplus, nminus;
+ int aces;
+
+ char str[MAXSIZE];
+ char *p = str;
+
+ strlcpy(str, acl_str, MAXSIZE);
+
+ if (sscanf(p, "%d", &nplus) != 1)
+ return false;
+
+ DEBUG(10, ("Found %d positive entries\n", nplus));
+
+ if ((p = strchr(p, '\n')) == NULL)
+ return false;
+ p += 1;
+
+ if (sscanf(p, "%d", &nminus) != 1)
+ return false;
+
+ DEBUG(10, ("Found %d negative entries\n", nminus));
+
+ if ((p = strchr(p, '\n')) == NULL)
+ return false;
+ p += 1;
+
+ for (aces = nplus+nminus; aces > 0; aces--)
+ {
+
+ const char *namep;
+ fstring name;
+ uint32_t rights;
+ char *space;
+
+ namep = p;
+
+ if ((p = strchr(p, '\t')) == NULL)
+ return false;
+ *p = '\0';
+ p += 1;
+
+ if (sscanf(p, "%d", &rights) != 1)
+ return false;
+
+ if ((p = strchr(p, '\n')) == NULL)
+ return false;
+ p += 1;
+
+ fstrcpy(name, namep);
+
+ while ((space = strchr_m(name, space_replacement)) != NULL)
+ *space = ' ';
+
+ add_afs_ace(acl, nplus>0, name, rights);
+
+ nplus -= 1;
+ }
+
+ return true;
+}
+
+static bool unparse_afs_acl(struct afs_acl *acl, char *acl_str)
+{
+ /* TODO: String length checks!!!! */
+
+ int positives = 0;
+ int negatives = 0;
+ fstring line;
+ struct afs_ace *ace = acl->acelist;
+
+ *acl_str = 0;
+
+ while (ace != NULL) {
+ if (ace->positive)
+ positives++;
+ else
+ negatives++;
+ ace = ace->next;
+ }
+
+ fstr_sprintf(line, "%d\n", positives);
+ if (strlcat(acl_str, line, MAXSIZE) >= MAXSIZE) {
+ return false;
+ }
+
+ fstr_sprintf(line, "%d\n", negatives);
+ if (strlcat(acl_str, line, MAXSIZE) >= MAXSIZE) {
+ return false;
+ }
+
+ ace = acl->acelist;
+
+ while (ace != NULL) {
+ fstr_sprintf(line, "%s\t%d\n", ace->name, ace->rights);
+ if (strlcat(acl_str, line, MAXSIZE) >= MAXSIZE) {
+ return false;
+ }
+ ace = ace->next;
+ }
+ return true;
+}
+
+static uint32_t afs_to_nt_file_rights(uint32_t rights)
+{
+ uint32_t result = 0;
+
+ if (rights & PRSFS_READ)
+ result |= FILE_READ_DATA | FILE_READ_EA |
+ FILE_EXECUTE | FILE_READ_ATTRIBUTES |
+ READ_CONTROL_ACCESS | SYNCHRONIZE_ACCESS;
+
+ if (rights & PRSFS_WRITE)
+ result |= FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES |
+ FILE_WRITE_EA | FILE_APPEND_DATA;
+
+ if (rights & PRSFS_LOCK)
+ result |= WRITE_OWNER_ACCESS;
+
+ if (rights & PRSFS_DELETE)
+ result |= DELETE_ACCESS;
+
+ return result;
+}
+
+static void afs_to_nt_dir_rights(uint32_t afs_rights, uint32_t *nt_rights,
+ uint8_t *flag)
+{
+ *nt_rights = 0;
+ *flag = SEC_ACE_FLAG_OBJECT_INHERIT |
+ SEC_ACE_FLAG_CONTAINER_INHERIT;
+
+ if (afs_rights & PRSFS_INSERT)
+ *nt_rights |= FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY;
+
+ if (afs_rights & PRSFS_LOOKUP)
+ *nt_rights |= FILE_READ_DATA | FILE_READ_EA |
+ FILE_EXECUTE | FILE_READ_ATTRIBUTES |
+ READ_CONTROL_ACCESS | SYNCHRONIZE_ACCESS;
+
+ if (afs_rights & PRSFS_WRITE)
+ *nt_rights |= FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA |
+ FILE_APPEND_DATA | FILE_WRITE_EA;
+
+ if ((afs_rights & (PRSFS_INSERT|PRSFS_LOOKUP|PRSFS_DELETE)) ==
+ (PRSFS_INSERT|PRSFS_LOOKUP|PRSFS_DELETE))
+ *nt_rights |= FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA |
+ GENERIC_WRITE_ACCESS;
+
+ if (afs_rights & PRSFS_DELETE)
+ *nt_rights |= DELETE_ACCESS;
+
+ if (afs_rights & PRSFS_ADMINISTER)
+ *nt_rights |= FILE_DELETE_CHILD | WRITE_DAC_ACCESS |
+ WRITE_OWNER_ACCESS;
+
+ if ( (afs_rights & PRSFS_LOOKUP) ==
+ (afs_rights & (PRSFS_LOOKUP|PRSFS_READ)) ) {
+ /* Only lookup right */
+ *flag = SEC_ACE_FLAG_CONTAINER_INHERIT;
+ }
+}
+
+#define AFS_FILE_RIGHTS (PRSFS_READ|PRSFS_WRITE|PRSFS_LOCK)
+#define AFS_DIR_RIGHTS (PRSFS_INSERT|PRSFS_LOOKUP|PRSFS_DELETE|PRSFS_ADMINISTER)
+
+static void split_afs_acl(struct afs_acl *acl,
+ struct afs_acl *dir_acl,
+ struct afs_acl *file_acl)
+{
+ struct afs_ace *ace;
+
+ init_afs_acl(dir_acl);
+ init_afs_acl(file_acl);
+
+ for (ace = acl->acelist; ace != NULL; ace = ace->next) {
+ if (ace->rights & AFS_FILE_RIGHTS) {
+ add_afs_ace(file_acl, ace->positive, ace->name,
+ ace->rights & AFS_FILE_RIGHTS);
+ }
+
+ if (ace->rights & AFS_DIR_RIGHTS) {
+ add_afs_ace(dir_acl, ace->positive, ace->name,
+ ace->rights & AFS_DIR_RIGHTS);
+ }
+ }
+}
+
+static bool same_principal(struct afs_ace *x, struct afs_ace *y)
+{
+ return ( (x->positive == y->positive) &&
+ (dom_sid_compare(&x->sid, &y->sid) == 0) );
+}
+
+static void merge_afs_acls(struct afs_acl *dir_acl,
+ struct afs_acl *file_acl,
+ struct afs_acl *target)
+{
+ struct afs_ace *ace;
+
+ init_afs_acl(target);
+
+ for (ace = dir_acl->acelist; ace != NULL; ace = ace->next) {
+ struct afs_ace *file_ace;
+ bool found = false;
+
+ for (file_ace = file_acl->acelist;
+ file_ace != NULL;
+ file_ace = file_ace->next) {
+ if (!same_principal(ace, file_ace))
+ continue;
+
+ add_afs_ace(target, ace->positive, ace->name,
+ ace->rights | file_ace->rights);
+ found = true;
+ break;
+ }
+ if (!found)
+ add_afs_ace(target, ace->positive, ace->name,
+ ace->rights);
+ }
+
+ for (ace = file_acl->acelist; ace != NULL; ace = ace->next) {
+ struct afs_ace *dir_ace;
+ bool already_seen = false;
+
+ for (dir_ace = dir_acl->acelist;
+ dir_ace != NULL;
+ dir_ace = dir_ace->next) {
+ if (!same_principal(ace, dir_ace))
+ continue;
+ already_seen = true;
+ break;
+ }
+ if (!already_seen)
+ add_afs_ace(target, ace->positive, ace->name,
+ ace->rights);
+ }
+}
+
+#define PERMS_READ 0x001200a9
+#define PERMS_CHANGE 0x001301bf
+#define PERMS_FULL 0x001f01ff
+
+static struct static_dir_ace_mapping {
+ uint8_t type;
+ uint8_t flags;
+ uint32_t mask;
+ uint32_t afs_rights;
+} ace_mappings[] = {
+
+ /* Full control */
+ { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT,
+ PERMS_FULL, 127 /* rlidwka */ },
+
+ /* Change (write) */
+ { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT,
+ PERMS_CHANGE, 63 /* rlidwk */ },
+
+ /* Read (including list folder content) */
+ { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT,
+ PERMS_READ, 9 /* rl */ },
+
+ /* Read without list folder content -- same as "l" */
+ { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT,
+ 0x00120089, 8 /* l */ },
+
+ /* some stupid workaround for preventing fallbacks */
+ { 0, 0x3, 0x0012019F, 9 /* rl */ },
+ { 0, 0x13, PERMS_FULL, 127 /* full */ },
+
+ /* read, delete and execute access plus synchronize */
+ { 0, 0x3, 0x001300A9, 9 /* should be rdl, set to rl */},
+ /* classical read list */
+ { 0, 0x13, 0x001200A9, 9 /* rl */},
+ /* almost full control, no delete */
+ { 0, 0x13, PERMS_CHANGE, 63 /* rwidlk */},
+
+ /* List folder */
+ { 0, SEC_ACE_FLAG_CONTAINER_INHERIT,
+ PERMS_READ, 8 /* l */ },
+
+ /* FULL without inheritance -- in all cases here we also get
+ the corresponding INHERIT_ONLY ACE in the same ACL */
+ { 0, 0, PERMS_FULL, 127 /* rlidwka */ },
+
+ /* FULL inherit only -- counterpart to previous one */
+ { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY,
+ PERMS_FULL | SEC_GENERIC_WRITE, 127 /* rlidwka */ },
+
+ /* CHANGE without inheritance -- in all cases here we also get
+ the corresponding INHERIT_ONLY ACE in the same ACL */
+ { 0, 0, PERMS_CHANGE, 63 /* rlidwk */ },
+
+ /* CHANGE inherit only -- counterpart to previous one */
+ { 0, SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_INHERIT_ONLY,
+ PERMS_CHANGE | SEC_GENERIC_WRITE, 63 /* rlidwk */ },
+
+ /* End marker, hopefully there's no afs right 9999 :-) */
+ { 0, 0, 0, 9999 }
+};
+
+static uint32_t nt_to_afs_dir_rights(const char *filename, const struct security_ace *ace)
+{
+ uint32_t result = 0;
+ uint32_t rights = ace->access_mask;
+ uint8_t flags = ace->flags;
+
+ struct static_dir_ace_mapping *m;
+
+ for (m = &ace_mappings[0]; m->afs_rights != 9999; m++) {
+ if ( (ace->type == m->type) &&
+ (ace->flags == m->flags) &&
+ (ace->access_mask == m->mask) )
+ return m->afs_rights;
+ }
+
+ DEBUG(1, ("AFSACL FALLBACK: 0x%X 0x%X 0x%X %s %X\n",
+ ace->type, ace->flags, ace->access_mask, filename, rights));
+
+ if (rights & (GENERIC_ALL_ACCESS|WRITE_DAC_ACCESS)) {
+ result |= PRSFS_READ | PRSFS_WRITE | PRSFS_INSERT |
+ PRSFS_LOOKUP | PRSFS_DELETE | PRSFS_LOCK |
+ PRSFS_ADMINISTER;
+ }
+
+ if (rights & (GENERIC_READ_ACCESS|FILE_READ_DATA)) {
+ result |= PRSFS_LOOKUP;
+ if (flags & SEC_ACE_FLAG_OBJECT_INHERIT) {
+ result |= PRSFS_READ;
+ }
+ }
+
+ if (rights & (GENERIC_WRITE_ACCESS|FILE_WRITE_DATA)) {
+ result |= PRSFS_INSERT | PRSFS_DELETE;
+ if (flags & SEC_ACE_FLAG_OBJECT_INHERIT) {
+ result |= PRSFS_WRITE | PRSFS_LOCK;
+ }
+ }
+
+ return result;
+}
+
+static uint32_t nt_to_afs_file_rights(const char *filename, const struct security_ace *ace)
+{
+ uint32_t result = 0;
+ uint32_t rights = ace->access_mask;
+
+ if (rights & (GENERIC_READ_ACCESS|FILE_READ_DATA)) {
+ result |= PRSFS_READ;
+ }
+
+ if (rights & (GENERIC_WRITE_ACCESS|FILE_WRITE_DATA)) {
+ result |= PRSFS_WRITE | PRSFS_LOCK;
+ }
+
+ return result;
+}
+
+static size_t afs_to_nt_acl_common(struct afs_acl *afs_acl,
+ SMB_STRUCT_STAT *psbuf,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ struct security_ace *nt_ace_list;
+ struct dom_sid owner_sid, group_sid;
+ struct security_acl *psa = NULL;
+ int good_aces;
+ size_t sd_size;
+
+ struct afs_ace *afs_ace;
+
+ uid_to_sid(&owner_sid, psbuf->st_ex_uid);
+ gid_to_sid(&group_sid, psbuf->st_ex_gid);
+
+ if (afs_acl->num_aces) {
+ nt_ace_list = talloc_array(mem_ctx, struct security_ace, afs_acl->num_aces);
+
+ if (nt_ace_list == NULL)
+ return 0;
+ } else {
+ nt_ace_list = NULL;
+ }
+
+ afs_ace = afs_acl->acelist;
+ good_aces = 0;
+
+ while (afs_ace != NULL) {
+ uint32_t nt_rights;
+ uint8_t flag = SEC_ACE_FLAG_OBJECT_INHERIT |
+ SEC_ACE_FLAG_CONTAINER_INHERIT;
+
+ if (afs_ace->type == SID_NAME_UNKNOWN) {
+ DEBUG(10, ("Ignoring unknown name %s\n",
+ afs_ace->name));
+ afs_ace = afs_ace->next;
+ continue;
+ }
+
+ if (S_ISDIR(psbuf->st_ex_mode))
+ afs_to_nt_dir_rights(afs_ace->rights, &nt_rights,
+ &flag);
+ else
+ nt_rights = afs_to_nt_file_rights(afs_ace->rights);
+
+ init_sec_ace(&nt_ace_list[good_aces++], &(afs_ace->sid),
+ SEC_ACE_TYPE_ACCESS_ALLOWED, nt_rights, flag);
+ afs_ace = afs_ace->next;
+ }
+
+ psa = make_sec_acl(mem_ctx, NT4_ACL_REVISION,
+ good_aces, nt_ace_list);
+ if (psa == NULL)
+ return 0;
+
+ *ppdesc = make_sec_desc(mem_ctx, SD_REVISION,
+ SEC_DESC_SELF_RELATIVE,
+ (security_info & SECINFO_OWNER)
+ ? &owner_sid : NULL,
+ (security_info & SECINFO_GROUP)
+ ? &group_sid : NULL,
+ NULL, psa, &sd_size);
+
+ return sd_size;
+}
+
+static size_t afs_to_nt_acl(struct afs_acl *afs_acl,
+ struct connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ int ret;
+
+ /*
+ * We can directly use SMB_VFS_STAT here, as if this was a
+ * POSIX call on a symlink, we've already refused it.
+ * For a Windows acl mapped call on a symlink, we want to follow
+ * it.
+ */
+ /* Get the stat struct for the owner info. */
+ ret = SMB_VFS_STAT(conn, smb_fname);
+ if (ret == -1) {
+ return 0;
+ }
+
+ return afs_to_nt_acl_common(afs_acl, &smb_fname->st, security_info,
+ mem_ctx, ppdesc);
+}
+
+static size_t afs_fto_nt_acl(struct afs_acl *afs_acl,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ SMB_STRUCT_STAT sbuf;
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /* Get the stat struct for the owner info. */
+ return afs_to_nt_acl(afs_acl, fsp->conn, fsp->fsp_name,
+ security_info, mem_ctx, ppdesc);
+ }
+
+ if(SMB_VFS_FSTAT(fsp, &sbuf) != 0) {
+ return 0;
+ }
+
+ return afs_to_nt_acl_common(afs_acl, &sbuf, security_info,
+ mem_ctx, ppdesc);
+}
+
+static bool mappable_sid(const struct dom_sid *sid)
+{
+ struct dom_sid domain_sid;
+
+ if (dom_sid_compare(sid, &global_sid_Builtin_Administrators) == 0)
+ return true;
+
+ if (dom_sid_compare(sid, &global_sid_World) == 0)
+ return true;
+
+ if (dom_sid_compare(sid, &global_sid_Authenticated_Users) == 0)
+ return true;
+
+ if (dom_sid_compare(sid, &global_sid_Builtin_Backup_Operators) == 0)
+ return true;
+
+ string_to_sid(&domain_sid, "S-1-5-21");
+
+ if (dom_sid_compare_domain(sid, &domain_sid) == 0)
+ return true;
+
+ return false;
+}
+
+static bool nt_to_afs_acl(const char *filename,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd,
+ uint32_t (*nt_to_afs_rights)(const char *filename,
+ const struct security_ace *ace),
+ struct afs_acl *afs_acl)
+{
+ const struct security_acl *dacl;
+ int i;
+
+ /* Currently we *only* look at the dacl */
+
+ if (((security_info_sent & SECINFO_DACL) == 0) ||
+ (psd->dacl == NULL))
+ return true;
+
+ if (!init_afs_acl(afs_acl))
+ return false;
+
+ dacl = psd->dacl;
+
+ for (i = 0; i < dacl->num_aces; i++) {
+ const struct security_ace *ace = &(dacl->aces[i]);
+ const char *dom_name, *name;
+ enum lsa_SidType name_type;
+ char *p;
+
+ if (ace->type != SEC_ACE_TYPE_ACCESS_ALLOWED) {
+ /* First cut: Only positive ACEs */
+ return false;
+ }
+
+ if (!mappable_sid(&ace->trustee)) {
+ struct dom_sid_buf buf;
+ DEBUG(10, ("Ignoring unmappable SID %s\n",
+ dom_sid_str_buf(&ace->trustee, &buf)));
+ continue;
+ }
+
+ if (dom_sid_compare(&ace->trustee,
+ &global_sid_Builtin_Administrators) == 0) {
+
+ name = "system:administrators";
+
+ } else if (dom_sid_compare(&ace->trustee,
+ &global_sid_World) == 0) {
+
+ name = "system:anyuser";
+
+ } else if (dom_sid_compare(&ace->trustee,
+ &global_sid_Authenticated_Users) == 0) {
+
+ name = "system:authuser";
+
+ } else if (dom_sid_compare(&ace->trustee,
+ &global_sid_Builtin_Backup_Operators)
+ == 0) {
+
+ name = "system:backup";
+
+ } else {
+
+ if (!lookup_sid(talloc_tos(), &ace->trustee,
+ &dom_name, &name, &name_type)) {
+ struct dom_sid_buf buf;
+ DEBUG(1, ("AFSACL: Could not lookup SID %s on file %s\n",
+ dom_sid_str_buf(&ace->trustee, &buf),
+ filename));
+ continue;
+ }
+
+ if ( (name_type == SID_NAME_USER) ||
+ (name_type == SID_NAME_DOM_GRP) ||
+ (name_type == SID_NAME_ALIAS) ) {
+ char *tmp;
+ tmp = talloc_asprintf(talloc_tos(), "%s%s%s",
+ dom_name, lp_winbind_separator(),
+ name);
+ if (tmp == NULL) {
+ return false;
+ }
+ if (!strlower_m(tmp)) {
+ return false;
+ }
+ name = tmp;
+ }
+
+ if (sidpts) {
+ struct dom_sid_buf buf;
+ /* Expect all users/groups in pts as SIDs */
+ name = talloc_strdup(
+ talloc_tos(),
+ dom_sid_str_buf(&ace->trustee, &buf));
+ if (name == NULL) {
+ return false;
+ }
+ }
+ }
+
+ while ((p = strchr_m(name, ' ')) != NULL)
+ *p = space_replacement;
+
+ add_afs_ace(afs_acl, true, name,
+ nt_to_afs_rights(filename, ace));
+ }
+
+ return true;
+}
+
+static bool afs_get_afs_acl(const char *filename, struct afs_acl *acl)
+{
+ struct afs_iob iob;
+
+ int ret;
+
+ char space[MAXSIZE];
+
+ DEBUG(5, ("afs_get_afs_acl: %s\n", filename));
+
+ iob.in_size = 0;
+ iob.out_size = MAXSIZE;
+ iob.in = iob.out = space;
+
+ ret = afs_syscall(AFSCALL_PIOCTL, filename, VIOCGETAL,
+ (char *)&iob, 0);
+
+ if (ret) {
+ DEBUG(1, ("got error from PIOCTL: %d\n", ret));
+ return false;
+ }
+
+ if (!init_afs_acl(acl))
+ return false;
+
+ if (!parse_afs_acl(acl, space)) {
+ DEBUG(1, ("Could not parse AFS acl\n"));
+ free_afs_acl(acl);
+ return false;
+ }
+
+ return true;
+}
+
+/* For setting an AFS ACL we have to take care of the ACEs we could
+ * not properly map to SIDs. Merge all of them into the new ACL. */
+
+static void merge_unknown_aces(struct afs_acl *src, struct afs_acl *dst)
+{
+ struct afs_ace *ace;
+
+ for (ace = src->acelist; ace != NULL; ace = ace->next)
+ {
+ struct afs_ace *copy;
+
+ if (ace->type != SID_NAME_UNKNOWN) {
+ DEBUG(10, ("Not merging known ACE for %s\n",
+ ace->name));
+ continue;
+ }
+
+ DEBUG(10, ("Merging unknown ACE for %s\n", ace->name));
+
+ copy = clone_afs_ace(dst->ctx, ace);
+
+ if (copy == NULL) {
+ DEBUG(0, ("Could not clone ACE for %s\n", ace->name));
+ continue;
+ }
+
+ copy->next = dst->acelist;
+ dst->acelist = copy;
+ dst->num_aces += 1;
+ }
+}
+
+static NTSTATUS afs_set_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ struct afs_acl old_afs_acl, new_afs_acl;
+ struct afs_acl dir_acl, file_acl;
+ char acl_string[MAXSIZE];
+ struct afs_iob iob;
+ int ret = -1;
+ char *name = NULL;
+ const char *fileacls;
+
+ fileacls = lp_parm_const_string(SNUM(handle->conn), "afsacl", "fileacls",
+ "yes");
+
+ sidpts = lp_parm_bool(SNUM(handle->conn), "afsacl", "sidpts", false);
+
+ ZERO_STRUCT(old_afs_acl);
+ ZERO_STRUCT(new_afs_acl);
+ ZERO_STRUCT(dir_acl);
+ ZERO_STRUCT(file_acl);
+
+ name = talloc_strdup(talloc_tos(), fsp->fsp_name->base_name);
+ if (!name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!fsp->fsp_flags.is_directory) {
+ /* We need to get the name of the directory containing the
+ * file, this is where the AFS acls live */
+ char *p = strrchr(name, '/');
+ if (p != NULL) {
+ *p = '\0';
+ } else {
+ name = talloc_strdup(talloc_tos(), ".");
+ if (!name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+
+ if (!afs_get_afs_acl(name, &old_afs_acl)) {
+ DEBUG(3, ("Could not get old ACL of %s\n", fsp_str_dbg(fsp)));
+ goto done;
+ }
+
+ split_afs_acl(&old_afs_acl, &dir_acl, &file_acl);
+
+ if (fsp->fsp_flags.is_directory) {
+
+ if (!strequal(fileacls, "yes")) {
+ /* Throw away file acls, we depend on the
+ * inheritance ACEs that also give us file
+ * permissions */
+ free_afs_acl(&file_acl);
+ }
+
+ free_afs_acl(&dir_acl);
+ if (!nt_to_afs_acl(fsp->fsp_name->base_name,
+ security_info_sent, psd,
+ nt_to_afs_dir_rights, &dir_acl))
+ goto done;
+ } else {
+ if (strequal(fileacls, "no")) {
+ ret = -1;
+ goto done;
+ }
+
+ if (strequal(fileacls, "ignore")) {
+ ret = 0;
+ goto done;
+ }
+
+ free_afs_acl(&file_acl);
+ if (!nt_to_afs_acl(fsp->fsp_name->base_name,
+ security_info_sent, psd,
+ nt_to_afs_file_rights, &file_acl))
+ goto done;
+ }
+
+ merge_afs_acls(&dir_acl, &file_acl, &new_afs_acl);
+
+ merge_unknown_aces(&old_afs_acl, &new_afs_acl);
+
+ unparse_afs_acl(&new_afs_acl, acl_string);
+
+ iob.in = acl_string;
+ iob.in_size = 1+strlen(iob.in);
+ iob.out = NULL;
+ iob.out_size = 0;
+
+ DEBUG(10, ("trying to set acl '%s' on file %s\n", iob.in, name));
+
+ ret = afs_syscall(AFSCALL_PIOCTL, name, VIOCSETAL, (char *)&iob, 0);
+
+ if (ret != 0) {
+ DEBUG(10, ("VIOCSETAL returned %d\n", ret));
+ }
+
+ done:
+ free_afs_acl(&dir_acl);
+ free_afs_acl(&file_acl);
+ free_afs_acl(&old_afs_acl);
+ free_afs_acl(&new_afs_acl);
+
+ return (ret == 0) ? NT_STATUS_OK : NT_STATUS_ACCESS_DENIED;
+}
+
+static NTSTATUS afsacl_fget_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ struct afs_acl acl;
+ size_t sd_size;
+
+ DEBUG(5, ("afsacl_fget_nt_acl: %s\n", fsp_str_dbg(fsp)));
+
+ sidpts = lp_parm_bool(SNUM(fsp->conn), "afsacl", "sidpts", false);
+
+ if (!afs_get_afs_acl(fsp->fsp_name->base_name, &acl)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ sd_size = afs_fto_nt_acl(&acl, fsp, security_info, mem_ctx, ppdesc);
+
+ free_afs_acl(&acl);
+
+ return (sd_size != 0) ? NT_STATUS_OK : NT_STATUS_ACCESS_DENIED;
+}
+
+static NTSTATUS afsacl_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ return afs_set_nt_acl(handle, fsp, security_info_sent, psd);
+}
+
+static int afsacl_connect(vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ const char *spc;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ spc = lp_parm_const_string(SNUM(handle->conn), "afsacl", "space", "%");
+
+ if (spc != NULL)
+ space_replacement = spc[0];
+
+ return 0;
+}
+
+/* We don't have a linear form of the AFS ACL yet */
+static int afsacl_sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static struct vfs_fn_pointers vfs_afsacl_fns = {
+ .connect_fn = afsacl_connect,
+ .fget_nt_acl_fn = afsacl_fget_nt_acl,
+ .fset_nt_acl_fn = afsacl_fset_nt_acl,
+ .sys_acl_blob_get_fd_fn = afsacl_sys_acl_blob_get_fd
+};
+
+static_decl_vfs;
+NTSTATUS vfs_afsacl_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "afsacl",
+ &vfs_afsacl_fns);
+}
diff --git a/source3/modules/vfs_aio_fork.c b/source3/modules/vfs_aio_fork.c
new file mode 100644
index 0000000..87dcbdd
--- /dev/null
+++ b/source3/modules/vfs_aio_fork.c
@@ -0,0 +1,936 @@
+/*
+ * Simulate the Posix AIO using mmap/fork
+ *
+ * Copyright (C) Volker Lendecke 2008
+ * Copyright (C) Jeremy Allison 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/shmem.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/async_req/async_sock.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/msghdr.h"
+#include "smbprofile.h"
+#include "lib/global_contexts.h"
+
+#if !defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL) && !defined(HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS)
+# error Can not pass file descriptors
+#endif
+
+#undef recvmsg
+
+#ifndef MAP_FILE
+#define MAP_FILE 0
+#endif
+
+struct aio_child_list;
+
+struct aio_fork_config {
+ bool erratic_testing_mode;
+ struct aio_child_list *children;
+};
+
+struct mmap_area {
+ size_t size;
+ void *ptr;
+};
+
+static int mmap_area_destructor(struct mmap_area *area)
+{
+ munmap(discard_const(area->ptr), area->size);
+ return 0;
+}
+
+static struct mmap_area *mmap_area_init(TALLOC_CTX *mem_ctx, size_t size)
+{
+ struct mmap_area *result;
+ int fd;
+
+ result = talloc(mem_ctx, struct mmap_area);
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ goto fail;
+ }
+
+ fd = open("/dev/zero", O_RDWR);
+ if (fd == -1) {
+ DEBUG(3, ("open(\"/dev/zero\") failed: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+
+ result->ptr = mmap(NULL, size, PROT_READ|PROT_WRITE,
+ MAP_SHARED|MAP_FILE, fd, 0);
+ close(fd);
+ if (result->ptr == MAP_FAILED) {
+ DEBUG(1, ("mmap failed: %s\n", strerror(errno)));
+ goto fail;
+ }
+
+ result->size = size;
+ talloc_set_destructor(result, mmap_area_destructor);
+
+ return result;
+
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+enum cmd_type {
+ READ_CMD,
+ WRITE_CMD,
+ FSYNC_CMD
+};
+
+static const char *cmd_type_str(enum cmd_type cmd)
+{
+ const char *result;
+
+ switch (cmd) {
+ case READ_CMD:
+ result = "READ";
+ break;
+ case WRITE_CMD:
+ result = "WRITE";
+ break;
+ case FSYNC_CMD:
+ result = "FSYNC";
+ break;
+ default:
+ result = "<UNKNOWN>";
+ break;
+ }
+ return result;
+}
+
+struct rw_cmd {
+ size_t n;
+ off_t offset;
+ enum cmd_type cmd;
+ bool erratic_testing_mode;
+};
+
+struct rw_ret {
+ ssize_t size;
+ int ret_errno;
+ uint64_t duration;
+};
+
+struct aio_child_list;
+
+struct aio_child {
+ struct aio_child *prev, *next;
+ struct aio_child_list *list;
+ pid_t pid;
+ int sockfd;
+ struct mmap_area *map;
+ bool dont_delete; /* Marked as in use since last cleanup */
+ bool busy;
+};
+
+struct aio_child_list {
+ struct aio_child *children;
+ struct tevent_timer *cleanup_event;
+};
+
+static ssize_t read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
+{
+ struct iovec iov[1];
+ struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 };
+ ssize_t n;
+ size_t bufsize = msghdr_prep_recv_fds(NULL, NULL, 0, 1);
+ uint8_t buf[bufsize];
+
+ msghdr_prep_recv_fds(&msg, buf, bufsize, 1);
+
+ iov[0].iov_base = (void *)ptr;
+ iov[0].iov_len = nbytes;
+
+ do {
+ n = recvmsg(fd, &msg, 0);
+ } while ((n == -1) && (errno == EINTR));
+
+ if (n <= 0) {
+ return n;
+ }
+
+ {
+ size_t num_fds = msghdr_extract_fds(&msg, NULL, 0);
+ int fds[num_fds];
+
+ msghdr_extract_fds(&msg, fds, num_fds);
+
+ if (num_fds != 1) {
+ size_t i;
+
+ for (i=0; i<num_fds; i++) {
+ close(fds[i]);
+ }
+
+ *recvfd = -1;
+ return n;
+ }
+
+ *recvfd = fds[0];
+ }
+
+ return(n);
+}
+
+static ssize_t write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
+{
+ struct msghdr msg = {0};
+ size_t bufsize = msghdr_prep_fds(NULL, NULL, 0, &sendfd, 1);
+ uint8_t buf[bufsize];
+ struct iovec iov;
+ ssize_t sent;
+
+ msghdr_prep_fds(&msg, buf, bufsize, &sendfd, 1);
+
+ iov.iov_base = (void *)ptr;
+ iov.iov_len = nbytes;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ do {
+ sent = sendmsg(fd, &msg, 0);
+ } while ((sent == -1) && (errno == EINTR));
+
+ return sent;
+}
+
+static void aio_child_cleanup(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct aio_child_list *list = talloc_get_type_abort(
+ private_data, struct aio_child_list);
+ struct aio_child *child, *next;
+
+ TALLOC_FREE(list->cleanup_event);
+
+ for (child = list->children; child != NULL; child = next) {
+ next = child->next;
+
+ if (child->busy) {
+ DEBUG(10, ("child %d currently active\n",
+ (int)child->pid));
+ continue;
+ }
+
+ if (child->dont_delete) {
+ DEBUG(10, ("Child %d was active since last cleanup\n",
+ (int)child->pid));
+ child->dont_delete = false;
+ continue;
+ }
+
+ DEBUG(10, ("Child %d idle for more than 30 seconds, "
+ "deleting\n", (int)child->pid));
+
+ TALLOC_FREE(child);
+ child = next;
+ }
+
+ if (list->children != NULL) {
+ /*
+ * Re-schedule the next cleanup round
+ */
+ list->cleanup_event = tevent_add_timer(global_event_context(), list,
+ timeval_add(&now, 30, 0),
+ aio_child_cleanup, list);
+
+ }
+}
+
+static struct aio_child_list *init_aio_children(struct vfs_handle_struct *handle)
+{
+ struct aio_fork_config *config;
+ struct aio_child_list *children;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct aio_fork_config,
+ return NULL);
+
+ if (config->children == NULL) {
+ config->children = talloc_zero(config, struct aio_child_list);
+ if (config->children == NULL) {
+ return NULL;
+ }
+ }
+ children = config->children;
+
+ /*
+ * Regardless of whether the child_list had been around or not, make
+ * sure that we have a cleanup timed event. This timed event will
+ * delete itself when it finds that no children are around anymore.
+ */
+
+ if (children->cleanup_event == NULL) {
+ children->cleanup_event =
+ tevent_add_timer(global_event_context(), children,
+ timeval_current_ofs(30, 0),
+ aio_child_cleanup, children);
+ if (children->cleanup_event == NULL) {
+ TALLOC_FREE(config->children);
+ return NULL;
+ }
+ }
+
+ return children;
+}
+
+static void aio_child_loop(int sockfd, struct mmap_area *map)
+{
+ while (true) {
+ int fd = -1;
+ ssize_t ret;
+ struct rw_cmd cmd_struct;
+ struct rw_ret ret_struct;
+ struct timespec start, end;
+
+ ret = read_fd(sockfd, &cmd_struct, sizeof(cmd_struct), &fd);
+ if (ret != sizeof(cmd_struct)) {
+ DEBUG(10, ("read_fd returned %d: %s\n", (int)ret,
+ strerror(errno)));
+ exit(1);
+ }
+
+ DEBUG(10, ("aio_child_loop: %s %d bytes at %d from fd %d\n",
+ cmd_type_str(cmd_struct.cmd),
+ (int)cmd_struct.n, (int)cmd_struct.offset, fd));
+
+ if (cmd_struct.erratic_testing_mode) {
+ /*
+ * For developer testing, we want erratic behaviour for
+ * async I/O times
+ */
+ uint8_t randval;
+ unsigned msecs;
+ /*
+ * use generate_random_buffer, we just forked from a
+ * common parent state
+ */
+ generate_random_buffer(&randval, sizeof(randval));
+ msecs = (randval%20)+1;
+ DEBUG(10, ("delaying for %u msecs\n", msecs));
+ smb_msleep(msecs);
+ }
+
+ ZERO_STRUCT(ret_struct);
+
+ PROFILE_TIMESTAMP(&start);
+
+ switch (cmd_struct.cmd) {
+ case READ_CMD:
+ ret_struct.size = sys_pread_full(
+ fd, discard_const(map->ptr), cmd_struct.n,
+ cmd_struct.offset);
+#if 0
+/* This breaks "make test" when run with aio_fork module. */
+#ifdef DEVELOPER
+ ret_struct.size = MAX(1, ret_struct.size * 0.9);
+#endif
+#endif
+ break;
+ case WRITE_CMD:
+ ret_struct.size = sys_pwrite_full(
+ fd, discard_const(map->ptr), cmd_struct.n,
+ cmd_struct.offset);
+ break;
+ case FSYNC_CMD:
+ ret_struct.size = fsync(fd);
+ break;
+ default:
+ ret_struct.size = -1;
+ errno = EINVAL;
+ }
+
+ PROFILE_TIMESTAMP(&end);
+ ret_struct.duration = nsec_time_diff(&end, &start);
+ DEBUG(10, ("aio_child_loop: syscall returned %d\n",
+ (int)ret_struct.size));
+
+ if (ret_struct.size == -1) {
+ ret_struct.ret_errno = errno;
+ }
+
+ /*
+ * Close the fd before telling our parent we're done. The
+ * parent might close and re-open the file very quickly, and
+ * with system-level share modes (GPFS) we would get an
+ * unjustified SHARING_VIOLATION.
+ */
+ close(fd);
+
+ ret = write_data(sockfd, (char *)&ret_struct,
+ sizeof(ret_struct));
+ if (ret != sizeof(ret_struct)) {
+ DEBUG(10, ("could not write ret_struct: %s\n",
+ strerror(errno)));
+ exit(2);
+ }
+ }
+}
+
+static int aio_child_destructor(struct aio_child *child)
+{
+ char c=0;
+
+ SMB_ASSERT(!child->busy);
+
+ DEBUG(10, ("aio_child_destructor: removing child %d on fd %d\n",
+ (int)child->pid, child->sockfd));
+
+ /*
+ * closing the sockfd makes the child not return from recvmsg() on RHEL
+ * 5.5 so instead force the child to exit by writing bad data to it
+ */
+ sys_write_v(child->sockfd, &c, sizeof(c));
+ close(child->sockfd);
+ DLIST_REMOVE(child->list->children, child);
+ return 0;
+}
+
+/*
+ * We have to close all fd's in open files, we might incorrectly hold a system
+ * level share mode on a file.
+ */
+
+static struct files_struct *close_fsp_fd(struct files_struct *fsp,
+ void *private_data)
+{
+ if ((fsp->fh != NULL) && (fsp_get_pathref_fd(fsp) != -1)) {
+ close(fsp_get_pathref_fd(fsp));
+ fsp_set_fd(fsp, -1);
+ }
+ return NULL;
+}
+
+static int create_aio_child(struct smbd_server_connection *sconn,
+ struct aio_child_list *children,
+ size_t map_size,
+ struct aio_child **presult)
+{
+ struct aio_child *result;
+ int fdpair[2];
+ int ret;
+
+ fdpair[0] = fdpair[1] = -1;
+
+ result = talloc_zero(children, struct aio_child);
+ if (result == NULL) {
+ return ENOMEM;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) == -1) {
+ ret = errno;
+ DEBUG(10, ("socketpair() failed: %s\n", strerror(errno)));
+ goto fail;
+ }
+
+ DEBUG(10, ("fdpair = %d/%d\n", fdpair[0], fdpair[1]));
+
+ result->map = mmap_area_init(result, map_size);
+ if (result->map == NULL) {
+ ret = errno;
+ DEBUG(0, ("Could not create mmap area\n"));
+ goto fail;
+ }
+
+ result->pid = fork();
+ if (result->pid == -1) {
+ ret = errno;
+ DEBUG(0, ("fork failed: %s\n", strerror(errno)));
+ goto fail;
+ }
+
+ if (result->pid == 0) {
+ close(fdpair[0]);
+ result->sockfd = fdpair[1];
+ files_forall(sconn, close_fsp_fd, NULL);
+ aio_child_loop(result->sockfd, result->map);
+ }
+
+ DEBUG(10, ("Child %d created with sockfd %d\n",
+ (int)result->pid, fdpair[0]));
+
+ result->sockfd = fdpair[0];
+ close(fdpair[1]);
+
+ result->list = children;
+ DLIST_ADD(children->children, result);
+
+ talloc_set_destructor(result, aio_child_destructor);
+
+ *presult = result;
+
+ return 0;
+
+ fail:
+ if (fdpair[0] != -1) close(fdpair[0]);
+ if (fdpair[1] != -1) close(fdpair[1]);
+ TALLOC_FREE(result);
+
+ return ret;
+}
+
+static int get_idle_child(struct vfs_handle_struct *handle,
+ struct aio_child **pchild)
+{
+ struct aio_child_list *children;
+ struct aio_child *child;
+
+ children = init_aio_children(handle);
+ if (children == NULL) {
+ return ENOMEM;
+ }
+
+ for (child = children->children; child != NULL; child = child->next) {
+ if (!child->busy) {
+ break;
+ }
+ }
+
+ if (child == NULL) {
+ int ret;
+
+ DEBUG(10, ("no idle child found, creating new one\n"));
+
+ ret = create_aio_child(handle->conn->sconn, children,
+ 128*1024, &child);
+ if (ret != 0) {
+ DEBUG(10, ("create_aio_child failed: %s\n",
+ strerror(errno)));
+ return ret;
+ }
+ }
+
+ child->dont_delete = true;
+ child->busy = true;
+
+ *pchild = child;
+ return 0;
+}
+
+struct aio_fork_pread_state {
+ struct aio_child *child;
+ size_t n;
+ void *data;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void aio_fork_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *aio_fork_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct aio_fork_pread_state *state;
+ struct rw_cmd cmd;
+ ssize_t written;
+ int err;
+ struct aio_fork_config *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct aio_fork_config,
+ return NULL);
+
+ req = tevent_req_create(mem_ctx, &state, struct aio_fork_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->n = n;
+ state->data = data;
+
+ if (n > 128*1024) {
+ /* TODO: support variable buffers */
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ err = get_idle_child(handle, &state->child);
+ if (err != 0) {
+ tevent_req_error(req, err);
+ return tevent_req_post(req, ev);
+ }
+
+ ZERO_STRUCT(cmd);
+ cmd.n = n;
+ cmd.offset = offset;
+ cmd.cmd = READ_CMD;
+ cmd.erratic_testing_mode = config->erratic_testing_mode;
+
+ DEBUG(10, ("sending fd %d to child %d\n", fsp_get_io_fd(fsp),
+ (int)state->child->pid));
+
+ /*
+ * Not making this async. We're writing into an empty unix
+ * domain socket. This should never block.
+ */
+ written = write_fd(state->child->sockfd, &cmd, sizeof(cmd),
+ fsp_get_io_fd(fsp));
+ if (written == -1) {
+ err = errno;
+
+ TALLOC_FREE(state->child);
+
+ DEBUG(10, ("write_fd failed: %s\n", strerror(err)));
+ tevent_req_error(req, err);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = read_packet_send(state, ev, state->child->sockfd,
+ sizeof(struct rw_ret), NULL, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(state->child); /* we sent sth down */
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, aio_fork_pread_done, req);
+ return req;
+}
+
+static void aio_fork_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct aio_fork_pread_state *state = tevent_req_data(
+ req, struct aio_fork_pread_state);
+ ssize_t nread;
+ uint8_t *buf;
+ int err;
+ struct rw_ret *retbuf;
+
+ nread = read_packet_recv(subreq, talloc_tos(), &buf, &err);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ TALLOC_FREE(state->child);
+ tevent_req_error(req, err);
+ return;
+ }
+
+ retbuf = (struct rw_ret *)buf;
+ state->ret = retbuf->size;
+ state->vfs_aio_state.error = retbuf->ret_errno;
+ state->vfs_aio_state.duration = retbuf->duration;
+
+ if ((size_t)state->ret > state->n) {
+ tevent_req_error(req, EIO);
+ state->child->busy = false;
+ return;
+ }
+ memcpy(state->data, state->child->map->ptr, state->ret);
+
+ state->child->busy = false;
+
+ tevent_req_done(req);
+}
+
+static ssize_t aio_fork_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct aio_fork_pread_state *state = tevent_req_data(
+ req, struct aio_fork_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct aio_fork_pwrite_state {
+ struct aio_child *child;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void aio_fork_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *aio_fork_pwrite_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp,
+ const void *data, size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct aio_fork_pwrite_state *state;
+ struct rw_cmd cmd;
+ ssize_t written;
+ int err;
+ struct aio_fork_config *config;
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct aio_fork_config,
+ return NULL);
+
+ req = tevent_req_create(mem_ctx, &state, struct aio_fork_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (n > 128*1024) {
+ /* TODO: support variable buffers */
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ err = get_idle_child(handle, &state->child);
+ if (err != 0) {
+ tevent_req_error(req, err);
+ return tevent_req_post(req, ev);
+ }
+
+ memcpy(state->child->map->ptr, data, n);
+
+ ZERO_STRUCT(cmd);
+ cmd.n = n;
+ cmd.offset = offset;
+ cmd.cmd = WRITE_CMD;
+ cmd.erratic_testing_mode = config->erratic_testing_mode;
+
+ DEBUG(10, ("sending fd %d to child %d\n", fsp_get_io_fd(fsp),
+ (int)state->child->pid));
+
+ /*
+ * Not making this async. We're writing into an empty unix
+ * domain socket. This should never block.
+ */
+ written = write_fd(state->child->sockfd, &cmd, sizeof(cmd),
+ fsp_get_io_fd(fsp));
+ if (written == -1) {
+ err = errno;
+
+ TALLOC_FREE(state->child);
+
+ DEBUG(10, ("write_fd failed: %s\n", strerror(err)));
+ tevent_req_error(req, err);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = read_packet_send(state, ev, state->child->sockfd,
+ sizeof(struct rw_ret), NULL, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(state->child); /* we sent sth down */
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, aio_fork_pwrite_done, req);
+ return req;
+}
+
+static void aio_fork_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct aio_fork_pwrite_state *state = tevent_req_data(
+ req, struct aio_fork_pwrite_state);
+ ssize_t nread;
+ uint8_t *buf;
+ int err;
+ struct rw_ret *retbuf;
+
+ nread = read_packet_recv(subreq, talloc_tos(), &buf, &err);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ TALLOC_FREE(state->child);
+ tevent_req_error(req, err);
+ return;
+ }
+
+ state->child->busy = false;
+
+ retbuf = (struct rw_ret *)buf;
+ state->ret = retbuf->size;
+ state->vfs_aio_state.error = retbuf->ret_errno;
+ state->vfs_aio_state.duration = retbuf->duration;
+ tevent_req_done(req);
+}
+
+static ssize_t aio_fork_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct aio_fork_pwrite_state *state = tevent_req_data(
+ req, struct aio_fork_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct aio_fork_fsync_state {
+ struct aio_child *child;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void aio_fork_fsync_done(struct tevent_req *subreq);
+
+static struct tevent_req *aio_fork_fsync_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp)
+{
+ struct tevent_req *req, *subreq;
+ struct aio_fork_fsync_state *state;
+ struct rw_cmd cmd;
+ ssize_t written;
+ int err;
+ struct aio_fork_config *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct aio_fork_config,
+ return NULL);
+
+ req = tevent_req_create(mem_ctx, &state, struct aio_fork_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ err = get_idle_child(handle, &state->child);
+ if (err != 0) {
+ tevent_req_error(req, err);
+ return tevent_req_post(req, ev);
+ }
+
+ ZERO_STRUCT(cmd);
+ cmd.cmd = FSYNC_CMD;
+ cmd.erratic_testing_mode = config->erratic_testing_mode;
+
+ DEBUG(10, ("sending fd %d to child %d\n", fsp_get_io_fd(fsp),
+ (int)state->child->pid));
+
+ /*
+ * Not making this async. We're writing into an empty unix
+ * domain socket. This should never block.
+ */
+ written = write_fd(state->child->sockfd, &cmd, sizeof(cmd),
+ fsp_get_io_fd(fsp));
+ if (written == -1) {
+ err = errno;
+
+ TALLOC_FREE(state->child);
+
+ DEBUG(10, ("write_fd failed: %s\n", strerror(err)));
+ tevent_req_error(req, err);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = read_packet_send(state, ev, state->child->sockfd,
+ sizeof(struct rw_ret), NULL, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(state->child); /* we sent sth down */
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, aio_fork_fsync_done, req);
+ return req;
+}
+
+static void aio_fork_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct aio_fork_fsync_state *state = tevent_req_data(
+ req, struct aio_fork_fsync_state);
+ ssize_t nread;
+ uint8_t *buf;
+ int err;
+ struct rw_ret *retbuf;
+
+ nread = read_packet_recv(subreq, talloc_tos(), &buf, &err);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ TALLOC_FREE(state->child);
+ tevent_req_error(req, err);
+ return;
+ }
+
+ state->child->busy = false;
+
+ retbuf = (struct rw_ret *)buf;
+ state->ret = retbuf->size;
+ state->vfs_aio_state.error = retbuf->ret_errno;
+ state->vfs_aio_state.duration = retbuf->duration;
+ tevent_req_done(req);
+}
+
+static int aio_fork_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct aio_fork_fsync_state *state = tevent_req_data(
+ req, struct aio_fork_fsync_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static int aio_fork_connect(vfs_handle_struct *handle, const char *service,
+ const char *user)
+{
+ int ret;
+ struct aio_fork_config *config;
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ config = talloc_zero(handle->conn, struct aio_fork_config);
+ if (!config) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ DEBUG(0, ("talloc_zero() failed\n"));
+ return -1;
+ }
+
+ config->erratic_testing_mode = lp_parm_bool(SNUM(handle->conn), "vfs_aio_fork",
+ "erratic_testing_mode", false);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct aio_fork_config,
+ return -1);
+
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_aio_fork_fns = {
+ .connect_fn = aio_fork_connect,
+ .pread_send_fn = aio_fork_pread_send,
+ .pread_recv_fn = aio_fork_pread_recv,
+ .pwrite_send_fn = aio_fork_pwrite_send,
+ .pwrite_recv_fn = aio_fork_pwrite_recv,
+ .fsync_send_fn = aio_fork_fsync_send,
+ .fsync_recv_fn = aio_fork_fsync_recv,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_aio_fork_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "aio_fork", &vfs_aio_fork_fns);
+}
diff --git a/source3/modules/vfs_aio_pthread.c b/source3/modules/vfs_aio_pthread.c
new file mode 100644
index 0000000..b099a6b
--- /dev/null
+++ b/source3/modules/vfs_aio_pthread.c
@@ -0,0 +1,538 @@
+/*
+ * Simulate Posix AIO using pthreads.
+ *
+ * Based on the aio_fork work from Volker and Volker's pthreadpool library.
+ *
+ * Copyright (C) Volker Lendecke 2008
+ * Copyright (C) Jeremy Allison 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/shmem.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/pthreadpool/pthreadpool_tevent.h"
+#ifdef HAVE_LINUX_FALLOC_H
+#include <linux/falloc.h>
+#endif
+
+#if defined(HAVE_OPENAT) && defined(HAVE_LINUX_THREAD_CREDENTIALS)
+
+/*
+ * We must have openat() to do any thread-based
+ * asynchronous opens. We also must be using
+ * thread-specific credentials (Linux-only
+ * for now).
+ */
+
+struct aio_open_private_data {
+ struct aio_open_private_data *prev, *next;
+ /* Inputs. */
+ int dir_fd;
+ bool opened_dir_fd;
+ int flags;
+ mode_t mode;
+ uint64_t mid;
+ bool in_progress;
+ struct smb_filename *fsp_name;
+ struct smb_filename *smb_fname;
+ connection_struct *conn;
+ struct smbXsrv_connection *xconn;
+ const struct security_unix_token *ux_tok;
+ uint64_t initial_allocation_size;
+ /* Returns. */
+ int ret_fd;
+ int ret_errno;
+};
+
+/* List of outstanding requests we have. */
+static struct aio_open_private_data *open_pd_list;
+
+static void aio_open_do(struct aio_open_private_data *opd);
+static void opd_free(struct aio_open_private_data *opd);
+
+/************************************************************************
+ Find the open private data by mid.
+***********************************************************************/
+
+static struct aio_open_private_data *find_open_private_data_by_mid(uint64_t mid)
+{
+ struct aio_open_private_data *opd;
+
+ for (opd = open_pd_list; opd != NULL; opd = opd->next) {
+ if (opd->mid == mid) {
+ return opd;
+ }
+ }
+
+ return NULL;
+}
+
+/************************************************************************
+ Callback when an open completes.
+***********************************************************************/
+
+static void aio_open_handle_completion(struct tevent_req *subreq)
+{
+ struct aio_open_private_data *opd =
+ tevent_req_callback_data(subreq,
+ struct aio_open_private_data);
+ int ret;
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ /*
+ * We're no longer in flight. Remove the
+ * destructor used to preserve opd so
+ * a talloc_free actually removes it.
+ */
+ talloc_set_destructor(opd, NULL);
+
+ if (opd->conn == NULL) {
+ /*
+ * We were shutdown closed in flight. No one
+ * wants the result, and state has been reparented
+ * to the NULL context, so just free it so we
+ * don't leak memory.
+ */
+ DBG_NOTICE("aio open request for %s abandoned in flight\n",
+ opd->fsp_name->base_name);
+ if (opd->ret_fd != -1) {
+ close(opd->ret_fd);
+ opd->ret_fd = -1;
+ }
+ /*
+ * Find outstanding event and reschedule so the client
+ * gets an error message return from the open.
+ */
+ schedule_deferred_open_message_smb(opd->xconn, opd->mid);
+ opd_free(opd);
+ return;
+ }
+
+ if (ret != 0) {
+ bool ok;
+
+ if (ret != EAGAIN) {
+ smb_panic("aio_open_handle_completion");
+ /* notreached. */
+ return;
+ }
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service(opd->conn, opd->conn->vuid);
+ if (!ok) {
+ smb_panic("Can't change to user");
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ aio_open_do(opd);
+ }
+
+ DEBUG(10,("aio_open_handle_completion: mid %llu "
+ "for file %s completed\n",
+ (unsigned long long)opd->mid,
+ opd->fsp_name->base_name));
+
+ opd->in_progress = false;
+
+ /* Find outstanding event and reschedule. */
+ if (!schedule_deferred_open_message_smb(opd->xconn, opd->mid)) {
+ /*
+ * Outstanding event didn't exist or was
+ * cancelled. Free up the fd and throw
+ * away the result.
+ */
+ if (opd->ret_fd != -1) {
+ close(opd->ret_fd);
+ opd->ret_fd = -1;
+ }
+ opd_free(opd);
+ }
+}
+
+/*****************************************************************
+ The core of the async open code - the worker function. Note we
+ use the new openat() system call to avoid any problems with
+ current working directory changes plus we change credentials
+ on the thread to prevent any security race conditions.
+*****************************************************************/
+
+static void aio_open_worker(void *private_data)
+{
+ struct aio_open_private_data *opd =
+ (struct aio_open_private_data *)private_data;
+
+ /* Become the correct credential on this thread. */
+ if (set_thread_credentials(opd->ux_tok->uid,
+ opd->ux_tok->gid,
+ (size_t)opd->ux_tok->ngroups,
+ opd->ux_tok->groups) != 0) {
+ opd->ret_fd = -1;
+ opd->ret_errno = errno;
+ return;
+ }
+
+ aio_open_do(opd);
+}
+
+static void aio_open_do(struct aio_open_private_data *opd)
+{
+ opd->ret_fd = openat(opd->dir_fd,
+ opd->smb_fname->base_name,
+ opd->flags,
+ opd->mode);
+
+ if (opd->ret_fd == -1) {
+ opd->ret_errno = errno;
+ } else {
+ /* Create was successful. */
+ opd->ret_errno = 0;
+
+#if defined(HAVE_LINUX_FALLOCATE)
+ /*
+ * See if we can set the initial
+ * allocation size. We don't record
+ * the return for this as it's an
+ * optimization - the upper layer
+ * will also do this for us once
+ * the open returns.
+ */
+ if (opd->initial_allocation_size) {
+ (void)fallocate(opd->ret_fd,
+ FALLOC_FL_KEEP_SIZE,
+ 0,
+ (off_t)opd->initial_allocation_size);
+ }
+#endif
+ }
+}
+
+/************************************************************************
+ Open private data teardown.
+***********************************************************************/
+
+static void opd_free(struct aio_open_private_data *opd)
+{
+ if (opd->opened_dir_fd && opd->dir_fd != -1) {
+ close(opd->dir_fd);
+ }
+ DLIST_REMOVE(open_pd_list, opd);
+ TALLOC_FREE(opd);
+}
+
+/************************************************************************
+ Create and initialize a private data struct for async open.
+***********************************************************************/
+
+static struct aio_open_private_data *create_private_open_data(
+ TALLOC_CTX *ctx,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ struct aio_open_private_data *opd = talloc_zero(ctx,
+ struct aio_open_private_data);
+
+ if (!opd) {
+ return NULL;
+ }
+
+ *opd = (struct aio_open_private_data) {
+ .dir_fd = -1,
+ .ret_fd = -1,
+ .ret_errno = EINPROGRESS,
+ .flags = flags,
+ .mode = mode,
+ .mid = fsp->mid,
+ .in_progress = true,
+ .conn = fsp->conn,
+ /*
+ * TODO: In future we need a proper algorithm
+ * to find the correct connection for a fsp.
+ * For now we only have one connection, so this is correct...
+ */
+ .xconn = fsp->conn->sconn->client->connections,
+ .initial_allocation_size = fsp->initial_allocation_size,
+ };
+
+ /* Copy our current credentials. */
+ opd->ux_tok = copy_unix_token(opd, get_current_utok(fsp->conn));
+ if (opd->ux_tok == NULL) {
+ opd_free(opd);
+ return NULL;
+ }
+
+ /*
+ * Copy the full fsp_name and smb_fname which is the basename.
+ */
+ opd->smb_fname = cp_smb_filename(opd, smb_fname);
+ if (opd->smb_fname == NULL) {
+ opd_free(opd);
+ return NULL;
+ }
+
+ opd->fsp_name = cp_smb_filename(opd, fsp->fsp_name);
+ if (opd->fsp_name == NULL) {
+ opd_free(opd);
+ return NULL;
+ }
+
+ if (fsp_get_pathref_fd(dirfsp) != AT_FDCWD) {
+ opd->dir_fd = fsp_get_pathref_fd(dirfsp);
+ } else {
+#if defined(O_DIRECTORY)
+ opd->dir_fd = open(".", O_RDONLY|O_DIRECTORY);
+#else
+ opd->dir_fd = open(".", O_RDONLY);
+#endif
+ opd->opened_dir_fd = true;
+ }
+ if (opd->dir_fd == -1) {
+ opd_free(opd);
+ return NULL;
+ }
+
+ DLIST_ADD_END(open_pd_list, opd);
+ return opd;
+}
+
+static int opd_inflight_destructor(struct aio_open_private_data *opd)
+{
+ /*
+ * Setting conn to NULL allows us to
+ * discover the connection was torn
+ * down which kills the fsp that owns
+ * opd.
+ */
+ DBG_NOTICE("aio open request for %s cancelled\n",
+ opd->fsp_name->base_name);
+ opd->conn = NULL;
+ /* Don't let opd go away. */
+ return -1;
+}
+
+/*****************************************************************
+ Setup an async open.
+*****************************************************************/
+
+static int open_async(const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ struct aio_open_private_data *opd = NULL;
+ struct tevent_req *subreq = NULL;
+
+ /*
+ * Allocate off fsp->conn, not NULL or fsp. As we're going
+ * async fsp will get talloc_free'd when we return
+ * EINPROGRESS/NT_STATUS_MORE_PROCESSING_REQUIRED. A new fsp
+ * pointer gets allocated on every re-run of the
+ * open code path. Allocating on fsp->conn instead
+ * of NULL allows use to get notified via destructor
+ * if the conn is force-closed or we shutdown.
+ * opd is always safely freed in all codepath so no
+ * memory leaks.
+ */
+ opd = create_private_open_data(fsp->conn,
+ dirfsp,
+ smb_fname,
+ fsp,
+ flags,
+ mode);
+ if (opd == NULL) {
+ DEBUG(10, ("open_async: Could not create private data.\n"));
+ return -1;
+ }
+
+ subreq = pthreadpool_tevent_job_send(opd,
+ fsp->conn->sconn->ev_ctx,
+ fsp->conn->sconn->pool,
+ aio_open_worker, opd);
+ if (subreq == NULL) {
+ opd_free(opd);
+ return -1;
+ }
+ tevent_req_set_callback(subreq, aio_open_handle_completion, opd);
+
+ DEBUG(5,("open_async: mid %llu created for file %s\n",
+ (unsigned long long)opd->mid,
+ opd->fsp_name->base_name));
+
+ /*
+ * Add a destructor to protect us from connection
+ * teardown whilst the open thread is in flight.
+ */
+ talloc_set_destructor(opd, opd_inflight_destructor);
+
+ /* Cause the calling code to reschedule us. */
+ errno = EINPROGRESS; /* Maps to NT_STATUS_MORE_PROCESSING_REQUIRED. */
+ return -1;
+}
+
+/*****************************************************************
+ Look for a matching SMB2 mid. If we find it we're rescheduled,
+ just return the completed open.
+*****************************************************************/
+
+static bool find_completed_open(files_struct *fsp,
+ int *p_fd,
+ int *p_errno)
+{
+ struct aio_open_private_data *opd;
+
+ opd = find_open_private_data_by_mid(fsp->mid);
+ if (!opd) {
+ return false;
+ }
+
+ if (opd->in_progress) {
+ DEBUG(0,("find_completed_open: mid %llu "
+ "still in progress for "
+ "file %s. PANIC !\n",
+ (unsigned long long)opd->mid,
+ opd->fsp_name->base_name));
+ /* Disaster ! This is an open timeout. Just panic. */
+ smb_panic("find_completed_open - in_progress\n");
+ /* notreached. */
+ return false;
+ }
+
+ *p_fd = opd->ret_fd;
+ *p_errno = opd->ret_errno;
+
+ DEBUG(5,("find_completed_open: mid %llu returning "
+ "fd = %d, errno = %d (%s) "
+ "for file %s\n",
+ (unsigned long long)opd->mid,
+ opd->ret_fd,
+ opd->ret_errno,
+ strerror(opd->ret_errno),
+ smb_fname_str_dbg(fsp->fsp_name)));
+
+ /* Now we can free the opd. */
+ opd_free(opd);
+ return true;
+}
+
+/*****************************************************************
+ The core open function. Only go async on O_CREAT|O_EXCL
+ opens to prevent any race conditions.
+*****************************************************************/
+
+static int aio_pthread_openat_fn(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int my_errno = 0;
+ int fd = -1;
+ bool aio_allow_open = lp_parm_bool(
+ SNUM(handle->conn), "aio_pthread", "aio open", false);
+
+ if (how->resolve != 0) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (is_named_stream(smb_fname)) {
+ /* Don't handle stream opens. */
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (fsp->conn->sconn->pool == NULL) {
+ /*
+ * a threadpool is required for async support
+ */
+ aio_allow_open = false;
+ }
+
+ if (fsp->conn->sconn->client != NULL &&
+ fsp->conn->sconn->client->server_multi_channel_enabled) {
+ /*
+ * This module is not compatible with multi channel yet.
+ */
+ aio_allow_open = false;
+ }
+
+ if (fsp->fsp_flags.is_pathref) {
+ /* Use SMB_VFS_NEXT_OPENAT() to call openat() with O_PATH. */
+ aio_allow_open = false;
+ }
+
+ if (!(how->flags & O_CREAT)) {
+ /* Only creates matter. */
+ aio_allow_open = false;
+ }
+
+ if (!(how->flags & O_EXCL)) {
+ /* Only creates with O_EXCL matter. */
+ aio_allow_open = false;
+ }
+
+ if (!aio_allow_open) {
+ /* aio opens turned off. */
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ /*
+ * See if this is a reentrant call - i.e. is this a
+ * restart of an existing open that just completed.
+ */
+
+ if (find_completed_open(fsp,
+ &fd,
+ &my_errno)) {
+ errno = my_errno;
+ return fd;
+ }
+
+ /* Ok, it's a create exclusive call - pass it to a thread helper. */
+ return open_async(dirfsp, smb_fname, fsp, how->flags, how->mode);
+}
+#endif
+
+static struct vfs_fn_pointers vfs_aio_pthread_fns = {
+#if defined(HAVE_OPENAT) && defined(HAVE_LINUX_THREAD_CREDENTIALS)
+ .openat_fn = aio_pthread_openat_fn,
+#endif
+};
+
+static_decl_vfs;
+NTSTATUS vfs_aio_pthread_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "aio_pthread", &vfs_aio_pthread_fns);
+}
diff --git a/source3/modules/vfs_aixacl.c b/source3/modules/vfs_aixacl.c
new file mode 100644
index 0000000..0628fae
--- /dev/null
+++ b/source3/modules/vfs_aixacl.c
@@ -0,0 +1,131 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set posix acls
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "vfs_aixacl_util.h"
+
+SMB_ACL_T aixacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+
+ struct acl *file_acl = (struct acl *)NULL;
+ struct smb_acl_t *result = (struct smb_acl_t *)NULL;
+
+ int rc = 0;
+ uid_t user_id;
+
+ /* AIX has no DEFAULT */
+ if ( type == SMB_ACL_TYPE_DEFAULT ) {
+ return NULL;
+ }
+
+ /* Get the acl using fstatacl */
+
+ DEBUG(10,("Entering AIX sys_acl_get_fd\n"));
+ DEBUG(10,("fd is %d\n",fsp_get_io_fd(fsp)));
+ file_acl = (struct acl *)SMB_MALLOC(BUFSIZ);
+
+ if(file_acl == NULL) {
+ errno=ENOMEM;
+ DEBUG(0,("Error in AIX sys_acl_get_fd is %d\n",errno));
+ return(NULL);
+ }
+
+ memset(file_acl,0,BUFSIZ);
+
+ rc = fstatacl(fsp_get_io_fd(fsp),0,file_acl,BUFSIZ);
+ if( (rc == -1) && (errno == ENOSPC)) {
+ struct acl *new_acl = SMB_MALLOC(file_acl->acl_len + sizeof(struct acl));
+ if( new_acl == NULL) {
+ SAFE_FREE(file_acl);
+ errno = ENOMEM;
+ return NULL;
+ }
+ file_acl = new_acl;
+ rc = fstatacl(fsp_get_io_fd(fsp),0,file_acl,file_acl->acl_len + sizeof(struct acl));
+ if( rc == -1) {
+ DEBUG(0,("fstatacl returned %d with errno %d\n",rc,errno));
+ SAFE_FREE(file_acl);
+ return(NULL);
+ }
+ }
+
+ DEBUG(10,("Got facl and returned it\n"));
+
+ result = aixacl_to_smbacl(file_acl, mem_ctx);
+ SAFE_FREE(file_acl);
+ return result;
+
+ /*errno = ENOTSUP;
+ return NULL;*/
+}
+
+int aixacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ struct acl *file_acl = NULL;
+ unsigned int rc;
+
+ file_acl = aixacl_smb_to_aixacl(type, theacl);
+ if (!file_acl)
+ return -1;
+
+ if (fsp->fsp_flags.is_pathref) {
+ /*
+ * This is no longer a handle based call.
+ */
+ return chacl(fsp->fsp_name->base_name,
+ file_acl,
+ file_acl->acl_len);
+ }
+
+ rc = fchacl(fsp_get_io_fd(fsp),file_acl,file_acl->acl_len);
+ DEBUG(10,("errno is %d\n",errno));
+ DEBUG(10,("return code is %d\n",rc));
+ SAFE_FREE(file_acl);
+ DEBUG(10,("Exiting aixacl_sys_acl_set_fd\n"));
+
+ return rc;
+}
+
+int aixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return 0; /* otherwise you can't set acl at upper level */
+}
+
+static struct vfs_fn_pointers vfs_aixacl_fns = {
+ .sys_acl_get_fd_fn = aixacl_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = aixacl_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = aixacl_sys_acl_delete_def_fd,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_aixacl_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "aixacl",
+ &vfs_aixacl_fns);
+}
diff --git a/source3/modules/vfs_aixacl.h b/source3/modules/vfs_aixacl.h
new file mode 100644
index 0000000..f9fe3f8
--- /dev/null
+++ b/source3/modules/vfs_aixacl.h
@@ -0,0 +1,34 @@
+/*
+ Copyright (C) Bjoern Jacke <bjacke@samba.org> 2022
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __VFS_AIXACL_H__
+#define __VFS_AIXACL_H__
+
+SMB_ACL_T aixacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+
+int aixacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T acl_d);
+
+int aixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp);
+
+#endif
diff --git a/source3/modules/vfs_aixacl2.c b/source3/modules/vfs_aixacl2.c
new file mode 100644
index 0000000..923b54f
--- /dev/null
+++ b/source3/modules/vfs_aixacl2.c
@@ -0,0 +1,480 @@
+/*
+ * Convert JFS2 NFS4/AIXC acls to NT acls and vice versa.
+ *
+ * Copyright (C) Volker Lendecke, 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "nfs4_acls.h"
+#include "vfs_aixacl_util.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#define AIXACL2_MODULE_NAME "aixacl2"
+
+typedef union aixjfs2_acl_t {
+ nfs4_acl_int_t jfs2_acl[1];
+ aixc_acl_t aixc_acl[1];
+}AIXJFS2_ACL_T;
+
+static int32_t aixacl2_getlen(AIXJFS2_ACL_T *acl, acl_type_t *type)
+{
+ int32_t len;
+
+ if(type->u64 == ACL_NFS4) {
+ len = acl->jfs2_acl[0].aclLength;
+ }
+ else {
+ if(type->u64 == ACL_AIXC) {
+ len = acl->aixc_acl[0].acl_len;
+ } else {
+ DEBUG(0,("aixacl2_getlen:unknown type:%d\n",type->u64));
+ return False;
+ }
+ }
+ DEBUG(10,("aixacl2_getlen:%d\n",len));
+ return len;
+}
+
+static AIXJFS2_ACL_T *aixjfs2_getacl_alloc(const char *fname, acl_type_t *type)
+{
+ AIXJFS2_ACL_T *acl;
+ size_t len = 200;
+ mode_t mode;
+ int ret;
+ uint64_t ctl_flag=0;
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_tos();
+ acl = (AIXJFS2_ACL_T *)TALLOC_SIZE(mem_ctx, len);
+ if (acl == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if(type->u64 == ACL_ANY) {
+ ctl_flag = ctl_flag | GET_ACLINFO_ONLY;
+ }
+
+ ret = aclx_get((char *)fname, ctl_flag, type, acl, &len, &mode);
+ if ((ret != 0) && (errno == ENOSPC)) {
+ len = aixacl2_getlen(acl, type) + sizeof(AIXJFS2_ACL_T);
+ DEBUG(10,("aixjfs2_getacl_alloc - acl_len:%d\n",len));
+
+ acl = (AIXJFS2_ACL_T *)TALLOC_SIZE(mem_ctx, len);
+ if (acl == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ret = aclx_get((char *)fname, ctl_flag, type, acl, &len, &mode);
+ }
+ if (ret != 0) {
+ DEBUG(8, ("aclx_get failed with %s\n", strerror(errno)));
+ return NULL;
+ }
+
+ return acl;
+}
+
+static bool aixjfs2_get_nfs4_acl(TALLOC_CTX *mem_ctx, const char *name,
+ struct SMB4ACL_T **ppacl, bool *pretryPosix)
+{
+ int32_t i;
+
+ AIXJFS2_ACL_T *pacl = NULL;
+ nfs4_acl_int_t *jfs2_acl = NULL;
+ nfs4_ace_int_t *jfs2_ace = NULL;
+ acl_type_t type;
+
+ DEBUG(10,("jfs2 get_nt_acl invoked for %s\n", name));
+
+ memset(&type, 0, sizeof(acl_type_t));
+ type.u64 = ACL_NFS4;
+
+ pacl = aixjfs2_getacl_alloc(name, &type);
+ if (pacl == NULL) {
+ DEBUG(9, ("aixjfs2_getacl_alloc failed for %s with %s\n",
+ name, strerror(errno)));
+ if (errno==ENOSYS)
+ *pretryPosix = True;
+ return False;
+ }
+
+ jfs2_acl = &pacl->jfs2_acl[0];
+ DEBUG(10, ("len: %d, version: %d, nace: %d, type: 0x%x\n",
+ jfs2_acl->aclLength, jfs2_acl->aclVersion, jfs2_acl->aclEntryN, type.u64));
+
+ *ppacl = smb_create_smb4acl(mem_ctx);
+ if (*ppacl==NULL)
+ return False;
+
+ jfs2_ace = &jfs2_acl->aclEntry[0];
+ for (i=0; i<jfs2_acl->aclEntryN; i++) {
+ SMB_ACE4PROP_T aceprop;
+
+ DEBUG(10, ("type: %d, iflags: %x, flags: %x, mask: %x, "
+ "who: %d, aclLen: %d\n", jfs2_ace->aceType, jfs2_ace->flags,
+ jfs2_ace->aceFlags, jfs2_ace->aceMask, jfs2_ace->aceWho.id, jfs2_ace->entryLen));
+
+ aceprop.aceType = jfs2_ace->aceType;
+ aceprop.aceFlags = jfs2_ace->aceFlags;
+ aceprop.aceMask = jfs2_ace->aceMask;
+ aceprop.flags = (jfs2_ace->flags&ACE4_ID_SPECIAL) ? SMB_ACE4_ID_SPECIAL : 0;
+
+ /* don't care it's real content is only 16 or 32 bit */
+ aceprop.who.id = jfs2_ace->aceWho.id;
+
+ if (smb_add_ace4(*ppacl, &aceprop)==NULL)
+ return False;
+
+ /* iterate to the next jfs2 ace */
+ jfs2_ace = (nfs4_ace_int_t *)(((char *)jfs2_ace) + jfs2_ace->entryLen);
+ }
+
+ DEBUG(10,("jfs2 get_nt_acl finished successfully\n"));
+
+ return True;
+}
+
+static NTSTATUS aixjfs2_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp, uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS status;
+ struct SMB4ACL_T *pacl = NULL;
+ bool result;
+ bool retryPosix = False;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *ppdesc = NULL;
+ result = aixjfs2_get_nfs4_acl(frame, fsp->fsp_name->base_name, &pacl,
+ &retryPosix);
+ if (retryPosix)
+ {
+ TALLOC_FREE(frame);
+ DEBUG(10, ("retrying with posix acl...\n"));
+ return posix_fget_nt_acl(fsp, security_info,
+ mem_ctx, ppdesc);
+ }
+ if (result==False) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = smb_fget_nt_acl_nfs4(
+ fsp, NULL, security_info, mem_ctx, ppdesc, pacl);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static int aixjfs2_sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob)
+{
+ struct SMB4ACL_T *pacl = NULL;
+ bool result;
+ bool retryPosix = False;
+
+ result = aixjfs2_get_nfs4_acl(mem_ctx, fsp->fsp_name->base_name, &pacl,
+ &retryPosix);
+ if (retryPosix)
+ {
+ return posix_sys_acl_blob_get_fd(handle, fsp, mem_ctx, blob_description, blob);
+ }
+
+ /* Now way to linarlise NFS4 ACLs at the moment, but the NT ACL is pretty close in this case */
+ errno = ENOSYS;
+ return -1;
+}
+
+static SMB_ACL_T aixjfs2_get_posix_acl(const char *path, acl_type_t type, TALLOC_CTX *mem_ctx)
+{
+ aixc_acl_t *pacl;
+ AIXJFS2_ACL_T *acl;
+ SMB_ACL_T result = NULL;
+ int ret;
+
+ acl = aixjfs2_getacl_alloc(path, &type);
+
+ if (acl == NULL) {
+ DEBUG(10, ("aixjfs2_getacl failed for %s with %s\n",
+ path, strerror(errno)));
+ if (errno == 0) {
+ errno = EINVAL;
+ }
+ goto done;
+ }
+
+ pacl = &acl->aixc_acl[0];
+ DEBUG(10, ("len: %d, mode: %d\n",
+ pacl->acl_len, pacl->acl_mode));
+
+ result = aixacl_to_smbacl(pacl, mem_ctx);
+ if (result == NULL) {
+ goto done;
+ }
+
+ done:
+ if (errno != 0) {
+ TALLOC_FREE(result);
+ }
+ return result;
+}
+
+SMB_ACL_T aixjfs2_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ acl_type_t aixjfs2_type;
+
+ switch(type) {
+ case SMB_ACL_TYPE_ACCESS:
+ aixjfs2_type.u64 = ACL_AIXC;
+ break;
+ case SMB_ACL_TYPE_DEFAULT:
+ DEBUG(0, ("Got AIX JFS2 unsupported type: %d\n", type));
+ return NULL;
+ default:
+ DEBUG(0, ("Got invalid type: %d\n", type));
+ smb_panic("exiting");
+ }
+
+ return aixjfs2_get_posix_acl(fsp->fsp_name->base_name,
+ aixjfs2_type, mem_ctx);
+}
+
+/*
+ * Test whether we have that aclType support on the given path
+ */
+static int aixjfs2_query_acl_support(
+ char *filepath,
+ uint64_t aclType,
+ acl_type_t *pacl_type_info
+)
+{
+ acl_types_list_t acl_type_list;
+ size_t acl_type_list_len = sizeof(acl_types_list_t);
+ uint32_t i;
+
+ memset(&acl_type_list, 0, sizeof(acl_type_list));
+
+ if (aclx_gettypes(filepath, &acl_type_list, &acl_type_list_len)) {
+ DEBUG(2, ("aclx_gettypes failed with error %s for %s\n",
+ strerror(errno), filepath));
+ return -1;
+ }
+
+ for(i=0; i<acl_type_list.num_entries; i++) {
+ if (acl_type_list.entries[i].u64==aclType) {
+ memcpy(pacl_type_info, acl_type_list.entries + i, sizeof(acl_type_t));
+ DEBUG(10, ("found %s ACL support for %s\n",
+ pacl_type_info->acl_type, filepath));
+ return 0;
+ }
+ }
+
+ return 1; /* haven't found that ACL type. */
+}
+
+static bool aixjfs2_process_smbacl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct SMB4ACL_T *smbacl)
+{
+ struct SMB4ACE_T *smbace;
+ TALLOC_CTX *mem_ctx;
+ nfs4_acl_int_t *jfs2acl;
+ int32_t entryLen;
+ uint32_t aclLen, naces;
+ int rc;
+ acl_type_t acltype;
+
+ DEBUG(10, ("jfs2_process_smbacl invoked on %s\n", fsp_str_dbg(fsp)));
+
+ /* no need to be freed which is alloced with mem_ctx */
+ mem_ctx = talloc_tos();
+
+ entryLen = sizeof(nfs4_ace_int_t);
+ if (entryLen & 0x03)
+ entryLen = entryLen + 4 - (entryLen%4);
+
+ naces = smb_get_naces(smbacl);
+ aclLen = ACL_V4_SIZ + naces * entryLen;
+ jfs2acl = (nfs4_acl_int_t *)TALLOC_SIZE(mem_ctx, aclLen);
+ if (jfs2acl==NULL) {
+ DEBUG(0, ("TALLOC_SIZE failed\n"));
+ errno = ENOMEM;
+ return False;
+ }
+
+ jfs2acl->aclLength = ACL_V4_SIZ;
+ jfs2acl->aclVersion = NFS4_ACL_INT_STRUCT_VERSION;
+ jfs2acl->aclEntryN = 0;
+
+ for(smbace = smb_first_ace4(smbacl); smbace!=NULL; smbace = smb_next_ace4(smbace))
+ {
+ SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace);
+ nfs4_ace_int_t *jfs2_ace = (nfs4_ace_int_t *)(((char *)jfs2acl) + jfs2acl->aclLength);
+
+ memset(jfs2_ace, 0, entryLen);
+ jfs2_ace->entryLen = entryLen; /* won't store textual "who" */
+ jfs2_ace->aceType = aceprop->aceType; /* only ACCESS|DENY supported by jfs2 */
+ jfs2_ace->aceFlags = aceprop->aceFlags;
+ jfs2_ace->aceMask = aceprop->aceMask;
+ jfs2_ace->flags = (aceprop->flags&SMB_ACE4_ID_SPECIAL) ? ACE4_ID_SPECIAL : 0;
+
+ /* don't care it's real content is only 16 or 32 bit */
+ jfs2_ace->aceWho.id = aceprop->who.id;
+
+ /* iterate to the next jfs2 ace */
+ jfs2acl->aclLength += jfs2_ace->entryLen;
+ jfs2acl->aclEntryN++;
+ }
+ SMB_ASSERT(jfs2acl->aclEntryN==naces);
+
+ /* Don't query it (again) */
+ memset(&acltype, 0, sizeof(acl_type_t));
+ acltype.u64 = ACL_NFS4;
+
+ /* won't set S_ISUID - the only one JFS2/NFS4 accepts */
+ rc = aclx_put(
+ fsp->fsp_name->base_name,
+ SET_ACL, /* set only the ACL, not mode bits */
+ acltype, /* not a pointer !!! */
+ jfs2acl,
+ jfs2acl->aclLength,
+ 0 /* don't set here mode bits */
+ );
+ if (rc) {
+ DEBUG(8, ("aclx_put failed with %s\n", strerror(errno)));
+ return False;
+ }
+
+ DEBUG(10, ("jfs2_process_smbacl succeeded.\n"));
+ return True;
+}
+
+static NTSTATUS aixjfs2_set_nt_acl_common(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd)
+{
+ acl_type_t acl_type_info;
+ NTSTATUS result = NT_STATUS_ACCESS_DENIED;
+ int rc;
+
+ rc = aixjfs2_query_acl_support(
+ fsp->fsp_name->base_name,
+ ACL_NFS4,
+ &acl_type_info);
+
+ if (rc==0)
+ {
+ result = smb_set_nt_acl_nfs4(handle,
+ fsp, NULL, security_info_sent, psd,
+ aixjfs2_process_smbacl);
+ } else if (rc==1) { /* assume POSIX ACL - by default... */
+ result = set_nt_acl(fsp, security_info_sent, psd);
+ } else
+ result = map_nt_error_from_unix(errno); /* query failed */
+
+ return result;
+}
+
+NTSTATUS aixjfs2_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd)
+{
+ return aixjfs2_set_nt_acl_common(handle, fsp, security_info_sent, psd);
+}
+
+int aixjfs2_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ struct acl *acl_aixc;
+ acl_type_t acl_type_info;
+ int rc;
+
+ DEBUG(10, ("aixjfs2_sys_acl_set_fd invoked for %s\n", fsp_str_dbg(fsp)));
+
+ rc = aixjfs2_query_acl_support(fsp->fsp_name->base_name, ACL_AIXC,
+ &acl_type_info);
+ if (rc) {
+ DEBUG(8, ("jfs2_set_nt_acl: AIXC support not found\n"));
+ return -1;
+ }
+
+ acl_aixc = aixacl_smb_to_aixacl(type, theacl);
+ if (!acl_aixc)
+ return -1;
+
+ if (fsp->fsp_flags.is_pathref) {
+ /*
+ * This is no longer a handle based call.
+ */
+ return aclx_put(fsp->fsp_name->base_name,
+ SET_ACL,
+ acl_type_info,
+ acl_aixc,
+ acl_aixc->acl_len,
+ 0);
+ }
+
+ rc = aclx_fput(
+ fsp_get_io_fd(fsp),
+ SET_ACL, /* set only the ACL, not mode bits */
+ acl_type_info,
+ acl_aixc,
+ acl_aixc->acl_len,
+ 0
+ );
+ if (rc) {
+ DEBUG(2, ("aclx_fput failed with %s for %s\n",
+ strerror(errno), fsp_str_dbg(fsp)));
+ return -1;
+ }
+
+ return 0;
+}
+
+int aixjfs2_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ /* Not available under AIXC ACL */
+ /* Don't report here any error otherwise */
+ /* upper layer will break the normal execution */
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_aixacl2_fns = {
+ .stat_fn = nfs4_acl_stat,
+ .fstat_fn = nfs4_acl_fstat,
+ .lstat_fn = nfs4_acl_lstat,
+ .fstatat_fn = nfs4_acl_fstatat,
+ .fget_nt_acl_fn = aixjfs2_fget_nt_acl,
+ .fset_nt_acl_fn = aixjfs2_fset_nt_acl,
+ .sys_acl_get_fd_fn = aixjfs2_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = aixjfs2_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = aixjfs2_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = aixjfs2_sys_acl_delete_def_fd
+};
+
+static_decl_vfs;
+NTSTATUS vfs_aixacl2_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, AIXACL2_MODULE_NAME,
+ &vfs_aixacl2_fns);
+}
diff --git a/source3/modules/vfs_aixacl_util.c b/source3/modules/vfs_aixacl_util.c
new file mode 100644
index 0000000..38b53eb
--- /dev/null
+++ b/source3/modules/vfs_aixacl_util.c
@@ -0,0 +1,288 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set posix acls
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "vfs_aixacl_util.h"
+
+SMB_ACL_T aixacl_to_smbacl(struct acl *file_acl, TALLOC_CTX *mem_ctx)
+{
+ struct acl_entry *acl_entry;
+ struct ace_id *idp;
+
+ struct smb_acl_t *result = sys_acl_init(mem_ctx);
+ struct smb_acl_entry *ace;
+ int i;
+
+ if (result == NULL) {
+ return NULL;
+ }
+
+ /* Point to the first acl entry in the acl */
+ acl_entry = file_acl->acl_ext;
+
+ DEBUG(10,("acl_entry is %p\n",(void *)acl_entry));
+ DEBUG(10,("acl_last(file_acl) id %p\n",(void *)acl_last(file_acl)));
+
+ /* Check if the extended acl bit is on. *
+ * If it isn't, do not show the *
+ * contents of the acl since AIX intends *
+ * the extended info to remain unused */
+
+ if(file_acl->acl_mode & S_IXACL){
+ /* while we are not pointing to the very end */
+ while(acl_entry < acl_last(file_acl)) {
+ /* before we malloc anything, make sure this is */
+ /* a valid acl entry and one that we want to map */
+ idp = id_nxt(acl_entry->ace_id);
+ if((acl_entry->ace_type == ACC_SPECIFY ||
+ (acl_entry->ace_type == ACC_PERMIT)) && (idp != id_last(acl_entry))) {
+ acl_entry = acl_nxt(acl_entry);
+ continue;
+ }
+
+ idp = acl_entry->ace_id;
+ DEBUG(10,("idp->id_data is %d\n",idp->id_data[0]));
+
+ result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count+1);
+ if (result == NULL) {
+ DEBUG(0, ("talloc_realloc failed\n"));
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ DEBUG(10,("idp->id_type is %d\n",idp->id_type));
+ ace = &result->acl[result->count];
+
+ ace->a_type = idp->id_type;
+
+ switch(ace->a_type) {
+ case ACEID_USER: {
+ ace->info.user.uid = idp->id_data[0];
+ DEBUG(10,("case ACEID_USER ace->info.user.uid is %d\n",ace->info.user.uid));
+ ace->a_type = SMB_ACL_USER;
+ break;
+ }
+
+ case ACEID_GROUP: {
+ ace->info.group.gid = idp->id_data[0];
+ DEBUG(10,("case ACEID_GROUP ace->info.group.gid is %d\n",ace->info.group.gid));
+ ace->a_type = SMB_ACL_GROUP;
+ break;
+ }
+ default:
+ break;
+ }
+ /* The access in the acl entries must be left shifted by *
+ * three bites, because they will ultimately be compared *
+ * to S_IRUSR, S_IWUSR, and S_IXUSR. */
+
+ switch(acl_entry->ace_type){
+ case ACC_PERMIT:
+ case ACC_SPECIFY:
+ ace->a_perm = acl_entry->ace_access;
+ ace->a_perm <<= 6;
+ DEBUG(10,("ace->a_perm is %d\n",ace->a_perm));
+ break;
+ case ACC_DENY:
+ /* Since there is no way to return a DENY acl entry *
+ * change to PERMIT and then shift. */
+ DEBUG(10,("acl_entry->ace_access is %d\n",acl_entry->ace_access));
+ ace->a_perm = ~acl_entry->ace_access & 7;
+ DEBUG(10,("ace->a_perm is %d\n",ace->a_perm));
+ ace->a_perm <<= 6;
+ break;
+ default:
+ DEBUG(0, ("unknown ace->type\n"));
+ TALLOC_FREE(result);
+ return(0);
+ }
+
+ result->count++;
+ ace->a_perm |= (ace->a_perm & S_IRUSR) ? SMB_ACL_READ : 0;
+ ace->a_perm |= (ace->a_perm & S_IWUSR) ? SMB_ACL_WRITE : 0;
+ ace->a_perm |= (ace->a_perm & S_IXUSR) ? SMB_ACL_EXECUTE : 0;
+ DEBUG(10,("ace->a_perm is %d\n",ace->a_perm));
+
+ DEBUG(10,("acl_entry = %p\n",(void *)acl_entry));
+ DEBUG(10,("The ace_type is %d\n",acl_entry->ace_type));
+
+ acl_entry = acl_nxt(acl_entry);
+ }
+ } /* end of if enabled */
+
+ /* Since owner, group, other acl entries are not *
+ * part of the acl entries in an acl, they must *
+ * be dummied up to become part of the list. */
+
+ for( i = 1; i < 4; i++) {
+ DEBUG(10,("i is %d\n",i));
+
+ result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count+1);
+ if (result->acl == NULL) {
+ TALLOC_FREE(result);
+ DEBUG(0, ("talloc_realloc failed\n"));
+ errno = ENOMEM;
+ DEBUG(0,("Error in AIX sys_acl_get_file is %d\n",errno));
+ return NULL;
+ }
+
+ ace = &result->acl[result->count];
+
+ ace->info.user.uid = 0;
+ ace->info.group.gid = 0;
+ DEBUG(10,("ace->info.user.uid = %d\n",ace->info.user.uid));
+
+ switch(i) {
+ case 2:
+ ace->a_perm = file_acl->g_access << 6;
+ ace->a_type = SMB_ACL_GROUP_OBJ;
+ break;
+
+ case 3:
+ ace->a_perm = file_acl->o_access << 6;
+ ace->a_type = SMB_ACL_OTHER;
+ break;
+
+ case 1:
+ ace->a_perm = file_acl->u_access << 6;
+ ace->a_type = SMB_ACL_USER_OBJ;
+ break;
+
+ default:
+ return(NULL);
+
+ }
+ ace->a_perm |= ((ace->a_perm & S_IRUSR) ? SMB_ACL_READ : 0);
+ ace->a_perm |= ((ace->a_perm & S_IWUSR) ? SMB_ACL_WRITE : 0);
+ ace->a_perm |= ((ace->a_perm & S_IXUSR) ? SMB_ACL_EXECUTE : 0);
+
+ memcpy(&result->acl[result->count],ace,sizeof(struct smb_acl_entry));
+ result->count++;
+ DEBUG(10,("ace->a_perm = %d\n",ace->a_perm));
+ DEBUG(10,("ace->a_type = %d\n",ace->a_type));
+ }
+
+ return result;
+}
+
+static ushort aixacl_smb_to_aixperm(SMB_ACL_PERM_T a_perm)
+{
+ ushort ret = (ushort)0;
+ if (a_perm & SMB_ACL_READ)
+ ret |= R_ACC;
+ if (a_perm & SMB_ACL_WRITE)
+ ret |= W_ACC;
+ if (a_perm & SMB_ACL_EXECUTE)
+ ret |= X_ACC;
+ return ret;
+}
+
+struct acl *aixacl_smb_to_aixacl(SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl)
+{
+ struct smb_acl_entry *smb_entry = NULL;
+ struct acl *file_acl = NULL;
+ struct acl *file_acl_temp = NULL;
+ struct acl_entry *acl_entry = NULL;
+ struct ace_id *ace_id = NULL;
+ unsigned int id_type;
+ unsigned int acl_length;
+ int i;
+
+ DEBUG(10,("Entering aixacl_smb_to_aixacl\n"));
+ /* AIX has no default ACL */
+ if(acltype == SMB_ACL_TYPE_DEFAULT)
+ return NULL;
+
+ acl_length = BUFSIZ;
+ file_acl = (struct acl *)SMB_MALLOC(BUFSIZ);
+ if(file_acl == NULL) {
+ errno = ENOMEM;
+ DEBUG(0,("Error in aixacl_smb_to_aixacl is %d\n",errno));
+ return NULL;
+ }
+
+ memset(file_acl,0,BUFSIZ);
+
+ file_acl->acl_len = ACL_SIZ;
+ file_acl->acl_mode = S_IXACL;
+
+ for(i=0; i<theacl->count; i++ ) {
+ smb_entry = &(theacl->acl[i]);
+ id_type = smb_entry->a_type;
+ DEBUG(10,("The id_type is %d\n",id_type));
+
+ switch(id_type) {
+ case SMB_ACL_USER_OBJ:
+ file_acl->u_access = aixacl_smb_to_aixperm(smb_entry->a_perm);
+ continue;
+ case SMB_ACL_GROUP_OBJ:
+ file_acl->g_access = aixacl_smb_to_aixperm(smb_entry->a_perm);
+ continue;
+ case SMB_ACL_OTHER:
+ file_acl->o_access = aixacl_smb_to_aixperm(smb_entry->a_perm);
+ continue;
+ case SMB_ACL_MASK:
+ continue;
+ case SMB_ACL_GROUP:
+ break; /* process this */
+ case SMB_ACL_USER:
+ break; /* process this */
+ default: /* abnormal case */
+ DEBUG(10,("The id_type is unknown !\n"));
+ continue;
+ }
+
+ if((file_acl->acl_len + sizeof(struct acl_entry)) > acl_length) {
+ acl_length += sizeof(struct acl_entry);
+ file_acl_temp = (struct acl *)SMB_MALLOC(acl_length);
+ if(file_acl_temp == NULL) {
+ SAFE_FREE(file_acl);
+ errno = ENOMEM;
+ DEBUG(0,("Error in aixacl_smb_to_aixacl is %d\n",errno));
+ return NULL;
+ }
+
+ memcpy(file_acl_temp,file_acl,file_acl->acl_len);
+ SAFE_FREE(file_acl);
+ file_acl = file_acl_temp;
+ }
+
+ acl_entry = (struct acl_entry *)((char *)file_acl + file_acl->acl_len);
+ file_acl->acl_len += sizeof(struct acl_entry);
+ acl_entry->ace_len = sizeof(struct acl_entry); /* contains 1 ace_id */
+ acl_entry->ace_access = aixacl_smb_to_aixperm(smb_entry->a_perm);
+
+ /* In order to use this, we'll need to wait until we can get denies */
+ /* if(!acl_entry->ace_access && acl_entry->ace_type == ACC_PERMIT)
+ acl_entry->ace_type = ACC_SPECIFY; */
+
+ acl_entry->ace_type = ACC_SPECIFY;
+
+ ace_id = acl_entry->ace_id;
+
+ ace_id->id_type = (smb_entry->a_type==SMB_ACL_GROUP) ? ACEID_GROUP : ACEID_USER;
+ DEBUG(10,("The id type is %d\n",ace_id->id_type));
+ ace_id->id_len = sizeof(struct ace_id); /* contains 1 id_data */
+ ace_id->id_data[0] = (smb_entry->a_type==SMB_ACL_GROUP) ? smb_entry->info.group.gid : smb_entry->info.user.uid;
+ }
+
+ return file_acl;
+}
diff --git a/source3/modules/vfs_aixacl_util.h b/source3/modules/vfs_aixacl_util.h
new file mode 100644
index 0000000..e283a3d
--- /dev/null
+++ b/source3/modules/vfs_aixacl_util.h
@@ -0,0 +1,22 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set posix acls
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+SMB_ACL_T aixacl_to_smbacl( struct acl *file_acl, TALLOC_CTX *mem_ctx);
+struct acl *aixacl_smb_to_aixacl(SMB_ACL_TYPE_T acltype, SMB_ACL_T theacl);
+
diff --git a/source3/modules/vfs_audit.c b/source3/modules/vfs_audit.c
new file mode 100644
index 0000000..2b01a6a
--- /dev/null
+++ b/source3/modules/vfs_audit.c
@@ -0,0 +1,355 @@
+/*
+ * Auditing VFS module for samba. Log selected file operations to syslog
+ * facility.
+ *
+ * Copyright (C) Tim Potter 1999-2000
+ * Copyright (C) Alexander Bokovoy 2002
+ * Copyright (C) Stefan (metze) Metzmacher 2002
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/syslog.h"
+#include "smbd/smbd.h"
+#include "lib/param/loadparm.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static int audit_syslog_facility(vfs_handle_struct *handle)
+{
+ static const struct enum_list enum_log_facilities[] = {
+#ifdef LOG_AUTH
+ { LOG_AUTH, "AUTH" },
+#endif
+#ifdef LOG_AUTHPRIV
+ { LOG_AUTHPRIV, "AUTHPRIV" },
+#endif
+#ifdef LOG_AUDIT
+ { LOG_AUDIT, "AUDIT" },
+#endif
+#ifdef LOG_CONSOLE
+ { LOG_CONSOLE, "CONSOLE" },
+#endif
+#ifdef LOG_CRON
+ { LOG_CRON, "CRON" },
+#endif
+#ifdef LOG_DAEMON
+ { LOG_DAEMON, "DAEMON" },
+#endif
+#ifdef LOG_FTP
+ { LOG_FTP, "FTP" },
+#endif
+#ifdef LOG_INSTALL
+ { LOG_INSTALL, "INSTALL" },
+#endif
+#ifdef LOG_KERN
+ { LOG_KERN, "KERN" },
+#endif
+#ifdef LOG_LAUNCHD
+ { LOG_LAUNCHD, "LAUNCHD" },
+#endif
+#ifdef LOG_LFMT
+ { LOG_LFMT, "LFMT" },
+#endif
+#ifdef LOG_LPR
+ { LOG_LPR, "LPR" },
+#endif
+#ifdef LOG_MAIL
+ { LOG_MAIL, "MAIL" },
+#endif
+#ifdef LOG_MEGASAFE
+ { LOG_MEGASAFE, "MEGASAFE" },
+#endif
+#ifdef LOG_NETINFO
+ { LOG_NETINFO, "NETINFO" },
+#endif
+#ifdef LOG_NEWS
+ { LOG_NEWS, "NEWS" },
+#endif
+#ifdef LOG_NFACILITIES
+ { LOG_NFACILITIES, "NFACILITIES" },
+#endif
+#ifdef LOG_NTP
+ { LOG_NTP, "NTP" },
+#endif
+#ifdef LOG_RAS
+ { LOG_RAS, "RAS" },
+#endif
+#ifdef LOG_REMOTEAUTH
+ { LOG_REMOTEAUTH, "REMOTEAUTH" },
+#endif
+#ifdef LOG_SECURITY
+ { LOG_SECURITY, "SECURITY" },
+#endif
+#ifdef LOG_SYSLOG
+ { LOG_SYSLOG, "SYSLOG" },
+#endif
+#ifdef LOG_USER
+ { LOG_USER, "USER" },
+#endif
+#ifdef LOG_UUCP
+ { LOG_UUCP, "UUCP" },
+#endif
+ { LOG_LOCAL0, "LOCAL0" },
+ { LOG_LOCAL1, "LOCAL1" },
+ { LOG_LOCAL2, "LOCAL2" },
+ { LOG_LOCAL3, "LOCAL3" },
+ { LOG_LOCAL4, "LOCAL4" },
+ { LOG_LOCAL5, "LOCAL5" },
+ { LOG_LOCAL6, "LOCAL6" },
+ { LOG_LOCAL7, "LOCAL7" },
+ { -1, NULL }
+ };
+
+ int facility;
+
+ facility = lp_parm_enum(SNUM(handle->conn), "audit", "facility", enum_log_facilities, LOG_USER);
+
+ return facility;
+}
+
+
+static int audit_syslog_priority(vfs_handle_struct *handle)
+{
+ static const struct enum_list enum_log_priorities[] = {
+ { LOG_EMERG, "EMERG" },
+ { LOG_ALERT, "ALERT" },
+ { LOG_CRIT, "CRIT" },
+ { LOG_ERR, "ERR" },
+ { LOG_WARNING, "WARNING" },
+ { LOG_NOTICE, "NOTICE" },
+ { LOG_INFO, "INFO" },
+ { LOG_DEBUG, "DEBUG" },
+ { -1, NULL }
+ };
+
+ int priority;
+
+ priority = lp_parm_enum(SNUM(handle->conn), "audit", "priority",
+ enum_log_priorities, LOG_NOTICE);
+ if (priority == -1) {
+ priority = LOG_WARNING;
+ }
+
+ return priority;
+}
+
+/* Implementation of vfs_ops. Pass everything on to the default
+ operation but log event first. */
+
+static int audit_connect(vfs_handle_struct *handle, const char *svc, const char *user)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_CONNECT(handle, svc, user);
+ if (result < 0) {
+ return result;
+ }
+
+ openlog("smbd_audit", LOG_PID, audit_syslog_facility(handle));
+
+ syslog(audit_syslog_priority(handle), "connect to service %s by user %s\n",
+ svc, user);
+
+ return 0;
+}
+
+static void audit_disconnect(vfs_handle_struct *handle)
+{
+ syslog(audit_syslog_priority(handle), "disconnected\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+
+ return;
+}
+
+static int audit_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+
+ syslog(audit_syslog_priority(handle), "mkdirat %s %s%s\n",
+ full_fname->base_name,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int audit_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+
+ syslog(audit_syslog_priority(handle),
+ "openat %s (fd %d) %s%s%s\n",
+ fsp_str_dbg(fsp), result,
+ ((how->flags & O_WRONLY) || (how->flags & O_RDWR)) ?
+ "for writing " : "",
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+
+ return result;
+}
+
+static int audit_close(vfs_handle_struct *handle, files_struct *fsp)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+
+ syslog(audit_syslog_priority(handle), "close fd %d %s%s\n",
+ fsp_get_pathref_fd(fsp),
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+
+ return result;
+}
+
+static int audit_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+ int result;
+ int saved_errno = 0;
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ TALLOC_FREE(full_fname_src);
+ errno = ENOMEM;
+ return -1;
+ }
+ result = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ if (result == -1) {
+ saved_errno = errno;
+ }
+
+ syslog(audit_syslog_priority(handle), "renameat %s -> %s %s%s\n",
+ full_fname_src->base_name,
+ full_fname_dst->base_name,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+
+ return result;
+}
+
+static int audit_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+
+ syslog(audit_syslog_priority(handle), "unlinkat %s %s%s\n",
+ full_fname->base_name,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int audit_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+
+ syslog(audit_syslog_priority(handle), "fchmod %s mode 0x%x %s%s\n",
+ fsp->fsp_name->base_name, mode,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+
+ return result;
+}
+
+static struct vfs_fn_pointers vfs_audit_fns = {
+ .connect_fn = audit_connect,
+ .disconnect_fn = audit_disconnect,
+ .mkdirat_fn = audit_mkdirat,
+ .openat_fn = audit_openat,
+ .close_fn = audit_close,
+ .renameat_fn = audit_renameat,
+ .unlinkat_fn = audit_unlinkat,
+ .fchmod_fn = audit_fchmod,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_audit_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "audit",
+ &vfs_audit_fns);
+}
diff --git a/source3/modules/vfs_btrfs.c b/source3/modules/vfs_btrfs.c
new file mode 100644
index 0000000..9031252
--- /dev/null
+++ b/source3/modules/vfs_btrfs.c
@@ -0,0 +1,887 @@
+/*
+ * Module to make use of awesome Btrfs features
+ *
+ * Copyright (C) David Disseldorp 2011-2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include <linux/ioctl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <libgen.h>
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "librpc/gen_ndr/smbXsrv.h"
+#include "librpc/gen_ndr/ioctl.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "offload_token.h"
+
+static uint32_t btrfs_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *_ts_res)
+{
+ uint32_t fs_capabilities;
+ enum timestamp_set_resolution ts_res;
+
+ /* inherit default capabilities, expose compression support */
+ fs_capabilities = SMB_VFS_NEXT_FS_CAPABILITIES(handle, &ts_res);
+ fs_capabilities |= (FILE_FILE_COMPRESSION
+ | FILE_SUPPORTS_BLOCK_REFCOUNTING);
+ *_ts_res = ts_res;
+
+ return fs_capabilities;
+}
+
+#define SHADOW_COPY_PREFIX "@GMT-" /* vfs_shadow_copy format */
+#define SHADOW_COPY_PATH_FORMAT "@GMT-%Y.%m.%d-%H.%M.%S"
+
+#define BTRFS_SUBVOL_RDONLY (1ULL << 1)
+#define BTRFS_SUBVOL_NAME_MAX 4039
+#define BTRFS_PATH_NAME_MAX 4087
+struct btrfs_ioctl_vol_args_v2 {
+ int64_t fd;
+ uint64_t transid;
+ uint64_t flags;
+ uint64_t unused[4];
+ char name[BTRFS_SUBVOL_NAME_MAX + 1];
+};
+struct btrfs_ioctl_vol_args {
+ int64_t fd;
+ char name[BTRFS_PATH_NAME_MAX + 1];
+};
+
+struct btrfs_ioctl_clone_range_args {
+ int64_t src_fd;
+ uint64_t src_offset;
+ uint64_t src_length;
+ uint64_t dest_offset;
+};
+
+#define BTRFS_IOCTL_MAGIC 0x94
+#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \
+ struct btrfs_ioctl_clone_range_args)
+#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \
+ struct btrfs_ioctl_vol_args)
+#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \
+ struct btrfs_ioctl_vol_args_v2)
+
+static struct vfs_offload_ctx *btrfs_offload_ctx;
+
+struct btrfs_offload_read_state {
+ struct vfs_handle_struct *handle;
+ files_struct *fsp;
+ uint32_t flags;
+ uint64_t xferlen;
+ DATA_BLOB token;
+};
+
+static void btrfs_offload_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *btrfs_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct btrfs_offload_read_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct btrfs_offload_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct btrfs_offload_read_state) {
+ .handle = handle,
+ .fsp = fsp,
+ };
+
+ status = vfs_offload_token_ctx_init(fsp->conn->sconn->client,
+ &btrfs_offload_ctx);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (fsctl == FSCTL_DUP_EXTENTS_TO_FILE) {
+ status = vfs_offload_token_create_blob(state, fsp, fsctl,
+ &state->token);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = vfs_offload_token_db_store_fsp(btrfs_offload_ctx, fsp,
+ &state->token);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp,
+ fsctl, ttl, offset, to_copy);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, btrfs_offload_read_done, req);
+ return req;
+}
+
+static void btrfs_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct btrfs_offload_read_state *state = tevent_req_data(
+ req, struct btrfs_offload_read_state);
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq,
+ state->handle,
+ state,
+ &state->flags,
+ &state->xferlen,
+ &state->token);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = vfs_offload_token_db_store_fsp(btrfs_offload_ctx,
+ state->fsp,
+ &state->token);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS btrfs_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token)
+{
+ struct btrfs_offload_read_state *state = tevent_req_data(
+ req, struct btrfs_offload_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *flags = state->flags;
+ *xferlen = state->xferlen;
+ token->length = state->token.length;
+ token->data = talloc_move(mem_ctx, &state->token.data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct btrfs_offload_write_state {
+ struct vfs_handle_struct *handle;
+ off_t copied;
+ bool need_unbecome_user;
+};
+
+static void btrfs_offload_write_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct btrfs_offload_write_state *state =
+ tevent_req_data(req,
+ struct btrfs_offload_write_state);
+ bool ok;
+
+ if (!state->need_unbecome_user) {
+ return;
+ }
+
+ ok = unbecome_user_without_service();
+ SMB_ASSERT(ok);
+ state->need_unbecome_user = false;
+}
+
+static void btrfs_offload_write_done(struct tevent_req *subreq);
+
+static struct tevent_req *btrfs_offload_write_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num)
+{
+ struct tevent_req *req = NULL;
+ struct btrfs_offload_write_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct btrfs_ioctl_clone_range_args cr_args;
+ struct lock_struct src_lck;
+ struct lock_struct dest_lck;
+ off_t src_off = transfer_offset;
+ files_struct *src_fsp = NULL;
+ int ret;
+ bool handle_offload_write = true;
+ bool do_locking = false;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct btrfs_offload_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->handle = handle;
+
+ tevent_req_set_cleanup_fn(req, btrfs_offload_write_cleanup);
+
+ status = vfs_offload_token_db_fetch_fsp(btrfs_offload_ctx,
+ token, &src_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ switch (fsctl) {
+ case FSCTL_SRV_COPYCHUNK:
+ case FSCTL_SRV_COPYCHUNK_WRITE:
+ do_locking = true;
+ break;
+
+ case FSCTL_DUP_EXTENTS_TO_FILE:
+ /* dup extents does not use locking */
+ break;
+
+ default:
+ handle_offload_write = false;
+ break;
+ }
+
+ if (num == 0) {
+ /*
+ * With a @src_length of zero, BTRFS_IOC_CLONE_RANGE clones
+ * all data from @src_offset->EOF! This is certainly not what
+ * the caller expects, and not what vfs_default does.
+ */
+ handle_offload_write = false;
+ }
+
+ if (!handle_offload_write) {
+ subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle,
+ state,
+ ev,
+ fsctl,
+ token,
+ transfer_offset,
+ dest_fsp,
+ dest_off,
+ num);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ btrfs_offload_write_done,
+ req);
+ return req;
+ }
+
+ status = vfs_offload_token_check_handles(
+ fsctl, src_fsp, dest_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ ok = become_user_without_service_by_fsp(src_fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+ state->need_unbecome_user = true;
+
+ status = vfs_stat_fsp(src_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (src_fsp->fsp_name->st.st_ex_size < src_off + num) {
+ /* [MS-SMB2] Handling a Server-Side Data Copy Request */
+ tevent_req_nterror(req, NT_STATUS_INVALID_VIEW_SIZE);
+ return tevent_req_post(req, ev);
+ }
+
+ if (do_locking) {
+ init_strict_lock_struct(src_fsp,
+ src_fsp->op->global->open_persistent_id,
+ src_off,
+ num,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(src_fsp),
+ &src_lck);
+ if (!SMB_VFS_STRICT_LOCK_CHECK(src_fsp->conn, src_fsp, &src_lck)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ ok = unbecome_user_without_service();
+ SMB_ASSERT(ok);
+ state->need_unbecome_user = false;
+
+ if (do_locking) {
+ init_strict_lock_struct(dest_fsp,
+ dest_fsp->op->global->open_persistent_id,
+ dest_off,
+ num,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(dest_fsp),
+ &dest_lck);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(dest_fsp->conn, dest_fsp, &dest_lck)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ ZERO_STRUCT(cr_args);
+ cr_args.src_fd = fsp_get_io_fd(src_fsp);
+ cr_args.src_offset = (uint64_t)src_off;
+ cr_args.dest_offset = (uint64_t)dest_off;
+ cr_args.src_length = (uint64_t)num;
+
+ ret = ioctl(fsp_get_io_fd(dest_fsp), BTRFS_IOC_CLONE_RANGE, &cr_args);
+ if (ret < 0) {
+ /*
+ * BTRFS_IOC_CLONE_RANGE only supports 'sectorsize' aligned
+ * cloning. Which is 4096 by default, therefore fall back to
+ * manual read/write on failure.
+ */
+ DEBUG(5, ("BTRFS_IOC_CLONE_RANGE failed: %s, length %llu, "
+ "src fd: %lld off: %llu, dest fd: %d off: %llu\n",
+ strerror(errno),
+ (unsigned long long)cr_args.src_length,
+ (long long)cr_args.src_fd,
+ (unsigned long long)cr_args.src_offset,
+ fsp_get_io_fd(dest_fsp),
+ (unsigned long long)cr_args.dest_offset));
+ subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle,
+ state,
+ ev,
+ fsctl,
+ token,
+ transfer_offset,
+ dest_fsp,
+ dest_off,
+ num);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /* wait for subreq completion */
+ tevent_req_set_callback(subreq,
+ btrfs_offload_write_done,
+ req);
+ return req;
+ }
+
+ DEBUG(5, ("BTRFS_IOC_CLONE_RANGE returned %d\n", ret));
+ /* BTRFS_IOC_CLONE_RANGE is all or nothing */
+ state->copied = num;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+/* only used if the request is passed through to next VFS module */
+static void btrfs_offload_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct btrfs_offload_write_state *state =
+ tevent_req_data(req,
+ struct btrfs_offload_write_state);
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle,
+ subreq,
+ &state->copied);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS btrfs_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ struct btrfs_offload_write_state *state =
+ tevent_req_data(req,
+ struct btrfs_offload_write_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DEBUG(4, ("server side copy chunk failed: %s\n",
+ nt_errstr(status)));
+ tevent_req_received(req);
+ return status;
+ }
+
+ DEBUG(10, ("server side copy chunk copied %llu\n",
+ (unsigned long long)state->copied));
+ *copied = state->copied;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS btrfs_fget_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ struct sys_proc_fd_path_buf buf;
+ int ret;
+ long flags = 0;
+ int fsp_fd = fsp_get_pathref_fd(fsp);
+ int fd = -1;
+ NTSTATUS status;
+
+ if (!fsp->fsp_flags.is_pathref) {
+ ret = ioctl(fsp_fd, FS_IOC_GETFLAGS, &flags);
+ if (ret < 0) {
+ DBG_WARNING("FS_IOC_GETFLAGS failed: %s, fd %lld\n",
+ strerror(errno), (long long)fd);
+ return map_nt_error_from_unix(errno);
+ }
+ if (flags & FS_COMPR_FL) {
+ *_compression_fmt = COMPRESSION_FORMAT_LZNT1;
+ } else {
+ *_compression_fmt = COMPRESSION_FORMAT_NONE;
+ }
+ return NT_STATUS_OK;
+ }
+
+ if (!fsp->fsp_flags.have_proc_fds) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ fd = open(sys_proc_fd_path(fsp_fd, &buf), O_RDONLY);
+ if (fd == -1) {
+ DBG_DEBUG("/proc open of %s failed: %s\n",
+ buf.buf,
+ strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
+ if (ret < 0) {
+ DEBUG(1, ("FS_IOC_GETFLAGS failed: %s, fd %lld\n",
+ strerror(errno), (long long)fd));
+ status = map_nt_error_from_unix(errno);
+ goto err_close;
+ }
+ if (flags & FS_COMPR_FL) {
+ *_compression_fmt = COMPRESSION_FORMAT_LZNT1;
+ } else {
+ *_compression_fmt = COMPRESSION_FORMAT_NONE;
+ }
+ status = NT_STATUS_OK;
+
+err_close:
+ if (fd != -1) {
+ close(fd);
+ }
+
+ return status;
+}
+
+static NTSTATUS btrfs_set_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ int ret;
+ long flags = 0;
+ int fd;
+ NTSTATUS status;
+
+ if ((fsp == NULL) || (fsp_get_io_fd(fsp) == -1)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_out;
+ }
+ fd = fsp_get_io_fd(fsp);
+
+ ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
+ if (ret < 0) {
+ DEBUG(1, ("FS_IOC_GETFLAGS failed: %s, fd %d\n",
+ strerror(errno), fd));
+ status = map_nt_error_from_unix(errno);
+ goto err_out;
+ }
+
+ if (compression_fmt == COMPRESSION_FORMAT_NONE) {
+ DEBUG(5, ("setting compression\n"));
+ flags &= (~FS_COMPR_FL);
+ } else if ((compression_fmt == COMPRESSION_FORMAT_DEFAULT)
+ || (compression_fmt == COMPRESSION_FORMAT_LZNT1)) {
+ DEBUG(5, ("clearing compression\n"));
+ flags |= FS_COMPR_FL;
+ } else {
+ DEBUG(1, ("invalid compression format 0x%x\n",
+ (int)compression_fmt));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ ret = ioctl(fd, FS_IOC_SETFLAGS, &flags);
+ if (ret < 0) {
+ DEBUG(1, ("FS_IOC_SETFLAGS failed: %s, fd %d\n",
+ strerror(errno), fd));
+ status = map_nt_error_from_unix(errno);
+ goto err_out;
+ }
+ status = NT_STATUS_OK;
+err_out:
+ return status;
+}
+
+/*
+ * Check whether a path can be shadow copied. Return the base volume, allowing
+ * the caller to determine if multiple paths lie on the same base volume.
+ */
+#define BTRFS_INODE_SUBVOL 256
+static NTSTATUS btrfs_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ struct stat st;
+ char *base;
+
+ if (!lp_parm_bool(SNUM(handle->conn),
+ "btrfs", "manipulate snapshots", false)) {
+ DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n"));
+ return SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx,
+ service_path, base_volume);
+ }
+
+ /* btrfs userspace uses this logic to confirm subvolume */
+ if (stat(service_path, &st) < 0) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ if ((st.st_ino != BTRFS_INODE_SUBVOL) || !S_ISDIR(st.st_mode)) {
+ DEBUG(0, ("%s not a btrfs subvolume, snapshots not available\n",
+ service_path));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* we "snapshot" the service path itself */
+ base = talloc_strdup(mem_ctx, service_path);
+ if (base == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *base_volume = base;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS btrfs_gen_snap_dest_path(TALLOC_CTX *mem_ctx,
+ const char *src_path,
+ time_t *tstamp,
+ char **dest_path, char **subvolume)
+{
+ struct tm t_gmt;
+ char time_str[50];
+ size_t tlen;
+
+ gmtime_r(tstamp, &t_gmt);
+
+ tlen = strftime(time_str, ARRAY_SIZE(time_str),
+ SHADOW_COPY_PATH_FORMAT, &t_gmt);
+ if (tlen <= 0) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *dest_path = talloc_strdup(mem_ctx, src_path);
+ *subvolume = talloc_strdup(mem_ctx, time_str);
+ if ((*dest_path == NULL) || (*subvolume == NULL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS btrfs_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **_base_path,
+ char **_snap_path)
+{
+ struct btrfs_ioctl_vol_args_v2 ioctl_arg;
+ DIR *src_dir;
+ DIR *dest_dir;
+ int src_fd;
+ int dest_fd;
+ char *dest_path = NULL;
+ char *dest_subvolume = NULL;
+ int ret;
+ NTSTATUS status;
+ char *base_path;
+ char *snap_path;
+ TALLOC_CTX *tmp_ctx;
+ int saved_errno;
+ size_t len;
+
+ if (!lp_parm_bool(SNUM(handle->conn),
+ "btrfs", "manipulate snapshots", false)) {
+ DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n"));
+ return SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume,
+ tstamp, rw, _base_path,
+ _snap_path);
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ base_path = talloc_strdup(tmp_ctx, base_volume);
+ if (base_path == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = btrfs_gen_snap_dest_path(tmp_ctx, base_volume, tstamp,
+ &dest_path, &dest_subvolume);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ snap_path = talloc_asprintf(tmp_ctx, "%s/%s", dest_path,
+ dest_subvolume);
+ if (snap_path == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ src_dir = opendir(base_volume);
+ if (src_dir == NULL) {
+ DEBUG(0, ("snap src %s open failed: %s\n",
+ base_volume, strerror(errno)));
+ status = map_nt_error_from_unix(errno);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ src_fd = dirfd(src_dir);
+ if (src_fd < 0) {
+ status = map_nt_error_from_unix(errno);
+ closedir(src_dir);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ dest_dir = opendir(dest_path);
+ if (dest_dir == NULL) {
+ DEBUG(0, ("snap dest %s open failed: %s\n",
+ dest_path, strerror(errno)));
+ status = map_nt_error_from_unix(errno);
+ closedir(src_dir);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ dest_fd = dirfd(dest_dir);
+ if (dest_fd < 0) {
+ status = map_nt_error_from_unix(errno);
+ closedir(src_dir);
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ /* avoid zeroing the entire struct here, name is 4k */
+ ioctl_arg.fd = src_fd;
+ ioctl_arg.transid = 0;
+ ioctl_arg.flags = (rw == false) ? BTRFS_SUBVOL_RDONLY : 0;
+ memset(ioctl_arg.unused, 0, sizeof(ioctl_arg.unused));
+ len = strlcpy(ioctl_arg.name, dest_subvolume,
+ ARRAY_SIZE(ioctl_arg.name));
+ if (len >= ARRAY_SIZE(ioctl_arg.name)) {
+ DEBUG(1, ("subvolume name too long for SNAP_CREATE ioctl\n"));
+ closedir(src_dir);
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ become_root();
+ ret = ioctl(dest_fd, BTRFS_IOC_SNAP_CREATE_V2, &ioctl_arg);
+ saved_errno = errno;
+ unbecome_root();
+ if (ret < 0) {
+ DEBUG(0, ("%s -> %s(%s) BTRFS_IOC_SNAP_CREATE_V2 failed: %s\n",
+ base_volume, dest_path, dest_subvolume,
+ strerror(saved_errno)));
+ status = map_nt_error_from_unix(saved_errno);
+ closedir(src_dir);
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ DEBUG(5, ("%s -> %s(%s) BTRFS_IOC_SNAP_CREATE_V2 done\n",
+ base_volume, dest_path, dest_subvolume));
+
+ *_base_path = talloc_steal(mem_ctx, base_path);
+ *_snap_path = talloc_steal(mem_ctx, snap_path);
+ closedir(src_dir);
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS btrfs_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ char *tstr;
+ struct tm t_gmt = {};
+ DIR *dest_dir;
+ int dest_fd;
+ struct btrfs_ioctl_vol_args ioctl_arg;
+ int ret;
+ NTSTATUS status;
+ char *dest_path;
+ char *subvolume;
+ TALLOC_CTX *tmp_ctx;
+ int saved_errno;
+ size_t len;
+
+ if (!lp_parm_bool(SNUM(handle->conn),
+ "btrfs", "manipulate snapshots", false)) {
+ DEBUG(2, ("Btrfs snapshot manipulation disabled, passing\n"));
+ return SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx,
+ base_path, snap_path);
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dest_path = talloc_strdup(tmp_ctx, snap_path);
+ if (dest_path == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ subvolume = talloc_strdup(tmp_ctx, snap_path);
+ if (subvolume == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ dest_path = dirname(dest_path);
+ subvolume = basename(subvolume);
+
+ /* confirm snap_path matches creation format */
+ tstr = strptime(subvolume, SHADOW_COPY_PATH_FORMAT, &t_gmt);
+ if ((tstr == NULL) || (*tstr != '\0')) {
+ DEBUG(0, ("snapshot path %s does not match creation format\n",
+ snap_path));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dest_dir = opendir(dest_path);
+ if (dest_dir == NULL) {
+ DEBUG(0, ("snap destroy dest %s open failed: %s\n",
+ dest_path, strerror(errno)));
+ status = map_nt_error_from_unix(errno);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ dest_fd = dirfd(dest_dir);
+ if (dest_fd < 0) {
+ status = map_nt_error_from_unix(errno);
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ ioctl_arg.fd = -1; /* not needed */
+ len = strlcpy(ioctl_arg.name, subvolume, ARRAY_SIZE(ioctl_arg.name));
+ if (len >= ARRAY_SIZE(ioctl_arg.name)) {
+ DEBUG(1, ("subvolume name too long for SNAP_DESTROY ioctl\n"));
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ become_root();
+ ret = ioctl(dest_fd, BTRFS_IOC_SNAP_DESTROY, &ioctl_arg);
+ saved_errno = errno;
+ unbecome_root();
+ if (ret < 0) {
+ DEBUG(0, ("%s(%s) BTRFS_IOC_SNAP_DESTROY failed: %s\n",
+ dest_path, subvolume, strerror(saved_errno)));
+ status = map_nt_error_from_unix(saved_errno);
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ DEBUG(5, ("%s(%s) BTRFS_IOC_SNAP_DESTROY done\n",
+ dest_path, subvolume));
+
+ closedir(dest_dir);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static struct vfs_fn_pointers btrfs_fns = {
+ .fs_capabilities_fn = btrfs_fs_capabilities,
+ .offload_read_send_fn = btrfs_offload_read_send,
+ .offload_read_recv_fn = btrfs_offload_read_recv,
+ .offload_write_send_fn = btrfs_offload_write_send,
+ .offload_write_recv_fn = btrfs_offload_write_recv,
+ .fget_compression_fn = btrfs_fget_compression,
+ .set_compression_fn = btrfs_set_compression,
+ .snap_check_path_fn = btrfs_snap_check_path,
+ .snap_create_fn = btrfs_snap_create,
+ .snap_delete_fn = btrfs_snap_delete,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_btrfs_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "btrfs", &btrfs_fns);
+}
diff --git a/source3/modules/vfs_cacheprime.c b/source3/modules/vfs_cacheprime.c
new file mode 100644
index 0000000..2718700
--- /dev/null
+++ b/source3/modules/vfs_cacheprime.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) James Peach 2005-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "lib/util/sys_rw.h"
+
+/* Cache priming module.
+ *
+ * The purpose of this module is to do RAID stripe width reads to prime the
+ * buffer cache to do zero-copy I/O for subsequent sendfile calls. The idea is
+ * to do a single large read at the start of the file to make sure that most or
+ * all of the file is pulled into the buffer cache. Subsequent I/Os have
+ * reduced latency.
+ *
+ * Tunables.
+ *
+ * cacheprime:rsize Amount of readahead in bytes. This should be a
+ * multiple of the RAID stripe width.
+ * cacheprime:debug Debug level at which to emit messages.
+ */
+
+#define READAHEAD_MIN (128 * 1024) /* min is 128 KiB */
+#define READAHEAD_MAX (100 * 1024 * 1024) /* max is 100 MiB */
+
+#define MODULE "cacheprime"
+
+static int module_debug;
+static ssize_t g_readsz = 0;
+static void * g_readbuf = NULL;
+
+/* Prime the kernel buffer cache with data from the specified file. We use
+ * per-fsp data to make sure we only ever do this once. If pread is being
+ * emulated by seek/read/seek, when this will suck quite a lot.
+ */
+static bool prime_cache(
+ struct vfs_handle_struct * handle,
+ files_struct * fsp,
+ off_t offset,
+ size_t count)
+{
+ off_t * last;
+ ssize_t nread;
+
+ last = VFS_ADD_FSP_EXTENSION(handle, fsp, off_t, NULL);
+ if (!last) {
+ return False;
+ }
+
+ if (*last == -1) {
+ /* Readahead disabled. */
+ return False;
+ }
+
+ if ((*last + g_readsz) > (offset + count)) {
+ /* Skip readahead ... we've already been here. */
+ return False;
+ }
+
+ DEBUG(module_debug,
+ ("%s: doing readahead of %lld bytes at %lld for %s\n",
+ MODULE, (long long)g_readsz, (long long)*last,
+ fsp_str_dbg(fsp)));
+
+ nread = sys_pread(fsp_get_io_fd(fsp), g_readbuf, g_readsz, *last);
+ if (nread < 0) {
+ *last = -1;
+ return False;
+ }
+
+ *last += nread;
+ return True;
+}
+
+static int cprime_connect(
+ struct vfs_handle_struct * handle,
+ const char * service,
+ const char * user)
+{
+ int ret;
+
+ module_debug = lp_parm_int(SNUM(handle->conn), MODULE, "debug", 100);
+ if (g_readbuf) {
+ /* Only allocate g_readbuf once. If the config changes and
+ * another client multiplexes onto this smbd, we don't want
+ * to risk memory corruption.
+ */
+ return SMB_VFS_NEXT_CONNECT(handle, service, user);
+ }
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ g_readsz = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
+ MODULE, "rsize", NULL));
+
+ if (g_readsz < READAHEAD_MIN) {
+ DEBUG(module_debug, ("%s: %ld bytes of readahead "
+ "requested, using minimum of %u\n",
+ MODULE, (long)g_readsz, READAHEAD_MIN));
+ g_readsz = READAHEAD_MIN;
+ } else if (g_readsz > READAHEAD_MAX) {
+ DEBUG(module_debug, ("%s: %ld bytes of readahead "
+ "requested, using maximum of %u\n",
+ MODULE, (long)g_readsz, READAHEAD_MAX));
+ g_readsz = READAHEAD_MAX;
+ }
+
+ if ((g_readbuf = SMB_MALLOC(g_readsz)) == NULL) {
+ /* Turn off readahead if we can't get a buffer. */
+ g_readsz = 0;
+ }
+
+ return 0;
+}
+
+static ssize_t cprime_sendfile(
+ struct vfs_handle_struct * handle,
+ int tofd,
+ files_struct * fromfsp,
+ const DATA_BLOB * header,
+ off_t offset,
+ size_t count)
+{
+ if (g_readbuf && offset == 0) {
+ prime_cache(handle, fromfsp, offset, count);
+ }
+
+ return SMB_VFS_NEXT_SENDFILE(handle, tofd, fromfsp,
+ header, offset, count);
+}
+
+static ssize_t cprime_pread(
+ vfs_handle_struct * handle,
+ files_struct * fsp,
+ void * data,
+ size_t count,
+ off_t offset)
+{
+ if (g_readbuf) {
+ prime_cache(handle, fsp, offset, count);
+ }
+
+ return SMB_VFS_NEXT_PREAD(handle, fsp, data, count, offset);
+}
+
+static struct vfs_fn_pointers vfs_cacheprime_fns = {
+ .sendfile_fn = cprime_sendfile,
+ .pread_fn = cprime_pread,
+ .connect_fn = cprime_connect,
+};
+
+/* -------------------------------------------------------------------------
+ * Samba module initialisation entry point.
+ * -------------------------------------------------------------------------
+ */
+
+static_decl_vfs;
+NTSTATUS vfs_cacheprime_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE,
+ &vfs_cacheprime_fns);
+}
+
+/* vim: set sw=4 ts=4 tw=79 et: */
diff --git a/source3/modules/vfs_cap.c b/source3/modules/vfs_cap.c
new file mode 100644
index 0000000..3553e11
--- /dev/null
+++ b/source3/modules/vfs_cap.c
@@ -0,0 +1,1004 @@
+/*
+ * CAP VFS module for Samba 3.x Version 0.3
+ *
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002-2003
+ * Copyright (C) Stefan (metze) Metzmacher, 2003
+ * Copyright (C) TAKAHASHI Motonobu (monyo), 2003
+ * Copyright (C) Jeremy Allison, 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+
+/* cap functions */
+static char *capencode(TALLOC_CTX *ctx, const char *from);
+static char *capdecode(TALLOC_CTX *ctx, const char *from);
+
+static uint64_t cap_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ char *capname = capencode(talloc_tos(), smb_fname->base_name);
+ struct smb_filename *cap_smb_fname = NULL;
+
+ if (!capname) {
+ errno = ENOMEM;
+ return (uint64_t)-1;
+ }
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ capname,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(capname);
+ errno = ENOMEM;
+ return (uint64_t)-1;
+ }
+ return SMB_VFS_NEXT_DISK_FREE(handle, cap_smb_fname,
+ bsize, dfree, dsize);
+}
+
+static int cap_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq)
+{
+ char *cappath = capencode(talloc_tos(), smb_fname->base_name);
+ struct smb_filename *cap_smb_fname = NULL;
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return -1;
+ }
+ return SMB_VFS_NEXT_GET_QUOTA(handle, cap_smb_fname, qtype, id, dq);
+}
+
+static struct dirent *
+cap_readdir(vfs_handle_struct *handle, struct files_struct *dirfsp, DIR *dirp)
+{
+ struct dirent *result;
+ struct dirent *newdirent;
+ char *newname;
+ size_t newnamelen;
+ DEBUG(3,("cap: cap_readdir\n"));
+
+ result = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirp);
+ if (!result) {
+ return NULL;
+ }
+
+ newname = capdecode(talloc_tos(), result->d_name);
+ if (!newname) {
+ return NULL;
+ }
+ DEBUG(3,("cap: cap_readdir: %s\n", newname));
+ newnamelen = strlen(newname)+1;
+ newdirent = talloc_size(
+ talloc_tos(), sizeof(struct dirent) + newnamelen);
+ if (!newdirent) {
+ return NULL;
+ }
+ talloc_set_name_const(newdirent, "struct dirent");
+ memcpy(newdirent, result, sizeof(struct dirent));
+ memcpy(&newdirent->d_name, newname, newnamelen);
+ return newdirent;
+}
+
+static int cap_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ char *cappath = capencode(talloc_tos(), smb_fname->base_name);
+ struct smb_filename *cap_smb_fname = NULL;
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ cap_smb_fname,
+ mode);
+}
+
+static int cap_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ char *cappath = NULL;
+ struct smb_filename *smb_fname = NULL;
+ int ret;
+ int saved_errno = 0;
+
+ cappath = capencode(talloc_tos(), smb_fname_in->base_name);
+ if (cappath == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return -1;
+ }
+ smb_fname->base_name = cappath;
+
+ DBG_DEBUG("cap_open for %s\n", smb_fname_str_dbg(smb_fname));
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int cap_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ char *capold = NULL;
+ char *capnew = NULL;
+ struct smb_filename *smb_fname_src_tmp = NULL;
+ struct smb_filename *smb_fname_dst_tmp = NULL;
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+ int ret = -1;
+ int saved_errno = 0;
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ capold = capencode(talloc_tos(), full_fname_src->base_name);
+ capnew = capencode(talloc_tos(), full_fname_dst->base_name);
+ if (!capold || !capnew) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ /* Setup temporary smb_filename structs. */
+ smb_fname_src_tmp = cp_smb_filename(talloc_tos(), full_fname_src);
+ if (smb_fname_src_tmp == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+ smb_fname_dst_tmp = cp_smb_filename(talloc_tos(), full_fname_dst);
+ if (smb_fname_dst_tmp == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ smb_fname_src_tmp->base_name = capold;
+ smb_fname_dst_tmp->base_name = capnew;
+
+ ret = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp->conn->cwd_fsp,
+ smb_fname_src_tmp,
+ dstfsp->conn->cwd_fsp,
+ smb_fname_dst_tmp);
+
+ out:
+
+ if (ret != 0) {
+ saved_errno = errno;
+ }
+
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+ TALLOC_FREE(capold);
+ TALLOC_FREE(capnew);
+ TALLOC_FREE(smb_fname_src_tmp);
+ TALLOC_FREE(smb_fname_dst_tmp);
+
+ if (ret != 0) {
+ errno = saved_errno;
+ }
+
+ return ret;
+}
+
+static int cap_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname)
+{
+ char *cappath;
+ char *tmp_base_name = NULL;
+ int ret;
+
+ cappath = capencode(talloc_tos(), smb_fname->base_name);
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ tmp_base_name = smb_fname->base_name;
+ smb_fname->base_name = cappath;
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+
+ smb_fname->base_name = tmp_base_name;
+ TALLOC_FREE(cappath);
+
+ return ret;
+}
+
+static int cap_lstat(vfs_handle_struct *handle, struct smb_filename *smb_fname)
+{
+ char *cappath;
+ char *tmp_base_name = NULL;
+ int ret;
+
+ cappath = capencode(talloc_tos(), smb_fname->base_name);
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ tmp_base_name = smb_fname->base_name;
+ smb_fname->base_name = cappath;
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+
+ smb_fname->base_name = tmp_base_name;
+ TALLOC_FREE(cappath);
+
+ return ret;
+}
+
+static int cap_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *smb_fname_tmp = NULL;
+ char *cappath = NULL;
+ int ret;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ cappath = capencode(talloc_tos(), full_fname->base_name);
+ if (!cappath) {
+ TALLOC_FREE(full_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Setup temporary smb_filename structs. */
+ smb_fname_tmp = cp_smb_filename(talloc_tos(), full_fname);
+ TALLOC_FREE(full_fname);
+ if (smb_fname_tmp == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ smb_fname_tmp->base_name = cappath;
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp->conn->cwd_fsp,
+ smb_fname_tmp,
+ flags);
+
+ TALLOC_FREE(smb_fname_tmp);
+ return ret;
+}
+
+static int cap_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ struct smb_filename *cap_smb_fname = NULL;
+ char *cappath = capencode(talloc_tos(), smb_fname->base_name);
+ int ret;
+ int saved_errno;
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_LCHOWN(handle, cap_smb_fname, uid, gid);
+ saved_errno = errno;
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(cap_smb_fname);
+ errno = saved_errno;
+ return ret;
+}
+
+static int cap_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *cap_smb_fname = NULL;
+ char *cappath = capencode(talloc_tos(), smb_fname->base_name);
+ int ret;
+ int saved_errno = 0;
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+ DEBUG(3,("cap: cap_chdir for %s\n", smb_fname->base_name));
+
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_CHDIR(handle, cap_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(cap_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int cap_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ struct smb_filename *full_fname = NULL;
+ char *capold = capencode(talloc_tos(), link_contents->base_name);
+ char *capnew = NULL;
+ struct smb_filename *new_link_target = NULL;
+ struct smb_filename *new_cap_smb_fname = NULL;
+ int saved_errno = 0;
+ int ret;
+
+ if (capold == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ new_smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ capnew = capencode(talloc_tos(), full_fname->base_name);
+ if (!capnew) {
+ TALLOC_FREE(full_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ new_link_target = synthetic_smb_fname(talloc_tos(),
+ capold,
+ NULL,
+ NULL,
+ new_smb_fname->twrp,
+ new_smb_fname->flags);
+ if (new_link_target == NULL) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(capold);
+ TALLOC_FREE(capnew);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ new_cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ capnew,
+ NULL,
+ NULL,
+ new_smb_fname->twrp,
+ new_smb_fname->flags);
+ if (new_cap_smb_fname == NULL) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(capold);
+ TALLOC_FREE(capnew);
+ TALLOC_FREE(new_link_target);
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_SYMLINKAT(handle,
+ new_link_target,
+ handle->conn->cwd_fsp,
+ new_cap_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(capold);
+ TALLOC_FREE(capnew);
+ TALLOC_FREE(new_link_target);
+ TALLOC_FREE(new_cap_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int cap_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *cap_smb_fname = NULL;
+ char *cappath = NULL;
+ int saved_errno = 0;
+ int ret;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ cappath = capencode(talloc_tos(), full_fname->base_name);
+ if (cappath == NULL) {
+ TALLOC_FREE(full_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_READLINKAT(handle,
+ handle->conn->cwd_fsp,
+ cap_smb_fname,
+ buf,
+ bufsiz);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(cap_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int cap_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ struct smb_filename *old_full_fname = NULL;
+ struct smb_filename *new_full_fname = NULL;
+ char *capold = NULL;
+ char *capnew = NULL;
+ struct smb_filename *old_cap_smb_fname = NULL;
+ struct smb_filename *new_cap_smb_fname = NULL;
+ int saved_errno = 0;
+ int ret;
+
+ /* Process 'old' name. */
+ old_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ old_smb_fname);
+ if (old_full_fname == NULL) {
+ goto nomem_out;
+ }
+ capold = capencode(talloc_tos(), old_full_fname->base_name);
+ if (capold == NULL) {
+ goto nomem_out;
+ }
+ TALLOC_FREE(old_full_fname);
+ old_cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ capold,
+ NULL,
+ NULL,
+ old_smb_fname->twrp,
+ old_smb_fname->flags);
+ if (old_cap_smb_fname == NULL) {
+ goto nomem_out;
+ }
+
+ /* Process 'new' name. */
+ new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (new_full_fname == NULL) {
+ goto nomem_out;
+ }
+ capnew = capencode(talloc_tos(), new_full_fname->base_name);
+ if (capnew == NULL) {
+ goto nomem_out;
+ }
+ TALLOC_FREE(new_full_fname);
+ new_cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ capnew,
+ NULL,
+ NULL,
+ new_smb_fname->twrp,
+ new_smb_fname->flags);
+ if (new_cap_smb_fname == NULL) {
+ goto nomem_out;
+ }
+
+ ret = SMB_VFS_NEXT_LINKAT(handle,
+ handle->conn->cwd_fsp,
+ old_cap_smb_fname,
+ handle->conn->cwd_fsp,
+ new_cap_smb_fname,
+ flags);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(capold);
+ TALLOC_FREE(capnew);
+ TALLOC_FREE(old_cap_smb_fname);
+ TALLOC_FREE(new_cap_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+
+ nomem_out:
+
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(capold);
+ TALLOC_FREE(capnew);
+ TALLOC_FREE(old_cap_smb_fname);
+ TALLOC_FREE(new_cap_smb_fname);
+ errno = ENOMEM;
+ return -1;
+}
+
+static int cap_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *cap_smb_fname = NULL;
+ char *cappath = NULL;
+ int ret;
+ int saved_errno = 0;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ cappath = capencode(talloc_tos(), full_fname->base_name);
+ if (!cappath) {
+ TALLOC_FREE(full_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_MKNODAT(handle,
+ handle->conn->cwd_fsp,
+ cap_smb_fname,
+ mode,
+ dev);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(cap_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static struct smb_filename *cap_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ /* monyo need capencode'ed and capdecode'ed? */
+ struct smb_filename *cap_smb_fname = NULL;
+ struct smb_filename *return_fname = NULL;
+ char *cappath = capencode(talloc_tos(), smb_fname->base_name);
+ int saved_errno = 0;
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ cap_smb_fname = synthetic_smb_fname(ctx,
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(cappath);
+ errno = ENOMEM;
+ return NULL;
+ }
+ return_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, cap_smb_fname);
+ if (return_fname == NULL) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(cap_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return return_fname;
+}
+
+static ssize_t cap_fgetxattr(vfs_handle_struct *handle, struct files_struct *fsp, const char *path, void *value, size_t size)
+{
+ char *cappath = capencode(talloc_tos(), path);
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FGETXATTR(handle, fsp, cappath, value, size);
+}
+
+static int cap_fremovexattr(vfs_handle_struct *handle, struct files_struct *fsp, const char *path)
+{
+ char *cappath = capencode(talloc_tos(), path);
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, cappath);
+}
+
+static int cap_fsetxattr(vfs_handle_struct *handle, struct files_struct *fsp, const char *path, const void *value, size_t size, int flags)
+{
+ char *cappath = capencode(talloc_tos(), path);
+
+ if (!cappath) {
+ errno = ENOMEM;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp, cappath, value, size, flags);
+}
+
+static NTSTATUS cap_create_dfs_pathat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ char *cappath = capencode(talloc_tos(), smb_fname->base_name);
+ struct smb_filename *cap_smb_fname = NULL;
+ NTSTATUS status;
+
+ if (cappath == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(cappath);
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle,
+ dirfsp,
+ cap_smb_fname,
+ reflist,
+ referral_count);
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(cap_smb_fname);
+ return status;
+}
+
+static NTSTATUS cap_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *cap_smb_fname = NULL;
+ char *cappath = NULL;
+ NTSTATUS status;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ cappath = capencode(talloc_tos(), full_fname->base_name);
+ if (cappath == NULL) {
+ TALLOC_FREE(full_fname);
+ return NT_STATUS_NO_MEMORY;
+ }
+ cap_smb_fname = synthetic_smb_fname(talloc_tos(),
+ cappath,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (cap_smb_fname == NULL) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(cappath);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle,
+ mem_ctx,
+ handle->conn->cwd_fsp,
+ cap_smb_fname,
+ ppreflist,
+ preferral_count);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return any stat(2) info. */
+ smb_fname->st = cap_smb_fname->st;
+ }
+
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(cappath);
+ TALLOC_FREE(cap_smb_fname);
+ return status;
+}
+
+static struct vfs_fn_pointers vfs_cap_fns = {
+ .disk_free_fn = cap_disk_free,
+ .get_quota_fn = cap_get_quota,
+ .readdir_fn = cap_readdir,
+ .mkdirat_fn = cap_mkdirat,
+ .openat_fn = cap_openat,
+ .renameat_fn = cap_renameat,
+ .stat_fn = cap_stat,
+ .lstat_fn = cap_lstat,
+ .unlinkat_fn = cap_unlinkat,
+ .lchown_fn = cap_lchown,
+ .chdir_fn = cap_chdir,
+ .symlinkat_fn = cap_symlinkat,
+ .readlinkat_fn = cap_readlinkat,
+ .linkat_fn = cap_linkat,
+ .mknodat_fn = cap_mknodat,
+ .realpath_fn = cap_realpath,
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fgetxattr_fn = cap_fgetxattr,
+ .fremovexattr_fn = cap_fremovexattr,
+ .fsetxattr_fn = cap_fsetxattr,
+ .create_dfs_pathat_fn = cap_create_dfs_pathat,
+ .read_dfs_pathat_fn = cap_read_dfs_pathat
+};
+
+static_decl_vfs;
+NTSTATUS vfs_cap_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "cap",
+ &vfs_cap_fns);
+}
+
+/* For CAP functions */
+#define hex_tag ':'
+#define hex2bin(c) hex2bin_table[(unsigned char)(c)]
+#define bin2hex(c) bin2hex_table[(unsigned char)(c)]
+#define is_hex(s) ((s)[0] == hex_tag)
+
+static unsigned char hex2bin_table[256] = {
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 */
+0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, /* 0x30 */
+0000, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0000, /* 0x40 */
+0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 */
+0000, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0000, /* 0x60 */
+0000, 0000, 0000, 0000, 0000, 0000, 0000, 0000,
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 */
+0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 */
+};
+static unsigned char bin2hex_table[256] = "0123456789abcdef";
+
+/*******************************************************************
+ original code -> ":xx" - CAP format
+********************************************************************/
+
+static char *capencode(TALLOC_CTX *ctx, const char *from)
+{
+ char *out = NULL;
+ const char *p1;
+ char *to = NULL;
+ size_t len = 0;
+
+ for (p1 = from; *p1; p1++) {
+ if ((unsigned char)*p1 >= 0x80) {
+ len += 3;
+ } else {
+ len++;
+ }
+ }
+ len++;
+
+ to = talloc_array(ctx, char, len);
+ if (!to) {
+ return NULL;
+ }
+
+ for (out = to; *from;) {
+ /* buffer husoku error */
+ if ((unsigned char)*from >= 0x80) {
+ *out++ = hex_tag;
+ *out++ = bin2hex (((*from)>>4)&0x0f);
+ *out++ = bin2hex ((*from)&0x0f);
+ from++;
+ } else {
+ *out++ = *from++;
+ }
+ }
+ *out = '\0';
+ return to;
+}
+
+/*******************************************************************
+ CAP -> original code
+********************************************************************/
+/* ":xx" -> a byte */
+
+static char *capdecode(TALLOC_CTX *ctx, const char *from)
+{
+ const char *p1;
+ char *out = NULL;
+ char *to = NULL;
+ size_t len = 0;
+
+ for (p1 = from; *p1; len++) {
+ if (is_hex(p1)) {
+ p1 += 3;
+ } else {
+ p1++;
+ }
+ }
+ len++;
+
+ to = talloc_array(ctx, char, len);
+ if (!to) {
+ return NULL;
+ }
+
+ for (out = to; *from;) {
+ if (is_hex(from)) {
+ *out++ = (hex2bin(from[1])<<4) | (hex2bin(from[2]));
+ from += 3;
+ } else {
+ *out++ = *from++;
+ }
+ }
+ *out = '\0';
+ return to;
+}
diff --git a/source3/modules/vfs_catia.c b/source3/modules/vfs_catia.c
new file mode 100644
index 0000000..36aa431
--- /dev/null
+++ b/source3/modules/vfs_catia.c
@@ -0,0 +1,1952 @@
+/*
+ * Catia VFS module
+ *
+ * Implement a fixed mapping of forbidden NT characters in filenames that are
+ * used a lot by the CAD package Catia.
+ *
+ * Catia V4 on AIX uses characters like "<*$ a *lot*, all forbidden under
+ * Windows...
+ *
+ * Copyright (C) Volker Lendecke, 2005
+ * Copyright (C) Aravind Srinivasan, 2009
+ * Copyright (C) Guenter Kukkukk, 2013
+ * Copyright (C) Ralph Boehme, 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "string_replace.h"
+
+static int vfs_catia_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_catia_debug_level
+
+struct share_mapping_entry {
+ int snum;
+ struct share_mapping_entry *next;
+ struct char_mappings **mappings;
+};
+
+struct catia_cache {
+ bool is_fsp_ext;
+ const struct catia_cache * const *busy;
+ char *orig_fname;
+ char *fname;
+ char *orig_base_fname;
+ char *base_fname;
+};
+
+static struct share_mapping_entry *srt_head = NULL;
+
+static struct share_mapping_entry *get_srt(connection_struct *conn,
+ struct share_mapping_entry **global)
+{
+ struct share_mapping_entry *share;
+
+ for (share = srt_head; share != NULL; share = share->next) {
+ if (share->snum == GLOBAL_SECTION_SNUM)
+ (*global) = share;
+
+ if (share->snum == SNUM(conn))
+ return share;
+ }
+
+ return share;
+}
+
+static struct share_mapping_entry *add_srt(int snum, const char **mappings)
+{
+ struct share_mapping_entry *sme = NULL;
+
+ sme = talloc_zero(NULL, struct share_mapping_entry);
+ if (sme == NULL)
+ return sme;
+
+ sme->snum = snum;
+ sme->next = srt_head;
+ srt_head = sme;
+
+ if (mappings == NULL) {
+ sme->mappings = NULL;
+ return sme;
+ }
+
+ sme->mappings = string_replace_init_map(sme, mappings);
+
+ return sme;
+}
+
+static bool init_mappings(connection_struct *conn,
+ struct share_mapping_entry **selected_out)
+{
+ const char **mappings = NULL;
+ struct share_mapping_entry *share_level = NULL;
+ struct share_mapping_entry *global = NULL;
+
+ /* check srt cache */
+ share_level = get_srt(conn, &global);
+ if (share_level) {
+ *selected_out = share_level;
+ return (share_level->mappings != NULL);
+ }
+
+ /* see if we have a global setting */
+ if (!global) {
+ /* global setting */
+ mappings = lp_parm_string_list(-1, "catia", "mappings", NULL);
+ global = add_srt(GLOBAL_SECTION_SNUM, mappings);
+ }
+
+ /* no global setting - what about share level ? */
+ mappings = lp_parm_string_list(SNUM(conn), "catia", "mappings", NULL);
+ share_level = add_srt(SNUM(conn), mappings);
+
+ if (share_level->mappings) {
+ (*selected_out) = share_level;
+ return True;
+ }
+ if (global->mappings) {
+ share_level->mappings = global->mappings;
+ (*selected_out) = share_level;
+ return True;
+ }
+
+ return False;
+}
+
+static NTSTATUS catia_string_replace_allocate(connection_struct *conn,
+ const char *name_in,
+ char **mapped_name,
+ enum vfs_translate_direction direction)
+{
+ struct share_mapping_entry *selected;
+ NTSTATUS status;
+
+ if (!init_mappings(conn, &selected)) {
+ /* No mappings found. Just use the old name */
+ *mapped_name = talloc_strdup(talloc_tos(), name_in);
+ if (!*mapped_name) {
+ errno = ENOMEM;
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+
+ status = string_replace_allocate(conn,
+ name_in,
+ selected->mappings,
+ talloc_tos(),
+ mapped_name,
+ direction);
+ return status;
+}
+
+static int catia_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ /*
+ * Unless we have an async implementation of get_dos_attributes turn
+ * this off.
+ */
+ lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false");
+
+ return SMB_VFS_NEXT_CONNECT(handle, service, user);
+}
+
+/*
+ * TRANSLATE_NAME call which converts the given name to
+ * "WINDOWS displayable" name
+ */
+static NTSTATUS catia_translate_name(struct vfs_handle_struct *handle,
+ const char *orig_name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **pmapped_name)
+{
+ char *name = NULL;
+ char *mapped_name;
+ NTSTATUS status, ret;
+
+ /*
+ * Copy the supplied name and free the memory for mapped_name,
+ * already allocated by the caller.
+ * We will be allocating new memory for mapped_name in
+ * catia_string_replace_allocate
+ */
+ name = talloc_strdup(talloc_tos(), orig_name);
+ if (!name) {
+ errno = ENOMEM;
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = catia_string_replace_allocate(handle->conn, name,
+ &mapped_name, direction);
+
+ TALLOC_FREE(name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = SMB_VFS_NEXT_TRANSLATE_NAME(handle, mapped_name, direction,
+ mem_ctx, pmapped_name);
+
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) {
+ *pmapped_name = talloc_move(mem_ctx, &mapped_name);
+ /* we need to return the former translation result here */
+ ret = status;
+ } else {
+ TALLOC_FREE(mapped_name);
+ }
+
+ return ret;
+}
+
+#define CATIA_DEBUG_CC(lvl, cc, fsp) \
+ catia_debug_cc((lvl), (cc), (fsp), __location__);
+
+static void catia_debug_cc(int lvl,
+ struct catia_cache *cc,
+ files_struct *fsp,
+ const char *location)
+{
+ DEBUG(lvl, ("%s: cc [%p] cc->busy [%p] "
+ "is_fsp_ext [%s] "
+ "fsp [%p] fsp name [%s] "
+ "orig_fname [%s] "
+ "fname [%s] "
+ "orig_base_fname [%s] "
+ "base_fname [%s]\n",
+ location,
+ cc, cc->busy,
+ cc->is_fsp_ext ? "yes" : "no",
+ fsp, fsp_str_dbg(fsp),
+ cc->orig_fname, cc->fname,
+ cc->orig_base_fname, cc->base_fname));
+}
+
+static void catia_free_cc(struct catia_cache **_cc,
+ vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ struct catia_cache *cc = *_cc;
+
+ if (cc->is_fsp_ext) {
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ cc = NULL;
+ } else {
+ TALLOC_FREE(cc);
+ }
+
+ *_cc = NULL;
+}
+
+static struct catia_cache *catia_validate_and_apply_cc(
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ const struct catia_cache * const *busy,
+ bool *make_tmp_cache)
+{
+ struct catia_cache *cc = NULL;
+
+ *make_tmp_cache = false;
+
+ cc = (struct catia_cache *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+ if (cc == NULL) {
+ return NULL;
+ }
+
+ if (cc->busy != NULL) {
+ if (cc->busy == busy) {
+ /* This should never happen */
+ CATIA_DEBUG_CC(0, cc, fsp);
+ smb_panic(__location__);
+ }
+
+ /*
+ * Recursion. Validate names, the names in the fsp's should be
+ * the translated names we had set.
+ */
+
+ if ((cc->fname != fsp->fsp_name->base_name)
+ ||
+ (fsp_is_alternate_stream(fsp) &&
+ (cc->base_fname != fsp->base_fsp->fsp_name->base_name)))
+ {
+ CATIA_DEBUG_CC(10, cc, fsp);
+
+ /*
+ * Names changed. Setting don't expose the cache on the
+ * fsp and ask the caller to create a temporary cache.
+ */
+ *make_tmp_cache = true;
+ return NULL;
+ }
+
+ /*
+ * Ok, a validated cache while in a recursion, just let the
+ * caller detect that cc->busy is != busy and there's
+ * nothing else to do.
+ */
+ CATIA_DEBUG_CC(10, cc, fsp);
+ return cc;
+ }
+
+ /* Not in a recursion */
+
+ if ((cc->orig_fname != fsp->fsp_name->base_name)
+ ||
+ (fsp_is_alternate_stream(fsp) &&
+ (cc->orig_base_fname != fsp->base_fsp->fsp_name->base_name)))
+ {
+ /*
+ * fsp names changed, this can happen in an rename op.
+ * Trigger recreation as a full fledged fsp extension.
+ */
+
+ CATIA_DEBUG_CC(10, cc, fsp);
+ catia_free_cc(&cc, handle, fsp);
+ return NULL;
+ }
+
+
+ /*
+ * Ok, we found a valid cache entry, no recursion. Just set translated
+ * names from the cache and mark the cc as busy.
+ */
+ fsp->fsp_name->base_name = cc->fname;
+ if (fsp_is_alternate_stream(fsp)) {
+ fsp->base_fsp->fsp_name->base_name = cc->base_fname;
+ }
+
+ cc->busy = busy;
+ CATIA_DEBUG_CC(10, cc, fsp);
+ return cc;
+}
+
+#define CATIA_FETCH_FSP_PRE_NEXT(mem_ctx, handle, fsp, _cc) \
+ catia_fetch_fsp_pre_next((mem_ctx), (handle), (fsp), (_cc), __func__);
+
+static int catia_fetch_fsp_pre_next(TALLOC_CTX *mem_ctx,
+ vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct catia_cache **_cc,
+ const char *function)
+{
+ const struct catia_cache * const *busy =
+ (const struct catia_cache * const *)_cc;
+ struct catia_cache *cc = NULL;
+ NTSTATUS status;
+ bool make_tmp_cache = false;
+
+ *_cc = NULL;
+
+ DBG_DEBUG("Called from [%s]\n", function);
+
+ cc = catia_validate_and_apply_cc(handle,
+ fsp,
+ busy,
+ &make_tmp_cache);
+ if (cc != NULL) {
+ if (cc->busy != busy) {
+ return 0;
+ }
+ *_cc = cc;
+ return 0;
+ }
+
+ if (!make_tmp_cache) {
+ cc = VFS_ADD_FSP_EXTENSION(
+ handle, fsp, struct catia_cache, NULL);
+ if (cc == NULL) {
+ return -1;
+ }
+ *cc = (struct catia_cache) {
+ .is_fsp_ext = true,
+ };
+
+ mem_ctx = VFS_MEMCTX_FSP_EXTENSION(handle, fsp);
+ if (mem_ctx == NULL) {
+ DBG_ERR("VFS_MEMCTX_FSP_EXTENSION failed\n");
+ catia_free_cc(&cc, handle, fsp);
+ return -1;
+ }
+ } else {
+ cc = talloc_zero(mem_ctx, struct catia_cache);
+ if (cc == NULL) {
+ return -1;
+ }
+ mem_ctx = cc;
+ }
+
+
+ status = catia_string_replace_allocate(handle->conn,
+ fsp->fsp_name->base_name,
+ &cc->fname,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ catia_free_cc(&cc, handle, fsp);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+ talloc_steal(mem_ctx, cc->fname);
+
+ if (fsp_is_alternate_stream(fsp)) {
+ status = catia_string_replace_allocate(
+ handle->conn,
+ fsp->base_fsp->fsp_name->base_name,
+ &cc->base_fname,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ catia_free_cc(&cc, handle, fsp);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+ talloc_steal(mem_ctx, cc->base_fname);
+ }
+
+ cc->orig_fname = fsp->fsp_name->base_name;
+ fsp->fsp_name->base_name = cc->fname;
+
+ if (fsp_is_alternate_stream(fsp)) {
+ cc->orig_base_fname = fsp->base_fsp->fsp_name->base_name;
+ fsp->base_fsp->fsp_name->base_name = cc->base_fname;
+ }
+
+ cc->busy = busy;
+ CATIA_DEBUG_CC(10, cc, fsp);
+
+ *_cc = cc;
+
+ return 0;
+}
+
+#define CATIA_FETCH_FSP_POST_NEXT(_cc, fsp) do { \
+ int catia_saved_errno = errno; \
+ catia_fetch_fsp_post_next((_cc), (fsp), __func__); \
+ errno = catia_saved_errno; \
+} while(0)
+
+static void catia_fetch_fsp_post_next(struct catia_cache **_cc,
+ files_struct *fsp,
+ const char *function)
+{
+ const struct catia_cache * const *busy =
+ (const struct catia_cache * const *)_cc;
+ struct catia_cache *cc = *_cc;
+
+ DBG_DEBUG("Called from [%s]\n", function);
+
+ if (cc == NULL) {
+ /*
+ * This can happen when recursing in the VFS on the fsp when the
+ * pre_next func noticed the recursion and set out cc pointer to
+ * NULL.
+ */
+ return;
+ }
+
+ if (cc->busy != busy) {
+ CATIA_DEBUG_CC(0, cc, fsp);
+ smb_panic(__location__);
+ return;
+ }
+
+ cc->busy = NULL;
+ *_cc = NULL;
+
+ fsp->fsp_name->base_name = cc->orig_fname;
+ if (fsp_is_alternate_stream(fsp)) {
+ fsp->base_fsp->fsp_name->base_name = cc->orig_base_fname;
+ }
+
+ CATIA_DEBUG_CC(10, cc, fsp);
+
+ if (!cc->is_fsp_ext) {
+ TALLOC_FREE(cc);
+ }
+
+ return;
+}
+
+static int catia_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ struct smb_filename *smb_fname = NULL;
+ struct catia_cache *cc = NULL;
+ char *mapped_name = NULL;
+ NTSTATUS status;
+ int ret;
+ int saved_errno = 0;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname_in->base_name,
+ &mapped_name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ TALLOC_FREE(mapped_name);
+ return ret;
+ }
+
+ smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(mapped_name);
+ errno = ENOMEM;
+ return -1;
+ }
+ smb_fname->base_name = mapped_name;
+
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(mapped_name);
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int catia_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smb_filename *smb_fname_src_tmp = NULL;
+ struct smb_filename *smb_fname_dst_tmp = NULL;
+ char *src_name_mapped = NULL;
+ char *dst_name_mapped = NULL;
+ NTSTATUS status;
+ int ret = -1;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname_src->base_name,
+ &src_name_mapped, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname_dst->base_name,
+ &dst_name_mapped, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ /* Setup temporary smb_filename structs. */
+ smb_fname_src_tmp = cp_smb_filename(ctx, smb_fname_src);
+ if (smb_fname_src_tmp == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ smb_fname_dst_tmp = cp_smb_filename(ctx, smb_fname_dst);
+ if (smb_fname_dst_tmp == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ smb_fname_src_tmp->base_name = src_name_mapped;
+ smb_fname_dst_tmp->base_name = dst_name_mapped;
+ DEBUG(10, ("converted old name: %s\n",
+ smb_fname_str_dbg(smb_fname_src_tmp)));
+ DEBUG(10, ("converted new name: %s\n",
+ smb_fname_str_dbg(smb_fname_dst_tmp)));
+
+ ret = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src_tmp,
+ dstfsp,
+ smb_fname_dst_tmp);
+
+out:
+ TALLOC_FREE(src_name_mapped);
+ TALLOC_FREE(dst_name_mapped);
+ TALLOC_FREE(smb_fname_src_tmp);
+ TALLOC_FREE(smb_fname_dst_tmp);
+ return ret;
+}
+
+
+static int catia_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ char *name = NULL;
+ char *tmp_base_name;
+ int ret;
+ NTSTATUS status;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &name, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ tmp_base_name = smb_fname->base_name;
+ smb_fname->base_name = name;
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ smb_fname->base_name = tmp_base_name;
+
+ TALLOC_FREE(name);
+ return ret;
+}
+
+static int catia_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ char *name = NULL;
+ char *tmp_base_name;
+ int ret;
+ NTSTATUS status;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &name, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ tmp_base_name = smb_fname->base_name;
+ smb_fname->base_name = name;
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ smb_fname->base_name = tmp_base_name;
+ TALLOC_FREE(name);
+
+ return ret;
+}
+
+static int catia_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct catia_cache *cc = NULL;
+ struct smb_filename *smb_fname_tmp = NULL;
+ char *name = NULL;
+ NTSTATUS status;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, dirfsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &name, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ /* Setup temporary smb_filename structs. */
+ smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
+ if (smb_fname_tmp == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ smb_fname_tmp->base_name = name;
+ smb_fname_tmp->fsp = smb_fname->fsp;
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname_tmp,
+ flags);
+ TALLOC_FREE(smb_fname_tmp);
+ TALLOC_FREE(name);
+
+out:
+ CATIA_FETCH_FSP_POST_NEXT(&cc, dirfsp);
+ return ret;
+}
+
+static int catia_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ char *name = NULL;
+ NTSTATUS status;
+ int ret;
+ int saved_errno;
+ struct smb_filename *catia_smb_fname = NULL;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+ catia_smb_fname = synthetic_smb_fname(talloc_tos(),
+ name,
+ NULL,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (catia_smb_fname == NULL) {
+ TALLOC_FREE(name);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_LCHOWN(handle, catia_smb_fname, uid, gid);
+ saved_errno = errno;
+ TALLOC_FREE(name);
+ TALLOC_FREE(catia_smb_fname);
+ errno = saved_errno;
+ return ret;
+}
+
+static int catia_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ char *name = NULL;
+ NTSTATUS status;
+ int ret;
+ struct smb_filename *catia_smb_fname = NULL;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+ catia_smb_fname = synthetic_smb_fname(talloc_tos(),
+ name,
+ NULL,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (catia_smb_fname == NULL) {
+ TALLOC_FREE(name);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ catia_smb_fname,
+ mode);
+ TALLOC_FREE(name);
+ TALLOC_FREE(catia_smb_fname);
+
+ return ret;
+}
+
+static int catia_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ char *name = NULL;
+ struct smb_filename *catia_smb_fname = NULL;
+ NTSTATUS status;
+ int ret;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ catia_smb_fname = synthetic_smb_fname(talloc_tos(),
+ name,
+ NULL,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (catia_smb_fname == NULL) {
+ TALLOC_FREE(name);
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_CHDIR(handle, catia_smb_fname);
+ TALLOC_FREE(name);
+ TALLOC_FREE(catia_smb_fname);
+
+ return ret;
+}
+
+static int catia_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static struct smb_filename *
+catia_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ char *mapped_name = NULL;
+ struct smb_filename *catia_smb_fname = NULL;
+ struct smb_filename *return_fname = NULL;
+ NTSTATUS status;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &mapped_name, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return NULL;
+ }
+
+ catia_smb_fname = synthetic_smb_fname(talloc_tos(),
+ mapped_name,
+ NULL,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (catia_smb_fname == NULL) {
+ TALLOC_FREE(mapped_name);
+ errno = ENOMEM;
+ return NULL;
+ }
+ return_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, catia_smb_fname);
+ TALLOC_FREE(mapped_name);
+ TALLOC_FREE(catia_smb_fname);
+ return return_fname;
+}
+
+static NTSTATUS
+catia_fstreaminfo(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *_num_streams,
+ struct stream_struct **_streams)
+{
+ char *mapped_name = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ struct smb_filename *catia_smb_fname = NULL;
+ struct smb_filename *smb_fname = NULL;
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+
+ smb_fname = fsp->fsp_name;
+ *_num_streams = 0;
+ *_streams = NULL;
+
+ status = catia_string_replace_allocate(handle->conn,
+ smb_fname->base_name,
+ &mapped_name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ mapped_name,
+ NULL,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &catia_smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(mapped_name);
+ return status;
+ }
+
+ status = SMB_VFS_NEXT_FSTREAMINFO(handle,
+ catia_smb_fname->fsp,
+ mem_ctx,
+ &num_streams,
+ &streams);
+ TALLOC_FREE(mapped_name);
+ TALLOC_FREE(catia_smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Translate stream names just like the base names
+ */
+ for (i = 0; i < num_streams; i++) {
+ /*
+ * Strip ":" prefix and ":$DATA" suffix to get a
+ * "pure" stream name and only translate that.
+ */
+ void *old_ptr = streams[i].name;
+ char *stream_name = streams[i].name + 1;
+ char *stream_type = strrchr_m(stream_name, ':');
+
+ if (stream_type != NULL) {
+ *stream_type = '\0';
+ stream_type += 1;
+ }
+
+ status = catia_string_replace_allocate(handle->conn,
+ stream_name,
+ &mapped_name,
+ vfs_translate_to_windows);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(streams);
+ return status;
+ }
+
+ if (stream_type != NULL) {
+ streams[i].name = talloc_asprintf(streams,
+ ":%s:%s",
+ mapped_name,
+ stream_type);
+ } else {
+ streams[i].name = talloc_asprintf(streams,
+ ":%s",
+ mapped_name);
+ }
+ TALLOC_FREE(mapped_name);
+ TALLOC_FREE(old_ptr);
+ if (streams[i].name == NULL) {
+ TALLOC_FREE(streams);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_num_streams = num_streams;
+ *_streams = streams;
+ return NT_STATUS_OK;
+}
+
+static int catia_fstat(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static ssize_t catia_pread(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ struct catia_cache *cc = NULL;
+ ssize_t result;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+static ssize_t catia_pwrite(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ struct catia_cache *cc = NULL;
+ ssize_t result;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+static int catia_ftruncate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static int catia_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static ssize_t catia_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ void *value,
+ size_t size)
+{
+ char *mapped_xattr_name = NULL;
+ NTSTATUS status;
+ ssize_t result;
+
+ status = catia_string_replace_allocate(handle->conn,
+ name, &mapped_xattr_name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_FGETXATTR(handle, fsp, mapped_xattr_name,
+ value, size);
+
+ TALLOC_FREE(mapped_xattr_name);
+
+ return result;
+}
+
+static ssize_t catia_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ char *list,
+ size_t size)
+{
+ struct catia_cache *cc = NULL;
+ ssize_t result;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+static int catia_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name)
+{
+ char *mapped_name = NULL;
+ NTSTATUS status;
+ int ret;
+
+ status = catia_string_replace_allocate(handle->conn,
+ name, &mapped_name, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, mapped_name);
+
+ TALLOC_FREE(mapped_name);
+
+ return ret;
+}
+
+static int catia_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ char *mapped_xattr_name = NULL;
+ NTSTATUS status;
+ int ret;
+
+ status = catia_string_replace_allocate(
+ handle->conn, name, &mapped_xattr_name, vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, mapped_xattr_name,
+ value, size, flags);
+
+ TALLOC_FREE(mapped_xattr_name);
+
+ return ret;
+}
+
+static SMB_ACL_T catia_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ struct catia_cache *cc = NULL;
+ struct smb_acl_t *result = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ result = SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, fsp, type, mem_ctx);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+static int catia_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx,
+ blob_description, blob);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static int catia_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static NTSTATUS catia_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ struct catia_cache *cc = NULL;
+ NTSTATUS status;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info,
+ mem_ctx, ppdesc);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return status;
+}
+
+static NTSTATUS catia_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ struct catia_cache *cc = NULL;
+ NTSTATUS status;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return status;
+}
+
+static NTSTATUS catia_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ struct catia_cache *cc = NULL;
+ NTSTATUS status;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ status = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return status;
+}
+
+static NTSTATUS catia_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ struct catia_cache *cc = NULL;
+ NTSTATUS status;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ status = SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return status;
+}
+
+static int catia_fchown(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uid_t uid,
+ gid_t gid)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static int catia_fchmod(vfs_handle_struct *handle,
+ files_struct *fsp,
+ mode_t mode)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+struct catia_pread_state {
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+ struct files_struct *fsp;
+ struct catia_cache *cc;
+};
+
+static void catia_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *catia_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n,
+ off_t offset)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct catia_pread_state *state = NULL;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct catia_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(state, handle, fsp, &state->cc);
+ if (ret != 0) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, catia_pread_done, req);
+
+ return req;
+}
+
+static void catia_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct catia_pread_state *state = tevent_req_data(
+ req, struct catia_pread_state);
+
+ state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ CATIA_FETCH_FSP_POST_NEXT(&state->cc, state->fsp);
+
+ tevent_req_done(req);
+}
+
+static ssize_t catia_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct catia_pread_state *state = tevent_req_data(
+ req, struct catia_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct catia_pwrite_state {
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+ struct files_struct *fsp;
+ struct catia_cache *cc;
+};
+
+static void catia_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *catia_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n,
+ off_t offset)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct catia_pwrite_state *state = NULL;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct catia_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(state, handle, fsp, &state->cc);
+ if (ret != 0) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, catia_pwrite_done, req);
+
+ return req;
+}
+
+static void catia_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct catia_pwrite_state *state = tevent_req_data(
+ req, struct catia_pwrite_state);
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ CATIA_FETCH_FSP_POST_NEXT(&state->cc, state->fsp);
+
+ tevent_req_done(req);
+}
+
+static ssize_t catia_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct catia_pwrite_state *state = tevent_req_data(
+ req, struct catia_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static off_t catia_lseek(vfs_handle_struct *handle,
+ files_struct *fsp,
+ off_t offset,
+ int whence)
+{
+ struct catia_cache *cc = NULL;
+ ssize_t result;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_LSEEK(handle, fsp, offset, whence);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+struct catia_fsync_state {
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+ struct files_struct *fsp;
+ struct catia_cache *cc;
+};
+
+static void catia_fsync_done(struct tevent_req *subreq);
+
+static struct tevent_req *catia_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct catia_fsync_state *state = NULL;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct catia_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(state, handle, fsp, &state->cc);
+ if (ret != 0) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, catia_fsync_done, req);
+
+ return req;
+}
+
+static void catia_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct catia_fsync_state *state = tevent_req_data(
+ req, struct catia_fsync_state);
+
+ state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ CATIA_FETCH_FSP_POST_NEXT(&state->cc, state->fsp);
+
+ tevent_req_done(req);
+}
+
+static int catia_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct catia_fsync_state *state = tevent_req_data(
+ req, struct catia_fsync_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static bool catia_lock(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int op,
+ off_t offset,
+ off_t count,
+ int type)
+{
+ struct catia_cache *cc = NULL;
+ bool ok;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return false;
+ }
+
+ ok = SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ok;
+}
+
+static int catia_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle,
+ fsp,
+ share_access,
+ access_mask);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static int catia_linux_setlease(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int leasetype)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ret;
+}
+
+static bool catia_getlock(vfs_handle_struct *handle,
+ files_struct *fsp,
+ off_t *poffset,
+ off_t *pcount,
+ int *ptype,
+ pid_t *ppid)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+ bool ok;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return false;
+ }
+
+ ok = SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, pcount, ptype, ppid);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ok;
+}
+
+static bool catia_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock)
+{
+ struct catia_cache *cc = NULL;
+ int ret;
+ bool ok;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return false;
+ }
+
+ ok = SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return ok;
+}
+
+static NTSTATUS catia_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags,
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len)
+{
+ NTSTATUS result;
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ result = SMB_VFS_NEXT_FSCTL(handle,
+ fsp,
+ ctx,
+ function,
+ req_flags,
+ _in_data,
+ in_len,
+ _out_data,
+ max_out_len,
+ out_len);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+static NTSTATUS catia_fget_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ NTSTATUS result;
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ result = SMB_VFS_NEXT_FGET_COMPRESSION(handle,
+ mem_ctx,
+ fsp,
+ _compression_fmt);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+static NTSTATUS catia_set_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ NTSTATUS result;
+ struct catia_cache *cc = NULL;
+ int ret;
+
+ ret = CATIA_FETCH_FSP_PRE_NEXT(talloc_tos(), handle, fsp, &cc);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ result = SMB_VFS_NEXT_SET_COMPRESSION(handle, mem_ctx, fsp,
+ compression_fmt);
+
+ CATIA_FETCH_FSP_POST_NEXT(&cc, fsp);
+
+ return result;
+}
+
+static NTSTATUS catia_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ char *mapped_name = NULL;
+ const char *path = smb_fname->base_name;
+ struct smb_filename *mapped_smb_fname = NULL;
+ NTSTATUS status;
+
+ status = catia_string_replace_allocate(handle->conn,
+ path,
+ &mapped_name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return status;
+ }
+ mapped_smb_fname = synthetic_smb_fname(talloc_tos(),
+ mapped_name,
+ NULL,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (mapped_smb_fname == NULL) {
+ TALLOC_FREE(mapped_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle,
+ dirfsp,
+ mapped_smb_fname,
+ reflist,
+ referral_count);
+ TALLOC_FREE(mapped_name);
+ TALLOC_FREE(mapped_smb_fname);
+ return status;
+}
+
+static NTSTATUS catia_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ char *mapped_name = NULL;
+ const char *path = smb_fname->base_name;
+ struct smb_filename *mapped_smb_fname = NULL;
+ NTSTATUS status;
+
+ status = catia_string_replace_allocate(handle->conn,
+ path,
+ &mapped_name,
+ vfs_translate_to_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return status;
+ }
+ mapped_smb_fname = synthetic_smb_fname(talloc_tos(),
+ mapped_name,
+ NULL,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (mapped_smb_fname == NULL) {
+ TALLOC_FREE(mapped_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle,
+ mem_ctx,
+ dirfsp,
+ mapped_smb_fname,
+ ppreflist,
+ preferral_count);
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return any stat(2) info. */
+ smb_fname->st = mapped_smb_fname->st;
+ }
+
+ TALLOC_FREE(mapped_name);
+ TALLOC_FREE(mapped_smb_fname);
+ return status;
+}
+
+static struct vfs_fn_pointers vfs_catia_fns = {
+ .connect_fn = catia_connect,
+
+ /* Directory operations */
+ .mkdirat_fn = catia_mkdirat,
+
+ /* File operations */
+ .openat_fn = catia_openat,
+ .pread_fn = catia_pread,
+ .pread_send_fn = catia_pread_send,
+ .pread_recv_fn = catia_pread_recv,
+ .pwrite_fn = catia_pwrite,
+ .pwrite_send_fn = catia_pwrite_send,
+ .pwrite_recv_fn = catia_pwrite_recv,
+ .lseek_fn = catia_lseek,
+ .renameat_fn = catia_renameat,
+ .fsync_send_fn = catia_fsync_send,
+ .fsync_recv_fn = catia_fsync_recv,
+ .stat_fn = catia_stat,
+ .fstat_fn = catia_fstat,
+ .lstat_fn = catia_lstat,
+ .unlinkat_fn = catia_unlinkat,
+ .fchmod_fn = catia_fchmod,
+ .fchown_fn = catia_fchown,
+ .lchown_fn = catia_lchown,
+ .chdir_fn = catia_chdir,
+ .fntimes_fn = catia_fntimes,
+ .ftruncate_fn = catia_ftruncate,
+ .fallocate_fn = catia_fallocate,
+ .lock_fn = catia_lock,
+ .filesystem_sharemode_fn = catia_filesystem_sharemode,
+ .linux_setlease_fn = catia_linux_setlease,
+ .getlock_fn = catia_getlock,
+ .realpath_fn = catia_realpath,
+ .fstreaminfo_fn = catia_fstreaminfo,
+ .strict_lock_check_fn = catia_strict_lock_check,
+ .translate_name_fn = catia_translate_name,
+ .fsctl_fn = catia_fsctl,
+ .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv,
+ .fset_dos_attributes_fn = catia_fset_dos_attributes,
+ .fget_dos_attributes_fn = catia_fget_dos_attributes,
+ .fget_compression_fn = catia_fget_compression,
+ .set_compression_fn = catia_set_compression,
+ .create_dfs_pathat_fn = catia_create_dfs_pathat,
+ .read_dfs_pathat_fn = catia_read_dfs_pathat,
+
+ /* NT ACL operations. */
+ .fget_nt_acl_fn = catia_fget_nt_acl,
+ .fset_nt_acl_fn = catia_fset_nt_acl,
+
+ /* POSIX ACL operations. */
+ .sys_acl_get_fd_fn = catia_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = catia_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = catia_sys_acl_set_fd,
+
+ /* EA operations. */
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fgetxattr_fn = catia_fgetxattr,
+ .flistxattr_fn = catia_flistxattr,
+ .fremovexattr_fn = catia_fremovexattr,
+ .fsetxattr_fn = catia_fsetxattr,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_catia_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "catia",
+ &vfs_catia_fns);
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ vfs_catia_debug_level = debug_add_class("catia");
+ if (vfs_catia_debug_level == -1) {
+ vfs_catia_debug_level = DBGC_VFS;
+ DEBUG(0, ("vfs_catia: Couldn't register custom debugging "
+ "class!\n"));
+ } else {
+ DEBUG(10, ("vfs_catia: Debug class number of "
+ "'catia': %d\n", vfs_catia_debug_level));
+ }
+
+ return ret;
+
+}
diff --git a/source3/modules/vfs_ceph.c b/source3/modules/vfs_ceph.c
new file mode 100644
index 0000000..c9ee541
--- /dev/null
+++ b/source3/modules/vfs_ceph.c
@@ -0,0 +1,1960 @@
+/*
+ Unix SMB/CIFS implementation.
+ Wrap disk only vfs functions to sidestep dodgy compilers.
+ Copyright (C) Tim Potter 1998
+ Copyright (C) Jeremy Allison 2007
+ Copyright (C) Brian Chrisman 2011 <bchrisman@gmail.com>
+ Copyright (C) Richard Sharpe 2011 <realrichardsharpe@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This VFS only works with the libcephfs.so user-space client. It is not needed
+ * if you are using the kernel client or the FUSE client.
+ *
+ * Add the following smb.conf parameter to each share that will be hosted on
+ * Ceph:
+ *
+ * vfs objects = [any others you need go here] ceph
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include <dirent.h>
+#include <sys/statvfs.h>
+#include "cephfs/libcephfs.h"
+#include "smbprofile.h"
+#include "modules/posixacl_xattr.h"
+#include "lib/util/tevent_unix.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#ifndef LIBCEPHFS_VERSION
+#define LIBCEPHFS_VERSION(maj, min, extra) ((maj << 16) + (min << 8) + extra)
+#define LIBCEPHFS_VERSION_CODE LIBCEPHFS_VERSION(0, 0, 0)
+#endif
+
+/*
+ * Use %llu whenever we have a 64bit unsigned int, and cast to (long long unsigned)
+ */
+#define llu(_var) ((long long unsigned)_var)
+
+/*
+ * Note, libcephfs's return code model is to return -errno! So we have to
+ * convert to what Samba expects, which is to set errno to -return and return -1
+ */
+#define WRAP_RETURN(_res) \
+ errno = 0; \
+ if (_res < 0) { \
+ errno = -_res; \
+ return -1; \
+ } \
+ return _res \
+
+/*
+ * Track unique connections, as virtual mounts, to cephfs file systems.
+ * Individual mounts will be set on the handle->data attribute, but
+ * the mounts themselves will be shared so as not to spawn extra mounts
+ * to the same cephfs.
+ *
+ * Individual mounts are IDed by a 'cookie' value that is a string built
+ * from identifying parameters found in smb.conf.
+ */
+
+static struct cephmount_cached {
+ char *cookie;
+ uint32_t count;
+ struct ceph_mount_info *mount;
+ struct cephmount_cached *next, *prev;
+} *cephmount_cached;
+
+static int cephmount_cache_add(const char *cookie,
+ struct ceph_mount_info *mount)
+{
+ struct cephmount_cached *entry = NULL;
+
+ entry = talloc_zero(NULL, struct cephmount_cached);
+ if (entry == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ entry->cookie = talloc_strdup(entry, cookie);
+ if (entry->cookie == NULL) {
+ talloc_free(entry);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ entry->mount = mount;
+ entry->count = 1;
+
+ DBG_DEBUG("adding mount cache entry for %s\n", entry->cookie);
+ DLIST_ADD(cephmount_cached, entry);
+ return 0;
+}
+
+static struct ceph_mount_info *cephmount_cache_update(const char *cookie)
+{
+ struct cephmount_cached *entry = NULL;
+
+ for (entry = cephmount_cached; entry; entry = entry->next) {
+ if (strcmp(entry->cookie, cookie) == 0) {
+ entry->count++;
+ DBG_DEBUG("updated mount cache: count is [%"
+ PRIu32 "]\n", entry->count);
+ return entry->mount;
+ }
+ }
+
+ errno = ENOENT;
+ return NULL;
+}
+
+static int cephmount_cache_remove(struct ceph_mount_info *mount)
+{
+ struct cephmount_cached *entry = NULL;
+
+ for (entry = cephmount_cached; entry; entry = entry->next) {
+ if (entry->mount == mount) {
+ if (--entry->count) {
+ DBG_DEBUG("updated mount cache: count is [%"
+ PRIu32 "]\n", entry->count);
+ return entry->count;
+ }
+
+ DBG_DEBUG("removing mount cache entry for %s\n",
+ entry->cookie);
+ DLIST_REMOVE(cephmount_cached, entry);
+ talloc_free(entry);
+ return 0;
+ }
+ }
+ errno = ENOENT;
+ return -1;
+}
+
+static char *cephmount_get_cookie(TALLOC_CTX * mem_ctx, const int snum)
+{
+ const char *conf_file =
+ lp_parm_const_string(snum, "ceph", "config_file", ".");
+ const char *user_id = lp_parm_const_string(snum, "ceph", "user_id", "");
+ const char *fsname =
+ lp_parm_const_string(snum, "ceph", "filesystem", "");
+ return talloc_asprintf(mem_ctx, "(%s/%s/%s)", conf_file, user_id,
+ fsname);
+}
+
+static int cephmount_select_fs(struct ceph_mount_info *mnt, const char *fsname)
+{
+ /*
+ * ceph_select_filesystem was added in ceph 'nautilus' (v14).
+ * Earlier versions of libcephfs will lack that API function.
+ * At the time of this writing (Feb 2023) all versions of ceph
+ * supported by ceph upstream have this function.
+ */
+#if defined(HAVE_CEPH_SELECT_FILESYSTEM)
+ DBG_DEBUG("[CEPH] calling: ceph_select_filesystem with %s\n", fsname);
+ return ceph_select_filesystem(mnt, fsname);
+#else
+ DBG_ERR("[CEPH] ceph_select_filesystem not available\n");
+ return -ENOTSUP;
+#endif
+}
+
+static struct ceph_mount_info *cephmount_mount_fs(const int snum)
+{
+ int ret;
+ char buf[256];
+ struct ceph_mount_info *mnt = NULL;
+ /* if config_file and/or user_id are NULL, ceph will use defaults */
+ const char *conf_file =
+ lp_parm_const_string(snum, "ceph", "config_file", NULL);
+ const char *user_id =
+ lp_parm_const_string(snum, "ceph", "user_id", NULL);
+ const char *fsname =
+ lp_parm_const_string(snum, "ceph", "filesystem", NULL);
+
+ DBG_DEBUG("[CEPH] calling: ceph_create\n");
+ ret = ceph_create(&mnt, user_id);
+ if (ret) {
+ errno = -ret;
+ return NULL;
+ }
+
+ DBG_DEBUG("[CEPH] calling: ceph_conf_read_file with %s\n",
+ (conf_file == NULL ? "default path" : conf_file));
+ ret = ceph_conf_read_file(mnt, conf_file);
+ if (ret) {
+ goto err_cm_release;
+ }
+
+ DBG_DEBUG("[CEPH] calling: ceph_conf_get\n");
+ ret = ceph_conf_get(mnt, "log file", buf, sizeof(buf));
+ if (ret < 0) {
+ goto err_cm_release;
+ }
+
+ /* libcephfs disables POSIX ACL support by default, enable it... */
+ ret = ceph_conf_set(mnt, "client_acl_type", "posix_acl");
+ if (ret < 0) {
+ goto err_cm_release;
+ }
+ /* tell libcephfs to perform local permission checks */
+ ret = ceph_conf_set(mnt, "fuse_default_permissions", "false");
+ if (ret < 0) {
+ goto err_cm_release;
+ }
+ /*
+ * select a cephfs file system to use:
+ * In ceph, multiple file system support has been stable since 'pacific'.
+ * Permit different shares to access different file systems.
+ */
+ if (fsname != NULL) {
+ ret = cephmount_select_fs(mnt, fsname);
+ if (ret < 0) {
+ goto err_cm_release;
+ }
+ }
+
+ DBG_DEBUG("[CEPH] calling: ceph_mount\n");
+ ret = ceph_mount(mnt, NULL);
+ if (ret >= 0) {
+ goto cm_done;
+ }
+
+ err_cm_release:
+ ceph_release(mnt);
+ mnt = NULL;
+ DBG_DEBUG("[CEPH] Error mounting fs: %s\n", strerror(-ret));
+ cm_done:
+ /*
+ * Handle the error correctly. Ceph returns -errno.
+ */
+ if (ret) {
+ errno = -ret;
+ }
+ return mnt;
+}
+
+/* Check for NULL pointer parameters in cephwrap_* functions */
+
+/* We don't want to have NULL function pointers lying around. Someone
+ is sure to try and execute them. These stubs are used to prevent
+ this possibility. */
+
+static int cephwrap_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ int ret = 0;
+ struct ceph_mount_info *cmount = NULL;
+ int snum = SNUM(handle->conn);
+ char *cookie = cephmount_get_cookie(handle, snum);
+ if (cookie == NULL) {
+ return -1;
+ }
+
+ cmount = cephmount_cache_update(cookie);
+ if (cmount != NULL) {
+ goto connect_ok;
+ }
+
+ cmount = cephmount_mount_fs(snum);
+ if (cmount == NULL) {
+ ret = -1;
+ goto connect_fail;
+ }
+ ret = cephmount_cache_add(cookie, cmount);
+ if (ret) {
+ goto connect_fail;
+ }
+
+ connect_ok:
+ handle->data = cmount;
+ DBG_WARNING("Connection established with the server: %s\n", cookie);
+ /*
+ * Unless we have an async implementation of getxattrat turn this off.
+ */
+ lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false");
+ connect_fail:
+ talloc_free(cookie);
+ return ret;
+}
+
+static void cephwrap_disconnect(struct vfs_handle_struct *handle)
+{
+ int ret = cephmount_cache_remove(handle->data);
+ if (ret < 0) {
+ DBG_ERR("failed to remove ceph mount from cache: %s\n",
+ strerror(errno));
+ return;
+ }
+ if (ret > 0) {
+ DBG_DEBUG("mount cache entry still in use\n");
+ return;
+ }
+
+ ret = ceph_unmount(handle->data);
+ if (ret < 0) {
+ DBG_ERR("[CEPH] failed to unmount: %s\n", strerror(-ret));
+ }
+
+ ret = ceph_release(handle->data);
+ if (ret < 0) {
+ DBG_ERR("[CEPH] failed to release: %s\n", strerror(-ret));
+ }
+ handle->data = NULL;
+}
+
+/* Disk operations */
+
+static uint64_t cephwrap_disk_free(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ struct statvfs statvfs_buf = { 0 };
+ int ret;
+
+ if (!(ret = ceph_statfs(handle->data, smb_fname->base_name,
+ &statvfs_buf))) {
+ /*
+ * Provide all the correct values.
+ */
+ *bsize = statvfs_buf.f_bsize;
+ *dfree = statvfs_buf.f_bavail;
+ *dsize = statvfs_buf.f_blocks;
+ DBG_DEBUG("[CEPH] bsize: %llu, dfree: %llu, dsize: %llu\n",
+ llu(*bsize), llu(*dfree), llu(*dsize));
+ return *dfree;
+ } else {
+ DBG_DEBUG("[CEPH] ceph_statfs returned %d\n", ret);
+ WRAP_RETURN(ret);
+ }
+}
+
+static int cephwrap_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ /* libcephfs: Ceph does not implement this */
+#if 0
+/* was ifdef HAVE_SYS_QUOTAS */
+ int ret;
+
+ ret = ceph_get_quota(handle->conn->connectpath, qtype, id, qt);
+
+ if (ret) {
+ errno = -ret;
+ ret = -1;
+ }
+
+ return ret;
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static int cephwrap_set_quota(struct vfs_handle_struct *handle, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *qt)
+{
+ /* libcephfs: Ceph does not implement this */
+#if 0
+/* was ifdef HAVE_SYS_QUOTAS */
+ int ret;
+
+ ret = ceph_set_quota(handle->conn->connectpath, qtype, id, qt);
+ if (ret) {
+ errno = -ret;
+ ret = -1;
+ }
+
+ return ret;
+#else
+ WRAP_RETURN(-ENOSYS);
+#endif
+}
+
+static int cephwrap_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ struct statvfs statvfs_buf = { 0 };
+ int ret;
+
+ ret = ceph_statfs(handle->data, smb_fname->base_name, &statvfs_buf);
+ if (ret < 0) {
+ WRAP_RETURN(ret);
+ }
+
+ statbuf->OptimalTransferSize = statvfs_buf.f_frsize;
+ statbuf->BlockSize = statvfs_buf.f_bsize;
+ statbuf->TotalBlocks = statvfs_buf.f_blocks;
+ statbuf->BlocksAvail = statvfs_buf.f_bfree;
+ statbuf->UserBlocksAvail = statvfs_buf.f_bavail;
+ statbuf->TotalFileNodes = statvfs_buf.f_files;
+ statbuf->FreeFileNodes = statvfs_buf.f_ffree;
+ statbuf->FsIdentifier = statvfs_buf.f_fsid;
+ DBG_DEBUG("[CEPH] f_bsize: %ld, f_blocks: %ld, f_bfree: %ld, f_bavail: %ld\n",
+ (long int)statvfs_buf.f_bsize, (long int)statvfs_buf.f_blocks,
+ (long int)statvfs_buf.f_bfree, (long int)statvfs_buf.f_bavail);
+
+ return ret;
+}
+
+static uint32_t cephwrap_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ uint32_t caps = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+
+ *p_ts_res = TIMESTAMP_SET_NT_OR_BETTER;
+
+ return caps;
+}
+
+/* Directory operations */
+
+static DIR *cephwrap_fdopendir(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *mask,
+ uint32_t attributes)
+{
+ int ret = 0;
+ struct ceph_dir_result *result = NULL;
+
+#ifdef HAVE_CEPH_FDOPENDIR
+ int dirfd = fsp_get_io_fd(fsp);
+ DBG_DEBUG("[CEPH] fdopendir(%p, %d)\n", handle, dirfd);
+ ret = ceph_fdopendir(handle->data, dirfd, &result);
+#else
+ DBG_DEBUG("[CEPH] fdopendir(%p, %p)\n", handle, fsp);
+ ret = ceph_opendir(handle->data, fsp->fsp_name->base_name, &result);
+#endif
+ if (ret < 0) {
+ result = NULL;
+ errno = -ret; /* We return result which is NULL in this case */
+ }
+
+ DBG_DEBUG("[CEPH] fdopendir(...) = %d\n", ret);
+ return (DIR *) result;
+}
+
+static struct dirent *cephwrap_readdir(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ struct dirent *result = NULL;
+
+ DBG_DEBUG("[CEPH] readdir(%p, %p)\n", handle, dirp);
+ result = ceph_readdir(handle->data, (struct ceph_dir_result *) dirp);
+ DBG_DEBUG("[CEPH] readdir(...) = %p\n", result);
+
+ return result;
+}
+
+static void cephwrap_rewinddir(struct vfs_handle_struct *handle, DIR *dirp)
+{
+ DBG_DEBUG("[CEPH] rewinddir(%p, %p)\n", handle, dirp);
+ ceph_rewinddir(handle->data, (struct ceph_dir_result *) dirp);
+}
+
+static int cephwrap_mkdirat(struct vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ int result = -1;
+#ifdef HAVE_CEPH_MKDIRAT
+ int dirfd = fsp_get_pathref_fd(dirfsp);
+
+ DBG_DEBUG("[CEPH] mkdirat(%p, %d, %s)\n",
+ handle,
+ dirfd,
+ smb_fname->base_name);
+
+ result = ceph_mkdirat(handle->data, dirfd, smb_fname->base_name, mode);
+
+ DBG_DEBUG("[CEPH] mkdirat(...) = %d\n", result);
+
+ WRAP_RETURN(result);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ DBG_DEBUG("[CEPH] mkdir(%p, %s)\n",
+ handle, smb_fname_str_dbg(full_fname));
+
+ result = ceph_mkdir(handle->data, full_fname->base_name, mode);
+
+ TALLOC_FREE(full_fname);
+
+ WRAP_RETURN(result);
+#endif
+}
+
+static int cephwrap_closedir(struct vfs_handle_struct *handle, DIR *dirp)
+{
+ int result;
+
+ DBG_DEBUG("[CEPH] closedir(%p, %p)\n", handle, dirp);
+ result = ceph_closedir(handle->data, (struct ceph_dir_result *) dirp);
+ DBG_DEBUG("[CEPH] closedir(...) = %d\n", result);
+ WRAP_RETURN(result);
+}
+
+/* File operations */
+
+static int cephwrap_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int flags = how->flags;
+ mode_t mode = how->mode;
+ struct smb_filename *name = NULL;
+ bool have_opath = false;
+ bool became_root = false;
+ int result = -ENOENT;
+#ifdef HAVE_CEPH_OPENAT
+ int dirfd = -1;
+#endif
+
+ if (how->resolve != 0) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (smb_fname->stream_name) {
+ goto out;
+ }
+
+#ifdef O_PATH
+ have_opath = true;
+ if (fsp->fsp_flags.is_pathref) {
+ flags |= O_PATH;
+ }
+#endif
+
+#ifdef HAVE_CEPH_OPENAT
+ dirfd = fsp_get_pathref_fd(dirfsp);
+
+ DBG_DEBUG("[CEPH] openat(%p, %d, %p, %d, %d)\n",
+ handle, dirfd, fsp, flags, mode);
+
+ if (fsp->fsp_flags.is_pathref && !have_opath) {
+ become_root();
+ became_root = true;
+ }
+
+ result = ceph_openat(handle->data,
+ dirfd,
+ smb_fname->base_name,
+ flags,
+ mode);
+
+#else
+ if (fsp_get_pathref_fd(dirfsp) != AT_FDCWD) {
+ name = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (name == NULL) {
+ return -1;
+ }
+ smb_fname = name;
+ }
+
+ DBG_DEBUG("[CEPH] openat(%p, %s, %p, %d, %d)\n", handle,
+ smb_fname_str_dbg(smb_fname), fsp, flags, mode);
+
+ if (fsp->fsp_flags.is_pathref && !have_opath) {
+ become_root();
+ became_root = true;
+ }
+
+ result = ceph_open(handle->data, smb_fname->base_name, flags, mode);
+#endif
+ if (became_root) {
+ unbecome_root();
+ }
+out:
+ TALLOC_FREE(name);
+ fsp->fsp_flags.have_proc_fds = false;
+ DBG_DEBUG("[CEPH] open(...) = %d\n", result);
+ WRAP_RETURN(result);
+}
+
+static int cephwrap_close(struct vfs_handle_struct *handle, files_struct *fsp)
+{
+ int result;
+
+ DBG_DEBUG("[CEPH] close(%p, %p)\n", handle, fsp);
+ result = ceph_close(handle->data, fsp_get_pathref_fd(fsp));
+ DBG_DEBUG("[CEPH] close(...) = %d\n", result);
+
+ WRAP_RETURN(result);
+}
+
+static ssize_t cephwrap_pread(struct vfs_handle_struct *handle, files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ ssize_t result;
+
+ DBG_DEBUG("[CEPH] pread(%p, %p, %p, %llu, %llu)\n", handle, fsp, data, llu(n), llu(offset));
+
+ result = ceph_read(handle->data, fsp_get_io_fd(fsp), data, n, offset);
+ DBG_DEBUG("[CEPH] pread(...) = %llu\n", llu(result));
+ WRAP_RETURN(result);
+}
+
+struct cephwrap_pread_state {
+ ssize_t bytes_read;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+/*
+ * Fake up an async ceph read by calling the synchronous API.
+ */
+static struct tevent_req *cephwrap_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct cephwrap_pread_state *state = NULL;
+ int ret = -1;
+
+ DBG_DEBUG("[CEPH] %s\n", __func__);
+ req = tevent_req_create(mem_ctx, &state, struct cephwrap_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ ret = ceph_read(handle->data, fsp_get_io_fd(fsp), data, n, offset);
+ if (ret < 0) {
+ /* ceph returns -errno on error. */
+ tevent_req_error(req, -ret);
+ return tevent_req_post(req, ev);
+ }
+
+ state->bytes_read = ret;
+ tevent_req_done(req);
+ /* Return and schedule the completion of the call. */
+ return tevent_req_post(req, ev);
+}
+
+static ssize_t cephwrap_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct cephwrap_pread_state *state =
+ tevent_req_data(req, struct cephwrap_pread_state);
+
+ DBG_DEBUG("[CEPH] %s\n", __func__);
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->bytes_read;
+}
+
+static ssize_t cephwrap_pwrite(struct vfs_handle_struct *handle, files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ ssize_t result;
+
+ DBG_DEBUG("[CEPH] pwrite(%p, %p, %p, %llu, %llu)\n", handle, fsp, data, llu(n), llu(offset));
+ result = ceph_write(handle->data, fsp_get_io_fd(fsp), data, n, offset);
+ DBG_DEBUG("[CEPH] pwrite(...) = %llu\n", llu(result));
+ WRAP_RETURN(result);
+}
+
+struct cephwrap_pwrite_state {
+ ssize_t bytes_written;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+/*
+ * Fake up an async ceph write by calling the synchronous API.
+ */
+static struct tevent_req *cephwrap_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct cephwrap_pwrite_state *state = NULL;
+ int ret = -1;
+
+ DBG_DEBUG("[CEPH] %s\n", __func__);
+ req = tevent_req_create(mem_ctx, &state, struct cephwrap_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ ret = ceph_write(handle->data, fsp_get_io_fd(fsp), data, n, offset);
+ if (ret < 0) {
+ /* ceph returns -errno on error. */
+ tevent_req_error(req, -ret);
+ return tevent_req_post(req, ev);
+ }
+
+ state->bytes_written = ret;
+ tevent_req_done(req);
+ /* Return and schedule the completion of the call. */
+ return tevent_req_post(req, ev);
+}
+
+static ssize_t cephwrap_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct cephwrap_pwrite_state *state =
+ tevent_req_data(req, struct cephwrap_pwrite_state);
+
+ DBG_DEBUG("[CEPH] %s\n", __func__);
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->bytes_written;
+}
+
+static off_t cephwrap_lseek(struct vfs_handle_struct *handle, files_struct *fsp, off_t offset, int whence)
+{
+ off_t result = 0;
+
+ DBG_DEBUG("[CEPH] cephwrap_lseek\n");
+ result = ceph_lseek(handle->data, fsp_get_io_fd(fsp), offset, whence);
+ WRAP_RETURN(result);
+}
+
+static ssize_t cephwrap_sendfile(struct vfs_handle_struct *handle, int tofd, files_struct *fromfsp, const DATA_BLOB *hdr,
+ off_t offset, size_t n)
+{
+ /*
+ * We cannot support sendfile because libcephfs is in user space.
+ */
+ DBG_DEBUG("[CEPH] cephwrap_sendfile\n");
+ errno = ENOTSUP;
+ return -1;
+}
+
+static ssize_t cephwrap_recvfile(struct vfs_handle_struct *handle,
+ int fromfd,
+ files_struct *tofsp,
+ off_t offset,
+ size_t n)
+{
+ /*
+ * We cannot support recvfile because libcephfs is in user space.
+ */
+ DBG_DEBUG("[CEPH] cephwrap_recvfile\n");
+ errno=ENOTSUP;
+ return -1;
+}
+
+static int cephwrap_renameat(struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+ int result = -1;
+
+ DBG_DEBUG("[CEPH] cephwrap_renameat\n");
+ if (smb_fname_src->stream_name || smb_fname_dst->stream_name) {
+ errno = ENOENT;
+ return result;
+ }
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ TALLOC_FREE(full_fname_src);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = ceph_rename(handle->data,
+ full_fname_src->base_name,
+ full_fname_dst->base_name);
+
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+
+ WRAP_RETURN(result);
+}
+
+/*
+ * Fake up an async ceph fsync by calling the synchronous API.
+ */
+
+static struct tevent_req *cephwrap_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *fsp)
+{
+ struct tevent_req *req = NULL;
+ struct vfs_aio_state *state = NULL;
+ int ret = -1;
+
+ DBG_DEBUG("[CEPH] cephwrap_fsync_send\n");
+
+ req = tevent_req_create(mem_ctx, &state, struct vfs_aio_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Make sync call. */
+ ret = ceph_fsync(handle->data, fsp_get_io_fd(fsp), false);
+
+ if (ret != 0) {
+ /* ceph_fsync returns -errno on error. */
+ tevent_req_error(req, -ret);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Mark it as done. */
+ tevent_req_done(req);
+ /* Return and schedule the completion of the call. */
+ return tevent_req_post(req, ev);
+}
+
+static int cephwrap_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_aio_state *state =
+ tevent_req_data(req, struct vfs_aio_state);
+
+ DBG_DEBUG("[CEPH] cephwrap_fsync_recv\n");
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = *state;
+ return 0;
+}
+
+#define SAMBA_STATX_ATTR_MASK (CEPH_STATX_BASIC_STATS|CEPH_STATX_BTIME)
+
+static void init_stat_ex_from_ceph_statx(struct stat_ex *dst, const struct ceph_statx *stx)
+{
+ DBG_DEBUG("[CEPH]\tstx = {dev = %llx, ino = %llu, mode = 0x%x, "
+ "nlink = %llu, uid = %d, gid = %d, rdev = %llx, size = %llu, "
+ "blksize = %llu, blocks = %llu, atime = %llu, mtime = %llu, "
+ "ctime = %llu, btime = %llu}\n",
+ llu(stx->stx_dev), llu(stx->stx_ino), stx->stx_mode,
+ llu(stx->stx_nlink), stx->stx_uid, stx->stx_gid,
+ llu(stx->stx_rdev), llu(stx->stx_size), llu(stx->stx_blksize),
+ llu(stx->stx_blocks), llu(stx->stx_atime.tv_sec),
+ llu(stx->stx_mtime.tv_sec), llu(stx->stx_ctime.tv_sec),
+ llu(stx->stx_btime.tv_sec));
+
+ if ((stx->stx_mask & SAMBA_STATX_ATTR_MASK) != SAMBA_STATX_ATTR_MASK) {
+ DBG_WARNING("%s: stx->stx_mask is incorrect (wanted %x, got %x)\n",
+ __func__, SAMBA_STATX_ATTR_MASK, stx->stx_mask);
+ }
+
+ dst->st_ex_dev = stx->stx_dev;
+ dst->st_ex_rdev = stx->stx_rdev;
+ dst->st_ex_ino = stx->stx_ino;
+ dst->st_ex_mode = stx->stx_mode;
+ dst->st_ex_uid = stx->stx_uid;
+ dst->st_ex_gid = stx->stx_gid;
+ dst->st_ex_size = stx->stx_size;
+ dst->st_ex_nlink = stx->stx_nlink;
+ dst->st_ex_atime = stx->stx_atime;
+ dst->st_ex_btime = stx->stx_btime;
+ dst->st_ex_ctime = stx->stx_ctime;
+ dst->st_ex_mtime = stx->stx_mtime;
+ dst->st_ex_blksize = stx->stx_blksize;
+ dst->st_ex_blocks = stx->stx_blocks;
+}
+
+static int cephwrap_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int result = -1;
+ struct ceph_statx stx = { 0 };
+
+ DBG_DEBUG("[CEPH] stat(%p, %s)\n", handle, smb_fname_str_dbg(smb_fname));
+
+ if (smb_fname->stream_name) {
+ errno = ENOENT;
+ return result;
+ }
+
+ result = ceph_statx(handle->data, smb_fname->base_name, &stx,
+ SAMBA_STATX_ATTR_MASK, 0);
+ DBG_DEBUG("[CEPH] statx(...) = %d\n", result);
+ if (result < 0) {
+ WRAP_RETURN(result);
+ }
+
+ init_stat_ex_from_ceph_statx(&smb_fname->st, &stx);
+ DBG_DEBUG("[CEPH] mode = 0x%x\n", smb_fname->st.st_ex_mode);
+ return result;
+}
+
+static int cephwrap_fstat(struct vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ int result = -1;
+ struct ceph_statx stx = { 0 };
+ int fd = fsp_get_pathref_fd(fsp);
+
+ DBG_DEBUG("[CEPH] fstat(%p, %d)\n", handle, fd);
+ result = ceph_fstatx(handle->data, fd, &stx,
+ SAMBA_STATX_ATTR_MASK, 0);
+ DBG_DEBUG("[CEPH] fstat(...) = %d\n", result);
+ if (result < 0) {
+ WRAP_RETURN(result);
+ }
+
+ init_stat_ex_from_ceph_statx(sbuf, &stx);
+ DBG_DEBUG("[CEPH] mode = 0x%x\n", sbuf->st_ex_mode);
+ return result;
+}
+
+static int cephwrap_fstatat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ int result = -1;
+ struct ceph_statx stx = { 0 };
+#ifdef HAVE_CEPH_STATXAT
+ int dirfd = fsp_get_pathref_fd(dirfsp);
+
+ DBG_DEBUG("[CEPH] fstatat(%p, %d, %s)\n",
+ handle, dirfd, smb_fname->base_name);
+ result = ceph_statxat(handle->data, dirfd, smb_fname->base_name,
+ &stx, SAMBA_STATX_ATTR_MASK, 0);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ DBG_DEBUG("[CEPH] fstatat(%p, %s)\n",
+ handle, smb_fname_str_dbg(full_fname));
+ result = ceph_statx(handle->data, full_fname->base_name,
+ &stx, SAMBA_STATX_ATTR_MASK, 0);
+
+ TALLOC_FREE(full_fname);
+#endif
+
+ DBG_DEBUG("[CEPH] fstatat(...) = %d\n", result);
+ if (result < 0) {
+ WRAP_RETURN(result);
+ }
+
+ init_stat_ex_from_ceph_statx(sbuf, &stx);
+ DBG_DEBUG("[CEPH] mode = 0x%x\n", sbuf->st_ex_mode);
+
+ return 0;
+}
+
+static int cephwrap_lstat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int result = -1;
+ struct ceph_statx stx = { 0 };
+
+ DBG_DEBUG("[CEPH] lstat(%p, %s)\n", handle, smb_fname_str_dbg(smb_fname));
+
+ if (smb_fname->stream_name) {
+ errno = ENOENT;
+ return result;
+ }
+
+ result = ceph_statx(handle->data, smb_fname->base_name, &stx,
+ SAMBA_STATX_ATTR_MASK, AT_SYMLINK_NOFOLLOW);
+ DBG_DEBUG("[CEPH] lstat(...) = %d\n", result);
+ if (result < 0) {
+ WRAP_RETURN(result);
+ }
+
+ init_stat_ex_from_ceph_statx(&smb_fname->st, &stx);
+ return result;
+}
+
+static int cephwrap_fntimes(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ struct ceph_statx stx = { 0 };
+ int result;
+ int mask = 0;
+
+ if (!is_omit_timespec(&ft->atime)) {
+ stx.stx_atime = ft->atime;
+ mask |= CEPH_SETATTR_ATIME;
+ }
+ if (!is_omit_timespec(&ft->mtime)) {
+ stx.stx_mtime = ft->mtime;
+ mask |= CEPH_SETATTR_MTIME;
+ }
+ if (!is_omit_timespec(&ft->create_time)) {
+ stx.stx_btime = ft->create_time;
+ mask |= CEPH_SETATTR_BTIME;
+ }
+
+ if (!mask) {
+ return 0;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to set xattrs.
+ */
+ result = ceph_fsetattrx(handle->data,
+ fsp_get_io_fd(fsp),
+ &stx,
+ mask);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ result = ceph_setattrx(handle->data,
+ fsp->fsp_name->base_name,
+ &stx,
+ mask,
+ 0);
+ }
+
+ DBG_DEBUG("[CEPH] ntimes(%p, %s, {%ld, %ld, %ld, %ld}) = %d\n",
+ handle, fsp_str_dbg(fsp), ft->mtime.tv_sec, ft->atime.tv_sec,
+ ft->ctime.tv_sec, ft->create_time.tv_sec, result);
+
+ return result;
+}
+
+static int cephwrap_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int result = -1;
+#ifdef HAVE_CEPH_UNLINKAT
+ int dirfd = fsp_get_pathref_fd(dirfsp);
+
+ DBG_DEBUG("[CEPH] unlinkat(%p, %d, %s)\n",
+ handle,
+ dirfd,
+ smb_fname_str_dbg(smb_fname));
+
+ if (smb_fname->stream_name) {
+ errno = ENOENT;
+ return result;
+ }
+
+ result = ceph_unlinkat(handle->data,
+ dirfd,
+ smb_fname->base_name,
+ flags);
+ DBG_DEBUG("[CEPH] unlinkat(...) = %d\n", result);
+ WRAP_RETURN(result);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ DBG_DEBUG("[CEPH] unlink(%p, %s)\n",
+ handle,
+ smb_fname_str_dbg(smb_fname));
+
+ if (smb_fname->stream_name) {
+ errno = ENOENT;
+ return result;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ if (flags & AT_REMOVEDIR) {
+ result = ceph_rmdir(handle->data, full_fname->base_name);
+ } else {
+ result = ceph_unlink(handle->data, full_fname->base_name);
+ }
+ TALLOC_FREE(full_fname);
+ DBG_DEBUG("[CEPH] unlink(...) = %d\n", result);
+ WRAP_RETURN(result);
+#endif
+}
+
+static int cephwrap_fchmod(struct vfs_handle_struct *handle, files_struct *fsp, mode_t mode)
+{
+ int result;
+
+ DBG_DEBUG("[CEPH] fchmod(%p, %p, %d)\n", handle, fsp, mode);
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to change permissions.
+ */
+ result = ceph_fchmod(handle->data, fsp_get_io_fd(fsp), mode);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ result = ceph_chmod(handle->data,
+ fsp->fsp_name->base_name,
+ mode);
+ }
+ DBG_DEBUG("[CEPH] fchmod(...) = %d\n", result);
+ WRAP_RETURN(result);
+}
+
+static int cephwrap_fchown(struct vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid)
+{
+ int result;
+
+ DBG_DEBUG("[CEPH] fchown(%p, %p, %d, %d)\n", handle, fsp, uid, gid);
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to change ownership.
+ */
+ result = ceph_fchown(handle->data,
+ fsp_get_io_fd(fsp),
+ uid,
+ gid);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ result = ceph_chown(handle->data,
+ fsp->fsp_name->base_name,
+ uid,
+ gid);
+ }
+
+ DBG_DEBUG("[CEPH] fchown(...) = %d\n", result);
+ WRAP_RETURN(result);
+}
+
+static int cephwrap_lchown(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int result;
+ DBG_DEBUG("[CEPH] lchown(%p, %s, %d, %d)\n", handle, smb_fname->base_name, uid, gid);
+ result = ceph_lchown(handle->data, smb_fname->base_name, uid, gid);
+ DBG_DEBUG("[CEPH] lchown(...) = %d\n", result);
+ WRAP_RETURN(result);
+}
+
+static int cephwrap_chdir(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int result = -1;
+ DBG_DEBUG("[CEPH] chdir(%p, %s)\n", handle, smb_fname->base_name);
+ result = ceph_chdir(handle->data, smb_fname->base_name);
+ DBG_DEBUG("[CEPH] chdir(...) = %d\n", result);
+ WRAP_RETURN(result);
+}
+
+static struct smb_filename *cephwrap_getwd(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ const char *cwd = ceph_getcwd(handle->data);
+ DBG_DEBUG("[CEPH] getwd(%p) = %s\n", handle, cwd);
+ return synthetic_smb_fname(ctx,
+ cwd,
+ NULL,
+ NULL,
+ 0,
+ 0);
+}
+
+static int strict_allocate_ftruncate(struct vfs_handle_struct *handle, files_struct *fsp, off_t len)
+{
+ off_t space_to_write;
+ int result;
+ NTSTATUS status;
+ SMB_STRUCT_STAT *pst;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ pst = &fsp->fsp_name->st;
+
+#ifdef S_ISFIFO
+ if (S_ISFIFO(pst->st_ex_mode))
+ return 0;
+#endif
+
+ if (pst->st_ex_size == len)
+ return 0;
+
+ /* Shrink - just ftruncate. */
+ if (pst->st_ex_size > len) {
+ result = ceph_ftruncate(handle->data, fsp_get_io_fd(fsp), len);
+ WRAP_RETURN(result);
+ }
+
+ space_to_write = len - pst->st_ex_size;
+ result = ceph_fallocate(handle->data, fsp_get_io_fd(fsp), 0, pst->st_ex_size,
+ space_to_write);
+ WRAP_RETURN(result);
+}
+
+static int cephwrap_ftruncate(struct vfs_handle_struct *handle, files_struct *fsp, off_t len)
+{
+ int result = -1;
+
+ DBG_DEBUG("[CEPH] ftruncate(%p, %p, %llu\n", handle, fsp, llu(len));
+
+ if (lp_strict_allocate(SNUM(fsp->conn))) {
+ return strict_allocate_ftruncate(handle, fsp, len);
+ }
+
+ result = ceph_ftruncate(handle->data, fsp_get_io_fd(fsp), len);
+ WRAP_RETURN(result);
+}
+
+static int cephwrap_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ int result;
+
+ DBG_DEBUG("[CEPH] fallocate(%p, %p, %u, %llu, %llu\n",
+ handle, fsp, mode, llu(offset), llu(len));
+ /* unsupported mode flags are rejected by libcephfs */
+ result = ceph_fallocate(handle->data, fsp_get_io_fd(fsp), mode, offset, len);
+ DBG_DEBUG("[CEPH] fallocate(...) = %d\n", result);
+ WRAP_RETURN(result);
+}
+
+static bool cephwrap_lock(struct vfs_handle_struct *handle, files_struct *fsp, int op, off_t offset, off_t count, int type)
+{
+ DBG_DEBUG("[CEPH] lock\n");
+ return true;
+}
+
+static int cephwrap_filesystem_sharemode(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ DBG_ERR("[CEPH] filesystem sharemodes unsupported! Consider setting "
+ "\"kernel share modes = no\"\n");
+
+ errno = ENOSYS;
+ return -1;
+}
+
+static int cephwrap_fcntl(vfs_handle_struct *handle,
+ files_struct *fsp, int cmd, va_list cmd_arg)
+{
+ /*
+ * SMB_VFS_FCNTL() is currently only called by vfs_set_blocking() to
+ * clear O_NONBLOCK, etc for LOCK_MAND and FIFOs. Ignore it.
+ */
+ if (cmd == F_GETFL) {
+ return 0;
+ } else if (cmd == F_SETFL) {
+ va_list dup_cmd_arg;
+ int opt;
+
+ va_copy(dup_cmd_arg, cmd_arg);
+ opt = va_arg(dup_cmd_arg, int);
+ va_end(dup_cmd_arg);
+ if (opt == 0) {
+ return 0;
+ }
+ DBG_ERR("unexpected fcntl SETFL(%d)\n", opt);
+ goto err_out;
+ }
+ DBG_ERR("unexpected fcntl: %d\n", cmd);
+err_out:
+ errno = EINVAL;
+ return -1;
+}
+
+static bool cephwrap_getlock(struct vfs_handle_struct *handle, files_struct *fsp, off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid)
+{
+ DBG_DEBUG("[CEPH] getlock returning false and errno=0\n");
+
+ errno = 0;
+ return false;
+}
+
+/*
+ * We cannot let this fall through to the default, because the file might only
+ * be accessible from libcephfs (which is a user-space client) but the fd might
+ * be for some file the kernel knows about.
+ */
+static int cephwrap_linux_setlease(struct vfs_handle_struct *handle, files_struct *fsp,
+ int leasetype)
+{
+ int result = -1;
+
+ DBG_DEBUG("[CEPH] linux_setlease\n");
+ errno = ENOSYS;
+ return result;
+}
+
+static int cephwrap_symlinkat(struct vfs_handle_struct *handle,
+ const struct smb_filename *link_target,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ int result = -1;
+#ifdef HAVE_CEPH_SYMLINKAT
+ int dirfd = fsp_get_pathref_fd(dirfsp);
+
+ DBG_DEBUG("[CEPH] symlinkat(%p, %s, %d, %s)\n",
+ handle,
+ link_target->base_name,
+ dirfd,
+ new_smb_fname->base_name);
+
+ result = ceph_symlinkat(handle->data,
+ link_target->base_name,
+ dirfd,
+ new_smb_fname->base_name);
+ DBG_DEBUG("[CEPH] symlinkat(...) = %d\n", result);
+ WRAP_RETURN(result);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ new_smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ DBG_DEBUG("[CEPH] symlink(%p, %s, %s)\n", handle,
+ link_target->base_name,
+ full_fname->base_name);
+
+ result = ceph_symlink(handle->data,
+ link_target->base_name,
+ full_fname->base_name);
+ TALLOC_FREE(full_fname);
+ DBG_DEBUG("[CEPH] symlink(...) = %d\n", result);
+ WRAP_RETURN(result);
+#endif
+}
+
+static int cephwrap_readlinkat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ int result = -1;
+#ifdef HAVE_CEPH_READLINKAT
+ int dirfd = fsp_get_pathref_fd(dirfsp);
+
+ DBG_DEBUG("[CEPH] readlinkat(%p, %d, %s, %p, %llu)\n",
+ handle,
+ dirfd,
+ smb_fname->base_name,
+ buf,
+ llu(bufsiz));
+
+ result = ceph_readlinkat(handle->data,
+ dirfd,
+ smb_fname->base_name,
+ buf,
+ bufsiz);
+
+ DBG_DEBUG("[CEPH] readlinkat(...) = %d\n", result);
+ WRAP_RETURN(result);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ DBG_DEBUG("[CEPH] readlink(%p, %s, %p, %llu)\n", handle,
+ full_fname->base_name, buf, llu(bufsiz));
+
+ result = ceph_readlink(handle->data, full_fname->base_name, buf, bufsiz);
+ TALLOC_FREE(full_fname);
+ DBG_DEBUG("[CEPH] readlink(...) = %d\n", result);
+ WRAP_RETURN(result);
+#endif
+}
+
+static int cephwrap_linkat(struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname_old = NULL;
+ struct smb_filename *full_fname_new = NULL;
+ int result = -1;
+
+ full_fname_old = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ old_smb_fname);
+ if (full_fname_old == NULL) {
+ return -1;
+ }
+ full_fname_new = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (full_fname_new == NULL) {
+ TALLOC_FREE(full_fname_old);
+ return -1;
+ }
+
+ DBG_DEBUG("[CEPH] link(%p, %s, %s)\n", handle,
+ full_fname_old->base_name,
+ full_fname_new->base_name);
+
+ result = ceph_link(handle->data,
+ full_fname_old->base_name,
+ full_fname_new->base_name);
+ DBG_DEBUG("[CEPH] link(...) = %d\n", result);
+ TALLOC_FREE(full_fname_old);
+ TALLOC_FREE(full_fname_new);
+ WRAP_RETURN(result);
+}
+
+static int cephwrap_mknodat(struct vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ struct smb_filename *full_fname = NULL;
+ int result = -1;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ DBG_DEBUG("[CEPH] mknodat(%p, %s)\n", handle, full_fname->base_name);
+ result = ceph_mknod(handle->data, full_fname->base_name, mode, dev);
+ DBG_DEBUG("[CEPH] mknodat(...) = %d\n", result);
+
+ TALLOC_FREE(full_fname);
+
+ WRAP_RETURN(result);
+}
+
+/*
+ * This is a simple version of real-path ... a better version is needed to
+ * ask libcephfs about symbolic links.
+ */
+static struct smb_filename *cephwrap_realpath(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ char *result = NULL;
+ const char *path = smb_fname->base_name;
+ size_t len = strlen(path);
+ struct smb_filename *result_fname = NULL;
+ int r = -1;
+
+ if (len && (path[0] == '/')) {
+ r = asprintf(&result, "%s", path);
+ } else if ((len >= 2) && (path[0] == '.') && (path[1] == '/')) {
+ if (len == 2) {
+ r = asprintf(&result, "%s",
+ handle->conn->cwd_fsp->fsp_name->base_name);
+ } else {
+ r = asprintf(&result, "%s/%s",
+ handle->conn->cwd_fsp->fsp_name->base_name, &path[2]);
+ }
+ } else {
+ r = asprintf(&result, "%s/%s",
+ handle->conn->cwd_fsp->fsp_name->base_name, path);
+ }
+
+ if (r < 0) {
+ return NULL;
+ }
+
+ DBG_DEBUG("[CEPH] realpath(%p, %s) = %s\n", handle, path, result);
+ result_fname = synthetic_smb_fname(ctx,
+ result,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ SAFE_FREE(result);
+ return result_fname;
+}
+
+
+static int cephwrap_fchflags(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static NTSTATUS cephwrap_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ /*
+ * Don't fall back to get_real_filename so callers can differentiate
+ * between a full directory scan and an actual case-insensitive stat.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static const char *cephwrap_connectpath(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ return handle->conn->connectpath;
+}
+
+/****************************************************************
+ Extended attribute operations.
+*****************************************************************/
+
+static ssize_t cephwrap_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ void *value,
+ size_t size)
+{
+ int ret;
+ DBG_DEBUG("[CEPH] fgetxattr(%p, %p, %s, %p, %llu)\n",
+ handle,
+ fsp,
+ name,
+ value,
+ llu(size));
+ if (!fsp->fsp_flags.is_pathref) {
+ ret = ceph_fgetxattr(handle->data,
+ fsp_get_io_fd(fsp),
+ name,
+ value,
+ size);
+ } else {
+ ret = ceph_getxattr(handle->data,
+ fsp->fsp_name->base_name,
+ name,
+ value,
+ size);
+ }
+ DBG_DEBUG("[CEPH] fgetxattr(...) = %d\n", ret);
+ if (ret < 0) {
+ WRAP_RETURN(ret);
+ }
+ return (ssize_t)ret;
+}
+
+static ssize_t cephwrap_flistxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, char *list, size_t size)
+{
+ int ret;
+ DBG_DEBUG("[CEPH] flistxattr(%p, %p, %p, %llu)\n",
+ handle, fsp, list, llu(size));
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to list xattrs.
+ */
+ ret = ceph_flistxattr(handle->data,
+ fsp_get_io_fd(fsp),
+ list,
+ size);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ ret = ceph_listxattr(handle->data,
+ fsp->fsp_name->base_name,
+ list,
+ size);
+ }
+ DBG_DEBUG("[CEPH] flistxattr(...) = %d\n", ret);
+ if (ret < 0) {
+ WRAP_RETURN(ret);
+ }
+ return (ssize_t)ret;
+}
+
+static int cephwrap_fremovexattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name)
+{
+ int ret;
+ DBG_DEBUG("[CEPH] fremovexattr(%p, %p, %s)\n", handle, fsp, name);
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to remove xattrs.
+ */
+ ret = ceph_fremovexattr(handle->data, fsp_get_io_fd(fsp), name);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ ret = ceph_removexattr(handle->data,
+ fsp->fsp_name->base_name,
+ name);
+ }
+ DBG_DEBUG("[CEPH] fremovexattr(...) = %d\n", ret);
+ WRAP_RETURN(ret);
+}
+
+static int cephwrap_fsetxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, const void *value, size_t size, int flags)
+{
+ int ret;
+ DBG_DEBUG("[CEPH] fsetxattr(%p, %p, %s, %p, %llu, %d)\n", handle, fsp, name, value, llu(size), flags);
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to set xattrs.
+ */
+ ret = ceph_fsetxattr(handle->data,
+ fsp_get_io_fd(fsp),
+ name,
+ value,
+ size,
+ flags);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ ret = ceph_setxattr(handle->data,
+ fsp->fsp_name->base_name,
+ name,
+ value,
+ size,
+ flags);
+ }
+ DBG_DEBUG("[CEPH] fsetxattr(...) = %d\n", ret);
+ WRAP_RETURN(ret);
+}
+
+static bool cephwrap_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp)
+{
+
+ /*
+ * We do not support AIO yet.
+ */
+
+ DBG_DEBUG("[CEPH] cephwrap_aio_force(%p, %p) = false (errno = ENOTSUP)\n", handle, fsp);
+ errno = ENOTSUP;
+ return false;
+}
+
+static NTSTATUS cephwrap_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ int ret;
+ char *msdfs_link = NULL;
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ goto out;
+ }
+
+ /* Form the msdfs_link contents */
+ msdfs_link = msdfs_link_string(frame,
+ reflist,
+ referral_count);
+ if (msdfs_link == NULL) {
+ goto out;
+ }
+
+ ret = ceph_symlink(handle->data,
+ msdfs_link,
+ full_fname->base_name);
+ if (ret == 0) {
+ status = NT_STATUS_OK;
+ } else {
+ status = map_nt_error_from_unix(-ret);
+ }
+
+ out:
+
+ DBG_DEBUG("[CEPH] create_dfs_pathat(%s) = %s\n",
+ full_fname != NULL ? full_fname->base_name : "",
+ nt_errstr(status));
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * Read and return the contents of a DFS redirect given a
+ * pathname. A caller can pass in NULL for ppreflist and
+ * preferral_count but still determine if this was a
+ * DFS redirect point by getting NT_STATUS_OK back
+ * without incurring the overhead of reading and parsing
+ * the referral contents.
+ */
+
+static NTSTATUS cephwrap_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ size_t bufsize;
+ char *link_target = NULL;
+ int referral_len;
+ bool ok;
+#if defined(HAVE_BROKEN_READLINK)
+ char link_target_buf[PATH_MAX];
+#else
+ char link_target_buf[7];
+#endif
+ struct ceph_statx stx = { 0 };
+ struct smb_filename *full_fname = NULL;
+ int ret;
+
+ if (is_named_stream(smb_fname)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto err;
+ }
+
+ if (ppreflist == NULL && preferral_count == NULL) {
+ /*
+ * We're only checking if this is a DFS
+ * redirect. We don't need to return data.
+ */
+ bufsize = sizeof(link_target_buf);
+ link_target = link_target_buf;
+ } else {
+ bufsize = PATH_MAX;
+ link_target = talloc_array(mem_ctx, char, bufsize);
+ if (!link_target) {
+ goto err;
+ }
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ ret = ceph_statx(handle->data,
+ full_fname->base_name,
+ &stx,
+ SAMBA_STATX_ATTR_MASK,
+ AT_SYMLINK_NOFOLLOW);
+ if (ret < 0) {
+ status = map_nt_error_from_unix(-ret);
+ goto err;
+ }
+
+ referral_len = ceph_readlink(handle->data,
+ full_fname->base_name,
+ link_target,
+ bufsize - 1);
+ if (referral_len < 0) {
+ /* ceph errors are -errno. */
+ if (-referral_len == EINVAL) {
+ DBG_INFO("%s is not a link.\n",
+ full_fname->base_name);
+ status = NT_STATUS_OBJECT_TYPE_MISMATCH;
+ } else {
+ status = map_nt_error_from_unix(-referral_len);
+ DBG_ERR("Error reading "
+ "msdfs link %s: %s\n",
+ full_fname->base_name,
+ strerror(errno));
+ }
+ goto err;
+ }
+ link_target[referral_len] = '\0';
+
+ DBG_INFO("%s -> %s\n",
+ full_fname->base_name,
+ link_target);
+
+ if (!strnequal(link_target, "msdfs:", 6)) {
+ status = NT_STATUS_OBJECT_TYPE_MISMATCH;
+ goto err;
+ }
+
+ if (ppreflist == NULL && preferral_count == NULL) {
+ /* Early return for checking if this is a DFS link. */
+ TALLOC_FREE(full_fname);
+ init_stat_ex_from_ceph_statx(&smb_fname->st, &stx);
+ return NT_STATUS_OK;
+ }
+
+ ok = parse_msdfs_symlink(mem_ctx,
+ lp_msdfs_shuffle_referrals(SNUM(handle->conn)),
+ link_target,
+ ppreflist,
+ preferral_count);
+
+ if (ok) {
+ init_stat_ex_from_ceph_statx(&smb_fname->st, &stx);
+ status = NT_STATUS_OK;
+ } else {
+ status = NT_STATUS_NO_MEMORY;
+ }
+
+ err:
+
+ if (link_target != link_target_buf) {
+ TALLOC_FREE(link_target);
+ }
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static struct vfs_fn_pointers ceph_fns = {
+ /* Disk operations */
+
+ .connect_fn = cephwrap_connect,
+ .disconnect_fn = cephwrap_disconnect,
+ .disk_free_fn = cephwrap_disk_free,
+ .get_quota_fn = cephwrap_get_quota,
+ .set_quota_fn = cephwrap_set_quota,
+ .statvfs_fn = cephwrap_statvfs,
+ .fs_capabilities_fn = cephwrap_fs_capabilities,
+
+ /* Directory operations */
+
+ .fdopendir_fn = cephwrap_fdopendir,
+ .readdir_fn = cephwrap_readdir,
+ .rewind_dir_fn = cephwrap_rewinddir,
+ .mkdirat_fn = cephwrap_mkdirat,
+ .closedir_fn = cephwrap_closedir,
+
+ /* File operations */
+
+ .create_dfs_pathat_fn = cephwrap_create_dfs_pathat,
+ .read_dfs_pathat_fn = cephwrap_read_dfs_pathat,
+ .openat_fn = cephwrap_openat,
+ .close_fn = cephwrap_close,
+ .pread_fn = cephwrap_pread,
+ .pread_send_fn = cephwrap_pread_send,
+ .pread_recv_fn = cephwrap_pread_recv,
+ .pwrite_fn = cephwrap_pwrite,
+ .pwrite_send_fn = cephwrap_pwrite_send,
+ .pwrite_recv_fn = cephwrap_pwrite_recv,
+ .lseek_fn = cephwrap_lseek,
+ .sendfile_fn = cephwrap_sendfile,
+ .recvfile_fn = cephwrap_recvfile,
+ .renameat_fn = cephwrap_renameat,
+ .fsync_send_fn = cephwrap_fsync_send,
+ .fsync_recv_fn = cephwrap_fsync_recv,
+ .stat_fn = cephwrap_stat,
+ .fstat_fn = cephwrap_fstat,
+ .lstat_fn = cephwrap_lstat,
+ .fstatat_fn = cephwrap_fstatat,
+ .unlinkat_fn = cephwrap_unlinkat,
+ .fchmod_fn = cephwrap_fchmod,
+ .fchown_fn = cephwrap_fchown,
+ .lchown_fn = cephwrap_lchown,
+ .chdir_fn = cephwrap_chdir,
+ .getwd_fn = cephwrap_getwd,
+ .fntimes_fn = cephwrap_fntimes,
+ .ftruncate_fn = cephwrap_ftruncate,
+ .fallocate_fn = cephwrap_fallocate,
+ .lock_fn = cephwrap_lock,
+ .filesystem_sharemode_fn = cephwrap_filesystem_sharemode,
+ .fcntl_fn = cephwrap_fcntl,
+ .linux_setlease_fn = cephwrap_linux_setlease,
+ .getlock_fn = cephwrap_getlock,
+ .symlinkat_fn = cephwrap_symlinkat,
+ .readlinkat_fn = cephwrap_readlinkat,
+ .linkat_fn = cephwrap_linkat,
+ .mknodat_fn = cephwrap_mknodat,
+ .realpath_fn = cephwrap_realpath,
+ .fchflags_fn = cephwrap_fchflags,
+ .get_real_filename_at_fn = cephwrap_get_real_filename_at,
+ .connectpath_fn = cephwrap_connectpath,
+
+ /* EA operations. */
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fgetxattr_fn = cephwrap_fgetxattr,
+ .flistxattr_fn = cephwrap_flistxattr,
+ .fremovexattr_fn = cephwrap_fremovexattr,
+ .fsetxattr_fn = cephwrap_fsetxattr,
+
+ /* Posix ACL Operations */
+ .sys_acl_get_fd_fn = posixacl_xattr_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = posixacl_xattr_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = posixacl_xattr_acl_delete_def_fd,
+
+ /* aio operations */
+ .aio_force_fn = cephwrap_aio_force,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_ceph_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "ceph", &ceph_fns);
+}
diff --git a/source3/modules/vfs_ceph_snapshots.c b/source3/modules/vfs_ceph_snapshots.c
new file mode 100644
index 0000000..98b8f5f
--- /dev/null
+++ b/source3/modules/vfs_ceph_snapshots.c
@@ -0,0 +1,1478 @@
+/*
+ * Module for accessing CephFS snapshots as Previous Versions. This module is
+ * separate to vfs_ceph, so that it can also be used atop a CephFS kernel backed
+ * share with vfs_default.
+ *
+ * Copyright (C) David Disseldorp 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dirent.h>
+#include <libgen.h>
+#include "includes.h"
+#include "include/ntioctl.h"
+#include "include/smb.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/smb_strtox.h"
+#include "source3/smbd/dir.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+/*
+ * CephFS has a magic snapshots subdirectory in all parts of the directory tree.
+ * This module automatically makes all snapshots in this subdir visible to SMB
+ * clients (if permitted by corresponding access control).
+ */
+#define CEPH_SNAP_SUBDIR_DEFAULT ".snap"
+/*
+ * The ceph.snap.btime (virtual) extended attribute carries the snapshot
+ * creation time in $secs.$nsecs format. It was added as part of
+ * https://tracker.ceph.com/issues/38838. Running Samba atop old Ceph versions
+ * which don't provide this xattr will not be able to enumerate or access
+ * snapshots using this module. As an alternative, vfs_shadow_copy2 could be
+ * used instead, alongside special shadow:format snapshot directory names.
+ */
+#define CEPH_SNAP_BTIME_XATTR "ceph.snap.btime"
+
+static int ceph_snap_get_btime_fsp(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ time_t *_snap_secs)
+{
+ int ret;
+ char snap_btime[33];
+ char *s = NULL;
+ char *endptr = NULL;
+ struct timespec snap_timespec;
+ int err;
+
+ ret = SMB_VFS_NEXT_FGETXATTR(handle,
+ fsp,
+ CEPH_SNAP_BTIME_XATTR,
+ snap_btime,
+ sizeof(snap_btime));
+ if (ret < 0) {
+ DBG_ERR("failed to get %s xattr: %s\n",
+ CEPH_SNAP_BTIME_XATTR, strerror(errno));
+ return -errno;
+ }
+
+ if (ret == 0 || ret >= sizeof(snap_btime) - 1) {
+ return -EINVAL;
+ }
+
+ /* ensure zero termination */
+ snap_btime[ret] = '\0';
+
+ /* format is sec.nsec */
+ s = strchr(snap_btime, '.');
+ if (s == NULL) {
+ DBG_ERR("invalid %s xattr value: %s\n",
+ CEPH_SNAP_BTIME_XATTR, snap_btime);
+ return -EINVAL;
+ }
+
+ /* First component is seconds, extract it */
+ *s = '\0';
+ snap_timespec.tv_sec = smb_strtoull(snap_btime,
+ &endptr,
+ 10,
+ &err,
+ SMB_STR_FULL_STR_CONV);
+ if (err != 0) {
+ return -err;
+ }
+
+ /* second component is nsecs */
+ s++;
+ snap_timespec.tv_nsec = smb_strtoul(s,
+ &endptr,
+ 10,
+ &err,
+ SMB_STR_FULL_STR_CONV);
+ if (err != 0) {
+ return -err;
+ }
+
+ /*
+ * >> 30 is a rough divide by ~10**9. No need to be exact, as @GMT
+ * tokens only offer 1-second resolution (while twrp is nsec).
+ */
+ *_snap_secs = snap_timespec.tv_sec + (snap_timespec.tv_nsec >> 30);
+
+ return 0;
+}
+
+/*
+ * XXX Ceph snapshots can be created with sub-second granularity, which means
+ * that multiple snapshots may be mapped to the same @GMT- label.
+ *
+ * @this_label is a pre-zeroed buffer to be filled with a @GMT label
+ * @return 0 if label successfully filled or -errno on error.
+ */
+static int ceph_snap_fill_label(struct vfs_handle_struct *handle,
+ TALLOC_CTX *tmp_ctx,
+ struct files_struct *dirfsp,
+ const char *subdir,
+ SHADOW_COPY_LABEL this_label)
+{
+ const char *parent_snapsdir = dirfsp->fsp_name->base_name;
+ struct smb_filename *smb_fname;
+ struct smb_filename *atname = NULL;
+ time_t snap_secs;
+ struct tm gmt_snap_time;
+ struct tm *tm_ret;
+ size_t str_sz;
+ char snap_path[PATH_MAX + 1];
+ int ret;
+ NTSTATUS status;
+
+ /*
+ * CephFS snapshot creation times are available via a special
+ * xattr - snapshot b/m/ctimes all match the snap source.
+ */
+ ret = snprintf(snap_path, sizeof(snap_path), "%s/%s",
+ parent_snapsdir, subdir);
+ if (ret >= sizeof(snap_path)) {
+ return -EINVAL;
+ }
+
+ smb_fname = synthetic_smb_fname(tmp_ctx,
+ snap_path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ return -ENOMEM;
+ }
+
+ ret = vfs_stat(handle->conn, smb_fname);
+ if (ret < 0) {
+ ret = -errno;
+ TALLOC_FREE(smb_fname);
+ return ret;
+ }
+
+ atname = synthetic_smb_fname(tmp_ctx,
+ subdir,
+ NULL,
+ &smb_fname->st,
+ 0,
+ 0);
+ if (atname == NULL) {
+ TALLOC_FREE(smb_fname);
+ return -ENOMEM;
+ }
+
+ status = openat_pathref_fsp(dirfsp, atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(atname);
+ return -map_errno_from_nt_status(status);
+ }
+
+ ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
+ if (ret < 0) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(atname);
+ return ret;
+ }
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(atname);
+
+ tm_ret = gmtime_r(&snap_secs, &gmt_snap_time);
+ if (tm_ret == NULL) {
+ return -EINVAL;
+ }
+ str_sz = strftime(this_label, sizeof(SHADOW_COPY_LABEL),
+ "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time);
+ if (str_sz == 0) {
+ DBG_ERR("failed to convert tm to @GMT token\n");
+ return -EINVAL;
+ }
+
+ DBG_DEBUG("mapped snapshot at %s to enum snaps label %s\n",
+ snap_path, this_label);
+
+ return 0;
+}
+
+static int ceph_snap_enum_snapdir(struct vfs_handle_struct *handle,
+ struct smb_filename *snaps_dname,
+ bool labels,
+ struct shadow_copy_data *sc_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smb_Dir *dir_hnd = NULL;
+ struct files_struct *dirfsp = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ NTSTATUS status;
+ int ret;
+ uint32_t slots;
+
+ DBG_DEBUG("enumerating shadow copy dir at %s\n",
+ snaps_dname->base_name);
+
+ /*
+ * CephFS stat(dir).size *normally* returns the number of child entries
+ * for a given dir, but it unfortunately that's not the case for the one
+ * place we need it (dir=.snap), so we need to dynamically determine it
+ * via readdir.
+ */
+
+ status = OpenDir(frame,
+ handle->conn,
+ snaps_dname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = -map_errno_from_nt_status(status);
+ goto err_out;
+ }
+
+ /* Check we have SEC_DIR_LIST access on this fsp. */
+ dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+ status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
+ dirfsp,
+ false,
+ SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("user does not have list permission "
+ "on snapdir %s\n",
+ fsp_str_dbg(dirfsp));
+ ret = -map_errno_from_nt_status(status);
+ goto err_out;
+ }
+
+ slots = 0;
+ sc_data->num_volumes = 0;
+ sc_data->labels = NULL;
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ sc_data->num_volumes++;
+ if (!labels) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ if (sc_data->num_volumes > slots) {
+ uint32_t new_slot_count = slots + 10;
+ SMB_ASSERT(new_slot_count > slots);
+ sc_data->labels = talloc_realloc(sc_data,
+ sc_data->labels,
+ SHADOW_COPY_LABEL,
+ new_slot_count);
+ if (sc_data->labels == NULL) {
+ TALLOC_FREE(talloced);
+ ret = -ENOMEM;
+ goto err_closedir;
+ }
+ memset(sc_data->labels[slots], 0,
+ sizeof(SHADOW_COPY_LABEL) * 10);
+
+ DBG_DEBUG("%d->%d slots for enum_snaps response\n",
+ slots, new_slot_count);
+ slots = new_slot_count;
+ }
+ DBG_DEBUG("filling shadow copy label for %s/%s\n",
+ snaps_dname->base_name, dname);
+ ret = ceph_snap_fill_label(handle,
+ snaps_dname,
+ dirfsp,
+ dname,
+ sc_data->labels[sc_data->num_volumes - 1]);
+ if (ret < 0) {
+ TALLOC_FREE(talloced);
+ goto err_closedir;
+ }
+ TALLOC_FREE(talloced);
+ }
+
+ DBG_DEBUG("%s shadow copy enumeration found %d labels \n",
+ snaps_dname->base_name, sc_data->num_volumes);
+
+ TALLOC_FREE(frame);
+ return 0;
+
+err_closedir:
+ TALLOC_FREE(frame);
+err_out:
+ TALLOC_FREE(sc_data->labels);
+ return ret;
+}
+
+/*
+ * Prior reading: The Meaning of Path Names
+ * https://wiki.samba.org/index.php/Writing_a_Samba_VFS_Module
+ *
+ * translate paths so that we can use the parent dir for .snap access:
+ * myfile -> parent= trimmed=myfile
+ * /a -> parent=/ trimmed=a
+ * dir/sub/file -> parent=dir/sub trimmed=file
+ * /dir/sub -> parent=/dir/ trimmed=sub
+ */
+static int ceph_snap_get_parent_path(const char *connectpath,
+ const char *path,
+ char *_parent_buf,
+ size_t buflen,
+ const char **_trimmed)
+{
+ const char *p;
+ size_t len;
+ int ret;
+
+ if (!strcmp(path, "/")) {
+ DBG_ERR("can't go past root for %s .snap dir\n", path);
+ return -EINVAL;
+ }
+
+ p = strrchr_m(path, '/'); /* Find final '/', if any */
+ if (p == NULL) {
+ DBG_DEBUG("parent .snap dir for %s is cwd\n", path);
+ ret = strlcpy(_parent_buf, "", buflen);
+ if (ret >= buflen) {
+ return -EINVAL;
+ }
+ if (_trimmed != NULL) {
+ *_trimmed = path;
+ }
+ return 0;
+ }
+
+ SMB_ASSERT(p >= path);
+ len = p - path;
+
+ ret = snprintf(_parent_buf, buflen, "%.*s", (int)len, path);
+ if (ret >= buflen) {
+ return -EINVAL;
+ }
+
+ /* for absolute paths, check that we're not going outside the share */
+ if ((len > 0) && (_parent_buf[0] == '/')) {
+ bool connectpath_match = false;
+ size_t clen = strlen(connectpath);
+ DBG_DEBUG("checking absolute path %s lies within share at %s\n",
+ _parent_buf, connectpath);
+ /* need to check for separator, to avoid /x/abcd vs /x/ab */
+ connectpath_match = (strncmp(connectpath,
+ _parent_buf,
+ clen) == 0);
+ if (!connectpath_match
+ || ((_parent_buf[clen] != '/') && (_parent_buf[clen] != '\0'))) {
+ DBG_ERR("%s parent path is outside of share at %s\n",
+ _parent_buf, connectpath);
+ return -EINVAL;
+ }
+ }
+
+ if (_trimmed != NULL) {
+ /*
+ * point to path component which was trimmed from _parent_buf
+ * excluding path separator.
+ */
+ *_trimmed = p + 1;
+ }
+
+ DBG_DEBUG("generated parent .snap path for %s as %s (trimmed \"%s\")\n",
+ path, _parent_buf, p + 1);
+
+ return 0;
+}
+
+static int ceph_snap_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *sc_data,
+ bool labels)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+ const char *parent_dir = NULL;
+ char tmp[PATH_MAX + 1];
+ char snaps_path[PATH_MAX + 1];
+ struct smb_filename *snaps_dname = NULL;
+ const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
+ "ceph", "snapdir",
+ CEPH_SNAP_SUBDIR_DEFAULT);
+
+ DBG_DEBUG("getting shadow copy data for %s\n",
+ fsp->fsp_name->base_name);
+
+ tmp_ctx = talloc_new(fsp);
+ if (tmp_ctx == NULL) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ if (sc_data == NULL) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ parent_dir = fsp->fsp_name->base_name;
+ } else {
+ ret = ceph_snap_get_parent_path(handle->conn->connectpath,
+ fsp->fsp_name->base_name,
+ tmp,
+ sizeof(tmp),
+ NULL); /* trimmed */
+ if (ret < 0) {
+ goto err_out;
+ }
+ parent_dir = tmp;
+ }
+
+ if (strlen(parent_dir) == 0) {
+ ret = strlcpy(snaps_path, snapdir, sizeof(snaps_path));
+ } else {
+ ret = snprintf(snaps_path, sizeof(snaps_path), "%s/%s",
+ parent_dir, snapdir);
+ }
+ if (ret >= sizeof(snaps_path)) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ snaps_dname = synthetic_smb_fname(tmp_ctx,
+ snaps_path,
+ NULL,
+ NULL,
+ 0,
+ fsp->fsp_name->flags);
+ if (snaps_dname == NULL) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ ret = ceph_snap_enum_snapdir(handle, snaps_dname, labels, sc_data);
+ if (ret < 0) {
+ goto err_out;
+ }
+
+ talloc_free(tmp_ctx);
+ return 0;
+
+err_out:
+ talloc_free(tmp_ctx);
+ errno = -ret;
+ return -1;
+}
+
+static int ceph_snap_gmt_strip_snapshot(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ time_t *_timestamp,
+ char *_stripped_buf,
+ size_t buflen)
+{
+ size_t len;
+
+ if (smb_fname->twrp == 0) {
+ goto no_snapshot;
+ }
+
+ if (_stripped_buf != NULL) {
+ len = strlcpy(_stripped_buf, smb_fname->base_name, buflen);
+ if (len >= buflen) {
+ return -ENAMETOOLONG;
+ }
+ }
+
+ *_timestamp = nt_time_to_unix(smb_fname->twrp);
+ return 0;
+no_snapshot:
+ *_timestamp = 0;
+ return 0;
+}
+
+static int ceph_snap_gmt_convert_dir(struct vfs_handle_struct *handle,
+ const char *name,
+ time_t timestamp,
+ char *_converted_buf,
+ size_t buflen)
+{
+ int ret;
+ NTSTATUS status;
+ struct smb_Dir *dir_hnd = NULL;
+ struct files_struct *dirfsp = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ struct smb_filename *snaps_dname = NULL;
+ const char *snapdir = lp_parm_const_string(SNUM(handle->conn),
+ "ceph", "snapdir",
+ CEPH_SNAP_SUBDIR_DEFAULT);
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+
+ if (tmp_ctx == NULL) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ /*
+ * Temporally use the caller's return buffer for this.
+ */
+ if (strlen(name) == 0) {
+ ret = strlcpy(_converted_buf, snapdir, buflen);
+ } else {
+ ret = snprintf(_converted_buf, buflen, "%s/%s", name, snapdir);
+ }
+ if (ret >= buflen) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ snaps_dname = synthetic_smb_fname(tmp_ctx,
+ _converted_buf,
+ NULL,
+ NULL,
+ 0,
+ 0); /* XXX check? */
+ if (snaps_dname == NULL) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ /* stat first to trigger error fallback in ceph_snap_gmt_convert() */
+ ret = SMB_VFS_NEXT_STAT(handle, snaps_dname);
+ if (ret < 0) {
+ ret = -errno;
+ goto err_out;
+ }
+
+ DBG_DEBUG("enumerating shadow copy dir at %s\n",
+ snaps_dname->base_name);
+
+ status = OpenDir(tmp_ctx,
+ handle->conn,
+ snaps_dname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = -map_errno_from_nt_status(status);
+ goto err_out;
+ }
+
+ /* Check we have SEC_DIR_LIST access on this fsp. */
+ dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+ status = smbd_check_access_rights_fsp(dirfsp->conn->cwd_fsp,
+ dirfsp,
+ false,
+ SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("user does not have list permission "
+ "on snapdir %s\n",
+ fsp_str_dbg(dirfsp));
+ ret = -map_errno_from_nt_status(status);
+ goto err_out;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *atname = NULL;
+ time_t snap_secs = 0;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ ret = snprintf(_converted_buf, buflen, "%s/%s",
+ snaps_dname->base_name, dname);
+ if (ret >= buflen) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ smb_fname = synthetic_smb_fname(tmp_ctx,
+ _converted_buf,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ ret = vfs_stat(handle->conn, smb_fname);
+ if (ret < 0) {
+ ret = -errno;
+ TALLOC_FREE(smb_fname);
+ goto err_out;
+ }
+
+ atname = synthetic_smb_fname(tmp_ctx,
+ dname,
+ NULL,
+ &smb_fname->st,
+ 0,
+ 0);
+ if (atname == NULL) {
+ TALLOC_FREE(smb_fname);
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ status = openat_pathref_fsp(dirfsp, atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(atname);
+ ret = -map_errno_from_nt_status(status);
+ goto err_out;
+ }
+
+ ret = ceph_snap_get_btime_fsp(handle, atname->fsp, &snap_secs);
+ if (ret < 0) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(atname);
+ goto err_out;
+ }
+
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(atname);
+
+ /*
+ * check gmt_snap_time matches @timestamp
+ */
+ if (timestamp == snap_secs) {
+ break;
+ }
+ DBG_DEBUG("[connectpath %s] %s@%lld no match for snap %s@%lld\n",
+ handle->conn->connectpath, name, (long long)timestamp,
+ dname, (long long)snap_secs);
+ TALLOC_FREE(talloced);
+ }
+
+ if (dname == NULL) {
+ DBG_INFO("[connectpath %s] failed to find %s @ time %lld\n",
+ handle->conn->connectpath, name, (long long)timestamp);
+ ret = -ENOENT;
+ goto err_out;
+ }
+
+ /* found, _converted_buf already contains path of interest */
+ DBG_DEBUG("[connectpath %s] converted %s @ time %lld to %s\n",
+ handle->conn->connectpath, name, (long long)timestamp,
+ _converted_buf);
+
+ TALLOC_FREE(talloced);
+ talloc_free(tmp_ctx);
+ return 0;
+
+err_out:
+ TALLOC_FREE(talloced);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int ceph_snap_gmt_convert(struct vfs_handle_struct *handle,
+ const char *name,
+ time_t timestamp,
+ char *_converted_buf,
+ size_t buflen)
+{
+ int ret;
+ char parent[PATH_MAX + 1];
+ const char *trimmed = NULL;
+ /*
+ * CephFS Snapshots for a given dir are nested under the ./.snap subdir
+ * *or* under ../.snap/dir (and subsequent parent dirs).
+ * Child dirs inherit snapshots created in parent dirs if the child
+ * exists at the time of snapshot creation.
+ *
+ * At this point we don't know whether @name refers to a file or dir, so
+ * first assume it's a dir (with a corresponding .snaps subdir)
+ */
+ ret = ceph_snap_gmt_convert_dir(handle,
+ name,
+ timestamp,
+ _converted_buf,
+ buflen);
+ if (ret >= 0) {
+ /* all done: .snap subdir exists - @name is a dir */
+ DBG_DEBUG("%s is a dir, accessing snaps via .snap\n", name);
+ return ret;
+ }
+
+ /* @name/.snap access failed, attempt snapshot access via parent */
+ DBG_DEBUG("%s/.snap access failed, attempting parent access\n",
+ name);
+
+ ret = ceph_snap_get_parent_path(handle->conn->connectpath,
+ name,
+ parent,
+ sizeof(parent),
+ &trimmed);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = ceph_snap_gmt_convert_dir(handle,
+ parent,
+ timestamp,
+ _converted_buf,
+ buflen);
+ if (ret < 0) {
+ return ret;
+ }
+
+ /*
+ * found snapshot via parent. Append the child path component
+ * that was trimmed... +1 for path separator + 1 for null termination.
+ */
+ if (strlen(_converted_buf) + 1 + strlen(trimmed) + 1 > buflen) {
+ return -EINVAL;
+ }
+ strlcat(_converted_buf, "/", buflen);
+ strlcat(_converted_buf, trimmed, buflen);
+
+ return 0;
+}
+
+static int ceph_snap_gmt_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int ret;
+ time_t timestamp_src, timestamp_dst;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ smb_fname_src,
+ &timestamp_src, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ smb_fname_dst,
+ &timestamp_dst, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp_src != 0) {
+ errno = EXDEV;
+ return -1;
+ }
+ if (timestamp_dst != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+}
+
+/* block links from writeable shares to snapshots for now, like other modules */
+static int ceph_snap_gmt_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ int ret;
+ time_t timestamp_old = 0;
+ time_t timestamp_new = 0;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ link_contents,
+ &timestamp_old,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ new_smb_fname,
+ &timestamp_new,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if ((timestamp_old != 0) || (timestamp_new != 0)) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_SYMLINKAT(handle,
+ link_contents,
+ dirfsp,
+ new_smb_fname);
+}
+
+static int ceph_snap_gmt_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ int ret;
+ time_t timestamp_old = 0;
+ time_t timestamp_new = 0;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ old_smb_fname,
+ &timestamp_old,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ new_smb_fname,
+ &timestamp_new,
+ NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if ((timestamp_old != 0) || (timestamp_new != 0)) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+}
+
+static int ceph_snap_gmt_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ time_t timestamp = 0;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ char *tmp;
+ int ret;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ smb_fname,
+ &timestamp, stripped, sizeof(stripped));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_STAT(handle, smb_fname);
+ }
+
+ ret = ceph_snap_gmt_convert(handle, stripped,
+ timestamp, conv, sizeof(conv));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ tmp = smb_fname->base_name;
+ smb_fname->base_name = conv;
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ smb_fname->base_name = tmp;
+ return ret;
+}
+
+static int ceph_snap_gmt_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ time_t timestamp = 0;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ char *tmp;
+ int ret;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ smb_fname,
+ &timestamp, stripped, sizeof(stripped));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+
+ ret = ceph_snap_gmt_convert(handle, stripped,
+ timestamp, conv, sizeof(conv));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ tmp = smb_fname->base_name;
+ smb_fname->base_name = conv;
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ smb_fname->base_name = tmp;
+ return ret;
+}
+
+static int ceph_snap_gmt_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ time_t timestamp = 0;
+ struct smb_filename *smb_fname = NULL;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ int ret;
+ int saved_errno = 0;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ smb_fname_in,
+ &timestamp,
+ stripped,
+ sizeof(stripped));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname_in,
+ fsp,
+ how);
+ }
+
+ ret = ceph_snap_gmt_convert(handle,
+ stripped,
+ timestamp,
+ conv,
+ sizeof(conv));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
+ if (smb_fname == NULL) {
+ return -1;
+ }
+ smb_fname->base_name = conv;
+
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int ceph_snap_gmt_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *csmb_fname,
+ int flags)
+{
+ time_t timestamp = 0;
+ int ret;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ csmb_fname,
+ flags);
+}
+
+static int ceph_snap_gmt_fchmod(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ mode_t mode)
+{
+ const struct smb_filename *csmb_fname = NULL;
+ time_t timestamp = 0;
+ int ret;
+
+ csmb_fname = fsp->fsp_name;
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+}
+
+static int ceph_snap_gmt_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *csmb_fname)
+{
+ time_t timestamp = 0;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ int ret;
+ struct smb_filename *new_fname;
+ int saved_errno;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, stripped, sizeof(stripped));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_CHDIR(handle, csmb_fname);
+ }
+
+ ret = ceph_snap_gmt_convert_dir(handle, stripped,
+ timestamp, conv, sizeof(conv));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
+ if (new_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ new_fname->base_name = conv;
+
+ ret = SMB_VFS_NEXT_CHDIR(handle, new_fname);
+ saved_errno = errno;
+ TALLOC_FREE(new_fname);
+ errno = saved_errno;
+ return ret;
+}
+
+static int ceph_snap_gmt_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ time_t timestamp = 0;
+ int ret;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ fsp->fsp_name,
+ &timestamp,
+ NULL,
+ 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+}
+
+static int ceph_snap_gmt_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *csmb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ time_t timestamp = 0;
+ char conv[PATH_MAX + 1];
+ int ret;
+ struct smb_filename *full_fname = NULL;
+ int saved_errno;
+
+ /*
+ * Now this function only looks at csmb_fname->twrp
+ * we don't need to copy out the path. Just use
+ * csmb_fname->base_name directly.
+ */
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_READLINKAT(handle,
+ dirfsp,
+ csmb_fname,
+ buf,
+ bufsiz);
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ csmb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ /* Find the snapshot path from the full pathname. */
+ ret = ceph_snap_gmt_convert(handle,
+ full_fname->base_name,
+ timestamp,
+ conv,
+ sizeof(conv));
+ if (ret < 0) {
+ TALLOC_FREE(full_fname);
+ errno = -ret;
+ return -1;
+ }
+ full_fname->base_name = conv;
+
+ ret = SMB_VFS_NEXT_READLINKAT(handle,
+ handle->conn->cwd_fsp,
+ full_fname,
+ buf,
+ bufsiz);
+ saved_errno = errno;
+ TALLOC_FREE(full_fname);
+ errno = saved_errno;
+ return ret;
+}
+
+static int ceph_snap_gmt_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *csmb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ time_t timestamp = 0;
+ int ret;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_MKNODAT(handle,
+ dirfsp,
+ csmb_fname,
+ mode,
+ dev);
+}
+
+static struct smb_filename *ceph_snap_gmt_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *csmb_fname)
+{
+ time_t timestamp = 0;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ struct smb_filename *result_fname;
+ int ret;
+ struct smb_filename *new_fname;
+ int saved_errno;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, stripped, sizeof(stripped));
+ if (ret < 0) {
+ errno = -ret;
+ return NULL;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_REALPATH(handle, ctx, csmb_fname);
+ }
+ ret = ceph_snap_gmt_convert(handle, stripped,
+ timestamp, conv, sizeof(conv));
+ if (ret < 0) {
+ errno = -ret;
+ return NULL;
+ }
+ new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
+ if (new_fname == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+ new_fname->base_name = conv;
+
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, new_fname);
+ saved_errno = errno;
+ TALLOC_FREE(new_fname);
+ errno = saved_errno;
+ return result_fname;
+}
+
+static int ceph_snap_gmt_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *csmb_fname,
+ mode_t mode)
+{
+ time_t timestamp = 0;
+ int ret;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ csmb_fname,
+ mode);
+}
+
+static int ceph_snap_gmt_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ time_t timestamp = 0;
+ int ret;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ fsp->fsp_name,
+ &timestamp, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
+}
+
+static int ceph_snap_gmt_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *aname, const void *value,
+ size_t size, int flags)
+{
+ const struct smb_filename *csmb_fname = NULL;
+ time_t timestamp = 0;
+ int ret;
+
+ csmb_fname = fsp->fsp_name;
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, NULL, 0);
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
+ aname, value, size, flags);
+}
+
+static NTSTATUS ceph_snap_gmt_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ time_t timestamp = 0;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ struct smb_filename *conv_fname = NULL;
+ int ret;
+ NTSTATUS status;
+
+ ret = ceph_snap_gmt_strip_snapshot(
+ handle,
+ dirfsp->fsp_name,
+ &timestamp,
+ stripped,
+ sizeof(stripped));
+ if (ret < 0) {
+ return map_nt_error_from_unix(-ret);
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, dirfsp, name, mem_ctx, found_name);
+ }
+ ret = ceph_snap_gmt_convert_dir(handle, stripped,
+ timestamp, conv, sizeof(conv));
+ if (ret < 0) {
+ return map_nt_error_from_unix(-ret);
+ }
+
+ status = synthetic_pathref(
+ talloc_tos(),
+ dirfsp->conn->cwd_fsp,
+ conv,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &conv_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, conv_fname->fsp, name, mem_ctx, found_name);
+ TALLOC_FREE(conv_fname);
+ return status;
+}
+
+static uint64_t ceph_snap_gmt_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *csmb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ time_t timestamp = 0;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ int ret;
+ struct smb_filename *new_fname;
+ int saved_errno;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, stripped, sizeof(stripped));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_DISK_FREE(handle, csmb_fname,
+ bsize, dfree, dsize);
+ }
+ ret = ceph_snap_gmt_convert(handle, stripped,
+ timestamp, conv, sizeof(conv));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
+ if (new_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ new_fname->base_name = conv;
+
+ ret = SMB_VFS_NEXT_DISK_FREE(handle, new_fname,
+ bsize, dfree, dsize);
+ saved_errno = errno;
+ TALLOC_FREE(new_fname);
+ errno = saved_errno;
+ return ret;
+}
+
+static int ceph_snap_gmt_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *csmb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq)
+{
+ time_t timestamp = 0;
+ char stripped[PATH_MAX + 1];
+ char conv[PATH_MAX + 1];
+ int ret;
+ struct smb_filename *new_fname;
+ int saved_errno;
+
+ ret = ceph_snap_gmt_strip_snapshot(handle,
+ csmb_fname,
+ &timestamp, stripped, sizeof(stripped));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_GET_QUOTA(handle, csmb_fname, qtype, id, dq);
+ }
+ ret = ceph_snap_gmt_convert(handle, stripped,
+ timestamp, conv, sizeof(conv));
+ if (ret < 0) {
+ errno = -ret;
+ return -1;
+ }
+ new_fname = cp_smb_filename(talloc_tos(), csmb_fname);
+ if (new_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ new_fname->base_name = conv;
+
+ ret = SMB_VFS_NEXT_GET_QUOTA(handle, new_fname, qtype, id, dq);
+ saved_errno = errno;
+ TALLOC_FREE(new_fname);
+ errno = saved_errno;
+ return ret;
+}
+
+static struct vfs_fn_pointers ceph_snap_fns = {
+ .get_shadow_copy_data_fn = ceph_snap_get_shadow_copy_data,
+ .disk_free_fn = ceph_snap_gmt_disk_free,
+ .get_quota_fn = ceph_snap_gmt_get_quota,
+ .renameat_fn = ceph_snap_gmt_renameat,
+ .linkat_fn = ceph_snap_gmt_linkat,
+ .symlinkat_fn = ceph_snap_gmt_symlinkat,
+ .stat_fn = ceph_snap_gmt_stat,
+ .lstat_fn = ceph_snap_gmt_lstat,
+ .openat_fn = ceph_snap_gmt_openat,
+ .unlinkat_fn = ceph_snap_gmt_unlinkat,
+ .fchmod_fn = ceph_snap_gmt_fchmod,
+ .chdir_fn = ceph_snap_gmt_chdir,
+ .fntimes_fn = ceph_snap_gmt_fntimes,
+ .readlinkat_fn = ceph_snap_gmt_readlinkat,
+ .mknodat_fn = ceph_snap_gmt_mknodat,
+ .realpath_fn = ceph_snap_gmt_realpath,
+ .mkdirat_fn = ceph_snap_gmt_mkdirat,
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fsetxattr_fn = ceph_snap_gmt_fsetxattr,
+ .fchflags_fn = ceph_snap_gmt_fchflags,
+ .get_real_filename_at_fn = ceph_snap_gmt_get_real_filename_at,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_ceph_snapshots_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "ceph_snapshots", &ceph_snap_fns);
+}
diff --git a/source3/modules/vfs_commit.c b/source3/modules/vfs_commit.c
new file mode 100644
index 0000000..355edea
--- /dev/null
+++ b/source3/modules/vfs_commit.c
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) James Peach 2006, 2007
+ * Copyright (c) David Losada Carballo 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "lib/util/tevent_unix.h"
+
+/* Commit data module.
+ *
+ * The purpose of this module is to flush data to disk at regular intervals,
+ * just like the NFS commit operation. There's two rationales for this. First,
+ * it minimises the data loss in case of a power outage without incurring
+ * the poor performance of synchronous I/O. Second, a steady flush rate
+ * can produce better throughput than suddenly dumping massive amounts of
+ * writes onto a disk.
+ *
+ * Tunables:
+ *
+ * commit: dthresh Amount of dirty data that can accumulate
+ * before we commit (sync) it.
+ *
+ * commit: debug Debug level at which to emit messages.
+ *
+ * commit: eof mode String. Tunes how the module tries to guess when
+ * the client has written the last bytes of the file.
+ * Possible values (default = hinted):
+ *
+ * (*) = hinted Some clients (i.e. Windows Explorer) declare the
+ * size of the file before transferring it. With this
+ * option, we remember that hint, and commit after
+ * writing in that file position. If the client
+ * doesn't declare the size of file, committing on EOF
+ * is not triggered.
+ *
+ * = growth Commits after a write operation has made the file
+ * size grow. If the client declares a file size, it
+ * refrains to commit until the file has reached it.
+ * Useful for defeating writeback on NFS shares.
+ *
+ */
+
+#define MODULE "commit"
+
+static int module_debug;
+
+enum eof_mode
+{
+ EOF_NONE = 0x0000,
+ EOF_HINTED = 0x0001,
+ EOF_GROWTH = 0x0002
+};
+
+struct commit_info
+{
+ /* For chunk-based commits */
+ off_t dbytes; /* Dirty (uncommitted) bytes */
+ off_t dthresh; /* Dirty data threshold */
+ /* For commits on EOF */
+ enum eof_mode on_eof;
+ off_t eof; /* Expected file size */
+};
+
+static int commit_do(
+ struct commit_info * c,
+ int fd)
+{
+ int result;
+
+ DEBUG(module_debug,
+ ("%s: flushing %lu dirty bytes\n",
+ MODULE, (unsigned long)c->dbytes));
+
+#if defined(HAVE_FDATASYNC)
+ result = fdatasync(fd);
+#elif defined(HAVE_FSYNC)
+ result = fsync(fd);
+#else
+ DEBUG(0, ("%s: WARNING: no commit support on this platform\n",
+ MODULE));
+ result = 0
+#endif
+ if (result == 0) {
+ c->dbytes = 0; /* on success, no dirty bytes */
+ }
+ return result;
+}
+
+static int commit_all(
+ struct vfs_handle_struct * handle,
+ files_struct * fsp)
+{
+ struct commit_info *c;
+
+ if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp))) {
+ if (c->dbytes) {
+ DEBUG(module_debug,
+ ("%s: flushing %lu dirty bytes\n",
+ MODULE, (unsigned long)c->dbytes));
+
+ return commit_do(c, fsp_get_io_fd(fsp));
+ }
+ }
+ return 0;
+}
+
+static int commit(
+ struct vfs_handle_struct * handle,
+ files_struct * fsp,
+ off_t offset,
+ ssize_t last_write)
+{
+ struct commit_info *c;
+
+ if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(handle, fsp))
+ == NULL) {
+ return 0;
+ }
+
+ c->dbytes += last_write; /* dirty bytes always counted */
+
+ if (c->dthresh && (c->dbytes > c->dthresh)) {
+ return commit_do(c, fsp_get_io_fd(fsp));
+ }
+
+ /* Return if we are not in EOF mode or if we have temporarily opted
+ * out of it.
+ */
+ if (c->on_eof == EOF_NONE || c->eof < 0) {
+ return 0;
+ }
+
+ /* This write hit or went past our cache the file size. */
+ if ((offset + last_write) >= c->eof) {
+ if (commit_do(c, fsp_get_io_fd(fsp)) == -1) {
+ return -1;
+ }
+
+ /* Hinted mode only commits the first time we hit EOF. */
+ if (c->on_eof == EOF_HINTED) {
+ c->eof = -1;
+ } else if (c->on_eof == EOF_GROWTH) {
+ c->eof = offset + last_write;
+ }
+ }
+
+ return 0;
+}
+
+static int commit_connect(
+ struct vfs_handle_struct * handle,
+ const char * service,
+ const char * user)
+{
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ module_debug = lp_parm_int(SNUM(handle->conn), MODULE, "debug", 100);
+ return 0;
+}
+
+static int commit_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ off_t dthresh;
+ const char *eof_mode;
+ struct commit_info *c = NULL;
+ int fd;
+
+ /* Don't bother with read-only files. */
+ if ((how->flags & O_ACCMODE) == O_RDONLY) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ /* Read and check module configuration */
+ dthresh = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
+ MODULE, "dthresh", NULL));
+
+ eof_mode = lp_parm_const_string(SNUM(handle->conn),
+ MODULE, "eof mode", "none");
+
+ if (dthresh > 0 || !strequal(eof_mode, "none")) {
+ c = VFS_ADD_FSP_EXTENSION(
+ handle, fsp, struct commit_info, NULL);
+ /* Process main tunables */
+ if (c) {
+ c->dthresh = dthresh;
+ c->dbytes = 0;
+ c->on_eof = EOF_NONE;
+ c->eof = 0;
+ }
+ }
+ /* Process eof_mode tunable */
+ if (c) {
+ if (strequal(eof_mode, "hinted")) {
+ c->on_eof = EOF_HINTED;
+ } else if (strequal(eof_mode, "growth")) {
+ c->on_eof = EOF_GROWTH;
+ }
+ }
+
+ fd = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+ if (fd == -1) {
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ return fd;
+ }
+
+ /* EOF commit modes require us to know the initial file size. */
+ if (c && (c->on_eof != EOF_NONE)) {
+ SMB_STRUCT_STAT st;
+ /*
+ * Setting the fd of the FSP is a hack
+ * but also practiced elsewhere -
+ * needed for calling the VFS.
+ */
+ fsp_set_fd(fsp, fd);
+ if (SMB_VFS_FSTAT(fsp, &st) == -1) {
+ int saved_errno = errno;
+ SMB_VFS_CLOSE(fsp);
+ fsp_set_fd(fsp, -1);
+ errno = saved_errno;
+ return -1;
+ }
+ c->eof = st.st_ex_size;
+ }
+
+ return fd;
+}
+
+static ssize_t commit_pwrite(
+ vfs_handle_struct * handle,
+ files_struct * fsp,
+ const void * data,
+ size_t count,
+ off_t offset)
+{
+ ssize_t ret;
+
+ ret = SMB_VFS_NEXT_PWRITE(handle, fsp, data, count, offset);
+ if (ret > 0) {
+ if (commit(handle, fsp, offset, ret) == -1) {
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+struct commit_pwrite_state {
+ struct vfs_handle_struct *handle;
+ struct files_struct *fsp;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void commit_pwrite_written(struct tevent_req *subreq);
+
+static struct tevent_req *commit_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct commit_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct commit_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->handle = handle;
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, commit_pwrite_written, req);
+ return req;
+}
+
+static void commit_pwrite_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct commit_pwrite_state *state = tevent_req_data(
+ req, struct commit_pwrite_state);
+ int commit_ret;
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ if (state->ret <= 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Ok, this is a sync fake. We should make the sync async as well, but
+ * I'm too lazy for that right now -- vl
+ */
+ commit_ret = commit(state->handle,
+ state->fsp,
+ fh_get_pos(state->fsp->fh),
+ state->ret);
+
+ if (commit_ret == -1) {
+ state->ret = -1;
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t commit_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct commit_pwrite_state *state =
+ tevent_req_data(req, struct commit_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static int commit_close(
+ vfs_handle_struct * handle,
+ files_struct * fsp)
+{
+ /* Commit errors not checked, close() will find them again */
+ commit_all(handle, fsp);
+ return SMB_VFS_NEXT_CLOSE(handle, fsp);
+}
+
+static int commit_ftruncate(
+ vfs_handle_struct * handle,
+ files_struct * fsp,
+ off_t len)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len);
+ if (result == 0) {
+ struct commit_info *c;
+ if ((c = (struct commit_info *)VFS_FETCH_FSP_EXTENSION(
+ handle, fsp))) {
+ commit(handle, fsp, len, 0);
+ c->eof = len;
+ }
+ }
+
+ return result;
+}
+
+static struct vfs_fn_pointers vfs_commit_fns = {
+ .openat_fn = commit_openat,
+ .close_fn = commit_close,
+ .pwrite_fn = commit_pwrite,
+ .pwrite_send_fn = commit_pwrite_send,
+ .pwrite_recv_fn = commit_pwrite_recv,
+ .connect_fn = commit_connect,
+ .ftruncate_fn = commit_ftruncate
+};
+
+static_decl_vfs;
+NTSTATUS vfs_commit_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE,
+ &vfs_commit_fns);
+}
+
+
diff --git a/source3/modules/vfs_crossrename.c b/source3/modules/vfs_crossrename.c
new file mode 100644
index 0000000..042144b
--- /dev/null
+++ b/source3/modules/vfs_crossrename.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) Björn Jacke 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "transfer_file.h"
+#include "smbprofile.h"
+
+#define MODULE "crossrename"
+static off_t module_sizelimit;
+
+static int crossrename_connect(
+ struct vfs_handle_struct * handle,
+ const char * service,
+ const char * user)
+{
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ module_sizelimit = (off_t) lp_parm_int(SNUM(handle->conn),
+ MODULE, "sizelimit", 20);
+ /* convert from MiB to byte: */
+ module_sizelimit *= 1048576;
+
+ return 0;
+}
+
+/*********************************************************
+ For rename across filesystems initial Patch from Warren Birnbaum
+ <warrenb@hpcvscdp.cv.hp.com>
+**********************************************************/
+
+static NTSTATUS copy_reg(vfs_handle_struct *handle,
+ struct files_struct *srcfsp,
+ const struct smb_filename *source,
+ struct files_struct *dstfsp,
+ const struct smb_filename *dest)
+{
+ NTSTATUS status;
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+ int ret;
+
+ if (!VALID_STAT(source->st)) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+ if (!S_ISREG(source->st.st_ex_mode)) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+
+ if (source->st.st_ex_size > module_sizelimit) {
+ DBG_INFO("%s: size of %s larger than sizelimit (%lld > %lld), "
+ "rename prohibited\n",
+ MODULE,
+ source->base_name,
+ (long long)source->st.st_ex_size,
+ (long long)module_sizelimit);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ source);
+ if (full_fname_src == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ dest);
+ if (full_fname_dst == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dstfsp,
+ dest,
+ 0);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ /*
+ * copy_internals() takes attribute values from the NTrename call.
+ *
+ * From MS-CIFS:
+ *
+ * "If the attribute is 0x0000, then only normal files are renamed.
+ * If the system file or hidden attributes are specified, then the
+ * rename is inclusive of both special types."
+ */
+ status = copy_internals(talloc_tos(),
+ handle->conn,
+ NULL,
+ srcfsp, /* src_dirfsp */
+ full_fname_src,
+ dstfsp, /* dst_dirfsp */
+ full_fname_dst,
+ FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ srcfsp,
+ source,
+ 0);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ out:
+
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+ return status;
+}
+
+static int crossrename_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int result = -1;
+
+ START_PROFILE(syscall_renameat);
+
+ if (smb_fname_src->stream_name || smb_fname_dst->stream_name) {
+ errno = ENOENT;
+ goto out;
+ }
+
+ result = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+
+ if ((result == -1) && (errno == EXDEV)) {
+ /* Rename across filesystems needed. */
+ NTSTATUS status = copy_reg(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ result = -1;
+ }
+ }
+
+ out:
+ END_PROFILE(syscall_renameat);
+ return result;
+}
+
+
+static struct vfs_fn_pointers vfs_crossrename_fns = {
+ .connect_fn = crossrename_connect,
+ .renameat_fn = crossrename_renameat
+};
+
+static_decl_vfs;
+NTSTATUS vfs_crossrename_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE,
+ &vfs_crossrename_fns);
+}
+
diff --git a/source3/modules/vfs_default.c b/source3/modules/vfs_default.c
new file mode 100644
index 0000000..62ad506
--- /dev/null
+++ b/source3/modules/vfs_default.c
@@ -0,0 +1,4098 @@
+/*
+ Unix SMB/CIFS implementation.
+ Wrap disk only vfs functions to sidestep dodgy compilers.
+ Copyright (C) Tim Potter 1998
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/time.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "ntioctl.h"
+#include "smbprofile.h"
+#include "../libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "source3/include/msdfs.h"
+#include "librpc/gen_ndr/ndr_dfsblobs.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/sys_rw.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "offload_token.h"
+#include "util_reparse.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+/* Check for NULL pointer parameters in vfswrap_* functions */
+
+/* We don't want to have NULL function pointers lying around. Someone
+ is sure to try and execute them. These stubs are used to prevent
+ this possibility. */
+
+static int vfswrap_connect(vfs_handle_struct *handle, const char *service, const char *user)
+{
+ bool bval;
+
+ handle->conn->have_proc_fds = sys_have_proc_fds();
+#ifdef DISABLE_PROC_FDS
+ handle->conn->have_proc_fds = false;
+#endif
+
+ /*
+ * assume the kernel will support openat2(),
+ * it will be reset on the first ENOSYS.
+ *
+ * Note that libreplace will always provide openat2(),
+ * but return -1/errno = ENOSYS...
+ *
+ * The option is only there to test the fallback code.
+ */
+ bval = lp_parm_bool(SNUM(handle->conn),
+ "vfs_default",
+ "VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS",
+ true);
+ if (bval) {
+ handle->conn->open_how_resolve |=
+ VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS;
+ }
+#ifdef DISABLE_VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS
+ handle->conn->open_how_resolve &= ~VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS;
+#endif
+
+ return 0; /* Return >= 0 for success */
+}
+
+static void vfswrap_disconnect(vfs_handle_struct *handle)
+{
+}
+
+/* Disk operations */
+
+static uint64_t vfswrap_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ if (sys_fsusage(smb_fname->base_name, dfree, dsize) != 0) {
+ return (uint64_t)-1;
+ }
+
+ *bsize = 512;
+ return *dfree / 2;
+}
+
+static int vfswrap_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+#ifdef HAVE_SYS_QUOTAS
+ int result;
+
+ START_PROFILE(syscall_get_quota);
+ result = sys_get_quota(smb_fname->base_name, qtype, id, qt);
+ END_PROFILE(syscall_get_quota);
+ return result;
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static int vfswrap_set_quota(struct vfs_handle_struct *handle, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *qt)
+{
+#ifdef HAVE_SYS_QUOTAS
+ int result;
+
+ START_PROFILE(syscall_set_quota);
+ result = sys_set_quota(handle->conn->connectpath, qtype, id, qt);
+ END_PROFILE(syscall_set_quota);
+ return result;
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static int vfswrap_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels)
+{
+ errno = ENOSYS;
+ return -1; /* Not implemented. */
+}
+
+static int vfswrap_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ return sys_statvfs(smb_fname->base_name, statbuf);
+}
+
+static uint32_t vfswrap_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = handle->conn;
+ uint32_t caps = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+ struct smb_filename *smb_fname_cpath = NULL;
+ struct vfs_statvfs_struct statbuf;
+ int ret;
+
+ smb_fname_cpath = synthetic_smb_fname(talloc_tos(),
+ conn->connectpath,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname_cpath == NULL) {
+ return caps;
+ }
+
+ ZERO_STRUCT(statbuf);
+ ret = SMB_VFS_STATVFS(conn, smb_fname_cpath, &statbuf);
+ if (ret == 0) {
+ caps = statbuf.FsCapabilities;
+ }
+
+ *p_ts_res = TIMESTAMP_SET_SECONDS;
+
+ /* Work out what timestamp resolution we can
+ * use when setting a timestamp. */
+
+ ret = SMB_VFS_STAT(conn, smb_fname_cpath);
+ if (ret == -1) {
+ TALLOC_FREE(smb_fname_cpath);
+ return caps;
+ }
+
+ if (smb_fname_cpath->st.st_ex_mtime.tv_nsec ||
+ smb_fname_cpath->st.st_ex_atime.tv_nsec ||
+ smb_fname_cpath->st.st_ex_ctime.tv_nsec) {
+ /* If any of the normal UNIX directory timestamps
+ * have a non-zero tv_nsec component assume
+ * we might be able to set sub-second timestamps.
+ * See what filetime set primitives we have.
+ */
+#if defined(HAVE_UTIMENSAT)
+ *p_ts_res = TIMESTAMP_SET_NT_OR_BETTER;
+#elif defined(HAVE_UTIMES)
+ /* utimes allows msec timestamps to be set. */
+ *p_ts_res = TIMESTAMP_SET_MSEC;
+#elif defined(HAVE_UTIME)
+ /* utime only allows sec timestamps to be set. */
+ *p_ts_res = TIMESTAMP_SET_SECONDS;
+#endif
+
+ DBG_DEBUG("vfswrap_fs_capabilities: timestamp "
+ "resolution of %s "
+ "available on share %s, directory %s\n",
+ *p_ts_res == TIMESTAMP_SET_MSEC ? "msec" : "sec",
+ lp_servicename(talloc_tos(), lp_sub, conn->params->service),
+ conn->connectpath );
+ }
+ TALLOC_FREE(smb_fname_cpath);
+ return caps;
+}
+
+static NTSTATUS vfswrap_get_dfs_referrals(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r)
+{
+ struct junction_map *junction = NULL;
+ size_t consumedcnt = 0;
+ bool self_referral = false;
+ char *pathnamep = NULL;
+ char *local_dfs_path = NULL;
+ NTSTATUS status;
+ size_t i;
+ uint16_t max_referral_level = r->in.req.max_referral_level;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_IN_DEBUG(dfs_GetDFSReferral, r);
+ }
+
+ /* get the junction entry */
+ if (r->in.req.servername == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /*
+ * Trim pathname sent by client so it begins with only one backslash.
+ * Two backslashes confuse some dfs clients
+ */
+
+ local_dfs_path = talloc_strdup(r, r->in.req.servername);
+ if (local_dfs_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pathnamep = local_dfs_path;
+ while (IS_DIRECTORY_SEP(pathnamep[0]) &&
+ IS_DIRECTORY_SEP(pathnamep[1])) {
+ pathnamep++;
+ }
+
+ junction = talloc_zero(r, struct junction_map);
+ if (junction == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* The following call can change cwd. */
+ status = get_referred_path(r,
+ handle->conn->session_info,
+ pathnamep,
+ handle->conn->sconn->remote_address,
+ handle->conn->sconn->local_address,
+ junction, &consumedcnt, &self_referral);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct smb_filename connectpath_fname = {
+ .base_name = handle->conn->connectpath
+ };
+ vfs_ChDir(handle->conn, &connectpath_fname);
+ return status;
+ }
+ {
+ struct smb_filename connectpath_fname = {
+ .base_name = handle->conn->connectpath
+ };
+ vfs_ChDir(handle->conn, &connectpath_fname);
+ }
+
+ if (!self_referral) {
+ pathnamep[consumedcnt] = '\0';
+
+ if (DEBUGLVL(DBGLVL_INFO)) {
+ dbgtext("Path %s to alternate path(s):",
+ pathnamep);
+ for (i=0; i < junction->referral_count; i++) {
+ dbgtext(" %s",
+ junction->referral_list[i].alternate_path);
+ }
+ dbgtext(".\n");
+ }
+ }
+
+ if (r->in.req.max_referral_level <= 2) {
+ max_referral_level = 2;
+ }
+ if (r->in.req.max_referral_level >= 3) {
+ max_referral_level = 3;
+ }
+
+ r->out.resp = talloc_zero(r, struct dfs_referral_resp);
+ if (r->out.resp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ r->out.resp->path_consumed = strlen_m(pathnamep) * 2;
+ r->out.resp->nb_referrals = junction->referral_count;
+
+ r->out.resp->header_flags = DFS_HEADER_FLAG_STORAGE_SVR;
+ if (self_referral) {
+ r->out.resp->header_flags |= DFS_HEADER_FLAG_REFERAL_SVR;
+ }
+
+ r->out.resp->referral_entries = talloc_zero_array(r,
+ struct dfs_referral_type,
+ r->out.resp->nb_referrals);
+ if (r->out.resp->referral_entries == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (max_referral_level) {
+ case 2:
+ for(i=0; i < junction->referral_count; i++) {
+ struct referral *ref = &junction->referral_list[i];
+ TALLOC_CTX *mem_ctx = r->out.resp->referral_entries;
+ struct dfs_referral_type *t =
+ &r->out.resp->referral_entries[i];
+ struct dfs_referral_v2 *v2 = &t->referral.v2;
+
+ t->version = 2;
+ v2->size = VERSION2_REFERRAL_SIZE;
+ if (self_referral) {
+ v2->server_type = DFS_SERVER_ROOT;
+ } else {
+ v2->server_type = DFS_SERVER_NON_ROOT;
+ }
+ v2->entry_flags = 0;
+ v2->proximity = ref->proximity;
+ v2->ttl = ref->ttl;
+ v2->DFS_path = talloc_strdup(mem_ctx, pathnamep);
+ if (v2->DFS_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ v2->DFS_alt_path = talloc_strdup(mem_ctx, pathnamep);
+ if (v2->DFS_alt_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ v2->netw_address = talloc_strdup(mem_ctx,
+ ref->alternate_path);
+ if (v2->netw_address == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ break;
+ case 3:
+ for(i=0; i < junction->referral_count; i++) {
+ struct referral *ref = &junction->referral_list[i];
+ TALLOC_CTX *mem_ctx = r->out.resp->referral_entries;
+ struct dfs_referral_type *t =
+ &r->out.resp->referral_entries[i];
+ struct dfs_referral_v3 *v3 = &t->referral.v3;
+ struct dfs_normal_referral *r1 = &v3->referrals.r1;
+
+ t->version = 3;
+ v3->size = VERSION3_REFERRAL_SIZE;
+ if (self_referral) {
+ v3->server_type = DFS_SERVER_ROOT;
+ } else {
+ v3->server_type = DFS_SERVER_NON_ROOT;
+ }
+ v3->entry_flags = 0;
+ v3->ttl = ref->ttl;
+ r1->DFS_path = talloc_strdup(mem_ctx, pathnamep);
+ if (r1->DFS_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ r1->DFS_alt_path = talloc_strdup(mem_ctx, pathnamep);
+ if (r1->DFS_alt_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ r1->netw_address = talloc_strdup(mem_ctx,
+ ref->alternate_path);
+ if (r1->netw_address == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ break;
+ default:
+ DBG_ERR("Invalid dfs referral version: %d\n",
+ max_referral_level);
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_OUT_DEBUG(dfs_GetDFSReferral, r);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS vfswrap_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ int ret;
+ char *msdfs_link = NULL;
+
+ /* Form the msdfs_link contents */
+ msdfs_link = msdfs_link_string(frame,
+ reflist,
+ referral_count);
+ if (msdfs_link == NULL) {
+ goto out;
+ }
+
+ ret = symlinkat(msdfs_link,
+ fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name);
+ if (ret == 0) {
+ status = NT_STATUS_OK;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ out:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * Read and return the contents of a DFS redirect given a
+ * pathname. A caller can pass in NULL for ppreflist and
+ * preferral_count but still determine if this was a
+ * DFS redirect point by getting NT_STATUS_OK back
+ * without incurring the overhead of reading and parsing
+ * the referral contents.
+ */
+
+static NTSTATUS vfswrap_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ size_t bufsize;
+ char *link_target = NULL;
+ int referral_len;
+ bool ok;
+#if defined(HAVE_BROKEN_READLINK)
+ char link_target_buf[PATH_MAX];
+#else
+ char link_target_buf[7];
+#endif
+ int ret;
+
+ if (is_named_stream(smb_fname)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto err;
+ }
+
+ if (ppreflist == NULL && preferral_count == NULL) {
+ /*
+ * We're only checking if this is a DFS
+ * redirect. We don't need to return data.
+ */
+ bufsize = sizeof(link_target_buf);
+ link_target = link_target_buf;
+ } else {
+ bufsize = PATH_MAX;
+ link_target = talloc_array(mem_ctx, char, bufsize);
+ if (!link_target) {
+ goto err;
+ }
+ }
+
+ referral_len = readlinkat(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ link_target,
+ bufsize - 1);
+ if (referral_len == -1) {
+ if (errno == EINVAL) {
+ /*
+ * If the path isn't a link, readlinkat
+ * returns EINVAL. Allow the caller to
+ * detect this.
+ */
+ DBG_INFO("%s is not a link.\n", smb_fname->base_name);
+ status = NT_STATUS_OBJECT_TYPE_MISMATCH;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ if (errno == ENOENT) {
+ DBG_NOTICE("Error reading "
+ "msdfs link %s: %s\n",
+ smb_fname->base_name,
+ strerror(errno));
+ } else {
+ DBG_ERR("Error reading "
+ "msdfs link %s: %s\n",
+ smb_fname->base_name,
+ strerror(errno));
+ }
+ }
+ goto err;
+ }
+ link_target[referral_len] = '\0';
+
+ DBG_INFO("%s -> %s\n",
+ smb_fname->base_name,
+ link_target);
+
+ if (!strnequal(link_target, "msdfs:", 6)) {
+ status = NT_STATUS_OBJECT_TYPE_MISMATCH;
+ goto err;
+ }
+
+ ret = sys_fstatat(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ &smb_fname->st,
+ AT_SYMLINK_NOFOLLOW,
+ lp_fake_directory_create_times(SNUM(handle->conn)));
+ if (ret < 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err;
+ }
+
+ if (ppreflist == NULL && preferral_count == NULL) {
+ /* Early return for checking if this is a DFS link. */
+ return NT_STATUS_OK;
+ }
+
+ ok = parse_msdfs_symlink(mem_ctx,
+ lp_msdfs_shuffle_referrals(SNUM(handle->conn)),
+ link_target,
+ ppreflist,
+ preferral_count);
+
+ if (ok) {
+ status = NT_STATUS_OK;
+ } else {
+ status = NT_STATUS_NO_MEMORY;
+ }
+
+ err:
+
+ if (link_target != link_target_buf) {
+ TALLOC_FREE(link_target);
+ }
+ return status;
+}
+
+static NTSTATUS vfswrap_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static NTSTATUS vfswrap_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static NTSTATUS vfswrap_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+/* Directory operations */
+
+static DIR *vfswrap_fdopendir(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr)
+{
+ DIR *result;
+
+ START_PROFILE(syscall_fdopendir);
+ result = sys_fdopendir(fsp_get_io_fd(fsp));
+ END_PROFILE(syscall_fdopendir);
+ return result;
+}
+
+static struct dirent *vfswrap_readdir(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ struct dirent *result;
+
+ START_PROFILE(syscall_readdir);
+
+ result = readdir(dirp);
+ END_PROFILE(syscall_readdir);
+
+ return result;
+}
+
+static NTSTATUS vfswrap_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **attr_data)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static void vfswrap_rewinddir(vfs_handle_struct *handle, DIR *dirp)
+{
+ START_PROFILE(syscall_rewinddir);
+ rewinddir(dirp);
+ END_PROFILE(syscall_rewinddir);
+}
+
+static int vfswrap_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ int result;
+
+ START_PROFILE(syscall_mkdirat);
+
+ result = mkdirat(fsp_get_pathref_fd(dirfsp), smb_fname->base_name, mode);
+
+ END_PROFILE(syscall_mkdirat);
+ return result;
+}
+
+static int vfswrap_closedir(vfs_handle_struct *handle, DIR *dirp)
+{
+ int result;
+
+ START_PROFILE(syscall_closedir);
+ result = closedir(dirp);
+ END_PROFILE(syscall_closedir);
+ return result;
+}
+
+/* File operations */
+
+static int vfswrap_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int flags = how->flags;
+ mode_t mode = how->mode;
+ bool have_opath = false;
+ bool became_root = false;
+ int result;
+
+ START_PROFILE(syscall_openat);
+
+ if (how->resolve & ~(VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS |
+ VFS_OPEN_HOW_WITH_BACKUP_INTENT)) {
+ errno = ENOSYS;
+ result = -1;
+ goto out;
+ }
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+#ifdef O_PATH
+ have_opath = true;
+ if (fsp->fsp_flags.is_pathref) {
+ flags |= O_PATH;
+ }
+ if (flags & O_PATH) {
+ /*
+ * From "man 2 openat":
+ *
+ * When O_PATH is specified in flags, flag bits other than
+ * O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored.
+ *
+ * From "man 2 openat2":
+ *
+ * Whereas openat(2) ignores unknown bits in its flags
+ * argument, openat2() returns an error if unknown or
+ * conflicting flags are specified in how.flags.
+ *
+ * So we better clear ignored/invalid flags
+ * and only keep the expected ones.
+ */
+ flags &= (O_PATH|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+ }
+#endif
+
+ if (how->resolve & VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS) {
+ struct open_how linux_how = {
+ .flags = flags,
+ .mode = mode,
+ .resolve = RESOLVE_NO_SYMLINKS,
+ };
+
+ result = openat2(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ &linux_how,
+ sizeof(linux_how));
+ if (result == -1) {
+ if (errno == ENOSYS) {
+ /*
+ * The kernel doesn't support
+ * openat2(), so indicate to
+ * the callers that
+ * VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS
+ * would just be a waste of time.
+ */
+ fsp->conn->open_how_resolve &=
+ ~VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS;
+ }
+ goto out;
+ }
+
+ goto done;
+ }
+
+ if (fsp->fsp_flags.is_pathref && !have_opath) {
+ become_root();
+ became_root = true;
+ }
+
+ result = openat(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ flags,
+ mode);
+
+ if (became_root) {
+ int err = errno;
+ unbecome_root();
+ errno = err;
+ }
+
+done:
+ if (result >= 0) {
+ fsp->fsp_flags.have_proc_fds = fsp->conn->have_proc_fds;
+ } else {
+ /*
+ * "/proc/self/fd/-1" never exists. Indicate to upper
+ * layers that for this fsp a possible name-based
+ * fallback is the only way to go.
+ */
+ fsp->fsp_flags.have_proc_fds = false;
+ }
+
+out:
+ END_PROFILE(syscall_openat);
+ return result;
+}
+static NTSTATUS vfswrap_create_file(vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ return create_file_default(handle->conn, req, dirfsp, smb_fname,
+ access_mask, share_access,
+ create_disposition, create_options,
+ file_attributes, oplock_request, lease,
+ allocation_size, private_flags,
+ sd, ea_list, result,
+ pinfo, in_context_blobs, out_context_blobs);
+}
+
+static int vfswrap_close(vfs_handle_struct *handle, files_struct *fsp)
+{
+ int result;
+
+ START_PROFILE(syscall_close);
+ result = fd_close_posix(fsp);
+ END_PROFILE(syscall_close);
+ return result;
+}
+
+static ssize_t vfswrap_pread(vfs_handle_struct *handle, files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ ssize_t result;
+
+#if defined(HAVE_PREAD) || defined(HAVE_PREAD64)
+ START_PROFILE_BYTES(syscall_pread, n);
+ result = sys_pread_full(fsp_get_io_fd(fsp), data, n, offset);
+ END_PROFILE_BYTES(syscall_pread);
+
+ if (result == -1 && errno == ESPIPE) {
+ /* Maintain the fiction that pipes can be seeked (sought?) on. */
+ result = sys_read(fsp_get_io_fd(fsp), data, n);
+ fh_set_pos(fsp->fh, 0);
+ }
+
+#else /* HAVE_PREAD */
+ errno = ENOSYS;
+ result = -1;
+#endif /* HAVE_PREAD */
+
+ return result;
+}
+
+static ssize_t vfswrap_pwrite(vfs_handle_struct *handle, files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ ssize_t result;
+
+#if defined(HAVE_PWRITE) || defined(HAVE_PRWITE64)
+ START_PROFILE_BYTES(syscall_pwrite, n);
+ result = sys_pwrite_full(fsp_get_io_fd(fsp), data, n, offset);
+ END_PROFILE_BYTES(syscall_pwrite);
+
+ if (result == -1 && errno == ESPIPE) {
+ /* Maintain the fiction that pipes can be sought on. */
+ result = sys_write(fsp_get_io_fd(fsp), data, n);
+ }
+
+#else /* HAVE_PWRITE */
+ errno = ENOSYS;
+ result = -1;
+#endif /* HAVE_PWRITE */
+
+ return result;
+}
+
+struct vfswrap_pread_state {
+ ssize_t ret;
+ int fd;
+ void *buf;
+ size_t count;
+ off_t offset;
+
+ struct vfs_aio_state vfs_aio_state;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+};
+
+static void vfs_pread_do(void *private_data);
+static void vfs_pread_done(struct tevent_req *subreq);
+static int vfs_pread_state_destructor(struct vfswrap_pread_state *state);
+
+static struct tevent_req *vfswrap_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct vfswrap_pread_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct vfswrap_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ret = -1;
+ state->fd = fsp_get_io_fd(fsp);
+ state->buf = data;
+ state->count = n;
+ state->offset = offset;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pread, profile_p,
+ state->profile_bytes, n);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+
+ subreq = pthreadpool_tevent_job_send(
+ state, ev, handle->conn->sconn->pool,
+ vfs_pread_do, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_pread_done, req);
+
+ talloc_set_destructor(state, vfs_pread_state_destructor);
+
+ return req;
+}
+
+static void vfs_pread_do(void *private_data)
+{
+ struct vfswrap_pread_state *state = talloc_get_type_abort(
+ private_data, struct vfswrap_pread_state);
+ struct timespec start_time;
+ struct timespec end_time;
+
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes);
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ state->ret = sys_pread_full(state->fd,
+ state->buf,
+ state->count,
+ state->offset);
+
+ if (state->ret == -1) {
+ state->vfs_aio_state.error = errno;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time);
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+}
+
+static int vfs_pread_state_destructor(struct vfswrap_pread_state *state)
+{
+ return -1;
+}
+
+static void vfs_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfswrap_pread_state *state = tevent_req_data(
+ req, struct vfswrap_pread_state);
+ int ret;
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+ SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes);
+ talloc_set_destructor(state, NULL);
+ if (ret != 0) {
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ vfs_pread_do(state);
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t vfswrap_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfswrap_pread_state *state = tevent_req_data(
+ req, struct vfswrap_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct vfswrap_pwrite_state {
+ ssize_t ret;
+ int fd;
+ const void *buf;
+ size_t count;
+ off_t offset;
+
+ struct vfs_aio_state vfs_aio_state;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+};
+
+static void vfs_pwrite_do(void *private_data);
+static void vfs_pwrite_done(struct tevent_req *subreq);
+static int vfs_pwrite_state_destructor(struct vfswrap_pwrite_state *state);
+
+static struct tevent_req *vfswrap_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct vfswrap_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct vfswrap_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ret = -1;
+ state->fd = fsp_get_io_fd(fsp);
+ state->buf = data;
+ state->count = n;
+ state->offset = offset;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pwrite, profile_p,
+ state->profile_bytes, n);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+
+ subreq = pthreadpool_tevent_job_send(
+ state, ev, handle->conn->sconn->pool,
+ vfs_pwrite_do, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_pwrite_done, req);
+
+ talloc_set_destructor(state, vfs_pwrite_state_destructor);
+
+ return req;
+}
+
+static void vfs_pwrite_do(void *private_data)
+{
+ struct vfswrap_pwrite_state *state = talloc_get_type_abort(
+ private_data, struct vfswrap_pwrite_state);
+ struct timespec start_time;
+ struct timespec end_time;
+
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes);
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ state->ret = sys_pwrite_full(state->fd,
+ state->buf,
+ state->count,
+ state->offset);
+
+ if (state->ret == -1) {
+ state->vfs_aio_state.error = errno;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time);
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+}
+
+static int vfs_pwrite_state_destructor(struct vfswrap_pwrite_state *state)
+{
+ return -1;
+}
+
+static void vfs_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfswrap_pwrite_state *state = tevent_req_data(
+ req, struct vfswrap_pwrite_state);
+ int ret;
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+ SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes);
+ talloc_set_destructor(state, NULL);
+ if (ret != 0) {
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ vfs_pwrite_do(state);
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t vfswrap_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfswrap_pwrite_state *state = tevent_req_data(
+ req, struct vfswrap_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct vfswrap_fsync_state {
+ ssize_t ret;
+ int fd;
+
+ struct vfs_aio_state vfs_aio_state;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+};
+
+static void vfs_fsync_do(void *private_data);
+static void vfs_fsync_done(struct tevent_req *subreq);
+static int vfs_fsync_state_destructor(struct vfswrap_fsync_state *state);
+
+static struct tevent_req *vfswrap_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req, *subreq;
+ struct vfswrap_fsync_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct vfswrap_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ret = -1;
+ state->fd = fsp_get_io_fd(fsp);
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_fsync, profile_p,
+ state->profile_bytes, 0);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+
+ subreq = pthreadpool_tevent_job_send(
+ state, ev, handle->conn->sconn->pool, vfs_fsync_do, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_fsync_done, req);
+
+ talloc_set_destructor(state, vfs_fsync_state_destructor);
+
+ return req;
+}
+
+static void vfs_fsync_do(void *private_data)
+{
+ struct vfswrap_fsync_state *state = talloc_get_type_abort(
+ private_data, struct vfswrap_fsync_state);
+ struct timespec start_time;
+ struct timespec end_time;
+
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes);
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ do {
+ state->ret = fsync(state->fd);
+ } while ((state->ret == -1) && (errno == EINTR));
+
+ if (state->ret == -1) {
+ state->vfs_aio_state.error = errno;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time);
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+}
+
+static int vfs_fsync_state_destructor(struct vfswrap_fsync_state *state)
+{
+ return -1;
+}
+
+static void vfs_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfswrap_fsync_state *state = tevent_req_data(
+ req, struct vfswrap_fsync_state);
+ int ret;
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+ SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes);
+ talloc_set_destructor(state, NULL);
+ if (ret != 0) {
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ vfs_fsync_do(state);
+ }
+
+ tevent_req_done(req);
+}
+
+static int vfswrap_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfswrap_fsync_state *state = tevent_req_data(
+ req, struct vfswrap_fsync_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static off_t vfswrap_lseek(vfs_handle_struct *handle, files_struct *fsp, off_t offset, int whence)
+{
+ off_t result = 0;
+
+ START_PROFILE(syscall_lseek);
+
+ result = lseek(fsp_get_io_fd(fsp), offset, whence);
+ /*
+ * We want to maintain the fiction that we can seek
+ * on a fifo for file system purposes. This allows
+ * people to set up UNIX fifo's that feed data to Windows
+ * applications. JRA.
+ */
+
+ if((result == -1) && (errno == ESPIPE)) {
+ result = 0;
+ errno = 0;
+ }
+
+ END_PROFILE(syscall_lseek);
+ return result;
+}
+
+static ssize_t vfswrap_sendfile(vfs_handle_struct *handle, int tofd, files_struct *fromfsp, const DATA_BLOB *hdr,
+ off_t offset, size_t n)
+{
+ ssize_t result;
+
+ START_PROFILE_BYTES(syscall_sendfile, n);
+ result = sys_sendfile(tofd, fsp_get_io_fd(fromfsp), hdr, offset, n);
+ END_PROFILE_BYTES(syscall_sendfile);
+ return result;
+}
+
+static ssize_t vfswrap_recvfile(vfs_handle_struct *handle,
+ int fromfd,
+ files_struct *tofsp,
+ off_t offset,
+ size_t n)
+{
+ ssize_t result;
+
+ START_PROFILE_BYTES(syscall_recvfile, n);
+ result = sys_recvfile(fromfd, fsp_get_io_fd(tofsp), offset, n);
+ END_PROFILE_BYTES(syscall_recvfile);
+ return result;
+}
+
+static int vfswrap_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int result = -1;
+
+ START_PROFILE(syscall_renameat);
+
+ SMB_ASSERT(!is_named_stream(smb_fname_src));
+ SMB_ASSERT(!is_named_stream(smb_fname_dst));
+
+ result = renameat(fsp_get_pathref_fd(srcfsp),
+ smb_fname_src->base_name,
+ fsp_get_pathref_fd(dstfsp),
+ smb_fname_dst->base_name);
+
+ END_PROFILE(syscall_renameat);
+ return result;
+}
+
+static int vfswrap_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int result = -1;
+
+ START_PROFILE(syscall_stat);
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ result = sys_stat(smb_fname->base_name, &smb_fname->st,
+ lp_fake_directory_create_times(SNUM(handle->conn)));
+
+ END_PROFILE(syscall_stat);
+ return result;
+}
+
+static int vfswrap_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ int result;
+
+ START_PROFILE(syscall_fstat);
+ result = sys_fstat(fsp_get_pathref_fd(fsp),
+ sbuf, lp_fake_directory_create_times(SNUM(handle->conn)));
+ END_PROFILE(syscall_fstat);
+ return result;
+}
+
+static int vfswrap_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int result = -1;
+
+ START_PROFILE(syscall_lstat);
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ result = sys_lstat(smb_fname->base_name, &smb_fname->st,
+ lp_fake_directory_create_times(SNUM(handle->conn)));
+
+ END_PROFILE(syscall_lstat);
+ return result;
+}
+
+static int vfswrap_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ int result = -1;
+
+ START_PROFILE(syscall_fstatat);
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ result = sys_fstatat(
+ fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ sbuf,
+ flags,
+ lp_fake_directory_create_times(SNUM(handle->conn)));
+
+ END_PROFILE(syscall_fstatat);
+ return result;
+}
+
+static NTSTATUS vfswrap_translate_name(struct vfs_handle_struct *handle,
+ const char *name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name)
+{
+ return NT_STATUS_NONE_MAPPED;
+}
+
+/**
+ * Return allocated parent directory and basename of path
+ *
+ * Note: if requesting atname, it is returned as talloc child of the
+ * parent. Freeing the parent is thus sufficient to free both.
+ */
+static NTSTATUS vfswrap_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out)
+{
+ struct smb_filename *parent = NULL;
+ struct smb_filename *name = NULL;
+ char *p = NULL;
+
+ parent = cp_smb_filename_nostream(mem_ctx, smb_fname_in);
+ if (parent == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SET_STAT_INVALID(parent->st);
+
+ p = strrchr_m(parent->base_name, '/'); /* Find final '/', if any */
+ if (p == NULL) {
+ TALLOC_FREE(parent->base_name);
+ parent->base_name = talloc_strdup(parent, ".");
+ if (parent->base_name == NULL) {
+ TALLOC_FREE(parent);
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = smb_fname_in->base_name;
+ } else {
+ *p = '\0';
+ p++;
+ }
+
+ if (atname_out == NULL) {
+ *parent_dir_out = parent;
+ return NT_STATUS_OK;
+ }
+
+ name = synthetic_smb_fname(
+ parent,
+ p,
+ smb_fname_in->stream_name,
+ &smb_fname_in->st,
+ smb_fname_in->twrp,
+ smb_fname_in->flags);
+ if (name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *parent_dir_out = parent;
+ *atname_out = name;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Implement the default fsctl operation.
+ */
+static bool vfswrap_logged_ioctl_message = false;
+
+static NTSTATUS vfswrap_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags, /* Needed for UNICODE ... */
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len)
+{
+ const char *in_data = (const char *)_in_data;
+ char **out_data = (char **)_out_data;
+ NTSTATUS status;
+
+ /*
+ * Currently all fsctls operate on the base
+ * file if given an alternate data stream.
+ * Revisit this if we implement fsctls later
+ * that need access to the ADS handle.
+ */
+ fsp = metadata_fsp(fsp);
+
+ switch (function) {
+ case FSCTL_SET_SPARSE:
+ {
+ bool set_sparse = true;
+
+ if (in_len >= 1 && in_data[0] == 0) {
+ set_sparse = false;
+ }
+
+ status = file_set_sparse(handle->conn, fsp, set_sparse);
+
+ DEBUG(NT_STATUS_IS_OK(status) ? 10 : 9,
+ ("FSCTL_SET_SPARSE: fname[%s] set[%u] - %s\n",
+ smb_fname_str_dbg(fsp->fsp_name), set_sparse,
+ nt_errstr(status)));
+
+ return status;
+ }
+
+ case FSCTL_CREATE_OR_GET_OBJECT_ID:
+ {
+ unsigned char objid[16];
+ char *return_data = NULL;
+
+ /* This should return the object-id on this file.
+ * I think I'll make this be the inode+dev. JRA.
+ */
+
+ DBG_DEBUG("FSCTL_CREATE_OR_GET_OBJECT_ID: called on %s\n",
+ fsp_fnum_dbg(fsp));
+
+ *out_len = MIN(max_out_len, 64);
+
+ /* Hmmm, will this cause problems if less data asked for? */
+ return_data = talloc_array(ctx, char, 64);
+ if (return_data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* For backwards compatibility only store the dev/inode. */
+ push_file_id_16(return_data, &fsp->file_id);
+ memcpy(return_data+16,create_volume_objectid(fsp->conn,objid),16);
+ push_file_id_16(return_data+32, &fsp->file_id);
+ memset(return_data+48, 0, 16);
+ *out_data = return_data;
+ return NT_STATUS_OK;
+ }
+
+ case FSCTL_GET_REPARSE_POINT:
+ {
+ status = fsctl_get_reparse_point(
+ fsp, ctx, out_data, max_out_len, out_len);
+ return status;
+ }
+
+ case FSCTL_SET_REPARSE_POINT:
+ {
+ status = fsctl_set_reparse_point(fsp, ctx, _in_data, in_len);
+ return status;
+ }
+
+ case FSCTL_DELETE_REPARSE_POINT:
+ {
+ status = fsctl_del_reparse_point(fsp, ctx, _in_data, in_len);
+ return status;
+ }
+
+ case FSCTL_GET_SHADOW_COPY_DATA:
+ {
+ /*
+ * This is called to retrieve the number of Shadow Copies (a.k.a. snapshots)
+ * and return their volume names. If max_data_count is 16, then it is just
+ * asking for the number of volumes and length of the combined names.
+ *
+ * pdata is the data allocated by our caller, but that uses
+ * total_data_count (which is 0 in our case) rather than max_data_count.
+ * Allocate the correct amount and return the pointer to let
+ * it be deallocated when we return.
+ */
+ struct shadow_copy_data *shadow_data = NULL;
+ bool labels = False;
+ uint32_t labels_data_count = 0;
+ uint32_t i;
+ char *cur_pdata = NULL;
+
+ if (max_out_len < 16) {
+ DBG_ERR("FSCTL_GET_SHADOW_COPY_DATA: max_data_count(%u) < 16 is invalid!\n",
+ max_out_len);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (max_out_len > 16) {
+ labels = True;
+ }
+
+ shadow_data = talloc_zero(ctx, struct shadow_copy_data);
+ if (shadow_data == NULL) {
+ DBG_ERR("TALLOC_ZERO() failed!\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Call the VFS routine to actually do the work.
+ */
+ if (SMB_VFS_GET_SHADOW_COPY_DATA(fsp, shadow_data, labels)!=0) {
+ int log_lev = DBGLVL_ERR;
+ if (errno == 0) {
+ /* broken module didn't set errno on error */
+ status = NT_STATUS_UNSUCCESSFUL;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_NOT_SUPPORTED)) {
+ log_lev = DBGLVL_INFO;
+ }
+ }
+ DEBUG(log_lev, ("FSCTL_GET_SHADOW_COPY_DATA: "
+ "connectpath %s, failed - %s.\n",
+ fsp->conn->connectpath,
+ nt_errstr(status)));
+ TALLOC_FREE(shadow_data);
+ return status;
+ }
+
+ labels_data_count = (shadow_data->num_volumes * 2 *
+ sizeof(SHADOW_COPY_LABEL)) + 2;
+
+ if (!labels) {
+ *out_len = 16;
+ } else {
+ *out_len = 12 + labels_data_count;
+ }
+
+ if (max_out_len < *out_len) {
+ DBG_ERR("FSCTL_GET_SHADOW_COPY_DATA: max_data_count(%u) too small (%u) bytes needed!\n",
+ max_out_len, *out_len);
+ TALLOC_FREE(shadow_data);
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ cur_pdata = talloc_zero_array(ctx, char, *out_len);
+ if (cur_pdata == NULL) {
+ TALLOC_FREE(shadow_data);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *out_data = cur_pdata;
+
+ /* num_volumes 4 bytes */
+ SIVAL(cur_pdata, 0, shadow_data->num_volumes);
+
+ if (labels) {
+ /* num_labels 4 bytes */
+ SIVAL(cur_pdata, 4, shadow_data->num_volumes);
+ }
+
+ /* needed_data_count 4 bytes */
+ SIVAL(cur_pdata, 8, labels_data_count);
+
+ cur_pdata += 12;
+
+ DBG_DEBUG("FSCTL_GET_SHADOW_COPY_DATA: %u volumes for path[%s].\n",
+ shadow_data->num_volumes, fsp_str_dbg(fsp));
+ if (labels && shadow_data->labels) {
+ for (i=0; i<shadow_data->num_volumes; i++) {
+ size_t len = 0;
+ status = srvstr_push(cur_pdata, req_flags,
+ cur_pdata, shadow_data->labels[i],
+ 2 * sizeof(SHADOW_COPY_LABEL),
+ STR_UNICODE|STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(*out_data);
+ TALLOC_FREE(shadow_data);
+ return status;
+ }
+ cur_pdata += 2 * sizeof(SHADOW_COPY_LABEL);
+ DEBUGADD(DBGLVL_DEBUG,("Label[%u]: '%s'\n",i,shadow_data->labels[i]));
+ }
+ }
+
+ TALLOC_FREE(shadow_data);
+
+ return NT_STATUS_OK;
+ }
+
+ case FSCTL_FIND_FILES_BY_SID:
+ {
+ /* pretend this succeeded -
+ *
+ * we have to send back a list with all files owned by this SID
+ *
+ * but I have to check that --metze
+ */
+ ssize_t ret;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ uid_t uid;
+ size_t sid_len;
+
+ DBG_DEBUG("FSCTL_FIND_FILES_BY_SID: called on %s\n",
+ fsp_fnum_dbg(fsp));
+
+ if (in_len < 8) {
+ /* NT_STATUS_BUFFER_TOO_SMALL maybe? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sid_len = MIN(in_len - 4,SID_MAX_SIZE);
+
+ /* unknown 4 bytes: this is not the length of the sid :-( */
+ /*unknown = IVAL(pdata,0);*/
+
+ ret = sid_parse(_in_data + 4, sid_len, &sid);
+ if (ret == -1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ DEBUGADD(DBGLVL_DEBUG, ("for SID: %s\n",
+ dom_sid_str_buf(&sid, &buf)));
+
+ if (!sid_to_uid(&sid, &uid)) {
+ DBG_ERR("sid_to_uid: failed, sid[%s] sid_len[%lu]\n",
+ dom_sid_str_buf(&sid, &buf),
+ (unsigned long)sid_len);
+ uid = (-1);
+ }
+
+ /* we can take a look at the find source :-)
+ *
+ * find ./ -uid $uid -name '*' is what we need here
+ *
+ *
+ * and send 4bytes len and then NULL terminated unicode strings
+ * for each file
+ *
+ * but I don't know how to deal with the paged results
+ * (maybe we can hang the result anywhere in the fsp struct)
+ *
+ * but I don't know how to deal with the paged results
+ * (maybe we can hang the result anywhere in the fsp struct)
+ *
+ * we don't send all files at once
+ * and at the next we should *not* start from the beginning,
+ * so we have to cache the result
+ *
+ * --metze
+ */
+
+ /* this works for now... */
+ return NT_STATUS_OK;
+ }
+
+ case FSCTL_QUERY_ALLOCATED_RANGES:
+ {
+ /* FIXME: This is just a dummy reply, telling that all of the
+ * file is allocated. MKS cp needs that.
+ * Adding the real allocated ranges via FIEMAP on Linux
+ * and SEEK_DATA/SEEK_HOLE on Solaris is needed to make
+ * this FSCTL correct for sparse files.
+ */
+ uint64_t offset, length;
+ char *out_data_tmp = NULL;
+
+ if (in_len != 16) {
+ DBG_ERR("FSCTL_QUERY_ALLOCATED_RANGES: data_count(%u) != 16 is invalid!\n",
+ in_len);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (max_out_len < 16) {
+ DBG_ERR("FSCTL_QUERY_ALLOCATED_RANGES: max_out_len (%u) < 16 is invalid!\n",
+ max_out_len);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ offset = BVAL(in_data,0);
+ length = BVAL(in_data,8);
+
+ if (offset + length < offset) {
+ /* No 64-bit integer wrap. */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Shouldn't this be SMB_VFS_STAT ... ? */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *out_len = 16;
+ out_data_tmp = talloc_array(ctx, char, *out_len);
+ if (out_data_tmp == NULL) {
+ DBG_DEBUG("unable to allocate memory for response\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (offset > fsp->fsp_name->st.st_ex_size ||
+ fsp->fsp_name->st.st_ex_size == 0 ||
+ length == 0) {
+ memset(out_data_tmp, 0, *out_len);
+ } else {
+ uint64_t end = offset + length;
+ end = MIN(end, fsp->fsp_name->st.st_ex_size);
+ SBVAL(out_data_tmp, 0, 0);
+ SBVAL(out_data_tmp, 8, end);
+ }
+
+ *out_data = out_data_tmp;
+
+ return NT_STATUS_OK;
+ }
+
+ case FSCTL_IS_VOLUME_DIRTY:
+ {
+ DBG_DEBUG("FSCTL_IS_VOLUME_DIRTY: called on %s "
+ "(but remotely not supported)\n", fsp_fnum_dbg(fsp));
+ /*
+ * http://msdn.microsoft.com/en-us/library/cc232128%28PROT.10%29.aspx
+ * says we have to respond with NT_STATUS_INVALID_PARAMETER
+ */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ default:
+ /*
+ * Only print once ... unfortunately there could be lots of
+ * different FSCTLs that are called.
+ */
+ if (!vfswrap_logged_ioctl_message) {
+ vfswrap_logged_ioctl_message = true;
+ DBG_NOTICE("%s (0x%x): Currently not implemented.\n",
+ __func__, function);
+ }
+ }
+
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static bool vfswrap_is_offline(struct connection_struct *conn,
+ const struct smb_filename *fname);
+
+struct vfswrap_get_dos_attributes_state {
+ struct vfs_aio_state aio_state;
+ connection_struct *conn;
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ files_struct *dir_fsp;
+ struct smb_filename *smb_fname;
+ uint32_t dosmode;
+ bool as_root;
+};
+
+static void vfswrap_get_dos_attributes_getxattr_done(struct tevent_req *subreq);
+
+static struct tevent_req *vfswrap_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct vfswrap_get_dos_attributes_state *state = NULL;
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfswrap_get_dos_attributes_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct vfswrap_get_dos_attributes_state) {
+ .conn = dir_fsp->conn,
+ .mem_ctx = mem_ctx,
+ .ev = ev,
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ };
+
+ if (!lp_store_dos_attributes(SNUM(dir_fsp->conn))) {
+ DBG_ERR("%s: \"smbd async dosmode\" enabled, but "
+ "\"store dos attributes\" is disabled\n",
+ dir_fsp->conn->connectpath);
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_GETXATTRAT_SEND(state,
+ ev,
+ dir_fsp,
+ smb_fname,
+ SAMBA_XATTR_DOS_ATTRIB,
+ sizeof(fstring));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ vfswrap_get_dos_attributes_getxattr_done,
+ req);
+
+ return req;
+}
+
+static void vfswrap_get_dos_attributes_getxattr_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct vfswrap_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct vfswrap_get_dos_attributes_state);
+ ssize_t xattr_size;
+ DATA_BLOB blob = {0};
+ char *path = NULL;
+ char *tofree = NULL;
+ char pathbuf[PATH_MAX+1];
+ ssize_t pathlen;
+ struct smb_filename smb_fname;
+ bool offline;
+ NTSTATUS status;
+
+ xattr_size = SMB_VFS_GETXATTRAT_RECV(subreq,
+ &state->aio_state,
+ state,
+ &blob.data);
+ TALLOC_FREE(subreq);
+ if (xattr_size == -1) {
+ status = map_nt_error_from_unix(state->aio_state.error);
+
+ if (state->as_root) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->as_root = true;
+
+ become_root();
+ subreq = SMB_VFS_GETXATTRAT_SEND(state,
+ state->ev,
+ state->dir_fsp,
+ state->smb_fname,
+ SAMBA_XATTR_DOS_ATTRIB,
+ sizeof(fstring));
+ unbecome_root();
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ vfswrap_get_dos_attributes_getxattr_done,
+ req);
+ return;
+ }
+
+ blob.length = xattr_size;
+
+ status = parse_dos_attribute_blob(state->smb_fname,
+ blob,
+ &state->dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ pathlen = full_path_tos(state->dir_fsp->fsp_name->base_name,
+ state->smb_fname->base_name,
+ pathbuf,
+ sizeof(pathbuf),
+ &path,
+ &tofree);
+ if (pathlen == -1) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ smb_fname = (struct smb_filename) {
+ .base_name = path,
+ .st = state->smb_fname->st,
+ .flags = state->smb_fname->flags,
+ .twrp = state->smb_fname->twrp,
+ };
+
+ offline = vfswrap_is_offline(state->conn, &smb_fname);
+ if (offline) {
+ state->dosmode |= FILE_ATTRIBUTE_OFFLINE;
+ }
+ TALLOC_FREE(tofree);
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS vfswrap_get_dos_attributes_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode)
+{
+ struct vfswrap_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct vfswrap_get_dos_attributes_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *aio_state = state->aio_state;
+ *dosmode = state->dosmode;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS vfswrap_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ bool offline;
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ offline = vfswrap_is_offline(handle->conn, fsp->fsp_name);
+ if (offline) {
+ *dosmode |= FILE_ATTRIBUTE_OFFLINE;
+ }
+
+ return fget_ea_dos_attribute(fsp, dosmode);
+}
+
+static NTSTATUS vfswrap_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ return set_ea_dos_attribute(handle->conn, fsp->fsp_name, dosmode);
+}
+
+static struct vfs_offload_ctx *vfswrap_offload_ctx;
+
+struct vfswrap_offload_read_state {
+ DATA_BLOB token;
+};
+
+static struct tevent_req *vfswrap_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ struct tevent_req *req = NULL;
+ struct vfswrap_offload_read_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfswrap_offload_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ status = vfs_offload_token_ctx_init(fsp->conn->sconn->client,
+ &vfswrap_offload_ctx);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (fsctl != FSCTL_SRV_REQUEST_RESUME_KEY) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return tevent_req_post(req, ev);
+ }
+
+ status = vfs_offload_token_create_blob(state, fsp, fsctl,
+ &state->token);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = vfs_offload_token_db_store_fsp(vfswrap_offload_ctx, fsp,
+ &state->token);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS vfswrap_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token)
+{
+ struct vfswrap_offload_read_state *state = tevent_req_data(
+ req, struct vfswrap_offload_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *flags = 0;
+ *xferlen = 0;
+ token->length = state->token.length;
+ token->data = talloc_move(mem_ctx, &state->token.data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct vfswrap_offload_write_state {
+ uint8_t *buf;
+ bool read_lck_locked;
+ bool write_lck_locked;
+ DATA_BLOB *token;
+ struct tevent_context *src_ev;
+ struct files_struct *src_fsp;
+ off_t src_off;
+ struct tevent_context *dst_ev;
+ struct files_struct *dst_fsp;
+ off_t dst_off;
+ off_t to_copy;
+ off_t remaining;
+ off_t copied;
+ size_t next_io_size;
+};
+
+static void vfswrap_offload_write_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct vfswrap_offload_write_state *state = tevent_req_data(
+ req, struct vfswrap_offload_write_state);
+ bool ok;
+
+ if (state->dst_fsp == NULL) {
+ return;
+ }
+
+ ok = change_to_user_and_service_by_fsp(state->dst_fsp);
+ SMB_ASSERT(ok);
+ state->dst_fsp = NULL;
+}
+
+static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req);
+static NTSTATUS vfswrap_offload_write_loop(struct tevent_req *req);
+
+static struct tevent_req *vfswrap_offload_write_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t to_copy)
+{
+ struct tevent_req *req;
+ struct vfswrap_offload_write_state *state = NULL;
+ /* off_t is signed! */
+ off_t max_offset = INT64_MAX - to_copy;
+ size_t num = MIN(to_copy, COPYCHUNK_MAX_TOTAL_LEN);
+ files_struct *src_fsp = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfswrap_offload_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct vfswrap_offload_write_state) {
+ .token = token,
+ .src_off = transfer_offset,
+ .dst_ev = ev,
+ .dst_fsp = dest_fsp,
+ .dst_off = dest_off,
+ .to_copy = to_copy,
+ .remaining = to_copy,
+ };
+
+ tevent_req_set_cleanup_fn(req, vfswrap_offload_write_cleanup);
+
+ switch (fsctl) {
+ case FSCTL_SRV_COPYCHUNK:
+ case FSCTL_SRV_COPYCHUNK_WRITE:
+ break;
+
+ case FSCTL_OFFLOAD_WRITE:
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+
+ case FSCTL_DUP_EXTENTS_TO_FILE:
+ DBG_DEBUG("COW clones not supported by vfs_default\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+
+ default:
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * From here on we assume a copy-chunk fsctl
+ */
+
+ if (to_copy == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->src_off > max_offset) {
+ /*
+ * Protect integer checks below.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (state->src_off < 0) {
+ /*
+ * Protect integer checks below.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (state->dst_off > max_offset) {
+ /*
+ * Protect integer checks below.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (state->dst_off < 0) {
+ /*
+ * Protect integer checks below.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = vfs_offload_token_db_fetch_fsp(vfswrap_offload_ctx,
+ token, &src_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_DEBUG("server side copy chunk of length %" PRIu64 "\n", to_copy);
+
+ status = vfs_offload_token_check_handles(fsctl, src_fsp, dest_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ ok = change_to_user_and_service_by_fsp(src_fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ state->src_ev = src_fsp->conn->sconn->ev_ctx;
+ state->src_fsp = src_fsp;
+
+ status = vfs_stat_fsp(src_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (src_fsp->fsp_name->st.st_ex_size < state->src_off + to_copy) {
+ /*
+ * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request
+ * If the SourceOffset or SourceOffset + Length extends beyond
+ * the end of file, the server SHOULD<240> treat this as a
+ * STATUS_END_OF_FILE error.
+ * ...
+ * <240> Section 3.3.5.15.6: Windows servers will return
+ * STATUS_INVALID_VIEW_SIZE instead of STATUS_END_OF_FILE.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_VIEW_SIZE);
+ return tevent_req_post(req, ev);
+ }
+
+ status = vfswrap_offload_copy_file_range(req);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->buf = talloc_array(state, uint8_t, num);
+ if (tevent_req_nomem(state->buf, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = vfswrap_offload_write_loop(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static NTSTATUS vfswrap_offload_copy_file_range(struct tevent_req *req)
+{
+ struct vfswrap_offload_write_state *state = tevent_req_data(
+ req, struct vfswrap_offload_write_state);
+ struct lock_struct lck;
+ ssize_t nwritten;
+ NTSTATUS status;
+ bool same_file;
+ bool ok;
+ static bool try_copy_file_range = true;
+
+ if (!try_copy_file_range) {
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ same_file = file_id_equal(&state->src_fsp->file_id,
+ &state->dst_fsp->file_id);
+ if (same_file &&
+ sys_io_ranges_overlap(state->remaining,
+ state->src_off,
+ state->remaining,
+ state->dst_off))
+ {
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ if (fsp_is_alternate_stream(state->src_fsp) ||
+ fsp_is_alternate_stream(state->dst_fsp))
+ {
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ init_strict_lock_struct(state->src_fsp,
+ state->src_fsp->op->global->open_persistent_id,
+ state->src_off,
+ state->remaining,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(state->src_fsp),
+ &lck);
+
+ ok = SMB_VFS_STRICT_LOCK_CHECK(state->src_fsp->conn,
+ state->src_fsp,
+ &lck);
+ if (!ok) {
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ ok = change_to_user_and_service_by_fsp(state->dst_fsp);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ init_strict_lock_struct(state->dst_fsp,
+ state->dst_fsp->op->global->open_persistent_id,
+ state->dst_off,
+ state->remaining,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(state->dst_fsp),
+ &lck);
+
+ ok = SMB_VFS_STRICT_LOCK_CHECK(state->dst_fsp->conn,
+ state->dst_fsp,
+ &lck);
+ if (!ok) {
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ while (state->remaining > 0) {
+ nwritten = copy_file_range(fsp_get_io_fd(state->src_fsp),
+ &state->src_off,
+ fsp_get_io_fd(state->dst_fsp),
+ &state->dst_off,
+ state->remaining,
+ 0);
+ if (nwritten == -1) {
+ DBG_DEBUG("copy_file_range src [%s]:[%jd] dst [%s]:[%jd] "
+ "n [%jd] failed: %s\n",
+ fsp_str_dbg(state->src_fsp),
+ (intmax_t)state->src_off,
+ fsp_str_dbg(state->dst_fsp),
+ (intmax_t)state->dst_off,
+ (intmax_t)state->remaining,
+ strerror(errno));
+ switch (errno) {
+ case EOPNOTSUPP:
+ case ENOSYS:
+ try_copy_file_range = false;
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ break;
+ case EXDEV:
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ break;
+ default:
+ status = map_nt_error_from_unix(errno);
+ if (NT_STATUS_EQUAL(
+ status,
+ NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ /* Avoid triggering the fallback */
+ status = NT_STATUS_INTERNAL_ERROR;
+ }
+ break;
+ }
+ return status;
+ }
+
+ if (state->remaining < nwritten) {
+ DBG_DEBUG("copy_file_range src [%s] dst [%s] "
+ "n [%jd] remaining [%jd]\n",
+ fsp_str_dbg(state->src_fsp),
+ fsp_str_dbg(state->dst_fsp),
+ (intmax_t)nwritten,
+ (intmax_t)state->remaining);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (nwritten == 0) {
+ break;
+ }
+ state->copied += nwritten;
+ state->remaining -= nwritten;
+ }
+
+ /*
+ * Tell the req cleanup function there's no need to call
+ * change_to_user_and_service_by_fsp() on the dst handle.
+ */
+ state->dst_fsp = NULL;
+ return NT_STATUS_OK;
+}
+
+static void vfswrap_offload_write_read_done(struct tevent_req *subreq);
+
+static NTSTATUS vfswrap_offload_write_loop(struct tevent_req *req)
+{
+ struct vfswrap_offload_write_state *state = tevent_req_data(
+ req, struct vfswrap_offload_write_state);
+ struct tevent_req *subreq = NULL;
+ struct lock_struct read_lck;
+ bool ok;
+
+ /*
+ * This is called under the context of state->src_fsp.
+ */
+
+ state->next_io_size = MIN(state->remaining, talloc_array_length(state->buf));
+
+ init_strict_lock_struct(state->src_fsp,
+ state->src_fsp->op->global->open_persistent_id,
+ state->src_off,
+ state->next_io_size,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(state->src_fsp),
+ &read_lck);
+
+ ok = SMB_VFS_STRICT_LOCK_CHECK(state->src_fsp->conn,
+ state->src_fsp,
+ &read_lck);
+ if (!ok) {
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ subreq = SMB_VFS_PREAD_SEND(state,
+ state->src_ev,
+ state->src_fsp,
+ state->buf,
+ state->next_io_size,
+ state->src_off);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, vfswrap_offload_write_read_done, req);
+
+ return NT_STATUS_OK;
+}
+
+static void vfswrap_offload_write_write_done(struct tevent_req *subreq);
+
+static void vfswrap_offload_write_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfswrap_offload_write_state *state = tevent_req_data(
+ req, struct vfswrap_offload_write_state);
+ struct vfs_aio_state aio_state;
+ struct lock_struct write_lck;
+ ssize_t nread;
+ bool ok;
+
+ nread = SMB_VFS_PREAD_RECV(subreq, &aio_state);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ DBG_ERR("read failed: %s\n", strerror(aio_state.error));
+ tevent_req_nterror(req, map_nt_error_from_unix(aio_state.error));
+ return;
+ }
+ if (nread != state->next_io_size) {
+ DBG_ERR("Short read, only %zd of %zu\n",
+ nread, state->next_io_size);
+ tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+ return;
+ }
+
+ state->src_off += nread;
+
+ ok = change_to_user_and_service_by_fsp(state->dst_fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ init_strict_lock_struct(state->dst_fsp,
+ state->dst_fsp->op->global->open_persistent_id,
+ state->dst_off,
+ state->next_io_size,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(state->dst_fsp),
+ &write_lck);
+
+ ok = SMB_VFS_STRICT_LOCK_CHECK(state->dst_fsp->conn,
+ state->dst_fsp,
+ &write_lck);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return;
+ }
+
+ subreq = SMB_VFS_PWRITE_SEND(state,
+ state->dst_ev,
+ state->dst_fsp,
+ state->buf,
+ state->next_io_size,
+ state->dst_off);
+ if (subreq == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, vfswrap_offload_write_write_done, req);
+}
+
+static void vfswrap_offload_write_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfswrap_offload_write_state *state = tevent_req_data(
+ req, struct vfswrap_offload_write_state);
+ struct vfs_aio_state aio_state;
+ ssize_t nwritten;
+ NTSTATUS status;
+ bool ok;
+
+ nwritten = SMB_VFS_PWRITE_RECV(subreq, &aio_state);
+ TALLOC_FREE(subreq);
+ if (nwritten == -1) {
+ DBG_ERR("write failed: %s\n", strerror(aio_state.error));
+ tevent_req_nterror(req, map_nt_error_from_unix(aio_state.error));
+ return;
+ }
+ if (nwritten != state->next_io_size) {
+ DBG_ERR("Short write, only %zd of %zu\n", nwritten, state->next_io_size);
+ tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+ return;
+ }
+
+ state->dst_off += nwritten;
+
+ if (state->remaining < nwritten) {
+ /* Paranoia check */
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ state->copied += nwritten;
+ state->remaining -= nwritten;
+ if (state->remaining == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ ok = change_to_user_and_service_by_fsp(state->src_fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ status = vfswrap_offload_write_loop(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ return;
+}
+
+static NTSTATUS vfswrap_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ struct vfswrap_offload_write_state *state = tevent_req_data(
+ req, struct vfswrap_offload_write_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DBG_DEBUG("copy chunk failed: %s\n", nt_errstr(status));
+ *copied = 0;
+ tevent_req_received(req);
+ return status;
+ }
+
+ *copied = state->copied;
+ DBG_DEBUG("copy chunk copied %lu\n", (unsigned long)*copied);
+ tevent_req_received(req);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS vfswrap_fget_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+}
+
+static NTSTATUS vfswrap_set_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+}
+
+/********************************************************************
+ Given a stat buffer return the allocated size on disk, taking into
+ account sparse files.
+********************************************************************/
+static uint64_t vfswrap_get_alloc_size(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ uint64_t result;
+
+ START_PROFILE(syscall_get_alloc_size);
+
+ if(S_ISDIR(sbuf->st_ex_mode)) {
+ result = 0;
+ goto out;
+ }
+
+#if defined(HAVE_STAT_ST_BLOCKS) && defined(STAT_ST_BLOCKSIZE)
+ /* The type of st_blocksize is blkcnt_t which *MUST* be
+ signed (according to POSIX) and can be less than 64-bits.
+ Ensure when we're converting to 64 bits wide we don't
+ sign extend. */
+#if defined(SIZEOF_BLKCNT_T_8)
+ result = (uint64_t)STAT_ST_BLOCKSIZE * (uint64_t)sbuf->st_ex_blocks;
+#elif defined(SIZEOF_BLKCNT_T_4)
+ {
+ uint64_t bs = ((uint64_t)sbuf->st_ex_blocks) & 0xFFFFFFFFLL;
+ result = (uint64_t)STAT_ST_BLOCKSIZE * bs;
+ }
+#else
+#error SIZEOF_BLKCNT_T_NOT_A_SUPPORTED_VALUE
+#endif
+ if (result == 0) {
+ /*
+ * Some file systems do not allocate a block for very
+ * small files. But for non-empty file should report a
+ * positive size.
+ */
+
+ uint64_t filesize = get_file_size_stat(sbuf);
+ if (filesize > 0) {
+ result = MIN((uint64_t)STAT_ST_BLOCKSIZE, filesize);
+ }
+ }
+#else
+ result = get_file_size_stat(sbuf);
+#endif
+
+ if (fsp && fsp->initial_allocation_size)
+ result = MAX(result,fsp->initial_allocation_size);
+
+ result = smb_roundup(handle->conn, result);
+
+ out:
+ END_PROFILE(syscall_get_alloc_size);
+ return result;
+}
+
+static int vfswrap_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int result = -1;
+
+ START_PROFILE(syscall_unlinkat);
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ result = unlinkat(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ flags);
+
+ END_PROFILE(syscall_unlinkat);
+ return result;
+}
+
+static int vfswrap_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode)
+{
+ int result;
+
+ START_PROFILE(syscall_fchmod);
+
+ if (!fsp->fsp_flags.is_pathref) {
+ result = fchmod(fsp_get_io_fd(fsp), mode);
+ END_PROFILE(syscall_fchmod);
+ return result;
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ result = chmod(sys_proc_fd_path(fd, &buf), mode);
+
+ END_PROFILE(syscall_fchmod);
+ return result;
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ result = chmod(fsp->fsp_name->base_name, mode);
+
+ END_PROFILE(syscall_fchmod);
+ return result;
+}
+
+static int vfswrap_fchown(vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid)
+{
+#ifdef HAVE_FCHOWN
+ int result;
+
+ START_PROFILE(syscall_fchown);
+ if (!fsp->fsp_flags.is_pathref) {
+ result = fchown(fsp_get_io_fd(fsp), uid, gid);
+ END_PROFILE(syscall_fchown);
+ return result;
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ result = chown(sys_proc_fd_path(fd, &buf), uid, gid);
+
+ END_PROFILE(syscall_fchown);
+ return result;
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ result = chown(fsp->fsp_name->base_name, uid, gid);
+ END_PROFILE(syscall_fchown);
+ return result;
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static int vfswrap_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int result;
+
+ START_PROFILE(syscall_lchown);
+ result = lchown(smb_fname->base_name, uid, gid);
+ END_PROFILE(syscall_lchown);
+ return result;
+}
+
+static int vfswrap_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int result;
+
+ START_PROFILE(syscall_chdir);
+ result = chdir(smb_fname->base_name);
+ END_PROFILE(syscall_chdir);
+ return result;
+}
+
+static struct smb_filename *vfswrap_getwd(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ char *result;
+ struct smb_filename *smb_fname = NULL;
+
+ START_PROFILE(syscall_getwd);
+ result = sys_getwd();
+ END_PROFILE(syscall_getwd);
+
+ if (result == NULL) {
+ return NULL;
+ }
+ smb_fname = synthetic_smb_fname(ctx,
+ result,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ /*
+ * sys_getwd() *always* returns malloced memory.
+ * We must free here to avoid leaks:
+ * BUG:https://bugzilla.samba.org/show_bug.cgi?id=13372
+ */
+ SAFE_FREE(result);
+ return smb_fname;
+}
+
+/*********************************************************************
+ nsec timestamp resolution call. Convert down to whatever the underlying
+ system will support.
+**********************************************************************/
+
+static int vfswrap_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ int result = -1;
+ struct timespec ts[2];
+ struct timespec *times = NULL;
+
+ START_PROFILE(syscall_fntimes);
+
+ if (fsp_is_alternate_stream(fsp)) {
+ errno = ENOENT;
+ goto out;
+ }
+
+ if (ft != NULL) {
+ if (is_omit_timespec(&ft->atime)) {
+ ft->atime = fsp->fsp_name->st.st_ex_atime;
+ }
+
+ if (is_omit_timespec(&ft->mtime)) {
+ ft->mtime = fsp->fsp_name->st.st_ex_mtime;
+ }
+
+ if (!is_omit_timespec(&ft->create_time)) {
+ set_create_timespec_ea(fsp,
+ ft->create_time);
+ }
+
+ if ((timespec_compare(&ft->atime,
+ &fsp->fsp_name->st.st_ex_atime) == 0) &&
+ (timespec_compare(&ft->mtime,
+ &fsp->fsp_name->st.st_ex_mtime) == 0)) {
+ result = 0;
+ goto out;
+ }
+
+ ts[0] = ft->atime;
+ ts[1] = ft->mtime;
+ times = ts;
+ } else {
+ times = NULL;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ result = futimens(fsp_get_io_fd(fsp), times);
+ goto out;
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ result = utimensat(AT_FDCWD,
+ sys_proc_fd_path(fd, &buf),
+ times,
+ 0);
+
+ goto out;
+ }
+
+ /*
+ * The fd is a pathref (opened with O_PATH) and there isn't fd to
+ * path translation mechanism. Fallback to path based call.
+ */
+ result = utimensat(AT_FDCWD, fsp->fsp_name->base_name, times, 0);
+
+out:
+ END_PROFILE(syscall_fntimes);
+
+ return result;
+}
+
+
+/*********************************************************************
+ A version of ftruncate that will write the space on disk if strict
+ allocate is set.
+**********************************************************************/
+
+static int strict_allocate_ftruncate(vfs_handle_struct *handle, files_struct *fsp, off_t len)
+{
+ off_t space_to_write;
+ uint64_t space_avail;
+ uint64_t bsize,dfree,dsize;
+ int ret;
+ NTSTATUS status;
+ SMB_STRUCT_STAT *pst;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(len, 0);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ pst = &fsp->fsp_name->st;
+
+#ifdef S_ISFIFO
+ if (S_ISFIFO(pst->st_ex_mode))
+ return 0;
+#endif
+
+ if (pst->st_ex_size == len)
+ return 0;
+
+ /* Shrink - just ftruncate. */
+ if (pst->st_ex_size > len)
+ return ftruncate(fsp_get_io_fd(fsp), len);
+
+ space_to_write = len - pst->st_ex_size;
+
+ /* for allocation try fallocate first. This can fail on some
+ platforms e.g. when the filesystem doesn't support it and no
+ emulation is being done by the libc (like on AIX with JFS1). In that
+ case we do our own emulation. fallocate implementations can
+ return ENOTSUP or EINVAL in cases like that. */
+ ret = SMB_VFS_FALLOCATE(fsp, 0, pst->st_ex_size, space_to_write);
+ if (ret == -1 && errno == ENOSPC) {
+ return -1;
+ }
+ if (ret == 0) {
+ return 0;
+ }
+ DBG_DEBUG("strict_allocate_ftruncate: SMB_VFS_FALLOCATE failed with "
+ "error %d. Falling back to slow manual allocation\n", errno);
+
+ /* available disk space is enough or not? */
+ space_avail =
+ get_dfree_info(fsp->conn, fsp->fsp_name, &bsize, &dfree, &dsize);
+ /* space_avail is 1k blocks */
+ if (space_avail == (uint64_t)-1 ||
+ ((uint64_t)space_to_write/1024 > space_avail) ) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ /* Write out the real space on disk. */
+ ret = vfs_slow_fallocate(fsp, pst->st_ex_size, space_to_write);
+ if (ret != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int vfswrap_ftruncate(vfs_handle_struct *handle, files_struct *fsp, off_t len)
+{
+ int result = -1;
+ SMB_STRUCT_STAT *pst;
+ NTSTATUS status;
+ char c = 0;
+
+ START_PROFILE(syscall_ftruncate);
+
+ if (lp_strict_allocate(SNUM(fsp->conn)) && !fsp->fsp_flags.is_sparse) {
+ result = strict_allocate_ftruncate(handle, fsp, len);
+ END_PROFILE(syscall_ftruncate);
+ return result;
+ }
+
+ /* we used to just check HAVE_FTRUNCATE_EXTEND and only use
+ ftruncate if the system supports it. Then I discovered that
+ you can have some filesystems that support ftruncate
+ expansion and some that don't! On Linux fat can't do
+ ftruncate extend but ext2 can. */
+
+ result = ftruncate(fsp_get_io_fd(fsp), len);
+
+ /* According to W. R. Stevens advanced UNIX prog. Pure 4.3 BSD cannot
+ extend a file with ftruncate. Provide alternate implementation
+ for this */
+
+ /* Do an fstat to see if the file is longer than the requested
+ size in which case the ftruncate above should have
+ succeeded or shorter, in which case seek to len - 1 and
+ write 1 byte of zero */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* We need to update the files_struct after successful ftruncate */
+ if (result == 0) {
+ goto done;
+ }
+
+ pst = &fsp->fsp_name->st;
+
+#ifdef S_ISFIFO
+ if (S_ISFIFO(pst->st_ex_mode)) {
+ result = 0;
+ goto done;
+ }
+#endif
+
+ if (pst->st_ex_size == len) {
+ result = 0;
+ goto done;
+ }
+
+ if (pst->st_ex_size > len) {
+ /* the ftruncate should have worked */
+ goto done;
+ }
+
+ if (SMB_VFS_PWRITE(fsp, &c, 1, len-1)!=1) {
+ goto done;
+ }
+
+ result = 0;
+
+ done:
+
+ END_PROFILE(syscall_ftruncate);
+ return result;
+}
+
+static int vfswrap_fallocate(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ int result;
+
+ START_PROFILE(syscall_fallocate);
+ if (mode == 0) {
+ result = sys_posix_fallocate(fsp_get_io_fd(fsp), offset, len);
+ /*
+ * posix_fallocate returns 0 on success, errno on error
+ * and doesn't set errno. Make it behave like fallocate()
+ * which returns -1, and sets errno on failure.
+ */
+ if (result != 0) {
+ errno = result;
+ result = -1;
+ }
+ } else {
+ /* sys_fallocate handles filtering of unsupported mode flags */
+ result = sys_fallocate(fsp_get_io_fd(fsp), mode, offset, len);
+ }
+ END_PROFILE(syscall_fallocate);
+ return result;
+}
+
+static bool vfswrap_lock(vfs_handle_struct *handle, files_struct *fsp, int op, off_t offset, off_t count, int type)
+{
+ bool result;
+
+ START_PROFILE(syscall_fcntl_lock);
+
+ if (fsp->fsp_flags.use_ofd_locks) {
+ op = map_process_lock_to_ofd_lock(op);
+ }
+
+ result = fcntl_lock(fsp_get_io_fd(fsp), op, offset, count, type);
+ END_PROFILE(syscall_fcntl_lock);
+ return result;
+}
+
+static int vfswrap_filesystem_sharemode(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int vfswrap_fcntl(vfs_handle_struct *handle, files_struct *fsp, int cmd,
+ va_list cmd_arg)
+{
+ void *argp;
+ va_list dup_cmd_arg;
+ int result;
+ int val;
+
+ START_PROFILE(syscall_fcntl);
+
+ va_copy(dup_cmd_arg, cmd_arg);
+
+ switch(cmd) {
+ case F_SETLK:
+ case F_SETLKW:
+ case F_GETLK:
+#if defined(HAVE_OFD_LOCKS)
+ case F_OFD_SETLK:
+ case F_OFD_SETLKW:
+ case F_OFD_GETLK:
+#endif
+#if defined(HAVE_F_OWNER_EX)
+ case F_GETOWN_EX:
+ case F_SETOWN_EX:
+#endif
+#if defined(HAVE_RW_HINTS)
+ case F_GET_RW_HINT:
+ case F_SET_RW_HINT:
+ case F_GET_FILE_RW_HINT:
+ case F_SET_FILE_RW_HINT:
+#endif
+ argp = va_arg(dup_cmd_arg, void *);
+ result = sys_fcntl_ptr(fsp_get_io_fd(fsp), cmd, argp);
+ break;
+ default:
+ val = va_arg(dup_cmd_arg, int);
+ result = sys_fcntl_int(fsp_get_io_fd(fsp), cmd, val);
+ }
+
+ va_end(dup_cmd_arg);
+
+ END_PROFILE(syscall_fcntl);
+ return result;
+}
+
+static bool vfswrap_getlock(vfs_handle_struct *handle, files_struct *fsp, off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid)
+{
+ bool result;
+ int op = F_GETLK;
+
+ START_PROFILE(syscall_fcntl_getlock);
+
+ if (fsp->fsp_flags.use_ofd_locks) {
+ op = map_process_lock_to_ofd_lock(op);
+ }
+
+ result = fcntl_getlock(fsp_get_io_fd(fsp), op, poffset, pcount, ptype, ppid);
+ END_PROFILE(syscall_fcntl_getlock);
+ return result;
+}
+
+static int vfswrap_linux_setlease(vfs_handle_struct *handle, files_struct *fsp,
+ int leasetype)
+{
+ int result = -1;
+
+ START_PROFILE(syscall_linux_setlease);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+ result = linux_setlease(fsp_get_io_fd(fsp), leasetype);
+#else
+ errno = ENOSYS;
+#endif
+ END_PROFILE(syscall_linux_setlease);
+ return result;
+}
+
+static int vfswrap_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_target,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ int result;
+
+ START_PROFILE(syscall_symlinkat);
+
+ SMB_ASSERT(!is_named_stream(new_smb_fname));
+
+ result = symlinkat(link_target->base_name,
+ fsp_get_pathref_fd(dirfsp),
+ new_smb_fname->base_name);
+ END_PROFILE(syscall_symlinkat);
+ return result;
+}
+
+static int vfswrap_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ int result;
+
+ START_PROFILE(syscall_readlinkat);
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ result = readlinkat(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ buf,
+ bufsiz);
+
+ END_PROFILE(syscall_readlinkat);
+ return result;
+}
+
+static int vfswrap_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ int result;
+
+ START_PROFILE(syscall_linkat);
+
+ SMB_ASSERT(!is_named_stream(old_smb_fname));
+ SMB_ASSERT(!is_named_stream(new_smb_fname));
+
+ result = linkat(fsp_get_pathref_fd(srcfsp),
+ old_smb_fname->base_name,
+ fsp_get_pathref_fd(dstfsp),
+ new_smb_fname->base_name,
+ flags);
+
+ END_PROFILE(syscall_linkat);
+ return result;
+}
+
+static int vfswrap_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ int result;
+
+ START_PROFILE(syscall_mknodat);
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ result = sys_mknodat(fsp_get_pathref_fd(dirfsp),
+ smb_fname->base_name,
+ mode,
+ dev);
+
+ END_PROFILE(syscall_mknodat);
+ return result;
+}
+
+static struct smb_filename *vfswrap_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ char *result;
+ struct smb_filename *result_fname = NULL;
+
+ START_PROFILE(syscall_realpath);
+ result = sys_realpath(smb_fname->base_name);
+ END_PROFILE(syscall_realpath);
+ if (result) {
+ result_fname = synthetic_smb_fname(ctx,
+ result,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ SAFE_FREE(result);
+ }
+ return result_fname;
+}
+
+static int vfswrap_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+#ifdef HAVE_FCHFLAGS
+ int fd = fsp_get_pathref_fd(fsp);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (!fsp->fsp_flags.is_pathref) {
+ return fchflags(fd, flags);
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ struct sys_proc_fd_path_buf buf;
+
+ return chflags(sys_proc_fd_path(fd, &buf), flags);
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ return chflags(fsp->fsp_name->base_name, flags);
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static struct file_id vfswrap_file_id_create(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct file_id key;
+
+ /* the ZERO_STRUCT ensures padding doesn't break using the key as a
+ * blob */
+ ZERO_STRUCT(key);
+
+ key.devid = sbuf->st_ex_dev;
+ key.inode = sbuf->st_ex_ino;
+ /* key.extid is unused by default. */
+
+ return key;
+}
+
+static uint64_t vfswrap_fs_file_id(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ uint64_t file_id;
+
+ if (handle->conn->base_share_dev == psbuf->st_ex_dev) {
+ return (uint64_t)psbuf->st_ex_ino;
+ }
+
+ /* FileIDLow */
+ file_id = ((psbuf->st_ex_ino) & UINT32_MAX);
+
+ /* FileIDHigh */
+ file_id |= ((uint64_t)((psbuf->st_ex_dev) & UINT32_MAX)) << 32;
+
+ return file_id;
+}
+
+static NTSTATUS vfswrap_fstreaminfo(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct stream_struct *tmp_streams = NULL;
+ unsigned int num_streams = *pnum_streams;
+ struct stream_struct *streams = *pstreams;
+ NTSTATUS status;
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (fsp->fsp_flags.is_directory) {
+ /*
+ * No default streams on directories
+ */
+ goto done;
+ }
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (num_streams + 1 < 1) {
+ /* Integer wrap. */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ tmp_streams = talloc_realloc(mem_ctx,
+ streams,
+ struct stream_struct,
+ num_streams + 1);
+ if (tmp_streams == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tmp_streams[num_streams].name = talloc_strdup(tmp_streams, "::$DATA");
+ if (tmp_streams[num_streams].name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tmp_streams[num_streams].size = fsp->fsp_name->st.st_ex_size;
+ tmp_streams[num_streams].alloc_size = SMB_VFS_GET_ALLOC_SIZE(
+ handle->conn,
+ fsp,
+ &fsp->fsp_name->st);
+ num_streams += 1;
+
+ *pnum_streams = num_streams;
+ *pstreams = tmp_streams;
+ done:
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS vfswrap_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ /*
+ * Don't fall back to get_real_filename so callers can differentiate
+ * between a full directory scan and an actual case-insensitive stat.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static const char *vfswrap_connectpath(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ return handle->conn->connectpath;
+}
+
+static NTSTATUS vfswrap_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ SMB_ASSERT(plock->lock_flav == WINDOWS_LOCK);
+
+ /* Note: blr is not used in the default implementation. */
+ return brl_lock_windows_default(br_lck, plock);
+}
+
+static bool vfswrap_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ SMB_ASSERT(plock->lock_flav == WINDOWS_LOCK);
+
+ return brl_unlock_windows_default(br_lck, plock);
+}
+
+static bool vfswrap_strict_lock_check(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct lock_struct *plock)
+{
+ SMB_ASSERT(plock->lock_type == READ_LOCK ||
+ plock->lock_type == WRITE_LOCK);
+
+ return strict_lock_check_default(fsp, plock);
+}
+
+/* NT ACL operations. */
+
+static NTSTATUS vfswrap_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS result;
+
+ START_PROFILE(fget_nt_acl);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ result = posix_fget_nt_acl(fsp, security_info,
+ mem_ctx, ppdesc);
+ END_PROFILE(fget_nt_acl);
+ return result;
+}
+
+static NTSTATUS vfswrap_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd)
+{
+ NTSTATUS result;
+
+ START_PROFILE(fset_nt_acl);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ result = set_nt_acl(fsp, security_info_sent, psd);
+ END_PROFILE(fset_nt_acl);
+ return result;
+}
+
+static NTSTATUS vfswrap_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied)
+{
+ return NT_STATUS_OK; /* Nothing to do here ... */
+}
+
+static SMB_ACL_T vfswrap_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ return sys_acl_get_fd(handle, fsp, type, mem_ctx);
+}
+
+static int vfswrap_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ return sys_acl_set_fd(handle, fsp, type, theacl);
+}
+
+static int vfswrap_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ return sys_acl_delete_def_fd(handle, fsp);
+}
+
+/****************************************************************
+ Extended attribute operations.
+*****************************************************************/
+
+static ssize_t vfswrap_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ void *value,
+ size_t size)
+{
+ int fd = fsp_get_pathref_fd(fsp);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (!fsp->fsp_flags.is_pathref) {
+ return fgetxattr(fd, name, value, size);
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ struct sys_proc_fd_path_buf buf;
+
+ return getxattr(sys_proc_fd_path(fd, &buf), name, value, size);
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ return getxattr(fsp->fsp_name->base_name, name, value, size);
+}
+
+struct vfswrap_getxattrat_state {
+ struct tevent_context *ev;
+ struct vfs_handle_struct *handle;
+ files_struct *dir_fsp;
+ const struct smb_filename *smb_fname;
+
+ /*
+ * The following variables are talloced off "state" which is protected
+ * by a destructor and thus are guaranteed to be safe to be used in the
+ * job function in the worker thread.
+ */
+ char *name;
+ const char *xattr_name;
+ uint8_t *xattr_value;
+ struct security_unix_token *token;
+
+ ssize_t xattr_size;
+ struct vfs_aio_state vfs_aio_state;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+};
+
+static int vfswrap_getxattrat_state_destructor(
+ struct vfswrap_getxattrat_state *state)
+{
+ return -1;
+}
+
+static void vfswrap_getxattrat_do_sync(struct tevent_req *req);
+static void vfswrap_getxattrat_do_async(void *private_data);
+static void vfswrap_getxattrat_done(struct tevent_req *subreq);
+
+static struct tevent_req *vfswrap_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct vfswrap_getxattrat_state *state = NULL;
+ size_t max_threads = 0;
+ bool have_per_thread_cwd = false;
+ bool have_per_thread_creds = false;
+ bool do_async = false;
+
+ SMB_ASSERT(!is_named_stream(smb_fname));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfswrap_getxattrat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct vfswrap_getxattrat_state) {
+ .ev = ev,
+ .handle = handle,
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ };
+
+ max_threads = pthreadpool_tevent_max_threads(dir_fsp->conn->sconn->pool);
+ if (max_threads >= 1) {
+ /*
+ * We need a non sync threadpool!
+ */
+ have_per_thread_cwd = per_thread_cwd_supported();
+ }
+#ifdef HAVE_LINUX_THREAD_CREDENTIALS
+ have_per_thread_creds = true;
+#endif
+ if (have_per_thread_cwd && have_per_thread_creds) {
+ do_async = true;
+ }
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_getxattrat, profile_p,
+ state->profile_bytes, 0);
+
+ if (fsp_get_pathref_fd(dir_fsp) == -1) {
+ DBG_ERR("Need a valid directory fd\n");
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ if (alloc_hint > 0) {
+ state->xattr_value = talloc_zero_array(state,
+ uint8_t,
+ alloc_hint);
+ if (tevent_req_nomem(state->xattr_value, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (!do_async) {
+ vfswrap_getxattrat_do_sync(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Now allocate all parameters from a memory context that won't go away
+ * no matter what. These parameters will get used in threads and we
+ * can't reliably cancel threads, so all buffers passed to the threads
+ * must not be freed before all referencing threads terminate.
+ */
+
+ state->name = talloc_strdup(state, smb_fname->base_name);
+ if (tevent_req_nomem(state->name, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->xattr_name = talloc_strdup(state, xattr_name);
+ if (tevent_req_nomem(state->xattr_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * This is a hot codepath so at first glance one might think we should
+ * somehow optimize away the token allocation and do a
+ * talloc_reference() or similar black magic instead. But due to the
+ * talloc_stackframe pool per SMB2 request this should be a simple copy
+ * without a malloc in most cases.
+ */
+ if (geteuid() == sec_initial_uid()) {
+ state->token = root_unix_token(state);
+ } else {
+ state->token = copy_unix_token(
+ state,
+ dir_fsp->conn->session_info->unix_token);
+ }
+ if (tevent_req_nomem(state->token, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+
+ subreq = pthreadpool_tevent_job_send(
+ state,
+ ev,
+ dir_fsp->conn->sconn->pool,
+ vfswrap_getxattrat_do_async,
+ state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfswrap_getxattrat_done, req);
+
+ talloc_set_destructor(state, vfswrap_getxattrat_state_destructor);
+
+ return req;
+}
+
+static void vfswrap_getxattrat_do_sync(struct tevent_req *req)
+{
+ struct vfswrap_getxattrat_state *state = tevent_req_data(
+ req, struct vfswrap_getxattrat_state);
+
+ state->xattr_size = vfswrap_fgetxattr(state->handle,
+ state->smb_fname->fsp,
+ state->xattr_name,
+ state->xattr_value,
+ talloc_array_length(state->xattr_value));
+ if (state->xattr_size == -1) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static void vfswrap_getxattrat_do_async(void *private_data)
+{
+ struct vfswrap_getxattrat_state *state = talloc_get_type_abort(
+ private_data, struct vfswrap_getxattrat_state);
+ struct timespec start_time;
+ struct timespec end_time;
+ int ret;
+
+ PROFILE_TIMESTAMP(&start_time);
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes);
+
+ /*
+ * Here we simulate a getxattrat()
+ * call using fchdir();getxattr()
+ */
+
+ per_thread_cwd_activate();
+
+ /* Become the correct credential on this thread. */
+ ret = set_thread_credentials(state->token->uid,
+ state->token->gid,
+ (size_t)state->token->ngroups,
+ state->token->groups);
+ if (ret != 0) {
+ state->xattr_size = -1;
+ state->vfs_aio_state.error = errno;
+ goto end_profile;
+ }
+
+ state->xattr_size = vfswrap_fgetxattr(state->handle,
+ state->smb_fname->fsp,
+ state->xattr_name,
+ state->xattr_value,
+ talloc_array_length(state->xattr_value));
+ if (state->xattr_size == -1) {
+ state->vfs_aio_state.error = errno;
+ }
+
+end_profile:
+ PROFILE_TIMESTAMP(&end_time);
+ state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+}
+
+static void vfswrap_getxattrat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfswrap_getxattrat_state *state = tevent_req_data(
+ req, struct vfswrap_getxattrat_state);
+ int ret;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dir_fsp);
+ SMB_ASSERT(ok);
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+ SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes);
+ talloc_set_destructor(state, NULL);
+ if (ret != 0) {
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ vfswrap_getxattrat_do_sync(req);
+ return;
+ }
+
+ if (state->xattr_size == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+
+ if (state->xattr_value == NULL) {
+ /*
+ * The caller only wanted the size.
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * shrink the buffer to the returned size.
+ * (can't fail). It means NULL if size is 0.
+ */
+ state->xattr_value = talloc_realloc(state,
+ state->xattr_value,
+ uint8_t,
+ state->xattr_size);
+
+ tevent_req_done(req);
+}
+
+static ssize_t vfswrap_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct vfswrap_getxattrat_state *state = tevent_req_data(
+ req, struct vfswrap_getxattrat_state);
+ ssize_t xattr_size;
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *aio_state = state->vfs_aio_state;
+ xattr_size = state->xattr_size;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+static ssize_t vfswrap_flistxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, char *list, size_t size)
+{
+ int fd = fsp_get_pathref_fd(fsp);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (!fsp->fsp_flags.is_pathref) {
+ return flistxattr(fd, list, size);
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ struct sys_proc_fd_path_buf buf;
+
+ return listxattr(sys_proc_fd_path(fd, &buf), list, size);
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ return listxattr(fsp->fsp_name->base_name, list, size);
+}
+
+static int vfswrap_fremovexattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name)
+{
+ int fd = fsp_get_pathref_fd(fsp);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (!fsp->fsp_flags.is_pathref) {
+ return fremovexattr(fd, name);
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ struct sys_proc_fd_path_buf buf;
+
+ return removexattr(sys_proc_fd_path(fd, &buf), name);
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ return removexattr(fsp->fsp_name->base_name, name);
+}
+
+static int vfswrap_fsetxattr(struct vfs_handle_struct *handle, struct files_struct *fsp, const char *name, const void *value, size_t size, int flags)
+{
+ int fd = fsp_get_pathref_fd(fsp);
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (!fsp->fsp_flags.is_pathref) {
+ return fsetxattr(fd, name, value, size, flags);
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ struct sys_proc_fd_path_buf buf;
+
+ return setxattr(sys_proc_fd_path(fd, &buf),
+ name,
+ value,
+ size,
+ flags);
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ return setxattr(fsp->fsp_name->base_name, name, value, size, flags);
+}
+
+static bool vfswrap_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp)
+{
+ return false;
+}
+
+static bool vfswrap_is_offline(struct connection_struct *conn,
+ const struct smb_filename *fname)
+{
+ NTSTATUS status;
+ char *path;
+ bool offline = false;
+
+ if (ISDOT(fname->base_name) || ISDOTDOT(fname->base_name)) {
+ return false;
+ }
+
+ if (!lp_dmapi_support(SNUM(conn)) || !dmapi_have_session()) {
+#if defined(ENOTSUP)
+ errno = ENOTSUP;
+#endif
+ return false;
+ }
+
+ status = get_full_smb_filename(talloc_tos(), fname, &path);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return false;
+ }
+
+ offline = (dmapi_file_flags(path) & FILE_ATTRIBUTE_OFFLINE) != 0;
+
+ TALLOC_FREE(path);
+
+ return offline;
+}
+
+static NTSTATUS vfswrap_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie)
+{
+ return vfs_default_durable_cookie(fsp, mem_ctx, cookie);
+}
+
+static NTSTATUS vfswrap_durable_disconnect(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ return vfs_default_durable_disconnect(fsp, old_cookie, mem_ctx,
+ new_cookie);
+}
+
+static NTSTATUS vfswrap_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie)
+{
+ return vfs_default_durable_reconnect(handle->conn, smb1req, op,
+ old_cookie, mem_ctx,
+ fsp, new_cookie);
+}
+
+static struct vfs_fn_pointers vfs_default_fns = {
+ /* Disk operations */
+
+ .connect_fn = vfswrap_connect,
+ .disconnect_fn = vfswrap_disconnect,
+ .disk_free_fn = vfswrap_disk_free,
+ .get_quota_fn = vfswrap_get_quota,
+ .set_quota_fn = vfswrap_set_quota,
+ .get_shadow_copy_data_fn = vfswrap_get_shadow_copy_data,
+ .statvfs_fn = vfswrap_statvfs,
+ .fs_capabilities_fn = vfswrap_fs_capabilities,
+ .get_dfs_referrals_fn = vfswrap_get_dfs_referrals,
+ .create_dfs_pathat_fn = vfswrap_create_dfs_pathat,
+ .read_dfs_pathat_fn = vfswrap_read_dfs_pathat,
+ .snap_check_path_fn = vfswrap_snap_check_path,
+ .snap_create_fn = vfswrap_snap_create,
+ .snap_delete_fn = vfswrap_snap_delete,
+
+ /* Directory operations */
+
+ .fdopendir_fn = vfswrap_fdopendir,
+ .readdir_fn = vfswrap_readdir,
+ .freaddir_attr_fn = vfswrap_freaddir_attr,
+ .rewind_dir_fn = vfswrap_rewinddir,
+ .mkdirat_fn = vfswrap_mkdirat,
+ .closedir_fn = vfswrap_closedir,
+
+ /* File operations */
+
+ .openat_fn = vfswrap_openat,
+ .create_file_fn = vfswrap_create_file,
+ .close_fn = vfswrap_close,
+ .pread_fn = vfswrap_pread,
+ .pread_send_fn = vfswrap_pread_send,
+ .pread_recv_fn = vfswrap_pread_recv,
+ .pwrite_fn = vfswrap_pwrite,
+ .pwrite_send_fn = vfswrap_pwrite_send,
+ .pwrite_recv_fn = vfswrap_pwrite_recv,
+ .lseek_fn = vfswrap_lseek,
+ .sendfile_fn = vfswrap_sendfile,
+ .recvfile_fn = vfswrap_recvfile,
+ .renameat_fn = vfswrap_renameat,
+ .fsync_send_fn = vfswrap_fsync_send,
+ .fsync_recv_fn = vfswrap_fsync_recv,
+ .stat_fn = vfswrap_stat,
+ .fstat_fn = vfswrap_fstat,
+ .lstat_fn = vfswrap_lstat,
+ .fstatat_fn = vfswrap_fstatat,
+ .get_alloc_size_fn = vfswrap_get_alloc_size,
+ .unlinkat_fn = vfswrap_unlinkat,
+ .fchmod_fn = vfswrap_fchmod,
+ .fchown_fn = vfswrap_fchown,
+ .lchown_fn = vfswrap_lchown,
+ .chdir_fn = vfswrap_chdir,
+ .getwd_fn = vfswrap_getwd,
+ .fntimes_fn = vfswrap_fntimes,
+ .ftruncate_fn = vfswrap_ftruncate,
+ .fallocate_fn = vfswrap_fallocate,
+ .lock_fn = vfswrap_lock,
+ .filesystem_sharemode_fn = vfswrap_filesystem_sharemode,
+ .fcntl_fn = vfswrap_fcntl,
+ .linux_setlease_fn = vfswrap_linux_setlease,
+ .getlock_fn = vfswrap_getlock,
+ .symlinkat_fn = vfswrap_symlinkat,
+ .readlinkat_fn = vfswrap_readlinkat,
+ .linkat_fn = vfswrap_linkat,
+ .mknodat_fn = vfswrap_mknodat,
+ .realpath_fn = vfswrap_realpath,
+ .fchflags_fn = vfswrap_fchflags,
+ .file_id_create_fn = vfswrap_file_id_create,
+ .fs_file_id_fn = vfswrap_fs_file_id,
+ .fstreaminfo_fn = vfswrap_fstreaminfo,
+ .get_real_filename_at_fn = vfswrap_get_real_filename_at,
+ .connectpath_fn = vfswrap_connectpath,
+ .brl_lock_windows_fn = vfswrap_brl_lock_windows,
+ .brl_unlock_windows_fn = vfswrap_brl_unlock_windows,
+ .strict_lock_check_fn = vfswrap_strict_lock_check,
+ .translate_name_fn = vfswrap_translate_name,
+ .parent_pathname_fn = vfswrap_parent_pathname,
+ .fsctl_fn = vfswrap_fsctl,
+ .fset_dos_attributes_fn = vfswrap_fset_dos_attributes,
+ .get_dos_attributes_send_fn = vfswrap_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = vfswrap_get_dos_attributes_recv,
+ .fget_dos_attributes_fn = vfswrap_fget_dos_attributes,
+ .offload_read_send_fn = vfswrap_offload_read_send,
+ .offload_read_recv_fn = vfswrap_offload_read_recv,
+ .offload_write_send_fn = vfswrap_offload_write_send,
+ .offload_write_recv_fn = vfswrap_offload_write_recv,
+ .fget_compression_fn = vfswrap_fget_compression,
+ .set_compression_fn = vfswrap_set_compression,
+
+ /* NT ACL operations. */
+
+ .fget_nt_acl_fn = vfswrap_fget_nt_acl,
+ .fset_nt_acl_fn = vfswrap_fset_nt_acl,
+ .audit_file_fn = vfswrap_audit_file,
+
+ /* POSIX ACL operations. */
+
+ .sys_acl_get_fd_fn = vfswrap_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = vfswrap_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = vfswrap_sys_acl_delete_def_fd,
+
+ /* EA operations. */
+ .getxattrat_send_fn = vfswrap_getxattrat_send,
+ .getxattrat_recv_fn = vfswrap_getxattrat_recv,
+ .fgetxattr_fn = vfswrap_fgetxattr,
+ .flistxattr_fn = vfswrap_flistxattr,
+ .fremovexattr_fn = vfswrap_fremovexattr,
+ .fsetxattr_fn = vfswrap_fsetxattr,
+
+ /* aio operations */
+ .aio_force_fn = vfswrap_aio_force,
+
+ /* durable handle operations */
+ .durable_cookie_fn = vfswrap_durable_cookie,
+ .durable_disconnect_fn = vfswrap_durable_disconnect,
+ .durable_reconnect_fn = vfswrap_durable_reconnect,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_default_init(TALLOC_CTX *ctx)
+{
+ /*
+ * Here we need to implement every call!
+ *
+ * As this is the end of the vfs module chain.
+ */
+ smb_vfs_assert_all_fns(&vfs_default_fns, DEFAULT_VFS_MODULE_NAME);
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ DEFAULT_VFS_MODULE_NAME, &vfs_default_fns);
+}
+
+
diff --git a/source3/modules/vfs_default_quota.c b/source3/modules/vfs_default_quota.c
new file mode 100644
index 0000000..326eb8c
--- /dev/null
+++ b/source3/modules/vfs_default_quota.c
@@ -0,0 +1,236 @@
+/*
+ * Store default Quotas in a specified quota record
+ *
+ * Copyright (C) Stefan (metze) Metzmacher 2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module allows the default quota values,
+ * in the windows explorer GUI, to be stored on a samba server.
+ * The problem is that linux filesystems only store quotas
+ * for users and groups, but no default quotas.
+ *
+ * Samba returns NO_LIMIT as the default quotas by default
+ * and refuses to update them.
+ *
+ * With this module you can store the default quotas that are reported to
+ * a windows client, in the quota record of a user. By default the root user
+ * is taken because quota limits for root are typically not enforced.
+ *
+ * This module takes 2 parametric parameters in smb.conf:
+ * (the default prefix for them is 'default_quota',
+ * it can be overwrittem when you load the module in
+ * the 'vfs objects' parameter like this:
+ * vfs objects = default_quota:myprefix)
+ *
+ * "<myprefix>:uid" parameter takes a integer argument,
+ * it specifies the uid of the quota record, that will be taken for
+ * storing the default USER-quotas.
+ *
+ * - default value: '0' (for root user)
+ * - e.g.: default_quota:uid = 65534
+ *
+ * "<myprefix>:uid nolimit" parameter takes a boolean argument,
+ * it specifies if we should report the stored default quota values,
+ * also for the user record, or if you should just report NO_LIMIT
+ * to the windows client for the user specified by the "<prefix>:uid" parameter.
+ *
+ * - default value: yes (that means to report NO_LIMIT)
+ * - e.g.: default_quota:uid nolimit = no
+ *
+ * "<myprefix>:gid" parameter takes a integer argument,
+ * it's just like "<prefix>:uid" but for group quotas.
+ * (NOTE: group quotas are not supported from the windows explorer!)
+ *
+ * - default value: '0' (for root group)
+ * - e.g.: default_quota:gid = 65534
+ *
+ * "<myprefix>:gid nolimit" parameter takes a boolean argument,
+ * it's just like "<prefix>:uid nolimit" but for group quotas.
+ * (NOTE: group quotas are not supported from the windows explorer!)
+ *
+ * - default value: yes (that means to report NO_LIMIT)
+ * - e.g.: default_quota:uid nolimit = no
+ *
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#define DEFAULT_QUOTA_NAME "default_quota"
+
+#define DEFAULT_QUOTA_UID_DEFAULT 0
+#define DEFAULT_QUOTA_UID_NOLIMIT_DEFAULT True
+#define DEFAULT_QUOTA_GID_DEFAULT 0
+#define DEFAULT_QUOTA_GID_NOLIMIT_DEFAULT True
+
+#define DEFAULT_QUOTA_UID(handle) \
+ (uid_t)lp_parm_int(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"uid",DEFAULT_QUOTA_UID_DEFAULT)
+
+#define DEFAULT_QUOTA_UID_NOLIMIT(handle) \
+ lp_parm_bool(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"uid nolimit",DEFAULT_QUOTA_UID_NOLIMIT_DEFAULT)
+
+#define DEFAULT_QUOTA_GID(handle) \
+ (gid_t)lp_parm_int(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"gid",DEFAULT_QUOTA_GID_DEFAULT)
+
+#define DEFAULT_QUOTA_GID_NOLIMIT(handle) \
+ lp_parm_bool(SNUM((handle)->conn),DEFAULT_QUOTA_NAME,"gid nolimit",DEFAULT_QUOTA_GID_NOLIMIT_DEFAULT)
+
+static int default_quota_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq)
+{
+ int ret = -1;
+
+ if ((ret = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname,
+ qtype, id, dq)) != 0) {
+ return ret;
+ }
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ /* we use id.uid == 0 for default quotas */
+ if ((id.uid==DEFAULT_QUOTA_UID(handle)) &&
+ DEFAULT_QUOTA_UID_NOLIMIT(handle)) {
+ SMB_QUOTAS_SET_NO_LIMIT(dq);
+ }
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_QUOTA_TYPE:
+ /* we use id.gid == 0 for default quotas */
+ if ((id.gid==DEFAULT_QUOTA_GID(handle)) &&
+ DEFAULT_QUOTA_GID_NOLIMIT(handle)) {
+ SMB_QUOTAS_SET_NO_LIMIT(dq);
+ }
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ case SMB_USER_FS_QUOTA_TYPE:
+ {
+ unid_t qid;
+ uint32_t qflags = dq->qflags;
+ qid.uid = DEFAULT_QUOTA_UID(handle);
+ SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname,
+ SMB_USER_QUOTA_TYPE, qid, dq);
+ dq->qflags = qflags;
+ }
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ {
+ unid_t qid;
+ uint32_t qflags = dq->qflags;
+ qid.gid = DEFAULT_QUOTA_GID(handle);
+ SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname,
+ SMB_GROUP_QUOTA_TYPE,
+ qid, dq);
+ dq->qflags = qflags;
+ }
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ default:
+ errno = ENOSYS;
+ return -1;
+ break;
+ }
+
+ return ret;
+}
+
+static int default_quota_set_quota(vfs_handle_struct *handle, enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *dq)
+{
+ int ret = -1;
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ /* we use id.uid == 0 for default quotas */
+ if ((id.uid==DEFAULT_QUOTA_UID(handle)) &&
+ DEFAULT_QUOTA_UID_NOLIMIT(handle)) {
+ return -1;
+ }
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_QUOTA_TYPE:
+ /* we use id.gid == 0 for default quotas */
+ if ((id.gid==DEFAULT_QUOTA_GID(handle)) &&
+ DEFAULT_QUOTA_GID_NOLIMIT(handle)) {
+ return -1;
+ }
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ case SMB_USER_FS_QUOTA_TYPE:
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ default:
+ errno = ENOSYS;
+ return -1;
+ break;
+ }
+
+ if ((ret=SMB_VFS_NEXT_SET_QUOTA(handle, qtype, id, dq))!=0) {
+ return ret;
+ }
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_QUOTA_TYPE:
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ case SMB_USER_FS_QUOTA_TYPE:
+ {
+ unid_t qid;
+ qid.uid = DEFAULT_QUOTA_UID(handle);
+ ret = SMB_VFS_NEXT_SET_QUOTA(handle, SMB_USER_QUOTA_TYPE, qid, dq);
+ }
+ break;
+#ifdef HAVE_GROUP_QUOTA
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ {
+ unid_t qid;
+ qid.gid = DEFAULT_QUOTA_GID(handle);
+ ret = SMB_VFS_NEXT_SET_QUOTA(handle, SMB_GROUP_QUOTA_TYPE, qid, dq);
+ }
+ break;
+#endif /* HAVE_GROUP_QUOTA */
+ default:
+ errno = ENOSYS;
+ return -1;
+ break;
+ }
+
+ return ret;
+}
+
+static struct vfs_fn_pointers vfs_default_quota_fns = {
+ .get_quota_fn = default_quota_get_quota,
+ .set_quota_fn = default_quota_set_quota
+};
+
+static_decl_vfs;
+NTSTATUS vfs_default_quota_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, DEFAULT_QUOTA_NAME,
+ &vfs_default_quota_fns);
+}
diff --git a/source3/modules/vfs_delay_inject.c b/source3/modules/vfs_delay_inject.c
new file mode 100644
index 0000000..e0a881e
--- /dev/null
+++ b/source3/modules/vfs_delay_inject.c
@@ -0,0 +1,440 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba VFS module for delay injection in VFS calls
+ * Copyright (C) Ralph Boehme 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static void inject_delay(const char *vfs_func, vfs_handle_struct *handle)
+{
+ int delay;
+
+ delay = lp_parm_int(SNUM(handle->conn), "delay_inject", vfs_func, 0);
+ if (delay == 0) {
+ return;
+ }
+
+ DBG_DEBUG("Injected delay for [%s] of [%d] ms\n", vfs_func, delay);
+
+ smb_msleep(delay);
+}
+
+static int vfs_delay_inject_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ inject_delay("fntimes", handle);
+
+ return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+}
+
+struct vfs_delay_inject_pread_state {
+ struct tevent_context *ev;
+ struct vfs_handle_struct *handle;
+ struct files_struct *fsp;
+ void *data;
+ size_t n;
+ off_t offset;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void vfs_delay_inject_pread_wait_done(struct tevent_req *subreq);
+static void vfs_delay_inject_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *vfs_delay_inject_pread_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n,
+ off_t offset)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct vfs_delay_inject_pread_state *state = NULL;
+ int delay;
+ struct timeval delay_tv;
+
+ delay = lp_parm_int(
+ SNUM(handle->conn), "delay_inject", "pread_send", 0);
+ delay_tv = tevent_timeval_current_ofs(delay / 1000,
+ (delay * 1000) % 1000000);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_delay_inject_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct vfs_delay_inject_pread_state) {
+ .ev = ev,
+ .handle = handle,
+ .fsp = fsp,
+ .data = data,
+ .n = n,
+ .offset = offset,
+ };
+
+ if (delay == 0) {
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state,
+ state->ev,
+ state->handle,
+ state->fsp,
+ state->data,
+ state->n,
+ state->offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ vfs_delay_inject_pread_done,
+ req);
+ return req;
+ }
+
+ subreq = tevent_wakeup_send(state, ev, delay_tv);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_delay_inject_pread_wait_done, req);
+ return req;
+}
+
+
+static void vfs_delay_inject_pread_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_delay_inject_pread_state *state = tevent_req_data(
+ req, struct vfs_delay_inject_pread_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state,
+ state->ev,
+ state->handle,
+ state->fsp,
+ state->data,
+ state->n,
+ state->offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, vfs_delay_inject_pread_done, req);
+}
+
+static void vfs_delay_inject_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_delay_inject_pread_state *state = tevent_req_data(
+ req, struct vfs_delay_inject_pread_state);
+
+ state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ tevent_req_done(req);
+}
+
+static ssize_t vfs_delay_inject_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_delay_inject_pread_state *state = tevent_req_data(
+ req, struct vfs_delay_inject_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct vfs_delay_inject_pwrite_state {
+ struct tevent_context *ev;
+ struct vfs_handle_struct *handle;
+ struct files_struct *fsp;
+ const void *data;
+ size_t n;
+ off_t offset;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void vfs_delay_inject_pwrite_wait_done(struct tevent_req *subreq);
+static void vfs_delay_inject_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *vfs_delay_inject_pwrite_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n,
+ off_t offset)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct vfs_delay_inject_pwrite_state *state = NULL;
+ int delay;
+ struct timeval delay_tv;
+
+ delay = lp_parm_int(
+ SNUM(handle->conn), "delay_inject", "pwrite_send", 0);
+ delay_tv = tevent_timeval_current_ofs(delay / 1000,
+ (delay * 1000) % 1000000);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_delay_inject_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct vfs_delay_inject_pwrite_state) {
+ .ev = ev,
+ .handle = handle,
+ .fsp = fsp,
+ .data = data,
+ .n = n,
+ .offset = offset,
+ };
+
+ if (delay == 0) {
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state,
+ state->ev,
+ state->handle,
+ state->fsp,
+ state->data,
+ state->n,
+ state->offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ vfs_delay_inject_pwrite_done,
+ req);
+ return req;
+ }
+
+ subreq = tevent_wakeup_send(state, ev, delay_tv);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, vfs_delay_inject_pwrite_wait_done, req);
+ return req;
+}
+
+
+static void vfs_delay_inject_pwrite_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_delay_inject_pwrite_state *state = tevent_req_data(
+ req, struct vfs_delay_inject_pwrite_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state,
+ state->ev,
+ state->handle,
+ state->fsp,
+ state->data,
+ state->n,
+ state->offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, vfs_delay_inject_pwrite_done, req);
+}
+
+static void vfs_delay_inject_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_delay_inject_pwrite_state *state = tevent_req_data(
+ req, struct vfs_delay_inject_pwrite_state);
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ tevent_req_done(req);
+}
+
+static ssize_t vfs_delay_inject_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_delay_inject_pwrite_state *state = tevent_req_data(
+ req, struct vfs_delay_inject_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct vfs_delay_inject_brl_lock_state {
+ struct vfs_delay_inject_brl_lock_state *prev, *next;
+ struct files_struct *fsp;
+ struct GUID req_guid;
+ struct timeval delay_tv;
+ struct tevent_timer *delay_te;
+};
+
+static struct vfs_delay_inject_brl_lock_state *brl_lock_states;
+
+static int vfs_delay_inject_brl_lock_state_destructor(struct vfs_delay_inject_brl_lock_state *state)
+{
+ DLIST_REMOVE(brl_lock_states, state);
+ return 0;
+}
+
+static void vfs_delay_inject_brl_lock_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct vfs_delay_inject_brl_lock_state *state =
+ talloc_get_type_abort(private_data,
+ struct vfs_delay_inject_brl_lock_state);
+ NTSTATUS status;
+
+ TALLOC_FREE(state->delay_te);
+
+ status = share_mode_wakeup_waiters(state->fsp->file_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct file_id_buf idbuf;
+ DBG_ERR("share_mode_wakeup_waiters(%s) %s\n",
+ file_id_str_buf(state->fsp->file_id, &idbuf),
+ nt_errstr(status));
+ }
+}
+
+static NTSTATUS vfs_delay_inject_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ struct files_struct *fsp = brl_fsp(br_lck);
+ TALLOC_CTX *req_mem_ctx = brl_req_mem_ctx(br_lck);
+ const struct GUID *req_guid = brl_req_guid(br_lck);
+ struct vfs_delay_inject_brl_lock_state *state = NULL;
+ bool expired;
+
+ for (state = brl_lock_states; state != NULL; state = state->next) {
+ bool match;
+
+ match = GUID_equal(&state->req_guid, req_guid);
+ if (match) {
+ break;
+ }
+ }
+
+ if (state == NULL) {
+ int delay;
+ bool use_timer;
+
+ state = talloc_zero(req_mem_ctx,
+ struct vfs_delay_inject_brl_lock_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->fsp = fsp;
+ state->req_guid = *req_guid;
+
+ delay = lp_parm_int(SNUM(handle->conn),
+ "delay_inject", "brl_lock_windows", 0);
+ state->delay_tv = timeval_current_ofs_msec(delay);
+
+ use_timer = lp_parm_bool(SNUM(handle->conn),
+ "delay_inject", "brl_lock_windows_use_timer", true);
+
+ if (use_timer) {
+ state->delay_te = tevent_add_timer(
+ global_event_context(),
+ state,
+ state->delay_tv,
+ vfs_delay_inject_brl_lock_timer,
+ state);
+ if (state->delay_te == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ talloc_set_destructor(state,
+ vfs_delay_inject_brl_lock_state_destructor);
+ DLIST_ADD_END(brl_lock_states, state);
+ }
+
+ if (state->delay_te != NULL) {
+ plock->context.smblctx = 0;
+ return NT_STATUS_RETRY;
+ }
+
+ expired = timeval_expired(&state->delay_tv);
+ if (!expired) {
+ plock->context.smblctx = UINT64_MAX;
+ return NT_STATUS_RETRY;
+ }
+
+ TALLOC_FREE(state);
+
+ return SMB_VFS_NEXT_BRL_LOCK_WINDOWS(handle, br_lck, plock);
+}
+
+static bool vfs_delay_inject_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ return SMB_VFS_NEXT_BRL_UNLOCK_WINDOWS(handle, br_lck, plock);
+}
+
+static struct vfs_fn_pointers vfs_delay_inject_fns = {
+ .fntimes_fn = vfs_delay_inject_fntimes,
+ .pread_send_fn = vfs_delay_inject_pread_send,
+ .pread_recv_fn = vfs_delay_inject_pread_recv,
+ .pwrite_send_fn = vfs_delay_inject_pwrite_send,
+ .pwrite_recv_fn = vfs_delay_inject_pwrite_recv,
+
+ .brl_lock_windows_fn = vfs_delay_inject_brl_lock_windows,
+ .brl_unlock_windows_fn = vfs_delay_inject_brl_unlock_windows,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_delay_inject_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "delay_inject",
+ &vfs_delay_inject_fns);
+}
diff --git a/source3/modules/vfs_dfs_samba4.c b/source3/modules/vfs_dfs_samba4.c
new file mode 100644
index 0000000..8b4724f
--- /dev/null
+++ b/source3/modules/vfs_dfs_samba4.c
@@ -0,0 +1,162 @@
+/*
+ * VFS module to retrieve DFS referrals from AD
+ *
+ * Copyright (C) 2007, Stefan Metzmacher
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "system/filesys.h"
+#include "source3/include/msdfs.h"
+#include "librpc/gen_ndr/ndr_dfsblobs.h"
+#include "source4/lib/events/events.h"
+#include "source4/auth/session.h"
+#include "lib/param/param.h"
+#include "source4/dsdb/samdb/samdb.h"
+#include "dfs_server/dfs_server_ad.h"
+
+static int vfs_dfs_samba4_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_dfs_samba4_debug_level
+
+struct dfs_samba4_handle_data {
+ struct tevent_context *ev;
+ struct loadparm_context *lp_ctx;
+ struct ldb_context *sam_ctx;
+};
+
+static int dfs_samba4_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct dfs_samba4_handle_data *data;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ data = talloc_zero(handle->conn, struct dfs_samba4_handle_data);
+ if (!data) {
+ DEBUG(0, ("talloc_zero() failed\n"));
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ data->ev = s4_event_context_init(data);
+ if (!data->ev) {
+ DEBUG(0, ("s4_event_context_init failed\n"));
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ data->lp_ctx = loadparm_init_s3(data, loadparm_s3_helpers());
+ if (data->lp_ctx == NULL) {
+ DEBUG(0, ("loadparm_init_s3 failed\n"));
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ data->sam_ctx = samdb_connect(data,
+ data->ev,
+ data->lp_ctx,
+ system_session(data->lp_ctx),
+ NULL,
+ 0);
+ if (!data->sam_ctx) {
+ DEBUG(0, ("samdb_connect failed\n"));
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
+ struct dfs_samba4_handle_data,
+ return -1);
+
+ DEBUG(10,("dfs_samba4: connect to service[%s]\n",
+ service));
+
+ return 0;
+}
+
+static void dfs_samba4_disconnect(struct vfs_handle_struct *handle)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ DEBUG(10,("dfs_samba4_disconnect() connect to service[%s].\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn))));
+
+ SMB_VFS_NEXT_DISCONNECT(handle);
+}
+
+static NTSTATUS dfs_samba4_get_referrals(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r)
+{
+ struct dfs_samba4_handle_data *data;
+ NTSTATUS status;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, data,
+ struct dfs_samba4_handle_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ DEBUG(8, ("dfs_samba4: Requested DFS name: %s utf16-length: %u\n",
+ r->in.req.servername,
+ (unsigned int)strlen_m(r->in.req.servername)*2));
+
+ status = dfs_server_ad_get_referrals(data->lp_ctx,
+ data->sam_ctx,
+ handle->conn->sconn->remote_address,
+ r);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return SMB_VFS_NEXT_GET_DFS_REFERRALS(handle, r);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static struct vfs_fn_pointers vfs_dfs_samba4_fns = {
+ .connect_fn = dfs_samba4_connect,
+ .disconnect_fn = dfs_samba4_disconnect,
+ .get_dfs_referrals_fn = dfs_samba4_get_referrals,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_dfs_samba4_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "dfs_samba4",
+ &vfs_dfs_samba4_fns);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ vfs_dfs_samba4_debug_level = debug_add_class("dfs_samba4");
+ if (vfs_dfs_samba4_debug_level == -1) {
+ vfs_dfs_samba4_debug_level = DBGC_VFS;
+ DEBUG(0, ("vfs_dfs_samba4: Couldn't register custom debugging class!\n"));
+ } else {
+ DEBUG(10, ("vfs_dfs_samba4: Debug class number of 'fileid': %d\n",
+ vfs_dfs_samba4_debug_level));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_dirsort.c b/source3/modules/vfs_dirsort.c
new file mode 100644
index 0000000..c4baf81
--- /dev/null
+++ b/source3/modules/vfs_dirsort.c
@@ -0,0 +1,267 @@
+/*
+ * VFS module to provide a sorted directory list.
+ *
+ * Copyright (C) Andy Kelk (andy@mopoke.co.uk), 2009
+ *
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+
+static int compare_dirent (const struct dirent *da, const struct dirent *db)
+{
+ return strcasecmp_m(da->d_name, db->d_name);
+}
+
+struct dirsort_privates {
+ struct dirsort_privates *prev, *next;
+ long pos;
+ struct dirent *directory_list;
+ unsigned int number_of_entries;
+ struct timespec mtime;
+ DIR *source_directory;
+ files_struct *fsp; /* If open via FDOPENDIR. */
+ struct smb_filename *smb_fname; /* If open via OPENDIR */
+};
+
+static bool get_sorted_dir_mtime(vfs_handle_struct *handle,
+ struct dirsort_privates *data,
+ struct timespec *ret_mtime)
+{
+ int ret;
+ struct timespec mtime;
+ NTSTATUS status;
+
+ if (data->fsp) {
+ status = vfs_stat_fsp(data->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ mtime = data->fsp->fsp_name->st.st_ex_mtime;
+ } else {
+ ret = SMB_VFS_STAT(handle->conn, data->smb_fname);
+ if (ret == -1) {
+ return false;
+ }
+ mtime = data->smb_fname->st.st_ex_mtime;
+ }
+
+ *ret_mtime = mtime;
+
+ return true;
+}
+
+static bool open_and_sort_dir(vfs_handle_struct *handle,
+ struct dirsort_privates *data)
+{
+ uint32_t total_count = 0;
+ /* This should be enough for most use cases */
+ uint32_t dirent_allocated = 64;
+ struct dirent *dp;
+
+ data->number_of_entries = 0;
+
+ if (get_sorted_dir_mtime(handle, data, &data->mtime) == false) {
+ return false;
+ }
+
+ dp = SMB_VFS_NEXT_READDIR(handle, data->fsp, data->source_directory);
+ if (dp == NULL) {
+ return false;
+ }
+
+ /* Set up an array and read the directory entries into it */
+ TALLOC_FREE(data->directory_list); /* destroy previous cache if needed */
+ data->directory_list = talloc_zero_array(data,
+ struct dirent,
+ dirent_allocated);
+ if (data->directory_list == NULL) {
+ return false;
+ }
+
+ do {
+ if (total_count >= dirent_allocated) {
+ struct dirent *dlist;
+
+ /*
+ * Be memory friendly.
+ *
+ * We should not double the amount of memory. With a lot
+ * of files we reach easily 50MB, and doubling will
+ * get much bigger just for a few files more.
+ *
+ * For 200k files this means 50 memory reallocations.
+ */
+ dirent_allocated += 4096;
+
+ dlist = talloc_realloc(data,
+ data->directory_list,
+ struct dirent,
+ dirent_allocated);
+ if (dlist == NULL) {
+ break;
+ }
+ data->directory_list = dlist;
+ }
+ data->directory_list[total_count] = *dp;
+
+ total_count++;
+ dp = SMB_VFS_NEXT_READDIR(handle,
+ data->fsp,
+ data->source_directory);
+ } while (dp != NULL);
+
+ data->number_of_entries = total_count;
+
+ /* Sort the directory entries by name */
+ TYPESAFE_QSORT(data->directory_list, data->number_of_entries, compare_dirent);
+ return true;
+}
+
+static DIR *dirsort_fdopendir(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr)
+{
+ struct dirsort_privates *list_head = NULL;
+ struct dirsort_privates *data = NULL;
+
+ if (SMB_VFS_HANDLE_TEST_DATA(handle)) {
+ /* Find the list head of all open directories. */
+ SMB_VFS_HANDLE_GET_DATA(handle, list_head, struct dirsort_privates,
+ return NULL);
+ }
+
+ /* set up our private data about this directory */
+ data = talloc_zero(handle->conn, struct dirsort_privates);
+ if (!data) {
+ return NULL;
+ }
+
+ data->fsp = fsp;
+
+ /* Open the underlying directory and count the number of entries */
+ data->source_directory = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask,
+ attr);
+
+ if (data->source_directory == NULL) {
+ TALLOC_FREE(data);
+ return NULL;
+ }
+
+ if (!open_and_sort_dir(handle, data)) {
+ SMB_VFS_NEXT_CLOSEDIR(handle,data->source_directory);
+ TALLOC_FREE(data);
+ /* fd is now closed. */
+ fsp_set_fd(fsp, -1);
+ return NULL;
+ }
+
+ /* Add to the private list of all open directories. */
+ DLIST_ADD(list_head, data);
+ SMB_VFS_HANDLE_SET_DATA(handle, list_head, NULL,
+ struct dirsort_privates, return NULL);
+
+ return data->source_directory;
+}
+
+static struct dirent *dirsort_readdir(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ struct dirsort_privates *data = NULL;
+ struct timespec current_mtime;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates,
+ return NULL);
+
+ while(data && (data->source_directory != dirp)) {
+ data = data->next;
+ }
+ if (data == NULL) {
+ return NULL;
+ }
+
+ if (get_sorted_dir_mtime(handle, data, &current_mtime) == false) {
+ return NULL;
+ }
+
+ /* throw away cache and re-read the directory if we've changed */
+ if (timespec_compare(&current_mtime, &data->mtime)) {
+ SMB_VFS_NEXT_REWINDDIR(handle, data->source_directory);
+ open_and_sort_dir(handle, data);
+ }
+
+ if (data->pos >= data->number_of_entries) {
+ return NULL;
+ }
+
+ return &data->directory_list[data->pos++];
+}
+
+static void dirsort_rewinddir(vfs_handle_struct *handle, DIR *dirp)
+{
+ struct dirsort_privates *data = NULL;
+ SMB_VFS_HANDLE_GET_DATA(handle, data, struct dirsort_privates, return);
+
+ /* Find the entry holding dirp. */
+ while(data && (data->source_directory != dirp)) {
+ data = data->next;
+ }
+ if (data == NULL) {
+ return;
+ }
+ data->pos = 0;
+}
+
+static int dirsort_closedir(vfs_handle_struct *handle, DIR *dirp)
+{
+ struct dirsort_privates *list_head = NULL;
+ struct dirsort_privates *data = NULL;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, list_head, struct dirsort_privates, return -1);
+ /* Find the entry holding dirp. */
+ for(data = list_head; data && (data->source_directory != dirp); data = data->next) {
+ ;
+ }
+ if (data == NULL) {
+ return -1;
+ }
+ /* Remove from the list and re-store the list head. */
+ DLIST_REMOVE(list_head, data);
+ SMB_VFS_HANDLE_SET_DATA(handle, list_head, NULL,
+ struct dirsort_privates, return -1);
+
+ ret = SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
+ TALLOC_FREE(data);
+ return ret;
+}
+
+static struct vfs_fn_pointers vfs_dirsort_fns = {
+ .fdopendir_fn = dirsort_fdopendir,
+ .readdir_fn = dirsort_readdir,
+ .rewind_dir_fn = dirsort_rewinddir,
+ .closedir_fn = dirsort_closedir,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_dirsort_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "dirsort",
+ &vfs_dirsort_fns);
+}
diff --git a/source3/modules/vfs_error_inject.c b/source3/modules/vfs_error_inject.c
new file mode 100644
index 0000000..529504f
--- /dev/null
+++ b/source3/modules/vfs_error_inject.c
@@ -0,0 +1,219 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba VFS module for error injection in VFS calls
+ * Copyright (C) Christof Schmitt 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+struct unix_error_map {
+ const char *err_str;
+ int error;
+} unix_error_map_array[] = {
+ { "ESTALE", ESTALE },
+ { "EBADF", EBADF },
+ { "EINTR", EINTR },
+ { "EACCES", EACCES },
+ { "EROFS", EROFS },
+};
+
+static int find_unix_error_from_string(const char *err_str)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(unix_error_map_array); i++) {
+ struct unix_error_map *m = &unix_error_map_array[i];
+
+ if (strequal(err_str, m->err_str)) {
+ return m->error;
+ }
+ }
+
+ return 0;
+}
+
+static int inject_unix_error(const char *vfs_func, vfs_handle_struct *handle)
+{
+ const char *err_str;
+ int error;
+
+ err_str = lp_parm_const_string(SNUM(handle->conn),
+ "error_inject", vfs_func, NULL);
+ if (err_str == NULL) {
+ return 0;
+ }
+
+ error = find_unix_error_from_string(err_str);
+ if (error != 0) {
+ DBG_WARNING("Returning error %s for VFS function %s\n",
+ err_str, vfs_func);
+ return error;
+ }
+
+ if (strequal(err_str, "panic")) {
+ DBG_ERR("Panic in VFS function %s\n", vfs_func);
+ smb_panic("error_inject");
+ }
+
+ DBG_ERR("Unknown error inject %s requested "
+ "for vfs function %s\n", err_str, vfs_func);
+
+ return 0;
+}
+
+static int vfs_error_inject_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int error;
+
+ error = inject_unix_error("chdir", handle);
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+}
+
+static ssize_t vfs_error_inject_pwrite(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const void *data,
+ size_t n,
+ off_t offset)
+{
+ int error;
+
+ error = inject_unix_error("pwrite", handle);
+ if (error != 0) {
+ errno = error;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+}
+
+static int vfs_error_inject_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int error = inject_unix_error("openat", handle);
+ int create_error = inject_unix_error("openat_create", handle);
+ int dirfsp_flags = (O_NOFOLLOW|O_DIRECTORY);
+ bool return_error;
+
+#ifdef O_PATH
+ dirfsp_flags |= O_PATH;
+#else
+#ifdef O_SEARCH
+ dirfsp_flags |= O_SEARCH;
+#endif
+#endif
+
+ if ((create_error != 0) && (how->flags & O_CREAT)) {
+ struct stat_ex st = {
+ .st_ex_nlink = 0,
+ };
+ int ret;
+
+ ret = SMB_VFS_FSTATAT(handle->conn,
+ dirfsp,
+ smb_fname,
+ &st,
+ AT_SYMLINK_NOFOLLOW);
+
+ if ((ret == -1) && (errno == ENOENT)) {
+ errno = create_error;
+ return -1;
+ }
+ }
+
+ return_error = (error != 0);
+ return_error &= !fsp->fsp_flags.is_pathref;
+ return_error &= ((how->flags & dirfsp_flags) != dirfsp_flags);
+
+ if (return_error) {
+ errno = error;
+ return -1;
+ }
+ return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+}
+
+static int vfs_error_inject_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *parent_fname = NULL;
+ int error = inject_unix_error("unlinkat", handle);
+ int ret;
+ NTSTATUS status;
+
+ if (error == 0) {
+ return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags);
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ status = SMB_VFS_PARENT_PATHNAME(handle->conn,
+ full_fname, /* TALLOC_CTX. */
+ full_fname,
+ &parent_fname,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(full_fname);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ ret = SMB_VFS_STAT(handle->conn, parent_fname);
+ if (ret != 0) {
+ TALLOC_FREE(full_fname);
+ return -1;
+ }
+
+ if (parent_fname->st.st_ex_uid == get_current_uid(dirfsp->conn)) {
+ return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags);
+ }
+
+ errno = error;
+ return -1;
+}
+
+static struct vfs_fn_pointers vfs_error_inject_fns = {
+ .chdir_fn = vfs_error_inject_chdir,
+ .pwrite_fn = vfs_error_inject_pwrite,
+ .openat_fn = vfs_error_inject_openat,
+ .unlinkat_fn = vfs_error_inject_unlinkat,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_error_inject_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "error_inject",
+ &vfs_error_inject_fns);
+}
diff --git a/source3/modules/vfs_expand_msdfs.c b/source3/modules/vfs_expand_msdfs.c
new file mode 100644
index 0000000..503ee84
--- /dev/null
+++ b/source3/modules/vfs_expand_msdfs.c
@@ -0,0 +1,270 @@
+/*
+ * Expand msdfs targets based on client IP
+ *
+ * Copyright (C) Volker Lendecke, 2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "smbd/globals.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "msdfs.h"
+#include "source3/lib/substitute.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+/**********************************************************
+ Under mapfile we expect a table of the following format:
+
+ IP-Prefix whitespace expansion
+
+ For example:
+ 192.168.234 local.samba.org
+ 192.168 remote.samba.org
+ default.samba.org
+
+ This is to redirect a DFS client to a host close to it.
+***********************************************************/
+
+static char *read_target_host(TALLOC_CTX *ctx, const char *mapfile,
+ const char *clientaddr)
+{
+ FILE *f;
+ char buf[1024];
+ char *space = buf;
+ bool found = false;
+
+ f = fopen(mapfile, "r");
+
+ if (f == NULL) {
+ DEBUG(0,("can't open IP map %s. Error %s\n",
+ mapfile, strerror(errno) ));
+ return NULL;
+ }
+
+ DEBUG(10, ("Scanning mapfile [%s]\n", mapfile));
+
+ while (fgets(buf, sizeof(buf), f) != NULL) {
+
+ if ((strlen(buf) > 0) && (buf[strlen(buf)-1] == '\n'))
+ buf[strlen(buf)-1] = '\0';
+
+ DEBUG(10, ("Scanning line [%s]\n", buf));
+
+ space = strchr_m(buf, ' ');
+
+ if (space == NULL) {
+ DEBUG(0, ("Ignoring invalid line %s\n", buf));
+ continue;
+ }
+
+ *space = '\0';
+
+ if (strncmp(clientaddr, buf, strlen(buf)) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ fclose(f);
+
+ if (!found) {
+ return NULL;
+ }
+
+ space += 1;
+
+ while (isspace(*space))
+ space += 1;
+
+ return talloc_strdup(ctx, space);
+}
+
+/**********************************************************
+
+ Expand the msdfs target host using read_target_host
+ explained above. The syntax used in the msdfs link is
+
+ msdfs:@table-filename@/share
+
+ Everything between and including the two @-signs is
+ replaced by the substitution string found in the table
+ described above.
+
+***********************************************************/
+
+static char *expand_msdfs_target(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ char *target)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *mapfilename = NULL;
+ char *filename_start = strchr_m(target, '@');
+ char *filename_end = NULL;
+ int filename_len = 0;
+ char *targethost = NULL;
+ char *new_target = NULL;
+ char *raddr;
+
+ if (filename_start == NULL) {
+ DEBUG(10, ("No filename start in %s\n", target));
+ return NULL;
+ }
+
+ filename_end = strchr_m(filename_start+1, '@');
+
+ if (filename_end == NULL) {
+ DEBUG(10, ("No filename end in %s\n", target));
+ return NULL;
+ }
+
+ filename_len = PTR_DIFF(filename_end, filename_start+1);
+ mapfilename = talloc_strdup(ctx, filename_start+1);
+ if (!mapfilename) {
+ return NULL;
+ }
+ mapfilename[filename_len] = '\0';
+
+ /*
+ * dfs links returned have had '/' characters replaced with '\'.
+ * Return them to '/' so we can have absolute path mapfilenames.
+ */
+ string_replace(mapfilename, '\\', '/');
+
+ DEBUG(10, ("Expanding from table [%s]\n", mapfilename));
+
+ raddr = tsocket_address_inet_addr_string(conn->sconn->remote_address,
+ ctx);
+ if (raddr == NULL) {
+ return NULL;
+ }
+
+ targethost = read_target_host(ctx, mapfilename, raddr);
+ if (targethost == NULL) {
+ DEBUG(1, ("Could not expand target host from file %s\n",
+ mapfilename));
+ return NULL;
+ }
+
+ targethost = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ targethost);
+
+ DEBUG(10, ("Expanded targethost to %s\n", targethost));
+
+ /* Replace the part between '@...@' */
+ *filename_start = '\0';
+ new_target = talloc_asprintf(ctx,
+ "%s%s%s",
+ target,
+ targethost,
+ filename_end+1);
+ if (!new_target) {
+ return NULL;
+ }
+
+ DEBUG(10, ("New DFS target: %s\n", new_target));
+ return new_target;
+}
+
+static NTSTATUS expand_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ NTSTATUS status;
+ size_t i;
+ struct referral *reflist = NULL;
+ size_t count = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /*
+ * Always call the NEXT function first, then
+ * modify the return if needed.
+ */
+ status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle,
+ mem_ctx,
+ dirfsp,
+ smb_fname,
+ ppreflist,
+ preferral_count);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * This function can be called to check if a pathname
+ * is an MSDFS link, but not return the values of it.
+ * In this case ppreflist and preferral_count are NULL,
+ * so don't bother trying to look at any returns.
+ */
+ if (ppreflist == NULL || preferral_count == NULL) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We are always returning the values returned
+ * returned by the NEXT call, but we might mess
+ * with the reflist[i].alternate_path values,
+ * so use local pointers to minimise indirections.
+ */
+ count = *preferral_count;
+ reflist = *ppreflist;
+
+ for (i = 0; i < count; i++) {
+ if (strchr_m(reflist[i].alternate_path, '@') != NULL) {
+ char *new_altpath = expand_msdfs_target(frame,
+ handle->conn,
+ reflist[i].alternate_path);
+ if (new_altpath == NULL) {
+ TALLOC_FREE(*ppreflist);
+ *preferral_count = 0;
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ reflist[i].alternate_path = talloc_move(reflist,
+ &new_altpath);
+ }
+ }
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static struct vfs_fn_pointers vfs_expand_msdfs_fns = {
+ .read_dfs_pathat_fn = expand_read_dfs_pathat,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_expand_msdfs_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "expand_msdfs",
+ &vfs_expand_msdfs_fns);
+}
diff --git a/source3/modules/vfs_extd_audit.c b/source3/modules/vfs_extd_audit.c
new file mode 100644
index 0000000..ea784ff
--- /dev/null
+++ b/source3/modules/vfs_extd_audit.c
@@ -0,0 +1,418 @@
+/*
+ * Auditing VFS module for samba. Log selected file operations to syslog
+ * facility.
+ *
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) John H Terpstra, 2003
+ * Copyright (C) Stefan (metze) Metzmacher, 2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/syslog.h"
+#include "smbd/smbd.h"
+#include "lib/param/loadparm.h"
+
+static int vfs_extd_audit_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_extd_audit_debug_level
+
+static int audit_syslog_facility(vfs_handle_struct *handle)
+{
+ static const struct enum_list enum_log_facilities[] = {
+#ifdef LOG_AUTH
+ { LOG_AUTH, "AUTH" },
+#endif
+#ifdef LOG_AUTHPRIV
+ { LOG_AUTHPRIV, "AUTHPRIV" },
+#endif
+#ifdef LOG_AUDIT
+ { LOG_AUDIT, "AUDIT" },
+#endif
+#ifdef LOG_CONSOLE
+ { LOG_CONSOLE, "CONSOLE" },
+#endif
+#ifdef LOG_CRON
+ { LOG_CRON, "CRON" },
+#endif
+#ifdef LOG_DAEMON
+ { LOG_DAEMON, "DAEMON" },
+#endif
+#ifdef LOG_FTP
+ { LOG_FTP, "FTP" },
+#endif
+#ifdef LOG_INSTALL
+ { LOG_INSTALL, "INSTALL" },
+#endif
+#ifdef LOG_KERN
+ { LOG_KERN, "KERN" },
+#endif
+#ifdef LOG_LAUNCHD
+ { LOG_LAUNCHD, "LAUNCHD" },
+#endif
+#ifdef LOG_LFMT
+ { LOG_LFMT, "LFMT" },
+#endif
+#ifdef LOG_LPR
+ { LOG_LPR, "LPR" },
+#endif
+#ifdef LOG_MAIL
+ { LOG_MAIL, "MAIL" },
+#endif
+#ifdef LOG_MEGASAFE
+ { LOG_MEGASAFE, "MEGASAFE" },
+#endif
+#ifdef LOG_NETINFO
+ { LOG_NETINFO, "NETINFO" },
+#endif
+#ifdef LOG_NEWS
+ { LOG_NEWS, "NEWS" },
+#endif
+#ifdef LOG_NFACILITIES
+ { LOG_NFACILITIES, "NFACILITIES" },
+#endif
+#ifdef LOG_NTP
+ { LOG_NTP, "NTP" },
+#endif
+#ifdef LOG_RAS
+ { LOG_RAS, "RAS" },
+#endif
+#ifdef LOG_REMOTEAUTH
+ { LOG_REMOTEAUTH, "REMOTEAUTH" },
+#endif
+#ifdef LOG_SECURITY
+ { LOG_SECURITY, "SECURITY" },
+#endif
+#ifdef LOG_SYSLOG
+ { LOG_SYSLOG, "SYSLOG" },
+#endif
+#ifdef LOG_USER
+ { LOG_USER, "USER" },
+#endif
+#ifdef LOG_UUCP
+ { LOG_UUCP, "UUCP" },
+#endif
+ { LOG_LOCAL0, "LOCAL0" },
+ { LOG_LOCAL1, "LOCAL1" },
+ { LOG_LOCAL2, "LOCAL2" },
+ { LOG_LOCAL3, "LOCAL3" },
+ { LOG_LOCAL4, "LOCAL4" },
+ { LOG_LOCAL5, "LOCAL5" },
+ { LOG_LOCAL6, "LOCAL6" },
+ { LOG_LOCAL7, "LOCAL7" },
+ { -1, NULL }
+ };
+
+ int facility;
+
+ facility = lp_parm_enum(SNUM(handle->conn), "extd_audit", "facility", enum_log_facilities, LOG_USER);
+
+ return facility;
+}
+
+
+static int audit_syslog_priority(vfs_handle_struct *handle)
+{
+ static const struct enum_list enum_log_priorities[] = {
+ { LOG_EMERG, "EMERG" },
+ { LOG_ALERT, "ALERT" },
+ { LOG_CRIT, "CRIT" },
+ { LOG_ERR, "ERR" },
+ { LOG_WARNING, "WARNING" },
+ { LOG_NOTICE, "NOTICE" },
+ { LOG_INFO, "INFO" },
+ { LOG_DEBUG, "DEBUG" },
+ { -1, NULL }
+ };
+
+ int priority;
+
+ priority = lp_parm_enum(SNUM(handle->conn), "extd_audit", "priority",
+ enum_log_priorities, LOG_NOTICE);
+ if (priority == -1) {
+ priority = LOG_WARNING;
+ }
+
+ return priority;
+}
+
+/* Implementation of vfs_ops. Pass everything on to the default
+ operation but log event first. */
+
+static int audit_connect(vfs_handle_struct *handle, const char *svc, const char *user)
+{
+ int result = SMB_VFS_NEXT_CONNECT(handle, svc, user);
+
+ if (result < 0) {
+ return result;
+ }
+
+ openlog("smbd_audit", LOG_PID, audit_syslog_facility(handle));
+
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle),
+ "connect to service %s by user %s\n",
+ svc, user);
+ }
+ DEBUG(10, ("Connected to service %s as user %s\n",
+ svc, user));
+
+ return 0;
+}
+
+static void audit_disconnect(vfs_handle_struct *handle)
+{
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle), "disconnected\n");
+ }
+ DEBUG(10, ("Disconnected from VFS module extd_audit\n"));
+ SMB_VFS_NEXT_DISCONNECT(handle);
+
+ return;
+}
+
+static int audit_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle), "mkdirat %s %s%s\n",
+ full_fname->base_name,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+ }
+ DEBUG(0, ("vfs_extd_audit: mkdirat %s %s %s\n",
+ full_fname->base_name,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : ""));
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int audit_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle),
+ "openat %s/%s (fd %d) %s%s%s\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname->base_name,
+ ret,
+ ((how->flags & O_WRONLY) || (how->flags & O_RDWR)) ?
+ "for writing " : "",
+ (ret < 0) ? "failed: " : "",
+ (ret < 0) ? strerror(errno) : "");
+ }
+ DEBUG(2, ("vfs_extd_audit: open %s/%s %s %s\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname),
+ (ret < 0) ? "failed: " : "",
+ (ret < 0) ? strerror(errno) : ""));
+
+ return ret;
+}
+
+static int audit_close(vfs_handle_struct *handle, files_struct *fsp)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle), "close fd %d %s%s\n",
+ fsp_get_pathref_fd(fsp),
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+ }
+ DEBUG(2, ("vfs_extd_audit: close fd %d %s %s\n",
+ fsp_get_pathref_fd(fsp),
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : ""));
+
+ return result;
+}
+
+static int audit_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+ int result;
+ int saved_errno = 0;
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ TALLOC_FREE(full_fname_src);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ if (result == -1) {
+ saved_errno = errno;
+ }
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle), "renameat %s -> %s %s%s\n",
+ full_fname_src->base_name,
+ full_fname_dst->base_name,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(saved_errno) : "");
+ }
+ DEBUG(1, ("vfs_extd_audit: renameat old: %s newname: %s %s %s\n",
+ smb_fname_str_dbg(full_fname_src),
+ smb_fname_str_dbg(full_fname_dst),
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(saved_errno) : ""));
+
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+
+ if (result == -1) {
+ errno = saved_errno;
+ }
+ return result;
+}
+
+static int audit_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle), "unlinkat %s %s%s\n",
+ full_fname->base_name,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+ }
+ DBG_ERR("unlinkat %s %s %s\n",
+ smb_fname_str_dbg(full_fname),
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int audit_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+
+ if (lp_syslog() > 0) {
+ syslog(audit_syslog_priority(handle), "fchmod %s mode 0x%x %s%s\n",
+ fsp->fsp_name->base_name, mode,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : "");
+ }
+ DEBUG(1, ("vfs_extd_audit: fchmod %s mode 0x%x %s %s\n",
+ fsp_str_dbg(fsp), (unsigned int)mode,
+ (result < 0) ? "failed: " : "",
+ (result < 0) ? strerror(errno) : ""));
+
+ return result;
+}
+
+static struct vfs_fn_pointers vfs_extd_audit_fns = {
+ .connect_fn = audit_connect,
+ .disconnect_fn = audit_disconnect,
+ .mkdirat_fn = audit_mkdirat,
+ .openat_fn = audit_openat,
+ .close_fn = audit_close,
+ .renameat_fn = audit_renameat,
+ .unlinkat_fn = audit_unlinkat,
+ .fchmod_fn = audit_fchmod,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_extd_audit_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "extd_audit", &vfs_extd_audit_fns);
+
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ vfs_extd_audit_debug_level = debug_add_class("extd_audit");
+ if (vfs_extd_audit_debug_level == -1) {
+ vfs_extd_audit_debug_level = DBGC_VFS;
+ DEBUG(0, ("vfs_extd_audit: Couldn't register custom debugging class!\n"));
+ } else {
+ DEBUG(10, ("vfs_extd_audit: Debug class number of 'extd_audit': %d\n", vfs_extd_audit_debug_level));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_fake_acls.c b/source3/modules/vfs_fake_acls.c
new file mode 100644
index 0000000..fefe6c5
--- /dev/null
+++ b/source3/modules/vfs_fake_acls.c
@@ -0,0 +1,704 @@
+/*
+ * Fake ACLs VFS module. Implements passthrough operation of all VFS
+ * calls to disk functions, except for file ownership and ACLs, which
+ * are stored in xattrs.
+ *
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) Andrew Bartlett, 2002,2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "auth.h"
+#include "librpc/gen_ndr/ndr_smb_acl.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#define FAKE_UID "system.fake_uid"
+#define FAKE_GID "system.fake_gid"
+#define FAKE_ACL_ACCESS_XATTR "system.fake_access_acl"
+#define FAKE_ACL_DEFAULT_XATTR "system.fake_default_acl"
+
+struct in_pathref_data {
+ bool calling_pathref_fsp;
+};
+
+static int fake_acls_fuid(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uid_t *uid)
+{
+ ssize_t size;
+ uint8_t uid_buf[4];
+
+ size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_UID, uid_buf, sizeof(uid_buf));
+ if (size == -1 && errno == ENOATTR) {
+ return 0;
+ }
+ if (size != 4) {
+ return -1;
+ }
+ *uid = IVAL(uid_buf, 0);
+ return 0;
+}
+
+static int fake_acls_fgid(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uid_t *gid)
+{
+ ssize_t size;
+ uint8_t gid_buf[4];
+
+ size = SMB_VFS_NEXT_FGETXATTR(handle, fsp, FAKE_GID, gid_buf, sizeof(gid_buf));
+ if (size == -1 && errno == ENOATTR) {
+ return 0;
+ }
+ if (size != 4) {
+ return -1;
+ }
+ *gid = IVAL(gid_buf, 0);
+ return 0;
+}
+
+static int fake_acls_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int ret = -1;
+ struct in_pathref_data *prd = NULL;
+ struct smb_filename *smb_fname_cp = NULL;
+ struct files_struct *fsp = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ prd,
+ struct in_pathref_data,
+ return -1);
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (smb_fname->fsp != NULL) {
+ fsp = metadata_fsp(smb_fname->fsp);
+ } else {
+ NTSTATUS status;
+
+ /*
+ * Ensure openat_pathref_fsp()
+ * can't recurse into fake_acls_stat().
+ * openat_pathref_fsp() doesn't care
+ * about the uid/gid values, it only
+ * wants a valid/invalid stat answer
+ * and we know smb_fname exists as
+ * the SMB_VFS_NEXT_STAT() returned
+ * zero above.
+ */
+ if (prd->calling_pathref_fsp) {
+ return 0;
+ }
+
+ /*
+ * openat_pathref_fsp() expects a talloc'ed
+ * smb_filename. stat can be passed a struct
+ * from the stack. Make a talloc'ed copy
+ * so openat_pathref_fsp() can add its
+ * destructor.
+ */
+ smb_fname_cp = cp_smb_filename(talloc_tos(),
+ smb_fname);
+ if (smb_fname_cp == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Recursion guard. */
+ prd->calling_pathref_fsp = true;
+ status = openat_pathref_fsp(handle->conn->cwd_fsp,
+ smb_fname_cp);
+ /* End recursion guard. */
+ prd->calling_pathref_fsp = false;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Ignore errors here. We know
+ * the path exists (the SMB_VFS_NEXT_STAT()
+ * above succeeded. So being unable to
+ * open a pathref fsp can be due to a
+ * range of errors (startup path beginning
+ * with '/' for example, path = ".." when
+ * enumerating a directory. Just treat this
+ * the same way as the path not having the
+ * FAKE_UID or FAKE_GID EA's present. For the
+ * test purposes of this module (fake NT ACLs
+ * from windows clients) this is close enough.
+ * Just report for debugging purposes.
+ */
+ DBG_DEBUG("Unable to get pathref fsp on %s. "
+ "Error %s\n",
+ smb_fname_str_dbg(smb_fname_cp),
+ nt_errstr(status));
+ TALLOC_FREE(smb_fname_cp);
+ return 0;
+ }
+ fsp = smb_fname_cp->fsp;
+ }
+
+ ret = fake_acls_fuid(handle,
+ fsp,
+ &smb_fname->st.st_ex_uid);
+ if (ret != 0) {
+ TALLOC_FREE(smb_fname_cp);
+ return ret;
+ }
+ ret = fake_acls_fgid(handle,
+ fsp,
+ &smb_fname->st.st_ex_gid);
+ if (ret != 0) {
+ TALLOC_FREE(smb_fname_cp);
+ return ret;
+ }
+ TALLOC_FREE(smb_fname_cp);
+ return ret;
+}
+
+static int fake_acls_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int ret = -1;
+ struct in_pathref_data *prd = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ prd,
+ struct in_pathref_data,
+ return -1);
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (ret == 0) {
+ struct smb_filename *smb_fname_base = NULL;
+ SMB_STRUCT_STAT sbuf = { 0 };
+ NTSTATUS status;
+
+ /*
+ * Ensure synthetic_pathref()
+ * can't recurse into fake_acls_lstat().
+ * synthetic_pathref() doesn't care
+ * about the uid/gid values, it only
+ * wants a valid/invalid stat answer
+ * and we know smb_fname exists as
+ * the SMB_VFS_NEXT_LSTAT() returned
+ * zero above.
+ */
+ if (prd->calling_pathref_fsp) {
+ return 0;
+ }
+
+ /* Recursion guard. */
+ prd->calling_pathref_fsp = true;
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ &sbuf,
+ smb_fname->twrp,
+ 0, /* we want stat, not lstat. */
+ &smb_fname_base);
+ /* End recursion guard. */
+ prd->calling_pathref_fsp = false;
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * This isn't quite right (calling fgetxattr not
+ * lgetxattr), but for the test purposes of this
+ * module (fake NT ACLs from windows clients), it is
+ * close enough. We removed the l*xattr functions
+ * because linux doesn't support using them, but we
+ * could fake them in xattr_tdb if we really wanted
+ * to. We ignore errors because the link might not
+ * point anywhere */
+ fake_acls_fuid(handle,
+ smb_fname_base->fsp,
+ &smb_fname->st.st_ex_uid);
+ fake_acls_fgid(handle,
+ smb_fname_base->fsp,
+ &smb_fname->st.st_ex_gid);
+ }
+ TALLOC_FREE(smb_fname_base);
+ }
+
+ return ret;
+}
+
+static int fake_acls_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ int ret = -1;
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (ret == 0) {
+ ret = fake_acls_fuid(handle, fsp, &sbuf->st_ex_uid);
+ if (ret != 0) {
+ return ret;
+ }
+ ret = fake_acls_fgid(handle, fsp, &sbuf->st_ex_gid);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ return ret;
+}
+
+static SMB_ACL_T fake_acls_blob2acl(DATA_BLOB *blob, TALLOC_CTX *mem_ctx)
+{
+ enum ndr_err_code ndr_err;
+ struct smb_acl_t *acl = talloc(mem_ctx, struct smb_acl_t);
+ if (!acl) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ ndr_err = ndr_pull_struct_blob(blob, acl, acl,
+ (ndr_pull_flags_fn_t)ndr_pull_smb_acl_t);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("ndr_pull_acl_t failed: %s\n",
+ ndr_errstr(ndr_err)));
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+ return acl;
+}
+
+static DATA_BLOB fake_acls_acl2blob(TALLOC_CTX *mem_ctx, SMB_ACL_T acl)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, acl,
+ (ndr_push_flags_fn_t)ndr_push_smb_acl_t);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("ndr_push_acl_t failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return data_blob_null;
+ }
+ return blob;
+}
+
+static SMB_ACL_T fake_acls_sys_acl_get_fd(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ DATA_BLOB blob = data_blob_null;
+ ssize_t length;
+ const char *name = NULL;
+ struct smb_acl_t *acl = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ switch (type) {
+ case SMB_ACL_TYPE_ACCESS:
+ name = FAKE_ACL_ACCESS_XATTR;
+ break;
+ case SMB_ACL_TYPE_DEFAULT:
+ name = FAKE_ACL_DEFAULT_XATTR;
+ break;
+ default:
+ DBG_ERR("Illegal ACL type %d\n", (int)type);
+ break;
+ }
+
+ if (name == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ do {
+ blob.length += 1000;
+ blob.data = talloc_realloc(frame, blob.data, uint8_t, blob.length);
+ if (!blob.data) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ length = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, blob.data, blob.length);
+ blob.length = length;
+ } while (length == -1 && errno == ERANGE);
+ if (length == -1 && errno == ENOATTR) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ if (length != -1) {
+ acl = fake_acls_blob2acl(&blob, mem_ctx);
+ }
+ TALLOC_FREE(frame);
+ return acl;
+}
+
+static int fake_acls_sys_acl_set_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ int ret;
+ const char *name = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ DATA_BLOB blob = fake_acls_acl2blob(frame, theacl);
+ if (!blob.data) {
+ DEBUG(0, ("Failed to convert ACL to linear blob for xattr storage\n"));
+ TALLOC_FREE(frame);
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch (type) {
+ case SMB_ACL_TYPE_ACCESS:
+ name = FAKE_ACL_ACCESS_XATTR;
+ break;
+ case SMB_ACL_TYPE_DEFAULT:
+ name = FAKE_ACL_DEFAULT_XATTR;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, blob.data, blob.length, 0);
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static int fake_acls_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ int ret;
+ const char *name = FAKE_ACL_DEFAULT_XATTR;
+
+ if (!fsp->fsp_flags.is_directory) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
+ if (ret == -1 && errno == ENOATTR) {
+ ret = 0;
+ errno = 0;
+ }
+
+ return ret;
+}
+
+static int fake_acls_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int ret;
+ uint8_t id_buf[4];
+ if (uid != -1) {
+ uid_t current_uid = get_current_uid(handle->conn);
+
+ if (current_uid != 0 && current_uid != uid) {
+ return EACCES;
+ }
+
+ /* This isn't quite right (calling setxattr not
+ * lsetxattr), but for the test purposes of this
+ * module (fake NT ACLs from windows clients), it is
+ * close enough. We removed the l*xattr functions
+ * because linux doesn't support using them, but we
+ * could fake them in xattr_tdb if we really wanted
+ * to.
+ */
+ SIVAL(id_buf, 0, uid);
+ ret = SMB_VFS_NEXT_FSETXATTR(handle,
+ smb_fname->fsp,
+ FAKE_UID,
+ id_buf,
+ sizeof(id_buf),
+ 0);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ if (gid != -1) {
+ SIVAL(id_buf, 0, gid);
+ ret = SMB_VFS_NEXT_FSETXATTR(handle,
+ smb_fname->fsp,
+ FAKE_GID,
+ id_buf,
+ sizeof(id_buf),
+ 0);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int fake_acls_fchown(vfs_handle_struct *handle, files_struct *fsp, uid_t uid, gid_t gid)
+{
+ int ret;
+ uint8_t id_buf[4];
+ if (uid != -1) {
+ uid_t current_uid = get_current_uid(handle->conn);
+
+ if (current_uid != 0 && current_uid != uid) {
+ return EACCES;
+ }
+
+ SIVAL(id_buf, 0, uid);
+ ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_UID, id_buf, sizeof(id_buf), 0);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ if (gid != -1) {
+ SIVAL(id_buf, 0, gid);
+ ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, FAKE_GID, id_buf, sizeof(id_buf), 0);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Implement the chmod uid/mask/other mode changes on a fake ACL.
+ */
+
+static int fake_acl_process_chmod(SMB_ACL_T *pp_the_acl,
+ uid_t owner,
+ mode_t mode)
+{
+ bool got_mask = false;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ mode_t umode = 0;
+ mode_t mmode = 0;
+ mode_t omode = 0;
+ int ret = -1;
+ SMB_ACL_T the_acl = *pp_the_acl;
+
+ /* Split the mode into u/mask/other masks. */
+ umode = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR);
+ mmode = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP);
+ omode = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH);
+
+ while (1) {
+ SMB_ACL_ENTRY_T entry;
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ uid_t *puid = NULL;
+
+ ret = sys_acl_get_entry(the_acl,
+ entry_id,
+ &entry);
+ if (ret == 0) {
+ /* End of ACL */
+ break;
+ }
+ if (ret == -1) {
+ return -1;
+ }
+
+ ret = sys_acl_get_tag_type(entry, &tagtype);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = sys_acl_get_permset(entry, &permset);
+ if (ret == -1) {
+ return -1;
+ }
+ switch (tagtype) {
+ case SMB_ACL_USER_OBJ:
+ ret = map_acl_perms_to_permset(umode, &permset);
+ if (ret == -1) {
+ return -1;
+ }
+ break;
+ case SMB_ACL_USER:
+ puid = (uid_t *)sys_acl_get_qualifier(entry);
+ if (puid == NULL) {
+ return -1;
+ }
+ if (owner != *puid) {
+ break;
+ }
+ ret = map_acl_perms_to_permset(umode, &permset);
+ if (ret == -1) {
+ return -1;
+ }
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ case SMB_ACL_GROUP:
+ /* Ignore all group entries. */
+ break;
+ case SMB_ACL_MASK:
+ ret = map_acl_perms_to_permset(mmode, &permset);
+ if (ret == -1) {
+ return -1;
+ }
+ got_mask = true;
+ break;
+ case SMB_ACL_OTHER:
+ ret = map_acl_perms_to_permset(omode, &permset);
+ if (ret == -1) {
+ return -1;
+ }
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ ret = sys_acl_set_permset(entry, permset);
+ if (ret == -1) {
+ return -1;
+ }
+ /* Move to next entry. */
+ entry_id = SMB_ACL_NEXT_ENTRY;
+ }
+
+ /*
+ * If we didn't see a mask entry, add one.
+ */
+
+ if (!got_mask) {
+ SMB_ACL_ENTRY_T mask_entry;
+ uint32_t mask_perm = 0;
+ SMB_ACL_PERMSET_T mask_permset = &mask_perm;
+ ret = sys_acl_create_entry(&the_acl, &mask_entry);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = map_acl_perms_to_permset(mmode, &mask_permset);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = sys_acl_set_permset(mask_entry, mask_permset);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = sys_acl_set_tag_type(mask_entry, SMB_ACL_MASK);
+ if (ret == -1) {
+ return -1;
+ }
+ /* In case we were realloced and moved. */
+ *pp_the_acl = the_acl;
+ }
+
+ return 0;
+}
+
+static int fake_acls_fchmod(vfs_handle_struct *handle,
+ files_struct *fsp,
+ mode_t mode)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = -1;
+ SMB_ACL_T the_acl = NULL;
+
+ /*
+ * Passthrough first to preserve the
+ * S_ISUID | S_ISGID | S_ISVTX
+ * bits.
+ */
+
+ ret = SMB_VFS_NEXT_FCHMOD(handle,
+ fsp,
+ mode);
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ the_acl = fake_acls_sys_acl_get_fd(handle,
+ fsp,
+ SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+ if (the_acl == NULL) {
+ TALLOC_FREE(frame);
+ if (errno == ENOATTR) {
+ /* No ACL on this file. Just passthrough. */
+ return 0;
+ }
+ return -1;
+ }
+ ret = fake_acl_process_chmod(&the_acl,
+ fsp->fsp_name->st.st_ex_uid,
+ mode);
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ ret = fake_acls_sys_acl_set_fd(handle,
+ fsp,
+ SMB_ACL_TYPE_ACCESS,
+ the_acl);
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static int fake_acls_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ struct in_pathref_data *prd = NULL;
+ int ret;
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+ /*
+ * Create a struct can tell us if we're recursing
+ * into openat_pathref_fsp() in this module. This will
+ * go away once we have SMB_VFS_STATX() and we will
+ * have a way for a caller to as for specific stat
+ * fields in a granular way. Then we will know exactly
+ * what fields the caller wants, so we won't have to
+ * fill in everything.
+ */
+ prd = talloc_zero(handle->conn, struct in_pathref_data);
+ if (prd == NULL) {
+ return -1;
+ }
+ SMB_VFS_HANDLE_SET_DATA(handle,
+ prd,
+ NULL,
+ struct in_pathref_data,
+ return -1);
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_fake_acls_fns = {
+ .connect_fn = fake_acls_connect,
+ .stat_fn = fake_acls_stat,
+ .lstat_fn = fake_acls_lstat,
+ .fstat_fn = fake_acls_fstat,
+ .fchmod_fn = fake_acls_fchmod,
+ .sys_acl_get_fd_fn = fake_acls_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = fake_acls_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = fake_acls_sys_acl_delete_def_fd,
+ .lchown_fn = fake_acls_lchown,
+ .fchown_fn = fake_acls_fchown,
+
+};
+
+static_decl_vfs;
+NTSTATUS vfs_fake_acls_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_acls",
+ &vfs_fake_acls_fns);
+}
diff --git a/source3/modules/vfs_fake_dfq.c b/source3/modules/vfs_fake_dfq.c
new file mode 100644
index 0000000..47c0b07
--- /dev/null
+++ b/source3/modules/vfs_fake_dfq.c
@@ -0,0 +1,281 @@
+/*
+ * Fake Disk-Free and Quota VFS module. Implements passthrough operation
+ * of all VFS calls, except for "disk free" and "get quota" which
+ * are handled by reading a text file named ".dfq" in the current directory.
+ *
+ * This module is intended for testing purposes.
+ *
+ * Copyright (C) Uri Simchoni, 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static int dfq_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt);
+
+static uint64_t dfq_load_param(int snum, const char *path, const char *section,
+ const char *param, uint64_t def_val)
+{
+ uint64_t ret;
+
+ char *option =
+ talloc_asprintf(talloc_tos(), "%s/%s/%s", section, param, path);
+ if (option == NULL) {
+ return def_val;
+ }
+
+ ret = (uint64_t)lp_parm_ulonglong(snum, "fake_dfq", option,
+ (unsigned long long)def_val);
+
+ TALLOC_FREE(option);
+
+ return ret;
+}
+
+static uint64_t dfq_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ uint64_t free_1k;
+ int snum = SNUM(handle->conn);
+ uint64_t dfq_bsize = 0;
+ struct smb_filename *rpath_fname = NULL;
+
+ /* look up the params based on real path to be resilient
+ * to refactoring of path<->realpath
+ */
+ rpath_fname = SMB_VFS_NEXT_REALPATH(handle, talloc_tos(), smb_fname);
+ if (rpath_fname != NULL) {
+ dfq_bsize = dfq_load_param(snum, rpath_fname->base_name,
+ "df", "block size", 0);
+ }
+ if (dfq_bsize == 0) {
+ TALLOC_FREE(rpath_fname);
+ return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, bsize, dfree,
+ dsize);
+ }
+
+ *bsize = dfq_bsize;
+ *dfree = dfq_load_param(snum, rpath_fname->base_name,
+ "df", "disk free", 0);
+ *dsize = dfq_load_param(snum, rpath_fname->base_name,
+ "df", "disk size", 0);
+
+ if ((*bsize) < 1024) {
+ free_1k = (*dfree) / (1024 / (*bsize));
+ } else {
+ free_1k = ((*bsize) / 1024) * (*dfree);
+ }
+
+ TALLOC_FREE(rpath_fname);
+ return free_1k;
+}
+
+static int dfq_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ int rc = 0;
+ int save_errno;
+ char *section = NULL;
+ int snum = SNUM(handle->conn);
+ uint64_t bsize = 0;
+ struct smb_filename *rpath_fname = NULL;
+
+ rpath_fname = SMB_VFS_NEXT_REALPATH(handle, talloc_tos(), smb_fname);
+ if (rpath_fname == NULL) {
+ goto dflt;
+ }
+
+ switch (qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ section = talloc_asprintf(talloc_tos(), "u%llu",
+ (unsigned long long)id.uid);
+ break;
+ case SMB_GROUP_QUOTA_TYPE:
+ section = talloc_asprintf(talloc_tos(), "g%llu",
+ (unsigned long long)id.gid);
+ break;
+ case SMB_USER_FS_QUOTA_TYPE:
+ section = talloc_strdup(talloc_tos(), "udflt");
+ break;
+ case SMB_GROUP_FS_QUOTA_TYPE:
+ section = talloc_strdup(talloc_tos(), "gdflt");
+ break;
+ default:
+ break;
+ }
+
+ if (section == NULL) {
+ goto dflt;
+ }
+
+ bsize = dfq_load_param(snum, rpath_fname->base_name,
+ section, "block size", 4096);
+ if (bsize == 0) {
+ goto dflt;
+ }
+
+ if (dfq_load_param(snum, rpath_fname->base_name,
+ section, "err", 0) != 0) {
+ errno = ENOTSUP;
+ rc = -1;
+ goto out;
+ }
+
+ if (dfq_load_param(snum, rpath_fname->base_name,
+ section, "nosys", 0) != 0) {
+ errno = ENOSYS;
+ rc = -1;
+ goto out;
+ }
+
+ ZERO_STRUCTP(qt);
+
+ qt->bsize = bsize;
+ qt->hardlimit = dfq_load_param(snum, rpath_fname->base_name,
+ section, "hard limit", 0);
+ qt->softlimit = dfq_load_param(snum, rpath_fname->base_name,
+ section, "soft limit", 0);
+ qt->curblocks = dfq_load_param(snum, rpath_fname->base_name,
+ section, "cur blocks", 0);
+ qt->ihardlimit =
+ dfq_load_param(snum, rpath_fname->base_name,
+ section, "inode hard limit", 0);
+ qt->isoftlimit =
+ dfq_load_param(snum, rpath_fname->base_name,
+ section, "inode soft limit", 0);
+ qt->curinodes = dfq_load_param(snum, rpath_fname->base_name,
+ section, "cur inodes", 0);
+ qt->qflags = dfq_load_param(snum, rpath_fname->base_name,
+ section, "qflags", QUOTAS_DENY_DISK);
+
+ goto out;
+
+dflt:
+ rc = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, qt);
+
+out:
+ save_errno = errno;
+ TALLOC_FREE(section);
+ TALLOC_FREE(rpath_fname);
+ errno = save_errno;
+ return rc;
+}
+
+static void dfq_fake_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf)
+{
+ int snum = SNUM(handle->conn);
+ char *full_path = NULL;
+ char *to_free = NULL;
+ char path[PATH_MAX + 1];
+ int len;
+ gid_t gid;
+
+ len = full_path_tos(handle->conn->cwd_fsp->fsp_name->base_name,
+ smb_fname->base_name,
+ path, sizeof(path),
+ &full_path, &to_free);
+ if (len == -1) {
+ DBG_ERR("Could not allocate memory in full_path_tos.\n");
+ return;
+ }
+
+ gid = dfq_load_param(snum, full_path, "stat", "sgid", 0);
+ if (gid != 0) {
+ sbuf->st_ex_gid = gid;
+ sbuf->st_ex_mode |= S_ISGID;
+ }
+
+ TALLOC_FREE(to_free);
+}
+
+static int dfq_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (ret == -1) {
+ return ret;
+ }
+
+ dfq_fake_stat(handle, smb_fname, &smb_fname->st);
+
+ return 0;
+}
+
+static int dfq_fstat(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (ret == -1) {
+ return ret;
+ }
+
+ dfq_fake_stat(handle, fsp->fsp_name, sbuf);
+
+ return 0;
+}
+
+static int dfq_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (ret == -1) {
+ return ret;
+ }
+
+ dfq_fake_stat(handle, smb_fname, &smb_fname->st);
+
+ return 0;
+}
+
+struct vfs_fn_pointers vfs_fake_dfq_fns = {
+ /* Disk operations */
+
+ .disk_free_fn = dfq_disk_free,
+ .get_quota_fn = dfq_get_quota,
+
+ .stat_fn = dfq_stat,
+ .fstat_fn = dfq_fstat,
+ .lstat_fn = dfq_lstat,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_fake_dfq_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_dfq",
+ &vfs_fake_dfq_fns);
+}
diff --git a/source3/modules/vfs_fake_perms.c b/source3/modules/vfs_fake_perms.c
new file mode 100644
index 0000000..0089186
--- /dev/null
+++ b/source3/modules/vfs_fake_perms.c
@@ -0,0 +1,108 @@
+/*
+ * Fake Perms VFS module. Implements passthrough operation of all VFS
+ * calls to disk functions, except for file permissions, which are now
+ * mode 0700 for the current uid/gid.
+ *
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) Andrew Bartlett, 2002
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "auth.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static int fake_perms_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ smb_fname->st.st_ex_mode = S_IFDIR | S_IRWXU;
+ } else {
+ smb_fname->st.st_ex_mode = S_IRWXU;
+ }
+
+ if (handle->conn->session_info != NULL) {
+ struct security_unix_token *utok;
+
+ utok = handle->conn->session_info->unix_token;
+ smb_fname->st.st_ex_uid = utok->uid;
+ smb_fname->st.st_ex_gid = utok->gid;
+ } else {
+ /*
+ * We have an artificial connection for dfs for example. It
+ * sucks, but the current uid/gid is the best we have.
+ */
+ smb_fname->st.st_ex_uid = geteuid();
+ smb_fname->st.st_ex_gid = getegid();
+ }
+
+ return ret;
+}
+
+static int fake_perms_fstat(vfs_handle_struct *handle, files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ int ret;
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (ret != 0) {
+ return ret;
+ }
+
+ if (S_ISDIR(sbuf->st_ex_mode)) {
+ sbuf->st_ex_mode = S_IFDIR | S_IRWXU;
+ } else {
+ sbuf->st_ex_mode = S_IRWXU;
+ }
+ if (handle->conn->session_info != NULL) {
+ struct security_unix_token *utok;
+
+ utok = handle->conn->session_info->unix_token;
+ sbuf->st_ex_uid = utok->uid;
+ sbuf->st_ex_gid = utok->gid;
+ } else {
+ /*
+ * We have an artificial connection for dfs for example. It
+ * sucks, but the current uid/gid is the best we have.
+ */
+ sbuf->st_ex_uid = geteuid();
+ sbuf->st_ex_gid = getegid();
+ }
+
+ return ret;
+}
+
+static struct vfs_fn_pointers vfs_fake_perms_fns = {
+ .stat_fn = fake_perms_stat,
+ .fstat_fn = fake_perms_fstat
+};
+
+static_decl_vfs;
+NTSTATUS vfs_fake_perms_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fake_perms",
+ &vfs_fake_perms_fns);
+}
diff --git a/source3/modules/vfs_fileid.c b/source3/modules/vfs_fileid.c
new file mode 100644
index 0000000..2c67946
--- /dev/null
+++ b/source3/modules/vfs_fileid.c
@@ -0,0 +1,723 @@
+/*
+ * VFS module to alter the algorithm to calculate
+ * the struct file_id used as key for the share mode
+ * and byte range locking db's.
+ *
+ * Copyright (C) 2007, Stefan Metzmacher
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+
+static int vfs_fileid_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_fileid_debug_level
+
+struct fileid_mount_entry {
+ SMB_DEV_T device;
+ const char *mnt_fsname;
+ fsid_t fsid;
+ uint64_t devid;
+};
+
+struct fileid_nolock_inode {
+ dev_t dev;
+ ino_t ino;
+};
+
+struct fileid_handle_data {
+ struct vfs_handle_struct *handle;
+ struct file_id (*mapping_fn)(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf);
+ char **fstype_deny_list;
+ char **fstype_allow_list;
+ char **mntdir_deny_list;
+ char **mntdir_allow_list;
+ unsigned num_mount_entries;
+ struct fileid_mount_entry *mount_entries;
+ struct {
+ bool force_all_inodes;
+ bool force_all_dirs;
+ uint64_t extid;
+ size_t num_inodes;
+ struct fileid_nolock_inode *inodes;
+ } nolock;
+};
+
+/* check if a mount entry is allowed based on fstype and mount directory */
+static bool fileid_mount_entry_allowed(struct fileid_handle_data *data,
+ struct mntent *m)
+{
+ int i;
+ char **fstype_deny = data->fstype_deny_list;
+ char **fstype_allow = data->fstype_allow_list;
+ char **mntdir_deny = data->mntdir_deny_list;
+ char **mntdir_allow = data->mntdir_allow_list;
+
+ if (fstype_deny != NULL) {
+ for (i = 0; fstype_deny[i] != NULL; i++) {
+ if (strcmp(m->mnt_type, fstype_deny[i]) == 0) {
+ return false;
+ }
+ }
+ }
+ if (fstype_allow != NULL) {
+ for (i = 0; fstype_allow[i] != NULL; i++) {
+ if (strcmp(m->mnt_type, fstype_allow[i]) == 0) {
+ break;
+ }
+ }
+ if (fstype_allow[i] == NULL) {
+ return false;
+ }
+ }
+ if (mntdir_deny != NULL) {
+ for (i=0; mntdir_deny[i] != NULL; i++) {
+ if (strcmp(m->mnt_dir, mntdir_deny[i]) == 0) {
+ return false;
+ }
+ }
+ }
+ if (mntdir_allow != NULL) {
+ for (i=0; mntdir_allow[i] != NULL; i++) {
+ if (strcmp(m->mnt_dir, mntdir_allow[i]) == 0) {
+ break;
+ }
+ }
+ if (mntdir_allow[i] == NULL) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+/* load all the mount entries from the mtab */
+static void fileid_load_mount_entries(struct fileid_handle_data *data)
+{
+ FILE *f;
+ struct mntent *m;
+
+ data->num_mount_entries = 0;
+ TALLOC_FREE(data->mount_entries);
+
+ f = setmntent("/etc/mtab", "r");
+ if (!f) return;
+
+ while ((m = getmntent(f))) {
+ struct stat st;
+ struct statfs sfs;
+ struct fileid_mount_entry *cur;
+ bool allowed;
+
+ allowed = fileid_mount_entry_allowed(data, m);
+ if (!allowed) {
+ DBG_DEBUG("skipping mount entry %s\n", m->mnt_dir);
+ continue;
+ }
+ if (stat(m->mnt_dir, &st) != 0) continue;
+ if (statfs(m->mnt_dir, &sfs) != 0) continue;
+
+ if (strncmp(m->mnt_fsname, "/dev/", 5) == 0) {
+ m->mnt_fsname += 5;
+ }
+
+ data->mount_entries = talloc_realloc(data,
+ data->mount_entries,
+ struct fileid_mount_entry,
+ data->num_mount_entries+1);
+ if (data->mount_entries == NULL) {
+ goto nomem;
+ }
+
+ cur = &data->mount_entries[data->num_mount_entries];
+ cur->device = st.st_dev;
+ cur->mnt_fsname = talloc_strdup(data->mount_entries,
+ m->mnt_fsname);
+ if (!cur->mnt_fsname) goto nomem;
+ cur->fsid = sfs.f_fsid;
+ cur->devid = (uint64_t)-1;
+
+ data->num_mount_entries++;
+ }
+ endmntent(f);
+ return;
+
+nomem:
+ if (f) endmntent(f);
+
+ data->num_mount_entries = 0;
+ TALLOC_FREE(data->mount_entries);
+
+ return;
+}
+
+/* find a mount entry given a dev_t */
+static struct fileid_mount_entry *fileid_find_mount_entry(struct fileid_handle_data *data,
+ SMB_DEV_T dev)
+{
+ unsigned i;
+
+ if (data->num_mount_entries == 0) {
+ fileid_load_mount_entries(data);
+ }
+ for (i=0;i<data->num_mount_entries;i++) {
+ if (data->mount_entries[i].device == dev) {
+ return &data->mount_entries[i];
+ }
+ }
+ /* 2nd pass after reloading */
+ fileid_load_mount_entries(data);
+ for (i=0;i<data->num_mount_entries;i++) {
+ if (data->mount_entries[i].device == dev) {
+ return &data->mount_entries[i];
+ }
+ }
+ return NULL;
+}
+
+
+/* a 64 bit hash, based on the one in tdb */
+static uint64_t fileid_uint64_hash(const uint8_t *s, size_t len)
+{
+ uint64_t value; /* Used to compute the hash value. */
+ uint32_t i; /* Used to cycle through random values. */
+
+ /* Set the initial value from the key size. */
+ for (value = 0x238F13AFLL * len, i=0; i < len; i++)
+ value = (value + (((uint64_t)s[i]) << (i*5 % 24)));
+
+ return (1103515243LL * value + 12345LL);
+}
+
+/* a device mapping using a fsname */
+static uint64_t fileid_device_mapping_fsname(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct fileid_mount_entry *m;
+
+ m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
+ if (!m) return sbuf->st_ex_dev;
+
+ if (m->devid == (uint64_t)-1) {
+ m->devid = fileid_uint64_hash((const uint8_t *)m->mnt_fsname,
+ strlen(m->mnt_fsname));
+ }
+
+ return m->devid;
+}
+
+static struct file_id fileid_mapping_fsname(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct file_id id = { .inode = sbuf->st_ex_ino, };
+
+ id.devid = fileid_device_mapping_fsname(data, sbuf);
+
+ return id;
+}
+
+/* a device mapping using a hostname */
+static uint64_t fileid_device_mapping_hostname(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ char hostname[HOST_NAME_MAX+1];
+ char *devname = NULL;
+ uint64_t id;
+ size_t devname_len;
+ int rc;
+
+ rc = gethostname(hostname, HOST_NAME_MAX+1);
+ if (rc != 0) {
+ DBG_ERR("gethostname failed\n");
+ return UINT64_MAX;
+ }
+
+ devname = talloc_asprintf(talloc_tos(), "%s%ju",
+ hostname, (uintmax_t)sbuf->st_ex_dev);
+ if (devname == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ return UINT64_MAX;
+ }
+ devname_len = talloc_array_length(devname) - 1;
+
+ id = fileid_uint64_hash((uint8_t *)devname, devname_len);
+
+ TALLOC_FREE(devname);
+
+ return id;
+}
+
+static struct file_id fileid_mapping_hostname(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct file_id id = { .inode = sbuf->st_ex_ino, };
+
+ id.devid = fileid_device_mapping_hostname(data, sbuf);
+
+ return id;
+}
+
+static bool fileid_is_nolock_inode(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ size_t i;
+
+ if (data->nolock.force_all_inodes) {
+ return true;
+ }
+
+ if (S_ISDIR(sbuf->st_ex_mode) && data->nolock.force_all_dirs) {
+ return true;
+ }
+
+ /*
+ * We could make this a binary search over an sorted array,
+ * but for now we keep things simple.
+ */
+
+ for (i=0; i < data->nolock.num_inodes; i++) {
+ if (data->nolock.inodes[i].ino != sbuf->st_ex_ino) {
+ continue;
+ }
+
+ if (data->nolock.inodes[i].dev == 0) {
+ /*
+ * legacy "fileid:nolockinode"
+ * handling ignoring dev
+ */
+ return true;
+ }
+
+ if (data->nolock.inodes[i].dev != sbuf->st_ex_dev) {
+ continue;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static int fileid_add_nolock_inode(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ bool exists = fileid_is_nolock_inode(data, sbuf);
+ struct fileid_nolock_inode *inodes = NULL;
+
+ if (exists) {
+ return 0;
+ }
+
+ inodes = talloc_realloc(data, data->nolock.inodes,
+ struct fileid_nolock_inode,
+ data->nolock.num_inodes + 1);
+ if (inodes == NULL) {
+ return -1;
+ }
+
+ inodes[data->nolock.num_inodes] = (struct fileid_nolock_inode) {
+ .dev = sbuf->st_ex_dev,
+ .ino = sbuf->st_ex_ino,
+ };
+ data->nolock.inodes = inodes;
+ data->nolock.num_inodes += 1;
+
+ return 0;
+}
+
+static uint64_t fileid_mapping_nolock_extid(uint64_t max_slots)
+{
+ char buf[8+4+HOST_NAME_MAX+1] = { 0, };
+ uint64_t slot = 0;
+ uint64_t id;
+ int rc;
+
+ if (max_slots > 1) {
+ slot = getpid() % max_slots;
+ }
+
+ PUSH_LE_U64(buf, 0, slot);
+ PUSH_LE_U32(buf, 8, get_my_vnn());
+
+ rc = gethostname(&buf[12], HOST_NAME_MAX+1);
+ if (rc != 0) {
+ DBG_ERR("gethostname failed\n");
+ return UINT64_MAX;
+ }
+
+ id = fileid_uint64_hash((uint8_t *)buf, ARRAY_SIZE(buf));
+
+ return id;
+}
+
+/* device mapping functions using a fsid */
+static uint64_t fileid_device_mapping_fsid(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct fileid_mount_entry *m;
+
+ m = fileid_find_mount_entry(data, sbuf->st_ex_dev);
+ if (!m) return sbuf->st_ex_dev;
+
+ if (m->devid == (uint64_t)-1) {
+ if (sizeof(fsid_t) > sizeof(uint64_t)) {
+ m->devid = fileid_uint64_hash((uint8_t *)&m->fsid,
+ sizeof(m->fsid));
+ } else {
+ union {
+ uint64_t ret;
+ fsid_t fsid;
+ } u;
+ ZERO_STRUCT(u);
+ u.fsid = m->fsid;
+ m->devid = u.ret;
+ }
+ }
+
+ return m->devid;
+}
+
+static struct file_id fileid_mapping_fsid(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct file_id id = { .inode = sbuf->st_ex_ino, };
+
+ id.devid = fileid_device_mapping_fsid(data, sbuf);
+
+ return id;
+}
+
+static struct file_id fileid_mapping_next_module(struct fileid_handle_data *data,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ return SMB_VFS_NEXT_FILE_ID_CREATE(data->handle, sbuf);
+}
+
+static int get_connectpath_ino(struct vfs_handle_struct *handle,
+ const char *path,
+ SMB_STRUCT_STAT *psbuf)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smb_filename *fname = NULL;
+ const char *fullpath = NULL;
+ int ret;
+
+ if (path[0] == '/') {
+ fullpath = path;
+ } else {
+ fullpath = talloc_asprintf(frame,
+ "%s/%s",
+ handle->conn->connectpath,
+ path);
+ if (fullpath == NULL) {
+ DBG_ERR("talloc_asprintf() failed\n");
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ }
+
+ fname = synthetic_smb_fname(frame,
+ fullpath,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (fname == NULL) {
+ DBG_ERR("synthetic_smb_fname(%s) failed - %s\n",
+ fullpath, strerror(errno));
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_STAT(handle, fname);
+ if (ret != 0) {
+ DBG_ERR("stat failed for %s with %s\n",
+ fullpath, strerror(errno));
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ *psbuf = fname->st;
+
+ TALLOC_FREE(frame);
+
+ return 0;
+}
+
+static int fileid_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct fileid_handle_data *data;
+ const char *algorithm;
+ const char **fstype_deny_list = NULL;
+ const char **fstype_allow_list = NULL;
+ const char **mntdir_deny_list = NULL;
+ const char **mntdir_allow_list = NULL;
+ ino_t nolockinode;
+ uint64_t max_slots = 0;
+ bool rootdir_nolock = false;
+ const char **nolock_paths = NULL;
+ size_t i;
+ int saved_errno;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ data = talloc_zero(handle, struct fileid_handle_data);
+ if (!data) {
+ saved_errno = errno;
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ DEBUG(0, ("talloc_zero() failed\n"));
+ errno = saved_errno;
+ return -1;
+ }
+ data->handle = handle;
+
+ /*
+ * "fileid:mapping" is only here as fallback for old setups
+ * "fileid:algorithm" is the option new setups should use
+ */
+ algorithm = lp_parm_const_string(SNUM(handle->conn),
+ "fileid", "mapping",
+ "fsname");
+ algorithm = lp_parm_const_string(SNUM(handle->conn),
+ "fileid", "algorithm",
+ algorithm);
+ if (strcmp("fsname", algorithm) == 0) {
+ data->mapping_fn = fileid_mapping_fsname;
+ } else if (strcmp("fsname_nodirs", algorithm) == 0) {
+ data->mapping_fn = fileid_mapping_fsname;
+ data->nolock.force_all_dirs = true;
+ } else if (strcmp("fsid", algorithm) == 0) {
+ data->mapping_fn = fileid_mapping_fsid;
+ } else if (strcmp("hostname", algorithm) == 0) {
+ data->mapping_fn = fileid_mapping_hostname;
+ data->nolock.force_all_inodes = true;
+ } else if (strcmp("fsname_norootdir", algorithm) == 0) {
+ data->mapping_fn = fileid_mapping_fsname;
+ rootdir_nolock = true;
+ } else if (strcmp("fsname_norootdir_ext", algorithm) == 0) {
+ data->mapping_fn = fileid_mapping_fsname;
+ rootdir_nolock = true;
+ max_slots = UINT64_MAX;
+ } else if (strcmp("next_module", algorithm) == 0) {
+ data->mapping_fn = fileid_mapping_next_module;
+ } else {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ DEBUG(0,("fileid_connect(): unknown algorithm[%s]\n", algorithm));
+ return -1;
+ }
+
+ fstype_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
+ "fstype deny", NULL);
+ if (fstype_deny_list != NULL) {
+ data->fstype_deny_list = str_list_copy(data, fstype_deny_list);
+ if (data->fstype_deny_list == NULL) {
+ saved_errno = errno;
+ DBG_ERR("str_list_copy failed\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ fstype_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
+ "fstype allow", NULL);
+ if (fstype_allow_list != NULL) {
+ data->fstype_allow_list = str_list_copy(data, fstype_allow_list);
+ if (data->fstype_allow_list == NULL) {
+ saved_errno = errno;
+ DBG_ERR("str_list_copy failed\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ mntdir_deny_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
+ "mntdir deny", NULL);
+ if (mntdir_deny_list != NULL) {
+ data->mntdir_deny_list = str_list_copy(data, mntdir_deny_list);
+ if (data->mntdir_deny_list == NULL) {
+ saved_errno = errno;
+ DBG_ERR("str_list_copy failed\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ mntdir_allow_list = lp_parm_string_list(SNUM(handle->conn), "fileid",
+ "mntdir allow", NULL);
+ if (mntdir_allow_list != NULL) {
+ data->mntdir_allow_list = str_list_copy(data, mntdir_allow_list);
+ if (data->mntdir_allow_list == NULL) {
+ saved_errno = errno;
+ DBG_ERR("str_list_copy failed\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ data->nolock.force_all_inodes = lp_parm_bool(SNUM(handle->conn),
+ "fileid", "nolock_all_inodes",
+ data->nolock.force_all_inodes);
+ data->nolock.force_all_dirs = lp_parm_bool(SNUM(handle->conn),
+ "fileid", "nolock_all_dirs",
+ data->nolock.force_all_dirs);
+
+ max_slots = lp_parm_ulonglong(SNUM(handle->conn),
+ "fileid", "nolock_max_slots",
+ max_slots);
+ max_slots = MAX(max_slots, 1);
+
+ data->nolock.extid = fileid_mapping_nolock_extid(max_slots);
+
+ nolockinode = lp_parm_ulong(SNUM(handle->conn), "fileid", "nolockinode", 0);
+ if (nolockinode != 0) {
+ SMB_STRUCT_STAT tmpsbuf = { .st_ex_ino = nolockinode, };
+
+ ret = fileid_add_nolock_inode(data, &tmpsbuf);
+ if (ret != 0) {
+ saved_errno = errno;
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ if (rootdir_nolock) {
+ SMB_STRUCT_STAT rootdirsbuf;
+
+ ret = get_connectpath_ino(handle, ".", &rootdirsbuf);
+ if (ret != 0) {
+ saved_errno = errno;
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = fileid_add_nolock_inode(data, &rootdirsbuf);
+ if (ret != 0) {
+ saved_errno = errno;
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+ }
+
+ nolock_paths = lp_parm_string_list(SNUM(handle->conn), "fileid", "nolock_paths", NULL);
+ for (i = 0; nolock_paths != NULL && nolock_paths[i] != NULL; i++) {
+ SMB_STRUCT_STAT tmpsbuf;
+
+ ret = get_connectpath_ino(handle, nolock_paths[i], &tmpsbuf);
+ if (ret == -1 && errno == ENOENT) {
+ DBG_ERR("ignoring non existing nolock_paths[%zu]='%s'\n",
+ i, nolock_paths[i]);
+ continue;
+ }
+ if (ret != 0) {
+ saved_errno = errno;
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+
+ ret = fileid_add_nolock_inode(data, &tmpsbuf);
+ if (ret != 0) {
+ saved_errno = errno;
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = saved_errno;
+ return -1;
+ }
+ DBG_DEBUG("Adding nolock_paths[%zu]='%s'\n",
+ i, nolock_paths[i]);
+ }
+
+ SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
+ struct fileid_handle_data,
+ return -1);
+
+ DBG_DEBUG("connect to service[%s] with algorithm[%s] nolock.inodes %zu\n",
+ service, algorithm, data->nolock.num_inodes);
+
+ return 0;
+}
+
+static void fileid_disconnect(struct vfs_handle_struct *handle)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ DEBUG(10,("fileid_disconnect() connect to service[%s].\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn))));
+
+ SMB_VFS_NEXT_DISCONNECT(handle);
+}
+
+static struct file_id fileid_file_id_create(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct fileid_handle_data *data;
+ struct file_id id = { .inode = 0, };
+
+ SMB_VFS_HANDLE_GET_DATA(handle, data,
+ struct fileid_handle_data,
+ return id);
+
+ id = data->mapping_fn(data, sbuf);
+ if (id.extid == 0 && fileid_is_nolock_inode(data, sbuf)) {
+ id.extid = data->nolock.extid;
+ }
+
+ DBG_DEBUG("Returning dev [%jx] inode [%jx] extid [%jx]\n",
+ (uintmax_t)id.devid, (uintmax_t)id.inode, (uintmax_t)id.extid);
+
+ return id;
+}
+
+static struct vfs_fn_pointers vfs_fileid_fns = {
+ .connect_fn = fileid_connect,
+ .disconnect_fn = fileid_disconnect,
+ .file_id_create_fn = fileid_file_id_create
+};
+
+static_decl_vfs;
+NTSTATUS vfs_fileid_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fileid",
+ &vfs_fileid_fns);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ vfs_fileid_debug_level = debug_add_class("fileid");
+ if (vfs_fileid_debug_level == -1) {
+ vfs_fileid_debug_level = DBGC_VFS;
+ DEBUG(0, ("vfs_fileid: Couldn't register custom debugging class!\n"));
+ } else {
+ DEBUG(10, ("vfs_fileid: Debug class number of 'fileid': %d\n", vfs_fileid_debug_level));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_fruit.c b/source3/modules/vfs_fruit.c
new file mode 100644
index 0000000..4ab0f46
--- /dev/null
+++ b/source3/modules/vfs_fruit.c
@@ -0,0 +1,5478 @@
+/*
+ * OS X and Netatalk interoperability VFS module for Samba-3.x
+ *
+ * Copyright (C) Ralph Boehme, 2013, 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "MacExtensions.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "lib/util/time.h"
+#include "system/shmem.h"
+#include "locking/proto.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "libcli/security/security.h"
+#include "../libcli/smb/smb2_create_ctx.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/tevent_unix.h"
+#include "offload_token.h"
+#include "string_replace.h"
+#include "hash_inode.h"
+#include "lib/adouble.h"
+#include "lib/util_macstreams.h"
+#include "source3/smbd/dir.h"
+
+/*
+ * Enhanced OS X and Netatalk compatibility
+ * ========================================
+ *
+ * This modules takes advantage of vfs_streams_xattr and
+ * vfs_catia. VFS modules vfs_fruit and vfs_streams_xattr must be
+ * loaded in the correct order:
+ *
+ * vfs modules = catia fruit streams_xattr
+ *
+ * The module intercepts the OS X special streams "AFP_AfpInfo" and
+ * "AFP_Resource" and handles them in a special way. All other named
+ * streams are deferred to vfs_streams_xattr.
+ *
+ * The OS X client maps all NTFS illegal characters to the Unicode
+ * private range. This module optionally stores the characters using
+ * their native ASCII encoding using vfs_catia. If you're not enabling
+ * this feature, you can skip catia from vfs modules.
+ *
+ * Finally, open modes are optionally checked against Netatalk AFP
+ * share modes.
+ *
+ * The "AFP_AfpInfo" named stream is a binary blob containing OS X
+ * extended metadata for files and directories. This module optionally
+ * reads and stores this metadata in a way compatible with Netatalk 3
+ * which stores the metadata in an EA "org.netatalk.metadata". Cf
+ * source3/include/MacExtensions.h for a description of the binary
+ * blobs content.
+ *
+ * The "AFP_Resource" named stream may be arbitrarily large, thus it
+ * can't be stored in an xattr on most filesystem. ZFS on Solaris is
+ * the only available filesystem where xattrs can be of any size and
+ * the OS supports using the file APIs for xattrs.
+ *
+ * The AFP_Resource stream is stored in an AppleDouble file prepending
+ * "._" to the filename. On Solaris with ZFS the stream is optionally
+ * stored in an EA "org.netatalk.resource".
+ *
+ *
+ * Extended Attributes
+ * ===================
+ *
+ * The OS X SMB client sends xattrs as ADS too. For xattr interop with
+ * other protocols you may want to adjust the xattr names the VFS
+ * module vfs_streams_xattr uses for storing ADS's. This defaults to
+ * user.DosStream.ADS_NAME:$DATA and can be changed by specifying
+ * these module parameters:
+ *
+ * streams_xattr:prefix = user.
+ * streams_xattr:store_stream_type = false
+ *
+ *
+ * TODO
+ * ====
+ *
+ * - log diagnostic if any needed VFS module is not loaded
+ * (eg with lp_vfs_objects())
+ * - add tests
+ */
+
+static int vfs_fruit_debug_level = DBGC_VFS;
+
+static struct global_fruit_config {
+ bool nego_aapl; /* client negotiated AAPL */
+
+} global_fruit_config;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_fruit_debug_level
+
+#define FRUIT_PARAM_TYPE_NAME "fruit"
+
+enum apple_fork {APPLE_FORK_DATA, APPLE_FORK_RSRC};
+
+enum fruit_rsrc {FRUIT_RSRC_STREAM, FRUIT_RSRC_ADFILE, FRUIT_RSRC_XATTR};
+enum fruit_meta {FRUIT_META_STREAM, FRUIT_META_NETATALK};
+enum fruit_locking {FRUIT_LOCKING_NETATALK, FRUIT_LOCKING_NONE};
+enum fruit_encoding {FRUIT_ENC_NATIVE, FRUIT_ENC_PRIVATE};
+
+struct fruit_config_data {
+ enum fruit_rsrc rsrc;
+ enum fruit_meta meta;
+ enum fruit_locking locking;
+ enum fruit_encoding encoding;
+ bool use_aapl; /* config from smb.conf */
+ bool use_copyfile;
+ bool readdir_attr_enabled;
+ bool unix_info_enabled;
+ bool copyfile_enabled;
+ bool veto_appledouble;
+ bool posix_rename;
+ bool aapl_zero_file_id;
+ const char *model;
+ bool time_machine;
+ off_t time_machine_max_size;
+ bool convert_adouble;
+ bool wipe_intentionally_left_blank_rfork;
+ bool delete_empty_adfiles;
+ bool validate_afpinfo;
+
+ /*
+ * Additional options, all enabled by default,
+ * possibly useful for analyzing performance. The associated
+ * operations with each of them may be expensive, so having
+ * the chance to disable them individually gives a chance
+ * tweaking the setup for the particular usecase.
+ */
+ bool readdir_attr_rsize;
+ bool readdir_attr_finder_info;
+ bool readdir_attr_max_access;
+ /* Recursion guard. Will go away when we have STATX. */
+ bool in_openat_pathref_fsp;
+};
+
+static const struct enum_list fruit_rsrc[] = {
+ {FRUIT_RSRC_STREAM, "stream"}, /* pass on to vfs_streams_xattr */
+ {FRUIT_RSRC_ADFILE, "file"}, /* ._ AppleDouble file */
+ {FRUIT_RSRC_XATTR, "xattr"}, /* Netatalk compatible xattr (ZFS only) */
+ { -1, NULL}
+};
+
+static const struct enum_list fruit_meta[] = {
+ {FRUIT_META_STREAM, "stream"}, /* pass on to vfs_streams_xattr */
+ {FRUIT_META_NETATALK, "netatalk"}, /* Netatalk compatible xattr */
+ { -1, NULL}
+};
+
+static const struct enum_list fruit_locking[] = {
+ {FRUIT_LOCKING_NETATALK, "netatalk"}, /* synchronize locks with Netatalk */
+ {FRUIT_LOCKING_NONE, "none"},
+ { -1, NULL}
+};
+
+static const struct enum_list fruit_encoding[] = {
+ {FRUIT_ENC_NATIVE, "native"}, /* map unicode private chars to ASCII */
+ {FRUIT_ENC_PRIVATE, "private"}, /* keep unicode private chars */
+ { -1, NULL}
+};
+
+struct fio {
+ vfs_handle_struct *handle;
+ files_struct *fsp; /* backlink to itself */
+
+ /* tcon config handle */
+ struct fruit_config_data *config;
+
+ /* Backend fsp for AppleDouble file, can be NULL */
+ files_struct *ad_fsp;
+ /* link from adouble_open_from_base_fsp() to fio */
+ struct fio *real_fio;
+
+ /* Denote stream type, meta or rsrc */
+ adouble_type_t type;
+
+ /*
+ * AFP_AfpInfo stream created, but not written yet, thus still a fake
+ * pipe fd. This is set to true in fruit_open_meta if there was no
+ * existing stream but the caller requested O_CREAT. It is later set to
+ * false when we get a write on the stream that then does open and
+ * create the stream.
+ */
+ bool fake_fd;
+ int flags;
+ int mode;
+};
+
+/*****************************************************************************
+ * Helper functions
+ *****************************************************************************/
+
+static struct adouble *ad_get_meta_fsp(TALLOC_CTX *ctx,
+ vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ NTSTATUS status;
+ struct adouble *ad = NULL;
+ struct smb_filename *smb_fname_cp = NULL;
+ struct fruit_config_data *config = NULL;
+
+ if (smb_fname->fsp != NULL) {
+ return ad_get(ctx, handle, smb_fname, ADOUBLE_META);
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct fruit_config_data,
+ return NULL);
+
+ if (config->in_openat_pathref_fsp) {
+ return NULL;
+ }
+
+ smb_fname_cp = cp_smb_filename(ctx,
+ smb_fname);
+ if (smb_fname_cp == NULL) {
+ return NULL;
+ }
+ TALLOC_FREE(smb_fname_cp->stream_name);
+ config->in_openat_pathref_fsp = true;
+ status = openat_pathref_fsp(handle->conn->cwd_fsp,
+ smb_fname_cp);
+ config->in_openat_pathref_fsp = false;
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname_cp);
+ return NULL;
+ }
+
+ ad = ad_get(ctx, handle, smb_fname_cp, ADOUBLE_META);
+ TALLOC_FREE(smb_fname_cp);
+ return ad;
+}
+
+static struct fio *fruit_get_complete_fio(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ struct fio *fio = (struct fio *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (fio == NULL) {
+ return NULL;
+ }
+
+ if (fio->real_fio != NULL) {
+ /*
+ * This is an fsp from adouble_open_from_base_fsp()
+ * we should just pass this to the next
+ * module.
+ */
+ return NULL;
+ }
+
+ return fio;
+}
+
+/**
+ * Initialize config struct from our smb.conf config parameters
+ **/
+static int init_fruit_config(vfs_handle_struct *handle)
+{
+ struct fruit_config_data *config;
+ int enumval = -1;
+ const char *tm_size_str = NULL;
+
+ config = talloc_zero(handle->conn, struct fruit_config_data);
+ if (!config) {
+ DEBUG(1, ("talloc_zero() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "resource", fruit_rsrc, FRUIT_RSRC_ADFILE);
+ if (enumval == -1) {
+ DEBUG(1, ("value for %s: resource type unknown\n",
+ FRUIT_PARAM_TYPE_NAME));
+ return -1;
+ }
+ config->rsrc = (enum fruit_rsrc)enumval;
+
+ enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "metadata", fruit_meta, FRUIT_META_NETATALK);
+ if (enumval == -1) {
+ DEBUG(1, ("value for %s: metadata type unknown\n",
+ FRUIT_PARAM_TYPE_NAME));
+ return -1;
+ }
+ config->meta = (enum fruit_meta)enumval;
+
+ enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "locking", fruit_locking, FRUIT_LOCKING_NONE);
+ if (enumval == -1) {
+ DEBUG(1, ("value for %s: locking type unknown\n",
+ FRUIT_PARAM_TYPE_NAME));
+ return -1;
+ }
+ config->locking = (enum fruit_locking)enumval;
+
+ enumval = lp_parm_enum(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "encoding", fruit_encoding, FRUIT_ENC_PRIVATE);
+ if (enumval == -1) {
+ DEBUG(1, ("value for %s: encoding type unknown\n",
+ FRUIT_PARAM_TYPE_NAME));
+ return -1;
+ }
+ config->encoding = (enum fruit_encoding)enumval;
+
+ if (config->rsrc == FRUIT_RSRC_ADFILE) {
+ config->veto_appledouble = lp_parm_bool(SNUM(handle->conn),
+ FRUIT_PARAM_TYPE_NAME,
+ "veto_appledouble",
+ true);
+ }
+
+ config->use_aapl = lp_parm_bool(
+ -1, FRUIT_PARAM_TYPE_NAME, "aapl", true);
+
+ config->time_machine = lp_parm_bool(
+ SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, "time machine", false);
+
+ config->unix_info_enabled = lp_parm_bool(
+ -1, FRUIT_PARAM_TYPE_NAME, "nfs_aces", true);
+
+ config->use_copyfile = lp_parm_bool(-1, FRUIT_PARAM_TYPE_NAME,
+ "copyfile", false);
+
+ config->posix_rename = lp_parm_bool(
+ SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME, "posix_rename", true);
+
+ config->aapl_zero_file_id =
+ lp_parm_bool(SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "zero_file_id", true);
+
+ config->readdir_attr_rsize = lp_parm_bool(
+ SNUM(handle->conn), "readdir_attr", "aapl_rsize", true);
+
+ config->readdir_attr_finder_info = lp_parm_bool(
+ SNUM(handle->conn), "readdir_attr", "aapl_finder_info", true);
+
+ config->readdir_attr_max_access = lp_parm_bool(
+ SNUM(handle->conn), "readdir_attr", "aapl_max_access", true);
+
+ config->model = lp_parm_const_string(
+ -1, FRUIT_PARAM_TYPE_NAME, "model", "MacSamba");
+
+ tm_size_str = lp_parm_const_string(
+ SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "time machine max size", NULL);
+ if (tm_size_str != NULL) {
+ config->time_machine_max_size = conv_str_size(tm_size_str);
+ }
+
+ config->convert_adouble = lp_parm_bool(
+ SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "convert_adouble", true);
+
+ config->wipe_intentionally_left_blank_rfork = lp_parm_bool(
+ SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "wipe_intentionally_left_blank_rfork", false);
+
+ config->delete_empty_adfiles = lp_parm_bool(
+ SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "delete_empty_adfiles", false);
+
+ config->validate_afpinfo = lp_parm_bool(
+ SNUM(handle->conn), FRUIT_PARAM_TYPE_NAME,
+ "validate_afpinfo", true);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct fruit_config_data,
+ return -1);
+
+ return 0;
+}
+
+static bool add_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
+ struct stream_struct **streams,
+ const char *name, off_t size,
+ off_t alloc_size)
+{
+ struct stream_struct *tmp;
+
+ tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct,
+ (*num_streams)+1);
+ if (tmp == NULL) {
+ return false;
+ }
+
+ tmp[*num_streams].name = talloc_asprintf(tmp, "%s:$DATA", name);
+ if (tmp[*num_streams].name == NULL) {
+ return false;
+ }
+
+ tmp[*num_streams].size = size;
+ tmp[*num_streams].alloc_size = alloc_size;
+
+ *streams = tmp;
+ *num_streams += 1;
+ return true;
+}
+
+static bool filter_empty_rsrc_stream(unsigned int *num_streams,
+ struct stream_struct **streams)
+{
+ struct stream_struct *tmp = *streams;
+ unsigned int i;
+
+ if (*num_streams == 0) {
+ return true;
+ }
+
+ for (i = 0; i < *num_streams; i++) {
+ if (strequal_m(tmp[i].name, AFPRESOURCE_STREAM)) {
+ break;
+ }
+ }
+
+ if (i == *num_streams) {
+ return true;
+ }
+
+ if (tmp[i].size > 0) {
+ return true;
+ }
+
+ TALLOC_FREE(tmp[i].name);
+ ARRAY_DEL_ELEMENT(tmp, i, *num_streams);
+ *num_streams -= 1;
+ return true;
+}
+
+static bool del_fruit_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
+ struct stream_struct **streams,
+ const char *name)
+{
+ struct stream_struct *tmp = *streams;
+ unsigned int i;
+
+ if (*num_streams == 0) {
+ return true;
+ }
+
+ for (i = 0; i < *num_streams; i++) {
+ if (strequal_m(tmp[i].name, name)) {
+ break;
+ }
+ }
+
+ if (i == *num_streams) {
+ return true;
+ }
+
+ TALLOC_FREE(tmp[i].name);
+ ARRAY_DEL_ELEMENT(tmp, i, *num_streams);
+ *num_streams -= 1;
+ return true;
+}
+
+static bool ad_empty_finderinfo(const struct adouble *ad)
+{
+ int cmp;
+ char emptybuf[ADEDLEN_FINDERI] = {0};
+ char *fi = NULL;
+
+ fi = ad_get_entry(ad, ADEID_FINDERI);
+ if (fi == NULL) {
+ DBG_ERR("Missing FinderInfo in struct adouble [%p]\n", ad);
+ return false;
+ }
+
+ cmp = memcmp(emptybuf, fi, ADEDLEN_FINDERI);
+ return (cmp == 0);
+}
+
+static bool ai_empty_finderinfo(const AfpInfo *ai)
+{
+ int cmp;
+ char emptybuf[ADEDLEN_FINDERI] = {0};
+
+ cmp = memcmp(emptybuf, &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI);
+ return (cmp == 0);
+}
+
+/**
+ * Update btime with btime from Netatalk
+ **/
+static void update_btime(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ uint32_t t;
+ struct timespec creation_time = {0};
+ struct adouble *ad;
+ struct fruit_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+ return);
+
+ switch (config->meta) {
+ case FRUIT_META_STREAM:
+ return;
+ case FRUIT_META_NETATALK:
+ /* Handled below */
+ break;
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", config->meta);
+ return;
+ }
+
+ ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname);
+ if (ad == NULL) {
+ return;
+ }
+ if (ad_getdate(ad, AD_DATE_UNIX | AD_DATE_CREATE, &t) != 0) {
+ TALLOC_FREE(ad);
+ return;
+ }
+ TALLOC_FREE(ad);
+
+ creation_time.tv_sec = convert_uint32_t_to_time_t(t);
+ update_stat_ex_create_time(&smb_fname->st, creation_time);
+
+ return;
+}
+
+/**
+ * Map an access mask to a Netatalk single byte byte range lock
+ **/
+static off_t access_to_netatalk_brl(enum apple_fork fork_type,
+ uint32_t access_mask)
+{
+ off_t offset;
+
+ switch (access_mask) {
+ case FILE_READ_DATA:
+ offset = AD_FILELOCK_OPEN_RD;
+ break;
+
+ case FILE_WRITE_DATA:
+ case FILE_APPEND_DATA:
+ offset = AD_FILELOCK_OPEN_WR;
+ break;
+
+ default:
+ offset = AD_FILELOCK_OPEN_NONE;
+ break;
+ }
+
+ if (fork_type == APPLE_FORK_RSRC) {
+ if (offset == AD_FILELOCK_OPEN_NONE) {
+ offset = AD_FILELOCK_RSRC_OPEN_NONE;
+ } else {
+ offset += 2;
+ }
+ }
+
+ return offset;
+}
+
+/**
+ * Map a deny mode to a Netatalk brl
+ **/
+static off_t denymode_to_netatalk_brl(enum apple_fork fork_type,
+ uint32_t deny_mode)
+{
+ off_t offset = 0;
+
+ switch (deny_mode) {
+ case DENY_READ:
+ offset = AD_FILELOCK_DENY_RD;
+ break;
+
+ case DENY_WRITE:
+ offset = AD_FILELOCK_DENY_WR;
+ break;
+
+ default:
+ smb_panic("denymode_to_netatalk_brl: bad deny mode\n");
+ }
+
+ if (fork_type == APPLE_FORK_RSRC) {
+ offset += 2;
+ }
+
+ return offset;
+}
+
+/**
+ * Call fcntl() with an exclusive F_GETLK request in order to
+ * determine if there's an existing shared lock
+ *
+ * @return true if the requested lock was found or any error occurred
+ * false if the lock was not found
+ **/
+static bool test_netatalk_lock(files_struct *fsp, off_t in_offset)
+{
+ bool result;
+ off_t offset = in_offset;
+ off_t len = 1;
+ int type = F_WRLCK;
+ pid_t pid = 0;
+
+ result = SMB_VFS_GETLOCK(fsp, &offset, &len, &type, &pid);
+ if (result == false) {
+ return true;
+ }
+
+ if (type != F_UNLCK) {
+ return true;
+ }
+
+ return false;
+}
+
+static NTSTATUS fruit_check_access(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t access_mask,
+ uint32_t share_mode)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ off_t off;
+ bool share_for_read = (share_mode & FILE_SHARE_READ);
+ bool share_for_write = (share_mode & FILE_SHARE_WRITE);
+ bool netatalk_already_open_for_reading = false;
+ bool netatalk_already_open_for_writing = false;
+ bool netatalk_already_open_with_deny_read = false;
+ bool netatalk_already_open_with_deny_write = false;
+ struct GUID req_guid = GUID_random();
+
+ /* FIXME: hardcoded data fork, add resource fork */
+ enum apple_fork fork_type = APPLE_FORK_DATA;
+
+ DBG_DEBUG("fruit_check_access: %s, am: %s/%s, sm: 0x%x\n",
+ fsp_str_dbg(fsp),
+ access_mask & FILE_READ_DATA ? "READ" :"-",
+ access_mask & FILE_WRITE_DATA ? "WRITE" : "-",
+ share_mode);
+
+ if (fsp_get_io_fd(fsp) == -1) {
+ return NT_STATUS_OK;
+ }
+
+ /* Read NetATalk opens and deny modes on the file. */
+ netatalk_already_open_for_reading = test_netatalk_lock(fsp,
+ access_to_netatalk_brl(fork_type,
+ FILE_READ_DATA));
+
+ netatalk_already_open_with_deny_read = test_netatalk_lock(fsp,
+ denymode_to_netatalk_brl(fork_type,
+ DENY_READ));
+
+ netatalk_already_open_for_writing = test_netatalk_lock(fsp,
+ access_to_netatalk_brl(fork_type,
+ FILE_WRITE_DATA));
+
+ netatalk_already_open_with_deny_write = test_netatalk_lock(fsp,
+ denymode_to_netatalk_brl(fork_type,
+ DENY_WRITE));
+
+ /* If there are any conflicts - sharing violation. */
+ if ((access_mask & FILE_READ_DATA) &&
+ netatalk_already_open_with_deny_read) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ if (!share_for_read &&
+ netatalk_already_open_for_reading) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ if ((access_mask & FILE_WRITE_DATA) &&
+ netatalk_already_open_with_deny_write) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ if (!share_for_write &&
+ netatalk_already_open_for_writing) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ if (!(access_mask & FILE_READ_DATA)) {
+ /*
+ * Nothing we can do here, we need read access
+ * to set locks.
+ */
+ return NT_STATUS_OK;
+ }
+
+ /* Set NetAtalk locks matching our access */
+ if (access_mask & FILE_READ_DATA) {
+ off = access_to_netatalk_brl(fork_type, FILE_READ_DATA);
+ req_guid.time_hi_and_version = __LINE__;
+ status = do_lock(
+ fsp,
+ talloc_tos(),
+ &req_guid,
+ fsp->op->global->open_persistent_id,
+ 1,
+ off,
+ READ_LOCK,
+ POSIX_LOCK,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!share_for_read) {
+ off = denymode_to_netatalk_brl(fork_type, DENY_READ);
+ req_guid.time_hi_and_version = __LINE__;
+ status = do_lock(
+ fsp,
+ talloc_tos(),
+ &req_guid,
+ fsp->op->global->open_persistent_id,
+ 1,
+ off,
+ READ_LOCK,
+ POSIX_LOCK,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (access_mask & FILE_WRITE_DATA) {
+ off = access_to_netatalk_brl(fork_type, FILE_WRITE_DATA);
+ req_guid.time_hi_and_version = __LINE__;
+ status = do_lock(
+ fsp,
+ talloc_tos(),
+ &req_guid,
+ fsp->op->global->open_persistent_id,
+ 1,
+ off,
+ READ_LOCK,
+ POSIX_LOCK,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!share_for_write) {
+ off = denymode_to_netatalk_brl(fork_type, DENY_WRITE);
+ req_guid.time_hi_and_version = __LINE__;
+ status = do_lock(
+ fsp,
+ talloc_tos(),
+ &req_guid,
+ fsp->op->global->open_persistent_id,
+ 1,
+ off,
+ READ_LOCK,
+ POSIX_LOCK,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS check_aapl(vfs_handle_struct *handle,
+ struct smb_request *req,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ struct fruit_config_data *config;
+ NTSTATUS status;
+ struct smb2_create_blob *aapl = NULL;
+ uint32_t cmd;
+ bool ok;
+ uint8_t p[16];
+ DATA_BLOB blob = data_blob_talloc(req, NULL, 0);
+ uint64_t req_bitmap, client_caps;
+ uint64_t server_caps = SMB2_CRTCTX_AAPL_UNIX_BASED;
+ smb_ucs2_t *model;
+ size_t modellen;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ if (!config->use_aapl
+ || in_context_blobs == NULL
+ || out_context_blobs == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ aapl = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_AAPL);
+ if (aapl == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (aapl->data.length != 24) {
+ DEBUG(1, ("unexpected AAPL ctxt length: %ju\n",
+ (uintmax_t)aapl->data.length));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ cmd = IVAL(aapl->data.data, 0);
+ if (cmd != SMB2_CRTCTX_AAPL_SERVER_QUERY) {
+ DEBUG(1, ("unsupported AAPL cmd: %d\n", cmd));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ req_bitmap = BVAL(aapl->data.data, 8);
+ client_caps = BVAL(aapl->data.data, 16);
+
+ SIVAL(p, 0, SMB2_CRTCTX_AAPL_SERVER_QUERY);
+ SIVAL(p, 4, 0);
+ SBVAL(p, 8, req_bitmap);
+ ok = data_blob_append(req, &blob, p, 16);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (req_bitmap & SMB2_CRTCTX_AAPL_SERVER_CAPS) {
+ if ((client_caps & SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR) &&
+ (handle->conn->fs_capabilities & FILE_NAMED_STREAMS)) {
+ server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR;
+ config->readdir_attr_enabled = true;
+ }
+
+ if (config->use_copyfile) {
+ server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_OSX_COPYFILE;
+ config->copyfile_enabled = true;
+ }
+
+ /*
+ * The client doesn't set the flag, so we can't check
+ * for it and just set it unconditionally
+ */
+ if (config->unix_info_enabled) {
+ server_caps |= SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE;
+ }
+
+ SBVAL(p, 0, server_caps);
+ ok = data_blob_append(req, &blob, p, 8);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ if (req_bitmap & SMB2_CRTCTX_AAPL_VOLUME_CAPS) {
+ int val = lp_case_sensitive(SNUM(handle->conn));
+ uint64_t caps = 0;
+
+ switch (val) {
+ case Auto:
+ break;
+
+ case True:
+ caps |= SMB2_CRTCTX_AAPL_CASE_SENSITIVE;
+ break;
+
+ default:
+ break;
+ }
+
+ if (config->time_machine) {
+ caps |= SMB2_CRTCTX_AAPL_FULL_SYNC;
+ }
+
+ SBVAL(p, 0, caps);
+
+ ok = data_blob_append(req, &blob, p, 8);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ if (req_bitmap & SMB2_CRTCTX_AAPL_MODEL_INFO) {
+ ok = convert_string_talloc(req,
+ CH_UNIX, CH_UTF16LE,
+ config->model, strlen(config->model),
+ &model, &modellen);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ SIVAL(p, 0, 0);
+ SIVAL(p + 4, 0, modellen);
+ ok = data_blob_append(req, &blob, p, 8);
+ if (!ok) {
+ talloc_free(model);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ok = data_blob_append(req, &blob, model, modellen);
+ talloc_free(model);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ status = smb2_create_blob_add(out_context_blobs,
+ out_context_blobs,
+ SMB2_CREATE_TAG_AAPL,
+ blob);
+ if (NT_STATUS_IS_OK(status)) {
+ global_fruit_config.nego_aapl = true;
+ }
+
+ return status;
+}
+
+static bool readdir_attr_meta_finderi_stream(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ AfpInfo *ai)
+{
+ struct smb_filename *stream_name = NULL;
+ files_struct *fsp = NULL;
+ ssize_t nread;
+ NTSTATUS status;
+ bool ok;
+ uint8_t buf[AFP_INFO_SIZE];
+
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ smb_fname->base_name,
+ AFPINFO_STREAM_NAME,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &stream_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ handle->conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ stream_name, /* fname */
+ FILE_READ_DATA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ TALLOC_FREE(stream_name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ nread = SMB_VFS_PREAD(fsp, &buf[0], AFP_INFO_SIZE, 0);
+ if (nread != AFP_INFO_SIZE) {
+ DBG_ERR("short read [%s] [%zd/%d]\n",
+ smb_fname_str_dbg(stream_name), nread, AFP_INFO_SIZE);
+ ok = false;
+ goto fail;
+ }
+
+ memcpy(&ai->afpi_FinderInfo[0], &buf[AFP_OFF_FinderInfo],
+ AFP_FinderSize);
+
+ ok = true;
+
+fail:
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+
+ return ok;
+}
+
+static bool readdir_attr_meta_finderi_netatalk(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ AfpInfo *ai)
+{
+ struct adouble *ad = NULL;
+ char *p = NULL;
+
+ ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname);
+ if (ad == NULL) {
+ return false;
+ }
+
+ p = ad_get_entry(ad, ADEID_FINDERI);
+ if (p == NULL) {
+ DBG_ERR("No ADEID_FINDERI for [%s]\n", smb_fname->base_name);
+ TALLOC_FREE(ad);
+ return false;
+ }
+
+ memcpy(&ai->afpi_FinderInfo[0], p, AFP_FinderSize);
+ TALLOC_FREE(ad);
+ return true;
+}
+
+static bool readdir_attr_meta_finderi(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct readdir_attr_data *attr_data)
+{
+ struct fruit_config_data *config = NULL;
+ uint32_t date_added;
+ AfpInfo ai = {0};
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return false);
+
+ switch (config->meta) {
+ case FRUIT_META_NETATALK:
+ ok = readdir_attr_meta_finderi_netatalk(
+ handle, smb_fname, &ai);
+ break;
+
+ case FRUIT_META_STREAM:
+ ok = readdir_attr_meta_finderi_stream(
+ handle, smb_fname, &ai);
+ break;
+
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", config->meta);
+ return false;
+ }
+
+ if (!ok) {
+ /* Don't bother with errors, it's likely ENOENT */
+ return true;
+ }
+
+ if (S_ISREG(smb_fname->st.st_ex_mode)) {
+ /* finder_type */
+ memcpy(&attr_data->attr_data.aapl.finder_info[0],
+ &ai.afpi_FinderInfo[0], 4);
+
+ /* finder_creator */
+ memcpy(&attr_data->attr_data.aapl.finder_info[0] + 4,
+ &ai.afpi_FinderInfo[4], 4);
+ }
+
+ /* finder_flags */
+ memcpy(&attr_data->attr_data.aapl.finder_info[0] + 8,
+ &ai.afpi_FinderInfo[8], 2);
+
+ /* finder_ext_flags */
+ memcpy(&attr_data->attr_data.aapl.finder_info[0] + 10,
+ &ai.afpi_FinderInfo[24], 2);
+
+ /* creation date */
+ date_added = convert_time_t_to_uint32_t(
+ smb_fname->st.st_ex_btime.tv_sec - AD_DATE_DELTA);
+
+ RSIVAL(&attr_data->attr_data.aapl.finder_info[0], 12, date_added);
+
+ return true;
+}
+
+static uint64_t readdir_attr_rfork_size_adouble(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ struct adouble *ad = NULL;
+ uint64_t rfork_size;
+
+ ad = ad_get(talloc_tos(), handle, smb_fname,
+ ADOUBLE_RSRC);
+ if (ad == NULL) {
+ return 0;
+ }
+
+ rfork_size = ad_getentrylen(ad, ADEID_RFORK);
+ TALLOC_FREE(ad);
+
+ return rfork_size;
+}
+
+static uint64_t readdir_attr_rfork_size_stream(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *stream_name = NULL;
+ int ret;
+ uint64_t rfork_size;
+
+ stream_name = synthetic_smb_fname(talloc_tos(),
+ smb_fname->base_name,
+ AFPRESOURCE_STREAM_NAME,
+ NULL,
+ smb_fname->twrp,
+ 0);
+ if (stream_name == NULL) {
+ return 0;
+ }
+
+ ret = SMB_VFS_STAT(handle->conn, stream_name);
+ if (ret != 0) {
+ TALLOC_FREE(stream_name);
+ return 0;
+ }
+
+ rfork_size = stream_name->st.st_ex_size;
+ TALLOC_FREE(stream_name);
+
+ return rfork_size;
+}
+
+static uint64_t readdir_attr_rfork_size(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ struct fruit_config_data *config = NULL;
+ uint64_t rfork_size;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return 0);
+
+ switch (config->rsrc) {
+ case FRUIT_RSRC_ADFILE:
+ rfork_size = readdir_attr_rfork_size_adouble(handle,
+ smb_fname);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ case FRUIT_RSRC_STREAM:
+ rfork_size = readdir_attr_rfork_size_stream(handle,
+ smb_fname);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc);
+ rfork_size = 0;
+ break;
+ }
+
+ return rfork_size;
+}
+
+static NTSTATUS readdir_attr_macmeta(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct readdir_attr_data *attr_data)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct fruit_config_data *config = NULL;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return NT_STATUS_UNSUCCESSFUL);
+
+
+ /* Ensure we return a default value in the creation_date field */
+ RSIVAL(&attr_data->attr_data.aapl.finder_info, 12, AD_DATE_START);
+
+ /*
+ * Resource fork length
+ */
+
+ if (config->readdir_attr_rsize) {
+ uint64_t rfork_size;
+
+ rfork_size = readdir_attr_rfork_size(handle, smb_fname);
+ attr_data->attr_data.aapl.rfork_size = rfork_size;
+ }
+
+ /*
+ * FinderInfo
+ */
+
+ if (config->readdir_attr_finder_info) {
+ ok = readdir_attr_meta_finderi(handle, smb_fname, attr_data);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ return status;
+}
+
+static NTSTATUS remove_virtual_nfs_aces(struct security_descriptor *psd)
+{
+ NTSTATUS status;
+ uint32_t i;
+
+ if (psd->dacl == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ for (i = 0; i < psd->dacl->num_aces; i++) {
+ /* MS NFS style mode/uid/gid */
+ int cmp = dom_sid_compare_domain(
+ &global_sid_Unix_NFS,
+ &psd->dacl->aces[i].trustee);
+ if (cmp != 0) {
+ /* Normal ACE entry. */
+ continue;
+ }
+
+ /*
+ * security_descriptor_dacl_del()
+ * *must* return NT_STATUS_OK as we know
+ * we have something to remove.
+ */
+
+ status = security_descriptor_dacl_del(psd,
+ &psd->dacl->aces[i].trustee);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("failed to remove MS NFS style ACE: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * security_descriptor_dacl_del() may delete more
+ * then one entry subsequent to this one if the
+ * SID matches, but we only need to ensure that
+ * we stay looking at the same element in the array.
+ */
+ i--;
+ }
+ return NT_STATUS_OK;
+}
+
+/* Search MS NFS style ACE with UNIX mode */
+static NTSTATUS check_ms_nfs(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct security_descriptor *psd,
+ mode_t *pmode,
+ bool *pdo_chmod)
+{
+ uint32_t i;
+ struct fruit_config_data *config = NULL;
+
+ *pdo_chmod = false;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ if (!global_fruit_config.nego_aapl) {
+ return NT_STATUS_OK;
+ }
+ if (psd->dacl == NULL || !config->unix_info_enabled) {
+ return NT_STATUS_OK;
+ }
+
+ for (i = 0; i < psd->dacl->num_aces; i++) {
+ if (dom_sid_compare_domain(
+ &global_sid_Unix_NFS_Mode,
+ &psd->dacl->aces[i].trustee) == 0) {
+ *pmode = (mode_t)psd->dacl->aces[i].trustee.sub_auths[2];
+ *pmode &= (S_IRWXU | S_IRWXG | S_IRWXO);
+ *pdo_chmod = true;
+
+ DEBUG(10, ("MS NFS chmod request %s, %04o\n",
+ fsp_str_dbg(fsp), (unsigned)(*pmode)));
+ break;
+ }
+ }
+
+ /*
+ * Remove any incoming virtual ACE entries generated by
+ * fruit_fget_nt_acl().
+ */
+
+ return remove_virtual_nfs_aces(psd);
+}
+
+/****************************************************************************
+ * VFS ops
+ ****************************************************************************/
+
+static int fruit_connect(vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ int rc;
+ char *list = NULL, *newlist = NULL;
+ struct fruit_config_data *config;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ DEBUG(10, ("fruit_connect\n"));
+
+ rc = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (rc < 0) {
+ return rc;
+ }
+
+ rc = init_fruit_config(handle);
+ if (rc != 0) {
+ return rc;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ if (config->veto_appledouble) {
+ list = lp_veto_files(talloc_tos(), lp_sub, SNUM(handle->conn));
+
+ if (list) {
+ if (strstr(list, "/" ADOUBLE_NAME_PREFIX "*/") == NULL) {
+ newlist = talloc_asprintf(
+ list,
+ "%s/" ADOUBLE_NAME_PREFIX "*/",
+ list);
+ lp_do_parameter(SNUM(handle->conn),
+ "veto files",
+ newlist);
+ }
+ } else {
+ lp_do_parameter(SNUM(handle->conn),
+ "veto files",
+ "/" ADOUBLE_NAME_PREFIX "*/");
+ }
+
+ TALLOC_FREE(list);
+ }
+
+ if (config->encoding == FRUIT_ENC_NATIVE) {
+ lp_do_parameter(SNUM(handle->conn),
+ "catia:mappings",
+ macos_string_replace_map);
+ }
+
+ if (config->time_machine) {
+ DBG_NOTICE("Enabling durable handles for Time Machine "
+ "support on [%s]\n", service);
+ lp_do_parameter(SNUM(handle->conn), "durable handles", "yes");
+ lp_do_parameter(SNUM(handle->conn), "kernel oplocks", "no");
+ lp_do_parameter(SNUM(handle->conn), "kernel share modes", "no");
+ if (!lp_strict_sync(SNUM(handle->conn))) {
+ DBG_WARNING("Time Machine without strict sync is not "
+ "recommended!\n");
+ }
+ lp_do_parameter(SNUM(handle->conn), "posix locking", "no");
+ }
+
+ return rc;
+}
+
+static void fio_ref_destroy_fn(void *p_data)
+{
+ struct fio *ref_fio = (struct fio *)p_data;
+ if (ref_fio->real_fio != NULL) {
+ SMB_ASSERT(ref_fio->real_fio->ad_fsp == ref_fio->fsp);
+ ref_fio->real_fio->ad_fsp = NULL;
+ ref_fio->real_fio = NULL;
+ }
+}
+
+static void fio_close_ad_fsp(struct fio *fio)
+{
+ if (fio->ad_fsp != NULL) {
+ fd_close(fio->ad_fsp);
+ file_free(NULL, fio->ad_fsp);
+ /* fio_ref_destroy_fn() should have cleared this */
+ SMB_ASSERT(fio->ad_fsp == NULL);
+ }
+}
+
+static void fio_destroy_fn(void *p_data)
+{
+ struct fio *fio = (struct fio *)p_data;
+ fio_close_ad_fsp(fio);
+}
+
+static int fruit_open_meta_stream(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ struct fruit_config_data *config = NULL;
+ struct fio *fio = NULL;
+ struct vfs_open_how how = {
+ .flags = flags & ~O_CREAT,
+ .mode = mode,
+ };
+ int fd;
+
+ DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn);
+ fio->handle = handle;
+ fio->fsp = fsp;
+ fio->type = ADOUBLE_META;
+ fio->config = config;
+
+ fd = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ &how);
+ if (fd != -1) {
+ return fd;
+ }
+
+ if (!(flags & O_CREAT)) {
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ return -1;
+ }
+
+ fd = vfs_fake_fd();
+ if (fd == -1) {
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ return -1;
+ }
+
+ fio->fake_fd = true;
+ fio->flags = flags;
+ fio->mode = mode;
+
+ return fd;
+}
+
+static int fruit_open_meta_netatalk(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ struct fruit_config_data *config = NULL;
+ struct fio *fio = NULL;
+ struct adouble *ad = NULL;
+ bool meta_exists = false;
+ int fd;
+
+ DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ /*
+ * We know this is a stream open, so fsp->base_fsp must
+ * already be open.
+ */
+ SMB_ASSERT(fsp_is_alternate_stream(fsp));
+ SMB_ASSERT(fsp->base_fsp->fsp_name->fsp == fsp->base_fsp);
+
+ ad = ad_get(talloc_tos(), handle, fsp->base_fsp->fsp_name, ADOUBLE_META);
+ if (ad != NULL) {
+ meta_exists = true;
+ }
+
+ TALLOC_FREE(ad);
+
+ if (!meta_exists && !(flags & O_CREAT)) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ fd = vfs_fake_fd();
+ if (fd == -1) {
+ return -1;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn);
+ fio->handle = handle;
+ fio->fsp = fsp;
+ fio->type = ADOUBLE_META;
+ fio->config = config;
+ fio->fake_fd = true;
+ fio->flags = flags;
+ fio->mode = mode;
+
+ return fd;
+}
+
+static int fruit_open_meta(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp, int flags, mode_t mode)
+{
+ int fd;
+ struct fruit_config_data *config = NULL;
+
+ DBG_DEBUG("path [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ switch (config->meta) {
+ case FRUIT_META_STREAM:
+ fd = fruit_open_meta_stream(handle, dirfsp, smb_fname,
+ fsp, flags, mode);
+ break;
+
+ case FRUIT_META_NETATALK:
+ fd = fruit_open_meta_netatalk(handle, dirfsp, smb_fname,
+ fsp, flags, mode);
+ break;
+
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", config->meta);
+ return -1;
+ }
+
+ DBG_DEBUG("path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd);
+
+ return fd;
+}
+
+static int fruit_open_rsrc_adouble(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+ int rc = 0;
+ struct fruit_config_data *config = NULL;
+ struct files_struct *ad_fsp = NULL;
+ struct fio *fio = NULL;
+ struct fio *ref_fio = NULL;
+ NTSTATUS status;
+ int fd = -1;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ if ((!(flags & O_CREAT)) &&
+ S_ISDIR(fsp->base_fsp->fsp_name->st.st_ex_mode))
+ {
+ /* sorry, but directories don't have a resource fork */
+ errno = ENOENT;
+ rc = -1;
+ goto exit;
+ }
+
+ /*
+ * We return a fake_fd to the vfs modules above,
+ * while we open an internal backend fsp for the
+ * '._' file for the next vfs modules.
+ *
+ * Note that adouble_open_from_base_fsp() recurses
+ * into fruit_openat(), but it'll just pass to
+ * the next module as just opens a flat file on
+ * disk.
+ */
+
+ fd = vfs_fake_fd();
+ if (fd == -1) {
+ rc = fd;
+ goto exit;
+ }
+
+ status = adouble_open_from_base_fsp(fsp->conn->cwd_fsp,
+ fsp->base_fsp,
+ ADOUBLE_RSRC,
+ flags,
+ mode,
+ &ad_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ rc = -1;
+ goto exit;
+ }
+
+ /*
+ * Now we need to glue both handles together,
+ * so that they automatically detach each other
+ * on close.
+ */
+ fio = fruit_get_complete_fio(handle, fsp);
+ if (fio == NULL) {
+ DBG_ERR("fio=NULL for [%s]\n", fsp_str_dbg(fsp));
+ errno = EBADF;
+ rc = -1;
+ goto exit;
+ }
+
+ ref_fio = VFS_ADD_FSP_EXTENSION(handle, ad_fsp,
+ struct fio,
+ fio_ref_destroy_fn);
+ if (ref_fio == NULL) {
+ int saved_errno = errno;
+ fd_close(ad_fsp);
+ file_free(NULL, ad_fsp);
+ ad_fsp = NULL;
+ errno = saved_errno;
+ rc = -1;
+ goto exit;
+ }
+
+ SMB_ASSERT(ref_fio->fsp == NULL);
+ ref_fio->handle = handle;
+ ref_fio->fsp = ad_fsp;
+ ref_fio->type = ADOUBLE_RSRC;
+ ref_fio->config = config;
+ ref_fio->real_fio = fio;
+ SMB_ASSERT(fio->ad_fsp == NULL);
+ fio->ad_fsp = ad_fsp;
+ fio->fake_fd = true;
+
+exit:
+
+ DEBUG(10, ("fruit_open resource fork: rc=%d\n", rc));
+ if (rc != 0) {
+ int saved_errno = errno;
+ if (fd != -1) {
+ vfs_fake_fd_close(fd);
+ }
+ errno = saved_errno;
+ return rc;
+ }
+ return fd;
+}
+
+static int fruit_open_rsrc_xattr(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ int flags,
+ mode_t mode)
+{
+#ifdef HAVE_ATTROPEN
+ int fd = -1;
+
+ /*
+ * As there's no attropenat() this is only going to work with AT_FDCWD.
+ */
+ SMB_ASSERT(fsp_get_pathref_fd(dirfsp) == AT_FDCWD);
+
+ fd = attropen(smb_fname->base_name,
+ AFPRESOURCE_EA_NETATALK,
+ flags,
+ mode);
+ if (fd == -1) {
+ return -1;
+ }
+
+ return fd;
+
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static int fruit_open_rsrc(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp, int flags, mode_t mode)
+{
+ int fd;
+ struct fruit_config_data *config = NULL;
+ struct fio *fio = NULL;
+
+ DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ fio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct fio, fio_destroy_fn);
+ fio->handle = handle;
+ fio->fsp = fsp;
+ fio->type = ADOUBLE_RSRC;
+ fio->config = config;
+
+ switch (config->rsrc) {
+ case FRUIT_RSRC_STREAM: {
+ struct vfs_open_how how = {
+ .flags = flags, .mode = mode,
+ };
+ fd = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ &how);
+ break;
+ }
+
+ case FRUIT_RSRC_ADFILE:
+ fd = fruit_open_rsrc_adouble(handle, dirfsp, smb_fname,
+ fsp, flags, mode);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ fd = fruit_open_rsrc_xattr(handle, dirfsp, smb_fname,
+ fsp, flags, mode);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc);
+ errno = EINVAL;
+ return -1;
+ }
+
+ DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd);
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ return fd;
+}
+
+static int fruit_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int fd;
+
+ DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ if (!is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ if (how->resolve != 0) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ SMB_ASSERT(fsp_is_alternate_stream(fsp));
+
+ if (is_afpinfo_stream(smb_fname->stream_name)) {
+ fd = fruit_open_meta(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how->flags,
+ how->mode);
+ } else if (is_afpresource_stream(smb_fname->stream_name)) {
+ fd = fruit_open_rsrc(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how->flags,
+ how->mode);
+ } else {
+ fd = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(smb_fname), fd);
+
+ /* Prevent reopen optimisation */
+ fsp->fsp_flags.have_proc_fds = false;
+ return fd;
+}
+
+static int fruit_close_meta(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ int ret;
+ struct fruit_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ switch (config->meta) {
+ case FRUIT_META_STREAM:
+ {
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ if (fio == NULL) {
+ return -1;
+ }
+ if (fio->fake_fd) {
+ ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp));
+ fsp_set_fd(fsp, -1);
+ } else {
+ ret = SMB_VFS_NEXT_CLOSE(handle, fsp);
+ }
+ break;
+ }
+ case FRUIT_META_NETATALK:
+ ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp));
+ fsp_set_fd(fsp, -1);
+ break;
+
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", config->meta);
+ return -1;
+ }
+
+ return ret;
+}
+
+
+static int fruit_close_rsrc(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ int ret;
+ struct fruit_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ switch (config->rsrc) {
+ case FRUIT_RSRC_STREAM:
+ ret = SMB_VFS_NEXT_CLOSE(handle, fsp);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ {
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ if (fio == NULL) {
+ return -1;
+ }
+ fio_close_ad_fsp(fio);
+ ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp));
+ fsp_set_fd(fsp, -1);
+ break;
+ }
+
+ case FRUIT_RSRC_XATTR:
+ ret = vfs_fake_fd_close(fsp_get_pathref_fd(fsp));
+ fsp_set_fd(fsp, -1);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int fruit_close(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ int ret;
+ int fd;
+
+ fd = fsp_get_pathref_fd(fsp);
+
+ DBG_DEBUG("Path [%s] fd [%d]\n", smb_fname_str_dbg(fsp->fsp_name), fd);
+
+ if (!fsp_is_alternate_stream(fsp)) {
+ return SMB_VFS_NEXT_CLOSE(handle, fsp);
+ }
+
+ if (is_afpinfo_stream(fsp->fsp_name->stream_name)) {
+ ret = fruit_close_meta(handle, fsp);
+ } else if (is_afpresource_stream(fsp->fsp_name->stream_name)) {
+ ret = fruit_close_rsrc(handle, fsp);
+ } else {
+ ret = SMB_VFS_NEXT_CLOSE(handle, fsp);
+ }
+
+ return ret;
+}
+
+static int fruit_renameat(struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int rc = -1;
+ struct fruit_config_data *config = NULL;
+ struct smb_filename *src_adp_smb_fname = NULL;
+ struct smb_filename *dst_adp_smb_fname = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ if (!VALID_STAT(smb_fname_src->st)) {
+ DBG_ERR("Need valid stat for [%s]\n",
+ smb_fname_str_dbg(smb_fname_src));
+ return -1;
+ }
+
+ rc = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ if (rc != 0) {
+ return -1;
+ }
+
+ if ((config->rsrc != FRUIT_RSRC_ADFILE) ||
+ (!S_ISREG(smb_fname_src->st.st_ex_mode)))
+ {
+ return 0;
+ }
+
+ rc = adouble_path(talloc_tos(), smb_fname_src, &src_adp_smb_fname);
+ if (rc != 0) {
+ goto done;
+ }
+
+ rc = adouble_path(talloc_tos(), smb_fname_dst, &dst_adp_smb_fname);
+ if (rc != 0) {
+ goto done;
+ }
+
+ DBG_DEBUG("%s -> %s\n",
+ smb_fname_str_dbg(src_adp_smb_fname),
+ smb_fname_str_dbg(dst_adp_smb_fname));
+
+ rc = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ src_adp_smb_fname,
+ dstfsp,
+ dst_adp_smb_fname);
+ if (errno == ENOENT) {
+ rc = 0;
+ }
+
+done:
+ TALLOC_FREE(src_adp_smb_fname);
+ TALLOC_FREE(dst_adp_smb_fname);
+ return rc;
+}
+
+static int fruit_unlink_meta_stream(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ 0);
+}
+
+static int fruit_unlink_meta_netatalk(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ SMB_ASSERT(smb_fname->fsp != NULL);
+ SMB_ASSERT(fsp_is_alternate_stream(smb_fname->fsp));
+ return SMB_VFS_FREMOVEXATTR(smb_fname->fsp->base_fsp,
+ AFPINFO_EA_NETATALK);
+}
+
+static int fruit_unlink_meta(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ struct fruit_config_data *config = NULL;
+ int rc;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ switch (config->meta) {
+ case FRUIT_META_STREAM:
+ rc = fruit_unlink_meta_stream(handle,
+ dirfsp,
+ smb_fname);
+ break;
+
+ case FRUIT_META_NETATALK:
+ rc = fruit_unlink_meta_netatalk(handle, smb_fname);
+ break;
+
+ default:
+ DBG_ERR("Unsupported meta config [%d]\n", config->meta);
+ return -1;
+ }
+
+ return rc;
+}
+
+static int fruit_unlink_rsrc_stream(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ bool force_unlink)
+{
+ int ret;
+
+ if (!force_unlink) {
+ struct smb_filename *full_fname = NULL;
+ off_t size;
+
+ /*
+ * TODO: use SMB_VFS_STATX() once we have it.
+ */
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ /*
+ * 0 byte resource fork streams are not listed by
+ * vfs_streaminfo, as a result stream cleanup/deletion of file
+ * deletion doesn't remove the resourcefork stream.
+ */
+
+ ret = SMB_VFS_NEXT_STAT(handle, full_fname);
+ if (ret != 0) {
+ TALLOC_FREE(full_fname);
+ DBG_ERR("stat [%s] failed [%s]\n",
+ smb_fname_str_dbg(full_fname), strerror(errno));
+ return -1;
+ }
+
+ size = full_fname->st.st_ex_size;
+ TALLOC_FREE(full_fname);
+
+ if (size > 0) {
+ /* OS X ignores resource fork stream delete requests */
+ return 0;
+ }
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ 0);
+ if ((ret != 0) && (errno == ENOENT) && force_unlink) {
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int fruit_unlink_rsrc_adouble(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ bool force_unlink)
+{
+ int rc;
+ struct adouble *ad = NULL;
+ struct smb_filename *adp_smb_fname = NULL;
+
+ if (!force_unlink) {
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ ad = ad_get(talloc_tos(), handle, full_fname,
+ ADOUBLE_RSRC);
+ TALLOC_FREE(full_fname);
+ if (ad == NULL) {
+ errno = ENOENT;
+ return -1;
+ }
+
+
+ /*
+ * 0 byte resource fork streams are not listed by
+ * vfs_streaminfo, as a result stream cleanup/deletion of file
+ * deletion doesn't remove the resourcefork stream.
+ */
+
+ if (ad_getentrylen(ad, ADEID_RFORK) > 0) {
+ /* OS X ignores resource fork stream delete requests */
+ TALLOC_FREE(ad);
+ return 0;
+ }
+
+ TALLOC_FREE(ad);
+ }
+
+ rc = adouble_path(talloc_tos(), smb_fname, &adp_smb_fname);
+ if (rc != 0) {
+ return -1;
+ }
+
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ adp_smb_fname,
+ 0);
+ TALLOC_FREE(adp_smb_fname);
+ if ((rc != 0) && (errno == ENOENT || errno == ENAMETOOLONG) && force_unlink) {
+ rc = 0;
+ }
+
+ return rc;
+}
+
+static int fruit_unlink_rsrc_xattr(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ bool force_unlink)
+{
+ /*
+ * OS X ignores resource fork stream delete requests, so nothing to do
+ * here. Removing the file will remove the xattr anyway, so we don't
+ * have to take care of removing 0 byte resource forks that could be
+ * left behind.
+ */
+ return 0;
+}
+
+static int fruit_unlink_rsrc(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ bool force_unlink)
+{
+ struct fruit_config_data *config = NULL;
+ int rc;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ switch (config->rsrc) {
+ case FRUIT_RSRC_STREAM:
+ rc = fruit_unlink_rsrc_stream(handle,
+ dirfsp,
+ smb_fname,
+ force_unlink);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ rc = fruit_unlink_rsrc_adouble(handle,
+ dirfsp,
+ smb_fname,
+ force_unlink);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ rc = fruit_unlink_rsrc_xattr(handle, smb_fname, force_unlink);
+ break;
+
+ default:
+ DBG_ERR("Unsupported rsrc config [%d]\n", config->rsrc);
+ return -1;
+ }
+
+ return rc;
+}
+
+static int fruit_fchmod(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ mode_t mode)
+{
+ int rc = -1;
+ struct fruit_config_data *config = NULL;
+ struct smb_filename *smb_fname_adp = NULL;
+ const struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ rc = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+ if (rc != 0) {
+ return rc;
+ }
+
+ smb_fname = fsp->fsp_name;
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ if (config->rsrc != FRUIT_RSRC_ADFILE) {
+ return 0;
+ }
+
+ if (!VALID_STAT(smb_fname->st)) {
+ return 0;
+ }
+
+ if (!S_ISREG(smb_fname->st.st_ex_mode)) {
+ return 0;
+ }
+
+ rc = adouble_path(talloc_tos(), smb_fname, &smb_fname_adp);
+ if (rc != 0) {
+ return -1;
+ }
+
+ status = openat_pathref_fsp(handle->conn->cwd_fsp,
+ smb_fname_adp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* detect ENOENT (mapped to OBJECT_NAME_NOT_FOUND) */
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)){
+ rc = 0;
+ goto out;
+ }
+ rc = -1;
+ goto out;
+ }
+
+ DBG_DEBUG("%s\n", smb_fname_adp->base_name);
+
+ rc = SMB_VFS_NEXT_FCHMOD(handle, smb_fname_adp->fsp, mode);
+ if (errno == ENOENT) {
+ rc = 0;
+ }
+out:
+ TALLOC_FREE(smb_fname_adp);
+ return rc;
+}
+
+static int fruit_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct fruit_config_data *config = NULL;
+ struct smb_filename *rsrc_smb_fname = NULL;
+ int ret;
+
+ if (flags & AT_REMOVEDIR) {
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ AT_REMOVEDIR);
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ if (is_afpinfo_stream(smb_fname->stream_name)) {
+ return fruit_unlink_meta(handle,
+ dirfsp,
+ smb_fname);
+ } else if (is_afpresource_stream(smb_fname->stream_name)) {
+ return fruit_unlink_rsrc(handle,
+ dirfsp,
+ smb_fname,
+ false);
+ } else if (is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ 0);
+ } else if (is_adouble_file(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ 0);
+ }
+
+ /*
+ * A request to delete the base file. Because 0 byte resource
+ * fork streams are not listed by fruit_streaminfo,
+ * delete_all_streams() can't remove 0 byte resource fork
+ * streams, so we have to cleanup this here.
+ */
+ rsrc_smb_fname = synthetic_smb_fname(talloc_tos(),
+ smb_fname->base_name,
+ AFPRESOURCE_STREAM_NAME,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (rsrc_smb_fname == NULL) {
+ return -1;
+ }
+
+ ret = fruit_unlink_rsrc(handle, dirfsp, rsrc_smb_fname, true);
+ if ((ret != 0) && (errno != ENOENT)) {
+ DBG_ERR("Forced unlink of [%s] failed [%s]\n",
+ smb_fname_str_dbg(rsrc_smb_fname), strerror(errno));
+ TALLOC_FREE(rsrc_smb_fname);
+ return -1;
+ }
+ TALLOC_FREE(rsrc_smb_fname);
+
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ 0);
+}
+
+static ssize_t fruit_pread_meta_stream(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ ssize_t nread;
+ int ret;
+
+ if ((fio == NULL) || fio->fake_fd) {
+ return -1;
+ }
+
+ nread = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+ if (nread == -1 || nread == n) {
+ return nread;
+ }
+
+ DBG_ERR("Removing [%s] after short read [%zd]\n",
+ fsp_str_dbg(fsp), nread);
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ fsp->conn->cwd_fsp,
+ fsp->fsp_name,
+ 0);
+ if (ret != 0) {
+ DBG_ERR("Removing [%s] failed\n", fsp_str_dbg(fsp));
+ return -1;
+ }
+
+ errno = EINVAL;
+ return -1;
+}
+
+static ssize_t fruit_pread_meta_adouble(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ AfpInfo *ai = NULL;
+ struct adouble *ad = NULL;
+ char afpinfo_buf[AFP_INFO_SIZE];
+ char *p = NULL;
+ ssize_t nread;
+
+ ai = afpinfo_new(talloc_tos());
+ if (ai == NULL) {
+ return -1;
+ }
+
+ ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META);
+ if (ad == NULL) {
+ nread = -1;
+ goto fail;
+ }
+
+ p = ad_get_entry(ad, ADEID_FINDERI);
+ if (p == NULL) {
+ DBG_ERR("No ADEID_FINDERI for [%s]\n", fsp_str_dbg(fsp));
+ nread = -1;
+ goto fail;
+ }
+
+ memcpy(&ai->afpi_FinderInfo[0], p, ADEDLEN_FINDERI);
+
+ nread = afpinfo_pack(ai, afpinfo_buf);
+ if (nread != AFP_INFO_SIZE) {
+ nread = -1;
+ goto fail;
+ }
+
+ memcpy(data, afpinfo_buf, n);
+ nread = n;
+
+fail:
+ TALLOC_FREE(ai);
+ return nread;
+}
+
+static ssize_t fruit_pread_meta(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ ssize_t nread;
+ ssize_t to_return;
+
+ /*
+ * OS X has a off-by-1 error in the offset calculation, so we're
+ * bug compatible here. It won't hurt, as any relevant real
+ * world read requests from the AFP_AfpInfo stream will be
+ * offset=0 n=60. offset is ignored anyway, see below.
+ */
+ if ((offset < 0) || (offset >= AFP_INFO_SIZE + 1)) {
+ return 0;
+ }
+
+ if (fio == NULL) {
+ DBG_ERR("Failed to fetch fsp extension\n");
+ return -1;
+ }
+
+ /* Yes, macOS always reads from offset 0 */
+ offset = 0;
+ to_return = MIN(n, AFP_INFO_SIZE);
+
+ switch (fio->config->meta) {
+ case FRUIT_META_STREAM:
+ nread = fruit_pread_meta_stream(handle, fsp, data,
+ to_return, offset);
+ break;
+
+ case FRUIT_META_NETATALK:
+ nread = fruit_pread_meta_adouble(handle, fsp, data,
+ to_return, offset);
+ break;
+
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta);
+ return -1;
+ }
+
+ if (nread == -1 && fio->fake_fd) {
+ AfpInfo *ai = NULL;
+ char afpinfo_buf[AFP_INFO_SIZE];
+
+ ai = afpinfo_new(talloc_tos());
+ if (ai == NULL) {
+ return -1;
+ }
+
+ nread = afpinfo_pack(ai, afpinfo_buf);
+ TALLOC_FREE(ai);
+ if (nread != AFP_INFO_SIZE) {
+ return -1;
+ }
+
+ memcpy(data, afpinfo_buf, to_return);
+ return to_return;
+ }
+
+ return nread;
+}
+
+static ssize_t fruit_pread_rsrc_stream(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+}
+
+static ssize_t fruit_pread_rsrc_xattr(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+}
+
+static ssize_t fruit_pread_rsrc_adouble(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ struct adouble *ad = NULL;
+ ssize_t nread;
+
+ if (fio == NULL || fio->ad_fsp == NULL) {
+ DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp));
+ errno = EBADF;
+ return -1;
+ }
+
+ ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ DBG_ERR("ad_fget [%s] failed [%s]\n",
+ fsp_str_dbg(fio->ad_fsp), strerror(errno));
+ return -1;
+ }
+
+ nread = SMB_VFS_NEXT_PREAD(handle, fio->ad_fsp, data, n,
+ offset + ad_getentryoff(ad, ADEID_RFORK));
+
+ TALLOC_FREE(ad);
+ return nread;
+}
+
+static ssize_t fruit_pread_rsrc(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ ssize_t nread;
+
+ if (fio == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ switch (fio->config->rsrc) {
+ case FRUIT_RSRC_STREAM:
+ nread = fruit_pread_rsrc_stream(handle, fsp, data, n, offset);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ nread = fruit_pread_rsrc_adouble(handle, fsp, data, n, offset);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ nread = fruit_pread_rsrc_xattr(handle, fsp, data, n, offset);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc);
+ return -1;
+ }
+
+ return nread;
+}
+
+static ssize_t fruit_pread(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ ssize_t nread;
+
+ DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n",
+ fsp_str_dbg(fsp), (intmax_t)offset, n);
+
+ if (fio == NULL) {
+ return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+ }
+
+ if (fio->type == ADOUBLE_META) {
+ nread = fruit_pread_meta(handle, fsp, data, n, offset);
+ } else {
+ nread = fruit_pread_rsrc(handle, fsp, data, n, offset);
+ }
+
+ DBG_DEBUG("Path [%s] nread [%zd]\n", fsp_str_dbg(fsp), nread);
+ return nread;
+}
+
+static bool fruit_must_handle_aio_stream(struct fio *fio)
+{
+ if (fio == NULL) {
+ return false;
+ };
+
+ if (fio->type == ADOUBLE_META) {
+ return true;
+ }
+
+ if ((fio->type == ADOUBLE_RSRC) &&
+ (fio->config->rsrc == FRUIT_RSRC_ADFILE))
+ {
+ return true;
+ }
+
+ return false;
+}
+
+struct fruit_pread_state {
+ ssize_t nread;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void fruit_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *fruit_pread_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct fruit_pread_state *state = NULL;
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fruit_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (fruit_must_handle_aio_stream(fio)) {
+ state->nread = SMB_VFS_PREAD(fsp, data, n, offset);
+ if (state->nread != n) {
+ if (state->nread != -1) {
+ errno = EIO;
+ }
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp,
+ data, n, offset);
+ if (tevent_req_nomem(req, subreq)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fruit_pread_done, req);
+ return req;
+}
+
+static void fruit_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fruit_pread_state *state = tevent_req_data(
+ req, struct fruit_pread_state);
+
+ state->nread = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ if (tevent_req_error(req, state->vfs_aio_state.error)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static ssize_t fruit_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct fruit_pread_state *state = tevent_req_data(
+ req, struct fruit_pread_state);
+ ssize_t retval = -1;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->nread;
+ tevent_req_received(req);
+ return retval;
+}
+
+static ssize_t fruit_pwrite_meta_stream(vfs_handle_struct *handle,
+ files_struct *fsp, const void *indata,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ const void *data = indata;
+ char afpinfo_buf[AFP_INFO_SIZE];
+ AfpInfo *ai = NULL;
+ size_t nwritten;
+ int ret;
+ bool ok;
+
+ DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n",
+ fsp_str_dbg(fsp), (intmax_t)offset, n);
+
+ if (fio == NULL) {
+ return -1;
+ }
+
+ if (fio->fake_fd) {
+ struct vfs_open_how how = {
+ .flags = fio->flags, .mode = fio->mode,
+ };
+ int fd = fsp_get_pathref_fd(fsp);
+
+ ret = vfs_fake_fd_close(fd);
+ fsp_set_fd(fsp, -1);
+ if (ret != 0) {
+ DBG_ERR("Close [%s] failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ return -1;
+ }
+
+ fd = SMB_VFS_NEXT_OPENAT(handle,
+ NULL, /* opening a stream */
+ fsp->fsp_name,
+ fsp,
+ &how);
+ if (fd == -1) {
+ DBG_ERR("On-demand create [%s] in write failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ return -1;
+ }
+ fsp_set_fd(fsp, fd);
+ fio->fake_fd = false;
+ }
+
+ ai = afpinfo_unpack(talloc_tos(), data, fio->config->validate_afpinfo);
+ if (ai == NULL) {
+ return -1;
+ }
+
+ if (ai_empty_finderinfo(ai)) {
+ /*
+ * Writing an all 0 blob to the metadata stream results in the
+ * stream being removed on a macOS server. This ensures we
+ * behave the same and it verified by the "delete AFP_AfpInfo by
+ * writing all 0" test.
+ */
+ ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, 0);
+ if (ret != 0) {
+ DBG_ERR("SMB_VFS_NEXT_FTRUNCATE on [%s] failed\n",
+ fsp_str_dbg(fsp));
+ return -1;
+ }
+
+ ok = set_delete_on_close(
+ fsp,
+ true,
+ handle->conn->session_info->security_token,
+ handle->conn->session_info->unix_token);
+ if (!ok) {
+ DBG_ERR("set_delete_on_close on [%s] failed\n",
+ fsp_str_dbg(fsp));
+ return -1;
+ }
+ return n;
+ }
+
+ if (!fio->config->validate_afpinfo) {
+ /*
+ * Ensure the buffer contains a valid header, so marshall
+ * the data from the afpinfo struck back into a buffer
+ * and write that instead of the possibly malformed data
+ * we got from the client.
+ */
+ nwritten = afpinfo_pack(ai, afpinfo_buf);
+ if (nwritten != AFP_INFO_SIZE) {
+ errno = EINVAL;
+ return -1;
+ }
+ data = afpinfo_buf;
+ }
+
+ nwritten = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+ if (nwritten != n) {
+ return -1;
+ }
+
+ return n;
+}
+
+static ssize_t fruit_pwrite_meta_netatalk(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ struct fruit_config_data *config = NULL;
+ struct adouble *ad = NULL;
+ AfpInfo *ai = NULL;
+ char *p = NULL;
+ int ret;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ ai = afpinfo_unpack(talloc_tos(), data, config->validate_afpinfo);
+ if (ai == NULL) {
+ return -1;
+ }
+
+ ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META);
+ if (ad == NULL) {
+ ad = ad_init(talloc_tos(), ADOUBLE_META);
+ if (ad == NULL) {
+ return -1;
+ }
+ }
+ p = ad_get_entry(ad, ADEID_FINDERI);
+ if (p == NULL) {
+ DBG_ERR("No ADEID_FINDERI for [%s]\n", fsp_str_dbg(fsp));
+ TALLOC_FREE(ad);
+ return -1;
+ }
+
+ memcpy(p, &ai->afpi_FinderInfo[0], ADEDLEN_FINDERI);
+
+ ret = ad_fset(handle, ad, fsp);
+ if (ret != 0) {
+ DBG_ERR("ad_pwrite [%s] failed\n", fsp_str_dbg(fsp));
+ TALLOC_FREE(ad);
+ return -1;
+ }
+
+ TALLOC_FREE(ad);
+
+ if (!ai_empty_finderinfo(ai)) {
+ return n;
+ }
+
+ /*
+ * Writing an all 0 blob to the metadata stream results in the stream
+ * being removed on a macOS server. This ensures we behave the same and
+ * it verified by the "delete AFP_AfpInfo by writing all 0" test.
+ */
+
+ ok = set_delete_on_close(
+ fsp,
+ true,
+ handle->conn->session_info->security_token,
+ handle->conn->session_info->unix_token);
+ if (!ok) {
+ DBG_ERR("set_delete_on_close on [%s] failed\n",
+ fsp_str_dbg(fsp));
+ return -1;
+ }
+
+ return n;
+}
+
+static ssize_t fruit_pwrite_meta(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ ssize_t nwritten;
+ uint8_t buf[AFP_INFO_SIZE];
+ size_t to_write;
+ size_t to_copy;
+ int cmp;
+
+ if (fio == NULL) {
+ DBG_ERR("Failed to fetch fsp extension\n");
+ return -1;
+ }
+
+ if (n < 3) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (offset != 0 && n < 60) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (fio->config->validate_afpinfo) {
+ cmp = memcmp(data, "AFP", 3);
+ if (cmp != 0) {
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ if (n <= AFP_OFF_FinderInfo) {
+ /*
+ * Nothing to do here really, just return
+ */
+ return n;
+ }
+
+ offset = 0;
+
+ to_copy = n;
+ if (to_copy > AFP_INFO_SIZE) {
+ to_copy = AFP_INFO_SIZE;
+ }
+ memcpy(buf, data, to_copy);
+
+ to_write = n;
+ if (to_write != AFP_INFO_SIZE) {
+ to_write = AFP_INFO_SIZE;
+ }
+
+ switch (fio->config->meta) {
+ case FRUIT_META_STREAM:
+ nwritten = fruit_pwrite_meta_stream(handle,
+ fsp,
+ buf,
+ to_write,
+ offset);
+ break;
+
+ case FRUIT_META_NETATALK:
+ nwritten = fruit_pwrite_meta_netatalk(handle,
+ fsp,
+ buf,
+ to_write,
+ offset);
+ break;
+
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta);
+ return -1;
+ }
+
+ if (nwritten != to_write) {
+ return -1;
+ }
+
+ /*
+ * Return the requested amount, verified against macOS SMB server
+ */
+ return n;
+}
+
+static ssize_t fruit_pwrite_rsrc_stream(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+}
+
+static ssize_t fruit_pwrite_rsrc_xattr(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+}
+
+static ssize_t fruit_pwrite_rsrc_adouble(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ struct adouble *ad = NULL;
+ ssize_t nwritten;
+ int ret;
+
+ if (fio == NULL || fio->ad_fsp == NULL) {
+ DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp));
+ errno = EBADF;
+ return -1;
+ }
+
+ ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ DBG_ERR("ad_fget [%s] failed [%s]\n",
+ fsp_str_dbg(fio->ad_fsp), strerror(errno));
+ return -1;
+ }
+
+ nwritten = SMB_VFS_NEXT_PWRITE(handle, fio->ad_fsp, data, n,
+ offset + ad_getentryoff(ad, ADEID_RFORK));
+ if (nwritten != n) {
+ DBG_ERR("Short write on [%s] [%zd/%zd]\n",
+ fsp_str_dbg(fio->ad_fsp), nwritten, n);
+ TALLOC_FREE(ad);
+ return -1;
+ }
+
+ if ((n + offset) > ad_getentrylen(ad, ADEID_RFORK)) {
+ ad_setentrylen(ad, ADEID_RFORK, n + offset);
+ ret = ad_fset(handle, ad, fio->ad_fsp);
+ if (ret != 0) {
+ DBG_ERR("ad_pwrite [%s] failed\n", fsp_str_dbg(fio->ad_fsp));
+ TALLOC_FREE(ad);
+ return -1;
+ }
+ }
+
+ TALLOC_FREE(ad);
+ return n;
+}
+
+static ssize_t fruit_pwrite_rsrc(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ ssize_t nwritten;
+
+ if (fio == NULL) {
+ DBG_ERR("Failed to fetch fsp extension\n");
+ return -1;
+ }
+
+ switch (fio->config->rsrc) {
+ case FRUIT_RSRC_STREAM:
+ nwritten = fruit_pwrite_rsrc_stream(handle, fsp, data, n, offset);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ nwritten = fruit_pwrite_rsrc_adouble(handle, fsp, data, n, offset);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ nwritten = fruit_pwrite_rsrc_xattr(handle, fsp, data, n, offset);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc);
+ return -1;
+ }
+
+ return nwritten;
+}
+
+static ssize_t fruit_pwrite(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ ssize_t nwritten;
+
+ DBG_DEBUG("Path [%s] offset=%"PRIdMAX", size=%zd\n",
+ fsp_str_dbg(fsp), (intmax_t)offset, n);
+
+ if (fio == NULL) {
+ return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+ }
+
+ if (fio->type == ADOUBLE_META) {
+ nwritten = fruit_pwrite_meta(handle, fsp, data, n, offset);
+ } else {
+ nwritten = fruit_pwrite_rsrc(handle, fsp, data, n, offset);
+ }
+
+ DBG_DEBUG("Path [%s] nwritten=%zd\n", fsp_str_dbg(fsp), nwritten);
+ return nwritten;
+}
+
+struct fruit_pwrite_state {
+ ssize_t nwritten;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void fruit_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *fruit_pwrite_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct fruit_pwrite_state *state = NULL;
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fruit_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (fruit_must_handle_aio_stream(fio)) {
+ state->nwritten = SMB_VFS_PWRITE(fsp, data, n, offset);
+ if (state->nwritten != n) {
+ if (state->nwritten != -1) {
+ errno = EIO;
+ }
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp,
+ data, n, offset);
+ if (tevent_req_nomem(req, subreq)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fruit_pwrite_done, req);
+ return req;
+}
+
+static void fruit_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fruit_pwrite_state *state = tevent_req_data(
+ req, struct fruit_pwrite_state);
+
+ state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ if (tevent_req_error(req, state->vfs_aio_state.error)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static ssize_t fruit_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct fruit_pwrite_state *state = tevent_req_data(
+ req, struct fruit_pwrite_state);
+ ssize_t retval = -1;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->nwritten;
+ tevent_req_received(req);
+ return retval;
+}
+
+struct fruit_fsync_state {
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void fruit_fsync_done(struct tevent_req *subreq);
+
+static struct tevent_req *fruit_fsync_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct fruit_fsync_state *state = NULL;
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fruit_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (fruit_must_handle_aio_stream(fio)) {
+ struct adouble *ad = NULL;
+
+ if (fio->type == ADOUBLE_META) {
+ /*
+ * We must never pass a fake_fd
+ * to lower level fsync calls.
+ * Everything is already done
+ * synchronously, so just return
+ * true.
+ */
+ SMB_ASSERT(fio->fake_fd);
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * We know the following must be true,
+ * as it's the condition for fruit_must_handle_aio_stream()
+ * to return true if fio->type == ADOUBLE_RSRC.
+ */
+ SMB_ASSERT(fio->config->rsrc == FRUIT_RSRC_ADFILE);
+ if (fio->ad_fsp == NULL) {
+ tevent_req_error(req, EBADF);
+ return tevent_req_post(req, ev);
+ }
+ ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return tevent_req_post(req, ev);
+ }
+ fsp = fio->ad_fsp;
+ }
+
+ subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp);
+ if (tevent_req_nomem(req, subreq)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fruit_fsync_done, req);
+ return req;
+}
+
+static void fruit_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fruit_fsync_state *state = tevent_req_data(
+ req, struct fruit_fsync_state);
+
+ state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->ret != 0) {
+ tevent_req_error(req, errno);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static int fruit_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct fruit_fsync_state *state = tevent_req_data(
+ req, struct fruit_fsync_state);
+ int retval = -1;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->ret;
+ tevent_req_received(req);
+ return retval;
+}
+
+/**
+ * Helper to stat/lstat the base file of an smb_fname.
+ */
+static int fruit_stat_base(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+ char *tmp_stream_name;
+ int rc;
+
+ tmp_stream_name = smb_fname->stream_name;
+ smb_fname->stream_name = NULL;
+ if (follow_links) {
+ rc = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ } else {
+ rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+ smb_fname->stream_name = tmp_stream_name;
+
+ DBG_DEBUG("fruit_stat_base [%s] dev [%ju] ino [%ju]\n",
+ smb_fname->base_name,
+ (uintmax_t)smb_fname->st.st_ex_dev,
+ (uintmax_t)smb_fname->st.st_ex_ino);
+ return rc;
+}
+
+static int fruit_stat_meta_stream(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+ int ret;
+ ino_t ino;
+
+ ret = fruit_stat_base(handle, smb_fname, false);
+ if (ret != 0) {
+ return -1;
+ }
+
+ ino = hash_inode(&smb_fname->st, smb_fname->stream_name);
+
+ if (follow_links) {
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ } else {
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+
+ smb_fname->st.st_ex_ino = ino;
+
+ return ret;
+}
+
+static int fruit_stat_meta_netatalk(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+ struct adouble *ad = NULL;
+
+ /* Populate the stat struct with info from the base file. */
+ if (fruit_stat_base(handle, smb_fname, follow_links) == -1) {
+ return -1;
+ }
+
+ ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname);
+ if (ad == NULL) {
+ DBG_INFO("fruit_stat_meta %s: %s\n",
+ smb_fname_str_dbg(smb_fname), strerror(errno));
+ errno = ENOENT;
+ return -1;
+ }
+ TALLOC_FREE(ad);
+
+ smb_fname->st.st_ex_size = AFP_INFO_SIZE;
+ smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st,
+ smb_fname->stream_name);
+ return 0;
+}
+
+static int fruit_stat_meta(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+ struct fruit_config_data *config = NULL;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ switch (config->meta) {
+ case FRUIT_META_STREAM:
+ ret = fruit_stat_meta_stream(handle, smb_fname, follow_links);
+ break;
+
+ case FRUIT_META_NETATALK:
+ ret = fruit_stat_meta_netatalk(handle, smb_fname, follow_links);
+ break;
+
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", config->meta);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int fruit_stat_rsrc_netatalk(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+ struct adouble *ad = NULL;
+ int ret;
+
+ ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ /* Populate the stat struct with info from the base file. */
+ ret = fruit_stat_base(handle, smb_fname, follow_links);
+ if (ret != 0) {
+ TALLOC_FREE(ad);
+ return -1;
+ }
+
+ smb_fname->st.st_ex_size = ad_getentrylen(ad, ADEID_RFORK);
+ smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st,
+ smb_fname->stream_name);
+ TALLOC_FREE(ad);
+ return 0;
+}
+
+static int fruit_stat_rsrc_stream(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+ int ret;
+
+ if (follow_links) {
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ } else {
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+
+ return ret;
+}
+
+static int fruit_stat_rsrc_xattr(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+#ifdef HAVE_ATTROPEN
+ int ret;
+ int fd = -1;
+
+ /* Populate the stat struct with info from the base file. */
+ ret = fruit_stat_base(handle, smb_fname, follow_links);
+ if (ret != 0) {
+ return -1;
+ }
+
+ fd = attropen(smb_fname->base_name,
+ AFPRESOURCE_EA_NETATALK,
+ O_RDONLY);
+ if (fd == -1) {
+ return 0;
+ }
+
+ ret = sys_fstat(fd, &smb_fname->st, false);
+ if (ret != 0) {
+ close(fd);
+ DBG_ERR("fstat [%s:%s] failed\n", smb_fname->base_name,
+ AFPRESOURCE_EA_NETATALK);
+ return -1;
+ }
+ close(fd);
+ fd = -1;
+
+ smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st,
+ smb_fname->stream_name);
+
+ return ret;
+
+#else
+ errno = ENOSYS;
+ return -1;
+#endif
+}
+
+static int fruit_stat_rsrc(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ bool follow_links)
+{
+ struct fruit_config_data *config = NULL;
+ int ret;
+
+ DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data, return -1);
+
+ switch (config->rsrc) {
+ case FRUIT_RSRC_STREAM:
+ ret = fruit_stat_rsrc_stream(handle, smb_fname, follow_links);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ ret = fruit_stat_rsrc_xattr(handle, smb_fname, follow_links);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ ret = fruit_stat_rsrc_netatalk(handle, smb_fname, follow_links);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", config->rsrc);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int fruit_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int rc = -1;
+
+ DEBUG(10, ("fruit_stat called for %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ if (!is_named_stream(smb_fname)) {
+ rc = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (rc == 0) {
+ update_btime(handle, smb_fname);
+ }
+ return rc;
+ }
+
+ /*
+ * Note if lp_posix_paths() is true, we can never
+ * get here as is_ntfs_stream_smb_fname() is
+ * always false. So we never need worry about
+ * not following links here.
+ */
+
+ if (is_afpinfo_stream(smb_fname->stream_name)) {
+ rc = fruit_stat_meta(handle, smb_fname, true);
+ } else if (is_afpresource_stream(smb_fname->stream_name)) {
+ rc = fruit_stat_rsrc(handle, smb_fname, true);
+ } else {
+ return SMB_VFS_NEXT_STAT(handle, smb_fname);
+ }
+
+ if (rc == 0) {
+ update_btime(handle, smb_fname);
+ smb_fname->st.st_ex_mode &= ~S_IFMT;
+ smb_fname->st.st_ex_mode |= S_IFREG;
+ smb_fname->st.st_ex_blocks =
+ smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1;
+ }
+ return rc;
+}
+
+static int fruit_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int rc = -1;
+
+ DEBUG(10, ("fruit_lstat called for %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ if (!is_named_stream(smb_fname)) {
+ rc = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (rc == 0) {
+ update_btime(handle, smb_fname);
+ }
+ return rc;
+ }
+
+ if (is_afpinfo_stream(smb_fname->stream_name)) {
+ rc = fruit_stat_meta(handle, smb_fname, false);
+ } else if (is_afpresource_stream(smb_fname->stream_name)) {
+ rc = fruit_stat_rsrc(handle, smb_fname, false);
+ } else {
+ return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+
+ if (rc == 0) {
+ update_btime(handle, smb_fname);
+ smb_fname->st.st_ex_mode &= ~S_IFMT;
+ smb_fname->st.st_ex_mode |= S_IFREG;
+ smb_fname->st.st_ex_blocks =
+ smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1;
+ }
+ return rc;
+}
+
+static int fruit_fstat_meta_stream(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ struct smb_filename smb_fname;
+ ino_t ino;
+ int ret;
+
+ if (fio == NULL) {
+ return -1;
+ }
+
+ if (fio->fake_fd) {
+ ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false);
+ if (ret != 0) {
+ return -1;
+ }
+
+ *sbuf = fsp->base_fsp->fsp_name->st;
+ sbuf->st_ex_size = AFP_INFO_SIZE;
+ sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name);
+ return 0;
+ }
+
+ smb_fname = (struct smb_filename) {
+ .base_name = fsp->fsp_name->base_name,
+ .twrp = fsp->fsp_name->twrp,
+ };
+
+ ret = fruit_stat_base(handle, &smb_fname, false);
+ if (ret != 0) {
+ return -1;
+ }
+ *sbuf = smb_fname.st;
+
+ ino = hash_inode(sbuf, fsp->fsp_name->stream_name);
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (ret != 0) {
+ return -1;
+ }
+
+ sbuf->st_ex_ino = ino;
+ return 0;
+}
+
+static int fruit_fstat_meta_netatalk(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ int ret;
+
+ ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false);
+ if (ret != 0) {
+ return -1;
+ }
+
+ *sbuf = fsp->base_fsp->fsp_name->st;
+ sbuf->st_ex_size = AFP_INFO_SIZE;
+ sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name);
+
+ return 0;
+}
+
+static int fruit_fstat_meta(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf,
+ struct fio *fio)
+{
+ int ret;
+
+ DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp));
+
+ switch (fio->config->meta) {
+ case FRUIT_META_STREAM:
+ ret = fruit_fstat_meta_stream(handle, fsp, sbuf);
+ break;
+
+ case FRUIT_META_NETATALK:
+ ret = fruit_fstat_meta_netatalk(handle, fsp, sbuf);
+ break;
+
+ default:
+ DBG_ERR("Unexpected meta config [%d]\n", fio->config->meta);
+ return -1;
+ }
+
+ DBG_DEBUG("Path [%s] ret [%d]\n", fsp_str_dbg(fsp), ret);
+ return ret;
+}
+
+static int fruit_fstat_rsrc_xattr(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+}
+
+static int fruit_fstat_rsrc_stream(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+}
+
+static int fruit_fstat_rsrc_adouble(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ struct adouble *ad = NULL;
+ int ret;
+
+ if (fio == NULL || fio->ad_fsp == NULL) {
+ DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp));
+ errno = EBADF;
+ return -1;
+ }
+
+ /* Populate the stat struct with info from the base file. */
+ ret = fruit_stat_base(handle, fsp->base_fsp->fsp_name, false);
+ if (ret == -1) {
+ return -1;
+ }
+
+ ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ DBG_ERR("ad_fget [%s] failed [%s]\n",
+ fsp_str_dbg(fio->ad_fsp), strerror(errno));
+ return -1;
+ }
+
+ *sbuf = fsp->base_fsp->fsp_name->st;
+ sbuf->st_ex_size = ad_getentrylen(ad, ADEID_RFORK);
+ sbuf->st_ex_ino = hash_inode(sbuf, fsp->fsp_name->stream_name);
+
+ TALLOC_FREE(ad);
+ return 0;
+}
+
+static int fruit_fstat_rsrc(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf, struct fio *fio)
+{
+ int ret;
+
+ switch (fio->config->rsrc) {
+ case FRUIT_RSRC_STREAM:
+ ret = fruit_fstat_rsrc_stream(handle, fsp, sbuf);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ ret = fruit_fstat_rsrc_adouble(handle, fsp, sbuf);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ ret = fruit_fstat_rsrc_xattr(handle, fsp, sbuf);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc);
+ return -1;
+ }
+
+ return ret;
+}
+
+static int fruit_fstat(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ int rc;
+
+ if (fio == NULL) {
+ return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ }
+
+ DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp));
+
+ if (fio->type == ADOUBLE_META) {
+ rc = fruit_fstat_meta(handle, fsp, sbuf, fio);
+ } else {
+ rc = fruit_fstat_rsrc(handle, fsp, sbuf, fio);
+ }
+
+ if (rc == 0) {
+ sbuf->st_ex_mode &= ~S_IFMT;
+ sbuf->st_ex_mode |= S_IFREG;
+ sbuf->st_ex_blocks = sbuf->st_ex_size / STAT_ST_BLOCKSIZE + 1;
+ }
+
+ DBG_DEBUG("Path [%s] rc [%d] size [%"PRIdMAX"]\n",
+ fsp_str_dbg(fsp), rc, (intmax_t)sbuf->st_ex_size);
+ return rc;
+}
+
+static NTSTATUS delete_invalid_meta_stream(
+ vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams,
+ off_t size)
+{
+ struct smb_filename *sname = NULL;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams, AFPINFO_STREAM);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (size == 0) {
+ return NT_STATUS_OK;
+ }
+
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ smb_fname->base_name,
+ AFPINFO_STREAM_NAME,
+ NULL,
+ smb_fname->twrp,
+ 0,
+ &sname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ handle->conn->cwd_fsp,
+ sname,
+ 0);
+ if (ret != 0) {
+ DBG_ERR("Removing [%s] failed\n", smb_fname_str_dbg(sname));
+ TALLOC_FREE(sname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ TALLOC_FREE(sname);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_streaminfo_meta_stream(
+ vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct stream_struct *stream = *pstreams;
+ unsigned int num_streams = *pnum_streams;
+ int i;
+
+ for (i = 0; i < num_streams; i++) {
+ if (strequal_m(stream[i].name, AFPINFO_STREAM)) {
+ break;
+ }
+ }
+
+ if (i == num_streams) {
+ return NT_STATUS_OK;
+ }
+
+ if (stream[i].size != AFP_INFO_SIZE) {
+ DBG_ERR("Removing invalid AFPINFO_STREAM size [%jd] from [%s]\n",
+ (intmax_t)stream[i].size, smb_fname_str_dbg(smb_fname));
+
+ return delete_invalid_meta_stream(handle,
+ smb_fname,
+ mem_ctx,
+ pnum_streams,
+ pstreams,
+ stream[i].size);
+ }
+
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_streaminfo_meta_netatalk(
+ vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct stream_struct *stream = *pstreams;
+ unsigned int num_streams = *pnum_streams;
+ struct adouble *ad = NULL;
+ bool is_fi_empty;
+ int i;
+ bool ok;
+
+ /* Remove the Netatalk xattr from the list */
+ ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams,
+ ":" NETATALK_META_XATTR ":$DATA");
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Check if there's a AFPINFO_STREAM from the VFS streams
+ * backend and if yes, remove it from the list
+ */
+ for (i = 0; i < num_streams; i++) {
+ if (strequal_m(stream[i].name, AFPINFO_STREAM)) {
+ break;
+ }
+ }
+
+ if (i < num_streams) {
+ DBG_WARNING("Unexpected AFPINFO_STREAM on [%s]\n",
+ smb_fname_str_dbg(smb_fname));
+
+ ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams,
+ AFPINFO_STREAM);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ ad = ad_get_meta_fsp(talloc_tos(), handle, smb_fname);
+ if (ad == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ is_fi_empty = ad_empty_finderinfo(ad);
+ TALLOC_FREE(ad);
+
+ if (is_fi_empty) {
+ return NT_STATUS_OK;
+ }
+
+ ok = add_fruit_stream(mem_ctx, pnum_streams, pstreams,
+ AFPINFO_STREAM_NAME, AFP_INFO_SIZE,
+ smb_roundup(handle->conn, AFP_INFO_SIZE));
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_streaminfo_meta(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct fruit_config_data *config = NULL;
+ NTSTATUS status;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ switch (config->meta) {
+ case FRUIT_META_NETATALK:
+ status = fruit_streaminfo_meta_netatalk(handle, fsp, smb_fname,
+ mem_ctx, pnum_streams,
+ pstreams);
+ break;
+
+ case FRUIT_META_STREAM:
+ status = fruit_streaminfo_meta_stream(handle, fsp, smb_fname,
+ mem_ctx, pnum_streams,
+ pstreams);
+ break;
+
+ default:
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return status;
+}
+
+static NTSTATUS fruit_streaminfo_rsrc_stream(
+ vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ bool ok;
+
+ ok = filter_empty_rsrc_stream(pnum_streams, pstreams);
+ if (!ok) {
+ DBG_ERR("Filtering resource stream failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_streaminfo_rsrc_xattr(
+ vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ bool ok;
+
+ ok = filter_empty_rsrc_stream(pnum_streams, pstreams);
+ if (!ok) {
+ DBG_ERR("Filtering resource stream failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_streaminfo_rsrc_adouble(
+ vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct stream_struct *stream = *pstreams;
+ unsigned int num_streams = *pnum_streams;
+ struct adouble *ad = NULL;
+ bool ok;
+ size_t rlen;
+ int i;
+
+ /*
+ * Check if there's a AFPRESOURCE_STREAM from the VFS streams backend
+ * and if yes, remove it from the list
+ */
+ for (i = 0; i < num_streams; i++) {
+ if (strequal_m(stream[i].name, AFPRESOURCE_STREAM)) {
+ break;
+ }
+ }
+
+ if (i < num_streams) {
+ DBG_WARNING("Unexpected AFPRESOURCE_STREAM on [%s]\n",
+ smb_fname_str_dbg(smb_fname));
+
+ ok = del_fruit_stream(mem_ctx, pnum_streams, pstreams,
+ AFPRESOURCE_STREAM);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ ad = ad_get(talloc_tos(), handle, smb_fname, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ rlen = ad_getentrylen(ad, ADEID_RFORK);
+ TALLOC_FREE(ad);
+
+ if (rlen == 0) {
+ return NT_STATUS_OK;
+ }
+
+ ok = add_fruit_stream(mem_ctx, pnum_streams, pstreams,
+ AFPRESOURCE_STREAM_NAME, rlen,
+ smb_roundup(handle->conn, rlen));
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_streaminfo_rsrc(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct fruit_config_data *config = NULL;
+ NTSTATUS status;
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_OK;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ switch (config->rsrc) {
+ case FRUIT_RSRC_STREAM:
+ status = fruit_streaminfo_rsrc_stream(handle, fsp, smb_fname,
+ mem_ctx, pnum_streams,
+ pstreams);
+ break;
+
+ case FRUIT_RSRC_XATTR:
+ status = fruit_streaminfo_rsrc_xattr(handle, fsp, smb_fname,
+ mem_ctx, pnum_streams,
+ pstreams);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ status = fruit_streaminfo_rsrc_adouble(handle, fsp, smb_fname,
+ mem_ctx, pnum_streams,
+ pstreams);
+ break;
+
+ default:
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return status;
+}
+
+static void fruit_filter_empty_streams(unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ unsigned num_streams = *pnum_streams;
+ struct stream_struct *streams = *pstreams;
+ unsigned i = 0;
+
+ if (!global_fruit_config.nego_aapl) {
+ return;
+ }
+
+ while (i < num_streams) {
+ struct smb_filename smb_fname = (struct smb_filename) {
+ .stream_name = streams[i].name,
+ };
+
+ if (is_ntfs_default_stream_smb_fname(&smb_fname)
+ || streams[i].size > 0)
+ {
+ i++;
+ continue;
+ }
+
+ streams[i] = streams[num_streams - 1];
+ num_streams--;
+ }
+
+ *pnum_streams = num_streams;
+}
+
+static NTSTATUS fruit_fstreaminfo(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct fruit_config_data *config = NULL;
+ const struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ smb_fname = fsp->fsp_name;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ DBG_DEBUG("Path [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ status = SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx,
+ pnum_streams, pstreams);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fruit_filter_empty_streams(pnum_streams, pstreams);
+
+ status = fruit_streaminfo_meta(handle, fsp, smb_fname,
+ mem_ctx, pnum_streams, pstreams);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = fruit_streaminfo_rsrc(handle, fsp, smb_fname,
+ mem_ctx, pnum_streams, pstreams);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int fruit_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ int rc = 0;
+ struct adouble *ad = NULL;
+ struct fruit_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+ return -1);
+
+ if ((config->meta != FRUIT_META_NETATALK) ||
+ is_omit_timespec(&ft->create_time))
+ {
+ return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+ }
+
+ DBG_DEBUG("set btime for %s to %s", fsp_str_dbg(fsp),
+ time_to_asc(convert_timespec_to_time_t(ft->create_time)));
+
+ ad = ad_fget(talloc_tos(), handle, fsp, ADOUBLE_META);
+ if (ad == NULL) {
+ goto exit;
+ }
+
+ ad_setdate(ad, AD_DATE_CREATE | AD_DATE_UNIX,
+ convert_time_t_to_uint32_t(ft->create_time.tv_sec));
+
+ rc = ad_fset(handle, ad, fsp);
+
+exit:
+
+ TALLOC_FREE(ad);
+ if (rc != 0) {
+ DBG_WARNING("%s\n", fsp_str_dbg(fsp));
+ return -1;
+ }
+ return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+}
+
+static int fruit_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+
+ if (fio == NULL) {
+ return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len);
+ }
+
+ /* Let the pwrite code path handle it. */
+ errno = ENOSYS;
+ return -1;
+}
+
+static int fruit_ftruncate_rsrc_xattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+#ifdef HAVE_ATTROPEN
+ return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
+#endif
+ return 0;
+}
+
+static int fruit_ftruncate_rsrc_adouble(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ int rc;
+ struct adouble *ad = NULL;
+ off_t ad_off;
+
+ if (fio == NULL || fio->ad_fsp == NULL) {
+ DBG_ERR("fio/ad_fsp=NULL for [%s]\n", fsp_str_dbg(fsp));
+ errno = EBADF;
+ return -1;
+ }
+
+ ad = ad_fget(talloc_tos(), handle, fio->ad_fsp, ADOUBLE_RSRC);
+ if (ad == NULL) {
+ DBG_ERR("ad_fget [%s] failed [%s]\n",
+ fsp_str_dbg(fio->ad_fsp), strerror(errno));
+ return -1;
+ }
+
+ ad_off = ad_getentryoff(ad, ADEID_RFORK);
+
+ rc = SMB_VFS_NEXT_FTRUNCATE(handle, fio->ad_fsp, offset + ad_off);
+ if (rc != 0) {
+ TALLOC_FREE(ad);
+ return -1;
+ }
+
+ ad_setentrylen(ad, ADEID_RFORK, offset);
+
+ rc = ad_fset(handle, ad, fio->ad_fsp);
+ if (rc != 0) {
+ DBG_ERR("ad_fset [%s] failed [%s]\n",
+ fsp_str_dbg(fio->ad_fsp), strerror(errno));
+ TALLOC_FREE(ad);
+ return -1;
+ }
+
+ TALLOC_FREE(ad);
+ return 0;
+}
+
+static int fruit_ftruncate_rsrc_stream(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+ return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
+}
+
+static int fruit_ftruncate_rsrc(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ int ret;
+
+ if (fio == NULL) {
+ DBG_ERR("Failed to fetch fsp extension\n");
+ return -1;
+ }
+
+ switch (fio->config->rsrc) {
+ case FRUIT_RSRC_XATTR:
+ ret = fruit_ftruncate_rsrc_xattr(handle, fsp, offset);
+ break;
+
+ case FRUIT_RSRC_ADFILE:
+ ret = fruit_ftruncate_rsrc_adouble(handle, fsp, offset);
+ break;
+
+ case FRUIT_RSRC_STREAM:
+ ret = fruit_ftruncate_rsrc_stream(handle, fsp, offset);
+ break;
+
+ default:
+ DBG_ERR("Unexpected rsrc config [%d]\n", fio->config->rsrc);
+ return -1;
+ }
+
+
+ return ret;
+}
+
+static int fruit_ftruncate_meta(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+ if (offset > 60) {
+ DBG_WARNING("ftruncate %s to %jd\n",
+ fsp_str_dbg(fsp), (intmax_t)offset);
+ /* OS X returns NT_STATUS_ALLOTTED_SPACE_EXCEEDED */
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ /* OS X returns success but does nothing */
+ DBG_INFO("ignoring ftruncate %s to %jd\n",
+ fsp_str_dbg(fsp), (intmax_t)offset);
+ return 0;
+}
+
+static int fruit_ftruncate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+ struct fio *fio = fruit_get_complete_fio(handle, fsp);
+ int ret;
+
+ DBG_DEBUG("Path [%s] offset [%"PRIdMAX"]\n", fsp_str_dbg(fsp),
+ (intmax_t)offset);
+
+ if (fio == NULL) {
+ return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
+ }
+
+ if (fio->type == ADOUBLE_META) {
+ ret = fruit_ftruncate_meta(handle, fsp, offset);
+ } else {
+ ret = fruit_ftruncate_rsrc(handle, fsp, offset);
+ }
+
+ DBG_DEBUG("Path [%s] result [%d]\n", fsp_str_dbg(fsp), ret);
+ return ret;
+}
+
+static NTSTATUS fruit_create_file(vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS status;
+ struct fruit_config_data *config = NULL;
+ files_struct *fsp = NULL;
+ bool internal_open = (oplock_request & INTERNAL_OPEN_ONLY);
+ int ret;
+
+ status = check_aapl(handle, req, in_context_blobs, out_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct fruit_config_data,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ if (is_apple_stream(smb_fname->stream_name) &&
+ !internal_open &&
+ config->convert_adouble)
+ {
+ uint32_t conv_flags = 0;
+
+ if (config->wipe_intentionally_left_blank_rfork) {
+ conv_flags |= AD_CONV_WIPE_BLANK;
+ }
+ if (config->delete_empty_adfiles) {
+ conv_flags |= AD_CONV_DELETE;
+ }
+
+ ret = ad_convert(handle,
+ smb_fname,
+ macos_string_replace_map,
+ conv_flags);
+ if (ret != 0) {
+ DBG_ERR("ad_convert(\"%s\") failed\n",
+ smb_fname_str_dbg(smb_fname));
+ }
+ }
+
+ status = SMB_VFS_NEXT_CREATE_FILE(
+ handle, req, dirfsp, smb_fname,
+ access_mask, share_access,
+ create_disposition, create_options,
+ file_attributes, oplock_request,
+ lease,
+ allocation_size, private_flags,
+ sd, ea_list, result,
+ pinfo, in_context_blobs, out_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp = *result;
+
+ if (global_fruit_config.nego_aapl) {
+ if (config->posix_rename && fsp->fsp_flags.is_directory) {
+ /*
+ * Enable POSIX directory rename behaviour
+ */
+ fsp->posix_flags |= FSP_POSIX_FLAGS_RENAME;
+ }
+ }
+
+ /*
+ * If this is a plain open for existing files, opening an 0
+ * byte size resource fork MUST fail with
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND.
+ *
+ * Cf the vfs_fruit torture tests in test_rfork_create().
+ */
+ if (global_fruit_config.nego_aapl &&
+ create_disposition == FILE_OPEN &&
+ smb_fname->st.st_ex_size == 0 &&
+ is_named_stream(smb_fname))
+ {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
+ }
+
+ if (is_named_stream(smb_fname) || fsp->fsp_flags.is_directory) {
+ return status;
+ }
+
+ if ((config->locking == FRUIT_LOCKING_NETATALK) &&
+ (fsp->op != NULL) &&
+ !fsp->fsp_flags.is_pathref)
+ {
+ status = fruit_check_access(
+ handle, *result,
+ access_mask,
+ share_access);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ return status;
+
+fail:
+ DEBUG(10, ("fruit_create_file: %s\n", nt_errstr(status)));
+
+ if (fsp) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ *result = NULL;
+ }
+
+ return status;
+}
+
+static NTSTATUS fruit_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **pattr_data)
+{
+ struct fruit_config_data *config = NULL;
+ struct readdir_attr_data *attr_data;
+ uint32_t conv_flags = 0;
+ NTSTATUS status;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ if (!global_fruit_config.nego_aapl) {
+ return SMB_VFS_NEXT_FREADDIR_ATTR(handle,
+ fsp,
+ mem_ctx,
+ pattr_data);
+ }
+
+ DBG_DEBUG("Path [%s]\n", fsp_str_dbg(fsp));
+
+ if (config->convert_adouble) {
+ if (config->wipe_intentionally_left_blank_rfork) {
+ conv_flags |= AD_CONV_WIPE_BLANK;
+ }
+ if (config->delete_empty_adfiles) {
+ conv_flags |= AD_CONV_DELETE;
+ }
+
+ ret = ad_convert(handle,
+ fsp->fsp_name,
+ macos_string_replace_map,
+ conv_flags);
+ if (ret != 0) {
+ DBG_ERR("ad_convert(\"%s\") failed\n",
+ fsp_str_dbg(fsp));
+ }
+ }
+
+ *pattr_data = talloc_zero(mem_ctx, struct readdir_attr_data);
+ if (*pattr_data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ attr_data = *pattr_data;
+ attr_data->type = RDATTR_AAPL;
+
+ /*
+ * Mac metadata: compressed FinderInfo, resource fork length
+ * and creation date
+ */
+ status = readdir_attr_macmeta(handle, fsp->fsp_name, attr_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Error handling is tricky: if we return failure from
+ * this function, the corresponding directory entry
+ * will to be passed to the client, so we really just
+ * want to error out on fatal errors.
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ goto fail;
+ }
+ }
+
+ /*
+ * UNIX mode
+ */
+ if (config->unix_info_enabled) {
+ attr_data->attr_data.aapl.unix_mode =
+ fsp->fsp_name->st.st_ex_mode;
+ }
+
+ /*
+ * max_access
+ */
+ if (!config->readdir_attr_max_access) {
+ attr_data->attr_data.aapl.max_access = FILE_GENERIC_ALL;
+ } else {
+ status = smbd_calculate_access_mask_fsp(fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &attr_data->attr_data.aapl.max_access);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ return NT_STATUS_OK;
+
+fail:
+ DBG_WARNING("Path [%s], error: %s\n", fsp_str_dbg(fsp),
+ nt_errstr(status));
+ TALLOC_FREE(*pattr_data);
+ return status;
+}
+
+static NTSTATUS fruit_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS status;
+ struct security_ace ace;
+ struct dom_sid sid;
+ struct fruit_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info,
+ mem_ctx, ppdesc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Add MS NFS style ACEs with uid, gid and mode
+ */
+ if (!global_fruit_config.nego_aapl) {
+ return NT_STATUS_OK;
+ }
+ if (!config->unix_info_enabled) {
+ return NT_STATUS_OK;
+ }
+
+ /* First remove any existing ACE's with NFS style mode/uid/gid SIDs. */
+ status = remove_virtual_nfs_aces(*ppdesc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("failed to remove MS NFS style ACEs\n");
+ return status;
+ }
+
+ /* MS NFS style mode */
+ sid_compose(&sid, &global_sid_Unix_NFS_Mode, fsp->fsp_name->st.st_ex_mode);
+ init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0);
+ status = security_descriptor_dacl_add(*ppdesc, &ace);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("failed to add MS NFS style ACE\n"));
+ return status;
+ }
+
+ /* MS NFS style uid */
+ sid_compose(&sid, &global_sid_Unix_NFS_Users, fsp->fsp_name->st.st_ex_uid);
+ init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0);
+ status = security_descriptor_dacl_add(*ppdesc, &ace);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("failed to add MS NFS style ACE\n"));
+ return status;
+ }
+
+ /* MS NFS style gid */
+ sid_compose(&sid, &global_sid_Unix_NFS_Groups, fsp->fsp_name->st.st_ex_gid);
+ init_sec_ace(&ace, &sid, SEC_ACE_TYPE_ACCESS_DENIED, 0, 0);
+ status = security_descriptor_dacl_add(*ppdesc, &ace);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("failed to add MS NFS style ACE\n"));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fruit_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *orig_psd)
+{
+ NTSTATUS status;
+ bool do_chmod;
+ mode_t ms_nfs_mode = 0;
+ int result;
+ struct security_descriptor *psd = NULL;
+ uint32_t orig_num_aces = 0;
+
+ if (orig_psd->dacl != NULL) {
+ orig_num_aces = orig_psd->dacl->num_aces;
+ }
+
+ psd = security_descriptor_copy(talloc_tos(), orig_psd);
+ if (psd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("fruit_fset_nt_acl: %s\n", fsp_str_dbg(fsp));
+
+ status = check_ms_nfs(handle, fsp, psd, &ms_nfs_mode, &do_chmod);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("fruit_fset_nt_acl: check_ms_nfs failed%s\n", fsp_str_dbg(fsp)));
+ TALLOC_FREE(psd);
+ return status;
+ }
+
+ /*
+ * If only ms_nfs ACE entries were sent, ensure we set the DACL
+ * sent/present flags correctly now we've removed them.
+ */
+
+ if (orig_num_aces != 0) {
+ /*
+ * Are there any ACE's left ?
+ */
+ if (psd->dacl->num_aces == 0) {
+ /* No - clear the DACL sent/present flags. */
+ security_info_sent &= ~SECINFO_DACL;
+ psd->type &= ~SEC_DESC_DACL_PRESENT;
+ }
+ }
+
+ status = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("fruit_fset_nt_acl: SMB_VFS_NEXT_FSET_NT_ACL failed%s\n", fsp_str_dbg(fsp)));
+ TALLOC_FREE(psd);
+ return status;
+ }
+
+ if (do_chmod) {
+ result = SMB_VFS_FCHMOD(fsp, ms_nfs_mode);
+ if (result != 0) {
+ DBG_WARNING("%s, result: %d, %04o error %s\n",
+ fsp_str_dbg(fsp),
+ result,
+ (unsigned)ms_nfs_mode,
+ strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(psd);
+ return status;
+ }
+ }
+
+ TALLOC_FREE(psd);
+ return NT_STATUS_OK;
+}
+
+static struct vfs_offload_ctx *fruit_offload_ctx;
+
+struct fruit_offload_read_state {
+ struct vfs_handle_struct *handle;
+ struct tevent_context *ev;
+ files_struct *fsp;
+ uint32_t fsctl;
+ uint32_t flags;
+ uint64_t xferlen;
+ DATA_BLOB token;
+};
+
+static void fruit_offload_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *fruit_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct fruit_offload_read_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fruit_offload_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct fruit_offload_read_state) {
+ .handle = handle,
+ .ev = ev,
+ .fsp = fsp,
+ .fsctl = fsctl,
+ };
+
+ subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp,
+ fsctl, ttl, offset, to_copy);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fruit_offload_read_done, req);
+ return req;
+}
+
+static void fruit_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fruit_offload_read_state *state = tevent_req_data(
+ req, struct fruit_offload_read_state);
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq,
+ state->handle,
+ state,
+ &state->flags,
+ &state->xferlen,
+ &state->token);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (state->fsctl != FSCTL_SRV_REQUEST_RESUME_KEY) {
+ tevent_req_done(req);
+ return;
+ }
+
+ status = vfs_offload_token_ctx_init(state->fsp->conn->sconn->client,
+ &fruit_offload_ctx);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = vfs_offload_token_db_store_fsp(fruit_offload_ctx,
+ state->fsp,
+ &state->token);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS fruit_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token)
+{
+ struct fruit_offload_read_state *state = tevent_req_data(
+ req, struct fruit_offload_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *flags = state->flags;
+ *xferlen = state->xferlen;
+ token->length = state->token.length;
+ token->data = talloc_move(mem_ctx, &state->token.data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct fruit_offload_write_state {
+ struct vfs_handle_struct *handle;
+ off_t copied;
+ struct files_struct *src_fsp;
+ struct files_struct *dst_fsp;
+ bool is_copyfile;
+};
+
+static void fruit_offload_write_done(struct tevent_req *subreq);
+static struct tevent_req *fruit_offload_write_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num)
+{
+ struct tevent_req *req, *subreq;
+ struct fruit_offload_write_state *state;
+ NTSTATUS status;
+ struct fruit_config_data *config;
+ off_t src_off = transfer_offset;
+ files_struct *src_fsp = NULL;
+ off_t to_copy = num;
+ bool copyfile_enabled = false;
+
+ DEBUG(10,("soff: %ju, doff: %ju, len: %ju\n",
+ (uintmax_t)src_off, (uintmax_t)dest_off, (uintmax_t)num));
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return NULL);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fruit_offload_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->handle = handle;
+ state->dst_fsp = dest_fsp;
+
+ switch (fsctl) {
+ case FSCTL_SRV_COPYCHUNK:
+ case FSCTL_SRV_COPYCHUNK_WRITE:
+ copyfile_enabled = config->copyfile_enabled;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Check if this a OS X copyfile style copychunk request with
+ * a requested chunk count of 0 that was translated to a
+ * offload_write_send VFS call overloading the parameters src_off
+ * = dest_off = num = 0.
+ */
+ if (copyfile_enabled && num == 0 && src_off == 0 && dest_off == 0) {
+ status = vfs_offload_token_db_fetch_fsp(
+ fruit_offload_ctx, token, &src_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ state->src_fsp = src_fsp;
+
+ status = vfs_stat_fsp(src_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ to_copy = src_fsp->fsp_name->st.st_ex_size;
+ state->is_copyfile = true;
+ }
+
+ subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle,
+ mem_ctx,
+ ev,
+ fsctl,
+ token,
+ transfer_offset,
+ dest_fsp,
+ dest_off,
+ to_copy);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, fruit_offload_write_done, req);
+ return req;
+}
+
+static void fruit_offload_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fruit_offload_write_state *state = tevent_req_data(
+ req, struct fruit_offload_write_state);
+ NTSTATUS status;
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+ unsigned int i;
+ struct smb_filename *src_fname_tmp = NULL;
+ struct smb_filename *dst_fname_tmp = NULL;
+
+ status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle,
+ subreq,
+ &state->copied);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (!state->is_copyfile) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Now copy all remaining streams. We know the share supports
+ * streams, because we're in vfs_fruit. We don't do this async
+ * because streams are few and small.
+ */
+ status = vfs_fstreaminfo(state->src_fsp,
+ req, &num_streams, &streams);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (num_streams == 1) {
+ /* There is always one stream, ::$DATA. */
+ tevent_req_done(req);
+ return;
+ }
+
+ for (i = 0; i < num_streams; i++) {
+ DEBUG(10, ("%s: stream: '%s'/%zu\n",
+ __func__, streams[i].name, (size_t)streams[i].size));
+
+ src_fname_tmp = synthetic_smb_fname(
+ req,
+ state->src_fsp->fsp_name->base_name,
+ streams[i].name,
+ NULL,
+ state->src_fsp->fsp_name->twrp,
+ state->src_fsp->fsp_name->flags);
+ if (tevent_req_nomem(src_fname_tmp, req)) {
+ return;
+ }
+
+ if (is_ntfs_default_stream_smb_fname(src_fname_tmp)) {
+ TALLOC_FREE(src_fname_tmp);
+ continue;
+ }
+
+ dst_fname_tmp = synthetic_smb_fname(
+ req,
+ state->dst_fsp->fsp_name->base_name,
+ streams[i].name,
+ NULL,
+ state->dst_fsp->fsp_name->twrp,
+ state->dst_fsp->fsp_name->flags);
+ if (tevent_req_nomem(dst_fname_tmp, req)) {
+ TALLOC_FREE(src_fname_tmp);
+ return;
+ }
+
+ status = copy_file(req,
+ state->handle->conn,
+ src_fname_tmp,
+ dst_fname_tmp,
+ FILE_CREATE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("%s: copy %s to %s failed: %s\n", __func__,
+ smb_fname_str_dbg(src_fname_tmp),
+ smb_fname_str_dbg(dst_fname_tmp),
+ nt_errstr(status)));
+ TALLOC_FREE(src_fname_tmp);
+ TALLOC_FREE(dst_fname_tmp);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ TALLOC_FREE(src_fname_tmp);
+ TALLOC_FREE(dst_fname_tmp);
+ }
+
+ TALLOC_FREE(streams);
+ TALLOC_FREE(src_fname_tmp);
+ TALLOC_FREE(dst_fname_tmp);
+ tevent_req_done(req);
+}
+
+static NTSTATUS fruit_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ struct fruit_offload_write_state *state = tevent_req_data(
+ req, struct fruit_offload_write_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DEBUG(1, ("server side copy chunk failed: %s\n",
+ nt_errstr(status)));
+ *copied = 0;
+ tevent_req_received(req);
+ return status;
+ }
+
+ *copied = state->copied;
+ tevent_req_received(req);
+
+ return NT_STATUS_OK;
+}
+
+static char *fruit_get_bandsize_line(char **lines, int numlines)
+{
+ static regex_t re;
+ static bool re_initialized = false;
+ int i;
+ int ret;
+
+ if (!re_initialized) {
+ ret = regcomp(&re, "^[[:blank:]]*<key>band-size</key>$", 0);
+ if (ret != 0) {
+ return NULL;
+ }
+ re_initialized = true;
+ }
+
+ for (i = 0; i < numlines; i++) {
+ regmatch_t matches[1];
+
+ ret = regexec(&re, lines[i], 1, matches, 0);
+ if (ret == 0) {
+ /*
+ * Check if the match was on the last line, sa we want
+ * the subsequent line.
+ */
+ if (i + 1 == numlines) {
+ return NULL;
+ }
+ return lines[i + 1];
+ }
+ if (ret != REG_NOMATCH) {
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static bool fruit_get_bandsize_from_line(char *line, size_t *_band_size)
+{
+ static regex_t re;
+ static bool re_initialized = false;
+ regmatch_t matches[2];
+ uint64_t band_size;
+ int ret;
+ bool ok;
+
+ if (!re_initialized) {
+ ret = regcomp(&re,
+ "^[[:blank:]]*"
+ "<integer>\\([[:digit:]]*\\)</integer>$",
+ 0);
+ if (ret != 0) {
+ return false;
+ }
+ re_initialized = true;
+ }
+
+ ret = regexec(&re, line, 2, matches, 0);
+ if (ret != 0) {
+ DBG_ERR("regex failed [%s]\n", line);
+ return false;
+ }
+
+ line[matches[1].rm_eo] = '\0';
+
+ ok = conv_str_u64(&line[matches[1].rm_so], &band_size);
+ if (!ok) {
+ return false;
+ }
+ *_band_size = (size_t)band_size;
+ return true;
+}
+
+/*
+ * This reads and parses an Info.plist from a TM sparsebundle looking for the
+ * "band-size" key and value.
+ */
+static bool fruit_get_bandsize(vfs_handle_struct *handle,
+ const char *dir,
+ size_t *band_size)
+{
+#define INFO_PLIST_MAX_SIZE 64*1024
+ char *plist = NULL;
+ struct smb_filename *smb_fname = NULL;
+ files_struct *fsp = NULL;
+ uint8_t *file_data = NULL;
+ char **lines = NULL;
+ char *band_size_line = NULL;
+ size_t plist_file_size;
+ ssize_t nread;
+ int numlines;
+ int ret;
+ bool ok = false;
+ NTSTATUS status;
+
+ plist = talloc_asprintf(talloc_tos(),
+ "%s/%s/Info.plist",
+ handle->conn->connectpath,
+ dir);
+ if (plist == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ plist,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (ret != 0) {
+ DBG_INFO("Ignoring Sparsebundle without Info.plist [%s]\n", dir);
+ ok = true;
+ goto out;
+ }
+
+ plist_file_size = smb_fname->st.st_ex_size;
+
+ if (plist_file_size > INFO_PLIST_MAX_SIZE) {
+ DBG_INFO("%s is too large, ignoring\n", plist);
+ ok = true;
+ goto out;
+ }
+
+ status = SMB_VFS_NEXT_CREATE_FILE(
+ handle, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition */
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* psbuf */
+ NULL, NULL); /* create context */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("Opening [%s] failed [%s]\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ ok = false;
+ goto out;
+ }
+
+ file_data = talloc_zero_array(talloc_tos(),
+ uint8_t,
+ plist_file_size + 1);
+ if (file_data == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ nread = SMB_VFS_NEXT_PREAD(handle, fsp, file_data, plist_file_size, 0);
+ if (nread != plist_file_size) {
+ DBG_ERR("Short read on [%s]: %zu/%zd\n",
+ fsp_str_dbg(fsp), nread, plist_file_size);
+ ok = false;
+ goto out;
+
+ }
+
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("close_file failed: %s\n", nt_errstr(status));
+ ok = false;
+ goto out;
+ }
+
+ lines = file_lines_parse((char *)file_data,
+ plist_file_size,
+ &numlines,
+ talloc_tos());
+ if (lines == NULL) {
+ ok = false;
+ goto out;
+ }
+
+ band_size_line = fruit_get_bandsize_line(lines, numlines);
+ if (band_size_line == NULL) {
+ DBG_ERR("Didn't find band-size key in [%s]\n",
+ smb_fname_str_dbg(smb_fname));
+ ok = false;
+ goto out;
+ }
+
+ ok = fruit_get_bandsize_from_line(band_size_line, band_size);
+ if (!ok) {
+ DBG_ERR("fruit_get_bandsize_from_line failed\n");
+ goto out;
+ }
+
+ DBG_DEBUG("Parsed band-size [%zu] for [%s]\n", *band_size, plist);
+
+out:
+ if (fsp != NULL) {
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("close_file failed: %s\n", nt_errstr(status));
+ }
+ }
+ TALLOC_FREE(plist);
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(file_data);
+ TALLOC_FREE(lines);
+ return ok;
+}
+
+struct fruit_disk_free_state {
+ off_t total_size;
+};
+
+static bool fruit_get_num_bands(vfs_handle_struct *handle,
+ const char *bundle,
+ size_t *_nbands)
+{
+ char *path = NULL;
+ struct smb_filename *bands_dir = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ size_t nbands;
+ NTSTATUS status;
+
+ path = talloc_asprintf(talloc_tos(),
+ "%s/%s/bands",
+ handle->conn->connectpath,
+ bundle);
+ if (path == NULL) {
+ return false;
+ }
+
+ bands_dir = synthetic_smb_fname(talloc_tos(),
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ TALLOC_FREE(path);
+ if (bands_dir == NULL) {
+ return false;
+ }
+
+ status = OpenDir(talloc_tos(),
+ handle->conn,
+ bands_dir,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(bands_dir);
+ errno = map_errno_from_nt_status(status);
+ return false;
+ }
+
+ nbands = 0;
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ continue;
+ }
+ nbands++;
+ }
+ TALLOC_FREE(dir_hnd);
+
+ DBG_DEBUG("%zu bands in [%s]\n", nbands, smb_fname_str_dbg(bands_dir));
+
+ TALLOC_FREE(bands_dir);
+
+ *_nbands = nbands;
+ return true;
+}
+
+static bool fruit_tmsize_do_dirent(vfs_handle_struct *handle,
+ struct fruit_disk_free_state *state,
+ const char *name)
+{
+ bool ok;
+ char *p = NULL;
+ size_t sparsebundle_strlen = strlen("sparsebundle");
+ size_t bandsize = 0;
+ size_t nbands;
+ off_t tm_size;
+
+ p = strstr(name, "sparsebundle");
+ if (p == NULL) {
+ return true;
+ }
+
+ if (p[sparsebundle_strlen] != '\0') {
+ return true;
+ }
+
+ DBG_DEBUG("Processing sparsebundle [%s]\n", name);
+
+ ok = fruit_get_bandsize(handle, name, &bandsize);
+ if (!ok) {
+ /*
+ * Beware of race conditions: this may be an uninitialized
+ * Info.plist that a client is just creating. We don't want let
+ * this to trigger complete failure.
+ */
+ DBG_ERR("Processing sparsebundle [%s] failed\n", name);
+ return true;
+ }
+
+ ok = fruit_get_num_bands(handle, name, &nbands);
+ if (!ok) {
+ /*
+ * Beware of race conditions: this may be a backup sparsebundle
+ * in an early stage lacking a bands subdirectory. We don't want
+ * let this to trigger complete failure.
+ */
+ DBG_ERR("Processing sparsebundle [%s] failed\n", name);
+ return true;
+ }
+
+ /*
+ * Arithmetic on 32-bit systems may cause overflow, depending on
+ * size_t precision. First we check its unlikely, then we
+ * force the precision into target off_t, then we check that
+ * the total did not overflow either.
+ */
+ if (bandsize > SIZE_MAX/nbands) {
+ DBG_ERR("tmsize potential overflow: bandsize [%zu] nbands [%zu]\n",
+ bandsize, nbands);
+ return false;
+ }
+ tm_size = (off_t)bandsize * (off_t)nbands;
+
+ if (state->total_size + tm_size < state->total_size) {
+ DBG_ERR("tm total size overflow: bandsize [%zu] nbands [%zu]\n",
+ bandsize, nbands);
+ return false;
+ }
+
+ state->total_size += tm_size;
+
+ DBG_DEBUG("[%s] tm_size [%jd] total_size [%jd]\n",
+ name, (intmax_t)tm_size, (intmax_t)state->total_size);
+
+ return true;
+}
+
+/**
+ * Calculate used size of a TimeMachine volume
+ *
+ * This assumes that the volume is used only for TimeMachine.
+ *
+ * - readdir(basedir of share), then
+ * - for every element that matches regex "^\(.*\)\.sparsebundle$" :
+ * - parse "\1.sparsebundle/Info.plist" and read the band-size XML key
+ * - count band files in "\1.sparsebundle/bands/"
+ * - calculate used size of all bands: band_count * band_size
+ **/
+static uint64_t fruit_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *_bsize,
+ uint64_t *_dfree,
+ uint64_t *_dsize)
+{
+ struct fruit_config_data *config = NULL;
+ struct fruit_disk_free_state state = {0};
+ struct smb_Dir *dir_hnd = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ uint64_t dfree;
+ uint64_t dsize;
+ bool ok;
+ NTSTATUS status;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return UINT64_MAX);
+
+ if (!config->time_machine ||
+ config->time_machine_max_size == 0)
+ {
+ return SMB_VFS_NEXT_DISK_FREE(handle,
+ smb_fname,
+ _bsize,
+ _dfree,
+ _dsize);
+ }
+
+ status = OpenDir(talloc_tos(),
+ handle->conn,
+ smb_fname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return UINT64_MAX;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ ok = fruit_tmsize_do_dirent(handle, &state, dname);
+ if (!ok) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(dir_hnd);
+ return UINT64_MAX;
+ }
+ TALLOC_FREE(talloced);
+ }
+
+ TALLOC_FREE(dir_hnd);
+
+ dsize = config->time_machine_max_size / 512;
+ dfree = dsize - (state.total_size / 512);
+ if (dfree > dsize) {
+ dfree = 0;
+ }
+
+ *_bsize = 512;
+ *_dsize = dsize;
+ *_dfree = dfree;
+ return dfree / 2;
+}
+
+static uint64_t fruit_fs_file_id(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ struct fruit_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct fruit_config_data,
+ return 0);
+
+ if (global_fruit_config.nego_aapl &&
+ config->aapl_zero_file_id)
+ {
+ return 0;
+ }
+
+ return SMB_VFS_NEXT_FS_FILE_ID(handle, psbuf);
+}
+
+static struct vfs_fn_pointers vfs_fruit_fns = {
+ .connect_fn = fruit_connect,
+ .disk_free_fn = fruit_disk_free,
+
+ /* File operations */
+ .fchmod_fn = fruit_fchmod,
+ .unlinkat_fn = fruit_unlinkat,
+ .renameat_fn = fruit_renameat,
+ .openat_fn = fruit_openat,
+ .close_fn = fruit_close,
+ .pread_fn = fruit_pread,
+ .pwrite_fn = fruit_pwrite,
+ .pread_send_fn = fruit_pread_send,
+ .pread_recv_fn = fruit_pread_recv,
+ .pwrite_send_fn = fruit_pwrite_send,
+ .pwrite_recv_fn = fruit_pwrite_recv,
+ .fsync_send_fn = fruit_fsync_send,
+ .fsync_recv_fn = fruit_fsync_recv,
+ .stat_fn = fruit_stat,
+ .lstat_fn = fruit_lstat,
+ .fstat_fn = fruit_fstat,
+ .fstreaminfo_fn = fruit_fstreaminfo,
+ .fntimes_fn = fruit_fntimes,
+ .ftruncate_fn = fruit_ftruncate,
+ .fallocate_fn = fruit_fallocate,
+ .create_file_fn = fruit_create_file,
+ .freaddir_attr_fn = fruit_freaddir_attr,
+ .offload_read_send_fn = fruit_offload_read_send,
+ .offload_read_recv_fn = fruit_offload_read_recv,
+ .offload_write_send_fn = fruit_offload_write_send,
+ .offload_write_recv_fn = fruit_offload_write_recv,
+ .fs_file_id_fn = fruit_fs_file_id,
+
+ /* NT ACL operations */
+ .fget_nt_acl_fn = fruit_fget_nt_acl,
+ .fset_nt_acl_fn = fruit_fset_nt_acl,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_fruit_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "fruit",
+ &vfs_fruit_fns);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ vfs_fruit_debug_level = debug_add_class("fruit");
+ if (vfs_fruit_debug_level == -1) {
+ vfs_fruit_debug_level = DBGC_VFS;
+ DEBUG(0, ("%s: Couldn't register custom debugging class!\n",
+ "vfs_fruit_init"));
+ } else {
+ DEBUG(10, ("%s: Debug class number of '%s': %d\n",
+ "vfs_fruit_init","fruit",vfs_fruit_debug_level));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_full_audit.c b/source3/modules/vfs_full_audit.c
new file mode 100644
index 0000000..9fd8a75
--- /dev/null
+++ b/source3/modules/vfs_full_audit.c
@@ -0,0 +1,3035 @@
+/*
+ * Auditing VFS module for samba. Log selected file operations to syslog
+ * facility.
+ *
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) John H Terpstra, 2003
+ * Copyright (C) Stefan (metze) Metzmacher, 2003
+ * Copyright (C) Volker Lendecke, 2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module implements parseable logging for all Samba VFS operations.
+ *
+ * You use it as follows:
+ *
+ * [tmp]
+ * path = /tmp
+ * vfs objects = full_audit
+ * full_audit:prefix = %u|%I
+ * full_audit:success = open opendir create_file
+ * full_audit:failure = all
+ *
+ * vfs op can be "all" which means log all operations.
+ * vfs op can be "none" which means no logging.
+ *
+ * This leads to syslog entries of the form:
+ * smbd_audit: nobody|192.168.234.1|opendir|ok|/tmp
+ * smbd_audit: nobody|192.168.234.1|create_file|fail (No such file or directory)|0x1|file|open|/ts/doesNotExist
+ * smbd_audit: nobody|192.168.234.1|open|ok|w|/tmp/file.txt
+ * smbd_audit: nobody|192.168.234.1|create_file|ok|0x3|file|open|/tmp/file.txt
+ *
+ * where "nobody" is the connected username and "192.168.234.1" is the
+ * client's IP address.
+ *
+ * Options:
+ *
+ * prefix: A macro expansion template prepended to the syslog entry.
+ *
+ * success: A list of VFS operations for which a successful completion should
+ * be logged. Defaults to no logging at all. The special operation "all" logs
+ * - you guessed it - everything.
+ *
+ * failure: A list of VFS operations for which failure to complete should be
+ * logged. Defaults to logging everything.
+ */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/syslog.h"
+#include "smbd/smbd.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "auth.h"
+#include "ntioctl.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/bitmap.h"
+#include "lib/util/tevent_unix.h"
+#include "libcli/security/sddl.h"
+#include "passdb/machine_sid.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+static int vfs_full_audit_debug_level = DBGC_VFS;
+
+struct vfs_full_audit_private_data {
+ struct bitmap *success_ops;
+ struct bitmap *failure_ops;
+ int syslog_facility;
+ int syslog_priority;
+ bool log_secdesc;
+ bool do_syslog;
+};
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_full_audit_debug_level
+
+typedef enum _vfs_op_type {
+ SMB_VFS_OP_NOOP = -1,
+
+ /* Disk operations */
+
+ SMB_VFS_OP_CONNECT = 0,
+ SMB_VFS_OP_DISCONNECT,
+ SMB_VFS_OP_DISK_FREE,
+ SMB_VFS_OP_GET_QUOTA,
+ SMB_VFS_OP_SET_QUOTA,
+ SMB_VFS_OP_GET_SHADOW_COPY_DATA,
+ SMB_VFS_OP_STATVFS,
+ SMB_VFS_OP_FS_CAPABILITIES,
+ SMB_VFS_OP_GET_DFS_REFERRALS,
+ SMB_VFS_OP_CREATE_DFS_PATHAT,
+ SMB_VFS_OP_READ_DFS_PATHAT,
+
+ /* Directory operations */
+
+ SMB_VFS_OP_FDOPENDIR,
+ SMB_VFS_OP_READDIR,
+ SMB_VFS_OP_REWINDDIR,
+ SMB_VFS_OP_MKDIRAT,
+ SMB_VFS_OP_CLOSEDIR,
+
+ /* File operations */
+
+ SMB_VFS_OP_OPEN,
+ SMB_VFS_OP_OPENAT,
+ SMB_VFS_OP_CREATE_FILE,
+ SMB_VFS_OP_CLOSE,
+ SMB_VFS_OP_READ,
+ SMB_VFS_OP_PREAD,
+ SMB_VFS_OP_PREAD_SEND,
+ SMB_VFS_OP_PREAD_RECV,
+ SMB_VFS_OP_WRITE,
+ SMB_VFS_OP_PWRITE,
+ SMB_VFS_OP_PWRITE_SEND,
+ SMB_VFS_OP_PWRITE_RECV,
+ SMB_VFS_OP_LSEEK,
+ SMB_VFS_OP_SENDFILE,
+ SMB_VFS_OP_RECVFILE,
+ SMB_VFS_OP_RENAMEAT,
+ SMB_VFS_OP_FSYNC_SEND,
+ SMB_VFS_OP_FSYNC_RECV,
+ SMB_VFS_OP_STAT,
+ SMB_VFS_OP_FSTAT,
+ SMB_VFS_OP_LSTAT,
+ SMB_VFS_OP_FSTATAT,
+ SMB_VFS_OP_GET_ALLOC_SIZE,
+ SMB_VFS_OP_UNLINKAT,
+ SMB_VFS_OP_FCHMOD,
+ SMB_VFS_OP_FCHOWN,
+ SMB_VFS_OP_LCHOWN,
+ SMB_VFS_OP_CHDIR,
+ SMB_VFS_OP_GETWD,
+ SMB_VFS_OP_NTIMES,
+ SMB_VFS_OP_FNTIMES,
+ SMB_VFS_OP_FTRUNCATE,
+ SMB_VFS_OP_FALLOCATE,
+ SMB_VFS_OP_LOCK,
+ SMB_VFS_OP_FILESYSTEM_SHAREMODE,
+ SMB_VFS_OP_FCNTL,
+ SMB_VFS_OP_LINUX_SETLEASE,
+ SMB_VFS_OP_GETLOCK,
+ SMB_VFS_OP_SYMLINKAT,
+ SMB_VFS_OP_READLINKAT,
+ SMB_VFS_OP_LINKAT,
+ SMB_VFS_OP_MKNODAT,
+ SMB_VFS_OP_REALPATH,
+ SMB_VFS_OP_FCHFLAGS,
+ SMB_VFS_OP_FILE_ID_CREATE,
+ SMB_VFS_OP_FS_FILE_ID,
+ SMB_VFS_OP_FSTREAMINFO,
+ SMB_VFS_OP_GET_REAL_FILENAME,
+ SMB_VFS_OP_GET_REAL_FILENAME_AT,
+ SMB_VFS_OP_CONNECTPATH,
+ SMB_VFS_OP_BRL_LOCK_WINDOWS,
+ SMB_VFS_OP_BRL_UNLOCK_WINDOWS,
+ SMB_VFS_OP_STRICT_LOCK_CHECK,
+ SMB_VFS_OP_TRANSLATE_NAME,
+ SMB_VFS_OP_PARENT_PATHNAME,
+ SMB_VFS_OP_FSCTL,
+ SMB_VFS_OP_OFFLOAD_READ_SEND,
+ SMB_VFS_OP_OFFLOAD_READ_RECV,
+ SMB_VFS_OP_OFFLOAD_WRITE_SEND,
+ SMB_VFS_OP_OFFLOAD_WRITE_RECV,
+ SMB_VFS_OP_FGET_COMPRESSION,
+ SMB_VFS_OP_SET_COMPRESSION,
+ SMB_VFS_OP_SNAP_CHECK_PATH,
+ SMB_VFS_OP_SNAP_CREATE,
+ SMB_VFS_OP_SNAP_DELETE,
+
+ /* DOS attribute operations. */
+ SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND,
+ SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV,
+ SMB_VFS_OP_FGET_DOS_ATTRIBUTES,
+ SMB_VFS_OP_FSET_DOS_ATTRIBUTES,
+
+ /* NT ACL operations. */
+
+ SMB_VFS_OP_FGET_NT_ACL,
+ SMB_VFS_OP_FSET_NT_ACL,
+ SMB_VFS_OP_AUDIT_FILE,
+
+ /* POSIX ACL operations. */
+
+ SMB_VFS_OP_SYS_ACL_GET_FD,
+ SMB_VFS_OP_SYS_ACL_BLOB_GET_FD,
+ SMB_VFS_OP_SYS_ACL_SET_FD,
+ SMB_VFS_OP_SYS_ACL_DELETE_DEF_FD,
+
+ /* EA operations. */
+ SMB_VFS_OP_GETXATTRAT_SEND,
+ SMB_VFS_OP_GETXATTRAT_RECV,
+ SMB_VFS_OP_FGETXATTR,
+ SMB_VFS_OP_FLISTXATTR,
+ SMB_VFS_OP_REMOVEXATTR,
+ SMB_VFS_OP_FREMOVEXATTR,
+ SMB_VFS_OP_FSETXATTR,
+
+ /* aio operations */
+ SMB_VFS_OP_AIO_FORCE,
+
+ /* offline operations */
+ SMB_VFS_OP_IS_OFFLINE,
+ SMB_VFS_OP_SET_OFFLINE,
+
+ /* Durable handle operations. */
+ SMB_VFS_OP_DURABLE_COOKIE,
+ SMB_VFS_OP_DURABLE_DISCONNECT,
+ SMB_VFS_OP_DURABLE_RECONNECT,
+
+ SMB_VFS_OP_FREADDIR_ATTR,
+
+ /* This should always be last enum value */
+
+ SMB_VFS_OP_LAST
+} vfs_op_type;
+
+/* The following array *must* be in the same order as defined in vfs_op_type */
+
+static struct {
+ vfs_op_type type;
+ const char *name;
+} vfs_op_names[] = {
+ { SMB_VFS_OP_CONNECT, "connect" },
+ { SMB_VFS_OP_DISCONNECT, "disconnect" },
+ { SMB_VFS_OP_DISK_FREE, "disk_free" },
+ { SMB_VFS_OP_GET_QUOTA, "get_quota" },
+ { SMB_VFS_OP_SET_QUOTA, "set_quota" },
+ { SMB_VFS_OP_GET_SHADOW_COPY_DATA, "get_shadow_copy_data" },
+ { SMB_VFS_OP_STATVFS, "statvfs" },
+ { SMB_VFS_OP_FS_CAPABILITIES, "fs_capabilities" },
+ { SMB_VFS_OP_GET_DFS_REFERRALS, "get_dfs_referrals" },
+ { SMB_VFS_OP_CREATE_DFS_PATHAT, "create_dfs_pathat" },
+ { SMB_VFS_OP_READ_DFS_PATHAT, "read_dfs_pathat" },
+ { SMB_VFS_OP_FDOPENDIR, "fdopendir" },
+ { SMB_VFS_OP_READDIR, "readdir" },
+ { SMB_VFS_OP_REWINDDIR, "rewinddir" },
+ { SMB_VFS_OP_MKDIRAT, "mkdirat" },
+ { SMB_VFS_OP_CLOSEDIR, "closedir" },
+ { SMB_VFS_OP_OPEN, "open" },
+ { SMB_VFS_OP_OPENAT, "openat" },
+ { SMB_VFS_OP_CREATE_FILE, "create_file" },
+ { SMB_VFS_OP_CLOSE, "close" },
+ { SMB_VFS_OP_READ, "read" },
+ { SMB_VFS_OP_PREAD, "pread" },
+ { SMB_VFS_OP_PREAD_SEND, "pread_send" },
+ { SMB_VFS_OP_PREAD_RECV, "pread_recv" },
+ { SMB_VFS_OP_WRITE, "write" },
+ { SMB_VFS_OP_PWRITE, "pwrite" },
+ { SMB_VFS_OP_PWRITE_SEND, "pwrite_send" },
+ { SMB_VFS_OP_PWRITE_RECV, "pwrite_recv" },
+ { SMB_VFS_OP_LSEEK, "lseek" },
+ { SMB_VFS_OP_SENDFILE, "sendfile" },
+ { SMB_VFS_OP_RECVFILE, "recvfile" },
+ { SMB_VFS_OP_RENAMEAT, "renameat" },
+ { SMB_VFS_OP_FSYNC_SEND, "fsync_send" },
+ { SMB_VFS_OP_FSYNC_RECV, "fsync_recv" },
+ { SMB_VFS_OP_STAT, "stat" },
+ { SMB_VFS_OP_FSTAT, "fstat" },
+ { SMB_VFS_OP_LSTAT, "lstat" },
+ { SMB_VFS_OP_FSTATAT, "fstatat" },
+ { SMB_VFS_OP_GET_ALLOC_SIZE, "get_alloc_size" },
+ { SMB_VFS_OP_UNLINKAT, "unlinkat" },
+ { SMB_VFS_OP_FCHMOD, "fchmod" },
+ { SMB_VFS_OP_FCHOWN, "fchown" },
+ { SMB_VFS_OP_LCHOWN, "lchown" },
+ { SMB_VFS_OP_CHDIR, "chdir" },
+ { SMB_VFS_OP_GETWD, "getwd" },
+ { SMB_VFS_OP_NTIMES, "ntimes" },
+ { SMB_VFS_OP_FNTIMES, "fntimes" },
+ { SMB_VFS_OP_FTRUNCATE, "ftruncate" },
+ { SMB_VFS_OP_FALLOCATE,"fallocate" },
+ { SMB_VFS_OP_LOCK, "lock" },
+ { SMB_VFS_OP_FILESYSTEM_SHAREMODE, "filesystem_sharemode" },
+ { SMB_VFS_OP_FCNTL, "fcntl" },
+ { SMB_VFS_OP_LINUX_SETLEASE, "linux_setlease" },
+ { SMB_VFS_OP_GETLOCK, "getlock" },
+ { SMB_VFS_OP_SYMLINKAT, "symlinkat" },
+ { SMB_VFS_OP_READLINKAT,"readlinkat" },
+ { SMB_VFS_OP_LINKAT, "linkat" },
+ { SMB_VFS_OP_MKNODAT, "mknodat" },
+ { SMB_VFS_OP_REALPATH, "realpath" },
+ { SMB_VFS_OP_FCHFLAGS, "fchflags" },
+ { SMB_VFS_OP_FILE_ID_CREATE, "file_id_create" },
+ { SMB_VFS_OP_FS_FILE_ID, "fs_file_id" },
+ { SMB_VFS_OP_FSTREAMINFO, "fstreaminfo" },
+ { SMB_VFS_OP_GET_REAL_FILENAME, "get_real_filename" },
+ { SMB_VFS_OP_GET_REAL_FILENAME_AT, "get_real_filename_at" },
+ { SMB_VFS_OP_CONNECTPATH, "connectpath" },
+ { SMB_VFS_OP_BRL_LOCK_WINDOWS, "brl_lock_windows" },
+ { SMB_VFS_OP_BRL_UNLOCK_WINDOWS, "brl_unlock_windows" },
+ { SMB_VFS_OP_STRICT_LOCK_CHECK, "strict_lock_check" },
+ { SMB_VFS_OP_TRANSLATE_NAME, "translate_name" },
+ { SMB_VFS_OP_PARENT_PATHNAME, "parent_pathname" },
+ { SMB_VFS_OP_FSCTL, "fsctl" },
+ { SMB_VFS_OP_OFFLOAD_READ_SEND, "offload_read_send" },
+ { SMB_VFS_OP_OFFLOAD_READ_RECV, "offload_read_recv" },
+ { SMB_VFS_OP_OFFLOAD_WRITE_SEND, "offload_write_send" },
+ { SMB_VFS_OP_OFFLOAD_WRITE_RECV, "offload_write_recv" },
+ { SMB_VFS_OP_FGET_COMPRESSION, "fget_compression" },
+ { SMB_VFS_OP_SET_COMPRESSION, "set_compression" },
+ { SMB_VFS_OP_SNAP_CHECK_PATH, "snap_check_path" },
+ { SMB_VFS_OP_SNAP_CREATE, "snap_create" },
+ { SMB_VFS_OP_SNAP_DELETE, "snap_delete" },
+ { SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND, "get_dos_attributes_send" },
+ { SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV, "get_dos_attributes_recv" },
+ { SMB_VFS_OP_FGET_DOS_ATTRIBUTES, "fget_dos_attributes" },
+ { SMB_VFS_OP_FSET_DOS_ATTRIBUTES, "fset_dos_attributes" },
+ { SMB_VFS_OP_FGET_NT_ACL, "fget_nt_acl" },
+ { SMB_VFS_OP_FSET_NT_ACL, "fset_nt_acl" },
+ { SMB_VFS_OP_AUDIT_FILE, "audit_file" },
+ { SMB_VFS_OP_SYS_ACL_GET_FD, "sys_acl_get_fd" },
+ { SMB_VFS_OP_SYS_ACL_BLOB_GET_FD, "sys_acl_blob_get_fd" },
+ { SMB_VFS_OP_SYS_ACL_SET_FD, "sys_acl_set_fd" },
+ { SMB_VFS_OP_SYS_ACL_DELETE_DEF_FD, "sys_acl_delete_def_fd" },
+ { SMB_VFS_OP_GETXATTRAT_SEND, "getxattrat_send" },
+ { SMB_VFS_OP_GETXATTRAT_RECV, "getxattrat_recv" },
+ { SMB_VFS_OP_FGETXATTR, "fgetxattr" },
+ { SMB_VFS_OP_FLISTXATTR, "flistxattr" },
+ { SMB_VFS_OP_REMOVEXATTR, "removexattr" },
+ { SMB_VFS_OP_FREMOVEXATTR, "fremovexattr" },
+ { SMB_VFS_OP_FSETXATTR, "fsetxattr" },
+ { SMB_VFS_OP_AIO_FORCE, "aio_force" },
+ { SMB_VFS_OP_IS_OFFLINE, "is_offline" },
+ { SMB_VFS_OP_SET_OFFLINE, "set_offline" },
+ { SMB_VFS_OP_DURABLE_COOKIE, "durable_cookie" },
+ { SMB_VFS_OP_DURABLE_DISCONNECT, "durable_disconnect" },
+ { SMB_VFS_OP_DURABLE_RECONNECT, "durable_reconnect" },
+ { SMB_VFS_OP_FREADDIR_ATTR, "freaddir_attr" },
+ { SMB_VFS_OP_LAST, NULL }
+};
+
+static int audit_syslog_facility(vfs_handle_struct *handle)
+{
+ static const struct enum_list enum_log_facilities[] = {
+#ifdef LOG_AUTH
+ { LOG_AUTH, "AUTH" },
+#endif
+#ifdef LOG_AUTHPRIV
+ { LOG_AUTHPRIV, "AUTHPRIV" },
+#endif
+#ifdef LOG_AUDIT
+ { LOG_AUDIT, "AUDIT" },
+#endif
+#ifdef LOG_CONSOLE
+ { LOG_CONSOLE, "CONSOLE" },
+#endif
+#ifdef LOG_CRON
+ { LOG_CRON, "CRON" },
+#endif
+#ifdef LOG_DAEMON
+ { LOG_DAEMON, "DAEMON" },
+#endif
+#ifdef LOG_FTP
+ { LOG_FTP, "FTP" },
+#endif
+#ifdef LOG_INSTALL
+ { LOG_INSTALL, "INSTALL" },
+#endif
+#ifdef LOG_KERN
+ { LOG_KERN, "KERN" },
+#endif
+#ifdef LOG_LAUNCHD
+ { LOG_LAUNCHD, "LAUNCHD" },
+#endif
+#ifdef LOG_LFMT
+ { LOG_LFMT, "LFMT" },
+#endif
+#ifdef LOG_LPR
+ { LOG_LPR, "LPR" },
+#endif
+#ifdef LOG_MAIL
+ { LOG_MAIL, "MAIL" },
+#endif
+#ifdef LOG_MEGASAFE
+ { LOG_MEGASAFE, "MEGASAFE" },
+#endif
+#ifdef LOG_NETINFO
+ { LOG_NETINFO, "NETINFO" },
+#endif
+#ifdef LOG_NEWS
+ { LOG_NEWS, "NEWS" },
+#endif
+#ifdef LOG_NFACILITIES
+ { LOG_NFACILITIES, "NFACILITIES" },
+#endif
+#ifdef LOG_NTP
+ { LOG_NTP, "NTP" },
+#endif
+#ifdef LOG_RAS
+ { LOG_RAS, "RAS" },
+#endif
+#ifdef LOG_REMOTEAUTH
+ { LOG_REMOTEAUTH, "REMOTEAUTH" },
+#endif
+#ifdef LOG_SECURITY
+ { LOG_SECURITY, "SECURITY" },
+#endif
+#ifdef LOG_SYSLOG
+ { LOG_SYSLOG, "SYSLOG" },
+#endif
+#ifdef LOG_USER
+ { LOG_USER, "USER" },
+#endif
+#ifdef LOG_UUCP
+ { LOG_UUCP, "UUCP" },
+#endif
+ { LOG_LOCAL0, "LOCAL0" },
+ { LOG_LOCAL1, "LOCAL1" },
+ { LOG_LOCAL2, "LOCAL2" },
+ { LOG_LOCAL3, "LOCAL3" },
+ { LOG_LOCAL4, "LOCAL4" },
+ { LOG_LOCAL5, "LOCAL5" },
+ { LOG_LOCAL6, "LOCAL6" },
+ { LOG_LOCAL7, "LOCAL7" },
+ { -1, NULL }
+ };
+
+ int facility;
+
+ facility = lp_parm_enum(SNUM(handle->conn), "full_audit", "facility", enum_log_facilities, LOG_USER);
+
+ return facility;
+}
+
+static int audit_syslog_priority(vfs_handle_struct *handle)
+{
+ static const struct enum_list enum_log_priorities[] = {
+ { LOG_EMERG, "EMERG" },
+ { LOG_ALERT, "ALERT" },
+ { LOG_CRIT, "CRIT" },
+ { LOG_ERR, "ERR" },
+ { LOG_WARNING, "WARNING" },
+ { LOG_NOTICE, "NOTICE" },
+ { LOG_INFO, "INFO" },
+ { LOG_DEBUG, "DEBUG" },
+ { -1, NULL }
+ };
+
+ int priority;
+
+ priority = lp_parm_enum(SNUM(handle->conn), "full_audit", "priority",
+ enum_log_priorities, LOG_NOTICE);
+ if (priority == -1) {
+ priority = LOG_WARNING;
+ }
+
+ return priority;
+}
+
+static char *audit_prefix(TALLOC_CTX *ctx, connection_struct *conn)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *prefix = NULL;
+ char *result;
+
+ prefix = talloc_strdup(ctx,
+ lp_parm_const_string(SNUM(conn), "full_audit",
+ "prefix", "%u|%I"));
+ if (!prefix) {
+ return NULL;
+ }
+ result = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ prefix);
+ TALLOC_FREE(prefix);
+ return result;
+}
+
+static bool log_success(struct vfs_full_audit_private_data *pd, vfs_op_type op)
+{
+ if (pd->success_ops == NULL) {
+ return True;
+ }
+
+ return bitmap_query(pd->success_ops, op);
+}
+
+static bool log_failure(struct vfs_full_audit_private_data *pd, vfs_op_type op)
+{
+ if (pd->failure_ops == NULL)
+ return True;
+
+ return bitmap_query(pd->failure_ops, op);
+}
+
+static struct bitmap *init_bitmap(TALLOC_CTX *mem_ctx, const char **ops)
+{
+ struct bitmap *bm;
+
+ if (ops == NULL) {
+ DBG_ERR("init_bitmap, ops list is empty (logic error)\n");
+ return NULL;
+ }
+
+ bm = bitmap_talloc(mem_ctx, SMB_VFS_OP_LAST);
+ if (bm == NULL) {
+ DBG_ERR("Could not alloc bitmap\n");
+ return NULL;
+ }
+
+ for (; *ops != NULL; ops += 1) {
+ int i;
+ bool neg = false;
+ const char *op;
+
+ if (strequal(*ops, "all")) {
+ for (i=0; i<SMB_VFS_OP_LAST; i++) {
+ bitmap_set(bm, i);
+ }
+ continue;
+ }
+
+ if (strequal(*ops, "none")) {
+ break;
+ }
+
+ op = ops[0];
+ if (op[0] == '!') {
+ neg = true;
+ op += 1;
+ }
+
+ for (i=0; i<SMB_VFS_OP_LAST; i++) {
+ if ((vfs_op_names[i].name == NULL)
+ || (vfs_op_names[i].type != i)) {
+ smb_panic("vfs_full_audit.c: name table not "
+ "in sync with vfs_op_type enums\n");
+ }
+ if (strequal(op, vfs_op_names[i].name)) {
+ if (neg) {
+ bitmap_clear(bm, i);
+ } else {
+ bitmap_set(bm, i);
+ }
+ break;
+ }
+ }
+ if (i == SMB_VFS_OP_LAST) {
+ DBG_ERR("Could not find opname %s\n", *ops);
+ TALLOC_FREE(bm);
+ return NULL;
+ }
+ }
+ return bm;
+}
+
+static const char *audit_opname(vfs_op_type op)
+{
+ if (op >= SMB_VFS_OP_LAST)
+ return "INVALID VFS OP";
+ return vfs_op_names[op].name;
+}
+
+static TALLOC_CTX *tmp_do_log_ctx;
+/*
+ * Get us a temporary talloc context usable just for DEBUG arguments
+ */
+static TALLOC_CTX *do_log_ctx(void)
+{
+ if (tmp_do_log_ctx == NULL) {
+ tmp_do_log_ctx = talloc_named_const(NULL, 0, "do_log_ctx");
+ }
+ return tmp_do_log_ctx;
+}
+
+static void do_log(vfs_op_type op, bool success, vfs_handle_struct *handle,
+ const char *format, ...) PRINTF_ATTRIBUTE(4, 5);
+
+static void do_log(vfs_op_type op, bool success, vfs_handle_struct *handle,
+ const char *format, ...)
+{
+ struct vfs_full_audit_private_data *pd;
+ fstring err_msg;
+ char *audit_pre = NULL;
+ va_list ap;
+ char *op_msg = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, pd,
+ struct vfs_full_audit_private_data,
+ return;);
+
+ if (success && (!log_success(pd, op)))
+ goto out;
+
+ if (!success && (!log_failure(pd, op)))
+ goto out;
+
+ if (success)
+ fstrcpy(err_msg, "ok");
+ else
+ fstr_sprintf(err_msg, "fail (%s)", strerror(errno));
+
+ va_start(ap, format);
+ op_msg = talloc_vasprintf(talloc_tos(), format, ap);
+ va_end(ap);
+
+ if (!op_msg) {
+ goto out;
+ }
+
+ audit_pre = audit_prefix(talloc_tos(), handle->conn);
+
+ if (pd->do_syslog) {
+ int priority;
+
+ /*
+ * Specify the facility to interoperate with other syslog
+ * callers (smbd for example).
+ */
+ priority = pd->syslog_priority | pd->syslog_facility;
+
+ syslog(priority, "%s|%s|%s|%s\n",
+ audit_pre ? audit_pre : "",
+ audit_opname(op), err_msg, op_msg);
+ } else {
+ DEBUG(1, ("%s|%s|%s|%s\n",
+ audit_pre ? audit_pre : "",
+ audit_opname(op), err_msg, op_msg));
+ }
+ out:
+ TALLOC_FREE(audit_pre);
+ TALLOC_FREE(op_msg);
+ TALLOC_FREE(tmp_do_log_ctx);
+}
+
+/**
+ * Return a string using the do_log_ctx()
+ */
+static const char *smb_fname_str_do_log(struct connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ char *fname = NULL;
+ NTSTATUS status;
+
+ if (smb_fname == NULL) {
+ return "";
+ }
+
+ if (smb_fname->base_name[0] != '/') {
+ char *abs_name = NULL;
+ struct smb_filename *fname_copy = cp_smb_filename(
+ do_log_ctx(),
+ smb_fname);
+ if (fname_copy == NULL) {
+ return "";
+ }
+
+ if (!ISDOT(smb_fname->base_name)) {
+ abs_name = talloc_asprintf(do_log_ctx(),
+ "%s/%s",
+ conn->cwd_fsp->fsp_name->base_name,
+ smb_fname->base_name);
+ } else {
+ abs_name = talloc_strdup(do_log_ctx(),
+ conn->cwd_fsp->fsp_name->base_name);
+ }
+ if (abs_name == NULL) {
+ return "";
+ }
+ fname_copy->base_name = abs_name;
+ smb_fname = fname_copy;
+ }
+
+ status = get_full_smb_filename(do_log_ctx(), smb_fname, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return "";
+ }
+ return fname;
+}
+
+/**
+ * Return an fsp debug string using the do_log_ctx()
+ */
+static const char *fsp_str_do_log(const struct files_struct *fsp)
+{
+ return smb_fname_str_do_log(fsp->conn, fsp->fsp_name);
+}
+
+/* Implementation of vfs_ops. Pass everything on to the default
+ operation but log event first. */
+
+static int smb_full_audit_connect(vfs_handle_struct *handle,
+ const char *svc, const char *user)
+{
+ int result;
+ const char *none[] = { "none" };
+ struct vfs_full_audit_private_data *pd = NULL;
+
+ result = SMB_VFS_NEXT_CONNECT(handle, svc, user);
+ if (result < 0) {
+ return result;
+ }
+
+ pd = talloc_zero(handle, struct vfs_full_audit_private_data);
+ if (!pd) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ pd->syslog_facility = audit_syslog_facility(handle);
+ if (pd->syslog_facility == -1) {
+ DEBUG(1, ("%s: Unknown facility %s\n", __func__,
+ lp_parm_const_string(SNUM(handle->conn),
+ "full_audit", "facility",
+ "USER")));
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ pd->syslog_priority = audit_syslog_priority(handle);
+
+ pd->log_secdesc = lp_parm_bool(SNUM(handle->conn),
+ "full_audit", "log_secdesc", false);
+
+ pd->do_syslog = lp_parm_bool(SNUM(handle->conn),
+ "full_audit", "syslog", true);
+
+#ifdef WITH_SYSLOG
+ if (pd->do_syslog) {
+ openlog("smbd_audit", 0, pd->syslog_facility);
+ }
+#endif
+
+ pd->success_ops = init_bitmap(
+ pd, lp_parm_string_list(SNUM(handle->conn), "full_audit",
+ "success", none));
+ if (pd->success_ops == NULL) {
+ DBG_ERR("Invalid success operations list. Failing connect\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+ pd->failure_ops = init_bitmap(
+ pd, lp_parm_string_list(SNUM(handle->conn), "full_audit",
+ "failure", none));
+ if (pd->failure_ops == NULL) {
+ DBG_ERR("Invalid failure operations list. Failing connect\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ /* Store the private data. */
+ SMB_VFS_HANDLE_SET_DATA(handle, pd, NULL,
+ struct vfs_full_audit_private_data, return -1);
+
+ do_log(SMB_VFS_OP_CONNECT, True, handle,
+ "%s", svc);
+
+ return 0;
+}
+
+static void smb_full_audit_disconnect(vfs_handle_struct *handle)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ SMB_VFS_NEXT_DISCONNECT(handle);
+
+ do_log(SMB_VFS_OP_DISCONNECT, True, handle,
+ "%s", lp_servicename(talloc_tos(), lp_sub, SNUM(handle->conn)));
+
+ /* The bitmaps will be disconnected when the private
+ data is deleted. */
+}
+
+static uint64_t smb_full_audit_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ uint64_t result;
+
+ result = SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, bsize, dfree, dsize);
+
+ /* Don't have a reasonable notion of failure here */
+
+ do_log(SMB_VFS_OP_DISK_FREE,
+ True,
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+
+static int smb_full_audit_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, qt);
+
+ do_log(SMB_VFS_OP_GET_QUOTA,
+ (result >= 0),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+
+static int smb_full_audit_set_quota(struct vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype, unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_SET_QUOTA(handle, qtype, id, qt);
+
+ do_log(SMB_VFS_OP_SET_QUOTA, (result >= 0), handle, "");
+
+ return result;
+}
+
+static int smb_full_audit_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_GET_SHADOW_COPY_DATA(handle, fsp, shadow_copy_data, labels);
+
+ do_log(SMB_VFS_OP_GET_SHADOW_COPY_DATA, (result >= 0), handle, "");
+
+ return result;
+}
+
+static int smb_full_audit_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf);
+
+ do_log(SMB_VFS_OP_STATVFS, (result >= 0), handle, "");
+
+ return result;
+}
+
+static uint32_t smb_full_audit_fs_capabilities(struct vfs_handle_struct *handle, enum timestamp_set_resolution *p_ts_res)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res);
+
+ do_log(SMB_VFS_OP_FS_CAPABILITIES, true, handle, "");
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_get_dfs_referrals(
+ struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_GET_DFS_REFERRALS(handle, r);
+
+ do_log(SMB_VFS_OP_GET_DFS_REFERRALS, NT_STATUS_IS_OK(status),
+ handle, "");
+
+ return status;
+}
+
+static NTSTATUS smb_full_audit_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ NTSTATUS status;
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle,
+ dirfsp,
+ smb_fname,
+ reflist,
+ referral_count);
+
+ do_log(SMB_VFS_OP_CREATE_DFS_PATHAT,
+ NT_STATUS_IS_OK(status),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, full_fname));
+
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static NTSTATUS smb_full_audit_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ struct smb_filename *full_fname = NULL;
+ NTSTATUS status;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle,
+ mem_ctx,
+ dirfsp,
+ smb_fname,
+ ppreflist,
+ preferral_count);
+
+ do_log(SMB_VFS_OP_READ_DFS_PATHAT,
+ NT_STATUS_IS_OK(status),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, full_fname));
+
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static NTSTATUS smb_full_audit_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx, service_path,
+ base_volume);
+ do_log(SMB_VFS_OP_SNAP_CHECK_PATH, NT_STATUS_IS_OK(status),
+ handle, "");
+
+ return status;
+}
+
+static NTSTATUS smb_full_audit_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume, tstamp,
+ rw, base_path, snap_path);
+ do_log(SMB_VFS_OP_SNAP_CREATE, NT_STATUS_IS_OK(status), handle, "");
+
+ return status;
+}
+
+static NTSTATUS smb_full_audit_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx, base_path,
+ snap_path);
+ do_log(SMB_VFS_OP_SNAP_DELETE, NT_STATUS_IS_OK(status), handle, "");
+
+ return status;
+}
+
+static DIR *smb_full_audit_fdopendir(vfs_handle_struct *handle,
+ files_struct *fsp, const char *mask, uint32_t attr)
+{
+ DIR *result;
+
+ result = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr);
+
+ do_log(SMB_VFS_OP_FDOPENDIR, (result != NULL), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static struct dirent *smb_full_audit_readdir(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ struct dirent *result;
+
+ result = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirp);
+
+ /* This operation has no reasonable error condition
+ * (End of dir is also failure), so always succeed.
+ */
+ do_log(SMB_VFS_OP_READDIR, True, handle, "");
+
+ return result;
+}
+
+static void smb_full_audit_rewinddir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ SMB_VFS_NEXT_REWINDDIR(handle, dirp);
+
+ do_log(SMB_VFS_OP_REWINDDIR, True, handle, "");
+}
+
+static int smb_full_audit_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+
+ do_log(SMB_VFS_OP_MKDIRAT,
+ (result >= 0),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, full_fname));
+
+ TALLOC_FREE(full_fname);
+
+ return result;
+}
+
+static int smb_full_audit_closedir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
+
+ do_log(SMB_VFS_OP_CLOSEDIR, (result >= 0), handle, "");
+
+ return result;
+}
+
+static int smb_full_audit_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+
+ do_log(SMB_VFS_OP_OPENAT, (result >= 0), handle, "%s|%s",
+ ((how->flags & O_WRONLY) || (how->flags & O_RDWR))?"w":"r",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_create_file(vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result_fsp,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS result;
+ const char* str_create_disposition;
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ str_create_disposition = "supersede";
+ break;
+ case FILE_OVERWRITE_IF:
+ str_create_disposition = "overwrite_if";
+ break;
+ case FILE_OPEN:
+ str_create_disposition = "open";
+ break;
+ case FILE_OVERWRITE:
+ str_create_disposition = "overwrite";
+ break;
+ case FILE_CREATE:
+ str_create_disposition = "create";
+ break;
+ case FILE_OPEN_IF:
+ str_create_disposition = "open_if";
+ break;
+ default:
+ str_create_disposition = "unknown";
+ }
+
+ result = SMB_VFS_NEXT_CREATE_FILE(
+ handle, /* handle */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ file_attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ lease, /* lease */
+ allocation_size, /* allocation_size */
+ private_flags,
+ sd, /* sd */
+ ea_list, /* ea_list */
+ result_fsp, /* result */
+ pinfo, /* pinfo */
+ in_context_blobs, out_context_blobs); /* create context */
+
+ do_log(SMB_VFS_OP_CREATE_FILE, (NT_STATUS_IS_OK(result)), handle,
+ "0x%x|%s|%s|%s", access_mask,
+ create_options & FILE_DIRECTORY_FILE ? "dir" : "file",
+ str_create_disposition,
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+
+static int smb_full_audit_close(vfs_handle_struct *handle, files_struct *fsp)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+
+ do_log(SMB_VFS_OP_CLOSE, (result >= 0), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static ssize_t smb_full_audit_pread(vfs_handle_struct *handle, files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ ssize_t result;
+
+ result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+
+ do_log(SMB_VFS_OP_PREAD, (result >= 0), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+struct smb_full_audit_pread_state {
+ vfs_handle_struct *handle;
+ files_struct *fsp;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_full_audit_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_full_audit_pread_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_full_audit_pread_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_full_audit_pread_state);
+ if (req == NULL) {
+ do_log(SMB_VFS_OP_PREAD_SEND, false, handle, "%s",
+ fsp_str_do_log(fsp));
+ return NULL;
+ }
+ state->handle = handle;
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ do_log(SMB_VFS_OP_PREAD_SEND, false, handle, "%s",
+ fsp_str_do_log(fsp));
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_full_audit_pread_done, req);
+
+ do_log(SMB_VFS_OP_PREAD_SEND, true, handle, "%s", fsp_str_do_log(fsp));
+ return req;
+}
+
+static void smb_full_audit_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_full_audit_pread_state *state = tevent_req_data(
+ req, struct smb_full_audit_pread_state);
+
+ state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t smb_full_audit_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_full_audit_pread_state *state = tevent_req_data(
+ req, struct smb_full_audit_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ do_log(SMB_VFS_OP_PREAD_RECV, false, state->handle, "%s",
+ fsp_str_do_log(state->fsp));
+ return -1;
+ }
+
+ do_log(SMB_VFS_OP_PREAD_RECV, (state->ret >= 0), state->handle, "%s",
+ fsp_str_do_log(state->fsp));
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static ssize_t smb_full_audit_pwrite(vfs_handle_struct *handle, files_struct *fsp,
+ const void *data, size_t n,
+ off_t offset)
+{
+ ssize_t result;
+
+ result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+
+ do_log(SMB_VFS_OP_PWRITE, (result >= 0), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+struct smb_full_audit_pwrite_state {
+ vfs_handle_struct *handle;
+ files_struct *fsp;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_full_audit_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_full_audit_pwrite_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp,
+ const void *data, size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_full_audit_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_full_audit_pwrite_state);
+ if (req == NULL) {
+ do_log(SMB_VFS_OP_PWRITE_SEND, false, handle, "%s",
+ fsp_str_do_log(fsp));
+ return NULL;
+ }
+ state->handle = handle;
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ do_log(SMB_VFS_OP_PWRITE_SEND, false, handle, "%s",
+ fsp_str_do_log(fsp));
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_full_audit_pwrite_done, req);
+
+ do_log(SMB_VFS_OP_PWRITE_SEND, true, handle, "%s",
+ fsp_str_do_log(fsp));
+ return req;
+}
+
+static void smb_full_audit_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_full_audit_pwrite_state *state = tevent_req_data(
+ req, struct smb_full_audit_pwrite_state);
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t smb_full_audit_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_full_audit_pwrite_state *state = tevent_req_data(
+ req, struct smb_full_audit_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ do_log(SMB_VFS_OP_PWRITE_RECV, false, state->handle, "%s",
+ fsp_str_do_log(state->fsp));
+ return -1;
+ }
+
+ do_log(SMB_VFS_OP_PWRITE_RECV, (state->ret >= 0), state->handle, "%s",
+ fsp_str_do_log(state->fsp));
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static off_t smb_full_audit_lseek(vfs_handle_struct *handle, files_struct *fsp,
+ off_t offset, int whence)
+{
+ ssize_t result;
+
+ result = SMB_VFS_NEXT_LSEEK(handle, fsp, offset, whence);
+
+ do_log(SMB_VFS_OP_LSEEK, (result != (ssize_t)-1), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static ssize_t smb_full_audit_sendfile(vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp,
+ const DATA_BLOB *hdr, off_t offset,
+ size_t n)
+{
+ ssize_t result;
+
+ result = SMB_VFS_NEXT_SENDFILE(handle, tofd, fromfsp, hdr, offset, n);
+
+ do_log(SMB_VFS_OP_SENDFILE, (result >= 0), handle,
+ "%s", fsp_str_do_log(fromfsp));
+
+ return result;
+}
+
+static ssize_t smb_full_audit_recvfile(vfs_handle_struct *handle, int fromfd,
+ files_struct *tofsp,
+ off_t offset,
+ size_t n)
+{
+ ssize_t result;
+
+ result = SMB_VFS_NEXT_RECVFILE(handle, fromfd, tofsp, offset, n);
+
+ do_log(SMB_VFS_OP_RECVFILE, (result >= 0), handle,
+ "%s", fsp_str_do_log(tofsp));
+
+ return result;
+}
+
+static int smb_full_audit_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int result;
+ int saved_errno;
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ TALLOC_FREE(full_fname_src);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+
+ if (result == -1) {
+ saved_errno = errno;
+ }
+ do_log(SMB_VFS_OP_RENAMEAT, (result >= 0), handle, "%s|%s",
+ smb_fname_str_do_log(handle->conn, full_fname_src),
+ smb_fname_str_do_log(handle->conn, full_fname_dst));
+
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+
+ if (result == -1) {
+ errno = saved_errno;
+ }
+ return result;
+}
+
+struct smb_full_audit_fsync_state {
+ vfs_handle_struct *handle;
+ files_struct *fsp;
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_full_audit_fsync_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_full_audit_fsync_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_full_audit_fsync_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_full_audit_fsync_state);
+ if (req == NULL) {
+ do_log(SMB_VFS_OP_FSYNC_SEND, false, handle, "%s",
+ fsp_str_do_log(fsp));
+ return NULL;
+ }
+ state->handle = handle;
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ do_log(SMB_VFS_OP_FSYNC_SEND, false, handle, "%s",
+ fsp_str_do_log(fsp));
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_full_audit_fsync_done, req);
+
+ do_log(SMB_VFS_OP_FSYNC_SEND, true, handle, "%s", fsp_str_do_log(fsp));
+ return req;
+}
+
+static void smb_full_audit_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_full_audit_fsync_state *state = tevent_req_data(
+ req, struct smb_full_audit_fsync_state);
+
+ state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static int smb_full_audit_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_full_audit_fsync_state *state = tevent_req_data(
+ req, struct smb_full_audit_fsync_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ do_log(SMB_VFS_OP_FSYNC_RECV, false, state->handle, "%s",
+ fsp_str_do_log(state->fsp));
+ return -1;
+ }
+
+ do_log(SMB_VFS_OP_FSYNC_RECV, (state->ret >= 0), state->handle, "%s",
+ fsp_str_do_log(state->fsp));
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static int smb_full_audit_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_STAT(handle, smb_fname);
+
+ do_log(SMB_VFS_OP_STAT, (result >= 0), handle, "%s",
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+
+static int smb_full_audit_fstat(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+
+ do_log(SMB_VFS_OP_FSTAT, (result >= 0), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+
+ do_log(SMB_VFS_OP_LSTAT, (result >= 0), handle, "%s",
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+
+static int smb_full_audit_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FSTATAT(handle, dirfsp, smb_fname, sbuf, flags);
+
+ do_log(SMB_VFS_OP_FSTATAT,
+ (result >= 0),
+ handle,
+ "%s/%s",
+ fsp_str_do_log(dirfsp),
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+static uint64_t smb_full_audit_get_alloc_size(vfs_handle_struct *handle,
+ files_struct *fsp, const SMB_STRUCT_STAT *sbuf)
+{
+ uint64_t result;
+
+ result = SMB_VFS_NEXT_GET_ALLOC_SIZE(handle, fsp, sbuf);
+
+ do_log(SMB_VFS_OP_GET_ALLOC_SIZE, (result != (uint64_t)-1), handle,
+ "%llu", (unsigned long long)result);
+
+ return result;
+}
+
+static int smb_full_audit_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+
+ do_log(SMB_VFS_OP_UNLINKAT, (result >= 0), handle, "%s",
+ smb_fname_str_do_log(handle->conn, full_fname));
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int smb_full_audit_fchmod(vfs_handle_struct *handle, files_struct *fsp,
+ mode_t mode)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+
+ do_log(SMB_VFS_OP_FCHMOD, (result >= 0), handle,
+ "%s|%o", fsp_str_do_log(fsp), mode);
+
+ return result;
+}
+
+static int smb_full_audit_fchown(vfs_handle_struct *handle, files_struct *fsp,
+ uid_t uid, gid_t gid)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid);
+
+ do_log(SMB_VFS_OP_FCHOWN, (result >= 0), handle, "%s|%ld|%ld",
+ fsp_str_do_log(fsp), (long int)uid, (long int)gid);
+
+ return result;
+}
+
+static int smb_full_audit_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid);
+
+ do_log(SMB_VFS_OP_LCHOWN, (result >= 0), handle, "%s|%ld|%ld",
+ smb_fname->base_name, (long int)uid, (long int)gid);
+
+ return result;
+}
+
+static int smb_full_audit_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+
+ do_log(SMB_VFS_OP_CHDIR,
+ (result >= 0),
+ handle,
+ "chdir|%s",
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+
+static struct smb_filename *smb_full_audit_getwd(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ struct smb_filename *result;
+
+ result = SMB_VFS_NEXT_GETWD(handle, ctx);
+
+ do_log(SMB_VFS_OP_GETWD, (result != NULL), handle, "%s",
+ result == NULL? "" : result->base_name);
+
+ return result;
+}
+
+static int smb_full_audit_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ int result;
+ time_t create_time = convert_timespec_to_time_t(ft->create_time);
+ time_t atime = convert_timespec_to_time_t(ft->atime);
+ time_t mtime = convert_timespec_to_time_t(ft->mtime);
+ time_t ctime = convert_timespec_to_time_t(ft->ctime);
+ const char *create_time_str = "";
+ const char *atime_str = "";
+ const char *mtime_str = "";
+ const char *ctime_str = "";
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (frame == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+
+ if (create_time > 0) {
+ create_time_str = timestring(frame, create_time);
+ }
+ if (atime > 0) {
+ atime_str = timestring(frame, atime);
+ }
+ if (mtime > 0) {
+ mtime_str = timestring(frame, mtime);
+ }
+ if (ctime > 0) {
+ ctime_str = timestring(frame, ctime);
+ }
+
+ do_log(SMB_VFS_OP_FNTIMES,
+ (result >= 0),
+ handle,
+ "%s|%s|%s|%s|%s",
+ fsp_str_do_log(fsp),
+ create_time_str,
+ atime_str,
+ mtime_str,
+ ctime_str);
+
+ TALLOC_FREE(frame);
+
+ return result;
+}
+
+static int smb_full_audit_ftruncate(vfs_handle_struct *handle, files_struct *fsp,
+ off_t len)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len);
+
+ do_log(SMB_VFS_OP_FTRUNCATE, (result >= 0), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_fallocate(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len);
+
+ do_log(SMB_VFS_OP_FALLOCATE, (result >= 0), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static bool smb_full_audit_lock(vfs_handle_struct *handle, files_struct *fsp,
+ int op, off_t offset, off_t count, int type)
+{
+ bool result;
+
+ result = SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type);
+
+ do_log(SMB_VFS_OP_LOCK, result, handle, "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle,
+ fsp,
+ share_access,
+ access_mask);
+
+ do_log(SMB_VFS_OP_FILESYSTEM_SHAREMODE, (result >= 0), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_fcntl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ int cmd, va_list cmd_arg)
+{
+ void *arg;
+ va_list dup_cmd_arg;
+ int result;
+
+ va_copy(dup_cmd_arg, cmd_arg);
+ arg = va_arg(dup_cmd_arg, void *);
+ result = SMB_VFS_NEXT_FCNTL(handle, fsp, cmd, arg);
+ va_end(dup_cmd_arg);
+
+ do_log(SMB_VFS_OP_FCNTL, (result >= 0), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_linux_setlease(vfs_handle_struct *handle, files_struct *fsp,
+ int leasetype)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype);
+
+ do_log(SMB_VFS_OP_LINUX_SETLEASE, (result >= 0), handle, "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static bool smb_full_audit_getlock(vfs_handle_struct *handle, files_struct *fsp,
+ off_t *poffset, off_t *pcount, int *ptype, pid_t *ppid)
+{
+ bool result;
+
+ result = SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, pcount, ptype, ppid);
+
+ do_log(SMB_VFS_OP_GETLOCK, result, handle, "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ new_smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_SYMLINKAT(handle,
+ link_contents,
+ dirfsp,
+ new_smb_fname);
+
+ do_log(SMB_VFS_OP_SYMLINKAT,
+ (result >= 0),
+ handle,
+ "%s|%s",
+ link_contents->base_name,
+ smb_fname_str_do_log(handle->conn, full_fname));
+
+ TALLOC_FREE(full_fname);
+
+ return result;
+}
+
+static int smb_full_audit_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_READLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+
+ do_log(SMB_VFS_OP_READLINKAT,
+ (result >= 0),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, full_fname));
+
+ TALLOC_FREE(full_fname);
+
+ return result;
+}
+
+static int smb_full_audit_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ struct smb_filename *old_full_fname = NULL;
+ struct smb_filename *new_full_fname = NULL;
+ int result;
+
+ old_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ old_smb_fname);
+ if (old_full_fname == NULL) {
+ return -1;
+ }
+ new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (new_full_fname == NULL) {
+ TALLOC_FREE(old_full_fname);
+ return -1;
+ }
+ result = SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+
+ do_log(SMB_VFS_OP_LINKAT,
+ (result >= 0),
+ handle,
+ "%s|%s",
+ smb_fname_str_do_log(handle->conn, old_full_fname),
+ smb_fname_str_do_log(handle->conn, new_full_fname));
+
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(new_full_fname);
+
+ return result;
+}
+
+static int smb_full_audit_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ result = SMB_VFS_NEXT_MKNODAT(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+
+ do_log(SMB_VFS_OP_MKNODAT,
+ (result >= 0),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, full_fname));
+
+ TALLOC_FREE(full_fname);
+
+ return result;
+}
+
+static struct smb_filename *smb_full_audit_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *result_fname = NULL;
+
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname);
+
+ do_log(SMB_VFS_OP_REALPATH,
+ (result_fname != NULL),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result_fname;
+}
+
+static int smb_full_audit_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
+
+ do_log(SMB_VFS_OP_FCHFLAGS,
+ (result != 0),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, fsp->fsp_name));
+
+ return result;
+}
+
+static struct file_id smb_full_audit_file_id_create(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct file_id id_zero = { 0 };
+ struct file_id result;
+ struct file_id_buf idbuf;
+
+ result = SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf);
+
+ do_log(SMB_VFS_OP_FILE_ID_CREATE,
+ !file_id_equal(&id_zero, &result),
+ handle,
+ "%s",
+ file_id_str_buf(result, &idbuf));
+
+ return result;
+}
+
+static uint64_t smb_full_audit_fs_file_id(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ uint64_t result;
+
+ result = SMB_VFS_NEXT_FS_FILE_ID(handle, sbuf);
+
+ do_log(SMB_VFS_OP_FS_FILE_ID,
+ result != 0,
+ handle, "%" PRIu64, result);
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_fstreaminfo(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx,
+ pnum_streams, pstreams);
+
+ do_log(SMB_VFS_OP_FSTREAMINFO,
+ NT_STATUS_IS_OK(result),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, fsp->fsp_name));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, dirfsp, name, mem_ctx, found_name);
+
+ do_log(SMB_VFS_OP_GET_REAL_FILENAME_AT,
+ NT_STATUS_IS_OK(result),
+ handle,
+ "%s/%s->%s",
+ fsp_str_dbg(dirfsp),
+ name,
+ NT_STATUS_IS_OK(result) ? *found_name : "");
+
+ return result;
+}
+
+static const char *smb_full_audit_connectpath(
+ vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ const char *result;
+
+ result = SMB_VFS_NEXT_CONNECTPATH(handle, dirfsp, smb_fname);
+
+ do_log(SMB_VFS_OP_CONNECTPATH,
+ result != NULL,
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, smb_fname));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_BRL_LOCK_WINDOWS(handle, br_lck, plock);
+
+ do_log(SMB_VFS_OP_BRL_LOCK_WINDOWS, NT_STATUS_IS_OK(result), handle,
+ "%s:%llu-%llu. type=%d.",
+ fsp_str_do_log(brl_fsp(br_lck)),
+ (unsigned long long)plock->start,
+ (unsigned long long)plock->size,
+ plock->lock_type);
+
+ return result;
+}
+
+static bool smb_full_audit_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ bool result;
+
+ result = SMB_VFS_NEXT_BRL_UNLOCK_WINDOWS(handle, br_lck, plock);
+
+ do_log(SMB_VFS_OP_BRL_UNLOCK_WINDOWS, (result == 0), handle,
+ "%s:%llu-%llu:%d", fsp_str_do_log(brl_fsp(br_lck)),
+ (unsigned long long)plock->start,
+ (unsigned long long)plock->size,
+ plock->lock_type);
+
+ return result;
+}
+
+static bool smb_full_audit_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock)
+{
+ bool result;
+
+ result = SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock);
+
+ do_log(SMB_VFS_OP_STRICT_LOCK_CHECK, result, handle,
+ "%s:%llu-%llu:%d", fsp_str_do_log(fsp),
+ (unsigned long long)plock->start,
+ (unsigned long long)plock->size,
+ plock->lock_type);
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_translate_name(struct vfs_handle_struct *handle,
+ const char *name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_TRANSLATE_NAME(handle, name, direction, mem_ctx,
+ mapped_name);
+
+ do_log(SMB_VFS_OP_TRANSLATE_NAME, NT_STATUS_IS_OK(result), handle, "");
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_PARENT_PATHNAME(handle,
+ mem_ctx,
+ smb_fname_in,
+ parent_dir_out,
+ atname_out);
+ do_log(SMB_VFS_OP_CONNECTPATH,
+ NT_STATUS_IS_OK(result),
+ handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, smb_fname_in));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags,
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_FSCTL(handle,
+ fsp,
+ ctx,
+ function,
+ req_flags,
+ _in_data,
+ in_len,
+ _out_data,
+ max_out_len,
+ out_len);
+
+ do_log(SMB_VFS_OP_FSCTL, NT_STATUS_IS_OK(result), handle, "");
+
+ return result;
+}
+
+static struct tevent_req *smb_full_audit_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ struct tevent_req *req = NULL;
+
+ req = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev, handle, fsp,
+ fsctl, ttl, offset, to_copy);
+
+ do_log(SMB_VFS_OP_OFFLOAD_READ_SEND, req, handle, "");
+
+ return req;
+}
+
+static NTSTATUS smb_full_audit_offload_read_recv(
+ struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *_token_blob)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(req, handle, mem_ctx,
+ flags, xferlen, _token_blob);
+
+ do_log(SMB_VFS_OP_OFFLOAD_READ_RECV, NT_STATUS_IS_OK(status), handle, "");
+
+ return status;
+}
+
+static struct tevent_req *smb_full_audit_offload_write_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num)
+{
+ struct tevent_req *req;
+
+ req = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, mem_ctx, ev,
+ fsctl, token, transfer_offset,
+ dest_fsp, dest_off, num);
+
+ do_log(SMB_VFS_OP_OFFLOAD_WRITE_SEND, req, handle, "");
+
+ return req;
+}
+
+static NTSTATUS smb_full_audit_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(handle, req, copied);
+
+ do_log(SMB_VFS_OP_OFFLOAD_WRITE_RECV, NT_STATUS_IS_OK(result), handle, "");
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_fget_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_FGET_COMPRESSION(handle, mem_ctx, fsp,
+ _compression_fmt);
+
+ do_log(SMB_VFS_OP_FGET_COMPRESSION, NT_STATUS_IS_OK(result), handle,
+ "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_set_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_SET_COMPRESSION(handle, mem_ctx, fsp,
+ compression_fmt);
+
+ do_log(SMB_VFS_OP_SET_COMPRESSION, NT_STATUS_IS_OK(result), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **pattr_data)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_FREADDIR_ATTR(handle, fsp, mem_ctx, pattr_data);
+
+ do_log(SMB_VFS_OP_FREADDIR_ATTR,
+ NT_STATUS_IS_OK(status),
+ handle,
+ "%s",
+ fsp_str_do_log(fsp));
+
+ return status;
+}
+
+struct smb_full_audit_get_dos_attributes_state {
+ struct vfs_aio_state aio_state;
+ vfs_handle_struct *handle;
+ files_struct *dir_fsp;
+ const struct smb_filename *smb_fname;
+ uint32_t dosmode;
+};
+
+static void smb_full_audit_get_dos_attributes_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_full_audit_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct smb_full_audit_get_dos_attributes_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_full_audit_get_dos_attributes_state);
+ if (req == NULL) {
+ do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND,
+ false,
+ handle,
+ "%s/%s",
+ fsp_str_do_log(dir_fsp),
+ smb_fname->base_name);
+ return NULL;
+ }
+ *state = (struct smb_full_audit_get_dos_attributes_state) {
+ .handle = handle,
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ };
+
+ subreq = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_SEND(mem_ctx,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND,
+ false,
+ handle,
+ "%s/%s",
+ fsp_str_do_log(dir_fsp),
+ smb_fname->base_name);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb_full_audit_get_dos_attributes_done,
+ req);
+
+ do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_SEND,
+ true,
+ handle,
+ "%s/%s",
+ fsp_str_do_log(dir_fsp),
+ smb_fname->base_name);
+
+ return req;
+}
+
+static void smb_full_audit_get_dos_attributes_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb_full_audit_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_full_audit_get_dos_attributes_state);
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_RECV(subreq,
+ &state->aio_state,
+ &state->dosmode);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS smb_full_audit_get_dos_attributes_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode)
+{
+ struct smb_full_audit_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_full_audit_get_dos_attributes_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV,
+ false,
+ state->handle,
+ "%s/%s",
+ fsp_str_do_log(state->dir_fsp),
+ state->smb_fname->base_name);
+ tevent_req_received(req);
+ return status;
+ }
+
+ do_log(SMB_VFS_OP_GET_DOS_ATTRIBUTES_RECV,
+ true,
+ state->handle,
+ "%s/%s",
+ fsp_str_do_log(state->dir_fsp),
+ state->smb_fname->base_name);
+
+ *aio_state = state->aio_state;
+ *dosmode = state->dosmode;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb_full_audit_fget_dos_attributes(
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle,
+ fsp,
+ dosmode);
+
+ do_log(SMB_VFS_OP_FGET_DOS_ATTRIBUTES,
+ NT_STATUS_IS_OK(status),
+ handle,
+ "%s",
+ fsp_str_do_log(fsp));
+
+ return status;
+}
+
+static NTSTATUS smb_full_audit_fset_dos_attributes(
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle,
+ fsp,
+ dosmode);
+
+ do_log(SMB_VFS_OP_FSET_DOS_ATTRIBUTES,
+ NT_STATUS_IS_OK(status),
+ handle,
+ "%s",
+ fsp_str_do_log(fsp));
+
+ return status;
+}
+
+static NTSTATUS smb_full_audit_fget_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info,
+ mem_ctx, ppdesc);
+
+ do_log(SMB_VFS_OP_FGET_NT_ACL, NT_STATUS_IS_OK(result), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ struct vfs_full_audit_private_data *pd;
+ NTSTATUS result;
+ char *sd = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, pd,
+ struct vfs_full_audit_private_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (pd->log_secdesc) {
+ sd = sddl_encode(talloc_tos(), psd, get_global_sam_sid());
+ }
+
+ result = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+
+ do_log(SMB_VFS_OP_FSET_NT_ACL, NT_STATUS_IS_OK(result), handle,
+ "%s [%s]", fsp_str_do_log(fsp), sd ? sd : "");
+
+ TALLOC_FREE(sd);
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_AUDIT_FILE(handle,
+ file,
+ sacl,
+ access_requested,
+ access_denied);
+
+ do_log(SMB_VFS_OP_AUDIT_FILE, NT_STATUS_IS_OK(result), handle,
+ "%s",
+ smb_fname_str_do_log(handle->conn, file));
+
+ return result;
+}
+
+static SMB_ACL_T smb_full_audit_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result;
+
+ result = SMB_VFS_NEXT_SYS_ACL_GET_FD(handle,
+ fsp,
+ type,
+ mem_ctx);
+
+ do_log(SMB_VFS_OP_SYS_ACL_GET_FD, (result != NULL), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx, blob_description, blob);
+
+ do_log(SMB_VFS_OP_SYS_ACL_BLOB_GET_FD, (result >= 0), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_sys_acl_set_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl);
+
+ do_log(SMB_VFS_OP_SYS_ACL_SET_FD, (result >= 0), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp);
+
+ do_log(SMB_VFS_OP_SYS_ACL_DELETE_DEF_FD,
+ (result >= 0),
+ handle,
+ "%s",
+ fsp_str_do_log(fsp));
+
+ return result;
+}
+
+struct smb_full_audit_getxattrat_state {
+ struct vfs_aio_state aio_state;
+ vfs_handle_struct *handle;
+ files_struct *dir_fsp;
+ const struct smb_filename *smb_fname;
+ const char *xattr_name;
+ ssize_t xattr_size;
+ uint8_t *xattr_value;
+};
+
+static void smb_full_audit_getxattrat_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_full_audit_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct smb_full_audit_getxattrat_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_full_audit_getxattrat_state);
+ if (req == NULL) {
+ do_log(SMB_VFS_OP_GETXATTRAT_SEND,
+ false,
+ handle,
+ "%s/%s|%s",
+ fsp_str_do_log(dir_fsp),
+ smb_fname->base_name,
+ xattr_name);
+ return NULL;
+ }
+ *state = (struct smb_full_audit_getxattrat_state) {
+ .handle = handle,
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ .xattr_name = xattr_name,
+ };
+
+ subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname,
+ xattr_name,
+ alloc_hint);
+ if (tevent_req_nomem(subreq, req)) {
+ do_log(SMB_VFS_OP_GETXATTRAT_SEND,
+ false,
+ handle,
+ "%s/%s|%s",
+ fsp_str_do_log(dir_fsp),
+ smb_fname->base_name,
+ xattr_name);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_full_audit_getxattrat_done, req);
+
+ do_log(SMB_VFS_OP_GETXATTRAT_SEND,
+ true,
+ handle,
+ "%s/%s|%s",
+ fsp_str_do_log(dir_fsp),
+ smb_fname->base_name,
+ xattr_name);
+
+ return req;
+}
+
+static void smb_full_audit_getxattrat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_full_audit_getxattrat_state *state = tevent_req_data(
+ req, struct smb_full_audit_getxattrat_state);
+
+ state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq,
+ &state->aio_state,
+ state,
+ &state->xattr_value);
+ TALLOC_FREE(subreq);
+ if (state->xattr_size == -1) {
+ tevent_req_error(req, state->aio_state.error);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t smb_full_audit_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct smb_full_audit_getxattrat_state *state = tevent_req_data(
+ req, struct smb_full_audit_getxattrat_state);
+ ssize_t xattr_size;
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ do_log(SMB_VFS_OP_GETXATTRAT_RECV,
+ false,
+ state->handle,
+ "%s/%s|%s",
+ fsp_str_do_log(state->dir_fsp),
+ state->smb_fname->base_name,
+ state->xattr_name);
+ tevent_req_received(req);
+ return -1;
+ }
+
+ do_log(SMB_VFS_OP_GETXATTRAT_RECV,
+ true,
+ state->handle,
+ "%s/%s|%s",
+ fsp_str_do_log(state->dir_fsp),
+ state->smb_fname->base_name,
+ state->xattr_name);
+
+ *aio_state = state->aio_state;
+ xattr_size = state->xattr_size;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+static ssize_t smb_full_audit_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name, void *value, size_t size)
+{
+ ssize_t result;
+
+ result = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size);
+
+ do_log(SMB_VFS_OP_FGETXATTR, (result >= 0), handle,
+ "%s|%s", fsp_str_do_log(fsp), name);
+
+ return result;
+}
+
+static ssize_t smb_full_audit_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ ssize_t result;
+
+ result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size);
+
+ do_log(SMB_VFS_OP_FLISTXATTR, (result >= 0), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static int smb_full_audit_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
+
+ do_log(SMB_VFS_OP_FREMOVEXATTR, (result >= 0), handle,
+ "%s|%s", fsp_str_do_log(fsp), name);
+
+ return result;
+}
+
+static int smb_full_audit_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ const void *value, size_t size, int flags)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags);
+
+ do_log(SMB_VFS_OP_FSETXATTR, (result >= 0), handle,
+ "%s|%s", fsp_str_do_log(fsp), name);
+
+ return result;
+}
+
+static bool smb_full_audit_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ bool result;
+
+ result = SMB_VFS_NEXT_AIO_FORCE(handle, fsp);
+ do_log(SMB_VFS_OP_AIO_FORCE, result, handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_DURABLE_COOKIE(handle,
+ fsp,
+ mem_ctx,
+ cookie);
+
+ do_log(SMB_VFS_OP_DURABLE_COOKIE, NT_STATUS_IS_OK(result), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_durable_disconnect(
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_DURABLE_DISCONNECT(handle,
+ fsp,
+ old_cookie,
+ mem_ctx,
+ new_cookie);
+
+ do_log(SMB_VFS_OP_DURABLE_DISCONNECT, NT_STATUS_IS_OK(result), handle,
+ "%s", fsp_str_do_log(fsp));
+
+ return result;
+}
+
+static NTSTATUS smb_full_audit_durable_reconnect(
+ struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie)
+{
+ NTSTATUS result;
+
+ result = SMB_VFS_NEXT_DURABLE_RECONNECT(handle,
+ smb1req,
+ op,
+ old_cookie,
+ mem_ctx,
+ fsp,
+ new_cookie);
+
+ do_log(SMB_VFS_OP_DURABLE_RECONNECT,
+ NT_STATUS_IS_OK(result),
+ handle,
+ "");
+
+ return result;
+}
+
+static struct vfs_fn_pointers vfs_full_audit_fns = {
+
+ /* Disk operations */
+
+ .connect_fn = smb_full_audit_connect,
+ .disconnect_fn = smb_full_audit_disconnect,
+ .disk_free_fn = smb_full_audit_disk_free,
+ .get_quota_fn = smb_full_audit_get_quota,
+ .set_quota_fn = smb_full_audit_set_quota,
+ .get_shadow_copy_data_fn = smb_full_audit_get_shadow_copy_data,
+ .statvfs_fn = smb_full_audit_statvfs,
+ .fs_capabilities_fn = smb_full_audit_fs_capabilities,
+ .get_dfs_referrals_fn = smb_full_audit_get_dfs_referrals,
+ .create_dfs_pathat_fn = smb_full_audit_create_dfs_pathat,
+ .read_dfs_pathat_fn = smb_full_audit_read_dfs_pathat,
+ .fdopendir_fn = smb_full_audit_fdopendir,
+ .readdir_fn = smb_full_audit_readdir,
+ .rewind_dir_fn = smb_full_audit_rewinddir,
+ .mkdirat_fn = smb_full_audit_mkdirat,
+ .closedir_fn = smb_full_audit_closedir,
+ .openat_fn = smb_full_audit_openat,
+ .create_file_fn = smb_full_audit_create_file,
+ .close_fn = smb_full_audit_close,
+ .pread_fn = smb_full_audit_pread,
+ .pread_send_fn = smb_full_audit_pread_send,
+ .pread_recv_fn = smb_full_audit_pread_recv,
+ .pwrite_fn = smb_full_audit_pwrite,
+ .pwrite_send_fn = smb_full_audit_pwrite_send,
+ .pwrite_recv_fn = smb_full_audit_pwrite_recv,
+ .lseek_fn = smb_full_audit_lseek,
+ .sendfile_fn = smb_full_audit_sendfile,
+ .recvfile_fn = smb_full_audit_recvfile,
+ .renameat_fn = smb_full_audit_renameat,
+ .fsync_send_fn = smb_full_audit_fsync_send,
+ .fsync_recv_fn = smb_full_audit_fsync_recv,
+ .stat_fn = smb_full_audit_stat,
+ .fstat_fn = smb_full_audit_fstat,
+ .lstat_fn = smb_full_audit_lstat,
+ .fstatat_fn = smb_full_audit_fstatat,
+ .get_alloc_size_fn = smb_full_audit_get_alloc_size,
+ .unlinkat_fn = smb_full_audit_unlinkat,
+ .fchmod_fn = smb_full_audit_fchmod,
+ .fchown_fn = smb_full_audit_fchown,
+ .lchown_fn = smb_full_audit_lchown,
+ .chdir_fn = smb_full_audit_chdir,
+ .getwd_fn = smb_full_audit_getwd,
+ .fntimes_fn = smb_full_audit_fntimes,
+ .ftruncate_fn = smb_full_audit_ftruncate,
+ .fallocate_fn = smb_full_audit_fallocate,
+ .lock_fn = smb_full_audit_lock,
+ .filesystem_sharemode_fn = smb_full_audit_filesystem_sharemode,
+ .fcntl_fn = smb_full_audit_fcntl,
+ .linux_setlease_fn = smb_full_audit_linux_setlease,
+ .getlock_fn = smb_full_audit_getlock,
+ .symlinkat_fn = smb_full_audit_symlinkat,
+ .readlinkat_fn = smb_full_audit_readlinkat,
+ .linkat_fn = smb_full_audit_linkat,
+ .mknodat_fn = smb_full_audit_mknodat,
+ .realpath_fn = smb_full_audit_realpath,
+ .fchflags_fn = smb_full_audit_fchflags,
+ .file_id_create_fn = smb_full_audit_file_id_create,
+ .fs_file_id_fn = smb_full_audit_fs_file_id,
+ .offload_read_send_fn = smb_full_audit_offload_read_send,
+ .offload_read_recv_fn = smb_full_audit_offload_read_recv,
+ .offload_write_send_fn = smb_full_audit_offload_write_send,
+ .offload_write_recv_fn = smb_full_audit_offload_write_recv,
+ .fget_compression_fn = smb_full_audit_fget_compression,
+ .set_compression_fn = smb_full_audit_set_compression,
+ .snap_check_path_fn = smb_full_audit_snap_check_path,
+ .snap_create_fn = smb_full_audit_snap_create,
+ .snap_delete_fn = smb_full_audit_snap_delete,
+ .fstreaminfo_fn = smb_full_audit_fstreaminfo,
+ .get_real_filename_at_fn = smb_full_audit_get_real_filename_at,
+ .connectpath_fn = smb_full_audit_connectpath,
+ .brl_lock_windows_fn = smb_full_audit_brl_lock_windows,
+ .brl_unlock_windows_fn = smb_full_audit_brl_unlock_windows,
+ .strict_lock_check_fn = smb_full_audit_strict_lock_check,
+ .translate_name_fn = smb_full_audit_translate_name,
+ .parent_pathname_fn = smb_full_audit_parent_pathname,
+ .fsctl_fn = smb_full_audit_fsctl,
+ .get_dos_attributes_send_fn = smb_full_audit_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = smb_full_audit_get_dos_attributes_recv,
+ .fget_dos_attributes_fn = smb_full_audit_fget_dos_attributes,
+ .fset_dos_attributes_fn = smb_full_audit_fset_dos_attributes,
+ .fget_nt_acl_fn = smb_full_audit_fget_nt_acl,
+ .fset_nt_acl_fn = smb_full_audit_fset_nt_acl,
+ .audit_file_fn = smb_full_audit_audit_file,
+ .sys_acl_get_fd_fn = smb_full_audit_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = smb_full_audit_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = smb_full_audit_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = smb_full_audit_sys_acl_delete_def_fd,
+ .getxattrat_send_fn = smb_full_audit_getxattrat_send,
+ .getxattrat_recv_fn = smb_full_audit_getxattrat_recv,
+ .fgetxattr_fn = smb_full_audit_fgetxattr,
+ .flistxattr_fn = smb_full_audit_flistxattr,
+ .fremovexattr_fn = smb_full_audit_fremovexattr,
+ .fsetxattr_fn = smb_full_audit_fsetxattr,
+ .aio_force_fn = smb_full_audit_aio_force,
+ .durable_cookie_fn = smb_full_audit_durable_cookie,
+ .durable_disconnect_fn = smb_full_audit_durable_disconnect,
+ .durable_reconnect_fn = smb_full_audit_durable_reconnect,
+ .freaddir_attr_fn = smb_full_audit_freaddir_attr,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_full_audit_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ smb_vfs_assert_all_fns(&vfs_full_audit_fns, "full_audit");
+
+ ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "full_audit",
+ &vfs_full_audit_fns);
+
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ vfs_full_audit_debug_level = debug_add_class("full_audit");
+ if (vfs_full_audit_debug_level == -1) {
+ vfs_full_audit_debug_level = DBGC_VFS;
+ DEBUG(0, ("vfs_full_audit: Couldn't register custom debugging "
+ "class!\n"));
+ } else {
+ DEBUG(10, ("vfs_full_audit: Debug class number of "
+ "'full_audit': %d\n", vfs_full_audit_debug_level));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_glusterfs.c b/source3/modules/vfs_glusterfs.c
new file mode 100644
index 0000000..235329f
--- /dev/null
+++ b/source3/modules/vfs_glusterfs.c
@@ -0,0 +1,2673 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Wrap GlusterFS GFAPI calls in vfs functions.
+
+ Copyright (c) 2013 Anand Avati <avati@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * @file vfs_glusterfs.c
+ * @author Anand Avati <avati@redhat.com>
+ * @date May 2013
+ * @brief Samba VFS module for glusterfs
+ *
+ * @todo
+ * - sendfile/recvfile support
+ *
+ * A Samba VFS module for GlusterFS, based on Gluster's libgfapi.
+ * This is a "bottom" vfs module (not something to be stacked on top of
+ * another module), and translates (most) calls to the closest actions
+ * available in libgfapi.
+ *
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include <stdio.h>
+#include <glusterfs/api/glfs.h>
+#include "lib/util/dlinklist.h"
+#include "lib/util/tevent_unix.h"
+#include "smbd/globals.h"
+#include "lib/util/sys_rw.h"
+#include "smbprofile.h"
+#include "modules/posixacl_xattr.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+
+#define DEFAULT_VOLFILE_SERVER "localhost"
+#define GLUSTER_NAME_MAX 255
+
+/**
+ * Helper to convert struct stat to struct stat_ex.
+ */
+static void smb_stat_ex_from_stat(struct stat_ex *dst, const struct stat *src)
+{
+ ZERO_STRUCTP(dst);
+
+ dst->st_ex_dev = src->st_dev;
+ dst->st_ex_ino = src->st_ino;
+ dst->st_ex_mode = src->st_mode;
+ dst->st_ex_nlink = src->st_nlink;
+ dst->st_ex_uid = src->st_uid;
+ dst->st_ex_gid = src->st_gid;
+ dst->st_ex_rdev = src->st_rdev;
+ dst->st_ex_size = src->st_size;
+ dst->st_ex_atime.tv_sec = src->st_atime;
+ dst->st_ex_mtime.tv_sec = src->st_mtime;
+ dst->st_ex_ctime.tv_sec = src->st_ctime;
+ dst->st_ex_btime.tv_sec = src->st_mtime;
+ dst->st_ex_blksize = src->st_blksize;
+ dst->st_ex_blocks = src->st_blocks;
+#ifdef STAT_HAVE_NSEC
+ dst->st_ex_atime.tv_nsec = src->st_atime_nsec;
+ dst->st_ex_mtime.tv_nsec = src->st_mtime_nsec;
+ dst->st_ex_ctime.tv_nsec = src->st_ctime_nsec;
+ dst->st_ex_btime.tv_nsec = src->st_mtime_nsec;
+#endif
+}
+
+/* pre-opened glfs_t */
+
+static struct glfs_preopened {
+ char *volume;
+ char *connectpath;
+ glfs_t *fs;
+ int ref;
+ struct glfs_preopened *next, *prev;
+} *glfs_preopened;
+
+
+static int glfs_set_preopened(const char *volume, const char *connectpath, glfs_t *fs)
+{
+ struct glfs_preopened *entry = NULL;
+
+ entry = talloc_zero(NULL, struct glfs_preopened);
+ if (!entry) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ entry->volume = talloc_strdup(entry, volume);
+ if (!entry->volume) {
+ talloc_free(entry);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ entry->connectpath = talloc_strdup(entry, connectpath);
+ if (entry->connectpath == NULL) {
+ talloc_free(entry);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ entry->fs = fs;
+ entry->ref = 1;
+
+ DLIST_ADD(glfs_preopened, entry);
+
+ return 0;
+}
+
+static glfs_t *glfs_find_preopened(const char *volume, const char *connectpath)
+{
+ struct glfs_preopened *entry = NULL;
+
+ for (entry = glfs_preopened; entry; entry = entry->next) {
+ if (strcmp(entry->volume, volume) == 0 &&
+ strcmp(entry->connectpath, connectpath) == 0)
+ {
+ entry->ref++;
+ return entry->fs;
+ }
+ }
+
+ return NULL;
+}
+
+static void glfs_clear_preopened(glfs_t *fs)
+{
+ struct glfs_preopened *entry = NULL;
+
+ for (entry = glfs_preopened; entry; entry = entry->next) {
+ if (entry->fs == fs) {
+ if (--entry->ref)
+ return;
+
+ DLIST_REMOVE(glfs_preopened, entry);
+
+ glfs_fini(entry->fs);
+ talloc_free(entry);
+ break;
+ }
+ }
+}
+
+static int vfs_gluster_set_volfile_servers(glfs_t *fs,
+ const char *volfile_servers)
+{
+ char *server = NULL;
+ size_t server_count = 0;
+ size_t server_success = 0;
+ int ret = -1;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ DBG_INFO("servers list %s\n", volfile_servers);
+
+ while (next_token_talloc(frame, &volfile_servers, &server, " \t")) {
+ char *transport = NULL;
+ char *host = NULL;
+ int port = 0;
+
+ server_count++;
+ DBG_INFO("server %zu %s\n", server_count, server);
+
+ /* Determine the transport type */
+ if (strncmp(server, "unix+", 5) == 0) {
+ port = 0;
+ transport = talloc_strdup(frame, "unix");
+ if (!transport) {
+ errno = ENOMEM;
+ goto out;
+ }
+ host = talloc_strdup(frame, server + 5);
+ if (!host) {
+ errno = ENOMEM;
+ goto out;
+ }
+ } else {
+ char *p = NULL;
+ char *port_index = NULL;
+
+ if (strncmp(server, "tcp+", 4) == 0) {
+ server += 4;
+ }
+
+ /* IPv6 is enclosed in []
+ * ':' before ']' is part of IPv6
+ * ':' after ']' indicates port
+ */
+ p = server;
+ if (server[0] == '[') {
+ server++;
+ p = index(server, ']');
+ if (p == NULL) {
+ /* Malformed IPv6 */
+ continue;
+ }
+ p[0] = '\0';
+ p++;
+ }
+
+ port_index = index(p, ':');
+
+ if (port_index == NULL) {
+ port = 0;
+ } else {
+ port = atoi(port_index + 1);
+ port_index[0] = '\0';
+ }
+ transport = talloc_strdup(frame, "tcp");
+ if (!transport) {
+ errno = ENOMEM;
+ goto out;
+ }
+ host = talloc_strdup(frame, server);
+ if (!host) {
+ errno = ENOMEM;
+ goto out;
+ }
+ }
+
+ DBG_INFO("Calling set volfile server with params "
+ "transport=%s, host=%s, port=%d\n", transport,
+ host, port);
+
+ ret = glfs_set_volfile_server(fs, transport, host, port);
+ if (ret < 0) {
+ DBG_WARNING("Failed to set volfile_server "
+ "transport=%s, host=%s, port=%d (%s)\n",
+ transport, host, port, strerror(errno));
+ } else {
+ server_success++;
+ }
+ }
+
+out:
+ if (server_count == 0) {
+ ret = -1;
+ } else if (server_success < server_count) {
+ DBG_WARNING("Failed to set %zu out of %zu servers parsed\n",
+ server_count - server_success, server_count);
+ ret = 0;
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/* Disk Operations */
+
+static int check_for_write_behind_translator(TALLOC_CTX *mem_ctx,
+ glfs_t *fs,
+ const char *volume)
+{
+ char *buf = NULL;
+ char **lines = NULL;
+ int numlines = 0;
+ int i;
+ char *option;
+ bool write_behind_present = false;
+ size_t newlen;
+ int ret;
+
+ ret = glfs_get_volfile(fs, NULL, 0);
+ if (ret == 0) {
+ DBG_ERR("%s: Failed to get volfile for "
+ "volume (%s): No volfile\n",
+ volume,
+ strerror(errno));
+ return -1;
+ }
+ if (ret > 0) {
+ DBG_ERR("%s: Invalid return %d for glfs_get_volfile for "
+ "volume (%s): No volfile\n",
+ volume,
+ ret,
+ strerror(errno));
+ return -1;
+ }
+
+ newlen = 0 - ret;
+
+ buf = talloc_zero_array(mem_ctx, char, newlen);
+ if (buf == NULL) {
+ return -1;
+ }
+
+ ret = glfs_get_volfile(fs, buf, newlen);
+ if (ret != newlen) {
+ TALLOC_FREE(buf);
+ DBG_ERR("%s: Failed to get volfile for volume (%s)\n",
+ volume, strerror(errno));
+ return -1;
+ }
+
+ option = talloc_asprintf(mem_ctx, "volume %s-write-behind", volume);
+ if (option == NULL) {
+ TALLOC_FREE(buf);
+ return -1;
+ }
+
+ /*
+ * file_lines_parse() plays horrible tricks with
+ * the passed-in talloc pointers and the hierarchy
+ * which makes freeing hard to get right.
+ *
+ * As we know mem_ctx is freed by the caller, after
+ * this point don't free on exit and let the caller
+ * handle it. This violates good Samba coding practice
+ * but we know we're not leaking here.
+ */
+
+ lines = file_lines_parse(buf,
+ newlen,
+ &numlines,
+ mem_ctx);
+ if (lines == NULL || numlines <= 0) {
+ return -1;
+ }
+ /* On success, buf is now a talloc child of lines !! */
+
+ for (i=0; i < numlines; i++) {
+ if (strequal(lines[i], option)) {
+ write_behind_present = true;
+ break;
+ }
+ }
+
+ if (write_behind_present) {
+ DBG_ERR("Write behind translator is enabled for "
+ "volume (%s), refusing to connect! "
+ "Please turn off the write behind translator by calling "
+ "'gluster volume set %s performance.write-behind off' "
+ "on the commandline. "
+ "Check the vfs_glusterfs(8) manpage for "
+ "further details.\n",
+ volume, volume);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int vfs_gluster_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *volfile_servers;
+ const char *volume;
+ char *logfile;
+ int loglevel;
+ glfs_t *fs = NULL;
+ TALLOC_CTX *tmp_ctx;
+ int ret = 0;
+ bool write_behind_pass_through_set = false;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ ret = -1;
+ goto done;
+ }
+ logfile = lp_parm_substituted_string(tmp_ctx,
+ lp_sub,
+ SNUM(handle->conn),
+ "glusterfs",
+ "logfile",
+ NULL);
+
+ loglevel = lp_parm_int(SNUM(handle->conn), "glusterfs", "loglevel", -1);
+
+ volfile_servers = lp_parm_substituted_string(tmp_ctx,
+ lp_sub,
+ SNUM(handle->conn),
+ "glusterfs",
+ "volfile_server",
+ NULL);
+ if (volfile_servers == NULL) {
+ volfile_servers = DEFAULT_VOLFILE_SERVER;
+ }
+
+ volume = lp_parm_const_string(SNUM(handle->conn), "glusterfs", "volume",
+ NULL);
+ if (volume == NULL) {
+ volume = service;
+ }
+
+ fs = glfs_find_preopened(volume, handle->conn->connectpath);
+ if (fs) {
+ goto done;
+ }
+
+ fs = glfs_new(volume);
+ if (fs == NULL) {
+ ret = -1;
+ goto done;
+ }
+
+ ret = vfs_gluster_set_volfile_servers(fs, volfile_servers);
+ if (ret < 0) {
+ DBG_ERR("Failed to set volfile_servers from list %s\n",
+ volfile_servers);
+ goto done;
+ }
+
+ ret = glfs_set_xlator_option(fs, "*-md-cache", "cache-posix-acl",
+ "true");
+ if (ret < 0) {
+ DEBUG(0, ("%s: Failed to set xlator options\n", volume));
+ goto done;
+ }
+
+ ret = glfs_set_xlator_option(fs, "*-md-cache", "cache-selinux",
+ "true");
+ if (ret < 0) {
+ DEBUG(0, ("%s: Failed to set xlator options\n", volume));
+ goto done;
+ }
+
+ ret = glfs_set_xlator_option(fs, "*-snapview-client",
+ "snapdir-entry-path",
+ handle->conn->connectpath);
+ if (ret < 0) {
+ DEBUG(0, ("%s: Failed to set xlator option:"
+ " snapdir-entry-path\n", volume));
+ goto done;
+ }
+
+#ifdef HAVE_GFAPI_VER_7_9
+ ret = glfs_set_xlator_option(fs, "*-write-behind", "pass-through",
+ "true");
+ if (ret < 0) {
+ DBG_ERR("%s: Failed to set xlator option: pass-through\n",
+ volume);
+ goto done;
+ }
+ write_behind_pass_through_set = true;
+#endif
+
+ ret = glfs_set_logging(fs, logfile, loglevel);
+ if (ret < 0) {
+ DEBUG(0, ("%s: Failed to set logfile %s loglevel %d\n",
+ volume, logfile, loglevel));
+ goto done;
+ }
+
+ ret = glfs_init(fs);
+ if (ret < 0) {
+ DEBUG(0, ("%s: Failed to initialize volume (%s)\n",
+ volume, strerror(errno)));
+ goto done;
+ }
+
+ if (!write_behind_pass_through_set) {
+ ret = check_for_write_behind_translator(tmp_ctx, fs, volume);
+ if (ret < 0) {
+ goto done;
+ }
+ }
+
+ ret = glfs_set_preopened(volume, handle->conn->connectpath, fs);
+ if (ret < 0) {
+ DEBUG(0, ("%s: Failed to register volume (%s)\n",
+ volume, strerror(errno)));
+ goto done;
+ }
+
+ /*
+ * The shadow_copy2 module will fail to export subdirectories
+ * of a gluster volume unless we specify the mount point,
+ * because the detection fails if the file system is not
+ * locally mounted:
+ * https://bugzilla.samba.org/show_bug.cgi?id=13091
+ */
+ lp_do_parameter(SNUM(handle->conn), "shadow:mountpoint", "/");
+
+ /*
+ * Unless we have an async implementation of getxattrat turn this off.
+ */
+ lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false");
+
+done:
+ if (ret < 0) {
+ if (fs)
+ glfs_fini(fs);
+ } else {
+ DBG_ERR("%s: Initialized volume from servers %s\n",
+ volume, volfile_servers);
+ handle->data = fs;
+ }
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static void vfs_gluster_disconnect(struct vfs_handle_struct *handle)
+{
+ glfs_t *fs = NULL;
+
+ fs = handle->data;
+
+ glfs_clear_preopened(fs);
+}
+
+static uint64_t vfs_gluster_disk_free(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize_p,
+ uint64_t *dfree_p,
+ uint64_t *dsize_p)
+{
+ struct statvfs statvfs = { 0, };
+ int ret;
+
+ ret = glfs_statvfs(handle->data, smb_fname->base_name, &statvfs);
+ if (ret < 0) {
+ return -1;
+ }
+
+ if (bsize_p != NULL) {
+ *bsize_p = (uint64_t)statvfs.f_bsize; /* Block size */
+ }
+ if (dfree_p != NULL) {
+ *dfree_p = (uint64_t)statvfs.f_bavail; /* Available Block units */
+ }
+ if (dsize_p != NULL) {
+ *dsize_p = (uint64_t)statvfs.f_blocks; /* Total Block units */
+ }
+
+ return (uint64_t)statvfs.f_bavail;
+}
+
+static int vfs_gluster_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static int
+vfs_gluster_set_quota(struct vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype, unid_t id, SMB_DISK_QUOTA *qt)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static int vfs_gluster_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *vfs_statvfs)
+{
+ struct statvfs statvfs = { 0, };
+ int ret;
+
+ ret = glfs_statvfs(handle->data, smb_fname->base_name, &statvfs);
+ if (ret < 0) {
+ DEBUG(0, ("glfs_statvfs(%s) failed: %s\n",
+ smb_fname->base_name, strerror(errno)));
+ return -1;
+ }
+
+ ZERO_STRUCTP(vfs_statvfs);
+
+ vfs_statvfs->OptimalTransferSize = statvfs.f_frsize;
+ vfs_statvfs->BlockSize = statvfs.f_bsize;
+ vfs_statvfs->TotalBlocks = statvfs.f_blocks;
+ vfs_statvfs->BlocksAvail = statvfs.f_bfree;
+ vfs_statvfs->UserBlocksAvail = statvfs.f_bavail;
+ vfs_statvfs->TotalFileNodes = statvfs.f_files;
+ vfs_statvfs->FreeFileNodes = statvfs.f_ffree;
+ vfs_statvfs->FsIdentifier = statvfs.f_fsid;
+ vfs_statvfs->FsCapabilities =
+ FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+
+ return ret;
+}
+
+static uint32_t vfs_gluster_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ uint32_t caps = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+
+#ifdef HAVE_GFAPI_VER_6
+ caps |= FILE_SUPPORTS_SPARSE_FILES;
+#endif
+
+#ifdef STAT_HAVE_NSEC
+ *p_ts_res = TIMESTAMP_SET_NT_OR_BETTER;
+#endif
+
+ return caps;
+}
+
+static glfs_fd_t *vfs_gluster_fetch_glfd(struct vfs_handle_struct *handle,
+ const files_struct *fsp)
+{
+ glfs_fd_t **glfd = (glfs_fd_t **)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+ if (glfd == NULL) {
+ DBG_INFO("Failed to fetch fsp extension\n");
+ return NULL;
+ }
+ if (*glfd == NULL) {
+ DBG_INFO("Empty glfs_fd_t pointer\n");
+ return NULL;
+ }
+
+ return *glfd;
+}
+
+static DIR *vfs_gluster_fdopendir(struct vfs_handle_struct *handle,
+ files_struct *fsp, const char *mask,
+ uint32_t attributes)
+{
+ glfs_fd_t *glfd = NULL;
+
+ glfd = glfs_opendir(handle->data, fsp->fsp_name->base_name);
+ if (glfd == NULL) {
+ return NULL;
+ }
+
+ return (DIR *)glfd;
+}
+
+static int vfs_gluster_closedir(struct vfs_handle_struct *handle, DIR *dirp)
+{
+ int ret;
+
+ START_PROFILE(syscall_closedir);
+ ret = glfs_closedir((void *)dirp);
+ END_PROFILE(syscall_closedir);
+
+ return ret;
+}
+
+static struct dirent *vfs_gluster_readdir(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ static char direntbuf[512];
+ int ret;
+ struct dirent *dirent = 0;
+
+ START_PROFILE(syscall_readdir);
+
+ ret = glfs_readdir_r((void *)dirp, (void *)direntbuf, &dirent);
+
+ if ((ret < 0) || (dirent == NULL)) {
+ END_PROFILE(syscall_readdir);
+ return NULL;
+ }
+
+ END_PROFILE(syscall_readdir);
+ return dirent;
+}
+
+static void vfs_gluster_rewinddir(struct vfs_handle_struct *handle, DIR *dirp)
+{
+ START_PROFILE(syscall_rewinddir);
+ glfs_seekdir((void *)dirp, 0);
+ END_PROFILE(syscall_rewinddir);
+}
+
+static int vfs_gluster_mkdirat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+
+ START_PROFILE(syscall_mkdirat);
+
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ END_PROFILE(syscall_mkdirat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_mkdirat(pglfd, smb_fname->base_name, mode);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ START_PROFILE(syscall_mkdirat);
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ END_PROFILE(syscall_mkdirat);
+ return -1;
+ }
+
+ ret = glfs_mkdir(handle->data, full_fname->base_name, mode);
+
+ TALLOC_FREE(full_fname);
+#endif
+
+ END_PROFILE(syscall_mkdirat);
+
+ return ret;
+}
+
+static int vfs_gluster_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int flags = how->flags;
+ struct smb_filename *full_fname = NULL;
+ bool have_opath = false;
+ bool became_root = false;
+ glfs_fd_t *glfd = NULL;
+ glfs_fd_t *pglfd = NULL;
+ glfs_fd_t **p_tmp;
+
+ START_PROFILE(syscall_openat);
+
+ if (how->resolve != 0) {
+ END_PROFILE(syscall_openat);
+ errno = ENOSYS;
+ return -1;
+ }
+
+ p_tmp = VFS_ADD_FSP_EXTENSION(handle, fsp, glfs_fd_t *, NULL);
+ if (p_tmp == NULL) {
+ END_PROFILE(syscall_openat);
+ errno = ENOMEM;
+ return -1;
+ }
+
+#ifdef O_PATH
+ have_opath = true;
+ if (fsp->fsp_flags.is_pathref) {
+ flags |= O_PATH;
+ }
+#endif
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ END_PROFILE(syscall_openat);
+ return -1;
+ }
+
+ if (fsp->fsp_flags.is_pathref && !have_opath) {
+ become_root();
+ became_root = true;
+ }
+
+ if (fsp_get_pathref_fd(dirfsp) != AT_FDCWD) {
+#ifdef HAVE_GFAPI_VER_7_11
+ /*
+ * Fetch Gluster fd for parent directory using dirfsp
+ * before calling glfs_openat();
+ */
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ END_PROFILE(syscall_openat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ glfd = glfs_openat(pglfd,
+ smb_fname->base_name,
+ flags,
+ how->mode);
+#else
+ /*
+ * Replace smb_fname with full_path constructed above.
+ */
+ smb_fname = full_fname;
+#endif
+ }
+
+ if (pglfd == NULL) {
+ /*
+ * smb_fname can either be a full_path or the same one
+ * as received from the caller. In the latter case we
+ * are operating at current working directory.
+ */
+ if (flags & O_CREAT) {
+ glfd = glfs_creat(handle->data,
+ smb_fname->base_name,
+ flags,
+ how->mode);
+ } else {
+ glfd = glfs_open(handle->data,
+ smb_fname->base_name,
+ flags);
+ }
+ }
+
+ if (became_root) {
+ unbecome_root();
+ }
+
+ TALLOC_FREE(full_fname);
+
+ fsp->fsp_flags.have_proc_fds = false;
+
+ if (glfd == NULL) {
+ END_PROFILE(syscall_openat);
+ /* no extension destroy_fn, so no need to save errno */
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ return -1;
+ }
+
+ *p_tmp = glfd;
+
+ END_PROFILE(syscall_openat);
+ /* An arbitrary value for error reporting, so you know its us. */
+ return 13371337;
+}
+
+static int vfs_gluster_close(struct vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ int ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_close);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_close);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+
+ ret = glfs_close(glfd);
+ END_PROFILE(syscall_close);
+
+ return ret;
+}
+
+static ssize_t vfs_gluster_pread(struct vfs_handle_struct *handle,
+ files_struct *fsp, void *data, size_t n,
+ off_t offset)
+{
+ ssize_t ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE_BYTES(syscall_pread, n);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE_BYTES(syscall_pread);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+#ifdef HAVE_GFAPI_VER_7_6
+ ret = glfs_pread(glfd, data, n, offset, 0, NULL);
+#else
+ ret = glfs_pread(glfd, data, n, offset, 0);
+#endif
+ END_PROFILE_BYTES(syscall_pread);
+
+ return ret;
+}
+
+struct vfs_gluster_pread_state {
+ ssize_t ret;
+ glfs_fd_t *fd;
+ void *buf;
+ size_t count;
+ off_t offset;
+
+ struct vfs_aio_state vfs_aio_state;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+};
+
+static void vfs_gluster_pread_do(void *private_data);
+static void vfs_gluster_pread_done(struct tevent_req *subreq);
+static int vfs_gluster_pread_state_destructor(struct vfs_gluster_pread_state *state);
+
+static struct tevent_req *vfs_gluster_pread_send(struct vfs_handle_struct
+ *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *fsp,
+ void *data, size_t n,
+ off_t offset)
+{
+ struct vfs_gluster_pread_state *state;
+ struct tevent_req *req, *subreq;
+
+ glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct vfs_gluster_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ret = -1;
+ state->fd = glfd;
+ state->buf = data;
+ state->count = n;
+ state->offset = offset;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pread, profile_p,
+ state->profile_bytes, n);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+
+ subreq = pthreadpool_tevent_job_send(
+ state, ev, handle->conn->sconn->pool,
+ vfs_gluster_pread_do, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_gluster_pread_done, req);
+
+ talloc_set_destructor(state, vfs_gluster_pread_state_destructor);
+
+ return req;
+}
+
+static void vfs_gluster_pread_do(void *private_data)
+{
+ struct vfs_gluster_pread_state *state = talloc_get_type_abort(
+ private_data, struct vfs_gluster_pread_state);
+ struct timespec start_time;
+ struct timespec end_time;
+
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes);
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ do {
+#ifdef HAVE_GFAPI_VER_7_6
+ state->ret = glfs_pread(state->fd, state->buf, state->count,
+ state->offset, 0, NULL);
+#else
+ state->ret = glfs_pread(state->fd, state->buf, state->count,
+ state->offset, 0);
+#endif
+ } while ((state->ret == -1) && (errno == EINTR));
+
+ if (state->ret == -1) {
+ state->vfs_aio_state.error = errno;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time);
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+}
+
+static int vfs_gluster_pread_state_destructor(struct vfs_gluster_pread_state *state)
+{
+ return -1;
+}
+
+static void vfs_gluster_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_gluster_pread_state *state = tevent_req_data(
+ req, struct vfs_gluster_pread_state);
+ int ret;
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+ SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes);
+ talloc_set_destructor(state, NULL);
+ if (ret != 0) {
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ vfs_gluster_pread_do(state);
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t vfs_gluster_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_gluster_pread_state *state = tevent_req_data(
+ req, struct vfs_gluster_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct vfs_gluster_pwrite_state {
+ ssize_t ret;
+ glfs_fd_t *fd;
+ const void *buf;
+ size_t count;
+ off_t offset;
+
+ struct vfs_aio_state vfs_aio_state;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+};
+
+static void vfs_gluster_pwrite_do(void *private_data);
+static void vfs_gluster_pwrite_done(struct tevent_req *subreq);
+static int vfs_gluster_pwrite_state_destructor(struct vfs_gluster_pwrite_state *state);
+
+static struct tevent_req *vfs_gluster_pwrite_send(struct vfs_handle_struct
+ *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *fsp,
+ const void *data, size_t n,
+ off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct vfs_gluster_pwrite_state *state;
+
+ glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct vfs_gluster_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ret = -1;
+ state->fd = glfd;
+ state->buf = data;
+ state->count = n;
+ state->offset = offset;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pwrite, profile_p,
+ state->profile_bytes, n);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+
+ subreq = pthreadpool_tevent_job_send(
+ state, ev, handle->conn->sconn->pool,
+ vfs_gluster_pwrite_do, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_gluster_pwrite_done, req);
+
+ talloc_set_destructor(state, vfs_gluster_pwrite_state_destructor);
+
+ return req;
+}
+
+static void vfs_gluster_pwrite_do(void *private_data)
+{
+ struct vfs_gluster_pwrite_state *state = talloc_get_type_abort(
+ private_data, struct vfs_gluster_pwrite_state);
+ struct timespec start_time;
+ struct timespec end_time;
+
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes);
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ do {
+#ifdef HAVE_GFAPI_VER_7_6
+ state->ret = glfs_pwrite(state->fd, state->buf, state->count,
+ state->offset, 0, NULL, NULL);
+#else
+ state->ret = glfs_pwrite(state->fd, state->buf, state->count,
+ state->offset, 0);
+#endif
+ } while ((state->ret == -1) && (errno == EINTR));
+
+ if (state->ret == -1) {
+ state->vfs_aio_state.error = errno;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time);
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+}
+
+static int vfs_gluster_pwrite_state_destructor(struct vfs_gluster_pwrite_state *state)
+{
+ return -1;
+}
+
+static void vfs_gluster_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_gluster_pwrite_state *state = tevent_req_data(
+ req, struct vfs_gluster_pwrite_state);
+ int ret;
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+ SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes);
+ talloc_set_destructor(state, NULL);
+ if (ret != 0) {
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ vfs_gluster_pwrite_do(state);
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t vfs_gluster_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_gluster_pwrite_state *state = tevent_req_data(
+ req, struct vfs_gluster_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+
+ return state->ret;
+}
+
+static ssize_t vfs_gluster_pwrite(struct vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ ssize_t ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE_BYTES(syscall_pwrite, n);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE_BYTES(syscall_pwrite);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+#ifdef HAVE_GFAPI_VER_7_6
+ ret = glfs_pwrite(glfd, data, n, offset, 0, NULL, NULL);
+#else
+ ret = glfs_pwrite(glfd, data, n, offset, 0);
+#endif
+ END_PROFILE_BYTES(syscall_pwrite);
+
+ return ret;
+}
+
+static off_t vfs_gluster_lseek(struct vfs_handle_struct *handle,
+ files_struct *fsp, off_t offset, int whence)
+{
+ off_t ret = 0;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_lseek);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_lseek);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_lseek(glfd, offset, whence);
+ END_PROFILE(syscall_lseek);
+
+ return ret;
+}
+
+static ssize_t vfs_gluster_sendfile(struct vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp,
+ const DATA_BLOB *hdr,
+ off_t offset, size_t n)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static ssize_t vfs_gluster_recvfile(struct vfs_handle_struct *handle,
+ int fromfd, files_struct *tofsp,
+ off_t offset, size_t n)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int vfs_gluster_renameat(struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *src_pglfd = NULL;
+ glfs_fd_t *dst_pglfd = NULL;
+
+ START_PROFILE(syscall_renameat);
+
+ src_pglfd = vfs_gluster_fetch_glfd(handle, srcfsp);
+ if (src_pglfd == NULL) {
+ END_PROFILE(syscall_renameat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ dst_pglfd = vfs_gluster_fetch_glfd(handle, dstfsp);
+ if (dst_pglfd == NULL) {
+ END_PROFILE(syscall_renameat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_renameat(src_pglfd, smb_fname_src->base_name,
+ dst_pglfd, smb_fname_dst->base_name);
+#else
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+
+ START_PROFILE(syscall_renameat);
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ END_PROFILE(syscall_renameat);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ END_PROFILE(syscall_renameat);
+ TALLOC_FREE(full_fname_src);
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = glfs_rename(handle->data,
+ full_fname_src->base_name,
+ full_fname_dst->base_name);
+
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+#endif
+
+ END_PROFILE(syscall_renameat);
+
+ return ret;
+}
+
+struct vfs_gluster_fsync_state {
+ ssize_t ret;
+ glfs_fd_t *fd;
+
+ struct vfs_aio_state vfs_aio_state;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+};
+
+static void vfs_gluster_fsync_do(void *private_data);
+static void vfs_gluster_fsync_done(struct tevent_req *subreq);
+static int vfs_gluster_fsync_state_destructor(struct vfs_gluster_fsync_state *state);
+
+static struct tevent_req *vfs_gluster_fsync_send(struct vfs_handle_struct
+ *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *fsp)
+{
+ struct tevent_req *req, *subreq;
+ struct vfs_gluster_fsync_state *state;
+
+ glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state, struct vfs_gluster_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ret = -1;
+ state->fd = glfd;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_fsync, profile_p,
+ state->profile_bytes, 0);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+
+ subreq = pthreadpool_tevent_job_send(
+ state, ev, handle->conn->sconn->pool, vfs_gluster_fsync_do, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_gluster_fsync_done, req);
+
+ talloc_set_destructor(state, vfs_gluster_fsync_state_destructor);
+
+ return req;
+}
+
+static void vfs_gluster_fsync_do(void *private_data)
+{
+ struct vfs_gluster_fsync_state *state = talloc_get_type_abort(
+ private_data, struct vfs_gluster_fsync_state);
+ struct timespec start_time;
+ struct timespec end_time;
+
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(state->profile_bytes);
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ do {
+#ifdef HAVE_GFAPI_VER_7_6
+ state->ret = glfs_fsync(state->fd, NULL, NULL);
+#else
+ state->ret = glfs_fsync(state->fd);
+#endif
+ } while ((state->ret == -1) && (errno == EINTR));
+
+ if (state->ret == -1) {
+ state->vfs_aio_state.error = errno;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ state->vfs_aio_state.duration = nsec_time_diff(&end_time, &start_time);
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->profile_bytes);
+}
+
+static int vfs_gluster_fsync_state_destructor(struct vfs_gluster_fsync_state *state)
+{
+ return -1;
+}
+
+static void vfs_gluster_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_gluster_fsync_state *state = tevent_req_data(
+ req, struct vfs_gluster_fsync_state);
+ int ret;
+
+ ret = pthreadpool_tevent_job_recv(subreq);
+ TALLOC_FREE(subreq);
+ SMBPROFILE_BYTES_ASYNC_END(state->profile_bytes);
+ talloc_set_destructor(state, NULL);
+ if (ret != 0) {
+ if (ret != EAGAIN) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * If we get EAGAIN from pthreadpool_tevent_job_recv() this
+ * means the lower level pthreadpool failed to create a new
+ * thread. Fallback to sync processing in that case to allow
+ * some progress for the client.
+ */
+ vfs_gluster_fsync_do(state);
+ }
+
+ tevent_req_done(req);
+}
+
+static int vfs_gluster_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_gluster_fsync_state *state = tevent_req_data(
+ req, struct vfs_gluster_fsync_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static int vfs_gluster_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ struct stat st;
+ int ret;
+
+ START_PROFILE(syscall_stat);
+ ret = glfs_stat(handle->data, smb_fname->base_name, &st);
+ if (ret == 0) {
+ smb_stat_ex_from_stat(&smb_fname->st, &st);
+ }
+ if (ret < 0 && errno != ENOENT) {
+ DEBUG(0, ("glfs_stat(%s) failed: %s\n",
+ smb_fname->base_name, strerror(errno)));
+ }
+ END_PROFILE(syscall_stat);
+
+ return ret;
+}
+
+static int vfs_gluster_fstat(struct vfs_handle_struct *handle,
+ files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ struct stat st;
+ int ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_fstat);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_fstat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_fstat(glfd, &st);
+ if (ret == 0) {
+ smb_stat_ex_from_stat(sbuf, &st);
+ }
+ if (ret < 0) {
+ DEBUG(0, ("glfs_fstat(%d) failed: %s\n",
+ fsp_get_io_fd(fsp), strerror(errno)));
+ }
+ END_PROFILE(syscall_fstat);
+
+ return ret;
+}
+
+static int vfs_gluster_fstatat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ struct stat st;
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+
+ START_PROFILE(syscall_fstatat);
+
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ END_PROFILE(syscall_fstatat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_fstatat(pglfd, smb_fname->base_name, &st, flags);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ START_PROFILE(syscall_fstatat);
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ END_PROFILE(syscall_fstatat);
+ return -1;
+ }
+
+ ret = glfs_stat(handle->data, full_fname->base_name, &st);
+
+ TALLOC_FREE(full_fname->base_name);
+#endif
+
+ if (ret == 0) {
+ smb_stat_ex_from_stat(sbuf, &st);
+ }
+
+ END_PROFILE(syscall_fstatat);
+
+ return ret;
+}
+
+static int vfs_gluster_lstat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ struct stat st;
+ int ret;
+
+ START_PROFILE(syscall_lstat);
+ ret = glfs_lstat(handle->data, smb_fname->base_name, &st);
+ if (ret == 0) {
+ smb_stat_ex_from_stat(&smb_fname->st, &st);
+ }
+ if (ret < 0 && errno != ENOENT) {
+ DEBUG(0, ("glfs_lstat(%s) failed: %s\n",
+ smb_fname->base_name, strerror(errno)));
+ }
+ END_PROFILE(syscall_lstat);
+
+ return ret;
+}
+
+static uint64_t vfs_gluster_get_alloc_size(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ uint64_t ret;
+
+ START_PROFILE(syscall_get_alloc_size);
+ ret = sbuf->st_ex_blocks * 512;
+ END_PROFILE(syscall_get_alloc_size);
+
+ return ret;
+}
+
+static int vfs_gluster_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+
+ START_PROFILE(syscall_unlinkat);
+
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ END_PROFILE(syscall_unlinkat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_unlinkat(pglfd, smb_fname->base_name, flags);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ START_PROFILE(syscall_unlinkat);
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ END_PROFILE(syscall_unlinkat);
+ return -1;
+ }
+
+ if (flags & AT_REMOVEDIR) {
+ ret = glfs_rmdir(handle->data, full_fname->base_name);
+ } else {
+ ret = glfs_unlink(handle->data, full_fname->base_name);
+ }
+
+ TALLOC_FREE(full_fname);
+#endif
+
+ END_PROFILE(syscall_unlinkat);
+
+ return ret;
+}
+
+static int vfs_gluster_fchmod(struct vfs_handle_struct *handle,
+ files_struct *fsp, mode_t mode)
+{
+ int ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_fchmod);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_fchmod);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to remove xattrs.
+ */
+ ret = glfs_fchmod(glfd, mode);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ ret = glfs_chmod(handle->data, fsp->fsp_name->base_name, mode);
+ }
+ END_PROFILE(syscall_fchmod);
+
+ return ret;
+}
+
+static int vfs_gluster_fchown(struct vfs_handle_struct *handle,
+ files_struct *fsp, uid_t uid, gid_t gid)
+{
+ int ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_fchown);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_fchown);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_fchown(glfd, uid, gid);
+ END_PROFILE(syscall_fchown);
+
+ return ret;
+}
+
+static int vfs_gluster_lchown(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int ret;
+
+ START_PROFILE(syscall_lchown);
+ ret = glfs_lchown(handle->data, smb_fname->base_name, uid, gid);
+ END_PROFILE(syscall_lchown);
+
+ return ret;
+}
+
+static int vfs_gluster_chdir(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int ret;
+
+ START_PROFILE(syscall_chdir);
+ ret = glfs_chdir(handle->data, smb_fname->base_name);
+ END_PROFILE(syscall_chdir);
+
+ return ret;
+}
+
+static struct smb_filename *vfs_gluster_getwd(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ char cwd[PATH_MAX] = { '\0' };
+ char *ret;
+ struct smb_filename *smb_fname = NULL;
+
+ START_PROFILE(syscall_getwd);
+
+ ret = glfs_getcwd(handle->data, cwd, PATH_MAX - 1);
+ END_PROFILE(syscall_getwd);
+
+ if (ret == NULL) {
+ return NULL;
+ }
+ smb_fname = synthetic_smb_fname(ctx,
+ ret,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ return smb_fname;
+}
+
+static int vfs_gluster_fntimes(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ int ret = -1;
+ struct timespec times[2];
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_fntimes);
+
+ if (is_omit_timespec(&ft->atime)) {
+ times[0].tv_sec = fsp->fsp_name->st.st_ex_atime.tv_sec;
+ times[0].tv_nsec = fsp->fsp_name->st.st_ex_atime.tv_nsec;
+ } else {
+ times[0].tv_sec = ft->atime.tv_sec;
+ times[0].tv_nsec = ft->atime.tv_nsec;
+ }
+
+ if (is_omit_timespec(&ft->mtime)) {
+ times[1].tv_sec = fsp->fsp_name->st.st_ex_mtime.tv_sec;
+ times[1].tv_nsec = fsp->fsp_name->st.st_ex_mtime.tv_nsec;
+ } else {
+ times[1].tv_sec = ft->mtime.tv_sec;
+ times[1].tv_nsec = ft->mtime.tv_nsec;
+ }
+
+ if ((timespec_compare(&times[0],
+ &fsp->fsp_name->st.st_ex_atime) == 0) &&
+ (timespec_compare(&times[1],
+ &fsp->fsp_name->st.st_ex_mtime) == 0)) {
+ END_PROFILE(syscall_fntimes);
+ return 0;
+ }
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_fntimes);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ ret = glfs_futimens(glfd, times);
+ } else {
+ ret = glfs_utimens(handle->data,
+ fsp->fsp_name->base_name,
+ times);
+ }
+ END_PROFILE(syscall_fntimes);
+
+ return ret;
+}
+
+static int vfs_gluster_ftruncate(struct vfs_handle_struct *handle,
+ files_struct *fsp, off_t offset)
+{
+ int ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_ftruncate);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_ftruncate);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+#ifdef HAVE_GFAPI_VER_7_6
+ ret = glfs_ftruncate(glfd, offset, NULL, NULL);
+#else
+ ret = glfs_ftruncate(glfd, offset);
+#endif
+ END_PROFILE(syscall_ftruncate);
+
+ return ret;
+}
+
+static int vfs_gluster_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset, off_t len)
+{
+ int ret;
+#ifdef HAVE_GFAPI_VER_6
+ glfs_fd_t *glfd = NULL;
+ int keep_size, punch_hole;
+
+ START_PROFILE(syscall_fallocate);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_fallocate);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ keep_size = mode & VFS_FALLOCATE_FL_KEEP_SIZE;
+ punch_hole = mode & VFS_FALLOCATE_FL_PUNCH_HOLE;
+
+ mode &= ~(VFS_FALLOCATE_FL_KEEP_SIZE|VFS_FALLOCATE_FL_PUNCH_HOLE);
+ if (mode != 0) {
+ END_PROFILE(syscall_fallocate);
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ if (punch_hole) {
+ ret = glfs_discard(glfd, offset, len);
+ if (ret != 0) {
+ DBG_DEBUG("glfs_discard failed: %s\n",
+ strerror(errno));
+ }
+ }
+
+ ret = glfs_fallocate(glfd, keep_size, offset, len);
+ END_PROFILE(syscall_fallocate);
+#else
+ errno = ENOTSUP;
+ ret = -1;
+#endif
+ return ret;
+}
+
+static struct smb_filename *vfs_gluster_realpath(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ char *result = NULL;
+ struct smb_filename *result_fname = NULL;
+ char *resolved_path = NULL;
+
+ START_PROFILE(syscall_realpath);
+
+ resolved_path = SMB_MALLOC_ARRAY(char, PATH_MAX+1);
+ if (resolved_path == NULL) {
+ END_PROFILE(syscall_realpath);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ result = glfs_realpath(handle->data,
+ smb_fname->base_name,
+ resolved_path);
+ if (result != NULL) {
+ result_fname = synthetic_smb_fname(ctx,
+ result,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ }
+
+ SAFE_FREE(resolved_path);
+ END_PROFILE(syscall_realpath);
+
+ return result_fname;
+}
+
+static bool vfs_gluster_lock(struct vfs_handle_struct *handle,
+ files_struct *fsp, int op, off_t offset,
+ off_t count, int type)
+{
+ struct flock flock = { 0, };
+ int ret;
+ glfs_fd_t *glfd = NULL;
+ bool ok = false;
+
+ START_PROFILE(syscall_fcntl_lock);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ ok = false;
+ goto out;
+ }
+
+ flock.l_type = type;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = offset;
+ flock.l_len = count;
+ flock.l_pid = 0;
+
+ ret = glfs_posix_lock(glfd, op, &flock);
+
+ if (op == F_GETLK) {
+ /* lock query, true if someone else has locked */
+ if ((ret != -1) &&
+ (flock.l_type != F_UNLCK) &&
+ (flock.l_pid != 0) && (flock.l_pid != getpid())) {
+ ok = true;
+ goto out;
+ }
+ /* not me */
+ ok = false;
+ goto out;
+ }
+
+ if (ret == -1) {
+ ok = false;
+ goto out;
+ }
+
+ ok = true;
+out:
+ END_PROFILE(syscall_fcntl_lock);
+
+ return ok;
+}
+
+static int vfs_gluster_filesystem_sharemode(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static int vfs_gluster_fcntl(vfs_handle_struct *handle,
+ files_struct *fsp, int cmd, va_list cmd_arg)
+{
+ /*
+ * SMB_VFS_FCNTL() is currently only called by vfs_set_blocking() to
+ * clear O_NONBLOCK, etc for LOCK_MAND and FIFOs. Ignore it.
+ */
+ if (cmd == F_GETFL) {
+ return 0;
+ } else if (cmd == F_SETFL) {
+ va_list dup_cmd_arg;
+ int opt;
+
+ va_copy(dup_cmd_arg, cmd_arg);
+ opt = va_arg(dup_cmd_arg, int);
+ va_end(dup_cmd_arg);
+ if (opt == 0) {
+ return 0;
+ }
+ DBG_ERR("unexpected fcntl SETFL(%d)\n", opt);
+ goto err_out;
+ }
+ DBG_ERR("unexpected fcntl: %d\n", cmd);
+err_out:
+ errno = EINVAL;
+ return -1;
+}
+
+static int vfs_gluster_linux_setlease(struct vfs_handle_struct *handle,
+ files_struct *fsp, int leasetype)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static bool vfs_gluster_getlock(struct vfs_handle_struct *handle,
+ files_struct *fsp, off_t *poffset,
+ off_t *pcount, int *ptype, pid_t *ppid)
+{
+ struct flock flock = { 0, };
+ int ret;
+ glfs_fd_t *glfd = NULL;
+
+ START_PROFILE(syscall_fcntl_getlock);
+
+ glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ END_PROFILE(syscall_fcntl_getlock);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return false;
+ }
+
+ flock.l_type = *ptype;
+ flock.l_whence = SEEK_SET;
+ flock.l_start = *poffset;
+ flock.l_len = *pcount;
+ flock.l_pid = 0;
+
+ ret = glfs_posix_lock(glfd, F_GETLK, &flock);
+
+ if (ret == -1) {
+ END_PROFILE(syscall_fcntl_getlock);
+ return false;
+ }
+
+ *ptype = flock.l_type;
+ *poffset = flock.l_start;
+ *pcount = flock.l_len;
+ *ppid = flock.l_pid;
+ END_PROFILE(syscall_fcntl_getlock);
+
+ return true;
+}
+
+static int vfs_gluster_symlinkat(struct vfs_handle_struct *handle,
+ const struct smb_filename *link_target,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+
+ START_PROFILE(syscall_symlinkat);
+
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ END_PROFILE(syscall_symlinkat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_symlinkat(link_target->base_name,
+ pglfd,
+ new_smb_fname->base_name);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ START_PROFILE(syscall_symlinkat);
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ new_smb_fname);
+ if (full_fname == NULL) {
+ END_PROFILE(syscall_symlinkat);
+ return -1;
+ }
+
+ ret = glfs_symlink(handle->data,
+ link_target->base_name,
+ full_fname->base_name);
+
+ TALLOC_FREE(full_fname);
+#endif
+
+ END_PROFILE(syscall_symlinkat);
+
+ return ret;
+}
+
+static int vfs_gluster_readlinkat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+
+ START_PROFILE(syscall_readlinkat);
+
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ END_PROFILE(syscall_readlinkat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_readlinkat(pglfd, smb_fname->base_name, buf, bufsiz);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ START_PROFILE(syscall_readlinkat);
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ END_PROFILE(syscall_readlinkat);
+ return -1;
+ }
+
+ ret = glfs_readlink(handle->data, full_fname->base_name, buf, bufsiz);
+
+ TALLOC_FREE(full_fname);
+#endif
+
+ END_PROFILE(syscall_readlinkat);
+
+ return ret;
+}
+
+static int vfs_gluster_linkat(struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *src_pglfd = NULL;
+ glfs_fd_t *dst_pglfd = NULL;
+
+ START_PROFILE(syscall_linkat);
+
+ src_pglfd = vfs_gluster_fetch_glfd(handle, srcfsp);
+ if (src_pglfd == NULL) {
+ END_PROFILE(syscall_linkat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ dst_pglfd = vfs_gluster_fetch_glfd(handle, dstfsp);
+ if (dst_pglfd == NULL) {
+ END_PROFILE(syscall_linkat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_linkat(src_pglfd,
+ old_smb_fname->base_name,
+ dst_pglfd,
+ new_smb_fname->base_name,
+ flags);
+#else
+ struct smb_filename *full_fname_old = NULL;
+ struct smb_filename *full_fname_new = NULL;
+
+ START_PROFILE(syscall_linkat);
+
+ full_fname_old = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ old_smb_fname);
+ if (full_fname_old == NULL) {
+ END_PROFILE(syscall_linkat);
+ return -1;
+ }
+
+ full_fname_new = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (full_fname_new == NULL) {
+ END_PROFILE(syscall_linkat);
+ TALLOC_FREE(full_fname_old);
+ return -1;
+ }
+
+ ret = glfs_link(handle->data,
+ full_fname_old->base_name,
+ full_fname_new->base_name);
+
+ TALLOC_FREE(full_fname_old);
+ TALLOC_FREE(full_fname_new);
+#endif
+
+ END_PROFILE(syscall_linkat);
+
+ return ret;
+}
+
+static int vfs_gluster_mknodat(struct vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ int ret;
+
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+
+ START_PROFILE(syscall_mknodat);
+
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ END_PROFILE(syscall_mknodat);
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ ret = glfs_mknodat(pglfd, smb_fname->base_name, mode, dev);
+#else
+ struct smb_filename *full_fname = NULL;
+
+ START_PROFILE(syscall_mknodat);
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ END_PROFILE(syscall_mknodat);
+ return -1;
+ }
+
+ ret = glfs_mknod(handle->data, full_fname->base_name, mode, dev);
+
+ TALLOC_FREE(full_fname);
+#endif
+
+ END_PROFILE(syscall_mknodat);
+
+ return ret;
+}
+
+static int vfs_gluster_fchflags(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+static NTSTATUS vfs_gluster_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ int ret;
+ char key_buf[GLUSTER_NAME_MAX + 64];
+ char val_buf[GLUSTER_NAME_MAX + 1];
+
+ if (strlen(name) >= GLUSTER_NAME_MAX) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ snprintf(key_buf, GLUSTER_NAME_MAX + 64,
+ "glusterfs.get_real_filename:%s", name);
+
+ ret = glfs_getxattr(handle->data,
+ dirfsp->fsp_name->base_name,
+ key_buf,
+ val_buf,
+ GLUSTER_NAME_MAX + 1);
+ if (ret == -1) {
+ if (errno == ENOATTR) {
+ errno = ENOENT;
+ }
+ return map_nt_error_from_unix(errno);
+ }
+
+ *found_name = talloc_strdup(mem_ctx, val_buf);
+ if (found_name[0] == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static const char *vfs_gluster_connectpath(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ return handle->conn->connectpath;
+}
+
+/* EA Operations */
+
+static ssize_t vfs_gluster_fgetxattr(struct vfs_handle_struct *handle,
+ files_struct *fsp, const char *name,
+ void *value, size_t size)
+{
+ glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to retrieve xattr value.
+ */
+ return glfs_fgetxattr(glfd, name, value, size);
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ return glfs_getxattr(handle->data,
+ fsp->fsp_name->base_name,
+ name,
+ value,
+ size);
+}
+
+static ssize_t vfs_gluster_flistxattr(struct vfs_handle_struct *handle,
+ files_struct *fsp, char *list,
+ size_t size)
+{
+ glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to list xattrs.
+ */
+ return glfs_flistxattr(glfd, list, size);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ return glfs_listxattr(handle->data,
+ fsp->fsp_name->base_name,
+ list,
+ size);
+ }
+}
+
+static int vfs_gluster_fremovexattr(struct vfs_handle_struct *handle,
+ files_struct *fsp, const char *name)
+{
+ glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to remove xattrs.
+ */
+ return glfs_fremovexattr(glfd, name);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ return glfs_removexattr(handle->data,
+ fsp->fsp_name->base_name,
+ name);
+ }
+}
+
+static int vfs_gluster_fsetxattr(struct vfs_handle_struct *handle,
+ files_struct *fsp, const char *name,
+ const void *value, size_t size, int flags)
+{
+ glfs_fd_t *glfd = vfs_gluster_fetch_glfd(handle, fsp);
+ if (glfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return -1;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * We can use an io_fd to set xattrs.
+ */
+ return glfs_fsetxattr(glfd, name, value, size, flags);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ return glfs_setxattr(handle->data,
+ fsp->fsp_name->base_name,
+ name,
+ value,
+ size,
+ flags);
+ }
+}
+
+/* AIO Operations */
+
+static bool vfs_gluster_aio_force(struct vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return false;
+}
+
+static NTSTATUS vfs_gluster_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ int ret;
+ char *msdfs_link = NULL;
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+#else
+ struct smb_filename *full_fname = NULL;
+#endif
+
+ /* Form the msdfs_link contents */
+ msdfs_link = msdfs_link_string(frame,
+ reflist,
+ referral_count);
+ if (msdfs_link == NULL) {
+ goto out;
+ }
+
+#ifdef HAVE_GFAPI_VER_7_11
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto out;
+ }
+
+ ret = glfs_symlinkat(msdfs_link, pglfd, smb_fname->base_name);
+#else
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ goto out;
+ }
+
+ ret = glfs_symlink(handle->data, msdfs_link, full_fname->base_name);
+
+ TALLOC_FREE(full_fname);
+#endif
+ if (ret == 0) {
+ status = NT_STATUS_OK;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ out:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * Read and return the contents of a DFS redirect given a
+ * pathname. A caller can pass in NULL for ppreflist and
+ * preferral_count but still determine if this was a
+ * DFS redirect point by getting NT_STATUS_OK back
+ * without incurring the overhead of reading and parsing
+ * the referral contents.
+ */
+
+static NTSTATUS vfs_gluster_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ size_t bufsize;
+ char *link_target = NULL;
+ int referral_len;
+ bool ok;
+#if defined(HAVE_BROKEN_READLINK)
+ char link_target_buf[PATH_MAX];
+#else
+ char link_target_buf[7];
+#endif
+ struct stat st;
+ struct smb_filename *full_fname = NULL;
+ int ret;
+#ifdef HAVE_GFAPI_VER_7_11
+ glfs_fd_t *pglfd = NULL;
+#endif
+
+ if (is_named_stream(smb_fname)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto err;
+ }
+
+ if (ppreflist == NULL && preferral_count == NULL) {
+ /*
+ * We're only checking if this is a DFS
+ * redirect. We don't need to return data.
+ */
+ bufsize = sizeof(link_target_buf);
+ link_target = link_target_buf;
+ } else {
+ bufsize = PATH_MAX;
+ link_target = talloc_array(mem_ctx, char, bufsize);
+ if (!link_target) {
+ goto err;
+ }
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ ret = glfs_lstat(handle->data, full_fname->base_name, &st);
+ if (ret < 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err;
+ }
+
+#ifdef HAVE_GFAPI_VER_7_11
+ pglfd = vfs_gluster_fetch_glfd(handle, dirfsp);
+ if (pglfd == NULL) {
+ DBG_ERR("Failed to fetch gluster fd\n");
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ referral_len = glfs_readlinkat(pglfd,
+ smb_fname->base_name,
+ link_target,
+ bufsize - 1);
+#else
+ referral_len = glfs_readlink(handle->data,
+ full_fname->base_name,
+ link_target,
+ bufsize - 1);
+#endif
+ if (referral_len < 0) {
+ if (errno == EINVAL) {
+ DBG_INFO("%s is not a link.\n", full_fname->base_name);
+ status = NT_STATUS_OBJECT_TYPE_MISMATCH;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ DBG_ERR("Error reading "
+ "msdfs link %s: %s\n",
+ full_fname->base_name,
+ strerror(errno));
+ }
+ goto err;
+ }
+ link_target[referral_len] = '\0';
+
+ DBG_INFO("%s -> %s\n",
+ full_fname->base_name,
+ link_target);
+
+ if (!strnequal(link_target, "msdfs:", 6)) {
+ status = NT_STATUS_OBJECT_TYPE_MISMATCH;
+ goto err;
+ }
+
+ if (ppreflist == NULL && preferral_count == NULL) {
+ /* Early return for checking if this is a DFS link. */
+ TALLOC_FREE(full_fname);
+ smb_stat_ex_from_stat(&smb_fname->st, &st);
+ return NT_STATUS_OK;
+ }
+
+ ok = parse_msdfs_symlink(mem_ctx,
+ lp_msdfs_shuffle_referrals(SNUM(handle->conn)),
+ link_target,
+ ppreflist,
+ preferral_count);
+
+ if (ok) {
+ smb_stat_ex_from_stat(&smb_fname->st, &st);
+ status = NT_STATUS_OK;
+ } else {
+ status = NT_STATUS_NO_MEMORY;
+ }
+
+ err:
+
+ if (link_target != link_target_buf) {
+ TALLOC_FREE(link_target);
+ }
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static struct vfs_fn_pointers glusterfs_fns = {
+
+ /* Disk Operations */
+
+ .connect_fn = vfs_gluster_connect,
+ .disconnect_fn = vfs_gluster_disconnect,
+ .disk_free_fn = vfs_gluster_disk_free,
+ .get_quota_fn = vfs_gluster_get_quota,
+ .set_quota_fn = vfs_gluster_set_quota,
+ .statvfs_fn = vfs_gluster_statvfs,
+ .fs_capabilities_fn = vfs_gluster_fs_capabilities,
+
+ .get_dfs_referrals_fn = NULL,
+
+ /* Directory Operations */
+
+ .fdopendir_fn = vfs_gluster_fdopendir,
+ .readdir_fn = vfs_gluster_readdir,
+ .rewind_dir_fn = vfs_gluster_rewinddir,
+ .mkdirat_fn = vfs_gluster_mkdirat,
+ .closedir_fn = vfs_gluster_closedir,
+
+ /* File Operations */
+
+ .openat_fn = vfs_gluster_openat,
+ .create_file_fn = NULL,
+ .close_fn = vfs_gluster_close,
+ .pread_fn = vfs_gluster_pread,
+ .pread_send_fn = vfs_gluster_pread_send,
+ .pread_recv_fn = vfs_gluster_pread_recv,
+ .pwrite_fn = vfs_gluster_pwrite,
+ .pwrite_send_fn = vfs_gluster_pwrite_send,
+ .pwrite_recv_fn = vfs_gluster_pwrite_recv,
+ .lseek_fn = vfs_gluster_lseek,
+ .sendfile_fn = vfs_gluster_sendfile,
+ .recvfile_fn = vfs_gluster_recvfile,
+ .renameat_fn = vfs_gluster_renameat,
+ .fsync_send_fn = vfs_gluster_fsync_send,
+ .fsync_recv_fn = vfs_gluster_fsync_recv,
+
+ .stat_fn = vfs_gluster_stat,
+ .fstat_fn = vfs_gluster_fstat,
+ .fstatat_fn = vfs_gluster_fstatat,
+ .lstat_fn = vfs_gluster_lstat,
+ .get_alloc_size_fn = vfs_gluster_get_alloc_size,
+ .unlinkat_fn = vfs_gluster_unlinkat,
+
+ .fchmod_fn = vfs_gluster_fchmod,
+ .fchown_fn = vfs_gluster_fchown,
+ .lchown_fn = vfs_gluster_lchown,
+ .chdir_fn = vfs_gluster_chdir,
+ .getwd_fn = vfs_gluster_getwd,
+ .fntimes_fn = vfs_gluster_fntimes,
+ .ftruncate_fn = vfs_gluster_ftruncate,
+ .fallocate_fn = vfs_gluster_fallocate,
+ .lock_fn = vfs_gluster_lock,
+ .filesystem_sharemode_fn = vfs_gluster_filesystem_sharemode,
+ .fcntl_fn = vfs_gluster_fcntl,
+ .linux_setlease_fn = vfs_gluster_linux_setlease,
+ .getlock_fn = vfs_gluster_getlock,
+ .symlinkat_fn = vfs_gluster_symlinkat,
+ .readlinkat_fn = vfs_gluster_readlinkat,
+ .linkat_fn = vfs_gluster_linkat,
+ .mknodat_fn = vfs_gluster_mknodat,
+ .realpath_fn = vfs_gluster_realpath,
+ .fchflags_fn = vfs_gluster_fchflags,
+ .file_id_create_fn = NULL,
+ .fstreaminfo_fn = NULL,
+ .get_real_filename_at_fn = vfs_gluster_get_real_filename_at,
+ .connectpath_fn = vfs_gluster_connectpath,
+ .create_dfs_pathat_fn = vfs_gluster_create_dfs_pathat,
+ .read_dfs_pathat_fn = vfs_gluster_read_dfs_pathat,
+
+ .brl_lock_windows_fn = NULL,
+ .brl_unlock_windows_fn = NULL,
+ .strict_lock_check_fn = NULL,
+ .translate_name_fn = NULL,
+ .fsctl_fn = NULL,
+
+ /* NT ACL Operations */
+ .fget_nt_acl_fn = NULL,
+ .fset_nt_acl_fn = NULL,
+ .audit_file_fn = NULL,
+
+ /* Posix ACL Operations */
+ .sys_acl_get_fd_fn = posixacl_xattr_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = posixacl_xattr_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = posixacl_xattr_acl_delete_def_fd,
+
+ /* EA Operations */
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fgetxattr_fn = vfs_gluster_fgetxattr,
+ .flistxattr_fn = vfs_gluster_flistxattr,
+ .fremovexattr_fn = vfs_gluster_fremovexattr,
+ .fsetxattr_fn = vfs_gluster_fsetxattr,
+
+ /* AIO Operations */
+ .aio_force_fn = vfs_gluster_aio_force,
+
+ /* Durable handle Operations */
+ .durable_cookie_fn = NULL,
+ .durable_disconnect_fn = NULL,
+ .durable_reconnect_fn = NULL,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_glusterfs_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "glusterfs", &glusterfs_fns);
+}
diff --git a/source3/modules/vfs_glusterfs_fuse.c b/source3/modules/vfs_glusterfs_fuse.c
new file mode 100644
index 0000000..88c740c
--- /dev/null
+++ b/source3/modules/vfs_glusterfs_fuse.c
@@ -0,0 +1,273 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (c) 2019 Guenther Deschner <gd@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+
+#define GLUSTER_NAME_MAX 255
+
+static NTSTATUS vfs_gluster_fuse_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **_found_name)
+{
+ int ret, dirfd;
+ char key_buf[GLUSTER_NAME_MAX + 64];
+ char val_buf[GLUSTER_NAME_MAX + 1];
+ char *found_name = NULL;
+
+ if (strlen(name) >= GLUSTER_NAME_MAX) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ snprintf(key_buf, GLUSTER_NAME_MAX + 64,
+ "glusterfs.get_real_filename:%s", name);
+
+ dirfd = openat(fsp_get_pathref_fd(dirfsp), ".", O_RDONLY);
+ if (dirfd == -1) {
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("Could not open '.' in %s: %s\n",
+ fsp_str_dbg(dirfsp),
+ strerror(errno));
+ return status;
+ }
+
+ ret = fgetxattr(dirfd, key_buf, val_buf, GLUSTER_NAME_MAX + 1);
+ close(dirfd);
+ if (ret == -1) {
+ if (errno == ENOATTR) {
+ errno = ENOENT;
+ }
+ return map_nt_error_from_unix(errno);
+ }
+
+ found_name = talloc_strdup(mem_ctx, val_buf);
+ if (found_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *_found_name = found_name;
+ return NT_STATUS_OK;
+}
+
+struct device_mapping_entry {
+ SMB_DEV_T device; /* the local device, for reference */
+ uint64_t mapped_device; /* the mapped device */
+};
+
+struct vfs_glusterfs_fuse_handle_data {
+ unsigned num_mapped_devices;
+ struct device_mapping_entry *mapped_devices;
+};
+
+/* a 64 bit hash, based on the one in tdb, copied from vfs_fileied */
+static uint64_t vfs_glusterfs_fuse_uint64_hash(const uint8_t *s, size_t len)
+{
+ uint64_t value; /* Used to compute the hash value. */
+ uint32_t i; /* Used to cycle through random values. */
+
+ /* Set the initial value from the key size. */
+ for (value = 0x238F13AFLL * len, i=0; i < len; i++)
+ value = (value + (((uint64_t)s[i]) << (i*5 % 24)));
+
+ return (1103515243LL * value + 12345LL);
+}
+
+static void vfs_glusterfs_fuse_load_devices(
+ struct vfs_glusterfs_fuse_handle_data *data)
+{
+ FILE *f;
+ struct mntent *m;
+
+ data->num_mapped_devices = 0;
+ TALLOC_FREE(data->mapped_devices);
+
+ f = setmntent("/etc/mtab", "r");
+ if (!f) {
+ return;
+ }
+
+ while ((m = getmntent(f))) {
+ struct stat st;
+ char *p;
+ uint64_t mapped_device;
+
+ if (stat(m->mnt_dir, &st) != 0) {
+ /* TODO: log? */
+ continue;
+ }
+
+ /* strip the host part off of the fsname */
+ p = strrchr(m->mnt_fsname, ':');
+ if (p == NULL) {
+ p = m->mnt_fsname;
+ } else {
+ /* TODO: consider the case of '' ? */
+ p++;
+ }
+
+ mapped_device = vfs_glusterfs_fuse_uint64_hash(
+ (const uint8_t *)p,
+ strlen(p));
+
+ data->mapped_devices = talloc_realloc(data,
+ data->mapped_devices,
+ struct device_mapping_entry,
+ data->num_mapped_devices + 1);
+ if (data->mapped_devices == NULL) {
+ goto nomem;
+ }
+
+ data->mapped_devices[data->num_mapped_devices].device =
+ st.st_dev;
+ data->mapped_devices[data->num_mapped_devices].mapped_device =
+ mapped_device;
+
+ data->num_mapped_devices++;
+ }
+
+ endmntent(f);
+ return;
+
+nomem:
+ data->num_mapped_devices = 0;
+ TALLOC_FREE(data->mapped_devices);
+
+ endmntent(f);
+ return;
+}
+
+static int vfs_glusterfs_fuse_map_device_cached(
+ struct vfs_glusterfs_fuse_handle_data *data,
+ SMB_DEV_T device,
+ uint64_t *mapped_device)
+{
+ unsigned i;
+
+ for (i = 0; i < data->num_mapped_devices; i++) {
+ if (data->mapped_devices[i].device == device) {
+ *mapped_device = data->mapped_devices[i].mapped_device;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int vfs_glusterfs_fuse_map_device(
+ struct vfs_glusterfs_fuse_handle_data *data,
+ SMB_DEV_T device,
+ uint64_t *mapped_device)
+{
+ int ret;
+
+ ret = vfs_glusterfs_fuse_map_device_cached(data, device, mapped_device);
+ if (ret == 0) {
+ return 0;
+ }
+
+ vfs_glusterfs_fuse_load_devices(data);
+
+ ret = vfs_glusterfs_fuse_map_device_cached(data, device, mapped_device);
+
+ return ret;
+}
+
+static struct file_id vfs_glusterfs_fuse_file_id_create(
+ struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct vfs_glusterfs_fuse_handle_data *data;
+ struct file_id id;
+ uint64_t mapped_device;
+ int ret;
+
+ ZERO_STRUCT(id);
+
+ id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf);
+
+ SMB_VFS_HANDLE_GET_DATA(handle, data,
+ struct vfs_glusterfs_fuse_handle_data,
+ return id);
+
+ ret = vfs_glusterfs_fuse_map_device(data, sbuf->st_ex_dev,
+ &mapped_device);
+ if (ret == 0) {
+ id.devid = mapped_device;
+ } else {
+ DBG_WARNING("Failed to map device [%jx], falling back to "
+ "standard file_id [%jx]\n",
+ (uintmax_t)sbuf->st_ex_dev,
+ (uintmax_t)id.devid);
+ }
+
+ DBG_DEBUG("Returning dev [%jx] inode [%jx]\n",
+ (uintmax_t)id.devid, (uintmax_t)id.inode);
+
+ return id;
+}
+
+static int vfs_glusterfs_fuse_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct vfs_glusterfs_fuse_handle_data *data;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ data = talloc_zero(handle->conn, struct vfs_glusterfs_fuse_handle_data);
+ if (data == NULL) {
+ DBG_ERR("talloc_zero() failed.\n");
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+
+ /*
+ * Fill the cache in the tree connect, so that the first file/dir access
+ * has chances of being fast...
+ */
+ vfs_glusterfs_fuse_load_devices(data);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, data, NULL,
+ struct vfs_glusterfs_fuse_handle_data,
+ return -1);
+
+ DBG_DEBUG("vfs_glusterfs_fuse_connect(): connected to service[%s]\n",
+ service);
+
+ return 0;
+}
+
+struct vfs_fn_pointers glusterfs_fuse_fns = {
+
+ .connect_fn = vfs_glusterfs_fuse_connect,
+ .get_real_filename_at_fn = vfs_gluster_fuse_get_real_filename_at,
+ .file_id_create_fn = vfs_glusterfs_fuse_file_id_create,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_glusterfs_fuse_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "glusterfs_fuse", &glusterfs_fuse_fns);
+}
diff --git a/source3/modules/vfs_gpfs.c b/source3/modules/vfs_gpfs.c
new file mode 100644
index 0000000..a8b4e38
--- /dev/null
+++ b/source3/modules/vfs_gpfs.c
@@ -0,0 +1,2546 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba VFS module for GPFS filesystem
+ * Copyright (C) Christian Ambach <cambach1@de.ibm.com> 2006
+ * Copyright (C) Christof Schmitt 2015
+ * Major code contributions by Chetan Shringarpure <chetan.sh@in.ibm.com>
+ * and Gomati Mohanan <gomati.mohanan@in.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "include/smbprofile.h"
+#include "modules/non_posix_acls.h"
+#include "libcli/security/security.h"
+#include "nfs4_acls.h"
+#include "system/filesys.h"
+#include "auth.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/gpfswrap.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include "lib/crypto/gnutls_helpers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#ifndef GPFS_GETACL_NATIVE
+#define GPFS_GETACL_NATIVE 0x00000004
+#endif
+
+struct gpfs_config_data {
+ struct smbacl4_vfs_params nfs4_params;
+ bool sharemodes;
+ bool leases;
+ bool hsm;
+ bool syncio;
+ bool winattr;
+ bool ftruncate;
+ bool getrealfilename;
+ bool dfreequota;
+ bool acl;
+ bool settimes;
+ bool recalls;
+ struct {
+ bool gpfs_fstat_x;
+ } pathref_ok;
+};
+
+struct gpfs_fsp_extension {
+ bool offline;
+};
+
+static inline unsigned int gpfs_acl_flags(gpfs_acl_t *gacl)
+{
+ if (gacl->acl_level == GPFS_ACL_LEVEL_V4FLAGS) {
+ return gacl->v4Level1.acl_flags;
+ }
+ return 0;
+}
+
+static inline gpfs_ace_v4_t *gpfs_ace_ptr(gpfs_acl_t *gacl, unsigned int i)
+{
+ if (gacl->acl_level == GPFS_ACL_LEVEL_V4FLAGS) {
+ return &gacl->v4Level1.ace_v4[i];
+ }
+ return &gacl->ace_v4[i];
+}
+
+static unsigned int vfs_gpfs_access_mask_to_allow(uint32_t access_mask)
+{
+ unsigned int allow = GPFS_SHARE_NONE;
+
+ if (access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) {
+ allow |= GPFS_SHARE_WRITE;
+ }
+ if (access_mask & (FILE_READ_DATA|FILE_EXECUTE)) {
+ allow |= GPFS_SHARE_READ;
+ }
+
+ return allow;
+}
+
+static unsigned int vfs_gpfs_share_access_to_deny(uint32_t share_access)
+{
+ unsigned int deny = GPFS_DENY_NONE;
+
+ if (!(share_access & FILE_SHARE_WRITE)) {
+ deny |= GPFS_DENY_WRITE;
+ }
+ if (!(share_access & FILE_SHARE_READ)) {
+ deny |= GPFS_DENY_READ;
+ }
+
+ /*
+ * GPFS_DENY_DELETE can only be set together with either
+ * GPFS_DENY_WRITE or GPFS_DENY_READ.
+ */
+ if ((deny & (GPFS_DENY_WRITE|GPFS_DENY_READ)) &&
+ !(share_access & FILE_SHARE_DELETE)) {
+ deny |= GPFS_DENY_DELETE;
+ }
+
+ return deny;
+}
+
+static int set_gpfs_sharemode(files_struct *fsp, uint32_t access_mask,
+ uint32_t share_access)
+{
+ unsigned int allow = GPFS_SHARE_NONE;
+ unsigned int deny = GPFS_DENY_NONE;
+ int result;
+
+ if (access_mask == 0) {
+ DBG_DEBUG("Clearing file system share mode.\n");
+ } else {
+ allow = vfs_gpfs_access_mask_to_allow(access_mask);
+ deny = vfs_gpfs_share_access_to_deny(share_access);
+ }
+ DBG_DEBUG("access_mask=0x%x, allow=0x%x, share_access=0x%x, "
+ "deny=0x%x\n", access_mask, allow, share_access, deny);
+
+ result = gpfswrap_set_share(fsp_get_io_fd(fsp), allow, deny);
+ if (result == 0) {
+ return 0;
+ }
+
+ if (errno == EACCES) {
+ DBG_NOTICE("GPFS share mode denied for %s/%s.\n",
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name);
+ } else if (errno == EPERM) {
+ DBG_ERR("Samba requested GPFS sharemode for %s/%s, but the "
+ "GPFS file system is not configured accordingly. "
+ "Configure file system with mmchfs -D nfs4 or "
+ "set gpfs:sharemodes=no in Samba.\n",
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name);
+ } else {
+ DBG_ERR("gpfs_set_share failed: %s\n", strerror(errno));
+ }
+
+ return result;
+}
+
+static int vfs_gpfs_filesystem_sharemode(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+
+ struct gpfs_config_data *config;
+ int ret = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ if(!config->sharemodes) {
+ return 0;
+ }
+
+ /*
+ * A named stream fsp will have the basefile open in the fsp
+ * fd, so lacking a distinct fd for the stream we have to skip
+ * set_gpfs_sharemode for stream.
+ */
+ if (fsp_is_alternate_stream(fsp)) {
+ DBG_NOTICE("Not requesting GPFS sharemode on stream: %s/%s\n",
+ fsp->conn->connectpath,
+ fsp_str_dbg(fsp));
+ return 0;
+ }
+
+ ret = set_gpfs_sharemode(fsp, access_mask, share_access);
+
+ return ret;
+}
+
+static int vfs_gpfs_close(vfs_handle_struct *handle, files_struct *fsp)
+{
+
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ if (config->sharemodes &&
+ (fsp->fsp_flags.kernel_share_modes_taken))
+ {
+ /*
+ * Always clear GPFS sharemode in case the actual
+ * close gets deferred due to outstanding POSIX locks
+ * (see fd_close_posix)
+ */
+ int ret = gpfswrap_set_share(fsp_get_io_fd(fsp), 0, 0);
+ if (ret != 0) {
+ DBG_ERR("Clearing GPFS sharemode on close failed for "
+ " %s/%s: %s\n",
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name,
+ strerror(errno));
+ }
+ }
+
+ return SMB_VFS_NEXT_CLOSE(handle, fsp);
+}
+
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+static int lease_type_to_gpfs(int leasetype)
+{
+ if (leasetype == F_RDLCK) {
+ return GPFS_LEASE_READ;
+ }
+
+ if (leasetype == F_WRLCK) {
+ return GPFS_LEASE_WRITE;
+ }
+
+ return GPFS_LEASE_NONE;
+}
+
+static int vfs_gpfs_setlease(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int leasetype)
+{
+ struct gpfs_config_data *config;
+ int ret=0;
+
+ START_PROFILE(syscall_linux_setlease);
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ ret = linux_set_lease_sighandler(fsp_get_io_fd(fsp));
+ if (ret == -1) {
+ goto failure;
+ }
+
+ if (config->leases) {
+ int gpfs_lease_type = lease_type_to_gpfs(leasetype);
+ int saved_errno = 0;
+
+ /*
+ * Ensure the lease owner is root to allow
+ * correct delivery of lease-break signals.
+ */
+ become_root();
+ ret = gpfswrap_set_lease(fsp_get_io_fd(fsp), gpfs_lease_type);
+ if (ret < 0) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ }
+
+failure:
+ END_PROFILE(syscall_linux_setlease);
+
+ return ret;
+}
+
+#else /* HAVE_KERNEL_OPLOCKS_LINUX */
+
+static int vfs_gpfs_setlease(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int leasetype)
+{
+ return ENOSYS;
+}
+#endif /* HAVE_KERNEL_OPLOCKS_LINUX */
+
+static NTSTATUS vfs_gpfs_get_real_filename_at(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ int result;
+ char *full_path = NULL;
+ char *to_free = NULL;
+ char real_pathname[PATH_MAX+1], tmpbuf[PATH_MAX];
+ size_t full_path_len;
+ int buflen;
+ bool mangled;
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (!config->getrealfilename) {
+ return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, dirfsp, name, mem_ctx, found_name);
+ }
+
+ mangled = mangle_is_mangled(name, handle->conn->params);
+ if (mangled) {
+ return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, dirfsp, name, mem_ctx, found_name);
+ }
+
+ full_path_len = full_path_tos(dirfsp->fsp_name->base_name, name,
+ tmpbuf, sizeof(tmpbuf),
+ &full_path, &to_free);
+ if (full_path_len == -1) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ buflen = sizeof(real_pathname) - 1;
+
+ result = gpfswrap_get_realfilename_path(full_path, real_pathname,
+ &buflen);
+
+ TALLOC_FREE(to_free);
+
+ if ((result == -1) && (errno == ENOSYS)) {
+ return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, dirfsp, name, mem_ctx, found_name);
+ }
+
+ if (result == -1) {
+ DEBUG(10, ("smbd_gpfs_get_realfilename_path returned %s\n",
+ strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * GPFS does not necessarily null-terminate the returned path
+ * but instead returns the buffer length in buflen.
+ */
+
+ if (buflen < sizeof(real_pathname)) {
+ real_pathname[buflen] = '\0';
+ } else {
+ real_pathname[sizeof(real_pathname)-1] = '\0';
+ }
+
+ DBG_DEBUG("%s/%s -> %s\n",
+ fsp_str_dbg(dirfsp),
+ name,
+ real_pathname);
+
+ name = strrchr_m(real_pathname, '/');
+ if (name == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ *found_name = talloc_strdup(mem_ctx, name+1);
+ if (*found_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void sd2gpfs_control(uint16_t control, struct gpfs_acl *gacl)
+{
+ unsigned int gpfs_aclflags = 0;
+ control &= SEC_DESC_DACL_PROTECTED | SEC_DESC_SACL_PROTECTED |
+ SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_SACL_AUTO_INHERITED |
+ SEC_DESC_DACL_DEFAULTED | SEC_DESC_SACL_DEFAULTED |
+ SEC_DESC_DACL_PRESENT | SEC_DESC_SACL_PRESENT;
+ gpfs_aclflags = control << 8;
+ if (!(control & SEC_DESC_DACL_PRESENT))
+ gpfs_aclflags |= ACL4_FLAG_NULL_DACL;
+ if (!(control & SEC_DESC_SACL_PRESENT))
+ gpfs_aclflags |= ACL4_FLAG_NULL_SACL;
+ gacl->acl_level = GPFS_ACL_LEVEL_V4FLAGS;
+ gacl->v4Level1.acl_flags = gpfs_aclflags;
+}
+
+static uint16_t gpfs2sd_control(unsigned int gpfs_aclflags)
+{
+ uint16_t control = gpfs_aclflags >> 8;
+ control &= SEC_DESC_DACL_PROTECTED | SEC_DESC_SACL_PROTECTED |
+ SEC_DESC_DACL_AUTO_INHERITED | SEC_DESC_SACL_AUTO_INHERITED |
+ SEC_DESC_DACL_DEFAULTED | SEC_DESC_SACL_DEFAULTED |
+ SEC_DESC_DACL_PRESENT | SEC_DESC_SACL_PRESENT;
+ control |= SEC_DESC_SELF_RELATIVE;
+ return control;
+}
+
+static void gpfs_dumpacl(int level, struct gpfs_acl *gacl)
+{
+ gpfs_aclCount_t i;
+ if (gacl==NULL)
+ {
+ DEBUG(0, ("gpfs acl is NULL\n"));
+ return;
+ }
+
+ DEBUG(level, ("len: %d, level: %d, version: %d, nace: %d, "
+ "control: %x\n",
+ gacl->acl_len, gacl->acl_level, gacl->acl_version,
+ gacl->acl_nace, gpfs_acl_flags(gacl)));
+
+ for(i=0; i<gacl->acl_nace; i++)
+ {
+ struct gpfs_ace_v4 *gace = gpfs_ace_ptr(gacl, i);
+ DEBUG(level, ("\tace[%d]: type:%d, flags:0x%x, mask:0x%x, "
+ "iflags:0x%x, who:%u\n",
+ i, gace->aceType, gace->aceFlags, gace->aceMask,
+ gace->aceIFlags, gace->aceWho));
+ }
+}
+
+static int gpfs_getacl_with_capability(struct files_struct *fsp,
+ int flags,
+ void *buf)
+{
+ int ret, saved_errno;
+
+ set_effective_capability(DAC_OVERRIDE_CAPABILITY);
+
+ ret = gpfswrap_fgetacl(fsp_get_pathref_fd(fsp), flags, buf);
+ saved_errno = errno;
+
+ drop_effective_capability(DAC_OVERRIDE_CAPABILITY);
+
+ errno = saved_errno;
+ return ret;
+}
+
+/*
+ * get the ACL from GPFS, allocated on the specified mem_ctx
+ * internally retries when initial buffer was too small
+ *
+ * caller needs to cast result to either
+ * raw = yes: struct gpfs_opaque_acl
+ * raw = no: struct gpfs_acl
+ *
+ */
+static void *vfs_gpfs_getacl(TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ const bool raw,
+ const gpfs_aclType_t type)
+{
+ const char *fname = fsp->fsp_name->base_name;
+ void *aclbuf;
+ size_t size = 512;
+ int ret, flags;
+ unsigned int *len;
+ size_t struct_size;
+ bool use_capability = false;
+
+again:
+
+ aclbuf = talloc_zero_size(mem_ctx, size);
+ if (aclbuf == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (raw) {
+ struct gpfs_opaque_acl *buf = (struct gpfs_opaque_acl *) aclbuf;
+ buf->acl_type = type;
+ flags = GPFS_GETACL_NATIVE;
+ len = (unsigned int *) &(buf->acl_buffer_len);
+ struct_size = sizeof(struct gpfs_opaque_acl);
+ } else {
+ struct gpfs_acl *buf = (struct gpfs_acl *) aclbuf;
+ buf->acl_type = type;
+ buf->acl_level = GPFS_ACL_LEVEL_V4FLAGS;
+ flags = GPFS_GETACL_STRUCT;
+ len = &(buf->acl_len);
+ /* reserve space for control flags in gpfs 3.5 and beyond */
+ struct_size = sizeof(struct gpfs_acl) + sizeof(unsigned int);
+ }
+
+ /* set the length of the buffer as input value */
+ *len = size;
+
+ if (use_capability) {
+ ret = gpfs_getacl_with_capability(fsp, flags, aclbuf);
+ } else {
+ ret = gpfswrap_fgetacl(fsp_get_pathref_fd(fsp), flags, aclbuf);
+ if ((ret != 0) && (errno == EACCES)) {
+ DBG_DEBUG("Retry with DAC capability for %s\n", fname);
+ use_capability = true;
+ ret = gpfs_getacl_with_capability(fsp, flags, aclbuf);
+ }
+ }
+
+ if ((ret != 0) && (errno == ENOSPC)) {
+ /*
+ * get the size needed to accommodate the complete buffer
+ *
+ * the value returned only applies to the ACL blob in the
+ * struct so make sure to also have headroom for the first
+ * struct members by adding room for the complete struct
+ * (might be a few bytes too much then)
+ */
+ size = *len + struct_size;
+ talloc_free(aclbuf);
+ DEBUG(10, ("Increasing ACL buffer size to %zu\n", size));
+ goto again;
+ }
+
+ if (ret != 0) {
+ DEBUG(5, ("smbd_gpfs_getacl failed with %s\n",
+ strerror(errno)));
+ talloc_free(aclbuf);
+ return NULL;
+ }
+
+ return aclbuf;
+}
+
+/* Tries to get nfs4 acls and returns SMB ACL allocated.
+ * On failure returns 1 if it got non-NFSv4 ACL to prompt
+ * retry with POSIX ACL checks.
+ * On failure returns -1 if there is system (GPFS) error, check errno.
+ * Returns 0 on success
+ */
+static int gpfs_get_nfs4_acl(TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ struct SMB4ACL_T **ppacl)
+{
+ const char *fname = fsp->fsp_name->base_name;
+ gpfs_aclCount_t i;
+ struct gpfs_acl *gacl = NULL;
+ DEBUG(10, ("gpfs_get_nfs4_acl invoked for %s\n", fname));
+
+ /* Get the ACL */
+ gacl = (struct gpfs_acl*) vfs_gpfs_getacl(talloc_tos(), fsp,
+ false, 0);
+ if (gacl == NULL) {
+ DEBUG(9, ("gpfs_getacl failed for %s with %s\n",
+ fname, strerror(errno)));
+ if (errno == ENODATA) {
+ /*
+ * GPFS returns ENODATA for snapshot
+ * directories. Retry with POSIX ACLs check.
+ */
+ return 1;
+ }
+
+ return -1;
+ }
+
+ if (gacl->acl_type != GPFS_ACL_TYPE_NFS4) {
+ DEBUG(10, ("Got non-nfsv4 acl\n"));
+ /* Retry with POSIX ACLs check */
+ talloc_free(gacl);
+ return 1;
+ }
+
+ *ppacl = smb_create_smb4acl(mem_ctx);
+
+ if (gacl->acl_level == GPFS_ACL_LEVEL_V4FLAGS) {
+ uint16_t control = gpfs2sd_control(gpfs_acl_flags(gacl));
+ smbacl4_set_controlflags(*ppacl, control);
+ }
+
+ DEBUG(10, ("len: %d, level: %d, version: %d, nace: %d, control: %x\n",
+ gacl->acl_len, gacl->acl_level, gacl->acl_version,
+ gacl->acl_nace, gpfs_acl_flags(gacl)));
+
+ for (i=0; i<gacl->acl_nace; i++) {
+ struct gpfs_ace_v4 *gace = gpfs_ace_ptr(gacl, i);
+ SMB_ACE4PROP_T smbace = { 0 };
+ DEBUG(10, ("type: %d, iflags: %x, flags: %x, mask: %x, "
+ "who: %d\n", gace->aceType, gace->aceIFlags,
+ gace->aceFlags, gace->aceMask, gace->aceWho));
+
+ if (gace->aceIFlags & ACE4_IFLAG_SPECIAL_ID) {
+ smbace.flags |= SMB_ACE4_ID_SPECIAL;
+ switch (gace->aceWho) {
+ case ACE4_SPECIAL_OWNER:
+ smbace.who.special_id = SMB_ACE4_WHO_OWNER;
+ break;
+ case ACE4_SPECIAL_GROUP:
+ smbace.who.special_id = SMB_ACE4_WHO_GROUP;
+ break;
+ case ACE4_SPECIAL_EVERYONE:
+ smbace.who.special_id = SMB_ACE4_WHO_EVERYONE;
+ break;
+ default:
+ DEBUG(8, ("invalid special gpfs id %d "
+ "ignored\n", gace->aceWho));
+ continue; /* don't add it */
+ }
+ } else {
+ if (gace->aceFlags & ACE4_FLAG_GROUP_ID)
+ smbace.who.gid = gace->aceWho;
+ else
+ smbace.who.uid = gace->aceWho;
+ }
+
+ /* remove redundant deny entries */
+ if (i > 0 && gace->aceType == SMB_ACE4_ACCESS_DENIED_ACE_TYPE) {
+ struct gpfs_ace_v4 *prev = gpfs_ace_ptr(gacl, i - 1);
+ if (prev->aceType == SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE &&
+ prev->aceFlags == gace->aceFlags &&
+ prev->aceIFlags == gace->aceIFlags &&
+ (gace->aceMask & prev->aceMask) == 0 &&
+ gace->aceWho == prev->aceWho) {
+ /* it's redundant - skip it */
+ continue;
+ }
+ }
+
+ smbace.aceType = gace->aceType;
+ smbace.aceFlags = gace->aceFlags;
+ smbace.aceMask = gace->aceMask;
+ smb_add_ace4(*ppacl, &smbace);
+ }
+
+ talloc_free(gacl);
+
+ return 0;
+}
+
+static NTSTATUS gpfsacl_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp, uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ struct SMB4ACL_T *pacl = NULL;
+ int result;
+ struct gpfs_config_data *config;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ *ppdesc = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (!config->acl) {
+ status = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info,
+ mem_ctx, ppdesc);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ result = gpfs_get_nfs4_acl(frame, fsp, &pacl);
+
+ if (result == 0) {
+ status = smb_fget_nt_acl_nfs4(fsp, &config->nfs4_params,
+ security_info,
+ mem_ctx, ppdesc, pacl);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (result > 0) {
+ DEBUG(10, ("retrying with posix acl...\n"));
+ status = posix_fget_nt_acl(fsp, security_info,
+ mem_ctx, ppdesc);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+
+ /* GPFS ACL was not read, something wrong happened, error code is set in errno */
+ return map_nt_error_from_unix(errno);
+}
+
+static bool vfs_gpfs_nfs4_ace_to_gpfs_ace(SMB_ACE4PROP_T *nfs4_ace,
+ struct gpfs_ace_v4 *gace,
+ uid_t owner_uid)
+{
+ gace->aceType = nfs4_ace->aceType;
+ gace->aceFlags = nfs4_ace->aceFlags;
+ gace->aceMask = nfs4_ace->aceMask;
+
+ if (nfs4_ace->flags & SMB_ACE4_ID_SPECIAL) {
+ switch(nfs4_ace->who.special_id) {
+ case SMB_ACE4_WHO_EVERYONE:
+ gace->aceIFlags = ACE4_IFLAG_SPECIAL_ID;
+ gace->aceWho = ACE4_SPECIAL_EVERYONE;
+ break;
+ case SMB_ACE4_WHO_OWNER:
+ /*
+ * With GPFS it is not possible to deny ACL or
+ * attribute access to the owner. Setting an
+ * ACL with such an entry is not possible.
+ * Denying ACL or attribute access for the
+ * owner through a named ACL entry can be
+ * stored in an ACL, it is just not effective.
+ *
+ * Map this case to a named entry to allow at
+ * least setting this ACL, which will be
+ * enforced by the smbd permission check. Do
+ * not do this for an inheriting OWNER entry,
+ * as this represents a CREATOR OWNER ACE. The
+ * remaining limitation is that CREATOR OWNER
+ * cannot deny ACL or attribute access.
+ */
+ if (!nfs_ace_is_inherit(nfs4_ace) &&
+ nfs4_ace->aceType ==
+ SMB_ACE4_ACCESS_DENIED_ACE_TYPE &&
+ nfs4_ace->aceMask & (SMB_ACE4_READ_ATTRIBUTES|
+ SMB_ACE4_WRITE_ATTRIBUTES|
+ SMB_ACE4_READ_ACL|
+ SMB_ACE4_WRITE_ACL)) {
+ gace->aceIFlags = 0;
+ gace->aceWho = owner_uid;
+ } else {
+ gace->aceIFlags = ACE4_IFLAG_SPECIAL_ID;
+ gace->aceWho = ACE4_SPECIAL_OWNER;
+ }
+ break;
+ case SMB_ACE4_WHO_GROUP:
+ gace->aceIFlags = ACE4_IFLAG_SPECIAL_ID;
+ gace->aceWho = ACE4_SPECIAL_GROUP;
+ break;
+ default:
+ DBG_WARNING("Unsupported special_id %d\n",
+ nfs4_ace->who.special_id);
+ return false;
+ }
+
+ return true;
+ }
+
+ gace->aceIFlags = 0;
+ gace->aceWho = (nfs4_ace->aceFlags & SMB_ACE4_IDENTIFIER_GROUP) ?
+ nfs4_ace->who.gid : nfs4_ace->who.uid;
+
+ return true;
+}
+
+static struct gpfs_acl *vfs_gpfs_smbacl2gpfsacl(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ struct SMB4ACL_T *smbacl,
+ bool controlflags)
+{
+ struct gpfs_acl *gacl;
+ gpfs_aclLen_t gacl_len;
+ struct SMB4ACE_T *smbace;
+
+ gacl_len = offsetof(gpfs_acl_t, ace_v4) + sizeof(unsigned int)
+ + smb_get_naces(smbacl) * sizeof(gpfs_ace_v4_t);
+
+ gacl = (struct gpfs_acl *)TALLOC_SIZE(mem_ctx, gacl_len);
+ if (gacl == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ gacl->acl_level = GPFS_ACL_LEVEL_BASE;
+ gacl->acl_version = GPFS_ACL_VERSION_NFS4;
+ gacl->acl_type = GPFS_ACL_TYPE_NFS4;
+ gacl->acl_nace = 0; /* change later... */
+
+ if (controlflags) {
+ gacl->acl_level = GPFS_ACL_LEVEL_V4FLAGS;
+ sd2gpfs_control(smbacl4_get_controlflags(smbacl), gacl);
+ }
+
+ for (smbace=smb_first_ace4(smbacl); smbace!=NULL; smbace = smb_next_ace4(smbace)) {
+ struct gpfs_ace_v4 *gace = gpfs_ace_ptr(gacl, gacl->acl_nace);
+ SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace);
+ bool add_ace;
+
+ add_ace = vfs_gpfs_nfs4_ace_to_gpfs_ace(aceprop, gace,
+ fsp->fsp_name->st.st_ex_uid);
+ if (!add_ace) {
+ continue;
+ }
+
+ gacl->acl_nace++;
+ }
+ gacl->acl_len = (char *)gpfs_ace_ptr(gacl, gacl->acl_nace)
+ - (char *)gacl;
+ return gacl;
+}
+
+static bool gpfsacl_process_smbacl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct SMB4ACL_T *smbacl)
+{
+ int ret;
+ struct gpfs_acl *gacl;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+
+ gacl = vfs_gpfs_smbacl2gpfsacl(mem_ctx, fsp, smbacl, true);
+ if (gacl == NULL) { /* out of memory */
+ return False;
+ }
+ ret = gpfswrap_putacl(fsp->fsp_name->base_name,
+ GPFS_PUTACL_STRUCT | GPFS_ACL_SAMBA, gacl);
+
+ if ((ret != 0) && (errno == EINVAL)) {
+ DEBUG(10, ("Retry without nfs41 control flags\n"));
+ talloc_free(gacl);
+ gacl = vfs_gpfs_smbacl2gpfsacl(mem_ctx, fsp, smbacl, false);
+ if (gacl == NULL) { /* out of memory */
+ return False;
+ }
+ ret = gpfswrap_putacl(fsp->fsp_name->base_name,
+ GPFS_PUTACL_STRUCT | GPFS_ACL_SAMBA,
+ gacl);
+ }
+
+ if (ret != 0) {
+ DEBUG(8, ("gpfs_putacl failed with %s\n", strerror(errno)));
+ gpfs_dumpacl(8, gacl);
+ return False;
+ }
+
+ DEBUG(10, ("gpfs_putacl succeeded\n"));
+ return True;
+}
+
+static NTSTATUS gpfsacl_set_nt_acl_internal(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd)
+{
+ struct gpfs_acl *acl;
+ NTSTATUS result = NT_STATUS_ACCESS_DENIED;
+
+ acl = (struct gpfs_acl*) vfs_gpfs_getacl(talloc_tos(),
+ fsp,
+ false, 0);
+ if (acl == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ if (acl->acl_version == GPFS_ACL_VERSION_NFS4) {
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ result = smb_set_nt_acl_nfs4(handle,
+ fsp, &config->nfs4_params, security_info_sent, psd,
+ gpfsacl_process_smbacl);
+ } else { /* assume POSIX ACL - by default... */
+ result = set_nt_acl(fsp, security_info_sent, psd);
+ }
+
+ talloc_free(acl);
+ return result;
+}
+
+static NTSTATUS gpfsacl_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd)
+{
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (!config->acl) {
+ return SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+ }
+
+ return gpfsacl_set_nt_acl_internal(handle, fsp, security_info_sent, psd);
+}
+
+static SMB_ACL_T gpfs2smb_acl(const struct gpfs_acl *pacl, TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result;
+ gpfs_aclCount_t i;
+
+ result = sys_acl_init(mem_ctx);
+ if (result == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ result->count = pacl->acl_nace;
+ result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry,
+ result->count);
+ if (result->acl == NULL) {
+ TALLOC_FREE(result);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ for (i=0; i<pacl->acl_nace; i++) {
+ struct smb_acl_entry *ace = &result->acl[i];
+ const struct gpfs_ace_v1 *g_ace = &pacl->ace_v1[i];
+
+ DEBUG(10, ("Converting type %d id %lu perm %x\n",
+ (int)g_ace->ace_type, (unsigned long)g_ace->ace_who,
+ (int)g_ace->ace_perm));
+
+ switch (g_ace->ace_type) {
+ case GPFS_ACL_USER:
+ ace->a_type = SMB_ACL_USER;
+ ace->info.user.uid = (uid_t)g_ace->ace_who;
+ break;
+ case GPFS_ACL_USER_OBJ:
+ ace->a_type = SMB_ACL_USER_OBJ;
+ break;
+ case GPFS_ACL_GROUP:
+ ace->a_type = SMB_ACL_GROUP;
+ ace->info.group.gid = (gid_t)g_ace->ace_who;
+ break;
+ case GPFS_ACL_GROUP_OBJ:
+ ace->a_type = SMB_ACL_GROUP_OBJ;
+ break;
+ case GPFS_ACL_OTHER:
+ ace->a_type = SMB_ACL_OTHER;
+ break;
+ case GPFS_ACL_MASK:
+ ace->a_type = SMB_ACL_MASK;
+ break;
+ default:
+ DEBUG(10, ("Got invalid ace_type: %d\n",
+ g_ace->ace_type));
+ TALLOC_FREE(result);
+ errno = EINVAL;
+ return NULL;
+ }
+
+ ace->a_perm = 0;
+ ace->a_perm |= (g_ace->ace_perm & ACL_PERM_READ) ?
+ SMB_ACL_READ : 0;
+ ace->a_perm |= (g_ace->ace_perm & ACL_PERM_WRITE) ?
+ SMB_ACL_WRITE : 0;
+ ace->a_perm |= (g_ace->ace_perm & ACL_PERM_EXECUTE) ?
+ SMB_ACL_EXECUTE : 0;
+
+ DEBUGADD(10, ("Converted to %d perm %x\n",
+ ace->a_type, ace->a_perm));
+ }
+
+ return result;
+}
+
+static SMB_ACL_T gpfsacl_get_posix_acl(struct files_struct *fsp,
+ gpfs_aclType_t type,
+ TALLOC_CTX *mem_ctx)
+{
+ struct gpfs_acl *pacl;
+ SMB_ACL_T result = NULL;
+
+ pacl = vfs_gpfs_getacl(talloc_tos(), fsp, false, type);
+
+ if (pacl == NULL) {
+ DBG_DEBUG("vfs_gpfs_getacl failed for %s with %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ if (errno == 0) {
+ errno = EINVAL;
+ }
+ goto done;
+ }
+
+ if (pacl->acl_version != GPFS_ACL_VERSION_POSIX) {
+ DEBUG(10, ("Got acl version %d, expected %d\n",
+ pacl->acl_version, GPFS_ACL_VERSION_POSIX));
+ errno = EINVAL;
+ goto done;
+ }
+
+ DEBUG(10, ("len: %d, level: %d, version: %d, nace: %d\n",
+ pacl->acl_len, pacl->acl_level, pacl->acl_version,
+ pacl->acl_nace));
+
+ result = gpfs2smb_acl(pacl, mem_ctx);
+ if (result != NULL) {
+ errno = 0;
+ }
+
+ done:
+
+ if (pacl != NULL) {
+ talloc_free(pacl);
+ }
+ if (errno != 0) {
+ TALLOC_FREE(result);
+ }
+ return result;
+}
+
+static SMB_ACL_T gpfsacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ gpfs_aclType_t gpfs_type;
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return NULL);
+
+ if (!config->acl) {
+ return SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, fsp, type, mem_ctx);
+ }
+
+ switch(type) {
+ case SMB_ACL_TYPE_ACCESS:
+ gpfs_type = GPFS_ACL_TYPE_ACCESS;
+ break;
+ case SMB_ACL_TYPE_DEFAULT:
+ gpfs_type = GPFS_ACL_TYPE_DEFAULT;
+ break;
+ default:
+ DEBUG(0, ("Got invalid type: %d\n", type));
+ smb_panic("exiting");
+ }
+ return gpfsacl_get_posix_acl(fsp, gpfs_type, mem_ctx);
+}
+
+static int gpfsacl_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ struct gpfs_config_data *config;
+ struct gpfs_opaque_acl *acl = NULL;
+ DATA_BLOB aclblob;
+ int result;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ if (!config->acl) {
+ return SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx,
+ blob_description, blob);
+ }
+
+ errno = 0;
+ acl = (struct gpfs_opaque_acl *) vfs_gpfs_getacl(mem_ctx,
+ fsp,
+ true,
+ GPFS_ACL_TYPE_NFS4);
+
+ if (errno) {
+ DEBUG(5, ("vfs_gpfs_getacl finished with errno %d: %s\n",
+ errno, strerror(errno)));
+
+ /* EINVAL means POSIX ACL, bail out on other cases */
+ if (errno != EINVAL) {
+ return -1;
+ }
+ }
+
+ if (acl != NULL) {
+ /*
+ * file has NFSv4 ACL
+ *
+ * we only need the actual ACL blob here
+ * acl_version will always be NFS4 because we asked
+ * for NFS4
+ * acl_type is only used for POSIX ACLs
+ */
+ aclblob.data = (uint8_t*) acl->acl_var_data;
+ aclblob.length = acl->acl_buffer_len;
+
+ *blob_description = talloc_strdup(mem_ctx, "gpfs_nfs4_acl");
+ if (!*blob_description) {
+ talloc_free(acl);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ result = non_posix_sys_acl_blob_get_fd_helper(handle, fsp,
+ aclblob, mem_ctx,
+ blob);
+
+ talloc_free(acl);
+ return result;
+ }
+
+ /* fall back to POSIX ACL */
+ return posix_sys_acl_blob_get_fd(handle, fsp, mem_ctx,
+ blob_description, blob);
+}
+
+static struct gpfs_acl *smb2gpfs_acl(const SMB_ACL_T pacl,
+ SMB_ACL_TYPE_T type)
+{
+ gpfs_aclLen_t len;
+ struct gpfs_acl *result;
+ int i;
+
+ DEBUG(10, ("smb2gpfs_acl: Got ACL with %d entries\n", pacl->count));
+
+ len = offsetof(gpfs_acl_t, ace_v1) + (pacl->count) *
+ sizeof(gpfs_ace_v1_t);
+
+ result = (struct gpfs_acl *)SMB_MALLOC(len);
+ if (result == NULL) {
+ errno = ENOMEM;
+ return result;
+ }
+
+ result->acl_len = len;
+ result->acl_level = 0;
+ result->acl_version = GPFS_ACL_VERSION_POSIX;
+ result->acl_type = (type == SMB_ACL_TYPE_DEFAULT) ?
+ GPFS_ACL_TYPE_DEFAULT : GPFS_ACL_TYPE_ACCESS;
+ result->acl_nace = pacl->count;
+
+ for (i=0; i<pacl->count; i++) {
+ const struct smb_acl_entry *ace = &pacl->acl[i];
+ struct gpfs_ace_v1 *g_ace = &result->ace_v1[i];
+
+ DEBUG(10, ("Converting type %d perm %x\n",
+ (int)ace->a_type, (int)ace->a_perm));
+
+ g_ace->ace_perm = 0;
+
+ switch(ace->a_type) {
+ case SMB_ACL_USER:
+ g_ace->ace_type = GPFS_ACL_USER;
+ g_ace->ace_who = (gpfs_uid_t)ace->info.user.uid;
+ break;
+ case SMB_ACL_USER_OBJ:
+ g_ace->ace_type = GPFS_ACL_USER_OBJ;
+ g_ace->ace_perm |= ACL_PERM_CONTROL;
+ g_ace->ace_who = 0;
+ break;
+ case SMB_ACL_GROUP:
+ g_ace->ace_type = GPFS_ACL_GROUP;
+ g_ace->ace_who = (gpfs_uid_t)ace->info.group.gid;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ g_ace->ace_type = GPFS_ACL_GROUP_OBJ;
+ g_ace->ace_who = 0;
+ break;
+ case SMB_ACL_MASK:
+ g_ace->ace_type = GPFS_ACL_MASK;
+ g_ace->ace_perm = 0x8f;
+ g_ace->ace_who = 0;
+ break;
+ case SMB_ACL_OTHER:
+ g_ace->ace_type = GPFS_ACL_OTHER;
+ g_ace->ace_who = 0;
+ break;
+ default:
+ DEBUG(10, ("Got invalid ace_type: %d\n", ace->a_type));
+ errno = EINVAL;
+ SAFE_FREE(result);
+ return NULL;
+ }
+
+ g_ace->ace_perm |= (ace->a_perm & SMB_ACL_READ) ?
+ ACL_PERM_READ : 0;
+ g_ace->ace_perm |= (ace->a_perm & SMB_ACL_WRITE) ?
+ ACL_PERM_WRITE : 0;
+ g_ace->ace_perm |= (ace->a_perm & SMB_ACL_EXECUTE) ?
+ ACL_PERM_EXECUTE : 0;
+
+ DEBUGADD(10, ("Converted to %d id %d perm %x\n",
+ g_ace->ace_type, g_ace->ace_who, g_ace->ace_perm));
+ }
+
+ return result;
+}
+
+static int gpfsacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ struct gpfs_config_data *config;
+ struct gpfs_acl *gpfs_acl = NULL;
+ int result;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ if (!config->acl) {
+ return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl);
+ }
+
+ gpfs_acl = smb2gpfs_acl(theacl, type);
+ if (gpfs_acl == NULL) {
+ return -1;
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ result = gpfswrap_putacl(fsp->fsp_name->base_name,
+ GPFS_PUTACL_STRUCT|GPFS_ACL_SAMBA,
+ gpfs_acl);
+ SAFE_FREE(gpfs_acl);
+ return result;
+}
+
+static int gpfsacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ if (!config->acl) {
+ return SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp);
+ }
+
+ errno = ENOTSUP;
+ return -1;
+}
+
+
+/*
+ * Assumed: mode bits are shiftable and standard
+ * Output: the new aceMask field for an smb nfs4 ace
+ */
+static uint32_t gpfsacl_mask_filter(uint32_t aceType, uint32_t aceMask, uint32_t rwx)
+{
+ const uint32_t posix_nfs4map[3] = {
+ SMB_ACE4_EXECUTE, /* execute */
+ SMB_ACE4_WRITE_DATA | SMB_ACE4_APPEND_DATA, /* write; GPFS specific */
+ SMB_ACE4_READ_DATA /* read */
+ };
+ int i;
+ uint32_t posix_mask = 0x01;
+ uint32_t posix_bit;
+ uint32_t nfs4_bits;
+
+ for(i=0; i<3; i++) {
+ nfs4_bits = posix_nfs4map[i];
+ posix_bit = rwx & posix_mask;
+
+ if (aceType==SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE) {
+ if (posix_bit)
+ aceMask |= nfs4_bits;
+ else
+ aceMask &= ~nfs4_bits;
+ } else {
+ /* add deny bits when suitable */
+ if (!posix_bit)
+ aceMask |= nfs4_bits;
+ else
+ aceMask &= ~nfs4_bits;
+ } /* other ace types are unexpected */
+
+ posix_mask <<= 1;
+ }
+
+ return aceMask;
+}
+
+static int gpfsacl_emu_chmod(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ mode_t mode)
+{
+ struct smb_filename *fname = fsp->fsp_name;
+ char *path = fsp->fsp_name->base_name;
+ struct SMB4ACL_T *pacl = NULL;
+ int result;
+ bool haveAllowEntry[SMB_ACE4_WHO_EVERYONE + 1] = {False, False, False, False};
+ int i;
+ files_struct fake_fsp = { 0 }; /* TODO: rationalize parametrization */
+ struct SMB4ACE_T *smbace;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ DEBUG(10, ("gpfsacl_emu_chmod invoked for %s mode %o\n", path, mode));
+
+ result = gpfs_get_nfs4_acl(frame, fsp, &pacl);
+ if (result) {
+ TALLOC_FREE(frame);
+ return result;
+ }
+
+ if (mode & ~(S_IRWXU | S_IRWXG | S_IRWXO)) {
+ DEBUG(2, ("WARNING: cutting extra mode bits %o on %s\n", mode, path));
+ }
+
+ for (smbace=smb_first_ace4(pacl); smbace!=NULL; smbace = smb_next_ace4(smbace)) {
+ SMB_ACE4PROP_T *ace = smb_get_ace4(smbace);
+ uint32_t specid = ace->who.special_id;
+
+ if (ace->flags&SMB_ACE4_ID_SPECIAL &&
+ ace->aceType<=SMB_ACE4_ACCESS_DENIED_ACE_TYPE &&
+ specid <= SMB_ACE4_WHO_EVERYONE) {
+
+ uint32_t newMask;
+
+ if (ace->aceType==SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE)
+ haveAllowEntry[specid] = True;
+
+ /* mode >> 6 for @owner, mode >> 3 for @group,
+ * mode >> 0 for @everyone */
+ newMask = gpfsacl_mask_filter(ace->aceType, ace->aceMask,
+ mode >> ((SMB_ACE4_WHO_EVERYONE - specid) * 3));
+ if (ace->aceMask!=newMask) {
+ DEBUG(10, ("ace changed for %s (%o -> %o) id=%d\n",
+ path, ace->aceMask, newMask, specid));
+ }
+ ace->aceMask = newMask;
+ }
+ }
+
+ /* make sure we have at least ALLOW entries
+ * for all the 3 special ids (@EVERYONE, @OWNER, @GROUP)
+ * - if necessary
+ */
+ for(i = SMB_ACE4_WHO_OWNER; i<=SMB_ACE4_WHO_EVERYONE; i++) {
+ SMB_ACE4PROP_T ace = { 0 };
+
+ if (haveAllowEntry[i]==True)
+ continue;
+
+ ace.aceType = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE;
+ ace.flags |= SMB_ACE4_ID_SPECIAL;
+ ace.who.special_id = i;
+
+ if (i==SMB_ACE4_WHO_GROUP) /* not sure it's necessary... */
+ ace.aceFlags |= SMB_ACE4_IDENTIFIER_GROUP;
+
+ ace.aceMask = gpfsacl_mask_filter(ace.aceType, ace.aceMask,
+ mode >> ((SMB_ACE4_WHO_EVERYONE - i) * 3));
+
+ /* don't add unnecessary aces */
+ if (!ace.aceMask)
+ continue;
+
+ /* we add it to the END - as windows expects allow aces */
+ smb_add_ace4(pacl, &ace);
+ DEBUG(10, ("Added ALLOW ace for %s, mode=%o, id=%d, aceMask=%x\n",
+ path, mode, i, ace.aceMask));
+ }
+
+ /* don't add complementary DENY ACEs here */
+ fake_fsp.fsp_name = synthetic_smb_fname(frame,
+ path,
+ NULL,
+ NULL,
+ fname->twrp,
+ 0);
+ if (fake_fsp.fsp_name == NULL) {
+ errno = ENOMEM;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ /* put the acl */
+ if (gpfsacl_process_smbacl(handle, &fake_fsp, pacl) == False) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0; /* ok for [f]chmod */
+}
+
+static int vfs_gpfs_fchmod(vfs_handle_struct *handle, files_struct *fsp, mode_t mode)
+{
+ SMB_STRUCT_STAT st;
+ int rc;
+
+ rc = SMB_VFS_NEXT_FSTAT(handle, fsp, &st);
+ if (rc != 0) {
+ return -1;
+ }
+
+ /* avoid chmod() if possible, to preserve acls */
+ if ((st.st_ex_mode & ~S_IFMT) == mode) {
+ return 0;
+ }
+
+ rc = gpfsacl_emu_chmod(handle, fsp, mode);
+ if (rc == 1) {
+ return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+ }
+ return rc;
+}
+
+static uint32_t vfs_gpfs_winattrs_to_dosmode(unsigned int winattrs)
+{
+ uint32_t dosmode = 0;
+
+ if (winattrs & GPFS_WINATTR_ARCHIVE){
+ dosmode |= FILE_ATTRIBUTE_ARCHIVE;
+ }
+ if (winattrs & GPFS_WINATTR_HIDDEN){
+ dosmode |= FILE_ATTRIBUTE_HIDDEN;
+ }
+ if (winattrs & GPFS_WINATTR_SYSTEM){
+ dosmode |= FILE_ATTRIBUTE_SYSTEM;
+ }
+ if (winattrs & GPFS_WINATTR_READONLY){
+ dosmode |= FILE_ATTRIBUTE_READONLY;
+ }
+ if (winattrs & GPFS_WINATTR_SPARSE_FILE) {
+ dosmode |= FILE_ATTRIBUTE_SPARSE;
+ }
+ if (winattrs & GPFS_WINATTR_OFFLINE) {
+ dosmode |= FILE_ATTRIBUTE_OFFLINE;
+ }
+
+ return dosmode;
+}
+
+static unsigned int vfs_gpfs_dosmode_to_winattrs(uint32_t dosmode)
+{
+ unsigned int winattrs = 0;
+
+ if (dosmode & FILE_ATTRIBUTE_ARCHIVE){
+ winattrs |= GPFS_WINATTR_ARCHIVE;
+ }
+ if (dosmode & FILE_ATTRIBUTE_HIDDEN){
+ winattrs |= GPFS_WINATTR_HIDDEN;
+ }
+ if (dosmode & FILE_ATTRIBUTE_SYSTEM){
+ winattrs |= GPFS_WINATTR_SYSTEM;
+ }
+ if (dosmode & FILE_ATTRIBUTE_READONLY){
+ winattrs |= GPFS_WINATTR_READONLY;
+ }
+ if (dosmode & FILE_ATTRIBUTE_SPARSE) {
+ winattrs |= GPFS_WINATTR_SPARSE_FILE;
+ }
+ if (dosmode & FILE_ATTRIBUTE_OFFLINE) {
+ winattrs |= GPFS_WINATTR_OFFLINE;
+ }
+
+ return winattrs;
+}
+
+static struct timespec gpfs_timestruc64_to_timespec(struct gpfs_timestruc64 g)
+{
+ return (struct timespec) { .tv_sec = g.tv_sec, .tv_nsec = g.tv_nsec };
+}
+
+static NTSTATUS vfs_gpfs_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ struct gpfs_config_data *config;
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+ const char *p = NULL;
+ struct gpfs_iattr64 iattr = { };
+ unsigned int litemask = 0;
+ struct timespec ts;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (!config->winattr) {
+ return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+ }
+
+ if (fsp->fsp_flags.is_pathref && !config->pathref_ok.gpfs_fstat_x) {
+ if (fsp->fsp_flags.have_proc_fds) {
+ p = sys_proc_fd_path(fd, &buf);
+ } else {
+ p = fsp->fsp_name->base_name;
+ }
+ }
+
+ if (p != NULL) {
+ ret = gpfswrap_stat_x(p, &litemask, &iattr, sizeof(iattr));
+ } else {
+ ret = gpfswrap_fstat_x(fd, &litemask, &iattr, sizeof(iattr));
+ }
+ if (ret == -1 && errno == ENOSYS) {
+ return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+ }
+
+ if (ret == -1 && errno == EACCES) {
+ int saved_errno = 0;
+
+ /*
+ * According to MS-FSA 2.1.5.1.2.1 "Algorithm to Check Access to
+ * an Existing File" FILE_LIST_DIRECTORY on a directory implies
+ * FILE_READ_ATTRIBUTES for directory entries. Being able to
+ * open a file implies FILE_LIST_DIRECTORY.
+ */
+
+ set_effective_capability(DAC_OVERRIDE_CAPABILITY);
+
+ if (p != NULL) {
+ ret = gpfswrap_stat_x(p,
+ &litemask,
+ &iattr,
+ sizeof(iattr));
+ } else {
+ ret = gpfswrap_fstat_x(fd,
+ &litemask,
+ &iattr,
+ sizeof(iattr));
+ }
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+
+ drop_effective_capability(DAC_OVERRIDE_CAPABILITY);
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ }
+
+ if (ret == -1) {
+ DBG_WARNING("Getting winattrs failed for %s: %s\n",
+ fsp->fsp_name->base_name, strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ ts = gpfs_timestruc64_to_timespec(iattr.ia_createtime);
+
+ *dosmode |= vfs_gpfs_winattrs_to_dosmode(iattr.ia_winflags);
+ update_stat_ex_create_time(&fsp->fsp_name->st, ts);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS vfs_gpfs_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ struct gpfs_config_data *config;
+ struct gpfs_winattr attrs = { };
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (!config->winattr) {
+ return SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+ }
+
+ attrs.winAttrs = vfs_gpfs_dosmode_to_winattrs(dosmode);
+
+ if (!fsp->fsp_flags.is_pathref) {
+ ret = gpfswrap_set_winattrs(fsp_get_io_fd(fsp),
+ GPFS_WINATTR_SET_ATTRS, &attrs);
+ if (ret == -1) {
+ DBG_WARNING("Setting winattrs failed for %s: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+ return NT_STATUS_OK;
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ ret = gpfswrap_set_winattrs_path(sys_proc_fd_path(fd, &buf),
+ GPFS_WINATTR_SET_ATTRS,
+ &attrs);
+ if (ret == -1) {
+ DBG_WARNING("Setting winattrs failed for "
+ "[%s][%s]: %s\n",
+ buf.buf,
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ ret = gpfswrap_set_winattrs_path(fsp->fsp_name->base_name,
+ GPFS_WINATTR_SET_ATTRS,
+ &attrs);
+ if (ret == -1) {
+ DBG_WARNING("Setting winattrs failed for [%s]: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int timespec_to_gpfs_time(
+ struct timespec ts, gpfs_timestruc_t *gt, int idx, int *flags)
+{
+ if (is_omit_timespec(&ts)) {
+ return 0;
+ }
+
+ if (ts.tv_sec < 0 || ts.tv_sec > UINT32_MAX) {
+ DBG_NOTICE("GPFS uses 32-bit unsigned timestamps "
+ "and cannot handle %jd.\n",
+ (intmax_t)ts.tv_sec);
+ errno = ERANGE;
+ return -1;
+ }
+
+ *flags |= 1 << idx;
+ gt[idx].tv_sec = ts.tv_sec;
+ gt[idx].tv_nsec = ts.tv_nsec;
+ DBG_DEBUG("Setting GPFS time %d, flags 0x%x\n", idx, *flags);
+
+ return 0;
+}
+
+static int smbd_gpfs_set_times(struct files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ gpfs_timestruc_t gpfs_times[4];
+ int flags = 0;
+ int rc;
+
+ ZERO_ARRAY(gpfs_times);
+ rc = timespec_to_gpfs_time(ft->atime, gpfs_times, 0, &flags);
+ if (rc != 0) {
+ return rc;
+ }
+
+ rc = timespec_to_gpfs_time(ft->mtime, gpfs_times, 1, &flags);
+ if (rc != 0) {
+ return rc;
+ }
+
+ /* No good mapping from LastChangeTime to ctime, not storing */
+ rc = timespec_to_gpfs_time(ft->create_time, gpfs_times, 3, &flags);
+ if (rc != 0) {
+ return rc;
+ }
+
+ if (!flags) {
+ DBG_DEBUG("nothing to do, return to avoid EINVAL\n");
+ return 0;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ rc = gpfswrap_set_times(fsp_get_io_fd(fsp), flags, gpfs_times);
+ if (rc != 0) {
+ DBG_WARNING("gpfs_set_times(%s) failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ }
+ return rc;
+ }
+
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ rc = gpfswrap_set_times_path(sys_proc_fd_path(fd, &buf),
+ flags,
+ gpfs_times);
+ if (rc != 0) {
+ DBG_WARNING("gpfs_set_times_path(%s,%s) failed: %s\n",
+ fsp_str_dbg(fsp),
+ buf.buf,
+ strerror(errno));
+ }
+ return rc;
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+
+ rc = gpfswrap_set_times_path(fsp->fsp_name->base_name,
+ flags,
+ gpfs_times);
+ if (rc != 0) {
+ DBG_WARNING("gpfs_set_times_path(%s) failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ }
+ return rc;
+}
+
+static int vfs_gpfs_fntimes(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+
+ struct gpfs_winattr attrs;
+ int ret;
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct gpfs_config_data,
+ return -1);
+
+ /* Try to use gpfs_set_times if it is enabled and available */
+ if (config->settimes) {
+ return smbd_gpfs_set_times(fsp, ft);
+ }
+
+ DBG_DEBUG("gpfs_set_times() not available or disabled, "
+ "use ntimes and winattr\n");
+
+ ret = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+ if (ret == -1) {
+ /* don't complain if access was denied */
+ if (errno != EPERM && errno != EACCES) {
+ DBG_WARNING("SMB_VFS_NEXT_FNTIMES failed: %s\n",
+ strerror(errno));
+ }
+ return -1;
+ }
+
+ if (is_omit_timespec(&ft->create_time)) {
+ DBG_DEBUG("Create Time is NULL\n");
+ return 0;
+ }
+
+ if (!config->winattr) {
+ return 0;
+ }
+
+ attrs.winAttrs = 0;
+ attrs.creationTime.tv_sec = ft->create_time.tv_sec;
+ attrs.creationTime.tv_nsec = ft->create_time.tv_nsec;
+
+ if (!fsp->fsp_flags.is_pathref) {
+ ret = gpfswrap_set_winattrs(fsp_get_io_fd(fsp),
+ GPFS_WINATTR_SET_CREATION_TIME,
+ &attrs);
+ if (ret == -1 && errno != ENOSYS) {
+ DBG_WARNING("Set GPFS ntimes failed %d\n", ret);
+ return -1;
+ }
+ return ret;
+ }
+
+ if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ ret = gpfswrap_set_winattrs_path(
+ sys_proc_fd_path(fd, &buf),
+ GPFS_WINATTR_SET_CREATION_TIME,
+ &attrs);
+ if (ret == -1 && errno != ENOSYS) {
+ DBG_WARNING("Set GPFS ntimes failed %d\n", ret);
+ return -1;
+ }
+ return ret;
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ ret = gpfswrap_set_winattrs_path(fsp->fsp_name->base_name,
+ GPFS_WINATTR_SET_CREATION_TIME,
+ &attrs);
+ if (ret == -1 && errno != ENOSYS) {
+ DBG_WARNING("Set GPFS ntimes failed %d\n", ret);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int vfs_gpfs_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, uint32_t mode,
+ off_t offset, off_t len)
+{
+ if (mode == (VFS_FALLOCATE_FL_PUNCH_HOLE|VFS_FALLOCATE_FL_KEEP_SIZE) &&
+ !fsp->fsp_flags.is_sparse &&
+ lp_strict_allocate(SNUM(fsp->conn))) {
+ /*
+ * This is from a ZERO_DATA request on a non-sparse
+ * file. GPFS does not support FL_KEEP_SIZE and thus
+ * cannot fill the whole again in the subsequent
+ * fallocate(FL_KEEP_SIZE). Deny this FL_PUNCH_HOLE
+ * call to not end up with a hole in a non-sparse
+ * file.
+ */
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len);
+}
+
+static int vfs_gpfs_ftruncate(vfs_handle_struct *handle, files_struct *fsp,
+ off_t len)
+{
+ int result;
+ struct gpfs_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ if (!config->ftruncate) {
+ return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len);
+ }
+
+ result = gpfswrap_ftruncate(fsp_get_io_fd(fsp), len);
+ if ((result == -1) && (errno == ENOSYS)) {
+ return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len);
+ }
+ return result;
+}
+
+static bool vfs_gpfs_is_offline(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ struct gpfs_winattr attrs;
+ struct gpfs_config_data *config;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return false);
+
+ if (!config->winattr) {
+ return false;
+ }
+
+ ret = gpfswrap_get_winattrs(fsp_get_pathref_fd(fsp), &attrs);
+ if (ret == -1) {
+ return false;
+ }
+
+ if ((attrs.winAttrs & GPFS_WINATTR_OFFLINE) != 0) {
+ DBG_DEBUG("%s is offline\n", fsp_str_dbg(fsp));
+ return true;
+ }
+
+ DBG_DEBUG("%s is online\n", fsp_str_dbg(fsp));
+ return false;
+}
+
+static bool vfs_gpfs_fsp_is_offline(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ struct gpfs_fsp_extension *ext;
+
+ ext = VFS_FETCH_FSP_EXTENSION(handle, fsp);
+ if (ext == NULL) {
+ /*
+ * Something bad happened, always ask.
+ */
+ return vfs_gpfs_is_offline(handle, fsp,
+ &fsp->fsp_name->st);
+ }
+
+ if (ext->offline) {
+ /*
+ * As long as it's offline, ask.
+ */
+ ext->offline = vfs_gpfs_is_offline(handle, fsp,
+ &fsp->fsp_name->st);
+ }
+
+ return ext->offline;
+}
+
+static bool vfs_gpfs_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ return vfs_gpfs_fsp_is_offline(handle, fsp);
+}
+
+static ssize_t vfs_gpfs_sendfile(vfs_handle_struct *handle, int tofd,
+ files_struct *fsp, const DATA_BLOB *hdr,
+ off_t offset, size_t n)
+{
+ if (vfs_gpfs_fsp_is_offline(handle, fsp)) {
+ errno = ENOSYS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, hdr, offset, n);
+}
+
+#ifdef O_PATH
+static int vfs_gpfs_check_pathref_fstat_x(struct gpfs_config_data *config,
+ struct connection_struct *conn)
+{
+ struct gpfs_iattr64 iattr = {0};
+ unsigned int litemask = 0;
+ int saved_errno;
+ int fd;
+ int ret;
+
+ fd = open(conn->connectpath, O_PATH);
+ if (fd == -1) {
+ DBG_ERR("openat() of share with O_PATH failed: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ ret = gpfswrap_fstat_x(fd, &litemask, &iattr, sizeof(iattr));
+ if (ret == 0) {
+ close(fd);
+ config->pathref_ok.gpfs_fstat_x = true;
+ return 0;
+ }
+
+ saved_errno = errno;
+ ret = close(fd);
+ if (ret != 0) {
+ DBG_ERR("close failed: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (saved_errno != EBADF) {
+ DBG_ERR("gpfswrap_fstat_x() of O_PATH handle failed: %s\n",
+ strerror(saved_errno));
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+static int vfs_gpfs_check_pathref(struct gpfs_config_data *config,
+ struct connection_struct *conn)
+{
+#ifndef O_PATH
+ /*
+ * This code path leaves all struct gpfs_config_data.pathref_ok members
+ * initialized to false.
+ */
+ return 0;
+#else
+ int ret;
+
+ ret = vfs_gpfs_check_pathref_fstat_x(config, conn);
+ if (ret != 0) {
+ return -1;
+ }
+
+ return 0;
+#endif
+}
+
+static int vfs_gpfs_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct gpfs_config_data *config;
+ int ret;
+ bool check_fstype;
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (IS_IPC(handle->conn)) {
+ return 0;
+ }
+
+ ret = gpfswrap_init();
+ if (ret < 0) {
+ DBG_ERR("Could not load GPFS library.\n");
+ return ret;
+ }
+
+ ret = gpfswrap_lib_init(0);
+ if (ret < 0) {
+ DBG_ERR("Could not open GPFS device file: %s\n",
+ strerror(errno));
+ return ret;
+ }
+
+ ret = gpfswrap_register_cifs_export();
+ if (ret < 0) {
+ DBG_ERR("Failed to register with GPFS: %s\n", strerror(errno));
+ return ret;
+ }
+
+ config = talloc_zero(handle->conn, struct gpfs_config_data);
+ if (!config) {
+ DEBUG(0, ("talloc_zero() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ check_fstype = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "check_fstype", true);
+
+ if (check_fstype) {
+ const char *connectpath = handle->conn->connectpath;
+ struct statfs buf = { 0 };
+
+ ret = statfs(connectpath, &buf);
+ if (ret != 0) {
+ DBG_ERR("statfs failed for share %s at path %s: %s\n",
+ service, connectpath, strerror(errno));
+ TALLOC_FREE(config);
+ return ret;
+ }
+
+ if (buf.f_type != GPFS_SUPER_MAGIC) {
+ DBG_ERR("SMB share %s, path %s not in GPFS file system."
+ " statfs magic: 0x%jx\n",
+ service,
+ connectpath,
+ (uintmax_t)buf.f_type);
+ errno = EINVAL;
+ TALLOC_FREE(config);
+ return -1;
+ }
+ }
+
+ ret = smbacl4_get_vfs_params(handle->conn, &config->nfs4_params);
+ if (ret < 0) {
+ TALLOC_FREE(config);
+ return ret;
+ }
+
+ config->sharemodes = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "sharemodes", true);
+
+ config->leases = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "leases", true);
+
+ config->hsm = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "hsm", false);
+
+ config->syncio = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "syncio", false);
+
+ config->winattr = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "winattr", false);
+
+ config->ftruncate = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "ftruncate", true);
+
+ config->getrealfilename = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "getrealfilename", true);
+
+ config->dfreequota = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "dfreequota", false);
+
+ config->acl = lp_parm_bool(SNUM(handle->conn), "gpfs", "acl", true);
+
+ config->settimes = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "settimes", true);
+ config->recalls = lp_parm_bool(SNUM(handle->conn), "gpfs",
+ "recalls", true);
+
+ ret = vfs_gpfs_check_pathref(config, handle->conn);
+ if (ret != 0) {
+ DBG_ERR("vfs_gpfs_check_pathref() on [%s] failed\n",
+ handle->conn->connectpath);
+ TALLOC_FREE(config);
+ return -1;
+ }
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct gpfs_config_data,
+ return -1);
+
+ if (config->leases) {
+ /*
+ * GPFS lease code is based on kernel oplock code
+ * so make sure it is turned on
+ */
+ if (!lp_kernel_oplocks(SNUM(handle->conn))) {
+ DEBUG(5, ("Enabling kernel oplocks for "
+ "gpfs:leases to work\n"));
+ lp_do_parameter(SNUM(handle->conn), "kernel oplocks",
+ "true");
+ }
+
+ /*
+ * as the kernel does not properly support Level II oplocks
+ * and GPFS leases code is based on kernel infrastructure, we
+ * need to turn off Level II oplocks if gpfs:leases is enabled
+ */
+ if (lp_level2_oplocks(SNUM(handle->conn))) {
+ DEBUG(5, ("gpfs:leases are enabled, disabling "
+ "Level II oplocks\n"));
+ lp_do_parameter(SNUM(handle->conn), "level2 oplocks",
+ "false");
+ }
+ }
+
+ /*
+ * Unless we have an async implementation of get_dos_attributes turn
+ * this off.
+ */
+ lp_do_parameter(SNUM(handle->conn), "smbd async dosmode", "false");
+
+ return 0;
+}
+
+static int get_gpfs_quota(const char *pathname, int type, int id,
+ struct gpfs_quotaInfo *qi)
+{
+ int ret;
+
+ ret = gpfswrap_quotactl(pathname, GPFS_QCMD(Q_GETQUOTA, type), id, qi);
+
+ if (ret) {
+ if (errno == GPFS_E_NO_QUOTA_INST) {
+ DEBUG(10, ("Quotas disabled on GPFS filesystem.\n"));
+ } else if (errno != ENOSYS) {
+ DEBUG(0, ("Get quota failed, type %d, id, %d, "
+ "errno %d.\n", type, id, errno));
+ }
+
+ return ret;
+ }
+
+ DEBUG(10, ("quota type %d, id %d, blk u:%lld h:%lld s:%lld gt:%u\n",
+ type, id, qi->blockUsage, qi->blockHardLimit,
+ qi->blockSoftLimit, qi->blockGraceTime));
+
+ return ret;
+}
+
+static void vfs_gpfs_disk_free_quota(struct gpfs_quotaInfo qi, time_t cur_time,
+ uint64_t *dfree, uint64_t *dsize)
+{
+ uint64_t usage, limit;
+
+ /*
+ * The quota reporting is done in units of 1024 byte blocks, but
+ * sys_fsusage uses units of 512 byte blocks, adjust the block number
+ * accordingly. Also filter possibly negative usage counts from gpfs.
+ */
+ usage = qi.blockUsage < 0 ? 0 : (uint64_t)qi.blockUsage * 2;
+ limit = (uint64_t)qi.blockHardLimit * 2;
+
+ /*
+ * When the grace time for the exceeded soft block quota has been
+ * exceeded, the soft block quota becomes an additional hard limit.
+ */
+ if (qi.blockSoftLimit &&
+ qi.blockGraceTime && cur_time > qi.blockGraceTime) {
+ /* report disk as full */
+ *dfree = 0;
+ *dsize = MIN(*dsize, usage);
+ }
+
+ if (!qi.blockHardLimit)
+ return;
+
+ if (usage >= limit) {
+ /* report disk as full */
+ *dfree = 0;
+ *dsize = MIN(*dsize, usage);
+
+ } else {
+ /* limit has not been reached, determine "free space" */
+ *dfree = MIN(*dfree, limit - usage);
+ *dsize = MIN(*dsize, limit);
+ }
+}
+
+static uint64_t vfs_gpfs_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ struct security_unix_token *utok;
+ struct gpfs_quotaInfo qi_user = { 0 }, qi_group = { 0 };
+ struct gpfs_config_data *config;
+ int err;
+ time_t cur_time;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct gpfs_config_data,
+ return (uint64_t)-1);
+ if (!config->dfreequota) {
+ return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname,
+ bsize, dfree, dsize);
+ }
+
+ err = sys_fsusage(smb_fname->base_name, dfree, dsize);
+ if (err) {
+ DEBUG (0, ("Could not get fs usage, errno %d\n", errno));
+ return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname,
+ bsize, dfree, dsize);
+ }
+
+ /* sys_fsusage returns units of 512 bytes */
+ *bsize = 512;
+
+ DEBUG(10, ("fs dfree %llu, dsize %llu\n",
+ (unsigned long long)*dfree, (unsigned long long)*dsize));
+
+ utok = handle->conn->session_info->unix_token;
+
+ err = get_gpfs_quota(smb_fname->base_name,
+ GPFS_USRQUOTA, utok->uid, &qi_user);
+ if (err) {
+ return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname,
+ bsize, dfree, dsize);
+ }
+
+ /*
+ * If new files created under this folder get this folder's
+ * GID, then available space is governed by the quota of the
+ * folder's GID, not the primary group of the creating user.
+ */
+ if (VALID_STAT(smb_fname->st) &&
+ S_ISDIR(smb_fname->st.st_ex_mode) &&
+ smb_fname->st.st_ex_mode & S_ISGID) {
+ become_root();
+ err = get_gpfs_quota(smb_fname->base_name, GPFS_GRPQUOTA,
+ smb_fname->st.st_ex_gid, &qi_group);
+ unbecome_root();
+
+ } else {
+ err = get_gpfs_quota(smb_fname->base_name, GPFS_GRPQUOTA,
+ utok->gid, &qi_group);
+ }
+
+ if (err) {
+ return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname,
+ bsize, dfree, dsize);
+ }
+
+ cur_time = time(NULL);
+
+ /* Adjust free space and size according to quota limits. */
+ vfs_gpfs_disk_free_quota(qi_user, cur_time, dfree, dsize);
+ vfs_gpfs_disk_free_quota(qi_group, cur_time, dfree, dsize);
+
+ return *dfree / 2;
+}
+
+static int vfs_gpfs_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq)
+{
+ switch(qtype) {
+ /*
+ * User/group quota are being used for disk-free
+ * determination, which in this module is done directly
+ * by the disk-free function. It's important that this
+ * module does not return wrong quota values by mistake,
+ * which would modify the correct values set by disk-free.
+ * User/group quota are also being used for processing
+ * NT_TRANSACT_GET_USER_QUOTA in smb1 protocol, which is
+ * currently not supported by this module.
+ */
+ case SMB_USER_QUOTA_TYPE:
+ case SMB_GROUP_QUOTA_TYPE:
+ errno = ENOSYS;
+ return -1;
+ default:
+ return SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname,
+ qtype, id, dq);
+ }
+}
+
+static uint32_t vfs_gpfs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ struct gpfs_config_data *config;
+ uint32_t next;
+
+ next = SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res);
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return next);
+
+ if (config->hsm) {
+ next |= FILE_SUPPORTS_REMOTE_STORAGE;
+ }
+ return next;
+}
+
+static int vfs_gpfs_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how)
+{
+ struct vfs_open_how how = *_how;
+ struct gpfs_config_data *config = NULL;
+ struct gpfs_fsp_extension *ext = NULL;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct gpfs_config_data,
+ return -1);
+
+ if (config->hsm && !config->recalls &&
+ !fsp->fsp_flags.is_pathref &&
+ vfs_gpfs_fsp_is_offline(handle, fsp))
+ {
+ DBG_DEBUG("Refusing access to offline file %s\n",
+ fsp_str_dbg(fsp));
+ errno = EACCES;
+ return -1;
+ }
+
+ if (config->syncio) {
+ how.flags |= O_SYNC;
+ }
+
+ ext = VFS_ADD_FSP_EXTENSION(handle, fsp, struct gpfs_fsp_extension,
+ NULL);
+ if (ext == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * Assume the file is offline until gpfs tells us it's online.
+ */
+ *ext = (struct gpfs_fsp_extension) { .offline = true };
+
+ ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, &how);
+ if (ret == -1) {
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ }
+ return ret;
+}
+
+static ssize_t vfs_gpfs_pread(vfs_handle_struct *handle, files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ ssize_t ret;
+ bool was_offline;
+
+ was_offline = vfs_gpfs_fsp_is_offline(handle, fsp);
+
+ ret = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+
+ if ((ret != -1) && was_offline) {
+ notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ return ret;
+}
+
+struct vfs_gpfs_pread_state {
+ struct files_struct *fsp;
+ ssize_t ret;
+ bool was_offline;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void vfs_gpfs_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *vfs_gpfs_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data, size_t n,
+ off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct vfs_gpfs_pread_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct vfs_gpfs_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->was_offline = vfs_gpfs_fsp_is_offline(handle, fsp);
+ state->fsp = fsp;
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_gpfs_pread_done, req);
+ return req;
+}
+
+static void vfs_gpfs_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_gpfs_pread_state *state = tevent_req_data(
+ req, struct vfs_gpfs_pread_state);
+
+ state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t vfs_gpfs_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_gpfs_pread_state *state = tevent_req_data(
+ req, struct vfs_gpfs_pread_state);
+ struct files_struct *fsp = state->fsp;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+
+ if ((state->ret != -1) && state->was_offline) {
+ DEBUG(10, ("sending notify\n"));
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ return state->ret;
+}
+
+static ssize_t vfs_gpfs_pwrite(vfs_handle_struct *handle, files_struct *fsp,
+ const void *data, size_t n, off_t offset)
+{
+ ssize_t ret;
+ bool was_offline;
+
+ was_offline = vfs_gpfs_fsp_is_offline(handle, fsp);
+
+ ret = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+
+ if ((ret != -1) && was_offline) {
+ notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ return ret;
+}
+
+struct vfs_gpfs_pwrite_state {
+ struct files_struct *fsp;
+ ssize_t ret;
+ bool was_offline;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void vfs_gpfs_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *vfs_gpfs_pwrite_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data, size_t n,
+ off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct vfs_gpfs_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct vfs_gpfs_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->was_offline = vfs_gpfs_fsp_is_offline(handle, fsp);
+ state->fsp = fsp;
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, vfs_gpfs_pwrite_done, req);
+ return req;
+}
+
+static void vfs_gpfs_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct vfs_gpfs_pwrite_state *state = tevent_req_data(
+ req, struct vfs_gpfs_pwrite_state);
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t vfs_gpfs_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_gpfs_pwrite_state *state = tevent_req_data(
+ req, struct vfs_gpfs_pwrite_state);
+ struct files_struct *fsp = state->fsp;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+
+ if ((state->ret != -1) && state->was_offline) {
+ DEBUG(10, ("sending notify\n"));
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ return state->ret;
+}
+
+
+static struct vfs_fn_pointers vfs_gpfs_fns = {
+ .connect_fn = vfs_gpfs_connect,
+ .disk_free_fn = vfs_gpfs_disk_free,
+ .get_quota_fn = vfs_gpfs_get_quota,
+ .fs_capabilities_fn = vfs_gpfs_capabilities,
+ .filesystem_sharemode_fn = vfs_gpfs_filesystem_sharemode,
+ .linux_setlease_fn = vfs_gpfs_setlease,
+ .get_real_filename_at_fn = vfs_gpfs_get_real_filename_at,
+ .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv,
+ .fget_dos_attributes_fn = vfs_gpfs_fget_dos_attributes,
+ .fset_dos_attributes_fn = vfs_gpfs_fset_dos_attributes,
+ .fget_nt_acl_fn = gpfsacl_fget_nt_acl,
+ .fset_nt_acl_fn = gpfsacl_fset_nt_acl,
+ .sys_acl_get_fd_fn = gpfsacl_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = gpfsacl_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = gpfsacl_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = gpfsacl_sys_acl_delete_def_fd,
+ .fchmod_fn = vfs_gpfs_fchmod,
+ .close_fn = vfs_gpfs_close,
+ .stat_fn = nfs4_acl_stat,
+ .fstat_fn = nfs4_acl_fstat,
+ .lstat_fn = nfs4_acl_lstat,
+ .fstatat_fn = nfs4_acl_fstatat,
+ .fntimes_fn = vfs_gpfs_fntimes,
+ .aio_force_fn = vfs_gpfs_aio_force,
+ .sendfile_fn = vfs_gpfs_sendfile,
+ .fallocate_fn = vfs_gpfs_fallocate,
+ .openat_fn = vfs_gpfs_openat,
+ .pread_fn = vfs_gpfs_pread,
+ .pread_send_fn = vfs_gpfs_pread_send,
+ .pread_recv_fn = vfs_gpfs_pread_recv,
+ .pwrite_fn = vfs_gpfs_pwrite,
+ .pwrite_send_fn = vfs_gpfs_pwrite_send,
+ .pwrite_recv_fn = vfs_gpfs_pwrite_recv,
+ .ftruncate_fn = vfs_gpfs_ftruncate
+};
+
+static_decl_vfs;
+NTSTATUS vfs_gpfs_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "gpfs",
+ &vfs_gpfs_fns);
+}
diff --git a/source3/modules/vfs_hpuxacl.c b/source3/modules/vfs_hpuxacl.c
new file mode 100644
index 0000000..31903a1
--- /dev/null
+++ b/source3/modules/vfs_hpuxacl.c
@@ -0,0 +1,1174 @@
+/*
+ * Unix SMB/Netbios implementation.
+ * VFS module to get and set HP-UX ACLs
+ * Copyright (C) Michael Adam 2006,2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module supports JFS (POSIX) ACLs on VxFS (Veritas * Filesystem).
+ * These are available on HP-UX 11.00 if JFS 3.3 is installed.
+ * On HP-UX 11i (11.11 and above) these ACLs are supported out of
+ * the box.
+ *
+ * There is another form of ACLs on HFS. These ACLs have a
+ * completely different API and their own set of userland tools.
+ * Since HFS seems to be considered deprecated, HFS acls
+ * are not supported. (They could be supported through a separate
+ * vfs-module if there is demand.)
+ */
+
+/* =================================================================
+ * NOTE:
+ *
+ * The original hpux-acl code in lib/sysacls.c was based upon the
+ * solaris acl code in the same file. Now for the new modularized
+ * acl implementation, I have taken the code from vfs_solarisacls.c
+ * and did similar adaptations as were done before, essentially
+ * reusing the original internal aclsort functions.
+ * The check for the presence of the acl() call has been adopted, and
+ * a check for the presence of the aclsort() call has been added.
+ *
+ * Michael Adam <obnox@samba.org>
+ *
+ * ================================================================= */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "modules/vfs_hpuxacl.h"
+
+
+/*
+ * including standard header <sys/aclv.h>
+ *
+ * included here as a quick hack for the special HP-UX-situation:
+ *
+ * The problem is that, on HP-UX, jfs/posix acls are
+ * defined in <sys/aclv.h>, while the deprecated hfs acls
+ * are defined inside <sys/acl.h>.
+ *
+ */
+/* GROUP is defined somewhere else so undef it here... */
+#undef GROUP
+#include <sys/aclv.h>
+/* dl.h: needed to check for acl call via shl_findsym */
+#include <dl.h>
+
+typedef struct acl HPUX_ACE_T;
+typedef struct acl *HPUX_ACL_T;
+typedef int HPUX_ACL_TAG_T; /* the type of an ACL entry */
+typedef ushort HPUX_PERM_T;
+
+/* Structure to capture the count for each type of ACE.
+ * (for hpux_internal_aclsort */
+struct hpux_acl_types {
+ int n_user;
+ int n_def_user;
+ int n_user_obj;
+ int n_def_user_obj;
+
+ int n_group;
+ int n_def_group;
+ int n_group_obj;
+ int n_def_group_obj;
+
+ int n_other;
+ int n_other_obj;
+ int n_def_other_obj;
+
+ int n_class_obj;
+ int n_def_class_obj;
+
+ int n_illegal_obj;
+};
+
+/* for convenience: check if hpux acl entry is a default entry? */
+#define _IS_DEFAULT(ace) ((ace).a_type & ACL_DEFAULT)
+#define _IS_OF_TYPE(ace, type) ( \
+ (((type) == SMB_ACL_TYPE_ACCESS) && !_IS_DEFAULT(ace)) \
+ || \
+ (((type) == SMB_ACL_TYPE_DEFAULT) && _IS_DEFAULT(ace)) \
+)
+
+
+/* prototypes for private functions */
+
+static HPUX_ACL_T hpux_acl_init(int count);
+static bool smb_acl_to_hpux_acl(SMB_ACL_T smb_acl,
+ HPUX_ACL_T *solariacl, int *count,
+ SMB_ACL_TYPE_T type);
+static SMB_ACL_T hpux_acl_to_smb_acl(HPUX_ACL_T hpuxacl, int count,
+ SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx);
+static HPUX_ACL_TAG_T smb_tag_to_hpux_tag(SMB_ACL_TAG_T smb_tag);
+static SMB_ACL_TAG_T hpux_tag_to_smb_tag(HPUX_ACL_TAG_T hpux_tag);
+static bool hpux_add_to_acl(HPUX_ACL_T *hpux_acl, int *count,
+ HPUX_ACL_T add_acl, int add_count, SMB_ACL_TYPE_T type);
+static bool hpux_acl_get_file(const char *name, HPUX_ACL_T *hpuxacl,
+ int *count);
+static SMB_ACL_PERM_T hpux_perm_to_smb_perm(const HPUX_PERM_T perm);
+static HPUX_PERM_T smb_perm_to_hpux_perm(const SMB_ACL_PERM_T perm);
+#if 0
+static bool hpux_acl_check(HPUX_ACL_T hpux_acl, int count);
+#endif
+/* aclsort (internal) and helpers: */
+static bool hpux_acl_sort(HPUX_ACL_T acl, int count);
+static int hpux_internal_aclsort(int acl_count, int calclass, HPUX_ACL_T aclp);
+static void hpux_count_obj(int acl_count, HPUX_ACL_T aclp,
+ struct hpux_acl_types *acl_type_count);
+static void hpux_swap_acl_entries(HPUX_ACE_T *aclp0, HPUX_ACE_T *aclp1);
+static bool hpux_prohibited_duplicate_type(int acl_type);
+
+static bool hpux_acl_call_present(void);
+static bool hpux_aclsort_call_present(void);
+
+
+/* public functions - the api */
+
+static SMB_ACL_T hpuxacl_sys_acl_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result = NULL;
+ int count;
+ HPUX_ACL_T hpux_acl = NULL;
+ const char *path_p = smb_fname->base_name;
+
+ DEBUG(10, ("hpuxacl_sys_acl_get_file called for file '%s'.\n",
+ path_p));
+
+ if(hpux_acl_call_present() == False) {
+ /* Looks like we don't have the acl() system call on HPUX.
+ * May be the system doesn't have the latest version of JFS.
+ */
+ goto done;
+ }
+
+ if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) {
+ DEBUG(10, ("invalid SMB_ACL_TYPE given (%d)\n", type));
+ errno = EINVAL;
+ goto done;
+ }
+
+ DEBUGADD(10, ("getting %s acl\n",
+ ((type == SMB_ACL_TYPE_ACCESS) ? "access" : "default")));
+
+ if (!hpux_acl_get_file(path_p, &hpux_acl, &count)) {
+ goto done;
+ }
+ result = hpux_acl_to_smb_acl(hpux_acl, count, type, mem_ctx);
+ if (result == NULL) {
+ DEBUG(10, ("conversion hpux_acl -> smb_acl failed (%s).\n",
+ strerror(errno)));
+ }
+
+ done:
+ DEBUG(10, ("hpuxacl_sys_acl_get_file %s.\n",
+ ((result == NULL) ? "failed" : "succeeded" )));
+ SAFE_FREE(hpux_acl);
+ return result;
+}
+
+
+/*
+ * get the access ACL of a file referred to by a fd
+ */
+SMB_ACL_T hpuxacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ /*
+ * HPUX doesn't have the facl call. Fake it using the path.... JRA.
+ */
+ /*
+ * We know we're in the same conn context. So we
+ * can use the relative path.
+ */
+ DEBUG(10, ("redirecting call of hpuxacl_sys_acl_get_fd to "
+ "hpuxacl_sys_acl_get_file (no facl syscall on HPUX).\n"));
+
+ return hpuxacl_sys_acl_get_file(handle,
+ fsp->fsp_name->base_name,
+ type,
+ mem_ctx);
+}
+
+
+int hpuxacl_sys_acl_set_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname_in,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ int ret = -1;
+ HPUX_ACL_T hpux_acl = NULL;
+ int count;
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ DEBUG(10, ("hpuxacl_sys_acl_set_file called for file '%s'\n",
+ name));
+
+ smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
+ if (smb_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if(hpux_acl_call_present() == False) {
+ /* Looks like we don't have the acl() system call on HPUX.
+ * May be the system doesn't have the latest version of JFS.
+ */
+ goto done;
+ }
+
+ if ((type != SMB_ACL_TYPE_ACCESS) && (type != SMB_ACL_TYPE_DEFAULT)) {
+ errno = EINVAL;
+ DEBUG(10, ("invalid smb acl type given (%d).\n", type));
+ goto done;
+ }
+ DEBUGADD(10, ("setting %s acl\n",
+ ((type == SMB_ACL_TYPE_ACCESS) ? "access" : "default")));
+
+ if(!smb_acl_to_hpux_acl(theacl, &hpux_acl, &count, type)) {
+ DEBUG(10, ("conversion smb_acl -> hpux_acl failed (%s).\n",
+ strerror(errno)));
+ goto done;
+ }
+
+ /*
+ * We can directly use SMB_VFS_STAT here, as if this was a
+ * POSIX call on a symlink, we've already refused it.
+ * For a Windows acl mapped call on a symlink, we want to follow
+ * it.
+ */
+ ret = SMB_VFS_STAT(handle->conn, smb_fname);
+ if (ret != 0) {
+ DEBUG(10, ("Error in stat call: %s\n", strerror(errno)));
+ goto done;
+ }
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ /*
+ * if the file is a directory, there is extra work to do:
+ * since the hpux acl call stores both the access acl and
+ * the default acl as provided, we have to get the acl part
+ * that has _not_ been specified in "type" from the file first
+ * and concatenate it with the acl provided.
+ */
+ HPUX_ACL_T other_acl;
+ int other_count;
+ SMB_ACL_TYPE_T other_type;
+
+ other_type = (type == SMB_ACL_TYPE_ACCESS)
+ ? SMB_ACL_TYPE_DEFAULT
+ : SMB_ACL_TYPE_ACCESS;
+ DEBUGADD(10, ("getting acl from filesystem\n"));
+ if (!hpux_acl_get_file(smb_fname->base_name, &other_acl,
+ &other_count)) {
+ DEBUG(10, ("error getting acl from directory\n"));
+ goto done;
+ }
+ DEBUG(10, ("adding %s part of fs acl to given acl\n",
+ ((other_type == SMB_ACL_TYPE_ACCESS)
+ ? "access"
+ : "default")));
+ if (!hpux_add_to_acl(&hpux_acl, &count, other_acl,
+ other_count, other_type))
+ {
+ DEBUG(10, ("error adding other acl.\n"));
+ SAFE_FREE(other_acl);
+ goto done;
+ }
+ SAFE_FREE(other_acl);
+ }
+ else if (type != SMB_ACL_TYPE_ACCESS) {
+ errno = EINVAL;
+ goto done;
+ }
+
+ if (!hpux_acl_sort(hpux_acl, count)) {
+ DEBUG(10, ("resulting acl is not valid!\n"));
+ goto done;
+ }
+ DEBUG(10, ("resulting acl is valid.\n"));
+
+ ret = acl(discard_const_p(char, smb_fname->base_name), ACL_SET, count,
+ hpux_acl);
+ if (ret != 0) {
+ DEBUG(0, ("ERROR calling acl: %s\n", strerror(errno)));
+ }
+
+ done:
+ DEBUG(10, ("hpuxacl_sys_acl_set_file %s.\n",
+ ((ret != 0) ? "failed" : "succeeded")));
+ TALLOC_FREE(smb_fname);
+ SAFE_FREE(hpux_acl);
+ return ret;
+}
+
+/*
+ * set the access ACL on the file referred to by a fd
+ */
+int hpuxacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ /*
+ * HPUX doesn't have the facl call. Fake it using the path.... JRA.
+ */
+ /*
+ * We know we're in the same conn context. So we
+ * can use the relative path.
+ */
+ DEBUG(10, ("redirecting call of hpuxacl_sys_acl_set_fd to "
+ "hpuxacl_sys_acl_set_file (no facl syscall on HPUX)\n"));
+
+ return hpuxacl_sys_acl_set_file(handle,
+ fsp->fsp_name->base_name,
+ type, theacl);
+}
+
+/*
+ * delete the default ACL of a directory
+ *
+ * This is achieved by fetching the access ACL and rewriting it
+ * directly, via the hpux system call: the ACL_SET call on
+ * directories writes both the access and the default ACL as provided.
+ *
+ * XXX: posix acl_delete_def_file returns an error if
+ * the file referred to by path is not a directory.
+ * this function does not complain but the actions
+ * have no effect on a file other than a directory.
+ * But sys_acl_delete_default_file is only called in
+ * smbd/posixacls.c after having checked that the file
+ * is a directory, anyways. So implementing the extra
+ * check is considered unnecessary. --- Agreed? XXX
+ */
+int hpuxacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ SMB_ACL_T smb_acl;
+ int ret = -1;
+ HPUX_ACL_T hpux_acl;
+ int count;
+
+ DBG_DEBUG("entering hpuxacl_sys_acl_delete_def_fd.\n");
+
+ smb_acl = hpuxacl_sys_acl_get_file(handle, fsp->fsp_name->base_name,
+ SMB_ACL_TYPE_ACCESS);
+ if (smb_acl == NULL) {
+ DBG_DEBUG("getting file acl failed!\n");
+ goto done;
+ }
+ if (!smb_acl_to_hpux_acl(smb_acl, &hpux_acl, &count,
+ SMB_ACL_TYPE_ACCESS))
+ {
+ DBG_DEBUG("conversion smb_acl -> hpux_acl failed.\n");
+ goto done;
+ }
+ if (!hpux_acl_sort(hpux_acl, count)) {
+ DBG_DEBUG("resulting acl is not valid!\n");
+ goto done;
+ }
+ ret = acl(discard_const_p(char, fsp->fsp_name->base_name),
+ ACL_SET, count, hpux_acl);
+ if (ret != 0) {
+ DBG_DEBUG("settinge file acl failed!\n");
+ }
+
+ done:
+ DBG_DEBUG("hpuxacl_sys_acl_delete_def_fd %s.\n",
+ ((ret != 0) ? "failed" : "succeeded" ));
+ TALLOC_FREE(smb_acl);
+ return ret;
+}
+
+/*
+ * private functions
+ */
+
+static HPUX_ACL_T hpux_acl_init(int count)
+{
+ HPUX_ACL_T hpux_acl =
+ (HPUX_ACL_T)SMB_MALLOC(sizeof(HPUX_ACE_T) * count);
+ if (hpux_acl == NULL) {
+ errno = ENOMEM;
+ }
+ return hpux_acl;
+}
+
+/*
+ * Convert the SMB acl to the ACCESS or DEFAULT part of a
+ * hpux ACL, as desired.
+ */
+static bool smb_acl_to_hpux_acl(SMB_ACL_T smb_acl,
+ HPUX_ACL_T *hpux_acl, int *count,
+ SMB_ACL_TYPE_T type)
+{
+ bool ret = False;
+ int i;
+ int check_which, check_rc;
+
+ DEBUG(10, ("entering smb_acl_to_hpux_acl\n"));
+
+ *hpux_acl = NULL;
+ *count = 0;
+
+ for (i = 0; i < smb_acl->count; i++) {
+ const struct smb_acl_entry *smb_entry = &(smb_acl->acl[i]);
+ HPUX_ACE_T hpux_entry;
+
+ ZERO_STRUCT(hpux_entry);
+
+ hpux_entry.a_type = smb_tag_to_hpux_tag(smb_entry->a_type);
+ if (hpux_entry.a_type == 0) {
+ DEBUG(10, ("smb_tag to hpux_tag failed\n"));
+ goto fail;
+ }
+ switch(hpux_entry.a_type) {
+ case USER:
+ DEBUG(10, ("got tag type USER with uid %d\n",
+ smb_entry->info.user.uid));
+ hpux_entry.a_id = (uid_t)smb_entry->info.user.uid;
+ break;
+ case GROUP:
+ DEBUG(10, ("got tag type GROUP with gid %d\n",
+ smb_entry->info.group.gid));
+ hpux_entry.a_id = (uid_t)smb_entry->info.group.gid;
+ break;
+ default:
+ break;
+ }
+ if (type == SMB_ACL_TYPE_DEFAULT) {
+ DEBUG(10, ("adding default bit to hpux ace\n"));
+ hpux_entry.a_type |= ACL_DEFAULT;
+ }
+
+ hpux_entry.a_perm =
+ smb_perm_to_hpux_perm(smb_entry->a_perm);
+ DEBUG(10, ("assembled the following hpux ace:\n"));
+ DEBUGADD(10, (" - type: 0x%04x\n", hpux_entry.a_type));
+ DEBUGADD(10, (" - id: %d\n", hpux_entry.a_id));
+ DEBUGADD(10, (" - perm: o%o\n", hpux_entry.a_perm));
+ if (!hpux_add_to_acl(hpux_acl, count, &hpux_entry,
+ 1, type))
+ {
+ DEBUG(10, ("error adding acl entry\n"));
+ goto fail;
+ }
+ DEBUG(10, ("count after adding: %d (i: %d)\n", *count, i));
+ DEBUG(10, ("test, if entry has been copied into acl:\n"));
+ DEBUGADD(10, (" - type: 0x%04x\n",
+ (*hpux_acl)[(*count)-1].a_type));
+ DEBUGADD(10, (" - id: %d\n",
+ (*hpux_acl)[(*count)-1].a_id));
+ DEBUGADD(10, (" - perm: o%o\n",
+ (*hpux_acl)[(*count)-1].a_perm));
+ }
+
+ ret = True;
+ goto done;
+
+ fail:
+ SAFE_FREE(*hpux_acl);
+ done:
+ DEBUG(10, ("smb_acl_to_hpux_acl %s\n",
+ ((ret == True) ? "succeeded" : "failed")));
+ return ret;
+}
+
+/*
+ * convert either the access or the default part of a
+ * soaris acl to the SMB_ACL format.
+ */
+static SMB_ACL_T hpux_acl_to_smb_acl(HPUX_ACL_T hpux_acl, int count,
+ SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result;
+ int i;
+
+ if ((result = sys_acl_init(mem_ctx)) == NULL) {
+ DEBUG(10, ("error allocating memory for SMB_ACL\n"));
+ goto fail;
+ }
+ for (i = 0; i < count; i++) {
+ SMB_ACL_ENTRY_T smb_entry;
+ SMB_ACL_PERM_T smb_perm;
+
+ if (!_IS_OF_TYPE(hpux_acl[i], type)) {
+ continue;
+ }
+ result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count + 1);
+ if (result->acl == NULL) {
+ DEBUG(10, ("error reallocating memory for SMB_ACL\n"));
+ goto fail;
+ }
+ smb_entry = &result->acl[result->count];
+ if (sys_acl_set_tag_type(smb_entry,
+ hpux_tag_to_smb_tag(hpux_acl[i].a_type)) != 0)
+ {
+ DEBUG(10, ("invalid tag type given: 0x%04x\n",
+ hpux_acl[i].a_type));
+ goto fail;
+ }
+ /* intentionally not checking return code here: */
+ sys_acl_set_qualifier(smb_entry, (void *)&hpux_acl[i].a_id);
+ smb_perm = hpux_perm_to_smb_perm(hpux_acl[i].a_perm);
+ if (sys_acl_set_permset(smb_entry, &smb_perm) != 0) {
+ DEBUG(10, ("invalid permset given: %d\n",
+ hpux_acl[i].a_perm));
+ goto fail;
+ }
+ result->count += 1;
+ }
+ goto done;
+ fail:
+ TALLOC_FREE(result);
+ done:
+ DEBUG(10, ("hpux_acl_to_smb_acl %s\n",
+ ((result == NULL) ? "failed" : "succeeded")));
+ return result;
+}
+
+
+
+static HPUX_ACL_TAG_T smb_tag_to_hpux_tag(SMB_ACL_TAG_T smb_tag)
+{
+ HPUX_ACL_TAG_T hpux_tag = 0;
+
+ DEBUG(10, ("smb_tag_to_hpux_tag\n"));
+ DEBUGADD(10, (" --> got smb tag 0x%04x\n", smb_tag));
+
+ switch (smb_tag) {
+ case SMB_ACL_USER:
+ hpux_tag = USER;
+ break;
+ case SMB_ACL_USER_OBJ:
+ hpux_tag = USER_OBJ;
+ break;
+ case SMB_ACL_GROUP:
+ hpux_tag = GROUP;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ hpux_tag = GROUP_OBJ;
+ break;
+ case SMB_ACL_OTHER:
+ hpux_tag = OTHER_OBJ;
+ break;
+ case SMB_ACL_MASK:
+ hpux_tag = CLASS_OBJ;
+ break;
+ default:
+ DEBUGADD(10, (" !!! unknown smb tag type 0x%04x\n", smb_tag));
+ break;
+ }
+
+ DEBUGADD(10, (" --> determined hpux tag 0x%04x\n", hpux_tag));
+
+ return hpux_tag;
+}
+
+static SMB_ACL_TAG_T hpux_tag_to_smb_tag(HPUX_ACL_TAG_T hpux_tag)
+{
+ SMB_ACL_TAG_T smb_tag = 0;
+
+ DEBUG(10, ("hpux_tag_to_smb_tag:\n"));
+ DEBUGADD(10, (" --> got hpux tag 0x%04x\n", hpux_tag));
+
+ hpux_tag &= ~ACL_DEFAULT;
+
+ switch (hpux_tag) {
+ case USER:
+ smb_tag = SMB_ACL_USER;
+ break;
+ case USER_OBJ:
+ smb_tag = SMB_ACL_USER_OBJ;
+ break;
+ case GROUP:
+ smb_tag = SMB_ACL_GROUP;
+ break;
+ case GROUP_OBJ:
+ smb_tag = SMB_ACL_GROUP_OBJ;
+ break;
+ case OTHER_OBJ:
+ smb_tag = SMB_ACL_OTHER;
+ break;
+ case CLASS_OBJ:
+ smb_tag = SMB_ACL_MASK;
+ break;
+ default:
+ DEBUGADD(10, (" !!! unknown hpux tag type: 0x%04x\n",
+ hpux_tag));
+ break;
+ }
+
+ DEBUGADD(10, (" --> determined smb tag 0x%04x\n", smb_tag));
+
+ return smb_tag;
+}
+
+
+/*
+ * The permission bits used in the following two permission conversion
+ * functions are same, but the functions make us independent of the concrete
+ * permission data types.
+ */
+static SMB_ACL_PERM_T hpux_perm_to_smb_perm(const HPUX_PERM_T perm)
+{
+ SMB_ACL_PERM_T smb_perm = 0;
+ smb_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0);
+ smb_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0);
+ smb_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
+ return smb_perm;
+}
+
+
+static HPUX_PERM_T smb_perm_to_hpux_perm(const SMB_ACL_PERM_T perm)
+{
+ HPUX_PERM_T hpux_perm = 0;
+ hpux_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0);
+ hpux_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0);
+ hpux_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
+ return hpux_perm;
+}
+
+
+static bool hpux_acl_get_file(const char *name, HPUX_ACL_T *hpux_acl,
+ int *count)
+{
+ bool result = False;
+ static HPUX_ACE_T dummy_ace;
+
+ DEBUG(10, ("hpux_acl_get_file called for file '%s'\n", name));
+
+ /*
+ * The original code tries some INITIAL_ACL_SIZE
+ * and only did the ACL_CNT call upon failure
+ * (for performance reasons).
+ * For the sake of simplicity, I skip this for now.
+ *
+ * NOTE: There is a catch here on HP-UX: acl with cmd parameter
+ * ACL_CNT fails with errno EINVAL when called with a NULL
+ * pointer as last argument. So we need to use a dummy acl
+ * struct here (we make it static so it does not need to be
+ * instantiated or malloced each time this function is
+ * called). Btw: the count parameter does not seem to matter...
+ */
+ *count = acl(discard_const_p(char, name), ACL_CNT, 0, &dummy_ace);
+ if (*count < 0) {
+ DEBUG(10, ("acl ACL_CNT failed: %s\n", strerror(errno)));
+ goto done;
+ }
+ *hpux_acl = hpux_acl_init(*count);
+ if (*hpux_acl == NULL) {
+ DEBUG(10, ("error allocating memory for hpux acl...\n"));
+ goto done;
+ }
+ *count = acl(discard_const_p(char, name), ACL_GET, *count, *hpux_acl);
+ if (*count < 0) {
+ DEBUG(10, ("acl ACL_GET failed: %s\n", strerror(errno)));
+ goto done;
+ }
+ result = True;
+
+ done:
+ DEBUG(10, ("hpux_acl_get_file %s.\n",
+ ((result == True) ? "succeeded" : "failed" )));
+ return result;
+}
+
+
+
+
+/*
+ * Add entries to a hpux ACL.
+ *
+ * Entries are directly added to the hpuxacl parameter.
+ * if memory allocation fails, this may result in hpuxacl
+ * being NULL. if the resulting acl is to be checked and is
+ * not valid, it is kept in hpuxacl but False is returned.
+ *
+ * The type of ACEs (access/default) to be added to the ACL can
+ * be selected via the type parameter.
+ * I use the SMB_ACL_TYPE_T type here. Since SMB_ACL_TYPE_ACCESS
+ * is defined as "0", this means that one can only add either
+ * access or default ACEs from the given ACL, not both at the same
+ * time. If it should become necessary to add all of an ACL, one
+ * would have to replace this parameter by another type.
+ */
+static bool hpux_add_to_acl(HPUX_ACL_T *hpux_acl, int *count,
+ HPUX_ACL_T add_acl, int add_count,
+ SMB_ACL_TYPE_T type)
+{
+ int i;
+
+ if ((type != SMB_ACL_TYPE_ACCESS) && (type != SMB_ACL_TYPE_DEFAULT))
+ {
+ DEBUG(10, ("invalid acl type given: %d\n", type));
+ errno = EINVAL;
+ return False;
+ }
+ for (i = 0; i < add_count; i++) {
+ if (!_IS_OF_TYPE(add_acl[i], type)) {
+ continue;
+ }
+ ADD_TO_ARRAY(NULL, HPUX_ACE_T, add_acl[i],
+ hpux_acl, count);
+ if (hpux_acl == NULL) {
+ DEBUG(10, ("error enlarging acl.\n"));
+ errno = ENOMEM;
+ return False;
+ }
+ }
+ return True;
+}
+
+
+/*
+ * sort the ACL and check it for validity
+ *
+ * [original comment from lib/sysacls.c:]
+ *
+ * if it's a minimal ACL with only 4 entries then we
+ * need to recalculate the mask permissions to make
+ * sure that they are the same as the GROUP_OBJ
+ * permissions as required by the UnixWare acl() system call.
+ *
+ * (note: since POSIX allows minimal ACLs which only contain
+ * 3 entries - ie there is no mask entry - we should, in theory,
+ * check for this and add a mask entry if necessary - however
+ * we "know" that the caller of this interface always specifies
+ * a mask, so in practice "this never happens" (tm) - if it *does*
+ * happen aclsort() will fail and return an error and someone will
+ * have to fix it...)
+ */
+static bool hpux_acl_sort(HPUX_ACL_T hpux_acl, int count)
+{
+ int fixmask = (count <= 4);
+
+ if (hpux_internal_aclsort(count, fixmask, hpux_acl) != 0) {
+ errno = EINVAL;
+ return False;
+ }
+ return True;
+}
+
+
+/*
+ * Helpers for hpux_internal_aclsort:
+ * - hpux_count_obj
+ * - hpux_swap_acl_entries
+ * - hpux_prohibited_duplicate_type
+ * - hpux_get_needed_class_perm
+ */
+
+/* hpux_count_obj:
+ * Counts the different number of objects in a given array of ACL
+ * structures.
+ * Inputs:
+ *
+ * acl_count - Count of ACLs in the array of ACL structures.
+ * aclp - Array of ACL structures.
+ * acl_type_count - Pointer to acl_types structure. Should already be
+ * allocated.
+ * Output:
+ *
+ * acl_type_count - This structure is filled up with counts of various
+ * acl types.
+ */
+
+static void hpux_count_obj(int acl_count, HPUX_ACL_T aclp, struct hpux_acl_types *acl_type_count)
+{
+ int i;
+
+ memset(acl_type_count, 0, sizeof(struct hpux_acl_types));
+
+ for(i=0;i<acl_count;i++) {
+ switch(aclp[i].a_type) {
+ case USER:
+ acl_type_count->n_user++;
+ break;
+ case USER_OBJ:
+ acl_type_count->n_user_obj++;
+ break;
+ case DEF_USER_OBJ:
+ acl_type_count->n_def_user_obj++;
+ break;
+ case GROUP:
+ acl_type_count->n_group++;
+ break;
+ case GROUP_OBJ:
+ acl_type_count->n_group_obj++;
+ break;
+ case DEF_GROUP_OBJ:
+ acl_type_count->n_def_group_obj++;
+ break;
+ case OTHER_OBJ:
+ acl_type_count->n_other_obj++;
+ break;
+ case DEF_OTHER_OBJ:
+ acl_type_count->n_def_other_obj++;
+ break;
+ case CLASS_OBJ:
+ acl_type_count->n_class_obj++;
+ break;
+ case DEF_CLASS_OBJ:
+ acl_type_count->n_def_class_obj++;
+ break;
+ case DEF_USER:
+ acl_type_count->n_def_user++;
+ break;
+ case DEF_GROUP:
+ acl_type_count->n_def_group++;
+ break;
+ default:
+ acl_type_count->n_illegal_obj++;
+ break;
+ }
+ }
+}
+
+/* hpux_swap_acl_entries: Swaps two ACL entries.
+ *
+ * Inputs: aclp0, aclp1 - ACL entries to be swapped.
+ */
+
+static void hpux_swap_acl_entries(HPUX_ACE_T *aclp0, HPUX_ACE_T *aclp1)
+{
+ HPUX_ACE_T temp_acl;
+
+ temp_acl.a_type = aclp0->a_type;
+ temp_acl.a_id = aclp0->a_id;
+ temp_acl.a_perm = aclp0->a_perm;
+
+ aclp0->a_type = aclp1->a_type;
+ aclp0->a_id = aclp1->a_id;
+ aclp0->a_perm = aclp1->a_perm;
+
+ aclp1->a_type = temp_acl.a_type;
+ aclp1->a_id = temp_acl.a_id;
+ aclp1->a_perm = temp_acl.a_perm;
+}
+
+/* hpux_prohibited_duplicate_type
+ * Identifies if given ACL type can have duplicate entries or
+ * not.
+ *
+ * Inputs: acl_type - ACL Type.
+ *
+ * Outputs:
+ *
+ * Return..
+ *
+ * True - If the ACL type matches any of the prohibited types.
+ * False - If the ACL type doesn't match any of the prohibited types.
+ */
+
+static bool hpux_prohibited_duplicate_type(int acl_type)
+{
+ switch(acl_type) {
+ case USER:
+ case GROUP:
+ case DEF_USER:
+ case DEF_GROUP:
+ return True;
+ default:
+ return False;
+ }
+}
+
+/* hpux_get_needed_class_perm
+ * Returns the permissions of a ACL structure only if the ACL
+ * type matches one of the pre-determined types for computing
+ * CLASS_OBJ permissions.
+ *
+ * Inputs: aclp - Pointer to ACL structure.
+ */
+
+static int hpux_get_needed_class_perm(struct acl *aclp)
+{
+ switch(aclp->a_type) {
+ case USER:
+ case GROUP_OBJ:
+ case GROUP:
+ case DEF_USER_OBJ:
+ case DEF_USER:
+ case DEF_GROUP_OBJ:
+ case DEF_GROUP:
+ case DEF_CLASS_OBJ:
+ case DEF_OTHER_OBJ:
+ return aclp->a_perm;
+ default:
+ return 0;
+ }
+}
+
+/* hpux_internal_aclsort: aclsort for HPUX.
+ *
+ * -> The aclsort() system call is available on the latest HPUX General
+ * -> Patch Bundles. So for HPUX, we developed our version of aclsort
+ * -> function. Because, we don't want to update to a new
+ * -> HPUX GR bundle just for aclsort() call.
+ *
+ * aclsort sorts the array of ACL structures as per the description in
+ * aclsort man page. Refer to aclsort man page for more details
+ *
+ * Inputs:
+ *
+ * acl_count - Count of ACLs in the array of ACL structures.
+ * calclass - If this is not zero, then we compute the CLASS_OBJ
+ * permissions.
+ * aclp - Array of ACL structures.
+ *
+ * Outputs:
+ *
+ * aclp - Sorted array of ACL structures.
+ *
+ * Outputs:
+ *
+ * Returns 0 for success -1 for failure. Prints a message to the Samba
+ * debug log in case of failure.
+ */
+
+static int hpux_internal_aclsort(int acl_count, int calclass, HPUX_ACL_T aclp)
+{
+ struct hpux_acl_types acl_obj_count;
+ int n_class_obj_perm = 0;
+ int i, j;
+
+ DEBUG(10,("Entering hpux_internal_aclsort. (calclass = %d)\n", calclass));
+
+ if (hpux_aclsort_call_present()) {
+ DEBUG(10, ("calling hpux aclsort\n"));
+ return aclsort(acl_count, calclass, aclp);
+ }
+
+ DEBUG(10, ("using internal aclsort\n"));
+
+ if(!acl_count) {
+ DEBUG(10,("Zero acl count passed. Returning Success\n"));
+ return 0;
+ }
+
+ if(aclp == NULL) {
+ DEBUG(0,("Null ACL pointer in hpux_acl_sort. Returning Failure. \n"));
+ return -1;
+ }
+
+ /* Count different types of ACLs in the ACLs array */
+
+ hpux_count_obj(acl_count, aclp, &acl_obj_count);
+
+ /* There should be only one entry each of type USER_OBJ, GROUP_OBJ,
+ * CLASS_OBJ and OTHER_OBJ
+ */
+
+ if ( (acl_obj_count.n_user_obj != 1) ||
+ (acl_obj_count.n_group_obj != 1) ||
+ (acl_obj_count.n_class_obj != 1) ||
+ (acl_obj_count.n_other_obj != 1) )
+ {
+ DEBUG(0,("hpux_internal_aclsort: More than one entry or no entries for \
+USER OBJ or GROUP_OBJ or OTHER_OBJ or CLASS_OBJ\n"));
+ return -1;
+ }
+
+ /* If any of the default objects are present, there should be only
+ * one of them each.
+ */
+
+ if ( (acl_obj_count.n_def_user_obj > 1) ||
+ (acl_obj_count.n_def_group_obj > 1) ||
+ (acl_obj_count.n_def_other_obj > 1) ||
+ (acl_obj_count.n_def_class_obj > 1) )
+ {
+ DEBUG(0,("hpux_internal_aclsort: More than one entry for DEF_CLASS_OBJ \
+or DEF_USER_OBJ or DEF_GROUP_OBJ or DEF_OTHER_OBJ\n"));
+ return -1;
+ }
+
+ /* We now have proper number of OBJ and DEF_OBJ entries. Now sort the acl
+ * structures.
+ *
+ * Sorting crieteria - First sort by ACL type. If there are multiple entries of
+ * same ACL type, sort by ACL id.
+ *
+ * I am using the trivial kind of sorting method here because, performance isn't
+ * really effected by the ACLs feature. More over there aren't going to be more
+ * than 17 entries on HPUX.
+ */
+
+ for(i=0; i<acl_count;i++) {
+ for (j=i+1; j<acl_count; j++) {
+ if( aclp[i].a_type > aclp[j].a_type ) {
+ /* ACL entries out of order, swap them */
+ hpux_swap_acl_entries((aclp+i), (aclp+j));
+ } else if ( aclp[i].a_type == aclp[j].a_type ) {
+ /* ACL entries of same type, sort by id */
+ if(aclp[i].a_id > aclp[j].a_id) {
+ hpux_swap_acl_entries((aclp+i), (aclp+j));
+ } else if (aclp[i].a_id == aclp[j].a_id) {
+ /* We have a duplicate entry. */
+ if(hpux_prohibited_duplicate_type(aclp[i].a_type)) {
+ DEBUG(0, ("hpux_internal_aclsort: Duplicate entry: Type(hex): %x Id: %d\n",
+ aclp[i].a_type, aclp[i].a_id));
+ return -1;
+ }
+ }
+ }
+ }
+ }
+
+ /* set the class obj permissions to the computed one. */
+ if(calclass) {
+ int n_class_obj_index = -1;
+
+ for(i=0;i<acl_count;i++) {
+ n_class_obj_perm |= hpux_get_needed_class_perm((aclp+i));
+
+ if(aclp[i].a_type == CLASS_OBJ)
+ n_class_obj_index = i;
+ }
+ aclp[n_class_obj_index].a_perm = n_class_obj_perm;
+ }
+
+ return 0;
+}
+
+
+/*
+ * hpux_acl_call_present:
+ *
+ * This checks if the POSIX ACL system call is defined
+ * which basically corresponds to whether JFS 3.3 or
+ * higher is installed. If acl() was called when it
+ * isn't defined, it causes the process to core dump
+ * so it is important to check this and avoid acl()
+ * calls if it isn't there.
+ */
+
+static bool hpux_acl_call_present(void)
+{
+
+ shl_t handle = NULL;
+ void *value;
+ int ret_val=0;
+ static bool already_checked = False;
+
+ if(already_checked)
+ return True;
+
+ errno = 0;
+
+ ret_val = shl_findsym(&handle, "acl", TYPE_PROCEDURE, &value);
+
+ if(ret_val != 0) {
+ DEBUG(5, ("hpux_acl_call_present: shl_findsym() returned %d, errno = %d, error %s\n",
+ ret_val, errno, strerror(errno)));
+ DEBUG(5,("hpux_acl_call_present: acl() system call is not present. Check if you have JFS 3.3 and above?\n"));
+ errno = ENOSYS;
+ return False;
+ }
+
+ DEBUG(10,("hpux_acl_call_present: acl() system call is present. We have JFS 3.3 or above \n"));
+
+ already_checked = True;
+ return True;
+}
+
+/*
+ * runtime check for presence of aclsort library call.
+ * same code as for acl call. if there are more of these,
+ * a dispatcher function could be handy...
+ */
+
+static bool hpux_aclsort_call_present(void)
+{
+ shl_t handle = NULL;
+ void *value;
+ int ret_val = 0;
+ static bool already_checked = False;
+
+ if (already_checked) {
+ return True;
+ }
+
+ errno = 0;
+ ret_val = shl_findsym(&handle, "aclsort", TYPE_PROCEDURE, &value);
+ if (ret_val != 0) {
+ DEBUG(5, ("hpux_aclsort_call_present: shl_findsym "
+ "returned %d, errno = %d, error %s",
+ ret_val, errno, strerror(errno)));
+ DEBUG(5, ("hpux_aclsort_call_present: "
+ "aclsort() function not available.\n"));
+ return False;
+ }
+ DEBUG(10,("hpux_aclsort_call_present: aclsort() function present.\n"));
+ already_checked = True;
+ return True;
+}
+
+#if 0
+/*
+ * acl check function:
+ * unused at the moment but could be used to get more
+ * concrete error messages for debugging...
+ * (acl sort just says that the acl is invalid...)
+ */
+static bool hpux_acl_check(HPUX_ACL_T hpux_acl, int count)
+{
+ int check_rc;
+ int check_which;
+
+ check_rc = aclcheck(hpux_acl, count, &check_which);
+ if (check_rc != 0) {
+ DEBUG(10, ("acl is not valid:\n"));
+ DEBUGADD(10, (" - return code: %d\n", check_rc));
+ DEBUGADD(10, (" - which: %d\n", check_which));
+ if (check_which != -1) {
+ DEBUGADD(10, (" - invalid entry:\n"));
+ DEBUGADD(10, (" * type: %d:\n",
+ hpux_acl[check_which].a_type));
+ DEBUGADD(10, (" * id: %d\n",
+ hpux_acl[check_which].a_id));
+ DEBUGADD(10, (" * perm: 0o%o\n",
+ hpux_acl[check_which].a_perm));
+ }
+ return False;
+ }
+ return True;
+}
+#endif
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers hpuxacl_fns = {
+ .sys_acl_get_fd_fn = hpuxacl_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ sys_acl_set_fd_fn = hpuxacl_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = hpuxacl_sys_acl_delete_def_fd,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_hpuxacl_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "hpuxacl",
+ &hpuxacl_fns);
+}
+
+/* ENTE */
diff --git a/source3/modules/vfs_hpuxacl.h b/source3/modules/vfs_hpuxacl.h
new file mode 100644
index 0000000..26562da
--- /dev/null
+++ b/source3/modules/vfs_hpuxacl.h
@@ -0,0 +1,56 @@
+/*
+ * Unix SMB/Netbios implementation.
+ * VFS module to get and set HP-UX ACLs - prototype header
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module supports JFS (POSIX) ACLs on VxFS (Veritas * Filesystem).
+ * These are available on HP-UX 11.00 if JFS 3.3 is installed.
+ * On HP-UX 11i (11.11 and above) these ACLs are supported out of
+ * the box.
+ *
+ * There is another form of ACLs on HFS. These ACLs have a
+ * completely different API and their own set of userland tools.
+ * Since HFS seems to be considered deprecated, HFS acls
+ * are not supported. (They could be supported through a separate
+ * vfs-module if there is demand.)
+ */
+
+#ifndef __VFS_HPUXACL_H__
+#define __VFS_HPUXACL_H__
+
+SMB_ACL_T hpuxacl_sys_acl_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ SMB_ACL_TYPE_T type);
+
+SMB_ACL_T hpuxacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp);
+
+int hpuxacl_sys_acl_set_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl);
+
+int hpuxacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_T theacl);
+
+int hpuxacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp);
+
+#endif
+
diff --git a/source3/modules/vfs_io_uring.c b/source3/modules/vfs_io_uring.c
new file mode 100644
index 0000000..b33c09a
--- /dev/null
+++ b/source3/modules/vfs_io_uring.c
@@ -0,0 +1,822 @@
+/*
+ * Use the io_uring of Linux (>= 5.1)
+ *
+ * Copyright (C) Volker Lendecke 2008
+ * Copyright (C) Jeremy Allison 2010
+ * Copyright (C) Stefan Metzmacher 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "replace.h"
+
+/*
+ * liburing.h only needs a forward declaration
+ * of struct open_how.
+ *
+ * If struct open_how is defined in liburing/compat.h
+ * itself, hide it away in order to avoid conflicts
+ * with including linux/openat2.h or defining 'struct open_how'
+ * in libreplace.
+ */
+struct open_how;
+#ifdef HAVE_STRUCT_OPEN_HOW_LIBURING_COMPAT_H
+#define open_how __ignore_liburing_compat_h_open_how
+#include <liburing/compat.h>
+#undef open_how
+#endif /* HAVE_STRUCT_OPEN_HOW_LIBURING_COMPAT_H */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/iov_buf.h"
+#include "smbprofile.h"
+#include <liburing.h>
+
+struct vfs_io_uring_request;
+
+struct vfs_io_uring_config {
+ struct io_uring uring;
+ struct tevent_fd *fde;
+ /* recursion guard. See comment above vfs_io_uring_queue_run() */
+ bool busy;
+ /* recursion guard. See comment above vfs_io_uring_queue_run() */
+ bool need_retry;
+ struct vfs_io_uring_request *queue;
+ struct vfs_io_uring_request *pending;
+};
+
+struct vfs_io_uring_request {
+ struct vfs_io_uring_request *prev, *next;
+ struct vfs_io_uring_request **list_head;
+ struct vfs_io_uring_config *config;
+ struct tevent_req *req;
+ void (*completion_fn)(struct vfs_io_uring_request *cur,
+ const char *location);
+ struct timespec start_time;
+ struct timespec end_time;
+ SMBPROFILE_BYTES_ASYNC_STATE(profile_bytes);
+ struct io_uring_sqe sqe;
+ struct io_uring_cqe cqe;
+};
+
+static void vfs_io_uring_finish_req(struct vfs_io_uring_request *cur,
+ const struct io_uring_cqe *cqe,
+ struct timespec end_time,
+ const char *location)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(cur->req,
+ struct tevent_req);
+ void *state = _tevent_req_data(req);
+
+ talloc_set_destructor(state, NULL);
+ if (cur->list_head != NULL) {
+ DLIST_REMOVE((*cur->list_head), cur);
+ cur->list_head = NULL;
+ }
+ cur->cqe = *cqe;
+
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(cur->profile_bytes);
+ cur->end_time = end_time;
+
+ /*
+ * We rely on being inside the _send() function
+ * or tevent_req_defer_callback() being called
+ * already.
+ */
+ cur->completion_fn(cur, location);
+}
+
+static void vfs_io_uring_config_destroy(struct vfs_io_uring_config *config,
+ int ret,
+ const char *location)
+{
+ struct vfs_io_uring_request *cur = NULL, *next = NULL;
+ struct timespec start_time;
+ struct timespec end_time;
+ struct io_uring_cqe err_cqe = {
+ .res = ret,
+ };
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ if (config->uring.ring_fd != -1) {
+ /* TODO: cancel queued and pending requests */
+ TALLOC_FREE(config->fde);
+ io_uring_queue_exit(&config->uring);
+ config->uring.ring_fd = -1;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ for (cur = config->pending; cur != NULL; cur = next) {
+ next = cur->next;
+ err_cqe.user_data = (uintptr_t)(void *)cur;
+ vfs_io_uring_finish_req(cur, &err_cqe, end_time, location);
+ }
+
+ for (cur = config->queue; cur != NULL; cur = next) {
+ next = cur->next;
+ err_cqe.user_data = (uintptr_t)(void *)cur;
+ cur->start_time = start_time;
+ vfs_io_uring_finish_req(cur, &err_cqe, end_time, location);
+ }
+}
+
+static int vfs_io_uring_config_destructor(struct vfs_io_uring_config *config)
+{
+ vfs_io_uring_config_destroy(config, -EUCLEAN, __location__);
+ return 0;
+}
+
+static int vfs_io_uring_request_state_deny_destructor(void *_state)
+{
+ struct __vfs_io_uring_generic_state {
+ struct vfs_io_uring_request ur;
+ } *state = (struct __vfs_io_uring_generic_state *)_state;
+ struct vfs_io_uring_request *cur = &state->ur;
+
+ /* our parent is gone */
+ cur->req = NULL;
+
+ /* remove ourself from any list */
+ DLIST_REMOVE((*cur->list_head), cur);
+ cur->list_head = NULL;
+
+ /*
+ * Our state is about to go away,
+ * all we can do is shutting down the whole uring.
+ * But that's ok as we're most likely called from exit_server()
+ */
+ vfs_io_uring_config_destroy(cur->config, -ESHUTDOWN, __location__);
+ return 0;
+}
+
+static void vfs_io_uring_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+static int vfs_io_uring_connect(vfs_handle_struct *handle, const char *service,
+ const char *user)
+{
+ int ret;
+ struct vfs_io_uring_config *config;
+ unsigned num_entries;
+ bool sqpoll;
+ unsigned flags = 0;
+
+ config = talloc_zero(handle->conn, struct vfs_io_uring_config);
+ if (config == NULL) {
+ DEBUG(0, ("talloc_zero() failed\n"));
+ return -1;
+ }
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct vfs_io_uring_config,
+ return -1);
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ num_entries = lp_parm_ulong(SNUM(handle->conn),
+ "io_uring",
+ "num_entries",
+ 128);
+ num_entries = MAX(num_entries, 1);
+
+ sqpoll = lp_parm_bool(SNUM(handle->conn),
+ "io_uring",
+ "sqpoll",
+ false);
+ if (sqpoll) {
+ flags |= IORING_SETUP_SQPOLL;
+ }
+
+ ret = io_uring_queue_init(num_entries, &config->uring, flags);
+ if (ret < 0) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = -ret;
+ return -1;
+ }
+
+ talloc_set_destructor(config, vfs_io_uring_config_destructor);
+
+#ifdef HAVE_IO_URING_RING_DONTFORK
+ ret = io_uring_ring_dontfork(&config->uring);
+ if (ret < 0) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = -ret;
+ return -1;
+ }
+#endif /* HAVE_IO_URING_RING_DONTFORK */
+
+ config->fde = tevent_add_fd(handle->conn->sconn->ev_ctx,
+ config,
+ config->uring.ring_fd,
+ TEVENT_FD_READ,
+ vfs_io_uring_fd_handler,
+ handle);
+ if (config->fde == NULL) {
+ ret = errno;
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ errno = ret;
+ return -1;
+ }
+
+ return 0;
+}
+
+static void _vfs_io_uring_queue_run(struct vfs_io_uring_config *config)
+{
+ struct vfs_io_uring_request *cur = NULL, *next = NULL;
+ struct io_uring_cqe *cqe = NULL;
+ unsigned cqhead;
+ unsigned nr = 0;
+ struct timespec start_time;
+ struct timespec end_time;
+ int ret;
+
+ PROFILE_TIMESTAMP(&start_time);
+
+ if (config->uring.ring_fd == -1) {
+ vfs_io_uring_config_destroy(config, -ESTALE, __location__);
+ return;
+ }
+
+ for (cur = config->queue; cur != NULL; cur = next) {
+ struct io_uring_sqe *sqe = NULL;
+ void *state = _tevent_req_data(cur->req);
+
+ next = cur->next;
+
+ sqe = io_uring_get_sqe(&config->uring);
+ if (sqe == NULL) {
+ break;
+ }
+
+ talloc_set_destructor(state,
+ vfs_io_uring_request_state_deny_destructor);
+ DLIST_REMOVE(config->queue, cur);
+ *sqe = cur->sqe;
+ DLIST_ADD_END(config->pending, cur);
+ cur->list_head = &config->pending;
+ SMBPROFILE_BYTES_ASYNC_SET_BUSY(cur->profile_bytes);
+
+ cur->start_time = start_time;
+ }
+
+ ret = io_uring_submit(&config->uring);
+ if (ret == -EAGAIN || ret == -EBUSY) {
+ /* We just retry later */
+ } else if (ret < 0) {
+ vfs_io_uring_config_destroy(config, ret, __location__);
+ return;
+ }
+
+ PROFILE_TIMESTAMP(&end_time);
+
+ io_uring_for_each_cqe(&config->uring, cqhead, cqe) {
+ cur = (struct vfs_io_uring_request *)io_uring_cqe_get_data(cqe);
+ vfs_io_uring_finish_req(cur, cqe, end_time, __location__);
+ nr++;
+ }
+
+ io_uring_cq_advance(&config->uring, nr);
+}
+
+/*
+ * Wrapper function to prevent recursion which could happen
+ * if we called _vfs_io_uring_queue_run() directly without
+ * recursion checks.
+ *
+ * Looking at the pread call, we can have:
+ *
+ * vfs_io_uring_pread_send()
+ * ->vfs_io_uring_pread_submit() <-----------------------------------
+ * ->vfs_io_uring_request_submit() |
+ * ->vfs_io_uring_queue_run() |
+ * ->_vfs_io_uring_queue_run() |
+ * |
+ * But inside _vfs_io_uring_queue_run() looks like: |
+ * |
+ * _vfs_io_uring_queue_run() { |
+ * if (THIS_IO_COMPLETED) { |
+ * ->vfs_io_uring_finish_req() |
+ * ->cur->completion_fn() |
+ * } |
+ * } |
+ * |
+ * cur->completion_fn() for pread is set to vfs_io_uring_pread_completion() |
+ * |
+ * vfs_io_uring_pread_completion() { |
+ * if (READ_TERMINATED) { |
+ * -> tevent_req_done() - We're done, go back up the stack. |
+ * return; |
+ * } |
+ * |
+ * We have a short read - adjust the io vectors |
+ * |
+ * ->vfs_io_uring_pread_submit() ---------------------------------------
+ * }
+ *
+ * So before calling _vfs_io_uring_queue_run() we backet it with setting
+ * a flag config->busy, and unset it once _vfs_io_uring_queue_run() finally
+ * exits the retry loop.
+ *
+ * If we end up back into vfs_io_uring_queue_run() we notice we've done so
+ * as config->busy is set and don't recurse into _vfs_io_uring_queue_run().
+ *
+ * We set the second flag config->need_retry that tells us to loop in the
+ * vfs_io_uring_queue_run() call above us in the stack and return.
+ *
+ * When the outer call to _vfs_io_uring_queue_run() returns we are in
+ * a loop checking if config->need_retry was set. That happens if
+ * the short read case occurs and _vfs_io_uring_queue_run() ended up
+ * recursing into vfs_io_uring_queue_run().
+ *
+ * Once vfs_io_uring_pread_completion() finishes without a short
+ * read (the READ_TERMINATED case, tevent_req_done() is called)
+ * then config->need_retry is left as false, we exit the loop,
+ * set config->busy to false so the next top level call into
+ * vfs_io_uring_queue_run() won't think it's a recursed call
+ * and return.
+ *
+ */
+
+static void vfs_io_uring_queue_run(struct vfs_io_uring_config *config)
+{
+ if (config->busy) {
+ /*
+ * We've recursed due to short read/write.
+ * Set need_retry to ensure we retry the
+ * io_uring_submit().
+ */
+ config->need_retry = true;
+ return;
+ }
+
+ /*
+ * Bracket the loop calling _vfs_io_uring_queue_run()
+ * with busy = true / busy = false.
+ * so we can detect recursion above.
+ */
+
+ config->busy = true;
+
+ do {
+ config->need_retry = false;
+ _vfs_io_uring_queue_run(config);
+ } while (config->need_retry);
+
+ config->busy = false;
+}
+
+static void vfs_io_uring_request_submit(struct vfs_io_uring_request *cur)
+{
+ struct vfs_io_uring_config *config = cur->config;
+
+ io_uring_sqe_set_data(&cur->sqe, cur);
+ DLIST_ADD_END(config->queue, cur);
+ cur->list_head = &config->queue;
+
+ vfs_io_uring_queue_run(config);
+}
+
+static void vfs_io_uring_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ vfs_handle_struct *handle = (vfs_handle_struct *)private_data;
+ struct vfs_io_uring_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct vfs_io_uring_config,
+ smb_panic(__location__));
+
+ vfs_io_uring_queue_run(config);
+}
+
+struct vfs_io_uring_pread_state {
+ struct files_struct *fsp;
+ off_t offset;
+ struct iovec iov;
+ size_t nread;
+ struct vfs_io_uring_request ur;
+};
+
+static void vfs_io_uring_pread_submit(struct vfs_io_uring_pread_state *state);
+static void vfs_io_uring_pread_completion(struct vfs_io_uring_request *cur,
+ const char *location);
+
+static struct tevent_req *vfs_io_uring_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct vfs_io_uring_pread_state *state = NULL;
+ struct vfs_io_uring_config *config = NULL;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct vfs_io_uring_config,
+ smb_panic(__location__));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_io_uring_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ur.config = config;
+ state->ur.req = req;
+ state->ur.completion_fn = vfs_io_uring_pread_completion;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pread, profile_p,
+ state->ur.profile_bytes, n);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->ur.profile_bytes);
+
+ ok = sys_valid_io_range(offset, n);
+ if (!ok) {
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ state->fsp = fsp;
+ state->offset = offset;
+ state->iov.iov_base = (void *)data;
+ state->iov.iov_len = n;
+ vfs_io_uring_pread_submit(state);
+
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_defer_callback(req, ev);
+ return req;
+}
+
+static void vfs_io_uring_pread_submit(struct vfs_io_uring_pread_state *state)
+{
+ io_uring_prep_readv(&state->ur.sqe,
+ fsp_get_io_fd(state->fsp),
+ &state->iov, 1,
+ state->offset);
+ vfs_io_uring_request_submit(&state->ur);
+}
+
+static void vfs_io_uring_pread_completion(struct vfs_io_uring_request *cur,
+ const char *location)
+{
+ struct vfs_io_uring_pread_state *state = tevent_req_data(
+ cur->req, struct vfs_io_uring_pread_state);
+ struct iovec *iov = &state->iov;
+ int num_iov = 1;
+ bool ok;
+
+ /*
+ * We rely on being inside the _send() function
+ * or tevent_req_defer_callback() being called
+ * already.
+ */
+
+ if (cur->cqe.res < 0) {
+ int err = -cur->cqe.res;
+ _tevent_req_error(cur->req, err, location);
+ return;
+ }
+
+ if (cur->cqe.res == 0) {
+ /*
+ * We reached EOF, we're done
+ */
+ tevent_req_done(cur->req);
+ return;
+ }
+
+ ok = iov_advance(&iov, &num_iov, cur->cqe.res);
+ if (!ok) {
+ /* This is not expected! */
+ DBG_ERR("iov_advance() failed cur->cqe.res=%d > iov_len=%d\n",
+ (int)cur->cqe.res,
+ (int)state->iov.iov_len);
+ tevent_req_error(cur->req, EIO);
+ return;
+ }
+
+ /* sys_valid_io_range() already checked the boundaries */
+ state->nread += state->ur.cqe.res;
+ if (num_iov == 0) {
+ /* We're done */
+ tevent_req_done(cur->req);
+ return;
+ }
+
+ /*
+ * sys_valid_io_range() already checked the boundaries
+ * now try to get the rest.
+ */
+ state->offset += state->ur.cqe.res;
+ vfs_io_uring_pread_submit(state);
+}
+
+static ssize_t vfs_io_uring_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_io_uring_pread_state *state = tevent_req_data(
+ req, struct vfs_io_uring_pread_state);
+ ssize_t ret;
+
+ SMBPROFILE_BYTES_ASYNC_END(state->ur.profile_bytes);
+ vfs_aio_state->duration = nsec_time_diff(&state->ur.end_time,
+ &state->ur.start_time);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ vfs_aio_state->error = 0;
+ ret = state->nread;
+
+ tevent_req_received(req);
+ return ret;
+}
+
+struct vfs_io_uring_pwrite_state {
+ struct files_struct *fsp;
+ off_t offset;
+ struct iovec iov;
+ size_t nwritten;
+ struct vfs_io_uring_request ur;
+};
+
+static void vfs_io_uring_pwrite_submit(struct vfs_io_uring_pwrite_state *state);
+static void vfs_io_uring_pwrite_completion(struct vfs_io_uring_request *cur,
+ const char *location);
+
+static struct tevent_req *vfs_io_uring_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct vfs_io_uring_pwrite_state *state = NULL;
+ struct vfs_io_uring_config *config = NULL;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct vfs_io_uring_config,
+ smb_panic(__location__));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_io_uring_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ur.config = config;
+ state->ur.req = req;
+ state->ur.completion_fn = vfs_io_uring_pwrite_completion;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_pwrite, profile_p,
+ state->ur.profile_bytes, n);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->ur.profile_bytes);
+
+ ok = sys_valid_io_range(offset, n);
+ if (!ok) {
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ state->fsp = fsp;
+ state->offset = offset;
+ state->iov.iov_base = discard_const(data);
+ state->iov.iov_len = n;
+ vfs_io_uring_pwrite_submit(state);
+
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_defer_callback(req, ev);
+ return req;
+}
+
+static void vfs_io_uring_pwrite_submit(struct vfs_io_uring_pwrite_state *state)
+{
+ io_uring_prep_writev(&state->ur.sqe,
+ fsp_get_io_fd(state->fsp),
+ &state->iov, 1,
+ state->offset);
+ vfs_io_uring_request_submit(&state->ur);
+}
+
+static void vfs_io_uring_pwrite_completion(struct vfs_io_uring_request *cur,
+ const char *location)
+{
+ struct vfs_io_uring_pwrite_state *state = tevent_req_data(
+ cur->req, struct vfs_io_uring_pwrite_state);
+ struct iovec *iov = &state->iov;
+ int num_iov = 1;
+ bool ok;
+
+ /*
+ * We rely on being inside the _send() function
+ * or tevent_req_defer_callback() being called
+ * already.
+ */
+
+ if (cur->cqe.res < 0) {
+ int err = -cur->cqe.res;
+ _tevent_req_error(cur->req, err, location);
+ return;
+ }
+
+ if (cur->cqe.res == 0) {
+ /*
+ * Ensure we can never spin.
+ */
+ tevent_req_error(cur->req, ENOSPC);
+ return;
+ }
+
+ ok = iov_advance(&iov, &num_iov, cur->cqe.res);
+ if (!ok) {
+ /* This is not expected! */
+ DBG_ERR("iov_advance() failed cur->cqe.res=%d > iov_len=%d\n",
+ (int)cur->cqe.res,
+ (int)state->iov.iov_len);
+ tevent_req_error(cur->req, EIO);
+ return;
+ }
+
+ /* sys_valid_io_range() already checked the boundaries */
+ state->nwritten += state->ur.cqe.res;
+ if (num_iov == 0) {
+ /* We're done */
+ tevent_req_done(cur->req);
+ return;
+ }
+
+ /*
+ * sys_valid_io_range() already checked the boundaries
+ * now try to write the rest.
+ */
+ state->offset += state->ur.cqe.res;
+ vfs_io_uring_pwrite_submit(state);
+}
+
+static ssize_t vfs_io_uring_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_io_uring_pwrite_state *state = tevent_req_data(
+ req, struct vfs_io_uring_pwrite_state);
+ ssize_t ret;
+
+ SMBPROFILE_BYTES_ASYNC_END(state->ur.profile_bytes);
+ vfs_aio_state->duration = nsec_time_diff(&state->ur.end_time,
+ &state->ur.start_time);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ vfs_aio_state->error = 0;
+ ret = state->nwritten;
+
+ tevent_req_received(req);
+ return ret;
+}
+
+struct vfs_io_uring_fsync_state {
+ struct vfs_io_uring_request ur;
+};
+
+static void vfs_io_uring_fsync_completion(struct vfs_io_uring_request *cur,
+ const char *location);
+
+static struct tevent_req *vfs_io_uring_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req = NULL;
+ struct vfs_io_uring_fsync_state *state = NULL;
+ struct vfs_io_uring_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct vfs_io_uring_config,
+ smb_panic(__location__));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_io_uring_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ur.config = config;
+ state->ur.req = req;
+ state->ur.completion_fn = vfs_io_uring_fsync_completion;
+
+ SMBPROFILE_BYTES_ASYNC_START(syscall_asys_fsync, profile_p,
+ state->ur.profile_bytes, 0);
+ SMBPROFILE_BYTES_ASYNC_SET_IDLE(state->ur.profile_bytes);
+
+ io_uring_prep_fsync(&state->ur.sqe,
+ fsp_get_io_fd(fsp),
+ 0); /* fsync_flags */
+ vfs_io_uring_request_submit(&state->ur);
+
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_defer_callback(req, ev);
+ return req;
+}
+
+static void vfs_io_uring_fsync_completion(struct vfs_io_uring_request *cur,
+ const char *location)
+{
+ /*
+ * We rely on being inside the _send() function
+ * or tevent_req_defer_callback() being called
+ * already.
+ */
+
+ if (cur->cqe.res < 0) {
+ int err = -cur->cqe.res;
+ _tevent_req_error(cur->req, err, location);
+ return;
+ }
+
+ if (cur->cqe.res > 0) {
+ /* This is not expected! */
+ DBG_ERR("got cur->cqe.res=%d\n", (int)cur->cqe.res);
+ tevent_req_error(cur->req, EIO);
+ return;
+ }
+
+ tevent_req_done(cur->req);
+}
+
+static int vfs_io_uring_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct vfs_io_uring_fsync_state *state = tevent_req_data(
+ req, struct vfs_io_uring_fsync_state);
+
+ SMBPROFILE_BYTES_ASYNC_END(state->ur.profile_bytes);
+ vfs_aio_state->duration = nsec_time_diff(&state->ur.end_time,
+ &state->ur.start_time);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ vfs_aio_state->error = 0;
+
+ tevent_req_received(req);
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_io_uring_fns = {
+ .connect_fn = vfs_io_uring_connect,
+ .pread_send_fn = vfs_io_uring_pread_send,
+ .pread_recv_fn = vfs_io_uring_pread_recv,
+ .pwrite_send_fn = vfs_io_uring_pwrite_send,
+ .pwrite_recv_fn = vfs_io_uring_pwrite_recv,
+ .fsync_send_fn = vfs_io_uring_fsync_send,
+ .fsync_recv_fn = vfs_io_uring_fsync_recv,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_io_uring_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "io_uring", &vfs_io_uring_fns);
+}
diff --git a/source3/modules/vfs_linux_xfs_sgid.c b/source3/modules/vfs_linux_xfs_sgid.c
new file mode 100644
index 0000000..a08e2d4
--- /dev/null
+++ b/source3/modules/vfs_linux_xfs_sgid.c
@@ -0,0 +1,128 @@
+/*
+ * Module to work around a bug in Linux XFS:
+ * http://oss.sgi.com/bugzilla/show_bug.cgi?id=280
+ *
+ * Copyright (c) Volker Lendecke 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+
+static int linux_xfs_sgid_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ struct smb_filename *dname = NULL;
+ struct smb_filename *fname = NULL;
+ int mkdir_res;
+ int res;
+ NTSTATUS status;
+
+ DEBUG(10, ("Calling linux_xfs_sgid_mkdirat(%s)\n",
+ smb_fname->base_name));
+
+ mkdir_res = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+ if (mkdir_res == -1) {
+ DEBUG(10, ("SMB_VFS_NEXT_MKDIRAT returned error: %s\n",
+ strerror(errno)));
+ return mkdir_res;
+ }
+
+ fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (fname == NULL) {
+ return -1;
+ }
+
+ status = SMB_VFS_PARENT_PATHNAME(handle->conn,
+ talloc_tos(),
+ fname,
+ &dname,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("SMB_VFS_PARENT_PATHNAME() failed with %s\n",
+ nt_errstr(status));
+ /* return success, we did the mkdir */
+ return mkdir_res;
+ }
+
+ res = SMB_VFS_NEXT_STAT(handle, dname);
+ if (res == -1) {
+ DBG_DEBUG("NEXT_STAT(%s) failed: %s\n",
+ smb_fname_str_dbg(dname),
+ strerror(errno));
+ /* return success, we did the mkdir */
+ return mkdir_res;
+ }
+ if ((dname->st.st_ex_mode & S_ISGID) == 0) {
+ /* No SGID to inherit */
+ DEBUG(10, ("No SGID to inherit\n"));
+ TALLOC_FREE(dname);
+ return mkdir_res;
+ }
+ TALLOC_FREE(dname);
+
+ res = SMB_VFS_NEXT_STAT(handle, fname);
+ if (res == -1) {
+ DBG_NOTICE("Could not stat just created dir %s: %s\n",
+ smb_fname_str_dbg(fname),
+ strerror(errno));
+ /* return success, we did the mkdir */
+ TALLOC_FREE(fname);
+ return mkdir_res;
+ }
+ fname->st.st_ex_mode |= S_ISGID;
+ fname->st.st_ex_mode &= ~S_IFDIR;
+
+ /*
+ * Yes, we have to do this as root. If you do it as
+ * non-privileged user, XFS on Linux will just ignore us and
+ * return success. What can you do...
+ */
+ become_root();
+ res = SMB_VFS_NEXT_FCHMOD(handle, smb_fname->fsp, fname->st.st_ex_mode);
+ unbecome_root();
+
+ if (res == -1) {
+ DBG_NOTICE("CHMOD(%s, %o) failed: %s\n",
+ smb_fname_str_dbg(fname),
+ (int)fname->st.st_ex_mode,
+ strerror(errno));
+ /* return success, we did the mkdir */
+ TALLOC_FREE(fname);
+ return mkdir_res;
+ }
+
+ TALLOC_FREE(fname);
+ return mkdir_res;
+}
+
+static struct vfs_fn_pointers linux_xfs_sgid_fns = {
+ .mkdirat_fn = linux_xfs_sgid_mkdirat,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_linux_xfs_sgid_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "linux_xfs_sgid", &linux_xfs_sgid_fns);
+}
diff --git a/source3/modules/vfs_media_harmony.c b/source3/modules/vfs_media_harmony.c
new file mode 100644
index 0000000..a027254
--- /dev/null
+++ b/source3/modules/vfs_media_harmony.c
@@ -0,0 +1,1873 @@
+/*
+ * $Id: media_harmony.c,v 1.1 2007/11/06 10:07:22 stuart_hc Exp $
+ *
+ * Samba VFS module supporting multiple AVID clients sharing media.
+ *
+ * Copyright (C) 2005 Philip de Nier <philipn@users.sourceforge.net>
+ * Copyright (C) 2012 Andrew Klaassen <clawsoon@yahoo.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+
+/*
+ * Media Harmony is a Samba VFS module that allows multiple AVID
+ * clients to share media. Each client sees their own copy of the
+ * AVID msmMMOB.mdb and msmFMID.pmr files and Creating directories.
+ *
+ * Add this module to the vfs objects option in your Samba share
+ * configuration.
+ * eg.
+ *
+ * [avid_win]
+ * path = /video
+ * vfs objects = media_harmony
+ * ...
+ *
+ * It is recommended that you separate out Samba shares for Mac
+ * and Windows clients, and add the following options to the shares
+ * for Windows clients (NOTE: replace @ with *):
+ *
+ * veto files = /.DS_Store/._@/.Trash@/.Spotlight@/.hidden/.hotfiles@/.vol/
+ * delete veto files = yes
+ *
+ * This prevents hidden files from Mac clients interfering with Windows
+ * clients. If you find any more problem hidden files then add them to
+ * the list.
+ *
+ *
+ * Andrew Klaassen, 2012-03-14
+ * To prevent Avid clients from interrupting each other (via Avid's habit
+ * of launching a database refresh whenever it notices an mtime update
+ * on media directories, i.e. whenever one editor adds new material to a
+ * shared share), I've added code that causes stat information for anything
+ * directly under "Avid MediaFile/MXF" to be taken from
+ * dirname_clientaddr_clientuser if it exists. These files ~aren't~
+ * hidden, unlike the client-suffixed database files.
+ *
+ * For example, stat information for
+ * Avid MediaFiles/MXF/1
+ * will come from
+ * Avid MediaFiles/MXF/1_192.168.1.10_dave
+ * for dave working on 192.168.1.10, but will come from
+ * Avid MediaFile/MXF/1_192.168.1.11_susan
+ * for susan working on 192.168.1.11. If those alternate
+ * directories don't exist, the user will get the actual directory's stat
+ * info. When an editor wants to force a database refresh, they update
+ * the mtime on "their" file. This will cause Avid
+ * on that client to see an updated mtime for "Avid MediaFiles/MXF/1",
+ * which will trigger an Avid database refresh just for that editor.
+ *
+ *
+ * Notes:
+ * - This module is designed to work with AVID editing applications that
+ * look in the Avid MediaFiles or OMFI MediaFiles directory for media.
+ * It is not designed to work as expected in all circumstances for
+ * general use. For example: it is possibly to open client specific
+ * files such as msmMMOB.mdb_192.168.1.10_userx even though is doesn't
+ * show up in a directory listing.
+ *
+ */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "../smbd/globals.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+
+#define MH_INFO_DEBUG 10
+#define MH_ERR_DEBUG 0
+
+static const char* MDB_FILENAME = "msmMMOB.mdb";
+static const size_t MDB_FILENAME_LEN = 11;
+static const char* PMR_FILENAME = "msmFMID.pmr";
+static const size_t PMR_FILENAME_LEN = 11;
+static const char* CREATING_DIRNAME = "Creating";
+static const size_t CREATING_DIRNAME_LEN = 8;
+static const char* AVID_MEDIAFILES_DIRNAME = "Avid MediaFiles";
+static const size_t AVID_MEDIAFILES_DIRNAME_LEN = 15;
+static const char* OMFI_MEDIAFILES_DIRNAME = "OMFI MediaFiles";
+static const size_t OMFI_MEDIAFILES_DIRNAME_LEN = 15;
+static const char* APPLE_DOUBLE_PREFIX = "._";
+static const size_t APPLE_DOUBLE_PREFIX_LEN = 2;
+static const char* AVID_MXF_DIRNAME = "Avid MediaFiles/MXF";
+static const size_t AVID_MXF_DIRNAME_LEN = 19;
+
+static int vfs_mh_debug_level = DBGC_VFS;
+
+/* supplements the directory list stream */
+typedef struct mh_dirinfo_struct
+{
+ DIR* dirstream;
+ char *dirpath;
+ char *clientPath;
+ bool isInMediaFiles;
+ char *clientMDBFilename;
+ char *clientPMRFilename;
+ char *clientCreatingDirname;
+} mh_dirinfo_struct;
+
+
+/* Add "_<ip address>_<user name>" suffix to path or filename.
+ *
+ * Success: return 0
+ * Failure: set errno, path NULL, return -1
+ */
+static int alloc_append_client_suffix(vfs_handle_struct *handle,
+ char **path)
+{
+ int status = 0;
+ char *raddr = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with *path '%s'\n", *path));
+
+ raddr = tsocket_address_inet_addr_string(
+ handle->conn->sconn->remote_address, talloc_tos());
+ if (raddr == NULL)
+ {
+ errno = ENOMEM;
+ status = -1;
+ goto err;
+ }
+
+ /* talloc_asprintf_append uses talloc_realloc, which
+ * frees original 'path' memory so we don't have to.
+ */
+ *path = talloc_asprintf_append(*path, "_%s_%s",
+ raddr,
+ handle->conn->session_info->unix_info->sanitized_username);
+ if (*path == NULL)
+ {
+ DEBUG(MH_ERR_DEBUG, ("alloc_append_client_suffix "
+ "out of memory\n"));
+ errno = ENOMEM;
+ status = -1;
+ goto err;
+ }
+ DEBUG(MH_INFO_DEBUG, ("Leaving with *path '%s'\n", *path));
+err:
+ TALLOC_FREE(raddr);
+ return status;
+}
+
+
+/* Returns True if the file or directory begins with the appledouble
+ * prefix.
+ */
+static bool is_apple_double(const char* fname)
+{
+ bool ret = False;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with fname '%s'\n", fname));
+
+ if (strncmp(APPLE_DOUBLE_PREFIX, fname, APPLE_DOUBLE_PREFIX_LEN)
+ == 0)
+ {
+ ret = True;
+ }
+ DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n",
+ ret == True ? "True" : "False"));
+ return ret;
+}
+
+static bool starts_with_media_dir(const char* media_dirname,
+ size_t media_dirname_len, const char* path)
+{
+ bool ret = False;
+ const char *path_start;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with media_dirname '%s' "
+ "path '%s'\n", media_dirname, path));
+
+ /* Sometimes Samba gives us "./OMFI MediaFiles". */
+ if (strncmp(path, "./", 2) == 0)
+ {
+ path_start = &path[2];
+ }
+ else {
+ path_start = path;
+ }
+
+ if (strncmp(media_dirname, path_start, media_dirname_len) == 0
+ &&
+ (
+ path_start[media_dirname_len] == '\0'
+ ||
+ path_start[media_dirname_len] == '/'
+ )
+ )
+ {
+ ret = True;
+ }
+
+ DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n",
+ ret == True ? "True" : "False"));
+ return ret;
+}
+
+/*
+ * Returns True if the file or directory referenced by the path is below
+ * the AVID_MEDIAFILES_DIRNAME or OMFI_MEDIAFILES_DIRNAME directory
+ * The AVID_MEDIAFILES_DIRNAME and OMFI_MEDIAFILES_DIRNAME are assumed to
+ * be in the root directory, which is generally a safe assumption
+ * in the fixed-path world of Avid.
+ */
+static bool is_in_media_files(const char* path)
+{
+ bool ret = False;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n", path));
+
+ if (
+ starts_with_media_dir(AVID_MEDIAFILES_DIRNAME,
+ AVID_MEDIAFILES_DIRNAME_LEN, path)
+ ||
+ starts_with_media_dir(OMFI_MEDIAFILES_DIRNAME,
+ OMFI_MEDIAFILES_DIRNAME_LEN, path)
+ )
+ {
+ ret = True;
+ }
+ DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n",
+ ret == True ? "True" : "False"));
+ return ret;
+}
+
+/*
+ * Returns depth of path under media directory. Deals with the
+ * occasional ..../. and ..../.. paths that get passed to stat.
+ *
+ * Assumes is_in_media_files has already been called and has returned
+ * true for the path; if it hasn't, this function will likely crash
+ * and burn.
+ *
+ * Not foolproof; something like "Avid MediaFiles/MXF/../foo/1"
+ * would fool it. Haven't seen paths like that getting to the
+ * stat function yet, so ignoring that possibility for now.
+ */
+static int depth_from_media_dir(const char* media_dirname,
+ size_t media_dirname_len, const char* path)
+{
+ int transition_count = 0;
+ const char *path_start;
+ const char *pathPtr;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with media_dirname '%s' "
+ "path '%s'\n", media_dirname, path));
+
+ /* Sometimes Samba gives us "./OMFI MediaFiles". */
+ if (strncmp(path, "./", 2) == 0)
+ {
+ path_start = &path[2];
+ }
+ else {
+ path_start = path;
+ }
+
+ if (path_start[media_dirname_len] == '\0')
+ {
+ goto out;
+ }
+
+ pathPtr = &path_start[media_dirname_len + 1];
+
+ while(1)
+ {
+ if (*pathPtr == '\0' || *pathPtr == '/')
+ {
+ if (
+ *(pathPtr - 1) == '.'
+ &&
+ *(pathPtr - 2) == '.'
+ &&
+ *(pathPtr - 3) == '/'
+ )
+ {
+ transition_count--;
+ }
+ else if (
+ !
+ (
+ *(pathPtr - 1) == '/'
+ ||
+ (
+ *(pathPtr - 1) == '.'
+ &&
+ *(pathPtr - 2) == '/'
+ )
+ )
+ )
+ {
+ transition_count++;
+ }
+ }
+ if (*pathPtr == '\0')
+ {
+ break;
+ }
+ pathPtr++;
+ }
+
+ DEBUG(MH_INFO_DEBUG, ("Leaving with transition_count '%i'\n",
+ transition_count));
+out:
+ return transition_count;
+}
+
+/* Identifies MDB and PMR files at end of path. */
+static bool is_avid_database(
+ char *path,
+ size_t path_len,
+ const char *avid_db_filename,
+ const size_t avid_db_filename_len)
+{
+ bool ret = False;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with path '%s', "
+ "avid_db_filename '%s', "
+ "path_len '%i', "
+ "avid_db_filename_len '%i'\n",
+ path, avid_db_filename,
+ (int)path_len, (int)avid_db_filename_len));
+
+ if (
+ path_len > avid_db_filename_len
+ &&
+ strcmp(&path[path_len - avid_db_filename_len],
+ avid_db_filename) == 0
+ &&
+ (
+ path[path_len - avid_db_filename_len - 1] == '/'
+ ||
+ (path_len > avid_db_filename_len
+ + APPLE_DOUBLE_PREFIX_LEN
+ &&
+ path[path_len - avid_db_filename_len
+ - APPLE_DOUBLE_PREFIX_LEN - 1] == '/'
+ &&
+ is_apple_double(&path[path_len
+ - avid_db_filename_len
+ - APPLE_DOUBLE_PREFIX_LEN]))
+ )
+ )
+ {
+ ret = True;
+ }
+ DEBUG(MH_INFO_DEBUG, ("Leaving with ret '%s'\n",
+ ret == True ? "True" : "False"));
+ return ret;
+}
+
+
+/* Add client suffix to paths to MDB_FILENAME, PMR_FILENAME and
+ * CREATING_SUBDIRNAME.
+ *
+ * Caller must free newPath.
+ *
+ * Success: return 0
+ * Failure: set errno, newPath NULL, return -1
+ */
+static int alloc_get_client_path(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const char *path,
+ char **newPath)
+{
+ /* replace /CREATING_DIRNAME/ or /._CREATING_DIRNAME/
+ * directory in path - potentially in middle of path
+ * - with suffixed name.
+ */
+ int status = 0;
+ char* pathPtr;
+ size_t intermPathLen;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n", path));
+
+ *newPath = talloc_strdup(ctx, path);
+ if (*newPath == NULL)
+ {
+ DEBUG(MH_ERR_DEBUG, ("alloc_get_client_path ENOMEM #1\n"));
+ errno = ENOMEM;
+ status = -1;
+ goto out;
+ }
+ DEBUG(MH_INFO_DEBUG, ("newPath #1 %s\n", *newPath));
+ if (
+ (pathPtr = strstr(path, CREATING_DIRNAME)) != NULL
+ &&
+ (
+ *(pathPtr + CREATING_DIRNAME_LEN) == '\0'
+ ||
+ *(pathPtr + CREATING_DIRNAME_LEN) == '/'
+ )
+ &&
+ (
+ (pathPtr - path > 0
+ &&
+ *(pathPtr - 1) == '/')
+ ||
+ (pathPtr - path > APPLE_DOUBLE_PREFIX_LEN
+ &&
+ *(pathPtr - APPLE_DOUBLE_PREFIX_LEN - 1) == '/'
+ &&
+ is_apple_double(pathPtr - APPLE_DOUBLE_PREFIX_LEN))
+ )
+ )
+ {
+ /* Insert client suffix into path. */
+ (*newPath)[pathPtr - path + CREATING_DIRNAME_LEN] = '\0';
+ DEBUG(MH_INFO_DEBUG, ("newPath #2 %s\n", *newPath));
+
+ if ((status = alloc_append_client_suffix(handle, newPath)))
+ {
+ goto out;
+ }
+
+ DEBUG(MH_INFO_DEBUG, ("newPath #3 %s\n", *newPath));
+ *newPath = talloc_strdup_append(*newPath,
+ pathPtr + CREATING_DIRNAME_LEN);
+ if (*newPath == NULL)
+ {
+ DEBUG(MH_ERR_DEBUG, ("alloc_get_client_path "
+ "ENOMEM #2\n"));
+ errno = ENOMEM;
+ status = -1;
+ goto out;
+ }
+ DEBUG(MH_INFO_DEBUG, ("newPath #4 %s\n", *newPath));
+ }
+
+ /* replace /MDB_FILENAME or /PMR_FILENAME or /._MDB_FILENAME
+ * or /._PMR_FILENAME at newPath end with suffixed name.
+ */
+ intermPathLen = strlen(*newPath);
+ if (
+ is_avid_database(*newPath, intermPathLen,
+ MDB_FILENAME, MDB_FILENAME_LEN)
+ ||
+ is_avid_database(*newPath, intermPathLen,
+ PMR_FILENAME, PMR_FILENAME_LEN)
+ )
+ {
+ DEBUG(MH_INFO_DEBUG, ("newPath #5 %s\n", *newPath));
+ if ((status = alloc_append_client_suffix(handle, newPath)))
+ {
+ goto out;
+ }
+ DEBUG(MH_INFO_DEBUG, ("newPath #6 %s\n", *newPath));
+ }
+out:
+ /* newPath must be freed in caller. */
+ DEBUG(MH_INFO_DEBUG, ("Leaving with *newPath '%s'\n", *newPath));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int alloc_get_client_smb_fname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname,
+ struct smb_filename **clientFname)
+{
+ int status = 0;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ *clientFname = cp_smb_filename(ctx, smb_fname);
+ if ((*clientFname) == NULL) {
+ DEBUG(MH_ERR_DEBUG, ("alloc_get_client_smb_fname "
+ "NTERR\n"));
+ errno = ENOMEM;
+ status = -1;
+ goto err;
+ }
+ if ((status = alloc_get_client_path(handle, ctx,
+ smb_fname->base_name,
+ &(*clientFname)->base_name)))
+ {
+ goto err;
+ }
+ DEBUG(MH_INFO_DEBUG, ("Leaving with (*clientFname)->base_name "
+ "'%s'\n", (*clientFname)->base_name));
+err:
+ return status;
+}
+
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int alloc_set_client_dirinfo_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ char **path,
+ const char *avid_db_filename)
+{
+ int status = 0;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with avid_db_filename '%s'\n",
+ avid_db_filename));
+
+ if ((*path = talloc_strdup(ctx, avid_db_filename)) == NULL)
+ {
+ DEBUG(MH_ERR_DEBUG, ("alloc_set_client_dirinfo_path "
+ "ENOMEM\n"));
+ errno = ENOMEM;
+ status = -1;
+ goto err;
+ }
+ if ((status = alloc_append_client_suffix(handle, path)))
+ {
+ goto err;
+ }
+ DEBUG(MH_INFO_DEBUG, ("Leaving with *path '%s'\n", *path));
+err:
+ return status;
+}
+
+/*
+ * Replace mtime on clientFname with mtime from client-suffixed
+ * equivalent, if it exists.
+ *
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int set_fake_mtime(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ struct smb_filename **clientFname,
+ int (*statFn)(const char *, SMB_STRUCT_STAT *, bool))
+{
+ int status = 0;
+ char *statPath;
+ SMB_STRUCT_STAT fakeStat;
+ int copy_len;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with (*clientFname)->base_name "
+ "'%s', (*clientFname)->st.st_ex_mtime %s",
+ (*clientFname)->base_name,
+ ctime(&((*clientFname)->st.st_ex_mtime.tv_sec))));
+
+ if (
+ depth_from_media_dir(AVID_MXF_DIRNAME,
+ AVID_MXF_DIRNAME_LEN,
+ (*clientFname)->base_name)
+ != 1
+ &&
+ depth_from_media_dir(OMFI_MEDIAFILES_DIRNAME,
+ OMFI_MEDIAFILES_DIRNAME_LEN,
+ (*clientFname)->base_name)
+ != 0
+ )
+ {
+ goto out;
+ }
+
+ copy_len = strlen((*clientFname)->base_name);
+
+ /* Hack to deal with occasional "Avid MediaFiles/MXF/1/." paths.
+ * We know we're under a media dir, so paths are at least 2 chars
+ * long.
+ */
+ if ((*clientFname)->base_name[copy_len - 1] == '.' &&
+ (*clientFname)->base_name[copy_len - 2] == '/')
+ {
+ copy_len -= 2;
+ }
+
+ if (((statPath = talloc_strndup(ctx,
+ (*clientFname)->base_name, copy_len)) == NULL))
+ {
+ errno = ENOMEM;
+ status = -1;
+ goto err;
+ }
+ if ((status = alloc_append_client_suffix(handle, &statPath)))
+ {
+ goto err;
+ }
+
+ DEBUG(MH_INFO_DEBUG, ("Fake stat'ing '%s'\n", statPath));
+ if (statFn(statPath, &fakeStat,
+ lp_fake_directory_create_times(SNUM(handle->conn))))
+ {
+ /* This can fail for legitimate reasons - i.e. the
+ * fakeStat directory doesn't exist, which is okay
+ * - so we don't set status. But if it does fail,
+ * we need to skip over the mtime assignment.
+ */
+ goto err;
+ }
+
+ DEBUG(MH_INFO_DEBUG, ("Setting fake mtime from '%s'\n", statPath));
+ (*clientFname)->st.st_ex_mtime = fakeStat.st_ex_mtime;
+err:
+ TALLOC_FREE(statPath);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with (*clientFname)->base_name "
+ "'%s', (*clientFname)->st.st_ex_mtime %s",
+ (*clientFname)->base_name,
+ ctime(&((*clientFname)->st.st_ex_mtime.tv_sec))));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ int status;
+ struct smb_filename *clientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n",
+ smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name))
+ {
+ status = SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf);
+ goto out;
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ smb_fname,
+ &clientFname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_STATVFS(handle, clientFname, statbuf);
+err:
+ TALLOC_FREE(clientFname);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with path '%s'\n",
+ smb_fname->base_name));
+ return status;
+}
+
+static int alloc_set_client_dirinfo(vfs_handle_struct *handle,
+ const char *fname,
+ struct mh_dirinfo_struct **dirInfo)
+{
+ int status = 0;
+ char *clientPath;
+ TALLOC_CTX *ctx;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with fname '%s'\n", fname));
+
+ *dirInfo = talloc(NULL, struct mh_dirinfo_struct);
+ if (*dirInfo == NULL)
+ {
+ goto err;
+ }
+
+ (*dirInfo)->dirpath = talloc_strdup(*dirInfo, fname);
+ if ((*dirInfo)->dirpath == NULL)
+ {
+ goto err;
+ }
+
+ if (!is_in_media_files(fname))
+ {
+ (*dirInfo)->clientPath = NULL;
+ (*dirInfo)->clientMDBFilename = NULL;
+ (*dirInfo)->clientPMRFilename = NULL;
+ (*dirInfo)->clientCreatingDirname = NULL;
+ (*dirInfo)->isInMediaFiles = False;
+ goto out;
+ }
+
+ (*dirInfo)->isInMediaFiles = True;
+
+ if (alloc_set_client_dirinfo_path(handle,
+ *dirInfo,
+ &((*dirInfo)->clientMDBFilename),
+ MDB_FILENAME))
+ {
+ goto err;
+ }
+
+ if (alloc_set_client_dirinfo_path(handle,
+ *dirInfo,
+ &((*dirInfo)->clientPMRFilename),
+ PMR_FILENAME))
+ {
+ goto err;
+ }
+
+ if (alloc_set_client_dirinfo_path(handle,
+ *dirInfo,
+ &((*dirInfo)->clientCreatingDirname),
+ CREATING_DIRNAME))
+ {
+ goto err;
+ }
+
+ clientPath = NULL;
+ ctx = talloc_tos();
+
+ if (alloc_get_client_path(handle, ctx,
+ fname,
+ &clientPath))
+ {
+ goto err;
+ }
+
+ (*dirInfo)->clientPath = talloc_strdup(*dirInfo, clientPath);
+ if ((*dirInfo)->clientPath == NULL)
+ {
+ goto err;
+ }
+
+ TALLOC_FREE(clientPath);
+
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with (*dirInfo)->dirpath '%s', "
+ "(*dirInfo)->clientPath '%s'\n",
+ (*dirInfo)->dirpath,
+ (*dirInfo)->clientPath));
+ return status;
+
+err:
+ DEBUG(MH_ERR_DEBUG, ("Failing with fname '%s'\n", fname));
+ TALLOC_FREE(*dirInfo);
+ status = -1;
+ errno = ENOMEM;
+ return status;
+}
+
+static DIR *mh_fdopendir(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr)
+{
+ struct mh_dirinfo_struct *dirInfo = NULL;
+ DIR *dirstream;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with fsp->fsp_name->base_name '%s'\n",
+ fsp->fsp_name->base_name));
+
+ dirstream = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr);
+ if (!dirstream)
+ {
+ goto err;
+ }
+
+ if (alloc_set_client_dirinfo(handle, fsp->fsp_name->base_name,
+ &dirInfo))
+ {
+ goto err;
+ }
+
+ dirInfo->dirstream = dirstream;
+
+ if (! dirInfo->isInMediaFiles) {
+ goto out;
+ }
+
+ if (set_fake_mtime(handle, fsp, &(fsp->fsp_name), sys_stat))
+ {
+ goto err;
+ }
+
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with dirInfo->dirpath '%s', "
+ "dirInfo->clientPath '%s', "
+ "fsp->fsp_name->st.st_ex_mtime %s",
+ dirInfo->dirpath,
+ dirInfo->clientPath,
+ ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec))));
+ /* Success is freed in closedir. */
+ return (DIR *) dirInfo;
+err:
+ /* Failure is freed here. */
+ DEBUG(MH_ERR_DEBUG, ("Failing with fsp->fsp_name->base_name '%s'\n",
+ fsp->fsp_name->base_name));
+ TALLOC_FREE(dirInfo);
+ return NULL;
+}
+
+/*
+ * skip MDB_FILENAME and PMR_FILENAME filenames and CREATING_DIRNAME
+ * directory, skip other client's suffixed MDB_FILENAME and PMR_FILENAME
+ * filenames and CREATING_DIRNAME directory, replace this client's
+ * suffixed MDB_FILENAME and PMR_FILENAME filenames and CREATING_DIRNAME
+ * directory with non suffixed.
+ *
+ * Success: return dirent
+ * End of data: return NULL
+ * Failure: set errno, return NULL
+ */
+static struct dirent *
+mh_readdir(vfs_handle_struct *handle, struct files_struct *dirfsp, DIR *dirp)
+{
+ mh_dirinfo_struct* dirInfo = (mh_dirinfo_struct*)dirp;
+ struct dirent *d = NULL;
+ int skip;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_readdir\n"));
+
+ DEBUG(MH_INFO_DEBUG, ("dirInfo->dirpath '%s', "
+ "dirInfo->clientPath '%s', "
+ "dirInfo->isInMediaFiles '%s', "
+ "dirInfo->clientMDBFilename '%s', "
+ "dirInfo->clientPMRFilename '%s', "
+ "dirInfo->clientCreatingDirname '%s'\n",
+ dirInfo->dirpath,
+ dirInfo->clientPath,
+ dirInfo->isInMediaFiles ? "True" : "False",
+ dirInfo->clientMDBFilename,
+ dirInfo->clientPMRFilename,
+ dirInfo->clientCreatingDirname));
+
+ if (! dirInfo->isInMediaFiles)
+ {
+ d = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream);
+ goto out;
+ }
+
+ do
+ {
+ const char* dname;
+ bool isAppleDouble;
+
+ skip = False;
+ d = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream);
+
+ if (d == NULL)
+ {
+ break;
+ }
+
+ /* ignore apple double prefix for logic below */
+ if (is_apple_double(d->d_name))
+ {
+ dname = &d->d_name[APPLE_DOUBLE_PREFIX_LEN];
+ isAppleDouble = True;
+ }
+ else
+ {
+ dname = d->d_name;
+ isAppleDouble = False;
+ }
+
+ /* skip Avid-special files with no client suffix */
+ if (
+ strcmp(dname, MDB_FILENAME) == 0
+ ||
+ strcmp(dname, PMR_FILENAME) == 0
+ ||
+ strcmp(dname, CREATING_DIRNAME) == 0
+ )
+ {
+ skip = True;
+ }
+ /* chop client suffix off this client's suffixed files */
+ else if (strcmp(dname, dirInfo->clientMDBFilename) == 0)
+ {
+ if (isAppleDouble)
+ {
+ d->d_name[MDB_FILENAME_LEN
+ + APPLE_DOUBLE_PREFIX_LEN] = '\0';
+ }
+ else
+ {
+ d->d_name[MDB_FILENAME_LEN] = '\0';
+ }
+ }
+ else if (strcmp(dname, dirInfo->clientPMRFilename) == 0)
+ {
+ if (isAppleDouble)
+ {
+ d->d_name[PMR_FILENAME_LEN
+ + APPLE_DOUBLE_PREFIX_LEN] = '\0';
+ }
+ else
+ {
+ d->d_name[PMR_FILENAME_LEN] = '\0';
+ }
+ }
+ else if (strcmp(dname, dirInfo->clientCreatingDirname)
+ == 0)
+ {
+ if (isAppleDouble)
+ {
+ d->d_name[CREATING_DIRNAME_LEN
+ + APPLE_DOUBLE_PREFIX_LEN] = '\0';
+ }
+ else
+ {
+ d->d_name[CREATING_DIRNAME_LEN] = '\0';
+ }
+ }
+ /*
+ * Anything that starts as an Avid-special file
+ * that's made it this far should be skipped. This
+ * is different from the original behaviour, which
+ * only skipped other client's suffixed files.
+ */
+ else if (
+ strncmp(MDB_FILENAME, dname,
+ MDB_FILENAME_LEN) == 0
+ ||
+ strncmp(PMR_FILENAME, dname,
+ PMR_FILENAME_LEN) == 0
+ ||
+ strncmp(CREATING_DIRNAME, dname,
+ CREATING_DIRNAME_LEN) == 0
+ )
+ {
+ skip = True;
+ }
+ }
+ while (skip);
+
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving mh_readdir\n"));
+ return d;
+}
+
+/*
+ * Success: no success result defined.
+ * Failure: no failure result defined.
+ */
+static void mh_rewinddir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ DEBUG(MH_INFO_DEBUG, ("Entering and leaving mh_rewinddir\n"));
+ SMB_VFS_NEXT_REWINDDIR(handle,
+ ((mh_dirinfo_struct*)dirp)->dirstream);
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ int status;
+ struct smb_filename *clientFname = NULL;
+ const char *path = smb_fname->base_name;
+ struct smb_filename *full_fname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with path '%s'\n", path));
+
+ if (!is_in_media_files(path)) {
+ status = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+ goto out;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ full_fname,
+ &clientFname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ clientFname,
+ mode);
+err:
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(clientFname);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with path '%s'\n", path));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_closedir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ DIR *realdirp = ((mh_dirinfo_struct*)dirp)->dirstream;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_closedir\n"));
+ // Will this talloc_free destroy realdirp?
+ TALLOC_FREE(dirp);
+
+ DEBUG(MH_INFO_DEBUG, ("Leaving mh_closedir\n"));
+ return SMB_VFS_NEXT_CLOSEDIR(handle, realdirp);
+}
+
+/*
+ * Success: return non-negative file descriptor
+ * Failure: set errno, return -1
+ */
+static int mh_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int ret;
+ struct smb_filename *clientFname;
+ TALLOC_CTX *ctx;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ goto out;
+ }
+
+ clientFname = NULL;
+ ctx = talloc_tos();
+
+ if (alloc_get_client_smb_fname(handle, ctx, smb_fname, &clientFname)) {
+ ret = -1;
+ goto err;
+ }
+
+ /*
+ * What about fsp->fsp_name? We also have to get correct stat info into
+ * fsp and smb_fname for DB files, don't we?
+ */
+
+ DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->base_name '%s' "
+ "smb_fname->st.st_ex_mtime %s"
+ " fsp->fsp_name->st.st_ex_mtime %s",
+ smb_fname->base_name,
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec)),
+ ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec))));
+
+ ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, clientFname, fsp, how);
+err:
+ TALLOC_FREE(clientFname);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+ return ret;
+}
+
+/*
+ * Success: return non-negative file descriptor
+ * Failure: set errno, return -1
+ */
+static NTSTATUS mh_create_file(vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result_fsp,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS status;
+ struct smb_filename *clientFname;
+ TALLOC_CTX *ctx;
+
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+ if (!is_in_media_files(smb_fname->base_name))
+ {
+ status = SMB_VFS_NEXT_CREATE_FILE(
+ handle,
+ req,
+ dirfsp,
+ smb_fname,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ allocation_size,
+ private_flags,
+ sd,
+ ea_list,
+ result_fsp,
+ pinfo,
+ in_context_blobs,
+ out_context_blobs);
+ goto out;
+ }
+
+ clientFname = NULL;
+ ctx = talloc_tos();
+
+ if (alloc_get_client_smb_fname(handle, ctx,
+ smb_fname,
+ &clientFname))
+ {
+ status = map_nt_error_from_unix(errno);
+ goto err;
+ }
+
+ /* This only creates files, so we don't have to worry about
+ * our fake directory stat'ing here.
+ */
+ // But we still need to route stat calls for DB files
+ // properly, right?
+ status = SMB_VFS_NEXT_CREATE_FILE(
+ handle,
+ req,
+ dirfsp,
+ clientFname,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ allocation_size,
+ private_flags,
+ sd,
+ ea_list,
+ result_fsp,
+ pinfo,
+ in_context_blobs,
+ out_context_blobs);
+err:
+ TALLOC_FREE(clientFname);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->base_name '%s'"
+ "smb_fname->st.st_ex_mtime %s"
+ " fsp->fsp_name->st.st_ex_mtime %s",
+ smb_fname->base_name,
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec)),
+ (*result_fsp) && VALID_STAT((*result_fsp)->fsp_name->st) ?
+ ctime(&((*result_fsp)->fsp_name->st.st_ex_mtime.tv_sec)) :
+ "No fsp time\n"));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int status = -1;
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+ struct smb_filename *srcClientFname = NULL;
+ struct smb_filename *dstClientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with "
+ "smb_fname_src->base_name '%s', "
+ "smb_fname_dst->base_name '%s'\n",
+ smb_fname_src->base_name,
+ smb_fname_dst->base_name));
+
+ if (!is_in_media_files(smb_fname_src->base_name)
+ &&
+ !is_in_media_files(smb_fname_dst->base_name))
+ {
+ status = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ goto out;
+ }
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ if ((status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ full_fname_src,
+ &srcClientFname)))
+ {
+ goto err;
+ }
+
+ if ((status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ full_fname_dst,
+ &dstClientFname)))
+ {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp->conn->cwd_fsp,
+ srcClientFname,
+ dstfsp->conn->cwd_fsp,
+ dstClientFname);
+err:
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+ TALLOC_FREE(dstClientFname);
+ TALLOC_FREE(srcClientFname);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname_src->base_name '%s',"
+ " smb_fname_dst->base_name '%s'\n",
+ smb_fname_src->base_name,
+ smb_fname_dst->base_name));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int status = 0;
+ struct smb_filename *clientFname;
+ TALLOC_CTX *ctx;
+
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name))
+ {
+ status = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ goto out;
+ }
+
+ clientFname = NULL;
+ ctx = talloc_tos();
+
+ if ((status = alloc_get_client_smb_fname(handle, ctx,
+ smb_fname,
+ &clientFname)))
+ {
+ goto err;
+ }
+ DEBUG(MH_INFO_DEBUG, ("Stat'ing clientFname->base_name '%s'\n",
+ clientFname->base_name));
+ if ((status = SMB_VFS_NEXT_STAT(handle, clientFname)))
+ {
+ goto err;
+ }
+ if ((status = set_fake_mtime(handle, ctx, &clientFname, sys_stat)))
+ {
+ goto err;
+ }
+
+ /* Unlike functions with const smb_filename, we have to
+ * modify smb_fname itself to pass our info back up.
+ */
+ DEBUG(MH_INFO_DEBUG, ("Setting smb_fname '%s' stat "
+ "from clientFname '%s'\n",
+ smb_fname->base_name,
+ clientFname->base_name));
+ smb_fname->st = clientFname->st;
+err:
+ TALLOC_FREE(clientFname);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->st.st_ex_mtime %s",
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int status = 0;
+ struct smb_filename *clientFname;
+ TALLOC_CTX *ctx;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name))
+ {
+ status = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ goto out;
+ }
+
+ clientFname = NULL;
+ ctx = talloc_tos();
+
+ if ((status = alloc_get_client_smb_fname(handle, ctx,
+ smb_fname,
+ &clientFname)))
+ {
+ goto err;
+ }
+ if ((status = SMB_VFS_NEXT_LSTAT(handle, clientFname)))
+ {
+ goto err;
+ }
+
+ if ((status = set_fake_mtime(handle, ctx, &clientFname, sys_lstat)))
+ {
+ goto err;
+ }
+ /* Unlike functions with const smb_filename, we have to
+ * modify smb_fname itself to pass our info back up.
+ */
+ smb_fname->st = clientFname->st;
+err:
+ TALLOC_FREE(clientFname);
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with smb_fname->st.st_ex_mtime %s",
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_fstat(vfs_handle_struct *handle,
+ files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ int status = 0;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering with fsp->fsp_name->base_name "
+ "'%s'\n", fsp_str_dbg(fsp)));
+
+ if ((status = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf)))
+ {
+ goto out;
+ }
+
+ if (fsp->fsp_name == NULL
+ || !is_in_media_files(fsp->fsp_name->base_name))
+ {
+ goto out;
+ }
+
+ if ((status = mh_stat(handle, fsp->fsp_name)))
+ {
+ goto out;
+ }
+
+ *sbuf = fsp->fsp_name->st;
+out:
+ DEBUG(MH_INFO_DEBUG, ("Leaving with fsp->fsp_name->st.st_ex_mtime "
+ "%s",
+ fsp->fsp_name != NULL ?
+ ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)) :
+ "0"));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int status;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *clientFname;
+ TALLOC_CTX *ctx;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_unlinkat\n"));
+ if (!is_in_media_files(smb_fname->base_name)) {
+ status = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto out;
+ }
+
+ clientFname = NULL;
+ ctx = talloc_tos();
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ if ((status = alloc_get_client_smb_fname(handle, ctx,
+ full_fname,
+ &clientFname))) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp->conn->cwd_fsp,
+ clientFname,
+ flags);
+err:
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(clientFname);
+out:
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int status;
+ struct smb_filename *clientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_lchown\n"));
+ if (!is_in_media_files(smb_fname->base_name))
+ {
+ status = SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid);
+ goto out;
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ smb_fname,
+ &clientFname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_LCHOWN(handle, clientFname, uid, gid);
+err:
+ TALLOC_FREE(clientFname);
+out:
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int status;
+ struct smb_filename *clientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_chdir\n"));
+ if (!is_in_media_files(smb_fname->base_name)) {
+ status = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+ goto out;
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ smb_fname,
+ &clientFname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_CHDIR(handle, clientFname);
+err:
+ TALLOC_FREE(clientFname);
+out:
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+
+static int mh_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ int status = -1;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *new_link_target = NULL;
+ struct smb_filename *newclientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_symlinkat\n"));
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ new_smb_fname);
+ if (full_fname == NULL) {
+ status = -1;
+ goto err;
+ }
+
+ if (!is_in_media_files(link_contents->base_name) &&
+ !is_in_media_files(full_fname->base_name)) {
+ status = SMB_VFS_NEXT_SYMLINKAT(handle,
+ link_contents,
+ dirfsp,
+ new_smb_fname);
+ goto out;
+ }
+
+ if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ link_contents,
+ &new_link_target))) {
+ goto err;
+ }
+ if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ full_fname,
+ &newclientFname))) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_SYMLINKAT(handle,
+ new_link_target,
+ handle->conn->cwd_fsp,
+ newclientFname);
+err:
+ TALLOC_FREE(new_link_target);
+ TALLOC_FREE(newclientFname);
+out:
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+/*
+ * Success: return byte count
+ * Failure: set errno, return -1
+ */
+static int mh_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ int status;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *clientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_readlinkat\n"));
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ status = -1;
+ goto err;
+ }
+
+ if (!is_in_media_files(full_fname->base_name)) {
+ status = SMB_VFS_NEXT_READLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+ goto out;
+ }
+
+ if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ full_fname,
+ &clientFname))) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_READLINKAT(handle,
+ handle->conn->cwd_fsp,
+ clientFname,
+ buf,
+ bufsiz);
+
+err:
+ TALLOC_FREE(clientFname);
+out:
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ int status;
+ struct smb_filename *old_full_fname = NULL;
+ struct smb_filename *oldclientFname = NULL;
+ struct smb_filename *new_full_fname = NULL;
+ struct smb_filename *newclientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_linkat\n"));
+
+ old_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ old_smb_fname);
+ if (old_full_fname == NULL) {
+ status = -1;
+ goto err;
+ }
+
+ new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (new_full_fname == NULL) {
+ status = -1;
+ goto err;
+ }
+
+ if (!is_in_media_files(old_full_fname->base_name) &&
+ !is_in_media_files(new_full_fname->base_name)) {
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(new_full_fname);
+
+ status = SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+ goto out;
+ }
+
+ if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ old_full_fname,
+ &oldclientFname))) {
+ goto err;
+ }
+ if ((status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ new_full_fname,
+ &newclientFname))) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_LINKAT(handle,
+ handle->conn->cwd_fsp,
+ oldclientFname,
+ handle->conn->cwd_fsp,
+ newclientFname,
+ flags);
+
+err:
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(new_full_fname);
+ TALLOC_FREE(newclientFname);
+ TALLOC_FREE(oldclientFname);
+out:
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int mh_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ int status;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *clientFname = NULL;
+ TALLOC_CTX *ctx;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_mknodat\n"));
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ status = -1;
+ goto err;
+ }
+
+ if (!is_in_media_files(full_fname->base_name)) {
+ status = SMB_VFS_NEXT_MKNODAT(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+ goto out;
+ }
+
+ ctx = talloc_tos();
+
+ if ((status = alloc_get_client_smb_fname(handle, ctx,
+ full_fname,
+ &clientFname))) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_MKNODAT(handle,
+ handle->conn->cwd_fsp,
+ clientFname,
+ mode,
+ dev);
+
+err:
+ TALLOC_FREE(clientFname);
+out:
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+/*
+ * Success: return path pointer
+ * Failure: set errno, return NULL pointer
+ */
+static struct smb_filename *mh_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *result_fname = NULL;
+ struct smb_filename *clientFname = NULL;
+
+ DEBUG(MH_INFO_DEBUG, ("Entering mh_realpath\n"));
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname);
+ }
+
+ if (alloc_get_client_smb_fname(handle, ctx,
+ smb_fname,
+ &clientFname) != 0) {
+ goto err;
+ }
+
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, clientFname);
+err:
+ TALLOC_FREE(clientFname);
+ return result_fname;
+}
+
+/* Ignoring get_real_filename function because the default
+ * doesn't do anything.
+ */
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ * In this case, "name" is an attr name.
+ */
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers vfs_mh_fns = {
+ /* Disk operations */
+
+ .statvfs_fn = mh_statvfs,
+
+ /* Directory operations */
+
+ .fdopendir_fn = mh_fdopendir,
+ .readdir_fn = mh_readdir,
+ .rewind_dir_fn = mh_rewinddir,
+ .mkdirat_fn = mh_mkdirat,
+ .closedir_fn = mh_closedir,
+
+ /* File operations */
+
+ .openat_fn = mh_openat,
+ .create_file_fn = mh_create_file,
+ .renameat_fn = mh_renameat,
+ .stat_fn = mh_stat,
+ .lstat_fn = mh_lstat,
+ .fstat_fn = mh_fstat,
+ .unlinkat_fn = mh_unlinkat,
+ .lchown_fn = mh_lchown,
+ .chdir_fn = mh_chdir,
+ .symlinkat_fn = mh_symlinkat,
+ .readlinkat_fn = mh_readlinkat,
+ .linkat_fn = mh_linkat,
+ .mknodat_fn = mh_mknodat,
+ .realpath_fn = mh_realpath,
+
+ /* EA operations. */
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+
+ /* aio operations */
+};
+
+static_decl_vfs;
+NTSTATUS vfs_media_harmony_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "media_harmony", &vfs_mh_fns);
+ if (!NT_STATUS_IS_OK(ret))
+ {
+ goto out;
+ }
+
+ vfs_mh_debug_level = debug_add_class("media_harmony");
+
+ if (vfs_mh_debug_level == -1) {
+ vfs_mh_debug_level = DBGC_VFS;
+ DEBUG(1, ("media_harmony_init: Couldn't register custom "
+ "debugging class.\n"));
+ } else {
+ DEBUG(3, ("media_harmony_init: Debug class number of "
+ "'media_harmony': %d\n",
+ vfs_mh_debug_level));
+ }
+
+out:
+ return ret;
+}
diff --git a/source3/modules/vfs_nfs4acl_xattr.c b/source3/modules/vfs_nfs4acl_xattr.c
new file mode 100644
index 0000000..1fd3519
--- /dev/null
+++ b/source3/modules/vfs_nfs4acl_xattr.c
@@ -0,0 +1,580 @@
+/*
+ * Convert NFSv4 acls stored per http://www.suse.de/~agruen/nfs4acl/ to NT acls and vice versa.
+ *
+ * Copyright (C) Jiri Sasek, 2007
+ * based on the foobar.c module which is copyrighted by Volker Lendecke
+ * based on pvfs_acl_nfs4.c Copyright (C) Andrew Tridgell 2006
+ *
+ * based on vfs_fake_acls:
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) Andrew Bartlett, 2002,2012
+ * Copyright (C) Ralph Boehme 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "libcli/security/security_token.h"
+#include "libcli/security/dom_sid.h"
+#include "nfs4_acls.h"
+#include "librpc/gen_ndr/ndr_nfs4acl.h"
+#include "nfs4acl_xattr.h"
+#include "nfs4acl_xattr_ndr.h"
+#include "nfs4acl_xattr_xdr.h"
+#include "nfs4acl_xattr_nfs.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static const struct enum_list nfs4acl_encoding[] = {
+ {NFS4ACL_ENCODING_NDR, "ndr"},
+ {NFS4ACL_ENCODING_XDR, "xdr"},
+ {NFS4ACL_ENCODING_NFS, "nfs"},
+};
+
+/*
+ * Check if someone changed the POSIX mode, for files we expect 0666, for
+ * directories 0777. Discard the ACL blob if the mode is different.
+ */
+static bool nfs4acl_validate_blob(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ struct nfs4acl_config *config = NULL;
+ mode_t expected_mode;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return false);
+
+ if (!config->validate_mode) {
+ return true;
+ }
+
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ expected_mode = 0777;
+ } else {
+ expected_mode = 0666;
+ }
+ if ((fsp->fsp_name->st.st_ex_mode & expected_mode) == expected_mode) {
+ return true;
+ }
+
+ ret = SMB_VFS_NEXT_FREMOVEXATTR(handle,
+ fsp,
+ config->xattr_name);
+ if (ret != 0 && errno != ENOATTR) {
+ DBG_ERR("Removing NFS4 xattr failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static NTSTATUS nfs4acl_get_blob(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob)
+{
+ struct nfs4acl_config *config = NULL;
+ size_t allocsize = 256;
+ ssize_t length;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ *blob = data_blob_null;
+
+ ok = nfs4acl_validate_blob(handle, fsp);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ do {
+
+ allocsize *= 4;
+ ok = data_blob_realloc(mem_ctx, blob, allocsize);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ length = SMB_VFS_NEXT_FGETXATTR(handle,
+ fsp,
+ config->xattr_name,
+ blob->data,
+ blob->length);
+ } while (length == -1 && errno == ERANGE && allocsize <= 65536);
+
+ if (length == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nfs4acl_xattr_default_sd(
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **sd)
+{
+ struct nfs4acl_config *config = NULL;
+ enum default_acl_style default_acl_style;
+ mode_t required_mode;
+ SMB_STRUCT_STAT sbuf = smb_fname->st;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ default_acl_style = config->default_acl_style;
+
+ if (!VALID_STAT(sbuf)) {
+ ret = vfs_stat_smb_basename(handle->conn,
+ smb_fname,
+ &sbuf);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ if (S_ISDIR(sbuf.st_ex_mode)) {
+ required_mode = 0777;
+ } else {
+ required_mode = 0666;
+ }
+ if ((sbuf.st_ex_mode & required_mode) != required_mode) {
+ default_acl_style = DEFAULT_ACL_POSIX;
+ }
+
+ return make_default_filesystem_acl(mem_ctx,
+ default_acl_style,
+ smb_fname->base_name,
+ &sbuf,
+ sd);
+}
+
+static NTSTATUS nfs4acl_blob_to_smb4(struct vfs_handle_struct *handle,
+ DATA_BLOB *blob,
+ TALLOC_CTX *mem_ctx,
+ struct SMB4ACL_T **smb4acl)
+{
+ struct nfs4acl_config *config = NULL;
+ NTSTATUS status;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ switch (config->encoding) {
+ case NFS4ACL_ENCODING_NDR:
+ status = nfs4acl_ndr_blob_to_smb4(handle, mem_ctx, blob, smb4acl);
+ break;
+ case NFS4ACL_ENCODING_XDR:
+ status = nfs4acl_xdr_blob_to_smb4(handle, mem_ctx, blob, smb4acl);
+ break;
+ case NFS4ACL_ENCODING_NFS:
+ status = nfs4acl_nfs_blob_to_smb4(handle, mem_ctx, blob, smb4acl);
+ break;
+ default:
+ status = NT_STATUS_INTERNAL_ERROR;
+ break;
+ }
+
+ return status;
+}
+
+static NTSTATUS nfs4acl_xattr_fget_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **sd)
+{
+ struct SMB4ACL_T *smb4acl = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ status = nfs4acl_get_blob(handle, fsp, frame, &blob);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ TALLOC_FREE(frame);
+ return nfs4acl_xattr_default_sd(
+ handle, fsp->fsp_name, mem_ctx, sd);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = nfs4acl_blob_to_smb4(handle, &blob, frame, &smb4acl);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = smb_fget_nt_acl_nfs4(fsp, NULL, security_info, mem_ctx,
+ sd, smb4acl);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static bool nfs4acl_smb4acl_set_fn(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct SMB4ACL_T *smb4acl)
+{
+ struct nfs4acl_config *config = NULL;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ int saved_errno = 0;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return false);
+
+ switch (config->encoding) {
+ case NFS4ACL_ENCODING_NDR:
+ status = nfs4acl_smb4acl_to_ndr_blob(handle, talloc_tos(),
+ smb4acl, &blob);
+ break;
+ case NFS4ACL_ENCODING_XDR:
+ status = nfs4acl_smb4acl_to_xdr_blob(handle, talloc_tos(),
+ smb4acl, &blob);
+ break;
+ case NFS4ACL_ENCODING_NFS:
+ status = nfs4acl_smb4acl_to_nfs_blob(handle, talloc_tos(),
+ smb4acl, &blob);
+ break;
+ default:
+ status = NT_STATUS_INTERNAL_ERROR;
+ break;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ ret = SMB_VFS_NEXT_FSETXATTR(handle, fsp, config->xattr_name,
+ blob.data, blob.length, 0);
+ if (ret != 0) {
+ saved_errno = errno;
+ }
+ data_blob_free(&blob);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ if (ret != 0) {
+ DBG_ERR("can't store acl in xattr: %s\n", strerror(errno));
+ return false;
+ }
+
+ return true;
+}
+
+static NTSTATUS nfs4acl_xattr_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ struct nfs4acl_config *config = NULL;
+ const struct security_token *token = NULL;
+ mode_t existing_mode;
+ mode_t expected_mode;
+ mode_t restored_mode;
+ bool chown_needed = false;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct nfs4acl_config,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ DBG_ERR("Invalid stat info on [%s]\n", fsp_str_dbg(fsp));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ existing_mode = fsp->fsp_name->st.st_ex_mode;
+ if (S_ISDIR(existing_mode)) {
+ expected_mode = 0777;
+ } else {
+ expected_mode = 0666;
+ }
+ if (!config->validate_mode) {
+ existing_mode = 0;
+ expected_mode = 0;
+ }
+ if ((existing_mode & expected_mode) != expected_mode) {
+
+ restored_mode = existing_mode | expected_mode;
+
+ ret = SMB_VFS_NEXT_FCHMOD(handle,
+ fsp,
+ restored_mode);
+ if (ret != 0) {
+ DBG_ERR("Resetting POSIX mode on [%s] from [0%o]: %s\n",
+ fsp_str_dbg(fsp), existing_mode,
+ strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ status = smb_set_nt_acl_nfs4(handle,
+ fsp,
+ &config->nfs4_params,
+ security_info_sent,
+ psd,
+ nfs4acl_smb4acl_set_fn);
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ return status;
+ }
+
+ /*
+ * We got access denied. If we're already root, or we didn't
+ * need to do a chown, or the fsp isn't open with WRITE_OWNER
+ * access, just return.
+ */
+
+ if ((security_info_sent & SECINFO_OWNER) &&
+ (psd->owner_sid != NULL))
+ {
+ chown_needed = true;
+ }
+ if ((security_info_sent & SECINFO_GROUP) &&
+ (psd->group_sid != NULL))
+ {
+ chown_needed = true;
+ }
+
+ if (get_current_uid(handle->conn) == 0 ||
+ chown_needed == false)
+ {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Only allow take-ownership, not give-ownership. That's the way Windows
+ * implements SEC_STD_WRITE_OWNER. MS-FSA 2.1.5.16 just states: If
+ * InputBuffer.OwnerSid is not a valid owner SID for a file in the
+ * objectstore, as determined in an implementation specific manner, the
+ * object store MUST return STATUS_INVALID_OWNER.
+ */
+ token = get_current_nttok(fsp->conn);
+ if (!security_token_is_sid(token, psd->owner_sid)) {
+ return NT_STATUS_INVALID_OWNER;
+ }
+
+ DBG_DEBUG("overriding chown on file %s for sid %s\n",
+ fsp_str_dbg(fsp),
+ dom_sid_str_buf(psd->owner_sid, &buf));
+
+ status = smb_set_nt_acl_nfs4(handle,
+ fsp,
+ &config->nfs4_params,
+ security_info_sent,
+ psd,
+ nfs4acl_smb4acl_set_fn);
+ return status;
+}
+
+static int nfs4acl_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct nfs4acl_config *config = NULL;
+ const struct enum_list *default_acl_style_list = NULL;
+ const char *default_xattr_name = NULL;
+ bool default_validate_mode = true;
+ int enumval;
+ unsigned nfs_version;
+ int ret;
+
+ default_acl_style_list = get_default_acl_style_list();
+
+ config = talloc_zero(handle->conn, struct nfs4acl_config);
+ if (config == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ TALLOC_FREE(config);
+ return ret;
+ }
+
+ ret = smbacl4_get_vfs_params(handle->conn, &config->nfs4_params);
+ if (ret < 0) {
+ TALLOC_FREE(config);
+ return ret;
+ }
+
+ enumval = lp_parm_enum(SNUM(handle->conn),
+ "nfs4acl_xattr",
+ "encoding",
+ nfs4acl_encoding,
+ NFS4ACL_ENCODING_NDR);
+ if (enumval == -1) {
+ DBG_ERR("Invalid \"nfs4acl_xattr:encoding\" parameter\n");
+ return -1;
+ }
+ config->encoding = (enum nfs4acl_encoding)enumval;
+
+ switch (config->encoding) {
+ case NFS4ACL_ENCODING_XDR:
+ default_xattr_name = NFS4ACL_XDR_XATTR_NAME;
+ break;
+ case NFS4ACL_ENCODING_NFS:
+ default_xattr_name = NFS4ACL_NFS_XATTR_NAME;
+ default_validate_mode = false;
+ break;
+ case NFS4ACL_ENCODING_NDR:
+ default:
+ default_xattr_name = NFS4ACL_NDR_XATTR_NAME;
+ break;
+ }
+
+ nfs_version = (unsigned)lp_parm_int(SNUM(handle->conn),
+ "nfs4acl_xattr",
+ "version",
+ 41);
+ switch (nfs_version) {
+ case 40:
+ config->nfs_version = ACL4_XATTR_VERSION_40;
+ break;
+ case 41:
+ config->nfs_version = ACL4_XATTR_VERSION_41;
+ break;
+ default:
+ config->nfs_version = ACL4_XATTR_VERSION_DEFAULT;
+ break;
+ }
+
+ config->default_acl_style = lp_parm_enum(SNUM(handle->conn),
+ "nfs4acl_xattr",
+ "default acl style",
+ default_acl_style_list,
+ DEFAULT_ACL_EVERYONE);
+
+ config->xattr_name = lp_parm_substituted_string(config, lp_sub,
+ SNUM(handle->conn),
+ "nfs4acl_xattr",
+ "xattr_name",
+ default_xattr_name);
+
+ config->nfs4_id_numeric = lp_parm_bool(SNUM(handle->conn),
+ "nfs4acl_xattr",
+ "nfs4_id_numeric",
+ false);
+
+
+ config->validate_mode = lp_parm_bool(SNUM(handle->conn),
+ "nfs4acl_xattr",
+ "validate_mode",
+ default_validate_mode);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config, NULL, struct nfs4acl_config,
+ return -1);
+
+ /*
+ * Ensure we have the parameters correct if we're using this module.
+ */
+ DBG_NOTICE("Setting 'inherit acls = true', "
+ "'dos filemode = true', "
+ "'force unknown acl user = true', "
+ "'create mask = 0666', "
+ "'directory mask = 0777' and "
+ "'store dos attributes = yes' "
+ "for service [%s]\n", service);
+
+ lp_do_parameter(SNUM(handle->conn), "inherit acls", "true");
+ lp_do_parameter(SNUM(handle->conn), "dos filemode", "true");
+ lp_do_parameter(SNUM(handle->conn), "force unknown acl user", "true");
+ lp_do_parameter(SNUM(handle->conn), "create mask", "0666");
+ lp_do_parameter(SNUM(handle->conn), "directory mask", "0777");
+ lp_do_parameter(SNUM(handle->conn), "store dos attributes", "yes");
+
+ return 0;
+}
+
+/*
+ As long as Samba does not support an exiplicit method for a module
+ to define conflicting vfs methods, we should override all conflicting
+ methods here. That way, we know we are using the NFSv4 storage
+
+ Function declarations taken from vfs_solarisacl
+*/
+
+static SMB_ACL_T nfs4acl_xattr_fail__sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ return (SMB_ACL_T)NULL;
+}
+
+static int nfs4acl_xattr_fail__sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ return -1;
+}
+
+static int nfs4acl_xattr_fail__sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return -1;
+}
+
+static int nfs4acl_xattr_fail__sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob)
+{
+ return -1;
+}
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers nfs4acl_xattr_fns = {
+ .connect_fn = nfs4acl_connect,
+ .fget_nt_acl_fn = nfs4acl_xattr_fget_nt_acl,
+ .fset_nt_acl_fn = nfs4acl_xattr_fset_nt_acl,
+
+ .sys_acl_get_fd_fn = nfs4acl_xattr_fail__sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = nfs4acl_xattr_fail__sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = nfs4acl_xattr_fail__sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = nfs4acl_xattr_fail__sys_acl_delete_def_fd,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_nfs4acl_xattr_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "nfs4acl_xattr",
+ &nfs4acl_xattr_fns);
+}
diff --git a/source3/modules/vfs_not_implemented.c b/source3/modules/vfs_not_implemented.c
new file mode 100644
index 0000000..b00a499
--- /dev/null
+++ b/source3/modules/vfs_not_implemented.c
@@ -0,0 +1,1193 @@
+/*
+ * VFS module with "not implemented " helper functions for other modules.
+ *
+ * Copyright (C) Tim Potter, 1999-2000
+ * Copyright (C) Alexander Bokovoy, 2002
+ * Copyright (C) Stefan (metze) Metzmacher, 2003,2018
+ * Copyright (C) Jeremy Allison 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+
+_PUBLIC_
+int vfs_not_implemented_connect(
+ vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+void vfs_not_implemented_disconnect(vfs_handle_struct *handle)
+{
+ ;
+}
+
+_PUBLIC_
+uint64_t vfs_not_implemented_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ *bsize = 0;
+ *dfree = 0;
+ *dsize = 0;
+ return 0;
+}
+
+_PUBLIC_
+int vfs_not_implemented_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_set_quota(vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id, SMB_DISK_QUOTA *dq)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_get_shadow_copy_data(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+uint32_t vfs_not_implemented_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ return 0;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_get_dfs_referrals(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+_PUBLIC_
+DIR *vfs_not_implemented_fdopendir(vfs_handle_struct *handle, files_struct *fsp,
+ const char *mask, uint32_t attr)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+
+_PUBLIC_
+struct dirent *vfs_not_implemented_readdir(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+
+_PUBLIC_
+void vfs_not_implemented_rewind_dir(vfs_handle_struct *handle, DIR *dirp)
+{
+ ;
+}
+
+_PUBLIC_
+int vfs_not_implemented_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_closedir(vfs_handle_struct *handle, DIR *dir)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_create_file(struct vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result, int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+int vfs_not_implemented_close_fn(vfs_handle_struct *handle, files_struct *fsp)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_pread(vfs_handle_struct *handle, files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+struct tevent_req *vfs_not_implemented_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ return NULL;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ vfs_aio_state->error = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_pwrite(vfs_handle_struct *handle, files_struct *fsp,
+ const void *data, size_t n, off_t offset)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+struct tevent_req *vfs_not_implemented_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ return NULL;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ vfs_aio_state->error = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+off_t vfs_not_implemented_lseek(vfs_handle_struct *handle, files_struct *fsp,
+ off_t offset, int whence)
+{
+ errno = ENOSYS;
+ return (off_t) - 1;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_sendfile(vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp, const DATA_BLOB *hdr,
+ off_t offset, size_t n)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_recvfile(vfs_handle_struct *handle, int fromfd,
+ files_struct *tofsp, off_t offset, size_t n)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+struct tevent_req *vfs_not_implemented_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ return NULL;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ vfs_aio_state->error = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_stat(vfs_handle_struct *handle, struct smb_filename *smb_fname)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fstat(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+uint64_t vfs_not_implemented_get_alloc_size(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fchmod(vfs_handle_struct *handle, files_struct *fsp,
+ mode_t mode)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fchown(vfs_handle_struct *handle, files_struct *fsp,
+ uid_t uid, gid_t gid)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+struct smb_filename *vfs_not_implemented_getwd(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_ftruncate(vfs_handle_struct *handle, files_struct *fsp,
+ off_t offset)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fallocate(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t mode, off_t offset, off_t len)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+bool vfs_not_implemented_lock(vfs_handle_struct *handle, files_struct *fsp, int op,
+ off_t offset, off_t count, int type)
+{
+ errno = ENOSYS;
+ return false;
+}
+
+_PUBLIC_
+int vfs_not_implemented_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fcntl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int cmd,
+ va_list cmd_arg)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_linux_setlease(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int leasetype)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+bool vfs_not_implemented_getlock(vfs_handle_struct *handle, files_struct *fsp,
+ off_t *poffset, off_t *pcount, int *ptype,
+ pid_t *ppid)
+{
+ errno = ENOSYS;
+ return false;
+}
+
+_PUBLIC_
+int vfs_not_implemented_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_vfs_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+struct smb_filename *vfs_not_implemented_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+struct file_id vfs_not_implemented_file_id_create(vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct file_id id;
+ ZERO_STRUCT(id);
+ errno = ENOSYS;
+ return id;
+}
+
+_PUBLIC_
+uint64_t vfs_not_implemented_fs_file_id(vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ errno = ENOSYS;
+ return 0;
+}
+
+struct vfs_not_implemented_offload_read_state {
+ bool dummy;
+};
+
+_PUBLIC_
+struct tevent_req *vfs_not_implemented_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ struct tevent_req *req = NULL;
+ struct vfs_not_implemented_offload_read_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_not_implemented_offload_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *_token_blob)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct vfs_not_implemented_offload_write_state {
+ uint64_t unused;
+};
+
+_PUBLIC_
+struct tevent_req *vfs_not_implemented_offload_write_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num)
+{
+ struct tevent_req *req;
+ struct vfs_not_implemented_offload_write_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_not_implemented_offload_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_fget_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_set_compression(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_fstreaminfo(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+const char *vfs_not_implemented_connectpath(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ errno = ENOSYS;
+ return NULL;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+bool vfs_not_implemented_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ errno = ENOSYS;
+ return false;
+}
+
+_PUBLIC_
+bool vfs_not_implemented_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock)
+{
+ errno = ENOSYS;
+ return false;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_translate_name(struct vfs_handle_struct *handle,
+ const char *mapped_name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx, char **pmapped_name)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags, /* Needed for UNICODE ... */
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len, uint32_t *out_len)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **pattr_data)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+struct vfs_not_implemented_get_dos_attributes_state {
+ struct vfs_aio_state aio_state;
+ uint32_t dosmode;
+};
+
+_PUBLIC_
+struct tevent_req *vfs_not_implemented_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct vfs_not_implemented_get_dos_attributes_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_not_implemented_get_dos_attributes_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_get_dos_attributes_recv(
+ struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode)
+{
+ struct vfs_not_implemented_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct vfs_not_implemented_get_dos_attributes_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *aio_state = state->aio_state;
+ *dosmode = state->dosmode;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_fget_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_fset_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+SMB_ACL_T vfs_not_implemented_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ errno = ENOSYS;
+ return (SMB_ACL_T) NULL;
+}
+
+_PUBLIC_
+int vfs_not_implemented_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp, TALLOC_CTX *mem_ctx,
+ char **blob_description, DATA_BLOB *blob)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_sys_acl_set_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+struct vfs_not_implemented_getxattrat_state {
+ struct vfs_aio_state aio_state;
+ ssize_t xattr_size;
+ uint8_t *xattr_value;
+};
+
+_PUBLIC_
+struct tevent_req *vfs_not_implemented_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct tevent_req *req = NULL;
+ struct vfs_not_implemented_getxattrat_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct vfs_not_implemented_getxattrat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ tevent_req_error(req, ENOSYS);
+ return tevent_req_post(req, ev);
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct vfs_not_implemented_getxattrat_state *state = tevent_req_data(
+ req, struct vfs_not_implemented_getxattrat_state);
+ ssize_t xattr_size;
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *aio_state = state->aio_state;
+ xattr_size = state->xattr_size;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_fgetxattr(vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ void *value, size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+ssize_t vfs_not_implemented_flistxattr(vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fremovexattr(vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+int vfs_not_implemented_fsetxattr(vfs_handle_struct *handle, struct files_struct *fsp,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+_PUBLIC_
+bool vfs_not_implemented_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ errno = ENOSYS;
+ return false;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_durable_disconnect(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+_PUBLIC_
+NTSTATUS vfs_not_implemented_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers vfs_not_implemented_fns = {
+ /* Disk operations */
+
+ .connect_fn = vfs_not_implemented_connect,
+ .disconnect_fn = vfs_not_implemented_disconnect,
+ .disk_free_fn = vfs_not_implemented_disk_free,
+ .get_quota_fn = vfs_not_implemented_get_quota,
+ .set_quota_fn = vfs_not_implemented_set_quota,
+ .get_shadow_copy_data_fn = vfs_not_implemented_get_shadow_copy_data,
+ .statvfs_fn = vfs_not_implemented_statvfs,
+ .fs_capabilities_fn = vfs_not_implemented_fs_capabilities,
+ .get_dfs_referrals_fn = vfs_not_implemented_get_dfs_referrals,
+ .create_dfs_pathat_fn = vfs_not_implemented_create_dfs_pathat,
+ .read_dfs_pathat_fn = vfs_not_implemented_read_dfs_pathat,
+ .snap_check_path_fn = vfs_not_implemented_snap_check_path,
+ .snap_create_fn = vfs_not_implemented_snap_create,
+ .snap_delete_fn = vfs_not_implemented_snap_delete,
+
+ /* Directory operations */
+
+ .fdopendir_fn = vfs_not_implemented_fdopendir,
+ .readdir_fn = vfs_not_implemented_readdir,
+ .rewind_dir_fn = vfs_not_implemented_rewind_dir,
+ .mkdirat_fn = vfs_not_implemented_mkdirat,
+ .closedir_fn = vfs_not_implemented_closedir,
+
+ /* File operations */
+
+ .openat_fn = vfs_not_implemented_openat,
+ .create_file_fn = vfs_not_implemented_create_file,
+ .close_fn = vfs_not_implemented_close_fn,
+ .pread_fn = vfs_not_implemented_pread,
+ .pread_send_fn = vfs_not_implemented_pread_send,
+ .pread_recv_fn = vfs_not_implemented_pread_recv,
+ .pwrite_fn = vfs_not_implemented_pwrite,
+ .pwrite_send_fn = vfs_not_implemented_pwrite_send,
+ .pwrite_recv_fn = vfs_not_implemented_pwrite_recv,
+ .lseek_fn = vfs_not_implemented_lseek,
+ .sendfile_fn = vfs_not_implemented_sendfile,
+ .recvfile_fn = vfs_not_implemented_recvfile,
+ .renameat_fn = vfs_not_implemented_renameat,
+ .fsync_send_fn = vfs_not_implemented_fsync_send,
+ .fsync_recv_fn = vfs_not_implemented_fsync_recv,
+ .stat_fn = vfs_not_implemented_stat,
+ .fstat_fn = vfs_not_implemented_fstat,
+ .lstat_fn = vfs_not_implemented_lstat,
+ .fstatat_fn = vfs_not_implemented_fstatat,
+ .get_alloc_size_fn = vfs_not_implemented_get_alloc_size,
+ .unlinkat_fn = vfs_not_implemented_unlinkat,
+ .fchmod_fn = vfs_not_implemented_fchmod,
+ .fchown_fn = vfs_not_implemented_fchown,
+ .lchown_fn = vfs_not_implemented_lchown,
+ .chdir_fn = vfs_not_implemented_chdir,
+ .getwd_fn = vfs_not_implemented_getwd,
+ .fntimes_fn = vfs_not_implemented_fntimes,
+ .ftruncate_fn = vfs_not_implemented_ftruncate,
+ .fallocate_fn = vfs_not_implemented_fallocate,
+ .lock_fn = vfs_not_implemented_lock,
+ .filesystem_sharemode_fn = vfs_not_implemented_filesystem_sharemode,
+ .fcntl_fn = vfs_not_implemented_fcntl,
+ .linux_setlease_fn = vfs_not_implemented_linux_setlease,
+ .getlock_fn = vfs_not_implemented_getlock,
+ .symlinkat_fn = vfs_not_implemented_symlinkat,
+ .readlinkat_fn = vfs_not_implemented_vfs_readlinkat,
+ .linkat_fn = vfs_not_implemented_linkat,
+ .mknodat_fn = vfs_not_implemented_mknodat,
+ .realpath_fn = vfs_not_implemented_realpath,
+ .fchflags_fn = vfs_not_implemented_fchflags,
+ .file_id_create_fn = vfs_not_implemented_file_id_create,
+ .fs_file_id_fn = vfs_not_implemented_fs_file_id,
+ .offload_read_send_fn = vfs_not_implemented_offload_read_send,
+ .offload_read_recv_fn = vfs_not_implemented_offload_read_recv,
+ .offload_write_send_fn = vfs_not_implemented_offload_write_send,
+ .offload_write_recv_fn = vfs_not_implemented_offload_write_recv,
+ .fget_compression_fn = vfs_not_implemented_fget_compression,
+ .set_compression_fn = vfs_not_implemented_set_compression,
+
+ .fstreaminfo_fn = vfs_not_implemented_fstreaminfo,
+ .get_real_filename_at_fn = vfs_not_implemented_get_real_filename_at,
+ .connectpath_fn = vfs_not_implemented_connectpath,
+ .brl_lock_windows_fn = vfs_not_implemented_brl_lock_windows,
+ .brl_unlock_windows_fn = vfs_not_implemented_brl_unlock_windows,
+ .strict_lock_check_fn = vfs_not_implemented_strict_lock_check,
+ .translate_name_fn = vfs_not_implemented_translate_name,
+ .parent_pathname_fn = vfs_not_implemented_parent_pathname,
+ .fsctl_fn = vfs_not_implemented_fsctl,
+ .freaddir_attr_fn = vfs_not_implemented_freaddir_attr,
+ .audit_file_fn = vfs_not_implemented_audit_file,
+
+ /* DOS attributes. */
+ .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv,
+ .fget_dos_attributes_fn = vfs_not_implemented_fget_dos_attributes,
+ .fset_dos_attributes_fn = vfs_not_implemented_fset_dos_attributes,
+
+ /* NT ACL operations. */
+
+ .fget_nt_acl_fn = vfs_not_implemented_fget_nt_acl,
+ .fset_nt_acl_fn = vfs_not_implemented_fset_nt_acl,
+
+ /* POSIX ACL operations. */
+
+ .sys_acl_get_fd_fn = vfs_not_implemented_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = vfs_not_implemented_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = vfs_not_implemented_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = vfs_not_implemented_sys_acl_delete_def_fd,
+
+ /* EA operations. */
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fgetxattr_fn = vfs_not_implemented_fgetxattr,
+ .flistxattr_fn = vfs_not_implemented_flistxattr,
+ .fremovexattr_fn = vfs_not_implemented_fremovexattr,
+ .fsetxattr_fn = vfs_not_implemented_fsetxattr,
+
+ /* aio operations */
+ .aio_force_fn = vfs_not_implemented_aio_force,
+
+ /* durable handle operations */
+ .durable_cookie_fn = vfs_not_implemented_durable_cookie,
+ .durable_disconnect_fn = vfs_not_implemented_durable_disconnect,
+ .durable_reconnect_fn = vfs_not_implemented_durable_reconnect,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_not_implemented_init(TALLOC_CTX *ctx)
+{
+ /*
+ * smb_vfs_assert_all_fns() makes sure every
+ * call is implemented.
+ */
+ smb_vfs_assert_all_fns(&vfs_not_implemented_fns, "vfs_not_implemented");
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "vfs_not_implemented",
+ &vfs_not_implemented_fns);
+}
diff --git a/source3/modules/vfs_offline.c b/source3/modules/vfs_offline.c
new file mode 100644
index 0000000..06edb76
--- /dev/null
+++ b/source3/modules/vfs_offline.c
@@ -0,0 +1,50 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba VFS module for marking all files as offline.
+
+ (c) Uri Simchoni, 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+static uint32_t offline_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) |
+ FILE_SUPPORTS_REMOTE_STORAGE;
+}
+
+static NTSTATUS offline_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ *dosmode |= FILE_ATTRIBUTE_OFFLINE;
+ return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+}
+
+static struct vfs_fn_pointers offline_fns = {
+ .fs_capabilities_fn = offline_fs_capabilities,
+ .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv,
+ .fget_dos_attributes_fn = offline_fget_dos_attributes,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_offline_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "offline",
+ &offline_fns);
+}
diff --git a/source3/modules/vfs_posix_eadb.c b/source3/modules/vfs_posix_eadb.c
new file mode 100644
index 0000000..b3e21b0
--- /dev/null
+++ b/source3/modules/vfs_posix_eadb.c
@@ -0,0 +1,450 @@
+/*
+ * Store posix-level xattrs in a tdb (posix:eadb format)
+ *
+ * Copyright (C) Andrew Bartlett, 2011
+ *
+ * Based on vfs_xattr_tdb by
+ * Copyright (C) Volker Lendecke, 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "librpc/gen_ndr/xattr.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include <tdb.h>
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "ntvfs/posix/posix_eadb.h"
+#include "param/param.h"
+#include "lib/param/loadparm.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+/*
+ * Worker routine for getxattr and fgetxattr
+ */
+
+static ssize_t posix_eadb_getattr(struct tdb_wrap *db_ctx,
+ const char *fname, int fd,
+ const char *name, void *value, size_t size)
+{
+ ssize_t result = -1;
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ DEBUG(10, ("posix_eadb_getattr called for file %s/fd %d, name %s\n",
+ fname, fd, name));
+
+ status = pull_xattr_blob_tdb_raw(db_ctx, talloc_tos(), name, fname, fd, size, &blob);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ errno = ENOATTR;
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("posix_eadb_fetch_attrs failed: %s\n",
+ nt_errstr(status)));
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (blob.length > size) {
+ errno = ERANGE;
+ goto fail;
+ }
+
+ memcpy(value, blob.data, blob.length);
+ result = blob.length;
+
+ fail:
+ return result;
+}
+
+static ssize_t posix_eadb_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name, void *value, size_t size)
+{
+ struct tdb_wrap *db;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1);
+
+ return posix_eadb_getattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), name, value, size);
+}
+
+/*
+ * Worker routine for setxattr and fsetxattr
+ */
+
+static int posix_eadb_setattr(struct tdb_wrap *db_ctx,
+ const char *fname, int fd, const char *name,
+ const void *value, size_t size, int flags)
+{
+ NTSTATUS status;
+ DATA_BLOB data = data_blob_const(value, size);
+
+ DEBUG(10, ("posix_eadb_setattr called for file %s/fd %d, name %s\n",
+ fname, fd, name));
+
+ status = push_xattr_blob_tdb_raw(db_ctx, name, fname, fd, &data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("push_xattr_blob_tdb_raw failed: %s\n",
+ nt_errstr(status)));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int posix_eadb_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name, const void *value,
+ size_t size, int flags)
+{
+ struct tdb_wrap *db;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1);
+
+ return posix_eadb_setattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), name, value, size, flags);
+}
+
+/*
+ * Worker routine for listxattr and flistxattr
+ */
+
+static ssize_t posix_eadb_listattr(struct tdb_wrap *db_ctx,
+ const char *fname, int fd, char *list,
+ size_t size)
+{
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ status = list_posix_eadb_raw(db_ctx, talloc_tos(), fname, fd, &blob);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("posix_eadb_fetch_attrs failed: %s\n",
+ nt_errstr(status)));
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (blob.length > size) {
+ errno = ERANGE;
+ TALLOC_FREE(blob.data);
+ return -1;
+ }
+
+ memcpy(list, blob.data, blob.length);
+
+ TALLOC_FREE(blob.data);
+ return blob.length;
+}
+
+static ssize_t posix_eadb_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ struct tdb_wrap *db;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1);
+
+ return posix_eadb_listattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), list, size);
+}
+
+/*
+ * Worker routine for removexattr and fremovexattr
+ */
+
+static int posix_eadb_removeattr(struct tdb_wrap *db_ctx,
+ const char *fname, int fd, const char *name)
+{
+ NTSTATUS status;
+
+ status = delete_posix_eadb_raw(db_ctx, name, fname, fd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("delete_posix_eadb_raw failed: %s\n",
+ nt_errstr(status)));
+ return -1;
+ }
+ return 0;
+}
+
+static int posix_eadb_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name)
+{
+ struct tdb_wrap *db;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, db, struct tdb_wrap, return -1);
+
+ return posix_eadb_removeattr(db, fsp->fsp_name->base_name, fsp_get_io_fd(fsp), name);
+}
+
+/*
+ * Open the tdb file upon VFS_CONNECT
+ */
+
+static bool posix_eadb_init(int snum, struct tdb_wrap **p_db)
+{
+ struct tdb_wrap *db;
+ struct loadparm_context *lp_ctx;
+ const char *eadb = lp_parm_const_string(snum, "posix", "eadb", NULL);
+
+ if (!eadb) {
+ DEBUG(0, ("Can not use vfs_posix_eadb without posix:eadb set\n"));
+ return false;
+ }
+
+ lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+
+ become_root();
+ db = tdb_wrap_open(NULL, eadb, 50000,
+ lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT),
+ O_RDWR|O_CREAT, 0600);
+
+ unbecome_root();
+ talloc_unlink(NULL, lp_ctx);
+ /* now we know dbname is not NULL */
+
+ if (db == NULL) {
+#if defined(ENOTSUP)
+ errno = ENOTSUP;
+#else
+ errno = ENOSYS;
+#endif
+ return false;
+ }
+
+ *p_db = db;
+ return true;
+}
+
+/*
+ * On unlink we need to delete the tdb record
+ */
+static int posix_eadb_unlink_internal(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *smb_fname_tmp = NULL;
+ int ret = -1;
+
+ struct tdb_wrap *ea_tdb;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, ea_tdb, struct tdb_wrap, return -1);
+
+ smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
+ if (smb_fname_tmp == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * TODO: use SMB_VFS_STATX() once we have that.
+ */
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ goto out;
+ }
+
+ if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ ret = SMB_VFS_NEXT_LSTAT(handle, full_fname);
+ } else {
+ ret = SMB_VFS_NEXT_STAT(handle, full_fname);
+ }
+ if (ret == -1) {
+ goto out;
+ }
+ smb_fname_tmp->st = full_fname->st;
+
+ if (smb_fname_tmp->st.st_ex_nlink == 1) {
+ NTSTATUS status;
+
+ /* Only remove record on last link to file. */
+
+ if (tdb_transaction_start(ea_tdb->tdb) != 0) {
+ ret = -1;
+ goto out;
+ }
+
+ status = unlink_posix_eadb_raw(ea_tdb,
+ full_fname->base_name,
+ -1);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_transaction_cancel(ea_tdb->tdb);
+ ret = -1;
+ goto out;
+ }
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname_tmp,
+ flags);
+
+ if (ret == -1) {
+ tdb_transaction_cancel(ea_tdb->tdb);
+ goto out;
+ } else {
+ if (tdb_transaction_commit(ea_tdb->tdb) != 0) {
+ ret = -1;
+ goto out;
+ }
+ }
+
+out:
+ TALLOC_FREE(smb_fname_tmp);
+ TALLOC_FREE(full_fname);
+ return ret;
+}
+
+/*
+ * On rmdir we need to delete the tdb record
+ */
+static int posix_eadb_rmdir_internal(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ NTSTATUS status;
+ struct tdb_wrap *ea_tdb;
+ int ret;
+ struct smb_filename *full_fname = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, ea_tdb, struct tdb_wrap, return -1);
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ if (tdb_transaction_start(ea_tdb->tdb) != 0) {
+ TALLOC_FREE(full_fname);
+ return -1;
+ }
+
+ status = unlink_posix_eadb_raw(ea_tdb, full_fname->base_name, -1);
+ TALLOC_FREE(full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_transaction_cancel(ea_tdb->tdb);
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ AT_REMOVEDIR);
+
+ if (ret == -1) {
+ tdb_transaction_cancel(ea_tdb->tdb);
+ } else {
+ if (tdb_transaction_commit(ea_tdb->tdb) != 0) {
+ return -1;
+ }
+ }
+
+ return ret;
+}
+
+static int posix_eadb_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret;
+
+ if (flags & AT_REMOVEDIR) {
+ ret = posix_eadb_rmdir_internal(handle,
+ dirfsp,
+ smb_fname);
+ } else {
+ ret = posix_eadb_unlink_internal(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ }
+ return ret;
+}
+
+/*
+ * Destructor for the VFS private data
+ */
+
+static void close_xattr_db(void **data)
+{
+ struct tdb_wrap **p_db = (struct tdb_wrap **)data;
+ TALLOC_FREE(*p_db);
+}
+
+static int posix_eadb_connect(vfs_handle_struct *handle, const char *service,
+ const char *user)
+{
+ char *sname = NULL;
+ int res, snum;
+ struct tdb_wrap *db;
+
+ res = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (res < 0) {
+ return res;
+ }
+
+ snum = find_service(talloc_tos(), service, &sname);
+ if (snum == -1 || sname == NULL) {
+ /*
+ * Should not happen, but we should not fail just *here*.
+ */
+ return 0;
+ }
+
+ if (!posix_eadb_init(snum, &db)) {
+ DEBUG(5, ("Could not init xattr tdb\n"));
+ lp_do_parameter(snum, "ea support", "False");
+ return 0;
+ }
+
+ lp_do_parameter(snum, "ea support", "True");
+
+ SMB_VFS_HANDLE_SET_DATA(handle, db, close_xattr_db,
+ struct tdb_wrap, return -1);
+
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_posix_eadb_fns = {
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fgetxattr_fn = posix_eadb_fgetxattr,
+ .fsetxattr_fn = posix_eadb_fsetxattr,
+ .flistxattr_fn = posix_eadb_flistxattr,
+ .fremovexattr_fn = posix_eadb_fremovexattr,
+ .unlinkat_fn = posix_eadb_unlinkat,
+ .connect_fn = posix_eadb_connect,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_posix_eadb_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "posix_eadb",
+ &vfs_posix_eadb_fns);
+}
diff --git a/source3/modules/vfs_posixacl.c b/source3/modules/vfs_posixacl.c
new file mode 100644
index 0000000..feb819d
--- /dev/null
+++ b/source3/modules/vfs_posixacl.c
@@ -0,0 +1,389 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set posix acls
+ Copyright (C) Volker Lendecke 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "modules/vfs_posixacl.h"
+
+/* prototypes for static functions first - for clarity */
+
+static bool smb_ace_to_internal(acl_entry_t posix_ace,
+ struct smb_acl_entry *ace);
+static struct smb_acl_t *smb_acl_to_internal(acl_t acl, TALLOC_CTX *mem_ctx);
+static int smb_acl_set_mode(acl_entry_t entry, SMB_ACL_PERM_T perm);
+static acl_t smb_acl_to_posix(const struct smb_acl_t *acl);
+
+
+/* public functions - the api */
+
+SMB_ACL_T posixacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ struct smb_acl_t *result;
+ acl_t acl = NULL;
+ acl_type_t acl_type;
+
+ switch(type) {
+ case SMB_ACL_TYPE_ACCESS:
+ acl_type = ACL_TYPE_ACCESS;
+ break;
+ case SMB_ACL_TYPE_DEFAULT:
+ acl_type = ACL_TYPE_DEFAULT;
+ break;
+ default:
+ errno = EINVAL;
+ return NULL;
+ }
+ if (!fsp->fsp_flags.is_pathref && (acl_type == ACL_TYPE_ACCESS)) {
+ /* POSIX API only allows ACL_TYPE_ACCESS fetched on fd. */
+ acl = acl_get_fd(fsp_get_io_fd(fsp));
+ } else if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ acl = acl_get_file(sys_proc_fd_path(fd, &buf), acl_type);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ acl = acl_get_file(fsp->fsp_name->base_name, acl_type);
+ }
+ if (acl == NULL) {
+ return NULL;
+ }
+
+ result = smb_acl_to_internal(acl, mem_ctx);
+ acl_free(acl);
+ return result;
+}
+
+int posixacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ int res;
+ acl_t acl = smb_acl_to_posix(theacl);
+ acl_type_t acl_type;
+ int fd = fsp_get_pathref_fd(fsp);
+
+ if (acl == NULL) {
+ return -1;
+ }
+
+ switch(type) {
+ case SMB_ACL_TYPE_ACCESS:
+ acl_type = ACL_TYPE_ACCESS;
+ break;
+ case SMB_ACL_TYPE_DEFAULT:
+ acl_type = ACL_TYPE_DEFAULT;
+ break;
+ default:
+ acl_free(acl);
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!fsp->fsp_flags.is_pathref && type == SMB_ACL_TYPE_ACCESS) {
+ res = acl_set_fd(fd, acl);
+ } else if (fsp->fsp_flags.have_proc_fds) {
+ struct sys_proc_fd_path_buf buf;
+
+ res = acl_set_file(sys_proc_fd_path(fd, &buf), acl_type, acl);
+ } else {
+ /*
+ * This is no longer a handle based call.
+ */
+ res = acl_set_file(fsp->fsp_name->base_name,
+ acl_type,
+ acl);
+ }
+
+ acl_free(acl);
+ return res;
+}
+
+int posixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ if (fsp->fsp_flags.have_proc_fds) {
+ int fd = fsp_get_pathref_fd(fsp);
+ struct sys_proc_fd_path_buf buf;
+
+ return acl_delete_def_file(sys_proc_fd_path(fd, &buf));
+ }
+
+ /*
+ * This is no longer a handle based call.
+ */
+ return acl_delete_def_file(fsp->fsp_name->base_name);
+}
+
+/* private functions */
+
+static bool smb_ace_to_internal(acl_entry_t posix_ace,
+ struct smb_acl_entry *ace)
+{
+ acl_tag_t tag;
+ acl_permset_t permset;
+
+ if (acl_get_tag_type(posix_ace, &tag) != 0) {
+ DEBUG(0, ("smb_acl_get_tag_type failed\n"));
+ return False;
+ }
+
+ switch(tag) {
+ case ACL_USER:
+ ace->a_type = SMB_ACL_USER;
+ break;
+ case ACL_USER_OBJ:
+ ace->a_type = SMB_ACL_USER_OBJ;
+ break;
+ case ACL_GROUP:
+ ace->a_type = SMB_ACL_GROUP;
+ break;
+ case ACL_GROUP_OBJ:
+ ace->a_type = SMB_ACL_GROUP_OBJ;
+ break;
+ case ACL_OTHER:
+ ace->a_type = SMB_ACL_OTHER;
+ break;
+ case ACL_MASK:
+ ace->a_type = SMB_ACL_MASK;
+ break;
+#ifdef HAVE_ACL_EVERYONE
+ case ACL_EVERYONE:
+ DEBUG(1, ("ACL tag type ACL_EVERYONE. FreeBSD with ZFS? Use 'vfs objects = zfsacl'\n"));
+ return false;
+#endif
+ default:
+ DEBUG(0, ("unknown tag type %d\n", (unsigned int)tag));
+ return False;
+ }
+ switch(ace->a_type) {
+ case SMB_ACL_USER: {
+ uid_t *puid = (uid_t *)acl_get_qualifier(posix_ace);
+ if (puid == NULL) {
+ DEBUG(0, ("smb_acl_get_qualifier failed\n"));
+ return False;
+ }
+ ace->info.user.uid = *puid;
+ acl_free(puid);
+ break;
+ }
+
+ case SMB_ACL_GROUP: {
+ gid_t *pgid = (uid_t *)acl_get_qualifier(posix_ace);
+ if (pgid == NULL) {
+ DEBUG(0, ("smb_acl_get_qualifier failed\n"));
+ return False;
+ }
+ ace->info.group.gid = *pgid;
+ acl_free(pgid);
+ break;
+ }
+ default:
+ break;
+ }
+ if (acl_get_permset(posix_ace, &permset) != 0) {
+ DEBUG(0, ("smb_acl_get_mode failed\n"));
+ return False;
+ }
+ ace->a_perm = 0;
+#ifdef HAVE_ACL_GET_PERM_NP
+ ace->a_perm |= (acl_get_perm_np(permset, ACL_READ) ? SMB_ACL_READ : 0);
+ ace->a_perm |= (acl_get_perm_np(permset, ACL_WRITE) ? SMB_ACL_WRITE : 0);
+ ace->a_perm |= (acl_get_perm_np(permset, ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
+#else
+ ace->a_perm |= (acl_get_perm(permset, ACL_READ) ? SMB_ACL_READ : 0);
+ ace->a_perm |= (acl_get_perm(permset, ACL_WRITE) ? SMB_ACL_WRITE : 0);
+ ace->a_perm |= (acl_get_perm(permset, ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
+#endif
+ return True;
+}
+
+static struct smb_acl_t *smb_acl_to_internal(acl_t acl, TALLOC_CTX *mem_ctx)
+{
+ struct smb_acl_t *result = sys_acl_init(mem_ctx);
+ int entry_id = ACL_FIRST_ENTRY;
+ acl_entry_t e;
+ if (result == NULL) {
+ return NULL;
+ }
+ while (acl_get_entry(acl, entry_id, &e) == 1) {
+
+ entry_id = ACL_NEXT_ENTRY;
+
+ result->acl = talloc_realloc(result, result->acl,
+ struct smb_acl_entry, result->count+1);
+ if (result->acl == NULL) {
+ TALLOC_FREE(result);
+ DEBUG(0, ("talloc_realloc failed\n"));
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ if (!smb_ace_to_internal(e, &result->acl[result->count])) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+
+ result->count += 1;
+ }
+ return result;
+}
+
+static int smb_acl_set_mode(acl_entry_t entry, SMB_ACL_PERM_T perm)
+{
+ int ret;
+ acl_permset_t permset;
+
+ if ((ret = acl_get_permset(entry, &permset)) != 0) {
+ return ret;
+ }
+ if ((ret = acl_clear_perms(permset)) != 0) {
+ return ret;
+ }
+ if ((perm & SMB_ACL_READ) &&
+ ((ret = acl_add_perm(permset, ACL_READ)) != 0)) {
+ return ret;
+ }
+ if ((perm & SMB_ACL_WRITE) &&
+ ((ret = acl_add_perm(permset, ACL_WRITE)) != 0)) {
+ return ret;
+ }
+ if ((perm & SMB_ACL_EXECUTE) &&
+ ((ret = acl_add_perm(permset, ACL_EXECUTE)) != 0)) {
+ return ret;
+ }
+
+ return 0;
+}
+
+static acl_t smb_acl_to_posix(const struct smb_acl_t *acl)
+{
+ acl_t result;
+ int i;
+
+ result = acl_init(acl->count);
+ if (result == NULL) {
+ DEBUG(10, ("acl_init failed\n"));
+ return NULL;
+ }
+
+ for (i=0; i<acl->count; i++) {
+ const struct smb_acl_entry *entry = &acl->acl[i];
+ acl_entry_t e;
+ acl_tag_t tag;
+
+ if (acl_create_entry(&result, &e) != 0) {
+ DEBUG(1, ("acl_create_entry failed: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+
+ switch (entry->a_type) {
+ case SMB_ACL_USER:
+ tag = ACL_USER;
+ break;
+ case SMB_ACL_USER_OBJ:
+ tag = ACL_USER_OBJ;
+ break;
+ case SMB_ACL_GROUP:
+ tag = ACL_GROUP;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ tag = ACL_GROUP_OBJ;
+ break;
+ case SMB_ACL_OTHER:
+ tag = ACL_OTHER;
+ break;
+ case SMB_ACL_MASK:
+ tag = ACL_MASK;
+ break;
+ default:
+ DEBUG(1, ("Unknown tag value %d\n", entry->a_type));
+ goto fail;
+ }
+
+ if (acl_set_tag_type(e, tag) != 0) {
+ DEBUG(10, ("acl_set_tag_type(%d) failed: %s\n",
+ tag, strerror(errno)));
+ goto fail;
+ }
+
+ switch (entry->a_type) {
+ case SMB_ACL_USER:
+ if (acl_set_qualifier(e, &entry->info.user.uid) != 0) {
+ DEBUG(1, ("acl_set_qualifiier failed: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+ break;
+ case SMB_ACL_GROUP:
+ if (acl_set_qualifier(e, &entry->info.group.gid) != 0) {
+ DEBUG(1, ("acl_set_qualifiier failed: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+ break;
+ default: /* Shut up, compiler! :-) */
+ break;
+ }
+
+ if (smb_acl_set_mode(e, entry->a_perm) != 0) {
+ goto fail;
+ }
+ }
+
+ if (acl_valid(result) != 0) {
+ char *acl_string = sys_acl_to_text(acl, NULL);
+ DEBUG(0, ("smb_acl_to_posix: ACL %s is invalid for set (%s)\n",
+ acl_string, strerror(errno)));
+ SAFE_FREE(acl_string);
+ goto fail;
+ }
+
+ return result;
+
+ fail:
+ if (result != NULL) {
+ acl_free(result);
+ }
+ return NULL;
+}
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers posixacl_fns = {
+ .sys_acl_get_fd_fn = posixacl_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = posixacl_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = posixacl_sys_acl_delete_def_fd,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_posixacl_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "posixacl",
+ &posixacl_fns);
+}
diff --git a/source3/modules/vfs_posixacl.h b/source3/modules/vfs_posixacl.h
new file mode 100644
index 0000000..91166ed
--- /dev/null
+++ b/source3/modules/vfs_posixacl.h
@@ -0,0 +1,38 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set posix acls
+ Copyright (C) Volker Lendecke 2006
+ Copyright (C) Michael Adam 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __VFS_POSIXACL_H__
+#define __VFS_POSIXACL_H__
+
+SMB_ACL_T posixacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+
+int posixacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl);
+
+int posixacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp);
+
+#endif
+
diff --git a/source3/modules/vfs_prealloc.c b/source3/modules/vfs_prealloc.c
new file mode 100644
index 0000000..51e9ad0
--- /dev/null
+++ b/source3/modules/vfs_prealloc.c
@@ -0,0 +1,222 @@
+/*
+ * XFS preallocation support module.
+ *
+ * Copyright (c) James Peach 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+
+/* Extent preallocation module.
+ *
+ * The purpose of this module is to preallocate space on the filesystem when
+ * we have a good idea of how large files are supposed to be. This lets writes
+ * proceed without having to allocate new extents and results in better file
+ * layouts on disk.
+ *
+ * Currently only implemented for XFS. This module is based on an original idea
+ * and implementation by Sebastian Brings.
+ *
+ * Tunables.
+ *
+ * prealloc: <ext> Number of bytes to preallocate for a file with
+ * the matching extension.
+ * prealloc:debug Debug level at which to emit messages.
+ *
+ * Example.
+ *
+ * prealloc:mpeg = 500M # Preallocate *.mpeg to 500 MiB.
+ */
+
+#ifdef HAVE_XFS_LIBXFS_H
+#include <xfs/libxfs.h>
+#define lock_type xfs_flock64_t
+#else
+#define lock_type struct flock64
+#endif
+
+#define MODULE "prealloc"
+static int module_debug;
+
+static int preallocate_space(int fd, off_t size)
+{
+ int err;
+ lock_type fl = {0};
+
+ if (size <= 0) {
+ return 0;
+ }
+
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = size;
+
+ /* IMPORTANT: We use RESVSP because we want the extents to be
+ * allocated, but we don't want the allocation to show up in
+ * st_size or persist after the close(2).
+ */
+
+#if defined(XFS_IOC_RESVSP64)
+ /* On Linux this comes in via libxfs.h. */
+ err = xfsctl(NULL, fd, XFS_IOC_RESVSP64, &fl);
+#elif defined(F_RESVSP64)
+ /* On IRIX, this comes from fcntl.h. */
+ err = fcntl(fd, F_RESVSP64, &fl);
+#else
+ err = -1;
+ errno = ENOSYS;
+#endif
+ if (err) {
+ DEBUG(module_debug,
+ ("%s: preallocate failed on fd=%d size=%lld: %s\n",
+ MODULE, fd, (long long)size, strerror(errno)));
+ }
+
+ return err;
+}
+
+static int prealloc_connect(
+ struct vfs_handle_struct * handle,
+ const char * service,
+ const char * user)
+{
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ module_debug = lp_parm_int(SNUM(handle->conn),
+ MODULE, "debug", 100);
+
+ return 0;
+}
+
+static int prealloc_openat(struct vfs_handle_struct* handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int fd;
+ off_t size = 0;
+ const char * dot;
+ char fext[10];
+
+ if (!(how->flags & (O_CREAT|O_TRUNC))) {
+ /* Caller is not intending to rewrite the file. Let's not mess
+ * with the allocation in this case.
+ */
+ goto normal_open;
+ }
+
+ *fext = '\0';
+ dot = strrchr(smb_fname->base_name, '.');
+ if (dot && *++dot) {
+ if (strlen(dot) < sizeof(fext)) {
+ bool ok;
+ strncpy(fext, dot, sizeof(fext));
+ ok = strlower_m(fext);
+ if (!ok);
+ goto normal_open;
+ }
+ }
+ }
+
+ if (*fext == '\0') {
+ goto normal_open;
+ }
+
+ /* Syntax for specifying preallocation size is:
+ * MODULE: <extension> = <size>
+ * where
+ * <extension> is the file extension in lower case
+ * <size> is a size like 10, 10K, 10M
+ */
+ size = conv_str_size(lp_parm_const_string(SNUM(handle->conn), MODULE,
+ fext, NULL));
+ if (size <= 0) {
+ /* No need to preallocate this file. */
+ goto normal_open;
+ }
+
+ fd = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+ if (fd < 0) {
+ return fd;
+ }
+
+ /* Preallocate only if the file is being created or replaced. Note that
+ * Samba won't ever pass down O_TRUNC, which is why we have to handle
+ * truncate calls specially.
+ */
+ if ((how->flags & O_CREAT) || (how->flags & O_TRUNC)) {
+ off_t * psize;
+
+ psize = VFS_ADD_FSP_EXTENSION(handle, fsp, off_t, NULL);
+ if (psize == NULL || *psize == -1) {
+ return fd;
+ }
+
+ DEBUG(module_debug,
+ ("%s: preallocating %s (fd=%d) to %lld bytes\n",
+ MODULE, smb_fname_str_dbg(smb_fname), fd,
+ (long long)size));
+
+ *psize = size;
+ if (preallocate_space(fd, *psize) < 0) {
+ VFS_REMOVE_FSP_EXTENSION(handle, fsp);
+ }
+ }
+
+ return fd;
+
+normal_open:
+ /* We are not creating or replacing a file. Skip the
+ * preallocation.
+ */
+ DEBUG(module_debug, ("%s: skipping preallocation for %s\n",
+ MODULE, smb_fname_str_dbg(smb_fname)));
+ return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+}
+
+static int prealloc_ftruncate(vfs_handle_struct * handle,
+ files_struct * fsp,
+ off_t offset)
+{
+ off_t *psize;
+ int ret = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
+
+ /* Maintain the allocated space even in the face of truncates. */
+ if ((psize = VFS_FETCH_FSP_EXTENSION(handle, fsp))) {
+ preallocate_space(fsp_get_io_fd(fsp), *psize);
+ }
+
+ return ret;
+}
+
+static struct vfs_fn_pointers prealloc_fns = {
+ .openat_fn = prealloc_openat,
+ .ftruncate_fn = prealloc_ftruncate,
+ .connect_fn = prealloc_connect,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_prealloc_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ MODULE, &prealloc_fns);
+}
diff --git a/source3/modules/vfs_preopen.c b/source3/modules/vfs_preopen.c
new file mode 100644
index 0000000..d1327f6
--- /dev/null
+++ b/source3/modules/vfs_preopen.c
@@ -0,0 +1,757 @@
+/*
+ * Force a readahead of files by opening them and reading the first bytes
+ *
+ * Copyright (C) Volker Lendecke 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util_matching.h"
+#include "lib/global_contexts.h"
+
+static int vfs_preopen_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_preopen_debug_level
+
+#define PREOPEN_MAX_DIGITS 19
+#define PREOPEN_MAX_NUMBER (uint64_t)9999999999999999999ULL
+
+struct preopen_state;
+
+struct preopen_helper {
+ struct preopen_state *state;
+ struct tevent_fd *fde;
+ pid_t pid;
+ int fd;
+ bool busy;
+};
+
+struct preopen_state {
+ int num_helpers;
+ struct preopen_helper *helpers;
+
+ size_t to_read; /* How many bytes to read in children? */
+ int queue_max;
+
+ int queue_dbglvl; /* DBGLVL_DEBUG by default */
+ int nomatch_dbglvl; /* DBGLVL_INFO by default */
+ int match_dbglvl; /* DBGLVL_INFO by default */
+ int reset_dbglvl; /* DBGLVL_INFO by default */
+ int nodigits_dbglvl; /* DBGLVL_WARNING by default */
+ int founddigits_dbglvl; /* DBGLVL_NOTICE by default */
+ int push_dbglvl; /* DBGLVL_NOTICE by default */
+
+ char *template_fname; /* Filename to be sent to children */
+ size_t number_start; /* start offset into "template_fname" */
+ int num_digits; /* How many digits is the number long? */
+
+ uint64_t fnum_sent; /* last fname sent to children */
+
+ uint64_t fnum_queue_end;/* last fname to be sent, based on
+ * last open call + preopen:queuelen
+ */
+
+ struct samba_path_matching *preopen_names;
+ ssize_t last_match_idx; /* remember the last match */
+};
+
+static void preopen_helper_destroy(struct preopen_helper *c)
+{
+ int status;
+ TALLOC_FREE(c->fde);
+ close(c->fd);
+ c->fd = -1;
+ kill(c->pid, SIGKILL);
+ waitpid(c->pid, &status, 0);
+ c->busy = true;
+}
+
+static void preopen_queue_run(struct preopen_state *state)
+{
+ char *pdelimiter;
+ char delimiter;
+
+ DBG_PREFIX(state->queue_dbglvl, ("START: "
+ "last_fname[%s] start_offset=%zu num_digits=%d "
+ "last_pushed_num=%"PRIu64" queue_end_num=%"PRIu64" num_helpers=%d\n",
+ state->template_fname,
+ state->number_start,
+ state->num_digits,
+ state->fnum_sent,
+ state->fnum_queue_end,
+ state->num_helpers));
+
+ pdelimiter = state->template_fname + state->number_start
+ + state->num_digits;
+ delimiter = *pdelimiter;
+
+ while (state->fnum_sent < state->fnum_queue_end) {
+
+ ssize_t written;
+ size_t to_write;
+ int helper;
+
+ for (helper=0; helper<state->num_helpers; helper++) {
+ if (state->helpers[helper].busy) {
+ continue;
+ }
+ break;
+ }
+ if (helper == state->num_helpers) {
+ /* everyone is busy */
+ DBG_PREFIX(state->queue_dbglvl, ("BUSY: "
+ "template_fname[%s] start_offset=%zu num_digits=%d "
+ "last_pushed_num=%"PRIu64" queue_end_num=%"PRIu64"\n",
+ state->template_fname,
+ state->number_start,
+ state->num_digits,
+ state->fnum_sent,
+ state->fnum_queue_end));
+ return;
+ }
+
+ snprintf(state->template_fname + state->number_start,
+ state->num_digits + 1,
+ "%.*llu", state->num_digits,
+ (long long unsigned int)(state->fnum_sent + 1));
+ *pdelimiter = delimiter;
+
+ DBG_PREFIX(state->push_dbglvl, (
+ "PUSH: fullpath[%s] to helper(idx=%d)\n",
+ state->template_fname, helper));
+
+ to_write = talloc_get_size(state->template_fname);
+ written = write_data(state->helpers[helper].fd,
+ state->template_fname, to_write);
+ state->helpers[helper].busy = true;
+
+ if (written != to_write) {
+ preopen_helper_destroy(&state->helpers[helper]);
+ }
+ state->fnum_sent += 1;
+ }
+ DBG_PREFIX(state->queue_dbglvl, ("END: "
+ "template_fname[%s] start_offset=%zu num_digits=%d "
+ "last_pushed_num=%"PRIu64" queue_end_num=%"PRIu64"\n",
+ state->template_fname,
+ state->number_start,
+ state->num_digits,
+ state->fnum_sent,
+ state->fnum_queue_end));
+}
+
+static void preopen_helper_readable(struct tevent_context *ev,
+ struct tevent_fd *fde, uint16_t flags,
+ void *priv)
+{
+ struct preopen_helper *helper = (struct preopen_helper *)priv;
+ struct preopen_state *state = helper->state;
+ ssize_t nread;
+ char c;
+
+ if ((flags & TEVENT_FD_READ) == 0) {
+ return;
+ }
+
+ nread = read(helper->fd, &c, 1);
+ if (nread <= 0) {
+ preopen_helper_destroy(helper);
+ return;
+ }
+
+ helper->busy = false;
+
+ DBG_PREFIX(state->queue_dbglvl, ("BEFORE: preopen_queue_run\n"));
+ preopen_queue_run(state);
+ DBG_PREFIX(state->queue_dbglvl, ("AFTER: preopen_queue_run\n"));
+}
+
+static int preopen_helpers_destructor(struct preopen_state *c)
+{
+ int i;
+
+ for (i=0; i<c->num_helpers; i++) {
+ if (c->helpers[i].fd == -1) {
+ continue;
+ }
+ preopen_helper_destroy(&c->helpers[i]);
+ }
+
+ return 0;
+}
+
+static bool preopen_helper_open_one(int sock_fd, char **pnamebuf,
+ size_t to_read, void *filebuf)
+{
+ char *namebuf = *pnamebuf;
+ ssize_t nread;
+ char c = 0;
+ int fd;
+
+ nread = 0;
+
+ do {
+ ssize_t thistime;
+
+ thistime = read(sock_fd, namebuf + nread,
+ talloc_get_size(namebuf) - nread);
+ if (thistime <= 0) {
+ return false;
+ }
+
+ nread += thistime;
+
+ if (nread == talloc_get_size(namebuf)) {
+ namebuf = talloc_realloc(
+ NULL, namebuf, char,
+ talloc_get_size(namebuf) * 2);
+ if (namebuf == NULL) {
+ return false;
+ }
+ *pnamebuf = namebuf;
+ }
+ } while (namebuf[nread - 1] != '\0');
+
+ fd = open(namebuf, O_RDONLY);
+ if (fd == -1) {
+ goto done;
+ }
+ nread = read(fd, filebuf, to_read);
+ close(fd);
+
+ done:
+ sys_write_v(sock_fd, &c, 1);
+ return true;
+}
+
+static bool preopen_helper(int fd, size_t to_read)
+{
+ char *namebuf;
+ void *readbuf;
+
+ namebuf = talloc_array(NULL, char, 1024);
+ if (namebuf == NULL) {
+ return false;
+ }
+
+ readbuf = talloc_size(NULL, to_read);
+ if (readbuf == NULL) {
+ TALLOC_FREE(namebuf);
+ return false;
+ }
+
+ while (preopen_helper_open_one(fd, &namebuf, to_read, readbuf)) {
+ ;
+ }
+
+ TALLOC_FREE(readbuf);
+ TALLOC_FREE(namebuf);
+ return false;
+}
+
+static NTSTATUS preopen_init_helper(struct preopen_helper *h)
+{
+ int fdpair[2];
+ NTSTATUS status;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(10, ("socketpair() failed: %s\n", strerror(errno)));
+ return status;
+ }
+
+ h->pid = fork();
+
+ if (h->pid == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ if (h->pid == 0) {
+ close(fdpair[0]);
+ preopen_helper(fdpair[1], h->state->to_read);
+ exit(0);
+ }
+ close(fdpair[1]);
+ h->fd = fdpair[0];
+ h->fde = tevent_add_fd(global_event_context(), h->state, h->fd,
+ TEVENT_FD_READ, preopen_helper_readable, h);
+ if (h->fde == NULL) {
+ close(h->fd);
+ h->fd = -1;
+ return NT_STATUS_NO_MEMORY;
+ }
+ h->busy = false;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS preopen_init_helpers(TALLOC_CTX *mem_ctx, size_t to_read,
+ int num_helpers, int queue_max,
+ struct preopen_state **presult)
+{
+ struct preopen_state *result;
+ int i;
+
+ result = talloc(mem_ctx, struct preopen_state);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result->num_helpers = num_helpers;
+ result->helpers = talloc_array(result, struct preopen_helper,
+ num_helpers);
+ if (result->helpers == NULL) {
+ TALLOC_FREE(result);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result->to_read = to_read;
+ result->queue_max = queue_max;
+ result->template_fname = NULL;
+ result->fnum_sent = 0;
+ result->fnum_queue_end = 0;
+
+ for (i=0; i<num_helpers; i++) {
+ result->helpers[i].state = result;
+ result->helpers[i].fd = -1;
+ }
+
+ talloc_set_destructor(result, preopen_helpers_destructor);
+
+ for (i=0; i<num_helpers; i++) {
+ preopen_init_helper(&result->helpers[i]);
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+}
+
+static void preopen_free_helpers(void **ptr)
+{
+ TALLOC_FREE(*ptr);
+}
+
+static struct preopen_state *preopen_state_get(vfs_handle_struct *handle)
+{
+ struct preopen_state *state;
+ NTSTATUS status;
+ const char *namelist;
+
+ if (SMB_VFS_HANDLE_TEST_DATA(handle)) {
+ SMB_VFS_HANDLE_GET_DATA(handle, state, struct preopen_state,
+ return NULL);
+ return state;
+ }
+
+ namelist = lp_parm_const_string(SNUM(handle->conn), "preopen", "names",
+ NULL);
+
+ if (namelist == NULL) {
+ return NULL;
+ }
+
+ status = preopen_init_helpers(
+ NULL,
+ lp_parm_int(SNUM(handle->conn), "preopen", "num_bytes", 1),
+ lp_parm_int(SNUM(handle->conn), "preopen", "helpers", 1),
+ lp_parm_int(SNUM(handle->conn), "preopen", "queuelen", 10),
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ state->queue_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "queue_log_level", DBGLVL_DEBUG);
+ state->nomatch_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "nomatch_log_level", DBGLVL_INFO);
+ state->match_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "match_log_level", DBGLVL_INFO);
+ state->reset_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "reset_log_level", DBGLVL_INFO);
+ state->nodigits_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "nodigits_log_level", DBGLVL_WARNING);
+ state->founddigits_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "founddigits_log_level", DBGLVL_NOTICE);
+ state->push_dbglvl = lp_parm_int(SNUM(handle->conn), "preopen", "push_log_level", DBGLVL_NOTICE);
+
+ if (lp_parm_bool(SNUM(handle->conn), "preopen", "posix-basic-regex", false)) {
+ status = samba_path_matching_regex_sub1_create(state,
+ namelist,
+ &state->preopen_names);
+ } else {
+ status = samba_path_matching_mswild_create(state,
+ true, /* case_sensitive */
+ namelist,
+ &state->preopen_names);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(state);
+ return NULL;
+ }
+ state->last_match_idx = -1;
+
+ if (!SMB_VFS_HANDLE_TEST_DATA(handle)) {
+ SMB_VFS_HANDLE_SET_DATA(handle, state, preopen_free_helpers,
+ struct preopen_state, return NULL);
+ }
+
+ return state;
+}
+
+static bool preopen_parse_fname(const char *fname, uint64_t *pnum,
+ size_t *pstart_idx, int *pnum_digits)
+{
+ char digits[PREOPEN_MAX_DIGITS+1] = { 0, };
+ const char *p;
+ char *q = NULL;
+ unsigned long long num;
+ size_t start_idx = 0;
+ int num_digits = -1;
+ int error = 0;
+
+ if (*pstart_idx > 0 && *pnum_digits > 0) {
+ /*
+ * If the caller knowns
+ * how many digits are expected
+ * and on what position,
+ * we should copy the exact
+ * subset before we start
+ * parsing the string into a number
+ */
+
+ if (*pnum_digits > PREOPEN_MAX_DIGITS) {
+ /*
+ * a string with as much digits as
+ * PREOPEN_MAX_DIGITS is the longest
+ * string that would make any sense for us.
+ *
+ * The rest will be checked via
+ * smb_strtoull().
+ */
+ return false;
+ }
+ p = fname + *pstart_idx;
+ memcpy(digits, p, *pnum_digits);
+ p = digits;
+ start_idx = *pstart_idx;
+ goto parse;
+ }
+
+ p = strrchr_m(fname, '/');
+ if (p == NULL) {
+ p = fname;
+ }
+
+ p += 1;
+ while (p[0] != '\0') {
+ if (isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2])) {
+ break;
+ }
+ p += 1;
+ }
+ if (*p == '\0') {
+ /* no digits around */
+ return false;
+ }
+
+ start_idx = (p - fname);
+
+parse:
+ num = smb_strtoull(p, (char **)&q, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ return false;
+ }
+
+ if (num >= PREOPEN_MAX_NUMBER) {
+ /* overflow */
+ return false;
+ }
+
+ num_digits = (q - p);
+
+ if (*pnum_digits != -1 && *pnum_digits != num_digits) {
+ /*
+ * If the caller knowns how many digits
+ * it expects we should fail if we got something
+ * different.
+ */
+ return false;
+ }
+
+ *pnum = num;
+ *pstart_idx = start_idx;
+ *pnum_digits = num_digits;
+ return true;
+}
+
+static uint64_t num_digits_max_value(int num_digits)
+{
+ uint64_t num_max = 1;
+ int i;
+
+ if (num_digits < 1) {
+ return 0;
+ }
+ if (num_digits >= PREOPEN_MAX_DIGITS) {
+ return PREOPEN_MAX_NUMBER;
+ }
+
+ for (i = 0; i < num_digits; i++) {
+ num_max *= 10;
+ }
+
+ /*
+ * We actually want
+ * 9 instead of 10
+ * 99 instead of 100
+ * 999 instead of 1000
+ */
+ return num_max - 1;
+}
+
+static int preopen_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ const char *dirname = dirfsp->fsp_name->base_name;
+ struct preopen_state *state;
+ int res;
+ uint64_t num;
+ uint64_t num_max;
+ NTSTATUS status;
+ char *new_template = NULL;
+ size_t new_start = 0;
+ int new_digits = -1;
+ size_t new_end = 0;
+ ssize_t match_idx = -1;
+ ssize_t replace_start = -1;
+ ssize_t replace_end = -1;
+ bool need_reset = false;
+
+ DBG_DEBUG("called on %s\n", smb_fname_str_dbg(smb_fname));
+
+ state = preopen_state_get(handle);
+ if (state == NULL) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ res = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+ if (res == -1) {
+ return -1;
+ }
+
+ if ((how->flags & O_ACCMODE) != O_RDONLY) {
+ return res;
+ }
+
+ /*
+ * Make sure we can later construct an absolute pathname
+ */
+ if (dirname[0] != '/') {
+ return res;
+ }
+ /*
+ * There's no point in preopen the directory itself.
+ */
+ if (ISDOT(smb_fname->base_name)) {
+ return res;
+ }
+ /*
+ * If we got an absolute path in
+ * smb_fname it's most likely the
+ * reopen via /proc/self/fd/$fd
+ */
+ if (smb_fname->base_name[0] == '/') {
+ return res;
+ }
+
+ status = samba_path_matching_check_last_component(state->preopen_names,
+ smb_fname->base_name,
+ &match_idx,
+ &replace_start,
+ &replace_end);
+ if (!NT_STATUS_IS_OK(status)) {
+ match_idx = -1;
+ }
+ if (match_idx < 0) {
+ DBG_PREFIX(state->nomatch_dbglvl, (
+ "No match with the preopen:names list by name[%s]\n",
+ smb_fname_str_dbg(smb_fname)));
+ return res;
+ }
+
+ if (replace_start != -1 && replace_end != -1) {
+ DBG_PREFIX(state->match_dbglvl, (
+ "Pattern(idx=%zd) from preopen:names list matched name[%s] hints(start=%zd,end=%zd)\n",
+ match_idx, smb_fname_str_dbg(smb_fname), replace_start, replace_end));
+ } else {
+ DBG_PREFIX(state->match_dbglvl, (
+ "Pattern(idx=%zd) from preopen:names list matched name[%s]\n",
+ match_idx, smb_fname_str_dbg(smb_fname)));
+ }
+
+ new_template = talloc_asprintf(
+ state, "%s/%s",
+ dirname, smb_fname->base_name);
+ if (new_template == NULL) {
+ DBG_ERR("talloc_asprintf(%s/%s) failed\n",
+ dirname, smb_fname_str_dbg(smb_fname));
+ return res;
+ }
+
+ if (replace_start != -1 && replace_end != -1) {
+ size_t dirofs = strlen(dirname) + 1;
+ new_start = dirofs + replace_start;
+ new_digits = replace_end - replace_start;
+ }
+
+ if (!preopen_parse_fname(new_template, &num,
+ &new_start, &new_digits)) {
+ DBG_PREFIX(state->nodigits_dbglvl, (
+ "Pattern(idx=%zd) no valid digits found on fullpath[%s]\n",
+ match_idx, new_template));
+ TALLOC_FREE(new_template);
+ return res;
+ }
+ new_end = new_start + new_digits;
+
+ DBG_PREFIX(state->founddigits_dbglvl, (
+ "Pattern(idx=%zd) found num_digits[%d] start_offset[%zd] parsed_num[%"PRIu64"] fullpath[%s]\n",
+ match_idx, new_digits, new_start, num, new_template));
+
+ if (state->last_match_idx != match_idx) {
+ /*
+ * If a different pattern caused the match
+ * we better reset the queue
+ */
+ if (state->last_match_idx != -1) {
+ DBG_PREFIX(state->reset_dbglvl, ("RESET: "
+ "pattern changed from idx=%zd to idx=%zd by fullpath[%s]\n",
+ state->last_match_idx, match_idx, new_template));
+ }
+ need_reset = true;
+ } else if (state->number_start != new_start) {
+ /*
+ * If the digits started at a different position
+ * we better reset the queue
+ */
+ DBG_PREFIX(state->reset_dbglvl, ("RESET: "
+ "start_offset changed from byte=%zd to byte=%zd by fullpath[%s]\n",
+ state->number_start, new_start, new_template));
+ need_reset = true;
+ } else if (state->num_digits != new_digits) {
+ /*
+ * If number of digits changed
+ * we better reset the queue
+ */
+ DBG_PREFIX(state->reset_dbglvl, ("RESET: "
+ "num_digits changed %d to %d by fullpath[%s]\n",
+ state->num_digits, new_digits, new_template));
+ need_reset = true;
+ } else if (strncmp(state->template_fname, new_template, new_start) != 0) {
+ /*
+ * If name before the digits changed
+ * we better reset the queue
+ */
+ DBG_PREFIX(state->reset_dbglvl, ("RESET: "
+ "leading pathprefix[%.*s] changed by fullpath[%s]\n",
+ (int)state->number_start, state->template_fname, new_template));
+ need_reset = true;
+ } else if (strcmp(state->template_fname + new_end, new_template + new_end) != 0) {
+ /*
+ * If name after the digits changed
+ * we better reset the queue
+ */
+ DBG_PREFIX(state->reset_dbglvl, ("RESET: "
+ "trailing suffix[%s] changed by fullpath[%s]\n",
+ state->template_fname + new_end, new_template));
+ need_reset = true;
+ }
+
+ if (need_reset) {
+ /*
+ * Reset the queue
+ */
+ state->fnum_sent = 0;
+ state->fnum_queue_end = 0;
+ state->last_match_idx = match_idx;
+ }
+
+ TALLOC_FREE(state->template_fname);
+ state->template_fname = new_template;
+ state->number_start = new_start;
+ state->num_digits = new_digits;
+
+ if (num > state->fnum_sent) {
+ /*
+ * Helpers were too slow, there's no point in reading
+ * files in helpers that we already read in the
+ * parent.
+ */
+ state->fnum_sent = num;
+ }
+
+ if ((state->fnum_queue_end != 0) /* Something was started earlier */
+ && (num < (state->fnum_queue_end - state->queue_max))) {
+ /*
+ * "num" is before the queue we announced. This means
+ * a new run is started.
+ */
+ state->fnum_sent = num;
+ }
+
+ num_max = num_digits_max_value(state->num_digits);
+ state->fnum_queue_end = MIN(num_max, num + state->queue_max);
+
+ DBG_PREFIX(state->queue_dbglvl, ("BEFORE: preopen_queue_run\n"));
+ preopen_queue_run(state);
+ DBG_PREFIX(state->queue_dbglvl, ("AFTER: preopen_queue_run\n"));
+
+ return res;
+}
+
+static struct vfs_fn_pointers vfs_preopen_fns = {
+ .openat_fn = preopen_openat,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_preopen_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS status;
+
+ status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "preopen",
+ &vfs_preopen_fns);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ vfs_preopen_debug_level = debug_add_class("preopen");
+ if (vfs_preopen_debug_level == -1) {
+ vfs_preopen_debug_level = DBGC_VFS;
+ DBG_ERR("Couldn't register custom debugging class!\n");
+ } else {
+ DBG_DEBUG("Debug class number of 'preopen': %d\n",
+ vfs_preopen_debug_level);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/modules/vfs_readahead.c b/source3/modules/vfs_readahead.c
new file mode 100644
index 0000000..bb31b57
--- /dev/null
+++ b/source3/modules/vfs_readahead.c
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) Jeremy Allison 2007.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+
+#if defined(HAVE_LINUX_READAHEAD) && ! defined(HAVE_READAHEAD_DECL)
+ssize_t readahead(int fd, off_t offset, size_t count);
+#endif
+
+struct readahead_data {
+ off_t off_bound;
+ off_t len;
+ bool didmsg;
+};
+
+/*
+ * This module copes with Vista AIO read requests on Linux
+ * by detecting the initial 0x80000 boundary reads and causing
+ * the buffer cache to be filled in advance.
+ */
+
+/*******************************************************************
+ sendfile wrapper that does readahead/posix_fadvise.
+*******************************************************************/
+
+static ssize_t readahead_sendfile(struct vfs_handle_struct *handle,
+ int tofd,
+ files_struct *fromfsp,
+ const DATA_BLOB *header,
+ off_t offset,
+ size_t count)
+{
+ struct readahead_data *rhd = (struct readahead_data *)handle->data;
+
+ if ( offset % rhd->off_bound == 0) {
+#if defined(HAVE_LINUX_READAHEAD)
+ int err = readahead(fsp_get_io_fd(fromfsp), offset, (size_t)rhd->len);
+ DEBUG(10,("readahead_sendfile: readahead on fd %u, offset %llu, len %u returned %d\n",
+ (unsigned int)fsp_get_io_fd(fromfsp),
+ (unsigned long long)offset,
+ (unsigned int)rhd->len,
+ err ));
+#elif defined(HAVE_POSIX_FADVISE)
+ int err = posix_fadvise(fsp_get_io_fd(fromfsp), offset, (off_t)rhd->len, POSIX_FADV_WILLNEED);
+ DEBUG(10,("readahead_sendfile: posix_fadvise on fd %u, offset %llu, len %u returned %d\n",
+ (unsigned int)fsp_get_io_fd(fromfsp),
+ (unsigned long long)offset,
+ (unsigned int)rhd->len,
+ err ));
+#else
+ if (!rhd->didmsg) {
+ DEBUG(0,("readahead_sendfile: no readahead on this platform\n"));
+ rhd->didmsg = True;
+ }
+#endif
+ }
+ return SMB_VFS_NEXT_SENDFILE(handle,
+ tofd,
+ fromfsp,
+ header,
+ offset,
+ count);
+}
+
+/*******************************************************************
+ pread wrapper that does readahead/posix_fadvise.
+*******************************************************************/
+
+static ssize_t readahead_pread(vfs_handle_struct *handle,
+ files_struct *fsp,
+ void *data,
+ size_t count,
+ off_t offset)
+{
+ struct readahead_data *rhd = (struct readahead_data *)handle->data;
+
+ if ( offset % rhd->off_bound == 0) {
+#if defined(HAVE_LINUX_READAHEAD)
+ int err = readahead(fsp_get_io_fd(fsp), offset, (size_t)rhd->len);
+ DEBUG(10,("readahead_pread: readahead on fd %u, offset %llu, len %u returned %d\n",
+ (unsigned int)fsp_get_io_fd(fsp),
+ (unsigned long long)offset,
+ (unsigned int)rhd->len,
+ err ));
+#elif defined(HAVE_POSIX_FADVISE)
+ int err = posix_fadvise(fsp_get_io_fd(fsp), offset, (off_t)rhd->len, POSIX_FADV_WILLNEED);
+ DEBUG(10,("readahead_pread: posix_fadvise on fd %u, offset %llu, len %u returned %d\n",
+ (unsigned int)fsp_get_io_fd(fsp),
+ (unsigned long long)offset,
+ (unsigned int)rhd->len,
+ err ));
+#else
+ if (!rhd->didmsg) {
+ DEBUG(0,("readahead_pread: no readahead on this platform\n"));
+ rhd->didmsg = True;
+ }
+#endif
+ }
+ return SMB_VFS_NEXT_PREAD(handle, fsp, data, count, offset);
+}
+
+/*******************************************************************
+ Directly called from main smbd when freeing handle.
+*******************************************************************/
+
+static void free_readahead_data(void **pptr)
+{
+ SAFE_FREE(*pptr);
+}
+
+/*******************************************************************
+ Allocate the handle specific data so we don't call the expensive
+ conv_str_size function for each sendfile/pread.
+*******************************************************************/
+
+static int readahead_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ struct readahead_data *rhd;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+ rhd = SMB_MALLOC_P(struct readahead_data);
+ if (!rhd) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ DEBUG(0,("readahead_connect: out of memory\n"));
+ return -1;
+ }
+ ZERO_STRUCTP(rhd);
+
+ rhd->didmsg = False;
+ rhd->off_bound = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
+ "readahead",
+ "offset",
+ NULL));
+ if (rhd->off_bound == 0) {
+ rhd->off_bound = 0x80000;
+ }
+ rhd->len = conv_str_size(lp_parm_const_string(SNUM(handle->conn),
+ "readahead",
+ "length",
+ NULL));
+ if (rhd->len == 0) {
+ rhd->len = rhd->off_bound;
+ }
+
+ handle->data = (void *)rhd;
+ handle->free_data = free_readahead_data;
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_readahead_fns = {
+ .sendfile_fn = readahead_sendfile,
+ .pread_fn = readahead_pread,
+ .connect_fn = readahead_connect
+};
+
+/*******************************************************************
+ Module initialization boilerplate.
+*******************************************************************/
+
+static_decl_vfs;
+NTSTATUS vfs_readahead_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "readahead",
+ &vfs_readahead_fns);
+}
diff --git a/source3/modules/vfs_readonly.c b/source3/modules/vfs_readonly.c
new file mode 100644
index 0000000..cde8ef9
--- /dev/null
+++ b/source3/modules/vfs_readonly.c
@@ -0,0 +1,113 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ VFS module to perform read-only limitation based on a time period
+ Copyright (C) Alexander Bokovoy 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ This work was sponsored by Optifacio Software Services, Inc.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "getdate.h"
+
+/*
+ This module performs a read-only limitation for specified share
+ (or all of them if it is loaded in a [global] section) based on period
+ definition in smb.conf. You can stack this module multiple times under
+ different names to get multiple limit intervals.
+
+ The module uses get_date() function from coreutils' date utility to parse
+ specified dates according to date(1) rules. Look into info page for date(1)
+ to understand the syntax.
+
+ The module accepts one parameter:
+
+ readonly: period = "begin date","end date"
+
+ where "begin date" and "end date" are mandatory and should comply with date(1)
+ syntax for date strings.
+
+ Example:
+
+ readonly: period = "today 14:00","today 15:00"
+
+ Default:
+
+ readonly: period = "today 0:0:0","tomorrow 0:0:0"
+
+ The default covers whole day thus making the share readonly
+
+ */
+
+#define MODULE_NAME "readonly"
+static int readonly_connect(vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ const char *period_def[] = {"today 0:0:0", "tomorrow 0:0:0"};
+
+ const char **period = lp_parm_string_list(SNUM(handle->conn),
+ (handle->param ? handle->param : MODULE_NAME),
+ "period", period_def);
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (period && period[0] && period[1]) {
+ int i;
+ time_t current_time = time(NULL);
+ time_t begin_period = get_date(period[0], &current_time);
+ time_t end_period = get_date(period[1], &current_time);
+
+ if ((current_time >= begin_period) && (current_time <= end_period)) {
+ connection_struct *conn = handle->conn;
+
+ handle->conn->read_only = True;
+
+ /* Wipe out the VUID cache. */
+ for (i=0; i< VUID_CACHE_SIZE; i++) {
+ struct vuid_cache_entry *ent = &conn->vuid_cache->array[i];
+ ent->vuid = UID_FIELD_INVALID;
+ TALLOC_FREE(ent->session_info);
+ ent->read_only = false;
+ ent->share_access = 0;
+ }
+ conn->vuid_cache->next_entry = 0;
+ }
+
+ return 0;
+
+ } else {
+
+ return 0;
+
+ }
+}
+
+
+static struct vfs_fn_pointers vfs_readonly_fns = {
+ .connect_fn = readonly_connect
+};
+
+NTSTATUS vfs_readonly_init(TALLOC_CTX *);
+NTSTATUS vfs_readonly_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, MODULE_NAME,
+ &vfs_readonly_fns);
+}
diff --git a/source3/modules/vfs_recycle.c b/source3/modules/vfs_recycle.c
new file mode 100644
index 0000000..327a7ee
--- /dev/null
+++ b/source3/modules/vfs_recycle.c
@@ -0,0 +1,737 @@
+/*
+ * Recycle bin VFS module for Samba.
+ *
+ * Copyright (C) 2001, Brandon Stone, Amherst College, <bbstone@amherst.edu>.
+ * Copyright (C) 2002, Jeremy Allison - modified to make a VFS module.
+ * Copyright (C) 2002, Alexander Bokovoy - cascaded VFS adoption,
+ * Copyright (C) 2002, Juergen Hasch - added some options.
+ * Copyright (C) 2002, Simo Sorce
+ * Copyright (C) 2002, Stefan (metze) Metzmacher
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "auth.h"
+#include "source3/lib/substitute.h"
+
+#define ALLOC_CHECK(ptr, label) do { if ((ptr) == NULL) { DEBUG(0, ("recycle.bin: out of memory!\n")); errno = ENOMEM; goto label; } } while(0)
+
+static int vfs_recycle_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_recycle_debug_level
+
+struct recycle_config_data {
+ const char *repository;
+ bool keeptree;
+ bool versions;
+ bool touch;
+ bool touch_mtime;
+ const char **exclude;
+ const char **exclude_dir;
+ const char **noversions;
+ mode_t directory_mode;
+ mode_t subdir_mode;
+ off_t minsize;
+ off_t maxsize;
+};
+
+static int vfs_recycle_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ struct recycle_config_data *config = NULL;
+ int ret;
+ int t;
+ const char *buff;
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (IS_IPC(handle->conn) || IS_PRINT(handle->conn)) {
+ return 0;
+ }
+
+ config = talloc_zero(handle->conn, struct recycle_config_data);
+ if (config == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+ config->repository = lp_parm_const_string(SNUM(handle->conn),
+ "recycle",
+ "repository",
+ ".recycle");
+ config->keeptree = lp_parm_bool(SNUM(handle->conn),
+ "recycle",
+ "keeptree",
+ False);
+ config->versions = lp_parm_bool(SNUM(handle->conn),
+ "recycle",
+ "versions",
+ False);
+ config->touch = lp_parm_bool(SNUM(handle->conn),
+ "recycle",
+ "touch",
+ False);
+ config->touch_mtime = lp_parm_bool(SNUM(handle->conn),
+ "recycle",
+ "touch_mtime",
+ False);
+ config->exclude = lp_parm_string_list(SNUM(handle->conn),
+ "recycle",
+ "exclude",
+ NULL);
+ config->exclude_dir = lp_parm_string_list(SNUM(handle->conn),
+ "recycle",
+ "exclude_dir",
+ NULL);
+ config->noversions = lp_parm_string_list(SNUM(handle->conn),
+ "recycle",
+ "noversions",
+ NULL);
+ config->minsize = conv_str_size(lp_parm_const_string(
+ SNUM(handle->conn), "recycle", "minsize", NULL));
+ config->maxsize = conv_str_size(lp_parm_const_string(
+ SNUM(handle->conn), "recycle", "maxsize", NULL));
+
+ buff = lp_parm_const_string(SNUM(handle->conn),
+ "recycle",
+ "directory_mode",
+ NULL);
+ if (buff != NULL ) {
+ sscanf(buff, "%o", &t);
+ } else {
+ t = S_IRUSR | S_IWUSR | S_IXUSR;
+ }
+ config->directory_mode = (mode_t)t;
+
+ buff = lp_parm_const_string(SNUM(handle->conn),
+ "recycle",
+ "subdir_mode",
+ NULL);
+ if (buff != NULL ) {
+ sscanf(buff, "%o", &t);
+ } else {
+ t = config->directory_mode;
+ }
+ config->subdir_mode = (mode_t)t;
+
+ SMB_VFS_HANDLE_SET_DATA(
+ handle, config, NULL, struct recycle_config_data, return -1);
+ return 0;
+}
+
+static bool recycle_directory_exist(vfs_handle_struct *handle, const char *dname)
+{
+ struct smb_filename smb_fname = {
+ .base_name = discard_const_p(char, dname)
+ };
+
+ if (SMB_VFS_STAT(handle->conn, &smb_fname) == 0) {
+ if (S_ISDIR(smb_fname.st.st_ex_mode)) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+static bool recycle_file_exist(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *smb_fname_tmp = NULL;
+ bool ret = false;
+
+ smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
+ if (smb_fname_tmp == NULL) {
+ return false;
+ }
+
+ if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) == 0) {
+ if (S_ISREG(smb_fname_tmp->st.st_ex_mode)) {
+ ret = true;
+ }
+ }
+
+ TALLOC_FREE(smb_fname_tmp);
+ return ret;
+}
+
+/**
+ * Return file size
+ * @param conn connection
+ * @param fname file name
+ * @return size in bytes
+ **/
+static off_t recycle_get_file_size(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *smb_fname_tmp = NULL;
+ off_t size;
+
+ smb_fname_tmp = cp_smb_filename(talloc_tos(), smb_fname);
+ if (smb_fname_tmp == NULL) {
+ size = (off_t)0;
+ goto out;
+ }
+
+ if (SMB_VFS_STAT(handle->conn, smb_fname_tmp) != 0) {
+ DBG_DEBUG("stat for %s returned %s\n",
+ smb_fname_str_dbg(smb_fname_tmp), strerror(errno));
+ size = (off_t)0;
+ goto out;
+ }
+
+ size = smb_fname_tmp->st.st_ex_size;
+ out:
+ TALLOC_FREE(smb_fname_tmp);
+ return size;
+}
+
+/**
+ * Create directory tree
+ * @param conn connection
+ * @param dname Directory tree to be created
+ * @param directory mode
+ * @param subdirectory mode
+ * @return Returns True for success
+ **/
+static bool recycle_create_dir(vfs_handle_struct *handle,
+ const char *dname,
+ mode_t dir_mode,
+ mode_t subdir_mode)
+{
+ size_t len;
+ mode_t mode = dir_mode;
+ char *new_dir = NULL;
+ char *tmp_str = NULL;
+ char *token;
+ char *tok_str;
+ bool ret = False;
+ char *saveptr;
+
+ tmp_str = SMB_STRDUP(dname);
+ ALLOC_CHECK(tmp_str, done);
+ tok_str = tmp_str;
+
+ len = strlen(dname)+1;
+ new_dir = (char *)SMB_MALLOC(len + 1);
+ ALLOC_CHECK(new_dir, done);
+ *new_dir = '\0';
+ if (dname[0] == '/') {
+ /* Absolute path. */
+ if (strlcat(new_dir,"/",len+1) >= len+1) {
+ goto done;
+ }
+ }
+
+ /* Create directory tree if necessary */
+ for(token = strtok_r(tok_str, "/", &saveptr); token;
+ token = strtok_r(NULL, "/", &saveptr)) {
+ if (strlcat(new_dir, token, len+1) >= len+1) {
+ goto done;
+ }
+ if (recycle_directory_exist(handle, new_dir))
+ DEBUG(10, ("recycle: dir %s already exists\n", new_dir));
+ else {
+ struct smb_filename *smb_fname = NULL;
+ int retval;
+
+ DEBUG(5, ("recycle: creating new dir %s\n", new_dir));
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ new_dir,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto done;
+ }
+
+ retval = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ smb_fname,
+ mode);
+ if (retval != 0) {
+ DBG_WARNING("recycle: mkdirat failed "
+ "for %s with error: %s\n",
+ new_dir,
+ strerror(errno));
+ TALLOC_FREE(smb_fname);
+ ret = False;
+ goto done;
+ }
+ TALLOC_FREE(smb_fname);
+ }
+ if (strlcat(new_dir, "/", len+1) >= len+1) {
+ goto done;
+ }
+ mode = subdir_mode;
+ }
+
+ ret = True;
+done:
+ SAFE_FREE(tmp_str);
+ SAFE_FREE(new_dir);
+ return ret;
+}
+
+/**
+ * Check if any of the components of "exclude_list" are contained in path.
+ * Return True if found
+ **/
+
+static bool matchdirparam(const char **dir_exclude_list, char *path)
+{
+ char *startp = NULL, *endp = NULL;
+
+ if (dir_exclude_list == NULL || dir_exclude_list[0] == NULL ||
+ *dir_exclude_list[0] == '\0' || path == NULL || *path == '\0') {
+ return False;
+ }
+
+ /*
+ * Walk the components of path, looking for matches with the
+ * exclude list on each component.
+ */
+
+ for (startp = path; startp; startp = endp) {
+ int i;
+
+ while (*startp == '/') {
+ startp++;
+ }
+ endp = strchr(startp, '/');
+ if (endp) {
+ *endp = '\0';
+ }
+
+ for(i=0; dir_exclude_list[i] ; i++) {
+ if(unix_wild_match(dir_exclude_list[i], startp)) {
+ /* Repair path. */
+ if (endp) {
+ *endp = '/';
+ }
+ return True;
+ }
+ }
+
+ /* Repair path. */
+ if (endp) {
+ *endp = '/';
+ }
+ }
+
+ return False;
+}
+
+/**
+ * Check if needle is contained in haystack, * and ? patterns are resolved
+ * @param haystack list of parameters separated by delimimiter character
+ * @param needle string to be matched exectly to haystack including pattern matching
+ * @return True if found
+ **/
+static bool matchparam(const char **haystack_list, const char *needle)
+{
+ int i;
+
+ if (haystack_list == NULL || haystack_list[0] == NULL ||
+ *haystack_list[0] == '\0' || needle == NULL || *needle == '\0') {
+ return False;
+ }
+
+ for(i=0; haystack_list[i] ; i++) {
+ if(unix_wild_match(haystack_list[i], needle)) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+/**
+ * Touch access or modify date
+ **/
+static void recycle_do_touch(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ bool touch_mtime)
+{
+ struct smb_filename *smb_fname_tmp = NULL;
+ struct smb_file_time ft;
+ int ret, err;
+ NTSTATUS status;
+
+ init_smb_file_time(&ft);
+
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ smb_fname->base_name,
+ smb_fname->stream_name,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &smb_fname_tmp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("synthetic_pathref for '%s' failed: %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ return;
+ }
+
+ /* atime */
+ ft.atime = timespec_current();
+ /* mtime */
+ ft.mtime = touch_mtime ? ft.atime : smb_fname_tmp->st.st_ex_mtime;
+
+ become_root();
+ ret = SMB_VFS_NEXT_FNTIMES(handle, smb_fname_tmp->fsp, &ft);
+ err = errno;
+ unbecome_root();
+ if (ret == -1 ) {
+ DEBUG(0, ("recycle: touching %s failed, reason = %s\n",
+ smb_fname_str_dbg(smb_fname_tmp), strerror(err)));
+ }
+
+ TALLOC_FREE(smb_fname_tmp);
+}
+
+/**
+ * Check if file should be recycled
+ **/
+static int recycle_unlink_internal(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = handle->conn;
+ struct smb_filename *full_fname = NULL;
+ char *path_name = NULL;
+ char *temp_name = NULL;
+ char *final_name = NULL;
+ struct smb_filename *smb_fname_final = NULL;
+ const char *base;
+ char *repository = NULL;
+ int i = 1;
+ off_t file_size; /* space_avail; */
+ bool exist;
+ int rc = -1;
+ struct recycle_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct recycle_config_data,
+ return true);
+
+ repository = talloc_sub_full(
+ NULL,
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ config->repository);
+ ALLOC_CHECK(repository, done);
+ /* shouldn't we allow absolute path names here? --metze */
+ /* Yes :-). JRA. */
+ trim_char(repository, '\0', '/');
+
+ if(!repository || *(repository) == '\0') {
+ DEBUG(3, ("recycle: repository path not set, purging %s...\n",
+ smb_fname_str_dbg(smb_fname)));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ /* we don't recycle the recycle bin... */
+ if (strncmp(full_fname->base_name, repository,
+ strlen(repository)) == 0) {
+ DEBUG(3, ("recycle: File is within recycling bin, unlinking ...\n"));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+
+ file_size = recycle_get_file_size(handle, full_fname);
+ /* it is wrong to purge filenames only because they are empty imho
+ * --- simo
+ *
+ if(fsize == 0) {
+ DEBUG(3, ("recycle: File %s is empty, purging...\n", file_name));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ file_name,
+ flags);
+ goto done;
+ }
+ */
+
+ /* FIXME: this is wrong, we should check the whole size of the recycle bin is
+ * not greater then maxsize, not the size of the single file, also it is better
+ * to remove older files
+ */
+ if (config->maxsize > 0 && file_size > config->maxsize) {
+ DBG_NOTICE("File %s exceeds maximum recycle size, "
+ "purging... \n",
+ smb_fname_str_dbg(full_fname));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+ if (config->minsize > 0 && file_size < config->minsize) {
+ DBG_NOTICE("File %s lowers minimum recycle size, "
+ "purging... \n",
+ smb_fname_str_dbg(full_fname));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+
+ /* FIXME: this is wrong: moving files with rename does not change the disk space
+ * allocation
+ *
+ space_avail = SMB_VFS_NEXT_DISK_FREE(handle, ".", True, &bsize, &dfree, &dsize) * 1024L;
+ DEBUG(5, ("space_avail = %Lu, file_size = %Lu\n", space_avail, file_size));
+ if(space_avail < file_size) {
+ DEBUG(3, ("recycle: Not enough diskspace, purging file %s\n", file_name));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ file_name,
+ flags);
+ goto done;
+ }
+ */
+
+ /* extract filename and path */
+ if (!parent_dirname(talloc_tos(), full_fname->base_name, &path_name, &base)) {
+ rc = -1;
+ errno = ENOMEM;
+ goto done;
+ }
+
+ /* original filename with path */
+ DEBUG(10, ("recycle: fname = %s\n", smb_fname_str_dbg(full_fname)));
+ /* original path */
+ DEBUG(10, ("recycle: fpath = %s\n", path_name));
+ /* filename without path */
+ DEBUG(10, ("recycle: base = %s\n", base));
+
+ if (matchparam(config->exclude, base)) {
+ DEBUG(3, ("recycle: file %s is excluded \n", base));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+
+ if (matchdirparam(config->exclude_dir, path_name)) {
+ DEBUG(3, ("recycle: directory %s is excluded \n", path_name));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+
+ if (config->keeptree) {
+ if (asprintf(&temp_name, "%s/%s", repository, path_name) == -1) {
+ ALLOC_CHECK(temp_name, done);
+ }
+ } else {
+ temp_name = SMB_STRDUP(repository);
+ }
+ ALLOC_CHECK(temp_name, done);
+
+ exist = recycle_directory_exist(handle, temp_name);
+ if (exist) {
+ DEBUG(10, ("recycle: Directory already exists\n"));
+ } else {
+ DEBUG(10, ("recycle: Creating directory %s\n", temp_name));
+ if (recycle_create_dir(handle,
+ temp_name,
+ config->directory_mode,
+ config->subdir_mode) == False)
+ {
+ DEBUG(3, ("recycle: Could not create directory, "
+ "purging %s...\n",
+ smb_fname_str_dbg(full_fname)));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+ }
+
+ if (asprintf(&final_name, "%s/%s", temp_name, base) == -1) {
+ ALLOC_CHECK(final_name, done);
+ }
+
+ /* Create smb_fname with final base name and orig stream name. */
+ smb_fname_final = synthetic_smb_fname(talloc_tos(),
+ final_name,
+ full_fname->stream_name,
+ NULL,
+ full_fname->twrp,
+ full_fname->flags);
+ if (smb_fname_final == NULL) {
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+
+ /* new filename with path */
+ DEBUG(10, ("recycle: recycled file name: %s\n",
+ smb_fname_str_dbg(smb_fname_final)));
+
+ /* check if we should delete file from recycle bin */
+ if (recycle_file_exist(handle, smb_fname_final)) {
+ if (config->versions == False ||
+ matchparam(config->noversions, base) == True) {
+ DEBUG(3, ("recycle: Removing old file %s from recycle "
+ "bin\n", smb_fname_str_dbg(smb_fname_final)));
+ if (SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp->conn->cwd_fsp,
+ smb_fname_final,
+ flags) != 0) {
+ DEBUG(1, ("recycle: Error deleting old file: %s\n", strerror(errno)));
+ }
+ }
+ }
+
+ /* rename file we move to recycle bin */
+ i = 1;
+ while (recycle_file_exist(handle, smb_fname_final)) {
+ SAFE_FREE(final_name);
+ if (asprintf(&final_name, "%s/Copy #%d of %s", temp_name, i++, base) == -1) {
+ ALLOC_CHECK(final_name, done);
+ }
+ TALLOC_FREE(smb_fname_final->base_name);
+ smb_fname_final->base_name = talloc_strdup(smb_fname_final,
+ final_name);
+ if (smb_fname_final->base_name == NULL) {
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+ }
+
+ DEBUG(10, ("recycle: Moving %s to %s\n", smb_fname_str_dbg(full_fname),
+ smb_fname_str_dbg(smb_fname_final)));
+ rc = SMB_VFS_NEXT_RENAMEAT(handle,
+ dirfsp,
+ smb_fname,
+ handle->conn->cwd_fsp,
+ smb_fname_final);
+ if (rc != 0) {
+ DEBUG(3, ("recycle: Move error %d (%s), purging file %s "
+ "(%s)\n", errno, strerror(errno),
+ smb_fname_str_dbg(full_fname),
+ smb_fname_str_dbg(smb_fname_final)));
+ rc = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ goto done;
+ }
+
+ /* touch access date of moved file */
+ if (config->touch || config->touch_mtime)
+ recycle_do_touch(handle, smb_fname_final, config->touch_mtime);
+
+done:
+ TALLOC_FREE(path_name);
+ SAFE_FREE(temp_name);
+ SAFE_FREE(final_name);
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(smb_fname_final);
+ TALLOC_FREE(repository);
+ return rc;
+}
+
+static int recycle_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret;
+
+ if (flags & AT_REMOVEDIR) {
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ } else {
+ ret = recycle_unlink_internal(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ }
+ return ret;
+}
+
+static struct vfs_fn_pointers vfs_recycle_fns = {
+ .connect_fn = vfs_recycle_connect,
+ .unlinkat_fn = recycle_unlinkat,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_recycle_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "recycle",
+ &vfs_recycle_fns);
+
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ vfs_recycle_debug_level = debug_add_class("recycle");
+ if (vfs_recycle_debug_level == -1) {
+ vfs_recycle_debug_level = DBGC_VFS;
+ DEBUG(0, ("vfs_recycle: Couldn't register custom debugging class!\n"));
+ } else {
+ DEBUG(10, ("vfs_recycle: Debug class number of 'recycle': %d\n", vfs_recycle_debug_level));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_shadow_copy.c b/source3/modules/vfs_shadow_copy.c
new file mode 100644
index 0000000..62b9291
--- /dev/null
+++ b/source3/modules/vfs_shadow_copy.c
@@ -0,0 +1,274 @@
+/*
+ * implementation of an Shadow Copy module
+ *
+ * Copyright (C) Stefan Metzmacher 2003-2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "ntioctl.h"
+#include "source3/smbd/dir.h"
+
+/*
+ Please read the VFS module Samba-HowTo-Collection.
+ there's a chapter about this module
+
+ For this share
+ Z:\
+
+ the ShadowCopies are in this directories
+
+ Z:\@GMT-2003.08.05-12.00.00\
+ Z:\@GMT-2003.08.05-12.01.00\
+ Z:\@GMT-2003.08.05-12.02.00\
+
+ e.g.
+
+ Z:\testfile.txt
+ Z:\@GMT-2003.08.05-12.02.00\testfile.txt
+
+ or:
+
+ Z:\testdir\testfile.txt
+ Z:\@GMT-2003.08.05-12.02.00\testdir\testfile.txt
+
+
+ Note: Files must differ to be displayed via Windows Explorer!
+ Directories are always displayed...
+*/
+
+static int vfs_shadow_copy_debug_level = DBGC_VFS;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS vfs_shadow_copy_debug_level
+
+#define SHADOW_COPY_PREFIX "@GMT-"
+#define SHADOW_COPY_SAMPLE "@GMT-2004.02.18-15.44.00"
+
+typedef struct {
+ int pos;
+ int num;
+ struct dirent *dirs;
+} shadow_copy_Dir;
+
+static bool shadow_copy_match_name(const char *name)
+{
+ if (strncmp(SHADOW_COPY_PREFIX,name, sizeof(SHADOW_COPY_PREFIX)-1)==0 &&
+ (strlen(SHADOW_COPY_SAMPLE) == strlen(name))) {
+ return True;
+ }
+
+ return False;
+}
+
+static DIR *shadow_copy_fdopendir(vfs_handle_struct *handle, files_struct *fsp, const char *mask, uint32_t attr)
+{
+ shadow_copy_Dir *dirp;
+ DIR *p = SMB_VFS_NEXT_FDOPENDIR(handle,fsp,mask,attr);
+
+ if (!p) {
+ DEBUG(10,("shadow_copy_opendir: SMB_VFS_NEXT_FDOPENDIR() failed for [%s]\n",
+ smb_fname_str_dbg(fsp->fsp_name)));
+ return NULL;
+ }
+
+ dirp = SMB_MALLOC_P(shadow_copy_Dir);
+ if (!dirp) {
+ DEBUG(0,("shadow_copy_fdopendir: Out of memory\n"));
+ SMB_VFS_NEXT_CLOSEDIR(handle,p);
+ /* We have now closed the fd in fsp. */
+ fsp_set_fd(fsp, -1);
+ return NULL;
+ }
+
+ ZERO_STRUCTP(dirp);
+
+ while (True) {
+ struct dirent *d;
+
+ d = SMB_VFS_NEXT_READDIR(handle, fsp, p);
+ if (d == NULL) {
+ break;
+ }
+
+ if (shadow_copy_match_name(d->d_name)) {
+ DEBUG(8,("shadow_copy_fdopendir: hide [%s]\n",d->d_name));
+ continue;
+ }
+
+ DEBUG(10,("shadow_copy_fdopendir: not hide [%s]\n",d->d_name));
+
+ dirp->dirs = SMB_REALLOC_ARRAY(dirp->dirs,struct dirent, dirp->num+1);
+ if (!dirp->dirs) {
+ DEBUG(0,("shadow_copy_fdopendir: Out of memory\n"));
+ break;
+ }
+
+ dirp->dirs[dirp->num++] = *d;
+ }
+
+ SMB_VFS_NEXT_CLOSEDIR(handle,p);
+ /* We have now closed the fd in fsp. */
+ fsp_set_fd(fsp, -1);
+ return((DIR *)dirp);
+}
+
+static struct dirent *shadow_copy_readdir(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *_dirp)
+{
+ shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp;
+
+ if (dirp->pos < dirp->num) {
+ return &(dirp->dirs[dirp->pos++]);
+ }
+
+ return NULL;
+}
+
+static void shadow_copy_rewinddir(struct vfs_handle_struct *handle, DIR *_dirp)
+{
+ shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp;
+ dirp->pos = 0 ;
+}
+
+static int shadow_copy_closedir(vfs_handle_struct *handle, DIR *_dirp)
+{
+ shadow_copy_Dir *dirp = (shadow_copy_Dir *)_dirp;
+
+ SAFE_FREE(dirp->dirs);
+ SAFE_FREE(dirp);
+
+ return 0;
+}
+
+static int shadow_copy_get_shadow_copy_data(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels)
+{
+ struct smb_Dir *dir_hnd = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ NTSTATUS status;
+ struct smb_filename *smb_fname = synthetic_smb_fname(talloc_tos(),
+ fsp->conn->connectpath,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ status = OpenDir(talloc_tos(),
+ handle->conn,
+ smb_fname,
+ NULL,
+ 0,
+ &dir_hnd);
+ TALLOC_FREE(smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("OpenDir() failed for [%s]\n", fsp->conn->connectpath);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ shadow_copy_data->num_volumes = 0;
+ shadow_copy_data->labels = NULL;
+
+ while (True) {
+ SHADOW_COPY_LABEL *tlabels;
+ int ret;
+
+ dname = ReadDirName(dir_hnd, &talloced);
+ if (dname == NULL) {
+ break;
+ }
+
+ /* */
+ if (!shadow_copy_match_name(dname)) {
+ DBG_DEBUG("ignore [%s]\n", dname);
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ DBG_DEBUG("not ignore [%s]\n", dname);
+
+ if (!labels) {
+ shadow_copy_data->num_volumes++;
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ tlabels = (SHADOW_COPY_LABEL *)TALLOC_REALLOC(shadow_copy_data,
+ shadow_copy_data->labels,
+ (shadow_copy_data->num_volumes+1)*sizeof(SHADOW_COPY_LABEL));
+ if (tlabels == NULL) {
+ DEBUG(0,("shadow_copy_get_shadow_copy_data: Out of memory\n"));
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(dir_hnd);
+ return -1;
+ }
+
+ ret = strlcpy(tlabels[shadow_copy_data->num_volumes], dname,
+ sizeof(tlabels[shadow_copy_data->num_volumes]));
+ if (ret != sizeof(tlabels[shadow_copy_data->num_volumes]) - 1) {
+ DBG_ERR("malformed label %s\n", dname);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(dir_hnd);
+ return -1;
+ }
+ shadow_copy_data->num_volumes++;
+
+ shadow_copy_data->labels = tlabels;
+ TALLOC_FREE(talloced);
+ }
+
+ TALLOC_FREE(dir_hnd);
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_shadow_copy_fns = {
+ .fdopendir_fn = shadow_copy_fdopendir,
+ .readdir_fn = shadow_copy_readdir,
+ .rewind_dir_fn = shadow_copy_rewinddir,
+ .closedir_fn = shadow_copy_closedir,
+ .get_shadow_copy_data_fn = shadow_copy_get_shadow_copy_data,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_shadow_copy_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "shadow_copy", &vfs_shadow_copy_fns);
+
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ vfs_shadow_copy_debug_level = debug_add_class("shadow_copy");
+ if (vfs_shadow_copy_debug_level == -1) {
+ vfs_shadow_copy_debug_level = DBGC_VFS;
+ DEBUG(0, ("%s: Couldn't register custom debugging class!\n",
+ "vfs_shadow_copy_init"));
+ } else {
+ DEBUG(10, ("%s: Debug class number of '%s': %d\n",
+ "vfs_shadow_copy_init","shadow_copy",vfs_shadow_copy_debug_level));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_shadow_copy2.c b/source3/modules/vfs_shadow_copy2.c
new file mode 100644
index 0000000..c6e6ecd
--- /dev/null
+++ b/source3/modules/vfs_shadow_copy2.c
@@ -0,0 +1,3303 @@
+/*
+ * shadow_copy2: a shadow copy module (second implementation)
+ *
+ * Copyright (C) Andrew Tridgell 2007 (portions taken from shadow_copy2)
+ * Copyright (C) Ed Plese 2009
+ * Copyright (C) Volker Lendecke 2011
+ * Copyright (C) Christian Ambach 2011
+ * Copyright (C) Michael Adam 2013
+ * Copyright (C) Rajesh Joseph 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This is a second implementation of a shadow copy module for exposing
+ * file system snapshots to windows clients as shadow copies.
+ *
+ * See the manual page for documentation.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "include/ntioctl.h"
+#include "util_tdb.h"
+#include "lib/util_path.h"
+#include "libcli/security/security.h"
+#include "lib/util/tevent_unix.h"
+
+struct shadow_copy2_config {
+ char *gmt_format;
+ bool use_sscanf;
+ bool use_localtime;
+ char *snapdir;
+ char *delimiter;
+ bool snapdirseverywhere;
+ bool crossmountpoints;
+ bool fixinodes;
+ char *sort_order;
+ bool snapdir_absolute;
+ char *mount_point;
+ char *rel_connectpath; /* share root, relative to a snapshot root */
+ char *snapshot_basepath; /* the absolute version of snapdir */
+};
+
+/* Data-structure to hold the list of snap entries */
+struct shadow_copy2_snapentry {
+ char *snapname;
+ char *time_fmt;
+ struct shadow_copy2_snapentry *next;
+ struct shadow_copy2_snapentry *prev;
+};
+
+struct shadow_copy2_snaplist_info {
+ struct shadow_copy2_snapentry *snaplist; /* snapshot list */
+ regex_t *regex; /* Regex to filter snaps */
+ time_t fetch_time; /* snaplist update time */
+};
+
+/*
+ * shadow_copy2 private structure. This structure will be
+ * used to keep module specific information
+ */
+struct shadow_copy2_private {
+ struct shadow_copy2_config *config;
+ struct shadow_copy2_snaplist_info *snaps;
+ char *shadow_cwd; /* Absolute $cwd path. */
+ /* Absolute connectpath - can vary depending on $cwd. */
+ char *shadow_connectpath;
+ /* talloc'ed realpath return. */
+ struct smb_filename *shadow_realpath;
+};
+
+static int shadow_copy2_get_shadow_copy_data(
+ vfs_handle_struct *handle, files_struct *fsp,
+ struct shadow_copy_data *shadow_copy2_data,
+ bool labels);
+
+/**
+ * This function will create a new snapshot list entry and
+ * return to the caller. This entry will also be added to
+ * the global snapshot list.
+ *
+ * @param[in] priv shadow_copy2 specific data structure
+ * @return Newly created snapshot entry or NULL on failure
+ */
+static struct shadow_copy2_snapentry *shadow_copy2_create_snapentry(
+ struct shadow_copy2_private *priv)
+{
+ struct shadow_copy2_snapentry *tmpentry = NULL;
+
+ tmpentry = talloc_zero(priv->snaps, struct shadow_copy2_snapentry);
+ if (tmpentry == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ DLIST_ADD(priv->snaps->snaplist, tmpentry);
+
+ return tmpentry;
+}
+
+/**
+ * This function will delete the entire snaplist and reset
+ * priv->snaps->snaplist to NULL.
+ *
+ * @param[in] priv shadow_copye specific data structure
+ */
+static void shadow_copy2_delete_snaplist(struct shadow_copy2_private *priv)
+{
+ struct shadow_copy2_snapentry *tmp = NULL;
+
+ while ((tmp = priv->snaps->snaplist) != NULL) {
+ DLIST_REMOVE(priv->snaps->snaplist, tmp);
+ talloc_free(tmp);
+ }
+}
+
+/**
+ * Given a timestamp this function searches the global snapshot list
+ * and returns the complete snapshot directory name saved in the entry.
+ *
+ * @param[in] priv shadow_copy2 specific structure
+ * @param[in] timestamp timestamp corresponding to one of the snapshot
+ * @param[out] snap_str buffer to copy the actual snapshot name
+ * @param[in] len length of snap_str buffer
+ *
+ * @return Length of actual snapshot name, and -1 on failure
+ */
+static ssize_t shadow_copy2_saved_snapname(struct shadow_copy2_private *priv,
+ struct tm *timestamp,
+ char *snap_str, size_t len)
+{
+ ssize_t snaptime_len = -1;
+ struct shadow_copy2_snapentry *entry = NULL;
+
+ snaptime_len = strftime(snap_str, len, GMT_FORMAT, timestamp);
+ if (snaptime_len == 0) {
+ DBG_ERR("strftime failed\n");
+ return -1;
+ }
+
+ snaptime_len = -1;
+
+ for (entry = priv->snaps->snaplist; entry; entry = entry->next) {
+ if (strcmp(entry->time_fmt, snap_str) == 0) {
+ snaptime_len = snprintf(snap_str, len, "%s",
+ entry->snapname);
+ return snaptime_len;
+ }
+ }
+
+ snap_str[0] = 0;
+ return snaptime_len;
+}
+
+
+/**
+ * This function will check if snaplist is updated or not. If snaplist
+ * is empty then it will create a new list. Each time snaplist is updated
+ * the time is recorded. If the snapshot time is greater than the snaplist
+ * update time then chances are we are working on an older list. Then discard
+ * the old list and fetch a new snaplist.
+ *
+ * @param[in] handle VFS handle struct
+ * @param[in] snap_time time of snapshot
+ *
+ * @return true if the list is updated else false
+ */
+static bool shadow_copy2_update_snaplist(struct vfs_handle_struct *handle,
+ time_t snap_time)
+{
+ int ret = -1;
+ bool snaplist_updated = false;
+ struct files_struct fsp = {0};
+ struct smb_filename smb_fname = {0};
+ double seconds = 0.0;
+ struct shadow_copy2_private *priv = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return false);
+
+ seconds = difftime(snap_time, priv->snaps->fetch_time);
+
+ /*
+ * Fetch the snapshot list if either the snaplist is empty or the
+ * required snapshot time is greater than the last fetched snaplist
+ * time.
+ */
+ if (seconds > 0 || (priv->snaps->snaplist == NULL)) {
+ smb_fname.base_name = discard_const_p(char, ".");
+ fsp.fsp_name = &smb_fname;
+
+ ret = shadow_copy2_get_shadow_copy_data(handle, &fsp,
+ NULL, false);
+ if (ret == 0) {
+ snaplist_updated = true;
+ } else {
+ DBG_ERR("Failed to get shadow copy data\n");
+ }
+
+ }
+
+ return snaplist_updated;
+}
+
+static bool shadow_copy2_find_slashes(TALLOC_CTX *mem_ctx, const char *str,
+ size_t **poffsets,
+ unsigned *pnum_offsets)
+{
+ unsigned num_offsets;
+ size_t *offsets;
+ const char *p;
+
+ num_offsets = 0;
+
+ p = str;
+ while ((p = strchr(p, '/')) != NULL) {
+ num_offsets += 1;
+ p += 1;
+ }
+
+ offsets = talloc_array(mem_ctx, size_t, num_offsets);
+ if (offsets == NULL) {
+ return false;
+ }
+
+ p = str;
+ num_offsets = 0;
+ while ((p = strchr(p, '/')) != NULL) {
+ offsets[num_offsets] = p-str;
+ num_offsets += 1;
+ p += 1;
+ }
+
+ *poffsets = offsets;
+ *pnum_offsets = num_offsets;
+ return true;
+}
+
+/**
+ * Given a timestamp, build the posix level GMT-tag string
+ * based on the configurable format.
+ */
+static ssize_t shadow_copy2_posix_gmt_string(struct vfs_handle_struct *handle,
+ time_t snapshot,
+ char *snaptime_string,
+ size_t len)
+{
+ struct tm snap_tm;
+ ssize_t snaptime_len;
+ struct shadow_copy2_config *config;
+ struct shadow_copy2_private *priv;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return 0);
+
+ config = priv->config;
+
+ if (config->use_sscanf) {
+ snaptime_len = snprintf(snaptime_string,
+ len,
+ config->gmt_format,
+ (unsigned long)snapshot);
+ if (snaptime_len <= 0) {
+ DEBUG(10, ("snprintf failed\n"));
+ return -1;
+ }
+ } else {
+ if (config->use_localtime) {
+ if (localtime_r(&snapshot, &snap_tm) == 0) {
+ DEBUG(10, ("gmtime_r failed\n"));
+ return -1;
+ }
+ } else {
+ if (gmtime_r(&snapshot, &snap_tm) == 0) {
+ DEBUG(10, ("gmtime_r failed\n"));
+ return -1;
+ }
+ }
+
+ if (priv->snaps->regex != NULL) {
+ snaptime_len = shadow_copy2_saved_snapname(priv,
+ &snap_tm, snaptime_string, len);
+ if (snaptime_len >= 0)
+ return snaptime_len;
+
+ /*
+ * If we fail to find the snapshot name, chances are
+ * that we have not updated our snaplist. Make sure the
+ * snaplist is updated.
+ */
+ if (!shadow_copy2_update_snaplist(handle, snapshot)) {
+ DBG_DEBUG("shadow_copy2_update_snaplist "
+ "failed\n");
+ return -1;
+ }
+
+ return shadow_copy2_saved_snapname(priv,
+ &snap_tm, snaptime_string, len);
+ }
+
+ snaptime_len = strftime(snaptime_string,
+ len,
+ config->gmt_format,
+ &snap_tm);
+ if (snaptime_len == 0) {
+ DEBUG(10, ("strftime failed\n"));
+ return -1;
+ }
+ }
+
+ return snaptime_len;
+}
+
+/**
+ * Given a timestamp, build the string to insert into a path
+ * as a path component for creating the local path to the
+ * snapshot at the given timestamp of the input path.
+ *
+ * In the case of a parallel snapdir (specified with an
+ * absolute path), this is the initial portion of the
+ * local path of any snapshot file. The complete path is
+ * obtained by appending the portion of the file's path
+ * below the share root's mountpoint.
+ */
+static char *shadow_copy2_insert_string(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ time_t snapshot)
+{
+ fstring snaptime_string;
+ ssize_t snaptime_len = 0;
+ char *result = NULL;
+ struct shadow_copy2_config *config;
+ struct shadow_copy2_private *priv;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return NULL);
+
+ config = priv->config;
+
+ snaptime_len = shadow_copy2_posix_gmt_string(handle,
+ snapshot,
+ snaptime_string,
+ sizeof(snaptime_string));
+ if (snaptime_len <= 0) {
+ return NULL;
+ }
+
+ if (config->snapdir_absolute) {
+ result = talloc_asprintf(mem_ctx, "%s/%s",
+ config->snapdir, snaptime_string);
+ } else {
+ result = talloc_asprintf(mem_ctx, "/%s/%s",
+ config->snapdir, snaptime_string);
+ }
+ if (result == NULL) {
+ DBG_WARNING("talloc_asprintf failed\n");
+ }
+
+ return result;
+}
+
+/**
+ * Build the posix snapshot path for the connection
+ * at the given timestamp, i.e. the absolute posix path
+ * that contains the snapshot for this file system.
+ *
+ * This only applies to classical case, i.e. not
+ * to the "snapdirseverywhere" mode.
+ */
+static char *shadow_copy2_snapshot_path(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ time_t snapshot)
+{
+ fstring snaptime_string;
+ ssize_t snaptime_len = 0;
+ char *result = NULL;
+ struct shadow_copy2_private *priv;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return NULL);
+
+ snaptime_len = shadow_copy2_posix_gmt_string(handle,
+ snapshot,
+ snaptime_string,
+ sizeof(snaptime_string));
+ if (snaptime_len <= 0) {
+ return NULL;
+ }
+
+ result = talloc_asprintf(mem_ctx, "%s/%s",
+ priv->config->snapshot_basepath, snaptime_string);
+ if (result == NULL) {
+ DBG_WARNING("talloc_asprintf failed\n");
+ }
+
+ return result;
+}
+
+static char *make_path_absolute(TALLOC_CTX *mem_ctx,
+ struct shadow_copy2_private *priv,
+ const char *name)
+{
+ char *newpath = NULL;
+ char *abs_path = NULL;
+
+ if (name[0] != '/') {
+ newpath = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ priv->shadow_cwd,
+ name);
+ if (newpath == NULL) {
+ return NULL;
+ }
+ name = newpath;
+ }
+ abs_path = canonicalize_absolute_path(mem_ctx, name);
+ TALLOC_FREE(newpath);
+ return abs_path;
+}
+
+/* Return a $cwd-relative path. */
+static bool make_relative_path(const char *cwd, char *abs_path)
+{
+ size_t cwd_len = strlen(cwd);
+ size_t abs_len = strlen(abs_path);
+
+ if (abs_len < cwd_len) {
+ return false;
+ }
+ if (memcmp(abs_path, cwd, cwd_len) != 0) {
+ return false;
+ }
+ /* The cwd_len != 1 case is for $cwd == '/' */
+ if (cwd_len != 1 &&
+ abs_path[cwd_len] != '/' &&
+ abs_path[cwd_len] != '\0')
+ {
+ return false;
+ }
+ if (abs_path[cwd_len] == '/') {
+ cwd_len++;
+ }
+ memmove(abs_path, &abs_path[cwd_len], abs_len + 1 - cwd_len);
+ return true;
+}
+
+static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
+ const char *name,
+ char *gmt, size_t gmt_len);
+
+/*
+ * Check if an incoming filename is already a snapshot converted pathname.
+ *
+ * If so, it returns the pathname truncated at the snapshot point which
+ * will be used as the connectpath.
+ */
+
+static int check_for_converted_path(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ struct shadow_copy2_private *priv,
+ char *abs_path,
+ bool *ppath_already_converted,
+ char **pconnectpath)
+{
+ size_t snapdirlen = 0;
+ char *p = strstr_m(abs_path, priv->config->snapdir);
+ char *q = NULL;
+ char *connect_path = NULL;
+ char snapshot[GMT_NAME_LEN+1];
+
+ *ppath_already_converted = false;
+
+ if (p == NULL) {
+ /* Must at least contain shadow:snapdir. */
+ return 0;
+ }
+
+ if (priv->config->snapdir[0] == '/' &&
+ p != abs_path) {
+ /* Absolute shadow:snapdir must be at the start. */
+ return 0;
+ }
+
+ snapdirlen = strlen(priv->config->snapdir);
+ if (p[snapdirlen] != '/') {
+ /* shadow:snapdir must end as a separate component. */
+ return 0;
+ }
+
+ if (p > abs_path && p[-1] != '/') {
+ /* shadow:snapdir must start as a separate component. */
+ return 0;
+ }
+
+ p += snapdirlen;
+ p++; /* Move past the / */
+
+ /*
+ * Need to return up to the next path
+ * component after the time.
+ * This will be used as the connectpath.
+ */
+ q = strchr(p, '/');
+ if (q == NULL) {
+ /*
+ * No next path component.
+ * Use entire string.
+ */
+ connect_path = talloc_strdup(mem_ctx,
+ abs_path);
+ } else {
+ connect_path = talloc_strndup(mem_ctx,
+ abs_path,
+ q - abs_path);
+ }
+ if (connect_path == NULL) {
+ return ENOMEM;
+ }
+
+ /*
+ * Point p at the same offset in connect_path as
+ * it is in abs_path.
+ */
+
+ p = &connect_path[p - abs_path];
+
+ /*
+ * Now ensure there is a time string at p.
+ * The SMB-format @GMT-token string is returned
+ * in snapshot.
+ */
+
+ if (!shadow_copy2_snapshot_to_gmt(handle,
+ p,
+ snapshot,
+ sizeof(snapshot))) {
+ TALLOC_FREE(connect_path);
+ return 0;
+ }
+
+ if (pconnectpath != NULL) {
+ *pconnectpath = connect_path;
+ }
+
+ *ppath_already_converted = true;
+
+ DBG_DEBUG("path |%s| is already converted. "
+ "connect path = |%s|\n",
+ abs_path,
+ connect_path);
+
+ return 0;
+}
+
+/**
+ * This function does two things.
+ *
+ * 1). Checks if an incoming filename is already a
+ * snapshot converted pathname.
+ * If so, it returns the pathname truncated
+ * at the snapshot point which will be used
+ * as the connectpath, and then does an early return.
+ *
+ * 2). Checks if an incoming filename contains an
+ * SMB-layer @GMT- style timestamp.
+ * If so, it strips the timestamp, and returns
+ * both the timestamp and the stripped path
+ * (making it cwd-relative).
+ */
+
+static bool _shadow_copy2_strip_snapshot_internal(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ time_t *ptimestamp,
+ char **pstripped,
+ char **psnappath,
+ bool *_already_converted,
+ const char *function)
+{
+ char *stripped = NULL;
+ struct shadow_copy2_private *priv;
+ char *abs_path = NULL;
+ bool ret = true;
+ bool already_converted = false;
+ int err = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return false);
+
+ DBG_DEBUG("[from %s()] Path '%s'\n",
+ function, smb_fname_str_dbg(smb_fname));
+
+ if (_already_converted != NULL) {
+ *_already_converted = false;
+ }
+
+ abs_path = make_path_absolute(mem_ctx, priv, smb_fname->base_name);
+ if (abs_path == NULL) {
+ ret = false;
+ goto out;
+ }
+
+ DBG_DEBUG("abs path '%s'\n", abs_path);
+
+ err = check_for_converted_path(mem_ctx,
+ handle,
+ priv,
+ abs_path,
+ &already_converted,
+ psnappath);
+ if (err != 0) {
+ /* error in conversion. */
+ ret = false;
+ goto out;
+ }
+
+ if (already_converted) {
+ if (_already_converted != NULL) {
+ *_already_converted = true;
+ }
+ goto out;
+ }
+
+ if (smb_fname->twrp == 0) {
+ goto out;
+ }
+
+ if (ptimestamp != NULL) {
+ *ptimestamp = nt_time_to_unix(smb_fname->twrp);
+ }
+
+ if (pstripped != NULL) {
+ stripped = talloc_strdup(mem_ctx, abs_path);
+ if (stripped == NULL) {
+ ret = false;
+ goto out;
+ }
+
+ if (smb_fname->base_name[0] != '/') {
+ ret = make_relative_path(priv->shadow_cwd, stripped);
+ if (!ret) {
+ DBG_DEBUG("Path '%s' "
+ "doesn't start with cwd '%s'\n",
+ stripped, priv->shadow_cwd);
+ ret = false;
+ errno = ENOENT;
+ goto out;
+ }
+ }
+ *pstripped = stripped;
+ }
+
+ ret = true;
+
+ out:
+ TALLOC_FREE(abs_path);
+ return ret;
+}
+
+#define shadow_copy2_strip_snapshot_internal(mem_ctx, handle, orig_name, \
+ ptimestamp, pstripped, psnappath, _already_converted) \
+ _shadow_copy2_strip_snapshot_internal((mem_ctx), (handle), (orig_name), \
+ (ptimestamp), (pstripped), (psnappath), (_already_converted), \
+ __FUNCTION__)
+
+static bool _shadow_copy2_strip_snapshot(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *orig_name,
+ time_t *ptimestamp,
+ char **pstripped,
+ const char *function)
+{
+ return _shadow_copy2_strip_snapshot_internal(mem_ctx,
+ handle,
+ orig_name,
+ ptimestamp,
+ pstripped,
+ NULL,
+ NULL,
+ function);
+}
+
+#define shadow_copy2_strip_snapshot(mem_ctx, handle, orig_name, \
+ ptimestamp, pstripped) \
+ _shadow_copy2_strip_snapshot((mem_ctx), (handle), (orig_name), \
+ (ptimestamp), (pstripped), __FUNCTION__)
+
+static bool _shadow_copy2_strip_snapshot_converted(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *orig_name,
+ time_t *ptimestamp,
+ char **pstripped,
+ bool *is_converted,
+ const char *function)
+{
+ return _shadow_copy2_strip_snapshot_internal(mem_ctx,
+ handle,
+ orig_name,
+ ptimestamp,
+ pstripped,
+ NULL,
+ is_converted,
+ function);
+}
+
+#define shadow_copy2_strip_snapshot_converted(mem_ctx, handle, orig_name, \
+ ptimestamp, pstripped, is_converted) \
+ _shadow_copy2_strip_snapshot_converted((mem_ctx), (handle), (orig_name), \
+ (ptimestamp), (pstripped), (is_converted), __FUNCTION__)
+
+static char *shadow_copy2_find_mount_point(TALLOC_CTX *mem_ctx,
+ vfs_handle_struct *handle)
+{
+ char *path = talloc_strdup(mem_ctx, handle->conn->connectpath);
+ dev_t dev;
+ struct stat st;
+ char *p;
+
+ if (stat(path, &st) != 0) {
+ talloc_free(path);
+ return NULL;
+ }
+
+ dev = st.st_dev;
+
+ while ((p = strrchr(path, '/')) && p > path) {
+ *p = 0;
+ if (stat(path, &st) != 0) {
+ talloc_free(path);
+ return NULL;
+ }
+ if (st.st_dev != dev) {
+ *p = '/';
+ break;
+ }
+ }
+
+ return path;
+}
+
+/**
+ * Convert from a name as handed in via the SMB layer
+ * and a timestamp into the local path of the snapshot
+ * of the provided file at the provided time.
+ * Also return the path in the snapshot corresponding
+ * to the file's share root.
+ */
+static char *shadow_copy2_do_convert(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const char *name, time_t timestamp,
+ size_t *snaproot_len)
+{
+ struct smb_filename converted_fname;
+ char *result = NULL;
+ size_t *slashes = NULL;
+ unsigned num_slashes;
+ char *path = NULL;
+ size_t pathlen;
+ char *insert = NULL;
+ char *converted = NULL;
+ size_t insertlen, connectlen = 0;
+ int saved_errno = 0;
+ int i;
+ size_t min_offset;
+ struct shadow_copy2_config *config;
+ struct shadow_copy2_private *priv;
+ size_t in_share_offset = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return NULL);
+
+ config = priv->config;
+
+ DEBUG(10, ("converting '%s'\n", name));
+
+ if (!config->snapdirseverywhere) {
+ int ret;
+ char *snapshot_path;
+
+ snapshot_path = shadow_copy2_snapshot_path(talloc_tos(),
+ handle,
+ timestamp);
+ if (snapshot_path == NULL) {
+ goto fail;
+ }
+
+ if (config->rel_connectpath == NULL) {
+ converted = talloc_asprintf(mem_ctx, "%s/%s",
+ snapshot_path, name);
+ } else {
+ converted = talloc_asprintf(mem_ctx, "%s/%s/%s",
+ snapshot_path,
+ config->rel_connectpath,
+ name);
+ }
+ if (converted == NULL) {
+ goto fail;
+ }
+
+ converted_fname = (struct smb_filename) {
+ .base_name = converted,
+ };
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, &converted_fname);
+ DEBUG(10, ("Trying[not snapdirseverywhere] %s: %d (%s)\n",
+ converted,
+ ret, ret == 0 ? "ok" : strerror(errno)));
+ if (ret == 0) {
+ DEBUG(10, ("Found %s\n", converted));
+ result = converted;
+ converted = NULL;
+ if (snaproot_len != NULL) {
+ *snaproot_len = strlen(snapshot_path);
+ if (config->rel_connectpath != NULL) {
+ *snaproot_len +=
+ strlen(config->rel_connectpath) + 1;
+ }
+ }
+ goto fail;
+ } else {
+ errno = ENOENT;
+ goto fail;
+ }
+ /* never reached ... */
+ }
+
+ connectlen = strlen(handle->conn->connectpath);
+ if (name[0] == 0) {
+ path = talloc_strdup(mem_ctx, handle->conn->connectpath);
+ } else {
+ path = talloc_asprintf(
+ mem_ctx, "%s/%s", handle->conn->connectpath, name);
+ }
+ if (path == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+ pathlen = talloc_get_size(path)-1;
+
+ if (!shadow_copy2_find_slashes(talloc_tos(), path,
+ &slashes, &num_slashes)) {
+ goto fail;
+ }
+
+ insert = shadow_copy2_insert_string(talloc_tos(), handle, timestamp);
+ if (insert == NULL) {
+ goto fail;
+ }
+ insertlen = talloc_get_size(insert)-1;
+
+ /*
+ * Note: We deliberately don't expensively initialize the
+ * array with talloc_zero here: Putting zero into
+ * converted[pathlen+insertlen] below is sufficient, because
+ * in the following for loop, the insert string is inserted
+ * at various slash places. So the memory up to position
+ * pathlen+insertlen will always be initialized when the
+ * converted string is used.
+ */
+ converted = talloc_array(mem_ctx, char, pathlen + insertlen + 1);
+ if (converted == NULL) {
+ goto fail;
+ }
+
+ if (path[pathlen-1] != '/') {
+ /*
+ * Append a fake slash to find the snapshot root
+ */
+ size_t *tmp;
+ tmp = talloc_realloc(talloc_tos(), slashes,
+ size_t, num_slashes+1);
+ if (tmp == NULL) {
+ goto fail;
+ }
+ slashes = tmp;
+ slashes[num_slashes] = pathlen;
+ num_slashes += 1;
+ }
+
+ min_offset = 0;
+
+ if (!config->crossmountpoints) {
+ min_offset = strlen(config->mount_point);
+ }
+
+ memcpy(converted, path, pathlen+1);
+ converted[pathlen+insertlen] = '\0';
+
+ converted_fname = (struct smb_filename) {
+ .base_name = converted,
+ };
+
+ for (i = num_slashes-1; i>=0; i--) {
+ int ret;
+ size_t offset;
+
+ offset = slashes[i];
+
+ if (offset < min_offset) {
+ errno = ENOENT;
+ goto fail;
+ }
+
+ if (offset >= connectlen) {
+ in_share_offset = offset;
+ }
+
+ memcpy(converted+offset, insert, insertlen);
+
+ offset += insertlen;
+ memcpy(converted+offset, path + slashes[i],
+ pathlen - slashes[i]);
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, &converted_fname);
+
+ DEBUG(10, ("Trying[snapdirseverywhere] %s: %d (%s)\n",
+ converted,
+ ret, ret == 0 ? "ok" : strerror(errno)));
+ if (ret == 0) {
+ /* success */
+ if (snaproot_len != NULL) {
+ *snaproot_len = in_share_offset + insertlen;
+ }
+ break;
+ }
+ if (errno == ENOTDIR) {
+ /*
+ * This is a valid condition: We appended the
+ * .snapshots/@GMT.. to a file name. Just try
+ * with the upper levels.
+ */
+ continue;
+ }
+ if (errno != ENOENT) {
+ /* Other problem than "not found" */
+ goto fail;
+ }
+ }
+
+ if (i >= 0) {
+ /*
+ * Found something
+ */
+ DEBUG(10, ("Found %s\n", converted));
+ result = converted;
+ converted = NULL;
+ } else {
+ errno = ENOENT;
+ }
+fail:
+ if (result == NULL) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(converted);
+ TALLOC_FREE(insert);
+ TALLOC_FREE(slashes);
+ TALLOC_FREE(path);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return result;
+}
+
+/**
+ * Convert from a name as handed in via the SMB layer
+ * and a timestamp into the local path of the snapshot
+ * of the provided file at the provided time.
+ */
+static char *shadow_copy2_convert(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const char *name, time_t timestamp)
+{
+ return shadow_copy2_do_convert(mem_ctx, handle, name, timestamp, NULL);
+}
+
+/*
+ modify a sbuf return to ensure that inodes in the shadow directory
+ are different from those in the main directory
+ */
+static void convert_sbuf(vfs_handle_struct *handle, const char *fname,
+ SMB_STRUCT_STAT *sbuf)
+{
+ struct shadow_copy2_private *priv;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return);
+
+ if (priv->config->fixinodes) {
+ /* some snapshot systems, like GPFS, return the same
+ device:inode for the snapshot files as the current
+ files. That breaks the 'restore' button in the shadow copy
+ GUI, as the client gets a sharing violation.
+
+ This is a crude way of allowing both files to be
+ open at once. It has a slight chance of inode
+ number collision, but I can't see a better approach
+ without significant VFS changes
+ */
+ TDB_DATA key = { .dptr = discard_const_p(uint8_t, fname),
+ .dsize = strlen(fname) };
+ uint32_t shash;
+
+ shash = tdb_jenkins_hash(&key) & 0xFF000000;
+ if (shash == 0) {
+ shash = 1;
+ }
+ sbuf->st_ex_ino ^= shash;
+ }
+}
+
+static int shadow_copy2_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ time_t timestamp_src = 0;
+ time_t timestamp_dst = 0;
+ char *snappath_src = NULL;
+ char *snappath_dst = NULL;
+
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle,
+ smb_fname_src,
+ &timestamp_src, NULL, &snappath_src,
+ NULL)) {
+ return -1;
+ }
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(), handle,
+ smb_fname_dst,
+ &timestamp_dst, NULL, &snappath_dst,
+ NULL)) {
+ return -1;
+ }
+ if (timestamp_src != 0) {
+ errno = EXDEV;
+ return -1;
+ }
+ if (timestamp_dst != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ /*
+ * Don't allow rename on already converted paths.
+ */
+ if (snappath_src != NULL) {
+ errno = EXDEV;
+ return -1;
+ }
+ if (snappath_dst != NULL) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+}
+
+static int shadow_copy2_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ time_t timestamp_old = 0;
+ time_t timestamp_new = 0;
+ char *snappath_old = NULL;
+ char *snappath_new = NULL;
+
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(),
+ handle,
+ link_contents,
+ &timestamp_old,
+ NULL,
+ &snappath_old,
+ NULL)) {
+ return -1;
+ }
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_new,
+ NULL,
+ &snappath_new,
+ NULL)) {
+ return -1;
+ }
+ if ((timestamp_old != 0) || (timestamp_new != 0)) {
+ errno = EROFS;
+ return -1;
+ }
+ /*
+ * Don't allow symlinks on already converted paths.
+ */
+ if ((snappath_old != NULL) || (snappath_new != NULL)) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_SYMLINKAT(handle,
+ link_contents,
+ dirfsp,
+ new_smb_fname);
+}
+
+static int shadow_copy2_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ time_t timestamp_old = 0;
+ time_t timestamp_new = 0;
+ char *snappath_old = NULL;
+ char *snappath_new = NULL;
+
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(),
+ handle,
+ old_smb_fname,
+ &timestamp_old,
+ NULL,
+ &snappath_old,
+ NULL)) {
+ return -1;
+ }
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_new,
+ NULL,
+ &snappath_new,
+ NULL)) {
+ return -1;
+ }
+ if ((timestamp_old != 0) || (timestamp_new != 0)) {
+ errno = EROFS;
+ return -1;
+ }
+ /*
+ * Don't allow links on already converted paths.
+ */
+ if ((snappath_old != NULL) || (snappath_new != NULL)) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+}
+
+static int shadow_copy2_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ struct shadow_copy2_private *priv = NULL;
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ bool converted = false;
+ char *abspath = NULL;
+ char *tmp;
+ int ret = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return -1);
+
+ if (!shadow_copy2_strip_snapshot_converted(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped,
+ &converted)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ TALLOC_FREE(stripped);
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (ret != 0) {
+ return ret;
+ }
+ if (!converted) {
+ return 0;
+ }
+
+ abspath = make_path_absolute(talloc_tos(),
+ priv,
+ smb_fname->base_name);
+ if (abspath == NULL) {
+ return -1;
+ }
+
+ convert_sbuf(handle, abspath, &smb_fname->st);
+ TALLOC_FREE(abspath);
+ return 0;
+ }
+
+ tmp = smb_fname->base_name;
+ smb_fname->base_name = shadow_copy2_convert(
+ talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+
+ if (smb_fname->base_name == NULL) {
+ smb_fname->base_name = tmp;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ if (ret != 0) {
+ goto out;
+ }
+
+ abspath = make_path_absolute(talloc_tos(),
+ priv,
+ smb_fname->base_name);
+ if (abspath == NULL) {
+ ret = -1;
+ goto out;
+ }
+
+ convert_sbuf(handle, abspath, &smb_fname->st);
+ TALLOC_FREE(abspath);
+
+out:
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = tmp;
+
+ return ret;
+}
+
+static int shadow_copy2_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ struct shadow_copy2_private *priv = NULL;
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ bool converted = false;
+ char *abspath = NULL;
+ char *tmp;
+ int ret = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return -1);
+
+ if (!shadow_copy2_strip_snapshot_converted(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped,
+ &converted)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ TALLOC_FREE(stripped);
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (ret != 0) {
+ return ret;
+ }
+ if (!converted) {
+ return 0;
+ }
+
+ abspath = make_path_absolute(talloc_tos(),
+ priv,
+ smb_fname->base_name);
+ if (abspath == NULL) {
+ return -1;
+ }
+
+ convert_sbuf(handle, abspath, &smb_fname->st);
+ TALLOC_FREE(abspath);
+ return 0;
+ }
+
+ tmp = smb_fname->base_name;
+ smb_fname->base_name = shadow_copy2_convert(
+ talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+
+ if (smb_fname->base_name == NULL) {
+ smb_fname->base_name = tmp;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ if (ret != 0) {
+ goto out;
+ }
+
+ abspath = make_path_absolute(talloc_tos(),
+ priv,
+ smb_fname->base_name);
+ if (abspath == NULL) {
+ ret = -1;
+ goto out;
+ }
+
+ convert_sbuf(handle, abspath, &smb_fname->st);
+ TALLOC_FREE(abspath);
+
+out:
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = tmp;
+
+ return ret;
+}
+
+static int shadow_copy2_fstat(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ struct shadow_copy2_private *priv = NULL;
+ time_t timestamp = 0;
+ struct smb_filename *orig_smb_fname = NULL;
+ struct smb_filename vss_smb_fname;
+ struct smb_filename *orig_base_smb_fname = NULL;
+ struct smb_filename vss_base_smb_fname;
+ char *stripped = NULL;
+ char *abspath = NULL;
+ bool converted = false;
+ bool ok;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return -1);
+
+ ok = shadow_copy2_strip_snapshot_converted(talloc_tos(),
+ handle,
+ fsp->fsp_name,
+ &timestamp,
+ &stripped,
+ &converted);
+ if (!ok) {
+ return -1;
+ }
+
+ if (timestamp == 0) {
+ TALLOC_FREE(stripped);
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (ret != 0) {
+ return ret;
+ }
+ if (!converted) {
+ return 0;
+ }
+
+ abspath = make_path_absolute(talloc_tos(),
+ priv,
+ fsp->fsp_name->base_name);
+ if (abspath == NULL) {
+ return -1;
+ }
+
+ convert_sbuf(handle, abspath, sbuf);
+ TALLOC_FREE(abspath);
+ return 0;
+ }
+
+ vss_smb_fname = *fsp->fsp_name;
+ vss_smb_fname.base_name = shadow_copy2_convert(talloc_tos(),
+ handle,
+ stripped,
+ timestamp);
+ TALLOC_FREE(stripped);
+ if (vss_smb_fname.base_name == NULL) {
+ return -1;
+ }
+
+ orig_smb_fname = fsp->fsp_name;
+ fsp->fsp_name = &vss_smb_fname;
+
+ if (fsp_is_alternate_stream(fsp)) {
+ vss_base_smb_fname = *fsp->base_fsp->fsp_name;
+ vss_base_smb_fname.base_name = vss_smb_fname.base_name;
+ orig_base_smb_fname = fsp->base_fsp->fsp_name;
+ fsp->base_fsp->fsp_name = &vss_base_smb_fname;
+ }
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (ret != 0) {
+ goto out;
+ }
+
+ abspath = make_path_absolute(talloc_tos(),
+ priv,
+ fsp->fsp_name->base_name);
+ if (abspath == NULL) {
+ ret = -1;
+ goto out;
+ }
+
+ convert_sbuf(handle, abspath, sbuf);
+ TALLOC_FREE(abspath);
+
+out:
+ fsp->fsp_name = orig_smb_fname;
+ if (fsp_is_alternate_stream(fsp)) {
+ fsp->base_fsp->fsp_name = orig_base_smb_fname;
+ }
+
+ return ret;
+}
+
+static int shadow_copy2_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ struct shadow_copy2_private *priv = NULL;
+ struct smb_filename *smb_fname = NULL;
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ char *abspath = NULL;
+ bool converted = false;
+ int ret;
+ bool ok;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return -1);
+
+ smb_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname_in);
+ if (smb_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ok = shadow_copy2_strip_snapshot_converted(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped,
+ &converted);
+ if (!ok) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ TALLOC_FREE(stripped);
+ ret = SMB_VFS_NEXT_FSTATAT(
+ handle, dirfsp, smb_fname_in, sbuf, flags);
+ if (ret != 0) {
+ return ret;
+ }
+ if (!converted) {
+ return 0;
+ }
+
+ abspath = make_path_absolute(
+ talloc_tos(), priv, smb_fname->base_name);
+ if (abspath == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ convert_sbuf(handle, abspath, sbuf);
+ TALLOC_FREE(abspath);
+ return 0;
+ }
+
+ smb_fname->base_name = shadow_copy2_convert(
+ smb_fname, handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (smb_fname->base_name == NULL) {
+ TALLOC_FREE(smb_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_FSTATAT(handle,
+ dirfsp,
+ smb_fname,
+ sbuf,
+ flags);
+ if (ret != 0) {
+ int saved_errno = errno;
+ TALLOC_FREE(smb_fname);
+ errno = saved_errno;
+ return -1;
+ }
+
+ abspath = make_path_absolute(
+ talloc_tos(), priv, smb_fname->base_name);
+ if (abspath == NULL) {
+ TALLOC_FREE(smb_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ convert_sbuf(handle, abspath, sbuf);
+ TALLOC_FREE(abspath);
+
+ TALLOC_FREE(smb_fname);
+
+ return 0;
+}
+
+static struct smb_filename *shadow_copy2_openat_name(
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *dirfsp,
+ const struct files_struct *fsp,
+ const struct smb_filename *smb_fname_in)
+{
+ struct smb_filename *result = NULL;
+
+ if (fsp->base_fsp != NULL) {
+ struct smb_filename *base_fname = fsp->base_fsp->fsp_name;
+
+ if (smb_fname_in->base_name[0] == '/') {
+ /*
+ * Special-case stream names from streams_depot
+ */
+ result = cp_smb_filename(mem_ctx, smb_fname_in);
+ } else {
+
+ SMB_ASSERT(is_named_stream(smb_fname_in));
+
+ result = synthetic_smb_fname(mem_ctx,
+ base_fname->base_name,
+ smb_fname_in->stream_name,
+ &smb_fname_in->st,
+ smb_fname_in->twrp,
+ smb_fname_in->flags);
+ }
+ } else {
+ result = full_path_from_dirfsp_atname(
+ mem_ctx, dirfsp, smb_fname_in);
+ }
+
+ return result;
+}
+
+static int shadow_copy2_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ struct files_struct *fsp,
+ const struct vfs_open_how *_how)
+{
+ struct vfs_open_how how = *_how;
+ struct smb_filename *smb_fname = NULL;
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ int saved_errno = 0;
+ int ret;
+ bool ok;
+
+ if (how.resolve != 0) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ smb_fname = shadow_copy2_openat_name(
+ talloc_tos(), dirfsp, fsp, smb_fname_in);
+ if (smb_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ok = shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped);
+ if (!ok) {
+ TALLOC_FREE(smb_fname);
+ return -1;
+ }
+ if (timestamp == 0) {
+ TALLOC_FREE(stripped);
+ TALLOC_FREE(smb_fname);
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname_in,
+ fsp,
+ &how);
+ }
+
+ smb_fname->base_name = shadow_copy2_convert(smb_fname,
+ handle,
+ stripped,
+ timestamp);
+ if (smb_fname->base_name == NULL) {
+ int err = errno;
+ TALLOC_FREE(stripped);
+ TALLOC_FREE(smb_fname);
+ errno = err;
+ return -1;
+ }
+ TALLOC_FREE(stripped);
+
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ &how);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+
+ TALLOC_FREE(smb_fname);
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int shadow_copy2_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ time_t timestamp = 0;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+}
+
+static int shadow_copy2_fchmod(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ mode_t mode)
+{
+ time_t timestamp = 0;
+ const struct smb_filename *smb_fname = NULL;
+
+ smb_fname = fsp->fsp_name;
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+}
+
+static void store_cwd_data(vfs_handle_struct *handle,
+ const char *connectpath)
+{
+ struct shadow_copy2_private *priv = NULL;
+ struct smb_filename *cwd_fname = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return);
+
+ TALLOC_FREE(priv->shadow_cwd);
+ cwd_fname = SMB_VFS_NEXT_GETWD(handle, talloc_tos());
+ if (cwd_fname == NULL) {
+ smb_panic("getwd failed\n");
+ }
+ DBG_DEBUG("shadow cwd = %s\n", cwd_fname->base_name);
+ priv->shadow_cwd = talloc_strdup(priv, cwd_fname->base_name);
+ TALLOC_FREE(cwd_fname);
+ if (priv->shadow_cwd == NULL) {
+ smb_panic("talloc failed\n");
+ }
+ TALLOC_FREE(priv->shadow_connectpath);
+ if (connectpath) {
+ DBG_DEBUG("shadow connectpath = %s\n", connectpath);
+ priv->shadow_connectpath = talloc_strdup(priv, connectpath);
+ if (priv->shadow_connectpath == NULL) {
+ smb_panic("talloc failed\n");
+ }
+ }
+}
+
+static int shadow_copy2_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ char *snappath = NULL;
+ int ret = -1;
+ int saved_errno = 0;
+ char *conv = NULL;
+ size_t rootpath_len = 0;
+ struct smb_filename *conv_smb_fname = NULL;
+
+ if (!shadow_copy2_strip_snapshot_internal(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped,
+ &snappath,
+ NULL)) {
+ return -1;
+ }
+ if (stripped != NULL) {
+ conv = shadow_copy2_do_convert(talloc_tos(),
+ handle,
+ stripped,
+ timestamp,
+ &rootpath_len);
+ TALLOC_FREE(stripped);
+ if (conv == NULL) {
+ return -1;
+ }
+ conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+ conv,
+ NULL,
+ NULL,
+ 0,
+ smb_fname->flags);
+ } else {
+ conv_smb_fname = cp_smb_filename(talloc_tos(), smb_fname);
+ }
+
+ if (conv_smb_fname == NULL) {
+ TALLOC_FREE(conv);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_CHDIR(handle, conv_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+
+ if (ret == 0) {
+ if (conv != NULL && rootpath_len != 0) {
+ conv[rootpath_len] = '\0';
+ } else if (snappath != 0) {
+ TALLOC_FREE(conv);
+ conv = snappath;
+ }
+ store_cwd_data(handle, conv);
+ }
+
+ TALLOC_FREE(stripped);
+ TALLOC_FREE(conv);
+ TALLOC_FREE(conv_smb_fname);
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int shadow_copy2_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ time_t timestamp = 0;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ fsp->fsp_name,
+ &timestamp,
+ NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+}
+
+static int shadow_copy2_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ int saved_errno = 0;
+ int ret;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *conv = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ full_fname,
+ &timestamp,
+ &stripped)) {
+ TALLOC_FREE(full_fname);
+ return -1;
+ }
+
+ if (timestamp == 0) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(stripped);
+ return SMB_VFS_NEXT_READLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+ }
+ conv = cp_smb_filename(talloc_tos(), full_fname);
+ if (conv == NULL) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(stripped);
+ errno = ENOMEM;
+ return -1;
+ }
+ TALLOC_FREE(full_fname);
+ conv->base_name = shadow_copy2_convert(
+ conv, handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (conv->base_name == NULL) {
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_READLINKAT(handle,
+ handle->conn->cwd_fsp,
+ conv,
+ buf,
+ bufsiz);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int shadow_copy2_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ time_t timestamp = 0;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_MKNODAT(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+}
+
+static struct smb_filename *shadow_copy2_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ struct smb_filename *result_fname = NULL;
+ struct smb_filename *conv_fname = NULL;
+ int saved_errno = 0;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, &stripped)) {
+ goto done;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname);
+ }
+
+ conv_fname = cp_smb_filename(talloc_tos(), smb_fname);
+ if (conv_fname == NULL) {
+ goto done;
+ }
+ conv_fname->base_name = shadow_copy2_convert(
+ conv_fname, handle, stripped, timestamp);
+ if (conv_fname->base_name == NULL) {
+ goto done;
+ }
+
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, conv_fname);
+
+done:
+ if (result_fname == NULL) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv_fname);
+ TALLOC_FREE(stripped);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return result_fname;
+}
+
+/**
+ * Check whether a given directory contains a
+ * snapshot directory as direct subdirectory.
+ * If yes, return the path of the snapshot-subdir,
+ * otherwise return NULL.
+ */
+static char *have_snapdir(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *path)
+{
+ struct smb_filename smb_fname;
+ int ret;
+ struct shadow_copy2_private *priv;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return NULL);
+
+ smb_fname = (struct smb_filename) {
+ .base_name = talloc_asprintf(
+ mem_ctx, "%s/%s", path, priv->config->snapdir),
+ };
+ if (smb_fname.base_name == NULL) {
+ return NULL;
+ }
+
+ ret = SMB_VFS_NEXT_STAT(handle, &smb_fname);
+ if ((ret == 0) && (S_ISDIR(smb_fname.st.st_ex_mode))) {
+ return smb_fname.base_name;
+ }
+ TALLOC_FREE(smb_fname.base_name);
+ return NULL;
+}
+
+/**
+ * Find the snapshot directory (if any) for the given
+ * filename (which is relative to the share).
+ */
+static const char *shadow_copy2_find_snapdir(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ char *path, *p;
+ const char *snapdir;
+ struct shadow_copy2_config *config;
+ struct shadow_copy2_private *priv;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return NULL);
+
+ config = priv->config;
+
+ /*
+ * If the non-snapdisrseverywhere mode, we should not search!
+ */
+ if (!config->snapdirseverywhere) {
+ return config->snapshot_basepath;
+ }
+
+ path = talloc_asprintf(mem_ctx, "%s/%s",
+ handle->conn->connectpath,
+ smb_fname->base_name);
+ if (path == NULL) {
+ return NULL;
+ }
+
+ snapdir = have_snapdir(handle, talloc_tos(), path);
+ if (snapdir != NULL) {
+ TALLOC_FREE(path);
+ return snapdir;
+ }
+
+ while ((p = strrchr(path, '/')) && (p > path)) {
+
+ p[0] = '\0';
+
+ snapdir = have_snapdir(handle, talloc_tos(), path);
+ if (snapdir != NULL) {
+ TALLOC_FREE(path);
+ return snapdir;
+ }
+ }
+ TALLOC_FREE(path);
+ return NULL;
+}
+
+static bool shadow_copy2_snapshot_to_gmt(vfs_handle_struct *handle,
+ const char *name,
+ char *gmt, size_t gmt_len)
+{
+ struct tm timestamp = { .tm_sec = 0, };
+ time_t timestamp_t;
+ unsigned long int timestamp_long;
+ const char *fmt;
+ struct shadow_copy2_config *config;
+ struct shadow_copy2_private *priv;
+ char *tmpstr = NULL;
+ char *tmp = NULL;
+ bool converted = false;
+ int ret = -1;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return NULL);
+
+ config = priv->config;
+
+ fmt = config->gmt_format;
+
+ /*
+ * If regex is provided, then we will have to parse the
+ * filename which will contain both the prefix and the time format.
+ * e.g. <prefix><delimiter><time_format>
+ */
+ if (priv->snaps->regex != NULL) {
+ tmpstr = talloc_strdup(talloc_tos(), name);
+ /* point "name" to the time format */
+ name = strstr(name, priv->config->delimiter);
+ if (name == NULL) {
+ goto done;
+ }
+ /* Extract the prefix */
+ tmp = strstr(tmpstr, priv->config->delimiter);
+ if (tmp == NULL) {
+ goto done;
+ }
+ *tmp = '\0';
+
+ /* Parse regex */
+ ret = regexec(priv->snaps->regex, tmpstr, 0, NULL, 0);
+ if (ret) {
+ DBG_DEBUG("shadow_copy2_snapshot_to_gmt: "
+ "no regex match for %s\n", tmpstr);
+ goto done;
+ }
+ }
+
+ if (config->use_sscanf) {
+ if (sscanf(name, fmt, &timestamp_long) != 1) {
+ DEBUG(10, ("shadow_copy2_snapshot_to_gmt: "
+ "no sscanf match %s: %s\n",
+ fmt, name));
+ goto done;
+ }
+ timestamp_t = timestamp_long;
+ gmtime_r(&timestamp_t, &timestamp);
+ } else {
+ if (strptime(name, fmt, &timestamp) == NULL) {
+ DEBUG(10, ("shadow_copy2_snapshot_to_gmt: "
+ "no match %s: %s\n",
+ fmt, name));
+ goto done;
+ }
+ DEBUG(10, ("shadow_copy2_snapshot_to_gmt: match %s: %s\n",
+ fmt, name));
+
+ if (config->use_localtime) {
+ timestamp.tm_isdst = -1;
+ timestamp_t = mktime(&timestamp);
+ gmtime_r(&timestamp_t, &timestamp);
+ }
+ }
+
+ strftime(gmt, gmt_len, GMT_FORMAT, &timestamp);
+ converted = true;
+
+done:
+ TALLOC_FREE(tmpstr);
+ return converted;
+}
+
+static int shadow_copy2_label_cmp_asc(const void *x, const void *y)
+{
+ return strncmp((const char *)x, (const char *)y, sizeof(SHADOW_COPY_LABEL));
+}
+
+static int shadow_copy2_label_cmp_desc(const void *x, const void *y)
+{
+ return -strncmp((const char *)x, (const char *)y, sizeof(SHADOW_COPY_LABEL));
+}
+
+/*
+ sort the shadow copy data in ascending or descending order
+ */
+static void shadow_copy2_sort_data(vfs_handle_struct *handle,
+ struct shadow_copy_data *shadow_copy2_data)
+{
+ int (*cmpfunc)(const void *, const void *);
+ const char *sort;
+ struct shadow_copy2_private *priv;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return);
+
+ sort = priv->config->sort_order;
+ if (sort == NULL) {
+ return;
+ }
+
+ if (strcmp(sort, "asc") == 0) {
+ cmpfunc = shadow_copy2_label_cmp_asc;
+ } else if (strcmp(sort, "desc") == 0) {
+ cmpfunc = shadow_copy2_label_cmp_desc;
+ } else {
+ return;
+ }
+
+ if (shadow_copy2_data && shadow_copy2_data->num_volumes > 0 &&
+ shadow_copy2_data->labels)
+ {
+ TYPESAFE_QSORT(shadow_copy2_data->labels,
+ shadow_copy2_data->num_volumes,
+ cmpfunc);
+ }
+}
+
+static int shadow_copy2_get_shadow_copy_data(
+ vfs_handle_struct *handle, files_struct *fsp,
+ struct shadow_copy_data *shadow_copy2_data,
+ bool labels)
+{
+ DIR *p = NULL;
+ const char *snapdir;
+ struct smb_filename *snapdir_smb_fname = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct files_struct *fspcwd = NULL;
+ struct dirent *d;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct shadow_copy2_private *priv = NULL;
+ struct shadow_copy2_snapentry *tmpentry = NULL;
+ bool get_snaplist = false;
+ struct vfs_open_how how = {
+ .flags = O_RDONLY, .mode = 0,
+ };
+ int fd;
+ int ret = -1;
+ NTSTATUS status;
+ int saved_errno = 0;
+
+ snapdir = shadow_copy2_find_snapdir(tmp_ctx, handle, fsp->fsp_name);
+ if (snapdir == NULL) {
+ DEBUG(0,("shadow:snapdir not found for %s in get_shadow_copy_data\n",
+ handle->conn->connectpath));
+ errno = EINVAL;
+ goto done;
+ }
+
+ snapdir_smb_fname = synthetic_smb_fname(talloc_tos(),
+ snapdir,
+ NULL,
+ NULL,
+ 0,
+ fsp->fsp_name->flags);
+ if (snapdir_smb_fname == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+ status = create_internal_dirfsp(handle->conn,
+ snapdir_smb_fname,
+ &dirfsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("create_internal_dir_fsp() failed for '%s'"
+ " - %s\n", snapdir, nt_errstr(status));
+ errno = ENOSYS;
+ goto done;
+ }
+
+ status = vfs_at_fspcwd(talloc_tos(), handle->conn, &fspcwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+#ifdef O_DIRECTORY
+ how.flags |= O_DIRECTORY;
+#endif
+
+ fd = SMB_VFS_NEXT_OPENAT(handle,
+ fspcwd,
+ snapdir_smb_fname,
+ dirfsp,
+ &how);
+ if (fd == -1) {
+ DBG_WARNING("SMB_VFS_NEXT_OPEN failed for '%s'"
+ " - %s\n", snapdir, strerror(errno));
+ errno = ENOSYS;
+ goto done;
+ }
+ fsp_set_fd(dirfsp, fd);
+
+ /* Now we have the handle, check access here. */
+ status = smbd_check_access_rights_fsp(fspcwd,
+ dirfsp,
+ false,
+ SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("user does not have list permission "
+ "on snapdir %s\n",
+ fsp_str_dbg(dirfsp));
+ errno = EACCES;
+ goto done;
+ }
+
+ p = SMB_VFS_NEXT_FDOPENDIR(handle, dirfsp, NULL, 0);
+ if (!p) {
+ DBG_NOTICE("shadow_copy2: SMB_VFS_NEXT_FDOPENDIR() failed for '%s'"
+ " - %s\n", snapdir, strerror(errno));
+ errno = ENOSYS;
+ goto done;
+ }
+
+ if (shadow_copy2_data != NULL) {
+ shadow_copy2_data->num_volumes = 0;
+ shadow_copy2_data->labels = NULL;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ goto done);
+
+ /*
+ * Normally this function is called twice once with labels = false and
+ * then with labels = true. When labels is false it will return the
+ * number of volumes so that the caller can allocate memory for that
+ * many labels. Therefore to eliminate snaplist both the times it is
+ * good to check if labels is set or not.
+ *
+ * shadow_copy2_data is NULL when we only want to update the list and
+ * don't want any labels.
+ */
+ if ((priv->snaps->regex != NULL) && (labels || shadow_copy2_data == NULL)) {
+ get_snaplist = true;
+ /* Reset the global snaplist */
+ shadow_copy2_delete_snaplist(priv);
+
+ /* Set the current time as snaplist update time */
+ time(&(priv->snaps->fetch_time));
+ }
+
+ while ((d = SMB_VFS_NEXT_READDIR(handle, dirfsp, p))) {
+ char snapshot[GMT_NAME_LEN+1];
+ SHADOW_COPY_LABEL *tlabels;
+
+ /*
+ * ignore names not of the right form in the snapshot
+ * directory
+ */
+ if (!shadow_copy2_snapshot_to_gmt(
+ handle, d->d_name,
+ snapshot, sizeof(snapshot))) {
+
+ DEBUG(6, ("shadow_copy2_get_shadow_copy_data: "
+ "ignoring %s\n", d->d_name));
+ continue;
+ }
+ DEBUG(6,("shadow_copy2_get_shadow_copy_data: %s -> %s\n",
+ d->d_name, snapshot));
+
+ if (get_snaplist) {
+ /*
+ * Create a snap entry for each successful
+ * pattern match.
+ */
+ tmpentry = shadow_copy2_create_snapentry(priv);
+ if (tmpentry == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ goto done;
+ }
+ tmpentry->snapname = talloc_strdup(tmpentry, d->d_name);
+ tmpentry->time_fmt = talloc_strdup(tmpentry, snapshot);
+ }
+
+ if (shadow_copy2_data == NULL) {
+ continue;
+ }
+
+ if (!labels) {
+ /* the caller doesn't want the labels */
+ shadow_copy2_data->num_volumes++;
+ continue;
+ }
+
+ tlabels = talloc_realloc(shadow_copy2_data,
+ shadow_copy2_data->labels,
+ SHADOW_COPY_LABEL,
+ shadow_copy2_data->num_volumes+1);
+ if (tlabels == NULL) {
+ DEBUG(0,("shadow_copy2: out of memory\n"));
+ goto done;
+ }
+
+ strlcpy(tlabels[shadow_copy2_data->num_volumes], snapshot,
+ sizeof(*tlabels));
+
+ shadow_copy2_data->num_volumes++;
+ shadow_copy2_data->labels = tlabels;
+ }
+
+ shadow_copy2_sort_data(handle, shadow_copy2_data);
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(fspcwd );
+ if (p != NULL) {
+ SMB_VFS_NEXT_CLOSEDIR(handle, p);
+ p = NULL;
+ if (dirfsp != NULL) {
+ /*
+ * VFS_CLOSEDIR implicitly
+ * closed the associated fd.
+ */
+ fsp_set_fd(dirfsp, -1);
+ }
+ }
+ if (dirfsp != NULL) {
+ fd_close(dirfsp);
+ file_free(NULL, dirfsp);
+ }
+ TALLOC_FREE(tmp_ctx);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int shadow_copy2_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ struct smb_filename *full_fname = NULL;
+ time_t timestamp = 0;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ full_fname,
+ &timestamp,
+ NULL)) {
+ TALLOC_FREE(full_fname);
+ return -1;
+ }
+ TALLOC_FREE(full_fname);
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+}
+
+static int shadow_copy2_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ time_t timestamp = 0;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ fsp->fsp_name,
+ &timestamp,
+ NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
+}
+
+static int shadow_copy2_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *aname, const void *value,
+ size_t size, int flags)
+{
+ time_t timestamp = 0;
+ const struct smb_filename *smb_fname = NULL;
+
+ smb_fname = fsp->fsp_name;
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
+ aname, value, size, flags);
+}
+
+static NTSTATUS shadow_copy2_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ time_t timestamp = 0;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ NULL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (timestamp != 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ return SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle,
+ dirfsp,
+ smb_fname,
+ reflist,
+ referral_count);
+}
+
+static NTSTATUS shadow_copy2_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *conv = NULL;
+ NTSTATUS status;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!shadow_copy2_strip_snapshot(mem_ctx,
+ handle,
+ full_fname,
+ &timestamp,
+ &stripped)) {
+ TALLOC_FREE(full_fname);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (timestamp == 0) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(stripped);
+ return SMB_VFS_NEXT_READ_DFS_PATHAT(handle,
+ mem_ctx,
+ dirfsp,
+ smb_fname,
+ ppreflist,
+ preferral_count);
+ }
+
+ conv = cp_smb_filename(mem_ctx, full_fname);
+ if (conv == NULL) {
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(stripped);
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(full_fname);
+ conv->base_name = shadow_copy2_convert(conv,
+ handle,
+ stripped,
+ timestamp);
+ TALLOC_FREE(stripped);
+ if (conv->base_name == NULL) {
+ TALLOC_FREE(conv);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = SMB_VFS_NEXT_READ_DFS_PATHAT(handle,
+ mem_ctx,
+ handle->conn->cwd_fsp,
+ conv,
+ ppreflist,
+ preferral_count);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return any stat(2) info. */
+ smb_fname->st = conv->st;
+ }
+
+ TALLOC_FREE(conv);
+ return status;
+}
+
+static const char *shadow_copy2_connectpath(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ char *tmp = NULL;
+ const char *fname = smb_fname_in->base_name;
+ const struct smb_filename *full = NULL;
+ struct smb_filename smb_fname = {0};
+ struct smb_filename *result_fname = NULL;
+ char *result = NULL;
+ char *parent_dir = NULL;
+ int saved_errno = 0;
+ size_t rootpath_len = 0;
+ struct shadow_copy2_private *priv = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, priv, struct shadow_copy2_private,
+ return NULL);
+
+ DBG_DEBUG("Calc connect path for [%s]\n", fname);
+
+ if (priv->shadow_connectpath != NULL) {
+ DBG_DEBUG("cached connect path is [%s]\n",
+ priv->shadow_connectpath);
+ return priv->shadow_connectpath;
+ }
+
+ full = full_path_from_dirfsp_atname(
+ talloc_tos(), dirfsp, smb_fname_in);
+ if (full == NULL) {
+ return NULL;
+ }
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(), handle, full,
+ &timestamp, &stripped)) {
+ goto done;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_CONNECTPATH(handle, dirfsp, smb_fname_in);
+ }
+
+ tmp = shadow_copy2_do_convert(talloc_tos(), handle, stripped, timestamp,
+ &rootpath_len);
+ if (tmp == NULL) {
+ if (errno != ENOENT) {
+ goto done;
+ }
+
+ /*
+ * If the converted path does not exist, and converting
+ * the parent yields something that does exist, then
+ * this path refers to something that has not been
+ * created yet, relative to the parent path.
+ * The snapshot finding is relative to the parent.
+ * (usually snapshots are read/only but this is not
+ * necessarily true).
+ * This code also covers getting a wildcard in the
+ * last component, because this function is called
+ * prior to sanitizing the path, and in SMB1 we may
+ * get wildcards in path names.
+ */
+ if (!parent_dirname(talloc_tos(), stripped, &parent_dir,
+ NULL)) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+ tmp = shadow_copy2_do_convert(talloc_tos(), handle, parent_dir,
+ timestamp, &rootpath_len);
+ if (tmp == NULL) {
+ goto done;
+ }
+ }
+
+ DBG_DEBUG("converted path is [%s] root path is [%.*s]\n", tmp,
+ (int)rootpath_len, tmp);
+
+ tmp[rootpath_len] = '\0';
+ smb_fname = (struct smb_filename) { .base_name = tmp };
+
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, priv, &smb_fname);
+ if (result_fname == NULL) {
+ goto done;
+ }
+
+ /*
+ * SMB_VFS_NEXT_REALPATH returns a talloc'ed string.
+ * Don't leak memory.
+ */
+ TALLOC_FREE(priv->shadow_realpath);
+ priv->shadow_realpath = result_fname;
+ result = priv->shadow_realpath->base_name;
+
+ DBG_DEBUG("connect path is [%s]\n", result);
+
+done:
+ if (result == NULL) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(tmp);
+ TALLOC_FREE(stripped);
+ TALLOC_FREE(parent_dir);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return result;
+}
+
+static NTSTATUS shadow_copy2_parent_pathname(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ char *converted_name = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *parent = NULL;
+ struct smb_filename *atname = NULL;
+ struct shadow_copy2_private *priv = NULL;
+ bool ok = false;
+ bool is_converted = false;
+ NTSTATUS status = NT_STATUS_OK;
+ TALLOC_CTX *frame = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ priv,
+ struct shadow_copy2_private,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ frame = talloc_stackframe();
+
+ smb_fname = cp_smb_filename(frame, smb_fname_in);
+ if (smb_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ /* First, call the default PARENT_PATHNAME. */
+ status = SMB_VFS_NEXT_PARENT_PATHNAME(handle,
+ frame,
+ smb_fname,
+ &parent,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (parent->twrp == 0) {
+ /*
+ * Parent is not a snapshot path, return
+ * the regular result.
+ */
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ /* See if we can find a snapshot for the parent. */
+ ok = shadow_copy2_strip_snapshot_converted(frame,
+ handle,
+ parent,
+ &timestamp,
+ &stripped,
+ &is_converted);
+ if (!ok) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ if (is_converted) {
+ /*
+ * Already found snapshot for parent so wipe
+ * out the twrp.
+ */
+ parent->twrp = 0;
+ goto out;
+ }
+
+ converted_name = shadow_copy2_convert(frame,
+ handle,
+ stripped,
+ timestamp);
+
+ if (converted_name == NULL) {
+ /*
+ * Can't find snapshot for parent so wipe
+ * out the twrp.
+ */
+ parent->twrp = 0;
+ }
+
+ out:
+
+ *parent_dir_out = talloc_move(ctx, &parent);
+ if (atname_out != NULL) {
+ *atname_out = talloc_move(*parent_dir_out, &atname);
+ }
+
+ fail:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static uint64_t shadow_copy2_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ int saved_errno = 0;
+ char *conv = NULL;
+ struct smb_filename *conv_smb_fname = NULL;
+ uint64_t ret = (uint64_t)-1;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped)) {
+ return (uint64_t)-1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname,
+ bsize, dfree, dsize);
+ }
+ conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (conv == NULL) {
+ return (uint64_t)-1;
+ }
+ conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+ conv,
+ NULL,
+ NULL,
+ 0,
+ smb_fname->flags);
+ if (conv_smb_fname == NULL) {
+ TALLOC_FREE(conv);
+ return (uint64_t)-1;
+ }
+ ret = SMB_VFS_NEXT_DISK_FREE(handle, conv_smb_fname,
+ bsize, dfree, dsize);
+ if (ret == (uint64_t)-1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv);
+ TALLOC_FREE(conv_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int shadow_copy2_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ int ret;
+ int saved_errno = 0;
+ char *conv;
+ struct smb_filename *conv_smb_fname = NULL;
+
+ if (!shadow_copy2_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, dq);
+ }
+
+ conv = shadow_copy2_convert(talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (conv == NULL) {
+ return -1;
+ }
+ conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+ conv,
+ NULL,
+ NULL,
+ 0,
+ smb_fname->flags);
+ if (conv_smb_fname == NULL) {
+ TALLOC_FREE(conv);
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_GET_QUOTA(handle, conv_smb_fname, qtype, id, dq);
+
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv);
+ TALLOC_FREE(conv_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+
+ return ret;
+}
+
+static ssize_t shadow_copy2_pwrite(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const void *data,
+ size_t n,
+ off_t offset)
+{
+ ssize_t nwritten;
+
+ nwritten = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+ if (nwritten == -1) {
+ if (errno == EBADF && fsp->fsp_flags.can_write) {
+ errno = EROFS;
+ }
+ }
+
+ return nwritten;
+}
+
+struct shadow_copy2_pwrite_state {
+ vfs_handle_struct *handle;
+ files_struct *fsp;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void shadow_copy2_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *shadow_copy2_pwrite_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp,
+ const void *data, size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct shadow_copy2_pwrite_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct shadow_copy2_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->handle = handle;
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state,
+ ev,
+ handle,
+ fsp,
+ data,
+ n,
+ offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, shadow_copy2_pwrite_done, req);
+
+ return req;
+}
+
+static void shadow_copy2_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct shadow_copy2_pwrite_state *state = tevent_req_data(
+ req, struct shadow_copy2_pwrite_state);
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->ret == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t shadow_copy2_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct shadow_copy2_pwrite_state *state = tevent_req_data(
+ req, struct shadow_copy2_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ if ((vfs_aio_state->error == EBADF) &&
+ state->fsp->fsp_flags.can_write)
+ {
+ vfs_aio_state->error = EROFS;
+ errno = EROFS;
+ }
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static int shadow_copy2_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct shadow_copy2_config *config;
+ struct shadow_copy2_private *priv;
+ int ret;
+ const char *snapdir;
+ const char *snapprefix = NULL;
+ const char *delimiter;
+ const char *gmt_format;
+ const char *sort_order;
+ const char *basedir = NULL;
+ const char *snapsharepath = NULL;
+ const char *mount_point;
+
+ DBG_DEBUG("cnum[%" PRIu32 "], connectpath[%s]\n",
+ handle->conn->cnum,
+ handle->conn->connectpath);
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ priv = talloc_zero(handle->conn, struct shadow_copy2_private);
+ if (priv == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+
+ priv->snaps = talloc_zero(priv, struct shadow_copy2_snaplist_info);
+ if (priv->snaps == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+
+ config = talloc_zero(priv, struct shadow_copy2_config);
+ if (config == NULL) {
+ DEBUG(0, ("talloc_zero() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ priv->config = config;
+
+ gmt_format = lp_parm_const_string(SNUM(handle->conn),
+ "shadow", "format",
+ GMT_FORMAT);
+ config->gmt_format = talloc_strdup(config, gmt_format);
+ if (config->gmt_format == NULL) {
+ DEBUG(0, ("talloc_strdup() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* config->gmt_format must not contain a path separator. */
+ if (strchr(config->gmt_format, '/') != NULL) {
+ DEBUG(0, ("shadow:format %s must not contain a /"
+ "character. Unable to initialize module.\n",
+ config->gmt_format));
+ errno = EINVAL;
+ return -1;
+ }
+
+ config->use_sscanf = lp_parm_bool(SNUM(handle->conn),
+ "shadow", "sscanf", false);
+
+ config->use_localtime = lp_parm_bool(SNUM(handle->conn),
+ "shadow", "localtime",
+ false);
+
+ snapdir = lp_parm_const_string(SNUM(handle->conn),
+ "shadow", "snapdir",
+ ".snapshots");
+ config->snapdir = talloc_strdup(config, snapdir);
+ if (config->snapdir == NULL) {
+ DEBUG(0, ("talloc_strdup() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ snapprefix = lp_parm_const_string(SNUM(handle->conn),
+ "shadow", "snapprefix",
+ NULL);
+ if (snapprefix != NULL) {
+ priv->snaps->regex = talloc_zero(priv->snaps, regex_t);
+ if (priv->snaps->regex == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* pre-compute regex rule for matching pattern later */
+ ret = regcomp(priv->snaps->regex, snapprefix, 0);
+ if (ret) {
+ DBG_ERR("Failed to create regex object\n");
+ return -1;
+ }
+ }
+
+ delimiter = lp_parm_const_string(SNUM(handle->conn),
+ "shadow", "delimiter",
+ "_GMT");
+ if (delimiter != NULL) {
+ priv->config->delimiter = talloc_strdup(priv->config, delimiter);
+ if (priv->config->delimiter == NULL) {
+ DBG_ERR("talloc_strdup() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ config->snapdirseverywhere = lp_parm_bool(SNUM(handle->conn),
+ "shadow",
+ "snapdirseverywhere",
+ false);
+
+ config->crossmountpoints = lp_parm_bool(SNUM(handle->conn),
+ "shadow", "crossmountpoints",
+ false);
+
+ if (config->crossmountpoints && !config->snapdirseverywhere) {
+ DBG_WARNING("Warning: 'crossmountpoints' depends on "
+ "'snapdirseverywhere'. Disabling crossmountpoints.\n");
+ }
+
+ config->fixinodes = lp_parm_bool(SNUM(handle->conn),
+ "shadow", "fixinodes",
+ false);
+
+ sort_order = lp_parm_const_string(SNUM(handle->conn),
+ "shadow", "sort", "desc");
+ config->sort_order = talloc_strdup(config, sort_order);
+ if (config->sort_order == NULL) {
+ DEBUG(0, ("talloc_strdup() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ mount_point = lp_parm_const_string(SNUM(handle->conn),
+ "shadow", "mountpoint", NULL);
+ if (mount_point != NULL) {
+ if (mount_point[0] != '/') {
+ DBG_WARNING("Warning: 'mountpoint' is relative "
+ "('%s'), but it has to be an absolute "
+ "path. Ignoring provided value.\n",
+ mount_point);
+ mount_point = NULL;
+ } else {
+ char *p;
+ p = strstr(handle->conn->connectpath, mount_point);
+ if (p != handle->conn->connectpath) {
+ DBG_WARNING("Warning: the share root (%s) is "
+ "not a subdirectory of the "
+ "specified mountpoint (%s). "
+ "Ignoring provided value.\n",
+ handle->conn->connectpath,
+ mount_point);
+ mount_point = NULL;
+ }
+ }
+ }
+
+ if (mount_point != NULL) {
+ config->mount_point = talloc_strdup(config, mount_point);
+ if (config->mount_point == NULL) {
+ DBG_ERR("talloc_strdup() failed\n");
+ return -1;
+ }
+ } else {
+ config->mount_point = shadow_copy2_find_mount_point(config,
+ handle);
+ if (config->mount_point == NULL) {
+ DBG_WARNING("shadow_copy2_find_mount_point "
+ "of the share root '%s' failed: %s\n",
+ handle->conn->connectpath, strerror(errno));
+ return -1;
+ }
+ }
+
+ basedir = lp_parm_const_string(SNUM(handle->conn),
+ "shadow", "basedir", NULL);
+
+ if (basedir != NULL) {
+ if (basedir[0] != '/') {
+ DBG_WARNING("Warning: 'basedir' is "
+ "relative ('%s'), but it has to be an "
+ "absolute path. Disabling basedir.\n",
+ basedir);
+ basedir = NULL;
+ } else {
+ char *p;
+ p = strstr(basedir, config->mount_point);
+ if (p != basedir) {
+ DEBUG(1, ("Warning: basedir (%s) is not a "
+ "subdirectory of the share root's "
+ "mount point (%s). "
+ "Disabling basedir\n",
+ basedir, config->mount_point));
+ basedir = NULL;
+ }
+ }
+ }
+
+ if (config->snapdirseverywhere && basedir != NULL) {
+ DBG_WARNING("Warning: 'basedir' is incompatible "
+ "with 'snapdirseverywhere'. Disabling basedir.\n");
+ basedir = NULL;
+ }
+
+ snapsharepath = lp_parm_const_string(SNUM(handle->conn), "shadow",
+ "snapsharepath", NULL);
+ if (snapsharepath != NULL) {
+ if (snapsharepath[0] == '/') {
+ DBG_WARNING("Warning: 'snapsharepath' is "
+ "absolute ('%s'), but it has to be a "
+ "relative path. Disabling snapsharepath.\n",
+ snapsharepath);
+ snapsharepath = NULL;
+ }
+ if (config->snapdirseverywhere && snapsharepath != NULL) {
+ DBG_WARNING("Warning: 'snapsharepath' is incompatible "
+ "with 'snapdirseverywhere'. Disabling "
+ "snapsharepath.\n");
+ snapsharepath = NULL;
+ }
+ }
+
+ if (basedir != NULL && snapsharepath != NULL) {
+ DBG_WARNING("Warning: 'snapsharepath' is incompatible with "
+ "'basedir'. Disabling snapsharepath\n");
+ snapsharepath = NULL;
+ }
+
+ if (snapsharepath != NULL) {
+ config->rel_connectpath = talloc_strdup(config, snapsharepath);
+ if (config->rel_connectpath == NULL) {
+ DBG_ERR("talloc_strdup() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ if (basedir == NULL) {
+ basedir = config->mount_point;
+ }
+
+ if (config->rel_connectpath == NULL &&
+ strlen(basedir) < strlen(handle->conn->connectpath)) {
+ config->rel_connectpath = talloc_strdup(config,
+ handle->conn->connectpath + strlen(basedir));
+ if (config->rel_connectpath == NULL) {
+ DEBUG(0, ("talloc_strdup() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ if (config->snapdir[0] == '/') {
+ config->snapdir_absolute = true;
+
+ if (config->snapdirseverywhere) {
+ DBG_WARNING("Warning: An absolute snapdir is "
+ "incompatible with 'snapdirseverywhere', "
+ "setting 'snapdirseverywhere' to "
+ "false.\n");
+ config->snapdirseverywhere = false;
+ }
+
+ if (config->crossmountpoints) {
+ DBG_WARNING("Warning: 'crossmountpoints' is not "
+ "supported with an absolute snapdir. "
+ "Disabling it.\n");
+ config->crossmountpoints = false;
+ }
+
+ config->snapshot_basepath = config->snapdir;
+ } else {
+ config->snapshot_basepath = talloc_asprintf(config, "%s/%s",
+ config->mount_point, config->snapdir);
+ if (config->snapshot_basepath == NULL) {
+ DEBUG(0, ("talloc_asprintf() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ trim_string(config->mount_point, NULL, "/");
+ trim_string(config->rel_connectpath, "/", "/");
+ trim_string(config->snapdir, NULL, "/");
+ trim_string(config->snapshot_basepath, NULL, "/");
+
+ DEBUG(10, ("shadow_copy2_connect: configuration:\n"
+ " share root: '%s'\n"
+ " mountpoint: '%s'\n"
+ " rel share root: '%s'\n"
+ " snapdir: '%s'\n"
+ " snapprefix: '%s'\n"
+ " delimiter: '%s'\n"
+ " snapshot base path: '%s'\n"
+ " format: '%s'\n"
+ " use sscanf: %s\n"
+ " snapdirs everywhere: %s\n"
+ " cross mountpoints: %s\n"
+ " fix inodes: %s\n"
+ " sort order: %s\n"
+ "",
+ handle->conn->connectpath,
+ config->mount_point,
+ config->rel_connectpath,
+ config->snapdir,
+ snapprefix,
+ config->delimiter,
+ config->snapshot_basepath,
+ config->gmt_format,
+ config->use_sscanf ? "yes" : "no",
+ config->snapdirseverywhere ? "yes" : "no",
+ config->crossmountpoints ? "yes" : "no",
+ config->fixinodes ? "yes" : "no",
+ config->sort_order
+ ));
+
+
+ SMB_VFS_HANDLE_SET_DATA(handle, priv,
+ NULL, struct shadow_copy2_private,
+ return -1);
+
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_shadow_copy2_fns = {
+ .connect_fn = shadow_copy2_connect,
+ .disk_free_fn = shadow_copy2_disk_free,
+ .get_quota_fn = shadow_copy2_get_quota,
+ .create_dfs_pathat_fn = shadow_copy2_create_dfs_pathat,
+ .read_dfs_pathat_fn = shadow_copy2_read_dfs_pathat,
+ .renameat_fn = shadow_copy2_renameat,
+ .linkat_fn = shadow_copy2_linkat,
+ .symlinkat_fn = shadow_copy2_symlinkat,
+ .stat_fn = shadow_copy2_stat,
+ .lstat_fn = shadow_copy2_lstat,
+ .fstat_fn = shadow_copy2_fstat,
+ .fstatat_fn = shadow_copy2_fstatat,
+ .openat_fn = shadow_copy2_openat,
+ .unlinkat_fn = shadow_copy2_unlinkat,
+ .fchmod_fn = shadow_copy2_fchmod,
+ .chdir_fn = shadow_copy2_chdir,
+ .fntimes_fn = shadow_copy2_fntimes,
+ .readlinkat_fn = shadow_copy2_readlinkat,
+ .mknodat_fn = shadow_copy2_mknodat,
+ .realpath_fn = shadow_copy2_realpath,
+ .get_shadow_copy_data_fn = shadow_copy2_get_shadow_copy_data,
+ .mkdirat_fn = shadow_copy2_mkdirat,
+ .fsetxattr_fn = shadow_copy2_fsetxattr,
+ .fchflags_fn = shadow_copy2_fchflags,
+ .pwrite_fn = shadow_copy2_pwrite,
+ .pwrite_send_fn = shadow_copy2_pwrite_send,
+ .pwrite_recv_fn = shadow_copy2_pwrite_recv,
+ .connectpath_fn = shadow_copy2_connectpath,
+ .parent_pathname_fn = shadow_copy2_parent_pathname,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_shadow_copy2_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "shadow_copy2", &vfs_shadow_copy2_fns);
+}
diff --git a/source3/modules/vfs_shell_snap.c b/source3/modules/vfs_shell_snap.c
new file mode 100644
index 0000000..d1b7b8c
--- /dev/null
+++ b/source3/modules/vfs_shell_snap.c
@@ -0,0 +1,201 @@
+/*
+ * Module for snapshot management using shell callouts
+ *
+ * Copyright (C) David Disseldorp 2013-2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "include/ntioctl.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+
+/*
+ * Check whether a path can be shadow copied. Return the base volume, allowing
+ * the caller to determine if multiple paths lie on the same base volume.
+ */
+static NTSTATUS shell_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ NTSTATUS status;
+ const char *cmd;
+ char *cmd_run;
+ int ret;
+ TALLOC_CTX *tmp_ctx;
+
+ cmd = lp_parm_const_string(handle->conn->params->service,
+ "shell_snap", "check path command", "");
+ if ((cmd == NULL) || (strlen(cmd) == 0)) {
+ DEBUG(0,
+ ("\"shell_snap:check path command\" not configured\n"));
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto err_out;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_out;
+ }
+
+ /* add service path argument */
+ cmd_run = talloc_asprintf(tmp_ctx, "%s %s", cmd, service_path);
+ if (cmd_run == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_tmp_free;
+ }
+
+ ret = smbrun(cmd_run, NULL, NULL);
+ if (ret != 0) {
+ DEBUG(0, ("%s failed with %d\n", cmd_run, ret));
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto err_tmp_free;
+ }
+
+ /* assume the service path is the base volume */
+ *base_volume = talloc_strdup(mem_ctx, service_path);
+ if (*base_volume == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_tmp_free;
+ }
+ status = NT_STATUS_OK;
+err_tmp_free:
+ talloc_free(tmp_ctx);
+err_out:
+ return status;
+}
+
+static NTSTATUS shell_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path)
+{
+ const char *cmd;
+ char *cmd_run;
+ char **qlines;
+ int numlines, ret;
+ int fd = -1;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ cmd = lp_parm_const_string(handle->conn->params->service,
+ "shell_snap", "create command", "");
+ if ((cmd == NULL) || (strlen(cmd) == 0)) {
+ DEBUG(1, ("\"shell_snap:create command\" not configured\n"));
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto err_out;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_out;
+ }
+
+ /* add base vol argument */
+ cmd_run = talloc_asprintf(tmp_ctx, "%s %s", cmd, base_volume);
+ if (cmd_run == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_tmp_free;
+ }
+
+ ret = smbrun(cmd_run, &fd, NULL);
+ talloc_free(cmd_run);
+ if (ret != 0) {
+ if (fd != -1) {
+ close(fd);
+ }
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_tmp_free;
+ }
+
+ numlines = 0;
+ qlines = fd_lines_load(fd, &numlines, PATH_MAX + 1, tmp_ctx);
+ close(fd);
+
+ /* script must return the snapshot path as a single line */
+ if ((numlines == 0) || (qlines == NULL) || (qlines[0] == NULL)) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_tmp_free;
+ }
+
+ *base_path = talloc_strdup(mem_ctx, base_volume);
+ if (*base_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_tmp_free;
+ }
+ *snap_path = talloc_strdup(mem_ctx, qlines[0]);
+ if (*snap_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ talloc_free(*base_path);
+ goto err_tmp_free;
+ }
+
+ status = NT_STATUS_OK;
+err_tmp_free:
+ talloc_free(tmp_ctx);
+err_out:
+ return status;
+}
+
+static NTSTATUS shell_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ const char *cmd;
+ char *cmd_run;
+ int ret;
+
+ cmd = lp_parm_const_string(handle->conn->params->service,
+ "shell_snap", "delete command", "");
+ if ((cmd == NULL) || (strlen(cmd) == 0)) {
+ DEBUG(1, ("\"shell_snap:delete command\" not configured\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* add base path and snap path arguments */
+ cmd_run = talloc_asprintf(mem_ctx, "%s %s %s",
+ cmd, base_path, snap_path);
+ if (cmd_run == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = smbrun(cmd_run, NULL, NULL);
+ talloc_free(cmd_run);
+ if (ret != 0) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static struct vfs_fn_pointers shell_snap_fns = {
+ .snap_check_path_fn = shell_snap_check_path,
+ .snap_create_fn = shell_snap_create,
+ .snap_delete_fn = shell_snap_delete,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_shell_snap_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "shell_snap", &shell_snap_fns);
+}
diff --git a/source3/modules/vfs_snapper.c b/source3/modules/vfs_snapper.c
new file mode 100644
index 0000000..f12a94b
--- /dev/null
+++ b/source3/modules/vfs_snapper.c
@@ -0,0 +1,2647 @@
+/*
+ * Module for snapshot IO using snapper
+ *
+ * Copyright (C) David Disseldorp 2012-2014
+ *
+ * Portions taken from vfs_shadow_copy2.c:
+ * Copyright (C) Andrew Tridgell 2007
+ * Copyright (C) Ed Plese 2009
+ * Copyright (C) Volker Lendecke 2011
+ * Copyright (C) Christian Ambach 2011
+ * Copyright (C) Michael Adam 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dbus/dbus.h>
+#ifdef HAVE_LINUX_IOCTL_H
+#include <linux/ioctl.h>
+#endif
+#include <sys/ioctl.h>
+#include <dirent.h>
+#include <libgen.h>
+#include "includes.h"
+#include "include/ntioctl.h"
+#include "include/smb.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/smb_strtox.h"
+
+#define SNAPPER_SIG_LIST_SNAPS_RSP "a(uquxussa{ss})"
+#define SNAPPER_SIG_LIST_CONFS_RSP "a(ssa{ss})"
+#define SNAPPER_SIG_CREATE_SNAP_RSP "u"
+#define SNAPPER_SIG_DEL_SNAPS_RSP ""
+#define SNAPPER_SIG_STRING_DICT "{ss}"
+
+struct snapper_dict {
+ char *key;
+ char *val;
+};
+
+struct snapper_snap {
+ uint32_t id;
+ uint16_t type;
+ uint32_t pre_id;
+ int64_t time;
+ uint32_t creator_uid;
+ char *desc;
+ char *cleanup;
+ uint32_t num_user_data;
+ struct snapper_dict *user_data;
+};
+
+struct snapper_conf {
+ char *name;
+ char *mnt;
+ uint32_t num_attrs;
+ struct snapper_dict *attrs;
+};
+
+static const struct {
+ const char *snapper_err_str;
+ NTSTATUS status;
+} snapper_err_map[] = {
+ { "error.no_permissions", NT_STATUS_ACCESS_DENIED },
+};
+
+static NTSTATUS snapper_err_ntstatus_map(const char *snapper_err_str)
+{
+ int i;
+
+ if (snapper_err_str == NULL) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ for (i = 0; i < ARRAY_SIZE(snapper_err_map); i++) {
+ if (!strcmp(snapper_err_map[i].snapper_err_str,
+ snapper_err_str)) {
+ return snapper_err_map[i].status;
+ }
+ }
+ DEBUG(2, ("no explicit mapping for dbus error: %s\n", snapper_err_str));
+
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+/*
+ * Strings are UTF-8. Other characters must be encoded hexadecimal as "\x??".
+ * As a consequence "\" must be encoded as "\\".
+ */
+static NTSTATUS snapper_dbus_str_encode(TALLOC_CTX *mem_ctx, const char *in_str,
+ char **_out_str)
+{
+ size_t in_len;
+ char *out_str;
+ int i;
+ int out_off;
+ int out_len;
+
+ if (in_str == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ in_len = strlen(in_str);
+
+ /* output can be max 4 times the length of @in_str, +1 for terminator */
+ out_len = (in_len * 4) + 1;
+
+ out_str = talloc_array(mem_ctx, char, out_len);
+ if (out_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ out_off = 0;
+ for (i = 0; i < in_len; i++) {
+ size_t pushed;
+
+ if (in_str[i] == '\\') {
+ pushed = snprintf(out_str + out_off, out_len - out_off,
+ "\\\\");
+ } else if ((unsigned char)in_str[i] > 127) {
+ pushed = snprintf(out_str + out_off, out_len - out_off,
+ "\\x%02x", (unsigned char)in_str[i]);
+ } else {
+ /* regular character */
+ *(out_str + out_off) = in_str[i];
+ pushed = sizeof(char);
+ }
+ if (pushed >= out_len - out_off) {
+ /* truncated, should never happen */
+ talloc_free(out_str);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ out_off += pushed;
+ }
+
+ *(out_str + out_off) = '\0';
+ *_out_str = out_str;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_dbus_str_decode(TALLOC_CTX *mem_ctx, const char *in_str,
+ char **_out_str)
+{
+ size_t in_len;
+ char *out_str;
+ int i;
+ int out_off;
+ int out_len;
+
+ if (in_str == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ in_len = strlen(in_str);
+
+ /* output cannot be larger than input, +1 for terminator */
+ out_len = in_len + 1;
+
+ out_str = talloc_array(mem_ctx, char, out_len);
+ if (out_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ out_off = 0;
+ for (i = 0; i < in_len; i++) {
+ int j;
+ char hex_buf[3];
+ unsigned int non_ascii_byte;
+
+ if (in_str[i] != '\\') {
+ out_str[out_off] = in_str[i];
+ out_off++;
+ continue;
+ }
+
+ i++;
+ if (in_str[i] == '\\') {
+ out_str[out_off] = '\\';
+ out_off++;
+ continue;
+ } else if (in_str[i] != 'x') {
+ goto err_invalid_src_encoding;
+ }
+
+ /* non-ASCII, encoded as two hex chars */
+ for (j = 0; j < 2; j++) {
+ i++;
+ if ((in_str[i] == '\0') || !isxdigit(in_str[i])) {
+ goto err_invalid_src_encoding;
+ }
+ hex_buf[j] = in_str[i];
+ }
+ hex_buf[2] = '\0';
+
+ sscanf(hex_buf, "%x", &non_ascii_byte);
+ out_str[out_off] = (unsigned char)non_ascii_byte;
+ out_off++;
+ }
+
+ out_str[out_off] = '\0';
+ *_out_str = out_str;
+
+ return NT_STATUS_OK;
+err_invalid_src_encoding:
+ DEBUG(0, ("invalid encoding %s\n", in_str));
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+static DBusConnection *snapper_dbus_conn_create(void)
+{
+ DBusError err;
+ DBusConnection *dconn;
+
+ dbus_error_init(&err);
+
+ /*
+ * Always create a new DBus connection, to ensure snapperd detects the
+ * correct client [E]UID. With dbus_bus_get() it does not!
+ */
+ dconn = dbus_bus_get_private(DBUS_BUS_SYSTEM, &err);
+ if (dbus_error_is_set(&err)) {
+ DEBUG(0, ("dbus connection error: %s\n", err.message));
+ dbus_error_free(&err);
+ }
+ if (dconn == NULL) {
+ return NULL;
+ }
+
+ /* dbus_bus_get_private() sets exit-on-disconnect by default, undo it */
+ dbus_connection_set_exit_on_disconnect(dconn, false);
+
+ return dconn;
+}
+
+static void snapper_dbus_conn_destroy(DBusConnection *dconn)
+{
+ if (dconn == NULL) {
+ DEBUG(2, ("attempt to destroy NULL dbus connection\n"));
+ return;
+ }
+
+ dbus_connection_close(dconn);
+ dbus_connection_unref(dconn);
+}
+
+/*
+ * send the message @send_msg over the dbus and wait for a response, return the
+ * responsee via @recv_msg_out.
+ * @send_msg is not freed, dbus_message_unref() must be handled by the caller.
+ */
+static NTSTATUS snapper_dbus_msg_xchng(DBusConnection *dconn,
+ DBusMessage *send_msg,
+ DBusMessage **recv_msg_out)
+{
+ DBusPendingCall *pending;
+ DBusMessage *recv_msg;
+
+ /* send message and get a handle for a reply */
+ if (!dbus_connection_send_with_reply(dconn, send_msg, &pending, -1)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (NULL == pending) {
+ DEBUG(0, ("dbus msg send failed\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dbus_connection_flush(dconn);
+
+ /* block until we receive a reply */
+ dbus_pending_call_block(pending);
+
+ /* get the reply message */
+ recv_msg = dbus_pending_call_steal_reply(pending);
+ if (recv_msg == NULL) {
+ DEBUG(0, ("Reply Null\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ /* free the pending message handle */
+ dbus_pending_call_unref(pending);
+ *recv_msg_out = recv_msg;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_type_check(DBusMessageIter *iter,
+ int expected_type)
+{
+ int type = dbus_message_iter_get_arg_type(iter);
+ if (type != expected_type) {
+ DEBUG(0, ("got type %d, expecting %d\n",
+ type, expected_type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_type_check_get(DBusMessageIter *iter,
+ int expected_type,
+ void *val)
+{
+ NTSTATUS status;
+ status = snapper_type_check(iter, expected_type);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_get_basic(iter, val);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_dict_unpack(TALLOC_CTX *mem_ctx,
+ DBusMessageIter *iter,
+ struct snapper_dict *dict_out)
+
+{
+ NTSTATUS status;
+ DBusMessageIter dct_iter;
+ char *key_encoded;
+ char *val_encoded;
+
+ status = snapper_type_check(iter, DBUS_TYPE_DICT_ENTRY);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ dbus_message_iter_recurse(iter, &dct_iter);
+
+ status = snapper_type_check_get(&dct_iter, DBUS_TYPE_STRING,
+ &key_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = snapper_dbus_str_decode(mem_ctx, key_encoded, &dict_out->key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&dct_iter);
+ status = snapper_type_check_get(&dct_iter, DBUS_TYPE_STRING,
+ &val_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(dict_out->key);
+ return status;
+ }
+ status = snapper_dbus_str_decode(mem_ctx, val_encoded, &dict_out->val);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(dict_out->key);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void snapper_dict_array_print(uint32_t num_dicts,
+ struct snapper_dict *dicts)
+{
+ int i;
+
+ for (i = 0; i < num_dicts; i++) {
+ DEBUG(10, ("dict (key: %s, val: %s)\n",
+ dicts[i].key, dicts[i].val));
+ }
+}
+
+static NTSTATUS snapper_dict_array_unpack(TALLOC_CTX *mem_ctx,
+ DBusMessageIter *iter,
+ uint32_t *num_dicts_out,
+ struct snapper_dict **dicts_out)
+{
+ NTSTATUS status;
+ DBusMessageIter array_iter;
+ uint32_t num_dicts;
+ struct snapper_dict *dicts = NULL;
+
+ status = snapper_type_check(iter, DBUS_TYPE_ARRAY);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ dbus_message_iter_recurse(iter, &array_iter);
+
+ num_dicts = 0;
+ while (dbus_message_iter_get_arg_type(&array_iter)
+ != DBUS_TYPE_INVALID) {
+ num_dicts++;
+ dicts = talloc_realloc(mem_ctx, dicts, struct snapper_dict,
+ num_dicts);
+ if (dicts == NULL)
+ abort();
+
+ status = snapper_dict_unpack(mem_ctx, &array_iter,
+ &dicts[num_dicts - 1]);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(dicts);
+ return status;
+ }
+ dbus_message_iter_next(&array_iter);
+ }
+
+ *num_dicts_out = num_dicts;
+ *dicts_out = dicts;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_list_confs_pack(DBusMessage **req_msg_out)
+{
+ DBusMessage *msg;
+
+ msg = dbus_message_new_method_call("org.opensuse.Snapper",
+ "/org/opensuse/Snapper",
+ "org.opensuse.Snapper",
+ "ListConfigs");
+ if (msg == NULL) {
+ DEBUG(0, ("null msg\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* no arguments to append */
+ *req_msg_out = msg;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_conf_unpack(TALLOC_CTX *mem_ctx,
+ DBusMessageIter *iter,
+ struct snapper_conf *conf_out)
+{
+ NTSTATUS status;
+ DBusMessageIter st_iter;
+ char *name_encoded;
+ char *mnt_encoded;
+
+ status = snapper_type_check(iter, DBUS_TYPE_STRUCT);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ dbus_message_iter_recurse(iter, &st_iter);
+
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING,
+ &name_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = snapper_dbus_str_decode(mem_ctx, name_encoded,
+ &conf_out->name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING,
+ &mnt_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(conf_out->name);
+ return status;
+ }
+
+ status = snapper_dbus_str_decode(mem_ctx, mnt_encoded,
+ &conf_out->mnt);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(conf_out->name);
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_dict_array_unpack(mem_ctx, &st_iter,
+ &conf_out->num_attrs,
+ &conf_out->attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(conf_out->mnt);
+ talloc_free(conf_out->name);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static struct snapper_conf *snapper_conf_array_base_find(int32_t num_confs,
+ struct snapper_conf *confs,
+ const char *base)
+{
+ int i;
+
+ for (i = 0; i < num_confs; i++) {
+ if (strcmp(confs[i].mnt, base) == 0) {
+ DEBUG(5, ("found snapper conf %s for path %s\n",
+ confs[i].name, base));
+ return &confs[i];
+ }
+ }
+ DEBUG(5, ("config for base %s not found\n", base));
+
+ return NULL;
+}
+
+static void snapper_conf_array_print(int32_t num_confs,
+ struct snapper_conf *confs)
+{
+ int i;
+
+ for (i = 0; i < num_confs; i++) {
+ DEBUG(10, ("name: %s, mnt: %s\n",
+ confs[i].name, confs[i].mnt));
+ snapper_dict_array_print(confs[i].num_attrs, confs[i].attrs);
+ }
+}
+
+static NTSTATUS snapper_conf_array_unpack(TALLOC_CTX *mem_ctx,
+ DBusMessageIter *iter,
+ uint32_t *num_confs_out,
+ struct snapper_conf **confs_out)
+{
+ uint32_t num_confs;
+ NTSTATUS status;
+ struct snapper_conf *confs = NULL;
+ DBusMessageIter array_iter;
+
+
+ status = snapper_type_check(iter, DBUS_TYPE_ARRAY);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ dbus_message_iter_recurse(iter, &array_iter);
+
+ num_confs = 0;
+ while (dbus_message_iter_get_arg_type(&array_iter)
+ != DBUS_TYPE_INVALID) {
+ num_confs++;
+ confs = talloc_realloc(mem_ctx, confs, struct snapper_conf,
+ num_confs);
+ if (confs == NULL)
+ abort();
+
+ status = snapper_conf_unpack(confs, &array_iter,
+ &confs[num_confs - 1]);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(confs);
+ return status;
+ }
+ dbus_message_iter_next(&array_iter);
+ }
+
+ *num_confs_out = num_confs;
+ *confs_out = confs;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_list_confs_unpack(TALLOC_CTX *mem_ctx,
+ DBusConnection *dconn,
+ DBusMessage *rsp_msg,
+ uint32_t *num_confs_out,
+ struct snapper_conf **confs_out)
+{
+ NTSTATUS status;
+ DBusMessageIter iter;
+ int msg_type;
+ uint32_t num_confs;
+ struct snapper_conf *confs;
+ const char *sig;
+
+ msg_type = dbus_message_get_type(rsp_msg);
+ if (msg_type == DBUS_MESSAGE_TYPE_ERROR) {
+ const char *err_str = dbus_message_get_error_name(rsp_msg);
+ DEBUG(0, ("list_confs error response: %s\n", err_str));
+ return snapper_err_ntstatus_map(err_str);
+ }
+
+ if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ DEBUG(0, ("unexpected list_confs ret type: %d\n",
+ msg_type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sig = dbus_message_get_signature(rsp_msg);
+ if ((sig == NULL)
+ || (strcmp(sig, SNAPPER_SIG_LIST_CONFS_RSP) != 0)) {
+ DEBUG(0, ("bad list confs response sig: %s, expected: %s\n",
+ (sig ? sig : "NULL"), SNAPPER_SIG_LIST_CONFS_RSP));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!dbus_message_iter_init(rsp_msg, &iter)) {
+ /* FIXME return empty? */
+ DEBUG(0, ("Message has no arguments!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = snapper_conf_array_unpack(mem_ctx, &iter, &num_confs, &confs);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to unpack conf array\n"));
+ return status;
+ }
+
+ snapper_conf_array_print(num_confs, confs);
+
+ *num_confs_out = num_confs;
+ *confs_out = confs;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_list_snaps_pack(TALLOC_CTX *mem_ctx,
+ char *snapper_conf,
+ DBusMessage **req_msg_out)
+{
+ DBusMessage *msg;
+ DBusMessageIter args;
+ char *conf_encoded;
+ NTSTATUS status;
+
+ msg = dbus_message_new_method_call("org.opensuse.Snapper", /* target for the method call */
+ "/org/opensuse/Snapper", /* object to call on */
+ "org.opensuse.Snapper", /* interface to call on */
+ "ListSnapshots"); /* method name */
+ if (msg == NULL) {
+ DEBUG(0, ("failed to create list snaps message\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = snapper_dbus_str_encode(mem_ctx, snapper_conf, &conf_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbus_message_unref(msg);
+ return status;
+ }
+
+ /* append arguments */
+ dbus_message_iter_init_append(msg, &args);
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+ &conf_encoded)) {
+ talloc_free(conf_encoded);
+ dbus_message_unref(msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *req_msg_out = msg;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_snap_struct_unpack(TALLOC_CTX *mem_ctx,
+ DBusMessageIter *iter,
+ struct snapper_snap *snap_out)
+{
+ NTSTATUS status;
+ DBusMessageIter st_iter;
+ char *desc_encoded;
+ char *cleanup_encoded;
+
+ status = snapper_type_check(iter, DBUS_TYPE_STRUCT);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ dbus_message_iter_recurse(iter, &st_iter);
+
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT32,
+ &snap_out->id);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT16,
+ &snap_out->type);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT32,
+ &snap_out->pre_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_INT64,
+ &snap_out->time);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_UINT32,
+ &snap_out->creator_uid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING,
+ &desc_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = snapper_dbus_str_decode(mem_ctx, desc_encoded,
+ &snap_out->desc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_type_check_get(&st_iter, DBUS_TYPE_STRING,
+ &cleanup_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(snap_out->desc);
+ return status;
+ }
+
+ status = snapper_dbus_str_decode(mem_ctx, cleanup_encoded,
+ &snap_out->cleanup);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(snap_out->desc);
+ return status;
+ }
+
+ dbus_message_iter_next(&st_iter);
+ status = snapper_dict_array_unpack(mem_ctx, &st_iter,
+ &snap_out->num_user_data,
+ &snap_out->user_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(snap_out->cleanup);
+ talloc_free(snap_out->desc);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void snapper_snap_array_print(int32_t num_snaps,
+ struct snapper_snap *snaps)
+{
+ int i;
+
+ for (i = 0; i < num_snaps; i++) {
+ DEBUG(10, ("id: %u, "
+ "type: %u, "
+ "pre_id: %u, "
+ "time: %ld, "
+ "creator_uid: %u, "
+ "desc: %s, "
+ "cleanup: %s\n",
+ (unsigned int)snaps[i].id,
+ (unsigned int)snaps[i].type,
+ (unsigned int)snaps[i].pre_id,
+ (long int)snaps[i].time,
+ (unsigned int)snaps[i].creator_uid,
+ snaps[i].desc,
+ snaps[i].cleanup));
+ snapper_dict_array_print(snaps[i].num_user_data,
+ snaps[i].user_data);
+ }
+}
+
+static NTSTATUS snapper_snap_array_unpack(TALLOC_CTX *mem_ctx,
+ DBusMessageIter *iter,
+ uint32_t *num_snaps_out,
+ struct snapper_snap **snaps_out)
+{
+ uint32_t num_snaps;
+ NTSTATUS status;
+ struct snapper_snap *snaps = NULL;
+ DBusMessageIter array_iter;
+
+
+ status = snapper_type_check(iter, DBUS_TYPE_ARRAY);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ dbus_message_iter_recurse(iter, &array_iter);
+
+ num_snaps = 0;
+ while (dbus_message_iter_get_arg_type(&array_iter)
+ != DBUS_TYPE_INVALID) {
+ num_snaps++;
+ snaps = talloc_realloc(mem_ctx, snaps, struct snapper_snap,
+ num_snaps);
+ if (snaps == NULL)
+ abort();
+
+ status = snapper_snap_struct_unpack(snaps, &array_iter,
+ &snaps[num_snaps - 1]);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(snaps);
+ return status;
+ }
+ dbus_message_iter_next(&array_iter);
+ }
+
+ *num_snaps_out = num_snaps;
+ *snaps_out = snaps;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_list_snaps_unpack(TALLOC_CTX *mem_ctx,
+ DBusMessage *rsp_msg,
+ uint32_t *num_snaps_out,
+ struct snapper_snap **snaps_out)
+{
+ NTSTATUS status;
+ DBusMessageIter iter;
+ int msg_type;
+ uint32_t num_snaps;
+ struct snapper_snap *snaps;
+ const char *sig;
+
+ msg_type = dbus_message_get_type(rsp_msg);
+ if (msg_type == DBUS_MESSAGE_TYPE_ERROR) {
+ const char *err_str = dbus_message_get_error_name(rsp_msg);
+ DEBUG(0, ("list_snaps error response: %s\n", err_str));
+ return snapper_err_ntstatus_map(err_str);
+ }
+
+ if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ DEBUG(0,("unexpected list_snaps ret type: %d\n",
+ msg_type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sig = dbus_message_get_signature(rsp_msg);
+ if ((sig == NULL)
+ || (strcmp(sig, SNAPPER_SIG_LIST_SNAPS_RSP) != 0)) {
+ DEBUG(0, ("bad list snaps response sig: %s, "
+ "expected: %s\n",
+ (sig ? sig : "NULL"),
+ SNAPPER_SIG_LIST_SNAPS_RSP));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* read the parameters */
+ if (!dbus_message_iter_init(rsp_msg, &iter)) {
+ DEBUG(0, ("response has no arguments!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = snapper_snap_array_unpack(mem_ctx, &iter, &num_snaps, &snaps);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to unpack snap array\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ snapper_snap_array_print(num_snaps, snaps);
+
+ *num_snaps_out = num_snaps;
+ *snaps_out = snaps;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_create_snap_pack(TALLOC_CTX *mem_ctx,
+ const char *snapper_conf,
+ const char *desc,
+ uint32_t num_user_data,
+ struct snapper_dict *user_data,
+ DBusMessage **req_msg_out)
+{
+ DBusMessage *msg;
+ DBusMessageIter args;
+ DBusMessageIter array_iter;
+ DBusMessageIter struct_iter;
+ const char *empty = "";
+ char *str_encoded;
+ uint32_t i;
+ bool ok;
+ TALLOC_CTX *enc_ctx;
+ NTSTATUS status;
+
+ DEBUG(10, ("CreateSingleSnapshot: %s, %s, %s, num user %u\n",
+ snapper_conf, desc, empty, num_user_data));
+
+ enc_ctx = talloc_new(mem_ctx);
+ if (enc_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ msg = dbus_message_new_method_call("org.opensuse.Snapper",
+ "/org/opensuse/Snapper",
+ "org.opensuse.Snapper",
+ "CreateSingleSnapshot");
+ if (msg == NULL) {
+ DEBUG(0, ("failed to create req msg\n"));
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = snapper_dbus_str_encode(enc_ctx, snapper_conf, &str_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return status;
+ }
+
+ /* append arguments */
+ dbus_message_iter_init_append(msg, &args);
+ ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+ &str_encoded);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = snapper_dbus_str_encode(enc_ctx, desc, &str_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return status;
+ }
+
+ ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+ &str_encoded);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* cleanup - no need to encode empty string */
+ ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+ &empty);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+ SNAPPER_SIG_STRING_DICT,
+ &array_iter);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_user_data; i++) {
+ ok = dbus_message_iter_open_container(&array_iter,
+ DBUS_TYPE_DICT_ENTRY,
+ NULL, &struct_iter);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = snapper_dbus_str_encode(enc_ctx, user_data[i].key,
+ &str_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return status;
+ }
+
+ ok = dbus_message_iter_append_basic(&struct_iter,
+ DBUS_TYPE_STRING,
+ &str_encoded);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = snapper_dbus_str_encode(enc_ctx, user_data[i].val,
+ &str_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return status;
+ }
+
+ ok = dbus_message_iter_append_basic(&struct_iter,
+ DBUS_TYPE_STRING,
+ &str_encoded);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = dbus_message_iter_close_container(&array_iter, &struct_iter);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ ok = dbus_message_iter_close_container(&args, &array_iter);
+ if (!ok) {
+ dbus_message_unref(msg);
+ talloc_free(enc_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *req_msg_out = msg;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_create_snap_unpack(DBusConnection *conn,
+ DBusMessage *rsp_msg,
+ uint32_t *snap_id_out)
+{
+ NTSTATUS status;
+ DBusMessageIter iter;
+ int msg_type;
+ const char *sig;
+ uint32_t snap_id;
+
+ msg_type = dbus_message_get_type(rsp_msg);
+ if (msg_type == DBUS_MESSAGE_TYPE_ERROR) {
+ const char *err_str = dbus_message_get_error_name(rsp_msg);
+ DEBUG(0, ("create snap error response: %s, euid %d egid %d\n",
+ err_str, geteuid(), getegid()));
+ return snapper_err_ntstatus_map(err_str);
+ }
+
+ if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ DEBUG(0, ("unexpected create snap ret type: %d\n",
+ msg_type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sig = dbus_message_get_signature(rsp_msg);
+ if ((sig == NULL)
+ || (strcmp(sig, SNAPPER_SIG_CREATE_SNAP_RSP) != 0)) {
+ DEBUG(0, ("bad create snap response sig: %s, expected: %s\n",
+ (sig ? sig : "NULL"), SNAPPER_SIG_CREATE_SNAP_RSP));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* read the parameters */
+ if (!dbus_message_iter_init(rsp_msg, &iter)) {
+ DEBUG(0, ("response has no arguments!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = snapper_type_check_get(&iter, DBUS_TYPE_UINT32, &snap_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *snap_id_out = snap_id;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_del_snap_pack(TALLOC_CTX *mem_ctx,
+ const char *snapper_conf,
+ uint32_t snap_id,
+ DBusMessage **req_msg_out)
+{
+ DBusMessage *msg;
+ DBusMessageIter args;
+ DBusMessageIter array_iter;
+ char *conf_encoded;
+ bool ok;
+ NTSTATUS status;
+
+ msg = dbus_message_new_method_call("org.opensuse.Snapper",
+ "/org/opensuse/Snapper",
+ "org.opensuse.Snapper",
+ "DeleteSnapshots");
+ if (msg == NULL) {
+ DEBUG(0, ("failed to create req msg\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = snapper_dbus_str_encode(mem_ctx, snapper_conf, &conf_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbus_message_unref(msg);
+ return status;
+ }
+
+ /* append arguments */
+ dbus_message_iter_init_append(msg, &args);
+ ok = dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+ &conf_encoded);
+ if (!ok) {
+ talloc_free(conf_encoded);
+ dbus_message_unref(msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_UINT32_AS_STRING,
+ &array_iter);
+ if (!ok) {
+ talloc_free(conf_encoded);
+ dbus_message_unref(msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_UINT32,
+ &snap_id);
+ if (!ok) {
+ talloc_free(conf_encoded);
+ dbus_message_unref(msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dbus_message_iter_close_container(&args, &array_iter);
+ *req_msg_out = msg;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_del_snap_unpack(DBusConnection *conn,
+ DBusMessage *rsp_msg)
+{
+ int msg_type;
+ const char *sig;
+
+ msg_type = dbus_message_get_type(rsp_msg);
+ if (msg_type == DBUS_MESSAGE_TYPE_ERROR) {
+ const char *err_str = dbus_message_get_error_name(rsp_msg);
+ DEBUG(0, ("del snap error response: %s\n", err_str));
+ return snapper_err_ntstatus_map(err_str);
+ }
+
+ if (msg_type != DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ DEBUG(0, ("unexpected del snap ret type: %d\n",
+ msg_type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sig = dbus_message_get_signature(rsp_msg);
+ if ((sig == NULL)
+ || (strcmp(sig, SNAPPER_SIG_DEL_SNAPS_RSP) != 0)) {
+ DEBUG(0, ("bad create snap response sig: %s, expected: %s\n",
+ (sig ? sig : "NULL"), SNAPPER_SIG_DEL_SNAPS_RSP));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* no parameters in response */
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_list_snaps_at_time_pack(TALLOC_CTX *mem_ctx,
+ const char *snapper_conf,
+ time_t time_lower,
+ time_t time_upper,
+ DBusMessage **req_msg_out)
+{
+ DBusMessage *msg;
+ DBusMessageIter args;
+ char *conf_encoded;
+ NTSTATUS status;
+
+ msg = dbus_message_new_method_call("org.opensuse.Snapper",
+ "/org/opensuse/Snapper",
+ "org.opensuse.Snapper",
+ "ListSnapshotsAtTime");
+ if (msg == NULL) {
+ DEBUG(0, ("failed to create list snaps message\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = snapper_dbus_str_encode(mem_ctx, snapper_conf, &conf_encoded);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbus_message_unref(msg);
+ return status;
+ }
+
+ dbus_message_iter_init_append(msg, &args);
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING,
+ &conf_encoded)) {
+ talloc_free(conf_encoded);
+ dbus_message_unref(msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_INT64,
+ &time_lower)) {
+ talloc_free(conf_encoded);
+ dbus_message_unref(msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_INT64,
+ &time_upper)) {
+ talloc_free(conf_encoded);
+ dbus_message_unref(msg);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *req_msg_out = msg;
+
+ return NT_STATUS_OK;
+}
+/* no snapper_list_snaps_at_time_unpack, use snapper_list_snaps_unpack */
+
+/*
+ * Determine the snapper snapshot id given a path.
+ * Ideally this should be determined via a lookup.
+ */
+static NTSTATUS snapper_snap_path_to_id(TALLOC_CTX *mem_ctx,
+ const char *snap_path,
+ uint32_t *snap_id_out)
+{
+ char *path_dup;
+ char *str_idx;
+ uint32_t snap_id;
+ int error = 0;
+
+ path_dup = talloc_strdup(mem_ctx, snap_path);
+ if (path_dup == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* trim trailing '/' */
+ str_idx = path_dup + strlen(path_dup) - 1;
+ while (*str_idx == '/') {
+ *str_idx = '\0';
+ str_idx--;
+ }
+
+ str_idx = strrchr(path_dup, '/');
+ if ((str_idx == NULL)
+ || (strcmp(str_idx + 1, "snapshot") != 0)) {
+ talloc_free(path_dup);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ while (*str_idx == '/') {
+ *str_idx = '\0';
+ str_idx--;
+ }
+
+ str_idx = strrchr(path_dup, '/');
+ if (str_idx == NULL) {
+ talloc_free(path_dup);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ str_idx++;
+ snap_id = smb_strtoul(str_idx, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ talloc_free(path_dup);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ talloc_free(path_dup);
+ *snap_id_out = snap_id;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Determine the snapper snapshot path given an id and base.
+ * Ideally this should be determined via a lookup.
+ */
+static NTSTATUS snapper_snap_id_to_path(TALLOC_CTX *mem_ctx,
+ const char *base_path,
+ uint32_t snap_id,
+ char **snap_path_out)
+{
+ char *snap_path;
+
+ snap_path = talloc_asprintf(mem_ctx, "%s/.snapshots/%u/snapshot",
+ base_path, snap_id);
+ if (snap_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *snap_path_out = snap_path;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_get_conf_call(TALLOC_CTX *mem_ctx,
+ DBusConnection *dconn,
+ const char *path,
+ char **conf_name_out,
+ char **base_path_out)
+{
+ NTSTATUS status;
+ DBusMessage *req_msg;
+ DBusMessage *rsp_msg;
+ uint32_t num_confs = 0;
+ struct snapper_conf *confs = NULL;
+ struct snapper_conf *conf;
+ char *conf_name;
+ char *base_path;
+
+ status = snapper_list_confs_pack(&req_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_req_free;
+ }
+
+ status = snapper_list_confs_unpack(mem_ctx, dconn, rsp_msg,
+ &num_confs, &confs);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_rsp_free;
+ }
+
+ /*
+ * for now we only support shares where the path directly corresponds
+ * to a snapper configuration.
+ */
+ conf = snapper_conf_array_base_find(num_confs, confs,
+ path);
+ if (conf == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto err_array_free;
+ }
+
+ conf_name = talloc_strdup(mem_ctx, conf->name);
+ if (conf_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_array_free;
+ }
+ base_path = talloc_strdup(mem_ctx, conf->mnt);
+ if (base_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_conf_name_free;
+ }
+
+ talloc_free(confs);
+ dbus_message_unref(rsp_msg);
+ dbus_message_unref(req_msg);
+
+ *conf_name_out = conf_name;
+ *base_path_out = base_path;
+
+ return NT_STATUS_OK;
+
+err_conf_name_free:
+ talloc_free(conf_name);
+err_array_free:
+ talloc_free(confs);
+err_rsp_free:
+ dbus_message_unref(rsp_msg);
+err_req_free:
+ dbus_message_unref(req_msg);
+err_out:
+ return status;
+}
+
+/*
+ * Check whether a path can be shadow copied. Return the base volume, allowing
+ * the caller to determine if multiple paths lie on the same base volume.
+ */
+static NTSTATUS snapper_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ NTSTATUS status;
+ DBusConnection *dconn;
+ char *conf_name;
+ char *base_path;
+
+ dconn = snapper_dbus_conn_create();
+ if (dconn == NULL) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = snapper_get_conf_call(mem_ctx, dconn, service_path,
+ &conf_name, &base_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_conn_close;
+ }
+
+ talloc_free(conf_name);
+ *base_volume = base_path;
+ snapper_dbus_conn_destroy(dconn);
+
+ return NT_STATUS_OK;
+
+err_conn_close:
+ snapper_dbus_conn_destroy(dconn);
+ return status;
+}
+
+static NTSTATUS snapper_create_snap_call(TALLOC_CTX *mem_ctx,
+ DBusConnection *dconn,
+ const char *conf_name,
+ const char *base_path,
+ const char *snap_desc,
+ uint32_t num_user_data,
+ struct snapper_dict *user_data,
+ char **snap_path_out)
+{
+ NTSTATUS status;
+ DBusMessage *req_msg;
+ DBusMessage *rsp_msg;
+ uint32_t snap_id = 0;
+ char *snap_path;
+
+ status = snapper_create_snap_pack(mem_ctx,
+ conf_name,
+ snap_desc,
+ num_user_data,
+ user_data,
+ &req_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_req_free;
+ }
+
+ status = snapper_create_snap_unpack(dconn, rsp_msg, &snap_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_rsp_free;
+ }
+
+ status = snapper_snap_id_to_path(mem_ctx, base_path, snap_id,
+ &snap_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_rsp_free;
+ }
+
+ dbus_message_unref(rsp_msg);
+ dbus_message_unref(req_msg);
+
+ DEBUG(6, ("created new snapshot %u at %s\n", snap_id, snap_path));
+ *snap_path_out = snap_path;
+
+ return NT_STATUS_OK;
+
+err_rsp_free:
+ dbus_message_unref(rsp_msg);
+err_req_free:
+ dbus_message_unref(req_msg);
+err_out:
+ return status;
+}
+
+static NTSTATUS snapper_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **_base_path,
+ char **_snap_path)
+{
+ DBusConnection *dconn;
+ NTSTATUS status;
+ char *conf_name;
+ char *base_path;
+ char *snap_path = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dconn = snapper_dbus_conn_create();
+ if (dconn == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = snapper_get_conf_call(tmp_ctx, dconn, base_volume,
+ &conf_name, &base_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ snapper_dbus_conn_destroy(dconn);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ status = snapper_create_snap_call(tmp_ctx, dconn,
+ conf_name, base_path,
+ "Snapshot created by Samba",
+ 0, NULL,
+ &snap_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ snapper_dbus_conn_destroy(dconn);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ snapper_dbus_conn_destroy(dconn);
+ *_base_path = talloc_steal(mem_ctx, base_path);
+ *_snap_path = talloc_steal(mem_ctx, snap_path);
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS snapper_delete_snap_call(TALLOC_CTX *mem_ctx,
+ DBusConnection *dconn,
+ const char *conf_name,
+ uint32_t snap_id)
+{
+ NTSTATUS status;
+ DBusMessage *req_msg = NULL;
+ DBusMessage *rsp_msg;
+
+ status = snapper_del_snap_pack(mem_ctx, conf_name, snap_id, &req_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_req_free;
+ }
+
+ status = snapper_del_snap_unpack(dconn, rsp_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_rsp_free;
+ }
+
+ dbus_message_unref(rsp_msg);
+ dbus_message_unref(req_msg);
+
+ DEBUG(6, ("deleted snapshot %u\n", snap_id));
+
+ return NT_STATUS_OK;
+
+err_rsp_free:
+ dbus_message_unref(rsp_msg);
+err_req_free:
+ dbus_message_unref(req_msg);
+err_out:
+ return status;
+}
+
+static NTSTATUS snapper_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ DBusConnection *dconn;
+ NTSTATUS status;
+ char *conf_name;
+ char *snap_base_path;
+ uint32_t snap_id;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dconn = snapper_dbus_conn_create();
+ if (dconn == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = snapper_get_conf_call(tmp_ctx, dconn, base_path,
+ &conf_name, &snap_base_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ snapper_dbus_conn_destroy(dconn);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ status = snapper_snap_path_to_id(tmp_ctx, snap_path, &snap_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ snapper_dbus_conn_destroy(dconn);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ status = snapper_delete_snap_call(tmp_ctx, dconn, conf_name, snap_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ snapper_dbus_conn_destroy(dconn);
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ snapper_dbus_conn_destroy(dconn);
+ talloc_free(tmp_ctx);
+
+ return NT_STATUS_OK;
+}
+
+/* sc_data used as parent talloc context for all labels */
+static int snapper_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *sc_data,
+ bool labels)
+{
+ DBusConnection *dconn;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+ char *conf_name;
+ char *base_path;
+ DBusMessage *req_msg = NULL;
+ DBusMessage *rsp_msg;
+ uint32_t num_snaps;
+ struct snapper_snap *snaps;
+ uint32_t i;
+ uint32_t lbl_off;
+
+ tmp_ctx = talloc_new(sc_data);
+ if (tmp_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_out;
+ }
+
+ dconn = snapper_dbus_conn_create();
+ if (dconn == NULL) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_mem_ctx_free;
+ }
+
+ if (fsp->conn->connectpath == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_conn_free;
+ }
+
+ status = snapper_get_conf_call(tmp_ctx, dconn,
+ fsp->conn->connectpath,
+ &conf_name,
+ &base_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_conn_free;
+ }
+
+ status = snapper_list_snaps_pack(tmp_ctx, conf_name, &req_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_conn_free;
+ }
+
+ status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_req_free;
+ }
+
+ status = snapper_list_snaps_unpack(tmp_ctx, rsp_msg,
+ &num_snaps, &snaps);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_rsp_free;
+ }
+ /* we should always get at least one snapshot (current) */
+ if (num_snaps == 0) {
+ DEBUG(1, ("zero snapshots in snap list response\n"));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_rsp_free;
+ }
+
+ /* subtract 1, (current) snapshot is not returned */
+ sc_data->num_volumes = num_snaps - 1;
+ sc_data->labels = NULL;
+
+ if ((labels == false) || (sc_data->num_volumes == 0)) {
+ /* tokens need not be added to the labels array */
+ goto done;
+ }
+
+ sc_data->labels = talloc_array(sc_data, SHADOW_COPY_LABEL,
+ sc_data->num_volumes);
+ if (sc_data->labels == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_rsp_free;
+ }
+
+ /* start at end for descending order, do not include 0 (current) */
+ lbl_off = 0;
+ for (i = num_snaps - 1; i > 0; i--) {
+ char *lbl = sc_data->labels[lbl_off++];
+ struct tm gmt_snap_time;
+ struct tm *tm_ret;
+ size_t str_sz;
+
+ tm_ret = gmtime_r((time_t *)&snaps[i].time, &gmt_snap_time);
+ if (tm_ret == NULL) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_labels_free;
+ }
+ str_sz = strftime(lbl, sizeof(SHADOW_COPY_LABEL),
+ "@GMT-%Y.%m.%d-%H.%M.%S", &gmt_snap_time);
+ if (str_sz == 0) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_labels_free;
+ }
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ dbus_message_unref(rsp_msg);
+ dbus_message_unref(req_msg);
+ snapper_dbus_conn_destroy(dconn);
+
+ return 0;
+
+err_labels_free:
+ TALLOC_FREE(sc_data->labels);
+err_rsp_free:
+ dbus_message_unref(rsp_msg);
+err_req_free:
+ dbus_message_unref(req_msg);
+err_conn_free:
+ snapper_dbus_conn_destroy(dconn);
+err_mem_ctx_free:
+ talloc_free(tmp_ctx);
+err_out:
+ errno = map_errno_from_nt_status(status);
+ return -1;
+}
+
+static bool snapper_gmt_strip_snapshot(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ time_t *ptimestamp,
+ char **pstripped)
+{
+ char *stripped;
+
+ if (smb_fname->twrp == 0) {
+ goto no_snapshot;
+ }
+
+ if (pstripped != NULL) {
+ stripped = talloc_strdup(mem_ctx, smb_fname->base_name);
+ if (stripped == NULL) {
+ return false;
+ }
+ *pstripped = stripped;
+ }
+
+ *ptimestamp = nt_time_to_unix(smb_fname->twrp);
+ return true;
+no_snapshot:
+ *ptimestamp = 0;
+ return true;
+}
+
+static NTSTATUS snapper_get_snap_at_time_call(TALLOC_CTX *mem_ctx,
+ DBusConnection *dconn,
+ const char *conf_name,
+ const char *base_path,
+ time_t snaptime,
+ char **snap_path_out)
+{
+ NTSTATUS status;
+ DBusMessage *req_msg = NULL;
+ DBusMessage *rsp_msg;
+ uint32_t num_snaps;
+ struct snapper_snap *snaps;
+ char *snap_path;
+
+ status = snapper_list_snaps_at_time_pack(mem_ctx,
+ conf_name,
+ snaptime,
+ snaptime,
+ &req_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = snapper_dbus_msg_xchng(dconn, req_msg, &rsp_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_req_free;
+ }
+
+ status = snapper_list_snaps_unpack(mem_ctx, rsp_msg,
+ &num_snaps, &snaps);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_rsp_free;
+ }
+
+ if (num_snaps == 0) {
+ DEBUG(4, ("no snapshots found with time: %lu\n",
+ (unsigned long)snaptime));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_snap_array_free;
+ } else if (num_snaps > 0) {
+ DEBUG(4, ("got %u snapshots for single time %lu, using top\n",
+ num_snaps, (unsigned long)snaptime));
+ }
+
+ status = snapper_snap_id_to_path(mem_ctx, base_path, snaps[0].id,
+ &snap_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_snap_array_free;
+ }
+
+ *snap_path_out = snap_path;
+err_snap_array_free:
+ talloc_free(snaps);
+err_rsp_free:
+ dbus_message_unref(rsp_msg);
+err_req_free:
+ dbus_message_unref(req_msg);
+err_out:
+ return status;
+}
+
+static NTSTATUS snapper_snap_path_expand(struct connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ time_t snap_time,
+ char **snap_dir_out)
+{
+ DBusConnection *dconn;
+ NTSTATUS status;
+ char *conf_name;
+ char *base_path;
+ char *snap_path = NULL;
+
+ dconn = snapper_dbus_conn_create();
+ if (dconn == NULL) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_out;
+ }
+
+ if (conn->connectpath == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_conn_free;
+ }
+
+ status = snapper_get_conf_call(mem_ctx, dconn,
+ conn->connectpath,
+ &conf_name,
+ &base_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_conn_free;
+ }
+
+ status = snapper_get_snap_at_time_call(mem_ctx, dconn,
+ conf_name, base_path, snap_time,
+ &snap_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_conf_name_free;
+ }
+
+ /* confirm snapshot path is nested under base path */
+ if (strncmp(snap_path, base_path, strlen(base_path)) != 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_snap_path_free;
+ }
+
+ talloc_free(conf_name);
+ talloc_free(base_path);
+ snapper_dbus_conn_destroy(dconn);
+ *snap_dir_out = snap_path;
+
+ return NT_STATUS_OK;
+
+err_snap_path_free:
+ talloc_free(snap_path);
+err_conf_name_free:
+ talloc_free(conf_name);
+ talloc_free(base_path);
+err_conn_free:
+ snapper_dbus_conn_destroy(dconn);
+err_out:
+ return status;
+}
+
+static char *snapper_gmt_convert(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ const char *name, time_t timestamp)
+{
+ char *snap_path = NULL;
+ char *path = NULL;
+ NTSTATUS status;
+ int saved_errno;
+
+ status = snapper_snap_path_expand(handle->conn, mem_ctx, timestamp,
+ &snap_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto err_out;
+ }
+
+ path = talloc_asprintf(mem_ctx, "%s/%s", snap_path, name);
+ if (path == NULL) {
+ errno = ENOMEM;
+ goto err_snap_path_free;
+ }
+
+ DEBUG(10, ("converted %s/%s @ time to %s\n",
+ handle->conn->connectpath, name, path));
+ return path;
+
+err_snap_path_free:
+ saved_errno = errno;
+ talloc_free(snap_path);
+ errno = saved_errno;
+err_out:
+ return NULL;
+}
+
+static int snapper_gmt_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ time_t timestamp_src, timestamp_dst;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname_src,
+ &timestamp_src, NULL)) {
+ return -1;
+ }
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname_dst,
+ &timestamp_dst, NULL)) {
+ return -1;
+ }
+ if (timestamp_src != 0) {
+ errno = EXDEV;
+ return -1;
+ }
+ if (timestamp_dst != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+}
+
+static int snapper_gmt_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ time_t timestamp_old = 0;
+ time_t timestamp_new = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ link_contents,
+ &timestamp_old,
+ NULL)) {
+ return -1;
+ }
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_new,
+ NULL)) {
+ return -1;
+ }
+ if ((timestamp_old != 0) || (timestamp_new != 0)) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_SYMLINKAT(handle,
+ link_contents,
+ dirfsp,
+ new_smb_fname);
+}
+
+static int snapper_gmt_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ time_t timestamp_old = 0;
+ time_t timestamp_new = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ old_smb_fname,
+ &timestamp_old,
+ NULL)) {
+ return -1;
+ }
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ new_smb_fname,
+ &timestamp_new,
+ NULL)) {
+ return -1;
+ }
+ if ((timestamp_old != 0) || (timestamp_new != 0)) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+}
+
+static int snapper_gmt_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ time_t timestamp;
+ char *stripped, *tmp;
+ int ret, saved_errno;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, &stripped)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_STAT(handle, smb_fname);
+ }
+
+ tmp = smb_fname->base_name;
+ smb_fname->base_name = snapper_gmt_convert(talloc_tos(), handle,
+ stripped, timestamp);
+ TALLOC_FREE(stripped);
+
+ if (smb_fname->base_name == NULL) {
+ smb_fname->base_name = tmp;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ saved_errno = errno;
+
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = tmp;
+
+ errno = saved_errno;
+ return ret;
+}
+
+static int snapper_gmt_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ time_t timestamp;
+ char *stripped, *tmp;
+ int ret, saved_errno;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, &stripped)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+
+ tmp = smb_fname->base_name;
+ smb_fname->base_name = snapper_gmt_convert(talloc_tos(), handle,
+ stripped, timestamp);
+ TALLOC_FREE(stripped);
+
+ if (smb_fname->base_name == NULL) {
+ smb_fname->base_name = tmp;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ saved_errno = errno;
+
+ TALLOC_FREE(smb_fname->base_name);
+ smb_fname->base_name = tmp;
+
+ errno = saved_errno;
+ return ret;
+}
+
+static int snapper_gmt_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ struct smb_filename *smb_fname = NULL;
+ time_t timestamp;
+ char *stripped = NULL;
+ int ret;
+ int saved_errno = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname_in,
+ &timestamp, &stripped)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname_in,
+ fsp,
+ how);
+ }
+
+ smb_fname = cp_smb_filename(talloc_tos(), smb_fname_in);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(stripped);
+ return -1;
+ }
+
+ smb_fname->base_name = snapper_gmt_convert(smb_fname, handle,
+ stripped, timestamp);
+ TALLOC_FREE(stripped);
+
+ if (smb_fname->base_name == NULL) {
+ TALLOC_FREE(smb_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int snapper_gmt_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ time_t timestamp = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+}
+
+static int snapper_gmt_fchmod(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ mode_t mode)
+{
+ time_t timestamp = 0;
+ const struct smb_filename *smb_fname = NULL;
+
+ smb_fname = fsp->fsp_name;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ NULL)) {
+ return -1;
+ }
+
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+}
+
+static int snapper_gmt_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ int ret;
+ int saved_errno = 0;
+ char *conv = NULL;
+ struct smb_filename *conv_smb_fname = NULL;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ &stripped)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+ }
+ conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (conv == NULL) {
+ return -1;
+ }
+ conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+ conv,
+ NULL,
+ NULL,
+ 0,
+ smb_fname->flags);
+ if (conv_smb_fname == NULL) {
+ TALLOC_FREE(conv);
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_CHDIR(handle, conv_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv);
+ TALLOC_FREE(conv_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int snapper_gmt_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ time_t timestamp = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ fsp->fsp_name,
+ &timestamp,
+ NULL)) {
+ return -1;
+ }
+
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+}
+
+static int snapper_gmt_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ time_t timestamp = 0;
+ int ret;
+ int saved_errno = 0;
+ struct smb_filename *full_fname = NULL;
+
+ /*
+ * Now this function only looks at smb_fname->twrp
+ * we don't need to copy out the path. Just use
+ * smb_fname->base_name directly.
+ */
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, NULL)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_READLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+ }
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ /* Find the snapshot path from the full pathname. */
+ full_fname->base_name = snapper_gmt_convert(full_fname,
+ handle,
+ full_fname->base_name,
+ timestamp);
+ if (full_fname->base_name == NULL) {
+ TALLOC_FREE(full_fname);
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_READLINKAT(handle,
+ handle->conn->cwd_fsp,
+ full_fname,
+ buf,
+ bufsiz);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(full_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int snapper_gmt_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ time_t timestamp = (time_t)0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_MKNODAT(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+}
+
+static struct smb_filename *snapper_gmt_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ struct smb_filename *result_fname = NULL;
+ struct smb_filename *conv_smb_fname = NULL;
+ int saved_errno = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname,
+ &timestamp, &stripped)) {
+ goto done;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname);
+ }
+
+ conv_smb_fname = cp_smb_filename(talloc_tos(), smb_fname);
+ if (conv_smb_fname == NULL) {
+ goto done;
+ }
+ conv_smb_fname->base_name = snapper_gmt_convert(conv_smb_fname, handle,
+ stripped, timestamp);
+ if (conv_smb_fname->base_name == NULL) {
+ goto done;
+ }
+
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, conv_smb_fname);
+
+done:
+ if (result_fname == NULL) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv_smb_fname);
+ TALLOC_FREE(stripped);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return result_fname;
+}
+
+static int snapper_gmt_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *fname,
+ mode_t mode)
+{
+ time_t timestamp = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle, fname,
+ &timestamp, NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ fname,
+ mode);
+}
+
+static int snapper_gmt_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ time_t timestamp = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ fsp->fsp_name, &timestamp, NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
+}
+
+static int snapper_gmt_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *aname, const void *value,
+ size_t size, int flags)
+{
+ time_t timestamp = 0;
+ const struct smb_filename *smb_fname = NULL;
+
+ smb_fname = fsp->fsp_name;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ NULL)) {
+ return -1;
+ }
+ if (timestamp != 0) {
+ errno = EROFS;
+ return -1;
+ }
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp,
+ aname, value, size, flags);
+}
+
+static NTSTATUS snapper_gmt_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ time_t timestamp;
+ char *stripped;
+ char *conv;
+ struct smb_filename *conv_fname = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ ok = snapper_gmt_strip_snapshot(
+ talloc_tos(), handle, dirfsp->fsp_name,&timestamp, &stripped);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, dirfsp, name, mem_ctx, found_name);
+ }
+ if (stripped[0] == '\0') {
+ *found_name = talloc_strdup(mem_ctx, name);
+ if (*found_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+ }
+ conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (conv == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ status = synthetic_pathref(
+ talloc_tos(),
+ dirfsp->conn->cwd_fsp,
+ conv,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &conv_fname);
+
+ status = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, conv_fname->fsp, name, mem_ctx, found_name);
+ TALLOC_FREE(conv);
+ return status;
+}
+
+static uint64_t snapper_gmt_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ uint64_t ret;
+ int saved_errno = 0;
+ char *conv = NULL;
+ struct smb_filename *conv_smb_fname = NULL;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname, &timestamp, &stripped)) {
+ return (uint64_t)-1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_DISK_FREE(handle, smb_fname,
+ bsize, dfree, dsize);
+ }
+
+ conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (conv == NULL) {
+ return (uint64_t)-1;
+ }
+ conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+ conv,
+ NULL,
+ NULL,
+ 0,
+ smb_fname->flags);
+ if (conv_smb_fname == NULL) {
+ TALLOC_FREE(conv);
+ errno = ENOMEM;
+ return (uint64_t)-1;
+ }
+
+ ret = SMB_VFS_NEXT_DISK_FREE(handle, conv_smb_fname,
+ bsize, dfree, dsize);
+
+ if (ret == (uint64_t)-1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static int snapper_gmt_get_quota(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *dq)
+{
+ time_t timestamp = 0;
+ char *stripped = NULL;
+ int ret;
+ int saved_errno = 0;
+ char *conv = NULL;
+ struct smb_filename *conv_smb_fname = NULL;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(), handle,
+ smb_fname, &timestamp, &stripped)) {
+ return -1;
+ }
+ if (timestamp == 0) {
+ return SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, dq);
+ }
+
+ conv = snapper_gmt_convert(talloc_tos(), handle, stripped, timestamp);
+ TALLOC_FREE(stripped);
+ if (conv == NULL) {
+ return -1;
+ }
+ conv_smb_fname = synthetic_smb_fname(talloc_tos(),
+ conv,
+ NULL,
+ NULL,
+ 0,
+ smb_fname->flags);
+ TALLOC_FREE(conv);
+ if (conv_smb_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_GET_QUOTA(handle, conv_smb_fname, qtype, id, dq);
+
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(conv_smb_fname);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+static NTSTATUS snapper_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ time_t timestamp = 0;
+
+ if (!snapper_gmt_strip_snapshot(talloc_tos(),
+ handle,
+ smb_fname,
+ &timestamp,
+ NULL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (timestamp != 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ return SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle,
+ dirfsp,
+ smb_fname,
+ reflist,
+ referral_count);
+}
+
+static struct vfs_fn_pointers snapper_fns = {
+ .snap_check_path_fn = snapper_snap_check_path,
+ .snap_create_fn = snapper_snap_create,
+ .snap_delete_fn = snapper_snap_delete,
+ .get_shadow_copy_data_fn = snapper_get_shadow_copy_data,
+ .create_dfs_pathat_fn = snapper_create_dfs_pathat,
+ .disk_free_fn = snapper_gmt_disk_free,
+ .get_quota_fn = snapper_gmt_get_quota,
+ .renameat_fn = snapper_gmt_renameat,
+ .linkat_fn = snapper_gmt_linkat,
+ .symlinkat_fn = snapper_gmt_symlinkat,
+ .stat_fn = snapper_gmt_stat,
+ .lstat_fn = snapper_gmt_lstat,
+ .openat_fn = snapper_gmt_openat,
+ .unlinkat_fn = snapper_gmt_unlinkat,
+ .fchmod_fn = snapper_gmt_fchmod,
+ .chdir_fn = snapper_gmt_chdir,
+ .fntimes_fn = snapper_gmt_fntimes,
+ .readlinkat_fn = snapper_gmt_readlinkat,
+ .mknodat_fn = snapper_gmt_mknodat,
+ .realpath_fn = snapper_gmt_realpath,
+ .mkdirat_fn = snapper_gmt_mkdirat,
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fsetxattr_fn = snapper_gmt_fsetxattr,
+ .fchflags_fn = snapper_gmt_fchflags,
+ .get_real_filename_at_fn = snapper_gmt_get_real_filename_at,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_snapper_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "snapper", &snapper_fns);
+}
diff --git a/source3/modules/vfs_solarisacl.c b/source3/modules/vfs_solarisacl.c
new file mode 100644
index 0000000..d31bda5
--- /dev/null
+++ b/source3/modules/vfs_solarisacl.c
@@ -0,0 +1,699 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set Solaris ACLs
+ Copyright (C) Michael Adam 2006,2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "modules/vfs_solarisacl.h"
+
+/* typedef struct acl SOLARIS_ACE_T; */
+typedef aclent_t SOLARIS_ACE_T;
+typedef aclent_t *SOLARIS_ACL_T;
+typedef int SOLARIS_ACL_TAG_T; /* the type of an ACL entry */
+typedef o_mode_t SOLARIS_PERM_T;
+
+/* for convenience: check if solaris acl entry is a default entry? */
+#define _IS_DEFAULT(ace) ((ace).a_type & ACL_DEFAULT)
+#define _IS_OF_TYPE(ace, type) ( \
+ (((type) == SMB_ACL_TYPE_ACCESS) && !_IS_DEFAULT(ace)) \
+ || \
+ (((type) == SMB_ACL_TYPE_DEFAULT) && _IS_DEFAULT(ace)) \
+)
+
+
+/* prototypes for private functions */
+
+static SOLARIS_ACL_T solaris_acl_init(int count);
+static bool smb_acl_to_solaris_acl(SMB_ACL_T smb_acl,
+ SOLARIS_ACL_T *solariacl, int *count,
+ SMB_ACL_TYPE_T type);
+static SMB_ACL_T solaris_acl_to_smb_acl(SOLARIS_ACL_T solarisacl, int count,
+ SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx);
+static SOLARIS_ACL_TAG_T smb_tag_to_solaris_tag(SMB_ACL_TAG_T smb_tag);
+static SMB_ACL_TAG_T solaris_tag_to_smb_tag(SOLARIS_ACL_TAG_T solaris_tag);
+static bool solaris_add_to_acl(SOLARIS_ACL_T *solaris_acl, int *count,
+ SOLARIS_ACL_T add_acl, int add_count, SMB_ACL_TYPE_T type);
+static bool solaris_acl_get_file(const char *name, SOLARIS_ACL_T *solarisacl,
+ int *count);
+static bool solaris_acl_get_fd(int fd, SOLARIS_ACL_T *solarisacl, int *count);
+static bool solaris_acl_sort(SOLARIS_ACL_T theacl, int count);
+static SMB_ACL_PERM_T solaris_perm_to_smb_perm(const SOLARIS_PERM_T perm);
+static SOLARIS_PERM_T smb_perm_to_solaris_perm(const SMB_ACL_PERM_T perm);
+#if 0
+static bool solaris_acl_check(SOLARIS_ACL_T solaris_acl, int count);
+#endif
+
+/* public functions - the api */
+
+static SMB_ACL_T solarisacl_sys_acl_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result = NULL;
+ int count;
+ SOLARIS_ACL_T solaris_acl = NULL;
+ const char *path_p = smb_fname->base_name;
+
+ DEBUG(10, ("solarisacl_sys_acl_get_file called for file '%s'.\n",
+ path_p));
+
+ if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) {
+ DEBUG(10, ("invalid SMB_ACL_TYPE given (%d)\n", type));
+ errno = EINVAL;
+ goto done;
+ }
+
+ DEBUGADD(10, ("getting %s acl\n",
+ ((type == SMB_ACL_TYPE_ACCESS) ? "access" : "default")));
+
+ if (!solaris_acl_get_file(path_p, &solaris_acl, &count)) {
+ goto done;
+ }
+ result = solaris_acl_to_smb_acl(solaris_acl, count, type, mem_ctx);
+ if (result == NULL) {
+ DEBUG(10, ("conversion solaris_acl -> smb_acl failed (%s).\n",
+ strerror(errno)));
+ }
+
+ done:
+ DEBUG(10, ("solarisacl_sys_acl_get_file %s.\n",
+ ((result == NULL) ? "failed" : "succeeded" )));
+ SAFE_FREE(solaris_acl);
+ return result;
+}
+
+
+/*
+ * get the access ACL of a file referred to by a fd
+ */
+SMB_ACL_T solarisacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result = NULL;
+ int count;
+ SOLARIS_ACL_T solaris_acl = NULL;
+
+ DEBUG(10, ("entering solarisacl_sys_acl_get_fd.\n"));
+
+ if (!solaris_acl_get_fd(fsp_get_io_fd(fsp), &solaris_acl, &count)) {
+ goto done;
+ }
+
+ if (type != SMB_ACL_TYPE_ACCESS && type != SMB_ACL_TYPE_DEFAULT) {
+ DEBUG(10, ("invalid SMB_ACL_TYPE given (%d)\n", type));
+ errno = EINVAL;
+ goto done;
+ }
+ /*
+ * The facl call returns both ACCESS and DEFAULT acls (as present).
+ * The posix acl_get_fd function returns only the
+ * access acl. So we need to filter this out here.
+ */
+ result = solaris_acl_to_smb_acl(solaris_acl, count,
+ type, mem_ctx);
+ if (result == NULL) {
+ DEBUG(10, ("conversion solaris_acl -> smb_acl failed (%s).\n",
+ strerror(errno)));
+ }
+
+ done:
+ DEBUG(10, ("solarisacl_sys_acl_get_fd %s.\n",
+ ((result == NULL) ? "failed" : "succeeded")));
+ SAFE_FREE(solaris_acl);
+ return result;
+}
+
+/*
+ * set the access ACL on the file referred to by a fd
+ */
+int solarisacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ SOLARIS_ACL_T solaris_acl = NULL;
+ int count;
+ SOLARIS_ACL_T other_acl = NULL;
+ int other_count;
+ SMB_ACL_TYPE_T other_type;
+ int ret = -1;
+
+ DEBUG(10, ("entering solarisacl_sys_acl_set_fd\n"));
+
+ /*
+ * the posix acl_set_fd call sets the access acl of the
+ * file referred to by fd. the solaris facl-SETACL call
+ * sets the access and default acl as provided, so we
+ * have to retrieve the default acl of the file and
+ * concatenate it with the access acl provided.
+ */
+ if (!smb_acl_to_solaris_acl(theacl, &solaris_acl, &count,
+ type))
+ {
+ DEBUG(10, ("conversion smb_acl -> solaris_acl failed (%s).\n",
+ strerror(errno)));
+ goto done;
+ }
+ if (!solaris_acl_get_fd(fsp_get_io_fd(fsp), &other_acl, &other_count)) {
+ DEBUG(10, ("error getting (default) acl from fd\n"));
+ goto done;
+ }
+
+ other_type = (type == SMB_ACL_TYPE_ACCESS)
+ ? SMB_ACL_TYPE_DEFAULT
+ : SMB_ACL_TYPE_ACCESS;
+
+ if (!solaris_add_to_acl(&solaris_acl, &count,
+ other_acl, other_count,
+ other_type))
+ {
+ DEBUG(10, ("error adding default acl to solaris acl\n"));
+ goto done;
+ }
+ if (!solaris_acl_sort(solaris_acl, count)) {
+ DEBUG(10, ("resulting acl is not valid!\n"));
+ goto done;
+ }
+
+ ret = facl(fsp_get_io_fd(fsp), SETACL, count, solaris_acl);
+ if (ret != 0) {
+ DEBUG(10, ("call of facl failed (%s).\n", strerror(errno)));
+ }
+
+ done:
+ DEBUG(10, ("solarisacl_sys_acl_set_fd %s.\n",
+ ((ret == 0) ? "succeeded" : "failed" )));
+ SAFE_FREE(solaris_acl);
+ SAFE_FREE(other_acl);
+ return ret;
+}
+
+/*
+ * delete the default ACL of a directory
+ *
+ * This is achieved by fetching the access ACL and rewriting it
+ * directly, via the solaris system call: the SETACL call on
+ * directories writes both the access and the default ACL as provided.
+ *
+ * XXX: posix acl_delete_def_file returns an error if
+ * the file referred to by path is not a directory.
+ * this function does not complain but the actions
+ * have no effect on a file other than a directory.
+ * But sys_acl_delete_default_file is only called in
+ * smbd/posixacls.c after having checked that the file
+ * is a directory, anyways. So implementing the extra
+ * check is considered unnecessary. --- Agreed? XXX
+ */
+int solarisacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ SMB_ACL_T smb_acl;
+ int ret = -1;
+ SOLARIS_ACL_T solaris_acl = NULL;
+ int count;
+
+ DBG_DEBUG("entering solarisacl_sys_acl_delete_def_fd.\n");
+
+ smb_acl = solarisacl_sys_acl_get_file(handle, fsp->fsp_name->base_name,
+ SMB_ACL_TYPE_ACCESS, talloc_tos());
+ if (smb_acl == NULL) {
+ DBG_DEBUG("getting file acl failed!\n");
+ goto done;
+ }
+ if (!smb_acl_to_solaris_acl(smb_acl, &solaris_acl, &count,
+ SMB_ACL_TYPE_ACCESS))
+ {
+ DBG_DEBUG("conversion smb_acl -> solaris_acl failed.\n");
+ goto done;
+ }
+ if (!solaris_acl_sort(solaris_acl, count)) {
+ DBG_DEBUG("resulting acl is not valid!\n");
+ goto done;
+ }
+ ret = acl(fsp->fsp_name->base_name, SETACL, count, solaris_acl);
+ if (ret != 0) {
+ DBG_DEBG("settinge file acl failed!\n");
+ }
+
+ done:
+ DBG_DEBUG("solarisacl_sys_acl_delete_def_fd %s.\n",
+ ((ret != 0) ? "failed" : "succeeded" ));
+ TALLOC_FREE(smb_acl);
+ return ret;
+}
+
+/* private functions */
+
+static SOLARIS_ACL_T solaris_acl_init(int count)
+{
+ SOLARIS_ACL_T solaris_acl =
+ (SOLARIS_ACL_T)SMB_MALLOC(sizeof(aclent_t) * count);
+ if (solaris_acl == NULL) {
+ errno = ENOMEM;
+ }
+ return solaris_acl;
+}
+
+/*
+ * Convert the SMB acl to the ACCESS or DEFAULT part of a
+ * solaris ACL, as desired.
+ */
+static bool smb_acl_to_solaris_acl(SMB_ACL_T smb_acl,
+ SOLARIS_ACL_T *solaris_acl, int *count,
+ SMB_ACL_TYPE_T type)
+{
+ bool ret = False;
+ int i;
+
+ DEBUG(10, ("entering smb_acl_to_solaris_acl\n"));
+
+ *solaris_acl = NULL;
+ *count = 0;
+
+ for (i = 0; i < smb_acl->count; i++) {
+ const struct smb_acl_entry *smb_entry = &(smb_acl->acl[i]);
+ SOLARIS_ACE_T solaris_entry;
+
+ ZERO_STRUCT(solaris_entry);
+
+ solaris_entry.a_type = smb_tag_to_solaris_tag(smb_entry->a_type);
+ if (solaris_entry.a_type == 0) {
+ DEBUG(10, ("smb_tag to solaris_tag failed\n"));
+ goto fail;
+ }
+ switch(solaris_entry.a_type) {
+ case USER:
+ DEBUG(10, ("got tag type USER with uid %u\n",
+ (unsigned int)smb_entry->info.user.uid));
+ solaris_entry.a_id = (uid_t)smb_entry->info.user.uid;
+ break;
+ case GROUP:
+ DEBUG(10, ("got tag type GROUP with gid %u\n",
+ (unsigned int)smb_entry->info.group.gid));
+ solaris_entry.a_id = (uid_t)smb_entry->info.group.gid;
+ break;
+ default:
+ break;
+ }
+ if (type == SMB_ACL_TYPE_DEFAULT) {
+ DEBUG(10, ("adding default bit to solaris ace\n"));
+ solaris_entry.a_type |= ACL_DEFAULT;
+ }
+
+ solaris_entry.a_perm =
+ smb_perm_to_solaris_perm(smb_entry->a_perm);
+ DEBUG(10, ("assembled the following solaris ace:\n"));
+ DEBUGADD(10, (" - type: 0x%04x\n", solaris_entry.a_type));
+ DEBUGADD(10, (" - id: %u\n", (unsigned int)solaris_entry.a_id));
+ DEBUGADD(10, (" - perm: o%o\n", solaris_entry.a_perm));
+ if (!solaris_add_to_acl(solaris_acl, count, &solaris_entry,
+ 1, type))
+ {
+ DEBUG(10, ("error adding acl entry\n"));
+ goto fail;
+ }
+ DEBUG(10, ("count after adding: %d (i: %d)\n", *count, i));
+ DEBUG(10, ("test, if entry has been copied into acl:\n"));
+ DEBUGADD(10, (" - type: 0x%04x\n",
+ (*solaris_acl)[(*count)-1].a_type));
+ DEBUGADD(10, (" - id: %u\n",
+ (unsigned int)(*solaris_acl)[(*count)-1].a_id));
+ DEBUGADD(10, (" - perm: o%o\n",
+ (*solaris_acl)[(*count)-1].a_perm));
+ }
+
+ ret = True;
+ goto done;
+
+ fail:
+ SAFE_FREE(*solaris_acl);
+ done:
+ DEBUG(10, ("smb_acl_to_solaris_acl %s\n",
+ ((ret == True) ? "succeeded" : "failed")));
+ return ret;
+}
+
+/*
+ * convert either the access or the default part of a
+ * soaris acl to the SMB_ACL format.
+ */
+static SMB_ACL_T solaris_acl_to_smb_acl(SOLARIS_ACL_T solaris_acl, int count,
+ SMB_ACL_TYPE_T type, TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result;
+ int i;
+
+ if ((result = sys_acl_init(mem_ctx)) == NULL) {
+ DEBUG(10, ("error allocating memory for SMB_ACL\n"));
+ goto fail;
+ }
+ for (i = 0; i < count; i++) {
+ SMB_ACL_ENTRY_T smb_entry;
+ SMB_ACL_PERM_T smb_perm;
+
+ if (!_IS_OF_TYPE(solaris_acl[i], type)) {
+ continue;
+ }
+ result->acl = talloc_realloc(result, result->acl, struct smb_acl_entry, result->count + 1);
+ if (result->acl == NULL) {
+ DEBUG(10, ("error reallocating memory for SMB_ACL\n"));
+ goto fail;
+ }
+ smb_entry = &result->acl[result->count];
+ if (sys_acl_set_tag_type(smb_entry,
+ solaris_tag_to_smb_tag(solaris_acl[i].a_type)) != 0)
+ {
+ DEBUG(10, ("invalid tag type given: 0x%04x\n",
+ solaris_acl[i].a_type));
+ goto fail;
+ }
+ /* intentionally not checking return code here: */
+ sys_acl_set_qualifier(smb_entry, (void *)&solaris_acl[i].a_id);
+ smb_perm = solaris_perm_to_smb_perm(solaris_acl[i].a_perm);
+ if (sys_acl_set_permset(smb_entry, &smb_perm) != 0) {
+ DEBUG(10, ("invalid permset given: %d\n",
+ solaris_acl[i].a_perm));
+ goto fail;
+ }
+ result->count += 1;
+ }
+ goto done;
+
+ fail:
+ TALLOC_FREE(result);
+ done:
+ DEBUG(10, ("solaris_acl_to_smb_acl %s\n",
+ ((result == NULL) ? "failed" : "succeeded")));
+ return result;
+}
+
+
+
+static SOLARIS_ACL_TAG_T smb_tag_to_solaris_tag(SMB_ACL_TAG_T smb_tag)
+{
+ SOLARIS_ACL_TAG_T solaris_tag = 0;
+
+ DEBUG(10, ("smb_tag_to_solaris_tag\n"));
+ DEBUGADD(10, (" --> got smb tag 0x%04x\n", smb_tag));
+
+ switch (smb_tag) {
+ case SMB_ACL_USER:
+ solaris_tag = USER;
+ break;
+ case SMB_ACL_USER_OBJ:
+ solaris_tag = USER_OBJ;
+ break;
+ case SMB_ACL_GROUP:
+ solaris_tag = GROUP;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ solaris_tag = GROUP_OBJ;
+ break;
+ case SMB_ACL_OTHER:
+ solaris_tag = OTHER_OBJ;
+ break;
+ case SMB_ACL_MASK:
+ solaris_tag = CLASS_OBJ;
+ break;
+ default:
+ DEBUGADD(10, (" !!! unknown smb tag type 0x%04x\n", smb_tag));
+ break;
+ }
+
+ DEBUGADD(10, (" --> determined solaris tag 0x%04x\n", solaris_tag));
+
+ return solaris_tag;
+}
+
+static SMB_ACL_TAG_T solaris_tag_to_smb_tag(SOLARIS_ACL_TAG_T solaris_tag)
+{
+ SMB_ACL_TAG_T smb_tag = 0;
+
+ DEBUG(10, ("solaris_tag_to_smb_tag:\n"));
+ DEBUGADD(10, (" --> got solaris tag 0x%04x\n", solaris_tag));
+
+ solaris_tag &= ~ACL_DEFAULT;
+
+ switch (solaris_tag) {
+ case USER:
+ smb_tag = SMB_ACL_USER;
+ break;
+ case USER_OBJ:
+ smb_tag = SMB_ACL_USER_OBJ;
+ break;
+ case GROUP:
+ smb_tag = SMB_ACL_GROUP;
+ break;
+ case GROUP_OBJ:
+ smb_tag = SMB_ACL_GROUP_OBJ;
+ break;
+ case OTHER_OBJ:
+ smb_tag = SMB_ACL_OTHER;
+ break;
+ case CLASS_OBJ:
+ smb_tag = SMB_ACL_MASK;
+ break;
+ default:
+ DEBUGADD(10, (" !!! unknown solaris tag type: 0x%04x\n",
+ solaris_tag));
+ break;
+ }
+
+ DEBUGADD(10, (" --> determined smb tag 0x%04x\n", smb_tag));
+
+ return smb_tag;
+}
+
+
+static SMB_ACL_PERM_T solaris_perm_to_smb_perm(const SOLARIS_PERM_T perm)
+{
+ SMB_ACL_PERM_T smb_perm = 0;
+ smb_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0);
+ smb_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0);
+ smb_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
+ return smb_perm;
+}
+
+
+static SOLARIS_PERM_T smb_perm_to_solaris_perm(const SMB_ACL_PERM_T perm)
+{
+ SOLARIS_PERM_T solaris_perm = 0;
+ solaris_perm |= ((perm & SMB_ACL_READ) ? SMB_ACL_READ : 0);
+ solaris_perm |= ((perm & SMB_ACL_WRITE) ? SMB_ACL_WRITE : 0);
+ solaris_perm |= ((perm & SMB_ACL_EXECUTE) ? SMB_ACL_EXECUTE : 0);
+ return solaris_perm;
+}
+
+
+static bool solaris_acl_get_file(const char *name, SOLARIS_ACL_T *solaris_acl,
+ int *count)
+{
+ bool result = False;
+
+ DEBUG(10, ("solaris_acl_get_file called for file '%s'\n", name));
+
+ /*
+ * The original code tries some INITIAL_ACL_SIZE
+ * and only did the GETACLCNT call upon failure
+ * (for performance reasons).
+ * For the sake of simplicity, I skip this for now.
+ */
+ *count = acl(name, GETACLCNT, 0, NULL);
+ if (*count < 0) {
+ DEBUG(10, ("acl GETACLCNT failed: %s\n", strerror(errno)));
+ goto done;
+ }
+ *solaris_acl = solaris_acl_init(*count);
+ if (*solaris_acl == NULL) {
+ DEBUG(10, ("error allocating memory for solaris acl...\n"));
+ goto done;
+ }
+ *count = acl(name, GETACL, *count, *solaris_acl);
+ if (*count < 0) {
+ DEBUG(10, ("acl GETACL failed: %s\n", strerror(errno)));
+ goto done;
+ }
+ result = True;
+
+ done:
+ DEBUG(10, ("solaris_acl_get_file %s.\n",
+ ((result == True) ? "succeeded" : "failed" )));
+ return result;
+}
+
+
+static bool solaris_acl_get_fd(int fd, SOLARIS_ACL_T *solaris_acl, int *count)
+{
+ bool ret = False;
+
+ DEBUG(10, ("entering solaris_acl_get_fd\n"));
+
+ /*
+ * see solaris_acl_get_file for comment about omission
+ * of INITIAL_ACL_SIZE...
+ */
+ *count = facl(fd, GETACLCNT, 0, NULL);
+ if (*count < 0) {
+ DEBUG(10, ("facl GETACLCNT failed: %s\n", strerror(errno)));
+ goto done;
+ }
+ *solaris_acl = solaris_acl_init(*count);
+ if (*solaris_acl == NULL) {
+ DEBUG(10, ("error allocating memory for solaris acl...\n"));
+ goto done;
+ }
+ *count = facl(fd, GETACL, *count, *solaris_acl);
+ if (*count < 0) {
+ DEBUG(10, ("facl GETACL failed: %s\n", strerror(errno)));
+ goto done;
+ }
+ ret = True;
+
+ done:
+ DEBUG(10, ("solaris_acl_get_fd %s\n",
+ ((ret == True) ? "succeeded" : "failed")));
+ return ret;
+}
+
+
+
+/*
+ * Add entries to a solaris ACL.
+ *
+ * Entries are directly added to the solarisacl parameter.
+ * if memory allocation fails, this may result in solarisacl
+ * being NULL. if the resulting acl is to be checked and is
+ * not valid, it is kept in solarisacl but False is returned.
+ *
+ * The type of ACEs (access/default) to be added to the ACL can
+ * be selected via the type parameter.
+ * I use the SMB_ACL_TYPE_T type here. Since SMB_ACL_TYPE_ACCESS
+ * is defined as "0", this means that one can only add either
+ * access or default ACEs, not both at the same time. If it
+ * should become necessary to add all of an ACL, one would have
+ * to replace this parameter by another type.
+ */
+static bool solaris_add_to_acl(SOLARIS_ACL_T *solaris_acl, int *count,
+ SOLARIS_ACL_T add_acl, int add_count,
+ SMB_ACL_TYPE_T type)
+{
+ int i;
+
+ if ((type != SMB_ACL_TYPE_ACCESS) && (type != SMB_ACL_TYPE_DEFAULT))
+ {
+ DEBUG(10, ("invalid acl type given: %d\n", type));
+ errno = EINVAL;
+ return False;
+ }
+ for (i = 0; i < add_count; i++) {
+ if (!_IS_OF_TYPE(add_acl[i], type)) {
+ continue;
+ }
+ ADD_TO_ARRAY(NULL, SOLARIS_ACE_T, add_acl[i],
+ solaris_acl, count);
+ if (solaris_acl == NULL) {
+ DEBUG(10, ("error enlarging acl.\n"));
+ errno = ENOMEM;
+ return False;
+ }
+ }
+ return True;
+}
+
+
+/*
+ * sort the ACL and check it for validity
+ *
+ * [original comment from lib/sysacls.c:]
+ *
+ * if it's a minimal ACL with only 4 entries then we
+ * need to recalculate the mask permissions to make
+ * sure that they are the same as the GROUP_OBJ
+ * permissions as required by the UnixWare acl() system call.
+ *
+ * (note: since POSIX allows minimal ACLs which only contain
+ * 3 entries - ie there is no mask entry - we should, in theory,
+ * check for this and add a mask entry if necessary - however
+ * we "know" that the caller of this interface always specifies
+ * a mask, so in practice "this never happens" (tm) - if it *does*
+ * happen aclsort() will fail and return an error and someone will
+ * have to fix it...)
+ */
+static bool solaris_acl_sort(SOLARIS_ACL_T solaris_acl, int count)
+{
+ int fixmask = (count <= 4);
+
+ if (aclsort(count, fixmask, solaris_acl) != 0) {
+ errno = EINVAL;
+ return False;
+ }
+ return True;
+}
+
+#if 0
+/*
+ * acl check function:
+ * unused at the moment but could be used to get more
+ * concrete error messages for debugging...
+ * (acl sort just says that the acl is invalid...)
+ */
+static bool solaris_acl_check(SOLARIS_ACL_T solaris_acl, int count)
+{
+ int check_rc;
+ int check_which;
+
+ check_rc = aclcheck(solaris_acl, count, &check_which);
+ if (check_rc != 0) {
+ DEBUG(10, ("acl is not valid:\n"));
+ DEBUGADD(10, (" - return code: %d\n", check_rc));
+ DEBUGADD(10, (" - which: %d\n", check_which));
+ if (check_which != -1) {
+ DEBUGADD(10, (" - invalid entry:\n"));
+ DEBUGADD(10, (" * type: %d:\n",
+ solaris_acl[check_which].a_type));
+ DEBUGADD(10, (" * id: %d\n",
+ solaris_acl[check_which].a_id));
+ DEBUGADD(10, (" * perm: 0o%o\n",
+ solaris_acl[check_which].a_perm));
+ }
+ return False;
+ }
+ return True;
+}
+#endif
+
+static struct vfs_fn_pointers solarisacl_fns = {
+ .sys_acl_get_fd_fn = solarisacl_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = posix_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = solarisacl_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = solarisacl_sys_acl_delete_def_fd,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_solarisacl_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "solarisacl",
+ &solarisacl_fns);
+}
+
+/* ENTE */
diff --git a/source3/modules/vfs_solarisacl.h b/source3/modules/vfs_solarisacl.h
new file mode 100644
index 0000000..54d538c
--- /dev/null
+++ b/source3/modules/vfs_solarisacl.h
@@ -0,0 +1,44 @@
+/*
+ Unix SMB/Netbios implementation.
+ VFS module to get and set Solaris ACLs - prototype header
+ Copyright (C) Michael Adam 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __VFS_SOLARISACL_H__
+#define __VFS_SOLARISACL_H__
+
+SMB_ACL_T solarisacl_sys_acl_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+
+SMB_ACL_T solarisacl_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx);
+
+int solarisacl_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl);
+
+int solarisacl_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp);
+
+NTSTATUS vfs_solarisacl_init(TALLOC_CTX *);
+
+#endif
+
diff --git a/source3/modules/vfs_streams_depot.c b/source3/modules/vfs_streams_depot.c
new file mode 100644
index 0000000..f9701cc
--- /dev/null
+++ b/source3/modules/vfs_streams_depot.c
@@ -0,0 +1,1218 @@
+/*
+ * Store streams in a separate subdirectory
+ *
+ * Copyright (C) Volker Lendecke, 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "source3/smbd/dir.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+/*
+ * Excerpt from a mail from tridge:
+ *
+ * Volker, what I'm thinking of is this:
+ * /mount-point/.streams/XX/YY/aaaa.bbbb/namedstream1
+ * /mount-point/.streams/XX/YY/aaaa.bbbb/namedstream2
+ *
+ * where XX/YY is a 2 level hash based on the fsid/inode. "aaaa.bbbb"
+ * is the fsid/inode. "namedstreamX" is a file named after the stream
+ * name.
+ */
+
+static uint32_t hash_fn(DATA_BLOB key)
+{
+ uint32_t value; /* Used to compute the hash value. */
+ uint32_t i; /* Used to cycle through random values. */
+
+ /* Set the initial value from the key size. */
+ for (value = 0x238F13AF * key.length, i=0; i < key.length; i++)
+ value = (value + (key.data[i] << (i*5 % 24)));
+
+ return (1103515243 * value + 12345);
+}
+
+/*
+ * With the hashing scheme based on the inode we need to protect against
+ * streams showing up on files with re-used inodes. This can happen if we
+ * create a stream directory from within Samba, and a local process or NFS
+ * client deletes the file without deleting the streams directory. When the
+ * inode is re-used and the stream directory is still around, the streams in
+ * there would be show up as belonging to the new file.
+ *
+ * There are several workarounds for this, probably the easiest one is on
+ * systems which have a true birthtime stat element: When the file has a later
+ * birthtime than the streams directory, then we have to recreate the
+ * directory.
+ *
+ * The other workaround is to somehow mark the file as generated by Samba with
+ * something that a NFS client would not do. The closest one is a special
+ * xattr value being set. On systems which do not support xattrs, it might be
+ * an option to put in a special ACL entry for a non-existing group.
+ */
+
+static bool file_is_valid(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ char buf;
+ NTSTATUS status;
+ struct smb_filename *pathref = NULL;
+ int ret;
+
+ DEBUG(10, ("file_is_valid (%s) called\n", smb_fname->base_name));
+
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ ret = SMB_VFS_FGETXATTR(pathref->fsp,
+ SAMBA_XATTR_MARKER,
+ &buf,
+ sizeof(buf));
+ if (ret != sizeof(buf)) {
+ int saved_errno = errno;
+ DBG_DEBUG("FGETXATTR failed: %s\n", strerror(saved_errno));
+ TALLOC_FREE(pathref);
+ errno = saved_errno;
+ return false;
+ }
+
+ TALLOC_FREE(pathref);
+
+ if (buf != '1') {
+ DEBUG(10, ("got wrong buffer content: '%c'\n", buf));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Return the root of the stream directory. Can be
+ * external to the share definition but by default
+ * is "handle->conn->connectpath/.streams".
+ *
+ * Note that this is an *absolute* path, starting
+ * with '/', so the dirfsp being used in the
+ * calls below isn't looked at.
+ */
+
+static char *stream_rootdir(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *tmp;
+
+ tmp = talloc_asprintf(ctx,
+ "%s/.streams",
+ handle->conn->connectpath);
+ if (tmp == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return lp_parm_substituted_string(ctx,
+ lp_sub,
+ SNUM(handle->conn),
+ "streams_depot",
+ "directory",
+ tmp);
+}
+
+/**
+ * Given an smb_filename, determine the stream directory using the file's
+ * base_name.
+ */
+static char *stream_dir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ const SMB_STRUCT_STAT *base_sbuf, bool create_it)
+{
+ uint32_t hash;
+ struct smb_filename *smb_fname_hash = NULL;
+ char *result = NULL;
+ SMB_STRUCT_STAT base_sbuf_tmp;
+ char *tmp = NULL;
+ uint8_t first, second;
+ char *id_hex;
+ struct file_id id;
+ uint8_t id_buf[16];
+ bool check_valid;
+ char *rootdir = NULL;
+ struct smb_filename *rootdir_fname = NULL;
+ struct smb_filename *tmp_fname = NULL;
+ int ret;
+
+ check_valid = lp_parm_bool(SNUM(handle->conn),
+ "streams_depot", "check_valid", true);
+
+ rootdir = stream_rootdir(handle,
+ talloc_tos());
+ if (rootdir == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ rootdir_fname = synthetic_smb_fname(talloc_tos(),
+ rootdir,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (rootdir_fname == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ /* Stat the base file if it hasn't already been done. */
+ if (base_sbuf == NULL) {
+ struct smb_filename *smb_fname_base;
+
+ smb_fname_base = synthetic_smb_fname(
+ talloc_tos(),
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (smb_fname_base == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+ if (SMB_VFS_NEXT_STAT(handle, smb_fname_base) == -1) {
+ TALLOC_FREE(smb_fname_base);
+ goto fail;
+ }
+ base_sbuf_tmp = smb_fname_base->st;
+ TALLOC_FREE(smb_fname_base);
+ } else {
+ base_sbuf_tmp = *base_sbuf;
+ }
+
+ id = SMB_VFS_FILE_ID_CREATE(handle->conn, &base_sbuf_tmp);
+
+ push_file_id_16((char *)id_buf, &id);
+
+ hash = hash_fn(data_blob_const(id_buf, sizeof(id_buf)));
+
+ first = hash & 0xff;
+ second = (hash >> 8) & 0xff;
+
+ id_hex = hex_encode_talloc(talloc_tos(), id_buf, sizeof(id_buf));
+
+ if (id_hex == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ result = talloc_asprintf(talloc_tos(), "%s/%2.2X/%2.2X/%s", rootdir,
+ first, second, id_hex);
+
+ TALLOC_FREE(id_hex);
+
+ if (result == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ smb_fname_hash = synthetic_smb_fname(talloc_tos(),
+ result,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (smb_fname_hash == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ if (SMB_VFS_NEXT_STAT(handle, smb_fname_hash) == 0) {
+ struct smb_filename *smb_fname_new = NULL;
+ char *newname;
+ bool delete_lost;
+
+ if (!S_ISDIR(smb_fname_hash->st.st_ex_mode)) {
+ errno = EINVAL;
+ goto fail;
+ }
+
+ if (!check_valid ||
+ file_is_valid(handle, smb_fname)) {
+ return result;
+ }
+
+ /*
+ * Someone has recreated a file under an existing inode
+ * without deleting the streams directory.
+ * Move it away or remove if streams_depot:delete_lost is set.
+ */
+
+ again:
+ delete_lost = lp_parm_bool(SNUM(handle->conn), "streams_depot",
+ "delete_lost", false);
+
+ if (delete_lost) {
+ DEBUG(3, ("Someone has recreated a file under an "
+ "existing inode. Removing: %s\n",
+ smb_fname_hash->base_name));
+ recursive_rmdir(talloc_tos(), handle->conn,
+ smb_fname_hash);
+ SMB_VFS_NEXT_UNLINKAT(handle,
+ handle->conn->cwd_fsp,
+ smb_fname_hash,
+ AT_REMOVEDIR);
+ } else {
+ newname = talloc_asprintf(talloc_tos(), "lost-%lu",
+ random());
+ DEBUG(3, ("Someone has recreated a file under an "
+ "existing inode. Renaming: %s to: %s\n",
+ smb_fname_hash->base_name,
+ newname));
+ if (newname == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ smb_fname_new = synthetic_smb_fname(
+ talloc_tos(),
+ newname,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ TALLOC_FREE(newname);
+ if (smb_fname_new == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ if (SMB_VFS_NEXT_RENAMEAT(handle,
+ handle->conn->cwd_fsp,
+ smb_fname_hash,
+ handle->conn->cwd_fsp,
+ smb_fname_new) == -1) {
+ TALLOC_FREE(smb_fname_new);
+ if ((errno == EEXIST) || (errno == ENOTEMPTY)) {
+ goto again;
+ }
+ goto fail;
+ }
+
+ TALLOC_FREE(smb_fname_new);
+ }
+ }
+
+ if (!create_it) {
+ errno = ENOENT;
+ goto fail;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ rootdir_fname,
+ 0755);
+ if ((ret != 0) && (errno != EEXIST)) {
+ goto fail;
+ }
+
+ tmp = talloc_asprintf(result, "%s/%2.2X", rootdir, first);
+ if (tmp == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ tmp_fname = synthetic_smb_fname(talloc_tos(),
+ tmp,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (tmp_fname == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ tmp_fname,
+ 0755);
+ if ((ret != 0) && (errno != EEXIST)) {
+ goto fail;
+ }
+
+ TALLOC_FREE(tmp);
+ TALLOC_FREE(tmp_fname);
+
+ tmp = talloc_asprintf(result, "%s/%2.2X/%2.2X", rootdir, first,
+ second);
+ if (tmp == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ tmp_fname = synthetic_smb_fname(talloc_tos(),
+ tmp,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (tmp_fname == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ tmp_fname,
+ 0755);
+ if ((ret != 0) && (errno != EEXIST)) {
+ goto fail;
+ }
+
+ TALLOC_FREE(tmp);
+ TALLOC_FREE(tmp_fname);
+
+ /* smb_fname_hash is the struct smb_filename version of 'result' */
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ smb_fname_hash,
+ 0755);
+ if ((ret != 0) && (errno != EEXIST)) {
+ goto fail;
+ }
+
+ TALLOC_FREE(rootdir_fname);
+ TALLOC_FREE(rootdir);
+ TALLOC_FREE(tmp_fname);
+ TALLOC_FREE(smb_fname_hash);
+ return result;
+
+ fail:
+ TALLOC_FREE(rootdir_fname);
+ TALLOC_FREE(rootdir);
+ TALLOC_FREE(tmp_fname);
+ TALLOC_FREE(smb_fname_hash);
+ TALLOC_FREE(result);
+ return NULL;
+}
+/**
+ * Given a stream name, populate smb_fname_out with the actual location of the
+ * stream.
+ */
+static NTSTATUS stream_smb_fname(vfs_handle_struct *handle,
+ const struct stat_ex *base_sbuf,
+ const struct smb_filename *smb_fname,
+ struct smb_filename **smb_fname_out,
+ bool create_dir)
+{
+ char *dirname, *stream_fname;
+ const char *stype;
+ NTSTATUS status;
+
+ *smb_fname_out = NULL;
+
+ stype = strchr_m(smb_fname->stream_name + 1, ':');
+
+ if (stype) {
+ if (strcasecmp_m(stype, ":$DATA") != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ dirname = stream_dir(handle, smb_fname, base_sbuf, create_dir);
+
+ if (dirname == NULL) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ stream_fname = talloc_asprintf(talloc_tos(), "%s/%s", dirname,
+ smb_fname->stream_name);
+
+ if (stream_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (stype == NULL) {
+ /* Append an explicit stream type if one wasn't specified. */
+ stream_fname = talloc_asprintf(talloc_tos(), "%s:$DATA",
+ stream_fname);
+ if (stream_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ } else {
+ /* Normalize the stream type to uppercase. */
+ if (!strupper_m(strrchr_m(stream_fname, ':') + 1)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+
+ DEBUG(10, ("stream filename = %s\n", stream_fname));
+
+ /* Create an smb_filename with stream_name == NULL. */
+ *smb_fname_out = synthetic_smb_fname(talloc_tos(),
+ stream_fname,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (*smb_fname_out == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+
+ fail:
+ DEBUG(5, ("stream_name failed: %s\n", strerror(errno)));
+ TALLOC_FREE(*smb_fname_out);
+ return status;
+}
+
+static NTSTATUS walk_streams(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname_base,
+ char **pdirname,
+ bool (*fn)(const struct smb_filename *dirname,
+ const char *dirent,
+ void *private_data),
+ void *private_data)
+{
+ char *dirname;
+ char *rootdir = NULL;
+ char *orig_connectpath = NULL;
+ struct smb_filename *dir_smb_fname = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ NTSTATUS status;
+
+ dirname = stream_dir(handle, smb_fname_base, &smb_fname_base->st,
+ false);
+
+ if (dirname == NULL) {
+ if (errno == ENOENT) {
+ /*
+ * no stream around
+ */
+ return NT_STATUS_OK;
+ }
+ return map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10, ("walk_streams: dirname=%s\n", dirname));
+
+ dir_smb_fname = synthetic_smb_fname(talloc_tos(),
+ dirname,
+ NULL,
+ NULL,
+ smb_fname_base->twrp,
+ smb_fname_base->flags);
+ if (dir_smb_fname == NULL) {
+ TALLOC_FREE(dirname);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * For OpenDir to succeed if the stream rootdir is outside
+ * the share path, we must temporarily swap out the connect
+ * path for this share. We're dealing with absolute paths
+ * here so we don't care about chdir calls.
+ */
+ rootdir = stream_rootdir(handle, talloc_tos());
+ if (rootdir == NULL) {
+ TALLOC_FREE(dir_smb_fname);
+ TALLOC_FREE(dirname);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ orig_connectpath = handle->conn->connectpath;
+ handle->conn->connectpath = rootdir;
+
+ status = OpenDir(
+ talloc_tos(), handle->conn, dir_smb_fname, NULL, 0, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ handle->conn->connectpath = orig_connectpath;
+ TALLOC_FREE(rootdir);
+ TALLOC_FREE(dir_smb_fname);
+ TALLOC_FREE(dirname);
+ return status;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ DBG_DEBUG("dirent=%s\n", dname);
+
+ if (!fn(dir_smb_fname, dname, private_data)) {
+ TALLOC_FREE(talloced);
+ break;
+ }
+ TALLOC_FREE(talloced);
+ }
+
+ /* Restore the original connectpath. */
+ handle->conn->connectpath = orig_connectpath;
+ TALLOC_FREE(rootdir);
+ TALLOC_FREE(dir_smb_fname);
+ TALLOC_FREE(dir_hnd);
+
+ if (pdirname != NULL) {
+ *pdirname = dirname;
+ }
+ else {
+ TALLOC_FREE(dirname);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int streams_depot_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ struct smb_filename *smb_fname_stream = NULL;
+ NTSTATUS status;
+ int ret = -1;
+
+ DEBUG(10, ("streams_depot_stat called for [%s]\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ if (!is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_STAT(handle, smb_fname);
+ }
+
+ /* Stat the actual stream now. */
+ status = stream_smb_fname(
+ handle, NULL, smb_fname, &smb_fname_stream, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = -1;
+ errno = map_errno_from_nt_status(status);
+ goto done;
+ }
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname_stream);
+
+ /* Update the original smb_fname with the stat info. */
+ smb_fname->st = smb_fname_stream->st;
+ done:
+ TALLOC_FREE(smb_fname_stream);
+ return ret;
+}
+
+
+
+static int streams_depot_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ struct smb_filename *smb_fname_stream = NULL;
+ NTSTATUS status;
+ int ret = -1;
+
+ DEBUG(10, ("streams_depot_lstat called for [%s]\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ if (!is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+
+ /* Stat the actual stream now. */
+ status = stream_smb_fname(
+ handle, NULL, smb_fname, &smb_fname_stream, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = -1;
+ errno = map_errno_from_nt_status(status);
+ goto done;
+ }
+
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname_stream);
+
+ done:
+ TALLOC_FREE(smb_fname_stream);
+ return ret;
+}
+
+static int streams_depot_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ struct smb_filename *smb_fname_stream = NULL;
+ struct files_struct *fspcwd = NULL;
+ NTSTATUS status;
+ bool create_it;
+ int ret = -1;
+
+ if (!is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ if (how->resolve != 0) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ SMB_ASSERT(fsp_is_alternate_stream(fsp));
+ SMB_ASSERT(dirfsp == NULL);
+ SMB_ASSERT(VALID_STAT(fsp->base_fsp->fsp_name->st));
+
+ create_it = (how->flags & O_CREAT);
+
+ /* Determine the stream name, and then open it. */
+ status = stream_smb_fname(
+ handle,
+ &fsp->base_fsp->fsp_name->st,
+ fsp->fsp_name,
+ &smb_fname_stream,
+ create_it);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = -1;
+ errno = map_errno_from_nt_status(status);
+ goto done;
+ }
+
+ if (create_it) {
+ bool check_valid = lp_parm_bool(
+ SNUM(handle->conn),
+ "streams_depot",
+ "check_valid",
+ true);
+
+ if (check_valid) {
+ char buf = '1';
+
+ DBG_DEBUG("marking file %s as valid\n",
+ fsp->base_fsp->fsp_name->base_name);
+
+ ret = SMB_VFS_FSETXATTR(
+ fsp->base_fsp,
+ SAMBA_XATTR_MARKER,
+ &buf,
+ sizeof(buf),
+ 0);
+
+ if (ret == -1) {
+ DBG_DEBUG("FSETXATTR failed: %s\n",
+ strerror(errno));
+ goto done;
+ }
+ }
+ }
+
+ status = vfs_at_fspcwd(talloc_tos(), handle->conn, &fspcwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = -1;
+ errno = map_errno_from_nt_status(status);
+ goto done;
+ }
+
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ fspcwd,
+ smb_fname_stream,
+ fsp,
+ how);
+
+ done:
+ TALLOC_FREE(smb_fname_stream);
+ TALLOC_FREE(fspcwd);
+ return ret;
+}
+
+static int streams_depot_unlink_internal(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ char *dirname = NULL;
+ int ret = -1;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ DEBUG(10, ("streams_depot_unlink called for %s\n",
+ smb_fname_str_dbg(full_fname)));
+
+ /* If there is a valid stream, just unlink the stream and return. */
+ if (is_named_stream(full_fname)) {
+ struct smb_filename *smb_fname_stream = NULL;
+ NTSTATUS status;
+
+ status = stream_smb_fname(
+ handle, NULL, full_fname, &smb_fname_stream, false);
+ TALLOC_FREE(full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp->conn->cwd_fsp,
+ smb_fname_stream,
+ 0);
+
+ TALLOC_FREE(smb_fname_stream);
+ return ret;
+ }
+
+ /*
+ * We potentially need to delete the per-inode streams directory
+ */
+
+ if (full_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ ret = SMB_VFS_NEXT_LSTAT(handle, full_fname);
+ } else {
+ ret = SMB_VFS_NEXT_STAT(handle, full_fname);
+ if (ret == -1 && (errno == ENOENT || errno == ELOOP)) {
+ if (VALID_STAT(smb_fname->st) &&
+ S_ISLNK(smb_fname->st.st_ex_mode)) {
+ /*
+ * Original name was a link - Could be
+ * trying to remove a dangling symlink.
+ */
+ ret = SMB_VFS_NEXT_LSTAT(handle, full_fname);
+ }
+ }
+ }
+ if (ret == -1) {
+ TALLOC_FREE(full_fname);
+ return -1;
+ }
+
+ /*
+ * We know the unlink should succeed as the ACL
+ * check is already done in the caller. Remove the
+ * file *after* the streams.
+ */
+ dirname = stream_dir(handle,
+ full_fname,
+ &full_fname->st,
+ false);
+ TALLOC_FREE(full_fname);
+ if (dirname != NULL) {
+ struct smb_filename *smb_fname_dir = NULL;
+
+ smb_fname_dir = synthetic_smb_fname(talloc_tos(),
+ dirname,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (smb_fname_dir == NULL) {
+ TALLOC_FREE(dirname);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp->conn->cwd_fsp,
+ smb_fname_dir,
+ AT_REMOVEDIR);
+ TALLOC_FREE(smb_fname_dir);
+ TALLOC_FREE(dirname);
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ return ret;
+}
+
+static int streams_depot_rmdir_internal(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *smb_fname_base = NULL;
+ int ret = -1;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ DBG_DEBUG("called for %s\n", full_fname->base_name);
+
+ /*
+ * We potentially need to delete the per-inode streams directory
+ */
+
+ smb_fname_base = synthetic_smb_fname(talloc_tos(),
+ full_fname->base_name,
+ NULL,
+ NULL,
+ full_fname->twrp,
+ full_fname->flags);
+ TALLOC_FREE(full_fname);
+ if (smb_fname_base == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (smb_fname_base->flags & SMB_FILENAME_POSIX_PATH) {
+ ret = SMB_VFS_NEXT_LSTAT(handle, smb_fname_base);
+ } else {
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname_base);
+ }
+
+ if (ret == -1) {
+ TALLOC_FREE(smb_fname_base);
+ return -1;
+ }
+
+ /*
+ * We know the rmdir should succeed as the ACL
+ * check is already done in the caller. Remove the
+ * directory *after* the streams.
+ */
+ {
+ char *dirname = stream_dir(handle, smb_fname_base,
+ &smb_fname_base->st, false);
+
+ if (dirname != NULL) {
+ struct smb_filename *smb_fname_dir =
+ synthetic_smb_fname(talloc_tos(),
+ dirname,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (smb_fname_dir == NULL) {
+ TALLOC_FREE(smb_fname_base);
+ TALLOC_FREE(dirname);
+ errno = ENOMEM;
+ return -1;
+ }
+ SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp->conn->cwd_fsp,
+ smb_fname_dir,
+ AT_REMOVEDIR);
+ TALLOC_FREE(smb_fname_dir);
+ }
+ TALLOC_FREE(dirname);
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ AT_REMOVEDIR);
+ TALLOC_FREE(smb_fname_base);
+ return ret;
+}
+
+static int streams_depot_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret;
+ if (flags & AT_REMOVEDIR) {
+ ret = streams_depot_rmdir_internal(handle,
+ dirfsp,
+ smb_fname);
+ } else {
+ ret = streams_depot_unlink_internal(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ }
+ return ret;
+}
+
+static int streams_depot_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ struct smb_filename *smb_fname_src_stream = NULL;
+ struct smb_filename *smb_fname_dst_stream = NULL;
+ struct smb_filename *full_src = NULL;
+ struct smb_filename *full_dst = NULL;
+ bool src_is_stream, dst_is_stream;
+ NTSTATUS status;
+ int ret = -1;
+
+ DEBUG(10, ("streams_depot_renameat called for %s => %s\n",
+ smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ src_is_stream = is_ntfs_stream_smb_fname(smb_fname_src);
+ dst_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst);
+
+ if (!src_is_stream && !dst_is_stream) {
+ return SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ }
+
+ /* for now don't allow renames from or to the default stream */
+ if (is_ntfs_default_stream_smb_fname(smb_fname_src) ||
+ is_ntfs_default_stream_smb_fname(smb_fname_dst)) {
+ errno = ENOSYS;
+ goto done;
+ }
+
+ full_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_src == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+ full_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_dst == NULL) {
+ errno = ENOMEM;
+ goto done;
+ }
+
+ status = stream_smb_fname(
+ handle, NULL, full_src, &smb_fname_src_stream, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto done;
+ }
+
+ status = stream_smb_fname(
+ handle, NULL, full_dst, &smb_fname_dst_stream, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto done;
+ }
+
+ /*
+ * We must use handle->conn->cwd_fsp as
+ * srcfsp and dstfsp directory handles here
+ * as we used the full pathname from the cwd dir
+ * to calculate the streams directory and filename
+ * within.
+ */
+ ret = SMB_VFS_NEXT_RENAMEAT(handle,
+ handle->conn->cwd_fsp,
+ smb_fname_src_stream,
+ handle->conn->cwd_fsp,
+ smb_fname_dst_stream);
+
+done:
+ TALLOC_FREE(smb_fname_src_stream);
+ TALLOC_FREE(smb_fname_dst_stream);
+ return ret;
+}
+
+static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
+ struct stream_struct **streams,
+ const char *name, off_t size,
+ off_t alloc_size)
+{
+ struct stream_struct *tmp;
+
+ tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct,
+ (*num_streams)+1);
+ if (tmp == NULL) {
+ return false;
+ }
+
+ tmp[*num_streams].name = talloc_strdup(tmp, name);
+ if (tmp[*num_streams].name == NULL) {
+ return false;
+ }
+
+ tmp[*num_streams].size = size;
+ tmp[*num_streams].alloc_size = alloc_size;
+
+ *streams = tmp;
+ *num_streams += 1;
+ return true;
+}
+
+struct streaminfo_state {
+ TALLOC_CTX *mem_ctx;
+ vfs_handle_struct *handle;
+ unsigned int num_streams;
+ struct stream_struct *streams;
+ NTSTATUS status;
+};
+
+static bool collect_one_stream(const struct smb_filename *dirfname,
+ const char *dirent,
+ void *private_data)
+{
+ const char *dirname = dirfname->base_name;
+ struct streaminfo_state *state =
+ (struct streaminfo_state *)private_data;
+ struct smb_filename *smb_fname = NULL;
+ char *sname = NULL;
+ bool ret;
+
+ sname = talloc_asprintf(talloc_tos(), "%s/%s", dirname, dirent);
+ if (sname == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ ret = false;
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ sname,
+ NULL,
+ NULL,
+ dirfname->twrp,
+ 0);
+ if (smb_fname == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ ret = false;
+ goto out;
+ }
+
+ if (SMB_VFS_NEXT_STAT(state->handle, smb_fname) == -1) {
+ DEBUG(10, ("Could not stat %s: %s\n", sname,
+ strerror(errno)));
+ ret = true;
+ goto out;
+ }
+
+ if (!add_one_stream(state->mem_ctx,
+ &state->num_streams, &state->streams,
+ dirent, smb_fname->st.st_ex_size,
+ SMB_VFS_GET_ALLOC_SIZE(state->handle->conn, NULL,
+ &smb_fname->st))) {
+ state->status = NT_STATUS_NO_MEMORY;
+ ret = false;
+ goto out;
+ }
+
+ ret = true;
+ out:
+ TALLOC_FREE(sname);
+ TALLOC_FREE(smb_fname);
+ return ret;
+}
+
+static NTSTATUS streams_depot_fstreaminfo(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ struct smb_filename *smb_fname_base = NULL;
+ int ret;
+ NTSTATUS status;
+ struct streaminfo_state state;
+
+ smb_fname_base = synthetic_smb_fname(talloc_tos(),
+ fsp->fsp_name->base_name,
+ NULL,
+ NULL,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags);
+ if (smb_fname_base == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, &smb_fname_base->st);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ state.streams = *pstreams;
+ state.num_streams = *pnum_streams;
+ state.mem_ctx = mem_ctx;
+ state.handle = handle;
+ state.status = NT_STATUS_OK;
+
+ status = walk_streams(handle,
+ smb_fname_base,
+ NULL,
+ collect_one_stream,
+ &state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(state.streams);
+ goto out;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ TALLOC_FREE(state.streams);
+ status = state.status;
+ goto out;
+ }
+
+ *pnum_streams = state.num_streams;
+ *pstreams = state.streams;
+ status = SMB_VFS_NEXT_FSTREAMINFO(handle,
+ fsp->base_fsp ? fsp->base_fsp : fsp,
+ mem_ctx,
+ pnum_streams,
+ pstreams);
+
+ out:
+ TALLOC_FREE(smb_fname_base);
+ return status;
+}
+
+static uint32_t streams_depot_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | FILE_NAMED_STREAMS;
+}
+
+static struct vfs_fn_pointers vfs_streams_depot_fns = {
+ .fs_capabilities_fn = streams_depot_fs_capabilities,
+ .openat_fn = streams_depot_openat,
+ .stat_fn = streams_depot_stat,
+ .lstat_fn = streams_depot_lstat,
+ .unlinkat_fn = streams_depot_unlinkat,
+ .renameat_fn = streams_depot_renameat,
+ .fstreaminfo_fn = streams_depot_fstreaminfo,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_streams_depot_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "streams_depot",
+ &vfs_streams_depot_fns);
+}
diff --git a/source3/modules/vfs_streams_xattr.c b/source3/modules/vfs_streams_xattr.c
new file mode 100644
index 0000000..03ff614
--- /dev/null
+++ b/source3/modules/vfs_streams_xattr.c
@@ -0,0 +1,1614 @@
+/*
+ * Store streams in xattrs
+ *
+ * Copyright (C) Volker Lendecke, 2008
+ *
+ * Partly based on James Peach's Darwin module, which is
+ *
+ * Copyright (C) James Peach 2006-2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "lib/util/tevent_unix.h"
+#include "librpc/gen_ndr/ioctl.h"
+#include "hash_inode.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+struct streams_xattr_config {
+ const char *prefix;
+ size_t prefix_len;
+ bool store_stream_type;
+};
+
+struct stream_io {
+ char *base;
+ char *xattr_name;
+ void *fsp_name_ptr;
+ files_struct *fsp;
+ vfs_handle_struct *handle;
+};
+
+static ssize_t get_xattr_size_fsp(struct files_struct *fsp,
+ const char *xattr_name)
+{
+ NTSTATUS status;
+ struct ea_struct ea;
+ ssize_t result;
+
+ status = get_ea_value_fsp(talloc_tos(),
+ fsp,
+ xattr_name,
+ &ea);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ result = ea.value.length-1;
+ TALLOC_FREE(ea.value.data);
+ return result;
+}
+
+/**
+ * Given a stream name, populate xattr_name with the xattr name to use for
+ * accessing the stream.
+ */
+static NTSTATUS streams_xattr_get_name(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const char *stream_name,
+ char **xattr_name)
+{
+ size_t stream_name_len = strlen(stream_name);
+ char *stype;
+ struct streams_xattr_config *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ SMB_ASSERT(stream_name[0] == ':');
+ stream_name += 1;
+
+ /*
+ * With vfs_fruit option "fruit:encoding = native" we're
+ * already converting stream names that contain illegal NTFS
+ * characters from their on-the-wire Unicode Private Range
+ * encoding to their native ASCII representation.
+ *
+ * As as result the name of xattrs storing the streams (via
+ * vfs_streams_xattr) may contain a colon, so we have to use
+ * strrchr_m() instead of strchr_m() for matching the stream
+ * type suffix.
+ *
+ * In check_path_syntax() we've already ensured the streamname
+ * we got from the client is valid.
+ */
+ stype = strrchr_m(stream_name, ':');
+
+ if (stype) {
+ /*
+ * We only support one stream type: "$DATA"
+ */
+ if (strcasecmp_m(stype, ":$DATA") != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Split name and type */
+ stream_name_len = (stype - stream_name);
+ }
+
+ *xattr_name = talloc_asprintf(ctx, "%s%.*s%s",
+ config->prefix,
+ (int)stream_name_len,
+ stream_name,
+ config->store_stream_type ? ":$DATA" : "");
+ if (*xattr_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10, ("xattr_name: %s, stream_name: %s\n", *xattr_name,
+ stream_name));
+
+ return NT_STATUS_OK;
+}
+
+static bool streams_xattr_recheck(struct stream_io *sio)
+{
+ NTSTATUS status;
+ char *xattr_name = NULL;
+
+ if (sio->fsp->fsp_name == sio->fsp_name_ptr) {
+ return true;
+ }
+
+ if (sio->fsp->fsp_name->stream_name == NULL) {
+ /* how can this happen */
+ errno = EINVAL;
+ return false;
+ }
+
+ status = streams_xattr_get_name(sio->handle, talloc_tos(),
+ sio->fsp->fsp_name->stream_name,
+ &xattr_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ TALLOC_FREE(sio->xattr_name);
+ TALLOC_FREE(sio->base);
+ sio->xattr_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp),
+ xattr_name);
+ if (sio->xattr_name == NULL) {
+ DBG_DEBUG("sio->xattr_name==NULL\n");
+ return false;
+ }
+ TALLOC_FREE(xattr_name);
+
+ sio->base = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(sio->handle, sio->fsp),
+ sio->fsp->fsp_name->base_name);
+ if (sio->base == NULL) {
+ DBG_DEBUG("sio->base==NULL\n");
+ return false;
+ }
+
+ sio->fsp_name_ptr = sio->fsp->fsp_name;
+
+ return true;
+}
+
+static int streams_xattr_fstat(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ int ret = -1;
+ struct stream_io *io = (struct stream_io *)
+ VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (io == NULL || !fsp_is_alternate_stream(fsp)) {
+ return SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ }
+
+ DBG_DEBUG("streams_xattr_fstat called for %s\n", fsp_str_dbg(io->fsp));
+
+ if (!streams_xattr_recheck(io)) {
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp->base_fsp, sbuf);
+ if (ret == -1) {
+ return -1;
+ }
+
+ sbuf->st_ex_size = get_xattr_size_fsp(fsp->base_fsp,
+ io->xattr_name);
+ if (sbuf->st_ex_size == -1) {
+ SET_STAT_INVALID(*sbuf);
+ return -1;
+ }
+
+ DEBUG(10, ("sbuf->st_ex_size = %d\n", (int)sbuf->st_ex_size));
+
+ sbuf->st_ex_ino = hash_inode(sbuf, io->xattr_name);
+ sbuf->st_ex_mode &= ~S_IFMT;
+ sbuf->st_ex_mode &= ~S_IFDIR;
+ sbuf->st_ex_mode |= S_IFREG;
+ sbuf->st_ex_blocks = sbuf->st_ex_size / STAT_ST_BLOCKSIZE + 1;
+
+ return 0;
+}
+
+static int streams_xattr_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ NTSTATUS status;
+ int result = -1;
+ char *xattr_name = NULL;
+ char *tmp_stream_name = NULL;
+ struct smb_filename *pathref = NULL;
+ struct files_struct *fsp = smb_fname->fsp;
+
+ if (!is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_STAT(handle, smb_fname);
+ }
+
+ /* Note if lp_posix_paths() is true, we can never
+ * get here as is_named_stream() is
+ * always false. So we never need worry about
+ * not following links here. */
+
+ /* Populate the stat struct with info from the base file. */
+ tmp_stream_name = smb_fname->stream_name;
+ smb_fname->stream_name = NULL;
+ result = SMB_VFS_NEXT_STAT(handle, smb_fname);
+ smb_fname->stream_name = tmp_stream_name;
+
+ if (result == -1) {
+ return -1;
+ }
+
+ /* Derive the xattr name to lookup. */
+ status = streams_xattr_get_name(handle, talloc_tos(),
+ smb_fname->stream_name, &xattr_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ /* Augment the base file's stat information before returning. */
+ if (fsp == NULL) {
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(xattr_name);
+ SET_STAT_INVALID(smb_fname->st);
+ errno = ENOENT;
+ return -1;
+ }
+ fsp = pathref->fsp;
+ } else {
+ fsp = fsp->base_fsp;
+ }
+
+ smb_fname->st.st_ex_size = get_xattr_size_fsp(fsp,
+ xattr_name);
+ if (smb_fname->st.st_ex_size == -1) {
+ TALLOC_FREE(xattr_name);
+ TALLOC_FREE(pathref);
+ SET_STAT_INVALID(smb_fname->st);
+ errno = ENOENT;
+ return -1;
+ }
+
+ smb_fname->st.st_ex_ino = hash_inode(&smb_fname->st, xattr_name);
+ smb_fname->st.st_ex_mode &= ~S_IFMT;
+ smb_fname->st.st_ex_mode |= S_IFREG;
+ smb_fname->st.st_ex_blocks =
+ smb_fname->st.st_ex_size / STAT_ST_BLOCKSIZE + 1;
+
+ TALLOC_FREE(xattr_name);
+ TALLOC_FREE(pathref);
+ return 0;
+}
+
+static int streams_xattr_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ if (is_named_stream(smb_fname)) {
+ /*
+ * There can never be EA's on a symlink.
+ * Windows will never see a symlink, and
+ * in SMB_FILENAME_POSIX_PATH mode we don't
+ * allow EA's on a symlink.
+ */
+ SET_STAT_INVALID(smb_fname->st);
+ errno = ENOENT;
+ return -1;
+ }
+ return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+}
+
+static int streams_xattr_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ NTSTATUS status;
+ struct streams_xattr_config *config = NULL;
+ struct stream_io *sio = NULL;
+ struct ea_struct ea;
+ char *xattr_name = NULL;
+ int fakefd = -1;
+ bool set_empty_xattr = false;
+ int ret;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config,
+ return -1);
+
+ DBG_DEBUG("called for %s with flags 0x%x\n",
+ smb_fname_str_dbg(smb_fname),
+ how->flags);
+
+ if (!is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ if (how->resolve != 0) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ SMB_ASSERT(fsp_is_alternate_stream(fsp));
+ SMB_ASSERT(dirfsp == NULL);
+
+ status = streams_xattr_get_name(handle, talloc_tos(),
+ smb_fname->stream_name, &xattr_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto fail;
+ }
+
+ status = get_ea_value_fsp(talloc_tos(),
+ fsp->base_fsp,
+ xattr_name,
+ &ea);
+
+ DBG_DEBUG("get_ea_value_fsp returned %s\n", nt_errstr(status));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ /*
+ * The base file is not there. This is an error even if
+ * we got O_CREAT, the higher levels should have created
+ * the base file for us.
+ */
+ DBG_DEBUG("streams_xattr_open: base file %s not around, "
+ "returning ENOENT\n", smb_fname->base_name);
+ errno = ENOENT;
+ goto fail;
+ }
+
+ if (!(how->flags & O_CREAT)) {
+ errno = ENOATTR;
+ goto fail;
+ }
+
+ set_empty_xattr = true;
+ }
+
+ if (how->flags & O_TRUNC) {
+ set_empty_xattr = true;
+ }
+
+ if (set_empty_xattr) {
+ /*
+ * The attribute does not exist or needs to be truncated
+ */
+
+ /*
+ * Darn, xattrs need at least 1 byte
+ */
+ char null = '\0';
+
+ DEBUG(10, ("creating or truncating attribute %s on file %s\n",
+ xattr_name, smb_fname->base_name));
+
+ ret = SMB_VFS_FSETXATTR(fsp->base_fsp,
+ xattr_name,
+ &null, sizeof(null),
+ how->flags & O_EXCL ? XATTR_CREATE : 0);
+ if (ret != 0) {
+ goto fail;
+ }
+ }
+
+ fakefd = vfs_fake_fd();
+
+ sio = VFS_ADD_FSP_EXTENSION(handle, fsp, struct stream_io, NULL);
+ if (sio == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ sio->xattr_name = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+ xattr_name);
+ if (sio->xattr_name == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ /*
+ * so->base needs to be a copy of fsp->fsp_name->base_name,
+ * making it identical to streams_xattr_recheck(). If the
+ * open is changing directories, fsp->fsp_name->base_name
+ * will be the full path from the share root, whilst
+ * smb_fname will be relative to the $cwd.
+ */
+ sio->base = talloc_strdup(VFS_MEMCTX_FSP_EXTENSION(handle, fsp),
+ fsp->fsp_name->base_name);
+ if (sio->base == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ sio->fsp_name_ptr = fsp->fsp_name;
+ sio->handle = handle;
+ sio->fsp = fsp;
+
+ return fakefd;
+
+ fail:
+ if (fakefd >= 0) {
+ vfs_fake_fd_close(fakefd);
+ fakefd = -1;
+ }
+
+ return -1;
+}
+
+static int streams_xattr_close(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ int ret;
+ int fd;
+
+ fd = fsp_get_pathref_fd(fsp);
+
+ DBG_DEBUG("streams_xattr_close called [%s] fd [%d]\n",
+ smb_fname_str_dbg(fsp->fsp_name), fd);
+
+ if (!fsp_is_alternate_stream(fsp)) {
+ return SMB_VFS_NEXT_CLOSE(handle, fsp);
+ }
+
+ ret = vfs_fake_fd_close(fd);
+ fsp_set_fd(fsp, -1);
+
+ return ret;
+}
+
+static int streams_xattr_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ NTSTATUS status;
+ int ret = -1;
+ char *xattr_name = NULL;
+ struct smb_filename *pathref = NULL;
+ struct files_struct *fsp = smb_fname->fsp;
+
+ if (!is_named_stream(smb_fname)) {
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ }
+
+ /* A stream can never be rmdir'ed */
+ SMB_ASSERT((flags & AT_REMOVEDIR) == 0);
+
+ status = streams_xattr_get_name(handle, talloc_tos(),
+ smb_fname->stream_name, &xattr_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto fail;
+ }
+
+ if (fsp == NULL) {
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = ENOENT;
+ goto fail;
+ }
+ fsp = pathref->fsp;
+ } else {
+ SMB_ASSERT(fsp_is_alternate_stream(smb_fname->fsp));
+ fsp = fsp->base_fsp;
+ }
+
+ ret = SMB_VFS_FREMOVEXATTR(fsp, xattr_name);
+
+ if ((ret == -1) && (errno == ENOATTR)) {
+ errno = ENOENT;
+ goto fail;
+ }
+
+ ret = 0;
+
+ fail:
+ TALLOC_FREE(xattr_name);
+ TALLOC_FREE(pathref);
+ return ret;
+}
+
+static int streams_xattr_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ NTSTATUS status;
+ int ret = -1;
+ char *src_xattr_name = NULL;
+ char *dst_xattr_name = NULL;
+ bool src_is_stream, dst_is_stream;
+ ssize_t oret;
+ ssize_t nret;
+ struct ea_struct ea;
+ struct smb_filename *pathref_src = NULL;
+ struct smb_filename *pathref_dst = NULL;
+ struct smb_filename *full_src = NULL;
+ struct smb_filename *full_dst = NULL;
+
+ src_is_stream = is_ntfs_stream_smb_fname(smb_fname_src);
+ dst_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst);
+
+ if (!src_is_stream && !dst_is_stream) {
+ return SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ }
+
+ /* For now don't allow renames from or to the default stream. */
+ if (is_ntfs_default_stream_smb_fname(smb_fname_src) ||
+ is_ntfs_default_stream_smb_fname(smb_fname_dst)) {
+ errno = ENOSYS;
+ goto done;
+ }
+
+ /* Don't rename if the streams are identical. */
+ if (strcasecmp_m(smb_fname_src->stream_name,
+ smb_fname_dst->stream_name) == 0) {
+ goto done;
+ }
+
+ /* Get the xattr names. */
+ status = streams_xattr_get_name(handle, talloc_tos(),
+ smb_fname_src->stream_name,
+ &src_xattr_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto fail;
+ }
+ status = streams_xattr_get_name(handle, talloc_tos(),
+ smb_fname_dst->stream_name,
+ &dst_xattr_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto fail;
+ }
+
+ full_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_src == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+ full_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_dst == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ /* Get a pathref for full_src (base file, no stream name). */
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ full_src->base_name,
+ NULL,
+ NULL,
+ full_src->twrp,
+ full_src->flags,
+ &pathref_src);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = ENOENT;
+ goto fail;
+ }
+
+ /* Read the old stream from the base file fsp. */
+ status = get_ea_value_fsp(talloc_tos(),
+ pathref_src->fsp,
+ src_xattr_name,
+ &ea);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto fail;
+ }
+
+ /* Get a pathref for full_dst (base file, no stream name). */
+ status = synthetic_pathref(talloc_tos(),
+ handle->conn->cwd_fsp,
+ full_dst->base_name,
+ NULL,
+ NULL,
+ full_dst->twrp,
+ full_dst->flags,
+ &pathref_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = ENOENT;
+ goto fail;
+ }
+
+ /* (Over)write the new stream on the base file fsp. */
+ nret = SMB_VFS_FSETXATTR(
+ pathref_dst->fsp,
+ dst_xattr_name,
+ ea.value.data,
+ ea.value.length,
+ 0);
+ if (nret < 0) {
+ if (errno == ENOATTR) {
+ errno = ENOENT;
+ }
+ goto fail;
+ }
+
+ /*
+ * Remove the old stream from the base file fsp.
+ */
+ oret = SMB_VFS_FREMOVEXATTR(pathref_src->fsp,
+ src_xattr_name);
+ if (oret < 0) {
+ if (errno == ENOATTR) {
+ errno = ENOENT;
+ }
+ goto fail;
+ }
+
+ done:
+ errno = 0;
+ ret = 0;
+ fail:
+ TALLOC_FREE(pathref_src);
+ TALLOC_FREE(pathref_dst);
+ TALLOC_FREE(full_src);
+ TALLOC_FREE(full_dst);
+ TALLOC_FREE(src_xattr_name);
+ TALLOC_FREE(dst_xattr_name);
+ return ret;
+}
+
+static NTSTATUS walk_xattr_streams(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const struct smb_filename *smb_fname,
+ bool (*fn)(struct ea_struct *ea,
+ void *private_data),
+ void *private_data)
+{
+ NTSTATUS status;
+ char **names;
+ size_t i, num_names;
+ struct streams_xattr_config *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct streams_xattr_config,
+ return NT_STATUS_UNSUCCESSFUL);
+
+ status = get_ea_names_from_fsp(talloc_tos(),
+ smb_fname->fsp,
+ &names,
+ &num_names);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (i=0; i<num_names; i++) {
+ struct ea_struct ea;
+
+ /*
+ * We want to check with samba_private_attr_name()
+ * whether the xattr name is a private one,
+ * unfortunately it flags xattrs that begin with the
+ * default streams prefix as private.
+ *
+ * By only calling samba_private_attr_name() in case
+ * the xattr does NOT begin with the default prefix,
+ * we know that if it returns 'true' it definitely one
+ * of our internal xattr like "user.DOSATTRIB".
+ */
+ if (strncasecmp_m(names[i], SAMBA_XATTR_DOSSTREAM_PREFIX,
+ strlen(SAMBA_XATTR_DOSSTREAM_PREFIX)) != 0) {
+ if (samba_private_attr_name(names[i])) {
+ continue;
+ }
+ }
+
+ if (strncmp(names[i], config->prefix,
+ config->prefix_len) != 0) {
+ continue;
+ }
+
+ status = get_ea_value_fsp(names,
+ smb_fname->fsp,
+ names[i],
+ &ea);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Could not get ea %s for file %s: %s\n",
+ names[i],
+ smb_fname->base_name,
+ nt_errstr(status)));
+ continue;
+ }
+
+ ea.name = talloc_asprintf(
+ ea.value.data, ":%s%s",
+ names[i] + config->prefix_len,
+ config->store_stream_type ? "" : ":$DATA");
+ if (ea.name == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ continue;
+ }
+
+ if (!fn(&ea, private_data)) {
+ TALLOC_FREE(ea.value.data);
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(ea.value.data);
+ }
+
+ TALLOC_FREE(names);
+ return NT_STATUS_OK;
+}
+
+static bool add_one_stream(TALLOC_CTX *mem_ctx, unsigned int *num_streams,
+ struct stream_struct **streams,
+ const char *name, off_t size,
+ off_t alloc_size)
+{
+ struct stream_struct *tmp;
+
+ tmp = talloc_realloc(mem_ctx, *streams, struct stream_struct,
+ (*num_streams)+1);
+ if (tmp == NULL) {
+ return false;
+ }
+
+ tmp[*num_streams].name = talloc_strdup(tmp, name);
+ if (tmp[*num_streams].name == NULL) {
+ return false;
+ }
+
+ tmp[*num_streams].size = size;
+ tmp[*num_streams].alloc_size = alloc_size;
+
+ *streams = tmp;
+ *num_streams += 1;
+ return true;
+}
+
+struct streaminfo_state {
+ TALLOC_CTX *mem_ctx;
+ vfs_handle_struct *handle;
+ unsigned int num_streams;
+ struct stream_struct *streams;
+ NTSTATUS status;
+};
+
+static bool collect_one_stream(struct ea_struct *ea, void *private_data)
+{
+ struct streaminfo_state *state =
+ (struct streaminfo_state *)private_data;
+
+ if (!add_one_stream(state->mem_ctx,
+ &state->num_streams, &state->streams,
+ ea->name, ea->value.length-1,
+ smb_roundup(state->handle->conn,
+ ea->value.length-1))) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return false;
+ }
+
+ return true;
+}
+
+static NTSTATUS streams_xattr_fstreaminfo(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ NTSTATUS status;
+ struct streaminfo_state state;
+
+ state.streams = *pstreams;
+ state.num_streams = *pnum_streams;
+ state.mem_ctx = mem_ctx;
+ state.handle = handle;
+ state.status = NT_STATUS_OK;
+
+ status = walk_xattr_streams(handle,
+ fsp,
+ fsp->fsp_name,
+ collect_one_stream,
+ &state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(state.streams);
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ TALLOC_FREE(state.streams);
+ return state.status;
+ }
+
+ *pnum_streams = state.num_streams;
+ *pstreams = state.streams;
+
+ return SMB_VFS_NEXT_FSTREAMINFO(handle,
+ fsp,
+ mem_ctx,
+ pnum_streams,
+ pstreams);
+}
+
+static uint32_t streams_xattr_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | FILE_NAMED_STREAMS;
+}
+
+static int streams_xattr_connect(vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct streams_xattr_config *config;
+ const char *default_prefix = SAMBA_XATTR_DOSSTREAM_PREFIX;
+ const char *prefix;
+ int rc;
+
+ rc = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (rc != 0) {
+ return rc;
+ }
+
+ config = talloc_zero(handle->conn, struct streams_xattr_config);
+ if (config == NULL) {
+ DEBUG(1, ("talloc_zero() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ prefix = lp_parm_const_string(SNUM(handle->conn),
+ "streams_xattr", "prefix",
+ default_prefix);
+ config->prefix = talloc_strdup(config, prefix);
+ if (config->prefix == NULL) {
+ DEBUG(1, ("talloc_strdup() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+ config->prefix_len = strlen(config->prefix);
+ DEBUG(10, ("streams_xattr using stream prefix: %s\n", config->prefix));
+
+ config->store_stream_type = lp_parm_bool(SNUM(handle->conn),
+ "streams_xattr",
+ "store_stream_type",
+ true);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct stream_xattr_config,
+ return -1);
+
+ return 0;
+}
+
+static ssize_t streams_xattr_pwrite(vfs_handle_struct *handle,
+ files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+ struct ea_struct ea;
+ NTSTATUS status;
+ int ret;
+
+ DEBUG(10, ("streams_xattr_pwrite called for %d bytes\n", (int)n));
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+ }
+
+ if (!streams_xattr_recheck(sio)) {
+ return -1;
+ }
+
+ if ((offset + n) >= lp_smbd_max_xattr_size(SNUM(handle->conn))) {
+ /*
+ * Requested write is beyond what can be read based on
+ * samba configuration.
+ * ReFS returns STATUS_FILESYSTEM_LIMITATION, which causes
+ * entire file to be skipped by File Explorer. VFAT returns
+ * NT_STATUS_OBJECT_NAME_COLLISION causes user to be prompted
+ * to skip writing metadata, but copy data.
+ */
+ DBG_ERR("Write to xattr [%s] on file [%s] exceeds maximum "
+ "supported extended attribute size. "
+ "Depending on filesystem type and operating system "
+ "(OS) specifics, this value may be increased using "
+ "the value of the parameter: "
+ "smbd max xattr size = <bytes>. Consult OS and "
+ "filesystem manpages prior to increasing this limit.\n",
+ sio->xattr_name, sio->base);
+ errno = EOVERFLOW;
+ return -1;
+ }
+
+ status = get_ea_value_fsp(talloc_tos(),
+ fsp->base_fsp,
+ sio->xattr_name,
+ &ea);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ if ((offset + n) > ea.value.length-1) {
+ uint8_t *tmp;
+
+ tmp = talloc_realloc(talloc_tos(), ea.value.data, uint8_t,
+ offset + n + 1);
+
+ if (tmp == NULL) {
+ TALLOC_FREE(ea.value.data);
+ errno = ENOMEM;
+ return -1;
+ }
+ ea.value.data = tmp;
+ ea.value.length = offset + n + 1;
+ ea.value.data[offset+n] = 0;
+ }
+
+ memcpy(ea.value.data + offset, data, n);
+
+ ret = SMB_VFS_FSETXATTR(fsp->base_fsp,
+ sio->xattr_name,
+ ea.value.data,
+ ea.value.length,
+ 0);
+ TALLOC_FREE(ea.value.data);
+
+ if (ret == -1) {
+ return -1;
+ }
+
+ return n;
+}
+
+static ssize_t streams_xattr_pread(vfs_handle_struct *handle,
+ files_struct *fsp, void *data,
+ size_t n, off_t offset)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+ struct ea_struct ea;
+ NTSTATUS status;
+ size_t length, overlap;
+
+ DEBUG(10, ("streams_xattr_pread: offset=%d, size=%d\n",
+ (int)offset, (int)n));
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+ }
+
+ if (!streams_xattr_recheck(sio)) {
+ return -1;
+ }
+
+ status = get_ea_value_fsp(talloc_tos(),
+ fsp->base_fsp,
+ sio->xattr_name,
+ &ea);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ length = ea.value.length-1;
+
+ DBG_DEBUG("get_ea_value_fsp returned %d bytes\n",
+ (int)length);
+
+ /* Attempt to read past EOF. */
+ if (length <= offset) {
+ return 0;
+ }
+
+ overlap = (offset + n) > length ? (length - offset) : n;
+ memcpy(data, ea.value.data + offset, overlap);
+
+ TALLOC_FREE(ea.value.data);
+ return overlap;
+}
+
+struct streams_xattr_pread_state {
+ ssize_t nread;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void streams_xattr_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *streams_xattr_pread_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct streams_xattr_pread_state *state = NULL;
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct streams_xattr_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (sio == NULL) {
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp,
+ data, n, offset);
+ if (tevent_req_nomem(req, subreq)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, streams_xattr_pread_done, req);
+ return req;
+ }
+
+ state->nread = SMB_VFS_PREAD(fsp, data, n, offset);
+ if (state->nread != n) {
+ if (state->nread != -1) {
+ errno = EIO;
+ }
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void streams_xattr_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct streams_xattr_pread_state *state = tevent_req_data(
+ req, struct streams_xattr_pread_state);
+
+ state->nread = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ if (tevent_req_error(req, state->vfs_aio_state.error)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static ssize_t streams_xattr_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct streams_xattr_pread_state *state = tevent_req_data(
+ req, struct streams_xattr_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->nread;
+}
+
+struct streams_xattr_pwrite_state {
+ ssize_t nwritten;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void streams_xattr_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *streams_xattr_pwrite_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct streams_xattr_pwrite_state *state = NULL;
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct streams_xattr_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (sio == NULL) {
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp,
+ data, n, offset);
+ if (tevent_req_nomem(req, subreq)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, streams_xattr_pwrite_done, req);
+ return req;
+ }
+
+ state->nwritten = SMB_VFS_PWRITE(fsp, data, n, offset);
+ if (state->nwritten != n) {
+ if (state->nwritten != -1) {
+ errno = EIO;
+ }
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void streams_xattr_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct streams_xattr_pwrite_state *state = tevent_req_data(
+ req, struct streams_xattr_pwrite_state);
+
+ state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+
+ if (tevent_req_error(req, state->vfs_aio_state.error)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static ssize_t streams_xattr_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct streams_xattr_pwrite_state *state = tevent_req_data(
+ req, struct streams_xattr_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->nwritten;
+}
+
+static int streams_xattr_ftruncate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ off_t offset)
+{
+ int ret;
+ uint8_t *tmp;
+ struct ea_struct ea;
+ NTSTATUS status;
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ DEBUG(10, ("streams_xattr_ftruncate called for file %s offset %.0f\n",
+ fsp_str_dbg(fsp), (double)offset));
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FTRUNCATE(handle, fsp, offset);
+ }
+
+ if (!streams_xattr_recheck(sio)) {
+ return -1;
+ }
+
+ status = get_ea_value_fsp(talloc_tos(),
+ fsp->base_fsp,
+ sio->xattr_name,
+ &ea);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ tmp = talloc_realloc(talloc_tos(), ea.value.data, uint8_t,
+ offset + 1);
+
+ if (tmp == NULL) {
+ TALLOC_FREE(ea.value.data);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /* Did we expand ? */
+ if (ea.value.length < offset + 1) {
+ memset(&tmp[ea.value.length], '\0',
+ offset + 1 - ea.value.length);
+ }
+
+ ea.value.data = tmp;
+ ea.value.length = offset + 1;
+ ea.value.data[offset] = 0;
+
+ ret = SMB_VFS_FSETXATTR(fsp->base_fsp,
+ sio->xattr_name,
+ ea.value.data,
+ ea.value.length,
+ 0);
+
+ TALLOC_FREE(ea.value.data);
+
+ if (ret == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int streams_xattr_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ DEBUG(10, ("streams_xattr_fallocate called for file %s offset %.0f"
+ "len = %.0f\n",
+ fsp_str_dbg(fsp), (double)offset, (double)len));
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len);
+ }
+
+ if (!streams_xattr_recheck(sio)) {
+ return -1;
+ }
+
+ /* Let the pwrite code path handle it. */
+ errno = ENOSYS;
+ return -1;
+}
+
+static int streams_xattr_fchown(vfs_handle_struct *handle, files_struct *fsp,
+ uid_t uid, gid_t gid)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid);
+ }
+
+ return 0;
+}
+
+static int streams_xattr_fchmod(vfs_handle_struct *handle,
+ files_struct *fsp,
+ mode_t mode)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+ }
+
+ return 0;
+}
+
+static ssize_t streams_xattr_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ void *value,
+ size_t size)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size);
+ }
+
+ errno = ENOTSUP;
+ return -1;
+}
+
+static ssize_t streams_xattr_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ char *list,
+ size_t size)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size);
+ }
+
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int streams_xattr_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
+ }
+
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int streams_xattr_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value,
+ size, flags);
+ }
+
+ errno = ENOTSUP;
+ return -1;
+}
+
+struct streams_xattr_fsync_state {
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void streams_xattr_fsync_done(struct tevent_req *subreq);
+
+static struct tevent_req *streams_xattr_fsync_send(
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct streams_xattr_fsync_state *state = NULL;
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct streams_xattr_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (sio == NULL) {
+ subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp);
+ if (tevent_req_nomem(req, subreq)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, streams_xattr_fsync_done, req);
+ return req;
+ }
+
+ /*
+ * There's no pathname based sync variant and we don't have access to
+ * the basefile handle, so we can't do anything here.
+ */
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void streams_xattr_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct streams_xattr_fsync_state *state = tevent_req_data(
+ req, struct streams_xattr_fsync_state);
+
+ state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->ret != 0) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int streams_xattr_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct streams_xattr_fsync_state *state = tevent_req_data(
+ req, struct streams_xattr_fsync_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static bool streams_xattr_lock(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int op,
+ off_t offset,
+ off_t count,
+ int type)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type);
+ }
+
+ return true;
+}
+
+static bool streams_xattr_getlock(vfs_handle_struct *handle,
+ files_struct *fsp,
+ off_t *poffset,
+ off_t *pcount,
+ int *ptype,
+ pid_t *ppid)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset,
+ pcount, ptype, ppid);
+ }
+
+ errno = ENOTSUP;
+ return false;
+}
+
+static int streams_xattr_filesystem_sharemode(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle,
+ fsp,
+ share_access,
+ access_mask);
+ }
+
+ return 0;
+}
+
+static int streams_xattr_linux_setlease(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int leasetype)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype);
+ }
+
+ return 0;
+}
+
+static bool streams_xattr_strict_lock_check(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct lock_struct *plock)
+{
+ struct stream_io *sio =
+ (struct stream_io *)VFS_FETCH_FSP_EXTENSION(handle, fsp);
+
+ if (sio == NULL) {
+ return SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock);
+ }
+
+ return true;
+}
+
+static int streams_xattr_fcntl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int cmd,
+ va_list cmd_arg)
+{
+ va_list dup_cmd_arg;
+ void *arg;
+ int ret;
+
+ if (fsp_is_alternate_stream(fsp)) {
+ switch (cmd) {
+ case F_GETFL:
+ case F_SETFL:
+ break;
+ default:
+ DBG_ERR("Unsupported fcntl() cmd [%d] on [%s]\n",
+ cmd, fsp_str_dbg(fsp));
+ errno = EINVAL;
+ return -1;
+ }
+ }
+
+ va_copy(dup_cmd_arg, cmd_arg);
+ arg = va_arg(dup_cmd_arg, void *);
+
+ ret = SMB_VFS_NEXT_FCNTL(handle, fsp, cmd, arg);
+
+ va_end(dup_cmd_arg);
+
+ return ret;
+}
+
+static struct vfs_fn_pointers vfs_streams_xattr_fns = {
+ .fs_capabilities_fn = streams_xattr_fs_capabilities,
+ .connect_fn = streams_xattr_connect,
+ .openat_fn = streams_xattr_openat,
+ .close_fn = streams_xattr_close,
+ .stat_fn = streams_xattr_stat,
+ .fstat_fn = streams_xattr_fstat,
+ .lstat_fn = streams_xattr_lstat,
+ .pread_fn = streams_xattr_pread,
+ .pwrite_fn = streams_xattr_pwrite,
+ .pread_send_fn = streams_xattr_pread_send,
+ .pread_recv_fn = streams_xattr_pread_recv,
+ .pwrite_send_fn = streams_xattr_pwrite_send,
+ .pwrite_recv_fn = streams_xattr_pwrite_recv,
+ .unlinkat_fn = streams_xattr_unlinkat,
+ .renameat_fn = streams_xattr_renameat,
+ .ftruncate_fn = streams_xattr_ftruncate,
+ .fallocate_fn = streams_xattr_fallocate,
+ .fstreaminfo_fn = streams_xattr_fstreaminfo,
+
+ .fsync_send_fn = streams_xattr_fsync_send,
+ .fsync_recv_fn = streams_xattr_fsync_recv,
+
+ .lock_fn = streams_xattr_lock,
+ .getlock_fn = streams_xattr_getlock,
+ .filesystem_sharemode_fn = streams_xattr_filesystem_sharemode,
+ .linux_setlease_fn = streams_xattr_linux_setlease,
+ .strict_lock_check_fn = streams_xattr_strict_lock_check,
+ .fcntl_fn = streams_xattr_fcntl,
+
+ .fchown_fn = streams_xattr_fchown,
+ .fchmod_fn = streams_xattr_fchmod,
+
+ .fgetxattr_fn = streams_xattr_fgetxattr,
+ .flistxattr_fn = streams_xattr_flistxattr,
+ .fremovexattr_fn = streams_xattr_fremovexattr,
+ .fsetxattr_fn = streams_xattr_fsetxattr,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_streams_xattr_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "streams_xattr",
+ &vfs_streams_xattr_fns);
+}
diff --git a/source3/modules/vfs_syncops.c b/source3/modules/vfs_syncops.c
new file mode 100644
index 0000000..a0d9809
--- /dev/null
+++ b/source3/modules/vfs_syncops.c
@@ -0,0 +1,418 @@
+/*
+ * ensure meta data operations are performed synchronously
+ *
+ * Copyright (C) Andrew Tridgell 2007
+ * Copyright (C) Christian Ambach, 2010-2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "source3/smbd/dir.h"
+
+/*
+
+ Some filesystems (even some journaled filesystems) require that a
+ fsync() be performed on many meta data operations to ensure that the
+ operation is guaranteed to remain in the filesystem after a power
+ failure. This is particularly important for some cluster filesystems
+ which are participating in a node failover system with clustered
+ Samba
+
+ On those filesystems this module provides a way to perform those
+ operations safely.
+
+ most of the performance loss with this module is in fsync on close().
+ You can disable that with
+ syncops:onclose = no
+ that can be set either globally or per share.
+
+ On certain filesystems that only require the last data written to be
+ fsync()'ed, you can disable the metadata synchronization of this module with
+ syncops:onmeta = no
+ This option can be set either globally or per share.
+
+ you can also disable the module completely for a share with
+ syncops:disable = true
+
+ */
+
+struct syncops_config_data {
+ bool onclose;
+ bool onmeta;
+ bool disable;
+};
+
+/*
+ given a filename, find the parent directory
+ */
+static char *parent_dir(TALLOC_CTX *mem_ctx, const char *name)
+{
+ const char *p = strrchr(name, '/');
+ if (p == NULL) {
+ return talloc_strdup(mem_ctx, ".");
+ }
+ return talloc_strndup(mem_ctx, name, (p+1) - name);
+}
+
+/*
+ fsync a directory by name
+ */
+static void syncops_sync_directory(connection_struct *conn,
+ char *dname)
+{
+ struct smb_Dir *dir_hnd = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename smb_dname = { .base_name = dname };
+ NTSTATUS status;
+
+ status = OpenDir(talloc_tos(),
+ conn,
+ &smb_dname,
+ "*",
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return;
+ }
+
+ dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+
+ smb_vfs_fsync_sync(dirfsp);
+
+ TALLOC_FREE(dir_hnd);
+}
+
+/*
+ sync two meta data changes for 2 names
+ */
+static void syncops_two_names(connection_struct *conn,
+ const struct smb_filename *name1,
+ const struct smb_filename *name2)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ char *parent1, *parent2;
+ parent1 = parent_dir(tmp_ctx, name1->base_name);
+ parent2 = parent_dir(tmp_ctx, name2->base_name);
+ if (!parent1 || !parent2) {
+ talloc_free(tmp_ctx);
+ return;
+ }
+ syncops_sync_directory(conn, parent1);
+ if (strcmp(parent1, parent2) != 0) {
+ syncops_sync_directory(conn, parent2);
+ }
+ talloc_free(tmp_ctx);
+}
+
+/*
+ sync two meta data changes for 1 names
+ */
+static void syncops_smb_fname(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ char *parent = NULL;
+ if (smb_fname != NULL) {
+ parent = parent_dir(NULL, smb_fname->base_name);
+ if (parent != NULL) {
+ syncops_sync_directory(conn, parent);
+ talloc_free(parent);
+ }
+ }
+}
+
+
+/*
+ renameat needs special handling, as we may need to fsync two directories
+ */
+static int syncops_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+
+ int ret;
+ struct smb_filename *full_fname_src = NULL;
+ struct smb_filename *full_fname_dst = NULL;
+ struct syncops_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct syncops_config_data,
+ return -1);
+
+ ret = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ if (ret == -1) {
+ return ret;
+ }
+ if (config->disable) {
+ return ret;
+ }
+ if (!config->onmeta) {
+ return ret;
+ }
+
+ full_fname_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_fname_src == NULL) {
+ errno = ENOMEM;
+ return ret;
+ }
+ full_fname_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_fname_dst == NULL) {
+ TALLOC_FREE(full_fname_src);
+ errno = ENOMEM;
+ return ret;
+ }
+ syncops_two_names(handle->conn,
+ full_fname_src,
+ full_fname_dst);
+ TALLOC_FREE(full_fname_src);
+ TALLOC_FREE(full_fname_dst);
+ return ret;
+}
+
+#define SYNCOPS_NEXT_SMB_FNAME(op, fname, args) do { \
+ int ret; \
+ struct smb_filename *full_fname = NULL; \
+ struct syncops_config_data *config; \
+ SMB_VFS_HANDLE_GET_DATA(handle, config, \
+ struct syncops_config_data, \
+ return -1); \
+ ret = SMB_VFS_NEXT_ ## op args; \
+ if (ret != 0) { \
+ return ret; \
+ } \
+ if (config->disable) { \
+ return ret; \
+ } \
+ if (!config->onmeta) { \
+ return ret; \
+ } \
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(), \
+ dirfsp, \
+ smb_fname); \
+ if (full_fname == NULL) { \
+ return ret; \
+ } \
+ syncops_smb_fname(dirfsp->conn, full_fname); \
+ TALLOC_FREE(full_fname); \
+ return ret; \
+} while (0)
+
+static int syncops_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ SYNCOPS_NEXT_SMB_FNAME(SYMLINKAT,
+ smb_fname,
+ (handle,
+ link_contents,
+ dirfsp,
+ smb_fname));
+}
+
+static int syncops_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ int ret;
+ struct syncops_config_data *config;
+ struct smb_filename *old_full_fname = NULL;
+ struct smb_filename *new_full_fname = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct syncops_config_data,
+ return -1);
+
+ ret = SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+
+ if (ret == -1) {
+ return ret;
+ }
+ if (config->disable) {
+ return ret;
+ }
+ if (!config->onmeta) {
+ return ret;
+ }
+
+ old_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ old_smb_fname);
+ if (old_full_fname == NULL) {
+ return ret;
+ }
+ new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (new_full_fname == NULL) {
+ TALLOC_FREE(old_full_fname);
+ return ret;
+ }
+ syncops_two_names(handle->conn,
+ old_full_fname,
+ new_full_fname);
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(new_full_fname);
+ return ret;
+}
+
+static int syncops_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ SYNCOPS_NEXT_SMB_FNAME(OPENAT, (how->flags & O_CREAT ? smb_fname : NULL),
+ (handle, dirfsp, smb_fname, fsp, how));
+}
+
+static int syncops_unlinkat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ SYNCOPS_NEXT_SMB_FNAME(UNLINKAT,
+ smb_fname,
+ (handle,
+ dirfsp,
+ smb_fname,
+ flags));
+}
+
+static int syncops_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ SYNCOPS_NEXT_SMB_FNAME(MKNODAT,
+ smb_fname,
+ (handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev));
+}
+
+static int syncops_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ SYNCOPS_NEXT_SMB_FNAME(MKDIRAT,
+ full_fname,
+ (handle,
+ dirfsp,
+ smb_fname,
+ mode));
+}
+
+/* close needs to be handled specially */
+static int syncops_close(vfs_handle_struct *handle, files_struct *fsp)
+{
+ struct syncops_config_data *config;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct syncops_config_data,
+ return -1);
+
+ if (fsp->fsp_flags.can_write && config->onclose) {
+ /* ideally we'd only do this if we have written some
+ data, but there is no flag for that in fsp yet. */
+ fsync(fsp_get_io_fd(fsp));
+ }
+ return SMB_VFS_NEXT_CLOSE(handle, fsp);
+}
+
+static int syncops_connect(struct vfs_handle_struct *handle, const char *service,
+ const char *user)
+{
+
+ struct syncops_config_data *config;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ config = talloc_zero(handle->conn, struct syncops_config_data);
+ if (!config) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ DEBUG(0, ("talloc_zero() failed\n"));
+ return -1;
+ }
+
+ config->onclose = lp_parm_bool(SNUM(handle->conn), "syncops",
+ "onclose", true);
+
+ config->onmeta = lp_parm_bool(SNUM(handle->conn), "syncops",
+ "onmeta", true);
+
+ config->disable = lp_parm_bool(SNUM(handle->conn), "syncops",
+ "disable", false);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct syncops_config_data,
+ return -1);
+
+ return 0;
+
+}
+
+static struct vfs_fn_pointers vfs_syncops_fns = {
+ .connect_fn = syncops_connect,
+ .mkdirat_fn = syncops_mkdirat,
+ .openat_fn = syncops_openat,
+ .renameat_fn = syncops_renameat,
+ .unlinkat_fn = syncops_unlinkat,
+ .symlinkat_fn = syncops_symlinkat,
+ .linkat_fn = syncops_linkat,
+ .mknodat_fn = syncops_mknodat,
+ .close_fn = syncops_close,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_syncops_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "syncops",
+ &vfs_syncops_fns);
+
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ return ret;
+}
diff --git a/source3/modules/vfs_time_audit.c b/source3/modules/vfs_time_audit.c
new file mode 100644
index 0000000..59bc688
--- /dev/null
+++ b/source3/modules/vfs_time_audit.c
@@ -0,0 +1,2812 @@
+/*
+ * Time auditing VFS module for samba. Log time taken for VFS call to syslog
+ * facility.
+ *
+ * Copyright (C) Abhidnya Chirmule <achirmul@in.ibm.com> 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module implements logging for time taken for all Samba VFS operations.
+ *
+ * vfs objects = time_audit
+ */
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "ntioctl.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static double audit_timeout;
+
+static void smb_time_audit_log_msg(const char *syscallname, double elapsed,
+ const char *msg)
+{
+ DEBUG(0, ("WARNING: VFS call \"%s\" took unexpectedly long "
+ "(%.2f seconds) %s%s-- Validate that file and storage "
+ "subsystems are operating normally\n", syscallname,
+ elapsed, (msg != NULL) ? msg : "",
+ (msg != NULL) ? " " : ""));
+}
+
+static void smb_time_audit_log(const char *syscallname, double elapsed)
+{
+ smb_time_audit_log_msg(syscallname, elapsed, NULL);
+}
+
+static void smb_time_audit_log_fsp(const char *syscallname, double elapsed,
+ const struct files_struct *fsp)
+{
+ char *base_name = NULL;
+ char *connectpath = NULL;
+ char *msg = NULL;
+
+ if (fsp == NULL) {
+ smb_time_audit_log(syscallname, elapsed);
+ return;
+ }
+ if (fsp->conn)
+ connectpath = fsp->conn->connectpath;
+ if (fsp->fsp_name)
+ base_name = fsp->fsp_name->base_name;
+
+ if (connectpath != NULL && base_name != NULL) {
+ msg = talloc_asprintf(talloc_tos(), "filename = \"%s/%s\"",
+ connectpath, base_name);
+ } else if (connectpath != NULL && base_name == NULL) {
+ msg = talloc_asprintf(talloc_tos(), "connectpath = \"%s\", "
+ "base_name = <NULL>",
+ connectpath);
+ } else if (connectpath == NULL && base_name != NULL) {
+ msg = talloc_asprintf(talloc_tos(), "connectpath = <NULL>, "
+ "base_name = \"%s\"",
+ base_name);
+ } else { /* connectpath == NULL && base_name == NULL */
+ msg = talloc_asprintf(talloc_tos(), "connectpath = <NULL>, "
+ "base_name = <NULL>");
+ }
+ smb_time_audit_log_msg(syscallname, elapsed, msg);
+ TALLOC_FREE(msg);
+}
+
+static void smb_time_audit_log_at(const char *syscallname,
+ double elapsed,
+ const struct files_struct *dir_fsp,
+ const struct smb_filename *smb_fname)
+{
+ char *msg = NULL;
+
+ msg = talloc_asprintf(talloc_tos(),
+ "filename = \"%s/%s/%s\"",
+ dir_fsp->conn->connectpath,
+ dir_fsp->fsp_name->base_name,
+ smb_fname->base_name);
+
+ smb_time_audit_log_msg(syscallname, elapsed, msg);
+ TALLOC_FREE(msg);
+}
+
+static void smb_time_audit_log_fname(const char *syscallname, double elapsed,
+ const char *fname)
+{
+ char cwd[PATH_MAX];
+ char *msg = NULL;
+
+ if (getcwd(cwd, sizeof(cwd)) == NULL) {
+ snprintf(cwd, sizeof(cwd), "<getcwd() error %d>", errno);
+ }
+ if (fname != NULL) {
+ msg = talloc_asprintf(talloc_tos(),
+ "cwd = \"%s\", filename = \"%s\"",
+ cwd, fname);
+ } else {
+ msg = talloc_asprintf(talloc_tos(),
+ "cwd = \"%s\", filename = <NULL>",
+ cwd);
+ }
+ smb_time_audit_log_msg(syscallname, elapsed, msg);
+ TALLOC_FREE(msg);
+}
+
+static void smb_time_audit_log_smb_fname(const char *syscallname, double elapsed,
+ const struct smb_filename *smb_fname)
+{
+ if (smb_fname != NULL) {
+ smb_time_audit_log_fname(syscallname, elapsed,
+ smb_fname->base_name);
+ } else {
+ smb_time_audit_log_fname(syscallname, elapsed,
+ "smb_fname = <NULL>");
+ }
+}
+
+static int smb_time_audit_connect(vfs_handle_struct *handle,
+ const char *svc, const char *user)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ if (!handle) {
+ return -1;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_CONNECT(handle, svc, user);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_msg("connect", timediff, user);
+ }
+ return result;
+}
+
+static void smb_time_audit_disconnect(vfs_handle_struct *handle)
+{
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("disconnect", timediff);
+ }
+}
+
+static uint64_t smb_time_audit_disk_free(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ uint64_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_DISK_FREE(handle, smb_fname, bsize, dfree, dsize);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ /* Don't have a reasonable notion of failure here */
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("disk_free",
+ timediff,
+ smb_fname->base_name);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_GET_QUOTA(handle, smb_fname, qtype, id, qt);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("get_quota",
+ timediff,
+ smb_fname->base_name);
+ }
+ return result;
+}
+
+static int smb_time_audit_set_quota(struct vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype, unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SET_QUOTA(handle, qtype, id, qt);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("set_quota", timediff);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_GET_SHADOW_COPY_DATA(handle, fsp,
+ shadow_copy_data, labels);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("get_shadow_copy_data", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("statvfs", timediff,
+ smb_fname->base_name);
+ }
+
+ return result;
+}
+
+static uint32_t smb_time_audit_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ uint32_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("fs_capabilities", timediff);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_get_dfs_referrals(
+ struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_GET_DFS_REFERRALS(handle, r);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("get_dfs_referrals", timediff);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_CREATE_DFS_PATHAT(handle,
+ dirfsp,
+ smb_fname,
+ reflist,
+ referral_count);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("create_dfs_pathat",
+ timediff,
+ full_fname);
+ }
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static NTSTATUS smb_time_audit_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_READ_DFS_PATHAT(handle,
+ mem_ctx,
+ dirfsp,
+ smb_fname,
+ ppreflist,
+ preferral_count);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("read_dfs_pathat",
+ timediff,
+ full_fname);
+ }
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static NTSTATUS smb_time_audit_snap_check_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ NTSTATUS status;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ status = SMB_VFS_NEXT_SNAP_CHECK_PATH(handle, mem_ctx, service_path,
+ base_volume);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("snap_check_path", timediff);
+ }
+
+ return status;
+}
+
+static NTSTATUS smb_time_audit_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path)
+{
+ NTSTATUS status;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ status = SMB_VFS_NEXT_SNAP_CREATE(handle, mem_ctx, base_volume, tstamp,
+ rw, base_path, snap_path);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2 ,&ts1) * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("snap_create", timediff);
+ }
+
+ return status;
+}
+
+static NTSTATUS smb_time_audit_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ NTSTATUS status;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ status = SMB_VFS_NEXT_SNAP_DELETE(handle, mem_ctx, base_path,
+ snap_path);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("snap_delete", timediff);
+ }
+
+ return status;
+}
+
+static DIR *smb_time_audit_fdopendir(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const char *mask, uint32_t attr)
+{
+ DIR *result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fdopendir", timediff, fsp);
+ }
+
+ return result;
+}
+
+static struct dirent *smb_time_audit_readdir(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ struct dirent *result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirp);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("readdir", timediff);
+ }
+
+ return result;
+}
+
+static void smb_time_audit_rewinddir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ SMB_VFS_NEXT_REWINDDIR(handle, dirp);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("rewinddir", timediff);
+ }
+
+}
+
+static int smb_time_audit_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("mkdirat",
+ timediff,
+ full_fname);
+ }
+
+ TALLOC_FREE(full_fname);
+
+ return result;
+}
+
+static int smb_time_audit_closedir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_CLOSEDIR(handle, dirp);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("closedir", timediff);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("openat", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_create_file(vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result_fsp,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_CREATE_FILE(
+ handle, /* handle */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ file_attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ lease, /* lease */
+ allocation_size, /* allocation_size */
+ private_flags,
+ sd, /* sd */
+ ea_list, /* ea_list */
+ result_fsp, /* result */
+ pinfo,
+ in_context_blobs, out_context_blobs); /* create context */
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ /*
+ * can't use result_fsp this time, may have
+ * invalid content causing smbd crash
+ */
+ smb_time_audit_log_smb_fname("create_file", timediff,
+ fname);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_close(vfs_handle_struct *handle, files_struct *fsp)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("close", timediff, fsp);
+ }
+
+ return result;
+}
+
+static ssize_t smb_time_audit_pread(vfs_handle_struct *handle,
+ files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ ssize_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("pread", timediff, fsp);
+ }
+
+ return result;
+}
+
+struct smb_time_audit_pread_state {
+ struct files_struct *fsp;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_time_audit_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_time_audit_pread_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_time_audit_pread_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_time_audit_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_time_audit_pread_done, req);
+ return req;
+}
+
+static void smb_time_audit_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_time_audit_pread_state *state = tevent_req_data(
+ req, struct smb_time_audit_pread_state);
+
+ state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t smb_time_audit_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_time_audit_pread_state *state = tevent_req_data(
+ req, struct smb_time_audit_pread_state);
+ double timediff;
+
+ timediff = state->vfs_aio_state.duration * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("async pread", timediff, state->fsp);
+ }
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static ssize_t smb_time_audit_pwrite(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const void *data, size_t n,
+ off_t offset)
+{
+ ssize_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("pwrite", timediff, fsp);
+ }
+
+ return result;
+}
+
+struct smb_time_audit_pwrite_state {
+ struct files_struct *fsp;
+ ssize_t ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_time_audit_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_time_audit_pwrite_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp,
+ const void *data, size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_time_audit_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_time_audit_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_time_audit_pwrite_done, req);
+ return req;
+}
+
+static void smb_time_audit_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_time_audit_pwrite_state *state = tevent_req_data(
+ req, struct smb_time_audit_pwrite_state);
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t smb_time_audit_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_time_audit_pwrite_state *state = tevent_req_data(
+ req, struct smb_time_audit_pwrite_state);
+ double timediff;
+
+ timediff = state->vfs_aio_state.duration * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("async pwrite", timediff, state->fsp);
+ }
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static off_t smb_time_audit_lseek(vfs_handle_struct *handle,
+ files_struct *fsp,
+ off_t offset, int whence)
+{
+ off_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_LSEEK(handle, fsp, offset, whence);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("lseek", timediff, fsp);
+ }
+
+ return result;
+}
+
+static ssize_t smb_time_audit_sendfile(vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp,
+ const DATA_BLOB *hdr, off_t offset,
+ size_t n)
+{
+ ssize_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SENDFILE(handle, tofd, fromfsp, hdr, offset, n);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("sendfile", timediff, fromfsp);
+ }
+
+ return result;
+}
+
+static ssize_t smb_time_audit_recvfile(vfs_handle_struct *handle, int fromfd,
+ files_struct *tofsp,
+ off_t offset,
+ size_t n)
+{
+ ssize_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_RECVFILE(handle, fromfd, tofsp, offset, n);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("recvfile", timediff, tofsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *oldname,
+ files_struct *dstfsp,
+ const struct smb_filename *newname)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+ struct smb_filename *new_full_fname = NULL;
+
+ new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ newname);
+ if (new_full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ oldname,
+ dstfsp,
+ newname);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("renameat",
+ timediff,
+ new_full_fname);
+ }
+
+ TALLOC_FREE(new_full_fname);
+ return result;
+}
+
+struct smb_time_audit_fsync_state {
+ struct files_struct *fsp;
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_time_audit_fsync_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_time_audit_fsync_send(
+ struct vfs_handle_struct *handle, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev, struct files_struct *fsp)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_time_audit_fsync_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_time_audit_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+
+ subreq = SMB_VFS_NEXT_FSYNC_SEND(state, ev, handle, fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_time_audit_fsync_done, req);
+ return req;
+}
+
+static void smb_time_audit_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_time_audit_fsync_state *state = tevent_req_data(
+ req, struct smb_time_audit_fsync_state);
+
+ state->ret = SMB_VFS_FSYNC_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static int smb_time_audit_fsync_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_time_audit_fsync_state *state = tevent_req_data(
+ req, struct smb_time_audit_fsync_state);
+ double timediff;
+
+ timediff = state->vfs_aio_state.duration * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("async fsync", timediff, state->fsp);
+ }
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static int smb_time_audit_stat(vfs_handle_struct *handle,
+ struct smb_filename *fname)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_STAT(handle, fname);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("stat", timediff, fname);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fstat(vfs_handle_struct *handle, files_struct *fsp,
+ SMB_STRUCT_STAT *sbuf)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fstat", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_lstat(vfs_handle_struct *handle,
+ struct smb_filename *path)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_LSTAT(handle, path);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("lstat", timediff, path);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FSTATAT(handle, dirfsp, smb_fname, sbuf, flags);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("fstatat", timediff, smb_fname);
+ }
+
+ return result;
+}
+
+static uint64_t smb_time_audit_get_alloc_size(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ uint64_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_GET_ALLOC_SIZE(handle, fsp, sbuf);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("get_alloc_size", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *path,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ path);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ path,
+ flags);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("unlinkat", timediff, full_fname);
+ }
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int smb_time_audit_fchmod(vfs_handle_struct *handle, files_struct *fsp,
+ mode_t mode)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fchmod", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fchown(vfs_handle_struct *handle, files_struct *fsp,
+ uid_t uid, gid_t gid)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fchown", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("lchown",
+ timediff,
+ smb_fname->base_name);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("chdir",
+ timediff,
+ smb_fname->base_name);
+ }
+
+ return result;
+}
+
+static struct smb_filename *smb_time_audit_getwd(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx)
+{
+ struct smb_filename *result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_GETWD(handle, mem_ctx);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("getwd", timediff);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fntimes", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_ftruncate(vfs_handle_struct *handle,
+ files_struct *fsp,
+ off_t len)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FTRUNCATE(handle, fsp, len);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("ftruncate", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fallocate(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ int result;
+ int saved_errno = 0;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FALLOCATE(handle, fsp, mode, offset, len);
+ if (result == -1) {
+ saved_errno = errno;
+ }
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fallocate", timediff, fsp);
+ }
+ if (result == -1) {
+ errno = saved_errno;
+ }
+ return result;
+}
+
+static bool smb_time_audit_lock(vfs_handle_struct *handle, files_struct *fsp,
+ int op, off_t offset, off_t count,
+ int type)
+{
+ bool result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_LOCK(handle, fsp, op, offset, count, type);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("lock", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_access,
+ uint32_t access_mask)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FILESYSTEM_SHAREMODE(handle,
+ fsp,
+ share_access,
+ access_mask);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("filesystem_sharemode", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fcntl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ int cmd, va_list cmd_arg)
+{
+ void *arg;
+ va_list dup_cmd_arg;
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ va_copy(dup_cmd_arg, cmd_arg);
+ arg = va_arg(dup_cmd_arg, void *);
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FCNTL(handle, fsp, cmd, arg);
+ clock_gettime_mono(&ts2);
+ va_end(dup_cmd_arg);
+
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fcntl", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_linux_setlease(vfs_handle_struct *handle,
+ files_struct *fsp,
+ int leasetype)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_LINUX_SETLEASE(handle, fsp, leasetype);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("linux_setlease", timediff, fsp);
+ }
+
+ return result;
+}
+
+static bool smb_time_audit_getlock(vfs_handle_struct *handle,
+ files_struct *fsp,
+ off_t *poffset, off_t *pcount,
+ int *ptype, pid_t *ppid)
+{
+ bool result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_GETLOCK(handle, fsp, poffset, pcount, ptype,
+ ppid);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("getlock", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ new_smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SYMLINKAT(handle,
+ link_contents,
+ dirfsp,
+ new_smb_fname);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("symlinkat", timediff,
+ full_fname->base_name);
+ }
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int smb_time_audit_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_READLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("readlinkat", timediff,
+ full_fname->base_name);
+ }
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static int smb_time_audit_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ struct smb_filename *new_full_fname = NULL;
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (new_full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("linkat", timediff,
+ new_full_fname->base_name);
+ }
+
+ TALLOC_FREE(new_full_fname);
+ return result;
+}
+
+static int smb_time_audit_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ struct smb_filename *full_fname = NULL;
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_MKNODAT(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("mknodat", timediff, full_fname);
+ }
+
+ TALLOC_FREE(full_fname);
+ return result;
+}
+
+static struct smb_filename *smb_time_audit_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *result_fname;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("realpath", timediff,
+ smb_fname->base_name);
+ }
+
+ return result_fname;
+}
+
+static int smb_time_audit_fchflags(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FCHFLAGS(handle, fsp, flags);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_smb_fname("chflags",
+ timediff,
+ fsp->fsp_name);
+ }
+
+ return result;
+}
+
+static struct file_id smb_time_audit_file_id_create(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ struct file_id id_zero;
+ struct file_id result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ ZERO_STRUCT(id_zero);
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FILE_ID_CREATE(handle, sbuf);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("file_id_create", timediff);
+ }
+
+ return result;
+}
+
+static uint64_t smb_time_audit_fs_file_id(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ uint64_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FS_FILE_ID(handle, sbuf);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("fs_file_id", timediff);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_fstreaminfo(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *pnum_streams,
+ struct stream_struct **pstreams)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FSTREAMINFO(handle, fsp, mem_ctx,
+ pnum_streams, pstreams);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fstreaminfo", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_get_real_filename_at(
+ struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_GET_REAL_FILENAME_AT(
+ handle, dirfsp, name, mem_ctx, found_name);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("get_real_filename_at",
+ timediff,
+ fsp_str_dbg(dirfsp));
+ }
+
+ return result;
+}
+
+static const char *smb_time_audit_connectpath(
+ vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ const char *result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_CONNECTPATH(handle, dirfsp, smb_fname);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("connectpath", timediff,
+ smb_fname->base_name);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_BRL_LOCK_WINDOWS(handle, br_lck, plock);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("brl_lock_windows", timediff,
+ brl_fsp(br_lck));
+ }
+
+ return result;
+}
+
+static bool smb_time_audit_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ bool result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_BRL_UNLOCK_WINDOWS(handle, br_lck, plock);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("brl_unlock_windows", timediff,
+ brl_fsp(br_lck));
+ }
+
+ return result;
+}
+
+static bool smb_time_audit_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock)
+{
+ bool result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_STRICT_LOCK_CHECK(handle, fsp, plock);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("strict_lock_check", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_translate_name(struct vfs_handle_struct *handle,
+ const char *name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_TRANSLATE_NAME(handle, name, direction, mem_ctx,
+ mapped_name);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("translate_name", timediff, name);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_PARENT_PATHNAME(handle,
+ mem_ctx,
+ smb_fname_in,
+ parent_dir_out,
+ atname_out);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("parent_pathname",
+ timediff,
+ smb_fname_in->base_name);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags,
+ const uint8_t *_in_data,
+ uint32_t in_len,
+ uint8_t **_out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FSCTL(handle,
+ fsp,
+ ctx,
+ function,
+ req_flags,
+ _in_data,
+ in_len,
+ _out_data,
+ max_out_len,
+ out_len);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fsctl", timediff, fsp);
+ }
+
+ return result;
+}
+
+struct smb_time_audit_get_dos_attributes_state {
+ struct vfs_aio_state aio_state;
+ files_struct *dir_fsp;
+ const struct smb_filename *smb_fname;
+ uint32_t dosmode;
+};
+
+static void smb_time_audit_get_dos_attributes_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_time_audit_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct smb_time_audit_get_dos_attributes_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_time_audit_get_dos_attributes_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct smb_time_audit_get_dos_attributes_state) {
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ };
+
+ subreq = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_SEND(mem_ctx,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb_time_audit_get_dos_attributes_done,
+ req);
+
+ return req;
+}
+
+static void smb_time_audit_get_dos_attributes_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb_time_audit_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_time_audit_get_dos_attributes_state);
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_GET_DOS_ATTRIBUTES_RECV(subreq,
+ &state->aio_state,
+ &state->dosmode);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS smb_time_audit_get_dos_attributes_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode)
+{
+ struct smb_time_audit_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_time_audit_get_dos_attributes_state);
+ NTSTATUS status;
+ double timediff;
+
+ timediff = state->aio_state.duration * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_at("async get_dos_attributes",
+ timediff,
+ state->dir_fsp,
+ state->smb_fname);
+ }
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *aio_state = state->aio_state;
+ *dosmode = state->dosmode;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb_time_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle,
+ fsp,
+ dosmode);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fget_dos_attributes", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle,
+ fsp,
+ dosmode);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fset_dos_attributes", timediff, fsp);
+ }
+
+ return result;
+}
+
+struct time_audit_offload_read_state {
+ struct vfs_handle_struct *handle;
+ struct timespec ts_send;
+ uint32_t flags;
+ uint64_t xferlen;
+ DATA_BLOB token_blob;
+};
+
+static void smb_time_audit_offload_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_time_audit_offload_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct time_audit_offload_read_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct time_audit_offload_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->handle = handle;
+ clock_gettime_mono(&state->ts_send);
+
+ subreq = SMB_VFS_NEXT_OFFLOAD_READ_SEND(mem_ctx, ev,
+ handle, fsp,
+ fsctl, ttl,
+ offset, to_copy);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, smb_time_audit_offload_read_done, req);
+ return req;
+}
+
+static void smb_time_audit_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct time_audit_offload_read_state *state = tevent_req_data(
+ req, struct time_audit_offload_read_state);
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_OFFLOAD_READ_RECV(subreq,
+ state->handle,
+ state,
+ &state->flags,
+ &state->xferlen,
+ &state->token_blob);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smb_time_audit_offload_read_recv(
+ struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token_blob)
+{
+ struct time_audit_offload_read_state *state = tevent_req_data(
+ req, struct time_audit_offload_read_state);
+ struct timespec ts_recv;
+ double timediff;
+ NTSTATUS status;
+
+ clock_gettime_mono(&ts_recv);
+ timediff = nsec_time_diff(&ts_recv, &state->ts_send) * 1.0e-9;
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("offload_read", timediff);
+ }
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *flags = state->flags;
+ *xferlen = state->xferlen;
+ token_blob->length = state->token_blob.length;
+ token_blob->data = talloc_move(mem_ctx, &state->token_blob.data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct time_audit_offload_write_state {
+ struct timespec ts_send;
+ struct vfs_handle_struct *handle;
+ off_t copied;
+};
+static void smb_time_audit_offload_write_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_time_audit_offload_write_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct time_audit_offload_write_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct time_audit_offload_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->handle = handle;
+ clock_gettime_mono(&state->ts_send);
+ subreq = SMB_VFS_NEXT_OFFLOAD_WRITE_SEND(handle, state, ev,
+ fsctl, token, transfer_offset,
+ dest_fsp, dest_off, num);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, smb_time_audit_offload_write_done, req);
+ return req;
+}
+
+static void smb_time_audit_offload_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct time_audit_offload_write_state *state = tevent_req_data(
+ req, struct time_audit_offload_write_state);
+ NTSTATUS status;
+
+ status = SMB_VFS_NEXT_OFFLOAD_WRITE_RECV(state->handle,
+ subreq,
+ &state->copied);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smb_time_audit_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ struct time_audit_offload_write_state *state = tevent_req_data(
+ req, struct time_audit_offload_write_state);
+ struct timespec ts_recv;
+ double timediff;
+ NTSTATUS status;
+
+ clock_gettime_mono(&ts_recv);
+ timediff = nsec_time_diff(&ts_recv, &state->ts_send)*1.0e-9;
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("offload_write", timediff);
+ }
+
+ *copied = state->copied;
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb_time_audit_fget_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FGET_COMPRESSION(handle, mem_ctx, fsp,
+ _compression_fmt);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("get_compression",
+ timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_set_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SET_COMPRESSION(handle, mem_ctx, fsp,
+ compression_fmt);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("set_compression", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **pattr_data)
+{
+ NTSTATUS status;
+ struct timespec ts1, ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ status = SMB_VFS_NEXT_FREADDIR_ATTR(handle, fsp, mem_ctx, pattr_data);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2, &ts1) * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("freaddir_attr", timediff, fsp);
+ }
+
+ return status;
+}
+
+static NTSTATUS smb_time_audit_fget_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FGET_NT_ACL(handle, fsp, security_info,
+ mem_ctx, ppdesc);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fget_nt_acl", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent,
+ psd);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fset_nt_acl", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_AUDIT_FILE(handle,
+ smb_fname,
+ sacl,
+ access_requested,
+ access_denied);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fname("audit_file",
+ timediff,
+ smb_fname->base_name);
+ }
+
+ return result;
+}
+
+static SMB_ACL_T smb_time_audit_sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ SMB_ACL_T result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SYS_ACL_GET_FD(handle, fsp, type, mem_ctx);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("sys_acl_get_fd", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SYS_ACL_BLOB_GET_FD(handle, fsp, mem_ctx, blob_description, blob);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("sys_acl_blob_get_fd", timediff);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("sys_acl_set_fd", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("sys_acl_delete_def_fd", timediff, fsp);
+ }
+
+ return result;
+}
+
+struct smb_time_audit_getxattrat_state {
+ struct vfs_aio_state aio_state;
+ files_struct *dir_fsp;
+ const struct smb_filename *smb_fname;
+ const char *xattr_name;
+ ssize_t xattr_size;
+ uint8_t *xattr_value;
+};
+
+static void smb_time_audit_getxattrat_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb_time_audit_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct smb_time_audit_getxattrat_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_time_audit_getxattrat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct smb_time_audit_getxattrat_state) {
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ .xattr_name = xattr_name,
+ };
+
+ subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname,
+ xattr_name,
+ alloc_hint);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_time_audit_getxattrat_done, req);
+
+ return req;
+}
+
+static void smb_time_audit_getxattrat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_time_audit_getxattrat_state *state = tevent_req_data(
+ req, struct smb_time_audit_getxattrat_state);
+
+ state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq,
+ &state->aio_state,
+ state,
+ &state->xattr_value);
+ TALLOC_FREE(subreq);
+ if (state->xattr_size == -1) {
+ tevent_req_error(req, state->aio_state.error);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static ssize_t smb_time_audit_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct smb_time_audit_getxattrat_state *state = tevent_req_data(
+ req, struct smb_time_audit_getxattrat_state);
+ ssize_t xattr_size;
+ double timediff;
+
+ timediff = state->aio_state.duration * 1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_at("async getxattrat",
+ timediff,
+ state->dir_fsp,
+ state->smb_fname);
+ }
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *aio_state = state->aio_state;
+ xattr_size = state->xattr_size;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+static ssize_t smb_time_audit_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name, void *value,
+ size_t size)
+{
+ ssize_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fgetxattr", timediff, fsp);
+ }
+
+ return result;
+}
+
+static ssize_t smb_time_audit_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ ssize_t result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("flistxattr", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fremovexattr", timediff, fsp);
+ }
+
+ return result;
+}
+
+static int smb_time_audit_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ const void *value, size_t size, int flags)
+{
+ int result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("fsetxattr", timediff, fsp);
+ }
+
+ return result;
+}
+
+static bool smb_time_audit_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ bool result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_AIO_FORCE(handle, fsp);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("aio_force", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_DURABLE_COOKIE(handle, fsp, mem_ctx, cookie);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("durable_cookie", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_durable_disconnect(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_DURABLE_DISCONNECT(handle, fsp, old_cookie,
+ mem_ctx, new_cookie);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log_fsp("durable_disconnect", timediff, fsp);
+ }
+
+ return result;
+}
+
+static NTSTATUS smb_time_audit_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie)
+{
+ NTSTATUS result;
+ struct timespec ts1,ts2;
+ double timediff;
+
+ clock_gettime_mono(&ts1);
+ result = SMB_VFS_NEXT_DURABLE_RECONNECT(handle, smb1req, op, old_cookie,
+ mem_ctx, fsp, new_cookie);
+ clock_gettime_mono(&ts2);
+ timediff = nsec_time_diff(&ts2,&ts1)*1.0e-9;
+
+ if (timediff > audit_timeout) {
+ smb_time_audit_log("durable_reconnect", timediff);
+ }
+
+ return result;
+}
+
+/* VFS operations */
+
+static struct vfs_fn_pointers vfs_time_audit_fns = {
+ .connect_fn = smb_time_audit_connect,
+ .disconnect_fn = smb_time_audit_disconnect,
+ .disk_free_fn = smb_time_audit_disk_free,
+ .get_quota_fn = smb_time_audit_get_quota,
+ .set_quota_fn = smb_time_audit_set_quota,
+ .get_shadow_copy_data_fn = smb_time_audit_get_shadow_copy_data,
+ .statvfs_fn = smb_time_audit_statvfs,
+ .fs_capabilities_fn = smb_time_audit_fs_capabilities,
+ .get_dfs_referrals_fn = smb_time_audit_get_dfs_referrals,
+ .create_dfs_pathat_fn = smb_time_audit_create_dfs_pathat,
+ .read_dfs_pathat_fn = smb_time_audit_read_dfs_pathat,
+ .fdopendir_fn = smb_time_audit_fdopendir,
+ .readdir_fn = smb_time_audit_readdir,
+ .rewind_dir_fn = smb_time_audit_rewinddir,
+ .mkdirat_fn = smb_time_audit_mkdirat,
+ .closedir_fn = smb_time_audit_closedir,
+ .openat_fn = smb_time_audit_openat,
+ .create_file_fn = smb_time_audit_create_file,
+ .close_fn = smb_time_audit_close,
+ .pread_fn = smb_time_audit_pread,
+ .pread_send_fn = smb_time_audit_pread_send,
+ .pread_recv_fn = smb_time_audit_pread_recv,
+ .pwrite_fn = smb_time_audit_pwrite,
+ .pwrite_send_fn = smb_time_audit_pwrite_send,
+ .pwrite_recv_fn = smb_time_audit_pwrite_recv,
+ .lseek_fn = smb_time_audit_lseek,
+ .sendfile_fn = smb_time_audit_sendfile,
+ .recvfile_fn = smb_time_audit_recvfile,
+ .renameat_fn = smb_time_audit_renameat,
+ .fsync_send_fn = smb_time_audit_fsync_send,
+ .fsync_recv_fn = smb_time_audit_fsync_recv,
+ .stat_fn = smb_time_audit_stat,
+ .fstat_fn = smb_time_audit_fstat,
+ .lstat_fn = smb_time_audit_lstat,
+ .fstatat_fn = smb_time_audit_fstatat,
+ .get_alloc_size_fn = smb_time_audit_get_alloc_size,
+ .unlinkat_fn = smb_time_audit_unlinkat,
+ .fchmod_fn = smb_time_audit_fchmod,
+ .fchown_fn = smb_time_audit_fchown,
+ .lchown_fn = smb_time_audit_lchown,
+ .chdir_fn = smb_time_audit_chdir,
+ .getwd_fn = smb_time_audit_getwd,
+ .fntimes_fn = smb_time_audit_fntimes,
+ .ftruncate_fn = smb_time_audit_ftruncate,
+ .fallocate_fn = smb_time_audit_fallocate,
+ .lock_fn = smb_time_audit_lock,
+ .filesystem_sharemode_fn = smb_time_audit_filesystem_sharemode,
+ .fcntl_fn = smb_time_audit_fcntl,
+ .linux_setlease_fn = smb_time_audit_linux_setlease,
+ .getlock_fn = smb_time_audit_getlock,
+ .symlinkat_fn = smb_time_audit_symlinkat,
+ .readlinkat_fn = smb_time_audit_readlinkat,
+ .linkat_fn = smb_time_audit_linkat,
+ .mknodat_fn = smb_time_audit_mknodat,
+ .realpath_fn = smb_time_audit_realpath,
+ .fchflags_fn = smb_time_audit_fchflags,
+ .file_id_create_fn = smb_time_audit_file_id_create,
+ .fs_file_id_fn = smb_time_audit_fs_file_id,
+ .offload_read_send_fn = smb_time_audit_offload_read_send,
+ .offload_read_recv_fn = smb_time_audit_offload_read_recv,
+ .offload_write_send_fn = smb_time_audit_offload_write_send,
+ .offload_write_recv_fn = smb_time_audit_offload_write_recv,
+ .fget_compression_fn = smb_time_audit_fget_compression,
+ .set_compression_fn = smb_time_audit_set_compression,
+ .snap_check_path_fn = smb_time_audit_snap_check_path,
+ .snap_create_fn = smb_time_audit_snap_create,
+ .snap_delete_fn = smb_time_audit_snap_delete,
+ .fstreaminfo_fn = smb_time_audit_fstreaminfo,
+ .get_real_filename_at_fn = smb_time_audit_get_real_filename_at,
+ .connectpath_fn = smb_time_audit_connectpath,
+ .brl_lock_windows_fn = smb_time_audit_brl_lock_windows,
+ .brl_unlock_windows_fn = smb_time_audit_brl_unlock_windows,
+ .strict_lock_check_fn = smb_time_audit_strict_lock_check,
+ .translate_name_fn = smb_time_audit_translate_name,
+ .parent_pathname_fn = smb_time_audit_parent_pathname,
+ .fsctl_fn = smb_time_audit_fsctl,
+ .get_dos_attributes_send_fn = smb_time_audit_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = smb_time_audit_get_dos_attributes_recv,
+ .fget_dos_attributes_fn = smb_time_fget_dos_attributes,
+ .fset_dos_attributes_fn = smb_time_fset_dos_attributes,
+ .fget_nt_acl_fn = smb_time_audit_fget_nt_acl,
+ .fset_nt_acl_fn = smb_time_audit_fset_nt_acl,
+ .audit_file_fn = smb_time_audit_audit_file,
+ .sys_acl_get_fd_fn = smb_time_audit_sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = smb_time_audit_sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = smb_time_audit_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = smb_time_audit_sys_acl_delete_def_fd,
+ .getxattrat_send_fn = smb_time_audit_getxattrat_send,
+ .getxattrat_recv_fn = smb_time_audit_getxattrat_recv,
+ .fgetxattr_fn = smb_time_audit_fgetxattr,
+ .flistxattr_fn = smb_time_audit_flistxattr,
+ .fremovexattr_fn = smb_time_audit_fremovexattr,
+ .fsetxattr_fn = smb_time_audit_fsetxattr,
+ .aio_force_fn = smb_time_audit_aio_force,
+ .durable_cookie_fn = smb_time_audit_durable_cookie,
+ .durable_disconnect_fn = smb_time_audit_durable_disconnect,
+ .durable_reconnect_fn = smb_time_audit_durable_reconnect,
+ .freaddir_attr_fn = smb_time_audit_freaddir_attr,
+};
+
+
+static_decl_vfs;
+NTSTATUS vfs_time_audit_init(TALLOC_CTX *ctx)
+{
+ smb_vfs_assert_all_fns(&vfs_time_audit_fns, "time_audit");
+
+ audit_timeout = (double)lp_parm_int(-1, "time_audit", "timeout",
+ 10000) / 1000.0;
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "time_audit",
+ &vfs_time_audit_fns);
+}
diff --git a/source3/modules/vfs_tsmsm.c b/source3/modules/vfs_tsmsm.c
new file mode 100644
index 0000000..89191f7
--- /dev/null
+++ b/source3/modules/vfs_tsmsm.c
@@ -0,0 +1,573 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba VFS module for handling offline files
+ with Tivoli Storage Manager Space Management
+
+ (c) Alexander Bokovoy, 2007, 2008
+ (c) Andrew Tridgell, 2007, 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ This VFS module accepts following options:
+ tsmsm: hsm script = <path to hsm script> (default does nothing)
+ hsm script should point to a shell script which accepts two arguments:
+ <operation> <filepath>
+ where <operation> is currently 'offline' to set offline status of the <filepath>
+
+ tsmsm: online ratio = ratio to check reported size against actual file size (0.5 by default)
+ tsmsm: dmapi attribute = name of DMAPI attribute that is present when a file is offline.
+ Default is "IBMobj" (which is what GPFS uses)
+
+ The TSMSM VFS module tries to avoid calling expensive DMAPI calls with some heuristics
+ based on the fact that number of blocks reported of a file multiplied by 512 will be
+ bigger than 'online ratio' of actual size for online (non-migrated) files.
+
+ If checks fail, we call DMAPI and ask for specific attribute which present for
+ offline (migrated) files. If this attribute presents, we consider file offline.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "lib/util/tevent_unix.h"
+
+#ifndef USE_DMAPI
+#error "This module requires DMAPI support!"
+#endif
+
+#ifdef HAVE_XFS_DMAPI_H
+#include <xfs/dmapi.h>
+#elif defined(HAVE_SYS_DMI_H)
+#include <sys/dmi.h>
+#elif defined(HAVE_SYS_JFSDMAPI_H)
+#include <sys/jfsdmapi.h>
+#elif defined(HAVE_SYS_DMAPI_H)
+#include <sys/dmapi.h>
+#elif defined(HAVE_DMAPI_H)
+#include <dmapi.h>
+#endif
+
+#ifndef _ISOC99_SOURCE
+#define _ISOC99_SOURCE
+#endif
+
+#include <math.h>
+
+/* optimisation tunables - used to avoid the DMAPI slow path */
+#define FILE_IS_ONLINE_RATIO 0.5
+
+/* default attribute name to look for */
+#define DM_ATTRIB_OBJECT "IBMObj"
+
+struct tsmsm_struct {
+ float online_ratio;
+ char *hsmscript;
+ const char *attrib_name;
+ const char *attrib_value;
+};
+
+static void tsmsm_free_data(void **pptr) {
+ struct tsmsm_struct **tsmd = (struct tsmsm_struct **)pptr;
+ if(!tsmd) return;
+ TALLOC_FREE(*tsmd);
+}
+
+/*
+ called when a client connects to a share
+*/
+static int tsmsm_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user) {
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct tsmsm_struct *tsmd;
+ const char *fres;
+ const char *tsmname;
+ int ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+
+ if (ret < 0) {
+ return ret;
+ }
+
+ tsmd = talloc_zero(handle, struct tsmsm_struct);
+ if (!tsmd) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ DEBUG(0,("tsmsm_connect: out of memory!\n"));
+ return -1;
+ }
+
+ if (!dmapi_have_session()) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ DEBUG(0,("tsmsm_connect: no DMAPI session for Samba is available!\n"));
+ TALLOC_FREE(tsmd);
+ return -1;
+ }
+
+ tsmname = (handle->param ? handle->param : "tsmsm");
+
+ /* Get 'hsm script' and 'dmapi attribute' parameters to tsmd context */
+ tsmd->hsmscript = lp_parm_substituted_string(
+ tsmd, lp_sub, SNUM(handle->conn), tsmname,
+ "hsm script", NULL);
+ talloc_steal(tsmd, tsmd->hsmscript);
+
+ tsmd->attrib_name = lp_parm_substituted_string(
+ tsmd, lp_sub, SNUM(handle->conn), tsmname,
+ "dmapi attribute", DM_ATTRIB_OBJECT);
+ talloc_steal(tsmd, tsmd->attrib_name);
+
+ tsmd->attrib_value = lp_parm_substituted_string(
+ tsmd, lp_sub, SNUM(handle->conn), tsmname,
+ "dmapi value", NULL);
+ talloc_steal(tsmd, tsmd->attrib_value);
+
+ /* retrieve 'online ratio'. In case of error default to FILE_IS_ONLINE_RATIO */
+ fres = lp_parm_const_string(SNUM(handle->conn), tsmname,
+ "online ratio", NULL);
+ if (fres == NULL) {
+ tsmd->online_ratio = FILE_IS_ONLINE_RATIO;
+ } else {
+ tsmd->online_ratio = strtof(fres, NULL);
+ if (tsmd->online_ratio > 1.0 ||
+ tsmd->online_ratio <= 0.0) {
+ DEBUG(1, ("tsmsm_connect: invalid online ration %f - using %f.\n",
+ tsmd->online_ratio, (float)FILE_IS_ONLINE_RATIO));
+ }
+ }
+
+ /* Store the private data. */
+ SMB_VFS_HANDLE_SET_DATA(handle, tsmd, tsmsm_free_data,
+ struct tsmsm_struct, return -1);
+ return 0;
+}
+
+static bool tsmsm_is_offline(struct vfs_handle_struct *handle,
+ const struct smb_filename *fname,
+ SMB_STRUCT_STAT *stbuf)
+{
+ struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data;
+ const dm_sessid_t *dmsession_id;
+ void *dmhandle = NULL;
+ size_t dmhandle_len = 0;
+ size_t rlen;
+ dm_attrname_t dmname;
+ int ret, lerrno;
+ bool offline;
+ char *buf = NULL;
+ size_t buflen;
+ NTSTATUS status;
+ char *path;
+
+ status = get_full_smb_filename(talloc_tos(), fname, &path);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ return false;
+ }
+
+ /* if the file has more than FILE_IS_ONLINE_RATIO of blocks available,
+ then assume it is not offline (it may not be 100%, as it could be sparse) */
+ if (512 * stbuf->st_ex_blocks >=
+ stbuf->st_ex_size * tsmd->online_ratio) {
+ DEBUG(10,("%s not offline: st_blocks=%llu st_size=%llu "
+ "online_ratio=%.2f\n", path,
+ (unsigned long long)stbuf->st_ex_blocks,
+ (unsigned long long)stbuf->st_ex_size, tsmd->online_ratio));
+ return false;
+ }
+
+ dmsession_id = dmapi_get_current_session();
+ if (dmsession_id == NULL) {
+ DEBUG(2, ("tsmsm_is_offline: no DMAPI session available? "
+ "Assume file is online.\n"));
+ return false;
+ }
+
+ /* using POSIX capabilities does not work here. It's a slow path, so
+ * become_root() is just as good anyway (tridge)
+ */
+
+ /* Also, AIX has DMAPI but no POSIX capabilities support. In this case,
+ * we need to be root to do DMAPI manipulations.
+ */
+ become_root();
+
+ /* go the slow DMAPI route */
+ if (dm_path_to_handle((char*)path, &dmhandle, &dmhandle_len) != 0) {
+ DEBUG(2,("dm_path_to_handle failed - assuming offline (%s) - %s\n",
+ path, strerror(errno)));
+ offline = true;
+ goto done;
+ }
+
+ memset(&dmname, 0, sizeof(dmname));
+ strlcpy((char *)&dmname.an_chars[0], tsmd->attrib_name, sizeof(dmname.an_chars));
+
+ if (tsmd->attrib_value != NULL) {
+ buflen = strlen(tsmd->attrib_value);
+ } else {
+ buflen = 1;
+ }
+ buf = talloc_zero_size(tsmd, buflen);
+ if (buf == NULL) {
+ DEBUG(0,("out of memory in tsmsm_is_offline -- assuming online (%s)\n", path));
+ errno = ENOMEM;
+ offline = false;
+ goto done;
+ }
+
+ do {
+ lerrno = 0;
+
+ ret = dm_get_dmattr(*dmsession_id, dmhandle, dmhandle_len,
+ DM_NO_TOKEN, &dmname, buflen, buf, &rlen);
+ if (ret == -1 && errno == EINVAL) {
+ DEBUG(0, ("Stale DMAPI session, re-creating it.\n"));
+ lerrno = EINVAL;
+ if (dmapi_new_session()) {
+ dmsession_id = dmapi_get_current_session();
+ } else {
+ DEBUG(0,
+ ("Unable to re-create DMAPI session, assuming offline (%s) - %s\n",
+ path, strerror(errno)));
+ offline = true;
+ dm_handle_free(dmhandle, dmhandle_len);
+ goto done;
+ }
+ }
+ } while (ret == -1 && lerrno == EINVAL);
+
+ /* check if we need a specific attribute value */
+ if (tsmd->attrib_value != NULL) {
+ offline = (ret == 0 && rlen == buflen &&
+ memcmp(buf, tsmd->attrib_value, buflen) == 0);
+ } else {
+ /* its offline if the specified DMAPI attribute exists */
+ offline = (ret == 0 || (ret == -1 && errno == E2BIG));
+ }
+
+ DEBUG(10,("dm_get_dmattr %s ret=%d (%s)\n", path, ret, strerror(errno)));
+
+ ret = 0;
+
+ dm_handle_free(dmhandle, dmhandle_len);
+
+done:
+ talloc_free(buf);
+ unbecome_root();
+ return offline;
+}
+
+static NTSTATUS tsmsm_fget_dos_attributes(struct vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t *dosmode)
+{
+ bool offline;
+
+ offline = tsmsm_is_offline(handle, fsp->fsp_name, &fsp->fsp_name->st);
+ if (offline) {
+ *dosmode |= FILE_ATTRIBUTE_OFFLINE;
+ }
+
+ return SMB_VFS_NEXT_FGET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+}
+
+static bool tsmsm_aio_force(struct vfs_handle_struct *handle, struct files_struct *fsp)
+{
+ SMB_STRUCT_STAT sbuf;
+ struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data;
+ /* see if the file might be offline. This is called before each IO
+ to ensure we use AIO if the file is offline. We don't do the full dmapi
+ call as that would be too slow, instead we err on the side of using AIO
+ if the file might be offline
+ */
+ if(SMB_VFS_FSTAT(fsp, &sbuf) == 0) {
+ DEBUG(10,("tsmsm_aio_force st_blocks=%llu st_size=%llu "
+ "online_ratio=%.2f\n", (unsigned long long)sbuf.st_ex_blocks,
+ (unsigned long long)sbuf.st_ex_size, tsmd->online_ratio));
+ return !(512 * sbuf.st_ex_blocks >=
+ sbuf.st_ex_size * tsmd->online_ratio);
+ }
+ return false;
+}
+
+struct tsmsm_pread_state {
+ struct files_struct *fsp;
+ ssize_t ret;
+ bool was_offline;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void tsmsm_pread_done(struct tevent_req *subreq);
+
+static struct tevent_req *tsmsm_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data, size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct tsmsm_pread_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct tsmsm_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+ state->was_offline = tsmsm_aio_force(handle, fsp);
+ subreq = SMB_VFS_NEXT_PREAD_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tsmsm_pread_done, req);
+ return req;
+}
+
+static void tsmsm_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tsmsm_pread_state *state = tevent_req_data(
+ req, struct tsmsm_pread_state);
+
+ state->ret = SMB_VFS_PREAD_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t tsmsm_pread_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct tsmsm_pread_state *state = tevent_req_data(
+ req, struct tsmsm_pread_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ if (state->ret >= 0 && state->was_offline) {
+ struct files_struct *fsp = state->fsp;
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+struct tsmsm_pwrite_state {
+ struct files_struct *fsp;
+ ssize_t ret;
+ bool was_offline;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void tsmsm_pwrite_done(struct tevent_req *subreq);
+
+static struct tevent_req *tsmsm_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data, size_t n,
+ off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct tsmsm_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct tsmsm_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->fsp = fsp;
+ state->was_offline = tsmsm_aio_force(handle, fsp);
+ subreq = SMB_VFS_NEXT_PWRITE_SEND(state, ev, handle, fsp, data,
+ n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, tsmsm_pwrite_done, req);
+ return req;
+}
+
+static void tsmsm_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct tsmsm_pwrite_state *state = tevent_req_data(
+ req, struct tsmsm_pwrite_state);
+
+ state->ret = SMB_VFS_PWRITE_RECV(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static ssize_t tsmsm_pwrite_recv(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct tsmsm_pwrite_state *state = tevent_req_data(
+ req, struct tsmsm_pwrite_state);
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ return -1;
+ }
+ if (state->ret >= 0 && state->was_offline) {
+ struct files_struct *fsp = state->fsp;
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ return state->ret;
+}
+
+static ssize_t tsmsm_sendfile(vfs_handle_struct *handle, int tofd, files_struct *fsp, const DATA_BLOB *hdr,
+ off_t offset, size_t n)
+{
+ bool file_offline = tsmsm_aio_force(handle, fsp);
+
+ if (file_offline) {
+ DEBUG(10,("tsmsm_sendfile on offline file - rejecting\n"));
+ errno = ENOSYS;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_SENDFILE(handle, tofd, fsp, hdr, offset, n);
+}
+
+/* We do overload pread to allow notification when file becomes online after offline status */
+/* We don't intercept SMB_VFS_READ here because all file I/O now goes through SMB_VFS_PREAD instead */
+static ssize_t tsmsm_pread(struct vfs_handle_struct *handle, struct files_struct *fsp,
+ void *data, size_t n, off_t offset) {
+ ssize_t result;
+ bool notify_online = tsmsm_aio_force(handle, fsp);
+
+ result = SMB_VFS_NEXT_PREAD(handle, fsp, data, n, offset);
+ if((result != -1) && notify_online) {
+ /* We can't actually force AIO at this point (came here not from reply_read_and_X)
+ what we can do is to send notification that file became online
+ */
+ notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ return result;
+}
+
+static ssize_t tsmsm_pwrite(struct vfs_handle_struct *handle, struct files_struct *fsp,
+ const void *data, size_t n, off_t offset) {
+ ssize_t result;
+ bool notify_online = tsmsm_aio_force(handle, fsp);
+
+ result = SMB_VFS_NEXT_PWRITE(handle, fsp, data, n, offset);
+ if((result != -1) && notify_online) {
+ /* We can't actually force AIO at this point (came here not from reply_read_and_X)
+ what we can do is to send notification that file became online
+ */
+ notify_fname(handle->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ return result;
+}
+
+static NTSTATUS tsmsm_set_offline(struct vfs_handle_struct *handle,
+ const struct smb_filename *fname)
+{
+ struct tsmsm_struct *tsmd = (struct tsmsm_struct *) handle->data;
+ int result = 0;
+ char *command;
+ NTSTATUS status;
+ char *path;
+
+ if (tsmd->hsmscript == NULL) {
+ /* no script enabled */
+ DEBUG(1, ("tsmsm_set_offline: No 'tsmsm:hsm script' configured\n"));
+ return NT_STATUS_OK;
+ }
+
+ status = get_full_smb_filename(talloc_tos(), fname, &path);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Now, call the script */
+ command = talloc_asprintf(tsmd, "%s offline \"%s\"", tsmd->hsmscript, path);
+ if(!command) {
+ DEBUG(1, ("tsmsm_set_offline: can't allocate memory to run hsm script\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ DEBUG(10, ("tsmsm_set_offline: Running [%s]\n", command));
+ result = smbrun(command, NULL, NULL);
+ if(result != 0) {
+ DEBUG(1,("tsmsm_set_offline: Running [%s] returned %d\n", command, result));
+ TALLOC_FREE(command);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ TALLOC_FREE(command);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS tsmsm_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ NTSTATUS status;
+ uint32_t old_dosmode;
+
+ old_dosmode = fdos_mode(fsp);
+
+ status = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!(old_dosmode & FILE_ATTRIBUTE_OFFLINE) &&
+ (dosmode & FILE_ATTRIBUTE_OFFLINE))
+ {
+ return NT_STATUS_OK;
+ }
+
+ return tsmsm_set_offline(handle, fsp->fsp_name);
+}
+
+static uint32_t tsmsm_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ return SMB_VFS_NEXT_FS_CAPABILITIES(handle, p_ts_res) | FILE_SUPPORTS_REMOTE_STORAGE | FILE_SUPPORTS_REPARSE_POINTS;
+}
+
+static struct vfs_fn_pointers tsmsm_fns = {
+ .connect_fn = tsmsm_connect,
+ .fs_capabilities_fn = tsmsm_fs_capabilities,
+ .aio_force_fn = tsmsm_aio_force,
+ .pread_fn = tsmsm_pread,
+ .pread_send_fn = tsmsm_pread_send,
+ .pread_recv_fn = tsmsm_pread_recv,
+ .pwrite_fn = tsmsm_pwrite,
+ .pwrite_send_fn = tsmsm_pwrite_send,
+ .pwrite_recv_fn = tsmsm_pwrite_recv,
+ .sendfile_fn = tsmsm_sendfile,
+ .fset_dos_attributes_fn = tsmsm_fset_dos_attributes,
+ .get_dos_attributes_send_fn = vfs_not_implemented_get_dos_attributes_send,
+ .get_dos_attributes_recv_fn = vfs_not_implemented_get_dos_attributes_recv,
+ .fget_dos_attributes_fn = tsmsm_fget_dos_attributes,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_tsmsm_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "tsmsm", &tsmsm_fns);
+}
diff --git a/source3/modules/vfs_unityed_media.c b/source3/modules/vfs_unityed_media.c
new file mode 100644
index 0000000..c848cf5
--- /dev/null
+++ b/source3/modules/vfs_unityed_media.c
@@ -0,0 +1,1544 @@
+/*
+ * Samba VFS module supporting multiple AVID clients sharing media.
+ *
+ * Copyright (C) 2005 Philip de Nier <philipn@users.sourceforge.net>
+ * Copyright (C) 2012 Andrew Klaassen <clawsoon@yahoo.com>
+ * Copyright (C) 2013 Milos Lukacek
+ * Copyright (C) 2013 Ralph Boehme <slow@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/*
+ * Unityed Media is a Samba VFS module that allows multiple AVID
+ * clients to share media.
+ *
+ * Add this module to the vfs objects option in your Samba share
+ * configuration.
+ * eg.
+ *
+ * [avid_win]
+ * path = /video
+ * vfs objects = unityed_media
+ * ...
+ *
+ * It is recommended that you separate out Samba shares for Mac
+ * and Windows clients, and add the following options to the shares
+ * for Windows clients (NOTE: replace @ with *):
+ *
+ * veto files = /.DS_Store/._@/.Trash@/.Spotlight@/.hidden/.hotfiles@/.vol/
+ * delete veto files = yes
+ *
+ * This prevents hidden files from Mac clients interfering with Windows
+ * clients. If you find any more problem hidden files then add them to
+ * the list.
+ *
+ * Notes:
+ * This module is designed to work with AVID editing applications that
+ * look in the Avid MediaFiles or OMFI MediaFiles directory for media.
+ * It is not designed to work as expected in all circumstances for
+ * general use.
+ */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "../smbd/globals.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/smb_strtox.h"
+#include <libgen.h>
+#include "source3/lib/substitute.h"
+
+#define UM_PARAM_TYPE_NAME "unityed_media"
+
+static const char *AVID_MXF_DIRNAME = "Avid MediaFiles/MXF";
+static const size_t AVID_MXF_DIRNAME_LEN = 19;
+static const char *OMFI_MEDIAFILES_DIRNAME = "OMFI MediaFiles";
+static const size_t OMFI_MEDIAFILES_DIRNAME_LEN = 15;
+static const char *APPLE_DOUBLE_PREFIX = "._";
+static const size_t APPLE_DOUBLE_PREFIX_LEN = 2;
+static int vfs_um_debug_level = DBGC_VFS;
+
+enum um_clientid {UM_CLIENTID_NAME, UM_CLIENTID_IP, UM_CLIENTID_HOSTNAME};
+
+struct um_config_data {
+ enum um_clientid clientid;
+};
+
+static const struct enum_list um_clientid[] = {
+ {UM_CLIENTID_NAME, "user"},
+ {UM_CLIENTID_IP, "ip"},
+ {UM_CLIENTID_HOSTNAME, "hostname"},
+ {-1, NULL}
+};
+
+/* supplements the directory list stream */
+typedef struct um_dirinfo_struct {
+ DIR* dirstream;
+ char *dirpath;
+ char *clientPath;
+ bool isInMediaFiles;
+ char *clientSubDirname;
+} um_dirinfo_struct;
+
+/**
+ * Returns true and first group of digits in path, false and 0 otherwise
+ **/
+static bool get_digit_group(const char *path, uintmax_t *digit)
+{
+ const char *p = path;
+ codepoint_t cp;
+ size_t size;
+ int error = 0;
+
+ DEBUG(10, ("get_digit_group entering with path '%s'\n",
+ path));
+
+ /*
+ * Delibiretly initialize to 0 because callers use this result
+ * even though the string doesn't contain any number and we
+ * returned false
+ */
+ *digit = 0;
+
+ while (*p) {
+ cp = next_codepoint(p, &size);
+ if (cp == -1) {
+ return false;
+ }
+ if ((size == 1) && (isdigit(cp))) {
+ *digit = (uintmax_t)smb_strtoul(p,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ return false;
+ }
+ DEBUG(10, ("num_suffix = '%ju'\n",
+ *digit));
+ return true;
+ }
+ p += size;
+ }
+
+ return false;
+}
+
+/* Add "_<remote_name>.<number>" suffix to path or filename.
+ *
+ * Success: return 0
+ * Failure: set errno, path NULL, return -1
+ */
+
+static int alloc_append_client_suffix(vfs_handle_struct *handle,
+ char **path)
+{
+ int status = 0;
+ uintmax_t number;
+ const char *clientid;
+ struct um_config_data *config;
+
+ DEBUG(10, ("Entering with path '%s'\n", *path));
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct um_config_data,
+ return -1);
+
+ (void)get_digit_group(*path, &number);
+
+ switch (config->clientid) {
+
+ case UM_CLIENTID_IP:
+ clientid = tsocket_address_inet_addr_string(
+ handle->conn->sconn->remote_address, talloc_tos());
+ if (clientid == NULL) {
+ errno = ENOMEM;
+ status = -1;
+ goto err;
+ }
+ break;
+
+ case UM_CLIENTID_HOSTNAME:
+ clientid = get_remote_machine_name();
+ break;
+
+ case UM_CLIENTID_NAME:
+ default:
+ clientid = get_current_username();
+ break;
+ }
+
+ *path = talloc_asprintf_append(*path, "_%s.%ju",
+ clientid, number);
+ if (*path == NULL) {
+ DEBUG(1, ("alloc_append_client_suffix "
+ "out of memory\n"));
+ errno = ENOMEM;
+ status = -1;
+ goto err;
+ }
+ DEBUG(10, ("Leaving with *path '%s'\n", *path));
+err:
+ return status;
+}
+
+/* Returns true if the file or directory begins with the appledouble
+ * prefix.
+ */
+static bool is_apple_double(const char* fname)
+{
+ bool ret = false;
+
+ DEBUG(10, ("Entering with fname '%s'\n", fname));
+
+ if (strnequal(APPLE_DOUBLE_PREFIX, fname, APPLE_DOUBLE_PREFIX_LEN)) {
+ ret = true;
+ }
+ DEBUG(10, ("Leaving with ret '%s'\n",
+ ret == true ? "true" : "false"));
+ return ret;
+}
+
+static bool starts_with_media_dir(const char* media_dirname,
+ size_t media_dirname_len,
+ const char *path)
+{
+ bool ret = false;
+ const char *path_start = path;
+
+ DEBUG(10, ("Entering with media_dirname '%s' "
+ "path '%s'\n", media_dirname, path));
+
+ /* Sometimes Samba gives us "./OMFI MediaFiles". */
+ if (strnequal(path, "./", 2)) {
+ path_start += 2;
+ }
+
+ if (strnequal(media_dirname, path_start, media_dirname_len)
+ &&
+ ((path_start[media_dirname_len] == '\0') ||
+ (path_start[media_dirname_len] == '/'))) {
+ ret = true;
+ }
+
+ DEBUG(10, ("Leaving with ret '%s'\n",
+ ret == true ? "true" : "false"));
+ return ret;
+}
+
+/*
+ * Returns true if the file or directory referenced by the path is ONE
+ * LEVEL below the AVID_MXF_DIRNAME or OMFI_MEDIAFILES_DIRNAME
+ * directory
+ */
+static bool is_in_media_dir(const char *path)
+{
+ int transition_count = 0;
+ const char *path_start = path;
+ const char *p;
+ const char *media_dirname;
+ size_t media_dirname_len;
+
+ DEBUG(10, ("Entering with path '%s'\n", path));
+
+ /* Sometimes Samba gives us "./OMFI MediaFiles". */
+ if (strnequal(path, "./", 2)) {
+ path_start += 2;
+ }
+
+ if (strnequal(path_start, AVID_MXF_DIRNAME, AVID_MXF_DIRNAME_LEN)) {
+ media_dirname = AVID_MXF_DIRNAME;
+ media_dirname_len = AVID_MXF_DIRNAME_LEN;
+ } else if (strnequal(path_start,
+ OMFI_MEDIAFILES_DIRNAME,
+ OMFI_MEDIAFILES_DIRNAME_LEN)) {
+ media_dirname = OMFI_MEDIAFILES_DIRNAME;
+ media_dirname_len = OMFI_MEDIAFILES_DIRNAME_LEN;
+ } else {
+ return false;
+ }
+
+ if (path_start[media_dirname_len] == '\0') {
+ goto out;
+ }
+
+ p = path_start + media_dirname_len + 1;
+
+ while (true) {
+ if (*p == '\0' || *p == '/') {
+ if (strnequal(p - 3, "/..", 3)) {
+ transition_count--;
+ } else if ((p[-1] != '/') || !strnequal(p - 2, "/.", 2)) {
+ transition_count++;
+ }
+ }
+ if (*p == '\0') {
+ break;
+ }
+ p++;
+ }
+
+out:
+ DEBUG(10, ("Going out with transition_count '%i'\n",
+ transition_count));
+ if (((transition_count == 1) && (media_dirname == AVID_MXF_DIRNAME))
+ ||
+ ((transition_count == 0) && (media_dirname == OMFI_MEDIAFILES_DIRNAME))) {
+ return true;
+ }
+ else return false;
+}
+
+/*
+ * Returns true if the file or directory referenced by the path is
+ * below the AVID_MEDIAFILES_DIRNAME or OMFI_MEDIAFILES_DIRNAME
+ * directory The AVID_MEDIAFILES_DIRNAME and OMFI_MEDIAFILES_DIRNAME
+ * are assumed to be in the root directory, which is generally a safe
+ * assumption in the fixed-path world of Avid.
+ */
+static bool is_in_media_files(const char *path)
+{
+ bool ret = false;
+
+ DEBUG(10, ("Entering with path '%s'\n", path));
+
+ if (starts_with_media_dir(AVID_MXF_DIRNAME,
+ AVID_MXF_DIRNAME_LEN, path) ||
+ starts_with_media_dir(OMFI_MEDIAFILES_DIRNAME,
+ OMFI_MEDIAFILES_DIRNAME_LEN, path)) {
+ ret = true;
+ }
+ DEBUG(10, ("Leaving with ret '%s'\n",
+ ret == true ? "true" : "false"));
+ return ret;
+}
+
+
+/* Add client suffix to "pure-number" path.
+ *
+ * Caller must free newPath.
+ *
+ * Success: return 0
+ * Failure: set errno, newPath NULL, return -1
+ */
+static int alloc_get_client_path(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const char *path_in,
+ char **path_out)
+{
+ int status = 0;
+ char *p;
+ char *digits;
+ size_t digits_len;
+ uintmax_t number;
+
+ *path_out = talloc_strdup(ctx, path_in);
+ if (*path_out == NULL) {
+ DEBUG(1, ("alloc_get_client_path ENOMEM\n"));
+ return -1;
+ }
+
+ (void)get_digit_group(*path_out, &number);
+
+ digits = talloc_asprintf(NULL, "%ju", number);
+ if (digits == NULL) {
+ DEBUG(1, ("alloc_get_client_path ENOMEM\n"));
+ return -1;
+ }
+ digits_len = strlen(digits);
+
+ p = strstr_m(path_in, digits);
+ if ((p)
+ &&
+ ((p[digits_len] == '\0') || (p[digits_len] == '/'))
+ &&
+ (((p - path_in > 0) && (p[-1] == '/'))
+ ||
+ (((p - path_in) > APPLE_DOUBLE_PREFIX_LEN)
+ &&
+ is_apple_double(p - APPLE_DOUBLE_PREFIX_LEN)
+ &&
+ (p[-(APPLE_DOUBLE_PREFIX_LEN + 1)] == '/'))))
+ {
+ (*path_out)[p - path_in + digits_len] = '\0';
+
+ status = alloc_append_client_suffix(handle, path_out);
+ if (status != 0) {
+ goto out;
+ }
+
+ *path_out = talloc_strdup_append(*path_out, p + digits_len);
+ if (*path_out == NULL) {
+ DEBUG(1, ("alloc_get_client_path ENOMEM\n"));
+ status = -1;
+ goto out;
+ }
+ }
+out:
+ /* path_out must be freed in caller. */
+ DEBUG(10, ("Result:'%s'\n", *path_out));
+ return status;
+}
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int alloc_get_client_smb_fname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname,
+ struct smb_filename **client_fname)
+{
+ int status ;
+
+ DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ *client_fname = cp_smb_filename(ctx, smb_fname);
+ if (*client_fname == NULL) {
+ DEBUG(1, ("cp_smb_filename returned NULL\n"));
+ return -1;
+ }
+ status = alloc_get_client_path(handle, ctx,
+ smb_fname->base_name,
+ &(*client_fname)->base_name);
+ if (status != 0) {
+ return -1;
+ }
+
+ DEBUG(10, ("Leaving with (*client_fname)->base_name "
+ "'%s'\n", (*client_fname)->base_name));
+
+ return 0;
+}
+
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int alloc_set_client_dirinfo_path(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ char **path,
+ const char *suffix_number)
+{
+ int status;
+
+ DEBUG(10, ("Entering with suffix_number '%s'\n",
+ suffix_number));
+
+ *path = talloc_strdup(ctx, suffix_number);
+ if (*path == NULL) {
+ DEBUG(1, ("alloc_set_client_dirinfo_path ENOMEM\n"));
+ return -1;
+ }
+ status = alloc_append_client_suffix(handle, path);
+ if (status != 0) {
+ return -1;
+ }
+
+ DEBUG(10, ("Leaving with *path '%s'\n", *path));
+
+ return 0;
+}
+
+static int alloc_set_client_dirinfo(vfs_handle_struct *handle,
+ const char *fname,
+ struct um_dirinfo_struct **di_result)
+{
+ int status = 0;
+ char *digits;
+ uintmax_t number;
+ struct um_dirinfo_struct *dip;
+
+ DEBUG(10, ("Entering with fname '%s'\n", fname));
+
+ *di_result = talloc(NULL, struct um_dirinfo_struct);
+ if (*di_result == NULL) {
+ goto err;
+ }
+ dip = *di_result;
+
+ dip->dirpath = talloc_strdup(dip, fname);
+ if (dip->dirpath == NULL) {
+ goto err;
+ }
+
+ if (!is_in_media_files(fname)) {
+ dip->isInMediaFiles = false;
+ dip->clientPath = NULL;
+ dip->clientSubDirname = NULL;
+ goto out;
+ }
+
+ dip->isInMediaFiles = true;
+
+ (void)get_digit_group(fname, &number);
+ digits = talloc_asprintf(talloc_tos(), "%ju", number);
+ if (digits == NULL) {
+ goto err;
+ }
+
+ status = alloc_set_client_dirinfo_path(handle, dip,
+ &dip->clientSubDirname,
+ digits);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = alloc_get_client_path(handle, dip, fname,
+ &dip->clientPath);
+ if (status != 0 || dip->clientPath == NULL) {
+ goto err;
+ }
+
+out:
+ DEBUG(10, ("Leaving with (*dirInfo)->dirpath '%s', "
+ "(*dirInfo)->clientPath '%s'\n",
+ dip->dirpath, dip->clientPath));
+ return status;
+
+err:
+ DEBUG(1, ("Failing with fname '%s'\n", fname));
+ TALLOC_FREE(*di_result);
+ status = -1;
+ errno = ENOMEM;
+ return status;
+}
+
+/**********************************************************************
+ * VFS functions
+ **********************************************************************/
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int um_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ int status;
+ struct smb_filename *client_fname = NULL;
+
+ DEBUG(10, ("Entering with path '%s'\n", smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_STATVFS(handle, smb_fname, statbuf);
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ smb_fname,
+ &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_STATVFS(handle, client_fname, statbuf);
+err:
+ TALLOC_FREE(client_fname);
+ DEBUG(10, ("Leaving with path '%s'\n", smb_fname->base_name));
+ return status;
+}
+
+static DIR *um_fdopendir(vfs_handle_struct *handle,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr)
+{
+ struct um_dirinfo_struct *dirInfo = NULL;
+ DIR *dirstream;
+
+ DEBUG(10, ("Entering with fsp->fsp_name->base_name '%s'\n",
+ fsp->fsp_name->base_name));
+
+ dirstream = SMB_VFS_NEXT_FDOPENDIR(handle, fsp, mask, attr);
+ if (!dirstream) {
+ goto err;
+ }
+
+ if (alloc_set_client_dirinfo(handle,
+ fsp->fsp_name->base_name,
+ &dirInfo)) {
+ goto err;
+ }
+
+ dirInfo->dirstream = dirstream;
+
+ if (!dirInfo->isInMediaFiles) {
+ /*
+ * FIXME: this is the original code, something must be
+ * missing here, but what? -slow
+ */
+ goto out;
+ }
+
+out:
+ DEBUG(10, ("Leaving with dirInfo->dirpath '%s', "
+ "dirInfo->clientPath '%s', "
+ "fsp->fsp_name->st.st_ex_mtime %s",
+ dirInfo->dirpath,
+ dirInfo->clientPath,
+ ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec))));
+ return (DIR *) dirInfo;
+
+err:
+ DEBUG(1, ("Failing with fsp->fsp_name->base_name '%s'\n",
+ fsp->fsp_name->base_name));
+ TALLOC_FREE(dirInfo);
+ return NULL;
+}
+
+/*
+ * skip own suffixed directory
+ * replace own suffixed directory with non suffixed.
+ *
+ * Success: return dirent
+ * End of data: return NULL
+ * Failure: set errno, return NULL
+ */
+static struct dirent *
+um_readdir(vfs_handle_struct *handle, struct files_struct *dirfsp, DIR *dirp)
+{
+ um_dirinfo_struct* dirInfo = (um_dirinfo_struct*)dirp;
+ struct dirent *d = NULL;
+ int skip;
+
+ DEBUG(10, ("dirInfo->dirpath '%s', "
+ "dirInfo->clientPath '%s', "
+ "dirInfo->isInMediaFiles '%s', "
+ "dirInfo->clientSubDirname '%s'\n",
+ dirInfo->dirpath,
+ dirInfo->clientPath,
+ dirInfo->isInMediaFiles ? "true" : "false",
+ dirInfo->clientSubDirname));
+
+ if (!dirInfo->isInMediaFiles) {
+ return SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream);
+ }
+
+ do {
+ const char* dname;
+ bool isAppleDouble;
+ char *digits;
+ size_t digits_len;
+ uintmax_t number;
+
+ skip = false;
+ d = SMB_VFS_NEXT_READDIR(handle, dirfsp, dirInfo->dirstream);
+
+ if (d == NULL) {
+ break;
+ }
+
+ /* ignore apple double prefix for logic below */
+ if (is_apple_double(d->d_name)) {
+ dname = &d->d_name[APPLE_DOUBLE_PREFIX_LEN];
+ isAppleDouble = true;
+ } else {
+ dname = d->d_name;
+ isAppleDouble = false;
+ }
+
+ DEBUG(10, ("dname = '%s'\n", dname));
+
+ (void)get_digit_group(dname, &number);
+ digits = talloc_asprintf(talloc_tos(), "%ju", number);
+ if (digits == NULL) {
+ DEBUG(1, ("out of memory\n"));
+ goto err;
+ }
+ digits_len = strlen(digits);
+
+ if (alloc_set_client_dirinfo_path(handle,
+ dirInfo,
+ &((dirInfo)->clientSubDirname),
+ digits)) {
+ goto err;
+ }
+
+ /*
+ * If set to "true", vfs shows digits-only
+ * non-suffixed subdirectories. Normally, such
+ * subdirectories can exists only in non-media
+ * directories, so we set it to "false". Otherwise,
+ * if we have such subdirectories (probably created
+ * over not "unityed" connection), it can be little
+ * bit confusing.
+ */
+ if (strequal(dname, digits)) {
+ skip = false;
+ } else if (strequal(dname, dirInfo->clientSubDirname)) {
+ /*
+ * Remove suffix of this client's suffixed
+ * subdirectories
+ */
+ if (isAppleDouble) {
+ d->d_name[digits_len + APPLE_DOUBLE_PREFIX_LEN] = '\0';
+ } else {
+ d->d_name[digits_len] = '\0';
+ }
+ } else if (strnequal(digits, dname, digits_len)) {
+ /*
+ * Set to false to see another clients subdirectories
+ */
+ skip = false;
+ }
+ } while (skip);
+
+ DEBUG(10, ("Leaving um_readdir\n"));
+ return d;
+err:
+ TALLOC_FREE(dirInfo);
+ return NULL;
+}
+
+static void um_rewinddir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ DEBUG(10, ("Entering and leaving um_rewinddir\n"));
+ SMB_VFS_NEXT_REWINDDIR(handle,
+ ((um_dirinfo_struct*)dirp)->dirstream);
+}
+
+static int um_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ int status;
+ const char *path = NULL;
+ struct smb_filename *client_fname = NULL;
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ path = full_fname->base_name;
+ DEBUG(10, ("Entering with path '%s'\n", path));
+
+ if (!is_in_media_files(path) || !is_in_media_dir(path)) {
+ TALLOC_FREE(full_fname);
+ return SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ full_fname,
+ &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ client_fname,
+ mode);
+err:
+ DEBUG(10, ("Leaving with path '%s'\n", path));
+ TALLOC_FREE(client_fname);
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static int um_closedir(vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ DIR *realdirp = ((um_dirinfo_struct*)dirp)->dirstream;
+
+ TALLOC_FREE(dirp);
+
+ return SMB_VFS_NEXT_CLOSEDIR(handle, realdirp);
+}
+
+static int um_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ struct smb_filename *client_fname = NULL;
+ int ret;
+
+ DBG_DEBUG("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name);
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ }
+
+ if (alloc_get_client_smb_fname(handle, talloc_tos(),
+ smb_fname,
+ &client_fname)) {
+ ret = -1;
+ goto err;
+ }
+
+ /*
+ * FIXME:
+ * What about fsp->fsp_name? We also have to get correct stat
+ * info into fsp and smb_fname for DB files, don't we?
+ */
+
+ DEBUG(10, ("Leaving with smb_fname->base_name '%s' "
+ "smb_fname->st.st_ex_mtime %s"
+ "fsp->fsp_name->st.st_ex_mtime %s",
+ smb_fname->base_name,
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec)),
+ ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec))));
+
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ client_fname,
+ fsp,
+ how);
+err:
+ TALLOC_FREE(client_fname);
+ DEBUG(10, ("Leaving with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+ return ret;
+}
+
+static NTSTATUS um_create_file(vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result_fsp,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS status;
+ struct smb_filename *client_fname = NULL;
+
+ DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_CREATE_FILE(
+ handle,
+ req,
+ dirfsp,
+ smb_fname,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ allocation_size,
+ private_flags,
+ sd,
+ ea_list,
+ result_fsp,
+ pinfo,
+ in_context_blobs,
+ out_context_blobs);
+ }
+
+ if (alloc_get_client_smb_fname(handle, talloc_tos(),
+ smb_fname,
+ &client_fname)) {
+ status = map_nt_error_from_unix(errno);
+ goto err;
+ }
+
+ /*
+ * FIXME:
+ * This only creates files, so we don't have to worry about
+ * our fake directory stat'ing here. But we still need to
+ * route stat calls for DB files properly, right?
+ */
+ status = SMB_VFS_NEXT_CREATE_FILE(
+ handle,
+ req,
+ dirfsp,
+ client_fname,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ allocation_size,
+ private_flags,
+ sd,
+ ea_list,
+ result_fsp,
+ pinfo,
+ in_context_blobs,
+ out_context_blobs);
+err:
+ TALLOC_FREE(client_fname);
+ DEBUG(10, ("Leaving with smb_fname->base_name '%s'"
+ "smb_fname->st.st_ex_mtime %s"
+ " fsp->fsp_name->st.st_ex_mtime %s",
+ smb_fname->base_name,
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec)),
+ (*result_fsp) && VALID_STAT((*result_fsp)->fsp_name->st) ?
+ ctime(&((*result_fsp)->fsp_name->st.st_ex_mtime.tv_sec)) :
+ "No fsp time\n"));
+ return status;
+}
+
+static int um_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int status;
+ struct smb_filename *src_full_fname = NULL;
+ struct smb_filename *dst_full_fname = NULL;
+ struct smb_filename *src_client_fname = NULL;
+ struct smb_filename *dst_client_fname = NULL;
+
+ src_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (src_full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ dst_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (dst_full_fname == NULL) {
+ TALLOC_FREE(src_full_fname);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ DBG_DEBUG( "Entering with "
+ "smb_fname_src->base_name '%s', "
+ "smb_fname_dst->base_name '%s'\n",
+ smb_fname_src->base_name,
+ smb_fname_dst->base_name);
+
+ if (!is_in_media_files(src_full_fname->base_name)
+ &&
+ !is_in_media_files(dst_full_fname->base_name)) {
+ TALLOC_FREE(src_full_fname);
+ TALLOC_FREE(dst_full_fname);
+ return SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ src_full_fname,
+ &src_client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ dst_full_fname,
+ &dst_client_fname);
+
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_RENAMEAT(handle,
+ handle->conn->cwd_fsp,
+ src_client_fname,
+ handle->conn->cwd_fsp,
+ dst_client_fname);
+
+err:
+ TALLOC_FREE(dst_client_fname);
+ TALLOC_FREE(src_client_fname);
+ TALLOC_FREE(src_full_fname);
+ TALLOC_FREE(dst_full_fname);
+ DBG_DEBUG( "Leaving with smb_fname_src->base_name '%s',"
+ " smb_fname_dst->base_name '%s'\n",
+ smb_fname_src->base_name,
+ smb_fname_dst->base_name);
+ return status;
+}
+
+
+/*
+ * Success: return 0
+ * Failure: set errno, return -1
+ */
+static int um_stat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int status = 0;
+ struct smb_filename *client_fname = NULL;
+
+ DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_STAT(handle, smb_fname);
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ smb_fname,
+ &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+ DEBUG(10, ("Stat'ing client_fname->base_name '%s'\n",
+ client_fname->base_name));
+
+ status = SMB_VFS_NEXT_STAT(handle, client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ /*
+ * Unlike functions with const smb_filename, we have to modify
+ * smb_fname itself to pass our info back up.
+ */
+ DEBUG(10, ("Setting smb_fname '%s' stat from client_fname '%s'\n",
+ smb_fname->base_name, client_fname->base_name));
+ smb_fname->st = client_fname->st;
+
+err:
+ TALLOC_FREE(client_fname);
+ DEBUG(10, ("Leaving with smb_fname->st.st_ex_mtime %s",
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
+ return status;
+}
+
+static int um_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ int status = 0;
+ struct smb_filename *client_fname = NULL;
+
+ DEBUG(10, ("Entering with smb_fname->base_name '%s'\n",
+ smb_fname->base_name));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_LSTAT(handle, smb_fname);
+ }
+
+ client_fname = NULL;
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ smb_fname,
+ &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+ status = SMB_VFS_NEXT_LSTAT(handle, client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ smb_fname->st = client_fname->st;
+
+err:
+ TALLOC_FREE(client_fname);
+ DEBUG(10, ("Leaving with smb_fname->st.st_ex_mtime %s",
+ ctime(&(smb_fname->st.st_ex_mtime.tv_sec))));
+ return status;
+}
+
+static int um_fstat(vfs_handle_struct *handle,
+ files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ int status = 0;
+
+ DEBUG(10, ("Entering with fsp->fsp_name->base_name "
+ "'%s'\n", fsp_str_dbg(fsp)));
+
+ status = SMB_VFS_NEXT_FSTAT(handle, fsp, sbuf);
+ if (status != 0) {
+ goto out;
+ }
+
+ if ((fsp->fsp_name == NULL) ||
+ !is_in_media_files(fsp->fsp_name->base_name)) {
+ goto out;
+ }
+
+ status = um_stat(handle, fsp->fsp_name);
+ if (status != 0) {
+ goto out;
+ }
+
+ *sbuf = fsp->fsp_name->st;
+
+out:
+ DEBUG(10, ("Leaving with fsp->fsp_name->st.st_ex_mtime %s",
+ fsp->fsp_name != NULL ?
+ ctime(&(fsp->fsp_name->st.st_ex_mtime.tv_sec)) : "0\n"));
+ return status;
+}
+
+static int um_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *client_fname = NULL;
+
+ DEBUG(10, ("Entering um_unlinkat\n"));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ ret = alloc_get_client_smb_fname(handle, talloc_tos(),
+ full_fname,
+ &client_fname);
+ if (ret != 0) {
+ goto err;
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp->conn->cwd_fsp,
+ client_fname,
+ flags);
+
+err:
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(client_fname);
+ return ret;
+}
+
+static int um_lchown(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ int status;
+ struct smb_filename *client_fname = NULL;
+
+ DEBUG(10, ("Entering um_lchown\n"));
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_LCHOWN(handle, smb_fname, uid, gid);
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ smb_fname,
+ &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_LCHOWN(handle, client_fname, uid, gid);
+
+err:
+ TALLOC_FREE(client_fname);
+ return status;
+}
+
+static int um_chdir(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int status;
+ struct smb_filename *client_fname = NULL;
+
+ DEBUG(10, ("Entering um_chdir\n"));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+ }
+
+ status = alloc_get_client_smb_fname(handle,
+ talloc_tos(),
+ smb_fname,
+ &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_CHDIR(handle, client_fname);
+
+err:
+ TALLOC_FREE(client_fname);
+ return status;
+}
+
+static int um_symlinkat(vfs_handle_struct *handle,
+ const struct smb_filename *link_contents,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ int status;
+ struct smb_filename *new_link_target = NULL;
+ struct smb_filename *new_client_fname = NULL;
+ struct smb_filename *full_fname = NULL;
+
+ DEBUG(10, ("Entering um_symlinkat\n"));
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ new_smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ if (!is_in_media_files(link_contents->base_name) &&
+ !is_in_media_files(full_fname->base_name)) {
+ TALLOC_FREE(full_fname);
+ return SMB_VFS_NEXT_SYMLINKAT(handle,
+ link_contents,
+ dirfsp,
+ new_smb_fname);
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ link_contents, &new_link_target);
+ if (status != 0) {
+ goto err;
+ }
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ full_fname, &new_client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_SYMLINKAT(handle,
+ new_link_target,
+ handle->conn->cwd_fsp,
+ new_client_fname);
+
+err:
+ TALLOC_FREE(new_link_target);
+ TALLOC_FREE(new_client_fname);
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static int um_readlinkat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ int status;
+ struct smb_filename *client_fname = NULL;
+ struct smb_filename *full_fname = NULL;
+
+ DEBUG(10, ("Entering um_readlinkat\n"));
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ if (!is_in_media_files(full_fname->base_name)) {
+ TALLOC_FREE(full_fname);
+ return SMB_VFS_NEXT_READLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ full_fname, &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_READLINKAT(handle,
+ handle->conn->cwd_fsp,
+ client_fname,
+ buf,
+ bufsiz);
+
+err:
+ TALLOC_FREE(full_fname);
+ TALLOC_FREE(client_fname);
+ return status;
+}
+
+static int um_linkat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ int status;
+ struct smb_filename *old_full_fname = NULL;
+ struct smb_filename *new_full_fname = NULL;
+ struct smb_filename *old_client_fname = NULL;
+ struct smb_filename *new_client_fname = NULL;
+
+ old_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ old_smb_fname);
+ if (old_full_fname == NULL) {
+ return -1;
+ }
+ new_full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ new_smb_fname);
+ if (new_full_fname == NULL) {
+ TALLOC_FREE(old_full_fname);
+ return -1;
+ }
+
+ DEBUG(10, ("Entering um_linkat\n"));
+ if (!is_in_media_files(old_full_fname->base_name) &&
+ !is_in_media_files(new_full_fname->base_name)) {
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(new_full_fname);
+ return SMB_VFS_NEXT_LINKAT(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ old_full_fname, &old_client_fname);
+ if (status != 0) {
+ goto err;
+ }
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ new_full_fname, &new_client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_LINKAT(handle,
+ handle->conn->cwd_fsp,
+ old_client_fname,
+ handle->conn->cwd_fsp,
+ new_client_fname,
+ flags);
+
+err:
+ TALLOC_FREE(old_full_fname);
+ TALLOC_FREE(new_full_fname);
+ TALLOC_FREE(old_client_fname);
+ TALLOC_FREE(new_client_fname);
+ return status;
+}
+
+static int um_mknodat(vfs_handle_struct *handle,
+ files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ int status;
+ struct smb_filename *client_fname = NULL;
+ struct smb_filename *full_fname = NULL;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ DEBUG(10, ("Entering um_mknodat\n"));
+ if (!is_in_media_files(full_fname->base_name)) {
+ TALLOC_FREE(full_fname);
+ return SMB_VFS_NEXT_MKNODAT(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ full_fname, &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ status = SMB_VFS_NEXT_MKNODAT(handle,
+ handle->conn->cwd_fsp,
+ client_fname,
+ mode,
+ dev);
+
+err:
+ TALLOC_FREE(client_fname);
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static struct smb_filename *um_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *client_fname = NULL;
+ struct smb_filename *result_fname = NULL;
+ int status;
+
+ DEBUG(10, ("Entering um_realpath\n"));
+
+ if (!is_in_media_files(smb_fname->base_name)) {
+ return SMB_VFS_NEXT_REALPATH(handle, ctx, smb_fname);
+ }
+
+ status = alloc_get_client_smb_fname(handle, talloc_tos(),
+ smb_fname, &client_fname);
+ if (status != 0) {
+ goto err;
+ }
+
+ result_fname = SMB_VFS_NEXT_REALPATH(handle, ctx, client_fname);
+
+err:
+ TALLOC_FREE(client_fname);
+ return result_fname;
+}
+
+static int um_connect(vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ int rc;
+ struct um_config_data *config;
+ int enumval;
+
+ rc = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (rc != 0) {
+ return rc;
+ }
+
+ config = talloc_zero(handle->conn, struct um_config_data);
+ if (!config) {
+ DEBUG(1, ("talloc_zero() failed\n"));
+ errno = ENOMEM;
+ return -1;
+ }
+
+ enumval = lp_parm_enum(SNUM(handle->conn), UM_PARAM_TYPE_NAME,
+ "clientid", um_clientid, UM_CLIENTID_NAME);
+ if (enumval == -1) {
+ DEBUG(1, ("value for %s: type unknown\n",
+ UM_PARAM_TYPE_NAME));
+ return -1;
+ }
+ config->clientid = (enum um_clientid)enumval;
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct um_config_data,
+ return -1);
+
+ return 0;
+}
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers vfs_um_fns = {
+ .connect_fn = um_connect,
+
+ /* Disk operations */
+
+ .statvfs_fn = um_statvfs,
+
+ /* Directory operations */
+
+ .fdopendir_fn = um_fdopendir,
+ .readdir_fn = um_readdir,
+ .rewind_dir_fn = um_rewinddir,
+ .mkdirat_fn = um_mkdirat,
+ .closedir_fn = um_closedir,
+
+ /* File operations */
+
+ .openat_fn = um_openat,
+ .create_file_fn = um_create_file,
+ .renameat_fn = um_renameat,
+ .stat_fn = um_stat,
+ .lstat_fn = um_lstat,
+ .fstat_fn = um_fstat,
+ .unlinkat_fn = um_unlinkat,
+ .lchown_fn = um_lchown,
+ .chdir_fn = um_chdir,
+ .symlinkat_fn = um_symlinkat,
+ .readlinkat_fn = um_readlinkat,
+ .linkat_fn = um_linkat,
+ .mknodat_fn = um_mknodat,
+ .realpath_fn = um_realpath,
+
+ /* EA operations. */
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_unityed_media_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "unityed_media", &vfs_um_fns);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ vfs_um_debug_level = debug_add_class("unityed_media");
+
+ if (vfs_um_debug_level == -1) {
+ vfs_um_debug_level = DBGC_VFS;
+ DEBUG(1, ("unityed_media_init: Couldn't register custom "
+ "debugging class.\n"));
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_virusfilter.c b/source3/modules/vfs_virusfilter.c
new file mode 100644
index 0000000..ea1886d
--- /dev/null
+++ b/source3/modules/vfs_virusfilter.c
@@ -0,0 +1,1677 @@
+/*
+ * Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ * Copyright (C) 2016-2017 Trever L. Adams
+ * Copyright (C) 2017 Ralph Boehme <slow@samba.org>
+ * Copyright (C) 2017 Jeremy Allison <jra@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/*
+ * Default configuration values
+ * ======================================================================
+ */
+
+#define VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX ".infected"
+#define VIRUSFILTER_DEFAULT_RENAME_PREFIX "virusfilter."
+#define VIRUSFILTER_DEFAULT_RENAME_SUFFIX ".infected"
+
+/* ====================================================================== */
+
+enum virusfilter_scanner_enum {
+ VIRUSFILTER_SCANNER_CLAMAV,
+ VIRUSFILTER_SCANNER_DUMMY,
+ VIRUSFILTER_SCANNER_FSAV,
+ VIRUSFILTER_SCANNER_SOPHOS
+};
+
+static const struct enum_list scanner_list[] = {
+ { VIRUSFILTER_SCANNER_CLAMAV, "clamav" },
+ { VIRUSFILTER_SCANNER_DUMMY, "dummy" },
+ { VIRUSFILTER_SCANNER_FSAV, "fsav" },
+ { VIRUSFILTER_SCANNER_SOPHOS, "sophos" },
+ { -1, NULL }
+};
+
+static const struct enum_list virusfilter_actions[] = {
+ { VIRUSFILTER_ACTION_QUARANTINE, "quarantine" },
+ { VIRUSFILTER_ACTION_RENAME, "rename" },
+ { VIRUSFILTER_ACTION_DELETE, "delete" },
+
+ /* alias for "delete" */
+ { VIRUSFILTER_ACTION_DELETE, "remove" },
+
+ /* alias for "delete" */
+ { VIRUSFILTER_ACTION_DELETE, "unlink" },
+ { VIRUSFILTER_ACTION_DO_NOTHING, "nothing" },
+ { -1, NULL}
+};
+
+static int virusfilter_config_destructor(struct virusfilter_config *config)
+{
+ TALLOC_FREE(config->backend);
+ return 0;
+}
+
+/*
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_directory_exist(
+ struct vfs_handle_struct *handle,
+ const char *dname)
+{
+ int ret = -1;
+ struct smb_filename smb_fname = {
+ .base_name = discard_const_p(char, dname)
+ };
+
+ ret = SMB_VFS_STAT(handle->conn, &smb_fname);
+ if (ret == 0) {
+ return S_ISDIR(smb_fname.st.st_ex_mode);
+ }
+
+ return false;
+}
+
+/**
+ * Create directory tree
+ * @param conn connection
+ * @param dname Directory tree to be created
+ * @return Returns true for success
+ * This is adapted from vfs_recycle module.
+ * Caller must have become_root();
+ */
+static bool quarantine_create_dir(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *dname)
+{
+ size_t len = 0;
+ size_t cat_len = 0;
+ char *new_dir = NULL;
+ char *tmp_str = NULL;
+ char *token = NULL;
+ char *tok_str = NULL;
+ bool status = false;
+ bool ok = false;
+ int ret = -1;
+ char *saveptr = NULL;
+
+ tmp_str = talloc_strdup(talloc_tos(), dname);
+ if (tmp_str == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ errno = ENOMEM;
+ goto done;
+ }
+ tok_str = tmp_str;
+
+ len = strlen(dname)+1;
+ new_dir = (char *)talloc_size(talloc_tos(), len + 1);
+ if (new_dir == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ errno = ENOMEM;
+ goto done;
+ }
+ *new_dir = '\0';
+ if (dname[0] == '/') {
+ /* Absolute path. */
+ cat_len = strlcat(new_dir, "/", len + 1);
+ if (cat_len >= len+1) {
+ goto done;
+ }
+ }
+
+ /* Create directory tree if necessary */
+ for (token = strtok_r(tok_str, "/", &saveptr);
+ token != NULL;
+ token = strtok_r(NULL, "/", &saveptr))
+ {
+ cat_len = strlcat(new_dir, token, len + 1);
+ if (cat_len >= len+1) {
+ goto done;
+ }
+ ok = quarantine_directory_exist(handle, new_dir);
+ if (ok == true) {
+ DBG_DEBUG("quarantine: dir %s already exists\n",
+ new_dir);
+ } else {
+ struct smb_filename *smb_fname = NULL;
+
+ DBG_INFO("quarantine: creating new dir %s\n", new_dir);
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ new_dir,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto done;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ handle->conn->cwd_fsp,
+ smb_fname,
+ config->quarantine_dir_mode);
+ if (ret != 0) {
+ TALLOC_FREE(smb_fname);
+
+ DBG_WARNING("quarantine: mkdirat failed for %s "
+ "with error: %s\n", new_dir,
+ strerror(errno));
+ status = false;
+ goto done;
+ }
+ TALLOC_FREE(smb_fname);
+ }
+ cat_len = strlcat(new_dir, "/", len + 1);
+ if (cat_len >= len + 1) {
+ goto done;
+ }
+ }
+
+ status = true;
+done:
+ TALLOC_FREE(tmp_str);
+ TALLOC_FREE(new_dir);
+ return status;
+}
+
+static int virusfilter_vfs_connect(
+ struct vfs_handle_struct *handle,
+ const char *svc,
+ const char *user)
+{
+ int snum = SNUM(handle->conn);
+ struct virusfilter_config *config = NULL;
+ const char *exclude_files = NULL;
+ const char *infected_files = NULL;
+ const char *temp_quarantine_dir_mode = NULL;
+ const char *infected_file_command = NULL;
+ const char *scan_error_command = NULL;
+ const char *quarantine_dir = NULL;
+ const char *quarantine_prefix = NULL;
+ const char *quarantine_suffix = NULL;
+ const char *rename_prefix = NULL;
+ const char *rename_suffix = NULL;
+ const char *socket_path = NULL;
+ char *sret = NULL;
+ char *tmp = NULL;
+ enum virusfilter_scanner_enum backend;
+ int connect_timeout = 0;
+ int io_timeout = 0;
+ int ret = -1;
+
+ config = talloc_zero(handle, struct virusfilter_config);
+ if (config == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return -1;
+ }
+ talloc_set_destructor(config, virusfilter_config_destructor);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config, NULL,
+ struct virusfilter_config, return -1);
+
+ config->scan_request_limit = lp_parm_int(
+ snum, "virusfilter", "scan request limit", 0);
+
+ config->scan_on_open = lp_parm_bool(
+ snum, "virusfilter", "scan on open", true);
+
+ config->scan_on_close = lp_parm_bool(
+ snum, "virusfilter", "scan on close", false);
+
+ config->max_nested_scan_archive = lp_parm_int(
+ snum, "virusfilter", "max nested scan archive", 1);
+
+ config->scan_archive = lp_parm_bool(
+ snum, "virusfilter", "scan archive", false);
+
+ config->scan_mime = lp_parm_bool(
+ snum, "virusfilter", "scan mime", false);
+
+ config->max_file_size = (ssize_t)lp_parm_ulong(
+ snum, "virusfilter", "max file size", 100000000L);
+
+ config->min_file_size = (ssize_t)lp_parm_ulong(
+ snum, "virusfilter", "min file size", 10);
+
+ exclude_files = lp_parm_const_string(
+ snum, "virusfilter", "exclude files", NULL);
+ if (exclude_files != NULL) {
+ set_namearray(&config->exclude_files, exclude_files);
+ }
+
+ infected_files = lp_parm_const_string(
+ snum, "virusfilter", "infected files", NULL);
+ if (infected_files != NULL) {
+ set_namearray(&config->infected_files, infected_files);
+ }
+
+ config->cache_entry_limit = lp_parm_int(
+ snum, "virusfilter", "cache entry limit", 100);
+
+ config->cache_time_limit = lp_parm_int(
+ snum, "virusfilter", "cache time limit", 10);
+
+ config->infected_file_action = lp_parm_enum(
+ snum, "virusfilter", "infected file action",
+ virusfilter_actions, VIRUSFILTER_ACTION_DO_NOTHING);
+
+ infected_file_command = lp_parm_const_string(
+ snum, "virusfilter", "infected file command", NULL);
+ if (infected_file_command != NULL) {
+ config->infected_file_command = talloc_strdup(
+ config,
+ infected_file_command);
+ if (config->infected_file_command == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+ scan_error_command = lp_parm_const_string(
+ snum, "virusfilter", "scan error command", NULL);
+ if (scan_error_command != NULL) {
+ config->scan_error_command = talloc_strdup(config,
+ scan_error_command);
+ if (config->scan_error_command == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ config->block_access_on_error = lp_parm_bool(
+ snum, "virusfilter", "block access on error", false);
+
+ tmp = talloc_asprintf(config, "%s/.quarantine",
+ handle->conn->connectpath);
+
+ quarantine_dir = lp_parm_const_string(
+ snum, "virusfilter", "quarantine directory",
+ tmp ? tmp : "/tmp/.quarantine");
+ if (quarantine_dir != NULL) {
+ config->quarantine_dir = talloc_strdup(config, quarantine_dir);
+ if (config->quarantine_dir == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ if (tmp != config->quarantine_dir) {
+ TALLOC_FREE(tmp);
+ }
+
+ temp_quarantine_dir_mode = lp_parm_const_string(
+ snum, "virusfilter", "quarantine directory mode", "0755");
+ if (temp_quarantine_dir_mode != NULL) {
+ unsigned int mode = 0;
+ sscanf(temp_quarantine_dir_mode, "%o", &mode);
+ config->quarantine_dir_mode = mode;
+ }
+
+ quarantine_prefix = lp_parm_const_string(
+ snum, "virusfilter", "quarantine prefix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+ if (quarantine_prefix != NULL) {
+ config->quarantine_prefix = talloc_strdup(config,
+ quarantine_prefix);
+ if (config->quarantine_prefix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ quarantine_suffix = lp_parm_const_string(
+ snum, "virusfilter", "quarantine suffix",
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+ if (quarantine_suffix != NULL) {
+ config->quarantine_suffix = talloc_strdup(config,
+ quarantine_suffix);
+ if (config->quarantine_suffix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ /*
+ * Make sure prefixes and suffixes do not contain directory
+ * delimiters
+ */
+ if (config->quarantine_prefix != NULL) {
+ sret = strstr(config->quarantine_prefix, "/");
+ if (sret != NULL) {
+ DBG_ERR("quarantine prefix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->quarantine_prefix,
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX);
+ config->quarantine_prefix =
+ VIRUSFILTER_DEFAULT_QUARANTINE_PREFIX;
+ }
+ }
+ if (config->quarantine_suffix != NULL) {
+ sret = strstr(config->quarantine_suffix, "/");
+ if (sret != NULL) {
+ DBG_ERR("quarantine suffix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->quarantine_suffix,
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX);
+ config->quarantine_suffix =
+ VIRUSFILTER_DEFAULT_QUARANTINE_SUFFIX;
+ }
+ }
+
+ config->quarantine_keep_tree = lp_parm_bool(
+ snum, "virusfilter", "quarantine keep tree", true);
+
+ config->quarantine_keep_name = lp_parm_bool(
+ snum, "virusfilter", "quarantine keep name", true);
+
+ rename_prefix = lp_parm_const_string(
+ snum, "virusfilter", "rename prefix",
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+ if (rename_prefix != NULL) {
+ config->rename_prefix = talloc_strdup(config, rename_prefix);
+ if (config->rename_prefix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ rename_suffix = lp_parm_const_string(
+ snum, "virusfilter", "rename suffix",
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+ if (rename_suffix != NULL) {
+ config->rename_suffix = talloc_strdup(config, rename_suffix);
+ if (config->rename_suffix == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ /*
+ * Make sure prefixes and suffixes do not contain directory
+ * delimiters
+ */
+ if (config->rename_prefix != NULL) {
+ sret = strstr(config->rename_prefix, "/");
+ if (sret != NULL) {
+ DBG_ERR("rename prefix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->rename_prefix,
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX);
+ config->rename_prefix =
+ VIRUSFILTER_DEFAULT_RENAME_PREFIX;
+ }
+ }
+ if (config->rename_suffix != NULL) {
+ sret = strstr(config->rename_suffix, "/");
+ if (sret != NULL) {
+ DBG_ERR("rename suffix must not contain directory "
+ "delimiter(s) such as '/' (%s replaced with %s)\n",
+ config->rename_suffix,
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX);
+ config->rename_suffix =
+ VIRUSFILTER_DEFAULT_RENAME_SUFFIX;
+ }
+ }
+
+ config->infected_open_errno = lp_parm_int(
+ snum, "virusfilter", "infected file errno on open", EACCES);
+
+ config->infected_close_errno = lp_parm_int(
+ snum, "virusfilter", "infected file errno on close", 0);
+
+ config->scan_error_open_errno = lp_parm_int(
+ snum, "virusfilter", "scan error errno on open", EACCES);
+
+ config->scan_error_close_errno = lp_parm_int(
+ snum, "virusfilter", "scan error errno on close", 0);
+
+ socket_path = lp_parm_const_string(
+ snum, "virusfilter", "socket path", NULL);
+ if (socket_path != NULL) {
+ config->socket_path = talloc_strdup(config, socket_path);
+ if (config->socket_path == NULL) {
+ DBG_ERR("virusfilter-vfs: out of memory!\n");
+ return -1;
+ }
+ }
+
+ /* canonicalize socket_path */
+ if (config->socket_path != NULL && config->socket_path[0] != '/') {
+ DBG_ERR("socket path must be an absolute path. "
+ "Using backend default\n");
+ config->socket_path = NULL;
+ }
+ if (config->socket_path != NULL) {
+ config->socket_path = canonicalize_absolute_path(
+ handle, config->socket_path);
+ if (config->socket_path == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ connect_timeout = lp_parm_int(snum, "virusfilter",
+ "connect timeout", 30000);
+
+ io_timeout = lp_parm_int(snum, "virusfilter", "io timeout", 60000);
+
+ config->io_h = virusfilter_io_new(config, connect_timeout, io_timeout);
+ if (config->io_h == NULL) {
+ DBG_ERR("virusfilter_io_new failed\n");
+ return -1;
+ }
+
+ if (config->cache_entry_limit > 0) {
+ config->cache = virusfilter_cache_new(handle,
+ config->cache_entry_limit,
+ config->cache_time_limit);
+ if (config->cache == NULL) {
+ DBG_ERR("Initializing cache failed: Cache disabled\n");
+ return -1;
+ }
+ }
+
+ /*
+ * Check quarantine directory now to save processing
+ * and becoming root over and over.
+ */
+ if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+ bool ok = true;
+ bool dir_exists;
+
+ /*
+ * Do SMB_VFS_NEXT_MKDIR(config->quarantine_dir)
+ * hierarchy
+ */
+ become_root();
+ dir_exists = quarantine_directory_exist(handle,
+ config->quarantine_dir);
+ if (!dir_exists) {
+ DBG_DEBUG("Creating quarantine directory: %s\n",
+ config->quarantine_dir);
+ ok = quarantine_create_dir(handle, config,
+ config->quarantine_dir);
+ }
+ unbecome_root();
+ if (!ok) {
+ DBG_ERR("Creating quarantine directory %s "
+ "failed with %s\n",
+ config->quarantine_dir,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ /*
+ * Now that the frontend options are initialized, load the configured
+ * backend.
+ */
+
+ backend = (enum virusfilter_scanner_enum)lp_parm_enum(snum,
+ "virusfilter",
+ "scanner",
+ scanner_list,
+ -1);
+ if (backend == (enum virusfilter_scanner_enum)-1) {
+ DBG_ERR("No AV-Scanner configured, "
+ "please set \"virusfilter:scanner\"\n");
+ return -1;
+ }
+
+ switch (backend) {
+ case VIRUSFILTER_SCANNER_SOPHOS:
+ ret = virusfilter_sophos_init(config);
+ break;
+ case VIRUSFILTER_SCANNER_FSAV:
+ ret = virusfilter_fsav_init(config);
+ break;
+ case VIRUSFILTER_SCANNER_CLAMAV:
+ ret = virusfilter_clamav_init(config);
+ break;
+ case VIRUSFILTER_SCANNER_DUMMY:
+ ret = virusfilter_dummy_init(config);
+ break;
+ default:
+ DBG_ERR("Unhandled scanner %d\n", backend);
+ return -1;
+ }
+ if (ret != 0) {
+ DBG_ERR("Scanner backend init failed\n");
+ return -1;
+ }
+
+ if (config->backend->fns->connect != NULL) {
+ ret = config->backend->fns->connect(handle, config, svc, user);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return SMB_VFS_NEXT_CONNECT(handle, svc, user);
+}
+
+static void virusfilter_vfs_disconnect(struct vfs_handle_struct *handle)
+{
+ struct virusfilter_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return);
+
+ if (config->backend->fns->disconnect != NULL) {
+ config->backend->fns->disconnect(handle);
+ }
+
+ free_namearray(config->exclude_files);
+ virusfilter_io_disconnect(config->io_h);
+
+ SMB_VFS_NEXT_DISCONNECT(handle);
+}
+
+static int virusfilter_set_module_env(TALLOC_CTX *mem_ctx,
+ struct virusfilter_config *config,
+ char **env_list)
+{
+ int ret;
+
+ ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_VERSION",
+ VIRUSFILTER_VERSION);
+ if (ret == -1) {
+ return -1;
+ }
+ ret = virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_MODULE_NAME",
+ config->backend->name);
+ if (ret == -1) {
+ return -1;
+ }
+
+ if (config->backend->version != 0) {
+ char *version = NULL;
+
+ version = talloc_asprintf(talloc_tos(), "%u",
+ config->backend->version);
+ if (version == NULL) {
+ return -1;
+ }
+ ret = virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_MODULE_VERSION",
+ version);
+ TALLOC_FREE(version);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static char *quarantine_check_tree(TALLOC_CTX *mem_ctx,
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct smb_filename *smb_fname,
+ char *q_dir_in,
+ char *cwd_fname)
+{
+ char *temp_path = NULL;
+ char *q_dir_out = NULL;
+ bool ok;
+
+ temp_path = talloc_asprintf(talloc_tos(), "%s/%s", q_dir_in, cwd_fname);
+ if (temp_path == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ goto out;
+ }
+
+ become_root();
+ ok = quarantine_directory_exist(handle, temp_path);
+ unbecome_root();
+ if (ok) {
+ DBG_DEBUG("quarantine: directory [%s] exists\n", temp_path);
+ q_dir_out = talloc_move(mem_ctx, &temp_path);
+ goto out;
+ }
+
+ DBG_DEBUG("quarantine: Creating directory %s\n", temp_path);
+
+ become_root();
+ ok = quarantine_create_dir(handle, config, temp_path);
+ unbecome_root();
+ if (!ok) {
+ DBG_NOTICE("Could not create quarantine directory [%s], "
+ "ignoring for [%s]\n",
+ temp_path, smb_fname_str_dbg(smb_fname));
+ goto out;
+ }
+
+ q_dir_out = talloc_move(mem_ctx, &temp_path);
+
+out:
+ TALLOC_FREE(temp_path);
+ return q_dir_out;
+}
+
+static virusfilter_action infected_file_action_quarantine(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *fsp,
+ const char **filepath_newp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ const struct smb_filename *smb_fname = fsp->fsp_name;
+ struct smb_filename *q_smb_fname = NULL;
+ char *q_dir = NULL;
+ char *q_prefix = NULL;
+ char *q_suffix = NULL;
+ char *q_filepath = NULL;
+ char *dir_name = NULL;
+ const char *base_name = NULL;
+ char *rand_filename_component = NULL;
+ virusfilter_action action = VIRUSFILTER_ACTION_QUARANTINE;
+ bool ok = false;
+ int ret = -1;
+ int saved_errno = 0;
+
+ q_dir = virusfilter_string_sub(frame, conn,
+ config->quarantine_dir);
+ q_prefix = virusfilter_string_sub(frame, conn,
+ config->quarantine_prefix);
+ q_suffix = virusfilter_string_sub(frame, conn,
+ config->quarantine_suffix);
+ if (q_dir == NULL || q_prefix == NULL || q_suffix == NULL) {
+ DBG_ERR("Quarantine failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (config->quarantine_keep_name || config->quarantine_keep_tree) {
+ ok = parent_dirname(frame, smb_fname->base_name,
+ &dir_name, &base_name);
+ if (!ok) {
+ DBG_ERR("parent_dirname failed\n");
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (config->quarantine_keep_tree) {
+ char *tree = NULL;
+
+ tree = quarantine_check_tree(frame, handle, config,
+ smb_fname, q_dir,
+ cwd_fname);
+ if (tree == NULL) {
+ /*
+ * If we can't create the tree, just move it
+ * into the toplevel quarantine dir.
+ */
+ tree = q_dir;
+ }
+ q_dir = tree;
+ }
+ }
+
+ /* Get a 16 byte + \0 random filename component. */
+ rand_filename_component = generate_random_str(frame, 16);
+ if (rand_filename_component == NULL) {
+ DBG_ERR("generate_random_str failed\n");
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (config->quarantine_keep_name) {
+ q_filepath = talloc_asprintf(frame, "%s/%s%s%s-%s",
+ q_dir, q_prefix,
+ base_name, q_suffix,
+ rand_filename_component);
+ } else {
+ q_filepath = talloc_asprintf(frame, "%s/%s%s",
+ q_dir, q_prefix,
+ rand_filename_component);
+ }
+ if (q_filepath == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ q_smb_fname = synthetic_smb_fname(frame,
+ q_filepath,
+ smb_fname->stream_name,
+ NULL,
+ 0,
+ smb_fname->flags);
+ if (q_smb_fname == NULL) {
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ become_root();
+ ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("Quarantine [%s/%s] rename to %s failed: %s\n",
+ cwd_fname, fname, q_filepath, strerror(saved_errno));
+ errno = saved_errno;
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ *filepath_newp = talloc_move(mem_ctx, &q_filepath);
+
+out:
+ TALLOC_FREE(frame);
+ return action;
+}
+
+static virusfilter_action infected_file_action_rename(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *fsp,
+ const char **filepath_newp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ const struct smb_filename *smb_fname = fsp->fsp_name;
+ struct smb_filename *q_smb_fname = NULL;
+ char *q_dir = NULL;
+ char *q_prefix = NULL;
+ char *q_suffix = NULL;
+ char *q_filepath = NULL;
+ const char *base_name = NULL;
+ virusfilter_action action = VIRUSFILTER_ACTION_RENAME;
+ bool ok = false;
+ int ret = -1;
+ int saved_errno = 0;
+
+ q_prefix = virusfilter_string_sub(frame, conn,
+ config->rename_prefix);
+ q_suffix = virusfilter_string_sub(frame, conn,
+ config->rename_suffix);
+ if (q_prefix == NULL || q_suffix == NULL) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ ok = parent_dirname(frame, fname, &q_dir, &base_name);
+ if (!ok) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ if (q_dir == NULL) {
+ DBG_ERR("Rename failed: %s/%s: Cannot allocate "
+ "memory\n", cwd_fname, fname);
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ q_filepath = talloc_asprintf(frame, "%s/%s%s%s", q_dir,
+ q_prefix, base_name, q_suffix);
+
+ q_smb_fname = synthetic_smb_fname(frame, q_filepath,
+ smb_fname->stream_name, NULL,
+ 0,
+ smb_fname->flags);
+ if (q_smb_fname == NULL) {
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ become_root();
+ ret = virusfilter_vfs_next_move(handle, smb_fname, q_smb_fname);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+
+ if (ret == -1) {
+ DBG_ERR("Rename failed: %s/%s: Rename failed: %s\n",
+ cwd_fname, fname, strerror(saved_errno));
+ errno = saved_errno;
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ goto out;
+ }
+
+ *filepath_newp = talloc_move(mem_ctx, &q_filepath);
+
+out:
+ TALLOC_FREE(frame);
+ return action;
+}
+
+static virusfilter_action infected_file_action_delete(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *fsp)
+{
+ int ret;
+ int saved_errno = 0;
+
+ become_root();
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ handle->conn->cwd_fsp,
+ fsp->fsp_name,
+ 0);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("Delete [%s/%s] failed: %s\n",
+ fsp->conn->cwd_fsp->fsp_name->base_name,
+ fsp->fsp_name->base_name,
+ strerror(saved_errno));
+ errno = saved_errno;
+ return VIRUSFILTER_ACTION_DO_NOTHING;
+ }
+
+ return VIRUSFILTER_ACTION_DELETE;
+}
+
+static virusfilter_action virusfilter_do_infected_file_action(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ TALLOC_CTX *mem_ctx,
+ const struct files_struct *fsp,
+ const char **filepath_newp)
+{
+ virusfilter_action action;
+
+ *filepath_newp = NULL;
+
+ switch (config->infected_file_action) {
+ case VIRUSFILTER_ACTION_RENAME:
+ action = infected_file_action_rename(handle, config, mem_ctx,
+ fsp, filepath_newp);
+ break;
+
+ case VIRUSFILTER_ACTION_QUARANTINE:
+ action = infected_file_action_quarantine(handle, config, mem_ctx,
+ fsp, filepath_newp);
+ break;
+
+ case VIRUSFILTER_ACTION_DELETE:
+ action = infected_file_action_delete(handle, fsp);
+ break;
+
+ case VIRUSFILTER_ACTION_DO_NOTHING:
+ default:
+ action = VIRUSFILTER_ACTION_DO_NOTHING;
+ break;
+ }
+
+ return action;
+}
+
+static virusfilter_action virusfilter_treat_infected_file(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = handle->conn;
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ char *fname = fsp->fsp_name->base_name;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ int i;
+ virusfilter_action action;
+ const char *action_name = "UNKNOWN";
+ const char *filepath_q = NULL;
+ char *env_list = NULL;
+ char *command = NULL;
+ int command_result;
+ int ret;
+
+ action = virusfilter_do_infected_file_action(handle, config, mem_ctx,
+ fsp, &filepath_q);
+ for (i=0; virusfilter_actions[i].name; i++) {
+ if (virusfilter_actions[i].value == action) {
+ action_name = virusfilter_actions[i].name;
+ break;
+ }
+ }
+ DBG_WARNING("Infected file action: %s/%s: %s\n", cwd_fname,
+ fname, action_name);
+
+ if (!config->infected_file_command) {
+ return action;
+ }
+
+ ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+ if (ret == -1) {
+ goto done;
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_SERVICE_FILE_PATH",
+ fname);
+ if (ret == -1) {
+ goto done;
+ }
+ if (report != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_FILE_REPORT",
+ report);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_INFECTED_FILE_ACTION",
+ action_name);
+ if (ret == -1) {
+ goto done;
+ }
+ if (filepath_q != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_QUARANTINED_FILE_PATH",
+ filepath_q);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ if (is_cache) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE", "yes");
+ if (ret == -1) {
+ goto done;
+ }
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ config->infected_file_command);
+ if (command == NULL) {
+ DBG_ERR("virusfilter_string_sub failed\n");
+ goto done;
+ }
+
+ DBG_NOTICE("Infected file command line: %s/%s: %s\n", cwd_fname,
+ fname, command);
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ conn, true);
+ if (command_result != 0) {
+ DBG_ERR("Infected file command failed: %d\n", command_result);
+ }
+
+ DBG_DEBUG("Infected file command finished: %d\n", command_result);
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+
+ return action;
+}
+
+static void virusfilter_treat_scan_error(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ const char *report,
+ bool is_cache)
+{
+ connection_struct *conn = handle->conn;
+ const char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ char *env_list = NULL;
+ char *command = NULL;
+ int command_result;
+ int ret;
+
+ if (!config->scan_error_command) {
+ return;
+ }
+ ret = virusfilter_set_module_env(mem_ctx, config, &env_list);
+ if (ret == -1) {
+ goto done;
+ }
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_SERVICE_FILE_PATH",
+ fname);
+ if (ret == -1) {
+ goto done;
+ }
+ if (report != NULL) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_SCAN_ERROR_REPORT",
+ report);
+ if (ret == -1) {
+ goto done;
+ }
+ }
+ if (is_cache) {
+ ret = virusfilter_env_set(mem_ctx, &env_list,
+ "VIRUSFILTER_RESULT_IS_CACHE", "1");
+ if (ret == -1) {
+ goto done;
+ }
+ }
+
+ command = virusfilter_string_sub(mem_ctx, conn,
+ config->scan_error_command);
+ if (command == NULL) {
+ DBG_ERR("virusfilter_string_sub failed\n");
+ goto done;
+ }
+
+ DBG_NOTICE("Scan error command line: %s/%s: %s\n", cwd_fname,
+ fname, command);
+
+ command_result = virusfilter_shell_run(mem_ctx, command, &env_list,
+ conn, true);
+ if (command_result != 0) {
+ DBG_ERR("Scan error command failed: %d\n", command_result);
+ }
+
+done:
+ TALLOC_FREE(env_list);
+ TALLOC_FREE(command);
+}
+
+static virusfilter_result virusfilter_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp)
+{
+ virusfilter_result scan_result;
+ char *scan_report = NULL;
+ const char *fname = fsp->fsp_name->base_name;
+ const char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ struct virusfilter_cache_entry *scan_cache_e = NULL;
+ bool is_cache = false;
+ virusfilter_action file_action = VIRUSFILTER_ACTION_DO_NOTHING;
+ bool add_scan_cache = true;
+ bool ok = false;
+
+ if (config->cache) {
+ DBG_DEBUG("Searching cache entry: fname: %s\n", fname);
+ scan_cache_e = virusfilter_cache_get(config->cache,
+ cwd_fname, fname);
+ if (scan_cache_e != NULL) {
+ DBG_DEBUG("Cache entry found: cached result: %d\n",
+ scan_cache_e->result);
+ is_cache = true;
+ scan_result = scan_cache_e->result;
+ scan_report = scan_cache_e->report;
+ goto virusfilter_scan_result_eval;
+ }
+ DBG_DEBUG("Cache entry not found\n");
+ }
+
+ if (config->backend->fns->scan_init != NULL) {
+ scan_result = config->backend->fns->scan_init(config);
+ if (scan_result != VIRUSFILTER_RESULT_OK) {
+ scan_result = VIRUSFILTER_RESULT_ERROR;
+ scan_report = talloc_asprintf(
+ talloc_tos(),
+ "Initializing scanner failed");
+ goto virusfilter_scan_result_eval;
+ }
+ }
+
+ scan_result = config->backend->fns->scan(handle, config, fsp,
+ &scan_report);
+
+ if (config->backend->fns->scan_end != NULL) {
+ bool scan_end = true;
+
+ if (config->scan_request_limit > 0) {
+ scan_end = false;
+ config->scan_request_count++;
+ if (config->scan_request_count >=
+ config->scan_request_limit)
+ {
+ scan_end = true;
+ config->scan_request_count = 0;
+ }
+ }
+ if (scan_end) {
+ config->backend->fns->scan_end(config);
+ }
+ }
+
+virusfilter_scan_result_eval:
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ DBG_INFO("Scan result: Clean: %s/%s\n", cwd_fname, fname);
+ break;
+
+ case VIRUSFILTER_RESULT_INFECTED:
+ DBG_ERR("Scan result: Infected: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "infected (memory error on report)");
+ file_action = virusfilter_treat_infected_file(handle,
+ config, fsp, scan_report, is_cache);
+ if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+ add_scan_cache = false;
+ }
+ break;
+
+ case VIRUSFILTER_RESULT_SUSPECTED:
+ if (!config->block_suspected_file) {
+ break;
+ }
+ DBG_ERR("Scan result: Suspected: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "suspected infection (memory error on report)");
+ file_action = virusfilter_treat_infected_file(handle,
+ config, fsp, scan_report, is_cache);
+ if (file_action != VIRUSFILTER_ACTION_DO_NOTHING) {
+ add_scan_cache = false;
+ }
+ break;
+
+ case VIRUSFILTER_RESULT_ERROR:
+ DBG_ERR("Scan result: Error: %s/%s: %s\n",
+ cwd_fname, fname, scan_report ? scan_report :
+ "error (memory error on report)");
+ virusfilter_treat_scan_error(handle, config, fsp,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+
+ default:
+ DBG_ERR("Scan result: Unknown result code %d: %s/%s: %s\n",
+ scan_result, cwd_fname, fname, scan_report ?
+ scan_report : "Unknown (memory error on report)");
+ virusfilter_treat_scan_error(handle, config, fsp,
+ scan_report, is_cache);
+ add_scan_cache = false;
+ break;
+ }
+
+ if (config->cache) {
+ if (!is_cache && add_scan_cache) {
+ DBG_DEBUG("Adding new cache entry: %s, %d\n", fname,
+ scan_result);
+ ok = virusfilter_cache_entry_add(
+ config->cache, cwd_fname, fname,
+ scan_result, scan_report);
+ if (!ok) {
+ DBG_ERR("Cannot create cache entry: "
+ "virusfilter_cache_entry_new failed\n");
+ goto virusfilter_scan_return;
+ }
+ } else if (is_cache) {
+ virusfilter_cache_entry_free(scan_cache_e);
+ }
+ }
+
+virusfilter_scan_return:
+ return scan_result;
+}
+
+static int virusfilter_vfs_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname_in,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct virusfilter_config *config = NULL;
+ const char *cwd_fname = dirfsp->fsp_name->base_name;
+ virusfilter_result scan_result;
+ const char *fname = fsp->fsp_name->base_name;
+ char *dir_name = NULL;
+ const char *base_name = NULL;
+ int scan_errno = 0;
+ size_t test_prefix;
+ size_t test_suffix;
+ int rename_trap_count = 0;
+ int ret;
+ bool ok1;
+ char *sret = NULL;
+ struct smb_filename *smb_fname = NULL;
+ SMB_STRUCT_STAT sbuf = smb_fname_in->st;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ if (fsp->fsp_flags.is_directory) {
+ DBG_INFO("Not scanned: Directory: %s/\n", cwd_fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ test_prefix = strlen(config->rename_prefix);
+ test_suffix = strlen(config->rename_suffix);
+ if (test_prefix > 0) {
+ rename_trap_count++;
+ }
+ if (test_suffix > 0) {
+ rename_trap_count++;
+ }
+
+ smb_fname = cp_smb_filename(mem_ctx, smb_fname_in);
+ if (smb_fname == NULL) {
+ goto virusfilter_vfs_open_fail;
+ }
+
+ if (is_named_stream(smb_fname)) {
+ DBG_INFO("Not scanned: only file backed streams can be scanned:"
+ " %s/%s\n", cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (!config->scan_on_open) {
+ DBG_INFO("Not scanned: scan on open is disabled: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (how->flags & O_TRUNC) {
+ DBG_INFO("Not scanned: Open flags have O_TRUNC: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ ret = SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf);
+ if (ret != 0) {
+
+ /*
+ * Do not return immediately if !(flags & O_CREAT) &&
+ * errno != ENOENT.
+ * Do not do this here or anywhere else. The module is
+ * stackable and there may be modules below, such as audit
+ * modules, which should be handled.
+ */
+ goto virusfilter_vfs_open_next;
+ }
+ ret = S_ISREG(sbuf.st_ex_mode);
+ if (ret == 0) {
+ DBG_INFO("Not scanned: Directory or special file: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+ if (config->max_file_size > 0 &&
+ sbuf.st_ex_size > config->max_file_size)
+ {
+ DBG_INFO("Not scanned: file size > max file size: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+ if (config->min_file_size > 0 &&
+ sbuf.st_ex_size < config->min_file_size)
+ {
+ DBG_INFO("Not scanned: file size < min file size: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ ok1 = is_in_path(fname, config->exclude_files, false);
+ if (config->exclude_files && ok1)
+ {
+ DBG_INFO("Not scanned: exclude files: %s/%s\n",
+ cwd_fname, fname);
+ goto virusfilter_vfs_open_next;
+ }
+
+ if (config->infected_file_action == VIRUSFILTER_ACTION_QUARANTINE) {
+ sret = strstr_m(fname, config->quarantine_dir);
+ if (sret != NULL) {
+ scan_errno = config->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ }
+
+ if (test_prefix > 0 || test_suffix > 0) {
+ ok1 = parent_dirname(mem_ctx, fname, &dir_name, &base_name);
+ if (ok1)
+ {
+ if (test_prefix > 0) {
+ ret = strncmp(base_name,
+ config->rename_prefix, test_prefix);
+ if (ret != 0) {
+ test_prefix = 0;
+ }
+ }
+ if (test_suffix > 0) {
+ ret = strcmp(base_name + (strlen(base_name)
+ - test_suffix),
+ config->rename_suffix);
+ if (ret != 0) {
+ test_suffix = 0;
+ }
+ }
+
+ TALLOC_FREE(dir_name);
+
+ if ((rename_trap_count == 2 && test_prefix &&
+ test_suffix) || (rename_trap_count == 1 &&
+ (test_prefix || test_suffix)))
+ {
+ scan_errno =
+ config->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ }
+ }
+
+ scan_result = virusfilter_scan(handle, config, fsp);
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ scan_errno = config->infected_open_errno;
+ goto virusfilter_vfs_open_fail;
+ case VIRUSFILTER_RESULT_ERROR:
+ if (config->block_access_on_error) {
+ DBG_INFO("Block access\n");
+ scan_errno = config->scan_error_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+ break;
+ default:
+ scan_errno = config->scan_error_open_errno;
+ goto virusfilter_vfs_open_fail;
+ }
+
+ TALLOC_FREE(smb_fname);
+
+virusfilter_vfs_open_next:
+ return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname_in, fsp, how);
+
+virusfilter_vfs_open_fail:
+ TALLOC_FREE(smb_fname);
+ errno = (scan_errno != 0) ? scan_errno : EACCES;
+ return -1;
+}
+
+static int virusfilter_vfs_close(
+ struct vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ /*
+ * The name of this variable is for consistency. If API changes to
+ * match _open change to cwd_fname as in virusfilter_vfs_open.
+ */
+ const char *cwd_fname = handle->conn->connectpath;
+
+ struct virusfilter_config *config = NULL;
+ char *fname = fsp->fsp_name->base_name;
+ int close_result = -1;
+ int close_errno = 0;
+ virusfilter_result scan_result;
+ int scan_errno = 0;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ /*
+ * Must close after scan? It appears not as the scanners are not
+ * internal and other modules such as greyhole seem to do
+ * SMB_VFS_NEXT_* functions before processing.
+ */
+ close_result = SMB_VFS_NEXT_CLOSE(handle, fsp);
+ if (close_result == -1) {
+ close_errno = errno;
+ }
+
+ /*
+ * Return immediately if close_result == -1, and close_errno == EBADF.
+ * If close failed, file likely doesn't exist, do not try to scan.
+ */
+ if (close_result == -1 && close_errno == EBADF) {
+ if (fsp->fsp_flags.modified) {
+ DBG_DEBUG("Removing cache entry (if existent): "
+ "fname: %s\n", fname);
+ virusfilter_cache_remove(config->cache,
+ cwd_fname, fname);
+ }
+ goto virusfilter_vfs_close_fail;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ DBG_INFO("Not scanned: Directory: %s/\n", cwd_fname);
+ return close_result;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ if (config->scan_on_open && fsp->fsp_flags.modified) {
+ if (config->cache) {
+ DBG_DEBUG("Removing cache entry (if existent)"
+ ": fname: %s\n", fname);
+ virusfilter_cache_remove(
+ config->cache,
+ cwd_fname, fname);
+ }
+ }
+ DBG_INFO("Not scanned: only file backed streams can be scanned:"
+ " %s/%s\n", cwd_fname, fname);
+ return close_result;
+ }
+
+ if (!config->scan_on_close) {
+ if (config->scan_on_open && fsp->fsp_flags.modified) {
+ if (config->cache) {
+ DBG_DEBUG("Removing cache entry (if existent)"
+ ": fname: %s\n", fname);
+ virusfilter_cache_remove(
+ config->cache,
+ cwd_fname, fname);
+ }
+ }
+ DBG_INFO("Not scanned: scan on close is disabled: %s/%s\n",
+ cwd_fname, fname);
+ return close_result;
+ }
+
+ if (!fsp->fsp_flags.modified) {
+ DBG_NOTICE("Not scanned: File not modified: %s/%s\n",
+ cwd_fname, fname);
+
+ return close_result;
+ }
+
+ if (is_in_path(fname, config->exclude_files, false)) {
+ DBG_INFO("Not scanned: exclude files: %s/%s\n",
+ cwd_fname, fname);
+ return close_result;
+ }
+
+ scan_result = virusfilter_scan(handle, config, fsp);
+
+ switch (scan_result) {
+ case VIRUSFILTER_RESULT_CLEAN:
+ break;
+ case VIRUSFILTER_RESULT_INFECTED:
+ scan_errno = config->infected_close_errno;
+ goto virusfilter_vfs_close_fail;
+ case VIRUSFILTER_RESULT_ERROR:
+ if (config->block_access_on_error) {
+ DBG_INFO("Block access\n");
+ scan_errno = config->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+ break;
+ default:
+ scan_errno = config->scan_error_close_errno;
+ goto virusfilter_vfs_close_fail;
+ }
+
+ if (close_errno != 0) {
+ errno = close_errno;
+ }
+
+ return close_result;
+
+virusfilter_vfs_close_fail:
+
+ errno = (scan_errno != 0) ? scan_errno : close_errno;
+
+ return close_result;
+}
+
+static int virusfilter_vfs_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ int ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+ struct virusfilter_config *config = NULL;
+ struct smb_filename *full_fname = NULL;
+ char *fname = NULL;
+ char *cwd_fname = dirfsp->fsp_name->base_name;
+
+ if (ret != 0 && errno != ENOENT) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ if (config->cache == NULL) {
+ return 0;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ fname = full_fname->base_name;
+
+ DBG_DEBUG("Removing cache entry (if existent): fname: %s\n", fname);
+ virusfilter_cache_remove(config->cache, cwd_fname, fname);
+
+ TALLOC_FREE(full_fname);
+ return 0;
+}
+
+static int virusfilter_vfs_renameat(
+ struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ int ret = SMB_VFS_NEXT_RENAMEAT(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+ struct virusfilter_config *config = NULL;
+ char *fname = NULL;
+ char *dst_fname = NULL;
+ char *cwd_fname = handle->conn->cwd_fsp->fsp_name->base_name;
+ struct smb_filename *full_src = NULL;
+ struct smb_filename *full_dst = NULL;
+
+ if (ret != 0) {
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct virusfilter_config, return -1);
+
+ if (config->cache == NULL) {
+ return 0;
+ }
+
+ full_src = full_path_from_dirfsp_atname(talloc_tos(),
+ srcfsp,
+ smb_fname_src);
+ if (full_src == NULL) {
+ errno = ENOMEM;
+ ret = -1;
+ goto out;
+ }
+
+ full_dst = full_path_from_dirfsp_atname(talloc_tos(),
+ dstfsp,
+ smb_fname_dst);
+ if (full_dst == NULL) {
+ errno = ENOMEM;
+ ret = -1;
+ goto out;
+ }
+
+ fname = full_src->base_name;
+ dst_fname = full_dst->base_name;
+
+ DBG_DEBUG("Renaming cache entry: fname: %s to: %s\n",
+ fname, dst_fname);
+ virusfilter_cache_entry_rename(config->cache,
+ cwd_fname,
+ fname,
+ dst_fname);
+
+ ret = 0;
+ out:
+ TALLOC_FREE(full_src);
+ TALLOC_FREE(full_dst);
+ return ret;
+}
+
+
+/* VFS operations */
+static struct vfs_fn_pointers vfs_virusfilter_fns = {
+ .connect_fn = virusfilter_vfs_connect,
+ .disconnect_fn = virusfilter_vfs_disconnect,
+ .openat_fn = virusfilter_vfs_openat,
+ .close_fn = virusfilter_vfs_close,
+ .unlinkat_fn = virusfilter_vfs_unlinkat,
+ .renameat_fn = virusfilter_vfs_renameat,
+};
+
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *);
+NTSTATUS vfs_virusfilter_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS status;
+
+ status = smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "virusfilter",
+ &vfs_virusfilter_fns);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ virusfilter_debug_class = debug_add_class("virusfilter");
+ if (virusfilter_debug_class == -1) {
+ virusfilter_debug_class = DBGC_VFS;
+ DBG_ERR("Couldn't register custom debugging class!\n");
+ } else {
+ DBG_DEBUG("Debug class number: %d\n", virusfilter_debug_class);
+ }
+
+ DBG_INFO("registered\n");
+
+ return status;
+}
diff --git a/source3/modules/vfs_virusfilter_clamav.c b/source3/modules/vfs_virusfilter_clamav.c
new file mode 100644
index 0000000..fb93cae
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_clamav.c
@@ -0,0 +1,195 @@
+/*
+ Samba-VirusFilter VFS modules
+ ClamAV clamd support
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Default values for standard "extra" configuration variables */
+
+#ifdef CLAMAV_DEFAULT_SOCKET_PATH
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH CLAMAV_DEFAULT_SOCKET_PATH
+#else
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/clamav/clamd.ctl"
+#endif
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+static int virusfilter_clamav_connect(struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user)
+{
+
+ /* To use clamd "zXXXX" commands */
+ virusfilter_io_set_writel_eol(config->io_h, "\0", 1);
+ virusfilter_io_set_readl_eol(config->io_h, "\0", 1);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_clamav_scan_init(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+ bool ok;
+
+ DBG_INFO("clamd: Connecting to socket: %s\n",
+ config->socket_path);
+
+ become_root();
+ ok = virusfilter_io_connect_path(io_h, config->socket_path);
+ unbecome_root();
+
+ if (!ok) {
+ DBG_ERR("clamd: Connecting to socket failed: %s: %s\n",
+ config->socket_path, strerror(errno));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ DBG_INFO("clamd: Connected\n");
+
+ return VIRUSFILTER_RESULT_OK;
+}
+
+static void virusfilter_clamav_scan_end(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+
+ DBG_INFO("clamd: Disconnecting\n");
+
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_clamav_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp)
+{
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ size_t filepath_len = strlen(cwd_fname) + 1 /* slash */ + strlen(fname);
+ struct virusfilter_io_handle *io_h = config->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+ char *report = NULL;
+ char *reply = NULL;
+ char *reply_msg = NULL;
+ char *reply_token;
+ bool ok;
+
+ DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "zSCAN %s/%s",
+ cwd_fname, fname);
+ if (!ok) {
+ DBG_ERR("clamd: zSCAN: I/O error: %s\n", strerror(errno));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_clamav_scan_return;
+ }
+
+ if (reply[filepath_len] != ':' ||
+ reply[filepath_len+1] != ' ')
+ {
+ DBG_ERR("clamd: zSCAN: Invalid reply: %s\n",
+ reply);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ goto virusfilter_clamav_scan_return;
+ }
+ reply_msg = reply + filepath_len + 2;
+
+ reply_token = strrchr(reply, ' ');
+
+ if (reply_token == NULL) {
+ DBG_ERR("clamd: zSCAN: Invalid reply: %s\n",
+ reply);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ goto virusfilter_clamav_scan_return;
+ }
+ *reply_token = '\0';
+ reply_token++;
+
+ if (strcmp(reply_token, "OK") == 0) {
+
+ /* <FILEPATH>: OK */
+ result = VIRUSFILTER_RESULT_CLEAN;
+ report = talloc_asprintf(talloc_tos(), "Clean");
+ } else if (strcmp(reply_token, "FOUND") == 0) {
+
+ /* <FILEPATH>: <REPORT> FOUND */
+ result = VIRUSFILTER_RESULT_INFECTED;
+ report = talloc_strdup(talloc_tos(), reply_msg);
+ } else if (strcmp(reply_token, "ERROR") == 0) {
+
+ /* <FILEPATH>: <REPORT> ERROR */
+ DBG_ERR("clamd: zSCAN: Error: %s\n", reply_msg);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s\t", reply_msg);
+ } else {
+ DBG_ERR("clamd: zSCAN: Invalid reply: %s\n", reply_token);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ }
+
+virusfilter_clamav_scan_return:
+ TALLOC_FREE(reply);
+ if (report == NULL) {
+ *reportp = talloc_asprintf(talloc_tos(),
+ "Scanner report memory error");
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_clamav = {
+ .connect = virusfilter_clamav_connect,
+ .disconnect = NULL,
+ .scan_init = virusfilter_clamav_scan_init,
+ .scan = virusfilter_clamav_scan,
+ .scan_end = virusfilter_clamav_scan_end,
+};
+
+int virusfilter_clamav_init(struct virusfilter_config *config)
+{
+ struct virusfilter_backend *backend = NULL;
+
+ if (config->socket_path == NULL) {
+ config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+ }
+
+ backend = talloc_zero(config, struct virusfilter_backend);
+ if (backend == NULL) {
+ return -1;
+ }
+
+ backend->fns = &virusfilter_backend_clamav;
+ backend->name = "clamav";
+
+ config->backend = backend;
+ return 0;
+}
diff --git a/source3/modules/vfs_virusfilter_common.h b/source3/modules/vfs_virusfilter_common.h
new file mode 100644
index 0000000..24359bf
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_common.h
@@ -0,0 +1,154 @@
+/*
+ Samba-VirusFilter VFS modules
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_COMMON_H
+#define _VIRUSFILTER_COMMON_H
+
+#include <stdint.h>
+#include <time.h>
+
+/* Samba common include file */
+#include "includes.h"
+
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "system/filesys.h"
+#include "transfer_file.h"
+#include "auth.h"
+#include "passdb.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../lib/tsocket/tsocket.h"
+
+/* Samba debug class for VIRUSFILTER */
+#undef DBGC_CLASS
+#define DBGC_CLASS virusfilter_debug_class
+extern int virusfilter_debug_class;
+
+#define VIRUSFILTER_VERSION "0.1.5"
+
+/* ====================================================================== */
+
+typedef enum {
+ VIRUSFILTER_ACTION_DO_NOTHING,
+ VIRUSFILTER_ACTION_QUARANTINE,
+ VIRUSFILTER_ACTION_RENAME,
+ VIRUSFILTER_ACTION_DELETE,
+} virusfilter_action;
+
+typedef enum {
+ VIRUSFILTER_RESULT_OK,
+ VIRUSFILTER_RESULT_CLEAN,
+ VIRUSFILTER_RESULT_ERROR,
+ VIRUSFILTER_RESULT_INFECTED,
+ VIRUSFILTER_RESULT_SUSPECTED,
+ /* FIXME: VIRUSFILTER_RESULT_RISKWARE, */
+} virusfilter_result;
+
+struct virusfilter_config {
+ int scan_request_count;
+ int scan_request_limit;
+
+ /* Scan on file operations */
+ bool scan_on_open;
+ bool scan_on_close;
+
+ /* Special scan options */
+ bool scan_archive;
+ int max_nested_scan_archive;
+ bool scan_mime;
+ bool block_suspected_file;
+
+ /* Size limit */
+ size_t max_file_size;
+ size_t min_file_size;
+
+ /* Exclude files */
+ name_compare_entry *exclude_files;
+
+ /* Infected files */
+ name_compare_entry *infected_files;
+
+ /* Scan result cache */
+ struct virusfilter_cache *cache;
+ int cache_entry_limit;
+ int cache_time_limit;
+
+ /* Infected file options */
+ virusfilter_action infected_file_action;
+ const char * infected_file_command;
+ int infected_open_errno;
+ int infected_close_errno;
+
+ /* Scan error options */
+ const char * scan_error_command;
+ int scan_error_open_errno;
+ int scan_error_close_errno;
+ bool block_access_on_error;
+
+ /* Quarantine infected files */
+ const char * quarantine_dir;
+ const char * quarantine_prefix;
+ const char * quarantine_suffix;
+ bool quarantine_keep_tree;
+ bool quarantine_keep_name;
+ mode_t quarantine_dir_mode;
+
+ /* Rename infected files */
+ const char * rename_prefix;
+ const char * rename_suffix;
+
+ /* Network options */
+ const char * socket_path;
+ struct virusfilter_io_handle *io_h;
+
+ /* The backend AV engine */
+ struct virusfilter_backend *backend;
+};
+
+struct virusfilter_backend_fns {
+ int (*connect)(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user);
+ void (*disconnect)(
+ struct vfs_handle_struct *handle);
+ virusfilter_result (*scan_init)(
+ struct virusfilter_config *config);
+ virusfilter_result (*scan)(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp);
+ void (*scan_end)(
+ struct virusfilter_config *config);
+};
+
+struct virusfilter_backend {
+ unsigned version;
+ const char *name;
+ const struct virusfilter_backend_fns *fns;
+ void *backend_private;
+};
+
+int virusfilter_sophos_init(struct virusfilter_config *config);
+int virusfilter_fsav_init(struct virusfilter_config *config);
+int virusfilter_clamav_init(struct virusfilter_config *config);
+int virusfilter_dummy_init(struct virusfilter_config *config);
+
+#endif /* _VIRUSFILTER_COMMON_H */
diff --git a/source3/modules/vfs_virusfilter_dummy.c b/source3/modules/vfs_virusfilter_dummy.c
new file mode 100644
index 0000000..03405cd
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_dummy.c
@@ -0,0 +1,58 @@
+/*
+ Samba-VirusFilter VFS modules
+ Dummy scanner with infected files support.
+ Copyright (C) 2022 Pavel Filipenský <pfilipen@redhat.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modules/vfs_virusfilter_utils.h"
+
+static virusfilter_result virusfilter_dummy_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp)
+{
+ bool ok;
+
+ DBG_INFO("Scanning file: %s\n", fsp_str_dbg(fsp));
+ ok = is_in_path(fsp->fsp_name->base_name,
+ config->infected_files,
+ false);
+ return ok ? VIRUSFILTER_RESULT_INFECTED : VIRUSFILTER_RESULT_CLEAN;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_dummy = {
+ .connect = NULL,
+ .disconnect = NULL,
+ .scan_init = NULL,
+ .scan = virusfilter_dummy_scan,
+ .scan_end = NULL,
+};
+
+int virusfilter_dummy_init(struct virusfilter_config *config)
+{
+ struct virusfilter_backend *backend = NULL;
+
+ backend = talloc_zero(config, struct virusfilter_backend);
+ if (backend == NULL) {
+ return -1;
+ }
+
+ backend->fns = &virusfilter_backend_dummy;
+ backend->name = "dummy";
+ config->backend = backend;
+ return 0;
+}
diff --git a/source3/modules/vfs_virusfilter_fsav.c b/source3/modules/vfs_virusfilter_fsav.c
new file mode 100644
index 0000000..25a2906
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_fsav.c
@@ -0,0 +1,451 @@
+/*
+ Samba-VirusFilter VFS modules
+ F-Secure Anti-Virus fsavd support
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+#ifdef FSAV_DEFAULT_SOCKET_PATH
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH FSAV_DEFAULT_SOCKET_PATH
+#else
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/tmp/.fsav-0"
+#endif
+
+/* Default values for module-specific configuration variables */
+/* 5 = F-Secure Linux 7 or later? */
+
+#define VIRUSFILTER_DEFAULT_FSAV_PROTOCOL 5
+#define VIRUSFILTER_DEFAULT_SCAN_RISKWARE false
+#define VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST true
+#define VIRUSFILTER_DEFAULT_FILTER_FILENAME false
+
+struct virusfilter_fsav_config {
+ /* Backpointer */
+ struct virusfilter_config *config;
+
+ int fsav_protocol;
+ bool scan_riskware;
+ bool stop_scan_on_first;
+ bool filter_filename;
+};
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config);
+
+static int virusfilter_fsav_destruct_config(
+ struct virusfilter_fsav_config *fsav_config)
+{
+ virusfilter_fsav_scan_end(fsav_config->config);
+ return 0;
+}
+
+static int virusfilter_fsav_connect(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user)
+{
+ int snum = SNUM(handle->conn);
+ struct virusfilter_fsav_config *fsav_config = NULL;
+
+ fsav_config = talloc_zero(config->backend,
+ struct virusfilter_fsav_config);
+ if (fsav_config == NULL) {
+ return -1;
+ }
+
+ fsav_config->config = config;
+
+ fsav_config->fsav_protocol = lp_parm_int(
+ snum, "virusfilter", "fsav protocol",
+ VIRUSFILTER_DEFAULT_FSAV_PROTOCOL);
+
+ fsav_config->scan_riskware = lp_parm_bool(
+ snum, "virusfilter", "scan riskware",
+ VIRUSFILTER_DEFAULT_SCAN_RISKWARE);
+
+ fsav_config->stop_scan_on_first = lp_parm_bool(
+ snum, "virusfilter", "stop scan on first",
+ VIRUSFILTER_DEFAULT_STOP_SCAN_ON_FIRST);
+
+ fsav_config->filter_filename = lp_parm_bool(
+ snum, "virusfilter", "filter filename",
+ VIRUSFILTER_DEFAULT_FILTER_FILENAME);
+
+ talloc_set_destructor(fsav_config, virusfilter_fsav_destruct_config);
+
+ config->backend->backend_private = fsav_config;
+
+ config->block_suspected_file = lp_parm_bool(
+ snum, "virusfilter", "block suspected file", false);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_fsav_scan_init(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_fsav_config *fsav_config = NULL;
+ struct virusfilter_io_handle *io_h = config->io_h;
+ char *reply = NULL;
+ bool ok;
+ int ret;
+
+ fsav_config = talloc_get_type_abort(config->backend->backend_private,
+ struct virusfilter_fsav_config);
+
+ if (io_h->stream != NULL) {
+ DBG_DEBUG("fsavd: Checking if connection is alive\n");
+
+ /* FIXME: I don't know the correct PING command format... */
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "PING");
+ if (ok) {
+ ret = strncmp(reply, "ERROR\t", 6);
+ if (ret == 0) {
+ DBG_DEBUG("fsavd: Re-using existent "
+ "connection\n");
+ goto virusfilter_fsav_init_succeed;
+ }
+ }
+
+ DBG_DEBUG("fsavd: Closing dead connection\n");
+ virusfilter_fsav_scan_end(config);
+ }
+
+ DBG_INFO("fsavd: Connecting to socket: %s\n",
+ config->socket_path);
+
+ become_root();
+ ok = virusfilter_io_connect_path(io_h, config->socket_path);
+ unbecome_root();
+
+ if (!ok) {
+ DBG_ERR("fsavd: Connecting to socket failed: %s: %s\n",
+ config->socket_path, strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("fsavd: Reading greeting message failed: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "DBVERSION\t", 10);
+ if (ret != 0) {
+ DBG_ERR("fsavd: Invalid greeting message: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ DBG_DEBUG("fsavd: Connected\n");
+
+ DBG_INFO("fsavd: Configuring\n");
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "PROTOCOL\t%d",
+ fsav_config->fsav_protocol);
+ if (!ok) {
+ DBG_ERR("fsavd: PROTOCOL: I/O error: %s\n", strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: PROTOCOL: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tSTOPONFIRST\t%d",
+ fsav_config->stop_scan_on_first ?
+ 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE STOPONFIRST: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE STOPONFIRST: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tFILTER\t%d",
+ fsav_config->filter_filename ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE FILTER: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE FILTER: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tARCHIVE\t%d",
+ config->scan_archive ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE ARCHIVE: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE ARCHIVE: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tMAXARCH\t%d",
+ config->max_nested_scan_archive);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE MAXARCH: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE MAXARCH: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "CONFIGURE\tMIME\t%d",
+ config->scan_mime ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE MIME: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE MIME: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply, "CONFIGURE\tRISKWARE\t%d",
+ fsav_config->scan_riskware ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("fsavd: CONFIGURE RISKWARE: I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_init_failed;
+ }
+ ret = strncmp(reply, "OK\t", 3);
+ if (ret != 0) {
+ DBG_ERR("fsavd: CONFIGURE RISKWARE: Not accepted: %s\n",
+ reply);
+ goto virusfilter_fsav_init_failed;
+ }
+
+ DBG_DEBUG("fsavd: Configured\n");
+
+virusfilter_fsav_init_succeed:
+ TALLOC_FREE(reply);
+ return VIRUSFILTER_RESULT_OK;
+
+virusfilter_fsav_init_failed:
+ TALLOC_FREE(reply);
+ virusfilter_fsav_scan_end(config);
+
+ return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_fsav_scan_end(struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+
+ DBG_INFO("fsavd: Disconnecting\n");
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_fsav_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp)
+{
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ struct virusfilter_io_handle *io_h = config->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_CLEAN;
+ char *report = NULL;
+ char *reply = NULL;
+ char *reply_token = NULL, *reply_saveptr = NULL;
+ bool ok;
+
+ DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+ ok = virusfilter_io_writevl(io_h, "SCAN\t", 5, cwd_fname,
+ (int)strlen(cwd_fname), "/", 1, fname,
+ (int)strlen(fname), NULL);
+ if (!ok) {
+ DBG_ERR("fsavd: SCAN: Write error: %s\n", strerror(errno));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n",
+ strerror(errno));
+ goto virusfilter_fsav_scan_return;
+ }
+
+ TALLOC_FREE(reply);
+
+ for (;;) {
+ if (virusfilter_io_readl(talloc_tos(), io_h, &reply) != true) {
+ DBG_ERR("fsavd: SCANFILE: Read error: %s\n",
+ strerror(errno));
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n",
+ strerror(errno));
+ break;
+ }
+
+ reply_token = strtok_r(reply, "\t", &reply_saveptr);
+
+ if (strcmp(reply_token, "OK") == 0) {
+ break;
+ } else if (strcmp(reply_token, "CLEAN") == 0) {
+
+ /* CLEAN\t<FILEPATH> */
+ result = VIRUSFILTER_RESULT_CLEAN;
+ report = talloc_asprintf(talloc_tos(), "Clean");
+ } else if (strcmp(reply_token, "INFECTED") == 0 ||
+ strcmp(reply_token, "ARCHIVE_INFECTED") == 0 ||
+ strcmp(reply_token, "MIME_INFECTED") == 0 ||
+ strcmp(reply_token, "RISKWARE") == 0 ||
+ strcmp(reply_token, "ARCHIVE_RISKWARE") == 0 ||
+ strcmp(reply_token, "MIME_RISKWARE") == 0)
+ {
+
+ /* INFECTED\t<FILEPATH>\t<REPORT>\t<ENGINE> */
+ result = VIRUSFILTER_RESULT_INFECTED;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ if (reply_token != NULL) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = talloc_asprintf(talloc_tos(),
+ "UNKNOWN INFECTION");
+ }
+ } else if (strcmp(reply_token, "OPEN_ARCHIVE") == 0) {
+
+ /* Ignore */
+ } else if (strcmp(reply_token, "CLOSE_ARCHIVE") == 0) {
+
+ /* Ignore */
+ } else if ((strcmp(reply_token, "SUSPECTED") == 0 ||
+ strcmp(reply_token, "ARCHIVE_SUSPECTED") == 0 ||
+ strcmp(reply_token, "MIME_SUSPECTED") == 0) &&
+ config->block_suspected_file)
+ {
+ result = VIRUSFILTER_RESULT_SUSPECTED;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ if (reply_token != NULL) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = talloc_asprintf(talloc_tos(),
+ "UNKNOWN REASON SUSPECTED");
+ }
+ } else if (strcmp(reply_token, "SCAN_FAILURE") == 0) {
+
+ /* SCAN_FAILURE\t<FILEPATH>\t0x<CODE>\t<REPORT> [<ENGINE>] */
+ result = VIRUSFILTER_RESULT_ERROR;
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ reply_token = strtok_r(NULL, "\t", &reply_saveptr);
+ DBG_ERR("fsavd: SCANFILE: Scaner error: %s\n",
+ reply_token ? reply_token : "UNKNOWN ERROR");
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s",
+ reply_token ? reply_token :
+ "UNKNOWN ERROR");
+ } else {
+ result = VIRUSFILTER_RESULT_ERROR;
+ DBG_ERR("fsavd: SCANFILE: Invalid reply: %s\n",
+ reply_token);
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner communication error");
+ }
+
+ TALLOC_FREE(reply);
+ }
+
+virusfilter_fsav_scan_return:
+ TALLOC_FREE(reply);
+
+ if (report == NULL) {
+ *reportp = talloc_asprintf(talloc_tos(), "Scanner report memory "
+ "error");
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_fsav ={
+ .connect = virusfilter_fsav_connect,
+ .disconnect = NULL,
+ .scan_init = virusfilter_fsav_scan_init,
+ .scan = virusfilter_fsav_scan,
+ .scan_end = virusfilter_fsav_scan_end,
+};
+
+int virusfilter_fsav_init(struct virusfilter_config *config)
+{
+ struct virusfilter_backend *backend = NULL;
+
+ if (config->socket_path == NULL) {
+ config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+ }
+
+ backend = talloc_zero(config, struct virusfilter_backend);
+ if (backend == NULL) {
+ return -1;
+ }
+
+ backend->fns = &virusfilter_backend_fsav;
+ backend->name = "fsav";
+
+ config->backend = backend;
+ return 0;
+}
diff --git a/source3/modules/vfs_virusfilter_sophos.c b/source3/modules/vfs_virusfilter_sophos.c
new file mode 100644
index 0000000..c8cdec5
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_sophos.c
@@ -0,0 +1,391 @@
+/*
+ Samba-VirusFilter VFS modules
+ Sophos Anti-Virus savdid (SSSP/1.0) support
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "vfs_virusfilter_common.h"
+#include "vfs_virusfilter_utils.h"
+
+/* Default values for standard "extra" configuration variables */
+#ifdef SOPHOS_DEFAULT_SOCKET_PATH
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH SOPHOS_DEFAULT_SOCKET_PATH
+#else
+# define VIRUSFILTER_DEFAULT_SOCKET_PATH "/var/run/savdi/sssp.sock"
+#endif
+
+static void virusfilter_sophos_scan_end(struct virusfilter_config *config);
+
+/* Python's urllib.quote(string[, safe]) clone */
+static int virusfilter_url_quote(const char *src, char *dst, int dst_size)
+{
+ char *dst_c = dst;
+ static char hex[] = "0123456789ABCDEF";
+
+ for (; *src != '\0'; src++) {
+ if ((*src < '0' && *src != '-' && *src != '.' && *src != '/') ||
+ (*src > '9' && *src < 'A') ||
+ (*src > 'Z' && *src < 'a' && *src != '_') ||
+ (*src > 'z'))
+ {
+ if (dst_size < 4) {
+ return -1;
+ }
+ *dst_c++ = '%';
+ *dst_c++ = hex[(*src >> 4) & 0x0F];
+ *dst_c++ = hex[*src & 0x0F];
+ dst_size -= 3;
+ } else {
+ if (dst_size < 2) {
+ return -1;
+ }
+ *dst_c++ = *src;
+ dst_size--;
+ }
+ }
+
+ *dst_c = '\0';
+
+ return (dst_c - dst);
+}
+
+static int virusfilter_sophos_connect(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const char *svc,
+ const char *user)
+{
+ virusfilter_io_set_readl_eol(config->io_h, "\x0D\x0A", 2);
+
+ return 0;
+}
+
+static virusfilter_result virusfilter_sophos_scan_ping(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+ char *reply = NULL;
+ bool ok;
+ int ret;
+
+ /* SSSP/1.0 has no "PING" command */
+ ok = virusfilter_io_writel(io_h, "SSSP/1.0 OPTIONS\n", 17);
+ if (!ok) {
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ for (;;) {
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+ ret = strcmp(reply, "");
+ if (ret == 0) {
+ break;
+ }
+ TALLOC_FREE(reply);
+ }
+
+ TALLOC_FREE(reply);
+ return VIRUSFILTER_RESULT_OK;
+}
+
+static virusfilter_result virusfilter_sophos_scan_init(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+ char *reply = NULL;
+ int ret;
+ bool ok;
+
+ if (io_h->stream != NULL) {
+ DBG_DEBUG("SSSP: Checking if connection is alive\n");
+
+ ret = virusfilter_sophos_scan_ping(config);
+ if (ret == VIRUSFILTER_RESULT_OK)
+ {
+ DBG_DEBUG("SSSP: Re-using existent connection\n");
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ DBG_INFO("SSSP: Closing dead connection\n");
+ virusfilter_sophos_scan_end(config);
+ }
+
+
+ DBG_INFO("SSSP: Connecting to socket: %s\n",
+ config->socket_path);
+
+ become_root();
+ ok = virusfilter_io_connect_path(io_h, config->socket_path);
+ unbecome_root();
+
+ if (!ok) {
+ DBG_ERR("SSSP: Connecting to socket failed: %s: %s\n",
+ config->socket_path, strerror(errno));
+ return VIRUSFILTER_RESULT_ERROR;
+ }
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: Reading greeting message failed: %s\n",
+ strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strncmp(reply, "OK SSSP/1.0", 11);
+ if (ret != 0) {
+ DBG_ERR("SSSP: Invalid greeting message: %s\n",
+ reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ DBG_DEBUG("SSSP: Connected\n");
+
+ DBG_INFO("SSSP: Configuring\n");
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_writefl_readl(io_h, &reply,
+ "SSSP/1.0 OPTIONS\noutput:brief\nsavigrp:GrpArchiveUnpack %d\n",
+ config->scan_archive ? 1 : 0);
+ if (!ok) {
+ DBG_ERR("SSSP: OPTIONS: I/O error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strncmp(reply, "ACC ", 4);
+ if (ret != 0) {
+ DBG_ERR("SSSP: OPTIONS: Not accepted: %s\n", reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strncmp(reply, "DONE OK ", 8);
+ if (ret != 0) {
+ DBG_ERR("SSSP: OPTIONS failed: %s\n", reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ TALLOC_FREE(reply);
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: OPTIONS: Read error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_init_failed;
+ }
+ ret = strcmp(reply, "");
+ if (ret != 0) {
+ DBG_ERR("SSSP: OPTIONS: Invalid reply: %s\n", reply);
+ goto virusfilter_sophos_scan_init_failed;
+ }
+
+ DBG_DEBUG("SSSP: Configured\n");
+
+ return VIRUSFILTER_RESULT_OK;
+
+virusfilter_sophos_scan_init_failed:
+
+ TALLOC_FREE(reply);
+
+ virusfilter_sophos_scan_end(config);
+
+ return VIRUSFILTER_RESULT_ERROR;
+}
+
+static void virusfilter_sophos_scan_end(
+ struct virusfilter_config *config)
+{
+ struct virusfilter_io_handle *io_h = config->io_h;
+
+ DBG_INFO("SSSP: Disconnecting\n");
+
+ virusfilter_io_disconnect(io_h);
+}
+
+static virusfilter_result virusfilter_sophos_scan(
+ struct vfs_handle_struct *handle,
+ struct virusfilter_config *config,
+ const struct files_struct *fsp,
+ char **reportp)
+{
+ char *cwd_fname = fsp->conn->cwd_fsp->fsp_name->base_name;
+ const char *fname = fsp->fsp_name->base_name;
+ char fileurl[VIRUSFILTER_IO_URL_MAX+1];
+ int fileurl_len, fileurl_len2;
+ struct virusfilter_io_handle *io_h = config->io_h;
+ virusfilter_result result = VIRUSFILTER_RESULT_ERROR;
+ char *report = NULL;
+ char *reply = NULL;
+ char *reply_token = NULL, *reply_saveptr = NULL;
+ int ret;
+ bool ok;
+
+ DBG_INFO("Scanning file: %s/%s\n", cwd_fname, fname);
+
+ fileurl_len = virusfilter_url_quote(cwd_fname, fileurl,
+ VIRUSFILTER_IO_URL_MAX);
+ if (fileurl_len < 0) {
+ DBG_ERR("virusfilter_url_quote failed: File path too long: "
+ "%s/%s\n", cwd_fname, fname);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(), "File path too long");
+ goto virusfilter_sophos_scan_return;
+ }
+ fileurl[fileurl_len] = '/';
+ fileurl_len++;
+
+ fileurl_len += fileurl_len2 = virusfilter_url_quote(fname,
+ fileurl + fileurl_len, VIRUSFILTER_IO_URL_MAX - fileurl_len);
+ if (fileurl_len2 < 0) {
+ DBG_ERR("virusfilter_url_quote failed: File path too long: "
+ "%s/%s\n", cwd_fname, fname);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(), "File path too long");
+ goto virusfilter_sophos_scan_return;
+ }
+ fileurl_len += fileurl_len2;
+
+ ok = virusfilter_io_writevl(io_h, "SSSP/1.0 SCANFILE ", 18, fileurl,
+ fileurl_len, NULL);
+ if (!ok) {
+ DBG_ERR("SSSP: SCANFILE: Write error: %s\n",
+ strerror(errno));
+ goto virusfilter_sophos_scan_io_error;
+ }
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: SCANFILE: Read error: %s\n", strerror(errno));
+ goto virusfilter_sophos_scan_io_error;
+ }
+ ret = strncmp(reply, "ACC ", 4);
+ if (ret != 0) {
+ DBG_ERR("SSSP: SCANFILE: Not accepted: %s\n",
+ reply);
+ result = VIRUSFILTER_RESULT_ERROR;
+ goto virusfilter_sophos_scan_return;
+ }
+
+ TALLOC_FREE(reply);
+
+ result = VIRUSFILTER_RESULT_CLEAN;
+ for (;;) {
+ ok = virusfilter_io_readl(talloc_tos(), io_h, &reply);
+ if (!ok) {
+ DBG_ERR("SSSP: SCANFILE: Read error: %s\n",
+ strerror(errno));
+ goto virusfilter_sophos_scan_io_error;
+ }
+
+ ret = strcmp(reply, "");
+ if (ret == 0) {
+ break;
+ }
+
+ reply_token = strtok_r(reply, " ", &reply_saveptr);
+
+ if (strcmp(reply_token, "VIRUS") == 0) {
+ result = VIRUSFILTER_RESULT_INFECTED;
+ reply_token = strtok_r(NULL, " ", &reply_saveptr);
+ if (reply_token != NULL) {
+ report = talloc_strdup(talloc_tos(),
+ reply_token);
+ } else {
+ report = talloc_asprintf(talloc_tos(),
+ "UNKNOWN INFECTION");
+ }
+ } else if (strcmp(reply_token, "OK") == 0) {
+
+ /* Ignore */
+ } else if (strcmp(reply_token, "DONE") == 0) {
+ reply_token = strtok_r(NULL, "", &reply_saveptr);
+ if (reply_token != NULL &&
+
+ /* Succeed */
+ strncmp(reply_token, "OK 0000 ", 8) != 0 &&
+
+ /* Infected */
+ strncmp(reply_token, "OK 0203 ", 8) != 0)
+ {
+ DBG_ERR("SSSP: SCANFILE: Error: %s\n",
+ reply_token);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(),
+ "Scanner error: %s\n",
+ reply_token);
+ }
+ } else {
+ DBG_ERR("SSSP: SCANFILE: Invalid reply: %s\n",
+ reply_token);
+ result = VIRUSFILTER_RESULT_ERROR;
+ report = talloc_asprintf(talloc_tos(), "Scanner "
+ "communication error");
+ }
+
+ TALLOC_FREE(reply);
+ }
+
+virusfilter_sophos_scan_return:
+ TALLOC_FREE(reply);
+
+ if (report == NULL) {
+ *reportp = talloc_asprintf(talloc_tos(),
+ "Scanner report memory error");
+ } else {
+ *reportp = report;
+ }
+
+ return result;
+
+virusfilter_sophos_scan_io_error:
+ *reportp = talloc_asprintf(talloc_tos(),
+ "Scanner I/O error: %s\n", strerror(errno));
+
+ return result;
+}
+
+static struct virusfilter_backend_fns virusfilter_backend_sophos ={
+ .connect = virusfilter_sophos_connect,
+ .disconnect = NULL,
+ .scan_init = virusfilter_sophos_scan_init,
+ .scan = virusfilter_sophos_scan,
+ .scan_end = virusfilter_sophos_scan_end,
+};
+
+int virusfilter_sophos_init(struct virusfilter_config *config)
+{
+ struct virusfilter_backend *backend = NULL;
+
+ if (config->socket_path == NULL) {
+ config->socket_path = VIRUSFILTER_DEFAULT_SOCKET_PATH;
+ }
+
+ backend = talloc_zero(config, struct virusfilter_backend);
+ if (backend == NULL) {
+ return -1;
+ }
+
+ backend->fns = &virusfilter_backend_sophos;
+ backend->name = "sophos";
+
+ config->backend = backend;
+ return 0;
+}
diff --git a/source3/modules/vfs_virusfilter_utils.c b/source3/modules/vfs_virusfilter_utils.c
new file mode 100644
index 0000000..b467779
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.c
@@ -0,0 +1,1036 @@
+/*
+ Samba-VirusFilter VFS modules
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+ Copyright (C) 2016-2017 Trever L. Adams
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modules/vfs_virusfilter_common.h"
+#include "modules/vfs_virusfilter_utils.h"
+
+struct iovec;
+
+#include "lib/util/iov_buf.h"
+#include <tevent.h>
+#include "lib/tsocket/tsocket.h"
+#include "source3/lib/substitute.h"
+
+int virusfilter_debug_class = DBGC_VFS;
+
+/* ====================================================================== */
+
+char *virusfilter_string_sub(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *str)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ return talloc_sub_full(mem_ctx,
+ lp_servicename(mem_ctx, lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ str);
+}
+
+int virusfilter_vfs_next_move(
+ struct vfs_handle_struct *vfs_h,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ int result;
+
+ result = SMB_VFS_NEXT_RENAMEAT(vfs_h,
+ vfs_h->conn->cwd_fsp,
+ smb_fname_src,
+ vfs_h->conn->cwd_fsp,
+ smb_fname_dst);
+ if (result == 0 || errno != EXDEV) {
+ return result;
+ }
+
+ /*
+ * For now, do not handle EXDEV as poking around violates
+ * stackability. Return -1, simply refuse access.
+ */
+ return -1;
+}
+
+/* Line-based socket I/O
+ * ======================================================================
+ */
+
+struct virusfilter_io_handle *virusfilter_io_new(
+ TALLOC_CTX *mem_ctx,
+ int connect_timeout,
+ int io_timeout)
+{
+ struct virusfilter_io_handle *io_h = talloc_zero(mem_ctx,
+ struct virusfilter_io_handle);
+
+ if (io_h == NULL) {
+ return NULL;
+ }
+
+ io_h->stream = NULL;
+ io_h->r_len = 0;
+
+ virusfilter_io_set_connect_timeout(io_h, connect_timeout);
+ virusfilter_io_set_io_timeout(io_h, io_timeout);
+ virusfilter_io_set_writel_eol(io_h, "\x0A", 1);
+ virusfilter_io_set_readl_eol(io_h, "\x0A", 1);
+
+ return io_h;
+}
+
+int virusfilter_io_set_connect_timeout(
+ struct virusfilter_io_handle *io_h,
+ int timeout)
+{
+ int timeout_old = io_h->connect_timeout;
+
+ /* timeout <= 0 means infinite */
+ io_h->connect_timeout = (timeout > 0) ? timeout : -1;
+
+ return timeout_old;
+}
+
+int virusfilter_io_set_io_timeout(
+ struct virusfilter_io_handle *io_h,
+ int timeout)
+{
+ int timeout_old = io_h->io_timeout;
+
+ /* timeout <= 0 means infinite */
+ io_h->io_timeout = (timeout > 0) ? timeout : -1;
+
+ return timeout_old;
+}
+
+void virusfilter_io_set_writel_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size)
+{
+ if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
+ return;
+ }
+
+ memcpy(io_h->w_eol, eol, eol_size);
+ io_h->w_eol_size = eol_size;
+}
+
+void virusfilter_io_set_readl_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size)
+{
+ if (eol_size < 1 || eol_size > VIRUSFILTER_IO_EOL_SIZE) {
+ return;
+ }
+
+ memcpy(io_h->r_eol, eol, eol_size);
+ io_h->r_eol_size = eol_size;
+}
+
+bool virusfilter_io_connect_path(
+ struct virusfilter_io_handle *io_h,
+ const char *path)
+{
+ struct sockaddr_un addr;
+ NTSTATUS status;
+ int socket, ret;
+ size_t len;
+ bool ok;
+
+ ZERO_STRUCT(addr);
+ addr.sun_family = AF_UNIX;
+
+ len = strlcpy(addr.sun_path, path, sizeof(addr.sun_path));
+ if (len >= sizeof(addr.sun_path)) {
+ io_h->stream = NULL;
+ return false;
+ }
+
+ status = open_socket_out((struct sockaddr_storage *)&addr, 0,
+ io_h->connect_timeout,
+ &socket);
+ if (!NT_STATUS_IS_OK(status)) {
+ io_h->stream = NULL;
+ return false;
+ }
+
+ /* We must not block */
+ ret = set_blocking(socket, false);
+ if (ret == -1) {
+ close(socket);
+ io_h->stream = NULL;
+ return false;
+ }
+
+ ok = smb_set_close_on_exec(socket);
+ if (!ok) {
+ close(socket);
+ io_h->stream = NULL;
+ return false;
+ }
+
+ ret = tstream_bsd_existing_socket(io_h, socket, &io_h->stream);
+ if (ret == -1) {
+ close(socket);
+ DBG_ERR("Could not convert socket to tstream: %s.\n",
+ strerror(errno));
+ io_h->stream = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+static void disconnect_done(struct tevent_req *req)
+{
+ uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+ int ret;
+ int err_ret;
+
+ ret = tstream_disconnect_recv(req, &err_ret);
+ TALLOC_FREE(req);
+ if (ret == -1) {
+ *perr = err_ret;
+ }
+}
+
+bool virusfilter_io_disconnect(
+ struct virusfilter_io_handle *io_h)
+{
+ struct tevent_req *req;
+ struct tevent_context *ev;
+ uint64_t *perror = NULL;
+ bool ok = true;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (io_h->stream == NULL) {
+ io_h->r_len = 0;
+ TALLOC_FREE(frame);
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ DBG_ERR("Failed to setup event context.\n");
+ ok = false;
+ goto fail;
+ }
+
+ /* Error return - must be talloc'ed. */
+ perror = talloc_zero(frame, uint64_t);
+ if (perror == NULL) {
+ goto fail;
+ }
+
+ req = tstream_disconnect_send(io_h, ev, io_h->stream);
+
+ /* Callback when disconnect is done. */
+ tevent_req_set_callback(req, disconnect_done, perror);
+
+ /* Set timeout. */
+ ok = tevent_req_set_endtime(req, ev, timeval_current_ofs_msec(
+ io_h->connect_timeout));
+ if (!ok) {
+ DBG_ERR("Can't set endtime\n");
+ goto fail;
+ }
+
+ /* Loop waiting for req to finish. */
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_ERR("tevent_req_poll failed\n");
+ goto fail;
+ }
+
+ /* Emit debug error if failed. */
+ if (*perror != 0) {
+ DBG_DEBUG("Error %s\n", strerror((int)*perror));
+ goto fail;
+ }
+
+ /* Here we know we disconnected. */
+
+ io_h->stream = NULL;
+ io_h->r_len = 0;
+
+ fail:
+ TALLOC_FREE(frame);
+ return ok;
+}
+
+static void writev_done(struct tevent_req *req)
+{
+ uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+ int ret;
+ int err_ret;
+
+ ret = tstream_writev_recv(req, &err_ret);
+ TALLOC_FREE(req);
+ if (ret == -1) {
+ *perr = err_ret;
+ }
+}
+
+/****************************************************************************
+ Write all data from an iov array, with msec timeout (per write)
+ NB. This can be called with a non-socket fd, don't add dependencies
+ on socket calls.
+****************************************************************************/
+
+bool write_data_iov_timeout(
+ struct tstream_context *stream,
+ const struct iovec *iov,
+ size_t iovcnt,
+ int ms_timeout)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ uint64_t *perror = NULL;
+ bool ok = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ DBG_ERR("Failed to setup event context.\n");
+ goto fail;
+ }
+
+ /* Error return - must be talloc'ed. */
+ perror = talloc_zero(frame, uint64_t);
+ if (perror == NULL) {
+ goto fail;
+ }
+
+ /* Send the data. */
+ req = tstream_writev_send(frame, ev, stream, iov, iovcnt);
+ if (req == NULL) {
+ DBG_ERR("Out of memory.\n");
+ goto fail;
+ }
+
+ /* Callback when *all* data sent. */
+ tevent_req_set_callback(req, writev_done, perror);
+
+ /* Set timeout. */
+ ok = tevent_req_set_endtime(req, ev,
+ timeval_current_ofs_msec(ms_timeout));
+ if (!ok) {
+ DBG_ERR("Can't set endtime\n");
+ goto fail;
+ }
+
+ /* Loop waiting for req to finish. */
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_ERR("tevent_req_poll failed\n");
+ goto fail;
+ }
+
+ /* Done with req - freed by the callback. */
+ req = NULL;
+
+ /* Emit debug error if failed. */
+ if (*perror != 0) {
+ DBG_DEBUG("Error %s\n", strerror((int)*perror));
+ goto fail;
+ }
+
+ /* Here we know we correctly wrote all data. */
+ TALLOC_FREE(frame);
+ return true;
+
+ fail:
+ TALLOC_FREE(frame);
+ return false;
+}
+
+bool virusfilter_io_write(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size)
+{
+ struct iovec iov;
+
+ if (data_size == 0) {
+ return VIRUSFILTER_RESULT_OK;
+ }
+
+ iov.iov_base = discard_const_p(void, data);
+ iov.iov_len = data_size;
+
+ return write_data_iov_timeout(io_h->stream, &iov, 1, io_h->io_timeout);
+}
+
+bool virusfilter_io_writel(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size)
+{
+ bool ok;
+
+ ok = virusfilter_io_write(io_h, data, data_size);
+ if (!ok) {
+ return ok;
+ }
+
+ return virusfilter_io_write(io_h, io_h->w_eol, io_h->w_eol_size);
+}
+
+bool PRINTF_ATTRIBUTE(2, 3) virusfilter_io_writefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, ...)
+{
+ va_list ap;
+ char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
+ int data_size;
+
+ va_start(ap, data_fmt);
+ data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
+ va_end(ap);
+
+ if (unlikely (data_size < 0)) {
+ DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
+ data_size += io_h->w_eol_size;
+
+ return virusfilter_io_write(io_h, data, data_size);
+}
+
+bool PRINTF_ATTRIBUTE(2, 0) virusfilter_io_vwritefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, va_list ap)
+{
+ char data[VIRUSFILTER_IO_BUFFER_SIZE + VIRUSFILTER_IO_EOL_SIZE];
+ int data_size;
+
+ data_size = vsnprintf(data, VIRUSFILTER_IO_BUFFER_SIZE, data_fmt, ap);
+
+ if (unlikely (data_size < 0)) {
+ DBG_ERR("vsnprintf failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ memcpy(data + data_size, io_h->w_eol, io_h->w_eol_size);
+ data_size += io_h->w_eol_size;
+
+ return virusfilter_io_write(io_h, data, data_size);
+}
+
+bool virusfilter_io_writev(
+ struct virusfilter_io_handle *io_h, ...)
+{
+ va_list ap;
+ struct iovec iov[VIRUSFILTER_IO_IOV_MAX], *iov_p;
+ int iov_n;
+
+ va_start(ap, io_h);
+ for (iov_p = iov, iov_n = 0;
+ iov_n < VIRUSFILTER_IO_IOV_MAX;
+ iov_p++, iov_n++)
+ {
+ iov_p->iov_base = va_arg(ap, void *);
+ if (iov_p->iov_base == NULL) {
+ break;
+ }
+ iov_p->iov_len = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ return write_data_iov_timeout(io_h->stream, iov, iov_n,
+ io_h->io_timeout);
+}
+
+bool virusfilter_io_writevl(
+ struct virusfilter_io_handle *io_h, ...)
+{
+ va_list ap;
+ struct iovec iov[VIRUSFILTER_IO_IOV_MAX + 1], *iov_p;
+ int iov_n;
+
+ va_start(ap, io_h);
+ for (iov_p = iov, iov_n = 0; iov_n < VIRUSFILTER_IO_IOV_MAX;
+ iov_p++, iov_n++)
+ {
+ iov_p->iov_base = va_arg(ap, void *);
+ if (iov_p->iov_base == NULL) {
+ break;
+ }
+ iov_p->iov_len = va_arg(ap, int);
+ }
+ va_end(ap);
+
+ iov_p->iov_base = io_h->r_eol;
+ iov_p->iov_len = io_h->r_eol_size;
+ iov_n++;
+
+ return write_data_iov_timeout(io_h->stream, iov, iov_n,
+ io_h->io_timeout);
+}
+
+static bool return_existing_line(TALLOC_CTX *ctx,
+ struct virusfilter_io_handle *io_h,
+ char **read_line)
+{
+ size_t read_line_len = 0;
+ char *end_p = NULL;
+ char *eol = NULL;
+
+ eol = memmem(io_h->r_buffer, io_h->r_len,
+ io_h->r_eol, io_h->r_eol_size);
+ if (eol == NULL) {
+ return false;
+ }
+ end_p = eol + io_h->r_eol_size;
+
+ *eol = '\0';
+ read_line_len = strlen(io_h->r_buffer) + 1;
+ *read_line = talloc_memdup(ctx,
+ io_h->r_buffer,
+ read_line_len);
+ if (*read_line == NULL) {
+ return false;
+ }
+
+ /*
+ * Copy the remaining buffer over the line
+ * we returned.
+ */
+ memmove(io_h->r_buffer,
+ end_p,
+ io_h->r_len - (end_p - io_h->r_buffer));
+
+ /* And reduce the size left in the buffer. */
+ io_h->r_len -= (end_p - io_h->r_buffer);
+ return true;
+}
+
+static void readv_done(struct tevent_req *req)
+{
+ uint64_t *perr = tevent_req_callback_data(req, uint64_t);
+ int ret;
+ int err_ret;
+
+ ret = tstream_readv_recv(req, &err_ret);
+ TALLOC_FREE(req);
+ if (ret == -1) {
+ *perr = err_ret;
+ }
+}
+
+bool virusfilter_io_readl(TALLOC_CTX *ctx,
+ struct virusfilter_io_handle *io_h,
+ char **read_line)
+{
+ struct tevent_context *ev = NULL;
+ bool ok = false;
+ uint64_t *perror = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Search for an existing complete line. */
+ ok = return_existing_line(ctx, io_h, read_line);
+ if (ok) {
+ goto finish;
+ }
+
+ /*
+ * No complete line in the buffer. We must read more
+ * from the server.
+ */
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ DBG_ERR("Failed to setup event context.\n");
+ goto finish;
+ }
+
+ /* Error return - must be talloc'ed. */
+ perror = talloc_zero(frame, uint64_t);
+ if (perror == NULL) {
+ goto finish;
+ }
+
+ for (;;) {
+ ssize_t pending = 0;
+ size_t read_size = 0;
+ struct iovec iov;
+ struct tevent_req *req = NULL;
+
+ /*
+ * How much can we read ?
+ */
+ pending = tstream_pending_bytes(io_h->stream);
+ if (pending < 0) {
+ DBG_ERR("tstream_pending_bytes failed (%s).\n",
+ strerror(errno));
+ goto finish;
+ }
+
+ read_size = pending;
+ /* Must read at least one byte. */
+ read_size = MIN(read_size, 1);
+
+ /* And max remaining buffer space. */
+ read_size = MAX(read_size,
+ (sizeof(io_h->r_buffer) - io_h->r_len));
+
+ if (read_size == 0) {
+ /* Buffer is full with no EOL. Error out. */
+ DBG_ERR("Line buffer full.\n");
+ goto finish;
+ }
+
+ iov.iov_base = io_h->r_buffer + io_h->r_len;
+ iov.iov_len = read_size;
+
+ /* Read the data. */
+ req = tstream_readv_send(frame,
+ ev,
+ io_h->stream,
+ &iov,
+ 1);
+ if (req == NULL) {
+ DBG_ERR("out of memory.\n");
+ goto finish;
+ }
+
+ /* Callback when *all* data read. */
+ tevent_req_set_callback(req, readv_done, perror);
+
+ /* Set timeout. */
+ ok = tevent_req_set_endtime(req, ev,
+ timeval_current_ofs_msec(io_h->io_timeout));
+ if (!ok) {
+ DBG_ERR("can't set endtime\n");
+ goto finish;
+ }
+
+ /* Loop waiting for req to finish. */
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_ERR("tevent_req_poll failed\n");
+ goto finish;
+ }
+
+ /* Done with req - freed by the callback. */
+ req = NULL;
+
+ /*
+ * Emit debug error if failed.
+ * EPIPE may be success so, don't exit.
+ */
+ if (*perror != 0 && *perror != EPIPE) {
+ DBG_DEBUG("Error %s\n", strerror((int)*perror));
+ errno = (int)*perror;
+ goto finish;
+ }
+
+ /*
+ * We read read_size bytes. Extend the usable
+ * buffer length.
+ */
+ io_h->r_len += read_size;
+
+ /* Paranoia... */
+ SMB_ASSERT(io_h->r_len <= sizeof(io_h->r_buffer));
+
+ /* Exit if we have a line to return. */
+ ok = return_existing_line(ctx, io_h, read_line);
+ if (ok) {
+ goto finish;
+ }
+ /* No eol - keep reading. */
+ }
+
+ finish:
+
+ TALLOC_FREE(frame);
+ return ok;
+}
+
+bool PRINTF_ATTRIBUTE(3, 4) virusfilter_io_writefl_readl(
+ struct virusfilter_io_handle *io_h,
+ char **read_line,
+ const char *fmt, ...)
+{
+ bool ok;
+
+ if (fmt) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ ok = virusfilter_io_vwritefl(io_h, fmt, ap);
+ va_end(ap);
+
+ if (!ok) {
+ return ok;
+ }
+ }
+
+ ok = virusfilter_io_readl(talloc_tos(), io_h, read_line);
+ if (!ok) {
+ DBG_ERR("virusfilter_io_readl not OK: %d\n", ok);
+ return false;
+ }
+ if (io_h->r_len == 0) { /* EOF */
+ DBG_ERR("virusfilter_io_readl EOF\n");
+ return false;
+ }
+
+ return true;
+}
+
+struct virusfilter_cache *virusfilter_cache_new(
+ TALLOC_CTX *ctx,
+ int entry_limit,
+ time_t time_limit)
+{
+ struct virusfilter_cache *cache;
+
+ if (time_limit == 0) {
+ return NULL;
+ }
+
+ cache = talloc_zero(ctx, struct virusfilter_cache);
+ if (cache == NULL) {
+ DBG_ERR("talloc_zero failed.\n");
+ return NULL;
+ }
+
+ cache->cache = memcache_init(cache->ctx, entry_limit *
+ (sizeof(struct virusfilter_cache_entry)
+ + VIRUSFILTER_CACHE_BUFFER_SIZE));
+ if (cache->cache == NULL) {
+ DBG_ERR("memcache_init failed.\n");
+ return NULL;
+ }
+ cache->ctx = ctx;
+ cache->time_limit = time_limit;
+
+ return cache;
+}
+
+bool virusfilter_cache_entry_add(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname,
+ virusfilter_result result,
+ char *report)
+{
+ int blob_size = sizeof(struct virusfilter_cache_entry);
+ struct virusfilter_cache_entry *cache_e =
+ talloc_zero_size(NULL, blob_size);
+ int fname_len = 0;
+
+ if (fname == NULL || directory == NULL) {
+ TALLOC_FREE(report);
+ return false;
+ }
+
+ fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+ if (fname == NULL) {
+ TALLOC_FREE(report);
+ return false;
+ }
+
+ fname_len = strlen(fname);
+
+ if (cache_e == NULL|| cache->time_limit == 0) {
+ TALLOC_FREE(report);
+ return false;
+ }
+
+ cache_e->result = result;
+ if (report != NULL) {
+ cache_e->report = talloc_steal(cache_e, report);
+ }
+ if (cache->time_limit > 0) {
+ cache_e->time = time(NULL);
+ }
+
+ memcache_add_talloc(cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, fname_len), &cache_e);
+
+ return true;
+}
+
+bool virusfilter_cache_entry_rename(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ char *old_fname,
+ char *new_fname)
+{
+ int old_fname_len = 0;
+ int new_fname_len = 0;
+ struct virusfilter_cache_entry *new_data = NULL;
+ struct virusfilter_cache_entry *old_data = NULL;
+
+ if (old_fname == NULL || new_fname == NULL || directory == NULL) {
+ return false;
+ }
+
+ old_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, old_fname);
+ new_fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, new_fname);
+
+ if (old_fname == NULL || new_fname == NULL) {
+ TALLOC_FREE(old_fname);
+ TALLOC_FREE(new_fname);
+ return false;
+ }
+
+ old_fname_len = strlen(old_fname);
+ new_fname_len = strlen(new_fname);
+
+ old_data = memcache_lookup_talloc(
+ cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(old_fname, old_fname_len));
+
+ if (old_data == NULL) {
+ return false;
+ }
+
+ new_data = talloc_memdup(cache->ctx, old_data,
+ sizeof(struct virusfilter_cache_entry));
+ if (new_data == NULL) {
+ return false;
+ }
+ new_data->report = talloc_strdup(new_data, old_data->report);
+
+ memcache_add_talloc(cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(new_fname, new_fname_len), &new_data);
+
+ memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(old_fname, old_fname_len));
+
+ return true;
+}
+
+void virusfilter_cache_purge(struct virusfilter_cache *cache)
+{
+ memcache_flush(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC);
+}
+
+struct virusfilter_cache_entry *virusfilter_cache_get(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname)
+{
+ int fname_len = 0;
+ struct virusfilter_cache_entry *cache_e = NULL;
+ struct virusfilter_cache_entry *data = NULL;
+
+ if (fname == NULL || directory == NULL) {
+ return 0;
+ }
+
+ fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+ if (fname == NULL) {
+ return 0;
+ }
+
+ fname_len = strlen(fname);
+
+ data = memcache_lookup_talloc(cache->cache,
+ VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, fname_len));
+
+ if (data == NULL) {
+ return cache_e;
+ }
+
+ if (cache->time_limit > 0) {
+ if (time(NULL) - data->time > cache->time_limit) {
+ DBG_DEBUG("Cache entry is too old: %s\n",
+ fname);
+ virusfilter_cache_remove(cache, directory, fname);
+ return cache_e;
+ }
+ }
+ cache_e = talloc_memdup(cache->ctx, data,
+ sizeof(struct virusfilter_cache_entry));
+ if (cache_e == NULL) {
+ return NULL;
+ }
+ if (data->report != NULL) {
+ cache_e->report = talloc_strdup(cache_e, data->report);
+ } else {
+ cache_e->report = NULL;
+ }
+
+ return cache_e;
+}
+
+void virusfilter_cache_remove(struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname)
+{
+ DBG_DEBUG("Purging cache entry: %s/%s\n", directory, fname);
+
+ if (fname == NULL || directory == NULL) {
+ return;
+ }
+
+ fname = talloc_asprintf(talloc_tos(), "%s/%s", directory, fname);
+
+ if (fname == NULL) {
+ return;
+ }
+
+ memcache_delete(cache->cache, VIRUSFILTER_SCAN_RESULTS_CACHE_TALLOC,
+ data_blob_const(fname, strlen(fname)));
+}
+
+void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e)
+{
+ if (cache_e != NULL) {
+ TALLOC_FREE(cache_e->report);
+ cache_e->report = NULL;
+ }
+ TALLOC_FREE(cache_e);
+}
+
+/* Shell scripting
+ * ======================================================================
+ */
+
+int virusfilter_env_set(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ const char *name,
+ const char *value)
+{
+ char *env_new;
+ int ret;
+
+ env_new = talloc_asprintf(mem_ctx, "%s=%s", name, value);
+ if (env_new == NULL) {
+ DBG_ERR("talloc_asprintf failed\n");
+ return -1;
+ }
+
+ ret = strv_add(mem_ctx, env_list, env_new);
+
+ TALLOC_FREE(env_new);
+
+ return ret;
+}
+
+/* virusfilter_env version Samba's *_sub_advanced() in substitute.c */
+int virusfilter_shell_set_conn_env(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ connection_struct *conn)
+{
+ int snum = SNUM(conn);
+ char *server_addr_p;
+ char *client_addr_p;
+ const char *local_machine_name = get_local_machine_name();
+ fstring pidstr;
+ int ret;
+
+ server_addr_p = tsocket_address_inet_addr_string(
+ conn->sconn->local_address, talloc_tos());
+
+ if (server_addr_p != NULL) {
+ ret = strncmp("::ffff:", server_addr_p, 7);
+ if (ret == 0) {
+ server_addr_p += 7;
+ }
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_IP",
+ server_addr_p);
+ }
+ TALLOC_FREE(server_addr_p);
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_NAME",
+ myhostname());
+ virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_SERVER_NETBIOS_NAME",
+ local_machine_name);
+ slprintf(pidstr,sizeof(pidstr)-1, "%ld", (long)getpid());
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVER_PID",
+ pidstr);
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_NAME",
+ lp_const_servicename(snum));
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_SERVICE_PATH",
+ conn->cwd_fsp->fsp_name->base_name);
+
+ client_addr_p = tsocket_address_inet_addr_string(
+ conn->sconn->remote_address, talloc_tos());
+
+ if (client_addr_p != NULL) {
+ ret = strncmp("::ffff:", client_addr_p, 7);
+ if (ret == 0) {
+ client_addr_p += 7;
+ }
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_IP",
+ client_addr_p);
+ }
+ TALLOC_FREE(client_addr_p);
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_CLIENT_NAME",
+ conn->sconn->remote_hostname);
+ virusfilter_env_set(mem_ctx, env_list,
+ "VIRUSFILTER_CLIENT_NETBIOS_NAME",
+ get_remote_machine_name());
+
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_NAME",
+ get_current_username());
+ virusfilter_env_set(mem_ctx, env_list, "VIRUSFILTER_USER_DOMAIN",
+ get_current_user_info_domain());
+
+ return 0;
+}
+
+/* Wrapper to Samba's smbrun() in smbrun.c */
+int virusfilter_shell_run(
+ TALLOC_CTX *mem_ctx,
+ const char *cmd,
+ char **env_list,
+ connection_struct *conn,
+ bool sanitize)
+{
+ int ret;
+
+ if (conn != NULL) {
+ ret = virusfilter_shell_set_conn_env(mem_ctx, env_list, conn);
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ if (sanitize) {
+ return smbrun(cmd, NULL, strv_to_env(talloc_tos(), *env_list));
+ } else {
+ return smbrun_no_sanitize(cmd, NULL, strv_to_env(talloc_tos(),
+ *env_list));
+ }
+}
diff --git a/source3/modules/vfs_virusfilter_utils.h b/source3/modules/vfs_virusfilter_utils.h
new file mode 100644
index 0000000..69754aa
--- /dev/null
+++ b/source3/modules/vfs_virusfilter_utils.h
@@ -0,0 +1,177 @@
+/*
+ Samba-VirusFilter VFS modules
+ Copyright (C) 2010-2016 SATOH Fumiyasu @ OSS Technology Corp., Japan
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _VIRUSFILTER_UTILS_H
+#define _VIRUSFILTER_UTILS_H
+
+#include "modules/vfs_virusfilter_common.h"
+#include "../lib/util/memcache.h"
+#include "../lib/util/strv.h"
+
+/*#define str_eq(s1, s2) \
+ ((strcmp((s1), (s2)) == 0) ? true : false)
+#define strn_eq(s1, s2, n) \
+ ((strncmp((s1), (s2), (n)) == 0) ? true : false) */
+
+/* "* 3" is for %-encoding */
+#define VIRUSFILTER_IO_URL_MAX (PATH_MAX * 3)
+#define VIRUSFILTER_IO_BUFFER_SIZE (VIRUSFILTER_IO_URL_MAX + 128)
+#define VIRUSFILTER_IO_EOL_SIZE 1
+#define VIRUSFILTER_IO_IOV_MAX 16
+#define VIRUSFILTER_CACHE_BUFFER_SIZE (PATH_MAX + 128)
+
+struct virusfilter_io_handle {
+ struct tstream_context *stream;
+ int connect_timeout; /* msec */
+ int io_timeout; /* msec */
+
+ /* end-of-line character(s) */
+ char w_eol[VIRUSFILTER_IO_EOL_SIZE];
+ int w_eol_size;
+
+ /* end-of-line character(s) */
+ char r_eol[VIRUSFILTER_IO_EOL_SIZE];
+ int r_eol_size;
+
+ /* buffer */
+ char r_buffer[VIRUSFILTER_IO_BUFFER_SIZE];
+ size_t r_len;
+};
+
+struct virusfilter_cache_entry {
+ time_t time;
+ virusfilter_result result;
+ char *report;
+};
+
+struct virusfilter_cache {
+ struct memcache *cache;
+ TALLOC_CTX *ctx;
+ time_t time_limit;
+};
+
+/* ====================================================================== */
+
+char *virusfilter_string_sub(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *str);
+int virusfilter_vfs_next_move(
+ vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst);
+
+/* Line-based socket I/O */
+struct virusfilter_io_handle *virusfilter_io_new(
+ TALLOC_CTX *mem_ctx,
+ int connect_timeout,
+ int timeout);
+int virusfilter_io_set_connect_timeout(
+ struct virusfilter_io_handle *io_h,
+ int timeout);
+int virusfilter_io_set_io_timeout(
+ struct virusfilter_io_handle *io_h, int timeout);
+void virusfilter_io_set_writel_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size);
+void virusfilter_io_set_readl_eol(
+ struct virusfilter_io_handle *io_h,
+ const char *eol,
+ int eol_size);
+bool virusfilter_io_connect_path(
+ struct virusfilter_io_handle *io_h,
+ const char *path);
+bool virusfilter_io_disconnect(
+ struct virusfilter_io_handle *io_h);
+bool write_data_iov_timeout(
+ struct tstream_context *stream,
+ const struct iovec *iov,
+ size_t iovcnt,
+ int ms_timeout);
+bool virusfilter_io_write(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size);
+bool virusfilter_io_writel(
+ struct virusfilter_io_handle *io_h,
+ const char *data,
+ size_t data_size);
+bool virusfilter_io_writefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, ...);
+bool virusfilter_io_vwritefl(
+ struct virusfilter_io_handle *io_h,
+ const char *data_fmt, va_list ap);
+bool virusfilter_io_writev(
+ struct virusfilter_io_handle *io_h, ...);
+bool virusfilter_io_writevl(
+ struct virusfilter_io_handle *io_h, ...);
+bool virusfilter_io_readl(TALLOC_CTX *ctx,
+ struct virusfilter_io_handle *io_h,
+ char **read_line);
+bool virusfilter_io_writefl_readl(
+ struct virusfilter_io_handle *io_h,
+ char **read_line,
+ const char *fmt, ...);
+
+/* Scan result cache */
+struct virusfilter_cache *virusfilter_cache_new(
+ TALLOC_CTX *ctx,
+ int entry_limit,
+ time_t time_limit);
+bool virusfilter_cache_entry_add(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname,
+ virusfilter_result result,
+ char *report);
+bool virusfilter_cache_entry_rename(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ char *old_fname,
+ char *new_fname);
+void virusfilter_cache_entry_free(struct virusfilter_cache_entry *cache_e);
+struct virusfilter_cache_entry *virusfilter_cache_get(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname);
+void virusfilter_cache_remove(
+ struct virusfilter_cache *cache,
+ const char *directory,
+ const char *fname);
+void virusfilter_cache_purge(struct virusfilter_cache *cache);
+
+/* Shell scripting */
+int virusfilter_env_set(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ const char *name,
+ const char *value);
+int virusfilter_shell_set_conn_env(
+ TALLOC_CTX *mem_ctx,
+ char **env_list,
+ connection_struct *conn);
+int virusfilter_shell_run(
+ TALLOC_CTX *mem_ctx,
+ const char *cmd,
+ char **env_list,
+ connection_struct *conn,
+ bool sanitize);
+
+#endif /* _VIRUSFILTER_UTILS_H */
diff --git a/source3/modules/vfs_vxfs.c b/source3/modules/vfs_vxfs.c
new file mode 100644
index 0000000..aae2ca1
--- /dev/null
+++ b/source3/modules/vfs_vxfs.c
@@ -0,0 +1,736 @@
+/*
+Unix SMB/CIFS implementation.
+Wrap VxFS calls in vfs functions.
+This module is for ACL and XATTR handling.
+
+Copyright (C) Symantec Corporation <www.symantec.com> 2014
+Copyright (C) Veritas Technologies LLC <www.veritas.com> 2016
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "system/filesys.h"
+#include "vfs_vxfs.h"
+
+#undef strcasecmp
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#define MODULE_NAME "vxfs"
+
+/*
+ * WARNING !! WARNING !!
+ *
+ * DO NOT CHANGE THIS FROM "system." space to
+ * "user." space unless you are shipping a product
+ * that RESTRICTS access to extended attributes
+ * to smbd-only. "system." space is restricted
+ * to root access only, "user." space is available
+ * to ANY USER.
+ *
+ * If this is changed to "user." and access
+ * to extended attributes is available via
+ * local processes or other remote file system
+ * (e.g. NFS) then the security of the system
+ * WILL BE COMPROMISED. i.e. non-root users
+ * WILL be able to overwrite Samba ACLs on
+ * the file system.
+ *
+ * If you need to modify this define, do
+ * so using CFLAGS on your build command
+ * line.
+ * e.g. CFLAGS=-DXATTR_USER_NTACL="user.NTACL"
+ *
+ * Added by: <jra@samba.org> 17 Sept. 2014.
+ *
+ */
+
+/*
+ * Note:
+ * XATTR_USER_NTACL: This extended attribute is used
+ * to store Access Control List system objects by VxFS from DLV11 onwards.
+ * XATTR_USER_NTACL_V0: This extended attribute was used
+ * to store Access Control List system objects by VxFS till DLV10.
+ */
+#ifndef XATTR_USER_NTACL
+#define XATTR_USER_NTACL "system.NTACL"
+#define XATTR_USER_NTACL_V0 "user.NTACL"
+#endif
+
+/* type values */
+#define VXFS_ACL_UNDEFINED_TYPE 0
+#define VXFS_ACL_USER_OBJ 1
+#define VXFS_ACL_GROUP_OBJ 2
+#define VXFS_ACL_USER 3
+#define VXFS_ACL_GROUP 4
+#define VXFS_ACL_OTHER 5
+#define VXFS_ACL_MASK 6
+
+/*
+ * Helper function for comparing two strings
+ */
+static int vxfs_strcasecmp(const char *str1, const char *str2)
+{
+ bool match = strequal_m(str1, str2);
+ if (match) {
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Compare aces
+ * This will compare two ace entries for sorting
+ * each entry contains: type, perms and id
+ * Sort by type first, if type is same sort by id.
+ */
+static int vxfs_ace_cmp(const void *ace1, const void *ace2)
+{
+ int ret = 0;
+ uint16_t type_a1, type_a2;
+ uint32_t id_a1, id_a2;
+
+ /* Type must be compared first */
+ type_a1 = SVAL(ace1, 0);
+ type_a2 = SVAL(ace2, 0);
+
+ ret = (type_a1 - type_a2);
+ if (!ret) {
+ /* Compare ID under type */
+ /* skip perm thus take offset as 4*/
+ id_a1 = IVAL(ace1, 4);
+ id_a2 = IVAL(ace2, 4);
+ ret = id_a1 - id_a2;
+ }
+
+ return ret;
+}
+
+static void vxfs_print_ace_buf(char *buf, int count) {
+
+ int i, offset = 0;
+ uint16_t type, perm;
+ uint32_t id;
+
+ DEBUG(10, ("vfs_vxfs: Printing aces:\n"));
+ for (i = 0; i < count; i++) {
+ type = SVAL(buf, offset);
+ offset += 2;
+ perm = SVAL(buf, offset);
+ offset += 2;
+ id = IVAL(buf, offset);
+ offset += 4;
+
+ DEBUG(10, ("vfs_vxfs: type = %u, perm = %u, id = %u\n",
+ (unsigned int)type, (unsigned int)perm,
+ (unsigned int)id));
+ }
+}
+
+/*
+ * Sort aces so that comparing 2 ACLs will be straight forward.
+ * This function will fill buffer as follows:
+ * For each ace:
+ * 1. ace->a_type will be filled as first 2 bytes in buf.
+ * 2. ace->a_perm will be filled as next 2 bytes.
+ * 3. ace->xid will be filled as next 4 bytes.
+ * Thus each ace entry in buf is equal to 8 bytes.
+ * Also a_type is mapped to VXFS_ACL_* so that ordering aces
+ * becomes easy.
+ */
+static char * vxfs_sort_acl(SMB_ACL_T theacl, TALLOC_CTX *mem_ctx,
+ uint32_t o_uid,
+ uint32_t o_gid) {
+
+ struct smb_acl_entry *smb_ace;
+ int i, count;
+ uint16_t type, perm;
+ uint32_t id;
+ int offset = 0;
+ char *buf = NULL;
+
+ count = theacl->count;
+
+ buf = talloc_zero_size(mem_ctx, count * 8);
+ if (!buf) {
+ return NULL;
+ }
+
+ smb_ace = theacl->acl;
+
+ for (i = 0; i < count; i++) {
+ /* Calculate type */
+ /* Map type to SMB_ACL_* to VXFS_ACL_* */
+ switch(smb_ace->a_type) {
+ case SMB_ACL_USER:
+ type = VXFS_ACL_USER;
+ break;
+ case SMB_ACL_USER_OBJ:
+ type = VXFS_ACL_USER_OBJ;
+ break;
+ case SMB_ACL_GROUP:
+ type = VXFS_ACL_GROUP;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ type = VXFS_ACL_GROUP_OBJ;
+ break;
+ case SMB_ACL_OTHER:
+ type = VXFS_ACL_OTHER;
+ break;
+ case SMB_ACL_MASK:
+ type = VXFS_ACL_MASK;
+ break;
+ default:
+ type = -1;
+ talloc_free(buf);
+ return NULL;
+ }
+
+ type = type & 0xff;
+
+ /* Calculate id:
+ * We get owner uid and owner group gid in o_uid and o_gid
+ * Put these ids instead of -1
+ */
+ switch(smb_ace->a_type) {
+ case SMB_ACL_USER:
+ id = smb_ace->info.user.uid;
+ break;
+ case SMB_ACL_GROUP:
+ id = smb_ace->info.group.gid;
+ break;
+ case SMB_ACL_USER_OBJ:
+ id = o_uid;
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ id = o_gid;
+ break;
+ case SMB_ACL_MASK:
+ case SMB_ACL_OTHER:
+ id = -1;
+ break;
+ default:
+ /* Can't happen.. */
+ id = -1;
+ break;
+ }
+
+ /* Calculate perm */
+ perm = smb_ace->a_perm & 0xff;
+
+ /* TYPE is the first 2 bytes of an entry */
+ SSVAL(buf, offset, type);
+ offset += 2;
+
+ /* PERM is the next 2 bytes of an entry */
+ SSVAL(buf, offset, perm);
+ offset += 2;
+
+ /* ID is the last 4 bytes of an entry */
+ SIVAL(buf, offset, id);
+ offset += 4;
+
+ smb_ace++;
+ }
+
+ qsort(buf, count, 8, vxfs_ace_cmp);
+
+ DEBUG(10, ("vfs_vxfs: Print sorted aces:\n"));
+ vxfs_print_ace_buf(buf, count);
+
+ return buf;
+}
+
+/* This function gets e_buf as an arg which is sorted and created out of
+ * existing ACL. This function will compact this e_buf to c_buf where USER
+ * and GROUP aces matching with USER_OBJ and GROUP_OBJ will be merged
+ * respectively.
+ * This is similar to what posix_acls.c does. This will make sure existing
+ * acls are converted much similar to what posix_acls calculates.
+ */
+
+static char * vxfs_compact_buf(char *e_buf, int *new_count, int count,
+ TALLOC_CTX *mem_ctx)
+{
+ int i, e_offset = 0, c_offset = 0;
+ uint16_t type, perm, o_perm;
+ uint32_t id, owner_id, group_id;
+ char *c_buf = NULL;
+
+
+ if (count < 2) {
+ return NULL;
+ }
+
+ c_buf = talloc_zero_size(mem_ctx, count * 8);
+ if (!c_buf) {
+ return NULL;
+ }
+
+ /*Copy first two enries from e_buf to c_buf
+ *These are USER_OBJ and GROUP_OBJ
+ */
+
+ memcpy(c_buf, e_buf, 16);
+
+ (*new_count) = 2;
+
+ owner_id = IVAL(e_buf, 4);
+ group_id = IVAL(e_buf, 12);
+
+ c_offset = e_offset = 16;
+
+ /* Start comparing other entries */
+ for (i = 2; i < count; i++) {
+
+ type = SVAL(e_buf, e_offset);
+ e_offset += 2;
+ perm = SVAL(e_buf, e_offset);
+ e_offset += 2;
+ id = IVAL(e_buf, e_offset);
+ e_offset += 4;
+
+ switch(type) {
+ case VXFS_ACL_USER:
+ if (id == owner_id) {
+ o_perm = SVAL(c_buf, 2);
+ o_perm |= perm;
+ SSVAL(c_buf, 2, o_perm);
+ DEBUG(10, ("vfs_vxfs: merging with owner"
+ "e_type = %u,"
+ "e_perm = %u,"
+ "e_id = %u\n", (unsigned int)type,
+ (unsigned int)perm,
+ (unsigned int)id));
+ continue;
+ }
+ break;
+ case VXFS_ACL_GROUP:
+ if (id == group_id) {
+ o_perm = SVAL(c_buf, 10);
+ o_perm |= perm;
+ SSVAL(c_buf, 10, o_perm);
+ DEBUG(10, ("vfs_vxfs: merging with owner group"
+ "e_type = %u,"
+ "e_perm = %u,"
+ "e_id = %u\n", (unsigned int)type,
+ (unsigned int)perm,
+ (unsigned int)id));
+ continue;
+ }
+ break;
+ }
+
+ SSVAL(c_buf, c_offset, type);
+ c_offset += 2;
+
+ SSVAL(c_buf, c_offset, perm);
+ c_offset += 2;
+
+ SIVAL(c_buf, c_offset, id);
+ c_offset += 4;
+
+ (*new_count)++;
+ }
+ DEBUG(10, ("vfs_vxfs: new_count is %d\n", *new_count));
+ return c_buf;
+}
+
+/* Actually compare New ACL and existing ACL buf */
+static bool vxfs_compare_acls(char *e_buf, char *n_buf, int n_count,
+ int e_count) {
+
+ uint16_t e_type, n_type;
+ int offset = 0;
+
+ if (!e_buf && !n_buf) {
+ DEBUG(10, ("vfs_vxfs: Empty buffers!\n"));
+ return false;
+ }
+
+ if ((e_count < 2) || (n_count < 2)) {
+ return false;
+ }
+ /*Get type from last entry from both buffers.
+ * It may or may not be ACL_MASK
+ */
+ n_type = SVAL(n_buf, offset + (8 * (n_count-1)));
+ e_type = SVAL(e_buf, offset + (8 * (e_count-1)));
+
+ /* Check for ACL_MASK entry properly. Handle all 4 cases*/
+
+ /* If ACL_MASK entry is present in any of the buffers,
+ * it will be always the last one. Calculate count to compare
+ * based on if ACL_MASK is present on new and existing ACL
+ */
+ if ((n_type != VXFS_ACL_MASK) && (e_type == VXFS_ACL_MASK)){
+ DEBUG(10, ("vfs_vxfs: New ACL does not have mask entry,"
+ "reduce count by 1 and compare\n"));
+ e_count = e_count -1;
+ }
+ if ((n_type == VXFS_ACL_MASK) && (e_type != VXFS_ACL_MASK)){
+ DEBUG(10, ("vfs_vxfs: new ACL to be set contains mask"
+ "existing ACL does not have mask entry\n"
+ "Need to set New ACL\n"));
+ return false;
+ }
+
+ if (memcmp(e_buf, n_buf, (e_count * 8)) != 0) {
+ DEBUG(10, ("vfs_vxfs: Compare with memcmp,"
+ "buffers not same!\n"));
+ return false;
+ }
+
+ return true;
+}
+
+/* In VxFS, POSIX ACLs are pointed by separate inode for each file/dir.
+ * However, files/dir share same POSIX ACL inode if ACLs are inherited
+ * from parent.
+ * To retain this behaviour, below function avoids ACL set call if
+ * underlying ACLs are already same and thus saves creating extra inode.
+ *
+ * This function will execute following steps:
+ * 1. Get existing ACL
+ * 2. Sort New ACL and existing ACL into buffers
+ * 3. Compact existing ACL buf
+ * 4. Finally compare New ACL buf and Compact buf
+ * 5. If same, return true
+ * 6. Else need to set New ACL
+ */
+
+static bool vxfs_compare(struct files_struct *fsp,
+ SMB_ACL_T the_acl,
+ SMB_ACL_TYPE_T the_acl_type)
+{
+ SMB_ACL_T existing_acl = NULL;
+ bool ret = false;
+ int count = 0;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ char *existing_buf = NULL, *new_buf = NULL, *compact_buf = NULL;
+ int status;
+ NTSTATUS ntstatus;
+
+ DEBUG(10, ("vfs_vxfs: Getting existing ACL for %s\n", fsp_str_dbg(fsp)));
+
+ existing_acl = SMB_VFS_SYS_ACL_GET_FD(fsp, the_acl_type, mem_ctx);
+ if (existing_acl == NULL) {
+ DEBUG(10, ("vfs_vxfs: Failed to get ACL\n"));
+ goto out;
+ }
+
+ DEBUG(10, ("vfs_vxfs: Existing ACL count=%d\n", existing_acl->count));
+ DEBUG(10, ("vfs_vxfs: New ACL count=%d\n", the_acl->count));
+
+ if (existing_acl->count == 0) {
+ DEBUG(10, ("vfs_vxfs: ACL count is 0, Need to set\n"));
+ goto out;
+ }
+
+ ntstatus = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ DEBUG(10, ("vfs_vxfs: stat failed!\n"));
+ errno = map_errno_from_nt_status(ntstatus);
+ goto out;
+ }
+
+ DEBUG(10, ("vfs_vxfs: Sorting existing ACL\n"));
+ existing_buf = vxfs_sort_acl(existing_acl, mem_ctx,
+ fsp->fsp_name->st.st_ex_uid,
+ fsp->fsp_name->st.st_ex_gid);
+ if (!existing_buf)
+ goto out;
+
+ DEBUG(10, ("vfs_vxfs: Sorting new ACL\n"));
+ new_buf = vxfs_sort_acl(the_acl, mem_ctx, fsp->fsp_name->st.st_ex_uid,
+ fsp->fsp_name->st.st_ex_gid);
+ if (!new_buf) {
+ goto out;
+ }
+
+ DEBUG(10, ("vfs_vxfs: Compact existing buf\n"));
+ compact_buf = vxfs_compact_buf(existing_buf, &count,
+ existing_acl->count,
+ mem_ctx);
+ if (!compact_buf) {
+ goto out;
+ }
+
+ vxfs_print_ace_buf(compact_buf, count);
+
+ /* COmpare ACLs only if count is same or mismatch by 1 */
+ if ((count == the_acl->count) ||
+ (count == the_acl->count + 1) ||
+ (count+1 == the_acl->count)) {
+
+ if (vxfs_compare_acls(compact_buf, new_buf, the_acl->count,
+ count)) {
+ DEBUG(10, ("vfs_vxfs: ACLs matched. Not setting.\n"));
+ ret = true;
+ goto out;
+ } else
+ DEBUG(10, ("vfs_vxfs: ACLs NOT matched. Setting\n"));
+ } else {
+ DEBUG(10, ("vfs_vxfs: ACLs count does not match. Setting\n"));
+ }
+
+out:
+
+ TALLOC_FREE(existing_acl);
+ TALLOC_FREE(existing_buf);
+ TALLOC_FREE(compact_buf);
+ TALLOC_FREE(new_buf);
+
+ return ret;
+}
+
+#ifdef VXFS_ACL_SHARE
+static int vxfs_sys_acl_set_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+
+ if (vxfs_compare(fsp, theacl, type)) {
+ return 0;
+ }
+
+ return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl);
+}
+#endif
+
+static int vxfs_fset_xattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ const void *value, size_t size, int flags){
+ int ret = 0;
+ int tmp_ret = 0;
+
+ DBG_DEBUG("In vxfs_fset_xattr\n");
+
+ ret = vxfs_setxattr_fd(fsp_get_io_fd(fsp), name, value, size, flags);
+ if ((ret == 0) ||
+ ((ret == -1) && (errno != ENOTSUP) && (errno != ENOSYS))) {
+ /*
+ * version 1: user.NTACL xattr without inheritance supported
+ * version 2: user.NTACL xattr with inheritance supported
+ * version 3: new styled xattr security.NTACL with inheritance supported
+ * Hence, the old styled xattr user.NTACL should be removed
+ */
+ tmp_ret = vxfs_strcasecmp(name, XATTR_NTACL_NAME);
+ if (tmp_ret == 0) {
+ SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, XATTR_USER_NTACL_V0);
+ DBG_DEBUG("Old style xattr %s removed...\n", XATTR_USER_NTACL_V0);
+ }
+
+ return ret;
+ }
+
+ DBG_DEBUG("Fallback to xattr\n");
+ if (strcmp(name, XATTR_NTACL_NAME) == 0) {
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp, XATTR_USER_NTACL,
+ value, size, flags);
+ }
+
+ /* Clients can't set XATTR_USER_NTACL directly. */
+ if (vxfs_strcasecmp(name, XATTR_USER_NTACL) == 0) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags);
+}
+
+static ssize_t vxfs_fget_xattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ void *value, size_t size){
+ int ret;
+
+ DEBUG(10, ("In vxfs_fget_xattr\n"));
+
+ ret = vxfs_getxattr_fd(fsp_get_io_fd(fsp), name, value, size);
+ if ((ret != -1) || ((errno != ENOTSUP) &&
+ (errno != ENOSYS) && (errno != ENODATA))) {
+ return ret;
+ }
+
+ DEBUG(10, ("Fallback to xattr\n"));
+ if (strcmp(name, XATTR_NTACL_NAME) == 0) {
+ return SMB_VFS_NEXT_FGETXATTR(handle, fsp, XATTR_USER_NTACL,
+ value, size);
+ }
+
+ /* Clients can't see XATTR_USER_NTACL directly. */
+ if (vxfs_strcasecmp(name, XATTR_USER_NTACL) == 0) {
+ errno = ENOATTR;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FGETXATTR(handle, fsp, name, value, size);
+}
+
+static int vxfs_fremove_xattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name){
+ int ret = 0, ret_new = 0, old_errno;
+
+ DEBUG(10, ("In vxfs_fremove_xattr\n"));
+
+ /* Remove with old way */
+ if (strcmp(name, XATTR_NTACL_NAME) == 0) {
+ ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp,
+ XATTR_USER_NTACL);
+ } else {
+ /* Clients can't remove XATTR_USER_NTACL directly. */
+ if (vxfs_strcasecmp(name, XATTR_USER_NTACL) != 0) {
+ ret = SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp,
+ name);
+ }
+ }
+ old_errno = errno;
+
+ /* Remove with new way */
+ ret_new = vxfs_removexattr_fd(fsp_get_io_fd(fsp), name);
+ /*
+ * If both fail, return failure else return whichever succeeded
+ */
+ if (errno == ENOTSUP || errno == ENOSYS) {
+ errno = old_errno;
+ }
+ if ((ret_new != -1) && (ret == -1)) {
+ ret = ret_new;
+ }
+
+ return ret;
+
+}
+
+static size_t vxfs_filter_list(char *list, size_t size)
+{
+ char *str = list;
+
+ while (str - list < size) {
+ size_t element_len = strlen(str) + 1;
+ if (vxfs_strcasecmp(str, XATTR_USER_NTACL) == 0) {
+ memmove(str,
+ str + element_len,
+ size - (str - list) - element_len);
+ size -= element_len;
+ continue;
+ }
+ str += element_len;
+ }
+ return size;
+}
+
+static ssize_t vxfs_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ ssize_t result;
+
+ result = vxfs_listxattr_fd(fsp_get_io_fd(fsp), list, size);
+ if (result >= 0 || ((errno != ENOTSUP) && (errno != ENOSYS))) {
+ return result;
+ }
+
+ result = SMB_VFS_NEXT_FLISTXATTR(handle, fsp, list, size);
+
+ if (result <= 0) {
+ return result;
+ }
+
+ /* Remove any XATTR_USER_NTACL elements from the returned list. */
+ result = vxfs_filter_list(list, result);
+
+ return result;
+}
+
+static NTSTATUS vxfs_fset_ea_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ NTSTATUS err;
+ int ret = 0;
+
+ DBG_DEBUG("Entered function\n");
+
+ err = SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+ if (!NT_STATUS_IS_OK(err)) {
+ DBG_DEBUG("err:%d\n", err);
+ return err;
+ }
+ if (!(dosmode & FILE_ATTRIBUTE_READONLY)) {
+ ret = vxfs_checkwxattr_fd(fsp_get_io_fd(fsp));
+ if (ret == -1) {
+ DBG_DEBUG("ret:%d\n", ret);
+ if ((errno != EOPNOTSUPP) && (errno != ENOENT)) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ if (dosmode & FILE_ATTRIBUTE_READONLY) {
+ ret = vxfs_setwxattr_fd(fsp_get_io_fd(fsp));
+ DBG_DEBUG("ret:%d\n", ret);
+ if (ret == -1) {
+ if ((errno != EOPNOTSUPP) && (errno != EINVAL)) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static int vfs_vxfs_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+
+ int ret;
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ vxfs_init();
+
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_vxfs_fns = {
+ .connect_fn = vfs_vxfs_connect,
+
+#ifdef VXFS_ACL_SHARE
+ .sys_acl_set_fd_fn = vxfs_sys_acl_set_fd,
+#endif
+
+ .fset_dos_attributes_fn = vxfs_fset_ea_dos_attributes,
+ .getxattrat_send_fn = vfs_not_implemented_getxattrat_send,
+ .getxattrat_recv_fn = vfs_not_implemented_getxattrat_recv,
+ .fgetxattr_fn = vxfs_fget_xattr,
+ .flistxattr_fn = vxfs_flistxattr,
+ .fremovexattr_fn = vxfs_fremove_xattr,
+ .fsetxattr_fn = vxfs_fset_xattr,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_vxfs_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "vxfs",
+ &vfs_vxfs_fns);
+}
diff --git a/source3/modules/vfs_vxfs.h b/source3/modules/vfs_vxfs.h
new file mode 100644
index 0000000..9194b8b
--- /dev/null
+++ b/source3/modules/vfs_vxfs.h
@@ -0,0 +1,36 @@
+/*
+Unix SMB/CIFS implementation.
+Wrap VxFS xattr calls in vfs functions.
+
+Copyright (C) Veritas Technologies LLC <www.veritas.com> 2016
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+int vxfs_setxattr_fd(int, const char *, const void *, size_t, int);
+
+int vxfs_getxattr_path(const char *, const char *, void *, size_t);
+int vxfs_getxattr_fd(int, const char *, void *, size_t);
+
+int vxfs_removexattr_fd(int, const char *);
+
+int vxfs_listxattr_fd(int, char *, size_t);
+
+int vxfs_setwxattr_path(const char *, bool);
+int vxfs_setwxattr_fd(int);
+
+int vxfs_checkwxattr_path(const char *);
+int vxfs_checkwxattr_fd(int);
+
+void vxfs_init(void);
diff --git a/source3/modules/vfs_widelinks.c b/source3/modules/vfs_widelinks.c
new file mode 100644
index 0000000..c5b5084
--- /dev/null
+++ b/source3/modules/vfs_widelinks.c
@@ -0,0 +1,418 @@
+/*
+ * Widelinks VFS module. Causes smbd not to see symlinks.
+ *
+ * Copyright (C) Jeremy Allison, 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ What does this module do ? It implements the explicitly insecure
+ "widelinks = yes" functionality that used to be in the core smbd
+ code.
+
+ Now this is implemented here, the insecure share-escape code that
+ explicitly allows escape from an exported share path can be removed
+ from smbd, leaving it a cleaner and more maintainable code base.
+
+ The smbd code can now always return ACCESS_DENIED if a path
+ leads outside a share.
+
+ How does it do that ? There are 2 features.
+
+ 1). When the upper layer code does a chdir() call to a pathname,
+ this module stores the requested pathname inside config->cwd.
+
+ When the upper layer code does a getwd() or realpath(), we return
+ the absolute path of the value stored in config->cwd, *not* the
+ position on the underlying filesystem.
+
+ This hides symlinks as if the chdir pathname contains a symlink,
+ normally doing a realpath call on it would return the real
+ position on the filesystem. For widelinks = yes, this isn't what
+ you want. You want the position you think is underneath the share
+ definition - the symlink path you used to go outside the share,
+ not the contents of the symlink itself.
+
+ That way, the upper layer smbd code can strictly enforce paths
+ being underneath a share definition without the knowledge that
+ "widelinks = yes" has moved us outside the share definition.
+
+ 1a). Note that when setting up a share, smbd may make calls such
+ as realpath and stat/lstat in order to set up the share definition.
+ These calls are made *before* smbd calls chdir() to move the working
+ directory below the exported share definition. In order to allow
+ this, all the vfs_widelinks functions are coded to just pass through
+ the vfs call to the next module in the chain if (a). The widelinks
+ module was loaded in error by an administrator and widelinks is
+ set to "no". This is the:
+
+ if (!config->active) {
+ Module not active.
+ SMB_VFS_NEXT_XXXXX(...)
+ }
+
+ idiom in the vfs functions.
+
+ 1b). If the module was correctly active, but smbd has yet
+ to call chdir(), then config->cwd == NULL. In that case
+ the correct action (to match the previous widelinks behavior
+ in the code inside smbd) is to pass through the vfs call to
+ the next module in the chain. That way, any symlinks in the
+ pathname are still exposed to smbd, which will restrict them to
+ be under the exported share definition. This allows the module
+ to "fail safe" for any vfs call made when setting up the share
+ structure definition, rather than fail unsafe by hiding symlinks
+ before chdir is called. This is the:
+
+ if (config->cwd == NULL) {
+ XXXXX syscall before chdir - see note 1b above.
+ return SMB_VFS_NEXT_XXXXX()
+ }
+
+ idiom in the vfs functions.
+
+ 2). The module hides the existence of symlinks by inside
+ lstat(), open(), and readdir() so long as it's not a POSIX
+ pathname request (those requests *must* be aware of symlinks
+ and the POSIX client has to follow them, it's expected that
+ a server will always fail to follow symlinks).
+
+ It does this by:
+
+ 2a). lstat -> stat
+ 2b). open removes any O_NOFOLLOW from flags.
+ 2c). The optimization in readdir that returns a stat
+ struct is removed as this could return a symlink mode
+ bit, causing smbd to always call stat/lstat itself on
+ a pathname (which we'll then use to hide symlinks).
+
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "lib/util_path.h"
+
+struct widelinks_config {
+ bool active;
+ bool is_dfs_share;
+ char *cwd;
+};
+
+static int widelinks_connect(struct vfs_handle_struct *handle,
+ const char *service,
+ const char *user)
+{
+ struct widelinks_config *config;
+ int ret;
+
+ ret = SMB_VFS_NEXT_CONNECT(handle,
+ service,
+ user);
+ if (ret != 0) {
+ return ret;
+ }
+
+ config = talloc_zero(handle->conn,
+ struct widelinks_config);
+ if (!config) {
+ SMB_VFS_NEXT_DISCONNECT(handle);
+ return -1;
+ }
+ config->active = lp_widelinks(SNUM(handle->conn));
+ if (!config->active) {
+ DBG_ERR("vfs_widelinks module loaded with "
+ "widelinks = no\n");
+ }
+ config->is_dfs_share =
+ (lp_host_msdfs() && lp_msdfs_root(SNUM(handle->conn)));
+ SMB_VFS_HANDLE_SET_DATA(handle,
+ config,
+ NULL, /* free_fn */
+ struct widelinks_config,
+ return -1);
+ return 0;
+}
+
+static int widelinks_chdir(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ int ret = -1;
+ struct widelinks_config *config = NULL;
+ char *new_cwd = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct widelinks_config,
+ return -1);
+
+ if (!config->active) {
+ /* Module not active. */
+ return SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+ }
+
+ /*
+ * We know we never get a path containing
+ * DOT or DOTDOT.
+ */
+
+ if (smb_fname->base_name[0] == '/') {
+ /* Absolute path - replace. */
+ new_cwd = talloc_strdup(config,
+ smb_fname->base_name);
+ } else {
+ if (config->cwd == NULL) {
+ /*
+ * Relative chdir before absolute one -
+ * see note 1b above.
+ */
+ struct smb_filename *current_dir_fname =
+ SMB_VFS_NEXT_GETWD(handle,
+ config);
+ if (current_dir_fname == NULL) {
+ return -1;
+ }
+ /* Paranoia.. */
+ if (current_dir_fname->base_name[0] != '/') {
+ DBG_ERR("SMB_VFS_NEXT_GETWD returned "
+ "non-absolute path |%s|\n",
+ current_dir_fname->base_name);
+ TALLOC_FREE(current_dir_fname);
+ return -1;
+ }
+ config->cwd = talloc_strdup(config,
+ current_dir_fname->base_name);
+ TALLOC_FREE(current_dir_fname);
+ if (config->cwd == NULL) {
+ return -1;
+ }
+ }
+ new_cwd = talloc_asprintf(config,
+ "%s/%s",
+ config->cwd,
+ smb_fname->base_name);
+ }
+ if (new_cwd == NULL) {
+ return -1;
+ }
+ ret = SMB_VFS_NEXT_CHDIR(handle, smb_fname);
+ if (ret == -1) {
+ TALLOC_FREE(new_cwd);
+ return ret;
+ }
+ /* Replace the cache we use for realpath/getwd. */
+ TALLOC_FREE(config->cwd);
+ config->cwd = new_cwd;
+ DBG_DEBUG("config->cwd now |%s|\n", config->cwd);
+ return 0;
+}
+
+static struct smb_filename *widelinks_getwd(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ struct widelinks_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct widelinks_config,
+ return NULL);
+
+ if (!config->active) {
+ /* Module not active. */
+ return SMB_VFS_NEXT_GETWD(handle, ctx);
+ }
+ if (config->cwd == NULL) {
+ /* getwd before chdir. See note 1b above. */
+ return SMB_VFS_NEXT_GETWD(handle, ctx);
+ }
+ return synthetic_smb_fname(ctx,
+ config->cwd,
+ NULL,
+ NULL,
+ 0,
+ 0);
+}
+
+static struct smb_filename *widelinks_realpath(vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname_in)
+{
+ struct widelinks_config *config = NULL;
+ char *pathname = NULL;
+ char *resolved_pathname = NULL;
+ struct smb_filename *smb_fname;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct widelinks_config,
+ return NULL);
+
+ if (!config->active) {
+ /* Module not active. */
+ return SMB_VFS_NEXT_REALPATH(handle,
+ ctx,
+ smb_fname_in);
+ }
+
+ if (config->cwd == NULL) {
+ /* realpath before chdir. See note 1b above. */
+ return SMB_VFS_NEXT_REALPATH(handle,
+ ctx,
+ smb_fname_in);
+ }
+
+ if (smb_fname_in->base_name[0] == '/') {
+ /* Absolute path - process as-is. */
+ pathname = talloc_strdup(config,
+ smb_fname_in->base_name);
+ } else {
+ /* Relative path - most commonly "." */
+ pathname = talloc_asprintf(config,
+ "%s/%s",
+ config->cwd,
+ smb_fname_in->base_name);
+ }
+
+ SMB_ASSERT(pathname[0] == '/');
+
+ resolved_pathname = canonicalize_absolute_path(config, pathname);
+ if (resolved_pathname == NULL) {
+ TALLOC_FREE(pathname);
+ return NULL;
+ }
+
+ DBG_DEBUG("realpath |%s| -> |%s| -> |%s|\n",
+ smb_fname_in->base_name,
+ pathname,
+ resolved_pathname);
+
+ smb_fname = synthetic_smb_fname(ctx,
+ resolved_pathname,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ TALLOC_FREE(pathname);
+ TALLOC_FREE(resolved_pathname);
+ return smb_fname;
+}
+
+static int widelinks_lstat(vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ struct widelinks_config *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct widelinks_config,
+ return -1);
+
+ if (!config->active) {
+ /* Module not active. */
+ return SMB_VFS_NEXT_LSTAT(handle,
+ smb_fname);
+ }
+
+ if (config->cwd == NULL) {
+ /* lstat before chdir. See note 1b above. */
+ return SMB_VFS_NEXT_LSTAT(handle,
+ smb_fname);
+ }
+
+ if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ /* POSIX sees symlinks. */
+ return SMB_VFS_NEXT_LSTAT(handle,
+ smb_fname);
+ }
+
+ /* Replace with STAT. */
+ return SMB_VFS_NEXT_STAT(handle, smb_fname);
+}
+
+static int widelinks_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how)
+{
+ struct vfs_open_how how = *_how;
+ struct widelinks_config *config = NULL;
+ int ret;
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct widelinks_config,
+ return -1);
+
+ if (config->active &&
+ (config->cwd != NULL) &&
+ !(smb_fname->flags & SMB_FILENAME_POSIX_PATH))
+ {
+ /*
+ * Module active, openat after chdir (see note 1b above) and not
+ * a POSIX open (POSIX sees symlinks), so remove O_NOFOLLOW.
+ */
+ how.flags = (how.flags & ~O_NOFOLLOW);
+ }
+
+ ret = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ &how);
+ if (config->is_dfs_share && ret == -1 && errno == ENOENT) {
+ struct smb_filename *full_fname = NULL;
+ int lstat_ret;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+ lstat_ret = SMB_VFS_NEXT_LSTAT(handle,
+ full_fname);
+ if (lstat_ret != -1 &&
+ VALID_STAT(full_fname->st) &&
+ S_ISLNK(full_fname->st.st_ex_mode)) {
+ fsp->fsp_name->st = full_fname->st;
+ }
+ TALLOC_FREE(full_fname);
+ errno = ELOOP;
+ }
+ return ret;
+}
+
+static struct vfs_fn_pointers vfs_widelinks_fns = {
+ .connect_fn = widelinks_connect,
+
+ .openat_fn = widelinks_openat,
+ .lstat_fn = widelinks_lstat,
+ /*
+ * NB. We don't need an lchown function as this
+ * is only called (a) on directory create and
+ * (b) on POSIX extensions names.
+ */
+ .chdir_fn = widelinks_chdir,
+ .getwd_fn = widelinks_getwd,
+ .realpath_fn = widelinks_realpath,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_widelinks_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION,
+ "widelinks",
+ &vfs_widelinks_fns);
+}
diff --git a/source3/modules/vfs_worm.c b/source3/modules/vfs_worm.c
new file mode 100644
index 0000000..5c1bc6d
--- /dev/null
+++ b/source3/modules/vfs_worm.c
@@ -0,0 +1,359 @@
+/*
+ * VFS module to disallow writes for older files
+ *
+ * Copyright (C) 2013, Volker Lendecke
+ * Copyright (C) 2023-2024, Björn Jacke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "libcli/security/security.h"
+
+struct worm_config_data {
+ double grace_period;
+};
+static const uint32_t write_access_flags = FILE_WRITE_DATA | FILE_APPEND_DATA |
+ FILE_WRITE_ATTRIBUTES |
+ DELETE_ACCESS | WRITE_DAC_ACCESS |
+ WRITE_OWNER_ACCESS | FILE_WRITE_EA;
+
+static int vfs_worm_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct worm_config_data *config = NULL;
+ int ret;
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ if (IS_IPC(handle->conn) || IS_PRINT(handle->conn)) {
+ return 0;
+ }
+
+ config = talloc_zero(handle->conn, struct worm_config_data);
+ if (config == NULL) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+ config->grace_period = lp_parm_int(SNUM(handle->conn), "worm",
+ "grace_period", 3600);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct worm_config_data,
+ return -1);
+ return 0;
+
+}
+
+static bool is_readonly(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ double age;
+ struct worm_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct worm_config_data,
+ return true);
+
+ if (!VALID_STAT(smb_fname->st)) {
+ goto out;
+ }
+
+ age = timespec_elapsed(&smb_fname->st.st_ex_ctime);
+
+ if (age > config->grace_period) {
+ return true;
+ }
+
+out:
+ return false;
+}
+static bool fsp_is_readonly(vfs_handle_struct *handle, files_struct *fsp)
+{
+ double age;
+ struct worm_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle,
+ config,
+ struct worm_config_data,
+ return true);
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ goto out;
+ }
+
+ age = timespec_elapsed(&fsp->fsp_name->st.st_ex_ctime);
+
+ if (age > config->grace_period) {
+ return true;
+ }
+
+out:
+ return false;
+}
+
+static NTSTATUS vfs_worm_create_file(vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS status;
+ bool readonly;
+
+ readonly = is_readonly(handle, smb_fname);
+
+ if (readonly && (access_mask & write_access_flags)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = SMB_VFS_NEXT_CREATE_FILE(
+ handle, req, dirfsp, smb_fname, access_mask,
+ share_access, create_disposition, create_options,
+ file_attributes, oplock_request, lease, allocation_size,
+ private_flags, sd, ea_list, result, pinfo,
+ in_context_blobs, out_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Access via MAXIMUM_ALLOWED_ACCESS?
+ */
+ if (readonly && ((*result)->access_mask & write_access_flags)) {
+ close_file_free(req, result, NORMAL_CLOSE);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+static int vfs_worm_openat(vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ if (is_readonly(handle, smb_fname) &&
+ (fsp->access_mask & write_access_flags)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_OPENAT(handle, dirfsp, smb_fname, fsp, how);
+}
+
+static int vfs_worm_fntimes(vfs_handle_struct *handle,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FNTIMES(handle, fsp, ft);
+}
+
+static int vfs_worm_fchmod(vfs_handle_struct *handle,
+ files_struct *fsp,
+ mode_t mode)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FCHMOD(handle, fsp, mode);
+}
+
+static int vfs_worm_fchown(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uid_t uid,
+ gid_t gid)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FCHOWN(handle, fsp, uid, gid);
+}
+
+static int vfs_worm_renameat(vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ if (is_readonly(handle, smb_fname_src)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_RENAMEAT(
+ handle, srcfsp, smb_fname_src, dstfsp, smb_fname_dst);
+}
+
+static int vfs_worm_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name,
+ const void *value,
+ size_t size,
+ int flags)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FSETXATTR(handle, fsp, name, value, size, flags);
+}
+
+static int vfs_worm_fremotexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
+}
+
+static int vfs_worm_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct smb_filename *full_fname = NULL;
+ bool readonly;
+
+ full_fname = full_path_from_dirfsp_atname(talloc_tos(),
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ return -1;
+ }
+
+ readonly = is_readonly(handle, full_fname);
+
+ TALLOC_FREE(full_fname);
+
+ if (readonly) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_UNLINKAT(handle, dirfsp, smb_fname, flags);
+}
+
+static NTSTATUS vfs_worm_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return SMB_VFS_NEXT_FSET_DOS_ATTRIBUTES(handle, fsp, dosmode);
+}
+
+static NTSTATUS vfs_worm_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return SMB_VFS_NEXT_FSET_NT_ACL(handle, fsp, security_info_sent, psd);
+}
+
+static int vfs_worm_sys_acl_set_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_SYS_ACL_SET_FD(handle, fsp, type, theacl);
+}
+
+static int vfs_worm_sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ if (fsp_is_readonly(handle, fsp)) {
+ errno = EACCES;
+ return -1;
+ }
+
+ return SMB_VFS_NEXT_SYS_ACL_DELETE_DEF_FD(handle, fsp);
+}
+
+static struct vfs_fn_pointers vfs_worm_fns = {
+ .connect_fn = vfs_worm_connect,
+ .create_file_fn = vfs_worm_create_file,
+ .openat_fn = vfs_worm_openat,
+ .fntimes_fn = vfs_worm_fntimes,
+ .fchmod_fn = vfs_worm_fchmod,
+ .fchown_fn = vfs_worm_fchown,
+ .renameat_fn = vfs_worm_renameat,
+ .fsetxattr_fn = vfs_worm_fsetxattr,
+ .fremovexattr_fn = vfs_worm_fremotexattr,
+ .unlinkat_fn = vfs_worm_unlinkat,
+ .fset_dos_attributes_fn = vfs_worm_fset_dos_attributes,
+ .fset_nt_acl_fn = vfs_worm_fset_nt_acl,
+ .sys_acl_set_fd_fn = vfs_worm_sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = vfs_worm_sys_acl_delete_def_fd,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_worm_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS ret;
+
+ ret = smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "worm",
+ &vfs_worm_fns);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ return ret;
+}
diff --git a/source3/modules/vfs_xattr_tdb.c b/source3/modules/vfs_xattr_tdb.c
new file mode 100644
index 0000000..447d868
--- /dev/null
+++ b/source3/modules/vfs_xattr_tdb.c
@@ -0,0 +1,702 @@
+/*
+ * Store posix-level xattrs in a tdb
+ *
+ * Copyright (C) Volker Lendecke, 2007
+ * Copyright (C) Andrew Bartlett, 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "source3/lib/xattr_tdb.h"
+#include "lib/util/tevent_unix.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+struct xattr_tdb_config {
+ struct db_context *db;
+ bool ignore_user_xattr;
+};
+
+static bool xattr_tdb_init(struct vfs_handle_struct *handle,
+ struct xattr_tdb_config **_config);
+
+static bool is_user_xattr(const char *xattr_name)
+{
+ int match;
+
+ match = strncmp(xattr_name, "user.", strlen("user."));
+ return (match == 0);
+}
+
+static int xattr_tdb_get_file_id(struct vfs_handle_struct *handle,
+ const char *path, struct file_id *id)
+{
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smb_filename *smb_fname;
+
+ smb_fname = synthetic_smb_fname(frame,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_STAT(handle, smb_fname);
+
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ *id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &smb_fname->st);
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+struct xattr_tdb_getxattrat_state {
+ struct vfs_aio_state vfs_aio_state;
+ ssize_t xattr_size;
+ uint8_t *xattr_value;
+};
+
+static void xattr_tdb_getxattrat_done(struct tevent_req *subreq);
+
+static struct tevent_req *xattr_tdb_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct xattr_tdb_config *config = NULL;
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct xattr_tdb_getxattrat_state *state = NULL;
+ struct smb_filename *cwd = NULL;
+ struct file_id id;
+ int ret;
+ int error;
+ int cwd_ret;
+ DATA_BLOB xattr_blob;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return NULL;
+ }
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct xattr_tdb_getxattrat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->xattr_size = -1;
+
+ if (config->ignore_user_xattr && is_user_xattr(xattr_name)) {
+ subreq = SMB_VFS_NEXT_GETXATTRAT_SEND(state,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname,
+ xattr_name,
+ alloc_hint);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, xattr_tdb_getxattrat_done, req);
+ return req;
+ }
+
+ cwd = SMB_VFS_GETWD(dir_fsp->conn, state);
+ if (tevent_req_nomem(cwd, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ret = SMB_VFS_CHDIR(dir_fsp->conn, dir_fsp->fsp_name);
+ if (ret != 0) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ ret = xattr_tdb_get_file_id(handle, smb_fname->base_name, &id);
+ error = errno;
+
+ cwd_ret = SMB_VFS_CHDIR(dir_fsp->conn, cwd);
+ SMB_ASSERT(cwd_ret == 0);
+
+ if (ret == -1) {
+ tevent_req_error(req, error);
+ return tevent_req_post(req, ev);
+ }
+
+ state->xattr_size = xattr_tdb_getattr(config->db,
+ state,
+ &id,
+ xattr_name,
+ &xattr_blob);
+ if (state->xattr_size == -1) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ if (alloc_hint == 0) {
+ /*
+ * The caller only wants to know the size.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->xattr_size == 0) {
+ /*
+ * There's no data.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (xattr_blob.length > alloc_hint) {
+ /*
+ * The data doesn't fit.
+ */
+ state->xattr_size = -1;
+ tevent_req_error(req, ERANGE);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * take the whole blob.
+ */
+ state->xattr_value = xattr_blob.data;
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void xattr_tdb_getxattrat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct xattr_tdb_getxattrat_state *state = tevent_req_data(
+ req, struct xattr_tdb_getxattrat_state);
+
+ state->xattr_size = SMB_VFS_NEXT_GETXATTRAT_RECV(subreq,
+ &state->vfs_aio_state,
+ state,
+ &state->xattr_value);
+ TALLOC_FREE(subreq);
+ if (state->xattr_size == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+
+static ssize_t xattr_tdb_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct xattr_tdb_getxattrat_state *state = tevent_req_data(
+ req, struct xattr_tdb_getxattrat_state);
+ ssize_t xattr_size;
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *aio_state = state->vfs_aio_state;
+ xattr_size = state->xattr_size;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+static ssize_t xattr_tdb_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name, void *value, size_t size)
+{
+ struct xattr_tdb_config *config = NULL;
+ SMB_STRUCT_STAT sbuf;
+ struct file_id id;
+ ssize_t xattr_size;
+ DATA_BLOB blob;
+ TALLOC_CTX *frame = NULL;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return -1;
+ }
+
+ if (config->ignore_user_xattr && is_user_xattr(name)) {
+ return SMB_VFS_NEXT_FGETXATTR(
+ handle, fsp, name, value, size);
+ }
+
+ if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) {
+ return -1;
+ }
+
+ frame = talloc_stackframe();
+
+ id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf);
+
+ xattr_size = xattr_tdb_getattr(config->db, frame, &id, name, &blob);
+ if (xattr_size < 0) {
+ errno = ENOATTR;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ if (size == 0) {
+ TALLOC_FREE(frame);
+ return xattr_size;
+ }
+
+ if (blob.length > size) {
+ TALLOC_FREE(frame);
+ errno = ERANGE;
+ return -1;
+ }
+ memcpy(value, blob.data, xattr_size);
+ TALLOC_FREE(frame);
+ return xattr_size;
+}
+
+static int xattr_tdb_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *name, const void *value,
+ size_t size, int flags)
+{
+ struct xattr_tdb_config *config = NULL;
+ SMB_STRUCT_STAT sbuf;
+ struct file_id id;
+ int ret;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return -1;
+ }
+
+ if (config->ignore_user_xattr && is_user_xattr(name)) {
+ return SMB_VFS_NEXT_FSETXATTR(
+ handle, fsp, name, value, size, flags);
+ }
+
+ if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) {
+ return -1;
+ }
+
+ id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf);
+
+ ret = xattr_tdb_setattr(config->db, &id, name, value, size, flags);
+ return ret;
+
+}
+
+static ssize_t xattr_tdb_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ struct xattr_tdb_config *config = NULL;
+ SMB_STRUCT_STAT sbuf;
+ struct file_id id;
+ ssize_t backend_size;
+ ssize_t ret;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return -1;
+ }
+
+ if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) {
+ return -1;
+ }
+
+ id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf);
+
+ ret = xattr_tdb_listattr(config->db, &id, list, size);
+ if (ret == -1) {
+ return -1;
+ }
+ if (ret == size) {
+ return ret;
+ }
+ if (!config->ignore_user_xattr) {
+ return ret;
+ }
+ SMB_ASSERT(ret < size);
+
+ backend_size = SMB_VFS_NEXT_FLISTXATTR(
+ handle, fsp, list + ret, size - ret);
+ if (backend_size == -1) {
+ return -1;
+ }
+
+ return ret + backend_size;
+}
+
+static int xattr_tdb_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name)
+{
+ struct xattr_tdb_config *config = NULL;
+ SMB_STRUCT_STAT sbuf;
+ struct file_id id;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return -1;
+ }
+
+ if (config->ignore_user_xattr && is_user_xattr(name)) {
+ return SMB_VFS_NEXT_FREMOVEXATTR(handle, fsp, name);
+ }
+
+ if (SMB_VFS_NEXT_FSTAT(handle, fsp, &sbuf) == -1) {
+ return -1;
+ }
+
+ id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &sbuf);
+
+ return xattr_tdb_removeattr(config->db, &id, name);
+}
+
+/*
+ * Destructor for the VFS private data
+ */
+
+static void config_destructor(void **data)
+{
+ struct xattr_tdb_config **config = (struct xattr_tdb_config **)data;
+ TALLOC_FREE((*config)->db);
+}
+
+/*
+ * Open the tdb file upon VFS_CONNECT
+ */
+
+static bool xattr_tdb_init(struct vfs_handle_struct *handle,
+ struct xattr_tdb_config **_config)
+{
+ struct xattr_tdb_config *config = NULL;
+ const char *dbname;
+ char *def_dbname;
+
+ if (SMB_VFS_HANDLE_TEST_DATA(handle)) {
+ SMB_VFS_HANDLE_GET_DATA(handle, config, struct xattr_tdb_config,
+ return false);
+ if (_config != NULL) {
+ *_config = config;
+ }
+ return true;
+ }
+
+ config = talloc_zero(handle->conn, struct xattr_tdb_config);
+ if (config == NULL) {
+ errno = ENOMEM;
+ goto error;
+ }
+
+ def_dbname = state_path(talloc_tos(), "xattr.tdb");
+ if (def_dbname == NULL) {
+ errno = ENOSYS;
+ goto error;
+ }
+
+ dbname = lp_parm_const_string(SNUM(handle->conn),
+ "xattr_tdb",
+ "file",
+ def_dbname);
+
+ /* now we know dbname is not NULL */
+
+ become_root();
+ config->db = db_open(handle, dbname, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_2, DBWRAP_FLAG_NONE);
+ unbecome_root();
+
+ if (config->db == NULL) {
+#if defined(ENOTSUP)
+ errno = ENOTSUP;
+#else
+ errno = ENOSYS;
+#endif
+ TALLOC_FREE(def_dbname);
+ goto error;
+ }
+ TALLOC_FREE(def_dbname);
+
+ config->ignore_user_xattr = lp_parm_bool(
+ SNUM(handle->conn), "xattr_tdb", "ignore_user_xattr", false);
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config, config_destructor,
+ struct xattr_tdb_config, return false);
+
+ if (_config != NULL) {
+ *_config = config;
+ }
+ return true;
+
+error:
+ DBG_WARNING("Failed to initialize config: %s\n", strerror(errno));
+ lp_do_parameter(SNUM(handle->conn), "ea support", "False");
+ return false;
+}
+
+static int xattr_tdb_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ struct xattr_tdb_config *config = NULL;
+ SMB_STRUCT_STAT sbuf;
+ int fd;
+ int ret;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return -1;
+ }
+
+ fd = SMB_VFS_NEXT_OPENAT(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+ if (fd == -1) {
+ return -1;
+ }
+
+ if ((how->flags & (O_CREAT|O_EXCL)) != (O_CREAT|O_EXCL)) {
+ return fd;
+ }
+
+ /*
+ * We know we used O_CREAT|O_EXCL and it worked.
+ * We must have created the file.
+ */
+
+ fsp_set_fd(fsp, fd);
+ ret = SMB_VFS_FSTAT(fsp, &sbuf);
+ fsp_set_fd(fsp, -1);
+ if (ret == -1) {
+ /* Can't happen... */
+ DBG_WARNING("SMB_VFS_FSTAT failed on file %s (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno));
+ return -1;
+ }
+
+ fsp->file_id = SMB_VFS_FILE_ID_CREATE(fsp->conn, &sbuf);
+
+ xattr_tdb_remove_all_attrs(config->db, &fsp->file_id);
+
+ return fd;
+}
+
+static int xattr_tdb_mkdirat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ struct xattr_tdb_config *config = NULL;
+ struct file_id fileid;
+ struct stat_ex sbuf = { .st_ex_nlink = 0, };
+ int ret;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return -1;
+ }
+
+ ret = SMB_VFS_NEXT_MKDIRAT(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+ if (ret < 0) {
+ return ret;
+ }
+
+ ret = SMB_VFS_NEXT_FSTATAT(
+ handle, dirfsp, smb_fname, &sbuf, AT_SYMLINK_NOFOLLOW);
+
+ if (ret == -1) {
+ /* Rename race. Let upper level take care of it. */
+ return -1;
+ }
+ if (!S_ISDIR(sbuf.st_ex_mode)) {
+ /* Rename race. Let upper level take care of it. */
+ return -1;
+ }
+
+ fileid = SMB_VFS_FILE_ID_CREATE(handle->conn, &sbuf);
+
+ xattr_tdb_remove_all_attrs(config->db, &fileid);
+ return 0;
+}
+
+/*
+ * On unlink we need to delete the tdb record
+ */
+static int xattr_tdb_unlinkat(vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ struct xattr_tdb_config *config = NULL;
+ struct smb_filename *smb_fname_tmp = NULL;
+ struct smb_filename *full_fname = NULL;
+ struct file_id id;
+ int ret = -1;
+ bool remove_record = false;
+ TALLOC_CTX *frame = NULL;
+
+ if (!xattr_tdb_init(handle, &config)) {
+ return -1;
+ }
+
+ frame = talloc_stackframe();
+
+ smb_fname_tmp = cp_smb_filename(frame, smb_fname);
+ if (smb_fname_tmp == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * TODO: use SMB_VFS_STATX() once we have that
+ */
+
+ full_fname = full_path_from_dirfsp_atname(frame,
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ goto out;
+ }
+
+ if (full_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ ret = SMB_VFS_NEXT_LSTAT(handle, full_fname);
+ } else {
+ ret = SMB_VFS_NEXT_STAT(handle, full_fname);
+ if (ret == -1 && (errno == ENOENT || errno == ELOOP)) {
+ if (VALID_STAT(smb_fname->st) &&
+ S_ISLNK(smb_fname->st.st_ex_mode)) {
+ /*
+ * Original name was a link - Could be
+ * trying to remove a dangling symlink.
+ */
+ ret = SMB_VFS_NEXT_LSTAT(handle, full_fname);
+ }
+ }
+ }
+ if (ret == -1) {
+ goto out;
+ }
+ smb_fname_tmp->st = full_fname->st;
+
+ if (flags & AT_REMOVEDIR) {
+ /* Always remove record when removing a directory succeeds. */
+ remove_record = true;
+ } else {
+ if (smb_fname_tmp->st.st_ex_nlink == 1) {
+ /* Only remove record on last link to file. */
+ remove_record = true;
+ }
+ }
+
+ ret = SMB_VFS_NEXT_UNLINKAT(handle,
+ dirfsp,
+ smb_fname_tmp,
+ flags);
+
+ if (ret == -1) {
+ goto out;
+ }
+
+ if (!remove_record) {
+ goto out;
+ }
+
+ id = SMB_VFS_NEXT_FILE_ID_CREATE(handle, &smb_fname_tmp->st);
+
+ xattr_tdb_remove_all_attrs(config->db, &id);
+
+ out:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static int xattr_tdb_connect(vfs_handle_struct *handle, const char *service,
+ const char *user)
+{
+ char *sname = NULL;
+ int res, snum;
+
+ res = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (res < 0) {
+ return res;
+ }
+
+ snum = find_service(talloc_tos(), service, &sname);
+ if (snum == -1 || sname == NULL) {
+ /*
+ * Should not happen, but we should not fail just *here*.
+ */
+ return 0;
+ }
+
+ if (!xattr_tdb_init(handle, NULL)) {
+ DEBUG(5, ("Could not init xattr tdb\n"));
+ lp_do_parameter(snum, "ea support", "False");
+ return 0;
+ }
+
+ lp_do_parameter(snum, "ea support", "True");
+
+ return 0;
+}
+
+static struct vfs_fn_pointers vfs_xattr_tdb_fns = {
+ .getxattrat_send_fn = xattr_tdb_getxattrat_send,
+ .getxattrat_recv_fn = xattr_tdb_getxattrat_recv,
+ .fgetxattr_fn = xattr_tdb_fgetxattr,
+ .fsetxattr_fn = xattr_tdb_fsetxattr,
+ .flistxattr_fn = xattr_tdb_flistxattr,
+ .fremovexattr_fn = xattr_tdb_fremovexattr,
+ .openat_fn = xattr_tdb_openat,
+ .mkdirat_fn = xattr_tdb_mkdirat,
+ .unlinkat_fn = xattr_tdb_unlinkat,
+ .connect_fn = xattr_tdb_connect,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_xattr_tdb_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "xattr_tdb",
+ &vfs_xattr_tdb_fns);
+}
diff --git a/source3/modules/vfs_zfsacl.c b/source3/modules/vfs_zfsacl.c
new file mode 100644
index 0000000..695abf1
--- /dev/null
+++ b/source3/modules/vfs_zfsacl.c
@@ -0,0 +1,507 @@
+/*
+ * Convert ZFS/NFSv4 acls to NT acls and vice versa.
+ *
+ * Copyright (C) Jiri Sasek, 2007
+ * based on the foobar.c module which is copyrighted by Volker Lendecke
+ *
+ * Many thanks to Axel Apitz for help to fix the special ace's handling
+ * issues.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "nfs4_acls.h"
+
+#ifdef HAVE_FREEBSD_SUNACL_H
+#include "sunacl.h"
+#endif
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+#define ZFSACL_MODULE_NAME "zfsacl"
+
+struct zfsacl_config_data {
+ struct smbacl4_vfs_params nfs4_params;
+ bool zfsacl_map_dacl_protected;
+ bool zfsacl_denymissingspecial;
+ bool zfsacl_block_special;
+};
+
+/* zfs_get_nt_acl()
+ * read the local file's acls and return it in NT form
+ * using the NFSv4 format conversion
+ */
+static NTSTATUS zfs_get_nt_acl_common(struct connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname,
+ const ace_t *acebuf,
+ int naces,
+ struct SMB4ACL_T **ppacl,
+ struct zfsacl_config_data *config)
+{
+ int i;
+ struct SMB4ACL_T *pacl;
+ SMB_STRUCT_STAT sbuf;
+ const SMB_STRUCT_STAT *psbuf = NULL;
+ int ret;
+ bool inherited_is_present = false;
+ bool is_dir;
+
+ if (VALID_STAT(smb_fname->st)) {
+ psbuf = &smb_fname->st;
+ }
+
+ if (psbuf == NULL) {
+ ret = vfs_stat_smb_basename(conn, smb_fname, &sbuf);
+ if (ret != 0) {
+ DBG_INFO("stat [%s]failed: %s\n",
+ smb_fname_str_dbg(smb_fname), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+ psbuf = &sbuf;
+ }
+ is_dir = S_ISDIR(psbuf->st_ex_mode);
+
+ mem_ctx = talloc_tos();
+
+ /* create SMB4ACL data */
+ if((pacl = smb_create_smb4acl(mem_ctx)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ for(i=0; i<naces; i++) {
+ SMB_ACE4PROP_T aceprop;
+ uint16_t special = 0;
+
+ aceprop.aceType = (uint32_t) acebuf[i].a_type;
+ aceprop.aceFlags = (uint32_t) acebuf[i].a_flags;
+ aceprop.aceMask = (uint32_t) acebuf[i].a_access_mask;
+ aceprop.who.id = (uint32_t) acebuf[i].a_who;
+
+ if (config->zfsacl_block_special &&
+ (aceprop.aceMask == 0) &&
+ (aceprop.aceFlags & ACE_EVERYONE) &&
+ (aceprop.aceFlags & ACE_INHERITED_ACE))
+ {
+ continue;
+ }
+ /*
+ * Windows clients expect SYNC on acls to correctly allow
+ * rename, cf bug #7909. But not on DENY ace entries, cf bug
+ * #8442.
+ */
+ if (aceprop.aceType == SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE) {
+ aceprop.aceMask |= SMB_ACE4_SYNCHRONIZE;
+ }
+
+ special = acebuf[i].a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE);
+
+ if (is_dir &&
+ (aceprop.aceMask & SMB_ACE4_ADD_FILE) &&
+ (special != 0))
+ {
+ aceprop.aceMask |= SMB_ACE4_DELETE_CHILD;
+ }
+
+#ifdef ACE_INHERITED_ACE
+ if (aceprop.aceFlags & ACE_INHERITED_ACE) {
+ inherited_is_present = true;
+ }
+#endif
+ switch(special) {
+ case(ACE_OWNER):
+ aceprop.flags = SMB_ACE4_ID_SPECIAL;
+ aceprop.who.special_id = SMB_ACE4_WHO_OWNER;
+ break;
+ case(ACE_GROUP):
+ aceprop.flags = SMB_ACE4_ID_SPECIAL;
+ aceprop.who.special_id = SMB_ACE4_WHO_GROUP;
+ break;
+ case(ACE_EVERYONE):
+ aceprop.flags = SMB_ACE4_ID_SPECIAL;
+ aceprop.who.special_id = SMB_ACE4_WHO_EVERYONE;
+ break;
+ default:
+ aceprop.flags = 0;
+ }
+ if (smb_add_ace4(pacl, &aceprop) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+#ifdef ACE_INHERITED_ACE
+ if (!inherited_is_present && config->zfsacl_map_dacl_protected) {
+ DBG_DEBUG("Setting SEC_DESC_DACL_PROTECTED on [%s]\n",
+ smb_fname_str_dbg(smb_fname));
+ smbacl4_set_controlflags(pacl,
+ SEC_DESC_DACL_PROTECTED |
+ SEC_DESC_SELF_RELATIVE);
+ }
+#endif
+ *ppacl = pacl;
+ return NT_STATUS_OK;
+}
+
+/* call-back function processing the NT acl -> ZFS acl using NFSv4 conv. */
+static bool zfs_process_smbacl(vfs_handle_struct *handle, files_struct *fsp,
+ struct SMB4ACL_T *smbacl)
+{
+ int naces = smb_get_naces(smbacl), i, rv;
+ ace_t *acebuf;
+ struct SMB4ACE_T *smbace;
+ TALLOC_CTX *mem_ctx;
+ bool have_special_id = false;
+ bool must_add_empty_ace = false;
+ struct zfsacl_config_data *config = NULL;
+ int fd;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct zfsacl_config_data,
+ return False);
+
+ if (config->zfsacl_block_special && S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ naces++;
+ must_add_empty_ace = true;
+ }
+ /* allocate the field of ZFS aces */
+ mem_ctx = talloc_tos();
+ acebuf = (ace_t *) talloc_size(mem_ctx, sizeof(ace_t)*naces);
+ if(acebuf == NULL) {
+ errno = ENOMEM;
+ return False;
+ }
+ /* handle all aces */
+ for(smbace = smb_first_ace4(smbacl), i = 0;
+ smbace!=NULL;
+ smbace = smb_next_ace4(smbace), i++) {
+ SMB_ACE4PROP_T *aceprop = smb_get_ace4(smbace);
+
+ acebuf[i].a_type = aceprop->aceType;
+ acebuf[i].a_flags = aceprop->aceFlags;
+ acebuf[i].a_access_mask = aceprop->aceMask;
+ /* SYNC on acls is a no-op on ZFS.
+ See bug #7909. */
+ acebuf[i].a_access_mask &= ~SMB_ACE4_SYNCHRONIZE;
+ acebuf[i].a_who = aceprop->who.id;
+ if(aceprop->flags & SMB_ACE4_ID_SPECIAL) {
+ switch(aceprop->who.special_id) {
+ case SMB_ACE4_WHO_EVERYONE:
+ acebuf[i].a_flags |= ACE_EVERYONE;
+ break;
+ case SMB_ACE4_WHO_OWNER:
+ acebuf[i].a_flags |= ACE_OWNER;
+ break;
+ case SMB_ACE4_WHO_GROUP:
+ acebuf[i].a_flags |= ACE_GROUP|ACE_IDENTIFIER_GROUP;
+ break;
+ default:
+ DEBUG(8, ("unsupported special_id %d\n", \
+ aceprop->who.special_id));
+ continue; /* don't add it !!! */
+ }
+ have_special_id = true;
+ }
+ }
+ if (must_add_empty_ace) {
+ acebuf[i].a_type = SMB_ACE4_ACCESS_ALLOWED_ACE_TYPE;
+ acebuf[i].a_flags = SMB_ACE4_DIRECTORY_INHERIT_ACE |
+ SMB_ACE4_FILE_INHERIT_ACE |
+ ACE_EVERYONE |
+ ACE_INHERITED_ACE;
+ acebuf[i].a_access_mask = 0;
+ i++;
+ }
+
+ if (!have_special_id && config->zfsacl_denymissingspecial) {
+ errno = EACCES;
+ return false;
+ }
+
+ SMB_ASSERT(i == naces);
+
+ /* store acl */
+ fd = fsp_get_pathref_fd(fsp);
+ if (fd == -1) {
+ errno = EBADF;
+ return false;
+ }
+ rv = facl(fd, ACE_SETACL, naces, acebuf);
+ if (rv != 0) {
+ if(errno == ENOSYS) {
+ DEBUG(9, ("acl(ACE_SETACL, %s): Operation is not "
+ "supported on the filesystem where the file "
+ "resides\n", fsp_str_dbg(fsp)));
+ } else {
+ DEBUG(9, ("acl(ACE_SETACL, %s): %s\n", fsp_str_dbg(fsp),
+ strerror(errno)));
+ }
+ return false;
+ }
+
+ return True;
+}
+
+/* zfs_set_nt_acl()
+ * set the local file's acls obtaining it in NT form
+ * using the NFSv4 format conversion
+ */
+static NTSTATUS zfs_set_nt_acl(vfs_handle_struct *handle, files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ struct zfsacl_config_data *config = NULL;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct zfsacl_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ return smb_set_nt_acl_nfs4(handle,
+ fsp,
+ &config->nfs4_params,
+ security_info_sent,
+ psd,
+ zfs_process_smbacl);
+}
+
+static int fget_zfsacl(TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ ace_t **outbuf)
+{
+ int naces, rv;
+ ace_t *acebuf = NULL;
+ int fd;
+
+ fd = fsp_get_pathref_fd(fsp);
+ if (fd == -1) {
+ errno = EBADF;
+ return -1;
+ }
+ naces = facl(fd, ACE_GETACLCNT, 0, NULL);
+ if (naces == -1) {
+ int dbg_level = 10;
+
+ if (errno == ENOSYS) {
+ dbg_level = 1;
+ }
+ DEBUG(dbg_level, ("facl(ACE_GETACLCNT, %s): %s\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+ return naces;
+ }
+
+ acebuf = talloc_size(mem_ctx, sizeof(ace_t)*naces);
+ if (acebuf == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ rv = facl(fd, ACE_GETACL, naces, acebuf);
+ if (rv == -1) {
+ DBG_DEBUG("acl(ACE_GETACL, %s): %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ return -1;
+ }
+
+ *outbuf = acebuf;
+ return naces;
+}
+
+static NTSTATUS zfsacl_fget_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ TALLOC_CTX *frame = NULL;
+ struct SMB4ACL_T *pacl;
+ NTSTATUS status;
+ struct zfsacl_config_data *config = NULL;
+ ace_t *acebuf = NULL;
+ int naces;
+
+ SMB_VFS_HANDLE_GET_DATA(handle, config,
+ struct zfsacl_config_data,
+ return NT_STATUS_INTERNAL_ERROR);
+
+ frame = talloc_stackframe();
+
+ naces = fget_zfsacl(talloc_tos(), fsp, &acebuf);
+ if (naces == -1) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(frame);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ return status;
+ }
+
+ status = make_default_filesystem_acl(mem_ctx,
+ DEFAULT_ACL_POSIX,
+ fsp->fsp_name->base_name,
+ &fsp->fsp_name->st,
+ ppdesc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ (*ppdesc)->type |= SEC_DESC_DACL_PROTECTED;
+ return NT_STATUS_OK;
+ }
+
+ status = zfs_get_nt_acl_common(handle->conn,
+ frame,
+ fsp->fsp_name,
+ acebuf,
+ naces,
+ &pacl,
+ config);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = smb_fget_nt_acl_nfs4(fsp, NULL, security_info, mem_ctx,
+ ppdesc, pacl);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS zfsacl_fset_nt_acl(vfs_handle_struct *handle,
+ files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ return zfs_set_nt_acl(handle, fsp, security_info_sent, psd);
+}
+
+/* nils.goroll@hamburg.de 2008-06-16 :
+
+ See also
+ - https://bugzilla.samba.org/show_bug.cgi?id=5446
+ - http://bugs.opensolaris.org/view_bug.do?bug_id=6688240
+
+ Solaris supports NFSv4 and ZFS ACLs through a common system call, acl(2)
+ with ACE_SETACL / ACE_GETACL / ACE_GETACLCNT, which is being wrapped for
+ use by samba in this module.
+
+ As the acl(2) interface is identical for ZFS and for NFS, this module,
+ vfs_zfsacl, can not only be used for ZFS, but also for sharing NFSv4
+ mounts on Solaris.
+
+ But while "traditional" POSIX DRAFT ACLs (using acl(2) with SETACL
+ / GETACL / GETACLCNT) fail for ZFS, the Solaris NFS client
+ implements a compatibility wrapper, which will make calls to
+ traditional ACL calls though vfs_solarisacl succeed. As the
+ compatibility wrapper's implementation is (by design) incomplete,
+ we want to make sure that it is never being called.
+
+ As long as Samba does not support an explicit method for a module
+ to define conflicting vfs methods, we should override all conflicting
+ methods here.
+
+ For this to work, we need to make sure that this module is initialised
+ *after* vfs_solarisacl
+
+ Function declarations taken from vfs_solarisacl
+*/
+
+static SMB_ACL_T zfsacl_fail__sys_acl_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ return (SMB_ACL_T)NULL;
+}
+
+static int zfsacl_fail__sys_acl_set_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ return -1;
+}
+
+static int zfsacl_fail__sys_acl_delete_def_fd(vfs_handle_struct *handle,
+ files_struct *fsp)
+{
+ return -1;
+}
+
+static int zfsacl_fail__sys_acl_blob_get_fd(vfs_handle_struct *handle, files_struct *fsp, TALLOC_CTX *mem_ctx, char **blob_description, DATA_BLOB *blob)
+{
+ return -1;
+}
+
+static int zfsacl_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ struct zfsacl_config_data *config = NULL;
+ int ret;
+
+ ret = SMB_VFS_NEXT_CONNECT(handle, service, user);
+ if (ret < 0) {
+ return ret;
+ }
+
+ config = talloc_zero(handle->conn, struct zfsacl_config_data);
+ if (!config) {
+ DBG_ERR("talloc_zero() failed\n");
+ errno = ENOMEM;
+ return -1;
+ }
+
+ config->zfsacl_map_dacl_protected = lp_parm_bool(SNUM(handle->conn),
+ "zfsacl", "map_dacl_protected", false);
+
+ config->zfsacl_denymissingspecial = lp_parm_bool(SNUM(handle->conn),
+ "zfsacl", "denymissingspecial", false);
+
+ config->zfsacl_block_special = lp_parm_bool(SNUM(handle->conn),
+ "zfsacl", "block_special", true);
+
+ ret = smbacl4_get_vfs_params(handle->conn, &config->nfs4_params);
+ if (ret < 0) {
+ TALLOC_FREE(config);
+ return ret;
+ }
+
+ SMB_VFS_HANDLE_SET_DATA(handle, config,
+ NULL, struct zfsacl_config_data,
+ return -1);
+
+ return 0;
+}
+
+/* VFS operations structure */
+
+static struct vfs_fn_pointers zfsacl_fns = {
+ .connect_fn = zfsacl_connect,
+ .stat_fn = nfs4_acl_stat,
+ .fstat_fn = nfs4_acl_fstat,
+ .lstat_fn = nfs4_acl_lstat,
+ .fstatat_fn = nfs4_acl_fstatat,
+ .sys_acl_get_fd_fn = zfsacl_fail__sys_acl_get_fd,
+ .sys_acl_blob_get_fd_fn = zfsacl_fail__sys_acl_blob_get_fd,
+ .sys_acl_set_fd_fn = zfsacl_fail__sys_acl_set_fd,
+ .sys_acl_delete_def_fd_fn = zfsacl_fail__sys_acl_delete_def_fd,
+ .fget_nt_acl_fn = zfsacl_fget_nt_acl,
+ .fset_nt_acl_fn = zfsacl_fset_nt_acl,
+};
+
+static_decl_vfs;
+NTSTATUS vfs_zfsacl_init(TALLOC_CTX *ctx)
+{
+ return smb_register_vfs(SMB_VFS_INTERFACE_VERSION, "zfsacl",
+ &zfsacl_fns);
+}
diff --git a/source3/modules/wscript_build b/source3/modules/wscript_build
new file mode 100644
index 0000000..1f0aa44
--- /dev/null
+++ b/source3/modules/wscript_build
@@ -0,0 +1,639 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_SUBSYSTEM('NFS4_ACLS',
+ source='nfs4_acls.c',
+ deps='samba-util tdb')
+
+bld.SAMBA3_BINARY('test_nfs4_acls',
+ source='test_nfs4_acls.c',
+ deps='smbd_base cmocka',
+ for_selftest=True)
+
+bld.SAMBA3_SUBSYSTEM('vfs_acl_common',
+ source='vfs_acl_common.c',
+ deps='gnutls')
+
+bld.SAMBA3_SUBSYSTEM('POSIXACL_XATTR',
+ source='posixacl_xattr.c',
+ enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_ceph') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_glusterfs')),
+ deps='acl attr')
+
+bld.SAMBA3_SUBSYSTEM('non_posix_acls',
+ source='non_posix_acls.c',
+ deps='samba-util vfs')
+
+bld.SAMBA3_SUBSYSTEM('VFS_VIRUSFILTER_UTILS',
+ source='vfs_virusfilter_utils.c',
+ deps='strv',
+ enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter')))
+
+bld.SAMBA3_SUBSYSTEM('VFS_AIXACL_UTIL',
+ source='vfs_aixacl_util.c',
+ enabled=(bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl') or bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2')))
+
+bld.SAMBA3_SUBSYSTEM('vfs',
+ source='',
+ deps='smbd_base')
+
+bld.SAMBA3_SUBSYSTEM('OFFLOAD_TOKEN',
+ source='offload_token.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('UTIL_REPARSE',
+ source='util_reparse.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('HASH_INODE',
+ source='hash_inode.c',
+ deps='gnutls')
+
+#
+# This is always be static, see
+# source3/wscript: required_static_modules
+#
+bld.SAMBA3_MODULE('vfs_default',
+ subsystem='vfs',
+ source='vfs_default.c',
+ deps='samba-util NDR_DFSBLOBS OFFLOAD_TOKEN UTIL_REPARSE',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_default'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_default'))
+
+#
+# This is always be static, see
+# source3/wscript: required_static_modules
+#
+bld.SAMBA3_MODULE('vfs_not_implemented',
+ subsystem='vfs',
+ source='vfs_not_implemented.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_not_implemented'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_not_implemented'))
+
+bld.SAMBA3_MODULE('vfs_audit',
+ subsystem='vfs',
+ source='vfs_audit.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_audit'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_audit'))
+
+bld.SAMBA3_MODULE('vfs_extd_audit',
+ subsystem='vfs',
+ source='vfs_extd_audit.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_extd_audit'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_extd_audit'))
+
+bld.SAMBA3_MODULE('vfs_full_audit',
+ subsystem='vfs',
+ source='vfs_full_audit.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_full_audit'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_full_audit'))
+
+bld.SAMBA3_BINARY('test_vfs_full_audit',
+ source='test_vfs_full_audit.c',
+ deps='smbd_base cmocka',
+ for_selftest=True)
+
+bld.SAMBA3_MODULE('vfs_fake_perms',
+ subsystem='vfs',
+ source='vfs_fake_perms.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fake_perms'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fake_perms'))
+
+bld.SAMBA3_MODULE('vfs_fake_acls',
+ subsystem='vfs',
+ source='vfs_fake_acls.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fake_acls'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fake_acls'),
+ install=False)
+
+bld.SAMBA3_MODULE('vfs_recycle',
+ subsystem='vfs',
+ source='vfs_recycle.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_recycle'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_recycle'))
+
+bld.SAMBA3_MODULE('vfs_fruit',
+ subsystem='vfs',
+ source='vfs_fruit.c',
+ deps='samba-util OFFLOAD_TOKEN STRING_REPLACE HASH_INODE ADOUBLE',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fruit'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fruit'))
+
+bld.SAMBA3_MODULE('vfs_default_quota',
+ subsystem='vfs',
+ source='vfs_default_quota.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_default_quota'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_default_quota'))
+
+module_cflags=''
+if bld.CONFIG_SET('HAVE_WNO_STRICT_OVERFLOW'):
+ module_cflags += ' -Wno-strict-overflow'
+
+if bld.CONFIG_SET('HAVE_WNO_UNUSED_BUT_SET_VARIABLE'):
+ module_cflags += ' -Wno-unused-but-set-variable'
+
+bld.SAMBA3_MODULE('vfs_readonly',
+ subsystem='vfs',
+ source='vfs_readonly.c getdate.c',
+ deps='samba-util',
+ cflags_end=module_cflags,
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_readonly'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_readonly'))
+
+bld.SAMBA3_MODULE('vfs_cap',
+ subsystem='vfs',
+ source='vfs_cap.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_cap'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_cap'))
+
+bld.SAMBA3_MODULE('vfs_expand_msdfs',
+ subsystem='vfs',
+ source='vfs_expand_msdfs.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_expand_msdfs'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_expand_msdfs'))
+
+bld.SAMBA3_MODULE('vfs_shadow_copy',
+ subsystem='vfs',
+ source='vfs_shadow_copy.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_shadow_copy'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_shadow_copy'))
+
+bld.SAMBA3_MODULE('vfs_shadow_copy2',
+ subsystem='vfs',
+ source='vfs_shadow_copy2.c',
+ allow_warnings=True,
+ deps='samba-util tdb',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_shadow_copy2'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_shadow_copy2'))
+
+bld.SAMBA3_MODULE('vfs_afsacl',
+ subsystem='vfs',
+ source='vfs_afsacl.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_afsacl'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_afsacl'))
+
+bld.SAMBA3_MODULE('vfs_xattr_tdb',
+ subsystem='vfs',
+ source='vfs_xattr_tdb.c',
+ deps='dbwrap xattr_tdb',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_xattr_tdb'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_xattr_tdb'))
+
+bld.SAMBA3_MODULE('vfs_posix_eadb',
+ subsystem='vfs',
+ source='vfs_posix_eadb.c',
+ deps='tdb-wrap posix_eadb',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_posix_eadb') and bld.AD_DC_BUILD_IS_ENABLED(),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_posix_eadb') and bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA3_MODULE('vfs_posixacl',
+ subsystem='vfs',
+ source='vfs_posixacl.c',
+ deps='acl attr',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_posixacl'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_posixacl'))
+
+bld.SAMBA3_BINARY('test_vfs_posixacl',
+ source='test_vfs_posixacl.c',
+ deps='smbd_base cmocka',
+ for_selftest=True)
+
+bld.SAMBA3_MODULE('vfs_aixacl',
+ subsystem='vfs',
+ source='vfs_aixacl.c',
+ deps='VFS_AIXACL_UTIL',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aixacl'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl'))
+
+bld.SAMBA3_MODULE('vfs_aixacl2',
+ subsystem='vfs',
+ source='vfs_aixacl2.c',
+ deps='NFS4_ACLS VFS_AIXACL_UTIL',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aixacl2'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aixacl2'))
+
+bld.SAMBA3_MODULE('vfs_solarisacl',
+ subsystem='vfs',
+ source='vfs_solarisacl.c',
+ init_function='',
+ deps='sec',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_solarisacl'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_solarisacl'))
+
+bld.SAMBA3_MODULE('vfs_zfsacl',
+ subsystem='vfs',
+ source='vfs_zfsacl.c',
+ deps='NFS4_ACLS sunacl',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_zfsacl'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_zfsacl'))
+
+if bld.SAMBA3_IS_ENABLED_MODULE('vfs_nfs4acl_xattr'):
+ bld.SAMBA_GENERATOR('nfs41acl-h',
+ source='nfs41acl.x',
+ target='nfs41acl.h',
+ rule='rpcgen -h ${SRC} > ${TGT}')
+
+ if bld.CONFIG_SET("HAVE_RPC_XDR_H"):
+ xdr_buf_hack = r'sed -e "s@^\([ \t]*register int32_t \*buf\);@\\1 = buf;@"'
+
+ # By default rpcgen assumes that the input file, generated header and
+ # source file are located in the same directory, which is extracted from
+ # the provided path to the input file.
+ # However if the build directory is not under the source tree, ${SRC} will
+ # be a long relative path through a common parent directory, resulting
+ # in an invalid path used in #include for the header.
+ # In order to fix that, the input file is first copied to the output build
+ # directory and then rpcgen is called with the proper path.
+ bld.SAMBA_GENERATOR('nfs41acl-xdr-c',
+ source='nfs41acl.x',
+ target='nfs41acl_xdr.c',
+ rule='cp -f ${SRC} ${TGT[0].parent} && rpcgen -c ' \
+ '${TGT[0].path_from(tsk.get_cwd())[:-len(tsk.outputs[0].name)] + tsk.inputs[0].name} | ' + \
+ xdr_buf_hack + ' > ${TGT}')
+
+ bld.SAMBA_SUBSYSTEM('VFS_NFS4_XDR',
+ source='nfs41acl_xdr.c',
+ deps='NFS4_ACLS NDR_NFS4ACL tirpc')
+ else:
+ bld.SET_TARGET_TYPE('VFS_NFS4_XDR', 'EMPTY')
+
+ bld.SAMBA3_MODULE('vfs_nfs4acl_xattr',
+ subsystem='vfs',
+ source = '''
+ vfs_nfs4acl_xattr.c
+ nfs4acl_xattr_ndr.c
+ nfs4acl_xattr_xdr.c
+ nfs4acl_xattr_nfs.c
+ nfs4acl_xattr_util.c
+ ''',
+ deps='NFS4_ACLS sunacl NDR_NFS4ACL VFS_NFS4_XDR',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_nfs4acl_xattr'))
+
+bld.SAMBA3_MODULE('vfs_hpuxacl',
+ subsystem='vfs',
+ source='vfs_hpuxacl.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_hpuxacl'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_hpuxacl'))
+
+bld.SAMBA3_MODULE('vfs_catia',
+ subsystem='vfs',
+ source='vfs_catia.c',
+ deps='samba-util STRING_REPLACE',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_catia'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_catia'))
+
+bld.SAMBA3_MODULE('vfs_streams_xattr',
+ subsystem='vfs',
+ source='vfs_streams_xattr.c',
+ deps='samba-util HASH_INODE',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_streams_xattr'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_streams_xattr'))
+
+bld.SAMBA3_MODULE('vfs_streams_depot',
+ subsystem='vfs',
+ source='vfs_streams_depot.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_streams_depot'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_streams_depot'))
+
+bld.SAMBA3_MODULE('vfs_cacheprime',
+ subsystem='vfs',
+ source='vfs_cacheprime.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_cacheprime'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_cacheprime'))
+
+bld.SAMBA3_MODULE('vfs_prealloc',
+ subsystem='vfs',
+ source='vfs_prealloc.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_prealloc'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_prealloc'))
+
+bld.SAMBA3_MODULE('vfs_commit',
+ subsystem='vfs',
+ source='vfs_commit.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_commit'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_commit'))
+
+bld.SAMBA3_MODULE('vfs_gpfs',
+ subsystem='vfs',
+ source='vfs_gpfs.c',
+ deps='NFS4_ACLS non_posix_acls gpfswrap',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_gpfs'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_gpfs'),
+ includes=bld.CONFIG_GET('CPPPATH_GPFS'))
+
+bld.SAMBA3_BINARY('test_vfs_gpfs',
+ source='test_vfs_gpfs.c',
+ deps='NFS4_ACLS non_posix_acls gpfswrap cmocka',
+ for_selftest=True,
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_gpfs'),
+ includes=bld.CONFIG_GET('CPPPATH_GPFS'))
+
+bld.SAMBA3_MODULE('vfs_readahead',
+ subsystem='vfs',
+ source='vfs_readahead.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_readahead'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_readahead'))
+
+bld.SAMBA3_MODULE('vfs_tsmsm',
+ subsystem='vfs',
+ source='vfs_tsmsm.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_tsmsm'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_tsmsm'))
+
+bld.SAMBA3_MODULE('vfs_fileid',
+ subsystem='vfs',
+ source='vfs_fileid.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fileid'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fileid'))
+
+bld.SAMBA3_MODULE('vfs_aio_fork',
+ subsystem='vfs',
+ source='vfs_aio_fork.c',
+ deps='samba-util tevent',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aio_fork'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aio_fork'))
+
+bld.SAMBA3_MODULE('vfs_aio_pthread',
+ subsystem='vfs',
+ source='vfs_aio_pthread.c',
+ deps='samba-util tevent',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_aio_pthread'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_aio_pthread'))
+
+bld.SAMBA3_MODULE('vfs_io_uring',
+ subsystem='vfs',
+ source='vfs_io_uring.c',
+ deps='samba-util tevent uring',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_io_uring'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_io_uring'))
+
+bld.SAMBA3_MODULE('vfs_preopen',
+ subsystem='vfs',
+ source='vfs_preopen.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_preopen'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_preopen'))
+
+bld.SAMBA3_MODULE('vfs_syncops',
+ subsystem='vfs',
+ source='vfs_syncops.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_syncops'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_syncops'))
+
+bld.SAMBA3_MODULE('vfs_acl_xattr',
+ subsystem='vfs',
+ source='vfs_acl_xattr.c',
+ deps='samba-util vfs_acl_common',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_acl_xattr'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_acl_xattr'))
+
+bld.SAMBA3_MODULE('vfs_acl_tdb',
+ subsystem='vfs',
+ source='vfs_acl_tdb.c',
+ deps='samba-util vfs_acl_common',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_acl_tdb'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_acl_tdb'))
+
+bld.SAMBA3_MODULE('vfs_dirsort',
+ subsystem='vfs',
+ source='vfs_dirsort.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_dirsort'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_dirsort'))
+
+bld.SAMBA3_MODULE('vfs_crossrename',
+ subsystem='vfs',
+ source='vfs_crossrename.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_crossrename'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_crossrename'))
+
+bld.SAMBA3_MODULE('vfs_linux_xfs_sgid',
+ subsystem='vfs',
+ source='vfs_linux_xfs_sgid.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_linux_xfs_sgid'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_linux_xfs_sgid'))
+
+bld.SAMBA3_MODULE('vfs_time_audit',
+ subsystem='vfs',
+ source='vfs_time_audit.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_time_audit'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_time_audit'))
+
+bld.SAMBA3_MODULE('vfs_media_harmony',
+ subsystem='vfs',
+ source='vfs_media_harmony.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_media_harmony'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_media_harmony'))
+
+bld.SAMBA3_MODULE('vfs_unityed_media',
+ subsystem='vfs',
+ source='vfs_unityed_media.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_unityed_media'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_unityed_media'))
+
+bld.SAMBA3_MODULE('vfs_dfs_samba4',
+ subsystem='vfs',
+ source='vfs_dfs_samba4.c',
+ deps='samba-util dfs_server_ad samdb tevent',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_dfs_samba4') and bld.AD_DC_BUILD_IS_ENABLED(),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_dfs_samba4') and bld.AD_DC_BUILD_IS_ENABLED())
+
+bld.SAMBA3_MODULE('vfs_btrfs',
+ subsystem='vfs',
+ source='vfs_btrfs.c',
+ deps='samba-util OFFLOAD_TOKEN',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_btrfs'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_btrfs'))
+
+bld.SAMBA3_MODULE('vfs_shell_snap',
+ subsystem='vfs',
+ source='vfs_shell_snap.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_shell_snap'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_shell_snap'))
+
+bld.SAMBA3_SUBSYSTEM('perfcount',
+ source='',
+ deps='smbd_base')
+
+bld.SAMBA3_MODULE('vfs_ceph',
+ subsystem='vfs',
+ source='vfs_ceph.c',
+ deps='POSIXACL_XATTR samba-util cephfs',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_ceph'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_ceph'),
+ cflags=bld.CONFIG_GET('CFLAGS_CEPHFS'),
+ includes=bld.CONFIG_GET('CPPPATH_CEPHFS'))
+
+bld.SAMBA3_MODULE('vfs_ceph_snapshots',
+ subsystem='vfs',
+ source='vfs_ceph_snapshots.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_ceph_snapshots'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_ceph_snapshots'))
+
+bld.SAMBA3_MODULE('vfs_glusterfs',
+ subsystem='vfs',
+ source='vfs_glusterfs.c',
+ deps='POSIXACL_XATTR samba-util gfapi',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_glusterfs'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_glusterfs'))
+
+bld.SAMBA3_MODULE('vfs_glusterfs_fuse',
+ subsystem='vfs',
+ source='vfs_glusterfs_fuse.c',
+ deps='',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_glusterfs_fuse'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_glusterfs_fuse'))
+
+bld.SAMBA3_MODULE('vfs_worm',
+ subsystem='vfs',
+ source='vfs_worm.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_worm'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_worm'))
+
+bld.SAMBA3_MODULE('vfs_snapper',
+ subsystem='vfs',
+ source='vfs_snapper.c',
+ deps='samba-util dbus-1',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_snapper'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_snapper'))
+
+bld.SAMBA3_MODULE('vfs_virusfilter',
+ subsystem='vfs',
+ source='''
+ vfs_virusfilter.c
+ vfs_virusfilter_sophos.c
+ vfs_virusfilter_fsav.c
+ vfs_virusfilter_clamav.c
+ vfs_virusfilter_dummy.c
+ ''',
+ deps='samba-util VFS_VIRUSFILTER_UTILS',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_virusfilter'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_virusfilter'))
+
+bld.SAMBA3_MODULE('vfs_vxfs',
+ subsystem='vfs',
+ source='lib_vxfs.c vfs_vxfs.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_vxfs'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_vxfs'))
+
+bld.SAMBA3_MODULE('vfs_offline',
+ subsystem='vfs',
+ source='vfs_offline.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_offline'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_offline'))
+
+bld.SAMBA3_MODULE('vfs_fake_dfq',
+ subsystem='vfs',
+ source='vfs_fake_dfq.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_fake_dfq'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_fake_dfq'),
+ install=False)
+
+bld.SAMBA3_MODULE('vfs_error_inject',
+ subsystem='vfs',
+ source='vfs_error_inject.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_error_inject'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_error_inject'),
+ install=False)
+
+bld.SAMBA3_MODULE('vfs_delay_inject',
+ subsystem='vfs',
+ source='vfs_delay_inject.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_delay_inject'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_delay_inject'),
+ install=False)
+
+bld.SAMBA3_MODULE('vfs_widelinks',
+ subsystem='vfs',
+ source='vfs_widelinks.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('vfs_widelinks'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('vfs_widelinks'))
diff --git a/source3/nmbd/asyncdns.c b/source3/nmbd/asyncdns.c
new file mode 100644
index 0000000..4601ba6
--- /dev/null
+++ b/source3/nmbd/asyncdns.c
@@ -0,0 +1,346 @@
+/*
+ Unix SMB/CIFS implementation.
+ a async DNS handler
+ Copyright (C) Andrew Tridgell 1997-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/sys_rw_data.h"
+
+/***************************************************************************
+ Add a DNS result to the name cache.
+****************************************************************************/
+
+static struct name_record *add_dns_result(struct nmb_name *question, struct in_addr addr)
+{
+ int name_type = question->name_type;
+ unstring qname;
+
+ pull_ascii_nstring(qname, sizeof(qname), question->name);
+
+ if (!addr.s_addr) {
+ /* add the fail to WINS cache of names. give it 1 hour in the cache */
+ DBG_INFO("add_dns_result: Negative DNS answer for %s\n", qname);
+ add_name_to_subnet( wins_server_subnet, qname, name_type,
+ NB_ACTIVE, 60*60, DNSFAIL_NAME, 1, &addr );
+ return NULL;
+ }
+
+ /* add it to our WINS cache of names. give it 2 hours in the cache */
+ DBG_INFO("add_dns_result: DNS gave answer for %s of %s\n", qname, inet_ntoa(addr));
+
+ add_name_to_subnet( wins_server_subnet, qname, name_type,
+ NB_ACTIVE, 2*60*60, DNS_NAME, 1, &addr);
+
+ return find_name_on_subnet(wins_server_subnet, question, FIND_ANY_NAME);
+}
+
+#ifndef SYNC_DNS
+
+static int fd_in = -1, fd_out = -1;
+static pid_t child_pid = -1;
+static int in_dns;
+
+/* this is the structure that is passed between the parent and child */
+struct query_record {
+ struct nmb_name name;
+ struct in_addr result;
+};
+
+/* a queue of pending requests waiting to be sent to the DNS child */
+static struct packet_struct *dns_queue;
+
+/* the packet currently being processed by the dns child */
+static struct packet_struct *dns_current;
+
+
+/***************************************************************************
+ return the fd used to gather async dns replies. This is added to the select
+ loop
+ ****************************************************************************/
+
+int asyncdns_fd(void)
+{
+ return fd_in;
+}
+
+/***************************************************************************
+ handle DNS queries arriving from the parent
+ ****************************************************************************/
+static void asyncdns_process(void)
+{
+ struct query_record r;
+ unstring qname;
+
+ debuglevel_set(-1);
+
+ while (1) {
+ NTSTATUS status;
+
+ status = read_data_ntstatus(fd_in, (char *)&r, sizeof(r));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ pull_ascii_nstring( qname, sizeof(qname), r.name.name);
+ r.result.s_addr = interpret_addr(qname);
+
+ if (write_data(fd_out, (char *)&r, sizeof(r)) != sizeof(r))
+ break;
+ }
+
+ _exit(0);
+}
+
+/**************************************************************************** **
+ catch a sigterm (in the child process - the parent has a different handler
+ see nmbd.c for details).
+ We need a separate term handler here so we don't release any
+ names that our parent is going to release, or overwrite a
+ WINS db that our parent is going to write.
+ **************************************************************************** */
+
+static void sig_term(int sig)
+{
+ _exit(0);
+}
+
+/***************************************************************************
+ Called by the parent process when it receives a SIGTERM - also kills the
+ child so we don't get child async dns processes lying around, causing trouble.
+ ****************************************************************************/
+
+void kill_async_dns_child(void)
+{
+ if (child_pid > 0) {
+ kill(child_pid, SIGTERM);
+ child_pid = -1;
+ }
+}
+
+/***************************************************************************
+ create a child process to handle DNS lookups
+ ****************************************************************************/
+void start_async_dns(struct messaging_context *msg)
+{
+ int fd1[2], fd2[2];
+ NTSTATUS status;
+
+ CatchChild();
+
+ if (pipe(fd1) || pipe(fd2)) {
+ DBG_ERR("can't create asyncdns pipes\n");
+ return;
+ }
+
+ child_pid = fork();
+
+ if (child_pid) {
+ fd_in = fd1[0];
+ fd_out = fd2[1];
+ close(fd1[1]);
+ close(fd2[0]);
+ DBG_NOTICE("started asyncdns process %d\n", (int)child_pid);
+ return;
+ }
+
+ fd_in = fd2[0];
+ fd_out = fd1[1];
+
+ CatchSignal(SIGUSR2, SIG_IGN);
+ CatchSignal(SIGUSR1, SIG_IGN);
+ CatchSignal(SIGHUP, SIG_IGN);
+ CatchSignal(SIGTERM, sig_term);
+
+ status = reinit_after_fork(msg, nmbd_event_context(), true);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("reinit_after_fork() failed\n");
+ smb_panic("reinit_after_fork() failed");
+ }
+
+ asyncdns_process();
+}
+
+
+/***************************************************************************
+check if a particular name is already being queried
+ ****************************************************************************/
+static bool query_current(struct query_record *r)
+{
+ return dns_current &&
+ nmb_name_equal(&r->name,
+ &dns_current->packet.nmb.question.question_name);
+}
+
+
+/***************************************************************************
+ write a query to the child process
+ ****************************************************************************/
+static bool write_child(struct packet_struct *p)
+{
+ struct query_record r;
+
+ r.name = p->packet.nmb.question.question_name;
+
+ return write_data(fd_out, (char *)&r, sizeof(r)) == sizeof(r);
+}
+
+/***************************************************************************
+ check the DNS queue
+ ****************************************************************************/
+void run_dns_queue(struct messaging_context *msg)
+{
+ struct query_record r;
+ struct packet_struct *p, *p2;
+ struct name_record *namerec;
+ NTSTATUS status;
+
+ if (fd_in == -1)
+ return;
+
+ if (!process_exists_by_pid(child_pid)) {
+ close(fd_in);
+ close(fd_out);
+ start_async_dns(msg);
+ }
+
+ status = read_data_ntstatus(fd_in, (char *)&r, sizeof(r));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("read from child failed: %s\n", nt_errstr(status));
+ fd_in = -1;
+ return;
+ }
+
+ namerec = add_dns_result(&r.name, r.result);
+
+ if (dns_current) {
+ if (query_current(&r)) {
+ DBG_INFO("DNS calling send_wins_name_query_response\n");
+ in_dns = 1;
+ if(namerec == NULL)
+ send_wins_name_query_response(NAM_ERR, dns_current, NULL);
+ else
+ send_wins_name_query_response(0,dns_current,namerec);
+ in_dns = 0;
+ }
+
+ dns_current->locked = False;
+ free_packet(dns_current);
+ dns_current = NULL;
+ }
+
+ /* loop over the whole dns queue looking for entries that
+ match the result we just got */
+ for (p = dns_queue; p;) {
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+
+ if (nmb_name_equal(question, &r.name)) {
+ DBG_INFO("DNS calling send_wins_name_query_response\n");
+ in_dns = 1;
+ if(namerec == NULL)
+ send_wins_name_query_response(NAM_ERR, p, NULL);
+ else
+ send_wins_name_query_response(0,p,namerec);
+ in_dns = 0;
+ p->locked = False;
+
+ p2 = p->next;
+ DLIST_REMOVE(dns_queue, p);
+ free_packet(p);
+ p = p2;
+ } else {
+ p = p->next;
+ }
+ }
+
+ if (dns_queue) {
+ dns_current = dns_queue;
+ DLIST_REMOVE(dns_queue, dns_queue);
+
+ if (!write_child(dns_current)) {
+ DBG_NOTICE("failed to send DNS query to child!\n");
+ return;
+ }
+ }
+}
+
+/***************************************************************************
+queue a DNS query
+ ****************************************************************************/
+
+bool queue_dns_query(struct packet_struct *p,struct nmb_name *question)
+{
+ if (in_dns || fd_in == -1)
+ return False;
+
+ if (!dns_current) {
+ if (!write_child(p)) {
+ DBG_NOTICE("failed to send DNS query to child!\n");
+ return False;
+ }
+ dns_current = p;
+ p->locked = True;
+ } else {
+ p->locked = True;
+ DLIST_ADD(dns_queue, p);
+ }
+
+ DBG_NOTICE("added DNS query for %s\n", nmb_namestr(question));
+ return True;
+}
+
+#else
+
+
+/***************************************************************************
+ we use this when we can't do async DNS lookups
+ ****************************************************************************/
+
+bool queue_dns_query(struct packet_struct *p,struct nmb_name *question)
+{
+ struct name_record *namerec = NULL;
+ struct in_addr dns_ip;
+ unstring qname;
+
+ pull_ascii_nstring(qname, sizeof(qname), question->name);
+
+ DBG_NOTICE("DNS search for %s -\n", nmb_namestr(question));
+
+ dns_ip.s_addr = interpret_addr(qname);
+
+ namerec = add_dns_result(question, dns_ip);
+ if(namerec == NULL) {
+ send_wins_name_query_response(NAM_ERR, p, NULL);
+ } else {
+ send_wins_name_query_response(0, p, namerec);
+ }
+ return False;
+}
+
+/***************************************************************************
+ With sync dns there is no child to kill on SIGTERM.
+ ****************************************************************************/
+
+void kill_async_dns_child(void)
+{
+ return;
+}
+#endif
diff --git a/source3/nmbd/nmbd.c b/source3/nmbd/nmbd.c
new file mode 100644
index 0000000..4bdf4b2
--- /dev/null
+++ b/source3/nmbd/nmbd.c
@@ -0,0 +1,1091 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Jeremy Allison 1997-2002
+ Copyright (C) Jelmer Vernooij 2002,2003 (Conversion to popt)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/cmdline/cmdline.h"
+#include "nmbd/nmbd.h"
+#include "serverid.h"
+#include "messages.h"
+#include "../lib/util/pidfile.h"
+#include "util_cluster.h"
+#include "lib/gencache.h"
+#include "lib/global_contexts.h"
+#include "source3/lib/substitute.h"
+
+int ClientNMB = -1;
+int ClientDGRAM = -1;
+int global_nmb_port = -1;
+
+extern bool rescan_listen_set;
+extern bool global_in_nmbd;
+
+/* have we found LanMan clients yet? */
+bool found_lm_clients = False;
+
+/* what server type are we currently */
+
+time_t StartupTime = 0;
+
+struct tevent_context *nmbd_event_context(void)
+{
+ return global_event_context();
+}
+
+/**************************************************************************** **
+ Handle a SIGTERM in band.
+ **************************************************************************** */
+
+static void terminate(struct messaging_context *msg)
+{
+ DBG_WARNING("Got SIGTERM: going down...\n");
+
+ /* Write out wins.dat file if samba is a WINS server */
+ wins_write_database(0,False);
+
+ /* Remove all SELF registered names from WINS */
+ release_wins_names();
+
+ /* Announce all server entries as 0 time-to-live, 0 type. */
+ announce_my_servers_removed();
+
+ /* If there was an async dns child - kill it. */
+ kill_async_dns_child();
+
+ pidfile_unlink(lp_pid_directory(), "nmbd");
+
+ exit(0);
+}
+
+static void nmbd_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct messaging_context *msg = talloc_get_type_abort(
+ private_data, struct messaging_context);
+
+ terminate(msg);
+}
+
+/*
+ handle stdin becoming readable when we are in --foreground mode
+ */
+static void nmbd_stdin_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ char c;
+ if (read(0, &c, 1) != 1) {
+ struct messaging_context *msg = talloc_get_type_abort(
+ private_data, struct messaging_context);
+
+ DBG_WARNING("EOF on stdin\n");
+ terminate(msg);
+ }
+}
+
+static bool nmbd_setup_sig_term_handler(struct messaging_context *msg)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(nmbd_event_context(),
+ nmbd_event_context(),
+ SIGTERM, 0,
+ nmbd_sig_term_handler,
+ msg);
+ if (!se) {
+ DBG_ERR("failed to setup SIGTERM handler\n");
+ return false;
+ }
+
+ return true;
+}
+
+static bool nmbd_setup_stdin_handler(struct messaging_context *msg, bool foreground)
+{
+ if (foreground) {
+ /* if we are running in the foreground then look for
+ EOF on stdin, and exit if it happens. This allows
+ us to die if the parent process dies
+ Only do this on a pipe or socket, no other device.
+ */
+ struct stat st;
+ if (fstat(0, &st) != 0) {
+ return false;
+ }
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) {
+ tevent_add_fd(nmbd_event_context(),
+ nmbd_event_context(),
+ 0,
+ TEVENT_FD_READ,
+ nmbd_stdin_handler,
+ msg);
+ }
+ }
+
+ return true;
+}
+
+static void msg_reload_nmbd_services(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+static void nmbd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct messaging_context *msg = talloc_get_type_abort(
+ private_data, struct messaging_context);
+
+ DBG_WARNING("Got SIGHUP dumping debug info.\n");
+ msg_reload_nmbd_services(msg, NULL, MSG_SMB_CONF_UPDATED,
+ messaging_server_id(msg), NULL);
+}
+
+static bool nmbd_setup_sig_hup_handler(struct messaging_context *msg)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(nmbd_event_context(),
+ nmbd_event_context(),
+ SIGHUP, 0,
+ nmbd_sig_hup_handler,
+ msg);
+ if (!se) {
+ DBG_ERR("failed to setup SIGHUP handler\n");
+ return false;
+ }
+
+ return true;
+}
+
+/**************************************************************************** **
+ Handle a SHUTDOWN message from smbcontrol.
+ **************************************************************************** */
+
+static void nmbd_terminate(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ terminate(msg);
+}
+
+/**************************************************************************** **
+ Expire old names from the namelist and server list.
+ **************************************************************************** */
+
+static void expire_names_and_servers(time_t t)
+{
+ static time_t lastrun = 0;
+
+ if ( !lastrun )
+ lastrun = t;
+ if ( t < (lastrun + 5) )
+ return;
+ lastrun = t;
+
+ /*
+ * Expire any timed out names on all the broadcast
+ * subnets and those registered with the WINS server.
+ * (nmbd_namelistdb.c)
+ */
+
+ expire_names(t);
+
+ /*
+ * Go through all the broadcast subnets and for each
+ * workgroup known on that subnet remove any expired
+ * server names. If a workgroup has an empty serverlist
+ * and has itself timed out then remove the workgroup.
+ * (nmbd_workgroupdb.c)
+ */
+
+ expire_workgroups_and_servers(t);
+}
+
+/************************************************************************** **
+ Reload the list of network interfaces.
+ Doesn't return until a network interface is up.
+ ************************************************************************** */
+
+static void reload_interfaces(time_t t)
+{
+ static time_t lastt;
+ int n;
+ bool print_waiting_msg = true;
+ struct subnet_record *subrec;
+
+ if (t && ((t - lastt) < NMBD_INTERFACES_RELOAD)) {
+ return;
+ }
+
+ lastt = t;
+
+ if (!interfaces_changed()) {
+ return;
+ }
+
+ try_again:
+
+ /* the list of probed interfaces has changed, we may need to add/remove
+ some subnets */
+ load_interfaces();
+
+ /* find any interfaces that need adding */
+ for (n=iface_count() - 1; n >= 0; n--) {
+ char str[INET6_ADDRSTRLEN];
+ const struct interface *iface = get_interface(n);
+ struct in_addr ip, nmask;
+
+ if (!iface) {
+ DBG_WARNING("reload_interfaces: failed to get interface %d\n", n);
+ continue;
+ }
+
+ /* Ensure we're only dealing with IPv4 here. */
+ if (iface->ip.ss_family != AF_INET) {
+ DBG_NOTICE("reload_interfaces: "
+ "ignoring non IPv4 interface.\n");
+ continue;
+ }
+
+ ip = ((const struct sockaddr_in *)(const void *)&iface->ip)->sin_addr;
+ nmask = ((const struct sockaddr_in *)(const void *)
+ &iface->netmask)->sin_addr;
+
+ /*
+ * We don't want to add a loopback interface, in case
+ * someone has added 127.0.0.1 for smbd, nmbd needs to
+ * ignore it here. JRA.
+ */
+
+ if (is_loopback_addr((const struct sockaddr *)(const void *)&iface->ip)) {
+ DBG_NOTICE("reload_interfaces: Ignoring loopback "
+ "interface %s\n",
+ print_sockaddr(str, sizeof(str), &iface->ip) );
+ continue;
+ }
+
+ for (subrec=subnetlist; subrec; subrec=subrec->next) {
+ if (ip_equal_v4(ip, subrec->myip) &&
+ ip_equal_v4(nmask, subrec->mask_ip)) {
+ break;
+ }
+ }
+
+ if (!subrec) {
+ /* it wasn't found! add it */
+ DBG_NOTICE("Found new interface %s\n",
+ print_sockaddr(str,
+ sizeof(str), &iface->ip) );
+ subrec = make_normal_subnet(iface);
+ if (subrec)
+ register_my_workgroup_one_subnet(subrec);
+ }
+ }
+
+ /* find any interfaces that need deleting */
+ for (subrec=subnetlist; subrec; subrec=subrec->next) {
+ for (n=iface_count() - 1; n >= 0; n--) {
+ struct interface *iface = get_interface(n);
+ struct in_addr ip, nmask;
+ if (!iface) {
+ continue;
+ }
+ /* Ensure we're only dealing with IPv4 here. */
+ if (iface->ip.ss_family != AF_INET) {
+ DBG_NOTICE("reload_interfaces: "
+ "ignoring non IPv4 interface.\n");
+ continue;
+ }
+ ip = ((struct sockaddr_in *)(void *)
+ &iface->ip)->sin_addr;
+ nmask = ((struct sockaddr_in *)(void *)
+ &iface->netmask)->sin_addr;
+ if (ip_equal_v4(ip, subrec->myip) &&
+ ip_equal_v4(nmask, subrec->mask_ip)) {
+ break;
+ }
+ }
+ if (n == -1) {
+ /* oops, an interface has disappeared. This is
+ tricky, we don't dare actually free the
+ interface as it could be being used, so
+ instead we just wear the memory leak and
+ remove it from the list of interfaces without
+ freeing it */
+ DBG_NOTICE("Deleting dead interface %s\n",
+ inet_ntoa(subrec->myip));
+ close_subnet(subrec);
+ }
+ }
+
+ rescan_listen_set = True;
+
+ /* We need to wait if there are no subnets... */
+ if (FIRST_SUBNET == NULL) {
+ void (*saved_handler)(int);
+
+ if (print_waiting_msg) {
+ DBG_WARNING("reload_interfaces: "
+ "No subnets to listen to. Waiting..\n");
+ print_waiting_msg = false;
+ }
+
+ /*
+ * Whilst we're waiting for an interface, allow SIGTERM to
+ * cause us to exit.
+ */
+ saved_handler = CatchSignal(SIGTERM, SIG_DFL);
+
+ /* We only count IPv4, non-loopback interfaces here. */
+ while (iface_count_v4_nl() == 0) {
+ usleep(NMBD_WAIT_INTERFACES_TIME_USEC);
+ load_interfaces();
+ }
+
+ CatchSignal(SIGTERM, saved_handler);
+
+ /*
+ * We got an interface, go back to blocking term.
+ */
+
+ goto try_again;
+ }
+}
+
+/**************************************************************************** **
+ Reload the services file.
+ **************************************************************************** */
+
+static bool reload_nmbd_services(bool test)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ bool ret;
+
+ set_remote_machine_name("nmbd", False);
+
+ if ( lp_loaded() ) {
+ char *fname = lp_next_configfile(talloc_tos(), lp_sub);
+ if (file_exist(fname) && !strcsequal(fname,get_dyn_CONFIGFILE())) {
+ set_dyn_CONFIGFILE(fname);
+ test = False;
+ }
+ TALLOC_FREE(fname);
+ }
+
+ if ( test && !lp_file_list_changed() )
+ return(True);
+
+ ret = lp_load_global(get_dyn_CONFIGFILE());
+
+ /* perhaps the config filename is now set */
+ if ( !test ) {
+ DBG_NOTICE( "services not loaded\n" );
+ reload_nmbd_services( True );
+ }
+
+ reopen_logs();
+
+ return(ret);
+}
+
+/**************************************************************************** **
+ * React on 'smbcontrol nmbd reload-config' in the same way as to SIGHUP
+ **************************************************************************** */
+
+static void msg_reload_nmbd_services(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ write_browse_list( 0, True );
+ dump_all_namelists();
+ reload_nmbd_services( True );
+ reopen_logs();
+ reload_interfaces(0);
+ nmbd_init_my_netbios_names();
+}
+
+static void msg_nmbd_send_packet(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct packet_struct *p = (struct packet_struct *)data->data;
+ struct subnet_record *subrec;
+ struct sockaddr_storage ss;
+ const struct sockaddr_storage *pss;
+ const struct in_addr *local_ip;
+
+ DBG_DEBUG("Received send_packet from %u\n", (unsigned int)procid_to_pid(&src));
+
+ if (data->length != sizeof(struct packet_struct)) {
+ DBG_WARNING("Discarding invalid packet length from %u\n",
+ (unsigned int)procid_to_pid(&src));
+ return;
+ }
+
+ if ((p->packet_type != NMB_PACKET) &&
+ (p->packet_type != DGRAM_PACKET)) {
+ DBG_WARNING("Discarding invalid packet type from %u: %d\n",
+ (unsigned int)procid_to_pid(&src), p->packet_type);
+ return;
+ }
+
+ in_addr_to_sockaddr_storage(&ss, p->ip);
+ pss = iface_ip((struct sockaddr *)(void *)&ss);
+
+ if (pss == NULL) {
+ DBG_WARNING("Could not find ip for packet from %u\n",
+ (unsigned int)procid_to_pid(&src));
+ return;
+ }
+
+ local_ip = &((const struct sockaddr_in *)pss)->sin_addr;
+ subrec = FIRST_SUBNET;
+
+ p->recv_fd = -1;
+ p->send_fd = (p->packet_type == NMB_PACKET) ?
+ subrec->nmb_sock : subrec->dgram_sock;
+
+ for (subrec = FIRST_SUBNET; subrec != NULL;
+ subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ if (ip_equal_v4(*local_ip, subrec->myip)) {
+ p->send_fd = (p->packet_type == NMB_PACKET) ?
+ subrec->nmb_sock : subrec->dgram_sock;
+ break;
+ }
+ }
+
+ if (p->packet_type == DGRAM_PACKET) {
+ p->port = 138;
+ p->packet.dgram.header.source_ip.s_addr = local_ip->s_addr;
+ p->packet.dgram.header.source_port = 138;
+ }
+
+ send_packet(p);
+}
+
+/**************************************************************************** **
+ The main select loop.
+ **************************************************************************** */
+
+static void process(struct messaging_context *msg)
+{
+ bool run_election;
+
+ while( True ) {
+ time_t t = time(NULL);
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /*
+ * Check all broadcast subnets to see if
+ * we need to run an election on any of them.
+ * (nmbd_elections.c)
+ */
+
+ run_election = check_elections();
+
+ /*
+ * Read incoming UDP packets.
+ * (nmbd_packets.c)
+ */
+
+ if (listen_for_packets(msg, run_election)) {
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ /*
+ * Process all incoming packets
+ * read above. This calls the success and
+ * failure functions registered when response
+ * packets arrive, and also deals with request
+ * packets from other sources.
+ * (nmbd_packets.c)
+ */
+
+ run_packet_queue();
+
+ /*
+ * Run any elections - initiate becoming
+ * a local master browser if we have won.
+ * (nmbd_elections.c)
+ */
+
+ run_elections(t);
+
+ /*
+ * Send out any broadcast announcements
+ * of our server names. This also announces
+ * the workgroup name if we are a local
+ * master browser.
+ * (nmbd_sendannounce.c)
+ */
+
+ announce_my_server_names(t);
+
+ /*
+ * Send out any LanMan broadcast announcements
+ * of our server names.
+ * (nmbd_sendannounce.c)
+ */
+
+ announce_my_lm_server_names(t);
+
+ /*
+ * If we are a local master browser, periodically
+ * announce ourselves to the domain master browser.
+ * This also deals with synchronising the domain master
+ * browser server lists with ourselves as a local
+ * master browser.
+ * (nmbd_sendannounce.c)
+ */
+
+ announce_myself_to_domain_master_browser(t);
+
+ /*
+ * Fulfill any remote announce requests.
+ * (nmbd_sendannounce.c)
+ */
+
+ announce_remote(t);
+
+ /*
+ * Fulfill any remote browse sync announce requests.
+ * (nmbd_sendannounce.c)
+ */
+
+ browse_sync_remote(t);
+
+ /*
+ * Scan the broadcast subnets, and WINS client
+ * namelists and refresh any that need refreshing.
+ * (nmbd_mynames.c)
+ */
+
+ refresh_my_names(t);
+
+ /*
+ * Scan the subnet namelists and server lists and
+ * expire those that have timed out.
+ * (nmbd.c)
+ */
+
+ expire_names_and_servers(t);
+
+ /*
+ * Write out a snapshot of our current browse list into
+ * the browse.dat file. This is used by smbd to service
+ * incoming NetServerEnum calls - used to synchronise
+ * browse lists over subnets.
+ * (nmbd_serverlistdb.c)
+ */
+
+ write_browse_list(t, False);
+
+ /*
+ * If we are a domain master browser, we have a list of
+ * local master browsers we should synchronise browse
+ * lists with (these are added by an incoming local
+ * master browser announcement packet). Expire any of
+ * these that are no longer current, and pull the server
+ * lists from each of these known local master browsers.
+ * (nmbd_browsesync.c)
+ */
+
+ dmb_expire_and_sync_browser_lists(t);
+
+ /*
+ * Check that there is a local master browser for our
+ * workgroup for all our broadcast subnets. If one
+ * is not found, start an election (which we ourselves
+ * may or may not participate in, depending on the
+ * setting of the 'local master' parameter.
+ * (nmbd_elections.c)
+ */
+
+ check_master_browser_exists(t);
+
+ /*
+ * If we are configured as a logon server, attempt to
+ * register the special NetBIOS names to become such
+ * (WORKGROUP<1c> name) on all broadcast subnets and
+ * with the WINS server (if used). If we are configured
+ * to become a domain master browser, attempt to register
+ * the special NetBIOS name (WORKGROUP<1b> name) to
+ * become such.
+ * (nmbd_become_dmb.c)
+ */
+
+ add_domain_names(t);
+
+ /*
+ * If we are a WINS server, do any timer dependent
+ * processing required.
+ * (nmbd_winsserver.c)
+ */
+
+ initiate_wins_processing(t);
+
+ /*
+ * If we are a domain master browser, attempt to contact the
+ * WINS server to get a list of all known WORKGROUPS/DOMAINS.
+ * This will only work to a Samba WINS server.
+ * (nmbd_browsesync.c)
+ */
+
+ if (lp_enhanced_browsing())
+ collect_all_workgroup_names_from_wins_server(t);
+
+ /*
+ * Go through the response record queue and time out or re-transmit
+ * and expired entries.
+ * (nmbd_packets.c)
+ */
+
+ retransmit_or_expire_response_records(t);
+
+ /*
+ * check to see if any remote browse sync child processes have completed
+ */
+
+ sync_check_completion();
+
+ /*
+ * regularly sync with any other DMBs we know about
+ */
+
+ if (lp_enhanced_browsing())
+ sync_all_dmbs(t);
+
+ /* check for new network interfaces */
+
+ reload_interfaces(t);
+
+ /* free up temp memory */
+ TALLOC_FREE(frame);
+ }
+}
+
+/**************************************************************************** **
+ Open the socket communication.
+ **************************************************************************** */
+
+static bool open_sockets(bool isdaemon, int port)
+{
+ struct sockaddr_storage ss;
+ const char *sock_addr = lp_nbt_client_socket_address();
+
+ /*
+ * The sockets opened here will be used to receive broadcast
+ * packets *only*. Interface specific sockets are opened in
+ * make_subnet() in namedbsubnet.c. Thus we bind to the
+ * address "0.0.0.0". The parameter 'socket address' is
+ * now deprecated.
+ */
+
+ if (!interpret_string_addr(&ss, sock_addr,
+ AI_NUMERICHOST|AI_PASSIVE)) {
+ DBG_ERR("open_sockets: unable to get socket address "
+ "from string %s\n", sock_addr);
+ return false;
+ }
+ if (ss.ss_family != AF_INET) {
+ DBG_ERR("open_sockets: unable to use IPv6 socket"
+ "%s in nmbd\n",
+ sock_addr);
+ return false;
+ }
+
+ if (isdaemon) {
+ ClientNMB = open_socket_in(SOCK_DGRAM, &ss, port, true);
+ } else {
+ ClientNMB = 0;
+ }
+
+ if (ClientNMB < 0) {
+ return false;
+ }
+
+ ClientDGRAM = open_socket_in(SOCK_DGRAM, &ss, DGRAM_PORT, true);
+
+ if (ClientDGRAM < 0) {
+ if (ClientNMB != 0) {
+ close(ClientNMB);
+ }
+ return false;
+ }
+
+ /* we are never interested in SIGPIPE */
+ BlockSignals(True,SIGPIPE);
+
+ set_socket_options( ClientNMB, "SO_BROADCAST" );
+ set_socket_options( ClientDGRAM, "SO_BROADCAST" );
+
+ /* Ensure we're non-blocking. */
+ set_blocking( ClientNMB, False);
+ set_blocking( ClientDGRAM, False);
+
+ DBG_INFO( "open_sockets: Broadcast sockets opened.\n" );
+ return( True );
+}
+
+/**************************************************************************** **
+ main program
+ **************************************************************************** */
+
+ int main(int argc, const char *argv[])
+{
+ struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
+ bool log_stdout = false;
+ poptContext pc;
+ char *p_lmhosts = NULL;
+ int opt;
+ struct messaging_context *msg;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "hosts",
+ .shortName = 'H',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &p_lmhosts,
+ .val = 0,
+ .descrip = "Load a netbios hosts file",
+ },
+ {
+ .longName = "port",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_INT,
+ .arg = &global_nmb_port,
+ .val = 0,
+ .descrip = "Listen on the specified port",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DAEMON
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Do this before any other talloc operation
+ */
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+
+ /*
+ * We want total control over the permissions on created files,
+ * so set our umask to 0.
+ */
+ umask(0);
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(ENOMEM);
+ }
+
+ cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
+
+ global_nmb_port = NMB_PORT;
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ d_fprintf(stderr, "\nInvalid options\n\n");
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ };
+ poptFreeContext(pc);
+
+ global_in_nmbd = true;
+
+ StartupTime = time(NULL);
+
+ sys_srandom(time(NULL) ^ getpid());
+
+ if (is_default_dyn_LOGFILEBASE()) {
+ char *lfile = NULL;
+ if (asprintf(&lfile, "%s/log.nmbd", get_dyn_LOGFILEBASE()) < 0) {
+ exit(1);
+ }
+ lp_set_logfile(lfile);
+ SAFE_FREE(lfile);
+ }
+
+ dump_core_setup("nmbd", lp_logfile(talloc_tos(), lp_sub));
+
+ /* POSIX demands that signals are inherited. If the invoking process has
+ * these signals masked, we will have problems, as we won't receive them. */
+ BlockSignals(False, SIGHUP);
+ BlockSignals(False, SIGUSR1);
+ BlockSignals(False, SIGTERM);
+
+#if defined(SIGFPE)
+ /* we are never interested in SIGFPE */
+ BlockSignals(True,SIGFPE);
+#endif
+
+ /* We no longer use USR2... */
+#if defined(SIGUSR2)
+ BlockSignals(True, SIGUSR2);
+#endif
+
+ /* Ignore children - no zombies. */
+ CatchChild();
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+ if ( cmdline_daemon_cfg->interactive ) {
+ log_stdout = True;
+ }
+
+ if ( log_stdout && cmdline_daemon_cfg->fork ) {
+ DBG_ERR("ERROR: Can't log to stdout (-S) unless daemon is in foreground (-F) or interactive (-i)\n");
+ exit(1);
+ }
+
+ reopen_logs();
+
+ DBG_STARTUP_NOTICE("nmbd version %s started.\n%s\n",
+ samba_version_string(),
+ samba_copyright_string());
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC
+ && !lp_parm_bool(-1, "server role check", "inhibit", false)) {
+ /* TODO: when we have a merged set of defaults for
+ * loadparm, we could possibly check if the internal
+ * nbt server is in the list, and allow a startup if disabled */
+ DBG_ERR("server role = 'active directory domain controller' not compatible with running nmbd standalone.\n"
+ "You should start 'samba' instead, and it will control starting the internal nbt server\n");
+ exit(1);
+ }
+
+ if (!cluster_probe_ok()) {
+ exit(1);
+ }
+
+ msg = messaging_init(NULL, global_event_context());
+ if (msg == NULL) {
+ DBG_ERR("Failed to init messaging context!\n");
+ return 1;
+ }
+
+ if ( !reload_nmbd_services(False) )
+ return(-1);
+
+ if (!nmbd_init_my_netbios_names()) {
+ return -1;
+ }
+
+ reload_nmbd_services( True );
+
+ if (strequal(lp_workgroup(),"*")) {
+ DBG_ERR("ERROR: a workgroup name of * is no longer supported\n");
+ exit(1);
+ }
+
+ set_samba_nb_type();
+
+ if (!cmdline_daemon_cfg->daemon && !is_a_socket(0)) {
+ DBG_NOTICE("standard input is not a socket, assuming -D option\n");
+ cmdline_daemon_cfg->daemon = true;
+ }
+
+ if (cmdline_daemon_cfg->daemon && !cmdline_daemon_cfg->interactive) {
+ DBG_NOTICE("Becoming a daemon.\n");
+ become_daemon(cmdline_daemon_cfg->fork,
+ cmdline_daemon_cfg->no_process_group,
+ log_stdout);
+ } else if (!cmdline_daemon_cfg->interactive) {
+ daemon_status("nmbd", "Starting process...");
+ }
+
+#ifdef HAVE_SETPGID
+ /*
+ * If we're interactive we want to set our own process group for
+ * signal management.
+ */
+ if (cmdline_daemon_cfg->interactive &&
+ !cmdline_daemon_cfg->no_process_group)
+ {
+ setpgid( (pid_t)0, (pid_t)0 );
+ }
+#endif
+
+#ifndef SYNC_DNS
+ /* Setup the async dns. We do it here so it doesn't have all the other
+ stuff initialised and thus chewing memory and sockets */
+ if(lp_we_are_a_wins_server() && lp_wins_dns_proxy()) {
+ start_async_dns(msg);
+ }
+#endif
+
+ ok = directory_create_or_exist(lp_lock_directory(), 0755);
+ if (!ok) {
+ exit_daemon("Failed to create directory for lock files, check 'lock directory'", errno);
+ }
+
+ ok = directory_create_or_exist(lp_pid_directory(), 0755);
+ if (!ok) {
+ exit_daemon("Failed to create directory for pid files, check 'pid directory'", errno);
+ }
+
+ pidfile_create(lp_pid_directory(), "nmbd");
+
+ status = reinit_after_fork(msg, nmbd_event_context(), false);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("reinit_after_fork() failed", map_errno_from_nt_status(status));
+ }
+
+ /*
+ * Do not initialize the parent-child-pipe before becoming
+ * a daemon: this is used to detect a died parent in the child
+ * process.
+ */
+ status = init_before_fork();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon(nt_errstr(status), map_errno_from_nt_status(status));
+ }
+
+ if (!nmbd_setup_sig_term_handler(msg))
+ exit_daemon("NMBD failed to setup signal handler", EINVAL);
+ if (!nmbd_setup_stdin_handler(msg, !cmdline_daemon_cfg->fork))
+ exit_daemon("NMBD failed to setup stdin handler", EINVAL);
+ if (!nmbd_setup_sig_hup_handler(msg))
+ exit_daemon("NMBD failed to setup SIGHUP handler", EINVAL);
+
+ if (!messaging_parent_dgm_cleanup_init(msg)) {
+ exit(1);
+ }
+
+ messaging_register(msg, NULL, MSG_FORCE_ELECTION,
+ nmbd_message_election);
+#if 0
+ /* Until winsrepl is done. */
+ messaging_register(msg, NULL, MSG_WINS_NEW_ENTRY,
+ nmbd_wins_new_entry);
+#endif
+ messaging_register(msg, NULL, MSG_SHUTDOWN,
+ nmbd_terminate);
+ messaging_register(msg, NULL, MSG_SMB_CONF_UPDATED,
+ msg_reload_nmbd_services);
+ messaging_register(msg, NULL, MSG_SEND_PACKET,
+ msg_nmbd_send_packet);
+
+ TimeInit();
+
+ DBG_NOTICE("Opening sockets %d\n", global_nmb_port);
+
+ if ( !open_sockets( cmdline_daemon_cfg->daemon, global_nmb_port ) ) {
+ kill_async_dns_child();
+ return 1;
+ }
+
+ /* Determine all the IP addresses we have. */
+ load_interfaces();
+
+ /* Create an nmbd subnet record for each of the above. */
+ if( False == create_subnets() ) {
+ kill_async_dns_child();
+ exit_daemon("NMBD failed when creating subnet lists", EACCES);
+ }
+
+ /* Load in any static local names. */
+ if (p_lmhosts) {
+ set_dyn_LMHOSTSFILE(p_lmhosts);
+ }
+ load_lmhosts_file(get_dyn_LMHOSTSFILE());
+ DBG_NOTICE("Loaded hosts file %s\n", get_dyn_LMHOSTSFILE());
+
+ /* If we are acting as a WINS server, initialise data structures. */
+ if( !initialise_wins() ) {
+ kill_async_dns_child();
+ exit_daemon( "NMBD failed when initialising WINS server.", EACCES);
+ }
+
+ /*
+ * Register nmbd primary workgroup and nmbd names on all
+ * the broadcast subnets, and on the WINS server (if specified).
+ * Also initiate the startup of our primary workgroup (start
+ * elections if we are setup as being able to be a local
+ * master browser.
+ */
+
+ if( False == register_my_workgroup_and_names() ) {
+ kill_async_dns_child();
+ exit_daemon( "NMBD failed when creating my workgroup.", EACCES);
+ }
+
+ if (!initialize_nmbd_proxy_logon()) {
+ kill_async_dns_child();
+ exit_daemon( "NMBD failed to setup nmbd_proxy_logon.", EACCES);
+ }
+
+ if (!nmbd_init_packet_server()) {
+ kill_async_dns_child();
+ exit_daemon( "NMBD failed to setup packet server.", EACCES);
+ }
+
+ if (!cmdline_daemon_cfg->interactive) {
+ daemon_ready("nmbd");
+ }
+
+ TALLOC_FREE(frame);
+ process(msg);
+
+ kill_async_dns_child();
+ return(0);
+}
diff --git a/source3/nmbd/nmbd.h b/source3/nmbd/nmbd.h
new file mode 100644
index 0000000..f207eb9
--- /dev/null
+++ b/source3/nmbd/nmbd.h
@@ -0,0 +1,39 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NBT netbios routines and daemon - version 2
+ *
+ * Copyright (C) Guenther Deschner 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NMBD_NMBD_H_
+#define _NMBD_NMBD_H_
+
+#ifndef HAVE_PIPE
+#define SYNC_DNS 1
+#endif
+
+#include "libsmb/nmblib.h"
+#include "nmbd/nmbd_proto.h"
+
+#define NMBD_WAIT_INTERFACES_TIME_USEC (250 * 1000)
+
+/****************************************************************************
+true if two IPv4 addresses are equal
+****************************************************************************/
+
+#define ip_equal_v4(ip1,ip2) ((ip1).s_addr == (ip2).s_addr)
+
+#endif /* _NMBD_NMBD_H_ */
diff --git a/source3/nmbd/nmbd_become_dmb.c b/source3/nmbd/nmbd_become_dmb.c
new file mode 100644
index 0000000..d006a6f
--- /dev/null
+++ b/source3/nmbd/nmbd_become_dmb.c
@@ -0,0 +1,398 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "nmbd/nmbd.h"
+
+extern uint16_t samba_nb_type; /* Samba's NetBIOS type. */
+
+static void become_domain_master_browser_bcast(const char *);
+
+/****************************************************************************
+ Fail to become a Domain Master Browser on a subnet.
+ ****************************************************************************/
+
+static void become_domain_master_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *fail_name)
+{
+ unstring failname;
+ struct work_record *work;
+ struct server_record *servrec;
+
+ pull_ascii_nstring(failname, sizeof(failname), fail_name->name);
+ work = find_workgroup_on_subnet(subrec, failname);
+ if(!work) {
+ DEBUG(0,("become_domain_master_fail: Error - cannot find \
+workgroup %s on subnet %s\n", failname, subrec->subnet_name));
+ return;
+ }
+
+ /* Set the state back to DOMAIN_NONE. */
+ work->dom_state = DOMAIN_NONE;
+
+ if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) {
+ DEBUG(0,("become_domain_master_fail: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), work->work_group, subrec->subnet_name));
+ return;
+ }
+
+ /* Update our server status. */
+ servrec->serv.type &= ~SV_TYPE_DOMAIN_MASTER;
+
+ /* Tell the namelist writer to write out a change. */
+ subrec->work_changed = True;
+
+ DEBUG(0,("become_domain_master_fail: Failed to become a domain master browser for \
+workgroup %s on subnet %s. Couldn't register name %s.\n",
+ work->work_group, subrec->subnet_name, nmb_namestr(fail_name)));
+}
+
+/****************************************************************************
+ Become a Domain Master Browser on a subnet.
+ ****************************************************************************/
+
+static void become_domain_master_stage2(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *registered_name,
+ uint16_t nb_flags,
+ int ttl, struct in_addr registered_ip)
+{
+ unstring regname;
+ struct work_record *work;
+ struct server_record *servrec;
+
+ pull_ascii_nstring(regname, sizeof(regname), registered_name->name);
+ work = find_workgroup_on_subnet( subrec, regname);
+
+ if(!work) {
+ DEBUG(0,("become_domain_master_stage2: Error - cannot find \
+workgroup %s on subnet %s\n", regname, subrec->subnet_name));
+ return;
+ }
+
+ if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) {
+ DEBUG(0,("become_domain_master_stage2: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), regname, subrec->subnet_name));
+ work->dom_state = DOMAIN_NONE;
+ return;
+ }
+
+ /* Set the state in the workgroup structure. */
+ work->dom_state = DOMAIN_MST; /* Become domain master. */
+
+ /* Update our server status. */
+ servrec->serv.type |= (SV_TYPE_NT|SV_TYPE_DOMAIN_MASTER);
+
+ /* Tell the namelist writer to write out a change. */
+ subrec->work_changed = True;
+
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "*****\n\nSamba server %s ", lp_netbios_name() );
+ dbgtext( "is now a domain master browser for " );
+ dbgtext( "workgroup %s ", work->work_group );
+ dbgtext( "on subnet %s\n\n*****\n", subrec->subnet_name );
+ }
+
+ if( subrec == unicast_subnet ) {
+ struct nmb_name nmbname;
+ struct in_addr my_first_ip;
+ const struct in_addr *nip;
+
+ /* Put our name and first IP address into the
+ workgroup struct as domain master browser. This
+ will stop us syncing with ourself if we are also
+ a local master browser. */
+
+ make_nmb_name(&nmbname, lp_netbios_name(), 0x20);
+
+ work->dmb_name = nmbname;
+
+ /* Pick the first interface IPv4 address as the domain master
+ * browser ip. */
+ nip = first_ipv4_iface();
+ if (!nip) {
+ DEBUG(0,("become_domain_master_stage2: "
+ "Error. get_interface returned NULL\n"));
+ return;
+ }
+ my_first_ip = *nip;
+
+ putip((char *)&work->dmb_addr, &my_first_ip);
+
+ /* We successfully registered by unicast with the
+ WINS server. We now expect to become the domain
+ master on the local subnets. If this fails, it's
+ probably a 1.9.16p2 to 1.9.16p11 server's fault.
+
+ This is a configuration issue that should be addressed
+ by the network administrator - you shouldn't have
+ several machines configured as a domain master browser
+ for the same WINS scope (except if they are 1.9.17 or
+ greater, and you know what you're doing.
+
+ see docs/DOMAIN.txt.
+
+ */
+ become_domain_master_browser_bcast(work->work_group);
+ } else {
+ /*
+ * Now we are a domain master on a broadcast subnet, we need to add
+ * the WORKGROUP<1b> name to the unicast subnet so that we can answer
+ * unicast requests sent to this name. This bug wasn't found for a while
+ * as it is strange to have a DMB without using WINS. JRA.
+ */
+ insert_permanent_name_into_unicast(subrec, registered_name, nb_flags);
+ }
+}
+
+/****************************************************************************
+ Start the name registration process when becoming a Domain Master Browser
+ on a subnet.
+****************************************************************************/
+
+static void become_domain_master_stage1(struct subnet_record *subrec, const char *wg_name)
+{
+ struct work_record *work;
+
+ DEBUG(2,("become_domain_master_stage1: Becoming domain master browser for \
+workgroup %s on subnet %s\n", wg_name, subrec->subnet_name));
+
+ /* First, find the workgroup on the subnet. */
+ if((work = find_workgroup_on_subnet( subrec, wg_name )) == NULL) {
+ DEBUG(0,("become_domain_master_stage1: Error - unable to find workgroup %s on subnet %s.\n",
+ wg_name, subrec->subnet_name));
+ return;
+ }
+
+ DEBUG(3,("become_domain_master_stage1: go to first stage: register <1b> name\n"));
+ work->dom_state = DOMAIN_WAIT;
+
+ /* WORKGROUP<1b> is the domain master browser name. */
+ register_name(subrec, work->work_group,0x1b,samba_nb_type,
+ become_domain_master_stage2,
+ become_domain_master_fail, NULL);
+}
+
+/****************************************************************************
+ Function called when a query for a WORKGROUP<1b> name succeeds.
+ This is normally a fail condition as it means there is already
+ a domain master browser for a workgroup and we were trying to
+ become one.
+****************************************************************************/
+
+static void become_domain_master_query_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname, struct in_addr ip,
+ struct res_rec *rrec)
+{
+ unstring name;
+ struct in_addr allones_ip;
+
+ pull_ascii_nstring(name, sizeof(name), nmbname->name);
+
+ /* If the given ip is not ours, then we can't become a domain
+ controller as the name is already registered.
+ */
+
+ /* BUG note. Samba 1.9.16p11 servers seem to return the broadcast
+ address or zero ip for this query. Pretend this is ok. */
+
+ allones_ip.s_addr = htonl(INADDR_BROADCAST);
+
+ if(ismyip_v4(ip) || ip_equal_v4(allones_ip, ip) || is_zero_ip_v4(ip)) {
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "become_domain_master_query_success():\n" );
+ dbgtext( "Our address (%s) ", inet_ntoa(ip) );
+ dbgtext( "returned in query for name %s ", nmb_namestr(nmbname) );
+ dbgtext( "(domain master browser name) " );
+ dbgtext( "on subnet %s.\n", subrec->subnet_name );
+ dbgtext( "Continuing with domain master code.\n" );
+ }
+
+ become_domain_master_stage1(subrec, name);
+ } else {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "become_domain_master_query_success:\n" );
+ dbgtext( "There is already a domain master browser at " );
+ dbgtext( "IP %s for workgroup %s ", inet_ntoa(ip), name );
+ dbgtext( "registered on subnet %s.\n", subrec->subnet_name );
+ }
+ }
+}
+
+/****************************************************************************
+ Function called when a query for a WORKGROUP<1b> name fails.
+ This is normally a success condition as it then allows us to register
+ our own Domain Master Browser name.
+ ****************************************************************************/
+
+static void become_domain_master_query_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *question_name, int fail_code)
+{
+ unstring name;
+
+ /* If the query was unicast, and the error is not NAM_ERR (name didn't exist),
+ then this is a failure. Otherwise, not finding the name is what we want. */
+
+ if((subrec == unicast_subnet) && (fail_code != NAM_ERR)) {
+ DEBUG(0,("become_domain_master_query_fail: Error %d returned when \
+querying WINS server for name %s.\n",
+ fail_code, nmb_namestr(question_name)));
+ return;
+ }
+
+ /* Otherwise - not having the name allows us to register it. */
+ pull_ascii_nstring(name, sizeof(name), question_name->name);
+ become_domain_master_stage1(subrec, name);
+}
+
+/****************************************************************************
+ Attempt to become a domain master browser on all broadcast subnets.
+ ****************************************************************************/
+
+static void become_domain_master_browser_bcast(const char *workgroup_name)
+{
+ struct subnet_record *subrec;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work = find_workgroup_on_subnet(subrec, workgroup_name);
+
+ if (work && (work->dom_state == DOMAIN_NONE)) {
+ struct nmb_name nmbname;
+ make_nmb_name(&nmbname,workgroup_name,0x1b);
+
+ /*
+ * Check for our name on the given broadcast subnet first, only initiate
+ * further processing if we cannot find it.
+ */
+
+ if (find_name_on_subnet(subrec, &nmbname, FIND_SELF_NAME) == NULL) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "become_domain_master_browser_bcast:\n" );
+ dbgtext( "Attempting to become domain master browser on " );
+ dbgtext( "workgroup %s on subnet %s\n",
+ workgroup_name, subrec->subnet_name );
+ }
+
+ /* Send out a query to establish whether there's a
+ domain controller on the local subnet. If not,
+ we can become a domain controller.
+ */
+
+ DEBUG(0,("become_domain_master_browser_bcast: querying subnet %s \
+for domain master browser on workgroup %s\n", subrec->subnet_name, workgroup_name));
+
+ query_name(subrec, workgroup_name, nmbname.name_type,
+ become_domain_master_query_success,
+ become_domain_master_query_fail,
+ NULL);
+ }
+ }
+ }
+}
+
+/****************************************************************************
+ Attempt to become a domain master browser by registering with WINS.
+ ****************************************************************************/
+
+static void become_domain_master_browser_wins(const char *workgroup_name)
+{
+ struct work_record *work;
+
+ work = find_workgroup_on_subnet(unicast_subnet, workgroup_name);
+
+ if (work && (work->dom_state == DOMAIN_NONE)) {
+ struct nmb_name nmbname;
+
+ make_nmb_name(&nmbname,workgroup_name,0x1b);
+
+ /*
+ * Check for our name on the unicast subnet first, only initiate
+ * further processing if we cannot find it.
+ */
+
+ if (find_name_on_subnet(unicast_subnet, &nmbname, FIND_SELF_NAME) == NULL) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "become_domain_master_browser_wins:\n" );
+ dbgtext( "Attempting to become domain master browser " );
+ dbgtext( "on workgroup %s, subnet %s.\n",
+ workgroup_name, unicast_subnet->subnet_name );
+ }
+
+ /* Send out a query to establish whether there's a
+ domain master browser registered with WINS. If not,
+ we can become a domain master browser.
+ */
+
+ DEBUG(0,("become_domain_master_browser_wins: querying WINS server from IP %s \
+for domain master browser name %s on workgroup %s\n",
+ inet_ntoa(unicast_subnet->myip), nmb_namestr(&nmbname), workgroup_name));
+
+ query_name(unicast_subnet, workgroup_name, nmbname.name_type,
+ become_domain_master_query_success,
+ become_domain_master_query_fail,
+ NULL);
+ }
+ }
+}
+
+/****************************************************************************
+ Add the domain logon server and domain master browser names
+ if we are set up to do so.
+ **************************************************************************/
+
+void add_domain_names(time_t t)
+{
+ static time_t lastrun = 0;
+
+ if ((lastrun != 0) && (t < lastrun + (CHECK_TIME_ADD_DOM_NAMES * 60)))
+ return;
+
+ lastrun = t;
+
+ /* Do the "internet group" - <1c> names. */
+ if (IS_DC)
+ add_logon_names();
+
+ /* Do the domain master names. */
+ if(lp_domain_master()) {
+ if(we_are_a_wins_client()) {
+ /* We register the WORKGROUP<1b> name with the WINS
+ server first, and call add_domain_master_bcast()
+ only if this is successful.
+
+ This results in domain logon services being gracefully provided,
+ as opposed to the aggressive nature of 1.9.16p2 to 1.9.16p11.
+ 1.9.16p2 to 1.9.16p11 - due to a bug in namelogon.c,
+ cannot provide domain master / domain logon services.
+ */
+ become_domain_master_browser_wins(lp_workgroup());
+ } else {
+ become_domain_master_browser_bcast(lp_workgroup());
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_become_lmb.c b/source3/nmbd/nmbd_become_lmb.c
new file mode 100644
index 0000000..0761f1a
--- /dev/null
+++ b/source3/nmbd/nmbd_become_lmb.c
@@ -0,0 +1,587 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "lib/util/string_wrappers.h"
+
+extern uint16_t samba_nb_type; /* Samba's NetBIOS name type. */
+
+/*******************************************************************
+ Utility function to add a name to the unicast subnet, or add in
+ our IP address if it already exists.
+******************************************************************/
+
+void insert_permanent_name_into_unicast( struct subnet_record *subrec,
+ struct nmb_name *nmbname, uint16_t nb_type )
+{
+ unstring name;
+ struct name_record *namerec;
+
+ if((namerec = find_name_on_subnet(unicast_subnet, nmbname, FIND_SELF_NAME)) == NULL) {
+ pull_ascii_nstring(name, sizeof(name), nmbname->name);
+ /* The name needs to be created on the unicast subnet. */
+ (void)add_name_to_subnet( unicast_subnet, name,
+ nmbname->name_type, nb_type,
+ PERMANENT_TTL, PERMANENT_NAME, 1, &subrec->myip);
+ } else {
+ /* The name already exists on the unicast subnet. Add our local
+ IP for the given broadcast subnet to the name. */
+ add_ip_to_name_record( namerec, subrec->myip);
+ }
+}
+
+/*******************************************************************
+ Utility function to remove a name from the unicast subnet.
+******************************************************************/
+
+static void remove_permanent_name_from_unicast( struct subnet_record *subrec,
+ struct nmb_name *nmbname )
+{
+ struct name_record *namerec;
+
+ if((namerec = find_name_on_subnet(unicast_subnet, nmbname, FIND_SELF_NAME)) != NULL) {
+ /* Remove this broadcast subnet IP address from the name. */
+ remove_ip_from_name_record( namerec, subrec->myip);
+ if(namerec->data.num_ips == 0)
+ remove_name_from_namelist( unicast_subnet, namerec);
+ }
+}
+
+/*******************************************************************
+ Utility function always called to set our workgroup and server
+ state back to potential browser, or none.
+******************************************************************/
+
+static void reset_workgroup_state( struct subnet_record *subrec, const char *workgroup_name,
+ bool force_new_election )
+{
+ struct work_record *work;
+ struct server_record *servrec;
+ struct nmb_name nmbname;
+
+ if((work = find_workgroup_on_subnet( subrec, workgroup_name)) == NULL) {
+ DBG_ERR("reset_workgroup_state: Error - cannot find workgroup %s on \
+subnet %s.\n", workgroup_name, subrec->subnet_name );
+ return;
+ }
+
+ if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) {
+ DBG_ERR("reset_workgroup_state: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), work->work_group, subrec->subnet_name);
+ work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE;
+ return;
+ }
+
+ /* Update our server status - remove any master flag and replace
+ it with the potential browser flag. */
+ servrec->serv.type &= ~SV_TYPE_MASTER_BROWSER;
+ servrec->serv.type |= (lp_local_master() ? SV_TYPE_POTENTIAL_BROWSER : 0);
+
+ /* Tell the namelist writer to write out a change. */
+ subrec->work_changed = True;
+
+ /* Reset our election flags. */
+ work->ElectionCriterion &= ~0x4;
+
+ work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE;
+
+ /* Forget who the local master browser was for
+ this workgroup. */
+
+ set_workgroup_local_master_browser_name( work, "");
+
+ /*
+ * Ensure the IP address of this subnet is not registered as one
+ * of the IP addresses of the WORKGROUP<1d> name on the unicast
+ * subnet. This undoes what we did below when we became a local
+ * master browser.
+ */
+
+ make_nmb_name(&nmbname, work->work_group, 0x1d);
+
+ remove_permanent_name_from_unicast( subrec, &nmbname);
+
+ if(force_new_election)
+ work->needelection = True;
+}
+
+/*******************************************************************
+ Unbecome the local master browser name release success function.
+******************************************************************/
+
+static void unbecome_local_master_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *released_name,
+ struct in_addr released_ip)
+{
+ bool force_new_election = False;
+ unstring relname;
+
+ memcpy((char *)&force_new_election, userdata->data, sizeof(bool));
+
+ DBG_NOTICE("unbecome_local_master_success: released name %s.\n",
+ nmb_namestr(released_name));
+
+ /* Now reset the workgroup and server state. */
+ pull_ascii_nstring(relname, sizeof(relname), released_name->name);
+ reset_workgroup_state( subrec, relname, force_new_election );
+
+ DBG_WARNING("*****\n\n"
+ "Samba name server %s has stopped being a local master browser "
+ "for workgroup %s on subnet %s\n\n*****\n",
+ lp_netbios_name(), relname, subrec->subnet_name );
+
+}
+
+/*******************************************************************
+ Unbecome the local master browser name release fail function.
+******************************************************************/
+
+static void unbecome_local_master_fail(struct subnet_record *subrec, struct response_record *rrec,
+ struct nmb_name *fail_name)
+{
+ struct name_record *namerec;
+ struct userdata_struct *userdata = rrec->userdata;
+ bool force_new_election = False;
+ unstring failname;
+
+ memcpy((char *)&force_new_election, userdata->data, sizeof(bool));
+
+ DBG_WARNING("unbecome_local_master_fail: failed to release name %s. "
+ "Removing from namelist anyway.\n", nmb_namestr(fail_name));
+
+ /* Do it anyway. */
+ namerec = find_name_on_subnet(subrec, fail_name, FIND_SELF_NAME);
+ if(namerec)
+ remove_name_from_namelist(subrec, namerec);
+
+ /* Now reset the workgroup and server state. */
+ pull_ascii_nstring(failname, sizeof(failname), fail_name->name);
+ reset_workgroup_state( subrec, failname, force_new_election );
+
+ DBG_WARNING("*****\n\n"
+ "Samba name server %s has stopped being a local master browser "
+ "for workgroup %s on subnet %s\n\n*****\n",
+ lp_netbios_name(), failname, subrec->subnet_name);
+}
+
+/*******************************************************************
+ Utility function to remove the WORKGROUP<1d> name.
+******************************************************************/
+
+static void release_1d_name( struct subnet_record *subrec, const char *workgroup_name,
+ bool force_new_election)
+{
+ struct nmb_name nmbname;
+ struct name_record *namerec;
+
+ make_nmb_name(&nmbname, workgroup_name, 0x1d);
+ if((namerec = find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME))!=NULL) {
+ struct userdata_struct *userdata;
+ size_t size = sizeof(struct userdata_struct) + sizeof(bool);
+
+ if((userdata = (struct userdata_struct *)SMB_MALLOC(size)) == NULL) {
+ DBG_ERR("release_1d_name: malloc fail.\n");
+ return;
+ }
+
+ userdata->copy_fn = NULL;
+ userdata->free_fn = NULL;
+ userdata->userdata_len = sizeof(bool);
+ memcpy((char *)userdata->data, &force_new_election, sizeof(bool));
+
+ release_name(subrec, namerec,
+ unbecome_local_master_success,
+ unbecome_local_master_fail,
+ userdata);
+
+ zero_free(userdata, size);
+ }
+}
+
+/*******************************************************************
+ Unbecome the local master browser MSBROWSE name release success function.
+******************************************************************/
+
+static void release_msbrowse_name_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *released_name,
+ struct in_addr released_ip)
+{
+ DBG_INFO("release_msbrowse_name_success: Released name %s on subnet %s\n.",
+ nmb_namestr(released_name), subrec->subnet_name );
+
+ /* Remove the permanent MSBROWSE name added into the unicast subnet. */
+ remove_permanent_name_from_unicast( subrec, released_name);
+}
+
+/*******************************************************************
+ Unbecome the local master browser MSBROWSE name release fail function.
+******************************************************************/
+
+static void release_msbrowse_name_fail( struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *fail_name)
+{
+ struct name_record *namerec;
+
+ DBG_INFO("release_msbrowse_name_fail: Failed to release name %s on subnet %s\n.",
+ nmb_namestr(fail_name), subrec->subnet_name );
+
+ /* Release the name anyway. */
+ namerec = find_name_on_subnet(subrec, fail_name, FIND_SELF_NAME);
+ if(namerec)
+ remove_name_from_namelist(subrec, namerec);
+
+ /* Remove the permanent MSBROWSE name added into the unicast subnet. */
+ remove_permanent_name_from_unicast( subrec, fail_name);
+}
+
+/*******************************************************************
+ Unbecome the local master browser. If force_new_election is true, restart
+ the election process after we've unbecome the local master.
+******************************************************************/
+
+void unbecome_local_master_browser(struct subnet_record *subrec, struct work_record *work,
+ bool force_new_election)
+{
+ struct name_record *namerec;
+ struct nmb_name nmbname;
+
+ /* Sanity check. */
+
+ DBG_NOTICE("unbecome_local_master_browser: unbecoming local master for workgroup %s \
+on subnet %s\n",work->work_group, subrec->subnet_name);
+
+ if(find_server_in_workgroup( work, lp_netbios_name()) == NULL) {
+ DBG_WARNING("unbecome_local_master_browser: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), work->work_group, subrec->subnet_name);
+ work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE;
+ return;
+ }
+
+ /* Set the state to unbecoming. */
+ work->mst_state = MST_UNBECOMING_MASTER;
+
+ /*
+ * Release the WORKGROUP<1d> name asap to allow another machine to
+ * claim it.
+ */
+
+ release_1d_name( subrec, work->work_group, force_new_election);
+
+ /* Deregister any browser names we may have. */
+ make_nmb_name(&nmbname, MSBROWSE, 0x1);
+ if((namerec = find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME))!=NULL) {
+ release_name(subrec, namerec,
+ release_msbrowse_name_success,
+ release_msbrowse_name_fail,
+ NULL);
+ }
+
+ /*
+ * Ensure we have sent and processed these release packets
+ * before returning - we don't want to process any election
+ * packets before dealing with the 1d release.
+ */
+
+ retransmit_or_expire_response_records(time(NULL));
+}
+
+/****************************************************************************
+ Success in registering the WORKGROUP<1d> name.
+ We are now *really* a local master browser.
+ ****************************************************************************/
+
+static void become_local_master_stage2(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *registered_name,
+ uint16_t nb_flags,
+ int ttl, struct in_addr registered_ip)
+{
+ int i = 0;
+ struct server_record *sl;
+ struct work_record *work;
+ struct server_record *servrec;
+ unstring regname;
+
+ pull_ascii_nstring(regname, sizeof(regname), registered_name->name);
+ work = find_workgroup_on_subnet( subrec, regname);
+
+ if(!work) {
+ DBG_WARNING("become_local_master_stage2: Error - cannot find \
+workgroup %s on subnet %s\n", regname, subrec->subnet_name);
+ return;
+ }
+
+ if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) {
+ DBG_WARNING("become_local_master_stage2: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), regname, subrec->subnet_name);
+ work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE;
+ return;
+ }
+
+ DBG_NOTICE("become_local_master_stage2: registered as master browser for workgroup %s \
+on subnet %s\n", work->work_group, subrec->subnet_name);
+
+ work->mst_state = MST_BROWSER; /* registering WORKGROUP(1d) succeeded */
+
+ /* update our server status */
+ servrec->serv.type |= SV_TYPE_MASTER_BROWSER;
+ servrec->serv.type &= ~SV_TYPE_POTENTIAL_BROWSER;
+
+ /* Tell the namelist writer to write out a change. */
+ subrec->work_changed = True;
+
+ /* Add this name to the workgroup as local master browser. */
+ set_workgroup_local_master_browser_name( work, lp_netbios_name());
+
+ /* Count the number of servers we have on our list. If it's
+ less than 10 (just a heuristic) request the servers
+ to announce themselves.
+ */
+ for( sl = work->serverlist; sl != NULL; sl = sl->next)
+ i++;
+
+ if (i < 10) {
+ /* Ask all servers on our local net to announce to us. */
+ broadcast_announce_request(subrec, work);
+ }
+
+ /*
+ * Now we are a local master on a broadcast subnet, we need to add
+ * the WORKGROUP<1d> name to the unicast subnet so that we can answer
+ * unicast requests sent to this name. We can create this name directly on
+ * the unicast subnet as a WINS server always returns true when registering
+ * this name, and discards the registration. We use the number of IP
+ * addresses registered to this name as a reference count, as we
+ * remove this broadcast subnet IP address from it when we stop becoming a local
+ * master browser for this broadcast subnet.
+ */
+
+ insert_permanent_name_into_unicast( subrec, registered_name, nb_flags);
+
+ /* Reset the announce master browser timer so that we try and tell a domain
+ master browser as soon as possible that we are a local master browser. */
+ reset_announce_timer();
+
+ DBG_WARNING( "*****\n\n"
+ "Samba name server %s is now a local master browser "
+ "for workgroup %s on subnet %s\n\n*****\n",
+ lp_netbios_name(), work->work_group, subrec->subnet_name );
+}
+
+/****************************************************************************
+ Failed to register the WORKGROUP<1d> name.
+ ****************************************************************************/
+
+static void become_local_master_fail2(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *fail_name)
+{
+ unstring failname;
+ struct work_record *work;
+
+ DBG_WARNING("become_local_master_fail2: failed to register name %s on subnet %s. \
+Failed to become a local master browser.\n", nmb_namestr(fail_name), subrec->subnet_name);
+
+ pull_ascii_nstring(failname, sizeof(failname), fail_name->name);
+ work = find_workgroup_on_subnet( subrec, failname);
+
+ if(!work) {
+ DBG_WARNING("become_local_master_fail2: Error - cannot find \
+workgroup %s on subnet %s\n", failname, subrec->subnet_name);
+ return;
+ }
+
+ /* Roll back all the way by calling unbecome_local_master_browser(). */
+ unbecome_local_master_browser(subrec, work, False);
+}
+
+/****************************************************************************
+ Success in registering the MSBROWSE name.
+ ****************************************************************************/
+
+static void become_local_master_stage1(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *registered_name,
+ uint16_t nb_flags,
+ int ttl, struct in_addr registered_ip)
+{
+ char *work_name = userdata->data;
+ struct work_record *work = find_workgroup_on_subnet( subrec, work_name);
+
+ if(!work) {
+ DBG_WARNING("become_local_master_stage1: Error - cannot find \
+%s on subnet %s\n", work_name, subrec->subnet_name);
+ return;
+ }
+
+ DBG_NOTICE("become_local_master_stage1: go to stage 2: register the %s<1d> name.\n",
+ work->work_group);
+
+ work->mst_state = MST_MSB; /* Registering MSBROWSE was successful. */
+
+ /*
+ * We registered the MSBROWSE name on a broadcast subnet, now need to add
+ * the MSBROWSE name to the unicast subnet so that we can answer
+ * unicast requests sent to this name. We create this name directly on
+ * the unicast subnet.
+ */
+
+ insert_permanent_name_into_unicast( subrec, registered_name, nb_flags);
+
+ /* Attempt to register the WORKGROUP<1d> name. */
+ register_name(subrec, work->work_group,0x1d,samba_nb_type,
+ become_local_master_stage2,
+ become_local_master_fail2,
+ NULL);
+}
+
+/****************************************************************************
+ Failed to register the MSBROWSE name.
+ ****************************************************************************/
+
+static void become_local_master_fail1(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *fail_name)
+{
+ char *work_name = rrec->userdata->data;
+ struct work_record *work = find_workgroup_on_subnet(subrec, work_name);
+
+ if(!work) {
+ DBG_WARNING("become_local_master_fail1: Error - cannot find \
+workgroup %s on subnet %s\n", work_name, subrec->subnet_name);
+ return;
+ }
+
+ if(find_server_in_workgroup(work, lp_netbios_name()) == NULL) {
+ DBG_WARNING("become_local_master_fail1: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), work->work_group, subrec->subnet_name);
+ return;
+ }
+
+ reset_workgroup_state( subrec, work->work_group, False );
+
+ DBG_WARNING("become_local_master_fail1: Failed to become a local master browser for \
+workgroup %s on subnet %s. Couldn't register name %s.\n",
+ work->work_group, subrec->subnet_name, nmb_namestr(fail_name));
+}
+
+/******************************************************************
+ Become the local master browser on a subnet.
+ This gets called if we win an election on this subnet.
+
+ Stage 1: mst_state was MST_POTENTIAL - go to MST_BACK register ^1^2__MSBROWSE__^2^1.
+ Stage 2: mst_state was MST_BACKUP - go to MST_MSB and register WORKGROUP<1d>.
+ Stage 3: mst_state was MST_MSB - go to MST_BROWSER.
+******************************************************************/
+
+void become_local_master_browser(struct subnet_record *subrec, struct work_record *work)
+{
+ struct userdata_struct *userdata;
+ size_t size = sizeof(struct userdata_struct) + sizeof(fstring) + 1;
+
+ /* Sanity check. */
+ if (!lp_local_master()) {
+ DBG_WARNING("become_local_master_browser: Samba not configured as a local master browser.\n");
+ return;
+ }
+
+ if(!AM_POTENTIAL_MASTER_BROWSER(work)) {
+ DBG_NOTICE("become_local_master_browser: Awaiting potential browser state. Current state is %d\n",
+ work->mst_state );
+ return;
+ }
+
+ if(find_server_in_workgroup( work, lp_netbios_name()) == NULL) {
+ DBG_WARNING("become_local_master_browser: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), work->work_group, subrec->subnet_name);
+ return;
+ }
+
+ DBG_NOTICE("become_local_master_browser: Starting to become a master browser for workgroup \
+%s on subnet %s\n", work->work_group, subrec->subnet_name);
+
+ DBG_NOTICE("become_local_master_browser: first stage - attempt to register ^1^2__MSBROWSE__^2^1\n");
+ work->mst_state = MST_BACKUP; /* an election win was successful */
+
+ work->ElectionCriterion |= 0x5;
+
+ /* Tell the namelist writer to write out a change. */
+ subrec->work_changed = True;
+
+ /* Setup the userdata_struct. */
+ if((userdata = (struct userdata_struct *)SMB_MALLOC(size)) == NULL) {
+ DBG_ERR("become_local_master_browser: malloc fail.\n");
+ return;
+ }
+
+ userdata->copy_fn = NULL;
+ userdata->free_fn = NULL;
+ userdata->userdata_len = strlen(work->work_group)+1;
+ strlcpy(userdata->data, work->work_group, size - sizeof(*userdata));
+
+ /* Register the special browser group name. */
+ register_name(subrec, MSBROWSE, 0x01, samba_nb_type|NB_GROUP,
+ become_local_master_stage1,
+ become_local_master_fail1,
+ userdata);
+
+ zero_free(userdata, size);
+}
+
+/***************************************************************
+ Utility function to set the local master browser name. Does
+ some sanity checking as old versions of Samba seem to sometimes
+ say that the master browser name for a workgroup is the same
+ as the workgroup name.
+****************************************************************/
+
+void set_workgroup_local_master_browser_name( struct work_record *work, const char *newname)
+{
+ DBG_INFO("set_workgroup_local_master_browser_name: setting local master name to '%s' \
+for workgroup %s.\n", newname, work->work_group );
+
+#if 0
+ /*
+ * Apparently some sites use the workgroup name as the local
+ * master browser name. Arrrrggghhhhh ! (JRA).
+ */
+ if(strequal( work->work_group, newname))
+ {
+ DEBUG(5, ("set_workgroup_local_master_browser_name: Refusing to set \
+local_master_browser_name for workgroup %s to workgroup name.\n",
+ work->work_group ));
+ return;
+ }
+#endif
+
+ unstrcpy(work->local_master_browser_name, newname);
+}
diff --git a/source3/nmbd/nmbd_browserdb.c b/source3/nmbd/nmbd_browserdb.c
new file mode 100644
index 0000000..b5fdbab
--- /dev/null
+++ b/source3/nmbd/nmbd_browserdb.c
@@ -0,0 +1,180 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+ Copyright (C) Christopher R. Hertel 1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+/* -------------------------------------------------------------------------- **
+ * Modified July 1998 by CRH.
+ * I converted this module to use the canned doubly-linked lists. I also
+ * added comments above the functions where possible.
+ */
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+/* -------------------------------------------------------------------------- **
+ * Variables...
+ *
+ * lmb_browserlist - This is our local master browser list.
+ */
+
+struct browse_cache_record *lmb_browserlist;
+
+/* -------------------------------------------------------------------------- **
+ * Functions...
+ */
+
+/* ************************************************************************** **
+ * Remove and free a browser list entry.
+ *
+ * Input: browc - A pointer to the entry to be removed from the list and
+ * freed.
+ * Output: none.
+ *
+ * ************************************************************************** **
+ */
+static void remove_lmb_browser_entry( struct browse_cache_record *browc )
+{
+ DLIST_REMOVE(lmb_browserlist, browc);
+ SAFE_FREE(browc);
+}
+
+/* ************************************************************************** **
+ * Update a browser death time.
+ *
+ * Input: browc - Pointer to the entry to be updated.
+ * Output: none.
+ *
+ * ************************************************************************** **
+ */
+void update_browser_death_time( struct browse_cache_record *browc )
+{
+ /* Allow the new lmb to miss an announce period before we remove it. */
+ browc->death_time = time(NULL) + ( (CHECK_TIME_MST_ANNOUNCE + 2) * 60 );
+}
+
+/* ************************************************************************** **
+ * Create a browser entry and add it to the local master browser list.
+ *
+ * Input: work_name
+ * browser_name
+ * ip
+ *
+ * Output: Pointer to the new entry, or NULL if malloc() failed.
+ *
+ * ************************************************************************** **
+ */
+struct browse_cache_record *create_browser_in_lmb_cache( const char *work_name,
+ const char *browser_name,
+ struct in_addr ip )
+{
+ struct browse_cache_record *browc;
+ time_t now = time( NULL );
+
+ browc = SMB_MALLOC_P(struct browse_cache_record);
+
+ if( NULL == browc ) {
+ DEBUG( 0, ("create_browser_in_lmb_cache: malloc fail !\n") );
+ return( NULL );
+ }
+
+ memset( (char *)browc, '\0', sizeof( *browc ) );
+
+ /* For a new lmb entry we want to sync with it after one minute. This
+ will allow it time to send out a local announce and build its
+ browse list.
+ */
+
+ browc->sync_time = now + 60;
+
+ /* Allow the new lmb to miss an announce period before we remove it. */
+ browc->death_time = now + ( (CHECK_TIME_MST_ANNOUNCE + 2) * 60 );
+
+ unstrcpy( browc->lmb_name, browser_name);
+ unstrcpy( browc->work_group, work_name);
+ if (!strupper_m( browc->lmb_name )) {
+ SAFE_FREE(browc);
+ return NULL;
+ }
+ if (!strupper_m( browc->work_group )) {
+ SAFE_FREE(browc);
+ return NULL;
+ }
+
+ browc->ip = ip;
+
+ DLIST_ADD_END(lmb_browserlist, browc);
+
+ DEBUG(3, ("nmbd_browserdb:create_browser_in_lmb_cache()\n"));
+ DEBUGADD(3, (" Added lmb cache entry for workgroup %s name %s IP %s "
+ "ttl %d\n", browc->work_group, browc->lmb_name,
+ inet_ntoa(ip), (int)browc->death_time));
+
+ return( browc );
+}
+
+/* ************************************************************************** **
+ * Find a browser entry in the local master browser list.
+ *
+ * Input: browser_name - The name for which to search.
+ *
+ * Output: A pointer to the matching entry, or NULL if no match was found.
+ *
+ * ************************************************************************** **
+ */
+struct browse_cache_record *find_browser_in_lmb_cache( const char *browser_name )
+{
+ struct browse_cache_record *browc;
+
+ for( browc = lmb_browserlist; browc; browc = browc->next ) {
+ if( strequal( browser_name, browc->lmb_name ) ) {
+ break;
+ }
+ }
+
+ return browc;
+}
+
+/* ************************************************************************** **
+ * Expire timed out browsers in the browserlist.
+ *
+ * Input: t - Expiration time. Entries with death times less than this
+ * value will be removed from the list.
+ * Output: none.
+ *
+ * ************************************************************************** **
+ */
+void expire_lmb_browsers( time_t t )
+{
+ struct browse_cache_record *browc;
+ struct browse_cache_record *nextbrowc;
+
+ for( browc = lmb_browserlist; browc; browc = nextbrowc) {
+ nextbrowc = browc->next;
+
+ if( browc->death_time < t ) {
+ DEBUG(3, ("nmbd_browserdb:expire_lmb_browsers()\n"));
+ DEBUGADD(3, (" Removing timed out lmb entry %s\n",
+ browc->lmb_name));
+ remove_lmb_browser_entry( browc );
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_browsesync.c b/source3/nmbd/nmbd_browsesync.c
new file mode 100644
index 0000000..68b8b95
--- /dev/null
+++ b/source3/nmbd/nmbd_browsesync.c
@@ -0,0 +1,692 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+/* This is our local master browser list database. */
+extern struct browse_cache_record *lmb_browserlist;
+
+/****************************************************************************
+As a domain master browser, do a sync with a local master browser.
+**************************************************************************/
+
+static void sync_with_lmb(struct browse_cache_record *browc)
+{
+ struct work_record *work;
+
+ if( !(work = find_workgroup_on_subnet(unicast_subnet, browc->work_group)) ) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "sync_with_lmb:\n" );
+ dbgtext( "Failed to get a workgroup for a local master browser " );
+ dbgtext( "cache entry workgroup " );
+ dbgtext( "%s, server %s\n", browc->work_group, browc->lmb_name );
+ }
+ return;
+ }
+
+ /* We should only be doing this if we are a domain master browser for
+ the given workgroup. Ensure this is so. */
+
+ if(!AM_DOMAIN_MASTER_BROWSER(work)) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "sync_with_lmb:\n" );
+ dbgtext( "We are trying to sync with a local master browser " );
+ dbgtext( "%s for workgroup %s\n", browc->lmb_name, browc->work_group );
+ dbgtext( "and we are not a domain master browser on this workgroup.\n" );
+ dbgtext( "Error!\n" );
+ }
+ return;
+ }
+
+ if( DEBUGLVL( 2 ) ) {
+ dbgtext( "sync_with_lmb:\n" );
+ dbgtext( "Initiating sync with local master browser " );
+ dbgtext( "%s<0x20> at IP %s ", browc->lmb_name, inet_ntoa(browc->ip) );
+ dbgtext( "for workgroup %s\n", browc->work_group );
+ }
+
+ sync_browse_lists(work, browc->lmb_name, 0x20, browc->ip, True, True);
+
+ browc->sync_time += (CHECK_TIME_DMB_TO_LMB_SYNC * 60);
+}
+
+/****************************************************************************
+Sync or expire any local master browsers.
+**************************************************************************/
+
+void dmb_expire_and_sync_browser_lists(time_t t)
+{
+ static time_t last_run = 0;
+ struct browse_cache_record *browc;
+
+ /* Only do this every 20 seconds. */
+ if (t - last_run < 20)
+ return;
+
+ last_run = t;
+
+ expire_lmb_browsers(t);
+
+ for( browc = lmb_browserlist; browc; browc = browc->next ) {
+ if (browc->sync_time < t)
+ sync_with_lmb(browc);
+ }
+}
+
+/****************************************************************************
+As a local master browser, send an announce packet to the domain master browser.
+**************************************************************************/
+
+static void announce_local_master_browser_to_domain_master_browser( struct work_record *work)
+{
+ char outbuf[1024];
+ unstring myname;
+ unstring dmb_name;
+ char *p;
+
+ if(ismyip_v4(work->dmb_addr)) {
+ if( DEBUGLVL( 2 ) ) {
+ dbgtext( "announce_local_master_browser_to_domain_master_browser:\n" );
+ dbgtext( "We are both a domain and a local master browser for " );
+ dbgtext( "workgroup %s. ", work->work_group );
+ dbgtext( "Do not announce to ourselves.\n" );
+ }
+ return;
+ }
+
+ memset(outbuf,'\0',sizeof(outbuf));
+ p = outbuf;
+ SCVAL(p,0,ANN_MasterAnnouncement);
+ p++;
+
+ unstrcpy(myname, lp_netbios_name());
+ if (!strupper_m(myname)) {
+ DEBUG(2,("strupper_m %s failed\n", myname));
+ return;
+ }
+ myname[15]='\0';
+ /* The call below does CH_UNIX -> CH_DOS conversion. JRA */
+ push_ascii(p, myname, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE);
+
+ p = skip_string(outbuf,sizeof(outbuf),p);
+
+ if( DEBUGLVL( 4 ) ) {
+ dbgtext( "announce_local_master_browser_to_domain_master_browser:\n" );
+ dbgtext( "Sending local master announce to " );
+ dbgtext( "%s for workgroup %s.\n", nmb_namestr(&work->dmb_name),
+ work->work_group );
+ }
+
+ /* Target name for send_mailslot must be in UNIX charset. */
+ pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name);
+ send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf),
+ lp_netbios_name(), 0x0, dmb_name, 0x0,
+ work->dmb_addr, FIRST_SUBNET->myip, DGRAM_PORT);
+}
+
+/****************************************************************************
+As a local master browser, do a sync with a domain master browser.
+**************************************************************************/
+
+static void sync_with_dmb(struct work_record *work)
+{
+ unstring dmb_name;
+
+ if( DEBUGLVL( 2 ) ) {
+ dbgtext( "sync_with_dmb:\n" );
+ dbgtext( "Initiating sync with domain master browser " );
+ dbgtext( "%s ", nmb_namestr(&work->dmb_name) );
+ dbgtext( "at IP %s ", inet_ntoa(work->dmb_addr) );
+ dbgtext( "for workgroup %s\n", work->work_group );
+ }
+
+ pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name);
+ sync_browse_lists(work, dmb_name, work->dmb_name.name_type,
+ work->dmb_addr, False, True);
+}
+
+/****************************************************************************
+ Function called when a node status query to a domain master browser IP succeeds.
+****************************************************************************/
+
+static void domain_master_node_status_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct res_rec *answers,
+ struct in_addr from_ip)
+{
+ struct work_record *work = find_workgroup_on_subnet( subrec, userdata->data);
+
+ if( work == NULL ) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "domain_master_node_status_success:\n" );
+ dbgtext( "Unable to find workgroup " );
+ dbgtext( "%s on subnet %s.\n", userdata->data, subrec->subnet_name );
+ }
+ return;
+ }
+
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "domain_master_node_status_success:\n" );
+ dbgtext( "Success in node status for workgroup " );
+ dbgtext( "%s from ip %s\n", work->work_group, inet_ntoa(from_ip) );
+ }
+
+ /* Go through the list of names found at answers->rdata and look for
+ the first SERVER<0x20> name. */
+
+ if (answers->rdlength > 0) {
+ char *p = answers->rdata;
+ int numnames = CVAL(p, 0);
+
+ p += 1;
+
+ while (numnames--) {
+ unstring qname;
+ uint16_t nb_flags;
+ int name_type;
+
+ pull_ascii_nstring(qname, sizeof(qname), p);
+ name_type = CVAL(p,15);
+ nb_flags = get_nb_flags(&p[16]);
+ trim_char(qname,'\0',' ');
+
+ p += 18;
+
+ if(!(nb_flags & NB_GROUP) && (name_type == 0x20)) {
+ struct nmb_name nmbname;
+
+ make_nmb_name(&nmbname, qname, name_type);
+
+ /* Copy the dmb name and IP address
+ into the workgroup struct. */
+
+ work->dmb_name = nmbname;
+ putip((char *)&work->dmb_addr, &from_ip);
+
+ /* Do the local master browser announcement to the domain
+ master browser name and IP. */
+ announce_local_master_browser_to_domain_master_browser( work );
+
+ /* Now synchronise lists with the domain master browser. */
+ sync_with_dmb(work);
+ break;
+ }
+ }
+ } else if( DEBUGLVL( 0 ) ) {
+ dbgtext( "domain_master_node_status_success:\n" );
+ dbgtext( "Failed to find a SERVER<0x20> name in reply from IP " );
+ dbgtext( "%s.\n", inet_ntoa(from_ip) );
+ }
+}
+
+/****************************************************************************
+ Function called when a node status query to a domain master browser IP fails.
+****************************************************************************/
+
+static void domain_master_node_status_fail(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ struct userdata_struct *userdata = rrec->userdata;
+
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "domain_master_node_status_fail:\n" );
+ dbgtext( "Doing a node status request to the domain master browser\n" );
+ dbgtext( "for workgroup %s ", userdata ? userdata->data : "NULL" );
+ dbgtext( "at IP %s failed.\n", inet_ntoa(rrec->packet->ip) );
+ dbgtext( "Cannot sync browser lists.\n" );
+ }
+}
+
+/****************************************************************************
+ Function called when a query for a WORKGROUP<1b> name succeeds.
+****************************************************************************/
+
+static void find_domain_master_name_query_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata_in,
+ struct nmb_name *q_name, struct in_addr answer_ip, struct res_rec *rrec)
+{
+ /*
+ * Unfortunately, finding the IP address of the Domain Master Browser,
+ * as we have here, is not enough. We need to now do a sync to the
+ * SERVERNAME<0x20> NetBIOS name, as only recent NT servers will
+ * respond to the SMBSERVER name. To get this name from IP
+ * address we do a Node status request, and look for the first
+ * NAME<0x20> in the response, and take that as the server name.
+ * We also keep a cache of the Domain Master Browser name for this
+ * workgroup in the Workgroup struct, so that if the same IP address
+ * is returned every time, we don't need to do the node status
+ * request.
+ */
+
+ struct work_record *work;
+ struct nmb_name nmbname;
+ struct userdata_struct *userdata;
+ size_t size = sizeof(struct userdata_struct) + sizeof(fstring)+1;
+ unstring qname;
+
+ pull_ascii_nstring(qname, sizeof(qname), q_name->name);
+ if( !(work = find_workgroup_on_subnet(subrec, qname)) ) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "find_domain_master_name_query_success:\n" );
+ dbgtext( "Failed to find workgroup %s\n", qname);
+ }
+ return;
+ }
+
+ /* First check if we already have a dmb for this workgroup. */
+
+ if(!is_zero_ip_v4(work->dmb_addr) && ip_equal_v4(work->dmb_addr, answer_ip)) {
+ /* Do the local master browser announcement to the domain
+ master browser name and IP. */
+ announce_local_master_browser_to_domain_master_browser( work );
+
+ /* Now synchronise lists with the domain master browser. */
+ sync_with_dmb(work);
+ return;
+ } else {
+ zero_ip_v4(&work->dmb_addr);
+ }
+
+ /* Now initiate the node status request. */
+
+ /* We used to use the name "*",0x0 here, but some Windows
+ * servers don't answer that name. However we *know* they
+ * have the name workgroup#1b (as we just looked it up).
+ * So do the node status request on this name instead.
+ * Found at LBL labs. JRA.
+ */
+
+ make_nmb_name(&nmbname,work->work_group,0x1b);
+
+ /* Put the workgroup name into the userdata so we know
+ what workgroup we're talking to when the reply comes
+ back. */
+
+ /* Setup the userdata_struct - this is copied so we can use
+ a stack variable for this. */
+
+ if((userdata = (struct userdata_struct *)SMB_MALLOC(size)) == NULL) {
+ DEBUG(0, ("find_domain_master_name_query_success: malloc fail.\n"));
+ return;
+ }
+
+ userdata->copy_fn = NULL;
+ userdata->free_fn = NULL;
+ userdata->userdata_len = strlen(work->work_group)+1;
+ strlcpy(userdata->data, work->work_group, size - sizeof(*userdata));
+
+ node_status( subrec, &nmbname, answer_ip,
+ domain_master_node_status_success,
+ domain_master_node_status_fail,
+ userdata);
+
+ zero_free(userdata, size);
+}
+
+/****************************************************************************
+ Function called when a query for a WORKGROUP<1b> name fails.
+ ****************************************************************************/
+
+static void find_domain_master_name_query_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *question_name, int fail_code)
+{
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "find_domain_master_name_query_fail:\n" );
+ dbgtext( "Unable to find the Domain Master Browser name " );
+ dbgtext( "%s for the workgroup %s.\n",
+ nmb_namestr(question_name), question_name->name );
+ dbgtext( "Unable to sync browse lists in this workgroup.\n" );
+ }
+}
+
+/****************************************************************************
+As a local master browser for a workgroup find the domain master browser
+name, announce ourselves as local master browser to it and then pull the
+full domain browse lists from it onto the given subnet.
+**************************************************************************/
+
+void announce_and_sync_with_domain_master_browser( struct subnet_record *subrec,
+ struct work_record *work)
+{
+ /* Only do this if we are using a WINS server. */
+ if(we_are_a_wins_client() == False) {
+ if( DEBUGLVL( 10 ) ) {
+ dbgtext( "announce_and_sync_with_domain_master_browser:\n" );
+ dbgtext( "Ignoring, as we are not a WINS client.\n" );
+ }
+ return;
+ }
+
+ /* First, query for the WORKGROUP<1b> name from the WINS server. */
+ query_name(unicast_subnet, work->work_group, 0x1b,
+ find_domain_master_name_query_success,
+ find_domain_master_name_query_fail,
+ NULL);
+}
+
+/****************************************************************************
+ Function called when a node status query to a domain master browser IP succeeds.
+ This function is only called on query to a Samba 1.9.18 or above WINS server.
+
+ Note that adding the workgroup name is enough for this workgroup to be
+ browsable by clients, as clients query the WINS server or broadcast
+ nets for the WORKGROUP<1b> name when they want to browse a workgroup
+ they are not in. We do not need to do a sync with this Domain Master
+ Browser in order for our browse clients to see machines in this workgroup.
+ JRA.
+****************************************************************************/
+
+static void get_domain_master_name_node_status_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct res_rec *answers,
+ struct in_addr from_ip)
+{
+ unstring server_name;
+
+ server_name[0] = 0;
+
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "get_domain_master_name_node_status_success:\n" );
+ dbgtext( "Success in node status from ip %s\n", inet_ntoa(from_ip) );
+ }
+
+ /*
+ * Go through the list of names found at answers->rdata and look for
+ * the first WORKGROUP<0x1b> name.
+ */
+
+ if (answers->rdlength > 0) {
+ char *p = answers->rdata;
+ int numnames = CVAL(p, 0);
+
+ p += 1;
+
+ while (numnames--) {
+ unstring qname;
+ uint16_t nb_flags;
+ int name_type;
+
+ pull_ascii_nstring(qname, sizeof(qname), p);
+ name_type = CVAL(p,15);
+ nb_flags = get_nb_flags(&p[16]);
+ trim_char(qname,'\0',' ');
+
+ p += 18;
+
+ if(!(nb_flags & NB_GROUP) && (name_type == 0x00) &&
+ server_name[0] == 0) {
+ /* this is almost certainly the server netbios name */
+ strlcpy(server_name, qname, sizeof(server_name));
+ continue;
+ }
+
+ if(!(nb_flags & NB_GROUP) && (name_type == 0x1b)) {
+ struct work_record *work;
+
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "get_domain_master_name_node_status_success:\n" );
+ dbgtext( "%s(%s) ", server_name, inet_ntoa(from_ip) );
+ dbgtext( "is a domain master browser for workgroup " );
+ dbgtext( "%s. Adding this name.\n", qname );
+ }
+
+ /*
+ * If we don't already know about this workgroup, add it
+ * to the workgroup list on the unicast_subnet.
+ */
+
+ work = find_workgroup_on_subnet( subrec, qname);
+ if (work == NULL) {
+ struct nmb_name nmbname;
+ /*
+ * Add it - with an hour in the cache.
+ */
+ work = create_workgroup_on_subnet(subrec, qname, 60*60);
+ if (work == NULL) {
+ return;
+ }
+
+ /* remember who the master is */
+ strlcpy(work->local_master_browser_name,
+ server_name,
+ sizeof(work->local_master_browser_name));
+ make_nmb_name(&nmbname, server_name, 0x20);
+ work->dmb_name = nmbname;
+ work->dmb_addr = from_ip;
+ }
+ break;
+ }
+ }
+ } else if( DEBUGLVL( 1 ) ) {
+ dbgtext( "get_domain_master_name_node_status_success:\n" );
+ dbgtext( "Failed to find a WORKGROUP<0x1b> name in reply from IP " );
+ dbgtext( "%s.\n", inet_ntoa(from_ip) );
+ }
+}
+
+/****************************************************************************
+ Function called when a node status query to a domain master browser IP fails.
+****************************************************************************/
+
+static void get_domain_master_name_node_status_fail(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ if( DEBUGLVL( 2 ) ) {
+ dbgtext( "get_domain_master_name_node_status_fail:\n" );
+ dbgtext( "Doing a node status request to the domain master browser " );
+ dbgtext( "at IP %s failed.\n", inet_ntoa(rrec->packet->ip) );
+ dbgtext( "Cannot get workgroup name.\n" );
+ }
+}
+
+/****************************************************************************
+ Function called when a query for *<1b> name succeeds.
+****************************************************************************/
+
+static void find_all_domain_master_names_query_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata_in,
+ struct nmb_name *q_name, struct in_addr answer_ip, struct res_rec *rrec)
+{
+ /*
+ * We now have a list of all the domain master browsers for all workgroups
+ * that have registered with the WINS server. Now do a node status request
+ * to each one and look for the first 1b name in the reply. This will be
+ * the workgroup name that we will add to the unicast subnet as a 'non-local'
+ * workgroup.
+ */
+
+ struct nmb_name nmbname;
+ struct in_addr send_ip;
+ int i;
+
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "find_all_domain_master_names_query_succes:\n" );
+ dbgtext( "Got answer from WINS server of %d ", (rrec->rdlength / 6) );
+ dbgtext( "IP addresses for Domain Master Browsers.\n" );
+ }
+
+ for(i = 0; i < rrec->rdlength / 6; i++) {
+ /* Initiate the node status requests. */
+ make_nmb_name(&nmbname, "*", 0);
+
+ putip((char *)&send_ip, (char *)&rrec->rdata[(i*6) + 2]);
+
+ /*
+ * Don't send node status requests to ourself.
+ */
+
+ if(ismyip_v4( send_ip )) {
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "find_all_domain_master_names_query_succes:\n" );
+ dbgtext( "Not sending node status to our own IP " );
+ dbgtext( "%s.\n", inet_ntoa(send_ip) );
+ }
+ continue;
+ }
+
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "find_all_domain_master_names_query_success:\n" );
+ dbgtext( "Sending node status request to IP %s.\n", inet_ntoa(send_ip) );
+ }
+
+ node_status( subrec, &nmbname, send_ip,
+ get_domain_master_name_node_status_success,
+ get_domain_master_name_node_status_fail,
+ NULL);
+ }
+}
+
+/****************************************************************************
+ Function called when a query for *<1b> name fails.
+ ****************************************************************************/
+static void find_all_domain_master_names_query_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *question_name, int fail_code)
+{
+ if( DEBUGLVL( 10 ) ) {
+ dbgtext( "find_domain_master_name_query_fail:\n" );
+ dbgtext( "WINS server did not reply to a query for name " );
+ dbgtext( "%s.\nThis means it ", nmb_namestr(question_name) );
+ dbgtext( "is probably not a Samba 1.9.18 or above WINS server.\n" );
+ }
+}
+
+/****************************************************************************
+ If we are a domain master browser on the unicast subnet, do a query to the
+ WINS server for the *<1b> name. This will only work to a Samba WINS server,
+ so ignore it if we fail. If we succeed, contact each of the IP addresses in
+ turn and do a node status request to them. If this succeeds then look for a
+ <1b> name in the reply - this is the workgroup name. Add this to the unicast
+ subnet. This is expensive, so we only do this every 15 minutes.
+**************************************************************************/
+
+void collect_all_workgroup_names_from_wins_server(time_t t)
+{
+ static time_t lastrun = 0;
+ struct work_record *work;
+
+ /* Only do this if we are using a WINS server. */
+ if(we_are_a_wins_client() == False)
+ return;
+
+ /* Check to see if we are a domain master browser on the unicast subnet. */
+ if((work = find_workgroup_on_subnet( unicast_subnet, lp_workgroup())) == NULL) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "collect_all_workgroup_names_from_wins_server:\n" );
+ dbgtext( "Cannot find my workgroup %s ", lp_workgroup() );
+ dbgtext( "on subnet %s.\n", unicast_subnet->subnet_name );
+ }
+ return;
+ }
+
+ if(!AM_DOMAIN_MASTER_BROWSER(work))
+ return;
+
+ if ((lastrun != 0) && (t < lastrun + (15 * 60)))
+ return;
+
+ lastrun = t;
+
+ /* First, query for the *<1b> name from the WINS server. */
+ query_name(unicast_subnet, "*", 0x1b,
+ find_all_domain_master_names_query_success,
+ find_all_domain_master_names_query_fail,
+ NULL);
+}
+
+
+/****************************************************************************
+ If we are a domain master browser on the unicast subnet, do a regular sync
+ with all other DMBs that we know of on that subnet.
+
+To prevent exponential network traffic with large numbers of workgroups
+we use a randomised system where sync probability is inversely proportional
+to the number of known workgroups
+**************************************************************************/
+
+void sync_all_dmbs(time_t t)
+{
+ static time_t lastrun = 0;
+ struct work_record *work;
+ size_t count=0;
+
+ /* Only do this if we are using a WINS server. */
+ if(we_are_a_wins_client() == False)
+ return;
+
+ /* Check to see if we are a domain master browser on the
+ unicast subnet. */
+ work = find_workgroup_on_subnet(unicast_subnet, lp_workgroup());
+ if (!work)
+ return;
+
+ if (!AM_DOMAIN_MASTER_BROWSER(work))
+ return;
+
+ if ((lastrun != 0) && (t < lastrun + (5 * 60)))
+ return;
+
+ /* count how many syncs we might need to do */
+ for (work=unicast_subnet->workgrouplist; work; work = work->next) {
+ if (strcmp(lp_workgroup(), work->work_group)) {
+ count++;
+ }
+ }
+
+ /* leave if we don't have to do any syncs */
+ if (count == 0) {
+ return;
+ }
+
+ /* sync with a probability of 1/count */
+ for (work=unicast_subnet->workgrouplist; work; work = work->next) {
+ if (strcmp(lp_workgroup(), work->work_group)) {
+ unstring dmb_name;
+
+ if (((unsigned)sys_random()) % count != 0)
+ continue;
+
+ lastrun = t;
+
+ if (!work->dmb_name.name[0]) {
+ /* we don't know the DMB - assume it is
+ the same as the unicast local master */
+ make_nmb_name(&work->dmb_name,
+ work->local_master_browser_name,
+ 0x20);
+ }
+
+ pull_ascii_nstring(dmb_name, sizeof(dmb_name), work->dmb_name.name);
+
+ DEBUG(3,("Initiating DMB<->DMB sync with %s(%s)\n",
+ dmb_name, inet_ntoa(work->dmb_addr)));
+
+ sync_browse_lists(work,
+ dmb_name,
+ work->dmb_name.name_type,
+ work->dmb_addr, False, False);
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_elections.c b/source3/nmbd/nmbd_elections.c
new file mode 100644
index 0000000..41470ea
--- /dev/null
+++ b/source3/nmbd/nmbd_elections.c
@@ -0,0 +1,395 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+/* Election parameters. */
+extern time_t StartupTime;
+
+/****************************************************************************
+ Send an election datagram packet.
+**************************************************************************/
+
+static void send_election_dgram(struct subnet_record *subrec, const char *workgroup_name,
+ uint32_t criterion, int timeup,const char *server_name)
+{
+ char outbuf[1024];
+ unstring srv_name;
+ char *p;
+
+ DEBUG(2,("send_election_dgram: Sending election packet for workgroup %s on subnet %s\n",
+ workgroup_name, subrec->subnet_name ));
+
+ memset(outbuf,'\0',sizeof(outbuf));
+ p = outbuf;
+ SCVAL(p,0,ANN_Election); /* Election opcode. */
+ p++;
+
+ SCVAL(p,0,((criterion == 0 && timeup == 0) ? 0 : ELECTION_VERSION));
+ SIVAL(p,1,criterion);
+ SIVAL(p,5,timeup*1000); /* ms - Despite what the spec says. */
+ p += 13;
+ unstrcpy(srv_name, server_name);
+ if (!strupper_m(srv_name)) {
+ DEBUG(2,("strupper_m failed for %s\n", srv_name));
+ return;
+ }
+ /* The following call does UNIX -> DOS charset conversion. */
+ push_ascii(p, srv_name, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE);
+ p = skip_string(outbuf,sizeof(outbuf),p);
+
+ send_mailslot(False, BROWSE_MAILSLOT, outbuf, PTR_DIFF(p,outbuf),
+ lp_netbios_name(), 0,
+ workgroup_name, 0x1e,
+ subrec->bcast_ip, subrec->myip, DGRAM_PORT);
+}
+
+/*******************************************************************
+ We found a current master browser on one of our broadcast interfaces.
+******************************************************************/
+
+static void check_for_master_browser_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *answer_name,
+ struct in_addr answer_ip, struct res_rec *rrec)
+{
+ unstring aname;
+ pull_ascii_nstring(aname, sizeof(aname), answer_name->name);
+ DEBUG(3,("check_for_master_browser_success: Local master browser for workgroup %s exists at \
+IP %s (just checking).\n", aname, inet_ntoa(answer_ip) ));
+}
+
+/*******************************************************************
+ We failed to find a current master browser on one of our broadcast interfaces.
+******************************************************************/
+
+static void check_for_master_browser_fail( struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *question_name,
+ int fail_code)
+{
+ unstring workgroup_name;
+ struct work_record *work;
+
+ pull_ascii_nstring(workgroup_name,sizeof(workgroup_name),question_name->name);
+
+ work = find_workgroup_on_subnet(subrec, workgroup_name);
+ if(work == NULL) {
+ DEBUG(0,("check_for_master_browser_fail: Unable to find workgroup %s on subnet %s.=\n",
+ workgroup_name, subrec->subnet_name ));
+ return;
+ }
+
+ if (strequal(work->work_group, lp_workgroup())) {
+
+ if (lp_local_master()) {
+ /* We have discovered that there is no local master
+ browser, and we are configured to initiate
+ an election that we will participate in.
+ */
+ DEBUG(2,("check_for_master_browser_fail: Forcing election on workgroup %s subnet %s\n",
+ work->work_group, subrec->subnet_name ));
+
+ /* Setting this means we will participate when the
+ election is run in run_elections(). */
+ work->needelection = True;
+ } else {
+ /* We need to force an election, because we are configured
+ not to become the local master, but we still need one,
+ having detected that one doesn't exist.
+ */
+ send_election_dgram(subrec, work->work_group, 0, 0, "");
+ }
+ }
+}
+
+/*******************************************************************
+ Ensure there is a local master browser for a workgroup on our
+ broadcast interfaces.
+******************************************************************/
+
+void check_master_browser_exists(time_t t)
+{
+ static time_t lastrun=0;
+ struct subnet_record *subrec;
+ const char *workgroup_name = lp_workgroup();
+
+ if (t < (lastrun + (CHECK_TIME_MST_BROWSE * 60)))
+ return;
+
+ lastrun = t;
+
+ dump_workgroups(False);
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work;
+
+ for (work = subrec->workgrouplist; work; work = work->next) {
+ if (strequal(work->work_group, workgroup_name) && !AM_LOCAL_MASTER_BROWSER(work)) {
+ /* Do a name query for the local master browser on this net. */
+ query_name( subrec, work->work_group, 0x1d,
+ check_for_master_browser_success,
+ check_for_master_browser_fail,
+ NULL);
+ }
+ }
+ }
+}
+
+/*******************************************************************
+ Run an election.
+******************************************************************/
+
+void run_elections(time_t t)
+{
+ static time_t lastime = 0;
+
+ struct subnet_record *subrec;
+
+ /* Send election packets once every 2 seconds - note */
+ if (lastime && (t - lastime < 2)) {
+ return;
+ }
+
+ lastime = t;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work;
+
+ for (work = subrec->workgrouplist; work; work = work->next) {
+ if (work->RunningElection) {
+ /*
+ * We can only run an election for a workgroup if we have
+ * registered the WORKGROUP<1e> name, as that's the name
+ * we must listen to.
+ */
+ struct nmb_name nmbname;
+
+ make_nmb_name(&nmbname, work->work_group, 0x1e);
+ if(find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME)==NULL) {
+ DEBUG(8,("run_elections: Cannot send election packet yet as name %s not \
+yet registered on subnet %s\n", nmb_namestr(&nmbname), subrec->subnet_name ));
+ continue;
+ }
+
+ send_election_dgram(subrec, work->work_group, work->ElectionCriterion,
+ t - StartupTime, lp_netbios_name());
+
+ if (work->ElectionCount++ >= 4) {
+ /* Won election (4 packets were sent out uncontested. */
+ DEBUG(2,("run_elections: >>> Won election for workgroup %s on subnet %s <<<\n",
+ work->work_group, subrec->subnet_name ));
+
+ work->RunningElection = False;
+
+ become_local_master_browser(subrec, work);
+ }
+ }
+ }
+ }
+}
+
+/*******************************************************************
+ Determine if I win an election.
+******************************************************************/
+
+static bool win_election(struct work_record *work, int version,
+ uint32_t criterion, int timeup, const char *server_name)
+{
+ int mytimeup = time(NULL) - StartupTime;
+ uint32_t mycriterion = work->ElectionCriterion;
+
+ /* If local master is false then never win in election broadcasts. */
+ if(!lp_local_master()) {
+ DEBUG(3,("win_election: Losing election as local master == False\n"));
+ return False;
+ }
+
+ DEBUG(4,("win_election: election comparison: %x:%x %x:%x %d:%d %s:%s\n",
+ version, ELECTION_VERSION,
+ criterion, mycriterion,
+ timeup, mytimeup,
+ server_name, lp_netbios_name()));
+
+ if (version > ELECTION_VERSION)
+ return(False);
+ if (version < ELECTION_VERSION)
+ return(True);
+
+ if (criterion > mycriterion)
+ return(False);
+ if (criterion < mycriterion)
+ return(True);
+
+ if (timeup > mytimeup)
+ return(False);
+ if (timeup < mytimeup)
+ return(True);
+
+ if (strcasecmp_m(lp_netbios_name(), server_name) > 0)
+ return(False);
+
+ return(True);
+}
+
+/*******************************************************************
+ Process an incoming election datagram packet.
+******************************************************************/
+
+void process_election(struct subnet_record *subrec, struct packet_struct *p, const char *buf)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ int version = CVAL(buf,0);
+ uint32_t criterion = IVAL(buf,1);
+ int timeup = IVAL(buf,5)/1000;
+ unstring server_name;
+ struct work_record *work;
+ unstring workgroup_name;
+
+ pull_ascii_nstring(server_name, sizeof(server_name), buf+13);
+ pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name);
+
+ server_name[15] = 0;
+
+ DEBUG(3,("process_election: Election request from %s at IP %s on subnet %s for workgroup %s.\n",
+ server_name,inet_ntoa(p->ip), subrec->subnet_name, workgroup_name ));
+
+ DEBUG(5,("process_election: vers=%d criterion=%08x timeup=%d\n", version,criterion,timeup));
+
+ if(( work = find_workgroup_on_subnet(subrec, workgroup_name)) == NULL) {
+ DEBUG(0,("process_election: Cannot find workgroup %s on subnet %s.\n",
+ workgroup_name, subrec->subnet_name ));
+ goto done;
+ }
+
+ if (!strequal(work->work_group, lp_workgroup())) {
+ DEBUG(3,("process_election: ignoring election request for workgroup %s on subnet %s as this \
+is not my workgroup.\n", work->work_group, subrec->subnet_name ));
+ goto done;
+ }
+
+ if (win_election(work, version,criterion,timeup,server_name)) {
+ /* We take precedence over the requesting server. */
+ if (!work->RunningElection) {
+ /* We weren't running an election - start running one. */
+
+ work->needelection = True;
+ work->ElectionCount=0;
+ }
+
+ /* Note that if we were running an election for this workgroup on this
+ subnet already, we just ignore the server we take precedence over. */
+ } else {
+ /* We lost. Stop participating. */
+ work->needelection = False;
+
+ if (work->RunningElection || AM_LOCAL_MASTER_BROWSER(work)) {
+ work->RunningElection = False;
+ DEBUG(3,("process_election: >>> Lost election for workgroup %s on subnet %s <<<\n",
+ work->work_group, subrec->subnet_name ));
+ if (AM_LOCAL_MASTER_BROWSER(work))
+ unbecome_local_master_browser(subrec, work, False);
+ }
+ }
+done:
+ return;
+}
+
+/****************************************************************************
+ This function looks over all the workgroups known on all the broadcast
+ subnets and decides if a browser election is to be run on that workgroup.
+ It returns True if any election packets need to be sent (this will then
+ be done by run_elections().
+***************************************************************************/
+
+bool check_elections(void)
+{
+ struct subnet_record *subrec;
+ bool run_any_election = False;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work;
+ for (work = subrec->workgrouplist; work; work = work->next) {
+ if (work->RunningElection) {
+ run_any_election = work->RunningElection;
+ }
+
+ /*
+ * Start an election if we have any chance of winning.
+ * Note this is a change to the previous code, that would
+ * only run an election if nmbd was in the potential browser
+ * state. We need to run elections in any state if we're told
+ * to. JRA.
+ */
+
+ if (work->needelection && !work->RunningElection && lp_local_master()) {
+ /*
+ * We can only run an election for a workgroup if we have
+ * registered the WORKGROUP<1e> name, as that's the name
+ * we must listen to.
+ */
+ struct nmb_name nmbname;
+
+ make_nmb_name(&nmbname, work->work_group, 0x1e);
+ if(find_name_on_subnet( subrec, &nmbname, FIND_SELF_NAME)==NULL) {
+ DEBUG(8,("check_elections: Cannot send election packet yet as name %s not \
+yet registered on subnet %s\n", nmb_namestr(&nmbname), subrec->subnet_name ));
+ continue;
+ }
+
+ DEBUG(3,("check_elections: >>> Starting election for workgroup %s on subnet %s <<<\n",
+ work->work_group, subrec->subnet_name ));
+
+ work->ElectionCount = 0;
+ work->RunningElection = True;
+ work->needelection = False;
+ }
+ }
+ }
+ return run_any_election;
+}
+
+/****************************************************************************
+ Process a internal Samba message forcing an election.
+***************************************************************************/
+
+void nmbd_message_election(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct subnet_record *subrec;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work;
+ for (work = subrec->workgrouplist; work; work = work->next) {
+ if (strequal(work->work_group, lp_workgroup())) {
+ work->needelection = True;
+ work->ElectionCount=0;
+ work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE;
+ }
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_incomingdgrams.c b/source3/nmbd/nmbd_incomingdgrams.c
new file mode 100644
index 0000000..3e27ff5
--- /dev/null
+++ b/source3/nmbd/nmbd_incomingdgrams.c
@@ -0,0 +1,841 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+extern bool found_lm_clients;
+
+#if 0
+
+/* XXXX note: This function is currently unsuitable for use, as it
+ does not properly check that a server is in a fit state to become
+ a backup browser before asking it to be one.
+ The code is left here to be worked on at a later date.
+*/
+
+/****************************************************************************
+Tell a server to become a backup browser
+**************************************************************************/
+
+void tell_become_backup(void)
+{
+ struct subnet_record *subrec;
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec))
+ {
+ struct work_record *work;
+ for (work = subrec->workgrouplist; work; work = work->next)
+ {
+ struct server_record *servrec;
+ int num_servers = 0;
+ int num_backups = 0;
+
+ for (servrec = work->serverlist; servrec; servrec = servrec->next)
+ {
+ num_servers++;
+
+ if (is_myname(servrec->serv.name))
+ continue;
+
+ if (servrec->serv.type & SV_TYPE_BACKUP_BROWSER)
+ {
+ num_backups++;
+ continue;
+ }
+
+ if (servrec->serv.type & SV_TYPE_MASTER_BROWSER)
+ continue;
+
+ if (!(servrec->serv.type & SV_TYPE_POTENTIAL_BROWSER))
+ continue;
+
+ DEBUG(3,("num servers: %d num backups: %d\n",
+ num_servers, num_backups));
+
+ /* make first server a backup server. thereafter make every
+ tenth server a backup server */
+ if (num_backups != 0 && (num_servers+9) / num_backups > 10)
+ continue;
+
+ DEBUG(2,("sending become backup to %s %s for %s\n",
+ servrec->serv.name, inet_ntoa(subrec->bcast_ip),
+ work->work_group));
+
+ /* type 11 request from MYNAME(20) to WG(1e) for SERVER */
+ do_announce_request(servrec->serv.name, work->work_group,
+ ANN_BecomeBackup, 0x20, 0x1e, subrec->bcast_ip);
+ }
+ }
+ }
+}
+#endif
+
+/*******************************************************************
+ Process an incoming host announcement packet.
+*******************************************************************/
+
+void process_host_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ int ttl = IVAL(buf,1)/1000;
+ unstring announce_name;
+ uint32_t servertype = IVAL(buf,23);
+ fstring comment;
+ struct work_record *work;
+ struct server_record *servrec;
+ unstring work_name;
+ unstring source_name;
+ ZERO_STRUCT(source_name);
+ ZERO_STRUCT(announce_name);
+
+ pull_ascii_fstring(comment, buf+31);
+
+ pull_ascii_nstring(announce_name, sizeof(announce_name), buf+5);
+ pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name);
+
+ DEBUG(3,("process_host_announce: from %s<%02x> IP %s to \
+%s for server %s.\n", source_name, source_name[15], inet_ntoa(p->ip),
+ nmb_namestr(&dgram->dest_name),announce_name));
+
+ DEBUG(5,("process_host_announce: ttl=%d server type=%08x comment=%s\n",
+ ttl, servertype,comment));
+
+ /* Filter servertype to remove impossible bits. */
+ servertype &= ~(SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM);
+
+ /* A host announcement must be sent to the name WORKGROUP<1d>. */
+ if(dgram->dest_name.name_type != 0x1d) {
+ DEBUG(2,("process_host_announce: incorrect name type for destination from IP %s \
+(was %02x) should be 0x1d. Allowing packet anyway.\n",
+ inet_ntoa(p->ip), dgram->dest_name.name_type));
+ /* Change it so it was. */
+ dgram->dest_name.name_type = 0x1d;
+ }
+
+ /* For a host announce the workgroup name is the destination name. */
+ pull_ascii_nstring(work_name, sizeof(work_name), dgram->dest_name.name);
+
+ /*
+ * Syntax servers version 5.1 send HostAnnounce packets to
+ * *THE WRONG NAME*. They send to LOCAL_MASTER_BROWSER_NAME<00>
+ * instead of WORKGROUP<1d> name. So to fix this we check if
+ * the workgroup name is our own name, and if so change it
+ * to be our primary workgroup name.
+ */
+
+ if(strequal(work_name, lp_netbios_name()))
+ unstrcpy(work_name,lp_workgroup());
+
+ /*
+ * We are being very aggressive here in adding a workgroup
+ * name on the basis of a host announcing itself as being
+ * in that workgroup. Maybe we should wait for the workgroup
+ * announce instead ? JRA.
+ */
+
+ work = find_workgroup_on_subnet(subrec, work_name);
+
+ if(servertype != 0) {
+ if (work ==NULL ) {
+ /* We have no record of this workgroup. Add it. */
+ if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL)
+ goto done;
+ }
+
+ if((servrec = find_server_in_workgroup( work, announce_name))==NULL) {
+ /* If this server is not already in the workgroup, add it. */
+ create_server_on_workgroup(work, announce_name,
+ servertype|SV_TYPE_LOCAL_LIST_ONLY,
+ ttl, comment);
+ } else {
+ /* Update the record. */
+ servrec->serv.type = servertype|SV_TYPE_LOCAL_LIST_ONLY;
+ update_server_ttl( servrec, ttl);
+ strlcpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment));
+ }
+ } else {
+ /*
+ * This server is announcing it is going down. Remove it from the
+ * workgroup.
+ */
+ if(!is_myname(announce_name) && (work != NULL) &&
+ ((servrec = find_server_in_workgroup( work, announce_name))!=NULL)) {
+ remove_server_from_workgroup( work, servrec);
+ }
+ }
+
+ subrec->work_changed = True;
+done:
+ return;
+}
+
+/*******************************************************************
+ Process an incoming WORKGROUP announcement packet.
+*******************************************************************/
+
+void process_workgroup_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ int ttl = IVAL(buf,1)/1000;
+ unstring workgroup_announce_name;
+ unstring master_name;
+ uint32_t servertype = IVAL(buf,23);
+ struct work_record *work;
+ unstring source_name;
+ unstring dest_name;
+
+ pull_ascii_nstring(workgroup_announce_name,sizeof(workgroup_announce_name),buf+5);
+ pull_ascii_nstring(master_name,sizeof(master_name),buf+31);
+ pull_ascii_nstring(source_name,sizeof(source_name),dgram->source_name.name);
+ pull_ascii_nstring(dest_name,sizeof(dest_name),dgram->dest_name.name);
+
+ DEBUG(3,("process_workgroup_announce: from %s<%02x> IP %s to \
+%s for workgroup %s.\n", source_name, source_name[15], inet_ntoa(p->ip),
+ nmb_namestr(&dgram->dest_name),workgroup_announce_name));
+
+ DEBUG(5,("process_workgroup_announce: ttl=%d server type=%08x master browser=%s\n",
+ ttl, servertype, master_name));
+
+ /* Workgroup announcements must only go to the MSBROWSE name. */
+ if (!strequal(dest_name, MSBROWSE) || (dgram->dest_name.name_type != 0x1)) {
+ DEBUG(0,("process_workgroup_announce: from IP %s should be to __MSBROWSE__<0x01> not %s\n",
+ inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name)));
+ goto done;
+ }
+
+ if ((work = find_workgroup_on_subnet(subrec, workgroup_announce_name))==NULL) {
+ /* We have no record of this workgroup. Add it. */
+ if((work = create_workgroup_on_subnet(subrec, workgroup_announce_name, ttl))==NULL)
+ goto done;
+ } else {
+ /* Update the workgroup death_time. */
+ update_workgroup_ttl(work, ttl);
+ }
+
+ if(*work->local_master_browser_name == '\0') {
+ /* Set the master browser name. */
+ set_workgroup_local_master_browser_name( work, master_name );
+ }
+
+ subrec->work_changed = True;
+
+done:
+ return;
+}
+
+/*******************************************************************
+ Process an incoming local master browser announcement packet.
+*******************************************************************/
+
+void process_local_master_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ int ttl = IVAL(buf,1)/1000;
+ unstring server_name;
+ uint32_t servertype = IVAL(buf,23);
+ fstring comment;
+ unstring work_name;
+ struct work_record *work = NULL;
+ struct server_record *servrec;
+ unstring source_name;
+
+ pull_ascii_nstring(server_name,sizeof(server_name),buf+5);
+ pull_ascii_fstring(comment, buf+31);
+ pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name);
+ pull_ascii_nstring(work_name, sizeof(work_name), dgram->dest_name.name);
+
+ DEBUG(3,("process_local_master_announce: from %s<%02x> IP %s to \
+%s for server %s.\n", source_name, source_name[15], inet_ntoa(p->ip),
+ nmb_namestr(&dgram->dest_name),server_name));
+
+ DEBUG(5,("process_local_master_announce: ttl=%d server type=%08x comment=%s\n",
+ ttl, servertype, comment));
+
+ /* A local master announcement must be sent to the name WORKGROUP<1e>. */
+ if(dgram->dest_name.name_type != 0x1e) {
+ DEBUG(0,("process_local_master_announce: incorrect name type for destination from IP %s \
+(was %02x) should be 0x1e. Ignoring packet.\n",
+ inet_ntoa(p->ip), dgram->dest_name.name_type));
+ goto done;
+ }
+
+ /* Filter servertype to remove impossible bits. */
+ servertype &= ~(SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM);
+
+ /* For a local master announce the workgroup name is the destination name. */
+
+ if ((work = find_workgroup_on_subnet(subrec, work_name))==NULL) {
+ /* Don't bother adding if it's a local master release announce. */
+ if(servertype == 0)
+ goto done;
+
+ /* We have no record of this workgroup. Add it. */
+ if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL)
+ goto done;
+ }
+
+ /* If we think we're the local master browser for this workgroup,
+ we should never have got this packet. We don't see our own
+ packets.
+ */
+ if(AM_LOCAL_MASTER_BROWSER(work)) {
+ DEBUG(0,("process_local_master_announce: Server %s at IP %s is announcing itself as \
+a local master browser for workgroup %s and we think we are master. Forcing election.\n",
+ server_name, inet_ntoa(p->ip), work_name));
+
+ /* Samba nmbd versions 1.9.17 to 1.9.17p4 have a bug in that when
+ they have become a local master browser once, they will never
+ stop sending local master announcements. To fix this we send
+ them a reset browser packet, with level 0x2 on the __SAMBA__
+ name that only they should be listening to. */
+
+ send_browser_reset( 0x2, "__SAMBA__" , 0x20, p->ip);
+
+ /* We should demote ourself and force an election. */
+
+ unbecome_local_master_browser( subrec, work, True);
+
+ /* The actual election requests are handled in nmbd_election.c */
+ goto done;
+ }
+
+ /* Find the server record on this workgroup. If it doesn't exist, add it. */
+
+ if(servertype != 0) {
+ if((servrec = find_server_in_workgroup( work, server_name))==NULL) {
+ /* If this server is not already in the workgroup, add it. */
+ create_server_on_workgroup(work, server_name,
+ servertype|SV_TYPE_LOCAL_LIST_ONLY,
+ ttl, comment);
+ } else {
+ /* Update the record. */
+ if (servrec->serv.type !=
+ (servertype|SV_TYPE_LOCAL_LIST_ONLY)) {
+ servrec->serv.type =
+ servertype|SV_TYPE_LOCAL_LIST_ONLY;
+ subrec->work_changed = true;
+ }
+ if (!strequal(servrec->serv.comment,comment)) {
+ strlcpy(servrec->serv.comment,
+ comment,
+ sizeof(servrec->serv.comment));
+ subrec->work_changed = true;
+ }
+ update_server_ttl(servrec, ttl);
+ }
+
+ if (!strequal(work->local_master_browser_name, server_name)) {
+ set_workgroup_local_master_browser_name( work, server_name );
+ subrec->work_changed = true;
+ }
+ } else {
+ /*
+ * This server is announcing it is going down. Remove it from the
+ * workgroup.
+ */
+ if(!is_myname(server_name) &&
+ ((servrec = find_server_in_workgroup( work, server_name))!=NULL)) {
+ remove_server_from_workgroup( work, servrec);
+ }
+ }
+
+done:
+ return;
+}
+
+/*******************************************************************
+ Process a domain master announcement frame.
+ Domain master browsers receive these from local masters. The Domain
+ master should then issue a sync with the local master, asking for
+ that machines local server list.
+******************************************************************/
+
+void process_master_browser_announce(struct subnet_record *subrec,
+ struct packet_struct *p,const char *buf)
+{
+ unstring local_master_name;
+ struct work_record *work;
+ struct browse_cache_record *browrec;
+
+ pull_ascii_nstring(local_master_name,sizeof(local_master_name),buf);
+
+ DEBUG(3,("process_master_browser_announce: Local master announce from %s IP %s.\n",
+ local_master_name, inet_ntoa(p->ip)));
+
+ if (!lp_domain_master()) {
+ DEBUG(0,("process_master_browser_announce: Not configured as domain \
+master - ignoring master announce.\n"));
+ goto done;
+ }
+
+ if((work = find_workgroup_on_subnet(subrec, lp_workgroup())) == NULL) {
+ DEBUG(0,("process_master_browser_announce: Cannot find workgroup %s on subnet %s\n",
+ lp_workgroup(), subrec->subnet_name));
+ goto done;
+ }
+
+ if(!AM_DOMAIN_MASTER_BROWSER(work)) {
+ DEBUG(0,("process_master_browser_announce: Local master announce made to us from \
+%s IP %s and we are not a domain master browser.\n", local_master_name, inet_ntoa(p->ip)));
+ goto done;
+ }
+
+ /* Add this host as a local master browser entry on the browse lists.
+ This causes a sync request to be made to it at a later date.
+ */
+
+ if((browrec = find_browser_in_lmb_cache( local_master_name )) == NULL) {
+ /* Add it. */
+ create_browser_in_lmb_cache( work->work_group, local_master_name, p->ip);
+ } else {
+ update_browser_death_time(browrec);
+ }
+
+done:
+ return;
+}
+
+/*******************************************************************
+ Process an incoming LanMan host announcement packet.
+*******************************************************************/
+
+void process_lm_host_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf, int len)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ uint32_t servertype = IVAL(buf,1);
+ int osmajor=CVAL(buf,5); /* major version of node software */
+ int osminor=CVAL(buf,6); /* minor version of node software */
+ int ttl = SVAL(buf,7);
+ unstring announce_name;
+ struct work_record *work;
+ struct server_record *servrec;
+ unstring work_name;
+ unstring source_name;
+ fstring comment;
+ char *s = get_safe_str_ptr(buf,len,discard_const_p(char, buf),9);
+
+ if (!s) {
+ goto done;
+ }
+ s = skip_string(buf,len,s);
+ if (!s) {
+ goto done;
+ }
+ pull_ascii(comment, s, sizeof(fstring), 43, STR_TERMINATE);
+
+ pull_ascii_nstring(announce_name,sizeof(announce_name),buf+9);
+ pull_ascii_nstring(source_name,sizeof(source_name),dgram->source_name.name);
+ /* For a LanMan host announce the workgroup name is the destination name. */
+ pull_ascii_nstring(work_name,sizeof(work_name),dgram->dest_name.name);
+
+ DEBUG(3,("process_lm_host_announce: LM Announcement from %s IP %s to \
+%s for server %s.\n", nmb_namestr(&dgram->source_name), inet_ntoa(p->ip),
+ nmb_namestr(&dgram->dest_name),announce_name));
+
+ DEBUG(5,("process_lm_host_announce: os=(%d,%d) ttl=%d server type=%08x comment=%s\n",
+ osmajor, osminor, ttl, servertype,comment));
+
+ if ((osmajor < 36) || (osmajor > 38) || (osminor !=0)) {
+ DEBUG(5,("process_lm_host_announce: LM Announcement packet does not \
+originate from OS/2 Warp client. Ignoring packet.\n"));
+ /* Could have been from a Windows machine (with its LM Announce enabled),
+ or a Samba server. Then don't disrupt the current browse list. */
+ goto done;
+ }
+
+ /* Filter servertype to remove impossible bits. */
+ servertype &= ~(SV_TYPE_LOCAL_LIST_ONLY|SV_TYPE_DOMAIN_ENUM);
+
+ /* A LanMan host announcement must be sent to the name WORKGROUP<00>. */
+ if(dgram->dest_name.name_type != 0x00) {
+ DEBUG(2,("process_lm_host_announce: incorrect name type for destination from IP %s \
+(was %02x) should be 0x00. Allowing packet anyway.\n",
+ inet_ntoa(p->ip), dgram->dest_name.name_type));
+ /* Change it so it was. */
+ dgram->dest_name.name_type = 0x00;
+ }
+
+ /*
+ * Syntax servers version 5.1 send HostAnnounce packets to
+ * *THE WRONG NAME*. They send to LOCAL_MASTER_BROWSER_NAME<00>
+ * instead of WORKGROUP<1d> name. So to fix this we check if
+ * the workgroup name is our own name, and if so change it
+ * to be our primary workgroup name. This code is probably
+ * not needed in the LanMan announce code, but it won't hurt.
+ */
+
+ if(strequal(work_name, lp_netbios_name()))
+ unstrcpy(work_name,lp_workgroup());
+
+ /*
+ * We are being very aggressive here in adding a workgroup
+ * name on the basis of a host announcing itself as being
+ * in that workgroup. Maybe we should wait for the workgroup
+ * announce instead ? JRA.
+ */
+
+ work = find_workgroup_on_subnet(subrec, work_name);
+
+ if(servertype != 0) {
+ if (work == NULL) {
+ /* We have no record of this workgroup. Add it. */
+ if((work = create_workgroup_on_subnet(subrec, work_name, ttl))==NULL)
+ goto done;
+ }
+
+ if((servrec = find_server_in_workgroup( work, announce_name))==NULL) {
+ /* If this server is not already in the workgroup, add it. */
+ create_server_on_workgroup(work, announce_name,
+ servertype|SV_TYPE_LOCAL_LIST_ONLY,
+ ttl, comment);
+ } else {
+ /* Update the record. */
+ servrec->serv.type = servertype|SV_TYPE_LOCAL_LIST_ONLY;
+ update_server_ttl( servrec, ttl);
+ strlcpy(servrec->serv.comment,comment,sizeof(servrec->serv.comment));
+ }
+ } else {
+ /*
+ * This server is announcing it is going down. Remove it from the
+ * workgroup.
+ */
+ if(!is_myname(announce_name) && (work != NULL) &&
+ ((servrec = find_server_in_workgroup( work, announce_name))!=NULL)) {
+ remove_server_from_workgroup( work, servrec);
+ }
+ }
+
+ subrec->work_changed = True;
+ found_lm_clients = True;
+
+done:
+ return;
+}
+
+/****************************************************************************
+ Send a backup list response.
+*****************************************************************************/
+
+static void send_backup_list_response(struct subnet_record *subrec,
+ struct work_record *work,
+ struct nmb_name *send_to_name,
+ unsigned char max_number_requested,
+ uint32_t token, struct in_addr sendto_ip,
+ int port)
+{
+ char outbuf[1024];
+ char *p, *countptr;
+ unsigned int count = 0;
+ unstring send_to_namestr;
+#if 0
+ struct server_record *servrec;
+#endif
+ unstring myname;
+
+ memset(outbuf,'\0',sizeof(outbuf));
+
+ DEBUG(3,("send_backup_list_response: sending backup list for workgroup %s to %s IP %s\n",
+ work->work_group, nmb_namestr(send_to_name), inet_ntoa(sendto_ip)));
+
+ p = outbuf;
+
+ SCVAL(p,0,ANN_GetBackupListResp); /* Backup list response opcode. */
+ p++;
+
+ countptr = p;
+ p++;
+
+ SIVAL(p,0,token); /* The sender's unique info. */
+ p += 4;
+
+ /* We always return at least one name - our own. */
+ count = 1;
+ unstrcpy(myname, lp_netbios_name());
+ if (!strupper_m(myname)) {
+ DEBUG(4,("strupper_m %s failed\n", myname));
+ return;
+ }
+ myname[15]='\0';
+ push_ascii(p, myname, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE);
+
+ p = skip_string(outbuf,sizeof(outbuf),p);
+
+ /* Look for backup browsers in this workgroup. */
+
+#if 0
+ /* we don't currently send become_backup requests so we should never
+ send any other servers names out as backups for our
+ workgroup. That's why this is commented out (tridge) */
+
+ /*
+ * NB. Note that the struct work_record here is not necessarily
+ * attached to the subnet *subrec.
+ */
+
+ for (servrec = work->serverlist; servrec; servrec = servrec->next)
+ {
+ int len = PTR_DIFF(p, outbuf);
+ if((sizeof(outbuf) - len) < 16)
+ break;
+
+ if(count >= (unsigned int)max_number_requested)
+ break;
+
+ if(strnequal(servrec->serv.name, lp_netbios_name(),15))
+ continue;
+
+ if(!(servrec->serv.type & SV_TYPE_BACKUP_BROWSER))
+ continue;
+
+ strlcpy(p, servrec->serv.name, 16);
+ strupper_m(p);
+ count++;
+
+ DEBUG(5,("send_backup_list_response: Adding server %s number %d\n",
+ p, count));
+
+ p = skip_string(outbuf,sizeof(outbuf),p);
+ }
+#endif
+
+ SCVAL(countptr, 0, count);
+
+ pull_ascii_nstring(send_to_namestr, sizeof(send_to_namestr), send_to_name->name);
+
+ DEBUG(4,("send_backup_list_response: sending response to %s<00> IP %s with %d servers.\n",
+ send_to_namestr, inet_ntoa(sendto_ip), count));
+
+ send_mailslot(True, BROWSE_MAILSLOT,
+ outbuf,PTR_DIFF(p,outbuf),
+ lp_netbios_name(), 0,
+ send_to_namestr,0,
+ sendto_ip, subrec->myip, port);
+}
+
+/*******************************************************************
+ Process a send backup list request packet.
+
+ A client sends a backup list request to ask for a list of servers on
+ the net that maintain server lists for a domain. A server is then
+ chosen from this list to send NetServerEnum commands to to list
+ available servers.
+
+********************************************************************/
+
+void process_get_backup_list_request(struct subnet_record *subrec,
+ struct packet_struct *p,const char *buf)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ struct work_record *work;
+ unsigned char max_number_requested = CVAL(buf,0);
+ uint32_t token = IVAL(buf,1); /* Sender's key index for the workgroup. */
+ int name_type = dgram->dest_name.name_type;
+ unstring workgroup_name;
+ struct subnet_record *search_subrec = subrec;
+
+ pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name);
+
+ DEBUG(3,("process_get_backup_list_request: request from %s IP %s to %s.\n",
+ nmb_namestr(&dgram->source_name), inet_ntoa(p->ip),
+ nmb_namestr(&dgram->dest_name)));
+
+ /* We have to be a master browser, or a domain master browser
+ for the requested workgroup. That means it must be our
+ workgroup. */
+
+ if(strequal(workgroup_name, lp_workgroup()) == False) {
+ DEBUG(7,("process_get_backup_list_request: Ignoring announce request for workgroup %s.\n",
+ workgroup_name));
+ goto done;
+ }
+
+ if((work = find_workgroup_on_subnet(search_subrec, workgroup_name)) == NULL) {
+ DEBUG(0,("process_get_backup_list_request: Cannot find workgroup %s on \
+subnet %s.\n", workgroup_name, search_subrec->subnet_name));
+ goto done;
+ }
+
+ /*
+ * If the packet was sent to WORKGROUP<1b> instead
+ * of WORKGROUP<1d> then it was unicast to us a domain master
+ * browser. Change search subrec to unicast.
+ */
+
+ if(name_type == 0x1b) {
+ /* We must be a domain master browser in order to
+ process this packet. */
+
+ if(!AM_DOMAIN_MASTER_BROWSER(work)) {
+ DEBUG(0,("process_get_backup_list_request: domain list requested for workgroup %s \
+and I am not a domain master browser.\n", workgroup_name));
+ goto done;
+ }
+
+ search_subrec = unicast_subnet;
+ } else if (name_type == 0x1d) {
+ /* We must be a local master browser in order to process this packet. */
+
+ if(!AM_LOCAL_MASTER_BROWSER(work)) {
+ DEBUG(0,("process_get_backup_list_request: domain list requested for workgroup %s \
+and I am not a local master browser.\n", workgroup_name));
+ goto done;
+ }
+ } else {
+ DEBUG(0,("process_get_backup_list_request: Invalid name type %x - should be 0x1b or 0x1d.\n",
+ name_type));
+ goto done;
+ }
+
+ send_backup_list_response(subrec, work, &dgram->source_name,
+ max_number_requested, token, p->ip, p->port);
+
+done:
+ return;
+}
+
+/*******************************************************************
+ Process a reset browser state packet.
+
+ Diagnostic packet:
+ 0x1 - Stop being a master browser and become a backup browser.
+ 0x2 - Discard browse lists, stop being a master browser, try again.
+ 0x4 - Stop being a master browser forever.
+
+******************************************************************/
+
+void process_reset_browser(struct subnet_record *subrec,
+ struct packet_struct *p,const char *buf)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ int state = CVAL(buf,0);
+ struct subnet_record *sr;
+
+ DEBUG(1,("process_reset_browser: received diagnostic browser reset \
+request from %s IP %s state=0x%X\n",
+ nmb_namestr(&dgram->source_name), inet_ntoa(p->ip), state));
+
+ /* Stop being a local master browser on all our broadcast subnets. */
+ if (state & 0x1) {
+ for (sr = FIRST_SUBNET; sr; sr = NEXT_SUBNET_EXCLUDING_UNICAST(sr)) {
+ struct work_record *work;
+ for (work = sr->workgrouplist; work; work = work->next) {
+ if (AM_LOCAL_MASTER_BROWSER(work))
+ unbecome_local_master_browser(sr, work, True);
+ }
+ }
+ }
+
+ /* Discard our browse lists. */
+ if (state & 0x2) {
+ /*
+ * Calling expire_workgroups_and_servers with a -1
+ * time causes all servers not marked with a PERMANENT_TTL
+ * on the workgroup lists to be discarded, and all
+ * workgroups with empty server lists to be discarded.
+ * This means we keep our own server names and workgroup
+ * as these have a PERMANENT_TTL.
+ */
+
+ expire_workgroups_and_servers(-1);
+ }
+
+ /* Request to stop browsing altogether. */
+ if (state & 0x4)
+ DEBUG(1,("process_reset_browser: ignoring request to stop being a browser.\n"));
+}
+
+/*******************************************************************
+ Process an announcement request packet.
+ We don't respond immediately, we just check it's a request for
+ our workgroup and then set the flag telling the announce code
+ in nmbd_sendannounce.c:announce_my_server_names that an
+ announcement is needed soon.
+******************************************************************/
+
+void process_announce_request(struct subnet_record *subrec, struct packet_struct *p, const char *buf)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ struct work_record *work;
+ unstring workgroup_name;
+
+ pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name);
+ DEBUG(3,("process_announce_request: Announce request from %s IP %s to %s.\n",
+ nmb_namestr(&dgram->source_name), inet_ntoa(p->ip),
+ nmb_namestr(&dgram->dest_name)));
+
+ /* We only send announcement requests on our workgroup. */
+ if(strequal(workgroup_name, lp_workgroup()) == False) {
+ DEBUG(7,("process_announce_request: Ignoring announce request for workgroup %s.\n",
+ workgroup_name));
+ goto done;
+ }
+
+ if((work = find_workgroup_on_subnet(subrec, workgroup_name)) == NULL) {
+ DEBUG(0,("process_announce_request: Unable to find workgroup %s on subnet !\n",
+ workgroup_name));
+ goto done;
+ }
+
+ work->needannounce = True;
+done:
+ return;
+}
+
+/*******************************************************************
+ Process a LanMan announcement request packet.
+ We don't respond immediately, we just check it's a request for
+ our workgroup and then set the flag telling that we have found
+ a LanMan client (DOS or OS/2) and that we will have to start
+ sending LanMan announcements (unless specifically disabled
+ through the "lm announce" parameter in smb.conf)
+******************************************************************/
+
+void process_lm_announce_request(struct subnet_record *subrec, struct packet_struct *p, const char *buf, int len)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ unstring workgroup_name;
+
+ pull_ascii_nstring(workgroup_name, sizeof(workgroup_name), dgram->dest_name.name);
+ DEBUG(3,("process_lm_announce_request: Announce request from %s IP %s to %s.\n",
+ nmb_namestr(&dgram->source_name), inet_ntoa(p->ip),
+ nmb_namestr(&dgram->dest_name)));
+
+ /* We only send announcement requests on our workgroup. */
+ if(strequal(workgroup_name, lp_workgroup()) == False) {
+ DEBUG(7,("process_lm_announce_request: Ignoring announce request for workgroup %s.\n",
+ workgroup_name));
+ goto done;
+ }
+
+ if(find_workgroup_on_subnet(subrec, workgroup_name) == NULL) {
+ DEBUG(0,("process_announce_request: Unable to find workgroup %s on subnet !\n",
+ workgroup_name));
+ goto done;
+ }
+
+ found_lm_clients = True;
+
+done:
+ return;
+}
diff --git a/source3/nmbd/nmbd_incomingrequests.c b/source3/nmbd/nmbd_incomingrequests.c
new file mode 100644
index 0000000..06c486d
--- /dev/null
+++ b/source3/nmbd/nmbd_incomingrequests.c
@@ -0,0 +1,590 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ This file contains all the code to process NetBIOS requests coming
+ in on port 137. It does not deal with the code needed to service
+ WINS server requests, but only broadcast and unicast requests.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+/****************************************************************************
+Send a name release response.
+**************************************************************************/
+
+static void send_name_release_response(int rcode, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ char rdata[6];
+
+ memcpy(&rdata[0], &nmb->additional->rdata[0], 6);
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ rcode, /* Result code. */
+ NMB_REL, /* nmbd type code. */
+ NMB_NAME_RELEASE_OPCODE, /* opcode. */
+ 0, /* ttl. */
+ rdata, /* data to send. */
+ 6); /* data length. */
+}
+
+/****************************************************************************
+Process a name release packet on a broadcast subnet.
+Ignore it if it's not one of our names.
+****************************************************************************/
+
+void process_name_release_request(struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct in_addr owner_ip;
+ struct nmb_name *question = &nmb->question.question_name;
+ unstring qname;
+ bool bcast = nmb->header.nm_flags.bcast;
+ uint16_t nb_flags = get_nb_flags(nmb->additional->rdata);
+ bool group = (nb_flags & NB_GROUP) ? True : False;
+ struct name_record *namerec;
+ int rcode = 0;
+
+ putip((char *)&owner_ip,&nmb->additional->rdata[2]);
+
+ if(!bcast) {
+ /* We should only get broadcast name release packets here.
+ Anyone trying to release unicast should be going to a WINS
+ server. If the code gets here, then either we are not a wins
+ server and they sent it anyway, or we are a WINS server and
+ the request was malformed. Either way, log an error here.
+ and send an error reply back.
+ */
+ DEBUG(0,("process_name_release_request: unicast name release request \
+received for name %s from IP %s on subnet %s. Error - should be sent to WINS server\n",
+ nmb_namestr(question), inet_ntoa(owner_ip), subrec->subnet_name));
+
+ send_name_release_response(FMT_ERR, p);
+ return;
+ }
+
+ DEBUG(3,("process_name_release_request: Name release on name %s, \
+subnet %s from owner IP %s\n",
+ nmb_namestr(&nmb->question.question_name),
+ subrec->subnet_name, inet_ntoa(owner_ip)));
+
+ /* If someone is releasing a broadcast group name, just ignore it. */
+ if( group && !ismyip_v4(owner_ip) )
+ return;
+
+ /*
+ * Code to work around a bug in FTP OnNet software NBT implementation.
+ * They do a broadcast name release for WORKGROUP<0> and WORKGROUP<1e>
+ * names and *don't set the group bit* !!!!!
+ */
+
+ pull_ascii_nstring(qname, sizeof(qname), question->name);
+ if( !group && !ismyip_v4(owner_ip) && strequal(qname, lp_workgroup()) &&
+ ((question->name_type == 0x0) || (question->name_type == 0x1e))) {
+ DEBUG(6,("process_name_release_request: FTP OnNet bug workaround. Ignoring \
+group release name %s from IP %s on subnet %s with no group bit set.\n",
+ nmb_namestr(question), inet_ntoa(owner_ip), subrec->subnet_name ));
+ return;
+ }
+
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ /* We only care about someone trying to release one of our names. */
+ if( namerec && ( (namerec->data.source == SELF_NAME)
+ || (namerec->data.source == PERMANENT_NAME) ) ) {
+ rcode = ACT_ERR;
+ DEBUG(0, ("process_name_release_request: Attempt to release name %s from IP %s \
+on subnet %s being rejected as it is one of our names.\n",
+ nmb_namestr(&nmb->question.question_name), inet_ntoa(owner_ip), subrec->subnet_name));
+ }
+
+ if(rcode == 0)
+ return;
+
+ /* Send a NAME RELEASE RESPONSE (pos/neg) see rfc1002.txt 4.2.10-11 */
+ send_name_release_response(rcode, p);
+}
+
+/****************************************************************************
+Send a name registration response.
+**************************************************************************/
+
+static void send_name_registration_response(int rcode, int ttl, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ char rdata[6];
+
+ memcpy(&rdata[0], &nmb->additional->rdata[0], 6);
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ rcode, /* Result code. */
+ NMB_REG, /* nmbd type code. */
+ NMB_NAME_REG_OPCODE, /* opcode. */
+ ttl, /* ttl. */
+ rdata, /* data to send. */
+ 6); /* data length. */
+}
+
+/****************************************************************************
+Process a name refresh request on a broadcast subnet.
+**************************************************************************/
+
+void process_name_refresh_request(struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ bool bcast = nmb->header.nm_flags.bcast;
+ struct in_addr from_ip;
+
+ putip((char *)&from_ip,&nmb->additional->rdata[2]);
+
+ if(!bcast) {
+ /* We should only get broadcast name refresh packets here.
+ Anyone trying to refresh unicast should be going to a WINS
+ server. If the code gets here, then either we are not a wins
+ server and they sent it anyway, or we are a WINS server and
+ the request was malformed. Either way, log an error here.
+ and send an error reply back.
+ */
+ DEBUG(0,("process_name_refresh_request: unicast name registration request \
+received for name %s from IP %s on subnet %s.\n",
+ nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+ DEBUG(0,("Error - should be sent to WINS server\n"));
+
+ send_name_registration_response(FMT_ERR, 0, p);
+ return;
+ }
+
+ /* Just log a message. We really don't care about broadcast name refreshes. */
+
+ DEBUG(3,("process_name_refresh_request: Name refresh for name %s \
+IP %s on subnet %s\n", nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+}
+
+/****************************************************************************
+Process a name registration request on a broadcast subnet.
+**************************************************************************/
+
+void process_name_registration_request(struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ bool bcast = nmb->header.nm_flags.bcast;
+ uint16_t nb_flags = get_nb_flags(nmb->additional->rdata);
+ bool group = (nb_flags & NB_GROUP) ? True : False;
+ struct name_record *namerec = NULL;
+ int ttl = nmb->additional->ttl;
+ struct in_addr from_ip;
+
+ putip((char *)&from_ip,&nmb->additional->rdata[2]);
+
+ if(!bcast) {
+ /* We should only get broadcast name registration packets here.
+ Anyone trying to register unicast should be going to a WINS
+ server. If the code gets here, then either we are not a wins
+ server and they sent it anyway, or we are a WINS server and
+ the request was malformed. Either way, log an error here.
+ and send an error reply back.
+ */
+ DEBUG(0,("process_name_registration_request: unicast name registration request \
+received for name %s from IP %s on subnet %s. Error - should be sent to WINS server\n",
+ nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+
+ send_name_registration_response(FMT_ERR, 0, p);
+ return;
+ }
+
+ DEBUG(3,("process_name_registration_request: Name registration for name %s \
+IP %s on subnet %s\n", nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+
+ /* See if the name already exists. */
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ /*
+ * If the name being registered exists and is a WINS_PROXY_NAME
+ * then delete the WINS proxy name entry so we don't reply erroneously
+ * later to queries.
+ */
+
+ if((namerec != NULL) && (namerec->data.source == WINS_PROXY_NAME)) {
+ remove_name_from_namelist( subrec, namerec );
+ namerec = NULL;
+ }
+
+ if (!group) {
+ /* Unique name. */
+
+ if( (namerec != NULL)
+ && ( (namerec->data.source == SELF_NAME)
+ || (namerec->data.source == PERMANENT_NAME)
+ || NAME_GROUP(namerec) ) ) {
+ /* No-one can register one of Samba's names, nor can they
+ register a name that's a group name as a unique name */
+
+ send_name_registration_response(ACT_ERR, 0, p);
+ return;
+ } else if(namerec != NULL) {
+ /* Update the namelist record with the new information. */
+ namerec->data.ip[0] = from_ip;
+ update_name_ttl(namerec, ttl);
+
+ DEBUG(3,("process_name_registration_request: Updated name record %s \
+with IP %s on subnet %s\n",nmb_namestr(&namerec->name),inet_ntoa(from_ip), subrec->subnet_name));
+ return;
+ }
+ } else {
+ /* Group name. */
+
+ if( (namerec != NULL)
+ && !NAME_GROUP(namerec)
+ && ( (namerec->data.source == SELF_NAME)
+ || (namerec->data.source == PERMANENT_NAME) ) ) {
+ /* Disallow group names when we have a unique name. */
+ send_name_registration_response(ACT_ERR, 0, p);
+ return;
+ }
+ }
+}
+
+/****************************************************************************
+This is used to sort names for a name status into a sensible order.
+We put our own names first, then in alphabetical order.
+**************************************************************************/
+
+static int status_compare(char *n1,char *n2)
+{
+ unstring name1, name2;
+ int l1,l2,l3;
+
+ memset(name1, '\0', sizeof(name1));
+ memset(name2, '\0', sizeof(name2));
+ pull_ascii_nstring(name1, sizeof(name1), n1);
+ pull_ascii_nstring(name2, sizeof(name2), n2);
+ n1 = name1;
+ n2 = name2;
+
+ /* It's a bit tricky because the names are space padded */
+ for (l1=0;l1<15 && n1[l1] && n1[l1] != ' ';l1++)
+ ;
+ for (l2=0;l2<15 && n2[l2] && n2[l2] != ' ';l2++)
+ ;
+ l3 = strlen(lp_netbios_name());
+
+ if ((l1==l3) && strncmp(n1,lp_netbios_name(),l3) == 0 &&
+ (l2!=l3 || strncmp(n2,lp_netbios_name(),l3) != 0))
+ return -1;
+
+ if ((l2==l3) && strncmp(n2,lp_netbios_name(),l3) == 0 &&
+ (l1!=l3 || strncmp(n1,lp_netbios_name(),l3) != 0))
+ return 1;
+
+ return memcmp(n1,n2,sizeof(name1));
+}
+
+/****************************************************************************
+ Process a node status query
+ ****************************************************************************/
+
+void process_node_status_request(struct subnet_record *subrec, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ unstring qname;
+ int ques_type = nmb->question.question_name.name_type;
+ char rdata[MAX_DGRAM_SIZE];
+ char *countptr, *buf, *bufend, *buf0;
+ int names_added,i;
+ struct name_record *namerec = NULL;
+
+ pull_ascii_nstring(qname, sizeof(qname), nmb->question.question_name.name);
+
+ DEBUG(3,("process_node_status_request: status request for name %s from IP %s on \
+subnet %s.\n", nmb_namestr(&nmb->question.question_name), inet_ntoa(p->ip), subrec->subnet_name));
+
+ if(find_name_on_subnet(subrec, &nmb->question.question_name, FIND_SELF_NAME) == 0) {
+ DEBUG(1,("process_node_status_request: status request for name %s from IP %s on \
+subnet %s - name not found.\n", nmb_namestr(&nmb->question.question_name),
+ inet_ntoa(p->ip), subrec->subnet_name));
+
+ return;
+ }
+
+ /* this is not an exact calculation. the 46 is for the stats buffer
+ and the 60 is to leave room for the header etc */
+ bufend = &rdata[MAX_DGRAM_SIZE-1] - (18 + 46 + 60);
+ countptr = buf = rdata;
+ buf += 1;
+ buf0 = buf;
+
+ names_added = 0;
+
+ namerec = subrec->namelist;
+
+ while (PTR_DIFF(bufend, buf) > 0) {
+ if( (namerec->data.source == SELF_NAME) || (namerec->data.source == PERMANENT_NAME) ) {
+ int name_type = namerec->name.name_type;
+ unstring name;
+
+ pull_ascii_nstring(name, sizeof(name), namerec->name.name);
+ if (!strupper_m(name)) {
+ DEBUG(2,("strupper_m %s failed\n", name));
+ return;
+ }
+ if (!strequal(name,"*") &&
+ !strequal(name,"__SAMBA__") &&
+ (name_type < 0x1b || name_type >= 0x20 ||
+ ques_type < 0x1b || ques_type >= 0x20 ||
+ strequal(qname, name))) {
+ /* Start with the name. */
+ size_t len;
+ push_ascii_nstring(buf, name);
+ len = strlen(buf);
+ memset(buf + len, ' ', MAX_NETBIOSNAME_LEN - len - 1);
+ buf[MAX_NETBIOSNAME_LEN - 1] = '\0';
+
+ /* Put the name type and netbios flags in the buffer. */
+
+ buf[15] = name_type;
+ set_nb_flags( &buf[16],namerec->data.nb_flags );
+ buf[16] |= NB_ACTIVE; /* all our names are active */
+
+ names_added++;
+ }
+ }
+
+ /* Remove duplicate names. */
+ if (names_added > 1) {
+ /* TODO: should use a real type and
+ TYPESAFE_QSORT() */
+ qsort( buf0, names_added, 18, QSORT_CAST status_compare );
+ }
+
+ for( i=1; i < names_added ; i++ ) {
+ if (memcmp(buf0 + 18*i,buf0 + 18*(i-1),16) == 0) {
+ names_added--;
+ if (names_added == i)
+ break;
+ memmove(buf0 + 18*i,buf0 + 18*(i+1),18*(names_added-i));
+ i--;
+ }
+ }
+
+ buf = buf0 + 18*names_added;
+
+ namerec = namerec->next;
+
+ if (!namerec) {
+ /* End of the subnet specific name list. Now
+ add the names on the unicast subnet . */
+ struct subnet_record *uni_subrec = unicast_subnet;
+
+ if (uni_subrec != subrec) {
+ subrec = uni_subrec;
+ namerec = subrec->namelist;
+ }
+ }
+ if (!namerec)
+ break;
+
+ }
+
+ SCVAL(countptr,0,names_added);
+
+ /* We don't send any stats as they could be used to attack
+ the protocol. */
+ memset(buf,'\0',46);
+
+ buf += 46;
+
+ /* Send a NODE STATUS RESPONSE */
+ reply_netbios_packet(p, /* Packet to reply to. */
+ 0, /* Result code. */
+ NMB_STATUS, /* nmbd type code. */
+ NMB_NAME_QUERY_OPCODE, /* opcode. */
+ 0, /* ttl. */
+ rdata, /* data to send. */
+ PTR_DIFF(buf,rdata)); /* data length. */
+}
+
+
+/***************************************************************************
+Process a name query.
+
+For broadcast name queries:
+
+ - Only reply if the query is for one of YOUR names.
+ - NEVER send a negative response to a broadcast query.
+
+****************************************************************************/
+
+void process_name_query_request(struct subnet_record *subrec, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ int name_type = question->name_type;
+ bool bcast = nmb->header.nm_flags.bcast;
+ int ttl=0;
+ int rcode = 0;
+ char *prdata = NULL;
+ char rdata[6];
+ bool success = False;
+ struct name_record *namerec = NULL;
+ int reply_data_len = 0;
+ int i;
+
+ DEBUG(3,("process_name_query_request: Name query from %s on subnet %s for name %s\n",
+ inet_ntoa(p->ip), subrec->subnet_name, nmb_namestr(question)));
+
+ /* Look up the name in the cache - if the request is a broadcast request that
+ came from a subnet we don't know about then search all the broadcast subnets
+ for a match (as we don't know what interface the request came in on). */
+
+ if(subrec == remote_broadcast_subnet)
+ namerec = find_name_for_remote_broadcast_subnet( question, FIND_ANY_NAME);
+ else
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ /* Check if it is a name that expired */
+ if (namerec &&
+ ((namerec->data.death_time != PERMANENT_TTL) &&
+ (namerec->data.death_time < p->timestamp))) {
+ DEBUG(5,("process_name_query_request: expired name %s\n", nmb_namestr(&namerec->name)));
+ namerec = NULL;
+ }
+
+ if (namerec) {
+ /*
+ * Always respond to unicast queries.
+ * Don't respond to broadcast queries unless the query is for
+ * a name we own, a Primary Domain Controller name, or a WINS_PROXY
+ * name with type 0 or 0x20. WINS_PROXY names are only ever added
+ * into the namelist if we were configured as a WINS proxy.
+ */
+
+ if (!bcast ||
+ (bcast && ((name_type == 0x1b) ||
+ (namerec->data.source == SELF_NAME) ||
+ (namerec->data.source == PERMANENT_NAME) ||
+ ((namerec->data.source == WINS_PROXY_NAME) &&
+ ((name_type == 0) || (name_type == 0x20)))))) {
+ /* The requested name is a directed query, or it's SELF or PERMANENT or WINS_PROXY,
+ or it's a Domain Master type. */
+
+ /*
+ * If this is a WINS_PROXY_NAME, then check that none of the IP
+ * addresses we are returning is on the same broadcast subnet
+ * as the requesting packet. If it is then don't reply as the
+ * actual machine will be replying also and we don't want two
+ * replies to a broadcast query.
+ */
+
+ if (namerec->data.source == WINS_PROXY_NAME) {
+ for( i = 0; i < namerec->data.num_ips; i++) {
+ if (same_net_v4(namerec->data.ip[i], subrec->myip, subrec->mask_ip)) {
+ DEBUG(5,("process_name_query_request: name %s is a WINS proxy name and is also on the same subnet (%s) as the requester. Not replying.\n",
+ nmb_namestr(&namerec->name), subrec->subnet_name ));
+ return;
+ }
+ }
+ }
+
+ ttl = (namerec->data.death_time != PERMANENT_TTL) ?
+ namerec->data.death_time - p->timestamp : lp_max_ttl();
+
+ /* Copy all known ip addresses into the return data. */
+ /* Optimise for the common case of one IP address so
+ we don't need a malloc. */
+
+ if (namerec->data.num_ips == 1) {
+ prdata = rdata;
+ } else {
+ if ((prdata = (char *)SMB_MALLOC( namerec->data.num_ips * 6 )) == NULL) {
+ DEBUG(0,("process_name_query_request: malloc fail !\n"));
+ return;
+ }
+ }
+
+ for (i = 0; i < namerec->data.num_ips; i++) {
+ set_nb_flags(&prdata[i*6],namerec->data.nb_flags);
+ putip((char *)&prdata[2+(i*6)], &namerec->data.ip[i]);
+ }
+
+ sort_query_replies(prdata, i, p->ip);
+
+ reply_data_len = namerec->data.num_ips * 6;
+ success = True;
+ }
+ }
+
+ /*
+ * If a machine is broadcasting a name lookup request and we have lp_wins_proxy()
+ * set we should initiate a WINS query here. On success we add the resolved name
+ * into our namelist with a type of WINS_PROXY_NAME and then reply to the query.
+ */
+
+ if(!success && (namerec == NULL) && we_are_a_wins_client() && lp_wins_proxy() &&
+ bcast && (subrec != remote_broadcast_subnet)) {
+ make_wins_proxy_name_query_request( subrec, p, question );
+ return;
+ }
+
+ if (!success && bcast) {
+ if(prdata != rdata)
+ SAFE_FREE(prdata);
+ return; /* Never reply with a negative response to broadcasts. */
+ }
+
+ /*
+ * Final check. From observation, if a unicast packet is sent
+ * to a non-WINS server with the recursion desired bit set
+ * then never send a negative response.
+ */
+
+ if(!success && !bcast && nmb->header.nm_flags.recursion_desired) {
+ if(prdata != rdata)
+ SAFE_FREE(prdata);
+ return;
+ }
+
+ if (success) {
+ rcode = 0;
+ DEBUG(3,("OK\n"));
+ } else {
+ rcode = NAM_ERR;
+ DEBUG(3,("UNKNOWN\n"));
+ }
+
+ /* See rfc1002.txt 4.2.13. */
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ rcode, /* Result code. */
+ NMB_QUERY, /* nmbd type code. */
+ NMB_NAME_QUERY_OPCODE, /* opcode. */
+ ttl, /* ttl. */
+ prdata, /* data to send. */
+ reply_data_len); /* data length. */
+
+ if(prdata != rdata)
+ SAFE_FREE(prdata);
+}
diff --git a/source3/nmbd/nmbd_lmhosts.c b/source3/nmbd/nmbd_lmhosts.c
new file mode 100644
index 0000000..ecf70bd
--- /dev/null
+++ b/source3/nmbd/nmbd_lmhosts.c
@@ -0,0 +1,105 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Revision History:
+
+ Handle lmhosts file reading.
+
+*/
+
+#include "includes.h"
+#include "../libcli/nbt/libnbt.h"
+#include "nmbd/nmbd.h"
+
+/****************************************************************************
+Load a lmhosts file.
+****************************************************************************/
+
+void load_lmhosts_file(const char *fname)
+{
+ char *name = NULL;
+ int name_type;
+ struct sockaddr_storage ss;
+ TALLOC_CTX *ctx = talloc_init("load_lmhosts_file");
+ FILE *fp = startlmhosts( fname );
+
+ if (!fp) {
+ DEBUG(2,("load_lmhosts_file: Can't open lmhosts file %s. Error was %s\n",
+ fname, strerror(errno)));
+ TALLOC_FREE(ctx);
+ return;
+ }
+
+ while (getlmhostsent(ctx, fp, &name, &name_type, &ss) ) {
+ struct in_addr ipaddr;
+ struct subnet_record *subrec = NULL;
+ enum name_source source = LMHOSTS_NAME;
+
+ if (ss.ss_family != AF_INET) {
+ TALLOC_FREE(name);
+ continue;
+ }
+
+ ipaddr = ((struct sockaddr_in *)&ss)->sin_addr;
+
+ /* We find a relevant subnet to put this entry on, then add it. */
+ /* Go through all the broadcast subnets and see if the mask matches. */
+ for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ if(same_net_v4(ipaddr, subrec->bcast_ip, subrec->mask_ip))
+ break;
+ }
+
+ /* If none match add the name to the remote_broadcast_subnet. */
+ if(subrec == NULL)
+ subrec = remote_broadcast_subnet;
+
+ if(name_type == -1) {
+ /* Add the (0) and (0x20) names directly into the namelist for this subnet. */
+ (void)add_name_to_subnet(subrec,name,0x00,(uint16_t)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr);
+ (void)add_name_to_subnet(subrec,name,0x20,(uint16_t)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr);
+ } else {
+ /* Add the given name type to the subnet namelist. */
+ (void)add_name_to_subnet(subrec,name,name_type,(uint16_t)NB_ACTIVE,PERMANENT_TTL,source,1,&ipaddr);
+ }
+ }
+
+ TALLOC_FREE(ctx);
+ endlmhosts(fp);
+}
+
+/****************************************************************************
+ Find a name read from the lmhosts file. We secretly check the names on
+ the remote_broadcast_subnet as if the name was added to a regular broadcast
+ subnet it will be found by normal name query processing.
+****************************************************************************/
+
+bool find_name_in_lmhosts(struct nmb_name *nmbname, struct name_record **namerecp)
+{
+ struct name_record *namerec;
+
+ *namerecp = NULL;
+
+ if((namerec = find_name_on_subnet(remote_broadcast_subnet, nmbname, FIND_ANY_NAME))==NULL)
+ return False;
+
+ if(!NAME_IS_ACTIVE(namerec) || (namerec->data.source != LMHOSTS_NAME))
+ return False;
+
+ *namerecp = namerec;
+ return True;
+}
diff --git a/source3/nmbd/nmbd_logonnames.c b/source3/nmbd/nmbd_logonnames.c
new file mode 100644
index 0000000..cc4776a
--- /dev/null
+++ b/source3/nmbd/nmbd_logonnames.c
@@ -0,0 +1,172 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "nmbd/nmbd.h"
+
+extern uint16_t samba_nb_type; /* Samba's NetBIOS type. */
+
+/****************************************************************************
+ Fail to become a Logon server on a subnet.
+****************************************************************************/
+
+static void become_logon_server_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *fail_name)
+{
+ unstring failname;
+ struct work_record *work;
+ struct server_record *servrec;
+
+ pull_ascii_nstring(failname, sizeof(failname), fail_name->name);
+ work = find_workgroup_on_subnet(subrec, failname);
+ if(!work) {
+ DEBUG(0,("become_logon_server_fail: Error - cannot find \
+workgroup %s on subnet %s\n", failname, subrec->subnet_name));
+ return;
+ }
+
+ if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) {
+ DEBUG(0,("become_logon_server_fail: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), failname, subrec->subnet_name));
+ work->log_state = LOGON_NONE;
+ return;
+ }
+
+ /* Set the state back to LOGON_NONE. */
+ work->log_state = LOGON_NONE;
+
+ servrec->serv.type &= ~SV_TYPE_DOMAIN_CTRL;
+
+ DEBUG(0,("become_logon_server_fail: Failed to become a domain master for \
+workgroup %s on subnet %s. Couldn't register name %s.\n",
+ work->work_group, subrec->subnet_name, nmb_namestr(fail_name)));
+
+}
+
+/****************************************************************************
+ Become a Logon server on a subnet.
+ ****************************************************************************/
+
+static void become_logon_server_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *registered_name,
+ uint16_t nb_flags,
+ int ttl, struct in_addr registered_ip)
+{
+ unstring reg_name;
+ struct work_record *work;
+ struct server_record *servrec;
+
+ pull_ascii_nstring(reg_name, sizeof(reg_name), registered_name->name);
+ work = find_workgroup_on_subnet( subrec, reg_name);
+ if(!work) {
+ DEBUG(0,("become_logon_server_success: Error - cannot find \
+workgroup %s on subnet %s\n", reg_name, subrec->subnet_name));
+ return;
+ }
+
+ if((servrec = find_server_in_workgroup( work, lp_netbios_name())) == NULL) {
+ DEBUG(0,("become_logon_server_success: Error - cannot find server %s \
+in workgroup %s on subnet %s\n",
+ lp_netbios_name(), reg_name, subrec->subnet_name));
+ work->log_state = LOGON_NONE;
+ return;
+ }
+
+ /* Set the state in the workgroup structure. */
+ work->log_state = LOGON_SRV; /* Become domain master. */
+
+ /* Update our server status. */
+ servrec->serv.type |= (SV_TYPE_NT|SV_TYPE_DOMAIN_MEMBER);
+ /* To allow Win95 policies to load we need to set type domain
+ controller.
+ */
+ servrec->serv.type |= SV_TYPE_DOMAIN_CTRL;
+
+ /* Tell the namelist writer to write out a change. */
+ subrec->work_changed = True;
+
+ /*
+ * Add the WORKGROUP<1C> name to the UNICAST subnet with the IP address
+ * for this subnet so we will respond to queries on this name.
+ */
+
+ {
+ struct nmb_name nmbname;
+ make_nmb_name(&nmbname,lp_workgroup(),0x1c);
+ insert_permanent_name_into_unicast(subrec, &nmbname, 0x1c);
+ }
+
+ DEBUG(0,("become_logon_server_success: Samba is now a logon server \
+for workgroup %s on subnet %s\n", work->work_group, subrec->subnet_name));
+}
+
+/*******************************************************************
+ Become a logon server by attempting to register the WORKGROUP<1c>
+ group name.
+******************************************************************/
+
+static void become_logon_server(struct subnet_record *subrec,
+ struct work_record *work)
+{
+ DEBUG(2,("become_logon_server: Attempting to become logon server for workgroup %s \
+on subnet %s\n", work->work_group,subrec->subnet_name));
+
+ DEBUG(3,("become_logon_server: go to first stage: register %s<1c> name\n",
+ work->work_group));
+ work->log_state = LOGON_WAIT;
+
+ register_name(subrec, work->work_group,0x1c,samba_nb_type|NB_GROUP,
+ become_logon_server_success,
+ become_logon_server_fail, NULL);
+}
+
+/*****************************************************************************
+ Add the internet group <1c> logon names by unicast and broadcast.
+ ****************************************************************************/
+
+void add_logon_names(void)
+{
+ struct subnet_record *subrec;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ struct work_record *work = find_workgroup_on_subnet(subrec, lp_workgroup());
+
+ if (work && (work->log_state == LOGON_NONE)) {
+ struct nmb_name nmbname;
+ make_nmb_name(&nmbname,lp_workgroup(),0x1c);
+
+ if (find_name_on_subnet(subrec, &nmbname, FIND_SELF_NAME) == NULL) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "add_domain_logon_names:\n" );
+ dbgtext( "Attempting to become logon server " );
+ dbgtext( "for workgroup %s ", lp_workgroup() );
+ dbgtext( "on subnet %s\n", subrec->subnet_name );
+ }
+ become_logon_server(subrec, work);
+ }
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_mynames.c b/source3/nmbd/nmbd_mynames.c
new file mode 100644
index 0000000..7efeb5c
--- /dev/null
+++ b/source3/nmbd/nmbd_mynames.c
@@ -0,0 +1,323 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+extern uint16_t samba_nb_type; /* Samba's NetBIOS type. */
+
+static const char **mynames = NULL;
+
+static bool add_unique_netbios_name(const char *name)
+{
+ size_t i, num_names = talloc_array_length(mynames);
+ char *str = NULL;
+ const char **tmp = NULL;
+
+ for (i=0; i<num_names; i++) {
+ if (strequal(name, mynames[i])) {
+ return true;
+ }
+ }
+
+ str = talloc_strdup(NULL, name);
+ if (str == NULL) {
+ return false;
+ }
+
+ tmp = talloc_realloc(NULL, mynames, const char *, num_names+1);
+ if (tmp == NULL) {
+ TALLOC_FREE(str);
+ return false;
+ }
+ tmp[num_names] = talloc_move(tmp, &str);
+ mynames = tmp;
+ return true;
+}
+
+bool nmbd_init_my_netbios_names(void)
+{
+ const char *name = lp_netbios_name();
+ const char **aliases = lp_netbios_aliases();
+
+ TALLOC_FREE(mynames);
+
+ if (name[0] != '\0') {
+ bool ok = add_unique_netbios_name(name);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ if (aliases == NULL) {
+ return true;
+ }
+
+ while (*aliases != NULL) {
+ bool ok = add_unique_netbios_name(*aliases);
+ if (!ok) {
+ return false;
+ }
+ aliases += 1;
+ }
+
+ return true;
+}
+
+const char *my_netbios_names(int i)
+{
+ size_t num_names = talloc_array_length(mynames);
+
+ if ((i >= 0) && (i < num_names)) {
+ return mynames[i];
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Fail function when registering my netbios names.
+**************************************************************************/
+
+static void my_name_register_failed(struct subnet_record *subrec,
+ struct response_record *rrec, struct nmb_name *nmbname)
+{
+ DEBUG(0,("my_name_register_failed: Failed to register my name %s on subnet %s.\n",
+ nmb_namestr(nmbname), subrec->subnet_name));
+}
+
+
+/****************************************************************************
+ Add my workgroup and my given names to one subnet
+ Also add the magic Samba names.
+**************************************************************************/
+
+void register_my_workgroup_one_subnet(struct subnet_record *subrec)
+{
+ int i;
+
+ struct work_record *work;
+
+ /* Create the workgroup on the subnet. */
+ if((work = create_workgroup_on_subnet(subrec, lp_workgroup(),
+ PERMANENT_TTL)) == NULL) {
+ DEBUG(0,("register_my_workgroup_and_names: Failed to create my workgroup %s on subnet %s. \
+Exiting.\n", lp_workgroup(), subrec->subnet_name));
+ return;
+ }
+
+ /* Each subnet entry, except for the wins_server_subnet has
+ the magic Samba names. */
+ add_samba_names_to_subnet(subrec);
+
+ /* Register all our names including aliases. */
+ for (i=0; my_netbios_names(i); i++) {
+ register_name(subrec, my_netbios_names(i),0x20,samba_nb_type,
+ NULL,
+ my_name_register_failed, NULL);
+ register_name(subrec, my_netbios_names(i),0x03,samba_nb_type,
+ NULL,
+ my_name_register_failed, NULL);
+ register_name(subrec, my_netbios_names(i),0x00,samba_nb_type,
+ NULL,
+ my_name_register_failed, NULL);
+ }
+
+ /* Initiate election processing, register the workgroup names etc. */
+ initiate_myworkgroup_startup(subrec, work);
+}
+
+/*******************************************************************
+ Utility function to add a name to the unicast subnet, or add in
+ our IP address if it already exists.
+******************************************************************/
+
+static void insert_refresh_name_into_unicast( struct subnet_record *subrec,
+ struct nmb_name *nmbname, uint16_t nb_type )
+{
+ struct name_record *namerec;
+
+ if (!we_are_a_wins_client()) {
+ insert_permanent_name_into_unicast(subrec, nmbname, nb_type);
+ return;
+ }
+
+ if((namerec = find_name_on_subnet(unicast_subnet, nmbname, FIND_SELF_NAME)) == NULL) {
+ unstring name;
+ pull_ascii_nstring(name, sizeof(name), nmbname->name);
+ /* The name needs to be created on the unicast subnet. */
+ (void)add_name_to_subnet( unicast_subnet, name,
+ nmbname->name_type, nb_type,
+ MIN(lp_max_ttl(), MAX_REFRESH_TIME), SELF_NAME, 1, &subrec->myip);
+ } else {
+ /* The name already exists on the unicast subnet. Add our local
+ IP for the given broadcast subnet to the name. */
+ add_ip_to_name_record( namerec, subrec->myip);
+ }
+}
+
+/****************************************************************************
+ Add my workgroup and my given names to the subnet lists.
+ Also add the magic Samba names.
+**************************************************************************/
+
+bool register_my_workgroup_and_names(void)
+{
+ struct subnet_record *subrec;
+ int i;
+ const char **cluster_addresses = NULL;
+
+ for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ register_my_workgroup_one_subnet(subrec);
+ }
+
+ /* We still need to add the magic Samba
+ names and the netbios names to the unicast subnet directly. This is
+ to allow unicast node status requests and queries to still work
+ in a broadcast only environment. */
+
+ add_samba_names_to_subnet(unicast_subnet);
+
+ for (i=0; my_netbios_names(i); i++) {
+ for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ /*
+ * Ensure all the IP addresses are added if we are multihomed.
+ */
+ struct nmb_name nmbname;
+
+ make_nmb_name(&nmbname, my_netbios_names(i),0x20);
+ insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type);
+
+ make_nmb_name(&nmbname, my_netbios_names(i),0x3);
+ insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type);
+
+ make_nmb_name(&nmbname, my_netbios_names(i),0x0);
+ insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type);
+ }
+ }
+
+ /*
+ * add in any cluster addresses. We need to response to these,
+ * but not listen on them. This allows us to run nmbd on every
+ * node in the cluster, and have all of them register with a
+ * WINS server correctly
+ */
+ if (lp_clustering()) {
+ cluster_addresses = lp_cluster_addresses();
+ }
+ if (cluster_addresses) {
+ int a, n;
+ unsigned name_types[] = {0x20, 0x3, 0x0};
+
+ for (i=0; my_netbios_names(i); i++) {
+ for(subrec = FIRST_SUBNET; subrec; subrec = subrec->next) {
+ for (n=0;n<ARRAY_SIZE(name_types);n++) {
+ struct name_record *namerec;
+ struct nmb_name nmbname;
+ struct in_addr ip;
+ make_nmb_name(&nmbname, my_netbios_names(i), name_types[n]);
+ namerec = find_name_on_subnet(unicast_subnet, &nmbname, FIND_SELF_NAME);
+ if (namerec == NULL) continue;
+ for (a=0;cluster_addresses[a];a++) {
+ ip = interpret_addr2(cluster_addresses[a]);
+ add_ip_to_name_record(namerec, ip);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Add the WORKGROUP<0> and WORKGROUP<1e> group names to the unicast subnet
+ * also for the same reasons.
+ */
+
+ for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ /*
+ * Ensure all the IP addresses are added if we are multihomed.
+ */
+ struct nmb_name nmbname;
+
+ make_nmb_name(&nmbname, lp_workgroup(), 0x0);
+ insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type|NB_GROUP);
+
+ make_nmb_name(&nmbname, lp_workgroup(), 0x1e);
+ insert_refresh_name_into_unicast(subrec, &nmbname, samba_nb_type|NB_GROUP);
+ }
+
+ /*
+ * We need to add the Samba names to the remote broadcast subnet,
+ * as NT 4.x does directed broadcast requests to the *<0x0> name.
+ */
+
+ add_samba_names_to_subnet(remote_broadcast_subnet);
+
+ return True;
+}
+
+/****************************************************************************
+ Remove all the names we registered.
+**************************************************************************/
+
+void release_wins_names(void)
+{
+ struct subnet_record *subrec = unicast_subnet;
+ struct name_record *namerec, *nextnamerec;
+
+ for (namerec = subrec->namelist; namerec; namerec = nextnamerec) {
+ nextnamerec = namerec->next;
+ if( (namerec->data.source == SELF_NAME)
+ && !NAME_IS_DEREGISTERING(namerec) )
+ release_name( subrec, namerec, standard_success_release,
+ NULL, NULL);
+ }
+}
+
+/*******************************************************************
+ Refresh our registered names with WINS
+******************************************************************/
+
+void refresh_my_names(time_t t)
+{
+ struct name_record *namerec;
+
+ if (wins_srv_count() < 1)
+ return;
+
+ for (namerec = unicast_subnet->namelist; namerec; namerec = namerec->next) {
+ /* Each SELF name has an individual time to be refreshed. */
+ if ((namerec->data.source == SELF_NAME) &&
+ (namerec->data.refresh_time < t) &&
+ (namerec->data.death_time != PERMANENT_TTL)) {
+ /* We cheat here and pretend the refresh is going to be
+ successful & update the refresh times. This stops
+ multiple refresh calls being done. We actually
+ deal with refresh failure in the fail_fn.
+ */
+ if (!is_refresh_already_queued(unicast_subnet, namerec)) {
+ wins_refresh_name(namerec);
+ }
+ namerec->data.death_time = t + lp_max_ttl();
+ namerec->data.refresh_time = t + MIN(lp_max_ttl()/2, MAX_REFRESH_TIME);
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_namelistdb.c b/source3/nmbd/nmbd_namelistdb.c
new file mode 100644
index 0000000..141adfa
--- /dev/null
+++ b/source3/nmbd/nmbd_namelistdb.c
@@ -0,0 +1,689 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "nmbd/nmbd.h"
+
+uint16_t samba_nb_type = 0; /* samba's NetBIOS name type */
+
+
+/**************************************************************************
+ Set Samba's NetBIOS name type.
+***************************************************************************/
+
+void set_samba_nb_type(void)
+{
+ if( lp_we_are_a_wins_server() || wins_srv_count() ) {
+ samba_nb_type = NB_HFLAG; /* samba is a 'hybrid' node type. */
+ } else {
+ samba_nb_type = NB_BFLAG; /* samba is broadcast-only node type. */
+ }
+}
+
+/***************************************************************************
+ Convert a NetBIOS name to upper case.
+***************************************************************************/
+
+static bool upcase_name( struct nmb_name *target, const struct nmb_name *source )
+{
+ int i;
+ unstring targ;
+ fstring scope;
+
+ if( NULL != source ) {
+ memcpy( target, source, sizeof( struct nmb_name ) );
+ }
+
+ pull_ascii_nstring(targ, sizeof(targ), target->name);
+ if (!strupper_m( targ )) {
+ return false;
+ }
+ push_ascii_nstring( target->name, targ);
+
+ pull_ascii(scope, target->scope, 64, -1, STR_TERMINATE);
+ if (!strupper_m( scope )) {
+ return false;
+ }
+ push_ascii(target->scope, scope, 64, STR_TERMINATE);
+
+ /* fudge... We're using a byte-by-byte compare, so we must be sure that
+ * unused space doesn't have garbage in it.
+ */
+
+ for( i = strlen( target->name ); i < sizeof( target->name ); i++ ) {
+ target->name[i] = '\0';
+ }
+ for( i = strlen( target->scope ); i < sizeof( target->scope ); i++ ) {
+ target->scope[i] = '\0';
+ }
+ return true;
+}
+
+/**************************************************************************
+ Remove a name from the namelist.
+***************************************************************************/
+
+void remove_name_from_namelist(struct subnet_record *subrec,
+ struct name_record *namerec )
+{
+ if (subrec == wins_server_subnet)
+ remove_name_from_wins_namelist(namerec);
+ else {
+ subrec->namelist_changed = True;
+ DLIST_REMOVE(subrec->namelist, namerec);
+ }
+
+ SAFE_FREE(namerec->data.ip);
+ ZERO_STRUCTP(namerec);
+ SAFE_FREE(namerec);
+}
+
+/**************************************************************************
+ Find a name in a subnet.
+**************************************************************************/
+
+struct name_record *find_name_on_subnet(struct subnet_record *subrec,
+ const struct nmb_name *nmbname,
+ bool self_only)
+{
+ struct nmb_name uc_name;
+ struct name_record *name_ret;
+
+ if (!upcase_name( &uc_name, nmbname )) {
+ return NULL;
+ }
+
+ if (subrec == wins_server_subnet) {
+ return find_name_on_wins_subnet(&uc_name, self_only);
+ }
+
+ for( name_ret = subrec->namelist; name_ret; name_ret = name_ret->next) {
+ if (memcmp(&uc_name, &name_ret->name, sizeof(struct nmb_name)) == 0) {
+ break;
+ }
+ }
+
+ if( name_ret ) {
+ /* Self names only - these include permanent names. */
+ if( self_only && (name_ret->data.source != SELF_NAME) && (name_ret->data.source != PERMANENT_NAME) ) {
+ DEBUG( 9, ( "find_name_on_subnet: on subnet %s - self name %s NOT FOUND\n",
+ subrec->subnet_name, nmb_namestr(nmbname) ) );
+ return NULL;
+ }
+
+ DEBUG( 9, ("find_name_on_subnet: on subnet %s - found name %s source=%d\n",
+ subrec->subnet_name, nmb_namestr(nmbname), name_ret->data.source) );
+
+ return name_ret;
+ }
+
+ DEBUG( 9, ( "find_name_on_subnet: on subnet %s - name %s NOT FOUND\n",
+ subrec->subnet_name, nmb_namestr(nmbname) ) );
+
+ return NULL;
+}
+
+/**************************************************************************
+ Find a name over all known broadcast subnets.
+************************************************************************/
+
+struct name_record *find_name_for_remote_broadcast_subnet(struct nmb_name *nmbname,
+ bool self_only)
+{
+ struct subnet_record *subrec;
+ struct name_record *namerec;
+
+ for( subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec) ) {
+ namerec = find_name_on_subnet(subrec, nmbname, self_only);
+ if (namerec) {
+ return namerec;
+ }
+ }
+
+ return NULL;
+}
+
+/**************************************************************************
+ Update the ttl of an entry in a subnet name list.
+***************************************************************************/
+
+void update_name_ttl( struct name_record *namerec, int ttl )
+{
+ time_t time_now = time(NULL);
+
+ if( namerec->data.death_time != PERMANENT_TTL) {
+ namerec->data.death_time = time_now + ttl;
+ }
+
+ namerec->data.refresh_time = time_now + MIN((ttl/2), MAX_REFRESH_TIME);
+
+ if (namerec->subnet == wins_server_subnet) {
+ wins_store_changed_namerec(namerec);
+ } else {
+ namerec->subnet->namelist_changed = True;
+ }
+}
+
+/**************************************************************************
+ Add an entry to a subnet name list.
+***********************************************************************/
+
+bool add_name_to_subnet( struct subnet_record *subrec,
+ const char *name,
+ int type,
+ uint16_t nb_flags,
+ int ttl,
+ enum name_source source,
+ int num_ips,
+ struct in_addr *iplist)
+{
+ bool ret = False;
+ struct name_record *namerec;
+ time_t time_now = time(NULL);
+
+ if (num_ips == 0) {
+ return false;
+ }
+
+ namerec = SMB_MALLOC_P(struct name_record);
+ if( NULL == namerec ) {
+ DEBUG( 0, ( "add_name_to_subnet: malloc fail.\n" ) );
+ return False;
+ }
+
+ memset( (char *)namerec, '\0', sizeof(*namerec) );
+ namerec->data.ip = SMB_MALLOC_ARRAY( struct in_addr, num_ips );
+ if( NULL == namerec->data.ip ) {
+ DEBUG( 0, ( "add_name_to_subnet: malloc fail when creating ip_flgs.\n" ) );
+ ZERO_STRUCTP(namerec);
+ SAFE_FREE(namerec);
+ return False;
+ }
+
+ namerec->subnet = subrec;
+
+ make_nmb_name(&namerec->name, name, type);
+ if (!upcase_name(&namerec->name, NULL )) {
+ SAFE_FREE(namerec->data.ip);
+ SAFE_FREE(namerec);
+ return False;
+ }
+
+ /* Enter the name as active. */
+ namerec->data.nb_flags = nb_flags | NB_ACTIVE;
+ namerec->data.wins_flags = WINS_ACTIVE;
+
+ /* If it's our primary name, flag it as so. */
+ if (strequal( my_netbios_names(0), name )) {
+ namerec->data.nb_flags |= NB_PERM;
+ }
+
+ /* Copy the IPs. */
+ namerec->data.num_ips = num_ips;
+ memcpy( (namerec->data.ip), iplist, num_ips * sizeof(struct in_addr) );
+
+ /* Data source. */
+ namerec->data.source = source;
+
+ /* Setup the death_time and refresh_time. */
+ if (ttl == PERMANENT_TTL) {
+ namerec->data.death_time = PERMANENT_TTL;
+ } else {
+ namerec->data.death_time = time_now + ttl;
+ }
+
+ namerec->data.refresh_time = time_now + MIN((ttl/2), MAX_REFRESH_TIME);
+
+ DEBUG( 3, ( "add_name_to_subnet: Added netbios name %s with first IP %s \
+ttl=%d nb_flags=%2x to subnet %s\n",
+ nmb_namestr( &namerec->name ),
+ inet_ntoa( *iplist ),
+ ttl,
+ (unsigned int)nb_flags,
+ subrec->subnet_name ) );
+
+ /* Now add the record to the name list. */
+
+ if (subrec == wins_server_subnet) {
+ ret = add_name_to_wins_subnet(namerec);
+ /* Free namerec - it's stored in the tdb. */
+ SAFE_FREE(namerec->data.ip);
+ SAFE_FREE(namerec);
+ } else {
+ DLIST_ADD(subrec->namelist, namerec);
+ subrec->namelist_changed = True;
+ ret = True;
+ }
+
+ return ret;
+}
+
+/*******************************************************************
+ Utility function automatically called when a name refresh or register
+ succeeds. By definition this is a SELF_NAME (or we wouldn't be registering
+ it).
+ ******************************************************************/
+
+void standard_success_register(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname, uint16_t nb_flags, int ttl,
+ struct in_addr registered_ip)
+{
+ struct name_record *namerec;
+
+ namerec = find_name_on_subnet( subrec, nmbname, FIND_SELF_NAME);
+ if (namerec == NULL) {
+ unstring name;
+ pull_ascii_nstring(name, sizeof(name), nmbname->name);
+ add_name_to_subnet( subrec, name, nmbname->name_type,
+ nb_flags, ttl, SELF_NAME, 1, &registered_ip );
+ } else {
+ update_name_ttl( namerec, ttl );
+ }
+}
+
+/*******************************************************************
+ Utility function automatically called when a name refresh or register
+ fails. Note that this is only ever called on a broadcast subnet with
+ one IP address per name. This is why it can just delete the name
+ without enumerating the IP addresses. JRA.
+ ******************************************************************/
+
+void standard_fail_register( struct subnet_record *subrec,
+ struct nmb_name *nmbname )
+{
+ struct name_record *namerec;
+
+ namerec = find_name_on_subnet( subrec, nmbname, FIND_SELF_NAME);
+
+ DEBUG( 0, ( "standard_fail_register: Failed to register/refresh name %s \
+on subnet %s\n", nmb_namestr(nmbname), subrec->subnet_name) );
+
+ /* Remove the name from the subnet. */
+ if( namerec ) {
+ remove_name_from_namelist(subrec, namerec);
+ }
+}
+
+/*******************************************************************
+ Utility function to remove an IP address from a name record.
+ ******************************************************************/
+
+static void remove_nth_ip_in_record( struct name_record *namerec, int ind)
+{
+ if( ind != namerec->data.num_ips ) {
+ memmove( (char *)(&namerec->data.ip[ind]),
+ (char *)(&namerec->data.ip[ind+1]),
+ ( namerec->data.num_ips - ind - 1) * sizeof(struct in_addr) );
+ }
+
+ namerec->data.num_ips--;
+ if (namerec->subnet == wins_server_subnet) {
+ wins_store_changed_namerec(namerec);
+ } else {
+ namerec->subnet->namelist_changed = True;
+ }
+}
+
+/*******************************************************************
+ Utility function to check if an IP address exists in a name record.
+ ******************************************************************/
+
+bool find_ip_in_name_record( struct name_record *namerec, struct in_addr ip )
+{
+ int i;
+
+ for(i = 0; i < namerec->data.num_ips; i++) {
+ if(ip_equal_v4( namerec->data.ip[i], ip)) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+/*******************************************************************
+ Utility function to add an IP address to a name record.
+ ******************************************************************/
+
+void add_ip_to_name_record( struct name_record *namerec, struct in_addr new_ip )
+{
+ struct in_addr *new_list;
+
+ /* Don't add one we already have. */
+ if( find_ip_in_name_record( namerec, new_ip )) {
+ return;
+ }
+
+ new_list = SMB_MALLOC_ARRAY( struct in_addr, namerec->data.num_ips + 1);
+ if( NULL == new_list ) {
+ DEBUG(0,("add_ip_to_name_record: Malloc fail !\n"));
+ return;
+ }
+
+ memcpy( (char *)new_list, (char *)namerec->data.ip, namerec->data.num_ips * sizeof(struct in_addr) );
+ new_list[namerec->data.num_ips] = new_ip;
+
+ SAFE_FREE(namerec->data.ip);
+ namerec->data.ip = new_list;
+ namerec->data.num_ips += 1;
+
+ if (namerec->subnet == wins_server_subnet) {
+ wins_store_changed_namerec(namerec);
+ } else {
+ namerec->subnet->namelist_changed = True;
+ }
+}
+
+/*******************************************************************
+ Utility function to remove an IP address from a name record.
+ ******************************************************************/
+
+void remove_ip_from_name_record( struct name_record *namerec,
+ struct in_addr remove_ip )
+{
+ /* Try and find the requested ip address - remove it. */
+ int i;
+ int orig_num = namerec->data.num_ips;
+
+ for(i = 0; i < orig_num; i++) {
+ if( ip_equal_v4( remove_ip, namerec->data.ip[i]) ) {
+ remove_nth_ip_in_record( namerec, i);
+ break;
+ }
+ }
+}
+
+/*******************************************************************
+ Utility function that release_name callers can plug into as the
+ success function when a name release is successful. Used to save
+ duplication of success_function code.
+ ******************************************************************/
+
+void standard_success_release( struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ struct in_addr released_ip )
+{
+ struct name_record *namerec;
+
+ namerec = find_name_on_subnet( subrec, nmbname, FIND_ANY_NAME );
+ if( namerec == NULL ) {
+ DEBUG( 0, ( "standard_success_release: Name release for name %s IP %s \
+on subnet %s. Name was not found on subnet.\n", nmb_namestr(nmbname), inet_ntoa(released_ip),
+ subrec->subnet_name) );
+ return;
+ } else {
+ int orig_num = namerec->data.num_ips;
+
+ remove_ip_from_name_record( namerec, released_ip );
+
+ if( namerec->data.num_ips == orig_num ) {
+ DEBUG( 0, ( "standard_success_release: Name release for name %s IP %s \
+on subnet %s. This ip is not known for this name.\n", nmb_namestr(nmbname), inet_ntoa(released_ip), subrec->subnet_name ) );
+ }
+ }
+
+ if( namerec->data.num_ips == 0 ) {
+ remove_name_from_namelist( subrec, namerec );
+ }
+}
+
+/*******************************************************************
+ Expires old names in a subnet namelist.
+ NB. Does not touch the wins_subnet - no wins specific processing here.
+******************************************************************/
+
+static void expire_names_on_subnet(struct subnet_record *subrec, time_t t)
+{
+ struct name_record *namerec;
+ struct name_record *next_namerec;
+
+ for( namerec = subrec->namelist; namerec; namerec = next_namerec ) {
+ next_namerec = namerec->next;
+ if( (namerec->data.death_time != PERMANENT_TTL) && (namerec->data.death_time < t) ) {
+ if( namerec->data.source == SELF_NAME ) {
+ DEBUG( 3, ( "expire_names_on_subnet: Subnet %s not expiring SELF \
+name %s\n", subrec->subnet_name, nmb_namestr(&namerec->name) ) );
+ namerec->data.death_time += 300;
+ namerec->subnet->namelist_changed = True;
+ continue;
+ }
+
+ DEBUG(3,("expire_names_on_subnet: Subnet %s - removing expired name %s\n",
+ subrec->subnet_name, nmb_namestr(&namerec->name)));
+
+ remove_name_from_namelist(subrec, namerec );
+ }
+ }
+}
+
+/*******************************************************************
+ Expires old names in all subnet namelists.
+ NB. Does not touch the wins_subnet.
+******************************************************************/
+
+void expire_names(time_t t)
+{
+ struct subnet_record *subrec;
+
+ for( subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec) ) {
+ expire_names_on_subnet( subrec, t );
+ }
+}
+
+/****************************************************************************
+ Add the magic samba names, useful for finding samba servers.
+ These go directly into the name list for a particular subnet,
+ without going through the normal registration process.
+ When adding them to the unicast subnet, add them as a list of
+ all broadcast subnet IP addresses.
+**************************************************************************/
+
+void add_samba_names_to_subnet( struct subnet_record *subrec )
+{
+ struct in_addr *iplist = &subrec->myip;
+ int num_ips = 1;
+
+ /* These names are added permanently (ttl of zero) and will NOT be refreshed. */
+
+ if( (subrec == unicast_subnet) || (subrec == wins_server_subnet) || (subrec == remote_broadcast_subnet) ) {
+ struct subnet_record *bcast_subrecs;
+ int i;
+
+ /* Create an IP list containing all our known subnets. */
+
+ num_ips = iface_count();
+ iplist = SMB_MALLOC_ARRAY( struct in_addr, num_ips);
+ if( NULL == iplist ) {
+ DEBUG(0,("add_samba_names_to_subnet: Malloc fail !\n"));
+ return;
+ }
+
+ for( bcast_subrecs = FIRST_SUBNET, i = 0; bcast_subrecs &&
+ i < num_ips;
+ bcast_subrecs = NEXT_SUBNET_EXCLUDING_UNICAST(bcast_subrecs), i++ ) {
+ iplist[i] = bcast_subrecs->myip;
+ }
+ num_ips = i;
+ }
+
+ add_name_to_subnet(subrec,"*",0x0,samba_nb_type, PERMANENT_TTL,
+ PERMANENT_NAME, num_ips, iplist);
+ add_name_to_subnet(subrec,"*",0x20,samba_nb_type,PERMANENT_TTL,
+ PERMANENT_NAME, num_ips, iplist);
+ add_name_to_subnet(subrec,"__SAMBA__",0x20,samba_nb_type,PERMANENT_TTL,
+ PERMANENT_NAME, num_ips, iplist);
+ add_name_to_subnet(subrec,"__SAMBA__",0x00,samba_nb_type,PERMANENT_TTL,
+ PERMANENT_NAME, num_ips, iplist);
+
+ if(iplist != &subrec->myip) {
+ SAFE_FREE(iplist);
+ }
+}
+
+/****************************************************************************
+ Dump a name_record struct.
+**************************************************************************/
+
+void dump_name_record( struct name_record *namerec, FILE *fp)
+{
+ const char *src_type;
+ struct tm *tm;
+ int i;
+
+ fprintf(fp,"\tName = %s\t", nmb_namestr(&namerec->name));
+ switch(namerec->data.source) {
+ case LMHOSTS_NAME:
+ src_type = "LMHOSTS_NAME";
+ break;
+ case WINS_PROXY_NAME:
+ src_type = "WINS_PROXY_NAME";
+ break;
+ case REGISTER_NAME:
+ src_type = "REGISTER_NAME";
+ break;
+ case SELF_NAME:
+ src_type = "SELF_NAME";
+ break;
+ case DNS_NAME:
+ src_type = "DNS_NAME";
+ break;
+ case DNSFAIL_NAME:
+ src_type = "DNSFAIL_NAME";
+ break;
+ case PERMANENT_NAME:
+ src_type = "PERMANENT_NAME";
+ break;
+ default:
+ src_type = "unknown!";
+ break;
+ }
+
+ fprintf(fp, "Source = %s\nb_flags = %x\t", src_type,
+ namerec->data.nb_flags);
+
+ if(namerec->data.death_time != PERMANENT_TTL) {
+ const char *asct;
+ tm = localtime(&namerec->data.death_time);
+ if (!tm) {
+ return;
+ }
+ asct = asctime(tm);
+ if (!asct) {
+ return;
+ }
+ fprintf(fp, "death_time = %s\t", asct);
+ } else {
+ fprintf(fp, "death_time = PERMANENT\t");
+ }
+
+ if(namerec->data.refresh_time != PERMANENT_TTL) {
+ const char *asct;
+ tm = localtime(&namerec->data.refresh_time);
+ if (!tm) {
+ return;
+ }
+ asct = asctime(tm);
+ if (!asct) {
+ return;
+ }
+ fprintf(fp, "refresh_time = %s\n", asct);
+ } else {
+ fprintf(fp, "refresh_time = PERMANENT\n");
+ }
+
+ fprintf(fp, "\t\tnumber of IPS = %d", namerec->data.num_ips);
+ for(i = 0; i < namerec->data.num_ips; i++) {
+ fprintf(fp, "\t%s", inet_ntoa(namerec->data.ip[i]));
+ }
+
+ fprintf(fp, "\n\n");
+}
+
+/****************************************************************************
+ Dump the contents of the namelists on all the subnets (including unicast)
+ into a file. Initiated by SIGHUP - used to debug the state of the namelists.
+**************************************************************************/
+
+static void dump_subnet_namelist(struct subnet_record *subrec, FILE *fp)
+{
+ struct name_record *namerec;
+ fprintf(fp, "Subnet %s\n----------------------\n", subrec->subnet_name);
+ for( namerec = subrec->namelist; namerec; namerec = namerec->next) {
+ dump_name_record(namerec, fp);
+ }
+}
+
+/****************************************************************************
+ Dump the contents of the namelists on all the subnets (including unicast)
+ into a file. Initiated by SIGHUP - used to debug the state of the namelists.
+**************************************************************************/
+
+void dump_all_namelists(void)
+{
+ int fd;
+ FILE *fp;
+ struct subnet_record *subrec;
+ char *dump_path;
+
+ dump_path = lock_path(talloc_tos(), "namelist.debug");
+ if (dump_path == NULL) {
+ DEBUG(0, ("out of memory!\n"));
+ return;
+ }
+
+ fd = open(dump_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd == -1) {
+ DBG_ERR("Can't open file %s: %s\n", dump_path,
+ strerror(errno));
+ return;
+ }
+ TALLOC_FREE(dump_path);
+
+ fp = fdopen(fd, "w");
+ if (!fp) {
+ DBG_ERR("fdopen failed: %s\n", strerror(errno));
+ close(fd);
+ return;
+ }
+ fd = -1;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ dump_subnet_namelist( subrec, fp );
+ }
+
+ if (!we_are_a_wins_client()) {
+ dump_subnet_namelist( unicast_subnet, fp );
+ }
+
+ if (remote_broadcast_subnet->namelist != NULL) {
+ dump_subnet_namelist( remote_broadcast_subnet, fp );
+ }
+
+ if (wins_server_subnet != NULL) {
+ dump_wins_subnet_namelist(fp );
+ }
+
+ fclose( fp );
+}
diff --git a/source3/nmbd/nmbd_namequery.c b/source3/nmbd/nmbd_namequery.c
new file mode 100644
index 0000000..ae50a23
--- /dev/null
+++ b/source3/nmbd/nmbd_namequery.c
@@ -0,0 +1,276 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+/****************************************************************************
+ Deal with a response packet when querying a name.
+****************************************************************************/
+
+static void query_name_response( struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ bool success = False;
+ struct nmb_name *question_name = &rrec->packet->packet.nmb.question.question_name;
+ struct in_addr answer_ip;
+
+ zero_ip_v4(&answer_ip);
+
+ /* Ensure we don't retry the query but leave the response record cleanup
+ to the timeout code. We may get more answer responses in which case
+ we should mark the name in conflict.. */
+ rrec->repeat_count = 0;
+
+ if(rrec->num_msgs == 1) {
+ /* This is the first response. */
+
+ if(nmb->header.opcode == NMB_WACK_OPCODE) {
+ /* WINS server is telling us to wait. Pretend we didn't get
+ the response but don't send out any more query requests. */
+
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "query_name_response: " );
+ dbgtext( "WACK from WINS server %s ", inet_ntoa(p->ip) );
+ dbgtext( "in querying name %s ", nmb_namestr(question_name) );
+ dbgtext( "on subnet %s.\n", subrec->subnet_name );
+ }
+
+ rrec->repeat_count = 0;
+ /* How long we should wait for. */
+ if (nmb->answers) {
+ rrec->repeat_time = p->timestamp + nmb->answers->ttl;
+ } else {
+ /* No answer - this is probably a corrupt
+ packet.... */
+ DEBUG(0,("query_name_response: missing answer record in "
+ "NMB_WACK_OPCODE response.\n"));
+ rrec->repeat_time = p->timestamp + 10;
+ }
+ rrec->num_msgs--;
+ return;
+ } else if(nmb->header.rcode != 0) {
+
+ success = False;
+
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "query_name_response: On subnet %s ", subrec->subnet_name );
+ dbgtext( "- negative response from IP %s ", inet_ntoa(p->ip) );
+ dbgtext( "for name %s. ", nmb_namestr(question_name) );
+ dbgtext( "Error code was %d.\n", nmb->header.rcode );
+ }
+ } else {
+ if (!nmb->answers) {
+ dbgtext( "query_name_response: On subnet %s ", subrec->subnet_name );
+ dbgtext( "IP %s ", inet_ntoa(p->ip) );
+ dbgtext( "returned a success response with no answer\n" );
+ return;
+ }
+
+ success = True;
+
+ putip((char *)&answer_ip,&nmb->answers->rdata[2]);
+
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "query_name_response: On subnet %s ", subrec->subnet_name );
+ dbgtext( "- positive response from IP %s ", inet_ntoa(p->ip) );
+ dbgtext( "for name %s. ", nmb_namestr(question_name) );
+ dbgtext( "IP of that name is %s\n", inet_ntoa(answer_ip) );
+ }
+
+ /* Interestingly, we could add these names to our namelists, and
+ change nmbd to a model that checked its own name cache first,
+ before sending out a query. This is a task for another day, though.
+ */
+ }
+ } else if( rrec->num_msgs > 1) {
+
+ if( DEBUGLVL( 0 ) ) {
+ if (nmb->answers)
+ putip( (char *)&answer_ip, &nmb->answers->rdata[2] );
+ dbgtext( "query_name_response: " );
+ dbgtext( "Multiple (%d) responses ", rrec->num_msgs );
+ dbgtext( "received for a query on subnet %s ", subrec->subnet_name );
+ dbgtext( "for name %s.\nThis response ", nmb_namestr(question_name) );
+ dbgtext( "was from IP %s, reporting ", inet_ntoa(p->ip) );
+ dbgtext( "an IP address of %s.\n", inet_ntoa(answer_ip) );
+ }
+
+ /* We have already called the success or fail function, so we
+ don't call again here. Leave the response record around in
+ case we get more responses. */
+
+ return;
+ }
+
+ if(success && rrec->success_fn)
+ (*(query_name_success_function)rrec->success_fn)(subrec, rrec->userdata, question_name, answer_ip, nmb->answers);
+ else if( rrec->fail_fn)
+ (*(query_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name, nmb->header.rcode);
+
+}
+
+/****************************************************************************
+ Deal with a timeout when querying a name.
+****************************************************************************/
+
+static void query_name_timeout_response(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb;
+ /* We can only fail here, never succeed. */
+ bool failed = True;
+ struct nmb_name *question_name = &sent_nmb->question.question_name;
+
+ if(rrec->num_msgs != 0) {
+ /* We got at least one response, and have called the success/fail
+ function already. */
+
+ failed = False;
+ }
+
+ if(failed) {
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "query_name_timeout_response: No response to " );
+ dbgtext( "query for name %s ", nmb_namestr(question_name) );
+ dbgtext( "on subnet %s.\n", subrec->subnet_name );
+ }
+
+ if(rrec->fail_fn)
+ (*(query_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name, 0);
+ }
+
+ remove_response_record(subrec, rrec);
+}
+
+/****************************************************************************
+ Lookup a name on our local namelists. We check the lmhosts file first. If the
+ name is not there we look for the name on the given subnet.
+****************************************************************************/
+
+static bool query_local_namelists(struct subnet_record *subrec, struct nmb_name *nmbname,
+ struct name_record **namerecp)
+{
+ struct name_record *namerec;
+
+ *namerecp = NULL;
+
+ if(find_name_in_lmhosts(nmbname, namerecp))
+ return True;
+
+ if((namerec = find_name_on_subnet(subrec, nmbname, FIND_ANY_NAME))==NULL)
+ return False;
+
+ if( NAME_IS_ACTIVE(namerec) && ( (namerec->data.source == SELF_NAME) || (namerec->data.source == LMHOSTS_NAME) ) ) {
+ *namerecp = namerec;
+ return True;
+ }
+ return False;
+}
+
+/****************************************************************************
+ Try and query for a name.
+****************************************************************************/
+
+bool query_name(struct subnet_record *subrec, const char *name, int type,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata)
+{
+ struct nmb_name nmbname;
+ struct name_record *namerec;
+
+ make_nmb_name(&nmbname, name, type);
+
+ /*
+ * We need to check our local namelists first.
+ * It may be an magic name, lmhosts name or just
+ * a name we have registered.
+ */
+
+ if(query_local_namelists(subrec, &nmbname, &namerec) == True) {
+ struct res_rec rrec;
+ int i;
+
+ memset((char *)&rrec, '\0', sizeof(struct res_rec));
+
+ /* Fake up the needed res_rec just in case it's used. */
+ rrec.rr_name = nmbname;
+ rrec.rr_type = RR_TYPE_NB;
+ rrec.rr_class = RR_CLASS_IN;
+ rrec.ttl = PERMANENT_TTL;
+ rrec.rdlength = namerec->data.num_ips * 6;
+ if(rrec.rdlength > MAX_DGRAM_SIZE) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "query_name: nmbd internal error - " );
+ dbgtext( "there are %d ip addresses ", namerec->data.num_ips );
+ dbgtext( "for name %s.\n", nmb_namestr(&nmbname) );
+ }
+ return False;
+ }
+
+ for( i = 0; i < namerec->data.num_ips; i++) {
+ set_nb_flags( &rrec.rdata[i*6], namerec->data.nb_flags );
+ putip( &rrec.rdata[(i*6) + 2], (char *)&namerec->data.ip[i]);
+ }
+
+ /* Call the success function directly. */
+ if(success_fn)
+ (*(query_name_success_function)success_fn)(subrec, userdata, &nmbname, namerec->data.ip[0], &rrec);
+ return False;
+ }
+
+ if(queue_query_name( subrec, query_name_response, query_name_timeout_response, success_fn, fail_fn, userdata, &nmbname) == NULL) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "query_name: Failed to send packet " );
+ dbgtext( "trying to query name %s\n", nmb_namestr(&nmbname) );
+ }
+ return True;
+ }
+ return False;
+}
+
+/****************************************************************************
+ Try and query for a name from nmbd acting as a WINS server.
+****************************************************************************/
+
+bool query_name_from_wins_server(struct in_addr ip_to,
+ const char *name, int type,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata)
+{
+ struct nmb_name nmbname;
+
+ make_nmb_name(&nmbname, name, type);
+
+ if(queue_query_name_from_wins_server( ip_to, query_name_response, query_name_timeout_response, success_fn, fail_fn, userdata, &nmbname) == NULL) {
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "query_name_from_wins_server: Failed to send packet " );
+ dbgtext( "trying to query name %s\n", nmb_namestr(&nmbname) );
+ }
+ return True;
+ }
+ return False;
+}
diff --git a/source3/nmbd/nmbd_nameregister.c b/source3/nmbd/nmbd_nameregister.c
new file mode 100644
index 0000000..4b7b6c6
--- /dev/null
+++ b/source3/nmbd/nmbd_nameregister.c
@@ -0,0 +1,608 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+/* forward declarations */
+static void wins_next_registration(struct response_record *rrec);
+
+
+/****************************************************************************
+ Deal with a response packet when registering one of our names.
+****************************************************************************/
+
+static void register_name_response(struct subnet_record *subrec,
+ struct response_record *rrec, struct packet_struct *p)
+{
+ /*
+ * If we are registering broadcast, then getting a response is an
+ * error - we do not have the name. If we are registering unicast,
+ * then we expect to get a response.
+ */
+
+ struct nmb_packet *nmb = &p->packet.nmb;
+ bool bcast = nmb->header.nm_flags.bcast;
+ bool success = True;
+ struct nmb_name *question_name = &rrec->packet->packet.nmb.question.question_name;
+ struct nmb_name *answer_name = &nmb->answers->rr_name;
+ struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb;
+ int ttl = 0;
+ uint16_t nb_flags = 0;
+ struct in_addr register_ip;
+ fstring reg_name;
+
+ putip(&register_ip,&sent_nmb->additional->rdata[2]);
+ fstrcpy(reg_name, inet_ntoa(register_ip));
+
+ if (subrec == unicast_subnet) {
+ /* we know that this wins server is definitely alive - for the moment! */
+ wins_srv_alive(rrec->packet->ip, register_ip);
+ }
+
+ /* Sanity check. Ensure that the answer name in the incoming packet is the
+ same as the requested name in the outgoing packet. */
+
+ if(!question_name || !answer_name) {
+ DEBUG(0,("register_name_response: malformed response (%s is NULL).\n",
+ question_name ? "question_name" : "answer_name" ));
+ return;
+ }
+
+ if(!nmb_name_equal(question_name, answer_name)) {
+ DEBUG(0,("register_name_response: Answer name %s differs from question name %s.\n",
+ nmb_namestr(answer_name), nmb_namestr(question_name)));
+ return;
+ }
+
+ if(bcast) {
+ /*
+ * Special hack to cope with old Samba nmbd's.
+ * Earlier versions of Samba (up to 1.9.16p11) respond
+ * to a broadcast name registration of WORKGROUP<1b> when
+ * they should not. Hence, until these versions are gone,
+ * we should treat such errors as success for this particular
+ * case only. jallison@whistle.com.
+ */
+
+#if 1 /* OLD_SAMBA_SERVER_HACK */
+ unstring ans_name;
+ pull_ascii_nstring(ans_name, sizeof(ans_name), answer_name->name);
+ if((nmb->header.rcode == ACT_ERR) && strequal(lp_workgroup(), ans_name) &&
+ (answer_name->name_type == 0x1b)) {
+ /* Pretend we did not get this. */
+ rrec->num_msgs--;
+
+ DEBUG(5,("register_name_response: Ignoring broadcast response to registration of name %s due to old Samba server bug.\n",
+ nmb_namestr(answer_name)));
+ return;
+ }
+#endif /* OLD_SAMBA_SERVER_HACK */
+
+ /* Someone else has the name. Log the problem. */
+ DEBUG(1,("register_name_response: Failed to register name %s IP %s on subnet %s via broadcast. Error code was %d. Reject came from IP %s\n",
+ nmb_namestr(answer_name),
+ reg_name,
+ subrec->subnet_name, nmb->header.rcode, inet_ntoa(p->ip)));
+ success = False;
+ } else {
+ if (!ip_equal_v4(rrec->packet->ip, p->ip)) {
+ DEBUG(5,("register_name_response: Ignoring WINS server response "
+ "from IP %s, for name %s. We sent to IP %s\n",
+ inet_ntoa(p->ip),
+ nmb_namestr(answer_name),
+ inet_ntoa(rrec->packet->ip)));
+ return;
+ }
+ /* Unicast - check to see if the response allows us to have the name. */
+ if (nmb->header.opcode == NMB_WACK_OPCODE) {
+ /* WINS server is telling us to wait. Pretend we didn't get
+ the response but don't send out any more register requests. */
+
+ DEBUG(5,("register_name_response: WACK from WINS server %s in registering name %s IP %s\n",
+ inet_ntoa(p->ip), nmb_namestr(answer_name), reg_name));
+
+ rrec->repeat_count = 0;
+ /* How long we should wait for. */
+ rrec->repeat_time = p->timestamp + nmb->answers->ttl;
+ rrec->num_msgs--;
+ return;
+ } else if (nmb->header.rcode != 0) {
+ /* Error code - we didn't get the name. */
+ success = False;
+
+ DEBUG(0,("register_name_response: %sserver at IP %s rejected our name registration of %s IP %s with error code %d.\n",
+ subrec==unicast_subnet?"WINS ":"",
+ inet_ntoa(p->ip),
+ nmb_namestr(answer_name),
+ reg_name,
+ nmb->header.rcode));
+ } else {
+ success = True;
+ /* Get the data we need to pass to the success function. */
+ nb_flags = get_nb_flags(nmb->answers->rdata);
+ ttl = nmb->answers->ttl;
+
+ /* send off a registration for the next IP, if any */
+ wins_next_registration(rrec);
+ }
+ }
+
+ DEBUG(5,("register_name_response: %s in registering %sname %s IP %s with %s.\n",
+ success ? "success" : "failure",
+ subrec==unicast_subnet?"WINS ":"",
+ nmb_namestr(answer_name),
+ reg_name,
+ inet_ntoa(rrec->packet->ip)));
+
+ if(success) {
+ /* Enter the registered name into the subnet name database before calling
+ the success function. */
+ standard_success_register(subrec, rrec->userdata, answer_name, nb_flags, ttl, register_ip);
+ if( rrec->success_fn)
+ (*(register_name_success_function)rrec->success_fn)(subrec, rrec->userdata, answer_name, nb_flags, ttl, register_ip);
+ } else {
+ struct nmb_name qname = *question_name;
+ if( rrec->fail_fn)
+ (*(register_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name);
+ /* Remove the name. */
+ standard_fail_register( subrec, &qname);
+ }
+
+ /* Ensure we don't retry. */
+ remove_response_record(subrec, rrec);
+}
+
+/****************************************************************************
+ Deal with a timeout of a WINS registration request
+****************************************************************************/
+
+static void wins_registration_timeout(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ struct userdata_struct *userdata = rrec->userdata;
+ struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb;
+ struct nmb_name *nmbname = &sent_nmb->question.question_name;
+ struct in_addr register_ip;
+ fstring src_addr;
+
+ putip(&register_ip,&sent_nmb->additional->rdata[2]);
+
+ fstrcpy(src_addr, inet_ntoa(register_ip));
+
+ DEBUG(2,("wins_registration_timeout: WINS server %s timed out registering IP %s\n",
+ inet_ntoa(rrec->packet->ip), src_addr));
+
+ /* mark it temporarily dead for this source address */
+ wins_srv_died(rrec->packet->ip, register_ip);
+
+ /* if we have some userdata then use that to work out what
+ wins server to try next */
+ if (userdata) {
+ const char *tag = (const char *)userdata->data;
+
+ /* try the next wins server in our failover list for
+ this tag */
+ rrec->packet->ip = wins_srv_ip_tag(tag, register_ip);
+ }
+
+ /* if we have run out of wins servers for this tag then they
+ must all have timed out. We treat this as *success*, not
+ failure, and go into our standard name refresh mode. This
+ copes with all the wins servers being down */
+ if (wins_srv_is_dead(rrec->packet->ip, register_ip)) {
+ uint16_t nb_flags = get_nb_flags(sent_nmb->additional->rdata);
+ int ttl = sent_nmb->additional->ttl;
+
+ standard_success_register(subrec, userdata, nmbname, nb_flags, ttl, register_ip);
+ if(rrec->success_fn) {
+ (*(register_name_success_function)rrec->success_fn)(subrec,
+ rrec->userdata,
+ nmbname,
+ nb_flags,
+ ttl,
+ register_ip);
+ }
+
+ /* send off a registration for the next IP, if any */
+ wins_next_registration(rrec);
+
+ /* don't need to send this packet any more */
+ remove_response_record(subrec, rrec);
+ return;
+ }
+
+ /* we will be moving to the next WINS server for this group,
+ send it immediately */
+ rrec->repeat_count = 2;
+ rrec->repeat_time = time(NULL) + 1;
+ rrec->in_expiration_processing = False;
+
+ DEBUG(6,("Retrying register of name %s IP %s with WINS server %s\n",
+ nmb_namestr(nmbname), src_addr, inet_ntoa(rrec->packet->ip)));
+
+ /* notice that we don't remove the response record. This keeps
+ us trying to register with each of our failover wins servers */
+}
+
+/****************************************************************************
+ Deal with a timeout when registering one of our names.
+****************************************************************************/
+
+static void register_name_timeout_response(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ /*
+ * If we are registering unicast, then NOT getting a response is an
+ * error - we do not have the name. If we are registering broadcast,
+ * then we don't expect to get a response.
+ */
+
+ struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb;
+ bool bcast = sent_nmb->header.nm_flags.bcast;
+ bool success = False;
+ struct nmb_name *question_name = &sent_nmb->question.question_name;
+ uint16_t nb_flags = 0;
+ int ttl = 0;
+ struct in_addr registered_ip;
+
+ if (bcast) {
+ if(rrec->num_msgs == 0) {
+ /* Not receiving a message is success for broadcast registration. */
+ success = True;
+
+ /* Pull the success values from the original request packet. */
+ nb_flags = get_nb_flags(sent_nmb->additional->rdata);
+ ttl = sent_nmb->additional->ttl;
+ putip(&registered_ip,&sent_nmb->additional->rdata[2]);
+ }
+ } else {
+ /* wins timeouts are special */
+ wins_registration_timeout(subrec, rrec);
+ return;
+ }
+
+ DEBUG(5,("register_name_timeout_response: %s in registering name %s on subnet %s.\n",
+ success ? "success" : "failure", nmb_namestr(question_name), subrec->subnet_name));
+ if(success) {
+ /* Enter the registered name into the subnet name database before calling
+ the success function. */
+ standard_success_register(subrec, rrec->userdata, question_name, nb_flags, ttl, registered_ip);
+ if( rrec->success_fn)
+ (*(register_name_success_function)rrec->success_fn)(subrec, rrec->userdata, question_name, nb_flags, ttl, registered_ip);
+ } else {
+ struct nmb_name qname = *question_name;
+ if( rrec->fail_fn)
+ (*(register_name_fail_function)rrec->fail_fn)(subrec, rrec, question_name);
+ /* Remove the name. */
+ standard_fail_register( subrec, &qname);
+ }
+
+ /* Ensure we don't retry. */
+ remove_response_record(subrec, rrec);
+}
+
+/****************************************************************************
+ Initiate one multi-homed name registration packet.
+****************************************************************************/
+
+static void multihomed_register_one(struct nmb_name *nmbname,
+ uint16_t nb_flags,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn,
+ struct in_addr ip,
+ const char *tag)
+{
+ struct userdata_struct *userdata;
+ struct in_addr wins_ip = wins_srv_ip_tag(tag, ip);
+ fstring ip_str;
+
+ userdata = (struct userdata_struct *)SMB_MALLOC(sizeof(*userdata) + strlen(tag) + 1);
+ if (!userdata) {
+ DEBUG(0,("Failed to allocate userdata structure!\n"));
+ return;
+ }
+ ZERO_STRUCTP(userdata);
+ userdata->userdata_len = strlen(tag) + 1;
+ strlcpy(userdata->data, tag, userdata->userdata_len);
+
+ fstrcpy(ip_str, inet_ntoa(ip));
+
+ DEBUG(6,("Registering name %s IP %s with WINS server %s using tag '%s'\n",
+ nmb_namestr(nmbname), ip_str, inet_ntoa(wins_ip), tag));
+
+ if (queue_register_multihomed_name(unicast_subnet,
+ register_name_response,
+ register_name_timeout_response,
+ success_fn,
+ fail_fn,
+ userdata,
+ nmbname,
+ nb_flags,
+ ip,
+ wins_ip) == NULL) {
+ DEBUG(0,("multihomed_register_one: Failed to send packet trying to register name %s IP %s\n",
+ nmb_namestr(nmbname), inet_ntoa(ip)));
+ }
+
+ free(userdata);
+}
+
+/****************************************************************************
+ We have finished the registration of one IP and need to see if we have
+ any more IPs left to register with this group of wins server for this name.
+****************************************************************************/
+
+static void wins_next_registration(struct response_record *rrec)
+{
+ struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb;
+ struct nmb_name *nmbname = &sent_nmb->question.question_name;
+ uint16_t nb_flags = get_nb_flags(sent_nmb->additional->rdata);
+ struct userdata_struct *userdata = rrec->userdata;
+ const char *tag;
+ struct in_addr last_ip;
+ struct subnet_record *subrec;
+
+ putip(&last_ip,&sent_nmb->additional->rdata[2]);
+
+ if (!userdata) {
+ /* it wasn't multi-homed */
+ return;
+ }
+
+ tag = (const char *)userdata->data;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ if (ip_equal_v4(last_ip, subrec->myip)) {
+ subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec);
+ break;
+ }
+ }
+
+ if (!subrec) {
+ /* no more to do! */
+ return;
+ }
+
+ switch (sent_nmb->header.opcode) {
+ case NMB_NAME_MULTIHOMED_REG_OPCODE:
+ multihomed_register_one(nmbname, nb_flags, NULL, NULL, subrec->myip, tag);
+ break;
+ case NMB_NAME_REFRESH_OPCODE_8:
+ queue_wins_refresh(nmbname,
+ register_name_response,
+ register_name_timeout_response,
+ nb_flags, subrec->myip, tag);
+ break;
+ }
+}
+
+/****************************************************************************
+ Try and register one of our names on the unicast subnet - multihomed.
+****************************************************************************/
+
+static void multihomed_register_name(struct nmb_name *nmbname, uint16_t nb_flags,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn)
+{
+ /*
+ If we are adding a group name, we just send multiple
+ register name packets to the WINS server (this is an
+ internet group name.
+
+ If we are adding a unique name, We need first to add
+ our names to the unicast subnet namelist. This is
+ because when a WINS server receives a multihomed
+ registration request, the first thing it does is to
+ send a name query to the registering machine, to see
+ if it has put the name in it's local namelist.
+ We need the name there so the query response code in
+ nmbd_incomingrequests.c will find it.
+
+ We are adding this name prematurely (we don't really
+ have it yet), but as this is on the unicast subnet
+ only we will get away with this (only the WINS server
+ will ever query names from us on this subnet).
+ */
+ int num_ips=0;
+ int i, t;
+ struct subnet_record *subrec;
+ char **wins_tags;
+ struct in_addr *ip_list;
+ unstring name;
+
+ for(subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec) )
+ num_ips++;
+
+ if((ip_list = SMB_MALLOC_ARRAY(struct in_addr, num_ips))==NULL) {
+ DEBUG(0,("multihomed_register_name: malloc fail !\n"));
+ return;
+ }
+
+ for (subrec = FIRST_SUBNET, i = 0;
+ subrec;
+ subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec), i++ ) {
+ ip_list[i] = subrec->myip;
+ }
+
+ pull_ascii_nstring(name, sizeof(name), nmbname->name);
+ add_name_to_subnet(unicast_subnet, name, nmbname->name_type,
+ nb_flags, lp_max_ttl(), SELF_NAME,
+ num_ips, ip_list);
+
+ /* get the list of wins tags - we try to register for each of them */
+ wins_tags = wins_srv_tags();
+
+ /* Now try and register the name for each wins tag. Note that
+ at this point we only register our first IP with each wins
+ group. We will register the rest from
+ wins_next_registration() when we get the reply for this
+ one. That follows the way W2K does things (tridge)
+ */
+ for (t=0; wins_tags && wins_tags[t]; t++) {
+ multihomed_register_one(nmbname, nb_flags,
+ success_fn, fail_fn,
+ ip_list[0],
+ wins_tags[t]);
+ }
+
+ wins_srv_tags_free(wins_tags);
+
+ SAFE_FREE(ip_list);
+}
+
+/****************************************************************************
+ Try and register one of our names.
+****************************************************************************/
+
+void register_name(struct subnet_record *subrec,
+ const char *name, int type, uint16_t nb_flags,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn,
+ struct userdata_struct *userdata)
+{
+ struct nmb_name nmbname;
+ nstring nname;
+ size_t converted_size;
+
+ errno = 0;
+ converted_size = push_ascii_nstring(nname, name);
+ if (converted_size != (size_t)-1) {
+ /* Success. */
+ make_nmb_name(&nmbname, name, type);
+ } else if (errno == E2BIG) {
+ /*
+ * Name converted to CH_DOS is too large.
+ * try to truncate.
+ */
+ char *converted_str_dos = NULL;
+ char *converted_str_unix = NULL;
+ bool ok;
+
+ converted_size = 0;
+
+ ok = convert_string_talloc(talloc_tos(),
+ CH_UNIX,
+ CH_DOS,
+ name,
+ strlen(name)+1,
+ &converted_str_dos,
+ &converted_size);
+ if (!ok) {
+ DEBUG(0,("register_name: NetBIOS name %s cannot be "
+ "converted. Failing to register name.\n",
+ name));
+ return;
+ }
+
+ /*
+ * As it's now CH_DOS codepage
+ * we truncate by writing '\0' at
+ * MAX_NETBIOSNAME_LEN-1 and then
+ * convert back to CH_UNIX which we
+ * need for the make_nmb_name() call.
+ */
+ if (converted_size >= MAX_NETBIOSNAME_LEN) {
+ converted_str_dos[MAX_NETBIOSNAME_LEN-1] = '\0';
+ }
+
+ ok = convert_string_talloc(talloc_tos(),
+ CH_DOS,
+ CH_UNIX,
+ converted_str_dos,
+ strlen(converted_str_dos)+1,
+ &converted_str_unix,
+ &converted_size);
+ if (!ok) {
+ DEBUG(0,("register_name: NetBIOS name %s cannot be "
+ "converted back to CH_UNIX. "
+ "Failing to register name.\n",
+ converted_str_dos));
+ TALLOC_FREE(converted_str_dos);
+ return;
+ }
+
+ make_nmb_name(&nmbname, converted_str_unix, type);
+
+ TALLOC_FREE(converted_str_dos);
+ TALLOC_FREE(converted_str_unix);
+ } else {
+ /*
+ * Generic conversion error. Fail to register.
+ */
+ DEBUG(0,("register_name: NetBIOS name %s cannot be "
+ "converted (%s). Failing to register name.\n",
+ name, strerror(errno)));
+ return;
+ }
+
+ /* Always set the NB_ACTIVE flag on the name we are
+ registering. Doesn't make sense without it.
+ */
+
+ nb_flags |= NB_ACTIVE;
+
+ if (subrec == unicast_subnet) {
+ /* we now always do multi-homed registration if we are
+ registering to a WINS server. This copes much
+ better with complex WINS setups */
+ multihomed_register_name(&nmbname, nb_flags,
+ success_fn, fail_fn);
+ return;
+ }
+
+ if (queue_register_name(subrec,
+ register_name_response,
+ register_name_timeout_response,
+ success_fn,
+ fail_fn,
+ userdata,
+ &nmbname,
+ nb_flags) == NULL) {
+ DEBUG(0,("register_name: Failed to send packet trying to register name %s\n",
+ nmb_namestr(&nmbname)));
+ }
+}
+
+/****************************************************************************
+ Try and refresh one of our names. This is *only* called for WINS refresh
+****************************************************************************/
+
+void wins_refresh_name(struct name_record *namerec)
+{
+ int t;
+ char **wins_tags;
+
+ /* get the list of wins tags - we try to refresh for each of them */
+ wins_tags = wins_srv_tags();
+
+ for (t=0; wins_tags && wins_tags[t]; t++) {
+ queue_wins_refresh(&namerec->name,
+ register_name_response,
+ register_name_timeout_response,
+ namerec->data.nb_flags,
+ namerec->data.ip[0], wins_tags[t]);
+ }
+
+ wins_srv_tags_free(wins_tags);
+}
diff --git a/source3/nmbd/nmbd_namerelease.c b/source3/nmbd/nmbd_namerelease.c
new file mode 100644
index 0000000..4f34a45
--- /dev/null
+++ b/source3/nmbd/nmbd_namerelease.c
@@ -0,0 +1,222 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+/****************************************************************************
+ Deal with a response packet when releasing one of our names.
+****************************************************************************/
+
+static void release_name_response(struct subnet_record *subrec,
+ struct response_record *rrec, struct packet_struct *p)
+{
+ /*
+ * If we are releasing broadcast, then getting a response is an
+ * error. If we are releasing unicast, then we expect to get a response.
+ */
+ struct nmb_packet *nmb = &p->packet.nmb;
+ bool bcast = nmb->header.nm_flags.bcast;
+ bool success = True;
+ struct nmb_name *question_name = &rrec->packet->packet.nmb.question.question_name;
+ struct nmb_name *answer_name = &nmb->answers->rr_name;
+ struct in_addr released_ip;
+
+ /* Sanity check. Ensure that the answer name in the incoming packet is the
+ same as the requested name in the outgoing packet. */
+ if (!nmb_name_equal(question_name, answer_name)) {
+ DEBUG(0,("release_name_response: Answer name %s differs from question name %s.\n",
+ nmb_namestr(answer_name), nmb_namestr(question_name)));
+ return;
+ }
+
+ if (bcast) {
+ /* Someone sent a response to a bcast release? ignore it. */
+ return;
+ }
+
+ /* Unicast - check to see if the response allows us to release the name. */
+ if (nmb->header.rcode != 0) {
+ /* Error code - we were told not to release the name ! What now ! */
+ success = False;
+
+ DEBUG(0,("release_name_response: WINS server at IP %s rejected our \
+name release of name %s with error code %d.\n",
+ inet_ntoa(p->ip),
+ nmb_namestr(answer_name), nmb->header.rcode));
+ } else if (nmb->header.opcode == NMB_WACK_OPCODE) {
+ /* WINS server is telling us to wait. Pretend we didn't get
+ the response but don't send out any more release requests. */
+
+ DEBUG(5,("release_name_response: WACK from WINS server %s in releasing \
+name %s on subnet %s.\n",
+ inet_ntoa(p->ip), nmb_namestr(answer_name), subrec->subnet_name));
+
+ rrec->repeat_count = 0;
+ /* How long we should wait for. */
+ rrec->repeat_time = p->timestamp + nmb->answers->ttl;
+ rrec->num_msgs--;
+ return;
+ }
+
+ DEBUG(5,("release_name_response: %s in releasing name %s on subnet %s.\n",
+ success ? "success" : "failure", nmb_namestr(answer_name), subrec->subnet_name));
+ if (success) {
+ putip((char*)&released_ip ,&nmb->answers->rdata[2]);
+
+ if(rrec->success_fn)
+ (*(release_name_success_function)rrec->success_fn)(subrec, rrec->userdata, answer_name, released_ip);
+ standard_success_release( subrec, rrec->userdata, answer_name, released_ip);
+ } else {
+ /* We have no standard_fail_release - maybe we should add one ? */
+ if (rrec->fail_fn) {
+ (*(release_name_fail_function)rrec->fail_fn)(subrec, rrec, answer_name);
+ }
+ }
+
+ remove_response_record(subrec, rrec);
+}
+
+/****************************************************************************
+ Deal with a timeout when releasing one of our names.
+****************************************************************************/
+
+static void release_name_timeout_response(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ /* a release is *always* considered to be successful when it
+ times out. This doesn't cause problems as if a WINS server
+ doesn't respond and someone else wants the name then the
+ normal WACK/name query from the WINS server will cope */
+ struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb;
+ bool bcast = sent_nmb->header.nm_flags.bcast;
+ struct nmb_name *question_name = &sent_nmb->question.question_name;
+ struct in_addr released_ip;
+
+ /* Get the ip address we were trying to release. */
+ putip((char*)&released_ip ,&sent_nmb->additional->rdata[2]);
+
+ if (!bcast) {
+ /* mark the WINS server temporarily dead */
+ wins_srv_died(rrec->packet->ip, released_ip);
+ }
+
+ DEBUG(5,("release_name_timeout_response: success in releasing name %s on subnet %s.\n",
+ nmb_namestr(question_name), subrec->subnet_name));
+
+ if (rrec->success_fn) {
+ (*(release_name_success_function)rrec->success_fn)(subrec, rrec->userdata, question_name, released_ip);
+ }
+
+ standard_success_release( subrec, rrec->userdata, question_name, released_ip);
+ remove_response_record(subrec, rrec);
+}
+
+
+/*
+ when releasing a name with WINS we need to send the release to each of
+ the WINS groups
+*/
+static void wins_release_name(struct name_record *namerec,
+ release_name_success_function success_fn,
+ release_name_fail_function fail_fn,
+ struct userdata_struct *userdata)
+{
+ int t, i;
+ char **wins_tags;
+
+ /* get the list of wins tags - we try to release for each of them */
+ wins_tags = wins_srv_tags();
+
+ for (t=0;wins_tags && wins_tags[t]; t++) {
+ for (i = 0; i < namerec->data.num_ips; i++) {
+ struct in_addr wins_ip = wins_srv_ip_tag(wins_tags[t], namerec->data.ip[i]);
+
+ bool last_one = ((i==namerec->data.num_ips - 1) && !wins_tags[t+1]);
+ if (queue_release_name(unicast_subnet,
+ release_name_response,
+ release_name_timeout_response,
+ last_one?success_fn : NULL,
+ last_one? fail_fn : NULL,
+ last_one? userdata : NULL,
+ &namerec->name,
+ namerec->data.nb_flags,
+ namerec->data.ip[i],
+ wins_ip) == NULL) {
+ DEBUG(0,("release_name: Failed to send packet trying to release name %s IP %s\n",
+ nmb_namestr(&namerec->name), inet_ntoa(namerec->data.ip[i]) ));
+ }
+ }
+ }
+
+ wins_srv_tags_free(wins_tags);
+}
+
+
+/****************************************************************************
+ Try and release one of our names.
+****************************************************************************/
+
+void release_name(struct subnet_record *subrec, struct name_record *namerec,
+ release_name_success_function success_fn,
+ release_name_fail_function fail_fn,
+ struct userdata_struct *userdata)
+{
+ int i;
+
+ /* Ensure it's a SELF name, and in the ACTIVE state. */
+ if ((namerec->data.source != SELF_NAME) || !NAME_IS_ACTIVE(namerec)) {
+ DEBUG(0,("release_name: Cannot release name %s from subnet %s. Source was %d \n",
+ nmb_namestr(&namerec->name), subrec->subnet_name, namerec->data.source));
+ return;
+ }
+
+ /* Set the name into the deregistering state. */
+ namerec->data.nb_flags |= NB_DEREG;
+
+ /* wins releases are a bit different */
+ if (subrec == unicast_subnet) {
+ wins_release_name(namerec, success_fn, fail_fn, userdata);
+ return;
+ }
+
+ /*
+ * Go through and release the name for all known ip addresses.
+ * Only call the success/fail function on the last one (it should
+ * only be done once).
+ */
+ for (i = 0; i < namerec->data.num_ips; i++) {
+ if (queue_release_name(subrec,
+ release_name_response,
+ release_name_timeout_response,
+ (i == (namerec->data.num_ips - 1)) ? success_fn : NULL,
+ (i == (namerec->data.num_ips - 1)) ? fail_fn : NULL,
+ (i == (namerec->data.num_ips - 1)) ? userdata : NULL,
+ &namerec->name,
+ namerec->data.nb_flags,
+ namerec->data.ip[i],
+ subrec->bcast_ip) == NULL) {
+ DEBUG(0,("release_name: Failed to send packet trying to release name %s IP %s\n",
+ nmb_namestr(&namerec->name), inet_ntoa(namerec->data.ip[i]) ));
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_nodestatus.c b/source3/nmbd/nmbd_nodestatus.c
new file mode 100644
index 0000000..1f3e1cf
--- /dev/null
+++ b/source3/nmbd/nmbd_nodestatus.c
@@ -0,0 +1,92 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+/****************************************************************************
+ Deal with a successful node status response.
+****************************************************************************/
+
+static void node_status_response(struct subnet_record *subrec,
+ struct response_record *rrec, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question_name = &rrec->packet->packet.nmb.question.question_name;
+ struct nmb_name *answer_name = &nmb->answers->rr_name;
+
+ /* Sanity check. Ensure that the answer name in the incoming packet is the
+ same as the requested name in the outgoing packet. */
+
+ if(!nmb_name_equal(question_name, answer_name)) {
+ DEBUG(0,("node_status_response: Answer name %s differs from question \
+name %s.\n", nmb_namestr(answer_name), nmb_namestr(question_name)));
+ return;
+ }
+
+ DEBUG(5,("node_status_response: response from name %s on subnet %s.\n",
+ nmb_namestr(answer_name), subrec->subnet_name));
+
+ /* Just send the whole answer resource record for the success function to parse. */
+ if(rrec->success_fn)
+ (*(node_status_success_function)rrec->success_fn)(subrec, rrec->userdata, nmb->answers, p->ip);
+
+ /* Ensure we don't retry. */
+ remove_response_record(subrec, rrec);
+}
+
+/****************************************************************************
+ Deal with a timeout when requesting a node status.
+****************************************************************************/
+
+static void node_status_timeout_response(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ struct nmb_packet *sent_nmb = &rrec->packet->packet.nmb;
+ struct nmb_name *question_name = &sent_nmb->question.question_name;
+
+ DEBUG(5,("node_status_timeout_response: failed to get node status from name %s on subnet %s\n",
+ nmb_namestr(question_name), subrec->subnet_name));
+
+ if( rrec->fail_fn)
+ (*rrec->fail_fn)(subrec, rrec);
+
+ /* Ensure we don't retry. */
+ remove_response_record(subrec, rrec);
+}
+
+/****************************************************************************
+ Try and do a node status to a name - given the name & IP address.
+****************************************************************************/
+
+bool node_status(struct subnet_record *subrec, struct nmb_name *nmbname,
+ struct in_addr send_ip, node_status_success_function success_fn,
+ node_status_fail_function fail_fn, struct userdata_struct *userdata)
+{
+ if(queue_node_status( subrec, node_status_response, node_status_timeout_response,
+ success_fn, fail_fn, userdata, nmbname, send_ip)==NULL) {
+ DEBUG(0,("node_status: Failed to send packet trying to get node status for \
+name %s, IP address %s\n", nmb_namestr(nmbname), inet_ntoa(send_ip)));
+ return True;
+ }
+ return False;
+}
diff --git a/source3/nmbd/nmbd_packets.c b/source3/nmbd/nmbd_packets.c
new file mode 100644
index 0000000..a1d8dee
--- /dev/null
+++ b/source3/nmbd/nmbd_packets.c
@@ -0,0 +1,2262 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+#include "../lib/util/select.h"
+#include "system/select.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/unexpected.h"
+#include "lib/util/string_wrappers.h"
+
+extern int ClientNMB;
+extern int ClientDGRAM;
+extern int global_nmb_port;
+
+extern int num_response_packets;
+
+bool rescan_listen_set = False;
+
+static struct nb_packet_server *packet_server;
+
+bool nmbd_init_packet_server(void)
+{
+ NTSTATUS status;
+
+ status = nb_packet_server_create(
+ NULL, nmbd_event_context(),
+ lp_parm_int(-1, "nmbd", "unexpected_clients", 200),
+ &packet_server);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("ERROR: nb_packet_server_create failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ return true;
+}
+
+
+/*******************************************************************
+ The global packet linked-list. Incoming entries are
+ added to the end of this list. It is supposed to remain fairly
+ short so we won't bother with an end pointer.
+******************************************************************/
+
+static struct packet_struct *packet_queue = NULL;
+
+/***************************************************************************
+Utility function to find the specific fd to send a packet out on.
+**************************************************************************/
+
+static int find_subnet_fd_for_address( struct in_addr local_ip )
+{
+ struct subnet_record *subrec;
+
+ for( subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec))
+ if(ip_equal_v4(local_ip, subrec->myip))
+ return subrec->nmb_sock;
+
+ return ClientNMB;
+}
+
+/***************************************************************************
+Utility function to find the specific fd to send a mailslot packet out on.
+**************************************************************************/
+
+static int find_subnet_mailslot_fd_for_address( struct in_addr local_ip )
+{
+ struct subnet_record *subrec;
+
+ for( subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec))
+ if(ip_equal_v4(local_ip, subrec->myip))
+ return subrec->dgram_sock;
+
+ return ClientDGRAM;
+}
+
+/***************************************************************************
+Get/Set problematic nb_flags as network byte order 16 bit int.
+**************************************************************************/
+
+uint16_t get_nb_flags(char *buf)
+{
+ return ((((uint16_t)*buf)&0xFFFF) & NB_FLGMSK);
+}
+
+void set_nb_flags(char *buf, uint16_t nb_flags)
+{
+ *buf++ = ((nb_flags & NB_FLGMSK) & 0xFF);
+ *buf = '\0';
+}
+
+/***************************************************************************
+Dumps out the browse packet data.
+**************************************************************************/
+
+static void debug_browse_data(const char *outbuf, int len)
+{
+ int i,j;
+
+ DEBUG( 4, ( "debug_browse_data():\n" ) );
+ for (i = 0; i < len; i+= 16) {
+ DEBUGADD( 4, ( "%3x char ", i ) );
+
+ for (j = 0; j < 16; j++) {
+ unsigned char x;
+ if (i+j >= len)
+ break;
+
+ x = outbuf[i+j];
+ if (x < 32 || x > 127)
+ x = '.';
+
+ DEBUGADD( 4, ( "%c", x ) );
+ }
+
+ DEBUGADD( 4, ( "%*s hex", 16-j, "" ) );
+
+ for (j = 0; j < 16; j++) {
+ if (i+j >= len)
+ break;
+ DEBUGADD( 4, ( " %02x", (unsigned char)outbuf[i+j] ) );
+ }
+
+ DEBUGADD( 4, ("\n") );
+ }
+}
+
+/***************************************************************************
+ Generates the unique transaction identifier
+**************************************************************************/
+
+static uint16_t name_trn_id=0;
+
+static uint16_t generate_name_trn_id(void)
+{
+ if (!name_trn_id) {
+ name_trn_id = ((unsigned)time(NULL)%(unsigned)0x7FFF) + ((unsigned)getpid()%(unsigned)100);
+ }
+ name_trn_id = (name_trn_id+1) % (unsigned)0x7FFF;
+ return name_trn_id;
+}
+
+/***************************************************************************
+ Either loops back or sends out a completed NetBIOS packet.
+**************************************************************************/
+
+static bool send_netbios_packet(struct packet_struct *p)
+{
+ bool loopback_this_packet = False;
+
+ /* Check if we are sending to or from ourselves as a WINS server. */
+ if(ismyip_v4(p->ip) && (p->port == global_nmb_port))
+ loopback_this_packet = True;
+
+ if(loopback_this_packet) {
+ struct packet_struct *lo_packet = NULL;
+ DEBUG(5,("send_netbios_packet: sending packet to ourselves.\n"));
+ if((lo_packet = copy_packet(p)) == NULL)
+ return False;
+ queue_packet(lo_packet);
+ } else if (!send_packet(p)) {
+ DEBUG(0,("send_netbios_packet: send_packet() to IP %s port %d failed\n",
+ inet_ntoa(p->ip),p->port));
+ return False;
+ }
+
+ return True;
+}
+
+/***************************************************************************
+ Sets up the common elements of an outgoing NetBIOS packet.
+
+ Note: do not attempt to rationalise whether rec_des should be set or not
+ in a particular situation. Just follow rfc_1002 or look at examples from WinXX.
+ It does NOT follow the rule that requests to the wins server always have
+ rec_des true. See for example name releases and refreshes
+**************************************************************************/
+
+static struct packet_struct *create_and_init_netbios_packet(struct nmb_name *nmbname,
+ bool bcast, bool rec_des,
+ struct in_addr to_ip)
+{
+ struct packet_struct *packet = NULL;
+ struct nmb_packet *nmb = NULL;
+
+ /* Allocate the packet_struct we will return. */
+ if((packet = SMB_MALLOC_P(struct packet_struct)) == NULL) {
+ DEBUG(0,("create_and_init_netbios_packet: malloc fail (1) for packet struct.\n"));
+ return NULL;
+ }
+
+ memset((char *)packet,'\0',sizeof(*packet));
+
+ nmb = &packet->packet.nmb;
+
+ nmb->header.name_trn_id = generate_name_trn_id();
+ nmb->header.response = False;
+ nmb->header.nm_flags.recursion_desired = rec_des;
+ nmb->header.nm_flags.recursion_available = False;
+ nmb->header.nm_flags.trunc = False;
+ nmb->header.nm_flags.authoritative = False;
+ nmb->header.nm_flags.bcast = bcast;
+
+ nmb->header.rcode = 0;
+ nmb->header.qdcount = 1;
+ nmb->header.ancount = 0;
+ nmb->header.nscount = 0;
+
+ nmb->question.question_name = *nmbname;
+ nmb->question.question_type = QUESTION_TYPE_NB_QUERY;
+ nmb->question.question_class = QUESTION_CLASS_IN;
+
+ packet->ip = to_ip;
+ packet->port = NMB_PORT;
+ packet->recv_fd = -1;
+ packet->send_fd = ClientNMB;
+ packet->timestamp = time(NULL);
+ packet->packet_type = NMB_PACKET;
+ packet->locked = False;
+
+ return packet; /* Caller must free. */
+}
+
+/***************************************************************************
+ Sets up the common elements of register, refresh or release packet.
+**************************************************************************/
+
+static bool create_and_init_additional_record(struct packet_struct *packet,
+ uint16_t nb_flags,
+ const struct in_addr *register_ip)
+{
+ struct nmb_packet *nmb = &packet->packet.nmb;
+
+ if((nmb->additional = SMB_MALLOC_P(struct res_rec)) == NULL) {
+ DEBUG(0,("create_and_init_additional_record: malloc fail for additional record.\n"));
+ return False;
+ }
+
+ memset((char *)nmb->additional,'\0',sizeof(struct res_rec));
+
+ nmb->additional->rr_name = nmb->question.question_name;
+ nmb->additional->rr_type = RR_TYPE_NB;
+ nmb->additional->rr_class = RR_CLASS_IN;
+
+ /* See RFC 1002, sections 5.1.1.1, 5.1.1.2 and 5.1.1.3 */
+ if (nmb->header.nm_flags.bcast)
+ nmb->additional->ttl = PERMANENT_TTL;
+ else
+ nmb->additional->ttl = lp_max_ttl();
+
+ nmb->additional->rdlength = 6;
+
+ set_nb_flags(nmb->additional->rdata,nb_flags);
+
+ /* Set the address for the name we are registering. */
+ putip(&nmb->additional->rdata[2], register_ip);
+
+ /*
+ it turns out that Jeremys code was correct, we are supposed
+ to send registrations from the IP we are registering. The
+ trick is what to do on timeouts! When we send on a
+ non-routable IP then the reply will timeout, and we should
+ treat this as success, not failure. That means we go into
+ our standard refresh cycle for that name which copes nicely
+ with disconnected networks.
+ */
+ packet->recv_fd = -1;
+ packet->send_fd = find_subnet_fd_for_address(*register_ip);
+
+ return True;
+}
+
+/***************************************************************************
+ Sends out a name query.
+**************************************************************************/
+
+static bool initiate_name_query_packet( struct packet_struct *packet)
+{
+ struct nmb_packet *nmb = NULL;
+
+ nmb = &packet->packet.nmb;
+
+ nmb->header.opcode = NMB_NAME_QUERY_OPCODE;
+ nmb->header.arcount = 0;
+
+ nmb->header.nm_flags.recursion_desired = True;
+
+ DEBUG(4,("initiate_name_query_packet: sending query for name %s (bcast=%s) to IP %s\n",
+ nmb_namestr(&nmb->question.question_name),
+ BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip)));
+
+ return send_netbios_packet( packet );
+}
+
+/***************************************************************************
+ Sends out a name query - from a WINS server.
+**************************************************************************/
+
+static bool initiate_name_query_packet_from_wins_server( struct packet_struct *packet)
+{
+ struct nmb_packet *nmb = NULL;
+
+ nmb = &packet->packet.nmb;
+
+ nmb->header.opcode = NMB_NAME_QUERY_OPCODE;
+ nmb->header.arcount = 0;
+
+ nmb->header.nm_flags.recursion_desired = False;
+
+ DEBUG(4,("initiate_name_query_packet_from_wins_server: sending query for name %s (bcast=%s) to IP %s\n",
+ nmb_namestr(&nmb->question.question_name),
+ BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip)));
+
+ return send_netbios_packet( packet );
+}
+
+/***************************************************************************
+ Sends out a name register.
+**************************************************************************/
+
+static bool initiate_name_register_packet( struct packet_struct *packet,
+ uint16_t nb_flags, const struct in_addr *register_ip)
+{
+ struct nmb_packet *nmb = &packet->packet.nmb;
+
+ nmb->header.opcode = NMB_NAME_REG_OPCODE;
+ nmb->header.arcount = 1;
+
+ nmb->header.nm_flags.recursion_desired = True;
+
+ if(create_and_init_additional_record(packet, nb_flags, register_ip) == False)
+ return False;
+
+ DEBUG(4,("initiate_name_register_packet: sending registration for name %s (bcast=%s) to IP %s\n",
+ nmb_namestr(&nmb->additional->rr_name),
+ BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip)));
+
+ return send_netbios_packet( packet );
+}
+
+/***************************************************************************
+ Sends out a multihomed name register.
+**************************************************************************/
+
+static bool initiate_multihomed_name_register_packet(struct packet_struct *packet,
+ uint16_t nb_flags, struct in_addr *register_ip)
+{
+ struct nmb_packet *nmb = &packet->packet.nmb;
+ fstring second_ip_buf;
+
+ fstrcpy(second_ip_buf, inet_ntoa(packet->ip));
+
+ nmb->header.opcode = NMB_NAME_MULTIHOMED_REG_OPCODE;
+ nmb->header.arcount = 1;
+
+ nmb->header.nm_flags.recursion_desired = True;
+
+ if(create_and_init_additional_record(packet, nb_flags, register_ip) == False)
+ return False;
+
+ DEBUG(4,("initiate_multihomed_name_register_packet: sending registration \
+for name %s IP %s (bcast=%s) to IP %s\n",
+ nmb_namestr(&nmb->additional->rr_name), inet_ntoa(*register_ip),
+ BOOLSTR(nmb->header.nm_flags.bcast), second_ip_buf ));
+
+ return send_netbios_packet( packet );
+}
+
+/***************************************************************************
+ Sends out a name refresh.
+**************************************************************************/
+
+static bool initiate_name_refresh_packet( struct packet_struct *packet,
+ uint16_t nb_flags, struct in_addr *refresh_ip)
+{
+ struct nmb_packet *nmb = &packet->packet.nmb;
+
+ nmb->header.opcode = NMB_NAME_REFRESH_OPCODE_8;
+ nmb->header.arcount = 1;
+
+ nmb->header.nm_flags.recursion_desired = False;
+
+ if(create_and_init_additional_record(packet, nb_flags, refresh_ip) == False)
+ return False;
+
+ DEBUG(4,("initiate_name_refresh_packet: sending refresh for name %s (bcast=%s) to IP %s\n",
+ nmb_namestr(&nmb->additional->rr_name),
+ BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip)));
+
+ return send_netbios_packet( packet );
+}
+
+/***************************************************************************
+ Sends out a name release.
+**************************************************************************/
+
+static bool initiate_name_release_packet( struct packet_struct *packet,
+ uint16_t nb_flags, struct in_addr *release_ip)
+{
+ struct nmb_packet *nmb = &packet->packet.nmb;
+
+ nmb->header.opcode = NMB_NAME_RELEASE_OPCODE;
+ nmb->header.arcount = 1;
+
+ nmb->header.nm_flags.recursion_desired = False;
+
+ if(create_and_init_additional_record(packet, nb_flags, release_ip) == False)
+ return False;
+
+ DEBUG(4,("initiate_name_release_packet: sending release for name %s (bcast=%s) to IP %s\n",
+ nmb_namestr(&nmb->additional->rr_name),
+ BOOLSTR(nmb->header.nm_flags.bcast), inet_ntoa(packet->ip)));
+
+ return send_netbios_packet( packet );
+}
+
+/***************************************************************************
+ Sends out a node status.
+**************************************************************************/
+
+static bool initiate_node_status_packet( struct packet_struct *packet )
+{
+ struct nmb_packet *nmb = &packet->packet.nmb;
+
+ nmb->header.opcode = NMB_NAME_QUERY_OPCODE;
+ nmb->header.arcount = 0;
+
+ nmb->header.nm_flags.recursion_desired = False;
+
+ nmb->question.question_type = QUESTION_TYPE_NB_STATUS;
+
+ DEBUG(4,("initiate_node_status_packet: sending node status request for name %s to IP %s\n",
+ nmb_namestr(&nmb->question.question_name),
+ inet_ntoa(packet->ip)));
+
+ return send_netbios_packet( packet );
+}
+
+/****************************************************************************
+ Simplification functions for queuing standard packets.
+ These should be the only publicly callable functions for sending
+ out packets.
+****************************************************************************/
+
+/****************************************************************************
+ Assertion - we should never be sending nmbd packets on the remote
+ broadcast subnet.
+****************************************************************************/
+
+static bool assert_check_subnet(struct subnet_record *subrec)
+{
+ if( subrec == remote_broadcast_subnet) {
+ DEBUG(0,("assert_check_subnet: Attempt to send packet on remote broadcast subnet. \
+This is a bug.\n"));
+ return True;
+ }
+ return False;
+}
+
+/****************************************************************************
+ Queue a register name packet to the broadcast address of a subnet.
+****************************************************************************/
+
+struct response_record *queue_register_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ uint16_t nb_flags)
+{
+ struct packet_struct *p;
+ struct response_record *rrec;
+ struct sockaddr_storage ss;
+ const struct sockaddr_storage *pss = NULL;
+ if(assert_check_subnet(subrec))
+ return NULL;
+
+ /* note that all name registration requests have RD set (rfc1002 - section 4.2.2 */
+ if ((p = create_and_init_netbios_packet(nmbname, (subrec != unicast_subnet), True,
+ subrec->bcast_ip)) == NULL)
+ return NULL;
+
+ in_addr_to_sockaddr_storage(&ss, subrec->bcast_ip);
+ pss = iface_ip((struct sockaddr *)(void *)&ss);
+ if (!pss || pss->ss_family != AF_INET) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ if(initiate_name_register_packet(p, nb_flags,
+ &((const struct sockaddr_in *)pss)->sin_addr) == False) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ if((rrec = make_response_record(subrec, /* subnet record. */
+ p, /* packet we sent. */
+ resp_fn, /* function to call on response. */
+ timeout_fn, /* function to call on timeout. */
+ (success_function)success_fn, /* function to call on operation success. */
+ (fail_function)fail_fn, /* function to call on operation fail. */
+ userdata)) == NULL) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ return rrec;
+}
+
+/****************************************************************************
+ Queue a refresh name packet to the broadcast address of a subnet.
+****************************************************************************/
+
+void queue_wins_refresh(struct nmb_name *nmbname,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ uint16_t nb_flags,
+ struct in_addr refresh_ip,
+ const char *tag)
+{
+ struct packet_struct *p;
+ struct response_record *rrec;
+ struct in_addr wins_ip;
+ struct userdata_struct *userdata;
+ fstring ip_str;
+
+ wins_ip = wins_srv_ip_tag(tag, refresh_ip);
+
+ if ((p = create_and_init_netbios_packet(nmbname, False, False, wins_ip)) == NULL) {
+ return;
+ }
+
+ if (!initiate_name_refresh_packet(p, nb_flags, &refresh_ip)) {
+ p->locked = False;
+ free_packet(p);
+ return;
+ }
+
+ fstrcpy(ip_str, inet_ntoa(refresh_ip));
+
+ DEBUG(6,("Refreshing name %s IP %s with WINS server %s using tag '%s'\n",
+ nmb_namestr(nmbname), ip_str, inet_ntoa(wins_ip), tag));
+
+ userdata = (struct userdata_struct *)SMB_MALLOC(sizeof(*userdata) + strlen(tag) + 1);
+ if (!userdata) {
+ p->locked = False;
+ free_packet(p);
+ DEBUG(0,("Failed to allocate userdata structure!\n"));
+ return;
+ }
+ ZERO_STRUCTP(userdata);
+ userdata->userdata_len = strlen(tag) + 1;
+ strlcpy(userdata->data, tag, userdata->userdata_len);
+
+ if ((rrec = make_response_record(unicast_subnet,
+ p,
+ resp_fn, timeout_fn,
+ NULL,
+ NULL,
+ userdata)) == NULL) {
+ p->locked = False;
+ free_packet(p);
+ return;
+ }
+
+ free(userdata);
+
+ /* we don't want to repeat refresh packets */
+ rrec->repeat_count = 0;
+}
+
+
+/****************************************************************************
+ Queue a multihomed register name packet to a given WINS server IP
+****************************************************************************/
+
+struct response_record *queue_register_multihomed_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ uint16_t nb_flags,
+ struct in_addr register_ip,
+ struct in_addr wins_ip)
+{
+ struct packet_struct *p;
+ struct response_record *rrec;
+ bool ret;
+
+ /* Sanity check. */
+ if(subrec != unicast_subnet) {
+ DEBUG(0,("queue_register_multihomed_name: should only be done on \
+unicast subnet. subnet is %s\n.", subrec->subnet_name ));
+ return NULL;
+ }
+
+ if(assert_check_subnet(subrec))
+ return NULL;
+
+ if ((p = create_and_init_netbios_packet(nmbname, False, True, wins_ip)) == NULL)
+ return NULL;
+
+ if (nb_flags & NB_GROUP)
+ ret = initiate_name_register_packet( p, nb_flags, &register_ip);
+ else
+ ret = initiate_multihomed_name_register_packet(p, nb_flags, &register_ip);
+
+ if (ret == False) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ if ((rrec = make_response_record(subrec, /* subnet record. */
+ p, /* packet we sent. */
+ resp_fn, /* function to call on response. */
+ timeout_fn, /* function to call on timeout. */
+ (success_function)success_fn, /* function to call on operation success. */
+ (fail_function)fail_fn, /* function to call on operation fail. */
+ userdata)) == NULL) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ return rrec;
+}
+
+/****************************************************************************
+ Queue a release name packet to the broadcast address of a subnet.
+****************************************************************************/
+
+struct response_record *queue_release_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ release_name_success_function success_fn,
+ release_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ uint16_t nb_flags,
+ struct in_addr release_ip,
+ struct in_addr dest_ip)
+{
+ struct packet_struct *p;
+ struct response_record *rrec;
+
+ if(assert_check_subnet(subrec))
+ return NULL;
+
+ if ((p = create_and_init_netbios_packet(nmbname, (subrec != unicast_subnet), False, dest_ip)) == NULL)
+ return NULL;
+
+ if(initiate_name_release_packet( p, nb_flags, &release_ip) == False) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ if((rrec = make_response_record(subrec, /* subnet record. */
+ p, /* packet we sent. */
+ resp_fn, /* function to call on response. */
+ timeout_fn, /* function to call on timeout. */
+ (success_function)success_fn, /* function to call on operation success. */
+ (fail_function)fail_fn, /* function to call on operation fail. */
+ userdata)) == NULL) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ /*
+ * For a broadcast release packet, only send once.
+ * This will cause us to remove the name asap. JRA.
+ */
+
+ if (subrec != unicast_subnet) {
+ rrec->repeat_count = 0;
+ rrec->repeat_time = 0;
+ }
+
+ return rrec;
+}
+
+/****************************************************************************
+ Queue a query name packet to the broadcast address of a subnet.
+****************************************************************************/
+
+struct response_record *queue_query_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname)
+{
+ struct packet_struct *p;
+ struct response_record *rrec;
+ struct in_addr to_ip;
+
+ if(assert_check_subnet(subrec))
+ return NULL;
+
+ to_ip = subrec->bcast_ip;
+
+ /* queries to the WINS server turn up here as queries to IP 0.0.0.0
+ These need to be handled a bit differently */
+ if (subrec->type == UNICAST_SUBNET && is_zero_ip_v4(to_ip)) {
+ /* What we really need to do is loop over each of our wins
+ * servers and wins server tags here, but that just doesn't
+ * fit our architecture at the moment (userdata may already
+ * be used when we get here). For now we just query the first
+ * active wins server on the first tag.
+ */
+ char **tags = wins_srv_tags();
+ if (!tags) {
+ return NULL;
+ }
+ to_ip = wins_srv_ip_tag(tags[0], to_ip);
+ wins_srv_tags_free(tags);
+ }
+
+ if(( p = create_and_init_netbios_packet(nmbname,
+ (subrec != unicast_subnet),
+ (subrec == unicast_subnet),
+ to_ip)) == NULL)
+ return NULL;
+
+ if(lp_bind_interfaces_only()) {
+ int i;
+
+ DEBUG(10,("queue_query_name: bind_interfaces_only is set, looking for suitable source IP\n"));
+ for(i = 0; i < iface_count(); i++) {
+ const struct in_addr *ifip = iface_n_ip_v4(i);
+
+ if (ifip == NULL) {
+ DEBUG(0,("queue_query_name: interface %d has NULL IP address !\n", i));
+ continue;
+ }
+
+ if (is_loopback_ip_v4(*ifip)) {
+ DEBUG(5,("queue_query_name: ignoring loopback interface (%d)\n", i));
+ continue;
+ }
+
+ DEBUG(10,("queue_query_name: using source IP %s\n",inet_ntoa(*ifip)));
+ p->send_fd = find_subnet_fd_for_address( *ifip );
+ break;
+ }
+ }
+
+ if(initiate_name_query_packet( p ) == False) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ if((rrec = make_response_record(subrec, /* subnet record. */
+ p, /* packet we sent. */
+ resp_fn, /* function to call on response. */
+ timeout_fn, /* function to call on timeout. */
+ (success_function)success_fn, /* function to call on operation success. */
+ (fail_function)fail_fn, /* function to call on operation fail. */
+ userdata)) == NULL) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ return rrec;
+}
+
+/****************************************************************************
+ Queue a query name packet to a given address from the WINS subnet.
+****************************************************************************/
+
+struct response_record *queue_query_name_from_wins_server( struct in_addr to_ip,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname)
+{
+ struct packet_struct *p;
+ struct response_record *rrec;
+
+ if ((p = create_and_init_netbios_packet(nmbname, False, False, to_ip)) == NULL)
+ return NULL;
+
+ if(initiate_name_query_packet_from_wins_server( p ) == False) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ if((rrec = make_response_record(wins_server_subnet, /* subnet record. */
+ p, /* packet we sent. */
+ resp_fn, /* function to call on response. */
+ timeout_fn, /* function to call on timeout. */
+ (success_function)success_fn, /* function to call on operation success. */
+ (fail_function)fail_fn, /* function to call on operation fail. */
+ userdata)) == NULL) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ return rrec;
+}
+
+/****************************************************************************
+ Queue a node status packet to a given name and address.
+****************************************************************************/
+
+struct response_record *queue_node_status( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ node_status_success_function success_fn,
+ node_status_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ struct in_addr send_ip)
+{
+ struct packet_struct *p;
+ struct response_record *rrec;
+
+ /* Sanity check. */
+ if(subrec != unicast_subnet) {
+ DEBUG(0,("queue_register_multihomed_name: should only be done on \
+unicast subnet. subnet is %s\n.", subrec->subnet_name ));
+ return NULL;
+ }
+
+ if(assert_check_subnet(subrec))
+ return NULL;
+
+ if(( p = create_and_init_netbios_packet(nmbname, False, False, send_ip)) == NULL)
+ return NULL;
+
+ if(initiate_node_status_packet(p) == False) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ if((rrec = make_response_record(subrec, /* subnet record. */
+ p, /* packet we sent. */
+ resp_fn, /* function to call on response. */
+ timeout_fn, /* function to call on timeout. */
+ (success_function)success_fn, /* function to call on operation success. */
+ (fail_function)fail_fn, /* function to call on operation fail. */
+ userdata)) == NULL) {
+ p->locked = False;
+ free_packet(p);
+ return NULL;
+ }
+
+ return rrec;
+}
+
+/****************************************************************************
+ Reply to a netbios name packet. see rfc1002.txt
+****************************************************************************/
+
+void reply_netbios_packet(struct packet_struct *orig_packet,
+ int rcode, enum netbios_reply_type_code rcv_code, int opcode,
+ int ttl, char *data,int len)
+{
+ struct packet_struct packet;
+ struct nmb_packet *nmb = NULL;
+ struct res_rec answers;
+ struct nmb_packet *orig_nmb = &orig_packet->packet.nmb;
+ bool loopback_this_packet = False;
+ int rr_type = RR_TYPE_NB;
+ const char *packet_type = "unknown";
+
+ /* Check if we are sending to or from ourselves. */
+ if(ismyip_v4(orig_packet->ip) && (orig_packet->port == global_nmb_port))
+ loopback_this_packet = True;
+
+ nmb = &packet.packet.nmb;
+
+ /* Do a partial copy of the packet. We clear the locked flag and
+ the resource record pointers. */
+ packet = *orig_packet; /* Full structure copy. */
+ packet.locked = False;
+ nmb->answers = NULL;
+ nmb->nsrecs = NULL;
+ nmb->additional = NULL;
+
+ switch (rcv_code) {
+ case NMB_STATUS:
+ packet_type = "nmb_status";
+ nmb->header.nm_flags.recursion_desired = False;
+ nmb->header.nm_flags.recursion_available = False;
+ rr_type = RR_TYPE_NBSTAT;
+ break;
+ case NMB_QUERY:
+ packet_type = "nmb_query";
+ nmb->header.nm_flags.recursion_desired = True;
+ nmb->header.nm_flags.recursion_available = True;
+ if (rcode) {
+ rr_type = RR_TYPE_NULL;
+ }
+ break;
+ case NMB_REG:
+ case NMB_REG_REFRESH:
+ packet_type = "nmb_reg";
+ nmb->header.nm_flags.recursion_desired = True;
+ nmb->header.nm_flags.recursion_available = True;
+ break;
+ case NMB_REL:
+ packet_type = "nmb_rel";
+ nmb->header.nm_flags.recursion_desired = False;
+ nmb->header.nm_flags.recursion_available = False;
+ break;
+ case NMB_WAIT_ACK:
+ packet_type = "nmb_wack";
+ nmb->header.nm_flags.recursion_desired = False;
+ nmb->header.nm_flags.recursion_available = False;
+ rr_type = RR_TYPE_NULL;
+ break;
+ case WINS_REG:
+ packet_type = "wins_reg";
+ nmb->header.nm_flags.recursion_desired = True;
+ nmb->header.nm_flags.recursion_available = True;
+ break;
+ case WINS_QUERY:
+ packet_type = "wins_query";
+ nmb->header.nm_flags.recursion_desired = True;
+ nmb->header.nm_flags.recursion_available = True;
+ if (rcode) {
+ rr_type = RR_TYPE_NULL;
+ }
+ break;
+ default:
+ DEBUG(0,("reply_netbios_packet: Unknown packet type: %s %s to ip %s\n",
+ packet_type, nmb_namestr(&orig_nmb->question.question_name),
+ inet_ntoa(packet.ip)));
+ return;
+ }
+
+ DEBUG(4, ("reply_netbios_packet: sending a reply of packet type: %s "
+ "%s to ip %s for id %d\n", packet_type,
+ nmb_namestr(&orig_nmb->question.question_name),
+ inet_ntoa(packet.ip), orig_nmb->header.name_trn_id));
+
+ nmb->header.name_trn_id = orig_nmb->header.name_trn_id;
+ nmb->header.opcode = opcode;
+ nmb->header.response = True;
+ nmb->header.nm_flags.bcast = False;
+ nmb->header.nm_flags.trunc = False;
+ nmb->header.nm_flags.authoritative = True;
+
+ nmb->header.rcode = rcode;
+ nmb->header.qdcount = 0;
+ nmb->header.ancount = 1;
+ nmb->header.nscount = 0;
+ nmb->header.arcount = 0;
+
+ memset((char*)&nmb->question,'\0',sizeof(nmb->question));
+
+ nmb->answers = &answers;
+ memset((char*)nmb->answers,'\0',sizeof(*nmb->answers));
+
+ nmb->answers->rr_name = orig_nmb->question.question_name;
+ nmb->answers->rr_type = rr_type;
+ nmb->answers->rr_class = RR_CLASS_IN;
+ nmb->answers->ttl = ttl;
+
+ if (data && len) {
+ if (len < 0 || len > sizeof(nmb->answers->rdata)) {
+ DEBUG(5,("reply_netbios_packet: "
+ "invalid packet len (%d)\n",
+ len ));
+ return;
+ }
+ nmb->answers->rdlength = len;
+ memcpy(nmb->answers->rdata, data, len);
+ }
+
+ packet.packet_type = NMB_PACKET;
+ packet.recv_fd = -1;
+ /* Ensure we send out on the same fd that the original
+ packet came in on to give the correct source IP address. */
+ if (orig_packet->send_fd != -1) {
+ packet.send_fd = orig_packet->send_fd;
+ } else {
+ packet.send_fd = orig_packet->recv_fd;
+ }
+ packet.timestamp = time(NULL);
+
+ debug_nmb_packet(&packet);
+
+ if(loopback_this_packet) {
+ struct packet_struct *lo_packet;
+ DEBUG(5,("reply_netbios_packet: sending packet to ourselves.\n"));
+ if((lo_packet = copy_packet(&packet)) == NULL)
+ return;
+ queue_packet(lo_packet);
+ } else if (!send_packet(&packet)) {
+ DEBUG(0,("reply_netbios_packet: send_packet to IP %s port %d failed\n",
+ inet_ntoa(packet.ip),packet.port));
+ }
+}
+
+/*******************************************************************
+ Queue a packet into a packet queue
+******************************************************************/
+
+void queue_packet(struct packet_struct *packet)
+{
+ DLIST_ADD_END(packet_queue, packet);
+}
+
+/****************************************************************************
+ Try and find a matching subnet record for a datagram port 138 packet.
+****************************************************************************/
+
+static struct subnet_record *find_subnet_for_dgram_browse_packet(struct packet_struct *p)
+{
+ struct subnet_record *subrec;
+
+ /* Go through all the broadcast subnets and see if the mask matches. */
+ for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ if(same_net_v4(p->ip, subrec->bcast_ip, subrec->mask_ip))
+ return subrec;
+ }
+
+ /* If the subnet record is the remote announce broadcast subnet,
+ hack it here to be the first subnet. This is really gross and
+ is needed due to people turning on port 137/138 broadcast
+ forwarding on their routers. May fire and brimstone rain
+ down upon them...
+ */
+
+ return FIRST_SUBNET;
+}
+
+/****************************************************************************
+Dispatch a browse frame from port 138 to the correct processing function.
+****************************************************************************/
+
+static void process_browse_packet(struct packet_struct *p, const char *buf,int len)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ int command = CVAL(buf,0);
+ struct subnet_record *subrec = find_subnet_for_dgram_browse_packet(p);
+ char scope[64];
+ unstring src_name;
+
+ /* Drop the packet if it's a different NetBIOS scope, or the source is from one of our names. */
+ pull_ascii(scope, dgram->dest_name.scope, 64, 64, STR_TERMINATE);
+ if (!strequal(scope, lp_netbios_scope())) {
+ DEBUG(7,("process_browse_packet: Discarding datagram from IP %s. Scope (%s) \
+mismatch with our scope (%s).\n", inet_ntoa(p->ip), scope, lp_netbios_scope()));
+ return;
+ }
+
+ pull_ascii_nstring(src_name, sizeof(src_name), dgram->source_name.name);
+ if (is_myname(src_name)) {
+ DEBUG(7,("process_browse_packet: Discarding datagram from IP %s. Source name \
+%s is one of our names !\n", inet_ntoa(p->ip), nmb_namestr(&dgram->source_name)));
+ return;
+ }
+
+ switch (command) {
+ case ANN_HostAnnouncement:
+ debug_browse_data(buf, len);
+ process_host_announce(subrec, p, buf+1);
+ break;
+ case ANN_DomainAnnouncement:
+ debug_browse_data(buf, len);
+ process_workgroup_announce(subrec, p, buf+1);
+ break;
+ case ANN_LocalMasterAnnouncement:
+ debug_browse_data(buf, len);
+ process_local_master_announce(subrec, p, buf+1);
+ break;
+ case ANN_AnnouncementRequest:
+ debug_browse_data(buf, len);
+ process_announce_request(subrec, p, buf+1);
+ break;
+ case ANN_Election:
+ debug_browse_data(buf, len);
+ process_election(subrec, p, buf+1);
+ break;
+ case ANN_GetBackupListReq:
+ debug_browse_data(buf, len);
+ process_get_backup_list_request(subrec, p, buf+1);
+ break;
+ case ANN_GetBackupListResp:
+ debug_browse_data(buf, len);
+ /* We never send ANN_GetBackupListReq so we should never get these. */
+ DEBUG(0,("process_browse_packet: Discarding GetBackupListResponse \
+packet from %s IP %s\n", nmb_namestr(&dgram->source_name), inet_ntoa(p->ip)));
+ break;
+ case ANN_ResetBrowserState:
+ debug_browse_data(buf, len);
+ process_reset_browser(subrec, p, buf+1);
+ break;
+ case ANN_MasterAnnouncement:
+ /* Master browser datagrams must be processed on the unicast subnet. */
+ subrec = unicast_subnet;
+
+ debug_browse_data(buf, len);
+ process_master_browser_announce(subrec, p, buf+1);
+ break;
+ case ANN_BecomeBackup:
+ /*
+ * We don't currently implement this. Log it just in case.
+ */
+ debug_browse_data(buf, len);
+ DEBUG(10,("process_browse_packet: On subnet %s ignoring browse packet \
+command ANN_BecomeBackup from %s IP %s to %s\n", subrec->subnet_name, nmb_namestr(&dgram->source_name),
+ inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name)));
+ break;
+ default:
+ debug_browse_data(buf, len);
+ DEBUG(0,("process_browse_packet: On subnet %s ignoring browse packet \
+command code %d from %s IP %s to %s\n", subrec->subnet_name, command, nmb_namestr(&dgram->source_name),
+ inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name)));
+ break;
+ }
+}
+
+/****************************************************************************
+ Dispatch a LanMan browse frame from port 138 to the correct processing function.
+****************************************************************************/
+
+static void process_lanman_packet(struct packet_struct *p, const char *buf,int len)
+{
+ struct dgram_packet *dgram = &p->packet.dgram;
+ int command = SVAL(buf,0);
+ struct subnet_record *subrec = find_subnet_for_dgram_browse_packet(p);
+ char scope[64];
+ unstring src_name;
+
+ /* Drop the packet if it's a different NetBIOS scope, or the source is from one of our names. */
+
+ pull_ascii(scope, dgram->dest_name.scope, 64, 64, STR_TERMINATE);
+ if (!strequal(scope, lp_netbios_scope())) {
+ DEBUG(7,("process_lanman_packet: Discarding datagram from IP %s. Scope (%s) \
+mismatch with our scope (%s).\n", inet_ntoa(p->ip), scope, lp_netbios_scope()));
+ return;
+ }
+
+ pull_ascii_nstring(src_name, sizeof(src_name), dgram->source_name.name);
+ if (is_myname(src_name)) {
+ DEBUG(0,("process_lanman_packet: Discarding datagram from IP %s. Source name \
+%s is one of our names !\n", inet_ntoa(p->ip), nmb_namestr(&dgram->source_name)));
+ return;
+ }
+
+ switch (command) {
+ case ANN_HostAnnouncement:
+ debug_browse_data(buf, len);
+ process_lm_host_announce(subrec, p, buf+1, len > 1 ? len-1 : 0);
+ break;
+ case ANN_AnnouncementRequest:
+ process_lm_announce_request(subrec, p, buf+1, len > 1 ? len-1 : 0);
+ break;
+ default:
+ DEBUG(0,("process_lanman_packet: On subnet %s ignoring browse packet \
+command code %d from %s IP %s to %s\n", subrec->subnet_name, command, nmb_namestr(&dgram->source_name),
+ inet_ntoa(p->ip), nmb_namestr(&dgram->dest_name)));
+ break;
+ }
+}
+
+/****************************************************************************
+ Determine if a packet is for us on port 138. Note that to have any chance of
+ being efficient we need to drop as many packets as possible at this
+ stage as subsequent processing is expensive.
+****************************************************************************/
+
+static bool listening(struct packet_struct *p,struct nmb_name *nbname)
+{
+ struct subnet_record *subrec = NULL;
+
+ for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ if(same_net_v4(p->ip, subrec->bcast_ip, subrec->mask_ip))
+ break;
+ }
+
+ if(subrec == NULL)
+ subrec = unicast_subnet;
+
+ return (find_name_on_subnet(subrec, nbname, FIND_SELF_NAME) != NULL);
+}
+
+/****************************************************************************
+ Process udp 138 datagrams
+****************************************************************************/
+
+static void process_dgram(struct packet_struct *p)
+{
+ const char *buf;
+ const char *buf2;
+ int len;
+ struct dgram_packet *dgram = &p->packet.dgram;
+
+ /* If we aren't listening to the destination name then ignore the packet */
+ if (!listening(p,&dgram->dest_name)) {
+ nb_packet_dispatch(packet_server, p);
+ DEBUG(5,("process_dgram: ignoring dgram packet sent to name %s from %s\n",
+ nmb_namestr(&dgram->dest_name), inet_ntoa(p->ip)));
+ return;
+ }
+
+ if (dgram->header.msg_type != 0x10 && dgram->header.msg_type != 0x11 && dgram->header.msg_type != 0x12) {
+ nb_packet_dispatch(packet_server, p);
+ /* Don't process error packets etc yet */
+ DEBUG(5,("process_dgram: ignoring dgram packet sent to name %s from IP %s as it is \
+an error packet of type %x\n", nmb_namestr(&dgram->dest_name), inet_ntoa(p->ip), dgram->header.msg_type));
+ return;
+ }
+
+ /* Ensure we have a large enough packet before looking inside. */
+ if (dgram->datasize < (smb_vwv12 - 2)) {
+ /* That's the offset minus the 4 byte length + 2 bytes of offset. */
+ DEBUG(0,("process_dgram: ignoring too short dgram packet (%u) sent to name %s from IP %s\n",
+ (unsigned int)dgram->datasize,
+ nmb_namestr(&dgram->dest_name),
+ inet_ntoa(p->ip) ));
+ return;
+ }
+
+ buf = &dgram->data[0];
+ buf -= 4; /* XXXX for the pseudo tcp length - someday I need to get rid of this */
+
+ if (CVAL(buf,smb_com) != SMBtrans)
+ return;
+
+ len = SVAL(buf,smb_vwv11);
+ buf2 = smb_base(buf) + SVAL(buf,smb_vwv12);
+
+ if (len <= 0 || len > dgram->datasize) {
+ DEBUG(0,("process_dgram: ignoring malformed1 (datasize = %d, len = %d) datagram \
+packet sent to name %s from IP %s\n",
+ dgram->datasize,
+ len,
+ nmb_namestr(&dgram->dest_name),
+ inet_ntoa(p->ip) ));
+ return;
+ }
+
+ if (buf2 < dgram->data || (buf2 >= dgram->data + dgram->datasize)) {
+ DEBUG(0,("process_dgram: ignoring malformed2 (datasize = %d, len=%d, off=%d) datagram \
+packet sent to name %s from IP %s\n",
+ dgram->datasize,
+ len,
+ (int)PTR_DIFF(buf2, dgram->data),
+ nmb_namestr(&dgram->dest_name),
+ inet_ntoa(p->ip) ));
+ return;
+ }
+
+ if ((buf2 + len < dgram->data) || (buf2 + len > dgram->data + dgram->datasize)) {
+ DEBUG(0,("process_dgram: ignoring malformed3 (datasize = %d, len=%d, off=%d) datagram \
+packet sent to name %s from IP %s\n",
+ dgram->datasize,
+ len,
+ (int)PTR_DIFF(buf2, dgram->data),
+ nmb_namestr(&dgram->dest_name),
+ inet_ntoa(p->ip) ));
+ return;
+ }
+
+ DEBUG(4,("process_dgram: datagram from %s to %s IP %s for %s of type %d len=%d\n",
+ nmb_namestr(&dgram->source_name),nmb_namestr(&dgram->dest_name),
+ inet_ntoa(p->ip), smb_buf_const(buf),CVAL(buf2,0),len));
+
+ /* Datagram packet received for the browser mailslot */
+ if (strequal(smb_buf_const(buf),BROWSE_MAILSLOT)) {
+ process_browse_packet(p,buf2,len);
+ return;
+ }
+
+ /* Datagram packet received for the LAN Manager mailslot */
+ if (strequal(smb_buf_const(buf),LANMAN_MAILSLOT)) {
+ process_lanman_packet(p,buf2,len);
+ return;
+ }
+
+ /* Datagram packet received for the domain logon mailslot */
+ if (strequal(smb_buf_const(buf),NET_LOGON_MAILSLOT)) {
+ process_logon_packet(p,buf2,len,NET_LOGON_MAILSLOT);
+ return;
+ }
+
+ /* Datagram packet received for the NT domain logon mailslot */
+ if (strequal(smb_buf_const(buf),NT_LOGON_MAILSLOT)) {
+ process_logon_packet(p,buf2,len,NT_LOGON_MAILSLOT);
+ return;
+ }
+
+ nb_packet_dispatch(packet_server, p);
+}
+
+/****************************************************************************
+ Validate a response nmb packet.
+****************************************************************************/
+
+static bool validate_nmb_response_packet( struct nmb_packet *nmb )
+{
+ bool ignore = False;
+
+ switch (nmb->header.opcode) {
+ case NMB_NAME_REG_OPCODE:
+ case NMB_NAME_REFRESH_OPCODE_8: /* ambiguity in rfc1002 about which is correct. */
+ case NMB_NAME_REFRESH_OPCODE_9: /* WinNT uses 8 by default. */
+ if (nmb->header.ancount == 0) {
+ DEBUG(0,("validate_nmb_response_packet: Bad REG/REFRESH Packet. "));
+ ignore = True;
+ }
+ break;
+
+ case NMB_NAME_QUERY_OPCODE:
+ if ((nmb->header.ancount != 0) && (nmb->header.ancount != 1)) {
+ DEBUG(0,("validate_nmb_response_packet: Bad QUERY Packet. "));
+ ignore = True;
+ }
+ break;
+
+ case NMB_NAME_RELEASE_OPCODE:
+ if (nmb->header.ancount == 0) {
+ DEBUG(0,("validate_nmb_response_packet: Bad RELEASE Packet. "));
+ ignore = True;
+ }
+ break;
+
+ case NMB_WACK_OPCODE:
+ /* Check WACK response here. */
+ if (nmb->header.ancount != 1) {
+ DEBUG(0,("validate_nmb_response_packet: Bad WACK Packet. "));
+ ignore = True;
+ }
+ break;
+ default:
+ DEBUG(0,("validate_nmb_response_packet: Ignoring packet with unknown opcode %d.\n",
+ nmb->header.opcode));
+ return True;
+ }
+
+ if(ignore)
+ DEBUG(0,("Ignoring response packet with opcode %d.\n", nmb->header.opcode));
+
+ return ignore;
+}
+
+/****************************************************************************
+ Validate a request nmb packet.
+****************************************************************************/
+
+static bool validate_nmb_packet( struct nmb_packet *nmb )
+{
+ bool ignore = False;
+
+ switch (nmb->header.opcode) {
+ case NMB_NAME_REG_OPCODE:
+ case NMB_NAME_REFRESH_OPCODE_8: /* ambiguity in rfc1002 about which is correct. */
+ case NMB_NAME_REFRESH_OPCODE_9: /* WinNT uses 8 by default. */
+ case NMB_NAME_MULTIHOMED_REG_OPCODE:
+ if (nmb->header.qdcount==0 || nmb->header.arcount==0) {
+ DEBUG(0,("validate_nmb_packet: Bad REG/REFRESH Packet. "));
+ ignore = True;
+ }
+ break;
+
+ case NMB_NAME_QUERY_OPCODE:
+ if ((nmb->header.qdcount == 0) || ((nmb->question.question_type != QUESTION_TYPE_NB_QUERY) &&
+ (nmb->question.question_type != QUESTION_TYPE_NB_STATUS))) {
+ DEBUG(0,("validate_nmb_packet: Bad QUERY Packet. "));
+ ignore = True;
+ }
+ break;
+
+ case NMB_NAME_RELEASE_OPCODE:
+ if (nmb->header.qdcount==0 || nmb->header.arcount==0) {
+ DEBUG(0,("validate_nmb_packet: Bad RELEASE Packet. "));
+ ignore = True;
+ }
+ break;
+ default:
+ DEBUG(0,("validate_nmb_packet: Ignoring packet with unknown opcode %d.\n",
+ nmb->header.opcode));
+ return True;
+ }
+
+ if(ignore)
+ DEBUG(0,("validate_nmb_packet: Ignoring request packet with opcode %d.\n", nmb->header.opcode));
+
+ return ignore;
+}
+
+/****************************************************************************
+ Find a subnet (and potentially a response record) for a packet.
+****************************************************************************/
+
+static struct subnet_record *find_subnet_for_nmb_packet( struct packet_struct *p,
+ struct response_record **pprrec)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct response_record *rrec = NULL;
+ struct subnet_record *subrec = NULL;
+
+ if(pprrec != NULL)
+ *pprrec = NULL;
+
+ if(nmb->header.response) {
+ /* It's a response packet. Find a record for it or it's an error. */
+
+ rrec = find_response_record( &subrec, nmb->header.name_trn_id);
+ if(rrec == NULL) {
+ DEBUG(3, ("find_subnet_for_nmb_packet: response "
+ "record not found for response id %d\n",
+ nmb->header.name_trn_id));
+ nb_packet_dispatch(packet_server, p);
+ return NULL;
+ }
+
+ if(subrec == NULL) {
+ DEBUG(0, ("find_subnet_for_nmb_packet: subnet record "
+ "not found for response id %d\n",
+ nmb->header.name_trn_id));
+ return NULL;
+ }
+
+ if(pprrec != NULL)
+ *pprrec = rrec;
+ return subrec;
+ }
+
+ /* Try and see what subnet this packet belongs to. */
+
+ /* WINS server ? */
+ if(packet_is_for_wins_server(p))
+ return wins_server_subnet;
+
+ /* If it wasn't a broadcast packet then send to the UNICAST subnet. */
+ if(nmb->header.nm_flags.bcast == False)
+ return unicast_subnet;
+
+ /* Go through all the broadcast subnets and see if the mask matches. */
+ for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ if(same_net_v4(p->ip, subrec->bcast_ip, subrec->mask_ip))
+ return subrec;
+ }
+
+ /* If none match it must have been a directed broadcast - assign the remote_broadcast_subnet. */
+ return remote_broadcast_subnet;
+}
+
+/****************************************************************************
+ Process a nmb request packet - validate the packet and route it.
+****************************************************************************/
+
+static void process_nmb_request(struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct subnet_record *subrec = NULL;
+
+ debug_nmb_packet(p);
+
+ /* Ensure we have a good packet. */
+ if(validate_nmb_packet(nmb))
+ return;
+
+ /* Allocate a subnet to this packet - if we cannot - fail. */
+ if((subrec = find_subnet_for_nmb_packet(p, NULL))==NULL)
+ return;
+
+ switch (nmb->header.opcode) {
+ case NMB_NAME_REG_OPCODE:
+ if(subrec == wins_server_subnet)
+ wins_process_name_registration_request(subrec, p);
+ else
+ process_name_registration_request(subrec, p);
+ break;
+
+ case NMB_NAME_REFRESH_OPCODE_8: /* ambiguity in rfc1002 about which is correct. */
+ case NMB_NAME_REFRESH_OPCODE_9:
+ if(subrec == wins_server_subnet)
+ wins_process_name_refresh_request(subrec, p);
+ else
+ process_name_refresh_request(subrec, p);
+ break;
+
+ case NMB_NAME_MULTIHOMED_REG_OPCODE:
+ if(subrec == wins_server_subnet) {
+ wins_process_multihomed_name_registration_request(subrec, p);
+ } else {
+ DEBUG(0,("process_nmb_request: Multihomed registration request must be \
+directed at a WINS server.\n"));
+ }
+ break;
+
+ case NMB_NAME_QUERY_OPCODE:
+ switch (nmb->question.question_type) {
+ case QUESTION_TYPE_NB_QUERY:
+ if(subrec == wins_server_subnet)
+ wins_process_name_query_request(subrec, p);
+ else
+ process_name_query_request(subrec, p);
+ break;
+ case QUESTION_TYPE_NB_STATUS:
+ if(subrec == wins_server_subnet) {
+ DEBUG(0,("process_nmb_request: NB_STATUS request directed at WINS server is \
+not allowed.\n"));
+ break;
+ } else {
+ process_node_status_request(subrec, p);
+ }
+ break;
+ }
+ break;
+
+ case NMB_NAME_RELEASE_OPCODE:
+ if(subrec == wins_server_subnet)
+ wins_process_name_release_request(subrec, p);
+ else
+ process_name_release_request(subrec, p);
+ break;
+ }
+}
+
+/****************************************************************************
+ Process a nmb response packet - validate the packet and route it.
+ to either the WINS server or a normal response.
+****************************************************************************/
+
+static void process_nmb_response(struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct subnet_record *subrec = NULL;
+ struct response_record *rrec = NULL;
+
+ debug_nmb_packet(p);
+
+ if(validate_nmb_response_packet(nmb))
+ return;
+
+ if((subrec = find_subnet_for_nmb_packet(p, &rrec))==NULL)
+ return;
+
+ if(rrec == NULL) {
+ DEBUG(0, ("process_nmb_response: response packet received but "
+ "no response record found for id = %d. Ignoring "
+ "packet.\n", nmb->header.name_trn_id));
+ return;
+ }
+
+ /* Increment the number of responses received for this record. */
+ rrec->num_msgs++;
+ /* Ensure we don't re-send the request. */
+ rrec->repeat_count = 0;
+
+ /* Call the response received function for this packet. */
+ (*rrec->resp_fn)(subrec, rrec, p);
+}
+
+/*******************************************************************
+ Run elements off the packet queue till its empty
+******************************************************************/
+
+void run_packet_queue(void)
+{
+ struct packet_struct *p;
+
+ while ((p = packet_queue)) {
+ DLIST_REMOVE(packet_queue, p);
+
+ switch (p->packet_type) {
+ case NMB_PACKET:
+ if(p->packet.nmb.header.response)
+ process_nmb_response(p);
+ else
+ process_nmb_request(p);
+ break;
+
+ case DGRAM_PACKET:
+ process_dgram(p);
+ break;
+ }
+ free_packet(p);
+ }
+}
+
+/*******************************************************************
+ Retransmit or timeout elements from all the outgoing subnet response
+ record queues. NOTE that this code must also check the WINS server
+ subnet for response records to timeout as the WINS server code
+ can send requests to check if a client still owns a name.
+ (Patch from Andrey Alekseyev <fetch@muffin.arcadia.spb.ru>).
+******************************************************************/
+
+void retransmit_or_expire_response_records(time_t t)
+{
+ struct subnet_record *subrec;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = get_next_subnet_maybe_unicast_or_wins_server(subrec)) {
+ struct response_record *rrec, *nextrrec;
+
+ restart:
+
+ for (rrec = subrec->responselist; rrec; rrec = nextrrec) {
+ nextrrec = rrec->next;
+
+ if (rrec->repeat_time <= t) {
+ if (rrec->repeat_count > 0) {
+ /* Resend while we have a non-zero repeat_count. */
+ if(!send_packet(rrec->packet)) {
+ DEBUG(0,("retransmit_or_expire_response_records: Failed to resend packet id %hu \
+to IP %s on subnet %s\n", rrec->response_id, inet_ntoa(rrec->packet->ip), subrec->subnet_name));
+ }
+ rrec->repeat_time = t + rrec->repeat_interval;
+ rrec->repeat_count--;
+ } else {
+ DEBUG(4,("retransmit_or_expire_response_records: timeout for packet id %hu to IP %s \
+on subnet %s\n", rrec->response_id, inet_ntoa(rrec->packet->ip), subrec->subnet_name));
+
+ /*
+ * Check the flag in this record to prevent recursion if we end
+ * up in this function again via the timeout function call.
+ */
+
+ if(!rrec->in_expiration_processing) {
+
+ /*
+ * Set the recursion protection flag in this record.
+ */
+
+ rrec->in_expiration_processing = True;
+
+ /* Call the timeout function. This will deal with removing the
+ timed out packet. */
+ if(rrec->timeout_fn) {
+ (*rrec->timeout_fn)(subrec, rrec);
+ } else {
+ /* We must remove the record ourself if there is
+ no timeout function. */
+ remove_response_record(subrec, rrec);
+ }
+ /* We have changed subrec->responselist,
+ * restart from the beginning of this list. */
+ goto restart;
+ } /* !rrec->in_expitation_processing */
+ } /* rrec->repeat_count > 0 */
+ } /* rrec->repeat_time <= t */
+ } /* end for rrec */
+ } /* end for subnet */
+}
+
+/****************************************************************************
+ Create an fd_set containing all the sockets in the subnet structures,
+ plus the broadcast sockets.
+***************************************************************************/
+
+struct socket_attributes {
+ enum packet_type type;
+ bool broadcast;
+ int fd;
+ bool triggered;
+};
+
+static bool create_listen_array(struct socket_attributes **pattrs,
+ int *pnum_sockets)
+{
+ struct subnet_record *subrec = NULL;
+ int count = 0;
+ int num = 0;
+ struct socket_attributes *attrs;
+
+ /* The ClientNMB and ClientDGRAM sockets */
+ count = 2;
+
+ /* Check that we can add all the fd's we need. */
+ for (subrec = FIRST_SUBNET;
+ subrec != NULL;
+ subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ if (subrec->nmb_sock != -1) {
+ count += 1;
+ }
+ if (subrec->dgram_sock != -1) {
+ count += 1;
+ }
+ if (subrec->nmb_bcast != -1) {
+ count += 1;
+ }
+ if (subrec->dgram_bcast != -1) {
+ count += 1;
+ }
+ }
+
+ attrs = talloc_zero_array(NULL, struct socket_attributes, count);
+ if (attrs == NULL) {
+ DEBUG(1, ("talloc fail for attrs. "
+ "size %d\n", count));
+ return true;
+ }
+
+ num = 0;
+
+ attrs[num].fd = ClientNMB;
+ attrs[num].type = NMB_PACKET;
+ attrs[num].broadcast = false;
+ num += 1;
+
+ attrs[num].fd = ClientDGRAM;
+ attrs[num].type = DGRAM_PACKET;
+ attrs[num].broadcast = false;
+ num += 1;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+
+ if (subrec->nmb_sock != -1) {
+ attrs[num].fd = subrec->nmb_sock;
+ attrs[num].type = NMB_PACKET;
+ attrs[num].broadcast = false;
+ num += 1;
+ }
+
+ if (subrec->nmb_bcast != -1) {
+ attrs[num].fd = subrec->nmb_bcast;
+ attrs[num].type = NMB_PACKET;
+ attrs[num].broadcast = true;
+ num += 1;
+ }
+
+ if (subrec->dgram_sock != -1) {
+ attrs[num].fd = subrec->dgram_sock;
+ attrs[num].type = DGRAM_PACKET;
+ attrs[num].broadcast = false;
+ num += 1;
+ }
+
+ if (subrec->dgram_bcast != -1) {
+ attrs[num].fd = subrec->dgram_bcast;
+ attrs[num].type = DGRAM_PACKET;
+ attrs[num].broadcast = true;
+ num += 1;
+ }
+ }
+
+ TALLOC_FREE(*pattrs);
+ *pattrs = attrs;
+
+ *pnum_sockets = count;
+
+ return False;
+}
+
+/****************************************************************************
+ List of packets we're processing this select.
+***************************************************************************/
+
+struct processed_packet {
+ struct processed_packet *next;
+ struct processed_packet *prev;
+ enum packet_type packet_type;
+ struct in_addr ip;
+ int packet_id;
+};
+
+/****************************************************************************
+ Have we seen this before ?
+***************************************************************************/
+
+static bool is_processed_packet(struct processed_packet *processed_packet_list,
+ struct packet_struct *packet)
+{
+ struct processed_packet *p = NULL;
+
+ for (p = processed_packet_list; p; p = p->next) {
+ if (ip_equal_v4(p->ip, packet->ip) && p->packet_type == packet->packet_type) {
+ if ((p->packet_type == NMB_PACKET) &&
+ (p->packet_id ==
+ packet->packet.nmb.header.name_trn_id)) {
+ return true;
+ } else if ((p->packet_type == DGRAM_PACKET) &&
+ (p->packet_id ==
+ packet->packet.dgram.header.dgm_id)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/****************************************************************************
+ Keep a list of what we've seen before.
+***************************************************************************/
+
+static bool store_processed_packet(struct processed_packet **pp_processed_packet_list,
+ struct packet_struct *packet)
+{
+ struct processed_packet *p = SMB_MALLOC_P(struct processed_packet);
+ if (!p) {
+ return false;
+ }
+ p->packet_type = packet->packet_type;
+ p->ip = packet->ip;
+ if (packet->packet_type == NMB_PACKET) {
+ p->packet_id = packet->packet.nmb.header.name_trn_id;
+ } else if (packet->packet_type == DGRAM_PACKET) {
+ p->packet_id = packet->packet.dgram.header.dgm_id;
+ } else {
+ SAFE_FREE(p);
+ return false;
+ }
+
+ DLIST_ADD(*pp_processed_packet_list, p);
+ return true;
+}
+
+/****************************************************************************
+ Throw away what we've seen before.
+***************************************************************************/
+
+static void free_processed_packet_list(struct processed_packet **pp_processed_packet_list)
+{
+ struct processed_packet *p = NULL, *next = NULL;
+
+ for (p = *pp_processed_packet_list; p; p = next) {
+ next = p->next;
+ DLIST_REMOVE(*pp_processed_packet_list, p);
+ SAFE_FREE(p);
+ }
+}
+
+/****************************************************************************
+ Timeout callback - just notice we timed out.
+***************************************************************************/
+
+static void nmbd_timeout_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ bool *got_timeout = private_data;
+ *got_timeout = true;
+}
+
+/****************************************************************************
+ fd callback - remember the fd that triggered.
+***************************************************************************/
+
+static void nmbd_fd_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct socket_attributes *attr = private_data;
+ attr->triggered = true;
+}
+
+/****************************************************************************
+ Read from a socket.
+****************************************************************************/
+
+static ssize_t read_udp_v4_socket(
+ int fd,
+ char *buf,
+ size_t len,
+ struct sockaddr_storage *psa)
+{
+ ssize_t ret;
+ socklen_t socklen = sizeof(*psa);
+ struct sockaddr_in *si = (struct sockaddr_in *)psa;
+
+ memset((char *)psa,'\0',socklen);
+
+ ret = (ssize_t)sys_recvfrom(fd,buf,len,0,
+ (struct sockaddr *)psa,&socklen);
+ if (ret <= 0) {
+ /* Don't print a low debug error for a non-blocking socket. */
+ if (errno == EAGAIN) {
+ DEBUG(10,("read_udp_v4_socket: returned EAGAIN\n"));
+ } else {
+ DEBUG(2,("read_udp_v4_socket: failed. errno=%s\n",
+ strerror(errno)));
+ }
+ return 0;
+ }
+
+ if (psa->ss_family != AF_INET) {
+ DEBUG(2,("read_udp_v4_socket: invalid address family %d "
+ "(not IPv4)\n", (int)psa->ss_family));
+ return 0;
+ }
+
+ DEBUG(10,("read_udp_v4_socket: ip %s port %d read: %lu\n",
+ inet_ntoa(si->sin_addr),
+ si->sin_port,
+ (unsigned long)ret));
+
+ return ret;
+}
+
+/*******************************************************************
+ Read a packet from a socket and parse it, returning a packet ready
+ to be used or put on the queue. This assumes a UDP socket.
+******************************************************************/
+
+static struct packet_struct *read_packet(int fd,enum packet_type packet_type)
+{
+ struct packet_struct *packet;
+ struct sockaddr_storage sa;
+ struct sockaddr_in *si = (struct sockaddr_in *)&sa;
+ char buf[MAX_DGRAM_SIZE];
+ int length;
+
+ length = read_udp_v4_socket(fd,buf,sizeof(buf),&sa);
+ if (length < MIN_DGRAM_SIZE || sa.ss_family != AF_INET) {
+ return NULL;
+ }
+
+ packet = parse_packet(buf,
+ length,
+ packet_type,
+ si->sin_addr,
+ ntohs(si->sin_port));
+ if (!packet)
+ return NULL;
+
+ packet->recv_fd = fd;
+ packet->send_fd = -1;
+
+ DEBUG(5,("Received a packet of len %d from (%s) port %d\n",
+ length, inet_ntoa(packet->ip), packet->port ) );
+
+ return(packet);
+}
+
+/****************************************************************************
+ Listens for NMB or DGRAM packets, and queues them.
+ return True if the socket is dead
+***************************************************************************/
+
+bool listen_for_packets(struct messaging_context *msg, bool run_election)
+{
+ static struct socket_attributes *attrs = NULL;
+ static int listen_number = 0;
+ int num_sockets;
+ int i;
+ int loop_rtn;
+ int timeout_secs;
+
+#ifndef SYNC_DNS
+ int dns_fd;
+ int dns_pollidx = -1;
+#endif
+ struct processed_packet *processed_packet_list = NULL;
+ struct tevent_timer *te = NULL;
+ bool got_timeout = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if ((attrs == NULL) || rescan_listen_set) {
+ if (create_listen_array(&attrs, &listen_number)) {
+ DEBUG(0,("listen_for_packets: Fatal error. unable to create listen set. Exiting.\n"));
+ TALLOC_FREE(frame);
+ return True;
+ }
+ rescan_listen_set = False;
+ }
+
+ num_sockets = listen_number;
+
+#ifndef SYNC_DNS
+ dns_fd = asyncdns_fd();
+ if (dns_fd != -1) {
+ attrs = talloc_realloc(NULL,
+ attrs,
+ struct socket_attributes,
+ num_sockets + 1);
+ if (attrs == NULL) {
+ TALLOC_FREE(frame);
+ return true;
+ }
+ dns_pollidx = num_sockets;
+ attrs[dns_pollidx].fd = dns_fd;
+ /*
+ * dummy values, we only need
+ * fd and triggered.
+ */
+ attrs[dns_pollidx].type = NMB_PACKET;
+ attrs[dns_pollidx].broadcast = false;
+ num_sockets += 1;
+ }
+#endif
+
+ for (i=0; i<num_sockets; i++) {
+ struct tevent_fd *tfd = tevent_add_fd(nmbd_event_context(),
+ frame,
+ attrs[i].fd,
+ TEVENT_FD_READ,
+ nmbd_fd_handler,
+ &attrs[i]);
+ if (tfd == NULL) {
+ TALLOC_FREE(frame);
+ return true;
+ }
+ attrs[i].triggered = false;
+ }
+
+ /*
+ * During elections and when expecting a netbios response packet we
+ * need to send election packets at tighter intervals.
+ * Ideally it needs to be the interval (in ms) between time now and
+ * the time we are expecting the next netbios packet.
+ */
+
+ if (run_election||num_response_packets) {
+ timeout_secs = 1;
+ } else {
+ timeout_secs = NMBD_SELECT_LOOP;
+ }
+
+ te = tevent_add_timer(nmbd_event_context(),
+ frame,
+ tevent_timeval_current_ofs(timeout_secs, 0),
+ nmbd_timeout_handler,
+ &got_timeout);
+ if (te == NULL) {
+ TALLOC_FREE(frame);
+ return true;
+ }
+
+ loop_rtn = tevent_loop_once(nmbd_event_context());
+
+ if (loop_rtn == -1) {
+ TALLOC_FREE(frame);
+ return true;
+ }
+
+ if (got_timeout) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+#ifndef SYNC_DNS
+ if ((dns_fd != -1) && (dns_pollidx != -1) &&
+ attrs[dns_pollidx].triggered){
+ run_dns_queue(msg);
+ TALLOC_FREE(frame);
+ return false;
+ }
+#endif
+
+ for(i = 0; i < listen_number; i++) {
+ enum packet_type packet_type;
+ struct packet_struct *packet;
+ const char *packet_name;
+ int client_fd;
+ int client_port;
+
+ if (!attrs[i].triggered) {
+ continue;
+ }
+
+ if (attrs[i].type == NMB_PACKET) {
+ /* Port 137 */
+ packet_type = NMB_PACKET;
+ packet_name = "nmb";
+ client_fd = ClientNMB;
+ client_port = global_nmb_port;
+ } else {
+ /* Port 138 */
+ packet_type = DGRAM_PACKET;
+ packet_name = "dgram";
+ client_fd = ClientDGRAM;
+ client_port = DGRAM_PORT;
+ }
+
+ packet = read_packet(attrs[i].fd, packet_type);
+ if (!packet) {
+ continue;
+ }
+
+ /*
+ * If we got a packet on the broadcast socket and interfaces
+ * only is set then check it came from one of our local nets.
+ */
+ if (lp_bind_interfaces_only() &&
+ (attrs[i].fd == client_fd) &&
+ (!is_local_net_v4(packet->ip))) {
+ DEBUG(7,("discarding %s packet sent to broadcast socket from %s:%d\n",
+ packet_name, inet_ntoa(packet->ip), packet->port));
+ free_packet(packet);
+ continue;
+ }
+
+ if (!IS_DC) {
+ if ((is_loopback_ip_v4(packet->ip) || ismyip_v4(packet->ip)) &&
+ packet->port == client_port)
+ {
+ if (client_port == DGRAM_PORT) {
+ DEBUG(7,("discarding own dgram packet from %s:%d\n",
+ inet_ntoa(packet->ip),packet->port));
+ free_packet(packet);
+ continue;
+ }
+
+ if (packet->packet.nmb.header.nm_flags.bcast) {
+ DEBUG(7,("discarding own nmb bcast packet from %s:%d\n",
+ inet_ntoa(packet->ip),packet->port));
+ free_packet(packet);
+ continue;
+ }
+ }
+ }
+
+ if (is_processed_packet(processed_packet_list, packet)) {
+ DEBUG(7,("discarding duplicate packet from %s:%d\n",
+ inet_ntoa(packet->ip),packet->port));
+ free_packet(packet);
+ continue;
+ }
+
+ store_processed_packet(&processed_packet_list, packet);
+
+ if (attrs[i].broadcast) {
+ /* this is a broadcast socket */
+ packet->send_fd = attrs[i-1].fd;
+ } else {
+ /* this is already a unicast socket */
+ packet->send_fd = attrs[i].fd;
+ }
+
+ queue_packet(packet);
+ }
+
+ free_processed_packet_list(&processed_packet_list);
+ TALLOC_FREE(frame);
+ return False;
+}
+
+/****************************************************************************
+ Construct and send a netbios DGRAM.
+**************************************************************************/
+
+bool send_mailslot(bool unique, const char *mailslot,char *buf, size_t len,
+ const char *srcname, int src_type,
+ const char *dstname, int dest_type,
+ struct in_addr dest_ip,struct in_addr src_ip,
+ int dest_port)
+{
+ bool loopback_this_packet = False;
+ struct packet_struct p;
+ struct dgram_packet *dgram = &p.packet.dgram;
+ char *ptr,*p2;
+ char tmp[4];
+
+ memset((char *)&p,'\0',sizeof(p));
+
+ if(ismyip_v4(dest_ip) && (dest_port == DGRAM_PORT)) /* Only if to DGRAM_PORT */
+ loopback_this_packet = True;
+
+ /* generate_name_trn_id(); */ /* Not used, so gone, RJS */
+
+ /* DIRECT GROUP or UNIQUE datagram. */
+ dgram->header.msg_type = unique ? 0x10 : 0x11;
+ dgram->header.flags.node_type = M_NODE;
+ dgram->header.flags.first = True;
+ dgram->header.flags.more = False;
+ dgram->header.dgm_id = generate_name_trn_id();
+ dgram->header.source_ip = src_ip;
+ dgram->header.source_port = DGRAM_PORT;
+ dgram->header.dgm_length = 0; /* Let build_dgram() handle this. */
+ dgram->header.packet_offset = 0;
+
+ make_nmb_name(&dgram->source_name,srcname,src_type);
+ make_nmb_name(&dgram->dest_name,dstname,dest_type);
+
+ ptr = &dgram->data[0];
+
+ /* Setup the smb part. */
+ ptr -= 4; /* XXX Ugliness because of handling of tcp SMB length. */
+ memcpy(tmp,ptr,4);
+
+ if (smb_size + 17*2 + strlen(mailslot) + 1 + len > MAX_DGRAM_SIZE) {
+ DEBUG(0, ("send_mailslot: Cannot write beyond end of packet\n"));
+ return false;
+ }
+
+ cli_set_message(ptr,17,strlen(mailslot) + 1 + len,True);
+ memcpy(ptr,tmp,4);
+
+ SCVAL(ptr,smb_com,SMBtrans);
+ SSVAL(ptr,smb_vwv1,len);
+ SSVAL(ptr,smb_vwv11,len);
+ SSVAL(ptr,smb_vwv12,70 + strlen(mailslot));
+ SSVAL(ptr,smb_vwv13,3);
+ SSVAL(ptr,smb_vwv14,1);
+ SSVAL(ptr,smb_vwv15,1);
+ SSVAL(ptr,smb_vwv16,2);
+ p2 = smb_buf(ptr);
+ strlcpy_base(p2, mailslot, dgram->data, sizeof(dgram->data));
+ p2 = skip_string(ptr,MAX_DGRAM_SIZE,p2);
+
+ if (((p2+len) > dgram->data+sizeof(dgram->data)) || ((p2+len) < p2)) {
+ DEBUG(0, ("send_mailslot: Cannot write beyond end of packet\n"));
+ return False;
+ } else {
+ if (len) {
+ memcpy(p2,buf,len);
+ }
+ p2 += len;
+ }
+
+ dgram->datasize = PTR_DIFF(p2,ptr+4); /* +4 for tcp length. */
+
+ p.ip = dest_ip;
+ p.port = dest_port;
+ p.recv_fd = -1;
+ p.send_fd = find_subnet_mailslot_fd_for_address( src_ip );
+ p.timestamp = time(NULL);
+ p.packet_type = DGRAM_PACKET;
+
+ DEBUG(4,("send_mailslot: Sending to mailslot %s from %s IP %s ", mailslot,
+ nmb_namestr(&dgram->source_name), inet_ntoa(src_ip)));
+ DEBUG(4,("to %s IP %s\n", nmb_namestr(&dgram->dest_name), inet_ntoa(dest_ip)));
+
+ debug_browse_data(buf, len);
+
+ if(loopback_this_packet) {
+ struct packet_struct *lo_packet = NULL;
+ DEBUG(5,("send_mailslot: sending packet to ourselves.\n"));
+ if((lo_packet = copy_packet(&p)) == NULL)
+ return False;
+ queue_packet(lo_packet);
+ return True;
+ } else {
+ return(send_packet(&p));
+ }
+}
diff --git a/source3/nmbd/nmbd_processlogon.c b/source3/nmbd/nmbd_processlogon.c
new file mode 100644
index 0000000..3e86289
--- /dev/null
+++ b/source3/nmbd/nmbd_processlogon.c
@@ -0,0 +1,570 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-2003
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Revision History:
+
+*/
+
+#include "includes.h"
+#include "../libcli/netlogon/netlogon.h"
+#include "../libcli/cldap/cldap.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "secrets.h"
+#include "nmbd/nmbd.h"
+
+struct sam_database_info {
+ uint32_t index;
+ uint32_t serial_lo, serial_hi;
+ uint32_t date_lo, date_hi;
+};
+
+/**
+ * check whether the client belongs to the hosts
+ * for which initial logon should be delayed...
+ */
+static bool delay_logon(const char *peer_name, const char *peer_addr)
+{
+ const char **delay_list = lp_init_logon_delayed_hosts();
+ const char *peer[2];
+
+ if (delay_list == NULL) {
+ return False;
+ }
+
+ peer[0] = peer_name;
+ peer[1] = peer_addr;
+
+ return list_match(delay_list, (const char *)peer, client_match);
+}
+
+static void delayed_init_logon_handler(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct packet_struct *p = (struct packet_struct *)private_data;
+
+ DEBUG(10, ("delayed_init_logon_handler (%lx): re-queuing packet.\n",
+ (unsigned long)te));
+
+ queue_packet(p);
+
+ TALLOC_FREE(te);
+}
+
+struct nmbd_proxy_logon_context {
+ struct cldap_socket *cldap_sock;
+};
+
+static struct nmbd_proxy_logon_context *global_nmbd_proxy_logon;
+
+bool initialize_nmbd_proxy_logon(void)
+{
+ const char *cldap_server = lp_parm_const_string(-1, "nmbd_proxy_logon",
+ "cldap_server", NULL);
+ struct nmbd_proxy_logon_context *ctx;
+ NTSTATUS status;
+ struct in_addr addr;
+ char addrstr[INET_ADDRSTRLEN];
+ const char *server_str;
+ int ret;
+ struct tsocket_address *server_addr;
+
+ if (!cldap_server) {
+ return true;
+ }
+
+ addr = interpret_addr2(cldap_server);
+ server_str = inet_ntop(AF_INET, &addr,
+ addrstr, sizeof(addrstr));
+ if (!server_str || strcmp("0.0.0.0", server_str) == 0) {
+ DEBUG(0,("Failed to resolve[%s] for nmbd_proxy_logon\n",
+ cldap_server));
+ return false;
+ }
+
+ ctx = talloc_zero(nmbd_event_context(),
+ struct nmbd_proxy_logon_context);
+ if (!ctx) {
+ return false;
+ }
+
+ ret = tsocket_address_inet_from_strings(ctx, "ipv4",
+ server_str, LDAP_PORT,
+ &server_addr);
+ if (ret != 0) {
+ TALLOC_FREE(ctx);
+ status = map_nt_error_from_unix(errno);
+ DEBUG(0,("Failed to create cldap tsocket_address for %s - %s\n",
+ server_str, nt_errstr(status)));
+ return false;
+ }
+
+ /* we create a connected udp socket */
+ status = cldap_socket_init(ctx, NULL, server_addr, &ctx->cldap_sock);
+ TALLOC_FREE(server_addr);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(ctx);
+ DEBUG(0,("failed to create cldap socket for %s: %s\n",
+ server_str, nt_errstr(status)));
+ return false;
+ }
+
+ global_nmbd_proxy_logon = ctx;
+ return true;
+}
+
+struct nmbd_proxy_logon_state {
+ struct in_addr local_ip;
+ struct packet_struct *p;
+ const char *remote_name;
+ uint8_t remote_name_type;
+ const char *remote_mailslot;
+ struct nbt_netlogon_packet req;
+ struct nbt_netlogon_response resp;
+ struct cldap_netlogon io;
+};
+
+static int nmbd_proxy_logon_state_destructor(struct nmbd_proxy_logon_state *s)
+{
+ s->p->locked = false;
+ free_packet(s->p);
+ return 0;
+}
+
+static void nmbd_proxy_logon_done(struct tevent_req *subreq);
+
+static void nmbd_proxy_logon(struct nmbd_proxy_logon_context *ctx,
+ struct in_addr local_ip,
+ struct packet_struct *p,
+ const uint8_t *buf,
+ uint32_t len)
+{
+ struct nmbd_proxy_logon_state *state;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = data_blob_const(buf, len);
+ const char *computer_name = NULL;
+ const char *mailslot_name = NULL;
+ const char *user_name = NULL;
+ const char *domain_sid = NULL;
+ uint32_t acct_control = 0;
+ uint32_t nt_version = 0;
+ struct tevent_req *subreq;
+ fstring source_name;
+ struct dgram_packet *dgram = &p->packet.dgram;
+
+ state = talloc_zero(ctx, struct nmbd_proxy_logon_state);
+ if (!state) {
+ DEBUG(0,("failed to allocate nmbd_proxy_logon_state\n"));
+ return;
+ }
+
+ pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name);
+ state->remote_name = talloc_strdup(state, source_name);
+ state->remote_name_type = dgram->source_name.name_type,
+ state->local_ip = local_ip;
+ state->p = p;
+
+ ndr_err = ndr_pull_struct_blob(
+ &blob, state, &state->req,
+ (ndr_pull_flags_fn_t)ndr_pull_nbt_netlogon_packet);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("failed parse nbt_netlogon_packet: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(state);
+ return;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10, ("nmbd_proxy_logon:\n"));
+ NDR_PRINT_DEBUG(nbt_netlogon_packet, &state->req);
+ }
+
+ switch (state->req.command) {
+ case LOGON_SAM_LOGON_REQUEST:
+ computer_name = state->req.req.logon.computer_name;
+ user_name = state->req.req.logon.user_name;
+ mailslot_name = state->req.req.logon.mailslot_name;
+ acct_control = state->req.req.logon.acct_control;
+ if (state->req.req.logon.sid_size > 0) {
+ domain_sid = dom_sid_string(state,
+ &state->req.req.logon.sid);
+ if (!domain_sid) {
+ DEBUG(0,("failed to get a string for sid\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ }
+ nt_version = state->req.req.logon.nt_version;
+ break;
+
+ default:
+ /* this can't happen as the caller already checks the command */
+ break;
+ }
+
+ state->remote_mailslot = mailslot_name;
+
+ if (user_name && strlen(user_name) == 0) {
+ user_name = NULL;
+ }
+
+ if (computer_name && strlen(computer_name) == 0) {
+ computer_name = NULL;
+ }
+
+ /*
+ * as the socket is connected,
+ * we don't need to specify the destination
+ */
+ state->io.in.dest_address = NULL;
+ state->io.in.dest_port = 0;
+ state->io.in.realm = NULL;
+ state->io.in.host = computer_name;
+ state->io.in.user = user_name;
+ state->io.in.domain_guid = NULL;
+ state->io.in.domain_sid = domain_sid;
+ state->io.in.acct_control = acct_control;
+ state->io.in.version = nt_version;
+ state->io.in.map_response = false;
+
+ subreq = cldap_netlogon_send(state, nmbd_event_context(),
+ ctx->cldap_sock,
+ &state->io);
+ if (!subreq) {
+ DEBUG(0,("failed to send cldap netlogon call\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ tevent_req_set_callback(subreq, nmbd_proxy_logon_done, state);
+
+ /* we reply async */
+ state->p->locked = true;
+ talloc_set_destructor(state, nmbd_proxy_logon_state_destructor);
+}
+
+static void nmbd_proxy_logon_done(struct tevent_req *subreq)
+{
+ struct nmbd_proxy_logon_state *state =
+ tevent_req_callback_data(subreq,
+ struct nmbd_proxy_logon_state);
+ NTSTATUS status;
+ DATA_BLOB response = data_blob_null;
+
+ status = cldap_netlogon_recv(subreq, state, &state->io);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("failed to recv cldap netlogon call: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(state);
+ return;
+ }
+
+ status = push_netlogon_samlogon_response(&response, state,
+ &state->io.out.netlogon);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("failed to push netlogon_samlogon_response: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(state);
+ return;
+ }
+
+ send_mailslot(true, state->remote_mailslot,
+ (char *)response.data, response.length,
+ lp_netbios_name(), 0x0,
+ state->remote_name,
+ state->remote_name_type,
+ state->p->ip,
+ state->local_ip,
+ state->p->port);
+ TALLOC_FREE(state);
+}
+
+/****************************************************************************
+Process a domain logon packet
+**************************************************************************/
+
+void process_logon_packet(struct packet_struct *p, const char *buf,int len,
+ const char *mailslot)
+{
+ fstring source_name;
+ struct dgram_packet *dgram = &p->packet.dgram;
+ struct sockaddr_storage ss;
+ const struct sockaddr_storage *pss;
+ struct in_addr ip;
+
+ DATA_BLOB blob_in, blob_out;
+ enum ndr_err_code ndr_err;
+ struct nbt_netlogon_packet request;
+ struct nbt_netlogon_response response;
+ NTSTATUS status;
+ const char *pdc_name;
+
+ in_addr_to_sockaddr_storage(&ss, p->ip);
+ pss = iface_ip((struct sockaddr *)&ss);
+ if (!pss) {
+ DEBUG(5,("process_logon_packet:can't find outgoing interface "
+ "for packet from IP %s\n",
+ inet_ntoa(p->ip) ));
+ return;
+ }
+ ip = ((const struct sockaddr_in *)pss)->sin_addr;
+
+ if (!IS_DC) {
+ DEBUG(5,("process_logon_packet: Logon packet received from IP %s and domain \
+logons are not enabled.\n", inet_ntoa(p->ip) ));
+ return;
+ }
+
+ pull_ascii_nstring(source_name, sizeof(source_name), dgram->source_name.name);
+
+ pdc_name = talloc_asprintf(talloc_tos(), "\\\\%s", lp_netbios_name());
+ if (!pdc_name) {
+ return;
+ }
+
+ ZERO_STRUCT(request);
+
+ blob_in = data_blob_const(buf, len);
+
+ ndr_err = ndr_pull_struct_blob(&blob_in, talloc_tos(), &request,
+ (ndr_pull_flags_fn_t)ndr_pull_nbt_netlogon_packet);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1,("process_logon_packet: Failed to pull logon packet\n"));
+ return;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(nbt_netlogon_packet, &request);
+ }
+
+ DEBUG(4,("process_logon_packet: Logon from %s: code = 0x%x\n",
+ inet_ntoa(p->ip), request.command));
+
+ switch (request.command) {
+ case LOGON_REQUEST: {
+
+ struct nbt_netlogon_response2 response2;
+
+ DEBUG(5,("process_logon_packet: Domain login request from %s at IP %s user=%s token=%x\n",
+ request.req.logon0.computer_name, inet_ntoa(p->ip),
+ request.req.logon0.user_name,
+ request.req.logon0.lm20_token));
+
+ response2.command = LOGON_RESPONSE2;
+ response2.pdc_name = pdc_name;
+ response2.lm20_token = 0xffff;
+
+ response.response_type = NETLOGON_RESPONSE2;
+ response.data.response2 = response2;
+
+ status = push_nbt_netlogon_response(&blob_out, talloc_tos(), &response);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("process_logon_packet: failed to push packet\n"));
+ return;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(nbt_netlogon_response2, &response.data.response2);
+ }
+
+ send_mailslot(True, request.req.logon0.mailslot_name,
+ (char *)blob_out.data,
+ blob_out.length,
+ lp_netbios_name(), 0x0,
+ source_name,
+ dgram->source_name.name_type,
+ p->ip, ip, p->port);
+ break;
+ }
+
+ case LOGON_PRIMARY_QUERY: {
+
+ struct nbt_netlogon_response_from_pdc get_pdc;
+
+ if (!lp_domain_master()) {
+ /* We're not Primary Domain Controller -- ignore this */
+ return;
+ }
+
+ DEBUG(5,("process_logon_packet: GETDC request from %s at IP %s, "
+ "reporting %s domain %s 0x%x ntversion=%x lm_nt token=%x lm_20 token=%x\n",
+ request.req.pdc.computer_name,
+ inet_ntoa(p->ip),
+ lp_netbios_name(),
+ lp_workgroup(),
+ NETLOGON_RESPONSE_FROM_PDC,
+ request.req.pdc.nt_version,
+ request.req.pdc.lmnt_token,
+ request.req.pdc.lm20_token));
+
+ get_pdc.command = NETLOGON_RESPONSE_FROM_PDC;
+ get_pdc.pdc_name = lp_netbios_name();
+ get_pdc._pad = data_blob_null;
+ get_pdc.unicode_pdc_name = lp_netbios_name();
+ get_pdc.domain_name = lp_workgroup();
+ get_pdc.nt_version = NETLOGON_NT_VERSION_1;
+ get_pdc.lmnt_token = 0xffff;
+ get_pdc.lm20_token = 0xffff;
+
+ response.response_type = NETLOGON_GET_PDC;
+ response.data.get_pdc = get_pdc;
+
+ status = push_nbt_netlogon_response(&blob_out, talloc_tos(), &response);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("process_logon_packet: failed to push packet\n"));
+ return;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(nbt_netlogon_response_from_pdc, &response.data.get_pdc);
+ }
+
+ send_mailslot(True, request.req.pdc.mailslot_name,
+ (char *)blob_out.data,
+ blob_out.length,
+ lp_netbios_name(), 0x0,
+ source_name,
+ dgram->source_name.name_type,
+ p->ip, ip, p->port);
+
+ return;
+ }
+
+ case LOGON_SAM_LOGON_REQUEST: {
+ char *source_addr;
+ bool user_unknown = false;
+
+ struct netlogon_samlogon_response samlogon;
+ struct NETLOGON_SAM_LOGON_RESPONSE_NT40 nt4;
+
+ if (global_nmbd_proxy_logon) {
+ nmbd_proxy_logon(global_nmbd_proxy_logon,
+ ip, p, (const uint8_t *)buf, len);
+ return;
+ }
+
+ source_addr = SMB_STRDUP(inet_ntoa(dgram->header.source_ip));
+ if (source_addr == NULL) {
+ DEBUG(3, ("out of memory copying client"
+ " address string\n"));
+ return;
+ }
+
+ DEBUG(5,("process_logon_packet: LOGON_SAM_LOGON_REQUEST request from %s(%s) for %s, returning logon svr %s domain %s code %x token=%x\n",
+ request.req.logon.computer_name,
+ inet_ntoa(p->ip),
+ request.req.logon.user_name,
+ pdc_name,
+ lp_workgroup(),
+ LOGON_SAM_LOGON_RESPONSE,
+ request.req.logon.lmnt_token));
+
+ if (!request.req.logon.user_name) {
+ user_unknown = true;
+ }
+
+ nt4.command = user_unknown ? LOGON_SAM_LOGON_USER_UNKNOWN :
+ LOGON_SAM_LOGON_RESPONSE;
+ nt4.pdc_name = pdc_name;
+ nt4.user_name = request.req.logon.user_name;
+ nt4.domain_name = lp_workgroup();
+ nt4.nt_version = NETLOGON_NT_VERSION_1;
+ nt4.lmnt_token = 0xffff;
+ nt4.lm20_token = 0xffff;
+
+ samlogon.ntver = NETLOGON_NT_VERSION_1;
+ samlogon.data.nt4 = nt4;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(NETLOGON_SAM_LOGON_RESPONSE_NT40, &nt4);
+ }
+
+ response.response_type = NETLOGON_SAMLOGON;
+ response.data.samlogon = samlogon;
+
+ status = push_nbt_netlogon_response(&blob_out, talloc_tos(), &response);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("process_logon_packet: failed to push packet\n"));
+ SAFE_FREE(source_addr);
+ return;
+ }
+
+ /*
+ * handle delay.
+ * packets requeued after delay are marked as
+ * locked.
+ */
+ if ((p->locked == False) &&
+ (strlen(request.req.logon.user_name) == 0) &&
+ delay_logon(source_name, source_addr))
+ {
+ struct timeval when;
+
+ DEBUG(3, ("process_logon_packet: "
+ "delaying initial logon "
+ "reply for client %s(%s) for "
+ "%u milliseconds\n",
+ source_name, source_addr,
+ lp_init_logon_delay()));
+
+ when = timeval_current_ofs_msec(lp_init_logon_delay());
+ p->locked = true;
+ tevent_add_timer(nmbd_event_context(),
+ NULL,
+ when,
+ delayed_init_logon_handler,
+ p);
+ } else {
+ DEBUG(3, ("process_logon_packet: "
+ "processing delayed initial "
+ "logon reply for client "
+ "%s(%s)\n",
+ source_name, source_addr));
+ p->locked = false;
+ send_mailslot(true, request.req.logon.mailslot_name,
+ (char *)blob_out.data,
+ blob_out.length,
+ lp_netbios_name(), 0x0,
+ source_name,
+ dgram->source_name.name_type,
+ p->ip, ip, p->port);
+ }
+
+ SAFE_FREE(source_addr);
+
+ break;
+ }
+
+ /* Announce change to UAS or SAM. Send by the domain controller when a
+ replication event is required. */
+
+ case NETLOGON_ANNOUNCE_UAS:
+ DEBUG(5, ("Got NETLOGON_ANNOUNCE_UAS\n"));
+ break;
+
+ default:
+ DEBUG(3,("process_logon_packet: Unknown domain request %d\n",
+ request.command));
+ return;
+ }
+}
diff --git a/source3/nmbd/nmbd_proto.h b/source3/nmbd/nmbd_proto.h
new file mode 100644
index 0000000..4cfb589
--- /dev/null
+++ b/source3/nmbd/nmbd_proto.h
@@ -0,0 +1,385 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * NBT netbios routines and daemon - version 2
+ *
+ * Copyright (C) Andrew Tridgell 1994-1998
+ * Copyright (C) Jeremy Allison 1994-2005
+ * Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ * Copyright (C) John H Terpstra 1995-1998
+ * Copyright (C) Christopher R. Hertel 1998
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ * Copyright (C) Jelmer Vernooij 2002,2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* The following definitions come from nmbd/asyncdns.c */
+
+int asyncdns_fd(void);
+void kill_async_dns_child(void);
+void start_async_dns(struct messaging_context *msg);
+void run_dns_queue(struct messaging_context *msg);
+bool queue_dns_query(struct packet_struct *p,struct nmb_name *question);
+bool queue_dns_query(struct packet_struct *p,struct nmb_name *question);
+void kill_async_dns_child(void);
+
+/* The following definitions come from nmbd/nmbd.c */
+
+struct tevent_context *nmbd_event_context(void);
+
+/* The following definitions come from nmbd/nmbd_become_dmb.c */
+
+void add_domain_names(time_t t);
+
+/* The following definitions come from nmbd/nmbd_become_lmb.c */
+
+void insert_permanent_name_into_unicast( struct subnet_record *subrec,
+ struct nmb_name *nmbname, uint16_t nb_type );
+void unbecome_local_master_browser(struct subnet_record *subrec, struct work_record *work,
+ bool force_new_election);
+void become_local_master_browser(struct subnet_record *subrec, struct work_record *work);
+void set_workgroup_local_master_browser_name( struct work_record *work, const char *newname);
+
+/* The following definitions come from nmbd/nmbd_browserdb.c */
+
+void update_browser_death_time( struct browse_cache_record *browc );
+struct browse_cache_record *create_browser_in_lmb_cache( const char *work_name,
+ const char *browser_name,
+ struct in_addr ip );
+struct browse_cache_record *find_browser_in_lmb_cache( const char *browser_name );
+void expire_lmb_browsers( time_t t );
+
+/* The following definitions come from nmbd/nmbd_browsesync.c */
+
+void dmb_expire_and_sync_browser_lists(time_t t);
+void announce_and_sync_with_domain_master_browser( struct subnet_record *subrec,
+ struct work_record *work);
+void collect_all_workgroup_names_from_wins_server(time_t t);
+void sync_all_dmbs(time_t t);
+
+/* The following definitions come from nmbd/nmbd_elections.c */
+
+void check_master_browser_exists(time_t t);
+void run_elections(time_t t);
+void process_election(struct subnet_record *subrec, struct packet_struct *p, const char *buf);
+bool check_elections(void);
+void nmbd_message_election(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+/* The following definitions come from nmbd/nmbd_incomingdgrams.c */
+
+void tell_become_backup(void);
+void process_host_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf);
+void process_workgroup_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf);
+void process_local_master_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf);
+void process_master_browser_announce(struct subnet_record *subrec,
+ struct packet_struct *p,const char *buf);
+void process_lm_host_announce(struct subnet_record *subrec, struct packet_struct *p, const char *buf, int len);
+void process_get_backup_list_request(struct subnet_record *subrec,
+ struct packet_struct *p,const char *buf);
+void process_reset_browser(struct subnet_record *subrec,
+ struct packet_struct *p,const char *buf);
+void process_announce_request(struct subnet_record *subrec, struct packet_struct *p, const char *buf);
+void process_lm_announce_request(struct subnet_record *subrec, struct packet_struct *p, const char *buf, int len);
+
+/* The following definitions come from nmbd/nmbd_incomingrequests.c */
+
+void process_name_release_request(struct subnet_record *subrec,
+ struct packet_struct *p);
+void process_name_refresh_request(struct subnet_record *subrec,
+ struct packet_struct *p);
+void process_name_registration_request(struct subnet_record *subrec,
+ struct packet_struct *p);
+void process_node_status_request(struct subnet_record *subrec, struct packet_struct *p);
+void process_name_query_request(struct subnet_record *subrec, struct packet_struct *p);
+
+/* The following definitions come from nmbd/nmbd_lmhosts.c */
+
+void load_lmhosts_file(const char *fname);
+bool find_name_in_lmhosts(struct nmb_name *nmbname, struct name_record **namerecp);
+
+/* The following definitions come from nmbd/nmbd_logonnames.c */
+
+void add_logon_names(void);
+
+/* The following definitions come from nmbd/nmbd_mynames.c */
+
+bool nmbd_init_my_netbios_names(void);
+const char *my_netbios_names(int i);
+void register_my_workgroup_one_subnet(struct subnet_record *subrec);
+bool register_my_workgroup_and_names(void);
+void release_wins_names(void);
+void refresh_my_names(time_t t);
+
+/* The following definitions come from nmbd/nmbd_namelistdb.c */
+
+void set_samba_nb_type(void);
+void remove_name_from_namelist(struct subnet_record *subrec,
+ struct name_record *namerec );
+struct name_record *find_name_on_subnet(struct subnet_record *subrec,
+ const struct nmb_name *nmbname,
+ bool self_only);
+struct name_record *find_name_for_remote_broadcast_subnet(struct nmb_name *nmbname,
+ bool self_only);
+void update_name_ttl( struct name_record *namerec, int ttl );
+bool add_name_to_subnet( struct subnet_record *subrec,
+ const char *name,
+ int type,
+ uint16_t nb_flags,
+ int ttl,
+ enum name_source source,
+ int num_ips,
+ struct in_addr *iplist);
+void standard_success_register(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname, uint16_t nb_flags, int ttl,
+ struct in_addr registered_ip);
+void standard_fail_register( struct subnet_record *subrec,
+ struct nmb_name *nmbname );
+bool find_ip_in_name_record( struct name_record *namerec, struct in_addr ip );
+void add_ip_to_name_record( struct name_record *namerec, struct in_addr new_ip );
+void remove_ip_from_name_record( struct name_record *namerec,
+ struct in_addr remove_ip );
+void standard_success_release( struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ struct in_addr released_ip );
+void expire_names(time_t t);
+void add_samba_names_to_subnet( struct subnet_record *subrec );
+void dump_name_record(struct name_record *namerec, FILE *fp);
+void dump_all_namelists(void);
+
+/* The following definitions come from nmbd/nmbd_namequery.c */
+
+bool query_name(struct subnet_record *subrec, const char *name, int type,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata);
+bool query_name_from_wins_server(struct in_addr ip_to,
+ const char *name, int type,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata);
+
+/* The following definitions come from nmbd/nmbd_nameregister.c */
+
+void register_name(struct subnet_record *subrec,
+ const char *name, int type, uint16_t nb_flags,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn,
+ struct userdata_struct *userdata);
+void wins_refresh_name(struct name_record *namerec);
+
+/* The following definitions come from nmbd/nmbd_namerelease.c */
+
+void release_name(struct subnet_record *subrec, struct name_record *namerec,
+ release_name_success_function success_fn,
+ release_name_fail_function fail_fn,
+ struct userdata_struct *userdata);
+
+/* The following definitions come from nmbd/nmbd_nodestatus.c */
+
+bool node_status(struct subnet_record *subrec, struct nmb_name *nmbname,
+ struct in_addr send_ip, node_status_success_function success_fn,
+ node_status_fail_function fail_fn, struct userdata_struct *userdata);
+
+/* The following definitions come from nmbd/nmbd_packets.c */
+
+bool nmbd_init_packet_server(void);
+
+uint16_t get_nb_flags(char *buf);
+void set_nb_flags(char *buf, uint16_t nb_flags);
+struct response_record *queue_register_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ uint16_t nb_flags);
+void queue_wins_refresh(struct nmb_name *nmbname,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ uint16_t nb_flags,
+ struct in_addr refresh_ip,
+ const char *tag);
+struct response_record *queue_register_multihomed_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ register_name_success_function success_fn,
+ register_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ uint16_t nb_flags,
+ struct in_addr register_ip,
+ struct in_addr wins_ip);
+struct response_record *queue_release_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ release_name_success_function success_fn,
+ release_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ uint16_t nb_flags,
+ struct in_addr release_ip,
+ struct in_addr dest_ip);
+struct response_record *queue_query_name( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname);
+struct response_record *queue_query_name_from_wins_server( struct in_addr to_ip,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ query_name_success_function success_fn,
+ query_name_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname);
+struct response_record *queue_node_status( struct subnet_record *subrec,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ node_status_success_function success_fn,
+ node_status_fail_function fail_fn,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname,
+ struct in_addr send_ip);
+void reply_netbios_packet(struct packet_struct *orig_packet,
+ int rcode, enum netbios_reply_type_code rcv_code, int opcode,
+ int ttl, char *data,int len);
+void queue_packet(struct packet_struct *packet);
+void run_packet_queue(void);
+void retransmit_or_expire_response_records(time_t t);
+bool listen_for_packets(struct messaging_context *msg, bool run_election);
+bool send_mailslot(bool unique, const char *mailslot,char *buf, size_t len,
+ const char *srcname, int src_type,
+ const char *dstname, int dest_type,
+ struct in_addr dest_ip,struct in_addr src_ip,
+ int dest_port);
+
+/* The following definitions come from nmbd/nmbd_processlogon.c */
+
+bool initialize_nmbd_proxy_logon(void);
+
+void process_logon_packet(struct packet_struct *p, const char *buf,int len,
+ const char *mailslot);
+
+/* The following definitions come from nmbd/nmbd_responserecordsdb.c */
+
+void remove_response_record(struct subnet_record *subrec,
+ struct response_record *rrec);
+struct response_record *make_response_record( struct subnet_record *subrec,
+ struct packet_struct *p,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ success_function success_fn,
+ fail_function fail_fn,
+ struct userdata_struct *userdata);
+struct response_record *find_response_record(struct subnet_record **ppsubrec,
+ uint16_t id);
+bool is_refresh_already_queued(struct subnet_record *subrec, struct name_record *namerec);
+
+/* The following definitions come from nmbd/nmbd_sendannounce.c */
+
+void send_browser_reset(int reset_type, const char *to_name, int to_type, struct in_addr to_ip);
+void broadcast_announce_request(struct subnet_record *subrec, struct work_record *work);
+void announce_my_server_names(time_t t);
+void announce_my_lm_server_names(time_t t);
+void reset_announce_timer(void);
+void announce_myself_to_domain_master_browser(time_t t);
+void announce_my_servers_removed(void);
+void announce_remote(time_t t);
+void browse_sync_remote(time_t t);
+
+/* The following definitions come from nmbd/nmbd_serverlistdb.c */
+
+void remove_all_servers(struct work_record *work);
+struct server_record *find_server_in_workgroup(struct work_record *work, const char *name);
+void remove_server_from_workgroup(struct work_record *work, struct server_record *servrec);
+struct server_record *create_server_on_workgroup(struct work_record *work,
+ const char *name,int servertype,
+ int ttl, const char *comment);
+void update_server_ttl(struct server_record *servrec, int ttl);
+void expire_servers(struct work_record *work, time_t t);
+void write_browse_list_entry(FILE *fp, const char *name, uint32_t rec_type,
+ const char *local_master_browser_name, const char *description);
+void write_browse_list(time_t t, bool force_write);
+
+/* The following definitions come from nmbd/nmbd_subnetdb.c */
+
+void close_subnet(struct subnet_record *subrec);
+struct subnet_record *make_normal_subnet(const struct interface *iface);
+bool create_subnets(void);
+bool we_are_a_wins_client(void);
+struct subnet_record *get_next_subnet_maybe_unicast(struct subnet_record *subrec);
+struct subnet_record *get_next_subnet_maybe_unicast_or_wins_server(struct subnet_record *subrec);
+
+/* The following definitions come from nmbd/nmbd_synclists.c */
+
+void sync_browse_lists(struct work_record *work,
+ char *name, int nm_type,
+ struct in_addr ip, bool local, bool servers);
+void sync_check_completion(void);
+
+/* The following definitions come from nmbd/nmbd_winsproxy.c */
+
+void make_wins_proxy_name_query_request( struct subnet_record *subrec,
+ struct packet_struct *incoming_packet,
+ struct nmb_name *question_name);
+
+/* The following definitions come from nmbd/nmbd_winsserver.c */
+
+struct name_record *find_name_on_wins_subnet(const struct nmb_name *nmbname, bool self_only);
+bool wins_store_changed_namerec(const struct name_record *namerec);
+bool add_name_to_wins_subnet(const struct name_record *namerec);
+bool remove_name_from_wins_namelist(struct name_record *namerec);
+void dump_wins_subnet_namelist(FILE *fp);
+bool packet_is_for_wins_server(struct packet_struct *packet);
+bool initialise_wins(void);
+void wins_process_name_refresh_request( struct subnet_record *subrec,
+ struct packet_struct *p );
+void wins_process_name_registration_request(struct subnet_record *subrec,
+ struct packet_struct *p);
+void wins_process_multihomed_name_registration_request( struct subnet_record *subrec,
+ struct packet_struct *p);
+void fetch_all_active_wins_1b_names(void);
+void send_wins_name_query_response(int rcode, struct packet_struct *p,
+ struct name_record *namerec);
+void wins_process_name_query_request(struct subnet_record *subrec,
+ struct packet_struct *p);
+void wins_process_name_release_request(struct subnet_record *subrec,
+ struct packet_struct *p);
+void initiate_wins_processing(time_t t);
+void wins_write_name_record(struct name_record *namerec, FILE *fp);
+void wins_write_database(time_t t, bool background);
+void nmbd_wins_new_entry(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+/* The following definitions come from nmbd/nmbd_workgroupdb.c */
+
+struct work_record *find_workgroup_on_subnet(struct subnet_record *subrec,
+ const char *name);
+struct work_record *create_workgroup_on_subnet(struct subnet_record *subrec,
+ const char *name, int ttl);
+void update_workgroup_ttl(struct work_record *work, int ttl);
+void initiate_myworkgroup_startup(struct subnet_record *subrec, struct work_record *work);
+void dump_workgroups(bool force_write);
+void expire_workgroups_and_servers(time_t t);
diff --git a/source3/nmbd/nmbd_responserecordsdb.c b/source3/nmbd/nmbd_responserecordsdb.c
new file mode 100644
index 0000000..8876722
--- /dev/null
+++ b/source3/nmbd/nmbd_responserecordsdb.c
@@ -0,0 +1,244 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios library routines
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+int num_response_packets = 0;
+
+/***************************************************************************
+ Add an expected response record into the list
+ **************************************************************************/
+
+static void add_response_record(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ num_response_packets++; /* count of total number of packets still around */
+
+ DEBUG(4,("add_response_record: adding response record id:%hu to subnet %s. num_records:%d\n",
+ rrec->response_id, subrec->subnet_name, num_response_packets));
+
+ DLIST_ADD_END(subrec->responselist, rrec);
+}
+
+/***************************************************************************
+ Remove an expected response record from the list
+ **************************************************************************/
+
+void remove_response_record(struct subnet_record *subrec,
+ struct response_record *rrec)
+{
+ /* It is possible this can be called twice,
+ with a rrec pointer that has been freed. So
+ before we indirect into rrec, search for it
+ on the responselist first. Bug #3617. JRA. */
+
+ struct response_record *p = NULL;
+
+ for (p = subrec->responselist; p; p = p->next) {
+ if (p == rrec) {
+ break;
+ }
+ }
+
+ if (p == NULL) {
+ /* We didn't find rrec on the list. */
+ return;
+ }
+
+ DLIST_REMOVE(subrec->responselist, rrec);
+
+ if(rrec->userdata) {
+ if(rrec->userdata->free_fn) {
+ (*rrec->userdata->free_fn)(rrec->userdata);
+ } else {
+ ZERO_STRUCTP(rrec->userdata);
+ SAFE_FREE(rrec->userdata);
+ }
+ }
+
+ /* Ensure we can delete. */
+ rrec->packet->locked = False;
+ free_packet(rrec->packet);
+
+ ZERO_STRUCTP(rrec);
+ SAFE_FREE(rrec);
+
+ num_response_packets--; /* count of total number of packets still around */
+}
+
+/****************************************************************************
+ Create a response record for an outgoing packet.
+ **************************************************************************/
+
+struct response_record *make_response_record( struct subnet_record *subrec,
+ struct packet_struct *p,
+ response_function resp_fn,
+ timeout_response_function timeout_fn,
+ success_function success_fn,
+ fail_function fail_fn,
+ struct userdata_struct *userdata)
+{
+ struct response_record *rrec;
+ struct nmb_packet *nmb = &p->packet.nmb;
+
+ if (!(rrec = SMB_MALLOC_P(struct response_record))) {
+ DEBUG(0,("make_response_queue_record: malloc fail for response_record.\n"));
+ return NULL;
+ }
+
+ memset((char *)rrec, '\0', sizeof(*rrec));
+
+ rrec->response_id = nmb->header.name_trn_id;
+
+ rrec->resp_fn = resp_fn;
+ rrec->timeout_fn = timeout_fn;
+ rrec->success_fn = success_fn;
+ rrec->fail_fn = fail_fn;
+
+ rrec->packet = p;
+
+ if(userdata) {
+ /* Intelligent userdata. */
+ if(userdata->copy_fn) {
+ if((rrec->userdata = (*userdata->copy_fn)(userdata)) == NULL) {
+ DEBUG(0,("make_response_queue_record: copy fail for userdata.\n"));
+ ZERO_STRUCTP(rrec);
+ SAFE_FREE(rrec);
+ return NULL;
+ }
+ } else {
+ /* Primitive userdata, do a memcpy. */
+ if((rrec->userdata = (struct userdata_struct *)
+ SMB_MALLOC(sizeof(struct userdata_struct)+userdata->userdata_len)) == NULL) {
+ DEBUG(0,("make_response_queue_record: malloc fail for userdata.\n"));
+ ZERO_STRUCTP(rrec);
+ SAFE_FREE(rrec);
+ return NULL;
+ }
+ rrec->userdata->copy_fn = userdata->copy_fn;
+ rrec->userdata->free_fn = userdata->free_fn;
+ rrec->userdata->userdata_len = userdata->userdata_len;
+ memcpy(rrec->userdata->data, userdata->data, userdata->userdata_len);
+ }
+ } else {
+ rrec->userdata = NULL;
+ }
+
+ rrec->num_msgs = 0;
+
+ if(!nmb->header.nm_flags.bcast)
+ rrec->repeat_interval = 5; /* 5 seconds for unicast packets. */
+ else
+ rrec->repeat_interval = 1; /* XXXX should be in ms */
+ rrec->repeat_count = 3; /* 3 retries */
+ rrec->repeat_time = time(NULL) + rrec->repeat_interval; /* initial retry time */
+
+ /* This packet is not being processed. */
+ rrec->in_expiration_processing = False;
+
+ /* Lock the packet so we won't lose it while it's on the list. */
+ p->locked = True;
+
+ add_response_record(subrec, rrec);
+
+ return rrec;
+}
+
+/****************************************************************************
+ Find a response in a subnet's name query response list.
+ **************************************************************************/
+
+static struct response_record *find_response_record_on_subnet(
+ struct subnet_record *subrec, uint16_t id)
+{
+ struct response_record *rrec = NULL;
+
+ for (rrec = subrec->responselist; rrec; rrec = rrec->next) {
+ if (rrec->response_id == id) {
+ DEBUG(4, ("find_response_record: found response record id = %hu on subnet %s\n",
+ id, subrec->subnet_name));
+ break;
+ }
+ }
+ return rrec;
+}
+
+/****************************************************************************
+ Find a response in any subnet's name query response list.
+ **************************************************************************/
+
+struct response_record *find_response_record(struct subnet_record **ppsubrec,
+ uint16_t id)
+{
+ struct response_record *rrec = NULL;
+
+ for ((*ppsubrec) = FIRST_SUBNET; (*ppsubrec);
+ (*ppsubrec) = NEXT_SUBNET_INCLUDING_UNICAST(*ppsubrec)) {
+ if((rrec = find_response_record_on_subnet(*ppsubrec, id)) != NULL)
+ return rrec;
+ }
+
+ /* There should never be response records on the remote_broadcast subnet.
+ Sanity check to ensure this is so. */
+ if(remote_broadcast_subnet->responselist != NULL) {
+ DEBUG(0,("find_response_record: response record found on subnet %s. This should \
+never happen !\n", remote_broadcast_subnet->subnet_name));
+ }
+
+ /* Now check the WINS server subnet if it exists. */
+ if(wins_server_subnet != NULL) {
+ *ppsubrec = wins_server_subnet;
+ if((rrec = find_response_record_on_subnet(*ppsubrec, id))!= NULL)
+ return rrec;
+ }
+
+ DEBUG(3,("find_response_record: response packet id %hu received with no \
+matching record.\n", id));
+
+ *ppsubrec = NULL;
+
+ return NULL;
+}
+
+/****************************************************************************
+ Check if a refresh is queued for a particular name on a particular subnet.
+ **************************************************************************/
+
+bool is_refresh_already_queued(struct subnet_record *subrec, struct name_record *namerec)
+{
+ struct response_record *rrec = NULL;
+
+ for (rrec = subrec->responselist; rrec; rrec = rrec->next) {
+ struct packet_struct *p = rrec->packet;
+ struct nmb_packet *nmb = &p->packet.nmb;
+
+ if((nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_8) ||
+ (nmb->header.opcode == NMB_NAME_REFRESH_OPCODE_9)) {
+ /* Yes it's a queued refresh - check if the name is correct. */
+ if(nmb_name_equal(&nmb->question.question_name, &namerec->name))
+ return True;
+ }
+ }
+
+ return False;
+}
diff --git a/source3/nmbd/nmbd_sendannounce.c b/source3/nmbd/nmbd_sendannounce.c
new file mode 100644
index 0000000..4e8be04
--- /dev/null
+++ b/source3/nmbd/nmbd_sendannounce.c
@@ -0,0 +1,602 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+extern int updatecount;
+extern bool found_lm_clients;
+
+/****************************************************************************
+ Send a browser reset packet.
+**************************************************************************/
+
+void send_browser_reset(int reset_type, const char *to_name, int to_type, struct in_addr to_ip)
+{
+ char outbuf[1024];
+ char *p;
+
+ DBG_NOTICE("send_browser_reset: sending reset request type %d to %s<%02x> IP %s.\n",
+ reset_type, to_name, to_type, inet_ntoa(to_ip) );
+
+ memset(outbuf,'\0',sizeof(outbuf));
+ p = outbuf;
+ SCVAL(p,0,ANN_ResetBrowserState);
+ p++;
+ SCVAL(p,0,reset_type);
+ p++;
+
+ send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf),
+ lp_netbios_name(), 0x0, to_name, to_type, to_ip,
+ FIRST_SUBNET->myip, DGRAM_PORT);
+}
+
+/****************************************************************************
+ Broadcast a packet to the local net requesting that all servers in this
+ workgroup announce themselves to us.
+ **************************************************************************/
+
+void broadcast_announce_request(struct subnet_record *subrec, struct work_record *work)
+{
+ char outbuf[1024];
+ char *p;
+
+ work->needannounce = True;
+
+ DBG_NOTICE("broadcast_announce_request: sending announce request for workgroup %s \
+to subnet %s\n", work->work_group, subrec->subnet_name);
+
+ memset(outbuf,'\0',sizeof(outbuf));
+ p = outbuf;
+ SCVAL(p,0,ANN_AnnouncementRequest);
+ p++;
+
+ SCVAL(p,0,work->token); /* (local) Unique workgroup token id. */
+ p++;
+ p += push_string_check(p+1, lp_netbios_name(), 15, STR_ASCII|STR_UPPER|STR_TERMINATE);
+
+ send_mailslot(False, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf),
+ lp_netbios_name(), 0x0, work->work_group,0x1e, subrec->bcast_ip,
+ subrec->myip, DGRAM_PORT);
+}
+
+/****************************************************************************
+ Broadcast an announcement.
+ **************************************************************************/
+
+static void send_announcement(struct subnet_record *subrec, int announce_type,
+ const char *from_name, const char *to_name, int to_type, struct in_addr to_ip,
+ time_t announce_interval,
+ const char *server_name, int server_type, const char *server_comment)
+{
+ char outbuf[1024];
+ unstring upper_server_name;
+ char *p;
+
+ memset(outbuf,'\0',sizeof(outbuf));
+ p = outbuf+1;
+
+ SCVAL(outbuf,0,announce_type);
+
+ /* Announcement parameters. */
+ SCVAL(p,0,updatecount);
+ SIVAL(p,1,announce_interval*1000); /* Milliseconds - despite the spec. */
+
+ strlcpy(upper_server_name, server_name ? server_name : "", sizeof(upper_server_name));
+ if (!strupper_m(upper_server_name)) {
+ DBG_WARNING("strupper_m %s failed\n", upper_server_name);
+ return;
+ }
+ push_string_check(p+5, upper_server_name, 16, STR_ASCII|STR_TERMINATE);
+
+ SCVAL(p,21,SAMBA_MAJOR_NBT_ANNOUNCE_VERSION); /* Major version. */
+ SCVAL(p,22,SAMBA_MINOR_NBT_ANNOUNCE_VERSION); /* Minor version. */
+
+ SIVAL(p,23,server_type & ~SV_TYPE_LOCAL_LIST_ONLY);
+ /* Browse version: got from NT/AS 4.00 - Value defined in smb.h (JHT). */
+ SSVAL(p,27,BROWSER_ELECTION_VERSION);
+ SSVAL(p,29,BROWSER_CONSTANT); /* Browse signature. */
+
+ p += 31 + push_string_check(p+31, server_comment, sizeof(outbuf) - (p + 31 - outbuf), STR_ASCII|STR_TERMINATE);
+
+ send_mailslot(False,BROWSE_MAILSLOT, outbuf, PTR_DIFF(p,outbuf),
+ from_name, 0x0, to_name, to_type, to_ip, subrec->myip,
+ DGRAM_PORT);
+}
+
+/****************************************************************************
+ Broadcast a LanMan announcement.
+**************************************************************************/
+
+static void send_lm_announcement(struct subnet_record *subrec, int announce_type,
+ char *from_name, char *to_name, int to_type, struct in_addr to_ip,
+ time_t announce_interval,
+ char *server_name, int server_type, char *server_comment)
+{
+ char outbuf[1024];
+ char *p=outbuf;
+
+ memset(outbuf,'\0',sizeof(outbuf));
+
+ SSVAL(p,0,announce_type);
+ SIVAL(p,2,server_type & ~SV_TYPE_LOCAL_LIST_ONLY);
+ SCVAL(p,6,SAMBA_MAJOR_NBT_ANNOUNCE_VERSION); /* Major version. */
+ SCVAL(p,7,SAMBA_MINOR_NBT_ANNOUNCE_VERSION); /* Minor version. */
+ SSVAL(p,8,announce_interval); /* In seconds - according to spec. */
+
+ p += 10;
+ p += push_string_check(p, server_name, 15, STR_ASCII|STR_UPPER|STR_TERMINATE);
+ p += push_string_check(p, server_comment, sizeof(outbuf)- (p - outbuf), STR_ASCII|STR_UPPER|STR_TERMINATE);
+
+ send_mailslot(False,LANMAN_MAILSLOT, outbuf, PTR_DIFF(p,outbuf),
+ from_name, 0x0, to_name, to_type, to_ip, subrec->myip,
+ DGRAM_PORT);
+}
+
+/****************************************************************************
+ We are a local master browser. Announce this to WORKGROUP<1e>.
+****************************************************************************/
+
+static void send_local_master_announcement(struct subnet_record *subrec, struct work_record *work,
+ struct server_record *servrec)
+{
+ /* Ensure we don't have the prohibited bit set. */
+ uint32_t type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ DBG_NOTICE("send_local_master_announcement: type %x for name %s on subnet %s for workgroup %s\n",
+ type, lp_netbios_name(), subrec->subnet_name, work->work_group);
+
+ send_announcement(subrec, ANN_LocalMasterAnnouncement,
+ lp_netbios_name(), /* From nbt name. */
+ work->work_group, 0x1e, /* To nbt name. */
+ subrec->bcast_ip, /* To ip. */
+ work->announce_interval, /* Time until next announce. */
+ lp_netbios_name(), /* Name to announce. */
+ type, /* Type field. */
+ servrec->serv.comment);
+}
+
+/****************************************************************************
+ Announce the workgroup WORKGROUP to MSBROWSE<01>.
+****************************************************************************/
+
+static void send_workgroup_announcement(struct subnet_record *subrec, struct work_record *work)
+{
+ DBG_NOTICE("send_workgroup_announcement: on subnet %s for workgroup %s\n",
+ subrec->subnet_name, work->work_group);
+
+ send_announcement(subrec, ANN_DomainAnnouncement,
+ lp_netbios_name(), /* From nbt name. */
+ MSBROWSE, 0x1, /* To nbt name. */
+ subrec->bcast_ip, /* To ip. */
+ work->announce_interval, /* Time until next announce. */
+ work->work_group, /* Name to announce. */
+ SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT, /* workgroup announce flags. */
+ lp_netbios_name()); /* From name as comment. */
+}
+
+/****************************************************************************
+ Announce the given host to WORKGROUP<1d>.
+****************************************************************************/
+
+static void send_host_announcement(struct subnet_record *subrec, struct work_record *work,
+ struct server_record *servrec)
+{
+ /* Ensure we don't have the prohibited bits set. */
+ uint32_t type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ DBG_NOTICE("send_host_announcement: type %x for host %s on subnet %s for workgroup %s\n",
+ type, servrec->serv.name, subrec->subnet_name, work->work_group);
+
+ send_announcement(subrec, ANN_HostAnnouncement,
+ servrec->serv.name, /* From nbt name. */
+ work->work_group, 0x1d, /* To nbt name. */
+ subrec->bcast_ip, /* To ip. */
+ work->announce_interval, /* Time until next announce. */
+ servrec->serv.name, /* Name to announce. */
+ type, /* Type field. */
+ servrec->serv.comment);
+}
+
+/****************************************************************************
+ Announce the given LanMan host
+****************************************************************************/
+
+static void send_lm_host_announcement(struct subnet_record *subrec, struct work_record *work,
+ struct server_record *servrec, int lm_interval)
+{
+ /* Ensure we don't have the prohibited bits set. */
+ uint32_t type = servrec->serv.type & ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ DBG_NOTICE("send_lm_host_announcement: type %x for host %s on subnet %s for workgroup %s, ttl: %d\n",
+ type, servrec->serv.name, subrec->subnet_name, work->work_group, lm_interval);
+
+ send_lm_announcement(subrec, ANN_HostAnnouncement,
+ servrec->serv.name, /* From nbt name. */
+ work->work_group, 0x00, /* To nbt name. */
+ subrec->bcast_ip, /* To ip. */
+ lm_interval, /* Time until next announce. */
+ servrec->serv.name, /* Name to announce (fstring not netbios name struct). */
+ type, /* Type field. */
+ servrec->serv.comment);
+}
+
+/****************************************************************************
+ Announce a server record.
+ ****************************************************************************/
+
+static void announce_server(struct subnet_record *subrec, struct work_record *work,
+ struct server_record *servrec)
+{
+ /* Only do domain announcements if we are a master and it's
+ our primary name we're being asked to announce. */
+
+ if (AM_LOCAL_MASTER_BROWSER(work) && strequal(lp_netbios_name(),servrec->serv.name)) {
+ send_local_master_announcement(subrec, work, servrec);
+ send_workgroup_announcement(subrec, work);
+ } else {
+ send_host_announcement(subrec, work, servrec);
+ }
+}
+
+/****************************************************************************
+ Go through all my registered names on all broadcast subnets and announce
+ them if the timeout requires it.
+ **************************************************************************/
+
+void announce_my_server_names(time_t t)
+{
+ struct subnet_record *subrec;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work = find_workgroup_on_subnet(subrec, lp_workgroup());
+
+ if(work) {
+ struct server_record *servrec;
+
+ if (work->needannounce) {
+ /* Drop back to a max 3 minute announce. This is to prevent a
+ single lost packet from breaking things for too long. */
+
+ work->announce_interval = MIN(work->announce_interval,
+ CHECK_TIME_MIN_HOST_ANNCE*60);
+ work->lastannounce_time = t - (work->announce_interval+1);
+ work->needannounce = False;
+ }
+
+ /* Announce every minute at first then progress to every 12 mins */
+ if (t >= work->lastannounce_time &&
+ (t - work->lastannounce_time) < work->announce_interval) {
+ continue;
+ }
+
+ if (work->announce_interval < (CHECK_TIME_MAX_HOST_ANNCE * 60))
+ work->announce_interval += 60;
+
+ work->lastannounce_time = t;
+
+ for (servrec = work->serverlist; servrec; servrec = servrec->next) {
+ if (is_myname(servrec->serv.name))
+ announce_server(subrec, work, servrec);
+ }
+ } /* if work */
+ } /* for subrec */
+}
+
+/****************************************************************************
+ Go through all my registered names on all broadcast subnets and announce
+ them as a LanMan server if the timeout requires it.
+**************************************************************************/
+
+void announce_my_lm_server_names(time_t t)
+{
+ struct subnet_record *subrec;
+ static time_t last_lm_announce_time=0;
+ int announce_interval = lp_lm_interval();
+ int lm_announce = lp_lm_announce();
+
+ if ((announce_interval <= 0) || (lm_announce <= 0)) {
+ /* user absolutely does not want LM announcements to be sent. */
+ return;
+ }
+
+ if ((lm_announce >= 2) && (!found_lm_clients)) {
+ /* has been set to 2 (Auto) but no LM clients detected (yet). */
+ return;
+ }
+
+ /* Otherwise: must have been set to 1 (Yes), or LM clients *have*
+ been detected. */
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work = find_workgroup_on_subnet(subrec, lp_workgroup());
+
+ if(work) {
+ struct server_record *servrec;
+
+ if (last_lm_announce_time && ((t - last_lm_announce_time) < announce_interval ))
+ continue;
+
+ last_lm_announce_time = t;
+
+ for (servrec = work->serverlist; servrec; servrec = servrec->next) {
+ if (is_myname(servrec->serv.name))
+ /* skipping equivalent of announce_server() */
+ send_lm_host_announcement(subrec, work, servrec, announce_interval);
+ }
+ } /* if work */
+ } /* for subrec */
+}
+
+/* Announce timer. Moved into global static so it can be reset
+ when a machine becomes a local master browser. */
+static time_t announce_timer_last=0;
+
+/****************************************************************************
+ Reset the announce_timer so that a local master browser announce will be done
+ immediately.
+ ****************************************************************************/
+
+void reset_announce_timer(void)
+{
+ announce_timer_last = time(NULL) - (CHECK_TIME_MST_ANNOUNCE * 60);
+}
+
+/****************************************************************************
+ Announce myself as a local master browser to a domain master browser.
+ **************************************************************************/
+
+void announce_myself_to_domain_master_browser(time_t t)
+{
+ struct subnet_record *subrec;
+ struct work_record *work;
+
+ if(!we_are_a_wins_client()) {
+ DBG_DEBUG("announce_myself_to_domain_master_browser: no unicast subnet, ignoring.\n");
+ return;
+ }
+
+ if (!announce_timer_last)
+ announce_timer_last = t;
+
+ if ((t-announce_timer_last) < (CHECK_TIME_MST_ANNOUNCE * 60)) {
+ DBG_DEBUG("announce_myself_to_domain_master_browser: t (%d) - last(%d) < %d\n",
+ (int)t, (int)announce_timer_last,
+ CHECK_TIME_MST_ANNOUNCE * 60 );
+ return;
+ }
+
+ announce_timer_last = t;
+
+ /* Look over all our broadcast subnets to see if any of them
+ has the state set as local master browser. */
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ for (work = subrec->workgrouplist; work; work = work->next) {
+ if (AM_LOCAL_MASTER_BROWSER(work)) {
+ DBG_NOTICE( "announce_myself_to_domain_master_browser: I am a local master browser for \
+workgroup %s on subnet %s\n", work->work_group, subrec->subnet_name);
+
+ /* Look in nmbd_browsersync.c for the rest of this code. */
+ announce_and_sync_with_domain_master_browser(subrec, work);
+ }
+ }
+ }
+}
+
+/****************************************************************************
+Announce all samba's server entries as 'gone'.
+This must *only* be called on shutdown.
+****************************************************************************/
+
+void announce_my_servers_removed(void)
+{
+ int announce_interval = lp_lm_interval();
+ int lm_announce = lp_lm_announce();
+ struct subnet_record *subrec;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_EXCLUDING_UNICAST(subrec)) {
+ struct work_record *work;
+ for (work = subrec->workgrouplist; work; work = work->next) {
+ struct server_record *servrec;
+
+ work->announce_interval = 0;
+ for (servrec = work->serverlist; servrec; servrec = servrec->next) {
+ if (!is_myname(servrec->serv.name))
+ continue;
+ servrec->serv.type = 0;
+ if(AM_LOCAL_MASTER_BROWSER(work))
+ send_local_master_announcement(subrec, work, servrec);
+ send_host_announcement(subrec, work, servrec);
+
+ if ((announce_interval <= 0) || (lm_announce <= 0)) {
+ /* user absolutely does not want LM announcements to be sent. */
+ continue;
+ }
+
+ if ((lm_announce >= 2) && (!found_lm_clients)) {
+ /* has been set to 2 (Auto) but no LM clients detected (yet). */
+ continue;
+ }
+
+ /*
+ * lm announce was set or we have seen lm announcements, so do
+ * a lm announcement of host removed.
+ */
+
+ send_lm_host_announcement(subrec, work, servrec, 0);
+ }
+ }
+ }
+}
+
+/****************************************************************************
+ Do all the "remote" announcements. These are used to put ourselves
+ on a remote browse list. They are done blind, no checking is done to
+ see if there is actually a local master browser at the other end.
+ **************************************************************************/
+
+void announce_remote(time_t t)
+{
+ char *s = NULL;
+ const char *ptr;
+ static time_t last_time = 0;
+ char *s2;
+ struct in_addr addr;
+ char *comment;
+ int stype = lp_default_server_announce();
+ TALLOC_CTX *frame = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (last_time && (t < (last_time + REMOTE_ANNOUNCE_INTERVAL)))
+ return;
+
+ last_time = t;
+
+ s = lp_remote_announce(talloc_tos(), lp_sub);
+ if (!*s)
+ return;
+
+ comment = string_truncate(lp_server_string(talloc_tos(), lp_sub),
+ MAX_SERVER_STRING_LENGTH);
+
+ frame = talloc_stackframe();
+ for (ptr=s; next_token_talloc(frame,&ptr,&s2,NULL); ) {
+ /* The entries are of the form a.b.c.d/WORKGROUP with
+ WORKGROUP being optional */
+ const char *wgroup;
+ char *pwgroup;
+ int i;
+
+ pwgroup = strchr_m(s2,'/');
+ if (pwgroup)
+ *pwgroup++ = 0;
+ if (!pwgroup || !*pwgroup)
+ wgroup = lp_workgroup();
+ else
+ wgroup = pwgroup;
+
+ addr = interpret_addr2(s2);
+
+ /* Announce all our names including aliases */
+ /* Give the ip address as the address of our first
+ broadcast subnet. */
+
+ for(i=0; my_netbios_names(i); i++) {
+ const char *name = my_netbios_names(i);
+
+ DBG_INFO("announce_remote: Doing remote announce for server %s to IP %s.\n",
+ name, inet_ntoa(addr) );
+
+ send_announcement(FIRST_SUBNET, ANN_HostAnnouncement,
+ name, /* From nbt name. */
+ wgroup, 0x1d, /* To nbt name. */
+ addr, /* To ip. */
+ REMOTE_ANNOUNCE_INTERVAL, /* Time until next announce. */
+ name, /* Name to announce. */
+ stype, /* Type field. */
+ comment);
+ }
+ }
+ TALLOC_FREE(frame);
+}
+
+/****************************************************************************
+ Implement the 'remote browse sync' feature Andrew added.
+ These are used to put our browse lists into remote browse lists.
+**************************************************************************/
+
+void browse_sync_remote(time_t t)
+{
+ char *s;
+ const char *ptr;
+ static time_t last_time = 0;
+ char *s2;
+ struct in_addr addr;
+ struct work_record *work;
+ char outbuf[1024];
+ char *p;
+ unstring myname;
+ TALLOC_CTX *frame = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (last_time && (t < (last_time + REMOTE_ANNOUNCE_INTERVAL)))
+ return;
+
+ last_time = t;
+
+ s = lp_remote_browse_sync(talloc_tos(), lp_sub);
+ if (!*s)
+ return;
+
+ /*
+ * We only do this if we are the local master browser
+ * for our workgroup on the firsst subnet.
+ */
+
+ if((work = find_workgroup_on_subnet(FIRST_SUBNET, lp_workgroup())) == NULL) {
+ DBG_WARNING("browse_sync_remote: Cannot find workgroup %s on subnet %s\n",
+ lp_workgroup(), FIRST_SUBNET->subnet_name );
+ return;
+ }
+
+ if(!AM_LOCAL_MASTER_BROWSER(work)) {
+ DBG_NOTICE("browse_sync_remote: We can only do this if we are a local master browser \
+for workgroup %s on subnet %s.\n", lp_workgroup(), FIRST_SUBNET->subnet_name );
+ return;
+ }
+
+ memset(outbuf,'\0',sizeof(outbuf));
+ p = outbuf;
+ SCVAL(p,0,ANN_MasterAnnouncement);
+ p++;
+
+ unstrcpy(myname, lp_netbios_name());
+ if (!strupper_m(myname)) {
+ DBG_WARNING("strupper_m %s failed\n", myname);
+ return;
+ }
+ myname[15]='\0';
+ push_ascii(p, myname, sizeof(outbuf)-PTR_DIFF(p,outbuf)-1, STR_TERMINATE);
+
+ p = skip_string(outbuf,sizeof(outbuf),p);
+
+ frame = talloc_stackframe();
+ for (ptr=s; next_token_talloc(frame,&ptr,&s2,NULL); ) {
+ /* The entries are of the form a.b.c.d */
+ addr = interpret_addr2(s2);
+
+ DBG_INFO("announce_remote: Doing remote browse sync announce for server %s to IP %s.\n",
+ lp_netbios_name(), inet_ntoa(addr) );
+
+ send_mailslot(True, BROWSE_MAILSLOT, outbuf,PTR_DIFF(p,outbuf),
+ lp_netbios_name(), 0x0, "*", 0x0, addr, FIRST_SUBNET->myip, DGRAM_PORT);
+ }
+ TALLOC_FREE(frame);
+}
diff --git a/source3/nmbd/nmbd_serverlistdb.c b/source3/nmbd/nmbd_serverlistdb.c
new file mode 100644
index 0000000..2aced3d
--- /dev/null
+++ b/source3/nmbd/nmbd_serverlistdb.c
@@ -0,0 +1,417 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+int updatecount = 0;
+
+/*******************************************************************
+ Remove all the servers in a work group.
+ ******************************************************************/
+
+void remove_all_servers(struct work_record *work)
+{
+ struct server_record *servrec;
+ struct server_record *nexts;
+
+ for (servrec = work->serverlist; servrec; servrec = nexts) {
+ DEBUG(7,("remove_all_servers: Removing server %s\n",servrec->serv.name));
+ nexts = servrec->next;
+ DLIST_REMOVE(work->serverlist, servrec);
+ ZERO_STRUCTP(servrec);
+ SAFE_FREE(servrec);
+ }
+
+ work->subnet->work_changed = True;
+}
+
+/***************************************************************************
+ Add a server into a workgroup serverlist.
+ **************************************************************************/
+
+static void add_server_to_workgroup(struct work_record *work,
+ struct server_record *servrec)
+{
+ DLIST_ADD_END(work->serverlist, servrec);
+ work->subnet->work_changed = True;
+}
+
+/****************************************************************************
+ Find a server in a server list.
+ **************************************************************************/
+
+struct server_record *find_server_in_workgroup(struct work_record *work, const char *name)
+{
+ struct server_record *ret;
+
+ for (ret = work->serverlist; ret; ret = ret->next) {
+ if (strequal(ret->serv.name,name))
+ return ret;
+ }
+ return NULL;
+}
+
+
+/****************************************************************************
+ Remove a server entry from this workgroup.
+ ****************************************************************************/
+
+void remove_server_from_workgroup(struct work_record *work, struct server_record *servrec)
+{
+ DLIST_REMOVE(work->serverlist, servrec);
+ ZERO_STRUCTP(servrec);
+ SAFE_FREE(servrec);
+ work->subnet->work_changed = True;
+}
+
+/****************************************************************************
+ Create a server entry on this workgroup.
+ ****************************************************************************/
+
+struct server_record *create_server_on_workgroup(struct work_record *work,
+ const char *name,int servertype,
+ int ttl, const char *comment)
+{
+ struct server_record *servrec;
+
+ if (name[0] == '*') {
+ DEBUG(7,("create_server_on_workgroup: not adding name starting with '*' (%s)\n",
+ name));
+ return (NULL);
+ }
+
+ if(find_server_in_workgroup(work, name) != NULL) {
+ DEBUG(0,("create_server_on_workgroup: Server %s already exists on \
+workgroup %s. This is a bug.\n", name, work->work_group));
+ return NULL;
+ }
+
+ if((servrec = SMB_MALLOC_P(struct server_record)) == NULL) {
+ DEBUG(0,("create_server_entry_on_workgroup: malloc fail !\n"));
+ return NULL;
+ }
+
+ memset((char *)servrec,'\0',sizeof(*servrec));
+
+ servrec->subnet = work->subnet;
+
+ fstrcpy(servrec->serv.name,name);
+ fstrcpy(servrec->serv.comment,comment);
+ if (!strupper_m(servrec->serv.name)) {
+ DEBUG(2,("strupper_m %s failed\n", servrec->serv.name));
+ SAFE_FREE(servrec);
+ return NULL;
+ }
+ servrec->serv.type = servertype;
+
+ update_server_ttl(servrec, ttl);
+
+ add_server_to_workgroup(work, servrec);
+
+ DEBUG(3,("create_server_on_workgroup: Created server entry %s of type %x (%s) on \
+workgroup %s.\n", name,servertype,comment, work->work_group));
+
+ return(servrec);
+}
+
+/*******************************************************************
+ Update the ttl field of a server record.
+*******************************************************************/
+
+void update_server_ttl(struct server_record *servrec, int ttl)
+{
+ if(ttl > lp_max_ttl())
+ ttl = lp_max_ttl();
+
+ if(is_myname(servrec->serv.name))
+ servrec->death_time = PERMANENT_TTL;
+ else
+ servrec->death_time = (ttl != PERMANENT_TTL) ? time(NULL)+(ttl*3) : PERMANENT_TTL;
+}
+
+/*******************************************************************
+ Expire old servers in the serverlist. A time of -1 indicates
+ everybody dies except those with a death_time of PERMANENT_TTL (which is 0).
+ This should only be called from expire_workgroups_and_servers().
+ ******************************************************************/
+
+void expire_servers(struct work_record *work, time_t t)
+{
+ struct server_record *servrec;
+ struct server_record *nexts;
+
+ for (servrec = work->serverlist; servrec; servrec = nexts) {
+ nexts = servrec->next;
+
+ if ((servrec->death_time != PERMANENT_TTL) && ((t == -1) || (servrec->death_time < t))) {
+ DEBUG(3,("expire_old_servers: Removing timed out server %s\n",servrec->serv.name));
+ remove_server_from_workgroup(work, servrec);
+ }
+ }
+}
+
+/*******************************************************************
+ Decide if we should write out a server record for this server.
+ We return zero if we should not. Check if we've already written
+ out this server record from an earlier subnet.
+******************************************************************/
+
+static uint32_t write_this_server_name( struct subnet_record *subrec,
+ struct work_record *work,
+ struct server_record *servrec)
+{
+ struct subnet_record *ssub;
+ struct work_record *iwork;
+
+ /* Go through all the subnets we have already seen. */
+ for (ssub = FIRST_SUBNET; ssub && (ssub != subrec); ssub = NEXT_SUBNET_INCLUDING_UNICAST(ssub)) {
+ for(iwork = ssub->workgrouplist; iwork; iwork = iwork->next) {
+ if(find_server_in_workgroup( iwork, servrec->serv.name) != NULL) {
+ /*
+ * We have already written out this server record, don't
+ * do it again. This gives precedence to servers we have seen
+ * on the broadcast subnets over servers that may have been
+ * added via a sync on the unicast_subet.
+ *
+ * The correct way to do this is to have a serverlist file
+ * per subnet - this means changes to smbd as well. I may
+ * add this at a later date (JRA).
+ */
+
+ return 0;
+ }
+ }
+ }
+
+ return servrec->serv.type;
+}
+
+/*******************************************************************
+ Decide if we should write out a workgroup record for this workgroup.
+ We return zero if we should not. Don't write out lp_workgroup() (we've
+ already done it) and also don't write out a second workgroup record
+ on the unicast subnet that we've already written out on one of the
+ broadcast subnets.
+******************************************************************/
+
+static uint32_t write_this_workgroup_name( struct subnet_record *subrec,
+ struct work_record *work)
+{
+ struct subnet_record *ssub;
+
+ if(strequal(lp_workgroup(), work->work_group))
+ return 0;
+
+ /* This is a workgroup we have seen on a broadcast subnet. All
+ these have the same type. */
+
+ if(subrec != unicast_subnet)
+ return (SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT|SV_TYPE_LOCAL_LIST_ONLY);
+
+ for(ssub = FIRST_SUBNET; ssub; ssub = NEXT_SUBNET_EXCLUDING_UNICAST(ssub)) {
+ /* This is the unicast subnet so check if we've already written out
+ this subnet when we passed over the broadcast subnets. */
+
+ if(find_workgroup_on_subnet( ssub, work->work_group) != NULL)
+ return 0;
+ }
+
+ /* All workgroups on the unicast subnet (except our own, which we
+ have already written out) cannot be local. */
+
+ return (SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT);
+}
+
+/*******************************************************************
+ Write out the browse.dat file.
+ ******************************************************************/
+
+void write_browse_list_entry(FILE *fp, const char *name, uint32_t rec_type,
+ const char *local_master_browser_name, const char *description)
+{
+ fstring tmp;
+
+ slprintf(tmp,sizeof(tmp)-1, "\"%s\"", name);
+ fprintf(fp, "%-25s ", tmp);
+ fprintf(fp, "%08x ", rec_type);
+ slprintf(tmp, sizeof(tmp)-1, "\"%s\" ", local_master_browser_name);
+ fprintf(fp, "%-30s", tmp);
+ fprintf(fp, "\"%s\"\n", description);
+}
+
+void write_browse_list(time_t t, bool force_write)
+{
+ struct subnet_record *subrec;
+ struct work_record *work;
+ struct server_record *servrec;
+ char *fname;
+ char *fnamenew;
+ uint32_t stype;
+ int i;
+ int fd;
+ FILE *fp;
+ bool list_changed = force_write;
+ static time_t lasttime = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ /* Always dump if we're being told to by a signal. */
+ if(force_write == False) {
+ if (!lasttime)
+ lasttime = t;
+ if (t - lasttime < 5)
+ return;
+ }
+
+ lasttime = t;
+
+ dump_workgroups(force_write);
+
+ for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ if(subrec->work_changed) {
+ list_changed = True;
+ break;
+ }
+ }
+
+ if(!list_changed)
+ return;
+
+ updatecount++;
+
+ fname = cache_path(talloc_tos(), SERVER_LIST);
+ if (!fname) {
+ return;
+ }
+ fnamenew = talloc_asprintf(ctx, "%s.",
+ fname);
+ if (!fnamenew) {
+ talloc_free(fname);
+ return;
+ }
+
+ fd = open(fnamenew, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd == -1) {
+ DBG_ERR("Can't open file %s: %s\n", fnamenew,
+ strerror(errno));
+ talloc_free(fnamenew);
+ talloc_free(fname);
+ return;
+ }
+
+ fp = fdopen(fd, "w");
+ if (!fp) {
+ DBG_ERR("fdopen failed: %s\n", strerror(errno));
+ close(fd);
+ talloc_free(fnamenew);
+ talloc_free(fname);
+ return;
+ }
+ fd = -1;
+
+ /*
+ * Write out a record for our workgroup. Use the record from the first
+ * subnet.
+ */
+
+ if((work = find_workgroup_on_subnet(FIRST_SUBNET, lp_workgroup())) == NULL) {
+ DEBUG(0,("write_browse_list: Fatal error - cannot find my workgroup %s\n",
+ lp_workgroup()));
+ fclose(fp);
+ talloc_free(fnamenew);
+ talloc_free(fname);
+ return;
+ }
+
+ write_browse_list_entry(fp, work->work_group,
+ SV_TYPE_DOMAIN_ENUM|SV_TYPE_NT|SV_TYPE_LOCAL_LIST_ONLY,
+ work->local_master_browser_name, work->work_group);
+
+ /*
+ * We need to do something special for our own names.
+ * This is due to the fact that we may be a local master browser on
+ * one of our broadcast subnets, and a domain master on the unicast
+ * subnet. We iterate over the subnets and only write out the name
+ * once.
+ */
+
+ for (i=0; my_netbios_names(i); i++) {
+ stype = 0;
+ for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ if((work = find_workgroup_on_subnet( subrec, lp_workgroup() )) == NULL)
+ continue;
+ if((servrec = find_server_in_workgroup( work, my_netbios_names(i))) == NULL)
+ continue;
+
+ stype |= servrec->serv.type;
+ }
+
+ /* Output server details, plus what workgroup they're in. */
+ write_browse_list_entry(fp, my_netbios_names(i), stype,
+ string_truncate(lp_server_string(talloc_tos(), lp_sub), MAX_SERVER_STRING_LENGTH), lp_workgroup());
+ }
+
+ for (subrec = FIRST_SUBNET; subrec ; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ subrec->work_changed = False;
+
+ for (work = subrec->workgrouplist; work ; work = work->next) {
+ /* Write out a workgroup record for a workgroup. */
+ uint32_t wg_type = write_this_workgroup_name( subrec, work);
+
+ if(wg_type) {
+ write_browse_list_entry(fp, work->work_group, wg_type,
+ work->local_master_browser_name,
+ work->work_group);
+ }
+
+ /* Now write out any server records a workgroup may have. */
+
+ for (servrec = work->serverlist; servrec ; servrec = servrec->next) {
+ uint32_t serv_type;
+
+ /* We have already written our names here. */
+ if(is_myname(servrec->serv.name))
+ continue;
+
+ serv_type = write_this_server_name(subrec, work, servrec);
+ if(serv_type) {
+ /* Output server details, plus what workgroup they're in. */
+ write_browse_list_entry(fp, servrec->serv.name, serv_type,
+ servrec->serv.comment, work->work_group);
+ }
+ }
+ }
+ }
+
+ fclose(fp);
+ unlink(fname);
+ chmod(fnamenew,0644);
+ rename(fnamenew,fname);
+ DEBUG(3,("write_browse_list: Wrote browse list into file %s\n",fname));
+ talloc_free(fnamenew);
+ talloc_free(fname);
+}
diff --git a/source3/nmbd/nmbd_subnetdb.c b/source3/nmbd/nmbd_subnetdb.c
new file mode 100644
index 0000000..b2bcedd
--- /dev/null
+++ b/source3/nmbd/nmbd_subnetdb.c
@@ -0,0 +1,436 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Revision History:
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+extern int global_nmb_port;
+
+/* This is the broadcast subnets database. */
+struct subnet_record *subnetlist = NULL;
+
+/* Extra subnets - keep these separate so enumeration code doesn't
+ run onto it by mistake. */
+
+struct subnet_record *unicast_subnet = NULL;
+struct subnet_record *remote_broadcast_subnet = NULL;
+struct subnet_record *wins_server_subnet = NULL;
+
+extern uint16_t samba_nb_type; /* Samba's NetBIOS name type. */
+
+/****************************************************************************
+ Add a subnet into the list.
+ **************************************************************************/
+
+static void add_subnet(struct subnet_record *subrec)
+{
+ DLIST_ADD(subnetlist, subrec);
+}
+
+/****************************************************************************
+stop listening on a subnet
+we don't free the record as we don't have proper reference counting for it
+yet and it may be in use by a response record
+ ****************************************************************************/
+
+void close_subnet(struct subnet_record *subrec)
+{
+ if (subrec->nmb_sock != -1) {
+ close(subrec->nmb_sock);
+ subrec->nmb_sock = -1;
+ }
+ if (subrec->nmb_bcast != -1) {
+ close(subrec->nmb_bcast);
+ subrec->nmb_bcast = -1;
+ }
+ if (subrec->dgram_sock != -1) {
+ close(subrec->dgram_sock);
+ subrec->dgram_sock = -1;
+ }
+ if (subrec->dgram_bcast != -1) {
+ close(subrec->dgram_bcast);
+ subrec->dgram_bcast = -1;
+ }
+
+ DLIST_REMOVE(subnetlist, subrec);
+}
+
+/****************************************************************************
+ Create a subnet entry.
+ ****************************************************************************/
+
+static struct subnet_record *make_subnet(const char *name, enum subnet_type type,
+ struct in_addr myip, struct in_addr bcast_ip,
+ struct in_addr mask_ip)
+{
+ struct subnet_record *subrec = NULL;
+ int nmb_sock = -1;
+ int dgram_sock = -1;
+ int nmb_bcast = -1;
+ int dgram_bcast = -1;
+ bool bind_bcast = lp_nmbd_bind_explicit_broadcast();
+
+ /* Check if we are creating a non broadcast subnet - if so don't create
+ sockets. */
+
+ if (type == NORMAL_SUBNET) {
+ struct sockaddr_storage ss;
+ struct sockaddr_storage ss_bcast;
+
+ in_addr_to_sockaddr_storage(&ss, myip);
+ in_addr_to_sockaddr_storage(&ss_bcast, bcast_ip);
+
+ /*
+ * Attempt to open the sockets on port 137/138 for this interface
+ * and bind them.
+ * Fail the subnet creation if this fails.
+ */
+
+ nmb_sock = open_socket_in(
+ SOCK_DGRAM, &ss, global_nmb_port, true);
+ if (nmb_sock < 0) {
+ DBG_ERR("Failed to open nmb socket on interface %s "
+ "for port %d: %s\n",
+ inet_ntoa(myip),
+ global_nmb_port,
+ strerror(-nmb_sock));
+ goto failed;
+ }
+ set_socket_options(nmb_sock,"SO_BROADCAST");
+ set_blocking(nmb_sock, false);
+
+ if (bind_bcast) {
+ nmb_bcast = open_socket_in(
+ SOCK_DGRAM, &ss_bcast, global_nmb_port, true);
+ if (nmb_bcast < 0) {
+ DBG_ERR("Failed to open nmb bcast socket on "
+ "interface %s for port %d: %s\n",
+ inet_ntoa(myip),
+ global_nmb_port,
+ strerror(-nmb_bcast));
+ goto failed;
+ }
+ set_socket_options(nmb_bcast, "SO_BROADCAST");
+ set_blocking(nmb_bcast, false);
+ }
+
+ dgram_sock = open_socket_in(SOCK_DGRAM, &ss, DGRAM_PORT, true);
+ if (dgram_sock < 0) {
+ DBG_ERR("Failed to open dgram socket on "
+ "interface %s for port %d: %s\n",
+ inet_ntoa(myip),
+ DGRAM_PORT,
+ strerror(-dgram_sock));
+ goto failed;
+ }
+ set_socket_options(dgram_sock, "SO_BROADCAST");
+ set_blocking(dgram_sock, false);
+
+ if (bind_bcast) {
+ dgram_bcast = open_socket_in(
+ SOCK_DGRAM, &ss_bcast, DGRAM_PORT, true);
+ if (dgram_bcast < 0) {
+ DBG_ERR("Failed to open dgram bcast socket on "
+ "interface %s for port %d: %s\n",
+ inet_ntoa(myip),
+ DGRAM_PORT,
+ strerror(-dgram_bcast));
+ goto failed;
+ }
+ set_socket_options(dgram_bcast, "SO_BROADCAST");
+ set_blocking(dgram_bcast, false);
+ }
+ }
+
+ subrec = SMB_MALLOC_P(struct subnet_record);
+ if (!subrec) {
+ DEBUG(0,("make_subnet: malloc fail !\n"));
+ goto failed;
+ }
+
+ ZERO_STRUCTP(subrec);
+
+ if((subrec->subnet_name = SMB_STRDUP(name)) == NULL) {
+ DEBUG(0,("make_subnet: malloc fail for subnet name !\n"));
+ goto failed;
+ }
+
+ DEBUG(2, ("making subnet name:%s ", name ));
+ DEBUG(2, ("Broadcast address:%s ", inet_ntoa(bcast_ip)));
+ DEBUG(2, ("Subnet mask:%s\n", inet_ntoa(mask_ip)));
+
+ subrec->namelist_changed = False;
+ subrec->work_changed = False;
+
+ subrec->bcast_ip = bcast_ip;
+ subrec->mask_ip = mask_ip;
+ subrec->myip = myip;
+ subrec->type = type;
+ subrec->nmb_sock = nmb_sock;
+ subrec->nmb_bcast = nmb_bcast;
+ subrec->dgram_sock = dgram_sock;
+ subrec->dgram_bcast = dgram_bcast;
+
+ return subrec;
+
+failed:
+ SAFE_FREE(subrec);
+ if (nmb_sock >= 0) {
+ close(nmb_sock);
+ }
+ if (nmb_bcast >= 0) {
+ close(nmb_bcast);
+ }
+ if (dgram_sock >= 0) {
+ close(dgram_sock);
+ }
+ if (dgram_bcast >= 0) {
+ close(dgram_bcast);
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Create a normal subnet
+**************************************************************************/
+
+struct subnet_record *make_normal_subnet(const struct interface *iface)
+{
+
+ struct subnet_record *subrec;
+ const struct in_addr *pip = &((const struct sockaddr_in *)&iface->ip)->sin_addr;
+ const struct in_addr *pbcast = &((const struct sockaddr_in *)&iface->bcast)->sin_addr;
+ const struct in_addr *pnmask = &((const struct sockaddr_in *)&iface->netmask)->sin_addr;
+
+ subrec = make_subnet(inet_ntoa(*pip), NORMAL_SUBNET,
+ *pip, *pbcast, *pnmask);
+ if (subrec) {
+ add_subnet(subrec);
+ }
+ return subrec;
+}
+
+/****************************************************************************
+ Create subnet entries.
+**************************************************************************/
+
+bool create_subnets(void)
+{
+ /* We only count IPv4 interfaces whilst we're waiting. */
+ int num_interfaces;
+ int i;
+ struct in_addr unicast_ip, ipzero;
+
+ try_interfaces_again:
+
+ /* Only count IPv4, non-loopback interfaces. */
+ if (iface_count_v4_nl() == 0) {
+ daemon_status("nmbd",
+ "No local IPv4 non-loopback interfaces "
+ "available, waiting for interface ...");
+ DEBUG(0,("NOTE: NetBIOS name resolution is not supported for "
+ "Internet Protocol Version 6 (IPv6).\n"));
+ }
+
+ /* We only count IPv4, non-loopback interfaces here. */
+ while (iface_count_v4_nl() == 0) {
+ void (*saved_handler)(int);
+
+ /*
+ * Whilst we're waiting for an interface, allow SIGTERM to
+ * cause us to exit.
+ */
+
+ saved_handler = CatchSignal(SIGTERM, SIG_DFL);
+
+ usleep(NMBD_WAIT_INTERFACES_TIME_USEC);
+ load_interfaces();
+
+ /*
+ * We got an interface, restore our normal term handler.
+ */
+
+ CatchSignal(SIGTERM, saved_handler);
+ }
+
+ /*
+ * Here we count v4 and v6 - we know there's at least one
+ * IPv4 interface and we filter on it below.
+ */
+ num_interfaces = iface_count();
+
+ /*
+ * Create subnets from all the local interfaces and thread them onto
+ * the linked list.
+ */
+
+ for (i = 0 ; i < num_interfaces; i++) {
+ const struct interface *iface = get_interface(i);
+
+ if (!iface) {
+ DEBUG(2,("create_subnets: can't get interface %d.\n", i ));
+ continue;
+ }
+
+ /* Ensure we're only dealing with IPv4 here. */
+ if (iface->ip.ss_family != AF_INET) {
+ DEBUG(2,("create_subnets: "
+ "ignoring non IPv4 interface.\n"));
+ continue;
+ }
+
+ /*
+ * We don't want to add a loopback interface, in case
+ * someone has added 127.0.0.1 for smbd, nmbd needs to
+ * ignore it here. JRA.
+ */
+
+ if (is_loopback_addr((const struct sockaddr *)&iface->ip)) {
+ DEBUG(2,("create_subnets: Ignoring loopback interface.\n" ));
+ continue;
+ }
+
+ if (!make_normal_subnet(iface))
+ return False;
+ }
+
+ /* We must have at least one subnet. */
+ if (subnetlist == NULL) {
+ void (*saved_handler)(int);
+
+ DEBUG(0,("create_subnets: Unable to create any subnet from "
+ "given interfaces. Is your interface line in "
+ "smb.conf correct ?\n"));
+
+ saved_handler = CatchSignal(SIGTERM, SIG_DFL);
+
+ usleep(NMBD_WAIT_INTERFACES_TIME_USEC);
+ load_interfaces();
+
+ CatchSignal(SIGTERM, saved_handler);
+ goto try_interfaces_again;
+ }
+
+ if (lp_we_are_a_wins_server()) {
+ /* Pick the first interface IPv4 address as the WINS server
+ * ip. */
+ const struct in_addr *nip = first_ipv4_iface();
+
+ if (!nip) {
+ return False;
+ }
+
+ unicast_ip = *nip;
+ } else {
+ /* note that we do not set the wins server IP here. We just
+ set it at zero and let the wins registration code cope
+ with getting the IPs right for each packet */
+ zero_ip_v4(&unicast_ip);
+ }
+
+ /*
+ * Create the unicast and remote broadcast subnets.
+ * Don't put these onto the linked list.
+ * The ip address of the unicast subnet is set to be
+ * the WINS server address, if it exists, or ipzero if not.
+ */
+
+ unicast_subnet = make_subnet( "UNICAST_SUBNET", UNICAST_SUBNET,
+ unicast_ip, unicast_ip, unicast_ip);
+
+ zero_ip_v4(&ipzero);
+
+ remote_broadcast_subnet = make_subnet( "REMOTE_BROADCAST_SUBNET",
+ REMOTE_BROADCAST_SUBNET,
+ ipzero, ipzero, ipzero);
+
+ if((unicast_subnet == NULL) || (remote_broadcast_subnet == NULL))
+ return False;
+
+ /*
+ * If we are WINS server, create the WINS_SERVER_SUBNET - don't put on
+ * the linked list.
+ */
+
+ if (lp_we_are_a_wins_server()) {
+ if( (wins_server_subnet = make_subnet( "WINS_SERVER_SUBNET",
+ WINS_SERVER_SUBNET,
+ ipzero, ipzero, ipzero )) == NULL )
+ return False;
+ }
+
+ return True;
+}
+
+/*******************************************************************
+Function to tell us if we can use the unicast subnet.
+******************************************************************/
+
+bool we_are_a_wins_client(void)
+{
+ if (wins_srv_count() > 0) {
+ return True;
+ }
+
+ return False;
+}
+
+/*******************************************************************
+Access function used by NEXT_SUBNET_INCLUDING_UNICAST
+******************************************************************/
+
+struct subnet_record *get_next_subnet_maybe_unicast(struct subnet_record *subrec)
+{
+ if(subrec == unicast_subnet)
+ return NULL;
+ else if((subrec->next == NULL) && we_are_a_wins_client())
+ return unicast_subnet;
+ else
+ return subrec->next;
+}
+
+/*******************************************************************
+ Access function used by retransmit_or_expire_response_records() in
+ nmbd_packets.c. Patch from Andrey Alekseyev <fetch@muffin.arcadia.spb.ru>
+ Needed when we need to enumerate all the broadcast, unicast and
+ WINS subnets.
+******************************************************************/
+
+struct subnet_record *get_next_subnet_maybe_unicast_or_wins_server(struct subnet_record *subrec)
+{
+ if(subrec == unicast_subnet) {
+ if(wins_server_subnet)
+ return wins_server_subnet;
+ else
+ return NULL;
+ }
+
+ if(wins_server_subnet && subrec == wins_server_subnet)
+ return NULL;
+
+ if((subrec->next == NULL) && we_are_a_wins_client())
+ return unicast_subnet;
+ else
+ return subrec->next;
+}
diff --git a/source3/nmbd/nmbd_synclists.c b/source3/nmbd/nmbd_synclists.c
new file mode 100644
index 0000000..0f5c42f
--- /dev/null
+++ b/source3/nmbd/nmbd_synclists.c
@@ -0,0 +1,325 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* this file handles asynchronous browse synchronisation requests. The
+ requests are done by forking and putting the result in a file in the
+ locks directory. We do it this way because we don't want nmbd to be
+ blocked waiting for some server to respond on a TCP connection. This
+ also allows us to have more than 1 sync going at once (tridge) */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "nmbd/nmbd.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+struct sync_record {
+ struct sync_record *next, *prev;
+ unstring workgroup;
+ unstring server;
+ char *fname;
+ struct in_addr ip;
+ pid_t pid;
+};
+
+/* a linked list of current sync connections */
+static struct sync_record *syncs;
+
+static FILE *fp;
+
+/*******************************************************************
+ This is the NetServerEnum callback.
+ Note sname and comment are in UNIX codepage format.
+ ******************************************************************/
+
+static void callback(const char *sname, uint32_t stype,
+ const char *comment, void *state)
+{
+ fprintf(fp,"\"%s\" %08X \"%s\"\n", sname, stype, comment);
+}
+
+/*******************************************************************
+ Synchronise browse lists with another browse server.
+ Log in on the remote server's SMB port to their IPC$ service,
+ do a NetServerEnum and record the results in fname
+******************************************************************/
+
+static void sync_child(char *name, int nm_type,
+ char *workgroup,
+ struct in_addr ip, bool local, bool servers,
+ char *fname)
+{
+ fstring unix_workgroup;
+ struct cli_state *cli;
+ uint32_t local_type = local ? SV_TYPE_LOCAL_LIST_ONLY : 0;
+ struct sockaddr_storage ss;
+ NTSTATUS status;
+
+ /* W2K DMB's return empty browse lists on port 445. Use 139.
+ * Patch from Andy Levine andyl@epicrealm.com.
+ */
+
+ in_addr_to_sockaddr_storage(&ss, ip);
+
+ status = cli_connect_nb(name, &ss, NBT_SMB_PORT, nm_type,
+ get_local_machine_name(), SMB_SIGNING_DEFAULT,
+ 0, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_CORE,
+ PROTOCOL_NT1,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(cli);
+ return;
+ }
+
+ status = cli_session_setup_anon(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ cli_shutdown(cli);
+ return;
+ }
+
+ if (!NT_STATUS_IS_OK(cli_tree_connect(cli, "IPC$", "IPC", NULL))) {
+ cli_shutdown(cli);
+ return;
+ }
+
+ /* All the cli_XX functions take UNIX character set. */
+ fstrcpy(unix_workgroup, cli->server_domain ? cli->server_domain : workgroup);
+
+ /* Fetch a workgroup list. */
+ cli_NetServerEnum(cli, unix_workgroup,
+ local_type|SV_TYPE_DOMAIN_ENUM,
+ callback, NULL);
+
+ /* Now fetch a server list. */
+ if (servers) {
+ fstrcpy(unix_workgroup, workgroup);
+ cli_NetServerEnum(cli, unix_workgroup,
+ local?SV_TYPE_LOCAL_LIST_ONLY:SV_TYPE_ALL,
+ callback, NULL);
+ }
+
+ cli_shutdown(cli);
+}
+
+/*******************************************************************
+ initialise a browse sync with another browse server. Log in on the
+ remote server's SMB port to their IPC$ service, do a NetServerEnum
+ and record the results
+******************************************************************/
+
+void sync_browse_lists(struct work_record *work,
+ char *name, int nm_type,
+ struct in_addr ip, bool local, bool servers)
+{
+ struct sync_record *s;
+ static int counter;
+ int fd;
+
+ /* Check we're not trying to sync with ourselves. This can
+ happen if we are a domain *and* a local master browser. */
+ if (ismyip_v4(ip)) {
+done:
+ return;
+ }
+
+ s = SMB_MALLOC_P(struct sync_record);
+ if (!s) goto done;
+
+ ZERO_STRUCTP(s);
+
+ unstrcpy(s->workgroup, work->work_group);
+ unstrcpy(s->server, name);
+ s->ip = ip;
+
+ if (asprintf(&s->fname, "%s/sync.%d", lp_lock_directory(), counter++) < 0) {
+ SAFE_FREE(s);
+ goto done;
+ }
+ /* Safe to use as 0 means no size change. */
+ all_string_sub(s->fname,"//", "/", 0);
+
+ DLIST_ADD(syncs, s);
+
+ /* the parent forks and returns, leaving the child to do the
+ actual sync */
+ CatchChild();
+ if ((s->pid = fork())) return;
+
+ BlockSignals( False, SIGTERM );
+
+ DEBUG(2,("Initiating browse sync for %s to %s(%s)\n",
+ work->work_group, name, inet_ntoa(ip)));
+
+ fd = open(s->fname, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd == -1) {
+ _exit(1);
+ }
+
+ fp = fdopen(fd, "w");
+ if (!fp) {
+ _exit(1);
+ }
+ fd = -1;
+
+ sync_child(name, nm_type, work->work_group, ip, local, servers,
+ s->fname);
+
+ fclose(fp);
+ _exit(0);
+}
+
+/**********************************************************************
+ Handle one line from a completed sync file.
+ **********************************************************************/
+
+static void complete_one(struct sync_record *s,
+ char *sname, uint32_t stype, char *comment)
+{
+ struct work_record *work;
+ struct server_record *servrec;
+
+ stype &= ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ if (stype & SV_TYPE_DOMAIN_ENUM) {
+ /* See if we can find the workgroup on this subnet. */
+ if((work=find_workgroup_on_subnet(unicast_subnet, sname))) {
+ /* We already know about this workgroup -
+ update the ttl. */
+ update_workgroup_ttl(work,lp_max_ttl());
+ } else {
+ /* Create the workgroup on the subnet. */
+ work = create_workgroup_on_subnet(unicast_subnet,
+ sname, lp_max_ttl());
+ if (work) {
+ /* remember who the master is */
+ unstrcpy(work->local_master_browser_name, comment);
+ }
+ }
+ return;
+ }
+
+ work = find_workgroup_on_subnet(unicast_subnet, s->workgroup);
+ if (!work) {
+ DEBUG(3,("workgroup %s doesn't exist on unicast subnet?\n",
+ s->workgroup));
+ return;
+ }
+
+ if ((servrec = find_server_in_workgroup( work, sname))) {
+ /* Check that this is not a locally known
+ server - if so ignore the entry. */
+ if(!(servrec->serv.type & SV_TYPE_LOCAL_LIST_ONLY)) {
+ /* We already know about this server - update
+ the ttl. */
+ update_server_ttl(servrec, lp_max_ttl());
+ /* Update the type. */
+ servrec->serv.type = stype;
+ }
+ return;
+ }
+
+ /* Create the server in the workgroup. */
+ create_server_on_workgroup(work, sname,stype, lp_max_ttl(), comment);
+}
+
+/**********************************************************************
+ Read the completed sync info.
+**********************************************************************/
+
+static void complete_sync(struct sync_record *s)
+{
+ FILE *f;
+ char *server;
+ char *type_str;
+ unsigned type;
+ char *comment;
+ char line[1024];
+ const char *ptr;
+ int count=0;
+
+ f = fopen(s->fname, "r");
+
+ if (!f)
+ return;
+
+ while (!feof(f)) {
+ TALLOC_CTX *frame = NULL;
+
+ if (!fgets_slash(NULL, line, sizeof(line), f))
+ continue;
+
+ ptr = line;
+
+ frame = talloc_stackframe();
+ if (!next_token_talloc(frame,&ptr,&server,NULL) ||
+ !next_token_talloc(frame,&ptr,&type_str,NULL) ||
+ !next_token_talloc(frame,&ptr,&comment,NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ sscanf(type_str, "%X", &type);
+
+ complete_one(s, server, type, comment);
+
+ count++;
+ TALLOC_FREE(frame);
+ }
+ fclose(f);
+
+ unlink(s->fname);
+
+ DEBUG(2,("sync with %s(%s) for workgroup %s completed (%d records)\n",
+ s->server, inet_ntoa(s->ip), s->workgroup, count));
+}
+
+/**********************************************************************
+ Check for completion of any of the child processes.
+**********************************************************************/
+
+void sync_check_completion(void)
+{
+ struct sync_record *s, *next;
+
+ for (s=syncs;s;s=next) {
+ next = s->next;
+ if (!process_exists_by_pid(s->pid)) {
+ /* it has completed - grab the info */
+ complete_sync(s);
+ DLIST_REMOVE(syncs, s);
+ SAFE_FREE(s->fname);
+ SAFE_FREE(s);
+ }
+ }
+}
diff --git a/source3/nmbd/nmbd_winsproxy.c b/source3/nmbd/nmbd_winsproxy.c
new file mode 100644
index 0000000..163d2c0
--- /dev/null
+++ b/source3/nmbd/nmbd_winsproxy.c
@@ -0,0 +1,233 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "nmbd/nmbd.h"
+
+/****************************************************************************
+Function called when the name lookup succeeded.
+****************************************************************************/
+
+static void wins_proxy_name_query_request_success( struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *nmbname, struct in_addr ip, struct res_rec *rrec)
+{
+ unstring name;
+ struct packet_struct *original_packet;
+ struct subnet_record *orig_broadcast_subnet;
+ struct name_record *namerec = NULL;
+ uint16_t nb_flags;
+ int num_ips;
+ int i;
+ int ttl = 3600; /* By default one hour in the cache. */
+ struct in_addr *iplist;
+
+ /* Extract the original packet and the original broadcast subnet from
+ the userdata. */
+
+ memcpy( (char *)&orig_broadcast_subnet, userdata->data, sizeof(struct subnet_record *) );
+ memcpy( (char *)&original_packet, &userdata->data[sizeof(struct subnet_record *)],
+ sizeof(struct packet_struct *) );
+
+ if (rrec) {
+ nb_flags = get_nb_flags( rrec->rdata );
+ num_ips = rrec->rdlength / 6;
+ } else {
+ nb_flags = 0;
+ num_ips = 0;
+ }
+
+ if(num_ips == 0) {
+ DEBUG(0,("wins_proxy_name_query_request_success: Invalid number of IP records (0) \
+returned for name %s.\n", nmb_namestr(nmbname) ));
+ return;
+ }
+
+ if(num_ips == 1) {
+ iplist = &ip;
+ } else {
+ if((iplist = SMB_MALLOC_ARRAY( struct in_addr, num_ips )) == NULL) {
+ DEBUG(0,("wins_proxy_name_query_request_success: malloc fail !\n"));
+ return;
+ }
+
+ for(i = 0; i < num_ips; i++) {
+ putip( (char *)&iplist[i], (char *)&rrec->rdata[ (i*6) + 2]);
+ }
+ }
+
+ /* Add the queried name to the original subnet as a WINS_PROXY_NAME. */
+
+ if(rrec->ttl == PERMANENT_TTL) {
+ ttl = lp_max_ttl();
+ }
+
+ pull_ascii_nstring(name, sizeof(name), nmbname->name);
+ add_name_to_subnet( orig_broadcast_subnet, name,
+ nmbname->name_type, nb_flags, ttl,
+ WINS_PROXY_NAME, num_ips, iplist );
+
+ if(iplist != &ip) {
+ SAFE_FREE(iplist);
+ }
+
+ namerec = find_name_on_subnet(orig_broadcast_subnet, nmbname, FIND_ANY_NAME);
+ if (!namerec) {
+ DEBUG(0,("wins_proxy_name_query_request_success: failed to add "
+ "name %s to subnet %s !\n",
+ name,
+ orig_broadcast_subnet->subnet_name ));
+ return;
+ }
+
+ /*
+ * Check that none of the IP addresses we are returning is on the
+ * same broadcast subnet as the original requesting packet. If it
+ * is then don't reply (although we still need to add the name
+ * to the cache) as the actual machine will be replying also
+ * and we don't want two replies to a broadcast query.
+ */
+
+ if(namerec && original_packet->packet.nmb.header.nm_flags.bcast) {
+ for( i = 0; i < namerec->data.num_ips; i++) {
+ if( same_net_v4( namerec->data.ip[i], orig_broadcast_subnet->myip,
+ orig_broadcast_subnet->mask_ip ) ) {
+ DEBUG( 5, ( "wins_proxy_name_query_request_success: name %s is a WINS \
+proxy name and is also on the same subnet (%s) as the requester. \
+Not replying.\n", nmb_namestr(&namerec->name), orig_broadcast_subnet->subnet_name ) );
+ return;
+ }
+ }
+ }
+
+ /* Finally reply to the original name query. */
+ reply_netbios_packet(original_packet, /* Packet to reply to. */
+ 0, /* Result code. */
+ NMB_QUERY, /* nmbd type code. */
+ NMB_NAME_QUERY_OPCODE, /* opcode. */
+ ttl, /* ttl. */
+ rrec->rdata, /* data to send. */
+ rrec->rdlength); /* data length. */
+}
+
+/****************************************************************************
+Function called when the name lookup failed.
+****************************************************************************/
+
+static void wins_proxy_name_query_request_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *question_name, int fail_code)
+{
+ DEBUG(4,("wins_proxy_name_query_request_fail: WINS server returned error code %d for lookup \
+of name %s.\n", fail_code, nmb_namestr(question_name) ));
+}
+
+/****************************************************************************
+Function to make a deep copy of the userdata we will need when the WINS
+proxy query returns.
+****************************************************************************/
+
+static struct userdata_struct *wins_proxy_userdata_copy_fn(struct userdata_struct *userdata)
+{
+ struct packet_struct *p, *copy_of_p;
+ struct userdata_struct *new_userdata = (struct userdata_struct *)SMB_MALLOC( userdata->userdata_len );
+
+ if(new_userdata == NULL)
+ return NULL;
+
+ new_userdata->copy_fn = userdata->copy_fn;
+ new_userdata->free_fn = userdata->free_fn;
+ new_userdata->userdata_len = userdata->userdata_len;
+
+ /* Copy the subnet_record pointer. */
+ memcpy( new_userdata->data, userdata->data, sizeof(struct subnet_record *) );
+
+ /* Extract the pointer to the packet struct */
+ memcpy((char *)&p, &userdata->data[sizeof(struct subnet_record *)], sizeof(struct packet_struct *) );
+
+ /* Do a deep copy of the packet. */
+ if((copy_of_p = copy_packet(p)) == NULL) {
+ SAFE_FREE(new_userdata);
+ return NULL;
+ }
+
+ /* Lock the copy. */
+ copy_of_p->locked = True;
+
+ memcpy( &new_userdata->data[sizeof(struct subnet_record *)], (char *)&copy_of_p,
+ sizeof(struct packet_struct *) );
+
+ return new_userdata;
+}
+
+/****************************************************************************
+Function to free the deep copy of the userdata we used when the WINS
+proxy query returned.
+****************************************************************************/
+
+static void wins_proxy_userdata_free_fn(struct userdata_struct *userdata)
+{
+ struct packet_struct *p;
+
+ /* Extract the pointer to the packet struct */
+ memcpy((char *)&p, &userdata->data[sizeof(struct subnet_record *)],
+ sizeof(struct packet_struct *));
+
+ /* Unlock the packet. */
+ p->locked = False;
+
+ free_packet(p);
+ ZERO_STRUCTP(userdata);
+ SAFE_FREE(userdata);
+}
+
+/****************************************************************************
+ Make a WINS query on behalf of a broadcast client name query request.
+****************************************************************************/
+
+void make_wins_proxy_name_query_request( struct subnet_record *subrec,
+ struct packet_struct *incoming_packet,
+ struct nmb_name *question_name)
+{
+ union {
+ struct userdata_struct ud;
+ char c[sizeof(struct userdata_struct) + sizeof(struct subrec *) +
+ sizeof(struct packet_struct *)+sizeof(long*)];
+ } ud;
+ struct userdata_struct *userdata = &ud.ud;
+ unstring qname;
+
+ memset(&ud, '\0', sizeof(ud));
+
+ userdata->copy_fn = wins_proxy_userdata_copy_fn;
+ userdata->free_fn = wins_proxy_userdata_free_fn;
+ userdata->userdata_len = sizeof(ud);
+ memcpy( userdata->data, (char *)&subrec, sizeof(struct subnet_record *));
+ memcpy( &userdata->data[sizeof(struct subnet_record *)], (char *)&incoming_packet,
+ sizeof(struct packet_struct *));
+
+ /* Now use the unicast subnet to query the name with the WINS server. */
+ pull_ascii_nstring(qname, sizeof(qname), question_name->name);
+ query_name( unicast_subnet, qname, question_name->name_type,
+ wins_proxy_name_query_request_success,
+ wins_proxy_name_query_request_fail,
+ userdata);
+}
diff --git a/source3/nmbd/nmbd_winsserver.c b/source3/nmbd/nmbd_winsserver.c
new file mode 100644
index 0000000..ecae447
--- /dev/null
+++ b/source3/nmbd/nmbd_winsserver.c
@@ -0,0 +1,2706 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+
+ Copyright (C) Jeremy Allison 1994-2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Converted to store WINS data in a tdb. Dec 2005. JRA.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "nmbd/nmbd.h"
+#include "util_tdb.h"
+
+#define WINS_LIST "wins.dat"
+#define WINS_VERSION 1
+#define WINSDB_VERSION 1
+
+/****************************************************************************
+ We don't store the NetBIOS scope in the wins.tdb. We key off the (utf8) netbios
+ name (65 bytes with the last byte being the name type).
+*****************************************************************************/
+
+TDB_CONTEXT *wins_tdb;
+
+/****************************************************************************
+ Delete all the temporary name records on the in-memory linked list.
+*****************************************************************************/
+
+static void wins_delete_all_tmp_in_memory_records(void)
+{
+ struct name_record *nr = NULL;
+ struct name_record *nrnext = NULL;
+
+ /* Delete all temporary name records on the wins subnet linked list. */
+ for( nr = wins_server_subnet->namelist; nr; nr = nrnext) {
+ nrnext = nr->next;
+ DLIST_REMOVE(wins_server_subnet->namelist, nr);
+ SAFE_FREE(nr->data.ip);
+ SAFE_FREE(nr);
+ }
+}
+
+/****************************************************************************
+ Delete all the temporary 1b name records on the in-memory linked list.
+*****************************************************************************/
+
+static void wins_delete_all_1b_in_memory_records(void)
+{
+ struct name_record *nr = NULL;
+ struct name_record *nrnext = NULL;
+
+ /* Delete all temporary 1b name records on the wins subnet linked list. */
+ for( nr = wins_server_subnet->namelist; nr; nr = nrnext) {
+ nrnext = nr->next;
+ if (nr->name.name_type == 0x1b) {
+ DLIST_REMOVE(wins_server_subnet->namelist, nr);
+ SAFE_FREE(nr->data.ip);
+ SAFE_FREE(nr);
+ }
+ }
+}
+
+/****************************************************************************
+ Convert a wins.tdb record to a struct name_record. Add in our lp_netbios_scope().
+*****************************************************************************/
+
+static struct name_record *wins_record_to_name_record(TDB_DATA key, TDB_DATA data)
+{
+ struct name_record *namerec = NULL;
+ uint16_t nb_flags;
+ unsigned char nr_src;
+ uint32_t death_time, refresh_time;
+ uint32_t id_low, id_high;
+ uint32_t saddr;
+ uint32_t wins_flags;
+ uint32_t num_ips;
+ size_t len;
+ int i;
+
+ if (data.dptr == NULL || data.dsize == 0) {
+ return NULL;
+ }
+
+ /* Min size is "wbddddddd" + 1 ip address (4). */
+ if (data.dsize < 2 + 1 + (7*4) + 4) {
+ return NULL;
+ }
+
+ len = tdb_unpack(data.dptr, data.dsize,
+ "wbddddddd",
+ &nb_flags,
+ &nr_src,
+ &death_time,
+ &refresh_time,
+ &id_low,
+ &id_high,
+ &saddr,
+ &wins_flags,
+ &num_ips );
+
+ namerec = SMB_MALLOC_P(struct name_record);
+ if (!namerec) {
+ return NULL;
+ }
+ ZERO_STRUCTP(namerec);
+
+ namerec->data.ip = SMB_MALLOC_ARRAY(struct in_addr, num_ips);
+ if (!namerec->data.ip) {
+ SAFE_FREE(namerec);
+ return NULL;
+ }
+
+ namerec->subnet = wins_server_subnet;
+ push_ascii_nstring(namerec->name.name, (const char *)key.dptr);
+ namerec->name.name_type = key.dptr[sizeof(unstring)];
+ /* Add the scope. */
+ push_ascii(namerec->name.scope, lp_netbios_scope(), 64, STR_TERMINATE);
+
+ /* We're using a byte-by-byte compare, so we must be sure that
+ * unused space doesn't have garbage in it.
+ */
+
+ for( i = strlen( namerec->name.name ); i < sizeof( namerec->name.name ); i++ ) {
+ namerec->name.name[i] = '\0';
+ }
+ for( i = strlen( namerec->name.scope ); i < sizeof( namerec->name.scope ); i++ ) {
+ namerec->name.scope[i] = '\0';
+ }
+
+ namerec->data.nb_flags = nb_flags;
+ namerec->data.source = (enum name_source)nr_src;
+ namerec->data.death_time = (time_t)death_time;
+ namerec->data.refresh_time = (time_t)refresh_time;
+ namerec->data.id = id_low;
+ namerec->data.id |= ((uint64_t)id_high << 32);
+ namerec->data.wins_ip.s_addr = saddr;
+ namerec->data.wins_flags = wins_flags,
+ namerec->data.num_ips = num_ips;
+
+ for (i = 0; i < num_ips; i++) {
+ namerec->data.ip[i].s_addr = IVAL(data.dptr, len + (i*4));
+ }
+
+ return namerec;
+}
+
+/****************************************************************************
+ Convert a struct name_record to a wins.tdb record. Ignore the scope.
+*****************************************************************************/
+
+static TDB_DATA name_record_to_wins_record(const struct name_record *namerec)
+{
+ TDB_DATA data;
+ size_t len = 0;
+ int i;
+ uint32_t id_low = (namerec->data.id & 0xFFFFFFFF);
+ uint32_t id_high = (namerec->data.id >> 32) & 0xFFFFFFFF;
+
+ ZERO_STRUCT(data);
+
+ len = (2 + 1 + (7*4)); /* "wbddddddd" */
+ len += (namerec->data.num_ips * 4);
+
+ data.dptr = (uint8_t *)SMB_MALLOC(len);
+ if (!data.dptr) {
+ return data;
+ }
+ data.dsize = len;
+
+ len = tdb_pack(data.dptr, data.dsize, "wbddddddd",
+ namerec->data.nb_flags,
+ (unsigned char)namerec->data.source,
+ (uint32_t)namerec->data.death_time,
+ (uint32_t)namerec->data.refresh_time,
+ id_low,
+ id_high,
+ (uint32_t)namerec->data.wins_ip.s_addr,
+ (uint32_t)namerec->data.wins_flags,
+ (uint32_t)namerec->data.num_ips );
+
+ for (i = 0; i < namerec->data.num_ips; i++) {
+ SIVAL(data.dptr, len + (i*4), namerec->data.ip[i].s_addr);
+ }
+
+ return data;
+}
+
+/****************************************************************************
+ Create key. Key is UNIX codepage namestring (usually utf8 64 byte len) with 1 byte type.
+*****************************************************************************/
+
+static TDB_DATA name_to_key(const struct nmb_name *nmbname)
+{
+ static char keydata[sizeof(unstring) + 1];
+ TDB_DATA key;
+
+ memset(keydata, '\0', sizeof(keydata));
+
+ pull_ascii_nstring(keydata, sizeof(unstring), nmbname->name);
+ (void)strupper_m(keydata);
+ keydata[sizeof(unstring)] = nmbname->name_type;
+ key.dptr = (uint8_t *)keydata;
+ key.dsize = sizeof(keydata);
+
+ return key;
+}
+
+/****************************************************************************
+ Lookup a given name in the wins.tdb and create a temporary malloc'ed data struct
+ on the linked list. We will free this later in XXXX().
+*****************************************************************************/
+
+struct name_record *find_name_on_wins_subnet(const struct nmb_name *nmbname, bool self_only)
+{
+ TDB_DATA data, key;
+ struct name_record *nr = NULL;
+ struct name_record *namerec = NULL;
+
+ if (!wins_tdb) {
+ return NULL;
+ }
+
+ key = name_to_key(nmbname);
+ data = tdb_fetch(wins_tdb, key);
+
+ if (data.dsize == 0) {
+ return NULL;
+ }
+
+ namerec = wins_record_to_name_record(key, data);
+
+ /* done with the this */
+
+ SAFE_FREE( data.dptr );
+
+ if (!namerec) {
+ return NULL;
+ }
+
+ /* Self names only - these include permanent names. */
+ if( self_only && (namerec->data.source != SELF_NAME) && (namerec->data.source != PERMANENT_NAME) ) {
+ DEBUG( 9, ( "find_name_on_wins_subnet: self name %s NOT FOUND\n", nmb_namestr(nmbname) ) );
+ SAFE_FREE(namerec->data.ip);
+ SAFE_FREE(namerec);
+ return NULL;
+ }
+
+ /* Search for this name record on the list. Replace it if found. */
+
+ for( nr = wins_server_subnet->namelist; nr; nr = nr->next) {
+ if (memcmp(nmbname->name, nr->name.name, 16) == 0) {
+ /* Delete it. */
+ DLIST_REMOVE(wins_server_subnet->namelist, nr);
+ SAFE_FREE(nr->data.ip);
+ SAFE_FREE(nr);
+ break;
+ }
+ }
+
+ DLIST_ADD(wins_server_subnet->namelist, namerec);
+ return namerec;
+}
+
+/****************************************************************************
+ Overwrite or add a given name in the wins.tdb.
+*****************************************************************************/
+
+static bool store_or_replace_wins_namerec(const struct name_record *namerec, int tdb_flag)
+{
+ TDB_DATA key, data;
+ int ret;
+
+ if (!wins_tdb) {
+ return False;
+ }
+
+ key = name_to_key(&namerec->name);
+ data = name_record_to_wins_record(namerec);
+
+ if (data.dptr == NULL) {
+ return False;
+ }
+
+ ret = tdb_store(wins_tdb, key, data, tdb_flag);
+
+ SAFE_FREE(data.dptr);
+ return (ret == 0) ? True : False;
+}
+
+/****************************************************************************
+ Overwrite a given name in the wins.tdb.
+*****************************************************************************/
+
+bool wins_store_changed_namerec(const struct name_record *namerec)
+{
+ return store_or_replace_wins_namerec(namerec, TDB_REPLACE);
+}
+
+/****************************************************************************
+ Primary interface into creating and overwriting records in the wins.tdb.
+*****************************************************************************/
+
+bool add_name_to_wins_subnet(const struct name_record *namerec)
+{
+ return store_or_replace_wins_namerec(namerec, TDB_INSERT);
+}
+
+/****************************************************************************
+ Delete a given name in the tdb and remove the temporary malloc'ed data struct
+ on the linked list.
+*****************************************************************************/
+
+bool remove_name_from_wins_namelist(struct name_record *namerec)
+{
+ TDB_DATA key;
+ int ret;
+
+ if (!wins_tdb) {
+ return False;
+ }
+
+ key = name_to_key(&namerec->name);
+ ret = tdb_delete(wins_tdb, key);
+
+ DLIST_REMOVE(wins_server_subnet->namelist, namerec);
+
+ /* namerec must be freed by the caller */
+
+ return (ret == 0) ? True : False;
+}
+
+/****************************************************************************
+ Dump out the complete namelist.
+*****************************************************************************/
+
+static int traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state)
+{
+ struct name_record *namerec = NULL;
+ FILE *fp = (FILE *)state;
+
+ if (kbuf.dsize != sizeof(unstring) + 1) {
+ return 0;
+ }
+
+ namerec = wins_record_to_name_record(kbuf, dbuf);
+ if (!namerec) {
+ return 0;
+ }
+
+ dump_name_record(namerec, fp);
+
+ SAFE_FREE(namerec->data.ip);
+ SAFE_FREE(namerec);
+ return 0;
+}
+
+void dump_wins_subnet_namelist(FILE *fp)
+{
+ tdb_traverse(wins_tdb, traverse_fn, (void *)fp);
+}
+
+/****************************************************************************
+ Change the wins owner address in the record.
+*****************************************************************************/
+
+static void update_wins_owner(struct name_record *namerec, struct in_addr wins_ip)
+{
+ namerec->data.wins_ip=wins_ip;
+}
+
+/****************************************************************************
+ Create the wins flags based on the nb flags and the input value.
+*****************************************************************************/
+
+static void update_wins_flag(struct name_record *namerec, int flags)
+{
+ namerec->data.wins_flags=0x0;
+
+ /* if it's a group, it can be a normal or a special one */
+ if (namerec->data.nb_flags & NB_GROUP) {
+ if (namerec->name.name_type==0x1C) {
+ namerec->data.wins_flags|=WINS_SGROUP;
+ } else {
+ if (namerec->data.num_ips>1) {
+ namerec->data.wins_flags|=WINS_SGROUP;
+ } else {
+ namerec->data.wins_flags|=WINS_NGROUP;
+ }
+ }
+ } else {
+ /* can be unique or multi-homed */
+ if (namerec->data.num_ips>1) {
+ namerec->data.wins_flags|=WINS_MHOMED;
+ } else {
+ namerec->data.wins_flags|=WINS_UNIQUE;
+ }
+ }
+
+ /* the node type are the same bits */
+ namerec->data.wins_flags|=namerec->data.nb_flags&NB_NODETYPEMASK;
+
+ /* the static bit is elsewhere */
+ if (namerec->data.death_time == PERMANENT_TTL) {
+ namerec->data.wins_flags|=WINS_STATIC;
+ }
+
+ /* and add the given bits */
+ namerec->data.wins_flags|=flags;
+
+ DEBUG(8,("update_wins_flag: nbflags: 0x%x, ttl: %d, flags: 0x%x, winsflags: 0x%x\n",
+ namerec->data.nb_flags, (int)namerec->data.death_time, flags, namerec->data.wins_flags));
+}
+
+/****************************************************************************
+ Return the general ID value and increase it if requested.
+*****************************************************************************/
+
+static void get_global_id_and_update(uint64_t *current_id, bool update)
+{
+ /*
+ * it's kept as a static here, to prevent people from messing
+ * with the value directly
+ */
+
+ static uint64_t general_id = 1;
+
+ DEBUG(5,("get_global_id_and_update: updating version ID: %d\n", (int)general_id));
+
+ *current_id = general_id;
+
+ if (update) {
+ general_id++;
+ }
+}
+
+/****************************************************************************
+ Possibly call the WINS hook external program when a WINS change is made.
+ Also stores the changed record back in the wins_tdb.
+*****************************************************************************/
+
+static void wins_hook(const char *operation, struct name_record *namerec, int ttl)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *command = NULL;
+ char *cmd = lp_wins_hook(talloc_tos(), lp_sub);
+ char *p, *namestr;
+ int i;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ wins_store_changed_namerec(namerec);
+
+ if (!cmd || !*cmd) {
+ return;
+ }
+
+ for (p=namerec->name.name; *p; p++) {
+ if (!(isalnum((int)*p) || strchr_m("._-",*p))) {
+ DEBUG(3,("not calling wins hook for invalid name %s\n", nmb_namestr(&namerec->name)));
+ return;
+ }
+ }
+
+ /* Use the name without the nametype (and scope) appended */
+
+ namestr = nmb_namestr(&namerec->name);
+ if ((p = strchr(namestr, '<'))) {
+ *p = 0;
+ }
+
+ command = talloc_asprintf(ctx,
+ "%s %s %s %02x %d",
+ cmd,
+ operation,
+ namestr,
+ namerec->name.name_type,
+ ttl);
+ if (!command) {
+ return;
+ }
+
+ for (i=0;i<namerec->data.num_ips;i++) {
+ command = talloc_asprintf_append(command,
+ " %s",
+ inet_ntoa(namerec->data.ip[i]));
+ if (!command) {
+ return;
+ }
+ }
+
+ DEBUG(3,("calling wins hook for %s\n", nmb_namestr(&namerec->name)));
+ smbrun(command, NULL, NULL);
+ TALLOC_FREE(command);
+}
+
+/****************************************************************************
+Determine if this packet should be allocated to the WINS server.
+*****************************************************************************/
+
+bool packet_is_for_wins_server(struct packet_struct *packet)
+{
+ struct nmb_packet *nmb = &packet->packet.nmb;
+
+ /* Only unicast packets go to a WINS server. */
+ if((wins_server_subnet == NULL) || (nmb->header.nm_flags.bcast == True)) {
+ DEBUG(10, ("packet_is_for_wins_server: failing WINS test #1.\n"));
+ return False;
+ }
+
+ /* Check for node status requests. */
+ if (nmb->question.question_type != QUESTION_TYPE_NB_QUERY) {
+ return False;
+ }
+
+ switch(nmb->header.opcode) {
+ /*
+ * A WINS server issues WACKS, not receives them.
+ */
+ case NMB_WACK_OPCODE:
+ DEBUG(10, ("packet_is_for_wins_server: failing WINS test #2 (WACK).\n"));
+ return False;
+ /*
+ * A WINS server only processes registration and
+ * release requests, not responses.
+ */
+ case NMB_NAME_REG_OPCODE:
+ case NMB_NAME_MULTIHOMED_REG_OPCODE:
+ case NMB_NAME_REFRESH_OPCODE_8: /* ambiguity in rfc1002 about which is correct. */
+ case NMB_NAME_REFRESH_OPCODE_9: /* WinNT uses 8 by default. */
+ if(nmb->header.response) {
+ DEBUG(10, ("packet_is_for_wins_server: failing WINS test #3 (response = 1).\n"));
+ return False;
+ }
+ break;
+
+ case NMB_NAME_RELEASE_OPCODE:
+ if(nmb->header.response) {
+ DEBUG(10, ("packet_is_for_wins_server: failing WINS test #4 (response = 1).\n"));
+ return False;
+ }
+ break;
+
+ /*
+ * Only process unicast name queries with rd = 1.
+ */
+ case NMB_NAME_QUERY_OPCODE:
+ if(!nmb->header.response && !nmb->header.nm_flags.recursion_desired) {
+ DEBUG(10, ("packet_is_for_wins_server: failing WINS test #5 (response = 1).\n"));
+ return False;
+ }
+ break;
+ }
+
+ return True;
+}
+
+/****************************************************************************
+Utility function to decide what ttl to give a register/refresh request.
+*****************************************************************************/
+
+static int get_ttl_from_packet(struct nmb_packet *nmb)
+{
+ int ttl = nmb->additional->ttl;
+
+ if (ttl < lp_min_wins_ttl()) {
+ ttl = lp_min_wins_ttl();
+ }
+
+ if (ttl > lp_max_wins_ttl()) {
+ ttl = lp_max_wins_ttl();
+ }
+
+ return ttl;
+}
+
+/****************************************************************************
+Load or create the WINS database.
+*****************************************************************************/
+
+bool initialise_wins(void)
+{
+ time_t time_now = time(NULL);
+ FILE *fp;
+ char line[1024];
+ char *db_path;
+ char *list_path;
+
+ if(!lp_we_are_a_wins_server()) {
+ return True;
+ }
+
+ db_path = state_path(talloc_tos(), "wins.tdb");
+ if (db_path == NULL) {
+ return false;
+ }
+
+ /* Open the wins.tdb. */
+ wins_tdb = tdb_open_log(db_path, 0, TDB_DEFAULT|TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
+ O_CREAT|O_RDWR, 0600);
+ TALLOC_FREE(db_path);
+ if (!wins_tdb) {
+ DEBUG(0,("initialise_wins: failed to open wins.tdb. Error was %s\n",
+ strerror(errno) ));
+ return False;
+ }
+
+ tdb_store_int32(wins_tdb, "WINSDB_VERSION", WINSDB_VERSION);
+
+ add_samba_names_to_subnet(wins_server_subnet);
+
+ list_path = state_path(talloc_tos(), WINS_LIST);
+ if (list_path == NULL) {
+ tdb_close(wins_tdb);
+ return false;
+ }
+
+ fp = fopen(list_path, "r");
+ TALLOC_FREE(list_path);
+ if (fp == NULL) {
+ DEBUG(2,("initialise_wins: Can't open wins database file %s. Error was %s\n",
+ WINS_LIST, strerror(errno) ));
+ return True;
+ }
+
+ while (!feof(fp)) {
+ char *name_str = NULL;
+ char *ip_str = NULL;
+ char *ttl_str = NULL, *nb_flags_str = NULL;
+ unsigned int num_ips;
+ char *name = NULL;
+ struct in_addr *ip_list = NULL;
+ int type = 0;
+ int nb_flags;
+ int ttl;
+ const char *ptr;
+ char *p = NULL;
+ bool got_token;
+ bool was_ip;
+ int i;
+ unsigned int hash;
+ int version;
+ TALLOC_CTX *frame = NULL;
+
+ /* Read a line from the wins.dat file. Strips whitespace
+ from the beginning and end of the line. */
+ if (!fgets_slash(NULL, line, sizeof(line), fp)) {
+ continue;
+ }
+
+ if (*line == '#') {
+ continue;
+ }
+
+ if (strncmp(line,"VERSION ", 8) == 0) {
+ if (sscanf(line,"VERSION %d %u", &version, &hash) != 2 ||
+ version != WINS_VERSION) {
+ DEBUG(0,("Discarding invalid wins.dat file [%s]\n",line));
+ fclose(fp);
+ return True;
+ }
+ continue;
+ }
+
+ ptr = line;
+
+ /*
+ * Now we handle multiple IP addresses per name we need
+ * to iterate over the line twice. The first time to
+ * determine how many IP addresses there are, the second
+ * time to actually parse them into the ip_list array.
+ */
+
+ frame = talloc_stackframe();
+ if (!next_token_talloc(frame,&ptr,&name_str,NULL)) {
+ DEBUG(0,("initialise_wins: Failed to parse name when parsing line %s\n", line ));
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ if (!next_token_talloc(frame,&ptr,&ttl_str,NULL)) {
+ DEBUG(0,("initialise_wins: Failed to parse time to live when parsing line %s\n", line ));
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ /*
+ * Determine the number of IP addresses per line.
+ */
+ num_ips = 0;
+ do {
+ got_token = next_token_talloc(frame,&ptr,&ip_str,NULL);
+ was_ip = False;
+
+ if(got_token && strchr(ip_str, '.')) {
+ num_ips++;
+ was_ip = True;
+ }
+ } while(got_token && was_ip);
+
+ if(num_ips == 0) {
+ DEBUG(0,("initialise_wins: Missing IP address when parsing line %s\n", line ));
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ if(!got_token) {
+ DEBUG(0,("initialise_wins: Missing nb_flags when parsing line %s\n", line ));
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ /* Allocate the space for the ip_list. */
+ if((ip_list = SMB_MALLOC_ARRAY( struct in_addr, num_ips)) == NULL) {
+ DEBUG(0,("initialise_wins: Malloc fail !\n"));
+ fclose(fp);
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* Reset and re-parse the line. */
+ ptr = line;
+ next_token_talloc(frame,&ptr,&name_str,NULL);
+ next_token_talloc(frame,&ptr,&ttl_str,NULL);
+ for(i = 0; i < num_ips; i++) {
+ next_token_talloc(frame,&ptr, &ip_str, NULL);
+ ip_list[i] = interpret_addr2(ip_str);
+ }
+ next_token_talloc(frame,&ptr,&nb_flags_str,NULL);
+
+ /*
+ * Deal with SELF or REGISTER name encoding. Default is REGISTER
+ * for compatibility with old nmbds.
+ */
+
+ if(nb_flags_str[strlen(nb_flags_str)-1] == 'S') {
+ DEBUG(5,("initialise_wins: Ignoring SELF name %s\n", line));
+ SAFE_FREE(ip_list);
+ TALLOC_FREE(frame);
+ continue;
+ }
+
+ if(nb_flags_str[strlen(nb_flags_str)-1] == 'R') {
+ nb_flags_str[strlen(nb_flags_str)-1] = '\0';
+ }
+
+ /* Netbios name. # divides the name from the type (hex): netbios#xx */
+ name = name_str;
+
+ if((p = strchr(name,'#')) != NULL) {
+ *p = 0;
+ sscanf(p+1,"%x",&type);
+ }
+
+ /* Decode the netbios flags (hex) and the time-to-live (in seconds). */
+ sscanf(nb_flags_str,"%x",&nb_flags);
+ sscanf(ttl_str,"%d",&ttl);
+
+ /* add all entries that have 60 seconds or more to live */
+ if ((ttl - 60) > time_now || ttl == PERMANENT_TTL) {
+ if(ttl != PERMANENT_TTL) {
+ ttl -= time_now;
+ }
+
+ DEBUG( 4, ("initialise_wins: add name: %s#%02x ttl = %d first IP %s flags = %2x\n",
+ name, type, ttl, inet_ntoa(ip_list[0]), nb_flags));
+
+ (void)add_name_to_subnet( wins_server_subnet, name, type, nb_flags,
+ ttl, REGISTER_NAME, num_ips, ip_list );
+ } else {
+ DEBUG(4, ("initialise_wins: not adding name (ttl problem) "
+ "%s#%02x ttl = %d first IP %s flags = %2x\n",
+ name, type, ttl, inet_ntoa(ip_list[0]), nb_flags));
+ }
+
+ TALLOC_FREE(frame);
+ SAFE_FREE(ip_list);
+ }
+
+ fclose(fp);
+ return True;
+}
+
+/****************************************************************************
+Send a WINS WACK (Wait ACKnowledgement) response.
+**************************************************************************/
+
+static void send_wins_wack_response(int ttl, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ unsigned char rdata[2];
+
+ rdata[0] = rdata[1] = 0;
+
+ /* Taken from nmblib.c - we need to send back almost
+ identical bytes from the requesting packet header. */
+
+ rdata[0] = (nmb->header.opcode & 0xF) << 3;
+ if (nmb->header.nm_flags.authoritative && nmb->header.response) {
+ rdata[0] |= 0x4;
+ }
+ if (nmb->header.nm_flags.trunc) {
+ rdata[0] |= 0x2;
+ }
+ if (nmb->header.nm_flags.recursion_desired) {
+ rdata[0] |= 0x1;
+ }
+ if (nmb->header.nm_flags.recursion_available && nmb->header.response) {
+ rdata[1] |= 0x80;
+ }
+ if (nmb->header.nm_flags.bcast) {
+ rdata[1] |= 0x10;
+ }
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ 0, /* Result code. */
+ NMB_WAIT_ACK, /* nmbd type code. */
+ NMB_WACK_OPCODE, /* opcode. */
+ ttl, /* ttl. */
+ (char *)rdata, /* data to send. */
+ 2); /* data length. */
+}
+
+/****************************************************************************
+Send a WINS name registration response.
+**************************************************************************/
+
+static void send_wins_name_registration_response(int rcode, int ttl, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ char rdata[6];
+
+ memcpy(&rdata[0], &nmb->additional->rdata[0], 6);
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ rcode, /* Result code. */
+ WINS_REG, /* nmbd type code. */
+ NMB_NAME_REG_OPCODE, /* opcode. */
+ ttl, /* ttl. */
+ rdata, /* data to send. */
+ 6); /* data length. */
+}
+
+/***********************************************************************
+ Deal with a name refresh request to a WINS server.
+************************************************************************/
+
+void wins_process_name_refresh_request( struct subnet_record *subrec,
+ struct packet_struct *p )
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ bool bcast = nmb->header.nm_flags.bcast;
+ uint16_t nb_flags = get_nb_flags(nmb->additional->rdata);
+ bool group = (nb_flags & NB_GROUP) ? True : False;
+ struct name_record *namerec = NULL;
+ int ttl = get_ttl_from_packet(nmb);
+ struct in_addr from_ip;
+ struct in_addr our_fake_ip;
+
+ our_fake_ip = interpret_addr2("0.0.0.0");
+ putip( (char *)&from_ip, &nmb->additional->rdata[2] );
+
+ if(bcast) {
+ /*
+ * We should only get unicast name refresh packets here.
+ * Anyone trying to refresh broadcast should not be going
+ * to a WINS server. Log an error here.
+ */
+ if( DEBUGLVL( 0 ) ) {
+ dbgtext( "wins_process_name_refresh_request: " );
+ dbgtext( "Broadcast name refresh request received " );
+ dbgtext( "for name %s ", nmb_namestr(question) );
+ dbgtext( "from IP %s ", inet_ntoa(from_ip) );
+ dbgtext( "on subnet %s. ", subrec->subnet_name );
+ dbgtext( "Error - Broadcasts should not be sent " );
+ dbgtext( "to a WINS server\n" );
+ }
+ return;
+ }
+
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "wins_process_name_refresh_request: " );
+ dbgtext( "Name refresh for name %s IP %s\n",
+ nmb_namestr(question), inet_ntoa(from_ip) );
+ }
+
+ /*
+ * See if the name already exists.
+ * If not, handle it as a name registration and return.
+ */
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ /*
+ * If this is a refresh request and the name doesn't exist then
+ * treat it like a registration request. This allows us to recover
+ * from errors (tridge)
+ */
+ if(namerec == NULL) {
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "wins_process_name_refresh_request: " );
+ dbgtext( "Name refresh for name %s ",
+ nmb_namestr( question ) );
+ dbgtext( "and the name does not exist. Treating " );
+ dbgtext( "as registration.\n" );
+ }
+ wins_process_name_registration_request(subrec,p);
+ return;
+ }
+
+ /*
+ * if the name is present but not active, simply remove it
+ * and treat the refresh request as a registration & return.
+ */
+ if (namerec != NULL && !WINS_STATE_ACTIVE(namerec)) {
+ if( DEBUGLVL( 5 ) ) {
+ dbgtext( "wins_process_name_refresh_request: " );
+ dbgtext( "Name (%s) in WINS ", nmb_namestr(question) );
+ dbgtext( "was not active - removing it.\n" );
+ }
+ remove_name_from_namelist( subrec, namerec );
+ namerec = NULL;
+ wins_process_name_registration_request( subrec, p );
+ return;
+ }
+
+ /*
+ * Check that the group bits for the refreshing name and the
+ * name in our database match. If not, refuse the refresh.
+ * [crh: Why RFS_ERR instead of ACT_ERR? Is this what MS does?]
+ */
+ if( (namerec != NULL) &&
+ ( (group && !NAME_GROUP(namerec))
+ || (!group && NAME_GROUP(namerec)) ) ) {
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "wins_process_name_refresh_request: " );
+ dbgtext( "Name %s ", nmb_namestr(question) );
+ dbgtext( "group bit = %s does not match ",
+ group ? "True" : "False" );
+ dbgtext( "group bit in WINS for this name.\n" );
+ }
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ }
+
+ /*
+ * For a unique name check that the person refreshing the name is
+ * one of the registered IP addresses. If not - fail the refresh.
+ * Do the same for group names with a type of 0x1c.
+ * Just return success for unique 0x1d refreshes. For normal group
+ * names update the ttl and return success.
+ */
+ if( (!group || (group && (question->name_type == 0x1c)))
+ && find_ip_in_name_record(namerec, from_ip) ) {
+ /*
+ * Update the ttl.
+ */
+ update_name_ttl(namerec, ttl);
+
+ /*
+ * if the record is a replica:
+ * we take ownership and update the version ID.
+ */
+ if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) {
+ update_wins_owner(namerec, our_fake_ip);
+ get_global_id_and_update(&namerec->data.id, True);
+ }
+
+ send_wins_name_registration_response(0, ttl, p);
+ wins_hook("refresh", namerec, ttl);
+ return;
+ } else if((group && (question->name_type == 0x1c))) {
+ /*
+ * Added by crh for bug #1079.
+ * Fix from Bert Driehuis
+ */
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "wins_process_name_refresh_request: " );
+ dbgtext( "Name refresh for name %s, ",
+ nmb_namestr(question) );
+ dbgtext( "but IP address %s ", inet_ntoa(from_ip) );
+ dbgtext( "is not yet associated with " );
+ dbgtext( "that name. Treating as registration.\n" );
+ }
+ wins_process_name_registration_request(subrec,p);
+ return;
+ } else if(group) {
+ /*
+ * Normal groups are all registered with an IP address of
+ * 255.255.255.255 so we can't search for the IP address.
+ */
+ update_name_ttl(namerec, ttl);
+ wins_hook("refresh", namerec, ttl);
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ } else if(!group && (question->name_type == 0x1d)) {
+ /*
+ * Special name type - just pretend the refresh succeeded.
+ */
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ } else {
+ /*
+ * Fail the refresh.
+ */
+ if( DEBUGLVL( 3 ) ) {
+ dbgtext( "wins_process_name_refresh_request: " );
+ dbgtext( "Name refresh for name %s with IP %s ",
+ nmb_namestr(question), inet_ntoa(from_ip) );
+ dbgtext( "and is IP is not known to the name.\n" );
+ }
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ }
+}
+
+/***********************************************************************
+ Deal with a name registration request query success to a client that
+ owned the name.
+
+ We have a locked pointer to the original packet stashed away in the
+ userdata pointer. The success here is actually a failure as it means
+ the client we queried wants to keep the name, so we must return
+ a registration failure to the original requester.
+************************************************************************/
+
+static void wins_register_query_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *question_name,
+ struct in_addr ip,
+ struct res_rec *answers)
+{
+ struct packet_struct *orig_reg_packet;
+
+ memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *));
+
+ DEBUG(3,("wins_register_query_success: Original client at IP %s still wants the \
+name %s. Rejecting registration request.\n", inet_ntoa(ip), nmb_namestr(question_name) ));
+
+ send_wins_name_registration_response(ACT_ERR, 0, orig_reg_packet);
+
+ orig_reg_packet->locked = False;
+ free_packet(orig_reg_packet);
+}
+
+/***********************************************************************
+ Deal with a name registration request query failure to a client that
+ owned the name.
+
+ We have a locked pointer to the original packet stashed away in the
+ userdata pointer. The failure here is actually a success as it means
+ the client we queried didn't want to keep the name, so we can remove
+ the old name record and then successfully add the new name.
+************************************************************************/
+
+static void wins_register_query_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *question_name,
+ int rcode)
+{
+ struct userdata_struct *userdata = rrec->userdata;
+ struct packet_struct *orig_reg_packet;
+ struct name_record *namerec = NULL;
+
+ memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *));
+
+ /*
+ * We want to just add the name, as we now know the original owner
+ * didn't want it. But we can't just do that as an arbitrary
+ * amount of time may have taken place between the name query
+ * request and this timeout/error response. So we check that
+ * the name still exists and is in the same state - if so
+ * we remove it and call wins_process_name_registration_request()
+ * as we know it will do the right thing now.
+ */
+
+ namerec = find_name_on_subnet(subrec, question_name, FIND_ANY_NAME);
+
+ if ((namerec != NULL) && (namerec->data.source == REGISTER_NAME) &&
+ ip_equal_v4(rrec->packet->ip, *namerec->data.ip)) {
+ remove_name_from_namelist( subrec, namerec);
+ namerec = NULL;
+ }
+
+ if(namerec == NULL) {
+ wins_process_name_registration_request(subrec, orig_reg_packet);
+ } else {
+ DEBUG(2,("wins_register_query_fail: The state of the WINS database changed between "
+ "querying for name %s in order to replace it and this reply.\n",
+ nmb_namestr(question_name) ));
+ }
+
+ orig_reg_packet->locked = False;
+ free_packet(orig_reg_packet);
+}
+
+/***********************************************************************
+ Deal with a name registration request to a WINS server.
+
+ Use the following pseudocode :
+
+ registering_group
+ |
+ |
+ +--------name exists
+ | |
+ | |
+ | +--- existing name is group
+ | | |
+ | | |
+ | | +--- add name (return).
+ | |
+ | |
+ | +--- exiting name is unique
+ | |
+ | |
+ | +--- query existing owner (return).
+ |
+ |
+ +--------name doesn't exist
+ |
+ |
+ +--- add name (return).
+
+ registering_unique
+ |
+ |
+ +--------name exists
+ | |
+ | |
+ | +--- existing name is group
+ | | |
+ | | |
+ | | +--- fail add (return).
+ | |
+ | |
+ | +--- exiting name is unique
+ | |
+ | |
+ | +--- query existing owner (return).
+ |
+ |
+ +--------name doesn't exist
+ |
+ |
+ +--- add name (return).
+
+ As can be seen from the above, the two cases may be collapsed onto each
+ other with the exception of the case where the name already exists and
+ is a group name. This case we handle with an if statement.
+
+************************************************************************/
+
+void wins_process_name_registration_request(struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ unstring name;
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ bool bcast = nmb->header.nm_flags.bcast;
+ uint16_t nb_flags = get_nb_flags(nmb->additional->rdata);
+ int ttl = get_ttl_from_packet(nmb);
+ struct name_record *namerec = NULL;
+ struct in_addr from_ip;
+ bool registering_group_name = (nb_flags & NB_GROUP) ? True : False;
+ struct in_addr our_fake_ip;
+
+ our_fake_ip = interpret_addr2("0.0.0.0");
+ putip((char *)&from_ip,&nmb->additional->rdata[2]);
+
+ if(bcast) {
+ /*
+ * We should only get unicast name registration packets here.
+ * Anyone trying to register broadcast should not be going to a WINS
+ * server. Log an error here.
+ */
+
+ DEBUG(0,("wins_process_name_registration_request: broadcast name registration request \
+received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n",
+ nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+ return;
+ }
+
+ DEBUG(3,("wins_process_name_registration_request: %s name registration for name %s \
+IP %s\n", registering_group_name ? "Group" : "Unique", nmb_namestr(question), inet_ntoa(from_ip) ));
+
+ /*
+ * See if the name already exists.
+ */
+
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ /*
+ * if the record exists but NOT in active state,
+ * consider it dead.
+ */
+ if ( (namerec != NULL) && !WINS_STATE_ACTIVE(namerec)) {
+ DEBUG(5,("wins_process_name_registration_request: Name (%s) in WINS was \
+not active - removing it.\n", nmb_namestr(question) ));
+ remove_name_from_namelist( subrec, namerec );
+ namerec = NULL;
+ }
+
+ /*
+ * Deal with the case where the name found was a dns entry.
+ * Remove it as we now have a NetBIOS client registering the
+ * name.
+ */
+
+ if( (namerec != NULL) && ( (namerec->data.source == DNS_NAME) || (namerec->data.source == DNSFAIL_NAME) ) ) {
+ DEBUG(5,("wins_process_name_registration_request: Name (%s) in WINS was \
+a dns lookup - removing it.\n", nmb_namestr(question) ));
+ remove_name_from_namelist( subrec, namerec );
+ namerec = NULL;
+ }
+
+ /*
+ * Reject if the name exists and is not a REGISTER_NAME.
+ * (ie. Don't allow any static names to be overwritten.
+ */
+
+ if((namerec != NULL) && (namerec->data.source != REGISTER_NAME)) {
+ DEBUG( 3, ( "wins_process_name_registration_request: Attempt \
+to register name %s. Name already exists in WINS with source type %d.\n",
+ nmb_namestr(question), namerec->data.source ));
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ }
+
+ /*
+ * Special policy decisions based on MS documentation.
+ * 1). All group names (except names ending in 0x1c) are added as 255.255.255.255.
+ * 2). All unique names ending in 0x1d are ignored, although a positive response is sent.
+ */
+
+ /*
+ * A group name is always added as the local broadcast address, except
+ * for group names ending in 0x1c.
+ * Group names with type 0x1c are registered with individual IP addresses.
+ */
+
+ if(registering_group_name && (question->name_type != 0x1c)) {
+ from_ip = interpret_addr2("255.255.255.255");
+ }
+
+ /*
+ * Ignore all attempts to register a unique 0x1d name, although return success.
+ */
+
+ if(!registering_group_name && (question->name_type == 0x1d)) {
+ DEBUG(3,("wins_process_name_registration_request: Ignoring request \
+to register name %s from IP %s.\n", nmb_namestr(question), inet_ntoa(p->ip) ));
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ }
+
+ /*
+ * Next two cases are the 'if statement' mentioned above.
+ */
+
+ if((namerec != NULL) && NAME_GROUP(namerec)) {
+ if(registering_group_name) {
+ /*
+ * If we are adding a group name, the name exists and is also a group entry just add this
+ * IP address to it and update the ttl.
+ */
+
+ DEBUG(3,("wins_process_name_registration_request: Adding IP %s to group name %s.\n",
+ inet_ntoa(from_ip), nmb_namestr(question) ));
+
+ /*
+ * Check the ip address is not already in the group.
+ */
+
+ if(!find_ip_in_name_record(namerec, from_ip)) {
+ /*
+ * Need to emulate the behaviour of Windows, as
+ * described in:
+ * http://lists.samba.org/archive/samba-technical/2001-October/016236.html
+ * (is there an MS reference for this
+ * somewhere?) because if the 1c list gets over
+ * 86 entries, the reply packet is too big
+ * (rdata>576 bytes) so no reply is sent.
+ *
+ * Keep only the "latest" 25 records, while
+ * ensuring that the PDC (0x1b) is never removed
+ * We do this by removing the first entry that
+ * isn't the 1b entry for the same name,
+ * on the grounds that insertion is at the end
+ * of the list, so the oldest entries are at
+ * the start.
+ *
+ */
+ while(namerec->data.num_ips>=25) {
+ struct name_record *name1brec = NULL;
+
+ /* We only do this for 1c types. */
+ if (namerec->name.name_type != 0x1c) {
+ break;
+ }
+ DEBUG(3,("wins_process_name_registration_request: "
+ "More than 25 IPs already in "
+ "the list. Looking for a 1b "
+ "record\n"));
+
+ /* Ensure we have all the active 1b
+ * names on the list. */
+ wins_delete_all_1b_in_memory_records();
+ fetch_all_active_wins_1b_names();
+
+ /* Per the above, find the 1b record,
+ and then remove the first IP that isn't the same */
+ for(name1brec = subrec->namelist;
+ name1brec;
+ name1brec = name1brec->next ) {
+ if( WINS_STATE_ACTIVE(name1brec) &&
+ name1brec->name.name_type == 0x1b) {
+ DEBUG(3,("wins_process_name_registration_request: "
+ "Found the #1b record "
+ "with ip %s\n",
+ inet_ntoa(name1brec->data.ip[0])));
+ break;
+ }
+ }
+ if(!name1brec) {
+ DEBUG(3,("wins_process_name_registration_request: "
+ "Didn't find a #1b name record. "
+ "Removing the first available "
+ "entry %s\n",
+ inet_ntoa(namerec->data.ip[0])));
+ remove_ip_from_name_record(namerec, namerec->data.ip[0]);
+ wins_hook("delete", namerec, 0);
+ } else {
+ int i;
+ for(i=0; i<namerec->data.num_ips; i++) {
+ /* The name1brec should only have
+ * the single IP address in it,
+ * so we only check against the first one*/
+ if(!ip_equal_v4( namerec->data.ip[i], name1brec->data.ip[0])) {
+ /* The i'th entry isn't the 1b address; delete it */
+ DEBUG(3,("wins_process_name_registration_request: "
+ "Entry at %d is not the #1b address. "
+ "About to remove it\n",
+ i));
+ remove_ip_from_name_record(namerec, namerec->data.ip[i]);
+ wins_hook("delete", namerec, 0);
+ break;
+ }
+ }
+ }
+ }
+ /* The list is guaranteed to be < 25 entries now
+ * - safe to add a new one */
+ add_ip_to_name_record(namerec, from_ip);
+ /* we need to update the record for replication */
+ get_global_id_and_update(&namerec->data.id, True);
+
+ /*
+ * if the record is a replica, we must change
+ * the wins owner to us to make the replication updates
+ * it on the other wins servers.
+ * And when the partner will receive this record,
+ * it will update its own record.
+ */
+
+ update_wins_owner(namerec, our_fake_ip);
+ }
+ update_name_ttl(namerec, ttl);
+ wins_hook("refresh", namerec, ttl);
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ } else {
+
+ /*
+ * If we are adding a unique name, the name exists in the WINS db
+ * and is a group name then reject the registration.
+ *
+ * explanation: groups have a higher priority than unique names.
+ */
+
+ DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \
+already exists in WINS as a GROUP name.\n", nmb_namestr(question) ));
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ }
+ }
+
+ /*
+ * From here on down we know that if the name exists in the WINS db it is
+ * a unique name, not a group name.
+ */
+
+ /*
+ * If the name exists and is one of our names then check the
+ * registering IP address. If it's not one of ours then automatically
+ * reject without doing the query - we know we will reject it.
+ */
+
+ if ( namerec != NULL ) {
+ pull_ascii_nstring(name, sizeof(name), namerec->name.name);
+ if( is_myname(name) ) {
+ if(!ismyip_v4(from_ip)) {
+ DEBUG(3,("wins_process_name_registration_request: Attempt to register name %s. Name \
+is one of our (WINS server) names. Denying registration.\n", nmb_namestr(question) ));
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ } else {
+ /*
+ * It's one of our names and one of our IP's - update the ttl.
+ */
+ update_name_ttl(namerec, ttl);
+ wins_hook("refresh", namerec, ttl);
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ }
+ }
+ } else {
+ name[0] = '\0';
+ }
+
+ /*
+ * If the name exists and it is a unique registration and the registering IP
+ * is the same as the (single) already registered IP then just update the ttl.
+ *
+ * But not if the record is an active replica. IF it's a replica, it means it can be
+ * the same client which has moved and not yet expired. So we don't update
+ * the ttl in this case and go beyond to do a WACK and query the old client
+ */
+
+ if( !registering_group_name
+ && (namerec != NULL)
+ && (namerec->data.num_ips == 1)
+ && ip_equal_v4( namerec->data.ip[0], from_ip )
+ && ip_equal_v4(namerec->data.wins_ip, our_fake_ip) ) {
+ update_name_ttl( namerec, ttl );
+ wins_hook("refresh", namerec, ttl);
+ send_wins_name_registration_response( 0, ttl, p );
+ return;
+ }
+
+ /*
+ * Finally if the name exists do a query to the registering machine
+ * to see if they still claim to have the name.
+ */
+
+ if( namerec != NULL ) {
+ long *ud[(sizeof(struct userdata_struct) + sizeof(struct packet_struct *))/sizeof(long *) + 1];
+ struct userdata_struct *userdata = (struct userdata_struct *)ud;
+
+ /*
+ * First send a WACK to the registering machine.
+ */
+
+ send_wins_wack_response(60, p);
+
+ /*
+ * When the reply comes back we need the original packet.
+ * Lock this so it won't be freed and then put it into
+ * the userdata structure.
+ */
+
+ p->locked = True;
+
+ userdata = (struct userdata_struct *)ud;
+
+ userdata->copy_fn = NULL;
+ userdata->free_fn = NULL;
+ userdata->userdata_len = sizeof(struct packet_struct *);
+ memcpy(userdata->data, (char *)&p, sizeof(struct packet_struct *) );
+
+ /*
+ * Use the new call to send a query directly to an IP address.
+ * This sends the query directly to the IP address, and ensures
+ * the recursion desired flag is not set (you were right Luke :-).
+ * This function should *only* be called from the WINS server
+ * code. JRA.
+ */
+
+ pull_ascii_nstring(name, sizeof(name), question->name);
+ query_name_from_wins_server( *namerec->data.ip,
+ name,
+ question->name_type,
+ wins_register_query_success,
+ wins_register_query_fail,
+ userdata );
+ return;
+ }
+
+ /*
+ * Name did not exist - add it.
+ */
+
+ pull_ascii_nstring(name, sizeof(name), question->name);
+ add_name_to_subnet( subrec, name, question->name_type,
+ nb_flags, ttl, REGISTER_NAME, 1, &from_ip);
+
+ if ((namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME))) {
+ get_global_id_and_update(&namerec->data.id, True);
+ update_wins_owner(namerec, our_fake_ip);
+ update_wins_flag(namerec, WINS_ACTIVE);
+ wins_hook("add", namerec, ttl);
+ }
+
+ send_wins_name_registration_response(0, ttl, p);
+}
+
+/***********************************************************************
+ Deal with a mutihomed name query success to the machine that
+ requested the multihomed name registration.
+
+ We have a locked pointer to the original packet stashed away in the
+ userdata pointer.
+************************************************************************/
+
+static void wins_multihomed_register_query_success(struct subnet_record *subrec,
+ struct userdata_struct *userdata,
+ struct nmb_name *question_name,
+ struct in_addr ip,
+ struct res_rec *answers)
+{
+ struct packet_struct *orig_reg_packet;
+ struct nmb_packet *nmb;
+ struct name_record *namerec = NULL;
+ struct in_addr from_ip;
+ int ttl;
+ struct in_addr our_fake_ip;
+
+ our_fake_ip = interpret_addr2("0.0.0.0");
+ memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *));
+
+ nmb = &orig_reg_packet->packet.nmb;
+
+ putip((char *)&from_ip,&nmb->additional->rdata[2]);
+ ttl = get_ttl_from_packet(nmb);
+
+ /*
+ * We want to just add the new IP, as we now know the requesting
+ * machine claims to own it. But we can't just do that as an arbitrary
+ * amount of time may have taken place between the name query
+ * request and this response. So we check that
+ * the name still exists and is in the same state - if so
+ * we just add the extra IP and update the ttl.
+ */
+
+ namerec = find_name_on_subnet(subrec, question_name, FIND_ANY_NAME);
+
+ if( (namerec == NULL) || (namerec->data.source != REGISTER_NAME) || !WINS_STATE_ACTIVE(namerec) ) {
+ DEBUG(3,("wins_multihomed_register_query_success: name %s is not in the correct state to add \
+a subsequent IP address.\n", nmb_namestr(question_name) ));
+ send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet);
+
+ orig_reg_packet->locked = False;
+ free_packet(orig_reg_packet);
+
+ return;
+ }
+
+ if(!find_ip_in_name_record(namerec, from_ip)) {
+ add_ip_to_name_record(namerec, from_ip);
+ }
+
+ get_global_id_and_update(&namerec->data.id, True);
+ update_wins_owner(namerec, our_fake_ip);
+ update_wins_flag(namerec, WINS_ACTIVE);
+ update_name_ttl(namerec, ttl);
+ wins_hook("add", namerec, ttl);
+ send_wins_name_registration_response(0, ttl, orig_reg_packet);
+
+ orig_reg_packet->locked = False;
+ free_packet(orig_reg_packet);
+}
+
+/***********************************************************************
+ Deal with a name registration request query failure to a client that
+ owned the name.
+
+ We have a locked pointer to the original packet stashed away in the
+ userdata pointer.
+************************************************************************/
+
+static void wins_multihomed_register_query_fail(struct subnet_record *subrec,
+ struct response_record *rrec,
+ struct nmb_name *question_name,
+ int rcode)
+{
+ struct userdata_struct *userdata = rrec->userdata;
+ struct packet_struct *orig_reg_packet;
+
+ memcpy((char *)&orig_reg_packet, userdata->data, sizeof(struct packet_struct *));
+
+ DEBUG(3,("wins_multihomed_register_query_fail: Registering machine at IP %s failed to answer \
+query successfully for name %s.\n", inet_ntoa(orig_reg_packet->ip), nmb_namestr(question_name) ));
+ send_wins_name_registration_response(RFS_ERR, 0, orig_reg_packet);
+
+ orig_reg_packet->locked = False;
+ free_packet(orig_reg_packet);
+ return;
+}
+
+/***********************************************************************
+ Deal with a multihomed name registration request to a WINS server.
+ These cannot be group name registrations.
+***********************************************************************/
+
+void wins_process_multihomed_name_registration_request( struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ bool bcast = nmb->header.nm_flags.bcast;
+ uint16_t nb_flags = get_nb_flags(nmb->additional->rdata);
+ int ttl = get_ttl_from_packet(nmb);
+ struct name_record *namerec = NULL;
+ struct in_addr from_ip;
+ bool group = (nb_flags & NB_GROUP) ? True : False;
+ struct in_addr our_fake_ip;
+ unstring qname;
+
+ our_fake_ip = interpret_addr2("0.0.0.0");
+ putip((char *)&from_ip,&nmb->additional->rdata[2]);
+
+ if(bcast) {
+ /*
+ * We should only get unicast name registration packets here.
+ * Anyone trying to register broadcast should not be going to a WINS
+ * server. Log an error here.
+ */
+
+ DEBUG(0,("wins_process_multihomed_name_registration_request: broadcast name registration request \
+received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n",
+ nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+ return;
+ }
+
+ /*
+ * Only unique names should be registered multihomed.
+ */
+
+ if(group) {
+ DEBUG(0,("wins_process_multihomed_name_registration_request: group name registration request \
+received for name %s from IP %s on subnet %s. Error - group names should not be multihomed.\n",
+ nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+ return;
+ }
+
+ DEBUG(3,("wins_process_multihomed_name_registration_request: name registration for name %s \
+IP %s\n", nmb_namestr(question), inet_ntoa(from_ip) ));
+
+ /*
+ * Deal with policy regarding 0x1d names.
+ */
+
+ if(question->name_type == 0x1d) {
+ DEBUG(3,("wins_process_multihomed_name_registration_request: Ignoring request \
+to register name %s from IP %s.\n", nmb_namestr(question), inet_ntoa(p->ip) ));
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ }
+
+ /*
+ * See if the name already exists.
+ */
+
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ /*
+ * if the record exists but NOT in active state,
+ * consider it dead.
+ */
+
+ if ((namerec != NULL) && !WINS_STATE_ACTIVE(namerec)) {
+ DEBUG(5,("wins_process_multihomed_name_registration_request: Name (%s) in WINS was not active - removing it.\n", nmb_namestr(question)));
+ remove_name_from_namelist(subrec, namerec);
+ namerec = NULL;
+ }
+
+ /*
+ * Deal with the case where the name found was a dns entry.
+ * Remove it as we now have a NetBIOS client registering the
+ * name.
+ */
+
+ if( (namerec != NULL) && ( (namerec->data.source == DNS_NAME) || (namerec->data.source == DNSFAIL_NAME) ) ) {
+ DEBUG(5,("wins_process_multihomed_name_registration_request: Name (%s) in WINS was a dns lookup \
+- removing it.\n", nmb_namestr(question) ));
+ remove_name_from_namelist( subrec, namerec);
+ namerec = NULL;
+ }
+
+ /*
+ * Reject if the name exists and is not a REGISTER_NAME.
+ * (ie. Don't allow any static names to be overwritten.
+ */
+
+ if( (namerec != NULL) && (namerec->data.source != REGISTER_NAME) ) {
+ DEBUG( 3, ( "wins_process_multihomed_name_registration_request: Attempt \
+to register name %s. Name already exists in WINS with source type %d.\n",
+ nmb_namestr(question), namerec->data.source ));
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ }
+
+ /*
+ * Reject if the name exists and is a GROUP name and is active.
+ */
+
+ if((namerec != NULL) && NAME_GROUP(namerec) && WINS_STATE_ACTIVE(namerec)) {
+ DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \
+already exists in WINS as a GROUP name.\n", nmb_namestr(question) ));
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ }
+
+ /*
+ * From here on down we know that if the name exists in the WINS db it is
+ * a unique name, not a group name.
+ */
+
+ /*
+ * If the name exists and is one of our names then check the
+ * registering IP address. If it's not one of ours then automatically
+ * reject without doing the query - we know we will reject it.
+ */
+
+ if((namerec != NULL) && (is_myname(namerec->name.name)) ) {
+ if(!ismyip_v4(from_ip)) {
+ DEBUG(3,("wins_process_multihomed_name_registration_request: Attempt to register name %s. Name \
+is one of our (WINS server) names. Denying registration.\n", nmb_namestr(question) ));
+ send_wins_name_registration_response(RFS_ERR, 0, p);
+ return;
+ } else {
+ /*
+ * It's one of our names and one of our IP's. Ensure the IP is in the record and
+ * update the ttl. Update the version ID to force replication.
+ */
+ update_name_ttl(namerec, ttl);
+
+ if(!find_ip_in_name_record(namerec, from_ip)) {
+ get_global_id_and_update(&namerec->data.id, True);
+ update_wins_owner(namerec, our_fake_ip);
+ update_wins_flag(namerec, WINS_ACTIVE);
+
+ add_ip_to_name_record(namerec, from_ip);
+ }
+
+ wins_hook("refresh", namerec, ttl);
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ }
+ }
+
+ /*
+ * If the name exists and is active, check if the IP address is already registered
+ * to that name. If so then update the ttl and reply success.
+ */
+
+ if((namerec != NULL) && find_ip_in_name_record(namerec, from_ip) && WINS_STATE_ACTIVE(namerec)) {
+ update_name_ttl(namerec, ttl);
+
+ /*
+ * If it's a replica, we need to become the wins owner
+ * to force the replication
+ */
+ if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) {
+ get_global_id_and_update(&namerec->data.id, True);
+ update_wins_owner(namerec, our_fake_ip);
+ update_wins_flag(namerec, WINS_ACTIVE);
+ }
+
+ wins_hook("refresh", namerec, ttl);
+ send_wins_name_registration_response(0, ttl, p);
+ return;
+ }
+
+ /*
+ * If the name exists do a query to the owner
+ * to see if they still want the name.
+ */
+
+ if(namerec != NULL) {
+ long *ud[(sizeof(struct userdata_struct) + sizeof(struct packet_struct *))/sizeof(long *) + 1];
+ struct userdata_struct *userdata = (struct userdata_struct *)ud;
+
+ /*
+ * First send a WACK to the registering machine.
+ */
+
+ send_wins_wack_response(60, p);
+
+ /*
+ * When the reply comes back we need the original packet.
+ * Lock this so it won't be freed and then put it into
+ * the userdata structure.
+ */
+
+ p->locked = True;
+
+ userdata = (struct userdata_struct *)ud;
+
+ userdata->copy_fn = NULL;
+ userdata->free_fn = NULL;
+ userdata->userdata_len = sizeof(struct packet_struct *);
+ memcpy(userdata->data, (char *)&p, sizeof(struct packet_struct *) );
+
+ /*
+ * Use the new call to send a query directly to an IP address.
+ * This sends the query directly to the IP address, and ensures
+ * the recursion desired flag is not set (you were right Luke :-).
+ * This function should *only* be called from the WINS server
+ * code. JRA.
+ *
+ * Note that this packet is sent to the current owner of the name,
+ * not the person who sent the packet
+ */
+
+ pull_ascii_nstring( qname, sizeof(qname), question->name);
+ query_name_from_wins_server( namerec->data.ip[0],
+ qname,
+ question->name_type,
+ wins_multihomed_register_query_success,
+ wins_multihomed_register_query_fail,
+ userdata );
+
+ return;
+ }
+
+ /*
+ * Name did not exist - add it.
+ */
+
+ pull_ascii_nstring( qname, sizeof(qname), question->name);
+ add_name_to_subnet( subrec, qname, question->name_type,
+ nb_flags, ttl, REGISTER_NAME, 1, &from_ip);
+
+ if ((namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME))) {
+ get_global_id_and_update(&namerec->data.id, True);
+ update_wins_owner(namerec, our_fake_ip);
+ update_wins_flag(namerec, WINS_ACTIVE);
+ wins_hook("add", namerec, ttl);
+ }
+
+ send_wins_name_registration_response(0, ttl, p);
+}
+
+/***********************************************************************
+ Fetch all *<1b> names from the WINS db and store on the namelist.
+***********************************************************************/
+
+static int fetch_1b_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state)
+{
+ struct name_record *namerec = NULL;
+
+ if (kbuf.dsize != sizeof(unstring) + 1) {
+ return 0;
+ }
+
+ /* Filter out all non-1b names. */
+ if (kbuf.dptr[sizeof(unstring)] != 0x1b) {
+ return 0;
+ }
+
+ namerec = wins_record_to_name_record(kbuf, dbuf);
+ if (!namerec) {
+ return 0;
+ }
+
+ DLIST_ADD(wins_server_subnet->namelist, namerec);
+ return 0;
+}
+
+void fetch_all_active_wins_1b_names(void)
+{
+ tdb_traverse(wins_tdb, fetch_1b_traverse_fn, NULL);
+}
+
+/***********************************************************************
+ Deal with the special name query for *<1b>.
+***********************************************************************/
+
+static void process_wins_dmb_query_request(struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ struct name_record *namerec = NULL;
+ char *prdata;
+ int num_ips;
+
+ /*
+ * Go through all the ACTIVE names in the WINS db looking for those
+ * ending in <1b>. Use this to calculate the number of IP
+ * addresses we need to return.
+ */
+
+ num_ips = 0;
+
+ /* First, clear the in memory list - we're going to re-populate
+ it with the tdb_traversal in fetch_all_active_wins_1b_names. */
+
+ wins_delete_all_tmp_in_memory_records();
+
+ fetch_all_active_wins_1b_names();
+
+ for( namerec = subrec->namelist; namerec; namerec = namerec->next ) {
+ if( WINS_STATE_ACTIVE(namerec) && namerec->name.name_type == 0x1b) {
+ num_ips += namerec->data.num_ips;
+ }
+ }
+
+ if(num_ips == 0) {
+ /*
+ * There are no 0x1b names registered. Return name query fail.
+ */
+ send_wins_name_query_response(NAM_ERR, p, NULL);
+ return;
+ }
+
+ if((prdata = (char *)SMB_MALLOC( num_ips * 6 )) == NULL) {
+ DEBUG(0,("process_wins_dmb_query_request: Malloc fail !.\n"));
+ return;
+ }
+
+ /*
+ * Go through all the names again in the WINS db looking for those
+ * ending in <1b>. Add their IP addresses into the list we will
+ * return.
+ */
+
+ num_ips = 0;
+ for( namerec = subrec->namelist; namerec; namerec = namerec->next ) {
+ if( WINS_STATE_ACTIVE(namerec) && namerec->name.name_type == 0x1b) {
+ int i;
+ for(i = 0; i < namerec->data.num_ips; i++) {
+ set_nb_flags(&prdata[num_ips * 6],namerec->data.nb_flags);
+ putip((char *)&prdata[(num_ips * 6) + 2], &namerec->data.ip[i]);
+ num_ips++;
+ }
+ }
+ }
+
+ /*
+ * Send back the reply containing the IP list.
+ */
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ 0, /* Result code. */
+ WINS_QUERY, /* nmbd type code. */
+ NMB_NAME_QUERY_OPCODE, /* opcode. */
+ lp_min_wins_ttl(), /* ttl. */
+ prdata, /* data to send. */
+ num_ips*6); /* data length. */
+
+ SAFE_FREE(prdata);
+}
+
+/****************************************************************************
+Send a WINS name query response.
+**************************************************************************/
+
+void send_wins_name_query_response(int rcode, struct packet_struct *p,
+ struct name_record *namerec)
+{
+ char rdata[6];
+ char *prdata = rdata;
+ int reply_data_len = 0;
+ int ttl = 0;
+ int i;
+
+ memset(rdata,'\0',6);
+
+ if(rcode == 0) {
+
+ int ip_count;
+
+ ttl = (namerec->data.death_time != PERMANENT_TTL) ? namerec->data.death_time - p->timestamp : lp_max_wins_ttl();
+
+ /* The netbios reply packet data section is limited to 576 bytes. In theory
+ * this should give us space for 96 addresses, but in practice, 86 appears
+ * to be the max (don't know why). If we send any more than that,
+ * reply_netbios_packet will fail to send a reply to avoid a memcpy buffer
+ * overflow. Keep the count to 85 and it will be ok */
+ ip_count=namerec->data.num_ips;
+ if(ip_count>85) {
+ ip_count=85;
+ }
+
+ /* Copy all known ip addresses into the return data. */
+ /* Optimise for the common case of one IP address so we don't need a malloc. */
+
+ if( ip_count == 1 ) {
+ prdata = rdata;
+ } else {
+ if((prdata = (char *)SMB_MALLOC( ip_count * 6 )) == NULL) {
+ DEBUG(0,("send_wins_name_query_response: malloc fail !\n"));
+ return;
+ }
+ }
+
+ for(i = 0; i < ip_count; i++) {
+ set_nb_flags(&prdata[i*6],namerec->data.nb_flags);
+ putip((char *)&prdata[2+(i*6)], &namerec->data.ip[i]);
+ }
+
+ sort_query_replies(prdata, i, p->ip);
+ reply_data_len = ip_count * 6;
+ }
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ rcode, /* Result code. */
+ WINS_QUERY, /* nmbd type code. */
+ NMB_NAME_QUERY_OPCODE, /* opcode. */
+ ttl, /* ttl. */
+ prdata, /* data to send. */
+ reply_data_len); /* data length. */
+
+ if(prdata != rdata) {
+ SAFE_FREE(prdata);
+ }
+}
+
+/***********************************************************************
+ Deal with a name query.
+***********************************************************************/
+
+void wins_process_name_query_request(struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ struct name_record *namerec = NULL;
+ unstring qname;
+
+ DEBUG(3,("wins_process_name_query: name query for name %s from IP %s\n",
+ nmb_namestr(question), inet_ntoa(p->ip) ));
+
+ /*
+ * Special name code. If the queried name is *<1b> then search
+ * the entire WINS database and return a list of all the IP addresses
+ * registered to any <1b> name. This is to allow domain master browsers
+ * to discover other domains that may not have a presence on their subnet.
+ */
+
+ pull_ascii_nstring(qname, sizeof(qname), question->name);
+ if(strequal( qname, "*") && (question->name_type == 0x1b)) {
+ process_wins_dmb_query_request( subrec, p);
+ return;
+ }
+
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ if(namerec != NULL) {
+ /*
+ * If the name is not anymore in active state then reply not found.
+ * it's fair even if we keep it in the cache for days.
+ */
+ if (!WINS_STATE_ACTIVE(namerec)) {
+ DEBUG(3,("wins_process_name_query: name query for name %s - name expired. Returning fail.\n",
+ nmb_namestr(question) ));
+ send_wins_name_query_response(NAM_ERR, p, namerec);
+ return;
+ }
+
+ /*
+ * If it's a DNSFAIL_NAME then reply name not found.
+ */
+
+ if( namerec->data.source == DNSFAIL_NAME ) {
+ DEBUG(3,("wins_process_name_query: name query for name %s returning DNS fail.\n",
+ nmb_namestr(question) ));
+ send_wins_name_query_response(NAM_ERR, p, namerec);
+ return;
+ }
+
+ /*
+ * If the name has expired then reply name not found.
+ */
+
+ if( (namerec->data.death_time != PERMANENT_TTL) && (namerec->data.death_time < p->timestamp) ) {
+ DEBUG(3,("wins_process_name_query: name query for name %s - name expired. Returning fail.\n",
+ nmb_namestr(question) ));
+ send_wins_name_query_response(NAM_ERR, p, namerec);
+ return;
+ }
+
+ DEBUG(3,("wins_process_name_query: name query for name %s returning first IP %s.\n",
+ nmb_namestr(question), inet_ntoa(namerec->data.ip[0]) ));
+
+ send_wins_name_query_response(0, p, namerec);
+ return;
+ }
+
+ /*
+ * Name not found in WINS - try a dns query if it's a 0x20 name.
+ */
+
+ if(lp_wins_dns_proxy() && ((question->name_type == 0x20) || question->name_type == 0)) {
+ DEBUG(3,("wins_process_name_query: name query for name %s not found - doing dns lookup.\n",
+ nmb_namestr(question) ));
+
+ queue_dns_query(p, question);
+ return;
+ }
+
+ /*
+ * Name not found - return error.
+ */
+
+ send_wins_name_query_response(NAM_ERR, p, NULL);
+}
+
+/****************************************************************************
+Send a WINS name release response.
+**************************************************************************/
+
+static void send_wins_name_release_response(int rcode, struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ char rdata[6];
+
+ memcpy(&rdata[0], &nmb->additional->rdata[0], 6);
+
+ reply_netbios_packet(p, /* Packet to reply to. */
+ rcode, /* Result code. */
+ NMB_REL, /* nmbd type code. */
+ NMB_NAME_RELEASE_OPCODE, /* opcode. */
+ 0, /* ttl. */
+ rdata, /* data to send. */
+ 6); /* data length. */
+}
+
+/***********************************************************************
+ Deal with a name release.
+***********************************************************************/
+
+void wins_process_name_release_request(struct subnet_record *subrec,
+ struct packet_struct *p)
+{
+ struct nmb_packet *nmb = &p->packet.nmb;
+ struct nmb_name *question = &nmb->question.question_name;
+ bool bcast = nmb->header.nm_flags.bcast;
+ uint16_t nb_flags = get_nb_flags(nmb->additional->rdata);
+ struct name_record *namerec = NULL;
+ struct in_addr from_ip;
+ bool releasing_group_name = (nb_flags & NB_GROUP) ? True : False;
+
+ putip((char *)&from_ip,&nmb->additional->rdata[2]);
+
+ if(bcast) {
+ /*
+ * We should only get unicast name registration packets here.
+ * Anyone trying to register broadcast should not be going to a WINS
+ * server. Log an error here.
+ */
+
+ DEBUG(0,("wins_process_name_release_request: broadcast name registration request \
+received for name %s from IP %s on subnet %s. Error - should not be sent to WINS server\n",
+ nmb_namestr(question), inet_ntoa(from_ip), subrec->subnet_name));
+ return;
+ }
+
+ DEBUG(3,("wins_process_name_release_request: %s name release for name %s \
+IP %s\n", releasing_group_name ? "Group" : "Unique", nmb_namestr(question), inet_ntoa(from_ip) ));
+
+ /*
+ * Deal with policy regarding 0x1d names.
+ */
+
+ if(!releasing_group_name && (question->name_type == 0x1d)) {
+ DEBUG(3,("wins_process_name_release_request: Ignoring request \
+to release name %s from IP %s.\n", nmb_namestr(question), inet_ntoa(p->ip) ));
+ send_wins_name_release_response(0, p);
+ return;
+ }
+
+ /*
+ * See if the name already exists.
+ */
+
+ namerec = find_name_on_subnet(subrec, question, FIND_ANY_NAME);
+
+ if( (namerec == NULL) || ((namerec != NULL) && (namerec->data.source != REGISTER_NAME)) ) {
+ send_wins_name_release_response(NAM_ERR, p);
+ return;
+ }
+
+ /*
+ * Check that the sending machine has permission to release this name.
+ * If it's a group name not ending in 0x1c then just say yes and let
+ * the group time out.
+ */
+
+ if(releasing_group_name && (question->name_type != 0x1c)) {
+ send_wins_name_release_response(0, p);
+ return;
+ }
+
+ /*
+ * Check that the releasing node is on the list of IP addresses
+ * for this name. Disallow the release if not.
+ */
+
+ if(!find_ip_in_name_record(namerec, from_ip)) {
+ DEBUG(3,("wins_process_name_release_request: Refusing request to \
+release name %s as IP %s is not one of the known IP's for this name.\n",
+ nmb_namestr(question), inet_ntoa(from_ip) ));
+ send_wins_name_release_response(NAM_ERR, p);
+ return;
+ }
+
+ /*
+ * Check if the record is active. IF it's already released
+ * or tombstoned, refuse the release.
+ */
+
+ if (!WINS_STATE_ACTIVE(namerec)) {
+ DEBUG(3,("wins_process_name_release_request: Refusing request to \
+release name %s as this record is not active anymore.\n", nmb_namestr(question) ));
+ send_wins_name_release_response(NAM_ERR, p);
+ return;
+ }
+
+ /*
+ * Check if the record is a 0x1c group
+ * and has more then one ip
+ * remove only this address.
+ */
+
+ if(releasing_group_name && (question->name_type == 0x1c) && (namerec->data.num_ips > 1)) {
+ remove_ip_from_name_record(namerec, from_ip);
+ DEBUG(3,("wins_process_name_release_request: Remove IP %s from NAME: %s\n",
+ inet_ntoa(from_ip),nmb_namestr(question)));
+ wins_hook("delete", namerec, 0);
+ send_wins_name_release_response(0, p);
+ return;
+ }
+
+ /*
+ * Send a release response.
+ * Flag the name as released and update the ttl
+ */
+
+ namerec->data.wins_flags |= WINS_RELEASED;
+ update_name_ttl(namerec, EXTINCTION_INTERVAL);
+
+ wins_hook("delete", namerec, 0);
+ send_wins_name_release_response(0, p);
+}
+
+/*******************************************************************
+ WINS time dependent processing.
+******************************************************************/
+
+static int wins_processing_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state)
+{
+ time_t t = *(time_t *)state;
+ bool store_record = False;
+ struct name_record *namerec = NULL;
+ struct in_addr our_fake_ip;
+
+ our_fake_ip = interpret_addr2("0.0.0.0");
+ if (kbuf.dsize != sizeof(unstring) + 1) {
+ return 0;
+ }
+
+ namerec = wins_record_to_name_record(kbuf, dbuf);
+ if (!namerec) {
+ return 0;
+ }
+
+ if( (namerec->data.death_time != PERMANENT_TTL) && (namerec->data.death_time < t) ) {
+ if( namerec->data.source == SELF_NAME ) {
+ DEBUG( 3, ( "wins_processing_traverse_fn: Subnet %s not expiring SELF name %s\n",
+ wins_server_subnet->subnet_name, nmb_namestr(&namerec->name) ) );
+ namerec->data.death_time += 300;
+ store_record = True;
+ goto done;
+ } else if (namerec->data.source == DNS_NAME || namerec->data.source == DNSFAIL_NAME) {
+ DEBUG(3,("wins_processing_traverse_fn: deleting timed out DNS name %s\n",
+ nmb_namestr(&namerec->name)));
+ remove_name_from_wins_namelist(namerec );
+ goto done;
+ }
+
+ /* handle records, samba is the wins owner */
+ if (ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) {
+ switch (namerec->data.wins_flags & WINS_STATE_MASK) {
+ case WINS_ACTIVE:
+ namerec->data.wins_flags&=~WINS_STATE_MASK;
+ namerec->data.wins_flags|=WINS_RELEASED;
+ namerec->data.death_time = t + EXTINCTION_INTERVAL;
+ DEBUG(3,("wins_processing_traverse_fn: expiring %s\n",
+ nmb_namestr(&namerec->name)));
+ store_record = True;
+ goto done;
+ case WINS_RELEASED:
+ namerec->data.wins_flags&=~WINS_STATE_MASK;
+ namerec->data.wins_flags|=WINS_TOMBSTONED;
+ namerec->data.death_time = t + EXTINCTION_TIMEOUT;
+ get_global_id_and_update(&namerec->data.id, True);
+ DEBUG(3,("wins_processing_traverse_fn: tombstoning %s\n",
+ nmb_namestr(&namerec->name)));
+ store_record = True;
+ goto done;
+ case WINS_TOMBSTONED:
+ DEBUG(3,("wins_processing_traverse_fn: deleting %s\n",
+ nmb_namestr(&namerec->name)));
+ remove_name_from_wins_namelist(namerec );
+ goto done;
+ }
+ } else {
+ switch (namerec->data.wins_flags & WINS_STATE_MASK) {
+ case WINS_ACTIVE:
+ /* that's not as MS says it should be */
+ namerec->data.wins_flags&=~WINS_STATE_MASK;
+ namerec->data.wins_flags|=WINS_TOMBSTONED;
+ namerec->data.death_time = t + EXTINCTION_TIMEOUT;
+ DEBUG(3,("wins_processing_traverse_fn: tombstoning %s\n",
+ nmb_namestr(&namerec->name)));
+ store_record = True;
+ goto done;
+ case WINS_TOMBSTONED:
+ DEBUG(3,("wins_processing_traverse_fn: deleting %s\n",
+ nmb_namestr(&namerec->name)));
+ remove_name_from_wins_namelist(namerec );
+ goto done;
+ case WINS_RELEASED:
+ DEBUG(0,("wins_processing_traverse_fn: %s is in released state and\
+we are not the wins owner !\n", nmb_namestr(&namerec->name)));
+ goto done;
+ }
+ }
+ }
+
+ done:
+
+ if (store_record) {
+ wins_store_changed_namerec(namerec);
+ }
+
+ SAFE_FREE(namerec->data.ip);
+ SAFE_FREE(namerec);
+
+ return 0;
+}
+
+/*******************************************************************
+ Time dependent wins processing.
+******************************************************************/
+
+void initiate_wins_processing(time_t t)
+{
+ static time_t lasttime = 0;
+
+ if (!lasttime) {
+ lasttime = t;
+ }
+ if (t - lasttime < 20) {
+ return;
+ }
+
+ if(!lp_we_are_a_wins_server()) {
+ lasttime = t;
+ return;
+ }
+
+ tdb_traverse(wins_tdb, wins_processing_traverse_fn, &t);
+
+ wins_delete_all_tmp_in_memory_records();
+
+ wins_write_database(t, True);
+
+ lasttime = t;
+}
+
+/*******************************************************************
+ Write out one record.
+******************************************************************/
+
+void wins_write_name_record(struct name_record *namerec, FILE *fp)
+{
+ int i;
+ struct tm *tm;
+
+ DEBUGADD(4,("%-19s ", nmb_namestr(&namerec->name) ));
+
+ if( namerec->data.death_time != PERMANENT_TTL ) {
+ char *ts, *nl;
+
+ tm = localtime(&namerec->data.death_time);
+ if (!tm) {
+ return;
+ }
+ ts = asctime(tm);
+ if (!ts) {
+ return;
+ }
+ nl = strrchr( ts, '\n' );
+ if( NULL != nl ) {
+ *nl = '\0';
+ }
+ DEBUGADD(4,("TTL = %s ", ts ));
+ } else {
+ DEBUGADD(4,("TTL = PERMANENT "));
+ }
+
+ for (i = 0; i < namerec->data.num_ips; i++) {
+ DEBUGADD(4,("%15s ", inet_ntoa(namerec->data.ip[i]) ));
+ }
+ DEBUGADD(4,("%2x\n", namerec->data.nb_flags ));
+
+ if( namerec->data.source == REGISTER_NAME ) {
+ unstring name;
+ pull_ascii_nstring(name, sizeof(name), namerec->name.name);
+ fprintf(fp, "\"%s#%02x\" %d ", name,
+ namerec->name.name_type, /* Ignore scope. */
+ (int)namerec->data.death_time);
+
+ for (i = 0; i < namerec->data.num_ips; i++)
+ fprintf(fp, "%s ", inet_ntoa(namerec->data.ip[i]));
+ fprintf(fp, "%2xR\n", namerec->data.nb_flags);
+ }
+}
+
+/*******************************************************************
+ Write out the current WINS database.
+******************************************************************/
+
+static int wins_writedb_traverse_fn(TDB_CONTEXT *tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state)
+{
+ struct name_record *namerec = NULL;
+ FILE *fp = (FILE *)state;
+
+ if (kbuf.dsize != sizeof(unstring) + 1) {
+ return 0;
+ }
+
+ namerec = wins_record_to_name_record(kbuf, dbuf);
+ if (!namerec) {
+ return 0;
+ }
+
+ wins_write_name_record(namerec, fp);
+
+ SAFE_FREE(namerec->data.ip);
+ SAFE_FREE(namerec);
+ return 0;
+}
+
+
+void wins_write_database(time_t t, bool background)
+{
+ static time_t last_write_time = 0;
+ char *fname = NULL;
+ char *fnamenew = NULL;
+
+ int fd;
+ FILE *fp;
+
+ if (background) {
+ if (!last_write_time) {
+ last_write_time = t;
+ }
+ if (t - last_write_time < 120) {
+ return;
+ }
+
+ }
+
+ if(!lp_we_are_a_wins_server()) {
+ return;
+ }
+
+ /* We will do the writing in a child process to ensure that the parent doesn't block while this is done */
+ if (background) {
+ CatchChild();
+ if (fork()) {
+ return;
+ }
+ if (tdb_reopen(wins_tdb)) {
+ DEBUG(0,("wins_write_database: tdb_reopen failed. Error was %s\n",
+ strerror(errno)));
+ _exit(0);
+ return;
+ }
+ }
+
+ if (!(fname = state_path(talloc_tos(), WINS_LIST))) {
+ goto err_exit;
+ }
+ /* This is safe as the 0 length means "don't expand". */
+ all_string_sub(fname,"//", "/", 0);
+
+ if (asprintf(&fnamenew, "%s.%u", fname, (unsigned int)getpid()) < 0) {
+ goto err_exit;
+ }
+
+ fd = open(fnamenew, O_WRONLY|O_CREAT, 0644);
+ if (fd == -1) {
+ DBG_ERR("Can't open %s: %s\n", fnamenew, strerror(errno));
+ goto err_exit;
+ }
+
+ fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ DBG_ERR("fdopen failed: %s\n", strerror(errno));
+ close(fd);
+ goto err_exit;
+ }
+ fd = -1;
+
+ DEBUG(4,("wins_write_database: Dump of WINS name list.\n"));
+
+ fprintf(fp,"VERSION %d %u\n", WINS_VERSION, 0);
+
+ tdb_traverse(wins_tdb, wins_writedb_traverse_fn, fp);
+
+ fclose(fp);
+ chmod(fnamenew,0644);
+ unlink(fname);
+ rename(fnamenew,fname);
+
+ err_exit:
+
+ SAFE_FREE(fnamenew);
+ TALLOC_FREE(fname);
+
+ if (background) {
+ _exit(0);
+ }
+}
+
+#if 0
+ Until winsrepl is done.
+/****************************************************************************
+ Process a internal Samba message receiving a wins record.
+***************************************************************************/
+
+void nmbd_wins_new_entry(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ WINS_RECORD *record;
+ struct name_record *namerec = NULL;
+ struct name_record *new_namerec = NULL;
+ struct nmb_name question;
+ bool overwrite=False;
+ struct in_addr our_fake_ip;
+ int i;
+
+ our_fake_ip = interpret_addr2("0.0.0.0");
+ if (buf==NULL) {
+ return;
+ }
+
+ /* Record should use UNIX codepage. Ensure this is so in the wrepld code. JRA. */
+ record=(WINS_RECORD *)buf;
+
+ make_nmb_name(&question, record->name, record->type);
+
+ namerec = find_name_on_subnet(wins_server_subnet, &question, FIND_ANY_NAME);
+
+ /* record doesn't exist, add it */
+ if (namerec == NULL) {
+ DEBUG(3,("nmbd_wins_new_entry: adding new replicated record: %s<%02x> for wins server: %s\n",
+ record->name, record->type, inet_ntoa(record->wins_ip)));
+
+ new_namerec=add_name_to_subnet( wins_server_subnet,
+ record->name,
+ record->type,
+ record->nb_flags,
+ EXTINCTION_INTERVAL,
+ REGISTER_NAME,
+ record->num_ips,
+ record->ip);
+
+ if (new_namerec!=NULL) {
+ update_wins_owner(new_namerec, record->wins_ip);
+ update_wins_flag(new_namerec, record->wins_flags);
+ new_namerec->data.id=record->id;
+
+ wins_server_subnet->namelist_changed = True;
+ }
+ }
+
+ /* check if we have a conflict */
+ if (namerec != NULL) {
+ /* both records are UNIQUE */
+ if (namerec->data.wins_flags&WINS_UNIQUE && record->wins_flags&WINS_UNIQUE) {
+
+ /* the database record is a replica */
+ if (!ip_equal_v4(namerec->data.wins_ip, our_fake_ip)) {
+ if (namerec->data.wins_flags&WINS_ACTIVE && record->wins_flags&WINS_TOMBSTONED) {
+ if (ip_equal_v4(namerec->data.wins_ip, record->wins_ip))
+ overwrite=True;
+ } else
+ overwrite=True;
+ } else {
+ /* we are the wins owner of the database record */
+ /* the 2 records have the same IP address */
+ if (ip_equal_v4(namerec->data.ip[0], record->ip[0])) {
+ if (namerec->data.wins_flags&WINS_ACTIVE && record->wins_flags&WINS_TOMBSTONED)
+ get_global_id_and_update(&namerec->data.id, True);
+ else
+ overwrite=True;
+
+ } else {
+ /* the 2 records have different IP address */
+ if (namerec->data.wins_flags&WINS_ACTIVE) {
+ if (record->wins_flags&WINS_TOMBSTONED)
+ get_global_id_and_update(&namerec->data.id, True);
+ if (record->wins_flags&WINS_ACTIVE)
+ /* send conflict challenge to the replica node */
+ ;
+ } else
+ overwrite=True;
+ }
+
+ }
+ }
+
+ /* the replica is a standard group */
+ if (record->wins_flags&WINS_NGROUP || record->wins_flags&WINS_SGROUP) {
+ /* if the database record is unique and active force a name release */
+ if (namerec->data.wins_flags&WINS_UNIQUE)
+ /* send a release name to the unique node */
+ ;
+ overwrite=True;
+
+ }
+
+ /* the replica is a special group */
+ if (record->wins_flags&WINS_SGROUP && namerec->data.wins_flags&WINS_SGROUP) {
+ if (namerec->data.wins_flags&WINS_ACTIVE) {
+ for (i=0; i<record->num_ips; i++)
+ if(!find_ip_in_name_record(namerec, record->ip[i]))
+ add_ip_to_name_record(namerec, record->ip[i]);
+ } else {
+ overwrite=True;
+ }
+ }
+
+ /* the replica is a multihomed host */
+
+ /* I'm giving up on multi homed. Too much complex to understand */
+
+ if (record->wins_flags&WINS_MHOMED) {
+ if (! (namerec->data.wins_flags&WINS_ACTIVE)) {
+ if ( !(namerec->data.wins_flags&WINS_RELEASED) && !(namerec->data.wins_flags&WINS_NGROUP))
+ overwrite=True;
+ }
+ else {
+ if (ip_equal_v4(record->wins_ip, namerec->data.wins_ip))
+ overwrite=True;
+
+ if (ip_equal_v4(namerec->data.wins_ip, our_fake_ip))
+ if (namerec->data.wins_flags&WINS_UNIQUE)
+ get_global_id_and_update(&namerec->data.id, True);
+
+ }
+
+ if (record->wins_flags&WINS_ACTIVE && namerec->data.wins_flags&WINS_ACTIVE)
+ if (namerec->data.wins_flags&WINS_UNIQUE ||
+ namerec->data.wins_flags&WINS_MHOMED)
+ if (ip_equal_v4(record->wins_ip, namerec->data.wins_ip))
+ overwrite=True;
+
+ }
+
+ if (overwrite == False)
+ DEBUG(3, ("nmbd_wins_new_entry: conflict in adding record: %s<%02x> from wins server: %s\n",
+ record->name, record->type, inet_ntoa(record->wins_ip)));
+ else {
+ DEBUG(3, ("nmbd_wins_new_entry: replacing record: %s<%02x> from wins server: %s\n",
+ record->name, record->type, inet_ntoa(record->wins_ip)));
+
+ /* remove the old record and add a new one */
+ remove_name_from_namelist( wins_server_subnet, namerec );
+ new_namerec=add_name_to_subnet( wins_server_subnet, record->name, record->type, record->nb_flags,
+ EXTINCTION_INTERVAL, REGISTER_NAME, record->num_ips, record->ip);
+ if (new_namerec!=NULL) {
+ update_wins_owner(new_namerec, record->wins_ip);
+ update_wins_flag(new_namerec, record->wins_flags);
+ new_namerec->data.id=record->id;
+
+ wins_server_subnet->namelist_changed = True;
+ }
+
+ wins_server_subnet->namelist_changed = True;
+ }
+
+ }
+}
+#endif
diff --git a/source3/nmbd/nmbd_workgroupdb.c b/source3/nmbd/nmbd_workgroupdb.c
new file mode 100644
index 0000000..cd97efd
--- /dev/null
+++ b/source3/nmbd/nmbd_workgroupdb.c
@@ -0,0 +1,327 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT netbios routines and daemon - version 2
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1994-1998
+ Copyright (C) Jeremy Allison 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "nmbd/nmbd.h"
+#include "lib/util/string_wrappers.h"
+
+extern uint16_t samba_nb_type;
+
+int workgroup_count = 0; /* unique index key: one for each workgroup */
+
+/****************************************************************************
+ Add a workgroup into the list.
+**************************************************************************/
+
+static void add_workgroup(struct subnet_record *subrec, struct work_record *work)
+{
+ work->subnet = subrec;
+ DLIST_ADD(subrec->workgrouplist, work);
+ subrec->work_changed = True;
+}
+
+/****************************************************************************
+ Copy name to unstring. Used by create_workgroup() and find_workgroup_on_subnet().
+**************************************************************************/
+
+static void name_to_unstring(unstring unname, const char *name)
+{
+ nstring nname;
+
+ errno = 0;
+ push_ascii_nstring(nname, name);
+ if (errno == E2BIG) {
+ unstring tname;
+ pull_ascii_nstring(tname, sizeof(tname), nname);
+ strlcpy(unname, tname, sizeof(nname));
+ DEBUG(0,("name_to_nstring: workgroup name %s is too long. Truncating to %s\n",
+ name, tname));
+ } else {
+ unstrcpy(unname, name);
+ }
+}
+
+/****************************************************************************
+ Create an empty workgroup.
+**************************************************************************/
+
+static struct work_record *create_workgroup(const char *name, int ttl)
+{
+ struct work_record *work;
+ struct subnet_record *subrec;
+ int t = -1;
+
+ if((work = SMB_MALLOC_P(struct work_record)) == NULL) {
+ DEBUG(0,("create_workgroup: malloc fail !\n"));
+ return NULL;
+ }
+ memset((char *)work, '\0', sizeof(*work));
+
+ name_to_unstring(work->work_group, name);
+
+ work->serverlist = NULL;
+
+ work->RunningElection = False;
+ work->ElectionCount = 0;
+ work->announce_interval = 0;
+ work->needelection = False;
+ work->needannounce = True;
+ work->lastannounce_time = time(NULL);
+ work->mst_state = lp_local_master() ? MST_POTENTIAL : MST_NONE;
+ work->dom_state = DOMAIN_NONE;
+ work->log_state = LOGON_NONE;
+
+ work->death_time = (ttl != PERMANENT_TTL) ? time(NULL)+(ttl*3) : PERMANENT_TTL;
+
+ /* Make sure all token representations of workgroups are unique. */
+
+ for (subrec = FIRST_SUBNET; subrec && (t == -1); subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ struct work_record *w;
+ for (w = subrec->workgrouplist; w && t == -1; w = w->next) {
+ if (strequal(w->work_group, work->work_group))
+ t = w->token;
+ }
+ }
+
+ if (t == -1)
+ work->token = ++workgroup_count;
+ else
+ work->token = t;
+
+ /* No known local master browser as yet. */
+ *work->local_master_browser_name = '\0';
+
+ /* No known domain master browser as yet. */
+ *work->dmb_name.name = '\0';
+ zero_ip_v4(&work->dmb_addr);
+
+ /* WfWg uses 01040b01 */
+ /* Win95 uses 01041501 */
+ /* NTAS uses ???????? */
+ work->ElectionCriterion = (MAINTAIN_LIST)|(BROWSER_ELECTION_VERSION<<8);
+ work->ElectionCriterion |= (lp_os_level() << 24);
+ if (lp_domain_master())
+ work->ElectionCriterion |= 0x80;
+
+ return work;
+}
+
+/*******************************************************************
+ Remove a workgroup.
+******************************************************************/
+
+static struct work_record *remove_workgroup_from_subnet(struct subnet_record *subrec,
+ struct work_record *work)
+{
+ struct work_record *ret_work = NULL;
+
+ DEBUG(3,("remove_workgroup: Removing workgroup %s\n", work->work_group));
+
+ ret_work = work->next;
+
+ remove_all_servers(work);
+
+ if (!work->serverlist) {
+ DLIST_REMOVE(subrec->workgrouplist, work);
+ ZERO_STRUCTP(work);
+ SAFE_FREE(work);
+ }
+
+ subrec->work_changed = True;
+
+ return ret_work;
+}
+
+/****************************************************************************
+ Find a workgroup in the workgroup list of a subnet.
+**************************************************************************/
+
+struct work_record *find_workgroup_on_subnet(struct subnet_record *subrec,
+ const char *name)
+{
+ struct work_record *ret;
+ unstring un_name;
+
+ DEBUG(4, ("find_workgroup_on_subnet: workgroup search for %s on subnet %s: ",
+ name, subrec->subnet_name));
+
+ name_to_unstring(un_name, name);
+
+ for (ret = subrec->workgrouplist; ret; ret = ret->next) {
+ if (strequal(ret->work_group,un_name)) {
+ DEBUGADD(4, ("found.\n"));
+ return(ret);
+ }
+ }
+ DEBUGADD(4, ("not found.\n"));
+ return NULL;
+}
+
+/****************************************************************************
+ Create a workgroup in the workgroup list of the subnet.
+**************************************************************************/
+
+struct work_record *create_workgroup_on_subnet(struct subnet_record *subrec,
+ const char *name, int ttl)
+{
+ struct work_record *work = NULL;
+
+ DEBUG(4,("create_workgroup_on_subnet: creating group %s on subnet %s\n",
+ name, subrec->subnet_name));
+
+ if ((work = create_workgroup(name, ttl))) {
+ add_workgroup(subrec, work);
+ subrec->work_changed = True;
+ return(work);
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Update a workgroup ttl.
+**************************************************************************/
+
+void update_workgroup_ttl(struct work_record *work, int ttl)
+{
+ if(work->death_time != PERMANENT_TTL)
+ work->death_time = time(NULL)+(ttl*3);
+ work->subnet->work_changed = True;
+}
+
+/****************************************************************************
+ Fail function called if we cannot register the WORKGROUP<0> and
+ WORKGROUP<1e> names on the net.
+**************************************************************************/
+
+static void fail_register(struct subnet_record *subrec, struct response_record *rrec,
+ struct nmb_name *nmbname)
+{
+ DEBUG(0,("fail_register: Failed to register name %s on subnet %s.\n",
+ nmb_namestr(nmbname), subrec->subnet_name));
+}
+
+/****************************************************************************
+ If the workgroup is our primary workgroup, add the required names to it.
+**************************************************************************/
+
+void initiate_myworkgroup_startup(struct subnet_record *subrec, struct work_record *work)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int i;
+
+ if(!strequal(lp_workgroup(), work->work_group))
+ return;
+
+ /* If this is a broadcast subnet then start elections on it if we are so configured. */
+
+ if ((subrec != unicast_subnet) && (subrec != remote_broadcast_subnet) &&
+ (subrec != wins_server_subnet) && lp_preferred_master() && lp_local_master()) {
+ DEBUG(3, ("initiate_myworkgroup_startup: preferred master startup for \
+workgroup %s on subnet %s\n", work->work_group, subrec->subnet_name));
+ work->needelection = True;
+ work->ElectionCriterion |= (1<<3);
+ }
+
+ /* Register the WORKGROUP<0> and WORKGROUP<1e> names on the network. */
+
+ register_name(subrec,lp_workgroup(),0x0,samba_nb_type|NB_GROUP, NULL, fail_register,NULL);
+ register_name(subrec,lp_workgroup(),0x1e,samba_nb_type|NB_GROUP, NULL, fail_register,NULL);
+
+ for( i = 0; my_netbios_names(i); i++) {
+ const char *name = my_netbios_names(i);
+ int stype = lp_default_server_announce() | (lp_local_master() ? SV_TYPE_POTENTIAL_BROWSER : 0 );
+
+ if(!strequal(lp_netbios_name(), name))
+ stype &= ~(SV_TYPE_MASTER_BROWSER|SV_TYPE_POTENTIAL_BROWSER|SV_TYPE_DOMAIN_MASTER|SV_TYPE_DOMAIN_MEMBER);
+
+ create_server_on_workgroup(work,name,stype|SV_TYPE_LOCAL_LIST_ONLY, PERMANENT_TTL,
+ string_truncate(lp_server_string(talloc_tos(), lp_sub), MAX_SERVER_STRING_LENGTH));
+ DEBUG(3,("initiate_myworkgroup_startup: Added server name entry %s \
+on subnet %s\n", name, subrec->subnet_name));
+ }
+}
+
+/****************************************************************************
+ Dump a copy of the workgroup database into the log file.
+ **************************************************************************/
+
+void dump_workgroups(bool force_write)
+{
+ struct subnet_record *subrec;
+ int debuglevel = force_write ? 0 : 4;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ if (subrec->workgrouplist) {
+ struct work_record *work;
+
+ if( DEBUGLVL( debuglevel ) ) {
+ dbgtext( "dump_workgroups()\n " );
+ dbgtext( "dump workgroup on subnet %15s: ", subrec->subnet_name );
+ dbgtext( "netmask=%15s:\n", inet_ntoa(subrec->mask_ip) );
+ }
+
+ for (work = subrec->workgrouplist; work; work = work->next) {
+ DEBUGADD( debuglevel, ( "\t%s(%d) current master browser = %s\n", work->work_group,
+ work->token, *work->local_master_browser_name ? work->local_master_browser_name : "UNKNOWN" ) );
+ if (work->serverlist) {
+ struct server_record *servrec;
+ for (servrec = work->serverlist; servrec; servrec = servrec->next) {
+ DEBUGADD( debuglevel, ( "\t\t%s %8x (%s)\n",
+ servrec->serv.name,
+ servrec->serv.type,
+ servrec->serv.comment ) );
+ }
+ }
+ }
+ }
+ }
+}
+
+/****************************************************************************
+ Expire any dead servers on all workgroups. If the workgroup has expired
+ remove it.
+ **************************************************************************/
+
+void expire_workgroups_and_servers(time_t t)
+{
+ struct subnet_record *subrec;
+
+ for (subrec = FIRST_SUBNET; subrec; subrec = NEXT_SUBNET_INCLUDING_UNICAST(subrec)) {
+ struct work_record *work;
+ struct work_record *nextwork;
+
+ for (work = subrec->workgrouplist; work; work = nextwork) {
+ nextwork = work->next;
+ expire_servers(work, t);
+
+ if ((work->serverlist == NULL) && (work->death_time != PERMANENT_TTL) &&
+ ((t == (time_t)-1) || (work->death_time < t))) {
+ DEBUG(3,("expire_workgroups_and_servers: Removing timed out workgroup %s\n",
+ work->work_group));
+ remove_workgroup_from_subnet(subrec, work);
+ }
+ }
+ }
+}
diff --git a/source3/nmbd/wscript_build b/source3/nmbd/wscript_build
new file mode 100644
index 0000000..399cdb4
--- /dev/null
+++ b/source3/nmbd/wscript_build
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+nmbd_cflags = ''
+if bld.CONFIG_SET('HAVE_WNO_ERROR_STRINGOP_OVERFLOW'):
+ nmbd_cflags = '-Wno-error=stringop-overflow'
+
+bld.SAMBA3_BINARY('nmbd',
+ source='''
+ asyncdns.c
+ nmbd.c
+ nmbd_become_dmb.c
+ nmbd_become_lmb.c
+ nmbd_browserdb.c
+ nmbd_browsesync.c
+ nmbd_elections.c
+ nmbd_incomingdgrams.c
+ nmbd_incomingrequests.c
+ nmbd_lmhosts.c
+ nmbd_logonnames.c
+ nmbd_mynames.c
+ nmbd_namelistdb.c
+ nmbd_namequery.c
+ nmbd_nameregister.c
+ nmbd_namerelease.c
+ nmbd_nodestatus.c
+ nmbd_packets.c
+ nmbd_processlogon.c
+ nmbd_responserecordsdb.c
+ nmbd_sendannounce.c
+ nmbd_serverlistdb.c
+ nmbd_subnetdb.c
+ nmbd_winsproxy.c
+ nmbd_winsserver.c
+ nmbd_workgroupdb.c
+ nmbd_synclists.c
+ ''',
+ cflags=nmbd_cflags,
+ deps='''
+ talloc
+ tevent
+ smbconf
+ libsmb
+ CMDLINE_S3
+ ''',
+ install_path='${SBINDIR}')
diff --git a/source3/param/loadparm.c b/source3/param/loadparm.c
new file mode 100644
index 0000000..797baa6
--- /dev/null
+++ b/source3/param/loadparm.c
@@ -0,0 +1,4835 @@
+/*
+ Unix SMB/CIFS implementation.
+ Parameter loading functions
+ Copyright (C) Karl Auer 1993-1998
+
+ Largely re-written by Andrew Tridgell, September 1994
+
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) Stefan (metze) Metzmacher 2002
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Michael Adam 2008
+ Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007
+ Copyright (C) Andrew Bartlett 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Load parameters.
+ *
+ * This module provides suitable callback functions for the params
+ * module. It builds the internal table of service details which is
+ * then used by the rest of the server.
+ *
+ * To add a parameter:
+ *
+ * 1) add it to the global or service structure definition
+ * 2) add it to the parm_table
+ * 3) add it to the list of available functions (eg: using FN_GLOBAL_STRING())
+ * 4) If it's a global then initialise it in init_globals. If a local
+ * (ie. service) parameter then initialise it in the sDefault structure
+ *
+ *
+ * Notes:
+ * The configuration file is processed sequentially for speed. It is NOT
+ * accessed randomly as happens in 'real' Windows. For this reason, there
+ * is a fair bit of sequence-dependent code here - ie., code which assumes
+ * that certain things happen before others. In particular, the code which
+ * happens at the boundary between sections is delicately poised, so be
+ * careful!
+ *
+ */
+
+#define LOADPARM_SUBSTITUTION_INTERNALS 1
+#include "includes.h"
+#include "system/filesys.h"
+#include "util_tdb.h"
+#include "lib/param/loadparm.h"
+#include "lib/param/param.h"
+#include "printing.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_init.h"
+
+#include "include/smb_ldap.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "intl.h"
+#include "../libcli/smb/smb_signing.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "../lib/util/bitmap.h"
+#include "librpc/gen_ndr/nbt.h"
+#include "librpc/gen_ndr/dns.h"
+#include "source4/lib/tls/tls.h"
+#include "libcli/auth/ntlm_check.h"
+#include "lib/crypto/gnutls_helpers.h"
+#include "lib/util/string_wrappers.h"
+#include "auth/credentials/credentials.h"
+#include "source3/lib/substitute.h"
+#include "source3/librpc/gen_ndr/ads.h"
+#include "lib/util/time_basic.h"
+#include "libds/common/flags.h"
+
+#ifdef HAVE_SYS_SYSCTL_H
+#include <sys/sysctl.h>
+#endif
+
+bool b_loaded = false;
+
+/* the special value for the include parameter
+ * to be interpreted not as a file name but to
+ * trigger loading of the global smb.conf options
+ * from registry. */
+#ifndef INCLUDE_REGISTRY_NAME
+#define INCLUDE_REGISTRY_NAME "registry"
+#endif
+
+static bool in_client = false; /* Not in the client by default */
+static struct smbconf_csn conf_last_csn;
+
+static int config_backend = CONFIG_BACKEND_FILE;
+
+/* some helpful bits */
+#define LP_SNUM_OK(i) (((i) >= 0) && ((i) < iNumServices) && \
+ (ServicePtrs != NULL) && \
+ (ServicePtrs[(i)] != NULL) && ServicePtrs[(i)]->valid)
+#define VALID(i) ((ServicePtrs != NULL) && (ServicePtrs[i]!= NULL) && \
+ ServicePtrs[i]->valid)
+
+#define USERSHARE_VALID 1
+#define USERSHARE_PENDING_DELETE 2
+
+static bool defaults_saved = false;
+
+#include "lib/param/param_global.h"
+
+static struct loadparm_global Globals;
+
+/* This is a default service used to prime a services structure */
+static const struct loadparm_service _sDefault =
+{
+ .valid = true,
+ .autoloaded = false,
+ .usershare = 0,
+ .usershare_last_mod = {0, 0},
+ .szService = NULL,
+ .path = NULL,
+ .invalid_users = NULL,
+ .valid_users = NULL,
+ .admin_users = NULL,
+ .copy = NULL,
+ .include = NULL,
+ .preexec = NULL,
+ .postexec = NULL,
+ .root_preexec = NULL,
+ .root_postexec = NULL,
+ .cups_options = NULL,
+ .print_command = NULL,
+ .lpq_command = NULL,
+ .lprm_command = NULL,
+ .lppause_command = NULL,
+ .lpresume_command = NULL,
+ .queuepause_command = NULL,
+ .queueresume_command = NULL,
+ ._printername = NULL,
+ .printjob_username = NULL,
+ .dont_descend = NULL,
+ .hosts_allow = NULL,
+ .hosts_deny = NULL,
+ .magic_script = NULL,
+ .magic_output = NULL,
+ .veto_files = NULL,
+ .hide_files = NULL,
+ .veto_oplock_files = NULL,
+ .comment = NULL,
+ .force_user = NULL,
+ .force_group = NULL,
+ .read_list = NULL,
+ .write_list = NULL,
+ .volume = NULL,
+ .fstype = NULL,
+ .vfs_objects = NULL,
+ .msdfs_proxy = NULL,
+ .aio_write_behind = NULL,
+ .dfree_command = NULL,
+ .min_print_space = 0,
+ .max_print_jobs = 1000,
+ .max_reported_print_jobs = 0,
+ .create_mask = 0744,
+ .force_create_mode = 0,
+ .directory_mask = 0755,
+ .force_directory_mode = 0,
+ .max_connections = 0,
+ .default_case = CASE_LOWER,
+ .printing = DEFAULT_PRINTING,
+ .csc_policy = 0,
+ .block_size = 1024,
+ .dfree_cache_time = 0,
+ .preexec_close = false,
+ .root_preexec_close = false,
+ .case_sensitive = Auto,
+ .preserve_case = true,
+ .short_preserve_case = true,
+ .hide_dot_files = true,
+ .hide_special_files = false,
+ .hide_unreadable = false,
+ .hide_unwriteable_files = false,
+ .browseable = true,
+ .access_based_share_enum = false,
+ .available = true,
+ .read_only = true,
+ .spotlight = false,
+ .guest_only = false,
+ .administrative_share = false,
+ .guest_ok = false,
+ .printable = false,
+ .print_notify_backchannel = false,
+ .map_system = false,
+ .map_hidden = false,
+ .map_archive = true,
+ .store_dos_attributes = true,
+ .smbd_max_xattr_size = 65536,
+ .dmapi_support = false,
+ .locking = true,
+ .strict_locking = Auto,
+ .posix_locking = true,
+ .oplocks = true,
+ .kernel_oplocks = false,
+ .level2_oplocks = true,
+ .mangled_names = MANGLED_NAMES_ILLEGAL,
+ .wide_links = false,
+ .follow_symlinks = true,
+ .sync_always = false,
+ .strict_allocate = false,
+ .strict_rename = false,
+ .strict_sync = true,
+ .mangling_char = '~',
+ .copymap = NULL,
+ .delete_readonly = false,
+ .fake_oplocks = false,
+ .delete_veto_files = false,
+ .dos_filemode = false,
+ .dos_filetimes = true,
+ .dos_filetime_resolution = false,
+ .fake_directory_create_times = false,
+ .blocking_locks = true,
+ .inherit_permissions = false,
+ .inherit_acls = false,
+ .inherit_owner = false,
+ .msdfs_root = false,
+ .msdfs_shuffle_referrals = false,
+ .use_client_driver = false,
+ .default_devmode = true,
+ .force_printername = false,
+ .nt_acl_support = true,
+ .force_unknown_acl_user = false,
+ ._use_sendfile = false,
+ .map_acl_inherit = false,
+ .afs_share = false,
+ .ea_support = true,
+ .acl_check_permissions = true,
+ .acl_map_full_control = true,
+ .acl_group_control = false,
+ .acl_allow_execute_always = false,
+ .acl_flag_inherited_canonicalization = true,
+ .aio_read_size = 1,
+ .aio_write_size = 1,
+ .map_readonly = MAP_READONLY_NO,
+ .server_smb_encrypt = SMB_ENCRYPTION_DEFAULT,
+ .kernel_share_modes = false,
+ .durable_handles = true,
+ .check_parent_directory_delete_on_close = false,
+ .param_opt = NULL,
+ .smbd_search_ask_sharemode = true,
+ .smbd_getinfo_ask_sharemode = true,
+ .spotlight_backend = SPOTLIGHT_BACKEND_NOINDEX,
+ .honor_change_notify_privilege = false,
+ .volume_serial_number = -1,
+ .dummy = ""
+};
+
+/*
+ * This is a copy of the default service structure. Service options in the
+ * global section would otherwise overwrite the initial default values.
+ */
+static struct loadparm_service sDefault;
+
+/* local variables */
+static struct loadparm_service **ServicePtrs = NULL;
+static int iNumServices = 0;
+static int iServiceIndex = 0;
+static struct db_context *ServiceHash;
+static bool bInGlobalSection = true;
+static bool bGlobalOnly = false;
+static struct file_lists *file_lists = NULL;
+static unsigned int *flags_list = NULL;
+
+static void set_allowed_client_auth(void);
+
+static bool lp_set_cmdline_helper(const char *pszParmName, const char *pszParmValue);
+static void free_param_opts(struct parmlist_entry **popts);
+
+/**
+ * Function to return the default value for the maximum number of open
+ * file descriptors permitted. This function tries to consult the
+ * kernel-level (sysctl) and ulimit (getrlimit()) values and goes
+ * the smaller of those.
+ */
+static int max_open_files(void)
+{
+ int sysctl_max = MAX_OPEN_FILES;
+ int rlimit_max = MAX_OPEN_FILES;
+
+#ifdef HAVE_SYSCTLBYNAME
+ {
+ size_t size = sizeof(sysctl_max);
+ sysctlbyname("kern.maxfilesperproc", &sysctl_max, &size, NULL,
+ 0);
+ }
+#endif
+
+#if (defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE))
+ {
+ struct rlimit rl;
+
+ ZERO_STRUCT(rl);
+
+ if (getrlimit(RLIMIT_NOFILE, &rl) == 0)
+ rlimit_max = rl.rlim_cur;
+
+#if defined(RLIM_INFINITY)
+ if(rl.rlim_cur == RLIM_INFINITY)
+ rlimit_max = MAX_OPEN_FILES;
+#endif
+ }
+#endif
+
+ if (sysctl_max < MIN_OPEN_FILES_WINDOWS) {
+ DEBUG(2,("max_open_files: increasing sysctl_max (%d) to "
+ "minimum Windows limit (%d)\n",
+ sysctl_max,
+ MIN_OPEN_FILES_WINDOWS));
+ sysctl_max = MIN_OPEN_FILES_WINDOWS;
+ }
+
+ if (rlimit_max < MIN_OPEN_FILES_WINDOWS) {
+ DEBUG(2,("rlimit_max: increasing rlimit_max (%d) to "
+ "minimum Windows limit (%d)\n",
+ rlimit_max,
+ MIN_OPEN_FILES_WINDOWS));
+ rlimit_max = MIN_OPEN_FILES_WINDOWS;
+ }
+
+ return MIN(sysctl_max, rlimit_max);
+}
+
+/**
+ * Common part of freeing allocated data for one parameter.
+ */
+static void free_one_parameter_common(void *parm_ptr,
+ struct parm_struct parm)
+{
+ if ((parm.type == P_STRING) ||
+ (parm.type == P_USTRING))
+ {
+ lpcfg_string_free((char**)parm_ptr);
+ } else if (parm.type == P_LIST || parm.type == P_CMDLIST) {
+ TALLOC_FREE(*((char***)parm_ptr));
+ }
+}
+
+/**
+ * Free the allocated data for one parameter for a share
+ * given as a service struct.
+ */
+static void free_one_parameter(struct loadparm_service *service,
+ struct parm_struct parm)
+{
+ void *parm_ptr;
+
+ if (parm.p_class != P_LOCAL) {
+ return;
+ }
+
+ parm_ptr = lp_parm_ptr(service, &parm);
+
+ free_one_parameter_common(parm_ptr, parm);
+}
+
+/**
+ * Free the allocated parameter data of a share given
+ * as a service struct.
+ */
+static void free_parameters(struct loadparm_service *service)
+{
+ uint32_t i;
+
+ for (i=0; parm_table[i].label; i++) {
+ free_one_parameter(service, parm_table[i]);
+ }
+}
+
+/**
+ * Free the allocated data for one parameter for a given share
+ * specified by an snum.
+ */
+static void free_one_parameter_by_snum(int snum, struct parm_struct parm)
+{
+ void *parm_ptr;
+
+ if (snum < 0) {
+ parm_ptr = lp_parm_ptr(NULL, &parm);
+ } else if (parm.p_class != P_LOCAL) {
+ return;
+ } else {
+ parm_ptr = lp_parm_ptr(ServicePtrs[snum], &parm);
+ }
+
+ free_one_parameter_common(parm_ptr, parm);
+}
+
+/**
+ * Free the allocated parameter data for a share specified
+ * by an snum.
+ */
+static void free_parameters_by_snum(int snum)
+{
+ uint32_t i;
+
+ for (i=0; parm_table[i].label; i++) {
+ free_one_parameter_by_snum(snum, parm_table[i]);
+ }
+}
+
+/**
+ * Free the allocated global parameters.
+ */
+static void free_global_parameters(void)
+{
+ uint32_t i;
+ struct parm_struct *parm;
+
+ free_param_opts(&Globals.param_opt);
+ free_parameters_by_snum(GLOBAL_SECTION_SNUM);
+
+ /* Reset references in the defaults because the context is going to be freed */
+ for (i=0; parm_table[i].label; i++) {
+ parm = &parm_table[i];
+ if ((parm->type == P_STRING) ||
+ (parm->type == P_USTRING)) {
+ if ((parm->def.svalue != NULL) &&
+ (*(parm->def.svalue) != '\0')) {
+ if (talloc_parent(parm->def.svalue) == Globals.ctx) {
+ parm->def.svalue = NULL;
+ }
+ }
+ }
+ }
+ TALLOC_FREE(Globals.ctx);
+}
+
+struct lp_stored_option {
+ struct lp_stored_option *prev, *next;
+ const char *label;
+ const char *value;
+};
+
+static struct lp_stored_option *stored_options;
+
+/*
+ save options set by lp_set_cmdline() into a list. This list is
+ re-applied when we do a globals reset, so that cmdline set options
+ are sticky across reloads of smb.conf
+ */
+bool store_lp_set_cmdline(const char *pszParmName, const char *pszParmValue)
+{
+ struct lp_stored_option *entry, *entry_next;
+ for (entry = stored_options; entry != NULL; entry = entry_next) {
+ entry_next = entry->next;
+ if (strcmp(pszParmName, entry->label) == 0) {
+ DLIST_REMOVE(stored_options, entry);
+ talloc_free(entry);
+ break;
+ }
+ }
+
+ entry = talloc(NULL, struct lp_stored_option);
+ if (!entry) {
+ return false;
+ }
+
+ entry->label = talloc_strdup(entry, pszParmName);
+ if (!entry->label) {
+ talloc_free(entry);
+ return false;
+ }
+
+ entry->value = talloc_strdup(entry, pszParmValue);
+ if (!entry->value) {
+ talloc_free(entry);
+ return false;
+ }
+
+ DLIST_ADD_END(stored_options, entry);
+
+ return true;
+}
+
+static bool apply_lp_set_cmdline(void)
+{
+ struct lp_stored_option *entry = NULL;
+ for (entry = stored_options; entry != NULL; entry = entry->next) {
+ if (!lp_set_cmdline_helper(entry->label, entry->value)) {
+ DEBUG(0, ("Failed to re-apply cmdline parameter %s = %s\n",
+ entry->label, entry->value));
+ return false;
+ }
+ }
+ return true;
+}
+
+/***************************************************************************
+ Initialise the global parameter structure.
+***************************************************************************/
+
+void loadparm_s3_init_globals(struct loadparm_context *lp_ctx,
+ bool reinit_globals)
+{
+ static bool done_init = false;
+ char *s = NULL;
+ int i;
+
+ /* If requested to initialize only once and we've already done it... */
+ if (!reinit_globals && done_init) {
+ /* ... then we have nothing more to do */
+ return;
+ }
+
+ if (!done_init) {
+ /* The logfile can be set before this is invoked. Free it if so. */
+ lpcfg_string_free(&Globals.logfile);
+ done_init = true;
+ } else {
+ free_global_parameters();
+ }
+
+ /* This memset and the free_global_parameters() above will
+ * wipe out smb.conf options set with lp_set_cmdline(). The
+ * apply_lp_set_cmdline() call puts these values back in the
+ * table once the defaults are set */
+ ZERO_STRUCT(Globals);
+
+ Globals.ctx = talloc_pooled_object(NULL, char, 272, 2048);
+
+ /* Initialize the flags list if necessary */
+ if (flags_list == NULL) {
+ get_flags();
+ }
+
+ for (i = 0; parm_table[i].label; i++) {
+ if ((parm_table[i].type == P_STRING ||
+ parm_table[i].type == P_USTRING))
+ {
+ lpcfg_string_set(
+ Globals.ctx,
+ (char **)lp_parm_ptr(NULL, &parm_table[i]),
+ "");
+ }
+ }
+
+
+ lpcfg_string_set(Globals.ctx, &sDefault.fstype, FSTYPE_STRING);
+ lpcfg_string_set(Globals.ctx, &sDefault.printjob_username, "%U");
+
+ init_printer_values(lp_ctx, Globals.ctx, &sDefault);
+
+ sDefault.ntvfs_handler = str_list_make_v3_const(Globals.ctx, "unixuid default", NULL);
+
+ DEBUG(3, ("Initialising global parameters\n"));
+
+ /* Must manually force to upper case here, as this does not go via the handler */
+ lpcfg_string_set(Globals.ctx, &Globals.netbios_name,
+ myhostname_upper());
+
+ lpcfg_string_set(Globals.ctx, &Globals.smb_passwd_file,
+ get_dyn_SMB_PASSWD_FILE());
+ lpcfg_string_set(Globals.ctx, &Globals.private_dir,
+ get_dyn_PRIVATE_DIR());
+ lpcfg_string_set(Globals.ctx, &Globals.binddns_dir,
+ get_dyn_BINDDNS_DIR());
+
+ /* use the new 'hash2' method by default, with a prefix of 1 */
+ lpcfg_string_set(Globals.ctx, &Globals.mangling_method, "hash2");
+ Globals.mangle_prefix = 1;
+
+ lpcfg_string_set(Globals.ctx, &Globals.guest_account, GUEST_ACCOUNT);
+
+ /* using UTF8 by default allows us to support all chars */
+ lpcfg_string_set(Globals.ctx, &Globals.unix_charset,
+ DEFAULT_UNIX_CHARSET);
+
+ /* Use codepage 850 as a default for the dos character set */
+ lpcfg_string_set(Globals.ctx, &Globals.dos_charset,
+ DEFAULT_DOS_CHARSET);
+
+ /*
+ * Allow the default PASSWD_CHAT to be overridden in local.h.
+ */
+ lpcfg_string_set(Globals.ctx, &Globals.passwd_chat,
+ DEFAULT_PASSWD_CHAT);
+
+ lpcfg_string_set(Globals.ctx, &Globals.workgroup, DEFAULT_WORKGROUP);
+
+ lpcfg_string_set(Globals.ctx, &Globals.passwd_program, "");
+ lpcfg_string_set(Globals.ctx, &Globals.lock_directory,
+ get_dyn_LOCKDIR());
+ lpcfg_string_set(Globals.ctx, &Globals.state_directory,
+ get_dyn_STATEDIR());
+ lpcfg_string_set(Globals.ctx, &Globals.cache_directory,
+ get_dyn_CACHEDIR());
+ lpcfg_string_set(Globals.ctx, &Globals.pid_directory,
+ get_dyn_PIDDIR());
+ lpcfg_string_set(Globals.ctx, &Globals.nbt_client_socket_address,
+ "0.0.0.0");
+ /*
+ * By default support explicit binding to broadcast
+ * addresses.
+ */
+ Globals.nmbd_bind_explicit_broadcast = true;
+
+ s = talloc_asprintf(Globals.ctx, "Samba %s", samba_version_string());
+ if (s == NULL) {
+ smb_panic("init_globals: ENOMEM");
+ }
+ lpcfg_string_set(Globals.ctx, &Globals.server_string, s);
+ TALLOC_FREE(s);
+#ifdef DEVELOPER
+ lpcfg_string_set(Globals.ctx, &Globals.panic_action,
+ "/bin/sleep 999999999");
+#endif
+
+ lpcfg_string_set(Globals.ctx, &Globals.socket_options,
+ DEFAULT_SOCKET_OPTIONS);
+
+ lpcfg_string_set(Globals.ctx, &Globals.logon_drive, "");
+ /* %N is the NIS auto.home server if -DAUTOHOME is used, else same as %L */
+ lpcfg_string_set(Globals.ctx, &Globals.logon_home, "\\\\%N\\%U");
+ lpcfg_string_set(Globals.ctx, &Globals.logon_path,
+ "\\\\%N\\%U\\profile");
+
+ Globals.name_resolve_order =
+ str_list_make_v3_const(Globals.ctx,
+ DEFAULT_NAME_RESOLVE_ORDER,
+ NULL);
+ lpcfg_string_set(Globals.ctx, &Globals.password_server, "*");
+
+ Globals.algorithmic_rid_base = BASE_RID;
+
+ Globals.load_printers = true;
+ Globals.printcap_cache_time = 750; /* 12.5 minutes */
+
+ Globals.config_backend = config_backend;
+ Globals._server_role = ROLE_AUTO;
+
+ /* Was 65535 (0xFFFF). 0x4101 matches W2K and causes major speed improvements... */
+ /* Discovered by 2 days of pain by Don McCall @ HP :-). */
+ Globals.max_xmit = 0x4104;
+ Globals.max_mux = 50; /* This is *needed* for profile support. */
+ Globals.lpq_cache_time = 30; /* changed to handle large print servers better -- jerry */
+ Globals._disable_spoolss = false;
+ Globals.max_smbd_processes = 0;/* no limit specified */
+ Globals.username_level = 0;
+ Globals.deadtime = 10080;
+ Globals.getwd_cache = true;
+ Globals.large_readwrite = true;
+ Globals.max_log_size = 5000;
+ Globals.max_open_files = max_open_files();
+ Globals.server_max_protocol = PROTOCOL_SMB3_11;
+ Globals.server_min_protocol = PROTOCOL_SMB2_02;
+ Globals._client_max_protocol = PROTOCOL_DEFAULT;
+ Globals.client_min_protocol = PROTOCOL_SMB2_02;
+ Globals._client_ipc_max_protocol = PROTOCOL_DEFAULT;
+ Globals._client_ipc_min_protocol = PROTOCOL_DEFAULT;
+ Globals._security = SEC_AUTO;
+ Globals.encrypt_passwords = true;
+ Globals.client_schannel = true;
+ Globals.winbind_sealed_pipes = true;
+ Globals.require_strong_key = true;
+ Globals.reject_md5_servers = true;
+ Globals.server_schannel = true;
+ Globals.server_schannel_require_seal = true;
+ Globals.reject_md5_clients = true;
+ Globals.read_raw = true;
+ Globals.write_raw = true;
+ Globals.null_passwords = false;
+ Globals.old_password_allowed_period = 60;
+ Globals.obey_pam_restrictions = false;
+ Globals.syslog = 1;
+ Globals.syslog_only = false;
+ Globals.timestamp_logs = true;
+ lpcfg_string_set(Globals.ctx, &Globals.log_level, "0");
+ Globals.debug_prefix_timestamp = false;
+ Globals.debug_hires_timestamp = true;
+ Globals.debug_syslog_format = false;
+ Globals.debug_pid = false;
+ Globals.debug_uid = false;
+ Globals.debug_class = false;
+ Globals.enable_core_files = true;
+ Globals.max_ttl = 60 * 60 * 24 * 3; /* 3 days default. */
+ Globals.max_wins_ttl = 60 * 60 * 24 * 6; /* 6 days default. */
+ Globals.min_wins_ttl = 60 * 60 * 6; /* 6 hours default. */
+ Globals.machine_password_timeout = 60 * 60 * 24 * 7; /* 7 days default. */
+ Globals.lm_announce = Auto; /* = Auto: send only if LM clients found */
+ Globals.lm_interval = 60;
+ Globals.time_server = false;
+ Globals.bind_interfaces_only = false;
+ Globals.unix_password_sync = false;
+ Globals.pam_password_change = false;
+ Globals.passwd_chat_debug = false;
+ Globals.passwd_chat_timeout = 2; /* 2 second default. */
+ Globals.nt_pipe_support = true; /* Do NT pipes by default. */
+ Globals.nt_status_support = true; /* Use NT status by default. */
+ Globals.smbd_profiling_level = 0;
+ Globals.stat_cache = true; /* use stat cache by default */
+ Globals.max_stat_cache_size = 512; /* 512k by default */
+ Globals.restrict_anonymous = 0;
+ Globals.client_lanman_auth = false; /* Do NOT use the LanMan hash if it is available */
+ Globals.client_plaintext_auth = false; /* Do NOT use a plaintext password even if is requested by the server */
+ Globals._lanman_auth = false; /* Do NOT use the LanMan hash, even if it is supplied */
+ Globals.ntlm_auth = NTLM_AUTH_NTLMV2_ONLY; /* Do NOT use NTLMv1 if it is supplied by the client (otherwise NTLMv2) */
+ Globals.nt_hash_store = NT_HASH_STORE_ALWAYS; /* Fill in NT hash when setting password */
+ Globals.raw_ntlmv2_auth = false; /* Reject NTLMv2 without NTLMSSP */
+ Globals.client_ntlmv2_auth = true; /* Client should always use use NTLMv2, as we can't tell that the server supports it, but most modern servers do */
+ /* Note, that we will also use NTLM2 session security (which is different), if it is available */
+
+ Globals.allow_dcerpc_auth_level_connect = false; /* we don't allow this by default */
+
+ Globals.map_to_guest = 0; /* By Default, "Never" */
+ Globals.oplock_break_wait_time = 0; /* By Default, 0 msecs. */
+ Globals.enhanced_browsing = true;
+ Globals.lock_spin_time = WINDOWS_MINIMUM_LOCK_TIMEOUT_MS; /* msec. */
+ Globals.use_mmap = true;
+ Globals.unicode = true;
+ Globals.smb1_unix_extensions = true;
+ Globals.reset_on_zero_vc = false;
+ Globals.log_writeable_files_on_exit = false;
+ Globals.create_krb5_conf = true;
+ Globals.include_system_krb5_conf = true;
+ Globals._winbind_max_domain_connections = 1;
+
+ /* hostname lookups can be very expensive and are broken on
+ a large number of sites (tridge) */
+ Globals.hostname_lookups = false;
+
+ Globals.change_notify = true,
+ Globals.kernel_change_notify = true,
+
+ lpcfg_string_set(Globals.ctx, &Globals.passdb_backend, "tdbsam");
+ lpcfg_string_set(Globals.ctx, &Globals.ldap_suffix, "");
+ lpcfg_string_set(Globals.ctx, &Globals._ldap_machine_suffix, "");
+ lpcfg_string_set(Globals.ctx, &Globals._ldap_user_suffix, "");
+ lpcfg_string_set(Globals.ctx, &Globals._ldap_group_suffix, "");
+ lpcfg_string_set(Globals.ctx, &Globals._ldap_idmap_suffix, "");
+
+ lpcfg_string_set(Globals.ctx, &Globals.ldap_admin_dn, "");
+ Globals.ldap_ssl = LDAP_SSL_START_TLS;
+ Globals.ldap_deref = -1;
+ Globals.ldap_passwd_sync = LDAP_PASSWD_SYNC_OFF;
+ Globals.ldap_delete_dn = false;
+ Globals.ldap_replication_sleep = 1000; /* wait 1 sec for replication */
+ Globals.ldap_follow_referral = Auto;
+ Globals.ldap_timeout = LDAP_DEFAULT_TIMEOUT;
+ Globals.ldap_connection_timeout = LDAP_CONNECTION_DEFAULT_TIMEOUT;
+ Globals.ldap_page_size = LDAP_PAGE_SIZE;
+
+ Globals.ldap_debug_level = 0;
+ Globals.ldap_debug_threshold = 10;
+
+ Globals.client_ldap_sasl_wrapping = ADS_AUTH_SASL_SEAL;
+
+ Globals.ldap_server_require_strong_auth =
+ LDAP_SERVER_REQUIRE_STRONG_AUTH_YES;
+
+ /* This is what we tell the afs client. in reality we set the token
+ * to never expire, though, when this runs out the afs client will
+ * forget the token. Set to 0 to get NEVERDATE.*/
+ Globals.afs_token_lifetime = 604800;
+ Globals.cups_connection_timeout = CUPS_DEFAULT_CONNECTION_TIMEOUT;
+
+/* these parameters are set to defaults that are more appropriate
+ for the increasing samba install base:
+
+ as a member of the workgroup, that will possibly become a
+ _local_ master browser (lm = true). this is opposed to a forced
+ local master browser startup (pm = true).
+
+ doesn't provide WINS server service by default (wsupp = false),
+ and doesn't provide domain master browser services by default, either.
+
+*/
+
+ Globals.show_add_printer_wizard = true;
+ Globals.os_level = 20;
+ Globals.local_master = true;
+ Globals._domain_master = Auto; /* depending on _domain_logons */
+ Globals._domain_logons = false;
+ Globals.browse_list = true;
+ Globals.we_are_a_wins_server = false;
+ Globals.wins_proxy = false;
+
+ TALLOC_FREE(Globals.init_logon_delayed_hosts);
+ Globals.init_logon_delay = 100; /* 100 ms default delay */
+
+ Globals.wins_dns_proxy = true;
+ Globals.dns_port = DNS_SERVICE_PORT;
+
+ Globals.allow_trusted_domains = true;
+ lpcfg_string_set(Globals.ctx, &Globals.idmap_backend, "tdb");
+
+ lpcfg_string_set(Globals.ctx, &Globals.template_shell, "/bin/false");
+ lpcfg_string_set(Globals.ctx, &Globals.template_homedir,
+ "/home/%D/%U");
+ lpcfg_string_set(Globals.ctx, &Globals.winbind_separator, "\\");
+ lpcfg_string_set(Globals.ctx, &Globals.winbindd_socket_directory,
+ dyn_WINBINDD_SOCKET_DIR);
+
+ lpcfg_string_set(Globals.ctx, &Globals.cups_server, "");
+ lpcfg_string_set(Globals.ctx, &Globals.iprint_server, "");
+
+ lpcfg_string_set(Globals.ctx, &Globals._ctdbd_socket, "");
+
+ Globals.cluster_addresses = NULL;
+ Globals.clustering = false;
+ Globals.ctdb_timeout = 0;
+ Globals.ctdb_locktime_warn_threshold = 0;
+
+ Globals.winbind_cache_time = 300; /* 5 minutes */
+ Globals.winbind_reconnect_delay = 30; /* 30 seconds */
+ Globals.winbind_request_timeout = 60; /* 60 seconds */
+ Globals.winbind_max_clients = 200;
+ Globals.winbind_enum_users = false;
+ Globals.winbind_enum_groups = false;
+ Globals.winbind_use_default_domain = false;
+ Globals.winbind_nested_groups = true;
+ Globals.winbind_expand_groups = 0;
+ Globals.winbind_nss_info = str_list_make_v3_const(NULL, "template", NULL);
+ Globals.winbind_refresh_tickets = false;
+ Globals.winbind_offline_logon = false;
+ Globals.winbind_scan_trusted_domains = false;
+
+ Globals.idmap_cache_time = 86400 * 7; /* a week by default */
+ Globals.idmap_negative_cache_time = 120; /* 2 minutes by default */
+
+ Globals.passdb_expand_explicit = false;
+
+ Globals.name_cache_timeout = 660; /* In seconds */
+
+ Globals.client_use_spnego = true;
+
+ Globals.client_signing = SMB_SIGNING_DEFAULT;
+ Globals._client_ipc_signing = SMB_SIGNING_DEFAULT;
+ Globals.server_signing = SMB_SIGNING_DEFAULT;
+
+ Globals.defer_sharing_violations = true;
+ Globals.smb_ports = str_list_make_v3_const(NULL, SMB_PORTS, NULL);
+
+ Globals.enable_privileges = true;
+ Globals.host_msdfs = true;
+ Globals.enable_asu_support = false;
+
+ /* User defined shares. */
+ s = talloc_asprintf(talloc_tos(), "%s/usershares", get_dyn_STATEDIR());
+ if (s == NULL) {
+ smb_panic("init_globals: ENOMEM");
+ }
+ lpcfg_string_set(Globals.ctx, &Globals.usershare_path, s);
+ TALLOC_FREE(s);
+ lpcfg_string_set(Globals.ctx, &Globals.usershare_template_share, "");
+ Globals.usershare_max_shares = 0;
+ /* By default disallow sharing of directories not owned by the sharer. */
+ Globals.usershare_owner_only = true;
+ /* By default disallow guest access to usershares. */
+ Globals.usershare_allow_guests = false;
+
+ Globals.keepalive = DEFAULT_KEEPALIVE;
+
+ /* By default no shares out of the registry */
+ Globals.registry_shares = false;
+
+ Globals.min_receivefile_size = 0;
+
+ Globals.multicast_dns_register = true;
+
+ Globals.smb2_max_read = DEFAULT_SMB2_MAX_READ;
+ Globals.smb2_max_write = DEFAULT_SMB2_MAX_WRITE;
+ Globals.smb2_max_trans = DEFAULT_SMB2_MAX_TRANSACT;
+ Globals.smb2_max_credits = DEFAULT_SMB2_MAX_CREDITS;
+ Globals.smb2_leases = true;
+ Globals.server_multi_channel_support = true;
+
+ lpcfg_string_set(Globals.ctx, &Globals.ncalrpc_dir,
+ get_dyn_NCALRPCDIR());
+
+ Globals.server_services = str_list_make_v3_const(NULL, "s3fs rpc nbt wrepl ldap cldap kdc drepl winbindd ntp_signd kcc dnsupdate dns", NULL);
+
+ Globals.dcerpc_endpoint_servers = str_list_make_v3_const(NULL, "epmapper wkssvc samr netlogon lsarpc drsuapi dssetup unixinfo browser eventlog6 backupkey dnsserver", NULL);
+
+ Globals.tls_enabled = true;
+ Globals.tls_verify_peer = TLS_VERIFY_PEER_AS_STRICT_AS_POSSIBLE;
+
+ lpcfg_string_set(Globals.ctx, &Globals._tls_keyfile, "tls/key.pem");
+ lpcfg_string_set(Globals.ctx, &Globals._tls_certfile, "tls/cert.pem");
+ lpcfg_string_set(Globals.ctx, &Globals._tls_cafile, "tls/ca.pem");
+ lpcfg_string_set(Globals.ctx,
+ &Globals.tls_priority,
+ "NORMAL:-VERS-SSL3.0");
+
+ Globals._preferred_master = Auto;
+
+ Globals.allow_dns_updates = DNS_UPDATE_SIGNED;
+ Globals.dns_zone_scavenging = false;
+
+ lpcfg_string_set(Globals.ctx, &Globals.ntp_signd_socket_directory,
+ get_dyn_NTP_SIGND_SOCKET_DIR());
+
+ s = talloc_asprintf(talloc_tos(), "%s/samba_kcc", get_dyn_SCRIPTSBINDIR());
+ if (s == NULL) {
+ smb_panic("init_globals: ENOMEM");
+ }
+ Globals.samba_kcc_command = str_list_make_v3_const(NULL, s, NULL);
+ TALLOC_FREE(s);
+
+#ifdef MIT_KDC_PATH
+ Globals.mit_kdc_command = str_list_make_v3_const(NULL, MIT_KDC_PATH, NULL);
+#endif
+
+ s = talloc_asprintf(talloc_tos(), "%s/samba_dnsupdate", get_dyn_SCRIPTSBINDIR());
+ if (s == NULL) {
+ smb_panic("init_globals: ENOMEM");
+ }
+ Globals.dns_update_command = str_list_make_v3_const(NULL, s, NULL);
+ TALLOC_FREE(s);
+
+ s = talloc_asprintf(talloc_tos(), "%s/samba-gpupdate", get_dyn_SCRIPTSBINDIR());
+ if (s == NULL) {
+ smb_panic("init_globals: ENOMEM");
+ }
+ Globals.gpo_update_command = str_list_make_v3_const(NULL, s, NULL);
+ TALLOC_FREE(s);
+
+ Globals.apply_group_policies = false;
+
+ s = talloc_asprintf(talloc_tos(), "%s/samba_spnupdate", get_dyn_SCRIPTSBINDIR());
+ if (s == NULL) {
+ smb_panic("init_globals: ENOMEM");
+ }
+ Globals.spn_update_command = str_list_make_v3_const(NULL, s, NULL);
+ TALLOC_FREE(s);
+
+ Globals.nsupdate_command = str_list_make_v3_const(NULL, "/usr/bin/nsupdate -g", NULL);
+
+ Globals.cldap_port = 389;
+
+ Globals.dgram_port = NBT_DGRAM_SERVICE_PORT;
+
+ Globals.nbt_port = NBT_NAME_SERVICE_PORT;
+
+ Globals.krb5_port = 88;
+
+ Globals.kpasswd_port = 464;
+
+ Globals.kdc_enable_fast = true;
+
+ Globals.aio_max_threads = 100;
+
+ lpcfg_string_set(Globals.ctx,
+ &Globals.rpc_server_dynamic_port_range,
+ "49152-65535");
+ Globals.rpc_low_port = SERVER_TCP_LOW_PORT;
+ Globals.rpc_high_port = SERVER_TCP_HIGH_PORT;
+ Globals.prefork_children = 4;
+ Globals.prefork_backoff_increment = 10;
+ Globals.prefork_maximum_backoff = 120;
+
+ Globals.ldap_max_anonymous_request_size = 256000;
+ Globals.ldap_max_authenticated_request_size = 16777216;
+ Globals.ldap_max_search_request_size = 256000;
+
+ /* Async DNS query timeout (in seconds). */
+ Globals.async_dns_timeout = 10;
+
+ Globals.client_smb_encrypt = SMB_ENCRYPTION_DEFAULT;
+
+ Globals._client_use_kerberos = CRED_USE_KERBEROS_DESIRED;
+
+ Globals.client_protection = CRED_CLIENT_PROTECTION_DEFAULT;
+
+ Globals.winbind_use_krb5_enterprise_principals = true;
+
+ Globals.client_smb3_signing_algorithms =
+ str_list_make_v3_const(NULL, DEFAULT_SMB3_SIGNING_ALGORITHMS, NULL);
+ Globals.server_smb3_signing_algorithms =
+ str_list_make_v3_const(NULL, DEFAULT_SMB3_SIGNING_ALGORITHMS, NULL);
+
+ Globals.client_smb3_encryption_algorithms =
+ str_list_make_v3_const(NULL, DEFAULT_SMB3_ENCRYPTION_ALGORITHMS, NULL);
+ Globals.server_smb3_encryption_algorithms =
+ str_list_make_v3_const(NULL, DEFAULT_SMB3_ENCRYPTION_ALGORITHMS, NULL);
+
+ Globals.min_domain_uid = 1000;
+
+ /*
+ * By default allow smbd and winbindd to start samba-dcerpcd as
+ * a named-pipe helper.
+ */
+ Globals.rpc_start_on_demand_helpers = true;
+
+ Globals.ad_dc_functional_level = DS_DOMAIN_FUNCTION_2008_R2,
+
+ Globals.acl_claims_evaluation = ACL_CLAIMS_EVALUATION_AD_DC_ONLY;
+
+ /* Now put back the settings that were set with lp_set_cmdline() */
+ apply_lp_set_cmdline();
+}
+
+/* Convenience routine to setup an lp_context with additional s3 variables */
+static struct loadparm_context *setup_lp_context(TALLOC_CTX *mem_ctx)
+{
+ struct loadparm_context *lp_ctx;
+
+ lp_ctx = loadparm_init_s3(mem_ctx,
+ loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(0, ("loadparm_init_s3 failed\n"));
+ return NULL;
+ }
+
+ lp_ctx->sDefault = talloc_zero(lp_ctx, struct loadparm_service);
+ if (lp_ctx->sDefault == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(lp_ctx);
+ return NULL;
+ }
+
+ *lp_ctx->sDefault = _sDefault;
+ lp_ctx->services = NULL; /* We do not want to access this directly */
+ lp_ctx->bInGlobalSection = bInGlobalSection;
+ lp_ctx->flags = flags_list;
+
+ return lp_ctx;
+}
+
+/*******************************************************************
+ Convenience routine to grab string parameters into talloced memory
+ and run standard_sub_basic on them. The buffers can be written to by
+ callers without affecting the source string.
+********************************************************************/
+
+static char *loadparm_s3_global_substitution_fn(
+ TALLOC_CTX *mem_ctx,
+ const struct loadparm_substitution *lp_sub,
+ const char *s,
+ void *private_data)
+{
+ char *ret;
+
+ /* The follow debug is useful for tracking down memory problems
+ especially if you have an inner loop that is calling a lp_*()
+ function that returns a string. Perhaps this debug should be
+ present all the time? */
+
+#if 0
+ DEBUG(10, ("lp_string(%s)\n", s));
+#endif
+ if (!s) {
+ return NULL;
+ }
+
+ ret = talloc_sub_basic(mem_ctx,
+ get_current_username(),
+ get_current_user_info_domain(),
+ s);
+ if (trim_char(ret, '\"', '\"')) {
+ if (strchr(ret,'\"') != NULL) {
+ TALLOC_FREE(ret);
+ ret = talloc_sub_basic(mem_ctx,
+ get_current_username(),
+ get_current_user_info_domain(),
+ s);
+ }
+ }
+ return ret;
+}
+
+static const struct loadparm_substitution s3_global_substitution = {
+ .substituted_string_fn = loadparm_s3_global_substitution_fn,
+};
+
+const struct loadparm_substitution *loadparm_s3_global_substitution(void)
+{
+ return &s3_global_substitution;
+}
+
+/*
+ In this section all the functions that are used to access the
+ parameters from the rest of the program are defined
+*/
+
+#define FN_GLOBAL_SUBSTITUTED_STRING(fn_name,ptr) \
+char *lp_ ## fn_name(TALLOC_CTX *ctx, const struct loadparm_substitution *lp_sub) \
+ {return lpcfg_substituted_string(ctx, lp_sub, *(char **)(&Globals.ptr) ? *(char **)(&Globals.ptr) : "");}
+#define FN_GLOBAL_CONST_STRING(fn_name,ptr) \
+ const char *lp_ ## fn_name(void) {return(*(const char * const *)(&Globals.ptr) ? *(const char * const *)(&Globals.ptr) : "");}
+#define FN_GLOBAL_LIST(fn_name,ptr) \
+ const char **lp_ ## fn_name(void) {return(*(const char ***)(&Globals.ptr));}
+#define FN_GLOBAL_BOOL(fn_name,ptr) \
+ bool lp_ ## fn_name(void) {return(*(bool *)(&Globals.ptr));}
+#define FN_GLOBAL_CHAR(fn_name,ptr) \
+ char lp_ ## fn_name(void) {return(*(char *)(&Globals.ptr));}
+#define FN_GLOBAL_INTEGER(fn_name,ptr) \
+ int lp_ ## fn_name(void) {return(*(int *)(&Globals.ptr));}
+
+#define FN_LOCAL_SUBSTITUTED_STRING(fn_name,val) \
+char *lp_ ## fn_name(TALLOC_CTX *ctx, const struct loadparm_substitution *lp_sub, int i) \
+ {return lpcfg_substituted_string((ctx), lp_sub, (LP_SNUM_OK(i) && ServicePtrs[(i)]->val) ? ServicePtrs[(i)]->val : sDefault.val);}
+#define FN_LOCAL_CONST_STRING(fn_name,val) \
+ const char *lp_ ## fn_name(int i) {return (const char *)((LP_SNUM_OK(i) && ServicePtrs[(i)]->val) ? ServicePtrs[(i)]->val : sDefault.val);}
+#define FN_LOCAL_LIST(fn_name,val) \
+ const char **lp_ ## fn_name(int i) {return(const char **)(LP_SNUM_OK(i)? ServicePtrs[(i)]->val : sDefault.val);}
+#define FN_LOCAL_BOOL(fn_name,val) \
+ bool lp_ ## fn_name(int i) {return(bool)(LP_SNUM_OK(i)? ServicePtrs[(i)]->val : sDefault.val);}
+#define FN_LOCAL_INTEGER(fn_name,val) \
+ int lp_ ## fn_name(int i) {return(LP_SNUM_OK(i)? ServicePtrs[(i)]->val : sDefault.val);}
+
+#define FN_LOCAL_PARM_BOOL(fn_name,val) \
+ bool lp_ ## fn_name(const struct share_params *p) {return(bool)(LP_SNUM_OK(p->service)? ServicePtrs[(p->service)]->val : sDefault.val);}
+#define FN_LOCAL_PARM_INTEGER(fn_name,val) \
+ int lp_ ## fn_name(const struct share_params *p) {return(LP_SNUM_OK(p->service)? ServicePtrs[(p->service)]->val : sDefault.val);}
+#define FN_LOCAL_PARM_CHAR(fn_name,val) \
+ char lp_ ## fn_name(const struct share_params *p) {return(LP_SNUM_OK(p->service)? ServicePtrs[(p->service)]->val : sDefault.val);}
+
+int lp_winbind_max_domain_connections(void)
+{
+ if (lp_winbind_offline_logon() &&
+ lp__winbind_max_domain_connections() > 1) {
+ DEBUG(1, ("offline logons active, restricting max domain "
+ "connections to 1\n"));
+ return 1;
+ }
+ return MAX(1, lp__winbind_max_domain_connections());
+}
+
+/* These functions remain in source3/param for now */
+
+#include "lib/param/param_functions.c"
+
+FN_LOCAL_SUBSTITUTED_STRING(servicename, szService)
+FN_LOCAL_CONST_STRING(const_servicename, szService)
+
+/* These functions cannot be auto-generated */
+FN_LOCAL_BOOL(autoloaded, autoloaded)
+FN_GLOBAL_CONST_STRING(dnsdomain, dnsdomain)
+
+/* local prototypes */
+
+static int map_parameter_canonical(const char *pszParmName, bool *inverse);
+static const char *get_boolean(bool bool_value);
+static bool do_parameter(const char *pszParmName, const char *pszParmValue,
+ void *userdata);
+static bool hash_a_service(const char *name, int number);
+static void free_service_byindex(int iService);
+static void show_parameter(int parmIndex);
+static bool is_synonym_of(int parm1, int parm2, bool *inverse);
+static bool lp_parameter_value_is_valid(const char *parm_name, const char *val);
+
+/*
+ * This is a helper function for parametrical options support. It returns a
+ * pointer to parametrical option value if it exists or NULL otherwise. Actual
+ * parametrical functions are quite simple
+ */
+static struct parmlist_entry *get_parametrics(int snum, const char *type,
+ const char *option)
+{
+ if (snum >= iNumServices) return NULL;
+
+ if (snum < 0) {
+ return get_parametric_helper(NULL, type, option, Globals.param_opt);
+ } else {
+ return get_parametric_helper(ServicePtrs[snum],
+ type, option, Globals.param_opt);
+ }
+}
+
+static void discard_whitespace(char *str)
+{
+ size_t len = strlen(str);
+ size_t i = 0;
+
+ while (i < len) {
+ if (isspace(str[i])) {
+ memmove(&str[i], &str[i+1], len-i);
+ len -= 1;
+ continue;
+ }
+ i += 1;
+ }
+}
+
+/**
+ * @brief Go through all global parametric parameters
+ *
+ * @param regex_str A regular expression to scan param for
+ * @param max_matches Max number of submatches the regexp expects
+ * @param cb Function to call on match. Should return true
+ * when it wants wi_scan_global_parametrics to stop
+ * scanning
+ * @param private_data Anonymous pointer passed to cb
+ *
+ * @return 0: success, regcomp/regexec return value on error.
+ * See "man regexec" for possible errors
+ */
+
+int lp_wi_scan_global_parametrics(
+ const char *regex_str, size_t max_matches,
+ bool (*cb)(const char *string, regmatch_t matches[],
+ void *private_data),
+ void *private_data)
+{
+ struct parmlist_entry *data;
+ regex_t regex;
+ int ret;
+
+ ret = regcomp(&regex, regex_str, REG_ICASE);
+ if (ret != 0) {
+ return ret;
+ }
+
+ for (data = Globals.param_opt; data != NULL; data = data->next) {
+ size_t keylen = strlen(data->key);
+ char key[keylen+1];
+ regmatch_t matches[max_matches];
+ bool stop;
+
+ memcpy(key, data->key, sizeof(key));
+ discard_whitespace(key);
+
+ ret = regexec(&regex, key, max_matches, matches, 0);
+ if (ret == REG_NOMATCH) {
+ continue;
+ }
+ if (ret != 0) {
+ goto fail;
+ }
+
+ stop = cb(key, matches, private_data);
+ if (stop) {
+ break;
+ }
+ }
+
+ ret = 0;
+fail:
+ regfree(&regex);
+ return ret;
+}
+
+
+#define MISSING_PARAMETER(name) \
+ DEBUG(0, ("%s(): value is NULL or empty!\n", #name))
+
+/*******************************************************************
+convenience routine to return enum parameters.
+********************************************************************/
+static int lp_enum(const char *s,const struct enum_list *_enum)
+{
+ int i;
+
+ if (!s || !*s || !_enum) {
+ MISSING_PARAMETER(lp_enum);
+ return (-1);
+ }
+
+ for (i=0; _enum[i].name; i++) {
+ if (strequal(_enum[i].name,s))
+ return _enum[i].value;
+ }
+
+ DEBUG(0,("lp_enum(%s,enum): value is not in enum_list!\n",s));
+ return (-1);
+}
+
+#undef MISSING_PARAMETER
+
+/* Return parametric option from a given service. Type is a part of option before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+char *lp_parm_substituted_string(TALLOC_CTX *mem_ctx,
+ const struct loadparm_substitution *lp_sub,
+ int snum,
+ const char *type,
+ const char *option,
+ const char *def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ SMB_ASSERT(lp_sub != NULL);
+
+ if (data == NULL||data->value==NULL) {
+ if (def) {
+ return lpcfg_substituted_string(mem_ctx, lp_sub, def);
+ } else {
+ return NULL;
+ }
+ }
+
+ return lpcfg_substituted_string(mem_ctx, lp_sub, data->value);
+}
+
+/* Return parametric option from a given service. Type is a part of option before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+const char *lp_parm_const_string(int snum, const char *type, const char *option, const char *def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ if (data == NULL||data->value==NULL)
+ return def;
+
+ return data->value;
+}
+
+
+/* Return parametric option from a given service. Type is a part of option before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+
+const char **lp_parm_string_list(int snum, const char *type, const char *option, const char **def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ if (data == NULL||data->value==NULL)
+ return (const char **)def;
+
+ if (data->list==NULL) {
+ data->list = str_list_make_v3(NULL, data->value, NULL);
+ }
+
+ return discard_const_p(const char *, data->list);
+}
+
+/* Return parametric option from a given service. Type is a part of option before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+
+int lp_parm_int(int snum, const char *type, const char *option, int def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ if (data && data->value && *data->value)
+ return lp_int(data->value);
+
+ return def;
+}
+
+/* Return parametric option from a given service. Type is a part of option before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+
+unsigned long lp_parm_ulong(int snum, const char *type, const char *option, unsigned long def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ if (data && data->value && *data->value)
+ return lp_ulong(data->value);
+
+ return def;
+}
+
+/* Return parametric option from a given service. Type is a part of option before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+
+unsigned long long lp_parm_ulonglong(int snum, const char *type,
+ const char *option, unsigned long long def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ if (data && data->value && *data->value) {
+ return lp_ulonglong(data->value);
+ }
+
+ return def;
+}
+
+/* Return parametric option from a given service. Type is a part of option
+ * before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+
+bool lp_parm_bool(int snum, const char *type, const char *option, bool def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ if (data && data->value && *data->value)
+ return lp_bool(data->value);
+
+ return def;
+}
+
+/* Return parametric option from a given service. Type is a part of option before ':' */
+/* Parametric option has following syntax: 'Type: option = value' */
+
+int lp_parm_enum(int snum, const char *type, const char *option,
+ const struct enum_list *_enum, int def)
+{
+ struct parmlist_entry *data = get_parametrics(snum, type, option);
+
+ if (data && data->value && *data->value && _enum)
+ return lp_enum(data->value, _enum);
+
+ return def;
+}
+
+/**
+ * free a param_opts structure.
+ * param_opts handling should be moved to talloc;
+ * then this whole functions reduces to a TALLOC_FREE().
+ */
+
+static void free_param_opts(struct parmlist_entry **popts)
+{
+ struct parmlist_entry *opt, *next_opt;
+
+ if (*popts != NULL) {
+ DEBUG(5, ("Freeing parametrics:\n"));
+ }
+ opt = *popts;
+ while (opt != NULL) {
+ lpcfg_string_free(&opt->key);
+ lpcfg_string_free(&opt->value);
+ TALLOC_FREE(opt->list);
+ next_opt = opt->next;
+ TALLOC_FREE(opt);
+ opt = next_opt;
+ }
+ *popts = NULL;
+}
+
+/***************************************************************************
+ Free the dynamically allocated parts of a service struct.
+***************************************************************************/
+
+static void free_service(struct loadparm_service *pservice)
+{
+ if (!pservice)
+ return;
+
+ if (pservice->szService)
+ DEBUG(5, ("free_service: Freeing service %s\n",
+ pservice->szService));
+
+ free_parameters(pservice);
+
+ lpcfg_string_free(&pservice->szService);
+ TALLOC_FREE(pservice->copymap);
+
+ free_param_opts(&pservice->param_opt);
+
+ ZERO_STRUCTP(pservice);
+}
+
+
+/***************************************************************************
+ remove a service indexed in the ServicePtrs array from the ServiceHash
+ and free the dynamically allocated parts
+***************************************************************************/
+
+static void free_service_byindex(int idx)
+{
+ if ( !LP_SNUM_OK(idx) )
+ return;
+
+ ServicePtrs[idx]->valid = false;
+
+ /* we have to cleanup the hash record */
+
+ if (ServicePtrs[idx]->szService) {
+ char *canon_name = canonicalize_servicename(
+ talloc_tos(),
+ ServicePtrs[idx]->szService );
+
+ dbwrap_delete_bystring(ServiceHash, canon_name );
+ TALLOC_FREE(canon_name);
+ }
+
+ free_service(ServicePtrs[idx]);
+ TALLOC_FREE(ServicePtrs[idx]);
+}
+
+/***************************************************************************
+ Add a new service to the services array initialising it with the given
+ service.
+***************************************************************************/
+
+static int add_a_service(const struct loadparm_service *pservice, const char *name)
+{
+ int i;
+ struct loadparm_service **tsp = NULL;
+
+ /* it might already exist */
+ if (name) {
+ i = getservicebyname(name, NULL);
+ if (i >= 0) {
+ return (i);
+ }
+ }
+
+ /* Re use empty slots if any before allocating new one.*/
+ for (i=0; i < iNumServices; i++) {
+ if (ServicePtrs[i] == NULL) {
+ break;
+ }
+ }
+ if (i == iNumServices) {
+ /* if not, then create one */
+ tsp = talloc_realloc(NULL, ServicePtrs,
+ struct loadparm_service *,
+ iNumServices + 1);
+ if (tsp == NULL) {
+ DEBUG(0, ("add_a_service: failed to enlarge "
+ "ServicePtrs!\n"));
+ return (-1);
+ }
+ ServicePtrs = tsp;
+ iNumServices++;
+ }
+ ServicePtrs[i] = talloc_zero(ServicePtrs, struct loadparm_service);
+ if (!ServicePtrs[i]) {
+ DEBUG(0,("add_a_service: out of memory!\n"));
+ return (-1);
+ }
+
+ ServicePtrs[i]->valid = true;
+
+ copy_service(ServicePtrs[i], pservice, NULL);
+ if (name)
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->szService,
+ name);
+
+ DEBUG(8,("add_a_service: Creating snum = %d for %s\n",
+ i, ServicePtrs[i]->szService));
+
+ if (!hash_a_service(ServicePtrs[i]->szService, i)) {
+ return (-1);
+ }
+
+ return (i);
+}
+
+/***************************************************************************
+ Convert a string to uppercase and remove whitespaces.
+***************************************************************************/
+
+char *canonicalize_servicename(TALLOC_CTX *ctx, const char *src)
+{
+ char *result;
+
+ if ( !src ) {
+ DEBUG(0,("canonicalize_servicename: NULL source name!\n"));
+ return NULL;
+ }
+
+ result = talloc_strdup(ctx, src);
+ SMB_ASSERT(result != NULL);
+
+ if (!strlower_m(result)) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ return result;
+}
+
+/***************************************************************************
+ Add a name/index pair for the services array to the hash table.
+***************************************************************************/
+
+static bool hash_a_service(const char *name, int idx)
+{
+ char *canon_name;
+
+ if ( !ServiceHash ) {
+ DEBUG(10,("hash_a_service: creating servicehash\n"));
+ ServiceHash = db_open_rbt(NULL);
+ if ( !ServiceHash ) {
+ DEBUG(0,("hash_a_service: open tdb servicehash failed!\n"));
+ return false;
+ }
+ }
+
+ DEBUG(10,("hash_a_service: hashing index %d for service name %s\n",
+ idx, name));
+
+ canon_name = canonicalize_servicename(talloc_tos(), name );
+
+ dbwrap_store_bystring(ServiceHash, canon_name,
+ make_tdb_data((uint8_t *)&idx, sizeof(idx)),
+ TDB_REPLACE);
+
+ TALLOC_FREE(canon_name);
+
+ return true;
+}
+
+/***************************************************************************
+ Add a new home service, with the specified home directory, defaults coming
+ from service ifrom.
+***************************************************************************/
+
+bool lp_add_home(const char *pszHomename, int iDefaultService,
+ const char *user, const char *pszHomedir)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int i;
+ char *global_path;
+
+ if (pszHomename == NULL || user == NULL || pszHomedir == NULL ||
+ pszHomedir[0] == '\0') {
+ return false;
+ }
+
+ i = add_a_service(ServicePtrs[iDefaultService], pszHomename);
+
+ if (i < 0)
+ return false;
+
+ global_path = lp_path(talloc_tos(), lp_sub, GLOBAL_SECTION_SNUM);
+ if (!(*(ServicePtrs[iDefaultService]->path))
+ || strequal(ServicePtrs[iDefaultService]->path, global_path)) {
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->path,
+ pszHomedir);
+ }
+ TALLOC_FREE(global_path);
+
+ if (!(*(ServicePtrs[i]->comment))) {
+ char *comment = talloc_asprintf(talloc_tos(), "Home directory of %s", user);
+ if (comment == NULL) {
+ return false;
+ }
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->comment,
+ comment);
+ TALLOC_FREE(comment);
+ }
+
+ /* set the browseable flag from the global default */
+
+ ServicePtrs[i]->browseable = sDefault.browseable;
+ ServicePtrs[i]->access_based_share_enum = sDefault.access_based_share_enum;
+
+ ServicePtrs[i]->autoloaded = true;
+
+ DEBUG(3, ("adding home's share [%s] for user '%s' at '%s'\n", pszHomename,
+ user, ServicePtrs[i]->path ));
+
+ return true;
+}
+
+/***************************************************************************
+ Add a new service, based on an old one.
+***************************************************************************/
+
+int lp_add_service(const char *pszService, int iDefaultService)
+{
+ if (iDefaultService < 0) {
+ return add_a_service(&sDefault, pszService);
+ }
+
+ return (add_a_service(ServicePtrs[iDefaultService], pszService));
+}
+
+/***************************************************************************
+ Add the IPC service.
+***************************************************************************/
+
+static bool lp_add_ipc(const char *ipc_name, bool guest_ok)
+{
+ char *comment = NULL;
+ int i = add_a_service(&sDefault, ipc_name);
+
+ if (i < 0)
+ return false;
+
+ comment = talloc_asprintf(talloc_tos(), "IPC Service (%s)",
+ Globals.server_string);
+ if (comment == NULL) {
+ return false;
+ }
+
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->path, tmpdir());
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->comment, comment);
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->fstype, "IPC");
+ ServicePtrs[i]->max_connections = 0;
+ ServicePtrs[i]->available = true;
+ ServicePtrs[i]->read_only = true;
+ ServicePtrs[i]->guest_only = false;
+ ServicePtrs[i]->administrative_share = true;
+ ServicePtrs[i]->guest_ok = guest_ok;
+ ServicePtrs[i]->printable = false;
+ ServicePtrs[i]->browseable = sDefault.browseable;
+ ServicePtrs[i]->autoloaded = false;
+
+ DEBUG(3, ("adding IPC service\n"));
+
+ TALLOC_FREE(comment);
+ return true;
+}
+
+/***************************************************************************
+ Add a new printer service, with defaults coming from service iFrom.
+***************************************************************************/
+
+bool lp_add_printer(const char *pszPrintername, int iDefaultService)
+{
+ const char *comment = "From Printcap";
+ int i = add_a_service(ServicePtrs[iDefaultService], pszPrintername);
+
+ if (i < 0)
+ return false;
+
+ /* note that we do NOT default the availability flag to true - */
+ /* we take it from the default service passed. This allows all */
+ /* dynamic printers to be disabled by disabling the [printers] */
+ /* entry (if/when the 'available' keyword is implemented!). */
+
+ /* the printer name is set to the service name. */
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->_printername,
+ pszPrintername);
+ lpcfg_string_set(ServicePtrs[i], &ServicePtrs[i]->comment, comment);
+
+ /* set the browseable flag from the global default */
+ ServicePtrs[i]->browseable = sDefault.browseable;
+
+ /* Printers cannot be read_only. */
+ ServicePtrs[i]->read_only = false;
+ /* No oplocks on printer services. */
+ ServicePtrs[i]->oplocks = false;
+ /* Printer services must be printable. */
+ ServicePtrs[i]->printable = true;
+
+ DEBUG(3, ("adding printer service %s\n", pszPrintername));
+
+ return true;
+}
+
+
+/***************************************************************************
+ Check whether the given parameter name is valid.
+ Parametric options (names containing a colon) are considered valid.
+***************************************************************************/
+
+bool lp_parameter_is_valid(const char *pszParmName)
+{
+ return ((lpcfg_map_parameter(pszParmName) != -1) ||
+ (strchr(pszParmName, ':') != NULL));
+}
+
+/***************************************************************************
+ Check whether the given name is the name of a global parameter.
+ Returns true for strings belonging to parameters of class
+ P_GLOBAL, false for all other strings, also for parametric options
+ and strings not belonging to any option.
+***************************************************************************/
+
+bool lp_parameter_is_global(const char *pszParmName)
+{
+ int num = lpcfg_map_parameter(pszParmName);
+
+ if (num >= 0) {
+ return (parm_table[num].p_class == P_GLOBAL);
+ }
+
+ return false;
+}
+
+/**************************************************************************
+ Determine the canonical name for a parameter.
+ Indicate when it is an inverse (boolean) synonym instead of a
+ "usual" synonym.
+**************************************************************************/
+
+bool lp_canonicalize_parameter(const char *parm_name, const char **canon_parm,
+ bool *inverse)
+{
+ int num;
+
+ if (!lp_parameter_is_valid(parm_name)) {
+ *canon_parm = NULL;
+ return false;
+ }
+
+ num = map_parameter_canonical(parm_name, inverse);
+ if (num < 0) {
+ /* parametric option */
+ *canon_parm = parm_name;
+ } else {
+ *canon_parm = parm_table[num].label;
+ }
+
+ return true;
+
+}
+
+/**************************************************************************
+ Determine the canonical name for a parameter.
+ Turn the value given into the inverse boolean expression when
+ the synonym is an inverse boolean synonym.
+
+ Return true if
+ - parm_name is a valid parameter name and
+ - val is a valid value for this parameter and
+ - in case the parameter is an inverse boolean synonym, if the val
+ string could successfully be converted to the reverse bool.
+ Return false in all other cases.
+**************************************************************************/
+
+bool lp_canonicalize_parameter_with_value(const char *parm_name,
+ const char *val,
+ const char **canon_parm,
+ const char **canon_val)
+{
+ int num;
+ bool inverse;
+ bool ret;
+
+ if (!lp_parameter_is_valid(parm_name)) {
+ *canon_parm = NULL;
+ *canon_val = NULL;
+ return false;
+ }
+
+ num = map_parameter_canonical(parm_name, &inverse);
+ if (num < 0) {
+ /* parametric option */
+ *canon_parm = parm_name;
+ *canon_val = val;
+ return true;
+ }
+
+ *canon_parm = parm_table[num].label;
+ if (inverse) {
+ if (!lp_invert_boolean(val, canon_val)) {
+ *canon_val = NULL;
+ return false;
+ }
+ } else {
+ *canon_val = val;
+ }
+
+ ret = lp_parameter_value_is_valid(*canon_parm, *canon_val);
+
+ return ret;
+}
+
+/***************************************************************************
+ Map a parameter's string representation to the index of the canonical
+ form of the parameter (it might be a synonym).
+ Returns -1 if the parameter string is not recognised.
+***************************************************************************/
+
+static int map_parameter_canonical(const char *pszParmName, bool *inverse)
+{
+ int parm_num, canon_num;
+ bool loc_inverse = false;
+
+ parm_num = lpcfg_map_parameter(pszParmName);
+ if ((parm_num < 0) || !(parm_table[parm_num].flags & FLAG_SYNONYM)) {
+ /* invalid, parametric or no candidate for synonyms ... */
+ goto done;
+ }
+
+ for (canon_num = 0; parm_table[canon_num].label; canon_num++) {
+ if (is_synonym_of(parm_num, canon_num, &loc_inverse)) {
+ parm_num = canon_num;
+ goto done;
+ }
+ }
+
+done:
+ if (inverse != NULL) {
+ *inverse = loc_inverse;
+ }
+ return parm_num;
+}
+
+/***************************************************************************
+ return true if parameter number parm1 is a synonym of parameter
+ number parm2 (parm2 being the principal name).
+ set inverse to true if parm1 is P_BOOLREV and parm2 is P_BOOL,
+ false otherwise.
+***************************************************************************/
+
+static bool is_synonym_of(int parm1, int parm2, bool *inverse)
+{
+ if ((parm_table[parm1].offset == parm_table[parm2].offset) &&
+ (parm_table[parm1].p_class == parm_table[parm2].p_class) &&
+ (parm_table[parm1].flags & FLAG_SYNONYM) &&
+ !(parm_table[parm2].flags & FLAG_SYNONYM))
+ {
+ if (inverse != NULL) {
+ if ((parm_table[parm1].type == P_BOOLREV) &&
+ (parm_table[parm2].type == P_BOOL))
+ {
+ *inverse = true;
+ } else {
+ *inverse = false;
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+/***************************************************************************
+ Show one parameter's name, type, [values,] and flags.
+ (helper functions for show_parameter_list)
+***************************************************************************/
+
+static void show_parameter(int parmIndex)
+{
+ size_t enumIndex, flagIndex;
+ size_t parmIndex2;
+ bool hadFlag;
+ bool hadSyn;
+ bool inverse;
+ const char *type[] = { "P_BOOL", "P_BOOLREV", "P_CHAR", "P_INTEGER",
+ "P_OCTAL", "P_LIST", "P_STRING", "P_USTRING",
+ "P_ENUM", "P_BYTES", "P_CMDLIST" };
+ unsigned flags[] = { FLAG_DEPRECATED, FLAG_SYNONYM };
+ const char *flag_names[] = { "FLAG_DEPRECATED", "FLAG_SYNONYM", NULL};
+
+ printf("%s=%s", parm_table[parmIndex].label,
+ type[parm_table[parmIndex].type]);
+ if (parm_table[parmIndex].type == P_ENUM) {
+ printf(",");
+ for (enumIndex=0;
+ parm_table[parmIndex].enum_list[enumIndex].name;
+ enumIndex++)
+ {
+ printf("%s%s",
+ enumIndex ? "|" : "",
+ parm_table[parmIndex].enum_list[enumIndex].name);
+ }
+ }
+ printf(",");
+ hadFlag = false;
+ for (flagIndex=0; flag_names[flagIndex]; flagIndex++) {
+ if (parm_table[parmIndex].flags & flags[flagIndex]) {
+ printf("%s%s",
+ hadFlag ? "|" : "",
+ flag_names[flagIndex]);
+ hadFlag = true;
+ }
+ }
+
+ /* output synonyms */
+ hadSyn = false;
+ for (parmIndex2=0; parm_table[parmIndex2].label; parmIndex2++) {
+ if (is_synonym_of(parmIndex, parmIndex2, &inverse)) {
+ printf(" (%ssynonym of %s)", inverse ? "inverse " : "",
+ parm_table[parmIndex2].label);
+ } else if (is_synonym_of(parmIndex2, parmIndex, &inverse)) {
+ if (!hadSyn) {
+ printf(" (synonyms: ");
+ hadSyn = true;
+ } else {
+ printf(", ");
+ }
+ printf("%s%s", parm_table[parmIndex2].label,
+ inverse ? "[i]" : "");
+ }
+ }
+ if (hadSyn) {
+ printf(")");
+ }
+
+ printf("\n");
+}
+
+/*
+ * Check the value for a P_ENUM
+ */
+static bool check_enum_parameter(struct parm_struct *parm, const char *value)
+{
+ int i;
+
+ for (i = 0; parm->enum_list[i].name; i++) {
+ if (strwicmp(value, parm->enum_list[i].name) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**************************************************************************
+ Check whether the given value is valid for the given parameter name.
+**************************************************************************/
+
+static bool lp_parameter_value_is_valid(const char *parm_name, const char *val)
+{
+ bool ret = false, tmp_bool;
+ int num = lpcfg_map_parameter(parm_name), tmp_int;
+ uint64_t tmp_int64 = 0;
+ struct parm_struct *parm;
+
+ /* parametric options (parameter names containing a colon) cannot
+ be checked and are therefore considered valid. */
+ if (strchr(parm_name, ':') != NULL) {
+ return true;
+ }
+
+ if (num >= 0) {
+ parm = &parm_table[num];
+ switch (parm->type) {
+ case P_BOOL:
+ case P_BOOLREV:
+ ret = set_boolean(val, &tmp_bool);
+ break;
+
+ case P_INTEGER:
+ ret = (sscanf(val, "%d", &tmp_int) == 1);
+ break;
+
+ case P_OCTAL:
+ ret = (sscanf(val, "%o", &tmp_int) == 1);
+ break;
+
+ case P_ENUM:
+ ret = check_enum_parameter(parm, val);
+ break;
+
+ case P_BYTES:
+ if (conv_str_size_error(val, &tmp_int64) &&
+ tmp_int64 <= INT_MAX) {
+ ret = true;
+ }
+ break;
+
+ case P_CHAR:
+ case P_LIST:
+ case P_STRING:
+ case P_USTRING:
+ case P_CMDLIST:
+ ret = true;
+ break;
+ }
+ }
+ return ret;
+}
+
+/***************************************************************************
+ Show all parameter's name, type, [values,] and flags.
+***************************************************************************/
+
+void show_parameter_list(void)
+{
+ int classIndex, parmIndex;
+ const char *section_names[] = { "local", "global", NULL};
+
+ for (classIndex=0; section_names[classIndex]; classIndex++) {
+ printf("[%s]\n", section_names[classIndex]);
+ for (parmIndex = 0; parm_table[parmIndex].label; parmIndex++) {
+ if (parm_table[parmIndex].p_class == classIndex) {
+ show_parameter(parmIndex);
+ }
+ }
+ }
+}
+
+/***************************************************************************
+ Get the standard string representation of a boolean value ("yes" or "no")
+***************************************************************************/
+
+static const char *get_boolean(bool bool_value)
+{
+ static const char *yes_str = "yes";
+ static const char *no_str = "no";
+
+ return (bool_value ? yes_str : no_str);
+}
+
+/***************************************************************************
+ Provide the string of the negated boolean value associated to the boolean
+ given as a string. Returns false if the passed string does not correctly
+ represent a boolean.
+***************************************************************************/
+
+bool lp_invert_boolean(const char *str, const char **inverse_str)
+{
+ bool val;
+
+ if (!set_boolean(str, &val)) {
+ return false;
+ }
+
+ *inverse_str = get_boolean(!val);
+ return true;
+}
+
+/***************************************************************************
+ Provide the canonical string representation of a boolean value given
+ as a string. Return true on success, false if the string given does
+ not correctly represent a boolean.
+***************************************************************************/
+
+bool lp_canonicalize_boolean(const char *str, const char**canon_str)
+{
+ bool val;
+
+ if (!set_boolean(str, &val)) {
+ return false;
+ }
+
+ *canon_str = get_boolean(val);
+ return true;
+}
+
+/***************************************************************************
+Find a service by name. Otherwise works like get_service.
+***************************************************************************/
+
+int getservicebyname(const char *pszServiceName, struct loadparm_service *pserviceDest)
+{
+ int iService = -1;
+ char *canon_name;
+ TDB_DATA data;
+ NTSTATUS status;
+
+ if (ServiceHash == NULL) {
+ return -1;
+ }
+
+ canon_name = canonicalize_servicename(talloc_tos(), pszServiceName);
+
+ status = dbwrap_fetch_bystring(ServiceHash, canon_name, canon_name,
+ &data);
+
+ if (NT_STATUS_IS_OK(status) &&
+ (data.dptr != NULL) &&
+ (data.dsize == sizeof(iService)))
+ {
+ memcpy(&iService, data.dptr, sizeof(iService));
+ }
+
+ TALLOC_FREE(canon_name);
+
+ if ((iService != -1) && (LP_SNUM_OK(iService))
+ && (pserviceDest != NULL)) {
+ copy_service(pserviceDest, ServicePtrs[iService], NULL);
+ }
+
+ return (iService);
+}
+
+/* Return a pointer to a service by name. Unlike getservicebyname, it does not copy the service */
+struct loadparm_service *lp_service(const char *pszServiceName)
+{
+ int iService = getservicebyname(pszServiceName, NULL);
+ if (iService == -1 || !LP_SNUM_OK(iService)) {
+ return NULL;
+ }
+ return ServicePtrs[iService];
+}
+
+struct loadparm_service *lp_servicebynum(int snum)
+{
+ if ((snum == -1) || !LP_SNUM_OK(snum)) {
+ return NULL;
+ }
+ return ServicePtrs[snum];
+}
+
+struct loadparm_service *lp_default_loadparm_service(void)
+{
+ return &sDefault;
+}
+
+static struct smbconf_ctx *lp_smbconf_ctx(void)
+{
+ sbcErr err;
+ static struct smbconf_ctx *conf_ctx = NULL;
+
+ if (conf_ctx == NULL) {
+ err = smbconf_init(NULL, &conf_ctx, "registry:");
+ if (!SBC_ERROR_IS_OK(err)) {
+ DEBUG(1, ("error initializing registry configuration: "
+ "%s\n", sbcErrorString(err)));
+ conf_ctx = NULL;
+ }
+ }
+
+ return conf_ctx;
+}
+
+static bool process_smbconf_service(struct smbconf_service *service)
+{
+ uint32_t count;
+ bool ret;
+
+ if (service == NULL) {
+ return false;
+ }
+
+ ret = lp_do_section(service->name, NULL);
+ if (ret != true) {
+ return false;
+ }
+ for (count = 0; count < service->num_params; count++) {
+
+ if (!bInGlobalSection && bGlobalOnly) {
+ ret = true;
+ } else {
+ const char *pszParmName = service->param_names[count];
+ const char *pszParmValue = service->param_values[count];
+
+ DEBUGADD(4, ("doing parameter %s = %s\n", pszParmName, pszParmValue));
+
+ ret = lp_do_parameter(bInGlobalSection ? -2 : iServiceIndex,
+ pszParmName, pszParmValue);
+ }
+
+ if (ret != true) {
+ return false;
+ }
+ }
+ if (iServiceIndex >= 0) {
+ return lpcfg_service_ok(ServicePtrs[iServiceIndex]);
+ }
+ return true;
+}
+
+/**
+ * load a service from registry and activate it
+ */
+bool process_registry_service(const char *service_name)
+{
+ sbcErr err;
+ struct smbconf_service *service = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct smbconf_ctx *conf_ctx = lp_smbconf_ctx();
+ bool ret = false;
+
+ if (conf_ctx == NULL) {
+ goto done;
+ }
+
+ DEBUG(5, ("process_registry_service: service name %s\n", service_name));
+
+ if (!smbconf_share_exists(conf_ctx, service_name)) {
+ /*
+ * Registry does not contain data for this service (yet),
+ * but make sure lp_load doesn't return false.
+ */
+ ret = true;
+ goto done;
+ }
+
+ err = smbconf_get_share(conf_ctx, mem_ctx, service_name, &service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ ret = process_smbconf_service(service);
+ if (!ret) {
+ goto done;
+ }
+
+ /* store the csn */
+ smbconf_changed(conf_ctx, &conf_last_csn, NULL, NULL);
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+/*
+ * process_registry_globals
+ */
+static bool process_registry_globals(void)
+{
+ bool ret;
+
+ add_to_file_list(NULL, &file_lists, INCLUDE_REGISTRY_NAME, INCLUDE_REGISTRY_NAME);
+
+ if (!bInGlobalSection && bGlobalOnly) {
+ ret = true;
+ } else {
+ const char *pszParmName = "registry shares";
+ const char *pszParmValue = "yes";
+
+ DEBUGADD(4, ("doing parameter %s = %s\n", pszParmName, pszParmValue));
+
+ ret = lp_do_parameter(bInGlobalSection ? -2 : iServiceIndex,
+ pszParmName, pszParmValue);
+ }
+
+ if (!ret) {
+ return ret;
+ }
+
+ return process_registry_service(GLOBAL_NAME);
+}
+
+bool process_registry_shares(void)
+{
+ sbcErr err;
+ uint32_t count;
+ struct smbconf_service **service = NULL;
+ uint32_t num_shares = 0;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct smbconf_ctx *conf_ctx = lp_smbconf_ctx();
+ bool ret = false;
+
+ if (conf_ctx == NULL) {
+ goto done;
+ }
+
+ err = smbconf_get_config(conf_ctx, mem_ctx, &num_shares, &service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ ret = true;
+
+ for (count = 0; count < num_shares; count++) {
+ if (strequal(service[count]->name, GLOBAL_NAME)) {
+ continue;
+ }
+ ret = process_smbconf_service(service[count]);
+ if (!ret) {
+ goto done;
+ }
+ }
+
+ /* store the csn */
+ smbconf_changed(conf_ctx, &conf_last_csn, NULL, NULL);
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+/**
+ * reload those shares from registry that are already
+ * activated in the services array.
+ */
+static bool reload_registry_shares(void)
+{
+ int i;
+ bool ret = true;
+
+ for (i = 0; i < iNumServices; i++) {
+ if (!VALID(i)) {
+ continue;
+ }
+
+ if (ServicePtrs[i]->usershare == USERSHARE_VALID) {
+ continue;
+ }
+
+ ret = process_registry_service(ServicePtrs[i]->szService);
+ if (!ret) {
+ goto done;
+ }
+ }
+
+done:
+ return ret;
+}
+
+
+#define MAX_INCLUDE_DEPTH 100
+
+static uint8_t include_depth;
+
+/**
+ * Free the file lists
+ */
+static void free_file_list(void)
+{
+ struct file_lists *f;
+ struct file_lists *next;
+
+ f = file_lists;
+ while( f ) {
+ next = f->next;
+ TALLOC_FREE( f );
+ f = next;
+ }
+ file_lists = NULL;
+}
+
+
+/**
+ * Utility function for outsiders to check if we're running on registry.
+ */
+bool lp_config_backend_is_registry(void)
+{
+ return (lp_config_backend() == CONFIG_BACKEND_REGISTRY);
+}
+
+/**
+ * Utility function to check if the config backend is FILE.
+ */
+bool lp_config_backend_is_file(void)
+{
+ return (lp_config_backend() == CONFIG_BACKEND_FILE);
+}
+
+/*******************************************************************
+ Check if a config file has changed date.
+********************************************************************/
+
+bool lp_file_list_changed(void)
+{
+ struct file_lists *f = file_lists;
+
+ DEBUG(6, ("lp_file_list_changed()\n"));
+
+ while (f) {
+ if (strequal(f->name, INCLUDE_REGISTRY_NAME)) {
+ struct smbconf_ctx *conf_ctx = lp_smbconf_ctx();
+
+ if (conf_ctx == NULL) {
+ return false;
+ }
+ if (smbconf_changed(conf_ctx, &conf_last_csn, NULL,
+ NULL))
+ {
+ DEBUGADD(6, ("registry config changed\n"));
+ return true;
+ }
+ } else {
+ struct timespec mod_time = {
+ .tv_sec = 0,
+ };
+ struct timeval_buf tbuf = {
+ .buf = {0},
+ };
+ char *n2 = NULL;
+ struct stat sb = {0};
+ int rc;
+
+ n2 = talloc_sub_basic(talloc_tos(),
+ get_current_username(),
+ get_current_user_info_domain(),
+ f->name);
+ if (!n2) {
+ return false;
+ }
+ DEBUGADD(6, ("file %s -> %s last mod_time: %s\n",
+ f->name, n2,
+ timespec_string_buf(&f->modtime,
+ true,
+ &tbuf)));
+
+ rc = stat(n2, &sb);
+ if (rc == 0) {
+ mod_time = get_mtimespec(&sb);
+ }
+
+ if (mod_time.tv_sec > 0 &&
+ ((timespec_compare(&mod_time, &f->modtime) != 0) ||
+ (f->subfname == NULL) ||
+ (strcmp(n2, f->subfname) != 0)))
+ {
+ f->modtime = mod_time;
+
+ DEBUGADD(6,
+ ("file %s modified: %s\n", n2,
+ timespec_string_buf(&f->modtime,
+ true,
+ &tbuf)));
+
+ TALLOC_FREE(f->subfname);
+ f->subfname = talloc_strdup(f, n2);
+ if (f->subfname == NULL) {
+ smb_panic("talloc_strdup failed");
+ }
+ TALLOC_FREE(n2);
+ return true;
+ }
+ TALLOC_FREE(n2);
+ }
+ f = f->next;
+ }
+ return false;
+}
+
+
+/**
+ * Initialize iconv conversion descriptors.
+ *
+ * This is called the first time it is needed, and also called again
+ * every time the configuration is reloaded, because the charset or
+ * codepage might have changed.
+ **/
+static void init_iconv(void)
+{
+ struct smb_iconv_handle *ret = NULL;
+
+ ret = reinit_iconv_handle(NULL,
+ lp_dos_charset(),
+ lp_unix_charset());
+ if (ret == NULL) {
+ smb_panic("reinit_iconv_handle failed");
+ }
+}
+
+/***************************************************************************
+ Handle the include operation.
+***************************************************************************/
+static bool bAllowIncludeRegistry = true;
+
+bool lp_include(struct loadparm_context *lp_ctx, struct loadparm_service *service,
+ const char *pszParmValue, char **ptr)
+{
+ char *fname;
+
+ if (include_depth >= MAX_INCLUDE_DEPTH) {
+ DEBUG(0, ("Error: Maximum include depth (%u) exceeded!\n",
+ include_depth));
+ return false;
+ }
+
+ if (strequal(pszParmValue, INCLUDE_REGISTRY_NAME)) {
+ if (!bAllowIncludeRegistry) {
+ return true;
+ }
+ if (lp_ctx->bInGlobalSection) {
+ bool ret;
+ include_depth++;
+ ret = process_registry_globals();
+ include_depth--;
+ return ret;
+ } else {
+ DEBUG(1, ("\"include = registry\" only effective "
+ "in %s section\n", GLOBAL_NAME));
+ return false;
+ }
+ }
+
+ fname = talloc_sub_basic(talloc_tos(), get_current_username(),
+ get_current_user_info_domain(),
+ pszParmValue);
+
+ add_to_file_list(NULL, &file_lists, pszParmValue, fname);
+
+ if (service == NULL) {
+ lpcfg_string_set(Globals.ctx, ptr, fname);
+ } else {
+ lpcfg_string_set(service, ptr, fname);
+ }
+
+ if (file_exist(fname)) {
+ bool ret;
+ include_depth++;
+ ret = pm_process(fname, lp_do_section, do_parameter, lp_ctx);
+ include_depth--;
+ TALLOC_FREE(fname);
+ return ret;
+ }
+
+ DEBUG(2, ("Can't find include file %s\n", fname));
+ TALLOC_FREE(fname);
+ return true;
+}
+
+bool lp_idmap_range(const char *domain_name, uint32_t *low, uint32_t *high)
+{
+ char *config_option = NULL;
+ const char *range = NULL;
+ bool ret = false;
+
+ SMB_ASSERT(low != NULL);
+ SMB_ASSERT(high != NULL);
+
+ if ((domain_name == NULL) || (domain_name[0] == '\0')) {
+ domain_name = "*";
+ }
+
+ config_option = talloc_asprintf(talloc_tos(), "idmap config %s",
+ domain_name);
+ if (config_option == NULL) {
+ DEBUG(0, ("out of memory\n"));
+ return false;
+ }
+
+ range = lp_parm_const_string(-1, config_option, "range", NULL);
+ if (range == NULL) {
+ DEBUG(1, ("idmap range not specified for domain '%s'\n", domain_name));
+ goto done;
+ }
+
+ if (sscanf(range, "%u - %u", low, high) != 2) {
+ DEBUG(1, ("error parsing idmap range '%s' for domain '%s'\n",
+ range, domain_name));
+ goto done;
+ }
+
+ ret = true;
+
+done:
+ talloc_free(config_option);
+ return ret;
+
+}
+
+bool lp_idmap_default_range(uint32_t *low, uint32_t *high)
+{
+ return lp_idmap_range("*", low, high);
+}
+
+const char *lp_idmap_backend(const char *domain_name)
+{
+ char *config_option = NULL;
+ const char *backend = NULL;
+
+ if ((domain_name == NULL) || (domain_name[0] == '\0')) {
+ domain_name = "*";
+ }
+
+ config_option = talloc_asprintf(talloc_tos(), "idmap config %s",
+ domain_name);
+ if (config_option == NULL) {
+ DEBUG(0, ("out of memory\n"));
+ return false;
+ }
+
+ backend = lp_parm_const_string(-1, config_option, "backend", NULL);
+ if (backend == NULL) {
+ DEBUG(1, ("idmap backend not specified for domain '%s'\n", domain_name));
+ goto done;
+ }
+
+done:
+ talloc_free(config_option);
+ return backend;
+}
+
+const char *lp_idmap_default_backend(void)
+{
+ return lp_idmap_backend("*");
+}
+
+/***************************************************************************
+ Handle ldap suffixes - default to ldapsuffix if sub-suffixes are not defined.
+***************************************************************************/
+
+static const char *append_ldap_suffix(TALLOC_CTX *ctx, const char *str )
+{
+ const char *suffix_string;
+
+ suffix_string = talloc_asprintf(ctx, "%s,%s", str,
+ Globals.ldap_suffix );
+ if ( !suffix_string ) {
+ DEBUG(0,("append_ldap_suffix: talloc_asprintf() failed!\n"));
+ return "";
+ }
+
+ return suffix_string;
+}
+
+const char *lp_ldap_machine_suffix(TALLOC_CTX *ctx)
+{
+ if (Globals._ldap_machine_suffix[0])
+ return append_ldap_suffix(ctx, Globals._ldap_machine_suffix);
+
+ return talloc_strdup(ctx, Globals.ldap_suffix);
+}
+
+const char *lp_ldap_user_suffix(TALLOC_CTX *ctx)
+{
+ if (Globals._ldap_user_suffix[0])
+ return append_ldap_suffix(ctx, Globals._ldap_user_suffix);
+
+ return talloc_strdup(ctx, Globals.ldap_suffix);
+}
+
+const char *lp_ldap_group_suffix(TALLOC_CTX *ctx)
+{
+ if (Globals._ldap_group_suffix[0])
+ return append_ldap_suffix(ctx, Globals._ldap_group_suffix);
+
+ return talloc_strdup(ctx, Globals.ldap_suffix);
+}
+
+const char *lp_ldap_idmap_suffix(TALLOC_CTX *ctx)
+{
+ if (Globals._ldap_idmap_suffix[0])
+ return append_ldap_suffix(ctx, Globals._ldap_idmap_suffix);
+
+ return talloc_strdup(ctx, Globals.ldap_suffix);
+}
+
+/**
+ return the parameter pointer for a parameter
+*/
+void *lp_parm_ptr(struct loadparm_service *service, struct parm_struct *parm)
+{
+ if (service == NULL) {
+ if (parm->p_class == P_LOCAL)
+ return (void *)(((char *)&sDefault)+parm->offset);
+ else if (parm->p_class == P_GLOBAL)
+ return (void *)(((char *)&Globals)+parm->offset);
+ else return NULL;
+ } else {
+ return (void *)(((char *)service) + parm->offset);
+ }
+}
+
+/***************************************************************************
+ Process a parameter for a particular service number. If snum < 0
+ then assume we are in the globals.
+***************************************************************************/
+
+bool lp_do_parameter(int snum, const char *pszParmName, const char *pszParmValue)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx;
+ bool ok;
+
+ lp_ctx = setup_lp_context(frame);
+ if (lp_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (snum < 0) {
+ ok = lpcfg_do_global_parameter(lp_ctx, pszParmName, pszParmValue);
+ } else {
+ ok = lpcfg_do_service_parameter(lp_ctx, ServicePtrs[snum],
+ pszParmName, pszParmValue);
+ }
+
+ TALLOC_FREE(frame);
+
+ return ok;
+}
+
+/***************************************************************************
+set a parameter, marking it with FLAG_CMDLINE. Parameters marked as
+FLAG_CMDLINE won't be overridden by loads from smb.conf.
+***************************************************************************/
+
+static bool lp_set_cmdline_helper(const char *pszParmName, const char *pszParmValue)
+{
+ int parmnum, i;
+ parmnum = lpcfg_map_parameter(pszParmName);
+ if (parmnum >= 0) {
+ flags_list[parmnum] &= ~FLAG_CMDLINE;
+ if (!lp_do_parameter(-1, pszParmName, pszParmValue)) {
+ return false;
+ }
+ flags_list[parmnum] |= FLAG_CMDLINE;
+
+ /* we have to also set FLAG_CMDLINE on aliases. Aliases must
+ * be grouped in the table, so we don't have to search the
+ * whole table */
+ for (i=parmnum-1;
+ i>=0 && parm_table[i].offset == parm_table[parmnum].offset
+ && parm_table[i].p_class == parm_table[parmnum].p_class;
+ i--) {
+ flags_list[i] |= FLAG_CMDLINE;
+ }
+ for (i=parmnum+1;i<num_parameters() && parm_table[i].offset == parm_table[parmnum].offset
+ && parm_table[i].p_class == parm_table[parmnum].p_class;i++) {
+ flags_list[i] |= FLAG_CMDLINE;
+ }
+
+ return true;
+ }
+
+ /* it might be parametric */
+ if (strchr(pszParmName, ':') != NULL) {
+ set_param_opt(NULL, &Globals.param_opt, pszParmName, pszParmValue, FLAG_CMDLINE);
+ return true;
+ }
+
+ DEBUG(0, ("Ignoring unknown parameter \"%s\"\n", pszParmName));
+ return false;
+}
+
+/***************************************************************************
+ Process a parameter.
+***************************************************************************/
+
+static bool do_parameter(const char *pszParmName, const char *pszParmValue,
+ void *userdata)
+{
+ if (!bInGlobalSection && bGlobalOnly)
+ return true;
+
+ DEBUGADD(4, ("doing parameter %s = %s\n", pszParmName, pszParmValue));
+
+ if (bInGlobalSection) {
+ return lpcfg_do_global_parameter(userdata, pszParmName, pszParmValue);
+ } else {
+ return lpcfg_do_service_parameter(userdata, ServicePtrs[iServiceIndex],
+ pszParmName, pszParmValue);
+ }
+}
+
+
+static const char *ad_dc_req_vfs_mods[] = {"dfs_samba4", "acl_xattr", NULL};
+
+/*
+ * check that @vfs_objects includes all vfs modules required by an AD DC.
+ */
+static bool check_ad_dc_required_mods(const char **vfs_objects)
+{
+ int i;
+ int j;
+ int got_req;
+
+ for (i = 0; ad_dc_req_vfs_mods[i] != NULL; i++) {
+ got_req = false;
+ for (j = 0; vfs_objects[j] != NULL; j++) {
+ if (!strwicmp(ad_dc_req_vfs_mods[i], vfs_objects[j])) {
+ got_req = true;
+ break;
+ }
+ }
+ if (!got_req) {
+ DEBUG(0, ("vfs objects specified without required AD "
+ "DC module: %s\n", ad_dc_req_vfs_mods[i]));
+ return false;
+ }
+ }
+
+ DEBUG(6, ("vfs objects specified with all required AD DC modules\n"));
+ return true;
+}
+
+
+/***************************************************************************
+ Initialize any local variables in the sDefault table, after parsing a
+ [globals] section.
+***************************************************************************/
+
+static void init_locals(void)
+{
+ /*
+ * We run this check once the [globals] is parsed, to force
+ * the VFS objects and other per-share settings we need for
+ * the standard way a AD DC is operated. We may change these
+ * as our code evolves, which is why we force these settings.
+ *
+ * We can't do this at the end of lp_load_ex(), as by that
+ * point the services have been loaded and they will already
+ * have "" as their vfs objects.
+ */
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ const char **vfs_objects = lp_vfs_objects(-1);
+ if (vfs_objects != NULL) {
+ /* ignore return, only warn if modules are missing */
+ check_ad_dc_required_mods(vfs_objects);
+ } else {
+ if (lp_parm_const_string(-1, "xattr_tdb", "file", NULL)) {
+ lp_do_parameter(-1, "vfs objects", "dfs_samba4 acl_xattr xattr_tdb");
+ } else if (lp_parm_const_string(-1, "posix", "eadb", NULL)) {
+ lp_do_parameter(-1, "vfs objects", "dfs_samba4 acl_xattr posix_eadb");
+ } else {
+ lp_do_parameter(-1, "vfs objects", "dfs_samba4 acl_xattr");
+ }
+ }
+
+ lp_do_parameter(-1, "map hidden", "no");
+ lp_do_parameter(-1, "map system", "no");
+ lp_do_parameter(-1, "map readonly", "no");
+ lp_do_parameter(-1, "map archive", "no");
+ lp_do_parameter(-1, "store dos attributes", "yes");
+ }
+}
+
+/***************************************************************************
+ Process a new section (service). At this stage all sections are services.
+ Later we'll have special sections that permit server parameters to be set.
+ Returns true on success, false on failure.
+***************************************************************************/
+
+bool lp_do_section(const char *pszSectionName, void *userdata)
+{
+ struct loadparm_context *lp_ctx = (struct loadparm_context *)userdata;
+ bool bRetval;
+ bool isglobal = ((strwicmp(pszSectionName, GLOBAL_NAME) == 0) ||
+ (strwicmp(pszSectionName, GLOBAL_NAME2) == 0));
+
+ /* if we were in a global section then do the local inits */
+ if (bInGlobalSection && !isglobal)
+ init_locals();
+
+ /* if we've just struck a global section, note the fact. */
+ bInGlobalSection = isglobal;
+ if (lp_ctx != NULL) {
+ lp_ctx->bInGlobalSection = isglobal;
+ }
+
+ /* check for multiple global sections */
+ if (bInGlobalSection) {
+ DEBUG(3, ("Processing section \"[%s]\"\n", pszSectionName));
+ return true;
+ }
+
+ if (!bInGlobalSection && bGlobalOnly)
+ return true;
+
+ /* if we have a current service, tidy it up before moving on */
+ bRetval = true;
+
+ if ((iServiceIndex >= 0) && (ServicePtrs[iServiceIndex] != NULL))
+ bRetval = lpcfg_service_ok(ServicePtrs[iServiceIndex]);
+
+ /* if all is still well, move to the next record in the services array */
+ if (bRetval) {
+ /* We put this here to avoid an odd message order if messages are */
+ /* issued by the post-processing of a previous section. */
+ DEBUG(2, ("Processing section \"[%s]\"\n", pszSectionName));
+
+ iServiceIndex = add_a_service(&sDefault, pszSectionName);
+ if (iServiceIndex < 0) {
+ DEBUG(0, ("Failed to add a new service\n"));
+ return false;
+ }
+ /* Clean all parametric options for service */
+ /* They will be added during parsing again */
+ free_param_opts(&ServicePtrs[iServiceIndex]->param_opt);
+ }
+
+ return bRetval;
+}
+
+/***************************************************************************
+ Display the contents of a parameter of a single services record.
+***************************************************************************/
+
+bool dump_a_parameter(int snum, char *parm_name, FILE * f, bool isGlobal)
+{
+ bool result = false;
+ struct loadparm_context *lp_ctx;
+
+ lp_ctx = setup_lp_context(talloc_tos());
+ if (lp_ctx == NULL) {
+ return false;
+ }
+
+ if (isGlobal) {
+ result = lpcfg_dump_a_parameter(lp_ctx, NULL, parm_name, f);
+ } else {
+ result = lpcfg_dump_a_parameter(lp_ctx, ServicePtrs[snum], parm_name, f);
+ }
+ TALLOC_FREE(lp_ctx);
+ return result;
+}
+
+#if 0
+/***************************************************************************
+ Display the contents of a single copy structure.
+***************************************************************************/
+static void dump_copy_map(bool *pcopymap)
+{
+ int i;
+ if (!pcopymap)
+ return;
+
+ printf("\n\tNon-Copied parameters:\n");
+
+ for (i = 0; parm_table[i].label; i++)
+ if (parm_table[i].p_class == P_LOCAL &&
+ parm_table[i].ptr && !pcopymap[i] &&
+ (i == 0 || (parm_table[i].ptr != parm_table[i - 1].ptr)))
+ {
+ printf("\t\t%s\n", parm_table[i].label);
+ }
+}
+#endif
+
+/***************************************************************************
+ Return TRUE if the passed service number is within range.
+***************************************************************************/
+
+bool lp_snum_ok(int iService)
+{
+ return (LP_SNUM_OK(iService) && ServicePtrs[iService]->available);
+}
+
+/***************************************************************************
+ Auto-load some home services.
+***************************************************************************/
+
+static void lp_add_auto_services(const char *str)
+{
+ char *s;
+ char *p;
+ int homes;
+ char *saveptr;
+
+ if (!str)
+ return;
+
+ s = talloc_strdup(talloc_tos(), str);
+ if (!s) {
+ smb_panic("talloc_strdup failed");
+ return;
+ }
+
+ homes = lp_servicenumber(HOMES_NAME);
+
+ for (p = strtok_r(s, LIST_SEP, &saveptr); p;
+ p = strtok_r(NULL, LIST_SEP, &saveptr)) {
+ char *home;
+
+ if (lp_servicenumber(p) >= 0)
+ continue;
+
+ home = get_user_home_dir(talloc_tos(), p);
+
+ if (home && home[0] && homes >= 0)
+ lp_add_home(p, homes, p, home);
+
+ TALLOC_FREE(home);
+ }
+ TALLOC_FREE(s);
+}
+
+/***************************************************************************
+ Auto-load one printer.
+***************************************************************************/
+
+void lp_add_one_printer(const char *name, const char *comment,
+ const char *location, void *pdata)
+{
+ int printers = lp_servicenumber(PRINTERS_NAME);
+ int i;
+
+ if (lp_servicenumber(name) < 0) {
+ lp_add_printer(name, printers);
+ if ((i = lp_servicenumber(name)) >= 0) {
+ lpcfg_string_set(ServicePtrs[i],
+ &ServicePtrs[i]->comment, comment);
+ ServicePtrs[i]->autoloaded = true;
+ }
+ }
+}
+
+/***************************************************************************
+ Have we loaded a services file yet?
+***************************************************************************/
+
+bool lp_loaded(void)
+{
+ return (b_loaded);
+}
+
+/***************************************************************************
+ Unload unused services.
+***************************************************************************/
+
+void lp_killunused(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int))
+{
+ int i;
+ for (i = 0; i < iNumServices; i++) {
+ if (!VALID(i))
+ continue;
+
+ /* don't kill autoloaded or usershare services */
+ if ( ServicePtrs[i]->autoloaded ||
+ ServicePtrs[i]->usershare == USERSHARE_VALID) {
+ continue;
+ }
+
+ if (!snumused || !snumused(sconn, i)) {
+ free_service_byindex(i);
+ }
+ }
+}
+
+/**
+ * Kill all except autoloaded and usershare services - convenience wrapper
+ */
+void lp_kill_all_services(void)
+{
+ lp_killunused(NULL, NULL);
+}
+
+/***************************************************************************
+ Unload a service.
+***************************************************************************/
+
+void lp_killservice(int iServiceIn)
+{
+ if (VALID(iServiceIn)) {
+ free_service_byindex(iServiceIn);
+ }
+}
+
+/***************************************************************************
+ Save the current values of all global and sDefault parameters into the
+ defaults union. This allows testparm to show only the
+ changed (ie. non-default) parameters.
+***************************************************************************/
+
+static void lp_save_defaults(void)
+{
+ int i;
+ struct parmlist_entry * parm;
+ for (i = 0; parm_table[i].label; i++) {
+ if (!(flags_list[i] & FLAG_CMDLINE)) {
+ flags_list[i] |= FLAG_DEFAULT;
+ }
+
+ if (i > 0 && parm_table[i].offset == parm_table[i - 1].offset
+ && parm_table[i].p_class == parm_table[i - 1].p_class)
+ continue;
+ switch (parm_table[i].type) {
+ case P_LIST:
+ case P_CMDLIST:
+ parm_table[i].def.lvalue = str_list_copy(
+ NULL, *(const char ***)lp_parm_ptr(NULL, &parm_table[i]));
+ break;
+ case P_STRING:
+ case P_USTRING:
+ lpcfg_string_set(
+ Globals.ctx,
+ &parm_table[i].def.svalue,
+ *(char **)lp_parm_ptr(
+ NULL, &parm_table[i]));
+ if (parm_table[i].def.svalue == NULL) {
+ smb_panic("lpcfg_string_set() failed");
+ }
+ break;
+ case P_BOOL:
+ case P_BOOLREV:
+ parm_table[i].def.bvalue =
+ *(bool *)lp_parm_ptr(NULL, &parm_table[i]);
+ break;
+ case P_CHAR:
+ parm_table[i].def.cvalue =
+ *(char *)lp_parm_ptr(NULL, &parm_table[i]);
+ break;
+ case P_INTEGER:
+ case P_OCTAL:
+ case P_ENUM:
+ case P_BYTES:
+ parm_table[i].def.ivalue =
+ *(int *)lp_parm_ptr(NULL, &parm_table[i]);
+ break;
+ }
+ }
+
+ for (parm=Globals.param_opt; parm; parm=parm->next) {
+ if (!(parm->priority & FLAG_CMDLINE)) {
+ parm->priority |= FLAG_DEFAULT;
+ }
+ }
+
+ for (parm=sDefault.param_opt; parm; parm=parm->next) {
+ if (!(parm->priority & FLAG_CMDLINE)) {
+ parm->priority |= FLAG_DEFAULT;
+ }
+ }
+
+ defaults_saved = true;
+}
+
+/***********************************************************
+ If we should send plaintext/LANMAN passwords in the client
+************************************************************/
+
+static void set_allowed_client_auth(void)
+{
+ if (Globals.client_ntlmv2_auth) {
+ Globals.client_lanman_auth = false;
+ }
+ if (!Globals.client_lanman_auth) {
+ Globals.client_plaintext_auth = false;
+ }
+}
+
+/***************************************************************************
+ JRA.
+ The following code allows smbd to read a user defined share file.
+ Yes, this is my intent. Yes, I'm comfortable with that...
+
+ THE FOLLOWING IS SECURITY CRITICAL CODE.
+
+ It washes your clothes, it cleans your house, it guards you while you sleep...
+ Do not f%^k with it....
+***************************************************************************/
+
+#define MAX_USERSHARE_FILE_SIZE (10*1024)
+
+/***************************************************************************
+ Check allowed stat state of a usershare file.
+ Ensure we print out who is dicking with us so the admin can
+ get their sorry ass fired.
+***************************************************************************/
+
+static bool check_usershare_stat(const char *fname,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ if (!S_ISREG(psbuf->st_ex_mode)) {
+ DEBUG(0,("check_usershare_stat: file %s owned by uid %u is "
+ "not a regular file\n",
+ fname, (unsigned int)psbuf->st_ex_uid ));
+ return false;
+ }
+
+ /* Ensure this doesn't have the other write bit set. */
+ if (psbuf->st_ex_mode & S_IWOTH) {
+ DEBUG(0,("check_usershare_stat: file %s owned by uid %u allows "
+ "public write. Refusing to allow as a usershare file.\n",
+ fname, (unsigned int)psbuf->st_ex_uid ));
+ return false;
+ }
+
+ /* Should be 10k or less. */
+ if (psbuf->st_ex_size > MAX_USERSHARE_FILE_SIZE) {
+ DEBUG(0,("check_usershare_stat: file %s owned by uid %u is "
+ "too large (%u) to be a user share file.\n",
+ fname, (unsigned int)psbuf->st_ex_uid,
+ (unsigned int)psbuf->st_ex_size ));
+ return false;
+ }
+
+ return true;
+}
+
+/***************************************************************************
+ Parse the contents of a usershare file.
+***************************************************************************/
+
+enum usershare_err parse_usershare_file(TALLOC_CTX *ctx,
+ SMB_STRUCT_STAT *psbuf,
+ const char *servicename,
+ int snum,
+ char **lines,
+ int numlines,
+ char **pp_sharepath,
+ char **pp_comment,
+ char **pp_cp_servicename,
+ struct security_descriptor **ppsd,
+ bool *pallow_guest)
+{
+ const char **prefixallowlist = lp_usershare_prefix_allow_list();
+ const char **prefixdenylist = lp_usershare_prefix_deny_list();
+ int us_vers;
+ DIR *dp;
+ SMB_STRUCT_STAT sbuf;
+ char *sharepath = NULL;
+ char *comment = NULL;
+
+ *pp_sharepath = NULL;
+ *pp_comment = NULL;
+
+ *pallow_guest = false;
+
+ if (numlines < 4) {
+ return USERSHARE_MALFORMED_FILE;
+ }
+
+ if (strcmp(lines[0], "#VERSION 1") == 0) {
+ us_vers = 1;
+ } else if (strcmp(lines[0], "#VERSION 2") == 0) {
+ us_vers = 2;
+ if (numlines < 5) {
+ return USERSHARE_MALFORMED_FILE;
+ }
+ } else {
+ return USERSHARE_BAD_VERSION;
+ }
+
+ if (strncmp(lines[1], "path=", 5) != 0) {
+ return USERSHARE_MALFORMED_PATH;
+ }
+
+ sharepath = talloc_strdup(ctx, &lines[1][5]);
+ if (!sharepath) {
+ return USERSHARE_POSIX_ERR;
+ }
+ trim_string(sharepath, " ", " ");
+
+ if (strncmp(lines[2], "comment=", 8) != 0) {
+ return USERSHARE_MALFORMED_COMMENT_DEF;
+ }
+
+ comment = talloc_strdup(ctx, &lines[2][8]);
+ if (!comment) {
+ return USERSHARE_POSIX_ERR;
+ }
+ trim_string(comment, " ", " ");
+ trim_char(comment, '"', '"');
+
+ if (strncmp(lines[3], "usershare_acl=", 14) != 0) {
+ return USERSHARE_MALFORMED_ACL_DEF;
+ }
+
+ if (!parse_usershare_acl(ctx, &lines[3][14], ppsd)) {
+ return USERSHARE_ACL_ERR;
+ }
+
+ if (us_vers == 2) {
+ if (strncmp(lines[4], "guest_ok=", 9) != 0) {
+ return USERSHARE_MALFORMED_ACL_DEF;
+ }
+ if (lines[4][9] == 'y') {
+ *pallow_guest = true;
+ }
+
+ /* Backwards compatible extension to file version #2. */
+ if (numlines > 5) {
+ if (strncmp(lines[5], "sharename=", 10) != 0) {
+ return USERSHARE_MALFORMED_SHARENAME_DEF;
+ }
+ if (!strequal(&lines[5][10], servicename)) {
+ return USERSHARE_BAD_SHARENAME;
+ }
+ *pp_cp_servicename = talloc_strdup(ctx, &lines[5][10]);
+ if (!*pp_cp_servicename) {
+ return USERSHARE_POSIX_ERR;
+ }
+ }
+ }
+
+ if (*pp_cp_servicename == NULL) {
+ *pp_cp_servicename = talloc_strdup(ctx, servicename);
+ if (!*pp_cp_servicename) {
+ return USERSHARE_POSIX_ERR;
+ }
+ }
+
+ if (snum != -1 && (strcmp(sharepath, ServicePtrs[snum]->path) == 0)) {
+ /* Path didn't change, no checks needed. */
+ *pp_sharepath = sharepath;
+ *pp_comment = comment;
+ return USERSHARE_OK;
+ }
+
+ /* The path *must* be absolute. */
+ if (sharepath[0] != '/') {
+ DEBUG(2,("parse_usershare_file: share %s: path %s is not an absolute path.\n",
+ servicename, sharepath));
+ return USERSHARE_PATH_NOT_ABSOLUTE;
+ }
+
+ /* If there is a usershare prefix deny list ensure one of these paths
+ doesn't match the start of the user given path. */
+ if (prefixdenylist) {
+ int i;
+ for ( i=0; prefixdenylist[i]; i++ ) {
+ DEBUG(10,("parse_usershare_file: share %s : checking prefixdenylist[%d]='%s' against %s\n",
+ servicename, i, prefixdenylist[i], sharepath ));
+ if (memcmp( sharepath, prefixdenylist[i], strlen(prefixdenylist[i])) == 0) {
+ DEBUG(2,("parse_usershare_file: share %s path %s starts with one of the "
+ "usershare prefix deny list entries.\n",
+ servicename, sharepath));
+ return USERSHARE_PATH_IS_DENIED;
+ }
+ }
+ }
+
+ /* If there is a usershare prefix allow list ensure one of these paths
+ does match the start of the user given path. */
+
+ if (prefixallowlist) {
+ int i;
+ for ( i=0; prefixallowlist[i]; i++ ) {
+ DEBUG(10,("parse_usershare_file: share %s checking prefixallowlist[%d]='%s' against %s\n",
+ servicename, i, prefixallowlist[i], sharepath ));
+ if (memcmp( sharepath, prefixallowlist[i], strlen(prefixallowlist[i])) == 0) {
+ break;
+ }
+ }
+ if (prefixallowlist[i] == NULL) {
+ DEBUG(2,("parse_usershare_file: share %s path %s doesn't start with one of the "
+ "usershare prefix allow list entries.\n",
+ servicename, sharepath));
+ return USERSHARE_PATH_NOT_ALLOWED;
+ }
+ }
+
+ /* Ensure this is pointing to a directory. */
+ dp = opendir(sharepath);
+
+ if (!dp) {
+ DEBUG(2,("parse_usershare_file: share %s path %s is not a directory.\n",
+ servicename, sharepath));
+ return USERSHARE_PATH_NOT_DIRECTORY;
+ }
+
+ /* Ensure the owner of the usershare file has permission to share
+ this directory. */
+
+ if (sys_stat(sharepath, &sbuf, false) == -1) {
+ DEBUG(2,("parse_usershare_file: share %s : stat failed on path %s. %s\n",
+ servicename, sharepath, strerror(errno) ));
+ closedir(dp);
+ return USERSHARE_POSIX_ERR;
+ }
+
+ closedir(dp);
+
+ if (!S_ISDIR(sbuf.st_ex_mode)) {
+ DEBUG(2,("parse_usershare_file: share %s path %s is not a directory.\n",
+ servicename, sharepath ));
+ return USERSHARE_PATH_NOT_DIRECTORY;
+ }
+
+ /* Check if sharing is restricted to owner-only. */
+ /* psbuf is the stat of the usershare definition file,
+ sbuf is the stat of the target directory to be shared. */
+
+ if (lp_usershare_owner_only()) {
+ /* root can share anything. */
+ if ((psbuf->st_ex_uid != 0) && (sbuf.st_ex_uid != psbuf->st_ex_uid)) {
+ return USERSHARE_PATH_NOT_ALLOWED;
+ }
+ }
+
+ *pp_sharepath = sharepath;
+ *pp_comment = comment;
+ return USERSHARE_OK;
+}
+
+/***************************************************************************
+ Deal with a usershare file.
+ Returns:
+ >= 0 - snum
+ -1 - Bad name, invalid contents.
+ - service name already existed and not a usershare, problem
+ with permissions to share directory etc.
+***************************************************************************/
+
+static int process_usershare_file(const char *dir_name, const char *file_name, int snum_template)
+{
+ SMB_STRUCT_STAT sbuf;
+ SMB_STRUCT_STAT lsbuf;
+ char *fname = NULL;
+ char *sharepath = NULL;
+ char *comment = NULL;
+ char *cp_service_name = NULL;
+ char **lines = NULL;
+ int numlines = 0;
+ int fd = -1;
+ int iService = -1;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ struct security_descriptor *psd = NULL;
+ bool guest_ok = false;
+ char *canon_name = NULL;
+ bool added_service = false;
+ int ret = -1;
+ NTSTATUS status;
+
+ /* Ensure share name doesn't contain invalid characters. */
+ if (!validate_net_name(file_name, INVALID_SHARENAME_CHARS, strlen(file_name))) {
+ DEBUG(0,("process_usershare_file: share name %s contains "
+ "invalid characters (any of %s)\n",
+ file_name, INVALID_SHARENAME_CHARS ));
+ goto out;
+ }
+
+ canon_name = canonicalize_servicename(ctx, file_name);
+ if (!canon_name) {
+ goto out;
+ }
+
+ fname = talloc_asprintf(ctx, "%s/%s", dir_name, file_name);
+ if (!fname) {
+ goto out;
+ }
+
+ /* Minimize the race condition by doing an lstat before we
+ open and fstat. Ensure this isn't a symlink link. */
+
+ if (sys_lstat(fname, &lsbuf, false) != 0) {
+ if (errno == ENOENT) {
+ /* Unknown share requested. Just ignore. */
+ goto out;
+ }
+ /* Only log messages for meaningful problems. */
+ DEBUG(0,("process_usershare_file: stat of %s failed. %s\n",
+ fname, strerror(errno) ));
+ goto out;
+ }
+
+ /* This must be a regular file, not a symlink, directory or
+ other strange filetype. */
+ if (!check_usershare_stat(fname, &lsbuf)) {
+ goto out;
+ }
+
+ {
+ TDB_DATA data;
+
+ status = dbwrap_fetch_bystring(ServiceHash, canon_name,
+ canon_name, &data);
+
+ iService = -1;
+
+ if (NT_STATUS_IS_OK(status) &&
+ (data.dptr != NULL) &&
+ (data.dsize == sizeof(iService))) {
+ memcpy(&iService, data.dptr, sizeof(iService));
+ }
+ }
+
+ if (iService != -1 &&
+ timespec_compare(&ServicePtrs[iService]->usershare_last_mod,
+ &lsbuf.st_ex_mtime) == 0) {
+ /* Nothing changed - Mark valid and return. */
+ DEBUG(10,("process_usershare_file: service %s not changed.\n",
+ canon_name ));
+ ServicePtrs[iService]->usershare = USERSHARE_VALID;
+ ret = iService;
+ goto out;
+ }
+
+ /* Try and open the file read only - no symlinks allowed. */
+#ifdef O_NOFOLLOW
+ fd = open(fname, O_RDONLY|O_NOFOLLOW, 0);
+#else
+ fd = open(fname, O_RDONLY, 0);
+#endif
+
+ if (fd == -1) {
+ DEBUG(0,("process_usershare_file: unable to open %s. %s\n",
+ fname, strerror(errno) ));
+ goto out;
+ }
+
+ /* Now fstat to be *SURE* it's a regular file. */
+ if (sys_fstat(fd, &sbuf, false) != 0) {
+ close(fd);
+ DEBUG(0,("process_usershare_file: fstat of %s failed. %s\n",
+ fname, strerror(errno) ));
+ goto out;
+ }
+
+ /* Is it the same dev/inode as was lstated ? */
+ if (!check_same_stat(&lsbuf, &sbuf)) {
+ close(fd);
+ DEBUG(0,("process_usershare_file: fstat of %s is a different file from lstat. "
+ "Symlink spoofing going on ?\n", fname ));
+ goto out;
+ }
+
+ /* This must be a regular file, not a symlink, directory or
+ other strange filetype. */
+ if (!check_usershare_stat(fname, &sbuf)) {
+ close(fd);
+ goto out;
+ }
+
+ lines = fd_lines_load(fd, &numlines, MAX_USERSHARE_FILE_SIZE, NULL);
+
+ close(fd);
+ if (lines == NULL) {
+ DEBUG(0,("process_usershare_file: loading file %s owned by %u failed.\n",
+ fname, (unsigned int)sbuf.st_ex_uid ));
+ goto out;
+ }
+
+ if (parse_usershare_file(ctx, &sbuf, file_name,
+ iService, lines, numlines, &sharepath,
+ &comment, &cp_service_name,
+ &psd, &guest_ok) != USERSHARE_OK) {
+ goto out;
+ }
+
+ /* Everything ok - add the service possibly using a template. */
+ if (iService < 0) {
+ const struct loadparm_service *sp = &sDefault;
+ if (snum_template != -1) {
+ sp = ServicePtrs[snum_template];
+ }
+
+ if ((iService = add_a_service(sp, cp_service_name)) < 0) {
+ DEBUG(0, ("process_usershare_file: Failed to add "
+ "new service %s\n", cp_service_name));
+ goto out;
+ }
+
+ added_service = true;
+
+ /* Read only is controlled by usershare ACL below. */
+ ServicePtrs[iService]->read_only = false;
+ }
+
+ /* Write the ACL of the new/modified share. */
+ status = set_share_security(canon_name, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("process_usershare_file: Failed to set share "
+ "security for user share %s\n",
+ canon_name ));
+ goto out;
+ }
+
+ /* If from a template it may be marked invalid. */
+ ServicePtrs[iService]->valid = true;
+
+ /* Set the service as a valid usershare. */
+ ServicePtrs[iService]->usershare = USERSHARE_VALID;
+
+ /* Set guest access. */
+ if (lp_usershare_allow_guests()) {
+ ServicePtrs[iService]->guest_ok = guest_ok;
+ }
+
+ /* And note when it was loaded. */
+ ServicePtrs[iService]->usershare_last_mod = sbuf.st_ex_mtime;
+ lpcfg_string_set(ServicePtrs[iService], &ServicePtrs[iService]->path,
+ sharepath);
+ lpcfg_string_set(ServicePtrs[iService],
+ &ServicePtrs[iService]->comment, comment);
+
+ ret = iService;
+
+ out:
+
+ if (ret == -1 && iService != -1 && added_service) {
+ lp_remove_service(iService);
+ }
+
+ TALLOC_FREE(lines);
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/***************************************************************************
+ Checks if a usershare entry has been modified since last load.
+***************************************************************************/
+
+static bool usershare_exists(int iService, struct timespec *last_mod)
+{
+ SMB_STRUCT_STAT lsbuf;
+ const char *usersharepath = Globals.usershare_path;
+ char *fname;
+
+ fname = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ usersharepath,
+ ServicePtrs[iService]->szService);
+ if (fname == NULL) {
+ return false;
+ }
+
+ if (sys_lstat(fname, &lsbuf, false) != 0) {
+ TALLOC_FREE(fname);
+ return false;
+ }
+
+ if (!S_ISREG(lsbuf.st_ex_mode)) {
+ TALLOC_FREE(fname);
+ return false;
+ }
+
+ TALLOC_FREE(fname);
+ *last_mod = lsbuf.st_ex_mtime;
+ return true;
+}
+
+static bool usershare_directory_is_root(uid_t uid)
+{
+ if (uid == 0) {
+ return true;
+ }
+
+ if (uid_wrapper_enabled()) {
+ return true;
+ }
+
+ return false;
+}
+
+/***************************************************************************
+ Load a usershare service by name. Returns a valid servicenumber or -1.
+***************************************************************************/
+
+int load_usershare_service(const char *servicename)
+{
+ SMB_STRUCT_STAT sbuf;
+ const char *usersharepath = Globals.usershare_path;
+ int max_user_shares = Globals.usershare_max_shares;
+ int snum_template = -1;
+
+ if (servicename[0] == '\0') {
+ /* Invalid service name. */
+ return -1;
+ }
+
+ if (*usersharepath == 0 || max_user_shares == 0) {
+ return -1;
+ }
+
+ if (sys_stat(usersharepath, &sbuf, false) != 0) {
+ DEBUG(0,("load_usershare_service: stat of %s failed. %s\n",
+ usersharepath, strerror(errno) ));
+ return -1;
+ }
+
+ if (!S_ISDIR(sbuf.st_ex_mode)) {
+ DEBUG(0,("load_usershare_service: %s is not a directory.\n",
+ usersharepath ));
+ return -1;
+ }
+
+ /*
+ * This directory must be owned by root, and have the 't' bit set.
+ * It also must not be writable by "other".
+ */
+
+#ifdef S_ISVTX
+ if (!usershare_directory_is_root(sbuf.st_ex_uid) ||
+ !(sbuf.st_ex_mode & S_ISVTX) || (sbuf.st_ex_mode & S_IWOTH)) {
+#else
+ if (!usershare_directory_is_root(sbuf.st_ex_uid) ||
+ (sbuf.st_ex_mode & S_IWOTH)) {
+#endif
+ DEBUG(0,("load_usershare_service: directory %s is not owned by root "
+ "or does not have the sticky bit 't' set or is writable by anyone.\n",
+ usersharepath ));
+ return -1;
+ }
+
+ /* Ensure the template share exists if it's set. */
+ if (Globals.usershare_template_share[0]) {
+ /* We can't use lp_servicenumber here as we are recommending that
+ template shares have -valid=false set. */
+ for (snum_template = iNumServices - 1; snum_template >= 0; snum_template--) {
+ if (ServicePtrs[snum_template]->szService &&
+ strequal(ServicePtrs[snum_template]->szService,
+ Globals.usershare_template_share)) {
+ break;
+ }
+ }
+
+ if (snum_template == -1) {
+ DEBUG(0,("load_usershare_service: usershare template share %s "
+ "does not exist.\n",
+ Globals.usershare_template_share ));
+ return -1;
+ }
+ }
+
+ return process_usershare_file(usersharepath, servicename, snum_template);
+}
+
+/***************************************************************************
+ Load all user defined shares from the user share directory.
+ We only do this if we're enumerating the share list.
+ This is the function that can delete usershares that have
+ been removed.
+***************************************************************************/
+
+int load_usershare_shares(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int))
+{
+ DIR *dp;
+ SMB_STRUCT_STAT sbuf;
+ struct dirent *de;
+ int num_usershares = 0;
+ int max_user_shares = Globals.usershare_max_shares;
+ unsigned int num_dir_entries, num_bad_dir_entries, num_tmp_dir_entries;
+ unsigned int allowed_bad_entries = ((2*max_user_shares)/10);
+ unsigned int allowed_tmp_entries = ((2*max_user_shares)/10);
+ int iService;
+ int snum_template = -1;
+ const char *usersharepath = Globals.usershare_path;
+ int ret = lp_numservices();
+ TALLOC_CTX *tmp_ctx;
+
+ if (max_user_shares == 0 || *usersharepath == '\0') {
+ return lp_numservices();
+ }
+
+ if (sys_stat(usersharepath, &sbuf, false) != 0) {
+ DEBUG(0,("load_usershare_shares: stat of %s failed. %s\n",
+ usersharepath, strerror(errno) ));
+ return ret;
+ }
+
+ /*
+ * This directory must be owned by root, and have the 't' bit set.
+ * It also must not be writable by "other".
+ */
+
+#ifdef S_ISVTX
+ if (sbuf.st_ex_uid != 0 || !(sbuf.st_ex_mode & S_ISVTX) || (sbuf.st_ex_mode & S_IWOTH)) {
+#else
+ if (sbuf.st_ex_uid != 0 || (sbuf.st_ex_mode & S_IWOTH)) {
+#endif
+ DEBUG(0,("load_usershare_shares: directory %s is not owned by root "
+ "or does not have the sticky bit 't' set or is writable by anyone.\n",
+ usersharepath ));
+ return ret;
+ }
+
+ /* Ensure the template share exists if it's set. */
+ if (Globals.usershare_template_share[0]) {
+ /* We can't use lp_servicenumber here as we are recommending that
+ template shares have -valid=false set. */
+ for (snum_template = iNumServices - 1; snum_template >= 0; snum_template--) {
+ if (ServicePtrs[snum_template]->szService &&
+ strequal(ServicePtrs[snum_template]->szService,
+ Globals.usershare_template_share)) {
+ break;
+ }
+ }
+
+ if (snum_template == -1) {
+ DEBUG(0,("load_usershare_shares: usershare template share %s "
+ "does not exist.\n",
+ Globals.usershare_template_share ));
+ return ret;
+ }
+ }
+
+ /* Mark all existing usershares as pending delete. */
+ for (iService = iNumServices - 1; iService >= 0; iService--) {
+ if (VALID(iService) && ServicePtrs[iService]->usershare) {
+ ServicePtrs[iService]->usershare = USERSHARE_PENDING_DELETE;
+ }
+ }
+
+ dp = opendir(usersharepath);
+ if (!dp) {
+ DEBUG(0,("load_usershare_shares:: failed to open directory %s. %s\n",
+ usersharepath, strerror(errno) ));
+ return ret;
+ }
+
+ for (num_dir_entries = 0, num_bad_dir_entries = 0, num_tmp_dir_entries = 0;
+ (de = readdir(dp));
+ num_dir_entries++ ) {
+ int r;
+ const char *n = de->d_name;
+
+ /* Ignore . and .. */
+ if (*n == '.') {
+ if ((n[1] == '\0') || (n[1] == '.' && n[2] == '\0')) {
+ continue;
+ }
+ }
+
+ if (n[0] == ':') {
+ /* Temporary file used when creating a share. */
+ num_tmp_dir_entries++;
+ }
+
+ /* Allow 20% tmp entries. */
+ if (num_tmp_dir_entries > allowed_tmp_entries) {
+ DEBUG(0,("load_usershare_shares: too many temp entries (%u) "
+ "in directory %s\n",
+ num_tmp_dir_entries, usersharepath));
+ break;
+ }
+
+ r = process_usershare_file(usersharepath, n, snum_template);
+ if (r == 0) {
+ /* Update the services count. */
+ num_usershares++;
+ if (num_usershares >= max_user_shares) {
+ DEBUG(0,("load_usershare_shares: max user shares reached "
+ "on file %s in directory %s\n",
+ n, usersharepath ));
+ break;
+ }
+ } else if (r == -1) {
+ num_bad_dir_entries++;
+ }
+
+ /* Allow 20% bad entries. */
+ if (num_bad_dir_entries > allowed_bad_entries) {
+ DEBUG(0,("load_usershare_shares: too many bad entries (%u) "
+ "in directory %s\n",
+ num_bad_dir_entries, usersharepath));
+ break;
+ }
+
+ /* Allow 20% bad entries. */
+ if (num_dir_entries > max_user_shares + allowed_bad_entries) {
+ DEBUG(0,("load_usershare_shares: too many total entries (%u) "
+ "in directory %s\n",
+ num_dir_entries, usersharepath));
+ break;
+ }
+ }
+
+ closedir(dp);
+
+ /* Sweep through and delete any non-refreshed usershares that are
+ not currently in use. */
+ tmp_ctx = talloc_stackframe();
+ for (iService = iNumServices - 1; iService >= 0; iService--) {
+ if (VALID(iService) && (ServicePtrs[iService]->usershare == USERSHARE_PENDING_DELETE)) {
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *servname;
+
+ if (snumused && snumused(sconn, iService)) {
+ continue;
+ }
+
+ servname = lp_servicename(tmp_ctx, lp_sub, iService);
+
+ /* Remove from the share ACL db. */
+ DEBUG(10,("load_usershare_shares: Removing deleted usershare %s\n",
+ servname ));
+ delete_share_security(servname);
+ free_service_byindex(iService);
+ }
+ }
+ talloc_free(tmp_ctx);
+
+ return lp_numservices();
+}
+
+/********************************************************
+ Destroy global resources allocated in this file
+********************************************************/
+
+void gfree_loadparm(void)
+{
+ int i;
+
+ free_file_list();
+
+ /* Free resources allocated to services */
+
+ for ( i = 0; i < iNumServices; i++ ) {
+ if ( VALID(i) ) {
+ free_service_byindex(i);
+ }
+ }
+
+ TALLOC_FREE( ServicePtrs );
+ iNumServices = 0;
+
+ /* Now release all resources allocated to global
+ parameters and the default service */
+
+ free_global_parameters();
+}
+
+
+/***************************************************************************
+ Allow client apps to specify that they are a client
+***************************************************************************/
+static void lp_set_in_client(bool b)
+{
+ in_client = b;
+}
+
+
+/***************************************************************************
+ Determine if we're running in a client app
+***************************************************************************/
+static bool lp_is_in_client(void)
+{
+ return in_client;
+}
+
+static void lp_enforce_ad_dc_settings(void)
+{
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "passdb backend", "samba_dsdb");
+ lp_do_parameter(GLOBAL_SECTION_SNUM,
+ "winbindd:use external pipes", "true");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:default", "external");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:svcctl", "embedded");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:srvsvc", "embedded");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:eventlog", "embedded");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:ntsvcs", "embedded");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:winreg", "embedded");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:spoolss", "embedded");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_daemon:spoolssd", "embedded");
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "rpc_server:tcpip", "no");
+}
+
+/***************************************************************************
+ Load the services array from the services file. Return true on success,
+ false on failure.
+***************************************************************************/
+
+static bool lp_load_ex(const char *pszFname,
+ bool global_only,
+ bool save_defaults,
+ bool add_ipc,
+ bool reinit_globals,
+ bool allow_include_registry,
+ bool load_all_shares)
+{
+ char *n2 = NULL;
+ bool bRetval;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx;
+ int max_protocol, min_protocol;
+
+ DEBUG(3, ("lp_load_ex: refreshing parameters\n"));
+
+ bInGlobalSection = true;
+ bGlobalOnly = global_only;
+ bAllowIncludeRegistry = allow_include_registry;
+ sDefault = _sDefault;
+
+ lp_ctx = setup_lp_context(talloc_tos());
+
+ loadparm_s3_init_globals(lp_ctx, reinit_globals);
+
+ free_file_list();
+
+ if (save_defaults) {
+ init_locals();
+ lp_save_defaults();
+ }
+
+ if (!reinit_globals) {
+ free_param_opts(&Globals.param_opt);
+ apply_lp_set_cmdline();
+ }
+
+ lp_do_parameter(-1, "idmap config * : backend", Globals.idmap_backend);
+
+ /* We get sections first, so have to start 'behind' to make up */
+ iServiceIndex = -1;
+
+ if (lp_config_backend_is_file()) {
+ n2 = talloc_sub_basic(talloc_tos(), get_current_username(),
+ get_current_user_info_domain(),
+ pszFname);
+ if (!n2) {
+ smb_panic("lp_load_ex: out of memory");
+ }
+
+ add_to_file_list(NULL, &file_lists, pszFname, n2);
+
+ bRetval = pm_process(n2, lp_do_section, do_parameter, lp_ctx);
+ TALLOC_FREE(n2);
+
+ /* finish up the last section */
+ DEBUG(4, ("pm_process() returned %s\n", BOOLSTR(bRetval)));
+ if (bRetval) {
+ if (iServiceIndex >= 0) {
+ bRetval = lpcfg_service_ok(ServicePtrs[iServiceIndex]);
+ }
+ }
+
+ if (lp_config_backend_is_registry()) {
+ bool ok;
+ /* config backend changed to registry in config file */
+ /*
+ * We need to use this extra global variable here to
+ * survive restart: init_globals uses this as a default
+ * for config_backend. Otherwise, init_globals would
+ * send us into an endless loop here.
+ */
+
+ config_backend = CONFIG_BACKEND_REGISTRY;
+ /* start over */
+ DEBUG(1, ("lp_load_ex: changing to config backend "
+ "registry\n"));
+ loadparm_s3_init_globals(lp_ctx, true);
+
+ TALLOC_FREE(lp_ctx);
+
+ lp_kill_all_services();
+ ok = lp_load_ex(pszFname, global_only, save_defaults,
+ add_ipc, reinit_globals,
+ allow_include_registry,
+ load_all_shares);
+ TALLOC_FREE(frame);
+ return ok;
+ }
+ } else if (lp_config_backend_is_registry()) {
+ bRetval = process_registry_globals();
+ } else {
+ DEBUG(0, ("Illegal config backend given: %d\n",
+ lp_config_backend()));
+ bRetval = false;
+ }
+
+ if (bRetval && lp_registry_shares()) {
+ if (load_all_shares) {
+ bRetval = process_registry_shares();
+ } else {
+ bRetval = reload_registry_shares();
+ }
+ }
+
+ {
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *serv = lp_auto_services(talloc_tos(), lp_sub);
+ lp_add_auto_services(serv);
+ TALLOC_FREE(serv);
+ }
+
+ if (add_ipc) {
+ /* When 'restrict anonymous = 2' guest connections to ipc$
+ are denied */
+ lp_add_ipc("IPC$", (lp_restrict_anonymous() < 2));
+ if ( lp_enable_asu_support() ) {
+ lp_add_ipc("ADMIN$", false);
+ }
+ }
+
+ set_allowed_client_auth();
+
+ if (lp_security() == SEC_ADS && strchr(lp_password_server(), ':')) {
+ DEBUG(1, ("WARNING: The optional ':port' in password server = %s is deprecated\n",
+ lp_password_server()));
+ }
+
+ b_loaded = true;
+
+ /* Now we check we_are_a_wins_server and set szWINSserver to 127.0.0.1 */
+ /* if we_are_a_wins_server is true and we are in the client */
+ if (lp_is_in_client() && Globals.we_are_a_wins_server) {
+ lp_do_parameter(GLOBAL_SECTION_SNUM, "wins server", "127.0.0.1");
+ }
+
+ init_iconv();
+
+ fault_configure(smb_panic_s3);
+
+ /*
+ * We run this check once the whole smb.conf is parsed, to
+ * force some settings for the standard way a AD DC is
+ * operated. We may change these as our code evolves, which
+ * is why we force these settings.
+ */
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ lp_enforce_ad_dc_settings();
+ }
+
+ bAllowIncludeRegistry = true;
+
+ /* Check if command line max protocol < min protocol, if so
+ * report a warning to the user.
+ */
+ max_protocol = lp_client_max_protocol();
+ min_protocol = lp_client_min_protocol();
+ if (max_protocol < min_protocol) {
+ const char *max_protocolp, *min_protocolp;
+ max_protocolp = lpcfg_get_smb_protocol(max_protocol);
+ min_protocolp = lpcfg_get_smb_protocol(min_protocol);
+ DBG_ERR("Max protocol %s is less than min protocol %s.\n",
+ max_protocolp, min_protocolp);
+ }
+
+ TALLOC_FREE(frame);
+ return (bRetval);
+}
+
+static bool lp_load(const char *pszFname,
+ bool global_only,
+ bool save_defaults,
+ bool add_ipc,
+ bool reinit_globals)
+{
+ return lp_load_ex(pszFname,
+ global_only,
+ save_defaults,
+ add_ipc,
+ reinit_globals,
+ true, /* allow_include_registry */
+ false); /* load_all_shares*/
+}
+
+bool lp_load_initial_only(const char *pszFname)
+{
+ return lp_load_ex(pszFname,
+ true, /* global only */
+ true, /* save_defaults */
+ false, /* add_ipc */
+ true, /* reinit_globals */
+ false, /* allow_include_registry */
+ false); /* load_all_shares*/
+}
+
+/**
+ * most common lp_load wrapper, loading only the globals
+ *
+ * If this is used in a daemon or client utility it should be called
+ * after processing popt.
+ */
+bool lp_load_global(const char *file_name)
+{
+ return lp_load(file_name,
+ true, /* global_only */
+ false, /* save_defaults */
+ false, /* add_ipc */
+ true); /* reinit_globals */
+}
+
+/**
+ * The typical lp_load wrapper with shares, loads global and
+ * shares, including IPC, but does not force immediate
+ * loading of all shares from registry.
+ */
+bool lp_load_with_shares(const char *file_name)
+{
+ return lp_load(file_name,
+ false, /* global_only */
+ false, /* save_defaults */
+ true, /* add_ipc */
+ true); /* reinit_globals */
+}
+
+/**
+ * lp_load wrapper, especially for clients
+ */
+bool lp_load_client(const char *file_name)
+{
+ lp_set_in_client(true);
+
+ return lp_load_global(file_name);
+}
+
+/**
+ * lp_load wrapper, loading only globals, but intended
+ * for subsequent calls, not reinitializing the globals
+ * to default values
+ */
+bool lp_load_global_no_reinit(const char *file_name)
+{
+ return lp_load(file_name,
+ true, /* global_only */
+ false, /* save_defaults */
+ false, /* add_ipc */
+ false); /* reinit_globals */
+}
+
+/**
+ * lp_load wrapper, loading globals and shares,
+ * intended for subsequent calls, i.e. not reinitializing
+ * the globals to default values.
+ */
+bool lp_load_no_reinit(const char *file_name)
+{
+ return lp_load(file_name,
+ false, /* global_only */
+ false, /* save_defaults */
+ false, /* add_ipc */
+ false); /* reinit_globals */
+}
+
+
+/**
+ * lp_load wrapper, especially for clients, no reinitialization
+ */
+bool lp_load_client_no_reinit(const char *file_name)
+{
+ lp_set_in_client(true);
+
+ return lp_load_global_no_reinit(file_name);
+}
+
+bool lp_load_with_registry_shares(const char *pszFname)
+{
+ return lp_load_ex(pszFname,
+ false, /* global_only */
+ true, /* save_defaults */
+ false, /* add_ipc */
+ true, /* reinit_globals */
+ true, /* allow_include_registry */
+ true); /* load_all_shares*/
+}
+
+/***************************************************************************
+ Return the max number of services.
+***************************************************************************/
+
+int lp_numservices(void)
+{
+ return (iNumServices);
+}
+
+/***************************************************************************
+Display the contents of the services array in human-readable form.
+***************************************************************************/
+
+void lp_dump(FILE *f, bool show_defaults, int maxtoprint)
+{
+ int iService;
+ struct loadparm_context *lp_ctx;
+
+ if (show_defaults)
+ defaults_saved = false;
+
+ lp_ctx = setup_lp_context(talloc_tos());
+ if (lp_ctx == NULL) {
+ return;
+ }
+
+ lpcfg_dump_globals(lp_ctx, f, !defaults_saved);
+
+ lpcfg_dump_a_service(&sDefault, &sDefault, f, flags_list, show_defaults);
+
+ for (iService = 0; iService < maxtoprint; iService++) {
+ fprintf(f,"\n");
+ lp_dump_one(f, show_defaults, iService);
+ }
+ TALLOC_FREE(lp_ctx);
+}
+
+/***************************************************************************
+Display the contents of one service in human-readable form.
+***************************************************************************/
+
+void lp_dump_one(FILE * f, bool show_defaults, int snum)
+{
+ if (VALID(snum)) {
+ if (ServicePtrs[snum]->szService[0] == '\0')
+ return;
+ lpcfg_dump_a_service(ServicePtrs[snum], &sDefault, f,
+ flags_list, show_defaults);
+ }
+}
+
+/***************************************************************************
+Return the number of the service with the given name, or -1 if it doesn't
+exist. Note that this is a DIFFERENT ANIMAL from the internal function
+getservicebyname()! This works ONLY if all services have been loaded, and
+does not copy the found service.
+***************************************************************************/
+
+int lp_servicenumber(const char *pszServiceName)
+{
+ int iService;
+ fstring serviceName;
+
+ if (!pszServiceName) {
+ return GLOBAL_SECTION_SNUM;
+ }
+
+ for (iService = iNumServices - 1; iService >= 0; iService--) {
+ if (VALID(iService) && ServicePtrs[iService]->szService) {
+ /*
+ * The substitution here is used to support %U in
+ * service names
+ */
+ fstrcpy(serviceName, ServicePtrs[iService]->szService);
+ standard_sub_basic(get_current_username(),
+ get_current_user_info_domain(),
+ serviceName,sizeof(serviceName));
+ if (strequal(serviceName, pszServiceName)) {
+ break;
+ }
+ }
+ }
+
+ if (iService >= 0 && ServicePtrs[iService]->usershare == USERSHARE_VALID) {
+ struct timespec last_mod;
+
+ if (!usershare_exists(iService, &last_mod)) {
+ /* Remove the share security tdb entry for it. */
+ delete_share_security(lp_const_servicename(iService));
+ /* Remove it from the array. */
+ free_service_byindex(iService);
+ /* Doesn't exist anymore. */
+ return GLOBAL_SECTION_SNUM;
+ }
+
+ /* Has it been modified ? If so delete and reload. */
+ if (timespec_compare(&ServicePtrs[iService]->usershare_last_mod,
+ &last_mod) < 0) {
+ /* Remove it from the array. */
+ free_service_byindex(iService);
+ /* and now reload it. */
+ iService = load_usershare_service(pszServiceName);
+ }
+ }
+
+ if (iService < 0) {
+ DEBUG(7,("lp_servicenumber: couldn't find %s\n", pszServiceName));
+ return GLOBAL_SECTION_SNUM;
+ }
+
+ return (iService);
+}
+
+/*******************************************************************
+ A useful volume label function.
+********************************************************************/
+
+const char *volume_label(TALLOC_CTX *ctx, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *ret;
+ const char *label = lp_volume(ctx, lp_sub, snum);
+ size_t end = 32;
+
+ if (!*label) {
+ label = lp_servicename(ctx, lp_sub, snum);
+ }
+
+ /*
+ * Volume label can be a max of 32 bytes. Make sure to truncate
+ * it at a codepoint boundary if it's longer than 32 and contains
+ * multibyte characters. Windows insists on a volume label being
+ * a valid mb sequence, and errors out if not.
+ */
+ if (strlen(label) > 32) {
+ /*
+ * A MB char can be a max of 5 bytes, thus
+ * we should have a valid mb character at a
+ * minimum position of (32-5) = 27.
+ */
+ while (end >= 27) {
+ /*
+ * Check if a codepoint starting from next byte
+ * is valid. If yes, then the current byte is the
+ * end of a MB or ascii sequence and the label can
+ * be safely truncated here. If not, keep going
+ * backwards till a valid codepoint is found.
+ */
+ size_t len = 0;
+ const char *s = &label[end];
+ codepoint_t c = next_codepoint(s, &len);
+ if (c != INVALID_CODEPOINT) {
+ break;
+ }
+ end--;
+ }
+ }
+
+ /* This returns a max of 33 byte guaranteed null terminated string. */
+ ret = talloc_strndup(ctx, label, end);
+ if (!ret) {
+ return "";
+ }
+ return ret;
+}
+
+/*******************************************************************
+ Get the default server type we will announce as via nmbd.
+********************************************************************/
+
+int lp_default_server_announce(void)
+{
+ int default_server_announce = 0;
+ default_server_announce |= SV_TYPE_WORKSTATION;
+ default_server_announce |= SV_TYPE_SERVER;
+ default_server_announce |= SV_TYPE_SERVER_UNIX;
+
+ /* note that the flag should be set only if we have a
+ printer service but nmbd doesn't actually load the
+ services so we can't tell --jerry */
+
+ default_server_announce |= SV_TYPE_PRINTQ_SERVER;
+
+ default_server_announce |= SV_TYPE_SERVER_NT;
+ default_server_announce |= SV_TYPE_NT;
+
+ switch (lp_server_role()) {
+ case ROLE_DOMAIN_MEMBER:
+ default_server_announce |= SV_TYPE_DOMAIN_MEMBER;
+ break;
+ case ROLE_DOMAIN_PDC:
+ case ROLE_IPA_DC:
+ default_server_announce |= SV_TYPE_DOMAIN_CTRL;
+ break;
+ case ROLE_DOMAIN_BDC:
+ default_server_announce |= SV_TYPE_DOMAIN_BAKCTRL;
+ break;
+ case ROLE_STANDALONE:
+ default:
+ break;
+ }
+ if (lp_time_server())
+ default_server_announce |= SV_TYPE_TIME_SOURCE;
+
+ if (lp_host_msdfs())
+ default_server_announce |= SV_TYPE_DFS_SERVER;
+
+ return default_server_announce;
+}
+
+/***********************************************************
+ If we are PDC then prefer us as DMB
+************************************************************/
+
+bool lp_domain_master(void)
+{
+ if (Globals._domain_master == Auto)
+ return (lp_server_role() == ROLE_DOMAIN_PDC ||
+ lp_server_role() == ROLE_IPA_DC);
+
+ return (bool)Globals._domain_master;
+}
+
+/***********************************************************
+ If we are PDC then prefer us as DMB
+************************************************************/
+
+static bool lp_domain_master_true_or_auto(void)
+{
+ if (Globals._domain_master) /* auto or yes */
+ return true;
+
+ return false;
+}
+
+/***********************************************************
+ If we are DMB then prefer us as LMB
+************************************************************/
+
+bool lp_preferred_master(void)
+{
+ int preferred_master = lp__preferred_master();
+
+ if (preferred_master == Auto)
+ return (lp_local_master() && lp_domain_master());
+
+ return (bool)preferred_master;
+}
+
+/*******************************************************************
+ Remove a service.
+********************************************************************/
+
+void lp_remove_service(int snum)
+{
+ ServicePtrs[snum]->valid = false;
+}
+
+const char *lp_printername(TALLOC_CTX *ctx,
+ const struct loadparm_substitution *lp_sub,
+ int snum)
+{
+ const char *ret = lp__printername(ctx, lp_sub, snum);
+
+ if (ret == NULL || *ret == '\0') {
+ ret = lp_const_servicename(snum);
+ }
+
+ return ret;
+}
+
+
+/***********************************************************
+ Allow daemons such as winbindd to fix their logfile name.
+************************************************************/
+
+void lp_set_logfile(const char *name)
+{
+ lpcfg_string_set(Globals.ctx, &Globals.logfile, name);
+ debug_set_logfile(name);
+}
+
+/*******************************************************************
+ Return the max print jobs per queue.
+********************************************************************/
+
+int lp_maxprintjobs(int snum)
+{
+ int maxjobs = lp_max_print_jobs(snum);
+
+ if (maxjobs <= 0 || maxjobs >= PRINT_MAX_JOBID)
+ maxjobs = PRINT_MAX_JOBID - 1;
+
+ return maxjobs;
+}
+
+const char *lp_printcapname(void)
+{
+ const char *printcap_name = lp_printcap_name();
+
+ if ((printcap_name != NULL) &&
+ (printcap_name[0] != '\0'))
+ return printcap_name;
+
+ if (sDefault.printing == PRINT_CUPS) {
+ return "cups";
+ }
+
+ if (sDefault.printing == PRINT_BSD)
+ return "/etc/printcap";
+
+ return PRINTCAP_NAME;
+}
+
+static uint32_t spoolss_state;
+
+bool lp_disable_spoolss( void )
+{
+ if ( spoolss_state == SVCCTL_STATE_UNKNOWN )
+ spoolss_state = lp__disable_spoolss() ? SVCCTL_STOPPED : SVCCTL_RUNNING;
+
+ return spoolss_state == SVCCTL_STOPPED ? true : false;
+}
+
+void lp_set_spoolss_state( uint32_t state )
+{
+ SMB_ASSERT( (state == SVCCTL_STOPPED) || (state == SVCCTL_RUNNING) );
+
+ spoolss_state = state;
+}
+
+uint32_t lp_get_spoolss_state( void )
+{
+ return lp_disable_spoolss() ? SVCCTL_STOPPED : SVCCTL_RUNNING;
+}
+
+/*******************************************************************
+ Turn off sendfile if we find the underlying OS doesn't support it.
+********************************************************************/
+
+void set_use_sendfile(int snum, bool val)
+{
+ if (LP_SNUM_OK(snum))
+ ServicePtrs[snum]->_use_sendfile = val;
+ else
+ sDefault._use_sendfile = val;
+}
+
+void lp_set_mangling_method(const char *new_method)
+{
+ lpcfg_string_set(Globals.ctx, &Globals.mangling_method, new_method);
+}
+
+/*******************************************************************
+ Global state for POSIX pathname processing.
+********************************************************************/
+
+static bool posix_pathnames;
+
+bool lp_posix_pathnames(void)
+{
+ return posix_pathnames;
+}
+
+/*******************************************************************
+ Change everything needed to ensure POSIX pathname processing (currently
+ not much).
+********************************************************************/
+
+void lp_set_posix_pathnames(void)
+{
+ posix_pathnames = true;
+}
+
+/*******************************************************************
+ Global state for POSIX lock processing - CIFS unix extensions.
+********************************************************************/
+
+bool posix_default_lock_was_set;
+static enum brl_flavour posix_cifsx_locktype; /* By default 0 == WINDOWS_LOCK */
+
+enum brl_flavour lp_posix_cifsu_locktype(files_struct *fsp)
+{
+ if (posix_default_lock_was_set) {
+ return posix_cifsx_locktype;
+ } else {
+ return (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
+ POSIX_LOCK : WINDOWS_LOCK;
+ }
+}
+
+/*******************************************************************
+********************************************************************/
+
+void lp_set_posix_default_cifsx_readwrite_locktype(enum brl_flavour val)
+{
+ posix_default_lock_was_set = true;
+ posix_cifsx_locktype = val;
+}
+
+int lp_min_receive_file_size(void)
+{
+ int min_receivefile_size = lp_min_receivefile_size();
+
+ if (min_receivefile_size < 0) {
+ return 0;
+ }
+ return min_receivefile_size;
+}
+
+/*******************************************************************
+ Safe wide links checks.
+ This helper function always verify the validity of wide links,
+ even after a configuration file reload.
+********************************************************************/
+
+void widelinks_warning(int snum)
+{
+ if (lp_allow_insecure_wide_links()) {
+ return;
+ }
+
+ if (lp_wide_links(snum)) {
+ if (lp_smb1_unix_extensions()) {
+ DBG_ERR("Share '%s' has wide links and SMB1 unix "
+ "extensions enabled. "
+ "These parameters are incompatible. "
+ "Wide links will be disabled for this share.\n",
+ lp_const_servicename(snum));
+ } else if (lp_smb3_unix_extensions(snum)) {
+ DBG_ERR("Share '%s' has wide links and SMB3 Unix "
+ "extensions enabled. "
+ "These parameters are incompatible. "
+ "Wide links will be disabled for this share.\n",
+ lp_const_servicename(snum));
+ }
+ }
+}
+
+bool lp_widelinks(int snum)
+{
+ /* wide links is always incompatible with unix extensions */
+ if (lp_smb1_unix_extensions() || lp_smb3_unix_extensions(snum)) {
+ /*
+ * Unless we have "allow insecure widelinks"
+ * turned on.
+ */
+ if (!lp_allow_insecure_wide_links()) {
+ return false;
+ }
+ }
+
+ return lp_wide_links(snum);
+}
+
+int lp_server_role(void)
+{
+ return lp_find_server_role(lp__server_role(),
+ lp__security(),
+ lp__domain_logons(),
+ lp_domain_master_true_or_auto());
+}
+
+int lp_security(void)
+{
+ return lp_find_security(lp__server_role(),
+ lp__security());
+}
+
+int lp_client_max_protocol(void)
+{
+ int client_max_protocol = lp__client_max_protocol();
+ if (client_max_protocol == PROTOCOL_DEFAULT) {
+ return PROTOCOL_LATEST;
+ }
+ return client_max_protocol;
+}
+
+int lp_client_ipc_min_protocol(void)
+{
+ int client_ipc_min_protocol = lp__client_ipc_min_protocol();
+ if (client_ipc_min_protocol == PROTOCOL_DEFAULT) {
+ client_ipc_min_protocol = lp_client_min_protocol();
+ }
+ if (client_ipc_min_protocol < PROTOCOL_NT1) {
+ return PROTOCOL_NT1;
+ }
+ return client_ipc_min_protocol;
+}
+
+int lp_client_ipc_max_protocol(void)
+{
+ int client_ipc_max_protocol = lp__client_ipc_max_protocol();
+ if (client_ipc_max_protocol == PROTOCOL_DEFAULT) {
+ return PROTOCOL_LATEST;
+ }
+ if (client_ipc_max_protocol < PROTOCOL_NT1) {
+ return PROTOCOL_NT1;
+ }
+ return client_ipc_max_protocol;
+}
+
+int lp_client_ipc_signing(void)
+{
+ int client_ipc_signing = lp__client_ipc_signing();
+ if (client_ipc_signing == SMB_SIGNING_DEFAULT) {
+ return SMB_SIGNING_REQUIRED;
+ }
+ return client_ipc_signing;
+}
+
+enum credentials_use_kerberos lp_client_use_kerberos(void)
+{
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) {
+ return CRED_USE_KERBEROS_REQUIRED;
+ }
+
+ return lp__client_use_kerberos();
+}
+
+
+int lp_rpc_low_port(void)
+{
+ return Globals.rpc_low_port;
+}
+
+int lp_rpc_high_port(void)
+{
+ return Globals.rpc_high_port;
+}
+
+/*
+ * Do not allow LanMan auth if unless NTLMv1 is also allowed
+ *
+ * This also ensures it is disabled if NTLM is totally disabled
+ */
+bool lp_lanman_auth(void)
+{
+ enum ntlm_auth_level ntlm_auth_level = lp_ntlm_auth();
+
+ if (ntlm_auth_level == NTLM_AUTH_ON) {
+ return lp__lanman_auth();
+ } else {
+ return false;
+ }
+}
+
+struct loadparm_global * get_globals(void)
+{
+ return &Globals;
+}
+
+unsigned int * get_flags(void)
+{
+ if (flags_list == NULL) {
+ flags_list = talloc_zero_array(NULL, unsigned int, num_parameters());
+ }
+
+ return flags_list;
+}
+
+enum samba_weak_crypto lp_weak_crypto(void)
+{
+ if (Globals.weak_crypto == SAMBA_WEAK_CRYPTO_UNKNOWN) {
+ Globals.weak_crypto = SAMBA_WEAK_CRYPTO_DISALLOWED;
+
+ if (samba_gnutls_weak_crypto_allowed()) {
+ Globals.weak_crypto = SAMBA_WEAK_CRYPTO_ALLOWED;
+ }
+ }
+
+ return Globals.weak_crypto;
+}
+
+uint32_t lp_get_async_dns_timeout(void)
+{
+ /*
+ * Clamp minimum async dns timeout to 1 second
+ * as per the man page.
+ */
+ return MAX(Globals.async_dns_timeout, 1);
+}
diff --git a/source3/param/loadparm.h b/source3/param/loadparm.h
new file mode 100644
index 0000000..7816291
--- /dev/null
+++ b/source3/param/loadparm.h
@@ -0,0 +1,204 @@
+/*
+ *
+ * Unix SMB/CIFS implementation.
+ *
+ * Type definitions for loadparm
+ *
+ * Copyright (c) 2020 Andreas Schneider <asn@samba.org>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _S3_LOADPARM_H
+#define _S3_LOADPARM_H
+
+#include <talloc.h>
+#include <regex.h>
+
+/* Forward declarations */
+typedef struct stat_ex SMB_STRUCT_STAT;
+typedef struct files_struct files_struct;
+struct smbd_server_connection;
+struct security_descriptor;
+struct loadparm_context;
+
+/* The following definitions come from param/loadparm.c */
+
+void loadparm_s3_init_globals(struct loadparm_context *lp_ctx,
+ bool reinit_globals);
+
+const struct loadparm_substitution *loadparm_s3_global_substitution(void);
+
+char *lp_parm_substituted_string(TALLOC_CTX *mem_ctx,
+ const struct loadparm_substitution *lp_sub,
+ int snum,
+ const char *type,
+ const char *option,
+ const char *def);
+
+char *lp_servicename(TALLOC_CTX *ctx, const struct loadparm_substitution *, int);
+const char *lp_const_servicename(int);
+bool lp_autoloaded(int);
+const char *lp_dnsdomain(void);
+int lp_winbind_max_domain_connections(void);
+bool lp_idmap_range(const char *domain_name, uint32_t *low, uint32_t *high);
+bool lp_idmap_default_range(uint32_t *low, uint32_t *high);
+const char *lp_idmap_backend(const char *domain_name);
+const char *lp_idmap_default_backend (void);
+int lp_security(void);
+int lp_client_max_protocol(void);
+int lp_client_ipc_min_protocol(void);
+int lp_client_ipc_max_protocol(void);
+int lp_client_ipc_signing(void);
+enum credentials_use_kerberos lp_client_use_kerberos(void);
+int lp_smb2_max_credits(void);
+int lp_cups_encrypt(void);
+bool lp_widelinks(int );
+int lp_rpc_low_port(void);
+int lp_rpc_high_port(void);
+bool lp_lanman_auth(void);
+enum samba_weak_crypto lp_weak_crypto(void);
+
+int lp_wi_scan_global_parametrics(
+ const char *regex, size_t max_matches,
+ bool (*cb)(const char *string, regmatch_t matches[],
+ void *private_data),
+ void *private_data);
+
+const char *lp_parm_const_string(int snum, const char *type, const char *option, const char *def);
+struct loadparm_service;
+const char *lp_parm_const_string_service(struct loadparm_service *service, const char *type,
+ const char *option, const char *def);
+const char **lp_parm_string_list(int snum, const char *type, const char *option, const char **def);
+int lp_parm_int(int snum, const char *type, const char *option, int def);
+unsigned long lp_parm_ulong(int snum, const char *type, const char *option, unsigned long def);
+unsigned long long lp_parm_ulonglong(int snum, const char *type,
+ const char *option,
+ unsigned long long def);
+bool lp_parm_bool(int snum, const char *type, const char *option, bool def);
+struct enum_list;
+int lp_parm_enum(int snum, const char *type, const char *option,
+ const struct enum_list *_enum, int def);
+char *canonicalize_servicename(TALLOC_CTX *ctx, const char *src);
+bool lp_add_home(const char *pszHomename, int iDefaultService,
+ const char *user, const char *pszHomedir);
+int lp_add_service(const char *pszService, int iDefaultService);
+bool lp_add_printer(const char *pszPrintername, int iDefaultService);
+bool lp_parameter_is_valid(const char *pszParmName);
+bool lp_parameter_is_global(const char *pszParmName);
+bool lp_canonicalize_parameter(const char *parm_name, const char **canon_parm,
+ bool *inverse);
+bool lp_canonicalize_parameter_with_value(const char *parm_name,
+ const char *val,
+ const char **canon_parm,
+ const char **canon_val);
+void show_parameter_list(void);
+bool lp_invert_boolean(const char *str, const char **inverse_str);
+bool lp_canonicalize_boolean(const char *str, const char**canon_str);
+bool process_registry_service(const char *service_name);
+bool process_registry_shares(void);
+bool lp_config_backend_is_registry(void);
+bool lp_config_backend_is_file(void);
+bool lp_file_list_changed(void);
+const char *lp_ldap_machine_suffix(TALLOC_CTX *ctx);
+const char *lp_ldap_user_suffix(TALLOC_CTX *ctx);
+const char *lp_ldap_group_suffix(TALLOC_CTX *ctx);
+const char *lp_ldap_idmap_suffix(TALLOC_CTX *ctx);
+struct parm_struct;
+/* Return a pointer to a service by name. */
+struct loadparm_service *lp_service(const char *pszServiceName);
+struct loadparm_service *lp_servicebynum(int snum);
+struct loadparm_service *lp_default_loadparm_service(void);
+void *lp_parm_ptr(struct loadparm_service *service, struct parm_struct *parm);
+void *lp_local_ptr_by_snum(int snum, struct parm_struct *parm);
+bool lp_do_parameter(int snum, const char *pszParmName, const char *pszParmValue);
+bool dump_a_parameter(int snum, char *parm_name, FILE * f, bool isGlobal);
+bool lp_snum_ok(int iService);
+void lp_add_one_printer(const char *name, const char *comment,
+ const char *location, void *pdata);
+bool lp_loaded(void);
+void lp_killunused(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int));
+void lp_kill_all_services(void);
+void lp_killservice(int iServiceIn);
+const char* server_role_str(uint32_t role);
+enum usershare_err parse_usershare_file(TALLOC_CTX *ctx,
+ SMB_STRUCT_STAT *psbuf,
+ const char *servicename,
+ int snum,
+ char **lines,
+ int numlines,
+ char **pp_sharepath,
+ char **pp_comment,
+ char **pp_cp_share_name,
+ struct security_descriptor **ppsd,
+ bool *pallow_guest);
+int load_usershare_service(const char *servicename);
+int load_usershare_shares(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int));
+void gfree_loadparm(void);
+bool lp_load_initial_only(const char *pszFname);
+bool lp_load_global(const char *file_name);
+bool lp_load_with_shares(const char *file_name);
+bool lp_load_client(const char *file_name);
+bool lp_load_global_no_reinit(const char *file_name);
+bool lp_load_no_reinit(const char *file_name);
+bool lp_load_client_no_reinit(const char *file_name);
+bool lp_load_with_registry_shares(const char *pszFname);
+int lp_numservices(void);
+void lp_dump(FILE *f, bool show_defaults, int maxtoprint);
+void lp_dump_one(FILE * f, bool show_defaults, int snum);
+int lp_servicenumber(const char *pszServiceName);
+const char *volume_label(TALLOC_CTX *ctx, int snum);
+bool lp_domain_master(void);
+bool lp_preferred_master(void);
+void lp_remove_service(int snum);
+void lp_copy_service(int snum, const char *new_name);
+int lp_default_server_announce(void);
+const char *lp_printername(TALLOC_CTX *ctx,
+ const struct loadparm_substitution *lp_sub,
+ int snum);
+void lp_set_logfile(const char *name);
+int lp_maxprintjobs(int snum);
+const char *lp_printcapname(void);
+bool lp_disable_spoolss( void );
+void lp_set_spoolss_state( uint32_t state );
+uint32_t lp_get_spoolss_state( void );
+struct smb1_signing_state;
+void set_use_sendfile(int snum, bool val);
+void lp_set_mangling_method(const char *new_method);
+bool lp_posix_pathnames(void);
+void lp_set_posix_pathnames(void);
+enum brl_flavour lp_posix_cifsu_locktype(files_struct *fsp);
+void lp_set_posix_default_cifsx_readwrite_locktype(enum brl_flavour val);
+int lp_min_receive_file_size(void);
+void widelinks_warning(int snum);
+const char *lp_ncalrpc_dir(void);
+void _lp_set_server_role(int server_role);
+uint32_t lp_get_async_dns_timeout(void);
+
+/* The following definitions come from param/loadparm_ctx.c */
+
+const struct loadparm_s3_helpers *loadparm_s3_helpers(void);
+
+/* The following definitions come from param/loadparm_server_role.c */
+
+int lp_server_role(void);
+void set_server_role(void);
+
+/* The following definitions come from param/util.c */
+
+uint32_t get_int_param( const char* param );
+char *get_string_param( const char* param );
+
+#endif /* _S3_LOADPARM_H */
diff --git a/source3/param/loadparm_ctx.c b/source3/param/loadparm_ctx.c
new file mode 100644
index 0000000..2cc6dfd
--- /dev/null
+++ b/source3/param/loadparm_ctx.c
@@ -0,0 +1,82 @@
+/*
+ Unix SMB/CIFS implementation.
+ Parameter loading functions
+ Copyright (C) Andrew Bartlett 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/param/s3_param.h"
+
+static struct loadparm_service *lp_service_for_s4_ctx(const char *servicename)
+{
+ TALLOC_CTX *mem_ctx;
+ struct loadparm_service *service;
+
+ mem_ctx = talloc_stackframe();
+ service = lp_service(servicename);
+ talloc_free(mem_ctx);
+
+ return service;
+}
+
+static struct loadparm_service *lp_servicebynum_for_s4_ctx(int servicenum)
+{
+ TALLOC_CTX *mem_ctx;
+ struct loadparm_service *service;
+
+ mem_ctx = talloc_stackframe();
+ service = lp_servicebynum(servicenum);
+ talloc_free(mem_ctx);
+
+ return service;
+}
+
+static bool lp_load_for_s4_ctx(const char *filename)
+{
+ TALLOC_CTX *mem_ctx;
+ bool status;
+
+ mem_ctx = talloc_stackframe();
+ status = lp_load_no_reinit(filename);
+ talloc_free(mem_ctx);
+
+ return status;
+}
+
+static struct loadparm_s3_helpers s3_fns =
+{
+ .get_parm_ptr = lp_parm_ptr,
+ .get_service = lp_service_for_s4_ctx,
+ .get_servicebynum = lp_servicebynum_for_s4_ctx,
+ .getservicebyname = getservicebyname,
+ .get_numservices = lp_numservices,
+ .load = lp_load_for_s4_ctx,
+ .store_cmdline = store_lp_set_cmdline,
+ .dump = lp_dump,
+ .lp_include = lp_include,
+ .init_ldap_debugging = init_ldap_debugging,
+ .do_section = lp_do_section,
+ .init_globals = loadparm_s3_init_globals,
+};
+
+const struct loadparm_s3_helpers *loadparm_s3_helpers(void)
+{
+ struct loadparm_s3_helpers *helpers;
+ helpers = &s3_fns;
+ helpers->globals = get_globals();
+ helpers->flags = get_flags();
+ return helpers;
+}
diff --git a/source3/param/pyparam.c b/source3/param/pyparam.c
new file mode 100644
index 0000000..8c057fd
--- /dev/null
+++ b/source3/param/pyparam.c
@@ -0,0 +1,92 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Amitay Isaacs <amitay@gmail.com> 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lib/replace/system/python.h"
+#include "includes.h"
+#include "python/py3compat.h"
+#include "param/param.h"
+#include "param/loadparm.h"
+#include "lib/talloc/pytalloc.h"
+
+static PyTypeObject *loadparm_Type = NULL;
+
+void initparam(void);
+
+static PyObject *py_get_context(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+ PyObject *py_loadparm;
+ const struct loadparm_s3_helpers *s3_context;
+ const struct loadparm_context *s4_context;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ s3_context = loadparm_s3_helpers();
+
+ s4_context = loadparm_init_s3(frame, s3_context);
+ if (s4_context == NULL) {
+ talloc_free(frame);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ py_loadparm = pytalloc_steal(loadparm_Type, discard_const_p(struct loadparm_context, s4_context));
+ if (py_loadparm == NULL) {
+ talloc_free(frame);
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ talloc_free(frame);
+
+ return py_loadparm;
+}
+
+static PyMethodDef pyparam_methods[] = {
+ { "get_context", (PyCFunction)py_get_context, METH_NOARGS,
+ "Returns LoadParm context." },
+ {0}
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "param",
+ .m_doc = "Parsing and writing Samba3 configuration files.",
+ .m_size = -1,
+ .m_methods = pyparam_methods,
+};
+
+MODULE_INIT_FUNC(param)
+{
+ PyObject *m = NULL, *mod = NULL;
+
+ m = PyModule_Create(&moduledef);
+ if (m == NULL)
+ return NULL;
+
+ mod = PyImport_ImportModule("samba.param");
+ if (mod == NULL) {
+ return NULL;
+ }
+
+ loadparm_Type = (PyTypeObject *)PyObject_GetAttrString(mod, "LoadParm");
+ Py_DECREF(mod);
+ if (loadparm_Type == NULL) {
+ return NULL;
+ }
+ return m;
+}
diff --git a/source3/param/pyparam.h b/source3/param/pyparam.h
new file mode 100644
index 0000000..8a40a9b
--- /dev/null
+++ b/source3/param/pyparam.h
@@ -0,0 +1,27 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) David Mulder <dmulder@samba.org> 2021
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PYPARAM_H_
+#define _PYPARAM_H_
+
+#include "param/param.h"
+
+_PUBLIC_ struct loadparm_context *lpcfg_from_py_object(TALLOC_CTX *mem_ctx, PyObject *py_obj);
+
+#endif /* _PYPARAM_H_ */
diff --git a/source3/param/pyparam_util.c b/source3/param/pyparam_util.c
new file mode 100644
index 0000000..293f9c2
--- /dev/null
+++ b/source3/param/pyparam_util.c
@@ -0,0 +1,76 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) David Mulder <dmulder@samba.org> 2021
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lib/replace/system/python.h"
+#include "includes.h"
+#include "param/param.h"
+#include "param/pyparam.h"
+#include "param/loadparm.h"
+#include "param/s3_param.h"
+#include <pytalloc.h>
+
+#define PyErr_FromString(str) Py_BuildValue("(s)", (str))
+#define PyLoadparmContext_AsLoadparmContext(obj) pytalloc_get_type(obj, struct loadparm_context)
+
+_PUBLIC_ struct loadparm_context *lpcfg_from_py_object(TALLOC_CTX *mem_ctx, PyObject *py_obj)
+{
+ PyObject *param_mod;
+ PyTypeObject *lp_type;
+ bool is_lpobj;
+ const struct loadparm_s3_helpers *s3_context;
+ struct loadparm_context *s4_context;
+
+ if (py_obj == Py_None) {
+ s3_context = loadparm_s3_helpers();
+
+ s4_context = loadparm_init_s3(mem_ctx, s3_context);
+ if (s4_context == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (!lpcfg_load_default(s4_context)) {
+ PyErr_FromString("Failed to load defaults\n");
+ return NULL;
+ }
+
+ return s4_context;
+ }
+
+ param_mod = PyImport_ImportModule("samba.param");
+ if (param_mod == NULL) {
+ return NULL;
+ }
+
+ lp_type = (PyTypeObject *)PyObject_GetAttrString(param_mod, "LoadParm");
+ Py_DECREF(param_mod);
+ if (lp_type == NULL) {
+ PyErr_SetString(PyExc_RuntimeError, "Unable to import LoadParm");
+ return NULL;
+ }
+
+ is_lpobj = PyObject_TypeCheck(py_obj, lp_type);
+ Py_DECREF(lp_type);
+ if (is_lpobj) {
+ return talloc_reference(mem_ctx, PyLoadparmContext_AsLoadparmContext(py_obj));
+ }
+
+ PyErr_SetNone(PyExc_TypeError);
+ return NULL;
+}
diff --git a/source3/param/service.c b/source3/param/service.c
new file mode 100644
index 0000000..b06a2fb
--- /dev/null
+++ b/source3/param/service.c
@@ -0,0 +1,335 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../lib/tsocket/tsocket.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "lib/param/loadparm.h"
+
+static int load_registry_service(const char *servicename)
+{
+ if (!lp_registry_shares()) {
+ return -1;
+ }
+
+ if ((servicename == NULL) || (*servicename == '\0')) {
+ return -1;
+ }
+
+ if (strequal(servicename, GLOBAL_NAME)) {
+ return -2;
+ }
+
+ if (!process_registry_service(servicename)) {
+ return -1;
+ }
+
+ return lp_servicenumber(servicename);
+}
+
+void load_registry_shares(void)
+{
+ DEBUG(8, ("load_registry_shares()\n"));
+ if (!lp_registry_shares()) {
+ return;
+ }
+
+ process_registry_shares();
+
+ return;
+}
+
+/****************************************************************************
+ Add a home service. Returns the new service number or -1 if fail.
+****************************************************************************/
+
+int add_home_service(const char *service, const char *username, const char *homedir)
+{
+ int iHomeService;
+
+ if (!service || !homedir || homedir[0] == '\0')
+ return -1;
+
+ if ((iHomeService = lp_servicenumber(HOMES_NAME)) < 0) {
+ if ((iHomeService = load_registry_service(HOMES_NAME)) < 0) {
+ return -1;
+ }
+ }
+
+ /*
+ * If this is a winbindd provided username, remove
+ * the domain component before adding the service.
+ * Log a warning if the "path=" parameter does not
+ * include any macros.
+ */
+
+ {
+ const char *p = strchr(service,*lp_winbind_separator());
+
+ /* We only want the 'user' part of the string */
+ if (p) {
+ service = p + 1;
+ }
+ }
+
+ if (!lp_add_home(service, iHomeService, username, homedir)) {
+ return -1;
+ }
+
+ return lp_servicenumber(service);
+
+}
+
+/**
+ * Find a service entry.
+ *
+ * @param service is modified (to canonical form??)
+ **/
+
+int find_service(TALLOC_CTX *ctx, const char *service_in, char **p_service_out)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int iService;
+
+ if (!service_in) {
+ return -1;
+ }
+
+ /* First make a copy. */
+ *p_service_out = talloc_strdup(ctx, service_in);
+ if (!*p_service_out) {
+ return -1;
+ }
+
+ all_string_sub(*p_service_out,"\\","/",0);
+
+ iService = lp_servicenumber(*p_service_out);
+
+ /*
+ * check for whether the service is a registry share before
+ * handling home directories. This is to ensure that
+ * that in the case service name is identical to a user's
+ * home directory, the explicit service is preferred.
+ */
+ if (iService < 0) {
+ iService = load_registry_service(*p_service_out);
+ }
+
+ /* now handle the special case of a home directory */
+ if (iService < 0) {
+ char *phome_dir = get_user_home_dir(ctx, *p_service_out);
+
+ if(!phome_dir) {
+ /*
+ * Try mapping the servicename, it may
+ * be a Windows to unix mapped user name.
+ */
+ if(map_username(ctx, *p_service_out, p_service_out)) {
+ if (*p_service_out == NULL) {
+ /* Out of memory. */
+ return -1;
+ }
+ phome_dir = get_user_home_dir(
+ ctx, *p_service_out);
+ }
+ }
+
+ DEBUG(3,("checking for home directory %s gave %s\n",*p_service_out,
+ phome_dir?phome_dir:"(NULL)"));
+
+ if (!strequal(phome_dir, "/")) {
+ iService = add_home_service(*p_service_out,
+ *p_service_out, /* username */
+ phome_dir);
+ }
+ }
+
+ /* If we still don't have a service, attempt to add it as a printer. */
+ if (iService < 0) {
+ int iPrinterService;
+
+ if ((iPrinterService = lp_servicenumber(PRINTERS_NAME)) < 0) {
+ iPrinterService = load_registry_service(PRINTERS_NAME);
+ }
+ if (iPrinterService >= 0) {
+ DEBUG(3,("checking whether %s is a valid printer name...\n",
+ *p_service_out));
+ if (printer_list_printername_exists(*p_service_out)) {
+ DEBUG(3,("%s is a valid printer name\n",
+ *p_service_out));
+ DEBUG(3,("adding %s as a printer service\n",
+ *p_service_out));
+ lp_add_printer(*p_service_out, iPrinterService);
+ iService = lp_servicenumber(*p_service_out);
+ if (iService < 0) {
+ DEBUG(0,("failed to add %s as a printer service!\n",
+ *p_service_out));
+ }
+ } else {
+ DEBUG(3,("%s is not a valid printer name\n",
+ *p_service_out));
+ }
+ }
+ }
+
+ /* Is it a usershare service ? */
+ if (iService < 0 && *lp_usershare_path(talloc_tos(), lp_sub)) {
+ /* Ensure the name is canonicalized. */
+ if (!strlower_m(*p_service_out)) {
+ goto fail;
+ }
+ iService = load_usershare_service(*p_service_out);
+ }
+
+ /* just possibly it's a default service? */
+ if (iService < 0) {
+ char *pdefservice = lp_defaultservice(talloc_tos(), lp_sub);
+ if (pdefservice &&
+ *pdefservice &&
+ !strequal(pdefservice, *p_service_out)
+ && !strstr_m(*p_service_out,"..")) {
+ /*
+ * We need to do a local copy here as lp_defaultservice()
+ * returns one of the rotating lp_string buffers that
+ * could get overwritten by the recursive find_service() call
+ * below. Fix from Josef Hinteregger <joehtg@joehtg.co.at>.
+ */
+ char *defservice = talloc_strdup(ctx, pdefservice);
+
+ if (!defservice) {
+ goto fail;
+ }
+
+ /* Disallow anything except explicit share names. */
+ if (strequal(defservice,HOMES_NAME) ||
+ strequal(defservice, PRINTERS_NAME) ||
+ strequal(defservice, "IPC$")) {
+ TALLOC_FREE(defservice);
+ goto fail;
+ }
+
+ iService = find_service(ctx, defservice, p_service_out);
+ if (!*p_service_out) {
+ TALLOC_FREE(defservice);
+ iService = -1;
+ goto fail;
+ }
+ if (iService >= 0) {
+ all_string_sub(*p_service_out, "_","/",0);
+ iService = lp_add_service(*p_service_out, iService);
+ }
+ TALLOC_FREE(defservice);
+ }
+ }
+
+ if (iService >= 0) {
+ if (!VALID_SNUM(iService)) {
+ DEBUG(0,("Invalid snum %d for %s\n",iService,
+ *p_service_out));
+ iService = -1;
+ }
+ }
+
+ fail:
+
+ if (iService < 0) {
+ DEBUG(3,("find_service() failed to find service %s\n",
+ *p_service_out));
+ }
+
+ return (iService);
+}
+
+bool lp_allow_local_address(
+ int snum, const struct tsocket_address *local_address)
+{
+ bool is_inet = tsocket_address_is_inet(local_address, "ip");
+ const char **server_addresses = lp_server_addresses(snum);
+ char *local = NULL;
+ ssize_t i;
+
+ if (!is_inet) {
+ return false;
+ }
+
+ if (server_addresses == NULL) {
+ return true;
+ }
+
+ local = tsocket_address_inet_addr_string(local_address, talloc_tos());
+ if (local == NULL) {
+ return false;
+ }
+
+ for (i=0; server_addresses[i] != NULL; i++) {
+ struct tsocket_address *server_addr = NULL;
+ char *server_addr_string = NULL;
+ bool equal;
+ int ret;
+
+ /*
+ * Go through struct tsocket_address to normalize the
+ * string representation
+ */
+
+ ret = tsocket_address_inet_from_strings(
+ talloc_tos(),
+ "ip",
+ server_addresses[i],
+ 0,
+ &server_addr);
+ if (ret == -1) {
+ DBG_WARNING("tsocket_address_inet_from_strings "
+ "failed for %s: %s, ignoring\n",
+ server_addresses[i],
+ strerror(errno));
+ continue;
+ }
+
+ server_addr_string = tsocket_address_inet_addr_string(
+ server_addr, talloc_tos());
+ TALLOC_FREE(server_addr);
+ if (server_addr_string == NULL) {
+ DBG_ERR("tsocket_address_inet_addr_string failed "
+ "for %s, ignoring\n",
+ server_addresses[i]);
+ continue;
+ }
+
+ equal = strequal(local, server_addr_string);
+ TALLOC_FREE(server_addr_string);
+
+ if (equal) {
+ TALLOC_FREE(local);
+ return true;
+ }
+ }
+
+ TALLOC_FREE(local);
+ return false;
+}
diff --git a/source3/param/test_lp_load.c b/source3/param/test_lp_load.c
new file mode 100644
index 0000000..493aa31
--- /dev/null
+++ b/source3/param/test_lp_load.c
@@ -0,0 +1,120 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test for lp_load()
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/param/param.h"
+
+int main(int argc, const char **argv)
+{
+ const char *config_file = NULL;
+ int ret = 0;
+ poptContext pc;
+ char *count_str = NULL;
+ int i, count = 1;
+ int opt;
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "count",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &count_str,
+ .val = 1,
+ .descrip = "Load config <count> number of times"
+ },
+ POPT_COMMON_DEBUG_ONLY
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_NONE,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(ENOMEM);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ poptSetOtherOptionHelp(pc, "[OPTION...] <config-file>");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ if (poptPeekArg(pc)) {
+ config_file = talloc_strdup(frame, poptGetArg(pc));
+ if (config_file == NULL) {
+ DBG_ERR("out of memory\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ } else {
+ config_file = get_dyn_CONFIGFILE();
+ }
+
+ poptFreeContext(pc);
+
+ if (count_str != NULL) {
+ count = atoi(count_str);
+ }
+
+ for (i=0; i < count; i++) {
+ printf("call lp_load() #%d: ", i+1);
+ if (!lp_load_with_registry_shares(config_file)) {
+ printf("ERROR.\n");
+ ret = 1;
+ goto done;
+ }
+ printf("ok.\n");
+ }
+
+
+done:
+ gfree_loadparm();
+ TALLOC_FREE(frame);
+ return ret;
+}
+
diff --git a/source3/param/util.c b/source3/param/util.c
new file mode 100644
index 0000000..94036f0
--- /dev/null
+++ b/source3/param/util.c
@@ -0,0 +1,50 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * param helper routines
+ * Copyright (C) Gerald Carter 2003
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+
+/*********************************************************
+ utility function to parse an integer parameter from
+ "parameter = value"
+**********************************************************/
+uint32_t get_int_param( const char* param )
+{
+ char *p;
+
+ p = strchr( param, '=' );
+ if ( !p )
+ return 0;
+
+ return atoi(p+1);
+}
+
+/*********************************************************
+ utility function to parse an integer parameter from
+ "parameter = value"
+**********************************************************/
+char* get_string_param( const char* param )
+{
+ char *p;
+
+ p = strchr( param, '=' );
+ if ( !p )
+ return NULL;
+
+ return (p+1);
+}
diff --git a/source3/param/wscript_build b/source3/param/wscript_build
new file mode 100644
index 0000000..9ae81a9
--- /dev/null
+++ b/source3/param/wscript_build
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_SUBSYSTEM('PARAM_UTIL',
+ source='util.c',
+ deps='talloc')
+
+bld.SAMBA3_SUBSYSTEM('LOADPARM_CTX',
+ source='loadparm_ctx.c',
+ deps='''talloc smbconf''')
+
+bld.SAMBA_GENERATOR('s3_param_proto_h',
+ source= '../../script/generate_param.py ../../docs-xml/smbdotconf/parameters.all.xml',
+ target='param_proto.h',
+ group='build_source',
+ rule='${PYTHON} ${SRC[0].abspath(env)} --file ${SRC[1].abspath(env)} --output ${TGT} --mode=S3PROTO')
+
+pytalloc_util = bld.pyembed_libname('pytalloc-util')
+pyparam_util = bld.pyembed_libname('pyparam3_util')
+libpython = bld.pyembed_libname('LIBPYTHON')
+
+bld.SAMBA3_PYTHON('pys3param',
+ source='pyparam.c',
+ deps='smbconf %s' % pyparam_util,
+ public_deps=' '.join(['samba-hostconfig', pytalloc_util, 'talloc']),
+ realname='samba/samba3/param.so')
+
+bld.SAMBA3_SUBSYSTEM(pyparam_util,
+ source='pyparam_util.c',
+ deps='%s samba-hostconfig %s' % (libpython, pytalloc_util),
+ pyext=True,
+ enabled=bld.PYTHON_BUILD_IS_ENABLED()
+ )
+
+bld.SAMBA3_SUBSYSTEM('param_service',
+ source='service.c',
+ deps = 'USER_UTIL smbconf')
+
+bld.SAMBA3_BINARY('test_lp_load',
+ source='test_lp_load.c',
+ deps='''
+ talloc
+ smbconf
+ CMDLINE_S3''',
+ install=False)
diff --git a/source3/passdb/ABI/pdb-0.1.0.sigs b/source3/passdb/ABI/pdb-0.1.0.sigs
new file mode 100644
index 0000000..f4de9c4
--- /dev/null
+++ b/source3/passdb/ABI/pdb-0.1.0.sigs
@@ -0,0 +1,311 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+my_sam_name: const char *(void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_gid_to_sid: bool (gid_t, struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_init: struct pdb_search *(TALLOC_CTX *, enum pdb_search_type)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_uid_to_sid: bool (uid_t, struct dom_sid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/pdb-0.1.1.sigs b/source3/passdb/ABI/pdb-0.1.1.sigs
new file mode 100644
index 0000000..99f9605
--- /dev/null
+++ b/source3/passdb/ABI/pdb-0.1.1.sigs
@@ -0,0 +1,312 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+my_sam_name: const char *(void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_gid_to_sid: bool (gid_t, struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_init: struct pdb_search *(TALLOC_CTX *, enum pdb_search_type)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_uid_to_sid: bool (uid_t, struct dom_sid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/pdb-0.1.2.sigs b/source3/passdb/ABI/pdb-0.1.2.sigs
new file mode 100644
index 0000000..8b97bac
--- /dev/null
+++ b/source3/passdb/ABI/pdb-0.1.2.sigs
@@ -0,0 +1,313 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+my_sam_name: const char *(void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_gid_to_sid: bool (gid_t, struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_init: struct pdb_search *(TALLOC_CTX *, enum pdb_search_type)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_uid_to_sid: bool (uid_t, struct dom_sid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/pdb-0.sigs b/source3/passdb/ABI/pdb-0.sigs
new file mode 100644
index 0000000..e6e3f73
--- /dev/null
+++ b/source3/passdb/ABI/pdb-0.sigs
@@ -0,0 +1,311 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+my_sam_name: const char *(void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_gid_to_sid: bool (gid_t, struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_init: struct pdb_search *(TALLOC_CTX *, enum pdb_search_type)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_uid_to_sid: bool (uid_t, struct dom_sid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.2.0.sigs b/source3/passdb/ABI/samba-passdb-0.2.0.sigs
new file mode 100644
index 0000000..e2246f6
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.2.0.sigs
@@ -0,0 +1,312 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+my_sam_name: const char *(void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_init: struct pdb_search *(TALLOC_CTX *, enum pdb_search_type)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.24.1.sigs b/source3/passdb/ABI/samba-passdb-0.24.1.sigs
new file mode 100644
index 0000000..e5885d0
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.24.1.sigs
@@ -0,0 +1,313 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+my_sam_name: const char *(void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_init: struct pdb_search *(TALLOC_CTX *, enum pdb_search_type)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.24.2.sigs b/source3/passdb/ABI/samba-passdb-0.24.2.sigs
new file mode 100644
index 0000000..6ab600e
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.24.2.sigs
@@ -0,0 +1,313 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+my_sam_name: const char *(void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_history: bool (struct samu *, const uint8_t *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.25.0.sigs b/source3/passdb/ABI/samba-passdb-0.25.0.sigs
new file mode 100644
index 0000000..546374c
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.25.0.sigs
@@ -0,0 +1,312 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_unix_group_name: bool (const char *, struct dom_sid *)
+lookup_unix_user_name: bool (const char *, struct dom_sid *)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_history: bool (struct samu *, const uint8_t *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.26.0.sigs b/source3/passdb/ABI/samba-passdb-0.26.0.sigs
new file mode 100644
index 0000000..f3762e5
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.26.0.sigs
@@ -0,0 +1,310 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_history: bool (struct samu *, const uint8_t *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_get_groups: bool (TALLOC_CTX *, const char *, uint32_t *, gid_t **)
+winbind_get_sid_aliases: bool (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.27.0.sigs b/source3/passdb/ABI/samba-passdb-0.27.0.sigs
new file mode 100644
index 0000000..1245ce5
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.27.0.sigs
@@ -0,0 +1,308 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_history: bool (struct samu *, const uint8_t *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.27.1.sigs b/source3/passdb/ABI/samba-passdb-0.27.1.sigs
new file mode 100644
index 0000000..6437ed2
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.27.1.sigs
@@ -0,0 +1,309 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_guests: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_history: bool (struct samu *, const uint8_t *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+unixid_from_both: void (struct unixid *, uint32_t)
+unixid_from_gid: void (struct unixid *, uint32_t)
+unixid_from_uid: void (struct unixid *, uint32_t)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_gid_to_sid: bool (struct dom_sid *, gid_t)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_uid_to_sid: bool (struct dom_sid *, uid_t)
diff --git a/source3/passdb/ABI/samba-passdb-0.27.2.sigs b/source3/passdb/ABI/samba-passdb-0.27.2.sigs
new file mode 100644
index 0000000..06fc3b7
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.27.2.sigs
@@ -0,0 +1,306 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_guests: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_history: bool (struct samu *, const uint8_t *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_xid_to_sid: bool (struct dom_sid *, const struct unixid *)
+xid_to_sid: void (struct dom_sid *, const struct unixid *)
diff --git a/source3/passdb/ABI/samba-passdb-0.28.0.sigs b/source3/passdb/ABI/samba-passdb-0.28.0.sigs
new file mode 100644
index 0000000..06fc3b7
--- /dev/null
+++ b/source3/passdb/ABI/samba-passdb-0.28.0.sigs
@@ -0,0 +1,306 @@
+PDB_secrets_clear_domain_protection: bool (const char *)
+PDB_secrets_fetch_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_fetch_domain_sid: bool (const char *, struct dom_sid *)
+PDB_secrets_mark_domain_protected: bool (const char *)
+PDB_secrets_store_domain_guid: bool (const char *, struct GUID *)
+PDB_secrets_store_domain_sid: bool (const char *, const struct dom_sid *)
+account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_default: bool (enum pdb_policy_type, uint32_t *)
+account_policy_get_desc: const char *(enum pdb_policy_type)
+account_policy_name_to_typenum: enum pdb_policy_type (const char *)
+account_policy_names_list: void (TALLOC_CTX *, const char ***, int *)
+account_policy_set: bool (enum pdb_policy_type, uint32_t)
+add_initial_entry: NTSTATUS (gid_t, const char *, enum lsa_SidType, const char *, const char *)
+algorithmic_pdb_gid_to_group_rid: uint32_t (gid_t)
+algorithmic_pdb_rid_is_user: bool (uint32_t)
+algorithmic_pdb_uid_to_user_rid: uint32_t (uid_t)
+algorithmic_pdb_user_rid_to_uid: uid_t (uint32_t)
+algorithmic_rid_base: int (void)
+builtin_domain_name: const char *(void)
+cache_account_policy_get: bool (enum pdb_policy_type, uint32_t *)
+cache_account_policy_set: bool (enum pdb_policy_type, uint32_t)
+create_builtin_administrators: NTSTATUS (const struct dom_sid *)
+create_builtin_guests: NTSTATUS (const struct dom_sid *)
+create_builtin_users: NTSTATUS (const struct dom_sid *)
+decode_account_policy_name: const char *(enum pdb_policy_type)
+get_account_pol_db: struct db_context *(void)
+get_account_policy_attr: const char *(enum pdb_policy_type)
+get_domain_group_from_sid: bool (struct dom_sid, GROUP_MAP *)
+get_primary_group_sid: NTSTATUS (TALLOC_CTX *, const char *, struct passwd **, struct dom_sid **)
+get_privileges_for_sid_as_set: NTSTATUS (TALLOC_CTX *, PRIVILEGE_SET **, struct dom_sid *)
+get_privileges_for_sids: bool (uint64_t *, struct dom_sid *, int)
+get_trust_pw_clear: bool (const char *, char **, const char **, enum netr_SchannelType *)
+get_trust_pw_hash: bool (const char *, uint8_t *, const char **, enum netr_SchannelType *)
+gid_to_sid: void (struct dom_sid *, gid_t)
+gid_to_unix_groups_sid: void (gid_t, struct dom_sid *)
+grab_named_mutex: struct named_mutex *(TALLOC_CTX *, const char *, int)
+grant_all_privileges: bool (const struct dom_sid *)
+grant_privilege_by_name: bool (const struct dom_sid *, const char *)
+grant_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+groupdb_tdb_init: const struct mapping_backend *(void)
+init_account_policy: bool (void)
+init_buffer_from_samu: uint32_t (uint8_t **, struct samu *, bool)
+init_samu_from_buffer: bool (struct samu *, uint32_t, uint8_t *, uint32_t)
+initialize_password_db: bool (bool, struct tevent_context *)
+is_dc_trusted_domain_situation: bool (const char *)
+is_privileged_sid: bool (const struct dom_sid *)
+local_password_change: NTSTATUS (const char *, int, const char *, char **, char **)
+login_cache_delentry: bool (const struct samu *)
+login_cache_init: bool (void)
+login_cache_read: bool (struct samu *, struct login_cache *)
+login_cache_shutdown: bool (void)
+login_cache_write: bool (const struct samu *, const struct login_cache *)
+lookup_builtin_name: bool (const char *, uint32_t *)
+lookup_builtin_rid: bool (TALLOC_CTX *, uint32_t, const char **)
+lookup_global_sam_name: bool (const char *, int, uint32_t *, enum lsa_SidType *)
+lookup_name: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_name_smbconf: bool (TALLOC_CTX *, const char *, int, const char **, const char **, struct dom_sid *, enum lsa_SidType *)
+lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+lookup_sids: NTSTATUS (TALLOC_CTX *, int, const struct dom_sid **, int, struct lsa_dom_info **, struct lsa_name_info **)
+lookup_wellknown_name: bool (TALLOC_CTX *, const char *, struct dom_sid *, const char **)
+lookup_wellknown_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **)
+make_pdb_method: NTSTATUS (struct pdb_methods **)
+make_pdb_method_name: NTSTATUS (struct pdb_methods **, const char *)
+max_algorithmic_gid: gid_t (void)
+max_algorithmic_uid: uid_t (void)
+pdb_add_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_add_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_add_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_add_sam_account: NTSTATUS (struct samu *)
+pdb_build_fields_present: uint32_t (struct samu *)
+pdb_capabilities: uint32_t (void)
+pdb_copy_sam_account: bool (struct samu *, struct samu *)
+pdb_create_alias: NTSTATUS (const char *, uint32_t *)
+pdb_create_builtin: NTSTATUS (uint32_t)
+pdb_create_builtin_alias: NTSTATUS (uint32_t, gid_t)
+pdb_create_dom_group: NTSTATUS (TALLOC_CTX *, const char *, uint32_t *)
+pdb_create_user: NTSTATUS (TALLOC_CTX *, const char *, uint32_t, uint32_t *)
+pdb_decode_acct_ctrl: uint32_t (const char *)
+pdb_default_add_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_default_alias_memberships: NTSTATUS (struct pdb_methods *, TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_default_create_alias: NTSTATUS (struct pdb_methods *, const char *, uint32_t *)
+pdb_default_del_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, const struct dom_sid *)
+pdb_default_delete_alias: NTSTATUS (struct pdb_methods *, const struct dom_sid *)
+pdb_default_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_default_enum_aliasmem: NTSTATUS (struct pdb_methods *, const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_default_enum_group_mapping: NTSTATUS (struct pdb_methods *, const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_default_get_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_default_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_default_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_default_set_aliasinfo: NTSTATUS (struct pdb_methods *, const struct dom_sid *, struct acct_info *)
+pdb_default_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_del_aliasmem: NTSTATUS (const struct dom_sid *, const struct dom_sid *)
+pdb_del_groupmem: NTSTATUS (TALLOC_CTX *, uint32_t, uint32_t)
+pdb_del_trusted_domain: NTSTATUS (const char *)
+pdb_del_trusteddom_pw: bool (const char *)
+pdb_delete_alias: NTSTATUS (const struct dom_sid *)
+pdb_delete_dom_group: NTSTATUS (TALLOC_CTX *, uint32_t)
+pdb_delete_group_mapping_entry: NTSTATUS (struct dom_sid)
+pdb_delete_sam_account: NTSTATUS (struct samu *)
+pdb_delete_secret: NTSTATUS (const char *)
+pdb_delete_user: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_element_is_changed: bool (const struct samu *, enum pdb_elements)
+pdb_element_is_set_or_changed: bool (const struct samu *, enum pdb_elements)
+pdb_encode_acct_ctrl: char *(uint32_t, size_t)
+pdb_enum_alias_memberships: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, const struct dom_sid *, size_t, uint32_t **, size_t *)
+pdb_enum_aliasmem: NTSTATUS (const struct dom_sid *, TALLOC_CTX *, struct dom_sid **, size_t *)
+pdb_enum_group_mapping: bool (const struct dom_sid *, enum lsa_SidType, GROUP_MAP ***, size_t *, bool)
+pdb_enum_group_members: NTSTATUS (TALLOC_CTX *, const struct dom_sid *, uint32_t **, size_t *)
+pdb_enum_group_memberships: NTSTATUS (TALLOC_CTX *, struct samu *, struct dom_sid **, gid_t **, uint32_t *)
+pdb_enum_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct pdb_trusted_domain ***)
+pdb_enum_trusteddoms: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+pdb_enum_upn_suffixes: NTSTATUS (TALLOC_CTX *, uint32_t *, char ***)
+pdb_find_backend_entry: struct pdb_init_function_entry *(const char *)
+pdb_get_account_policy: bool (enum pdb_policy_type, uint32_t *)
+pdb_get_acct_ctrl: uint32_t (const struct samu *)
+pdb_get_acct_desc: const char *(const struct samu *)
+pdb_get_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_get_backend_private_data: void *(const struct samu *, const struct pdb_methods *)
+pdb_get_backends: const struct pdb_init_function_entry *(void)
+pdb_get_bad_password_count: uint16_t (const struct samu *)
+pdb_get_bad_password_time: time_t (const struct samu *)
+pdb_get_code_page: uint16_t (const struct samu *)
+pdb_get_comment: const char *(const struct samu *)
+pdb_get_country_code: uint16_t (const struct samu *)
+pdb_get_dir_drive: const char *(const struct samu *)
+pdb_get_domain: const char *(const struct samu *)
+pdb_get_domain_info: struct pdb_domain_info *(TALLOC_CTX *)
+pdb_get_fullname: const char *(const struct samu *)
+pdb_get_group_rid: uint32_t (struct samu *)
+pdb_get_group_sid: const struct dom_sid *(struct samu *)
+pdb_get_homedir: const char *(const struct samu *)
+pdb_get_hours: const uint8_t *(const struct samu *)
+pdb_get_hours_len: uint32_t (const struct samu *)
+pdb_get_init_flags: enum pdb_value_state (const struct samu *, enum pdb_elements)
+pdb_get_kickoff_time: time_t (const struct samu *)
+pdb_get_lanman_passwd: const uint8_t *(const struct samu *)
+pdb_get_logoff_time: time_t (const struct samu *)
+pdb_get_logon_count: uint16_t (const struct samu *)
+pdb_get_logon_divs: uint16_t (const struct samu *)
+pdb_get_logon_script: const char *(const struct samu *)
+pdb_get_logon_time: time_t (const struct samu *)
+pdb_get_munged_dial: const char *(const struct samu *)
+pdb_get_nt_passwd: const uint8_t *(const struct samu *)
+pdb_get_nt_username: const char *(const struct samu *)
+pdb_get_pass_can_change: bool (const struct samu *)
+pdb_get_pass_can_change_time: time_t (const struct samu *)
+pdb_get_pass_can_change_time_noncalc: time_t (const struct samu *)
+pdb_get_pass_last_set_time: time_t (const struct samu *)
+pdb_get_pass_must_change_time: time_t (const struct samu *)
+pdb_get_plaintext_passwd: const char *(const struct samu *)
+pdb_get_profile_path: const char *(const struct samu *)
+pdb_get_pw_history: const uint8_t *(const struct samu *, uint32_t *)
+pdb_get_secret: NTSTATUS (TALLOC_CTX *, const char *, DATA_BLOB *, NTTIME *, DATA_BLOB *, NTTIME *, struct security_descriptor **)
+pdb_get_seq_num: bool (time_t *)
+pdb_get_tevent_context: struct tevent_context *(void)
+pdb_get_trust_credentials: NTSTATUS (const char *, const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusted_domain: NTSTATUS (TALLOC_CTX *, const char *, struct pdb_trusted_domain **)
+pdb_get_trusted_domain_by_sid: NTSTATUS (TALLOC_CTX *, struct dom_sid *, struct pdb_trusted_domain **)
+pdb_get_trusteddom_creds: NTSTATUS (const char *, TALLOC_CTX *, struct cli_credentials **)
+pdb_get_trusteddom_pw: bool (const char *, char **, struct dom_sid *, time_t *)
+pdb_get_unknown_6: uint32_t (const struct samu *)
+pdb_get_user_rid: uint32_t (const struct samu *)
+pdb_get_user_sid: const struct dom_sid *(const struct samu *)
+pdb_get_username: const char *(const struct samu *)
+pdb_get_workstations: const char *(const struct samu *)
+pdb_getgrgid: bool (GROUP_MAP *, gid_t)
+pdb_getgrnam: bool (GROUP_MAP *, const char *)
+pdb_getgrsid: bool (GROUP_MAP *, struct dom_sid)
+pdb_gethexhours: bool (const char *, unsigned char *)
+pdb_gethexpwd: bool (const char *, unsigned char *)
+pdb_getsampwnam: bool (struct samu *, const char *)
+pdb_getsampwsid: bool (struct samu *, const struct dom_sid *)
+pdb_group_rid_to_gid: gid_t (uint32_t)
+pdb_id_to_sid: bool (struct unixid *, struct dom_sid *)
+pdb_increment_bad_password_count: bool (struct samu *)
+pdb_is_password_change_time_max: bool (time_t)
+pdb_is_responsible_for_builtin: bool (void)
+pdb_is_responsible_for_everything_else: bool (void)
+pdb_is_responsible_for_our_sam: bool (void)
+pdb_is_responsible_for_unix_groups: bool (void)
+pdb_is_responsible_for_unix_users: bool (void)
+pdb_is_responsible_for_wellknown: bool (void)
+pdb_lookup_rids: NTSTATUS (const struct dom_sid *, int, uint32_t *, const char **, enum lsa_SidType *)
+pdb_new_rid: bool (uint32_t *)
+pdb_nop_add_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_nop_delete_group_mapping_entry: NTSTATUS (struct pdb_methods *, struct dom_sid)
+pdb_nop_enum_group_mapping: NTSTATUS (struct pdb_methods *, enum lsa_SidType, GROUP_MAP **, size_t *, bool)
+pdb_nop_getgrgid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, gid_t)
+pdb_nop_getgrnam: NTSTATUS (struct pdb_methods *, GROUP_MAP *, const char *)
+pdb_nop_getgrsid: NTSTATUS (struct pdb_methods *, GROUP_MAP *, struct dom_sid)
+pdb_nop_update_group_mapping_entry: NTSTATUS (struct pdb_methods *, GROUP_MAP *)
+pdb_rename_sam_account: NTSTATUS (struct samu *, const char *)
+pdb_search_aliases: struct pdb_search *(TALLOC_CTX *, const struct dom_sid *)
+pdb_search_entries: uint32_t (struct pdb_search *, uint32_t, uint32_t, struct samr_displayentry **)
+pdb_search_groups: struct pdb_search *(TALLOC_CTX *)
+pdb_search_users: struct pdb_search *(TALLOC_CTX *, uint32_t)
+pdb_set_account_policy: bool (enum pdb_policy_type, uint32_t)
+pdb_set_acct_ctrl: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_acct_desc: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_aliasinfo: NTSTATUS (const struct dom_sid *, struct acct_info *)
+pdb_set_backend_private_data: bool (struct samu *, void *, void (*)(void **), const struct pdb_methods *, enum pdb_value_state)
+pdb_set_bad_password_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_bad_password_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_code_page: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_comment: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_country_code: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_dir_drive: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_domain: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_fullname: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_group_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_group_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_homedir: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_hours: bool (struct samu *, const uint8_t *, int, enum pdb_value_state)
+pdb_set_hours_len: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_init_flags: bool (struct samu *, enum pdb_elements, enum pdb_value_state)
+pdb_set_kickoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_lanman_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_logoff_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_logon_count: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_divs: bool (struct samu *, uint16_t, enum pdb_value_state)
+pdb_set_logon_script: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_logon_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_munged_dial: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_nt_passwd: bool (struct samu *, const uint8_t *, enum pdb_value_state)
+pdb_set_nt_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pass_can_change: bool (struct samu *, bool)
+pdb_set_pass_can_change_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_pass_last_set_time: bool (struct samu *, time_t, enum pdb_value_state)
+pdb_set_plaintext_passwd: bool (struct samu *, const char *)
+pdb_set_plaintext_pw_only: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_profile_path: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_pw_history: bool (struct samu *, const uint8_t *, uint32_t, enum pdb_value_state)
+pdb_set_secret: NTSTATUS (const char *, DATA_BLOB *, DATA_BLOB *, struct security_descriptor *)
+pdb_set_trusted_domain: NTSTATUS (const char *, const struct pdb_trusted_domain *)
+pdb_set_trusteddom_pw: bool (const char *, const char *, const struct dom_sid *)
+pdb_set_unix_primary_group: NTSTATUS (TALLOC_CTX *, struct samu *)
+pdb_set_unknown_6: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_upn_suffixes: NTSTATUS (uint32_t, const char **)
+pdb_set_user_sid: bool (struct samu *, const struct dom_sid *, enum pdb_value_state)
+pdb_set_user_sid_from_rid: bool (struct samu *, uint32_t, enum pdb_value_state)
+pdb_set_user_sid_from_string: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_username: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_set_workstations: bool (struct samu *, const char *, enum pdb_value_state)
+pdb_sethexhours: void (char *, const unsigned char *)
+pdb_sethexpwd: void (char *, const unsigned char *, uint32_t)
+pdb_sid_to_id: bool (const struct dom_sid *, struct unixid *)
+pdb_sid_to_id_unix_users_and_groups: bool (const struct dom_sid *, struct unixid *)
+pdb_update_autolock_flag: bool (struct samu *, bool *)
+pdb_update_bad_password_count: bool (struct samu *, bool *)
+pdb_update_group_mapping_entry: NTSTATUS (GROUP_MAP *)
+pdb_update_history: bool (struct samu *, const uint8_t *)
+pdb_update_login_attempts: NTSTATUS (struct samu *, bool)
+pdb_update_sam_account: NTSTATUS (struct samu *)
+privilege_create_account: NTSTATUS (const struct dom_sid *)
+privilege_delete_account: NTSTATUS (const struct dom_sid *)
+privilege_enum_sids: NTSTATUS (enum sec_privilege, TALLOC_CTX *, struct dom_sid **, int *)
+privilege_enumerate_accounts: NTSTATUS (struct dom_sid **, int *)
+revoke_all_privileges: bool (const struct dom_sid *)
+revoke_privilege_by_name: bool (const struct dom_sid *, const char *)
+revoke_privilege_set: bool (const struct dom_sid *, struct lsa_PrivilegeSet *)
+samu_alloc_rid_unix: NTSTATUS (struct pdb_methods *, struct samu *, const struct passwd *)
+samu_new: struct samu *(TALLOC_CTX *)
+samu_set_unix: NTSTATUS (struct samu *, const struct passwd *)
+secrets_trusted_domains: NTSTATUS (TALLOC_CTX *, uint32_t *, struct trustdom_info ***)
+sid_check_is_builtin: bool (const struct dom_sid *)
+sid_check_is_for_passdb: bool (const struct dom_sid *)
+sid_check_is_in_builtin: bool (const struct dom_sid *)
+sid_check_is_in_unix_groups: bool (const struct dom_sid *)
+sid_check_is_in_unix_users: bool (const struct dom_sid *)
+sid_check_is_in_wellknown_domain: bool (const struct dom_sid *)
+sid_check_is_unix_groups: bool (const struct dom_sid *)
+sid_check_is_unix_users: bool (const struct dom_sid *)
+sid_check_is_wellknown_builtin: bool (const struct dom_sid *)
+sid_check_is_wellknown_domain: bool (const struct dom_sid *, const char **)
+sid_check_object_is_for_passdb: bool (const struct dom_sid *)
+sid_to_gid: bool (const struct dom_sid *, gid_t *)
+sid_to_uid: bool (const struct dom_sid *, uid_t *)
+sids_to_unixids: bool (const struct dom_sid *, uint32_t, struct unixid *)
+smb_add_user_group: int (const char *, const char *)
+smb_create_group: int (const char *, gid_t *)
+smb_delete_group: int (const char *)
+smb_delete_user_group: int (const char *, const char *)
+smb_nscd_flush_group_cache: void (void)
+smb_nscd_flush_user_cache: void (void)
+smb_register_passdb: NTSTATUS (int, const char *, pdb_init_function)
+smb_set_primary_group: int (const char *, const char *)
+uid_to_sid: void (struct dom_sid *, uid_t)
+uid_to_unix_users_sid: void (uid_t, struct dom_sid *)
+unix_groups_domain_name: const char *(void)
+unix_users_domain_name: const char *(void)
+wb_is_trusted_domain: wbcErr (const char *)
+winbind_allocate_gid: bool (gid_t *)
+winbind_allocate_uid: bool (uid_t *)
+winbind_getpwnam: struct passwd *(const char *)
+winbind_getpwsid: struct passwd *(const struct dom_sid *)
+winbind_lookup_name: bool (const char *, const char *, struct dom_sid *, enum lsa_SidType *)
+winbind_lookup_rids: bool (TALLOC_CTX *, const struct dom_sid *, int, uint32_t *, const char **, const char ***, enum lsa_SidType **)
+winbind_lookup_sid: bool (TALLOC_CTX *, const struct dom_sid *, const char **, const char **, enum lsa_SidType *)
+winbind_lookup_usersids: bool (TALLOC_CTX *, const struct dom_sid *, uint32_t *, struct dom_sid **)
+winbind_ping: bool (void)
+winbind_sid_to_gid: bool (gid_t *, const struct dom_sid *)
+winbind_sid_to_uid: bool (uid_t *, const struct dom_sid *)
+winbind_xid_to_sid: bool (struct dom_sid *, const struct unixid *)
+xid_to_sid: void (struct dom_sid *, const struct unixid *)
diff --git a/source3/passdb/account_pol.c b/source3/passdb/account_pol.c
new file mode 100644
index 0000000..34c0d72
--- /dev/null
+++ b/source3/passdb/account_pol.c
@@ -0,0 +1,494 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * account policy storage
+ * Copyright (C) Jean François Micouleau 1998-2001
+ * Copyright (C) Andrew Bartlett 2002
+ * Copyright (C) Guenther Deschner 2004-2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "passdb.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "lib/privileges.h"
+#include "lib/gencache.h"
+#include "lib/util/smb_strtox.h"
+
+static struct db_context *db;
+
+/* cache all entries for 60 seconds for to save ldap-queries (cache is updated
+ * after this period if admins do not use pdbedit or usermanager but manipulate
+ * ldap directly) - gd */
+
+#define DATABASE_VERSION 3
+#define AP_TTL 60
+
+
+struct ap_table {
+ enum pdb_policy_type type;
+ const char *string;
+ uint32_t default_val;
+ const char *description;
+ const char *ldap_attr;
+};
+
+static const struct ap_table account_policy_names[] = {
+ {PDB_POLICY_MIN_PASSWORD_LEN, "min password length", MINPASSWDLENGTH,
+ "Minimal password length (default: 5)",
+ "sambaMinPwdLength" },
+
+ {PDB_POLICY_PASSWORD_HISTORY, "password history", 0,
+ "Length of Password History Entries (default: 0 => off)",
+ "sambaPwdHistoryLength" },
+
+ {PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS, "user must logon to change password", 0,
+ "Force Users to logon for password change (default: 0 => off, 2 => on)",
+ "sambaLogonToChgPwd" },
+
+ {PDB_POLICY_MAX_PASSWORD_AGE, "maximum password age", (uint32_t) -1,
+ "Maximum password age, in seconds (default: -1 => never expire passwords)",
+ "sambaMaxPwdAge" },
+
+ {PDB_POLICY_MIN_PASSWORD_AGE,"minimum password age", 0,
+ "Minimal password age, in seconds (default: 0 => allow immediate password change)",
+ "sambaMinPwdAge" },
+
+ {PDB_POLICY_LOCK_ACCOUNT_DURATION, "lockout duration", 30,
+ "Lockout duration in minutes (default: 30, -1 => forever)",
+ "sambaLockoutDuration" },
+
+ {PDB_POLICY_RESET_COUNT_TIME, "reset count minutes", 30,
+ "Reset time after lockout in minutes (default: 30)",
+ "sambaLockoutObservationWindow" },
+
+ {PDB_POLICY_BAD_ATTEMPT_LOCKOUT, "bad lockout attempt", 0,
+ "Lockout users after bad logon attempts (default: 0 => off)",
+ "sambaLockoutThreshold" },
+
+ {PDB_POLICY_TIME_TO_LOGOUT, "disconnect time", (uint32_t) -1,
+ "Disconnect Users outside logon hours (default: -1 => off, 0 => on)",
+ "sambaForceLogoff" },
+
+ {PDB_POLICY_REFUSE_MACHINE_PW_CHANGE, "refuse machine password change", 0,
+ "Allow Machine Password changes (default: 0 => off)",
+ "sambaRefuseMachinePwdChange" },
+
+ {0, NULL, 0, "", NULL}
+};
+
+void account_policy_names_list(TALLOC_CTX *mem_ctx, const char ***names, int *num_names)
+{
+ const char **nl;
+ int i, count = ARRAY_SIZE(account_policy_names);
+
+ nl = talloc_array(mem_ctx, const char *, count);
+ if (!nl) {
+ *num_names = 0;
+ return;
+ }
+ for (i=0; i<count; i++) {
+ nl[i] = account_policy_names[i].string;
+ }
+ /* Do not return the last null entry */
+ *num_names = count-1;
+ *names = nl;
+ return;
+}
+
+/****************************************************************************
+Get the account policy name as a string from its #define'ed number
+****************************************************************************/
+
+const char *decode_account_policy_name(enum pdb_policy_type type)
+{
+ int i;
+ for (i=0; account_policy_names[i].string; i++) {
+ if (type == account_policy_names[i].type) {
+ return account_policy_names[i].string;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+Get the account policy LDAP attribute as a string from its #define'ed number
+****************************************************************************/
+
+const char *get_account_policy_attr(enum pdb_policy_type type)
+{
+ int i;
+ for (i=0; account_policy_names[i].type; i++) {
+ if (type == account_policy_names[i].type) {
+ return account_policy_names[i].ldap_attr;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+Get the account policy description as a string from its #define'ed number
+****************************************************************************/
+
+const char *account_policy_get_desc(enum pdb_policy_type type)
+{
+ int i;
+ for (i=0; account_policy_names[i].string; i++) {
+ if (type == account_policy_names[i].type) {
+ return account_policy_names[i].description;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+Get the account policy name as a string from its #define'ed number
+****************************************************************************/
+
+enum pdb_policy_type account_policy_name_to_typenum(const char *name)
+{
+ int i;
+ for (i=0; account_policy_names[i].string; i++) {
+ if (strcmp(name, account_policy_names[i].string) == 0) {
+ return account_policy_names[i].type;
+ }
+ }
+ return 0;
+}
+
+/*****************************************************************************
+Get default value for account policy
+*****************************************************************************/
+
+bool account_policy_get_default(enum pdb_policy_type type, uint32_t *val)
+{
+ int i;
+ for (i=0; account_policy_names[i].type; i++) {
+ if (account_policy_names[i].type == type) {
+ *val = account_policy_names[i].default_val;
+ return True;
+ }
+ }
+ DEBUG(0,("no default for account_policy index %d found. This should never happen\n",
+ type));
+ return False;
+}
+
+/*****************************************************************************
+ Set default for a type if it is empty
+*****************************************************************************/
+
+static bool account_policy_set_default_on_empty(enum pdb_policy_type type)
+{
+
+ uint32_t value;
+
+ if (!account_policy_get(type, &value) &&
+ !account_policy_get_default(type, &value)) {
+ return False;
+ }
+
+ return account_policy_set(type, value);
+}
+
+/*****************************************************************************
+ Open the account policy tdb.
+***`*************************************************************************/
+
+bool init_account_policy(void)
+{
+
+ const char *vstring = "INFO/version";
+ uint32_t version = 0;
+ int i;
+ NTSTATUS status;
+ char *db_path;
+
+ if (db != NULL) {
+ return True;
+ }
+
+ db_path = state_path(talloc_tos(), "account_policy.tdb");
+ if (db_path == NULL) {
+ return false;
+ }
+
+ db = db_open(NULL, db_path, 0, TDB_DEFAULT,
+ O_RDWR, 0600, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+
+ if (db == NULL) { /* the account policies files does not exist or open
+ * failed, try to create a new one */
+ db = db_open(NULL, db_path, 0,
+ TDB_DEFAULT, O_RDWR|O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ DEBUG(0,("Failed to open account policy database\n"));
+ TALLOC_FREE(db_path);
+ return False;
+ }
+ }
+ TALLOC_FREE(db_path);
+
+ status = dbwrap_fetch_uint32_bystring(db, vstring, &version);
+ if (!NT_STATUS_IS_OK(status)) {
+ version = 0;
+ }
+
+ if (version == DATABASE_VERSION) {
+ return true;
+ }
+
+ /* handle a Samba upgrade */
+
+ if (dbwrap_transaction_start(db) != 0) {
+ DEBUG(0, ("transaction_start failed\n"));
+ TALLOC_FREE(db);
+ return false;
+ }
+
+ status = dbwrap_fetch_uint32_bystring(db, vstring, &version);
+ if (!NT_STATUS_IS_OK(status)) {
+ version = 0;
+ }
+
+ if (version == DATABASE_VERSION) {
+ /*
+ * Race condition
+ */
+ if (dbwrap_transaction_cancel(db)) {
+ smb_panic("transaction_cancel failed");
+ }
+ return true;
+ }
+
+ if (version != DATABASE_VERSION) {
+ status = dbwrap_store_uint32_bystring(db, vstring,
+ DATABASE_VERSION);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dbwrap_store_uint32_t failed: %s\n",
+ nt_errstr(status)));
+ goto cancel;
+ }
+
+ for (i=0; account_policy_names[i].type; i++) {
+
+ if (!account_policy_set_default_on_empty(account_policy_names[i].type)) {
+ DEBUG(0,("failed to set default value in account policy tdb\n"));
+ goto cancel;
+ }
+ }
+ }
+
+ /* These exist by default on NT4 in [HKLM\SECURITY\Policy\Accounts] */
+
+ privilege_create_account( &global_sid_World );
+ privilege_create_account( &global_sid_Builtin_Account_Operators );
+ privilege_create_account( &global_sid_Builtin_Server_Operators );
+ privilege_create_account( &global_sid_Builtin_Print_Operators );
+ privilege_create_account( &global_sid_Builtin_Backup_Operators );
+
+ /* BUILTIN\Administrators get everything -- *always* */
+
+ if ( lp_enable_privileges() ) {
+ if ( !grant_all_privileges( &global_sid_Builtin_Administrators ) ) {
+ DEBUG(1,("init_account_policy: Failed to grant privileges "
+ "to BUILTIN\\Administrators!\n"));
+ }
+ }
+
+ if (dbwrap_transaction_commit(db) != 0) {
+ DEBUG(0, ("transaction_commit failed\n"));
+ TALLOC_FREE(db);
+ return false;
+ }
+
+ return True;
+
+ cancel:
+ if (dbwrap_transaction_cancel(db)) {
+ smb_panic("transaction_cancel failed");
+ }
+ TALLOC_FREE(db);
+
+ return false;
+}
+
+/*****************************************************************************
+Get an account policy (from tdb)
+*****************************************************************************/
+
+bool account_policy_get(enum pdb_policy_type type, uint32_t *value)
+{
+ const char *name;
+ uint32_t regval;
+ NTSTATUS status;
+
+ if (!init_account_policy()) {
+ return False;
+ }
+
+ if (value) {
+ *value = 0;
+ }
+
+ name = decode_account_policy_name(type);
+ if (name == NULL) {
+ DEBUG(1, ("account_policy_get: Field %d is not a valid account policy type! Cannot get, returning 0.\n", type));
+ return False;
+ }
+
+ status = dbwrap_fetch_uint32_bystring(db, name, &regval);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("account_policy_get: tdb_fetch_uint32_t failed for type %d (%s), returning 0\n", type, name));
+ return False;
+ }
+
+ if (value) {
+ *value = regval;
+ }
+
+ DEBUG(10,("account_policy_get: name: %s, val: %d\n", name, regval));
+ return True;
+}
+
+
+/****************************************************************************
+Set an account policy (in tdb)
+****************************************************************************/
+
+bool account_policy_set(enum pdb_policy_type type, uint32_t value)
+{
+ const char *name;
+ NTSTATUS status;
+
+ if (!init_account_policy()) {
+ return False;
+ }
+
+ name = decode_account_policy_name(type);
+ if (name == NULL) {
+ DEBUG(1, ("Field %d is not a valid account policy type! Cannot set.\n", type));
+ return False;
+ }
+
+ status = dbwrap_trans_store_uint32_bystring(db, name, value);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("store_uint32_t failed for type %d (%s) on value "
+ "%u: %s\n", type, name, value, nt_errstr(status)));
+ return False;
+ }
+
+ DEBUG(10,("account_policy_set: name: %s, value: %d\n", name, value));
+
+ return True;
+}
+
+/****************************************************************************
+Set an account policy in the cache
+****************************************************************************/
+
+bool cache_account_policy_set(enum pdb_policy_type type, uint32_t value)
+{
+ const char *policy_name = NULL;
+ char *cache_key = NULL;
+ char *cache_value = NULL;
+ bool ret = False;
+
+ policy_name = decode_account_policy_name(type);
+ if (policy_name == NULL) {
+ DEBUG(0,("cache_account_policy_set: no policy found\n"));
+ return False;
+ }
+
+ if (asprintf(&cache_key, "ACCT_POL/%s", policy_name) < 0) {
+ DEBUG(0, ("asprintf failed\n"));
+ goto done;
+ }
+
+ if (asprintf(&cache_value, "%lu\n", (unsigned long)value) < 0) {
+ DEBUG(0, ("asprintf failed\n"));
+ goto done;
+ }
+
+ DEBUG(10,("cache_account_policy_set: updating account pol cache\n"));
+
+ ret = gencache_set(cache_key, cache_value, time(NULL)+AP_TTL);
+
+ done:
+ SAFE_FREE(cache_key);
+ SAFE_FREE(cache_value);
+ return ret;
+}
+
+/*****************************************************************************
+Get an account policy from the cache
+*****************************************************************************/
+
+bool cache_account_policy_get(enum pdb_policy_type type, uint32_t *value)
+{
+ const char *policy_name = NULL;
+ char *cache_key = NULL;
+ char *cache_value = NULL;
+ bool ret = False;
+
+ policy_name = decode_account_policy_name(type);
+ if (policy_name == NULL) {
+ DEBUG(0,("cache_account_policy_set: no policy found\n"));
+ return False;
+ }
+
+ if (asprintf(&cache_key, "ACCT_POL/%s", policy_name) < 0) {
+ DEBUG(0, ("asprintf failed\n"));
+ goto done;
+ }
+
+ if (gencache_get(cache_key, talloc_tos(), &cache_value, NULL)) {
+ int error = 0;
+ uint32_t tmp;
+
+ tmp = smb_strtoul(cache_value,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+ *value = tmp;
+ ret = True;
+ }
+
+ done:
+ SAFE_FREE(cache_key);
+ TALLOC_FREE(cache_value);
+ return ret;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+struct db_context *get_account_pol_db( void )
+{
+
+ if ( db == NULL ) {
+ if ( !init_account_policy() ) {
+ return NULL;
+ }
+ }
+
+ return db;
+}
diff --git a/source3/passdb/login_cache.c b/source3/passdb/login_cache.c
new file mode 100644
index 0000000..6b636b3
--- /dev/null
+++ b/source3/passdb/login_cache.c
@@ -0,0 +1,202 @@
+/*
+ Unix SMB/CIFS implementation.
+ struct samu local cache for
+ Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2004.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "passdb.h"
+#include "util_tdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+#define LOGIN_CACHE_FILE "login_cache.tdb"
+
+#define SAM_CACHE_FORMAT "dwwd"
+
+static TDB_CONTEXT *cache;
+
+bool login_cache_init(void)
+{
+ char* cache_fname = NULL;
+
+ /* skip file open if it's already opened */
+ if (cache) return True;
+
+ cache_fname = cache_path(talloc_tos(), LOGIN_CACHE_FILE);
+ if (cache_fname == NULL) {
+ DEBUG(0, ("Filename allocation failed.\n"));
+ return False;
+ }
+
+ DEBUG(5, ("Opening cache file at %s\n", cache_fname));
+
+ cache = tdb_open_log(cache_fname, 0, TDB_DEFAULT,
+ O_RDWR|O_CREAT, 0644);
+
+ if (!cache)
+ DEBUG(5, ("Attempt to open %s failed.\n", cache_fname));
+
+ TALLOC_FREE(cache_fname);
+
+ return (cache ? True : False);
+}
+
+bool login_cache_shutdown(void)
+{
+ /* tdb_close routine returns non-zero on error */
+ if (!cache) return False;
+ DEBUG(5, ("Closing cache file\n"));
+ return tdb_close(cache) == 0;
+}
+
+/* if we can't read the cache, oh well, no need to return anything */
+bool login_cache_read(struct samu *sampass, struct login_cache *entry)
+{
+ char *keystr;
+ TDB_DATA databuf;
+ uint32_t entry_timestamp = 0, bad_password_time = 0;
+ uint16_t acct_ctrl;
+
+ if (!login_cache_init()) {
+ return false;
+ }
+
+ if (pdb_get_nt_username(sampass) == NULL) {
+ return false;
+ }
+
+ keystr = SMB_STRDUP(pdb_get_nt_username(sampass));
+ if (!keystr || !keystr[0]) {
+ SAFE_FREE(keystr);
+ return false;
+ }
+
+ DEBUG(7, ("Looking up login cache for user %s\n",
+ keystr));
+ databuf = tdb_fetch_bystring(cache, keystr);
+ SAFE_FREE(keystr);
+
+ ZERO_STRUCTP(entry);
+
+ if (tdb_unpack (databuf.dptr, databuf.dsize, SAM_CACHE_FORMAT,
+ &entry_timestamp,
+ &acct_ctrl,
+ &entry->bad_password_count,
+ &bad_password_time) == -1) {
+ DEBUG(7, ("No cache entry found\n"));
+ SAFE_FREE(databuf.dptr);
+ return false;
+ }
+
+ /*
+ * Deal with 32-bit acct_ctrl. In the tdb we only store 16-bit
+ * ("w" in SAM_CACHE_FORMAT). Fixes bug 7253.
+ */
+ entry->acct_ctrl = acct_ctrl;
+
+ /* Deal with possible 64-bit time_t. */
+ entry->entry_timestamp = (time_t)entry_timestamp;
+ entry->bad_password_time = (time_t)bad_password_time;
+
+ SAFE_FREE(databuf.dptr);
+
+ DEBUG(5, ("Found login cache entry: timestamp %12u, flags 0x%x, count %d, time %12u\n",
+ (unsigned int)entry->entry_timestamp, entry->acct_ctrl,
+ entry->bad_password_count, (unsigned int)entry->bad_password_time));
+ return true;
+}
+
+bool login_cache_write(const struct samu *sampass,
+ const struct login_cache *entry)
+{
+ char *keystr;
+ TDB_DATA databuf;
+ bool ret;
+ uint32_t entry_timestamp;
+ uint32_t bad_password_time = entry->bad_password_time;
+
+ if (!login_cache_init())
+ return False;
+
+ if (pdb_get_nt_username(sampass) == NULL) {
+ return False;
+ }
+
+ keystr = SMB_STRDUP(pdb_get_nt_username(sampass));
+ if (!keystr || !keystr[0]) {
+ SAFE_FREE(keystr);
+ return False;
+ }
+
+ entry_timestamp = (uint32_t)time(NULL);
+
+ databuf.dsize =
+ tdb_pack(NULL, 0, SAM_CACHE_FORMAT,
+ entry_timestamp,
+ entry->acct_ctrl,
+ entry->bad_password_count,
+ bad_password_time);
+ databuf.dptr = SMB_MALLOC_ARRAY(uint8_t, databuf.dsize);
+ if (!databuf.dptr) {
+ SAFE_FREE(keystr);
+ return False;
+ }
+
+ if (tdb_pack(databuf.dptr, databuf.dsize, SAM_CACHE_FORMAT,
+ entry_timestamp,
+ entry->acct_ctrl,
+ entry->bad_password_count,
+ bad_password_time)
+ != databuf.dsize) {
+ SAFE_FREE(keystr);
+ SAFE_FREE(databuf.dptr);
+ return False;
+ }
+
+ ret = tdb_store_bystring(cache, keystr, databuf, 0);
+ SAFE_FREE(keystr);
+ SAFE_FREE(databuf.dptr);
+ return ret == 0;
+}
+
+bool login_cache_delentry(const struct samu *sampass)
+{
+ int ret;
+ char *keystr;
+
+ if (!login_cache_init())
+ return False;
+
+ if (pdb_get_nt_username(sampass) == NULL) {
+ return False;
+ }
+
+ keystr = SMB_STRDUP(pdb_get_nt_username(sampass));
+ if (!keystr || !keystr[0]) {
+ SAFE_FREE(keystr);
+ return False;
+ }
+
+ DEBUG(9, ("About to delete entry for %s\n", keystr));
+ ret = tdb_delete_bystring(cache, keystr);
+ DEBUG(9, ("tdb_delete returned %d\n", ret));
+
+ SAFE_FREE(keystr);
+ return ret == 0;
+}
diff --git a/source3/passdb/lookup_sid.c b/source3/passdb/lookup_sid.c
new file mode 100644
index 0000000..426ea3f
--- /dev/null
+++ b/source3/passdb/lookup_sid.c
@@ -0,0 +1,1731 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Gerald (Jerry) Carter 2003
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "passdb.h"
+#include "lib/util_unixsids.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "secrets.h"
+#include "../lib/util/memcache.h"
+#include "idmap_cache.h"
+#include "../libcli/security/security.h"
+#include "lib/winbind_util.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "lib/util/bitmap.h"
+
+static bool lookup_unix_user_name(const char *name, struct dom_sid *sid)
+{
+ struct passwd *pwd;
+ bool ret;
+
+ pwd = Get_Pwnam_alloc(talloc_tos(), name);
+ if (pwd == NULL) {
+ return False;
+ }
+
+ /*
+ * For 64-bit uid's we have enough space in the whole SID,
+ * should they become necessary
+ */
+ ret = sid_compose(sid, &global_sid_Unix_Users, pwd->pw_uid);
+ TALLOC_FREE(pwd);
+ return ret;
+}
+
+static bool lookup_unix_group_name(const char *name, struct dom_sid *sid)
+{
+ struct group *grp;
+
+ grp = getgrnam(name);
+ if (grp == NULL) {
+ return False;
+ }
+
+ /*
+ * For 64-bit gid's we have enough space in the whole SID,
+ * should they become necessary
+ */
+ return sid_compose(sid, &global_sid_Unix_Groups, grp->gr_gid);
+}
+
+/*****************************************************************
+ Dissect a user-provided name into domain, name, sid and type.
+
+ If an explicit domain name was given in the form domain\user, it
+ has to try that. If no explicit domain name was given, we have
+ to do guesswork.
+*****************************************************************/
+
+bool lookup_name(TALLOC_CTX *mem_ctx,
+ const char *full_name, int flags,
+ const char **ret_domain, const char **ret_name,
+ struct dom_sid *ret_sid, enum lsa_SidType *ret_type)
+{
+ char *p;
+ const char *tmp;
+ const char *domain = NULL;
+ const char *name = NULL;
+ uint32_t rid;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+
+ if (tmp_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return false;
+ }
+
+ p = strchr_m(full_name, '\\');
+
+ if (p != NULL) {
+ domain = talloc_strndup(tmp_ctx, full_name,
+ PTR_DIFF(p, full_name));
+ name = talloc_strdup(tmp_ctx, p+1);
+ } else {
+ char *q = strchr_m(full_name, '@');
+
+ /* Set the domain for UPNs */
+ if (q != NULL) {
+ name = talloc_strndup(tmp_ctx,
+ full_name,
+ PTR_DIFF(q, full_name));
+ domain = talloc_strdup(tmp_ctx, q + 1);
+ } else {
+ domain = talloc_strdup(tmp_ctx, "");
+ name = talloc_strdup(tmp_ctx, full_name);
+ }
+ }
+
+ if ((domain == NULL) || (name == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ DEBUG(10,("lookup_name: %s => domain=[%s], name=[%s]\n",
+ full_name, domain, name));
+ DEBUG(10, ("lookup_name: flags = 0x0%x\n", flags));
+
+ if ((flags & LOOKUP_NAME_DOMAIN) || (flags == 0)) {
+ bool check_global_sam = false;
+
+ check_global_sam = strequal(domain, get_global_sam_name());
+
+ /* If we are running on a DC that has PASSDB module with domain
+ * information, check if DNS forest name is matching the domain
+ * name. This is the case of IPA domain controller when
+ * trusted AD DC looks up users found in a Global Catalog of
+ * the forest root domain. */
+ if (!check_global_sam && (IS_DC)) {
+ struct pdb_domain_info *dom_info = NULL;
+ dom_info = pdb_get_domain_info(tmp_ctx);
+
+ if ((dom_info != NULL) && (dom_info->dns_forest != NULL)) {
+ check_global_sam = strequal(domain, dom_info->dns_forest);
+ }
+
+ TALLOC_FREE(dom_info);
+ }
+
+ if (check_global_sam) {
+ /* It's our own domain, lookup the name in passdb */
+ if (lookup_global_sam_name(name, flags, &rid, &type)) {
+ sid_compose(&sid, get_global_sam_sid(), rid);
+ goto ok;
+ }
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ }
+
+ if ((flags & LOOKUP_NAME_BUILTIN) &&
+ strequal(domain, builtin_domain_name()))
+ {
+ if (strlen(name) == 0) {
+ /* Swap domain and name */
+ tmp = name; name = domain; domain = tmp;
+ sid_copy(&sid, &global_sid_Builtin);
+ type = SID_NAME_DOMAIN;
+ goto ok;
+ }
+
+ /* Explicit request for a name in BUILTIN */
+ if (lookup_builtin_name(name, &rid)) {
+ sid_compose(&sid, &global_sid_Builtin, rid);
+ type = SID_NAME_ALIAS;
+ goto ok;
+ }
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ /* Try the explicit winbind lookup first, don't let it guess the
+ * domain yet at this point yet. This comes later. */
+
+ if ((domain[0] != '\0') &&
+ (flags & ~(LOOKUP_NAME_DOMAIN|LOOKUP_NAME_ISOLATED)) &&
+ (winbind_lookup_name(domain, name, &sid, &type))) {
+ goto ok;
+ }
+
+ if (((flags & (LOOKUP_NAME_NO_NSS|LOOKUP_NAME_GROUP)) == 0)
+ && strequal(domain, unix_users_domain_name())) {
+ if (lookup_unix_user_name(name, &sid)) {
+ type = SID_NAME_USER;
+ goto ok;
+ }
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ if (((flags & LOOKUP_NAME_NO_NSS) == 0)
+ && strequal(domain, unix_groups_domain_name())) {
+ if (lookup_unix_group_name(name, &sid)) {
+ type = SID_NAME_DOM_GRP;
+ goto ok;
+ }
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ /*
+ * Finally check for a well known domain name ("NT Authority"),
+ * this is being taken care of in lookup_wellknown_name().
+ */
+ if ((domain[0] != '\0') &&
+ (flags & LOOKUP_NAME_WKN) &&
+ lookup_wellknown_name(tmp_ctx, name, &sid, &domain))
+ {
+ type = SID_NAME_WKN_GRP;
+ goto ok;
+ }
+
+ /*
+ * If we're told not to look up 'isolated' names then we're
+ * done.
+ */
+ if (!(flags & LOOKUP_NAME_ISOLATED)) {
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ /*
+ * No domain names beyond this point
+ */
+ if (domain[0] != '\0') {
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ /* Now the guesswork begins, we haven't been given an explicit
+ * domain. Try the sequence as documented on
+ * http://msdn.microsoft.com/library/en-us/secmgmt/security/lsalookupnames.asp
+ * November 27, 2005 */
+
+ /* 1. well-known names */
+
+ /*
+ * Check for well known names without a domain name.
+ * e.g. \Creator Owner.
+ */
+
+ if ((flags & LOOKUP_NAME_WKN) &&
+ lookup_wellknown_name(tmp_ctx, name, &sid, &domain))
+ {
+ type = SID_NAME_WKN_GRP;
+ goto ok;
+ }
+
+ /* 2. Builtin domain as such */
+
+ if ((flags & (LOOKUP_NAME_BUILTIN|LOOKUP_NAME_REMOTE)) &&
+ strequal(name, builtin_domain_name()))
+ {
+ /* Swap domain and name */
+ tmp = name; name = domain; domain = tmp;
+ sid_copy(&sid, &global_sid_Builtin);
+ type = SID_NAME_DOMAIN;
+ goto ok;
+ }
+
+ /* 3. Account domain */
+
+ if ((flags & LOOKUP_NAME_DOMAIN) &&
+ strequal(name, get_global_sam_name()))
+ {
+ if (!secrets_fetch_domain_sid(name, &sid)) {
+ DEBUG(3, ("Could not fetch my SID\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ /* Swap domain and name */
+ tmp = name; name = domain; domain = tmp;
+ type = SID_NAME_DOMAIN;
+ goto ok;
+ }
+
+ /* 4. Primary domain */
+
+ if ((flags & LOOKUP_NAME_DOMAIN) && !IS_DC &&
+ strequal(name, lp_workgroup()))
+ {
+ if (!secrets_fetch_domain_sid(name, &sid)) {
+ DEBUG(3, ("Could not fetch the domain SID\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ /* Swap domain and name */
+ tmp = name; name = domain; domain = tmp;
+ type = SID_NAME_DOMAIN;
+ goto ok;
+ }
+
+ /* 5. Trusted domains as such, to me it looks as if members don't do
+ this, tested an XP workstation in a NT domain -- vl */
+
+ if ((flags & LOOKUP_NAME_REMOTE) && IS_DC &&
+ (pdb_get_trusteddom_pw(name, NULL, &sid, NULL)))
+ {
+ /* Swap domain and name */
+ tmp = name; name = domain; domain = tmp;
+ type = SID_NAME_DOMAIN;
+ goto ok;
+ }
+
+ /* 6. Builtin aliases */
+
+ if ((flags & LOOKUP_NAME_BUILTIN) &&
+ lookup_builtin_name(name, &rid))
+ {
+ domain = talloc_strdup(tmp_ctx, builtin_domain_name());
+ sid_compose(&sid, &global_sid_Builtin, rid);
+ type = SID_NAME_ALIAS;
+ goto ok;
+ }
+
+ /* 7. Local systems' SAM (DCs don't have a local SAM) */
+ /* 8. Primary SAM (On members, this is the domain) */
+
+ /* Both cases are done by looking at our passdb */
+
+ if ((flags & LOOKUP_NAME_DOMAIN) &&
+ lookup_global_sam_name(name, flags, &rid, &type))
+ {
+ domain = talloc_strdup(tmp_ctx, get_global_sam_name());
+ sid_compose(&sid, get_global_sam_sid(), rid);
+ goto ok;
+ }
+
+ /* Now our local possibilities are exhausted. */
+
+ if (!(flags & LOOKUP_NAME_REMOTE)) {
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ /* If we are not a DC, we have to ask in our primary domain. Let
+ * winbind do that. */
+
+ if (!IS_DC &&
+ (winbind_lookup_name(lp_workgroup(), name, &sid, &type))) {
+ domain = talloc_strdup(tmp_ctx, lp_workgroup());
+ goto ok;
+ }
+
+ /* 9. Trusted domains */
+
+ /* If we're a DC we have to ask all trusted DC's. Winbind does not do
+ * that (yet), but give it a chance. */
+
+ if (IS_DC && winbind_lookup_name("", name, &sid, &type)) {
+ struct dom_sid dom_sid;
+ enum lsa_SidType domain_type;
+
+ if (type == SID_NAME_DOMAIN) {
+ /* Swap name and type */
+ tmp = name; name = domain; domain = tmp;
+ goto ok;
+ }
+
+ /* Here we have to cope with a little deficiency in the
+ * winbind API: We have to ask it again for the name of the
+ * domain it figured out itself. Maybe fix that later... */
+
+ sid_copy(&dom_sid, &sid);
+ sid_split_rid(&dom_sid, NULL);
+
+ if (!winbind_lookup_sid(tmp_ctx, &dom_sid, &domain, NULL,
+ &domain_type) ||
+ (domain_type != SID_NAME_DOMAIN)) {
+ DEBUG(2, ("winbind could not find the domain's name "
+ "it just looked up for us\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ goto ok;
+ }
+
+ /* 10. Don't translate */
+
+ /* 11. Ok, windows would end here. Samba has two more options:
+ Unmapped users and unmapped groups */
+
+ if (((flags & (LOOKUP_NAME_NO_NSS|LOOKUP_NAME_GROUP)) == 0)
+ && lookup_unix_user_name(name, &sid)) {
+ domain = talloc_strdup(tmp_ctx, unix_users_domain_name());
+ type = SID_NAME_USER;
+ goto ok;
+ }
+
+ if (((flags & LOOKUP_NAME_NO_NSS) == 0)
+ && lookup_unix_group_name(name, &sid)) {
+ domain = talloc_strdup(tmp_ctx, unix_groups_domain_name());
+ type = SID_NAME_DOM_GRP;
+ goto ok;
+ }
+
+ /*
+ * Ok, all possibilities tried. Fail.
+ */
+
+ TALLOC_FREE(tmp_ctx);
+ return false;
+
+ ok:
+ if ((domain == NULL) || (name == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ /*
+ * Hand over the results to the talloc context we've been given.
+ */
+
+ if ((ret_name != NULL) &&
+ !(*ret_name = talloc_strdup(mem_ctx, name))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ if (ret_domain != NULL) {
+ char *tmp_dom;
+ if (!(tmp_dom = talloc_strdup(mem_ctx, domain))) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ if (!strupper_m(tmp_dom)) {
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ *ret_domain = tmp_dom;
+ }
+
+ if (ret_sid != NULL) {
+ sid_copy(ret_sid, &sid);
+ }
+
+ if (ret_type != NULL) {
+ *ret_type = type;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return true;
+}
+
+/************************************************************************
+ Names from smb.conf can be unqualified. eg. valid users = foo
+ These names should never map to a remote name. Try global_sam_name()\foo,
+ and then "Unix Users"\foo (or "Unix Groups"\foo).
+************************************************************************/
+
+bool lookup_name_smbconf(TALLOC_CTX *mem_ctx,
+ const char *full_name, int flags,
+ const char **ret_domain, const char **ret_name,
+ struct dom_sid *ret_sid, enum lsa_SidType *ret_type)
+{
+ char *qualified_name = NULL;
+ const char *p = strchr_m(full_name, *lp_winbind_separator());
+ bool is_qualified = p != NULL || strchr_m(full_name, '@') != NULL;
+
+ /* For DOMAIN\user or user@REALM directly call lookup_name(). */
+ if (is_qualified) {
+
+ /* The name is already qualified with a domain. */
+
+ if (p != NULL && *lp_winbind_separator() != '\\') {
+ /* lookup_name() needs '\\' as a separator */
+
+ qualified_name = talloc_strdup(mem_ctx, full_name);
+ if (qualified_name == NULL) {
+ return false;
+ }
+ qualified_name[p - full_name] = '\\';
+ full_name = qualified_name;
+ }
+
+ return lookup_name(mem_ctx, full_name, flags,
+ ret_domain, ret_name,
+ ret_sid, ret_type);
+ }
+
+ /* Try with winbind default domain name. */
+ if (lp_winbind_use_default_domain()) {
+ bool ok;
+
+ qualified_name = talloc_asprintf(mem_ctx,
+ "%s\\%s",
+ lp_workgroup(),
+ full_name);
+ if (qualified_name == NULL) {
+ return false;
+ }
+
+ ok = lookup_name(mem_ctx,
+ qualified_name,
+ flags,
+ ret_domain,
+ ret_name,
+ ret_sid,
+ ret_type);
+ if (ok) {
+ return true;
+ }
+ }
+
+ /* Try with our own SAM name. */
+ qualified_name = talloc_asprintf(mem_ctx, "%s\\%s",
+ get_global_sam_name(),
+ full_name );
+ if (!qualified_name) {
+ return false;
+ }
+
+ if (lookup_name(mem_ctx, qualified_name, flags,
+ ret_domain, ret_name,
+ ret_sid, ret_type)) {
+ return true;
+ }
+
+ /* Finally try with "Unix Users" or "Unix Group" */
+ qualified_name = talloc_asprintf(mem_ctx, "%s\\%s",
+ flags & LOOKUP_NAME_GROUP ?
+ unix_groups_domain_name() :
+ unix_users_domain_name(),
+ full_name );
+ if (!qualified_name) {
+ return false;
+ }
+
+ return lookup_name(mem_ctx, qualified_name, flags,
+ ret_domain, ret_name,
+ ret_sid, ret_type);
+}
+
+static bool wb_lookup_rids(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ int num_rids, uint32_t *rids,
+ const char **domain_name,
+ const char **names, enum lsa_SidType *types)
+{
+ int i;
+ const char **my_names;
+ enum lsa_SidType *my_types;
+ TALLOC_CTX *tmp_ctx;
+
+ if (!(tmp_ctx = talloc_init("wb_lookup_rids"))) {
+ return false;
+ }
+
+ if (!winbind_lookup_rids(tmp_ctx, domain_sid, num_rids, rids,
+ domain_name, &my_names, &my_types)) {
+ *domain_name = "";
+ for (i=0; i<num_rids; i++) {
+ names[i] = "";
+ types[i] = SID_NAME_UNKNOWN;
+ }
+ TALLOC_FREE(tmp_ctx);
+ return true;
+ }
+
+ if (!(*domain_name = talloc_strdup(mem_ctx, *domain_name))) {
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ /*
+ * winbind_lookup_rids allocates its own array. We've been given the
+ * array, so copy it over
+ */
+
+ for (i=0; i<num_rids; i++) {
+ if (my_names[i] == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ if (!(names[i] = talloc_strdup(names, my_names[i]))) {
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ types[i] = my_types[i];
+ }
+ TALLOC_FREE(tmp_ctx);
+ return true;
+}
+
+static bool lookup_rids(TALLOC_CTX *mem_ctx, const struct dom_sid *domain_sid,
+ int num_rids, uint32_t *rids,
+ const char **domain_name,
+ const char ***names, enum lsa_SidType **types)
+{
+ int i;
+ struct dom_sid_buf buf;
+
+ DEBUG(10, ("lookup_rids called for domain sid '%s'\n",
+ dom_sid_str_buf(domain_sid, &buf)));
+
+ if (num_rids) {
+ *names = talloc_zero_array(mem_ctx, const char *, num_rids);
+ *types = talloc_array(mem_ctx, enum lsa_SidType, num_rids);
+
+ if ((*names == NULL) || (*types == NULL)) {
+ return false;
+ }
+
+ for (i = 0; i < num_rids; i++)
+ (*types)[i] = SID_NAME_UNKNOWN;
+ } else {
+ *names = NULL;
+ *types = NULL;
+ }
+
+ if (sid_check_is_our_sam(domain_sid)) {
+ NTSTATUS result;
+
+ if (*domain_name == NULL) {
+ *domain_name = talloc_strdup(
+ mem_ctx, get_global_sam_name());
+ }
+
+ if (*domain_name == NULL) {
+ return false;
+ }
+
+ become_root();
+ result = pdb_lookup_rids(domain_sid, num_rids, rids,
+ *names, *types);
+ unbecome_root();
+
+ return (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED) ||
+ NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED));
+ }
+
+ if (sid_check_is_builtin(domain_sid)) {
+
+ if (*domain_name == NULL) {
+ *domain_name = talloc_strdup(
+ mem_ctx, builtin_domain_name());
+ }
+
+ if (*domain_name == NULL) {
+ return false;
+ }
+
+ for (i=0; i<num_rids; i++) {
+ if (lookup_builtin_rid(*names, rids[i],
+ &(*names)[i])) {
+ if ((*names)[i] == NULL) {
+ return false;
+ }
+ (*types)[i] = SID_NAME_ALIAS;
+ } else {
+ (*types)[i] = SID_NAME_UNKNOWN;
+ }
+ }
+ return true;
+ }
+
+ if (sid_check_is_wellknown_domain(domain_sid, NULL)) {
+ for (i=0; i<num_rids; i++) {
+ struct dom_sid sid;
+ sid_compose(&sid, domain_sid, rids[i]);
+ if (lookup_wellknown_sid(mem_ctx, &sid,
+ domain_name, &(*names)[i])) {
+ if ((*names)[i] == NULL) {
+ return false;
+ }
+ (*types)[i] = SID_NAME_WKN_GRP;
+ } else {
+ (*types)[i] = SID_NAME_UNKNOWN;
+ }
+ }
+ return true;
+ }
+
+ if (sid_check_is_unix_users(domain_sid)) {
+ if (*domain_name == NULL) {
+ *domain_name = talloc_strdup(
+ mem_ctx, unix_users_domain_name());
+ if (*domain_name == NULL) {
+ return false;
+ }
+ }
+ for (i=0; i<num_rids; i++) {
+ (*names)[i] = talloc_strdup(
+ (*names), uidtoname(rids[i]));
+ if ((*names)[i] == NULL) {
+ return false;
+ }
+ (*types)[i] = SID_NAME_USER;
+ }
+ return true;
+ }
+
+ if (sid_check_is_unix_groups(domain_sid)) {
+ if (*domain_name == NULL) {
+ *domain_name = talloc_strdup(
+ mem_ctx, unix_groups_domain_name());
+ if (*domain_name == NULL) {
+ return false;
+ }
+ }
+ for (i=0; i<num_rids; i++) {
+ (*names)[i] = talloc_strdup(
+ (*names), gidtoname(rids[i]));
+ if ((*names)[i] == NULL) {
+ return false;
+ }
+ (*types)[i] = SID_NAME_DOM_GRP;
+ }
+ return true;
+ }
+
+ return wb_lookup_rids(mem_ctx, domain_sid, num_rids, rids,
+ domain_name, *names, *types);
+}
+
+/*
+ * Is the SID a domain as such? If yes, lookup its name.
+ */
+
+static bool lookup_as_domain(const struct dom_sid *sid, TALLOC_CTX *mem_ctx,
+ const char **name)
+{
+ const char *tmp;
+ enum lsa_SidType type;
+
+ if (sid_check_is_our_sam(sid)) {
+ *name = talloc_strdup(mem_ctx, get_global_sam_name());
+ return true;
+ }
+
+ if (sid_check_is_builtin(sid)) {
+ *name = talloc_strdup(mem_ctx, builtin_domain_name());
+ return true;
+ }
+
+ if (sid_check_is_wellknown_domain(sid, &tmp)) {
+ *name = talloc_strdup(mem_ctx, tmp);
+ return true;
+ }
+
+ if (sid_check_is_unix_users(sid)) {
+ *name = talloc_strdup(mem_ctx, unix_users_domain_name());
+ return true;
+ }
+
+ if (sid_check_is_unix_groups(sid)) {
+ *name = talloc_strdup(mem_ctx, unix_groups_domain_name());
+ return true;
+ }
+
+ if (sid->num_auths != 4) {
+ /* This can't be a domain */
+ return false;
+ }
+
+ if (IS_DC) {
+ uint32_t i, num_domains;
+ struct trustdom_info **domains;
+
+ /* This is relatively expensive, but it happens only on DCs
+ * and for SIDs that have 4 sub-authorities and thus look like
+ * domains */
+
+ if (!NT_STATUS_IS_OK(pdb_enum_trusteddoms(mem_ctx,
+ &num_domains,
+ &domains))) {
+ return false;
+ }
+
+ for (i=0; i<num_domains; i++) {
+ if (dom_sid_equal(sid, &domains[i]->sid)) {
+ *name = talloc_strdup(mem_ctx,
+ domains[i]->name);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ if (winbind_lookup_sid(mem_ctx, sid, &tmp, NULL, &type) &&
+ (type == SID_NAME_DOMAIN)) {
+ *name = tmp;
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * This tries to implement the rather weird rules for the lsa_lookup level
+ * parameter.
+ *
+ * This is as close as we can get to what W2k3 does. With this we survive the
+ * RPC-LSALOOKUP samba4 test as of 2006-01-08. NT4 as a PDC is a bit more
+ * different, but I assume that's just being too liberal. For example, W2k3
+ * replies to everything else but the levels 1-6 with INVALID_PARAMETER
+ * whereas NT4 does the same as level 1 (I think). I did not fully test that
+ * with NT4, this is what w2k3 does.
+ *
+ * Level 1: Ask everywhere
+ * Level 2: Ask domain and trusted domains, no builtin and wkn
+ * Level 3: Only ask domain
+ * Level 4: W2k3ad: Only ask AD trusts
+ * Level 5: Only ask transitive forest trusts
+ * Level 6: Like 4
+ */
+
+static bool check_dom_sid_to_level(const struct dom_sid *sid, int level)
+{
+ struct dom_sid_buf buf;
+ int ret = false;
+
+ switch(level) {
+ case 1:
+ ret = true;
+ break;
+ case 2:
+ ret = (!sid_check_is_builtin(sid) &&
+ !sid_check_is_wellknown_domain(sid, NULL));
+ break;
+ case 3:
+ case 4:
+ case 6:
+ ret = sid_check_is_our_sam(sid);
+ break;
+ case 5:
+ ret = false;
+ break;
+ }
+
+ DEBUG(10, ("%s SID %s in level %d\n",
+ ret ? "Accepting" : "Rejecting",
+ dom_sid_str_buf(sid, &buf),
+ level));
+ return ret;
+}
+
+/*
+ * Lookup a bunch of SIDs. This is modeled after lsa_lookup_sids with
+ * references to domains, it is explicitly made for this.
+ *
+ * This attempts to be as efficient as possible: It collects all SIDs
+ * belonging to a domain and hands them in bulk to the appropriate lookup
+ * function. In particular pdb_lookup_rids with ldapsam_trusted benefits
+ * *hugely* from this.
+ */
+
+NTSTATUS lookup_sids(TALLOC_CTX *mem_ctx, int num_sids,
+ const struct dom_sid **sids, int level,
+ struct lsa_dom_info **ret_domains,
+ struct lsa_name_info **ret_names)
+{
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS result;
+ struct lsa_name_info *name_infos;
+ struct lsa_dom_info *dom_infos = NULL;
+
+ int i, j;
+
+ if (!(tmp_ctx = talloc_new(mem_ctx))) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (num_sids) {
+ name_infos = talloc_array(mem_ctx, struct lsa_name_info, num_sids);
+ if (name_infos == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ } else {
+ name_infos = NULL;
+ }
+
+ dom_infos = talloc_zero_array(mem_ctx, struct lsa_dom_info,
+ LSA_REF_DOMAIN_LIST_MULTIPLIER);
+ if (dom_infos == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ /* First build up the data structures:
+ *
+ * dom_infos is a list of domains referenced in the list of
+ * SIDs. Later we will walk the list of domains and look up the RIDs
+ * in bulk.
+ *
+ * name_infos is a shadow-copy of the SIDs array to collect the real
+ * data.
+ *
+ * dom_info->idxs is an index into the name_infos array. The
+ * difficulty we have here is that we need to keep the SIDs the client
+ * asked for in the same order for the reply
+ */
+
+ for (i=0; i<num_sids; i++) {
+ struct dom_sid sid;
+ uint32_t rid = 0;
+ const char *domain_name = NULL;
+
+ sid_copy(&sid, sids[i]);
+ name_infos[i].type = SID_NAME_USE_NONE;
+
+ if (lookup_as_domain(&sid, name_infos, &domain_name)) {
+ /* We can't push that through the normal lookup
+ * process, as this would reference illegal
+ * domains.
+ *
+ * For example S-1-5-32 would end up referencing
+ * domain S-1-5- with RID 32 which is clearly wrong.
+ */
+ if (domain_name == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ name_infos[i].rid = 0;
+ name_infos[i].type = SID_NAME_DOMAIN;
+ name_infos[i].name = NULL;
+
+ if (sid_check_is_builtin(&sid)) {
+ /* Yes, W2k3 returns "BUILTIN" both as domain
+ * and name here */
+ name_infos[i].name = talloc_strdup(
+ name_infos, builtin_domain_name());
+ if (name_infos[i].name == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+ } else {
+ /* This is a normal SID with rid component */
+ if (!sid_split_rid(&sid, &rid)) {
+ result = NT_STATUS_INVALID_SID;
+ goto fail;
+ }
+ }
+
+ if (!check_dom_sid_to_level(&sid, level)) {
+ name_infos[i].rid = 0;
+ name_infos[i].type = SID_NAME_UNKNOWN;
+ name_infos[i].name = NULL;
+ continue;
+ }
+
+ for (j=0; j<LSA_REF_DOMAIN_LIST_MULTIPLIER; j++) {
+ if (!dom_infos[j].valid) {
+ break;
+ }
+ if (dom_sid_equal(&sid, &dom_infos[j].sid)) {
+ break;
+ }
+ }
+
+ if (j == LSA_REF_DOMAIN_LIST_MULTIPLIER) {
+ /* TODO: What's the right error message here? */
+ result = NT_STATUS_NONE_MAPPED;
+ goto fail;
+ }
+
+ if (!dom_infos[j].valid) {
+ /* We found a domain not yet referenced, create a new
+ * ref. */
+ dom_infos[j].valid = true;
+ sid_copy(&dom_infos[j].sid, &sid);
+
+ if (domain_name != NULL) {
+ /* This name was being found above in the case
+ * when we found a domain SID */
+ dom_infos[j].name =
+ talloc_strdup(dom_infos, domain_name);
+ if (dom_infos[j].name == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ } else {
+ /* lookup_rids will take care of this */
+ dom_infos[j].name = NULL;
+ }
+ }
+
+ name_infos[i].dom_idx = j;
+
+ if (name_infos[i].type == SID_NAME_USE_NONE) {
+ name_infos[i].rid = rid;
+
+ ADD_TO_ARRAY(dom_infos, int, i, &dom_infos[j].idxs,
+ &dom_infos[j].num_idxs);
+
+ if (dom_infos[j].idxs == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+ }
+
+ /* Iterate over the domains found */
+
+ for (i=0; i<LSA_REF_DOMAIN_LIST_MULTIPLIER; i++) {
+ uint32_t *rids;
+ const char *domain_name = NULL;
+ const char **names;
+ enum lsa_SidType *types;
+ struct lsa_dom_info *dom = &dom_infos[i];
+
+ if (!dom->valid) {
+ /* No domains left, we're done */
+ break;
+ }
+
+ if (dom->num_idxs == 0) {
+ /*
+ * This happens only if the only sid related to
+ * this domain is the domain sid itself, which
+ * is mapped to SID_NAME_DOMAIN above.
+ */
+ continue;
+ }
+
+ if (!(rids = talloc_array(tmp_ctx, uint32_t, dom->num_idxs))) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ for (j=0; j<dom->num_idxs; j++) {
+ rids[j] = name_infos[dom->idxs[j]].rid;
+ }
+
+ if (!lookup_rids(tmp_ctx, &dom->sid,
+ dom->num_idxs, rids, &domain_name,
+ &names, &types)) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!(dom->name = talloc_strdup(dom_infos, domain_name))) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ for (j=0; j<dom->num_idxs; j++) {
+ int idx = dom->idxs[j];
+ name_infos[idx].type = types[j];
+ if (types[j] != SID_NAME_UNKNOWN) {
+ name_infos[idx].name =
+ talloc_strdup(name_infos, names[j]);
+ if (name_infos[idx].name == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ } else {
+ name_infos[idx].name = NULL;
+ }
+ }
+ }
+
+ *ret_domains = dom_infos;
+ *ret_names = name_infos;
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(dom_infos);
+ TALLOC_FREE(name_infos);
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/*****************************************************************
+ *THE CANONICAL* convert SID to name function.
+*****************************************************************/
+
+bool lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ const char **ret_domain, const char **ret_name,
+ enum lsa_SidType *ret_type)
+{
+ struct lsa_dom_info *domain;
+ struct lsa_name_info *name;
+ struct dom_sid_buf buf;
+ TALLOC_CTX *tmp_ctx;
+ bool ret = false;
+
+ DEBUG(10, ("lookup_sid called for SID '%s'\n",
+ dom_sid_str_buf(sid, &buf)));
+
+ if (!(tmp_ctx = talloc_new(mem_ctx))) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return false;
+ }
+
+ if (!NT_STATUS_IS_OK(lookup_sids(tmp_ctx, 1, &sid, 1,
+ &domain, &name))) {
+ goto done;
+ }
+
+ if (name->type == SID_NAME_UNKNOWN) {
+ goto done;
+ }
+
+ if ((ret_domain != NULL) &&
+ !(*ret_domain = talloc_strdup(mem_ctx, domain->name))) {
+ goto done;
+ }
+
+ if ((ret_name != NULL) &&
+ !(*ret_name = talloc_strdup(mem_ctx, name->name))) {
+ goto done;
+ }
+
+ if (ret_type != NULL) {
+ *ret_type = name->type;
+ }
+
+ ret = true;
+
+ done:
+ if (ret) {
+ DEBUG(10, ("Sid %s -> %s\\%s(%d)\n",
+ dom_sid_str_buf(sid, &buf),
+ domain->name, name->name, name->type));
+ } else {
+ DEBUG(10, ("failed to lookup sid %s\n",
+ dom_sid_str_buf(sid, &buf)));
+ }
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+/*****************************************************************
+ *THE LEGACY* convert SID to id function.
+*****************************************************************/
+
+static bool legacy_sid_to_unixid(const struct dom_sid *psid, struct unixid *id)
+{
+ bool ret;
+
+ become_root();
+ ret = pdb_sid_to_id(psid, id);
+ unbecome_root();
+
+ if (!ret) {
+ struct dom_sid_buf buf;
+ DEBUG(10,("LEGACY: mapping failed for sid %s\n",
+ dom_sid_str_buf(psid, &buf)));
+ return false;
+ }
+
+ return true;
+}
+
+static bool legacy_sid_to_gid(const struct dom_sid *psid, gid_t *pgid)
+{
+ struct unixid id;
+ if (!legacy_sid_to_unixid(psid, &id)) {
+ return false;
+ }
+ if (id.type == ID_TYPE_GID || id.type == ID_TYPE_BOTH) {
+ *pgid = id.id;
+ return true;
+ }
+ return false;
+}
+
+static bool legacy_sid_to_uid(const struct dom_sid *psid, uid_t *puid)
+{
+ struct unixid id;
+ if (!legacy_sid_to_unixid(psid, &id)) {
+ return false;
+ }
+ if (id.type == ID_TYPE_UID || id.type == ID_TYPE_BOTH) {
+ *puid = id.id;
+ return true;
+ }
+ return false;
+}
+
+void xid_to_sid(struct dom_sid *psid, const struct unixid *xid)
+{
+ bool expired = true;
+ bool ret;
+ struct dom_sid_buf buf;
+
+ SMB_ASSERT(xid->type == ID_TYPE_UID || xid->type == ID_TYPE_GID);
+
+ *psid = (struct dom_sid) {0};
+
+ ret = idmap_cache_find_xid2sid(xid, psid, &expired);
+ if (ret && !expired) {
+ DBG_DEBUG("%cID %"PRIu32" -> %s from cache\n",
+ xid->type == ID_TYPE_UID ? 'U' : 'G',
+ xid->id,
+ dom_sid_str_buf(psid, &buf));
+ goto done;
+ }
+
+ ret = winbind_xid_to_sid(psid, xid);
+ if (ret) {
+ /*
+ * winbind can return an explicit negative mapping
+ * here. It's up to winbind to prime the cache either
+ * positively or negatively, don't mess with the cache
+ * here.
+ */
+ DBG_DEBUG("%cID %"PRIu32" -> %s from cache\n",
+ xid->type == ID_TYPE_UID ? 'U' : 'G',
+ xid->id,
+ dom_sid_str_buf(psid, &buf));
+ goto done;
+ }
+
+ {
+ /*
+ * Make a copy, pdb_id_to_sid might want to turn
+ * xid->type into ID_TYPE_BOTH, which we ignore here.
+ */
+ struct unixid rw_xid = *xid;
+
+ become_root();
+ ret = pdb_id_to_sid(&rw_xid, psid);
+ unbecome_root();
+ }
+
+ if (ret) {
+ DBG_DEBUG("%cID %"PRIu32" -> %s from passdb\n",
+ xid->type == ID_TYPE_UID ? 'U' : 'G',
+ xid->id,
+ dom_sid_str_buf(psid, &buf));
+ goto done;
+ }
+
+done:
+ if (is_null_sid(psid)) {
+ /*
+ * Nobody found anything: Return S-1-22-xx-yy. Don't
+ * store that in caches, this is up to the layers
+ * beneath us.
+ */
+ if (xid->type == ID_TYPE_UID) {
+ uid_to_unix_users_sid(xid->id, psid);
+ } else {
+ gid_to_unix_groups_sid(xid->id, psid);
+ }
+
+ DBG_DEBUG("%cID %"PRIu32" -> %s fallback\n",
+ xid->type == ID_TYPE_UID ? 'U' : 'G',
+ xid->id,
+ dom_sid_str_buf(psid, &buf));
+ }
+}
+
+void uid_to_sid(struct dom_sid *psid, uid_t uid)
+{
+ struct unixid xid = { .type = ID_TYPE_UID, .id = uid};
+ xid_to_sid(psid, &xid);
+}
+
+void gid_to_sid(struct dom_sid *psid, gid_t gid)
+{
+ struct unixid xid = { .type = ID_TYPE_GID, .id = gid};
+ xid_to_sid(psid, &xid);
+}
+
+bool sids_to_unixids(const struct dom_sid *sids, uint32_t num_sids,
+ struct unixid *ids)
+{
+ struct wbcDomainSid *wbc_sids = NULL;
+ struct wbcUnixId *wbc_ids = NULL;
+ struct bitmap *found = NULL;
+ uint32_t i, num_not_cached;
+ uint32_t wbc_ids_size = 0;
+ wbcErr err;
+ bool ret = false;
+
+ wbc_sids = talloc_array(talloc_tos(), struct wbcDomainSid, num_sids);
+ if (wbc_sids == NULL) {
+ return false;
+ }
+ found = bitmap_talloc(wbc_sids, num_sids);
+ if (found == NULL) {
+ goto fail;
+ }
+
+ /*
+ * We go through the requested SID array three times.
+ * First time to look for global_sid_Unix_Users
+ * and global_sid_Unix_Groups SIDS, and to look
+ * for mappings cached in the idmap_cache.
+ *
+ * Use bitmap_set() to mark an ids[] array entry as
+ * being mapped.
+ */
+
+ num_not_cached = 0;
+
+ for (i=0; i<num_sids; i++) {
+ bool expired;
+ uint32_t rid;
+
+ if (sid_peek_check_rid(&global_sid_Unix_Users,
+ &sids[i], &rid)) {
+ ids[i].type = ID_TYPE_UID;
+ ids[i].id = rid;
+ bitmap_set(found, i);
+ continue;
+ }
+ if (sid_peek_check_rid(&global_sid_Unix_Groups,
+ &sids[i], &rid)) {
+ ids[i].type = ID_TYPE_GID;
+ ids[i].id = rid;
+ bitmap_set(found, i);
+ continue;
+ }
+ if (idmap_cache_find_sid2unixid(&sids[i], &ids[i], &expired)
+ && !expired)
+ {
+ bitmap_set(found, i);
+ continue;
+ }
+ ids[i].type = ID_TYPE_NOT_SPECIFIED;
+ memcpy(&wbc_sids[num_not_cached], &sids[i],
+ ndr_size_dom_sid(&sids[i], 0));
+ num_not_cached += 1;
+ }
+ if (num_not_cached == 0) {
+ goto done;
+ }
+
+ /*
+ * For the ones that we couldn't map in the loop above, query winbindd
+ * via wbcSidsToUnixIds().
+ */
+
+ wbc_ids_size = num_not_cached;
+ wbc_ids = talloc_array(talloc_tos(), struct wbcUnixId, wbc_ids_size);
+ if (wbc_ids == NULL) {
+ goto fail;
+ }
+ for (i=0; i<wbc_ids_size; i++) {
+ wbc_ids[i].type = WBC_ID_TYPE_NOT_SPECIFIED;
+ wbc_ids[i].id.gid = (uint32_t)-1;
+ }
+ err = wbcSidsToUnixIds(wbc_sids, wbc_ids_size, wbc_ids);
+ if (!WBC_ERROR_IS_OK(err)) {
+ DEBUG(10, ("wbcSidsToUnixIds returned %s\n",
+ wbcErrorString(err)));
+ }
+
+ /*
+ * Second time through the SID array, replace
+ * the ids[] entries that wbcSidsToUnixIds() was able to
+ * map.
+ *
+ * Use bitmap_set() to mark an ids[] array entry as
+ * being mapped.
+ */
+
+ num_not_cached = 0;
+
+ for (i=0; i<num_sids; i++) {
+ if (bitmap_query(found, i)) {
+ continue;
+ }
+
+ SMB_ASSERT(num_not_cached < wbc_ids_size);
+
+ switch (wbc_ids[num_not_cached].type) {
+ case WBC_ID_TYPE_UID:
+ ids[i].type = ID_TYPE_UID;
+ ids[i].id = wbc_ids[num_not_cached].id.uid;
+ bitmap_set(found, i);
+ break;
+ case WBC_ID_TYPE_GID:
+ ids[i].type = ID_TYPE_GID;
+ ids[i].id = wbc_ids[num_not_cached].id.gid;
+ bitmap_set(found, i);
+ break;
+ case WBC_ID_TYPE_BOTH:
+ ids[i].type = ID_TYPE_BOTH;
+ ids[i].id = wbc_ids[num_not_cached].id.uid;
+ bitmap_set(found, i);
+ break;
+ case WBC_ID_TYPE_NOT_SPECIFIED:
+ /*
+ * wbcSidsToUnixIds() wasn't able to map this
+ * so we still need to check legacy_sid_to_XXX()
+ * below. Don't mark the bitmap entry
+ * as being found so the final loop knows
+ * to try and map this entry.
+ */
+ ids[i].type = ID_TYPE_NOT_SPECIFIED;
+ ids[i].id = (uint32_t)-1;
+ break;
+ default:
+ /*
+ * A successful return from wbcSidsToUnixIds()
+ * cannot return anything other than the values
+ * checked for above. Ensure this is so.
+ */
+ smb_panic(__location__);
+ break;
+ }
+ num_not_cached += 1;
+ }
+
+ /*
+ * Third and final time through the SID array,
+ * try legacy_sid_to_gid()/legacy_sid_to_uid()
+ * for entries we haven't already been able to
+ * map.
+ *
+ * Use bitmap_set() to mark an ids[] array entry as
+ * being mapped.
+ */
+
+ for (i=0; i<num_sids; i++) {
+ if (bitmap_query(found, i)) {
+ continue;
+ }
+ if (legacy_sid_to_gid(&sids[i], &ids[i].id)) {
+ ids[i].type = ID_TYPE_GID;
+ bitmap_set(found, i);
+ continue;
+ }
+ if (legacy_sid_to_uid(&sids[i], &ids[i].id)) {
+ ids[i].type = ID_TYPE_UID;
+ bitmap_set(found, i);
+ continue;
+ }
+ }
+done:
+ /*
+ * Pass through the return array for consistency.
+ * Any ids[].id mapped to (uint32_t)-1 must be returned
+ * as ID_TYPE_NOT_SPECIFIED.
+ */
+ for (i=0; i<num_sids; i++) {
+ switch(ids[i].type) {
+ case ID_TYPE_GID:
+ case ID_TYPE_UID:
+ case ID_TYPE_BOTH:
+ if (ids[i].id == (uint32_t)-1) {
+ ids[i].type = ID_TYPE_NOT_SPECIFIED;
+ }
+ break;
+ case ID_TYPE_NOT_SPECIFIED:
+ break;
+ case ID_TYPE_WB_REQUIRE_TYPE:
+ /*
+ * these are internal between winbindd
+ * parent and child.
+ */
+ smb_panic(__location__);
+ break;
+ }
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(wbc_ids);
+ TALLOC_FREE(wbc_sids);
+ return ret;
+}
+
+/*****************************************************************
+ *THE CANONICAL* convert SID to uid function.
+*****************************************************************/
+
+bool sid_to_uid(const struct dom_sid *psid, uid_t *puid)
+{
+ bool expired = true;
+ bool ret;
+ uint32_t rid;
+ struct dom_sid_buf buf;
+
+ /* Optimize for the Unix Users Domain
+ * as the conversion is straightforward */
+ if (sid_peek_check_rid(&global_sid_Unix_Users, psid, &rid)) {
+ uid_t uid = rid;
+ *puid = uid;
+
+ /* return here, don't cache */
+ DEBUG(10,("sid %s -> uid %u\n",
+ dom_sid_str_buf(psid, &buf),
+ (unsigned int)*puid ));
+ return true;
+ }
+
+ if (sid_check_is_in_unix_groups(psid)) {
+ DBG_DEBUG("SID %s is a group, failing\n",
+ dom_sid_str_buf(psid, &buf));
+ return false;
+ }
+
+ /* Check the winbindd cache directly. */
+ ret = idmap_cache_find_sid2uid(psid, puid, &expired);
+
+ if (ret && !expired && (*puid == (uid_t)-1)) {
+ /*
+ * Negative cache entry, we already asked.
+ * do legacy.
+ */
+ return legacy_sid_to_uid(psid, puid);
+ }
+
+ if (!ret || expired) {
+ /* Not in cache. Ask winbindd. */
+ if (!winbind_sid_to_uid(puid, psid)) {
+ DEBUG(5, ("winbind failed to find a uid for sid %s\n",
+ dom_sid_str_buf(psid, &buf)));
+ /* winbind failed. do legacy */
+ return legacy_sid_to_uid(psid, puid);
+ }
+ }
+
+ /* TODO: Here would be the place to allocate both a gid and a uid for
+ * the SID in question */
+
+ DEBUG(10,("sid %s -> uid %u\n",
+ dom_sid_str_buf(psid, &buf),
+ (unsigned int)*puid ));
+
+ return true;
+}
+
+/*****************************************************************
+ *THE CANONICAL* convert SID to gid function.
+ Group mapping is used for gids that maps to Wellknown SIDs
+*****************************************************************/
+
+bool sid_to_gid(const struct dom_sid *psid, gid_t *pgid)
+{
+ bool expired = true;
+ bool ret;
+ uint32_t rid;
+ struct dom_sid_buf buf;
+
+ /* Optimize for the Unix Groups Domain
+ * as the conversion is straightforward */
+ if (sid_peek_check_rid(&global_sid_Unix_Groups, psid, &rid)) {
+ gid_t gid = rid;
+ *pgid = gid;
+
+ /* return here, don't cache */
+ DEBUG(10,("sid %s -> gid %u\n",
+ dom_sid_str_buf(psid, &buf),
+ (unsigned int)*pgid ));
+ return true;
+ }
+
+ if (sid_check_is_in_unix_users(psid)) {
+ DBG_DEBUG("SID %s is a user, failing\n",
+ dom_sid_str_buf(psid, &buf));
+ return false;
+ }
+
+ /* Check the winbindd cache directly. */
+ ret = idmap_cache_find_sid2gid(psid, pgid, &expired);
+
+ if (ret && !expired && (*pgid == (gid_t)-1)) {
+ /*
+ * Negative cache entry, we already asked.
+ * do legacy.
+ */
+ return legacy_sid_to_gid(psid, pgid);
+ }
+
+ if (!ret || expired) {
+ /* Not in cache or negative. Ask winbindd. */
+ /* Ask winbindd if it can map this sid to a gid.
+ * (Idmap will check it is a valid SID and of the right type) */
+
+ if ( !winbind_sid_to_gid(pgid, psid) ) {
+
+ DEBUG(10,("winbind failed to find a gid for sid %s\n",
+ dom_sid_str_buf(psid, &buf)));
+ /* winbind failed. do legacy */
+ return legacy_sid_to_gid(psid, pgid);
+ }
+ }
+
+ DEBUG(10,("sid %s -> gid %u\n",
+ dom_sid_str_buf(psid, &buf),
+ (unsigned int)*pgid ));
+
+ return true;
+}
+
+/**
+ * @brief This function gets the primary group SID mapping the primary
+ * GID of the user as obtained by an actual getpwnam() call.
+ * This is necessary to avoid issues with arbitrary group SIDs
+ * stored in passdb. We try as hard as we can to get the SID
+ * corresponding to the GID, including trying group mapping.
+ * If nothing else works, we will force "Domain Users" as the
+ * primary group.
+ * This is needed because we must always be able to lookup the
+ * primary group SID, so we cannot settle for an arbitrary SID.
+ *
+ * This call can be expensive. Use with moderation.
+ * If you have a "samu" struct around use pdb_get_group_sid()
+ * instead as it does properly cache results.
+ *
+ * @param mem_ctx[in] The memory context iused to allocate the result.
+ * @param username[in] The user's name
+ * @param _pwd[in|out] If available, pass in user's passwd struct.
+ * It will contain a tallocated passwd if NULL was
+ * passed in.
+ * @param _group_sid[out] The user's Primary Group SID
+ *
+ * @return NTSTATUS error code.
+ */
+NTSTATUS get_primary_group_sid(TALLOC_CTX *mem_ctx,
+ const char *username,
+ struct passwd **_pwd,
+ struct dom_sid **_group_sid)
+{
+ TALLOC_CTX *tmp_ctx;
+ bool need_lookup_sid = false;
+ struct dom_sid *group_sid;
+ struct passwd *pwd = *_pwd;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pwd) {
+ pwd = Get_Pwnam_alloc(mem_ctx, username);
+ if (!pwd) {
+ DEBUG(0, ("Failed to find a Unix account for %s\n",
+ username));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ }
+
+ group_sid = talloc_zero(mem_ctx, struct dom_sid);
+ if (!group_sid) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ gid_to_sid(group_sid, pwd->pw_gid);
+ if (!is_null_sid(group_sid)) {
+ struct dom_sid domain_sid;
+ uint32_t rid;
+
+ /* We need a sid within our domain */
+ sid_copy(&domain_sid, group_sid);
+ sid_split_rid(&domain_sid, &rid);
+ if (dom_sid_equal(&domain_sid, get_global_sam_sid())) {
+ /*
+ * As shortcut for the expensive lookup_sid call
+ * compare the domain sid part
+ */
+ switch (rid) {
+ case DOMAIN_RID_ADMINS:
+ case DOMAIN_RID_USERS:
+ goto done;
+ default:
+ need_lookup_sid = true;
+ break;
+ }
+ } else {
+ /* Try group mapping */
+ struct unixid id;
+
+ id.id = pwd->pw_gid;
+ id.type = ID_TYPE_GID;
+
+ ZERO_STRUCTP(group_sid);
+ if (pdb_id_to_sid(&id, group_sid)) {
+ need_lookup_sid = true;
+ }
+ }
+ }
+
+ /* We must verify that this is a valid SID that resolves to a
+ * group of the correct type */
+ if (need_lookup_sid) {
+ enum lsa_SidType type = SID_NAME_UNKNOWN;
+ bool lookup_ret;
+ struct dom_sid_buf buf;
+
+ DEBUG(10, ("do lookup_sid(%s) for group of user %s\n",
+ dom_sid_str_buf(group_sid, &buf),
+ username));
+
+ /* Now check that it's actually a domain group and
+ * not something else */
+ lookup_ret = lookup_sid(tmp_ctx, group_sid,
+ NULL, NULL, &type);
+
+ if (lookup_ret && (type == SID_NAME_DOM_GRP)) {
+ goto done;
+ }
+
+ DEBUG(3, ("Primary group %s for user %s is"
+ " a %s and not a domain group\n",
+ dom_sid_str_buf(group_sid, &buf),
+ username,
+ sid_type_lookup(type)));
+ }
+
+ /* Everything else, failed.
+ * Just set it to the 'Domain Users' RID of 513 which will
+ always resolve to a name */
+ DEBUG(3, ("Forcing Primary Group to 'Domain Users' for %s\n",
+ username));
+
+ sid_compose(group_sid, get_global_sam_sid(), DOMAIN_RID_USERS);
+
+done:
+ *_pwd = talloc_move(mem_ctx, &pwd);
+ *_group_sid = talloc_move(mem_ctx, &group_sid);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
diff --git a/source3/passdb/lookup_sid.h b/source3/passdb/lookup_sid.h
new file mode 100644
index 0000000..8a21cca
--- /dev/null
+++ b/source3/passdb/lookup_sid.h
@@ -0,0 +1,96 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Gerald (Jerry) Carter 2003
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#ifndef _PASSDB_LOOKUP_SID_H_
+#define _PASSDB_LOOKUP_SID_H_
+
+#include "../librpc/gen_ndr/lsa.h"
+
+struct passwd;
+struct unixid;
+
+#define LOOKUP_NAME_NONE 0x00000000
+#define LOOKUP_NAME_ISOLATED 0x00000001 /* Look up unqualified names */
+#define LOOKUP_NAME_REMOTE 0x00000002 /* Ask others */
+#define LOOKUP_NAME_GROUP 0x00000004 /* This is a NASTY hack for
+ valid users = @foo where foo also
+ exists in as user. */
+#define LOOKUP_NAME_NO_NSS 0x00000008 /* no NSS calls to avoid
+ winbind recursions */
+#define LOOKUP_NAME_BUILTIN 0x00000010 /* builtin names */
+#define LOOKUP_NAME_WKN 0x00000020 /* well known names */
+#define LOOKUP_NAME_DOMAIN 0x00000040 /* only lookup own domain */
+#define LOOKUP_NAME_LOCAL (LOOKUP_NAME_ISOLATED\
+ |LOOKUP_NAME_BUILTIN\
+ |LOOKUP_NAME_WKN\
+ |LOOKUP_NAME_DOMAIN)
+#define LOOKUP_NAME_ALL (LOOKUP_NAME_ISOLATED\
+ |LOOKUP_NAME_REMOTE\
+ |LOOKUP_NAME_BUILTIN\
+ |LOOKUP_NAME_WKN\
+ |LOOKUP_NAME_DOMAIN)
+
+struct lsa_dom_info {
+ bool valid;
+ struct dom_sid sid;
+ const char *name;
+ int num_idxs;
+ int *idxs;
+};
+
+struct lsa_name_info {
+ uint32_t rid;
+ enum lsa_SidType type;
+ const char *name;
+ int dom_idx;
+};
+
+/* The following definitions come from passdb/lookup_sid.c */
+
+bool lookup_name(TALLOC_CTX *mem_ctx,
+ const char *full_name, int flags,
+ const char **ret_domain, const char **ret_name,
+ struct dom_sid *ret_sid, enum lsa_SidType *ret_type);
+bool lookup_name_smbconf(TALLOC_CTX *mem_ctx,
+ const char *full_name, int flags,
+ const char **ret_domain, const char **ret_name,
+ struct dom_sid *ret_sid, enum lsa_SidType *ret_type);
+NTSTATUS lookup_sids(TALLOC_CTX *mem_ctx, int num_sids,
+ const struct dom_sid **sids, int level,
+ struct lsa_dom_info **ret_domains,
+ struct lsa_name_info **ret_names);
+bool lookup_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ const char **ret_domain, const char **ret_name,
+ enum lsa_SidType *ret_type);
+void uid_to_sid(struct dom_sid *psid, uid_t uid);
+void gid_to_sid(struct dom_sid *psid, gid_t gid);
+void xid_to_sid(struct dom_sid *psid, const struct unixid *xid);
+bool sid_to_uid(const struct dom_sid *psid, uid_t *puid);
+bool sid_to_gid(const struct dom_sid *psid, gid_t *pgid);
+bool sids_to_unixids(const struct dom_sid *sids, uint32_t num_sids,
+ struct unixid *ids);
+NTSTATUS get_primary_group_sid(TALLOC_CTX *mem_ctx,
+ const char *username,
+ struct passwd **_pwd,
+ struct dom_sid **_group_sid);
+
+#endif /* _PASSDB_LOOKUP_SID_H_ */
diff --git a/source3/passdb/machine_account_secrets.c b/source3/passdb/machine_account_secrets.c
new file mode 100644
index 0000000..c97b35e
--- /dev/null
+++ b/source3/passdb/machine_account_secrets.c
@@ -0,0 +1,2080 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Andrew Tridgell 1992-2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Tim Potter 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* the Samba secrets database stores any generated, private information
+ such as the local SID and machine trust password */
+
+#include "includes.h"
+#include "passdb.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "secrets.h"
+#include "dbwrap/dbwrap.h"
+#include "../librpc/ndr/libndr.h"
+#include "util_tdb.h"
+#include "libcli/security/security.h"
+
+#include "librpc/gen_ndr/libnet_join.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
+#include "lib/crypto/crypto.h"
+#include "lib/krb5_wrap/krb5_samba.h"
+#include "lib/util/time_basic.h"
+#include "../libds/common/flags.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+static char *domain_info_keystr(const char *domain);
+
+static char *des_salt_key(const char *realm);
+
+/**
+ * Form a key for fetching the domain sid
+ *
+ * @param domain domain name
+ *
+ * @return keystring
+ **/
+static const char *domain_sid_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_DOMAIN_SID, domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+static const char *domain_guid_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_DOMAIN_GUID, domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+static const char *protect_ids_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_PROTECT_IDS, domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/* N O T E: never use this outside of passdb modules that store the SID on their own */
+bool secrets_mark_domain_protected(const char *domain)
+{
+ bool ret;
+
+ ret = secrets_store(protect_ids_keystr(domain), "TRUE", 5);
+ if (!ret) {
+ DEBUG(0, ("Failed to protect the Domain IDs\n"));
+ }
+ return ret;
+}
+
+bool secrets_clear_domain_protection(const char *domain)
+{
+ bool ret;
+ void *protection = secrets_fetch(protect_ids_keystr(domain), NULL);
+
+ if (protection) {
+ SAFE_FREE(protection);
+ ret = secrets_delete_entry(protect_ids_keystr(domain));
+ if (!ret) {
+ DEBUG(0, ("Failed to remove Domain IDs protection\n"));
+ }
+ return ret;
+ }
+ return true;
+}
+
+bool secrets_store_domain_sid(const char *domain, const struct dom_sid *sid)
+{
+ char *protect_ids;
+ bool ret;
+ struct dom_sid clean_sid = { 0 };
+
+ protect_ids = secrets_fetch(protect_ids_keystr(domain), NULL);
+ if (protect_ids) {
+ if (strncmp(protect_ids, "TRUE", 4)) {
+ DEBUG(0, ("Refusing to store a Domain SID, "
+ "it has been marked as protected!\n"));
+ SAFE_FREE(protect_ids);
+ return false;
+ }
+ }
+ SAFE_FREE(protect_ids);
+
+ /*
+ * use a copy to prevent uninitialized memory from being carried over
+ * to the tdb
+ */
+ sid_copy(&clean_sid, sid);
+
+ ret = secrets_store(domain_sid_keystr(domain),
+ &clean_sid,
+ sizeof(struct dom_sid));
+
+ /* Force a re-query */
+ if (ret) {
+ /*
+ * Do not call get_global_domain_sid() here, or we will call it
+ * recursively.
+ */
+ reset_global_sam_sid();
+ }
+ return ret;
+}
+
+bool secrets_fetch_domain_sid(const char *domain, struct dom_sid *sid)
+{
+ struct dom_sid *dyn_sid;
+ size_t size = 0;
+
+ dyn_sid = (struct dom_sid *)secrets_fetch(domain_sid_keystr(domain), &size);
+
+ if (dyn_sid == NULL)
+ return False;
+
+ if (size != sizeof(struct dom_sid)) {
+ SAFE_FREE(dyn_sid);
+ return False;
+ }
+
+ *sid = *dyn_sid;
+ SAFE_FREE(dyn_sid);
+ return True;
+}
+
+bool secrets_store_domain_guid(const char *domain, const struct GUID *guid)
+{
+ char *protect_ids;
+ const char *key;
+
+ protect_ids = secrets_fetch(protect_ids_keystr(domain), NULL);
+ if (protect_ids) {
+ if (strncmp(protect_ids, "TRUE", 4)) {
+ DEBUG(0, ("Refusing to store a Domain SID, "
+ "it has been marked as protected!\n"));
+ SAFE_FREE(protect_ids);
+ return false;
+ }
+ }
+ SAFE_FREE(protect_ids);
+
+ key = domain_guid_keystr(domain);
+ return secrets_store(key, guid, sizeof(struct GUID));
+}
+
+bool secrets_fetch_domain_guid(const char *domain, struct GUID *guid)
+{
+ struct GUID *dyn_guid;
+ const char *key;
+ size_t size = 0;
+ struct GUID new_guid;
+
+ key = domain_guid_keystr(domain);
+ dyn_guid = (struct GUID *)secrets_fetch(key, &size);
+
+ if (!dyn_guid) {
+ if (lp_server_role() == ROLE_DOMAIN_PDC ||
+ lp_server_role() == ROLE_IPA_DC) {
+ new_guid = GUID_random();
+ if (!secrets_store_domain_guid(domain, &new_guid))
+ return False;
+ dyn_guid = (struct GUID *)secrets_fetch(key, &size);
+ }
+ if (dyn_guid == NULL) {
+ return False;
+ }
+ }
+
+ if (size != sizeof(struct GUID)) {
+ DEBUG(1,("UUID size %d is wrong!\n", (int)size));
+ SAFE_FREE(dyn_guid);
+ return False;
+ }
+
+ *guid = *dyn_guid;
+ SAFE_FREE(dyn_guid);
+ return True;
+}
+
+/**
+ * Form a key for fetching the machine trust account sec channel type
+ *
+ * @param domain domain name
+ *
+ * @return keystring
+ **/
+static const char *machine_sec_channel_type_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_MACHINE_SEC_CHANNEL_TYPE,
+ domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/**
+ * Form a key for fetching the machine trust account last change time
+ *
+ * @param domain domain name
+ *
+ * @return keystring
+ **/
+static const char *machine_last_change_time_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_MACHINE_LAST_CHANGE_TIME,
+ domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+
+/**
+ * Form a key for fetching the machine previous trust account password
+ *
+ * @param domain domain name
+ *
+ * @return keystring
+ **/
+static const char *machine_prev_password_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_MACHINE_PASSWORD_PREV, domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/**
+ * Form a key for fetching the machine trust account password
+ *
+ * @param domain domain name
+ *
+ * @return keystring
+ **/
+static const char *machine_password_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_MACHINE_PASSWORD, domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/**
+ * Form a key for fetching the machine trust account password
+ *
+ * @param domain domain name
+ *
+ * @return stored password's key
+ **/
+static const char *trust_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_MACHINE_ACCT_PASS, domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/************************************************************************
+ Routine to get the default secure channel type for trust accounts
+************************************************************************/
+
+enum netr_SchannelType get_default_sec_channel(void)
+{
+ if (IS_DC) {
+ return SEC_CHAN_BDC;
+ } else {
+ return SEC_CHAN_WKSTA;
+ }
+}
+
+/************************************************************************
+ Routine to get the trust account password for a domain.
+ This only tries to get the legacy hashed version of the password.
+ The user of this function must have locked the trust password file using
+ the above secrets_lock_trust_account_password().
+************************************************************************/
+
+bool secrets_fetch_trust_account_password_legacy(const char *domain,
+ uint8_t ret_pwd[16],
+ time_t *pass_last_set_time,
+ enum netr_SchannelType *channel)
+{
+ struct machine_acct_pass *pass;
+ size_t size = 0;
+
+ if (!(pass = (struct machine_acct_pass *)secrets_fetch(
+ trust_keystr(domain), &size))) {
+ DEBUG(5, ("secrets_fetch failed!\n"));
+ return False;
+ }
+
+ if (size != sizeof(*pass)) {
+ DEBUG(0, ("secrets were of incorrect size!\n"));
+ BURN_FREE(pass, size);
+ return False;
+ }
+
+ if (pass_last_set_time) {
+ *pass_last_set_time = pass->mod_time;
+ }
+ memcpy(ret_pwd, pass->hash, 16);
+
+ if (channel) {
+ *channel = get_default_sec_channel();
+ }
+
+ BURN_FREE(pass, size);
+ return True;
+}
+
+/************************************************************************
+ Routine to delete all information related to the domain joined machine.
+************************************************************************/
+
+bool secrets_delete_machine_password_ex(const char *domain, const char *realm)
+{
+ const char *tmpkey = NULL;
+ bool ok;
+
+ tmpkey = domain_info_keystr(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+
+ if (realm != NULL) {
+ tmpkey = des_salt_key(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ tmpkey = domain_guid_keystr(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+
+ tmpkey = machine_prev_password_keystr(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+
+ tmpkey = machine_password_keystr(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+
+ tmpkey = machine_sec_channel_type_keystr(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+
+ tmpkey = machine_last_change_time_keystr(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+
+ tmpkey = domain_sid_keystr(domain);
+ ok = secrets_delete(tmpkey);
+ if (!ok) {
+ return false;
+ }
+
+ return true;
+}
+
+/************************************************************************
+ Routine to delete the domain sid
+************************************************************************/
+
+bool secrets_delete_domain_sid(const char *domain)
+{
+ return secrets_delete_entry(domain_sid_keystr(domain));
+}
+
+/************************************************************************
+ Set the machine trust account password, the old pw and last change
+ time, domain SID and salting principals based on values passed in
+ (added to support the secrets_tdb_sync module on secrets.ldb)
+************************************************************************/
+
+bool secrets_store_machine_pw_sync(const char *pass, const char *oldpass, const char *domain,
+ const char *realm,
+ const char *salting_principal, uint32_t supported_enc_types,
+ const struct dom_sid *domain_sid, uint32_t last_change_time,
+ uint32_t secure_channel_type,
+ bool delete_join)
+{
+ bool ret;
+ uint8_t last_change_time_store[4];
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint8_t sec_channel_bytes[4];
+
+ if (delete_join) {
+ secrets_delete_machine_password_ex(domain, realm);
+ TALLOC_FREE(frame);
+ return true;
+ }
+
+ ret = secrets_store(machine_password_keystr(domain), pass, strlen(pass)+1);
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if (oldpass) {
+ ret = secrets_store(machine_prev_password_keystr(domain), oldpass, strlen(oldpass)+1);
+ } else {
+ ret = secrets_delete(machine_prev_password_keystr(domain));
+ }
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if (secure_channel_type == 0) {
+ /* We delete this and instead have the read code fall back to
+ * a default based on server role, as our caller can't specify
+ * this with any more certainty */
+ ret = secrets_delete(machine_sec_channel_type_keystr(domain));
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ } else {
+ SIVAL(&sec_channel_bytes, 0, secure_channel_type);
+ ret = secrets_store(machine_sec_channel_type_keystr(domain),
+ &sec_channel_bytes, sizeof(sec_channel_bytes));
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+ }
+
+ SIVAL(&last_change_time_store, 0, last_change_time);
+ ret = secrets_store(machine_last_change_time_keystr(domain),
+ &last_change_time_store, sizeof(last_change_time));
+
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ ret = secrets_store_domain_sid(domain, domain_sid);
+
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return ret;
+ }
+
+ if (realm != NULL) {
+ char *key = des_salt_key(realm);
+
+ if (salting_principal != NULL) {
+ ret = secrets_store(key,
+ salting_principal,
+ strlen(salting_principal)+1);
+ } else {
+ ret = secrets_delete(key);
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/************************************************************************
+ Return the standard DES salt key
+************************************************************************/
+
+char* kerberos_standard_des_salt( void )
+{
+ fstring salt;
+
+ fstr_sprintf( salt, "host/%s.%s@", lp_netbios_name(), lp_realm() );
+ (void)strlower_m( salt );
+ fstrcat( salt, lp_realm() );
+
+ return SMB_STRDUP( salt );
+}
+
+/************************************************************************
+************************************************************************/
+
+static char *des_salt_key(const char *realm)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/DES/%s",
+ SECRETS_SALTING_PRINCIPAL,
+ realm);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/************************************************************************
+************************************************************************/
+
+bool kerberos_secrets_store_des_salt( const char* salt )
+{
+ char* key;
+ bool ret;
+
+ key = des_salt_key(lp_realm());
+ if (key == NULL) {
+ DEBUG(0,("kerberos_secrets_store_des_salt: failed to generate key!\n"));
+ return False;
+ }
+
+ if ( !salt ) {
+ DEBUG(8,("kerberos_secrets_store_des_salt: deleting salt\n"));
+ secrets_delete_entry( key );
+ return True;
+ }
+
+ DEBUG(3,("kerberos_secrets_store_des_salt: Storing salt \"%s\"\n", salt));
+
+ ret = secrets_store( key, salt, strlen(salt)+1 );
+
+ TALLOC_FREE(key);
+
+ return ret;
+}
+
+/************************************************************************
+************************************************************************/
+
+static
+char* kerberos_secrets_fetch_des_salt( void )
+{
+ char *salt, *key;
+
+ key = des_salt_key(lp_realm());
+ if (key == NULL) {
+ DEBUG(0,("kerberos_secrets_fetch_des_salt: failed to generate key!\n"));
+ return NULL;
+ }
+
+ salt = (char*)secrets_fetch( key, NULL );
+
+ TALLOC_FREE(key);
+
+ return salt;
+}
+
+/************************************************************************
+ Routine to get the salting principal for this service.
+ Caller must free if return is not null.
+ ************************************************************************/
+
+char *kerberos_secrets_fetch_salt_princ(void)
+{
+ char *salt_princ_s;
+ /* lookup new key first */
+
+ salt_princ_s = kerberos_secrets_fetch_des_salt();
+ if (salt_princ_s == NULL) {
+ /* fall back to host/machine.realm@REALM */
+ salt_princ_s = kerberos_standard_des_salt();
+ }
+
+ return salt_princ_s;
+}
+
+/************************************************************************
+ Routine to fetch the previous plaintext machine account password for a realm
+ the password is assumed to be a null terminated ascii string.
+************************************************************************/
+
+char *secrets_fetch_prev_machine_password(const char *domain)
+{
+ return (char *)secrets_fetch(machine_prev_password_keystr(domain), NULL);
+}
+
+/************************************************************************
+ Routine to fetch the last change time of the machine account password
+ for a realm
+************************************************************************/
+
+time_t secrets_fetch_pass_last_set_time(const char *domain)
+{
+ uint32_t *last_set_time;
+ time_t pass_last_set_time;
+
+ last_set_time = secrets_fetch(machine_last_change_time_keystr(domain),
+ NULL);
+ if (last_set_time) {
+ pass_last_set_time = IVAL(last_set_time,0);
+ SAFE_FREE(last_set_time);
+ } else {
+ pass_last_set_time = 0;
+ }
+
+ return pass_last_set_time;
+}
+
+/************************************************************************
+ Routine to fetch the plaintext machine account password for a realm
+ the password is assumed to be a null terminated ascii string.
+************************************************************************/
+
+char *secrets_fetch_machine_password(const char *domain,
+ time_t *pass_last_set_time,
+ enum netr_SchannelType *channel)
+{
+ char *ret;
+ ret = (char *)secrets_fetch(machine_password_keystr(domain), NULL);
+
+ if (pass_last_set_time) {
+ *pass_last_set_time = secrets_fetch_pass_last_set_time(domain);
+ }
+
+ if (channel) {
+ size_t size;
+ uint32_t *channel_type;
+ channel_type = (unsigned int *)secrets_fetch(machine_sec_channel_type_keystr(domain), &size);
+ if (channel_type) {
+ *channel = IVAL(channel_type,0);
+ SAFE_FREE(channel_type);
+ } else {
+ *channel = get_default_sec_channel();
+ }
+ }
+
+ return ret;
+}
+
+static int password_nt_hash_destructor(struct secrets_domain_info1_password *pw)
+{
+ ZERO_STRUCT(pw->nt_hash);
+
+ return 0;
+}
+
+static int setup_password_zeroing(struct secrets_domain_info1_password *pw)
+{
+ if (pw != NULL) {
+ size_t i;
+
+ talloc_keep_secret(pw->cleartext_blob.data);
+ talloc_set_destructor(pw, password_nt_hash_destructor);
+ for (i = 0; i < pw->num_keys; i++) {
+ talloc_keep_secret(pw->keys[i].value.data);
+ }
+ }
+
+ return 0;
+}
+
+static char *domain_info_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_MACHINE_DOMAIN_INFO,
+ domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/************************************************************************
+ Routine to get account password to trusted domain
+************************************************************************/
+
+static NTSTATUS secrets_fetch_domain_info1_by_key(const char *key,
+ TALLOC_CTX *mem_ctx,
+ struct secrets_domain_info1 **_info1)
+{
+ struct secrets_domain_infoB sdib = { .version = 0, };
+ enum ndr_err_code ndr_err;
+ /* unpacking structures */
+ DATA_BLOB blob;
+
+ /* fetching trusted domain password structure */
+ blob.data = (uint8_t *)secrets_fetch(key, &blob.length);
+ if (blob.data == NULL) {
+ DBG_NOTICE("secrets_fetch failed!\n");
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* unpack trusted domain password */
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &sdib,
+ (ndr_pull_flags_fn_t)ndr_pull_secrets_domain_infoB);
+ BURN_FREE(blob.data, blob.length);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_pull_struct_blob failed - %s!\n",
+ ndr_errstr(ndr_err));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (sdib.info.info1->next_change != NULL) {
+ setup_password_zeroing(sdib.info.info1->next_change->password);
+ }
+ setup_password_zeroing(sdib.info.info1->password);
+ setup_password_zeroing(sdib.info.info1->old_password);
+ setup_password_zeroing(sdib.info.info1->older_password);
+
+ if (sdib.version != SECRETS_DOMAIN_INFO_VERSION_1) {
+ DBG_ERR("sdib.version = %u\n", (unsigned)sdib.version);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *_info1 = sdib.info.info1;
+ return NT_STATUS_OK;;
+}
+
+static NTSTATUS secrets_fetch_domain_info(const char *domain,
+ TALLOC_CTX *mem_ctx,
+ struct secrets_domain_info1 **pinfo)
+{
+ char *key = domain_info_keystr(domain);
+ return secrets_fetch_domain_info1_by_key(key, mem_ctx, pinfo);
+}
+
+void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info1,
+ const char *name)
+{
+ struct secrets_domain_infoB sdib = {
+ .version = SECRETS_DOMAIN_INFO_VERSION_1,
+ };
+
+ sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
+
+ NDR_PRINT_DEBUG_LEVEL(lvl, secrets_domain_infoB, &sdib);
+}
+
+char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
+ const char *name, bool include_secrets)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct secrets_domain_infoB sdib = {
+ .version = SECRETS_DOMAIN_INFO_VERSION_1,
+ };
+ struct ndr_print *ndr = NULL;
+ char *ret = NULL;
+
+ sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
+
+ ndr = talloc_zero(frame, struct ndr_print);
+ if (ndr == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ ndr->private_data = talloc_strdup(ndr, "");
+ if (ndr->private_data == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ ndr->print = ndr_print_string_helper;
+ ndr->depth = 1;
+ ndr->print_secrets = include_secrets;
+
+ ndr_print_secrets_domain_infoB(ndr, name, &sdib);
+ ret = talloc_steal(mem_ctx, (char *)ndr->private_data);
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static NTSTATUS secrets_store_domain_info1_by_key(const char *key,
+ const struct secrets_domain_info1 *info1)
+{
+ struct secrets_domain_infoB sdib = {
+ .version = SECRETS_DOMAIN_INFO_VERSION_1,
+ };
+ /* packing structures */
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ bool ok;
+
+ sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &sdib,
+ (ndr_push_flags_fn_t)ndr_push_secrets_domain_infoB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ ok = secrets_store(key, blob.data, blob.length);
+ data_blob_clear_free(&blob);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS secrets_store_domain_info(const struct secrets_domain_info1 *info,
+ bool upgrade)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *domain = info->domain_info.name.string;
+ const char *realm = info->domain_info.dns_domain.string;
+ char *key = domain_info_keystr(domain);
+ struct db_context *db = NULL;
+ struct timeval last_change_tv;
+ const DATA_BLOB *cleartext_blob = NULL;
+ DATA_BLOB pw_blob = data_blob_null;
+ DATA_BLOB old_pw_blob = data_blob_null;
+ const char *pw = NULL;
+ const char *old_pw = NULL;
+ bool ok;
+ NTSTATUS status;
+ int ret;
+ int role = lp_server_role();
+
+ switch (info->secure_channel_type) {
+ case SEC_CHAN_WKSTA:
+ case SEC_CHAN_BDC:
+ if (!upgrade && role >= ROLE_ACTIVE_DIRECTORY_DC) {
+ DBG_ERR("AD_DC not supported for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ break;
+ default:
+ DBG_ERR("SEC_CHAN_* not supported for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ db = secrets_db_ctx();
+
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ ok = secrets_clear_domain_protection(domain);
+ if (!ok) {
+ DBG_ERR("secrets_clear_domain_protection(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ ok = secrets_delete_machine_password_ex(domain, realm);
+ if (!ok) {
+ DBG_ERR("secrets_delete_machine_password_ex(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ status = secrets_store_domain_info1_by_key(key, info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_domain_info1_by_key() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We use info->password_last_change instead
+ * of info->password.change_time because
+ * we may want to defer the next change approach
+ * if the server rejected the change the last time,
+ * e.g. due to RefusePasswordChange=1.
+ */
+ nttime_to_timeval(&last_change_tv, info->password_last_change);
+
+ cleartext_blob = &info->password->cleartext_blob;
+ ok = convert_string_talloc(frame, CH_UTF16MUNGED, CH_UNIX,
+ cleartext_blob->data,
+ cleartext_blob->length,
+ (void **)&pw_blob.data,
+ &pw_blob.length);
+ if (!ok) {
+ status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+ "failed for pw of %s - %s\n",
+ domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ pw = (const char *)pw_blob.data;
+ if (info->old_password != NULL) {
+ cleartext_blob = &info->old_password->cleartext_blob;
+ ok = convert_string_talloc(frame, CH_UTF16MUNGED, CH_UNIX,
+ cleartext_blob->data,
+ cleartext_blob->length,
+ (void **)&old_pw_blob.data,
+ &old_pw_blob.length);
+ if (!ok) {
+ status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
+ "failed for old_pw of %s - %s\n",
+ domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ data_blob_clear_free(&pw_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ old_pw = (const char *)old_pw_blob.data;
+ }
+
+ ok = secrets_store_machine_pw_sync(pw, old_pw,
+ domain, realm,
+ info->salt_principal,
+ info->supported_enc_types,
+ info->domain_info.sid,
+ last_change_tv.tv_sec,
+ info->secure_channel_type,
+ false); /* delete_join */
+ data_blob_clear_free(&pw_blob);
+ data_blob_clear_free(&old_pw_blob);
+ if (!ok) {
+ DBG_ERR("secrets_store_machine_pw_sync(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ if (!GUID_all_zero(&info->domain_info.domain_guid)) {
+ ok = secrets_store_domain_guid(domain,
+ &info->domain_info.domain_guid);
+ if (!ok) {
+ DBG_ERR("secrets_store_domain_guid(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ }
+
+ ok = secrets_mark_domain_protected(domain);
+ if (!ok) {
+ DBG_ERR("secrets_mark_domain_protected(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static int secrets_domain_info_kerberos_keys(struct secrets_domain_info1_password *p,
+ const char *salt_principal)
+{
+#ifdef HAVE_ADS
+ krb5_error_code krb5_ret;
+ krb5_context krb5_ctx = NULL;
+ DATA_BLOB cleartext_utf8_b = data_blob_null;
+ krb5_data cleartext_utf8;
+ krb5_data salt;
+ krb5_keyblock key;
+ DATA_BLOB aes_256_b = data_blob_null;
+ DATA_BLOB aes_128_b = data_blob_null;
+ bool ok;
+#endif /* HAVE_ADS */
+ DATA_BLOB arc4_b = data_blob_null;
+ const uint16_t max_keys = 4;
+ struct secrets_domain_info1_kerberos_key *keys = NULL;
+ uint16_t idx = 0;
+ char *salt_data = NULL;
+
+ /*
+ * We calculate:
+ * ENCTYPE_AES256_CTS_HMAC_SHA1_96
+ * ENCTYPE_AES128_CTS_HMAC_SHA1_96
+ * ENCTYPE_ARCFOUR_HMAC
+ * ENCTYPE_DES_CBC_MD5
+ *
+ * We don't include ENCTYPE_DES_CBC_CRC
+ * as W2008R2 also doesn't store it anymore.
+ *
+ * Note we store all enctypes we support,
+ * including the weak encryption types,
+ * but that's no problem as we also
+ * store the cleartext password anyway.
+ *
+ * Which values are then used to construct
+ * a keytab is configured at runtime and the
+ * configuration of msDS-SupportedEncryptionTypes.
+ *
+ * If we don't have kerberos support or no
+ * salt, we only generate an entry for arcfour-hmac-md5.
+ */
+ keys = talloc_zero_array(p,
+ struct secrets_domain_info1_kerberos_key,
+ max_keys);
+ if (keys == NULL) {
+ return ENOMEM;
+ }
+
+ arc4_b = data_blob_talloc(keys,
+ p->nt_hash.hash,
+ sizeof(p->nt_hash.hash));
+ if (arc4_b.data == NULL) {
+ DBG_ERR("data_blob_talloc failed for arcfour-hmac-md5.\n");
+ TALLOC_FREE(keys);
+ return ENOMEM;
+ }
+ talloc_keep_secret(arc4_b.data);
+
+#ifdef HAVE_ADS
+ if (salt_principal == NULL) {
+ goto no_kerberos;
+ }
+
+ krb5_ret = smb_krb5_init_context_common(&krb5_ctx);
+ if (krb5_ret != 0) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(krb5_ret));
+ TALLOC_FREE(keys);
+ return krb5_ret;
+ }
+
+ krb5_ret = smb_krb5_salt_principal2data(krb5_ctx, salt_principal,
+ p, &salt_data);
+ if (krb5_ret != 0) {
+ DBG_ERR("smb_krb5_salt_principal2data(%s) failed: %s\n",
+ salt_principal,
+ smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
+ krb5_free_context(krb5_ctx);
+ TALLOC_FREE(keys);
+ return krb5_ret;
+ }
+
+ salt = (krb5_data) {
+ .data = discard_const(salt_data),
+ .length = strlen(salt_data),
+ };
+
+ ok = convert_string_talloc(keys, CH_UTF16MUNGED, CH_UTF8,
+ p->cleartext_blob.data,
+ p->cleartext_blob.length,
+ (void **)&cleartext_utf8_b.data,
+ &cleartext_utf8_b.length);
+ if (!ok) {
+ if (errno != 0) {
+ krb5_ret = errno;
+ } else {
+ krb5_ret = EINVAL;
+ }
+ krb5_free_context(krb5_ctx);
+ TALLOC_FREE(keys);
+ return krb5_ret;
+ }
+ talloc_keep_secret(cleartext_utf8_b.data);
+ cleartext_utf8.data = (void *)cleartext_utf8_b.data;
+ cleartext_utf8.length = cleartext_utf8_b.length;
+
+ krb5_ret = smb_krb5_create_key_from_string(krb5_ctx,
+ NULL,
+ &salt,
+ &cleartext_utf8,
+ ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+ &key);
+ if (krb5_ret != 0) {
+ DBG_ERR("generation of a aes256-cts-hmac-sha1-96 key failed: %s\n",
+ smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
+ krb5_free_context(krb5_ctx);
+ TALLOC_FREE(keys);
+ TALLOC_FREE(salt_data);
+ return krb5_ret;
+ }
+ aes_256_b = data_blob_talloc(keys,
+ KRB5_KEY_DATA(&key),
+ KRB5_KEY_LENGTH(&key));
+ krb5_free_keyblock_contents(krb5_ctx, &key);
+ if (aes_256_b.data == NULL) {
+ DBG_ERR("data_blob_talloc failed for aes-256.\n");
+ krb5_free_context(krb5_ctx);
+ TALLOC_FREE(keys);
+ TALLOC_FREE(salt_data);
+ return ENOMEM;
+ }
+ talloc_keep_secret(aes_256_b.data);
+
+ krb5_ret = smb_krb5_create_key_from_string(krb5_ctx,
+ NULL,
+ &salt,
+ &cleartext_utf8,
+ ENCTYPE_AES128_CTS_HMAC_SHA1_96,
+ &key);
+ if (krb5_ret != 0) {
+ DBG_ERR("generation of a aes128-cts-hmac-sha1-96 key failed: %s\n",
+ smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
+ krb5_free_context(krb5_ctx);
+ TALLOC_FREE(keys);
+ TALLOC_FREE(salt_data);
+ return krb5_ret;
+ }
+ aes_128_b = data_blob_talloc(keys,
+ KRB5_KEY_DATA(&key),
+ KRB5_KEY_LENGTH(&key));
+ krb5_free_keyblock_contents(krb5_ctx, &key);
+ if (aes_128_b.data == NULL) {
+ DBG_ERR("data_blob_talloc failed for aes-128.\n");
+ krb5_free_context(krb5_ctx);
+ TALLOC_FREE(keys);
+ TALLOC_FREE(salt_data);
+ return ENOMEM;
+ }
+ talloc_keep_secret(aes_128_b.data);
+
+ krb5_free_context(krb5_ctx);
+no_kerberos:
+
+ if (aes_256_b.length != 0) {
+ keys[idx].keytype = ENCTYPE_AES256_CTS_HMAC_SHA1_96;
+ keys[idx].iteration_count = 4096;
+ keys[idx].value = aes_256_b;
+ idx += 1;
+ }
+
+ if (aes_128_b.length != 0) {
+ keys[idx].keytype = ENCTYPE_AES128_CTS_HMAC_SHA1_96;
+ keys[idx].iteration_count = 4096;
+ keys[idx].value = aes_128_b;
+ idx += 1;
+ }
+
+#endif /* HAVE_ADS */
+
+ keys[idx].keytype = ENCTYPE_ARCFOUR_HMAC;
+ keys[idx].iteration_count = 4096;
+ keys[idx].value = arc4_b;
+ idx += 1;
+
+ p->salt_data = salt_data;
+ p->default_iteration_count = 4096;
+ p->num_keys = idx;
+ p->keys = keys;
+ return 0;
+}
+
+static NTSTATUS secrets_domain_info_password_create(TALLOC_CTX *mem_ctx,
+ const char *cleartext_unix,
+ const char *salt_principal,
+ NTTIME change_time,
+ const char *change_server,
+ struct secrets_domain_info1_password **_p)
+{
+ struct secrets_domain_info1_password *p = NULL;
+ bool ok;
+ size_t len;
+ int ret;
+
+ if (change_server == NULL) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ p = talloc_zero(mem_ctx, struct secrets_domain_info1_password);
+ if (p == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p->change_time = change_time;
+ p->change_server = talloc_strdup(p, change_server);
+ if (p->change_server == NULL) {
+ TALLOC_FREE(p);
+ return NT_STATUS_NO_MEMORY;
+ }
+ len = strlen(cleartext_unix);
+ ok = convert_string_talloc(p, CH_UNIX, CH_UTF16,
+ cleartext_unix, len,
+ (void **)&p->cleartext_blob.data,
+ &p->cleartext_blob.length);
+ if (!ok) {
+ NTSTATUS status = NT_STATUS_UNMAPPABLE_CHARACTER;
+ if (errno == ENOMEM) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(p);
+ return status;
+ }
+ talloc_keep_secret(p->cleartext_blob.data);
+ mdfour(p->nt_hash.hash,
+ p->cleartext_blob.data,
+ p->cleartext_blob.length);
+
+ talloc_set_destructor(p, password_nt_hash_destructor);
+ ret = secrets_domain_info_kerberos_keys(p, salt_principal);
+ if (ret != 0) {
+ NTSTATUS status = krb5_to_nt_status(ret);
+ TALLOC_FREE(p);
+ return status;
+ }
+
+ *_p = p;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
+ TALLOC_CTX *mem_ctx,
+ struct secrets_domain_info1 **pinfo)
+{
+ TALLOC_CTX *frame = NULL;
+ struct secrets_domain_info1 *old = NULL;
+ struct secrets_domain_info1 *info = NULL;
+ const char *dns_domain = NULL;
+ const char *server = NULL;
+ struct db_context *db = NULL;
+ time_t last_set_time;
+ NTTIME last_set_nt;
+ enum netr_SchannelType channel;
+ char *pw = NULL;
+ char *old_pw = NULL;
+ struct dom_sid domain_sid;
+ struct GUID domain_guid;
+ bool ok;
+ NTSTATUS status;
+ int ret;
+
+ ok = strequal(domain, lp_workgroup());
+ if (ok) {
+ dns_domain = lp_dnsdomain();
+
+ if (dns_domain != NULL && dns_domain[0] == '\0') {
+ dns_domain = NULL;
+ }
+ }
+
+ last_set_time = secrets_fetch_pass_last_set_time(domain);
+ if (last_set_time == 0) {
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ unix_to_nt_time(&last_set_nt, last_set_time);
+
+ frame = talloc_stackframe();
+
+ status = secrets_fetch_domain_info(domain, frame, &old);
+ if (NT_STATUS_IS_OK(status)) {
+ if (old->password_last_change >= last_set_nt) {
+ *pinfo = talloc_move(mem_ctx, &old);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+ TALLOC_FREE(old);
+ }
+
+ info = talloc_zero(frame, struct secrets_domain_info1);
+ if (info == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db = secrets_db_ctx();
+
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ pw = secrets_fetch_machine_password(domain,
+ &last_set_time,
+ &channel);
+ if (pw == NULL) {
+ DBG_ERR("secrets_fetch_machine_password(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ unix_to_nt_time(&last_set_nt, last_set_time);
+
+ old_pw = secrets_fetch_prev_machine_password(domain);
+
+ ok = secrets_fetch_domain_sid(domain, &domain_sid);
+ if (!ok) {
+ DBG_ERR("secrets_fetch_domain_sid(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ BURN_FREE_STR(old_pw);
+ BURN_FREE_STR(pw);
+ TALLOC_FREE(frame);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ ok = secrets_fetch_domain_guid(domain, &domain_guid);
+ if (!ok) {
+ domain_guid = GUID_zero();
+ }
+
+ info->computer_name = lp_netbios_name();
+ info->account_name = talloc_asprintf(frame, "%s$", info->computer_name);
+ if (info->account_name == NULL) {
+ DBG_ERR("talloc_asprintf(%s$) failed\n", info->computer_name);
+ dbwrap_transaction_cancel(db);
+ BURN_FREE_STR(old_pw);
+ BURN_FREE_STR(pw);
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ info->secure_channel_type = channel;
+
+ info->domain_info.name.string = domain;
+ info->domain_info.dns_domain.string = dns_domain;
+ info->domain_info.dns_forest.string = dns_domain;
+ info->domain_info.domain_guid = domain_guid;
+ info->domain_info.sid = &domain_sid;
+
+ info->trust_flags = NETR_TRUST_FLAG_PRIMARY;
+ info->trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
+
+ if (dns_domain != NULL) {
+ /*
+ * We just assume all AD domains are
+ * NETR_TRUST_FLAG_NATIVE these days.
+ *
+ * This isn't used anyway for now.
+ */
+ info->trust_flags |= NETR_TRUST_FLAG_NATIVE;
+
+ info->trust_type = LSA_TRUST_TYPE_UPLEVEL;
+
+ server = info->domain_info.dns_domain.string;
+ } else {
+ info->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+
+ server = talloc_asprintf(info,
+ "%s#%02X",
+ domain,
+ NBT_NAME_PDC);
+ if (server == NULL) {
+ DBG_ERR("talloc_asprintf(%s#%02X) failed\n",
+ domain, NBT_NAME_PDC);
+ dbwrap_transaction_cancel(db);
+ BURN_FREE_STR(pw);
+ BURN_FREE_STR(old_pw);
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ info->trust_attributes = LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL;
+
+ info->join_time = 0;
+
+ /*
+ * We don't have enough information about the configured
+ * enctypes.
+ */
+ info->supported_enc_types = 0;
+ info->salt_principal = NULL;
+ if (info->trust_type == LSA_TRUST_TYPE_UPLEVEL) {
+ char *p = NULL;
+
+ p = kerberos_secrets_fetch_salt_princ();
+ if (p == NULL) {
+ dbwrap_transaction_cancel(db);
+ BURN_FREE_STR(old_pw);
+ BURN_FREE_STR(pw);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ info->salt_principal = talloc_strdup(info, p);
+ SAFE_FREE(p);
+ if (info->salt_principal == NULL) {
+ dbwrap_transaction_cancel(db);
+ BURN_FREE_STR(pw);
+ BURN_FREE_STR(old_pw);
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ info->password_last_change = last_set_nt;
+ info->password_changes = 1;
+ info->next_change = NULL;
+
+ status = secrets_domain_info_password_create(info,
+ pw,
+ info->salt_principal,
+ last_set_nt, server,
+ &info->password);
+ BURN_FREE_STR(pw);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_domain_info_password_create(pw) failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ BURN_FREE_STR(old_pw);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * After a join we don't have old passwords.
+ */
+ if (old_pw != NULL) {
+ status = secrets_domain_info_password_create(info,
+ old_pw,
+ info->salt_principal,
+ 0, server,
+ &info->old_password);
+ BURN_FREE_STR(old_pw);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_domain_info_password_create(old) failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ info->password_changes += 1;
+ } else {
+ info->old_password = NULL;
+ }
+ info->older_password = NULL;
+
+ secrets_debug_domain_info(DBGLVL_INFO, info, "upgrade");
+
+ status = secrets_store_domain_info(info, true /* upgrade */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_domain_info() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We now reparse it.
+ */
+ status = secrets_fetch_domain_info(domain, frame, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_fetch_domain_info() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ *pinfo = talloc_move(mem_ctx, &info);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_store_JoinCtx(const struct libnet_JoinCtx *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct secrets_domain_info1 *old = NULL;
+ struct secrets_domain_info1 *info = NULL;
+ struct db_context *db = NULL;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+ const char *domain = r->out.netbios_domain_name;
+ NTSTATUS status;
+ int ret;
+
+ info = talloc_zero(frame, struct secrets_domain_info1);
+ if (info == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->computer_name = r->in.machine_name;
+ info->account_name = r->out.account_name;
+ info->secure_channel_type = r->in.secure_channel_type;
+
+ info->domain_info.name.string =
+ r->out.netbios_domain_name;
+ info->domain_info.dns_domain.string =
+ r->out.dns_domain_name;
+ info->domain_info.dns_forest.string =
+ r->out.forest_name;
+ info->domain_info.domain_guid = r->out.domain_guid;
+ info->domain_info.sid = r->out.domain_sid;
+
+ info->trust_flags = NETR_TRUST_FLAG_PRIMARY;
+ info->trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
+ if (r->out.domain_is_ad) {
+ /*
+ * We just assume all AD domains are
+ * NETR_TRUST_FLAG_NATIVE these days.
+ *
+ * This isn't used anyway for now.
+ */
+ info->trust_flags |= NETR_TRUST_FLAG_NATIVE;
+
+ info->trust_type = LSA_TRUST_TYPE_UPLEVEL;
+ } else {
+ info->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+ }
+ info->trust_attributes = LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL;
+
+ info->join_time = now;
+
+ info->supported_enc_types = r->out.set_encryption_types;
+ info->salt_principal = r->out.krb5_salt;
+
+ if (info->salt_principal == NULL && r->out.domain_is_ad) {
+ char *p = NULL;
+
+ ret = smb_krb5_salt_principal_str(info->domain_info.dns_domain.string,
+ info->account_name,
+ NULL /* userPrincipalName */,
+ UF_WORKSTATION_TRUST_ACCOUNT,
+ info, &p);
+ if (ret != 0) {
+ status = krb5_to_nt_status(ret);
+ DBG_ERR("smb_krb5_salt_principal() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+ info->salt_principal = p;
+ }
+
+ info->password_last_change = now;
+ info->password_changes = 1;
+ info->next_change = NULL;
+
+ status = secrets_domain_info_password_create(info,
+ r->in.machine_password,
+ info->salt_principal,
+ now, r->in.dc_name,
+ &info->password);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_domain_info_password_create(pw) failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ db = secrets_db_ctx();
+
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = secrets_fetch_or_upgrade_domain_info(domain, frame, &old);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
+ DBG_DEBUG("no old join for domain(%s) available\n",
+ domain);
+ old = NULL;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_fetch_or_upgrade_domain_info(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We reuse values from an old join, so that
+ * we still accept already granted kerberos tickets.
+ */
+ if (old != NULL) {
+ info->old_password = old->password;
+ info->older_password = old->old_password;
+ }
+
+ secrets_debug_domain_info(DBGLVL_INFO, info, "join");
+
+ status = secrets_store_domain_info(info, false /* upgrade */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_domain_info() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
+ const char *cleartext_unix,
+ TALLOC_CTX *mem_ctx,
+ struct secrets_domain_info1 **pinfo,
+ struct secrets_domain_info1_change **pprev)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct db_context *db = NULL;
+ struct secrets_domain_info1 *info = NULL;
+ struct secrets_domain_info1_change *prev = NULL;
+ struct secrets_domain_info1_change *next = NULL;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+ NTSTATUS status;
+ int ret;
+
+ db = secrets_db_ctx();
+
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = secrets_fetch_or_upgrade_domain_info(domain, frame, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_fetch_or_upgrade_domain_info(%s) failed\n",
+ domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ prev = info->next_change;
+ info->next_change = NULL;
+
+ next = talloc_zero(frame, struct secrets_domain_info1_change);
+ if (next == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (prev != NULL) {
+ *next = *prev;
+ } else {
+ status = secrets_domain_info_password_create(next,
+ cleartext_unix,
+ info->salt_principal,
+ now, dcname,
+ &next->password);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_domain_info_password_create(next) failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ next->local_status = NT_STATUS_OK;
+ next->remote_status = NT_STATUS_NOT_COMMITTED;
+ next->change_time = now;
+ next->change_server = dcname;
+
+ info->next_change = next;
+
+ secrets_debug_domain_info(DBGLVL_INFO, info, "prepare_change");
+
+ status = secrets_store_domain_info(info, false /* upgrade */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_domain_info() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We now reparse it.
+ */
+ status = secrets_fetch_domain_info(domain, frame, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_fetch_domain_info(%s) failed\n", domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ *pinfo = talloc_move(mem_ctx, &info);
+ if (prev != NULL) {
+ *pprev = talloc_move(mem_ctx, &prev);
+ } else {
+ *pprev = NULL;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS secrets_check_password_change(const struct secrets_domain_info1 *cookie,
+ TALLOC_CTX *mem_ctx,
+ struct secrets_domain_info1 **pstored)
+{
+ const char *domain = cookie->domain_info.name.string;
+ struct secrets_domain_info1 *stored = NULL;
+ struct secrets_domain_info1_change *sn = NULL;
+ struct secrets_domain_info1_change *cn = NULL;
+ NTSTATUS status;
+ bool cmp;
+
+ if (cookie->next_change == NULL) {
+ DBG_ERR("cookie->next_change == NULL for %s.\n", domain);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (cookie->next_change->password == NULL) {
+ DBG_ERR("cookie->next_change->password == NULL for %s.\n", domain);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (cookie->password == NULL) {
+ DBG_ERR("cookie->password == NULL for %s.\n", domain);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Here we check that the given structure still contains the
+ * same secrets_domain_info1_change as currently stored.
+ *
+ * There's always a gap between secrets_prepare_password_change()
+ * and the callers of secrets_check_password_change().
+ */
+
+ status = secrets_fetch_domain_info(domain, mem_ctx, &stored);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_fetch_domain_info(%s) failed\n", domain);
+ return status;
+ }
+
+ if (stored->next_change == NULL) {
+ /*
+ * We hit a race..., the administrator
+ * rejoined or something similar happened.
+ */
+ DBG_ERR("stored->next_change == NULL for %s.\n", domain);
+ TALLOC_FREE(stored);
+ return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+ }
+
+ if (stored->password_last_change != cookie->password_last_change) {
+ struct timeval store_tv;
+ struct timeval_buf store_buf;
+ struct timeval cookie_tv;
+ struct timeval_buf cookie_buf;
+
+ nttime_to_timeval(&store_tv, stored->password_last_change);
+ nttime_to_timeval(&cookie_tv, cookie->password_last_change);
+
+ DBG_ERR("password_last_change differs %s != %s for %s.\n",
+ timeval_str_buf(&store_tv, false, false, &store_buf),
+ timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+ domain);
+ TALLOC_FREE(stored);
+ return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+ }
+
+ sn = stored->next_change;
+ cn = cookie->next_change;
+
+ if (sn->change_time != cn->change_time) {
+ struct timeval store_tv;
+ struct timeval_buf store_buf;
+ struct timeval cookie_tv;
+ struct timeval_buf cookie_buf;
+
+ nttime_to_timeval(&store_tv, sn->change_time);
+ nttime_to_timeval(&cookie_tv, cn->change_time);
+
+ DBG_ERR("next change_time differs %s != %s for %s.\n",
+ timeval_str_buf(&store_tv, false, false, &store_buf),
+ timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+ domain);
+ TALLOC_FREE(stored);
+ return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+ }
+
+ if (sn->password->change_time != cn->password->change_time) {
+ struct timeval store_tv;
+ struct timeval_buf store_buf;
+ struct timeval cookie_tv;
+ struct timeval_buf cookie_buf;
+
+ nttime_to_timeval(&store_tv, sn->password->change_time);
+ nttime_to_timeval(&cookie_tv, cn->password->change_time);
+
+ DBG_ERR("next password.change_time differs %s != %s for %s.\n",
+ timeval_str_buf(&store_tv, false, false, &store_buf),
+ timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
+ domain);
+ TALLOC_FREE(stored);
+ return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+ }
+
+ cmp = mem_equal_const_time(sn->password->nt_hash.hash,
+ cn->password->nt_hash.hash,
+ 16);
+ if (!cmp) {
+ DBG_ERR("next password.nt_hash differs for %s.\n",
+ domain);
+ TALLOC_FREE(stored);
+ return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+ }
+
+ cmp = mem_equal_const_time(stored->password->nt_hash.hash,
+ cookie->password->nt_hash.hash,
+ 16);
+ if (!cmp) {
+ DBG_ERR("password.nt_hash differs for %s.\n",
+ domain);
+ TALLOC_FREE(stored);
+ return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
+ }
+
+ *pstored = stored;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS secrets_abort_password_change(const char *change_server,
+ NTSTATUS local_status,
+ NTSTATUS remote_status,
+ const struct secrets_domain_info1 *cookie,
+ bool defer)
+{
+ const char *domain = cookie->domain_info.name.string;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct db_context *db = NULL;
+ struct secrets_domain_info1 *info = NULL;
+ const char *reason = defer ? "defer_change" : "failed_change";
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+ NTSTATUS status;
+ int ret;
+
+ db = secrets_db_ctx();
+
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ /*
+ * secrets_check_password_change()
+ * checks that cookie->next_change
+ * is valid and the same as store
+ * in the database.
+ */
+ status = secrets_check_password_change(cookie, frame, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_check_password_change(%s) failed\n", domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * Remember the last server and error.
+ */
+ info->next_change->change_server = change_server;
+ info->next_change->change_time = now;
+ info->next_change->local_status = local_status;
+ info->next_change->remote_status = remote_status;
+
+ /*
+ * Make sure the next automatic change is deferred.
+ */
+ if (defer) {
+ info->password_last_change = now;
+ }
+
+ secrets_debug_domain_info(DBGLVL_WARNING, info, reason);
+
+ status = secrets_store_domain_info(info, false /* upgrade */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_domain_info() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS secrets_failed_password_change(const char *change_server,
+ NTSTATUS local_status,
+ NTSTATUS remote_status,
+ const struct secrets_domain_info1 *cookie)
+{
+ static const bool defer = false;
+ return secrets_abort_password_change(change_server,
+ local_status,
+ remote_status,
+ cookie, defer);
+}
+
+NTSTATUS secrets_defer_password_change(const char *change_server,
+ NTSTATUS local_status,
+ NTSTATUS remote_status,
+ const struct secrets_domain_info1 *cookie)
+{
+ static const bool defer = true;
+ return secrets_abort_password_change(change_server,
+ local_status,
+ remote_status,
+ cookie, defer);
+}
+
+NTSTATUS secrets_finish_password_change(const char *change_server,
+ NTTIME change_time,
+ const struct secrets_domain_info1 *cookie)
+{
+ const char *domain = cookie->domain_info.name.string;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct db_context *db = NULL;
+ struct secrets_domain_info1 *info = NULL;
+ struct secrets_domain_info1_change *nc = NULL;
+ NTSTATUS status;
+ int ret;
+
+ db = secrets_db_ctx();
+
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_start() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ /*
+ * secrets_check_password_change() checks that cookie->next_change is
+ * valid and the same as store in the database.
+ */
+ status = secrets_check_password_change(cookie, frame, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_check_password_change(%s) failed\n", domain);
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ nc = info->next_change;
+
+ nc->password->change_server = change_server;
+ nc->password->change_time = change_time;
+
+ info->password_last_change = change_time;
+ info->password_changes += 1;
+ info->next_change = NULL;
+
+ info->older_password = info->old_password;
+ info->old_password = info->password;
+ info->password = nc->password;
+
+ secrets_debug_domain_info(DBGLVL_WARNING, info, "finish_change");
+
+ status = secrets_store_domain_info(info, false /* upgrade */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("secrets_store_domain_info() failed "
+ "for %s - %s\n", domain, nt_errstr(status));
+ dbwrap_transaction_cancel(db);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
+ domain);
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
diff --git a/source3/passdb/machine_sid.c b/source3/passdb/machine_sid.c
new file mode 100644
index 0000000..fa420d8
--- /dev/null
+++ b/source3/passdb/machine_sid.c
@@ -0,0 +1,251 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Jeremy Allison 1996-2002
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Gerald (Jerry) Carter 2000
+ Copyright (C) Stefan (metze) Metzmacher 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "passdb/machine_sid.h"
+#include "secrets.h"
+#include "dbwrap/dbwrap.h"
+#include "../libcli/security/security.h"
+
+/* NOTE! the global_sam_sid is the SID of our local SAM. This is only
+ equal to the domain SID when we are a DC, otherwise its our
+ workstation SID */
+static struct dom_sid *global_sam_sid=NULL;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+/****************************************************************************
+ Read a SID from a file. This is for compatibility with the old MACHINE.SID
+ style of SID storage
+****************************************************************************/
+
+static bool read_sid_from_file(const char *fname, struct dom_sid *sid)
+{
+ char **lines;
+ int numlines;
+ bool ret;
+
+ lines = file_lines_load(fname, &numlines,0, NULL);
+
+ if (!lines || numlines < 1) {
+ TALLOC_FREE(lines);
+ return False;
+ }
+
+ ret = string_to_sid(sid, lines[0]);
+ TALLOC_FREE(lines);
+ return ret;
+}
+
+/*
+ generate a random sid - used to build our own sid if we don't have one
+*/
+static void generate_random_sid(struct dom_sid *sid)
+{
+ int i;
+ uchar raw_sid_data[12];
+
+ *sid = (struct dom_sid) {
+ .sid_rev_num = 1,
+ .id_auth[5] = 5,
+ };
+
+ sid->sub_auths[sid->num_auths++] = 21;
+
+ generate_random_buffer(raw_sid_data, 12);
+ for (i = 0; i < 3; i++)
+ sid->sub_auths[sid->num_auths++] = IVAL(raw_sid_data, i*4);
+}
+
+/****************************************************************************
+ Generate the global machine sid.
+****************************************************************************/
+
+static struct dom_sid *pdb_generate_sam_sid(void)
+{
+ struct dom_sid domain_sid;
+ char *fname = NULL;
+ struct dom_sid *sam_sid;
+
+ if(!(sam_sid=SMB_MALLOC_P(struct dom_sid)))
+ return NULL;
+
+ if ( IS_DC ) {
+ if (secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) {
+ sid_copy(sam_sid, &domain_sid);
+ return sam_sid;
+ }
+ }
+
+ if (secrets_fetch_domain_sid(lp_netbios_name(), sam_sid)) {
+
+ /* We got our sid. If not a pdc/bdc, we're done. */
+ if ( !IS_DC )
+ return sam_sid;
+
+ if (!secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) {
+
+ /* No domain sid and we're a pdc/bdc. Store it */
+
+ if (!secrets_store_domain_sid(lp_workgroup(), sam_sid)) {
+ DEBUG(0,("pdb_generate_sam_sid: Can't store domain SID as a pdc/bdc.\n"));
+ SAFE_FREE(sam_sid);
+ return NULL;
+ }
+ return sam_sid;
+ }
+
+ if (!dom_sid_equal(&domain_sid, sam_sid)) {
+
+ /* Domain name sid doesn't match global sam sid. Re-store domain sid as 'local' sid. */
+
+ DEBUG(0,("pdb_generate_sam_sid: Mismatched SIDs as a pdc/bdc.\n"));
+ if (!secrets_store_domain_sid(lp_netbios_name(), &domain_sid)) {
+ DEBUG(0,("pdb_generate_sam_sid: Can't re-store domain SID for local sid as PDC/BDC.\n"));
+ SAFE_FREE(sam_sid);
+ return NULL;
+ }
+ return sam_sid;
+ }
+
+ return sam_sid;
+ }
+
+ /* check for an old MACHINE.SID file for backwards compatibility */
+ if (asprintf(&fname, "%s/MACHINE.SID", lp_private_dir()) == -1) {
+ SAFE_FREE(sam_sid);
+ return NULL;
+ }
+
+ if (read_sid_from_file(fname, sam_sid)) {
+ /* remember it for future reference and unlink the old MACHINE.SID */
+ if (!secrets_store_domain_sid(lp_netbios_name(), sam_sid)) {
+ DEBUG(0,("pdb_generate_sam_sid: Failed to store SID from file.\n"));
+ SAFE_FREE(fname);
+ SAFE_FREE(sam_sid);
+ return NULL;
+ }
+ unlink(fname);
+ if ( !IS_DC ) {
+ if (!secrets_store_domain_sid(lp_workgroup(), sam_sid)) {
+ DEBUG(0,("pdb_generate_sam_sid: Failed to store domain SID from file.\n"));
+ SAFE_FREE(fname);
+ SAFE_FREE(sam_sid);
+ return NULL;
+ }
+ }
+
+ /* Stored the old sid from MACHINE.SID successfully.*/
+ SAFE_FREE(fname);
+ return sam_sid;
+ }
+
+ SAFE_FREE(fname);
+
+ /* we don't have the SID in secrets.tdb, we will need to
+ generate one and save it */
+ generate_random_sid(sam_sid);
+
+ if (!secrets_store_domain_sid(lp_netbios_name(), sam_sid)) {
+ DEBUG(0,("pdb_generate_sam_sid: Failed to store generated machine SID.\n"));
+ SAFE_FREE(sam_sid);
+ return NULL;
+ }
+ if ( IS_DC ) {
+ if (!secrets_store_domain_sid(lp_workgroup(), sam_sid)) {
+ DEBUG(0,("pdb_generate_sam_sid: Failed to store generated domain SID.\n"));
+ SAFE_FREE(sam_sid);
+ return NULL;
+ }
+ }
+
+ return sam_sid;
+}
+
+/* return our global_sam_sid */
+struct dom_sid *get_global_sam_sid(void)
+{
+ struct db_context *db;
+
+ if (global_sam_sid != NULL)
+ return global_sam_sid;
+
+ /*
+ * memory for global_sam_sid is allocated in
+ * pdb_generate_sam_sid() as needed
+ *
+ * Note: this is guarded by a transaction
+ * to prevent races on startup which
+ * can happen with some dbwrap backends
+ */
+
+ db = secrets_db_ctx();
+ if (!db) {
+ smb_panic("could not open secrets db");
+ }
+
+ if (dbwrap_transaction_start(db) != 0) {
+ smb_panic("could not start transaction on secrets db");
+ }
+
+ if (!(global_sam_sid = pdb_generate_sam_sid())) {
+ dbwrap_transaction_cancel(db);
+ smb_panic("could not generate a machine SID");
+ }
+
+ if (dbwrap_transaction_commit(db) != 0) {
+ smb_panic("could not start commit secrets db");
+ }
+
+ return global_sam_sid;
+}
+
+/**
+ * Force get_global_sam_sid to requery the backends
+ */
+void reset_global_sam_sid(void)
+{
+ SAFE_FREE(global_sam_sid);
+}
+
+/*****************************************************************
+ Check if the SID is our sam SID (S-1-5-21-x-y-z).
+*****************************************************************/
+
+bool sid_check_is_our_sam(const struct dom_sid *sid)
+{
+ return dom_sid_equal(sid, get_global_sam_sid());
+}
+
+/*****************************************************************
+ Check if the SID is our domain SID (S-1-5-21-x-y-z).
+*****************************************************************/
+
+bool sid_check_is_in_our_sam(const struct dom_sid *sid)
+{
+ struct dom_sid dom_sid;
+
+ sid_copy(&dom_sid, sid);
+ sid_split_rid(&dom_sid, NULL);
+ return sid_check_is_our_sam(&dom_sid);
+}
diff --git a/source3/passdb/machine_sid.h b/source3/passdb/machine_sid.h
new file mode 100644
index 0000000..33dce25
--- /dev/null
+++ b/source3/passdb/machine_sid.h
@@ -0,0 +1,33 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Password and authentication handling
+ * Copyright (C) Jeremy Allison 1996-2002
+ * Copyright (C) Andrew Tridgell 2002
+ * Copyright (C) Gerald (Jerry) Carter 2000
+ * Copyright (C) Stefan (metze) Metzmacher 2002
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* The following definitions come from passdb/machine_sid.c */
+
+#ifndef _PASSDB_MACHINE_SID_H_
+#define _PASSDB_MACHINE_SID_H_
+
+struct dom_sid *get_global_sam_sid(void);
+void reset_global_sam_sid(void) ;
+bool sid_check_is_our_sam(const struct dom_sid *sid);
+bool sid_check_is_in_our_sam(const struct dom_sid *sid);
+
+#endif /* _PASSDB_MACHINE_SID_H_ */
diff --git a/source3/passdb/passdb.c b/source3/passdb/passdb.c
new file mode 100644
index 0000000..4bc48f6
--- /dev/null
+++ b/source3/passdb/passdb.c
@@ -0,0 +1,2755 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Jeremy Allison 1996-2001
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Gerald (Jerry) Carter 2000-2006
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Simo Sorce 2003
+ Copyright (C) Volker Lendecke 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "passdb.h"
+#include "system/passwd.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "secrets.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/util_pw.h"
+#include "util_tdb.h"
+#include "auth/credentials/credentials.h"
+#include "lib/param/param.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+/**********************************************************************
+***********************************************************************/
+
+static int samu_destroy(struct samu *user)
+{
+ data_blob_clear_free( &user->lm_pw );
+ data_blob_clear_free( &user->nt_pw );
+
+ if ( user->plaintext_pw )
+ BURN_STR(user->plaintext_pw);
+
+ return 0;
+}
+
+/**********************************************************************
+ generate a new struct samuser
+***********************************************************************/
+
+struct samu *samu_new( TALLOC_CTX *ctx )
+{
+ struct samu *user;
+
+ if ( !(user = talloc_zero( ctx, struct samu )) ) {
+ DEBUG(0,("samuser_new: Talloc failed!\n"));
+ return NULL;
+ }
+
+ talloc_set_destructor( user, samu_destroy );
+
+ /* no initial methods */
+
+ user->methods = NULL;
+
+ /* Don't change these timestamp settings without a good reason.
+ They are important for NT member server compatibility. */
+
+ user->logon_time = (time_t)0;
+ user->pass_last_set_time = (time_t)0;
+ user->pass_can_change_time = (time_t)0;
+ user->logoff_time = get_time_t_max();
+ user->kickoff_time = get_time_t_max();
+ user->fields_present = 0x00ffffff;
+ user->logon_divs = 168; /* hours per week */
+ user->hours_len = 21; /* 21 times 8 bits = 168 */
+ memset(user->hours, 0xff, user->hours_len); /* available at all hours */
+ user->bad_password_count = 0;
+ user->logon_count = 0;
+ user->unknown_6 = 0x000004ec; /* don't know */
+
+ /* Some parts of samba strlen their pdb_get...() returns,
+ so this keeps the interface unchanged for now. */
+
+ user->username = "";
+ user->domain = "";
+ user->nt_username = "";
+ user->full_name = "";
+ user->home_dir = "";
+ user->logon_script = "";
+ user->profile_path = "";
+ user->acct_desc = "";
+ user->workstations = "";
+ user->comment = "";
+ user->munged_dial = "";
+
+ user->plaintext_pw = NULL;
+
+ /* Unless we know otherwise have a Account Control Bit
+ value of 'normal user'. This helps User Manager, which
+ asks for a filtered list of users. */
+
+ user->acct_ctrl = ACB_NORMAL;
+
+ return user;
+}
+
+static int count_commas(const char *str)
+{
+ int num_commas = 0;
+ const char *comma = str;
+
+ while ((comma = strchr(comma, ',')) != NULL) {
+ comma += 1;
+ num_commas += 1;
+ }
+ return num_commas;
+}
+
+/*********************************************************************
+ Initialize a struct samu from a struct passwd including the user
+ and group SIDs. The *user structure is filled out with the Unix
+ attributes and a user SID.
+*********************************************************************/
+
+static NTSTATUS samu_set_unix_internal(struct pdb_methods *methods,
+ struct samu *user, const struct passwd *pwd, bool create)
+{
+ const char *guest_account = lp_guest_account();
+ const char *domain = lp_netbios_name();
+ char *fullname;
+ uint32_t urid;
+ bool ok;
+
+ if ( !pwd ) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /* Basic properties based upon the Unix account information */
+
+ ok = pdb_set_username(user, pwd->pw_name, PDB_SET);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fullname = NULL;
+
+ if (count_commas(pwd->pw_gecos) == 3) {
+ /*
+ * Heuristic: This seems to be a gecos field that has been
+ * edited by chfn(1). Only use the part before the first
+ * comma. Fixes bug 5198.
+ */
+ fullname = talloc_strndup(
+ talloc_tos(), pwd->pw_gecos,
+ strchr(pwd->pw_gecos, ',') - pwd->pw_gecos);
+ if (fullname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (fullname != NULL) {
+ ok = pdb_set_fullname(user, fullname, PDB_SET);
+ } else {
+ ok = pdb_set_fullname(user, pwd->pw_gecos, PDB_SET);
+ }
+ TALLOC_FREE(fullname);
+
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = pdb_set_domain(user, get_global_sam_name(), PDB_DEFAULT);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+#if 0
+ /* This can lead to a primary group of S-1-22-2-XX which
+ will be rejected by other parts of the Samba code.
+ Rely on pdb_get_group_sid() to "Do The Right Thing" (TM)
+ --jerry */
+
+ gid_to_sid(&group_sid, pwd->pw_gid);
+ pdb_set_group_sid(user, &group_sid, PDB_SET);
+#endif
+
+ /* save the password structure for later use */
+
+ user->unix_pw = tcopy_passwd( user, pwd );
+ if (user->unix_pw == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Special case for the guest account which must have a RID of 501 */
+
+ if ( strequal( pwd->pw_name, guest_account ) ) {
+ if ( !pdb_set_user_sid_from_rid(user, DOMAIN_RID_GUEST, PDB_DEFAULT)) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ return NT_STATUS_OK;
+ }
+
+ /* Non-guest accounts...Check for a workstation or user account */
+
+ if (pwd->pw_name[strlen(pwd->pw_name)-1] == '$') {
+ /* workstation */
+
+ if (!pdb_set_acct_ctrl(user, ACB_WSTRUST, PDB_DEFAULT)) {
+ DEBUG(1, ("Failed to set 'workstation account' flags for user %s.\n",
+ pwd->pw_name));
+ return NT_STATUS_INVALID_COMPUTER_NAME;
+ }
+ }
+ else {
+ /* user */
+
+ if (!pdb_set_acct_ctrl(user, ACB_NORMAL, PDB_DEFAULT)) {
+ DEBUG(1, ("Failed to set 'normal account' flags for user %s.\n",
+ pwd->pw_name));
+ return NT_STATUS_INVALID_ACCOUNT_NAME;
+ }
+
+ /* set some basic attributes */
+
+ ok = pdb_set_profile_path(
+ user,
+ talloc_sub_specified(
+ user,
+ lp_logon_path(),
+ pwd->pw_name,
+ NULL,
+ domain,
+ pwd->pw_uid,
+ pwd->pw_gid),
+ PDB_DEFAULT);
+ ok &= pdb_set_homedir(
+ user,
+ talloc_sub_specified(
+ user,
+ lp_logon_home(),
+ pwd->pw_name,
+ NULL,
+ domain,
+ pwd->pw_uid,
+ pwd->pw_gid),
+ PDB_DEFAULT);
+ ok &= pdb_set_dir_drive(
+ user,
+ talloc_sub_specified(
+ user,
+ lp_logon_drive(),
+ pwd->pw_name,
+ NULL,
+ domain,
+ pwd->pw_uid,
+ pwd->pw_gid),
+ PDB_DEFAULT);
+ ok &= pdb_set_logon_script(
+ user,
+ talloc_sub_specified(
+ user,
+ lp_logon_script(),
+ pwd->pw_name,
+ NULL,
+ domain,
+ pwd->pw_uid,
+ pwd->pw_gid),
+ PDB_DEFAULT);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* Now deal with the user SID. If we have a backend that can generate
+ RIDs, then do so. But sometimes the caller just wanted a structure
+ initialized and will fill in these fields later (such as from a
+ netr_SamInfo3 structure) */
+
+ if ( create && (methods->capabilities(methods) & PDB_CAP_STORE_RIDS)) {
+ uint32_t user_rid;
+ struct dom_sid user_sid;
+
+ if ( !methods->new_rid(methods, &user_rid) ) {
+ DEBUG(3, ("Could not allocate a new RID\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ sid_compose(&user_sid, get_global_sam_sid(), user_rid);
+
+ if ( !pdb_set_user_sid(user, &user_sid, PDB_SET) ) {
+ DEBUG(3, ("pdb_set_user_sid failed\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+ }
+
+ /* generate a SID for the user with the RID algorithm */
+
+ urid = algorithmic_pdb_uid_to_user_rid( user->unix_pw->pw_uid );
+
+ if ( !pdb_set_user_sid_from_rid( user, urid, PDB_SET) ) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Set the Unix user attributes
+********************************************************************/
+
+NTSTATUS samu_set_unix(struct samu *user, const struct passwd *pwd)
+{
+ return samu_set_unix_internal( NULL, user, pwd, False );
+}
+
+NTSTATUS samu_alloc_rid_unix(struct pdb_methods *methods,
+ struct samu *user, const struct passwd *pwd)
+{
+ return samu_set_unix_internal( methods, user, pwd, True );
+}
+
+/**********************************************************
+ Encode the account control bits into a string.
+ length = length of string to encode into (including terminating
+ null). length *MUST BE MORE THAN 2* !
+ **********************************************************/
+
+char *pdb_encode_acct_ctrl(uint32_t acct_ctrl, size_t length)
+{
+ fstring acct_str;
+ char *result;
+
+ size_t i = 0;
+
+ SMB_ASSERT(length <= sizeof(acct_str));
+
+ acct_str[i++] = '[';
+
+ if (acct_ctrl & ACB_PWNOTREQ ) acct_str[i++] = 'N';
+ if (acct_ctrl & ACB_DISABLED ) acct_str[i++] = 'D';
+ if (acct_ctrl & ACB_HOMDIRREQ) acct_str[i++] = 'H';
+ if (acct_ctrl & ACB_TEMPDUP ) acct_str[i++] = 'T';
+ if (acct_ctrl & ACB_NORMAL ) acct_str[i++] = 'U';
+ if (acct_ctrl & ACB_MNS ) acct_str[i++] = 'M';
+ if (acct_ctrl & ACB_WSTRUST ) acct_str[i++] = 'W';
+ if (acct_ctrl & ACB_SVRTRUST ) acct_str[i++] = 'S';
+ if (acct_ctrl & ACB_AUTOLOCK ) acct_str[i++] = 'L';
+ if (acct_ctrl & ACB_PWNOEXP ) acct_str[i++] = 'X';
+ if (acct_ctrl & ACB_DOMTRUST ) acct_str[i++] = 'I';
+
+ for ( ; i < length - 2 ; i++ )
+ acct_str[i] = ' ';
+
+ i = length - 2;
+ acct_str[i++] = ']';
+ acct_str[i++] = '\0';
+
+ result = talloc_strdup(talloc_tos(), acct_str);
+ SMB_ASSERT(result != NULL);
+ return result;
+}
+
+/**********************************************************
+ Decode the account control bits from a string.
+ **********************************************************/
+
+uint32_t pdb_decode_acct_ctrl(const char *p)
+{
+ uint32_t acct_ctrl = 0;
+ bool finished = false;
+
+ /*
+ * Check if the account type bits have been encoded after the
+ * NT password (in the form [NDHTUWSLXI]).
+ */
+
+ if (*p != '[')
+ return 0;
+
+ for (p++; *p && !finished; p++) {
+ switch (*p) {
+ case 'N': { acct_ctrl |= ACB_PWNOTREQ ; break; /* 'N'o password. */ }
+ case 'D': { acct_ctrl |= ACB_DISABLED ; break; /* 'D'isabled. */ }
+ case 'H': { acct_ctrl |= ACB_HOMDIRREQ; break; /* 'H'omedir required. */ }
+ case 'T': { acct_ctrl |= ACB_TEMPDUP ; break; /* 'T'emp account. */ }
+ case 'U': { acct_ctrl |= ACB_NORMAL ; break; /* 'U'ser account (normal). */ }
+ case 'M': { acct_ctrl |= ACB_MNS ; break; /* 'M'NS logon user account. What is this ? */ }
+ case 'W': { acct_ctrl |= ACB_WSTRUST ; break; /* 'W'orkstation account. */ }
+ case 'S': { acct_ctrl |= ACB_SVRTRUST ; break; /* 'S'erver account. */ }
+ case 'L': { acct_ctrl |= ACB_AUTOLOCK ; break; /* 'L'ocked account. */ }
+ case 'X': { acct_ctrl |= ACB_PWNOEXP ; break; /* No 'X'piry on password */ }
+ case 'I': { acct_ctrl |= ACB_DOMTRUST ; break; /* 'I'nterdomain trust account. */ }
+ case ' ': { break; }
+ case ':':
+ case '\n':
+ case '\0':
+ case ']':
+ default: { finished = true; }
+ }
+ }
+
+ return acct_ctrl;
+}
+
+/*************************************************************
+ Routine to set 32 hex password characters from a 16 byte array.
+**************************************************************/
+
+void pdb_sethexpwd(char p[33], const unsigned char *pwd, uint32_t acct_ctrl)
+{
+ if (pwd != NULL) {
+ hex_encode_buf(p, pwd, 16);
+ } else {
+ if (acct_ctrl & ACB_PWNOTREQ)
+ strlcpy(p, "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX", 33);
+ else
+ strlcpy(p, "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 33);
+ }
+}
+
+/*************************************************************
+ Routine to get the 32 hex characters and turn them
+ into a 16 byte array.
+**************************************************************/
+
+bool pdb_gethexpwd(const char *p, unsigned char *pwd)
+{
+ int i;
+ unsigned char lonybble, hinybble;
+ const char *hexchars = "0123456789ABCDEF";
+ char *p1, *p2;
+
+ if (!p)
+ return false;
+
+ for (i = 0; i < 32; i += 2) {
+ hinybble = toupper_m(p[i]);
+ lonybble = toupper_m(p[i + 1]);
+
+ p1 = strchr(hexchars, hinybble);
+ p2 = strchr(hexchars, lonybble);
+
+ if (!p1 || !p2)
+ return false;
+
+ hinybble = PTR_DIFF(p1, hexchars);
+ lonybble = PTR_DIFF(p2, hexchars);
+
+ pwd[i / 2] = (hinybble << 4) | lonybble;
+ }
+ return true;
+}
+
+/*************************************************************
+ Routine to set 42 hex hours characters from a 21 byte array.
+**************************************************************/
+
+void pdb_sethexhours(char *p, const unsigned char *hours)
+{
+ if (hours != NULL) {
+ hex_encode_buf(p, hours, 21);
+ } else {
+ strlcpy(p, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 44);
+ }
+}
+
+/*************************************************************
+ Routine to get the 42 hex characters and turn them
+ into a 21 byte array.
+**************************************************************/
+
+bool pdb_gethexhours(const char *p, unsigned char *hours)
+{
+ int i;
+ unsigned char lonybble, hinybble;
+ const char *hexchars = "0123456789ABCDEF";
+ char *p1, *p2;
+
+ if (!p) {
+ return (False);
+ }
+
+ for (i = 0; i < 42; i += 2) {
+ hinybble = toupper_m(p[i]);
+ lonybble = toupper_m(p[i + 1]);
+
+ p1 = strchr(hexchars, hinybble);
+ p2 = strchr(hexchars, lonybble);
+
+ if (!p1 || !p2) {
+ return (False);
+ }
+
+ hinybble = PTR_DIFF(p1, hexchars);
+ lonybble = PTR_DIFF(p2, hexchars);
+
+ hours[i / 2] = (hinybble << 4) | lonybble;
+ }
+ return (True);
+}
+
+/********************************************************************
+********************************************************************/
+
+int algorithmic_rid_base(void)
+{
+ int rid_offset;
+
+ rid_offset = lp_algorithmic_rid_base();
+
+ if (rid_offset < BASE_RID) {
+ /* Try to prevent admin foot-shooting, we can't put algorithmic
+ rids below 1000, that's the 'well known RIDs' on NT */
+ DEBUG(0, ("'algorithmic rid base' must be equal to or above %ld\n", BASE_RID));
+ rid_offset = BASE_RID;
+ }
+ if (rid_offset & 1) {
+ DEBUG(0, ("algorithmic rid base must be even\n"));
+ rid_offset += 1;
+ }
+ return rid_offset;
+}
+
+/*******************************************************************
+ Converts NT user RID to a UNIX uid.
+ ********************************************************************/
+
+uid_t algorithmic_pdb_user_rid_to_uid(uint32_t user_rid)
+{
+ int rid_offset = algorithmic_rid_base();
+ return (uid_t)(((user_rid & (~USER_RID_TYPE)) - rid_offset)/RID_MULTIPLIER);
+}
+
+uid_t max_algorithmic_uid(void)
+{
+ return algorithmic_pdb_user_rid_to_uid(0xfffffffe);
+}
+
+/*******************************************************************
+ converts UNIX uid to an NT User RID.
+ ********************************************************************/
+
+uint32_t algorithmic_pdb_uid_to_user_rid(uid_t uid)
+{
+ int rid_offset = algorithmic_rid_base();
+ return (((((uint32_t)uid)*RID_MULTIPLIER) + rid_offset) | USER_RID_TYPE);
+}
+
+/*******************************************************************
+ Converts NT group RID to a UNIX gid.
+ ********************************************************************/
+
+gid_t pdb_group_rid_to_gid(uint32_t group_rid)
+{
+ int rid_offset = algorithmic_rid_base();
+ return (gid_t)(((group_rid & (~GROUP_RID_TYPE))- rid_offset)/RID_MULTIPLIER);
+}
+
+gid_t max_algorithmic_gid(void)
+{
+ return pdb_group_rid_to_gid(0xffffffff);
+}
+
+/*******************************************************************
+ converts NT Group RID to a UNIX uid.
+
+ warning: you must not call that function only
+ you must do a call to the group mapping first.
+ there is not anymore a direct link between the gid and the rid.
+ ********************************************************************/
+
+uint32_t algorithmic_pdb_gid_to_group_rid(gid_t gid)
+{
+ int rid_offset = algorithmic_rid_base();
+ return (((((uint32_t)gid)*RID_MULTIPLIER) + rid_offset) | GROUP_RID_TYPE);
+}
+
+/*******************************************************************
+ Decides if a RID is a well known RID.
+ ********************************************************************/
+
+static bool rid_is_well_known(uint32_t rid)
+{
+ /* Not using rid_offset here, because this is the actual
+ NT fixed value (1000) */
+
+ return (rid < BASE_RID);
+}
+
+/*******************************************************************
+ Decides if a RID is a user or group RID.
+ ********************************************************************/
+
+bool algorithmic_pdb_rid_is_user(uint32_t rid)
+{
+ if ( rid_is_well_known(rid) ) {
+ /*
+ * The only well known user RIDs are DOMAIN_RID_ADMINISTRATOR
+ * and DOMAIN_RID_GUEST.
+ */
+ if(rid == DOMAIN_RID_ADMINISTRATOR || rid == DOMAIN_RID_GUEST)
+ return True;
+ } else if((rid & RID_TYPE_MASK) == USER_RID_TYPE) {
+ return True;
+ }
+ return False;
+}
+
+/*******************************************************************
+ Convert a name into a SID. Used in the lookup name rpc.
+ ********************************************************************/
+
+bool lookup_global_sam_name(const char *name, int flags, uint32_t *rid,
+ enum lsa_SidType *type)
+{
+ GROUP_MAP *map;
+ bool ret;
+
+ /* Windows treats "MACHINE\None" as a special name for
+ rid 513 on non-DCs. You cannot create a user or group
+ name "None" on Windows. You will get an error that
+ the group already exists. */
+
+ if ( strequal( name, "None" ) ) {
+ *rid = DOMAIN_RID_USERS;
+ *type = SID_NAME_DOM_GRP;
+
+ return True;
+ }
+
+ /* LOOKUP_NAME_GROUP is a hack to allow valid users = @foo to work
+ * correctly in the case where foo also exists as a user. If the flag
+ * is set, don't look for users at all. */
+
+ if ((flags & LOOKUP_NAME_GROUP) == 0) {
+ struct samu *sam_account = NULL;
+ struct dom_sid user_sid;
+
+ if ( !(sam_account = samu_new( NULL )) ) {
+ return False;
+ }
+
+ become_root();
+ ret = pdb_getsampwnam(sam_account, name);
+ unbecome_root();
+
+ if (ret) {
+ sid_copy(&user_sid, pdb_get_user_sid(sam_account));
+ }
+
+ TALLOC_FREE(sam_account);
+
+ if (ret) {
+ if (!sid_check_is_in_our_sam(&user_sid)) {
+ struct dom_sid_buf buf;
+ DBG_ERR("User %s with invalid SID %s"
+ " in passdb\n",
+ name,
+ dom_sid_str_buf(&user_sid, &buf));
+ return False;
+ }
+
+ sid_peek_rid(&user_sid, rid);
+ *type = SID_NAME_USER;
+ return True;
+ }
+ }
+
+ /*
+ * Maybe it is a group ?
+ */
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return false;
+ }
+
+ become_root();
+ ret = pdb_getgrnam(map, name);
+ unbecome_root();
+
+ if (!ret) {
+ TALLOC_FREE(map);
+ return False;
+ }
+
+ /* BUILTIN groups are looked up elsewhere */
+ if (!sid_check_is_in_our_sam(&map->sid)) {
+ struct dom_sid_buf buf;
+ DEBUG(10, ("Found group %s (%s) not in our domain -- "
+ "ignoring.\n",
+ name,
+ dom_sid_str_buf(&map->sid, &buf)));
+ TALLOC_FREE(map);
+ return False;
+ }
+
+ /* yes it's a mapped group */
+ sid_peek_rid(&map->sid, rid);
+ *type = map->sid_name_use;
+ TALLOC_FREE(map);
+ return True;
+}
+
+/*************************************************************
+ Change a password entry in the local passdb backend.
+
+ Assumptions:
+ - always called as root
+ - ignores the account type except when adding a new account
+ - will create/delete the unix account if the relative
+ add/delete user script is configured
+
+ *************************************************************/
+
+NTSTATUS local_password_change(const char *user_name,
+ int local_flags,
+ const char *new_passwd,
+ char **pp_err_str,
+ char **pp_msg_str)
+{
+ TALLOC_CTX *tosctx;
+ struct samu *sam_pass;
+ uint32_t acb;
+ uint32_t rid;
+ NTSTATUS result;
+ bool user_exists;
+ int ret = -1;
+
+ *pp_err_str = NULL;
+ *pp_msg_str = NULL;
+
+ tosctx = talloc_tos();
+
+ sam_pass = samu_new(tosctx);
+ if (!sam_pass) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* Get the smb passwd entry for this user */
+ user_exists = pdb_getsampwnam(sam_pass, user_name);
+
+ /* Check delete first, we don't need to do anything else if we
+ * are going to delete the account */
+ if (user_exists && (local_flags & LOCAL_DELETE_USER)) {
+
+ result = pdb_delete_user(tosctx, sam_pass);
+ if (!NT_STATUS_IS_OK(result)) {
+ ret = asprintf(pp_err_str,
+ "Failed to delete entry for user %s.\n",
+ user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ } else {
+ ret = asprintf(pp_msg_str,
+ "Deleted user %s.\n",
+ user_name);
+ if (ret < 0) {
+ *pp_msg_str = NULL;
+ }
+ }
+ goto done;
+ }
+
+ if (user_exists && (local_flags & LOCAL_ADD_USER)) {
+ /* the entry already existed */
+ local_flags &= ~LOCAL_ADD_USER;
+ }
+
+ if (!user_exists && !(local_flags & LOCAL_ADD_USER)) {
+ ret = asprintf(pp_err_str,
+ "Failed to find entry for user %s.\n",
+ user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ /* First thing add the new user if we are required to do so */
+ if (local_flags & LOCAL_ADD_USER) {
+
+ if (local_flags & LOCAL_TRUST_ACCOUNT) {
+ acb = ACB_WSTRUST;
+ } else if (local_flags & LOCAL_INTERDOM_ACCOUNT) {
+ acb = ACB_DOMTRUST;
+ } else {
+ acb = ACB_NORMAL;
+ }
+
+ result = pdb_create_user(tosctx, user_name, acb, &rid);
+ if (!NT_STATUS_IS_OK(result)) {
+ ret = asprintf(pp_err_str,
+ "Failed to add entry for user %s.\n",
+ user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ sam_pass = samu_new(tosctx);
+ if (!sam_pass) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* Now get back the smb passwd entry for this new user */
+ user_exists = pdb_getsampwnam(sam_pass, user_name);
+ if (!user_exists) {
+ ret = asprintf(pp_err_str,
+ "Failed to add entry for user %s.\n",
+ user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ acb = pdb_get_acct_ctrl(sam_pass);
+
+ /*
+ * We are root - just write the new password
+ * and the valid last change time.
+ */
+ if ((local_flags & LOCAL_SET_NO_PASSWORD) && !(acb & ACB_PWNOTREQ)) {
+ acb |= ACB_PWNOTREQ;
+ if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) {
+ ret = asprintf(pp_err_str,
+ "Failed to set 'no password required' "
+ "flag for user %s.\n", user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ if (local_flags & LOCAL_SET_PASSWORD) {
+ /*
+ * If we're dealing with setting a completely empty user account
+ * ie. One with a password of 'XXXX', but not set disabled (like
+ * an account created from scratch) then if the old password was
+ * 'XX's then getsmbpwent will have set the ACB_DISABLED flag.
+ * We remove that as we're giving this user their first password
+ * and the decision hasn't really been made to disable them (ie.
+ * don't create them disabled). JRA.
+ */
+ if ((pdb_get_lanman_passwd(sam_pass) == NULL) &&
+ (acb & ACB_DISABLED)) {
+ acb &= (~ACB_DISABLED);
+ if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) {
+ ret = asprintf(pp_err_str,
+ "Failed to unset 'disabled' "
+ "flag for user %s.\n",
+ user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ acb &= (~ACB_PWNOTREQ);
+ if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) {
+ ret = asprintf(pp_err_str,
+ "Failed to unset 'no password required'"
+ " flag for user %s.\n", user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (!pdb_set_plaintext_passwd(sam_pass, new_passwd)) {
+ ret = asprintf(pp_err_str,
+ "Failed to set password for "
+ "user %s.\n", user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ if ((local_flags & LOCAL_DISABLE_USER) && !(acb & ACB_DISABLED)) {
+ acb |= ACB_DISABLED;
+ if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) {
+ ret = asprintf(pp_err_str,
+ "Failed to set 'disabled' flag for "
+ "user %s.\n", user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ if ((local_flags & LOCAL_ENABLE_USER) && (acb & ACB_DISABLED)) {
+ acb &= (~ACB_DISABLED);
+ if (!pdb_set_acct_ctrl(sam_pass, acb, PDB_CHANGED)) {
+ ret = asprintf(pp_err_str,
+ "Failed to unset 'disabled' flag for "
+ "user %s.\n", user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ /* now commit changes if any */
+ result = pdb_update_sam_account(sam_pass);
+ if (!NT_STATUS_IS_OK(result)) {
+ ret = asprintf(pp_err_str,
+ "Failed to modify entry for user %s.\n",
+ user_name);
+ if (ret < 0) {
+ *pp_err_str = NULL;
+ }
+ goto done;
+ }
+
+ if (local_flags & LOCAL_ADD_USER) {
+ ret = asprintf(pp_msg_str, "Added user %s.\n", user_name);
+ } else if (local_flags & LOCAL_DISABLE_USER) {
+ ret = asprintf(pp_msg_str, "Disabled user %s.\n", user_name);
+ } else if (local_flags & LOCAL_ENABLE_USER) {
+ ret = asprintf(pp_msg_str, "Enabled user %s.\n", user_name);
+ } else if (local_flags & LOCAL_SET_NO_PASSWORD) {
+ ret = asprintf(pp_msg_str,
+ "User %s password set to none.\n", user_name);
+ }
+
+ if (ret < 0) {
+ *pp_msg_str = NULL;
+ }
+
+ result = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(sam_pass);
+ return result;
+}
+
+/**********************************************************************
+ Marshall/unmarshall struct samu structs.
+ *********************************************************************/
+
+#define SAMU_BUFFER_FORMAT_V0 "ddddddBBBBBBBBBBBBddBBwdwdBwwd"
+#define SAMU_BUFFER_FORMAT_V1 "dddddddBBBBBBBBBBBBddBBwdwdBwwd"
+#define SAMU_BUFFER_FORMAT_V2 "dddddddBBBBBBBBBBBBddBBBwwdBwwd"
+#define SAMU_BUFFER_FORMAT_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd"
+/* nothing changed between V3 and V4 */
+
+/*********************************************************************
+*********************************************************************/
+
+static bool init_samu_from_buffer_v0(struct samu *sampass, uint8_t *buf, uint32_t buflen)
+{
+
+ /* times are stored as 32bit integer
+ take care on system with 64bit wide time_t
+ --SSS */
+ uint32_t logon_time,
+ logoff_time,
+ kickoff_time,
+ pass_last_set_time,
+ pass_can_change_time,
+ pass_must_change_time;
+ char *username = NULL;
+ char *domain = NULL;
+ char *nt_username = NULL;
+ char *dir_drive = NULL;
+ char *unknown_str = NULL;
+ char *munged_dial = NULL;
+ char *fullname = NULL;
+ char *homedir = NULL;
+ char *logon_script = NULL;
+ char *profile_path = NULL;
+ char *acct_desc = NULL;
+ char *workstations = NULL;
+ uint32_t username_len, domain_len, nt_username_len,
+ dir_drive_len, unknown_str_len, munged_dial_len,
+ fullname_len, homedir_len, logon_script_len,
+ profile_path_len, acct_desc_len, workstations_len;
+
+ uint32_t user_rid, group_rid, remove_me, hours_len, unknown_6;
+ uint16_t acct_ctrl, logon_divs;
+ uint16_t bad_password_count, logon_count;
+ uint8_t *hours = NULL;
+ uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL;
+ uint32_t len = 0;
+ uint32_t lm_pw_len, nt_pw_len, hourslen;
+ bool ret = True;
+
+ if(sampass == NULL || buf == NULL) {
+ DEBUG(0, ("init_samu_from_buffer_v0: NULL parameters found!\n"));
+ return False;
+ }
+
+/* SAMU_BUFFER_FORMAT_V0 "ddddddBBBBBBBBBBBBddBBwdwdBwwd" */
+
+ /* unpack the buffer into variables */
+ len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V0,
+ &logon_time, /* d */
+ &logoff_time, /* d */
+ &kickoff_time, /* d */
+ &pass_last_set_time, /* d */
+ &pass_can_change_time, /* d */
+ &pass_must_change_time, /* d */
+ &username_len, &username, /* B */
+ &domain_len, &domain, /* B */
+ &nt_username_len, &nt_username, /* B */
+ &fullname_len, &fullname, /* B */
+ &homedir_len, &homedir, /* B */
+ &dir_drive_len, &dir_drive, /* B */
+ &logon_script_len, &logon_script, /* B */
+ &profile_path_len, &profile_path, /* B */
+ &acct_desc_len, &acct_desc, /* B */
+ &workstations_len, &workstations, /* B */
+ &unknown_str_len, &unknown_str, /* B */
+ &munged_dial_len, &munged_dial, /* B */
+ &user_rid, /* d */
+ &group_rid, /* d */
+ &lm_pw_len, &lm_pw_ptr, /* B */
+ &nt_pw_len, &nt_pw_ptr, /* B */
+ &acct_ctrl, /* w */
+ &remove_me, /* remove on the next TDB_FORMAT upgrade */ /* d */
+ &logon_divs, /* w */
+ &hours_len, /* d */
+ &hourslen, &hours, /* B */
+ &bad_password_count, /* w */
+ &logon_count, /* w */
+ &unknown_6); /* d */
+
+ if (len == (uint32_t) -1) {
+ ret = False;
+ goto done;
+ }
+
+ pdb_set_logon_time(sampass, logon_time, PDB_SET);
+ pdb_set_logoff_time(sampass, logoff_time, PDB_SET);
+ pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET);
+ pdb_set_pass_can_change_time(sampass, pass_can_change_time, PDB_SET);
+ pdb_set_pass_last_set_time(sampass, pass_last_set_time, PDB_SET);
+
+ pdb_set_username(sampass, username, PDB_SET);
+ pdb_set_domain(sampass, domain, PDB_SET);
+ pdb_set_nt_username(sampass, nt_username, PDB_SET);
+ pdb_set_fullname(sampass, fullname, PDB_SET);
+
+ if (homedir) {
+ pdb_set_homedir(sampass, homedir, PDB_SET);
+ }
+ else {
+ pdb_set_homedir(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_home()),
+ PDB_DEFAULT);
+ }
+
+ if (dir_drive)
+ pdb_set_dir_drive(sampass, dir_drive, PDB_SET);
+ else {
+ pdb_set_dir_drive(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_drive()),
+ PDB_DEFAULT);
+ }
+
+ if (logon_script)
+ pdb_set_logon_script(sampass, logon_script, PDB_SET);
+ else {
+ pdb_set_logon_script(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_script()),
+ PDB_DEFAULT);
+ }
+
+ if (profile_path) {
+ pdb_set_profile_path(sampass, profile_path, PDB_SET);
+ } else {
+ pdb_set_profile_path(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_path()),
+ PDB_DEFAULT);
+ }
+
+ pdb_set_acct_desc(sampass, acct_desc, PDB_SET);
+ pdb_set_workstations(sampass, workstations, PDB_SET);
+ pdb_set_munged_dial(sampass, munged_dial, PDB_SET);
+
+ if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) {
+ if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) {
+ if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ pdb_set_pw_history(sampass, NULL, 0, PDB_SET);
+ pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET);
+ pdb_set_group_sid_from_rid(sampass, group_rid, PDB_SET);
+ pdb_set_hours_len(sampass, hours_len, PDB_SET);
+ pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET);
+ pdb_set_logon_count(sampass, logon_count, PDB_SET);
+ pdb_set_unknown_6(sampass, unknown_6, PDB_SET);
+ pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET);
+ pdb_set_logon_divs(sampass, logon_divs, PDB_SET);
+ pdb_set_hours(sampass, hours, hours_len, PDB_SET);
+
+done:
+
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ SAFE_FREE(nt_username);
+ SAFE_FREE(fullname);
+ SAFE_FREE(homedir);
+ SAFE_FREE(dir_drive);
+ SAFE_FREE(logon_script);
+ SAFE_FREE(profile_path);
+ SAFE_FREE(acct_desc);
+ SAFE_FREE(workstations);
+ SAFE_FREE(munged_dial);
+ SAFE_FREE(unknown_str);
+ SAFE_FREE(lm_pw_ptr);
+ SAFE_FREE(nt_pw_ptr);
+ SAFE_FREE(hours);
+
+ return ret;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool init_samu_from_buffer_v1(struct samu *sampass, uint8_t *buf, uint32_t buflen)
+{
+
+ /* times are stored as 32bit integer
+ take care on system with 64bit wide time_t
+ --SSS */
+ uint32_t logon_time,
+ logoff_time,
+ kickoff_time,
+ bad_password_time,
+ pass_last_set_time,
+ pass_can_change_time,
+ pass_must_change_time;
+ char *username = NULL;
+ char *domain = NULL;
+ char *nt_username = NULL;
+ char *dir_drive = NULL;
+ char *unknown_str = NULL;
+ char *munged_dial = NULL;
+ char *fullname = NULL;
+ char *homedir = NULL;
+ char *logon_script = NULL;
+ char *profile_path = NULL;
+ char *acct_desc = NULL;
+ char *workstations = NULL;
+ uint32_t username_len, domain_len, nt_username_len,
+ dir_drive_len, unknown_str_len, munged_dial_len,
+ fullname_len, homedir_len, logon_script_len,
+ profile_path_len, acct_desc_len, workstations_len;
+
+ uint32_t user_rid, group_rid, remove_me, hours_len, unknown_6;
+ uint16_t acct_ctrl, logon_divs;
+ uint16_t bad_password_count, logon_count;
+ uint8_t *hours = NULL;
+ uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL;
+ uint32_t len = 0;
+ uint32_t lm_pw_len, nt_pw_len, hourslen;
+ bool ret = True;
+
+ if(sampass == NULL || buf == NULL) {
+ DEBUG(0, ("init_samu_from_buffer_v1: NULL parameters found!\n"));
+ return False;
+ }
+
+/* SAMU_BUFFER_FORMAT_V1 "dddddddBBBBBBBBBBBBddBBwdwdBwwd" */
+
+ /* unpack the buffer into variables */
+ len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V1,
+ &logon_time, /* d */
+ &logoff_time, /* d */
+ &kickoff_time, /* d */
+ /* Change from V0 is addition of bad_password_time field. */
+ &bad_password_time, /* d */
+ &pass_last_set_time, /* d */
+ &pass_can_change_time, /* d */
+ &pass_must_change_time, /* d */
+ &username_len, &username, /* B */
+ &domain_len, &domain, /* B */
+ &nt_username_len, &nt_username, /* B */
+ &fullname_len, &fullname, /* B */
+ &homedir_len, &homedir, /* B */
+ &dir_drive_len, &dir_drive, /* B */
+ &logon_script_len, &logon_script, /* B */
+ &profile_path_len, &profile_path, /* B */
+ &acct_desc_len, &acct_desc, /* B */
+ &workstations_len, &workstations, /* B */
+ &unknown_str_len, &unknown_str, /* B */
+ &munged_dial_len, &munged_dial, /* B */
+ &user_rid, /* d */
+ &group_rid, /* d */
+ &lm_pw_len, &lm_pw_ptr, /* B */
+ &nt_pw_len, &nt_pw_ptr, /* B */
+ &acct_ctrl, /* w */
+ &remove_me, /* d */
+ &logon_divs, /* w */
+ &hours_len, /* d */
+ &hourslen, &hours, /* B */
+ &bad_password_count, /* w */
+ &logon_count, /* w */
+ &unknown_6); /* d */
+
+ if (len == (uint32_t) -1) {
+ ret = False;
+ goto done;
+ }
+
+ pdb_set_logon_time(sampass, logon_time, PDB_SET);
+ pdb_set_logoff_time(sampass, logoff_time, PDB_SET);
+ pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET);
+
+ /* Change from V0 is addition of bad_password_time field. */
+ pdb_set_bad_password_time(sampass, bad_password_time, PDB_SET);
+ pdb_set_pass_can_change_time(sampass, pass_can_change_time, PDB_SET);
+ pdb_set_pass_last_set_time(sampass, pass_last_set_time, PDB_SET);
+
+ pdb_set_username(sampass, username, PDB_SET);
+ pdb_set_domain(sampass, domain, PDB_SET);
+ pdb_set_nt_username(sampass, nt_username, PDB_SET);
+ pdb_set_fullname(sampass, fullname, PDB_SET);
+
+ if (homedir) {
+ pdb_set_homedir(sampass, homedir, PDB_SET);
+ }
+ else {
+ pdb_set_homedir(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_home()),
+ PDB_DEFAULT);
+ }
+
+ if (dir_drive)
+ pdb_set_dir_drive(sampass, dir_drive, PDB_SET);
+ else {
+ pdb_set_dir_drive(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_drive()),
+ PDB_DEFAULT);
+ }
+
+ if (logon_script)
+ pdb_set_logon_script(sampass, logon_script, PDB_SET);
+ else {
+ pdb_set_logon_script(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_script()),
+ PDB_DEFAULT);
+ }
+
+ if (profile_path) {
+ pdb_set_profile_path(sampass, profile_path, PDB_SET);
+ } else {
+ pdb_set_profile_path(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_path()),
+ PDB_DEFAULT);
+ }
+
+ pdb_set_acct_desc(sampass, acct_desc, PDB_SET);
+ pdb_set_workstations(sampass, workstations, PDB_SET);
+ pdb_set_munged_dial(sampass, munged_dial, PDB_SET);
+
+ if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) {
+ if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) {
+ if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ pdb_set_pw_history(sampass, NULL, 0, PDB_SET);
+
+ pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET);
+ pdb_set_group_sid_from_rid(sampass, group_rid, PDB_SET);
+ pdb_set_hours_len(sampass, hours_len, PDB_SET);
+ pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET);
+ pdb_set_logon_count(sampass, logon_count, PDB_SET);
+ pdb_set_unknown_6(sampass, unknown_6, PDB_SET);
+ pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET);
+ pdb_set_logon_divs(sampass, logon_divs, PDB_SET);
+ pdb_set_hours(sampass, hours, hours_len, PDB_SET);
+
+done:
+
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ SAFE_FREE(nt_username);
+ SAFE_FREE(fullname);
+ SAFE_FREE(homedir);
+ SAFE_FREE(dir_drive);
+ SAFE_FREE(logon_script);
+ SAFE_FREE(profile_path);
+ SAFE_FREE(acct_desc);
+ SAFE_FREE(workstations);
+ SAFE_FREE(munged_dial);
+ SAFE_FREE(unknown_str);
+ SAFE_FREE(lm_pw_ptr);
+ SAFE_FREE(nt_pw_ptr);
+ SAFE_FREE(hours);
+
+ return ret;
+}
+
+static bool init_samu_from_buffer_v2(struct samu *sampass, uint8_t *buf, uint32_t buflen)
+{
+
+ /* times are stored as 32bit integer
+ take care on system with 64bit wide time_t
+ --SSS */
+ uint32_t logon_time,
+ logoff_time,
+ kickoff_time,
+ bad_password_time,
+ pass_last_set_time,
+ pass_can_change_time,
+ pass_must_change_time;
+ char *username = NULL;
+ char *domain = NULL;
+ char *nt_username = NULL;
+ char *dir_drive = NULL;
+ char *unknown_str = NULL;
+ char *munged_dial = NULL;
+ char *fullname = NULL;
+ char *homedir = NULL;
+ char *logon_script = NULL;
+ char *profile_path = NULL;
+ char *acct_desc = NULL;
+ char *workstations = NULL;
+ uint32_t username_len, domain_len, nt_username_len,
+ dir_drive_len, unknown_str_len, munged_dial_len,
+ fullname_len, homedir_len, logon_script_len,
+ profile_path_len, acct_desc_len, workstations_len;
+
+ uint32_t user_rid, group_rid, hours_len, unknown_6;
+ uint16_t acct_ctrl, logon_divs;
+ uint16_t bad_password_count, logon_count;
+ uint8_t *hours = NULL;
+ uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL, *nt_pw_hist_ptr = NULL;
+ uint32_t len = 0;
+ uint32_t lm_pw_len, nt_pw_len, nt_pw_hist_len, hourslen;
+ uint32_t pwHistLen = 0;
+ bool ret = True;
+ fstring tmp_string;
+ bool expand_explicit = lp_passdb_expand_explicit();
+
+ if(sampass == NULL || buf == NULL) {
+ DEBUG(0, ("init_samu_from_buffer_v2: NULL parameters found!\n"));
+ return False;
+ }
+
+/* SAMU_BUFFER_FORMAT_V2 "dddddddBBBBBBBBBBBBddBBBwwdBwwd" */
+
+ /* unpack the buffer into variables */
+ len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V2,
+ &logon_time, /* d */
+ &logoff_time, /* d */
+ &kickoff_time, /* d */
+ &bad_password_time, /* d */
+ &pass_last_set_time, /* d */
+ &pass_can_change_time, /* d */
+ &pass_must_change_time, /* d */
+ &username_len, &username, /* B */
+ &domain_len, &domain, /* B */
+ &nt_username_len, &nt_username, /* B */
+ &fullname_len, &fullname, /* B */
+ &homedir_len, &homedir, /* B */
+ &dir_drive_len, &dir_drive, /* B */
+ &logon_script_len, &logon_script, /* B */
+ &profile_path_len, &profile_path, /* B */
+ &acct_desc_len, &acct_desc, /* B */
+ &workstations_len, &workstations, /* B */
+ &unknown_str_len, &unknown_str, /* B */
+ &munged_dial_len, &munged_dial, /* B */
+ &user_rid, /* d */
+ &group_rid, /* d */
+ &lm_pw_len, &lm_pw_ptr, /* B */
+ &nt_pw_len, &nt_pw_ptr, /* B */
+ /* Change from V1 is addition of password history field. */
+ &nt_pw_hist_len, &nt_pw_hist_ptr, /* B */
+ &acct_ctrl, /* w */
+ /* Also "remove_me" field was removed. */
+ &logon_divs, /* w */
+ &hours_len, /* d */
+ &hourslen, &hours, /* B */
+ &bad_password_count, /* w */
+ &logon_count, /* w */
+ &unknown_6); /* d */
+
+ if (len == (uint32_t) -1) {
+ ret = False;
+ goto done;
+ }
+
+ pdb_set_logon_time(sampass, logon_time, PDB_SET);
+ pdb_set_logoff_time(sampass, logoff_time, PDB_SET);
+ pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET);
+ pdb_set_bad_password_time(sampass, bad_password_time, PDB_SET);
+ pdb_set_pass_can_change_time(sampass, pass_can_change_time, PDB_SET);
+ pdb_set_pass_last_set_time(sampass, pass_last_set_time, PDB_SET);
+
+ pdb_set_username(sampass, username, PDB_SET);
+ pdb_set_domain(sampass, domain, PDB_SET);
+ pdb_set_nt_username(sampass, nt_username, PDB_SET);
+ pdb_set_fullname(sampass, fullname, PDB_SET);
+
+ if (homedir) {
+ fstrcpy( tmp_string, homedir );
+ if (expand_explicit) {
+ standard_sub_basic( username, domain, tmp_string,
+ sizeof(tmp_string) );
+ }
+ pdb_set_homedir(sampass, tmp_string, PDB_SET);
+ }
+ else {
+ pdb_set_homedir(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_home()),
+ PDB_DEFAULT);
+ }
+
+ if (dir_drive)
+ pdb_set_dir_drive(sampass, dir_drive, PDB_SET);
+ else
+ pdb_set_dir_drive(sampass, lp_logon_drive(), PDB_DEFAULT );
+
+ if (logon_script) {
+ fstrcpy( tmp_string, logon_script );
+ if (expand_explicit) {
+ standard_sub_basic( username, domain, tmp_string,
+ sizeof(tmp_string) );
+ }
+ pdb_set_logon_script(sampass, tmp_string, PDB_SET);
+ }
+ else {
+ pdb_set_logon_script(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_script()),
+ PDB_DEFAULT);
+ }
+
+ if (profile_path) {
+ fstrcpy( tmp_string, profile_path );
+ if (expand_explicit) {
+ standard_sub_basic( username, domain, tmp_string,
+ sizeof(tmp_string) );
+ }
+ pdb_set_profile_path(sampass, tmp_string, PDB_SET);
+ }
+ else {
+ pdb_set_profile_path(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_path()),
+ PDB_DEFAULT);
+ }
+
+ pdb_set_acct_desc(sampass, acct_desc, PDB_SET);
+ pdb_set_workstations(sampass, workstations, PDB_SET);
+ pdb_set_munged_dial(sampass, munged_dial, PDB_SET);
+
+ if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) {
+ if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) {
+ if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ /* Change from V1 is addition of password history field. */
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen);
+ if (pwHistLen) {
+ uint8_t *pw_hist = SMB_MALLOC_ARRAY(uint8_t, pwHistLen * PW_HISTORY_ENTRY_LEN);
+ if (!pw_hist) {
+ ret = False;
+ goto done;
+ }
+ memset(pw_hist, '\0', pwHistLen * PW_HISTORY_ENTRY_LEN);
+ if (nt_pw_hist_ptr && nt_pw_hist_len) {
+ int i;
+ SMB_ASSERT((nt_pw_hist_len % PW_HISTORY_ENTRY_LEN) == 0);
+ nt_pw_hist_len /= PW_HISTORY_ENTRY_LEN;
+ for (i = 0; (i < pwHistLen) && (i < nt_pw_hist_len); i++) {
+ memcpy(&pw_hist[i*PW_HISTORY_ENTRY_LEN],
+ &nt_pw_hist_ptr[i*PW_HISTORY_ENTRY_LEN],
+ PW_HISTORY_ENTRY_LEN);
+ }
+ }
+ if (!pdb_set_pw_history(sampass, pw_hist, pwHistLen, PDB_SET)) {
+ SAFE_FREE(pw_hist);
+ ret = False;
+ goto done;
+ }
+ SAFE_FREE(pw_hist);
+ } else {
+ pdb_set_pw_history(sampass, NULL, 0, PDB_SET);
+ }
+
+ pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET);
+ pdb_set_group_sid_from_rid(sampass, group_rid, PDB_SET);
+ pdb_set_hours_len(sampass, hours_len, PDB_SET);
+ pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET);
+ pdb_set_logon_count(sampass, logon_count, PDB_SET);
+ pdb_set_unknown_6(sampass, unknown_6, PDB_SET);
+ pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET);
+ pdb_set_logon_divs(sampass, logon_divs, PDB_SET);
+ pdb_set_hours(sampass, hours, hours_len, PDB_SET);
+
+done:
+
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ SAFE_FREE(nt_username);
+ SAFE_FREE(fullname);
+ SAFE_FREE(homedir);
+ SAFE_FREE(dir_drive);
+ SAFE_FREE(logon_script);
+ SAFE_FREE(profile_path);
+ SAFE_FREE(acct_desc);
+ SAFE_FREE(workstations);
+ SAFE_FREE(munged_dial);
+ SAFE_FREE(unknown_str);
+ SAFE_FREE(lm_pw_ptr);
+ SAFE_FREE(nt_pw_ptr);
+ SAFE_FREE(nt_pw_hist_ptr);
+ SAFE_FREE(hours);
+
+ return ret;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool init_samu_from_buffer_v3(struct samu *sampass, uint8_t *buf, uint32_t buflen)
+{
+
+ /* times are stored as 32bit integer
+ take care on system with 64bit wide time_t
+ --SSS */
+ uint32_t logon_time,
+ logoff_time,
+ kickoff_time,
+ bad_password_time,
+ pass_last_set_time,
+ pass_can_change_time,
+ pass_must_change_time;
+ char *username = NULL;
+ char *domain = NULL;
+ char *nt_username = NULL;
+ char *dir_drive = NULL;
+ char *comment = NULL;
+ char *munged_dial = NULL;
+ char *fullname = NULL;
+ char *homedir = NULL;
+ char *logon_script = NULL;
+ char *profile_path = NULL;
+ char *acct_desc = NULL;
+ char *workstations = NULL;
+ uint32_t username_len, domain_len, nt_username_len,
+ dir_drive_len, comment_len, munged_dial_len,
+ fullname_len, homedir_len, logon_script_len,
+ profile_path_len, acct_desc_len, workstations_len;
+
+ uint32_t user_rid, group_rid, hours_len, unknown_6, acct_ctrl;
+ uint16_t logon_divs;
+ uint16_t bad_password_count, logon_count;
+ uint8_t *hours = NULL;
+ uint8_t *lm_pw_ptr = NULL, *nt_pw_ptr = NULL, *nt_pw_hist_ptr = NULL;
+ uint32_t len = 0;
+ uint32_t lm_pw_len, nt_pw_len, nt_pw_hist_len, hourslen;
+ uint32_t pwHistLen = 0;
+ bool ret = True;
+ fstring tmp_string;
+ bool expand_explicit = lp_passdb_expand_explicit();
+
+ if(sampass == NULL || buf == NULL) {
+ DEBUG(0, ("init_samu_from_buffer_v3: NULL parameters found!\n"));
+ return False;
+ }
+
+/* SAMU_BUFFER_FORMAT_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" */
+
+ /* unpack the buffer into variables */
+ len = tdb_unpack (buf, buflen, SAMU_BUFFER_FORMAT_V3,
+ &logon_time, /* d */
+ &logoff_time, /* d */
+ &kickoff_time, /* d */
+ &bad_password_time, /* d */
+ &pass_last_set_time, /* d */
+ &pass_can_change_time, /* d */
+ &pass_must_change_time, /* d */
+ &username_len, &username, /* B */
+ &domain_len, &domain, /* B */
+ &nt_username_len, &nt_username, /* B */
+ &fullname_len, &fullname, /* B */
+ &homedir_len, &homedir, /* B */
+ &dir_drive_len, &dir_drive, /* B */
+ &logon_script_len, &logon_script, /* B */
+ &profile_path_len, &profile_path, /* B */
+ &acct_desc_len, &acct_desc, /* B */
+ &workstations_len, &workstations, /* B */
+ &comment_len, &comment, /* B */
+ &munged_dial_len, &munged_dial, /* B */
+ &user_rid, /* d */
+ &group_rid, /* d */
+ &lm_pw_len, &lm_pw_ptr, /* B */
+ &nt_pw_len, &nt_pw_ptr, /* B */
+ /* Change from V1 is addition of password history field. */
+ &nt_pw_hist_len, &nt_pw_hist_ptr, /* B */
+ /* Change from V2 is the uint32_t acb_mask */
+ &acct_ctrl, /* d */
+ /* Also "remove_me" field was removed. */
+ &logon_divs, /* w */
+ &hours_len, /* d */
+ &hourslen, &hours, /* B */
+ &bad_password_count, /* w */
+ &logon_count, /* w */
+ &unknown_6); /* d */
+
+ if (len == (uint32_t) -1) {
+ ret = False;
+ goto done;
+ }
+
+ pdb_set_logon_time(sampass, convert_uint32_t_to_time_t(logon_time), PDB_SET);
+ pdb_set_logoff_time(sampass, convert_uint32_t_to_time_t(logoff_time), PDB_SET);
+ pdb_set_kickoff_time(sampass, convert_uint32_t_to_time_t(kickoff_time), PDB_SET);
+ pdb_set_bad_password_time(sampass, convert_uint32_t_to_time_t(bad_password_time), PDB_SET);
+ pdb_set_pass_can_change_time(sampass, convert_uint32_t_to_time_t(pass_can_change_time), PDB_SET);
+ pdb_set_pass_last_set_time(sampass, convert_uint32_t_to_time_t(pass_last_set_time), PDB_SET);
+
+ pdb_set_username(sampass, username, PDB_SET);
+ pdb_set_domain(sampass, domain, PDB_SET);
+ pdb_set_nt_username(sampass, nt_username, PDB_SET);
+ pdb_set_fullname(sampass, fullname, PDB_SET);
+
+ if (homedir) {
+ fstrcpy( tmp_string, homedir );
+ if (expand_explicit) {
+ standard_sub_basic( username, domain, tmp_string,
+ sizeof(tmp_string) );
+ }
+ pdb_set_homedir(sampass, tmp_string, PDB_SET);
+ }
+ else {
+ pdb_set_homedir(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_home()),
+ PDB_DEFAULT);
+ }
+
+ if (dir_drive)
+ pdb_set_dir_drive(sampass, dir_drive, PDB_SET);
+ else
+ pdb_set_dir_drive(sampass, lp_logon_drive(), PDB_DEFAULT );
+
+ if (logon_script) {
+ fstrcpy( tmp_string, logon_script );
+ if (expand_explicit) {
+ standard_sub_basic( username, domain, tmp_string,
+ sizeof(tmp_string) );
+ }
+ pdb_set_logon_script(sampass, tmp_string, PDB_SET);
+ }
+ else {
+ pdb_set_logon_script(sampass,
+ talloc_sub_basic(sampass, username, domain,
+ lp_logon_script()),
+ PDB_DEFAULT);
+ }
+
+ if (profile_path) {
+ fstrcpy( tmp_string, profile_path );
+ if (expand_explicit) {
+ standard_sub_basic( username, domain, tmp_string,
+ sizeof(tmp_string) );
+ }
+ pdb_set_profile_path(sampass, tmp_string, PDB_SET);
+ }
+ else {
+ pdb_set_profile_path(sampass,
+ talloc_sub_basic(sampass, username, domain, lp_logon_path()),
+ PDB_DEFAULT);
+ }
+
+ pdb_set_acct_desc(sampass, acct_desc, PDB_SET);
+ pdb_set_comment(sampass, comment, PDB_SET);
+ pdb_set_workstations(sampass, workstations, PDB_SET);
+ pdb_set_munged_dial(sampass, munged_dial, PDB_SET);
+
+ if (lm_pw_ptr && lm_pw_len == LM_HASH_LEN) {
+ if (!pdb_set_lanman_passwd(sampass, lm_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ if (nt_pw_ptr && nt_pw_len == NT_HASH_LEN) {
+ if (!pdb_set_nt_passwd(sampass, nt_pw_ptr, PDB_SET)) {
+ ret = False;
+ goto done;
+ }
+ }
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen);
+ if (pwHistLen) {
+ uint8_t *pw_hist = (uint8_t *)SMB_MALLOC(pwHistLen * PW_HISTORY_ENTRY_LEN);
+ if (!pw_hist) {
+ ret = False;
+ goto done;
+ }
+ memset(pw_hist, '\0', pwHistLen * PW_HISTORY_ENTRY_LEN);
+ if (nt_pw_hist_ptr && nt_pw_hist_len) {
+ int i;
+ SMB_ASSERT((nt_pw_hist_len % PW_HISTORY_ENTRY_LEN) == 0);
+ nt_pw_hist_len /= PW_HISTORY_ENTRY_LEN;
+ for (i = 0; (i < pwHistLen) && (i < nt_pw_hist_len); i++) {
+ memcpy(&pw_hist[i*PW_HISTORY_ENTRY_LEN],
+ &nt_pw_hist_ptr[i*PW_HISTORY_ENTRY_LEN],
+ PW_HISTORY_ENTRY_LEN);
+ }
+ }
+ if (!pdb_set_pw_history(sampass, pw_hist, pwHistLen, PDB_SET)) {
+ SAFE_FREE(pw_hist);
+ ret = False;
+ goto done;
+ }
+ SAFE_FREE(pw_hist);
+ } else {
+ pdb_set_pw_history(sampass, NULL, 0, PDB_SET);
+ }
+
+ pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET);
+ pdb_set_hours_len(sampass, hours_len, PDB_SET);
+ pdb_set_bad_password_count(sampass, bad_password_count, PDB_SET);
+ pdb_set_logon_count(sampass, logon_count, PDB_SET);
+ pdb_set_unknown_6(sampass, unknown_6, PDB_SET);
+ /* Change from V2 is the uint32_t acct_ctrl */
+ pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET);
+ pdb_set_logon_divs(sampass, logon_divs, PDB_SET);
+ pdb_set_hours(sampass, hours, hours_len, PDB_SET);
+
+done:
+
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ SAFE_FREE(nt_username);
+ SAFE_FREE(fullname);
+ SAFE_FREE(homedir);
+ SAFE_FREE(dir_drive);
+ SAFE_FREE(logon_script);
+ SAFE_FREE(profile_path);
+ SAFE_FREE(acct_desc);
+ SAFE_FREE(workstations);
+ SAFE_FREE(munged_dial);
+ SAFE_FREE(comment);
+ SAFE_FREE(lm_pw_ptr);
+ SAFE_FREE(nt_pw_ptr);
+ SAFE_FREE(nt_pw_hist_ptr);
+ SAFE_FREE(hours);
+
+ return ret;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static uint32_t init_buffer_from_samu_v3 (uint8_t **buf, struct samu *sampass, bool size_only)
+{
+ size_t len, buflen;
+
+ /* times are stored as 32bit integer
+ take care on system with 64bit wide time_t
+ --SSS */
+ uint32_t logon_time,
+ logoff_time,
+ kickoff_time,
+ bad_password_time,
+ pass_last_set_time,
+ pass_can_change_time,
+ pass_must_change_time;
+
+ uint32_t user_rid, group_rid;
+
+ const char *username;
+ const char *domain;
+ const char *nt_username;
+ const char *dir_drive;
+ const char *comment;
+ const char *munged_dial;
+ const char *fullname;
+ const char *homedir;
+ const char *logon_script;
+ const char *profile_path;
+ const char *acct_desc;
+ const char *workstations;
+ uint32_t username_len, domain_len, nt_username_len,
+ dir_drive_len, comment_len, munged_dial_len,
+ fullname_len, homedir_len, logon_script_len,
+ profile_path_len, acct_desc_len, workstations_len;
+
+ const uint8_t *lm_pw;
+ const uint8_t *nt_pw;
+ const uint8_t *nt_pw_hist;
+ uint32_t lm_pw_len = 16;
+ uint32_t nt_pw_len = 16;
+ uint32_t nt_pw_hist_len;
+ uint32_t pwHistLen = 0;
+
+ *buf = NULL;
+ buflen = 0;
+
+ logon_time = convert_time_t_to_uint32_t(pdb_get_logon_time(sampass));
+ logoff_time = convert_time_t_to_uint32_t(pdb_get_logoff_time(sampass));
+ kickoff_time = convert_time_t_to_uint32_t(pdb_get_kickoff_time(sampass));
+ bad_password_time = convert_time_t_to_uint32_t(pdb_get_bad_password_time(sampass));
+ pass_can_change_time = convert_time_t_to_uint32_t(pdb_get_pass_can_change_time_noncalc(sampass));
+ pass_must_change_time = convert_time_t_to_uint32_t(pdb_get_pass_must_change_time(sampass));
+ pass_last_set_time = convert_time_t_to_uint32_t(pdb_get_pass_last_set_time(sampass));
+
+ user_rid = pdb_get_user_rid(sampass);
+ group_rid = pdb_get_group_rid(sampass);
+
+ username = pdb_get_username(sampass);
+ if (username) {
+ username_len = strlen(username) +1;
+ } else {
+ username_len = 0;
+ }
+
+ domain = pdb_get_domain(sampass);
+ if (domain) {
+ domain_len = strlen(domain) +1;
+ } else {
+ domain_len = 0;
+ }
+
+ nt_username = pdb_get_nt_username(sampass);
+ if (nt_username) {
+ nt_username_len = strlen(nt_username) +1;
+ } else {
+ nt_username_len = 0;
+ }
+
+ fullname = pdb_get_fullname(sampass);
+ if (fullname) {
+ fullname_len = strlen(fullname) +1;
+ } else {
+ fullname_len = 0;
+ }
+
+ /*
+ * Only updates fields which have been set (not defaults from smb.conf)
+ */
+
+ if (!IS_SAM_DEFAULT(sampass, PDB_DRIVE)) {
+ dir_drive = pdb_get_dir_drive(sampass);
+ } else {
+ dir_drive = NULL;
+ }
+ if (dir_drive) {
+ dir_drive_len = strlen(dir_drive) +1;
+ } else {
+ dir_drive_len = 0;
+ }
+
+ if (!IS_SAM_DEFAULT(sampass, PDB_SMBHOME)) {
+ homedir = pdb_get_homedir(sampass);
+ } else {
+ homedir = NULL;
+ }
+ if (homedir) {
+ homedir_len = strlen(homedir) +1;
+ } else {
+ homedir_len = 0;
+ }
+
+ if (!IS_SAM_DEFAULT(sampass, PDB_LOGONSCRIPT)) {
+ logon_script = pdb_get_logon_script(sampass);
+ } else {
+ logon_script = NULL;
+ }
+ if (logon_script) {
+ logon_script_len = strlen(logon_script) +1;
+ } else {
+ logon_script_len = 0;
+ }
+
+ if (!IS_SAM_DEFAULT(sampass, PDB_PROFILE)) {
+ profile_path = pdb_get_profile_path(sampass);
+ } else {
+ profile_path = NULL;
+ }
+ if (profile_path) {
+ profile_path_len = strlen(profile_path) +1;
+ } else {
+ profile_path_len = 0;
+ }
+
+ lm_pw = pdb_get_lanman_passwd(sampass);
+ if (!lm_pw) {
+ lm_pw_len = 0;
+ }
+
+ nt_pw = pdb_get_nt_passwd(sampass);
+ if (!nt_pw) {
+ nt_pw_len = 0;
+ }
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen);
+ nt_pw_hist = pdb_get_pw_history(sampass, &nt_pw_hist_len);
+ if (pwHistLen && nt_pw_hist && nt_pw_hist_len) {
+ nt_pw_hist_len *= PW_HISTORY_ENTRY_LEN;
+ } else {
+ nt_pw_hist_len = 0;
+ }
+
+ acct_desc = pdb_get_acct_desc(sampass);
+ if (acct_desc) {
+ acct_desc_len = strlen(acct_desc) +1;
+ } else {
+ acct_desc_len = 0;
+ }
+
+ workstations = pdb_get_workstations(sampass);
+ if (workstations) {
+ workstations_len = strlen(workstations) +1;
+ } else {
+ workstations_len = 0;
+ }
+
+ comment = pdb_get_comment(sampass);
+ if (comment) {
+ comment_len = strlen(comment) +1;
+ } else {
+ comment_len = 0;
+ }
+
+ munged_dial = pdb_get_munged_dial(sampass);
+ if (munged_dial) {
+ munged_dial_len = strlen(munged_dial) +1;
+ } else {
+ munged_dial_len = 0;
+ }
+
+/* SAMU_BUFFER_FORMAT_V3 "dddddddBBBBBBBBBBBBddBBBdwdBwwd" */
+
+ /* one time to get the size needed */
+ len = tdb_pack(NULL, 0, SAMU_BUFFER_FORMAT_V3,
+ logon_time, /* d */
+ logoff_time, /* d */
+ kickoff_time, /* d */
+ bad_password_time, /* d */
+ pass_last_set_time, /* d */
+ pass_can_change_time, /* d */
+ pass_must_change_time, /* d */
+ username_len, username, /* B */
+ domain_len, domain, /* B */
+ nt_username_len, nt_username, /* B */
+ fullname_len, fullname, /* B */
+ homedir_len, homedir, /* B */
+ dir_drive_len, dir_drive, /* B */
+ logon_script_len, logon_script, /* B */
+ profile_path_len, profile_path, /* B */
+ acct_desc_len, acct_desc, /* B */
+ workstations_len, workstations, /* B */
+ comment_len, comment, /* B */
+ munged_dial_len, munged_dial, /* B */
+ user_rid, /* d */
+ group_rid, /* d */
+ lm_pw_len, lm_pw, /* B */
+ nt_pw_len, nt_pw, /* B */
+ nt_pw_hist_len, nt_pw_hist, /* B */
+ pdb_get_acct_ctrl(sampass), /* d */
+ pdb_get_logon_divs(sampass), /* w */
+ pdb_get_hours_len(sampass), /* d */
+ MAX_HOURS_LEN, pdb_get_hours(sampass), /* B */
+ pdb_get_bad_password_count(sampass), /* w */
+ pdb_get_logon_count(sampass), /* w */
+ pdb_get_unknown_6(sampass)); /* d */
+
+ if (size_only) {
+ return buflen;
+ }
+
+ /* malloc the space needed */
+ if ( (*buf=(uint8_t*)SMB_MALLOC(len)) == NULL) {
+ DEBUG(0,("init_buffer_from_samu_v3: Unable to malloc() memory for buffer!\n"));
+ return (-1);
+ }
+
+ /* now for the real call to tdb_pack() */
+ buflen = tdb_pack(*buf, len, SAMU_BUFFER_FORMAT_V3,
+ logon_time, /* d */
+ logoff_time, /* d */
+ kickoff_time, /* d */
+ bad_password_time, /* d */
+ pass_last_set_time, /* d */
+ pass_can_change_time, /* d */
+ pass_must_change_time, /* d */
+ username_len, username, /* B */
+ domain_len, domain, /* B */
+ nt_username_len, nt_username, /* B */
+ fullname_len, fullname, /* B */
+ homedir_len, homedir, /* B */
+ dir_drive_len, dir_drive, /* B */
+ logon_script_len, logon_script, /* B */
+ profile_path_len, profile_path, /* B */
+ acct_desc_len, acct_desc, /* B */
+ workstations_len, workstations, /* B */
+ comment_len, comment, /* B */
+ munged_dial_len, munged_dial, /* B */
+ user_rid, /* d */
+ group_rid, /* d */
+ lm_pw_len, lm_pw, /* B */
+ nt_pw_len, nt_pw, /* B */
+ nt_pw_hist_len, nt_pw_hist, /* B */
+ pdb_get_acct_ctrl(sampass), /* d */
+ pdb_get_logon_divs(sampass), /* w */
+ pdb_get_hours_len(sampass), /* d */
+ MAX_HOURS_LEN, pdb_get_hours(sampass), /* B */
+ pdb_get_bad_password_count(sampass), /* w */
+ pdb_get_logon_count(sampass), /* w */
+ pdb_get_unknown_6(sampass)); /* d */
+
+ /* check to make sure we got it correct */
+ if (buflen != len) {
+ DEBUG(0, ("init_buffer_from_samu_v3: something odd is going on here: bufflen (%lu) != len (%lu) in tdb_pack operations!\n",
+ (unsigned long)buflen, (unsigned long)len));
+ /* error */
+ SAFE_FREE (*buf);
+ return (-1);
+ }
+
+ return (buflen);
+}
+
+static bool init_samu_from_buffer_v4(struct samu *sampass, uint8_t *buf, uint32_t buflen)
+{
+ /* nothing changed between V3 and V4 */
+ return init_samu_from_buffer_v3(sampass, buf, buflen);
+}
+
+static uint32_t init_buffer_from_samu_v4(uint8_t **buf, struct samu *sampass, bool size_only)
+{
+ /* nothing changed between V3 and V4 */
+ return init_buffer_from_samu_v3(buf, sampass, size_only);
+}
+
+/**********************************************************************
+ Initialize a struct samu struct from a BYTE buffer of size len
+ *********************************************************************/
+
+bool init_samu_from_buffer(struct samu *sampass, uint32_t level,
+ uint8_t *buf, uint32_t buflen)
+{
+ switch (level) {
+ case SAMU_BUFFER_V0:
+ return init_samu_from_buffer_v0(sampass, buf, buflen);
+ case SAMU_BUFFER_V1:
+ return init_samu_from_buffer_v1(sampass, buf, buflen);
+ case SAMU_BUFFER_V2:
+ return init_samu_from_buffer_v2(sampass, buf, buflen);
+ case SAMU_BUFFER_V3:
+ return init_samu_from_buffer_v3(sampass, buf, buflen);
+ case SAMU_BUFFER_V4:
+ return init_samu_from_buffer_v4(sampass, buf, buflen);
+ }
+
+ return false;
+}
+
+/**********************************************************************
+ Initialize a BYTE buffer from a struct samu struct
+ *********************************************************************/
+
+uint32_t init_buffer_from_samu (uint8_t **buf, struct samu *sampass, bool size_only)
+{
+ return init_buffer_from_samu_v4(buf, sampass, size_only);
+}
+
+/*********************************************************************
+*********************************************************************/
+
+bool pdb_copy_sam_account(struct samu *dst, struct samu *src )
+{
+ uint8_t *buf = NULL;
+ int len;
+
+ len = init_buffer_from_samu(&buf, src, False);
+ if (len == -1 || !buf) {
+ SAFE_FREE(buf);
+ return False;
+ }
+
+ if (!init_samu_from_buffer( dst, SAMU_BUFFER_LATEST, buf, len )) {
+ free(buf);
+ return False;
+ }
+
+ dst->methods = src->methods;
+
+ if ( src->unix_pw ) {
+ dst->unix_pw = tcopy_passwd( dst, src->unix_pw );
+ if (!dst->unix_pw) {
+ free(buf);
+ return False;
+ }
+ }
+
+ if (src->group_sid) {
+ pdb_set_group_sid(dst, src->group_sid, PDB_SET);
+ }
+
+ free(buf);
+ return True;
+}
+
+/*********************************************************************
+ Update the bad password count checking the PDB_POLICY_RESET_COUNT_TIME
+*********************************************************************/
+
+bool pdb_update_bad_password_count(struct samu *sampass, bool *updated)
+{
+ time_t LastBadPassword;
+ uint16_t BadPasswordCount;
+ uint32_t resettime;
+ bool res;
+
+ BadPasswordCount = pdb_get_bad_password_count(sampass);
+ if (!BadPasswordCount) {
+ DEBUG(9, ("No bad password attempts.\n"));
+ return True;
+ }
+
+ become_root();
+ res = pdb_get_account_policy(PDB_POLICY_RESET_COUNT_TIME, &resettime);
+ unbecome_root();
+
+ if (!res) {
+ DEBUG(0, ("pdb_update_bad_password_count: pdb_get_account_policy failed.\n"));
+ return False;
+ }
+
+ /* First, check if there is a reset time to compare */
+ if ((resettime == (uint32_t) -1) || (resettime == 0)) {
+ DEBUG(9, ("No reset time, can't reset bad pw count\n"));
+ return True;
+ }
+
+ LastBadPassword = pdb_get_bad_password_time(sampass);
+ DEBUG(7, ("LastBadPassword=%d, resettime=%d, current time=%d.\n",
+ (uint32_t) LastBadPassword, resettime, (uint32_t)time(NULL)));
+ if (time(NULL) > (LastBadPassword + convert_uint32_t_to_time_t(resettime)*60)){
+ pdb_set_bad_password_count(sampass, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(sampass, 0, PDB_CHANGED);
+ if (updated) {
+ *updated = True;
+ }
+ }
+
+ return True;
+}
+
+/*********************************************************************
+ Update the ACB_AUTOLOCK flag checking the PDB_POLICY_LOCK_ACCOUNT_DURATION
+*********************************************************************/
+
+bool pdb_update_autolock_flag(struct samu *sampass, bool *updated)
+{
+ uint32_t duration;
+ time_t LastBadPassword;
+ bool res;
+
+ if (!(pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK)) {
+ DEBUG(9, ("pdb_update_autolock_flag: Account %s not autolocked, no check needed\n",
+ pdb_get_username(sampass)));
+ return True;
+ }
+
+ become_root();
+ res = pdb_get_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, &duration);
+ unbecome_root();
+
+ if (!res) {
+ DEBUG(0, ("pdb_update_autolock_flag: pdb_get_account_policy failed.\n"));
+ return False;
+ }
+
+ /* First, check if there is a duration to compare */
+ if ((duration == (uint32_t) -1) || (duration == 0)) {
+ DEBUG(9, ("pdb_update_autolock_flag: No reset duration, can't reset autolock\n"));
+ return True;
+ }
+
+ LastBadPassword = pdb_get_bad_password_time(sampass);
+ DEBUG(7, ("pdb_update_autolock_flag: Account %s, LastBadPassword=%d, duration=%d, current time =%d.\n",
+ pdb_get_username(sampass), (uint32_t)LastBadPassword, duration*60, (uint32_t)time(NULL)));
+
+ if (LastBadPassword == (time_t)0) {
+ DEBUG(1,("pdb_update_autolock_flag: Account %s "
+ "administratively locked out with no bad password "
+ "time. Leaving locked out.\n",
+ pdb_get_username(sampass) ));
+ return True;
+ }
+
+ if ((time(NULL) > (LastBadPassword + convert_uint32_t_to_time_t(duration) * 60))) {
+ pdb_set_acct_ctrl(sampass,
+ pdb_get_acct_ctrl(sampass) & ~ACB_AUTOLOCK,
+ PDB_CHANGED);
+ pdb_set_bad_password_count(sampass, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(sampass, 0, PDB_CHANGED);
+ if (updated) {
+ *updated = True;
+ }
+ }
+
+ return True;
+}
+
+/*********************************************************************
+ Increment the bad_password_count
+*********************************************************************/
+
+bool pdb_increment_bad_password_count(struct samu *sampass)
+{
+ uint32_t account_policy_lockout;
+ bool autolock_updated = False, badpw_updated = False;
+ bool ret;
+
+ /* Retrieve the account lockout policy */
+ become_root();
+ ret = pdb_get_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, &account_policy_lockout);
+ unbecome_root();
+ if ( !ret ) {
+ DEBUG(0, ("pdb_increment_bad_password_count: pdb_get_account_policy failed.\n"));
+ return False;
+ }
+
+ /* If there is no policy, we don't need to continue checking */
+ if (!account_policy_lockout) {
+ DEBUG(9, ("No lockout policy, don't track bad passwords\n"));
+ return True;
+ }
+
+ /* Check if the autolock needs to be cleared */
+ if (!pdb_update_autolock_flag(sampass, &autolock_updated))
+ return False;
+
+ /* Check if the badpw count needs to be reset */
+ if (!pdb_update_bad_password_count(sampass, &badpw_updated))
+ return False;
+
+ /*
+ Ok, now we can assume that any resetting that needs to be
+ done has been done, and just get on with incrementing
+ and autolocking if necessary
+ */
+
+ pdb_set_bad_password_count(sampass,
+ pdb_get_bad_password_count(sampass)+1,
+ PDB_CHANGED);
+ pdb_set_bad_password_time(sampass, time(NULL), PDB_CHANGED);
+
+
+ if (pdb_get_bad_password_count(sampass) < account_policy_lockout)
+ return True;
+
+ if (!pdb_set_acct_ctrl(sampass,
+ pdb_get_acct_ctrl(sampass) | ACB_AUTOLOCK,
+ PDB_CHANGED)) {
+ DEBUG(1, ("pdb_increment_bad_password_count:failed to set 'autolock' flag. \n"));
+ return False;
+ }
+
+ return True;
+}
+
+bool is_dc_trusted_domain_situation(const char *domain_name)
+{
+ return IS_DC && !strequal(domain_name, lp_workgroup());
+}
+
+/*******************************************************************
+ Wrapper around retrieving the clear text trust account password.
+ appropriate account name is stored in account_name.
+ Caller must free password, but not account_name.
+*******************************************************************/
+
+static bool get_trust_pw_clear2(const char *domain,
+ const char **account_name,
+ enum netr_SchannelType *channel,
+ char **cur_pw,
+ time_t *_last_set_time,
+ char **prev_pw)
+{
+ char *pwd;
+ time_t last_set_time;
+
+ if (cur_pw != NULL) {
+ *cur_pw = NULL;
+ }
+ if (_last_set_time != NULL) {
+ *_last_set_time = 0;
+ }
+ if (prev_pw != NULL) {
+ *prev_pw = NULL;
+ }
+
+ /* if we are a DC and this is not our domain, then lookup an account
+ * for the domain trust */
+
+ if (is_dc_trusted_domain_situation(domain)) {
+ if (!lp_allow_trusted_domains()) {
+ return false;
+ }
+
+ if (!pdb_get_trusteddom_pw(domain, cur_pw, NULL,
+ &last_set_time))
+ {
+ DEBUG(0, ("get_trust_pw: could not fetch trust "
+ "account password for trusted domain %s\n",
+ domain));
+ return false;
+ }
+
+ if (channel != NULL) {
+ *channel = SEC_CHAN_DOMAIN;
+ }
+
+ if (account_name != NULL) {
+ *account_name = lp_workgroup();
+ }
+
+ if (_last_set_time != NULL) {
+ *_last_set_time = last_set_time;
+ }
+
+ return true;
+ }
+
+ /*
+ * Since we can only be member of one single domain, we are now
+ * in a member situation:
+ *
+ * - Either we are a DC (selfjoined) and the domain is our
+ * own domain.
+ * - Or we are on a member and the domain is our own or some
+ * other (potentially trusted) domain.
+ *
+ * In both cases, we can only get the machine account password
+ * for our own domain to connect to our own dc. (For a member,
+ * request to trusted domains are performed through our dc.)
+ *
+ * So we simply use our own domain name to retrieve the
+ * machine account password and ignore the request domain here.
+ */
+
+ pwd = secrets_fetch_machine_password(lp_workgroup(), &last_set_time, channel);
+
+ if (pwd != NULL) {
+ struct timeval expire;
+
+ *cur_pw = pwd;
+
+ if (account_name != NULL) {
+ *account_name = lp_netbios_name();
+ }
+
+ if (_last_set_time != NULL) {
+ *_last_set_time = last_set_time;
+ }
+
+ if (prev_pw == NULL) {
+ return true;
+ }
+
+ ZERO_STRUCT(expire);
+ expire.tv_sec = lp_machine_password_timeout();
+ expire.tv_sec /= 2;
+ expire.tv_sec += last_set_time;
+ if (timeval_expired(&expire)) {
+ return true;
+ }
+
+ pwd = secrets_fetch_prev_machine_password(lp_workgroup());
+ if (pwd != NULL) {
+ *prev_pw = pwd;
+ }
+
+ return true;
+ }
+
+ DEBUG(5, ("get_trust_pw_clear2: could not fetch clear text trust "
+ "account password for domain %s\n", domain));
+ return false;
+}
+
+bool get_trust_pw_clear(const char *domain, char **ret_pwd,
+ const char **account_name,
+ enum netr_SchannelType *channel)
+{
+ return get_trust_pw_clear2(domain,
+ account_name,
+ channel,
+ ret_pwd,
+ NULL,
+ NULL);
+}
+
+/*******************************************************************
+ Wrapper around retrieving the trust account password.
+ appropriate account name is stored in account_name.
+*******************************************************************/
+
+static bool get_trust_pw_hash2(const char *domain,
+ const char **account_name,
+ enum netr_SchannelType *channel,
+ struct samr_Password *current_nt_hash,
+ time_t *last_set_time,
+ struct samr_Password **_previous_nt_hash)
+{
+ char *cur_pw = NULL;
+ char *prev_pw = NULL;
+ char **_prev_pw = NULL;
+ bool ok;
+
+ if (_previous_nt_hash != NULL) {
+ *_previous_nt_hash = NULL;
+ _prev_pw = &prev_pw;
+ }
+
+ ok = get_trust_pw_clear2(domain, account_name, channel,
+ &cur_pw, last_set_time, _prev_pw);
+ if (ok) {
+ struct samr_Password *previous_nt_hash = NULL;
+
+ E_md4hash(cur_pw, current_nt_hash->hash);
+ BURN_FREE_STR(cur_pw);
+
+ if (prev_pw == NULL) {
+ return true;
+ }
+
+ previous_nt_hash = SMB_MALLOC_P(struct samr_Password);
+ if (previous_nt_hash == NULL) {
+ return false;
+ }
+
+ E_md4hash(prev_pw, previous_nt_hash->hash);
+ BURN_FREE_STR(prev_pw);
+
+ *_previous_nt_hash = previous_nt_hash;
+ return true;
+ } else if (is_dc_trusted_domain_situation(domain)) {
+ return false;
+ }
+
+ /* as a fallback, try to get the hashed pwd directly from the tdb... */
+
+ if (secrets_fetch_trust_account_password_legacy(domain,
+ current_nt_hash->hash,
+ last_set_time,
+ channel))
+ {
+ if (account_name != NULL) {
+ *account_name = lp_netbios_name();
+ }
+
+ return true;
+ }
+
+ DEBUG(5, ("get_trust_pw_hash: could not fetch trust account "
+ "password for domain %s\n", domain));
+ return False;
+}
+
+bool get_trust_pw_hash(const char *domain, uint8_t ret_pwd[16],
+ const char **account_name,
+ enum netr_SchannelType *channel)
+{
+ struct samr_Password current_nt_hash;
+ bool ok;
+
+ ok = get_trust_pw_hash2(domain, account_name, channel,
+ &current_nt_hash, NULL, NULL);
+ if (!ok) {
+ return false;
+ }
+
+ memcpy(ret_pwd, current_nt_hash.hash, sizeof(current_nt_hash.hash));
+ return true;
+}
+
+NTSTATUS pdb_get_trust_credentials(const char *netbios_domain,
+ const char *dns_domain, /* optional */
+ TALLOC_CTX *mem_ctx,
+ struct cli_credentials **_creds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct loadparm_context *lp_ctx;
+ enum netr_SchannelType channel;
+ time_t last_set_time;
+ const char *_account_name;
+ const char *account_name;
+ char *cur_pw = NULL;
+ char *prev_pw = NULL;
+ struct samr_Password cur_nt_hash;
+ struct cli_credentials *creds = NULL;
+ bool ok;
+
+ /*
+ * If this is our primary trust relationship, use the common
+ * code to read the secrets.ldb or secrets.tdb file.
+ */
+ if (strequal(netbios_domain, lp_workgroup())) {
+ struct db_context *db_ctx = secrets_db_ctx();
+ if (db_ctx == NULL) {
+ DEBUG(1, ("failed to open secrets.tdb to obtain our trust credentials for %s\n",
+ netbios_domain));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(1, ("loadparm_init_s3 failed\n"));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ creds = cli_credentials_init(mem_ctx);
+ if (creds == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ ok = cli_credentials_set_conf(creds, lp_ctx);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ status = cli_credentials_set_machine_account_db_ctx(creds,
+ lp_ctx,
+ db_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ goto done;
+ } else if (!IS_DC) {
+ DEBUG(1, ("Refusing to get trust account info for %s, "
+ "which is not our primary domain %s, "
+ "as we are not a DC\n",
+ netbios_domain, lp_workgroup()));
+ status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto fail;
+ }
+
+ status = pdb_get_trusteddom_creds(netbios_domain, mem_ctx, &creds);
+ if (NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ goto fail;
+ }
+
+ ok = get_trust_pw_clear2(netbios_domain,
+ &_account_name,
+ &channel,
+ &cur_pw,
+ &last_set_time,
+ &prev_pw);
+ if (!ok) {
+ ok = get_trust_pw_hash2(netbios_domain,
+ &_account_name,
+ &channel,
+ &cur_nt_hash,
+ &last_set_time,
+ NULL);
+ if (!ok) {
+ DEBUG(1, ("get_trust_pw_*2 failed for domain[%s]\n",
+ netbios_domain));
+ status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto fail;
+ }
+ }
+
+ account_name = talloc_asprintf(frame, "%s$", _account_name);
+ if (account_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(1, ("loadparm_init_s3 failed\n"));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ creds = cli_credentials_init(mem_ctx);
+ if (creds == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ ok = cli_credentials_set_conf(creds, lp_ctx);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ cli_credentials_set_secure_channel_type(creds, channel);
+ cli_credentials_set_password_last_changed_time(creds, last_set_time);
+
+ ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (dns_domain != NULL) {
+ ok = cli_credentials_set_realm(creds, dns_domain, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ /*
+ * It's not possible to use NTLMSSP with a domain trust account.
+ */
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+ } else {
+ /*
+ * We can't use kerberos against an NT4 domain.
+ *
+ * We should have a mode that also disallows NTLMSSP here,
+ * as only NETLOGON SCHANNEL is possible.
+ */
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+ }
+
+ ok = cli_credentials_set_username(creds, account_name, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (cur_pw == NULL) {
+ ok = cli_credentials_set_nt_hash(creds, &cur_nt_hash, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ /*
+ * We currently can't do kerberos just with an NTHASH.
+ */
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+ goto done;
+ }
+
+ ok = cli_credentials_set_password(creds, cur_pw, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (prev_pw != NULL) {
+ ok = cli_credentials_set_old_password(creds, prev_pw, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ done:
+ *_creds = creds;
+ creds = NULL;
+ status = NT_STATUS_OK;
+ fail:
+ TALLOC_FREE(creds);
+ SAFE_FREE(cur_pw);
+ SAFE_FREE(prev_pw);
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/passdb/pdb_compat.c b/source3/passdb/pdb_compat.c
new file mode 100644
index 0000000..2a32ec8
--- /dev/null
+++ b/source3/passdb/pdb_compat.c
@@ -0,0 +1,105 @@
+/*
+ Unix SMB/CIFS implementation.
+ struct samu access routines
+ Copyright (C) Jeremy Allison 1996-2001
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Gerald (Jerry) Carter 2000-2001
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Stefan (metze) Metzmacher 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "passdb.h"
+#include "../libcli/security/security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+uint32_t pdb_get_user_rid (const struct samu *sampass)
+{
+ uint32_t u_rid;
+
+ if (sampass)
+ if (sid_peek_check_rid(get_global_sam_sid(), pdb_get_user_sid(sampass),&u_rid))
+ return u_rid;
+
+ return (0);
+}
+
+uint32_t pdb_get_group_rid (struct samu *sampass)
+{
+ uint32_t g_rid;
+
+ if (sampass)
+ if (sid_peek_check_rid(get_global_sam_sid(), pdb_get_group_sid(sampass),&g_rid))
+ return g_rid;
+ return (0);
+}
+
+bool pdb_set_user_sid_from_rid (struct samu *sampass, uint32_t rid, enum pdb_value_state flag)
+{
+ struct dom_sid u_sid;
+ struct dom_sid_buf buf;
+ const struct dom_sid *global_sam_sid;
+
+ if (!sampass)
+ return False;
+
+ if (!(global_sam_sid = get_global_sam_sid())) {
+ DEBUG(1, ("pdb_set_user_sid_from_rid: Could not read global sam sid!\n"));
+ return False;
+ }
+
+ if (!sid_compose(&u_sid, global_sam_sid, rid)) {
+ return False;
+ }
+
+ if (!pdb_set_user_sid(sampass, &u_sid, flag))
+ return False;
+
+ DEBUG(10, ("pdb_set_user_sid_from_rid:\n\tsetting user sid %s from rid %d\n",
+ dom_sid_str_buf(&u_sid, &buf), rid));
+
+ return True;
+}
+
+bool pdb_set_group_sid_from_rid (struct samu *sampass, uint32_t grid, enum pdb_value_state flag)
+{
+ struct dom_sid g_sid;
+ struct dom_sid_buf buf;
+ const struct dom_sid *global_sam_sid;
+
+ if (!sampass)
+ return False;
+
+ if (!(global_sam_sid = get_global_sam_sid())) {
+ DEBUG(1, ("pdb_set_user_sid_from_rid: Could not read global sam sid!\n"));
+ return False;
+ }
+
+ if (!sid_compose(&g_sid, global_sam_sid, grid)) {
+ return False;
+ }
+
+ if (!pdb_set_group_sid(sampass, &g_sid, flag))
+ return False;
+
+ DEBUG(10, ("pdb_set_group_sid_from_rid:\n\tsetting group sid %s from rid %d\n",
+ dom_sid_str_buf(&g_sid, &buf), grid));
+
+ return True;
+}
+
diff --git a/source3/passdb/pdb_get_set.c b/source3/passdb/pdb_get_set.c
new file mode 100644
index 0000000..6789cc0
--- /dev/null
+++ b/source3/passdb/pdb_get_set.c
@@ -0,0 +1,1163 @@
+/*
+ Unix SMB/CIFS implementation.
+ struct samu access routines
+ Copyright (C) Jeremy Allison 1996-2001
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Gerald (Jerry) Carter 2000-2006
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Stefan (metze) Metzmacher 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "passdb.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/bitmap.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+/**
+ * @todo Redefine this to NULL, but this changes the API because
+ * much of samba assumes that the pdb_get...() functions
+ * return strings. (ie not null-pointers).
+ * See also pdb_fill_default_sam().
+ */
+
+#define PDB_NOT_QUITE_NULL ""
+
+/*********************************************************************
+ Test if a change time is a max value. Copes with old and new values
+ of max.
+ ********************************************************************/
+
+bool pdb_is_password_change_time_max(time_t test_time)
+{
+ if (test_time == get_time_t_max()) {
+ return true;
+ }
+#if (defined(SIZEOF_TIME_T) && (SIZEOF_TIME_T == 8))
+ if (test_time == 0x7FFFFFFFFFFFFFFFLL) {
+ return true;
+ }
+#endif
+ if (test_time == 0x7FFFFFFF) {
+ return true;
+ }
+ return false;
+}
+
+/*********************************************************************
+ Return an unchanging version of max password change time - 0x7FFFFFFF.
+ ********************************************************************/
+
+static time_t pdb_password_change_time_max(void)
+{
+ return 0x7FFFFFFF;
+}
+
+/*********************************************************************
+ Collection of get...() functions for struct samu.
+ ********************************************************************/
+
+uint32_t pdb_get_acct_ctrl(const struct samu *sampass)
+{
+ return sampass->acct_ctrl;
+}
+
+time_t pdb_get_logon_time(const struct samu *sampass)
+{
+ return sampass->logon_time;
+}
+
+time_t pdb_get_logoff_time(const struct samu *sampass)
+{
+ return sampass->logoff_time;
+}
+
+time_t pdb_get_kickoff_time(const struct samu *sampass)
+{
+ return sampass->kickoff_time;
+}
+
+time_t pdb_get_bad_password_time(const struct samu *sampass)
+{
+ return sampass->bad_password_time;
+}
+
+time_t pdb_get_pass_last_set_time(const struct samu *sampass)
+{
+ return sampass->pass_last_set_time;
+}
+
+time_t pdb_get_pass_can_change_time(const struct samu *sampass)
+{
+ uint32_t allow;
+
+ /* if the last set time is zero, it means the user cannot
+ change their password, and this time must be zero. jmcd
+ */
+ if (sampass->pass_last_set_time == 0)
+ return (time_t) 0;
+
+ /* if the time is max, and the field has been changed,
+ we're trying to update this real value from the sampass
+ to indicate that the user cannot change their password. jmcd
+ */
+ if (pdb_is_password_change_time_max(sampass->pass_can_change_time) &&
+ IS_SAM_CHANGED(sampass, PDB_CANCHANGETIME))
+ return sampass->pass_can_change_time;
+
+ if (!pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, &allow))
+ allow = 0;
+
+ /* in normal cases, just calculate it from policy */
+ return sampass->pass_last_set_time + allow;
+}
+
+/* we need this for loading from the backend, so that we don't overwrite
+ non-changed max times, otherwise the pass_can_change checking won't work */
+time_t pdb_get_pass_can_change_time_noncalc(const struct samu *sampass)
+{
+ return sampass->pass_can_change_time;
+}
+
+time_t pdb_get_pass_must_change_time(const struct samu *sampass)
+{
+ uint32_t expire;
+
+ if (sampass->pass_last_set_time == 0)
+ return (time_t) 0;
+
+ if (sampass->acct_ctrl & ACB_PWNOEXP)
+ return pdb_password_change_time_max();
+
+ if (!pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &expire)
+ || expire == (uint32_t)-1 || expire == 0)
+ return get_time_t_max();
+
+ return sampass->pass_last_set_time + expire;
+}
+
+bool pdb_get_pass_can_change(const struct samu *sampass)
+{
+ if (pdb_is_password_change_time_max(sampass->pass_can_change_time))
+ return False;
+ return True;
+}
+
+uint16_t pdb_get_logon_divs(const struct samu *sampass)
+{
+ return sampass->logon_divs;
+}
+
+uint32_t pdb_get_hours_len(const struct samu *sampass)
+{
+ return sampass->hours_len;
+}
+
+const uint8_t *pdb_get_hours(const struct samu *sampass)
+{
+ return (sampass->hours);
+}
+
+const uint8_t *pdb_get_nt_passwd(const struct samu *sampass)
+{
+ SMB_ASSERT((!sampass->nt_pw.data)
+ || sampass->nt_pw.length == NT_HASH_LEN);
+ return (uint8_t *)sampass->nt_pw.data;
+}
+
+const uint8_t *pdb_get_lanman_passwd(const struct samu *sampass)
+{
+ SMB_ASSERT((!sampass->lm_pw.data)
+ || sampass->lm_pw.length == LM_HASH_LEN);
+ return (uint8_t *)sampass->lm_pw.data;
+}
+
+const uint8_t *pdb_get_pw_history(const struct samu *sampass, uint32_t *current_hist_len)
+{
+ SMB_ASSERT((!sampass->nt_pw_his.data)
+ || ((sampass->nt_pw_his.length % PW_HISTORY_ENTRY_LEN) == 0));
+ *current_hist_len = sampass->nt_pw_his.length / PW_HISTORY_ENTRY_LEN;
+ return (uint8_t *)sampass->nt_pw_his.data;
+}
+
+/* Return the plaintext password if known. Most of the time
+ it isn't, so don't assume anything magic about this function.
+
+ Used to pass the plaintext to passdb backends that might
+ want to store more than just the NTLM hashes.
+*/
+const char *pdb_get_plaintext_passwd(const struct samu *sampass)
+{
+ return sampass->plaintext_pw;
+}
+
+const struct dom_sid *pdb_get_user_sid(const struct samu *sampass)
+{
+ return &sampass->user_sid;
+}
+
+const struct dom_sid *pdb_get_group_sid(struct samu *sampass)
+{
+ NTSTATUS status;
+
+ /* Return the cached group SID if we have that */
+ if (sampass->group_sid) {
+ return sampass->group_sid;
+ }
+
+ /* No algorithmic mapping, meaning that we have to figure out the
+ primary group SID according to group mapping and the user SID must
+ be a newly allocated one. We rely on the user's Unix primary gid.
+ We have no choice but to fail if we can't find it. */
+ status = get_primary_group_sid(sampass,
+ pdb_get_username(sampass),
+ &sampass->unix_pw,
+ &sampass->group_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ return sampass->group_sid;
+}
+
+/**
+ * Get flags showing what is initialised in the struct samu
+ * @param sampass the struct samu in question
+ * @return the flags indicating the members initialised in the struct.
+ **/
+
+enum pdb_value_state pdb_get_init_flags(const struct samu *sampass, enum pdb_elements element)
+{
+ enum pdb_value_state ret = PDB_DEFAULT;
+
+ if (!sampass->change_flags || !sampass->set_flags)
+ return ret;
+
+ if (bitmap_query(sampass->set_flags, element)) {
+ DEBUG(11, ("element %d: SET\n", element));
+ ret = PDB_SET;
+ }
+
+ if (bitmap_query(sampass->change_flags, element)) {
+ DEBUG(11, ("element %d: CHANGED\n", element));
+ ret = PDB_CHANGED;
+ }
+
+ if (ret == PDB_DEFAULT) {
+ DEBUG(11, ("element %d: DEFAULT\n", element));
+ }
+
+ return ret;
+}
+
+const char *pdb_get_username(const struct samu *sampass)
+{
+ return sampass->username;
+}
+
+const char *pdb_get_domain(const struct samu *sampass)
+{
+ return sampass->domain;
+}
+
+const char *pdb_get_nt_username(const struct samu *sampass)
+{
+ return sampass->nt_username;
+}
+
+const char *pdb_get_fullname(const struct samu *sampass)
+{
+ return sampass->full_name;
+}
+
+const char *pdb_get_homedir(const struct samu *sampass)
+{
+ return sampass->home_dir;
+}
+
+const char *pdb_get_dir_drive(const struct samu *sampass)
+{
+ return sampass->dir_drive;
+}
+
+const char *pdb_get_logon_script(const struct samu *sampass)
+{
+ return sampass->logon_script;
+}
+
+const char *pdb_get_profile_path(const struct samu *sampass)
+{
+ return sampass->profile_path;
+}
+
+const char *pdb_get_acct_desc(const struct samu *sampass)
+{
+ return sampass->acct_desc;
+}
+
+const char *pdb_get_workstations(const struct samu *sampass)
+{
+ return sampass->workstations;
+}
+
+const char *pdb_get_comment(const struct samu *sampass)
+{
+ return sampass->comment;
+}
+
+const char *pdb_get_munged_dial(const struct samu *sampass)
+{
+ return sampass->munged_dial;
+}
+
+uint16_t pdb_get_bad_password_count(const struct samu *sampass)
+{
+ return sampass->bad_password_count;
+}
+
+uint16_t pdb_get_logon_count(const struct samu *sampass)
+{
+ return sampass->logon_count;
+}
+
+uint16_t pdb_get_country_code(const struct samu *sampass)
+{
+ return sampass->country_code;
+}
+
+uint16_t pdb_get_code_page(const struct samu *sampass)
+{
+ return sampass->code_page;
+}
+
+uint32_t pdb_get_unknown_6(const struct samu *sampass)
+{
+ return sampass->unknown_6;
+}
+
+void *pdb_get_backend_private_data(const struct samu *sampass, const struct pdb_methods *my_methods)
+{
+ if (my_methods == sampass->backend_private_methods) {
+ return sampass->backend_private_data;
+ } else {
+ return NULL;
+ }
+}
+
+/*********************************************************************
+ Collection of set...() functions for struct samu.
+ ********************************************************************/
+
+bool pdb_set_acct_ctrl(struct samu *sampass, uint32_t acct_ctrl, enum pdb_value_state flag)
+{
+ sampass->acct_ctrl = acct_ctrl;
+ return pdb_set_init_flags(sampass, PDB_ACCTCTRL, flag);
+}
+
+bool pdb_set_logon_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag)
+{
+ sampass->logon_time = mytime;
+ return pdb_set_init_flags(sampass, PDB_LOGONTIME, flag);
+}
+
+bool pdb_set_logoff_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag)
+{
+ sampass->logoff_time = mytime;
+ return pdb_set_init_flags(sampass, PDB_LOGOFFTIME, flag);
+}
+
+bool pdb_set_kickoff_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag)
+{
+ sampass->kickoff_time = mytime;
+ return pdb_set_init_flags(sampass, PDB_KICKOFFTIME, flag);
+}
+
+bool pdb_set_bad_password_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag)
+{
+ sampass->bad_password_time = mytime;
+ return pdb_set_init_flags(sampass, PDB_BAD_PASSWORD_TIME, flag);
+}
+
+bool pdb_set_pass_can_change_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag)
+{
+ sampass->pass_can_change_time = mytime;
+ return pdb_set_init_flags(sampass, PDB_CANCHANGETIME, flag);
+}
+
+bool pdb_set_pass_last_set_time(struct samu *sampass, time_t mytime, enum pdb_value_state flag)
+{
+ sampass->pass_last_set_time = mytime;
+ return pdb_set_init_flags(sampass, PDB_PASSLASTSET, flag);
+}
+
+bool pdb_set_hours_len(struct samu *sampass, uint32_t len, enum pdb_value_state flag)
+{
+ sampass->hours_len = len;
+ return pdb_set_init_flags(sampass, PDB_HOURSLEN, flag);
+}
+
+bool pdb_set_logon_divs(struct samu *sampass, uint16_t hours, enum pdb_value_state flag)
+{
+ sampass->logon_divs = hours;
+ return pdb_set_init_flags(sampass, PDB_LOGONDIVS, flag);
+}
+
+/**
+ * Set flags showing what is initialised in the struct samu
+ * @param sampass the struct samu in question
+ * @param flag The *new* flag to be set. Old flags preserved
+ * this flag is only added.
+ **/
+
+bool pdb_set_init_flags(struct samu *sampass, enum pdb_elements element, enum pdb_value_state value_flag)
+{
+ if (!sampass->set_flags) {
+ if ((sampass->set_flags =
+ bitmap_talloc(sampass,
+ PDB_COUNT))==NULL) {
+ DEBUG(0,("bitmap_talloc failed\n"));
+ return False;
+ }
+ }
+ if (!sampass->change_flags) {
+ if ((sampass->change_flags =
+ bitmap_talloc(sampass,
+ PDB_COUNT))==NULL) {
+ DEBUG(0,("bitmap_talloc failed\n"));
+ return False;
+ }
+ }
+
+ switch(value_flag) {
+ case PDB_CHANGED:
+ if (!bitmap_set(sampass->change_flags, element)) {
+ DEBUG(0,("Can't set flag: %d in change_flags.\n",element));
+ return False;
+ }
+ if (!bitmap_set(sampass->set_flags, element)) {
+ DEBUG(0,("Can't set flag: %d in set_flags.\n",element));
+ return False;
+ }
+ DEBUG(11, ("element %d -> now CHANGED\n", element));
+ break;
+ case PDB_SET:
+ if (!bitmap_clear(sampass->change_flags, element)) {
+ DEBUG(0,("Can't set flag: %d in change_flags.\n",element));
+ return False;
+ }
+ if (!bitmap_set(sampass->set_flags, element)) {
+ DEBUG(0,("Can't set flag: %d in set_flags.\n",element));
+ return False;
+ }
+ DEBUG(11, ("element %d -> now SET\n", element));
+ break;
+ case PDB_DEFAULT:
+ default:
+ if (!bitmap_clear(sampass->change_flags, element)) {
+ DEBUG(0,("Can't set flag: %d in change_flags.\n",element));
+ return False;
+ }
+ if (!bitmap_clear(sampass->set_flags, element)) {
+ DEBUG(0,("Can't set flag: %d in set_flags.\n",element));
+ return False;
+ }
+ DEBUG(11, ("element %d -> now DEFAULT\n", element));
+ break;
+ }
+
+ return True;
+}
+
+bool pdb_set_user_sid(struct samu *sampass, const struct dom_sid *u_sid, enum pdb_value_state flag)
+{
+ struct dom_sid_buf buf;
+
+ if (!u_sid)
+ return False;
+
+ sid_copy(&sampass->user_sid, u_sid);
+
+ DEBUG(10, ("pdb_set_user_sid: setting user sid %s\n",
+ dom_sid_str_buf(&sampass->user_sid, &buf)));
+
+ return pdb_set_init_flags(sampass, PDB_USERSID, flag);
+}
+
+bool pdb_set_user_sid_from_string(struct samu *sampass, const char *u_sid, enum pdb_value_state flag)
+{
+ struct dom_sid new_sid;
+
+ if (!u_sid)
+ return False;
+
+ DEBUG(10, ("pdb_set_user_sid_from_string: setting user sid %s\n",
+ u_sid));
+
+ if (!string_to_sid(&new_sid, u_sid)) {
+ DEBUG(1, ("pdb_set_user_sid_from_string: %s isn't a valid SID!\n", u_sid));
+ return False;
+ }
+
+ if (!pdb_set_user_sid(sampass, &new_sid, flag)) {
+ DEBUG(1, ("pdb_set_user_sid_from_string: could not set sid %s on struct samu!\n", u_sid));
+ return False;
+ }
+
+ return True;
+}
+
+/********************************************************************
+ We never fill this in from a passdb backend but rather set is
+ based on the user's primary group membership. However, the
+ struct samu* is overloaded and reused in domain memship code
+ as well and built from the netr_SamInfo3 or PAC so we
+ have to allow the explicitly setting of a group SID here.
+********************************************************************/
+
+bool pdb_set_group_sid(struct samu *sampass, const struct dom_sid *g_sid, enum pdb_value_state flag)
+{
+ gid_t gid;
+ struct dom_sid dug_sid;
+ struct dom_sid_buf buf;
+
+ if (!g_sid)
+ return False;
+
+ if ( !(sampass->group_sid = talloc( sampass, struct dom_sid )) ) {
+ return False;
+ }
+
+ /* if we cannot resolve the SID to gid, then just ignore it and
+ store DOMAIN_USERS as the primary groupSID */
+
+ sid_compose(&dug_sid, get_global_sam_sid(), DOMAIN_RID_USERS);
+
+ if (dom_sid_equal(&dug_sid, g_sid)) {
+ sid_copy(sampass->group_sid, &dug_sid);
+ } else if (sid_to_gid( g_sid, &gid ) ) {
+ sid_copy(sampass->group_sid, g_sid);
+ } else {
+ sid_copy(sampass->group_sid, &dug_sid);
+ }
+
+ DEBUG(10, ("pdb_set_group_sid: setting group sid %s\n",
+ dom_sid_str_buf(sampass->group_sid, &buf)));
+
+ return pdb_set_init_flags(sampass, PDB_GROUPSID, flag);
+}
+
+/*********************************************************************
+ Set the user's UNIX name.
+ ********************************************************************/
+
+bool pdb_set_username(struct samu *sampass, const char *username, enum pdb_value_state flag)
+{
+ if (username) {
+ DEBUG(10, ("pdb_set_username: setting username %s, was %s\n", username,
+ (sampass->username)?(sampass->username):"NULL"));
+
+ sampass->username = talloc_strdup(sampass, username);
+
+ if (!sampass->username) {
+ DEBUG(0, ("pdb_set_username: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->username = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_USERNAME, flag);
+}
+
+/*********************************************************************
+ Set the domain name.
+ ********************************************************************/
+
+bool pdb_set_domain(struct samu *sampass, const char *domain, enum pdb_value_state flag)
+{
+ if (domain) {
+ DEBUG(10, ("pdb_set_domain: setting domain %s, was %s\n", domain,
+ (sampass->domain)?(sampass->domain):"NULL"));
+
+ sampass->domain = talloc_strdup(sampass, domain);
+
+ if (!sampass->domain) {
+ DEBUG(0, ("pdb_set_domain: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->domain = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_DOMAIN, flag);
+}
+
+/*********************************************************************
+ Set the user's NT name.
+ ********************************************************************/
+
+bool pdb_set_nt_username(struct samu *sampass, const char *nt_username, enum pdb_value_state flag)
+{
+ if (nt_username) {
+ DEBUG(10, ("pdb_set_nt_username: setting nt username %s, was %s\n", nt_username,
+ (sampass->nt_username)?(sampass->nt_username):"NULL"));
+
+ sampass->nt_username = talloc_strdup(sampass, nt_username);
+
+ if (!sampass->nt_username) {
+ DEBUG(0, ("pdb_set_nt_username: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->nt_username = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_NTUSERNAME, flag);
+}
+
+/*********************************************************************
+ Set the user's full name.
+ ********************************************************************/
+
+bool pdb_set_fullname(struct samu *sampass, const char *full_name, enum pdb_value_state flag)
+{
+ if (full_name) {
+ DEBUG(10, ("pdb_set_full_name: setting full name %s, was %s\n", full_name,
+ (sampass->full_name)?(sampass->full_name):"NULL"));
+
+ sampass->full_name = talloc_strdup(sampass, full_name);
+
+ if (!sampass->full_name) {
+ DEBUG(0, ("pdb_set_fullname: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->full_name = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_FULLNAME, flag);
+}
+
+/*********************************************************************
+ Set the user's logon script.
+ ********************************************************************/
+
+bool pdb_set_logon_script(struct samu *sampass, const char *logon_script, enum pdb_value_state flag)
+{
+ if (logon_script) {
+ DEBUG(10, ("pdb_set_logon_script: setting logon script %s, was %s\n", logon_script,
+ (sampass->logon_script)?(sampass->logon_script):"NULL"));
+
+ sampass->logon_script = talloc_strdup(sampass, logon_script);
+
+ if (!sampass->logon_script) {
+ DEBUG(0, ("pdb_set_logon_script: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->logon_script = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_LOGONSCRIPT, flag);
+}
+
+/*********************************************************************
+ Set the user's profile path.
+ ********************************************************************/
+
+bool pdb_set_profile_path(struct samu *sampass, const char *profile_path, enum pdb_value_state flag)
+{
+ if (profile_path) {
+ DEBUG(10, ("pdb_set_profile_path: setting profile path %s, was %s\n", profile_path,
+ (sampass->profile_path)?(sampass->profile_path):"NULL"));
+
+ sampass->profile_path = talloc_strdup(sampass, profile_path);
+
+ if (!sampass->profile_path) {
+ DEBUG(0, ("pdb_set_profile_path: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->profile_path = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_PROFILE, flag);
+}
+
+/*********************************************************************
+ Set the user's directory drive.
+ ********************************************************************/
+
+bool pdb_set_dir_drive(struct samu *sampass, const char *dir_drive, enum pdb_value_state flag)
+{
+ if (dir_drive) {
+ DEBUG(10, ("pdb_set_dir_drive: setting dir drive %s, was %s\n", dir_drive,
+ (sampass->dir_drive)?(sampass->dir_drive):"NULL"));
+
+ sampass->dir_drive = talloc_strdup(sampass, dir_drive);
+
+ if (!sampass->dir_drive) {
+ DEBUG(0, ("pdb_set_dir_drive: talloc_strdup() failed!\n"));
+ return False;
+ }
+
+ } else {
+ sampass->dir_drive = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_DRIVE, flag);
+}
+
+/*********************************************************************
+ Set the user's home directory.
+ ********************************************************************/
+
+bool pdb_set_homedir(struct samu *sampass, const char *home_dir, enum pdb_value_state flag)
+{
+ if (home_dir) {
+ DEBUG(10, ("pdb_set_homedir: setting home dir %s, was %s\n", home_dir,
+ (sampass->home_dir)?(sampass->home_dir):"NULL"));
+
+ sampass->home_dir = talloc_strdup(sampass, home_dir);
+
+ if (!sampass->home_dir) {
+ DEBUG(0, ("pdb_set_home_dir: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->home_dir = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_SMBHOME, flag);
+}
+
+/*********************************************************************
+ Set the user's account description.
+ ********************************************************************/
+
+bool pdb_set_acct_desc(struct samu *sampass, const char *acct_desc, enum pdb_value_state flag)
+{
+ if (acct_desc) {
+ sampass->acct_desc = talloc_strdup(sampass, acct_desc);
+
+ if (!sampass->acct_desc) {
+ DEBUG(0, ("pdb_set_acct_desc: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->acct_desc = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_ACCTDESC, flag);
+}
+
+/*********************************************************************
+ Set the user's workstation allowed list.
+ ********************************************************************/
+
+bool pdb_set_workstations(struct samu *sampass, const char *workstations, enum pdb_value_state flag)
+{
+ if (workstations) {
+ DEBUG(10, ("pdb_set_workstations: setting workstations %s, was %s\n", workstations,
+ (sampass->workstations)?(sampass->workstations):"NULL"));
+
+ sampass->workstations = talloc_strdup(sampass, workstations);
+
+ if (!sampass->workstations) {
+ DEBUG(0, ("pdb_set_workstations: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->workstations = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_WORKSTATIONS, flag);
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+bool pdb_set_comment(struct samu *sampass, const char *comment, enum pdb_value_state flag)
+{
+ if (comment) {
+ sampass->comment = talloc_strdup(sampass, comment);
+
+ if (!sampass->comment) {
+ DEBUG(0, ("pdb_set_comment: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->comment = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_COMMENT, flag);
+}
+
+/*********************************************************************
+ Set the user's dial string.
+ ********************************************************************/
+
+bool pdb_set_munged_dial(struct samu *sampass, const char *munged_dial, enum pdb_value_state flag)
+{
+ if (munged_dial) {
+ sampass->munged_dial = talloc_strdup(sampass, munged_dial);
+
+ if (!sampass->munged_dial) {
+ DEBUG(0, ("pdb_set_munged_dial: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->munged_dial = PDB_NOT_QUITE_NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_MUNGEDDIAL, flag);
+}
+
+/*********************************************************************
+ Set the user's NT hash.
+ ********************************************************************/
+
+bool pdb_set_nt_passwd(struct samu *sampass, const uint8_t pwd[NT_HASH_LEN], enum pdb_value_state flag)
+{
+ data_blob_clear_free(&sampass->nt_pw);
+
+ if (pwd) {
+ sampass->nt_pw =
+ data_blob_talloc(sampass, pwd, NT_HASH_LEN);
+ } else {
+ sampass->nt_pw = data_blob_null;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_NTPASSWD, flag);
+}
+
+/*********************************************************************
+ Set the user's LM hash.
+ ********************************************************************/
+
+bool pdb_set_lanman_passwd(struct samu *sampass, const uint8_t pwd[LM_HASH_LEN], enum pdb_value_state flag)
+{
+ data_blob_clear_free(&sampass->lm_pw);
+
+ /* on keep the password if we are allowing LANMAN authentication */
+
+ if (pwd && lp_lanman_auth() ) {
+ sampass->lm_pw = data_blob_talloc(sampass, pwd, LM_HASH_LEN);
+ } else {
+ sampass->lm_pw = data_blob_null;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_LMPASSWD, flag);
+}
+
+/*********************************************************************
+ Set the user's password history hash. historyLen is the number of
+ PW_HISTORY_SALT_LEN+SALTED_MD5_HASH_LEN length
+ entries to store in the history - this must match the size of the uint8_t array
+ in pwd.
+********************************************************************/
+
+bool pdb_set_pw_history(struct samu *sampass, const uint8_t *pwd, uint32_t historyLen, enum pdb_value_state flag)
+{
+ DATA_BLOB new_nt_pw_his = {};
+
+ if (historyLen && pwd){
+ new_nt_pw_his = data_blob_talloc(sampass,
+ pwd, historyLen*PW_HISTORY_ENTRY_LEN);
+ if (new_nt_pw_his.length == 0) {
+ DEBUG(0, ("pdb_set_pw_history: data_blob_talloc() failed!\n"));
+ return False;
+ }
+ }
+
+ data_blob_clear_free(&sampass->nt_pw_his);
+ sampass->nt_pw_his = new_nt_pw_his;
+
+ return pdb_set_init_flags(sampass, PDB_PWHISTORY, flag);
+}
+
+/*********************************************************************
+ Set the user's plaintext password only (base procedure, see helper
+ below)
+ ********************************************************************/
+
+bool pdb_set_plaintext_pw_only(struct samu *sampass, const char *password, enum pdb_value_state flag)
+{
+ BURN_STR(sampass->plaintext_pw);
+
+ if (password != NULL) {
+ sampass->plaintext_pw = talloc_strdup(sampass, password);
+
+ if (!sampass->plaintext_pw) {
+ DEBUG(0, ("pdb_set_unknown_str: talloc_strdup() failed!\n"));
+ return False;
+ }
+ } else {
+ sampass->plaintext_pw = NULL;
+ }
+
+ return pdb_set_init_flags(sampass, PDB_PLAINTEXT_PW, flag);
+}
+
+bool pdb_set_bad_password_count(struct samu *sampass, uint16_t bad_password_count, enum pdb_value_state flag)
+{
+ sampass->bad_password_count = bad_password_count;
+ return pdb_set_init_flags(sampass, PDB_BAD_PASSWORD_COUNT, flag);
+}
+
+bool pdb_set_logon_count(struct samu *sampass, uint16_t logon_count, enum pdb_value_state flag)
+{
+ sampass->logon_count = logon_count;
+ return pdb_set_init_flags(sampass, PDB_LOGON_COUNT, flag);
+}
+
+bool pdb_set_country_code(struct samu *sampass, uint16_t country_code,
+ enum pdb_value_state flag)
+{
+ sampass->country_code = country_code;
+ return pdb_set_init_flags(sampass, PDB_COUNTRY_CODE, flag);
+}
+
+bool pdb_set_code_page(struct samu *sampass, uint16_t code_page,
+ enum pdb_value_state flag)
+{
+ sampass->code_page = code_page;
+ return pdb_set_init_flags(sampass, PDB_CODE_PAGE, flag);
+}
+
+bool pdb_set_unknown_6(struct samu *sampass, uint32_t unkn, enum pdb_value_state flag)
+{
+ sampass->unknown_6 = unkn;
+ return pdb_set_init_flags(sampass, PDB_UNKNOWN6, flag);
+}
+
+bool pdb_set_hours(struct samu *sampass, const uint8_t *hours, int hours_len,
+ enum pdb_value_state flag)
+{
+ if (hours_len > sizeof(sampass->hours)) {
+ return false;
+ }
+
+ if (!hours) {
+ memset ((char *)sampass->hours, 0, hours_len);
+ } else {
+ memcpy (sampass->hours, hours, hours_len);
+ }
+
+ return pdb_set_init_flags(sampass, PDB_HOURS, flag);
+}
+
+bool pdb_set_backend_private_data(struct samu *sampass, void *private_data,
+ void (*free_fn)(void **),
+ const struct pdb_methods *my_methods,
+ enum pdb_value_state flag)
+{
+ if (sampass->backend_private_data &&
+ sampass->backend_private_data_free_fn) {
+ sampass->backend_private_data_free_fn(
+ &sampass->backend_private_data);
+ }
+
+ sampass->backend_private_data = private_data;
+ sampass->backend_private_data_free_fn = free_fn;
+ sampass->backend_private_methods = my_methods;
+
+ return pdb_set_init_flags(sampass, PDB_BACKEND_PRIVATE_DATA, flag);
+}
+
+
+/* Helpful interfaces to the above */
+
+bool pdb_set_pass_can_change(struct samu *sampass, bool canchange)
+{
+ return pdb_set_pass_can_change_time(sampass,
+ canchange ? 0 : pdb_password_change_time_max(),
+ PDB_CHANGED);
+}
+
+
+/*********************************************************************
+ Set the user's PLAINTEXT password. Used as an interface to the above.
+ Also sets the last change time to NOW.
+ ********************************************************************/
+
+bool pdb_set_plaintext_passwd(struct samu *sampass, const char *plaintext)
+{
+ uchar new_lanman_p16[LM_HASH_LEN];
+ uchar new_nt_p16[NT_HASH_LEN];
+ bool ok;
+
+ if (!plaintext)
+ return False;
+
+ /* Calculate the MD4 hash (NT compatible) of the password */
+ E_md4hash(plaintext, new_nt_p16);
+
+ if (!pdb_set_nt_passwd (sampass, new_nt_p16, PDB_CHANGED)) {
+ ZERO_STRUCT(new_nt_p16);
+ return False;
+ }
+
+ if (!E_deshash(plaintext, new_lanman_p16)) {
+ /* E_deshash returns false for 'long' passwords (> 14
+ DOS chars). This allows us to match Win2k, which
+ does not store a LM hash for these passwords (which
+ would reduce the effective password length to 14 */
+
+ if (!pdb_set_lanman_passwd (sampass, NULL, PDB_CHANGED)) {
+ ZERO_STRUCT(new_nt_p16);
+ ZERO_STRUCT(new_lanman_p16);
+ return False;
+ }
+ } else {
+ if (!pdb_set_lanman_passwd (sampass, new_lanman_p16, PDB_CHANGED)) {
+ ZERO_STRUCT(new_nt_p16);
+ ZERO_STRUCT(new_lanman_p16);
+ return False;
+ }
+ }
+ ZERO_STRUCT(new_lanman_p16);
+
+ if (!pdb_set_plaintext_pw_only (sampass, plaintext, PDB_CHANGED)) {
+ ZERO_STRUCT(new_nt_p16);
+ return False;
+ }
+
+ if (!pdb_set_pass_last_set_time (sampass, time(NULL), PDB_CHANGED)) {
+ ZERO_STRUCT(new_nt_p16);
+ return False;
+ }
+
+ ok = pdb_update_history(sampass, new_nt_p16);
+ ZERO_STRUCT(new_nt_p16);
+ return ok;
+}
+
+/*********************************************************************
+ Update password history after change
+ ********************************************************************/
+
+bool pdb_update_history(struct samu *sampass, const uint8_t new_nt[NT_HASH_LEN])
+{
+ uchar *pwhistory;
+ uint32_t pwHistLen;
+ uint32_t current_history_len;
+ const uint8_t *current_history;
+
+ if ((pdb_get_acct_ctrl(sampass) & ACB_NORMAL) == 0) {
+ /*
+ * No password history for non-user accounts
+ */
+ return true;
+ }
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen);
+
+ if (pwHistLen == 0) {
+ /* Set the history length to zero. */
+ pdb_set_pw_history(sampass, NULL, 0, PDB_CHANGED);
+ return true;
+ }
+
+ /*
+ * We need to make sure we don't have a race condition here -
+ * the account policy history length can change between when
+ * the pw_history was first loaded into the struct samu struct
+ * and now.... JRA.
+ */
+ current_history = pdb_get_pw_history(sampass, &current_history_len);
+ if ((current_history_len != 0) && (current_history == NULL)) {
+ DEBUG(1, ("pdb_update_history: pwhistory == NULL!\n"));
+ return false;
+ }
+
+ /*
+ * Ensure we have space for the needed history. This
+ * also takes care of an account which did not have
+ * any history at all so far, i.e. pwhistory==NULL
+ */
+ pwhistory = talloc_zero_array(
+ sampass, uchar,
+ pwHistLen*PW_HISTORY_ENTRY_LEN);
+ if (!pwhistory) {
+ return false;
+ }
+
+ memcpy(pwhistory, current_history,
+ current_history_len*PW_HISTORY_ENTRY_LEN);
+
+ /*
+ * Make room for the new password in the history list.
+ */
+ if (pwHistLen > 1) {
+ memmove(&pwhistory[PW_HISTORY_ENTRY_LEN], pwhistory,
+ (pwHistLen-1)*PW_HISTORY_ENTRY_LEN );
+ }
+
+ /*
+ * Fill the salt area with 0-s: this indicates that
+ * a plain nt hash is stored in the has area.
+ * The old format was to store a 16 byte salt and
+ * then an md5hash of the nt_hash concatenated with
+ * the salt.
+ */
+ memset(pwhistory, 0, PW_HISTORY_SALT_LEN);
+
+ /*
+ * Store the plain nt hash in the second 16 bytes.
+ * The old format was to store the md5 hash of
+ * the salt+newpw.
+ */
+ memcpy(&pwhistory[PW_HISTORY_SALT_LEN], new_nt, SALTED_MD5_HASH_LEN);
+
+ pdb_set_pw_history(sampass, pwhistory, pwHistLen, PDB_CHANGED);
+
+ return True;
+
+}
+
+/* check for any PDB_SET/CHANGED field and fill the appropriate mask bit */
+uint32_t pdb_build_fields_present(struct samu *sampass)
+{
+ /* value set to all for testing */
+ return 0x00ffffff;
+}
+
+/**********************************************************************
+ Helper function to determine for update_sam_account whether
+ we need LDAP modification.
+*********************************************************************/
+
+bool pdb_element_is_changed(const struct samu *sampass,
+ enum pdb_elements element)
+{
+ return IS_SAM_CHANGED(sampass, element);
+}
+
+/**********************************************************************
+ Helper function to determine for update_sam_account whether
+ we need LDAP modification.
+ *********************************************************************/
+
+bool pdb_element_is_set_or_changed(const struct samu *sampass,
+ enum pdb_elements element)
+{
+ return (IS_SAM_SET(sampass, element) ||
+ IS_SAM_CHANGED(sampass, element));
+}
diff --git a/source3/passdb/pdb_interface.c b/source3/passdb/pdb_interface.c
new file mode 100644
index 0000000..d3648f1
--- /dev/null
+++ b/source3/passdb/pdb_interface.c
@@ -0,0 +1,2713 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Jelmer Vernooij 2002
+ Copyright (C) Simo Sorce 2003
+ Copyright (C) Volker Lendecke 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "passdb.h"
+#include "secrets.h"
+#include "messages.h"
+#include "serverid.h"
+#include "../librpc/gen_ndr/samr.h"
+#include "../librpc/gen_ndr/drsblobs.h"
+#include "../librpc/gen_ndr/ndr_drsblobs.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "../lib/util/memcache.h"
+#include "nsswitch/winbind_client.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/util_pw.h"
+#include "passdb/pdb_secrets.h"
+#include "lib/util_sid_passdb.h"
+#include "idmap_cache.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+static_decl_pdb;
+
+static struct pdb_init_function_entry *backends = NULL;
+
+static void lazy_initialize_passdb(void)
+{
+ static bool initialized = False;
+ if(initialized) {
+ return;
+ }
+ static_init_pdb(NULL);
+ initialized = True;
+}
+
+static bool lookup_global_sam_rid(TALLOC_CTX *mem_ctx, uint32_t rid,
+ const char **name,
+ enum lsa_SidType *psid_name_use,
+ uid_t *uid, gid_t *gid);
+
+NTSTATUS smb_register_passdb(int version, const char *name, pdb_init_function init)
+{
+ struct pdb_init_function_entry *entry = NULL;
+
+ if(version != PASSDB_INTERFACE_VERSION) {
+ DEBUG(0,("Can't register passdb backend!\n"
+ "You tried to register a passdb module with PASSDB_INTERFACE_VERSION %d, "
+ "while this version of samba uses version %d\n",
+ version,PASSDB_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !init) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(5,("Attempting to register passdb backend %s\n", name));
+
+ /* Check for duplicates */
+ if (pdb_find_backend_entry(name)) {
+ DEBUG(0,("There already is a passdb backend registered with the name %s!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = SMB_XMALLOC_P(struct pdb_init_function_entry);
+ entry->name = smb_xstrdup(name);
+ entry->init = init;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5,("Successfully added passdb backend '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+struct pdb_init_function_entry *pdb_find_backend_entry(const char *name)
+{
+ struct pdb_init_function_entry *entry = backends;
+
+ while(entry) {
+ if (strcmp(entry->name, name)==0) return entry;
+ entry = entry->next;
+ }
+
+ return NULL;
+}
+
+const struct pdb_init_function_entry *pdb_get_backends(void)
+{
+ return backends;
+}
+
+
+/*
+ * The event context for the passdb backend. I know this is a bad hack and yet
+ * another static variable, but our pdb API is a global thing per
+ * definition. The first use for this is the LDAP idle function, more might be
+ * added later.
+ *
+ * I don't feel too bad about this static variable, it replaces the
+ * smb_idle_event_list that used to exist in lib/module.c. -- VL
+ */
+
+static struct tevent_context *pdb_tevent_ctx;
+
+struct tevent_context *pdb_get_tevent_context(void)
+{
+ return pdb_tevent_ctx;
+}
+
+/******************************************************************
+ Make a pdb_methods from scratch
+ *******************************************************************/
+
+NTSTATUS make_pdb_method_name(struct pdb_methods **methods, const char *selected)
+{
+ char *module_name = smb_xstrdup(selected);
+ char *module_location = NULL, *p;
+ struct pdb_init_function_entry *entry;
+ NTSTATUS nt_status;
+
+ lazy_initialize_passdb();
+
+ p = strchr(module_name, ':');
+
+ if (p) {
+ *p = 0;
+ module_location = p+1;
+ trim_char(module_location, ' ', ' ');
+ }
+
+ trim_char(module_name, ' ', ' ');
+
+
+ DEBUG(5,("Attempting to find a passdb backend to match %s (%s)\n", selected, module_name));
+
+ entry = pdb_find_backend_entry(module_name);
+
+ /* Try to find a module that contains this module */
+ if (!entry) {
+ DEBUG(2,("No builtin backend found, trying to load plugin\n"));
+ if(NT_STATUS_IS_OK(smb_probe_module("pdb", module_name)) && !(entry = pdb_find_backend_entry(module_name))) {
+ DEBUG(0,("Plugin is available, but doesn't register passdb backend %s\n", module_name));
+ SAFE_FREE(module_name);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ /* No such backend found */
+ if(!entry) {
+ DEBUG(0,("No builtin nor plugin backend for %s found\n", module_name));
+ SAFE_FREE(module_name);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(5,("Found pdb backend %s\n", module_name));
+
+ nt_status = entry->init(methods, module_location);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("pdb backend %s did not correctly init (error was %s)\n",
+ selected, nt_errstr(nt_status)));
+ SAFE_FREE(module_name);
+ return nt_status;
+ }
+
+ SAFE_FREE(module_name);
+
+ DEBUG(5,("pdb backend %s has a valid init\n", selected));
+
+ return nt_status;
+}
+
+/******************************************************************
+ Return an already initialized pdb_methods structure
+*******************************************************************/
+
+static struct pdb_methods *pdb_get_methods_reload( bool reload )
+{
+ static struct pdb_methods *pdb = NULL;
+ const char *backend = lp_passdb_backend();
+ NTSTATUS status = NT_STATUS_OK;
+
+ if ( pdb && reload ) {
+ if (pdb->free_private_data != NULL) {
+ pdb->free_private_data( &(pdb->private_data) );
+ }
+ status = make_pdb_method_name(&pdb, backend);
+ }
+
+ if ( !pdb ) {
+ status = make_pdb_method_name(&pdb, backend);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ return pdb;
+}
+
+static struct pdb_methods *pdb_get_methods(void)
+{
+ struct pdb_methods *pdb;
+
+ pdb = pdb_get_methods_reload(false);
+ if (!pdb) {
+ char *msg = NULL;
+ if (asprintf(&msg, "pdb_get_methods: "
+ "failed to get pdb methods for backend %s\n",
+ lp_passdb_backend()) > 0) {
+ smb_panic(msg);
+ } else {
+ smb_panic("pdb_get_methods");
+ }
+ }
+
+ return pdb;
+}
+
+struct pdb_domain_info *pdb_get_domain_info(TALLOC_CTX *mem_ctx)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->get_domain_info(pdb, mem_ctx);
+}
+
+/**
+ * @brief Check if the user account has been locked out and try to unlock it.
+ *
+ * If the user has been automatically locked out and a lockout duration is set,
+ * then check if we can unlock the account and reset the bad password values.
+ *
+ * @param[in] sampass The sam user to check.
+ *
+ * @return True if the function was successful, false on an error.
+ */
+static bool pdb_try_account_unlock(struct samu *sampass)
+{
+ uint32_t acb_info = pdb_get_acct_ctrl(sampass);
+
+ if ((acb_info & ACB_NORMAL) && (acb_info & ACB_AUTOLOCK)) {
+ uint32_t lockout_duration;
+ time_t bad_password_time;
+ time_t now = time(NULL);
+ bool ok;
+
+ ok = pdb_get_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION,
+ &lockout_duration);
+ if (!ok) {
+ DEBUG(0, ("pdb_try_account_unlock: "
+ "pdb_get_account_policy failed.\n"));
+ return false;
+ }
+
+ if (lockout_duration == (uint32_t) -1 ||
+ lockout_duration == 0) {
+ DEBUG(9, ("pdb_try_account_unlock: No reset duration, "
+ "can't reset autolock\n"));
+ return false;
+ }
+ lockout_duration *= 60;
+
+ bad_password_time = pdb_get_bad_password_time(sampass);
+ if (bad_password_time == (time_t) 0) {
+ DEBUG(2, ("pdb_try_account_unlock: Account %s "
+ "administratively locked out "
+ "with no bad password "
+ "time. Leaving locked out.\n",
+ pdb_get_username(sampass)));
+ return true;
+ }
+
+ if ((bad_password_time +
+ convert_uint32_t_to_time_t(lockout_duration)) < now) {
+ NTSTATUS status;
+
+ pdb_set_acct_ctrl(sampass, acb_info & ~ACB_AUTOLOCK,
+ PDB_CHANGED);
+ pdb_set_bad_password_count(sampass, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(sampass, 0, PDB_CHANGED);
+
+ become_root();
+ status = pdb_update_sam_account(sampass);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("_samr_OpenUser: Couldn't "
+ "update account %s - %s\n",
+ pdb_get_username(sampass),
+ nt_errstr(status)));
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+/**
+ * @brief Get a sam user structure by the given username.
+ *
+ * This functions also checks if the account has been automatically locked out
+ * and unlocks it if a lockout duration time has been defined and the time has
+ * elapsed.
+ *
+ * @param[in] sam_acct The sam user structure to fill.
+ *
+ * @param[in] username The username to look for.
+ *
+ * @return True on success, false on error.
+ */
+bool pdb_getsampwnam(struct samu *sam_acct, const char *username)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ struct samu *for_cache;
+ const struct dom_sid *user_sid;
+ NTSTATUS status;
+ bool ok;
+
+ status = pdb->getsampwnam(pdb, sam_acct, username);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ ok = pdb_try_account_unlock(sam_acct);
+ if (!ok) {
+ DEBUG(1, ("pdb_getsampwnam: Failed to unlock account %s\n",
+ username));
+ }
+
+ for_cache = samu_new(NULL);
+ if (for_cache == NULL) {
+ return False;
+ }
+
+ if (!pdb_copy_sam_account(for_cache, sam_acct)) {
+ TALLOC_FREE(for_cache);
+ return False;
+ }
+
+ user_sid = pdb_get_user_sid(for_cache);
+
+ ok = memcache_add_talloc(NULL,
+ PDB_GETPWSID_CACHE,
+ data_blob_const(user_sid, sizeof(*user_sid)),
+ &for_cache);
+ if (!ok) {
+ TALLOC_FREE(for_cache);
+ }
+
+ return True;
+}
+
+/**********************************************************************
+**********************************************************************/
+
+static bool guest_user_info( struct samu *user )
+{
+ struct passwd *pwd;
+ NTSTATUS result;
+ const char *guestname = lp_guest_account();
+
+ pwd = Get_Pwnam_alloc(talloc_tos(), guestname);
+ if (pwd == NULL) {
+ DEBUG(0,("guest_user_info: Unable to locate guest account [%s]!\n",
+ guestname));
+ return False;
+ }
+
+ result = samu_set_unix(user, pwd );
+
+ TALLOC_FREE( pwd );
+
+ return NT_STATUS_IS_OK( result );
+}
+
+/**
+ * @brief Get a sam user structure by the given username.
+ *
+ * This functions also checks if the account has been automatically locked out
+ * and unlocks it if a lockout duration time has been defined and the time has
+ * elapsed.
+ *
+ *
+ * @param[in] sam_acct The sam user structure to fill.
+ *
+ * @param[in] sid The user SDI to look up.
+ *
+ * @return True on success, false on error.
+ */
+bool pdb_getsampwsid(struct samu *sam_acct, const struct dom_sid *sid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ uint32_t rid;
+ void *cache_data;
+ bool ok = false;
+
+ /* hard code the Guest RID of 501 */
+
+ if ( !sid_peek_check_rid( get_global_sam_sid(), sid, &rid ) )
+ return False;
+
+ if ( rid == DOMAIN_RID_GUEST ) {
+ DEBUG(6,("pdb_getsampwsid: Building guest account\n"));
+ return guest_user_info( sam_acct );
+ }
+
+ /* check the cache first */
+
+ cache_data = memcache_lookup_talloc(
+ NULL, PDB_GETPWSID_CACHE, data_blob_const(sid, sizeof(*sid)));
+
+ if (cache_data != NULL) {
+ struct samu *cache_copy = talloc_get_type_abort(
+ cache_data, struct samu);
+
+ ok = pdb_copy_sam_account(sam_acct, cache_copy);
+ } else {
+ ok = NT_STATUS_IS_OK(pdb->getsampwsid(pdb, sam_acct, sid));
+ }
+
+ if (!ok) {
+ return false;
+ }
+
+ ok = pdb_try_account_unlock(sam_acct);
+ if (!ok) {
+ DEBUG(1, ("pdb_getsampwsid: Failed to unlock account %s\n",
+ sam_acct->username));
+ }
+
+ return true;
+}
+
+static NTSTATUS pdb_default_create_user(struct pdb_methods *methods,
+ TALLOC_CTX *tmp_ctx, const char *name,
+ uint32_t acb_info, uint32_t *rid)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct samu *sam_pass;
+ NTSTATUS status;
+ struct passwd *pwd;
+
+ if ((sam_pass = samu_new(tmp_ctx)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !(pwd = Get_Pwnam_alloc(tmp_ctx, name)) ) {
+ char *add_script = NULL;
+ int add_ret;
+ fstring name2;
+
+ if ((acb_info & ACB_NORMAL) && name[strlen(name)-1] != '$') {
+ add_script = lp_add_user_script(tmp_ctx, lp_sub);
+ } else {
+ add_script = lp_add_machine_script(tmp_ctx, lp_sub);
+ }
+
+ if (!add_script || add_script[0] == '\0') {
+ DEBUG(3, ("Could not find user %s and no add script "
+ "defined\n", name));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /* lowercase the username before creating the Unix account for
+ compatibility with previous Samba releases */
+ fstrcpy( name2, name );
+ if (!strlower_m( name2 )) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ add_script = talloc_all_string_sub(tmp_ctx,
+ add_script,
+ "%u",
+ name2);
+ if (!add_script) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ add_ret = smbrun(add_script, NULL, NULL);
+ DEBUG(add_ret ? 0 : 3, ("_samr_create_user: Running the command `%s' gave %d\n",
+ add_script, add_ret));
+ if (add_ret == 0) {
+ smb_nscd_flush_user_cache();
+ }
+
+ flush_pwnam_cache();
+
+ pwd = Get_Pwnam_alloc(tmp_ctx, name);
+
+ if(pwd == NULL) {
+ DEBUG(3, ("Could not find user %s, add script did not work\n", name));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ }
+
+ /* we have a valid SID coming out of this call */
+
+ status = samu_alloc_rid_unix(methods, sam_pass, pwd);
+
+ TALLOC_FREE( pwd );
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("pdb_default_create_user: failed to create a new user structure: %s\n", nt_errstr(status)));
+ return status;
+ }
+
+ if (!sid_peek_check_rid(get_global_sam_sid(),
+ pdb_get_user_sid(sam_pass), rid)) {
+ DEBUG(0, ("Could not get RID of fresh user\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* Use the username case specified in the original request */
+
+ pdb_set_username( sam_pass, name, PDB_SET );
+
+ /* Disable the account on creation, it does not have a reasonable password yet. */
+
+ acb_info |= ACB_DISABLED;
+
+ pdb_set_acct_ctrl(sam_pass, acb_info, PDB_CHANGED);
+
+ status = methods->add_sam_account(methods, sam_pass);
+
+ TALLOC_FREE(sam_pass);
+
+ return status;
+}
+
+NTSTATUS pdb_create_user(TALLOC_CTX *mem_ctx, const char *name, uint32_t flags,
+ uint32_t *rid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->create_user(pdb, mem_ctx, name, flags, rid);
+}
+
+/****************************************************************************
+ Delete a UNIX user on demand.
+****************************************************************************/
+
+static int smb_delete_user(const char *unix_user)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *del_script = NULL;
+ int ret;
+
+ /* safety check */
+
+ if ( strequal( unix_user, "root" ) ) {
+ DEBUG(0,("smb_delete_user: Refusing to delete local system root account!\n"));
+ return -1;
+ }
+
+ del_script = lp_delete_user_script(talloc_tos(), lp_sub);
+ if (!del_script || !*del_script) {
+ return -1;
+ }
+ del_script = talloc_all_string_sub(talloc_tos(),
+ del_script,
+ "%u",
+ unix_user);
+ if (!del_script) {
+ return -1;
+ }
+ ret = smbrun(del_script, NULL, NULL);
+ flush_pwnam_cache();
+ if (ret == 0) {
+ smb_nscd_flush_user_cache();
+ }
+ DEBUG(ret ? 0 : 3,("smb_delete_user: Running the command `%s' gave %d\n",del_script,ret));
+
+ return ret;
+}
+
+static NTSTATUS pdb_default_delete_user(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *sam_acct)
+{
+ NTSTATUS status;
+ fstring username;
+
+ status = methods->delete_sam_account(methods, sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Now delete the unix side ....
+ * note: we don't check if the delete really happened as the script is
+ * not necessary present and maybe the sysadmin doesn't want to delete
+ * the unix side
+ */
+
+ /* always lower case the username before handing it off to
+ external scripts */
+
+ fstrcpy( username, pdb_get_username(sam_acct) );
+ if (!strlower_m( username )) {
+ return status;
+ }
+
+ smb_delete_user( username );
+
+ return status;
+}
+
+NTSTATUS pdb_delete_user(TALLOC_CTX *mem_ctx, struct samu *sam_acct)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ uid_t uid = -1;
+ NTSTATUS status;
+ const struct dom_sid *user_sid;
+ char *msg_data;
+
+ user_sid = pdb_get_user_sid(sam_acct);
+
+ /* sanity check to make sure we don't delete root */
+
+ if ( !sid_to_uid(user_sid, &uid ) ) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if ( uid == 0 ) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ memcache_delete(NULL,
+ PDB_GETPWSID_CACHE,
+ data_blob_const(user_sid, sizeof(*user_sid)));
+
+ status = pdb->delete_user(pdb, mem_ctx, sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ msg_data = talloc_asprintf(mem_ctx, "USER %s",
+ pdb_get_username(sam_acct));
+ if (!msg_data) {
+ /* not fatal, and too late to rollback,
+ * just return */
+ return status;
+ }
+ messaging_send_all(global_messaging_context(),
+ ID_CACHE_DELETE,
+ msg_data,
+ strlen(msg_data) + 1);
+
+ TALLOC_FREE(msg_data);
+ return status;
+}
+
+NTSTATUS pdb_add_sam_account(struct samu *sam_acct)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->add_sam_account(pdb, sam_acct);
+}
+
+NTSTATUS pdb_update_sam_account(struct samu *sam_acct)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+
+ memcache_flush(NULL, PDB_GETPWSID_CACHE);
+
+ return pdb->update_sam_account(pdb, sam_acct);
+}
+
+NTSTATUS pdb_delete_sam_account(struct samu *sam_acct)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ const struct dom_sid *user_sid = pdb_get_user_sid(sam_acct);
+
+ memcache_delete(NULL,
+ PDB_GETPWSID_CACHE,
+ data_blob_const(user_sid, sizeof(*user_sid)));
+
+ return pdb->delete_sam_account(pdb, sam_acct);
+}
+
+NTSTATUS pdb_rename_sam_account(struct samu *oldname, const char *newname)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ uid_t uid;
+ NTSTATUS status;
+
+ memcache_flush(NULL, PDB_GETPWSID_CACHE);
+
+ /* sanity check to make sure we don't rename root */
+
+ if ( !sid_to_uid( pdb_get_user_sid(oldname), &uid ) ) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if ( uid == 0 ) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pdb->rename_sam_account(pdb, oldname, newname);
+
+ /* always flush the cache here just to be safe */
+ flush_pwnam_cache();
+
+ return status;
+}
+
+NTSTATUS pdb_update_login_attempts(struct samu *sam_acct, bool success)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->update_login_attempts(pdb, sam_acct, success);
+}
+
+bool pdb_getgrsid(GROUP_MAP *map, struct dom_sid sid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return NT_STATUS_IS_OK(pdb->getgrsid(pdb, map, sid));
+}
+
+bool pdb_getgrgid(GROUP_MAP *map, gid_t gid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return NT_STATUS_IS_OK(pdb->getgrgid(pdb, map, gid));
+}
+
+bool pdb_getgrnam(GROUP_MAP *map, const char *name)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return NT_STATUS_IS_OK(pdb->getgrnam(pdb, map, name));
+}
+
+static NTSTATUS pdb_default_create_dom_group(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ uint32_t *rid)
+{
+ struct dom_sid group_sid;
+ struct group *grp;
+ struct dom_sid_buf tmp;
+
+ grp = getgrnam(name);
+
+ if (grp == NULL) {
+ gid_t gid;
+
+ if (smb_create_group(name, &gid) != 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ grp = getgrgid(gid);
+ }
+
+ if (grp == NULL) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (pdb_capabilities() & PDB_CAP_STORE_RIDS) {
+ if (!pdb_new_rid(rid)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ *rid = algorithmic_pdb_gid_to_group_rid( grp->gr_gid );
+ }
+
+ sid_compose(&group_sid, get_global_sam_sid(), *rid);
+
+ return add_initial_entry(
+ grp->gr_gid,
+ dom_sid_str_buf(&group_sid, &tmp),
+ SID_NAME_DOM_GRP,
+ name,
+ NULL);
+}
+
+NTSTATUS pdb_create_dom_group(TALLOC_CTX *mem_ctx, const char *name,
+ uint32_t *rid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->create_dom_group(pdb, mem_ctx, name, rid);
+}
+
+static NTSTATUS pdb_default_delete_dom_group(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t rid)
+{
+ struct dom_sid group_sid;
+ GROUP_MAP *map;
+ NTSTATUS status;
+ struct group *grp;
+ const char *grp_name;
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* coverity */
+ map->gid = (gid_t) -1;
+
+ sid_compose(&group_sid, get_global_sam_sid(), rid);
+
+ if (!get_domain_group_from_sid(group_sid, map)) {
+ DEBUG(10, ("Could not find group for rid %d\n", rid));
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ /* We need the group name for the smb_delete_group later on */
+
+ if (map->gid == (gid_t)-1) {
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ grp = getgrgid(map->gid);
+ if (grp == NULL) {
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ TALLOC_FREE(map);
+
+ /* Copy the name, no idea what pdb_delete_group_mapping_entry does.. */
+
+ grp_name = talloc_strdup(mem_ctx, grp->gr_name);
+ if (grp_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pdb_delete_group_mapping_entry(group_sid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Don't check the result of smb_delete_group */
+
+ smb_delete_group(grp_name);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pdb_delete_dom_group(TALLOC_CTX *mem_ctx, uint32_t rid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->delete_dom_group(pdb, mem_ctx, rid);
+}
+
+NTSTATUS pdb_add_group_mapping_entry(GROUP_MAP *map)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->add_group_mapping_entry(pdb, map);
+}
+
+NTSTATUS pdb_update_group_mapping_entry(GROUP_MAP *map)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->update_group_mapping_entry(pdb, map);
+}
+
+NTSTATUS pdb_delete_group_mapping_entry(struct dom_sid sid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->delete_group_mapping_entry(pdb, sid);
+}
+
+bool pdb_enum_group_mapping(const struct dom_sid *sid,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries,
+ bool unix_only)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return NT_STATUS_IS_OK(pdb-> enum_group_mapping(pdb, sid, sid_name_use,
+ pp_rmap, p_num_entries, unix_only));
+}
+
+NTSTATUS pdb_enum_group_members(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ uint32_t **pp_member_rids,
+ size_t *p_num_members)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ NTSTATUS result;
+
+ result = pdb->enum_group_members(pdb, mem_ctx,
+ sid, pp_member_rids, p_num_members);
+
+ /* special check for rid 513 */
+
+ if ( !NT_STATUS_IS_OK( result ) ) {
+ uint32_t rid;
+
+ sid_peek_rid( sid, &rid );
+
+ if ( rid == DOMAIN_RID_USERS ) {
+ *p_num_members = 0;
+ *pp_member_rids = NULL;
+
+ return NT_STATUS_OK;
+ }
+ }
+
+ return result;
+}
+
+NTSTATUS pdb_enum_group_memberships(TALLOC_CTX *mem_ctx, struct samu *user,
+ struct dom_sid **pp_sids, gid_t **pp_gids,
+ uint32_t *p_num_groups)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->enum_group_memberships(
+ pdb, mem_ctx, user,
+ pp_sids, pp_gids, p_num_groups);
+}
+
+static NTSTATUS pdb_default_set_unix_primary_group(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *sampass)
+{
+ struct group *grp;
+ gid_t gid;
+
+ if (!sid_to_gid(pdb_get_group_sid(sampass), &gid) ||
+ (grp = getgrgid(gid)) == NULL) {
+ return NT_STATUS_INVALID_PRIMARY_GROUP;
+ }
+
+ if (smb_set_primary_group(grp->gr_name,
+ pdb_get_username(sampass)) != 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pdb_set_unix_primary_group(TALLOC_CTX *mem_ctx, struct samu *user)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->set_unix_primary_group(pdb, mem_ctx, user);
+}
+
+/*
+ * Helper function to see whether a user is in a group. We can't use
+ * user_in_group_sid here because this creates dependencies only smbd can
+ * fulfil.
+ */
+
+static bool pdb_user_in_group(TALLOC_CTX *mem_ctx, struct samu *account,
+ const struct dom_sid *group_sid)
+{
+ struct dom_sid *sids;
+ gid_t *gids;
+ uint32_t i, num_groups;
+
+ if (!NT_STATUS_IS_OK(pdb_enum_group_memberships(mem_ctx, account,
+ &sids, &gids,
+ &num_groups))) {
+ return False;
+ }
+
+ for (i=0; i<num_groups; i++) {
+ if (dom_sid_equal(group_sid, &sids[i])) {
+ return True;
+ }
+ }
+ return False;
+}
+
+static NTSTATUS pdb_default_add_groupmem(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t group_rid,
+ uint32_t member_rid)
+{
+ struct dom_sid group_sid, member_sid;
+ struct samu *account = NULL;
+ GROUP_MAP *map;
+ struct group *grp;
+ struct passwd *pwd;
+ const char *group_name;
+ uid_t uid;
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* coverity */
+ map->gid = (gid_t) -1;
+
+ sid_compose(&group_sid, get_global_sam_sid(), group_rid);
+ sid_compose(&member_sid, get_global_sam_sid(), member_rid);
+
+ if (!get_domain_group_from_sid(group_sid, map) ||
+ (map->gid == (gid_t)-1) ||
+ ((grp = getgrgid(map->gid)) == NULL)) {
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ TALLOC_FREE(map);
+
+ group_name = talloc_strdup(mem_ctx, grp->gr_name);
+ if (group_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !(account = samu_new( NULL )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_getsampwsid(account, &member_sid) ||
+ !sid_to_uid(&member_sid, &uid) ||
+ ((pwd = getpwuid_alloc(mem_ctx, uid)) == NULL)) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (pdb_user_in_group(mem_ctx, account, &group_sid)) {
+ return NT_STATUS_MEMBER_IN_GROUP;
+ }
+
+ /*
+ * ok, the group exist, the user exist, the user is not in the group,
+ * we can (finally) add it to the group !
+ */
+
+ smb_add_user_group(group_name, pwd->pw_name);
+
+ if (!pdb_user_in_group(mem_ctx, account, &group_sid)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pdb_add_groupmem(TALLOC_CTX *mem_ctx, uint32_t group_rid,
+ uint32_t member_rid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->add_groupmem(pdb, mem_ctx, group_rid, member_rid);
+}
+
+static NTSTATUS pdb_default_del_groupmem(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t group_rid,
+ uint32_t member_rid)
+{
+ struct dom_sid group_sid, member_sid;
+ struct samu *account = NULL;
+ GROUP_MAP *map;
+ struct group *grp;
+ struct passwd *pwd;
+ const char *group_name;
+ uid_t uid;
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sid_compose(&group_sid, get_global_sam_sid(), group_rid);
+ sid_compose(&member_sid, get_global_sam_sid(), member_rid);
+
+ if (!get_domain_group_from_sid(group_sid, map) ||
+ (map->gid == (gid_t)-1) ||
+ ((grp = getgrgid(map->gid)) == NULL)) {
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ TALLOC_FREE(map);
+
+ group_name = talloc_strdup(mem_ctx, grp->gr_name);
+ if (group_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !(account = samu_new( NULL )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_getsampwsid(account, &member_sid) ||
+ !sid_to_uid(&member_sid, &uid) ||
+ ((pwd = getpwuid_alloc(mem_ctx, uid)) == NULL)) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (!pdb_user_in_group(mem_ctx, account, &group_sid)) {
+ return NT_STATUS_MEMBER_NOT_IN_GROUP;
+ }
+
+ /*
+ * ok, the group exist, the user exist, the user is in the group,
+ * we can (finally) delete it from the group!
+ */
+
+ smb_delete_user_group(group_name, pwd->pw_name);
+
+ if (pdb_user_in_group(mem_ctx, account, &group_sid)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pdb_del_groupmem(TALLOC_CTX *mem_ctx, uint32_t group_rid,
+ uint32_t member_rid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->del_groupmem(pdb, mem_ctx, group_rid, member_rid);
+}
+
+NTSTATUS pdb_create_alias(const char *name, uint32_t *rid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->create_alias(pdb, name, rid);
+}
+
+NTSTATUS pdb_delete_alias(const struct dom_sid *sid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->delete_alias(pdb, sid);
+}
+
+NTSTATUS pdb_get_aliasinfo(const struct dom_sid *sid, struct acct_info *info)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->get_aliasinfo(pdb, sid, info);
+}
+
+NTSTATUS pdb_set_aliasinfo(const struct dom_sid *sid, struct acct_info *info)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->set_aliasinfo(pdb, sid, info);
+}
+
+NTSTATUS pdb_add_aliasmem(const struct dom_sid *alias, const struct dom_sid *member)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->add_aliasmem(pdb, alias, member);
+}
+
+NTSTATUS pdb_del_aliasmem(const struct dom_sid *alias, const struct dom_sid *member)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->del_aliasmem(pdb, alias, member);
+}
+
+NTSTATUS pdb_enum_aliasmem(const struct dom_sid *alias, TALLOC_CTX *mem_ctx,
+ struct dom_sid **pp_members, size_t *p_num_members)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->enum_aliasmem(pdb, alias, mem_ctx, pp_members,
+ p_num_members);
+}
+
+NTSTATUS pdb_enum_alias_memberships(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *members, size_t num_members,
+ uint32_t **pp_alias_rids,
+ size_t *p_num_alias_rids)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->enum_alias_memberships(pdb, mem_ctx,
+ domain_sid,
+ members, num_members,
+ pp_alias_rids,
+ p_num_alias_rids);
+}
+
+NTSTATUS pdb_lookup_rids(const struct dom_sid *domain_sid,
+ int num_rids,
+ uint32_t *rids,
+ const char **names,
+ enum lsa_SidType *attrs)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->lookup_rids(pdb, domain_sid, num_rids, rids, names, attrs);
+}
+
+bool pdb_get_account_policy(enum pdb_policy_type type, uint32_t *value)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ NTSTATUS status;
+
+ become_root();
+ status = pdb->get_account_policy(pdb, type, value);
+ unbecome_root();
+
+ return NT_STATUS_IS_OK(status);
+}
+
+bool pdb_set_account_policy(enum pdb_policy_type type, uint32_t value)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ NTSTATUS status;
+
+ become_root();
+ status = pdb->set_account_policy(pdb, type, value);
+ unbecome_root();
+
+ return NT_STATUS_IS_OK(status);
+}
+
+bool pdb_get_seq_num(time_t *seq_num)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return NT_STATUS_IS_OK(pdb->get_seq_num(pdb, seq_num));
+}
+
+/*
+ * Instead of passing down a gid or uid, this function sends down a pointer
+ * to a unixid.
+ *
+ * This acts as an in-out variable so that the idmap functions can correctly
+ * receive ID_TYPE_BOTH, filling in cache details correctly rather than forcing
+ * the cache to store ID_TYPE_UID or ID_TYPE_GID.
+ */
+bool pdb_id_to_sid(struct unixid *id, struct dom_sid *sid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ bool ret;
+
+ ret = pdb->id_to_sid(pdb, id, sid);
+
+ if (ret) {
+ idmap_cache_set_sid2unixid(sid, id);
+ }
+
+ return ret;
+}
+
+bool pdb_sid_to_id(const struct dom_sid *sid, struct unixid *id)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ bool ret;
+
+ /* only ask the backend if it is responsible */
+ if (!sid_check_object_is_for_passdb(sid)) {
+ return false;
+ }
+
+ ret = pdb->sid_to_id(pdb, sid, id);
+
+ if (ret) {
+ idmap_cache_set_sid2unixid(sid, id);
+ }
+
+ return ret;
+}
+
+uint32_t pdb_capabilities(void)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->capabilities(pdb);
+}
+
+/********************************************************************
+ Allocate a new RID from the passdb backend. Verify that it is free
+ by calling lookup_global_sam_rid() to verify that the RID is not
+ in use. This handles servers that have existing users or groups
+ with add RIDs (assigned from previous algorithmic mappings)
+********************************************************************/
+
+bool pdb_new_rid(uint32_t *rid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ const char *name = NULL;
+ enum lsa_SidType type;
+ uint32_t allocated_rid = 0;
+ int i;
+ TALLOC_CTX *ctx;
+
+ if ((pdb_capabilities() & PDB_CAP_STORE_RIDS) == 0) {
+ DEBUG(0, ("Trying to allocate a RID when algorithmic RIDs "
+ "are active\n"));
+ return False;
+ }
+
+ if (algorithmic_rid_base() != BASE_RID) {
+ DEBUG(0, ("'algorithmic rid base' is set but a passdb backend "
+ "without algorithmic RIDs is chosen.\n"));
+ DEBUGADD(0, ("Please map all used groups using 'net groupmap "
+ "add', set the maximum used RID\n"));
+ DEBUGADD(0, ("and remove the parameter\n"));
+ return False;
+ }
+
+ if ( (ctx = talloc_init("pdb_new_rid")) == NULL ) {
+ DEBUG(0,("pdb_new_rid: Talloc initialization failure\n"));
+ return False;
+ }
+
+ /* Attempt to get an unused RID (max tires is 250...yes that it is
+ and arbitrary number I pulkled out of my head). -- jerry */
+
+ for ( i=0; allocated_rid==0 && i<250; i++ ) {
+ /* get a new RID */
+
+ if ( !pdb->new_rid(pdb, &allocated_rid) ) {
+ return False;
+ }
+
+ /* validate that the RID is not in use */
+
+ if (lookup_global_sam_rid(ctx, allocated_rid, &name, &type, NULL, NULL)) {
+ allocated_rid = 0;
+ }
+ }
+
+ TALLOC_FREE( ctx );
+
+ if ( allocated_rid == 0 ) {
+ DEBUG(0,("pdb_new_rid: Failed to find unused RID\n"));
+ return False;
+ }
+
+ *rid = allocated_rid;
+
+ return True;
+}
+
+/***************************************************************
+ Initialize the static context (at smbd startup etc).
+
+ If uninitialised, context will auto-init on first use.
+ ***************************************************************/
+
+bool initialize_password_db(bool reload, struct tevent_context *tevent_ctx)
+{
+ if (tevent_ctx) {
+ pdb_tevent_ctx = tevent_ctx;
+ }
+ return (pdb_get_methods_reload(reload) != NULL);
+}
+
+/***************************************************************************
+ Default implementations of some functions.
+ ****************************************************************************/
+
+static NTSTATUS pdb_default_getsampwnam (struct pdb_methods *methods, struct samu *user, const char *sname)
+{
+ return NT_STATUS_NO_SUCH_USER;
+}
+
+static NTSTATUS pdb_default_getsampwsid(struct pdb_methods *my_methods, struct samu * user, const struct dom_sid *sid)
+{
+ return NT_STATUS_NO_SUCH_USER;
+}
+
+static NTSTATUS pdb_default_add_sam_account (struct pdb_methods *methods, struct samu *newpwd)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_default_update_sam_account (struct pdb_methods *methods, struct samu *newpwd)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_default_delete_sam_account (struct pdb_methods *methods, struct samu *pwd)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_default_rename_sam_account (struct pdb_methods *methods, struct samu *pwd, const char *newname)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_default_update_login_attempts (struct pdb_methods *methods, struct samu *newpwd, bool success)
+{
+ /* Only the pdb_nds backend implements this, by
+ * default just return ok. */
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_default_get_account_policy(struct pdb_methods *methods, enum pdb_policy_type type, uint32_t *value)
+{
+ return account_policy_get(type, value) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+static NTSTATUS pdb_default_set_account_policy(struct pdb_methods *methods, enum pdb_policy_type type, uint32_t value)
+{
+ return account_policy_set(type, value) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+static NTSTATUS pdb_default_get_seq_num(struct pdb_methods *methods, time_t *seq_num)
+{
+ *seq_num = time(NULL);
+ return NT_STATUS_OK;
+}
+
+static bool pdb_default_uid_to_sid(struct pdb_methods *methods, uid_t uid,
+ struct dom_sid *sid)
+{
+ struct samu *sampw = NULL;
+ struct passwd *unix_pw;
+ fstring pw_name = { 0 };
+ bool ret;
+
+ unix_pw = getpwuid( uid );
+
+ if ( !unix_pw ) {
+ DEBUG(4,("pdb_default_uid_to_sid: host has no idea of uid "
+ "%lu\n", (unsigned long)uid));
+ return False;
+ }
+
+ if (unix_pw->pw_name == NULL) {
+ DBG_DEBUG("No pw_name for uid %d\n", (int)uid);
+ return false;
+ }
+
+ /*
+ * Make a copy, "unix_pw" might go away soon.
+ */
+ fstrcpy(pw_name, unix_pw->pw_name);
+
+ if ( !(sampw = samu_new( NULL )) ) {
+ DEBUG(0,("pdb_default_uid_to_sid: samu_new() failed!\n"));
+ return False;
+ }
+
+ become_root();
+ ret = NT_STATUS_IS_OK(methods->getsampwnam(methods, sampw, pw_name));
+ unbecome_root();
+
+ if (!ret) {
+ DEBUG(5, ("pdb_default_uid_to_sid: Did not find user "
+ "%s (%u)\n", unix_pw->pw_name, (unsigned int)uid));
+ TALLOC_FREE(sampw);
+ return False;
+ }
+
+ sid_copy(sid, pdb_get_user_sid(sampw));
+
+ TALLOC_FREE(sampw);
+
+ return True;
+}
+
+static bool pdb_default_gid_to_sid(struct pdb_methods *methods, gid_t gid,
+ struct dom_sid *sid)
+{
+ GROUP_MAP *map;
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return false;
+ }
+
+ if (!NT_STATUS_IS_OK(methods->getgrgid(methods, map, gid))) {
+ TALLOC_FREE(map);
+ return false;
+ }
+
+ sid_copy(sid, &map->sid);
+ TALLOC_FREE(map);
+ return true;
+}
+
+static bool pdb_default_id_to_sid(struct pdb_methods *methods, struct unixid *id,
+ struct dom_sid *sid)
+{
+ switch (id->type) {
+ case ID_TYPE_UID:
+ return pdb_default_uid_to_sid(methods, id->id, sid);
+
+ case ID_TYPE_GID:
+ return pdb_default_gid_to_sid(methods, id->id, sid);
+
+ default:
+ return false;
+ }
+}
+/**
+ * The "Unix User" and "Unix Group" domains have a special
+ * id mapping that is a rid-algorithm with range starting at 0.
+ */
+bool pdb_sid_to_id_unix_users_and_groups(const struct dom_sid *sid,
+ struct unixid *id)
+{
+ uint32_t rid;
+
+ id->id = -1;
+
+ if (sid_peek_check_rid(&global_sid_Unix_Users, sid, &rid)) {
+ id->id = rid;
+ id->type = ID_TYPE_UID;
+ return true;
+ }
+
+ if (sid_peek_check_rid(&global_sid_Unix_Groups, sid, &rid)) {
+ id->id = rid;
+ id->type = ID_TYPE_GID;
+ return true;
+ }
+
+ return false;
+}
+
+static bool pdb_default_sid_to_id(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct unixid *id)
+{
+ TALLOC_CTX *mem_ctx;
+ bool ret = False;
+ uint32_t rid;
+ struct dom_sid_buf buf;
+
+ id->id = -1;
+
+ mem_ctx = talloc_new(NULL);
+
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return False;
+ }
+
+ if (sid_peek_check_rid(get_global_sam_sid(), sid, &rid)) {
+ const char *name;
+ enum lsa_SidType type;
+ uid_t uid = (uid_t)-1;
+ gid_t gid = (gid_t)-1;
+ /* Here we might have users as well as groups and aliases */
+ ret = lookup_global_sam_rid(mem_ctx, rid, &name, &type, &uid, &gid);
+ if (ret) {
+ switch (type) {
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ id->type = ID_TYPE_GID;
+ id->id = gid;
+ break;
+ case SID_NAME_USER:
+ id->type = ID_TYPE_UID;
+ id->id = uid;
+ break;
+ default:
+ DEBUG(5, ("SID %s belongs to our domain, and "
+ "an object exists in the database, "
+ "but it is neither a user nor a "
+ "group (got type %d).\n",
+ dom_sid_str_buf(sid, &buf),
+ type));
+ ret = false;
+ }
+ } else {
+ DEBUG(5, ("SID %s belongs to our domain, but there is "
+ "no corresponding object in the database.\n",
+ dom_sid_str_buf(sid, &buf)));
+ }
+ goto done;
+ }
+
+ /*
+ * "Unix User" and "Unix Group"
+ */
+ ret = pdb_sid_to_id_unix_users_and_groups(sid, id);
+ if (ret) {
+ goto done;
+ }
+
+ /* BUILTIN */
+
+ if (sid_check_is_in_builtin(sid) ||
+ sid_check_is_in_wellknown_domain(sid)) {
+ /* Here we only have aliases */
+ GROUP_MAP *map;
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (!map) {
+ ret = false;
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(methods->getgrsid(methods, map, *sid))) {
+ DEBUG(10, ("Could not find map for sid %s\n",
+ dom_sid_str_buf(sid, &buf)));
+ goto done;
+ }
+ if ((map->sid_name_use != SID_NAME_ALIAS) &&
+ (map->sid_name_use != SID_NAME_WKN_GRP)) {
+ DEBUG(10, ("Map for sid %s is a %s, expected an "
+ "alias\n",
+ dom_sid_str_buf(sid, &buf),
+ sid_type_lookup(map->sid_name_use)));
+ goto done;
+ }
+
+ id->id = map->gid;
+ id->type = ID_TYPE_GID;
+ ret = True;
+ goto done;
+ }
+
+ DEBUG(5, ("Sid %s is neither ours, a Unix SID, nor builtin\n",
+ dom_sid_str_buf(sid, &buf)));
+
+ done:
+
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static bool get_memberuids(TALLOC_CTX *mem_ctx, gid_t gid, uid_t **pp_uids, uint32_t *p_num)
+{
+ struct group *grp;
+ char **gr;
+ struct passwd *pwd;
+ bool winbind_env;
+ bool ret = False;
+
+ *pp_uids = NULL;
+ *p_num = 0;
+
+ /* We only look at our own sam, so don't care about imported stuff */
+ winbind_env = winbind_env_set();
+ (void)winbind_off();
+
+ if ((grp = getgrgid(gid)) == NULL) {
+ /* allow winbindd lookups, but only if they weren't already disabled */
+ goto done;
+ }
+
+ /* Primary group members */
+ setpwent();
+ while ((pwd = getpwent()) != NULL) {
+ if (pwd->pw_gid == gid) {
+ if (!add_uid_to_array_unique(mem_ctx, pwd->pw_uid,
+ pp_uids, p_num)) {
+ goto done;
+ }
+ }
+ }
+ endpwent();
+
+ /* Secondary group members */
+ for (gr = grp->gr_mem; (*gr != NULL) && ((*gr)[0] != '\0'); gr += 1) {
+ struct passwd *pw = getpwnam(*gr);
+
+ if (pw == NULL)
+ continue;
+ if (!add_uid_to_array_unique(mem_ctx, pw->pw_uid, pp_uids, p_num)) {
+ goto done;
+ }
+ }
+
+ ret = True;
+
+ done:
+
+ /* allow winbindd lookups, but only if they weren't already disabled */
+ if (!winbind_env) {
+ (void)winbind_on();
+ }
+
+ return ret;
+}
+
+static NTSTATUS pdb_default_enum_group_members(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group,
+ uint32_t **pp_member_rids,
+ size_t *p_num_members)
+{
+ gid_t gid;
+ uid_t *uids;
+ uint32_t i, num_uids;
+
+ *pp_member_rids = NULL;
+ *p_num_members = 0;
+
+ if (!sid_to_gid(group, &gid))
+ return NT_STATUS_NO_SUCH_GROUP;
+
+ if(!get_memberuids(mem_ctx, gid, &uids, &num_uids))
+ return NT_STATUS_NO_SUCH_GROUP;
+
+ if (num_uids == 0)
+ return NT_STATUS_OK;
+
+ *pp_member_rids = talloc_zero_array(mem_ctx, uint32_t, num_uids);
+
+ for (i=0; i<num_uids; i++) {
+ struct dom_sid sid;
+
+ uid_to_sid(&sid, uids[i]);
+
+ if (!sid_check_is_in_our_sam(&sid)) {
+ DEBUG(5, ("Inconsistent SAM -- group member uid not "
+ "in our domain\n"));
+ continue;
+ }
+
+ sid_peek_rid(&sid, &(*pp_member_rids)[*p_num_members]);
+ *p_num_members += 1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_default_enum_group_memberships(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user,
+ struct dom_sid **pp_sids,
+ gid_t **pp_gids,
+ uint32_t *p_num_groups)
+{
+ size_t i;
+ gid_t gid;
+ struct passwd *pw;
+ const char *username = pdb_get_username(user);
+
+
+ /* Ignore the primary group SID. Honor the real Unix primary group.
+ The primary group SID is only of real use to Windows clients */
+
+ if ( !(pw = Get_Pwnam_alloc(mem_ctx, username)) ) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ gid = pw->pw_gid;
+
+ TALLOC_FREE( pw );
+
+ if (!getgroups_unix_user(mem_ctx, username, gid, pp_gids, p_num_groups)) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (*p_num_groups == 0) {
+ smb_panic("primary group missing");
+ }
+
+ *pp_sids = talloc_array(mem_ctx, struct dom_sid, *p_num_groups);
+
+ if (*pp_sids == NULL) {
+ TALLOC_FREE(*pp_gids);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<*p_num_groups; i++) {
+ gid_to_sid(&(*pp_sids)[i], (*pp_gids)[i]);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Look up a rid in the SAM we're responsible for (i.e. passdb)
+ ********************************************************************/
+
+static bool lookup_global_sam_rid(TALLOC_CTX *mem_ctx, uint32_t rid,
+ const char **name,
+ enum lsa_SidType *psid_name_use,
+ uid_t *uid, gid_t *gid)
+{
+ struct samu *sam_account = NULL;
+ GROUP_MAP *map = NULL;
+ bool ret;
+ struct dom_sid sid;
+
+ *psid_name_use = SID_NAME_UNKNOWN;
+
+ DEBUG(5,("lookup_global_sam_rid: looking up RID %u.\n",
+ (unsigned int)rid));
+
+ sid_compose(&sid, get_global_sam_sid(), rid);
+
+ /* see if the passdb can help us with the name of the user */
+
+ if ( !(sam_account = samu_new( NULL )) ) {
+ return False;
+ }
+
+ map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (!map) {
+ return false;
+ }
+
+ /* BEING ROOT BLOCK */
+ become_root();
+ ret = pdb_getsampwsid(sam_account, &sid);
+ if (!ret) {
+ TALLOC_FREE(sam_account);
+ ret = pdb_getgrsid(map, sid);
+ }
+ unbecome_root();
+ /* END BECOME_ROOT BLOCK */
+
+ if (sam_account || !ret) {
+ TALLOC_FREE(map);
+ }
+
+ if (sam_account) {
+ struct passwd *pw;
+
+ *name = talloc_strdup(mem_ctx, pdb_get_username(sam_account));
+ if (!*name) {
+ TALLOC_FREE(sam_account);
+ return False;
+ }
+
+ *psid_name_use = SID_NAME_USER;
+
+ TALLOC_FREE(sam_account);
+
+ if (uid == NULL) {
+ return True;
+ }
+
+ pw = Get_Pwnam_alloc(talloc_tos(), *name);
+ if (pw == NULL) {
+ return False;
+ }
+ *uid = pw->pw_uid;
+ TALLOC_FREE(pw);
+ return True;
+
+ } else if (map && (map->gid != (gid_t)-1)) {
+
+ /* do not resolve SIDs to a name unless there is a valid
+ gid associated with it */
+
+ *name = talloc_steal(mem_ctx, map->nt_name);
+ *psid_name_use = map->sid_name_use;
+
+ if (gid) {
+ *gid = map->gid;
+ }
+
+ TALLOC_FREE(map);
+ return True;
+ }
+
+ TALLOC_FREE(map);
+
+ /* Windows will always map RID 513 to something. On a non-domain
+ controller, this gets mapped to SERVER\None. */
+
+ if (uid || gid) {
+ DEBUG(5, ("Can't find a unix id for an unmapped group\n"));
+ return False;
+ }
+
+ if ( rid == DOMAIN_RID_USERS ) {
+ *name = talloc_strdup(mem_ctx, "None" );
+ *psid_name_use = SID_NAME_DOM_GRP;
+
+ return True;
+ }
+
+ return False;
+}
+
+static NTSTATUS pdb_default_lookup_rids(struct pdb_methods *methods,
+ const struct dom_sid *domain_sid,
+ int num_rids,
+ uint32_t *rids,
+ const char **names,
+ enum lsa_SidType *attrs)
+{
+ int i;
+ NTSTATUS result;
+ bool have_mapped = False;
+ bool have_unmapped = False;
+
+ if (sid_check_is_builtin(domain_sid)) {
+
+ for (i=0; i<num_rids; i++) {
+ const char *name;
+
+ if (lookup_builtin_rid(names, rids[i], &name)) {
+ attrs[i] = SID_NAME_ALIAS;
+ names[i] = name;
+ DEBUG(5,("lookup_rids: %s:%d\n",
+ names[i], attrs[i]));
+ have_mapped = True;
+ } else {
+ have_unmapped = True;
+ attrs[i] = SID_NAME_UNKNOWN;
+ }
+ }
+ goto done;
+ }
+
+ /* Should not happen, but better check once too many */
+ if (!sid_check_is_our_sam(domain_sid)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ for (i = 0; i < num_rids; i++) {
+ const char *name;
+
+ if (lookup_global_sam_rid(names, rids[i], &name, &attrs[i],
+ NULL, NULL)) {
+ if (name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ names[i] = name;
+ DEBUG(5,("lookup_rids: %s:%d\n", names[i], attrs[i]));
+ have_mapped = True;
+ } else {
+ have_unmapped = True;
+ attrs[i] = SID_NAME_UNKNOWN;
+ }
+ }
+
+ done:
+
+ result = NT_STATUS_NONE_MAPPED;
+
+ if (have_mapped)
+ result = have_unmapped ? STATUS_SOME_UNMAPPED : NT_STATUS_OK;
+
+ return result;
+}
+
+static int pdb_search_destructor(struct pdb_search *search)
+{
+ if ((!search->search_ended) && (search->search_end != NULL)) {
+ search->search_end(search);
+ }
+ return 0;
+}
+
+struct pdb_search *pdb_search_init(TALLOC_CTX *mem_ctx,
+ enum pdb_search_type type)
+{
+ struct pdb_search *result;
+
+ result = talloc(mem_ctx, struct pdb_search);
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ result->type = type;
+ result->cache = NULL;
+ result->num_entries = 0;
+ result->cache_size = 0;
+ result->search_ended = False;
+ result->search_end = NULL;
+
+ /* Segfault appropriately if not initialized */
+ result->next_entry = NULL;
+ result->search_end = NULL;
+
+ talloc_set_destructor(result, pdb_search_destructor);
+
+ return result;
+}
+
+static void fill_displayentry(TALLOC_CTX *mem_ctx, uint32_t rid,
+ uint16_t acct_flags,
+ const char *account_name,
+ const char *fullname,
+ const char *description,
+ struct samr_displayentry *entry)
+{
+ entry->rid = rid;
+ entry->acct_flags = acct_flags;
+
+ if (account_name != NULL)
+ entry->account_name = talloc_strdup(mem_ctx, account_name);
+ else
+ entry->account_name = "";
+
+ if (fullname != NULL)
+ entry->fullname = talloc_strdup(mem_ctx, fullname);
+ else
+ entry->fullname = "";
+
+ if (description != NULL)
+ entry->description = talloc_strdup(mem_ctx, description);
+ else
+ entry->description = "";
+}
+
+struct group_search {
+ GROUP_MAP **groups;
+ size_t num_groups, current_group;
+};
+
+static bool next_entry_groups(struct pdb_search *s,
+ struct samr_displayentry *entry)
+{
+ struct group_search *state = (struct group_search *)s->private_data;
+ uint32_t rid;
+ GROUP_MAP *map;
+
+ if (state->current_group == state->num_groups)
+ return False;
+
+ map = state->groups[state->current_group];
+
+ sid_peek_rid(&map->sid, &rid);
+
+ fill_displayentry(s, rid, 0, map->nt_name, NULL, map->comment, entry);
+
+ state->current_group += 1;
+ return True;
+}
+
+static void search_end_groups(struct pdb_search *search)
+{
+ struct group_search *state =
+ (struct group_search *)search->private_data;
+ TALLOC_FREE(state->groups);
+}
+
+static bool pdb_search_grouptype(struct pdb_methods *methods,
+ struct pdb_search *search,
+ const struct dom_sid *sid, enum lsa_SidType type)
+{
+ struct group_search *state;
+
+ state = talloc_zero(search, struct group_search);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return False;
+ }
+
+ if (!NT_STATUS_IS_OK(methods->enum_group_mapping(methods, sid, type,
+ &state->groups, &state->num_groups,
+ True))) {
+ DEBUG(0, ("Could not enum groups\n"));
+ return False;
+ }
+
+ state->current_group = 0;
+ search->private_data = state;
+ search->next_entry = next_entry_groups;
+ search->search_end = search_end_groups;
+ return True;
+}
+
+static bool pdb_default_search_groups(struct pdb_methods *methods,
+ struct pdb_search *search)
+{
+ return pdb_search_grouptype(methods, search, get_global_sam_sid(), SID_NAME_DOM_GRP);
+}
+
+static bool pdb_default_search_aliases(struct pdb_methods *methods,
+ struct pdb_search *search,
+ const struct dom_sid *sid)
+{
+
+ return pdb_search_grouptype(methods, search, sid, SID_NAME_ALIAS);
+}
+
+static struct samr_displayentry *pdb_search_getentry(struct pdb_search *search,
+ uint32_t idx)
+{
+ if (idx < search->num_entries)
+ return &search->cache[idx];
+
+ if (search->search_ended)
+ return NULL;
+
+ while (idx >= search->num_entries) {
+ struct samr_displayentry entry;
+
+ if (!search->next_entry(search, &entry)) {
+ search->search_end(search);
+ search->search_ended = True;
+ break;
+ }
+
+ ADD_TO_LARGE_ARRAY(search, struct samr_displayentry,
+ entry, &search->cache, &search->num_entries,
+ &search->cache_size);
+ }
+
+ return (search->num_entries > idx) ? &search->cache[idx] : NULL;
+}
+
+struct pdb_search *pdb_search_users(TALLOC_CTX *mem_ctx, uint32_t acct_flags)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ struct pdb_search *result;
+
+ result = pdb_search_init(mem_ctx, PDB_USER_SEARCH);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (!pdb->search_users(pdb, result, acct_flags)) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ return result;
+}
+
+struct pdb_search *pdb_search_groups(TALLOC_CTX *mem_ctx)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ struct pdb_search *result;
+
+ result = pdb_search_init(mem_ctx, PDB_GROUP_SEARCH);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (!pdb->search_groups(pdb, result)) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ return result;
+}
+
+struct pdb_search *pdb_search_aliases(TALLOC_CTX *mem_ctx, const struct dom_sid *sid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ struct pdb_search *result;
+
+ if (pdb == NULL) return NULL;
+
+ result = pdb_search_init(mem_ctx, PDB_ALIAS_SEARCH);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (!pdb->search_aliases(pdb, result, sid)) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ return result;
+}
+
+uint32_t pdb_search_entries(struct pdb_search *search,
+ uint32_t start_idx, uint32_t max_entries,
+ struct samr_displayentry **result)
+{
+ struct samr_displayentry *end_entry;
+ uint32_t end_idx = start_idx+max_entries-1;
+
+ /* The first entry needs to be searched after the last. Otherwise the
+ * first entry might have moved due to a realloc during the search for
+ * the last entry. */
+
+ end_entry = pdb_search_getentry(search, end_idx);
+ *result = pdb_search_getentry(search, start_idx);
+
+ if (end_entry != NULL)
+ return max_entries;
+
+ if (start_idx >= search->num_entries)
+ return 0;
+
+ return search->num_entries - start_idx;
+}
+
+/*******************************************************************
+ trustdom methods
+ *******************************************************************/
+
+bool pdb_get_trusteddom_pw(const char *domain, char** pwd, struct dom_sid *sid,
+ time_t *pass_last_set_time)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->get_trusteddom_pw(pdb, domain, pwd, sid,
+ pass_last_set_time);
+}
+
+NTSTATUS pdb_get_trusteddom_creds(const char *domain, TALLOC_CTX *mem_ctx,
+ struct cli_credentials **creds)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->get_trusteddom_creds(pdb, domain, mem_ctx, creds);
+}
+
+bool pdb_set_trusteddom_pw(const char* domain, const char* pwd,
+ const struct dom_sid *sid)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->set_trusteddom_pw(pdb, domain, pwd, sid);
+}
+
+bool pdb_del_trusteddom_pw(const char *domain)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->del_trusteddom_pw(pdb, domain);
+}
+
+NTSTATUS pdb_enum_trusteddoms(TALLOC_CTX *mem_ctx, uint32_t *num_domains,
+ struct trustdom_info ***domains)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->enum_trusteddoms(pdb, mem_ctx, num_domains, domains);
+}
+
+/*******************************************************************
+ the defaults for trustdom methods:
+ these simply call the original passdb/secrets.c actions,
+ to be replaced by pdb_ldap.
+ *******************************************************************/
+
+static bool pdb_default_get_trusteddom_pw(struct pdb_methods *methods,
+ const char *domain,
+ char** pwd,
+ struct dom_sid *sid,
+ time_t *pass_last_set_time)
+{
+ return secrets_fetch_trusted_domain_password(domain, pwd,
+ sid, pass_last_set_time);
+
+}
+
+static NTSTATUS pdb_default_get_trusteddom_creds(struct pdb_methods *methods,
+ const char *domain,
+ TALLOC_CTX *mem_ctx,
+ struct cli_credentials **creds)
+{
+ *creds = NULL;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static bool pdb_default_set_trusteddom_pw(struct pdb_methods *methods,
+ const char* domain,
+ const char* pwd,
+ const struct dom_sid *sid)
+{
+ return secrets_store_trusted_domain_password(domain, pwd, sid);
+}
+
+static bool pdb_default_del_trusteddom_pw(struct pdb_methods *methods,
+ const char *domain)
+{
+ return trusted_domain_password_delete(domain);
+}
+
+static NTSTATUS pdb_default_enum_trusteddoms(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_domains,
+ struct trustdom_info ***domains)
+{
+ return secrets_trusted_domains(mem_ctx, num_domains, domains);
+}
+
+/*******************************************************************
+ trusted_domain methods
+ *******************************************************************/
+
+NTSTATUS pdb_get_trusted_domain(TALLOC_CTX *mem_ctx, const char *domain,
+ struct pdb_trusted_domain **td)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->get_trusted_domain(pdb, mem_ctx, domain, td);
+}
+
+NTSTATUS pdb_get_trusted_domain_by_sid(TALLOC_CTX *mem_ctx, struct dom_sid *sid,
+ struct pdb_trusted_domain **td)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->get_trusted_domain_by_sid(pdb, mem_ctx, sid, td);
+}
+
+NTSTATUS pdb_set_trusted_domain(const char* domain,
+ const struct pdb_trusted_domain *td)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->set_trusted_domain(pdb, domain, td);
+}
+
+NTSTATUS pdb_del_trusted_domain(const char *domain)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->del_trusted_domain(pdb, domain);
+}
+
+NTSTATUS pdb_enum_trusted_domains(TALLOC_CTX *mem_ctx, uint32_t *num_domains,
+ struct pdb_trusted_domain ***domains)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->enum_trusted_domains(pdb, mem_ctx, num_domains, domains);
+}
+
+static NTSTATUS pdb_default_get_trusted_domain(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const char *domain,
+ struct pdb_trusted_domain **td)
+{
+ struct trustAuthInOutBlob taiob;
+ struct AuthenticationInformation aia;
+ struct pdb_trusted_domain *tdom;
+ enum ndr_err_code ndr_err;
+ time_t last_set_time;
+ char *pwd;
+ bool ok;
+
+ tdom = talloc(mem_ctx, struct pdb_trusted_domain);
+ if (!tdom) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ tdom->domain_name = talloc_strdup(tdom, domain);
+ tdom->netbios_name = talloc_strdup(tdom, domain);
+ if (!tdom->domain_name || !tdom->netbios_name) {
+ talloc_free(tdom);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ tdom->trust_auth_incoming = data_blob_null;
+
+ ok = pdb_get_trusteddom_pw(domain, &pwd, &tdom->security_identifier,
+ &last_set_time);
+ if (!ok) {
+ talloc_free(tdom);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ZERO_STRUCT(taiob);
+ ZERO_STRUCT(aia);
+ taiob.count = 1;
+ taiob.current.count = 1;
+ taiob.current.array = &aia;
+ unix_to_nt_time(&aia.LastUpdateTime, last_set_time);
+
+ aia.AuthType = TRUST_AUTH_TYPE_CLEAR;
+ aia.AuthInfo.clear.size = strlen(pwd);
+ aia.AuthInfo.clear.password = (uint8_t *)talloc_memdup(tdom, pwd,
+ aia.AuthInfo.clear.size);
+ SAFE_FREE(pwd);
+ if (aia.AuthInfo.clear.password == NULL) {
+ talloc_free(tdom);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ taiob.previous.count = 0;
+ taiob.previous.array = NULL;
+
+ ndr_err = ndr_push_struct_blob(&tdom->trust_auth_outgoing,
+ tdom, &taiob,
+ (ndr_push_flags_fn_t)ndr_push_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tdom);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ tdom->trust_direction = LSA_TRUST_DIRECTION_OUTBOUND;
+ tdom->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+ tdom->trust_attributes = 0;
+ tdom->trust_forest_trust_info = data_blob_null;
+
+ *td = tdom;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_default_get_trusted_domain_by_sid(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid *sid,
+ struct pdb_trusted_domain **td)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+#define IS_NULL_DATA_BLOB(d) ((d).data == NULL && (d).length == 0)
+
+static NTSTATUS pdb_default_set_trusted_domain(struct pdb_methods *methods,
+ const char* domain,
+ const struct pdb_trusted_domain *td)
+{
+ struct trustAuthInOutBlob taiob;
+ struct AuthenticationInformation *aia;
+ enum ndr_err_code ndr_err;
+ char *pwd;
+ bool ok;
+
+ if (td->trust_attributes != 0 ||
+ td->trust_type != LSA_TRUST_TYPE_DOWNLEVEL ||
+ td->trust_direction != LSA_TRUST_DIRECTION_OUTBOUND ||
+ !IS_NULL_DATA_BLOB(td->trust_auth_incoming) ||
+ !IS_NULL_DATA_BLOB(td->trust_forest_trust_info)) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ ZERO_STRUCT(taiob);
+ ndr_err = ndr_pull_struct_blob(&td->trust_auth_outgoing, talloc_tos(),
+ &taiob,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ aia = (struct AuthenticationInformation *) taiob.current.array;
+
+ if (taiob.count != 1 || taiob.current.count != 1 ||
+ taiob.previous.count != 0 ||
+ aia->AuthType != TRUST_AUTH_TYPE_CLEAR) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ pwd = talloc_strndup(talloc_tos(), (char *) aia->AuthInfo.clear.password,
+ aia->AuthInfo.clear.size);
+ if (!pwd) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = pdb_set_trusteddom_pw(domain, pwd, &td->security_identifier);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_default_del_trusted_domain(struct pdb_methods *methods,
+ const char *domain)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_default_enum_trusted_domains(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_domains,
+ struct pdb_trusted_domain ***domains)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static struct pdb_domain_info *pdb_default_get_domain_info(
+ struct pdb_methods *m, TALLOC_CTX *mem_ctx)
+{
+ return NULL;
+}
+
+/*****************************************************************
+ UPN suffixes
+ *****************************************************************/
+static NTSTATUS pdb_default_enum_upn_suffixes(struct pdb_methods *pdb,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_suffixes,
+ char ***suffixes)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_default_set_upn_suffixes(struct pdb_methods *pdb,
+ uint32_t num_suffixes,
+ const char **suffixes)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS pdb_enum_upn_suffixes(TALLOC_CTX *mem_ctx,
+ uint32_t *num_suffixes,
+ char ***suffixes)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->enum_upn_suffixes(pdb, mem_ctx, num_suffixes, suffixes);
+}
+
+NTSTATUS pdb_set_upn_suffixes(uint32_t num_suffixes,
+ const char **suffixes)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->set_upn_suffixes(pdb, num_suffixes, suffixes);
+}
+
+/*******************************************************************
+ idmap control methods
+ *******************************************************************/
+static bool pdb_default_is_responsible_for_our_sam(
+ struct pdb_methods *methods)
+{
+ return true;
+}
+
+static bool pdb_default_is_responsible_for_builtin(
+ struct pdb_methods *methods)
+{
+ return true;
+}
+
+static bool pdb_default_is_responsible_for_wellknown(
+ struct pdb_methods *methods)
+{
+ return false;
+}
+
+static bool pdb_default_is_responsible_for_unix_users(
+ struct pdb_methods *methods)
+{
+ return true;
+}
+
+static bool pdb_default_is_responsible_for_unix_groups(
+ struct pdb_methods *methods)
+{
+ return true;
+}
+
+static bool pdb_default_is_responsible_for_everything_else(
+ struct pdb_methods *methods)
+{
+ return false;
+}
+
+bool pdb_is_responsible_for_our_sam(void)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->is_responsible_for_our_sam(pdb);
+}
+
+bool pdb_is_responsible_for_builtin(void)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->is_responsible_for_builtin(pdb);
+}
+
+bool pdb_is_responsible_for_wellknown(void)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->is_responsible_for_wellknown(pdb);
+}
+
+bool pdb_is_responsible_for_unix_users(void)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->is_responsible_for_unix_users(pdb);
+}
+
+bool pdb_is_responsible_for_unix_groups(void)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->is_responsible_for_unix_groups(pdb);
+}
+
+bool pdb_is_responsible_for_everything_else(void)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->is_responsible_for_everything_else(pdb);
+}
+
+/*******************************************************************
+ secret methods
+ *******************************************************************/
+
+NTSTATUS pdb_get_secret(TALLOC_CTX *mem_ctx,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ NTTIME *secret_current_lastchange,
+ DATA_BLOB *secret_old,
+ NTTIME *secret_old_lastchange,
+ struct security_descriptor **sd)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->get_secret(pdb, mem_ctx, secret_name,
+ secret_current, secret_current_lastchange,
+ secret_old, secret_old_lastchange,
+ sd);
+}
+
+NTSTATUS pdb_set_secret(const char *secret_name,
+ DATA_BLOB *secret_current,
+ DATA_BLOB *secret_old,
+ struct security_descriptor *sd)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->set_secret(pdb, secret_name,
+ secret_current,
+ secret_old,
+ sd);
+}
+
+NTSTATUS pdb_delete_secret(const char *secret_name)
+{
+ struct pdb_methods *pdb = pdb_get_methods();
+ return pdb->delete_secret(pdb, secret_name);
+}
+
+static NTSTATUS pdb_default_get_secret(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ NTTIME *secret_current_lastchange,
+ DATA_BLOB *secret_old,
+ NTTIME *secret_old_lastchange,
+ struct security_descriptor **sd)
+{
+ return lsa_secret_get(mem_ctx, secret_name,
+ secret_current,
+ secret_current_lastchange,
+ secret_old,
+ secret_old_lastchange,
+ sd);
+}
+
+static NTSTATUS pdb_default_set_secret(struct pdb_methods *methods,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ DATA_BLOB *secret_old,
+ struct security_descriptor *sd)
+{
+ return lsa_secret_set(secret_name,
+ secret_current,
+ secret_old,
+ sd);
+}
+
+static NTSTATUS pdb_default_delete_secret(struct pdb_methods *methods,
+ const char *secret_name)
+{
+ return lsa_secret_delete(secret_name);
+}
+
+/*******************************************************************
+ Create a pdb_methods structure and initialize it with the default
+ operations. In this way a passdb module can simply implement
+ the functionality it cares about. However, normally this is done
+ in groups of related functions.
+*******************************************************************/
+
+NTSTATUS make_pdb_method( struct pdb_methods **methods )
+{
+ /* allocate memory for the structure as its own talloc CTX */
+
+ *methods = talloc_zero(NULL, struct pdb_methods);
+ if (*methods == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*methods)->get_domain_info = pdb_default_get_domain_info;
+ (*methods)->getsampwnam = pdb_default_getsampwnam;
+ (*methods)->getsampwsid = pdb_default_getsampwsid;
+ (*methods)->create_user = pdb_default_create_user;
+ (*methods)->delete_user = pdb_default_delete_user;
+ (*methods)->add_sam_account = pdb_default_add_sam_account;
+ (*methods)->update_sam_account = pdb_default_update_sam_account;
+ (*methods)->delete_sam_account = pdb_default_delete_sam_account;
+ (*methods)->rename_sam_account = pdb_default_rename_sam_account;
+ (*methods)->update_login_attempts = pdb_default_update_login_attempts;
+
+ (*methods)->getgrsid = pdb_default_getgrsid;
+ (*methods)->getgrgid = pdb_default_getgrgid;
+ (*methods)->getgrnam = pdb_default_getgrnam;
+ (*methods)->create_dom_group = pdb_default_create_dom_group;
+ (*methods)->delete_dom_group = pdb_default_delete_dom_group;
+ (*methods)->add_group_mapping_entry = pdb_default_add_group_mapping_entry;
+ (*methods)->update_group_mapping_entry = pdb_default_update_group_mapping_entry;
+ (*methods)->delete_group_mapping_entry = pdb_default_delete_group_mapping_entry;
+ (*methods)->enum_group_mapping = pdb_default_enum_group_mapping;
+ (*methods)->enum_group_members = pdb_default_enum_group_members;
+ (*methods)->enum_group_memberships = pdb_default_enum_group_memberships;
+ (*methods)->set_unix_primary_group = pdb_default_set_unix_primary_group;
+ (*methods)->add_groupmem = pdb_default_add_groupmem;
+ (*methods)->del_groupmem = pdb_default_del_groupmem;
+ (*methods)->create_alias = pdb_default_create_alias;
+ (*methods)->delete_alias = pdb_default_delete_alias;
+ (*methods)->get_aliasinfo = pdb_default_get_aliasinfo;
+ (*methods)->set_aliasinfo = pdb_default_set_aliasinfo;
+ (*methods)->add_aliasmem = pdb_default_add_aliasmem;
+ (*methods)->del_aliasmem = pdb_default_del_aliasmem;
+ (*methods)->enum_aliasmem = pdb_default_enum_aliasmem;
+ (*methods)->enum_alias_memberships = pdb_default_alias_memberships;
+ (*methods)->lookup_rids = pdb_default_lookup_rids;
+ (*methods)->get_account_policy = pdb_default_get_account_policy;
+ (*methods)->set_account_policy = pdb_default_set_account_policy;
+ (*methods)->get_seq_num = pdb_default_get_seq_num;
+ (*methods)->id_to_sid = pdb_default_id_to_sid;
+ (*methods)->sid_to_id = pdb_default_sid_to_id;
+
+ (*methods)->search_groups = pdb_default_search_groups;
+ (*methods)->search_aliases = pdb_default_search_aliases;
+
+ (*methods)->get_trusteddom_pw = pdb_default_get_trusteddom_pw;
+ (*methods)->get_trusteddom_creds = pdb_default_get_trusteddom_creds;
+ (*methods)->set_trusteddom_pw = pdb_default_set_trusteddom_pw;
+ (*methods)->del_trusteddom_pw = pdb_default_del_trusteddom_pw;
+ (*methods)->enum_trusteddoms = pdb_default_enum_trusteddoms;
+
+ (*methods)->get_trusted_domain = pdb_default_get_trusted_domain;
+ (*methods)->get_trusted_domain_by_sid = pdb_default_get_trusted_domain_by_sid;
+ (*methods)->set_trusted_domain = pdb_default_set_trusted_domain;
+ (*methods)->del_trusted_domain = pdb_default_del_trusted_domain;
+ (*methods)->enum_trusted_domains = pdb_default_enum_trusted_domains;
+
+ (*methods)->get_secret = pdb_default_get_secret;
+ (*methods)->set_secret = pdb_default_set_secret;
+ (*methods)->delete_secret = pdb_default_delete_secret;
+
+ (*methods)->enum_upn_suffixes = pdb_default_enum_upn_suffixes;
+ (*methods)->set_upn_suffixes = pdb_default_set_upn_suffixes;
+
+ (*methods)->is_responsible_for_our_sam =
+ pdb_default_is_responsible_for_our_sam;
+ (*methods)->is_responsible_for_builtin =
+ pdb_default_is_responsible_for_builtin;
+ (*methods)->is_responsible_for_wellknown =
+ pdb_default_is_responsible_for_wellknown;
+ (*methods)->is_responsible_for_unix_users =
+ pdb_default_is_responsible_for_unix_users;
+ (*methods)->is_responsible_for_unix_groups =
+ pdb_default_is_responsible_for_unix_groups;
+ (*methods)->is_responsible_for_everything_else =
+ pdb_default_is_responsible_for_everything_else;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/passdb/pdb_ldap.c b/source3/passdb/pdb_ldap.c
new file mode 100644
index 0000000..ed5c3e9
--- /dev/null
+++ b/source3/passdb/pdb_ldap.c
@@ -0,0 +1,6822 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Jean François Micouleau 1998
+ Copyright (C) Gerald Carter 2001-2003
+ Copyright (C) Shahms King 2001
+ Copyright (C) Andrew Bartlett 2002-2003
+ Copyright (C) Stefan (metze) Metzmacher 2002-2003
+ Copyright (C) Simo Sorce 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+/* TODO:
+* persistent connections: if using NSS LDAP, many connections are made
+* however, using only one within Samba would be nice
+*
+* Clean up SSL stuff, compile on OpenLDAP 1.x, 2.x, and Netscape SDK
+*
+* Other LDAP based login attributes: accountExpires, etc.
+* (should be the domain of Samba proper, but the sam_password/struct samu
+* structures don't have fields for some of these attributes)
+*
+* SSL is done, but can't get the certificate based authentication to work
+* against on my test platform (Linux 2.4, OpenLDAP 2.x)
+*/
+
+/* NOTE: this will NOT work against an Active Directory server
+* due to the fact that the two password fields cannot be retrieved
+* from a server; recommend using security = domain in this situation
+* and/or winbind
+*/
+
+#include "includes.h"
+#include "passdb.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "secrets.h"
+#include "idmap_cache.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/util_pw.h"
+#include "lib/winbind_util.h"
+#include "librpc/gen_ndr/idmap.h"
+#include "lib/param/loadparm.h"
+#include "lib/util_sid_passdb.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+#include <lber.h>
+#include <ldap.h>
+
+
+#include "smbldap.h"
+#include "passdb/pdb_ldap.h"
+#include "passdb/pdb_nds.h"
+#include "passdb/pdb_ldap_util.h"
+#include "passdb/pdb_ldap_schema.h"
+
+/**********************************************************************
+ Simple helper function to make stuff better readable
+ **********************************************************************/
+
+LDAP *priv2ld(struct ldapsam_privates *priv)
+{
+ return smbldap_get_ldap(priv->smbldap_state);
+}
+
+/**********************************************************************
+ Get the attribute name given a user schema version.
+ **********************************************************************/
+
+static const char* get_userattr_key2string( int schema_ver, int key )
+{
+ switch ( schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT:
+ return get_attr_key2string( attrib_map_v30, key );
+
+ default:
+ DEBUG(0,("get_userattr_key2string: unknown schema version specified\n"));
+ break;
+ }
+ return NULL;
+}
+
+/**********************************************************************
+ Return the list of attribute names given a user schema version.
+**********************************************************************/
+
+const char** get_userattr_list( TALLOC_CTX *mem_ctx, int schema_ver )
+{
+ switch ( schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT:
+ return get_attr_list( mem_ctx, attrib_map_v30 );
+ default:
+ DEBUG(0,("get_userattr_list: unknown schema version specified!\n"));
+ break;
+ }
+
+ return NULL;
+}
+
+/**************************************************************************
+ Return the list of attribute names to delete given a user schema version.
+**************************************************************************/
+
+static const char** get_userattr_delete_list( TALLOC_CTX *mem_ctx,
+ int schema_ver )
+{
+ switch ( schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT:
+ return get_attr_list( mem_ctx,
+ attrib_map_to_delete_v30 );
+ default:
+ DEBUG(0,("get_userattr_delete_list: unknown schema version specified!\n"));
+ break;
+ }
+
+ return NULL;
+}
+
+
+/*******************************************************************
+ Generate the LDAP search filter for the objectclass based on the
+ version of the schema we are using.
+******************************************************************/
+
+static const char* get_objclass_filter( int schema_ver )
+{
+ fstring objclass_filter;
+ char *result;
+
+ switch( schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT:
+ fstr_sprintf( objclass_filter, "(objectclass=%s)", LDAP_OBJ_SAMBASAMACCOUNT );
+ break;
+ default:
+ DEBUG(0,("get_objclass_filter: Invalid schema version specified!\n"));
+ objclass_filter[0] = '\0';
+ break;
+ }
+
+ result = talloc_strdup(talloc_tos(), objclass_filter);
+ SMB_ASSERT(result != NULL);
+ return result;
+}
+
+/*****************************************************************
+ Scan a sequence number off OpenLDAP's syncrepl contextCSN
+******************************************************************/
+
+static NTSTATUS ldapsam_get_seq_num(struct pdb_methods *my_methods, time_t *seq_num)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL;
+ LDAPMessage *msg = NULL;
+ LDAPMessage *entry = NULL;
+ TALLOC_CTX *mem_ctx;
+ char **values = NULL;
+ int rc, num_result, num_values, rid;
+ char *suffix = NULL;
+ char *tok;
+ const char *p;
+ const char **attrs;
+
+ /* Unfortunately there is no proper way to detect syncrepl-support in
+ * smbldap_connect_system(). The syncrepl OIDs are submitted for publication
+ * but do not show up in the root-DSE yet. Neither we can query the
+ * subschema-context for the syncProviderSubentry or syncConsumerSubentry
+ * objectclass. Currently we require lp_ldap_suffix() to show up as
+ * namingContext. - Guenther
+ */
+
+ if (!lp_parm_bool(-1, "ldapsam", "syncrepl_seqnum", False)) {
+ return ntstatus;
+ }
+
+ if (!seq_num) {
+ DEBUG(3,("ldapsam_get_seq_num: no sequence_number\n"));
+ return ntstatus;
+ }
+
+ if (!smbldap_has_naming_context(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ lp_ldap_suffix())) {
+ DEBUG(3,("ldapsam_get_seq_num: DIT not configured to hold %s "
+ "as top-level namingContext\n", lp_ldap_suffix()));
+ return ntstatus;
+ }
+
+ mem_ctx = talloc_init("ldapsam_get_seq_num");
+
+ if (mem_ctx == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ if ((attrs = talloc_array(mem_ctx, const char *, 2)) == NULL) {
+ ntstatus = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* if we got a syncrepl-rid (up to three digits long) we speak with a consumer */
+ rid = lp_parm_int(-1, "ldapsam", "syncrepl_rid", -1);
+ if (rid > 0) {
+
+ /* consumer syncreplCookie: */
+ /* csn=20050126161620Z#0000001#00#00000 */
+ attrs[0] = talloc_strdup(mem_ctx, "syncreplCookie");
+ attrs[1] = NULL;
+ suffix = talloc_asprintf(mem_ctx,
+ "cn=syncrepl%d,%s", rid, lp_ldap_suffix());
+ if (!suffix) {
+ ntstatus = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ } else {
+
+ /* provider contextCSN */
+ /* 20050126161620Z#000009#00#000000 */
+ attrs[0] = talloc_strdup(mem_ctx, "contextCSN");
+ attrs[1] = NULL;
+ suffix = talloc_asprintf(mem_ctx,
+ "cn=ldapsync,%s", lp_ldap_suffix());
+
+ if (!suffix) {
+ ntstatus = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ rc = smbldap_search(ldap_state->smbldap_state, suffix,
+ LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, &msg);
+
+ if (rc != LDAP_SUCCESS) {
+ goto done;
+ }
+
+ num_result = ldap_count_entries(
+ smbldap_get_ldap(ldap_state->smbldap_state), msg);
+ if (num_result != 1) {
+ DEBUG(3,("ldapsam_get_seq_num: Expected one entry, got %d\n", num_result));
+ goto done;
+ }
+
+ entry = ldap_first_entry(
+ smbldap_get_ldap(ldap_state->smbldap_state), msg);
+ if (entry == NULL) {
+ DEBUG(3,("ldapsam_get_seq_num: Could not retrieve entry\n"));
+ goto done;
+ }
+
+ values = ldap_get_values(
+ smbldap_get_ldap(ldap_state->smbldap_state), entry, attrs[0]);
+ if (values == NULL) {
+ DEBUG(3,("ldapsam_get_seq_num: no values\n"));
+ goto done;
+ }
+
+ num_values = ldap_count_values(values);
+ if (num_values == 0) {
+ DEBUG(3,("ldapsam_get_seq_num: not a single value\n"));
+ goto done;
+ }
+
+ p = values[0];
+ if (!next_token_talloc(mem_ctx, &p, &tok, "#")) {
+ DEBUG(0,("ldapsam_get_seq_num: failed to parse sequence number\n"));
+ goto done;
+ }
+
+ p = tok;
+ if (!strncmp(p, "csn=", strlen("csn=")))
+ p += strlen("csn=");
+
+ DEBUG(10,("ldapsam_get_seq_num: got %s: %s\n", attrs[0], p));
+
+ *seq_num = generalized_to_unix_time(p);
+
+ /* very basic sanity check */
+ if (*seq_num <= 0) {
+ DEBUG(3,("ldapsam_get_seq_num: invalid sequence number: %d\n",
+ (int)*seq_num));
+ goto done;
+ }
+
+ ntstatus = NT_STATUS_OK;
+
+ done:
+ if (values != NULL)
+ ldap_value_free(values);
+ if (msg != NULL)
+ ldap_msgfree(msg);
+ if (mem_ctx)
+ talloc_destroy(mem_ctx);
+
+ return ntstatus;
+}
+
+/*******************************************************************
+ Run the search by name.
+******************************************************************/
+
+int ldapsam_search_suffix_by_name(struct ldapsam_privates *ldap_state,
+ const char *user,
+ LDAPMessage ** result,
+ const char **attr)
+{
+ char *filter = NULL;
+ char *escape_user = escape_ldap_string(talloc_tos(), user);
+ int ret = -1;
+
+ if (!escape_user) {
+ return LDAP_NO_MEMORY;
+ }
+
+ /*
+ * in the filter expression, replace %u with the real name
+ * so in ldap filter, %u MUST exist :-)
+ */
+ filter = talloc_asprintf(talloc_tos(), "(&%s%s)", "(uid=%u)",
+ get_objclass_filter(ldap_state->schema_ver));
+ if (!filter) {
+ TALLOC_FREE(escape_user);
+ return LDAP_NO_MEMORY;
+ }
+ /*
+ * have to use this here because $ is filtered out
+ * in string_sub
+ */
+
+ filter = talloc_all_string_sub(talloc_tos(),
+ filter, "%u", escape_user);
+ TALLOC_FREE(escape_user);
+ if (!filter) {
+ return LDAP_NO_MEMORY;
+ }
+
+ ret = smbldap_search_suffix(ldap_state->smbldap_state,
+ filter, attr, result);
+ TALLOC_FREE(filter);
+ return ret;
+}
+
+/*******************************************************************
+ Run the search by SID.
+******************************************************************/
+
+static int ldapsam_search_suffix_by_sid (struct ldapsam_privates *ldap_state,
+ const struct dom_sid *sid, LDAPMessage ** result,
+ const char **attr)
+{
+ char *filter = NULL;
+ int rc;
+ struct dom_sid_buf sid_string;
+
+ filter = talloc_asprintf(talloc_tos(), "(&(%s=%s)%s)",
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_SID),
+ dom_sid_str_buf(sid, &sid_string),
+ get_objclass_filter(ldap_state->schema_ver));
+ if (!filter) {
+ return LDAP_NO_MEMORY;
+ }
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state,
+ filter, attr, result);
+
+ TALLOC_FREE(filter);
+ return rc;
+}
+
+/*******************************************************************
+ Delete complete object or objectclass and attrs from
+ object found in search_result depending on lp_ldap_delete_dn
+******************************************************************/
+
+static int ldapsam_delete_entry(struct ldapsam_privates *priv,
+ TALLOC_CTX *mem_ctx,
+ LDAPMessage *entry,
+ const char *objectclass,
+ const char **attrs)
+{
+ LDAPMod **mods = NULL;
+ char *name;
+ const char *dn;
+ BerElement *ptr = NULL;
+
+ dn = smbldap_talloc_dn(mem_ctx, priv2ld(priv), entry);
+ if (dn == NULL) {
+ return LDAP_NO_MEMORY;
+ }
+
+ if (lp_ldap_delete_dn()) {
+ return smbldap_delete(priv->smbldap_state, dn);
+ }
+
+ /* Ok, delete only the SAM attributes */
+
+ for (name = ldap_first_attribute(priv2ld(priv), entry, &ptr);
+ name != NULL;
+ name = ldap_next_attribute(priv2ld(priv), entry, ptr)) {
+ const char **attrib;
+
+ /* We are only allowed to delete the attributes that
+ really exist. */
+
+ for (attrib = attrs; *attrib != NULL; attrib++) {
+ if (strequal(*attrib, name)) {
+ DEBUG(10, ("ldapsam_delete_entry: deleting "
+ "attribute %s\n", name));
+ smbldap_set_mod(&mods, LDAP_MOD_DELETE, name,
+ NULL);
+ }
+ }
+ ldap_memfree(name);
+ }
+
+ if (ptr != NULL) {
+ ber_free(ptr, 0);
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_DELETE, "objectClass", objectclass);
+ smbldap_talloc_autofree_ldapmod(mem_ctx, mods);
+
+ return smbldap_modify(priv->smbldap_state, dn, mods);
+}
+
+static time_t ldapsam_get_entry_timestamp( struct ldapsam_privates *ldap_state, LDAPMessage * entry)
+{
+ char *temp;
+ struct tm tm = {};
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state), entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_MOD_TIMESTAMP),
+ talloc_tos());
+ if (!temp) {
+ return (time_t) 0;
+ }
+
+ if ( !strptime(temp, "%Y%m%d%H%M%SZ", &tm)) {
+ DEBUG(2,("ldapsam_get_entry_timestamp: strptime failed on: %s\n",
+ (char*)temp));
+ TALLOC_FREE(temp);
+ return (time_t) 0;
+ }
+ TALLOC_FREE(temp);
+ tzset();
+ return timegm(&tm);
+}
+
+/**********************************************************************
+ Initialize struct samu from an LDAP query.
+ (Based on init_sam_from_buffer in pdb_tdb.c)
+*********************************************************************/
+
+static bool init_sam_from_ldap(struct ldapsam_privates *ldap_state,
+ struct samu * sampass,
+ LDAPMessage * entry)
+{
+ time_t logon_time,
+ logoff_time,
+ kickoff_time,
+ pass_last_set_time,
+ pass_can_change_time,
+ ldap_entry_time,
+ bad_password_time;
+ char *username = NULL,
+ *domain = NULL,
+ *nt_username = NULL,
+ *fullname = NULL,
+ *homedir = NULL,
+ *dir_drive = NULL,
+ *logon_script = NULL,
+ *profile_path = NULL,
+ *acct_desc = NULL,
+ *workstations = NULL,
+ *munged_dial = NULL;
+ uint32_t user_rid;
+ uint8_t smblmpwd[LM_HASH_LEN],
+ smbntpwd[NT_HASH_LEN];
+ bool use_samba_attrs = True;
+ uint16_t logon_divs;
+ uint16_t bad_password_count = 0,
+ logon_count = 0;
+ uint32_t hours_len;
+ uint8_t hours[MAX_HOURS_LEN];
+ char *temp = NULL;
+ struct login_cache cache_entry;
+ uint32_t pwHistLen;
+ bool expand_explicit = lp_passdb_expand_explicit();
+ bool ret = false;
+ TALLOC_CTX *ctx = talloc_init("init_sam_from_ldap");
+
+ if (!ctx) {
+ return false;
+ }
+ if (sampass == NULL || ldap_state == NULL || entry == NULL) {
+ DEBUG(0, ("init_sam_from_ldap: NULL parameters found!\n"));
+ goto fn_exit;
+ }
+
+ if (priv2ld(ldap_state) == NULL) {
+ DEBUG(0, ("init_sam_from_ldap: ldap_state->smbldap_state->"
+ "ldap_struct is NULL!\n"));
+ goto fn_exit;
+ }
+
+ if (!(username = smbldap_talloc_first_attribute(priv2ld(ldap_state),
+ entry,
+ "uid",
+ ctx))) {
+ DEBUG(1, ("init_sam_from_ldap: No uid attribute found for "
+ "this user!\n"));
+ goto fn_exit;
+ }
+
+ DEBUG(2, ("init_sam_from_ldap: Entry found for user: %s\n", username));
+
+ nt_username = talloc_strdup(ctx, username);
+ if (!nt_username) {
+ goto fn_exit;
+ }
+
+ domain = talloc_strdup(ctx, ldap_state->domain_name);
+ if (!domain) {
+ goto fn_exit;
+ }
+
+ pdb_set_username(sampass, username, PDB_SET);
+
+ pdb_set_domain(sampass, domain, PDB_DEFAULT);
+ pdb_set_nt_username(sampass, nt_username, PDB_SET);
+
+ /* deal with different attributes between the schema first */
+
+ if ( ldap_state->schema_ver == SCHEMAVER_SAMBASAMACCOUNT ) {
+ if ((temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_SID),
+ ctx))!=NULL) {
+ pdb_set_user_sid_from_string(sampass, temp, PDB_SET);
+ }
+ } else {
+ if ((temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_RID),
+ ctx))!=NULL) {
+ user_rid = (uint32_t)atol(temp);
+ pdb_set_user_sid_from_rid(sampass, user_rid, PDB_SET);
+ }
+ }
+
+ if (IS_SAM_DEFAULT(sampass, PDB_USERSID)) {
+ DEBUG(1, ("init_sam_from_ldap: no %s or %s attribute found for this user %s\n",
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_SID),
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_RID),
+ username));
+ return False;
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_PWD_LAST_SET),
+ ctx);
+ if (temp) {
+ pass_last_set_time = (time_t) atol(temp);
+ pdb_set_pass_last_set_time(sampass,
+ pass_last_set_time, PDB_SET);
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_LOGON_TIME),
+ ctx);
+ if (temp) {
+ logon_time = (time_t) atol(temp);
+ pdb_set_logon_time(sampass, logon_time, PDB_SET);
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_LOGOFF_TIME),
+ ctx);
+ if (temp) {
+ logoff_time = (time_t) atol(temp);
+ pdb_set_logoff_time(sampass, logoff_time, PDB_SET);
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_KICKOFF_TIME),
+ ctx);
+ if (temp) {
+ kickoff_time = (time_t) atol(temp);
+ pdb_set_kickoff_time(sampass, kickoff_time, PDB_SET);
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_PWD_CAN_CHANGE),
+ ctx);
+ if (temp) {
+ pass_can_change_time = (time_t) atol(temp);
+ pdb_set_pass_can_change_time(sampass,
+ pass_can_change_time, PDB_SET);
+ }
+
+ /* recommend that 'gecos' and 'displayName' should refer to the same
+ * attribute OID. userFullName depreciated, only used by Samba
+ * primary rules of LDAP: don't make a new attribute when one is already defined
+ * that fits your needs; using cn then displayName rather than 'userFullName'
+ */
+
+ fullname = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_DISPLAY_NAME),
+ ctx);
+ if (fullname) {
+ pdb_set_fullname(sampass, fullname, PDB_SET);
+ } else {
+ fullname = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_CN),
+ ctx);
+ if (fullname) {
+ pdb_set_fullname(sampass, fullname, PDB_SET);
+ }
+ }
+
+ dir_drive = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_HOME_DRIVE),
+ ctx);
+ if (dir_drive) {
+ pdb_set_dir_drive(sampass, dir_drive, PDB_SET);
+ } else {
+ pdb_set_dir_drive( sampass, lp_logon_drive(), PDB_DEFAULT );
+ }
+
+ homedir = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_HOME_PATH),
+ ctx);
+ if (homedir) {
+ if (expand_explicit) {
+ homedir = talloc_sub_basic(ctx,
+ username,
+ domain,
+ homedir);
+ if (!homedir) {
+ goto fn_exit;
+ }
+ }
+ pdb_set_homedir(sampass, homedir, PDB_SET);
+ } else {
+ pdb_set_homedir(sampass,
+ talloc_sub_basic(ctx, username, domain,
+ lp_logon_home()),
+ PDB_DEFAULT);
+ }
+
+ logon_script = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_LOGON_SCRIPT),
+ ctx);
+ if (logon_script) {
+ if (expand_explicit) {
+ logon_script = talloc_sub_basic(ctx,
+ username,
+ domain,
+ logon_script);
+ if (!logon_script) {
+ goto fn_exit;
+ }
+ }
+ pdb_set_logon_script(sampass, logon_script, PDB_SET);
+ } else {
+ pdb_set_logon_script(sampass,
+ talloc_sub_basic(ctx, username, domain,
+ lp_logon_script()),
+ PDB_DEFAULT );
+ }
+
+ profile_path = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_PROFILE_PATH),
+ ctx);
+ if (profile_path) {
+ if (expand_explicit) {
+ profile_path = talloc_sub_basic(ctx,
+ username,
+ domain,
+ profile_path);
+ if (!profile_path) {
+ goto fn_exit;
+ }
+ }
+ pdb_set_profile_path(sampass, profile_path, PDB_SET);
+ } else {
+ pdb_set_profile_path(sampass,
+ talloc_sub_basic(ctx, username, domain,
+ lp_logon_path()),
+ PDB_DEFAULT );
+ }
+
+ acct_desc = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_DESC),
+ ctx);
+ if (acct_desc) {
+ pdb_set_acct_desc(sampass, acct_desc, PDB_SET);
+ }
+
+ workstations = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_WKS),
+ ctx);
+ if (workstations) {
+ pdb_set_workstations(sampass, workstations, PDB_SET);
+ }
+
+ munged_dial = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_MUNGED_DIAL),
+ ctx);
+ if (munged_dial) {
+ pdb_set_munged_dial(sampass, munged_dial, PDB_SET);
+ }
+
+ /* FIXME: hours stuff should be cleaner */
+
+ logon_divs = 168;
+ hours_len = 21;
+ memset(hours, 0xff, hours_len);
+
+ if (ldap_state->is_nds_ldap) {
+ char *user_dn;
+ size_t pwd_len;
+ char clear_text_pw[512];
+
+ /* Make call to Novell eDirectory ldap extension to get clear text password.
+ NOTE: This will only work if we have an SSL connection to eDirectory. */
+ user_dn = smbldap_talloc_dn(
+ ctx, smbldap_get_ldap(ldap_state->smbldap_state),
+ entry);
+ if (user_dn != NULL) {
+ DEBUG(3, ("init_sam_from_ldap: smbldap_talloc_dn(ctx, %s) returned '%s'\n", username, user_dn));
+
+ pwd_len = sizeof(clear_text_pw);
+ if (pdb_nds_get_password(ldap_state->smbldap_state, user_dn, &pwd_len, clear_text_pw) == LDAP_SUCCESS) {
+ nt_lm_owf_gen(clear_text_pw, smbntpwd, smblmpwd);
+ if (!pdb_set_lanman_passwd(sampass, smblmpwd, PDB_SET)) {
+ TALLOC_FREE(user_dn);
+ return False;
+ }
+ ZERO_STRUCT(smblmpwd);
+ if (!pdb_set_nt_passwd(sampass, smbntpwd, PDB_SET)) {
+ TALLOC_FREE(user_dn);
+ return False;
+ }
+ ZERO_STRUCT(smbntpwd);
+ use_samba_attrs = False;
+ }
+
+ TALLOC_FREE(user_dn);
+
+ } else {
+ DEBUG(0, ("init_sam_from_ldap: failed to get user_dn for '%s'\n", username));
+ }
+ }
+
+ if (use_samba_attrs) {
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_LMPW),
+ ctx);
+ if (temp) {
+ pdb_gethexpwd(temp, smblmpwd);
+ memset((char *)temp, '\0', strlen(temp)+1);
+ if (!pdb_set_lanman_passwd(sampass, smblmpwd, PDB_SET)) {
+ goto fn_exit;
+ }
+ ZERO_STRUCT(smblmpwd);
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_NTPW),
+ ctx);
+ if (temp) {
+ pdb_gethexpwd(temp, smbntpwd);
+ memset((char *)temp, '\0', strlen(temp)+1);
+ if (!pdb_set_nt_passwd(sampass, smbntpwd, PDB_SET)) {
+ goto fn_exit;
+ }
+ ZERO_STRUCT(smbntpwd);
+ }
+ }
+
+ pwHistLen = 0;
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen);
+ if (pwHistLen > 0){
+ uint8_t *pwhist = NULL;
+ int i;
+ char *history_string = talloc_array(ctx, char,
+ MAX_PW_HISTORY_LEN*64);
+
+ if (!history_string) {
+ goto fn_exit;
+ }
+
+ pwHistLen = MIN(pwHistLen, MAX_PW_HISTORY_LEN);
+
+ pwhist = talloc_zero_array(ctx, uint8_t,
+ pwHistLen * PW_HISTORY_ENTRY_LEN);
+ if (pwhist == NULL) {
+ DEBUG(0, ("init_sam_from_ldap: talloc failed!\n"));
+ goto fn_exit;
+ }
+
+ if (smbldap_get_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_PWD_HISTORY),
+ history_string,
+ MAX_PW_HISTORY_LEN*64)) {
+ bool hex_failed = false;
+ for (i = 0; i < pwHistLen; i++){
+ /* Get the 16 byte salt. */
+ if (!pdb_gethexpwd(&history_string[i*64],
+ &pwhist[i*PW_HISTORY_ENTRY_LEN])) {
+ hex_failed = true;
+ break;
+ }
+ /* Get the 16 byte MD5 hash of salt+passwd. */
+ if (!pdb_gethexpwd(&history_string[(i*64)+32],
+ &pwhist[(i*PW_HISTORY_ENTRY_LEN)+
+ PW_HISTORY_SALT_LEN])) {
+ hex_failed = True;
+ break;
+ }
+ }
+ if (hex_failed) {
+ DEBUG(2,("init_sam_from_ldap: Failed to get password history for user %s\n",
+ username));
+ memset(pwhist, '\0', pwHistLen * PW_HISTORY_ENTRY_LEN);
+ }
+ }
+ if (!pdb_set_pw_history(sampass, pwhist, pwHistLen, PDB_SET)){
+ goto fn_exit;
+ }
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_ACB_INFO),
+ ctx);
+ if (temp) {
+ uint32_t acct_ctrl = 0;
+ acct_ctrl = pdb_decode_acct_ctrl(temp);
+
+ if (acct_ctrl == 0) {
+ acct_ctrl |= ACB_NORMAL;
+ }
+
+ pdb_set_acct_ctrl(sampass, acct_ctrl, PDB_SET);
+ }
+
+ pdb_set_hours_len(sampass, hours_len, PDB_SET);
+ pdb_set_logon_divs(sampass, logon_divs, PDB_SET);
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_BAD_PASSWORD_COUNT),
+ ctx);
+ if (temp) {
+ bad_password_count = (uint32_t) atol(temp);
+ pdb_set_bad_password_count(sampass,
+ bad_password_count, PDB_SET);
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_BAD_PASSWORD_TIME),
+ ctx);
+ if (temp) {
+ bad_password_time = (time_t) atol(temp);
+ pdb_set_bad_password_time(sampass, bad_password_time, PDB_SET);
+ }
+
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_LOGON_COUNT),
+ ctx);
+ if (temp) {
+ logon_count = (uint32_t) atol(temp);
+ pdb_set_logon_count(sampass, logon_count, PDB_SET);
+ }
+
+ /* pdb_set_unknown_6(sampass, unknown6, PDB_SET); */
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_LOGON_HOURS),
+ ctx);
+ if (temp) {
+ pdb_gethexhours(temp, hours);
+ memset((char *)temp, '\0', strlen(temp) +1);
+ pdb_set_hours(sampass, hours, hours_len, PDB_SET);
+ ZERO_STRUCT(hours);
+ }
+
+ if (lp_parm_bool(-1, "ldapsam", "trusted", False)) {
+ struct passwd unix_pw;
+ bool have_uid = false;
+ bool have_gid = false;
+ struct dom_sid mapped_gsid;
+ const struct dom_sid *primary_gsid;
+ struct unixid id;
+ int error = 0;
+
+ ZERO_STRUCT(unix_pw);
+
+ unix_pw.pw_name = username;
+ unix_pw.pw_passwd = discard_const_p(char, "x");
+
+ temp = smbldap_talloc_single_attribute(
+ priv2ld(ldap_state),
+ entry,
+ "uidNumber",
+ ctx);
+ if (temp) {
+ /* We've got a uid, feed the cache */
+ unix_pw.pw_uid = smb_strtoul(temp,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_ERR("Failed to convert UID\n");
+ goto fn_exit;
+ }
+ have_uid = true;
+ }
+ temp = smbldap_talloc_single_attribute(
+ priv2ld(ldap_state),
+ entry,
+ "gidNumber",
+ ctx);
+ if (temp) {
+ /* We've got a uid, feed the cache */
+ unix_pw.pw_gid = smb_strtoul(temp,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_ERR("Failed to convert GID\n");
+ goto fn_exit;
+ }
+ have_gid = true;
+ }
+ unix_pw.pw_gecos = smbldap_talloc_single_attribute(
+ priv2ld(ldap_state),
+ entry,
+ "gecos",
+ ctx);
+ if (unix_pw.pw_gecos == NULL) {
+ unix_pw.pw_gecos = fullname;
+ }
+ unix_pw.pw_dir = smbldap_talloc_single_attribute(
+ priv2ld(ldap_state),
+ entry,
+ "homeDirectory",
+ ctx);
+ if (unix_pw.pw_dir == NULL) {
+ unix_pw.pw_dir = discard_const_p(char, "");
+ }
+ unix_pw.pw_shell = smbldap_talloc_single_attribute(
+ priv2ld(ldap_state),
+ entry,
+ "loginShell",
+ ctx);
+ if (unix_pw.pw_shell == NULL) {
+ unix_pw.pw_shell = discard_const_p(char, "");
+ }
+
+ if (have_uid && have_gid) {
+ sampass->unix_pw = tcopy_passwd(sampass, &unix_pw);
+ } else {
+ sampass->unix_pw = Get_Pwnam_alloc(sampass, unix_pw.pw_name);
+ }
+
+ if (sampass->unix_pw == NULL) {
+ DEBUG(0,("init_sam_from_ldap: Failed to find Unix account for %s\n",
+ pdb_get_username(sampass)));
+ goto fn_exit;
+ }
+
+ id.id = sampass->unix_pw->pw_uid;
+ id.type = ID_TYPE_UID;
+
+ idmap_cache_set_sid2unixid(pdb_get_user_sid(sampass), &id);
+
+ gid_to_sid(&mapped_gsid, sampass->unix_pw->pw_gid);
+ primary_gsid = pdb_get_group_sid(sampass);
+ if (primary_gsid && dom_sid_equal(primary_gsid, &mapped_gsid)) {
+ id.id = sampass->unix_pw->pw_gid;
+ id.type = ID_TYPE_GID;
+
+ idmap_cache_set_sid2unixid(primary_gsid, &id);
+ }
+ }
+
+ /* check the timestamp of the cache vs ldap entry */
+ if (!(ldap_entry_time = ldapsam_get_entry_timestamp(ldap_state,
+ entry))) {
+ ret = true;
+ goto fn_exit;
+ }
+
+ /* see if we have newer updates */
+ if (!login_cache_read(sampass, &cache_entry)) {
+ DEBUG (9, ("No cache entry, bad count = %u, bad time = %u\n",
+ (unsigned int)pdb_get_bad_password_count(sampass),
+ (unsigned int)pdb_get_bad_password_time(sampass)));
+ ret = true;
+ goto fn_exit;
+ }
+
+ DEBUG(7, ("ldap time is %u, cache time is %u, bad time = %u\n",
+ (unsigned int)ldap_entry_time,
+ (unsigned int)cache_entry.entry_timestamp,
+ (unsigned int)cache_entry.bad_password_time));
+
+ if (ldap_entry_time > cache_entry.entry_timestamp) {
+ /* cache is older than directory , so
+ we need to delete the entry but allow the
+ fields to be written out */
+ login_cache_delentry(sampass);
+ } else {
+ /* read cache in */
+ pdb_set_acct_ctrl(sampass,
+ pdb_get_acct_ctrl(sampass) |
+ (cache_entry.acct_ctrl & ACB_AUTOLOCK),
+ PDB_SET);
+ pdb_set_bad_password_count(sampass,
+ cache_entry.bad_password_count,
+ PDB_SET);
+ pdb_set_bad_password_time(sampass,
+ cache_entry.bad_password_time,
+ PDB_SET);
+ }
+
+ ret = true;
+
+ fn_exit:
+
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/**********************************************************************
+ Initialize the ldap db from a struct samu. Called on update.
+ (Based on init_buffer_from_sam in pdb_tdb.c)
+*********************************************************************/
+
+static bool init_ldap_from_sam (struct ldapsam_privates *ldap_state,
+ LDAPMessage *existing,
+ LDAPMod *** mods, struct samu * sampass,
+ bool (*need_update)(const struct samu *,
+ enum pdb_elements))
+{
+ char *temp = NULL;
+
+ if (mods == NULL || sampass == NULL) {
+ DEBUG(0, ("init_ldap_from_sam: NULL parameters found!\n"));
+ return False;
+ }
+
+ *mods = NULL;
+
+ /*
+ * took out adding "objectclass: sambaAccount"
+ * do this on a per-mod basis
+ */
+ if (need_update(sampass, PDB_USERNAME)) {
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ "uid", pdb_get_username(sampass));
+ if (ldap_state->is_nds_ldap) {
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ "cn", pdb_get_username(sampass));
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ "sn", pdb_get_username(sampass));
+ }
+ }
+
+ DEBUG(2, ("init_ldap_from_sam: Setting entry for user: %s\n", pdb_get_username(sampass)));
+
+ /* only update the RID if we actually need to */
+ if (need_update(sampass, PDB_USERSID)) {
+ struct dom_sid_buf sid_str;
+ const struct dom_sid *user_sid = pdb_get_user_sid(sampass);
+
+ switch ( ldap_state->schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT:
+ smbldap_make_mod(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_USER_SID),
+ dom_sid_str_buf(user_sid, &sid_str));
+ break;
+
+ default:
+ DEBUG(0,("init_ldap_from_sam: unknown schema version specified\n"));
+ break;
+ }
+ }
+
+ /* we don't need to store the primary group RID - so leaving it
+ 'free' to hang off the unix primary group makes life easier */
+
+ if (need_update(sampass, PDB_GROUPSID)) {
+ struct dom_sid_buf sid_str;
+ const struct dom_sid *group_sid = pdb_get_group_sid(sampass);
+
+ switch ( ldap_state->schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT:
+ smbldap_make_mod(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_PRIMARY_GROUP_SID),
+ dom_sid_str_buf(group_sid, &sid_str));
+ break;
+
+ default:
+ DEBUG(0,("init_ldap_from_sam: unknown schema version specified\n"));
+ break;
+ }
+
+ }
+
+ /* displayName, cn, and gecos should all be the same
+ * most easily accomplished by giving them the same OID
+ * gecos isn't set here b/c it should be handled by the
+ * add-user script
+ * We change displayName only and fall back to cn if
+ * it does not exist.
+ */
+
+ if (need_update(sampass, PDB_FULLNAME))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_DISPLAY_NAME),
+ pdb_get_fullname(sampass));
+
+ if (need_update(sampass, PDB_ACCTDESC))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_DESC),
+ pdb_get_acct_desc(sampass));
+
+ if (need_update(sampass, PDB_WORKSTATIONS))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_USER_WKS),
+ pdb_get_workstations(sampass));
+
+ if (need_update(sampass, PDB_MUNGEDDIAL))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_MUNGED_DIAL),
+ pdb_get_munged_dial(sampass));
+
+ if (need_update(sampass, PDB_SMBHOME))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_HOME_PATH),
+ pdb_get_homedir(sampass));
+
+ if (need_update(sampass, PDB_DRIVE))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_HOME_DRIVE),
+ pdb_get_dir_drive(sampass));
+
+ if (need_update(sampass, PDB_LOGONSCRIPT))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LOGON_SCRIPT),
+ pdb_get_logon_script(sampass));
+
+ if (need_update(sampass, PDB_PROFILE))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PROFILE_PATH),
+ pdb_get_profile_path(sampass));
+
+ if (asprintf(&temp, "%li", (long int)pdb_get_logon_time(sampass)) < 0) {
+ return false;
+ }
+ if (need_update(sampass, PDB_LOGONTIME))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LOGON_TIME), temp);
+ SAFE_FREE(temp);
+
+ if (asprintf(&temp, "%li", (long int)pdb_get_logoff_time(sampass)) < 0) {
+ return false;
+ }
+ if (need_update(sampass, PDB_LOGOFFTIME))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LOGOFF_TIME), temp);
+ SAFE_FREE(temp);
+
+ if (asprintf(&temp, "%li", (long int)pdb_get_kickoff_time(sampass)) < 0) {
+ return false;
+ }
+ if (need_update(sampass, PDB_KICKOFFTIME))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_KICKOFF_TIME), temp);
+ SAFE_FREE(temp);
+
+ if (asprintf(&temp, "%li", (long int)pdb_get_pass_can_change_time_noncalc(sampass)) < 0) {
+ return false;
+ }
+ if (need_update(sampass, PDB_CANCHANGETIME))
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_CAN_CHANGE), temp);
+ SAFE_FREE(temp);
+
+ if ((pdb_get_acct_ctrl(sampass)&(ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST))
+ || (lp_ldap_passwd_sync()!=LDAP_PASSWD_SYNC_ONLY)) {
+
+ if (need_update(sampass, PDB_LMPASSWD)) {
+ const uchar *lm_pw = pdb_get_lanman_passwd(sampass);
+ if (lm_pw) {
+ char pwstr[34];
+ pdb_sethexpwd(pwstr, lm_pw,
+ pdb_get_acct_ctrl(sampass));
+ smbldap_make_mod(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LMPW),
+ pwstr);
+ } else {
+ smbldap_make_mod(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_LMPW),
+ NULL);
+ }
+ }
+ if (need_update(sampass, PDB_NTPASSWD)) {
+ const uchar *nt_pw = pdb_get_nt_passwd(sampass);
+ if (nt_pw) {
+ char pwstr[34];
+ pdb_sethexpwd(pwstr, nt_pw,
+ pdb_get_acct_ctrl(sampass));
+ smbldap_make_mod(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_NTPW),
+ pwstr);
+ } else {
+ smbldap_make_mod(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_NTPW),
+ NULL);
+ }
+ }
+
+ if (need_update(sampass, PDB_PWHISTORY)) {
+ char *pwstr = NULL;
+ uint32_t pwHistLen = 0;
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHistLen);
+
+ pwstr = SMB_MALLOC_ARRAY(char, 1024);
+ if (!pwstr) {
+ return false;
+ }
+ if (pwHistLen == 0) {
+ /* Remove any password history from the LDAP store. */
+ memset(pwstr, '0', 64); /* NOTE !!!! '0' *NOT '\0' */
+ pwstr[64] = '\0';
+ } else {
+ int i;
+ uint32_t currHistLen = 0;
+ const uint8_t *pwhist = pdb_get_pw_history(sampass, &currHistLen);
+ if (pwhist != NULL) {
+ /* We can only store (1024-1/64 password history entries. */
+ pwHistLen = MIN(pwHistLen, ((1024-1)/64));
+ for (i=0; i< pwHistLen && i < currHistLen; i++) {
+ /* Store the salt. */
+ pdb_sethexpwd(&pwstr[i*64], &pwhist[i*PW_HISTORY_ENTRY_LEN], 0);
+ /* Followed by the md5 hash of salt + md4 hash */
+ pdb_sethexpwd(&pwstr[(i*64)+32],
+ &pwhist[(i*PW_HISTORY_ENTRY_LEN)+PW_HISTORY_SALT_LEN], 0);
+ DEBUG(100, ("pwstr=%s\n", pwstr));
+ }
+ }
+ }
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_HISTORY),
+ pwstr);
+ SAFE_FREE(pwstr);
+ }
+
+ if (need_update(sampass, PDB_PASSLASTSET)) {
+ if (asprintf(&temp, "%li",
+ (long int)pdb_get_pass_last_set_time(sampass)) < 0) {
+ return false;
+ }
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_PWD_LAST_SET),
+ temp);
+ SAFE_FREE(temp);
+ }
+ }
+
+ if (need_update(sampass, PDB_HOURS)) {
+ const uint8_t *hours = pdb_get_hours(sampass);
+ if (hours) {
+ char hourstr[44];
+ pdb_sethexhours(hourstr, hours);
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing,
+ mods,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_LOGON_HOURS),
+ hourstr);
+ }
+ }
+
+ if (need_update(sampass, PDB_ACCTCTRL))
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(ldap_state->schema_ver, LDAP_ATTR_ACB_INFO),
+ pdb_encode_acct_ctrl (pdb_get_acct_ctrl(sampass), NEW_PW_FORMAT_SPACE_PADDED_LEN));
+
+ /* password lockout cache:
+ - If we are now autolocking or clearing, we write to ldap
+ - If we are clearing, we delete the cache entry
+ - If the count is > 0, we update the cache
+
+ This even means when autolocking, we cache, just in case the
+ update doesn't work, and we have to cache the autolock flag */
+
+ if (need_update(sampass, PDB_BAD_PASSWORD_COUNT)) /* &&
+ need_update(sampass, PDB_BAD_PASSWORD_TIME)) */ {
+ uint16_t badcount = pdb_get_bad_password_count(sampass);
+ time_t badtime = pdb_get_bad_password_time(sampass);
+ uint32_t pol;
+ pdb_get_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, &pol);
+
+ DEBUG(3, ("updating bad password fields, policy=%u, count=%u, time=%u\n",
+ (unsigned int)pol, (unsigned int)badcount, (unsigned int)badtime));
+
+ if ((badcount >= pol) || (badcount == 0)) {
+ DEBUG(7, ("making mods to update ldap, count=%u, time=%u\n",
+ (unsigned int)badcount, (unsigned int)badtime));
+ if (asprintf(&temp, "%li", (long)badcount) < 0) {
+ return false;
+ }
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(
+ ldap_state->schema_ver,
+ LDAP_ATTR_BAD_PASSWORD_COUNT),
+ temp);
+ SAFE_FREE(temp);
+
+ if (asprintf(&temp, "%li", (long int)badtime) < 0) {
+ return false;
+ }
+ smbldap_make_mod(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ existing, mods,
+ get_userattr_key2string(
+ ldap_state->schema_ver,
+ LDAP_ATTR_BAD_PASSWORD_TIME),
+ temp);
+ SAFE_FREE(temp);
+ }
+ if (badcount == 0) {
+ DEBUG(7, ("bad password count is reset, deleting login cache entry for %s\n", pdb_get_nt_username(sampass)));
+ login_cache_delentry(sampass);
+ } else {
+ struct login_cache cache_entry;
+
+ cache_entry.entry_timestamp = time(NULL);
+ cache_entry.acct_ctrl = pdb_get_acct_ctrl(sampass);
+ cache_entry.bad_password_count = badcount;
+ cache_entry.bad_password_time = badtime;
+
+ DEBUG(7, ("Updating bad password count and time in login cache\n"));
+ login_cache_write(sampass, &cache_entry);
+ }
+ }
+
+ return True;
+}
+
+/**********************************************************************
+ End enumeration of the LDAP password list.
+*********************************************************************/
+
+static void ldapsam_endsampwent(struct pdb_methods *my_methods)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ if (ldap_state->result) {
+ ldap_msgfree(ldap_state->result);
+ ldap_state->result = NULL;
+ }
+}
+
+static void append_attr(TALLOC_CTX *mem_ctx, const char ***attr_list,
+ const char *new_attr)
+{
+ int i;
+
+ if (new_attr == NULL) {
+ return;
+ }
+
+ for (i=0; (*attr_list)[i] != NULL; i++) {
+ ;
+ }
+
+ (*attr_list) = talloc_realloc(mem_ctx, (*attr_list),
+ const char *, i+2);
+ SMB_ASSERT((*attr_list) != NULL);
+ (*attr_list)[i] = talloc_strdup((*attr_list), new_attr);
+ (*attr_list)[i+1] = NULL;
+}
+
+static void ldapsam_add_unix_attributes(TALLOC_CTX *mem_ctx,
+ const char ***attr_list)
+{
+ append_attr(mem_ctx, attr_list, "uidNumber");
+ append_attr(mem_ctx, attr_list, "gidNumber");
+ append_attr(mem_ctx, attr_list, "homeDirectory");
+ append_attr(mem_ctx, attr_list, "loginShell");
+ append_attr(mem_ctx, attr_list, "gecos");
+}
+
+/**********************************************************************
+Get struct samu entry from LDAP by username.
+*********************************************************************/
+
+static NTSTATUS ldapsam_getsampwnam(struct pdb_methods *my_methods, struct samu *user, const char *sname)
+{
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int count;
+ const char ** attr_list;
+ int rc;
+
+ attr_list = get_userattr_list( user, ldap_state->schema_ver );
+ append_attr(user, &attr_list,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_MOD_TIMESTAMP));
+ ldapsam_add_unix_attributes(user, &attr_list);
+ rc = ldapsam_search_suffix_by_name(ldap_state, sname, &result,
+ attr_list);
+ TALLOC_FREE( attr_list );
+
+ if ( rc != LDAP_SUCCESS )
+ return NT_STATUS_NO_SUCH_USER;
+
+ count = ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+
+ if (count < 1) {
+ DEBUG(4, ("ldapsam_getsampwnam: Unable to locate user [%s] count=%d\n", sname, count));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_USER;
+ } else if (count > 1) {
+ DEBUG(1, ("ldapsam_getsampwnam: Duplicate entries for this user [%s] Failing. count=%d\n", sname, count));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ entry = ldap_first_entry(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+ if (entry) {
+ if (!init_sam_from_ldap(ldap_state, user, entry)) {
+ DEBUG(1,("ldapsam_getsampwnam: init_sam_from_ldap failed for user '%s'!\n", sname));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ pdb_set_backend_private_data(user, result, NULL,
+ my_methods, PDB_CHANGED);
+ smbldap_talloc_autofree_ldapmsg(user, result);
+ ret = NT_STATUS_OK;
+ } else {
+ ldap_msgfree(result);
+ }
+ return ret;
+}
+
+static int ldapsam_get_ldap_user_by_sid(struct ldapsam_privates *ldap_state,
+ const struct dom_sid *sid, LDAPMessage **result)
+{
+ int rc = -1;
+ const char ** attr_list;
+
+ switch ( ldap_state->schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT: {
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return LDAP_NO_MEMORY;
+ }
+
+ attr_list = get_userattr_list(tmp_ctx,
+ ldap_state->schema_ver);
+ append_attr(tmp_ctx, &attr_list,
+ get_userattr_key2string(
+ ldap_state->schema_ver,
+ LDAP_ATTR_MOD_TIMESTAMP));
+ ldapsam_add_unix_attributes(tmp_ctx, &attr_list);
+ rc = ldapsam_search_suffix_by_sid(ldap_state, sid,
+ result, attr_list);
+ TALLOC_FREE(tmp_ctx);
+
+ if ( rc != LDAP_SUCCESS )
+ return rc;
+ break;
+ }
+
+ default:
+ DEBUG(0,("Invalid schema version specified\n"));
+ break;
+ }
+ return rc;
+}
+
+/**********************************************************************
+ Get struct samu entry from LDAP by SID.
+*********************************************************************/
+
+static NTSTATUS ldapsam_getsampwsid(struct pdb_methods *my_methods, struct samu * user, const struct dom_sid *sid)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int count;
+ int rc;
+
+ rc = ldapsam_get_ldap_user_by_sid(ldap_state,
+ sid, &result);
+ if (rc != LDAP_SUCCESS)
+ return NT_STATUS_NO_SUCH_USER;
+
+ count = ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+
+ if (count < 1) {
+ struct dom_sid_buf buf;
+ DEBUG(4, ("ldapsam_getsampwsid: Unable to locate SID [%s] "
+ "count=%d\n",
+ dom_sid_str_buf(sid, &buf),
+ count));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_USER;
+ } else if (count > 1) {
+ struct dom_sid_buf buf;
+ DEBUG(1, ("ldapsam_getsampwsid: More than one user with SID "
+ "[%s]. Failing. count=%d\n",
+ dom_sid_str_buf(sid, &buf),
+ count));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ entry = ldap_first_entry(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+ if (!entry) {
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (!init_sam_from_ldap(ldap_state, user, entry)) {
+ DEBUG(1,("ldapsam_getsampwsid: init_sam_from_ldap failed!\n"));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ pdb_set_backend_private_data(user, result, NULL,
+ my_methods, PDB_CHANGED);
+ smbldap_talloc_autofree_ldapmsg(user, result);
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Do the actual modification - also change a plaintext password if
+ it it set.
+**********************************************************************/
+
+static NTSTATUS ldapsam_modify_entry(struct pdb_methods *my_methods,
+ struct samu *newpwd, char *dn,
+ LDAPMod **mods, int ldap_op,
+ bool (*need_update)(const struct samu *, enum pdb_elements))
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ int rc;
+
+ if (!newpwd || !dn) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!(pdb_get_acct_ctrl(newpwd)&(ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST)) &&
+ (lp_ldap_passwd_sync() != LDAP_PASSWD_SYNC_OFF) &&
+ need_update(newpwd, PDB_PLAINTEXT_PW) &&
+ (pdb_get_plaintext_passwd(newpwd)!=NULL)) {
+ BerElement *ber;
+ struct berval *bv;
+ char *retoid = NULL;
+ struct berval *retdata = NULL;
+ char *utf8_password;
+ char *utf8_dn;
+ size_t converted_size;
+ int ret;
+
+ if (!ldap_state->is_nds_ldap) {
+
+ if (!smbldap_has_extension(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ LDAP_EXOP_MODIFY_PASSWD)) {
+ DEBUG(2, ("ldap password change requested, but LDAP "
+ "server does not support it -- ignoring\n"));
+ return NT_STATUS_OK;
+ }
+ }
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_password,
+ pdb_get_plaintext_passwd(newpwd),
+ &converted_size))
+ {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!push_utf8_talloc(talloc_tos(), &utf8_dn, dn, &converted_size)) {
+ TALLOC_FREE(utf8_password);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ((ber = ber_alloc_t(LBER_USE_DER))==NULL) {
+ DEBUG(0,("ber_alloc_t returns NULL\n"));
+ TALLOC_FREE(utf8_password);
+ TALLOC_FREE(utf8_dn);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if ((ber_printf (ber, "{") < 0) ||
+ (ber_printf (ber, "ts", LDAP_TAG_EXOP_MODIFY_PASSWD_ID,
+ utf8_dn) < 0)) {
+ DEBUG(0,("ldapsam_modify_entry: ber_printf returns a "
+ "value <0\n"));
+ ber_free(ber,1);
+ TALLOC_FREE(utf8_dn);
+ TALLOC_FREE(utf8_password);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if ((utf8_password != NULL) && (*utf8_password != '\0')) {
+ ret = ber_printf(ber, "ts}",
+ LDAP_TAG_EXOP_MODIFY_PASSWD_NEW,
+ utf8_password);
+ } else {
+ ret = ber_printf(ber, "}");
+ }
+
+ if (ret < 0) {
+ DEBUG(0,("ldapsam_modify_entry: ber_printf returns a "
+ "value <0\n"));
+ ber_free(ber,1);
+ TALLOC_FREE(utf8_dn);
+ TALLOC_FREE(utf8_password);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if ((rc = ber_flatten (ber, &bv))<0) {
+ DEBUG(0,("ldapsam_modify_entry: ber_flatten returns a value <0\n"));
+ ber_free(ber,1);
+ TALLOC_FREE(utf8_dn);
+ TALLOC_FREE(utf8_password);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ TALLOC_FREE(utf8_dn);
+ TALLOC_FREE(utf8_password);
+ ber_free(ber, 1);
+
+ if (!ldap_state->is_nds_ldap) {
+ rc = smbldap_extended_operation(ldap_state->smbldap_state,
+ LDAP_EXOP_MODIFY_PASSWD,
+ bv, NULL, NULL, &retoid,
+ &retdata);
+ } else {
+ rc = pdb_nds_set_password(ldap_state->smbldap_state, dn,
+ pdb_get_plaintext_passwd(newpwd));
+ }
+ if (rc != LDAP_SUCCESS) {
+ char *ld_error = NULL;
+
+ if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
+ DEBUG(3, ("Could not set userPassword "
+ "attribute due to an objectClass "
+ "violation -- ignoring\n"));
+ ber_bvfree(bv);
+ return NT_STATUS_OK;
+ }
+
+ ldap_get_option(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ LDAP_OPT_ERROR_STRING,
+ &ld_error);
+ DEBUG(0,("ldapsam_modify_entry: LDAP Password could not be changed for user %s: %s\n\t%s\n",
+ pdb_get_username(newpwd), ldap_err2string(rc), ld_error?ld_error:"unknown"));
+ SAFE_FREE(ld_error);
+ ber_bvfree(bv);
+#if defined(LDAP_CONSTRAINT_VIOLATION)
+ if (rc == LDAP_CONSTRAINT_VIOLATION)
+ return NT_STATUS_PASSWORD_RESTRICTION;
+#endif
+ return NT_STATUS_UNSUCCESSFUL;
+ } else {
+ DEBUG(3,("ldapsam_modify_entry: LDAP Password changed for user %s\n",pdb_get_username(newpwd)));
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("ldapsam_modify_entry: LDAP Password changed to %s\n",pdb_get_plaintext_passwd(newpwd)));
+#endif
+ if (retdata)
+ ber_bvfree(retdata);
+ if (retoid)
+ ldap_memfree(retoid);
+ }
+ ber_bvfree(bv);
+ }
+
+ if (!mods) {
+ DEBUG(5,("ldapsam_modify_entry: mods is empty: nothing to modify\n"));
+ /* may be password change below however */
+ } else {
+ switch(ldap_op) {
+ case LDAP_MOD_ADD:
+ if (ldap_state->is_nds_ldap) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ "objectclass",
+ "inetOrgPerson");
+ } else {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ "objectclass",
+ LDAP_OBJ_ACCOUNT);
+ }
+ rc = smbldap_add(ldap_state->smbldap_state,
+ dn, mods);
+ break;
+ case LDAP_MOD_REPLACE:
+ rc = smbldap_modify(ldap_state->smbldap_state,
+ dn ,mods);
+ break;
+ default:
+ DEBUG(0,("ldapsam_modify_entry: Wrong LDAP operation type: %d!\n",
+ ldap_op));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (rc!=LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Delete entry from LDAP for username.
+*********************************************************************/
+
+static NTSTATUS ldapsam_delete_sam_account(struct pdb_methods *my_methods,
+ struct samu * sam_acct)
+{
+ struct ldapsam_privates *priv =
+ (struct ldapsam_privates *)my_methods->private_data;
+ const char *sname;
+ int rc;
+ LDAPMessage *msg, *entry;
+ NTSTATUS result = NT_STATUS_NO_MEMORY;
+ const char **attr_list;
+ TALLOC_CTX *mem_ctx;
+
+ if (!sam_acct) {
+ DEBUG(0, ("ldapsam_delete_sam_account: sam_acct was NULL!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sname = pdb_get_username(sam_acct);
+
+ DEBUG(3, ("ldapsam_delete_sam_account: Deleting user %s from "
+ "LDAP.\n", sname));
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ goto done;
+ }
+
+ attr_list = get_userattr_delete_list(mem_ctx, priv->schema_ver );
+ if (attr_list == NULL) {
+ goto done;
+ }
+
+ rc = ldapsam_search_suffix_by_name(priv, sname, &msg, attr_list);
+
+ if ((rc != LDAP_SUCCESS) ||
+ (ldap_count_entries(priv2ld(priv), msg) != 1) ||
+ ((entry = ldap_first_entry(priv2ld(priv), msg)) == NULL)) {
+ DEBUG(5, ("Could not find user %s\n", sname));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ rc = ldapsam_delete_entry(
+ priv, mem_ctx, entry,
+ priv->schema_ver == SCHEMAVER_SAMBASAMACCOUNT ?
+ LDAP_OBJ_SAMBASAMACCOUNT : 0,
+ attr_list);
+
+ result = (rc == LDAP_SUCCESS) ?
+ NT_STATUS_OK : NT_STATUS_ACCESS_DENIED;
+
+ done:
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+/**********************************************************************
+ Update struct samu.
+*********************************************************************/
+
+static NTSTATUS ldapsam_update_sam_account(struct pdb_methods *my_methods, struct samu * newpwd)
+{
+ NTSTATUS ret;
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ int rc = 0;
+ char *dn;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ const char **attr_list;
+
+ result = (LDAPMessage *)pdb_get_backend_private_data(newpwd, my_methods);
+ if (!result) {
+ attr_list = get_userattr_list(NULL, ldap_state->schema_ver);
+ if (pdb_get_username(newpwd) == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ rc = ldapsam_search_suffix_by_name(ldap_state, pdb_get_username(newpwd), &result, attr_list );
+ TALLOC_FREE( attr_list );
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ pdb_set_backend_private_data(newpwd, result, NULL,
+ my_methods, PDB_CHANGED);
+ smbldap_talloc_autofree_ldapmsg(newpwd, result);
+ }
+
+ if (ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ result) == 0) {
+ DEBUG(0, ("ldapsam_update_sam_account: No user to modify!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ entry = ldap_first_entry(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+ dn = smbldap_talloc_dn(talloc_tos(),
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry);
+ if (!dn) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(4, ("ldapsam_update_sam_account: user %s to be modified has dn: %s\n", pdb_get_username(newpwd), dn));
+
+ if (!init_ldap_from_sam(ldap_state, entry, &mods, newpwd,
+ pdb_element_is_changed)) {
+ DEBUG(0, ("ldapsam_update_sam_account: init_ldap_from_sam failed!\n"));
+ TALLOC_FREE(dn);
+ if (mods != NULL)
+ ldap_mods_free(mods,True);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if ((lp_ldap_passwd_sync() != LDAP_PASSWD_SYNC_ONLY)
+ && (mods == NULL)) {
+ DEBUG(4,("ldapsam_update_sam_account: mods is empty: nothing to update for user: %s\n",
+ pdb_get_username(newpwd)));
+ TALLOC_FREE(dn);
+ return NT_STATUS_OK;
+ }
+
+ ret = ldapsam_modify_entry(my_methods,newpwd,dn,mods,LDAP_MOD_REPLACE, pdb_element_is_changed);
+
+ if (mods != NULL) {
+ ldap_mods_free(mods,True);
+ }
+
+ TALLOC_FREE(dn);
+
+ /*
+ * We need to set the backend private data to NULL here. For example
+ * setuserinfo level 25 does a pdb_update_sam_account twice on the
+ * same one, and with the explicit delete / add logic for attribute
+ * values the second time we would use the wrong "old" value which
+ * does not exist in LDAP anymore. Thus the LDAP server would refuse
+ * the update.
+ * The existing LDAPMessage is still being auto-freed by the
+ * destructor.
+ */
+ pdb_set_backend_private_data(newpwd, NULL, NULL, my_methods,
+ PDB_CHANGED);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+
+ DEBUG(2, ("ldapsam_update_sam_account: successfully modified uid = %s in the LDAP database\n",
+ pdb_get_username(newpwd)));
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ Renames a struct samu
+ - The "rename user script" has full responsibility for changing everything
+***************************************************************************/
+
+static NTSTATUS ldapsam_del_groupmem(struct pdb_methods *my_methods,
+ TALLOC_CTX *tmp_ctx,
+ uint32_t group_rid,
+ uint32_t member_rid);
+
+static NTSTATUS ldapsam_enum_group_memberships(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user,
+ struct dom_sid **pp_sids,
+ gid_t **pp_gids,
+ uint32_t *p_num_groups);
+
+static NTSTATUS ldapsam_rename_sam_account(struct pdb_methods *my_methods,
+ struct samu *old_acct,
+ const char *newname)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *oldname;
+ int rc;
+ char *rename_script = NULL;
+ fstring oldname_lower, newname_lower;
+
+ if (!old_acct) {
+ DEBUG(0, ("ldapsam_rename_sam_account: old_acct was NULL!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (!newname) {
+ DEBUG(0, ("ldapsam_rename_sam_account: newname was NULL!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ oldname = pdb_get_username(old_acct);
+
+ /* rename the posix user */
+ rename_script = lp_rename_user_script(talloc_tos(), lp_sub);
+ if (rename_script == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!(*rename_script)) {
+ TALLOC_FREE(rename_script);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ DEBUG (3, ("ldapsam_rename_sam_account: Renaming user %s to %s.\n",
+ oldname, newname));
+
+ /* We have to allow the account name to end with a '$'.
+ Also, follow the semantics in _samr_create_user() and lower case the
+ posix name but preserve the case in passdb */
+
+ fstrcpy( oldname_lower, oldname );
+ if (!strlower_m( oldname_lower )) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ fstrcpy( newname_lower, newname );
+ if (!strlower_m( newname_lower )) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ rename_script = realloc_string_sub2(rename_script,
+ "%unew",
+ newname_lower,
+ true,
+ true);
+ if (!rename_script) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ rename_script = realloc_string_sub2(rename_script,
+ "%uold",
+ oldname_lower,
+ true,
+ true);
+ rc = smbrun(rename_script, NULL, NULL);
+
+ DEBUG(rc ? 0 : 3,("Running the command `%s' gave %d\n",
+ rename_script, rc));
+
+ TALLOC_FREE(rename_script);
+
+ if (rc == 0) {
+ smb_nscd_flush_user_cache();
+ }
+
+ if (rc)
+ return NT_STATUS_UNSUCCESSFUL;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Add struct samu to LDAP.
+*********************************************************************/
+
+static NTSTATUS ldapsam_add_sam_account(struct pdb_methods *my_methods, struct samu * newpwd)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ int rc;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ int ldap_op = LDAP_MOD_REPLACE;
+ uint32_t num_result;
+ const char **attr_list;
+ char *escape_user = NULL;
+ const char *username = pdb_get_username(newpwd);
+ const struct dom_sid *sid = pdb_get_user_sid(newpwd);
+ char *filter = NULL;
+ char *dn = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ TALLOC_CTX *ctx = talloc_init("ldapsam_add_sam_account");
+
+ if (!ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!username || !*username) {
+ DEBUG(0, ("ldapsam_add_sam_account: Cannot add user without a username!\n"));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fn_exit;
+ }
+
+ /* free this list after the second search or in case we exit on failure */
+ attr_list = get_userattr_list(ctx, ldap_state->schema_ver);
+
+ rc = ldapsam_search_suffix_by_name (ldap_state, username, &result, attr_list);
+
+ if (rc != LDAP_SUCCESS) {
+ goto fn_exit;
+ }
+
+ if (ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ result) != 0) {
+ DEBUG(0,("ldapsam_add_sam_account: User '%s' already in the base, with samba attributes\n",
+ username));
+ goto fn_exit;
+ }
+ ldap_msgfree(result);
+ result = NULL;
+
+ if (pdb_element_is_set_or_changed(newpwd, PDB_USERSID)) {
+ rc = ldapsam_get_ldap_user_by_sid(ldap_state,
+ sid, &result);
+ if (rc == LDAP_SUCCESS) {
+ if (ldap_count_entries(
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ result) != 0) {
+ struct dom_sid_buf buf;
+ DEBUG(0,("ldapsam_add_sam_account: SID '%s' "
+ "already in the base, with samba "
+ "attributes\n",
+ dom_sid_str_buf(sid, &buf)));
+ goto fn_exit;
+ }
+ ldap_msgfree(result);
+ result = NULL;
+ }
+ }
+
+ /* does the entry already exist but without a samba attributes?
+ we need to return the samba attributes here */
+
+ escape_user = escape_ldap_string(talloc_tos(), username);
+ filter = talloc_strdup(attr_list, "(uid=%u)");
+ if (!filter) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fn_exit;
+ }
+ filter = talloc_all_string_sub(attr_list, filter, "%u", escape_user);
+ TALLOC_FREE(escape_user);
+ if (!filter) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fn_exit;
+ }
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state,
+ filter, attr_list, &result);
+ if ( rc != LDAP_SUCCESS ) {
+ goto fn_exit;
+ }
+
+ num_result = ldap_count_entries(
+ smbldap_get_ldap(ldap_state->smbldap_state), result);
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_add_sam_account: More than one user with that uid exists: bailing out!\n"));
+ goto fn_exit;
+ }
+
+ /* Check if we need to update an existing entry */
+ if (num_result == 1) {
+ DEBUG(3,("ldapsam_add_sam_account: User exists without samba attributes: adding them\n"));
+ ldap_op = LDAP_MOD_REPLACE;
+ entry = ldap_first_entry(
+ smbldap_get_ldap(ldap_state->smbldap_state), result);
+ dn = smbldap_talloc_dn(
+ ctx, smbldap_get_ldap(ldap_state->smbldap_state),
+ entry);
+ if (!dn) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fn_exit;
+ }
+
+ } else if (ldap_state->schema_ver == SCHEMAVER_SAMBASAMACCOUNT) {
+
+ struct dom_sid_buf buf;
+
+ /* There might be a SID for this account already - say an idmap entry */
+
+ filter = talloc_asprintf(ctx,
+ "(&(%s=%s)(|(objectClass=%s)(objectClass=%s)))",
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_SID),
+ dom_sid_str_buf(sid, &buf),
+ LDAP_OBJ_IDMAP_ENTRY,
+ LDAP_OBJ_SID_ENTRY);
+ if (!filter) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fn_exit;
+ }
+
+ /* free old result before doing a new search */
+ if (result != NULL) {
+ ldap_msgfree(result);
+ result = NULL;
+ }
+ rc = smbldap_search_suffix(ldap_state->smbldap_state,
+ filter, attr_list, &result);
+
+ if ( rc != LDAP_SUCCESS ) {
+ goto fn_exit;
+ }
+
+ num_result = ldap_count_entries(
+ smbldap_get_ldap(ldap_state->smbldap_state), result);
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_add_sam_account: More than one user with specified Sid exists: bailing out!\n"));
+ goto fn_exit;
+ }
+
+ /* Check if we need to update an existing entry */
+ if (num_result == 1) {
+
+ DEBUG(3,("ldapsam_add_sam_account: User exists without samba attributes: adding them\n"));
+ ldap_op = LDAP_MOD_REPLACE;
+ entry = ldap_first_entry (
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+ dn = smbldap_talloc_dn (
+ ctx,
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry);
+ if (!dn) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fn_exit;
+ }
+ }
+ }
+
+ if (num_result == 0) {
+ char *escape_username;
+ /* Check if we need to add an entry */
+ DEBUG(3,("ldapsam_add_sam_account: Adding new user\n"));
+ ldap_op = LDAP_MOD_ADD;
+
+ escape_username = escape_rdn_val_string_alloc(username);
+ if (!escape_username) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fn_exit;
+ }
+
+ if (username[strlen(username)-1] == '$') {
+ dn = talloc_asprintf(ctx,
+ "uid=%s,%s",
+ escape_username,
+ lp_ldap_machine_suffix(talloc_tos()));
+ } else {
+ dn = talloc_asprintf(ctx,
+ "uid=%s,%s",
+ escape_username,
+ lp_ldap_user_suffix(talloc_tos()));
+ }
+
+ SAFE_FREE(escape_username);
+ if (!dn) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fn_exit;
+ }
+ }
+
+ if (!init_ldap_from_sam(ldap_state, entry, &mods, newpwd,
+ pdb_element_is_set_or_changed)) {
+ DEBUG(0, ("ldapsam_add_sam_account: init_ldap_from_sam failed!\n"));
+ if (mods != NULL) {
+ ldap_mods_free(mods, true);
+ }
+ goto fn_exit;
+ }
+
+ if (mods == NULL) {
+ DEBUG(0,("ldapsam_add_sam_account: mods is empty: nothing to add for user: %s\n",pdb_get_username(newpwd)));
+ goto fn_exit;
+ }
+ switch ( ldap_state->schema_ver ) {
+ case SCHEMAVER_SAMBASAMACCOUNT:
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectclass", LDAP_OBJ_SAMBASAMACCOUNT);
+ break;
+ default:
+ DEBUG(0,("ldapsam_add_sam_account: invalid schema version specified\n"));
+ break;
+ }
+
+ status = ldapsam_modify_entry(my_methods,newpwd,dn,mods,ldap_op, pdb_element_is_set_or_changed);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("ldapsam_add_sam_account: failed to modify/add user with uid = %s (dn = %s)\n",
+ pdb_get_username(newpwd),dn));
+ ldap_mods_free(mods, true);
+ goto fn_exit;
+ }
+
+ DEBUG(2,("ldapsam_add_sam_account: added: uid == %s in the LDAP database\n", pdb_get_username(newpwd)));
+ ldap_mods_free(mods, true);
+
+ status = NT_STATUS_OK;
+
+ fn_exit:
+
+ TALLOC_FREE(ctx);
+ if (result) {
+ ldap_msgfree(result);
+ }
+ return status;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static int ldapsam_search_one_group (struct ldapsam_privates *ldap_state,
+ const char *filter,
+ LDAPMessage ** result)
+{
+ int scope = LDAP_SCOPE_SUBTREE;
+ int rc;
+ const char **attr_list;
+
+ attr_list = get_attr_list(NULL, groupmap_attr_list);
+ rc = smbldap_search(ldap_state->smbldap_state,
+ lp_ldap_suffix(), scope,
+ filter, attr_list, 0, result);
+ TALLOC_FREE(attr_list);
+
+ return rc;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static bool init_group_from_ldap(struct ldapsam_privates *ldap_state,
+ GROUP_MAP *map, LDAPMessage *entry)
+{
+ char *temp = NULL;
+ TALLOC_CTX *ctx = talloc_init("init_group_from_ldap");
+
+ if (ldap_state == NULL || map == NULL || entry == NULL ||
+ smbldap_get_ldap(ldap_state->smbldap_state) == NULL) {
+ DEBUG(0, ("init_group_from_ldap: NULL parameters found!\n"));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_GIDNUMBER),
+ ctx);
+ if (!temp) {
+ DEBUG(0, ("init_group_from_ldap: Mandatory attribute %s not found\n",
+ get_attr_key2string( groupmap_attr_list, LDAP_ATTR_GIDNUMBER)));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ DEBUG(2, ("init_group_from_ldap: Entry found for group: %s\n", temp));
+
+ map->gid = (gid_t)atol(temp);
+
+ TALLOC_FREE(temp);
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_GROUP_SID),
+ ctx);
+ if (!temp) {
+ DEBUG(0, ("init_group_from_ldap: Mandatory attribute %s not found\n",
+ get_attr_key2string( groupmap_attr_list, LDAP_ATTR_GROUP_SID)));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ if (!string_to_sid(&map->sid, temp)) {
+ DEBUG(1, ("SID string [%s] could not be read as a valid SID\n", temp));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ TALLOC_FREE(temp);
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_GROUP_TYPE),
+ ctx);
+ if (!temp) {
+ DEBUG(0, ("init_group_from_ldap: Mandatory attribute %s not found\n",
+ get_attr_key2string( groupmap_attr_list, LDAP_ATTR_GROUP_TYPE)));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ map->sid_name_use = (enum lsa_SidType)atol(temp);
+
+ if ((map->sid_name_use < SID_NAME_USER) ||
+ (map->sid_name_use > SID_NAME_UNKNOWN)) {
+ DEBUG(0, ("init_group_from_ldap: Unknown Group type: %d\n", map->sid_name_use));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ TALLOC_FREE(temp);
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_DISPLAY_NAME),
+ ctx);
+ if (!temp) {
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_CN),
+ ctx);
+ if (!temp) {
+ DEBUG(0, ("init_group_from_ldap: Attributes cn not found either \
+for gidNumber(%lu)\n",(unsigned long)map->gid));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ }
+ map->nt_name = talloc_strdup(map, temp);
+ if (!map->nt_name) {
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ TALLOC_FREE(temp);
+ temp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_DESC),
+ ctx);
+ if (!temp) {
+ temp = talloc_strdup(ctx, "");
+ if (!temp) {
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ }
+ map->comment = talloc_strdup(map, temp);
+ if (!map->comment) {
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ if (lp_parm_bool(-1, "ldapsam", "trusted", false)) {
+ struct unixid id;
+ id.id = map->gid;
+ id.type = ID_TYPE_GID;
+
+ idmap_cache_set_sid2unixid(&map->sid, &id);
+ }
+
+ TALLOC_FREE(ctx);
+ return true;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_getgroup(struct pdb_methods *methods,
+ const char *filter,
+ GROUP_MAP *map)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int count;
+
+ if (ldapsam_search_one_group(ldap_state, filter, &result)
+ != LDAP_SUCCESS) {
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ count = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (count < 1) {
+ DEBUG(4, ("ldapsam_getgroup: Did not find group, filter was "
+ "%s\n", filter));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ if (count > 1) {
+ DEBUG(1, ("ldapsam_getgroup: Duplicate entries for filter %s: "
+ "count=%d\n", filter, count));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+
+ if (!entry) {
+ ldap_msgfree(result);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (!init_group_from_ldap(ldap_state, map, entry)) {
+ DEBUG(1, ("ldapsam_getgroup: init_group_from_ldap failed for "
+ "group filter %s\n", filter));
+ ldap_msgfree(result);
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ ldap_msgfree(result);
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_getgrsid(struct pdb_methods *methods, GROUP_MAP *map,
+ struct dom_sid sid)
+{
+ char *filter = NULL;
+ NTSTATUS status;
+ struct dom_sid_buf tmp;
+
+ if (asprintf(&filter, "(&(objectClass=%s)(%s=%s))",
+ LDAP_OBJ_GROUPMAP,
+ get_attr_key2string(groupmap_attr_list, LDAP_ATTR_GROUP_SID),
+ dom_sid_str_buf(&sid, &tmp)) < 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = ldapsam_getgroup(methods, filter, map);
+ SAFE_FREE(filter);
+ return status;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_getgrgid(struct pdb_methods *methods, GROUP_MAP *map,
+ gid_t gid)
+{
+ char *filter = NULL;
+ NTSTATUS status;
+
+ if (asprintf(&filter, "(&(objectClass=%s)(%s=%lu))",
+ LDAP_OBJ_GROUPMAP,
+ get_attr_key2string(groupmap_attr_list, LDAP_ATTR_GIDNUMBER),
+ (unsigned long)gid) < 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = ldapsam_getgroup(methods, filter, map);
+ SAFE_FREE(filter);
+ return status;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_getgrnam(struct pdb_methods *methods, GROUP_MAP *map,
+ const char *name)
+{
+ char *filter = NULL;
+ char *escape_name = escape_ldap_string(talloc_tos(), name);
+ NTSTATUS status;
+
+ if (!escape_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (asprintf(&filter, "(&(objectClass=%s)(|(%s=%s)(%s=%s)))",
+ LDAP_OBJ_GROUPMAP,
+ get_attr_key2string(groupmap_attr_list, LDAP_ATTR_DISPLAY_NAME), escape_name,
+ get_attr_key2string(groupmap_attr_list, LDAP_ATTR_CN),
+ escape_name) < 0) {
+ TALLOC_FREE(escape_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ TALLOC_FREE(escape_name);
+ status = ldapsam_getgroup(methods, filter, map);
+ SAFE_FREE(filter);
+ return status;
+}
+
+static bool ldapsam_extract_rid_from_entry(LDAP *ldap_struct,
+ LDAPMessage *entry,
+ const struct dom_sid *domain_sid,
+ uint32_t *rid)
+{
+ fstring str;
+ struct dom_sid sid;
+
+ if (!smbldap_get_single_attribute(ldap_struct, entry, "sambaSID",
+ str, sizeof(str)-1)) {
+ DEBUG(10, ("Could not find sambaSID attribute\n"));
+ return False;
+ }
+
+ if (!string_to_sid(&sid, str)) {
+ DEBUG(10, ("Could not convert string %s to sid\n", str));
+ return False;
+ }
+
+ if (dom_sid_compare_domain(&sid, domain_sid) != 0) {
+ struct dom_sid_buf buf;
+ DEBUG(10, ("SID %s is not in expected domain %s\n",
+ str,
+ dom_sid_str_buf(domain_sid, &buf)));
+ return False;
+ }
+
+ if (!sid_peek_rid(&sid, rid)) {
+ DEBUG(10, ("Could not peek into RID\n"));
+ return False;
+ }
+
+ return True;
+}
+
+static NTSTATUS ldapsam_enum_group_members(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group,
+ uint32_t **pp_member_rids,
+ size_t *p_num_members)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ struct smbldap_state *conn = ldap_state->smbldap_state;
+ const char *id_attrs[] = { "memberUid", "gidNumber", NULL };
+ const char *sid_attrs[] = { "sambaSID", NULL };
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry;
+ char *filter;
+ char **values = NULL;
+ char **memberuid;
+ char *gidstr;
+ int rc, count;
+ struct dom_sid_buf buf;
+
+ *pp_member_rids = NULL;
+ *p_num_members = 0;
+
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass="LDAP_OBJ_POSIXGROUP")"
+ "(objectClass="LDAP_OBJ_GROUPMAP")"
+ "(sambaSID=%s))",
+ dom_sid_str_buf(group, &buf));
+ if (filter == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = smbldap_search(conn, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, id_attrs, 0,
+ &result);
+
+ if (rc != LDAP_SUCCESS)
+ goto done;
+
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ count = ldap_count_entries(smbldap_get_ldap(conn), result);
+
+ if (count > 1) {
+ DEBUG(1, ("Found more than one groupmap entry for %s\n",
+ dom_sid_str_buf(group, &buf)));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ if (count == 0) {
+ ret = NT_STATUS_NO_SUCH_GROUP;
+ goto done;
+ }
+
+ entry = ldap_first_entry(smbldap_get_ldap(conn), result);
+ if (entry == NULL)
+ goto done;
+
+ gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", mem_ctx);
+ if (!gidstr) {
+ DEBUG (0, ("ldapsam_enum_group_members: Unable to find the group's gid!\n"));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ values = ldap_get_values(smbldap_get_ldap(conn), entry, "memberUid");
+
+ if ((values != NULL) && (values[0] != NULL)) {
+
+ filter = talloc_strdup(mem_ctx, "(&(objectClass="LDAP_OBJ_SAMBASAMACCOUNT")(|");
+
+ for (memberuid = values; *memberuid != NULL; memberuid += 1) {
+ char *escape_memberuid;
+
+ escape_memberuid = escape_ldap_string(talloc_tos(),
+ *memberuid);
+ if (escape_memberuid == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ filter = talloc_asprintf_append_buffer(filter, "(uid=%s)", escape_memberuid);
+ TALLOC_FREE(escape_memberuid);
+ if (filter == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ filter = talloc_asprintf_append_buffer(filter, "))");
+ if (filter == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = smbldap_search(conn, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, sid_attrs, 0,
+ &result);
+
+ if (rc != LDAP_SUCCESS)
+ goto done;
+
+ count = ldap_count_entries(smbldap_get_ldap(conn), result);
+ DEBUG(10,("ldapsam_enum_group_members: found %d accounts\n", count));
+
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ for (entry = ldap_first_entry(smbldap_get_ldap(conn), result);
+ entry != NULL;
+ entry = ldap_next_entry(smbldap_get_ldap(conn), entry))
+ {
+ char *sidstr;
+ struct dom_sid sid;
+ uint32_t rid;
+
+ sidstr = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(conn), entry, "sambaSID",
+ mem_ctx);
+ if (!sidstr) {
+ DEBUG(0, ("Severe DB error, %s can't miss the sambaSID"
+ "attribute\n", LDAP_OBJ_SAMBASAMACCOUNT));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ if (!string_to_sid(&sid, sidstr))
+ goto done;
+
+ if (!sid_check_is_in_our_sam(&sid)) {
+ DEBUG(0, ("Inconsistent SAM -- group member uid not "
+ "in our domain\n"));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ sid_peek_rid(&sid, &rid);
+
+ if (!add_rid_to_array_unique(mem_ctx, rid, pp_member_rids,
+ p_num_members)) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+ }
+
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass=%s)"
+ "(gidNumber=%s))",
+ LDAP_OBJ_SAMBASAMACCOUNT,
+ gidstr);
+
+ rc = smbldap_search(conn, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, sid_attrs, 0,
+ &result);
+
+ if (rc != LDAP_SUCCESS)
+ goto done;
+
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ for (entry = ldap_first_entry(smbldap_get_ldap(conn), result);
+ entry != NULL;
+ entry = ldap_next_entry(smbldap_get_ldap(conn), entry))
+ {
+ uint32_t rid;
+
+ if (!ldapsam_extract_rid_from_entry(smbldap_get_ldap(conn),
+ entry,
+ get_global_sam_sid(),
+ &rid)) {
+ DEBUG(0, ("Severe DB error, %s can't miss the samba SID"
+ "attribute\n", LDAP_OBJ_SAMBASAMACCOUNT));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ if (!add_rid_to_array_unique(mem_ctx, rid, pp_member_rids,
+ p_num_members)) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ ret = NT_STATUS_OK;
+
+ done:
+
+ if (values)
+ ldap_value_free(values);
+
+ return ret;
+}
+
+static NTSTATUS ldapsam_enum_group_memberships(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user,
+ struct dom_sid **pp_sids,
+ gid_t **pp_gids,
+ uint32_t *p_num_groups)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ struct smbldap_state *conn = ldap_state->smbldap_state;
+ char *filter;
+ const char *attrs[] = { "gidNumber", "sambaSID", NULL };
+ char *escape_name;
+ int rc, count;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ uint32_t num_sids;
+ uint32_t num_gids;
+ char *gidstr;
+ gid_t primary_gid = -1;
+ int error = 0;
+
+ *pp_sids = NULL;
+ num_sids = 0;
+
+ if (pdb_get_username(user) == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ escape_name = escape_ldap_string(talloc_tos(), pdb_get_username(user));
+ if (escape_name == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ if (user->unix_pw) {
+ primary_gid = user->unix_pw->pw_gid;
+ } else {
+ /* retrieve the users primary gid */
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass="LDAP_OBJ_SAMBASAMACCOUNT")(uid=%s))",
+ escape_name);
+ if (filter == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = smbldap_search(conn, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, attrs, 0, &result);
+
+ if (rc != LDAP_SUCCESS)
+ goto done;
+
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ count = ldap_count_entries(priv2ld(ldap_state), result);
+
+ switch (count) {
+ case 0:
+ DEBUG(1, ("User account [%s] not found!\n", pdb_get_username(user)));
+ ret = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ case 1:
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+
+ gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", mem_ctx);
+ if (!gidstr) {
+ DEBUG (1, ("Unable to find the member's gid!\n"));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+ primary_gid = smb_strtoul(gidstr,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_ERR("Failed to convert GID\n");
+ goto done;
+ }
+ break;
+ default:
+ DEBUG(1, ("found more than one account with the same user name ?!\n"));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+ }
+
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass="LDAP_OBJ_POSIXGROUP")(|(memberUid=%s)(gidNumber=%u)))",
+ escape_name, (unsigned int)primary_gid);
+ if (filter == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = smbldap_search(conn, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, attrs, 0, &result);
+
+ if (rc != LDAP_SUCCESS)
+ goto done;
+
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ num_gids = 0;
+ *pp_gids = NULL;
+
+ num_sids = 0;
+ *pp_sids = NULL;
+
+ /* We need to add the primary group as the first gid/sid */
+
+ if (!add_gid_to_array_unique(mem_ctx, primary_gid, pp_gids, &num_gids)) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* This sid will be replaced later */
+
+ ret = add_sid_to_array_unique(mem_ctx, &global_sid_NULL, pp_sids,
+ &num_sids);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto done;
+ }
+
+ for (entry = ldap_first_entry(smbldap_get_ldap(conn), result);
+ entry != NULL;
+ entry = ldap_next_entry(smbldap_get_ldap(conn), entry))
+ {
+ fstring str;
+ struct dom_sid sid;
+ gid_t gid;
+
+ if (!smbldap_get_single_attribute(smbldap_get_ldap(conn),
+ entry, "sambaSID",
+ str, sizeof(str)-1))
+ continue;
+
+ if (!string_to_sid(&sid, str))
+ goto done;
+
+ if (!smbldap_get_single_attribute(smbldap_get_ldap(conn),
+ entry, "gidNumber",
+ str, sizeof(str)-1))
+ continue;
+
+ gid = smb_strtoul(str, NULL, 10, &error, SMB_STR_FULL_STR_CONV);
+
+ if (error != 0) {
+ goto done;
+ }
+
+ if (gid == primary_gid) {
+ sid_copy(&(*pp_sids)[0], &sid);
+ } else {
+ if (!add_gid_to_array_unique(mem_ctx, gid, pp_gids,
+ &num_gids)) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ ret = add_sid_to_array_unique(mem_ctx, &sid, pp_sids,
+ &num_sids);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto done;
+ }
+ }
+ }
+
+ if (dom_sid_compare(&global_sid_NULL, &(*pp_sids)[0]) == 0) {
+ DEBUG(3, ("primary group of [%s] not found\n",
+ pdb_get_username(user)));
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ *p_num_groups = num_sids;
+
+ ret = NT_STATUS_OK;
+
+ done:
+
+ TALLOC_FREE(escape_name);
+ return ret;
+}
+
+/**********************************************************************
+ * Augment a posixGroup object with a sambaGroupMapping domgroup
+ *********************************************************************/
+
+static NTSTATUS ldapsam_map_posixgroup(TALLOC_CTX *mem_ctx,
+ struct ldapsam_privates *ldap_state,
+ GROUP_MAP *map)
+{
+ const char *filter, *dn;
+ LDAPMessage *msg, *entry;
+ LDAPMod **mods;
+ struct dom_sid_buf buf;
+ int rc;
+
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectClass="LDAP_OBJ_POSIXGROUP")(gidNumber=%u))",
+ (unsigned int)map->gid);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter,
+ get_attr_list(mem_ctx, groupmap_attr_list),
+ &msg);
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, msg);
+
+ if ((rc != LDAP_SUCCESS) ||
+ (ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ msg) != 1) ||
+ ((entry = ldap_first_entry(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ msg)) == NULL)) {
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ dn = smbldap_talloc_dn(mem_ctx,
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry);
+ if (dn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ mods = NULL;
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass",
+ LDAP_OBJ_GROUPMAP);
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), entry,
+ &mods, "sambaSid",
+ dom_sid_str_buf(&map->sid, &buf));
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), entry,
+ &mods, "sambaGroupType",
+ talloc_asprintf(mem_ctx, "%d", map->sid_name_use));
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), entry,
+ &mods, "displayName",
+ map->nt_name);
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), entry,
+ &mods, "description",
+ map->comment);
+ smbldap_talloc_autofree_ldapmod(mem_ctx, mods);
+
+ rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_add_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *msg = NULL;
+ LDAPMod **mods = NULL;
+ const char *attrs[] = { NULL };
+ char *filter;
+
+ char *dn;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS result;
+
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ struct unixid id;
+
+ int rc;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ filter = talloc_asprintf(mem_ctx, "(sambaSid=%s)",
+ dom_sid_str_buf(&map->sid, &buf));
+ if (filter == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = smbldap_search(ldap_state->smbldap_state, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, attrs, True, &msg);
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, msg);
+
+ if ((rc == LDAP_SUCCESS) &&
+ (ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ msg) > 0)) {
+
+ DEBUG(3, ("SID %s already present in LDAP, refusing to add "
+ "group mapping entry\n",
+ dom_sid_str_buf(&map->sid, &buf)));
+ result = NT_STATUS_GROUP_EXISTS;
+ goto done;
+ }
+
+ switch (map->sid_name_use) {
+
+ case SID_NAME_DOM_GRP:
+ /* To map a domain group we need to have a posix group
+ to attach to. */
+ result = ldapsam_map_posixgroup(mem_ctx, ldap_state, map);
+ goto done;
+ break;
+
+ case SID_NAME_ALIAS:
+ if (!sid_check_is_in_our_sam(&map->sid)
+ && !sid_check_is_in_builtin(&map->sid) )
+ {
+ DEBUG(3, ("Refusing to map sid %s as an alias, not in our domain\n",
+ dom_sid_str_buf(&map->sid, &buf)));
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+ break;
+
+ default:
+ DEBUG(3, ("Got invalid use '%s' for mapping\n",
+ sid_type_lookup(map->sid_name_use)));
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* Domain groups have been mapped in a separate routine, we have to
+ * create an alias now */
+
+ if (map->gid == -1) {
+ DEBUG(10, ("Refusing to map gid==-1\n"));
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ id.id = map->gid;
+ id.type = ID_TYPE_GID;
+
+ if (pdb_id_to_sid(&id, &sid)) {
+ DEBUG(3, ("Gid %u is already mapped to SID %s, refusing to "
+ "add\n",
+ (unsigned int)map->gid,
+ dom_sid_str_buf(&sid, &buf)));
+ result = NT_STATUS_GROUP_EXISTS;
+ goto done;
+ }
+
+ /* Ok, enough checks done. It's still racy to go ahead now, but that's
+ * the best we can get out of LDAP. */
+
+ dn = talloc_asprintf(mem_ctx, "sambaSid=%s,%s",
+ dom_sid_str_buf(&map->sid, &buf),
+ lp_ldap_group_suffix(talloc_tos()));
+ if (dn == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ mods = NULL;
+
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), NULL,
+ &mods, "objectClass", LDAP_OBJ_SID_ENTRY);
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), NULL,
+ &mods, "objectClass", LDAP_OBJ_GROUPMAP);
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), NULL,
+ &mods, "sambaSid",
+ dom_sid_str_buf(&map->sid, &buf));
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), NULL,
+ &mods, "sambaGroupType",
+ talloc_asprintf(mem_ctx, "%d", map->sid_name_use));
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), NULL,
+ &mods, "displayName",
+ map->nt_name);
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), NULL,
+ &mods, "description",
+ map->comment);
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), NULL,
+ &mods, "gidNumber",
+ talloc_asprintf(mem_ctx, "%u",
+ (unsigned int)map->gid));
+ smbldap_talloc_autofree_ldapmod(mem_ctx, mods);
+
+ rc = smbldap_add(ldap_state->smbldap_state, dn, mods);
+
+ result = (rc == LDAP_SUCCESS) ?
+ NT_STATUS_OK : NT_STATUS_ACCESS_DENIED;
+
+ done:
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+/**********************************************************************
+ * Update a group mapping entry. We're quite strict about what can be changed:
+ * Only the description and displayname may be changed. It simply does not
+ * make any sense to change the SID, gid or the type in a mapping.
+ *********************************************************************/
+
+static NTSTATUS ldapsam_update_group_mapping_entry(struct pdb_methods *methods,
+ GROUP_MAP *map)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ int rc;
+ const char *filter, *dn;
+ LDAPMessage *msg = NULL;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS result;
+ struct dom_sid_buf buf;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Make 100% sure that sid, gid and type are not changed by looking up
+ * exactly the values we're given in LDAP. */
+
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass="LDAP_OBJ_GROUPMAP")"
+ "(sambaSid=%s)(gidNumber=%u)"
+ "(sambaGroupType=%d))",
+ dom_sid_str_buf(&map->sid, &buf),
+ (unsigned int)map->gid, map->sid_name_use);
+ if (filter == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter,
+ get_attr_list(mem_ctx, groupmap_attr_list),
+ &msg);
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, msg);
+
+ if ((rc != LDAP_SUCCESS) ||
+ (ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ msg) != 1) ||
+ ((entry = ldap_first_entry(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ msg)) == NULL)) {
+ result = NT_STATUS_NO_SUCH_GROUP;
+ goto done;
+ }
+
+ dn = smbldap_talloc_dn(
+ mem_ctx, smbldap_get_ldap(ldap_state->smbldap_state), entry);
+
+ if (dn == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ mods = NULL;
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), entry,
+ &mods, "displayName", map->nt_name);
+ smbldap_make_mod(smbldap_get_ldap(ldap_state->smbldap_state), entry,
+ &mods, "description", map->comment);
+ smbldap_talloc_autofree_ldapmod(mem_ctx, mods);
+
+ if (mods == NULL) {
+ DEBUG(4, ("ldapsam_update_group_mapping_entry: mods is empty: "
+ "nothing to do\n"));
+ result = NT_STATUS_OK;
+ goto done;
+ }
+
+ rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);
+
+ if (rc != LDAP_SUCCESS) {
+ result = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ DEBUG(2, ("ldapsam_update_group_mapping_entry: successfully modified "
+ "group %lu in LDAP\n", (unsigned long)map->gid));
+
+ result = NT_STATUS_OK;
+
+ done:
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_delete_group_mapping_entry(struct pdb_methods *methods,
+ struct dom_sid sid)
+{
+ struct ldapsam_privates *priv =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *msg, *entry;
+ int rc;
+ NTSTATUS result;
+ TALLOC_CTX *mem_ctx;
+ char *filter;
+ struct dom_sid_buf buf;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ filter = talloc_asprintf(mem_ctx, "(&(objectClass="LDAP_OBJ_GROUPMAP")("LDAP_ATTRIBUTE_SID"=%s))",
+ dom_sid_str_buf(&sid, &buf));
+ if (filter == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ rc = smbldap_search_suffix(priv->smbldap_state, filter,
+ get_attr_list(mem_ctx, groupmap_attr_list),
+ &msg);
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, msg);
+
+ if ((rc != LDAP_SUCCESS) ||
+ (ldap_count_entries(priv2ld(priv), msg) != 1) ||
+ ((entry = ldap_first_entry(priv2ld(priv), msg)) == NULL)) {
+ result = NT_STATUS_NO_SUCH_GROUP;
+ goto done;
+ }
+
+ rc = ldapsam_delete_entry(priv, mem_ctx, entry, LDAP_OBJ_GROUPMAP,
+ get_attr_list(mem_ctx,
+ groupmap_attr_list_to_delete));
+
+ if ((rc == LDAP_NAMING_VIOLATION) ||
+ (rc == LDAP_NOT_ALLOWED_ON_RDN) ||
+ (rc == LDAP_OBJECT_CLASS_VIOLATION)) {
+ const char *attrs[] = { "sambaGroupType", "description",
+ "displayName", "sambaSIDList",
+ NULL };
+
+ /* Second try. Don't delete the sambaSID attribute, this is
+ for "old" entries that are tacked on a winbind
+ sambaIdmapEntry. */
+
+ rc = ldapsam_delete_entry(priv, mem_ctx, entry,
+ LDAP_OBJ_GROUPMAP, attrs);
+ }
+
+ if ((rc == LDAP_NAMING_VIOLATION) ||
+ (rc == LDAP_NOT_ALLOWED_ON_RDN) ||
+ (rc == LDAP_OBJECT_CLASS_VIOLATION)) {
+ const char *attrs[] = { "sambaGroupType", "description",
+ "displayName", "sambaSIDList",
+ "gidNumber", NULL };
+
+ /* Third try. This is a post-3.0.21 alias (containing only
+ * sambaSidEntry and sambaGroupMapping classes), we also have
+ * to delete the gidNumber attribute, only the sambaSidEntry
+ * remains */
+
+ rc = ldapsam_delete_entry(priv, mem_ctx, entry,
+ LDAP_OBJ_GROUPMAP, attrs);
+ }
+
+ result = (rc == LDAP_SUCCESS) ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+
+ done:
+ TALLOC_FREE(mem_ctx);
+ return result;
+ }
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_setsamgrent(struct pdb_methods *my_methods,
+ bool update)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)my_methods->private_data;
+ const char *filter = NULL;
+ int rc;
+ const char **attr_list;
+
+ filter = "(objectclass="LDAP_OBJ_GROUPMAP")";
+ attr_list = get_attr_list( NULL, groupmap_attr_list );
+ rc = smbldap_search(ldap_state->smbldap_state, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter,
+ attr_list, 0, &ldap_state->result);
+ TALLOC_FREE(attr_list);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0, ("ldapsam_setsamgrent: LDAP search failed: %s\n",
+ ldap_err2string(rc)));
+ DEBUG(3, ("ldapsam_setsamgrent: Query was: %s, %s\n",
+ lp_ldap_suffix(), filter));
+ ldap_msgfree(ldap_state->result);
+ ldap_state->result = NULL;
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(2, ("ldapsam_setsamgrent: %d entries in the base!\n",
+ ldap_count_entries(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ ldap_state->result)));
+
+ ldap_state->entry =
+ ldap_first_entry(smbldap_get_ldap(ldap_state->smbldap_state),
+ ldap_state->result);
+ ldap_state->index = 0;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static void ldapsam_endsamgrent(struct pdb_methods *my_methods)
+{
+ ldapsam_endsampwent(my_methods);
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_getsamgrent(struct pdb_methods *my_methods,
+ GROUP_MAP *map)
+{
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)my_methods->private_data;
+ bool bret = False;
+
+ while (!bret) {
+ if (!ldap_state->entry)
+ return ret;
+
+ ldap_state->index++;
+ bret = init_group_from_ldap(ldap_state, map,
+ ldap_state->entry);
+
+ ldap_state->entry = ldap_next_entry(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ ldap_state->entry);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS ldapsam_enum_group_mapping(struct pdb_methods *methods,
+ const struct dom_sid *domsid, enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries,
+ bool unix_only)
+{
+ GROUP_MAP *map = NULL;
+ size_t entries = 0;
+
+ *p_num_entries = 0;
+ *pp_rmap = NULL;
+
+ if (!NT_STATUS_IS_OK(ldapsam_setsamgrent(methods, False))) {
+ DEBUG(0, ("ldapsam_enum_group_mapping: Unable to open "
+ "passdb\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ while (true) {
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!NT_STATUS_IS_OK(ldapsam_getsamgrent(methods, map))) {
+ TALLOC_FREE(map);
+ break;
+ }
+
+ if (sid_name_use != SID_NAME_UNKNOWN &&
+ sid_name_use != map->sid_name_use) {
+ DEBUG(11,("ldapsam_enum_group_mapping: group %s is "
+ "not of the requested type\n",
+ map->nt_name));
+ continue;
+ }
+ if (unix_only == ENUM_ONLY_MAPPED && map->gid == -1) {
+ DEBUG(11,("ldapsam_enum_group_mapping: group %s is "
+ "non mapped\n", map->nt_name));
+ continue;
+ }
+
+ *pp_rmap = talloc_realloc(NULL, *pp_rmap,
+ GROUP_MAP *, entries + 1);
+ if (!(*pp_rmap)) {
+ DEBUG(0,("ldapsam_enum_group_mapping: Unable to "
+ "enlarge group map!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ (*pp_rmap)[entries] = talloc_move((*pp_rmap), &map);
+
+ entries += 1;
+ }
+
+ ldapsam_endsamgrent(methods);
+
+ *p_num_entries = entries;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_modify_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias,
+ const struct dom_sid *member,
+ int modop)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ char *dn = NULL;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int count;
+ LDAPMod **mods = NULL;
+ int rc;
+ enum lsa_SidType type = SID_NAME_USE_NONE;
+ struct dom_sid_buf tmp;
+
+ char *filter = NULL;
+
+ if (sid_check_is_in_builtin(alias)) {
+ type = SID_NAME_ALIAS;
+ }
+
+ if (sid_check_is_in_our_sam(alias)) {
+ type = SID_NAME_ALIAS;
+ }
+
+ if (type == SID_NAME_USE_NONE) {
+ struct dom_sid_buf buf;
+ DEBUG(5, ("SID %s is neither in builtin nor in our domain!\n",
+ dom_sid_str_buf(alias, &buf)));
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ if (asprintf(&filter,
+ "(&(objectClass=%s)(sambaSid=%s)(sambaGroupType=%d))",
+ LDAP_OBJ_GROUPMAP,
+ dom_sid_str_buf(alias, &tmp),
+ type) < 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ldapsam_search_one_group(ldap_state, filter,
+ &result) != LDAP_SUCCESS) {
+ SAFE_FREE(filter);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ count = ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+
+ if (count < 1) {
+ DEBUG(4, ("ldapsam_modify_aliasmem: Did not find alias\n"));
+ ldap_msgfree(result);
+ SAFE_FREE(filter);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ if (count > 1) {
+ DEBUG(1, ("ldapsam_modify_aliasmem: Duplicate entries for "
+ "filter %s: count=%d\n", filter, count));
+ ldap_msgfree(result);
+ SAFE_FREE(filter);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ SAFE_FREE(filter);
+
+ entry = ldap_first_entry(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+
+ if (!entry) {
+ ldap_msgfree(result);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dn = smbldap_talloc_dn(talloc_tos(),
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry);
+ if (!dn) {
+ ldap_msgfree(result);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ smbldap_set_mod(&mods, modop,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_SID_LIST),
+ dom_sid_str_buf(member, &tmp));
+
+ rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);
+
+ ldap_mods_free(mods, True);
+ ldap_msgfree(result);
+ TALLOC_FREE(dn);
+
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
+ return NT_STATUS_MEMBER_IN_ALIAS;
+ }
+
+ if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
+ return NT_STATUS_MEMBER_NOT_IN_ALIAS;
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_add_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias,
+ const struct dom_sid *member)
+{
+ return ldapsam_modify_aliasmem(methods, alias, member, LDAP_MOD_ADD);
+}
+
+static NTSTATUS ldapsam_del_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias,
+ const struct dom_sid *member)
+{
+ return ldapsam_modify_aliasmem(methods, alias, member,
+ LDAP_MOD_DELETE);
+}
+
+static NTSTATUS ldapsam_enum_aliasmem(struct pdb_methods *methods,
+ const struct dom_sid *alias,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid **pp_members,
+ size_t *p_num_members)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int count;
+ char **values = NULL;
+ int i;
+ char *filter = NULL;
+ uint32_t num_members = 0;
+ enum lsa_SidType type = SID_NAME_USE_NONE;
+ struct dom_sid_buf tmp;
+
+ *pp_members = NULL;
+ *p_num_members = 0;
+
+ if (sid_check_is_in_builtin(alias)) {
+ type = SID_NAME_ALIAS;
+ }
+
+ if (sid_check_is_in_our_sam(alias)) {
+ type = SID_NAME_ALIAS;
+ }
+
+ if (type == SID_NAME_USE_NONE) {
+ DEBUG(5, ("SID %s is neither in builtin nor in our domain!\n",
+ dom_sid_str_buf(alias, &tmp)));
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ if (asprintf(&filter,
+ "(&(objectClass=%s)(sambaSid=%s)(sambaGroupType=%d))",
+ LDAP_OBJ_GROUPMAP,
+ dom_sid_str_buf(alias, &tmp),
+ type) < 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ldapsam_search_one_group(ldap_state, filter,
+ &result) != LDAP_SUCCESS) {
+ SAFE_FREE(filter);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ count = ldap_count_entries(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+
+ if (count < 1) {
+ DEBUG(4, ("ldapsam_enum_aliasmem: Did not find alias\n"));
+ ldap_msgfree(result);
+ SAFE_FREE(filter);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ if (count > 1) {
+ DEBUG(1, ("ldapsam_enum_aliasmem: Duplicate entries for "
+ "filter %s: count=%d\n", filter, count));
+ ldap_msgfree(result);
+ SAFE_FREE(filter);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ SAFE_FREE(filter);
+
+ entry = ldap_first_entry(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+
+ if (!entry) {
+ ldap_msgfree(result);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ values = ldap_get_values(smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string(groupmap_attr_list,
+ LDAP_ATTR_SID_LIST));
+
+ if (values == NULL) {
+ ldap_msgfree(result);
+ return NT_STATUS_OK;
+ }
+
+ count = ldap_count_values(values);
+
+ for (i=0; i<count; i++) {
+ struct dom_sid member;
+ NTSTATUS status;
+
+ if (!string_to_sid(&member, values[i]))
+ continue;
+
+ status = add_sid_to_array(mem_ctx, &member, pp_members,
+ &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldap_value_free(values);
+ ldap_msgfree(result);
+ return status;
+ }
+ }
+
+ *p_num_members = num_members;
+ ldap_value_free(values);
+ ldap_msgfree(result);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_alias_memberships(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *members,
+ size_t num_members,
+ uint32_t **pp_alias_rids,
+ size_t *p_num_alias_rids)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAP *ldap_struct;
+
+ const char *attrs[] = { LDAP_ATTRIBUTE_SID, NULL };
+
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int i;
+ int rc;
+ char *filter;
+ enum lsa_SidType type = SID_NAME_USE_NONE;
+ bool is_builtin = false;
+ bool sid_added = false;
+
+ *pp_alias_rids = NULL;
+ *p_num_alias_rids = 0;
+
+ if (sid_check_is_builtin(domain_sid)) {
+ is_builtin = true;
+ type = SID_NAME_ALIAS;
+ }
+
+ if (sid_check_is_our_sam(domain_sid)) {
+ type = SID_NAME_ALIAS;
+ }
+
+ if (type == SID_NAME_USE_NONE) {
+ struct dom_sid_buf buf;
+ DEBUG(5, ("SID %s is neither builtin nor domain!\n",
+ dom_sid_str_buf(domain_sid, &buf)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (num_members == 0) {
+ return NT_STATUS_OK;
+ }
+
+ filter = talloc_asprintf(mem_ctx,
+ "(&(objectclass="LDAP_OBJ_GROUPMAP")(sambaGroupType=%d)(|",
+ type);
+
+ for (i=0; i<num_members; i++) {
+ struct dom_sid_buf buf;
+ filter = talloc_asprintf(mem_ctx, "%s(sambaSIDList=%s)",
+ filter,
+ dom_sid_str_buf(&members[i], &buf));
+ }
+
+ filter = talloc_asprintf(mem_ctx, "%s))", filter);
+
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (is_builtin &&
+ ldap_state->search_cache.filter &&
+ strcmp(ldap_state->search_cache.filter, filter) == 0) {
+ filter = talloc_move(filter, &ldap_state->search_cache.filter);
+ result = ldap_state->search_cache.result;
+ ldap_state->search_cache.result = NULL;
+ } else {
+ rc = smbldap_search(ldap_state->smbldap_state, lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, attrs, 0, &result);
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(filter, result);
+ }
+
+ ldap_struct = smbldap_get_ldap(ldap_state->smbldap_state);
+
+ for (entry = ldap_first_entry(ldap_struct, result);
+ entry != NULL;
+ entry = ldap_next_entry(ldap_struct, entry))
+ {
+ fstring sid_str;
+ struct dom_sid sid;
+ uint32_t rid;
+
+ if (!smbldap_get_single_attribute(ldap_struct, entry,
+ LDAP_ATTRIBUTE_SID,
+ sid_str,
+ sizeof(sid_str)-1))
+ continue;
+
+ if (!string_to_sid(&sid, sid_str))
+ continue;
+
+ if (!sid_peek_check_rid(domain_sid, &sid, &rid))
+ continue;
+
+ sid_added = true;
+
+ if (!add_rid_to_array_unique(mem_ctx, rid, pp_alias_rids,
+ p_num_alias_rids)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!is_builtin && !sid_added) {
+ TALLOC_FREE(ldap_state->search_cache.filter);
+ /*
+ * Note: result is a talloc child of filter because of the
+ * smbldap_talloc_autofree_ldapmsg() usage
+ */
+ ldap_state->search_cache.filter = talloc_move(ldap_state, &filter);
+ ldap_state->search_cache.result = result;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_set_account_policy_in_ldap(struct pdb_methods *methods,
+ enum pdb_policy_type type,
+ uint32_t value)
+{
+ NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL;
+ int rc;
+ LDAPMod **mods = NULL;
+ fstring value_string;
+ const char *policy_attr = NULL;
+
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+
+ DEBUG(10,("ldapsam_set_account_policy_in_ldap\n"));
+
+ if (!ldap_state->domain_dn) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ policy_attr = get_account_policy_attr(type);
+ if (policy_attr == NULL) {
+ DEBUG(0,("ldapsam_set_account_policy_in_ldap: invalid "
+ "policy\n"));
+ return ntstatus;
+ }
+
+ slprintf(value_string, sizeof(value_string) - 1, "%i", value);
+
+ smbldap_set_mod(&mods, LDAP_MOD_REPLACE, policy_attr, value_string);
+
+ rc = smbldap_modify(ldap_state->smbldap_state, ldap_state->domain_dn,
+ mods);
+
+ ldap_mods_free(mods, True);
+
+ if (rc != LDAP_SUCCESS) {
+ return ntstatus;
+ }
+
+ if (!cache_account_policy_set(type, value)) {
+ DEBUG(0,("ldapsam_set_account_policy_in_ldap: failed to "
+ "update local tdb cache\n"));
+ return ntstatus;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_set_account_policy(struct pdb_methods *methods,
+ enum pdb_policy_type type,
+ uint32_t value)
+{
+ return ldapsam_set_account_policy_in_ldap(methods, type,
+ value);
+}
+
+static NTSTATUS ldapsam_get_account_policy_from_ldap(struct pdb_methods *methods,
+ enum pdb_policy_type type,
+ uint32_t *value)
+{
+ NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int count;
+ int rc;
+ char **vals = NULL;
+ const char *filter;
+ const char *policy_attr = NULL;
+
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+
+ const char *attrs[2];
+
+ DEBUG(10,("ldapsam_get_account_policy_from_ldap\n"));
+
+ if (!ldap_state->domain_dn) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ policy_attr = get_account_policy_attr(type);
+ if (!policy_attr) {
+ DEBUG(0,("ldapsam_get_account_policy_from_ldap: invalid "
+ "policy index: %d\n", type));
+ return ntstatus;
+ }
+
+ attrs[0] = policy_attr;
+ attrs[1] = NULL;
+
+ filter = "(objectClass="LDAP_OBJ_DOMINFO")";
+ rc = smbldap_search(ldap_state->smbldap_state, ldap_state->domain_dn,
+ LDAP_SCOPE_BASE, filter, attrs, 0,
+ &result);
+ if (rc != LDAP_SUCCESS) {
+ return ntstatus;
+ }
+
+ count = ldap_count_entries(priv2ld(ldap_state), result);
+ if (count < 1) {
+ goto out;
+ }
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (entry == NULL) {
+ goto out;
+ }
+
+ vals = ldap_get_values(priv2ld(ldap_state), entry, policy_attr);
+ if (vals == NULL) {
+ goto out;
+ }
+
+ *value = (uint32_t)atol(vals[0]);
+
+ ntstatus = NT_STATUS_OK;
+
+out:
+ if (vals)
+ ldap_value_free(vals);
+ ldap_msgfree(result);
+
+ return ntstatus;
+}
+
+/* wrapper around ldapsam_get_account_policy_from_ldap(), handles tdb as cache
+
+ - if user hasn't decided to use account policies inside LDAP just reuse the
+ old tdb values
+
+ - if there is a valid cache entry, return that
+ - if there is an LDAP entry, update cache and return
+ - otherwise set to default, update cache and return
+
+ Guenther
+*/
+static NTSTATUS ldapsam_get_account_policy(struct pdb_methods *methods,
+ enum pdb_policy_type type,
+ uint32_t *value)
+{
+ NTSTATUS ntstatus;
+
+ if (cache_account_policy_get(type, value)) {
+ DEBUG(11,("ldapsam_get_account_policy: got valid value from "
+ "cache\n"));
+ return NT_STATUS_OK;
+ }
+
+ ntstatus = ldapsam_get_account_policy_from_ldap(methods, type,
+ value);
+ if (NT_STATUS_IS_OK(ntstatus)) {
+ goto update_cache;
+ }
+
+ DEBUG(10,("ldapsam_get_account_policy: failed to retrieve from "
+ "ldap\n"));
+
+#if 0
+ /* should we automagically migrate old tdb value here ? */
+ if (account_policy_get(type, value))
+ goto update_ldap;
+
+ DEBUG(10,("ldapsam_get_account_policy: no tdb for %d, trying "
+ "default\n", type));
+#endif
+
+ if (!account_policy_get_default(type, value)) {
+ return ntstatus;
+ }
+
+/* update_ldap: */
+
+ ntstatus = ldapsam_set_account_policy(methods, type, *value);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ return ntstatus;
+ }
+
+ update_cache:
+
+ if (!cache_account_policy_set(type, *value)) {
+ DEBUG(0,("ldapsam_get_account_policy: failed to update local "
+ "tdb as a cache\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_lookup_rids(struct pdb_methods *methods,
+ const struct dom_sid *domain_sid,
+ int num_rids,
+ uint32_t *rids,
+ const char **names,
+ enum lsa_SidType *attrs)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *msg = NULL;
+ LDAPMessage *entry;
+ char *allsids = NULL;
+ size_t i, num_mapped;
+ int rc;
+ NTSTATUS result = NT_STATUS_NO_MEMORY;
+ TALLOC_CTX *mem_ctx;
+ LDAP *ld;
+ bool is_builtin;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ goto done;
+ }
+
+ if (!sid_check_is_builtin(domain_sid) &&
+ !sid_check_is_our_sam(domain_sid)) {
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (num_rids == 0) {
+ result = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ for (i=0; i<num_rids; i++)
+ attrs[i] = SID_NAME_UNKNOWN;
+
+ allsids = talloc_strdup(mem_ctx, "");
+ if (allsids == NULL) {
+ goto done;
+ }
+
+ for (i=0; i<num_rids; i++) {
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ sid_compose(&sid, domain_sid, rids[i]);
+ allsids = talloc_asprintf_append_buffer(
+ allsids,
+ "(sambaSid=%s)",
+ dom_sid_str_buf(&sid, &buf));
+ if (allsids == NULL) {
+ goto done;
+ }
+ }
+
+ /* First look for users */
+
+ {
+ char *filter;
+ const char *ldap_attrs[] = { "uid", "sambaSid", NULL };
+
+ filter = talloc_asprintf(
+ mem_ctx, ("(&(objectClass="LDAP_OBJ_SAMBASAMACCOUNT")(|%s))"),
+ allsids);
+
+ if (filter == NULL) {
+ goto done;
+ }
+
+ rc = smbldap_search(ldap_state->smbldap_state,
+ lp_ldap_user_suffix(talloc_tos()),
+ LDAP_SCOPE_SUBTREE, filter, ldap_attrs, 0,
+ &msg);
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, msg);
+ }
+
+ if (rc != LDAP_SUCCESS)
+ goto done;
+
+ ld = smbldap_get_ldap(ldap_state->smbldap_state);
+ num_mapped = 0;
+
+ for (entry = ldap_first_entry(ld, msg);
+ entry != NULL;
+ entry = ldap_next_entry(ld, entry)) {
+ uint32_t rid;
+ int rid_index;
+ const char *name;
+
+ if (!ldapsam_extract_rid_from_entry(ld, entry, domain_sid,
+ &rid)) {
+ DEBUG(2, ("Could not find sid from ldap entry\n"));
+ continue;
+ }
+
+ name = smbldap_talloc_single_attribute(ld, entry, "uid",
+ names);
+ if (name == NULL) {
+ DEBUG(2, ("Could not retrieve uid attribute\n"));
+ continue;
+ }
+
+ for (rid_index = 0; rid_index < num_rids; rid_index++) {
+ if (rid == rids[rid_index])
+ break;
+ }
+
+ if (rid_index == num_rids) {
+ DEBUG(2, ("Got a RID not asked for: %d\n", rid));
+ continue;
+ }
+
+ attrs[rid_index] = SID_NAME_USER;
+ names[rid_index] = name;
+ num_mapped += 1;
+ }
+
+ if (num_mapped == num_rids) {
+ /* No need to look for groups anymore -- we're done */
+ result = NT_STATUS_OK;
+ goto done;
+ }
+
+ /* Same game for groups */
+
+ {
+ char *filter;
+ const char *ldap_attrs[] = { "cn", "displayName", "sambaSid",
+ "sambaGroupType", NULL };
+
+ filter = talloc_asprintf(
+ mem_ctx, "(&(objectClass="LDAP_OBJ_GROUPMAP")(|%s))",
+ allsids);
+ if (filter == NULL) {
+ goto done;
+ }
+
+ rc = smbldap_search(ldap_state->smbldap_state,
+ lp_ldap_suffix(),
+ LDAP_SCOPE_SUBTREE, filter, ldap_attrs, 0,
+ &msg);
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, msg);
+ }
+
+ if (rc != LDAP_SUCCESS)
+ goto done;
+
+ /* ldap_struct might have changed due to a reconnect */
+
+ ld = smbldap_get_ldap(ldap_state->smbldap_state);
+
+ /* For consistency checks, we already checked we're only domain or builtin */
+
+ is_builtin = sid_check_is_builtin(domain_sid);
+
+ for (entry = ldap_first_entry(ld, msg);
+ entry != NULL;
+ entry = ldap_next_entry(ld, entry))
+ {
+ uint32_t rid;
+ int rid_index;
+ const char *attr;
+ enum lsa_SidType type;
+ const char *dn = smbldap_talloc_dn(mem_ctx, ld, entry);
+
+ attr = smbldap_talloc_single_attribute(ld, entry, "sambaGroupType",
+ mem_ctx);
+ if (attr == NULL) {
+ DEBUG(2, ("Could not extract type from ldap entry %s\n",
+ dn));
+ continue;
+ }
+
+ type = (enum lsa_SidType)atol(attr);
+
+ /* Consistency checks */
+ if ((is_builtin && (type != SID_NAME_ALIAS)) ||
+ (!is_builtin && ((type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_DOM_GRP)))) {
+ DEBUG(2, ("Rejecting invalid group mapping entry %s\n", dn));
+ }
+
+ if (!ldapsam_extract_rid_from_entry(ld, entry, domain_sid,
+ &rid)) {
+ DEBUG(2, ("Could not find sid from ldap entry %s\n", dn));
+ continue;
+ }
+
+ attr = smbldap_talloc_single_attribute(ld, entry, "displayName", names);
+
+ if (attr == NULL) {
+ DEBUG(10, ("Could not retrieve 'displayName' attribute from %s\n",
+ dn));
+ attr = smbldap_talloc_single_attribute(ld, entry, "cn", names);
+ }
+
+ if (attr == NULL) {
+ DEBUG(2, ("Could not retrieve naming attribute from %s\n",
+ dn));
+ continue;
+ }
+
+ for (rid_index = 0; rid_index < num_rids; rid_index++) {
+ if (rid == rids[rid_index])
+ break;
+ }
+
+ if (rid_index == num_rids) {
+ DEBUG(2, ("Got a RID not asked for: %d\n", rid));
+ continue;
+ }
+
+ attrs[rid_index] = type;
+ names[rid_index] = attr;
+ num_mapped += 1;
+ }
+
+ result = NT_STATUS_NONE_MAPPED;
+
+ if (num_mapped > 0)
+ result = (num_mapped == num_rids) ?
+ NT_STATUS_OK : STATUS_SOME_UNMAPPED;
+ done:
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+static char *get_ldap_filter(TALLOC_CTX *mem_ctx, const char *username)
+{
+ char *filter = NULL;
+ char *escaped = NULL;
+ char *result = NULL;
+
+ if (asprintf(&filter, "(&%s(objectclass=%s))",
+ "(uid=%u)", LDAP_OBJ_SAMBASAMACCOUNT) < 0) {
+ goto done;
+ }
+
+ escaped = escape_ldap_string(talloc_tos(), username);
+ if (escaped == NULL) goto done;
+
+ result = talloc_string_sub(mem_ctx, filter, "%u", username);
+
+ done:
+ SAFE_FREE(filter);
+ TALLOC_FREE(escaped);
+
+ return result;
+}
+
+static const char **talloc_attrs(TALLOC_CTX *mem_ctx, ...)
+{
+ int i, num = 0;
+ va_list ap;
+ const char **result;
+
+ va_start(ap, mem_ctx);
+ while (va_arg(ap, const char *) != NULL)
+ num += 1;
+ va_end(ap);
+
+ if ((result = talloc_array(mem_ctx, const char *, num+1)) == NULL) {
+ return NULL;
+ }
+
+ va_start(ap, mem_ctx);
+ for (i=0; i<num; i++) {
+ result[i] = talloc_strdup(result, va_arg(ap, const char*));
+ if (result[i] == NULL) {
+ talloc_free(result);
+ va_end(ap);
+ return NULL;
+ }
+ }
+ va_end(ap);
+
+ result[num] = NULL;
+ return result;
+}
+
+struct ldap_search_state {
+ struct smbldap_state *connection;
+
+ uint32_t acct_flags;
+ uint16_t group_type;
+
+ const char *base;
+ int scope;
+ const char *filter;
+ const char **attrs;
+ int attrsonly;
+ void *pagedresults_cookie;
+
+ LDAPMessage *entries, *current_entry;
+ bool (*ldap2displayentry)(struct ldap_search_state *state,
+ TALLOC_CTX *mem_ctx,
+ LDAP *ld, LDAPMessage *entry,
+ struct samr_displayentry *result);
+};
+
+static bool ldapsam_search_firstpage(struct pdb_search *search)
+{
+ struct ldap_search_state *state =
+ (struct ldap_search_state *)search->private_data;
+ LDAP *ld;
+ int rc = LDAP_OPERATIONS_ERROR;
+
+ state->entries = NULL;
+
+ if (smbldap_get_paged_results(state->connection)) {
+ rc = smbldap_search_paged(state->connection, state->base,
+ state->scope, state->filter,
+ state->attrs, state->attrsonly,
+ lp_ldap_page_size(), &state->entries,
+ &state->pagedresults_cookie);
+ }
+
+ if ((rc != LDAP_SUCCESS) || (state->entries == NULL)) {
+
+ if (state->entries != NULL) {
+ /* Left over from unsuccessful paged attempt */
+ ldap_msgfree(state->entries);
+ state->entries = NULL;
+ }
+
+ rc = smbldap_search(state->connection, state->base,
+ state->scope, state->filter, state->attrs,
+ state->attrsonly, &state->entries);
+
+ if ((rc != LDAP_SUCCESS) || (state->entries == NULL))
+ return False;
+
+ /* Ok, the server was lying. It told us it could do paged
+ * searches when it could not. */
+ smbldap_set_paged_results(state->connection, false);
+ }
+
+ ld = smbldap_get_ldap(state->connection);
+ if ( ld == NULL) {
+ DEBUG(5, ("Don't have an LDAP connection right after a "
+ "search\n"));
+ return False;
+ }
+ state->current_entry = ldap_first_entry(ld, state->entries);
+
+ return True;
+}
+
+static bool ldapsam_search_nextpage(struct pdb_search *search)
+{
+ struct ldap_search_state *state =
+ (struct ldap_search_state *)search->private_data;
+ int rc;
+
+ if (!smbldap_get_paged_results(state->connection)) {
+ /* There is no next page when there are no paged results */
+ return False;
+ }
+
+ rc = smbldap_search_paged(state->connection, state->base,
+ state->scope, state->filter, state->attrs,
+ state->attrsonly, lp_ldap_page_size(),
+ &state->entries,
+ &state->pagedresults_cookie);
+
+ if ((rc != LDAP_SUCCESS) || (state->entries == NULL))
+ return False;
+
+ state->current_entry = ldap_first_entry(
+ smbldap_get_ldap(state->connection), state->entries);
+
+ if (state->current_entry == NULL) {
+ ldap_msgfree(state->entries);
+ state->entries = NULL;
+ return false;
+ }
+
+ return True;
+}
+
+static bool ldapsam_search_next_entry(struct pdb_search *search,
+ struct samr_displayentry *entry)
+{
+ struct ldap_search_state *state =
+ (struct ldap_search_state *)search->private_data;
+ bool result;
+
+ retry:
+ if ((state->entries == NULL) && (state->pagedresults_cookie == NULL))
+ return False;
+
+ if ((state->entries == NULL) &&
+ !ldapsam_search_nextpage(search))
+ return False;
+
+ if (state->current_entry == NULL) {
+ return false;
+ }
+
+ result = state->ldap2displayentry(state, search,
+ smbldap_get_ldap(state->connection),
+ state->current_entry, entry);
+
+ if (!result) {
+ char *dn;
+ dn = ldap_get_dn(smbldap_get_ldap(state->connection),
+ state->current_entry);
+ DEBUG(5, ("Skipping entry %s\n", dn != NULL ? dn : "<NULL>"));
+ if (dn != NULL) ldap_memfree(dn);
+ }
+
+ state->current_entry = ldap_next_entry(
+ smbldap_get_ldap(state->connection), state->current_entry);
+
+ if (state->current_entry == NULL) {
+ ldap_msgfree(state->entries);
+ state->entries = NULL;
+ }
+
+ if (!result) goto retry;
+
+ return True;
+}
+
+static void ldapsam_search_end(struct pdb_search *search)
+{
+ struct ldap_search_state *state =
+ (struct ldap_search_state *)search->private_data;
+ int rc;
+
+ if (state->pagedresults_cookie == NULL)
+ return;
+
+ if (state->entries != NULL)
+ ldap_msgfree(state->entries);
+
+ state->entries = NULL;
+ state->current_entry = NULL;
+
+ if (!smbldap_get_paged_results(state->connection)) {
+ return;
+ }
+
+ /* Tell the LDAP server we're not interested in the rest anymore. */
+
+ rc = smbldap_search_paged(state->connection, state->base, state->scope,
+ state->filter, state->attrs,
+ state->attrsonly, 0, &state->entries,
+ &state->pagedresults_cookie);
+
+ if (rc != LDAP_SUCCESS)
+ DEBUG(5, ("Could not end search properly\n"));
+
+ return;
+}
+
+static bool ldapuser2displayentry(struct ldap_search_state *state,
+ TALLOC_CTX *mem_ctx,
+ LDAP *ld, LDAPMessage *entry,
+ struct samr_displayentry *result)
+{
+ char **vals;
+ size_t converted_size;
+ struct dom_sid sid;
+ uint32_t acct_flags;
+
+ vals = ldap_get_values(ld, entry, "sambaAcctFlags");
+ if ((vals == NULL) || (vals[0] == NULL)) {
+ acct_flags = ACB_NORMAL;
+ } else {
+ acct_flags = pdb_decode_acct_ctrl(vals[0]);
+ ldap_value_free(vals);
+ }
+
+ if ((state->acct_flags != 0) &&
+ ((state->acct_flags & acct_flags) == 0))
+ return False;
+
+ result->acct_flags = acct_flags;
+ result->account_name = "";
+ result->fullname = "";
+ result->description = "";
+
+ vals = ldap_get_values(ld, entry, "uid");
+ if ((vals == NULL) || (vals[0] == NULL)) {
+ DEBUG(5, ("\"uid\" not found\n"));
+ return False;
+ }
+ if (!pull_utf8_talloc(mem_ctx,
+ discard_const_p(char *, &result->account_name),
+ vals[0], &converted_size))
+ {
+ DEBUG(0,("ldapuser2displayentry: pull_utf8_talloc failed: %s\n",
+ strerror(errno)));
+ }
+
+ ldap_value_free(vals);
+
+ vals = ldap_get_values(ld, entry, "displayName");
+ if ((vals == NULL) || (vals[0] == NULL))
+ DEBUG(8, ("\"displayName\" not found\n"));
+ else if (!pull_utf8_talloc(mem_ctx,
+ discard_const_p(char *, &result->fullname),
+ vals[0], &converted_size))
+ {
+ DEBUG(0,("ldapuser2displayentry: pull_utf8_talloc failed: %s\n",
+ strerror(errno)));
+ }
+
+ ldap_value_free(vals);
+
+ vals = ldap_get_values(ld, entry, "description");
+ if ((vals == NULL) || (vals[0] == NULL))
+ DEBUG(8, ("\"description\" not found\n"));
+ else if (!pull_utf8_talloc(mem_ctx,
+ discard_const_p(char *, &result->description),
+ vals[0], &converted_size))
+ {
+ DEBUG(0,("ldapuser2displayentry: pull_utf8_talloc failed: %s\n",
+ strerror(errno)));
+ }
+
+ ldap_value_free(vals);
+
+ if ((result->account_name == NULL) ||
+ (result->fullname == NULL) ||
+ (result->description == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ return False;
+ }
+
+ vals = ldap_get_values(ld, entry, "sambaSid");
+ if ((vals == NULL) || (vals[0] == NULL)) {
+ DEBUG(0, ("\"objectSid\" not found\n"));
+ return False;
+ }
+
+ if (!string_to_sid(&sid, vals[0])) {
+ DEBUG(0, ("Could not convert %s to SID\n", vals[0]));
+ ldap_value_free(vals);
+ return False;
+ }
+ ldap_value_free(vals);
+
+ if (!sid_peek_check_rid(get_global_sam_sid(), &sid, &result->rid)) {
+ struct dom_sid_buf buf;
+ DEBUG(0, ("sid %s does not belong to our domain\n",
+ dom_sid_str_buf(&sid, &buf)));
+ return False;
+ }
+
+ return True;
+}
+
+
+static bool ldapsam_search_users(struct pdb_methods *methods,
+ struct pdb_search *search,
+ uint32_t acct_flags)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ struct ldap_search_state *state;
+
+ state = talloc(search, struct ldap_search_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return False;
+ }
+
+ state->connection = ldap_state->smbldap_state;
+
+ if ((acct_flags != 0) && ((acct_flags & ACB_NORMAL) != 0))
+ state->base = lp_ldap_user_suffix(talloc_tos());
+ else if ((acct_flags != 0) &&
+ ((acct_flags & (ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST)) != 0))
+ state->base = lp_ldap_machine_suffix(talloc_tos());
+ else
+ state->base = lp_ldap_suffix();
+
+ state->acct_flags = acct_flags;
+ state->base = talloc_strdup(search, state->base);
+ state->scope = LDAP_SCOPE_SUBTREE;
+ state->filter = get_ldap_filter(search, "*");
+ state->attrs = talloc_attrs(search, "uid", "sambaSid",
+ "displayName", "description",
+ "sambaAcctFlags", NULL);
+ state->attrsonly = 0;
+ state->pagedresults_cookie = NULL;
+ state->entries = NULL;
+ state->ldap2displayentry = ldapuser2displayentry;
+
+ if ((state->filter == NULL) || (state->attrs == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ return False;
+ }
+
+ search->private_data = state;
+ search->next_entry = ldapsam_search_next_entry;
+ search->search_end = ldapsam_search_end;
+
+ return ldapsam_search_firstpage(search);
+}
+
+static bool ldapgroup2displayentry(struct ldap_search_state *state,
+ TALLOC_CTX *mem_ctx,
+ LDAP *ld, LDAPMessage *entry,
+ struct samr_displayentry *result)
+{
+ char **vals;
+ size_t converted_size;
+ struct dom_sid sid;
+ uint16_t group_type;
+
+ result->account_name = "";
+ result->fullname = "";
+ result->description = "";
+
+
+ vals = ldap_get_values(ld, entry, "sambaGroupType");
+ if ((vals == NULL) || (vals[0] == NULL)) {
+ DEBUG(5, ("\"sambaGroupType\" not found\n"));
+ if (vals != NULL) {
+ ldap_value_free(vals);
+ }
+ return False;
+ }
+
+ group_type = atoi(vals[0]);
+
+ if ((state->group_type != 0) &&
+ ((state->group_type != group_type))) {
+ ldap_value_free(vals);
+ return False;
+ }
+
+ ldap_value_free(vals);
+
+ /* display name is the NT group name */
+
+ vals = ldap_get_values(ld, entry, "displayName");
+ if ((vals == NULL) || (vals[0] == NULL)) {
+ DEBUG(8, ("\"displayName\" not found\n"));
+
+ /* fallback to the 'cn' attribute */
+ vals = ldap_get_values(ld, entry, "cn");
+ if ((vals == NULL) || (vals[0] == NULL)) {
+ DEBUG(5, ("\"cn\" not found\n"));
+ return False;
+ }
+ if (!pull_utf8_talloc(mem_ctx,
+ discard_const_p(char *,
+ &result->account_name),
+ vals[0], &converted_size))
+ {
+ DEBUG(0,("ldapgroup2displayentry: pull_utf8_talloc "
+ "failed: %s\n", strerror(errno)));
+ }
+ }
+ else if (!pull_utf8_talloc(mem_ctx,
+ discard_const_p(char *,
+ &result->account_name),
+ vals[0], &converted_size))
+ {
+ DEBUG(0,("ldapgroup2displayentry: pull_utf8_talloc failed: %s\n",
+ strerror(errno)));
+ }
+
+ ldap_value_free(vals);
+
+ vals = ldap_get_values(ld, entry, "description");
+ if ((vals == NULL) || (vals[0] == NULL))
+ DEBUG(8, ("\"description\" not found\n"));
+ else if (!pull_utf8_talloc(mem_ctx,
+ discard_const_p(char *, &result->description),
+ vals[0], &converted_size))
+ {
+ DEBUG(0,("ldapgroup2displayentry: pull_utf8_talloc failed: %s\n",
+ strerror(errno)));
+ }
+ ldap_value_free(vals);
+
+ if ((result->account_name == NULL) ||
+ (result->fullname == NULL) ||
+ (result->description == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ return False;
+ }
+
+ vals = ldap_get_values(ld, entry, "sambaSid");
+ if ((vals == NULL) || (vals[0] == NULL)) {
+ DEBUG(0, ("\"objectSid\" not found\n"));
+ if (vals != NULL) {
+ ldap_value_free(vals);
+ }
+ return False;
+ }
+
+ if (!string_to_sid(&sid, vals[0])) {
+ DEBUG(0, ("Could not convert %s to SID\n", vals[0]));
+ return False;
+ }
+
+ ldap_value_free(vals);
+
+ switch (group_type) {
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+
+ if (!sid_peek_check_rid(get_global_sam_sid(), &sid, &result->rid)
+ && !sid_peek_check_rid(&global_sid_Builtin, &sid, &result->rid))
+ {
+ struct dom_sid_buf buf;
+ DEBUG(0, ("%s is not in our domain\n",
+ dom_sid_str_buf(&sid, &buf)));
+ return False;
+ }
+ break;
+
+ default:
+ DEBUG(0,("unknown group type: %d\n", group_type));
+ return False;
+ }
+
+ result->acct_flags = 0;
+
+ return True;
+}
+
+static bool ldapsam_search_grouptype(struct pdb_methods *methods,
+ struct pdb_search *search,
+ const struct dom_sid *sid,
+ enum lsa_SidType type)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ struct ldap_search_state *state;
+ struct dom_sid_buf tmp;
+
+ state = talloc(search, struct ldap_search_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return False;
+ }
+
+ state->connection = ldap_state->smbldap_state;
+
+ state->base = lp_ldap_suffix();
+ state->connection = ldap_state->smbldap_state;
+ state->scope = LDAP_SCOPE_SUBTREE;
+ state->filter = talloc_asprintf(search, "(&(objectclass="LDAP_OBJ_GROUPMAP")"
+ "(sambaGroupType=%d)(sambaSID=%s*))",
+ type,
+ dom_sid_str_buf(sid, &tmp));
+ state->attrs = talloc_attrs(search, "cn", "sambaSid",
+ "displayName", "description",
+ "sambaGroupType", NULL);
+ state->attrsonly = 0;
+ state->pagedresults_cookie = NULL;
+ state->entries = NULL;
+ state->group_type = type;
+ state->ldap2displayentry = ldapgroup2displayentry;
+
+ if ((state->filter == NULL) || (state->attrs == NULL)) {
+ DEBUG(0, ("talloc failed\n"));
+ return False;
+ }
+
+ search->private_data = state;
+ search->next_entry = ldapsam_search_next_entry;
+ search->search_end = ldapsam_search_end;
+
+ return ldapsam_search_firstpage(search);
+}
+
+static bool ldapsam_search_groups(struct pdb_methods *methods,
+ struct pdb_search *search)
+{
+ return ldapsam_search_grouptype(methods, search, get_global_sam_sid(), SID_NAME_DOM_GRP);
+}
+
+static bool ldapsam_search_aliases(struct pdb_methods *methods,
+ struct pdb_search *search,
+ const struct dom_sid *sid)
+{
+ return ldapsam_search_grouptype(methods, search, sid, SID_NAME_ALIAS);
+}
+
+static uint32_t ldapsam_capabilities(struct pdb_methods *methods)
+{
+ return PDB_CAP_STORE_RIDS;
+}
+
+static NTSTATUS ldapsam_get_new_rid(struct ldapsam_privates *priv,
+ uint32_t *rid)
+{
+ struct smbldap_state *smbldap_state = priv->smbldap_state;
+
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ NTSTATUS status;
+ char *value;
+ int rc;
+ uint32_t nextRid = 0;
+ const char *dn;
+ uint32_t tmp;
+ int error = 0;
+
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = smbldap_search_domain_info(smbldap_state, &result,
+ get_global_sam_name(), False);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not get domain info: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ entry = ldap_first_entry(priv2ld(priv), result);
+ if (entry == NULL) {
+ DEBUG(0, ("Could not get domain info entry\n"));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ /* Find the largest of the three attributes "sambaNextRid",
+ "sambaNextGroupRid" and "sambaNextUserRid". I gave up on the
+ concept of differentiating between user and group rids, and will
+ use only "sambaNextRid" in the future. But for compatibility
+ reasons I look if others have chosen different strategies -- VL */
+
+ value = smbldap_talloc_single_attribute(priv2ld(priv), entry,
+ "sambaNextRid", mem_ctx);
+ if (value != NULL) {
+ tmp = (uint32_t)smb_strtoul(value,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+
+ nextRid = MAX(nextRid, tmp);
+ }
+
+ value = smbldap_talloc_single_attribute(priv2ld(priv), entry,
+ "sambaNextUserRid", mem_ctx);
+ if (value != NULL) {
+ tmp = (uint32_t)smb_strtoul(value,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+
+ nextRid = MAX(nextRid, tmp);
+ }
+
+ value = smbldap_talloc_single_attribute(priv2ld(priv), entry,
+ "sambaNextGroupRid", mem_ctx);
+ if (value != NULL) {
+ tmp = (uint32_t)smb_strtoul(value,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+
+ nextRid = MAX(nextRid, tmp);
+ }
+
+ if (nextRid == 0) {
+ nextRid = BASE_RID-1;
+ }
+
+ nextRid += 1;
+
+ smbldap_make_mod(priv2ld(priv), entry, &mods, "sambaNextRid",
+ talloc_asprintf(mem_ctx, "%d", nextRid));
+ smbldap_talloc_autofree_ldapmod(mem_ctx, mods);
+
+ if ((dn = smbldap_talloc_dn(mem_ctx, priv2ld(priv), entry)) == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = smbldap_modify(smbldap_state, dn, mods);
+
+ /* ACCESS_DENIED is used as a placeholder for "the modify failed,
+ * please retry" */
+
+ status = (rc == LDAP_SUCCESS) ? NT_STATUS_OK : NT_STATUS_ACCESS_DENIED;
+
+ done:
+ if (NT_STATUS_IS_OK(status)) {
+ *rid = nextRid;
+ }
+
+ TALLOC_FREE(mem_ctx);
+ return status;
+}
+
+static NTSTATUS ldapsam_new_rid_internal(struct pdb_methods *methods, uint32_t *rid)
+{
+ int i;
+
+ for (i=0; i<10; i++) {
+ NTSTATUS result = ldapsam_get_new_rid(
+ (struct ldapsam_privates *)methods->private_data, rid);
+ if (NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ if (!NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) {
+ return result;
+ }
+
+ /* The ldap update failed (maybe a race condition), retry */
+ }
+
+ /* Tried 10 times, fail. */
+ return NT_STATUS_ACCESS_DENIED;
+}
+
+static bool ldapsam_new_rid(struct pdb_methods *methods, uint32_t *rid)
+{
+ NTSTATUS result = ldapsam_new_rid_internal(methods, rid);
+ return NT_STATUS_IS_OK(result) ? True : False;
+}
+
+static bool ldapsam_sid_to_id(struct pdb_methods *methods,
+ const struct dom_sid *sid,
+ struct unixid *id)
+{
+ struct ldapsam_privates *priv =
+ (struct ldapsam_privates *)methods->private_data;
+ char *filter;
+ int error = 0;
+ struct dom_sid_buf buf;
+ const char *attrs[] = { "sambaGroupType", "gidNumber", "uidNumber",
+ NULL };
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ bool ret = False;
+ char *value;
+ int rc;
+
+ TALLOC_CTX *mem_ctx;
+
+ ret = pdb_sid_to_id_unix_users_and_groups(sid, id);
+ if (ret == true) {
+ return true;
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_new failed\n"));
+ return False;
+ }
+
+ filter = talloc_asprintf(mem_ctx,
+ "(&(sambaSid=%s)"
+ "(|(objectClass="LDAP_OBJ_GROUPMAP")(objectClass="LDAP_OBJ_SAMBASAMACCOUNT")))",
+ dom_sid_str_buf(sid, &buf));
+ if (filter == NULL) {
+ DEBUG(5, ("talloc_asprintf failed\n"));
+ goto done;
+ }
+
+ rc = smbldap_search_suffix(priv->smbldap_state, filter,
+ attrs, &result);
+ if (rc != LDAP_SUCCESS) {
+ goto done;
+ }
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ if (ldap_count_entries(priv2ld(priv), result) != 1) {
+ DEBUG(10, ("Got %d entries, expected one\n",
+ ldap_count_entries(priv2ld(priv), result)));
+ goto done;
+ }
+
+ entry = ldap_first_entry(priv2ld(priv), result);
+
+ value = smbldap_talloc_single_attribute(priv2ld(priv), entry,
+ "sambaGroupType", mem_ctx);
+
+ if (value != NULL) {
+ const char *gid_str;
+ /* It's a group */
+
+ gid_str = smbldap_talloc_single_attribute(
+ priv2ld(priv), entry, "gidNumber", mem_ctx);
+ if (gid_str == NULL) {
+ DEBUG(1, ("%s has sambaGroupType but no gidNumber\n",
+ smbldap_talloc_dn(mem_ctx, priv2ld(priv),
+ entry)));
+ goto done;
+ }
+
+ id->id = smb_strtoul(gid_str,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+
+ id->type = ID_TYPE_GID;
+ ret = True;
+ goto done;
+ }
+
+ /* It must be a user */
+
+ value = smbldap_talloc_single_attribute(priv2ld(priv), entry,
+ "uidNumber", mem_ctx);
+ if (value == NULL) {
+ DEBUG(1, ("Could not find uidNumber in %s\n",
+ smbldap_talloc_dn(mem_ctx, priv2ld(priv), entry)));
+ goto done;
+ }
+
+ id->id = smb_strtoul(value, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+
+ id->type = ID_TYPE_UID;
+ ret = True;
+ done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+/**
+ * Find the SID for a uid.
+ * This is shortcut is only used if ldapsam:trusted is set to true.
+ */
+static bool ldapsam_uid_to_sid(struct pdb_methods *methods, uid_t uid,
+ struct dom_sid *sid)
+{
+ struct ldapsam_privates *priv =
+ (struct ldapsam_privates *)methods->private_data;
+ char *filter;
+ const char *attrs[] = { "sambaSID", NULL };
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ bool ret = false;
+ char *user_sid_string;
+ struct dom_sid user_sid;
+ int rc;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ filter = talloc_asprintf(tmp_ctx,
+ "(&(uidNumber=%u)"
+ "(objectClass="LDAP_OBJ_POSIXACCOUNT")"
+ "(objectClass="LDAP_OBJ_SAMBASAMACCOUNT"))",
+ (unsigned int)uid);
+ if (filter == NULL) {
+ DEBUG(3, ("talloc_asprintf failed\n"));
+ goto done;
+ }
+
+ rc = smbldap_search_suffix(priv->smbldap_state, filter, attrs, &result);
+ if (rc != LDAP_SUCCESS) {
+ goto done;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ if (ldap_count_entries(priv2ld(priv), result) != 1) {
+ DEBUG(3, ("ERROR: Got %d entries for uid %u, expected one\n",
+ ldap_count_entries(priv2ld(priv), result),
+ (unsigned int)uid));
+ goto done;
+ }
+
+ entry = ldap_first_entry(priv2ld(priv), result);
+
+ user_sid_string = smbldap_talloc_single_attribute(priv2ld(priv), entry,
+ "sambaSID", tmp_ctx);
+ if (user_sid_string == NULL) {
+ DEBUG(1, ("Could not find sambaSID in object '%s'\n",
+ smbldap_talloc_dn(tmp_ctx, priv2ld(priv), entry)));
+ goto done;
+ }
+
+ if (!string_to_sid(&user_sid, user_sid_string)) {
+ DEBUG(3, ("Error calling string_to_sid for sid '%s'\n",
+ user_sid_string));
+ goto done;
+ }
+
+ sid_copy(sid, &user_sid);
+
+ ret = true;
+
+ done:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+/**
+ * Find the SID for a gid.
+ * This is shortcut is only used if ldapsam:trusted is set to true.
+ */
+static bool ldapsam_gid_to_sid(struct pdb_methods *methods, gid_t gid,
+ struct dom_sid *sid)
+{
+ struct ldapsam_privates *priv =
+ (struct ldapsam_privates *)methods->private_data;
+ char *filter;
+ const char *attrs[] = { "sambaSID", NULL };
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ bool ret = false;
+ char *group_sid_string;
+ struct dom_sid group_sid;
+ int rc;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ filter = talloc_asprintf(tmp_ctx,
+ "(&(gidNumber=%u)"
+ "(objectClass="LDAP_OBJ_GROUPMAP"))",
+ (unsigned int)gid);
+ if (filter == NULL) {
+ DEBUG(3, ("talloc_asprintf failed\n"));
+ goto done;
+ }
+
+ rc = smbldap_search_suffix(priv->smbldap_state, filter, attrs, &result);
+ if (rc != LDAP_SUCCESS) {
+ goto done;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ if (ldap_count_entries(priv2ld(priv), result) != 1) {
+ DEBUG(3, ("ERROR: Got %d entries for gid %u, expected one\n",
+ ldap_count_entries(priv2ld(priv), result),
+ (unsigned int)gid));
+ goto done;
+ }
+
+ entry = ldap_first_entry(priv2ld(priv), result);
+
+ group_sid_string = smbldap_talloc_single_attribute(priv2ld(priv), entry,
+ "sambaSID", tmp_ctx);
+ if (group_sid_string == NULL) {
+ DEBUG(1, ("Could not find sambaSID in object '%s'\n",
+ smbldap_talloc_dn(tmp_ctx, priv2ld(priv), entry)));
+ goto done;
+ }
+
+ if (!string_to_sid(&group_sid, group_sid_string)) {
+ DEBUG(3, ("Error calling string_to_sid for sid '%s'\n",
+ group_sid_string));
+ goto done;
+ }
+
+ sid_copy(sid, &group_sid);
+
+ ret = true;
+
+ done:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static bool ldapsam_id_to_sid(struct pdb_methods *methods, struct unixid *id,
+ struct dom_sid *sid)
+{
+ switch (id->type) {
+ case ID_TYPE_UID:
+ return ldapsam_uid_to_sid(methods, id->id, sid);
+
+ case ID_TYPE_GID:
+ return ldapsam_gid_to_sid(methods, id->id, sid);
+
+ default:
+ return false;
+ }
+}
+
+
+/*
+ * The following functions are called only if
+ * ldapsam:trusted and ldapsam:editposix are
+ * set to true
+ */
+
+/*
+ * ldapsam_create_user creates a new
+ * posixAccount and sambaSamAccount object
+ * in the ldap users subtree
+ *
+ * The uid is allocated by winbindd.
+ */
+
+static NTSTATUS ldapsam_create_user(struct pdb_methods *my_methods,
+ TALLOC_CTX *tmp_ctx, const char *name,
+ uint32_t acb_info, uint32_t *rid)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ LDAPMessage *entry = NULL;
+ LDAPMessage *result = NULL;
+ uint32_t num_result;
+ bool is_machine = False;
+ bool add_posix = False;
+ bool init_okay = False;
+ LDAPMod **mods = NULL;
+ struct samu *user;
+ char *filter;
+ char *username;
+ char *homedir;
+ char *gidstr;
+ char *uidstr;
+ char *shell;
+ const char *dn = NULL;
+ struct dom_sid group_sid;
+ struct dom_sid user_sid;
+ gid_t gid = -1;
+ uid_t uid = -1;
+ NTSTATUS ret;
+ int rc;
+
+ if (((acb_info & ACB_NORMAL) && name[strlen(name)-1] == '$') ||
+ acb_info & ACB_WSTRUST ||
+ acb_info & ACB_SVRTRUST ||
+ acb_info & ACB_DOMTRUST) {
+ is_machine = True;
+ }
+
+ username = escape_ldap_string(talloc_tos(), name);
+ filter = talloc_asprintf(tmp_ctx, "(&(uid=%s)(objectClass="LDAP_OBJ_POSIXACCOUNT"))",
+ username);
+ TALLOC_FREE(username);
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("ldapsam_create_user: ldap search failed!\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_create_user: More than one user with name [%s] ?!\n", name));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (num_result == 1) {
+ char *tmp;
+ /* check if it is just a posix account.
+ * or if there is a sid attached to this entry
+ */
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (!entry) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ tmp = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "sambaSID", tmp_ctx);
+ if (tmp) {
+ DEBUG (1, ("ldapsam_create_user: The user [%s] already exist!\n", name));
+ return NT_STATUS_USER_EXISTS;
+ }
+
+ /* it is just a posix account, retrieve the dn for later use */
+ dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry);
+ if (!dn) {
+ DEBUG(0,("ldapsam_create_user: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (num_result == 0) {
+ add_posix = True;
+ }
+
+ /* Create the basic samu structure and generate the mods for the ldap commit */
+ if (!NT_STATUS_IS_OK((ret = ldapsam_new_rid_internal(my_methods, rid)))) {
+ DEBUG(1, ("ldapsam_create_user: Could not allocate a new RID\n"));
+ return ret;
+ }
+
+ sid_compose(&user_sid, get_global_sam_sid(), *rid);
+
+ user = samu_new(tmp_ctx);
+ if (!user) {
+ DEBUG(1,("ldapsam_create_user: Unable to allocate user struct\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_set_username(user, name, PDB_SET)) {
+ DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ if (!pdb_set_domain(user, get_global_sam_name(), PDB_SET)) {
+ DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ if (is_machine) {
+ if (acb_info & ACB_NORMAL) {
+ if (!pdb_set_acct_ctrl(user, ACB_WSTRUST, PDB_SET)) {
+ DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ } else {
+ if (!pdb_set_acct_ctrl(user, acb_info, PDB_SET)) {
+ DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ } else {
+ if (!pdb_set_acct_ctrl(user, ACB_NORMAL | ACB_DISABLED, PDB_SET)) {
+ DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+ if (!pdb_set_user_sid(user, &user_sid, PDB_SET)) {
+ DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ init_okay = init_ldap_from_sam(ldap_state, entry, &mods, user, pdb_element_is_set_or_changed);
+
+ if (!init_okay) {
+ DEBUG(1,("ldapsam_create_user: Unable to fill user structs\n"));
+ ldap_mods_free(mods, true);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (ldap_state->schema_ver != SCHEMAVER_SAMBASAMACCOUNT) {
+ DEBUG(1,("ldapsam_create_user: Unsupported schema version\n"));
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_SAMBASAMACCOUNT);
+
+ if (add_posix) {
+ char *escape_name;
+
+ DEBUG(3,("ldapsam_create_user: Creating new posix user\n"));
+
+ /* retrieve the Domain Users group gid */
+ if (!sid_compose(&group_sid, get_global_sam_sid(), DOMAIN_RID_USERS) ||
+ !sid_to_gid(&group_sid, &gid)) {
+ DEBUG (0, ("ldapsam_create_user: Unable to get the Domain Users gid: bailing out!\n"));
+ ldap_mods_free(mods, true);
+ return NT_STATUS_INVALID_PRIMARY_GROUP;
+ }
+
+ /* lets allocate a new userid for this user */
+ if (!winbind_allocate_uid(&uid)) {
+ DEBUG (0, ("ldapsam_create_user: Unable to allocate a new user id: bailing out!\n"));
+ ldap_mods_free(mods, true);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+
+ if (is_machine) {
+ /* TODO: choose a more appropriate default for machines */
+ homedir = talloc_sub_specified(tmp_ctx,
+ lp_template_homedir(),
+ "SMB_workstations_home",
+ NULL,
+ ldap_state->domain_name,
+ uid,
+ gid);
+ shell = talloc_strdup(tmp_ctx, "/bin/false");
+ } else {
+ homedir = talloc_sub_specified(tmp_ctx,
+ lp_template_homedir(),
+ name,
+ NULL,
+ ldap_state->domain_name,
+ uid,
+ gid);
+ shell = talloc_sub_specified(tmp_ctx,
+ lp_template_shell(),
+ name,
+ NULL,
+ ldap_state->domain_name,
+ uid,
+ gid);
+ }
+ uidstr = talloc_asprintf(tmp_ctx, "%u", (unsigned int)uid);
+ gidstr = talloc_asprintf(tmp_ctx, "%u", (unsigned int)gid);
+
+ escape_name = escape_rdn_val_string_alloc(name);
+ if (!escape_name) {
+ DEBUG (0, ("ldapsam_create_user: Out of memory!\n"));
+ ldap_mods_free(mods, true);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (is_machine) {
+ dn = talloc_asprintf(tmp_ctx, "uid=%s,%s", escape_name, lp_ldap_machine_suffix (talloc_tos()));
+ } else {
+ dn = talloc_asprintf(tmp_ctx, "uid=%s,%s", escape_name, lp_ldap_user_suffix (talloc_tos()));
+ }
+
+ SAFE_FREE(escape_name);
+
+ if (!homedir || !shell || !uidstr || !gidstr || !dn) {
+ DEBUG (0, ("ldapsam_create_user: Out of memory!\n"));
+ ldap_mods_free(mods, true);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_ACCOUNT);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_POSIXACCOUNT);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "uidNumber", uidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "homeDirectory", homedir);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "loginShell", shell);
+ }
+
+ if (add_posix) {
+ rc = smbldap_add(ldap_state->smbldap_state, dn, mods);
+ } else {
+ rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);
+ }
+
+ ldap_mods_free(mods, true);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("ldapsam_create_user: failed to create a new user [%s] (dn = %s)\n", name ,dn));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(2,("ldapsam_create_user: added account [%s] in the LDAP database\n", name));
+
+ flush_pwnam_cache();
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_delete_user(struct pdb_methods *my_methods, TALLOC_CTX *tmp_ctx, struct samu *sam_acct)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int num_result;
+ const char *dn;
+ char *filter;
+ int rc;
+
+ DEBUG(0,("ldapsam_delete_user: Attempt to delete user [%s]\n", pdb_get_username(sam_acct)));
+
+ filter = talloc_asprintf(tmp_ctx,
+ "(&(uid=%s)"
+ "(objectClass="LDAP_OBJ_POSIXACCOUNT")"
+ "(objectClass="LDAP_OBJ_SAMBASAMACCOUNT"))",
+ pdb_get_username(sam_acct));
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("ldapsam_delete_user: user search failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result == 0) {
+ DEBUG(0,("ldapsam_delete_user: user not found!\n"));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_delete_user: More than one user with name [%s] ?!\n", pdb_get_username(sam_acct)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (!entry) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* it is just a posix account, retrieve the dn for later use */
+ dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry);
+ if (!dn) {
+ DEBUG(0,("ldapsam_delete_user: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* try to remove memberships first */
+ {
+ NTSTATUS status;
+ struct dom_sid *sids = NULL;
+ gid_t *gids = NULL;
+ uint32_t num_groups = 0;
+ int i;
+ uint32_t user_rid = pdb_get_user_rid(sam_acct);
+
+ status = ldapsam_enum_group_memberships(my_methods,
+ tmp_ctx,
+ sam_acct,
+ &sids,
+ &gids,
+ &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto delete_dn;
+ }
+
+ for (i=0; i < num_groups; i++) {
+
+ uint32_t group_rid;
+
+ sid_peek_rid(&sids[i], &group_rid);
+
+ ldapsam_del_groupmem(my_methods,
+ tmp_ctx,
+ group_rid,
+ user_rid);
+ }
+ }
+
+ delete_dn:
+
+ rc = smbldap_delete(ldap_state->smbldap_state, dn);
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ flush_pwnam_cache();
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * ldapsam_create_group creates a new
+ * posixGroup and sambaGroupMapping object
+ * in the ldap groups subtree
+ *
+ * The gid is allocated by winbindd.
+ */
+
+static NTSTATUS ldapsam_create_dom_group(struct pdb_methods *my_methods,
+ TALLOC_CTX *tmp_ctx,
+ const char *name,
+ uint32_t *rid)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ NTSTATUS ret;
+ LDAPMessage *entry = NULL;
+ LDAPMessage *result = NULL;
+ uint32_t num_result;
+ bool is_new_entry = False;
+ LDAPMod **mods = NULL;
+ char *filter;
+ char *groupname;
+ const char *grouptype;
+ char *gidstr;
+ const char *dn = NULL;
+ struct dom_sid group_sid;
+ struct dom_sid_buf buf;
+ gid_t gid = -1;
+ int rc;
+ int error = 0;
+
+ groupname = escape_ldap_string(talloc_tos(), name);
+ filter = talloc_asprintf(tmp_ctx, "(&(cn=%s)(objectClass="LDAP_OBJ_POSIXGROUP"))",
+ groupname);
+ TALLOC_FREE(groupname);
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("ldapsam_create_group: ldap search failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_create_group: There exists more than one group with name [%s]: bailing out!\n", name));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (num_result == 1) {
+ char *tmp;
+ /* check if it is just a posix group.
+ * or if there is a sid attached to this entry
+ */
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (!entry) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ tmp = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "sambaSID", tmp_ctx);
+ if (tmp) {
+ DEBUG (1, ("ldapsam_create_group: The group [%s] already exist!\n", name));
+ return NT_STATUS_GROUP_EXISTS;
+ }
+
+ /* it is just a posix group, retrieve the gid and the dn for later use */
+ tmp = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", tmp_ctx);
+ if (!tmp) {
+ DEBUG (1, ("ldapsam_create_group: Couldn't retrieve the gidNumber for [%s]?!?!\n", name));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ gid = smb_strtoul(tmp, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_ERR("Failed to convert gidNumber\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry);
+ if (!dn) {
+ DEBUG(0,("ldapsam_create_group: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (num_result == 0) {
+ is_new_entry = true;
+ }
+
+ if (!NT_STATUS_IS_OK((ret = ldapsam_new_rid_internal(my_methods, rid)))) {
+ DEBUG(1, ("ldapsam_create_group: Could not allocate a new RID\n"));
+ return ret;
+ }
+
+ sid_compose(&group_sid, get_global_sam_sid(), *rid);
+
+ grouptype = talloc_asprintf(tmp_ctx, "%d", SID_NAME_DOM_GRP);
+
+ if (!grouptype) {
+ DEBUG(0,("ldapsam_create_group: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_GROUPMAP);
+ smbldap_set_mod(&mods,
+ LDAP_MOD_ADD,
+ "sambaSid",
+ dom_sid_str_buf(&group_sid, &buf));
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaGroupType", grouptype);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "displayName", name);
+
+ if (is_new_entry) {
+ char *escape_name;
+
+ DEBUG(3,("ldapsam_create_user: Creating new posix group\n"));
+
+ /* lets allocate a new groupid for this group */
+ if (!winbind_allocate_gid(&gid)) {
+ DEBUG (0, ("ldapsam_create_group: Unable to allocate a new group id: bailing out!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ gidstr = talloc_asprintf(tmp_ctx, "%u", (unsigned int)gid);
+
+ escape_name = escape_rdn_val_string_alloc(name);
+ if (!escape_name) {
+ DEBUG (0, ("ldapsam_create_group: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dn = talloc_asprintf(tmp_ctx, "cn=%s,%s", escape_name, lp_ldap_group_suffix(talloc_tos()));
+
+ SAFE_FREE(escape_name);
+
+ if (!gidstr || !dn) {
+ DEBUG (0, ("ldapsam_create_group: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectclass", LDAP_OBJ_POSIXGROUP);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr);
+ }
+
+ smbldap_talloc_autofree_ldapmod(tmp_ctx, mods);
+
+ if (is_new_entry) {
+ rc = smbldap_add(ldap_state->smbldap_state, dn, mods);
+#if 0
+ if (rc == LDAP_OBJECT_CLASS_VIOLATION) {
+ /* This call may fail with rfc2307bis schema */
+ /* Retry adding a structural class */
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "????");
+ rc = smbldap_add(ldap_state->smbldap_state, dn, mods);
+ }
+#endif
+ } else {
+ rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("ldapsam_create_group: failed to create a new group [%s] (dn = %s)\n", name ,dn));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(2,("ldapsam_create_group: added group [%s] in the LDAP database\n", name));
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_delete_dom_group(struct pdb_methods *my_methods, TALLOC_CTX *tmp_ctx, uint32_t rid)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ int num_result;
+ const char *dn;
+ char *gidstr;
+ char *filter;
+ struct dom_sid group_sid;
+ struct dom_sid_buf buf;
+ int rc;
+
+ /* get the group sid */
+ sid_compose(&group_sid, get_global_sam_sid(), rid);
+
+ filter = talloc_asprintf(tmp_ctx,
+ "(&(sambaSID=%s)"
+ "(objectClass="LDAP_OBJ_POSIXGROUP")"
+ "(objectClass="LDAP_OBJ_GROUPMAP"))",
+ dom_sid_str_buf(&group_sid, &buf));
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1,("ldapsam_delete_dom_group: group search failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result == 0) {
+ DEBUG(1,("ldapsam_delete_dom_group: group not found!\n"));
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_delete_dom_group: More than one group with the same SID ?!\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (!entry) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* here it is, retrieve the dn for later use */
+ dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry);
+ if (!dn) {
+ DEBUG(0,("ldapsam_delete_dom_group: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", tmp_ctx);
+ if (!gidstr) {
+ DEBUG (0, ("ldapsam_delete_dom_group: Unable to find the group's gid!\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ /* check no user have this group marked as primary group */
+ filter = talloc_asprintf(tmp_ctx,
+ "(&(gidNumber=%s)"
+ "(objectClass="LDAP_OBJ_POSIXACCOUNT")"
+ "(objectClass="LDAP_OBJ_SAMBASAMACCOUNT"))",
+ gidstr);
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1,("ldapsam_delete_dom_group: accounts search failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result != 0) {
+ DEBUG(3,("ldapsam_delete_dom_group: Can't delete group, it is a primary group for %d users\n", num_result));
+ return NT_STATUS_MEMBERS_PRIMARY_GROUP;
+ }
+
+ rc = smbldap_delete(ldap_state->smbldap_state, dn);
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_change_groupmem(struct pdb_methods *my_methods,
+ TALLOC_CTX *tmp_ctx,
+ uint32_t group_rid,
+ uint32_t member_rid,
+ int modop)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ LDAPMessage *entry = NULL;
+ LDAPMessage *result = NULL;
+ uint32_t num_result;
+ LDAPMod **mods = NULL;
+ char *filter;
+ char *uidstr;
+ const char *dn = NULL;
+ struct dom_sid group_sid;
+ struct dom_sid member_sid;
+ struct dom_sid_buf buf;
+ int rc;
+ int error = 0;
+
+ switch (modop) {
+ case LDAP_MOD_ADD:
+ DEBUG(1,("ldapsam_change_groupmem: add new member(rid=%d) to a domain group(rid=%d)\n", member_rid, group_rid));
+ break;
+ case LDAP_MOD_DELETE:
+ DEBUG(1,("ldapsam_change_groupmem: delete member(rid=%d) from a domain group(rid=%d)\n", member_rid, group_rid));
+ break;
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* get member sid */
+ sid_compose(&member_sid, get_global_sam_sid(), member_rid);
+
+ /* get the group sid */
+ sid_compose(&group_sid, get_global_sam_sid(), group_rid);
+
+ filter = talloc_asprintf(tmp_ctx,
+ "(&(sambaSID=%s)"
+ "(objectClass="LDAP_OBJ_POSIXACCOUNT")"
+ "(objectClass="LDAP_OBJ_SAMBASAMACCOUNT"))",
+ dom_sid_str_buf(&member_sid, &buf));
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get the member uid */
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1,("ldapsam_change_groupmem: member search failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result == 0) {
+ DEBUG(1,("ldapsam_change_groupmem: member not found!\n"));
+ return NT_STATUS_NO_SUCH_MEMBER;
+ }
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_change_groupmem: More than one account with the same SID ?!\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (!entry) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (modop == LDAP_MOD_DELETE) {
+ /* check if we are trying to remove the member from his primary group */
+ char *gidstr;
+ gid_t user_gid, group_gid;
+
+ gidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "gidNumber", tmp_ctx);
+ if (!gidstr) {
+ DEBUG (0, ("ldapsam_change_groupmem: Unable to find the member's gid!\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ user_gid = smb_strtoul(gidstr, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_ERR("Failed to convert user gid\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (!sid_to_gid(&group_sid, &group_gid)) {
+ DEBUG (0, ("ldapsam_change_groupmem: Unable to get group gid from SID!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (user_gid == group_gid) {
+ DEBUG (3, ("ldapsam_change_groupmem: can't remove user from its own primary group!\n"));
+ return NT_STATUS_MEMBERS_PRIMARY_GROUP;
+ }
+ }
+
+ /* here it is, retrieve the uid for later use */
+ uidstr = smbldap_talloc_single_attribute(priv2ld(ldap_state), entry, "uid", tmp_ctx);
+ if (!uidstr) {
+ DEBUG (0, ("ldapsam_change_groupmem: Unable to find the member's name!\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ filter = talloc_asprintf(tmp_ctx,
+ "(&(sambaSID=%s)"
+ "(objectClass="LDAP_OBJ_POSIXGROUP")"
+ "(objectClass="LDAP_OBJ_GROUPMAP"))",
+ dom_sid_str_buf(&group_sid, &buf));
+
+ /* get the group */
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1,("ldapsam_change_groupmem: group search failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(tmp_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result == 0) {
+ DEBUG(1,("ldapsam_change_groupmem: group not found!\n"));
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_change_groupmem: More than one group with the same SID ?!\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (!entry) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* here it is, retrieve the dn for later use */
+ dn = smbldap_talloc_dn(tmp_ctx, priv2ld(ldap_state), entry);
+ if (!dn) {
+ DEBUG(0,("ldapsam_change_groupmem: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbldap_set_mod(&mods, modop, "memberUid", uidstr);
+
+ smbldap_talloc_autofree_ldapmod(tmp_ctx, mods);
+
+ rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);
+ if (rc != LDAP_SUCCESS) {
+ if (rc == LDAP_TYPE_OR_VALUE_EXISTS && modop == LDAP_MOD_ADD) {
+ DEBUG(1,("ldapsam_change_groupmem: member is already in group, add failed!\n"));
+ return NT_STATUS_MEMBER_IN_GROUP;
+ }
+ if (rc == LDAP_NO_SUCH_ATTRIBUTE && modop == LDAP_MOD_DELETE) {
+ DEBUG(1,("ldapsam_change_groupmem: member is not in group, delete failed!\n"));
+ return NT_STATUS_MEMBER_NOT_IN_GROUP;
+ }
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ldapsam_add_groupmem(struct pdb_methods *my_methods,
+ TALLOC_CTX *tmp_ctx,
+ uint32_t group_rid,
+ uint32_t member_rid)
+{
+ return ldapsam_change_groupmem(my_methods, tmp_ctx, group_rid, member_rid, LDAP_MOD_ADD);
+}
+static NTSTATUS ldapsam_del_groupmem(struct pdb_methods *my_methods,
+ TALLOC_CTX *tmp_ctx,
+ uint32_t group_rid,
+ uint32_t member_rid)
+{
+ return ldapsam_change_groupmem(my_methods, tmp_ctx, group_rid, member_rid, LDAP_MOD_DELETE);
+}
+
+static NTSTATUS ldapsam_set_primary_group(struct pdb_methods *my_methods,
+ TALLOC_CTX *mem_ctx,
+ struct samu *sampass)
+{
+ struct ldapsam_privates *ldap_state = (struct ldapsam_privates *)my_methods->private_data;
+ LDAPMessage *entry = NULL;
+ LDAPMessage *result = NULL;
+ uint32_t num_result;
+ LDAPMod **mods = NULL;
+ char *filter;
+ char *escape_username;
+ char *gidstr;
+ char *dn = NULL;
+ gid_t gid;
+ int rc;
+
+ DEBUG(0,("ldapsam_set_primary_group: Attempt to set primary group for user [%s]\n", pdb_get_username(sampass)));
+
+ if (!sid_to_gid(pdb_get_group_sid(sampass), &gid)) {
+ DEBUG(0,("ldapsam_set_primary_group: failed to retrieve gid from user's group SID!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ gidstr = talloc_asprintf(mem_ctx, "%u", (unsigned int)gid);
+ if (!gidstr) {
+ DEBUG(0,("ldapsam_set_primary_group: Out of Memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ escape_username = escape_ldap_string(talloc_tos(),
+ pdb_get_username(sampass));
+ if (escape_username== NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ filter = talloc_asprintf(mem_ctx,
+ "(&(uid=%s)"
+ "(objectClass="LDAP_OBJ_POSIXACCOUNT")"
+ "(objectClass="LDAP_OBJ_SAMBASAMACCOUNT"))",
+ escape_username);
+
+ TALLOC_FREE(escape_username);
+
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rc = smbldap_search_suffix(ldap_state->smbldap_state, filter, NULL, &result);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("ldapsam_set_primary_group: user search failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result == 0) {
+ DEBUG(0,("ldapsam_set_primary_group: user not found!\n"));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (num_result > 1) {
+ DEBUG (0, ("ldapsam_set_primary_group: More than one user with name [%s] ?!\n", pdb_get_username(sampass)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ entry = ldap_first_entry(priv2ld(ldap_state), result);
+ if (!entry) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* retrieve the dn for later use */
+ dn = smbldap_talloc_dn(mem_ctx, priv2ld(ldap_state), entry);
+ if (!dn) {
+ DEBUG(0,("ldapsam_set_primary_group: Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* remove the old one, and add the new one, this way we do not risk races */
+ smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "gidNumber", gidstr);
+
+ if (mods == NULL) {
+ TALLOC_FREE(dn);
+ return NT_STATUS_OK;
+ }
+
+ rc = smbldap_modify(ldap_state->smbldap_state, dn, mods);
+ TALLOC_FREE(dn);
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("ldapsam_set_primary_group: failed to modify [%s] primary group to [%s]\n",
+ pdb_get_username(sampass), gidstr));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ flush_pwnam_cache();
+
+ return NT_STATUS_OK;
+}
+
+
+/**********************************************************************
+ trusted domains functions
+ *********************************************************************/
+
+static char *trusteddom_dn(struct ldapsam_privates *ldap_state,
+ const char *domain)
+{
+ return talloc_asprintf(talloc_tos(), "sambaDomainName=%s,%s", domain,
+ ldap_state->domain_dn);
+}
+
+static bool get_trusteddom_pw_int(struct ldapsam_privates *ldap_state,
+ TALLOC_CTX *mem_ctx,
+ const char *domain, LDAPMessage **entry)
+{
+ int rc;
+ char *filter;
+ int scope = LDAP_SCOPE_SUBTREE;
+ const char **attrs = NULL; /* NULL: get all attrs */
+ int attrsonly = 0; /* 0: return values too */
+ LDAPMessage *result = NULL;
+ char *trusted_dn;
+ uint32_t num_result;
+
+ filter = talloc_asprintf(talloc_tos(),
+ "(&(objectClass="LDAP_OBJ_TRUSTDOM_PASSWORD")(sambaDomainName=%s))",
+ domain);
+
+ trusted_dn = trusteddom_dn(ldap_state, domain);
+ if (trusted_dn == NULL) {
+ return False;
+ }
+ rc = smbldap_search(ldap_state->smbldap_state, trusted_dn, scope,
+ filter, attrs, attrsonly, &result);
+
+ if (result != NULL) {
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+ }
+
+ if (rc == LDAP_NO_SUCH_OBJECT) {
+ *entry = NULL;
+ return True;
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ return False;
+ }
+
+ num_result = ldap_count_entries(priv2ld(ldap_state), result);
+
+ if (num_result > 1) {
+ DEBUG(1, ("ldapsam_get_trusteddom_pw: more than one "
+ "%s object for domain '%s'?!\n",
+ LDAP_OBJ_TRUSTDOM_PASSWORD, domain));
+ return False;
+ }
+
+ if (num_result == 0) {
+ DEBUG(1, ("ldapsam_get_trusteddom_pw: no "
+ "%s object for domain %s.\n",
+ LDAP_OBJ_TRUSTDOM_PASSWORD, domain));
+ *entry = NULL;
+ } else {
+ *entry = ldap_first_entry(priv2ld(ldap_state), result);
+ }
+
+ return True;
+}
+
+static bool ldapsam_get_trusteddom_pw(struct pdb_methods *methods,
+ const char *domain,
+ char** pwd,
+ struct dom_sid *sid,
+ time_t *pass_last_set_time)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *entry = NULL;
+
+ DEBUG(10, ("ldapsam_get_trusteddom_pw called for domain %s\n", domain));
+
+ if (!get_trusteddom_pw_int(ldap_state, talloc_tos(), domain, &entry) ||
+ (entry == NULL))
+ {
+ return False;
+ }
+
+ /* password */
+ if (pwd != NULL) {
+ char *pwd_str;
+ pwd_str = smbldap_talloc_single_attribute(priv2ld(ldap_state),
+ entry, "sambaClearTextPassword", talloc_tos());
+ if (pwd_str == NULL) {
+ return False;
+ }
+ /* trusteddom_pw routines do not use talloc yet... */
+ *pwd = SMB_STRDUP(pwd_str);
+ if (*pwd == NULL) {
+ return False;
+ }
+ }
+
+ /* last change time */
+ if (pass_last_set_time != NULL) {
+ char *time_str;
+ time_str = smbldap_talloc_single_attribute(priv2ld(ldap_state),
+ entry, "sambaPwdLastSet", talloc_tos());
+ if (time_str == NULL) {
+ return False;
+ }
+ *pass_last_set_time = (time_t)atol(time_str);
+ }
+
+ /* domain sid */
+ if (sid != NULL) {
+ char *sid_str;
+ struct dom_sid dom_sid;
+ sid_str = smbldap_talloc_single_attribute(priv2ld(ldap_state),
+ entry, "sambaSID",
+ talloc_tos());
+ if (sid_str == NULL) {
+ return False;
+ }
+ if (!string_to_sid(&dom_sid, sid_str)) {
+ return False;
+ }
+ sid_copy(sid, &dom_sid);
+ }
+
+ return True;
+}
+
+static bool ldapsam_set_trusteddom_pw(struct pdb_methods *methods,
+ const char* domain,
+ const char* pwd,
+ const struct dom_sid *sid)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ char *prev_pwd = NULL;
+ char *trusted_dn = NULL;
+ struct dom_sid_buf buf;
+ int rc;
+
+ DEBUG(10, ("ldapsam_set_trusteddom_pw called for domain %s\n", domain));
+
+ /*
+ * get the current entry (if there is one) in order to put the
+ * current password into the previous password attribute
+ */
+ if (!get_trusteddom_pw_int(ldap_state, talloc_tos(), domain, &entry)) {
+ return False;
+ }
+
+ mods = NULL;
+ smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "objectClass",
+ LDAP_OBJ_TRUSTDOM_PASSWORD);
+ smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "sambaDomainName",
+ domain);
+ smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "sambaSID",
+ dom_sid_str_buf(sid, &buf));
+ smbldap_make_mod(priv2ld(ldap_state), entry, &mods, "sambaPwdLastSet",
+ talloc_asprintf(talloc_tos(), "%li", (long int)time(NULL)));
+ smbldap_make_mod(priv2ld(ldap_state), entry, &mods,
+ "sambaClearTextPassword", pwd);
+
+ if (entry != NULL) {
+ prev_pwd = smbldap_talloc_single_attribute(priv2ld(ldap_state),
+ entry, "sambaClearTextPassword", talloc_tos());
+ if (prev_pwd != NULL) {
+ smbldap_make_mod(priv2ld(ldap_state), entry, &mods,
+ "sambaPreviousClearTextPassword",
+ prev_pwd);
+ }
+ }
+
+ smbldap_talloc_autofree_ldapmod(talloc_tos(), mods);
+
+ trusted_dn = trusteddom_dn(ldap_state, domain);
+ if (trusted_dn == NULL) {
+ return False;
+ }
+ if (entry == NULL) {
+ rc = smbldap_add(ldap_state->smbldap_state, trusted_dn, mods);
+ } else {
+ rc = smbldap_modify(ldap_state->smbldap_state, trusted_dn, mods);
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1, ("error writing trusted domain password!\n"));
+ return False;
+ }
+
+ return True;
+}
+
+static bool ldapsam_del_trusteddom_pw(struct pdb_methods *methods,
+ const char *domain)
+{
+ int rc;
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ LDAPMessage *entry = NULL;
+ const char *trusted_dn;
+
+ if (!get_trusteddom_pw_int(ldap_state, talloc_tos(), domain, &entry)) {
+ return False;
+ }
+
+ if (entry == NULL) {
+ DEBUG(5, ("ldapsam_del_trusteddom_pw: no such trusted domain: "
+ "%s\n", domain));
+ return True;
+ }
+
+ trusted_dn = smbldap_talloc_dn(talloc_tos(), priv2ld(ldap_state),
+ entry);
+ if (trusted_dn == NULL) {
+ DEBUG(0,("ldapsam_del_trusteddom_pw: Out of memory!\n"));
+ return False;
+ }
+
+ rc = smbldap_delete(ldap_state->smbldap_state, trusted_dn);
+ if (rc != LDAP_SUCCESS) {
+ return False;
+ }
+
+ return True;
+}
+
+static NTSTATUS ldapsam_enum_trusteddoms(struct pdb_methods *methods,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_domains,
+ struct trustdom_info ***domains)
+{
+ int rc;
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)methods->private_data;
+ const char *filter;
+ int scope = LDAP_SCOPE_SUBTREE;
+ const char *attrs[] = { "sambaDomainName", "sambaSID", NULL };
+ int attrsonly = 0; /* 0: return values too */
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+
+ filter = "(objectClass="LDAP_OBJ_TRUSTDOM_PASSWORD")";
+
+ rc = smbldap_search(ldap_state->smbldap_state,
+ ldap_state->domain_dn,
+ scope,
+ filter,
+ attrs,
+ attrsonly,
+ &result);
+
+ if (result != NULL) {
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ *num_domains = 0;
+ if (!(*domains = talloc_array(mem_ctx, struct trustdom_info *, 1))) {
+ DEBUG(1, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (entry = ldap_first_entry(priv2ld(ldap_state), result);
+ entry != NULL;
+ entry = ldap_next_entry(priv2ld(ldap_state), entry))
+ {
+ char *dom_name, *dom_sid_str;
+ struct trustdom_info *dom_info;
+
+ dom_info = talloc(*domains, struct trustdom_info);
+ if (dom_info == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dom_name = smbldap_talloc_single_attribute(priv2ld(ldap_state),
+ entry,
+ "sambaDomainName",
+ talloc_tos());
+ if (dom_name == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ dom_info->name = dom_name;
+
+ dom_sid_str = smbldap_talloc_single_attribute(
+ priv2ld(ldap_state), entry, "sambaSID",
+ talloc_tos());
+ if (dom_sid_str == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!string_to_sid(&dom_info->sid, dom_sid_str)) {
+ DEBUG(1, ("Error calling string_to_sid on SID %s\n",
+ dom_sid_str));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ADD_TO_ARRAY(*domains, struct trustdom_info *, dom_info,
+ domains, num_domains);
+
+ if (*domains == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ DEBUG(5, ("ldapsam_enum_trusteddoms: got %d domains\n", *num_domains));
+ return NT_STATUS_OK;
+}
+
+
+/**********************************************************************
+ Housekeeping
+ *********************************************************************/
+
+static void free_private_data(void **vp)
+{
+ struct ldapsam_privates **ldap_state = (struct ldapsam_privates **)vp;
+
+ smbldap_free_struct(&(*ldap_state)->smbldap_state);
+
+ if ((*ldap_state)->result != NULL) {
+ ldap_msgfree((*ldap_state)->result);
+ (*ldap_state)->result = NULL;
+ }
+ if ((*ldap_state)->domain_dn != NULL) {
+ SAFE_FREE((*ldap_state)->domain_dn);
+ }
+
+ *ldap_state = NULL;
+
+ /* No need to free any further, as it is talloc()ed */
+}
+
+/*********************************************************************
+ Initialise the parts of the pdb_methods structure that are common to
+ all pdb_ldap modes
+*********************************************************************/
+
+static NTSTATUS pdb_init_ldapsam_common(struct pdb_methods **pdb_method, const char *location)
+{
+ NTSTATUS nt_status;
+ struct ldapsam_privates *ldap_state;
+ char *bind_dn = NULL;
+ char *bind_secret = NULL;
+
+ if (!NT_STATUS_IS_OK(nt_status = make_pdb_method( pdb_method ))) {
+ return nt_status;
+ }
+
+ (*pdb_method)->name = "ldapsam";
+
+ (*pdb_method)->getsampwnam = ldapsam_getsampwnam;
+ (*pdb_method)->getsampwsid = ldapsam_getsampwsid;
+ (*pdb_method)->add_sam_account = ldapsam_add_sam_account;
+ (*pdb_method)->update_sam_account = ldapsam_update_sam_account;
+ (*pdb_method)->delete_sam_account = ldapsam_delete_sam_account;
+ (*pdb_method)->rename_sam_account = ldapsam_rename_sam_account;
+
+ (*pdb_method)->getgrsid = ldapsam_getgrsid;
+ (*pdb_method)->getgrgid = ldapsam_getgrgid;
+ (*pdb_method)->getgrnam = ldapsam_getgrnam;
+ (*pdb_method)->add_group_mapping_entry = ldapsam_add_group_mapping_entry;
+ (*pdb_method)->update_group_mapping_entry = ldapsam_update_group_mapping_entry;
+ (*pdb_method)->delete_group_mapping_entry = ldapsam_delete_group_mapping_entry;
+ (*pdb_method)->enum_group_mapping = ldapsam_enum_group_mapping;
+
+ (*pdb_method)->get_account_policy = ldapsam_get_account_policy;
+ (*pdb_method)->set_account_policy = ldapsam_set_account_policy;
+
+ (*pdb_method)->get_seq_num = ldapsam_get_seq_num;
+
+ (*pdb_method)->capabilities = ldapsam_capabilities;
+ (*pdb_method)->new_rid = ldapsam_new_rid;
+
+ (*pdb_method)->get_trusteddom_pw = ldapsam_get_trusteddom_pw;
+ (*pdb_method)->set_trusteddom_pw = ldapsam_set_trusteddom_pw;
+ (*pdb_method)->del_trusteddom_pw = ldapsam_del_trusteddom_pw;
+ (*pdb_method)->enum_trusteddoms = ldapsam_enum_trusteddoms;
+
+ /* TODO: Setup private data and free */
+
+ if ( !(ldap_state = talloc_zero(*pdb_method, struct ldapsam_privates)) ) {
+ DEBUG(0, ("pdb_init_ldapsam_common: talloc() failed for ldapsam private_data!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!fetch_ldap_pw(&bind_dn, &bind_secret)) {
+ DEBUG(0, ("pdb_init_ldapsam_common: Failed to retrieve LDAP password from secrets.tdb\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = smbldap_init(*pdb_method, pdb_get_tevent_context(),
+ location, false, bind_dn, bind_secret,
+ &ldap_state->smbldap_state);
+ BURN_FREE_STR(bind_secret);
+ SAFE_FREE(bind_dn);
+ if ( !NT_STATUS_IS_OK(nt_status) ) {
+ return nt_status;
+ }
+
+ if ( !(ldap_state->domain_name = talloc_strdup(*pdb_method, get_global_sam_name()) ) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*pdb_method)->private_data = ldap_state;
+
+ (*pdb_method)->free_private_data = free_private_data;
+
+ return NT_STATUS_OK;
+}
+
+static bool ldapsam_is_responsible_for_wellknown(struct pdb_methods *m)
+{
+ return true;
+}
+
+/**********************************************************************
+ Initialise the normal mode for pdb_ldap
+ *********************************************************************/
+
+NTSTATUS pdb_ldapsam_init_common(struct pdb_methods **pdb_method,
+ const char *location)
+{
+ NTSTATUS nt_status;
+ struct ldapsam_privates *ldap_state = NULL;
+ uint32_t alg_rid_base;
+ char *alg_rid_base_string = NULL;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ struct dom_sid ldap_domain_sid;
+ struct dom_sid secrets_domain_sid;
+ char *domain_sid_string = NULL;
+ char *dn = NULL;
+ char *uri = talloc_strdup( NULL, location );
+
+ trim_char( uri, '\"', '\"' );
+ nt_status = pdb_init_ldapsam_common(pdb_method, uri);
+
+ TALLOC_FREE(uri);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ (*pdb_method)->name = "ldapsam";
+
+ (*pdb_method)->add_aliasmem = ldapsam_add_aliasmem;
+ (*pdb_method)->del_aliasmem = ldapsam_del_aliasmem;
+ (*pdb_method)->enum_aliasmem = ldapsam_enum_aliasmem;
+ (*pdb_method)->enum_alias_memberships = ldapsam_alias_memberships;
+ (*pdb_method)->search_users = ldapsam_search_users;
+ (*pdb_method)->search_groups = ldapsam_search_groups;
+ (*pdb_method)->search_aliases = ldapsam_search_aliases;
+ (*pdb_method)->is_responsible_for_wellknown =
+ ldapsam_is_responsible_for_wellknown;
+
+ if (lp_parm_bool(-1, "ldapsam", "trusted", False)) {
+ (*pdb_method)->enum_group_members = ldapsam_enum_group_members;
+ (*pdb_method)->enum_group_memberships =
+ ldapsam_enum_group_memberships;
+ (*pdb_method)->lookup_rids = ldapsam_lookup_rids;
+ (*pdb_method)->sid_to_id = ldapsam_sid_to_id;
+ (*pdb_method)->id_to_sid = ldapsam_id_to_sid;
+
+ if (lp_parm_bool(-1, "ldapsam", "editposix", False)) {
+ (*pdb_method)->create_user = ldapsam_create_user;
+ (*pdb_method)->delete_user = ldapsam_delete_user;
+ (*pdb_method)->create_dom_group = ldapsam_create_dom_group;
+ (*pdb_method)->delete_dom_group = ldapsam_delete_dom_group;
+ (*pdb_method)->add_groupmem = ldapsam_add_groupmem;
+ (*pdb_method)->del_groupmem = ldapsam_del_groupmem;
+ (*pdb_method)->set_unix_primary_group = ldapsam_set_primary_group;
+ }
+ }
+
+ ldap_state = (struct ldapsam_privates *)((*pdb_method)->private_data);
+ ldap_state->schema_ver = SCHEMAVER_SAMBASAMACCOUNT;
+
+ /* Try to setup the Domain Name, Domain SID, algorithmic rid base */
+
+ nt_status = smbldap_search_domain_info(ldap_state->smbldap_state,
+ &result,
+ ldap_state->domain_name, True);
+
+ if ( !NT_STATUS_IS_OK(nt_status) ) {
+ DEBUG(0, ("pdb_init_ldapsam: WARNING: Could not get domain "
+ "info, nor add one to the domain. "
+ "We cannot work reliably without it.\n"));
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ /* Given that the above might fail, everything below this must be
+ * optional */
+
+ entry = ldap_first_entry(smbldap_get_ldap(ldap_state->smbldap_state),
+ result);
+ if (!entry) {
+ DEBUG(0, ("pdb_init_ldapsam: Could not get domain info "
+ "entry\n"));
+ ldap_msgfree(result);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dn = smbldap_talloc_dn(talloc_tos(),
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry);
+ if (!dn) {
+ ldap_msgfree(result);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ldap_state->domain_dn = smb_xstrdup(dn);
+ TALLOC_FREE(dn);
+
+ domain_sid_string = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_userattr_key2string(ldap_state->schema_ver,
+ LDAP_ATTR_USER_SID),
+ talloc_tos());
+
+ if (domain_sid_string) {
+ bool found_sid;
+ if (!string_to_sid(&ldap_domain_sid, domain_sid_string)) {
+ DEBUG(1, ("pdb_init_ldapsam: SID [%s] could not be "
+ "read as a valid SID\n", domain_sid_string));
+ ldap_msgfree(result);
+ TALLOC_FREE(domain_sid_string);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ found_sid = PDB_secrets_fetch_domain_sid(ldap_state->domain_name,
+ &secrets_domain_sid);
+ if (!found_sid || !dom_sid_equal(&secrets_domain_sid,
+ &ldap_domain_sid)) {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(1, ("pdb_init_ldapsam: Resetting SID for domain "
+ "%s based on pdb_ldap results %s -> %s\n",
+ ldap_state->domain_name,
+ dom_sid_str_buf(&secrets_domain_sid, &buf1),
+ dom_sid_str_buf(&ldap_domain_sid, &buf2)));
+
+ /* reset secrets.tdb sid */
+ PDB_secrets_store_domain_sid(ldap_state->domain_name,
+ &ldap_domain_sid);
+ DEBUG(1, ("New global sam SID: %s\n",
+ dom_sid_str_buf(get_global_sam_sid(),
+ &buf1)));
+ }
+ sid_copy(&ldap_state->domain_sid, &ldap_domain_sid);
+ TALLOC_FREE(domain_sid_string);
+ }
+
+ alg_rid_base_string = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ entry,
+ get_attr_key2string( dominfo_attr_list,
+ LDAP_ATTR_ALGORITHMIC_RID_BASE ),
+ talloc_tos());
+ if (alg_rid_base_string) {
+ alg_rid_base = (uint32_t)atol(alg_rid_base_string);
+ if (alg_rid_base != algorithmic_rid_base()) {
+ DEBUG(0, ("The value of 'algorithmic RID base' has "
+ "changed since the LDAP\n"
+ "database was initialised. Aborting. \n"));
+ ldap_msgfree(result);
+ TALLOC_FREE(alg_rid_base_string);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ TALLOC_FREE(alg_rid_base_string);
+ }
+ ldap_msgfree(result);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pdb_ldapsam_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS nt_status;
+
+ nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION,
+ "ldapsam",
+ pdb_ldapsam_init_common);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ /* Let pdb_nds register backends */
+ pdb_nds_init(ctx);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/passdb/pdb_ldap.h b/source3/passdb/pdb_ldap.h
new file mode 100644
index 0000000..755574a
--- /dev/null
+++ b/source3/passdb/pdb_ldap.h
@@ -0,0 +1,71 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Jean François Micouleau 1998
+ Copyright (C) Gerald Carter 2001-2003
+ Copyright (C) Shahms King 2001
+ Copyright (C) Andrew Bartlett 2002-2003
+ Copyright (C) Stefan (metze) Metzmacher 2002-2003
+ Copyright (C) Simo Sorce 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _PASSDB_PDB_LDAP_H_
+#define _PASSDB_PDB_LDAP_H_
+
+/* struct used by both pdb_ldap.c and pdb_nds.c */
+
+struct ldapsam_privates {
+ struct smbldap_state *smbldap_state;
+
+ /* Former statistics */
+ LDAPMessage *result;
+ LDAPMessage *entry;
+ int index;
+
+ const char *domain_name;
+ struct dom_sid domain_sid;
+
+ /* configuration items */
+ int schema_ver;
+
+ char *domain_dn;
+
+ /* Is this NDS ldap? */
+ int is_nds_ldap;
+
+ /* ldap server location parameter */
+ char *location;
+
+ struct {
+ char *filter;
+ LDAPMessage *result;
+ } search_cache;
+};
+
+/* The following definitions come from passdb/pdb_ldap.c */
+
+const char** get_userattr_list( TALLOC_CTX *mem_ctx, int schema_ver );
+NTSTATUS pdb_ldapsam_init_common(struct pdb_methods **pdb_method, const char *location);
+NTSTATUS pdb_ldapsam_init(TALLOC_CTX *);
+int ldapsam_search_suffix_by_name(struct ldapsam_privates *ldap_state,
+ const char *user,
+ LDAPMessage ** result,
+ const char **attr);
+const char** get_userattr_list( TALLOC_CTX *mem_ctx, int schema_ver );
+LDAP *priv2ld(struct ldapsam_privates *priv);
+
+#endif /* _PASSDB_PDB_LDAP_H_ */
diff --git a/source3/passdb/pdb_ldap_schema.c b/source3/passdb/pdb_ldap_schema.c
new file mode 100644
index 0000000..da738d5
--- /dev/null
+++ b/source3/passdb/pdb_ldap_schema.c
@@ -0,0 +1,191 @@
+/*
+ Unix SMB/CIFS implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Jean François Micouleau 1998
+ Copyright (C) Gerald Carter 2001-2003
+ Copyright (C) Shahms King 2001
+ Copyright (C) Andrew Bartlett 2002-2003
+ Copyright (C) Stefan (metze) Metzmacher 2002-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "passdb/pdb_ldap_schema.h"
+
+/* attributes used by Samba 3.0's sambaSamAccount */
+
+ATTRIB_MAP_ENTRY attrib_map_v30[] = {
+ { LDAP_ATTR_UID, "uid" },
+ { LDAP_ATTR_UIDNUMBER, LDAP_ATTRIBUTE_UIDNUMBER},
+ { LDAP_ATTR_GIDNUMBER, LDAP_ATTRIBUTE_GIDNUMBER},
+ { LDAP_ATTR_UNIX_HOME, "homeDirectory" },
+ { LDAP_ATTR_PWD_LAST_SET, "sambaPwdLastSet" },
+ { LDAP_ATTR_PWD_CAN_CHANGE, "sambaPwdCanChange" },
+ { LDAP_ATTR_PWD_MUST_CHANGE, "sambaPwdMustChange" },
+ { LDAP_ATTR_LOGON_TIME, "sambaLogonTime" },
+ { LDAP_ATTR_LOGOFF_TIME, "sambaLogoffTime" },
+ { LDAP_ATTR_KICKOFF_TIME, "sambaKickoffTime" },
+ { LDAP_ATTR_CN, "cn" },
+ { LDAP_ATTR_SN, "sn" },
+ { LDAP_ATTR_DISPLAY_NAME, "displayName" },
+ { LDAP_ATTR_HOME_DRIVE, "sambaHomeDrive" },
+ { LDAP_ATTR_HOME_PATH, "sambaHomePath" },
+ { LDAP_ATTR_LOGON_SCRIPT, "sambaLogonScript" },
+ { LDAP_ATTR_PROFILE_PATH, "sambaProfilePath" },
+ { LDAP_ATTR_DESC, "description" },
+ { LDAP_ATTR_USER_WKS, "sambaUserWorkstations" },
+ { LDAP_ATTR_USER_SID, LDAP_ATTRIBUTE_SID },
+ { LDAP_ATTR_PRIMARY_GROUP_SID, "sambaPrimaryGroupSID" },
+ { LDAP_ATTR_LMPW, "sambaLMPassword" },
+ { LDAP_ATTR_NTPW, "sambaNTPassword" },
+ { LDAP_ATTR_DOMAIN, "sambaDomainName" },
+ { LDAP_ATTR_OBJCLASS, "objectClass" },
+ { LDAP_ATTR_ACB_INFO, "sambaAcctFlags" },
+ { LDAP_ATTR_MUNGED_DIAL, "sambaMungedDial" },
+ { LDAP_ATTR_BAD_PASSWORD_COUNT, "sambaBadPasswordCount" },
+ { LDAP_ATTR_BAD_PASSWORD_TIME, "sambaBadPasswordTime" },
+ { LDAP_ATTR_PWD_HISTORY, "sambaPasswordHistory" },
+ { LDAP_ATTR_MOD_TIMESTAMP, "modifyTimestamp" },
+ { LDAP_ATTR_LOGON_HOURS, "sambaLogonHours" },
+ { LDAP_ATTR_LIST_END, NULL }
+};
+
+ATTRIB_MAP_ENTRY attrib_map_to_delete_v30[] = {
+ { LDAP_ATTR_PWD_LAST_SET, "sambaPwdLastSet" },
+ { LDAP_ATTR_PWD_CAN_CHANGE, "sambaPwdCanChange" },
+ { LDAP_ATTR_PWD_MUST_CHANGE, "sambaPwdMustChange" },
+ { LDAP_ATTR_LOGON_TIME, "sambaLogonTime" },
+ { LDAP_ATTR_LOGOFF_TIME, "sambaLogoffTime" },
+ { LDAP_ATTR_KICKOFF_TIME, "sambaKickoffTime" },
+ { LDAP_ATTR_DISPLAY_NAME, "displayName" },
+ { LDAP_ATTR_HOME_DRIVE, "sambaHomeDrive" },
+ { LDAP_ATTR_HOME_PATH, "sambaHomePath" },
+ { LDAP_ATTR_LOGON_SCRIPT, "sambaLogonScript" },
+ { LDAP_ATTR_PROFILE_PATH, "sambaProfilePath" },
+ { LDAP_ATTR_USER_WKS, "sambaUserWorkstations" },
+ { LDAP_ATTR_USER_SID, LDAP_ATTRIBUTE_SID },
+ { LDAP_ATTR_PRIMARY_GROUP_SID, "sambaPrimaryGroupSID" },
+ { LDAP_ATTR_LMPW, "sambaLMPassword" },
+ { LDAP_ATTR_NTPW, "sambaNTPassword" },
+ { LDAP_ATTR_DOMAIN, "sambaDomainName" },
+ { LDAP_ATTR_ACB_INFO, "sambaAcctFlags" },
+ { LDAP_ATTR_MUNGED_DIAL, "sambaMungedDial" },
+ { LDAP_ATTR_BAD_PASSWORD_COUNT, "sambaBadPasswordCount" },
+ { LDAP_ATTR_BAD_PASSWORD_TIME, "sambaBadPasswordTime" },
+ { LDAP_ATTR_PWD_HISTORY, "sambaPasswordHistory" },
+ { LDAP_ATTR_LOGON_HOURS, "sambaLogonHours" },
+ { LDAP_ATTR_LIST_END, NULL }
+};
+
+/* attributes used for allocating RIDs */
+
+ATTRIB_MAP_ENTRY dominfo_attr_list[] = {
+ { LDAP_ATTR_DOMAIN, "sambaDomainName" },
+ { LDAP_ATTR_NEXT_RID, "sambaNextRid" },
+ { LDAP_ATTR_NEXT_USERRID, "sambaNextUserRid" },
+ { LDAP_ATTR_NEXT_GROUPRID, "sambaNextGroupRid" },
+ { LDAP_ATTR_DOM_SID, LDAP_ATTRIBUTE_SID },
+ { LDAP_ATTR_ALGORITHMIC_RID_BASE,"sambaAlgorithmicRidBase"},
+ { LDAP_ATTR_OBJCLASS, "objectClass" },
+ { LDAP_ATTR_LIST_END, NULL },
+};
+
+/* Samba 3.0 group mapping attributes */
+
+ATTRIB_MAP_ENTRY groupmap_attr_list[] = {
+ { LDAP_ATTR_GIDNUMBER, LDAP_ATTRIBUTE_GIDNUMBER},
+ { LDAP_ATTR_GROUP_SID, LDAP_ATTRIBUTE_SID },
+ { LDAP_ATTR_GROUP_TYPE, "sambaGroupType" },
+ { LDAP_ATTR_SID_LIST, "sambaSIDList" },
+ { LDAP_ATTR_DESC, "description" },
+ { LDAP_ATTR_DISPLAY_NAME, "displayName" },
+ { LDAP_ATTR_CN, "cn" },
+ { LDAP_ATTR_OBJCLASS, "objectClass" },
+ { LDAP_ATTR_LIST_END, NULL }
+};
+
+ATTRIB_MAP_ENTRY groupmap_attr_list_to_delete[] = {
+ { LDAP_ATTR_GROUP_SID, LDAP_ATTRIBUTE_SID },
+ { LDAP_ATTR_GROUP_TYPE, "sambaGroupType" },
+ { LDAP_ATTR_DESC, "description" },
+ { LDAP_ATTR_DISPLAY_NAME, "displayName" },
+ { LDAP_ATTR_SID_LIST, "sambaSIDList" },
+ { LDAP_ATTR_LIST_END, NULL }
+};
+
+/* idmap_ldap sambaUnixIdPool */
+
+ATTRIB_MAP_ENTRY idpool_attr_list[] = {
+ { LDAP_ATTR_UIDNUMBER, LDAP_ATTRIBUTE_UIDNUMBER},
+ { LDAP_ATTR_GIDNUMBER, LDAP_ATTRIBUTE_GIDNUMBER},
+ { LDAP_ATTR_OBJCLASS, "objectClass" },
+ { LDAP_ATTR_LIST_END, NULL }
+};
+
+ATTRIB_MAP_ENTRY sidmap_attr_list[] = {
+ { LDAP_ATTR_SID, LDAP_ATTRIBUTE_SID },
+ { LDAP_ATTR_UIDNUMBER, LDAP_ATTRIBUTE_UIDNUMBER},
+ { LDAP_ATTR_GIDNUMBER, LDAP_ATTRIBUTE_GIDNUMBER},
+ { LDAP_ATTR_OBJCLASS, "objectClass" },
+ { LDAP_ATTR_LIST_END, NULL }
+};
+
+/**********************************************************************
+ perform a simple table lookup and return the attribute name
+ **********************************************************************/
+
+ const char* get_attr_key2string( ATTRIB_MAP_ENTRY table[], int key )
+{
+ int i = 0;
+
+ while ( table[i].attrib != LDAP_ATTR_LIST_END ) {
+ if ( table[i].attrib == key )
+ return table[i].name;
+ i++;
+ }
+
+ return NULL;
+}
+
+
+/**********************************************************************
+ Return the list of attribute names from a mapping table
+ **********************************************************************/
+
+ const char** get_attr_list( TALLOC_CTX *mem_ctx, ATTRIB_MAP_ENTRY table[] )
+{
+ const char **names;
+ int i = 0;
+
+ while ( table[i].attrib != LDAP_ATTR_LIST_END )
+ i++;
+ i++;
+
+ names = talloc_array( mem_ctx, const char*, i );
+ if ( !names ) {
+ DEBUG(0,("get_attr_list: out of memory\n"));
+ return NULL;
+ }
+
+ i = 0;
+ while ( table[i].attrib != LDAP_ATTR_LIST_END ) {
+ names[i] = talloc_strdup( names, table[i].name );
+ i++;
+ }
+ names[i] = NULL;
+
+ return names;
+}
diff --git a/source3/passdb/pdb_ldap_schema.h b/source3/passdb/pdb_ldap_schema.h
new file mode 100644
index 0000000..ea98db2
--- /dev/null
+++ b/source3/passdb/pdb_ldap_schema.h
@@ -0,0 +1,124 @@
+/*
+ Unix SMB/CIFS Implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Gerald Carter 2001-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _PASSDB_PDB_LDAP_SCHEMA_H_
+#define _PASSDB_PDB_LDAP_SCHEMA_H_
+
+/* Schema versions */
+#define SCHEMAVER_SAMBAACCOUNT 1 /* Samba 2.2 */
+#define SCHEMAVER_SAMBASAMACCOUNT 2 /* Samba 3.0 */
+
+/* objectclass names */
+
+#define LDAP_OBJ_SAMBASAMACCOUNT "sambaSamAccount"
+#define LDAP_OBJ_GROUPMAP "sambaGroupMapping"
+#define LDAP_OBJ_DOMINFO "sambaDomain"
+#define LDAP_OBJ_IDPOOL "sambaUnixIdPool"
+#define LDAP_OBJ_IDMAP_ENTRY "sambaIdmapEntry"
+#define LDAP_OBJ_SID_ENTRY "sambaSidEntry"
+#define LDAP_OBJ_TRUST_PASSWORD "sambaTrustPassword"
+#define LDAP_OBJ_TRUSTDOM_PASSWORD "sambaTrustedDomainPassword"
+#define LDAP_OBJ_TRUSTED_DOMAIN "sambaTrustedDomain"
+
+#define LDAP_OBJ_ACCOUNT "account"
+#define LDAP_OBJ_POSIXACCOUNT "posixAccount"
+#define LDAP_OBJ_POSIXGROUP "posixGroup"
+#define LDAP_OBJ_OU "organizationalUnit"
+
+/* some generic attributes that get reused a lot */
+
+#define LDAP_ATTRIBUTE_SID "sambaSID"
+#define LDAP_ATTRIBUTE_UIDNUMBER "uidNumber"
+#define LDAP_ATTRIBUTE_GIDNUMBER "gidNumber"
+#define LDAP_ATTRIBUTE_SID_LIST "sambaSIDList"
+
+/* attribute map table indexes */
+
+#define LDAP_ATTR_LIST_END 0
+#define LDAP_ATTR_UID 1
+#define LDAP_ATTR_UIDNUMBER 2
+#define LDAP_ATTR_GIDNUMBER 3
+#define LDAP_ATTR_UNIX_HOME 4
+#define LDAP_ATTR_PWD_LAST_SET 5
+#define LDAP_ATTR_PWD_CAN_CHANGE 6
+#define LDAP_ATTR_PWD_MUST_CHANGE 7
+#define LDAP_ATTR_LOGON_TIME 8
+#define LDAP_ATTR_LOGOFF_TIME 9
+#define LDAP_ATTR_KICKOFF_TIME 10
+#define LDAP_ATTR_CN 11
+#define LDAP_ATTR_DISPLAY_NAME 12
+#define LDAP_ATTR_HOME_PATH 13
+#define LDAP_ATTR_LOGON_SCRIPT 14
+#define LDAP_ATTR_PROFILE_PATH 15
+#define LDAP_ATTR_DESC 16
+#define LDAP_ATTR_USER_WKS 17
+#define LDAP_ATTR_USER_SID 18
+#define LDAP_ATTR_USER_RID 18
+#define LDAP_ATTR_PRIMARY_GROUP_SID 19
+#define LDAP_ATTR_PRIMARY_GROUP_RID 20
+#define LDAP_ATTR_LMPW 21
+#define LDAP_ATTR_NTPW 22
+#define LDAP_ATTR_DOMAIN 23
+#define LDAP_ATTR_OBJCLASS 24
+#define LDAP_ATTR_ACB_INFO 25
+#define LDAP_ATTR_NEXT_USERRID 26
+#define LDAP_ATTR_NEXT_GROUPRID 27
+#define LDAP_ATTR_DOM_SID 28
+#define LDAP_ATTR_HOME_DRIVE 29
+#define LDAP_ATTR_GROUP_SID 30
+#define LDAP_ATTR_GROUP_TYPE 31
+#define LDAP_ATTR_SID 32
+#define LDAP_ATTR_ALGORITHMIC_RID_BASE 33
+#define LDAP_ATTR_NEXT_RID 34
+#define LDAP_ATTR_BAD_PASSWORD_COUNT 35
+#define LDAP_ATTR_LOGON_COUNT 36
+#define LDAP_ATTR_MUNGED_DIAL 37
+#define LDAP_ATTR_BAD_PASSWORD_TIME 38
+#define LDAP_ATTR_PWD_HISTORY 39
+#define LDAP_ATTR_SID_LIST 40
+#define LDAP_ATTR_MOD_TIMESTAMP 41
+#define LDAP_ATTR_LOGON_HOURS 42
+#define LDAP_ATTR_TRUST_PASSWD_FLAGS 43
+#define LDAP_ATTR_SN 44
+
+
+typedef struct _attrib_map_entry {
+ int attrib;
+ const char *name;
+} ATTRIB_MAP_ENTRY;
+
+
+/* structures */
+
+extern ATTRIB_MAP_ENTRY attrib_map_v30[];
+extern ATTRIB_MAP_ENTRY attrib_map_to_delete_v30[];
+extern ATTRIB_MAP_ENTRY dominfo_attr_list[];
+extern ATTRIB_MAP_ENTRY groupmap_attr_list[];
+extern ATTRIB_MAP_ENTRY groupmap_attr_list_to_delete[];
+extern ATTRIB_MAP_ENTRY idpool_attr_list[];
+extern ATTRIB_MAP_ENTRY sidmap_attr_list[];
+extern ATTRIB_MAP_ENTRY trustpw_attr_list[];
+
+/* The following definitions come from passdb/pdb_ldap_schema.c */
+
+const char* get_attr_key2string( ATTRIB_MAP_ENTRY table[], int key );
+const char** get_attr_list( TALLOC_CTX *mem_ctx, ATTRIB_MAP_ENTRY table[] );
+
+#endif /* _PASSDB_PDB_LDAP_SCHEMA_H_ */
diff --git a/source3/passdb/pdb_ldap_util.c b/source3/passdb/pdb_ldap_util.c
new file mode 100644
index 0000000..0c1708d
--- /dev/null
+++ b/source3/passdb/pdb_ldap_util.c
@@ -0,0 +1,338 @@
+/*
+ Unix SMB/CIFS Implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Jean François Micouleau 1998
+ Copyright (C) Gerald Carter 2001-2003
+ Copyright (C) Shahms King 2001
+ Copyright (C) Andrew Bartlett 2002-2003
+ Copyright (C) Stefan (metze) Metzmacher 2002-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "smbldap.h"
+#include "passdb.h"
+#include "passdb/pdb_ldap_util.h"
+#include "passdb/pdb_ldap_schema.h"
+#include "libcli/security/dom_sid.h"
+
+/**********************************************************************
+ Add the account-policies below the sambaDomain object to LDAP,
+*********************************************************************/
+
+static NTSTATUS add_new_domain_account_policies(struct smbldap_state *ldap_state,
+ const char *domain_name)
+{
+ NTSTATUS ntstatus = NT_STATUS_UNSUCCESSFUL;
+ int i, rc;
+ uint32_t policy_default;
+ const char *policy_attr = NULL;
+ char *dn = NULL;
+ LDAPMod **mods = NULL;
+ char *escape_domain_name;
+
+ DEBUG(3,("add_new_domain_account_policies: Adding new account policies for domain\n"));
+
+ escape_domain_name = escape_rdn_val_string_alloc(domain_name);
+ if (!escape_domain_name) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (asprintf(&dn, "%s=%s,%s",
+ get_attr_key2string(dominfo_attr_list, LDAP_ATTR_DOMAIN),
+ escape_domain_name, lp_ldap_suffix()) < 0) {
+ SAFE_FREE(escape_domain_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SAFE_FREE(escape_domain_name);
+
+ for (i=1; decode_account_policy_name(i) != NULL; i++) {
+ char *val = NULL;
+
+ policy_attr = get_account_policy_attr(i);
+ if (!policy_attr) {
+ DEBUG(0,("add_new_domain_account_policies: ops. no policy!\n"));
+ continue;
+ }
+
+ if (!account_policy_get_default(i, &policy_default)) {
+ DEBUG(0,("add_new_domain_account_policies: failed to get default account policy\n"));
+ SAFE_FREE(dn);
+ return ntstatus;
+ }
+
+ DEBUG(10,("add_new_domain_account_policies: adding \"%s\" with value: %d\n", policy_attr, policy_default));
+
+ if (asprintf(&val, "%d", policy_default) < 0) {
+ SAFE_FREE(dn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbldap_set_mod( &mods, LDAP_MOD_REPLACE, policy_attr, val);
+
+ rc = smbldap_modify(ldap_state, dn, mods);
+
+ SAFE_FREE(val);
+
+ if (rc!=LDAP_SUCCESS) {
+ char *ld_error = NULL;
+ ldap_get_option(smbldap_get_ldap(ldap_state),
+ LDAP_OPT_ERROR_STRING, &ld_error);
+ DEBUG(1,("add_new_domain_account_policies: failed to add account policies to dn= %s with: %s\n\t%s\n",
+ dn, ldap_err2string(rc),
+ ld_error ? ld_error : "unknown"));
+ SAFE_FREE(ld_error);
+ SAFE_FREE(dn);
+ ldap_mods_free(mods, True);
+ return ntstatus;
+ }
+ }
+
+ SAFE_FREE(dn);
+ ldap_mods_free(mods, True);
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Add the sambaDomain to LDAP, so we don't have to search for this stuff
+ again. This is a once-add operation for now.
+
+ TODO: Add other attributes, and allow modification.
+*********************************************************************/
+
+static NTSTATUS add_new_domain_info(struct smbldap_state *ldap_state,
+ const char *domain_name)
+{
+ struct dom_sid_buf sid_string;
+ fstring algorithmic_rid_base_string;
+ char *filter = NULL;
+ char *dn = NULL;
+ LDAPMod **mods = NULL;
+ int rc;
+ LDAPMessage *result = NULL;
+ int num_result;
+ const char **attr_list;
+ char *escape_domain_name;
+
+ /* escape for filter */
+ escape_domain_name = escape_ldap_string(talloc_tos(), domain_name);
+ if (!escape_domain_name) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (asprintf(&filter, "(&(%s=%s)(objectclass=%s))",
+ get_attr_key2string(dominfo_attr_list, LDAP_ATTR_DOMAIN),
+ escape_domain_name, LDAP_OBJ_DOMINFO) < 0) {
+ TALLOC_FREE(escape_domain_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ TALLOC_FREE(escape_domain_name);
+
+ attr_list = get_attr_list(NULL, dominfo_attr_list );
+ rc = smbldap_search_suffix(ldap_state, filter, attr_list, &result);
+ TALLOC_FREE( attr_list );
+ SAFE_FREE(filter);
+
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ num_result = ldap_count_entries(smbldap_get_ldap(ldap_state), result);
+
+ if (num_result > 1) {
+ DEBUG (0, ("add_new_domain_info: More than domain with that name exists: bailing "
+ "out!\n"));
+ ldap_msgfree(result);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* Check if we need to add an entry */
+ DEBUG(3,("add_new_domain_info: Adding new domain\n"));
+
+ /* this time escape for DN */
+ escape_domain_name = escape_rdn_val_string_alloc(domain_name);
+ if (!escape_domain_name) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (asprintf(&dn, "%s=%s,%s",
+ get_attr_key2string(dominfo_attr_list, LDAP_ATTR_DOMAIN),
+ escape_domain_name, lp_ldap_suffix()) < 0) {
+ SAFE_FREE(escape_domain_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SAFE_FREE(escape_domain_name);
+
+ /* Free original search */
+ ldap_msgfree(result);
+
+ /* make the changes - the entry *must* not already have samba
+ * attributes */
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(dominfo_attr_list,
+ LDAP_ATTR_DOMAIN),
+ domain_name);
+
+ /* If we don't have an entry, then ask secrets.tdb for what it thinks.
+ It may choose to make it up */
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(dominfo_attr_list,
+ LDAP_ATTR_DOM_SID),
+ dom_sid_str_buf(get_global_sam_sid(), &sid_string));
+
+ slprintf(algorithmic_rid_base_string,
+ sizeof(algorithmic_rid_base_string) - 1, "%i",
+ algorithmic_rid_base());
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(dominfo_attr_list,
+ LDAP_ATTR_ALGORITHMIC_RID_BASE),
+ algorithmic_rid_base_string);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectclass", LDAP_OBJ_DOMINFO);
+
+ /* add the sambaNextUserRid attributes. */
+
+ {
+ uint32_t rid = BASE_RID;
+ fstring rid_str;
+
+ fstr_sprintf( rid_str, "%i", rid );
+ DEBUG(10,("add_new_domain_info: setting next available user rid [%s]\n", rid_str));
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(dominfo_attr_list,
+ LDAP_ATTR_NEXT_USERRID),
+ rid_str);
+ }
+
+
+ rc = smbldap_add(ldap_state, dn, mods);
+
+ if (rc!=LDAP_SUCCESS) {
+ char *ld_error = NULL;
+ ldap_get_option(smbldap_get_ldap(ldap_state),
+ LDAP_OPT_ERROR_STRING, &ld_error);
+ DEBUG(1,("add_new_domain_info: failed to add domain dn= %s with: %s\n\t%s\n",
+ dn, ldap_err2string(rc),
+ ld_error?ld_error:"unknown"));
+ SAFE_FREE(ld_error);
+ SAFE_FREE(dn);
+ ldap_mods_free(mods, True);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(2,("add_new_domain_info: added: domain = %s in the LDAP database\n", domain_name));
+ ldap_mods_free(mods, True);
+ SAFE_FREE(dn);
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+Search for the domain info entry
+*********************************************************************/
+
+NTSTATUS smbldap_search_domain_info(struct smbldap_state *ldap_state,
+ LDAPMessage ** result, const char *domain_name,
+ bool try_add)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ char *filter = NULL;
+ int rc;
+ const char **attr_list;
+ int count;
+ char *escape_domain_name;
+
+ escape_domain_name = escape_ldap_string(talloc_tos(), domain_name);
+ if (!escape_domain_name) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (asprintf(&filter, "(&(objectClass=%s)(%s=%s))",
+ LDAP_OBJ_DOMINFO,
+ get_attr_key2string(dominfo_attr_list, LDAP_ATTR_DOMAIN),
+ escape_domain_name) < 0) {
+ TALLOC_FREE(escape_domain_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ TALLOC_FREE(escape_domain_name);
+
+ DEBUG(2, ("smbldap_search_domain_info: Searching for:[%s]\n", filter));
+
+ attr_list = get_attr_list( NULL, dominfo_attr_list );
+ rc = smbldap_search_suffix(ldap_state, filter, attr_list , result);
+ TALLOC_FREE( attr_list );
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(2,("smbldap_search_domain_info: Problem during LDAPsearch: %s\n", ldap_err2string (rc)));
+ DEBUG(2,("smbldap_search_domain_info: Query was: %s, %s\n", lp_ldap_suffix(), filter));
+ goto failed;
+ }
+
+ SAFE_FREE(filter);
+
+ count = ldap_count_entries(smbldap_get_ldap(ldap_state), *result);
+
+ if (count == 1) {
+ return NT_STATUS_OK;
+ }
+
+ ldap_msgfree(*result);
+ *result = NULL;
+
+ if (count < 1) {
+
+ DEBUG(3, ("smbldap_search_domain_info: Got no domain info entries for domain\n"));
+
+ if (!try_add)
+ goto failed;
+
+ status = add_new_domain_info(ldap_state, domain_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smbldap_search_domain_info: Adding domain info for %s failed with %s\n",
+ domain_name, nt_errstr(status)));
+ goto failed;
+ }
+
+ status = add_new_domain_account_policies(ldap_state, domain_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smbldap_search_domain_info: Adding domain account policies for %s failed with %s\n",
+ domain_name, nt_errstr(status)));
+ goto failed;
+ }
+
+ return smbldap_search_domain_info(ldap_state, result, domain_name, False);
+
+ }
+
+ if (count > 1 ) {
+
+ DEBUG(0, ("smbldap_search_domain_info: Got too many (%d) domain info entries for domain %s\n",
+ count, domain_name));
+ goto failed;
+ }
+
+failed:
+ return status;
+}
diff --git a/source3/passdb/pdb_ldap_util.h b/source3/passdb/pdb_ldap_util.h
new file mode 100644
index 0000000..7e8967c
--- /dev/null
+++ b/source3/passdb/pdb_ldap_util.h
@@ -0,0 +1,32 @@
+/*
+ Unix SMB/CIFS Implementation.
+ LDAP protocol helper functions for SAMBA
+ Copyright (C) Gerald Carter 2001-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _PASSDB_PDB_LDAP_UTIL_H_
+#define _PASSDB_PDB_LDAP_UTIL_H_
+
+/* The following definitions come from passdb/pdb_ldap_util.c */
+
+#ifdef HAVE_LDAP
+NTSTATUS smbldap_search_domain_info(struct smbldap_state *ldap_state,
+ LDAPMessage ** result, const char *domain_name,
+ bool try_add);
+#endif /* HAVE_LDAP */
+
+#endif /* _PASSDB_PDB_LDAP_UTIL_H_ */
diff --git a/source3/passdb/pdb_nds.c b/source3/passdb/pdb_nds.c
new file mode 100644
index 0000000..fc0d19b
--- /dev/null
+++ b/source3/passdb/pdb_nds.c
@@ -0,0 +1,908 @@
+/*
+ Unix SMB/CIFS Implementation.
+ NDS LDAP helper functions for SAMBA
+ Copyright (C) Vince Brimhall 2004-2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "passdb.h"
+
+#include <lber.h>
+#include <ldap.h>
+
+#include "smbldap.h"
+#include "passdb/pdb_ldap.h"
+#include "passdb/pdb_nds.h"
+
+#define NMASLDAP_GET_LOGIN_CONFIG_REQUEST "2.16.840.1.113719.1.39.42.100.3"
+#define NMASLDAP_GET_LOGIN_CONFIG_RESPONSE "2.16.840.1.113719.1.39.42.100.4"
+#define NMASLDAP_SET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.11"
+#define NMASLDAP_SET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.12"
+#define NMASLDAP_GET_PASSWORD_REQUEST "2.16.840.1.113719.1.39.42.100.13"
+#define NMASLDAP_GET_PASSWORD_RESPONSE "2.16.840.1.113719.1.39.42.100.14"
+
+#define NMAS_LDAP_EXT_VERSION 1
+
+/**********************************************************************
+ Take the request BER value and input data items and BER encodes the
+ data into the BER value
+**********************************************************************/
+
+static int berEncodePasswordData(
+ struct berval **requestBV,
+ const char *objectDN,
+ const char *password,
+ const char *password2)
+{
+ int err = 0, rc=0;
+ BerElement *requestBer = NULL;
+
+ const char * utf8ObjPtr = NULL;
+ int utf8ObjSize = 0;
+ const char * utf8PwdPtr = NULL;
+ int utf8PwdSize = 0;
+ const char * utf8Pwd2Ptr = NULL;
+ int utf8Pwd2Size = 0;
+
+
+ /* Convert objectDN and tag strings from Unicode to UTF-8 */
+ utf8ObjSize = strlen(objectDN)+1;
+ utf8ObjPtr = objectDN;
+
+ if (password != NULL)
+ {
+ utf8PwdSize = strlen(password)+1;
+ utf8PwdPtr = password;
+ }
+
+ if (password2 != NULL)
+ {
+ utf8Pwd2Size = strlen(password2)+1;
+ utf8Pwd2Ptr = password2;
+ }
+
+ /* Allocate a BerElement for the request parameters. */
+ if((requestBer = ber_alloc()) == NULL)
+ {
+ err = LDAP_ENCODING_ERROR;
+ goto Cleanup;
+ }
+
+ if (password != NULL && password2 != NULL)
+ {
+ /* BER encode the NMAS Version, the objectDN, and the password */
+ rc = ber_printf(requestBer, "{iooo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize, utf8Pwd2Ptr, utf8Pwd2Size);
+ }
+ else if (password != NULL)
+ {
+ /* BER encode the NMAS Version, the objectDN, and the password */
+ rc = ber_printf(requestBer, "{ioo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize);
+ }
+ else
+ {
+ /* BER encode the NMAS Version and the objectDN */
+ rc = ber_printf(requestBer, "{io}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize);
+ }
+
+ if (rc < 0)
+ {
+ err = LDAP_ENCODING_ERROR;
+ goto Cleanup;
+ }
+ else
+ {
+ err = 0;
+ }
+
+ /* Convert the BER we just built to a berval that we'll send with the extended request. */
+ if(ber_flatten(requestBer, requestBV) == LBER_ERROR)
+ {
+ err = LDAP_ENCODING_ERROR;
+ goto Cleanup;
+ }
+
+Cleanup:
+
+ if(requestBer)
+ {
+ ber_free(requestBer, 1);
+ }
+
+ return err;
+}
+
+/**********************************************************************
+ Take the request BER value and input data items and BER encodes the
+ data into the BER value
+**********************************************************************/
+
+static int berEncodeLoginData(
+ struct berval **requestBV,
+ char *objectDN,
+ unsigned int methodIDLen,
+ unsigned int *methodID,
+ char *tag,
+ size_t putDataLen,
+ void *putData)
+{
+ int err = 0;
+ BerElement *requestBer = NULL;
+
+ unsigned int i;
+ unsigned int elemCnt = methodIDLen / sizeof(unsigned int);
+
+ char *utf8ObjPtr=NULL;
+ int utf8ObjSize = 0;
+
+ char *utf8TagPtr = NULL;
+ int utf8TagSize = 0;
+
+ utf8ObjPtr = objectDN;
+ utf8ObjSize = strlen(utf8ObjPtr)+1;
+
+ utf8TagPtr = tag;
+ utf8TagSize = strlen(utf8TagPtr)+1;
+
+ /* Allocate a BerElement for the request parameters. */
+ if((requestBer = ber_alloc()) == NULL)
+ {
+ err = LDAP_ENCODING_ERROR;
+ goto Cleanup;
+ }
+
+ /* BER encode the NMAS Version and the objectDN */
+ err = (ber_printf(requestBer, "{io", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize) < 0) ? LDAP_ENCODING_ERROR : 0;
+
+ /* BER encode the MethodID Length and value */
+ if (!err)
+ {
+ err = (ber_printf(requestBer, "{i{", methodIDLen) < 0) ? LDAP_ENCODING_ERROR : 0;
+ }
+
+ for (i = 0; !err && i < elemCnt; i++)
+ {
+ err = (ber_printf(requestBer, "i", methodID[i]) < 0) ? LDAP_ENCODING_ERROR : 0;
+ }
+
+ if (!err)
+ {
+ err = (ber_printf(requestBer, "}}", 0) < 0) ? LDAP_ENCODING_ERROR : 0;
+ }
+
+ if (!err) {
+ if (putData) {
+ /* BER Encode the tag and data */
+ err = (ber_printf(requestBer, "oio}", utf8TagPtr,
+ utf8TagSize, putDataLen, putData,
+ putDataLen) < 0)
+ ? LDAP_ENCODING_ERROR : 0;
+ } else {
+ /* BER Encode the tag */
+ err = (ber_printf(requestBer, "o}", utf8TagPtr,
+ utf8TagSize) < 0)
+ ? LDAP_ENCODING_ERROR : 0;
+ }
+ }
+
+ if (err)
+ {
+ goto Cleanup;
+ }
+
+ /* Convert the BER we just built to a berval that we'll send with the extended request. */
+ if(ber_flatten(requestBer, requestBV) == LBER_ERROR)
+ {
+ err = LDAP_ENCODING_ERROR;
+ goto Cleanup;
+ }
+
+Cleanup:
+
+ if(requestBer)
+ {
+ ber_free(requestBer, 1);
+ }
+
+ return err;
+}
+
+/**********************************************************************
+ Takes the reply BER Value and decodes the NMAS server version and
+ return code and if a non null retData buffer was supplied, tries to
+ decode the return data and length
+**********************************************************************/
+
+static int berDecodeLoginData(
+ struct berval *replyBV,
+ int *serverVersion,
+ size_t *retDataLen,
+ void *retData )
+{
+ int err = 0;
+ BerElement *replyBer = NULL;
+ char *retOctStr = NULL;
+ size_t retOctStrLen = 0;
+
+ if((replyBer = ber_init(replyBV)) == NULL)
+ {
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+ if(retData)
+ {
+ retOctStrLen = *retDataLen + 1;
+ retOctStr = SMB_MALLOC_ARRAY(char, retOctStrLen);
+ if(!retOctStr)
+ {
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+ if(ber_scanf(replyBer, "{iis}", serverVersion, &err, retOctStr, &retOctStrLen) != -1)
+ {
+ if (*retDataLen >= retOctStrLen)
+ {
+ memcpy(retData, retOctStr, retOctStrLen);
+ }
+ else if (!err)
+ {
+ err = LDAP_NO_MEMORY;
+ }
+
+ *retDataLen = retOctStrLen;
+ }
+ else if (!err)
+ {
+ err = LDAP_DECODING_ERROR;
+ }
+ }
+ else
+ {
+ if(ber_scanf(replyBer, "{ii}", serverVersion, &err) == -1)
+ {
+ if (!err)
+ {
+ err = LDAP_DECODING_ERROR;
+ }
+ }
+ }
+
+Cleanup:
+
+ if(replyBer)
+ {
+ ber_free(replyBer, 1);
+ }
+
+ if (retOctStr != NULL)
+ {
+ memset(retOctStr, 0, retOctStrLen);
+ free(retOctStr);
+ }
+
+ return err;
+}
+
+/**********************************************************************
+ Retrieves data in the login configuration of the specified object
+ that is tagged with the specified methodID and tag.
+**********************************************************************/
+
+static int getLoginConfig(
+ LDAP *ld,
+ char *objectDN,
+ unsigned int methodIDLen,
+ unsigned int *methodID,
+ char *tag,
+ size_t *dataLen,
+ void *data )
+{
+ int err = 0;
+ struct berval *requestBV = NULL;
+ char *replyOID = NULL;
+ struct berval *replyBV = NULL;
+ int serverVersion = 0;
+
+ /* Validate unicode parameters. */
+ if((strlen(objectDN) == 0) || ld == NULL)
+ {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ err = berEncodeLoginData(&requestBV, objectDN, methodIDLen, methodID, tag, 0, NULL);
+ if(err)
+ {
+ goto Cleanup;
+ }
+
+ /* Call the ldap_extended_operation (synchronously) */
+ if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_LOGIN_CONFIG_REQUEST,
+ requestBV, NULL, NULL, &replyOID, &replyBV)))
+ {
+ goto Cleanup;
+ }
+
+ /* Make sure there is a return OID */
+ if(!replyOID)
+ {
+ err = LDAP_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ /* Is this what we were expecting to get back. */
+ if(strcmp(replyOID, NMASLDAP_GET_LOGIN_CONFIG_RESPONSE))
+ {
+ err = LDAP_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ /* Do we have a good returned berval? */
+ if(!replyBV)
+ {
+ /* No; returned berval means we experienced a rather drastic error. */
+ /* Return operations error. */
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+ err = berDecodeLoginData(replyBV, &serverVersion, dataLen, data);
+
+ if(serverVersion != NMAS_LDAP_EXT_VERSION)
+ {
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+Cleanup:
+
+ if(replyBV)
+ {
+ ber_bvfree(replyBV);
+ }
+
+ /* Free the return OID string if one was returned. */
+ if(replyOID)
+ {
+ ldap_memfree(replyOID);
+ }
+
+ /* Free memory allocated while building the request ber and berval. */
+ if(requestBV)
+ {
+ ber_bvfree(requestBV);
+ }
+
+ /* Return the appropriate error/success code. */
+ return err;
+}
+
+/**********************************************************************
+ Attempts to get the Simple Password
+**********************************************************************/
+
+static int nmasldap_get_simple_pwd(
+ LDAP *ld,
+ char *objectDN,
+ size_t pwdLen,
+ char *pwd )
+{
+ int err = 0;
+ unsigned int methodID = 0;
+ unsigned int methodIDLen = sizeof(methodID);
+ char tag[] = {'P','A','S','S','W','O','R','D',' ','H','A','S','H',0};
+ char *pwdBuf=NULL;
+ size_t pwdBufLen, bufferLen;
+
+ bufferLen = pwdBufLen = pwdLen+2;
+ pwdBuf = SMB_MALLOC_ARRAY(char, pwdBufLen); /* digest and null */
+ if(pwdBuf == NULL)
+ {
+ return LDAP_NO_MEMORY;
+ }
+
+ err = getLoginConfig(ld, objectDN, methodIDLen, &methodID, tag, &pwdBufLen, pwdBuf);
+ if (err == 0)
+ {
+ if (pwdBufLen !=0)
+ {
+ pwdBuf[pwdBufLen] = 0; /* null terminate */
+
+ switch (pwdBuf[0])
+ {
+ case 1: /* cleartext password */
+ break;
+ case 2: /* SHA1 HASH */
+ case 3: /* MD5_ID */
+ case 4: /* UNIXCrypt_ID */
+ case 8: /* SSHA_ID */
+ default: /* Unknown digest */
+ err = LDAP_INAPPROPRIATE_AUTH; /* only return clear text */
+ break;
+ }
+
+ if (!err)
+ {
+ if (pwdLen >= pwdBufLen-1)
+ {
+ memcpy(pwd, &pwdBuf[1], pwdBufLen-1); /* skip digest tag and include null */
+ }
+ else
+ {
+ err = LDAP_NO_MEMORY;
+ }
+ }
+ }
+ }
+
+ if (pwdBuf != NULL)
+ {
+ memset(pwdBuf, 0, bufferLen);
+ free(pwdBuf);
+ }
+
+ return err;
+}
+
+
+/**********************************************************************
+ Attempts to set the Universal Password
+**********************************************************************/
+
+static int nmasldap_set_password(
+ LDAP *ld,
+ const char *objectDN,
+ const char *pwd )
+{
+ int err = 0;
+
+ struct berval *requestBV = NULL;
+ char *replyOID = NULL;
+ struct berval *replyBV = NULL;
+ int serverVersion;
+
+ /* Validate char parameters. */
+ if(objectDN == NULL || (strlen(objectDN) == 0) || pwd == NULL || ld == NULL)
+ {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ err = berEncodePasswordData(&requestBV, objectDN, pwd, NULL);
+ if(err)
+ {
+ goto Cleanup;
+ }
+
+ /* Call the ldap_extended_operation (synchronously) */
+ if((err = ldap_extended_operation_s(ld, NMASLDAP_SET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV)))
+ {
+ goto Cleanup;
+ }
+
+ /* Make sure there is a return OID */
+ if(!replyOID)
+ {
+ err = LDAP_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ /* Is this what we were expecting to get back. */
+ if(strcmp(replyOID, NMASLDAP_SET_PASSWORD_RESPONSE))
+ {
+ err = LDAP_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ /* Do we have a good returned berval? */
+ if(!replyBV)
+ {
+ /* No; returned berval means we experienced a rather drastic error. */
+ /* Return operations error. */
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+ err = berDecodeLoginData(replyBV, &serverVersion, NULL, NULL);
+
+ if(serverVersion != NMAS_LDAP_EXT_VERSION)
+ {
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+Cleanup:
+
+ if(replyBV)
+ {
+ ber_bvfree(replyBV);
+ }
+
+ /* Free the return OID string if one was returned. */
+ if(replyOID)
+ {
+ ldap_memfree(replyOID);
+ }
+
+ /* Free memory allocated while building the request ber and berval. */
+ if(requestBV)
+ {
+ ber_bvfree(requestBV);
+ }
+
+ /* Return the appropriate error/success code. */
+ return err;
+}
+
+/**********************************************************************
+ Attempts to get the Universal Password
+**********************************************************************/
+
+static int nmasldap_get_password(
+ LDAP *ld,
+ char *objectDN,
+ size_t *pwdSize, /* in bytes */
+ unsigned char *pwd )
+{
+ int err = 0;
+
+ struct berval *requestBV = NULL;
+ char *replyOID = NULL;
+ struct berval *replyBV = NULL;
+ int serverVersion;
+ char *pwdBuf;
+ size_t pwdBufLen, bufferLen;
+
+ /* Validate char parameters. */
+ if(objectDN == NULL || (strlen(objectDN) == 0) || pwdSize == NULL || ld == NULL)
+ {
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ }
+
+ bufferLen = pwdBufLen = *pwdSize;
+ pwdBuf = SMB_MALLOC_ARRAY(char, pwdBufLen+2);
+ if(pwdBuf == NULL)
+ {
+ return LDAP_NO_MEMORY;
+ }
+
+ err = berEncodePasswordData(&requestBV, objectDN, NULL, NULL);
+ if(err)
+ {
+ goto Cleanup;
+ }
+
+ /* Call the ldap_extended_operation (synchronously) */
+ if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV)))
+ {
+ goto Cleanup;
+ }
+
+ /* Make sure there is a return OID */
+ if(!replyOID)
+ {
+ err = LDAP_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ /* Is this what we were expecting to get back. */
+ if(strcmp(replyOID, NMASLDAP_GET_PASSWORD_RESPONSE))
+ {
+ err = LDAP_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ /* Do we have a good returned berval? */
+ if(!replyBV)
+ {
+ /* No; returned berval means we experienced a rather drastic error. */
+ /* Return operations error. */
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+ err = berDecodeLoginData(replyBV, &serverVersion, &pwdBufLen, pwdBuf);
+
+ if(serverVersion != NMAS_LDAP_EXT_VERSION)
+ {
+ err = LDAP_OPERATIONS_ERROR;
+ goto Cleanup;
+ }
+
+ if (!err && pwdBufLen != 0)
+ {
+ if (*pwdSize >= pwdBufLen+1 && pwd != NULL)
+ {
+ memcpy(pwd, pwdBuf, pwdBufLen);
+ pwd[pwdBufLen] = 0; /* add null termination */
+ }
+ *pwdSize = pwdBufLen; /* does not include null termination */
+ }
+
+Cleanup:
+
+ if(replyBV)
+ {
+ ber_bvfree(replyBV);
+ }
+
+ /* Free the return OID string if one was returned. */
+ if(replyOID)
+ {
+ ldap_memfree(replyOID);
+ }
+
+ /* Free memory allocated while building the request ber and berval. */
+ if(requestBV)
+ {
+ ber_bvfree(requestBV);
+ }
+
+ if (pwdBuf != NULL)
+ {
+ memset(pwdBuf, 0, bufferLen);
+ free(pwdBuf);
+ }
+
+ /* Return the appropriate error/success code. */
+ return err;
+}
+
+/**********************************************************************
+ Get the user's password from NDS.
+ *********************************************************************/
+
+int pdb_nds_get_password(
+ struct smbldap_state *ldap_state,
+ char *object_dn,
+ size_t *pwd_len,
+ char *pwd )
+{
+ LDAP *ld = smbldap_get_ldap(ldap_state);
+ int rc = -1;
+
+ rc = nmasldap_get_password(ld, object_dn, pwd_len, (unsigned char *)pwd);
+ if (rc == LDAP_SUCCESS) {
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("nmasldap_get_password returned %s for %s\n", pwd, object_dn));
+#endif
+ DEBUG(5, ("NDS Universal Password retrieved for %s\n", object_dn));
+ } else {
+ DEBUG(3, ("NDS Universal Password NOT retrieved for %s\n", object_dn));
+ }
+
+ if (rc != LDAP_SUCCESS) {
+ rc = nmasldap_get_simple_pwd(ld, object_dn, *pwd_len, pwd);
+ if (rc == LDAP_SUCCESS) {
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("nmasldap_get_simple_pwd returned %s for %s\n", pwd, object_dn));
+#endif
+ DEBUG(5, ("NDS Simple Password retrieved for %s\n", object_dn));
+ } else {
+ /* We couldn't get the password */
+ DEBUG(3, ("NDS Simple Password NOT retrieved for %s\n", object_dn));
+ return LDAP_INVALID_CREDENTIALS;
+ }
+ }
+
+ /* We got the password */
+ return LDAP_SUCCESS;
+}
+
+/**********************************************************************
+ Set the users NDS, Universal and Simple passwords.
+ ********************************************************************/
+
+int pdb_nds_set_password(
+ struct smbldap_state *ldap_state,
+ char *object_dn,
+ const char *pwd )
+{
+ LDAP *ld = smbldap_get_ldap(ldap_state);
+ int rc = -1;
+ LDAPMod **tmpmods = NULL;
+
+ rc = nmasldap_set_password(ld, object_dn, pwd);
+ if (rc == LDAP_SUCCESS) {
+ DEBUG(5,("NDS Universal Password changed for user %s\n", object_dn));
+ } else {
+ char *ld_error = NULL;
+ ldap_get_option(ld, LDAP_OPT_ERROR_STRING, &ld_error);
+
+ /* This will fail if Universal Password is not enabled for the user's context */
+ DEBUG(3,("NDS Universal Password could not be changed for user %s: %s (%s)\n",
+ object_dn, ldap_err2string(rc), ld_error?ld_error:"unknown"));
+ SAFE_FREE(ld_error);
+ }
+
+ /* Set eDirectory Password */
+ smbldap_set_mod(&tmpmods, LDAP_MOD_REPLACE, "userPassword", pwd);
+ rc = smbldap_modify(ldap_state, object_dn, tmpmods);
+
+ return rc;
+}
+
+/**********************************************************************
+ Allow ldap server to update internal login attempt counters by
+ performing a simple bind. If the samba authentication failed attempt
+ the bind with a bogus, randomly generated password to count the
+ failed attempt. If the bind fails even though samba authentication
+ succeeded, this would indicate that the user's account is disabled,
+ time restrictions are in place or some other password policy
+ violation.
+*********************************************************************/
+
+static NTSTATUS pdb_nds_update_login_attempts(struct pdb_methods *methods,
+ struct samu *sam_acct, bool success)
+{
+ struct ldapsam_privates *ldap_state;
+
+ if ((!methods) || (!sam_acct)) {
+ DEBUG(3,("pdb_nds_update_login_attempts: invalid parameter.\n"));
+ return NT_STATUS_MEMORY_NOT_ALLOCATED;
+ }
+
+ ldap_state = (struct ldapsam_privates *)methods->private_data;
+
+ if (ldap_state) {
+ /* Attempt simple bind with user credentials to update eDirectory
+ password policy */
+ int rc = 0;
+ char *dn;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ const char **attr_list;
+ size_t pwd_len;
+ char clear_text_pw[512];
+ LDAP *ld = NULL;
+ const char *username = pdb_get_username(sam_acct);
+ bool got_clear_text_pw = False;
+
+ DEBUG(5,("pdb_nds_update_login_attempts: %s login for %s\n",
+ success ? "Successful" : "Failed", username));
+
+ result = (LDAPMessage *)pdb_get_backend_private_data(sam_acct, methods);
+ if (!result) {
+ attr_list = get_userattr_list(NULL,
+ ldap_state->schema_ver);
+ rc = ldapsam_search_suffix_by_name(ldap_state, username, &result, attr_list );
+ TALLOC_FREE( attr_list );
+ if (rc != LDAP_SUCCESS) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ pdb_set_backend_private_data(sam_acct, result, NULL,
+ methods, PDB_CHANGED);
+ smbldap_talloc_autofree_ldapmsg(sam_acct, result);
+ }
+
+ if (ldap_count_entries(
+ smbldap_get_ldap(ldap_state->smbldap_state),
+ result) == 0) {
+ DEBUG(0, ("pdb_nds_update_login_attempts: No user to modify!\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ entry = ldap_first_entry(
+ smbldap_get_ldap(ldap_state->smbldap_state), result);
+ dn = smbldap_talloc_dn(talloc_tos(),
+ smbldap_get_ldap(
+ ldap_state->smbldap_state),
+ entry);
+ if (!dn) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DEBUG(3, ("pdb_nds_update_login_attempts: username %s found dn '%s'\n", username, dn));
+
+ pwd_len = sizeof(clear_text_pw);
+ if (success == True) {
+ if (pdb_nds_get_password(ldap_state->smbldap_state, dn, &pwd_len, clear_text_pw) == LDAP_SUCCESS) {
+ /* Got clear text password. Use simple ldap bind */
+ got_clear_text_pw = True;
+ }
+ } else {
+ /* This is a long term key */
+ generate_secret_buffer((unsigned char *)clear_text_pw, 24);
+ clear_text_pw[24] = '\0';
+ DEBUG(5,("pdb_nds_update_login_attempts: using random password %s\n", clear_text_pw));
+ }
+
+ if((success != True) || (got_clear_text_pw == True)) {
+
+ rc = smbldap_setup_full_conn(&ld, ldap_state->location);
+ if (rc) {
+ TALLOC_FREE(dn);
+ return NT_STATUS_INVALID_CONNECTION;
+ }
+
+ /* Attempt simple bind with real or bogus password */
+ rc = ldap_simple_bind_s(ld, dn, clear_text_pw);
+ ldap_unbind(ld);
+ if (rc == LDAP_SUCCESS) {
+ DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Successful for %s\n", username));
+ } else {
+ NTSTATUS nt_status = NT_STATUS_ACCOUNT_RESTRICTION;
+ DEBUG(5,("pdb_nds_update_login_attempts: ldap_simple_bind_s Failed for %s\n", username));
+ switch(rc) {
+ case LDAP_INVALID_CREDENTIALS:
+ nt_status = NT_STATUS_WRONG_PASSWORD;
+ break;
+ case LDAP_UNWILLING_TO_PERFORM:
+ /* eDir returns this if the account was disabled. */
+ /* The problem is we don't know if the given
+ password was correct for this account or
+ not. We have to return more info than we
+ should and tell the client NT_STATUS_ACCOUNT_DISABLED
+ so they don't think the password was bad. JRA. */
+ nt_status = NT_STATUS_ACCOUNT_DISABLED;
+ break;
+ default:
+ break;
+ }
+ return nt_status;
+ }
+ }
+ TALLOC_FREE(dn);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Initialise the parts of the pdb_methods structure that are common
+ to NDS_ldapsam modes
+ *********************************************************************/
+
+static NTSTATUS pdb_init_NDS_ldapsam_common(struct pdb_methods **pdb_method, const char *location)
+{
+ struct ldapsam_privates *ldap_state =
+ (struct ldapsam_privates *)((*pdb_method)->private_data);
+
+ /* Mark this as eDirectory ldap */
+ ldap_state->is_nds_ldap = True;
+
+ /* Add pdb_nds specific method for updating login attempts. */
+ (*pdb_method)->update_login_attempts = pdb_nds_update_login_attempts;
+
+ /* Save location for use in pdb_nds_update_login_attempts */
+ ldap_state->location = SMB_STRDUP(location);
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Initialise the 'nds' normal mode for pdb_ldap
+ *********************************************************************/
+
+static NTSTATUS pdb_init_NDS_ldapsam(struct pdb_methods **pdb_method, const char *location)
+{
+ NTSTATUS nt_status = pdb_ldapsam_init_common(pdb_method, location);
+
+ (*pdb_method)->name = "NDS_ldapsam";
+
+ pdb_init_NDS_ldapsam_common(pdb_method, location);
+
+ return nt_status;
+}
+
+NTSTATUS pdb_nds_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS nt_status;
+ if (!NT_STATUS_IS_OK(nt_status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "NDS_ldapsam", pdb_init_NDS_ldapsam)))
+ return nt_status;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/passdb/pdb_nds.h b/source3/passdb/pdb_nds.h
new file mode 100644
index 0000000..ee71c78
--- /dev/null
+++ b/source3/passdb/pdb_nds.h
@@ -0,0 +1,39 @@
+/*
+ Unix SMB/CIFS Implementation.
+ NDS LDAP helper functions for SAMBA
+ Copyright (C) Vince Brimhall 2004-2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#ifndef _PASSDB_PDB_NDS_H_
+#define _PASSDB_PDB_NDS_H_
+
+/* The following definitions come from passdb/pdb_nds.c */
+
+struct smbldap_state;
+
+int pdb_nds_get_password(
+ struct smbldap_state *ldap_state,
+ char *object_dn,
+ size_t *pwd_len,
+ char *pwd );
+int pdb_nds_set_password(
+ struct smbldap_state *ldap_state,
+ char *object_dn,
+ const char *pwd );
+NTSTATUS pdb_nds_init(TALLOC_CTX *);
+
+#endif /* _PASSDB_PDB_NDS_H_ */
diff --git a/source3/passdb/pdb_samba_dsdb.c b/source3/passdb/pdb_samba_dsdb.c
new file mode 100644
index 0000000..ef90ab7
--- /dev/null
+++ b/source3/passdb/pdb_samba_dsdb.c
@@ -0,0 +1,3895 @@
+/*
+ Unix SMB/CIFS implementation.
+ pdb glue module for direct access to the dsdb via LDB APIs
+ Copyright (C) Volker Lendecke 2009-2011
+ Copyright (C) Andrew Bartlett 2010-2012
+ Copyright (C) Matthias Dieter Wallnöfer 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* This module, is a port of Volker's pdb_ads to ldb and DSDB APIs */
+
+#include "includes.h"
+#include "source3/include/passdb.h"
+#include "source4/dsdb/samdb/samdb.h"
+#include "ldb_errors.h"
+#include "libcli/security/dom_sid.h"
+#include "source4/winbind/idmap.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "librpc/gen_ndr/ndr_lsa.h"
+#include "libds/common/flag_mapping.h"
+#include "source4/lib/events/events.h"
+#include "source4/auth/session.h"
+#include "source4/auth/system_session_proto.h"
+#include "lib/param/param.h"
+#include "source4/dsdb/common/util.h"
+#include "source3/include/secrets.h"
+#include "source4/auth/auth_sam.h"
+#include "auth/credentials/credentials.h"
+#include "lib/util/base64.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "lib/util/util_ldb.h"
+
+struct pdb_samba_dsdb_state {
+ struct tevent_context *ev;
+ struct ldb_context *ldb;
+ struct idmap_context *idmap_ctx;
+ struct loadparm_context *lp_ctx;
+};
+
+static NTSTATUS pdb_samba_dsdb_getsampwsid(struct pdb_methods *m,
+ struct samu *sam_acct,
+ const struct dom_sid *sid);
+static NTSTATUS pdb_samba_dsdb_getsamupriv(struct pdb_samba_dsdb_state *state,
+ const char *filter,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **pmsg);
+static bool pdb_samba_dsdb_sid_to_id(struct pdb_methods *m, const struct dom_sid *sid,
+ struct unixid *id);
+
+static bool pdb_samba_dsdb_pull_time(struct ldb_message *msg, const char *attr,
+ time_t *ptime)
+{
+ uint64_t tmp;
+ if (! ldb_msg_find_element(msg, attr)) {
+ return false;
+ }
+ tmp = ldb_msg_find_attr_as_uint64(msg, attr, 0);
+ *ptime = nt_time_to_unix(tmp);
+ return true;
+}
+
+static struct pdb_domain_info *pdb_samba_dsdb_get_domain_info(
+ struct pdb_methods *m, TALLOC_CTX *mem_ctx)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct pdb_domain_info *info;
+ struct dom_sid *domain_sid;
+ struct ldb_dn *forest_dn, *domain_dn;
+ struct ldb_result *dom_res = NULL;
+ const char *dom_attrs[] = {
+ "objectSid",
+ "objectGUID",
+ "fSMORoleOwner",
+ NULL
+ };
+ char *p;
+ int ret;
+
+ info = talloc(mem_ctx, struct pdb_domain_info);
+ if (info == NULL) {
+ return NULL;
+ }
+
+ domain_dn = ldb_get_default_basedn(state->ldb);
+
+ ret = ldb_search(state->ldb, info, &dom_res,
+ domain_dn, LDB_SCOPE_BASE, dom_attrs, NULL);
+ if (ret != LDB_SUCCESS) {
+ goto fail;
+ }
+ if (dom_res->count != 1) {
+ goto fail;
+ }
+
+ info->guid = samdb_result_guid(dom_res->msgs[0], "objectGUID");
+
+ domain_sid = samdb_result_dom_sid(state, dom_res->msgs[0], "objectSid");
+ if (!domain_sid) {
+ goto fail;
+ }
+ info->sid = *domain_sid;
+
+ TALLOC_FREE(dom_res);
+
+ info->name = talloc_strdup(info, lpcfg_sam_name(state->lp_ctx));
+ info->dns_domain = ldb_dn_canonical_string(info, domain_dn);
+
+ if (!info->dns_domain) {
+ goto fail;
+ }
+ p = strchr(info->dns_domain, '/');
+ if (p) {
+ *p = '\0';
+ }
+
+ forest_dn = ldb_get_root_basedn(state->ldb);
+ if (!forest_dn) {
+ goto fail;
+ }
+
+ info->dns_forest = ldb_dn_canonical_string(info, forest_dn);
+ if (!info->dns_forest) {
+ goto fail;
+ }
+ p = strchr(info->dns_forest, '/');
+ if (p) {
+ *p = '\0';
+ }
+
+ return info;
+
+fail:
+ TALLOC_FREE(dom_res);
+ TALLOC_FREE(info);
+ return NULL;
+}
+
+static struct ldb_message *pdb_samba_dsdb_get_samu_private(
+ struct pdb_methods *m, struct samu *sam)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct ldb_message *msg;
+ struct dom_sid_buf sidstr;
+ char *filter;
+ NTSTATUS status;
+
+ msg = (struct ldb_message *)
+ pdb_get_backend_private_data(sam, m);
+
+ if (msg != NULL) {
+ return talloc_get_type_abort(msg, struct ldb_message);
+ }
+
+ filter = talloc_asprintf(
+ talloc_tos(),
+ "(&(objectsid=%s)(objectclass=user))",
+ dom_sid_str_buf(pdb_get_user_sid(sam), &sidstr));
+ if (filter == NULL) {
+ return NULL;
+ }
+
+ status = pdb_samba_dsdb_getsamupriv(state, filter, sam, &msg);
+ TALLOC_FREE(filter);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ return msg;
+}
+
+static NTSTATUS pdb_samba_dsdb_init_sam_from_priv(struct pdb_methods *m,
+ struct samu *sam,
+ struct ldb_message *msg)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ const char *str;
+ time_t tmp_time;
+ struct dom_sid *sid, group_sid;
+ uint64_t n;
+ const DATA_BLOB *blob;
+
+ str = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL);
+ if (str == NULL) {
+ DEBUG(10, ("no samAccountName\n"));
+ goto fail;
+ }
+ pdb_set_username(sam, str, PDB_SET);
+
+ if (pdb_samba_dsdb_pull_time(msg, "lastLogon", &tmp_time)) {
+ pdb_set_logon_time(sam, tmp_time, PDB_SET);
+ }
+ if (pdb_samba_dsdb_pull_time(msg, "lastLogoff", &tmp_time)) {
+ pdb_set_logoff_time(sam, tmp_time, PDB_SET);
+ }
+ if (pdb_samba_dsdb_pull_time(msg, "pwdLastSet", &tmp_time)) {
+ pdb_set_pass_last_set_time(sam, tmp_time, PDB_SET);
+ }
+ if (pdb_samba_dsdb_pull_time(msg, "accountExpires", &tmp_time)) {
+ pdb_set_kickoff_time(sam, tmp_time, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "displayName",
+ NULL);
+ if (str != NULL) {
+ pdb_set_fullname(sam, str, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "homeDirectory",
+ NULL);
+ if (str != NULL) {
+ pdb_set_homedir(sam, str, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "homeDrive", NULL);
+ if (str != NULL) {
+ pdb_set_dir_drive(sam, str, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "scriptPath", NULL);
+ if (str != NULL) {
+ pdb_set_logon_script(sam, str, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "profilePath",
+ NULL);
+ if (str != NULL) {
+ pdb_set_profile_path(sam, str, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "comment",
+ NULL);
+ if (str != NULL) {
+ pdb_set_comment(sam, str, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "description",
+ NULL);
+ if (str != NULL) {
+ pdb_set_acct_desc(sam, str, PDB_SET);
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "userWorkstations",
+ NULL);
+ if (str != NULL) {
+ pdb_set_workstations(sam, str, PDB_SET);
+ }
+
+ blob = ldb_msg_find_ldb_val(msg, "userParameters");
+ if (blob != NULL) {
+ str = base64_encode_data_blob(frame, *blob);
+ if (str == NULL) {
+ DEBUG(0, ("base64_encode_data_blob() failed\n"));
+ goto fail;
+ }
+ pdb_set_munged_dial(sam, str, PDB_SET);
+ }
+
+ sid = samdb_result_dom_sid(talloc_tos(), msg, "objectSid");
+ if (!sid) {
+ DEBUG(10, ("Could not pull SID\n"));
+ goto fail;
+ }
+ pdb_set_user_sid(sam, sid, PDB_SET);
+
+ n = samdb_result_acct_flags(msg, "msDS-User-Account-Control-Computed");
+ if (n == 0) {
+ DEBUG(10, ("Could not pull userAccountControl\n"));
+ goto fail;
+ }
+ pdb_set_acct_ctrl(sam, n, PDB_SET);
+
+ blob = ldb_msg_find_ldb_val(msg, "unicodePwd");
+ if (blob) {
+ if (blob->length != NT_HASH_LEN) {
+ DEBUG(0, ("Got NT hash of length %d, expected %d\n",
+ (int)blob->length, NT_HASH_LEN));
+ goto fail;
+ }
+ pdb_set_nt_passwd(sam, blob->data, PDB_SET);
+ }
+
+ blob = ldb_msg_find_ldb_val(msg, "dBCSPwd");
+ if (blob) {
+ if (blob->length != LM_HASH_LEN) {
+ DEBUG(0, ("Got LM hash of length %d, expected %d\n",
+ (int)blob->length, LM_HASH_LEN));
+ goto fail;
+ }
+ pdb_set_lanman_passwd(sam, blob->data, PDB_SET);
+ }
+
+ n = ldb_msg_find_attr_as_uint(msg, "primaryGroupID", 0);
+ if (n == 0) {
+ DEBUG(10, ("Could not pull primaryGroupID\n"));
+ goto fail;
+ }
+ sid_compose(&group_sid, samdb_domain_sid(state->ldb), n);
+ pdb_set_group_sid(sam, &group_sid, PDB_SET);
+
+ status = NT_STATUS_OK;
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static bool pdb_samba_dsdb_add_time(struct ldb_message *msg,
+ const char *attrib, time_t t)
+{
+ uint64_t nt_time;
+
+ unix_to_nt_time(&nt_time, t);
+
+ return ldb_msg_add_fmt(msg, attrib, "%llu", (unsigned long long) nt_time);
+}
+
+static int pdb_samba_dsdb_replace_by_sam(struct pdb_samba_dsdb_state *state,
+ bool (*need_update)(const struct samu *,
+ enum pdb_elements),
+ struct ldb_dn *dn,
+ struct samu *sam)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = LDB_SUCCESS;
+ const char *pw;
+ struct ldb_message *msg;
+ struct ldb_request *req;
+ uint32_t dsdb_flags = 0;
+ /* TODO: All fields :-) */
+
+ msg = ldb_msg_new(frame);
+ if (!msg) {
+ talloc_free(frame);
+ return false;
+ }
+
+ msg->dn = dn;
+
+ /* build modify request */
+ ret = ldb_build_mod_req(&req, state->ldb, frame, msg, NULL, NULL,
+ ldb_op_default_callback,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(frame);
+ return ret;
+ }
+
+ /* If we set a plaintext password, the system will
+ * force the pwdLastSet to now() */
+ if (need_update(sam, PDB_PASSLASTSET)) {
+ dsdb_flags |= DSDB_PASSWORD_BYPASS_LAST_SET;
+
+ ret |= pdb_samba_dsdb_add_time(msg, "pwdLastSet",
+ pdb_get_pass_last_set_time(sam));
+ }
+
+ pw = pdb_get_plaintext_passwd(sam);
+ if (need_update(sam, PDB_PLAINTEXT_PW)) {
+ struct ldb_val pw_utf16;
+ if (pw == NULL) {
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (!convert_string_talloc(msg,
+ CH_UNIX, CH_UTF16,
+ pw, strlen(pw),
+ (void *)&pw_utf16.data,
+ &pw_utf16.length)) {
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ ret |= ldb_msg_add_value(msg, "clearTextPassword", &pw_utf16, NULL);
+ } else {
+ bool changed_lm_pw = false;
+ bool changed_nt_pw = false;
+ bool changed_history = false;
+ if (need_update(sam, PDB_LMPASSWD)) {
+ struct ldb_val val;
+ val.data = discard_const_p(uint8_t, pdb_get_lanman_passwd(sam));
+ if (!val.data) {
+ samdb_msg_add_delete(state->ldb, msg, msg,
+ "dBCSPwd");
+ } else {
+ val.length = LM_HASH_LEN;
+ ret |= ldb_msg_add_value(msg, "dBCSPwd", &val, NULL);
+ }
+ changed_lm_pw = true;
+ }
+ if (need_update(sam, PDB_NTPASSWD)) {
+ struct ldb_val val;
+ val.data = discard_const_p(uint8_t, pdb_get_nt_passwd(sam));
+ if (!val.data) {
+ samdb_msg_add_delete(state->ldb, msg, msg,
+ "unicodePwd");
+ } else {
+ val.length = NT_HASH_LEN;
+ ret |= ldb_msg_add_value(msg, "unicodePwd", &val, NULL);
+ }
+ changed_nt_pw = true;
+ }
+
+ /* Try to ensure we don't get out of sync */
+ if (changed_lm_pw && !changed_nt_pw) {
+ samdb_msg_add_delete(state->ldb, msg, msg,
+ "unicodePwd");
+ } else if (changed_nt_pw && !changed_lm_pw) {
+ samdb_msg_add_delete(state->ldb, msg, msg,
+ "dBCSPwd");
+ }
+ if (changed_lm_pw || changed_nt_pw) {
+ samdb_msg_add_delete(state->ldb, msg, msg,
+ "supplementalCredentials");
+
+ }
+
+ if (need_update(sam, PDB_PWHISTORY)) {
+ uint32_t current_hist_len;
+ const uint8_t *history = pdb_get_pw_history(sam, &current_hist_len);
+
+ bool invalid_history = false;
+ struct samr_Password *history_hashes = talloc_array(talloc_tos(), struct samr_Password,
+ current_hist_len);
+ if (!history) {
+ invalid_history = true;
+ } else {
+ unsigned int i;
+ /* Parse the history into the correct format */
+ for (i = 0; i < current_hist_len; i++) {
+ if (!all_zero(&history[i*PW_HISTORY_ENTRY_LEN],
+ 16)) {
+ /* If the history is in the old format, with a salted hash, then we can't migrate it to AD format */
+ invalid_history = true;
+ break;
+ }
+ /* Copy out the 2nd 16 bytes of the 32 byte password history, containing the NT hash */
+ memcpy(history_hashes[i].hash,
+ &history[(i*PW_HISTORY_ENTRY_LEN) + PW_HISTORY_SALT_LEN],
+ sizeof(history_hashes[i].hash));
+ }
+ }
+ if (invalid_history) {
+ ret |= samdb_msg_add_delete(state->ldb, msg, msg,
+ "ntPwdHistory");
+
+ ret |= samdb_msg_add_delete(state->ldb, msg, msg,
+ "lmPwdHistory");
+ } else {
+ ret |= samdb_msg_add_hashes(state->ldb, msg, msg,
+ "ntPwdHistory",
+ history_hashes,
+ current_hist_len);
+ }
+ changed_history = true;
+ }
+ if (changed_lm_pw || changed_nt_pw || changed_history) {
+ /* These attributes can only be modified directly by using a special control */
+ dsdb_flags |= DSDB_BYPASS_PASSWORD_HASH;
+ }
+ }
+
+ /* PDB_USERSID is only allowed on ADD, handled in caller */
+ if (need_update(sam, PDB_GROUPSID)) {
+ const struct dom_sid *sid = pdb_get_group_sid(sam);
+ uint32_t rid;
+ NTSTATUS status = dom_sid_split_rid(NULL, sid, NULL, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+ if (!dom_sid_in_domain(samdb_domain_sid(state->ldb), sid)) {
+ talloc_free(frame);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+ ret |= samdb_msg_add_uint(state->ldb, msg, msg, "primaryGroupID", rid);
+ }
+ if (need_update(sam, PDB_FULLNAME)) {
+ ret |= ldb_msg_add_string(msg, "displayName", pdb_get_fullname(sam));
+ }
+
+ if (need_update(sam, PDB_SMBHOME)) {
+ ret |= ldb_msg_add_string(msg, "homeDirectory",
+ pdb_get_homedir(sam));
+ }
+
+ if (need_update(sam, PDB_PROFILE)) {
+ ret |= ldb_msg_add_string(msg, "profilePath",
+ pdb_get_profile_path(sam));
+ }
+
+ if (need_update(sam, PDB_DRIVE)) {
+ ret |= ldb_msg_add_string(msg, "homeDrive",
+ pdb_get_dir_drive(sam));
+ }
+
+ if (need_update(sam, PDB_LOGONSCRIPT)) {
+ ret |= ldb_msg_add_string(msg, "scriptPath",
+ pdb_get_logon_script(sam));
+ }
+
+ if (need_update(sam, PDB_KICKOFFTIME)) {
+ ret |= pdb_samba_dsdb_add_time(msg, "accountExpires",
+ pdb_get_kickoff_time(sam));
+ }
+
+ if (need_update(sam, PDB_LOGONTIME)) {
+ ret |= pdb_samba_dsdb_add_time(msg, "lastLogon",
+ pdb_get_logon_time(sam));
+ }
+
+ if (need_update(sam, PDB_LOGOFFTIME)) {
+ ret |= pdb_samba_dsdb_add_time(msg, "lastLogoff",
+ pdb_get_logoff_time(sam));
+ }
+
+ if (need_update(sam, PDB_USERNAME)) {
+ ret |= ldb_msg_add_string(msg, "samAccountName",
+ pdb_get_username(sam));
+ }
+
+ if (need_update(sam, PDB_HOURSLEN) || need_update(sam, PDB_HOURS)) {
+ struct ldb_val hours = data_blob_const(pdb_get_hours(sam), pdb_get_hours_len(sam));
+ ret |= ldb_msg_add_value(msg, "logonHours",
+ &hours, NULL);
+ }
+
+ if (need_update(sam, PDB_ACCTCTRL)) {
+ ret |= samdb_msg_add_acct_flags(state->ldb, msg, msg,
+ "userAccountControl", pdb_get_acct_ctrl(sam));
+ }
+
+ if (need_update(sam, PDB_COMMENT)) {
+ ret |= ldb_msg_add_string(msg, "comment",
+ pdb_get_comment(sam));
+ }
+
+ if (need_update(sam, PDB_ACCTDESC)) {
+ ret |= ldb_msg_add_string(msg, "description",
+ pdb_get_acct_desc(sam));
+ }
+
+ if (need_update(sam, PDB_WORKSTATIONS)) {
+ ret |= ldb_msg_add_string(msg, "userWorkstations",
+ pdb_get_workstations(sam));
+ }
+
+ /* This will need work, it is actually a UTF8 'string' with internal NULLs, to handle TS parameters */
+ if (need_update(sam, PDB_MUNGEDDIAL)) {
+ const char *base64_munged_dial = NULL;
+
+ base64_munged_dial = pdb_get_munged_dial(sam);
+ if (base64_munged_dial != NULL && strlen(base64_munged_dial) > 0) {
+ struct ldb_val blob;
+
+ blob = base64_decode_data_blob_talloc(msg,
+ base64_munged_dial);
+ if (blob.data == NULL) {
+ DEBUG(0, ("Failed to decode userParameters from "
+ "munged dialback string[%s] for %s\n",
+ base64_munged_dial,
+ ldb_dn_get_linearized(msg->dn)));
+ talloc_free(frame);
+ return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+ }
+ ret |= ldb_msg_add_steal_value(msg, "userParameters",
+ &blob);
+ }
+ }
+
+ if (need_update(sam, PDB_COUNTRY_CODE)) {
+ ret |= ldb_msg_add_fmt(msg, "countryCode",
+ "%i", (int)pdb_get_country_code(sam));
+ }
+
+ if (need_update(sam, PDB_CODE_PAGE)) {
+ ret |= ldb_msg_add_fmt(msg, "codePage",
+ "%i", (int)pdb_get_code_page(sam));
+ }
+
+ /* Not yet handled here or not meaningful for modifies on a Samba_Dsdb backend:
+ PDB_BAD_PASSWORD_TIME,
+ PDB_CANCHANGETIME, - these are calculated per policy, not stored
+ PDB_DOMAIN,
+ PDB_NTUSERNAME, - this makes no sense, and never really did
+ PDB_LOGONDIVS,
+ PDB_USERSID, - Handled in pdb_samba_dsdb_add_sam_account()
+ PDB_FIELDS_PRESENT,
+ PDB_BAD_PASSWORD_COUNT,
+ PDB_LOGON_COUNT,
+ PDB_UNKNOWN6,
+ PDB_BACKEND_PRIVATE_DATA,
+
+ */
+ if (ret != LDB_SUCCESS) {
+ talloc_free(frame);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ if (msg->num_elements == 0) {
+ talloc_free(frame);
+ /* Nothing to do, just return success */
+ return LDB_SUCCESS;
+ }
+
+ ret = dsdb_replace(state->ldb, msg, dsdb_flags);
+
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,("Failed to modify account record %s to set user attributes: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(state->ldb)));
+ }
+
+ talloc_free(frame);
+ return ret;
+}
+
+static NTSTATUS pdb_samba_dsdb_getsamupriv(struct pdb_samba_dsdb_state *state,
+ const char *filter,
+ TALLOC_CTX *mem_ctx,
+ struct ldb_message **msg)
+{
+ const char * attrs[] = {
+ "lastLogon", "lastLogoff", "pwdLastSet", "accountExpires",
+ "sAMAccountName", "displayName", "homeDirectory",
+ "homeDrive", "scriptPath", "profilePath", "description",
+ "userWorkstations", "comment", "userParameters", "objectSid",
+ "primaryGroupID", "userAccountControl",
+ "msDS-User-Account-Control-Computed", "logonHours",
+ "badPwdCount", "logonCount", "countryCode", "codePage",
+ "unicodePwd", "dBCSPwd", NULL };
+
+ int rc = dsdb_search_one(state->ldb, mem_ctx, msg, ldb_get_default_basedn(state->ldb), LDB_SCOPE_SUBTREE, attrs, 0, "%s", filter);
+ if (rc != LDB_SUCCESS) {
+ DEBUG(10, ("ldap_search failed %s\n",
+ ldb_errstring(state->ldb)));
+ return NT_STATUS_LDAP(rc);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_getsampwfilter(struct pdb_methods *m,
+ struct pdb_samba_dsdb_state *state,
+ struct samu *sam_acct,
+ const char *exp_fmt, ...)
+ PRINTF_ATTRIBUTE(4,5);
+
+static NTSTATUS pdb_samba_dsdb_getsampwfilter(struct pdb_methods *m,
+ struct pdb_samba_dsdb_state *state,
+ struct samu *sam_acct,
+ const char *exp_fmt, ...)
+{
+ struct ldb_message *priv;
+ NTSTATUS status;
+ va_list ap;
+ char *expression = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(state);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ va_start(ap, exp_fmt);
+ expression = talloc_vasprintf(tmp_ctx, exp_fmt, ap);
+ va_end(ap);
+
+ if (!expression) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pdb_samba_dsdb_getsamupriv(state, expression, sam_acct, &priv);
+ talloc_free(tmp_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("pdb_samba_dsdb_getsamupriv failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = pdb_samba_dsdb_init_sam_from_priv(m, sam_acct, priv);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("pdb_samba_dsdb_init_sam_from_priv failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(priv);
+ return status;
+ }
+
+ pdb_set_backend_private_data(sam_acct, priv, NULL, m, PDB_SET);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_getsampwnam(struct pdb_methods *m,
+ struct samu *sam_acct,
+ const char *username)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+
+ return pdb_samba_dsdb_getsampwfilter(m, state, sam_acct,
+ "(&(samaccountname=%s)(objectclass=user))",
+ username);
+}
+
+static NTSTATUS pdb_samba_dsdb_getsampwsid(struct pdb_methods *m,
+ struct samu *sam_acct,
+ const struct dom_sid *sid)
+{
+ NTSTATUS status;
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct dom_sid_buf buf;
+
+ status = pdb_samba_dsdb_getsampwfilter(m, state, sam_acct,
+ "(&(objectsid=%s)(objectclass=user))",
+ dom_sid_str_buf(sid, &buf));
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_create_user(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ const char *name, uint32_t acct_flags,
+ uint32_t *rid)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct dom_sid *sid;
+ struct ldb_dn *dn;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ /* Internally this uses transactions to ensure all the steps
+ * happen or fail as one */
+ status = dsdb_add_user(state->ldb, tmp_ctx, name, acct_flags, NULL,
+ &sid, &dn);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ sid_peek_rid(sid, rid);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_delete_user(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ struct samu *sam)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct ldb_dn *dn;
+ int rc;
+ struct dom_sid_buf buf;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ dn = ldb_dn_new_fmt(
+ tmp_ctx,
+ state->ldb,
+ "<SID=%s>",
+ dom_sid_str_buf(pdb_get_user_sid(sam), &buf));
+ if (!dn || !ldb_dn_validate(dn)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ rc = ldb_delete(state->ldb, dn);
+
+ if (rc != LDB_SUCCESS) {
+ DEBUG(10, ("ldb_delete for %s failed: %s\n", ldb_dn_get_linearized(dn),
+ ldb_errstring(state->ldb)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_LDAP(rc);
+ }
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+/* This interface takes a fully populated struct samu and places it in
+ * the database. This is not implemented at this time as we need to
+ * be careful around the creation of arbitrary SIDs (ie, we must ensure
+ * they are not left in a RID pool */
+static NTSTATUS pdb_samba_dsdb_add_sam_account(struct pdb_methods *m,
+ struct samu *sampass)
+{
+ int ret;
+ NTSTATUS status;
+ struct ldb_dn *dn;
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ uint32_t acb_flags = pdb_get_acct_ctrl(sampass);
+ const char *username = pdb_get_username(sampass);
+ const struct dom_sid *user_sid = pdb_get_user_sid(sampass);
+ TALLOC_CTX *tframe = talloc_stackframe();
+
+ acb_flags &= (ACB_NORMAL|ACB_WSTRUST|ACB_SVRTRUST|ACB_DOMTRUST);
+
+ ret = ldb_transaction_start(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tframe);
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+
+ status = dsdb_add_user(state->ldb, talloc_tos(), username,
+ acb_flags, user_sid, NULL, &dn);
+ if (!NT_STATUS_IS_OK(status)) {
+ ldb_transaction_cancel(state->ldb);
+ talloc_free(tframe);
+ return status;
+ }
+
+ ret = pdb_samba_dsdb_replace_by_sam(state, pdb_element_is_set_or_changed,
+ dn, sampass);
+ if (ret != LDB_SUCCESS) {
+ ldb_transaction_cancel(state->ldb);
+ talloc_free(tframe);
+ return dsdb_ldb_err_to_ntstatus(ret);
+ }
+
+ ret = ldb_transaction_commit(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,("Failed to commit transaction to add and modify account record %s: %s\n",
+ ldb_dn_get_linearized(dn),
+ ldb_errstring(state->ldb)));
+ talloc_free(tframe);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ talloc_free(tframe);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Update the Samba_Dsdb LDB with the changes from a struct samu.
+ *
+ * This takes care not to update elements that have not been changed
+ * by the caller
+ */
+static NTSTATUS pdb_samba_dsdb_update_sam_account(struct pdb_methods *m,
+ struct samu *sam)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct ldb_message *msg = pdb_samba_dsdb_get_samu_private(
+ m, sam);
+ int ret;
+
+ ret = pdb_samba_dsdb_replace_by_sam(state, pdb_element_is_changed, msg->dn,
+ sam);
+ return dsdb_ldb_err_to_ntstatus(ret);
+}
+
+static NTSTATUS pdb_samba_dsdb_delete_sam_account(struct pdb_methods *m,
+ struct samu *username)
+{
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+ status = pdb_samba_dsdb_delete_user(m, tmp_ctx, username);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_rename_sam_account(struct pdb_methods *m,
+ struct samu *oldname,
+ const char *newname)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/* This is not implemented, as this module is expected to be used
+ * with auth_samba_dsdb, and this is responsible for login counters etc
+ *
+ */
+static NTSTATUS pdb_samba_dsdb_update_login_attempts(struct pdb_methods *m,
+ struct samu *sam_acct,
+ bool success)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_samba_dsdb_getgrfilter(struct pdb_methods *m,
+ GROUP_MAP *map,
+ const char *exp_fmt, ...)
+ PRINTF_ATTRIBUTE(3,4);
+
+static NTSTATUS pdb_samba_dsdb_getgrfilter(struct pdb_methods *m, GROUP_MAP *map,
+ const char *exp_fmt, ...)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ const char *attrs[] = { "objectClass", "objectSid", "description", "samAccountName", "groupType",
+ NULL };
+ struct ldb_message *msg;
+ va_list ap;
+ char *expression = NULL;
+ struct dom_sid *sid;
+ const char *str;
+ int rc;
+ struct id_map id_map;
+ struct id_map *id_maps[2];
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ va_start(ap, exp_fmt);
+ expression = talloc_vasprintf(tmp_ctx, exp_fmt, ap);
+ va_end(ap);
+
+ if (!expression) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rc = dsdb_search_one(state->ldb, tmp_ctx, &msg, ldb_get_default_basedn(state->ldb), LDB_SCOPE_SUBTREE, attrs, 0, "%s", expression);
+ if (rc == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_GROUP;
+ } else if (rc != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("dsdb_search_one failed %s\n",
+ ldb_errstring(state->ldb)));
+ return NT_STATUS_LDAP(rc);
+ }
+
+ sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid");
+ if (!sid) {
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("Could not pull SID\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ map->sid = *sid;
+
+ if (samdb_find_attribute(state->ldb, msg, "objectClass", "group")) {
+ NTSTATUS status;
+ uint32_t grouptype = ldb_msg_find_attr_as_uint(msg, "groupType", 0);
+ switch (grouptype) {
+ case GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+ case GTYPE_SECURITY_DOMAIN_LOCAL_GROUP:
+ map->sid_name_use = SID_NAME_ALIAS;
+ break;
+ case GTYPE_SECURITY_GLOBAL_GROUP:
+ map->sid_name_use = SID_NAME_DOM_GRP;
+ break;
+ default:
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("Could not pull groupType\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ ZERO_STRUCT(id_map);
+ id_map.sid = sid;
+ id_maps[0] = &id_map;
+ id_maps[1] = NULL;
+
+ status = idmap_sids_to_xids(state->idmap_ctx, tmp_ctx, id_maps);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ if (id_map.xid.type == ID_TYPE_GID || id_map.xid.type == ID_TYPE_BOTH) {
+ map->gid = id_map.xid.id;
+ } else {
+ DEBUG(1, (__location__ "Did not get GUID when mapping SID for %s\n", expression));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ } else if (samdb_find_attribute(state->ldb, msg, "objectClass", "user")) {
+ DEBUG(1, (__location__ "Got SID_NAME_USER when searching for a group with %s\n", expression));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "samAccountName",
+ NULL);
+ if (str == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ map->nt_name = talloc_strdup(map, str);
+ if (!map->nt_name) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "description",
+ NULL);
+ if (str != NULL) {
+ map->comment = talloc_strdup(map, str);
+ } else {
+ map->comment = talloc_strdup(map, "");
+ }
+ if (!map->comment) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_getgrsid(struct pdb_methods *m, GROUP_MAP *map,
+ struct dom_sid sid)
+{
+ char *filter;
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ filter = talloc_asprintf(talloc_tos(),
+ "(&(objectsid=%s)(objectclass=group))",
+ dom_sid_str_buf(&sid, &buf));
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pdb_samba_dsdb_getgrfilter(m, map, "%s", filter);
+ TALLOC_FREE(filter);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_getgrgid(struct pdb_methods *m, GROUP_MAP *map,
+ gid_t gid)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ NTSTATUS status;
+ struct id_map id_map;
+ struct id_map *id_maps[2];
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ id_map.xid.id = gid;
+ id_map.xid.type = ID_TYPE_GID;
+ id_maps[0] = &id_map;
+ id_maps[1] = NULL;
+
+ status = idmap_xids_to_sids(state->idmap_ctx, tmp_ctx, id_maps);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ status = pdb_samba_dsdb_getgrsid(m, map, *id_map.sid);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_getgrnam(struct pdb_methods *m, GROUP_MAP *map,
+ const char *name)
+{
+ char *filter;
+ NTSTATUS status;
+
+ filter = talloc_asprintf(talloc_tos(),
+ "(&(samaccountname=%s)(objectclass=group))",
+ name);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = pdb_samba_dsdb_getgrfilter(m, map, "%s", filter);
+ TALLOC_FREE(filter);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_create_dom_group(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx, const char *name,
+ uint32_t *rid)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ NTSTATUS status;
+ struct dom_sid *sid;
+ struct ldb_dn *dn;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ status = dsdb_add_domain_group(state->ldb, tmp_ctx, name, &sid, &dn);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ sid_peek_rid(sid, rid);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_delete_dom_group(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx, uint32_t rid)
+{
+ const char *attrs[] = { NULL };
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct dom_sid sid;
+ struct ldb_message *msg;
+ struct ldb_dn *dn;
+ int rc;
+ struct dom_sid_buf buf;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ sid_compose(&sid, samdb_domain_sid(state->ldb), rid);
+
+ if (ldb_transaction_start(state->ldb) != LDB_SUCCESS) {
+ DEBUG(0, ("Unable to start transaction in pdb_samba_dsdb_delete_dom_group()\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ dn = ldb_dn_new_fmt(
+ tmp_ctx,
+ state->ldb,
+ "<SID=%s>",
+ dom_sid_str_buf(&sid, &buf));
+ if (!dn || !ldb_dn_validate(dn)) {
+ talloc_free(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_NO_MEMORY;
+ }
+ rc = dsdb_search_one(state->ldb, tmp_ctx, &msg, dn, LDB_SCOPE_BASE, attrs, 0, "objectclass=group");
+ if (rc == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_NO_SUCH_GROUP;
+ } else if (rc != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("dsdb_search_one failed %s\n",
+ ldb_errstring(state->ldb)));
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_LDAP(rc);
+ }
+ rc = ldb_delete(state->ldb, dn);
+ if (rc == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_NO_SUCH_GROUP;
+ } else if (rc != LDB_SUCCESS) {
+ DEBUG(10, ("ldb_delete failed %s\n",
+ ldb_errstring(state->ldb)));
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_LDAP(rc);
+ }
+
+ if (ldb_transaction_commit(state->ldb) != LDB_SUCCESS) {
+ DEBUG(0, ("Unable to commit transaction in pdb_samba_dsdb_delete_dom_group()\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_add_group_mapping_entry(struct pdb_methods *m,
+ GROUP_MAP *map)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_samba_dsdb_update_group_mapping_entry(struct pdb_methods *m,
+ GROUP_MAP *map)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_samba_dsdb_delete_group_mapping_entry(struct pdb_methods *m,
+ struct dom_sid sid)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_samba_dsdb_enum_group_mapping(struct pdb_methods *m,
+ const struct dom_sid *sid,
+ enum lsa_SidType sid_name_use,
+ GROUP_MAP ***pp_rmap,
+ size_t *p_num_entries,
+ bool unix_only)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_samba_dsdb_enum_group_members(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group,
+ uint32_t **pmembers,
+ size_t *pnum_members)
+{
+ unsigned int i, num_sids, num_members;
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct dom_sid *members_as_sids;
+ struct dom_sid *dom_sid;
+ uint32_t *members;
+ struct ldb_dn *dn;
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ dn = ldb_dn_new_fmt(
+ tmp_ctx,
+ state->ldb,
+ "<SID=%s>",
+ dom_sid_str_buf(group, &buf));
+ if (!dn || !ldb_dn_validate(dn)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dsdb_enum_group_mem(state->ldb, tmp_ctx, dn, &members_as_sids, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ status = dom_sid_split_rid(tmp_ctx, group, &dom_sid, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+
+ *pmembers = members = talloc_array(mem_ctx, uint32_t, num_sids);
+ if (*pmembers == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ num_members = 0;
+
+ for (i = 0; i < num_sids; i++) {
+ if (!dom_sid_in_domain(dom_sid, &members_as_sids[i])) {
+ continue;
+ }
+ status = dom_sid_split_rid(NULL, &members_as_sids[i],
+ NULL, &members[num_members]);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ num_members++;
+ }
+ *pnum_members = num_members;
+ return NT_STATUS_OK;
+}
+
+/* Just convert the primary group SID into a group */
+static NTSTATUS fake_enum_group_memberships(struct pdb_samba_dsdb_state *state,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user,
+ struct dom_sid **pp_sids,
+ gid_t **pp_gids,
+ uint32_t *p_num_groups)
+{
+ NTSTATUS status;
+ size_t num_groups = 0;
+ struct dom_sid *group_sids = NULL;
+ gid_t *gids = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ if (user->group_sid) {
+ struct id_map *id_maps[2];
+ struct id_map id_map;
+
+ num_groups = 1;
+
+ group_sids = talloc_array(tmp_ctx, struct dom_sid, num_groups);
+ if (group_sids == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ gids = talloc_array(tmp_ctx, gid_t, num_groups);
+ if (gids == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ group_sids[0] = *user->group_sid;
+
+ ZERO_STRUCT(id_map);
+ id_map.sid = &group_sids[0];
+ id_maps[0] = &id_map;
+ id_maps[1] = NULL;
+
+ status = idmap_sids_to_xids(state->idmap_ctx, tmp_ctx, id_maps);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ if (id_map.xid.type == ID_TYPE_GID || id_map.xid.type == ID_TYPE_BOTH) {
+ gids[0] = id_map.xid.id;
+ } else {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(1, (__location__
+ "Group %s, of which %s is a member, could not be converted to a GID\n",
+ dom_sid_str_buf(&group_sids[0], &buf1),
+ dom_sid_str_buf(&user->user_sid, &buf2)));
+ talloc_free(tmp_ctx);
+ /* We must error out, otherwise a user might
+ * avoid a DENY acl based on a group they
+ * missed out on */
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+ }
+
+ *pp_sids = talloc_steal(mem_ctx, group_sids);
+ *pp_gids = talloc_steal(mem_ctx, gids);
+ *p_num_groups = num_groups;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_enum_group_memberships(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user,
+ struct dom_sid **pp_sids,
+ gid_t **pp_gids,
+ uint32_t *p_num_groups)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct ldb_message *msg = pdb_samba_dsdb_get_samu_private(
+ m, user);
+ const char *attrs[] = { "tokenGroups", NULL};
+ struct ldb_message *tokengroups_msg;
+ struct ldb_message_element *tokengroups;
+ int i, rc;
+ NTSTATUS status;
+ unsigned int count = 0;
+ size_t num_groups;
+ struct dom_sid *group_sids;
+ gid_t *gids;
+ TALLOC_CTX *tmp_ctx;
+
+ if (msg == NULL) {
+ /* Fake up some things here */
+ return fake_enum_group_memberships(state,
+ mem_ctx,
+ user, pp_sids,
+ pp_gids, p_num_groups);
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ rc = dsdb_search_one(state->ldb, tmp_ctx, &tokengroups_msg, msg->dn, LDB_SCOPE_BASE, attrs, 0, NULL);
+
+ if (rc == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_SUCH_USER;
+ } else if (rc != LDB_SUCCESS) {
+ DEBUG(10, ("dsdb_search_one failed %s\n",
+ ldb_errstring(state->ldb)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_LDAP(rc);
+ }
+
+ tokengroups = ldb_msg_find_element(tokengroups_msg, "tokenGroups");
+
+ if (tokengroups) {
+ count = tokengroups->num_values;
+ }
+
+ group_sids = talloc_array(tmp_ctx, struct dom_sid, count);
+ if (group_sids == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ gids = talloc_array(tmp_ctx, gid_t, count);
+ if (gids == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ num_groups = 0;
+
+ for (i=0; i<count; i++) {
+ struct id_map *id_maps[2];
+ struct id_map id_map;
+ struct ldb_val *v = &tokengroups->values[i];
+ enum ndr_err_code ndr_err
+ = ndr_pull_struct_blob(v, group_sids, &group_sids[num_groups],
+ (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ ZERO_STRUCT(id_map);
+ id_map.sid = &group_sids[num_groups];
+ id_maps[0] = &id_map;
+ id_maps[1] = NULL;
+
+ status = idmap_sids_to_xids(state->idmap_ctx, tmp_ctx, id_maps);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ if (id_map.xid.type == ID_TYPE_GID || id_map.xid.type == ID_TYPE_BOTH) {
+ gids[num_groups] = id_map.xid.id;
+ } else {
+ struct dom_sid_buf buf;
+ DEBUG(1, (__location__
+ "Group %s, of which %s is a member, could not be converted to a GID\n",
+ dom_sid_str_buf(&group_sids[num_groups],
+ &buf),
+ ldb_dn_get_linearized(msg->dn)));
+ talloc_free(tmp_ctx);
+ /* We must error out, otherwise a user might
+ * avoid a DENY acl based on a group they
+ * missed out on */
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ num_groups += 1;
+ if (num_groups == count) {
+ break;
+ }
+ }
+
+ *pp_sids = talloc_steal(mem_ctx, group_sids);
+ *pp_gids = talloc_steal(mem_ctx, gids);
+ *p_num_groups = num_groups;
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_set_unix_primary_group(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ struct samu *user)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_samba_dsdb_mod_groupmem_by_sid(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *groupsid,
+ const struct dom_sid *membersid,
+ int mod_op)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct ldb_message *msg;
+ int ret;
+ struct ldb_message_element *el;
+ struct dom_sid_buf buf;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ msg->dn = ldb_dn_new_fmt(
+ msg,
+ state->ldb,
+ "<SID=%s>",
+ dom_sid_str_buf(groupsid, &buf));
+ if (!msg->dn || !ldb_dn_validate(msg->dn)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = ldb_msg_add_fmt(
+ msg,
+ "member",
+ "<SID=%s>",
+ dom_sid_str_buf(membersid, &buf));
+ if (ret != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ el = ldb_msg_find_element(msg, "member");
+ el->flags = mod_op;
+
+ /* No need for transactions here, the ldb auto-transaction
+ * code will handle things for the single operation */
+ ret = ldb_modify(state->ldb, msg);
+ talloc_free(tmp_ctx);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(10, ("ldb_modify failed: %s\n",
+ ldb_errstring(state->ldb)));
+ if (ret == LDB_ERR_ATTRIBUTE_OR_VALUE_EXISTS) {
+ return NT_STATUS_MEMBER_IN_GROUP;
+ }
+ if (ret == LDB_ERR_NO_SUCH_ATTRIBUTE) {
+ return NT_STATUS_MEMBER_NOT_IN_GROUP;
+ }
+ return NT_STATUS_LDAP(ret);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_mod_groupmem(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ uint32_t grouprid, uint32_t memberrid,
+ int mod_op)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ const struct dom_sid *dom_sid, *groupsid, *membersid;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ dom_sid = samdb_domain_sid(state->ldb);
+
+ groupsid = dom_sid_add_rid(tmp_ctx, dom_sid, grouprid);
+ if (groupsid == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ membersid = dom_sid_add_rid(tmp_ctx, dom_sid, memberrid);
+ if (membersid == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = pdb_samba_dsdb_mod_groupmem_by_sid(m, tmp_ctx, groupsid, membersid, mod_op);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_add_groupmem(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ uint32_t group_rid, uint32_t member_rid)
+{
+ return pdb_samba_dsdb_mod_groupmem(m, mem_ctx, group_rid, member_rid,
+ LDB_FLAG_MOD_ADD);
+}
+
+static NTSTATUS pdb_samba_dsdb_del_groupmem(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ uint32_t group_rid, uint32_t member_rid)
+{
+ return pdb_samba_dsdb_mod_groupmem(m, mem_ctx, group_rid, member_rid,
+ LDB_FLAG_MOD_DELETE);
+}
+
+static NTSTATUS pdb_samba_dsdb_create_alias(struct pdb_methods *m,
+ const char *name, uint32_t *rid)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct dom_sid *sid;
+
+ struct ldb_dn *dn;
+ NTSTATUS status;
+
+ /* Internally this uses transactions to ensure all the steps
+ * happen or fail as one */
+ status = dsdb_add_domain_alias(state->ldb, frame, name, &sid, &dn);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ }
+
+ sid_peek_rid(sid, rid);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_delete_alias(struct pdb_methods *m,
+ const struct dom_sid *sid)
+{
+ const char *attrs[] = { NULL };
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct ldb_message *msg;
+ struct ldb_dn *dn;
+ int rc;
+ struct dom_sid_buf buf;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ dn = ldb_dn_new_fmt(
+ tmp_ctx,
+ state->ldb,
+ "<SID=%s>",
+ dom_sid_str_buf(sid, &buf));
+ if (!dn || !ldb_dn_validate(dn)) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ldb_transaction_start(state->ldb) != LDB_SUCCESS) {
+ DBG_ERR("Failed to start transaction: %s\n",
+ ldb_errstring(state->ldb));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ rc = dsdb_search_one(state->ldb, tmp_ctx, &msg, dn, LDB_SCOPE_BASE, attrs, 0, "(objectclass=group)"
+ "(|(grouptype=%d)(grouptype=%d)))",
+ GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
+ GTYPE_SECURITY_DOMAIN_LOCAL_GROUP);
+ if (rc == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ } else if (rc != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("dsdb_search_one failed %s\n",
+ ldb_errstring(state->ldb)));
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_LDAP(rc);
+ }
+ rc = ldb_delete(state->ldb, dn);
+ if (rc == LDB_ERR_NO_SUCH_OBJECT) {
+ talloc_free(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return NT_STATUS_NO_SUCH_ALIAS;
+ } else if (rc != LDB_SUCCESS) {
+ DEBUG(10, ("ldb_delete failed %s\n",
+ ldb_errstring(state->ldb)));
+ ldb_transaction_cancel(state->ldb);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_LDAP(rc);
+ }
+
+ if (ldb_transaction_commit(state->ldb) != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to commit transaction in pdb_samba_dsdb_delete_alias(): %s\n",
+ ldb_errstring(state->ldb)));
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_add_aliasmem(struct pdb_methods *m,
+ const struct dom_sid *alias,
+ const struct dom_sid *member)
+{
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ status = pdb_samba_dsdb_mod_groupmem_by_sid(m, frame, alias, member, LDB_FLAG_MOD_ADD);
+ talloc_free(frame);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_del_aliasmem(struct pdb_methods *m,
+ const struct dom_sid *alias,
+ const struct dom_sid *member)
+{
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ status = pdb_samba_dsdb_mod_groupmem_by_sid(m, frame, alias, member, LDB_FLAG_MOD_DELETE);
+ talloc_free(frame);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_enum_aliasmem(struct pdb_methods *m,
+ const struct dom_sid *alias,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid **pmembers,
+ size_t *pnum_members)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct ldb_dn *dn;
+ unsigned int num_members;
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ dn = ldb_dn_new_fmt(
+ tmp_ctx,
+ state->ldb,
+ "<SID=%s>",
+ dom_sid_str_buf(alias, &buf));
+ if (!dn || !ldb_dn_validate(dn)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dsdb_enum_group_mem(state->ldb, mem_ctx, dn, pmembers, &num_members);
+ if (NT_STATUS_IS_OK(status)) {
+ *pnum_members = num_members;
+ }
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_enum_alias_memberships(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *members,
+ size_t num_members,
+ uint32_t **palias_rids,
+ size_t *pnum_alias_rids)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ uint32_t *alias_rids = NULL;
+ size_t num_alias_rids = 0;
+ int i;
+ struct auth_SidAttr *groupSIDs = NULL;
+ uint32_t num_groupSIDs = 0;
+ char *filter;
+ NTSTATUS status;
+ const char *sid_dn;
+ DATA_BLOB sid_blob;
+
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+ /*
+ * TODO: Get the filter right so that we only get the aliases from
+ * either the SAM or BUILTIN
+ */
+
+ filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:"LDB_OID_COMPARATOR_AND":=%u))",
+ GROUP_TYPE_BUILTIN_LOCAL_GROUP);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_members; i++) {
+ struct dom_sid_buf buf;
+
+ sid_dn = talloc_asprintf(
+ tmp_ctx,
+ "<SID=%s>",
+ dom_sid_str_buf(&members[i], &buf));
+ if (sid_dn == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sid_blob = data_blob_string_const(sid_dn);
+
+ status = dsdb_expand_nested_groups(state->ldb, &sid_blob, true, filter,
+ tmp_ctx, &groupSIDs, &num_groupSIDs);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return status;
+ }
+ }
+
+ alias_rids = talloc_array(mem_ctx, uint32_t, num_groupSIDs);
+ if (alias_rids == NULL) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_groupSIDs; i++) {
+ if (sid_peek_check_rid(domain_sid, &groupSIDs[i].sid,
+ &alias_rids[num_alias_rids])) {
+ num_alias_rids++;;
+ }
+ }
+
+ *palias_rids = alias_rids;
+ *pnum_alias_rids = num_alias_rids;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_lookup_rids(struct pdb_methods *m,
+ const struct dom_sid *domain_sid,
+ int num_rids,
+ uint32_t *rids,
+ const char **names,
+ enum lsa_SidType *lsa_attrs)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ NTSTATUS status;
+
+ TALLOC_CTX *tmp_ctx;
+
+ if (num_rids == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ tmp_ctx = talloc_stackframe();
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ status = dsdb_lookup_rids(state->ldb, tmp_ctx, domain_sid, num_rids, rids, names, lsa_attrs);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_lookup_names(struct pdb_methods *m,
+ const struct dom_sid *domain_sid,
+ int num_names,
+ const char **pp_names,
+ uint32_t *rids,
+ enum lsa_SidType *attrs)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS pdb_samba_dsdb_get_account_policy(struct pdb_methods *m,
+ enum pdb_policy_type type,
+ uint32_t *value)
+{
+ return account_policy_get(type, value)
+ ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+static NTSTATUS pdb_samba_dsdb_set_account_policy(struct pdb_methods *m,
+ enum pdb_policy_type type,
+ uint32_t value)
+{
+ return account_policy_set(type, value)
+ ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+static NTSTATUS pdb_samba_dsdb_get_seq_num(struct pdb_methods *m,
+ time_t *seq_num_out)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ uint64_t seq_num;
+ int ret = ldb_sequence_number(state->ldb, LDB_SEQ_HIGHEST_SEQ, &seq_num);
+ if (ret == LDB_SUCCESS) {
+ *seq_num_out = seq_num;
+ return NT_STATUS_OK;
+ } else {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+}
+
+struct pdb_samba_dsdb_search_state {
+ uint32_t acct_flags;
+ struct samr_displayentry *entries;
+ uint32_t num_entries;
+ ssize_t array_size;
+ uint32_t current;
+};
+
+static bool pdb_samba_dsdb_next_entry(struct pdb_search *search,
+ struct samr_displayentry *entry)
+{
+ struct pdb_samba_dsdb_search_state *state = talloc_get_type_abort(
+ search->private_data, struct pdb_samba_dsdb_search_state);
+
+ if (state->current == state->num_entries) {
+ return false;
+ }
+
+ entry->idx = state->entries[state->current].idx;
+ entry->rid = state->entries[state->current].rid;
+ entry->acct_flags = state->entries[state->current].acct_flags;
+
+ entry->account_name = talloc_strdup(
+ search, state->entries[state->current].account_name);
+ entry->fullname = talloc_strdup(
+ search, state->entries[state->current].fullname);
+ entry->description = talloc_strdup(
+ search, state->entries[state->current].description);
+
+ state->current += 1;
+ return true;
+}
+
+static void pdb_samba_dsdb_search_end(struct pdb_search *search)
+{
+ struct pdb_samba_dsdb_search_state *state = talloc_get_type_abort(
+ search->private_data, struct pdb_samba_dsdb_search_state);
+ talloc_free(state);
+}
+
+static bool pdb_samba_dsdb_search_filter(struct pdb_methods *m,
+ struct pdb_search *search,
+ struct pdb_samba_dsdb_search_state **pstate,
+ const char *exp_fmt, ...)
+ PRINTF_ATTRIBUTE(4, 5);
+
+static bool pdb_samba_dsdb_search_filter(struct pdb_methods *m,
+ struct pdb_search *search,
+ struct pdb_samba_dsdb_search_state **pstate,
+ const char *exp_fmt, ...)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct pdb_samba_dsdb_search_state *sstate;
+ const char * attrs[] = { "objectSid", "sAMAccountName", "displayName",
+ "userAccountControl", "description", NULL };
+ struct ldb_result *res;
+ int i, rc, num_users;
+
+ va_list ap;
+ char *expression = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (!tmp_ctx) {
+ return false;
+ }
+
+ va_start(ap, exp_fmt);
+ expression = talloc_vasprintf(tmp_ctx, exp_fmt, ap);
+ va_end(ap);
+
+ if (!expression) {
+ talloc_free(tmp_ctx);
+ return LDB_ERR_OPERATIONS_ERROR;
+ }
+
+ sstate = talloc_zero(tmp_ctx, struct pdb_samba_dsdb_search_state);
+ if (sstate == NULL) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ rc = dsdb_search(state->ldb, tmp_ctx, &res, ldb_get_default_basedn(state->ldb), LDB_SCOPE_SUBTREE, attrs, 0, "%s", expression);
+ if (rc != LDB_SUCCESS) {
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("dsdb_search failed: %s\n",
+ ldb_errstring(state->ldb)));
+ return false;
+ }
+
+ num_users = res->count;
+
+ sstate->entries = talloc_array(sstate, struct samr_displayentry,
+ num_users);
+ if (sstate->entries == NULL) {
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("talloc failed\n"));
+ return false;
+ }
+
+ sstate->num_entries = 0;
+
+ for (i=0; i<num_users; i++) {
+ struct samr_displayentry *e;
+ struct dom_sid *sid;
+
+ e = &sstate->entries[sstate->num_entries];
+
+ e->idx = sstate->num_entries;
+ sid = samdb_result_dom_sid(tmp_ctx, res->msgs[i], "objectSid");
+ if (!sid) {
+ talloc_free(tmp_ctx);
+ DEBUG(10, ("Could not pull SID\n"));
+ return false;
+ }
+ sid_peek_rid(sid, &e->rid);
+
+ e->acct_flags = samdb_result_acct_flags(res->msgs[i], "userAccountControl");
+ e->account_name = ldb_msg_find_attr_as_string(
+ res->msgs[i], "samAccountName", NULL);
+ if (e->account_name == NULL) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ e->fullname = ldb_msg_find_attr_as_string(
+ res->msgs[i], "displayName", "");
+ e->description = ldb_msg_find_attr_as_string(
+ res->msgs[i], "description", "");
+
+ sstate->num_entries += 1;
+ if (sstate->num_entries >= num_users) {
+ break;
+ }
+ }
+ talloc_steal(sstate->entries, res->msgs);
+ search->private_data = talloc_steal(search, sstate);
+ search->next_entry = pdb_samba_dsdb_next_entry;
+ search->search_end = pdb_samba_dsdb_search_end;
+ *pstate = sstate;
+ talloc_free(tmp_ctx);
+ return true;
+}
+
+static bool pdb_samba_dsdb_search_users(struct pdb_methods *m,
+ struct pdb_search *search,
+ uint32_t acct_flags)
+{
+ struct pdb_samba_dsdb_search_state *sstate;
+ bool ret;
+
+ ret = pdb_samba_dsdb_search_filter(m, search, &sstate, "(objectclass=user)");
+ if (!ret) {
+ return false;
+ }
+ sstate->acct_flags = acct_flags;
+ return true;
+}
+
+static bool pdb_samba_dsdb_search_groups(struct pdb_methods *m,
+ struct pdb_search *search)
+{
+ struct pdb_samba_dsdb_search_state *sstate;
+ bool ret;
+
+ ret = pdb_samba_dsdb_search_filter(m, search, &sstate,
+ "(&(grouptype=%d)(objectclass=group))",
+ GTYPE_SECURITY_GLOBAL_GROUP);
+ if (!ret) {
+ return false;
+ }
+ sstate->acct_flags = 0;
+ return true;
+}
+
+static bool pdb_samba_dsdb_search_aliases(struct pdb_methods *m,
+ struct pdb_search *search,
+ const struct dom_sid *sid)
+{
+ struct pdb_samba_dsdb_search_state *sstate;
+ bool ret;
+
+ ret = pdb_samba_dsdb_search_filter(m, search, &sstate,
+ "(&(grouptype=%d)(objectclass=group))",
+ sid_check_is_builtin(sid)
+ ? GTYPE_SECURITY_BUILTIN_LOCAL_GROUP
+ : GTYPE_SECURITY_DOMAIN_LOCAL_GROUP);
+ if (!ret) {
+ return false;
+ }
+ sstate->acct_flags = 0;
+ return true;
+}
+
+/*
+ * Instead of taking a gid or uid, this function takes a pointer to a
+ * unixid.
+ *
+ * This acts as an in-out variable so that the idmap functions can correctly
+ * receive ID_TYPE_BOTH, and this function ensures cache details are filled
+ * correctly rather than forcing the cache to store ID_TYPE_UID or ID_TYPE_GID.
+ */
+static bool pdb_samba_dsdb_id_to_sid(struct pdb_methods *m, struct unixid *id,
+ struct dom_sid *sid)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ NTSTATUS status;
+ struct id_map id_map;
+ struct id_map *id_maps[2];
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (!tmp_ctx) {
+ return false;
+ }
+
+ id_map.xid = *id;
+ id_maps[0] = &id_map;
+ id_maps[1] = NULL;
+
+ status = idmap_xids_to_sids(state->idmap_ctx, tmp_ctx, id_maps);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ if (id_map.xid.type != ID_TYPE_NOT_SPECIFIED) {
+ id->type = id_map.xid.type;
+ }
+ *sid = *id_map.sid;
+ talloc_free(tmp_ctx);
+ return true;
+}
+
+static bool pdb_samba_dsdb_sid_to_id(struct pdb_methods *m, const struct dom_sid *sid,
+ struct unixid *id)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ struct id_map id_map;
+ struct id_map *id_maps[2];
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (!tmp_ctx) {
+ return false;
+ }
+
+ ZERO_STRUCT(id_map);
+ id_map.sid = discard_const_p(struct dom_sid, sid);
+ id_maps[0] = &id_map;
+ id_maps[1] = NULL;
+
+ status = idmap_sids_to_xids(state->idmap_ctx, tmp_ctx, id_maps);
+ talloc_free(tmp_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ if (id_map.xid.type != ID_TYPE_NOT_SPECIFIED) {
+ *id = id_map.xid;
+ return true;
+ }
+ return false;
+}
+
+static uint32_t pdb_samba_dsdb_capabilities(struct pdb_methods *m)
+{
+ return PDB_CAP_STORE_RIDS | PDB_CAP_ADS | PDB_CAP_TRUSTED_DOMAINS_EX;
+}
+
+static bool pdb_samba_dsdb_new_rid(struct pdb_methods *m, uint32_t *rid)
+{
+ return false;
+}
+
+static bool pdb_samba_dsdb_get_trusteddom_pw(struct pdb_methods *m,
+ const char *domain, char** pwd,
+ struct dom_sid *sid,
+ time_t *pass_last_set_time)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char * const attrs[] = {
+ "securityIdentifier",
+ "flatName",
+ "trustPartner",
+ "trustAuthOutgoing",
+ "whenCreated",
+ "msDS-SupportedEncryptionTypes",
+ "trustAttributes",
+ "trustDirection",
+ "trustType",
+ NULL
+ };
+ struct ldb_message *msg;
+ const struct ldb_val *password_val;
+ int trust_direction_flags;
+ int trust_type;
+ int i;
+ DATA_BLOB password_utf16;
+ struct trustAuthInOutBlob password_blob;
+ struct AuthenticationInformationArray *auth_array;
+ char *password_talloc;
+ size_t password_len;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ const char *netbios_domain = NULL;
+ const struct dom_sid *domain_sid = NULL;
+
+ status = dsdb_trust_search_tdo(state->ldb, domain, NULL,
+ attrs, tmp_ctx, &msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * This can be called to work out of a domain is
+ * trusted, rather than just to get the password
+ */
+ DEBUG(2, ("Failed to get trusted domain password for %s - %s. "
+ "It may not be a trusted domain.\n", domain,
+ nt_errstr(status)));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ netbios_domain = ldb_msg_find_attr_as_string(msg, "flatName", NULL);
+ if (netbios_domain == NULL) {
+ DEBUG(2, ("Trusted domain %s has to flatName defined.\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ domain_sid = samdb_result_dom_sid(tmp_ctx, msg, "securityIdentifier");
+ if (domain_sid == NULL) {
+ DEBUG(2, ("Trusted domain %s has no securityIdentifier defined.\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ trust_direction_flags = ldb_msg_find_attr_as_int(msg, "trustDirection", 0);
+ if (!(trust_direction_flags & LSA_TRUST_DIRECTION_OUTBOUND)) {
+ DBG_WARNING("Trusted domain %s is not an outbound trust.\n",
+ domain);
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ trust_type = ldb_msg_find_attr_as_int(msg, "trustType", 0);
+ if (trust_type == LSA_TRUST_TYPE_MIT) {
+ DBG_WARNING("Trusted domain %s is not an AD trust "
+ "(trustType == LSA_TRUST_TYPE_MIT).\n", domain);
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ password_val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing");
+ if (password_val == NULL) {
+ DEBUG(2, ("Failed to get trusted domain password for %s, "
+ "attribute trustAuthOutgoing not returned.\n", domain));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ ndr_err = ndr_pull_struct_blob(password_val, tmp_ctx, &password_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("Failed to get trusted domain password for %s, "
+ "attribute trustAuthOutgoing could not be parsed %s.\n",
+ domain,
+ ndr_map_error2string(ndr_err)));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ auth_array = &password_blob.current;
+
+ for (i=0; i < auth_array->count; i++) {
+ if (auth_array->array[i].AuthType == TRUST_AUTH_TYPE_CLEAR) {
+ break;
+ }
+ }
+
+ if (i == auth_array->count) {
+ DEBUG(0, ("Trusted domain %s does not have a "
+ "clear-text password stored\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ password_utf16 = data_blob_const(auth_array->array[i].AuthInfo.clear.password,
+ auth_array->array[i].AuthInfo.clear.size);
+
+ /*
+ * In the future, make this function return a
+ * cli_credentials that can store a MD4 hash with cli_credential_set_nt_hash()
+ * but for now convert to UTF8 and fail if the string can not be converted.
+ *
+ * We can't safely convert the random strings windows uses into
+ * utf8.
+ */
+ if (!convert_string_talloc(tmp_ctx,
+ CH_UTF16MUNGED, CH_UTF8,
+ password_utf16.data, password_utf16.length,
+ (void *)&password_talloc,
+ &password_len)) {
+ DEBUG(0, ("FIXME: Could not convert password for trusted domain %s"
+ " to UTF8. This may be a password set from Windows.\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+ *pwd = SMB_STRNDUP(password_talloc, password_len);
+ if (pass_last_set_time) {
+ *pass_last_set_time = nt_time_to_unix(auth_array->array[i].LastUpdateTime);
+ }
+
+ if (sid != NULL) {
+ sid_copy(sid, domain_sid);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return true;
+}
+
+static NTSTATUS pdb_samba_dsdb_get_trusteddom_creds(struct pdb_methods *m,
+ const char *domain,
+ TALLOC_CTX *mem_ctx,
+ struct cli_credentials **_creds)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char * const attrs[] = {
+ "securityIdentifier",
+ "flatName",
+ "trustPartner",
+ "trustAuthOutgoing",
+ "whenCreated",
+ "msDS-SupportedEncryptionTypes",
+ "trustAttributes",
+ "trustDirection",
+ "trustType",
+ NULL
+ };
+ struct ldb_message *msg;
+ const struct ldb_val *password_val;
+ int trust_direction_flags;
+ int trust_type;
+ int i;
+ DATA_BLOB password_utf16 = {};
+ struct samr_Password *password_nt = NULL;
+ uint32_t password_version = 0;
+ DATA_BLOB old_password_utf16 = {};
+ struct samr_Password *old_password_nt = NULL;
+ struct trustAuthInOutBlob password_blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ time_t last_set_time = 0;
+ struct cli_credentials *creds = NULL;
+ bool ok;
+ const char *my_netbios_name = NULL;
+ const char *my_netbios_domain = NULL;
+ const char *my_dns_domain = NULL;
+ const char *netbios_domain = NULL;
+ char *account_name = NULL;
+ char *principal_name = NULL;
+ const char *dns_domain = NULL;
+
+ status = dsdb_trust_search_tdo(state->ldb, domain, NULL,
+ attrs, tmp_ctx, &msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * This can be called to work out of a domain is
+ * trusted, rather than just to get the password
+ */
+ DEBUG(2, ("Failed to get trusted domain password for %s - %s "
+ "It may not be a trusted domain.\n", domain,
+ nt_errstr(status)));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ netbios_domain = ldb_msg_find_attr_as_string(msg, "flatName", NULL);
+ if (netbios_domain == NULL) {
+ DEBUG(2, ("Trusted domain %s has to flatName defined.\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ dns_domain = ldb_msg_find_attr_as_string(msg, "trustPartner", NULL);
+
+ trust_direction_flags = ldb_msg_find_attr_as_int(msg, "trustDirection", 0);
+ if (!(trust_direction_flags & LSA_TRUST_DIRECTION_OUTBOUND)) {
+ DBG_WARNING("Trusted domain %s is not an outbound trust.\n",
+ domain);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ trust_type = ldb_msg_find_attr_as_int(msg, "trustType", 0);
+ if (trust_type == LSA_TRUST_TYPE_MIT) {
+ DBG_WARNING("Trusted domain %s is not an AD trust "
+ "(trustType == LSA_TRUST_TYPE_MIT).\n", domain);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ password_val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing");
+ if (password_val == NULL) {
+ DEBUG(2, ("Failed to get trusted domain password for %s, "
+ "attribute trustAuthOutgoing not returned.\n", domain));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ ndr_err = ndr_pull_struct_blob(password_val, tmp_ctx, &password_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("Failed to get trusted domain password for %s, "
+ "attribute trustAuthOutgoing could not be parsed %s.\n",
+ domain,
+ ndr_map_error2string(ndr_err)));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ for (i=0; i < password_blob.current.count; i++) {
+ struct AuthenticationInformation *a =
+ &password_blob.current.array[i];
+
+ switch (a->AuthType) {
+ case TRUST_AUTH_TYPE_NONE:
+ break;
+
+ case TRUST_AUTH_TYPE_VERSION:
+ password_version = a->AuthInfo.version.version;
+ break;
+
+ case TRUST_AUTH_TYPE_CLEAR:
+ last_set_time = nt_time_to_unix(a->LastUpdateTime);
+
+ password_utf16 = data_blob_const(a->AuthInfo.clear.password,
+ a->AuthInfo.clear.size);
+ password_nt = NULL;
+ break;
+
+ case TRUST_AUTH_TYPE_NT4OWF:
+ if (password_utf16.length != 0) {
+ break;
+ }
+
+ last_set_time = nt_time_to_unix(a->LastUpdateTime);
+
+ password_nt = &a->AuthInfo.nt4owf.password;
+ break;
+ }
+ }
+
+ for (i=0; i < password_blob.previous.count; i++) {
+ struct AuthenticationInformation *a = &password_blob.previous.array[i];
+
+ switch (a->AuthType) {
+ case TRUST_AUTH_TYPE_NONE:
+ break;
+
+ case TRUST_AUTH_TYPE_VERSION:
+ break;
+
+ case TRUST_AUTH_TYPE_CLEAR:
+ old_password_utf16 = data_blob_const(a->AuthInfo.clear.password,
+ a->AuthInfo.clear.size);
+ old_password_nt = NULL;
+ break;
+
+ case TRUST_AUTH_TYPE_NT4OWF:
+ if (old_password_utf16.length != 0) {
+ break;
+ }
+
+ old_password_nt = &a->AuthInfo.nt4owf.password;
+ break;
+ }
+ }
+
+ if (password_utf16.length == 0 && password_nt == NULL) {
+ DEBUG(0, ("Trusted domain %s does not have a "
+ "clear-text nor nt password stored\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ my_netbios_name = lpcfg_netbios_name(state->lp_ctx);
+ my_netbios_domain = lpcfg_workgroup(state->lp_ctx);
+ my_dns_domain = lpcfg_dnsdomain(state->lp_ctx);
+
+ creds = cli_credentials_init(tmp_ctx);
+ if (creds == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = cli_credentials_set_workstation(creds, my_netbios_name, CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ok = cli_credentials_set_realm(creds, dns_domain, CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (my_dns_domain != NULL && dns_domain != NULL) {
+ cli_credentials_set_secure_channel_type(creds, SEC_CHAN_DNS_DOMAIN);
+ account_name = talloc_asprintf(tmp_ctx, "%s.", my_dns_domain);
+ if (account_name == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ principal_name = talloc_asprintf(tmp_ctx, "%s$@%s", my_netbios_domain,
+ cli_credentials_get_realm(creds));
+ if (principal_name == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ cli_credentials_set_secure_channel_type(creds, SEC_CHAN_DOMAIN);
+ account_name = talloc_asprintf(tmp_ctx, "%s$", my_netbios_domain);
+ if (account_name == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ principal_name = NULL;
+ }
+
+ ok = cli_credentials_set_username(creds, account_name, CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (principal_name != NULL) {
+ ok = cli_credentials_set_principal(creds, principal_name,
+ CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (old_password_nt != NULL) {
+ ok = cli_credentials_set_old_nt_hash(creds, old_password_nt);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (old_password_utf16.length > 0) {
+ ok = cli_credentials_set_old_utf16_password(creds,
+ &old_password_utf16);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (password_nt != NULL) {
+ ok = cli_credentials_set_nt_hash(creds, password_nt,
+ CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (password_utf16.length > 0) {
+ ok = cli_credentials_set_utf16_password(creds,
+ &password_utf16,
+ CRED_SPECIFIED);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ cli_credentials_set_password_last_changed_time(creds, last_set_time);
+ cli_credentials_set_kvno(creds, password_version);
+
+ if (password_utf16.length > 0 && dns_domain != NULL) {
+ /*
+ * Force kerberos if this is an active directory domain
+ */
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+ } else {
+ /*
+ * TODO: we should allow krb5 with the raw nt hash.
+ */
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+ }
+
+ *_creds = talloc_move(mem_ctx, &creds);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static bool pdb_samba_dsdb_set_trusteddom_pw(struct pdb_methods *m,
+ const char* domain, const char* pwd,
+ const struct dom_sid *sid)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char * const attrs[] = {
+ "trustAuthOutgoing",
+ "trustDirection",
+ "trustType",
+ NULL
+ };
+ struct ldb_message *msg = NULL;
+ int trust_direction_flags;
+ int trust_type;
+ uint32_t i; /* The same type as old_blob.current.count */
+ const struct ldb_val *old_val = NULL;
+ struct trustAuthInOutBlob old_blob = {};
+ uint32_t old_version = 0;
+ uint32_t new_version = 0;
+ DATA_BLOB new_utf16 = {};
+ struct trustAuthInOutBlob new_blob = {};
+ struct ldb_val new_val = {};
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ bool ok;
+ int ret;
+
+ ret = ldb_transaction_start(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(2, ("Failed to start transaction.\n"));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ ok = samdb_is_pdc(state->ldb);
+ if (!ok) {
+ DEBUG(2, ("Password changes for domain %s are only allowed on a PDC.\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ status = dsdb_trust_search_tdo(state->ldb, domain, NULL,
+ attrs, tmp_ctx, &msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * This can be called to work out of a domain is
+ * trusted, rather than just to get the password
+ */
+ DEBUG(2, ("Failed to get trusted domain password for %s - %s. "
+ "It may not be a trusted domain.\n", domain,
+ nt_errstr(status)));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ trust_direction_flags = ldb_msg_find_attr_as_int(msg, "trustDirection", 0);
+ if (!(trust_direction_flags & LSA_TRUST_DIRECTION_OUTBOUND)) {
+ DBG_WARNING("Trusted domain %s is not an outbound trust, can't set a password.\n",
+ domain);
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ trust_type = ldb_msg_find_attr_as_int(msg, "trustType", 0);
+ switch (trust_type) {
+ case LSA_TRUST_TYPE_DOWNLEVEL:
+ case LSA_TRUST_TYPE_UPLEVEL:
+ break;
+ default:
+ DEBUG(0, ("Trusted domain %s is of type 0x%X - "
+ "password changes are not supported\n",
+ domain, (unsigned)trust_type));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ old_val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing");
+ if (old_val != NULL) {
+ ndr_err = ndr_pull_struct_blob(old_val, tmp_ctx, &old_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("Failed to get trusted domain password for %s, "
+ "attribute trustAuthOutgoing could not be parsed %s.\n",
+ domain,
+ ndr_map_error2string(ndr_err)));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+ }
+
+ for (i=0; i < old_blob.current.count; i++) {
+ struct AuthenticationInformation *a =
+ &old_blob.current.array[i];
+
+ switch (a->AuthType) {
+ case TRUST_AUTH_TYPE_NONE:
+ break;
+
+ case TRUST_AUTH_TYPE_VERSION:
+ old_version = a->AuthInfo.version.version;
+ break;
+
+ case TRUST_AUTH_TYPE_CLEAR:
+ break;
+
+ case TRUST_AUTH_TYPE_NT4OWF:
+ break;
+ }
+ }
+
+ new_version = old_version + 1;
+ ok = convert_string_talloc(tmp_ctx,
+ CH_UNIX, CH_UTF16,
+ pwd, strlen(pwd),
+ (void *)&new_utf16.data,
+ &new_utf16.length);
+ if (!ok) {
+ DEBUG(0, ("Failed to generate new_utf16 password for domain %s\n",
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ if (new_utf16.length < 28) {
+ DEBUG(0, ("new_utf16[%zu] version[%u] for domain %s to short.\n",
+ new_utf16.length,
+ (unsigned)new_version,
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+ if (new_utf16.length > 498) {
+ DEBUG(0, ("new_utf16[%zu] version[%u] for domain %s to long.\n",
+ new_utf16.length,
+ (unsigned)new_version,
+ domain));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ new_blob.count = MAX(old_blob.current.count, 2);
+ new_blob.current.array = talloc_zero_array(tmp_ctx,
+ struct AuthenticationInformation,
+ new_blob.count);
+ if (new_blob.current.array == NULL) {
+ DEBUG(0, ("talloc_zero_array(%u) failed\n",
+ (unsigned)new_blob.count));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+ new_blob.previous.array = talloc_zero_array(tmp_ctx,
+ struct AuthenticationInformation,
+ new_blob.count);
+ if (new_blob.current.array == NULL) {
+ DEBUG(0, ("talloc_zero_array(%u) failed\n",
+ (unsigned)new_blob.count));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ for (i = 0; i < old_blob.current.count; i++) {
+ new_blob.previous.array[i] = old_blob.current.array[i];
+ new_blob.previous.count++;
+ }
+ for (; i < new_blob.count; i++) {
+ struct AuthenticationInformation *pi =
+ &new_blob.previous.array[i];
+
+ if (i == 0) {
+ /*
+ * new_blob.previous is still empty so
+ * we'll do new_blob.previous = new_blob.current
+ * below.
+ */
+ break;
+ }
+
+ pi->LastUpdateTime = now;
+ pi->AuthType = TRUST_AUTH_TYPE_NONE;
+ new_blob.previous.count++;
+ }
+
+ for (i = 0; i < new_blob.count; i++) {
+ struct AuthenticationInformation *ci =
+ &new_blob.current.array[i];
+
+ ci->LastUpdateTime = now;
+ switch (i) {
+ case 0:
+ ci->AuthType = TRUST_AUTH_TYPE_CLEAR;
+ ci->AuthInfo.clear.size = new_utf16.length;
+ ci->AuthInfo.clear.password = new_utf16.data;
+ break;
+ case 1:
+ ci->AuthType = TRUST_AUTH_TYPE_VERSION;
+ ci->AuthInfo.version.version = new_version;
+ break;
+ default:
+ ci->AuthType = TRUST_AUTH_TYPE_NONE;
+ break;
+ }
+
+ new_blob.current.count++;
+ }
+
+ if (new_blob.previous.count == 0) {
+ TALLOC_FREE(new_blob.previous.array);
+ new_blob.previous = new_blob.current;
+ }
+
+ ndr_err = ndr_push_struct_blob(&new_val, tmp_ctx, &new_blob,
+ (ndr_push_flags_fn_t)ndr_push_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("Failed to generate trustAuthOutgoing for "
+ "trusted domain password for %s: %s.\n",
+ domain, ndr_map_error2string(ndr_err)));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ msg->num_elements = 0;
+ ret = ldb_msg_append_value(msg, "trustAuthOutgoing",
+ &new_val, LDB_FLAG_MOD_REPLACE);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("ldb_msg_append_value() failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ ret = ldb_modify(state->ldb, msg);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to replace trustAuthOutgoing for "
+ "trusted domain password for %s: %s - %s\n",
+ domain, ldb_strerror(ret), ldb_errstring(state->ldb)));
+ TALLOC_FREE(tmp_ctx);
+ ldb_transaction_cancel(state->ldb);
+ return false;
+ }
+
+ ret = ldb_transaction_commit(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0, ("Failed to commit trustAuthOutgoing for "
+ "trusted domain password for %s: %s - %s\n",
+ domain, ldb_strerror(ret), ldb_errstring(state->ldb)));
+ TALLOC_FREE(tmp_ctx);
+ return false;
+ }
+
+ DEBUG(1, ("Added new_version[%u] to trustAuthOutgoing for "
+ "trusted domain password for %s.\n",
+ (unsigned)new_version, domain));
+ TALLOC_FREE(tmp_ctx);
+ return true;
+}
+
+static bool pdb_samba_dsdb_del_trusteddom_pw(struct pdb_methods *m,
+ const char *domain)
+{
+ return false;
+}
+
+static NTSTATUS pdb_samba_dsdb_enum_trusteddoms(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *_num_domains,
+ struct trustdom_info ***_domains)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char * const attrs[] = {
+ "securityIdentifier",
+ "flatName",
+ "trustDirection",
+ NULL
+ };
+ struct ldb_result *res = NULL;
+ unsigned int i;
+ struct trustdom_info **domains = NULL;
+ NTSTATUS status;
+ uint32_t di = 0;
+
+ *_num_domains = 0;
+ *_domains = NULL;
+
+ status = dsdb_trust_search_tdos(state->ldb, NULL,
+ attrs, tmp_ctx, &res);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dsdb_trust_search_tdos() - %s\n", nt_errstr(status));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ if (res->count == 0) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ domains = talloc_zero_array(tmp_ctx, struct trustdom_info *,
+ res->count);
+ if (domains == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < res->count; i++) {
+ struct ldb_message *msg = res->msgs[i];
+ struct trustdom_info *d = NULL;
+ const char *name = NULL;
+ struct dom_sid *sid = NULL;
+ uint32_t direction;
+
+ d = talloc_zero(domains, struct trustdom_info);
+ if (d == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ name = ldb_msg_find_attr_as_string(msg, "flatName", NULL);
+ if (name == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ sid = samdb_result_dom_sid(msg, msg, "securityIdentifier");
+ if (sid == NULL) {
+ continue;
+ }
+
+ direction = ldb_msg_find_attr_as_uint(msg, "trustDirection", 0);
+ if (!(direction & LSA_TRUST_DIRECTION_OUTBOUND)) {
+ continue;
+ }
+
+ d->name = talloc_strdup(d, name);
+ if (d->name == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ d->sid = *sid;
+
+ domains[di++] = d;
+ }
+
+ domains = talloc_realloc(domains, domains, struct trustdom_info *, di);
+ *_domains = talloc_move(mem_ctx, &domains);
+ *_num_domains = di;
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_msg_to_trusted_domain(const struct ldb_message *msg,
+ TALLOC_CTX *mem_ctx,
+ struct pdb_trusted_domain **_d)
+{
+ struct pdb_trusted_domain *d = NULL;
+ const char *str = NULL;
+ struct dom_sid *sid = NULL;
+ const struct ldb_val *val = NULL;
+ uint64_t val64;
+
+ *_d = NULL;
+
+ d = talloc_zero(mem_ctx, struct pdb_trusted_domain);
+ if (d == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "flatName", NULL);
+ if (str == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ d->netbios_name = talloc_strdup(d, str);
+ if (d->netbios_name == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ str = ldb_msg_find_attr_as_string(msg, "trustPartner", NULL);
+ if (str != NULL) {
+ d->domain_name = talloc_strdup(d, str);
+ if (d->domain_name == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ sid = samdb_result_dom_sid(d, msg, "securityIdentifier");
+ if (sid != NULL) {
+ d->security_identifier = *sid;
+ TALLOC_FREE(sid);
+ }
+
+ val = ldb_msg_find_ldb_val(msg, "trustAuthOutgoing");
+ if (val != NULL) {
+ d->trust_auth_outgoing = data_blob_dup_talloc(d, *val);
+ if (d->trust_auth_outgoing.data == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ val = ldb_msg_find_ldb_val(msg, "trustAuthIncoming");
+ if (val != NULL) {
+ d->trust_auth_incoming = data_blob_dup_talloc(d, *val);
+ if (d->trust_auth_incoming.data == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ d->trust_direction = ldb_msg_find_attr_as_uint(msg, "trustDirection", 0);
+ d->trust_type = ldb_msg_find_attr_as_uint(msg, "trustType", 0);
+ d->trust_attributes = ldb_msg_find_attr_as_uint(msg, "trustAttributes", 0);
+
+ val64 = ldb_msg_find_attr_as_uint64(msg, "trustPosixOffset", UINT64_MAX);
+ if (val64 != UINT64_MAX) {
+ d->trust_posix_offset = talloc(d, uint32_t);
+ if (d->trust_posix_offset == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *d->trust_posix_offset = (uint32_t)val64;
+ }
+
+ val64 = ldb_msg_find_attr_as_uint64(msg, "msDS-SupportedEncryptionTypes", UINT64_MAX);
+ if (val64 != UINT64_MAX) {
+ d->supported_enc_type = talloc(d, uint32_t);
+ if (d->supported_enc_type == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *d->supported_enc_type = (uint32_t)val64;
+ }
+
+ val = ldb_msg_find_ldb_val(msg, "msDS-TrustForestTrustInfo");
+ if (val != NULL) {
+ d->trust_forest_trust_info = data_blob_dup_talloc(d, *val);
+ if (d->trust_forest_trust_info.data == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_d = d;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_get_trusted_domain(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ const char *domain,
+ struct pdb_trusted_domain **td)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char * const attrs[] = {
+ "securityIdentifier",
+ "flatName",
+ "trustPartner",
+ "trustAuthOutgoing",
+ "trustAuthIncoming",
+ "trustAttributes",
+ "trustDirection",
+ "trustType",
+ "trustPosixOffset",
+ "msDS-SupportedEncryptionTypes",
+ "msDS-TrustForestTrustInfo",
+ NULL
+ };
+ struct ldb_message *msg = NULL;
+ struct pdb_trusted_domain *d = NULL;
+ NTSTATUS status;
+
+ status = dsdb_trust_search_tdo(state->ldb, domain, NULL,
+ attrs, tmp_ctx, &msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dsdb_trust_search_tdo(%s) - %s\n",
+ domain, nt_errstr(status));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ status = pdb_samba_dsdb_msg_to_trusted_domain(msg, mem_ctx, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("pdb_samba_dsdb_msg_to_trusted_domain(%s) - %s\n",
+ domain, nt_errstr(status));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ *td = d;
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_get_trusted_domain_by_sid(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid *sid,
+ struct pdb_trusted_domain **td)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char * const attrs[] = {
+ "securityIdentifier",
+ "flatName",
+ "trustPartner",
+ "trustAuthOutgoing",
+ "trustAuthIncoming",
+ "trustAttributes",
+ "trustDirection",
+ "trustType",
+ "trustPosixOffset",
+ "msDS-SupportedEncryptionTypes",
+ "msDS-TrustForestTrustInfo",
+ NULL
+ };
+ struct ldb_message *msg = NULL;
+ struct pdb_trusted_domain *d = NULL;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ status = dsdb_trust_search_tdo_by_sid(state->ldb, sid,
+ attrs, tmp_ctx, &msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dsdb_trust_search_tdo_by_sid(%s) - %s\n",
+ dom_sid_str_buf(sid, &buf),
+ nt_errstr(status));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ status = pdb_samba_dsdb_msg_to_trusted_domain(msg, mem_ctx, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("pdb_samba_dsdb_msg_to_trusted_domain(%s) - %s\n",
+ dom_sid_str_buf(sid, &buf),
+ nt_errstr(status));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ *td = d;
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS add_trust_user(TALLOC_CTX *mem_ctx,
+ struct ldb_context *sam_ldb,
+ struct ldb_dn *base_dn,
+ const char *netbios_name,
+ struct trustAuthInOutBlob *taiob)
+{
+ struct ldb_request *req = NULL;
+ struct ldb_message *msg = NULL;
+ struct ldb_dn *dn = NULL;
+ uint32_t i;
+ int ret;
+ bool ok;
+
+ dn = ldb_dn_copy(mem_ctx, base_dn);
+ if (dn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ok = ldb_dn_add_child_fmt(dn, "cn=%s$,cn=users", netbios_name);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ msg = ldb_msg_new(mem_ctx);
+ if (msg == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ msg->dn = dn;
+
+ ret = ldb_msg_add_string(msg, "objectClass", "user");
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = ldb_msg_add_fmt(msg, "samAccountName", "%s$", netbios_name);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = samdb_msg_add_uint(sam_ldb, msg, msg, "userAccountControl",
+ UF_INTERDOMAIN_TRUST_ACCOUNT);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < taiob->count; i++) {
+ struct AuthenticationInformation *auth_info =
+ &taiob->current.array[i];
+ const char *attribute = NULL;
+ struct ldb_val v;
+
+ switch (taiob->current.array[i].AuthType) {
+ case TRUST_AUTH_TYPE_NT4OWF:
+ attribute = "unicodePwd";
+ v.data = (uint8_t *)&auth_info->AuthInfo.nt4owf.password;
+ v.length = 16;
+ break;
+
+ case TRUST_AUTH_TYPE_CLEAR:
+ attribute = "clearTextPassword";
+ v.data = auth_info->AuthInfo.clear.password;
+ v.length = auth_info->AuthInfo.clear.size;
+ break;
+
+ default:
+ continue;
+ }
+
+ ret = ldb_msg_add_value(msg, attribute, &v, NULL);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* create the trusted_domain user account */
+ ret = ldb_build_add_req(&req, sam_ldb, mem_ctx, msg, NULL, NULL,
+ ldb_op_default_callback, NULL);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = ldb_request_add_control(
+ req, DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID,
+ false, NULL);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dsdb_autotransaction_request(sam_ldb, req);
+ if (ret != LDB_SUCCESS) {
+ DEBUG(0,("Failed to create user record %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(sam_ldb)));
+
+ switch (ret) {
+ case LDB_ERR_ENTRY_ALREADY_EXISTS:
+ return NT_STATUS_DOMAIN_EXISTS;
+ case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS:
+ return NT_STATUS_ACCESS_DENIED;
+ default:
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_set_trusted_domain(struct pdb_methods *methods,
+ const char* domain,
+ const struct pdb_trusted_domain *td)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ methods->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ bool in_txn = false;
+ struct ldb_dn *base_dn = NULL;
+ struct ldb_message *msg = NULL;
+ const char *attrs[] = {
+ NULL
+ };
+ char *netbios_encoded = NULL;
+ char *dns_encoded = NULL;
+ char *sid_encoded = NULL;
+ int ret;
+ struct trustAuthInOutBlob taiob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ bool ok;
+
+ base_dn = ldb_dn_copy(tmp_ctx, ldb_get_default_basedn(state->ldb));
+ if (base_dn == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /*
+ * We expect S-1-5-21-A-B-C, but we don't
+ * allow S-1-5-21-0-0-0 as this is used
+ * for claims and compound identities.
+ */
+ ok = dom_sid_is_valid_account_domain(&td->security_identifier);
+ if (!ok) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (strequal(td->netbios_name, "BUILTIN")) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ if (strequal(td->domain_name, "BUILTIN")) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ dns_encoded = ldb_binary_encode_string(tmp_ctx, td->domain_name);
+ if (dns_encoded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ netbios_encoded = ldb_binary_encode_string(tmp_ctx, td->netbios_name);
+ if (netbios_encoded == NULL) {
+ status =NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ sid_encoded = ldap_encode_ndr_dom_sid(tmp_ctx, &td->security_identifier);
+ if (sid_encoded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ok = samdb_is_pdc(state->ldb);
+ if (!ok) {
+ DBG_ERR("Adding TDO is only allowed on a PDC.\n");
+ TALLOC_FREE(tmp_ctx);
+ status = NT_STATUS_INVALID_DOMAIN_ROLE;
+ goto out;
+ }
+
+ status = dsdb_trust_search_tdo(state->ldb,
+ td->netbios_name,
+ td->domain_name,
+ attrs,
+ tmp_ctx,
+ &msg);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DBG_ERR("dsdb_trust_search_tdo returned %s\n",
+ nt_errstr(status));
+ status = NT_STATUS_INVALID_DOMAIN_STATE;
+ goto out;
+ }
+
+ ret = ldb_transaction_start(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto out;
+ }
+ in_txn = true;
+
+ msg = ldb_msg_new(tmp_ctx);
+ if (msg == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ msg->dn = samdb_system_container_dn(state->ldb, tmp_ctx);
+ if (msg->dn == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ok = ldb_dn_add_child_fmt(msg->dn, "cn=%s", td->domain_name);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = ldb_msg_add_string(msg, "objectClass", "trustedDomain");
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = ldb_msg_add_string(msg, "flatname", td->netbios_name);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = ldb_msg_add_string(msg, "trustPartner", td->domain_name);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = samdb_msg_add_dom_sid(state->ldb,
+ tmp_ctx,
+ msg,
+ "securityIdentifier",
+ &td->security_identifier);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = samdb_msg_add_int(state->ldb,
+ tmp_ctx,
+ msg,
+ "trustType",
+ td->trust_type);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = samdb_msg_add_int(state->ldb,
+ tmp_ctx,
+ msg,
+ "trustAttributes",
+ td->trust_attributes);
+ if (ret != LDB_SUCCESS) {
+ status =NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = samdb_msg_add_int(state->ldb,
+ tmp_ctx,
+ msg,
+ "trustDirection",
+ td->trust_direction);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (td->trust_auth_incoming.data != NULL) {
+ ret = ldb_msg_add_value(msg,
+ "trustAuthIncoming",
+ &td->trust_auth_incoming,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ if (td->trust_auth_outgoing.data != NULL) {
+ ret = ldb_msg_add_value(msg,
+ "trustAuthOutgoing",
+ &td->trust_auth_outgoing,
+ NULL);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ /* create the trusted_domain */
+ ret = ldb_add(state->ldb, msg);
+ switch (ret) {
+ case LDB_SUCCESS:
+ break;
+
+ case LDB_ERR_ENTRY_ALREADY_EXISTS:
+ DBG_ERR("Failed to create trusted domain record %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(state->ldb));
+ status = NT_STATUS_DOMAIN_EXISTS;
+ goto out;
+
+ case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS:
+ DBG_ERR("Failed to create trusted domain record %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(state->ldb));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+
+ default:
+ DBG_ERR("Failed to create trusted domain record %s: %s\n",
+ ldb_dn_get_linearized(msg->dn),
+ ldb_errstring(state->ldb));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto out;
+ }
+
+ ndr_err = ndr_pull_struct_blob(
+ &td->trust_auth_outgoing,
+ tmp_ctx,
+ &taiob,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto out;
+ }
+
+ if (td->trust_direction == LSA_TRUST_DIRECTION_INBOUND) {
+ status = add_trust_user(tmp_ctx,
+ state->ldb,
+ base_dn,
+ td->netbios_name,
+ &taiob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ ret = ldb_transaction_commit(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ in_txn = false;
+
+ /*
+ * TODO: Notify winbindd that we have a new trust
+ */
+
+ status = NT_STATUS_OK;
+
+out:
+ if (in_txn) {
+ ldb_transaction_cancel(state->ldb);
+ }
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS delete_trust_user(TALLOC_CTX *mem_ctx,
+ struct pdb_samba_dsdb_state *state,
+ const char *trust_user)
+{
+ const char *attrs[] = { "userAccountControl", NULL };
+ struct ldb_message **msgs;
+ uint32_t uac;
+ int ret;
+
+ ret = gendb_search(state->ldb,
+ mem_ctx,
+ ldb_get_default_basedn(state->ldb),
+ &msgs,
+ attrs,
+ "samAccountName=%s$",
+ trust_user);
+ if (ret > 1) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (ret == 0) {
+ return NT_STATUS_OK;
+ }
+
+ uac = ldb_msg_find_attr_as_uint(msgs[0],
+ "userAccountControl",
+ 0);
+ if (!(uac & UF_INTERDOMAIN_TRUST_ACCOUNT)) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ ret = ldb_delete(state->ldb, msgs[0]->dn);
+ switch (ret) {
+ case LDB_SUCCESS:
+ break;
+ case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS:
+ return NT_STATUS_ACCESS_DENIED;
+ default:
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_samba_dsdb_del_trusted_domain(struct pdb_methods *methods,
+ const char *domain)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ methods->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct pdb_trusted_domain *td = NULL;
+ struct ldb_dn *tdo_dn = NULL;
+ bool in_txn = false;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ status = pdb_samba_dsdb_get_trusted_domain(methods,
+ tmp_ctx,
+ domain,
+ &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DBG_ERR("Searching TDO for %s returned %s\n",
+ domain, nt_errstr(status));
+ return status;
+ }
+ DBG_NOTICE("No TDO object for %s\n", domain);
+ return NT_STATUS_OK;
+ }
+
+ tdo_dn = samdb_system_container_dn(state->ldb, tmp_ctx);
+ if (tdo_dn == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ok = ldb_dn_add_child_fmt(tdo_dn, "cn=%s", domain);
+ if (!ok) {
+ TALLOC_FREE(tmp_ctx);
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = ldb_transaction_start(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto out;
+ }
+ in_txn = true;
+
+ ret = ldb_delete(state->ldb, tdo_dn);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto out;
+ }
+
+ if (td->trust_direction == LSA_TRUST_DIRECTION_INBOUND) {
+ status = delete_trust_user(tmp_ctx, state, domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ ret = ldb_transaction_commit(state->ldb);
+ if (ret != LDB_SUCCESS) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto out;
+ }
+ in_txn = false;
+
+ status = NT_STATUS_OK;
+
+out:
+ if (in_txn) {
+ ldb_transaction_cancel(state->ldb);
+ }
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+static NTSTATUS pdb_samba_dsdb_enum_trusted_domains(struct pdb_methods *m,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *_num_domains,
+ struct pdb_trusted_domain ***_domains)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ m->private_data, struct pdb_samba_dsdb_state);
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ const char * const attrs[] = {
+ "securityIdentifier",
+ "flatName",
+ "trustPartner",
+ "trustAuthOutgoing",
+ "trustAuthIncoming",
+ "trustAttributes",
+ "trustDirection",
+ "trustType",
+ "trustPosixOffset",
+ "msDS-SupportedEncryptionTypes",
+ "msDS-TrustForestTrustInfo",
+ NULL
+ };
+ struct ldb_result *res = NULL;
+ unsigned int i;
+ struct pdb_trusted_domain **domains = NULL;
+ NTSTATUS status;
+ uint32_t di = 0;
+
+ *_num_domains = 0;
+ *_domains = NULL;
+
+ status = dsdb_trust_search_tdos(state->ldb, NULL,
+ attrs, tmp_ctx, &res);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dsdb_trust_search_tdos() - %s\n", nt_errstr(status));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ if (res->count == 0) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ domains = talloc_zero_array(tmp_ctx, struct pdb_trusted_domain *,
+ res->count);
+ if (domains == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < res->count; i++) {
+ struct ldb_message *msg = res->msgs[i];
+ struct pdb_trusted_domain *d = NULL;
+
+ status = pdb_samba_dsdb_msg_to_trusted_domain(msg, domains, &d);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("pdb_samba_dsdb_msg_to_trusted_domain() - %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ domains[di++] = d;
+ }
+
+ domains = talloc_realloc(domains, domains, struct pdb_trusted_domain *,
+ di);
+ *_domains = talloc_move(mem_ctx, &domains);
+ *_num_domains = di;
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static bool pdb_samba_dsdb_is_responsible_for_wellknown(struct pdb_methods *m)
+{
+ return true;
+}
+
+static bool pdb_samba_dsdb_is_responsible_for_everything_else(struct pdb_methods *m)
+{
+ return true;
+}
+
+static void pdb_samba_dsdb_init_methods(struct pdb_methods *m)
+{
+ m->name = "samba_dsdb";
+ m->get_domain_info = pdb_samba_dsdb_get_domain_info;
+ m->getsampwnam = pdb_samba_dsdb_getsampwnam;
+ m->getsampwsid = pdb_samba_dsdb_getsampwsid;
+ m->create_user = pdb_samba_dsdb_create_user;
+ m->delete_user = pdb_samba_dsdb_delete_user;
+ m->add_sam_account = pdb_samba_dsdb_add_sam_account;
+ m->update_sam_account = pdb_samba_dsdb_update_sam_account;
+ m->delete_sam_account = pdb_samba_dsdb_delete_sam_account;
+ m->rename_sam_account = pdb_samba_dsdb_rename_sam_account;
+ m->update_login_attempts = pdb_samba_dsdb_update_login_attempts;
+ m->getgrsid = pdb_samba_dsdb_getgrsid;
+ m->getgrgid = pdb_samba_dsdb_getgrgid;
+ m->getgrnam = pdb_samba_dsdb_getgrnam;
+ m->create_dom_group = pdb_samba_dsdb_create_dom_group;
+ m->delete_dom_group = pdb_samba_dsdb_delete_dom_group;
+ m->add_group_mapping_entry = pdb_samba_dsdb_add_group_mapping_entry;
+ m->update_group_mapping_entry = pdb_samba_dsdb_update_group_mapping_entry;
+ m->delete_group_mapping_entry = pdb_samba_dsdb_delete_group_mapping_entry;
+ m->enum_group_mapping = pdb_samba_dsdb_enum_group_mapping;
+ m->enum_group_members = pdb_samba_dsdb_enum_group_members;
+ m->enum_group_memberships = pdb_samba_dsdb_enum_group_memberships;
+ m->set_unix_primary_group = pdb_samba_dsdb_set_unix_primary_group;
+ m->add_groupmem = pdb_samba_dsdb_add_groupmem;
+ m->del_groupmem = pdb_samba_dsdb_del_groupmem;
+ m->create_alias = pdb_samba_dsdb_create_alias;
+ m->delete_alias = pdb_samba_dsdb_delete_alias;
+ m->get_aliasinfo = pdb_default_get_aliasinfo;
+ m->add_aliasmem = pdb_samba_dsdb_add_aliasmem;
+ m->del_aliasmem = pdb_samba_dsdb_del_aliasmem;
+ m->enum_aliasmem = pdb_samba_dsdb_enum_aliasmem;
+ m->enum_alias_memberships = pdb_samba_dsdb_enum_alias_memberships;
+ m->lookup_rids = pdb_samba_dsdb_lookup_rids;
+ m->lookup_names = pdb_samba_dsdb_lookup_names;
+ m->get_account_policy = pdb_samba_dsdb_get_account_policy;
+ m->set_account_policy = pdb_samba_dsdb_set_account_policy;
+ m->get_seq_num = pdb_samba_dsdb_get_seq_num;
+ m->search_users = pdb_samba_dsdb_search_users;
+ m->search_groups = pdb_samba_dsdb_search_groups;
+ m->search_aliases = pdb_samba_dsdb_search_aliases;
+ m->id_to_sid = pdb_samba_dsdb_id_to_sid;
+ m->sid_to_id = pdb_samba_dsdb_sid_to_id;
+ m->capabilities = pdb_samba_dsdb_capabilities;
+ m->new_rid = pdb_samba_dsdb_new_rid;
+ m->get_trusteddom_pw = pdb_samba_dsdb_get_trusteddom_pw;
+ m->get_trusteddom_creds = pdb_samba_dsdb_get_trusteddom_creds;
+ m->set_trusteddom_pw = pdb_samba_dsdb_set_trusteddom_pw;
+ m->del_trusteddom_pw = pdb_samba_dsdb_del_trusteddom_pw;
+ m->enum_trusteddoms = pdb_samba_dsdb_enum_trusteddoms;
+ m->get_trusted_domain = pdb_samba_dsdb_get_trusted_domain;
+ m->get_trusted_domain_by_sid = pdb_samba_dsdb_get_trusted_domain_by_sid;
+ m->set_trusted_domain = pdb_samba_dsdb_set_trusted_domain;
+ m->del_trusted_domain = pdb_samba_dsdb_del_trusted_domain;
+ m->enum_trusted_domains = pdb_samba_dsdb_enum_trusted_domains;
+ m->is_responsible_for_wellknown =
+ pdb_samba_dsdb_is_responsible_for_wellknown;
+ m->is_responsible_for_everything_else =
+ pdb_samba_dsdb_is_responsible_for_everything_else;
+}
+
+static void free_private_data(void **vp)
+{
+ struct pdb_samba_dsdb_state *state = talloc_get_type_abort(
+ *vp, struct pdb_samba_dsdb_state);
+ talloc_unlink(state, state->ldb);
+ return;
+}
+
+static NTSTATUS pdb_samba_dsdb_init_secrets(struct pdb_methods *m)
+{
+ struct pdb_domain_info *dom_info;
+ struct dom_sid stored_sid;
+ struct GUID stored_guid;
+ bool sid_exists_and_matches = false;
+ bool guid_exists_and_matches = false;
+ bool ret;
+
+ dom_info = pdb_samba_dsdb_get_domain_info(m, m);
+ if (!dom_info) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ret = secrets_fetch_domain_sid(dom_info->name, &stored_sid);
+ if (ret) {
+ if (dom_sid_equal(&stored_sid, &dom_info->sid)) {
+ sid_exists_and_matches = true;
+ }
+ }
+
+ if (sid_exists_and_matches == false) {
+ secrets_clear_domain_protection(dom_info->name);
+ ret = secrets_store_domain_sid(dom_info->name,
+ &dom_info->sid);
+ ret &= secrets_mark_domain_protected(dom_info->name);
+ if (!ret) {
+ goto done;
+ }
+ }
+
+ ret = secrets_fetch_domain_guid(dom_info->name, &stored_guid);
+ if (ret) {
+ if (GUID_equal(&stored_guid, &dom_info->guid)) {
+ guid_exists_and_matches = true;
+ }
+ }
+
+ if (guid_exists_and_matches == false) {
+ secrets_clear_domain_protection(dom_info->name);
+ ret = secrets_store_domain_guid(dom_info->name,
+ &dom_info->guid);
+ ret &= secrets_mark_domain_protected(dom_info->name);
+ if (!ret) {
+ goto done;
+ }
+ }
+
+done:
+ TALLOC_FREE(dom_info);
+ if (!ret) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS pdb_init_samba_dsdb(struct pdb_methods **pdb_method,
+ const char *location)
+{
+ struct pdb_methods *m;
+ struct pdb_samba_dsdb_state *state;
+ NTSTATUS status;
+ char *errstring = NULL;
+ int ret;
+
+ if ( !NT_STATUS_IS_OK(status = make_pdb_method( &m )) ) {
+ return status;
+ }
+
+ state = talloc_zero(m, struct pdb_samba_dsdb_state);
+ if (state == NULL) {
+ goto nomem;
+ }
+ m->private_data = state;
+ m->free_private_data = free_private_data;
+ pdb_samba_dsdb_init_methods(m);
+
+ state->ev = s4_event_context_init(state);
+ if (!state->ev) {
+ DEBUG(0, ("s4_event_context_init failed\n"));
+ goto nomem;
+ }
+
+ state->lp_ctx = loadparm_init_s3(state, loadparm_s3_helpers());
+ if (state->lp_ctx == NULL) {
+ DEBUG(0, ("loadparm_init_s3 failed\n"));
+ goto nomem;
+ }
+
+ if (location == NULL) {
+ location = "sam.ldb";
+ }
+
+ ret = samdb_connect_url(state,
+ state->ev,
+ state->lp_ctx,
+ system_session(state->lp_ctx),
+ 0,
+ location,
+ NULL,
+ &state->ldb,
+ &errstring);
+
+ if (!state->ldb) {
+ DEBUG(0, ("samdb_connect failed: %s: %s\n",
+ errstring, ldb_strerror(ret)));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ state->idmap_ctx = idmap_init(state, state->ev,
+ state->lp_ctx);
+ if (!state->idmap_ctx) {
+ DEBUG(0, ("idmap failed\n"));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ status = pdb_samba_dsdb_init_secrets(m);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("pdb_samba_dsdb_init_secrets failed!\n"));
+ goto fail;
+ }
+
+ *pdb_method = m;
+ return NT_STATUS_OK;
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ TALLOC_FREE(m);
+ return status;
+}
+
+NTSTATUS pdb_samba_dsdb_init(TALLOC_CTX *);
+NTSTATUS pdb_samba_dsdb_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS status = smb_register_passdb(PASSDB_INTERFACE_VERSION, "samba_dsdb",
+ pdb_init_samba_dsdb);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return smb_register_passdb(PASSDB_INTERFACE_VERSION, "samba4",
+ pdb_init_samba_dsdb);
+}
diff --git a/source3/passdb/pdb_secrets.c b/source3/passdb/pdb_secrets.c
new file mode 100644
index 0000000..2e98305
--- /dev/null
+++ b/source3/passdb/pdb_secrets.c
@@ -0,0 +1,172 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Andrew Tridgell 1992-2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Tim Potter 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* the Samba secrets database stores any generated, private information
+ such as the local SID and machine trust password */
+
+#include "includes.h"
+#include "passdb.h"
+#include "passdb/pdb_secrets.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
+#include "secrets.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "util_tdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+/**
+ * Get trusted domains info from secrets.tdb.
+ **/
+
+struct list_trusted_domains_state {
+ uint32_t num_domains;
+ struct trustdom_info **domains;
+};
+
+static int list_trusted_domain(struct db_record *rec, void *private_data)
+{
+ const size_t prefix_len = strlen(SECRETS_DOMTRUST_ACCT_PASS);
+ struct TRUSTED_DOM_PASS pass;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ struct trustdom_info *dom_info;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ struct list_trusted_domains_state *state =
+ (struct list_trusted_domains_state *)private_data;
+
+ key = dbwrap_record_get_key(rec);
+ value = dbwrap_record_get_value(rec);
+
+ if ((key.dsize < prefix_len)
+ || (strncmp((char *)key.dptr, SECRETS_DOMTRUST_ACCT_PASS,
+ prefix_len) != 0)) {
+ return 0;
+ }
+
+ blob = data_blob_const(value.dptr, value.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &pass,
+ (ndr_pull_flags_fn_t)ndr_pull_TRUSTED_DOM_PASS);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+
+ if (pass.domain_sid.num_auths != 4) {
+ struct dom_sid_buf buf;
+ DEBUG(0, ("SID %s is not a domain sid, has %d "
+ "auths instead of 4\n",
+ dom_sid_str_buf(&pass.domain_sid, &buf),
+ pass.domain_sid.num_auths));
+ return 0;
+ }
+
+ if (!(dom_info = talloc(state->domains, struct trustdom_info))) {
+ DEBUG(0, ("talloc failed\n"));
+ return 0;
+ }
+
+ dom_info->name = talloc_strdup(dom_info, pass.uni_name);
+ if (!dom_info->name) {
+ TALLOC_FREE(dom_info);
+ return 0;
+ }
+
+ sid_copy(&dom_info->sid, &pass.domain_sid);
+
+ ADD_TO_ARRAY(state->domains, struct trustdom_info *, dom_info,
+ &state->domains, &state->num_domains);
+
+ if (state->domains == NULL) {
+ state->num_domains = 0;
+ return -1;
+ }
+ return 0;
+}
+
+NTSTATUS secrets_trusted_domains(TALLOC_CTX *mem_ctx, uint32_t *num_domains,
+ struct trustdom_info ***domains)
+{
+ struct list_trusted_domains_state state;
+ struct db_context *db_ctx;
+
+ if (!secrets_init()) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ db_ctx = secrets_db_ctx();
+
+ state.num_domains = 0;
+
+ /*
+ * Make sure that a talloc context for the trustdom_info structs
+ * exists
+ */
+
+ if (!(state.domains = talloc_array(
+ mem_ctx, struct trustdom_info *, 1))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dbwrap_traverse_read(db_ctx, list_trusted_domain, (void *)&state, NULL);
+
+ *num_domains = state.num_domains;
+ *domains = state.domains;
+ return NT_STATUS_OK;
+}
+
+/* In order to avoid direct linking against libsecrets for pdb modules
+ * following helpers are provided for pdb module writers.
+ * To differentiate them from pdb_* API, they are prefixed by PDB upper case
+ */
+bool PDB_secrets_store_domain_sid(const char *domain, const struct dom_sid *sid)
+{
+ return secrets_store_domain_sid(domain, sid);
+}
+
+bool PDB_secrets_mark_domain_protected(const char *domain)
+{
+ return secrets_mark_domain_protected(domain);
+}
+
+bool PDB_secrets_clear_domain_protection(const char *domain)
+{
+ return secrets_clear_domain_protection(domain);
+}
+
+bool PDB_secrets_fetch_domain_sid(const char *domain, struct dom_sid *sid)
+{
+ return secrets_fetch_domain_sid(domain, sid);
+}
+
+bool PDB_secrets_store_domain_guid(const char *domain, struct GUID *guid)
+{
+ return secrets_store_domain_guid(domain, guid);
+}
+
+bool PDB_secrets_fetch_domain_guid(const char *domain, struct GUID *guid)
+{
+ return secrets_fetch_domain_guid(domain, guid);
+}
diff --git a/source3/passdb/pdb_secrets.h b/source3/passdb/pdb_secrets.h
new file mode 100644
index 0000000..d9b1ace
--- /dev/null
+++ b/source3/passdb/pdb_secrets.h
@@ -0,0 +1,30 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Andrew Tridgell 1992-2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Tim Potter 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PASSDB_PDB_SECRETS_H_
+#define _PASSDB_PDB_SECRETS_H_
+
+/* The following definitions come from passdb/pdb_secrets.c */
+
+NTSTATUS secrets_trusted_domains(TALLOC_CTX *mem_ctx, uint32_t *num_domains,
+ struct trustdom_info ***domains);
+
+#endif /* _PASSDB_PDB_SECRETS_H_ */
diff --git a/source3/passdb/pdb_smbpasswd.c b/source3/passdb/pdb_smbpasswd.c
new file mode 100644
index 0000000..adeb0e3
--- /dev/null
+++ b/source3/passdb/pdb_smbpasswd.c
@@ -0,0 +1,1750 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * SMB parameters and setup
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Modified by Jeremy Allison 1995.
+ * Modified by Gerald (Jerry) Carter 2000-2001,2003
+ * Modified by Andrew Bartlett 2002.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "passdb.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "../librpc/gen_ndr/samr.h"
+#include "../libcli/security/security.h"
+#include "passdb/pdb_smbpasswd.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+/*
+ smb_passwd is analogous to sam_passwd used everywhere
+ else. However, smb_passwd is limited to the information
+ stored by an smbpasswd entry
+ */
+
+struct smb_passwd
+{
+ uint32_t smb_userid; /* this is actually the unix uid_t */
+ const char *smb_name; /* username string */
+
+ const unsigned char *smb_passwd; /* Null if no password */
+ const unsigned char *smb_nt_passwd; /* Null if no password */
+
+ uint16_t acct_ctrl; /* account info (ACB_xxxx bit-mask) */
+ time_t pass_last_set_time; /* password last set time */
+};
+
+struct smbpasswd_privates
+{
+ /* used for maintain locks on the smbpasswd file */
+ int pw_file_lock_depth;
+
+ /* Global File pointer */
+ FILE *pw_file;
+
+ /* formerly static variables */
+ struct smb_passwd pw_buf;
+ fstring user_name;
+ unsigned char smbpwd[16];
+ unsigned char smbntpwd[16];
+
+ /* retrieve-once info */
+ const char *smbpasswd_file;
+};
+
+enum pwf_access_type { PWF_READ, PWF_UPDATE, PWF_CREATE };
+
+static SIG_ATOMIC_T gotalarm;
+
+/***************************************************************
+ Signal function to tell us we timed out.
+****************************************************************/
+
+static void gotalarm_sig(int signum)
+{
+ gotalarm = 1;
+}
+
+/***************************************************************
+ Lock or unlock a fd for a known lock type. Abandon after waitsecs
+ seconds.
+****************************************************************/
+
+static bool do_file_lock(int fd, int waitsecs, int type)
+{
+ struct flock lock;
+ int ret;
+ void (*oldsig_handler)(int);
+
+ gotalarm = 0;
+ oldsig_handler = CatchSignal(SIGALRM, gotalarm_sig);
+
+ lock.l_type = type;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 1;
+ lock.l_pid = 0;
+
+ alarm(waitsecs);
+ /* Note we must *NOT* use sys_fcntl here ! JRA */
+ ret = fcntl(fd, F_SETLKW, &lock);
+ alarm(0);
+ CatchSignal(SIGALRM, oldsig_handler);
+
+ if (gotalarm && ret == -1) {
+ DEBUG(0, ("do_file_lock: failed to %s file.\n",
+ type == F_UNLCK ? "unlock" : "lock"));
+ return False;
+ }
+
+ return (ret == 0);
+}
+
+/***************************************************************
+ Lock an fd. Abandon after waitsecs seconds.
+****************************************************************/
+
+static bool pw_file_lock(int fd, int type, int secs, int *plock_depth)
+{
+ if (fd < 0) {
+ return False;
+ }
+
+ if(*plock_depth == 0) {
+ if (!do_file_lock(fd, secs, type)) {
+ DEBUG(10,("pw_file_lock: locking file failed, error = %s.\n",
+ strerror(errno)));
+ return False;
+ }
+ }
+
+ (*plock_depth)++;
+
+ return True;
+}
+
+/***************************************************************
+ Unlock an fd. Abandon after waitsecs seconds.
+****************************************************************/
+
+static bool pw_file_unlock(int fd, int *plock_depth)
+{
+ bool ret=True;
+
+ if (fd == 0 || *plock_depth == 0) {
+ return True;
+ }
+
+ if(*plock_depth == 1) {
+ ret = do_file_lock(fd, 5, F_UNLCK);
+ }
+
+ if (*plock_depth > 0) {
+ (*plock_depth)--;
+ }
+
+ if(!ret) {
+ DEBUG(10,("pw_file_unlock: unlocking file failed, error = %s.\n",
+ strerror(errno)));
+ }
+ return ret;
+}
+
+/**************************************************************
+ Initialize a smb_passwd struct
+ *************************************************************/
+
+static void pdb_init_smb(struct smb_passwd *user)
+{
+ if (user == NULL)
+ return;
+ ZERO_STRUCTP (user);
+
+ user->pass_last_set_time = (time_t)0;
+}
+
+/***************************************************************
+ Internal fn to enumerate the smbpasswd list. Returns a void pointer
+ to ensure no modification outside this module. Checks for atomic
+ rename of smbpasswd file on update or create once the lock has
+ been granted to prevent race conditions. JRA.
+****************************************************************/
+
+static FILE *startsmbfilepwent(const char *pfile, enum pwf_access_type type, int *lock_depth)
+{
+ FILE *fp = NULL;
+ const char *open_mode = NULL;
+ int race_loop = 0;
+ int lock_type = F_RDLCK;
+ struct stat st;
+
+ if (!*pfile) {
+ DEBUG(0, ("startsmbfilepwent: No SMB password file set\n"));
+ return (NULL);
+ }
+
+ switch(type) {
+ case PWF_READ:
+ open_mode = "rb";
+ lock_type = F_RDLCK;
+ break;
+ case PWF_UPDATE:
+ open_mode = "r+b";
+ lock_type = F_WRLCK;
+ break;
+ case PWF_CREATE:
+ /*
+ * Ensure atomic file creation.
+ */
+ {
+ int i, fd = -1;
+
+ for(i = 0; i < 5; i++) {
+ if((fd = open(pfile, O_CREAT|O_TRUNC|O_EXCL|O_RDWR, 0600))!=-1) {
+ break;
+ }
+ usleep(200); /* Spin, spin... */
+ }
+ if(fd == -1) {
+ DEBUG(0,("startsmbfilepwent_internal: too many race conditions \
+creating file %s\n", pfile));
+ return NULL;
+ }
+ close(fd);
+ open_mode = "r+b";
+ lock_type = F_WRLCK;
+ break;
+ }
+ default:
+ DEBUG(10, ("Invalid open mode: %d\n", type));
+ return NULL;
+ }
+
+ for(race_loop = 0; race_loop < 5; race_loop++) {
+ DEBUG(10, ("startsmbfilepwent_internal: opening file %s\n", pfile));
+
+ if((fp = fopen(pfile, open_mode)) == NULL) {
+
+ /*
+ * If smbpasswd file doesn't exist, then create new one. This helps to avoid
+ * confusing error msg when adding user account first time.
+ */
+ if (errno == ENOENT) {
+ if ((fp = fopen(pfile, "a+")) != NULL) {
+ DEBUG(0, ("startsmbfilepwent_internal: file %s did not \
+exist. File successfully created.\n", pfile));
+ } else {
+ DEBUG(0, ("startsmbfilepwent_internal: file %s did not \
+exist. Couldn't create new one. Error was: %s\n",
+ pfile, strerror(errno)));
+ return NULL;
+ }
+ } else {
+ DEBUG(0, ("startsmbfilepwent_internal: unable to open file %s. \
+Error was: %s\n", pfile, strerror(errno)));
+ return NULL;
+ }
+ }
+
+ if (!pw_file_lock(fileno(fp), lock_type, 5, lock_depth)) {
+ DEBUG(0, ("startsmbfilepwent_internal: unable to lock file %s. \
+Error was %s\n", pfile, strerror(errno) ));
+ fclose(fp);
+ return NULL;
+ }
+
+ /*
+ * Only check for replacement races on update or create.
+ * For read we don't mind if the data is one record out of date.
+ */
+
+ if(type == PWF_READ) {
+ break;
+ } else {
+ SMB_STRUCT_STAT sbuf1, sbuf2;
+
+ /*
+ * Avoid the potential race condition between the open and the lock
+ * by doing a stat on the filename and an fstat on the fd. If the
+ * two inodes differ then someone did a rename between the open and
+ * the lock. Back off and try the open again. Only do this 5 times to
+ * prevent infinite loops. JRA.
+ */
+
+ if (sys_stat(pfile, &sbuf1, false) != 0) {
+ DEBUG(0, ("startsmbfilepwent_internal: unable to stat file %s. \
+Error was %s\n", pfile, strerror(errno)));
+ pw_file_unlock(fileno(fp), lock_depth);
+ fclose(fp);
+ return NULL;
+ }
+
+ if (sys_fstat(fileno(fp), &sbuf2, false) != 0) {
+ DEBUG(0, ("startsmbfilepwent_internal: unable to fstat file %s. \
+Error was %s\n", pfile, strerror(errno)));
+ pw_file_unlock(fileno(fp), lock_depth);
+ fclose(fp);
+ return NULL;
+ }
+
+ if( sbuf1.st_ex_ino == sbuf2.st_ex_ino) {
+ /* No race. */
+ break;
+ }
+
+ /*
+ * Race occurred - back off and try again...
+ */
+
+ pw_file_unlock(fileno(fp), lock_depth);
+ fclose(fp);
+ }
+ }
+
+ if(race_loop == 5) {
+ DEBUG(0, ("startsmbfilepwent_internal: too many race conditions opening file %s\n", pfile));
+ return NULL;
+ }
+
+ /* Set a buffer to do more efficient reads */
+ setvbuf(fp, (char *)NULL, _IOFBF, 1024);
+
+ /* Ensure we have a valid stat. */
+ if (fstat(fileno(fp), &st) != 0) {
+ DBG_ERR("Unable to fstat file %s. Error was %s\n",
+ pfile,
+ strerror(errno));
+ pw_file_unlock(fileno(fp), lock_depth);
+ fclose(fp);
+ return NULL;
+ }
+
+ /* If file has invalid permissions != 0600, then [f]chmod(). */
+ if ((st.st_mode & 0777) != (S_IRUSR|S_IWUSR)) {
+ DBG_WARNING("file %s has invalid permissions 0%o should "
+ "be 0600.\n",
+ pfile,
+ (unsigned int)st.st_mode & 0777);
+ /* Make sure it is only rw by the owner */
+#ifdef HAVE_FCHMOD
+ if (fchmod(fileno(fp), S_IRUSR|S_IWUSR) == -1) {
+#else
+ if (chmod(pfile, S_IRUSR|S_IWUSR) == -1) {
+#endif
+ DBG_ERR("Failed to set 0600 permissions on password file %s. "
+ "Error was %s\n.",
+ pfile,
+ strerror(errno));
+ pw_file_unlock(fileno(fp), lock_depth);
+ fclose(fp);
+ return NULL;
+ }
+ }
+
+ /* We have a lock on the file. */
+ return fp;
+}
+
+/***************************************************************
+ End enumeration of the smbpasswd list.
+****************************************************************/
+
+static void endsmbfilepwent(FILE *fp, int *lock_depth)
+{
+ if (!fp) {
+ return;
+ }
+
+ pw_file_unlock(fileno(fp), lock_depth);
+ fclose(fp);
+ DEBUG(7, ("endsmbfilepwent_internal: closed password file.\n"));
+}
+
+/*************************************************************************
+ Routine to return the next entry in the smbpasswd list.
+ *************************************************************************/
+
+static struct smb_passwd *getsmbfilepwent(struct smbpasswd_privates *smbpasswd_state, FILE *fp)
+{
+ /* Static buffers we will return. */
+ struct smb_passwd *pw_buf = &smbpasswd_state->pw_buf;
+ char *user_name = smbpasswd_state->user_name;
+ unsigned char *smbpwd = smbpasswd_state->smbpwd;
+ unsigned char *smbntpwd = smbpasswd_state->smbntpwd;
+ char linebuf[256];
+ unsigned char *p;
+ long uidval;
+ size_t linebuf_len;
+ char *status;
+
+ if(fp == NULL) {
+ DEBUG(0,("getsmbfilepwent: Bad password file pointer.\n"));
+ return NULL;
+ }
+
+ pdb_init_smb(pw_buf);
+ pw_buf->acct_ctrl = ACB_NORMAL;
+
+ /*
+ * Scan the file, a line at a time and check if the name matches.
+ */
+ status = linebuf;
+ while (status && !feof(fp)) {
+ linebuf[0] = '\0';
+
+ status = fgets(linebuf, 256, fp);
+ if (status == NULL && ferror(fp)) {
+ return NULL;
+ }
+
+ /*
+ * Check if the string is terminated with a newline - if not
+ * then we must keep reading and discard until we get one.
+ */
+ if ((linebuf_len = strlen(linebuf)) == 0) {
+ continue;
+ }
+
+ if (linebuf[linebuf_len - 1] != '\n') {
+ while (!ferror(fp) && !feof(fp)) {
+ int c;
+ c = fgetc(fp);
+ if (c == '\n') {
+ break;
+ }
+ }
+ } else {
+ linebuf[linebuf_len - 1] = '\0';
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100, ("getsmbfilepwent: got line |%s|\n", linebuf));
+#endif
+ if ((linebuf[0] == 0) && feof(fp)) {
+ DEBUG(4, ("getsmbfilepwent: end of file reached\n"));
+ break;
+ }
+
+ /*
+ * The line we have should be of the form :-
+ *
+ * username:uid:32hex bytes:[Account type]:LCT-12345678....other flags presently
+ * ignored....
+ *
+ * or,
+ *
+ * username:uid:32hex bytes:32hex bytes:[Account type]:LCT-12345678....ignored....
+ *
+ * if Windows NT compatible passwords are also present.
+ * [Account type] is an ascii encoding of the type of account.
+ * LCT-(8 hex digits) is the time_t value of the last change time.
+ */
+
+ if (linebuf[0] == '#' || linebuf[0] == '\0') {
+ DEBUG(6, ("getsmbfilepwent: skipping comment or blank line\n"));
+ continue;
+ }
+ p = (unsigned char *) strchr_m(linebuf, ':');
+ if (p == NULL) {
+ DEBUG(0, ("getsmbfilepwent: malformed password entry (no :)\n"));
+ continue;
+ }
+
+ strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
+ user_name[PTR_DIFF(p, linebuf)] = '\0';
+
+ /* Get smb uid. */
+
+ p++; /* Go past ':' */
+
+ if(*p == '-') {
+ DEBUG(0, ("getsmbfilepwent: user name %s has a negative uid.\n", user_name));
+ continue;
+ }
+
+ if (!isdigit(*p)) {
+ DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (uid not number)\n",
+ user_name));
+ continue;
+ }
+
+ uidval = atoi((char *) p);
+
+ while (*p && isdigit(*p)) {
+ p++;
+ }
+
+ if (*p != ':') {
+ DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (no : after uid)\n",
+ user_name));
+ continue;
+ }
+
+ pw_buf->smb_name = user_name;
+ pw_buf->smb_userid = uidval;
+
+ /*
+ * Now get the password value - this should be 32 hex digits
+ * which are the ascii representations of a 16 byte string.
+ * Get two at a time and put them into the password.
+ */
+
+ /* Skip the ':' */
+ p++;
+
+ if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) {
+ DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (passwd too short)\n",
+ user_name ));
+ continue;
+ }
+
+ if (p[32] != ':') {
+ DEBUG(0, ("getsmbfilepwent: malformed password entry for user %s (no terminating :)\n",
+ user_name));
+ continue;
+ }
+
+ if (strnequal((char *) p, "NO PASSWORD", 11)) {
+ pw_buf->smb_passwd = NULL;
+ pw_buf->acct_ctrl |= ACB_PWNOTREQ;
+ } else {
+ if (*p == '*' || *p == 'X') {
+ /* NULL LM password */
+ pw_buf->smb_passwd = NULL;
+ DEBUG(10, ("getsmbfilepwent: LM password for user %s invalidated\n", user_name));
+ } else if (pdb_gethexpwd((char *)p, smbpwd)) {
+ pw_buf->smb_passwd = smbpwd;
+ } else {
+ pw_buf->smb_passwd = NULL;
+ DEBUG(0, ("getsmbfilepwent: Malformed Lanman password entry for user %s \
+(non hex chars)\n", user_name));
+ }
+ }
+
+ /*
+ * Now check if the NT compatible password is
+ * available.
+ */
+ pw_buf->smb_nt_passwd = NULL;
+ p += 33; /* Move to the first character of the line after the lanman password. */
+ if ((linebuf_len >= (PTR_DIFF(p, linebuf) + 33)) && (p[32] == ':')) {
+ if (*p != '*' && *p != 'X') {
+ if(pdb_gethexpwd((char *)p,smbntpwd)) {
+ pw_buf->smb_nt_passwd = smbntpwd;
+ }
+ }
+ p += 33; /* Move to the first character of the line after the NT password. */
+ }
+
+ DEBUG(5,("getsmbfilepwent: returning passwd entry for user %s, uid %ld\n",
+ user_name, uidval));
+
+ if (*p == '[') {
+ unsigned char *end_p = (unsigned char *)strchr_m((char *)p, ']');
+ pw_buf->acct_ctrl = pdb_decode_acct_ctrl((char*)p);
+
+ /* Must have some account type set. */
+ if(pw_buf->acct_ctrl == 0) {
+ pw_buf->acct_ctrl = ACB_NORMAL;
+ }
+
+ /* Now try and get the last change time. */
+ if(end_p) {
+ p = end_p + 1;
+ }
+ if(*p == ':') {
+ p++;
+ if(*p && (strncasecmp_m((char *)p, "LCT-", 4)==0)) {
+ int i;
+ p += 4;
+ for(i = 0; i < 8; i++) {
+ if(p[i] == '\0' || !isxdigit(p[i])) {
+ break;
+ }
+ }
+ if(i == 8) {
+ /*
+ * p points at 8 characters of hex digits -
+ * read into a time_t as the seconds since
+ * 1970 that the password was last changed.
+ */
+ pw_buf->pass_last_set_time = (time_t)strtol((char *)p, NULL, 16);
+ }
+ }
+ }
+ } else {
+ /* 'Old' style file. Fake up based on user name. */
+ /*
+ * Currently trust accounts are kept in the same
+ * password file as 'normal accounts'. If this changes
+ * we will have to fix this code. JRA.
+ */
+ if(pw_buf->smb_name[strlen(pw_buf->smb_name) - 1] == '$') {
+ pw_buf->acct_ctrl &= ~ACB_NORMAL;
+ pw_buf->acct_ctrl |= ACB_WSTRUST;
+ }
+ }
+
+ return pw_buf;
+ }
+
+ DEBUG(5,("getsmbfilepwent: end of file reached.\n"));
+ return NULL;
+}
+
+/************************************************************************
+ Create a new smbpasswd entry - malloced space returned.
+*************************************************************************/
+
+static char *format_new_smbpasswd_entry(const struct smb_passwd *newpwd)
+{
+ int new_entry_length;
+ char *new_entry;
+ char *p;
+
+ new_entry_length = strlen(newpwd->smb_name) + 1 + 15 + 1 + 32 + 1 + 32 + 1 +
+ NEW_PW_FORMAT_SPACE_PADDED_LEN + 1 + 13 + 2;
+
+ if((new_entry = (char *)SMB_MALLOC( new_entry_length )) == NULL) {
+ DEBUG(0, ("format_new_smbpasswd_entry: Malloc failed adding entry for user %s.\n",
+ newpwd->smb_name ));
+ return NULL;
+ }
+
+ slprintf(new_entry, new_entry_length - 1, "%s:%u:", newpwd->smb_name, (unsigned)newpwd->smb_userid);
+
+ p = new_entry+strlen(new_entry);
+ pdb_sethexpwd(p, newpwd->smb_passwd, newpwd->acct_ctrl);
+ p+=strlen(p);
+ *p = ':';
+ p++;
+
+ pdb_sethexpwd(p, newpwd->smb_nt_passwd, newpwd->acct_ctrl);
+ p+=strlen(p);
+ *p = ':';
+ p++;
+
+ /* Add the account encoding and the last change time. */
+ slprintf((char *)p, new_entry_length - 1 - (p - new_entry), "%s:LCT-%08X:\n",
+ pdb_encode_acct_ctrl(newpwd->acct_ctrl, NEW_PW_FORMAT_SPACE_PADDED_LEN),
+ (uint32_t)newpwd->pass_last_set_time);
+
+ return new_entry;
+}
+
+/************************************************************************
+ Routine to add an entry to the smbpasswd file.
+*************************************************************************/
+
+static NTSTATUS add_smbfilepwd_entry(struct smbpasswd_privates *smbpasswd_state,
+ struct smb_passwd *newpwd)
+{
+ const char *pfile = smbpasswd_state->smbpasswd_file;
+ struct smb_passwd *pwd = NULL;
+ FILE *fp = NULL;
+ int wr_len;
+ int fd;
+ size_t new_entry_length;
+ char *new_entry;
+ off_t offpos;
+
+ /* Open the smbpassword file - for update. */
+ fp = startsmbfilepwent(pfile, PWF_UPDATE, &smbpasswd_state->pw_file_lock_depth);
+
+ if (fp == NULL && errno == ENOENT) {
+ /* Try again - create. */
+ fp = startsmbfilepwent(pfile, PWF_CREATE, &smbpasswd_state->pw_file_lock_depth);
+ }
+
+ if (fp == NULL) {
+ DEBUG(0, ("add_smbfilepwd_entry: unable to open file.\n"));
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * Scan the file, a line at a time and check if the name matches.
+ */
+
+ while ((pwd = getsmbfilepwent(smbpasswd_state, fp)) != NULL) {
+ if (strequal(newpwd->smb_name, pwd->smb_name)) {
+ DEBUG(0, ("add_smbfilepwd_entry: entry with name %s already exists\n", pwd->smb_name));
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ return NT_STATUS_USER_EXISTS;
+ }
+ }
+
+ /* Ok - entry doesn't exist. We can add it */
+
+ /* Create a new smb passwd entry and set it to the given password. */
+ /*
+ * The add user write needs to be atomic - so get the fd from
+ * the fp and do a raw write() call.
+ */
+ fd = fileno(fp);
+
+ if((offpos = lseek(fd, 0, SEEK_END)) == -1) {
+ NTSTATUS result = map_nt_error_from_unix(errno);
+ DEBUG(0, ("add_smbfilepwd_entry(lseek): Failed to add entry for user %s to file %s. \
+Error was %s\n", newpwd->smb_name, pfile, strerror(errno)));
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ return result;
+ }
+
+ if((new_entry = format_new_smbpasswd_entry(newpwd)) == NULL) {
+ DEBUG(0, ("add_smbfilepwd_entry(malloc): Failed to add entry for user %s to file %s. \
+Error was %s\n", newpwd->smb_name, pfile, strerror(errno)));
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ new_entry_length = strlen(new_entry);
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100, ("add_smbfilepwd_entry(%d): new_entry_len %d made line |%s|",
+ fd, (int)new_entry_length, new_entry));
+#endif
+
+ if ((wr_len = write(fd, new_entry, new_entry_length)) != new_entry_length) {
+ NTSTATUS result = map_nt_error_from_unix(errno);
+ DEBUG(0, ("add_smbfilepwd_entry(write): %d Failed to add entry for user %s to file %s. \
+Error was %s\n", wr_len, newpwd->smb_name, pfile, strerror(errno)));
+
+ /* Remove the entry we just wrote. */
+ if(ftruncate(fd, offpos) == -1) {
+ DEBUG(0, ("add_smbfilepwd_entry: ERROR failed to ftruncate file %s. \
+Error was %s. Password file may be corrupt ! Please examine by hand !\n",
+ newpwd->smb_name, strerror(errno)));
+ }
+
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ free(new_entry);
+ return result;
+ }
+
+ free(new_entry);
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ return NT_STATUS_OK;
+}
+
+/************************************************************************
+ Routine to search the smbpasswd file for an entry matching the username.
+ and then modify its password entry. We can't use the startsmbpwent()/
+ getsmbpwent()/endsmbpwent() interfaces here as we depend on looking
+ in the actual file to decide how much room we have to write data.
+ override = False, normal
+ override = True, override XXXXXXXX'd out password or NO PASS
+************************************************************************/
+
+static bool mod_smbfilepwd_entry(struct smbpasswd_privates *smbpasswd_state, const struct smb_passwd* pwd)
+{
+ /* Static buffers we will return. */
+ fstring user_name;
+
+ char *status;
+#define LINEBUF_SIZE 255
+ char linebuf[LINEBUF_SIZE + 1];
+ char readbuf[1024];
+ char ascii_p16[FSTRING_LEN + 20];
+ fstring encode_bits;
+ unsigned char *p = NULL;
+ size_t linebuf_len = 0;
+ FILE *fp;
+ int lockfd;
+ const char *pfile = smbpasswd_state->smbpasswd_file;
+ bool found_entry = False;
+ bool got_pass_last_set_time = False;
+
+ off_t pwd_seekpos = 0;
+
+ int i;
+ int wr_len;
+ int fd;
+
+ if (!*pfile) {
+ DEBUG(0, ("No SMB password file set\n"));
+ return False;
+ }
+ DEBUG(10, ("mod_smbfilepwd_entry: opening file %s\n", pfile));
+
+ fp = fopen(pfile, "r+");
+
+ if (fp == NULL) {
+ DEBUG(0, ("mod_smbfilepwd_entry: unable to open file %s\n", pfile));
+ return False;
+ }
+ /* Set a buffer to do more efficient reads */
+ setvbuf(fp, readbuf, _IOFBF, sizeof(readbuf));
+
+ lockfd = fileno(fp);
+
+ if (!pw_file_lock(lockfd, F_WRLCK, 5, &smbpasswd_state->pw_file_lock_depth)) {
+ DEBUG(0, ("mod_smbfilepwd_entry: unable to lock file %s\n", pfile));
+ fclose(fp);
+ return False;
+ }
+
+ /* Make sure it is only rw by the owner */
+ chmod(pfile, 0600);
+
+ /* We have a write lock on the file. */
+ /*
+ * Scan the file, a line at a time and check if the name matches.
+ */
+ status = linebuf;
+ while (status && !feof(fp)) {
+ pwd_seekpos = ftell(fp);
+
+ linebuf[0] = '\0';
+
+ status = fgets(linebuf, LINEBUF_SIZE, fp);
+ if (status == NULL && ferror(fp)) {
+ pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ /*
+ * Check if the string is terminated with a newline - if not
+ * then we must keep reading and discard until we get one.
+ */
+ linebuf_len = strlen(linebuf);
+ if (linebuf[linebuf_len - 1] != '\n') {
+ while (!ferror(fp) && !feof(fp)) {
+ int c;
+ c = fgetc(fp);
+ if (c == '\n') {
+ break;
+ }
+ }
+ } else {
+ linebuf[linebuf_len - 1] = '\0';
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100, ("mod_smbfilepwd_entry: got line |%s|\n", linebuf));
+#endif
+
+ if ((linebuf[0] == 0) && feof(fp)) {
+ DEBUG(4, ("mod_smbfilepwd_entry: end of file reached\n"));
+ break;
+ }
+
+ /*
+ * The line we have should be of the form :-
+ *
+ * username:uid:[32hex bytes]:....other flags presently
+ * ignored....
+ *
+ * or,
+ *
+ * username:uid:[32hex bytes]:[32hex bytes]:[attributes]:LCT-XXXXXXXX:...ignored.
+ *
+ * if Windows NT compatible passwords are also present.
+ */
+
+ if (linebuf[0] == '#' || linebuf[0] == '\0') {
+ DEBUG(6, ("mod_smbfilepwd_entry: skipping comment or blank line\n"));
+ continue;
+ }
+
+ p = (unsigned char *) strchr_m(linebuf, ':');
+
+ if (p == NULL) {
+ DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry (no :)\n"));
+ continue;
+ }
+
+ strncpy(user_name, linebuf, PTR_DIFF(p, linebuf));
+ user_name[PTR_DIFF(p, linebuf)] = '\0';
+ if (strequal(user_name, pwd->smb_name)) {
+ found_entry = True;
+ break;
+ }
+ }
+
+ if (!found_entry) {
+ pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+
+ DEBUG(2, ("Cannot update entry for user %s, as they don't exist in the smbpasswd file!\n",
+ pwd->smb_name));
+ return False;
+ }
+
+ DEBUG(6, ("mod_smbfilepwd_entry: entry exists for user %s\n", pwd->smb_name));
+
+ /* User name matches - get uid and password */
+ p++; /* Go past ':' */
+
+ if (!isdigit(*p)) {
+ DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (uid not number)\n",
+ pwd->smb_name));
+ pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ while (*p && isdigit(*p)) {
+ p++;
+ }
+ if (*p != ':') {
+ DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (no : after uid)\n",
+ pwd->smb_name));
+ pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ /*
+ * Now get the password value - this should be 32 hex digits
+ * which are the ascii representations of a 16 byte string.
+ * Get two at a time and put them into the password.
+ */
+ p++;
+
+ /* Record exact password position */
+ pwd_seekpos += PTR_DIFF(p, linebuf);
+
+ if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) {
+ DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (passwd too short)\n",
+ pwd->smb_name));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return (False);
+ }
+
+ if (p[32] != ':') {
+ DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (no terminating :)\n",
+ pwd->smb_name));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ /* Now check if the NT compatible password is available. */
+ p += 33; /* Move to the first character of the line after the lanman password. */
+ if (linebuf_len < (PTR_DIFF(p, linebuf) + 33)) {
+ DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (passwd too short)\n",
+ pwd->smb_name));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return (False);
+ }
+
+ if (p[32] != ':') {
+ DEBUG(0, ("mod_smbfilepwd_entry: malformed password entry for user %s (no terminating :)\n",
+ pwd->smb_name));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ /*
+ * Now check if the account info and the password last
+ * change time is available.
+ */
+ p += 33; /* Move to the first character of the line after the NT password. */
+
+ if (*p == '[') {
+ i = 0;
+ encode_bits[i++] = *p++;
+ while((linebuf_len > PTR_DIFF(p, linebuf)) && (*p != ']')) {
+ encode_bits[i++] = *p++;
+ }
+
+ encode_bits[i++] = ']';
+ encode_bits[i++] = '\0';
+
+ if(i == NEW_PW_FORMAT_SPACE_PADDED_LEN) {
+ /*
+ * We are using a new format, space padded
+ * acct ctrl field. Encode the given acct ctrl
+ * bits into it.
+ */
+ fstrcpy(encode_bits, pdb_encode_acct_ctrl(pwd->acct_ctrl, NEW_PW_FORMAT_SPACE_PADDED_LEN));
+ } else {
+ DEBUG(0,("mod_smbfilepwd_entry: Using old smbpasswd format for user %s. \
+This is no longer supported.!\n", pwd->smb_name));
+ DEBUG(0,("mod_smbfilepwd_entry: No changes made, failing.!\n"));
+ pw_file_unlock(lockfd, &smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ /* Go past the ']' */
+ if(linebuf_len > PTR_DIFF(p, linebuf)) {
+ p++;
+ }
+
+ if((linebuf_len > PTR_DIFF(p, linebuf)) && (*p == ':')) {
+ p++;
+
+ /* We should be pointing at the LCT entry. */
+ if((linebuf_len > (PTR_DIFF(p, linebuf) + 13)) && (strncasecmp_m((char *)p, "LCT-", 4) == 0)) {
+ p += 4;
+ for(i = 0; i < 8; i++) {
+ if(p[i] == '\0' || !isxdigit(p[i])) {
+ break;
+ }
+ }
+ if(i == 8) {
+ /*
+ * p points at 8 characters of hex digits -
+ * read into a time_t as the seconds since
+ * 1970 that the password was last changed.
+ */
+ got_pass_last_set_time = True;
+ } /* i == 8 */
+ } /* *p && strncasecmp_m() */
+ } /* p == ':' */
+ } /* p == '[' */
+
+ /* Entry is correctly formed. */
+
+ /* Create the 32 byte representation of the new p16 */
+ pdb_sethexpwd(ascii_p16, pwd->smb_passwd, pwd->acct_ctrl);
+
+ /* Add on the NT md4 hash */
+ ascii_p16[32] = ':';
+ wr_len = 66;
+ pdb_sethexpwd(ascii_p16+33, pwd->smb_nt_passwd, pwd->acct_ctrl);
+ ascii_p16[65] = ':';
+ ascii_p16[66] = '\0'; /* null-terminate the string so that strlen works */
+
+ /* Add on the account info bits and the time of last password change. */
+ if(got_pass_last_set_time) {
+ slprintf(&ascii_p16[strlen(ascii_p16)],
+ sizeof(ascii_p16)-(strlen(ascii_p16)+1),
+ "%s:LCT-%08X:",
+ encode_bits, (uint32_t)pwd->pass_last_set_time );
+ wr_len = strlen(ascii_p16);
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,("mod_smbfilepwd_entry: "));
+ dump_data(100, (uint8_t *)ascii_p16, wr_len);
+#endif
+
+ if(wr_len > LINEBUF_SIZE) {
+ DEBUG(0, ("mod_smbfilepwd_entry: line to write (%d) is too long.\n", wr_len+1));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return (False);
+ }
+
+ /*
+ * Do an atomic write into the file at the position defined by
+ * seekpos.
+ */
+
+ /* The mod user write needs to be atomic - so get the fd from
+ the fp and do a raw write() call.
+ */
+
+ fd = fileno(fp);
+
+ if (lseek(fd, pwd_seekpos - 1, SEEK_SET) != pwd_seekpos - 1) {
+ DEBUG(0, ("mod_smbfilepwd_entry: seek fail on file %s.\n", pfile));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ /* Sanity check - ensure the areas we are writing are framed by ':' */
+ if (read(fd, linebuf, wr_len+1) != wr_len+1) {
+ DEBUG(0, ("mod_smbfilepwd_entry: read fail on file %s.\n", pfile));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ if ((linebuf[0] != ':') || (linebuf[wr_len] != ':')) {
+ DEBUG(0, ("mod_smbfilepwd_entry: check on passwd file %s failed.\n", pfile));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ if (lseek(fd, pwd_seekpos, SEEK_SET) != pwd_seekpos) {
+ DEBUG(0, ("mod_smbfilepwd_entry: seek fail on file %s.\n", pfile));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ if (write(fd, ascii_p16, wr_len) != wr_len) {
+ DEBUG(0, ("mod_smbfilepwd_entry: write failed in passwd file %s\n", pfile));
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return False;
+ }
+
+ pw_file_unlock(lockfd,&smbpasswd_state->pw_file_lock_depth);
+ fclose(fp);
+ return True;
+}
+
+/************************************************************************
+ Routine to delete an entry in the smbpasswd file by name.
+*************************************************************************/
+
+static bool del_smbfilepwd_entry(struct smbpasswd_privates *smbpasswd_state, const char *name)
+{
+ const char *pfile = smbpasswd_state->smbpasswd_file;
+ char *pfile2 = NULL;
+ struct smb_passwd *pwd = NULL;
+ FILE *fp = NULL;
+ FILE *fp_write = NULL;
+ int pfile2_lockdepth = 0;
+
+ pfile2 = talloc_asprintf(talloc_tos(),
+ "%s.%u",
+ pfile, (unsigned)getpid());
+ if (!pfile2) {
+ return false;
+ }
+
+ /*
+ * Open the smbpassword file - for update. It needs to be update
+ * as we need any other processes to wait until we have replaced
+ * it.
+ */
+
+ if((fp = startsmbfilepwent(pfile, PWF_UPDATE, &smbpasswd_state->pw_file_lock_depth)) == NULL) {
+ DEBUG(0, ("del_smbfilepwd_entry: unable to open file %s.\n", pfile));
+ return False;
+ }
+
+ /*
+ * Create the replacement password file.
+ */
+ if((fp_write = startsmbfilepwent(pfile2, PWF_CREATE, &pfile2_lockdepth)) == NULL) {
+ DEBUG(0, ("del_smbfilepwd_entry: unable to open file %s.\n", pfile));
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ return False;
+ }
+
+ /*
+ * Scan the file, a line at a time and check if the name matches.
+ */
+
+ while ((pwd = getsmbfilepwent(smbpasswd_state, fp)) != NULL) {
+ char *new_entry;
+ size_t new_entry_length;
+
+ if (strequal(name, pwd->smb_name)) {
+ DEBUG(10, ("del_smbfilepwd_entry: found entry with "
+ "name %s - deleting it.\n", name));
+ continue;
+ }
+
+ /*
+ * We need to copy the entry out into the second file.
+ */
+
+ if((new_entry = format_new_smbpasswd_entry(pwd)) == NULL) {
+ DEBUG(0, ("del_smbfilepwd_entry(malloc): Failed to copy entry for user %s to file %s. \
+Error was %s\n", pwd->smb_name, pfile2, strerror(errno)));
+ unlink(pfile2);
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ endsmbfilepwent(fp_write, &pfile2_lockdepth);
+ return False;
+ }
+
+ new_entry_length = strlen(new_entry);
+
+ if(fwrite(new_entry, 1, new_entry_length, fp_write) != new_entry_length) {
+ DEBUG(0, ("del_smbfilepwd_entry(write): Failed to copy entry for user %s to file %s. \
+Error was %s\n", pwd->smb_name, pfile2, strerror(errno)));
+ unlink(pfile2);
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ endsmbfilepwent(fp_write, &pfile2_lockdepth);
+ free(new_entry);
+ return False;
+ }
+
+ free(new_entry);
+ }
+
+ /*
+ * Ensure pfile2 is flushed before rename.
+ */
+
+ if(fflush(fp_write) != 0) {
+ DEBUG(0, ("del_smbfilepwd_entry: Failed to flush file %s. Error was %s\n", pfile2, strerror(errno)));
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ endsmbfilepwent(fp_write,&pfile2_lockdepth);
+ return False;
+ }
+
+ /*
+ * Do an atomic rename - then release the locks.
+ */
+
+ if(rename(pfile2,pfile) != 0) {
+ unlink(pfile2);
+ }
+
+ endsmbfilepwent(fp, &smbpasswd_state->pw_file_lock_depth);
+ endsmbfilepwent(fp_write,&pfile2_lockdepth);
+ return True;
+}
+
+/*********************************************************************
+ Create a smb_passwd struct from a struct samu.
+ We will not allocate any new memory. The smb_passwd struct
+ should only stay around as long as the struct samu does.
+ ********************************************************************/
+
+static bool build_smb_pass (struct smb_passwd *smb_pw, const struct samu *sampass)
+{
+ uint32_t rid;
+
+ if (sampass == NULL)
+ return False;
+ ZERO_STRUCTP(smb_pw);
+
+ if (!IS_SAM_DEFAULT(sampass, PDB_USERSID)) {
+ rid = pdb_get_user_rid(sampass);
+
+ /* If the user specified a RID, make sure its able to be both stored and retrieved */
+ if (rid == DOMAIN_RID_GUEST) {
+ struct passwd *passwd = Get_Pwnam_alloc(NULL, lp_guest_account());
+ if (!passwd) {
+ DEBUG(0, ("Could not find guest account via Get_Pwnam_alloc()! (%s)\n", lp_guest_account()));
+ return False;
+ }
+ smb_pw->smb_userid=passwd->pw_uid;
+ TALLOC_FREE(passwd);
+ } else if (algorithmic_pdb_rid_is_user(rid)) {
+ smb_pw->smb_userid=algorithmic_pdb_user_rid_to_uid(rid);
+ } else {
+ DEBUG(0,("build_sam_pass: Failing attempt to store user with non-uid based user RID. \n"));
+ return False;
+ }
+ }
+
+ smb_pw->smb_name=(const char*)pdb_get_username(sampass);
+
+ smb_pw->smb_passwd=pdb_get_lanman_passwd(sampass);
+ smb_pw->smb_nt_passwd=pdb_get_nt_passwd(sampass);
+
+ smb_pw->acct_ctrl=pdb_get_acct_ctrl(sampass);
+ smb_pw->pass_last_set_time=pdb_get_pass_last_set_time(sampass);
+
+ return True;
+}
+
+/*********************************************************************
+ Create a struct samu from a smb_passwd struct
+ ********************************************************************/
+
+static bool build_sam_account(struct smbpasswd_privates *smbpasswd_state,
+ struct samu *sam_pass, const struct smb_passwd *pw_buf)
+{
+ struct passwd *pwfile;
+
+ if ( !sam_pass ) {
+ DEBUG(5,("build_sam_account: struct samu is NULL\n"));
+ return False;
+ }
+
+ /* verify the user account exists */
+
+ if ( !(pwfile = Get_Pwnam_alloc(NULL, pw_buf->smb_name )) ) {
+ DEBUG(0,("build_sam_account: smbpasswd database is corrupt! username %s with uid "
+ "%u is not in unix passwd database!\n", pw_buf->smb_name, pw_buf->smb_userid));
+ return False;
+ }
+
+ if ( !NT_STATUS_IS_OK( samu_set_unix(sam_pass, pwfile )) )
+ return False;
+
+ TALLOC_FREE(pwfile);
+
+ /* set remaining fields */
+
+ if (!pdb_set_nt_passwd (sam_pass, pw_buf->smb_nt_passwd, PDB_SET))
+ return False;
+ if (!pdb_set_lanman_passwd (sam_pass, pw_buf->smb_passwd, PDB_SET))
+ return False;
+ pdb_set_acct_ctrl (sam_pass, pw_buf->acct_ctrl, PDB_SET);
+ pdb_set_pass_last_set_time (sam_pass, pw_buf->pass_last_set_time, PDB_SET);
+ pdb_set_pass_can_change_time (sam_pass, pw_buf->pass_last_set_time, PDB_SET);
+
+ return True;
+}
+
+/*****************************************************************
+ Functions to be implemented by the new passdb API
+ ****************************************************************/
+
+/****************************************************************
+ Search smbpasswd file by iterating over the entries. Do not
+ call getpwnam() for unix account information until we have found
+ the correct entry
+ ***************************************************************/
+
+static NTSTATUS smbpasswd_getsampwnam(struct pdb_methods *my_methods,
+ struct samu *sam_acct, const char *username)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data;
+ struct smb_passwd *smb_pw;
+ FILE *fp = NULL;
+
+ DEBUG(10, ("getsampwnam (smbpasswd): search by name: %s\n", username));
+
+ /* startsmbfilepwent() is used here as we don't want to lookup
+ the UNIX account in the local system password file until
+ we have a match. */
+ fp = startsmbfilepwent(smbpasswd_state->smbpasswd_file, PWF_READ, &(smbpasswd_state->pw_file_lock_depth));
+
+ if (fp == NULL) {
+ DEBUG(0, ("Unable to open passdb database.\n"));
+ return nt_status;
+ }
+
+ while ( ((smb_pw=getsmbfilepwent(smbpasswd_state, fp)) != NULL)&& (!strequal(smb_pw->smb_name, username)) )
+ /* do nothing....another loop */ ;
+
+ endsmbfilepwent(fp, &(smbpasswd_state->pw_file_lock_depth));
+
+
+ /* did we locate the username in smbpasswd */
+ if (smb_pw == NULL)
+ return nt_status;
+
+ DEBUG(10, ("getsampwnam (smbpasswd): found by name: %s\n", smb_pw->smb_name));
+
+ if (!sam_acct) {
+ DEBUG(10,("getsampwnam (smbpasswd): struct samu is NULL\n"));
+ return nt_status;
+ }
+
+ /* now build the struct samu */
+ if (!build_sam_account(smbpasswd_state, sam_acct, smb_pw))
+ return nt_status;
+
+ /* success */
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbpasswd_getsampwsid(struct pdb_methods *my_methods, struct samu *sam_acct, const struct dom_sid *sid)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data;
+ struct smb_passwd *smb_pw;
+ struct dom_sid_buf buf;
+ FILE *fp = NULL;
+ uint32_t rid;
+
+ DEBUG(10, ("smbpasswd_getsampwrid: search by sid: %s\n",
+ dom_sid_str_buf(sid, &buf)));
+
+ if (!sid_peek_check_rid(get_global_sam_sid(), sid, &rid))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ /* More special case 'guest account' hacks... */
+ if (rid == DOMAIN_RID_GUEST) {
+ const char *guest_account = lp_guest_account();
+ if (!(guest_account && *guest_account)) {
+ DEBUG(1, ("Guest account not specified!\n"));
+ return nt_status;
+ }
+ return smbpasswd_getsampwnam(my_methods, sam_acct, guest_account);
+ }
+
+ /* Open the sam password file - not for update. */
+ fp = startsmbfilepwent(smbpasswd_state->smbpasswd_file, PWF_READ, &(smbpasswd_state->pw_file_lock_depth));
+
+ if (fp == NULL) {
+ DEBUG(0, ("Unable to open passdb database.\n"));
+ return nt_status;
+ }
+
+ while ( ((smb_pw=getsmbfilepwent(smbpasswd_state, fp)) != NULL) && (algorithmic_pdb_uid_to_user_rid(smb_pw->smb_userid) != rid) )
+ /* do nothing */ ;
+
+ endsmbfilepwent(fp, &(smbpasswd_state->pw_file_lock_depth));
+
+
+ /* did we locate the username in smbpasswd */
+ if (smb_pw == NULL)
+ return nt_status;
+
+ DEBUG(10, ("getsampwrid (smbpasswd): found by name: %s\n", smb_pw->smb_name));
+
+ if (!sam_acct) {
+ DEBUG(10,("getsampwrid: (smbpasswd) struct samu is NULL\n"));
+ return nt_status;
+ }
+
+ /* now build the struct samu */
+ if (!build_sam_account (smbpasswd_state, sam_acct, smb_pw))
+ return nt_status;
+
+ /* build_sam_account might change the SID on us, if the name was for the guest account */
+ if (NT_STATUS_IS_OK(nt_status) && !dom_sid_equal(pdb_get_user_sid(sam_acct), sid)) {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(1, ("looking for user with sid %s instead returned %s "
+ "for account %s!?!\n",
+ dom_sid_str_buf(sid, &buf1),
+ dom_sid_str_buf(pdb_get_user_sid(sam_acct), &buf2),
+ pdb_get_username(sam_acct)));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /* success */
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbpasswd_add_sam_account(struct pdb_methods *my_methods, struct samu *sampass)
+{
+ struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data;
+ struct smb_passwd smb_pw;
+
+ /* convert the struct samu */
+ if (!build_smb_pass(&smb_pw, sampass)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* add the entry */
+ return add_smbfilepwd_entry(smbpasswd_state, &smb_pw);
+}
+
+static NTSTATUS smbpasswd_update_sam_account(struct pdb_methods *my_methods, struct samu *sampass)
+{
+ struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data;
+ struct smb_passwd smb_pw;
+
+ /* convert the struct samu */
+ if (!build_smb_pass(&smb_pw, sampass)) {
+ DEBUG(0, ("smbpasswd_update_sam_account: build_smb_pass failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* update the entry */
+ if(!mod_smbfilepwd_entry(smbpasswd_state, &smb_pw)) {
+ DEBUG(0, ("smbpasswd_update_sam_account: mod_smbfilepwd_entry failed!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbpasswd_delete_sam_account (struct pdb_methods *my_methods, struct samu *sampass)
+{
+ struct smbpasswd_privates *smbpasswd_state = (struct smbpasswd_privates*)my_methods->private_data;
+
+ const char *username = pdb_get_username(sampass);
+
+ if (del_smbfilepwd_entry(smbpasswd_state, username))
+ return NT_STATUS_OK;
+
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+static NTSTATUS smbpasswd_rename_sam_account (struct pdb_methods *my_methods,
+ struct samu *old_acct,
+ const char *newname)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *rename_script = NULL;
+ struct samu *new_acct = NULL;
+ bool interim_account = False;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+
+ if (!*(lp_rename_user_script(talloc_tos(), lp_sub)))
+ goto done;
+
+ if ( !(new_acct = samu_new( NULL )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !pdb_copy_sam_account( new_acct, old_acct )
+ || !pdb_set_username(new_acct, newname, PDB_CHANGED))
+ {
+ goto done;
+ }
+
+ ret = smbpasswd_add_sam_account(my_methods, new_acct);
+ if (!NT_STATUS_IS_OK(ret))
+ goto done;
+
+ interim_account = True;
+
+ /* rename the posix user */
+ rename_script = lp_rename_user_script(ctx, lp_sub);
+ if (!rename_script) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (*rename_script) {
+ int rename_ret;
+
+ rename_script = talloc_string_sub2(ctx,
+ rename_script,
+ "%unew",
+ newname,
+ true,
+ false,
+ true);
+ if (!rename_script) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ rename_script = talloc_string_sub2(ctx,
+ rename_script,
+ "%uold",
+ pdb_get_username(old_acct),
+ true,
+ false,
+ true);
+ if (!rename_script) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rename_ret = smbrun(rename_script, NULL, NULL);
+
+ DEBUG(rename_ret ? 0 : 3,("Running the command `%s' gave %d\n", rename_script, rename_ret));
+
+ if (rename_ret == 0) {
+ smb_nscd_flush_user_cache();
+ }
+
+ if (rename_ret)
+ goto done;
+ } else {
+ goto done;
+ }
+
+ smbpasswd_delete_sam_account(my_methods, old_acct);
+ interim_account = False;
+
+done:
+ /* cleanup */
+ if (interim_account)
+ smbpasswd_delete_sam_account(my_methods, new_acct);
+
+ if (new_acct)
+ TALLOC_FREE(new_acct);
+
+ return (ret);
+}
+
+static uint32_t smbpasswd_capabilities(struct pdb_methods *methods)
+{
+ return 0;
+}
+
+static void free_private_data(void **vp)
+{
+ struct smbpasswd_privates **privates = (struct smbpasswd_privates**)vp;
+
+ endsmbfilepwent((*privates)->pw_file, &((*privates)->pw_file_lock_depth));
+
+ *privates = NULL;
+ /* No need to free any further, as it is talloc()ed */
+}
+
+struct smbpasswd_search_state {
+ uint32_t acct_flags;
+
+ struct samr_displayentry *entries;
+ uint32_t num_entries;
+ ssize_t array_size;
+ uint32_t current;
+};
+
+static void smbpasswd_search_end(struct pdb_search *search)
+{
+ struct smbpasswd_search_state *state = talloc_get_type_abort(
+ search->private_data, struct smbpasswd_search_state);
+ TALLOC_FREE(state);
+}
+
+static bool smbpasswd_search_next_entry(struct pdb_search *search,
+ struct samr_displayentry *entry)
+{
+ struct smbpasswd_search_state *state = talloc_get_type_abort(
+ search->private_data, struct smbpasswd_search_state);
+
+ if (state->current == state->num_entries) {
+ return false;
+ }
+
+ entry->idx = state->entries[state->current].idx;
+ entry->rid = state->entries[state->current].rid;
+ entry->acct_flags = state->entries[state->current].acct_flags;
+
+ entry->account_name = talloc_strdup(
+ search, state->entries[state->current].account_name);
+ entry->fullname = talloc_strdup(
+ search, state->entries[state->current].fullname);
+ entry->description = talloc_strdup(
+ search, state->entries[state->current].description);
+
+ if ((entry->account_name == NULL) || (entry->fullname == NULL)
+ || (entry->description == NULL)) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ return false;
+ }
+
+ state->current += 1;
+ return true;
+}
+
+static bool smbpasswd_search_users(struct pdb_methods *methods,
+ struct pdb_search *search,
+ uint32_t acct_flags)
+{
+ struct smbpasswd_privates *smbpasswd_state =
+ (struct smbpasswd_privates*)methods->private_data;
+
+ struct smbpasswd_search_state *search_state;
+ struct smb_passwd *pwd;
+ FILE *fp;
+
+ search_state = talloc_zero(search, struct smbpasswd_search_state);
+ if (search_state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return false;
+ }
+ search_state->acct_flags = acct_flags;
+
+ fp = startsmbfilepwent(smbpasswd_state->smbpasswd_file, PWF_READ,
+ &smbpasswd_state->pw_file_lock_depth);
+
+ if (fp == NULL) {
+ DEBUG(10, ("Unable to open smbpasswd file.\n"));
+ TALLOC_FREE(search_state);
+ return false;
+ }
+
+ while ((pwd = getsmbfilepwent(smbpasswd_state, fp)) != NULL) {
+ struct samr_displayentry entry;
+ struct samu *user;
+
+ if ((acct_flags != 0)
+ && ((acct_flags & pwd->acct_ctrl) == 0)) {
+ continue;
+ }
+
+ user = samu_new(talloc_tos());
+ if (user == NULL) {
+ DEBUG(0, ("samu_new failed\n"));
+ break;
+ }
+
+ if (!build_sam_account(smbpasswd_state, user, pwd)) {
+ /* Already got debug msgs... */
+ break;
+ }
+
+ ZERO_STRUCT(entry);
+
+ entry.acct_flags = pdb_get_acct_ctrl(user);
+ sid_peek_rid(pdb_get_user_sid(user), &entry.rid);
+ entry.account_name = talloc_strdup(
+ search_state, pdb_get_username(user));
+ entry.fullname = talloc_strdup(
+ search_state, pdb_get_fullname(user));
+ entry.description = talloc_strdup(
+ search_state, pdb_get_acct_desc(user));
+
+ TALLOC_FREE(user);
+
+ if ((entry.account_name == NULL) || (entry.fullname == NULL)
+ || (entry.description == NULL)) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ break;
+ }
+
+ ADD_TO_LARGE_ARRAY(search_state, struct samr_displayentry,
+ entry, &search_state->entries,
+ &search_state->num_entries,
+ &search_state->array_size);
+ }
+
+ endsmbfilepwent(fp, &(smbpasswd_state->pw_file_lock_depth));
+
+ search->private_data = search_state;
+ search->next_entry = smbpasswd_search_next_entry;
+ search->search_end = smbpasswd_search_end;
+
+ return true;
+}
+
+static NTSTATUS pdb_init_smbpasswd( struct pdb_methods **pdb_method, const char *location )
+{
+ NTSTATUS nt_status;
+ struct smbpasswd_privates *privates;
+
+ if ( !NT_STATUS_IS_OK(nt_status = make_pdb_method( pdb_method )) ) {
+ return nt_status;
+ }
+
+ (*pdb_method)->name = "smbpasswd";
+
+ (*pdb_method)->getsampwnam = smbpasswd_getsampwnam;
+ (*pdb_method)->getsampwsid = smbpasswd_getsampwsid;
+ (*pdb_method)->add_sam_account = smbpasswd_add_sam_account;
+ (*pdb_method)->update_sam_account = smbpasswd_update_sam_account;
+ (*pdb_method)->delete_sam_account = smbpasswd_delete_sam_account;
+ (*pdb_method)->rename_sam_account = smbpasswd_rename_sam_account;
+ (*pdb_method)->search_users = smbpasswd_search_users;
+
+ (*pdb_method)->capabilities = smbpasswd_capabilities;
+
+ /* Setup private data and free function */
+
+ if ( !(privates = talloc_zero( *pdb_method, struct smbpasswd_privates )) ) {
+ DEBUG(0, ("talloc() failed for smbpasswd private_data!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Store some config details */
+
+ if (location) {
+ privates->smbpasswd_file = talloc_strdup(*pdb_method, location);
+ } else {
+ privates->smbpasswd_file = talloc_strdup(*pdb_method, lp_smb_passwd_file());
+ }
+
+ if (!privates->smbpasswd_file) {
+ DEBUG(0, ("talloc_strdp() failed for storing smbpasswd location!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ (*pdb_method)->private_data = privates;
+
+ (*pdb_method)->free_private_data = free_private_data;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pdb_smbpasswd_init(TALLOC_CTX *ctx)
+{
+ return smb_register_passdb(PASSDB_INTERFACE_VERSION, "smbpasswd", pdb_init_smbpasswd);
+}
diff --git a/source3/passdb/pdb_smbpasswd.h b/source3/passdb/pdb_smbpasswd.h
new file mode 100644
index 0000000..5dd7c8c
--- /dev/null
+++ b/source3/passdb/pdb_smbpasswd.h
@@ -0,0 +1,30 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * SMB parameters and setup
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Modified by Jeremy Allison 1995.
+ * Modified by Gerald (Jerry) Carter 2000-2001,2003
+ * Modified by Andrew Bartlett 2002.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PASSDB_PDB_SMBPASSWD_H_
+#define _PASSDB_PDB_SMBPASSWD_H_
+
+/* The following definitions come from passdb/pdb_smbpasswd.c */
+
+NTSTATUS pdb_smbpasswd_init(TALLOC_CTX *) ;
+
+#endif /* _PASSDB_PDB_SMBPASSWD_H_ */
diff --git a/source3/passdb/pdb_tdb.c b/source3/passdb/pdb_tdb.c
new file mode 100644
index 0000000..f9ba193
--- /dev/null
+++ b/source3/passdb/pdb_tdb.c
@@ -0,0 +1,1369 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * SMB parameters and setup
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Simo Sorce 2000-2003
+ * Copyright (C) Gerald Carter 2000-2006
+ * Copyright (C) Jeremy Allison 2001-2009
+ * Copyright (C) Andrew Bartlett 2002
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "passdb.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "util_tdb.h"
+#include "passdb/pdb_tdb.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/string_wrappers.h"
+
+#if 0 /* when made a module use this */
+
+static int tdbsam_debug_level = DBGC_ALL;
+#undef DBGC_CLASS
+#define DBGC_CLASS tdbsam_debug_level
+
+#else
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+#endif
+
+#define TDBSAM_VERSION 4 /* Most recent TDBSAM version */
+#define TDBSAM_MINOR_VERSION 0 /* Most recent TDBSAM minor version */
+#define TDBSAM_VERSION_STRING "INFO/version"
+#define TDBSAM_MINOR_VERSION_STRING "INFO/minor_version"
+#define PASSDB_FILE_NAME "passdb.tdb"
+#define USERPREFIX "USER_"
+#define USERPREFIX_LEN 5
+#define RIDPREFIX "RID_"
+#define PRIVPREFIX "PRIV_"
+#define NEXT_RID_STRING "NEXT_RID"
+
+/* GLOBAL TDB SAM CONTEXT */
+
+static struct db_context *db_sam;
+static char *tdbsam_filename;
+static bool map_builtin;
+
+struct tdbsam_convert_state {
+ int32_t from;
+ bool success;
+};
+
+static int tdbsam_convert_one(struct db_record *rec, void *priv)
+{
+ struct tdbsam_convert_state *state =
+ (struct tdbsam_convert_state *)priv;
+ struct samu *user;
+ TDB_DATA data;
+ NTSTATUS status;
+ bool ret;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+
+ if (key.dsize < USERPREFIX_LEN) {
+ return 0;
+ }
+ if (strncmp((char *)key.dptr, USERPREFIX, USERPREFIX_LEN) != 0) {
+ return 0;
+ }
+
+ user = samu_new(talloc_tos());
+ if (user == NULL) {
+ DEBUG(0,("tdbsam_convert: samu_new() failed!\n"));
+ state->success = false;
+ return -1;
+ }
+
+ DEBUG(10,("tdbsam_convert: Try unpacking a record with (key:%s) "
+ "(version:%d)\n", (char *)key.dptr, state->from));
+
+ value = dbwrap_record_get_value(rec);
+
+ switch (state->from) {
+ case 0:
+ ret = init_samu_from_buffer(user, SAMU_BUFFER_V0,
+ (uint8_t *)value.dptr,
+ value.dsize);
+ break;
+ case 1:
+ ret = init_samu_from_buffer(user, SAMU_BUFFER_V1,
+ (uint8_t *)value.dptr,
+ value.dsize);
+ break;
+ case 2:
+ ret = init_samu_from_buffer(user, SAMU_BUFFER_V2,
+ (uint8_t *)value.dptr,
+ value.dsize);
+ break;
+ case 3:
+ ret = init_samu_from_buffer(user, SAMU_BUFFER_V3,
+ (uint8_t *)value.dptr,
+ value.dsize);
+ break;
+ case 4:
+ ret = init_samu_from_buffer(user, SAMU_BUFFER_V4,
+ (uint8_t *)value.dptr,
+ value.dsize);
+ break;
+ default:
+ /* unknown tdbsam version */
+ ret = False;
+ }
+ if (!ret) {
+ DEBUG(0,("tdbsam_convert: Bad struct samu entry returned "
+ "from TDB (key:%s) (version:%d)\n", (char *)key.dptr,
+ state->from));
+ TALLOC_FREE(user);
+ state->success = false;
+ return -1;
+ }
+
+ data.dsize = init_buffer_from_samu(&data.dptr, user, false);
+ TALLOC_FREE(user);
+
+ if (data.dsize == -1) {
+ DEBUG(0,("tdbsam_convert: cannot pack the struct samu into "
+ "the new format\n"));
+ state->success = false;
+ return -1;
+ }
+
+ status = dbwrap_record_store(rec, data, TDB_MODIFY);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not store the new record: %s\n",
+ nt_errstr(status)));
+ state->success = false;
+ return -1;
+ }
+
+ return 0;
+}
+
+/**********************************************************************
+ Struct and function to backup an old record.
+ *********************************************************************/
+
+struct tdbsam_backup_state {
+ struct db_context *new_db;
+ bool success;
+};
+
+static int backup_copy_fn(struct db_record *orig_rec, void *state)
+{
+ struct tdbsam_backup_state *bs = (struct tdbsam_backup_state *)state;
+ struct db_record *new_rec;
+ NTSTATUS status;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(orig_rec);
+
+ new_rec = dbwrap_fetch_locked(bs->new_db, talloc_tos(), key);
+ if (new_rec == NULL) {
+ bs->success = false;
+ return 1;
+ }
+
+ value = dbwrap_record_get_value(orig_rec);
+
+ status = dbwrap_record_store(new_rec, value, TDB_INSERT);
+
+ TALLOC_FREE(new_rec);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ bs->success = false;
+ return 1;
+ }
+ return 0;
+}
+
+/**********************************************************************
+ Make a backup of an old passdb and replace the new one with it. We
+ have to do this as between 3.0.x and 3.2.x the hash function changed
+ by mistake (used unsigned char * instead of char *). This means the
+ previous simple update code will fail due to not being able to find
+ existing records to replace in the tdbsam_convert_one() function. JRA.
+ *********************************************************************/
+
+static bool tdbsam_convert_backup(const char *dbname, struct db_context **pp_db)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *tmp_fname = NULL;
+ struct db_context *tmp_db = NULL;
+ struct db_context *orig_db = *pp_db;
+ struct tdbsam_backup_state bs;
+ NTSTATUS status;
+
+ tmp_fname = talloc_asprintf(frame, "%s.tmp", dbname);
+ if (!tmp_fname) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ unlink(tmp_fname);
+
+ /* Remember to open this on the NULL context. We need
+ * it to stay around after we return from here. */
+
+ tmp_db = db_open(NULL, tmp_fname, 0,
+ TDB_DEFAULT, O_CREAT|O_RDWR, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (tmp_db == NULL) {
+ DEBUG(0, ("tdbsam_convert_backup: Failed to create backup TDB passwd "
+ "[%s]\n", tmp_fname));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (dbwrap_transaction_start(orig_db) != 0) {
+ DEBUG(0, ("tdbsam_convert_backup: Could not start transaction (1)\n"));
+ unlink(tmp_fname);
+ TALLOC_FREE(tmp_db);
+ TALLOC_FREE(frame);
+ return false;
+ }
+ if (dbwrap_transaction_start(tmp_db) != 0) {
+ DEBUG(0, ("tdbsam_convert_backup: Could not start transaction (2)\n"));
+ dbwrap_transaction_cancel(orig_db);
+ unlink(tmp_fname);
+ TALLOC_FREE(tmp_db);
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ bs.new_db = tmp_db;
+ bs.success = true;
+
+ status = dbwrap_traverse(orig_db, backup_copy_fn, (void *)&bs, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("tdbsam_convert_backup: traverse failed\n"));
+ goto cancel;
+ }
+
+ if (!bs.success) {
+ DEBUG(0, ("tdbsam_convert_backup: Rewriting records failed\n"));
+ goto cancel;
+ }
+
+ if (dbwrap_transaction_commit(orig_db) != 0) {
+ smb_panic("tdbsam_convert_backup: orig commit failed\n");
+ }
+ if (dbwrap_transaction_commit(tmp_db) != 0) {
+ smb_panic("tdbsam_convert_backup: orig commit failed\n");
+ }
+
+ /* be sure to close the DBs _before_ renaming the file */
+
+ TALLOC_FREE(orig_db);
+ TALLOC_FREE(tmp_db);
+
+ /* This is safe from other users as we know we're
+ * under a mutex here. */
+
+ if (rename(tmp_fname, dbname) == -1) {
+ DEBUG(0, ("tdbsam_convert_backup: rename of %s to %s failed %s\n",
+ tmp_fname,
+ dbname,
+ strerror(errno)));
+ smb_panic("tdbsam_convert_backup: replace passdb failed\n");
+ }
+
+ TALLOC_FREE(frame);
+
+ /* re-open the converted TDB */
+
+ orig_db = db_open(NULL, dbname, 0,
+ TDB_DEFAULT, O_CREAT|O_RDWR, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (orig_db == NULL) {
+ DEBUG(0, ("tdbsam_convert_backup: Failed to re-open "
+ "converted passdb TDB [%s]\n", dbname));
+ return false;
+ }
+
+ DEBUG(1, ("tdbsam_convert_backup: updated %s file.\n",
+ dbname ));
+
+ /* Replace the global db pointer. */
+ *pp_db = orig_db;
+ return true;
+
+ cancel:
+
+ if (dbwrap_transaction_cancel(orig_db) != 0) {
+ smb_panic("tdbsam_convert: transaction_cancel failed");
+ }
+
+ if (dbwrap_transaction_cancel(tmp_db) != 0) {
+ smb_panic("tdbsam_convert: transaction_cancel failed");
+ }
+
+ unlink(tmp_fname);
+ TALLOC_FREE(tmp_db);
+ TALLOC_FREE(frame);
+ return false;
+}
+
+static bool tdbsam_upgrade_next_rid(struct db_context *db)
+{
+ TDB_CONTEXT *tdb;
+ uint32_t rid;
+ bool ok = false;
+ NTSTATUS status;
+ char *db_path;
+
+ status = dbwrap_fetch_uint32_bystring(db, NEXT_RID_STRING, &rid);
+ if (NT_STATUS_IS_OK(status)) {
+ return true;
+ }
+
+ db_path = state_path(talloc_tos(), "winbindd_idmap.tdb");
+ if (db_path == NULL) {
+ return false;
+ }
+
+ tdb = tdb_open_log(db_path, 0,
+ TDB_DEFAULT, O_RDONLY, 0644);
+ TALLOC_FREE(db_path);
+ if (tdb) {
+ ok = tdb_fetch_uint32(tdb, "RID_COUNTER", &rid);
+ if (!ok) {
+ rid = BASE_RID;
+ }
+ tdb_close(tdb);
+ } else {
+ rid = BASE_RID;
+ }
+
+ status = dbwrap_store_uint32_bystring(db, NEXT_RID_STRING, rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool tdbsam_convert(struct db_context **pp_db, const char *name, int32_t from)
+{
+ struct tdbsam_convert_state state;
+ struct db_context *db = NULL;
+ NTSTATUS status;
+
+ /* We only need the update backup for local db's. */
+ if (db_is_local(name) && !tdbsam_convert_backup(name, pp_db)) {
+ DEBUG(0, ("tdbsam_convert: Could not backup %s\n", name));
+ return false;
+ }
+
+ db = *pp_db;
+ state.from = from;
+ state.success = true;
+
+ if (dbwrap_transaction_start(db) != 0) {
+ DEBUG(0, ("tdbsam_convert: Could not start transaction\n"));
+ return false;
+ }
+
+ if (!tdbsam_upgrade_next_rid(db)) {
+ DEBUG(0, ("tdbsam_convert: tdbsam_upgrade_next_rid failed\n"));
+ goto cancel;
+ }
+
+ status = dbwrap_traverse(db, tdbsam_convert_one, &state, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("tdbsam_convert: traverse failed\n"));
+ goto cancel;
+ }
+
+ if (!state.success) {
+ DEBUG(0, ("tdbsam_convert: Converting records failed\n"));
+ goto cancel;
+ }
+
+ status = dbwrap_store_int32_bystring(db, TDBSAM_VERSION_STRING,
+ TDBSAM_VERSION);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("tdbsam_convert: Could not store tdbsam version: "
+ "%s\n", nt_errstr(status)));
+ goto cancel;
+ }
+
+ status = dbwrap_store_int32_bystring(db, TDBSAM_MINOR_VERSION_STRING,
+ TDBSAM_MINOR_VERSION);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("tdbsam_convert: Could not store tdbsam minor "
+ "version: %s\n", nt_errstr(status)));
+ goto cancel;
+ }
+
+ if (dbwrap_transaction_commit(db) != 0) {
+ DEBUG(0, ("tdbsam_convert: Could not commit transaction\n"));
+ return false;
+ }
+
+ return true;
+
+ cancel:
+ if (dbwrap_transaction_cancel(db) != 0) {
+ smb_panic("tdbsam_convert: transaction_cancel failed");
+ }
+
+ return false;
+}
+
+/*********************************************************************
+ Open the tdbsam file based on the absolute path specified.
+ Uses a reference count to allow multiple open calls.
+*********************************************************************/
+
+static bool tdbsam_open( const char *name )
+{
+ int32_t version;
+ int32_t minor_version;
+ NTSTATUS status;
+
+ /* check if we are already open */
+
+ if ( db_sam ) {
+ return true;
+ }
+
+ /* Try to open tdb passwd. Create a new one if necessary */
+
+ db_sam = db_open(NULL, name, 0, TDB_DEFAULT, O_CREAT|O_RDWR, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db_sam == NULL) {
+ DEBUG(0, ("tdbsam_open: Failed to open/create TDB passwd "
+ "[%s]\n", name));
+ return false;
+ }
+
+ /* Check the version */
+ status = dbwrap_fetch_int32_bystring(db_sam, TDBSAM_VERSION_STRING,
+ &version);
+ if (!NT_STATUS_IS_OK(status)) {
+ version = 0; /* Version not found, assume version 0 */
+ }
+
+ /* Get the minor version */
+ status = dbwrap_fetch_int32_bystring(
+ db_sam, TDBSAM_MINOR_VERSION_STRING, &minor_version);
+ if (!NT_STATUS_IS_OK(status)) {
+ minor_version = 0; /* Minor version not found, assume 0 */
+ }
+
+ /* Compare the version */
+ if (version > TDBSAM_VERSION) {
+ /* Version more recent than the latest known */
+ DEBUG(0, ("tdbsam_open: unknown version => %d\n", version));
+ TALLOC_FREE(db_sam);
+ return false;
+ }
+
+ if ( version < TDBSAM_VERSION ||
+ (version == TDBSAM_VERSION &&
+ minor_version < TDBSAM_MINOR_VERSION) ) {
+ /*
+ * Ok - we think we're going to have to convert.
+ * Due to the backup process we now must do to
+ * upgrade we have to get a mutex and re-check
+ * the version. Someone else may have upgraded
+ * whilst we were checking.
+ */
+
+ struct named_mutex *mtx = grab_named_mutex(NULL,
+ "tdbsam_upgrade_mutex",
+ 600);
+
+ if (!mtx) {
+ DEBUG(0, ("tdbsam_open: failed to grab mutex.\n"));
+ TALLOC_FREE(db_sam);
+ return false;
+ }
+
+ /* Re-check the version */
+ status = dbwrap_fetch_int32_bystring(
+ db_sam, TDBSAM_VERSION_STRING, &version);
+ if (!NT_STATUS_IS_OK(status)) {
+ version = 0; /* Version not found, assume version 0 */
+ }
+
+ /* Re-check the minor version */
+ status = dbwrap_fetch_int32_bystring(
+ db_sam, TDBSAM_MINOR_VERSION_STRING, &minor_version);
+ if (!NT_STATUS_IS_OK(status)) {
+ minor_version = 0; /* Minor version not found, assume 0 */
+ }
+
+ /* Compare the version */
+ if (version > TDBSAM_VERSION) {
+ /* Version more recent than the latest known */
+ DEBUG(0, ("tdbsam_open: unknown version => %d\n", version));
+ TALLOC_FREE(db_sam);
+ TALLOC_FREE(mtx);
+ return false;
+ }
+
+ if ( version < TDBSAM_VERSION ||
+ (version == TDBSAM_VERSION &&
+ minor_version < TDBSAM_MINOR_VERSION) ) {
+ /*
+ * Note that minor versions we read that are greater
+ * than the current minor version we have hard coded
+ * are assumed to be compatible if they have the same
+ * major version. That allows previous versions of the
+ * passdb code that don't know about minor versions to
+ * still use this database. JRA.
+ */
+
+ DEBUG(1, ("tdbsam_open: Converting version %d.%d database to "
+ "version %d.%d.\n",
+ version,
+ minor_version,
+ TDBSAM_VERSION,
+ TDBSAM_MINOR_VERSION));
+
+ if ( !tdbsam_convert(&db_sam, name, version) ) {
+ DEBUG(0, ("tdbsam_open: Error when trying to convert "
+ "tdbsam [%s]\n",name));
+ TALLOC_FREE(db_sam);
+ TALLOC_FREE(mtx);
+ return false;
+ }
+
+ DEBUG(3, ("TDBSAM converted successfully.\n"));
+ }
+ TALLOC_FREE(mtx);
+ }
+
+ DEBUG(4,("tdbsam_open: successfully opened %s\n", name ));
+
+ return true;
+}
+
+/******************************************************************
+ Lookup a name in the SAM TDB
+******************************************************************/
+
+static NTSTATUS tdbsam_getsampwnam (struct pdb_methods *my_methods,
+ struct samu *user, const char *sname)
+{
+ TDB_DATA data;
+ fstring keystr;
+ fstring name;
+ NTSTATUS status;
+
+ if ( !user ) {
+ DEBUG(0,("pdb_getsampwnam: struct samu is NULL.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Data is stored in all lower-case */
+ fstrcpy(name, sname);
+ if (!strlower_m(name)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* set search key */
+ fstr_sprintf(keystr, "%s%s", USERPREFIX, name);
+
+ /* open the database */
+
+ if ( !tdbsam_open( tdbsam_filename ) ) {
+ DEBUG(0,("tdbsam_getsampwnam: failed to open %s!\n", tdbsam_filename));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* get the record */
+
+ status = dbwrap_fetch_bystring(db_sam, talloc_tos(), keystr, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("pdb_getsampwnam (TDB): error fetching database.\n"));
+ DEBUGADD(5, (" Key: %s\n", keystr));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ if (data.dsize == 0) {
+ DEBUG(5, ("%s: Got 0-sized record for key %s\n", __func__,
+ keystr));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /* unpack the buffer */
+
+ if (!init_samu_from_buffer(user, SAMU_BUFFER_LATEST, data.dptr, data.dsize)) {
+ DBG_ERR("Bad struct samu entry returned from TDB!\n");
+ TALLOC_FREE(data.dptr);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* success */
+
+ TALLOC_FREE(data.dptr);
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ Search by rid
+ **************************************************************************/
+
+static NTSTATUS tdbsam_getsampwrid (struct pdb_methods *my_methods,
+ struct samu *user, uint32_t rid)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ TDB_DATA data;
+ fstring keystr;
+ fstring name;
+
+ if ( !user ) {
+ DEBUG(0,("pdb_getsampwrid: struct samu is NULL.\n"));
+ return nt_status;
+ }
+
+ /* set search key */
+
+ fstr_sprintf(keystr, "%s%.8x", RIDPREFIX, rid);
+
+ /* open the database */
+
+ if ( !tdbsam_open( tdbsam_filename ) ) {
+ DEBUG(0,("tdbsam_getsampwrid: failed to open %s!\n", tdbsam_filename));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* get the record */
+
+ nt_status = dbwrap_fetch_bystring(db_sam, talloc_tos(), keystr, &data);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(5,("pdb_getsampwrid (TDB): error looking up RID %d by key %s.\n", rid, keystr));
+ return nt_status;
+ }
+
+ fstrcpy(name, (const char *)data.dptr);
+ TALLOC_FREE(data.dptr);
+
+ return tdbsam_getsampwnam (my_methods, user, name);
+}
+
+static NTSTATUS tdbsam_getsampwsid(struct pdb_methods *my_methods,
+ struct samu * user, const struct dom_sid *sid)
+{
+ uint32_t rid;
+
+ if ( !sid_peek_check_rid(get_global_sam_sid(), sid, &rid) )
+ return NT_STATUS_UNSUCCESSFUL;
+
+ return tdbsam_getsampwrid(my_methods, user, rid);
+}
+
+static bool tdb_delete_samacct_only( struct samu *sam_pass )
+{
+ fstring keystr;
+ fstring name;
+ NTSTATUS status;
+
+ fstrcpy(name, pdb_get_username(sam_pass));
+ if (!strlower_m(name)) {
+ return false;
+ }
+
+ /* set the search key */
+
+ fstr_sprintf(keystr, "%s%s", USERPREFIX, name);
+
+ /* it's outaa here! 8^) */
+ if ( !tdbsam_open( tdbsam_filename ) ) {
+ DEBUG(0,("tdb_delete_samacct_only: failed to open %s!\n",
+ tdbsam_filename));
+ return false;
+ }
+
+ status = dbwrap_delete_bystring(db_sam, keystr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Error deleting entry from tdb passwd "
+ "database: %s!\n", nt_errstr(status)));
+ return false;
+ }
+
+ return true;
+}
+
+/***************************************************************************
+ Delete a struct samu records for the username and RID key
+****************************************************************************/
+
+static NTSTATUS tdbsam_delete_sam_account(struct pdb_methods *my_methods,
+ struct samu *sam_pass)
+{
+ NTSTATUS nt_status;
+ fstring keystr;
+ uint32_t rid;
+ fstring name;
+
+ /* open the database */
+
+ if ( !tdbsam_open( tdbsam_filename ) ) {
+ DEBUG(0,("tdbsam_delete_sam_account: failed to open %s!\n",
+ tdbsam_filename));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ fstrcpy(name, pdb_get_username(sam_pass));
+ if (!strlower_m(name)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* set the search key */
+
+ fstr_sprintf(keystr, "%s%s", USERPREFIX, name);
+
+ rid = pdb_get_user_rid(sam_pass);
+
+ /* it's outaa here! 8^) */
+
+ if (dbwrap_transaction_start(db_sam) != 0) {
+ DEBUG(0, ("Could not start transaction\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ nt_status = dbwrap_delete_bystring(db_sam, keystr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(5, ("Error deleting entry from tdb passwd "
+ "database: %s!\n", nt_errstr(nt_status)));
+ goto cancel;
+ }
+
+ /* set the search key */
+
+ fstr_sprintf(keystr, "%s%.8x", RIDPREFIX, rid);
+
+ /* it's outaa here! 8^) */
+
+ nt_status = dbwrap_delete_bystring(db_sam, keystr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(5, ("Error deleting entry from tdb rid "
+ "database: %s!\n", nt_errstr(nt_status)));
+ goto cancel;
+ }
+
+ if (dbwrap_transaction_commit(db_sam) != 0) {
+ DEBUG(0, ("Could not commit transaction\n"));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ return NT_STATUS_OK;
+
+ cancel:
+ if (dbwrap_transaction_cancel(db_sam) != 0) {
+ smb_panic("transaction_cancel failed");
+ }
+
+ return nt_status;
+}
+
+
+/***************************************************************************
+ Update the TDB SAM account record only
+ Assumes that the tdbsam is already open
+****************************************************************************/
+static bool tdb_update_samacct_only( struct samu* newpwd, int flag )
+{
+ TDB_DATA data;
+ uint8_t *buf = NULL;
+ fstring keystr;
+ fstring name;
+ bool ret = false;
+ NTSTATUS status;
+
+ /* copy the struct samu struct into a BYTE buffer for storage */
+
+ if ( (data.dsize=init_buffer_from_samu(&buf, newpwd, False)) == -1 ) {
+ DEBUG(0,("tdb_update_sam: ERROR - Unable to copy struct samu info BYTE buffer!\n"));
+ goto done;
+ }
+ data.dptr = buf;
+
+ fstrcpy(name, pdb_get_username(newpwd));
+ if (!strlower_m(name)) {
+ goto done;
+ }
+
+ DEBUG(5, ("Storing %saccount %s with RID %d\n",
+ flag == TDB_INSERT ? "(new) " : "", name,
+ pdb_get_user_rid(newpwd)));
+
+ /* setup the USER index key */
+ fstr_sprintf(keystr, "%s%s", USERPREFIX, name);
+
+ /* add the account */
+
+ status = dbwrap_store_bystring(db_sam, keystr, data, flag);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to modify passwd TDB: %s!\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ ret = true;
+
+done:
+ /* cleanup */
+ SAFE_FREE(buf);
+ return ret;
+}
+
+/***************************************************************************
+ Update the TDB SAM RID record only
+ Assumes that the tdbsam is already open
+****************************************************************************/
+static bool tdb_update_ridrec_only( struct samu* newpwd, int flag )
+{
+ TDB_DATA data;
+ fstring keystr;
+ fstring name;
+ NTSTATUS status;
+
+ fstrcpy(name, pdb_get_username(newpwd));
+ if (!strlower_m(name)) {
+ return false;
+ }
+
+ /* setup RID data */
+ data = string_term_tdb_data(name);
+
+ /* setup the RID index key */
+ fstr_sprintf(keystr, "%s%.8x", RIDPREFIX, pdb_get_user_rid(newpwd));
+
+ /* add the reference */
+ status = dbwrap_store_bystring(db_sam, keystr, data, flag);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to modify TDB passwd: %s!\n",
+ nt_errstr(status)));
+ return false;
+ }
+
+ return true;
+
+}
+
+/***************************************************************************
+ Update the TDB SAM
+****************************************************************************/
+
+static bool tdb_update_sam(struct pdb_methods *my_methods, struct samu* newpwd,
+ int flag)
+{
+ uint32_t oldrid;
+ uint32_t newrid;
+
+ if (!(newrid = pdb_get_user_rid(newpwd))) {
+ DEBUG(0,("tdb_update_sam: struct samu (%s) with no RID!\n",
+ pdb_get_username(newpwd)));
+ return False;
+ }
+
+ oldrid = newrid;
+
+ /* open the database */
+
+ if ( !tdbsam_open( tdbsam_filename ) ) {
+ DEBUG(0,("tdbsam_getsampwnam: failed to open %s!\n", tdbsam_filename));
+ return False;
+ }
+
+ if (dbwrap_transaction_start(db_sam) != 0) {
+ DEBUG(0, ("Could not start transaction\n"));
+ return false;
+ }
+
+ /* If we are updating, we may be changing this users RID. Retrieve the old RID
+ so we can check. */
+
+ if (flag == TDB_MODIFY) {
+ struct samu *account = samu_new(talloc_tos());
+ if (account == NULL) {
+ DEBUG(0,("tdb_update_sam: samu_new() failed\n"));
+ goto cancel;
+ }
+ if (!NT_STATUS_IS_OK(tdbsam_getsampwnam(my_methods, account, pdb_get_username(newpwd)))) {
+ DEBUG(0,("tdb_update_sam: tdbsam_getsampwnam() for %s failed\n",
+ pdb_get_username(newpwd)));
+ TALLOC_FREE(account);
+ goto cancel;
+ }
+ if (!(oldrid = pdb_get_user_rid(account))) {
+ DEBUG(0,("tdb_update_sam: pdb_get_user_rid() failed\n"));
+ TALLOC_FREE(account);
+ goto cancel;
+ }
+ TALLOC_FREE(account);
+ }
+
+ /* Update the new samu entry. */
+ if (!tdb_update_samacct_only(newpwd, flag)) {
+ goto cancel;
+ }
+
+ /* Now take care of the case where the RID changed. We need
+ * to delete the old RID key and add the new. */
+
+ if (flag == TDB_MODIFY && newrid != oldrid) {
+ fstring keystr;
+
+ /* Delete old RID key */
+ DEBUG(10, ("tdb_update_sam: Deleting key for RID %u\n", oldrid));
+ fstr_sprintf(keystr, "%s%.8x", RIDPREFIX, oldrid);
+ if (!NT_STATUS_IS_OK(dbwrap_delete_bystring(db_sam, keystr))) {
+ DEBUG(0, ("tdb_update_sam: Can't delete %s\n", keystr));
+ goto cancel;
+ }
+ /* Insert new RID key */
+ DEBUG(10, ("tdb_update_sam: Inserting key for RID %u\n", newrid));
+ if (!tdb_update_ridrec_only(newpwd, TDB_INSERT)) {
+ goto cancel;
+ }
+ } else {
+ DEBUG(10, ("tdb_update_sam: %s key for RID %u\n",
+ flag == TDB_MODIFY ? "Updating" : "Inserting", newrid));
+ if (!tdb_update_ridrec_only(newpwd, flag)) {
+ goto cancel;
+ }
+ }
+
+ if (dbwrap_transaction_commit(db_sam) != 0) {
+ DEBUG(0, ("Could not commit transaction\n"));
+ return false;
+ }
+
+ return true;
+
+ cancel:
+ if (dbwrap_transaction_cancel(db_sam) != 0) {
+ smb_panic("transaction_cancel failed");
+ }
+ return false;
+}
+
+/***************************************************************************
+ Modifies an existing struct samu
+****************************************************************************/
+
+static NTSTATUS tdbsam_update_sam_account (struct pdb_methods *my_methods, struct samu *newpwd)
+{
+ if ( !tdb_update_sam(my_methods, newpwd, TDB_MODIFY) )
+ return NT_STATUS_UNSUCCESSFUL;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ Adds an existing struct samu
+****************************************************************************/
+
+static NTSTATUS tdbsam_add_sam_account (struct pdb_methods *my_methods, struct samu *newpwd)
+{
+ if ( !tdb_update_sam(my_methods, newpwd, TDB_INSERT) )
+ return NT_STATUS_UNSUCCESSFUL;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ Renames a struct samu
+ - check for the posix user/rename user script
+ - Add and lock the new user record
+ - rename the posix user
+ - rewrite the rid->username record
+ - delete the old user
+ - unlock the new user record
+***************************************************************************/
+static NTSTATUS tdbsam_rename_sam_account(struct pdb_methods *my_methods,
+ struct samu *old_acct,
+ const char *newname)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct samu *new_acct = NULL;
+ char *rename_script = NULL;
+ int rename_ret;
+ fstring oldname_lower;
+ fstring newname_lower;
+
+ /* can't do anything without an external script */
+
+ if ( !(new_acct = samu_new( talloc_tos() )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rename_script = lp_rename_user_script(new_acct, lp_sub);
+ if (!rename_script) {
+ TALLOC_FREE(new_acct);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!*rename_script) {
+ TALLOC_FREE(new_acct);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if ( !pdb_copy_sam_account(new_acct, old_acct)
+ || !pdb_set_username(new_acct, newname, PDB_CHANGED))
+ {
+ TALLOC_FREE(new_acct);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* open the database */
+ if ( !tdbsam_open( tdbsam_filename ) ) {
+ DEBUG(0, ("tdbsam_getsampwnam: failed to open %s!\n",
+ tdbsam_filename));
+ TALLOC_FREE(new_acct);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (dbwrap_transaction_start(db_sam) != 0) {
+ DEBUG(0, ("Could not start transaction\n"));
+ TALLOC_FREE(new_acct);
+ return NT_STATUS_ACCESS_DENIED;
+
+ }
+
+ /* add the new account and lock it */
+ if ( !tdb_update_samacct_only(new_acct, TDB_INSERT) ) {
+ goto cancel;
+ }
+
+ /* Rename the posix user. Follow the semantics of _samr_create_user()
+ so that we lower case the posix name but preserve the case in passdb */
+
+ fstrcpy( oldname_lower, pdb_get_username(old_acct) );
+ if (!strlower_m( oldname_lower )) {
+ goto cancel;
+ }
+
+ fstrcpy( newname_lower, newname );
+ if (!strlower_m( newname_lower )) {
+ goto cancel;
+ }
+
+ rename_script = talloc_string_sub2(new_acct,
+ rename_script,
+ "%unew",
+ newname_lower,
+ true,
+ false,
+ true);
+ if (!rename_script) {
+ goto cancel;
+ }
+ rename_script = talloc_string_sub2(new_acct,
+ rename_script,
+ "%uold",
+ oldname_lower,
+ true,
+ false,
+ true);
+ if (!rename_script) {
+ goto cancel;
+ }
+ rename_ret = smbrun(rename_script, NULL, NULL);
+
+ DEBUG(rename_ret ? 0 : 3,("Running the command `%s' gave %d\n",
+ rename_script, rename_ret));
+
+ if (rename_ret != 0) {
+ goto cancel;
+ }
+
+ smb_nscd_flush_user_cache();
+
+ /* rewrite the rid->username record */
+
+ if ( !tdb_update_ridrec_only( new_acct, TDB_MODIFY) ) {
+ goto cancel;
+ }
+
+ tdb_delete_samacct_only( old_acct );
+
+ if (dbwrap_transaction_commit(db_sam) != 0) {
+ /*
+ * Ok, we're screwed. We've changed the posix account, but
+ * could not adapt passdb.tdb. Shall we change the posix
+ * account back?
+ */
+ DEBUG(0, ("transaction_commit failed\n"));
+ TALLOC_FREE(new_acct);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ TALLOC_FREE(new_acct );
+ return NT_STATUS_OK;
+
+ cancel:
+ if (dbwrap_transaction_cancel(db_sam) != 0) {
+ smb_panic("transaction_cancel failed");
+ }
+
+ TALLOC_FREE(new_acct);
+
+ return NT_STATUS_ACCESS_DENIED;
+}
+
+static uint32_t tdbsam_capabilities(struct pdb_methods *methods)
+{
+ return PDB_CAP_STORE_RIDS;
+}
+
+static bool tdbsam_new_rid(struct pdb_methods *methods, uint32_t *prid)
+{
+ uint32_t rid;
+ NTSTATUS status;
+
+ rid = BASE_RID; /* Default if not set */
+
+ if (!tdbsam_open(tdbsam_filename)) {
+ DEBUG(0,("tdbsam_new_rid: failed to open %s!\n",
+ tdbsam_filename));
+ return false;
+ }
+
+ status = dbwrap_trans_change_uint32_atomic_bystring(
+ db_sam, NEXT_RID_STRING, &rid, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("tdbsam_new_rid: Failed to increase %s: %s\n",
+ NEXT_RID_STRING, nt_errstr(status)));
+ return false;
+ }
+
+ *prid = rid;
+
+ return true;
+}
+
+struct tdbsam_search_state {
+ struct pdb_methods *methods;
+ uint32_t acct_flags;
+
+ uint32_t *rids;
+ uint32_t num_rids;
+ ssize_t array_size;
+ uint32_t current;
+};
+
+static int tdbsam_collect_rids(struct db_record *rec, void *private_data)
+{
+ struct tdbsam_search_state *state = talloc_get_type_abort(
+ private_data, struct tdbsam_search_state);
+ size_t prefixlen = strlen(RIDPREFIX);
+ uint32_t rid;
+ int error = 0;
+ TDB_DATA key;
+
+ key = dbwrap_record_get_key(rec);
+
+ if ((key.dsize < prefixlen)
+ || (strncmp((char *)key.dptr, RIDPREFIX, prefixlen))) {
+ return 0;
+ }
+
+ rid = smb_strtoul((char *)key.dptr+prefixlen,
+ NULL,
+ 16,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ return 0;
+ }
+
+ ADD_TO_LARGE_ARRAY(state, uint32_t, rid, &state->rids, &state->num_rids,
+ &state->array_size);
+
+ return 0;
+}
+
+static void tdbsam_search_end(struct pdb_search *search)
+{
+ struct tdbsam_search_state *state = talloc_get_type_abort(
+ search->private_data, struct tdbsam_search_state);
+ TALLOC_FREE(state);
+}
+
+static bool tdbsam_search_next_entry(struct pdb_search *search,
+ struct samr_displayentry *entry)
+{
+ struct tdbsam_search_state *state = talloc_get_type_abort(
+ search->private_data, struct tdbsam_search_state);
+ struct samu *user = NULL;
+ NTSTATUS status;
+ uint32_t rid;
+
+ again:
+ TALLOC_FREE(user);
+ user = samu_new(talloc_tos());
+ if (user == NULL) {
+ DEBUG(0, ("samu_new failed\n"));
+ return false;
+ }
+
+ if (state->current == state->num_rids) {
+ TALLOC_FREE(user);
+ return false;
+ }
+
+ rid = state->rids[state->current++];
+
+ status = tdbsam_getsampwrid(state->methods, user, rid);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ /*
+ * Someone has deleted that user since we listed the RIDs
+ */
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("tdbsam_getsampwrid failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(user);
+ return false;
+ }
+
+ if ((state->acct_flags != 0) &&
+ ((state->acct_flags & pdb_get_acct_ctrl(user)) == 0)) {
+ goto again;
+ }
+
+ entry->acct_flags = pdb_get_acct_ctrl(user);
+ entry->rid = rid;
+ entry->account_name = talloc_strdup(search, pdb_get_username(user));
+ entry->fullname = talloc_strdup(search, pdb_get_fullname(user));
+ entry->description = talloc_strdup(search, pdb_get_acct_desc(user));
+
+ TALLOC_FREE(user);
+
+ if ((entry->account_name == NULL) || (entry->fullname == NULL)
+ || (entry->description == NULL)) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ return false;
+ }
+
+ return true;
+}
+
+static bool tdbsam_search_users(struct pdb_methods *methods,
+ struct pdb_search *search,
+ uint32_t acct_flags)
+{
+ struct tdbsam_search_state *state;
+
+ if (!tdbsam_open(tdbsam_filename)) {
+ DEBUG(0,("tdbsam_getsampwnam: failed to open %s!\n",
+ tdbsam_filename));
+ return false;
+ }
+
+ state = talloc_zero(search, struct tdbsam_search_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return false;
+ }
+ state->acct_flags = acct_flags;
+ state->methods = methods;
+
+ dbwrap_traverse_read(db_sam, tdbsam_collect_rids, state, NULL);
+
+ search->private_data = state;
+ search->next_entry = tdbsam_search_next_entry;
+ search->search_end = tdbsam_search_end;
+
+ return true;
+}
+
+static bool tdbsam_is_responsible_for_builtin(struct pdb_methods *m)
+{
+ return map_builtin;
+}
+
+/*********************************************************************
+ Initialize the tdb sam backend. Setup the dispatch table of methods,
+ open the tdb, etc...
+*********************************************************************/
+
+static NTSTATUS pdb_init_tdbsam(struct pdb_methods **pdb_method, const char *location)
+{
+ NTSTATUS nt_status;
+ char *tdbfile = NULL;
+ const char *pfile = location;
+
+ if (!NT_STATUS_IS_OK(nt_status = make_pdb_method( pdb_method ))) {
+ return nt_status;
+ }
+
+ (*pdb_method)->name = "tdbsam";
+
+ (*pdb_method)->getsampwnam = tdbsam_getsampwnam;
+ (*pdb_method)->getsampwsid = tdbsam_getsampwsid;
+ (*pdb_method)->add_sam_account = tdbsam_add_sam_account;
+ (*pdb_method)->update_sam_account = tdbsam_update_sam_account;
+ (*pdb_method)->delete_sam_account = tdbsam_delete_sam_account;
+ (*pdb_method)->rename_sam_account = tdbsam_rename_sam_account;
+ (*pdb_method)->search_users = tdbsam_search_users;
+
+ (*pdb_method)->capabilities = tdbsam_capabilities;
+ (*pdb_method)->new_rid = tdbsam_new_rid;
+
+ (*pdb_method)->is_responsible_for_builtin =
+ tdbsam_is_responsible_for_builtin;
+ map_builtin = lp_parm_bool(-1, "tdbsam", "map builtin", true);
+
+ /* save the path for later */
+
+ if (!location) {
+ if (asprintf(&tdbfile, "%s/%s", lp_private_dir(),
+ PASSDB_FILE_NAME) < 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pfile = tdbfile;
+ }
+
+ /* Do not leak memory if the init function is called more than once */
+ SAFE_FREE(tdbsam_filename);
+ tdbsam_filename = SMB_STRDUP(pfile);
+ if (!tdbsam_filename) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SAFE_FREE(tdbfile);
+
+ /* no private data */
+
+ (*pdb_method)->private_data = NULL;
+ (*pdb_method)->free_private_data = NULL;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS pdb_tdbsam_init(TALLOC_CTX *ctx)
+{
+ return smb_register_passdb(PASSDB_INTERFACE_VERSION, "tdbsam", pdb_init_tdbsam);
+}
diff --git a/source3/passdb/pdb_tdb.h b/source3/passdb/pdb_tdb.h
new file mode 100644
index 0000000..b90beb7
--- /dev/null
+++ b/source3/passdb/pdb_tdb.h
@@ -0,0 +1,32 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * SMB parameters and setup
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Simo Sorce 2000-2003
+ * Copyright (C) Gerald Carter 2000-2006
+ * Copyright (C) Jeremy Allison 2001-2009
+ * Copyright (C) Andrew Bartlett 2002
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* The following definitions come from passdb/pdb_tdb.c */
+
+#ifndef _PASSDB_PDB_TDB_H_
+#define _PASSDB_PDB_TDB_H_
+
+NTSTATUS pdb_tdbsam_init(TALLOC_CTX *);
+
+#endif /* _PASSDB_PDB_TDB_H_ */
diff --git a/source3/passdb/pdb_util.c b/source3/passdb/pdb_util.c
new file mode 100644
index 0000000..b732aca
--- /dev/null
+++ b/source3/passdb/pdb_util.c
@@ -0,0 +1,245 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Authentication utility functions
+ * Copyright (C) Andrew Tridgell 1992-1998
+ * Copyright (C) Andrew Bartlett 2001
+ * Copyright (C) Jeremy Allison 2000-2001
+ * Copyright (C) Rafal Szczesniak 2002
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "lib/winbind_util.h"
+#include "../librpc/gen_ndr/idmap.h"
+
+/**
+ * Add sid as a member of builtin_sid.
+ *
+ * @param[in] builtin_sid An existing builtin group.
+ * @param[in] dom_sid sid to add as a member of builtin_sid.
+ * @return Normal NTSTATUS return
+ */
+static NTSTATUS add_sid_to_builtin(const struct dom_sid *builtin_sid,
+ const struct dom_sid *dom_sid)
+{
+ NTSTATUS status;
+
+ if (!dom_sid || !builtin_sid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = pdb_add_aliasmem(builtin_sid, dom_sid);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MEMBER_IN_ALIAS)) {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(5, ("add_sid_to_builtin %s is already a member of %s\n",
+ dom_sid_str_buf(dom_sid, &buf1),
+ dom_sid_str_buf(builtin_sid, &buf2)));
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(4, ("add_sid_to_builtin %s could not be added to %s: "
+ "%s\n",
+ dom_sid_str_buf(dom_sid, &buf1),
+ dom_sid_str_buf(builtin_sid, &buf2),
+ nt_errstr(status)));
+ }
+ return status;
+}
+
+/**
+ * Create the requested BUILTIN if it doesn't already exist. This requires
+ * winbindd to be running.
+ *
+ * @param[in] rid BUILTIN rid to create
+ * @return Normal NTSTATUS return.
+ */
+NTSTATUS pdb_create_builtin(uint32_t rid)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct dom_sid sid;
+ gid_t gid;
+ bool mapresult;
+
+ if (!sid_compose(&sid, &global_sid_Builtin, rid)) {
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ if (!pdb_is_responsible_for_builtin()) {
+ /*
+ * if this backend is not responsible for BUILTIN
+ *
+ * Use the gid from the mapping request for entry.
+ * If the mapping fails, bail out
+ */
+ mapresult = sid_to_gid(&sid, &gid);
+ if (!mapresult) {
+ status = NT_STATUS_NO_SUCH_GROUP;
+ } else {
+ status = pdb_create_builtin_alias(rid, gid);
+ }
+ } else {
+ /*
+ * this backend is responsible for BUILTIN
+ *
+ * a failed mapping result means that the entry
+ * does not exist yet, so create it
+ *
+ * we use pdb_sid_to_id intentionally here to
+ * directly query the passdb backend (sid_to_gid
+ * would finally do the same)
+ */
+ struct unixid id;
+ mapresult = pdb_sid_to_id(&sid, &id);
+ if (!mapresult) {
+ if (!lp_winbind_nested_groups() || !winbind_ping()) {
+ return NT_STATUS_PROTOCOL_UNREACHABLE;
+ }
+ status = pdb_create_builtin_alias(rid, 0);
+ }
+ }
+ return status;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+NTSTATUS create_builtin_users(const struct dom_sid *dom_sid)
+{
+ NTSTATUS status;
+ struct dom_sid dom_users;
+
+ status = pdb_create_builtin(BUILTIN_RID_USERS);
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(5,("create_builtin_users: Failed to create Users\n"));
+ return status;
+ }
+
+ /* add domain users */
+ if ((IS_DC || (lp_server_role() == ROLE_DOMAIN_MEMBER)) &&
+ (dom_sid != NULL) &&
+ sid_compose(&dom_users, dom_sid, DOMAIN_RID_USERS))
+ {
+ status = add_sid_to_builtin(&global_sid_Builtin_Users,
+ &dom_users);
+ }
+
+ return status;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+NTSTATUS create_builtin_administrators(const struct dom_sid *dom_sid)
+{
+ NTSTATUS status;
+ struct dom_sid dom_admins, root_sid;
+ fstring root_name;
+ enum lsa_SidType type;
+ TALLOC_CTX *ctx;
+ bool ret;
+
+ status = pdb_create_builtin(BUILTIN_RID_ADMINISTRATORS);
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(5,("create_builtin_administrators: Failed to create Administrators\n"));
+ return status;
+ }
+
+ /* add domain admins */
+ if ((IS_DC || (lp_server_role() == ROLE_DOMAIN_MEMBER)) &&
+ (dom_sid != NULL) &&
+ sid_compose(&dom_admins, dom_sid, DOMAIN_RID_ADMINS))
+ {
+ status = add_sid_to_builtin(&global_sid_Builtin_Administrators,
+ &dom_admins);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* add root */
+ if ( (ctx = talloc_init("create_builtin_administrators")) == NULL ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ fstr_sprintf( root_name, "%s\\root", get_global_sam_name() );
+ ret = lookup_name(ctx, root_name, LOOKUP_NAME_DOMAIN, NULL, NULL,
+ &root_sid, &type);
+ TALLOC_FREE( ctx );
+
+ if ( ret ) {
+ status = add_sid_to_builtin(&global_sid_Builtin_Administrators,
+ &root_sid);
+ }
+
+ return status;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+NTSTATUS create_builtin_guests(const struct dom_sid *dom_sid)
+{
+ NTSTATUS status;
+ struct dom_sid tmp_sid = { 0, };
+
+ status = pdb_create_builtin(BUILTIN_RID_GUESTS);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("create_builtin_guests: Failed to create Guests\n"));
+ return status;
+ }
+
+ /* add local guest */
+ if (sid_compose(&tmp_sid, get_global_sam_sid(), DOMAIN_RID_GUEST)) {
+ status = add_sid_to_builtin(&global_sid_Builtin_Guests,
+ &tmp_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* add local guests */
+ if (sid_compose(&tmp_sid, get_global_sam_sid(), DOMAIN_RID_GUESTS)) {
+ status = add_sid_to_builtin(&global_sid_Builtin_Guests,
+ &tmp_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (lp_server_role() != ROLE_DOMAIN_MEMBER) {
+ return NT_STATUS_OK;
+ }
+
+ if (dom_sid == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* add domain guests */
+ if (sid_compose(&tmp_sid, dom_sid, DOMAIN_RID_GUESTS)) {
+ status = add_sid_to_builtin(&global_sid_Builtin_Guests,
+ &tmp_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/passdb/py_passdb.c b/source3/passdb/py_passdb.c
new file mode 100644
index 0000000..02d1d55
--- /dev/null
+++ b/source3/passdb/py_passdb.c
@@ -0,0 +1,4074 @@
+/*
+ Python interface to passdb
+
+ Copyright (C) Amitay Isaacs 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lib/replace/system/python.h"
+#include <pytalloc.h>
+#include "includes.h"
+#include "python/py3compat.h"
+#include "lib/util/talloc_stack.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/idmap.h"
+#include "passdb.h"
+#include "secrets.h"
+#include "idmap.h"
+#include "lib/util/string_wrappers.h"
+
+#ifndef Py_TYPE /* Py_TYPE is only available on Python > 2.6 */
+#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)
+#endif
+
+#ifndef PY_CHECK_TYPE
+#define PY_CHECK_TYPE(type, var, fail) \
+ if (!PyObject_TypeCheck(var, type)) {\
+ PyErr_Format(PyExc_TypeError, __location__ ": Expected type '%s' for '%s' of type '%s'", (type)->tp_name, #var, Py_TYPE(var)->tp_name); \
+ fail; \
+ }
+#endif
+
+
+static PyTypeObject *dom_sid_Type = NULL;
+static PyTypeObject *security_Type = NULL;
+static PyTypeObject *guid_Type = NULL;
+
+static PyTypeObject PySamu;
+static PyTypeObject PyGroupmap;
+static PyTypeObject PyPDB;
+
+static PyObject *py_pdb_error;
+
+void initpassdb(void);
+
+
+/************************** PIDL Autogeneratd ******************************/
+
+static PyObject *py_samu_get_logon_time(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_logon_time;
+
+ py_logon_time = PyLong_FromLong(pdb_get_logon_time(sam_acct));
+ talloc_free(frame);
+ return py_logon_time;
+}
+
+static int py_samu_set_logon_time(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_logon_time(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_logoff_time(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_logoff_time;
+
+ py_logoff_time = PyLong_FromLong(pdb_get_logoff_time(sam_acct));
+ talloc_free(frame);
+ return py_logoff_time;
+}
+
+static int py_samu_set_logoff_time(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_logoff_time(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_kickoff_time(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_kickoff_time;
+
+ py_kickoff_time = PyLong_FromLong(pdb_get_kickoff_time(sam_acct));
+ talloc_free(frame);
+ return py_kickoff_time;
+}
+
+static int py_samu_set_kickoff_time(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_kickoff_time(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_bad_password_time(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_bad_password_time;
+
+ py_bad_password_time = PyLong_FromLong(pdb_get_bad_password_time(sam_acct));
+ talloc_free(frame);
+ return py_bad_password_time;
+}
+
+static int py_samu_set_bad_password_time(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_bad_password_time(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_pass_last_set_time(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_pass_last_set_time;
+
+ py_pass_last_set_time = PyLong_FromLong(pdb_get_pass_last_set_time(sam_acct));
+ talloc_free(frame);
+ return py_pass_last_set_time;
+}
+
+static int py_samu_set_pass_last_set_time(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_pass_last_set_time(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_pass_can_change_time(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_pass_can_change_time;
+
+ py_pass_can_change_time = PyLong_FromLong(pdb_get_pass_can_change_time(sam_acct));
+ talloc_free(frame);
+ return py_pass_can_change_time;
+}
+
+static int py_samu_set_pass_can_change_time(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_pass_can_change_time(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_pass_must_change_time(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_pass_must_change_time;
+
+ py_pass_must_change_time = PyLong_FromLong(pdb_get_pass_must_change_time(sam_acct));
+ talloc_free(frame);
+ return py_pass_must_change_time;
+}
+
+static int py_samu_set_pass_must_change_time(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+
+ /* TODO: make this not a get/set or give a better exception */
+ talloc_free(frame);
+ return -1;
+}
+
+static PyObject *py_samu_get_username(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_username;
+ const char *username;
+
+ username = pdb_get_username(sam_acct);
+ if (username == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_username = PyUnicode_FromString(username);
+ talloc_free(frame);
+ return py_username;
+}
+
+static int py_samu_set_username(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_username(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_domain(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_domain;
+ const char *domain;
+
+ domain = pdb_get_domain(sam_acct);
+ if (domain == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_domain = PyUnicode_FromString(domain);
+ talloc_free(frame);
+ return py_domain;
+}
+
+static int py_samu_set_domain(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_domain(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_nt_username(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_nt_username;
+ const char *nt_username;
+
+ nt_username = pdb_get_nt_username(sam_acct);
+ if (nt_username == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_nt_username = PyUnicode_FromString(nt_username);
+ talloc_free(frame);
+ return py_nt_username;
+}
+
+static int py_samu_set_nt_username(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_nt_username(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_full_name(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_full_name;
+ const char *full_name;
+
+ full_name = pdb_get_fullname(sam_acct);
+ if (full_name == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_full_name = PyUnicode_FromString(full_name);
+ talloc_free(frame);
+ return py_full_name;
+}
+
+static int py_samu_set_full_name(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_fullname(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_home_dir(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_home_dir;
+ const char *home_dir;
+
+ home_dir = pdb_get_homedir(sam_acct);
+ if (home_dir == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_home_dir = PyUnicode_FromString(home_dir);
+ talloc_free(frame);
+ return py_home_dir;
+}
+
+static int py_samu_set_home_dir(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_homedir(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_dir_drive(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_dir_drive;
+ const char *dir_drive;
+
+ dir_drive = pdb_get_dir_drive(sam_acct);
+ if (dir_drive == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_dir_drive = PyUnicode_FromString(dir_drive);
+ talloc_free(frame);
+ return py_dir_drive;
+}
+
+static int py_samu_set_dir_drive(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_dir_drive(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_logon_script(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_logon_script;
+ const char *logon_script;
+
+ logon_script = pdb_get_logon_script(sam_acct);
+ if (logon_script == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_logon_script = PyUnicode_FromString(logon_script);
+ talloc_free(frame);
+ return py_logon_script;
+}
+
+static int py_samu_set_logon_script(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_logon_script(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_profile_path(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_profile_path;
+ const char *profile_path;
+
+ profile_path = pdb_get_profile_path(sam_acct);
+ if (profile_path == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_profile_path = PyUnicode_FromString(profile_path);
+ talloc_free(frame);
+ return py_profile_path;
+}
+
+static int py_samu_set_profile_path(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_profile_path(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_acct_desc(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_acct_desc;
+ const char *acct_desc;
+
+ acct_desc = pdb_get_acct_desc(sam_acct);
+ if (acct_desc == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_acct_desc = PyUnicode_FromString(acct_desc);
+ talloc_free(frame);
+ return py_acct_desc;
+}
+
+static int py_samu_set_acct_desc(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_acct_desc(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_workstations(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_workstations;
+ const char *workstations;
+
+ workstations = pdb_get_workstations(sam_acct);
+ if (workstations == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_workstations = PyUnicode_FromString(workstations);
+ talloc_free(frame);
+ return py_workstations;
+}
+
+static int py_samu_set_workstations(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_workstations(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_comment(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_comment;
+ const char *comment;
+
+ comment = pdb_get_comment(sam_acct);
+ if (comment == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_comment = PyUnicode_FromString(comment);
+ talloc_free(frame);
+ return py_comment;
+}
+
+static int py_samu_set_comment(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_comment(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_munged_dial(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_munged_dial;
+ const char *munged_dial;
+
+ munged_dial = pdb_get_munged_dial(sam_acct);
+ if (munged_dial == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_munged_dial = PyUnicode_FromString(munged_dial);
+ talloc_free(frame);
+ return py_munged_dial;
+}
+
+static int py_samu_set_munged_dial(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (!pdb_set_munged_dial(sam_acct, PyUnicode_AsUTF8(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_user_sid(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_user_sid;
+ const struct dom_sid *user_sid;
+ struct dom_sid *copy_user_sid;
+ TALLOC_CTX *mem_ctx;
+
+ user_sid = pdb_get_user_sid(sam_acct);
+ if(user_sid == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+ copy_user_sid = dom_sid_dup(mem_ctx, user_sid);
+ if (copy_user_sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(mem_ctx);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_user_sid = pytalloc_steal(dom_sid_Type, copy_user_sid);
+
+ talloc_free(mem_ctx);
+
+ talloc_free(frame);
+ return py_user_sid;
+}
+
+static int py_samu_set_user_sid(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(dom_sid_Type, value, return -1;);
+ if (!pdb_set_user_sid(sam_acct, (struct dom_sid *)pytalloc_get_ptr(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_group_sid(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ const struct dom_sid *group_sid;
+ struct dom_sid *copy_group_sid;
+
+ group_sid = pdb_get_group_sid(sam_acct);
+ if (group_sid == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ copy_group_sid = dom_sid_dup(NULL, group_sid);
+ if (copy_group_sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return pytalloc_steal(dom_sid_Type, copy_group_sid);
+}
+
+static int py_samu_set_group_sid(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(dom_sid_Type, value, return -1;);
+ if (!pdb_set_group_sid(sam_acct, (struct dom_sid *)pytalloc_get_ptr(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_lanman_passwd(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_lm_pw;
+ const char *lm_pw;
+
+ lm_pw = (const char *)pdb_get_lanman_passwd(sam_acct);
+ if (lm_pw == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_lm_pw = PyBytes_FromStringAndSize(lm_pw, LM_HASH_LEN);
+ talloc_free(frame);
+ return py_lm_pw;
+}
+
+static int py_samu_set_lanman_passwd(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyBytes_Type, value, return -1;);
+ if (!pdb_set_lanman_passwd(sam_acct, (uint8_t *)PyBytes_AsString(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_nt_passwd(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_nt_pw;
+ const char *nt_pw;
+
+ nt_pw = (const char *)pdb_get_nt_passwd(sam_acct);
+ if (nt_pw == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_nt_pw = PyBytes_FromStringAndSize(nt_pw, NT_HASH_LEN);
+ talloc_free(frame);
+ return py_nt_pw;
+}
+
+static int py_samu_set_nt_passwd(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ if (!pdb_set_nt_passwd(sam_acct, (uint8_t *)PyBytes_AsString(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_pw_history(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_nt_pw_his;
+ const char *nt_pw_his;
+ uint32_t hist_len;
+
+ nt_pw_his = (const char *)pdb_get_pw_history(sam_acct, &hist_len);
+ if (nt_pw_his == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_nt_pw_his = PyBytes_FromStringAndSize(nt_pw_his, hist_len*PW_HISTORY_ENTRY_LEN);
+ talloc_free(frame);
+ return py_nt_pw_his;
+}
+
+static int py_samu_set_pw_history(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ char *nt_pw_his;
+ Py_ssize_t len;
+ uint32_t hist_len;
+
+ PyBytes_AsStringAndSize(value, &nt_pw_his, &len);
+ hist_len = len / PW_HISTORY_ENTRY_LEN;
+ if (!pdb_set_pw_history(sam_acct, (uint8_t *)nt_pw_his, hist_len, PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_plaintext_passwd(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_plaintext_pw;
+ const char *plaintext_pw;
+
+ plaintext_pw = pdb_get_plaintext_passwd(sam_acct);
+ if (plaintext_pw == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ py_plaintext_pw = PyUnicode_FromString(plaintext_pw);
+
+ BURN_STR(discard_const_p(char, plaintext_pw));
+ talloc_free(frame);
+ return py_plaintext_pw;
+}
+
+static int py_samu_set_plaintext_passwd(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ if (!pdb_set_plaintext_passwd(sam_acct, PyUnicode_AsUTF8(value))) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_acct_ctrl(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_acct_ctrl;
+
+ py_acct_ctrl = PyLong_FromLong(pdb_get_acct_ctrl(sam_acct));
+ talloc_free(frame);
+ return py_acct_ctrl;
+}
+
+static int py_samu_set_acct_ctrl(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_acct_ctrl(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_logon_divs(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_logon_divs;
+
+ py_logon_divs = PyLong_FromLong(pdb_get_logon_divs(sam_acct));
+ talloc_free(frame);
+ return py_logon_divs;
+}
+
+static int py_samu_set_logon_divs(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_logon_divs(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_hours_len(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_hours_len;
+
+ py_hours_len = PyLong_FromLong(pdb_get_hours_len(sam_acct));
+ talloc_free(frame);
+ return py_hours_len;
+}
+
+static int py_samu_set_hours_len(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_hours_len(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_hours(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_hours;
+ const char *hours;
+ int hours_len, i;
+
+ hours = (const char *)pdb_get_hours(sam_acct);
+ if(! hours) {
+ Py_RETURN_NONE;
+ }
+
+ hours_len = pdb_get_hours_len(sam_acct);
+ if ((py_hours = PyList_New(hours_len)) == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ for (i=0; i<hours_len; i++) {
+ PyList_SetItem(py_hours, i, PyLong_FromLong(hours[i]));
+ }
+ talloc_free(frame);
+ return py_hours;
+}
+
+static int py_samu_set_hours(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ int i;
+ uint8_t *hours;
+ int hours_len;
+ bool status;
+
+ PY_CHECK_TYPE(&PyList_Type, value, return -1;);
+
+ hours_len = PyList_GET_SIZE(value);
+
+ hours = talloc_array(pytalloc_get_mem_ctx(obj), uint8_t, hours_len);
+ if (!hours) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return -1;
+ }
+
+ for (i=0; i < hours_len; i++) {
+ PY_CHECK_TYPE(&PyLong_Type, PyList_GET_ITEM(value,i), return -1;);
+ hours[i] = PyLong_AsLong(PyList_GET_ITEM(value, i));
+ }
+
+ status = pdb_set_hours(sam_acct, hours, hours_len, PDB_CHANGED);
+ talloc_free(hours);
+
+ if(! status) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_bad_password_count(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_bad_password_count;
+
+ py_bad_password_count = PyLong_FromLong(pdb_get_bad_password_count(sam_acct));
+ talloc_free(frame);
+ return py_bad_password_count;
+}
+
+static int py_samu_set_bad_password_count(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_bad_password_count(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_logon_count(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_logon_count;
+
+ py_logon_count = PyLong_FromLong(pdb_get_logon_count(sam_acct));
+ talloc_free(frame);
+ return py_logon_count;
+}
+
+static int py_samu_set_logon_count(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_logon_count(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_country_code(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_country_code;
+
+ py_country_code = PyLong_FromLong(pdb_get_country_code(sam_acct));
+ talloc_free(frame);
+ return py_country_code;
+}
+
+static int py_samu_set_country_code(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_country_code(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_samu_get_code_page(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+ PyObject *py_code_page;
+
+ py_code_page = PyLong_FromLong(pdb_get_code_page(sam_acct));
+ talloc_free(frame);
+ return py_code_page;
+}
+
+static int py_samu_set_code_page(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct = (struct samu *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ if (!pdb_set_code_page(sam_acct, PyLong_AsLong(value), PDB_CHANGED)) {
+ talloc_free(frame);
+ return -1;
+ }
+ talloc_free(frame);
+ return 0;
+}
+
+static PyGetSetDef py_samu_getsetters[] = {
+ {
+ .name = discard_const_p(char, "logon_time"),
+ .get = py_samu_get_logon_time,
+ .set = py_samu_set_logon_time,
+ },
+ {
+ .name = discard_const_p(char, "logoff_time"),
+ .get = py_samu_get_logoff_time,
+ .set = py_samu_set_logoff_time,
+ },
+ {
+ .name = discard_const_p(char, "kickoff_time"),
+ .get = py_samu_get_kickoff_time,
+ .set = py_samu_set_kickoff_time,
+ },
+ {
+ .name = discard_const_p(char, "bad_password_time"),
+ .get = py_samu_get_bad_password_time,
+ .set = py_samu_set_bad_password_time,
+ },
+ {
+ .name = discard_const_p(char, "pass_last_set_time"),
+ .get = py_samu_get_pass_last_set_time,
+ .set = py_samu_set_pass_last_set_time,
+ },
+ {
+ .name = discard_const_p(char, "pass_can_change_time"),
+ .get = py_samu_get_pass_can_change_time,
+ .set = py_samu_set_pass_can_change_time,
+ },
+ {
+ .name = discard_const_p(char, "pass_must_change_time"),
+ .get = py_samu_get_pass_must_change_time,
+ .set = py_samu_set_pass_must_change_time,
+ },
+ {
+ .name = discard_const_p(char, "username"),
+ .get = py_samu_get_username,
+ .set = py_samu_set_username,
+ },
+ {
+ .name = discard_const_p(char, "domain"),
+ .get = py_samu_get_domain,
+ .set = py_samu_set_domain,
+ },
+ {
+ .name = discard_const_p(char, "nt_username"),
+ .get = py_samu_get_nt_username,
+ .set = py_samu_set_nt_username,
+ },
+ {
+ .name = discard_const_p(char, "full_name"),
+ .get = py_samu_get_full_name,
+ .set = py_samu_set_full_name,
+ },
+ {
+ .name = discard_const_p(char, "home_dir"),
+ .get = py_samu_get_home_dir,
+ .set = py_samu_set_home_dir,
+ },
+ {
+ .name = discard_const_p(char, "dir_drive"),
+ .get = py_samu_get_dir_drive,
+ .set = py_samu_set_dir_drive,
+ },
+ {
+ .name = discard_const_p(char, "logon_script"),
+ .get = py_samu_get_logon_script,
+ .set = py_samu_set_logon_script,
+ },
+ {
+ .name = discard_const_p(char, "profile_path"),
+ .get = py_samu_get_profile_path,
+ .set = py_samu_set_profile_path,
+ },
+ {
+ .name = discard_const_p(char, "acct_desc"),
+ .get = py_samu_get_acct_desc,
+ .set = py_samu_set_acct_desc,
+ },
+ {
+ .name = discard_const_p(char, "workstations"),
+ .get = py_samu_get_workstations,
+ .set = py_samu_set_workstations,
+ },
+ {
+ .name = discard_const_p(char, "comment"),
+ .get = py_samu_get_comment,
+ .set = py_samu_set_comment,
+ },
+ {
+ .name = discard_const_p(char, "munged_dial"),
+ .get = py_samu_get_munged_dial,
+ .set = py_samu_set_munged_dial,
+ },
+ {
+ .name = discard_const_p(char, "user_sid"),
+ .get = py_samu_get_user_sid,
+ .set = py_samu_set_user_sid,
+ },
+ {
+ .name = discard_const_p(char, "group_sid"),
+ .get = py_samu_get_group_sid,
+ .set = py_samu_set_group_sid,
+ },
+ {
+ .name = discard_const_p(char, "lanman_passwd"),
+ .get = py_samu_get_lanman_passwd,
+ .set = py_samu_set_lanman_passwd,
+ },
+ {
+ .name = discard_const_p(char, "nt_passwd"),
+ .get = py_samu_get_nt_passwd,
+ .set = py_samu_set_nt_passwd,
+ },
+ {
+ .name = discard_const_p(char, "pw_history"),
+ .get = py_samu_get_pw_history,
+ .set = py_samu_set_pw_history,
+ },
+ {
+ .name = discard_const_p(char, "plaintext_passwd"),
+ .get = py_samu_get_plaintext_passwd,
+ .set = py_samu_set_plaintext_passwd,
+ },
+ {
+ .name = discard_const_p(char, "acct_ctrl"),
+ .get = py_samu_get_acct_ctrl,
+ .set = py_samu_set_acct_ctrl,
+ },
+ {
+ .name = discard_const_p(char, "logon_divs"),
+ .get = py_samu_get_logon_divs,
+ .set = py_samu_set_logon_divs,
+ },
+ {
+ .name = discard_const_p(char, "hours_len"),
+ .get = py_samu_get_hours_len,
+ .set = py_samu_set_hours_len,
+ },
+ {
+ .name = discard_const_p(char, "hours"),
+ .get = py_samu_get_hours,
+ .set = py_samu_set_hours,
+ },
+ {
+ .name = discard_const_p(char, "bad_password_count"),
+ .get = py_samu_get_bad_password_count,
+ .set = py_samu_set_bad_password_count,
+ },
+ {
+ .name = discard_const_p(char, "logon_count"),
+ .get = py_samu_get_logon_count,
+ .set = py_samu_set_logon_count,
+ },
+ {
+ .name = discard_const_p(char, "country_code"),
+ .get = py_samu_get_country_code,
+ .set = py_samu_set_country_code,
+ },
+ {
+ .name = discard_const_p(char, "code_page"),
+ .get = py_samu_get_code_page,
+ .set = py_samu_set_code_page,
+ },
+ {
+ .name = NULL,
+ }
+};
+
+
+/************************** PIDL Autogeneratd ******************************/
+
+static PyObject *py_samu_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct samu *sam_acct;
+
+ sam_acct = samu_new(NULL);
+ if (!sam_acct) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return pytalloc_steal(type, sam_acct);
+}
+
+static PyTypeObject PySamu = {
+ .tp_name = "passdb.Samu",
+ .tp_getset = py_samu_getsetters,
+ .tp_methods = NULL,
+ .tp_new = py_samu_new,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = "Samu() -> samu object\n",
+};
+
+
+static PyObject *py_groupmap_get_gid(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+ PyObject *py_gid;
+
+ py_gid = Py_BuildValue("i", group_map->gid);
+ talloc_free(frame);
+ return py_gid;
+}
+
+static int py_groupmap_set_gid(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ group_map->gid = PyLong_AsLong(value);
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_groupmap_get_sid(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+ PyObject *py_sid;
+ struct dom_sid *group_sid;
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ group_sid = dom_sid_dup(mem_ctx, &group_map->sid);
+ if (group_sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(mem_ctx);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_sid = pytalloc_steal(dom_sid_Type, group_sid);
+
+ talloc_free(mem_ctx);
+
+ talloc_free(frame);
+ return py_sid;
+}
+
+static int py_groupmap_set_sid(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(dom_sid_Type, value, return -1;);
+ group_map->sid = *pytalloc_get_type(value, struct dom_sid);
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_groupmap_get_sid_name_use(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+ PyObject *py_sid_name_use;
+
+ py_sid_name_use = PyLong_FromLong(group_map->sid_name_use);
+ talloc_free(frame);
+ return py_sid_name_use;
+}
+
+static int py_groupmap_set_sid_name_use(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyLong_Type, value, return -1;);
+ group_map->sid_name_use = PyLong_AsLong(value);
+ talloc_free(frame);
+ return 0;
+}
+
+static PyObject *py_groupmap_get_nt_name(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+ PyObject *py_nt_name;
+ if (group_map->nt_name == NULL) {
+ py_nt_name = Py_None;
+ Py_INCREF(py_nt_name);
+ } else {
+ py_nt_name = PyUnicode_FromString(group_map->nt_name);
+ }
+ talloc_free(frame);
+ return py_nt_name;
+}
+
+static int py_groupmap_set_nt_name(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (group_map->nt_name != NULL) {
+ TALLOC_FREE(group_map->nt_name);
+ }
+ if (value == Py_None) {
+ group_map->nt_name = talloc_strdup(group_map, "");
+ } else {
+ group_map->nt_name = talloc_strdup(group_map,
+ PyUnicode_AsUTF8(value));
+ }
+ TALLOC_FREE(frame);
+ if (group_map->nt_name == NULL) {
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *py_groupmap_get_comment(PyObject *obj, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+ PyObject *py_comment;
+ if (group_map->comment == NULL) {
+ py_comment = Py_None;
+ Py_INCREF(py_comment);
+ } else {
+ py_comment = PyUnicode_FromString(group_map->comment);
+ }
+ talloc_free(frame);
+ return py_comment;
+}
+
+static int py_groupmap_set_comment(PyObject *obj, PyObject *value, void *closure)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map = (GROUP_MAP *)pytalloc_get_ptr(obj);
+
+ PY_CHECK_TYPE(&PyUnicode_Type, value, return -1;);
+ if (group_map->comment != NULL) {
+ TALLOC_FREE(group_map->comment);
+ }
+ if (value == Py_None) {
+ group_map->comment = talloc_strdup(group_map, "");
+ } else {
+ group_map->comment = talloc_strdup(group_map,
+ PyUnicode_AsUTF8(value));
+ }
+ TALLOC_FREE(frame);
+ if (group_map->comment == NULL) {
+ return -1;
+ }
+ return 0;
+}
+
+static PyGetSetDef py_groupmap_getsetters[] = {
+ {
+ .name = discard_const_p(char, "gid"),
+ .get = py_groupmap_get_gid,
+ .set = py_groupmap_set_gid,
+ },
+ {
+ .name = discard_const_p(char, "sid"),
+ .get = py_groupmap_get_sid,
+ .set = py_groupmap_set_sid,
+ },
+ {
+ .name = discard_const_p(char, "sid_name_use"),
+ .get = py_groupmap_get_sid_name_use,
+ .set = py_groupmap_set_sid_name_use,
+ },
+ {
+ .name = discard_const_p(char, "nt_name"),
+ .get = py_groupmap_get_nt_name,
+ .set = py_groupmap_set_nt_name,
+ },
+ {
+ .name = discard_const_p(char, "comment"),
+ .get = py_groupmap_get_comment,
+ .set = py_groupmap_set_comment,
+ },
+ {
+ .name = NULL,
+ },
+};
+
+static PyObject *py_groupmap_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ GROUP_MAP *group_map;
+ TALLOC_CTX *mem_ctx;
+ PyObject *py_group_map;
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ group_map = talloc_zero(mem_ctx, GROUP_MAP);
+ if (group_map == NULL) {
+ PyErr_NoMemory();
+ talloc_free(mem_ctx);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_group_map = pytalloc_steal(type, group_map);
+ if (py_group_map == NULL) {
+ PyErr_NoMemory();
+ talloc_free(mem_ctx);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(mem_ctx);
+
+ talloc_free(frame);
+ return py_group_map;
+}
+
+
+static PyTypeObject PyGroupmap = {
+ .tp_name = "passdb.Groupmap",
+ .tp_getset = py_groupmap_getsetters,
+ .tp_methods = NULL,
+ .tp_new = py_groupmap_new,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = "Groupmap() -> group map object\n",
+};
+
+
+static PyObject *py_pdb_domain_info(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ struct pdb_domain_info *domain_info;
+ PyObject *py_domain_info;
+ struct dom_sid *sid;
+ struct GUID *guid;
+ PyObject *py_dom_sid = NULL;
+ PyObject *py_guid = NULL;
+
+ methods = pytalloc_get_ptr(self);
+
+ domain_info = methods->get_domain_info(methods, frame);
+ if (! domain_info) {
+ Py_RETURN_NONE;
+ }
+
+ sid = dom_sid_dup(frame, &domain_info->sid);
+ if (sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ guid = talloc(frame, struct GUID);
+ if (guid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+ *guid = domain_info->guid;
+
+ py_dom_sid = pytalloc_steal(dom_sid_Type, sid);
+ py_guid = pytalloc_steal(guid_Type, guid);
+
+ py_domain_info = Py_BuildValue(
+ "{s:s, s:s, s:s, s:O, s:O}",
+ "name", domain_info->name,
+ "dns_domain", domain_info->dns_domain,
+ "dns_forest", domain_info->dns_forest,
+ "dom_sid", py_dom_sid,
+ "guid", py_guid);
+
+
+ Py_CLEAR(py_dom_sid);
+ Py_CLEAR(py_guid);
+ talloc_free(frame);
+ return py_domain_info;
+}
+
+
+static PyObject *py_pdb_getsampwnam(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ const char *username;
+ struct pdb_methods *methods;
+ struct samu *sam_acct;
+ PyObject *py_sam_acct;
+
+ if (!PyArg_ParseTuple(args, "s:getsampwnam", &username)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ py_sam_acct = py_samu_new(&PySamu, NULL, NULL);
+ if (py_sam_acct == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+ sam_acct = (struct samu *)pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->getsampwnam(methods, sam_acct, username);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get user information for '%s', (%d,%s)",
+ username,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ Py_DECREF(py_sam_acct);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return py_sam_acct;
+}
+
+static PyObject *py_pdb_getsampwsid(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ struct samu *sam_acct;
+ PyObject *py_sam_acct;
+ PyObject *py_user_sid;
+
+ if (!PyArg_ParseTuple(args, "O:getsampwsid", &py_user_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ py_sam_acct = py_samu_new(&PySamu, NULL, NULL);
+ if (py_sam_acct == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+ sam_acct = (struct samu *)pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->getsampwsid(methods, sam_acct, pytalloc_get_ptr(py_user_sid));
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get user information from SID, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ Py_DECREF(py_sam_acct);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return py_sam_acct;
+}
+
+static PyObject *py_pdb_create_user(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *username;
+ unsigned int acct_flags;
+ unsigned int rid;
+
+ if (!PyArg_ParseTuple(args, "sI:create_user", &username, &acct_flags)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->create_user(methods, frame, username, acct_flags, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to create user (%s), (%d,%s)",
+ username,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return PyLong_FromLong(rid);
+}
+
+static PyObject *py_pdb_delete_user(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ struct samu *sam_acct;
+ PyObject *py_sam_acct;
+
+ if (!PyArg_ParseTuple(args, "O!:delete_user", &PySamu, &py_sam_acct)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sam_acct = pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->delete_user(methods, frame, sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete user, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_pdb_add_sam_account(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ struct samu *sam_acct;
+ PyObject *py_sam_acct;
+
+ if (!PyArg_ParseTuple(args, "O!:add_sam_account", &PySamu, &py_sam_acct)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sam_acct = pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->add_sam_account(methods, sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to add sam account '%s', (%d,%s)",
+ sam_acct->username,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_pdb_update_sam_account(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ struct samu *sam_acct;
+ PyObject *py_sam_acct;
+
+ if (!PyArg_ParseTuple(args, "O!:update_sam_account", &PySamu, &py_sam_acct)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sam_acct = pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->update_sam_account(methods, sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to update sam account, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_pdb_delete_sam_account(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ struct samu *sam_acct;
+ PyObject *py_sam_acct;
+
+ if (!PyArg_ParseTuple(args, "O!:delete_sam_account", &PySamu, &py_sam_acct)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sam_acct = pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->delete_sam_account(methods, sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete sam account, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_pdb_rename_sam_account(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ struct samu *sam_acct;
+ const char *new_username;
+ PyObject *py_sam_acct;
+
+ if (!PyArg_ParseTuple(args, "O!s:rename_sam_account", &PySamu, &py_sam_acct,
+ &new_username)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sam_acct = pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->rename_sam_account(methods, sam_acct, new_username);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to rename sam account, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_getgrsid(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ GROUP_MAP *group_map;
+ struct dom_sid *domain_sid;
+ PyObject *py_domain_sid, *py_group_map;
+
+ if (!PyArg_ParseTuple(args, "O!:getgrsid", dom_sid_Type, &py_domain_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ domain_sid = pytalloc_get_ptr(py_domain_sid);
+
+ py_group_map = py_groupmap_new(&PyGroupmap, NULL, NULL);
+ if (py_group_map == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ group_map = pytalloc_get_ptr(py_group_map);
+
+ status = methods->getgrsid(methods, group_map, *domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get group information by sid, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return py_group_map;
+}
+
+
+static PyObject *py_pdb_getgrgid(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ GROUP_MAP *group_map;
+ PyObject *py_group_map;
+ unsigned int gid_value;
+
+ if (!PyArg_ParseTuple(args, "I:getgrgid", &gid_value)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ py_group_map = py_groupmap_new(&PyGroupmap, NULL, NULL);
+ if (py_group_map == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ group_map = pytalloc_get_ptr(py_group_map);
+
+ status = methods->getgrgid(methods, group_map, gid_value);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get group information by gid, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return py_group_map;
+}
+
+
+static PyObject *py_pdb_getgrnam(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ GROUP_MAP *group_map;
+ PyObject *py_group_map;
+ const char *groupname;
+
+ if (!PyArg_ParseTuple(args, "s:getgrnam", &groupname)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ py_group_map = py_groupmap_new(&PyGroupmap, NULL, NULL);
+ if (py_group_map == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ group_map = pytalloc_get_ptr(py_group_map);
+
+ status = methods->getgrnam(methods, group_map, groupname);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get group information by name, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return py_group_map;
+}
+
+
+static PyObject *py_pdb_create_dom_group(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *groupname;
+ uint32_t group_rid;
+
+ if (!PyArg_ParseTuple(args, "s:create_dom_group", &groupname)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->create_dom_group(methods, frame, groupname, &group_rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to create domain group (%s), (%d,%s)",
+ groupname,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return PyLong_FromLong(group_rid);
+}
+
+
+static PyObject *py_pdb_delete_dom_group(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ unsigned int group_rid;
+
+ if (!PyArg_ParseTuple(args, "I:delete_dom_group", &group_rid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->delete_dom_group(methods, frame, group_rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete domain group (rid=%d), (%d,%s)",
+ group_rid,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_add_group_mapping_entry(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_group_map;
+ GROUP_MAP *group_map;
+
+ if (!PyArg_ParseTuple(args, "O!:add_group_mapping_entry", &PyGroupmap, &py_group_map)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ group_map = pytalloc_get_ptr(py_group_map);
+
+ status = methods->add_group_mapping_entry(methods, group_map);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to add group mapping entry, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_update_group_mapping_entry(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_group_map;
+ GROUP_MAP *group_map;
+
+ if (!PyArg_ParseTuple(args, "O!:update_group_mapping_entry", &PyGroupmap, &py_group_map)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ group_map = pytalloc_get_ptr(py_group_map);
+
+ status = methods->update_group_mapping_entry(methods, group_map);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to update group mapping entry, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_delete_group_mapping_entry(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_group_sid;
+ struct dom_sid *group_sid;
+
+ if (!PyArg_ParseTuple(args, "O!:delete_group_mapping_entry", dom_sid_Type, &py_group_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ group_sid = pytalloc_get_ptr(py_group_sid);
+
+ status = methods->delete_group_mapping_entry(methods, *group_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete group mapping entry, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_enum_group_mapping(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ enum lsa_SidType sid_name_use;
+ int lsa_sidtype_value = SID_NAME_UNKNOWN;
+ int unix_only = 0;
+ PyObject *py_domain_sid = Py_None;
+ struct dom_sid *domain_sid = NULL;
+ GROUP_MAP **gmap = NULL;
+ GROUP_MAP *group_map;
+ size_t i, num_entries;
+ PyObject *py_gmap_list, *py_group_map;
+
+ if (!PyArg_ParseTuple(args, "|O!ii:enum_group_mapping", dom_sid_Type, &py_domain_sid,
+ &lsa_sidtype_value, &unix_only)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sid_name_use = lsa_sidtype_value;
+
+ if (py_domain_sid != Py_None) {
+ domain_sid = pytalloc_get_ptr(py_domain_sid);
+ }
+
+ status = methods->enum_group_mapping(methods, domain_sid, sid_name_use,
+ &gmap, &num_entries, unix_only);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to enumerate group mappings, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_gmap_list = PyList_New(0);
+ if (py_gmap_list == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ for(i=0; i<num_entries; i++) {
+ py_group_map = py_groupmap_new(&PyGroupmap, NULL, NULL);
+ if (py_group_map) {
+ int res = 0;
+ group_map = pytalloc_get_ptr(py_group_map);
+ *group_map = *gmap[i];
+ talloc_steal(group_map, gmap[i]->nt_name);
+ talloc_steal(group_map, gmap[i]->comment);
+
+ res = PyList_Append(py_gmap_list, py_group_map);
+ Py_CLEAR(py_group_map);
+ if (res == -1) {
+ Py_CLEAR(py_gmap_list);
+ talloc_free(frame);
+ return NULL;
+ }
+ }
+ }
+
+ talloc_free(gmap);
+
+ talloc_free(frame);
+ return py_gmap_list;
+}
+
+
+static PyObject *py_pdb_enum_group_members(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_group_sid;
+ struct dom_sid *group_sid;
+ uint32_t *member_rids;
+ size_t i, num_members;
+ PyObject *py_sid_list;
+ struct dom_sid *domain_sid, *member_sid;
+
+ if (!PyArg_ParseTuple(args, "O!:enum_group_members", dom_sid_Type, &py_group_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ group_sid = pytalloc_get_ptr(py_group_sid);
+
+ status = methods->enum_group_members(methods, frame, group_sid,
+ &member_rids, &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to enumerate group members, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_sid_list = PyList_New(0);
+ if (py_sid_list == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ domain_sid = get_global_sam_sid();
+
+ for(i=0; i<num_members; i++) {
+ int res = 0;
+ PyObject *py_member_sid = NULL;
+ member_sid = dom_sid_add_rid(frame, domain_sid, member_rids[i]);
+ py_member_sid = pytalloc_steal(dom_sid_Type, member_sid);
+ res = PyList_Append(py_sid_list,
+ py_member_sid);
+ Py_CLEAR(py_member_sid);
+ if (res == -1) {
+ talloc_free(frame);
+ Py_CLEAR(py_sid_list);
+ return NULL;
+ }
+ }
+
+ talloc_free(frame);
+ return py_sid_list;
+}
+
+
+static PyObject *py_pdb_enum_group_memberships(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ uint32_t i;
+
+ struct samu *sam_acct;
+ PyObject *py_sam_acct;
+ PyObject *py_sid_list;
+ struct dom_sid *user_group_sids = NULL;
+ gid_t *user_group_ids = NULL;
+ uint32_t num_groups = 0;
+
+ if (!PyArg_ParseTuple(args, "O!:enum_group_memberships", &PySamu, &py_sam_acct)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sam_acct = pytalloc_get_ptr(py_sam_acct);
+
+ status = methods->enum_group_memberships(methods, frame, sam_acct,
+ &user_group_sids, &user_group_ids, &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to enumerate group memberships, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_sid_list = PyList_New(0);
+ if (py_sid_list == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ for(i=0; i<num_groups; i++) {
+ PyObject *py_sid =
+ pytalloc_steal(dom_sid_Type,
+ dom_sid_dup(NULL, &user_group_sids[i]));
+ PyList_Append(py_sid_list, py_sid);
+ Py_CLEAR(py_sid);
+ }
+
+ talloc_free(frame);
+ return py_sid_list;
+}
+
+
+static PyObject *py_pdb_add_groupmem(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ uint32_t group_rid, member_rid;
+
+ if (!PyArg_ParseTuple(args, "II:add_groupmem", &group_rid, &member_rid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->add_groupmem(methods, frame, group_rid, member_rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to add group member, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_del_groupmem(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ uint32_t group_rid, member_rid;
+
+ if (!PyArg_ParseTuple(args, "II:del_groupmem", &group_rid, &member_rid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->del_groupmem(methods, frame, group_rid, member_rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to rename sam account, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_create_alias(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *alias_name;
+ uint32_t rid;
+
+ if (!PyArg_ParseTuple(args, "s:create_alias", &alias_name)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->create_alias(methods, alias_name, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to create alias (%s), (%d,%s)",
+ alias_name,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return PyLong_FromLong(rid);
+}
+
+
+static PyObject *py_pdb_delete_alias(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_alias_sid;
+ struct dom_sid *alias_sid;
+
+ if (!PyArg_ParseTuple(args, "O!:delete_alias", dom_sid_Type, &py_alias_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ alias_sid = pytalloc_get_ptr(py_alias_sid);
+
+ status = methods->delete_alias(methods, alias_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete alias, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_get_aliasinfo(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_alias_sid;
+ struct dom_sid *alias_sid;
+ struct acct_info *alias_info;
+ PyObject *py_alias_info;
+
+ if (!PyArg_ParseTuple(args, "O!:get_aliasinfo", dom_sid_Type, &py_alias_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ alias_sid = pytalloc_get_ptr(py_alias_sid);
+
+ alias_info = talloc_zero(frame, struct acct_info);
+ if (!alias_info) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ status = methods->get_aliasinfo(methods, alias_sid, alias_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get alias information, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_alias_info = Py_BuildValue(
+ "{s:s, s:s, s:l}",
+ "acct_name", alias_info->acct_name,
+ "acct_desc", alias_info->acct_desc,
+ "rid", alias_info->rid);
+
+ talloc_free(frame);
+ return py_alias_info;
+}
+
+
+static PyObject *py_pdb_set_aliasinfo(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_alias_sid, *py_alias_info;
+ struct dom_sid *alias_sid;
+ struct acct_info alias_info;
+
+ if (!PyArg_ParseTuple(args, "O!O:set_alias_info", dom_sid_Type, &py_alias_sid,
+ &py_alias_info)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ alias_sid = pytalloc_get_ptr(py_alias_sid);
+
+ alias_info.acct_name = talloc_strdup(frame, PyUnicode_AsUTF8(PyDict_GetItemString(py_alias_info, "acct_name")));
+ if (alias_info.acct_name == NULL) {
+ PyErr_Format(py_pdb_error, "Unable to allocate memory");
+ talloc_free(frame);
+ return NULL;
+ }
+ alias_info.acct_desc = talloc_strdup(frame, PyUnicode_AsUTF8(PyDict_GetItemString(py_alias_info, "acct_desc")));
+ if (alias_info.acct_desc == NULL) {
+ PyErr_Format(py_pdb_error, "Unable to allocate memory");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ status = methods->set_aliasinfo(methods, alias_sid, &alias_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to set alias information, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_add_aliasmem(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_alias_sid, *py_member_sid;
+ struct dom_sid *alias_sid, *member_sid;
+
+ if (!PyArg_ParseTuple(args, "O!O!:add_aliasmem", dom_sid_Type, &py_alias_sid,
+ dom_sid_Type, &py_member_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ alias_sid = pytalloc_get_ptr(py_alias_sid);
+ member_sid = pytalloc_get_ptr(py_member_sid);
+
+ status = methods->add_aliasmem(methods, alias_sid, member_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to add member to alias, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_del_aliasmem(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_alias_sid, *py_member_sid;
+ const struct dom_sid *alias_sid, *member_sid;
+
+ if (!PyArg_ParseTuple(args, "O!O!:del_aliasmem", dom_sid_Type, &py_alias_sid,
+ dom_sid_Type, &py_member_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ alias_sid = pytalloc_get_ptr(py_alias_sid);
+ member_sid = pytalloc_get_ptr(py_member_sid);
+
+ status = methods->del_aliasmem(methods, alias_sid, member_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete member from alias, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_enum_aliasmem(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_alias_sid;
+ struct dom_sid *alias_sid, *member_sid, *tmp_sid;
+ PyObject *py_member_list, *py_member_sid;
+ size_t i, num_members;
+
+ if (!PyArg_ParseTuple(args, "O!:enum_aliasmem", dom_sid_Type, &py_alias_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ alias_sid = pytalloc_get_ptr(py_alias_sid);
+
+ status = methods->enum_aliasmem(methods, alias_sid, frame, &member_sid, &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to enumerate members for alias, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_member_list = PyList_New(0);
+ if (py_member_list == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ for(i=0; i<num_members; i++) {
+ int res = 0;
+ py_member_sid = pytalloc_new(struct dom_sid, dom_sid_Type);
+ if (py_member_sid == NULL) {
+ PyErr_NoMemory();
+ Py_CLEAR(py_member_list);
+ talloc_free(frame);
+ return NULL;
+ }
+ tmp_sid = pytalloc_get_ptr(py_member_sid);
+ *tmp_sid = member_sid[i];
+ res = PyList_Append(py_member_list, py_member_sid);
+ Py_CLEAR(py_member_sid);
+ if (res == -1) {
+ Py_CLEAR(py_member_list);
+ talloc_free(frame);
+ return NULL;
+ }
+ }
+
+ talloc_free(frame);
+ return py_member_list;
+}
+
+
+static PyObject *py_pdb_get_account_policy(PyObject *self, PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_acct_policy;
+ uint32_t value;
+ const char **names;
+ int count, i;
+ enum pdb_policy_type type;
+
+ methods = pytalloc_get_ptr(self);
+
+ py_acct_policy = PyDict_New();
+ if (py_acct_policy == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ account_policy_names_list(frame, &names, &count);
+ for (i=0; i<count; i++) {
+ type = account_policy_name_to_typenum(names[i]);
+ status = methods->get_account_policy(methods, type, &value);
+ if (NT_STATUS_IS_OK(status)) {
+ int res = 0;
+ PyObject *py_value = Py_BuildValue("i", value);
+ if (py_value == NULL) {
+ Py_CLEAR(py_acct_policy);
+ break;
+ }
+ res = PyDict_SetItemString(py_acct_policy,
+ names[i],
+ py_value);
+ Py_CLEAR(py_value);
+ if (res == -1) {
+ Py_CLEAR(py_acct_policy);
+ break;
+ }
+ }
+ }
+
+ talloc_free(frame);
+ return py_acct_policy;
+}
+
+
+static PyObject *py_pdb_set_account_policy(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_acct_policy, *py_value;
+ const char **names;
+ int count, i;
+ enum pdb_policy_type type;
+
+ if (!PyArg_ParseTuple(args, "O!:set_account_policy", PyDict_Type, &py_acct_policy)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ account_policy_names_list(frame, &names, &count);
+ for (i=0; i<count; i++) {
+ if ((py_value = PyDict_GetItemString(py_acct_policy, names[i])) != NULL) {
+ type = account_policy_name_to_typenum(names[i]);
+ status = methods->set_account_policy(methods, type, PyLong_AsLong(py_value));
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Error setting account policy (%s), (%d,%s)",
+ names[i],
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ }
+ }
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_pdb_search_users(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ unsigned int acct_flags;
+ struct pdb_search *search;
+ struct samr_displayentry *entry;
+ PyObject *py_userlist, *py_dict;
+
+ if (!PyArg_ParseTuple(args, "I:search_users", &acct_flags)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ search = talloc_zero(frame, struct pdb_search);
+ if (search == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ if (!methods->search_users(methods, search, acct_flags)) {
+ PyErr_Format(py_pdb_error, "Unable to search users");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ entry = talloc_zero(frame, struct samr_displayentry);
+ if (entry == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_userlist = PyList_New(0);
+ if (py_userlist == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ while (search->next_entry(search, entry)) {
+ int res = 1;
+ py_dict = Py_BuildValue(
+ "{s:l, s:l, s:l, s:s, s:s, s:s}",
+ "idx", entry->idx,
+ "rid", entry->rid,
+ "acct_flags", entry->acct_flags,
+ "account_name", entry->account_name,
+ "fullname", entry->fullname,
+ "description", entry->description);
+ if (py_dict == NULL) {
+ Py_CLEAR(py_userlist);
+ goto out;
+ }
+
+ res = PyList_Append(py_userlist, py_dict);
+ Py_CLEAR(py_dict);
+ if (res == -1) {
+ Py_CLEAR(py_userlist);
+ goto out;
+ }
+ }
+ search->search_end(search);
+
+out:
+ talloc_free(frame);
+ return py_userlist;
+}
+
+
+static PyObject *py_pdb_search_groups(PyObject *self, PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ struct pdb_search *search;
+ struct samr_displayentry *entry;
+ PyObject *py_grouplist, *py_dict;
+
+ methods = pytalloc_get_ptr(self);
+
+ search = talloc_zero(frame, struct pdb_search);
+ if (search == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ if (!methods->search_groups(methods, search)) {
+ PyErr_Format(py_pdb_error, "Unable to search groups");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ entry = talloc_zero(frame, struct samr_displayentry);
+ if (entry == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_grouplist = PyList_New(0);
+ if (py_grouplist == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ while (search->next_entry(search, entry)) {
+ int res = 0;
+ py_dict = Py_BuildValue(
+ "{s:l, s:l, s:l, s:s, s:s, s:s}",
+ "idx", entry->idx,
+ "rid", entry->rid,
+ "acct_flags", entry->acct_flags,
+ "account_name", entry->account_name,
+ "fullname", entry->fullname,
+ "description", entry->description);
+
+ if (py_dict == NULL) {
+ Py_CLEAR(py_grouplist);
+ goto out;
+ }
+
+ res = PyList_Append(py_grouplist, py_dict);
+ Py_CLEAR(py_dict);
+ if (res == -1) {
+ Py_CLEAR(py_grouplist);
+ goto out;
+ }
+ }
+ search->search_end(search);
+out:
+ talloc_free(frame);
+ return py_grouplist;
+}
+
+
+static PyObject *py_pdb_search_aliases(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ struct pdb_search *search;
+ struct samr_displayentry *entry;
+ PyObject *py_aliaslist, *py_dict;
+ PyObject *py_domain_sid = Py_None;
+ struct dom_sid *domain_sid = NULL;
+
+ if (!PyArg_ParseTuple(args, "|O!:search_aliases", dom_sid_Type, &py_domain_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ if (py_domain_sid != Py_None) {
+ domain_sid = pytalloc_get_ptr(py_domain_sid);
+ }
+
+ search = talloc_zero(frame, struct pdb_search);
+ if (search == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ if (!methods->search_aliases(methods, search, domain_sid)) {
+ PyErr_Format(py_pdb_error, "Unable to search aliases");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ entry = talloc_zero(frame, struct samr_displayentry);
+ if (entry == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_aliaslist = PyList_New(0);
+ if (py_aliaslist == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ while (search->next_entry(search, entry)) {
+ int res = 0;
+
+ py_dict = Py_BuildValue(
+ "{s:l, s:l, s:l, s:s, s:s, s:s}",
+ "idx", entry->idx,
+ "rid", entry->rid,
+ "acct_flags", entry->acct_flags,
+ "account_name", entry->account_name,
+ "fullname", entry->fullname,
+ "description", entry->description);
+
+ if (py_dict == NULL) {
+ Py_CLEAR(py_aliaslist);
+ goto out;
+ }
+ res = PyList_Append(py_aliaslist, py_dict);
+ Py_CLEAR(py_dict);
+ if (res == -1) {
+ Py_CLEAR(py_aliaslist);
+ goto out;
+ }
+ }
+ search->search_end(search);
+out:
+ talloc_free(frame);
+ return py_aliaslist;
+}
+
+
+static PyObject *py_pdb_uid_to_sid(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ struct unixid id;
+ unsigned int uid;
+ struct dom_sid user_sid, *copy_user_sid;
+ PyObject *py_user_sid;
+
+ if (!PyArg_ParseTuple(args, "I:uid_to_sid", &uid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ id.id = uid;
+ id.type = ID_TYPE_UID;
+
+ if (!methods->id_to_sid(methods, &id, &user_sid)) {
+ PyErr_Format(py_pdb_error, "Unable to get sid for uid=%d", uid);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ copy_user_sid = dom_sid_dup(frame, &user_sid);
+ if (copy_user_sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_user_sid = pytalloc_steal(dom_sid_Type, copy_user_sid);
+
+ talloc_free(frame);
+ return py_user_sid;
+}
+
+
+static PyObject *py_pdb_gid_to_sid(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ struct unixid id;
+ unsigned int gid;
+ struct dom_sid group_sid, *copy_group_sid;
+ PyObject *py_group_sid;
+
+ if (!PyArg_ParseTuple(args, "I:gid_to_sid", &gid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ id.id = gid;
+ id.type = ID_TYPE_GID;
+
+ methods = pytalloc_get_ptr(self);
+
+ if (!methods->id_to_sid(methods, &id, &group_sid)) {
+ PyErr_Format(py_pdb_error, "Unable to get sid for gid=%d", gid);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ copy_group_sid = dom_sid_dup(frame, &group_sid);
+ if (copy_group_sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_group_sid = pytalloc_steal(dom_sid_Type, copy_group_sid);
+
+ talloc_free(frame);
+ return py_group_sid;
+}
+
+
+static PyObject *py_pdb_sid_to_id(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ PyObject *py_sid;
+ struct dom_sid *sid;
+ struct unixid id;
+
+ if (!PyArg_ParseTuple(args, "O!:sid_to_id", dom_sid_Type, &py_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ sid = pytalloc_get_ptr(py_sid);
+
+ if (!methods->sid_to_id(methods, sid, &id)) {
+ PyErr_Format(py_pdb_error, "Unable to get id for sid");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return Py_BuildValue("(II)", id.id, id.type);
+}
+
+
+static PyObject *py_pdb_new_rid(PyObject *self, PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ uint32_t rid;
+
+ methods = pytalloc_get_ptr(self);
+
+ if (!methods->new_rid(methods, &rid)) {
+ PyErr_Format(py_pdb_error, "Unable to get new rid");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return PyLong_FromLong(rid);
+}
+
+
+static PyObject *py_pdb_get_trusteddom_pw(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ const char *domain;
+ char *pwd;
+ struct dom_sid sid, *copy_sid;
+ PyObject *py_sid;
+ time_t last_set_time;
+ PyObject *py_value;
+
+ if (!PyArg_ParseTuple(args, "s:get_trusteddom_pw", &domain)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ if (!methods->get_trusteddom_pw(methods, domain, &pwd, &sid, &last_set_time)) {
+ PyErr_Format(py_pdb_error, "Unable to get trusted domain password");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ copy_sid = dom_sid_dup(frame, &sid);
+ if (copy_sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_sid = pytalloc_steal(dom_sid_Type, copy_sid);
+ if (py_sid == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_value = Py_BuildValue(
+ "{s:s, s:O, s:l}",
+ "pwd", pwd,
+ "sid", py_sid,
+ "last_set_tim", last_set_time);
+
+ Py_CLEAR(py_sid);
+ talloc_free(frame);
+ return py_value;
+}
+
+
+static PyObject *py_pdb_set_trusteddom_pw(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ const char *domain;
+ const char *pwd;
+ const struct dom_sid *domain_sid;
+ PyObject *py_domain_sid;
+
+ if (!PyArg_ParseTuple(args, "ssO!:set_trusteddom_pw", &domain, &pwd,
+ dom_sid_Type, &py_domain_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ domain_sid = pytalloc_get_ptr(py_domain_sid);
+
+ if (!methods->set_trusteddom_pw(methods, domain, pwd, domain_sid)) {
+ PyErr_Format(py_pdb_error, "Unable to set trusted domain password");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_del_trusteddom_pw(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pdb_methods *methods;
+ const char *domain;
+
+ if (!PyArg_ParseTuple(args, "s:del_trusteddom_pw", &domain)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ if (!methods->del_trusteddom_pw(methods, domain)) {
+ PyErr_Format(py_pdb_error, "Unable to delete trusted domain password");
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_enum_trusteddoms(PyObject *self, PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ uint32_t i, num_domains;
+ struct trustdom_info **domains;
+ PyObject *py_domain_list, *py_dict;
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->enum_trusteddoms(methods, frame, &num_domains, &domains);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to enumerate trusted domains, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_domain_list = PyList_New(0);
+ if (py_domain_list == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ for(i=0; i<num_domains; i++) {
+ int res = 0;
+ PyObject *py_sid =
+ pytalloc_steal(dom_sid_Type, &domains[i]->sid);
+ py_dict = Py_BuildValue(
+ "{s:s, s:O}",
+ "name", domains[i]->name,
+ "sid", py_sid);
+ Py_CLEAR(py_sid);
+ if (py_dict == NULL) {
+ DBG_ERR("Failed to insert entry to dict\n");
+ Py_CLEAR(py_domain_list);
+ break;
+ }
+
+ res = PyList_Append(py_domain_list, py_dict);
+ Py_CLEAR(py_dict);
+ if (res == -1) {
+ Py_CLEAR(py_domain_list);
+ break;
+ }
+ }
+
+ talloc_free(frame);
+ return py_domain_list;
+}
+
+
+static PyObject *py_pdb_get_trusted_domain(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *domain;
+ struct pdb_trusted_domain *td;
+ PyObject *py_domain_info;
+ PyObject *py_sid = NULL;
+
+ if (!PyArg_ParseTuple(args, "s:get_trusted_domain", &domain)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->get_trusted_domain(methods, frame, domain, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get trusted domain information, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_sid = pytalloc_steal(dom_sid_Type, &td->security_identifier);
+
+ py_domain_info = Py_BuildValue(
+ "{s:s, s:s, s:O,"
+ " s:"PYARG_BYTES_LEN","
+ " s:"PYARG_BYTES_LEN","
+ " s:l, s:l, s:l,"
+ " s:"PYARG_BYTES_LEN"}",
+ "domain_name", td->domain_name,
+ "netbios_name", td->netbios_name,
+ "security_identifier", py_sid,
+ "trust_auth_incoming",
+ (const char *)td->trust_auth_incoming.data,
+ td->trust_auth_incoming.length,
+ "trust_auth_outgoing",
+ (const char *)td->trust_auth_outgoing.data,
+ td->trust_auth_outgoing.length,
+ "trust_direction", td->trust_direction,
+ "trust_type", td->trust_type,
+ "trust_attributes", td->trust_attributes,
+ "trust_forest_trust_info",
+ (const char *)td->trust_forest_trust_info.data,
+ td->trust_forest_trust_info.length);
+ Py_CLEAR(py_sid);
+
+ talloc_free(frame);
+ return py_domain_info;
+}
+
+
+static PyObject *py_pdb_get_trusted_domain_by_sid(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ PyObject *py_domain_sid;
+ struct dom_sid *domain_sid;
+ struct pdb_trusted_domain *td;
+ PyObject *py_domain_info;
+ PyObject *py_sid = NULL;
+
+ if (!PyArg_ParseTuple(args, "O!:get_trusted_domain_by_sid", dom_sid_Type, &py_domain_sid)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ domain_sid = pytalloc_get_ptr(py_domain_sid);
+
+ status = methods->get_trusted_domain_by_sid(methods, frame, domain_sid, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get trusted domain information, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_sid = pytalloc_steal(dom_sid_Type, &td->security_identifier);
+
+ py_domain_info = Py_BuildValue(
+ "{s:s, s:s, s:O,"
+ " s:"PYARG_BYTES_LEN","
+ " s:"PYARG_BYTES_LEN","
+ " s:l, s:l, s:l,"
+ " s:"PYARG_BYTES_LEN"}",
+ "domain_name", td->domain_name,
+ "netbios_name", td->netbios_name,
+ "security_identifier", py_sid,
+ "trust_auth_incoming",
+ (const char *)td->trust_auth_incoming.data,
+ td->trust_auth_incoming.length,
+ "trust_auth_outgoing",
+ (const char *)td->trust_auth_outgoing.data,
+ td->trust_auth_outgoing.length,
+ "trust_direction", td->trust_direction,
+ "trust_type", td->trust_type,
+ "trust_attributes", td->trust_attributes,
+ "trust_forest_trust_info",
+ (const char *)td->trust_forest_trust_info.data,
+ td->trust_forest_trust_info.length);
+ Py_CLEAR(py_sid);
+
+ talloc_free(frame);
+ return py_domain_info;
+}
+
+
+static PyObject *py_pdb_set_trusted_domain(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *domain;
+ PyObject *py_td_info;
+ struct pdb_trusted_domain td_info;
+ PyObject *py_tmp;
+ Py_ssize_t len;
+
+ if (!PyArg_ParseTuple(args, "sO!:set_trusted_domain", &domain, &PyDict_Type, &py_td_info)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_tmp = PyDict_GetItemString(py_td_info, "domain_name");
+ td_info.domain_name = discard_const_p(char, PyUnicode_AsUTF8(py_tmp));
+
+ py_tmp = PyDict_GetItemString(py_td_info, "netbios_name");
+ td_info.netbios_name = discard_const_p(char, PyUnicode_AsUTF8(py_tmp));
+
+ py_tmp = PyDict_GetItemString(py_td_info, "security_identifier");
+ td_info.security_identifier = *pytalloc_get_type(py_tmp, struct dom_sid);
+
+ py_tmp = PyDict_GetItemString(py_td_info, "trust_auth_incoming");
+ PyBytes_AsStringAndSize(py_tmp, (char **)&td_info.trust_auth_incoming.data, &len);
+ td_info.trust_auth_incoming.length = len;
+
+ py_tmp = PyDict_GetItemString(py_td_info, "trust_auth_outgoing");
+ PyBytes_AsStringAndSize(py_tmp, (char **)&td_info.trust_auth_outgoing.data, &len);
+ td_info.trust_auth_outgoing.length = len;
+
+ py_tmp = PyDict_GetItemString(py_td_info, "trust_direction");
+ td_info.trust_direction = PyLong_AsLong(py_tmp);
+
+ py_tmp = PyDict_GetItemString(py_td_info, "trust_type");
+ td_info.trust_type = PyLong_AsLong(py_tmp);
+
+ py_tmp = PyDict_GetItemString(py_td_info, "trust_attributes");
+ td_info.trust_attributes = PyLong_AsLong(py_tmp);
+
+ py_tmp = PyDict_GetItemString(py_td_info, "trust_forest_trust_info");
+ PyBytes_AsStringAndSize(py_tmp, (char **)&td_info.trust_forest_trust_info.data, &len);
+ td_info.trust_forest_trust_info.length = len;
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->set_trusted_domain(methods, domain, &td_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to set trusted domain information, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_del_trusted_domain(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *domain;
+
+ if (!PyArg_ParseTuple(args, "s:del_trusted_domain", &domain)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->del_trusted_domain(methods, domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete trusted domain, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_enum_trusted_domains(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ uint32_t i, num_domains;
+ struct pdb_trusted_domain **td_info;
+ PyObject *py_td_info, *py_domain_info;
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->enum_trusted_domains(methods, frame, &num_domains, &td_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete trusted domain, (%d,%s)",
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_td_info = PyList_New(0);
+ if (py_td_info == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ for (i=0; i<num_domains; i++) {
+ int res = 0;
+ struct pdb_trusted_domain *td = td_info[i];
+ PyObject *py_sid =
+ pytalloc_steal(dom_sid_Type, &td->security_identifier);
+
+ py_domain_info = Py_BuildValue(
+ "{s:s, s:s, s:O,"
+ " s:"PYARG_BYTES_LEN","
+ " s:"PYARG_BYTES_LEN","
+ " s:l, s:l, s:l,"
+ " s:"PYARG_BYTES_LEN"}",
+ "domain_name", td->domain_name,
+ "netbios_name", td->netbios_name,
+ "security_identifier", py_sid,
+ "trust_auth_incoming",
+ (const char *)td->trust_auth_incoming.data,
+ td->trust_auth_incoming.length,
+ "trust_auth_outgoing",
+ (const char *)td->trust_auth_outgoing.data,
+ td->trust_auth_outgoing.length,
+ "trust_direction", td->trust_direction,
+ "trust_type", td->trust_type,
+ "trust_attributes", td->trust_attributes,
+ "trust_forest_trust_info",
+ (const char *)td->trust_forest_trust_info.data,
+ td->trust_forest_trust_info.length);
+ Py_CLEAR(py_sid);
+
+ if (py_domain_info == NULL) {
+ Py_CLEAR(py_td_info);
+ break;
+ }
+ res = PyList_Append(py_td_info, py_domain_info);
+ Py_CLEAR(py_domain_info);
+ if (res == -1) {
+ Py_CLEAR(py_td_info);
+ break;
+ }
+ }
+
+ talloc_free(frame);
+ return py_td_info;
+}
+
+
+static PyObject *py_pdb_get_secret(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *secret_name;
+ DATA_BLOB secret_current, secret_old;
+ NTTIME secret_current_lastchange, secret_old_lastchange;
+ PyObject *py_sd;
+ struct security_descriptor *sd;
+ PyObject *py_secret;
+
+ if (!PyArg_ParseTuple(args, "s:get_secret_name", &secret_name)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ py_sd = pytalloc_new(struct security_descriptor, security_Type);
+ if (py_sd == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+ sd = pytalloc_get_ptr(py_sd);
+
+ status = methods->get_secret(methods, frame, secret_name,
+ &secret_current,
+ &secret_current_lastchange,
+ &secret_old,
+ &secret_old_lastchange,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to get information for secret (%s), (%d,%s)",
+ secret_name,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_secret = Py_BuildValue(
+ "{s:"PYARG_BYTES_LEN","
+ " s:K"
+ " s:"PYARG_BYTES_LEN","
+ " s:K, s:O}",
+ "secret_current", (const char*)secret_current.data,
+ secret_current.length,
+ "secret_current_lastchange", secret_current_lastchange,
+ "secret_old", (const char*)secret_old.data,
+ secret_old.length,
+ "secret_old_lastchange", secret_old_lastchange,
+ "sd", py_sd);
+
+ Py_CLEAR(py_sd);
+ if (py_secret == NULL) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return py_secret;
+}
+
+
+static PyObject *py_pdb_set_secret(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *secret_name;
+ PyObject *py_secret;
+ PyObject *py_secret_cur, *py_secret_old, *py_sd;
+ DATA_BLOB secret_current, secret_old;
+ struct security_descriptor *sd;
+ Py_ssize_t len;
+
+ if (!PyArg_ParseTuple(args, "sO!:set_secret_name", &secret_name, PyDict_Type, &py_secret)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_secret_cur = PyDict_GetItemString(py_secret, "secret_current");
+ py_secret_old = PyDict_GetItemString(py_secret, "secret_old");
+ py_sd = PyDict_GetItemString(py_secret, "sd");
+
+ PY_CHECK_TYPE(&PyBytes_Type, py_secret_cur, return NULL;);
+ PY_CHECK_TYPE(&PyBytes_Type, py_secret_old, return NULL;);
+ PY_CHECK_TYPE(security_Type, py_sd, return NULL;);
+
+ methods = pytalloc_get_ptr(self);
+
+ PyBytes_AsStringAndSize(py_secret_cur, (char **)&secret_current.data, &len);
+ secret_current.length = len;
+ PyBytes_AsStringAndSize(py_secret_old, (char **)&secret_old.data, &len);
+ secret_current.length = len;
+ sd = pytalloc_get_ptr(py_sd);
+
+ status = methods->set_secret(methods, secret_name, &secret_current, &secret_old, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to set information for secret (%s), (%d,%s)",
+ secret_name,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_pdb_delete_secret(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct pdb_methods *methods;
+ const char *secret_name;
+
+ if (!PyArg_ParseTuple(args, "s:delete_secret", &secret_name)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ methods = pytalloc_get_ptr(self);
+
+ status = methods->delete_secret(methods, secret_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Unable to delete secret (%s), (%d,%s)",
+ secret_name,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef py_pdb_methods[] = {
+ { "domain_info", py_pdb_domain_info, METH_NOARGS,
+ "domain_info() -> str\n\n \
+ Get domain information for the database." },
+ { "getsampwnam", py_pdb_getsampwnam, METH_VARARGS,
+ "getsampwnam(username) -> samu object\n\n \
+ Get user information by name." },
+ { "getsampwsid", py_pdb_getsampwsid, METH_VARARGS,
+ "getsampwsid(user_sid) -> samu object\n\n \
+ Get user information by sid (dcerpc.security.dom_sid object)." },
+ { "create_user", py_pdb_create_user, METH_VARARGS,
+ "create_user(username, acct_flags) -> rid\n\n \
+ Create user. acct_flags are samr account control flags." },
+ { "delete_user", py_pdb_delete_user, METH_VARARGS,
+ "delete_user(samu object) -> None\n\n \
+ Delete user." },
+ { "add_sam_account", py_pdb_add_sam_account, METH_VARARGS,
+ "add_sam_account(samu object) -> None\n\n \
+ Add SAM account." },
+ { "update_sam_account", py_pdb_update_sam_account, METH_VARARGS,
+ "update_sam_account(samu object) -> None\n\n \
+ Update SAM account." },
+ { "delete_sam_account", py_pdb_delete_sam_account, METH_VARARGS,
+ "delete_sam_account(samu object) -> None\n\n \
+ Delete SAM account." },
+ { "rename_sam_account", py_pdb_rename_sam_account, METH_VARARGS,
+ "rename_sam_account(samu object1, new_username) -> None\n\n \
+ Rename SAM account." },
+ /* update_login_attempts */
+ { "getgrsid", py_pdb_getgrsid, METH_VARARGS,
+ "getgrsid(group_sid) -> groupmap object\n\n \
+ Get group information by sid (dcerpc.security.dom_sid object)." },
+ { "getgrgid", py_pdb_getgrgid, METH_VARARGS,
+ "getgrsid(gid) -> groupmap object\n\n \
+ Get group information by gid." },
+ { "getgrnam", py_pdb_getgrnam, METH_VARARGS,
+ "getgrsid(groupname) -> groupmap object\n\n \
+ Get group information by name." },
+ { "create_dom_group", py_pdb_create_dom_group, METH_VARARGS,
+ "create_dom_group(groupname) -> group_rid\n\n \
+ Create new domain group by name." },
+ { "delete_dom_group", py_pdb_delete_dom_group, METH_VARARGS,
+ "delete_dom_group(group_rid) -> None\n\n \
+ Delete domain group identified by rid" },
+ { "add_group_mapping_entry", py_pdb_add_group_mapping_entry, METH_VARARGS,
+ "add_group_mapping_entry(groupmap) -> None\n \
+ Add group mapping entry for groupmap object." },
+ { "update_group_mapping_entry", py_pdb_update_group_mapping_entry, METH_VARARGS,
+ "update_group_mapping_entry(groupmap) -> None\n\n \
+ Update group mapping entry for groupmap object." },
+ { "delete_group_mapping_entry", py_pdb_delete_group_mapping_entry, METH_VARARGS,
+ "delete_group_mapping_entry(groupmap) -> None\n\n \
+ Delete group mapping entry for groupmap object." },
+ { "enum_group_mapping", py_pdb_enum_group_mapping, METH_VARARGS,
+ "enum_group_mapping([domain_sid, [type, [unix_only]]]) -> List\n\n \
+ Return list of group mappings as groupmap objects. Optional arguments are domain_sid object, type of group, unix only flag." },
+ { "enum_group_members", py_pdb_enum_group_members, METH_VARARGS,
+ "enum_group_members(group_sid) -> List\n\n \
+ Return list of users (dom_sid object) in group." },
+ { "enum_group_memberships", py_pdb_enum_group_memberships, METH_VARARGS,
+ "enum_group_memberships(samu object) -> List\n\n \
+ Return list of groups (dom_sid object) this user is part of." },
+ /* set_unix_primary_group */
+ { "add_groupmem", py_pdb_add_groupmem, METH_VARARGS,
+ "add_groupmem(group_rid, member_rid) -> None\n\n \
+ Add user to group." },
+ { "del_groupmem", py_pdb_del_groupmem, METH_VARARGS,
+ "del_groupmem(group_rid, member_rid) -> None\n\n \
+ Remove user from group." },
+ { "create_alias", py_pdb_create_alias, METH_VARARGS,
+ "create_alias(alias_name) -> alias_rid\n\n \
+ Create alias entry." },
+ { "delete_alias", py_pdb_delete_alias, METH_VARARGS,
+ "delete_alias(alias_sid) -> None\n\n \
+ Delete alias entry." },
+ { "get_aliasinfo", py_pdb_get_aliasinfo, METH_VARARGS,
+ "get_aliasinfo(alias_sid) -> Mapping\n\n \
+ Get alias information as a dictionary with keys - acct_name, acct_desc, rid." },
+ { "set_aliasinfo", py_pdb_set_aliasinfo, METH_VARARGS,
+ "set_alias_info(alias_sid, Mapping) -> None\n\n \
+ Set alias information from a dictionary with keys - acct_name, acct_desc." },
+ { "add_aliasmem", py_pdb_add_aliasmem, METH_VARARGS,
+ "add_aliasmem(alias_sid, member_sid) -> None\n\n \
+ Add user to alias entry." },
+ { "del_aliasmem", py_pdb_del_aliasmem, METH_VARARGS,
+ "del_aliasmem(alias_sid, member_sid) -> None\n\n \
+ Remove a user from alias entry." },
+ { "enum_aliasmem", py_pdb_enum_aliasmem, METH_VARARGS,
+ "enum_aliasmem(alias_sid) -> List\n\n \
+ Return a list of members (dom_sid object) for alias entry." },
+ /* enum_alias_memberships */
+ /* lookup_rids */
+ /* lookup_names */
+ { "get_account_policy", py_pdb_get_account_policy, METH_NOARGS,
+ "get_account_policy() -> Mapping\n\n \
+ Get account policy information as a dictionary." },
+ { "set_account_policy", py_pdb_set_account_policy, METH_VARARGS,
+ "get_account_policy(Mapping) -> None\n\n \
+ Set account policy settings from a dictionary." },
+ /* get_seq_num */
+ { "search_users", py_pdb_search_users, METH_VARARGS,
+ "search_users(acct_flags) -> List\n\n \
+ Search users. acct_flags are samr account control flags.\n \
+ Each list entry is dictionary with keys - idx, rid, acct_flags, account_name, fullname, description." },
+ { "search_groups", py_pdb_search_groups, METH_NOARGS,
+ "search_groups() -> List\n\n \
+ Search unix only groups. \n \
+ Each list entry is dictionary with keys - idx, rid, acct_flags, account_name, fullname, description." },
+ { "search_aliases", py_pdb_search_aliases, METH_VARARGS,
+ "search_aliases([domain_sid]) -> List\n\n \
+ Search aliases. domain_sid is dcerpc.security.dom_sid object.\n \
+ Each list entry is dictionary with keys - idx, rid, acct_flags, account_name, fullname, description." },
+ { "uid_to_sid", py_pdb_uid_to_sid, METH_VARARGS,
+ "uid_to_sid(uid) -> sid\n\n \
+ Return sid for given user id." },
+ { "gid_to_sid", py_pdb_gid_to_sid, METH_VARARGS,
+ "gid_to_sid(gid) -> sid\n\n \
+ Return sid for given group id." },
+ { "sid_to_id", py_pdb_sid_to_id, METH_VARARGS,
+ "sid_to_id(sid) -> Tuple\n\n \
+ Return id and type for given sid." },
+ /* capabilities */
+ { "new_rid", py_pdb_new_rid, METH_NOARGS,
+ "new_rid() -> rid\n\n \
+ Get a new rid." },
+ { "get_trusteddom_pw", py_pdb_get_trusteddom_pw, METH_VARARGS,
+ "get_trusteddom_pw(domain) -> Mapping\n\n \
+ Get trusted domain password, sid and last set time in a dictionary." },
+ { "set_trusteddom_pw", py_pdb_set_trusteddom_pw, METH_VARARGS,
+ "set_trusteddom_pw(domain, pwd, sid) -> None\n\n \
+ Set trusted domain password." },
+ { "del_trusteddom_pw", py_pdb_del_trusteddom_pw, METH_VARARGS,
+ "del_trusteddom_pw(domain) -> None\n\n \
+ Delete trusted domain password." },
+ { "enum_trusteddoms", py_pdb_enum_trusteddoms, METH_NOARGS,
+ "enum_trusteddoms() -> List\n\n \
+ Get list of trusted domains. Each item is a dictionary with name and sid keys" },
+ { "get_trusted_domain", py_pdb_get_trusted_domain, METH_VARARGS,
+ "get_trusted_domain(domain) -> Mapping\n\n \
+ Get trusted domain information by name. Information is a dictionary with keys - domain_name, netbios_name, security_identifier, trust_auth_incoming, trust_auth_outgoing, trust_direction, trust_type, trust_attributes, trust_forest_trust_info." },
+ { "get_trusted_domain_by_sid", py_pdb_get_trusted_domain_by_sid, METH_VARARGS,
+ "get_trusted_domain_by_sid(domain_sid) -> Mapping\n\n \
+ Get trusted domain information by sid. Information is a dictionary with keys - domain_name, netbios_name, security_identifier, trust_auth_incoming, trust_auth_outgoing, trust_direction, trust_type, trust_attributes, trust_forest_trust_info" },
+ { "set_trusted_domain", py_pdb_set_trusted_domain, METH_VARARGS,
+ "set_trusted_domain(domain, Mapping) -> None\n\n \
+ Set trusted domain information for domain. Mapping is a dictionary with keys - domain_name, netbios_name, security_identifier, trust_auth_incoming, trust_auth_outgoing, trust_direction, trust_type, trust_attributes, trust_forest_trust_info." },
+ { "del_trusted_domain", py_pdb_del_trusted_domain, METH_VARARGS,
+ "del_trusted_domain(domain) -> None\n\n \
+ Delete trusted domain." },
+ { "enum_trusted_domains", py_pdb_enum_trusted_domains, METH_VARARGS,
+ "enum_trusted_domains() -> List\n\n \
+ Get list of trusted domains. Each entry is a dictionary with keys - domain_name, netbios_name, security_identifier, trust_auth_incoming, trust_auth_outgoing, trust_direction, trust_type, trust_attributes, trust_forest_trust_info." },
+ { "get_secret", py_pdb_get_secret, METH_VARARGS,
+ "get_secret(secret_name) -> Mapping\n\n \
+ Get secret information for secret_name. Information is a dictionary with keys - secret_current, secret_current_lastchange, secret_old, secret_old_lastchange, sd." },
+ { "set_secret", py_pdb_set_secret, METH_VARARGS,
+ "set_secret(secret_name, Mapping) -> None\n\n \
+ Set secret information for secret_name using dictionary with keys - secret_current, sd." },
+ { "delete_secret", py_pdb_delete_secret, METH_VARARGS,
+ "delete_secret(secret_name) -> None\n\n \
+ Delete secret information for secret_name." },
+ {0},
+};
+
+
+static PyObject *py_pdb_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *url = NULL;
+ PyObject *pypdb;
+ NTSTATUS status;
+ struct pdb_methods *methods;
+
+ if (!PyArg_ParseTuple(args, "s", &url)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ /* Initialize list of methods */
+ status = make_pdb_method_name(&methods, url);
+ if (!NT_STATUS_IS_OK(status)) {
+ PyErr_Format(py_pdb_error, "Cannot load backend methods for '%s' backend (%d,%s)",
+ url,
+ NT_STATUS_V(status),
+ get_friendly_nt_error_msg(status));
+ talloc_free(frame);
+ return NULL;
+ }
+
+ if ((pypdb = pytalloc_steal(type, methods)) == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ return pypdb;
+}
+
+
+static PyTypeObject PyPDB = {
+ .tp_name = "passdb.PDB",
+ .tp_new = py_pdb_new,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_methods = py_pdb_methods,
+ .tp_doc = "PDB(url[, read_write_flags]) -> Password DB object\n",
+};
+
+
+/*
+ * Return a list of passdb backends
+ */
+static PyObject *py_passdb_backends(PyObject *self, PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ PyObject *py_blist;
+ const struct pdb_init_function_entry *entry;
+
+ entry = pdb_get_backends();
+ if(! entry) {
+ Py_RETURN_NONE;
+ }
+
+ if((py_blist = PyList_New(0)) == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ while(entry) {
+ int res = 0;
+ PyObject *entry_name = PyUnicode_FromString(entry->name);
+ if (entry_name) {
+ res = PyList_Append(py_blist, entry_name);
+ } else {
+ Py_CLEAR(entry_name);
+ Py_CLEAR(py_blist);
+ break;
+ }
+ Py_CLEAR(entry_name);
+ if (res == -1) {
+ Py_CLEAR(py_blist);
+ break;
+ }
+ entry = entry->next;
+ }
+
+ talloc_free(frame);
+ return py_blist;
+}
+
+
+static PyObject *py_set_smb_config(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *smb_config;
+
+ if (!PyArg_ParseTuple(args, "s", &smb_config)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ /* Load smbconf parameters */
+ if (!lp_load_global(smb_config)) {
+ PyErr_Format(py_pdb_error, "Cannot open '%s'", smb_config);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+
+static PyObject *py_set_secrets_dir(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *private_dir;
+
+ if (!PyArg_ParseTuple(args, "s", &private_dir)) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ /* Initialize secrets database */
+ if (!secrets_init_path(private_dir)) {
+ PyErr_Format(py_pdb_error, "Cannot open secrets file database in '%s'",
+ private_dir);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_reload_static_pdb(PyObject *self, PyObject *args)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* Initialize secrets database */
+ if (!initialize_password_db(true, NULL)) {
+ PyErr_Format(py_pdb_error, "Cannot re-open passdb backend %s", lp_passdb_backend());
+ talloc_free(frame);
+ return NULL;
+ }
+
+ talloc_free(frame);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_get_domain_sid(PyObject *self, PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct dom_sid domain_sid, *domain_sid_copy;
+ PyObject *py_dom_sid = Py_None;
+ bool ret = false;
+
+ ret = secrets_fetch_domain_sid(lp_workgroup(), &domain_sid);
+ if (!ret) {
+ talloc_free(frame);
+ return PyErr_NoMemory();
+ }
+
+ domain_sid_copy = dom_sid_dup(frame, &domain_sid);
+ if (domain_sid_copy == NULL) {
+ talloc_free(frame);
+ return PyErr_NoMemory();
+ }
+
+ py_dom_sid = pytalloc_steal(dom_sid_Type, domain_sid_copy);
+
+ talloc_free(frame);
+ return py_dom_sid;
+}
+
+static PyObject *py_get_global_sam_sid(PyObject *self, PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct dom_sid *domain_sid, *domain_sid_copy;
+ PyObject *py_dom_sid;
+
+ domain_sid = get_global_sam_sid();
+
+ domain_sid_copy = dom_sid_dup(frame, domain_sid);
+ if (domain_sid_copy == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ py_dom_sid = pytalloc_steal(dom_sid_Type, domain_sid_copy);
+
+ talloc_free(frame);
+ return py_dom_sid;
+}
+
+
+static PyMethodDef py_passdb_methods[] = {
+ { "get_backends", py_passdb_backends, METH_NOARGS,
+ "get_backends() -> list\n\n \
+ Get a list of password database backends supported." },
+ { "set_smb_config", py_set_smb_config, METH_VARARGS,
+ "set_smb_config(path) -> None\n\n \
+ Set path to smb.conf file to load configuration parameters." },
+ { "set_secrets_dir", py_set_secrets_dir, METH_VARARGS,
+ "set_secrets_dir(private_dir) -> None\n\n \
+ Set path to private directory to load secrets database from non-default location." },
+ { "get_global_sam_sid", py_get_global_sam_sid, METH_NOARGS,
+ "get_global_sam_sid() -> dom_sid\n\n \
+ Return domain SID." },
+ { "get_domain_sid", py_get_domain_sid, METH_NOARGS,
+ "get_domain_sid() -> dom_sid\n\n \
+ Return domain SID from secrets database." },
+ { "reload_static_pdb", py_reload_static_pdb, METH_NOARGS,
+ "reload_static_pdb() -> None\n\n \
+ Re-initialise the static pdb used internally. Needed if 'passdb backend' is changed." },
+ {0},
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "passdb",
+ .m_doc = "SAMBA Password Database",
+ .m_size = -1,
+ .m_methods = py_passdb_methods,
+};
+
+MODULE_INIT_FUNC(passdb)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ PyObject *m = NULL, *mod = NULL;
+ char exception_name[] = "passdb.error";
+
+ if (pytalloc_BaseObject_PyType_Ready(&PyPDB) < 0) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ if (pytalloc_BaseObject_PyType_Ready(&PySamu) < 0) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ if (pytalloc_BaseObject_PyType_Ready(&PyGroupmap) < 0) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ m = PyModule_Create(&moduledef);
+ if (m == NULL) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ /* Create new exception for passdb module */
+ py_pdb_error = PyErr_NewException(exception_name, NULL, NULL);
+ Py_INCREF(py_pdb_error);
+ PyModule_AddObject(m, "error", py_pdb_error);
+
+ Py_INCREF(&PyPDB);
+ PyModule_AddObject(m, "PDB", (PyObject *)&PyPDB);
+
+ Py_INCREF(&PySamu);
+ PyModule_AddObject(m, "Samu", (PyObject *)&PySamu);
+
+ Py_INCREF(&PyGroupmap);
+ PyModule_AddObject(m, "Groupmap", (PyObject *)&PyGroupmap);
+
+ /* Import dom_sid type from dcerpc.security */
+ mod = PyImport_ImportModule("samba.dcerpc.security");
+ if (mod == NULL) {
+ talloc_free(frame);
+ return NULL;
+ }
+
+ dom_sid_Type = (PyTypeObject *)PyObject_GetAttrString(mod, "dom_sid");
+ if (dom_sid_Type == NULL) {
+ Py_DECREF(mod);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ /* Import security_descriptor type from dcerpc.security */
+ security_Type = (PyTypeObject *)PyObject_GetAttrString(mod, "descriptor");
+ Py_DECREF(mod);
+ if (security_Type == NULL) {
+ Py_DECREF(dom_sid_Type);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ /* Import GUID type from dcerpc.misc */
+ mod = PyImport_ImportModule("samba.dcerpc.misc");
+ if (mod == NULL) {
+ Py_DECREF(security_Type);
+ Py_DECREF(dom_sid_Type);
+ talloc_free(frame);
+ return NULL;
+ }
+
+ guid_Type = (PyTypeObject *)PyObject_GetAttrString(mod, "GUID");
+ Py_DECREF(mod);
+ if (guid_Type == NULL) {
+ Py_DECREF(security_Type);
+ Py_DECREF(dom_sid_Type);
+ talloc_free(frame);
+ return NULL;
+ }
+ talloc_free(frame);
+ return m;
+}
diff --git a/source3/passdb/secrets.c b/source3/passdb/secrets.c
new file mode 100644
index 0000000..6bbc21a
--- /dev/null
+++ b/source3/passdb/secrets.c
@@ -0,0 +1,546 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Andrew Tridgell 1992-2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Tim Potter 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* the Samba secrets database stores any generated, private information
+ such as the local SID and machine trust password */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
+#include "secrets.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "util_tdb.h"
+#include "auth/credentials/credentials.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_PASSDB
+
+static struct db_context *db_ctx;
+
+/* open up the secrets database with specified private_dir path */
+bool secrets_init_path(const char *private_dir)
+{
+ char *fname = NULL;
+ TALLOC_CTX *frame;
+
+ if (db_ctx != NULL) {
+ return True;
+ }
+
+ if (private_dir == NULL) {
+ return False;
+ }
+
+ frame = talloc_stackframe();
+ fname = talloc_asprintf(frame, "%s/secrets.tdb", private_dir);
+ if (fname == NULL) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ db_ctx = db_open(NULL, fname, 0,
+ TDB_DEFAULT, O_RDWR|O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+
+ if (db_ctx == NULL) {
+ DEBUG(0,("Failed to open %s\n", fname));
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/* open up the secrets database */
+bool secrets_init(void)
+{
+ return secrets_init_path(lp_private_dir());
+}
+
+struct db_context *secrets_db_ctx(void)
+{
+ if (!secrets_init()) {
+ return NULL;
+ }
+
+ return db_ctx;
+}
+
+/*
+ * close secrets.tdb
+ */
+void secrets_shutdown(void)
+{
+ TALLOC_FREE(db_ctx);
+}
+
+/* read a entry from the secrets database - the caller must free the result
+ if size is non-null then the size of the entry is put in there
+ */
+void *secrets_fetch(const char *key, size_t *size)
+{
+ TDB_DATA dbuf;
+ void *result;
+ NTSTATUS status;
+
+ if (!secrets_init()) {
+ return NULL;
+ }
+
+ status = dbwrap_fetch(db_ctx, talloc_tos(), string_tdb_data(key),
+ &dbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ result = smb_memdup(dbuf.dptr, dbuf.dsize);
+ if (result == NULL) {
+ return NULL;
+ }
+ /*
+ * secrets_fetch() is a generic code and may be used for sensitive data,
+ * so clear the local dbuf.dptr memory via BURN_PTR_SIZE().
+ * The future plan is to convert secrets_fetch() to talloc.
+ * That would improve performance via:
+ * - avoid smb_memdup() above, instead directly return dbuf.dptr
+ * - BURN_PTR_SIZE() will be done not here but in the caller and only
+ * if the caller asks for sensitive data.
+ */
+ BURN_PTR_SIZE(dbuf.dptr, dbuf.dsize);
+ TALLOC_FREE(dbuf.dptr);
+
+ if (size) {
+ *size = dbuf.dsize;
+ }
+
+ return result;
+}
+
+/* store a secrets entry
+ */
+bool secrets_store(const char *key, const void *data, size_t size)
+{
+ NTSTATUS status;
+
+ if (!secrets_init()) {
+ return false;
+ }
+
+ status = dbwrap_trans_store(db_ctx, string_tdb_data(key),
+ make_tdb_data((const uint8_t *)data, size),
+ TDB_REPLACE);
+ return NT_STATUS_IS_OK(status);
+}
+
+bool secrets_store_creds(struct cli_credentials *creds)
+{
+ const char *p = NULL;
+ bool ok;
+
+ p = cli_credentials_get_username(creds);
+ if (p == NULL) {
+ return false;
+ }
+
+ ok = secrets_store(SECRETS_AUTH_USER, p, strlen(p) + 1);
+ if (!ok) {
+ DBG_ERR("Failed storing auth user name\n");
+ return false;
+ }
+
+
+ p = cli_credentials_get_domain(creds);
+ if (p == NULL) {
+ return false;
+ }
+
+ ok = secrets_store(SECRETS_AUTH_DOMAIN, p, strlen(p) + 1);
+ if (!ok) {
+ DBG_ERR("Failed storing auth domain name\n");
+ return false;
+ }
+
+
+ p = cli_credentials_get_password(creds);
+ if (p == NULL) {
+ return false;
+ }
+
+ ok = secrets_store(SECRETS_AUTH_PASSWORD, p, strlen(p) + 1);
+ if (!ok) {
+ DBG_ERR("Failed storing auth password\n");
+ return false;
+ }
+
+ return true;
+}
+
+
+/* delete a secets database entry
+ */
+bool secrets_delete_entry(const char *key)
+{
+ NTSTATUS status;
+ if (!secrets_init()) {
+ return false;
+ }
+
+ status = dbwrap_trans_delete(db_ctx, string_tdb_data(key));
+
+ return NT_STATUS_IS_OK(status);
+}
+
+/*
+ * Deletes the key if it exists.
+ */
+bool secrets_delete(const char *key)
+{
+ bool exists;
+
+ if (!secrets_init()) {
+ return false;
+ }
+
+ exists = dbwrap_exists(db_ctx, string_tdb_data(key));
+ if (!exists) {
+ return true;
+ }
+
+ return secrets_delete_entry(key);
+}
+
+/**
+ * Form a key for fetching a trusted domain password
+ *
+ * @param domain trusted domain name
+ *
+ * @return stored password's key
+ **/
+static char *trustdom_keystr(const char *domain)
+{
+ char *keystr;
+
+ keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
+ SECRETS_DOMTRUST_ACCT_PASS,
+ domain);
+ SMB_ASSERT(keystr != NULL);
+ return keystr;
+}
+
+/************************************************************************
+ Routine to get account password to trusted domain
+************************************************************************/
+
+bool secrets_fetch_trusted_domain_password(const char *domain, char** pwd,
+ struct dom_sid *sid, time_t *pass_last_set_time)
+{
+ struct TRUSTED_DOM_PASS pass;
+ enum ndr_err_code ndr_err;
+
+ /* unpacking structures */
+ DATA_BLOB blob;
+
+ /* fetching trusted domain password structure */
+ if (!(blob.data = (uint8_t *)secrets_fetch(trustdom_keystr(domain),
+ &blob.length))) {
+ DEBUG(5, ("secrets_fetch failed!\n"));
+ return False;
+ }
+
+ /* unpack trusted domain password */
+ ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &pass,
+ (ndr_pull_flags_fn_t)ndr_pull_TRUSTED_DOM_PASS);
+
+ /* This blob is NOT talloc based! */
+ BURN_FREE(blob.data, blob.length);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+
+ if (pass.pass != NULL) {
+ talloc_keep_secret(discard_const_p(char, pass.pass));
+ }
+
+ /* the trust's password */
+ if (pwd) {
+ *pwd = SMB_STRDUP(pass.pass);
+ if (!*pwd) {
+ return False;
+ }
+ }
+
+ /* last change time */
+ if (pass_last_set_time) *pass_last_set_time = pass.mod_time;
+
+ /* domain sid */
+ if (sid != NULL) sid_copy(sid, &pass.domain_sid);
+
+ return True;
+}
+
+/**
+ * Routine to store the password for trusted domain
+ *
+ * @param domain remote domain name
+ * @param pwd plain text password of trust relationship
+ * @param sid remote domain sid
+ *
+ * @return true if succeeded
+ **/
+
+bool secrets_store_trusted_domain_password(const char* domain, const char* pwd,
+ const struct dom_sid *sid)
+{
+ bool ret;
+
+ /* packing structures */
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ struct TRUSTED_DOM_PASS pass;
+ ZERO_STRUCT(pass);
+
+ pass.uni_name = domain;
+ pass.uni_name_len = strlen(domain)+1;
+
+ /* last change time */
+ pass.mod_time = time(NULL);
+
+ /* password of the trust */
+ pass.pass_len = strlen(pwd);
+ pass.pass = pwd;
+
+ /* domain sid */
+ sid_copy(&pass.domain_sid, sid);
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &pass,
+ (ndr_push_flags_fn_t)ndr_push_TRUSTED_DOM_PASS);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+
+ ret = secrets_store(trustdom_keystr(domain), blob.data, blob.length);
+
+ /* This blob is talloc based. */
+ data_blob_clear_free(&blob);
+
+ return ret;
+}
+
+/************************************************************************
+ Routine to delete the password for trusted domain
+************************************************************************/
+
+bool trusted_domain_password_delete(const char *domain)
+{
+ return secrets_delete_entry(trustdom_keystr(domain));
+}
+
+bool secrets_store_ldap_pw(const char* dn, char* pw)
+{
+ char *key = NULL;
+ bool ret;
+
+ if (asprintf(&key, "%s/%s", SECRETS_LDAP_BIND_PW, dn) < 0) {
+ DEBUG(0, ("secrets_store_ldap_pw: asprintf failed!\n"));
+ return False;
+ }
+
+ ret = secrets_store(key, pw, strlen(pw)+1);
+
+ SAFE_FREE(key);
+ return ret;
+}
+
+/*******************************************************************
+ Find the ldap password.
+******************************************************************/
+
+bool fetch_ldap_pw(char **dn, char** pw)
+{
+ char *key = NULL;
+ size_t size = 0;
+
+ *dn = smb_xstrdup(lp_ldap_admin_dn());
+
+ if (asprintf(&key, "%s/%s", SECRETS_LDAP_BIND_PW, *dn) < 0) {
+ SAFE_FREE(*dn);
+ DEBUG(0, ("fetch_ldap_pw: asprintf failed!\n"));
+ return false;
+ }
+
+ *pw=(char *)secrets_fetch(key, &size);
+ SAFE_FREE(key);
+
+ if (*pw == NULL || size == 0 || (*pw)[size-1] != '\0') {
+ DBG_ERR("No valid password for %s\n", *dn);
+ BURN_FREE_STR(*pw);
+ SAFE_FREE(*dn);
+ return false;
+ }
+
+ return true;
+}
+
+/*******************************************************************************
+ Store a complete AFS keyfile into secrets.tdb.
+*******************************************************************************/
+
+bool secrets_store_afs_keyfile(const char *cell, const struct afs_keyfile *keyfile)
+{
+ fstring key;
+
+ if ((cell == NULL) || (keyfile == NULL))
+ return False;
+
+ if (ntohl(keyfile->nkeys) > SECRETS_AFS_MAXKEYS)
+ return False;
+
+ slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_AFS_KEYFILE, cell);
+ return secrets_store(key, keyfile, sizeof(struct afs_keyfile));
+}
+
+/*******************************************************************************
+ Fetch the current (highest) AFS key from secrets.tdb
+*******************************************************************************/
+bool secrets_fetch_afs_key(const char *cell, struct afs_key *result)
+{
+ fstring key;
+ struct afs_keyfile *keyfile;
+ size_t size = 0;
+ uint32_t i;
+
+ slprintf(key, sizeof(key)-1, "%s/%s", SECRETS_AFS_KEYFILE, cell);
+
+ keyfile = (struct afs_keyfile *)secrets_fetch(key, &size);
+
+ if (keyfile == NULL)
+ return False;
+
+ if (size != sizeof(struct afs_keyfile)) {
+ BURN_FREE(keyfile, sizeof(*keyfile));
+ return False;
+ }
+
+ i = ntohl(keyfile->nkeys);
+
+ if (i > SECRETS_AFS_MAXKEYS) {
+ BURN_FREE(keyfile, sizeof(*keyfile));
+ return False;
+ }
+
+ *result = keyfile->entry[i-1];
+
+ result->kvno = ntohl(result->kvno);
+
+ BURN_FREE(keyfile, sizeof(*keyfile));
+
+ return True;
+}
+
+/******************************************************************************
+ When kerberos is not available, choose between anonymous or
+ authenticated connections.
+
+ We need to use an authenticated connection if DCs have the
+ RestrictAnonymous registry entry set > 0, or the "Additional
+ restrictions for anonymous connections" set in the win2k Local
+ Security Policy.
+
+ Caller to free() result in domain, username, password
+*******************************************************************************/
+void secrets_fetch_ipc_userpass(char **username, char **domain, char **password)
+{
+ *username = (char *)secrets_fetch(SECRETS_AUTH_USER, NULL);
+ *domain = (char *)secrets_fetch(SECRETS_AUTH_DOMAIN, NULL);
+ *password = (char *)secrets_fetch(SECRETS_AUTH_PASSWORD, NULL);
+
+ if (*username && **username) {
+
+ if (!*domain || !**domain) {
+ SAFE_FREE(*domain);
+ *domain = smb_xstrdup(lp_workgroup());
+ }
+
+ if (!*password || !**password) {
+ BURN_FREE_STR(*password);
+ *password = smb_xstrdup("");
+ }
+
+ DEBUG(3, ("IPC$ connections done by user %s\\%s\n",
+ *domain, *username));
+
+ } else {
+ DEBUG(3, ("IPC$ connections done anonymously\n"));
+ SAFE_FREE(*username);
+ SAFE_FREE(*domain);
+ BURN_FREE_STR(*password);
+ *username = smb_xstrdup("");
+ *domain = smb_xstrdup("");
+ *password = smb_xstrdup("");
+ }
+}
+
+bool secrets_store_generic(const char *owner, const char *key, const char *secret)
+{
+ char *tdbkey = NULL;
+ bool ret;
+
+ if (asprintf(&tdbkey, "SECRETS/GENERIC/%s/%s", owner, key) < 0) {
+ DEBUG(0, ("asprintf failed!\n"));
+ return False;
+ }
+
+ ret = secrets_store(tdbkey, secret, strlen(secret)+1);
+
+ SAFE_FREE(tdbkey);
+ return ret;
+}
+
+/*******************************************************************
+ Find the ldap password.
+******************************************************************/
+
+char *secrets_fetch_generic(const char *owner, const char *key)
+{
+ char *secret = NULL;
+ char *tdbkey = NULL;
+
+ if (( ! owner) || ( ! key)) {
+ DEBUG(1, ("Invalid Parameters\n"));
+ return NULL;
+ }
+
+ if (asprintf(&tdbkey, "SECRETS/GENERIC/%s/%s", owner, key) < 0) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NULL;
+ }
+
+ secret = (char *)secrets_fetch(tdbkey, NULL);
+ SAFE_FREE(tdbkey);
+
+ return secret;
+}
+
diff --git a/source3/passdb/secrets_lsa.c b/source3/passdb/secrets_lsa.c
new file mode 100644
index 0000000..7ff6d51
--- /dev/null
+++ b/source3/passdb/secrets_lsa.c
@@ -0,0 +1,245 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Guenther Deschner 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_secrets.h"
+#include "secrets.h"
+
+/******************************************************************************
+*******************************************************************************/
+
+static char *lsa_secret_key(TALLOC_CTX *mem_ctx,
+ const char *secret_name)
+{
+ return talloc_asprintf_strupper_m(mem_ctx, "SECRETS/LSA/%s",
+ secret_name);
+}
+
+/******************************************************************************
+*******************************************************************************/
+
+static NTSTATUS lsa_secret_get_common(TALLOC_CTX *mem_ctx,
+ const char *secret_name,
+ struct lsa_secret *secret)
+{
+ char *key;
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+
+ ZERO_STRUCTP(secret);
+
+ key = lsa_secret_key(mem_ctx, secret_name);
+ if (!key) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ blob.data = (uint8_t *)secrets_fetch(key, &blob.length);
+ talloc_free(key);
+
+ if (!blob.data) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, secret,
+ (ndr_pull_flags_fn_t)ndr_pull_lsa_secret);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ SAFE_FREE(blob.data);
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ /* This is NOT a talloc blob */
+ BURN_FREE(blob.data, blob.length);
+
+ if (secret->secret_current != NULL &&
+ secret->secret_current->data != NULL) {
+ talloc_keep_secret(secret->secret_current->data);
+ }
+ if (secret->secret_old != NULL && secret->secret_old->data != NULL) {
+ talloc_keep_secret(secret->secret_old->data);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+*******************************************************************************/
+
+NTSTATUS lsa_secret_get(TALLOC_CTX *mem_ctx,
+ const char *secret_name,
+ DATA_BLOB *secret_current,
+ NTTIME *secret_current_lastchange,
+ DATA_BLOB *secret_old,
+ NTTIME *secret_old_lastchange,
+ struct security_descriptor **sd)
+{
+ NTSTATUS status;
+ struct lsa_secret secret;
+
+ status = lsa_secret_get_common(mem_ctx, secret_name, &secret);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (secret_current) {
+ *secret_current = data_blob_null;
+ if (secret.secret_current) {
+ *secret_current = *secret.secret_current;
+ }
+ }
+ if (secret_current_lastchange) {
+ *secret_current_lastchange = secret.secret_current_lastchange;
+ }
+ if (secret_old) {
+ *secret_old = data_blob_null;
+ if (secret.secret_old) {
+ *secret_old = *secret.secret_old;
+ }
+ }
+ if (secret_old_lastchange) {
+ *secret_old_lastchange = secret.secret_old_lastchange;
+ }
+ if (sd) {
+ *sd = secret.sd;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+*******************************************************************************/
+
+static NTSTATUS lsa_secret_set_common(TALLOC_CTX *mem_ctx,
+ const char *key,
+ struct lsa_secret *secret,
+ DATA_BLOB *secret_current,
+ DATA_BLOB *secret_old,
+ struct security_descriptor *sd)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ struct timeval now = timeval_current();
+
+ if (!secret) {
+ secret = talloc_zero(mem_ctx, struct lsa_secret);
+ }
+
+ if (!secret) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (secret_old) {
+ secret->secret_old = secret_old;
+ secret->secret_old_lastchange = timeval_to_nttime(&now);
+ } else {
+ if (secret->secret_current) {
+ secret->secret_old = secret->secret_current;
+ secret->secret_old_lastchange = secret->secret_current_lastchange;
+ } else {
+ secret->secret_old = NULL;
+ secret->secret_old_lastchange = timeval_to_nttime(&now);
+ }
+ }
+ if (secret_current) {
+ secret->secret_current = secret_current;
+ secret->secret_current_lastchange = timeval_to_nttime(&now);
+ } else {
+ secret->secret_current = NULL;
+ secret->secret_current_lastchange = timeval_to_nttime(&now);
+ }
+ if (sd) {
+ secret->sd = sd;
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, secret,
+ (ndr_push_flags_fn_t)ndr_push_lsa_secret);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (!secrets_store(key, blob.data, blob.length)) {
+ data_blob_clear(&blob);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ data_blob_clear(&blob);
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+*******************************************************************************/
+
+NTSTATUS lsa_secret_set(const char *secret_name,
+ DATA_BLOB *secret_current,
+ DATA_BLOB *secret_old,
+ struct security_descriptor *sd)
+{
+ char *key;
+ struct lsa_secret secret;
+ NTSTATUS status;
+
+ key = lsa_secret_key(talloc_tos(), secret_name);
+ if (!key) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = lsa_secret_get_common(talloc_tos(), secret_name, &secret);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ talloc_free(key);
+ return status;
+ }
+
+ status = lsa_secret_set_common(talloc_tos(), key,
+ &secret,
+ secret_current,
+ secret_old,
+ sd);
+ talloc_free(key);
+
+ return status;
+}
+
+/******************************************************************************
+*******************************************************************************/
+
+NTSTATUS lsa_secret_delete(const char *secret_name)
+{
+ char *key;
+ struct lsa_secret secret;
+ NTSTATUS status;
+
+ key = lsa_secret_key(talloc_tos(), secret_name);
+ if (!key) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = lsa_secret_get_common(talloc_tos(), secret_name, &secret);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(key);
+ return status;
+ }
+
+ if (!secrets_delete_entry(key)) {
+ talloc_free(key);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ talloc_free(key);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/passdb/wscript_build b/source3/passdb/wscript_build
new file mode 100644
index 0000000..7facc1f
--- /dev/null
+++ b/source3/passdb/wscript_build
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_MODULE('pdb_tdbsam',
+ subsystem='pdb',
+ source='pdb_tdb.c',
+ deps='samba-util dbwrap tdb-wrap3',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('pdb_tdbsam'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('pdb_tdbsam'))
+
+bld.SAMBA3_MODULE('pdb_ldapsam',
+ subsystem='pdb',
+ deps='smbldap smbldaphelper LIBCLI_AUTH',
+ source='pdb_ldap.c pdb_nds.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('pdb_ldapsam'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('pdb_ldapsam') and bld.CONFIG_SET('HAVE_LDAP'))
+
+bld.SAMBA3_MODULE('pdb_smbpasswd',
+ subsystem='pdb',
+ source='pdb_smbpasswd.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('pdb_smbpasswd'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('pdb_smbpasswd'))
+
+bld.SAMBA3_MODULE('pdb_samba_dsdb',
+ subsystem='pdb',
+ source='pdb_samba_dsdb.c',
+ init_function='',
+ deps='IDMAP samdb',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('pdb_samba_dsdb') and bld.AD_DC_BUILD_IS_ENABLED(),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('pdb_samba_dsdb') and bld.AD_DC_BUILD_IS_ENABLED())
+
+pyrpc_util = bld.pyembed_libname('pyrpc_util')
+pytalloc_util = bld.pyembed_libname('pytalloc-util')
+bld.SAMBA3_PYTHON('pypassdb',
+ source='py_passdb.c',
+ deps='pdb',
+ public_deps=' '.join(['samba-util', 'tdb', 'talloc', pyrpc_util, pytalloc_util]),
+ realname='samba/samba3/passdb.so'
+ )
diff --git a/source3/printing/load.c b/source3/printing/load.c
new file mode 100644
index 0000000..ea5154d
--- /dev/null
+++ b/source3/printing/load.c
@@ -0,0 +1,109 @@
+/*
+ Unix SMB/CIFS implementation.
+ load printer lists
+ Copyright (C) Andrew Tridgell 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/load.h"
+#include "lib/param/loadparm.h"
+
+/***************************************************************************
+auto-load some homes and printer services
+***************************************************************************/
+static void add_auto_printers(void)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *p;
+ int pnum = lp_servicenumber(PRINTERS_NAME);
+ char *str;
+ char *saveptr;
+ char *auto_serv = NULL;
+
+ if (pnum < 0)
+ if (process_registry_service(PRINTERS_NAME))
+ pnum = lp_servicenumber(PRINTERS_NAME);
+
+ if (pnum < 0)
+ return;
+
+ auto_serv = lp_auto_services(talloc_tos(), lp_sub);
+ str = SMB_STRDUP(auto_serv);
+ TALLOC_FREE(auto_serv);
+ if (str == NULL) {
+ return;
+ }
+
+ for (p = strtok_r(str, LIST_SEP, &saveptr); p;
+ p = strtok_r(NULL, LIST_SEP, &saveptr)) {
+ if (lp_servicenumber(p) >= 0)
+ continue;
+
+ if (printer_list_printername_exists(p))
+ lp_add_printer(p, pnum);
+ }
+
+ SAFE_FREE(str);
+}
+
+/***************************************************************************
+load automatic printer services from pre-populated pcap cache
+***************************************************************************/
+void load_printers(void)
+{
+ NTSTATUS status;
+
+ if (!pcap_cache_loaded(NULL)) {
+ return;
+ }
+
+ add_auto_printers();
+
+ if (!lp_load_printers()) {
+ return;
+ }
+
+ /*
+ * Do not add printers from pcap, if we don't have a [printers] share.
+ */
+ if (lp_servicenumber(PRINTERS_NAME) < 0) {
+ return;
+ }
+
+ status = printer_list_read_run_fn(lp_add_one_printer, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("printer_list_read_run_fn failed: %s\n",
+ nt_errstr(status));
+ }
+}
+
+bool pcap_cache_loaded(time_t *_last_change)
+{
+ NTSTATUS status;
+ time_t last;
+
+ status = printer_list_get_last_refresh(&last);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ if (_last_change != NULL) {
+ *_last_change = last;
+ }
+ return true;
+}
diff --git a/source3/printing/load.h b/source3/printing/load.h
new file mode 100644
index 0000000..5a37769
--- /dev/null
+++ b/source3/printing/load.h
@@ -0,0 +1,28 @@
+/*
+ Unix SMB/CIFS implementation.
+ load printer lists
+ Copyright (C) Andrew Tridgell 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PRINTING_LOAD_H_
+#define _PRINTING_LOAD_H_
+
+/* The following definitions come from printing/load.c */
+
+bool pcap_cache_loaded(time_t *_last_change);
+void load_printers(void);
+
+#endif /* _PRINTING_LOAD_H_ */
diff --git a/source3/printing/lpq_parse.c b/source3/printing/lpq_parse.c
new file mode 100644
index 0000000..8351402
--- /dev/null
+++ b/source3/printing/lpq_parse.c
@@ -0,0 +1,1164 @@
+/*
+ Unix SMB/CIFS implementation.
+ lpq parsing routines
+ Copyright (C) Andrew Tridgell 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "lib/util/string_wrappers.h"
+
+static const char *Months[13] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Err"};
+
+
+/*******************************************************************
+ Process time fields
+********************************************************************/
+
+static time_t EntryTime(char *tok[], int ptr, int count, int minimum)
+{
+ time_t jobtime,jobtime1;
+
+ jobtime = time(NULL); /* default case: take current time */
+ if (count >= minimum) {
+ struct tm *t;
+ int i, day, hour, min, sec;
+
+ for (i=0; i<13; i++) {
+ if (!strncmp(tok[ptr], Months[i],3)) {
+ break; /* Find month */
+ }
+ }
+
+ if (i<12) {
+ fstring c;
+ t = localtime(&jobtime);
+ if (!t) {
+ return (time_t)-1;
+ }
+ day = atoi(tok[ptr+1]);
+ fstrcpy(c,tok[ptr+2]);
+ *(c+2)=0;
+ hour = atoi(c);
+ *(c+5)=0;
+ min = atoi(c+3);
+ if(*(c+6) != 0) {
+ sec = atoi(c+6);
+ } else {
+ sec=0;
+ }
+
+ if ((t->tm_mon < i)|| ((t->tm_mon == i)&&
+ ((t->tm_mday < day)||
+ ((t->tm_mday == day)&&
+ (t->tm_hour*60+t->tm_min < hour*60+min))))) {
+ t->tm_year--; /* last year's print job */
+ }
+
+ t->tm_mon = i;
+ t->tm_mday = day;
+ t->tm_hour = hour;
+ t->tm_min = min;
+ t->tm_sec = sec;
+ jobtime1 = mktime(t);
+ if (jobtime1 != (time_t)-1) {
+ jobtime = jobtime1;
+ }
+ }
+ }
+ return jobtime;
+}
+
+/****************************************************************************
+parse a lpq line
+
+here is an example of lpq output under bsd
+
+Warning: no daemon present
+Rank Owner Job Files Total Size
+1st tridge 148 README 8096 bytes
+
+here is an example of lpq output under osf/1
+
+Warning: no daemon present
+Rank Pri Owner Job Files Total Size
+1st 0 tridge 148 README 8096 bytes
+
+
+<allan@umich.edu> June 30, 1998.
+Modified to handle file names with spaces, like the parse_lpq_lprng code
+further below.
+****************************************************************************/
+
+static bool parse_lpq_bsd(char *line,print_queue_struct *buf,bool first)
+{
+#ifdef OSF1
+#define RANKTOK 0
+#define PRIOTOK 1
+#define USERTOK 2
+#define JOBTOK 3
+#define FILETOK 4
+#define TOTALTOK (count - 2)
+#define NTOK 6
+#define MAXTOK 128
+#else /* OSF1 */
+#define RANKTOK 0
+#define USERTOK 1
+#define JOBTOK 2
+#define FILETOK 3
+#define TOTALTOK (count - 2)
+#define NTOK 5
+#define MAXTOK 128
+#endif /* OSF1 */
+
+ char *tok[MAXTOK];
+ int count = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *line2 = NULL;
+ char *saveptr;
+
+ line2 = talloc_strdup(ctx, line);
+ if (!line2) {
+ return false;
+ }
+
+#ifdef OSF1
+ {
+ size_t length;
+ length = strlen(line2);
+ if (line2[length-3] == ':') {
+ return False;
+ }
+ }
+#endif /* OSF1 */
+
+ /* FIXME: Use next_token_talloc rather than strtok! */
+ tok[0] = strtok_r(line2," \t", &saveptr);
+ count++;
+
+ while ((count < MAXTOK)
+ && ((tok[count] = strtok_r(NULL, " \t", &saveptr)) != NULL)) {
+ count++;
+ }
+
+ /* we must get at least NTOK tokens */
+ if (count < NTOK) {
+ return False;
+ }
+
+ /* the Job and Total columns must be integer */
+ if (!isdigit((int)*tok[JOBTOK]) || !isdigit((int)*tok[TOTALTOK])) {
+ return False;
+ }
+
+ buf->sysjob = atoi(tok[JOBTOK]);
+ buf->size = atoi(tok[TOTALTOK]);
+ buf->status = strequal(tok[RANKTOK],"active")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[USERTOK]);
+ fstrcpy(buf->fs_file,tok[FILETOK]);
+
+ if ((FILETOK + 1) != TOTALTOK) {
+ int i;
+
+ for (i = (FILETOK + 1); i < TOTALTOK; i++) {
+ /* FIXME: Using fstrcat rather than other means is a bit
+ * inefficient; this might be a problem for enormous queues with
+ * many fields. */
+ fstrcat(buf->fs_file, " ");
+ fstrcat(buf->fs_file, tok[i]);
+ }
+ /* Ensure null termination. */
+ buf->fs_file[sizeof(buf->fs_file)-1] = '\0';
+ }
+
+#ifdef PRIOTOK
+ buf->priority = atoi(tok[PRIOTOK]);
+#else
+ buf->priority = 1;
+#endif
+ return True;
+}
+
+/*
+<magnus@hum.auc.dk>
+LPRng_time modifies the current date by inserting the hour and minute from
+the lpq output. The lpq time looks like "23:15:07"
+
+<allan@umich.edu> June 30, 1998.
+Modified to work with the re-written parse_lpq_lprng routine.
+
+<J.P.M.v.Itegem@tue.nl> Dec 17,1999
+Modified to work with lprng 3.16
+With lprng 3.16 The lpq time looks like
+ "23:15:07"
+ "23:15:07.100"
+ "1999-12-16-23:15:07"
+ "1999-12-16-23:15:07.100"
+
+*/
+static time_t LPRng_time(char *time_string)
+{
+ time_t jobtime;
+ struct tm *t;
+
+ jobtime = time(NULL); /* default case: take current time */
+ t = localtime(&jobtime);
+ if (!t) {
+ return (time_t)-1;
+ }
+
+ if ( atoi(time_string) < 24 ){
+ if (strlen(time_string) < 7) {
+ return (time_t)-1;
+ }
+ t->tm_hour = atoi(time_string);
+ t->tm_min = atoi(time_string+3);
+ t->tm_sec = atoi(time_string+6);
+ } else {
+ if (strlen(time_string) < 18) {
+ return (time_t)-1;
+ }
+ t->tm_year = atoi(time_string)-1900;
+ t->tm_mon = atoi(time_string+5)-1;
+ t->tm_mday = atoi(time_string+8);
+ t->tm_hour = atoi(time_string+11);
+ t->tm_min = atoi(time_string+14);
+ t->tm_sec = atoi(time_string+17);
+ }
+ jobtime = mktime(t);
+
+ return jobtime;
+}
+
+/****************************************************************************
+ parse a lprng lpq line
+ <allan@umich.edu> June 30, 1998.
+ Re-wrote this to handle file names with spaces, multiple file names on one
+ lpq line, etc;
+
+****************************************************************************/
+
+static bool parse_lpq_lprng(char *line,print_queue_struct *buf,bool first)
+{
+#define LPRNG_RANKTOK 0
+#define LPRNG_USERTOK 1
+#define LPRNG_PRIOTOK 2
+#define LPRNG_JOBTOK 3
+#define LPRNG_FILETOK 4
+#define LPRNG_TOTALTOK (num_tok - 2)
+#define LPRNG_TIMETOK (num_tok - 1)
+#define LPRNG_NTOK 7
+#define LPRNG_MAXTOK 128 /* PFMA just to keep us from running away. */
+
+ char *tokarr[LPRNG_MAXTOK];
+ const char *cptr;
+ char *ptr;
+ int num_tok = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ cptr = line;
+ while((num_tok < LPRNG_MAXTOK) && next_token_talloc(frame, &cptr,
+ &tokarr[num_tok], " \t")) {
+ num_tok++;
+ }
+
+ /* We must get at least LPRNG_NTOK tokens. */
+ if (num_tok < LPRNG_NTOK) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ if (!isdigit((int)*tokarr[LPRNG_JOBTOK]) || !isdigit((int)*tokarr[LPRNG_TOTALTOK])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ buf->sysjob = atoi(tokarr[LPRNG_JOBTOK]);
+ buf->size = atoi(tokarr[LPRNG_TOTALTOK]);
+
+ if (strequal(tokarr[LPRNG_RANKTOK],"active")) {
+ buf->status = LPQ_PRINTING;
+ } else if (strequal(tokarr[LPRNG_RANKTOK],"done")) {
+ buf->status = LPQ_PRINTED;
+ } else if (isdigit((int)*tokarr[LPRNG_RANKTOK])) {
+ buf->status = LPQ_QUEUED;
+ } else {
+ buf->status = LPQ_PAUSED;
+ }
+
+ buf->priority = *tokarr[LPRNG_PRIOTOK] -'A';
+
+ buf->time = LPRng_time(tokarr[LPRNG_TIMETOK]);
+
+ fstrcpy(buf->fs_user,tokarr[LPRNG_USERTOK]);
+
+ /* The '@hostname' prevents windows from displaying the printing icon
+ * for the current user on the taskbar. Plop in a null.
+ */
+
+ if ((ptr = strchr_m(buf->fs_user,'@')) != NULL) {
+ *ptr = '\0';
+ }
+
+ fstrcpy(buf->fs_file,tokarr[LPRNG_FILETOK]);
+
+ if ((LPRNG_FILETOK + 1) != LPRNG_TOTALTOK) {
+ int i;
+
+ for (i = (LPRNG_FILETOK + 1); i < LPRNG_TOTALTOK; i++) {
+ /* FIXME: Using fstrcat rather than other means is a bit
+ * inefficient; this might be a problem for enormous queues with
+ * many fields. */
+ fstrcat(buf->fs_file, " ");
+ fstrcat(buf->fs_file, tokarr[i]);
+ }
+ /* Ensure null termination. */
+ buf->fs_file[sizeof(buf->fs_file)-1] = '\0';
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/*******************************************************************
+parse lpq on an aix system
+
+Queue Dev Status Job Files User PP % Blks Cp Rnk
+------- ----- --------- --- ------------------ ---------- ---- -- ----- --- ---
+laser laser READY
+laser laser RUNNING 537 6297doc.A kvintus@IE 0 10 2445 1 1
+ QUEUED 538 C.ps root@IEDVB 124 1 2
+ QUEUED 539 E.ps root@IEDVB 28 1 3
+ QUEUED 540 L.ps root@IEDVB 172 1 4
+ QUEUED 541 P.ps root@IEDVB 22 1 5
+********************************************************************/
+
+static bool parse_lpq_aix(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[11];
+ int count=0;
+ const char *cline = line;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* handle the case of "(standard input)" as a filename */
+ string_sub(line,"standard input","STDIN",0);
+ all_string_sub(line,"(","\"",0);
+ all_string_sub(line,")","\"",0);
+
+ for (count=0; count<10 &&
+ next_token_talloc(frame,&cline,&tok[count],NULL); count++) {
+ ;
+ }
+
+ /* we must get 6 tokens */
+ if (count < 10) {
+ if ((count == 7) && ((strcmp(tok[0],"QUEUED") == 0) || (strcmp(tok[0],"HELD") == 0))) {
+ /* the 2nd and 5th columns must be integer */
+ if (!isdigit((int)*tok[1]) || !isdigit((int)*tok[4])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ buf->size = atoi(tok[4]) * 1024;
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[2],' ')) {
+ tok[2] = talloc_strdup(frame,"STDIN");
+ if (!tok[2]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ /* only take the last part of the filename */
+ {
+ char *p = strrchr_m(tok[2],'/');
+ if (p) {
+ tok[2] = p+1;
+ }
+ }
+
+ buf->sysjob = atoi(tok[1]);
+ buf->status = strequal(tok[0],"HELD")?LPQ_PAUSED:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[3]);
+ fstrcpy(buf->fs_file,tok[2]);
+ } else {
+ DEBUG(6,("parse_lpq_aix count=%d\n", count));
+ TALLOC_FREE(frame);
+ return False;
+ }
+ } else {
+ /* the 4th and 9th columns must be integer */
+ if (!isdigit((int)*tok[3]) || !isdigit((int)*tok[8])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ buf->size = atoi(tok[8]) * 1024;
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[4],' ')) {
+ tok[4] = talloc_strdup(frame,"STDIN");
+ if (!tok[4]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ /* only take the last part of the filename */
+ {
+ char *p = strrchr_m(tok[4],'/');
+ if (p) {
+ tok[4] = p+1;
+ }
+ }
+
+ buf->sysjob = atoi(tok[3]);
+ buf->status = strequal(tok[2],"RUNNING")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[5]);
+ fstrcpy(buf->fs_file,tok[4]);
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/****************************************************************************
+parse a lpq line
+here is an example of lpq output under hpux; note there's no space after -o !
+$> lpstat -oljplus
+ljplus-2153 user priority 0 Jan 19 08:14 on ljplus
+ util.c 125697 bytes
+ server.c 110712 bytes
+ljplus-2154 user priority 0 Jan 19 08:14 from client
+ (standard input) 7551 bytes
+****************************************************************************/
+
+static bool parse_lpq_hpux(char *line, print_queue_struct *buf, bool first)
+{
+ /* must read two lines to process, therefore keep some values static */
+ static bool header_line_ok=False, base_prio_reset=False;
+ static char *jobuser;
+ static int jobid;
+ static int jobprio;
+ static time_t jobtime;
+ static int jobstat=LPQ_QUEUED;
+ /* to store minimum priority to print, lpstat command should be invoked
+ with -p option first, to work */
+ static int base_prio;
+ int count;
+ char htab = '\011';
+ const char *cline = line;
+ char *tok[12];
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* If a line begins with a horizontal TAB, it is a subline type */
+
+ if (line[0] == htab) { /* subline */
+ /* check if it contains the base priority */
+ if (!strncmp(line,"\tfence priority : ",18)) {
+ base_prio=atoi(&line[18]);
+ DEBUG(4, ("fence priority set at %d\n", base_prio));
+ }
+
+ if (!header_line_ok) {
+ TALLOC_FREE(frame);
+ return False; /* incorrect header line */
+ }
+
+ /* handle the case of "(standard input)" as a filename */
+ string_sub(line,"standard input","STDIN",0);
+ all_string_sub(line,"(","\"",0);
+ all_string_sub(line,")","\"",0);
+
+ for (count=0; count<2 &&
+ next_token_talloc(frame, &cline, &tok[count],NULL);
+ count++) {
+ ;
+ }
+ /* we must get 2 tokens */
+ if (count < 2) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 2nd column must be integer */
+ if (!isdigit((int)*tok[1])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[0],' ')) {
+ tok[0] = talloc_strdup(frame, "STDIN");
+ if (!tok[0]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ buf->size = atoi(tok[1]);
+ fstrcpy(buf->fs_file,tok[0]);
+
+ /* fill things from header line */
+ buf->time = jobtime;
+ buf->sysjob = jobid;
+ buf->status = jobstat;
+ buf->priority = jobprio;
+ if (jobuser) {
+ fstrcpy(buf->fs_user,jobuser);
+ } else {
+ buf->fs_user[0] = '\0';
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+ } else { /* header line */
+ header_line_ok=False; /* reset it */
+ if (first) {
+ if (!base_prio_reset) {
+ base_prio=0; /* reset it */
+ base_prio_reset=True;
+ }
+ } else if (base_prio) {
+ base_prio_reset=False;
+ }
+
+ /* handle the dash in the job id */
+ string_sub(line,"-"," ",0);
+
+ for (count=0; count<12 &&
+ next_token_talloc(frame, &cline, &tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 8 tokens */
+ if (count < 8) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* first token must be printer name (cannot check ?) */
+ /* the 2nd, 5th & 7th column must be integer */
+ if (!isdigit((int)*tok[1]) || !isdigit((int)*tok[4]) || !isdigit((int)*tok[6])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ jobid = atoi(tok[1]);
+ SAFE_FREE(jobuser);
+ jobuser = SMB_STRDUP(tok[2]);
+ jobprio = atoi(tok[4]);
+
+ /* process time */
+ jobtime=EntryTime(tok, 5, count, 8);
+ if (jobprio < base_prio) {
+ jobstat = LPQ_PAUSED;
+ DEBUG (4, ("job %d is paused: prio %d < %d; jobstat=%d\n",
+ jobid, jobprio, base_prio, jobstat));
+ } else {
+ jobstat = LPQ_QUEUED;
+ if ((count >8) && (((strequal(tok[8],"on")) ||
+ ((strequal(tok[8],"from")) &&
+ ((count > 10)&&(strequal(tok[10],"on"))))))) {
+ jobstat = LPQ_PRINTING;
+ }
+ }
+
+ header_line_ok=True; /* information is correct */
+ TALLOC_FREE(frame);
+ return False; /* need subline info to include into queuelist */
+ }
+}
+
+/****************************************************************************
+parse a lpstat line
+
+here is an example of "lpstat -o dcslw" output under sysv
+
+dcslw-896 tridge 4712 Dec 20 10:30:30 on dcslw
+dcslw-897 tridge 4712 Dec 20 10:30:30 being held
+
+****************************************************************************/
+
+static bool parse_lpq_sysv(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[9];
+ int count=0;
+ char *p;
+ const char *cline = line;
+ TALLOC_CTX *frame = NULL;
+
+ /*
+ * Handle the dash in the job id, but make sure that we skip over
+ * the printer name in case we have a dash in that.
+ * Patch from Dom.Mitchell@palmerharvey.co.uk.
+ */
+
+ /*
+ * Move to the first space.
+ */
+ for (p = line ; !isspace(*p) && *p; p++) {
+ ;
+ }
+
+ /*
+ * Back up until the last '-' character or
+ * start of line.
+ */
+ for (; (p >= line) && (*p != '-'); p--) {
+ ;
+ }
+
+ if((p >= line) && (*p == '-')) {
+ *p = ' ';
+ }
+
+ frame = talloc_stackframe();
+ for (count=0; count<9 &&
+ next_token_talloc(frame, &cline, &tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 7 tokens */
+ if (count < 7) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 2nd and 4th, 6th columns must be integer */
+ if (!isdigit((int)*tok[1]) || !isdigit((int)*tok[3])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ if (!isdigit((int)*tok[5])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* if the user contains a ! then trim the first part of it */
+ if ((p=strchr_m(tok[2],'!'))) {
+ tok[2] = p+1;
+ }
+
+ buf->sysjob = atoi(tok[1]);
+ buf->size = atoi(tok[3]);
+ if (count > 7 && strequal(tok[7],"on")) {
+ buf->status = LPQ_PRINTING;
+ } else if (count > 8 && strequal(tok[7],"being") && strequal(tok[8],"held")) {
+ buf->status = LPQ_PAUSED;
+ } else {
+ buf->status = LPQ_QUEUED;
+ }
+ buf->priority = 0;
+ buf->time = EntryTime(tok, 4, count, 7);
+ fstrcpy(buf->fs_user,tok[2]);
+ fstrcpy(buf->fs_file,tok[2]);
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/****************************************************************************
+parse a lpq line
+
+here is an example of lpq output under qnx
+Spooler: /qnx/spooler, on node 1
+Printer: txt (ready)
+0000: root [job #1 ] active 1146 bytes /etc/profile
+0001: root [job #2 ] ready 2378 bytes /etc/install
+0002: root [job #3 ] ready 1146 bytes -- standard input --
+****************************************************************************/
+
+static bool parse_lpq_qnx(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[7];
+ int count=0;
+ const char *cline = line;
+ TALLOC_CTX *frame = NULL;
+
+ DEBUG(4,("antes [%s]\n", line));
+
+ /* handle the case of "-- standard input --" as a filename */
+ string_sub(line,"standard input","STDIN",0);
+ DEBUG(4,("despues [%s]\n", line));
+ all_string_sub(line,"-- ","\"",0);
+ all_string_sub(line," --","\"",0);
+ DEBUG(4,("despues 1 [%s]\n", line));
+
+ string_sub(line,"[job #","",0);
+ string_sub(line,"]","",0);
+ DEBUG(4,("despues 2 [%s]\n", line));
+
+ frame = talloc_stackframe();
+ for (count=0; count<7 &&
+ next_token_talloc(frame,&cline,&tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 7 tokens */
+ if (count < 7) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 3rd and 5th columns must be integer */
+ if (!isdigit((int)*tok[2]) || !isdigit((int)*tok[4])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* only take the last part of the filename */
+ {
+ char *p = strrchr_m(tok[6],'/');
+ if (p) {
+ tok[6] = p+1;
+ }
+ }
+
+ buf->sysjob = atoi(tok[2]);
+ buf->size = atoi(tok[4]);
+ buf->status = strequal(tok[3],"active")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[1]);
+ fstrcpy(buf->fs_file,tok[6]);
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/****************************************************************************
+ parse a lpq line for the plp printing system
+ Bertrand Wallrich <Bertrand.Wallrich@loria.fr>
+
+redone by tridge. Here is a sample queue:
+
+Local Printer 'lp2' (fjall):
+ Printing (started at Jun 15 13:33:58, attempt 1).
+ Rank Owner Pr Opt Job Host Files Size Date
+ active tridge X - 6 fjall /etc/hosts 739 Jun 15 13:33
+ 3rd tridge X - 7 fjall /etc/hosts 739 Jun 15 13:33
+
+****************************************************************************/
+
+static bool parse_lpq_plp(char *line,print_queue_struct *buf,bool first)
+{
+ char *tok[11];
+ int count=0;
+ const char *cline = line;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* handle the case of "(standard input)" as a filename */
+ string_sub(line,"stdin","STDIN",0);
+ all_string_sub(line,"(","\"",0);
+ all_string_sub(line,")","\"",0);
+
+ for (count=0; count<11 &&
+ next_token_talloc(frame,&cline,&tok[count],NULL);
+ count++) {
+ ;
+ }
+
+ /* we must get 11 tokens */
+ if (count < 11) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the first must be "active" or begin with an integer */
+ if (strcmp(tok[0],"active") && !isdigit((int)tok[0][0])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* the 5th and 8th must be integer */
+ if (!isdigit((int)*tok[4]) || !isdigit((int)*tok[7])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* if the fname contains a space then use STDIN */
+ if (strchr_m(tok[6],' ')) {
+ tok[6] = talloc_strdup(frame, "STDIN");
+ if (!tok[6]) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+
+ /* only take the last part of the filename */
+ {
+ fstring tmp;
+ char *p = strrchr_m(tok[6],'/');
+ if (p) {
+ size_t len = strlen(tok[6])+1;
+ fstrcpy(tmp,p+1);
+ strlcpy(tok[6],tmp, len);
+ }
+ }
+
+ buf->sysjob = atoi(tok[4]);
+
+ buf->size = atoi(tok[7]);
+ if (strchr_m(tok[7],'K')) {
+ buf->size *= 1024;
+ }
+ if (strchr_m(tok[7],'M')) {
+ buf->size *= 1024*1024;
+ }
+
+ buf->status = strequal(tok[0],"active")?LPQ_PRINTING:LPQ_QUEUED;
+ buf->priority = 0;
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user,tok[1]);
+ fstrcpy(buf->fs_file,tok[6]);
+ TALLOC_FREE(frame);
+ return True;
+}
+
+/*******************************************************************
+parse lpq on an NT system
+
+ Windows 2000 LPD Server
+ Printer \\10.0.0.2\NP17PCL (Paused)
+
+Owner Status Jobname Job-Id Size Pages Priority
+----------------------------------------------------------------------------
+root (9.99. Printing /usr/lib/rhs/rhs-pr 3 625 0 1
+root (9.99. Paused /usr/lib/rhs/rhs-pr 4 625 0 1
+jmcd Waiting Re: Samba Open Sour 26 32476 1 1
+
+********************************************************************/
+
+static bool parse_lpq_nt(char *line,print_queue_struct *buf,bool first)
+{
+#define LPRNT_OWNSIZ 11
+#define LPRNT_STATSIZ 9
+#define LPRNT_JOBSIZ 19
+#define LPRNT_IDSIZ 6
+#define LPRNT_SIZSIZ 9
+ typedef struct {
+ char owner[LPRNT_OWNSIZ];
+ char space1;
+ char status[LPRNT_STATSIZ];
+ char space2;
+ char jobname[LPRNT_JOBSIZ];
+ char space3;
+ char jobid[LPRNT_IDSIZ];
+ char space4;
+ char size[LPRNT_SIZSIZ];
+ char terminator;
+ } nt_lpq_line;
+
+ char parse_line_char[sizeof(nt_lpq_line)];
+ nt_lpq_line *parse_line = (nt_lpq_line *)parse_line_char;
+#define LPRNT_PRINTING "Printing"
+#define LPRNT_WAITING "Waiting"
+#define LPRNT_PAUSED "Paused"
+
+ memset(parse_line_char, '\0', sizeof(parse_line_char));
+ strncpy(parse_line_char, line, sizeof(parse_line_char) -1);
+
+ if (strlen(parse_line_char) != sizeof(parse_line_char) - 1) {
+ return False;
+ }
+
+ /* Just want the first word in the owner field - the username */
+ if (strchr_m(parse_line->owner, ' ')) {
+ *(strchr_m(parse_line->owner, ' ')) = '\0';
+ } else {
+ parse_line->space1 = '\0';
+ }
+
+ /* Make sure we have an owner */
+ if (!strlen(parse_line->owner)) {
+ return False;
+ }
+
+ /* Make sure the status is valid */
+ parse_line->space2 = '\0';
+ trim_char(parse_line->status, '\0', ' ');
+ if (!strequal(parse_line->status, LPRNT_PRINTING) &&
+ !strequal(parse_line->status, LPRNT_PAUSED) &&
+ !strequal(parse_line->status, LPRNT_WAITING)) {
+ return False;
+ }
+
+ parse_line->space3 = '\0';
+ trim_char(parse_line->jobname, '\0', ' ');
+
+ buf->sysjob = atoi(parse_line->jobid);
+ buf->priority = 0;
+ buf->size = atoi(parse_line->size);
+ buf->time = time(NULL);
+ fstrcpy(buf->fs_user, parse_line->owner);
+ fstrcpy(buf->fs_file, parse_line->jobname);
+ if (strequal(parse_line->status, LPRNT_PRINTING)) {
+ buf->status = LPQ_PRINTING;
+ } else if (strequal(parse_line->status, LPRNT_PAUSED)) {
+ buf->status = LPQ_PAUSED;
+ } else {
+ buf->status = LPQ_QUEUED;
+ }
+
+ return True;
+}
+
+/*******************************************************************
+parse lpq on an OS2 system
+
+JobID File Name Rank Size Status Comment
+----- --------------- ------ -------- ------------ ------------
+ 3 Control 1 68 Queued root@psflinu
+ 4 /etc/motd 2 11666 Queued root@psflinu
+
+********************************************************************/
+
+static bool parse_lpq_os2(char *line,print_queue_struct *buf,bool first)
+{
+#define LPROS2_IDSIZ 5
+#define LPROS2_JOBSIZ 15
+#define LPROS2_SIZSIZ 8
+#define LPROS2_STATSIZ 12
+#define LPROS2_OWNSIZ 12
+ typedef struct {
+ char jobid[LPROS2_IDSIZ];
+ char space1[2];
+ char jobname[LPROS2_JOBSIZ];
+ char space2[14];
+ char size[LPROS2_SIZSIZ];
+ char space3[4];
+ char status[LPROS2_STATSIZ];
+ char space4[4];
+ char owner[LPROS2_OWNSIZ];
+ char terminator;
+ } os2_lpq_line;
+
+ char parse_line_char[sizeof(os2_lpq_line)];
+ os2_lpq_line *parse_line = (os2_lpq_line *)parse_line_char;
+#define LPROS2_PRINTING "Printing"
+#define LPROS2_WAITING "Queued"
+#define LPROS2_PAUSED "Paused"
+
+ memset(parse_line_char, '\0', sizeof(parse_line_char));
+ strncpy(parse_line_char, line, sizeof(parse_line_char) -1);
+
+ if (strlen(parse_line_char) != sizeof(parse_line_char) - 1) {
+ return False;
+ }
+
+ /* Get the jobid */
+ buf->sysjob = atoi(parse_line->jobid);
+
+ /* Get the job name */
+ parse_line->space2[0] = '\0';
+ trim_char(parse_line->jobname, '\0', ' ');
+ fstrcpy(buf->fs_file, parse_line->jobname);
+
+ buf->priority = 0;
+ buf->size = atoi(parse_line->size);
+ buf->time = time(NULL);
+
+ /* Make sure we have an owner */
+ if (!strlen(parse_line->owner)) {
+ return False;
+ }
+
+ /* Make sure we have a valid status */
+ parse_line->space4[0] = '\0';
+ trim_char(parse_line->status, '\0', ' ');
+ if (!strequal(parse_line->status, LPROS2_PRINTING) &&
+ !strequal(parse_line->status, LPROS2_PAUSED) &&
+ !strequal(parse_line->status, LPROS2_WAITING)) {
+ return False;
+ }
+
+ fstrcpy(buf->fs_user, parse_line->owner);
+ if (strequal(parse_line->status, LPROS2_PRINTING)) {
+ buf->status = LPQ_PRINTING;
+ } else if (strequal(parse_line->status, LPROS2_PAUSED)) {
+ buf->status = LPQ_PAUSED;
+ } else {
+ buf->status = LPQ_QUEUED;
+ }
+
+ return True;
+}
+
+static const char *stat0_strings[] = { "enabled", "online", "idle", "no entries", "free", "ready", NULL };
+static const char *stat1_strings[] = { "offline", "disabled", "down", "off", "waiting", "no daemon", NULL };
+static const char *stat2_strings[] = { "jam", "paper", "error", "responding", "not accepting", "not running", "turned off", NULL };
+
+#ifdef DEVELOPER
+
+/****************************************************************************
+parse a vlp line
+****************************************************************************/
+
+static bool parse_lpq_vlp(char *line,print_queue_struct *buf,bool first)
+{
+ int toknum = 0;
+ char *tok;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *cline = line;
+
+ /* First line is printer status */
+
+ if (!isdigit(line[0])) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+
+ /* Parse a print job entry */
+
+ while(next_token_talloc(frame, &cline, &tok, NULL)) {
+ switch (toknum) {
+ case 0:
+ buf->sysjob = atoi(tok);
+ break;
+ case 1:
+ buf->size = atoi(tok);
+ break;
+ case 2:
+ buf->status = atoi(tok);
+ break;
+ case 3:
+ buf->time = atoi(tok);
+ break;
+ case 4:
+ fstrcpy(buf->fs_user, tok);
+ break;
+ case 5:
+ fstrcpy(buf->fs_file, tok);
+ break;
+ }
+ toknum++;
+ }
+
+ TALLOC_FREE(frame);
+ return True;
+}
+
+#endif /* DEVELOPER */
+
+/****************************************************************************
+parse a lpq line. Choose printing style
+****************************************************************************/
+
+bool parse_lpq_entry(enum printing_types printing_type,char *line,
+ print_queue_struct *buf,
+ print_status_struct *status,bool first)
+{
+ bool ret;
+
+ switch (printing_type) {
+ case PRINT_SYSV:
+ ret = parse_lpq_sysv(line,buf,first);
+ break;
+ case PRINT_AIX:
+ ret = parse_lpq_aix(line,buf,first);
+ break;
+ case PRINT_HPUX:
+ ret = parse_lpq_hpux(line,buf,first);
+ break;
+ case PRINT_QNX:
+ ret = parse_lpq_qnx(line,buf,first);
+ break;
+ case PRINT_LPRNG:
+ ret = parse_lpq_lprng(line,buf,first);
+ break;
+ case PRINT_PLP:
+ ret = parse_lpq_plp(line,buf,first);
+ break;
+ case PRINT_LPRNT:
+ ret = parse_lpq_nt(line,buf,first);
+ break;
+ case PRINT_LPROS2:
+ ret = parse_lpq_os2(line,buf,first);
+ break;
+#ifdef DEVELOPER
+ case PRINT_VLP:
+ case PRINT_TEST:
+ ret = parse_lpq_vlp(line,buf,first);
+ break;
+#endif /* DEVELOPER */
+ default:
+ ret = parse_lpq_bsd(line,buf,first);
+ break;
+ }
+
+ /* We don't want the newline in the status message. */
+ {
+ char *p = strchr_m(line,'\n');
+ if (p) {
+ *p = 0;
+ }
+ }
+
+ /* in the LPRNG case, we skip lines starting by a space.*/
+ if (!ret && (printing_type==PRINT_LPRNG) ) {
+ if (line[0]==' ') {
+ return ret;
+ }
+ }
+
+ if (status && !ret) {
+ /* a few simple checks to see if the line might be a
+ printer status line:
+ handle them so that most severe condition is shown */
+ int i;
+ if (!strlower_m(line)) {
+ return false;
+ }
+
+ switch (status->status) {
+ case LPSTAT_OK:
+ for (i=0; stat0_strings[i]; i++) {
+ if (strstr_m(line,stat0_strings[i])) {
+ fstrcpy(status->message,line);
+ status->status=LPSTAT_OK;
+ return ret;
+ }
+ }
+ FALL_THROUGH;
+ case LPSTAT_STOPPED:
+ for (i=0; stat1_strings[i]; i++) {
+ if (strstr_m(line,stat1_strings[i])) {
+ fstrcpy(status->message,line);
+ status->status=LPSTAT_STOPPED;
+ return ret;
+ }
+ }
+ FALL_THROUGH;
+ case LPSTAT_ERROR:
+ for (i=0; stat2_strings[i]; i++) {
+ if (strstr_m(line,stat2_strings[i])) {
+ fstrcpy(status->message,line);
+ status->status=LPSTAT_ERROR;
+ return ret;
+ }
+ }
+ break;
+ }
+ }
+
+ return ret;
+}
+
diff --git a/source3/printing/notify.c b/source3/printing/notify.c
new file mode 100644
index 0000000..8312b0b
--- /dev/null
+++ b/source3/printing/notify.c
@@ -0,0 +1,693 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Tim Potter, 2002
+ Copyright (C) Gerald Carter, 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "../librpc/gen_ndr/spoolss.h"
+#include "nt_printing.h"
+#include "printing/notify.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+static TALLOC_CTX *send_ctx;
+
+static unsigned int num_messages;
+
+static struct notify_queue {
+ struct notify_queue *next, *prev;
+ struct spoolss_notify_msg *msg;
+ struct timeval tv;
+ uint8_t *buf;
+ size_t buflen;
+} *notify_queue_head = NULL;
+
+static struct tevent_timer *notify_event;
+
+static bool print_notify_pid_list(const char *printername, TALLOC_CTX *mem_ctx,
+ size_t *p_num_pids, pid_t **pp_pid_list);
+
+static bool create_send_ctx(void)
+{
+ if (!send_ctx)
+ send_ctx = talloc_init("print notify queue");
+
+ if (!send_ctx)
+ return False;
+
+ return True;
+}
+
+/****************************************************************************
+ Turn a queue name into a snum.
+****************************************************************************/
+
+int print_queue_snum(const char *qname)
+{
+ int snum = lp_servicenumber(qname);
+ if (snum == -1 || !lp_printable(snum))
+ return -1;
+ return snum;
+}
+
+/*******************************************************************
+ Used to decide if we need a short select timeout.
+*******************************************************************/
+
+static bool print_notify_messages_pending(void)
+{
+ return (notify_queue_head != NULL);
+}
+
+/*******************************************************************
+ Flatten data into a message.
+*******************************************************************/
+
+static bool flatten_message(struct notify_queue *q)
+{
+ struct spoolss_notify_msg *msg = q->msg;
+ uint8_t *buf = NULL;
+ size_t buflen = 0, len;
+
+again:
+ len = 0;
+
+ /* Pack header */
+
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "f", msg->printer);
+
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "ddddddd",
+ (uint32_t)q->tv.tv_sec, (uint32_t)q->tv.tv_usec,
+ msg->type, msg->field, msg->id, msg->len, msg->flags);
+
+ /* Pack data */
+
+ if (msg->len == 0)
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "dd",
+ msg->notify.value[0], msg->notify.value[1]);
+ else
+ len += tdb_pack(buf ? buf + len : NULL,
+ buf ? buflen - len : 0, "B",
+ msg->len, msg->notify.data);
+
+ if (buflen != len) {
+ buf = (uint8_t *)TALLOC_REALLOC(send_ctx, buf, len);
+ if (!buf)
+ return False;
+ buflen = len;
+ goto again;
+ }
+
+ q->buf = buf;
+ q->buflen = buflen;
+
+ return True;
+}
+
+/*******************************************************************
+ Send the batched messages - on a per-printer basis.
+*******************************************************************/
+
+static void print_notify_send_messages_to_printer(struct messaging_context *msg_ctx,
+ const char *printer,
+ unsigned int timeout)
+{
+ char *buf;
+ struct notify_queue *pq, *pq_next;
+ size_t msg_count = 0, offset = 0;
+ size_t num_pids = 0;
+ size_t i;
+ pid_t *pid_list = NULL;
+ struct timeval end_time = timeval_zero();
+
+ /* Count the space needed to send the messages. */
+ for (pq = notify_queue_head; pq; pq = pq->next) {
+ if (strequal(printer, pq->msg->printer)) {
+ if (!flatten_message(pq)) {
+ DEBUG(0,("print_notify_send_messages: Out of memory\n"));
+ talloc_free_children(send_ctx);
+ num_messages = 0;
+ return;
+ }
+ offset += (pq->buflen + 4);
+ msg_count++;
+ }
+ }
+ offset += 4; /* For count. */
+
+ buf = (char *)TALLOC(send_ctx, offset);
+ if (!buf) {
+ DEBUG(0,("print_notify_send_messages: Out of memory\n"));
+ talloc_free_children(send_ctx);
+ num_messages = 0;
+ return;
+ }
+
+ offset = 0;
+ SIVAL(buf,offset,msg_count);
+ offset += 4;
+ for (pq = notify_queue_head; pq; pq = pq_next) {
+ pq_next = pq->next;
+
+ if (strequal(printer, pq->msg->printer)) {
+ SIVAL(buf,offset,pq->buflen);
+ offset += 4;
+ memcpy(buf + offset, pq->buf, pq->buflen);
+ offset += pq->buflen;
+
+ /* Remove from list. */
+ DLIST_REMOVE(notify_queue_head, pq);
+ }
+ }
+
+ DEBUG(5, ("print_notify_send_messages_to_printer: sending %lu print notify message%s to printer %s\n",
+ (unsigned long)msg_count, msg_count != 1 ? "s" : "", printer));
+
+ /*
+ * Get the list of PID's to send to.
+ */
+
+ if (!print_notify_pid_list(printer, send_ctx, &num_pids, &pid_list))
+ return;
+
+ if (timeout != 0) {
+ end_time = timeval_current_ofs(timeout, 0);
+ }
+
+ for (i = 0; i < num_pids; i++) {
+ messaging_send_buf(msg_ctx,
+ pid_to_procid(pid_list[i]),
+ MSG_PRINTER_NOTIFY2 | MSG_FLAG_LOWPRIORITY,
+ (uint8_t *)buf, offset);
+
+ if ((timeout != 0) && timeval_expired(&end_time)) {
+ break;
+ }
+ }
+}
+
+/*******************************************************************
+ Actually send the batched messages.
+*******************************************************************/
+
+void print_notify_send_messages(struct messaging_context *msg_ctx,
+ unsigned int timeout)
+{
+ if (!print_notify_messages_pending())
+ return;
+
+ if (!create_send_ctx())
+ return;
+
+ while (print_notify_messages_pending())
+ print_notify_send_messages_to_printer(
+ msg_ctx, notify_queue_head->msg->printer, timeout);
+
+ talloc_free_children(send_ctx);
+ num_messages = 0;
+}
+
+/*******************************************************************
+ Event handler to send the messages.
+*******************************************************************/
+
+static void print_notify_event_send_messages(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct messaging_context *msg_ctx = talloc_get_type_abort(
+ private_data, struct messaging_context);
+ /* Remove this timed event handler. */
+ TALLOC_FREE(notify_event);
+
+ change_to_root_user();
+ print_notify_send_messages(msg_ctx, 0);
+}
+
+/**********************************************************************
+ deep copy a SPOOLSS_NOTIFY_MSG structure using a TALLOC_CTX
+ *********************************************************************/
+
+static bool copy_notify2_msg( SPOOLSS_NOTIFY_MSG *to, SPOOLSS_NOTIFY_MSG *from )
+{
+
+ if ( !to || !from )
+ return False;
+
+ memcpy( to, from, sizeof(SPOOLSS_NOTIFY_MSG) );
+
+ if ( from->len ) {
+ to->notify.data = (char *)talloc_memdup(send_ctx, from->notify.data, from->len );
+ if ( !to->notify.data ) {
+ DEBUG(0,("copy_notify2_msg: talloc_memdup() of size [%d] failed!\n", from->len ));
+ return False;
+ }
+ }
+
+
+ return True;
+}
+
+/*******************************************************************
+ Batch up print notify messages.
+*******************************************************************/
+
+static void send_spoolss_notify2_msg(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ SPOOLSS_NOTIFY_MSG *msg)
+{
+ struct notify_queue *pnqueue, *tmp_ptr;
+
+ /*
+ * Ensure we only have one job total_bytes and job total_pages for
+ * each job. There is no point in sending multiple messages that match
+ * as they will just cause flickering updates in the client.
+ */
+
+ if ((num_messages < 100) && (msg->type == JOB_NOTIFY_TYPE)
+ && (msg->field == JOB_NOTIFY_FIELD_TOTAL_BYTES
+ || msg->field == JOB_NOTIFY_FIELD_TOTAL_PAGES ))
+ {
+
+ for (tmp_ptr = notify_queue_head; tmp_ptr; tmp_ptr = tmp_ptr->next)
+ {
+ if (tmp_ptr->msg->type == msg->type &&
+ tmp_ptr->msg->field == msg->field &&
+ tmp_ptr->msg->id == msg->id &&
+ tmp_ptr->msg->flags == msg->flags &&
+ strequal(tmp_ptr->msg->printer, msg->printer)) {
+
+ DEBUG(5,("send_spoolss_notify2_msg: replacing message 0x%02x/0x%02x for "
+ "printer %s in notify_queue\n", msg->type, msg->field, msg->printer));
+
+ tmp_ptr->msg = msg;
+ return;
+ }
+ }
+ }
+
+ /* Store the message on the pending queue. */
+
+ pnqueue = talloc(send_ctx, struct notify_queue);
+ if (!pnqueue) {
+ DEBUG(0,("send_spoolss_notify2_msg: Out of memory.\n"));
+ return;
+ }
+
+ /* allocate a new msg structure and copy the fields */
+
+ if ( !(pnqueue->msg = talloc(send_ctx, SPOOLSS_NOTIFY_MSG)) ) {
+ DEBUG(0,("send_spoolss_notify2_msg: talloc() of size [%lu] failed!\n",
+ (unsigned long)sizeof(SPOOLSS_NOTIFY_MSG)));
+ return;
+ }
+ copy_notify2_msg(pnqueue->msg, msg);
+ GetTimeOfDay(&pnqueue->tv);
+ pnqueue->buf = NULL;
+ pnqueue->buflen = 0;
+
+ DEBUG(5, ("send_spoolss_notify2_msg: appending message 0x%02x/0x%02x for printer %s \
+to notify_queue_head\n", msg->type, msg->field, msg->printer));
+
+ /*
+ * Note we add to the end of the list to ensure
+ * the messages are sent in the order they were received. JRA.
+ */
+
+ DLIST_ADD_END(notify_queue_head, pnqueue);
+ num_messages++;
+
+ if ((notify_event == NULL) && (ev != NULL)) {
+ /* Add an event for 1 second's time to send this queue. */
+ notify_event = tevent_add_timer(
+ ev, NULL, timeval_current_ofs(1,0),
+ print_notify_event_send_messages, msg_ctx);
+ }
+
+}
+
+static void send_notify_field_values(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t type,
+ uint32_t field, uint32_t id, uint32_t value1,
+ uint32_t value2, uint32_t flags)
+{
+ struct spoolss_notify_msg *msg;
+
+ if (lp_disable_spoolss())
+ return;
+
+ if (!create_send_ctx())
+ return;
+
+ msg = talloc_zero(send_ctx, struct spoolss_notify_msg);
+ if (!msg)
+ return;
+
+ fstrcpy(msg->printer, sharename);
+ msg->type = type;
+ msg->field = field;
+ msg->id = id;
+ msg->notify.value[0] = value1;
+ msg->notify.value[1] = value2;
+ msg->flags = flags;
+
+ send_spoolss_notify2_msg(ev, msg_ctx, msg);
+}
+
+static void send_notify_field_buffer(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t type,
+ uint32_t field, uint32_t id, uint32_t len,
+ const char *buffer)
+{
+ struct spoolss_notify_msg *msg;
+
+ if (lp_disable_spoolss())
+ return;
+
+ if (!create_send_ctx())
+ return;
+
+ msg = talloc_zero(send_ctx, struct spoolss_notify_msg);
+ if (!msg)
+ return;
+
+ fstrcpy(msg->printer, sharename);
+ msg->type = type;
+ msg->field = field;
+ msg->id = id;
+ msg->len = len;
+ msg->notify.data = discard_const_p(char, buffer);
+
+ send_spoolss_notify2_msg(ev, msg_ctx, msg);
+}
+
+/* Send a message that the printer status has changed */
+
+void notify_printer_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t status)
+{
+ /* Printer status stored in value1 */
+
+ int snum = print_queue_snum(sharename);
+
+ send_notify_field_values(ev, msg_ctx, sharename, PRINTER_NOTIFY_TYPE,
+ PRINTER_NOTIFY_FIELD_STATUS, snum,
+ status, 0, 0);
+}
+
+void notify_printer_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t status)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ if (sharename)
+ notify_printer_status_byname(ev, msg_ctx, sharename, status);
+}
+
+void notify_job_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t status,
+ uint32_t flags)
+{
+ /* Job id stored in id field, status in value1 */
+
+ send_notify_field_values(ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE,
+ JOB_NOTIFY_FIELD_STATUS, jobid,
+ status, 0, flags);
+}
+
+void notify_job_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, uint32_t status)
+{
+ notify_job_status_byname(ev, msg_ctx, sharename, jobid, status, 0);
+}
+
+void notify_job_total_bytes(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t size)
+{
+ /* Job id stored in id field, status in value1 */
+
+ send_notify_field_values(ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE,
+ JOB_NOTIFY_FIELD_TOTAL_BYTES, jobid,
+ size, 0, 0);
+}
+
+void notify_job_total_pages(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t pages)
+{
+ /* Job id stored in id field, status in value1 */
+
+ send_notify_field_values(ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE,
+ JOB_NOTIFY_FIELD_TOTAL_PAGES, jobid,
+ pages, 0, 0);
+}
+
+void notify_job_username(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name)
+{
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_USER_NAME,
+ jobid, strlen(name) + 1, name);
+}
+
+void notify_job_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name)
+{
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DOCUMENT,
+ jobid, strlen(name) + 1, name);
+}
+
+void notify_job_submitted(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ time_t submitted)
+{
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_SUBMITTED,
+ jobid, sizeof(submitted), (char *)&submitted);
+}
+
+void notify_printer_driver(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *driver_name)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DRIVER_NAME,
+ snum, strlen(driver_name) + 1, driver_name);
+}
+
+void notify_printer_comment(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *comment)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_COMMENT,
+ snum, strlen(comment) + 1, comment);
+}
+
+void notify_printer_sharename(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *share_name)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SHARE_NAME,
+ snum, strlen(share_name) + 1, share_name);
+}
+
+void notify_printer_printername(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *printername)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRINTER_NAME,
+ snum, strlen(printername) + 1, printername);
+}
+
+void notify_printer_port(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *port_name)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PORT_NAME,
+ snum, strlen(port_name) + 1, port_name);
+}
+
+void notify_printer_location(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *location)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_LOCATION,
+ snum, strlen(location) + 1, location);
+}
+
+void notify_printer_sepfile(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *sepfile)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ sharename, PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SEPFILE,
+ snum, strlen(sepfile) + 1, sepfile);
+}
+
+
+void notify_printer_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *printername, uint32_t change,
+ const char *value)
+{
+ int snum = print_queue_snum(printername);
+ int type = PRINTER_NOTIFY_TYPE;
+
+ if ( snum == -1 )
+ return;
+
+ send_notify_field_buffer(
+ ev, msg_ctx,
+ printername, type, change, snum, strlen(value)+1, value );
+}
+
+
+/****************************************************************************
+ Return a malloced list of pid_t's that are interested in getting update
+ messages on this print queue. Used in printing/notify to send the messages.
+****************************************************************************/
+
+static bool print_notify_pid_list(const char *printername, TALLOC_CTX *mem_ctx,
+ size_t *p_num_pids, pid_t **pp_pid_list)
+{
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ TDB_DATA data;
+ bool ret = True;
+ size_t i, num_pids, offset;
+ pid_t *pid_list;
+
+ *p_num_pids = 0;
+ *pp_pid_list = NULL;
+
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+
+ if (tdb_read_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_pid_list: Failed to lock printer %s database\n",
+ printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ if (!data.dptr) {
+ ret = True;
+ goto done;
+ }
+
+ num_pids = data.dsize / 8;
+
+ if (num_pids) {
+ if ((pid_list = talloc_array(mem_ctx, pid_t, num_pids)) == NULL) {
+ ret = False;
+ goto done;
+ }
+ } else {
+ pid_list = NULL;
+ }
+
+ for( i = 0, offset = 0; i < num_pids; offset += 8, i++)
+ pid_list[i] = (pid_t)IVAL(data.dptr, offset);
+
+ *pp_pid_list = pid_list;
+ *p_num_pids = num_pids;
+
+ ret = True;
+
+ done:
+
+ tdb_read_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
diff --git a/source3/printing/notify.h b/source3/printing/notify.h
new file mode 100644
index 0000000..427bbf0
--- /dev/null
+++ b/source3/printing/notify.h
@@ -0,0 +1,87 @@
+#ifndef _PRINTING_NOTIFY_H_
+#define _PRINTING_NOTIFY_H_
+
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Tim Potter, 2002
+ Copyright (C) Gerald Carter, 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* The following definitions come from printing/notify.c */
+
+int print_queue_snum(const char *qname);
+void print_notify_send_messages(struct messaging_context *msg_ctx,
+ unsigned int timeout);
+void notify_printer_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t status);
+void notify_printer_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t status);
+void notify_job_status_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t status,
+ uint32_t flags);
+void notify_job_status(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, uint32_t status);
+void notify_job_total_bytes(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t size);
+void notify_job_total_pages(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ uint32_t pages);
+void notify_job_username(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name);
+void notify_job_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, char *name);
+void notify_job_submitted(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid,
+ time_t submitted);
+void notify_printer_driver(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *driver_name);
+void notify_printer_comment(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *comment);
+void notify_printer_sharename(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *share_name);
+void notify_printer_printername(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *printername);
+void notify_printer_port(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *port_name);
+void notify_printer_location(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *location);
+void notify_printer_byname(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *printername, uint32_t change,
+ const char *value);
+void notify_printer_sepfile(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, const char *sepfile);
+#endif
diff --git a/source3/printing/nt_printing.c b/source3/printing/nt_printing.c
new file mode 100644
index 0000000..7907fb4
--- /dev/null
+++ b/source3/printing/nt_printing.c
@@ -0,0 +1,2419 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing/nt_printing_tdb.h"
+#include "printing/queue_process.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "rpc_server/spoolss/srv_spoolss_util.h"
+#include "nt_printing.h"
+#include "secrets.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+#include "smbd/smbd.h"
+#include "auth.h"
+#include "messages.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+
+/* Map generic permissions to printer object specific permissions */
+
+const struct generic_mapping printer_generic_mapping = {
+ PRINTER_READ,
+ PRINTER_WRITE,
+ PRINTER_EXECUTE,
+ PRINTER_ALL_ACCESS
+};
+
+/* Map generic permissions to print server object specific permissions */
+
+const struct generic_mapping printserver_generic_mapping = {
+ SERVER_READ,
+ SERVER_WRITE,
+ SERVER_EXECUTE,
+ SERVER_ALL_ACCESS
+};
+
+/* Map generic permissions to job object specific permissions */
+
+const struct generic_mapping job_generic_mapping = {
+ JOB_READ,
+ JOB_WRITE,
+ JOB_EXECUTE,
+ JOB_ALL_ACCESS
+};
+
+static bool print_driver_directories_init(void)
+{
+ int service;
+ size_t i;
+ char *driver_path;
+ bool ok;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ const char *dir_list[] = {
+ "W32X86/PCC",
+ "x64/PCC",
+ "ARM64",
+ "color"
+ };
+
+ service = lp_servicenumber("print$");
+ if (service < 0) {
+ /* We don't have a print$ share */
+ DEBUG(5, ("No print$ share has been configured.\n"));
+ talloc_free(mem_ctx);
+ return true;
+ }
+
+ driver_path = lp_path(mem_ctx, lp_sub, service);
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1, ("Failed to create printer driver directory %s\n",
+ driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ for (i = 0; archi_table[i].long_archi != NULL; i++) {
+ const char *arch_path;
+
+ arch_path = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ driver_path,
+ archi_table[i].short_archi);
+ if (arch_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(arch_path, 0755);
+ if (!ok) {
+ DEBUG(1, ("Failed to create printer driver "
+ "architecture directory %s\n",
+ arch_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(dir_list); i++) {
+ const char *path;
+
+ path = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ driver_path,
+ dir_list[i]);
+ if (path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(path, 0755);
+ if (!ok) {
+ DEBUG(1, ("Failed to create printer driver "
+ "architecture directory %s\n",
+ path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+ }
+
+ driver_path = state_path(talloc_tos(), "DriverStore");
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1,("failed to create path %s\n", driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ driver_path = state_path(talloc_tos(), "DriverStore/FileRepository");
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1,("failed to create path %s\n", driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ driver_path = state_path(talloc_tos(), "DriverStore/Temp");
+ if (driver_path == NULL) {
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ ok = directory_create_or_exist(driver_path, 0755);
+ if (!ok) {
+ DEBUG(1,("failed to create path %s\n", driver_path));
+ talloc_free(mem_ctx);
+ return false;
+ }
+
+ talloc_free(mem_ctx);
+ return true;
+}
+
+/****************************************************************************
+ Forward a MSG_PRINTER_DRVUPGRADE message from another smbd to the
+ background lpq updater.
+****************************************************************************/
+
+static void forward_drv_upgrade_printer_msg(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ send_to_bgqd(msg, msg_type, data->data, data->length);
+}
+
+/****************************************************************************
+ Open the NT printing tdbs. Done once before fork().
+****************************************************************************/
+
+bool nt_printing_init(struct messaging_context *msg_ctx)
+{
+ WERROR win_rc;
+
+ if (!print_driver_directories_init()) {
+ return false;
+ }
+
+ if (!nt_printing_tdb_upgrade()) {
+ return false;
+ }
+
+ /*
+ * register callback to handle updating printers as new
+ * drivers are installed. Forwards to background lpq updater.
+ */
+ messaging_register(msg_ctx, NULL, MSG_PRINTER_DRVUPGRADE,
+ forward_drv_upgrade_printer_msg);
+
+ if ( lp_security() == SEC_ADS ) {
+ win_rc = check_published_printers(msg_ctx);
+ if (!W_ERROR_IS_OK(win_rc))
+ DEBUG(0, ("nt_printing_init: error checking published printers: %s\n", win_errstr(win_rc)));
+ }
+
+ return true;
+}
+
+/*******************************************************************
+ Function to allow filename parsing "the old way".
+********************************************************************/
+
+static NTSTATUS driver_unix_convert(connection_struct *conn,
+ const char *old_name,
+ struct files_struct **pdirfsp,
+ struct smb_filename **psmb_fname)
+{
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *name = talloc_strdup(ctx, old_name);
+
+ if (!name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ unix_format(name);
+ name = unix_clean_name(ctx, name);
+ if (!name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ trim_string(name,"/","/");
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ 0, /* ucf_flags */
+ 0, /* twrp */
+ pdirfsp,
+ psmb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Function to do the mapping between the long architecture name and
+ the short one.
+****************************************************************************/
+
+const char *get_short_archi(const char *long_archi)
+{
+ int i=-1;
+
+ DEBUG(107,("Getting architecture dependent directory\n"));
+ do {
+ i++;
+ } while ( (archi_table[i].long_archi!=NULL ) &&
+ strcasecmp_m(long_archi, archi_table[i].long_archi) );
+
+ if (archi_table[i].long_archi==NULL) {
+ DEBUGADD(10,("Unknown architecture [%s] !\n", long_archi));
+ return NULL;
+ }
+
+ /* this might be client code - but shouldn't this be an fstrcpy etc? */
+
+ DEBUGADD(108,("index: [%d]\n", i));
+ DEBUGADD(108,("long architecture: [%s]\n", archi_table[i].long_archi));
+ DEBUGADD(108,("short architecture: [%s]\n", archi_table[i].short_archi));
+
+ return archi_table[i].short_archi;
+}
+
+/****************************************************************************
+ Read data from fsp on the vfs.
+****************************************************************************/
+
+static ssize_t printing_pread_data(files_struct *fsp,
+ char *buf,
+ off_t *poff,
+ size_t byte_count)
+{
+ size_t total=0;
+ off_t in_pos = *poff;
+
+ /* Don't allow integer wrap on read. */
+ if (in_pos + byte_count < in_pos) {
+ return -1;
+ }
+
+ while (total < byte_count) {
+ ssize_t ret = read_file(fsp,
+ buf + total,
+ in_pos,
+ byte_count - total);
+
+ if (ret == 0) {
+ *poff = in_pos;
+ return total;
+ }
+ if (ret == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ return -1;
+ }
+ }
+ in_pos += ret;
+ total += ret;
+ }
+ *poff = in_pos;
+ return (ssize_t)total;
+}
+
+/****************************************************************************
+ Detect the major and minor version of a PE file.
+ Returns:
+
+ 1 if file is a PE file and we got version numbers,
+ 0 if this file is a PE file and we couldn't get the version numbers,
+ -1 on error.
+
+ NB. buf is passed into and freed inside this function. This is a
+ bad API design, but fixing this is a task for another day.
+****************************************************************************/
+
+static int handle_pe_file(files_struct *fsp,
+ off_t in_pos,
+ char *fname,
+ char *buf,
+ uint32_t *major,
+ uint32_t *minor)
+{
+ unsigned int i;
+ unsigned int num_sections;
+ unsigned int section_table_bytes;
+ ssize_t byte_count;
+ off_t rel_pos;
+ int ret = -1;
+
+ /* Just skip over optional header to get to section table */
+ rel_pos = SVAL(buf,PE_HEADER_OPTIONAL_HEADER_SIZE)-
+ (NE_HEADER_SIZE-PE_HEADER_SIZE);
+
+ if (in_pos + rel_pos < in_pos) {
+ /* Integer wrap. */
+ goto out;
+ }
+ in_pos = rel_pos + in_pos;
+
+ /* get the section table */
+ num_sections = SVAL(buf,PE_HEADER_NUMBER_OF_SECTIONS);
+
+ if (num_sections >= (UINT_MAX / PE_HEADER_SECT_HEADER_SIZE)) {
+ /* Integer wrap. */
+ goto out;
+ }
+
+ section_table_bytes = num_sections * PE_HEADER_SECT_HEADER_SIZE;
+ if (section_table_bytes == 0) {
+ goto out;
+ }
+
+ SAFE_FREE(buf);
+ buf = (char *)SMB_MALLOC(section_table_bytes);
+ if (buf == NULL) {
+ DBG_ERR("PE file [%s] section table malloc "
+ "failed bytes = %d\n",
+ fname,
+ section_table_bytes);
+ goto out;
+ }
+
+ byte_count = printing_pread_data(fsp, buf, &in_pos, section_table_bytes);
+ if (byte_count < section_table_bytes) {
+ DBG_NOTICE("PE file [%s] Section header too short, "
+ "bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ goto out;
+ }
+
+ /*
+ * Iterate the section table looking for
+ * the resource section ".rsrc"
+ */
+ for (i = 0; i < num_sections; i++) {
+ int sec_offset = i * PE_HEADER_SECT_HEADER_SIZE;
+
+ if (strcmp(".rsrc",
+ &buf[sec_offset+ PE_HEADER_SECT_NAME_OFFSET]) == 0) {
+ unsigned int section_pos = IVAL(buf,
+ sec_offset+
+ PE_HEADER_SECT_PTR_DATA_OFFSET);
+ unsigned int section_bytes = IVAL(buf,
+ sec_offset+
+ PE_HEADER_SECT_SIZE_DATA_OFFSET);
+
+ if (section_bytes == 0) {
+ goto out;
+ }
+
+ SAFE_FREE(buf);
+ buf=(char *)SMB_MALLOC(section_bytes);
+ if (buf == NULL) {
+ DBG_ERR("PE file [%s] version malloc "
+ "failed bytes = %d\n",
+ fname,
+ section_bytes);
+ goto out;
+ }
+
+ /*
+ * Read from the start of the .rsrc
+ * section info
+ */
+ in_pos = section_pos;
+
+ byte_count = printing_pread_data(fsp,
+ buf,
+ &in_pos,
+ section_bytes);
+ if (byte_count < section_bytes) {
+ DBG_NOTICE("PE file "
+ "[%s] .rsrc section too short, "
+ "bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ goto out;
+ }
+
+ if (section_bytes < VS_VERSION_INFO_UNICODE_SIZE) {
+ goto out;
+ }
+
+ for (i=0;
+ i< section_bytes - VS_VERSION_INFO_UNICODE_SIZE;
+ i++) {
+ /*
+ * Scan for 1st 3 unicoded bytes
+ * followed by word aligned magic
+ * value.
+ */
+ int mpos;
+ bool magic_match = false;
+
+ if (buf[i] == 'V' &&
+ buf[i+1] == '\0' &&
+ buf[i+2] == 'S') {
+ magic_match = true;
+ }
+
+ if (magic_match == false) {
+ continue;
+ }
+
+ /* Align to next long address */
+ mpos = (i + sizeof(VS_SIGNATURE)*2 +
+ 3) & 0xfffffffc;
+
+ if (IVAL(buf,mpos) == VS_MAGIC_VALUE) {
+ *major = IVAL(buf,
+ mpos+ VS_MAJOR_OFFSET);
+ *minor = IVAL(buf,
+ mpos+ VS_MINOR_OFFSET);
+
+ DBG_INFO("PE file [%s] Version = "
+ "%08x:%08x (%d.%d.%d.%d)\n",
+ fname,
+ *major,
+ *minor,
+ (*major>>16)&0xffff,
+ *major&0xffff,
+ (*minor>>16)&0xffff,
+ *minor&0xffff);
+ ret = 1;
+ goto out;
+ }
+ }
+ }
+ }
+
+ /* Version info not found, fall back to origin date/time */
+ DBG_DEBUG("PE file [%s] has no version info\n", fname);
+ ret = 0;
+
+ out:
+
+ SAFE_FREE(buf);
+ return ret;
+}
+
+/****************************************************************************
+ Detect the major and minor version of an NE file.
+ Returns:
+
+ 1 if file is an NE file and we got version numbers,
+ 0 if this file is an NE file and we couldn't get the version numbers,
+ -1 on error.
+
+ NB. buf is passed into and freed inside this function. This is a
+ bad API design, but fixing this is a task for another day.
+****************************************************************************/
+
+static int handle_ne_file(files_struct *fsp,
+ off_t in_pos,
+ char *fname,
+ char *buf,
+ uint32_t *major,
+ uint32_t *minor)
+{
+ unsigned int i;
+ ssize_t byte_count;
+ int ret = -1;
+
+ if (CVAL(buf,NE_HEADER_TARGET_OS_OFFSET) != NE_HEADER_TARGOS_WIN ) {
+ DBG_NOTICE("NE file [%s] wrong target OS = 0x%x\n",
+ fname,
+ CVAL(buf,NE_HEADER_TARGET_OS_OFFSET));
+ /*
+ * At this point, we assume the file is in error.
+ * It still could be something else besides a NE file,
+ * but it unlikely at this point.
+ */
+ goto out;
+ }
+
+ /* Allocate a bit more space to speed up things */
+ SAFE_FREE(buf);
+ buf=(char *)SMB_MALLOC(VS_NE_BUF_SIZE);
+ if (buf == NULL) {
+ DBG_ERR("NE file [%s] malloc failed bytes = %d\n",
+ fname,
+ PE_HEADER_SIZE);
+ goto out;
+ }
+
+ /*
+ * This is a HACK! I got tired of trying to sort through the
+ * messy 'NE' file format. If anyone wants to clean this up
+ * please have at it, but this works. 'NE' files will
+ * eventually fade away. JRR
+ */
+ byte_count = printing_pread_data(fsp, buf, &in_pos, VS_NE_BUF_SIZE);
+ while (byte_count > 0) {
+ /*
+ * Cover case that should not occur in a well
+ * formed 'NE' .dll file
+ */
+ if (byte_count-VS_VERSION_INFO_SIZE <= 0) {
+ break;
+ }
+
+ for(i=0; i<byte_count; i++) {
+ /*
+ * Fast skip past data that can't
+ * possibly match
+ */
+ if (buf[i] != 'V') {
+ byte_count = printing_pread_data(fsp,
+ buf,
+ &in_pos,
+ VS_NE_BUF_SIZE);
+ continue;
+ }
+
+ /*
+ * Potential match data crosses buf boundary,
+ * move it to beginning of buf, and fill the
+ * buf with as much as it will hold.
+ */
+ if (i>byte_count-VS_VERSION_INFO_SIZE) {
+ ssize_t amount_read;
+ ssize_t amount_unused = byte_count-i;
+
+ memmove(buf, &buf[i], amount_unused);
+ amount_read = printing_pread_data(fsp,
+ &buf[amount_unused],
+ &in_pos,
+ VS_NE_BUF_SIZE- amount_unused);
+ if (amount_read < 0) {
+ DBG_ERR("NE file [%s] Read "
+ "error, errno=%d\n",
+ fname,
+ errno);
+ goto out;
+ }
+
+ if (amount_read + amount_unused <
+ amount_read) {
+ /* Check for integer wrap. */
+ break;
+ }
+
+ byte_count = amount_read +
+ amount_unused;
+ if (byte_count < VS_VERSION_INFO_SIZE) {
+ break;
+ }
+
+ i = 0;
+ }
+
+ /*
+ * Check that the full signature string and
+ * the magic number that follows exist (not
+ * a perfect solution, but the chances that this
+ * occurs in code is, well, remote. Yes I know
+ * I'm comparing the 'V' twice, as it is
+ * simpler to read the code.
+ */
+ if (strcmp(&buf[i], VS_SIGNATURE) == 0) {
+ /*
+ * Compute skip alignment to next
+ * long address.
+ */
+ off_t cpos = in_pos;
+ int skip = -(cpos - (byte_count - i) +
+ sizeof(VS_SIGNATURE)) & 3;
+ if (IVAL(buf,
+ i+sizeof(VS_SIGNATURE)+skip)
+ != 0xfeef04bd) {
+ byte_count = printing_pread_data(fsp,
+ buf,
+ &in_pos,
+ VS_NE_BUF_SIZE);
+ continue;
+ }
+
+ *major = IVAL(buf,
+ i+sizeof(VS_SIGNATURE)+
+ skip+VS_MAJOR_OFFSET);
+ *minor = IVAL(buf,
+ i+sizeof(VS_SIGNATURE)+
+ skip+VS_MINOR_OFFSET);
+ DBG_INFO("NE file [%s] Version "
+ "= %08x:%08x (%d.%d.%d.%d)\n",
+ fname,
+ *major,
+ *minor,
+ (*major>>16)&0xffff,
+ *major&0xffff,
+ (*minor>>16)&0xffff,
+ *minor&0xffff);
+ ret = 1;
+ goto out;
+ }
+ }
+ }
+
+ /* Version info not found, fall back to origin date/time */
+ DBG_ERR("NE file [%s] Version info not found\n", fname);
+ ret = 0;
+
+ out:
+
+ SAFE_FREE(buf);
+ return ret;
+}
+
+/****************************************************************************
+ Version information in Microsoft files is held in a VS_VERSION_INFO structure.
+ There are two case to be covered here: PE (Portable Executable) and NE (New
+ Executable) files. Both files support the same INFO structure, but PE files
+ store the signature in unicode, and NE files store it as !unicode.
+ returns -1 on error, 1 on version info found, and 0 on no version info found.
+****************************************************************************/
+
+static int get_file_version(files_struct *fsp,
+ char *fname,
+ uint32_t *major,
+ uint32_t *minor)
+{
+ char *buf = NULL;
+ ssize_t byte_count;
+ off_t in_pos = fh_get_pos(fsp->fh);
+
+ buf=(char *)SMB_MALLOC(DOS_HEADER_SIZE);
+ if (buf == NULL) {
+ DBG_ERR("PE file [%s] DOS Header malloc failed bytes = %d\n",
+ fname,
+ DOS_HEADER_SIZE);
+ goto error_exit;
+ }
+
+ byte_count = printing_pread_data(fsp, buf, &in_pos, DOS_HEADER_SIZE);
+ if (byte_count < DOS_HEADER_SIZE) {
+ DBG_NOTICE("File [%s] DOS header too short, bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ goto no_version_info;
+ }
+
+ /* Is this really a DOS header? */
+ if (SVAL(buf,DOS_HEADER_MAGIC_OFFSET) != DOS_HEADER_MAGIC) {
+ DBG_INFO("File [%s] bad DOS magic = 0x%x\n",
+ fname,
+ SVAL(buf,DOS_HEADER_MAGIC_OFFSET));
+ goto no_version_info;
+ }
+
+ /*
+ * Skip OEM header (if any) and the
+ * DOS stub to start of Windows header.
+ */
+ in_pos = SVAL(buf,DOS_HEADER_LFANEW_OFFSET);
+
+ /* Note: DOS_HEADER_SIZE and NE_HEADER_SIZE are incidentally same */
+ byte_count = printing_pread_data(fsp, buf, &in_pos, NE_HEADER_SIZE);
+ if (byte_count < NE_HEADER_SIZE) {
+ DBG_NOTICE("File [%s] Windows header too short, "
+ "bytes read = %lu\n",
+ fname,
+ (unsigned long)byte_count);
+ /*
+ * Assume this isn't an error...
+ * the file just looks sort of like a PE/NE file
+ */
+ goto no_version_info;
+ }
+
+ /*
+ * The header may be a PE (Portable Executable)
+ * or an NE (New Executable).
+ */
+ if (IVAL(buf,PE_HEADER_SIGNATURE_OFFSET) == PE_HEADER_SIGNATURE) {
+ return handle_pe_file(fsp,
+ in_pos,
+ fname,
+ buf,
+ major,
+ minor);
+ } else if (SVAL(buf,NE_HEADER_SIGNATURE_OFFSET) ==
+ NE_HEADER_SIGNATURE) {
+ return handle_ne_file(fsp,
+ in_pos,
+ fname,
+ buf,
+ major,
+ minor);
+ } else {
+ /*
+ * Assume this isn't an error... the file just
+ * looks sort of like a PE/NE file.
+ */
+ DBG_NOTICE("File [%s] unknown file format, signature = 0x%x\n",
+ fname,
+ IVAL(buf,PE_HEADER_SIGNATURE_OFFSET));
+ /* Fallthrough into no_version_info: */
+ }
+
+ no_version_info:
+ SAFE_FREE(buf);
+ return 0;
+
+ error_exit:
+ SAFE_FREE(buf);
+ return -1;
+}
+
+/****************************************************************************
+Drivers for Microsoft systems contain multiple files. Often, multiple drivers
+share one or more files. During the MS installation process files are checked
+to insure that only a newer version of a shared file is installed over an
+older version. There are several possibilities for this comparison. If there
+is no previous version, the new one is newer (obviously). If either file is
+missing the version info structure, compare the creation date (on Unix use
+the modification date). Otherwise chose the numerically larger version number.
+****************************************************************************/
+
+static int file_version_is_newer(connection_struct *conn, fstring new_file, fstring old_file)
+{
+ bool use_version = true;
+
+ uint32_t new_major;
+ uint32_t new_minor;
+ time_t new_create_time;
+
+ uint32_t old_major;
+ uint32_t old_minor;
+ time_t old_create_time;
+
+ struct smb_filename *smb_fname = NULL;
+ files_struct *fsp = NULL;
+ struct files_struct *dirfsp = NULL;
+ SMB_STRUCT_STAT st;
+
+ NTSTATUS status;
+ int ret;
+
+ SET_STAT_INVALID(st);
+ new_create_time = (time_t)0;
+ old_create_time = (time_t)0;
+
+ /* Get file version info (if available) for previous file (if it exists) */
+ status = driver_unix_convert(conn, old_file, &dirfsp, &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error_exit;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = 1;
+ goto done;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Old file not found, so by definition new file is in fact newer */
+ DEBUG(10,("file_version_is_newer: Can't open old file [%s], "
+ "errno = %d\n", smb_fname_str_dbg(smb_fname),
+ errno));
+ ret = 1;
+ goto done;
+
+ }
+
+ ret = get_file_version(fsp, old_file, &old_major, &old_minor);
+ if (ret == -1) {
+ goto error_exit;
+ }
+
+ if (!ret) {
+ DEBUG(6,("file_version_is_newer: Version info not found [%s], "
+ "use mod time\n",
+ old_file));
+ use_version = false;
+ if (SMB_VFS_FSTAT(fsp, &st) == -1) {
+ goto error_exit;
+ }
+ old_create_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ DEBUGADD(6,("file_version_is_newer: mod time = %ld sec\n",
+ (long)old_create_time));
+ }
+
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+
+ /* Get file version info (if available) for new file */
+ status = driver_unix_convert(conn, new_file, &dirfsp, &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error_exit;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("Can't open new file [%s], errno = %d\n",
+ smb_fname_str_dbg(smb_fname), errno);
+ goto error_exit;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* New file not found, this shouldn't occur if the caller did its job */
+ DEBUG(3,("file_version_is_newer: Can't open new file [%s], "
+ "errno = %d\n", smb_fname_str_dbg(smb_fname), errno));
+ goto error_exit;
+
+ }
+
+ ret = get_file_version(fsp, new_file, &new_major, &new_minor);
+ if (ret == -1) {
+ goto error_exit;
+ }
+
+ if (!ret) {
+ DEBUG(6,("file_version_is_newer: Version info not found [%s], "
+ "use mod time\n",
+ new_file));
+ use_version = false;
+ if (SMB_VFS_FSTAT(fsp, &st) == -1) {
+ goto error_exit;
+ }
+ new_create_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ DEBUGADD(6,("file_version_is_newer: mod time = %ld sec\n",
+ (long)new_create_time));
+ }
+
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+
+ if (use_version && (new_major != old_major || new_minor != old_minor)) {
+ /* Compare versions and choose the larger version number */
+ if (new_major > old_major ||
+ (new_major == old_major && new_minor > old_minor)) {
+
+ DEBUG(6,("file_version_is_newer: Replacing [%s] with [%s]\n", old_file, new_file));
+ ret = 1;
+ goto done;
+ }
+ else {
+ DEBUG(6,("file_version_is_newer: Leaving [%s] unchanged\n", old_file));
+ ret = 0;
+ goto done;
+ }
+
+ } else {
+ /* Compare modification time/dates and choose the newest time/date */
+ if (new_create_time > old_create_time) {
+ DEBUG(6,("file_version_is_newer: Replacing [%s] with [%s]\n", old_file, new_file));
+ ret = 1;
+ goto done;
+ }
+ else {
+ DEBUG(6,("file_version_is_newer: Leaving [%s] unchanged\n", old_file));
+ ret = 0;
+ goto done;
+ }
+ }
+
+ error_exit:
+ if(fsp)
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ ret = -1;
+ done:
+ TALLOC_FREE(smb_fname);
+ return ret;
+}
+
+/****************************************************************************
+Determine the correct cVersion associated with an architecture and driver
+****************************************************************************/
+static uint32_t get_correct_cversion(const struct auth_session_info *session_info,
+ const char *architecture,
+ const char *driverpath_in,
+ const char *driver_directory,
+ WERROR *perr)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int cversion = -1;
+ NTSTATUS nt_status;
+ struct smb_filename *smb_fname = NULL;
+ files_struct *fsp = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ char *printdollar = NULL;
+ char *printdollar_path = NULL;
+ char *working_dir = NULL;
+ int printdollar_snum;
+ uint32_t major, minor;
+ int ret;
+
+ *perr = WERR_INVALID_PARAMETER;
+
+ /* If architecture is Windows 95/98/ME, the version is always 0. */
+ if (strcmp(architecture, SPL_ARCH_WIN40) == 0) {
+ DEBUG(10,("get_correct_cversion: Driver is Win9x, cversion = 0\n"));
+ *perr = WERR_OK;
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /* If architecture is Windows x64, the version is always 3. */
+ if (strcmp(architecture, SPL_ARCH_X64) == 0 ||
+ strcmp(architecture, SPL_ARCH_ARM64) == 0) {
+ DBG_DEBUG("get_correct_cversion: this architecture must be, cversion = 3\n");
+ *perr = WERR_OK;
+ TALLOC_FREE(frame);
+ return 3;
+ }
+
+ printdollar_snum = find_service(frame, "print$", &printdollar);
+ if (!printdollar) {
+ *perr = WERR_NOT_ENOUGH_MEMORY;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ if (printdollar_snum == -1) {
+ *perr = WERR_BAD_NET_NAME;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ printdollar_path = lp_path(frame, lp_sub, printdollar_snum);
+ if (printdollar_path == NULL) {
+ *perr = WERR_NOT_ENOUGH_MEMORY;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ working_dir = talloc_asprintf(frame,
+ "%s/%s",
+ printdollar_path,
+ architecture);
+ /*
+ * If the driver has been uploaded into a temorpary driver
+ * directory, switch to the driver directory.
+ */
+ if (driver_directory != NULL) {
+ working_dir = talloc_asprintf(frame, "%s/%s/%s",
+ printdollar_path,
+ architecture,
+ driver_directory);
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ printdollar_snum,
+ working_dir,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("get_correct_cversion: create_conn_struct "
+ "returned %s\n", nt_errstr(nt_status)));
+ *perr = ntstatus_to_werror(nt_status);
+ TALLOC_FREE(frame);
+ return -1;
+ }
+ conn = c->conn;
+
+ nt_status = set_conn_force_user_group(conn, printdollar_snum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ *perr = ntstatus_to_werror(nt_status);
+ goto error_free_conn;
+ }
+
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ *perr = WERR_ACCESS_DENIED;
+ goto error_free_conn;
+ }
+
+ /*
+ * We switch to the directory where the driver files are located,
+ * so only work on the file names
+ */
+ nt_status = driver_unix_convert(conn,
+ driverpath_in,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ *perr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+
+ nt_status = vfs_file_exist(conn, smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(3,("get_correct_cversion: vfs_file_exist failed\n"));
+ *perr = WERR_FILE_NOT_FOUND;
+ goto error_exit;
+ }
+
+ nt_status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_NOTICE("Can't open file [%s]: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(nt_status));
+ *perr = WERR_ACCESS_DENIED;
+ goto error_exit;
+ }
+
+ nt_status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* private_flags */
+ 0, /* allocation_size */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(3,("get_correct_cversion: Can't open file [%s], errno = "
+ "%d\n", smb_fname_str_dbg(smb_fname), errno));
+ *perr = WERR_ACCESS_DENIED;
+ goto error_exit;
+ }
+
+ ret = get_file_version(fsp, smb_fname->base_name, &major, &minor);
+ if (ret == -1) {
+ *perr = WERR_INVALID_PARAMETER;
+ goto error_exit;
+ } else if (!ret) {
+ DEBUG(6,("get_correct_cversion: Version info not "
+ "found [%s]\n",
+ smb_fname_str_dbg(smb_fname)));
+ *perr = WERR_INVALID_PARAMETER;
+ goto error_exit;
+ }
+
+ /*
+ * This is a Microsoft'ism. See references in MSDN to VER_FILEVERSION
+ * for more details. Version in this case is not just the version of the
+ * file, but the version in the sense of kernel mode (2) vs. user mode
+ * (3) drivers. Other bits of the version fields are the version info.
+ * JRR 010716
+ */
+ cversion = major & 0x0000ffff;
+ switch (cversion) {
+ case 2: /* WinNT drivers */
+ case 3: /* Win2K drivers */
+ break;
+
+ default:
+ DEBUG(6,("get_correct_cversion: cversion "
+ "invalid [%s] cversion = %d\n",
+ smb_fname_str_dbg(smb_fname),
+ cversion));
+ goto error_exit;
+ }
+
+ DEBUG(10,("get_correct_cversion: Version info found [%s] major"
+ " = 0x%x minor = 0x%x\n",
+ smb_fname_str_dbg(smb_fname), major, minor));
+
+ DEBUG(10,("get_correct_cversion: Driver file [%s] cversion = %d\n",
+ smb_fname_str_dbg(smb_fname), cversion));
+ *perr = WERR_OK;
+
+ error_exit:
+ unbecome_user_without_service();
+ error_free_conn:
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ if (!W_ERROR_IS_OK(*perr)) {
+ cversion = -1;
+ }
+
+ TALLOC_FREE(frame);
+ return cversion;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+#define strip_driver_path(_mem_ctx, _element) do { \
+ if (_element && ((_p = strrchr((_element), '\\')) != NULL)) { \
+ (_element) = talloc_asprintf((_mem_ctx), "%s", _p+1); \
+ W_ERROR_HAVE_NO_MEMORY((_element)); \
+ } \
+} while (0);
+
+static WERROR clean_up_driver_struct_level(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ const char *architecture,
+ const char **driver_path,
+ const char **data_file,
+ const char **config_file,
+ const char **help_file,
+ struct spoolss_StringArray *dependent_files,
+ enum spoolss_DriverOSVersion *version,
+ uint32_t flags,
+ const char **driver_directory)
+{
+ const char *short_architecture;
+ int i;
+ WERROR err;
+ char *_p;
+
+ if (!*driver_path || !*data_file) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!strequal(architecture, SPOOLSS_ARCHITECTURE_4_0) && !*config_file) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (flags & APD_COPY_FROM_DIRECTORY) {
+ char *path;
+ char *q;
+
+ /*
+ * driver_path is set to:
+ *
+ * \\PRINTSRV\print$\x64\{279245b0-a8bd-4431-bf6f-baee92ac15c0}\pscript5.dll
+ */
+ path = talloc_strdup(mem_ctx, *driver_path);
+ if (path == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* Remove pscript5.dll */
+ q = strrchr_m(path, '\\');
+ if (q == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+ *q = '\0';
+
+ /* Get \{279245b0-a8bd-4431-bf6f-baee92ac15c0} */
+ q = strrchr_m(path, '\\');
+ if (q == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * Set driver_directory to:
+ *
+ * {279245b0-a8bd-4431-bf6f-baee92ac15c0}
+ *
+ * This is the directory where all the files have been uploaded
+ */
+ *driver_directory = q + 1;
+ }
+
+ /* clean up the driver name.
+ * we can get .\driver.dll
+ * or worse c:\windows\system\driver.dll !
+ */
+ /* using an intermediate string to not have overlapping memcpy()'s */
+
+ strip_driver_path(mem_ctx, *driver_path);
+ strip_driver_path(mem_ctx, *data_file);
+ if (*config_file) {
+ strip_driver_path(mem_ctx, *config_file);
+ }
+ if (help_file) {
+ strip_driver_path(mem_ctx, *help_file);
+ }
+
+ if (dependent_files && dependent_files->string) {
+ for (i=0; dependent_files->string[i]; i++) {
+ strip_driver_path(mem_ctx, dependent_files->string[i]);
+ }
+ }
+
+ short_architecture = get_short_archi(architecture);
+ if (!short_architecture) {
+ return WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+
+ /* jfm:7/16/2000 the client always sends the cversion=0.
+ * The server should check which version the driver is by reading
+ * the PE header of driver->driverpath.
+ *
+ * For Windows 95/98 the version is 0 (so the value sent is correct)
+ * For Windows NT (the architecture doesn't matter)
+ * NT 3.1: cversion=0
+ * NT 3.5/3.51: cversion=1
+ * NT 4: cversion=2
+ * NT2K: cversion=3
+ */
+
+ *version = get_correct_cversion(session_info,
+ short_architecture,
+ *driver_path,
+ *driver_directory,
+ &err);
+ if (*version == -1) {
+ return err;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+WERROR clean_up_driver_struct(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ const struct spoolss_AddDriverInfoCtr *r,
+ uint32_t flags,
+ const char **driver_directory)
+{
+ switch (r->level) {
+ case 3:
+ return clean_up_driver_struct_level(mem_ctx, session_info,
+ r->info.info3->architecture,
+ &r->info.info3->driver_path,
+ &r->info.info3->data_file,
+ &r->info.info3->config_file,
+ &r->info.info3->help_file,
+ r->info.info3->dependent_files,
+ &r->info.info3->version,
+ flags,
+ driver_directory);
+ case 6:
+ return clean_up_driver_struct_level(mem_ctx, session_info,
+ r->info.info6->architecture,
+ &r->info.info6->driver_path,
+ &r->info.info6->data_file,
+ &r->info.info6->config_file,
+ &r->info.info6->help_file,
+ r->info.info6->dependent_files,
+ &r->info.info6->version,
+ flags,
+ driver_directory);
+ case 8:
+ return clean_up_driver_struct_level(mem_ctx, session_info,
+ r->info.info8->architecture,
+ &r->info.info8->driver_path,
+ &r->info.info8->data_file,
+ &r->info.info8->config_file,
+ &r->info.info8->help_file,
+ r->info.info8->dependent_files,
+ &r->info.info8->version,
+ flags,
+ driver_directory);
+ default:
+ return WERR_NOT_SUPPORTED;
+ }
+}
+
+/****************************************************************************
+ This function sucks and should be replaced. JRA.
+****************************************************************************/
+
+static void convert_level_6_to_level3(struct spoolss_AddDriverInfo3 *dst,
+ const struct spoolss_AddDriverInfo6 *src)
+{
+ dst->version = src->version;
+
+ dst->driver_name = src->driver_name;
+ dst->architecture = src->architecture;
+ dst->driver_path = src->driver_path;
+ dst->data_file = src->data_file;
+ dst->config_file = src->config_file;
+ dst->help_file = src->help_file;
+ dst->monitor_name = src->monitor_name;
+ dst->default_datatype = src->default_datatype;
+ dst->_ndr_size_dependent_files = src->_ndr_size_dependent_files;
+ dst->dependent_files = src->dependent_files;
+}
+
+static void convert_level_8_to_level3(struct spoolss_AddDriverInfo3 *dst,
+ const struct spoolss_AddDriverInfo8 *src)
+{
+ dst->version = src->version;
+
+ dst->driver_name = src->driver_name;
+ dst->architecture = src->architecture;
+ dst->driver_path = src->driver_path;
+ dst->data_file = src->data_file;
+ dst->config_file = src->config_file;
+ dst->help_file = src->help_file;
+ dst->monitor_name = src->monitor_name;
+ dst->default_datatype = src->default_datatype;
+ dst->_ndr_size_dependent_files = src->_ndr_size_dependent_files;
+ dst->dependent_files = src->dependent_files;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR move_driver_file_to_download_area(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *driver_file,
+ const char *short_architecture,
+ uint32_t driver_version,
+ uint32_t version,
+ const char *driver_directory)
+{
+ struct smb_filename *smb_fname_old = NULL;
+ struct smb_filename *smb_fname_new = NULL;
+ char *old_name = NULL;
+ char *new_name = NULL;
+ NTSTATUS status;
+ WERROR ret;
+
+ if (driver_directory != NULL) {
+ old_name = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
+ short_architecture,
+ driver_directory,
+ driver_file);
+ } else {
+ old_name = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ short_architecture,
+ driver_file);
+ }
+ if (old_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ new_name = talloc_asprintf(mem_ctx, "%s/%d/%s",
+ short_architecture, driver_version, driver_file);
+ if (new_name == NULL) {
+ TALLOC_FREE(old_name);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (version != -1 && (version = file_version_is_newer(conn, old_name, new_name)) > 0) {
+ struct files_struct *dirfsp = NULL;
+
+ status = driver_unix_convert(conn,
+ old_name,
+ &dirfsp,
+ &smb_fname_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ /* Setup a synthetic smb_filename struct */
+ smb_fname_new = talloc_zero(mem_ctx, struct smb_filename);
+ if (!smb_fname_new) {
+ ret = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ smb_fname_new->base_name = new_name;
+
+ DEBUG(10,("move_driver_file_to_download_area: copying '%s' to "
+ "'%s'\n", smb_fname_old->base_name,
+ smb_fname_new->base_name));
+
+ status = copy_file(mem_ctx, conn, smb_fname_old, smb_fname_new,
+ FILE_OVERWRITE_IF);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("move_driver_file_to_download_area: Unable "
+ "to rename [%s] to [%s]: %s\n",
+ smb_fname_old->base_name, new_name,
+ nt_errstr(status)));
+ ret = WERR_APP_INIT_FAILURE;
+ goto out;
+ }
+ }
+
+ ret = WERR_OK;
+ out:
+ TALLOC_FREE(smb_fname_old);
+ TALLOC_FREE(smb_fname_new);
+ return ret;
+}
+
+WERROR move_driver_to_download_area(const struct auth_session_info *session_info,
+ const struct spoolss_AddDriverInfoCtr *r,
+ const char *driver_directory)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct spoolss_AddDriverInfo3 *driver;
+ struct spoolss_AddDriverInfo3 converted_driver;
+ const char *short_architecture;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ char *new_dir = NULL;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ NTSTATUS nt_status;
+ int i;
+ int ver = 0;
+ char *printdollar = NULL;
+ int printdollar_snum;
+ WERROR err = WERR_OK;
+
+ switch (r->level) {
+ case 3:
+ driver = r->info.info3;
+ break;
+ case 6:
+ convert_level_6_to_level3(&converted_driver, r->info.info6);
+ driver = &converted_driver;
+ break;
+ case 8:
+ convert_level_8_to_level3(&converted_driver, r->info.info8);
+ driver = &converted_driver;
+ break;
+ default:
+ DEBUG(0,("move_driver_to_download_area: Unknown info level (%u)\n", (unsigned int)r->level));
+ TALLOC_FREE(frame);
+ return WERR_INVALID_LEVEL;
+ }
+
+ short_architecture = get_short_archi(driver->architecture);
+ if (!short_architecture) {
+ TALLOC_FREE(frame);
+ return WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+
+ printdollar_snum = find_service(frame, "print$", &printdollar);
+ if (!printdollar) {
+ TALLOC_FREE(frame);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (printdollar_snum == -1) {
+ TALLOC_FREE(frame);
+ return WERR_BAD_NET_NAME;
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ printdollar_snum,
+ lp_path(frame, lp_sub, printdollar_snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("move_driver_to_download_area: create_conn_struct "
+ "returned %s\n", nt_errstr(nt_status)));
+ err = ntstatus_to_werror(nt_status);
+ TALLOC_FREE(frame);
+ return err;
+ }
+ conn = c->conn;
+
+ nt_status = set_conn_force_user_group(conn, printdollar_snum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ err = ntstatus_to_werror(nt_status);
+ goto err_free_conn;
+ }
+
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ err = WERR_ACCESS_DENIED;
+ goto err_free_conn;
+ }
+
+ new_dir = talloc_asprintf(frame,
+ "%s/%d",
+ short_architecture,
+ driver->version);
+ if (!new_dir) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto err_exit;
+ }
+ nt_status = driver_unix_convert(conn, new_dir, &dirfsp, &smb_dname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto err_exit;
+ }
+
+ DEBUG(5,("Creating first directory: %s\n", smb_dname->base_name));
+
+ nt_status = create_directory(conn, NULL, dirfsp, smb_dname);
+ if (!NT_STATUS_IS_OK(nt_status)
+ && !NT_STATUS_EQUAL(nt_status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ DEBUG(0, ("failed to create driver destination directory: %s\n",
+ nt_errstr(nt_status)));
+ err = ntstatus_to_werror(nt_status);
+ goto err_exit;
+ }
+
+ /* For each driver file, archi\filexxx.yyy, if there is a duplicate file
+ * listed for this driver which has already been moved, skip it (note:
+ * drivers may list the same file name several times. Then check if the
+ * file already exists in archi\version\, if so, check that the version
+ * info (or time stamps if version info is unavailable) is newer (or the
+ * date is later). If it is, move it to archi\version\filexxx.yyy.
+ * Otherwise, delete the file.
+ *
+ * If a file is not moved to archi\version\ because of an error, all the
+ * rest of the 'unmoved' driver files are removed from archi\. If one or
+ * more of the driver's files was already moved to archi\version\, it
+ * potentially leaves the driver in a partially updated state. Version
+ * trauma will most likely occur if an client attempts to use any printer
+ * bound to the driver. Perhaps a rewrite to make sure the moves can be
+ * done is appropriate... later JRR
+ */
+
+ DEBUG(5,("Moving files now !\n"));
+
+ if (driver->driver_path && strlen(driver->driver_path)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->driver_path,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+
+ if (driver->data_file && strlen(driver->data_file)) {
+ if (!strequal(driver->data_file, driver->driver_path)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->data_file,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ }
+
+ if (driver->config_file && strlen(driver->config_file)) {
+ if (!strequal(driver->config_file, driver->driver_path) &&
+ !strequal(driver->config_file, driver->data_file)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->config_file,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ }
+
+ if (driver->help_file && strlen(driver->help_file)) {
+ if (!strequal(driver->help_file, driver->driver_path) &&
+ !strequal(driver->help_file, driver->data_file) &&
+ !strequal(driver->help_file, driver->config_file)) {
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->help_file,
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ }
+
+ if (driver->dependent_files && driver->dependent_files->string) {
+ for (i=0; driver->dependent_files->string[i]; i++) {
+ if (!strequal(driver->dependent_files->string[i], driver->driver_path) &&
+ !strequal(driver->dependent_files->string[i], driver->data_file) &&
+ !strequal(driver->dependent_files->string[i], driver->config_file) &&
+ !strequal(driver->dependent_files->string[i], driver->help_file)) {
+ int j;
+ for (j=0; j < i; j++) {
+ if (strequal(driver->dependent_files->string[i], driver->dependent_files->string[j])) {
+ goto NextDriver;
+ }
+ }
+
+ err = move_driver_file_to_download_area(frame,
+ conn,
+ driver->dependent_files->string[i],
+ short_architecture,
+ driver->version,
+ ver,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ goto err_exit;
+ }
+ }
+ NextDriver: ;
+ }
+ }
+
+ err = WERR_OK;
+ err_exit:
+ unbecome_user_without_service();
+ err_free_conn:
+ TALLOC_FREE(frame);
+ return err;
+}
+
+/****************************************************************************
+ Determine whether or not a particular driver is currently assigned
+ to a printer
+****************************************************************************/
+
+bool printer_driver_in_use(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const struct spoolss_DriverInfo8 *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ int n_services = lp_numservices();
+ bool in_use = false;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+
+ if (!r) {
+ return false;
+ }
+
+ DEBUG(10,("printer_driver_in_use: Beginning search through ntprinters.tdb...\n"));
+
+ /* loop through the printers.tdb and check for the drivername */
+
+ for (snum=0; snum<n_services && !in_use; snum++) {
+ if (!lp_snum_ok(snum) || !lp_printable(snum)) {
+ continue;
+ }
+
+ result = winreg_get_printer(mem_ctx, b,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ continue; /* skip */
+ }
+
+ if (strequal(r->driver_name, pinfo2->drivername)) {
+ in_use = true;
+ }
+
+ TALLOC_FREE(pinfo2);
+ }
+
+ DEBUG(10,("printer_driver_in_use: Completed search through ntprinters.tdb...\n"));
+
+ if ( in_use ) {
+ struct spoolss_DriverInfo8 *driver = NULL;
+ WERROR werr;
+
+ DEBUG(5,("printer_driver_in_use: driver \"%s\" is currently in use\n", r->driver_name));
+
+ /* we can still remove the driver if there is one of
+ "Windows NT x86" version 2 or 3 left */
+
+ if (strequal(SPOOLSS_ARCHITECTURE_NT_X86, r->architecture)) {
+ if (r->version == 2) {
+ werr = winreg_get_driver(mem_ctx, b,
+ r->architecture,
+ r->driver_name,
+ 3, &driver);
+ } else if (r->version == 3) {
+ werr = winreg_get_driver(mem_ctx, b,
+ r->architecture,
+ r->driver_name,
+ 2, &driver);
+ } else {
+ DBG_ERR("Unknown driver version (%d)\n",
+ r->version);
+ werr = WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+ } else if (strequal(SPOOLSS_ARCHITECTURE_x64, r->architecture)) {
+ werr = winreg_get_driver(mem_ctx, b,
+ SPOOLSS_ARCHITECTURE_NT_X86,
+ r->driver_name,
+ DRIVER_ANY_VERSION,
+ &driver);
+ } else {
+ DBG_ERR("Unknown driver architecture: %s\n",
+ r->architecture);
+ werr = WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+
+ /* now check the error code */
+
+ if ( W_ERROR_IS_OK(werr) ) {
+ /* it's ok to remove the driver, we have other architectures left */
+ in_use = false;
+ talloc_free(driver);
+ }
+ }
+
+ /* report that the driver is not in use by default */
+
+ return in_use;
+}
+
+
+/**********************************************************************
+ Check to see if a ogiven file is in use by *info
+ *********************************************************************/
+
+static bool drv_file_in_use(const char *file, const struct spoolss_DriverInfo8 *info)
+{
+ int i = 0;
+
+ if ( !info )
+ return False;
+
+ /* mz: skip files that are in the list but already deleted */
+ if (!file || !file[0]) {
+ return false;
+ }
+
+ if (strequal(file, info->driver_path))
+ return True;
+
+ if (strequal(file, info->data_file))
+ return True;
+
+ if (strequal(file, info->config_file))
+ return True;
+
+ if (strequal(file, info->help_file))
+ return True;
+
+ /* see of there are any dependent files to examine */
+
+ if (!info->dependent_files)
+ return False;
+
+ while (info->dependent_files[i] && *info->dependent_files[i]) {
+ if (strequal(file, info->dependent_files[i]))
+ return True;
+ i++;
+ }
+
+ return False;
+
+}
+
+/**********************************************************************
+ Utility function to remove the dependent file pointed to by the
+ input parameter from the list
+ *********************************************************************/
+
+static void trim_dependent_file(TALLOC_CTX *mem_ctx, const char **files, int idx)
+{
+
+ /* bump everything down a slot */
+
+ while (files && files[idx+1]) {
+ files[idx] = talloc_strdup(mem_ctx, files[idx+1]);
+ idx++;
+ }
+
+ files[idx] = NULL;
+
+ return;
+}
+
+/**********************************************************************
+ Check if any of the files used by src are also used by drv
+ *********************************************************************/
+
+static bool trim_overlap_drv_files(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo8 *src,
+ const struct spoolss_DriverInfo8 *drv)
+{
+ bool in_use = False;
+ int i = 0;
+
+ if ( !src || !drv )
+ return False;
+
+ /* check each file. Remove it from the src structure if it overlaps */
+
+ if (drv_file_in_use(src->driver_path, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing driverfile [%s] from list\n", src->driver_path));
+ src->driver_path = talloc_strdup(mem_ctx, "");
+ if (!src->driver_path) { return false; }
+ }
+
+ if (drv_file_in_use(src->data_file, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing datafile [%s] from list\n", src->data_file));
+ src->data_file = talloc_strdup(mem_ctx, "");
+ if (!src->data_file) { return false; }
+ }
+
+ if (drv_file_in_use(src->config_file, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing configfile [%s] from list\n", src->config_file));
+ src->config_file = talloc_strdup(mem_ctx, "");
+ if (!src->config_file) { return false; }
+ }
+
+ if (drv_file_in_use(src->help_file, drv)) {
+ in_use = True;
+ DEBUG(10,("Removing helpfile [%s] from list\n", src->help_file));
+ src->help_file = talloc_strdup(mem_ctx, "");
+ if (!src->help_file) { return false; }
+ }
+
+ /* are there any dependentfiles to examine? */
+
+ if (!src->dependent_files)
+ return in_use;
+
+ while (src->dependent_files[i] && *src->dependent_files[i]) {
+ if (drv_file_in_use(src->dependent_files[i], drv)) {
+ in_use = True;
+ DEBUG(10,("Removing [%s] from dependent file list\n", src->dependent_files[i]));
+ trim_dependent_file(mem_ctx, src->dependent_files, i);
+ } else
+ i++;
+ }
+
+ return in_use;
+}
+
+/****************************************************************************
+ Determine whether or not a particular driver files are currently being
+ used by any other driver.
+
+ Return value is True if any files were in use by other drivers
+ and False otherwise.
+
+ Upon return, *info has been modified to only contain the driver files
+ which are not in use
+
+ Fix from mz:
+
+ This needs to check all drivers to ensure that all files in use
+ have been removed from *info, not just the ones in the first
+ match.
+****************************************************************************/
+
+bool printer_driver_files_in_use(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct spoolss_DriverInfo8 *info)
+{
+ uint32_t version;
+ struct spoolss_DriverInfo8 *driver;
+ bool in_use = false;
+ uint32_t i, num_drivers;
+ const char **drivers;
+ WERROR result;
+
+ if ( !info )
+ return False;
+
+ version = info->version;
+
+ /* loop over all driver versions */
+
+ DEBUG(5,("printer_driver_files_in_use: Beginning search of drivers...\n"));
+
+ /* get the list of drivers */
+
+ result = winreg_get_driver_list(mem_ctx, b,
+ info->architecture, version,
+ &num_drivers, &drivers);
+ if (!W_ERROR_IS_OK(result)) {
+ return true;
+ }
+
+ DEBUGADD(4, ("we have:[%d] drivers in environment [%s] and version [%d]\n",
+ num_drivers, info->architecture, version));
+
+ /* check each driver for overlap in files */
+
+ for (i = 0; i < num_drivers; i++) {
+ DEBUGADD(5,("\tdriver: [%s]\n", drivers[i]));
+
+ driver = NULL;
+
+ result = winreg_get_driver(mem_ctx, b,
+ info->architecture, drivers[i],
+ version, &driver);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(drivers);
+ return True;
+ }
+
+ /* check if d2 uses any files from d1 */
+ /* only if this is a different driver than the one being deleted */
+
+ if (!strequal(info->driver_name, driver->driver_name)) {
+ if (trim_overlap_drv_files(mem_ctx, info, driver)) {
+ /* mz: Do not instantly return -
+ * we need to ensure this file isn't
+ * also in use by other drivers. */
+ in_use = true;
+ }
+ }
+
+ talloc_free(driver);
+ }
+
+ talloc_free(drivers);
+
+ DEBUG(5,("printer_driver_files_in_use: Completed search of drivers...\n"));
+
+ return in_use;
+}
+
+static NTSTATUS driver_unlink_internals(connection_struct *conn,
+ const char *short_arch,
+ int vers,
+ const char *fname)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(conn);
+ struct smb_filename *smb_fname = NULL;
+ char *print_dlr_path;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ print_dlr_path = talloc_asprintf(tmp_ctx, "%s/%d/%s",
+ short_arch, vers, fname);
+ if (print_dlr_path == NULL) {
+ goto err_out;
+ }
+
+ status = synthetic_pathref(tmp_ctx,
+ conn->cwd_fsp,
+ print_dlr_path,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = unlink_internals(conn, NULL, 0, NULL, smb_fname);
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************************
+ Actually delete the driver files. Make sure that
+ printer_driver_files_in_use() return False before calling
+ this.
+****************************************************************************/
+
+bool delete_driver_files(const struct auth_session_info *session_info,
+ const struct spoolss_DriverInfo8 *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *short_arch;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ NTSTATUS nt_status;
+ char *printdollar = NULL;
+ int printdollar_snum;
+ bool ret = false;
+
+ if (!r) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ DEBUG(6,("delete_driver_files: deleting driver [%s] - version [%d]\n",
+ r->driver_name, r->version));
+
+ printdollar_snum = find_service(frame, "print$", &printdollar);
+ if (!printdollar) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ if (printdollar_snum == -1) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ printdollar_snum,
+ lp_path(frame, lp_sub, printdollar_snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("delete_driver_files: create_conn_struct "
+ "returned %s\n", nt_errstr(nt_status)));
+ TALLOC_FREE(frame);
+ return false;
+ }
+ conn = c->conn;
+
+ nt_status = set_conn_force_user_group(conn, printdollar_snum);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ ret = false;
+ goto err_free_conn;
+ }
+
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ ret = false;
+ goto err_free_conn;
+ }
+
+ if ( !CAN_WRITE(conn) ) {
+ DEBUG(3,("delete_driver_files: Cannot delete print driver when [print$] is read-only\n"));
+ ret = false;
+ goto err_out;
+ }
+
+ short_arch = get_short_archi(r->architecture);
+ if (short_arch == NULL) {
+ DEBUG(0, ("bad architecture %s\n", r->architecture));
+ ret = false;
+ goto err_out;
+ }
+
+ /* now delete the files */
+
+ if (r->driver_path && r->driver_path[0]) {
+ DEBUG(10,("deleting driverfile [%s]\n", r->driver_path));
+ driver_unlink_internals(conn, short_arch, r->version, r->driver_path);
+ }
+
+ if (r->config_file && r->config_file[0]) {
+ DEBUG(10,("deleting configfile [%s]\n", r->config_file));
+ driver_unlink_internals(conn, short_arch, r->version, r->config_file);
+ }
+
+ if (r->data_file && r->data_file[0]) {
+ DEBUG(10,("deleting datafile [%s]\n", r->data_file));
+ driver_unlink_internals(conn, short_arch, r->version, r->data_file);
+ }
+
+ if (r->help_file && r->help_file[0]) {
+ DEBUG(10,("deleting helpfile [%s]\n", r->help_file));
+ driver_unlink_internals(conn, short_arch, r->version, r->help_file);
+ }
+
+ if (r->dependent_files) {
+ int i = 0;
+ while (r->dependent_files[i] && r->dependent_files[i][0]) {
+ DEBUG(10,("deleting dependent file [%s]\n", r->dependent_files[i]));
+ driver_unlink_internals(conn, short_arch, r->version, r->dependent_files[i]);
+ i++;
+ }
+ }
+
+ ret = true;
+ err_out:
+ unbecome_user_without_service();
+ err_free_conn:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/* error code:
+ 0: everything OK
+ 1: level not implemented
+ 2: file doesn't exist
+ 3: can't allocate memory
+ 4: can't free memory
+ 5: non existent struct
+*/
+
+/*
+ A printer and a printer driver are 2 different things.
+ NT manages them separately, Samba does the same.
+ Why ? Simply because it's easier and it makes sense !
+
+ Now explanation: You have 3 printers behind your samba server,
+ 2 of them are the same make and model (laser A and B). But laser B
+ has an 3000 sheet feeder and laser A doesn't such an option.
+ Your third printer is an old dot-matrix model for the accounting :-).
+
+ If the /usr/local/samba/lib directory (default dir), you will have
+ 5 files to describe all of this.
+
+ 3 files for the printers (1 by printer):
+ NTprinter_laser A
+ NTprinter_laser B
+ NTprinter_accounting
+ 2 files for the drivers (1 for the laser and 1 for the dot matrix)
+ NTdriver_printer model X
+ NTdriver_printer model Y
+
+jfm: I should use this comment for the text file to explain
+ same thing for the forms BTW.
+ Je devrais mettre mes commentaires en francais, ca serait mieux :-)
+
+*/
+
+/* Convert generic access rights to printer object specific access rights.
+ It turns out that NT4 security descriptors use generic access rights and
+ NT5 the object specific ones. */
+
+void map_printer_permissions(struct security_descriptor *sd)
+{
+ uint32_t i;
+
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+ se_map_generic(&sd->dacl->aces[i].access_mask,
+ &printer_generic_mapping);
+ }
+}
+
+void map_job_permissions(struct security_descriptor *sd)
+{
+ uint32_t i;
+
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+ se_map_generic(&sd->dacl->aces[i].access_mask,
+ &job_generic_mapping);
+ }
+}
+
+
+/****************************************************************************
+ Check a user has permissions to perform the given operation. We use the
+ permission constants defined in include/rpc_spoolss.h to check the various
+ actions we perform when checking printer access.
+
+ PRINTER_ACCESS_ADMINISTER:
+ print_queue_pause, print_queue_resume, update_printer_sec,
+ update_printer, spoolss_addprinterex_level_2,
+ _spoolss_setprinterdata
+
+ PRINTER_ACCESS_USE:
+ print_job_start
+
+ JOB_ACCESS_ADMINISTER:
+ print_job_delete, print_job_pause, print_job_resume,
+ print_queue_purge
+
+ Try access control in the following order (for performance reasons):
+ 1) root and SE_PRINT_OPERATOR can do anything (easy check)
+ 2) check security descriptor (bit comparisons in memory)
+ 3) "printer admins" (may result in numerous calls to winbind)
+
+ ****************************************************************************/
+WERROR print_access_check(const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx, int snum,
+ int access_type)
+{
+ struct spoolss_security_descriptor *secdesc = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t access_granted;
+ size_t sd_size;
+ NTSTATUS status;
+ WERROR result;
+ const char *pname;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ /* If user is NULL then use the current_user structure */
+
+ /* Always allow root or SE_PRINT_OPERATROR to do anything */
+
+ if ((session_info->unix_token->uid == sec_initial_uid())
+ || security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR)) {
+ return WERR_OK;
+ }
+
+ /* Get printer name */
+
+ pname = lp_printername(talloc_tos(), lp_sub, snum);
+
+ if (!pname || !*pname) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Get printer security descriptor */
+
+ if(!(mem_ctx = talloc_init("print_access_check"))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_get_printer_secdesc_internal(mem_ctx,
+ get_session_info_system(),
+ msg_ctx,
+ pname,
+ &secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_destroy(mem_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (access_type == JOB_ACCESS_ADMINISTER) {
+ struct spoolss_security_descriptor *parent_secdesc = secdesc;
+
+ /* Create a child security descriptor to check permissions
+ against. This is because print jobs are child objects
+ objects of a printer. */
+ status = se_create_child_secdesc(mem_ctx,
+ &secdesc,
+ &sd_size,
+ parent_secdesc,
+ parent_secdesc->owner_sid,
+ parent_secdesc->group_sid,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_destroy(mem_ctx);
+ return ntstatus_to_werror(status);
+ }
+
+ map_job_permissions(secdesc);
+ } else {
+ map_printer_permissions(secdesc);
+ }
+
+ /* Check access */
+ status = se_access_check(secdesc, session_info->security_token, access_type,
+ &access_granted);
+
+ DEBUG(4, ("access check was %s\n", NT_STATUS_IS_OK(status) ? "SUCCESS" : "FAILURE"));
+
+ talloc_destroy(mem_ctx);
+
+ return ntstatus_to_werror(status);
+}
+
+/****************************************************************************
+ Check the time parameters allow a print operation.
+*****************************************************************************/
+
+bool print_time_access_check(const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servicename)
+{
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+ bool ok = False;
+ time_t now = time(NULL);
+ struct tm *t;
+ uint32_t mins;
+
+ result = winreg_get_printer_internal(NULL, session_info, msg_ctx,
+ servicename, &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ return False;
+ }
+
+ if (pinfo2->starttime == 0 && pinfo2->untiltime == 0) {
+ ok = True;
+ }
+
+ t = gmtime(&now);
+ mins = (uint32_t)t->tm_hour*60 + (uint32_t)t->tm_min;
+
+ if (mins >= pinfo2->starttime && mins <= pinfo2->untiltime) {
+ ok = True;
+ }
+
+ TALLOC_FREE(pinfo2);
+
+ if (!ok) {
+ errno = EACCES;
+ }
+
+ return ok;
+}
+
+void nt_printer_remove(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer)
+{
+ WERROR result;
+
+ result = winreg_delete_printer_key_internal(mem_ctx, session_info, msg_ctx,
+ printer, "");
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("nt_printer_remove: failed to remove printer %s: "
+ "%s\n", printer, win_errstr(result)));
+ }
+}
+
+void nt_printer_add(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer)
+{
+ WERROR result;
+
+ result = winreg_create_printer_internal(mem_ctx, session_info, msg_ctx,
+ printer);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("nt_printer_add: failed to add printer %s: %s\n",
+ printer, win_errstr(result)));
+ }
+}
diff --git a/source3/printing/nt_printing_ads.c b/source3/printing/nt_printing_ads.c
new file mode 100644
index 0000000..4d5991e
--- /dev/null
+++ b/source3/printing/nt_printing_ads.c
@@ -0,0 +1,908 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/spoolss.h"
+#include "rpc_server/spoolss/srv_spoolss_util.h"
+#include "nt_printing.h"
+#include "ads.h"
+#include "secrets.h"
+#include "krb5_env.h"
+#include "../libcli/registry/util_reg.h"
+#include "auth.h"
+#include "../librpc/ndr/libndr.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+
+#ifdef HAVE_ADS
+/*****************************************************************
+ ****************************************************************/
+
+WERROR nt_printer_guid_store(struct messaging_context *msg_ctx,
+ const char *printer, struct GUID guid)
+{
+ TALLOC_CTX *tmp_ctx;
+ const struct auth_session_info *session_info;
+ const char *guid_str;
+ DATA_BLOB blob;
+ WERROR result;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) {
+ DEBUG(0, ("Out of memory?!\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ session_info = get_session_info_system();
+ if (session_info == NULL) {
+ DEBUG(0, ("Could not get system session_info\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ guid_str = GUID_string(tmp_ctx, &guid);
+ if (!guid_str) {
+ DEBUG(0, ("Out of memory?!\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* We used to store this as a REG_BINARY but that causes
+ Vista to whine */
+
+ if (!push_reg_sz(tmp_ctx, &blob, guid_str)) {
+ DEBUG(0, ("Could not marshall string %s for objectGUID\n",
+ guid_str));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_set_printer_dataex_internal(tmp_ctx, session_info, msg_ctx,
+ printer,
+ SPOOL_DSSPOOLER_KEY, "objectGUID",
+ REG_SZ, blob.data, blob.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to store GUID for printer %s\n", printer));
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ talloc_free(tmp_ctx);
+
+ return result;
+}
+
+static WERROR nt_printer_dn_lookup(TALLOC_CTX *mem_ctx,
+ ADS_STRUCT *ads,
+ const char *printer,
+ char **pprinter_dn)
+{
+ char *printer_dn = NULL;
+ char *srv_dn = NULL;
+ char *srv_cn_0 = NULL;
+ char *srv_cn_escaped = NULL;
+ char *sharename_escaped = NULL;
+ char *srv_dn_utf8 = NULL;
+ char **srv_cn_utf8 = NULL;
+ size_t converted_size;
+ ADS_STATUS ads_status;
+ LDAPMessage *res;
+ WERROR result;
+ bool ok;
+
+ ads_status = ads_find_machine_acct(ads, &res, lp_netbios_name());
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(2, ("Failed to find machine account for %s\n",
+ lp_netbios_name()));
+ result = WERR_NOT_FOUND;
+ goto err_out;
+ }
+
+ /*
+ * We use ldap_get_dn here as we need the answer in utf8 to call
+ * ldap_explode_dn(). JRA.
+ */
+ srv_dn_utf8 = ldap_get_dn((LDAP *)ads->ldap.ld, (LDAPMessage *)res);
+ ads_msgfree(ads, res);
+ if (srv_dn_utf8 == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ srv_cn_utf8 = ldap_explode_dn(srv_dn_utf8, 1);
+ if (srv_cn_utf8 == NULL) {
+ ldap_memfree(srv_dn_utf8);
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ /* Now convert to CH_UNIX. */
+ ok = pull_utf8_talloc(mem_ctx, &srv_dn, srv_dn_utf8, &converted_size);
+ ldap_memfree(srv_dn_utf8);
+ if (!ok) {
+ ldap_memfree(srv_cn_utf8);
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ ok = pull_utf8_talloc(mem_ctx, &srv_cn_0, srv_cn_utf8[0], &converted_size);
+ ldap_memfree(srv_cn_utf8);
+ if (!ok) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ srv_cn_escaped = escape_rdn_val_string_alloc(srv_cn_0);
+ if (srv_cn_escaped == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ sharename_escaped = escape_rdn_val_string_alloc(printer);
+ if (sharename_escaped == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto err_out;
+ }
+
+ printer_dn = talloc_asprintf(mem_ctx,
+ "cn=%s-%s,%s",
+ srv_cn_escaped,
+ sharename_escaped,
+ srv_dn);
+ if (printer_dn == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ *pprinter_dn = printer_dn;
+
+ result = WERR_OK;
+err_out:
+ SAFE_FREE(sharename_escaped);
+ SAFE_FREE(srv_cn_escaped);
+ TALLOC_FREE(srv_cn_0);
+ TALLOC_FREE(srv_dn);
+ return result;
+}
+
+static WERROR nt_printer_guid_retrieve_internal(ADS_STRUCT *ads,
+ const char *printer_dn,
+ struct GUID *pguid)
+{
+ ADS_STATUS ads_status;
+ LDAPMessage *res;
+ const char *attrs[] = {"objectGUID", NULL};
+ struct GUID guid;
+ bool ok;
+
+ ads_status = ads_search_dn(ads, &res, printer_dn, attrs);
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(2, ("Failed to retrieve GUID from DC - %s\n",
+ ads_errstr(ads_status)));
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ ZERO_STRUCT(guid);
+ ok = ads_pull_guid(ads, res, &guid);
+ ads_msgfree(ads, res);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *pguid = guid;
+
+ return WERR_OK;
+}
+
+WERROR nt_printer_guid_retrieve(TALLOC_CTX *mem_ctx, const char *printer,
+ struct GUID *pguid)
+{
+ ADS_STRUCT *ads = NULL;
+ char *old_krb5ccname = NULL;
+ char *printer_dn;
+ WERROR result;
+ ADS_STATUS ads_status;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ char *machine_password = NULL;
+
+ ads = ads_init(tmp_ctx,
+ lp_realm(),
+ lp_workgroup(),
+ NULL,
+ ADS_SASL_PLAIN);
+ if (ads == NULL) {
+ result = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto out;
+ }
+
+ old_krb5ccname = getenv(KRB5_ENV_CCNAME);
+ setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1);
+ ADS_TALLOC_CONST_FREE(ads->auth.password);
+ machine_password = secrets_fetch_machine_password(lp_workgroup(),
+ NULL, NULL);
+ if (machine_password != NULL) {
+ ads->auth.password = talloc_strdup(ads, machine_password);
+ SAFE_FREE(machine_password);
+ if (ads->auth.password == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+ }
+
+ ads_status = ads_connect(ads);
+ if (!ADS_ERR_OK(ads_status)) {
+ DEBUG(3, ("ads_connect failed: %s\n", ads_errstr(ads_status)));
+ result = WERR_ACCESS_DENIED;
+ goto out;
+ }
+
+ result = nt_printer_dn_lookup(tmp_ctx, ads, printer, &printer_dn);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ result = nt_printer_guid_retrieve_internal(ads, printer_dn, pguid);
+out:
+ TALLOC_FREE(tmp_ctx);
+ ads_kdestroy("MEMORY:prtpub_cache");
+ unsetenv(KRB5_ENV_CCNAME);
+ if (old_krb5ccname != NULL) {
+ setenv(KRB5_ENV_CCNAME, old_krb5ccname, 0);
+ }
+
+ return result;
+}
+
+WERROR nt_printer_guid_get(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer, struct GUID *guid)
+{
+ TALLOC_CTX *tmp_ctx;
+ enum winreg_Type type;
+ DATA_BLOB blob;
+ uint32_t len;
+ NTSTATUS status;
+ WERROR result;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ DEBUG(0, ("out of memory?!\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_get_printer_dataex_internal(tmp_ctx, session_info,
+ msg_ctx, printer,
+ SPOOL_DSSPOOLER_KEY,
+ "objectGUID",
+ &type,
+ &blob.data,
+ &len);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to get GUID for printer %s\n", printer));
+ goto out_ctx_free;
+ }
+ blob.length = (size_t)len;
+
+ /* We used to store the guid as REG_BINARY, then swapped
+ to REG_SZ for Vista compatibility so check for both */
+
+ switch (type) {
+ case REG_SZ: {
+ bool ok;
+ const char *guid_str;
+ ok = pull_reg_sz(tmp_ctx, &blob, &guid_str);
+ if (!ok) {
+ DEBUG(0, ("Failed to unmarshall GUID for printer %s\n",
+ printer));
+ result = WERR_REGISTRY_CORRUPT;
+ goto out_ctx_free;
+ }
+ status = GUID_from_string(guid_str, guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("bad GUID for printer %s\n", printer));
+ result = ntstatus_to_werror(status);
+ goto out_ctx_free;
+ }
+ break;
+ }
+ case REG_BINARY:
+ if (blob.length != sizeof(struct GUID)) {
+ DEBUG(0, ("bad GUID for printer %s\n", printer));
+ result = WERR_REGISTRY_CORRUPT;
+ goto out_ctx_free;
+ }
+ memcpy(guid, blob.data, sizeof(struct GUID));
+ break;
+ default:
+ DEBUG(0,("GUID value stored as invalid type (%d)\n", type));
+ result = WERR_REGISTRY_CORRUPT;
+ goto out_ctx_free;
+ break;
+ }
+ result = WERR_OK;
+
+out_ctx_free:
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+static WERROR nt_printer_devmode_to_mods(TALLOC_CTX *ctx,
+ struct spoolss_DeviceMode *devmode,
+ ADS_MODLIST *mods)
+{
+ char *str = NULL;
+ ADS_STATUS status;
+
+ /*
+ the device mode fields bits allow us to make an educated guess if a
+ printer feature is supported. For sure a feature must be unsupported if
+ the fields bit is not set. Device Mode Extra Data and FeatureOptionPairs
+ might help to figure out more information here. Common attributes, that
+ we can't handle yet:
+ SPOOL_REG_PRINTBINNAMES - printBinNames
+ SPOOL_REG_PRINTMAXXEXTENT - printMaxXExtent
+ SPOOL_REG_PRINTMAXYEXTENT - printMaxYExtent
+ SPOOL_REG_PRINTMINXEXTENT - printMinXExtent
+ SPOOL_REG_PRINTMINYEXTENT - printMinYExtent
+ SPOOL_REG_PRINTSTAPLINGSUPPORTED - printStaplingSupported
+ SPOOL_REG_PRINTPAGESPERMINUTE - printPagesPerMinute
+ SPOOL_REG_PRINTRATE - printRate
+ SPOOL_REG_PRINTRATEUNIT - printRateUnit
+ SPOOL_REG_PRINTMEDIAREADY - printMediaReady
+ SPOOL_REG_PRINTMEDIASUPPORTED - printMediaSupported
+ SPOOL_REG_PRINTNUMBERUP - printNumberUp
+ SPOOL_REG_PRINTMAXCOPIES - printMaxCopies
+ */
+ if (devmode->fields & DEVMODE_COLOR) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLOR, "TRUE");
+ } else {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLOR, "FALSE");
+ }
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (devmode->fields & DEVMODE_DUPLEX) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTDUPLEXSUPPORTED, "TRUE");
+ } else {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTDUPLEXSUPPORTED, "FALSE");
+ }
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (devmode->fields & DEVMODE_COLLATE) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLLATE, "TRUE");
+ } else {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTCOLLATE, "FALSE");
+ }
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* portrait mode is always supported, LANDSCAPE is optional */
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTORIENTATIONSSUPPORTED, "PORTRAIT");
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (devmode->fields & DEVMODE_ORIENTATION) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTORIENTATIONSSUPPORTED, "LANDSCAPE");
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ /* the driverVersion attribute in AD contains actually specversion */
+ str = talloc_asprintf(ctx, "%u", devmode->specversion);
+ if (str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (strlen(str) != 0) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_DRIVERVERSION, str);
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ /* devmode->yresolution is a good candidate for printMaxResolutionSupported */
+ str = talloc_asprintf(ctx, "%u", devmode->yresolution);
+ if (str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (strlen(str) != 0) {
+ status = ads_mod_str(ctx, mods, SPOOL_REG_PRINTMAXRESOLUTIONSUPPORTED, str);
+ if (!ADS_ERR_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return WERR_OK;
+}
+
+
+
+static WERROR nt_printer_info_to_mods(TALLOC_CTX *ctx,
+ struct spoolss_PrinterInfo2 *info2,
+ ADS_MODLIST *mods)
+{
+ char *info_str;
+
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTERNAME, info2->sharename);
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSHARENAME, info2->sharename);
+ ads_mod_str(ctx, mods, SPOOL_REG_SHORTSERVERNAME, lp_netbios_name());
+ ads_mod_str(ctx, mods, SPOOL_REG_SERVERNAME, get_mydnsfullname());
+
+ info_str = talloc_asprintf(ctx, "\\\\%s\\%s",
+ get_mydnsfullname(), info2->sharename);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_UNCNAME, info_str);
+
+ info_str = talloc_asprintf(ctx, "%d", 4);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_VERSIONNUMBER, info_str);
+
+ /* empty strings in the mods list result in an attribute error */
+ if (strlen(info2->drivername) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_DRIVERNAME, info2->drivername);
+ if (strlen(info2->location) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_LOCATION, info2->location);
+ if (strlen(info2->comment) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_DESCRIPTION, info2->comment);
+ if (strlen(info2->portname) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_PORTNAME, info2->portname);
+ if (strlen(info2->sepfile) != 0)
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSEPARATORFILE, info2->sepfile);
+
+ info_str = talloc_asprintf(ctx, "%u", info2->starttime);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSTARTTIME, info_str);
+
+ info_str = talloc_asprintf(ctx, "%u", info2->untiltime);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTENDTIME, info_str);
+
+ info_str = talloc_asprintf(ctx, "%u", info2->priority);
+ if (info_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_mod_str(ctx, mods, SPOOL_REG_PRIORITY, info_str);
+
+ if (info2->attributes & PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS) {
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTKEEPPRINTEDJOBS, "TRUE");
+ } else {
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTKEEPPRINTEDJOBS, "FALSE");
+ }
+
+ switch (info2->attributes & 0x3) {
+ case 0:
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSPOOLING,
+ SPOOL_REGVAL_PRINTWHILESPOOLING);
+ break;
+ case 1:
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSPOOLING,
+ SPOOL_REGVAL_PRINTAFTERSPOOLED);
+ break;
+ case 2:
+ ads_mod_str(ctx, mods, SPOOL_REG_PRINTSPOOLING,
+ SPOOL_REGVAL_PRINTDIRECT);
+ break;
+ default:
+ DEBUG(3, ("unsupported printer attributes %x\n",
+ info2->attributes));
+ }
+
+ if (info2->devmode != NULL) {
+ WERROR werr;
+ werr = nt_printer_devmode_to_mods(ctx, info2->devmode, mods);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR nt_printer_publish_ads(struct messaging_context *msg_ctx,
+ ADS_STRUCT *ads,
+ struct spoolss_PrinterInfo2 *pinfo2)
+{
+ ADS_STATUS ads_rc;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ ADS_MODLIST mods;
+ struct GUID guid;
+ WERROR win_rc = WERR_OK;
+ const char *printer = pinfo2->sharename;
+ char *printer_dn = NULL;
+
+ /* build the ads mods */
+ DEBUG(5, ("publishing printer %s\n", printer));
+
+ win_rc = nt_printer_dn_lookup(ctx, ads, printer, &printer_dn);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ DEBUG(2, ("Failed to create printer dn\n"));
+ TALLOC_FREE(ctx);
+ return win_rc;
+ }
+
+ mods = ads_init_mods(ctx);
+
+ if (mods == NULL) {
+ TALLOC_FREE(ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ win_rc = nt_printer_info_to_mods(ctx, pinfo2, &mods);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ TALLOC_FREE(ctx);
+ return win_rc;
+ }
+
+ /* publish it */
+ ads_rc = ads_mod_printer_entry(ads, printer_dn, ctx, &mods);
+ if (ads_rc.err.rc == LDAP_NO_SUCH_OBJECT) {
+ int i;
+ for (i=0; mods[i] != 0; i++)
+ ;
+ mods[i] = (LDAPMod *)-1;
+ ads_rc = ads_add_printer_entry(ads, printer_dn, ctx, &mods);
+ }
+
+ if (!ADS_ERR_OK(ads_rc)) {
+ DEBUG(3, ("error publishing %s: %s\n",
+ printer, ads_errstr(ads_rc)));
+ /* XXX failed to publish, so no guid to retrieve */
+ }
+
+ win_rc = nt_printer_guid_retrieve_internal(ads, printer_dn, &guid);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ TALLOC_FREE(ctx);
+ return win_rc;
+ }
+
+ win_rc = nt_printer_guid_store(msg_ctx, printer, guid);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ DEBUG(3, ("failed to store printer %s guid\n",
+ printer));
+ /* not catastrophic, retrieve on next use */
+ win_rc = WERR_OK;
+ }
+
+ TALLOC_FREE(ctx);
+
+ return win_rc;
+}
+
+static WERROR nt_printer_unpublish_ads(ADS_STRUCT *ads,
+ const char *printer)
+{
+ ADS_STATUS ads_rc;
+ LDAPMessage *res = NULL;
+ char *prt_dn = NULL;
+
+ DEBUG(5, ("unpublishing printer %s\n", printer));
+
+ /* remove the printer from the directory */
+ ads_rc = ads_find_printer_on_server(ads, &res,
+ printer, lp_netbios_name());
+
+ if (ADS_ERR_OK(ads_rc) && res && ads_count_replies(ads, res)) {
+ prt_dn = ads_get_dn(ads, talloc_tos(), res);
+ if (!prt_dn) {
+ ads_msgfree(ads, res);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ads_rc = ads_del_dn(ads, prt_dn);
+ TALLOC_FREE(prt_dn);
+ }
+
+ if (res) {
+ ads_msgfree(ads, res);
+ }
+ return WERR_OK;
+}
+
+/****************************************************************************
+ * Publish a printer in the directory
+ *
+ * @param mem_ctx memory context
+ * @param session_info session_info to access winreg pipe
+ * @param pinfo2 printer information
+ * @param action publish/unpublish action
+ * @return WERROR indicating status of publishing
+ ***************************************************************************/
+
+WERROR nt_printer_publish(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ int action)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ uint32_t info2_mask = SPOOLSS_PRINTER_INFO_ATTRIBUTES;
+ struct spoolss_SetPrinterInfo2 *sinfo2;
+ ADS_STATUS ads_rc;
+ ADS_STRUCT *ads = NULL;
+ WERROR win_rc;
+ char *old_krb5ccname = NULL;
+ char *machine_password = NULL;
+
+ sinfo2 = talloc_zero(tmp_ctx, struct spoolss_SetPrinterInfo2);
+ if (!sinfo2) {
+ win_rc = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ switch (action) {
+ case DSPRINT_PUBLISH:
+ case DSPRINT_UPDATE:
+ pinfo2->attributes |= PRINTER_ATTRIBUTE_PUBLISHED;
+ break;
+ case DSPRINT_UNPUBLISH:
+ pinfo2->attributes &= (~PRINTER_ATTRIBUTE_PUBLISHED);
+ break;
+ default:
+ win_rc = WERR_NOT_SUPPORTED;
+ goto done;
+ }
+
+ sinfo2->attributes = pinfo2->attributes;
+
+ win_rc = winreg_update_printer_internal(tmp_ctx, session_info, msg_ctx,
+ pinfo2->sharename, info2_mask,
+ sinfo2, NULL, NULL);
+ if (!W_ERROR_IS_OK(win_rc)) {
+ DBG_NOTICE("Failed to update data for printer [%s] - %s\n",
+ pinfo2->sharename,
+ win_errstr(win_rc));
+ goto done;
+ }
+
+ TALLOC_FREE(sinfo2);
+
+ ads = ads_init(tmp_ctx,
+ lp_realm(),
+ lp_workgroup(),
+ NULL,
+ ADS_SASL_PLAIN);
+ if (!ads) {
+ DEBUG(3, ("ads_init() failed\n"));
+ win_rc = WERR_RPC_S_SERVER_UNAVAILABLE;
+ goto done;
+ }
+ old_krb5ccname = getenv(KRB5_ENV_CCNAME);
+ setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1);
+ ADS_TALLOC_CONST_FREE(ads->auth.password);
+ machine_password = secrets_fetch_machine_password(lp_workgroup(),
+ NULL, NULL);
+ if (machine_password != NULL) {
+ ads->auth.password = talloc_strdup(ads, machine_password);
+ SAFE_FREE(machine_password);
+ if (ads->auth.password == NULL) {
+ win_rc = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+
+ /* ads_connect() will find the DC for us */
+ ads_rc = ads_connect(ads);
+ if (!ADS_ERR_OK(ads_rc)) {
+ DEBUG(3, ("ads_connect failed: %s\n", ads_errstr(ads_rc)));
+ win_rc = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ switch (action) {
+ case DSPRINT_PUBLISH:
+ case DSPRINT_UPDATE:
+ win_rc = nt_printer_publish_ads(msg_ctx, ads, pinfo2);
+ break;
+ case DSPRINT_UNPUBLISH:
+ win_rc = nt_printer_unpublish_ads(ads, pinfo2->sharename);
+ break;
+ }
+
+done:
+ ads_kdestroy("MEMORY:prtpub_cache");
+ unsetenv(KRB5_ENV_CCNAME);
+ if (old_krb5ccname) {
+ setenv(KRB5_ENV_CCNAME, old_krb5ccname, 0);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+
+ return win_rc;
+}
+
+WERROR check_published_printers(struct messaging_context *msg_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ ADS_STATUS ads_rc;
+ ADS_STRUCT *ads = NULL;
+ int snum;
+ int n_services = lp_numservices();
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct auth_session_info *session_info = NULL;
+ struct spoolss_PrinterInfo2 *pinfo2;
+ NTSTATUS status;
+ WERROR result;
+ char *old_krb5ccname = NULL;
+ char *machine_password = NULL;
+
+ ads = ads_init(tmp_ctx,
+ lp_realm(),
+ lp_workgroup(),
+ NULL,
+ ADS_SASL_PLAIN);
+ if (!ads) {
+ DEBUG(3, ("ads_init() failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return WERR_RPC_S_SERVER_UNAVAILABLE;
+ }
+ old_krb5ccname = getenv(KRB5_ENV_CCNAME);
+ setenv(KRB5_ENV_CCNAME, "MEMORY:prtpub_cache", 1);
+ ADS_TALLOC_CONST_FREE(ads->auth.password);
+ machine_password = secrets_fetch_machine_password(lp_workgroup(),
+ NULL, NULL);
+ if (machine_password != NULL) {
+ ads->auth.password = talloc_strdup(ads, machine_password);
+ SAFE_FREE(machine_password);
+ if (ads->auth.password == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+ /* ads_connect() will find the DC for us */
+ ads_rc = ads_connect(ads);
+ if (!ADS_ERR_OK(ads_rc)) {
+ DEBUG(3, ("ads_connect failed: %s\n", ads_errstr(ads_rc)));
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ status = make_session_info_system(tmp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("check_published_printers: "
+ "Could not create system session_info\n"));
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ for (snum = 0; snum < n_services; snum++) {
+ if (!lp_snum_ok(snum) || !lp_printable(snum)) {
+ continue;
+ }
+
+ result = winreg_get_printer_internal(tmp_ctx, session_info, msg_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ continue;
+ }
+
+ if (pinfo2->attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
+ nt_printer_publish_ads(msg_ctx, ads, pinfo2);
+ }
+
+ TALLOC_FREE(pinfo2);
+ }
+
+ result = WERR_OK;
+done:
+ ads_kdestroy("MEMORY:prtpub_cache");
+ unsetenv(KRB5_ENV_CCNAME);
+ if (old_krb5ccname) {
+ setenv(KRB5_ENV_CCNAME, old_krb5ccname, 0);
+ }
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+bool is_printer_published(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **info2)
+{
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+
+ result = winreg_printer_binding_handle(mem_ctx,
+ session_info,
+ msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ return false;
+ }
+
+ result = winreg_get_printer(mem_ctx, b,
+ printer, &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ return false;
+ }
+
+ if (!(pinfo2->attributes & PRINTER_ATTRIBUTE_PUBLISHED)) {
+ TALLOC_FREE(pinfo2);
+ return false;
+ }
+
+ if (info2) {
+ *info2 = talloc_move(mem_ctx, &pinfo2);
+ }
+ talloc_free(pinfo2);
+ return true;
+}
+#else
+WERROR nt_printer_guid_store(struct messaging_context *msg_ctx,
+ const char *printer, struct GUID guid)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR nt_printer_guid_retrieve(TALLOC_CTX *mem_ctx, const char *printer,
+ struct GUID *pguid)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR nt_printer_guid_get(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer, struct GUID *guid)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR nt_printer_publish(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ int action)
+{
+ return WERR_OK;
+}
+
+WERROR check_published_printers(struct messaging_context *msg_ctx)
+{
+ return WERR_OK;
+}
+
+bool is_printer_published(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **info2)
+{
+ return False;
+}
+#endif /* HAVE_ADS */
diff --git a/source3/printing/nt_printing_migrate.c b/source3/printing/nt_printing_migrate.c
new file mode 100644
index 0000000..f56aa70
--- /dev/null
+++ b/source3/printing/nt_printing_migrate.c
@@ -0,0 +1,386 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ * Copyright (C) Bjoern Baumbach <bb@sernet.de> 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing/nt_printing_migrate.h"
+
+#include "rpc_client/rpc_client.h"
+#include "librpc/gen_ndr/ndr_ntprinting.h"
+#include "librpc/gen_ndr/ndr_spoolss_c.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+
+static const char *driver_file_basename(const char *file)
+{
+ const char *basefile;
+
+ basefile = strrchr(file, '\\');
+ if (basefile == NULL) {
+ basefile = file;
+ } else {
+ basefile++;
+ }
+
+ return basefile;
+}
+
+NTSTATUS printing_tdb_migrate_form(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct ntprinting_form r;
+ struct spoolss_AddFormInfo1 f1;
+ DATA_BLOB blob;
+ WERROR result;
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_ntprinting_form);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("Form pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Don't migrate builtin forms */
+ if (r.flag == SPOOLSS_FORM_BUILTIN) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(2, ("Migrating Form: %s\n", key_name));
+
+ f1.form_name = key_name;
+ f1.flags = r.flag;
+
+ f1.size.width = r.width;
+ f1.size.height = r.length;
+
+ f1.area.top = r.top;
+ f1.area.right = r.right;
+ f1.area.bottom = r.bottom;
+ f1.area.left = r.left;
+
+ result = winreg_printer_addform1(mem_ctx,
+ b,
+ &f1);
+ if (W_ERROR_EQUAL(result, WERR_FILE_EXISTS)) {
+ /* Don't migrate form if it already exists. */
+ result = WERR_OK;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS printing_tdb_migrate_driver(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct ntprinting_driver r;
+ struct spoolss_AddDriverInfoCtr d;
+ struct spoolss_AddDriverInfo3 d3;
+ struct spoolss_StringArray a;
+ DATA_BLOB blob;
+ WERROR result;
+ const char *driver_name;
+ uint32_t driver_version;
+ int i;
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ if (do_string_conversion) {
+ r.string_flags = LIBNDR_FLAG_STR_ASCII;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_ntprinting_driver);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("Driver pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(2, ("Migrating Printer Driver: %s\n", key_name));
+
+ ZERO_STRUCT(d3);
+ ZERO_STRUCT(a);
+
+ /* remove paths from file names */
+ if (r.dependent_files != NULL) {
+ for (i = 0 ; r.dependent_files[i] != NULL; i++) {
+ r.dependent_files[i] = driver_file_basename(r.dependent_files[i]);
+ }
+ }
+ a.string = r.dependent_files;
+
+ r.driverpath = driver_file_basename(r.driverpath);
+ r.configfile = driver_file_basename(r.configfile);
+ r.datafile = driver_file_basename(r.datafile);
+ r.helpfile = driver_file_basename(r.helpfile);
+
+ d3.architecture = r.environment;
+ d3.config_file = r.configfile;
+ d3.data_file = r.datafile;
+ d3.default_datatype = r.defaultdatatype;
+ d3.dependent_files = &a;
+ d3.driver_path = r.driverpath;
+ d3.help_file = r.helpfile;
+ d3.monitor_name = r.monitorname;
+ d3.driver_name = r.name;
+ d3.version = r.version;
+
+ d.level = 3;
+ d.info.info3 = &d3;
+
+ result = winreg_add_driver(mem_ctx,
+ b,
+ &d,
+ &driver_name,
+ &driver_version);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS printing_tdb_migrate_printer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct ntprinting_printer r;
+ struct spoolss_SetPrinterInfo2 info2;
+ struct spoolss_DeviceMode dm;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ WERROR result;
+ int j;
+ uint32_t info2_mask = (SPOOLSS_PRINTER_INFO_ALL)
+ & ~SPOOLSS_PRINTER_INFO_SECDESC;
+
+ if (strequal(key_name, "printers")) {
+ return NT_STATUS_OK;
+ }
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ if (do_string_conversion) {
+ r.info.string_flags = LIBNDR_FLAG_STR_ASCII;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t) ndr_pull_ntprinting_printer);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("printer pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(2, ("Migrating Printer: %s\n", key_name));
+
+ ZERO_STRUCT(devmode_ctr);
+
+ /* Create printer info level 2 */
+ ZERO_STRUCT(info2);
+
+ info2.attributes = r.info.attributes;
+ info2.averageppm = r.info.averageppm;
+ info2.cjobs = r.info.cjobs;
+ info2.comment = r.info.comment;
+ info2.datatype = r.info.datatype;
+ info2.defaultpriority = r.info.default_priority;
+ info2.drivername = r.info.drivername;
+ info2.location = r.info.location;
+ info2.parameters = r.info.parameters;
+ info2.portname = r.info.portname;
+ info2.printername = r.info.printername;
+ info2.printprocessor = r.info.printprocessor;
+ info2.priority = r.info.priority;
+ info2.sepfile = r.info.sepfile;
+ info2.sharename = r.info.sharename;
+ info2.starttime = r.info.starttime;
+ info2.status = r.info.status;
+ info2.untiltime = r.info.untiltime;
+
+ /* Create Device Mode */
+ if (r.devmode == NULL) {
+ info2_mask &= ~SPOOLSS_PRINTER_INFO_DEVMODE;
+ } else {
+ ZERO_STRUCT(dm);
+
+ dm.bitsperpel = r.devmode->bitsperpel;
+ dm.collate = r.devmode->collate;
+ dm.color = r.devmode->color;
+ dm.copies = r.devmode->copies;
+ dm.defaultsource = r.devmode->defaultsource;
+ dm.devicename = r.devmode->devicename;
+ dm.displayflags = r.devmode->displayflags;
+ dm.displayfrequency = r.devmode->displayfrequency;
+ dm.dithertype = r.devmode->dithertype;
+ dm.driverversion = r.devmode->driverversion;
+ dm.duplex = r.devmode->duplex;
+ dm.fields = r.devmode->fields;
+ dm.formname = r.devmode->formname;
+ dm.icmintent = r.devmode->icmintent;
+ dm.icmmethod = r.devmode->icmmethod;
+ dm.logpixels = r.devmode->logpixels;
+ dm.mediatype = r.devmode->mediatype;
+ dm.orientation = r.devmode->orientation;
+ dm.panningheight = r.devmode->pelsheight;
+ dm.panningwidth = r.devmode->panningwidth;
+ dm.paperlength = r.devmode->paperlength;
+ dm.papersize = r.devmode->papersize;
+ dm.paperwidth = r.devmode->paperwidth;
+ dm.pelsheight = r.devmode->pelsheight;
+ dm.pelswidth = r.devmode->pelswidth;
+ dm.printquality = r.devmode->printquality;
+ dm.size = r.devmode->size;
+ dm.scale = r.devmode->scale;
+ dm.specversion = r.devmode->specversion;
+ dm.ttoption = r.devmode->ttoption;
+ dm.yresolution = r.devmode->yresolution;
+
+ if (r.devmode->nt_dev_private != NULL) {
+ dm.driverextra_data.data = r.devmode->nt_dev_private->data;
+ dm.driverextra_data.length = r.devmode->nt_dev_private->length;
+ dm.__driverextra_length = r.devmode->nt_dev_private->length;
+ }
+
+ devmode_ctr.devmode = &dm;
+ }
+
+ result = winreg_update_printer(mem_ctx, b,
+ key_name,
+ info2_mask,
+ &info2,
+ &dm,
+ NULL);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("SetPrinter(%s) level 2 refused -- %s.\n",
+ key_name, win_errstr(result)));
+ status = werror_to_ntstatus(result);
+ goto done;
+ }
+
+ /* migrate printerdata */
+ for (j = 0; j < r.count; j++) {
+ char *valuename;
+ const char *keyname;
+
+ if (r.printer_data[j].type == REG_NONE) {
+ continue;
+ }
+
+ keyname = r.printer_data[j].name;
+ valuename = strchr(keyname, '\\');
+ if (valuename == NULL) {
+ continue;
+ } else {
+ valuename[0] = '\0';
+ valuename++;
+ }
+
+ result = winreg_set_printer_dataex(mem_ctx, b,
+ key_name,
+ keyname,
+ valuename,
+ r.printer_data[j].type,
+ r.printer_data[j].data.data,
+ r.printer_data[j].data.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("SetPrinterDataEx: printer [%s], keyname [%s], "
+ "valuename [%s] refused -- %s.\n",
+ key_name, keyname, valuename,
+ win_errstr(result)));
+ status = werror_to_ntstatus(result);
+ break;
+ }
+ }
+
+ status = NT_STATUS_OK;
+ done:
+
+ return status;
+}
+
+NTSTATUS printing_tdb_migrate_secdesc(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length)
+{
+ struct dcerpc_binding_handle *b = winreg_pipe->binding_handle;
+ enum ndr_err_code ndr_err;
+ struct sec_desc_buf secdesc_ctr;
+ DATA_BLOB blob;
+ WERROR result;
+
+ if (strequal(key_name, "printers")) {
+ return NT_STATUS_OK;
+ }
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(secdesc_ctr);
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &secdesc_ctr,
+ (ndr_pull_flags_fn_t)ndr_pull_sec_desc_buf);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("security descriptor pull failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(2, ("Migrating Security Descriptor: %s\n", key_name));
+
+ result = winreg_set_printer_secdesc(mem_ctx, b,
+ key_name,
+ secdesc_ctr.sd);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/printing/nt_printing_migrate.h b/source3/printing/nt_printing_migrate.h
new file mode 100644
index 0000000..0c9800d
--- /dev/null
+++ b/source3/printing/nt_printing_migrate.h
@@ -0,0 +1,47 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NT_PRINTING_MIGRATE_H_
+#define _NT_PRINTING_MIGRATE_H_
+
+NTSTATUS printing_tdb_migrate_form(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length);
+NTSTATUS printing_tdb_migrate_driver(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion);
+NTSTATUS printing_tdb_migrate_printer(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion);
+NTSTATUS printing_tdb_migrate_secdesc(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *winreg_pipe,
+ const char *key_name,
+ unsigned char *data,
+ size_t length);
+
+#endif /* _NT_PRINTING_MIGRATE_H_ */
diff --git a/source3/printing/nt_printing_migrate_internal.c b/source3/printing/nt_printing_migrate_internal.c
new file mode 100644
index 0000000..8bcc2d4
--- /dev/null
+++ b/source3/printing/nt_printing_migrate_internal.c
@@ -0,0 +1,272 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/nt_printing_migrate.h"
+#include "printing/nt_printing_migrate_internal.h"
+
+#include "rpc_client/rpc_client.h"
+#include "librpc/gen_ndr/ndr_spoolss_c.h"
+#include "librpc/gen_ndr/ndr_winreg.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "auth.h"
+#include "util_tdb.h"
+
+#define FORMS_PREFIX "FORMS/"
+#define DRIVERS_PREFIX "DRIVERS/"
+#define PRINTERS_PREFIX "PRINTERS/"
+#define SECDESC_PREFIX "SECDESC/"
+
+static int rename_file_with_suffix(TALLOC_CTX *mem_ctx,
+ const char *path,
+ const char *suffix)
+{
+ int rc = -1;
+ char *dst_path;
+
+ dst_path = talloc_asprintf(mem_ctx, "%s%s", path, suffix);
+ if (dst_path == NULL) {
+ DEBUG(3, ("error out of memory\n"));
+ return rc;
+ }
+
+ rc = (rename(path, dst_path) != 0);
+
+ if (rc == 0) {
+ DEBUG(5, ("moved '%s' to '%s'\n", path, dst_path));
+ } else if (errno == ENOENT) {
+ DEBUG(3, ("file '%s' does not exist - so not moved\n", path));
+ rc = 0;
+ } else {
+ DEBUG(3, ("error renaming %s to %s: %s\n", path, dst_path,
+ strerror(errno)));
+ }
+
+ TALLOC_FREE(dst_path);
+ return rc;
+}
+
+static NTSTATUS migrate_internal(TALLOC_CTX *mem_ctx,
+ const char *tdb_path,
+ struct rpc_pipe_client *winreg_pipe)
+{
+ const char *backup_suffix = ".bak";
+ TDB_DATA kbuf, newkey, dbuf;
+ TDB_CONTEXT *tdb;
+ NTSTATUS status;
+ int rc;
+
+ tdb = tdb_open_log(tdb_path, 0, TDB_DEFAULT, O_RDONLY, 0600);
+ if (tdb == NULL && errno == ENOENT) {
+ /* if we have no printers database then migration is
+ considered successful */
+ DEBUG(4, ("No printers database to migrate in %s\n", tdb_path));
+ return NT_STATUS_OK;
+ }
+ if (tdb == NULL) {
+ DEBUG(2, ("Failed to open tdb file: %s\n", tdb_path));
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ for (kbuf = tdb_firstkey(tdb);
+ kbuf.dptr;
+ newkey = tdb_nextkey(tdb, kbuf), free(kbuf.dptr), kbuf = newkey)
+ {
+ dbuf = tdb_fetch(tdb, kbuf);
+ if (!dbuf.dptr) {
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, FORMS_PREFIX, strlen(FORMS_PREFIX)) == 0) {
+ status = printing_tdb_migrate_form(mem_ctx,
+ winreg_pipe,
+ (const char *) kbuf.dptr + strlen(FORMS_PREFIX),
+ dbuf.dptr,
+ dbuf.dsize);
+ SAFE_FREE(dbuf.dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, DRIVERS_PREFIX, strlen(DRIVERS_PREFIX)) == 0) {
+ status = printing_tdb_migrate_driver(mem_ctx,
+ winreg_pipe,
+ (const char *) kbuf.dptr + strlen(DRIVERS_PREFIX),
+ dbuf.dptr,
+ dbuf.dsize,
+ false);
+ SAFE_FREE(dbuf.dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, PRINTERS_PREFIX, strlen(PRINTERS_PREFIX)) == 0) {
+ const char *printer_name = (const char *)(kbuf.dptr
+ + strlen(PRINTERS_PREFIX));
+ status = printing_tdb_migrate_printer(mem_ctx,
+ winreg_pipe,
+ printer_name,
+ dbuf.dptr,
+ dbuf.dsize,
+ false);
+ SAFE_FREE(dbuf.dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+ SAFE_FREE(dbuf.dptr);
+ }
+
+ for (kbuf = tdb_firstkey(tdb);
+ kbuf.dptr;
+ newkey = tdb_nextkey(tdb, kbuf), free(kbuf.dptr), kbuf = newkey)
+ {
+ dbuf = tdb_fetch(tdb, kbuf);
+ if (!dbuf.dptr) {
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX)) == 0) {
+ const char *secdesc_name = (const char *)(kbuf.dptr
+ + strlen(SECDESC_PREFIX));
+ status = printing_tdb_migrate_secdesc(mem_ctx,
+ winreg_pipe,
+ secdesc_name,
+ dbuf.dptr,
+ dbuf.dsize);
+ SAFE_FREE(dbuf.dptr);
+ if (NT_STATUS_EQUAL(status, werror_to_ntstatus(WERR_FILE_NOT_FOUND))) {
+ DEBUG(2, ("Skipping secdesc migration for non-existent "
+ "printer: %s\n", secdesc_name));
+ } else if (!NT_STATUS_IS_OK(status)) {
+ tdb_close(tdb);
+ return status;
+ }
+ continue;
+ }
+ SAFE_FREE(dbuf.dptr);
+ }
+
+ tdb_close(tdb);
+
+ rc = rename_file_with_suffix(mem_ctx, tdb_path, backup_suffix);
+ if (rc != 0) {
+ DEBUG(0, ("Error moving tdb to '%s%s'\n",
+ tdb_path, backup_suffix));
+ }
+
+ return NT_STATUS_OK;
+}
+
+bool nt_printing_tdb_migrate(struct messaging_context *msg_ctx)
+{
+ const char *drivers_path;
+ const char *printers_path;
+ const char *forms_path;
+ bool drivers_exists;
+ bool printers_exists;
+ bool forms_exists;
+ struct auth_session_info *session_info;
+ struct rpc_pipe_client *winreg_pipe = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+
+ /* paths talloced on new stackframe */
+ drivers_path = state_path(talloc_tos(), "ntdrivers.tdb");
+ printers_path = state_path(talloc_tos(), "ntprinters.tdb");
+ forms_path = state_path(talloc_tos(), "ntforms.tdb");
+ if ((drivers_path == NULL) || (printers_path == NULL)
+ || (forms_path == NULL)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ drivers_exists = file_exist(drivers_path);
+ printers_exists = file_exist(printers_path);
+ forms_exists = file_exist(forms_path);
+
+ if (!drivers_exists && !printers_exists && !forms_exists) {
+ talloc_free(tmp_ctx);
+ return true;
+ }
+
+ status = make_session_info_system(tmp_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't create session_info: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ status = rpc_pipe_open_interface(tmp_ctx,
+ &ndr_table_winreg,
+ session_info,
+ NULL,
+ NULL,
+ msg_ctx,
+ &winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't open internal winreg pipe: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ if (drivers_exists) {
+ status = migrate_internal(tmp_ctx, drivers_path, winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't migrate drivers tdb file: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ }
+
+ if (printers_exists) {
+ status = migrate_internal(tmp_ctx, printers_path, winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't migrate printers tdb file: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ }
+
+ if (forms_exists) {
+ status = migrate_internal(tmp_ctx, forms_path, winreg_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Couldn't migrate forms tdb file: %s\n",
+ nt_errstr(status)));
+ talloc_free(tmp_ctx);
+ return false;
+ }
+ }
+
+ talloc_free(tmp_ctx);
+ return true;
+}
diff --git a/source3/printing/nt_printing_migrate_internal.h b/source3/printing/nt_printing_migrate_internal.h
new file mode 100644
index 0000000..dfcf914
--- /dev/null
+++ b/source3/printing/nt_printing_migrate_internal.h
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NT_PRINTING_MIGRATE_INTERNAL_H_
+#define _NT_PRINTING_MIGRATE_INTERNAL_H_
+
+bool nt_printing_tdb_migrate(struct messaging_context *msg_ctx);
+
+#endif /* _NT_PRINTING_MIGRATE_INTERNAL_H_ */
diff --git a/source3/printing/nt_printing_os2.c b/source3/printing/nt_printing_os2.c
new file mode 100644
index 0000000..82b8248
--- /dev/null
+++ b/source3/printing/nt_printing_os2.c
@@ -0,0 +1,167 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing/nt_printing_os2.h"
+
+/****************************************************************************
+ ***************************************************************************/
+
+static char *win_driver;
+static char *os2_driver;
+
+static const char *get_win_driver(void)
+{
+ if (win_driver == NULL) {
+ return "";
+ }
+ return win_driver;
+}
+
+static const char *get_os2_driver(void)
+{
+ if (os2_driver == NULL) {
+ return "";
+ }
+ return os2_driver;
+}
+
+static bool set_driver_mapping(const char *from, const char *to)
+{
+ SAFE_FREE(win_driver);
+ SAFE_FREE(os2_driver);
+
+ win_driver = SMB_STRDUP(from);
+ os2_driver = SMB_STRDUP(to);
+
+ if (win_driver == NULL || os2_driver == NULL) {
+ SAFE_FREE(win_driver);
+ SAFE_FREE(os2_driver);
+ return false;
+ }
+ return true;
+}
+
+/**
+ * @internal
+ *
+ * @brief Map a Windows driver to a OS/2 driver.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in,out] pdrivername The drivername of Windows to remap.
+ *
+ * @return WERR_OK on success, a corresponding WERROR on failure.
+ */
+WERROR spoolss_map_to_os2_driver(TALLOC_CTX *mem_ctx, const char **pdrivername)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *mapfile = lp_os2_driver_map(talloc_tos(), lp_sub);
+ char **lines = NULL;
+ const char *drivername;
+ int numlines = 0;
+ int i;
+
+ if (pdrivername == NULL || *pdrivername == NULL || *pdrivername[0] == '\0') {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ drivername = *pdrivername;
+
+ if (mapfile[0] == '\0') {
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ if (strequal(drivername, get_win_driver())) {
+ DEBUG(3,("Mapped Windows driver %s to OS/2 driver %s\n",
+ drivername, get_os2_driver()));
+ drivername = talloc_strdup(mem_ctx, get_os2_driver());
+ if (drivername == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *pdrivername = drivername;
+ return WERR_OK;
+ }
+
+ lines = file_lines_load(mapfile, &numlines, 0, NULL);
+ if (numlines == 0 || lines == NULL) {
+ DEBUG(0,("No entries in OS/2 driver map %s\n", mapfile));
+ TALLOC_FREE(lines);
+ return WERR_EMPTY;
+ }
+
+ DEBUG(4,("Scanning OS/2 driver map %s\n",mapfile));
+
+ for( i = 0; i < numlines; i++) {
+ char *nt_name = lines[i];
+ char *os2_name = strchr(nt_name, '=');
+
+ if (os2_name == NULL) {
+ continue;
+ }
+
+ *os2_name++ = '\0';
+
+ while (isspace(*nt_name)) {
+ nt_name++;
+ }
+
+ if (*nt_name == '\0' || strchr("#;", *nt_name)) {
+ continue;
+ }
+
+ {
+ int l = strlen(nt_name);
+ while (l && isspace(nt_name[l - 1])) {
+ nt_name[l - 1] = 0;
+ l--;
+ }
+ }
+
+ while (isspace(*os2_name)) {
+ os2_name++;
+ }
+
+ {
+ int l = strlen(os2_name);
+ while (l && isspace(os2_name[l-1])) {
+ os2_name[l-1] = 0;
+ l--;
+ }
+ }
+
+ if (strequal(nt_name, drivername)) {
+ DEBUG(3,("Mapped Windows driver %s to OS/2 driver %s\n",drivername,os2_name));
+ set_driver_mapping(drivername, os2_name);
+ drivername = talloc_strdup(mem_ctx, os2_name);
+ TALLOC_FREE(lines);
+ if (drivername == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *pdrivername = drivername;
+ return WERR_OK;
+ }
+ }
+
+ TALLOC_FREE(lines);
+ return WERR_OK;
+}
diff --git a/source3/printing/nt_printing_os2.h b/source3/printing/nt_printing_os2.h
new file mode 100644
index 0000000..0ef07de
--- /dev/null
+++ b/source3/printing/nt_printing_os2.h
@@ -0,0 +1,22 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000.
+ * Copyright (C) Gerald Carter 2002-2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+WERROR spoolss_map_to_os2_driver(TALLOC_CTX *mem_ctx, const char **pdrivername);
diff --git a/source3/printing/nt_printing_tdb.c b/source3/printing/nt_printing_tdb.c
new file mode 100644
index 0000000..eddefe5
--- /dev/null
+++ b/source3/printing/nt_printing_tdb.c
@@ -0,0 +1,498 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (c) Andrew Tridgell 1992-2000,
+ * Copyright (c) Jean François Micouleau 1998-2000.
+ * Copyright (c) Gerald Carter 2002-2005.
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/nt_printing_tdb.h"
+#include "librpc/gen_ndr/spoolss.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "libcli/security/security.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+#define FORMS_PREFIX "FORMS/"
+#define DRIVERS_PREFIX "DRIVERS/"
+#define PRINTERS_PREFIX "PRINTERS/"
+#define SECDESC_PREFIX "SECDESC/"
+
+#define NTDRIVERS_DATABASE_VERSION_1 1
+#define NTDRIVERS_DATABASE_VERSION_2 2
+#define NTDRIVERS_DATABASE_VERSION_3 3 /* little endian version of v2 */
+#define NTDRIVERS_DATABASE_VERSION_4 4 /* fix generic bits in security descriptors */
+#define NTDRIVERS_DATABASE_VERSION_5 5 /* normalize keys in ntprinters.tdb */
+
+static TDB_CONTEXT *tdb_forms; /* used for forms files */
+static TDB_CONTEXT *tdb_drivers; /* used for driver files */
+static TDB_CONTEXT *tdb_printers; /* used for printers files */
+
+/****************************************************************************
+ generate a new TDB_DATA key for storing a printer
+****************************************************************************/
+
+static TDB_DATA make_printer_tdbkey(TALLOC_CTX *ctx, const char *sharename )
+{
+ fstring share;
+ char *keystr = NULL;
+ TDB_DATA key;
+
+ fstrcpy(share, sharename);
+ (void)strlower_m(share);
+
+ keystr = talloc_asprintf(ctx, "%s%s", PRINTERS_PREFIX, share);
+ key = string_term_tdb_data(keystr ? keystr : "");
+
+ return key;
+}
+
+/****************************************************************************
+ generate a new TDB_DATA key for storing a printer security descriptor
+****************************************************************************/
+
+static TDB_DATA make_printers_secdesc_tdbkey(TALLOC_CTX *ctx,
+ const char* sharename )
+{
+ fstring share;
+ char *keystr = NULL;
+ TDB_DATA key;
+
+ fstrcpy(share, sharename );
+ (void)strlower_m(share);
+
+ keystr = talloc_asprintf(ctx, "%s%s", SECDESC_PREFIX, share);
+ key = string_term_tdb_data(keystr ? keystr : "");
+
+ return key;
+}
+
+/****************************************************************************
+ Upgrade the tdb files to version 3
+****************************************************************************/
+
+static bool upgrade_to_version_3(void)
+{
+ TDB_DATA kbuf, newkey, dbuf;
+
+ DEBUG(0,("upgrade_to_version_3: upgrading print tdb's to version 3\n"));
+
+ for (kbuf = tdb_firstkey(tdb_drivers); kbuf.dptr;
+ newkey = tdb_nextkey(tdb_drivers, kbuf), free(kbuf.dptr), kbuf=newkey) {
+
+ dbuf = tdb_fetch(tdb_drivers, kbuf);
+
+ if (strncmp((const char *)kbuf.dptr, FORMS_PREFIX, strlen(FORMS_PREFIX)) == 0) {
+ DEBUG(0,("upgrade_to_version_3:moving form\n"));
+ if (tdb_store(tdb_forms, kbuf, dbuf, TDB_REPLACE) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to move form. Error (%s).\n", tdb_errorstr(tdb_forms)));
+ return False;
+ }
+ if (tdb_delete(tdb_drivers, kbuf) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to delete form. Error (%s)\n", tdb_errorstr(tdb_drivers)));
+ return False;
+ }
+ }
+
+ if (strncmp((const char *)kbuf.dptr, PRINTERS_PREFIX, strlen(PRINTERS_PREFIX)) == 0) {
+ DEBUG(0,("upgrade_to_version_3:moving printer\n"));
+ if (tdb_store(tdb_printers, kbuf, dbuf, TDB_REPLACE) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to move printer. Error (%s)\n", tdb_errorstr(tdb_printers)));
+ return False;
+ }
+ if (tdb_delete(tdb_drivers, kbuf) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to delete printer. Error (%s)\n", tdb_errorstr(tdb_drivers)));
+ return False;
+ }
+ }
+
+ if (strncmp((const char *)kbuf.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX)) == 0) {
+ DEBUG(0,("upgrade_to_version_3:moving secdesc\n"));
+ if (tdb_store(tdb_printers, kbuf, dbuf, TDB_REPLACE) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to move secdesc. Error (%s)\n", tdb_errorstr(tdb_printers)));
+ return False;
+ }
+ if (tdb_delete(tdb_drivers, kbuf) != 0) {
+ SAFE_FREE(dbuf.dptr);
+ DEBUG(0,("upgrade_to_version_3: failed to delete secdesc. Error (%s)\n", tdb_errorstr(tdb_drivers)));
+ return False;
+ }
+ }
+
+ SAFE_FREE(dbuf.dptr);
+ }
+
+ return True;
+}
+
+/*******************************************************************
+ Fix an issue with security descriptors. Printer sec_desc must
+ use more than the generic bits that were previously used
+ in <= 3.0.14a. They must also have a owner and group SID assigned.
+ Otherwise, any printers than have been migrated to a Windows
+ host using printmig.exe will not be accessible.
+*******************************************************************/
+
+static int sec_desc_upg_fn( TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *state )
+{
+ NTSTATUS status;
+ struct sec_desc_buf *sd_orig = NULL;
+ struct sec_desc_buf *sd_new, *sd_store;
+ struct security_descriptor *sec, *new_sec;
+ TALLOC_CTX *ctx = state;
+ int result, i;
+ size_t size_new_sec;
+
+ if (!data.dptr || data.dsize == 0) {
+ return 0;
+ }
+
+ if ( strncmp((const char *) key.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX) ) != 0 ) {
+ return 0;
+ }
+
+ /* upgrade the security descriptor */
+
+ status = unmarshall_sec_desc_buf(ctx, data.dptr, data.dsize, &sd_orig);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* delete bad entries */
+ DEBUG(0,("sec_desc_upg_fn: Failed to parse original sec_desc for %si. Deleting....\n",
+ (const char *)key.dptr ));
+ tdb_delete( tdb_printers, key );
+ return 0;
+ }
+
+ if (!sd_orig) {
+ return 0;
+ }
+ sec = sd_orig->sd;
+
+ /* is this even valid? */
+
+ if ( !sec->dacl ) {
+ return 0;
+ }
+
+ /* update access masks */
+
+ for ( i=0; i<sec->dacl->num_aces; i++ ) {
+ switch ( sec->dacl->aces[i].access_mask ) {
+ case (GENERIC_READ_ACCESS | GENERIC_WRITE_ACCESS | GENERIC_EXECUTE_ACCESS):
+ sec->dacl->aces[i].access_mask = PRINTER_ACE_PRINT;
+ break;
+
+ case GENERIC_ALL_ACCESS:
+ sec->dacl->aces[i].access_mask = PRINTER_ACE_FULL_CONTROL;
+ break;
+
+ case READ_CONTROL_ACCESS:
+ sec->dacl->aces[i].access_mask = PRINTER_ACE_MANAGE_DOCUMENTS;
+
+ break;
+ default: /* no change */
+ break;
+ }
+ }
+
+ /* create a new struct security_descriptor with the appropriate owner and group SIDs */
+
+ new_sec = make_sec_desc( ctx, SD_REVISION, SEC_DESC_SELF_RELATIVE,
+ &global_sid_Builtin_Administrators,
+ &global_sid_Builtin_Administrators,
+ NULL, NULL, &size_new_sec );
+ if (!new_sec) {
+ return 0;
+ }
+ sd_new = make_sec_desc_buf( ctx, size_new_sec, new_sec );
+ if (!sd_new) {
+ return 0;
+ }
+
+ if ( !(sd_store = sec_desc_merge_buf( ctx, sd_new, sd_orig )) ) {
+ DEBUG(0,("sec_desc_upg_fn: Failed to update sec_desc for %s\n", key.dptr ));
+ return 0;
+ }
+
+ /* store it back */
+
+ status = marshall_sec_desc_buf(ctx, sd_store, &data.dptr, &data.dsize);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("sec_desc_upg_fn: Failed to parse new sec_desc for %s\n", key.dptr ));
+ return 0;
+ }
+
+ result = tdb_store( tdb_printers, key, data, TDB_REPLACE );
+
+ /* 0 to continue and non-zero to stop traversal */
+
+ return (result != 0);
+}
+
+/*******************************************************************
+ Upgrade the tdb files to version 4
+*******************************************************************/
+
+static bool upgrade_to_version_4(void)
+{
+ TALLOC_CTX *ctx;
+ int result;
+
+ DEBUG(0,("upgrade_to_version_4: upgrading printer security descriptors\n"));
+
+ if ( !(ctx = talloc_init( "upgrade_to_version_4" )) )
+ return False;
+
+ result = tdb_traverse( tdb_printers, sec_desc_upg_fn, ctx );
+
+ talloc_destroy( ctx );
+
+ return ( result >= 0 );
+}
+
+/*******************************************************************
+ Fix an issue with security descriptors. Printer sec_desc must
+ use more than the generic bits that were previously used
+ in <= 3.0.14a. They must also have a owner and group SID assigned.
+ Otherwise, any printers than have been migrated to a Windows
+ host using printmig.exe will not be accessible.
+*******************************************************************/
+
+static int normalize_printers_fn( TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *state )
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ TDB_DATA new_key;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ /* upgrade printer records and security descriptors */
+
+ if ( strncmp((const char *) key.dptr, PRINTERS_PREFIX, strlen(PRINTERS_PREFIX) ) == 0 ) {
+ new_key = make_printer_tdbkey(ctx, (const char *)key.dptr+strlen(PRINTERS_PREFIX) );
+ }
+ else if ( strncmp((const char *) key.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX) ) == 0 ) {
+ new_key = make_printers_secdesc_tdbkey(ctx, (const char *)key.dptr+strlen(SECDESC_PREFIX) );
+ }
+ else {
+ /* ignore this record */
+ return 0;
+ }
+
+ /* delete the original record and store under the normalized key */
+
+ if ( tdb_delete( the_tdb, key ) != 0 ) {
+ DEBUG(0,("normalize_printers_fn: tdb_delete for [%s] failed!\n",
+ key.dptr));
+ return 1;
+ }
+
+ if ( tdb_store( the_tdb, new_key, data, TDB_REPLACE) != 0 ) {
+ DEBUG(0,("normalize_printers_fn: failed to store new record for [%s]!\n",
+ key.dptr));
+ return 1;
+ }
+
+ return 0;
+}
+
+/*******************************************************************
+ Upgrade the tdb files to version 5
+*******************************************************************/
+
+static bool upgrade_to_version_5(void)
+{
+ TALLOC_CTX *ctx;
+ int result;
+
+ DEBUG(0,("upgrade_to_version_5: normalizing printer keys\n"));
+
+ if ( !(ctx = talloc_init( "upgrade_to_version_5" )) )
+ return False;
+
+ result = tdb_traverse( tdb_printers, normalize_printers_fn, NULL );
+
+ talloc_destroy( ctx );
+
+ return ( result >= 0 );
+}
+
+bool nt_printing_tdb_upgrade(void)
+{
+ char *drivers_path;
+ char *printers_path;
+ char *forms_path;
+ bool drivers_exists;
+ bool printers_exists;
+ bool forms_exists;
+ const char *vstring = "INFO/version";
+ int32_t vers_id;
+ bool ret;
+
+ drivers_path = state_path(talloc_tos(), "ntdrivers.tdb");
+ if (drivers_path == NULL) {
+ ret = false;
+ goto err_out;
+ }
+ printers_path = state_path(talloc_tos(), "ntprinters.tdb");
+ if (printers_path == NULL) {
+ ret = false;
+ goto err_drvdb_free;
+ }
+ forms_path = state_path(talloc_tos(), "ntforms.tdb");
+ if (forms_path == NULL) {
+ ret = false;
+ goto err_prdb_free;
+ }
+
+ drivers_exists = file_exist(drivers_path);
+ printers_exists = file_exist(printers_path);
+ forms_exists = file_exist(forms_path);
+
+ if (!drivers_exists && !printers_exists && !forms_exists) {
+ ret = true;
+ goto err_formsdb_free;
+ }
+
+ tdb_drivers = tdb_open_log(drivers_path,
+ 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT,
+ 0600);
+ if (tdb_drivers == NULL) {
+ DEBUG(0,("nt_printing_init: Failed to open nt drivers "
+ "database %s (%s)\n",
+ drivers_path, strerror(errno)));
+ ret = false;
+ goto err_formsdb_free;
+ }
+
+ tdb_printers = tdb_open_log(printers_path,
+ 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT,
+ 0600);
+ if (tdb_printers == NULL) {
+ DEBUG(0,("nt_printing_init: Failed to open nt printers "
+ "database %s (%s)\n",
+ printers_path, strerror(errno)));
+ ret = false;
+ goto err_drvdb_close;
+ }
+
+ tdb_forms = tdb_open_log(forms_path,
+ 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT,
+ 0600);
+ if (tdb_forms == NULL) {
+ DEBUG(0,("nt_printing_init: Failed to open nt forms "
+ "database %s (%s)\n",
+ forms_path, strerror(errno)));
+ ret = false;
+ goto err_prdb_close;
+ }
+
+ /* Samba upgrade */
+ vers_id = tdb_fetch_int32(tdb_drivers, vstring);
+ if (vers_id == -1) {
+ DEBUG(10, ("Fresh database\n"));
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_5);
+ vers_id = NTDRIVERS_DATABASE_VERSION_5;
+ }
+
+ if (vers_id != NTDRIVERS_DATABASE_VERSION_5) {
+ if ((vers_id == NTDRIVERS_DATABASE_VERSION_1) ||
+ (IREV(vers_id) == NTDRIVERS_DATABASE_VERSION_1)) {
+ if (!upgrade_to_version_3()) {
+ ret = false;
+ goto err_formsdb_close;
+ }
+
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_3);
+ vers_id = NTDRIVERS_DATABASE_VERSION_3;
+ }
+
+ if ((vers_id == NTDRIVERS_DATABASE_VERSION_2) ||
+ (IREV(vers_id) == NTDRIVERS_DATABASE_VERSION_2)) {
+ /*
+ * Written on a bigendian machine with old fetch_int
+ * code. Save as le. The only upgrade between V2 and V3
+ * is to save the version in little-endian.
+ */
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_3);
+ vers_id = NTDRIVERS_DATABASE_VERSION_3;
+ }
+
+ if (vers_id == NTDRIVERS_DATABASE_VERSION_3) {
+ if (!upgrade_to_version_4()) {
+ ret = false;
+ goto err_formsdb_close;
+ }
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_4);
+ vers_id = NTDRIVERS_DATABASE_VERSION_4;
+ }
+
+ if (vers_id == NTDRIVERS_DATABASE_VERSION_4 ) {
+ if (!upgrade_to_version_5()) {
+ ret = false;
+ goto err_formsdb_close;
+ }
+ tdb_store_int32(tdb_drivers, vstring, NTDRIVERS_DATABASE_VERSION_5);
+ vers_id = NTDRIVERS_DATABASE_VERSION_5;
+ }
+
+ if (vers_id != NTDRIVERS_DATABASE_VERSION_5) {
+ DEBUG(0,("nt_printing_init: Unknown printer database version [%d]\n", vers_id));
+ ret = false;
+ goto err_formsdb_close;
+ }
+ }
+ ret = true;
+
+err_formsdb_close:
+ if (tdb_forms) {
+ tdb_close(tdb_forms);
+ tdb_forms = NULL;
+ }
+err_prdb_close:
+ if (tdb_printers) {
+ tdb_close(tdb_printers);
+ tdb_printers = NULL;
+ }
+err_drvdb_close:
+ if (tdb_drivers) {
+ tdb_close(tdb_drivers);
+ tdb_drivers = NULL;
+ }
+err_formsdb_free:
+ talloc_free(forms_path);
+err_prdb_free:
+ talloc_free(printers_path);
+err_drvdb_free:
+ talloc_free(drivers_path);
+err_out:
+ return ret;
+}
diff --git a/source3/printing/nt_printing_tdb.h b/source3/printing/nt_printing_tdb.h
new file mode 100644
index 0000000..81e1813
--- /dev/null
+++ b/source3/printing/nt_printing_tdb.h
@@ -0,0 +1,28 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (c) Andrew Tridgell 1992-2000,
+ * Copyright (c) Jean François Micouleau 1998-2000.
+ * Copyright (c) Gerald Carter 2002-2005.
+ * Copyright (c) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NT_PRINTING_TDB_H_
+#define _NT_PRINTING_TDB_H_
+
+bool nt_printing_tdb_upgrade(void);
+
+#endif /* _NT_PRINTING_TDB_H_ */
diff --git a/source3/printing/pcap.c b/source3/printing/pcap.c
new file mode 100644
index 0000000..ec68d29
--- /dev/null
+++ b/source3/printing/pcap.c
@@ -0,0 +1,213 @@
+/*
+ Unix SMB/CIFS implementation.
+ printcap parsing
+ Copyright (C) Karl Auer 1993-1998
+
+ Re-working by Martin Kiff, 1994
+
+ Re-written again by Andrew Tridgell
+
+ Modified for SVID support by Norm Jacobs, 1997
+
+ Modified for CUPS support by Michael Sweet, 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Modified to call SVID/XPG4 support if printcap name is set to "lpstat"
+ * in smb.conf under Solaris.
+ *
+ * Modified to call CUPS support if printcap name is set to "cups"
+ * in smb.conf.
+ *
+ * Modified to call iPrint support if printcap name is set to "iprint"
+ * in smb.conf.
+ */
+
+#include "includes.h"
+#include "printing/pcap.h"
+#include "printer_list.h"
+
+struct pcap_cache {
+ char *name;
+ char *comment;
+ char *location;
+ struct pcap_cache *next;
+};
+
+bool pcap_cache_add_specific(struct pcap_cache **ppcache, const char *name, const char *comment, const char *location)
+{
+ struct pcap_cache *p;
+
+ if (name == NULL || ((p = SMB_MALLOC_P(struct pcap_cache)) == NULL))
+ return false;
+
+ p->name = SMB_STRDUP(name);
+ p->comment = (comment && *comment) ? SMB_STRDUP(comment) : NULL;
+ p->location = (location && *location) ? SMB_STRDUP(location) : NULL;
+
+ DEBUG(11,("pcap_cache_add_specific: Adding name %s info %s, location: %s\n",
+ p->name, p->comment ? p->comment : "",
+ p->location ? p->location : ""));
+
+ p->next = *ppcache;
+ *ppcache = p;
+
+ return true;
+}
+
+void pcap_cache_destroy_specific(struct pcap_cache **pp_cache)
+{
+ struct pcap_cache *p, *next;
+
+ for (p = *pp_cache; p != NULL; p = next) {
+ next = p->next;
+
+ SAFE_FREE(p->name);
+ SAFE_FREE(p->comment);
+ SAFE_FREE(p->location);
+ SAFE_FREE(p);
+ }
+ *pp_cache = NULL;
+}
+
+bool pcap_cache_replace(const struct pcap_cache *pcache)
+{
+ const struct pcap_cache *p;
+ NTSTATUS status;
+ time_t t = time_mono(NULL);
+
+ status = printer_list_mark_reload();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to mark printer list for reload!\n"));
+ return false;
+ }
+
+ for (p = pcache; p; p = p->next) {
+ status = printer_list_set_printer(talloc_tos(), p->name,
+ p->comment, p->location, t);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ }
+
+ status = printer_list_clean_old();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to cleanup printer list!\n"));
+ return false;
+ }
+
+ return true;
+}
+
+void pcap_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *))
+{
+ const char *pcap_name = lp_printcapname();
+ bool pcap_reloaded = False;
+ bool post_cache_fill_fn_handled = false;
+ struct pcap_cache *pcache = NULL;
+
+ DEBUG(3, ("reloading printcap cache\n"));
+
+ if (!lp_load_printers()) {
+ DBG_NOTICE("skipping reload - load printers disabled\n");
+ return;
+ }
+
+ /* only go looking if a printcap name is supplied */
+ if (pcap_name == NULL || *pcap_name == 0) {
+ DEBUG(0, ("No printcap file name configured!\n"));
+ return;
+ }
+
+#ifdef HAVE_CUPS
+ if (strequal(pcap_name, "cups")) {
+ pcap_reloaded = cups_cache_reload(ev, msg_ctx,
+ post_cache_fill_fn);
+ /*
+ * cups_cache_reload() is async and calls post_cache_fill_fn()
+ * on successful completion
+ */
+ post_cache_fill_fn_handled = true;
+ goto done;
+ }
+#endif
+
+#ifdef HAVE_IPRINT
+ if (strequal(pcap_name, "iprint")) {
+ pcap_reloaded = iprint_cache_reload(&pcache);
+ goto done;
+ }
+#endif
+
+#if defined(SYSV) || defined(HPUX)
+ if (strequal(pcap_name, "lpstat")) {
+ pcap_reloaded = sysv_cache_reload(&pcache);
+ goto done;
+ }
+#endif
+
+#ifdef AIX
+ if (strstr_m(pcap_name, "/qconfig") != NULL) {
+ pcap_reloaded = aix_cache_reload(&pcache);
+ goto done;
+ }
+#endif
+
+ pcap_reloaded = std_pcap_cache_reload(pcap_name, &pcache);
+
+/* Fix silly compiler warning about done not being used if none of the above
+ * ifdefs are used */
+#if defined(HAVE_CUPS) || defined(HAVE_IPRINT) || defined(SYSV) || defined(HPUX) || defined(AIX)
+done:
+#endif
+ DEBUG(3, ("reload status: %s\n", (pcap_reloaded) ? "ok" : "error"));
+
+ if ((pcap_reloaded) && (post_cache_fill_fn_handled == false)) {
+ /* cleanup old entries only if the operation was successful,
+ * otherwise keep around the old entries until we can
+ * successfully reload */
+
+ if (!pcap_cache_replace(pcache)) {
+ DEBUG(0, ("Failed to replace printer list!\n"));
+ }
+
+ if (post_cache_fill_fn != NULL) {
+ post_cache_fill_fn(ev, msg_ctx);
+ }
+ }
+ pcap_cache_destroy_specific(&pcache);
+
+ return;
+}
+
+/***************************************************************************
+run a function on each printer name in the printcap file.
+***************************************************************************/
+
+void pcap_printer_fn_specific(const struct pcap_cache *pc,
+ void (*fn)(const char *, const char *, const char *, void *),
+ void *pdata)
+{
+ const struct pcap_cache *p;
+
+ for (p = pc; p != NULL; p = p->next)
+ fn(p->name, p->comment, p->location, pdata);
+
+ return;
+}
diff --git a/source3/printing/pcap.h b/source3/printing/pcap.h
new file mode 100644
index 0000000..99a0a91
--- /dev/null
+++ b/source3/printing/pcap.h
@@ -0,0 +1,69 @@
+/*
+ Unix SMB/CIFS implementation.
+ printcap headers
+
+ Copyright (C) Karl Auer 1993-1998
+
+ Re-working by Martin Kiff, 1994
+
+ Re-written again by Andrew Tridgell
+
+ Modified for SVID support by Norm Jacobs, 1997
+
+ Modified for CUPS support by Michael Sweet, 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PRINTING_PCAP_H_
+#define _PRINTING_PCAP_H_
+
+struct pcap_cache;
+
+/* The following definitions come from printing/pcap.c */
+
+bool pcap_cache_add_specific(struct pcap_cache **ppcache, const char *name, const char *comment, const char *location);
+void pcap_cache_destroy_specific(struct pcap_cache **ppcache);
+bool pcap_cache_replace(const struct pcap_cache *cache);
+void pcap_printer_fn_specific(const struct pcap_cache *, void (*fn)(const char *, const char *, const char *, void *), void *);
+
+void pcap_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *));
+bool pcap_printername_ok(const char *printername);
+
+/* The following definitions come from printing/print_aix.c */
+
+bool aix_cache_reload(struct pcap_cache **_pcache);
+
+/* The following definitions come from printing/print_cups.c */
+
+bool cups_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *));
+
+/* The following definitions come from printing/print_iprint.c */
+
+bool iprint_cache_reload(struct pcap_cache **_pcache);
+
+/* The following definitions come from printing/print_svid.c */
+
+bool sysv_cache_reload(struct pcap_cache **_pcache);
+
+/* The following definitions come from printing/print_standard.c */
+bool std_pcap_cache_reload(const char *pcap_name, struct pcap_cache **_pcache);
+
+#endif /* _PRINTING_PCAP_H_ */
diff --git a/source3/printing/print_aix.c b/source3/printing/print_aix.c
new file mode 100644
index 0000000..d3836a4
--- /dev/null
+++ b/source3/printing/print_aix.c
@@ -0,0 +1,140 @@
+/*
+ AIX-specific printcap loading
+ Copyright (C) Jean-Pierre.Boulard@univ-rennes1.fr 1996
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This module implements AIX-specific printcap loading. Most of the code
+ * here was originally provided by Jean-Pierre.Boulard@univ-rennes1.fr in
+ * the Samba 1.9.14 release, and was formerly contained in pcap.c. It has
+ * been moved here and condensed as part of a larger effort to clean up and
+ * simplify the printcap code. -- Rob Foehl, 2004/12/06
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/pcap.h"
+
+#ifdef AIX
+bool aix_cache_reload(struct pcap_cache **_pcache)
+{
+ int iEtat;
+ FILE *pfile;
+ char *line = NULL, *p;
+ char *name = NULL;
+ struct pcap_cache *pcache = NULL;
+ TALLOC_CTX *ctx = talloc_init("aix_cache_reload");
+
+ if (!ctx) {
+ return false;
+ }
+
+ DEBUG(5, ("reloading aix printcap cache\n"));
+
+ if ((pfile = fopen(lp_printcapname(), "r")) == NULL) {
+ DEBUG(0,( "Unable to open qconfig file %s for read!\n", lp_printcapname()));
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ iEtat = 0;
+ /* scan qconfig file for searching <printername>: */
+ while (line = fgets_slash(ctx, NULL, 1024, pfile)) {
+ bool ok;
+
+ if (*line == '*' || *line == 0) {
+ TALLOC_FREE(line);
+ continue;
+ }
+
+ switch (iEtat) {
+ case 0: /* locate an entry */
+ if (*line == '\t' || *line == ' ') {
+ TALLOC_FREE(line);
+ continue;
+ }
+
+ if ((p = strchr_m(line, ':'))) {
+ char *saveptr;
+ *p = '\0';
+ p = strtok_r(line, ":", &saveptr);
+ if (strcmp(p, "bsh") != 0) {
+ name = talloc_strdup(ctx, p);
+ if (!name) {
+ pcap_cache_destroy_specific(&pcache);
+ TALLOC_FREE(line);
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ iEtat = 1;
+ continue;
+ }
+ }
+ break;
+
+ case 1: /* scanning device stanza */
+ if (*line == '*' || *line == 0)
+ continue;
+
+ if (*line != '\t' && *line != ' ') {
+ /* name is found without stanza device */
+ /* probably a good printer ??? */
+ iEtat = 0;
+ ok = pcap_cache_add_specific(&pcache,
+ name, NULL, NULL);
+ if (!ok) {
+ pcap_cache_destroy_specific(&pcache);
+ TALLOC_FREE(line);
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ continue;
+ }
+
+ if (strstr_m(line, "backend")) {
+ /* it's a device, not a virtual printer */
+ iEtat = 0;
+ } else if (strstr_m(line, "device")) {
+ /* it's a good virtual printer */
+ iEtat = 0;
+ ok = pcap_cache_add_specific(&pcache,
+ name, NULL, NULL);
+ if (!ok) {
+ pcap_cache_destroy_specific(&pcache);
+ SAFE_FREE(line);
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return false;
+ }
+ continue;
+ }
+ break;
+ }
+ }
+
+ *_pcache = pcache;
+ fclose(pfile);
+ TALLOC_FREE(ctx);
+ return true;
+}
+
+#else
+/* this keeps fussy compilers happy */
+ void print_aix_dummy(void);
+ void print_aix_dummy(void) {}
+#endif /* AIX */
diff --git a/source3/printing/print_cups.c b/source3/printing/print_cups.c
new file mode 100644
index 0000000..d8ba9cc
--- /dev/null
+++ b/source3/printing/print_cups.c
@@ -0,0 +1,1749 @@
+/*
+ * Support code for the Common UNIX Printing System ("CUPS")
+ *
+ * Copyright 1999-2003 by Michael R Sweet.
+ * Copyright 2008 Jeremy Allison.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * JRA. Converted to utf8 pull/push.
+ */
+
+#include "includes.h"
+#include "printing.h"
+#include "printing/pcap.h"
+#include "librpc/gen_ndr/ndr_printcap.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_CUPS
+#include <cups/cups.h>
+#include <cups/language.h>
+#include <cups/http.h>
+
+/* CUPS prior to version 1.7 doesn't have HTTP_URI_STATUS_OK */
+#if (CUPS_VERSION_MAJOR == 1) && (CUPS_VERSION_MINOR < 7)
+#define HTTP_URI_STATUS_OK HTTP_URI_OK
+#endif
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetGroupTag(attr) attr->group_tag
+#define ippGetName(attr) attr->name
+#define ippGetValueTag(attr) attr->value_tag
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetString(attr, element, language) attr->values[element].string.text
+
+static ipp_attribute_t *
+ippFirstAttribute(ipp_t *ipp)
+{
+ if (!ipp)
+ return (NULL);
+ return (ipp->current = ipp->attrs);
+}
+
+static ipp_attribute_t *
+ippNextAttribute(ipp_t *ipp)
+{
+ if (!ipp || !ipp->current)
+ return (NULL);
+ return (ipp->current = ipp->current->next);
+}
+
+static int ippSetOperation(ipp_t *ipp, ipp_op_t op)
+{
+ ipp->request.op.operation_id = op;
+ return (1);
+}
+
+static int ippSetRequestId(ipp_t *ipp, int request_id)
+{
+ ipp->request.any.request_id = request_id;
+ return (1);
+}
+#endif
+
+static SIG_ATOMIC_T gotalarm;
+
+/***************************************************************
+ Signal function to tell us we timed out.
+****************************************************************/
+
+static void gotalarm_sig(int signum)
+{
+ gotalarm = 1;
+}
+
+extern userdom_struct current_user_info;
+
+/*
+ * 'cups_passwd_cb()' - The CUPS password callback...
+ */
+
+static const char * /* O - Password or NULL */
+cups_passwd_cb(const char *prompt) /* I - Prompt */
+{
+ /*
+ * Always return NULL to indicate that no password is available...
+ */
+
+ return (NULL);
+}
+
+static http_t *cups_connect(TALLOC_CTX *frame)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ http_t *http = NULL;
+ char *server = NULL, *p = NULL;
+ int port;
+ int timeout = lp_cups_connection_timeout();
+ size_t size;
+
+ if (lp_cups_server(talloc_tos(), lp_sub) != NULL && strlen(lp_cups_server(talloc_tos(), lp_sub)) > 0) {
+ if (!push_utf8_talloc(frame, &server, lp_cups_server(talloc_tos(), lp_sub), &size)) {
+ return NULL;
+ }
+ } else {
+ server = talloc_strdup(frame,cupsServer());
+ }
+ if (!server) {
+ return NULL;
+ }
+
+ p = strchr(server, ':');
+ if (p) {
+ port = atoi(p+1);
+ *p = '\0';
+ } else {
+ port = ippPort();
+ }
+
+ DEBUG(10, ("connecting to cups server %s:%d\n",
+ server, port));
+
+ gotalarm = 0;
+
+ if (timeout) {
+ CatchSignal(SIGALRM, gotalarm_sig);
+ alarm(timeout);
+ }
+
+#if defined(HAVE_HTTPCONNECT2)
+ http = httpConnect2(server,
+ port,
+ NULL,
+ AF_UNSPEC,
+ lp_cups_encrypt() ?
+ HTTP_ENCRYPTION_ALWAYS :
+ HTTP_ENCRYPTION_IF_REQUESTED,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#elif defined(HAVE_HTTPCONNECTENCRYPT)
+ http = httpConnectEncrypt(server, port, lp_cups_encrypt());
+#else
+ http = httpConnect(server, port);
+#endif
+
+
+ CatchSignal(SIGALRM, SIG_IGN);
+ alarm(0);
+
+ if (http == NULL) {
+ DEBUG(3,("Unable to connect to CUPS server %s:%d - %s\n",
+ server, port, strerror(errno)));
+ }
+
+ return http;
+}
+
+static bool send_pcap_blob(DATA_BLOB *pcap_blob, int fd)
+{
+ size_t ret;
+
+ ret = sys_write(fd, &pcap_blob->length, sizeof(pcap_blob->length));
+ if (ret != sizeof(pcap_blob->length)) {
+ return false;
+ }
+
+ ret = sys_write(fd, pcap_blob->data, pcap_blob->length);
+ if (ret != pcap_blob->length) {
+ return false;
+ }
+
+ DEBUG(10, ("successfully sent blob of len %d\n", (int)ret));
+ return true;
+}
+
+static bool recv_pcap_blob(TALLOC_CTX *mem_ctx, int fd, DATA_BLOB *pcap_blob)
+{
+ size_t blob_len;
+ size_t ret;
+
+ ret = sys_read(fd, &blob_len, sizeof(blob_len));
+ if (ret != sizeof(blob_len)) {
+ return false;
+ }
+
+ *pcap_blob = data_blob_talloc_named(mem_ctx, NULL, blob_len,
+ "cups pcap");
+ if (pcap_blob->length != blob_len) {
+ return false;
+ }
+ ret = sys_read(fd, pcap_blob->data, blob_len);
+ if (ret != blob_len) {
+ talloc_free(pcap_blob->data);
+ return false;
+ }
+
+ DEBUG(10, ("successfully recvd blob of len %d\n", (int)ret));
+ return true;
+}
+
+static bool process_cups_printers_response(TALLOC_CTX *mem_ctx,
+ ipp_t *response,
+ struct pcap_data *pcap_data)
+{
+ ipp_attribute_t *attr;
+ char *name;
+ char *info;
+ char *location = NULL;
+ struct pcap_printer *printer;
+ bool ret_ok = false;
+
+ for (attr = ippFirstAttribute(response); attr != NULL;) {
+ /*
+ * Skip leading attributes until we hit a printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Pull the needed attributes from this printer...
+ */
+
+ name = NULL;
+ info = NULL;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) {
+ size_t size;
+ if (strcmp(ippGetName(attr), "printer-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME) {
+ if (!pull_utf8_talloc(mem_ctx,
+ &name,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto err_out;
+ }
+ }
+
+ if (strcmp(ippGetName(attr), "printer-info") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
+ if (!pull_utf8_talloc(mem_ctx,
+ &info,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto err_out;
+ }
+ }
+
+ if (strcmp(ippGetName(attr), "printer-location") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_TEXT) {
+ if (!pull_utf8_talloc(mem_ctx,
+ &location,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto err_out;
+ }
+ }
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ */
+
+ if (name == NULL)
+ break;
+
+ if (pcap_data->count == 0) {
+ printer = talloc_array(mem_ctx, struct pcap_printer, 1);
+ } else {
+ printer = talloc_realloc(mem_ctx, pcap_data->printers,
+ struct pcap_printer,
+ pcap_data->count + 1);
+ }
+ if (printer == NULL) {
+ goto err_out;
+ }
+ pcap_data->printers = printer;
+ pcap_data->printers[pcap_data->count].name = name;
+ pcap_data->printers[pcap_data->count].info = info;
+ pcap_data->printers[pcap_data->count].location = location;
+ pcap_data->count++;
+ }
+
+ ret_ok = true;
+err_out:
+ return ret_ok;
+}
+
+/*
+ * request printer list from cups, send result back to up parent via fd.
+ * returns true if the (possibly failed) result was successfully sent to parent.
+ */
+static bool cups_cache_reload_async(int fd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct pcap_data pcap_data;
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ static const char *requested[] =/* Requested attributes */
+ {
+ "printer-name",
+ "printer-info",
+ "printer-location"
+ };
+ bool ret = False;
+ enum ndr_err_code ndr_ret;
+ DATA_BLOB pcap_blob;
+
+ ZERO_STRUCT(pcap_data);
+ pcap_data.status = NT_STATUS_UNSUCCESSFUL;
+
+ DEBUG(5, ("reloading cups printcap cache\n"));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build a CUPS_GET_PRINTERS request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, CUPS_GET_PRINTERS);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requested-attributes",
+ (sizeof(requested) / sizeof(requested[0])),
+ NULL, requested);
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ ret = process_cups_printers_response(frame, response, &pcap_data);
+ if (!ret) {
+ DEBUG(0,("failed to process cups response\n"));
+ goto out;
+ }
+
+ ippDelete(response);
+ response = NULL;
+
+ /*
+ * Build a CUPS_GET_CLASSES request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, CUPS_GET_CLASSES);
+ ippSetRequestId(request, 1);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requested-attributes",
+ (sizeof(requested) / sizeof(requested[0])),
+ NULL, requested);
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ ret = process_cups_printers_response(frame, response, &pcap_data);
+ if (!ret) {
+ DEBUG(0,("failed to process cups response\n"));
+ goto out;
+ }
+
+ pcap_data.status = NT_STATUS_OK;
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ ret = false;
+ ndr_ret = ndr_push_struct_blob(&pcap_blob, frame, &pcap_data,
+ (ndr_push_flags_fn_t)ndr_push_pcap_data);
+ if (ndr_ret == NDR_ERR_SUCCESS) {
+ ret = send_pcap_blob(&pcap_blob, fd);
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static struct tevent_fd *cache_fd_event;
+
+static bool cups_pcap_load_async(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int *pfd)
+{
+ int fds[2];
+ pid_t pid;
+ NTSTATUS status;
+
+ *pfd = -1;
+
+ if (cache_fd_event) {
+ DEBUG(3,("cups_pcap_load_async: already waiting for "
+ "a refresh event\n" ));
+ return false;
+ }
+
+ DEBUG(5,("cups_pcap_load_async: asynchronously loading cups printers\n"));
+
+ if (pipe(fds) == -1) {
+ return false;
+ }
+
+ pid = fork();
+ if (pid == (pid_t)-1) {
+ DEBUG(10,("cups_pcap_load_async: fork failed %s\n",
+ strerror(errno) ));
+ close(fds[0]);
+ close(fds[1]);
+ return false;
+ }
+
+ if (pid) {
+ DEBUG(10,("cups_pcap_load_async: child pid = %u\n",
+ (unsigned int)pid ));
+ /* Parent. */
+ close(fds[1]);
+ *pfd = fds[0];
+ return true;
+ }
+
+ /* Child. */
+
+ close_all_print_db();
+
+ status = reinit_after_fork(msg_ctx, ev, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("cups_pcap_load_async: reinit_after_fork() failed\n"));
+ smb_panic("cups_pcap_load_async: reinit_after_fork() failed");
+ }
+
+ close(fds[0]);
+ cups_cache_reload_async(fds[1]);
+ close(fds[1]);
+ TALLOC_FREE(msg_ctx);
+ _exit(0);
+}
+
+struct cups_async_cb_args {
+ int pipe_fd;
+ struct tevent_context *event_ctx;
+ struct messaging_context *msg_ctx;
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *);
+};
+
+static void cups_async_callback(struct tevent_context *event_ctx,
+ struct tevent_fd *event,
+ uint16_t flags,
+ void *p)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cups_async_cb_args *cb_args = (struct cups_async_cb_args *)p;
+ struct pcap_cache *tmp_pcap_cache = NULL;
+ bool ret_ok;
+ struct pcap_data pcap_data;
+ DATA_BLOB pcap_blob;
+ enum ndr_err_code ndr_ret;
+ uint32_t i;
+
+ DEBUG(5,("cups_async_callback: callback received for printer data. "
+ "fd = %d\n", cb_args->pipe_fd));
+
+ ret_ok = recv_pcap_blob(frame, cb_args->pipe_fd, &pcap_blob);
+ if (!ret_ok) {
+ DEBUG(0,("failed to recv pcap blob\n"));
+ goto err_out;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(&pcap_blob, frame, &pcap_data,
+ (ndr_pull_flags_fn_t)ndr_pull_pcap_data);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ goto err_out;
+ }
+
+ if (!NT_STATUS_IS_OK(pcap_data.status)) {
+ DEBUG(3,("failed to retrieve printer list: %s\n",
+ nt_errstr(pcap_data.status)));
+ goto err_out;
+ }
+
+ for (i = 0; i < pcap_data.count; i++) {
+ ret_ok = pcap_cache_add_specific(&tmp_pcap_cache,
+ pcap_data.printers[i].name,
+ pcap_data.printers[i].info,
+ pcap_data.printers[i].location);
+ if (!ret_ok) {
+ DEBUG(0, ("failed to add to tmp pcap cache\n"));
+ goto err_out;
+ }
+ }
+
+ /* replace the system-wide pcap cache with a (possibly empty) new one */
+ ret_ok = pcap_cache_replace(tmp_pcap_cache);
+ if (!ret_ok) {
+ DEBUG(0, ("failed to replace pcap cache\n"));
+ } else if (cb_args->post_cache_fill_fn != NULL) {
+ /* Caller requested post cache fill callback */
+ cb_args->post_cache_fill_fn(cb_args->event_ctx,
+ cb_args->msg_ctx);
+ }
+err_out:
+ pcap_cache_destroy_specific(&tmp_pcap_cache);
+ TALLOC_FREE(frame);
+ TALLOC_FREE(cache_fd_event);
+ close(cb_args->pipe_fd);
+ TALLOC_FREE(cb_args);
+}
+
+bool cups_cache_reload(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ void (*post_cache_fill_fn)(struct tevent_context *,
+ struct messaging_context *))
+{
+ struct cups_async_cb_args *cb_args;
+ int *p_pipe_fd;
+
+ cb_args = talloc(NULL, struct cups_async_cb_args);
+ if (cb_args == NULL) {
+ return false;
+ }
+
+ cb_args->post_cache_fill_fn = post_cache_fill_fn;
+ cb_args->event_ctx = ev;
+ cb_args->msg_ctx = msg_ctx;
+ p_pipe_fd = &cb_args->pipe_fd;
+ *p_pipe_fd = -1;
+
+ /* Set up an async refresh. */
+ if (!cups_pcap_load_async(ev, msg_ctx, p_pipe_fd)) {
+ talloc_free(cb_args);
+ return false;
+ }
+
+ DEBUG(10,("cups_cache_reload: async read on fd %d\n",
+ *p_pipe_fd ));
+
+ /* Trigger an event when the pipe can be read. */
+ cache_fd_event = tevent_add_fd(ev,
+ NULL, *p_pipe_fd,
+ TEVENT_FD_READ,
+ cups_async_callback,
+ (void *)cb_args);
+ if (!cache_fd_event) {
+ close(*p_pipe_fd);
+ TALLOC_FREE(cb_args);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * 'cups_job_delete()' - Delete a job.
+ */
+
+static int cups_job_delete(const char *sharename, const char *lprm_command, struct printjob *pjob)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *user = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_job_delete(%s, %p (%d))\n", sharename, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_CANCEL_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * job-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_CANCEL_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/jobs/%d",
+ pjob->sysjob);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_job_pause()' - Pause a job.
+ */
+
+static int cups_job_pause(int snum, struct printjob *pjob)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *user = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_job_pause(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_HOLD_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * job-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_HOLD_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/jobs/%d",
+ pjob->sysjob);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_job_resume()' - Resume a paused job.
+ */
+
+static int cups_job_resume(int snum, struct printjob *pjob)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *user = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_job_resume(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_RELEASE_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * job-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_RELEASE_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/jobs/%d",
+ pjob->sysjob);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/jobs")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_job_submit()' - Submit a job for printing.
+ */
+
+static int cups_job_submit(int snum, struct printjob *pjob,
+ enum printing_types printing_type,
+ char *lpq_cmd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr_job_id = NULL; /* IPP Attribute "job-id" */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ char *new_jobname = NULL;
+ int num_options = 0;
+ cups_option_t *options = NULL;
+ char *printername = NULL;
+ char *user = NULL;
+ char *jobname = NULL;
+ char *cupsoptions = NULL;
+ char *filename = NULL;
+ size_t size;
+
+ DEBUG(5,("cups_job_submit(%d, %p)\n", snum, pjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_PRINT_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ * [document-data]
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_PRINT_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ if (!push_utf8_talloc(frame, &printername,
+ lp_printername(talloc_tos(), lp_sub, snum),
+ &size)) {
+ goto out;
+ }
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &user, pjob->user, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, user);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "job-originating-host-name", NULL,
+ pjob->clientmachine);
+
+ if (!push_utf8_talloc(frame, &jobname, pjob->jobname, &size)) {
+ goto out;
+ }
+ new_jobname = talloc_asprintf(frame,
+ "%s%.8u %s", PRINT_SPOOL_PREFIX,
+ pjob->jobid, jobname);
+ if (new_jobname == NULL) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
+ new_jobname);
+
+ /*
+ * add any options defined in smb.conf
+ */
+
+ if (!push_utf8_talloc(frame, &cupsoptions,
+ lp_cups_options(talloc_tos(), lp_sub, snum), &size)) {
+ goto out;
+ }
+ num_options = 0;
+ options = NULL;
+ num_options = cupsParseOptions(cupsoptions, num_options, &options);
+
+ if ( num_options )
+ cupsEncodeOptions(request, num_options, options);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ if (!push_utf8_talloc(frame, &filename, pjob->filename, &size)) {
+ goto out;
+ }
+ if ((response = cupsDoFileRequest(http, request, uri, pjob->filename)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to print file to %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ attr_job_id = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER);
+ if(attr_job_id) {
+ pjob->sysjob = ippGetInteger(attr_job_id, 0);
+ DEBUG(5,("cups_job_submit: job-id %d\n", pjob->sysjob));
+ } else {
+ DEBUG(0,("Missing job-id attribute in IPP response\n"));
+ }
+ }
+ } else {
+ DEBUG(0,("Unable to print file to `%s' - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ if ( ret == 0 )
+ unlink(pjob->filename);
+ /* else print_job_end will do it for us */
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+
+ return ret;
+}
+
+/*
+ * 'cups_queue_get()' - Get all the jobs in the print queue.
+ */
+
+static int cups_queue_get(const char *sharename,
+ enum printing_types printing_type,
+ char *lpq_command,
+ print_queue_struct **q,
+ print_status_struct *status)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *printername = NULL;
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr = NULL; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ int qcount = 0, /* Number of active queue entries */
+ qalloc = 0; /* Number of queue entries allocated */
+ print_queue_struct *queue = NULL, /* Queue entries */
+ *temp; /* Temporary pointer for queue */
+ char *user_name = NULL, /* job-originating-user-name attribute */
+ *job_name = NULL; /* job-name attribute */
+ int job_id; /* job-id attribute */
+ int job_k_octets; /* job-k-octets attribute */
+ time_t job_time; /* time-at-creation attribute */
+ ipp_jstate_t job_status; /* job-status attribute */
+ int job_priority; /* job-priority attribute */
+ size_t size;
+ static const char *jattrs[] = /* Requested job attributes */
+ {
+ "job-id",
+ "job-k-octets",
+ "job-name",
+ "job-originating-user-name",
+ "job-priority",
+ "job-state",
+ "time-at-creation",
+ };
+ static const char *pattrs[] = /* Requested printer attributes */
+ {
+ "printer-state",
+ "printer-state-message"
+ };
+
+ *q = NULL;
+
+ /* HACK ALERT!!! The problem with support the 'printer name'
+ option is that we key the tdb off the sharename. So we will
+ overload the lpq_command string to pass in the printername
+ (which is basically what we do for non-cups printers ... using
+ the lpq_command to get the queue listing). */
+
+ if (!push_utf8_talloc(frame, &printername, lpq_command, &size)) {
+ goto out;
+ }
+ DEBUG(5,("cups_queue_get(%s, %p, %p)\n", lpq_command, q, status));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Generate the printer URI...
+ */
+
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_GET_JOBS request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ * printer-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_JOBS);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(jattrs) / sizeof(jattrs[0])),
+ NULL, jattrs);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(ippGetStatusCode(response))));
+ goto out;
+ }
+
+ /*
+ * Process the jobs...
+ */
+
+ qcount = 0;
+ qalloc = 0;
+ queue = NULL;
+
+ for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) {
+ /*
+ * Skip leading attributes until we hit a job...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_JOB)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Allocate memory as needed...
+ */
+ if (qcount >= qalloc) {
+ qalloc += 16;
+
+ queue = SMB_REALLOC_ARRAY(queue, print_queue_struct, qalloc);
+
+ if (queue == NULL) {
+ DEBUG(0,("cups_queue_get: Not enough memory!\n"));
+ qcount = 0;
+ goto out;
+ }
+ }
+
+ temp = queue + qcount;
+ memset(temp, 0, sizeof(print_queue_struct));
+
+ /*
+ * Pull the needed attributes from this job...
+ */
+
+ job_id = 0;
+ job_priority = 50;
+ job_status = IPP_JOB_PENDING;
+ job_time = 0;
+ job_k_octets = 0;
+ user_name = NULL;
+ job_name = NULL;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_JOB) {
+ if (ippGetName(attr) == NULL) {
+ attr = ippNextAttribute(response);
+ break;
+ }
+
+ if (strcmp(ippGetName(attr), "job-id") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_id = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-k-octets") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_k_octets = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-priority") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_priority = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-state") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_ENUM)
+ job_status = (ipp_jstate_t)ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "time-at-creation") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_time = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME) {
+ if (!pull_utf8_talloc(frame,
+ &job_name,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto out;
+ }
+ }
+
+ if (strcmp(ippGetName(attr), "job-originating-user-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME) {
+ if (!pull_utf8_talloc(frame,
+ &user_name,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ goto out;
+ }
+ }
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ */
+
+ if (user_name == NULL || job_name == NULL || job_id == 0) {
+ if (attr == NULL)
+ break;
+ else
+ continue;
+ }
+
+ temp->sysjob = job_id;
+ temp->size = job_k_octets * 1024;
+ temp->status = job_status == IPP_JOB_PENDING ? LPQ_QUEUED :
+ job_status == IPP_JOB_STOPPED ? LPQ_PAUSED :
+ job_status == IPP_JOB_HELD ? LPQ_PAUSED :
+ LPQ_PRINTING;
+ temp->priority = job_priority;
+ temp->time = job_time;
+ strlcpy(temp->fs_user, user_name, sizeof(temp->fs_user));
+ strlcpy(temp->fs_file, job_name, sizeof(temp->fs_file));
+
+ qcount ++;
+
+ if (attr == NULL)
+ break;
+ }
+
+ ippDelete(response);
+ response = NULL;
+
+ /*
+ * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the
+ * following attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ * printer-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_PRINTER_ATTRIBUTES);
+ ippSetRequestId(request, 1);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "requested-attributes",
+ (sizeof(pattrs) / sizeof(pattrs[0])),
+ NULL, pattrs);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/")) == NULL) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(ippGetStatusCode(response))));
+ goto out;
+ }
+
+ /*
+ * Get the current printer status and convert it to the SAMBA values.
+ */
+
+ if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL) {
+ if (ippGetInteger(attr, 0) == IPP_PRINTER_STOPPED)
+ status->status = LPSTAT_STOPPED;
+ else
+ status->status = LPSTAT_OK;
+ }
+
+ if ((attr = ippFindAttribute(response, "printer-state-message",
+ IPP_TAG_TEXT)) != NULL) {
+ char *msg = NULL;
+ if (!pull_utf8_talloc(frame, &msg,
+ ippGetString(attr, 0, NULL),
+ &size)) {
+ SAFE_FREE(queue);
+ qcount = 0;
+ goto out;
+ }
+ fstrcpy(status->message, msg);
+ }
+
+ out:
+
+ /*
+ * Return the job queue...
+ */
+
+ *q = queue;
+
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return qcount;
+}
+
+
+/*
+ * 'cups_queue_pause()' - Pause a print queue.
+ */
+
+static int cups_queue_pause(int snum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *printername = NULL;
+ char *username = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_queue_pause(%d)\n", snum));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_PAUSE_PRINTER request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_PAUSE_PRINTER);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ if (!push_utf8_talloc(frame, &printername,
+ lp_printername(talloc_tos(), lp_sub, snum), &size)) {
+ goto out;
+ }
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &username, current_user_info.unix_name, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, username);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/admin/")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to pause printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to pause printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+/*
+ * 'cups_queue_resume()' - Restart a print queue.
+ */
+
+static int cups_queue_resume(int snum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char *printername = NULL;
+ char *username = NULL;
+ char uri[HTTP_MAX_URI] = {0}; /* printer-uri attribute */
+ http_uri_status_t ustatus;
+ size_t size;
+
+ DEBUG(5,("cups_queue_resume(%d)\n", snum));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(cups_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+ if ((http = cups_connect(frame)) == NULL) {
+ goto out;
+ }
+
+ /*
+ * Build an IPP_RESUME_PRINTER request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_RESUME_PRINTER);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ if (!push_utf8_talloc(frame, &printername, lp_printername(talloc_tos(), lp_sub, snum),
+ &size)) {
+ goto out;
+ }
+ ustatus = httpAssembleURIf(HTTP_URI_CODING_ALL,
+ uri,
+ sizeof(uri),
+ "ipp",
+ NULL, /* username */
+ "localhost",
+ ippPort(),
+ "/printers/%s",
+ printername);
+ if (ustatus != HTTP_URI_STATUS_OK) {
+ goto out;
+ }
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ if (!push_utf8_talloc(frame, &username, current_user_info.unix_name, &size)) {
+ goto out;
+ }
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, username);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/admin/")) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to resume printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to resume printer %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/*******************************************************************
+ * CUPS printing interface definitions...
+ ******************************************************************/
+
+struct printif cups_printif =
+{
+ PRINT_CUPS,
+ cups_queue_get,
+ cups_queue_pause,
+ cups_queue_resume,
+ cups_job_delete,
+ cups_job_pause,
+ cups_job_resume,
+ cups_job_submit,
+};
+
+#else
+ /* this keeps fussy compilers happy */
+ void print_cups_dummy(void);
+ void print_cups_dummy(void) {}
+#endif /* HAVE_CUPS */
diff --git a/source3/printing/print_generic.c b/source3/printing/print_generic.c
new file mode 100644
index 0000000..8798a4c
--- /dev/null
+++ b/source3/printing/print_generic.c
@@ -0,0 +1,358 @@
+/*
+ Unix SMB/CIFS implementation.
+ printing command routines
+ Copyright (C) Andrew Tridgell 1992-2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "smbd/proto.h"
+#include "source3/lib/substitute.h"
+
+extern userdom_struct current_user_info;
+
+/****************************************************************************
+ Run a given print command
+ a null terminated list of value/substitute pairs is provided
+ for local substitution strings
+****************************************************************************/
+static int print_run_command(int snum, const char* printername, bool do_sub,
+ const char *command, int *outfd, ...)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *syscmd;
+ char *arg;
+ int ret;
+ TALLOC_CTX *ctx = talloc_tos();
+ va_list ap;
+ va_start(ap, outfd);
+
+ /* check for a valid system printername and valid command to run */
+
+ if ( !printername || !*printername ) {
+ va_end(ap);
+ return -1;
+ }
+
+ if (!command || !*command) {
+ va_end(ap);
+ return -1;
+ }
+
+ syscmd = talloc_strdup(ctx, command);
+ if (!syscmd) {
+ va_end(ap);
+ return -1;
+ }
+
+ DBG_DEBUG("Incoming command '%s'\n", syscmd);
+
+ while ((arg = va_arg(ap, char *))) {
+ char *value = va_arg(ap,char *);
+ syscmd = talloc_string_sub(ctx, syscmd, arg, value);
+ if (!syscmd) {
+ va_end(ap);
+ return -1;
+ }
+ }
+ va_end(ap);
+
+ syscmd = talloc_string_sub(ctx, syscmd, "%p", printername);
+ if (!syscmd) {
+ return -1;
+ }
+
+ syscmd = lpcfg_substituted_string(ctx, lp_sub, syscmd);
+ if (syscmd == NULL) {
+ return -1;
+ }
+
+ if (do_sub && snum != -1) {
+ syscmd = talloc_sub_advanced(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ syscmd);
+ if (!syscmd) {
+ return -1;
+ }
+ }
+
+ ret = smbrun_no_sanitize(syscmd, outfd, NULL);
+
+ DEBUG(3,("Running the command `%s' gave %d\n",syscmd,ret));
+
+ return ret;
+}
+
+
+/****************************************************************************
+delete a print job
+****************************************************************************/
+static int generic_job_delete( const char *sharename, const char *lprm_command, struct printjob *pjob)
+{
+ fstring jobstr;
+
+ /* need to delete the spooled entry */
+ fstr_sprintf(jobstr, "%d", pjob->sysjob);
+ return print_run_command( -1, sharename, False, lprm_command, NULL,
+ "%j", jobstr,
+ "%T", http_timestring(talloc_tos(), pjob->starttime),
+ NULL);
+}
+
+/****************************************************************************
+pause a job
+****************************************************************************/
+static int generic_job_pause(int snum, struct printjob *pjob)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ fstring jobstr;
+
+ /* need to pause the spooled entry */
+ fstr_sprintf(jobstr, "%d", pjob->sysjob);
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_lppause_command(snum), NULL,
+ "%j", jobstr,
+ NULL);
+}
+
+/****************************************************************************
+resume a job
+****************************************************************************/
+static int generic_job_resume(int snum, struct printjob *pjob)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ fstring jobstr;
+
+ /* need to pause the spooled entry */
+ fstr_sprintf(jobstr, "%d", pjob->sysjob);
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_lpresume_command(snum), NULL,
+ "%j", jobstr,
+ NULL);
+}
+
+/****************************************************************************
+get the current list of queued jobs
+****************************************************************************/
+static int generic_queue_get(const char *printer_name,
+ enum printing_types printing_type,
+ char *lpq_command,
+ print_queue_struct **q,
+ print_status_struct *status)
+{
+ char **qlines;
+ int fd;
+ int numlines, i, qcount;
+ print_queue_struct *queue = NULL;
+
+ /* never do substitution when running the 'lpq command' since we can't
+ get it right when using the background update daemon. Make the caller
+ do it before passing off the command string to us here. */
+
+ print_run_command(-1, printer_name, False, lpq_command, &fd, NULL);
+
+ if (fd == -1) {
+ DEBUG(5,("generic_queue_get: Can't read print queue status for printer %s\n",
+ printer_name ));
+ return 0;
+ }
+
+ numlines = 0;
+ qlines = fd_lines_load(fd, &numlines,0,NULL);
+ close(fd);
+
+ /* turn the lpq output into a series of job structures */
+ qcount = 0;
+ ZERO_STRUCTP(status);
+ if (numlines && qlines) {
+ queue = SMB_MALLOC_ARRAY(print_queue_struct, numlines+1);
+ if (!queue) {
+ TALLOC_FREE(qlines);
+ *q = NULL;
+ return 0;
+ }
+ memset(queue, '\0', sizeof(print_queue_struct)*(numlines+1));
+
+ for (i=0; i<numlines; i++) {
+ /* parse the line */
+ if (parse_lpq_entry(printing_type,qlines[i],
+ &queue[qcount],status,qcount==0)) {
+ qcount++;
+ }
+ }
+ }
+
+ TALLOC_FREE(qlines);
+ *q = queue;
+ return qcount;
+}
+
+/****************************************************************************
+ Submit a file for printing - called from print_job_end()
+****************************************************************************/
+
+static int generic_job_submit(int snum, struct printjob *pjob,
+ enum printing_types printing_type,
+ char *lpq_cmd)
+{
+ int ret = -1;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *current_directory = NULL;
+ char *print_directory = NULL;
+ char *wd = NULL;
+ char *p = NULL;
+ char *jobname = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+ fstring job_page_count, job_size;
+ print_queue_struct *q;
+ print_status_struct status;
+
+ /* we print from the directory path to give the best chance of
+ parsing the lpq output */
+ wd = sys_getwd();
+ if (!wd) {
+ return -1;
+ }
+
+ current_directory = talloc_strdup(ctx, wd);
+ SAFE_FREE(wd);
+
+ if (!current_directory) {
+ return -1;
+ }
+ print_directory = talloc_strdup(ctx, pjob->filename);
+ if (!print_directory) {
+ return -1;
+ }
+ p = strrchr_m(print_directory,'/');
+ if (!p) {
+ return -1;
+ }
+ *p++ = 0;
+
+ if (chdir(print_directory) != 0) {
+ return -1;
+ }
+
+ jobname = talloc_strdup(ctx, pjob->jobname);
+ if (!jobname) {
+ ret = -1;
+ goto out;
+ }
+ jobname = talloc_string_sub(ctx, jobname, "'", "_");
+ if (!jobname) {
+ ret = -1;
+ goto out;
+ }
+ fstr_sprintf(job_page_count, "%d", pjob->page_count);
+ fstr_sprintf(job_size, "%zu", pjob->size);
+
+ /* send it to the system spooler */
+ ret = print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_print_command(snum), NULL,
+ "%s", p,
+ "%J", jobname,
+ "%f", p,
+ "%z", job_size,
+ "%c", job_page_count,
+ NULL);
+ if (ret != 0) {
+ ret = -1;
+ goto out;
+ }
+
+ /*
+ * check the queue for the newly submitted job, this allows us to
+ * determine the backend job identifier (sysjob).
+ */
+ pjob->sysjob = -1;
+ ret = generic_queue_get(lp_printername(talloc_tos(), lp_sub, snum),
+ printing_type, lpq_cmd, &q, &status);
+ if (ret > 0) {
+ int i;
+ for (i = 0; i < ret; i++) {
+ if (strcmp(q[i].fs_file, p) == 0) {
+ pjob->sysjob = q[i].sysjob;
+ DEBUG(5, ("new job %u (%s) matches sysjob %d\n",
+ pjob->jobid, jobname, pjob->sysjob));
+ break;
+ }
+ }
+ SAFE_FREE(q);
+ ret = 0;
+ }
+ if (pjob->sysjob == -1) {
+ DEBUG(2, ("failed to get sysjob for job %u (%s), tracking as "
+ "Unix job\n", pjob->jobid, jobname));
+ }
+
+
+ out:
+
+ if (chdir(current_directory) == -1) {
+ smb_panic("chdir failed in generic_job_submit");
+ }
+ TALLOC_FREE(current_directory);
+ return ret;
+}
+
+/****************************************************************************
+ pause a queue
+****************************************************************************/
+static int generic_queue_pause(int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_queuepause_command(snum), NULL, NULL);
+}
+
+/****************************************************************************
+ resume a queue
+****************************************************************************/
+static int generic_queue_resume(int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ return print_run_command(snum, lp_printername(talloc_tos(), lp_sub, snum), True,
+ lp_queueresume_command(snum), NULL, NULL);
+}
+
+/****************************************************************************
+ * Generic printing interface definitions...
+ ***************************************************************************/
+
+struct printif generic_printif =
+{
+ DEFAULT_PRINTING,
+ generic_queue_get,
+ generic_queue_pause,
+ generic_queue_resume,
+ generic_job_delete,
+ generic_job_pause,
+ generic_job_resume,
+ generic_job_submit,
+};
+
diff --git a/source3/printing/print_iprint.c b/source3/printing/print_iprint.c
new file mode 100644
index 0000000..2b2215e
--- /dev/null
+++ b/source3/printing/print_iprint.c
@@ -0,0 +1,1367 @@
+/*
+ * Support code for Novell iPrint using the Common UNIX Printing
+ * System ("CUPS") libraries
+ *
+ * Copyright 1999-2003 by Michael R Sweet.
+ * Portions Copyright 2005 by Joel J. Smith.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "printing.h"
+#include "printing/pcap.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_IPRINT
+#include <cups/cups.h>
+#include <cups/language.h>
+
+#define OPERATION_NOVELL_LIST_PRINTERS 0x401A
+#define OPERATION_NOVELL_MGMT 0x401C
+#define NOVELL_SERVER_SYSNAME "sysname="
+#define NOVELL_SERVER_SYSNAME_NETWARE "NetWare IA32"
+#define NOVELL_SERVER_VERSION_STRING "iprintserverversion="
+#define NOVELL_SERVER_VERSION_OES_SP1 33554432
+
+#if (CUPS_VERSION_MAJOR > 1) || (CUPS_VERSION_MINOR > 5)
+#define HAVE_CUPS_1_6 1
+#endif
+
+#ifndef HAVE_CUPS_1_6
+#define ippGetCount(attr) attr->num_values
+#define ippGetGroupTag(attr) attr->group_tag
+#define ippGetName(attr) attr->name
+#define ippGetValueTag(attr) attr->value_tag
+#define ippGetStatusCode(ipp) ipp->request.status.status_code
+#define ippGetBoolean(attr, element) attr->values[element].boolean
+#define ippGetInteger(attr, element) attr->values[element].integer
+#define ippGetString(attr, element, language) attr->values[element].string.text
+
+static ipp_attribute_t *
+ippFirstAttribute(ipp_t *ipp)
+{
+ if (!ipp)
+ return (NULL);
+ return (ipp->current = ipp->attrs);
+}
+
+static ipp_attribute_t *
+ippNextAttribute(ipp_t *ipp)
+{
+ if (!ipp || !ipp->current)
+ return (NULL);
+ return (ipp->current = ipp->current->next);
+}
+
+static int ippSetOperation(ipp_t *ipp, ipp_op_t op)
+{
+ ipp->request.op.operation_id = op;
+ return (1);
+}
+
+static int ippSetRequestId(ipp_t *ipp, int request_id)
+{
+ ipp->request.any.request_id = request_id;
+ return (1);
+}
+#endif
+
+/*
+ * 'iprint_passwd_cb()' - The iPrint password callback...
+ */
+
+static const char * /* O - Password or NULL */
+iprint_passwd_cb(const char *prompt) /* I - Prompt */
+{
+ /*
+ * Always return NULL to indicate that no password is available...
+ */
+
+ return (NULL);
+}
+
+static const char *iprint_server(void)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *server = lp_iprint_server(talloc_tos(), lp_sub);
+
+ if ((server != NULL) && (strlen(server) > 0)) {
+ DEBUG(10, ("iprint server explicitly set to %s\n",
+ server));
+ return server;
+ }
+
+ DEBUG(10, ("iprint server left to default %s\n", cupsServer()));
+ return cupsServer();
+}
+
+/*
+ * Pass in an already connected http_t*
+ * Returns the server version if one can be found, multiplied by
+ * -1 for all NetWare versions. Returns 0 if a server version
+ * cannot be determined
+ */
+
+static int iprint_get_server_version(http_t *http, char* serviceUri)
+{
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char *ver; /* server version pointer */
+ char *vertmp; /* server version tmp pointer */
+ int serverVersion = 0; /* server version */
+ char *os; /* server os */
+ int osFlag = 0; /* 0 for NetWare, 1 for anything else */
+ char *temp; /* pointer for string manipulation */
+
+ /*
+ * Build an OPERATION_NOVELL_MGMT("get-server-version") request,
+ * which requires the following attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * operation-name
+ * service-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, (ipp_op_t)OPERATION_NOVELL_MGMT);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "service-uri", NULL, serviceUri);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "operation-name", NULL, "get-server-version");
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if (((response = cupsDoRequest(http, request, "/ipp/")) == NULL) ||
+ (ippGetStatusCode(response) >= IPP_OK_CONFLICT))
+ goto out;
+
+ if (((attr = ippFindAttribute(response, "server-version",
+ IPP_TAG_STRING)) != NULL)) {
+ if ((ver = strstr(ippGetString(attr, 0, NULL),
+ NOVELL_SERVER_VERSION_STRING)) != NULL) {
+ ver += strlen(NOVELL_SERVER_VERSION_STRING);
+ /*
+ * Strangely, libcups stores a IPP_TAG_STRING (octet
+ * string) as a null-terminated string with no length
+ * even though it could be binary data with nulls in
+ * it. Luckily, in this case the value is not binary.
+ */
+ serverVersion = strtol(ver, &vertmp, 10);
+
+ /* Check for not found, overflow or negative version */
+ if ((ver == vertmp) || (serverVersion < 0))
+ serverVersion = 0;
+ }
+
+ if ((os = strstr(ippGetString(attr, 0, NULL),
+ NOVELL_SERVER_SYSNAME)) != NULL) {
+ os += strlen(NOVELL_SERVER_SYSNAME);
+ if ((temp = strchr(os,'<')) != NULL)
+ *temp = '\0';
+ if (strcmp(os,NOVELL_SERVER_SYSNAME_NETWARE))
+ osFlag = 1; /* 1 for non-NetWare systems */
+ }
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (osFlag == 0)
+ serverVersion *= -1;
+
+ return serverVersion;
+}
+
+
+static int iprint_cache_add_printer(http_t *http,
+ int reqId,
+ const char *url,
+ struct pcap_cache **pcache)
+{
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ const char *name, /* printer-name attribute */
+ *info; /* printer-info attribute */
+ char smb_enabled, /* smb-enabled attribute */
+ secure; /* security-enabled attrib. */
+
+ const char *httpPath; /* path portion of the printer-uri */
+
+ static const char *pattrs[] = /* Requested printer attributes */
+ {
+ "printer-name",
+ "security-enabled",
+ "printer-info",
+ "smb-enabled"
+ };
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_PRINTER_ATTRIBUTES);
+ ippSetRequestId(request, reqId);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, url);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(pattrs) / sizeof(pattrs[0])),
+ NULL, pattrs);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((httpPath = strstr(url,"://")) == NULL ||
+ (httpPath = strchr(httpPath+3,'/')) == NULL)
+ {
+ ippDelete(request);
+ request = NULL;
+ goto out;
+ }
+
+ if ((response = cupsDoRequest(http, request, httpPath)) == NULL) {
+ ipp_status_t lastErr = cupsLastError();
+
+ /*
+ * Ignore printers that cannot be queried without credentials
+ */
+ if (lastErr == IPP_FORBIDDEN ||
+ lastErr == IPP_NOT_AUTHENTICATED ||
+ lastErr == IPP_NOT_AUTHORIZED)
+ goto out;
+
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(lastErr)));
+ goto out;
+ }
+
+ for (attr = ippFirstAttribute(response); attr != NULL;) {
+ /*
+ * Skip leading attributes until we hit a printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Pull the needed attributes from this printer...
+ */
+
+ name = NULL;
+ info = NULL;
+ smb_enabled= 1;
+ secure = 0;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER) {
+ if (strcmp(ippGetName(attr), "printer-name") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_NAME)
+ name = ippGetString(attr, 0, NULL);
+
+ if (strcmp(ippGetName(attr), "printer-info") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_TEXT ||
+ ippGetValueTag(attr) == IPP_TAG_TEXTLANG))
+ info = ippGetString(attr, 0, NULL);
+
+ /*
+ * If the smb-enabled attribute is present and the
+ * value is set to 0, don't show the printer.
+ * If the attribute is not present, assume that the
+ * printer should show up
+ */
+ if (!strcmp(ippGetName(attr), "smb-enabled") &&
+ ((ippGetValueTag(attr) == IPP_TAG_INTEGER &&
+ !ippGetInteger(attr, 0)) ||
+ (ippGetValueTag(attr) == IPP_TAG_BOOLEAN &&
+ !ippGetBoolean(attr, 0))))
+ smb_enabled = 0;
+
+ /*
+ * If the security-enabled attribute is present and the
+ * value is set to 1, don't show the printer.
+ * If the attribute is not present, assume that the
+ * printer should show up
+ */
+ if (!strcmp(ippGetName(attr), "security-enabled") &&
+ ((ippGetValueTag(attr) == IPP_TAG_INTEGER &&
+ ippGetInteger(attr, 0)) ||
+ (ippGetValueTag(attr) == IPP_TAG_BOOLEAN &&
+ ippGetBoolean(attr, 0))))
+ secure = 1;
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ * Make sure the printer is not a secure printer
+ * and make sure smb printing hasn't been explicitly
+ * disabled for the printer
+ */
+
+ if (name != NULL && !secure && smb_enabled)
+ pcap_cache_add_specific(pcache, name, info, NULL);
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+ return(0);
+}
+
+bool iprint_cache_reload(struct pcap_cache **_pcache)
+{
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ int i;
+ bool ret = false;
+ struct pcap_cache *pcache = NULL;
+
+ DEBUG(5, ("reloading iprint printcap cache\n"));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build a OPERATION_NOVELL_LIST_PRINTERS request, which requires the following attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, (ipp_op_t)OPERATION_NOVELL_LIST_PRINTERS);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "ipp-server", NULL, "ippSrvr");
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ if ((response = cupsDoRequest(http, request, "/ipp")) == NULL) {
+ DEBUG(0,("Unable to get printer list - %s\n",
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ for (attr = ippFirstAttribute(response); attr != NULL;) {
+ /*
+ * Skip leading attributes until we hit a printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_PRINTER)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Pull the needed attributes from this printer...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_PRINTER)
+ {
+ if (strcmp(ippGetName(attr), "printer-name") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_URI ||
+ ippGetValueTag(attr) == IPP_TAG_NAME ||
+ ippGetValueTag(attr) == IPP_TAG_TEXT ||
+ ippGetValueTag(attr) == IPP_TAG_NAMELANG ||
+ ippGetValueTag(attr) == IPP_TAG_TEXTLANG))
+ {
+ for (i = 0; i<ippGetCount(attr); i++)
+ {
+ const char *url = ippGetString(attr, i, NULL);
+ if (!url || !strlen(url))
+ continue;
+ iprint_cache_add_printer(http, i+2, url,
+ &pcache);
+ }
+ }
+ attr = ippNextAttribute(response);
+ }
+ }
+
+ ret = true;
+ *_pcache = pcache;
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_delete()' - Delete a job.
+ */
+
+static int iprint_job_delete(const char *sharename, const char *lprm_command, struct printjob *pjob)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the printer-uri */
+
+
+ DEBUG(5,("iprint_job_delete(%s, %p (%d))\n", sharename, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_CANCEL_JOB request, which uses the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * job-id
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_CANCEL_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(), sharename);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", pjob->sysjob);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s", sharename);
+
+ if ((response = cupsDoRequest(http, request, httpPath)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to cancel job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_pause()' - Pause a job.
+ */
+
+static int iprint_job_pause(int snum, struct printjob *pjob)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the printer-uri */
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+
+ DEBUG(5,("iprint_job_pause(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_HOLD_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * job-id
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_HOLD_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(),
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", pjob->sysjob);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s",
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ if ((response = cupsDoRequest(http, request, httpPath)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to hold job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_resume()' - Resume a paused job.
+ */
+
+static int iprint_job_resume(int snum, struct printjob *pjob)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the printer-uri */
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+
+ DEBUG(5,("iprint_job_resume(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_RELEASE_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * job-id
+ * requesting-user-name
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_RELEASE_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(),
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri", NULL, uri);
+
+ ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", pjob->sysjob);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s",
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ if ((response = cupsDoRequest(http, request, httpPath)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to release job %d - %s\n", pjob->sysjob,
+ ippErrorString(cupsLastError())));
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+
+/*
+ * 'iprint_job_submit()' - Submit a job for printing.
+ */
+
+static int iprint_job_submit(int snum, struct printjob *pjob,
+ enum printing_types printing_type,
+ char *lpq_cmd)
+{
+ int ret = 1; /* Return value */
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ DEBUG(5,("iprint_job_submit(%d, %p (%d))\n", snum, pjob, pjob->sysjob));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Build an IPP_PRINT_JOB request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * printer-uri
+ * requesting-user-name
+ * [document-data]
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_PRINT_JOB);
+ ippSetRequestId(request, 1);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(),
+ lp_printername(talloc_tos(), lp_sub, snum));
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "requesting-user-name",
+ NULL, pjob->user);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
+ "job-originating-host-name", NULL,
+ pjob->clientmachine);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name", NULL,
+ pjob->jobname);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(uri, sizeof(uri) - 1, "/ipp/%s", lp_printername(talloc_tos(), lp_sub, snum));
+
+ if ((response = cupsDoFileRequest(http, request, uri, pjob->filename)) != NULL) {
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to print file to %s - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ } else {
+ ret = 0;
+ }
+ } else {
+ DEBUG(0,("Unable to print file to `%s' - %s\n",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ ippErrorString(cupsLastError())));
+ }
+
+ if ( ret == 0 )
+ unlink(pjob->filename);
+ /* else print_job_end will do it for us */
+
+ if ( ret == 0 ) {
+
+ attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER);
+ if (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_JOB)
+ {
+ pjob->sysjob = ippGetInteger(attr, 0);
+ }
+ }
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return ret;
+}
+
+/*
+ * 'iprint_queue_get()' - Get all the jobs in the print queue.
+ */
+
+static int iprint_queue_get(const char *sharename,
+ enum printing_types printing_type,
+ char *lpq_command,
+ print_queue_struct **q,
+ print_status_struct *status)
+{
+ fstring printername;
+ http_t *http = NULL; /* HTTP connection to server */
+ ipp_t *request = NULL, /* IPP Request */
+ *response = NULL; /* IPP Response */
+ ipp_attribute_t *attr = NULL; /* Current attribute */
+ cups_lang_t *language = NULL; /* Default language */
+ char uri[HTTP_MAX_URI]; /* printer-uri attribute */
+ char serviceUri[HTTP_MAX_URI]; /* service-uri attribute */
+ char httpPath[HTTP_MAX_URI]; /* path portion of the uri */
+ int jobUseUnixTime = 0; /* Whether job times should
+ * be assumed to be Unix time */
+ int qcount = 0, /* Number of active queue entries */
+ qalloc = 0; /* Number of queue entries allocated */
+ print_queue_struct *queue = NULL, /* Queue entries */
+ *temp; /* Temporary pointer for queue */
+ const char *user_name, /* job-originating-user-name attribute */
+ *job_name; /* job-name attribute */
+ int job_id; /* job-id attribute */
+ int job_k_octets; /* job-k-octets attribute */
+ time_t job_time; /* time-at-creation attribute */
+ time_t printer_up_time = 0; /* printer's uptime */
+ ipp_jstate_t job_status; /* job-status attribute */
+ int job_priority; /* job-priority attribute */
+ static const char *jattrs[] = /* Requested job attributes */
+ {
+ "job-id",
+ "job-k-octets",
+ "job-name",
+ "job-originating-user-name",
+ "job-priority",
+ "job-state",
+ "time-at-creation",
+ };
+ static const char *pattrs[] = /* Requested printer attributes */
+ {
+ "printer-state",
+ "printer-state-message",
+ "printer-current-time",
+ "printer-up-time"
+ };
+
+ *q = NULL;
+
+ /* HACK ALERT!!! The problem with support the 'printer name'
+ option is that we key the tdb off the sharename. So we will
+ overload the lpq_command string to pass in the printername
+ (which is basically what we do for non-cups printers ... using
+ the lpq_command to get the queue listing). */
+
+ fstrcpy( printername, lpq_command );
+
+ DEBUG(5,("iprint_queue_get(%s, %p, %p)\n", printername, q, status));
+
+ /*
+ * Make sure we don't ask for passwords...
+ */
+
+ cupsSetPasswordCB(iprint_passwd_cb);
+
+ /*
+ * Try to connect to the server...
+ */
+
+#ifdef HAVE_HTTPCONNECT2
+ http = httpConnect2(iprint_server(),
+ ippPort(),
+ NULL,
+ AF_UNSPEC,
+ HTTP_ENCRYPTION_NEVER,
+ 1, /* blocking */
+ 30 * 1000, /* timeout */
+ NULL);
+#else
+ http = httpConnect(iprint_server(), ippPort());
+#endif
+ if (http == NULL) {
+ DEBUG(0,("Unable to connect to iPrint server %s - %s\n",
+ iprint_server(), strerror(errno)));
+ goto out;
+ }
+
+ /*
+ * Generate the printer URI and the service URI that goes with it...
+ */
+
+ slprintf(uri, sizeof(uri) - 1, "ipp://%s/ipp/%s", iprint_server(), printername);
+ slprintf(serviceUri, sizeof(serviceUri) - 1, "ipp://%s/ipp/", iprint_server());
+
+ /*
+ * For Linux iPrint servers from OES SP1 on, the iPrint server
+ * uses Unix time for job start times unless it detects the iPrint
+ * client in an http User-Agent header. (This was done to accommodate
+ * CUPS broken behavior. According to RFC 2911, section 4.3.14, job
+ * start times are supposed to be relative to how long the printer has
+ * been up.) Since libcups doesn't allow us to set that header before
+ * the request is sent, this ugly hack allows us to detect the server
+ * version and decide how to interpret the job time.
+ */
+ if (iprint_get_server_version(http, serviceUri) >=
+ NOVELL_SERVER_VERSION_OES_SP1)
+ jobUseUnixTime = 1;
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_PRINTER_ATTRIBUTES);
+ ippSetRequestId(request, 2);
+
+ language = cupsLangDefault();
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(pattrs) / sizeof(pattrs[0])),
+ NULL, pattrs);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s", printername);
+
+ if ((response = cupsDoRequest(http, request, httpPath)) == NULL) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(cupsLastError())));
+ *q = queue;
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get printer status for %s - %s\n", printername,
+ ippErrorString(ippGetStatusCode(response))));
+ *q = queue;
+ goto out;
+ }
+
+ /*
+ * Get the current printer status and convert it to the SAMBA values.
+ */
+
+ if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL) {
+ if (ippGetInteger(attr, 0) == IPP_PRINTER_STOPPED)
+ status->status = LPSTAT_STOPPED;
+ else
+ status->status = LPSTAT_OK;
+ }
+
+ if ((attr = ippFindAttribute(response, "printer-state-message",
+ IPP_TAG_TEXT)) != NULL)
+ fstrcpy(status->message, ippGetString(attr, 0, NULL));
+
+ if ((attr = ippFindAttribute(response, "printer-up-time",
+ IPP_TAG_INTEGER)) != NULL)
+ printer_up_time = ippGetInteger(attr, 0);
+
+ ippDelete(response);
+ response = NULL;
+
+ /*
+ * Build an IPP_GET_JOBS request, which requires the following
+ * attributes:
+ *
+ * attributes-charset
+ * attributes-natural-language
+ * requested-attributes
+ * printer-uri
+ */
+
+ request = ippNew();
+
+ ippSetOperation(request, IPP_GET_JOBS);
+ ippSetRequestId(request, 3);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
+ "attributes-charset", NULL, "utf-8");
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
+ "attributes-natural-language", NULL, language->language);
+
+ ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
+ "printer-uri", NULL, uri);
+
+ ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
+ "requested-attributes",
+ (sizeof(jattrs) / sizeof(jattrs[0])),
+ NULL, jattrs);
+
+ /*
+ * Do the request and get back a response...
+ */
+
+ slprintf(httpPath, sizeof(httpPath) - 1, "/ipp/%s", printername);
+
+ if ((response = cupsDoRequest(http, request, httpPath)) == NULL) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(cupsLastError())));
+ goto out;
+ }
+
+ if (ippGetStatusCode(response) >= IPP_OK_CONFLICT) {
+ DEBUG(0,("Unable to get jobs for %s - %s\n", uri,
+ ippErrorString(ippGetStatusCode(response))));
+ goto out;
+ }
+
+ /*
+ * Process the jobs...
+ */
+
+ qcount = 0;
+ qalloc = 0;
+ queue = NULL;
+
+ for (attr = ippFirstAttribute(response); attr != NULL; attr = ippNextAttribute(response)) {
+ /*
+ * Skip leading attributes until we hit a job...
+ */
+
+ while (attr != NULL && ippGetGroupTag(attr) != IPP_TAG_JOB)
+ attr = ippNextAttribute(response);
+
+ if (attr == NULL)
+ break;
+
+ /*
+ * Allocate memory as needed...
+ */
+ if (qcount >= qalloc) {
+ qalloc += 16;
+
+ queue = SMB_REALLOC_ARRAY(queue, print_queue_struct, qalloc);
+
+ if (queue == NULL) {
+ DEBUG(0,("iprint_queue_get: Not enough memory!\n"));
+ qcount = 0;
+ goto out;
+ }
+ }
+
+ temp = queue + qcount;
+ memset(temp, 0, sizeof(print_queue_struct));
+
+ /*
+ * Pull the needed attributes from this job...
+ */
+
+ job_id = 0;
+ job_priority = 50;
+ job_status = IPP_JOB_PENDING;
+ job_time = 0;
+ job_k_octets = 0;
+ user_name = NULL;
+ job_name = NULL;
+
+ while (attr != NULL && ippGetGroupTag(attr) == IPP_TAG_JOB) {
+ if (ippGetName(attr) == NULL) {
+ attr = ippNextAttribute(response);
+ break;
+ }
+
+ if (strcmp(ippGetName(attr), "job-id") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_id = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-k-octets") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_k_octets = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-priority") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ job_priority = ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "job-state") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_ENUM)
+ job_status = (ipp_jstate_t)ippGetInteger(attr, 0);
+
+ if (strcmp(ippGetName(attr), "time-at-creation") == 0 &&
+ ippGetValueTag(attr) == IPP_TAG_INTEGER)
+ {
+ /*
+ * If jobs times are in Unix time, the accuracy of the job
+ * start time depends upon the iPrint server's time being
+ * set correctly. Otherwise, the accuracy depends upon
+ * the Samba server's time being set correctly
+ */
+
+ if (jobUseUnixTime)
+ job_time = ippGetInteger(attr, 0);
+ else
+ job_time = time(NULL) - printer_up_time + ippGetInteger(attr, 0);
+ }
+
+ if (strcmp(ippGetName(attr), "job-name") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_NAMELANG ||
+ ippGetValueTag(attr) == IPP_TAG_NAME))
+ job_name = ippGetString(attr, 0, NULL);
+
+ if (strcmp(ippGetName(attr), "job-originating-user-name") == 0 &&
+ (ippGetValueTag(attr) == IPP_TAG_NAMELANG ||
+ ippGetValueTag(attr) == IPP_TAG_NAME))
+ user_name = ippGetString(attr, 0, NULL);
+
+ attr = ippNextAttribute(response);
+ }
+
+ /*
+ * See if we have everything needed...
+ */
+
+ if (user_name == NULL || job_name == NULL || job_id == 0) {
+ if (attr == NULL)
+ break;
+ else
+ continue;
+ }
+
+ temp->sysjob = job_id;
+ temp->size = job_k_octets * 1024;
+ temp->status = job_status == IPP_JOB_PENDING ? LPQ_QUEUED :
+ job_status == IPP_JOB_STOPPED ? LPQ_PAUSED :
+ job_status == IPP_JOB_HELD ? LPQ_PAUSED :
+ LPQ_PRINTING;
+ temp->priority = job_priority;
+ temp->time = job_time;
+ strncpy(temp->fs_user, user_name, sizeof(temp->fs_user) - 1);
+ strncpy(temp->fs_file, job_name, sizeof(temp->fs_file) - 1);
+
+ qcount ++;
+
+ if (attr == NULL)
+ break;
+ }
+
+ /*
+ * Return the job queue...
+ */
+
+ *q = queue;
+
+ out:
+ if (response)
+ ippDelete(response);
+
+ if (language)
+ cupsLangFree(language);
+
+ if (http)
+ httpClose(http);
+
+ return qcount;
+}
+
+
+/*
+ * 'iprint_queue_pause()' - Pause a print queue.
+ */
+
+static int iprint_queue_pause(int snum)
+{
+ return(-1); /* Not supported without credentials */
+}
+
+
+/*
+ * 'iprint_queue_resume()' - Restart a print queue.
+ */
+
+static int iprint_queue_resume(int snum)
+{
+ return(-1); /* Not supported without credentials */
+}
+
+/*******************************************************************
+ * iPrint printing interface definitions...
+ ******************************************************************/
+
+struct printif iprint_printif =
+{
+ PRINT_IPRINT,
+ iprint_queue_get,
+ iprint_queue_pause,
+ iprint_queue_resume,
+ iprint_job_delete,
+ iprint_job_pause,
+ iprint_job_resume,
+ iprint_job_submit,
+};
+
+#else
+ /* this keeps fussy compilers happy */
+ void print_iprint_dummy(void);
+ void print_iprint_dummy(void) {}
+#endif /* HAVE_IPRINT */
diff --git a/source3/printing/print_standard.c b/source3/printing/print_standard.c
new file mode 100644
index 0000000..7cbdac7
--- /dev/null
+++ b/source3/printing/print_standard.c
@@ -0,0 +1,157 @@
+/*
+ Unix SMB/CIFS implementation.
+ printcap parsing
+ Copyright (C) Karl Auer 1993-1998
+
+ Re-working by Martin Kiff, 1994
+
+ Re-written again by Andrew Tridgell
+
+ Modified for SVID support by Norm Jacobs, 1997
+
+ Modified for CUPS support by Michael Sweet, 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This module contains code to parse and cache printcap data, possibly
+ * in concert with the CUPS/SYSV/AIX-specific code found elsewhere.
+ *
+ * The way this module looks at the printcap file is very simplistic.
+ * Only the local printcap file is inspected (no searching of NIS
+ * databases etc).
+ *
+ * There are assumed to be one or more printer names per record, held
+ * as a set of sub-fields separated by vertical bar symbols ('|') in the
+ * first field of the record. The field separator is assumed to be a colon
+ * ':' and the record separator a newline.
+ *
+ * Lines ending with a backspace '\' are assumed to flag that the following
+ * line is a continuation line so that a set of lines can be read as one
+ * printcap entry.
+ *
+ * A line stating with a hash '#' is assumed to be a comment and is ignored
+ * Comments are discarded before the record is strung together from the
+ * set of continuation lines.
+ *
+ * Opening a pipe for "lpc status" and reading that would probably
+ * be pretty effective. Code to do this already exists in the freely
+ * distributable PCNFS server code.
+ */
+
+/* printcap parsing specific code moved here from printing/pcap.c */
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing/pcap.h"
+
+/* handle standard printcap - moved from pcap_printer_fn() */
+bool std_pcap_cache_reload(const char *pcap_name, struct pcap_cache **_pcache)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ FILE *pcap_file;
+ char *pcap_line;
+ struct pcap_cache *pcache = NULL;
+ bool print_warning = false;
+
+ if ((pcap_file = fopen(pcap_name, "r")) == NULL) {
+ DEBUG(0, ("Unable to open printcap file %s for read!\n", pcap_name));
+ talloc_free(frame);
+ return false;
+ }
+
+ while ((pcap_line = fgets_slash(frame, NULL, 1024,
+ pcap_file)) != NULL) {
+ char *name = NULL;
+ char *comment = NULL;
+ char *p, *q;
+
+ if (*pcap_line == '#' || *pcap_line == 0) {
+ TALLOC_FREE(pcap_line);
+ continue;
+ }
+
+ /* now we have a real printer line - cut at the first : */
+ if ((p = strchr_m(pcap_line, ':')) != NULL)
+ *p = 0;
+
+ /*
+ * now find the most likely printer name and comment
+ * this is pure guesswork, but it's better than nothing
+ */
+ for (p = pcap_line; p != NULL; p = q) {
+ bool has_punctuation = false;
+
+ if ((q = strchr_m(p, '|')) != NULL)
+ *q++ = 0;
+
+ has_punctuation = (strchr_m(p, ' ') ||
+ strchr_m(p, '\t') ||
+ strchr_m(p, '"') ||
+ strchr_m(p, '\'') ||
+ strchr_m(p, ';') ||
+ strchr_m(p, ',') ||
+ strchr_m(p, '(') ||
+ strchr_m(p, ')'));
+
+ if (name == NULL && !has_punctuation) {
+ name = talloc_strdup(frame, p);
+ TALLOC_FREE(pcap_line);
+ continue;
+ }
+
+ if (has_punctuation) {
+ comment = talloc_strdup(frame, p);
+ TALLOC_FREE(pcap_line);
+ continue;
+ }
+ }
+
+ if (name != NULL) {
+ bool ok;
+
+ if (!print_warning && strlen(name) > MAXPRINTERLEN) {
+ print_warning = true;
+ }
+
+ ok = pcap_cache_add_specific(&pcache,
+ name,
+ comment,
+ NULL);
+ if (!ok) {
+ fclose(pcap_file);
+ pcap_cache_destroy_specific(&pcache);
+ talloc_free(frame);
+ return false;
+ }
+ }
+ TALLOC_FREE(name);
+ TALLOC_FREE(comment);
+ TALLOC_FREE(pcap_line);
+ }
+
+ if (print_warning) {
+ DBG_WARNING("WARNING: You have some printer names that are "
+ "longer than %u characters. These may not be "
+ "accessible to some older clients!\n",
+ (unsigned int)MAXPRINTERLEN);
+ }
+
+ fclose(pcap_file);
+ *_pcache = pcache;
+ talloc_free(frame);
+ return true;
+}
diff --git a/source3/printing/print_svid.c b/source3/printing/print_svid.c
new file mode 100644
index 0000000..4006323
--- /dev/null
+++ b/source3/printing/print_svid.c
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 1997-1998 by Norm Jacobs, Colorado Springs, Colorado, USA
+ * Copyright (C) 1997-1998 by Sun Microsystem, Inc.
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module implements support for gathering and comparing available
+ * printer information on a SVID or XPG4 compliant system. It does this
+ * through the use of the SVID/XPG4 command "lpstat(1)".
+ *
+ * The expectations is that execution of the command "lpstat -v" will
+ * generate responses in the form of:
+ *
+ * device for serial: /dev/term/b
+ * system for fax: server
+ * system for color: server (as printer chroma)
+ */
+
+
+#include "includes.h"
+#include "printing/pcap.h"
+#include "lib/util_file.h"
+
+#if defined(SYSV) || defined(HPUX)
+bool sysv_cache_reload(struct pcap_cache **_pcache)
+{
+ char **lines;
+ int i;
+ struct pcap_cache *pcache = NULL;
+ char **argl = NULL;
+
+#if defined(HPUX)
+ DEBUG(5, ("reloading hpux printcap cache\n"));
+#else
+ DEBUG(5, ("reloading sysv printcap cache\n"));
+#endif
+
+ argl = str_list_make_empty(talloc_tos());
+ str_list_add_printf(&argl, "/usr/bin/lpstat");
+ str_list_add_printf(&argl, "-v");
+ if (argl == NULL) {
+ return false;
+ }
+
+ lines = file_lines_ploadv(talloc_tos(), argl, NULL);
+ if (lines == NULL) {
+#if defined(HPUX)
+
+ /*
+ * if "lpstat -v" is NULL then we check if schedular is running if it is
+ * that means no printers are added on the HP-UX system, if schedular is not
+ * running we display reload error.
+ */
+
+ char **scheduler;
+
+ argl[1] = talloc_strdup(argl, "-r");
+ if (argl[1] == NULL) {
+ TALLOC_FREE(argl);
+ return false;
+ }
+ scheduler = file_lines_ploadv(talloc_tos(), argl, NULL);
+ TALLOC_FREE(argl);
+ if(!strcmp(*scheduler,"scheduler is running")){
+ DEBUG(3,("No Printers found!!!\n"));
+ TALLOC_FREE(scheduler);
+ return True;
+ }
+ else{
+ DEBUG(3,("Scheduler is not running!!!\n"));
+ TALLOC_FREE(scheduler);
+ return False;
+ }
+#else
+ DEBUG(3,("No Printers found!!!\n"));
+ return False;
+#endif
+ }
+ TALLOC_FREE(argl);
+
+ for (i = 0; lines[i]; i++) {
+ char *name, *tmp;
+ char *buf = lines[i];
+
+ /* eat "system/device for " */
+ if (((tmp = strchr_m(buf, ' ')) == NULL) ||
+ ((tmp = strchr_m(++tmp, ' ')) == NULL))
+ continue;
+
+ /*
+ * In case we're only at the "for ".
+ */
+
+ if(!strncmp("for ", ++tmp, 4)) {
+ tmp=strchr_m(tmp, ' ');
+ tmp++;
+ }
+
+ /* Eat whitespace. */
+
+ while(*tmp == ' ')
+ ++tmp;
+
+ /*
+ * On HPUX there is an extra line that can be ignored.
+ * d.thibadeau 2001/08/09
+ */
+ if(!strncmp("remote to", tmp, 9))
+ continue;
+
+ name = tmp;
+
+ /* truncate the ": ..." */
+ if ((tmp = strchr_m(name, ':')) != NULL)
+ *tmp = '\0';
+
+ /* add it to the cache */
+ if (!pcap_cache_add_specific(&pcache, name, NULL, NULL)) {
+ TALLOC_FREE(lines);
+ pcap_cache_destroy_specific(&pcache);
+ return false;
+ }
+ }
+
+ TALLOC_FREE(lines);
+ *_pcache = pcache;
+ return true;
+}
+
+#else
+/* this keeps fussy compilers happy */
+ void print_svid_dummy(void);
+ void print_svid_dummy(void) {}
+#endif
diff --git a/source3/printing/printer_list.c b/source3/printing/printer_list.c
new file mode 100644
index 0000000..7fd07ed
--- /dev/null
+++ b/source3/printing/printer_list.c
@@ -0,0 +1,460 @@
+/*
+ Unix SMB/CIFS implementation.
+ Share Database of available printers.
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "util_tdb.h"
+#include "printer_list.h"
+
+#define PL_KEY_PREFIX "PRINTERLIST/PRN/"
+#define PL_KEY_FORMAT PL_KEY_PREFIX"%s"
+#define PL_TIMESTAMP_KEY "PRINTERLIST/GLOBAL/LAST_REFRESH"
+#define PL_DATA_FORMAT "ddPPP"
+#define PL_TSTAMP_FORMAT "dd"
+
+static struct db_context *printerlist_db;
+
+static struct db_context *get_printer_list_db(void)
+{
+ char *db_path;
+
+ if (printerlist_db != NULL) {
+ return printerlist_db;
+ }
+
+ db_path = lock_path(talloc_tos(), "printer_list.tdb");
+ if (db_path == NULL) {
+ return NULL;
+ }
+
+ printerlist_db = db_open(NULL,
+ db_path,
+ 0,
+ TDB_DEFAULT|TDB_INCOMPATIBLE_HASH,
+ O_RDWR|O_CREAT,
+ 0644,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(db_path);
+ if (printerlist_db == NULL) {
+ DBG_ERR("Failed to open printer_list.tdb\n");
+ }
+ return printerlist_db;
+}
+
+NTSTATUS printer_list_get_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char **comment,
+ const char **location,
+ time_t *last_refresh)
+{
+ struct db_context *db;
+ char *key;
+ TDB_DATA data;
+ uint32_t time_h, time_l;
+ char *nstr = NULL;
+ char *cstr = NULL;
+ char *lstr = NULL;
+ NTSTATUS status;
+ int ret;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ key = talloc_asprintf(mem_ctx, PL_KEY_FORMAT, name);
+ if (!key) {
+ DEBUG(0, ("Failed to allocate key name!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dbwrap_fetch_bystring_upper(db, key, key, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(6, ("Failed to fetch record! "
+ "The printer database is empty?\n"));
+ goto done;
+ }
+
+ ret = tdb_unpack(data.dptr, data.dsize,
+ PL_DATA_FORMAT,
+ &time_h, &time_l, &nstr, &cstr, &lstr);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to unpack printer data\n"));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ if (last_refresh) {
+ *last_refresh = (time_t)(((uint64_t)time_h << 32) + time_l);
+ }
+
+ if (comment) {
+ *comment = talloc_strdup(mem_ctx, cstr);
+ if (!*comment) {
+ DEBUG(1, ("Failed to strdup comment!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ if (location) {
+ *location = talloc_strdup(mem_ctx, lstr);
+ if (*location == NULL) {
+ DEBUG(1, ("Failed to strdup location!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+ SAFE_FREE(nstr);
+ SAFE_FREE(cstr);
+ SAFE_FREE(lstr);
+ TALLOC_FREE(key);
+ return status;
+}
+
+bool printer_list_printername_exists(const char *name)
+{
+ struct db_context *db = get_printer_list_db();
+ char *key = NULL;
+ bool ok;
+
+ if (db == NULL) {
+ return false;
+ }
+
+ key = talloc_asprintf_strupper_m(
+ talloc_tos(), PL_KEY_FORMAT, name);
+ if (key == NULL) {
+ return false;
+ }
+
+ ok = dbwrap_exists(db, string_term_tdb_data(key));
+ TALLOC_FREE(key);
+ return ok;
+}
+
+NTSTATUS printer_list_set_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *comment,
+ const char *location,
+ time_t last_refresh)
+{
+ struct db_context *db;
+ char *key;
+ TDB_DATA data;
+ uint64_t time_64;
+ uint32_t time_h, time_l;
+ NTSTATUS status;
+ int len;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ key = talloc_asprintf(mem_ctx, PL_KEY_FORMAT, name);
+ if (!key) {
+ DEBUG(0, ("Failed to allocate key name!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (comment == NULL) {
+ comment = "";
+ }
+
+ if (location == NULL) {
+ location = "";
+ }
+
+ time_64 = last_refresh;
+ time_l = time_64 & 0xFFFFFFFFL;
+ time_h = time_64 >> 32;
+
+ len = tdb_pack(NULL, 0,
+ PL_DATA_FORMAT,
+ time_h,
+ time_l,
+ name,
+ comment,
+ location);
+
+ data.dptr = talloc_array(key, uint8_t, len);
+ if (!data.dptr) {
+ DEBUG(0, ("Failed to allocate tdb data buffer!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ data.dsize = len;
+
+ len = tdb_pack(data.dptr, data.dsize,
+ PL_DATA_FORMAT,
+ time_h,
+ time_l,
+ name,
+ comment,
+ location);
+
+ status = dbwrap_store_bystring_upper(db, key, data, TDB_REPLACE);
+
+done:
+ TALLOC_FREE(key);
+ return status;
+}
+
+NTSTATUS printer_list_get_last_refresh(time_t *last_refresh)
+{
+ struct db_context *db;
+ TDB_DATA data;
+ uint32_t time_h, time_l;
+ NTSTATUS status;
+ int ret;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ ZERO_STRUCT(data);
+
+ status = dbwrap_fetch_bystring(db, talloc_tos(), PL_TIMESTAMP_KEY, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to fetch record!\n"));
+ goto done;
+ }
+
+ ret = tdb_unpack(data.dptr, data.dsize,
+ PL_TSTAMP_FORMAT, &time_h, &time_l);
+ TALLOC_FREE(data.dptr);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to unpack printer data\n"));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ *last_refresh = (time_t)(((uint64_t)time_h << 32) + time_l);
+ status = NT_STATUS_OK;
+
+done:
+ return status;
+}
+
+NTSTATUS printer_list_mark_reload(void)
+{
+ struct db_context *db;
+ TDB_DATA data;
+ uint32_t time_h, time_l;
+ time_t now = time_mono(NULL);
+ NTSTATUS status;
+ int len;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ time_l = ((uint64_t)now) & 0xFFFFFFFFL;
+ time_h = ((uint64_t)now) >> 32;
+
+ len = tdb_pack(NULL, 0, PL_TSTAMP_FORMAT, time_h, time_l);
+
+ data.dptr = talloc_array(talloc_tos(), uint8_t, len);
+ if (!data.dptr) {
+ DEBUG(0, ("Failed to allocate tdb data buffer!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ data.dsize = len;
+
+ len = tdb_pack(data.dptr, data.dsize,
+ PL_TSTAMP_FORMAT, time_h, time_l);
+
+ status = dbwrap_store_bystring(db, PL_TIMESTAMP_KEY,
+ data, TDB_REPLACE);
+
+done:
+ TALLOC_FREE(data.dptr);
+ return status;
+}
+
+typedef int (printer_list_trv_fn_t)(struct db_record *, void *);
+
+static NTSTATUS printer_list_traverse(printer_list_trv_fn_t *fn,
+ void *private_data,
+ bool read_only)
+{
+ struct db_context *db;
+ NTSTATUS status;
+
+ db = get_printer_list_db();
+ if (db == NULL) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ if (read_only) {
+ status = dbwrap_traverse_read(db, fn, private_data, NULL);
+ } else {
+ status = dbwrap_traverse(db, fn, private_data, NULL);
+ }
+
+ return status;
+}
+
+struct printer_list_clean_state {
+ time_t last_refresh;
+ NTSTATUS status;
+};
+
+static int printer_list_clean_fn(struct db_record *rec, void *private_data)
+{
+ struct printer_list_clean_state *state =
+ (struct printer_list_clean_state *)private_data;
+ uint32_t time_h, time_l;
+ time_t refresh;
+ char *name;
+ char *comment;
+ char *location;
+ int ret;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+
+ /* skip anything that does not contain PL_DATA_FORMAT data */
+ if (strncmp((char *)key.dptr,
+ PL_KEY_PREFIX, sizeof(PL_KEY_PREFIX)-1)) {
+ return 0;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ ret = tdb_unpack(value.dptr, value.dsize,
+ PL_DATA_FORMAT, &time_h, &time_l, &name, &comment,
+ &location);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to unpack printer data\n"));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+
+ SAFE_FREE(name);
+ SAFE_FREE(comment);
+ SAFE_FREE(location);
+
+ refresh = (time_t)(((uint64_t)time_h << 32) + time_l);
+
+ if (refresh < state->last_refresh) {
+ state->status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+NTSTATUS printer_list_clean_old(void)
+{
+ struct printer_list_clean_state state;
+ NTSTATUS status;
+
+ status = printer_list_get_last_refresh(&state.last_refresh);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ state.status = NT_STATUS_OK;
+
+ status = printer_list_traverse(printer_list_clean_fn, &state, false);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL) &&
+ !NT_STATUS_IS_OK(state.status)) {
+ status = state.status;
+ }
+
+ return status;
+}
+
+struct printer_list_exec_state {
+ void (*fn)(const char *, const char *, const char *, void *);
+ void *private_data;
+ NTSTATUS status;
+};
+
+static int printer_list_exec_fn(struct db_record *rec, void *private_data)
+{
+ struct printer_list_exec_state *state =
+ (struct printer_list_exec_state *)private_data;
+ uint32_t time_h, time_l;
+ char *name;
+ char *comment;
+ char *location;
+ int ret;
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+
+ /* always skip PL_TIMESTAMP_KEY key */
+ if (strequal((const char *)key.dptr, PL_TIMESTAMP_KEY)) {
+ return 0;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ ret = tdb_unpack(value.dptr, value.dsize,
+ PL_DATA_FORMAT, &time_h, &time_l, &name, &comment,
+ &location);
+ if (ret == -1) {
+ DEBUG(1, ("Failed to unpack printer data\n"));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+
+ state->fn(name, comment, location, state->private_data);
+
+ SAFE_FREE(name);
+ SAFE_FREE(comment);
+ SAFE_FREE(location);
+ return 0;
+}
+
+NTSTATUS printer_list_read_run_fn(void (*fn)(const char *, const char *, const char *, void *),
+ void *private_data)
+{
+ struct printer_list_exec_state state;
+ NTSTATUS status;
+
+ state.fn = fn;
+ state.private_data = private_data;
+ state.status = NT_STATUS_OK;
+
+ status = printer_list_traverse(printer_list_exec_fn, &state, true);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL) &&
+ !NT_STATUS_IS_OK(state.status)) {
+ status = state.status;
+ }
+
+ return status;
+}
diff --git a/source3/printing/printer_list.h b/source3/printing/printer_list.h
new file mode 100644
index 0000000..c687048
--- /dev/null
+++ b/source3/printing/printer_list.h
@@ -0,0 +1,105 @@
+/*
+ Unix SMB/CIFS implementation.
+ Share Database of available printers.
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PRINTER_LIST_H_
+#define _PRINTER_LIST_H_
+
+/**
+ * @brief Get the comment and the last refresh time from the printer list
+ * database.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] name The printer name to lookup.
+ *
+ * @param[out] comment A pointer to store the comment of the printer.
+ *
+ * @param[out] location A pointer to store the location of the printer.
+ *
+ * @param[out] last_refresh A pointer to store the last refresh time of the
+ * printer.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_get_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char **comment,
+ const char **location,
+ time_t *last_refresh);
+
+bool printer_list_printername_exists(const char *name);
+
+/**
+ * @brief Add a printer to the printer list database.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] name The printer name to store in the db.
+ *
+ * @param[in] comment The comment to store in the db.
+ *
+ * @param[in] location The location to store in the db.
+ *
+ * @param[in] last_refresh The last refresh time of the printer to store in
+ * the db.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_set_printer(TALLOC_CTX *mem_ctx,
+ const char *name,
+ const char *comment,
+ const char *location,
+ time_t last_refresh);
+
+/**
+ * @brief Get the time of the last refresh of the printer database.
+ *
+ * @param[out] last_refresh The last refresh time in the db.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_get_last_refresh(time_t *last_refresh);
+
+/**
+ * @brief Mark the database as reloaded.
+ *
+ * This sets the last refresh time to the current time. You can get the last
+ * reload/refresh time of the database with printer_list_get_last_refresh().
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_mark_reload(void);
+
+/**
+ * @brief Cleanup old entries in the database.
+ *
+ * Entries older than the last refresh times will be deleted.
+ *
+ * @return NT_STATUS_OK on success, a correspoining NTSTATUS error
+ * code on a failure.
+ */
+NTSTATUS printer_list_clean_old(void);
+
+NTSTATUS printer_list_read_run_fn(void (*fn)(const char *, const char *, const char *, void *),
+ void *private_data);
+#endif /* _PRINTER_LIST_H_ */
diff --git a/source3/printing/printing.c b/source3/printing/printing.c
new file mode 100644
index 0000000..70b2b11
--- /dev/null
+++ b/source3/printing/printing.c
@@ -0,0 +1,3266 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/globals.h"
+#include "system/syslog.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "nt_printing.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "printing/notify.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/queue_process.h"
+#include "serverid.h"
+#include "smbd/smbd.h"
+#include "auth.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+
+extern userdom_struct current_user_info;
+
+/* Current printer interface */
+static bool remove_from_jobs_added(const char* sharename, uint32_t jobid);
+
+static int get_queue_status(const char* sharename, print_status_struct *);
+
+/****************************************************************************
+ Initialise the printing backend. Called once at startup before the fork().
+****************************************************************************/
+
+bool print_backend_init(struct messaging_context *msg_ctx)
+{
+ const char *sversion = "INFO/version";
+ int services = lp_numservices();
+ int snum;
+ bool ok;
+ char *print_cache_path;
+
+ print_cache_path = cache_path(talloc_tos(), "printing");
+ if (print_cache_path == NULL) {
+ return false;
+ }
+ ok = directory_create_or_exist(print_cache_path, 0755);
+ TALLOC_FREE(print_cache_path);
+ if (!ok) {
+ return false;
+ }
+
+ /* handle a Samba upgrade */
+
+ for (snum = 0; snum < services; snum++) {
+ struct tdb_print_db *pdb;
+ if (!lp_printable(snum))
+ continue;
+
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (!pdb)
+ continue;
+ if (tdb_lock_bystring(pdb->tdb, sversion) != 0) {
+ DEBUG(0,("print_backend_init: Failed to open printer %s database\n", lp_const_servicename(snum) ));
+ release_print_db(pdb);
+ return False;
+ }
+ if (tdb_fetch_int32(pdb->tdb, sversion) != PRINT_DATABASE_VERSION) {
+ tdb_wipe_all(pdb->tdb);
+ tdb_store_int32(pdb->tdb, sversion, PRINT_DATABASE_VERSION);
+ }
+ tdb_unlock_bystring(pdb->tdb, sversion);
+ release_print_db(pdb);
+ }
+
+ close_all_print_db(); /* Don't leave any open. */
+
+ /* do NT print initialization... */
+ return nt_printing_init(msg_ctx);
+}
+
+/****************************************************************************
+ Shut down printing backend. Called once at shutdown to close the tdb.
+****************************************************************************/
+
+void printing_end(void)
+{
+ close_all_print_db(); /* Don't leave any open. */
+}
+
+/****************************************************************************
+ Retrieve the set of printing functions for a given service. This allows
+ us to set the printer function table based on the value of the 'printing'
+ service parameter.
+
+ Use the generic interface as the default and only use cups interface only
+ when asked for (and only when supported)
+****************************************************************************/
+
+static struct printif *get_printer_fns_from_type( enum printing_types type )
+{
+ struct printif *printer_fns = &generic_printif;
+
+#ifdef HAVE_CUPS
+ if ( type == PRINT_CUPS ) {
+ printer_fns = &cups_printif;
+ }
+#endif /* HAVE_CUPS */
+
+#ifdef HAVE_IPRINT
+ if ( type == PRINT_IPRINT ) {
+ printer_fns = &iprint_printif;
+ }
+#endif /* HAVE_IPRINT */
+
+ printer_fns->type = type;
+
+ return printer_fns;
+}
+
+static struct printif *get_printer_fns( int snum )
+{
+ return get_printer_fns_from_type( (enum printing_types)lp_printing(snum) );
+}
+
+
+/****************************************************************************
+ Useful function to generate a tdb key.
+****************************************************************************/
+
+static TDB_DATA print_key(uint32_t jobid, uint32_t *tmp)
+{
+ TDB_DATA ret;
+
+ SIVAL(tmp, 0, jobid);
+ ret.dptr = (uint8_t *)tmp;
+ ret.dsize = sizeof(*tmp);
+ return ret;
+}
+
+/****************************************************************************
+ Pack the devicemode to store it in a tdb.
+****************************************************************************/
+static int pack_devicemode(struct spoolss_DeviceMode *devmode, uint8_t *buf, int buflen)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = { .data = NULL };
+ int len = 0;
+
+ if (devmode) {
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(),
+ devmode,
+ (ndr_push_flags_fn_t)
+ ndr_push_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("pack_devicemode: "
+ "error encoding spoolss_DeviceMode\n"));
+ goto done;
+ }
+ }
+
+ len = tdb_pack(buf, buflen, "B", blob.length, blob.data);
+
+ if (devmode) {
+ DEBUG(8, ("Packed devicemode [%s]\n", devmode->formname));
+ }
+
+done:
+ return len;
+}
+
+/****************************************************************************
+ Unpack the devicemode to store it in a tdb.
+****************************************************************************/
+static int unpack_devicemode(TALLOC_CTX *mem_ctx,
+ const uint8_t *buf, int buflen,
+ struct spoolss_DeviceMode **devmode)
+{
+ struct spoolss_DeviceMode *dm;
+ enum ndr_err_code ndr_err;
+ char *data = NULL;
+ uint32_t data_len = 0;
+ DATA_BLOB blob;
+ int len = 0;
+
+ *devmode = NULL;
+
+ len = tdb_unpack(buf, buflen, "B", &data_len, &data);
+ if (!data) {
+ return len;
+ }
+
+ dm = talloc_zero(mem_ctx, struct spoolss_DeviceMode);
+ if (!dm) {
+ goto done;
+ }
+
+ blob = data_blob_const(data, data_len);
+
+ ndr_err = ndr_pull_struct_blob(&blob, dm, dm,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("unpack_devicemode: "
+ "error parsing spoolss_DeviceMode\n"));
+ goto done;
+ }
+
+ DEBUG(8, ("Unpacked devicemode [%s](%s)\n",
+ dm->devicename, dm->formname));
+ if (dm->driverextra_data.data) {
+ DEBUG(8, ("with a private section of %d bytes\n",
+ dm->__driverextra_length));
+ }
+
+ *devmode = dm;
+
+done:
+ SAFE_FREE(data);
+ return len;
+}
+
+/***********************************************************************
+ unpack a pjob from a tdb buffer
+***********************************************************************/
+
+static int unpack_pjob(TALLOC_CTX *mem_ctx, uint8_t *buf, int buflen,
+ struct printjob *pjob)
+{
+ int len = 0;
+ int used;
+ uint32_t pjpid, pjjobid, pjsysjob, pjfd, pjstarttime, pjstatus;
+ uint32_t pjsize, pjpage_count, pjspooled, pjsmbjob;
+
+ if (!buf || !pjob) {
+ return -1;
+ }
+
+ len += tdb_unpack(buf+len, buflen-len, "ddddddddddfffff",
+ &pjpid,
+ &pjjobid,
+ &pjsysjob,
+ &pjfd,
+ &pjstarttime,
+ &pjstatus,
+ &pjsize,
+ &pjpage_count,
+ &pjspooled,
+ &pjsmbjob,
+ pjob->filename,
+ pjob->jobname,
+ pjob->user,
+ pjob->clientmachine,
+ pjob->queuename);
+
+ if (len == -1) {
+ return -1;
+ }
+
+ used = unpack_devicemode(mem_ctx, buf+len, buflen-len, &pjob->devmode);
+ if (used == -1) {
+ return -1;
+ }
+
+ len += used;
+
+ pjob->pid = pjpid;
+ pjob->jobid = pjjobid;
+ pjob->sysjob = pjsysjob;
+ pjob->fd = pjfd;
+ pjob->starttime = pjstarttime;
+ pjob->status = pjstatus;
+ pjob->size = pjsize;
+ pjob->page_count = pjpage_count;
+ pjob->spooled = pjspooled;
+ pjob->smbjob = pjsmbjob;
+
+ return len;
+
+}
+
+/****************************************************************************
+ Useful function to find a print job in the database.
+****************************************************************************/
+
+static struct printjob *print_job_find(TALLOC_CTX *mem_ctx,
+ const char *sharename,
+ uint32_t jobid)
+{
+ struct printjob *pjob;
+ uint32_t tmp;
+ TDB_DATA ret;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ DEBUG(10,("print_job_find: looking up job %u for share %s\n",
+ (unsigned int)jobid, sharename ));
+
+ if (!pdb) {
+ return NULL;
+ }
+
+ ret = tdb_fetch(pdb->tdb, print_key(jobid, &tmp));
+ release_print_db(pdb);
+
+ if (!ret.dptr) {
+ DEBUG(10, ("print_job_find: failed to find jobid %u.\n",
+ jobid));
+ return NULL;
+ }
+
+ pjob = talloc_zero(mem_ctx, struct printjob);
+ if (pjob == NULL) {
+ goto err_out;
+ }
+
+ if (unpack_pjob(mem_ctx, ret.dptr, ret.dsize, pjob) == -1) {
+ DEBUG(10, ("failed to unpack jobid %u.\n", jobid));
+ talloc_free(pjob);
+ pjob = NULL;
+ goto err_out;
+ }
+
+ DEBUG(10,("print_job_find: returning system job %d for jobid %u.\n",
+ pjob->sysjob, jobid));
+ SMB_ASSERT(pjob->jobid == jobid);
+
+err_out:
+ SAFE_FREE(ret.dptr);
+ return pjob;
+}
+
+struct job_traverse_state {
+ int sysjob;
+ uint32_t jobid;
+};
+
+/* find spoolss jobid based on sysjob */
+static int sysjob_to_jobid_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *private_data)
+{
+ struct printjob *pjob;
+ struct job_traverse_state *state =
+ (struct job_traverse_state *)private_data;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ pjob = (struct printjob *)data.dptr;
+ if (key.dsize != sizeof(uint32_t))
+ return 0;
+
+ if (state->sysjob == pjob->sysjob) {
+ state->jobid = pjob->jobid;
+ return 1;
+ }
+
+ return 0;
+}
+
+uint32_t sysjob_to_jobid_pdb(struct tdb_print_db *pdb, int sysjob)
+{
+ struct job_traverse_state state;
+
+ state.sysjob = sysjob;
+ state.jobid = (uint32_t)-1;
+
+ tdb_traverse(pdb->tdb, sysjob_to_jobid_traverse_fn, &state);
+
+ return state.jobid;
+}
+
+/****************************************************************************
+ This is a *horribly expensive call as we have to iterate through all the
+ current printer tdb's. Don't do this often ! JRA.
+****************************************************************************/
+
+uint32_t sysjob_to_jobid(int unix_jobid)
+{
+ int services = lp_numservices();
+ int snum;
+ struct job_traverse_state state;
+
+ state.sysjob = unix_jobid;
+ state.jobid = (uint32_t)-1;
+
+ for (snum = 0; snum < services; snum++) {
+ struct tdb_print_db *pdb;
+ if (!lp_printable(snum))
+ continue;
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (!pdb) {
+ continue;
+ }
+ tdb_traverse(pdb->tdb, sysjob_to_jobid_traverse_fn, &state);
+ release_print_db(pdb);
+ if (state.jobid != (uint32_t)-1)
+ return state.jobid;
+ }
+ return (uint32_t)-1;
+}
+
+/* find sysjob based on spoolss jobid */
+static int jobid_to_sysjob_traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA key,
+ TDB_DATA data, void *private_data)
+{
+ struct printjob *pjob;
+ struct job_traverse_state *state =
+ (struct job_traverse_state *)private_data;
+
+ if (!data.dptr || data.dsize == 0)
+ return 0;
+
+ pjob = (struct printjob *)data.dptr;
+ if (key.dsize != sizeof(uint32_t))
+ return 0;
+
+ if (state->jobid == pjob->jobid) {
+ state->sysjob = pjob->sysjob;
+ return 1;
+ }
+
+ return 0;
+}
+
+int jobid_to_sysjob_pdb(struct tdb_print_db *pdb, uint32_t jobid)
+{
+ struct job_traverse_state state;
+
+ state.sysjob = -1;
+ state.jobid = jobid;
+
+ tdb_traverse(pdb->tdb, jobid_to_sysjob_traverse_fn, &state);
+
+ return state.sysjob;
+}
+
+/****************************************************************************
+ Send notifications based on what has changed after a pjob_store.
+****************************************************************************/
+
+static const struct {
+ uint32_t lpq_status;
+ uint32_t spoolss_status;
+} lpq_to_spoolss_status_map[] = {
+ { LPQ_QUEUED, JOB_STATUS_QUEUED },
+ { LPQ_PAUSED, JOB_STATUS_PAUSED },
+ { LPQ_SPOOLING, JOB_STATUS_SPOOLING },
+ { LPQ_PRINTING, JOB_STATUS_PRINTING },
+ { LPQ_DELETING, JOB_STATUS_DELETING },
+ { LPQ_OFFLINE, JOB_STATUS_OFFLINE },
+ { LPQ_PAPEROUT, JOB_STATUS_PAPEROUT },
+ { LPQ_PRINTED, JOB_STATUS_PRINTED },
+ { LPQ_DELETED, JOB_STATUS_DELETED },
+ { LPQ_BLOCKED, JOB_STATUS_BLOCKED_DEVQ },
+ { LPQ_USER_INTERVENTION, JOB_STATUS_USER_INTERVENTION },
+ { (uint32_t)-1, 0 }
+};
+
+/* Convert a lpq status value stored in printing.tdb into the
+ appropriate win32 API constant. */
+
+static uint32_t map_to_spoolss_status(uint32_t lpq_status)
+{
+ int i = 0;
+
+ while (lpq_to_spoolss_status_map[i].lpq_status != -1) {
+ if (lpq_to_spoolss_status_map[i].lpq_status == lpq_status)
+ return lpq_to_spoolss_status_map[i].spoolss_status;
+ i++;
+ }
+
+ return 0;
+}
+
+/***************************************************************************
+ Append a jobid to a list
+***************************************************************************/
+
+static bool add_to_jobs_list(
+ struct tdb_print_db *pdb, uint32_t jobid, const char *key)
+{
+ uint8_t store_jobid[sizeof(uint32_t)];
+ TDB_DATA data = {
+ .dptr = store_jobid, .dsize = sizeof(store_jobid)
+ };
+ int ret;
+
+ SIVAL(&store_jobid, 0, jobid);
+
+ DBG_DEBUG("Added jobid %"PRIu32" to %s\n", jobid, key);
+
+ ret = tdb_append(pdb->tdb, string_tdb_data(key), data);
+ return ret == 0;
+}
+
+/***************************************************************************
+ Remove a jobid from the 'jobs changed' list.
+***************************************************************************/
+
+static bool remove_from_jobs_list(
+ const char *keystr, const char *sharename, uint32_t jobid)
+{
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ TDB_DATA data, key;
+ size_t job_count, i;
+ bool ret = False;
+ bool gotlock = False;
+
+ if (!pdb) {
+ return False;
+ }
+
+ ZERO_STRUCT(data);
+
+ key = string_tdb_data(keystr);
+
+ if (tdb_chainlock_with_timeout(pdb->tdb, key, 5) != 0)
+ goto out;
+
+ gotlock = True;
+
+ data = tdb_fetch(pdb->tdb, key);
+
+ if (data.dptr == NULL || data.dsize == 0 || (data.dsize % 4 != 0))
+ goto out;
+
+ job_count = data.dsize / 4;
+ for (i = 0; i < job_count; i++) {
+ uint32_t ch_jobid;
+
+ ch_jobid = IVAL(data.dptr, i*4);
+ if (ch_jobid == jobid) {
+ if (i < job_count -1 )
+ memmove(data.dptr + (i*4), data.dptr + (i*4) + 4, (job_count - i - 1)*4 );
+ data.dsize -= 4;
+ if (tdb_store(pdb->tdb, key, data, TDB_REPLACE) != 0)
+ goto out;
+ break;
+ }
+ }
+
+ ret = True;
+ out:
+
+ if (gotlock)
+ tdb_chainunlock(pdb->tdb, key);
+ SAFE_FREE(data.dptr);
+ release_print_db(pdb);
+ if (ret)
+ DBG_DEBUG("removed jobid %"PRIu32"\n", jobid);
+ else
+ DBG_DEBUG("Failed to remove jobid %"PRIu32"\n", jobid);
+ return ret;
+}
+
+static bool remove_from_jobs_changed(const char* sharename, uint32_t jobid)
+{
+ bool ret = remove_from_jobs_list(
+ "INFO/jobs_changed", sharename, jobid);
+ return ret;
+}
+
+static void pjob_store_notify(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid,
+ struct printjob *old_data,
+ struct printjob *new_data,
+ bool *pchanged)
+{
+ bool new_job = false;
+ bool changed = false;
+
+ if (old_data == NULL) {
+ new_job = true;
+ }
+
+ /* ACHTUNG! Due to a bug in Samba's spoolss parsing of the
+ NOTIFY_INFO_DATA buffer, we *have* to send the job submission
+ time first or else we'll end up with potential alignment
+ errors. I don't think the systemtime should be spooled as
+ a string, but this gets us around that error.
+ --jerry (i'll feel dirty for this) */
+
+ if (new_job) {
+ notify_job_submitted(ev, msg_ctx,
+ sharename, jobid, new_data->starttime);
+ notify_job_username(ev, msg_ctx,
+ sharename, jobid, new_data->user);
+ notify_job_name(ev, msg_ctx,
+ sharename, jobid, new_data->jobname);
+ notify_job_status(ev, msg_ctx,
+ sharename, jobid, map_to_spoolss_status(new_data->status));
+ notify_job_total_bytes(ev, msg_ctx,
+ sharename, jobid, new_data->size);
+ notify_job_total_pages(ev, msg_ctx,
+ sharename, jobid, new_data->page_count);
+ } else {
+ if (!strequal(old_data->jobname, new_data->jobname)) {
+ notify_job_name(ev, msg_ctx, sharename,
+ jobid, new_data->jobname);
+ changed = true;
+ }
+
+ if (old_data->status != new_data->status) {
+ notify_job_status(ev, msg_ctx,
+ sharename, jobid,
+ map_to_spoolss_status(new_data->status));
+ }
+
+ if (old_data->size != new_data->size) {
+ notify_job_total_bytes(ev, msg_ctx,
+ sharename, jobid, new_data->size);
+ }
+
+ if (old_data->page_count != new_data->page_count) {
+ notify_job_total_pages(ev, msg_ctx,
+ sharename, jobid,
+ new_data->page_count);
+ }
+ }
+
+ *pchanged = changed;
+}
+
+/****************************************************************************
+ Store a job structure back to the database.
+****************************************************************************/
+
+static bool pjob_store(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid,
+ struct printjob *pjob)
+{
+ uint32_t tmp;
+ TDB_DATA old_data, new_data;
+ bool ret = False;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ uint8_t *buf = NULL;
+ int len, newlen, buflen;
+
+
+ if (!pdb)
+ return False;
+
+ /* Get old data */
+
+ old_data = tdb_fetch(pdb->tdb, print_key(jobid, &tmp));
+
+ /* Doh! Now we have to pack/unpack data since the NT_DEVICEMODE was added */
+
+ newlen = 0;
+
+ do {
+ len = 0;
+ buflen = newlen;
+ len += tdb_pack(buf+len, buflen-len, "ddddddddddfffff",
+ (uint32_t)pjob->pid,
+ (uint32_t)pjob->jobid,
+ (uint32_t)pjob->sysjob,
+ (uint32_t)pjob->fd,
+ (uint32_t)pjob->starttime,
+ (uint32_t)pjob->status,
+ (uint32_t)pjob->size,
+ (uint32_t)pjob->page_count,
+ (uint32_t)pjob->spooled,
+ (uint32_t)pjob->smbjob,
+ pjob->filename,
+ pjob->jobname,
+ pjob->user,
+ pjob->clientmachine,
+ pjob->queuename);
+
+ len += pack_devicemode(pjob->devmode, buf+len, buflen-len);
+
+ if (buflen != len) {
+ buf = (uint8_t *)SMB_REALLOC(buf, len);
+ if (!buf) {
+ DEBUG(0,("pjob_store: failed to enlarge buffer!\n"));
+ goto done;
+ }
+ newlen = len;
+ }
+ } while ( buflen != len );
+
+
+ /* Store new data */
+
+ new_data.dptr = buf;
+ new_data.dsize = len;
+ ret = (tdb_store(pdb->tdb, print_key(jobid, &tmp), new_data,
+ TDB_REPLACE) == 0);
+
+ /* Send notify updates for what has changed */
+
+ if (ret) {
+ bool changed = false;
+ struct printjob old_pjob;
+
+ if (old_data.dsize) {
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL)
+ goto done;
+
+ len = unpack_pjob(tmp_ctx, old_data.dptr,
+ old_data.dsize, &old_pjob);
+ if (len != -1 ) {
+ pjob_store_notify(ev,
+ msg_ctx,
+ sharename, jobid, &old_pjob,
+ pjob,
+ &changed);
+ if (changed) {
+ add_to_jobs_list(
+ pdb,
+ jobid,
+ "INFO/jobs_changed");
+ }
+ }
+ talloc_free(tmp_ctx);
+
+ } else {
+ /* new job */
+ pjob_store_notify(ev, msg_ctx,
+ sharename, jobid, NULL, pjob,
+ &changed);
+ }
+ }
+
+done:
+ release_print_db(pdb);
+ SAFE_FREE( old_data.dptr );
+ SAFE_FREE( buf );
+
+ return ret;
+}
+
+/****************************************************************************
+ Remove a job structure from the database.
+****************************************************************************/
+
+static void pjob_delete(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char* sharename, uint32_t jobid)
+{
+ uint32_t tmp;
+ struct printjob *pjob;
+ uint32_t job_status = 0;
+ struct tdb_print_db *pdb;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ pdb = get_print_db_byname(sharename);
+ if (!pdb) {
+ goto err_out;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ DEBUG(5, ("we were asked to delete nonexistent job %u\n",
+ jobid));
+ goto err_release;
+ }
+
+ /* We must cycle through JOB_STATUS_DELETING and
+ JOB_STATUS_DELETED for the port monitor to delete the job
+ properly. */
+
+ job_status = JOB_STATUS_DELETING|JOB_STATUS_DELETED;
+ notify_job_status(ev, msg_ctx, sharename, jobid, job_status);
+
+ /* Remove from printing.tdb */
+
+ tdb_delete(pdb->tdb, print_key(jobid, &tmp));
+ remove_from_jobs_added(sharename, jobid);
+ rap_jobid_delete(sharename, jobid);
+err_release:
+ release_print_db(pdb);
+err_out:
+ talloc_free(tmp_ctx);
+}
+
+/****************************************************************************
+ List a unix job in the print database.
+****************************************************************************/
+
+static void print_unix_job(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, print_queue_struct *q,
+ uint32_t jobid)
+{
+ struct printjob pj, *old_pj;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ if (jobid == (uint32_t)-1) {
+ jobid = q->sysjob + UNIX_JOB_START;
+ }
+
+ /* Preserve the timestamp on an existing unix print job */
+
+ old_pj = print_job_find(tmp_ctx, sharename, jobid);
+
+ ZERO_STRUCT(pj);
+
+ pj.pid = (pid_t)-1;
+ pj.jobid = jobid;
+ pj.sysjob = q->sysjob;
+ pj.fd = -1;
+ pj.starttime = old_pj ? old_pj->starttime : q->time;
+ pj.status = q->status;
+ pj.size = q->size;
+ pj.spooled = True;
+ fstrcpy(pj.filename, old_pj ? old_pj->filename : "");
+ if (jobid < UNIX_JOB_START) {
+ pj.smbjob = True;
+ fstrcpy(pj.jobname, old_pj ? old_pj->jobname : "Remote Downlevel Document");
+ } else {
+ pj.smbjob = False;
+ fstrcpy(pj.jobname, old_pj ? old_pj->jobname : q->fs_file);
+ }
+ fstrcpy(pj.user, old_pj ? old_pj->user : q->fs_user);
+ fstrcpy(pj.queuename, old_pj ? old_pj->queuename : sharename );
+
+ pjob_store(ev, msg_ctx, sharename, jobid, &pj);
+ talloc_free(tmp_ctx);
+}
+
+
+struct traverse_struct {
+ print_queue_struct *queue;
+ size_t qcount, snum, maxcount, total_jobs;
+ const char *sharename;
+ time_t lpq_time;
+ const char *lprm_command;
+ struct printif *print_if;
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ TALLOC_CTX *mem_ctx;
+};
+
+/****************************************************************************
+ Utility fn to delete any jobs that are no longer active.
+****************************************************************************/
+
+static int traverse_fn_delete(TDB_CONTEXT *t, TDB_DATA key, TDB_DATA data, void *state)
+{
+ struct traverse_struct *ts = (struct traverse_struct *)state;
+ struct printjob pjob;
+ uint32_t jobid;
+ size_t i = 0;
+
+ if ( key.dsize != sizeof(jobid) )
+ return 0;
+
+ if (unpack_pjob(ts->mem_ctx, data.dptr, data.dsize, &pjob) == -1)
+ return 0;
+ talloc_free(pjob.devmode);
+ jobid = pjob.jobid;
+
+ if (!pjob.smbjob) {
+ /* remove a unix job if it isn't in the system queue any more */
+ for (i=0;i<ts->qcount;i++) {
+ if (ts->queue[i].sysjob == pjob.sysjob) {
+ break;
+ }
+ }
+ if (i == ts->qcount) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to !smbjob\n",
+ (unsigned int)jobid ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ return 0;
+ }
+
+ /* need to continue the the bottom of the function to
+ save the correct attributes */
+ }
+
+ /* maybe it hasn't been spooled yet */
+ if (!pjob.spooled) {
+ /* if a job is not spooled and the process doesn't
+ exist then kill it. This cleans up after smbd
+ deaths */
+ if (!process_exists_by_pid(pjob.pid)) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to !process_exists (%u)\n",
+ (unsigned int)jobid, (unsigned int)pjob.pid ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ } else
+ ts->total_jobs++;
+ return 0;
+ }
+
+ /* this check only makes sense for jobs submitted from Windows clients */
+
+ if (pjob.smbjob) {
+ for (i=0;i<ts->qcount;i++) {
+ if ( pjob.status == LPQ_DELETED )
+ continue;
+
+ if (ts->queue[i].sysjob == pjob.sysjob) {
+
+ /* try to clean up any jobs that need to be deleted */
+
+ if ( pjob.status == LPQ_DELETING ) {
+ int result;
+
+ result = (*(ts->print_if->job_delete))(
+ ts->sharename, ts->lprm_command, &pjob );
+
+ if ( result != 0 ) {
+ /* if we can't delete, then reset the job status */
+ pjob.status = LPQ_QUEUED;
+ pjob_store(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid, &pjob);
+ }
+ else {
+ /* if we deleted the job, the remove the tdb record */
+ pjob_delete(ts->ev,
+ ts->msg_ctx,
+ ts->sharename, jobid);
+ pjob.status = LPQ_DELETED;
+ }
+
+ }
+
+ break;
+ }
+ }
+ }
+
+ /* The job isn't in the system queue - we have to assume it has
+ completed, so delete the database entry. */
+
+ if (i == ts->qcount) {
+
+ /* A race can occur between the time a job is spooled and
+ when it appears in the lpq output. This happens when
+ the job is added to printing.tdb when another smbd
+ running print_queue_update() has completed a lpq and
+ is currently traversing the printing tdb and deleting jobs.
+ Don't delete the job if it was submitted after the lpq_time. */
+
+ if (pjob.starttime < ts->lpq_time) {
+ DEBUG(10,("traverse_fn_delete: pjob %u deleted due to pjob.starttime (%u) < ts->lpq_time (%u)\n",
+ (unsigned int)jobid,
+ (unsigned int)pjob.starttime,
+ (unsigned int)ts->lpq_time ));
+ pjob_delete(ts->ev, ts->msg_ctx,
+ ts->sharename, jobid);
+ } else
+ ts->total_jobs++;
+ return 0;
+ }
+
+ /* Save the pjob attributes we will store. */
+ ts->queue[i].sysjob = pjob.sysjob;
+ ts->queue[i].size = pjob.size;
+ ts->queue[i].page_count = pjob.page_count;
+ ts->queue[i].status = pjob.status;
+ ts->queue[i].priority = 1;
+ ts->queue[i].time = pjob.starttime;
+ fstrcpy(ts->queue[i].fs_user, pjob.user);
+ fstrcpy(ts->queue[i].fs_file, pjob.jobname);
+
+ ts->total_jobs++;
+
+ return 0;
+}
+
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static void print_cache_flush(const char *sharename)
+{
+ fstring key;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return;
+ slprintf(key, sizeof(key)-1, "CACHE/%s", sharename);
+ tdb_store_int32(pdb->tdb, key, -1);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+ Check if someone already thinks they are doing the update.
+****************************************************************************/
+
+static pid_t get_updating_pid(const char *sharename)
+{
+ fstring keystr;
+ TDB_DATA data, key;
+ pid_t updating_pid;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return (pid_t)-1;
+ slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ data = tdb_fetch(pdb->tdb, key);
+ release_print_db(pdb);
+ if (!data.dptr || data.dsize != sizeof(pid_t)) {
+ SAFE_FREE(data.dptr);
+ return (pid_t)-1;
+ }
+
+ updating_pid = IVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+
+ if (process_exists_by_pid(updating_pid))
+ return updating_pid;
+
+ return (pid_t)-1;
+}
+
+/****************************************************************************
+ Set the fact that we're doing the update, or have finished doing the update
+ in the tdb.
+****************************************************************************/
+
+static void set_updating_pid(const fstring sharename, bool updating)
+{
+ fstring keystr;
+ TDB_DATA key;
+ TDB_DATA data;
+ pid_t updating_pid = getpid();
+ uint8_t buffer[4];
+
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return;
+
+ slprintf(keystr, sizeof(keystr)-1, "UPDATING/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ DEBUG(5, ("set_updating_pid: %supdating lpq cache for print share %s\n",
+ updating ? "" : "not ",
+ sharename ));
+
+ if ( !updating ) {
+ tdb_delete(pdb->tdb, key);
+ release_print_db(pdb);
+ return;
+ }
+
+ SIVAL( buffer, 0, updating_pid);
+ data.dptr = buffer;
+ data.dsize = 4; /* we always assume this is a 4 byte value */
+
+ tdb_store(pdb->tdb, key, data, TDB_REPLACE);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+ Sort print jobs by submittal time.
+****************************************************************************/
+
+static int printjob_comp(print_queue_struct *j1, print_queue_struct *j2)
+{
+ /* Silly cases */
+
+ if (!j1 && !j2)
+ return 0;
+ if (!j1)
+ return -1;
+ if (!j2)
+ return 1;
+
+ /* Sort on job start time */
+
+ if (j1->time == j2->time)
+ return 0;
+ return (j1->time > j2->time) ? 1 : -1;
+}
+
+/****************************************************************************
+ Store the sorted queue representation for later portmon retrieval.
+ Skip deleted jobs
+****************************************************************************/
+
+static void store_queue_struct(struct tdb_print_db *pdb, struct traverse_struct *pts)
+{
+ TDB_DATA data;
+ int max_reported_jobs = lp_max_reported_print_jobs(pts->snum);
+ print_queue_struct *queue = pts->queue;
+ size_t len;
+ size_t i;
+ unsigned int qcount;
+
+ if (max_reported_jobs && (max_reported_jobs < pts->qcount))
+ pts->qcount = max_reported_jobs;
+ qcount = 0;
+
+ /* Work out the size. */
+ data.dsize = 0;
+ data.dsize += tdb_pack(NULL, 0, "d", qcount);
+
+ for (i = 0; i < pts->qcount; i++) {
+ if ( queue[i].status == LPQ_DELETED )
+ continue;
+
+ qcount++;
+ data.dsize += tdb_pack(NULL, 0, "ddddddff",
+ (uint32_t)queue[i].sysjob,
+ (uint32_t)queue[i].size,
+ (uint32_t)queue[i].page_count,
+ (uint32_t)queue[i].status,
+ (uint32_t)queue[i].priority,
+ (uint32_t)queue[i].time,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ }
+
+ if ((data.dptr = (uint8_t *)SMB_MALLOC(data.dsize)) == NULL)
+ return;
+
+ len = 0;
+ len += tdb_pack(data.dptr + len, data.dsize - len, "d", qcount);
+ for (i = 0; i < pts->qcount; i++) {
+ if ( queue[i].status == LPQ_DELETED )
+ continue;
+
+ len += tdb_pack(data.dptr + len, data.dsize - len, "ddddddff",
+ (uint32_t)queue[i].sysjob,
+ (uint32_t)queue[i].size,
+ (uint32_t)queue[i].page_count,
+ (uint32_t)queue[i].status,
+ (uint32_t)queue[i].priority,
+ (uint32_t)queue[i].time,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ }
+
+ tdb_store(pdb->tdb, string_tdb_data("INFO/linear_queue_array"), data,
+ TDB_REPLACE);
+ SAFE_FREE(data.dptr);
+ return;
+}
+
+static TDB_DATA get_jobs_added_data(struct tdb_print_db *pdb)
+{
+ TDB_DATA data;
+
+ ZERO_STRUCT(data);
+
+ data = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_added"));
+ if (data.dptr == NULL || data.dsize == 0 || (data.dsize % 4 != 0)) {
+ SAFE_FREE(data.dptr);
+ ZERO_STRUCT(data);
+ }
+
+ return data;
+}
+
+static void check_job_added(const char *sharename, TDB_DATA data, uint32_t jobid)
+{
+ unsigned int i;
+ unsigned int job_count = data.dsize / 4;
+
+ for (i = 0; i < job_count; i++) {
+ uint32_t ch_jobid;
+
+ ch_jobid = IVAL(data.dptr, i*4);
+ if (ch_jobid == jobid)
+ remove_from_jobs_added(sharename, jobid);
+ }
+}
+
+/****************************************************************************
+ Check if the print queue has been updated recently enough.
+****************************************************************************/
+
+static bool print_cache_expired(const char *sharename, bool check_pending)
+{
+ fstring key;
+ time_t last_qscan_time, time_now = time(NULL);
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ bool result = False;
+
+ if (!pdb)
+ return False;
+
+ snprintf(key, sizeof(key), "CACHE/%s", sharename);
+ last_qscan_time = (time_t)tdb_fetch_int32(pdb->tdb, key);
+
+ /*
+ * Invalidate the queue for 3 reasons.
+ * (1). last queue scan time == -1.
+ * (2). Current time - last queue scan time > allowed cache time.
+ * (3). last queue scan time > current time + MAX_CACHE_VALID_TIME (1 hour by default).
+ * This last test picks up machines for which the clock has been moved
+ * forward, an lpq scan done and then the clock moved back. Otherwise
+ * that last lpq scan would stay around for a loooong loooong time... :-). JRA.
+ */
+
+ if (last_qscan_time == ((time_t)-1)
+ || (time_now - last_qscan_time) >= lp_lpq_cache_time()
+ || last_qscan_time > (time_now + MAX_CACHE_VALID_TIME))
+ {
+ uint32_t u;
+ time_t msg_pending_time;
+
+ DEBUG(4, ("print_cache_expired: cache expired for queue %s "
+ "(last_qscan_time = %d, time now = %d, qcachetime = %d)\n",
+ sharename, (int)last_qscan_time, (int)time_now,
+ (int)lp_lpq_cache_time() ));
+
+ /* check if another smbd has already sent a message to update the
+ queue. Give the pending message one minute to clear and
+ then send another message anyways. Make sure to check for
+ clocks that have been run forward and then back again. */
+
+ snprintf(key, sizeof(key), "MSG_PENDING/%s", sharename);
+
+ if ( check_pending
+ && tdb_fetch_uint32( pdb->tdb, key, &u )
+ && (msg_pending_time=u) > 0
+ && msg_pending_time <= time_now
+ && (time_now - msg_pending_time) < 60 )
+ {
+ DEBUG(4,("print_cache_expired: message already pending for %s. Accepting cache\n",
+ sharename));
+ goto done;
+ }
+
+ result = True;
+ }
+
+done:
+ release_print_db(pdb);
+ return result;
+}
+
+/****************************************************************************
+ main work for updating the lpq cache for a printer queue
+****************************************************************************/
+
+static void print_queue_update_internal(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct printif *current_printif,
+ char *lpq_command, char *lprm_command)
+{
+ size_t i, qcount;
+ print_queue_struct *queue = NULL;
+ print_status_struct status;
+ print_status_struct old_status;
+ struct printjob *pjob;
+ struct traverse_struct tstruct;
+ TDB_DATA data, key;
+ TDB_DATA jcdata;
+ fstring keystr, cachestr;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+
+ if ((pdb == NULL) || (tmp_ctx == NULL)) {
+ return;
+ }
+
+ DEBUG(5,("print_queue_update_internal: printer = %s, type = %d, lpq command = [%s]\n",
+ sharename, current_printif->type, lpq_command));
+
+ /*
+ * Update the cache time FIRST ! Stops others even
+ * attempting to get the lock and doing this
+ * if the lpq takes a long time.
+ */
+
+ slprintf(cachestr, sizeof(cachestr)-1, "CACHE/%s", sharename);
+ tdb_store_int32(pdb->tdb, cachestr, (int)time(NULL));
+
+ /* get the current queue using the appropriate interface */
+ ZERO_STRUCT(status);
+
+ qcount = (*(current_printif->queue_get))(sharename,
+ current_printif->type,
+ lpq_command, &queue, &status);
+
+ DBG_NOTICE("%zu job%s in queue for %s\n",
+ qcount,
+ (qcount != 1) ? "s" : "",
+ sharename);
+
+ /* Sort the queue by submission time otherwise they are displayed
+ in hash order. */
+
+ TYPESAFE_QSORT(queue, qcount, printjob_comp);
+
+ /*
+ any job in the internal database that is marked as spooled
+ and doesn't exist in the system queue is considered finished
+ and removed from the database
+
+ any job in the system database but not in the internal database
+ is added as a unix job
+
+ fill in any system job numbers as we go
+ */
+ jcdata = get_jobs_added_data(pdb);
+
+ for (i=0; i<qcount; i++) {
+ uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ /* assume its a unix print job */
+ print_unix_job(ev, msg_ctx,
+ sharename, &queue[i], jobid);
+ continue;
+ }
+
+ /* we have an active SMB print job - update its status */
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ /* err, somethings wrong. Probably smbd was restarted
+ with jobs in the queue. All we can do is treat them
+ like unix jobs. Pity. */
+ DEBUG(1, ("queued print job %d not found in jobs list, "
+ "assuming unix job\n", jobid));
+ print_unix_job(ev, msg_ctx,
+ sharename, &queue[i], jobid);
+ continue;
+ }
+
+ /* don't reset the status on jobs to be deleted */
+
+ if ( pjob->status != LPQ_DELETING )
+ pjob->status = queue[i].status;
+
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+
+ check_job_added(sharename, jcdata, jobid);
+ }
+
+ SAFE_FREE(jcdata.dptr);
+
+ /* now delete any queued entries that don't appear in the
+ system queue */
+ tstruct.queue = queue;
+ tstruct.qcount = qcount;
+ tstruct.snum = -1;
+ tstruct.total_jobs = 0;
+ tstruct.lpq_time = time(NULL);
+ tstruct.sharename = sharename;
+ tstruct.lprm_command = lprm_command;
+ tstruct.print_if = current_printif;
+ tstruct.ev = ev;
+ tstruct.msg_ctx = msg_ctx;
+ tstruct.mem_ctx = tmp_ctx;
+
+ tdb_traverse(pdb->tdb, traverse_fn_delete, (void *)&tstruct);
+
+ /* Store the linearised queue, max jobs only. */
+ store_queue_struct(pdb, &tstruct);
+
+ SAFE_FREE(tstruct.queue);
+ talloc_free(tmp_ctx);
+
+ DBG_DEBUG("printer %s INFO, total_jobs = %zu\n",
+ sharename,
+ tstruct.total_jobs);
+
+ tdb_store_int32(pdb->tdb, "INFO/total_jobs", tstruct.total_jobs);
+
+ get_queue_status(sharename, &old_status);
+ if (old_status.qcount != qcount) {
+ DBG_DEBUG("Queue status change %zu jobs -> %zu jobs "
+ "for printer %s\n",
+ old_status.qcount,
+ qcount,
+ sharename);
+ }
+
+ /* store the new queue status structure */
+ slprintf(keystr, sizeof(keystr)-1, "STATUS/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ status.qcount = qcount;
+ data.dptr = (uint8_t *)&status;
+ data.dsize = sizeof(status);
+ tdb_store(pdb->tdb, key, data, TDB_REPLACE);
+
+ /*
+ * Update the cache time again. We want to do this call
+ * as little as possible...
+ */
+
+ slprintf(keystr, sizeof(keystr)-1, "CACHE/%s", sharename);
+ tdb_store_int32(pdb->tdb, keystr, (int32_t)time(NULL));
+
+ /* clear the msg pending record for this queue */
+
+ snprintf(keystr, sizeof(keystr), "MSG_PENDING/%s", sharename);
+
+ if ( !tdb_store_uint32( pdb->tdb, keystr, 0 ) ) {
+ /* log a message but continue on */
+
+ DEBUG(0,("print_queue_update: failed to store MSG_PENDING flag for [%s]!\n",
+ sharename));
+ }
+
+ release_print_db( pdb );
+
+ return;
+}
+
+/****************************************************************************
+ Update the internal database from the system print queue for a queue.
+ obtain a lock on the print queue before proceeding (needed when multiple
+ smbd processes maytry to update the lpq cache concurrently).
+****************************************************************************/
+
+static void print_queue_update_with_lock( struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct printif *current_printif,
+ char *lpq_command, char *lprm_command )
+{
+ fstring keystr;
+ struct tdb_print_db *pdb;
+
+ DEBUG(5,("print_queue_update_with_lock: printer share = %s\n", sharename));
+ pdb = get_print_db_byname(sharename);
+ if (!pdb)
+ return;
+
+ if ( !print_cache_expired(sharename, False) ) {
+ DEBUG(5,("print_queue_update_with_lock: print cache for %s is still ok\n", sharename));
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * Check to see if someone else is doing this update.
+ * This is essentially a mutex on the update.
+ */
+
+ if (get_updating_pid(sharename) != -1) {
+ release_print_db(pdb);
+ return;
+ }
+
+ /* Lock the queue for the database update */
+
+ slprintf(keystr, sizeof(keystr) - 1, "LOCK/%s", sharename);
+ /* Only wait 10 seconds for this. */
+ if (tdb_lock_bystring_with_timeout(pdb->tdb, keystr, 10) != 0) {
+ DEBUG(0,("print_queue_update_with_lock: Failed to lock printer %s database\n", sharename));
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * Ensure that no one else got in here.
+ * If the updating pid is still -1 then we are
+ * the winner.
+ */
+
+ if (get_updating_pid(sharename) != -1) {
+ /*
+ * Someone else is doing the update, exit.
+ */
+ tdb_unlock_bystring(pdb->tdb, keystr);
+ release_print_db(pdb);
+ return;
+ }
+
+ /*
+ * We're going to do the update ourselves.
+ */
+
+ /* Tell others we're doing the update. */
+ set_updating_pid(sharename, True);
+
+ /*
+ * Allow others to enter and notice we're doing
+ * the update.
+ */
+
+ tdb_unlock_bystring(pdb->tdb, keystr);
+
+ /* do the main work now */
+
+ print_queue_update_internal(ev, msg_ctx,
+ sharename, current_printif,
+ lpq_command, lprm_command);
+
+ /* Delete our pid from the db. */
+ set_updating_pid(sharename, False);
+ release_print_db(pdb);
+}
+
+/****************************************************************************
+this is the receive function of the background lpq updater
+****************************************************************************/
+void print_queue_receive(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ fstring sharename;
+ char *lpqcommand = NULL, *lprmcommand = NULL;
+ int printing_type;
+ size_t len;
+
+ len = tdb_unpack( (uint8_t *)data->data, data->length, "fdPP",
+ sharename,
+ &printing_type,
+ &lpqcommand,
+ &lprmcommand );
+
+ if ( len == -1 ) {
+ SAFE_FREE(lpqcommand);
+ SAFE_FREE(lprmcommand);
+ DEBUG(0,("print_queue_receive: Got invalid print queue update message\n"));
+ return;
+ }
+
+ print_queue_update_with_lock(global_event_context(), msg, sharename,
+ get_printer_fns_from_type((enum printing_types)printing_type),
+ lpqcommand, lprmcommand );
+
+ SAFE_FREE(lpqcommand);
+ SAFE_FREE(lprmcommand);
+ return;
+}
+
+/****************************************************************************
+update the internal database from the system print queue for a queue
+****************************************************************************/
+
+static void print_queue_update(struct messaging_context *msg_ctx,
+ int snum, bool force)
+{
+ char key[268];
+ fstring sharename;
+ char *lpqcommand = NULL;
+ char *lprmcommand = NULL;
+ uint8_t *buffer = NULL;
+ size_t len = 0;
+ size_t newlen;
+ struct tdb_print_db *pdb;
+ int type;
+ struct printif *current_printif;
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ fstrcpy( sharename, lp_const_servicename(snum));
+
+ /* don't strip out characters like '$' from the printername */
+
+ lpqcommand = talloc_string_sub2(ctx,
+ lp_lpq_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (!lpqcommand) {
+ return;
+ }
+ lpqcommand = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ get_current_user_info_domain(),
+ lpqcommand);
+ if (!lpqcommand) {
+ return;
+ }
+
+ lprmcommand = talloc_string_sub2(ctx,
+ lp_lprm_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (!lprmcommand) {
+ return;
+ }
+ lprmcommand = talloc_sub_full(ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ get_current_user_info_domain(),
+ lprmcommand);
+ if (!lprmcommand) {
+ return;
+ }
+
+ /*
+ * Make sure that the background queue process exists.
+ * Otherwise just do the update ourselves
+ */
+
+ if ( force || background_lpq_updater_pid == -1 ) {
+ DEBUG(4,("print_queue_update: updating queue [%s] myself\n", sharename));
+ current_printif = get_printer_fns( snum );
+ print_queue_update_with_lock(global_event_context(), msg_ctx,
+ sharename, current_printif,
+ lpqcommand, lprmcommand);
+
+ return;
+ }
+
+ type = lp_printing(snum);
+
+ /* get the length */
+
+ len = tdb_pack( NULL, 0, "fdPP",
+ sharename,
+ type,
+ lpqcommand,
+ lprmcommand );
+
+ buffer = SMB_XMALLOC_ARRAY( uint8_t, len );
+
+ /* now pack the buffer */
+ newlen = tdb_pack( buffer, len, "fdPP",
+ sharename,
+ type,
+ lpqcommand,
+ lprmcommand );
+
+ SMB_ASSERT( newlen == len );
+
+ DEBUG(10,("print_queue_update: Sending message -> printer = %s, "
+ "type = %d, lpq command = [%s] lprm command = [%s]\n",
+ sharename, type, lpqcommand, lprmcommand ));
+
+ /* here we set a msg pending record for other smbd processes
+ to throttle the number of duplicate print_queue_update msgs
+ sent. */
+
+ pdb = get_print_db_byname(sharename);
+ if (!pdb) {
+ SAFE_FREE(buffer);
+ return;
+ }
+
+ snprintf(key, sizeof(key), "MSG_PENDING/%s", sharename);
+
+ if ( !tdb_store_uint32( pdb->tdb, key, time(NULL) ) ) {
+ /* log a message but continue on */
+
+ DEBUG(0,("print_queue_update: failed to store MSG_PENDING flag for [%s]!\n",
+ sharename));
+ }
+
+ release_print_db( pdb );
+
+ /* finally send the message */
+
+ send_to_bgqd(msg_ctx, MSG_PRINTER_UPDATE, (uint8_t *)buffer, len);
+
+ SAFE_FREE( buffer );
+
+ return;
+}
+
+/****************************************************************************
+ Create/Update an entry in the print tdb that will allow us to send notify
+ updates only to interested smbd's.
+****************************************************************************/
+
+bool print_notify_register_pid(int snum)
+{
+ TDB_DATA data;
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ const char *printername;
+ uint32_t mypid = (uint32_t)getpid();
+ bool ret = False;
+ size_t i;
+
+ /* if (snum == -1), then the change notify request was
+ on a print server handle and we need to register on
+ all print queues */
+
+ if (snum == -1)
+ {
+ int num_services = lp_numservices();
+ int idx;
+
+ for ( idx=0; idx<num_services; idx++ ) {
+ if (lp_snum_ok(idx) && lp_printable(idx) )
+ print_notify_register_pid(idx);
+ }
+
+ return True;
+ }
+ else /* register for a specific printer */
+ {
+ printername = lp_const_servicename(snum);
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+ }
+
+ if (tdb_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to lock printer %s\n",
+ printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ /* Add ourselves and increase the refcount. */
+
+ for (i = 0; i < data.dsize; i += 8) {
+ if (IVAL(data.dptr,i) == mypid) {
+ uint32_t new_refcount = IVAL(data.dptr, i+4) + 1;
+ SIVAL(data.dptr, i+4, new_refcount);
+ break;
+ }
+ }
+
+ if (i == data.dsize) {
+ /* We weren't in the list. Realloc. */
+ data.dptr = (uint8_t *)SMB_REALLOC(data.dptr, data.dsize + 8);
+ if (!data.dptr) {
+ DEBUG(0,("print_notify_register_pid: Relloc fail for printer %s\n",
+ printername));
+ goto done;
+ }
+ data.dsize += 8;
+ SIVAL(data.dptr,data.dsize - 8,mypid);
+ SIVAL(data.dptr,data.dsize - 4,1); /* Refcount. */
+ }
+
+ /* Store back the record. */
+ if (tdb_store_bystring(tdb, NOTIFY_PID_LIST_KEY, data, TDB_REPLACE) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to update pid \
+list for printer %s\n", printername));
+ goto done;
+ }
+
+ ret = True;
+
+ done:
+
+ tdb_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/****************************************************************************
+ Update an entry in the print tdb that will allow us to send notify
+ updates only to interested smbd's.
+****************************************************************************/
+
+bool print_notify_deregister_pid(int snum)
+{
+ TDB_DATA data;
+ struct tdb_print_db *pdb = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ const char *printername;
+ uint32_t mypid = (uint32_t)getpid();
+ size_t i;
+ bool ret = False;
+
+ /* if ( snum == -1 ), we are deregister a print server handle
+ which means to deregister on all print queues */
+
+ if (snum == -1)
+ {
+ int num_services = lp_numservices();
+ int idx;
+
+ for ( idx=0; idx<num_services; idx++ ) {
+ if ( lp_snum_ok(idx) && lp_printable(idx) )
+ print_notify_deregister_pid(idx);
+ }
+
+ return True;
+ }
+ else /* deregister a specific printer */
+ {
+ printername = lp_const_servicename(snum);
+ pdb = get_print_db_byname(printername);
+ if (!pdb)
+ return False;
+ tdb = pdb->tdb;
+ }
+
+ if (tdb_lock_bystring_with_timeout(tdb, NOTIFY_PID_LIST_KEY, 10) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to lock \
+printer %s database\n", printername));
+ if (pdb)
+ release_print_db(pdb);
+ return False;
+ }
+
+ data = get_printer_notify_pid_list( tdb, printername, True );
+
+ /* Reduce refcount. Remove ourselves if zero. */
+
+ for (i = 0; i < data.dsize; ) {
+ if (IVAL(data.dptr,i) == mypid) {
+ uint32_t refcount = IVAL(data.dptr, i+4);
+
+ refcount--;
+
+ if (refcount == 0) {
+ if (data.dsize - i > 8)
+ memmove( &data.dptr[i], &data.dptr[i+8], data.dsize - i - 8);
+ data.dsize -= 8;
+ continue;
+ }
+ SIVAL(data.dptr, i+4, refcount);
+ }
+
+ i += 8;
+ }
+
+ if (data.dsize == 0)
+ SAFE_FREE(data.dptr);
+
+ /* Store back the record. */
+ if (tdb_store_bystring(tdb, NOTIFY_PID_LIST_KEY, data, TDB_REPLACE) != 0) {
+ DEBUG(0,("print_notify_register_pid: Failed to update pid \
+list for printer %s\n", printername));
+ goto done;
+ }
+
+ ret = True;
+
+ done:
+
+ tdb_unlock_bystring(tdb, NOTIFY_PID_LIST_KEY);
+ if (pdb)
+ release_print_db(pdb);
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+/****************************************************************************
+ Check if a jobid is valid. It is valid if it exists in the database.
+****************************************************************************/
+
+bool print_job_exists(const char* sharename, uint32_t jobid)
+{
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ bool ret;
+ uint32_t tmp;
+
+ if (!pdb)
+ return False;
+ ret = tdb_exists(pdb->tdb, print_key(jobid, &tmp));
+ release_print_db(pdb);
+ return ret;
+}
+
+/****************************************************************************
+ Return the device mode assigned to a specific print job.
+ Only valid for the process doing the spooling and when the job
+ has not been spooled.
+****************************************************************************/
+
+struct spoolss_DeviceMode *print_job_devmode(TALLOC_CTX *mem_ctx,
+ const char *sharename,
+ uint32_t jobid)
+{
+ struct printjob *pjob = print_job_find(mem_ctx, sharename, jobid);
+ if (pjob == NULL) {
+ return NULL;
+ }
+
+ return pjob->devmode;
+}
+
+/****************************************************************************
+ Set the name of a job. Only possible for owner.
+****************************************************************************/
+
+bool print_job_set_name(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const char *sharename, uint32_t jobid, const char *name)
+{
+ struct printjob *pjob;
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || pjob->pid != getpid()) {
+ ret = false;
+ goto err_out;
+ }
+
+ fstrcpy(pjob->jobname, name);
+ ret = pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Get the name of a job. Only possible for owner.
+****************************************************************************/
+
+bool print_job_get_name(TALLOC_CTX *mem_ctx, const char *sharename, uint32_t jobid, char **name)
+{
+ struct printjob *pjob;
+
+ pjob = print_job_find(mem_ctx, sharename, jobid);
+ if (!pjob || pjob->pid != getpid()) {
+ return false;
+ }
+
+ *name = pjob->jobname;
+ return true;
+}
+
+
+/***************************************************************************
+ Remove a jobid from the 'jobs added' list.
+***************************************************************************/
+
+static bool remove_from_jobs_added(const char* sharename, uint32_t jobid)
+{
+ bool ret = remove_from_jobs_list("INFO/jobs_added", sharename, jobid);
+ return ret;
+}
+
+/****************************************************************************
+ Delete a print job - don't update queue.
+****************************************************************************/
+
+static bool print_job_delete1(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int result = 0;
+ struct printif *current_printif = get_printer_fns( snum );
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ ret = false;
+ goto err_out;
+ }
+
+ /*
+ * If already deleting just return.
+ */
+
+ if (pjob->status == LPQ_DELETING) {
+ ret = true;
+ goto err_out;
+ }
+
+ /* Hrm - we need to be able to cope with deleting a job before it
+ has reached the spooler. Just mark it as LPQ_DELETING and
+ let the print_queue_update() code remove the record */
+
+
+ if (pjob->sysjob == -1) {
+ DEBUG(5, ("attempt to delete job %u not seen by lpr\n", (unsigned int)jobid));
+ }
+
+ /* Set the tdb entry to be deleting. */
+
+ pjob->status = LPQ_DELETING;
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+
+ if (pjob->spooled && pjob->sysjob != -1)
+ {
+ result = (*(current_printif->job_delete))(
+ lp_printername(talloc_tos(), lp_sub, snum),
+ lp_lprm_command(snum),
+ pjob);
+
+ /* Delete the tdb entry if the delete succeeded or the job hasn't
+ been spooled. */
+
+ if (result == 0) {
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ int njobs = 1;
+
+ if (!pdb) {
+ ret = false;
+ goto err_out;
+ }
+ pjob_delete(ev, msg_ctx, sharename, jobid);
+ /* Ensure we keep a rough count of the number of total jobs... */
+ tdb_change_int32_atomic(pdb->tdb, "INFO/total_jobs", &njobs, -1);
+ release_print_db(pdb);
+ }
+ }
+
+ remove_from_jobs_added( sharename, jobid );
+
+ ret = (result == 0);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Return true if the current user owns the print job.
+****************************************************************************/
+
+static bool is_owner(const struct auth_session_info *server_info,
+ const char *servicename,
+ uint32_t jobid)
+{
+ struct printjob *pjob;
+ bool ret;
+ TALLOC_CTX *tmp_ctx = talloc_new(server_info);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ pjob = print_job_find(tmp_ctx, servicename, jobid);
+ if (!pjob || !server_info) {
+ ret = false;
+ goto err_out;
+ }
+
+ ret = strequal(pjob->user, server_info->unix_info->sanitized_username);
+err_out:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Delete a print job.
+****************************************************************************/
+
+WERROR print_job_delete(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ bool owner;
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ owner = is_owner(server_info, lp_const_servicename(snum), jobid);
+
+ /* Check access against security descriptor or whether the user
+ owns their job. */
+
+ if (!owner &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job delete denied. "
+ "User name: %s, Printer name: %s.\n",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /*
+ * get the spooled filename of the print job
+ * if this works, then the file has not been spooled
+ * to the underlying print system. Just delete the
+ * spool file & return.
+ */
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || pjob->spooled || pjob->pid != getpid()) {
+ DEBUG(10, ("Skipping spool file removal for job %u\n", jobid));
+ } else {
+ DEBUG(10, ("Removing spool file [%s]\n", pjob->filename));
+ if (unlink(pjob->filename) == -1) {
+ werr = map_werror_from_unix(errno);
+ goto err_out;
+ }
+ }
+
+ if (!print_job_delete1(global_event_context(), msg_ctx, snum, jobid)) {
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /* force update the database and say the delete failed if the
+ job still exists */
+
+ print_queue_update(msg_ctx, snum, True);
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (pjob && (pjob->status != LPQ_DELETING)) {
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+ werr = WERR_PRINTER_HAS_JOBS_QUEUED;
+
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Pause a job.
+****************************************************************************/
+
+WERROR print_job_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret = -1;
+ struct printif *current_printif = get_printer_fns( snum );
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || !server_info) {
+ DEBUG(10, ("print_job_pause: no pjob or user for jobid %u\n",
+ (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!pjob->spooled || pjob->sysjob == -1) {
+ DEBUG(10, ("print_job_pause: not spooled or bad sysjob = %d for jobid %u\n",
+ (int)pjob->sysjob, (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!is_owner(server_info, lp_const_servicename(snum), jobid) &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job pause denied. "
+ "User name: %s, Printer name: %s.\n",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ /* need to pause the spooled entry */
+ ret = (*(current_printif->job_pause))(snum, pjob);
+
+ if (ret != 0) {
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_job_status(global_event_context(), msg_ctx, sharename, jobid,
+ JOB_STATUS_PAUSED);
+
+ /* how do we tell if this succeeded? */
+ werr = WERR_OK;
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Resume a job.
+****************************************************************************/
+
+WERROR print_job_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char *sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL)
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob || !server_info) {
+ DEBUG(10, ("print_job_resume: no pjob or user for jobid %u\n",
+ (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!pjob->spooled || pjob->sysjob == -1) {
+ DEBUG(10, ("print_job_resume: not spooled or bad sysjob = %d for jobid %u\n",
+ (int)pjob->sysjob, (unsigned int)jobid ));
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ if (!is_owner(server_info, lp_const_servicename(snum), jobid) &&
+ !W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ JOB_ACCESS_ADMINISTER))) {
+ DEBUG(0, ("print job resume denied. "
+ "User name: %s, Printer name: %s.\n",
+ uidtoname(server_info->unix_token->uid),
+ lp_printername(tmp_ctx, lp_sub, snum)));
+
+ werr = WERR_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ ret = (*(current_printif->job_resume))(snum, pjob);
+
+ if (ret != 0) {
+ werr = WERR_INVALID_PARAMETER;
+ goto err_out;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_job_status(global_event_context(), msg_ctx, sharename, jobid,
+ JOB_STATUS_QUEUED);
+
+ werr = WERR_OK;
+err_out:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/****************************************************************************
+ Write to a print file.
+****************************************************************************/
+
+ssize_t print_job_write(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid, const char *buf, size_t size)
+{
+ const char* sharename = lp_const_servicename(snum);
+ ssize_t return_code;
+ struct printjob *pjob;
+ TALLOC_CTX *tmp_ctx = talloc_new(ev);
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ /* don't allow another process to get this info - it is meaningless */
+ if (pjob->pid != getpid()) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ /* if SMBD is spooling this can't be allowed */
+ if (pjob->status == PJOB_SMBD_SPOOLING) {
+ return_code = -1;
+ goto err_out;
+ }
+
+ return_code = write_data(pjob->fd, buf, size);
+ if (return_code > 0) {
+ pjob->size += size;
+ pjob_store(ev, msg_ctx, sharename, jobid, pjob);
+ }
+err_out:
+ talloc_free(tmp_ctx);
+ return return_code;
+}
+
+/****************************************************************************
+ Get the queue status - do not update if db is out of date.
+****************************************************************************/
+
+static int get_queue_status(const char* sharename, print_status_struct *status)
+{
+ fstring keystr;
+ TDB_DATA data;
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ int len;
+
+ if (status) {
+ ZERO_STRUCTP(status);
+ }
+
+ if (!pdb)
+ return 0;
+
+ if (status) {
+ fstr_sprintf(keystr, "STATUS/%s", sharename);
+ data = tdb_fetch(pdb->tdb, string_tdb_data(keystr));
+ if (data.dptr) {
+ if (data.dsize == sizeof(print_status_struct))
+ /* this memcpy is ok since the status struct was
+ not packed before storing it in the tdb */
+ memcpy(status, data.dptr, sizeof(print_status_struct));
+ SAFE_FREE(data.dptr);
+ }
+ }
+ len = tdb_fetch_int32(pdb->tdb, "INFO/total_jobs");
+ release_print_db(pdb);
+ return (len == -1 ? 0 : len);
+}
+
+/****************************************************************************
+ Determine the number of jobs in a queue.
+****************************************************************************/
+
+int print_queue_length(struct messaging_context *msg_ctx, int snum,
+ print_status_struct *pstatus)
+{
+ const char* sharename = lp_const_servicename( snum );
+ print_status_struct status;
+ int len;
+
+ ZERO_STRUCT( status );
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ /* also fetch the queue status */
+ memset(&status, 0, sizeof(status));
+ len = get_queue_status(sharename, &status);
+
+ if (pstatus)
+ *pstatus = status;
+
+ return len;
+}
+
+/***************************************************************************
+ Allocate a jobid. Hold the lock for as short a time as possible.
+***************************************************************************/
+
+static WERROR allocate_print_jobid(struct tdb_print_db *pdb, int snum,
+ const char *sharename, uint32_t *pjobid)
+{
+ int i;
+ uint32_t jobid;
+ enum TDB_ERROR terr;
+ int ret;
+
+ *pjobid = (uint32_t)-1;
+
+ for (i = 0; i < 3; i++) {
+ /* Lock the database - only wait 20 seconds. */
+ ret = tdb_lock_bystring_with_timeout(pdb->tdb,
+ "INFO/nextjob", 20);
+ if (ret != 0) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to lock printing database %s\n",
+ sharename));
+ terr = tdb_error(pdb->tdb);
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+
+ if (!tdb_fetch_uint32(pdb->tdb, "INFO/nextjob", &jobid)) {
+ terr = tdb_error(pdb->tdb);
+ if (terr != TDB_ERR_NOEXIST) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to fetch INFO/nextjob "
+ "for print queue %s\n", sharename));
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+ DEBUG(10, ("allocate_print_jobid: "
+ "No existing jobid in %s\n", sharename));
+ jobid = 0;
+ }
+
+ DEBUG(10, ("allocate_print_jobid: "
+ "Read jobid %u from %s\n", jobid, sharename));
+
+ jobid = NEXT_JOBID(jobid);
+
+ ret = tdb_store_int32(pdb->tdb, "INFO/nextjob", jobid);
+ if (ret != 0) {
+ terr = tdb_error(pdb->tdb);
+ DEBUG(3, ("allocate_print_jobid: "
+ "Failed to store INFO/nextjob.\n"));
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+
+ /* We've finished with the INFO/nextjob lock. */
+ tdb_unlock_bystring(pdb->tdb, "INFO/nextjob");
+
+ if (!print_job_exists(sharename, jobid)) {
+ break;
+ }
+ DEBUG(10, ("allocate_print_jobid: "
+ "Found jobid %u in %s\n", jobid, sharename));
+ }
+
+ if (i > 2) {
+ DEBUG(0, ("allocate_print_jobid: "
+ "Failed to allocate a print job for queue %s\n",
+ sharename));
+ /* Probably full... */
+ return WERR_NO_SPOOL_SPACE;
+ }
+
+ /* Store a dummy placeholder. */
+ {
+ uint32_t tmp;
+ TDB_DATA dummy = {
+ .dsize = 0,
+ };
+ if (tdb_store(pdb->tdb, print_key(jobid, &tmp), dummy,
+ TDB_INSERT) != 0) {
+ DEBUG(3, ("allocate_print_jobid: "
+ "jobid (%d) failed to store placeholder.\n",
+ jobid ));
+ terr = tdb_error(pdb->tdb);
+ return ntstatus_to_werror(map_nt_error_from_tdb(terr));
+ }
+ }
+
+ *pjobid = jobid;
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Do all checks needed to determine if we can start a job.
+***************************************************************************/
+
+static WERROR print_job_checks(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ int snum, int *njobs)
+{
+ const char *sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint64_t dspace, dsize;
+ uint64_t minspace;
+ int ret;
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_USE))) {
+ DEBUG(3, ("print_job_checks: "
+ "job start denied by security descriptor\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!print_time_access_check(server_info, msg_ctx, sharename)) {
+ DEBUG(3, ("print_job_checks: "
+ "job start denied by time check\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* see if we have sufficient disk space */
+ if (lp_min_print_space(snum)) {
+ minspace = lp_min_print_space(snum);
+ ret = sys_fsusage(lp_path(talloc_tos(), lp_sub, snum), &dspace, &dsize);
+ if (ret == 0 && dspace < 2*minspace) {
+ DEBUG(3, ("print_job_checks: "
+ "disk space check failed.\n"));
+ return WERR_NO_SPOOL_SPACE;
+ }
+ }
+
+ /* for autoloaded printers, check that the printcap entry still exists */
+ if (lp_autoloaded(snum) &&
+ !printer_list_printername_exists(sharename)) {
+ DEBUG(3, ("print_job_checks: printer name %s check failed.\n",
+ sharename));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Insure the maximum queue size is not violated */
+ *njobs = print_queue_length(msg_ctx, snum, NULL);
+ if (*njobs > lp_maxprintjobs(snum)) {
+ DEBUG(3, ("print_job_checks: Queue %s number of jobs (%d) "
+ "larger than max printjobs per queue (%d).\n",
+ sharename, *njobs, lp_maxprintjobs(snum)));
+ return WERR_NO_SPOOL_SPACE;
+ }
+
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Create a job file.
+***************************************************************************/
+
+static WERROR print_job_spool_file(int snum, uint32_t jobid,
+ const char *output_file,
+ struct printjob *pjob)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ WERROR werr;
+ SMB_STRUCT_STAT st;
+ const char *path;
+ int len;
+ mode_t mask;
+
+ /* if this file is within the printer path, it means that smbd
+ * is spooling it and will pass us control when it is finished.
+ * Verify that the file name is ok, within path, and it is
+ * already already there */
+ if (output_file) {
+ path = lp_path(talloc_tos(), lp_sub, snum);
+ len = strlen(path);
+ if (strncmp(output_file, path, len) == 0 &&
+ (output_file[len - 1] == '/' || output_file[len] == '/')) {
+
+ /* verify path is not too long */
+ if (strlen(output_file) >= sizeof(pjob->filename)) {
+ return WERR_INVALID_NAME;
+ }
+
+ /* verify that the file exists */
+ if (sys_stat(output_file, &st, false) != 0) {
+ return WERR_INVALID_NAME;
+ }
+
+ fstrcpy(pjob->filename, output_file);
+
+ DEBUG(3, ("print_job_spool_file:"
+ "External spooling activated\n"));
+
+ /* we do not open the file until spooling is done */
+ pjob->fd = -1;
+ pjob->status = PJOB_SMBD_SPOOLING;
+
+ return WERR_OK;
+ }
+ }
+
+ slprintf(pjob->filename, sizeof(pjob->filename)-1,
+ "%s/%sXXXXXX", lp_path(talloc_tos(), lp_sub, snum),
+ PRINT_SPOOL_PREFIX);
+ mask = umask(S_IRWXO | S_IRWXG);
+ pjob->fd = mkstemp(pjob->filename);
+ umask(mask);
+
+ if (pjob->fd == -1) {
+ werr = map_werror_from_unix(errno);
+ if (W_ERROR_EQUAL(werr, WERR_ACCESS_DENIED)) {
+ /* Common setup error, force a report. */
+ DEBUG(0, ("print_job_spool_file: "
+ "insufficient permissions to open spool "
+ "file %s.\n", pjob->filename));
+ } else {
+ /* Normal case, report at level 3 and above. */
+ DEBUG(3, ("print_job_spool_file: "
+ "can't open spool file %s\n",
+ pjob->filename));
+ }
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
+/***************************************************************************
+ Start spooling a job - return the jobid.
+***************************************************************************/
+
+WERROR print_job_start(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ const char *clientmachine,
+ int snum, const char *docname, const char *filename,
+ struct spoolss_DeviceMode *devmode, uint32_t *_jobid)
+{
+ uint32_t jobid;
+ char *path = NULL, *userstr = NULL;
+ struct printjob pjob;
+ const char *sharename = lp_const_servicename(snum);
+ struct tdb_print_db *pdb = get_print_db_byname(sharename);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int njobs;
+ WERROR werr;
+
+ if (!pdb) {
+ return WERR_INTERNAL_DB_CORRUPTION;
+ }
+
+ path = lp_path(talloc_tos(), lp_sub, snum);
+
+ werr = print_job_checks(server_info, msg_ctx, snum, &njobs);
+ if (!W_ERROR_IS_OK(werr)) {
+ release_print_db(pdb);
+ return werr;
+ }
+
+ DEBUG(10, ("print_job_start: "
+ "Queue %s number of jobs (%d), max printjobs = %d\n",
+ sharename, njobs, lp_maxprintjobs(snum)));
+
+ werr = allocate_print_jobid(pdb, snum, sharename, &jobid);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* create the database entry */
+
+ ZERO_STRUCT(pjob);
+
+ pjob.pid = getpid();
+ pjob.jobid = jobid;
+ pjob.sysjob = -1;
+ pjob.fd = -1;
+ pjob.starttime = time(NULL);
+ pjob.status = LPQ_SPOOLING;
+ pjob.size = 0;
+ pjob.spooled = False;
+ pjob.smbjob = True;
+ pjob.devmode = devmode;
+
+ fstrcpy(pjob.jobname, docname);
+
+ fstrcpy(pjob.clientmachine, clientmachine);
+
+ userstr = talloc_sub_full(talloc_tos(),
+ sharename,
+ server_info->unix_info->sanitized_username,
+ path, server_info->unix_token->gid,
+ server_info->unix_info->sanitized_username,
+ server_info->info->domain_name,
+ lp_printjob_username(snum));
+ if (userstr == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+ strlcpy(pjob.user, userstr, sizeof(pjob.user));
+ TALLOC_FREE(userstr);
+
+ fstrcpy(pjob.queuename, lp_const_servicename(snum));
+
+ /* we have a job entry - now create the spool file */
+ werr = print_job_spool_file(snum, jobid, filename, &pjob);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, &pjob);
+
+ /* Update the 'jobs added' entry used by print_queue_status. */
+ add_to_jobs_list(pdb, jobid, "INFO/jobs_added");
+
+ /* Ensure we keep a rough count of the number of total jobs... */
+ tdb_change_int32_atomic(pdb->tdb, "INFO/total_jobs", &njobs, 1);
+
+ release_print_db(pdb);
+
+ *_jobid = jobid;
+ return WERR_OK;
+
+fail:
+ if (jobid != -1) {
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+ }
+
+ release_print_db(pdb);
+
+ DEBUG(3, ("print_job_start: returning fail. "
+ "Error = %s\n", win_errstr(werr)));
+ return werr;
+}
+
+/****************************************************************************
+ Update the number of pages spooled to jobid
+****************************************************************************/
+
+void print_job_endpage(struct messaging_context *msg_ctx,
+ int snum, uint32_t jobid)
+{
+ const char* sharename = lp_const_servicename(snum);
+ struct printjob *pjob;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ goto err_out;
+ }
+ /* don't allow another process to get this info - it is meaningless */
+ if (pjob->pid != getpid()) {
+ goto err_out;
+ }
+
+ pjob->page_count++;
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, pjob);
+err_out:
+ talloc_free(tmp_ctx);
+}
+
+/****************************************************************************
+ Print a file - called on closing the file. This spools the job.
+ If normal close is false then we're tearing down the jobs - treat as an
+ error.
+****************************************************************************/
+
+NTSTATUS print_job_end(struct messaging_context *msg_ctx, int snum,
+ uint32_t jobid, enum file_close_type close_type)
+{
+ const char* sharename = lp_const_servicename(snum);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct printjob *pjob;
+ int ret;
+ SMB_STRUCT_STAT sbuf;
+ struct printif *current_printif = get_printer_fns(snum);
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ char *lpq_cmd;
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (!pjob) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto err_out;
+ }
+
+ if (pjob->spooled || pjob->pid != getpid()) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_out;
+ }
+
+ if (close_type == NORMAL_CLOSE || close_type == SHUTDOWN_CLOSE) {
+ if (pjob->status == PJOB_SMBD_SPOOLING) {
+ /* take over the file now, smbd is done */
+ if (sys_stat(pjob->filename, &sbuf, false) != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3, ("print_job_end: "
+ "stat file failed for jobid %d\n",
+ jobid));
+ goto fail;
+ }
+
+ pjob->status = LPQ_SPOOLING;
+
+ } else {
+
+ if ((sys_fstat(pjob->fd, &sbuf, false) != 0)) {
+ status = map_nt_error_from_unix(errno);
+ close(pjob->fd);
+ DEBUG(3, ("print_job_end: "
+ "stat file failed for jobid %d\n",
+ jobid));
+ goto fail;
+ }
+
+ close(pjob->fd);
+ }
+
+ pjob->size = sbuf.st_ex_size;
+ } else {
+
+ /*
+ * Not a normal close, something has gone wrong. Cleanup.
+ */
+ if (pjob->fd != -1) {
+ close(pjob->fd);
+ }
+ goto fail;
+ }
+
+ /* Technically, this is not quite right. If the printer has a separator
+ * page turned on, the NT spooler prints the separator page even if the
+ * print job is 0 bytes. 010215 JRR */
+ if (pjob->size == 0 || pjob->status == LPQ_DELETING) {
+ /* don't bother spooling empty files or something being deleted. */
+ DEBUG(5,("print_job_end: canceling spool of %s (%s)\n",
+ pjob->filename, pjob->size ? "deleted" : "zero length" ));
+ unlink(pjob->filename);
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+ return NT_STATUS_OK;
+ }
+
+ /* don't strip out characters like '$' from the printername */
+ lpq_cmd = talloc_string_sub2(tmp_ctx,
+ lp_lpq_command(snum),
+ "%p",
+ lp_printername(talloc_tos(), lp_sub, snum),
+ false, false, false);
+ if (lpq_cmd == NULL) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+ lpq_cmd = talloc_sub_full(tmp_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ current_user_info.unix_name,
+ "",
+ get_current_gid(NULL),
+ get_current_username(),
+ get_current_user_info_domain(),
+ lpq_cmd);
+ if (lpq_cmd == NULL) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+
+ ret = (*(current_printif->job_submit))(snum, pjob,
+ current_printif->type, lpq_cmd);
+ if (ret) {
+ status = NT_STATUS_PRINT_CANCELLED;
+ goto fail;
+ }
+
+ /* The print job has been successfully handed over to the back-end */
+
+ pjob->spooled = True;
+ pjob->status = LPQ_QUEUED;
+ pjob_store(global_event_context(), msg_ctx, sharename, jobid, pjob);
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ return NT_STATUS_OK;
+
+fail:
+
+ /* The print job was not successfully started. Cleanup */
+ /* Still need to add proper error return propagation! 010122:JRR */
+ pjob->fd = -1;
+ unlink(pjob->filename);
+ pjob_delete(global_event_context(), msg_ctx, sharename, jobid);
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************************
+ Get a snapshot of jobs in the system without traversing.
+****************************************************************************/
+
+static bool get_stored_queue_info(struct messaging_context *msg_ctx,
+ struct tdb_print_db *pdb, int snum,
+ int *pcount, print_queue_struct **ppqueue)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ TDB_DATA data, cgdata, jcdata;
+ print_queue_struct *queue = NULL;
+ uint32_t qcount = 0;
+ uint32_t extra_count = 0;
+ uint32_t changed_count = 0;
+ int total_count = 0;
+ size_t len = 0;
+ uint32_t i;
+ int max_reported_jobs = lp_max_reported_print_jobs(snum);
+ bool ret = false;
+ const char* sharename = lp_servicename(talloc_tos(), lp_sub, snum);
+ TALLOC_CTX *tmp_ctx = talloc_new(msg_ctx);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ *pcount = 0;
+ *ppqueue = NULL;
+
+ ZERO_STRUCT(data);
+ ZERO_STRUCT(cgdata);
+
+ /* Get the stored queue data. */
+ data = tdb_fetch(pdb->tdb, string_tdb_data("INFO/linear_queue_array"));
+
+ if (data.dptr && data.dsize >= sizeof(qcount))
+ len += tdb_unpack(data.dptr + len, data.dsize - len, "d", &qcount);
+
+ /* Get the added jobs list. */
+ cgdata = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_added"));
+ if (cgdata.dptr != NULL && (cgdata.dsize % 4 == 0))
+ extra_count = cgdata.dsize/4;
+
+ /* Get the changed jobs list. */
+ jcdata = tdb_fetch(pdb->tdb, string_tdb_data("INFO/jobs_changed"));
+ if (jcdata.dptr != NULL && (jcdata.dsize % 4 == 0))
+ changed_count = jcdata.dsize / 4;
+
+ DEBUG(5,("get_stored_queue_info: qcount = %u, extra_count = %u\n", (unsigned int)qcount, (unsigned int)extra_count));
+
+ /* Allocate the queue size. */
+ if (qcount == 0 && extra_count == 0)
+ goto out;
+
+ if ((queue = SMB_MALLOC_ARRAY(print_queue_struct, qcount + extra_count)) == NULL)
+ goto out;
+
+ /* Retrieve the linearised queue data. */
+
+ for(i = 0; i < qcount; i++) {
+ uint32_t qjob, qsize, qpage_count, qstatus, qpriority, qtime;
+ len += tdb_unpack(data.dptr + len, data.dsize - len, "ddddddff",
+ &qjob,
+ &qsize,
+ &qpage_count,
+ &qstatus,
+ &qpriority,
+ &qtime,
+ queue[i].fs_user,
+ queue[i].fs_file);
+ queue[i].sysjob = qjob;
+ queue[i].size = qsize;
+ queue[i].page_count = qpage_count;
+ queue[i].status = qstatus;
+ queue[i].priority = qpriority;
+ queue[i].time = qtime;
+ }
+
+ total_count = qcount;
+
+ /* Add new jobids to the queue. */
+ for (i = 0; i < extra_count; i++) {
+ uint32_t jobid;
+ struct printjob *pjob;
+
+ jobid = IVAL(cgdata.dptr, i*4);
+ DEBUG(5,("get_stored_queue_info: added job = %u\n", (unsigned int)jobid));
+ pjob = print_job_find(tmp_ctx, lp_const_servicename(snum), jobid);
+ if (!pjob) {
+ DEBUG(5,("get_stored_queue_info: failed to find added job = %u\n", (unsigned int)jobid));
+ remove_from_jobs_added(sharename, jobid);
+ continue;
+ }
+
+ queue[total_count].sysjob = pjob->sysjob;
+ queue[total_count].size = pjob->size;
+ queue[total_count].page_count = pjob->page_count;
+ queue[total_count].status = pjob->status;
+ queue[total_count].priority = 1;
+ queue[total_count].time = pjob->starttime;
+ fstrcpy(queue[total_count].fs_user, pjob->user);
+ fstrcpy(queue[total_count].fs_file, pjob->jobname);
+ total_count++;
+ talloc_free(pjob);
+ }
+
+ /* Update the changed jobids. */
+ for (i = 0; i < changed_count; i++) {
+ uint32_t jobid = IVAL(jcdata.dptr, i * 4);
+ struct printjob *pjob;
+ uint32_t j;
+ bool found = false;
+
+ pjob = print_job_find(tmp_ctx, sharename, jobid);
+ if (pjob == NULL) {
+ DEBUG(5,("get_stored_queue_info: failed to find "
+ "changed job = %u\n",
+ (unsigned int)jobid));
+ remove_from_jobs_changed(sharename, jobid);
+ continue;
+ }
+
+ for (j = 0; j < total_count; j++) {
+ if (queue[j].sysjob == pjob->sysjob) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ DEBUG(5,("get_stored_queue_info: changed job: %u\n",
+ (unsigned int)jobid));
+
+ queue[j].sysjob = pjob->sysjob;
+ queue[j].size = pjob->size;
+ queue[j].page_count = pjob->page_count;
+ queue[j].status = pjob->status;
+ queue[j].priority = 1;
+ queue[j].time = pjob->starttime;
+ fstrcpy(queue[j].fs_user, pjob->user);
+ fstrcpy(queue[j].fs_file, pjob->jobname);
+ talloc_free(pjob);
+
+ DEBUG(5,("updated queue[%u], jobid: %u, sysjob: %u, "
+ "jobname: %s\n",
+ (unsigned int)j, (unsigned int)jobid,
+ (unsigned int)queue[j].sysjob, pjob->jobname));
+ }
+
+ remove_from_jobs_changed(sharename, jobid);
+ }
+
+ /* Sort the queue by submission time otherwise they are displayed
+ in hash order. */
+
+ TYPESAFE_QSORT(queue, total_count, printjob_comp);
+
+ DEBUG(5,("get_stored_queue_info: total_count = %u\n", (unsigned int)total_count));
+
+ if (max_reported_jobs && total_count > max_reported_jobs)
+ total_count = max_reported_jobs;
+
+ *ppqueue = queue;
+ *pcount = total_count;
+
+ ret = true;
+
+ out:
+
+ SAFE_FREE(data.dptr);
+ SAFE_FREE(cgdata.dptr);
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/****************************************************************************
+ Get a printer queue listing.
+ set queue = NULL and status = NULL if you just want to update the cache
+****************************************************************************/
+
+int print_queue_status(struct messaging_context *msg_ctx, int snum,
+ print_queue_struct **ppqueue,
+ print_status_struct *status)
+{
+ fstring keystr;
+ TDB_DATA data, key;
+ const char *sharename;
+ struct tdb_print_db *pdb;
+ int count = 0;
+
+ /* make sure the database is up to date */
+
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, False);
+
+ /* return if we are done */
+ if ( !ppqueue || !status )
+ return 0;
+
+ *ppqueue = NULL;
+ sharename = lp_const_servicename(snum);
+ pdb = get_print_db_byname(sharename);
+
+ if (!pdb)
+ return 0;
+
+ /*
+ * Fetch the queue status. We must do this first, as there may
+ * be no jobs in the queue.
+ */
+
+ ZERO_STRUCTP(status);
+ slprintf(keystr, sizeof(keystr)-1, "STATUS/%s", sharename);
+ key = string_tdb_data(keystr);
+
+ data = tdb_fetch(pdb->tdb, key);
+ if (data.dptr) {
+ if (data.dsize == sizeof(*status)) {
+ /* this memcpy is ok since the status struct was
+ not packed before storing it in the tdb */
+ memcpy(status, data.dptr, sizeof(*status));
+ }
+ SAFE_FREE(data.dptr);
+ }
+
+ /*
+ * Now, fetch the print queue information. We first count the number
+ * of entries, and then only retrieve the queue if necessary.
+ */
+
+ if (!get_stored_queue_info(msg_ctx, pdb, snum, &count, ppqueue)) {
+ release_print_db(pdb);
+ return 0;
+ }
+
+ release_print_db(pdb);
+ return count;
+}
+
+/****************************************************************************
+ Pause a queue.
+****************************************************************************/
+
+WERROR print_queue_pause(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_ADMINISTER))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+
+ become_root();
+
+ ret = (*(current_printif->queue_pause))(snum);
+
+ unbecome_root();
+
+ if (ret != 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* force update the database */
+ print_cache_flush(lp_const_servicename(snum));
+
+ /* Send a printer notify message */
+
+ notify_printer_status(global_event_context(), msg_ctx, snum,
+ PRINTER_STATUS_PAUSED);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Resume a queue.
+****************************************************************************/
+
+WERROR print_queue_resume(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ int ret;
+ struct printif *current_printif = get_printer_fns( snum );
+
+ if (!W_ERROR_IS_OK(print_access_check(server_info, msg_ctx, snum,
+ PRINTER_ACCESS_ADMINISTER))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ become_root();
+
+ ret = (*(current_printif->queue_resume))(snum);
+
+ unbecome_root();
+
+ if (ret != 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* make sure the database is up to date */
+ if (print_cache_expired(lp_const_servicename(snum), True))
+ print_queue_update(msg_ctx, snum, True);
+
+ /* Send a printer notify message */
+
+ notify_printer_status(global_event_context(), msg_ctx, snum,
+ PRINTER_STATUS_OK);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Purge a queue - implemented by deleting all jobs that we can delete.
+****************************************************************************/
+
+WERROR print_queue_purge(const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx, int snum)
+{
+ print_queue_struct *queue;
+ print_status_struct status;
+ int njobs, i;
+ bool can_job_admin;
+
+ /* Force and update so the count is accurate (i.e. not a cached count) */
+ print_queue_update(msg_ctx, snum, True);
+
+ can_job_admin = W_ERROR_IS_OK(print_access_check(server_info,
+ msg_ctx,
+ snum,
+ JOB_ACCESS_ADMINISTER));
+ njobs = print_queue_status(msg_ctx, snum, &queue, &status);
+
+ if ( can_job_admin )
+ become_root();
+
+ for (i = 0; i < njobs; i++) {
+ struct tdb_print_db *pdb;
+ int jobid;
+ bool owner;
+ pdb = get_print_db_byname(lp_const_servicename(snum));
+ if (pdb == NULL) {
+ DEBUG(1, ("failed to find printdb for %s\n",
+ lp_const_servicename(snum)));
+ continue;
+ }
+ jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ DEBUG(2, ("jobid for system job %d not found\n",
+ queue[i].sysjob));
+ continue; /* unix job */
+ }
+ owner = is_owner(server_info, lp_const_servicename(snum),
+ jobid);
+
+ if (owner || can_job_admin) {
+ print_job_delete1(global_event_context(), msg_ctx,
+ snum, jobid);
+ }
+ }
+
+ if ( can_job_admin )
+ unbecome_root();
+
+ /* update the cache */
+ print_queue_update(msg_ctx, snum, True);
+
+ SAFE_FREE(queue);
+
+ return WERR_OK;
+}
diff --git a/source3/printing/printing_db.c b/source3/printing/printing_db.c
new file mode 100644
index 0000000..d54a39a
--- /dev/null
+++ b/source3/printing/printing_db.c
@@ -0,0 +1,228 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "system/filesys.h"
+#include "printing.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+static struct tdb_print_db *print_db_head;
+
+/****************************************************************************
+ Function to find or create the printer specific job tdb given a printername.
+ Limits the number of tdb's open to MAX_PRINT_DBS_OPEN.
+****************************************************************************/
+
+struct tdb_print_db *get_print_db_byname(const char *printername)
+{
+ struct tdb_print_db *p = NULL, *last_entry = NULL;
+ size_t num_open = 0;
+ char *printdb_path = NULL;
+ bool done_become_root = False;
+ char *print_cache_path;
+ int ret;
+
+ SMB_ASSERT(printername != NULL);
+
+ for (p = print_db_head, last_entry = print_db_head; p; p = p->next) {
+ /* Ensure the list terminates... JRA. */
+ SMB_ASSERT(p->next != print_db_head);
+
+ if (p->tdb && strequal(p->printer_name, printername)) {
+ DLIST_PROMOTE(print_db_head, p);
+ p->ref_count++;
+ return p;
+ }
+ num_open++;
+ last_entry = p;
+ }
+
+ /* Not found. */
+ if (num_open >= MAX_PRINT_DBS_OPEN) {
+ /* Try and recycle the last entry. */
+ if (print_db_head && last_entry) {
+ DLIST_PROMOTE(print_db_head, last_entry);
+ }
+
+ for (p = print_db_head; p; p = p->next) {
+ if (p->ref_count)
+ continue;
+ if (p->tdb) {
+ if (tdb_close(p->tdb)) {
+ DEBUG(0,("get_print_db: Failed to close tdb for printer %s\n",
+ p->printer_name ));
+ return NULL;
+ }
+ }
+ p->tdb = NULL;
+ p->ref_count = 0;
+ memset(p->printer_name, '\0', sizeof(p->printer_name));
+ break;
+ }
+ if (p && print_db_head) {
+ DLIST_PROMOTE(print_db_head, p);
+ p = print_db_head;
+ }
+ }
+
+ if (!p) {
+ /* Create one. */
+ p = SMB_MALLOC_P(struct tdb_print_db);
+ if (!p) {
+ DEBUG(0,("get_print_db: malloc fail !\n"));
+ return NULL;
+ }
+ ZERO_STRUCTP(p);
+ DLIST_ADD(print_db_head, p);
+ }
+
+ print_cache_path = cache_path(talloc_tos(), "printing/");
+ if (print_cache_path == NULL) {
+ DLIST_REMOVE(print_db_head, p);
+ SAFE_FREE(p);
+ return NULL;
+ }
+ ret = asprintf(&printdb_path, "%s%s.tdb",
+ print_cache_path, printername);
+ TALLOC_FREE(print_cache_path);
+ if (ret < 0) {
+ DLIST_REMOVE(print_db_head, p);
+ SAFE_FREE(p);
+ return NULL;
+ }
+
+ if (geteuid() != sec_initial_uid()) {
+ become_root();
+ done_become_root = True;
+ }
+
+ p->tdb = tdb_open_log(printdb_path, 5000, TDB_DEFAULT, O_RDWR|O_CREAT,
+ 0600);
+
+ if (done_become_root)
+ unbecome_root();
+
+ if (!p->tdb) {
+ DEBUG(0,("get_print_db: Failed to open printer backend database %s.\n",
+ printdb_path ));
+ DLIST_REMOVE(print_db_head, p);
+ SAFE_FREE(printdb_path);
+ SAFE_FREE(p);
+ return NULL;
+ }
+ SAFE_FREE(printdb_path);
+ fstrcpy(p->printer_name, printername);
+ p->ref_count++;
+ return p;
+}
+
+/***************************************************************************
+ Remove a reference count.
+****************************************************************************/
+
+void release_print_db( struct tdb_print_db *pdb)
+{
+ pdb->ref_count--;
+ SMB_ASSERT(pdb->ref_count >= 0);
+}
+
+/***************************************************************************
+ Close all open print db entries.
+****************************************************************************/
+
+void close_all_print_db(void)
+{
+ struct tdb_print_db *p = NULL, *next_p = NULL;
+
+ for (p = print_db_head; p; p = next_p) {
+ next_p = p->next;
+
+ if (p->tdb)
+ tdb_close(p->tdb);
+ DLIST_REMOVE(print_db_head, p);
+ ZERO_STRUCTP(p);
+ SAFE_FREE(p);
+ }
+}
+
+
+/****************************************************************************
+ Fetch and clean the pid_t record list for all pids interested in notify
+ messages. data needs freeing on exit.
+****************************************************************************/
+
+TDB_DATA get_printer_notify_pid_list(struct tdb_context *tdb, const char *printer_name, bool cleanlist)
+{
+ TDB_DATA data;
+ size_t i;
+
+ ZERO_STRUCT(data);
+
+ data = tdb_fetch_bystring( tdb, NOTIFY_PID_LIST_KEY );
+
+ if (!data.dptr) {
+ ZERO_STRUCT(data);
+ return data;
+ }
+
+ if (data.dsize % 8) {
+ DEBUG(0,("get_printer_notify_pid_list: Size of record for printer %s not a multiple of 8 !\n", printer_name ));
+ tdb_delete_bystring(tdb, NOTIFY_PID_LIST_KEY );
+ SAFE_FREE(data.dptr);
+ ZERO_STRUCT(data);
+ return data;
+ }
+
+ if (!cleanlist)
+ return data;
+
+ /*
+ * Weed out all dead entries.
+ */
+
+ for( i = 0; i < data.dsize; i += 8) {
+ pid_t pid = (pid_t)IVAL(data.dptr, i);
+
+ if (pid == getpid())
+ continue;
+
+ /* Entry is dead if process doesn't exist or refcount is zero. */
+
+ while ((i < data.dsize) && ((IVAL(data.dptr, i + 4) == 0) || !process_exists_by_pid(pid))) {
+
+ /* Refcount == zero is a logic error and should never happen. */
+ if (IVAL(data.dptr, i + 4) == 0) {
+ DEBUG(0,("get_printer_notify_pid_list: Refcount == 0 for pid = %u printer %s !\n",
+ (unsigned int)pid, printer_name ));
+ }
+
+ if (data.dsize - i > 8)
+ memmove( &data.dptr[i], &data.dptr[i+8], data.dsize - i - 8);
+ data.dsize -= 8;
+ }
+ }
+
+ return data;
+}
+
+
diff --git a/source3/printing/printspoolss.c b/source3/printing/printspoolss.c
new file mode 100644
index 0000000..94404f7
--- /dev/null
+++ b/source3/printing/printspoolss.c
@@ -0,0 +1,406 @@
+/*
+ Unix SMB/CIFS implementation.
+ Printing routines that bridge to spoolss
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "smbd/globals.h"
+#include "../libcli/security/security.h"
+#include "smbd/fd_handle.h"
+#include "source3/printing/rap_jobid.h"
+
+struct print_file_data {
+ char *svcname;
+ char *docname;
+ char *filename;
+ struct policy_handle handle;
+ uint32_t jobid;
+ uint16_t rap_jobid;
+};
+
+uint16_t print_spool_rap_jobid(struct print_file_data *print_file)
+{
+ if (print_file == NULL) {
+ return 0;
+ }
+
+ return print_file->rap_jobid;
+}
+
+void print_spool_terminate(struct connection_struct *conn,
+ struct print_file_data *print_file);
+
+/***************************************************************************
+ * Open a Document over spoolss
+ ***************************************************************************/
+
+#define DOCNAME_DEFAULT "Remote Downlevel Document"
+
+NTSTATUS print_spool_open(files_struct *fsp,
+ const char *fname,
+ uint64_t current_vuid)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+ struct print_file_data *pf;
+ struct dcerpc_binding_handle *b = NULL;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct spoolss_DocumentInfoCtr info_ctr;
+ struct spoolss_DocumentInfo1 *info1;
+ int fd = -1;
+ WERROR werr;
+ mode_t mask;
+
+ tmp_ctx = talloc_new(fsp);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pf = talloc_zero(fsp, struct print_file_data);
+ if (!pf) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ pf->svcname = lp_servicename(pf, lp_sub, SNUM(fsp->conn));
+
+ /* the document name is derived from the file name.
+ * "Remote Downlevel Document" is added in front to
+ * mimic what windows does in this case */
+ pf->docname = talloc_strdup(pf, DOCNAME_DEFAULT);
+ if (!pf->docname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ if (fname) {
+ const char *p = strrchr(fname, '/');
+ if (!p) {
+ p = fname;
+ }
+ pf->docname = talloc_asprintf_append(pf->docname, " %s", p);
+ if (!pf->docname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ /*
+ * Ok, now we have to open an actual file.
+ * Here is the reason:
+ * We want to write the spool job to this file in
+ * smbd for scalability reason (and also because
+ * apparently window printer drivers can seek when
+ * spooling to a file).
+ * So we first create a file, and then we pass it
+ * to spoolss in output_file so it can monitor and
+ * take over once we call EndDocPrinter().
+ * Of course we will not start writing until
+ * StartDocPrinter() actually gives the ok.
+ * smbd spooler files do not include a print jobid
+ * path component, as the jobid is only known after
+ * calling StartDocPrinter().
+ */
+
+ pf->filename = talloc_asprintf(pf, "%s/%sXXXXXX",
+ lp_path(talloc_tos(),
+ lp_sub,
+ SNUM(fsp->conn)),
+ PRINT_SPOOL_PREFIX);
+ if (!pf->filename) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ errno = 0;
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(pf->filename);
+ umask(mask);
+ if (fd == -1) {
+ if (errno == EACCES) {
+ /* Common setup error, force a report. */
+ DEBUG(0, ("Insufficient permissions "
+ "to open spool file %s.\n",
+ pf->filename));
+ } else {
+ /* Normal case, report at level 3 and above. */
+ DEBUG(3, ("can't open spool file %s,\n",
+ pf->filename));
+ DEBUGADD(3, ("errno = %d (%s).\n",
+ errno, strerror(errno)));
+ }
+ status = map_nt_error_from_unix(errno);
+ goto done;
+ }
+
+ /* now open a document over spoolss so that it does
+ * all printer verification, and eventually assigns
+ * a job id */
+
+ status = rpc_pipe_open_interface(fsp->conn,
+ &ndr_table_spoolss,
+ fsp->conn->session_info,
+ fsp->conn->sconn->remote_address,
+ fsp->conn->sconn->local_address,
+ fsp->conn->sconn->msg_ctx,
+ &fsp->conn->spoolss_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ b = fsp->conn->spoolss_pipe->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, pf, pf->svcname,
+ "RAW", devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &pf->handle, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ info1 = talloc(tmp_ctx, struct spoolss_DocumentInfo1);
+ if (info1 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ info1->document_name = pf->docname;
+ info1->output_file = pf->filename;
+ info1->datatype = "RAW";
+
+ info_ctr.level = 1;
+ info_ctr.info.info1 = info1;
+
+ status = dcerpc_spoolss_StartDocPrinter(b, tmp_ctx,
+ &pf->handle,
+ &info_ctr,
+ &pf->jobid,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ /* Convert to RAP id. */
+ pf->rap_jobid = pjobid_to_rap(pf->svcname, pf->jobid);
+ if (pf->rap_jobid == 0) {
+ /* No errno around here */
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ /* setup a full fsp */
+ fsp->fsp_name = synthetic_smb_fname(fsp,
+ pf->filename,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (fsp->fsp_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (sys_fstat(fd, &fsp->fsp_name->st, false) != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto done;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(fsp->conn, &fsp->fsp_name->st);
+ fsp_set_fd(fsp, fd);
+
+ fsp->vuid = current_vuid;
+ fsp->fsp_flags.can_lock = false;
+ fsp->fsp_flags.can_read = false;
+ fsp->access_mask = FILE_GENERIC_WRITE;
+ fsp->fsp_flags.can_write = true;
+ fsp->fsp_flags.modified = false;
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = false;
+ fsp->fsp_flags.delete_on_close = false;
+ fsp->fsp_flags.is_fsa = true;
+
+ fsp->print_file = pf;
+
+ status = NT_STATUS_OK;
+done:
+ if (!NT_STATUS_IS_OK(status)) {
+ if (fd != -1) {
+ close(fd);
+ if (fsp->print_file) {
+ unlink(fsp->print_file->filename);
+ }
+ }
+ /* We need to delete the job from spoolss too */
+ if (pf && pf->jobid) {
+ print_spool_terminate(fsp->conn, pf);
+ }
+ }
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+int print_spool_write(files_struct *fsp,
+ const char *data, uint32_t size,
+ off_t offset, uint32_t *written)
+{
+ SMB_STRUCT_STAT st;
+ ssize_t n;
+ int ret;
+
+ *written = 0;
+
+ /* first of all stat file to find out if it is still there.
+ * spoolss may have deleted it to signal someone has killed
+ * the job through it's interface */
+
+ if (sys_fstat(fsp_get_io_fd(fsp), &st, false) != 0) {
+ ret = errno;
+ DEBUG(3, ("printfile_offset: sys_fstat failed on %s (%s)\n",
+ fsp_str_dbg(fsp), strerror(ret)));
+ return ret;
+ }
+
+ /* check if the file is unlinked, this will signal spoolss has
+ * killed it, just return an error and close the file */
+ if (st.st_ex_nlink == 0) {
+ close(fsp_get_io_fd(fsp));
+ return EBADF;
+ }
+
+ /* When print files go beyond 4GB, the 32-bit offset sent in
+ * old SMBwrite calls is relative to the current 4GB chunk
+ * we're writing to.
+ * Discovered by Sebastian Kloska <oncaphillis@snafu.de>.
+ */
+ if (offset < 0xffffffff00000000LL) {
+ offset = (st.st_ex_size & 0xffffffff00000000LL) + offset;
+ }
+
+ n = write_data_at_offset(fsp_get_io_fd(fsp), data, size, offset);
+ if (n == -1) {
+ ret = errno;
+ print_spool_terminate(fsp->conn, fsp->print_file);
+ } else {
+ *written = n;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+void print_spool_end(files_struct *fsp, enum file_close_type close_type)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (fsp->fsp_flags.delete_on_close) {
+ int ret;
+
+ /*
+ * Job was requested to be cancelled by setting
+ * delete on close so truncate the job file.
+ * print_job_end() which is called from
+ * _spoolss_EndDocPrinter() will take
+ * care of deleting it for us.
+ */
+ ret = ftruncate(fsp_get_io_fd(fsp), 0);
+ if (ret == -1) {
+ DBG_ERR("ftruncate failed: %s\n", strerror(errno));
+ }
+ }
+
+ b = fsp->conn->spoolss_pipe->binding_handle;
+
+ switch (close_type) {
+ case NORMAL_CLOSE:
+ case SHUTDOWN_CLOSE:
+ /* this also automatically calls spoolss_EndDocPrinter */
+ status = dcerpc_spoolss_ClosePrinter(b, fsp->print_file,
+ &fsp->print_file->handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_IS_OK(status = werror_to_ntstatus(werr))) {
+ DEBUG(3, ("Failed to close printer %s [%s]\n",
+ fsp->print_file->svcname, nt_errstr(status)));
+ }
+ break;
+ case ERROR_CLOSE:
+ print_spool_terminate(fsp->conn, fsp->print_file);
+ break;
+ }
+}
+
+
+void print_spool_terminate(struct connection_struct *conn,
+ struct print_file_data *print_file)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = NULL;
+
+ rap_jobid_delete(print_file->svcname, print_file->jobid);
+
+ status = rpc_pipe_open_interface(conn,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &conn->spoolss_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("print_spool_terminate: "
+ "Failed to get spoolss pipe [%s]\n",
+ nt_errstr(status)));
+ return;
+ }
+ b = conn->spoolss_pipe->binding_handle;
+
+ status = dcerpc_spoolss_SetJob(b, print_file,
+ &print_file->handle,
+ print_file->jobid,
+ NULL, SPOOLSS_JOB_CONTROL_DELETE,
+ &werr);
+ if (!NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_IS_OK(status = werror_to_ntstatus(werr))) {
+ DEBUG(3, ("Failed to delete job %d [%s]\n",
+ print_file->jobid, nt_errstr(status)));
+ return;
+ }
+ status = dcerpc_spoolss_ClosePrinter(b, print_file,
+ &print_file->handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_IS_OK(status = werror_to_ntstatus(werr))) {
+ DEBUG(3, ("Failed to close printer %s [%s]\n",
+ print_file->svcname, nt_errstr(status)));
+ return;
+ }
+}
diff --git a/source3/printing/queue_process.c b/source3/printing/queue_process.c
new file mode 100644
index 0000000..6613e8f
--- /dev/null
+++ b/source3/printing/queue_process.c
@@ -0,0 +1,451 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+ Copyright (C) Simo Sorce 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include <spawn.h>
+#include "smbd/globals.h"
+#include "include/messages.h"
+#include "lib/util/util_process.h"
+#include "lib/util/sys_rw.h"
+#include "printing.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/queue_process.h"
+#include "locking/proto.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "rpc_server/rpc_config.h"
+#include "printing/load.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "auth.h"
+#include "nt_printing.h"
+#include "util_event.h"
+#include "lib/global_contexts.h"
+#include "lib/util/pidfile.h"
+
+/**
+ * @brief Purge stale printers and reload from pre-populated pcap cache.
+ *
+ * This function should normally only be called as a callback on a successful
+ * pcap_cache_reload().
+ *
+ * This function can cause DELETION of printers and drivers from our registry,
+ * so calling it on a failed pcap reload may REMOVE permanently all printers
+ * and drivers.
+ *
+ * @param[in] ev The event context.
+ *
+ * @param[in] msg_ctx The messaging context.
+ */
+static void delete_and_reload_printers_full(struct tevent_context *ev,
+ struct messaging_context *msg_ctx)
+{
+ struct auth_session_info *session_info = NULL;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int n_services;
+ int pnum;
+ int snum;
+ const char *pname;
+ const char *sname;
+ NTSTATUS status;
+
+ n_services = lp_numservices();
+ pnum = lp_servicenumber(PRINTERS_NAME);
+
+ status = make_session_info_system(talloc_tos(), &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("reload_printers: "
+ "Could not create system session_info\n"));
+ /* can't remove stale printers before we
+ * are fully initialized */
+ return;
+ }
+
+ /*
+ * Add default config for printers added to smb.conf file and remove
+ * stale printers
+ */
+ for (snum = 0; snum < n_services; snum++) {
+ /* avoid removing PRINTERS_NAME */
+ if (snum == pnum) {
+ continue;
+ }
+
+ /* skip no-printer services */
+ if (!snum_is_shared_printer(snum)) {
+ continue;
+ }
+
+ sname = lp_const_servicename(snum);
+ pname = lp_printername(session_info, lp_sub, snum);
+
+ /* check printer, but avoid removing non-autoloaded printers */
+ if (lp_autoloaded(snum) &&
+ !printer_list_printername_exists(pname)) {
+ DEBUG(3, ("removing stale printer %s\n", pname));
+
+ if (is_printer_published(session_info, session_info,
+ msg_ctx,
+ NULL,
+ lp_servicename(session_info,
+ lp_sub,
+ snum),
+ &pinfo2)) {
+ nt_printer_publish(session_info,
+ session_info,
+ msg_ctx,
+ pinfo2,
+ DSPRINT_UNPUBLISH);
+ TALLOC_FREE(pinfo2);
+ }
+ nt_printer_remove(session_info, session_info, msg_ctx,
+ pname);
+ } else {
+ DEBUG(8, ("Adding default registry entry for printer "
+ "[%s], if it doesn't exist.\n", sname));
+ nt_printer_add(session_info, session_info, msg_ctx,
+ sname);
+ }
+ }
+
+ /* finally, purge old snums */
+ delete_and_reload_printers();
+
+ TALLOC_FREE(session_info);
+}
+
+
+/****************************************************************************
+ Notify smbds of new printcap data
+**************************************************************************/
+static void reload_pcap_change_notify(struct tevent_context *ev,
+ struct messaging_context *msg_ctx)
+{
+ /*
+ * Reload the printers first in the background process so that
+ * newly added printers get default values created in the registry.
+ *
+ * This will block the process for some time (~1 sec per printer), but
+ * it doesn't block smbd's serving clients.
+ */
+ delete_and_reload_printers_full(ev, msg_ctx);
+
+ messaging_send_all(msg_ctx, MSG_PRINTER_PCAP, NULL, 0);
+}
+
+struct bq_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct idle_event *housekeep;
+ struct tevent_signal *sighup_handler;
+ struct tevent_signal *sigchld_handler;
+};
+
+static bool print_queue_housekeeping(const struct timeval *now, void *pvt)
+{
+ struct bq_state *state;
+
+ state = talloc_get_type_abort(pvt, struct bq_state);
+
+ DEBUG(5, ("print queue housekeeping\n"));
+ pcap_cache_reload(state->ev, state->msg, reload_pcap_change_notify);
+
+ return true;
+}
+
+static bool printing_subsystem_queue_tasks(struct bq_state *state)
+{
+ uint32_t housekeeping_period = lp_printcap_cache_time();
+
+ /* cancel any existing housekeeping event */
+ TALLOC_FREE(state->housekeep);
+
+ if ((housekeeping_period == 0) || !lp_load_printers()) {
+ DEBUG(4, ("background print queue housekeeping disabled\n"));
+ return true;
+ }
+
+ state->housekeep = event_add_idle(state->ev, NULL,
+ timeval_set(housekeeping_period, 0),
+ "print_queue_housekeeping",
+ print_queue_housekeeping, state);
+ if (state->housekeep == NULL) {
+ DEBUG(0,("Could not add print_queue_housekeeping event\n"));
+ return false;
+ }
+
+ return true;
+}
+
+static void bq_reopen_logs(char *logfile)
+{
+ if (logfile) {
+ lp_set_logfile(logfile);
+ }
+ reopen_logs();
+}
+
+static void bq_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *pvt)
+{
+ struct bq_state *state;
+
+ state = talloc_get_type_abort(pvt, struct bq_state);
+ change_to_root_user();
+
+ DEBUG(1, ("Reloading pcap cache after SIGHUP\n"));
+ pcap_cache_reload(state->ev, state->msg,
+ reload_pcap_change_notify);
+ printing_subsystem_queue_tasks(state);
+ bq_reopen_logs(NULL);
+}
+
+static void bq_sig_chld_handler(struct tevent_context *ev_ctx,
+ struct tevent_signal *se,
+ int signum, int count,
+ void *siginfo, void *pvt)
+{
+ int status;
+ pid_t pid;
+
+ do {
+ do {
+ pid = waitpid(-1, &status, WNOHANG);
+ } while ((pid == -1) && (errno == EINTR));
+
+ if (WIFEXITED(status)) {
+ DBG_INFO("Bq child process %d terminated with %d\n",
+ (int)pid,
+ WEXITSTATUS(status));
+ } else {
+ DBG_NOTICE("Bq child process %d terminated abnormally\n",
+ (int)pid);
+ }
+ } while (pid > 0);
+}
+
+static void bq_smb_conf_updated(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct bq_state *state;
+
+ state = talloc_get_type_abort(private_data, struct bq_state);
+
+ DEBUG(10,("smb_conf_updated: Got message saying smb.conf was "
+ "updated. Reloading.\n"));
+ change_to_root_user();
+ pcap_cache_reload(state->ev, msg_ctx, reload_pcap_change_notify);
+ printing_subsystem_queue_tasks(state);
+}
+
+static int bq_state_destructor(struct bq_state *s)
+{
+ struct messaging_context *msg_ctx = s->msg;
+ TALLOC_FREE(s->sighup_handler);
+ TALLOC_FREE(s->sigchld_handler);
+ messaging_deregister(msg_ctx, MSG_PRINTER_DRVUPGRADE, NULL);
+ messaging_deregister(msg_ctx, MSG_PRINTER_UPDATE, NULL);
+ messaging_deregister(msg_ctx, MSG_SMB_CONF_UPDATED, s);
+ return 0;
+}
+
+struct bq_state *register_printing_bq_handlers(
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx)
+{
+ struct bq_state *state = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ state = talloc_zero(mem_ctx, struct bq_state);
+ if (state == NULL) {
+ return NULL;
+ }
+ state->ev = messaging_tevent_context(msg_ctx);
+ state->msg = msg_ctx;
+
+ status = messaging_register(
+ msg_ctx, state, MSG_SMB_CONF_UPDATED, bq_smb_conf_updated);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ status = messaging_register(
+ msg_ctx, NULL, MSG_PRINTER_UPDATE, print_queue_receive);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail_dereg_smb_conf_updated;
+ }
+ status = messaging_register(
+ msg_ctx, NULL, MSG_PRINTER_DRVUPGRADE, do_drv_upgrade_printer);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail_dereg_printer_update;
+ }
+
+ state->sighup_handler = tevent_add_signal(
+ state->ev, state, SIGHUP, 0, bq_sig_hup_handler, state);
+ if (state->sighup_handler == NULL) {
+ goto fail_dereg_printer_drvupgrade;
+ }
+ state->sigchld_handler = tevent_add_signal(
+ state->ev, state, SIGCHLD, 0, bq_sig_chld_handler, NULL);
+ if (state->sigchld_handler == NULL) {
+ goto fail_free_handlers;
+ }
+
+ /* Initialize the printcap cache as soon as the daemon starts. */
+ pcap_cache_reload(state->ev, state->msg, reload_pcap_change_notify);
+
+ ok = printing_subsystem_queue_tasks(state);
+ if (!ok) {
+ goto fail_free_handlers;
+ }
+
+ talloc_set_destructor(state, bq_state_destructor);
+
+ return state;
+
+fail_free_handlers:
+ TALLOC_FREE(state->sighup_handler);
+ TALLOC_FREE(state->sigchld_handler);
+fail_dereg_printer_drvupgrade:
+ messaging_deregister(msg_ctx, MSG_PRINTER_DRVUPGRADE, NULL);
+fail_dereg_printer_update:
+ messaging_deregister(msg_ctx, MSG_PRINTER_UPDATE, NULL);
+fail_dereg_smb_conf_updated:
+ messaging_deregister(msg_ctx, MSG_SMB_CONF_UPDATED, state);
+fail:
+ TALLOC_FREE(state);
+ return NULL;
+}
+
+/****************************************************************************
+main thread of the background lpq updater
+****************************************************************************/
+pid_t start_background_queue(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ char *logfile)
+{
+ pid_t pid;
+ int ret;
+ ssize_t nread;
+ char **argv = NULL;
+ int ready_fds[2];
+
+ DEBUG(3,("start_background_queue: Starting background LPQ thread\n"));
+
+ ret = pipe(ready_fds);
+ if (ret == -1) {
+ return -1;
+ }
+
+ argv = str_list_make_empty(talloc_tos());
+ str_list_add_printf(
+ &argv, "%s/samba-bgqd", get_dyn_SAMBA_LIBEXECDIR());
+ str_list_add_printf(
+ &argv, "--ready-signal-fd=%d", ready_fds[1]);
+ str_list_add_printf(
+ &argv, "--parent-watch-fd=%d", 0);
+ str_list_add_printf(
+ &argv, "--debuglevel=%d", debuglevel_get_class(DBGC_RPC_SRV));
+ if (!is_default_dyn_CONFIGFILE()) {
+ str_list_add_printf(
+ &argv, "--configfile=%s", get_dyn_CONFIGFILE());
+ }
+ if (!is_default_dyn_LOGFILEBASE()) {
+ str_list_add_printf(
+ &argv, "--log-basename=%s", get_dyn_LOGFILEBASE());
+ }
+ str_list_add_printf(&argv, "-F");
+ if (argv == NULL) {
+ goto nomem;
+ }
+
+ ret = posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
+ if (ret == -1) {
+ goto fail;
+ }
+ TALLOC_FREE(argv);
+
+ close(ready_fds[1]);
+
+ nread = sys_read(ready_fds[0], &pid, sizeof(pid));
+ close(ready_fds[0]);
+ if (nread != sizeof(pid)) {
+ goto fail;
+ }
+
+ return pid;
+
+nomem:
+ errno = ENOMEM;
+fail:
+ {
+ int err = errno;
+ TALLOC_FREE(argv);
+ errno = err;
+ }
+
+ return -1;
+}
+
+
+/* Run before the parent forks */
+bool printing_subsystem_init(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ struct dcesrv_context *dce_ctx)
+{
+ pid_t pid = -1;
+
+ pid = start_background_queue(NULL, NULL, NULL);
+ if (pid == -1) {
+ return false;
+ }
+ background_lpq_updater_pid = pid;
+
+ if (!print_backend_init(msg_ctx)) {
+ return false;
+ }
+
+ return true;
+}
+
+void send_to_bgqd(struct messaging_context *msg_ctx,
+ uint32_t msg_type,
+ const uint8_t *buf,
+ size_t buflen)
+{
+ pid_t bgqd = pidfile_pid(lp_pid_directory(), "samba-bgqd");
+
+ if (bgqd == -1) {
+ return;
+ }
+ messaging_send_buf(
+ msg_ctx, pid_to_procid(bgqd), msg_type, buf, buflen);
+}
diff --git a/source3/printing/queue_process.h b/source3/printing/queue_process.h
new file mode 100644
index 0000000..93bc79f
--- /dev/null
+++ b/source3/printing/queue_process.h
@@ -0,0 +1,44 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ printing backend routines
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 2002
+ Copyright (C) Simo Sorce 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SOURCE3_PRINTING_QUEUE_PROCESS_H_
+#define _SOURCE3_PRINTING_QUEUE_PROCESS_H_
+
+struct dcesrv_context;
+
+bool printing_subsystem_init(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ struct dcesrv_context *dce_ctx);
+pid_t start_background_queue(struct tevent_context *ev,
+ struct messaging_context *msg,
+ char *logfile);
+void send_to_bgqd(struct messaging_context *msg_ctx,
+ uint32_t msg_type,
+ const uint8_t *buf,
+ size_t buflen);
+
+struct bq_state;
+struct bq_state *register_printing_bq_handlers(
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx);
+
+#endif /* _SOURCE3_PRINTING_QUEUE_PROCESS_H_ */
diff --git a/source3/printing/rap_jobid.c b/source3/printing/rap_jobid.c
new file mode 100644
index 0000000..7af3d47
--- /dev/null
+++ b/source3/printing/rap_jobid.c
@@ -0,0 +1,164 @@
+/*
+ * Maintain rap vs spoolss jobids
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ the printing backend revolves around a tdb database that stores the
+ SMB view of the print queue
+
+ The key for this database is a jobid - a internally generated number that
+ uniquely identifies a print job
+
+ reading the print queue involves two steps:
+ - possibly running lpq and updating the internal database from that
+ - reading entries from the database
+
+ jobids are assigned when a job starts spooling.
+*/
+
+#include "rap_jobid.h"
+#include <tdb.h>
+#include "source3/include/util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+static TDB_CONTEXT *rap_tdb;
+static uint16_t next_rap_jobid;
+struct rap_jobid_key {
+ fstring sharename;
+ uint32_t jobid;
+};
+
+/***************************************************************************
+ Nightmare. LANMAN jobid's are 16 bit numbers..... We must map them to 32
+ bit RPC jobids.... JRA.
+***************************************************************************/
+
+uint16_t pjobid_to_rap(const char* sharename, uint32_t jobid)
+{
+ uint16_t rap_jobid;
+ TDB_DATA data, key;
+ struct rap_jobid_key jinfo;
+ uint8_t buf[2];
+
+ DEBUG(10,("pjobid_to_rap: called.\n"));
+
+ if (!rap_tdb) {
+ /* Create the in-memory tdb. */
+ rap_tdb = tdb_open_log(NULL, 0, TDB_INTERNAL, (O_RDWR|O_CREAT), 0644);
+ if (!rap_tdb)
+ return 0;
+ }
+
+ ZERO_STRUCT( jinfo );
+ fstrcpy( jinfo.sharename, sharename );
+ jinfo.jobid = jobid;
+ key.dptr = (uint8_t *)&jinfo;
+ key.dsize = sizeof(jinfo);
+
+ data = tdb_fetch(rap_tdb, key);
+ if (data.dptr && data.dsize == sizeof(uint16_t)) {
+ rap_jobid = SVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+ DEBUG(10,("pjobid_to_rap: jobid %u maps to RAP jobid %u\n",
+ (unsigned int)jobid, (unsigned int)rap_jobid));
+ return rap_jobid;
+ }
+ SAFE_FREE(data.dptr);
+ /* Not found - create and store mapping. */
+ rap_jobid = ++next_rap_jobid;
+ if (rap_jobid == 0)
+ rap_jobid = ++next_rap_jobid;
+ SSVAL(buf,0,rap_jobid);
+ data.dptr = buf;
+ data.dsize = sizeof(rap_jobid);
+ tdb_store(rap_tdb, key, data, TDB_REPLACE);
+ tdb_store(rap_tdb, data, key, TDB_REPLACE);
+
+ DEBUG(10,("pjobid_to_rap: created jobid %u maps to RAP jobid %u\n",
+ (unsigned int)jobid, (unsigned int)rap_jobid));
+ return rap_jobid;
+}
+
+bool rap_to_pjobid(uint16_t rap_jobid, fstring sharename, uint32_t *pjobid)
+{
+ TDB_DATA data, key;
+ uint8_t buf[2];
+
+ DEBUG(10,("rap_to_pjobid called.\n"));
+
+ if (!rap_tdb)
+ return False;
+
+ SSVAL(buf,0,rap_jobid);
+ key.dptr = buf;
+ key.dsize = sizeof(rap_jobid);
+ data = tdb_fetch(rap_tdb, key);
+ if ( data.dptr && data.dsize == sizeof(struct rap_jobid_key) )
+ {
+ struct rap_jobid_key *jinfo = (struct rap_jobid_key*)data.dptr;
+ if (sharename != NULL) {
+ fstrcpy( sharename, jinfo->sharename );
+ }
+ *pjobid = jinfo->jobid;
+ DEBUG(10,("rap_to_pjobid: jobid %u maps to RAP jobid %u\n",
+ (unsigned int)*pjobid, (unsigned int)rap_jobid));
+ SAFE_FREE(data.dptr);
+ return True;
+ }
+
+ DEBUG(10,("rap_to_pjobid: Failed to lookup RAP jobid %u\n",
+ (unsigned int)rap_jobid));
+ SAFE_FREE(data.dptr);
+ return False;
+}
+
+void rap_jobid_delete(const char* sharename, uint32_t jobid)
+{
+ TDB_DATA key, data;
+ uint16_t rap_jobid;
+ struct rap_jobid_key jinfo;
+ uint8_t buf[2];
+
+ DEBUG(10,("rap_jobid_delete: called.\n"));
+
+ if (!rap_tdb)
+ return;
+
+ ZERO_STRUCT( jinfo );
+ fstrcpy( jinfo.sharename, sharename );
+ jinfo.jobid = jobid;
+ key.dptr = (uint8_t *)&jinfo;
+ key.dsize = sizeof(jinfo);
+
+ data = tdb_fetch(rap_tdb, key);
+ if (!data.dptr || (data.dsize != sizeof(uint16_t))) {
+ DEBUG(10,("rap_jobid_delete: cannot find jobid %u\n",
+ (unsigned int)jobid ));
+ SAFE_FREE(data.dptr);
+ return;
+ }
+
+ DEBUG(10,("rap_jobid_delete: deleting jobid %u\n",
+ (unsigned int)jobid ));
+
+ rap_jobid = SVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+ SSVAL(buf,0,rap_jobid);
+ data.dptr = buf;
+ data.dsize = sizeof(rap_jobid);
+ tdb_delete(rap_tdb, key);
+ tdb_delete(rap_tdb, data);
+}
diff --git a/source3/printing/rap_jobid.h b/source3/printing/rap_jobid.h
new file mode 100644
index 0000000..6325689
--- /dev/null
+++ b/source3/printing/rap_jobid.h
@@ -0,0 +1,29 @@
+/*
+ * Maintain rap vs spoolss jobids
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef __PRINTING_RAP_JOBID_H__
+#define __PRINTING_RAP_JOBID_H__
+
+#include "includes.h"
+
+uint16_t pjobid_to_rap(const char *sharename, uint32_t jobid);
+bool rap_to_pjobid(
+ uint16_t rap_jobid, fstring sharename, uint32_t *pjobid);
+void rap_jobid_delete(const char *sharename, uint32_t jobid);
+
+#endif
diff --git a/source3/printing/samba-bgqd.c b/source3/printing/samba-bgqd.c
new file mode 100644
index 0000000..59ed0cc
--- /dev/null
+++ b/source3/printing/samba-bgqd.c
@@ -0,0 +1,359 @@
+/*
+ * Printing background queue helper
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "source3/locking/share_mode_lock.h"
+#include "source3/param/loadparm.h"
+#include "source3/param/param_proto.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/cmdline/closefrom_except.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/debug.h"
+#include "lib/util/signal.h"
+#include "lib/util/fault.h"
+#include "lib/util/become_daemon.h"
+#include "lib/util/charset/charset.h"
+#include "lib/util/samba_util.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/pidfile.h"
+#include "lib/async_req/async_sock.h"
+#include "dynconfig/dynconfig.h"
+#include "source3/lib/global_contexts.h"
+#include "messages.h"
+#include "nsswitch/winbind_client.h"
+#include "source3/include/auth.h"
+#include "source3/lib/util_procid.h"
+#include "source3/auth/proto.h"
+#include "source3/printing/queue_process.h"
+#include "source3/lib/substitute.h"
+
+static void watch_handler(struct tevent_req *req)
+{
+ bool *pdone = tevent_req_callback_data_void(req);
+ *pdone = true;
+}
+
+static void bgqd_sig_term_handler(
+ struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ bool *pdone = private_data;
+ *pdone = true;
+}
+
+static bool ready_signal_filter(
+ struct messaging_rec *rec, void *private_data)
+{
+ pid_t pid = getpid();
+ ssize_t written;
+
+ if (rec->msg_type != MSG_DAEMON_READY_FD) {
+ return false;
+ }
+ if (rec->num_fds != 1) {
+ return false;
+ }
+
+ written = sys_write(rec->fds[0], &pid, sizeof(pid));
+ if (written != sizeof(pid)) {
+ DBG_ERR("Could not write pid: %s\n", strerror(errno));
+ }
+
+ return false;
+}
+
+static int samba_bgqd_pidfile_create(
+ struct messaging_context *msg_ctx,
+ const char *progname,
+ int ready_signal_fd)
+{
+ const char *piddir = lp_pid_directory();
+ size_t len = strlen(piddir) + strlen(progname) + 6;
+ char pidFile[len];
+ pid_t existing_pid;
+ int fd, ret;
+
+ snprintf(pidFile,
+ sizeof(pidFile),
+ "%s/%s.pid",
+ piddir, progname);
+
+ ret = pidfile_path_create(pidFile, &fd, &existing_pid);
+ if (ret == 0) {
+ struct tevent_req *ready_signal_req = NULL;
+
+ /*
+ * Listen for fd's sent via MSG_DAEMON_READY_FD:
+ * Multiple instances of this process might have raced
+ * for creating the pidfile. Make sure the parent does
+ * not suffer from this race, reply on behalf of the
+ * loser of this race.
+ */
+
+ ready_signal_req = messaging_filtered_read_send(
+ msg_ctx,
+ messaging_tevent_context(msg_ctx),
+ msg_ctx,
+ ready_signal_filter,
+ NULL);
+ if (ready_signal_req == NULL) {
+ DBG_DEBUG("messaging_filtered_read_send failed\n");
+ pidfile_unlink(piddir, progname);
+ pidfile_fd_close(fd);
+ return ENOMEM;
+ }
+
+ /* leak fd */
+ return 0;
+ }
+
+ if (ret != EAGAIN) {
+ DBG_DEBUG("pidfile_path_create() failed: %s\n",
+ strerror(ret));
+ return ret;
+ }
+
+ DBG_DEBUG("%s pid %d exists\n", progname, (int)existing_pid);
+
+ if (ready_signal_fd != -1) {
+ /*
+ * We lost the race for the pidfile, but someone else
+ * can report readiness on our behalf.
+ */
+ NTSTATUS status = messaging_send_iov(
+ msg_ctx,
+ pid_to_procid(existing_pid),
+ MSG_DAEMON_READY_FD,
+ NULL,
+ 0,
+ &ready_signal_fd,
+ 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not send ready_signal_fd: %s\n",
+ nt_errstr(status));
+ }
+ }
+
+ return EAGAIN;
+}
+
+int main(int argc, const char *argv[])
+{
+ struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *progname = getprogname();
+ TALLOC_CTX *frame = NULL;
+ poptContext pc;
+ struct messaging_context *msg_ctx = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *watch_req = NULL;
+ struct tevent_signal *sigterm_handler = NULL;
+ struct bq_state *bq = NULL;
+ int log_stdout = 0;
+ int ready_signal_fd = -1;
+ int watch_fd = -1;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+ bool done = false;
+ int exitcode = 1;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DAEMON
+
+ /*
+ * File descriptor to write the PID of the helper
+ * process to
+ */
+ {
+ .longName = "ready-signal-fd",
+ .argInfo = POPT_ARG_INT,
+ .arg = &ready_signal_fd,
+ .descrip = "Fd to signal readiness to" ,
+ },
+
+ /*
+ * Read end of a pipe held open by the parent
+ * smbd. Exit this process when it becomes readable.
+ */
+ {
+ .longName = "parent-watch-fd",
+ .argInfo = POPT_ARG_INT,
+ .arg = &watch_fd,
+ .descrip = "Fd to watch for exiting",
+ },
+ POPT_TABLEEND
+ };
+
+ {
+ const char *fd_params[] = {
+ "ready-signal-fd", "parent-watch-fd",
+ };
+
+ closefrom_except_fd_params(
+ 3, ARRAY_SIZE(fd_params), fd_params, argc, argv);
+ }
+
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+ umask(0);
+ set_remote_machine_name("smbd-bgqd", true);
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to setup cmdline parser!\n");
+ exit(ENOMEM);
+ }
+
+ cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
+
+ pc = samba_popt_get_context(progname,
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to get popt context!\n");
+ exit(ENOMEM);
+ }
+
+ ret = poptGetNextOpt(pc);
+ if (ret < -1) {
+ fprintf(stderr, "invalid options: %s\n", poptStrerror(ret));
+ goto done;
+ }
+
+ poptFreeContext(pc);
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+
+ /* main process will notify systemd */
+ daemon_sd_notifications(false);
+
+ if (!cmdline_daemon_cfg->fork) {
+ daemon_status(progname, "Starting process ... ");
+ } else {
+ become_daemon(true,
+ cmdline_daemon_cfg->no_process_group,
+ log_stdout);
+ }
+
+ BlockSignals(true, SIGPIPE);
+
+ smb_init_locale();
+ dump_core_setup(progname, lp_logfile(frame, lp_sub));
+
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ DBG_ERR("messaging_init() failed\n");
+ goto done;
+ }
+ ev = messaging_tevent_context(msg_ctx);
+
+ ret = samba_bgqd_pidfile_create(msg_ctx, progname, ready_signal_fd);
+ if (ret != 0) {
+ goto done;
+ }
+
+ if (watch_fd != -1) {
+ watch_req = wait_for_read_send(ev, ev, watch_fd, true);
+ if (watch_req == NULL) {
+ fprintf(stderr, "tevent_add_fd failed\n");
+ goto done;
+ }
+ tevent_req_set_callback(watch_req, watch_handler, &done);
+ }
+
+ (void)winbind_off();
+ ok = init_guest_session_info(frame);
+ (void)winbind_on();
+ if (!ok) {
+ DBG_ERR("init_guest_session_info failed\n");
+ goto done;
+ }
+
+ (void)winbind_off();
+ status = init_system_session_info(frame);
+ (void)winbind_on();
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_system_session_info failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ sigterm_handler = tevent_add_signal(
+ ev, frame, SIGTERM, 0, bgqd_sig_term_handler, &done);
+ if (sigterm_handler == NULL) {
+ DBG_ERR("Could not install SIGTERM handler\n");
+ goto done;
+ }
+
+ bq = register_printing_bq_handlers(frame, msg_ctx);
+ if (bq == NULL) {
+ DBG_ERR("Could not register bq handlers\n");
+ goto done;
+ }
+
+ ok = locking_init();
+ if (!ok) {
+ DBG_ERR("locking_init failed\n");
+ goto done;
+ }
+
+ if (ready_signal_fd != -1) {
+ pid_t pid = getpid();
+ ssize_t written;
+
+ written = sys_write(ready_signal_fd, &pid, sizeof(pid));
+ if (written != sizeof(pid)) {
+ DBG_ERR("Reporting readiness failed\n");
+ goto done;
+ }
+ close(ready_signal_fd);
+ ready_signal_fd = -1;
+ }
+
+ while (!done) {
+ TALLOC_CTX *tmp = talloc_stackframe();
+ ret = tevent_loop_once(ev);
+ TALLOC_FREE(tmp);
+ if (ret != 0) {
+ DBG_ERR("tevent_loop_once failed\n");
+ break;
+ }
+ }
+
+ exitcode = 0;
+done:
+ TALLOC_FREE(watch_req);
+ TALLOC_FREE(bq);
+ TALLOC_FREE(sigterm_handler);
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ return exitcode;
+}
diff --git a/source3/printing/tests/README.vlp b/source3/printing/tests/README.vlp
new file mode 100644
index 0000000..fc0b91a
--- /dev/null
+++ b/source3/printing/tests/README.vlp
@@ -0,0 +1,19 @@
+Virtual line printer test program (vlp)
+=======================================
+
+This can be useful for testing/debugging Samba print code. It gives you a
+virtual full-function printer.
+
+Setup
+
+Set up Samba to use vlp.
+ In your smb.conf file under [global], add the following option:
+ printing = vlp
+ and then add any number of print shares, without needing to make them
+ really exist.
+
+ [testprinter]
+ printable = yes
+
+ is all you need for the most basic virtual printer.
+
diff --git a/source3/printing/tests/vlp.c b/source3/printing/tests/vlp.c
new file mode 100644
index 0000000..b19b21e
--- /dev/null
+++ b/source3/printing/tests/vlp.c
@@ -0,0 +1,445 @@
+/*
+ Unix SMB/Netbios implementation.
+
+ Virtual lp system for printer testing
+
+ Copyright (C) Tim Potter 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "util_tdb.h"
+
+#ifdef malloc
+#undef malloc
+#endif
+
+#define PRINT_FIRSTJOB 100
+
+static TDB_CONTEXT *tdb;
+
+struct vlp_job {
+ fstring owner;
+ int jobid;
+ fstring jobname;
+ int size;
+ int status;
+ time_t submit_time;
+ int deleted;
+};
+
+/* Print usage */
+
+static void usage(void)
+{
+ printf("Usage: vlp tdbfile=/tmp/vlp.tdb lpq|lprm|print|queuepause|queueresume|"
+ "lppause|lpresume [args]\n");
+}
+
+/* Return an array of vlp jobs that is the printer queue */
+
+static void get_job_list(char *printer, struct vlp_job **job_list,
+ int *num_jobs)
+{
+ fstring keystr;
+ TDB_DATA data;
+
+ slprintf(keystr, sizeof(keystr) - 1, "LPQ/%s", printer);
+ data = tdb_fetch_bystring(tdb, keystr);
+
+ *job_list = (struct vlp_job *)data.dptr;
+ *num_jobs = data.dsize / sizeof(struct vlp_job);
+}
+
+/* Store an array of vl jobs for the queue */
+
+static void set_job_list(char *printer, struct vlp_job *job_list,
+ int num_jobs)
+{
+ fstring keystr;
+ TDB_DATA data;
+
+ slprintf(keystr, sizeof(keystr) - 1, "LPQ/%s", printer);
+
+ data.dptr = (unsigned char *)job_list;
+ data.dsize = num_jobs * sizeof(struct vlp_job);
+ tdb_store_bystring(tdb, keystr, data, TDB_REPLACE);
+}
+
+/* Return the next job number for a printer */
+
+static int next_jobnum(char *printer)
+{
+ fstring keystr;
+ int jobnum;
+
+ slprintf(keystr, sizeof(keystr) - 1, "JOBNUM/%s", printer);
+
+ tdb_lock_bystring(tdb, keystr);
+
+ jobnum = tdb_fetch_int32(tdb, keystr);
+
+ /* Create next job index if none exists */
+
+ if (jobnum == -1) {
+ jobnum = PRINT_FIRSTJOB;
+ } else {
+ jobnum++;
+ }
+
+ tdb_store_int32(tdb, keystr, jobnum);
+
+ tdb_unlock_bystring(tdb, keystr);
+
+ return jobnum;
+}
+
+static void set_printer_status(char *printer, int status)
+{
+ fstring keystr;
+
+ slprintf(keystr, sizeof(keystr) - 1, "STATUS/%s", printer);
+ tdb_store_int32(tdb, keystr, status);
+}
+
+static int get_printer_status(char *printer)
+{
+ fstring keystr;
+ TDB_DATA data;
+
+ slprintf(keystr, sizeof(keystr) - 1, "STATUS/%s", printer);
+
+ data.dptr = (unsigned char *)keystr;
+ data.dsize = strlen(keystr) + 1;
+
+ if (!tdb_exists(tdb, data)) {
+ set_printer_status(printer, LPSTAT_OK);
+ return LPSTAT_OK;
+ }
+
+ return tdb_fetch_int32(tdb, keystr);
+}
+
+/* Display printer queue */
+
+static int lpq_command(int argc, char **argv)
+{
+ char *printer;
+ struct vlp_job *job_list = NULL;
+ int i, num_jobs;
+
+ if (argc != 2) {
+ printf("Usage: lpq <printername>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+
+ /* Display printer status */
+
+ switch (get_printer_status(printer)) {
+ case LPSTAT_OK:
+ printf("enabled\n");
+ break;
+ case LPSTAT_STOPPED:
+ printf("disabled\n");
+ break;
+ case LPSTAT_ERROR:
+ default:
+ printf("error\n");
+ break;
+ }
+
+ /* Print queued documents */
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].deleted) continue;
+ printf("%d\t%d\t%d\t%ld\t%s\t%s\n", job_list[i].jobid,
+ job_list[i].size,
+ (i == 0 && job_list[i].status == LPQ_QUEUED) ?
+ LPQ_SPOOLING : job_list[i].status,
+ (long int)job_list[i].submit_time, job_list[i].owner,
+ job_list[i].jobname);
+ }
+
+ free(job_list);
+
+ return 0;
+}
+
+/* Remove a job */
+
+static int lprm_command(int argc, char **argv)
+{
+ char *printer;
+ int jobid, num_jobs, i;
+ struct vlp_job *job_list;
+
+ if (argc < 3) {
+ printf("Usage: lprm <printername> <jobid>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ jobid = atoi(argv[2]);
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].jobid == jobid) {
+ job_list[i].deleted = 1;
+ set_job_list(printer, job_list, num_jobs);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/* print command = print-test %p %s */
+
+static int print_command(int argc, char **argv)
+{
+ char *printer;
+ fstring keystr;
+ struct passwd *pw;
+ TDB_DATA value, queue;
+ struct vlp_job job;
+
+ if (argc < 3) {
+ printf("Usage: print <printername> <jobname>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+
+ ZERO_STRUCT(job);
+
+ /* Create a job record */
+
+ slprintf(job.jobname, sizeof(job.jobname) - 1, "%s", argv[2]);
+
+ if (!(pw = getpwuid(geteuid()))) {
+ printf("getpwuid failed\n");
+ return 1;
+ }
+
+ slprintf(job.owner, sizeof(job.owner) - 1, "%s", pw->pw_name);
+
+ job.jobid = next_jobnum(printer);
+ job.size = 666;
+ job.submit_time = time(NULL);
+
+ /* Store job entry in queue */
+
+ slprintf(keystr, sizeof(keystr) - 1, "LPQ/%s", printer);
+
+ value = tdb_fetch_bystring(tdb, keystr);
+
+ if (value.dptr) {
+
+ /* Add job to end of queue */
+
+ queue.dptr = (unsigned char *)malloc(value.dsize + sizeof(struct vlp_job));
+ if (!queue.dptr) return 1;
+
+ memcpy(queue.dptr, value.dptr, value.dsize);
+ memcpy(queue.dptr + value.dsize, &job, sizeof(struct vlp_job));
+
+ queue.dsize = value.dsize + sizeof(struct vlp_job);
+
+ tdb_store_bystring(tdb, keystr, queue, TDB_REPLACE);
+
+ free(queue.dptr);
+
+ } else {
+
+ /* Create new queue */
+ queue.dptr = (unsigned char *)&job;
+ queue.dsize = sizeof(struct vlp_job);
+
+ tdb_store_bystring(tdb, keystr, queue, TDB_REPLACE);
+ }
+
+ return 0;
+}
+
+/* Pause the queue */
+
+static int queuepause_command(int argc, char **argv)
+{
+ char *printer;
+
+ if (argc != 2) {
+ printf("Usage: queuepause <printername>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ set_printer_status(printer, LPSTAT_STOPPED);
+
+ return 0;
+}
+
+/* Resume the queue */
+
+static int queueresume_command(int argc, char **argv)
+{
+ char *printer;
+
+ if (argc != 2) {
+ printf("Usage: queueresume <printername>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ set_printer_status(printer, LPSTAT_OK);
+
+ return 0;
+}
+
+/* Pause a job */
+
+static int lppause_command(int argc, char **argv)
+{
+ struct vlp_job *job_list;
+ char *printer;
+ int jobid, num_jobs, i;
+
+ if (argc != 3) {
+ printf("Usage: lppause <printername> <jobid>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ jobid = atoi(argv[2]);
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].jobid == jobid) {
+ job_list[i].status = LPQ_PAUSED;
+ set_job_list(printer, job_list, num_jobs);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Resume a job */
+
+static int lpresume_command(int argc, char **argv)
+{
+ struct vlp_job *job_list;
+ char *printer;
+ int jobid, num_jobs, i;
+
+ if (argc != 3) {
+ printf("Usage: lpresume <printername> <jobid>\n");
+ return 1;
+ }
+
+ printer = argv[1];
+ jobid = atoi(argv[2]);
+
+ get_job_list(printer, &job_list, &num_jobs);
+
+ for (i = 0; i < num_jobs; i++) {
+ if (job_list[i].jobid == jobid) {
+ job_list[i].status = LPQ_QUEUED;
+ set_job_list(printer, job_list, num_jobs);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int main(int argc, char **argv)
+{
+ /* Parameter check */
+ const char *printdb_path = NULL;
+
+ if (argc < 2) {
+ usage();
+ return 1;
+ }
+
+ if (strncmp(argv[1], "tdbfile", strlen("tdbfile")) != 0) {
+ usage();
+ return 1;
+ }
+
+ printdb_path = get_string_param(argv[1]);
+ if (!printdb_path) {
+ return 1;
+ }
+
+ /* FIXME: We should *never* open a tdb without logging! */
+ if (!(tdb = tdb_open(printdb_path, 0, 0, O_RDWR | O_CREAT,
+ 0666))) {
+ printf("%s: unable to open %s\n", argv[0], printdb_path);
+ return 1;
+ }
+
+ /* Ensure we are modes 666 */
+
+ chmod(printdb_path, 0666);
+
+ /* Do commands */
+ if (argc < 3) {
+ usage();
+ return 1;
+ }
+
+ if (strcmp(argv[2], "lpq") == 0) {
+ return lpq_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "lprm") == 0) {
+ return lprm_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "print") == 0) {
+ return print_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "queuepause") == 0) {
+ return queuepause_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "queueresume") == 0) {
+ return queueresume_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "lppause") == 0) {
+ return lppause_command(argc - 2, &argv[2]);
+ }
+
+ if (strcmp(argv[2], "lpresume") == 0) {
+ return lpresume_command(argc - 2, &argv[2]);
+ }
+
+ /* Unknown command */
+
+ printf("%s: invalid command %s\n", argv[0], argv[1]);
+ return 1;
+}
diff --git a/source3/profile/profile.c b/source3/profile/profile.c
new file mode 100644
index 0000000..0b02e36
--- /dev/null
+++ b/source3/profile/profile.c
@@ -0,0 +1,332 @@
+/*
+ Unix SMB/CIFS implementation.
+ store smbd profiling information in shared memory
+ Copyright (C) Andrew Tridgell 1999
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/time.h"
+#include "messages.h"
+#include "smbprofile.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include <tevent.h>
+#include "../lib/crypto/crypto.h"
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+
+struct profile_stats *profile_p;
+struct smbprofile_global_state smbprofile_state;
+
+/****************************************************************************
+Set a profiling level.
+****************************************************************************/
+void set_profile_level(int level, const struct server_id *src)
+{
+ SMB_ASSERT(smbprofile_state.internal.db != NULL);
+
+ switch (level) {
+ case 0: /* turn off profiling */
+ smbprofile_state.config.do_count = false;
+ smbprofile_state.config.do_times = false;
+ DEBUG(1,("INFO: Profiling turned OFF from pid %d\n",
+ (int)procid_to_pid(src)));
+ break;
+ case 1: /* turn on counter profiling only */
+ smbprofile_state.config.do_count = true;
+ smbprofile_state.config.do_times = false;
+ DEBUG(1,("INFO: Profiling counts turned ON from pid %d\n",
+ (int)procid_to_pid(src)));
+ break;
+ case 2: /* turn on complete profiling */
+ smbprofile_state.config.do_count = true;
+ smbprofile_state.config.do_times = true;
+ DEBUG(1,("INFO: Full profiling turned ON from pid %d\n",
+ (int)procid_to_pid(src)));
+ break;
+ case 3: /* reset profile values */
+ ZERO_STRUCT(profile_p->values);
+ tdb_wipe_all(smbprofile_state.internal.db->tdb);
+ DEBUG(1,("INFO: Profiling values cleared from pid %d\n",
+ (int)procid_to_pid(src)));
+ break;
+ }
+}
+
+/****************************************************************************
+receive a set profile level message
+****************************************************************************/
+static void profile_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ int level;
+
+ if (data->length != sizeof(level)) {
+ DEBUG(0, ("got invalid profile message\n"));
+ return;
+ }
+
+ memcpy(&level, data->data, sizeof(level));
+ set_profile_level(level, &src);
+}
+
+/****************************************************************************
+receive a request profile level message
+****************************************************************************/
+static void reqprofile_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ int level;
+
+ level = 1;
+ if (smbprofile_state.config.do_count) {
+ level += 2;
+ }
+ if (smbprofile_state.config.do_times) {
+ level += 4;
+ }
+
+ DEBUG(1,("INFO: Received REQ_PROFILELEVEL message from PID %u\n",
+ (unsigned int)procid_to_pid(&src)));
+ messaging_send_buf(msg_ctx, src, MSG_PROFILELEVEL,
+ (uint8_t *)&level, sizeof(level));
+}
+
+/*******************************************************************
+ open the profiling shared memory area
+ ******************************************************************/
+bool profile_setup(struct messaging_context *msg_ctx, bool rdonly)
+{
+ char *db_name;
+ bool ok = false;
+ int rc;
+
+ if (smbprofile_state.internal.db != NULL) {
+ return true;
+ }
+
+ db_name = cache_path(talloc_tos(), "smbprofile.tdb");
+ if (db_name == NULL) {
+ return false;
+ }
+
+ smbprofile_state.internal.db = tdb_wrap_open(
+ NULL, db_name, 0,
+ rdonly ? 0 : TDB_CLEAR_IF_FIRST|TDB_MUTEX_LOCKING,
+ O_CREAT | (rdonly ? O_RDONLY : O_RDWR), 0644);
+ TALLOC_FREE(db_name);
+ if (smbprofile_state.internal.db == NULL) {
+ return false;
+ }
+
+ if (msg_ctx != NULL) {
+ messaging_register(msg_ctx, NULL, MSG_PROFILE,
+ profile_message);
+ messaging_register(msg_ctx, NULL, MSG_REQ_PROFILELEVEL,
+ reqprofile_message);
+ }
+
+ profile_p = &smbprofile_state.stats.global;
+
+ rc = smbprofile_magic(profile_p, &profile_p->magic);
+ if (rc != 0) {
+ goto out;
+ }
+
+ ok = true;
+out:
+
+ return ok;
+}
+
+void smbprofile_dump_setup(struct tevent_context *ev)
+{
+ TALLOC_FREE(smbprofile_state.internal.te);
+ smbprofile_state.internal.ev = ev;
+}
+
+static void smbprofile_dump_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ smbprofile_dump();
+}
+
+void smbprofile_dump_schedule_timer(void)
+{
+ struct timeval tv;
+
+ GetTimeOfDay(&tv);
+ tv.tv_sec += 1;
+
+ smbprofile_state.internal.te = tevent_add_timer(
+ smbprofile_state.internal.ev,
+ smbprofile_state.internal.ev,
+ tv,
+ smbprofile_dump_timer,
+ NULL);
+}
+
+static int profile_stats_parser(TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct profile_stats *s = private_data;
+
+ if (value.dsize != sizeof(struct profile_stats)) {
+ *s = (struct profile_stats) {};
+ return 0;
+ }
+
+ memcpy(s, value.dptr, value.dsize);
+ if (s->magic != profile_p->magic) {
+ *s = (struct profile_stats) {};
+ return 0;
+ }
+
+ return 0;
+}
+
+void smbprofile_dump(void)
+{
+ pid_t pid = 0;
+ TDB_DATA key = { .dptr = (uint8_t *)&pid, .dsize = sizeof(pid) };
+ struct profile_stats s = {};
+ int ret;
+#ifdef HAVE_GETRUSAGE
+ struct rusage rself;
+#endif /* HAVE_GETRUSAGE */
+
+ TALLOC_FREE(smbprofile_state.internal.te);
+
+ if (! (smbprofile_state.config.do_count ||
+ smbprofile_state.config.do_times)) {
+ return;
+ }
+
+ if (smbprofile_state.internal.db == NULL) {
+ return;
+ }
+
+ pid = tevent_cached_getpid();
+
+ ret = tdb_chainlock(smbprofile_state.internal.db->tdb, key);
+ if (ret != 0) {
+ return;
+ }
+
+ tdb_parse_record(smbprofile_state.internal.db->tdb,
+ key, profile_stats_parser, &s);
+
+ smbprofile_stats_accumulate(profile_p, &s);
+
+#ifdef HAVE_GETRUSAGE
+ ret = getrusage(RUSAGE_SELF, &rself);
+ if (ret != 0) {
+ ZERO_STRUCT(rself);
+ }
+
+ profile_p->values.cpu_user_stats.time =
+ (rself.ru_utime.tv_sec * 1000000) +
+ rself.ru_utime.tv_usec;
+ profile_p->values.cpu_system_stats.time =
+ (rself.ru_stime.tv_sec * 1000000) +
+ rself.ru_stime.tv_usec;
+#endif /* HAVE_GETRUSAGE */
+
+ tdb_store(smbprofile_state.internal.db->tdb, key,
+ (TDB_DATA) {
+ .dptr = (uint8_t *)profile_p,
+ .dsize = sizeof(*profile_p)
+ },
+ 0);
+
+ tdb_chainunlock(smbprofile_state.internal.db->tdb, key);
+ ZERO_STRUCT(profile_p->values);
+
+ return;
+}
+
+void smbprofile_cleanup(pid_t pid, pid_t dst)
+{
+ TDB_DATA key = { .dptr = (uint8_t *)&pid, .dsize = sizeof(pid) };
+ struct profile_stats s = {};
+ struct profile_stats acc = {};
+ int ret;
+
+ if (smbprofile_state.internal.db == NULL) {
+ return;
+ }
+
+ ret = tdb_chainlock(smbprofile_state.internal.db->tdb, key);
+ if (ret != 0) {
+ return;
+ }
+ ret = tdb_parse_record(smbprofile_state.internal.db->tdb,
+ key, profile_stats_parser, &s);
+ if (ret == -1) {
+ tdb_chainunlock(smbprofile_state.internal.db->tdb, key);
+ return;
+ }
+ tdb_delete(smbprofile_state.internal.db->tdb, key);
+ tdb_chainunlock(smbprofile_state.internal.db->tdb, key);
+
+ pid = dst;
+ ret = tdb_chainlock(smbprofile_state.internal.db->tdb, key);
+ if (ret != 0) {
+ return;
+ }
+ tdb_parse_record(smbprofile_state.internal.db->tdb,
+ key, profile_stats_parser, &acc);
+
+ /*
+ * We may have to fix the disconnect count
+ * in case the process died
+ */
+ s.values.disconnect_stats.count = s.values.connect_stats.count;
+
+ smbprofile_stats_accumulate(&acc, &s);
+
+ acc.magic = profile_p->magic;
+ tdb_store(smbprofile_state.internal.db->tdb, key,
+ (TDB_DATA) {
+ .dptr = (uint8_t *)&acc,
+ .dsize = sizeof(acc)
+ },
+ 0);
+
+ tdb_chainunlock(smbprofile_state.internal.db->tdb, key);
+}
+
+void smbprofile_collect(struct profile_stats *stats)
+{
+ if (smbprofile_state.internal.db == NULL) {
+ return;
+ }
+ smbprofile_collect_tdb(smbprofile_state.internal.db->tdb,
+ profile_p->magic,
+ stats);
+}
diff --git a/source3/profile/profile_dummy.c b/source3/profile/profile_dummy.c
new file mode 100644
index 0000000..5d3213e
--- /dev/null
+++ b/source3/profile/profile_dummy.c
@@ -0,0 +1,31 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * profile.c implementation if profiles are not enabled
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbprofile.h"
+
+bool profile_setup(struct messaging_context *msg_ctx, bool rdonly)
+{
+ return true;
+}
+
+void set_profile_level(int level, const struct server_id *src)
+{
+ DBG_NOTICE("INFO: Profiling support unavailable in this build.\n");
+}
diff --git a/source3/profile/profile_read.c b/source3/profile/profile_read.c
new file mode 100644
index 0000000..45a1980
--- /dev/null
+++ b/source3/profile/profile_read.c
@@ -0,0 +1,202 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * store smbd profiling information in shared memory
+ * Copyright (C) Andrew Tridgell 1999
+ * Copyright (C) James Peach 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <tdb.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include "lib/crypto/gnutls_helpers.h"
+#include "lib/util/byteorder.h"
+#include "source3/include/smbprofile.h"
+
+void smbprofile_stats_accumulate(struct profile_stats *acc,
+ const struct profile_stats *add)
+{
+#define SMBPROFILE_STATS_START
+#define SMBPROFILE_STATS_SECTION_START(name, display)
+#define SMBPROFILE_STATS_COUNT(name) \
+ do { \
+ acc->values.name##_stats.count += \
+ add->values.name##_stats.count; \
+ } while (0);
+#define SMBPROFILE_STATS_TIME(name) \
+ do { \
+ acc->values.name##_stats.time += \
+ add->values.name##_stats.time; \
+ } while (0);
+#define SMBPROFILE_STATS_BASIC(name) \
+ do { \
+ acc->values.name##_stats.count += \
+ add->values.name##_stats.count; \
+ acc->values.name##_stats.time += \
+ add->values.name##_stats.time; \
+ } while (0);
+#define SMBPROFILE_STATS_BYTES(name) \
+ do { \
+ acc->values.name##_stats.count += \
+ add->values.name##_stats.count; \
+ acc->values.name##_stats.time += \
+ add->values.name##_stats.time; \
+ acc->values.name##_stats.idle += \
+ add->values.name##_stats.idle; \
+ acc->values.name##_stats.bytes += \
+ add->values.name##_stats.bytes; \
+ } while (0);
+#define SMBPROFILE_STATS_IOBYTES(name) \
+ do { \
+ acc->values.name##_stats.count += \
+ add->values.name##_stats.count; \
+ acc->values.name##_stats.time += \
+ add->values.name##_stats.time; \
+ acc->values.name##_stats.idle += \
+ add->values.name##_stats.idle; \
+ acc->values.name##_stats.inbytes += \
+ add->values.name##_stats.inbytes; \
+ acc->values.name##_stats.outbytes += \
+ add->values.name##_stats.outbytes; \
+ } while (0);
+#define SMBPROFILE_STATS_SECTION_END
+#define SMBPROFILE_STATS_END
+ SMBPROFILE_STATS_ALL_SECTIONS
+#undef SMBPROFILE_STATS_START
+#undef SMBPROFILE_STATS_SECTION_START
+#undef SMBPROFILE_STATS_COUNT
+#undef SMBPROFILE_STATS_TIME
+#undef SMBPROFILE_STATS_BASIC
+#undef SMBPROFILE_STATS_BYTES
+#undef SMBPROFILE_STATS_IOBYTES
+#undef SMBPROFILE_STATS_SECTION_END
+#undef SMBPROFILE_STATS_END
+}
+
+int smbprofile_magic(const struct profile_stats *stats, uint64_t *_magic)
+{
+ uint8_t digest[gnutls_hash_get_len(GNUTLS_DIG_SHA1)];
+ gnutls_hash_hd_t hash_hnd = NULL;
+ int rc;
+
+ GNUTLS_FIPS140_SET_LAX_MODE();
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA1);
+ if (rc < 0) {
+ goto out;
+ }
+ rc = gnutls_hash(hash_hnd, stats, sizeof(*stats));
+
+#define __UPDATE(str) \
+ do { \
+ rc |= gnutls_hash(hash_hnd, str, strlen(str)); \
+ } while (0)
+#define SMBPROFILE_STATS_START
+#define SMBPROFILE_STATS_SECTION_START(name, display) \
+ do { \
+ __UPDATE(#name "+" #display); \
+ } while (0);
+#define SMBPROFILE_STATS_COUNT(name) \
+ do { \
+ __UPDATE(#name "+count"); \
+ } while (0);
+#define SMBPROFILE_STATS_TIME(name) \
+ do { \
+ __UPDATE(#name "+time"); \
+ } while (0);
+#define SMBPROFILE_STATS_BASIC(name) \
+ do { \
+ __UPDATE(#name "+count"); \
+ __UPDATE(#name "+time"); \
+ } while (0);
+#define SMBPROFILE_STATS_BYTES(name) \
+ do { \
+ __UPDATE(#name "+count"); \
+ __UPDATE(#name "+time"); \
+ __UPDATE(#name "+idle"); \
+ __UPDATE(#name "+bytes"); \
+ } while (0);
+#define SMBPROFILE_STATS_IOBYTES(name) \
+ do { \
+ __UPDATE(#name "+count"); \
+ __UPDATE(#name "+time"); \
+ __UPDATE(#name "+idle"); \
+ __UPDATE(#name "+inbytes"); \
+ __UPDATE(#name "+outbytes"); \
+ } while (0);
+#define SMBPROFILE_STATS_SECTION_END
+#define SMBPROFILE_STATS_END
+ SMBPROFILE_STATS_ALL_SECTIONS
+#undef __UPDATE
+#undef SMBPROFILE_STATS_START
+#undef SMBPROFILE_STATS_SECTION_START
+#undef SMBPROFILE_STATS_COUNT
+#undef SMBPROFILE_STATS_TIME
+#undef SMBPROFILE_STATS_BASIC
+#undef SMBPROFILE_STATS_BYTES
+#undef SMBPROFILE_STATS_IOBYTES
+#undef SMBPROFILE_STATS_SECTION_END
+#undef SMBPROFILE_STATS_END
+ if (rc != 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ goto out;
+ }
+
+ gnutls_hash_deinit(hash_hnd, digest);
+out:
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+
+ if (rc == 0) {
+ uint64_t magic = PULL_LE_U64(digest, 0);
+ if (magic == 0) {
+ magic = PULL_LE_U64(digest, 0);
+ }
+ *_magic = magic;
+ }
+
+ return rc;
+}
+
+static int smbprofile_collect_fn(struct tdb_context *tdb,
+ TDB_DATA key,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct profile_stats *acc = (struct profile_stats *)private_data;
+ const struct profile_stats *v;
+
+ if (value.dsize != sizeof(struct profile_stats)) {
+ return 0;
+ }
+
+ v = (const struct profile_stats *)value.dptr;
+
+ if (v->magic != acc->magic) {
+ return 0;
+ }
+
+ smbprofile_stats_accumulate(acc, v);
+ return 0;
+}
+
+void smbprofile_collect_tdb(struct tdb_context *tdb,
+ uint64_t magic,
+ struct profile_stats *stats)
+{
+ *stats = (struct profile_stats){.magic = magic};
+
+ tdb_traverse_read(tdb, smbprofile_collect_fn, stats);
+}
diff --git a/source3/registry/reg_api.c b/source3/registry/reg_api.c
new file mode 100644
index 0000000..ce5beb9
--- /dev/null
+++ b/source3/registry/reg_api.c
@@ -0,0 +1,1063 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007-2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Attempt to wrap the existing API in a more winreg.idl-like way */
+
+/*
+ * Here is a list of winreg.idl functions and corresponding implementations
+ * provided here:
+ *
+ * 0x00 winreg_OpenHKCR
+ * 0x01 winreg_OpenHKCU
+ * 0x02 winreg_OpenHKLM
+ * 0x03 winreg_OpenHKPD
+ * 0x04 winreg_OpenHKU
+ * 0x05 winreg_CloseKey
+ * 0x06 winreg_CreateKey reg_createkey
+ * 0x07 winreg_DeleteKey reg_deletekey
+ * 0x08 winreg_DeleteValue reg_deletevalue
+ * 0x09 winreg_EnumKey reg_enumkey
+ * 0x0a winreg_EnumValue reg_enumvalue
+ * 0x0b winreg_FlushKey
+ * 0x0c winreg_GetKeySecurity reg_getkeysecurity
+ * 0x0d winreg_LoadKey
+ * 0x0e winreg_NotifyChangeKeyValue
+ * 0x0f winreg_OpenKey reg_openkey
+ * 0x10 winreg_QueryInfoKey reg_queryinfokey
+ * 0x11 winreg_QueryValue reg_queryvalue
+ * 0x12 winreg_ReplaceKey
+ * 0x13 winreg_RestoreKey reg_restorekey
+ * 0x14 winreg_SaveKey reg_savekey
+ * 0x15 winreg_SetKeySecurity reg_setkeysecurity
+ * 0x16 winreg_SetValue reg_setvalue
+ * 0x17 winreg_UnLoadKey
+ * 0x18 winreg_InitiateSystemShutdown
+ * 0x19 winreg_AbortSystemShutdown
+ * 0x1a winreg_GetVersion reg_getversion
+ * 0x1b winreg_OpenHKCC
+ * 0x1c winreg_OpenHKDD
+ * 0x1d winreg_QueryMultipleValues reg_querymultiplevalues
+ * 0x1e winreg_InitiateSystemShutdownEx
+ * 0x1f winreg_SaveKeyEx
+ * 0x20 winreg_OpenHKPT
+ * 0x21 winreg_OpenHKPN
+ * 0x22 winreg_QueryMultipleValues2 reg_querymultiplevalues
+ *
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_api.h"
+#include "reg_cachehook.h"
+#include "reg_backend_db.h"
+#include "reg_dispatcher.h"
+#include "reg_objects.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "reg_parse_internal.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+
+/**********************************************************************
+ * Helper functions
+ **********************************************************************/
+
+static WERROR fill_value_cache(struct registry_key *key)
+{
+ WERROR werr;
+
+ if (key->values != NULL) {
+ if (!reg_values_need_update(key->key, key->values)) {
+ return WERR_OK;
+ }
+ }
+
+ TALLOC_FREE(key->values);
+ werr = regval_ctr_init(key, &(key->values));
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ if (fetch_reg_values(key->key, key->values) == -1) {
+ TALLOC_FREE(key->values);
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR fill_subkey_cache(struct registry_key *key)
+{
+ WERROR werr;
+
+ if (key->subkeys != NULL) {
+ if (!reg_subkeys_need_update(key->key, key->subkeys)) {
+ return WERR_OK;
+ }
+ }
+
+ TALLOC_FREE(key->subkeys);
+ werr = regsubkey_ctr_init(key, &(key->subkeys));
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ if (fetch_reg_keys(key->key, key->subkeys) == -1) {
+ TALLOC_FREE(key->subkeys);
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ return WERR_OK;
+}
+
+static int regkey_destructor(struct registry_key_handle *key)
+{
+ return regdb_close();
+}
+
+static WERROR regkey_open_onelevel(TALLOC_CTX *mem_ctx,
+ struct registry_key *parent,
+ const char *name,
+ const struct security_token *token,
+ uint32_t access_desired,
+ struct registry_key **pregkey)
+{
+ WERROR result;
+ struct registry_key *regkey;
+ struct registry_key_handle *key;
+
+ DEBUG(7,("regkey_open_onelevel: name = [%s]\n", name));
+
+ SMB_ASSERT(strchr(name, '\\') == NULL);
+
+ if (!(regkey = talloc_zero(mem_ctx, struct registry_key)) ||
+ !(regkey->token = security_token_duplicate(regkey, token)) ||
+ !(regkey->key = talloc_zero(regkey, struct registry_key_handle)))
+ {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = regdb_open();
+ if (!(W_ERROR_IS_OK(result))) {
+ goto done;
+ }
+
+ key = regkey->key;
+ talloc_set_destructor(key, regkey_destructor);
+
+ /* initialization */
+
+ key->type = REG_KEY_GENERIC;
+
+ if (name[0] == '\0') {
+ /*
+ * Open a copy of the parent key
+ */
+ if (!parent) {
+ result = WERR_FILE_NOT_FOUND;
+ goto done;
+ }
+ key->name = talloc_strdup(key, parent->key->name);
+ }
+ else {
+ /*
+ * Normal subkey open
+ */
+ key->name = talloc_asprintf(key, "%s%s%s",
+ parent ? parent->key->name : "",
+ parent ? "\\": "",
+ name);
+ }
+
+ if (key->name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* Tag this as a Performance Counter Key */
+
+ if( strncasecmp_m(key->name, KEY_HKPD, strlen(KEY_HKPD)) == 0 )
+ key->type = REG_KEY_HKPD;
+
+ /* Look up the table of registry I/O operations */
+
+ key->ops = reghook_cache_find( key->name );
+ if (key->ops == NULL) {
+ DEBUG(0,("reg_open_onelevel: Failed to assign "
+ "registry_ops to [%s]\n", key->name ));
+ result = WERR_FILE_NOT_FOUND;
+ goto done;
+ }
+
+ /* FIXME: Existence is currently checked by fetching the subkeys */
+
+ result = fill_subkey_cache(regkey);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ if ( !regkey_access_check( key, access_desired, &key->access_granted,
+ token ) ) {
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ *pregkey = regkey;
+ result = WERR_OK;
+
+done:
+ if ( !W_ERROR_IS_OK(result) ) {
+ TALLOC_FREE(regkey);
+ }
+
+ return result;
+}
+
+WERROR reg_openhive(TALLOC_CTX *mem_ctx, const char *hive,
+ uint32_t desired_access,
+ const struct security_token *token,
+ struct registry_key **pkey)
+{
+ const struct hive_info *hi;
+ SMB_ASSERT(hive != NULL);
+ SMB_ASSERT(strchr(hive, '\\') == NULL);
+
+ hi = hive_info(hive);
+ if (hi == NULL) {
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ return regkey_open_onelevel(mem_ctx, NULL, hi->short_name, token,
+ desired_access, pkey);
+}
+
+
+/**********************************************************************
+ * The API functions
+ **********************************************************************/
+
+WERROR reg_openkey(TALLOC_CTX *mem_ctx, struct registry_key *parent,
+ const char *name, uint32_t desired_access,
+ struct registry_key **pkey)
+{
+ struct registry_key *direct_parent = parent;
+ WERROR err;
+ char *p, *path;
+ size_t len;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ path = talloc_strdup(frame, name);
+ if (path == NULL) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto error;
+ }
+
+ len = strlen(path);
+
+ if ((len > 0) && (path[len-1] == '\\')) {
+ path[len-1] = '\0';
+ }
+
+ while ((p = strchr(path, '\\')) != NULL) {
+ char *name_component;
+ struct registry_key *tmp;
+
+ name_component = talloc_strndup(frame, path, (p - path));
+ if (name_component == NULL) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto error;
+ }
+
+ err = regkey_open_onelevel(frame, direct_parent,
+ name_component, parent->token,
+ KEY_ENUMERATE_SUB_KEYS, &tmp);
+
+ if (!W_ERROR_IS_OK(err)) {
+ goto error;
+ }
+
+ direct_parent = tmp;
+ path = p+1;
+ }
+
+ err = regkey_open_onelevel(mem_ctx, direct_parent, path, parent->token,
+ desired_access, pkey);
+
+error:
+ talloc_free(frame);
+ return err;
+}
+
+WERROR reg_enumkey(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ uint32_t idx, char **name, NTTIME *last_write_time)
+{
+ WERROR err;
+
+ if (!(key->key->access_granted & KEY_ENUMERATE_SUB_KEYS)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ err = fill_subkey_cache(key);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ if (idx >= regsubkey_ctr_numkeys(key->subkeys)) {
+ return WERR_NO_MORE_ITEMS;
+ }
+
+ if (!(*name = talloc_strdup(mem_ctx,
+ regsubkey_ctr_specific_key(key->subkeys, idx))))
+ {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (last_write_time) {
+ *last_write_time = 0;
+ }
+
+ return WERR_OK;
+}
+
+WERROR reg_enumvalue(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ uint32_t idx, char **pname, struct registry_value **pval)
+{
+ struct registry_value *val;
+ struct regval_blob *blob;
+ WERROR err;
+
+ if (!(key->key->access_granted & KEY_QUERY_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ err = fill_value_cache(key);
+ if (!(W_ERROR_IS_OK(err))) {
+ return err;
+ }
+
+ if (idx >= regval_ctr_numvals(key->values)) {
+ return WERR_NO_MORE_ITEMS;
+ }
+
+ blob = regval_ctr_specific_value(key->values, idx);
+
+ val = talloc_zero(mem_ctx, struct registry_value);
+ if (val == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ val->type = regval_type(blob);
+ val->data = data_blob_talloc(mem_ctx, regval_data_p(blob), regval_size(blob));
+
+ if (pname
+ && !(*pname = talloc_strdup(
+ mem_ctx, regval_name(blob)))) {
+ TALLOC_FREE(val);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *pval = val;
+ return WERR_OK;
+}
+
+static WERROR reg_enumvalue_nocachefill(TALLOC_CTX *mem_ctx,
+ struct registry_key *key,
+ uint32_t idx, char **pname,
+ struct registry_value **pval)
+{
+ struct registry_value *val;
+ struct regval_blob *blob;
+
+ if (!(key->key->access_granted & KEY_QUERY_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (idx >= regval_ctr_numvals(key->values)) {
+ return WERR_NO_MORE_ITEMS;
+ }
+
+ blob = regval_ctr_specific_value(key->values, idx);
+
+ val = talloc_zero(mem_ctx, struct registry_value);
+ if (val == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ val->type = regval_type(blob);
+ val->data = data_blob_talloc(mem_ctx, regval_data_p(blob), regval_size(blob));
+
+ if (pname
+ && !(*pname = talloc_strdup(
+ mem_ctx, regval_name(blob)))) {
+ TALLOC_FREE(val);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *pval = val;
+ return WERR_OK;
+}
+
+WERROR reg_queryvalue(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ const char *name, struct registry_value **pval)
+{
+ WERROR err;
+ uint32_t i;
+
+ if (!(key->key->access_granted & KEY_QUERY_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!(W_ERROR_IS_OK(err = fill_value_cache(key)))) {
+ return err;
+ }
+
+ for (i=0; i < regval_ctr_numvals(key->values); i++) {
+ struct regval_blob *blob;
+ blob = regval_ctr_specific_value(key->values, i);
+ if (strequal(regval_name(blob), name)) {
+ /*
+ * don't use reg_enumvalue here:
+ * re-reading the values from the disk
+ * would change the indexing and break
+ * this function.
+ */
+ return reg_enumvalue_nocachefill(mem_ctx, key, i,
+ NULL, pval);
+ }
+ }
+
+ return WERR_FILE_NOT_FOUND;
+}
+
+WERROR reg_querymultiplevalues(TALLOC_CTX *mem_ctx,
+ struct registry_key *key,
+ uint32_t num_names,
+ const char **names,
+ uint32_t *pnum_vals,
+ struct registry_value **pvals)
+{
+ WERROR err;
+ uint32_t i, n, found = 0;
+ struct registry_value *vals;
+
+ if (num_names == 0) {
+ return WERR_OK;
+ }
+
+ if (!(key->key->access_granted & KEY_QUERY_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!(W_ERROR_IS_OK(err = fill_value_cache(key)))) {
+ return err;
+ }
+
+ vals = talloc_zero_array(mem_ctx, struct registry_value, num_names);
+ if (vals == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (n=0; n < num_names; n++) {
+ for (i=0; i < regval_ctr_numvals(key->values); i++) {
+ struct regval_blob *blob;
+ blob = regval_ctr_specific_value(key->values, i);
+ if (strequal(regval_name(blob), names[n])) {
+ struct registry_value *v;
+ err = reg_enumvalue(mem_ctx, key, i, NULL, &v);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+ vals[n] = *v;
+ found++;
+ }
+ }
+ }
+
+ *pvals = vals;
+ *pnum_vals = found;
+
+ return WERR_OK;
+}
+
+WERROR reg_queryinfokey(struct registry_key *key, uint32_t *num_subkeys,
+ uint32_t *max_subkeylen, uint32_t *max_subkeysize,
+ uint32_t *num_values, uint32_t *max_valnamelen,
+ uint32_t *max_valbufsize, uint32_t *secdescsize,
+ NTTIME *last_changed_time)
+{
+ uint32_t i, max_size;
+ size_t max_len;
+ TALLOC_CTX *mem_ctx;
+ WERROR err;
+ struct security_descriptor *secdesc;
+
+ if (!(key->key->access_granted & KEY_QUERY_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!W_ERROR_IS_OK(fill_subkey_cache(key)) ||
+ !W_ERROR_IS_OK(fill_value_cache(key))) {
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ max_len = 0;
+ for (i=0; i< regsubkey_ctr_numkeys(key->subkeys); i++) {
+ max_len = MAX(max_len,
+ strlen(regsubkey_ctr_specific_key(key->subkeys, i)));
+ }
+
+ *num_subkeys = regsubkey_ctr_numkeys(key->subkeys);
+ *max_subkeylen = max_len;
+ *max_subkeysize = 0; /* Class length? */
+
+ max_len = 0;
+ max_size = 0;
+ for (i=0; i < regval_ctr_numvals(key->values); i++) {
+ struct regval_blob *blob;
+ blob = regval_ctr_specific_value(key->values, i);
+ max_len = MAX(max_len, strlen(regval_name(blob)));
+ max_size = MAX(max_size, regval_size(blob));
+ }
+
+ *num_values = regval_ctr_numvals(key->values);
+ *max_valnamelen = max_len;
+ *max_valbufsize = max_size;
+
+ if (!(mem_ctx = talloc_new(key))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ err = regkey_get_secdesc(mem_ctx, key->key, &secdesc);
+ if (!W_ERROR_IS_OK(err)) {
+ TALLOC_FREE(mem_ctx);
+ return err;
+ }
+
+ *secdescsize = ndr_size_security_descriptor(secdesc, 0);
+ TALLOC_FREE(mem_ctx);
+
+ *last_changed_time = 0;
+
+ return WERR_OK;
+}
+
+WERROR reg_createkey(TALLOC_CTX *ctx, struct registry_key *parent,
+ const char *subkeypath, uint32_t desired_access,
+ struct registry_key **pkey,
+ enum winreg_CreateAction *paction)
+{
+ struct registry_key *key = parent;
+ TALLOC_CTX *mem_ctx;
+ char *path, *end;
+ WERROR err;
+ uint32_t access_granted;
+
+ mem_ctx = talloc_new(ctx);
+ if (mem_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = talloc_strdup(mem_ctx, subkeypath);
+ if (path == NULL) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ err = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_createkey: failed to start transaction: %s\n",
+ win_errstr(err)));
+ goto done;
+ }
+
+ while ((end = strchr(path, '\\')) != NULL) {
+ struct registry_key *tmp;
+ enum winreg_CreateAction action;
+
+ *end = '\0';
+
+ err = reg_createkey(mem_ctx, key, path,
+ KEY_ENUMERATE_SUB_KEYS, &tmp, &action);
+ if (!W_ERROR_IS_OK(err)) {
+ goto trans_done;
+ }
+
+ if (key != parent) {
+ TALLOC_FREE(key);
+ }
+
+ key = tmp;
+ path = end+1;
+ }
+
+ /*
+ * At this point, "path" contains the one-element subkey of "key". We
+ * can try to open it.
+ */
+
+ err = reg_openkey(ctx, key, path, desired_access, pkey);
+ if (W_ERROR_IS_OK(err)) {
+ if (paction != NULL) {
+ *paction = REG_OPENED_EXISTING_KEY;
+ }
+ goto trans_done;
+ }
+
+ if (!W_ERROR_EQUAL(err, WERR_FILE_NOT_FOUND)) {
+ /*
+ * Something but "notfound" has happened, so bail out
+ */
+ goto trans_done;
+ }
+
+ /*
+ * We may (e.g. in the iteration) have opened the key with ENUM_SUBKEY.
+ * Instead of re-opening the key with CREATE_SUB_KEY, we simply
+ * duplicate the access check here and skip the expensive full open.
+ */
+ if (!regkey_access_check(key->key, KEY_CREATE_SUB_KEY, &access_granted,
+ key->token))
+ {
+ err = WERR_ACCESS_DENIED;
+ goto trans_done;
+ }
+
+ /*
+ * Actually create the subkey
+ */
+
+ err = create_reg_subkey(key->key, path);
+ if (!W_ERROR_IS_OK(err)) {
+ goto trans_done;
+ }
+
+ /*
+ * Now open the newly created key
+ */
+
+ err = reg_openkey(ctx, key, path, desired_access, pkey);
+ if (W_ERROR_IS_OK(err) && (paction != NULL)) {
+ *paction = REG_CREATED_NEW_KEY;
+ }
+
+trans_done:
+ if (W_ERROR_IS_OK(err)) {
+ err = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_createkey: Error committing transaction: %s\n", win_errstr(err)));
+ }
+ } else {
+ WERROR err1 = regdb_transaction_cancel();
+ if (!W_ERROR_IS_OK(err1)) {
+ DEBUG(0, ("reg_createkey: Error cancelling transaction: %s\n", win_errstr(err1)));
+ }
+ }
+
+ done:
+ TALLOC_FREE(mem_ctx);
+ return err;
+}
+
+static WERROR reg_deletekey_internal(TALLOC_CTX *mem_ctx,
+ struct registry_key *parent,
+ const char *path, bool lazy)
+{
+ WERROR err;
+ char *name, *end;
+ struct registry_key *key;
+ name = talloc_strdup(mem_ctx, path);
+ if (name == NULL) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* no subkeys - proceed with delete */
+ end = strrchr(name, '\\');
+ if (end != NULL) {
+ *end = '\0';
+
+ err = reg_openkey(mem_ctx, parent, name,
+ KEY_CREATE_SUB_KEY, &key);
+ W_ERROR_NOT_OK_GOTO_DONE(err);
+
+ parent = key;
+ name = end+1;
+ }
+
+ if (name[0] == '\0') {
+ err = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ err = delete_reg_subkey(parent->key, name, lazy);
+
+done:
+ return err;
+}
+
+WERROR reg_deletekey(struct registry_key *parent, const char *path)
+{
+ WERROR err;
+ struct registry_key *key;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ /* check if the key has subkeys */
+ err = reg_openkey(mem_ctx, parent, path, REG_KEY_READ, &key);
+ W_ERROR_NOT_OK_GOTO_DONE(err);
+
+ err = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_deletekey: Error starting transaction: %s\n",
+ win_errstr(err)));
+ goto done;
+ }
+
+ err = fill_subkey_cache(key);
+ if (!W_ERROR_IS_OK(err)) {
+ goto trans_done;
+ }
+
+ if (regsubkey_ctr_numkeys(key->subkeys) > 0) {
+ err = WERR_ACCESS_DENIED;
+ goto trans_done;
+ }
+ err = reg_deletekey_internal(mem_ctx, parent, path, false);
+
+trans_done:
+ if (W_ERROR_IS_OK(err)) {
+ err = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_deletekey: Error committing transaction: %s\n", win_errstr(err)));
+ }
+ } else {
+ WERROR err1 = regdb_transaction_cancel();
+ if (!W_ERROR_IS_OK(err1)) {
+ DEBUG(0, ("reg_deletekey: Error cancelling transaction: %s\n", win_errstr(err1)));
+ }
+ }
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return err;
+}
+
+
+WERROR reg_setvalue(struct registry_key *key, const char *name,
+ const struct registry_value *val)
+{
+ struct regval_blob *existing;
+ WERROR err;
+ int res;
+
+ if (!(key->key->access_granted & KEY_SET_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ err = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_setvalue: Failed to start transaction: %s\n",
+ win_errstr(err)));
+ return err;
+ }
+
+ err = fill_value_cache(key);
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_setvalue: Error filling value cache: %s\n", win_errstr(err)));
+ goto done;
+ }
+
+ existing = regval_ctr_getvalue(key->values, name);
+
+ if ((existing != NULL) &&
+ (regval_size(existing) == val->data.length) &&
+ (memcmp(regval_data_p(existing), val->data.data,
+ val->data.length) == 0))
+ {
+ err = WERR_OK;
+ goto done;
+ }
+
+ res = regval_ctr_addvalue(key->values, name, val->type,
+ val->data.data, val->data.length);
+
+ if (res == 0) {
+ TALLOC_FREE(key->values);
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ if (!store_reg_values(key->key, key->values)) {
+ TALLOC_FREE(key->values);
+ DEBUG(0, ("reg_setvalue: store_reg_values failed\n"));
+ err = WERR_REGISTRY_IO_FAILED;
+ goto done;
+ }
+
+ err = WERR_OK;
+
+done:
+ if (W_ERROR_IS_OK(err)) {
+ err = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_setvalue: Error committing transaction: %s\n", win_errstr(err)));
+ }
+ } else {
+ WERROR err1 = regdb_transaction_cancel();
+ if (!W_ERROR_IS_OK(err1)) {
+ DEBUG(0, ("reg_setvalue: Error cancelling transaction: %s\n", win_errstr(err1)));
+ }
+ }
+
+ return err;
+}
+
+static WERROR reg_value_exists(struct registry_key *key, const char *name)
+{
+ struct regval_blob *blob;
+
+ blob = regval_ctr_getvalue(key->values, name);
+
+ if (blob == NULL) {
+ return WERR_FILE_NOT_FOUND;
+ } else {
+ return WERR_OK;
+ }
+}
+
+WERROR reg_deletevalue(struct registry_key *key, const char *name)
+{
+ WERROR err;
+
+ if (!(key->key->access_granted & KEY_SET_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ err = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_deletevalue: Failed to start transaction: %s\n",
+ win_errstr(err)));
+ return err;
+ }
+
+ err = fill_value_cache(key);
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_deletevalue; Error filling value cache: %s\n",
+ win_errstr(err)));
+ goto done;
+ }
+
+ err = reg_value_exists(key, name);
+ if (!W_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ regval_ctr_delvalue(key->values, name);
+
+ if (!store_reg_values(key->key, key->values)) {
+ TALLOC_FREE(key->values);
+ err = WERR_REGISTRY_IO_FAILED;
+ DEBUG(0, ("reg_deletevalue: store_reg_values failed\n"));
+ goto done;
+ }
+
+ err = WERR_OK;
+
+done:
+ if (W_ERROR_IS_OK(err)) {
+ err = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(err)) {
+ DEBUG(0, ("reg_deletevalue: Error committing transaction: %s\n", win_errstr(err)));
+ }
+ } else {
+ WERROR err1 = regdb_transaction_cancel();
+ if (!W_ERROR_IS_OK(err1)) {
+ DEBUG(0, ("reg_deletevalue: Error cancelling transaction: %s\n", win_errstr(err1)));
+ }
+ }
+
+ return err;
+}
+
+WERROR reg_getkeysecurity(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ struct security_descriptor **psecdesc)
+{
+ return regkey_get_secdesc(mem_ctx, key->key, psecdesc);
+}
+
+WERROR reg_setkeysecurity(struct registry_key *key,
+ struct security_descriptor *psecdesc)
+{
+ return regkey_set_secdesc(key->key, psecdesc);
+}
+
+WERROR reg_getversion(uint32_t *version)
+{
+ if (version == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ *version = 0x00000005; /* Windows 2000 registry API version */
+ return WERR_OK;
+}
+
+/**********************************************************************
+ * Higher level utility functions
+ **********************************************************************/
+
+WERROR reg_deleteallvalues(struct registry_key *key)
+{
+ WERROR err;
+ uint32_t i;
+
+ if (!(key->key->access_granted & KEY_SET_VALUE)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!W_ERROR_IS_OK(err = fill_value_cache(key))) {
+ return err;
+ }
+
+ for (i=0; i < regval_ctr_numvals(key->values); i++) {
+ struct regval_blob *blob;
+ blob = regval_ctr_specific_value(key->values, i);
+ regval_ctr_delvalue(key->values, regval_name(blob));
+ }
+
+ if (!store_reg_values(key->key, key->values)) {
+ TALLOC_FREE(key->values);
+ return WERR_REGISTRY_IO_FAILED;
+ }
+
+ return WERR_OK;
+}
+
+/*
+ * Utility function to delete a registry key with all its subkeys.
+ * Note that reg_deletekey returns ACCESS_DENIED when called on a
+ * key that has subkeys.
+ */
+static WERROR reg_deletekey_recursive_internal(struct registry_key *parent,
+ const char *path,
+ bool del_key, bool lazy)
+{
+ WERROR werr;
+ struct registry_key *key;
+ char *subkey_name = NULL;
+ uint32_t i;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ DEBUG(5, ("reg_deletekey_recursive_internal: deleting '%s' from '%s'\n",
+ path, parent->key->name));
+
+ /* recurse through subkeys first */
+ werr = reg_openkey(mem_ctx, parent, path, REG_KEY_ALL, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(3, ("reg_deletekey_recursive_internal: error opening "
+ "subkey '%s' of '%s': '%s'\n",
+ path, parent->key->name, win_errstr(werr)));
+ goto done;
+ }
+
+ werr = fill_subkey_cache(key);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ /*
+ * loop from top to bottom for performance:
+ * this way, we need to rehash the regsubkey containers less
+ */
+ for (i = regsubkey_ctr_numkeys(key->subkeys) ; i > 0; i--) {
+ subkey_name = regsubkey_ctr_specific_key(key->subkeys, i-1);
+ werr = reg_deletekey_recursive_internal(key, subkey_name, true, del_key);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+ }
+
+ if (del_key) {
+ /* now delete the actual key */
+ werr = reg_deletekey_internal(mem_ctx, parent, path, lazy);
+ }
+
+done:
+
+ DEBUG(5, ("reg_deletekey_recursive_internal: done deleting '%s' from "
+ "'%s': %s\n",
+ path, parent->key->name, win_errstr(werr)));
+ TALLOC_FREE(mem_ctx);
+ return werr;
+}
+
+static WERROR reg_deletekey_recursive_trans(struct registry_key *parent,
+ const char *path,
+ bool del_key)
+{
+ WERROR werr;
+
+ werr = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("reg_deletekey_recursive_trans: "
+ "error starting transaction: %s\n",
+ win_errstr(werr)));
+ return werr;
+ }
+
+ werr = reg_deletekey_recursive_internal(parent, path, del_key, false);
+
+ if (!W_ERROR_IS_OK(werr)) {
+ WERROR werr2;
+ DEBUG(W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND) ? 5 : 1,
+ (__location__ ": failed to delete key '%s' from key "
+ "'%s': %s\n", path, parent->key->name,
+ win_errstr(werr)));
+
+ werr2 = regdb_transaction_cancel();
+ if (!W_ERROR_IS_OK(werr2)) {
+ DEBUG(0, ("reg_deletekey_recursive_trans: "
+ "error cancelling transaction: %s\n",
+ win_errstr(werr2)));
+ /*
+ * return the original werr or the
+ * error from cancelling the transaction?
+ */
+ }
+ } else {
+ werr = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("reg_deletekey_recursive_trans: "
+ "error committing transaction: %s\n",
+ win_errstr(werr)));
+ } else {
+ DEBUG(5, ("reg_deletekey_recursive_trans: deleted key '%s' from '%s'\n",
+ path, parent->key->name));
+
+ }
+ }
+
+ return werr;
+}
+
+WERROR reg_deletekey_recursive(struct registry_key *parent,
+ const char *path)
+{
+ return reg_deletekey_recursive_trans(parent, path, true);
+}
+
+WERROR reg_deletesubkeys_recursive(struct registry_key *parent,
+ const char *path)
+{
+ return reg_deletekey_recursive_trans(parent, path, false);
+}
+
diff --git a/source3/registry/reg_api.h b/source3/registry/reg_api.h
new file mode 100644
index 0000000..535e4ff
--- /dev/null
+++ b/source3/registry/reg_api.h
@@ -0,0 +1,70 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Virtual Windows Registry Layer
+ *
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007-2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_API_H
+#define _REG_API_H
+
+WERROR reg_openhive(TALLOC_CTX *mem_ctx, const char *hive,
+ uint32_t desired_access,
+ const struct security_token *token,
+ struct registry_key **pkey);
+WERROR reg_openkey(TALLOC_CTX *mem_ctx, struct registry_key *parent,
+ const char *name, uint32_t desired_access,
+ struct registry_key **pkey);
+WERROR reg_enumkey(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ uint32_t idx, char **name, NTTIME *last_write_time);
+WERROR reg_enumvalue(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ uint32_t idx, char **pname, struct registry_value **pval);
+WERROR reg_queryvalue(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ const char *name, struct registry_value **pval);
+WERROR reg_querymultiplevalues(TALLOC_CTX *mem_ctx,
+ struct registry_key *key,
+ uint32_t num_names,
+ const char **names,
+ uint32_t *pnum_vals,
+ struct registry_value **pvals);
+WERROR reg_queryinfokey(struct registry_key *key, uint32_t *num_subkeys,
+ uint32_t *max_subkeylen, uint32_t *max_subkeysize,
+ uint32_t *num_values, uint32_t *max_valnamelen,
+ uint32_t *max_valbufsize, uint32_t *secdescsize,
+ NTTIME *last_changed_time);
+WERROR reg_createkey(TALLOC_CTX *ctx, struct registry_key *parent,
+ const char *subkeypath, uint32_t desired_access,
+ struct registry_key **pkey,
+ enum winreg_CreateAction *paction);
+WERROR reg_deletekey(struct registry_key *parent, const char *path);
+WERROR reg_setvalue(struct registry_key *key, const char *name,
+ const struct registry_value *val);
+WERROR reg_deletevalue(struct registry_key *key, const char *name);
+WERROR reg_getkeysecurity(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ struct security_descriptor **psecdesc);
+WERROR reg_setkeysecurity(struct registry_key *key,
+ struct security_descriptor *psecdesc);
+WERROR reg_getversion(uint32_t *version);
+WERROR reg_deleteallvalues(struct registry_key *key);
+WERROR reg_deletekey_recursive(struct registry_key *parent,
+ const char *path);
+WERROR reg_deletesubkeys_recursive(struct registry_key *parent,
+ const char *path);
+
+
+#endif /* _REG_API_H */
diff --git a/source3/registry/reg_api_util.c b/source3/registry/reg_api_util.c
new file mode 100644
index 0000000..2eca74e
--- /dev/null
+++ b/source3/registry/reg_api_util.c
@@ -0,0 +1,84 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007-2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Higher level utility functions on top of reg_api.c
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_api.h"
+#include "reg_api_util.h"
+#include "libcli/registry/util_reg.h"
+
+/**
+ * Utility function to open a complete registry path including the hive prefix.
+ */
+WERROR reg_open_path(TALLOC_CTX *mem_ctx, const char *orig_path,
+ uint32_t desired_access, const struct security_token *token,
+ struct registry_key **pkey)
+{
+ struct registry_key *hive, *key;
+ char *path, *p;
+ WERROR err;
+
+ if (!(path = SMB_STRDUP(orig_path))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p = strchr(path, '\\');
+
+ if ((p == NULL) || (p[1] == '\0')) {
+ /*
+ * No key behind the hive, just return the hive
+ */
+
+ err = reg_openhive(mem_ctx, path, desired_access, token,
+ &hive);
+ if (!W_ERROR_IS_OK(err)) {
+ SAFE_FREE(path);
+ return err;
+ }
+ SAFE_FREE(path);
+ *pkey = hive;
+ return WERR_OK;
+ }
+
+ *p = '\0';
+
+ err = reg_openhive(mem_ctx, path, KEY_ENUMERATE_SUB_KEYS, token,
+ &hive);
+ if (!W_ERROR_IS_OK(err)) {
+ SAFE_FREE(path);
+ return err;
+ }
+
+ err = reg_openkey(mem_ctx, hive, p+1, desired_access, &key);
+
+ TALLOC_FREE(hive);
+ SAFE_FREE(path);
+
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ *pkey = key;
+ return WERR_OK;
+}
diff --git a/source3/registry/reg_api_util.h b/source3/registry/reg_api_util.h
new file mode 100644
index 0000000..67c77a0
--- /dev/null
+++ b/source3/registry/reg_api_util.h
@@ -0,0 +1,35 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007-2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Higher level utility functions on top of reg_api.c
+ */
+
+#ifndef _REG_API_UTIL_H
+#define _REG_API_UTIL_H
+
+/**
+ * Utility function to open a complete registry path including the hive prefix.
+ */
+WERROR reg_open_path(TALLOC_CTX *mem_ctx, const char *orig_path,
+ uint32_t desired_access, const struct security_token *token,
+ struct registry_key **pkey);
+
+#endif /* _REG_API_UTIL_H */
diff --git a/source3/registry/reg_backend_current_version.c b/source3/registry/reg_backend_current_version.c
new file mode 100644
index 0000000..adf304e
--- /dev/null
+++ b/source3/registry/reg_backend_current_version.c
@@ -0,0 +1,78 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * CurrentVersion registry backend.
+ *
+ * This is a virtual overlay, dynamically presenting version information.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_util_internal.h"
+#include "reg_objects.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops;
+
+#define KEY_CURRENT_VERSION_NORM "HKLM\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION"
+
+static int current_version_fetch_values(const char *key, struct regval_ctr *values)
+{
+ const char *sysroot_string = "c:\\Windows";
+ fstring sysversion;
+ char *path = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ path = talloc_strdup(ctx, key);
+ if (path == NULL) {
+ return -1;
+ }
+ path = normalize_reg_path(ctx, path);
+ if (path == NULL) {
+ return -1;
+ }
+
+ if (strncmp(path, KEY_CURRENT_VERSION_NORM, strlen(path)) != 0) {
+ return regdb_ops.fetch_values(key, values);
+ }
+
+ regval_ctr_addvalue_sz(values, "SystemRoot", sysroot_string);
+
+ fstr_sprintf(sysversion, "%d.%d", SAMBA_MAJOR_NBT_ANNOUNCE_VERSION,
+ SAMBA_MINOR_NBT_ANNOUNCE_VERSION);
+
+ regval_ctr_addvalue_sz(values, "CurrentVersion", sysversion);
+
+ return regval_ctr_numvals(values);
+}
+
+static int current_version_fetch_subkeys(const char *key,
+ struct regsubkey_ctr *subkey_ctr)
+{
+ return regdb_ops.fetch_subkeys(key, subkey_ctr);
+}
+
+struct registry_ops current_version_reg_ops = {
+ .fetch_values = current_version_fetch_values,
+ .fetch_subkeys = current_version_fetch_subkeys,
+};
diff --git a/source3/registry/reg_backend_db.c b/source3/registry/reg_backend_db.c
new file mode 100644
index 0000000..44b3b5f
--- /dev/null
+++ b/source3/registry/reg_backend_db.c
@@ -0,0 +1,2270 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2007-2011
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of internal registry database functions. */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "registry.h"
+#include "reg_db.h"
+#include "reg_util_internal.h"
+#include "reg_parse_internal.h"
+#include "reg_backend_db.h"
+#include "reg_objects.h"
+#include "nt_printing.h"
+#include "util_tdb.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/secdesc.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+#define REGDB_VERSION_KEYNAME "INFO/version"
+
+static struct db_context *regdb = NULL;
+static int regdb_refcount;
+
+static bool regdb_key_exists(struct db_context *db, const char *key);
+static WERROR regdb_fetch_keys_internal(struct db_context *db, const char *key,
+ struct regsubkey_ctr *ctr);
+static bool regdb_store_keys_internal(struct db_context *db, const char *key,
+ struct regsubkey_ctr *ctr);
+static int regdb_fetch_values_internal(struct db_context *db, const char* key,
+ struct regval_ctr *values);
+static NTSTATUS regdb_store_values_internal(struct db_context *db, const char *key,
+ struct regval_ctr *values);
+static WERROR regdb_store_subkey_list(struct db_context *db, const char *parent,
+ const char *key);
+
+static WERROR regdb_create_basekey(struct db_context *db, const char *key);
+static WERROR regdb_create_subkey_internal(struct db_context *db,
+ const char *key,
+ const char *subkey);
+
+
+struct regdb_trans_ctx {
+ NTSTATUS (*action)(struct db_context *, void *);
+ void *private_data;
+};
+
+static NTSTATUS regdb_trans_do_action(struct db_context *db, void *private_data)
+{
+ NTSTATUS status;
+ int32_t version_id;
+ struct regdb_trans_ctx *ctx = (struct regdb_trans_ctx *)private_data;
+
+ status = dbwrap_fetch_int32_bystring(db, REGDB_VERSION_KEYNAME,
+ &version_id);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("ERROR: could not fetch registry db version: %s. "
+ "Denying access.\n", nt_errstr(status)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (version_id != REGDB_CODE_VERSION) {
+ DEBUG(0, ("ERROR: changed registry version %d found while "
+ "trying to write to the registry. Version %d "
+ "expected. Denying access.\n",
+ version_id, REGDB_CODE_VERSION));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = ctx->action(db, ctx->private_data);
+ return status;
+}
+
+static WERROR regdb_trans_do(struct db_context *db,
+ NTSTATUS (*action)(struct db_context *, void *),
+ void *private_data)
+{
+ NTSTATUS status;
+ struct regdb_trans_ctx ctx;
+
+
+ ctx.action = action;
+ ctx.private_data = private_data;
+
+ status = dbwrap_trans_do(db, regdb_trans_do_action, &ctx);
+
+ return ntstatus_to_werror(status);
+}
+
+/* List the deepest path into the registry. All part components will be created.*/
+
+/* If you want to have a part of the path controlled by the tdb and part by
+ a virtual registry db (e.g. printing), then you have to list the deepest path.
+ For example,"HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Print"
+ allows the reg_db backend to handle everything up to
+ "HKLM/SOFTWARE/Microsoft/Windows NT/CurrentVersion" and then we'll hook
+ the reg_printing backend onto the last component of the path (see
+ KEY_PRINTING_2K in include/rpc_reg.h) --jerry */
+
+static const char *builtin_registry_paths[] = {
+ KEY_PRINTING_2K,
+ KEY_PCC,
+ KEY_PRINTING_PORTS,
+ KEY_PRINTING,
+ KEY_PRINTING "\\Forms",
+ KEY_PRINTING "\\Printers",
+ KEY_PRINTING "\\Environments\\Windows NT x86\\Print Processors\\winprint",
+ KEY_PRINTING "\\Environments\\Windows x64\\Print Processors\\winprint",
+ KEY_SHARES,
+ KEY_EVENTLOG,
+ KEY_SMBCONF,
+ KEY_PERFLIB,
+ KEY_PERFLIB_009,
+ KEY_GROUP_POLICY,
+ KEY_SAMBA_GROUP_POLICY,
+ KEY_GP_MACHINE_POLICY,
+ KEY_GP_MACHINE_WIN_POLICY,
+ KEY_HKCU,
+ KEY_GP_USER_POLICY,
+ KEY_GP_USER_WIN_POLICY,
+ "HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\GPExtensions",
+ "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors",
+ KEY_PROD_OPTIONS,
+ "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\DefaultUserConfiguration",
+ KEY_TCPIP_PARAMS,
+ KEY_NETLOGON_PARAMS,
+ KEY_HKU,
+ KEY_HKCR,
+ KEY_HKPD,
+ KEY_HKPT,
+ NULL };
+
+struct builtin_regkey_value {
+ const char *path;
+ const char *valuename;
+ uint32_t type;
+ union {
+ const char *string;
+ uint32_t dw_value;
+ } data;
+};
+
+static struct builtin_regkey_value builtin_registry_values[] = {
+ { KEY_PRINTING_PORTS,
+ SAMBA_PRINTER_PORT_NAME, REG_SZ, { "" } },
+ { KEY_PRINTING_2K,
+ "DefaultSpoolDirectory", REG_SZ, { "C:\\Windows\\System32\\Spool\\Printers" } },
+ { KEY_EVENTLOG,
+ "DisplayName", REG_SZ, { "Event Log" } },
+ { KEY_EVENTLOG,
+ "ErrorControl", REG_DWORD, { (char*)0x00000001 } },
+ { NULL, NULL, 0, { NULL } }
+};
+
+static WERROR create_key_recursive(struct db_context *db,
+ char *path,
+ const char *subkey)
+{
+ WERROR werr;
+ char *p;
+
+ if (subkey == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (path == NULL) {
+ return regdb_create_basekey(db, subkey);
+ }
+
+ p = strrchr_m(path, '\\');
+
+ if (p == NULL) {
+ werr = create_key_recursive(db, NULL, path);
+ } else {
+ *p = '\0';
+ werr = create_key_recursive(db, path, p+1);
+ *p = '\\';
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = regdb_create_subkey_internal(db, path, subkey);
+
+done:
+ return werr;
+}
+
+/**
+ * Initialize a key in the registry:
+ * create each component key of the specified path.
+ */
+static WERROR init_registry_key_internal(struct db_context *db,
+ const char *add_path)
+{
+ char *subkey, *key;
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (add_path == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ key = talloc_strdup(frame, add_path);
+
+ subkey = strrchr_m(key, '\\');
+ if (subkey == NULL) {
+ subkey = key;
+ key = NULL;
+ } else {
+ *subkey = '\0';
+ subkey++;
+ }
+
+ werr = create_key_recursive(db, key, subkey);
+
+done:
+ talloc_free(frame);
+ return werr;
+}
+
+struct init_registry_key_context {
+ const char *add_path;
+};
+
+static NTSTATUS init_registry_key_action(struct db_context *db,
+ void *private_data)
+{
+ struct init_registry_key_context *init_ctx =
+ (struct init_registry_key_context *)private_data;
+
+ return werror_to_ntstatus(init_registry_key_internal(
+ db, init_ctx->add_path));
+}
+
+/**
+ * Initialize a key in the registry:
+ * create each component key of the specified path,
+ * wrapped in one db transaction.
+ */
+WERROR init_registry_key(const char *add_path)
+{
+ struct init_registry_key_context init_ctx;
+
+ if (regdb_key_exists(regdb, add_path)) {
+ return WERR_OK;
+ }
+
+ init_ctx.add_path = add_path;
+
+ return regdb_trans_do(regdb,
+ init_registry_key_action,
+ &init_ctx);
+}
+
+/***********************************************************************
+ Open the registry data in the tdb
+ ***********************************************************************/
+
+static void regdb_ctr_add_value(struct regval_ctr *ctr,
+ struct builtin_regkey_value *value)
+{
+ switch(value->type) {
+ case REG_DWORD:
+ regval_ctr_addvalue(ctr, value->valuename, REG_DWORD,
+ (uint8_t *)&value->data.dw_value,
+ sizeof(uint32_t));
+ break;
+
+ case REG_SZ:
+ regval_ctr_addvalue_sz(ctr, value->valuename,
+ value->data.string);
+ break;
+
+ default:
+ DEBUG(0, ("regdb_ctr_add_value: invalid value type in "
+ "registry values [%d]\n", value->type));
+ }
+}
+
+static NTSTATUS init_registry_data_action(struct db_context *db,
+ void *private_data)
+{
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct regval_ctr *values;
+ int i;
+
+ /* loop over all of the predefined paths and add each component */
+
+ for (i=0; builtin_registry_paths[i] != NULL; i++) {
+ if (regdb_key_exists(db, builtin_registry_paths[i])) {
+ continue;
+ }
+ status = werror_to_ntstatus(init_registry_key_internal(db,
+ builtin_registry_paths[i]));
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ /* loop over all of the predefined values and add each component */
+
+ for (i=0; builtin_registry_values[i].path != NULL; i++) {
+ WERROR werr;
+
+ werr = regval_ctr_init(frame, &values);
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ regdb_fetch_values_internal(db,
+ builtin_registry_values[i].path,
+ values);
+
+ /* preserve existing values across restarts. Only add new ones */
+
+ if (!regval_ctr_value_exists(values,
+ builtin_registry_values[i].valuename))
+ {
+ regdb_ctr_add_value(values,
+ &builtin_registry_values[i]);
+ status = regdb_store_values_internal(db,
+ builtin_registry_values[i].path,
+ values);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+ TALLOC_FREE(values);
+ }
+
+ status = NT_STATUS_OK;
+
+done:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+WERROR init_registry_data(void)
+{
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct regval_ctr *values;
+ int i;
+
+ /*
+ * First, check for the existence of the needed keys and values.
+ * If all do already exist, we can save the writes.
+ */
+ for (i=0; builtin_registry_paths[i] != NULL; i++) {
+ if (!regdb_key_exists(regdb, builtin_registry_paths[i])) {
+ goto do_init;
+ }
+ }
+
+ for (i=0; builtin_registry_values[i].path != NULL; i++) {
+ werr = regval_ctr_init(frame, &values);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ regdb_fetch_values_internal(regdb,
+ builtin_registry_values[i].path,
+ values);
+ if (!regval_ctr_value_exists(values,
+ builtin_registry_values[i].valuename))
+ {
+ TALLOC_FREE(values);
+ goto do_init;
+ }
+
+ TALLOC_FREE(values);
+ }
+
+ werr = WERR_OK;
+ goto done;
+
+do_init:
+
+ /*
+ * There are potentially quite a few store operations which are all
+ * individually wrapped in tdb transactions. Wrapping them in a single
+ * transaction gives just a single transaction_commit() to actually do
+ * its fsync()s. See tdb/common/transaction.c for info about nested
+ * transaction behaviour.
+ */
+
+ werr = regdb_trans_do(regdb,
+ init_registry_data_action,
+ NULL);
+
+done:
+ TALLOC_FREE(frame);
+ return werr;
+}
+
+static int regdb_normalize_keynames_fn(struct db_record *rec,
+ void *private_data)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ const char *keyname;
+ NTSTATUS status;
+ TDB_DATA key;
+ TDB_DATA value;
+ struct db_context *db = (struct db_context *)private_data;
+
+ key = dbwrap_record_get_key(rec);
+ if (key.dptr == NULL || key.dsize == 0) {
+ return 0;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ if (db == NULL) {
+ DEBUG(0, ("regdb_normalize_keynames_fn: ERROR: "
+ "NULL db context handed in via private_data\n"));
+ return 1;
+ }
+
+ if (strncmp((const char *)key.dptr, REGDB_VERSION_KEYNAME,
+ strlen(REGDB_VERSION_KEYNAME)) == 0)
+ {
+ return 0;
+ }
+
+ keyname = strchr((const char *)key.dptr, '/');
+ if (keyname) {
+ keyname = talloc_string_sub(mem_ctx,
+ (const char *)key.dptr,
+ "/",
+ "\\");
+
+ DEBUG(2, ("regdb_normalize_keynames_fn: Convert %s to %s\n",
+ (const char *)key.dptr,
+ keyname));
+
+ /* Delete the original record and store the normalized key */
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("regdb_normalize_keynames_fn: "
+ "tdb_delete for [%s] failed!\n",
+ (const char *)key.dptr));
+ return 1;
+ }
+
+ status = dbwrap_store_bystring(db, keyname, value, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("regdb_normalize_keynames_fn: "
+ "failed to store new record for [%s]!\n",
+ keyname));
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static WERROR regdb_store_regdb_version(struct db_context *db, uint32_t version)
+{
+ NTSTATUS status;
+ if (db == NULL) {
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ status = dbwrap_trans_store_int32_bystring(db, REGDB_VERSION_KEYNAME,
+ version);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("regdb_store_regdb_version: error storing %s = %d: %s\n",
+ REGDB_VERSION_KEYNAME, version, nt_errstr(status)));
+ return ntstatus_to_werror(status);
+ } else {
+ DEBUG(10, ("regdb_store_regdb_version: stored %s = %d\n",
+ REGDB_VERSION_KEYNAME, version));
+ return WERR_OK;
+ }
+}
+
+static WERROR regdb_upgrade_v1_to_v2(struct db_context *db)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+ WERROR werr;
+
+ mem_ctx = talloc_stackframe();
+
+ status = dbwrap_traverse(db, regdb_normalize_keynames_fn, db, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = WERR_REGISTRY_IO_FAILED;
+ goto done;
+ }
+
+ werr = regdb_store_regdb_version(db, REGDB_VERSION_V2);
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static bool tdb_data_read_uint32(TDB_DATA *buf, uint32_t *result)
+{
+ const size_t len = sizeof(uint32_t);
+ if (buf->dsize >= len) {
+ *result = IVAL(buf->dptr, 0);
+ buf->dptr += len;
+ buf->dsize -= len;
+ return true;
+ }
+ return false;
+}
+
+static bool tdb_data_read_cstr(TDB_DATA *buf, char **result)
+{
+ const size_t len = strnlen((char*)buf->dptr, buf->dsize) + 1;
+ if (buf->dsize >= len) {
+ *result = (char*)buf->dptr;
+ buf->dptr += len;
+ buf->dsize -= len;
+ return true;
+ }
+ return false;
+}
+
+static bool tdb_data_is_cstr(TDB_DATA d) {
+ if (tdb_data_is_empty(d) || (d.dptr[d.dsize-1] != '\0')) {
+ return false;
+ }
+ return strlen((char *)d.dptr) == (d.dsize-1);
+}
+
+static bool upgrade_v2_to_v3_check_subkeylist(struct db_context *db,
+ const char *key,
+ const char *subkey)
+{
+ static uint32_t zero = 0;
+ static TDB_DATA empty_subkey_list = {
+ .dptr = (unsigned char*)&zero,
+ .dsize = sizeof(uint32_t),
+ };
+ bool success = false;
+ char *path = talloc_asprintf(talloc_tos(), "%s\\%s", key, subkey);
+ if (!strupper_m(path)) {
+ goto done;
+ }
+
+ if (!dbwrap_exists(db, string_term_tdb_data(path))) {
+ NTSTATUS status;
+
+ DEBUG(10, ("regdb_upgrade_v2_to_v3: writing subkey list [%s]\n",
+ path));
+
+ status = dbwrap_store_bystring(db, path, empty_subkey_list,
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("regdb_upgrade_v2_to_v3: writing subkey list "
+ "[%s] failed\n", path));
+ goto done;
+ }
+ }
+ success = true;
+done:
+ talloc_free(path);
+ return success;
+}
+
+static bool upgrade_v2_to_v3_check_parent(struct db_context *db,
+ const char *key)
+{
+ const char *sep = strrchr_m(key, '\\');
+ if (sep != NULL) {
+ char *pkey = talloc_strndup(talloc_tos(), key, sep-key);
+ if (!dbwrap_exists(db, string_term_tdb_data(pkey))) {
+ DEBUG(0, ("regdb_upgrade_v2_to_v3: missing subkey list "
+ "[%s]\nrun \"net registry check\"\n", pkey));
+ }
+ talloc_free(pkey);
+ }
+ return true;
+}
+
+
+#define IS_EQUAL(d,s) (((d).dsize == strlen(s)+1) && \
+ (strcmp((char*)(d).dptr, (s)) == 0))
+#define STARTS_WITH(d,s) (((d).dsize > strlen(s)) && \
+ (strncmp((char*)(d).dptr, (s), strlen(s)) == 0))
+#define SSTR(d) (int)(d).dsize , (char*)(d).dptr
+
+
+static int regdb_upgrade_v2_to_v3_fn(struct db_record *rec, void *private_data)
+{
+ struct db_context *db = (struct db_context *)private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+
+ if (tdb_data_is_empty(key)) {
+ return 0;
+ }
+
+ if (db == NULL) {
+ DEBUG(0, ("regdb_upgrade_v2_to_v3_fn: ERROR: "
+ "NULL db context handed in via private_data\n"));
+ return 1;
+ }
+
+ if (IS_EQUAL(key, REGDB_VERSION_KEYNAME) ||
+ STARTS_WITH(key, REG_VALUE_PREFIX) ||
+ STARTS_WITH(key, REG_SECDESC_PREFIX))
+ {
+ DEBUG(10, ("regdb_upgrade_v2_to_v3: skipping [%.*s]\n",
+ SSTR(key)));
+ return 0;
+ }
+
+ if (STARTS_WITH(key, REG_SORTED_SUBKEYS_PREFIX)) {
+ NTSTATUS status;
+ /* Delete the deprecated sorted subkeys cache. */
+
+ DEBUG(10, ("regdb_upgrade_v2_to_v3: deleting [%.*s]\n",
+ SSTR(key)));
+
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("regdb_upgrade_v2_to_v3: deleting [%.*s] "
+ "failed!\n", SSTR(key)));
+ return 1;
+ }
+
+ return 0;
+ }
+
+ if ( tdb_data_is_cstr(key) &&
+ hive_info((char*)key.dptr) != NULL )
+ {
+ /*
+ * Found a regular subkey list record.
+ * Walk the list and create the list record for those
+ * subkeys that don't already have one.
+ */
+ TDB_DATA pos = val;
+ char *subkey, *path = (char*)key.dptr;
+ uint32_t num_items, found_items = 0;
+
+
+ DEBUG(10, ("regdb_upgrade_v2_to_v3: scanning subkeylist of "
+ "[%s]\n", path));
+
+ if (!tdb_data_read_uint32(&pos, &num_items)) {
+ /* invalid or empty - skip */
+ return 0;
+ }
+
+ while (tdb_data_read_cstr(&pos, &subkey)) {
+ found_items++;
+
+ if (!upgrade_v2_to_v3_check_subkeylist(db, path, subkey))
+ {
+ return 1;
+ }
+
+ if (!upgrade_v2_to_v3_check_parent(db, path)) {
+ return 1;
+ }
+ }
+ if (found_items != num_items) {
+ DEBUG(0, ("regdb_upgrade_v2_to_v3: inconsistent subkey "
+ "list [%s]\nrun \"net registry check\"\n",
+ path));
+ }
+ } else {
+ DEBUG(10, ("regdb_upgrade_v2_to_v3: skipping invalid [%.*s]\n"
+ "run \"net registry check\"\n", SSTR(key)));
+ }
+
+ return 0;
+}
+
+static WERROR regdb_upgrade_v2_to_v3(struct db_context *db)
+{
+ NTSTATUS status;
+ WERROR werr;
+
+ status = dbwrap_traverse(db, regdb_upgrade_v2_to_v3_fn, db, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = WERR_REGISTRY_IO_FAILED;
+ goto done;
+ }
+
+ werr = regdb_store_regdb_version(db, REGDB_VERSION_V3);
+
+done:
+ return werr;
+}
+
+/***********************************************************************
+ Open the registry database
+ ***********************************************************************/
+
+WERROR regdb_init(void)
+{
+ int32_t vers_id;
+ WERROR werr;
+ NTSTATUS status;
+ char *db_path;
+
+ if (regdb) {
+ DEBUG(10, ("regdb_init: incrementing refcount (%d->%d)\n",
+ regdb_refcount, regdb_refcount+1));
+ regdb_refcount++;
+ return WERR_OK;
+ }
+
+ /*
+ * Clustered Samba can only work as root because we need messaging to
+ * talk to ctdb which only works as root.
+ */
+ if (!uid_wrapper_enabled() && lp_clustering() && geteuid() != 0) {
+ DBG_ERR("Cluster mode requires running as root.\n");
+ return WERR_ACCESS_DENIED;
+ }
+
+ db_path = state_path(talloc_tos(), "registry.tdb");
+ if (db_path == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ regdb = db_open(NULL, db_path, 0,
+ REG_TDB_FLAGS, O_RDWR, 0600,
+ DBWRAP_LOCK_ORDER_1, REG_DBWRAP_FLAGS);
+ if (!regdb) {
+ regdb = db_open(NULL, db_path, 0,
+ REG_TDB_FLAGS, O_RDWR|O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1, REG_DBWRAP_FLAGS);
+ if (!regdb) {
+ werr = ntstatus_to_werror(map_nt_error_from_unix(errno));
+ DEBUG(1,("regdb_init: Failed to open registry %s (%s)\n",
+ db_path, strerror(errno) ));
+ TALLOC_FREE(db_path);
+ return werr;
+ }
+
+ werr = regdb_store_regdb_version(regdb, REGDB_CODE_VERSION);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("regdb_init: Failed to store version: %s\n",
+ win_errstr(werr)));
+ TALLOC_FREE(db_path);
+ return werr;
+ }
+
+ DEBUG(10,("regdb_init: Successfully created registry tdb\n"));
+ }
+ TALLOC_FREE(db_path);
+
+ regdb_refcount = 1;
+ DEBUG(10, ("regdb_init: registry db opened. refcount reset (%d)\n",
+ regdb_refcount));
+
+ status = dbwrap_fetch_int32_bystring(regdb, REGDB_VERSION_KEYNAME,
+ &vers_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Reading registry version failed: %s, "
+ "initializing to version %d\n",
+ nt_errstr(status), REGDB_VERSION_V1);
+
+ /*
+ * There was a regdb format version prior to version 1
+ * which did not store a INFO/version key. The format
+ * of this version was identical to version 1 except for
+ * the lack of the sorted subkey cache records.
+ * Since these are disposable, we can safely assume version
+ * 1 if no INFO/version key is found and run the db through
+ * the whole chain of upgrade. If the database was not
+ * initialized, this does not harm. If it was the unversioned
+ * version ("0"), then it do the right thing with the records.
+ */
+ werr = regdb_store_regdb_version(regdb, REGDB_VERSION_V1);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ vers_id = REGDB_VERSION_V1;
+ }
+
+ if (vers_id == REGDB_CODE_VERSION) {
+ return WERR_OK;
+ }
+
+ if (vers_id > REGDB_CODE_VERSION || vers_id == 0) {
+ DEBUG(0, ("regdb_init: unknown registry version %d "
+ "(code version = %d), refusing initialization\n",
+ vers_id, REGDB_CODE_VERSION));
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ if (dbwrap_transaction_start(regdb) != 0) {
+ return WERR_REGISTRY_IO_FAILED;
+ }
+
+ if (vers_id == REGDB_VERSION_V1) {
+ DEBUG(10, ("regdb_init: upgrading registry from version %d "
+ "to %d\n", REGDB_VERSION_V1, REGDB_VERSION_V2));
+
+ werr = regdb_upgrade_v1_to_v2(regdb);
+ if (!W_ERROR_IS_OK(werr)) {
+ dbwrap_transaction_cancel(regdb);
+ return werr;
+ }
+
+ vers_id = REGDB_VERSION_V2;
+ }
+
+ if (vers_id == REGDB_VERSION_V2) {
+ DEBUG(10, ("regdb_init: upgrading registry from version %d "
+ "to %d\n", REGDB_VERSION_V2, REGDB_VERSION_V3));
+
+ werr = regdb_upgrade_v2_to_v3(regdb);
+ if (!W_ERROR_IS_OK(werr)) {
+ dbwrap_transaction_cancel(regdb);
+ return werr;
+ }
+
+ vers_id = REGDB_VERSION_V3;
+ }
+
+ /* future upgrade code should go here */
+
+ if (dbwrap_transaction_commit(regdb) != 0) {
+ return WERR_REGISTRY_IO_FAILED;
+ }
+
+ return WERR_OK;
+}
+
+/***********************************************************************
+ Open the registry. Must already have been initialized by regdb_init()
+ ***********************************************************************/
+
+WERROR regdb_open( void )
+{
+ WERROR result;
+ char *db_path = NULL;
+ int saved_errno;
+
+ if ( regdb ) {
+ DEBUG(10, ("regdb_open: incrementing refcount (%d->%d)\n",
+ regdb_refcount, regdb_refcount+1));
+ regdb_refcount++;
+ result = WERR_OK;
+ goto done;
+ }
+
+ db_path = state_path(talloc_tos(), "registry.tdb");
+ if (db_path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ become_root();
+
+ regdb = db_open(NULL, db_path, 0,
+ REG_TDB_FLAGS, O_RDWR, 0600,
+ DBWRAP_LOCK_ORDER_1, REG_DBWRAP_FLAGS);
+ saved_errno = errno;
+ unbecome_root();
+ if ( !regdb ) {
+ result = ntstatus_to_werror(map_nt_error_from_unix(saved_errno));
+ DEBUG(0,("regdb_open: Failed to open %s! (%s)\n",
+ db_path, strerror(saved_errno)));
+ goto done;
+ }
+
+ regdb_refcount = 1;
+ DEBUG(10, ("regdb_open: registry db opened. refcount reset (%d)\n",
+ regdb_refcount));
+
+ result = WERR_OK;
+done:
+ TALLOC_FREE(db_path);
+ return result;
+}
+
+/***********************************************************************
+ ***********************************************************************/
+
+int regdb_close( void )
+{
+ if (regdb_refcount == 0) {
+ return 0;
+ }
+
+ regdb_refcount--;
+
+ DEBUG(10, ("regdb_close: decrementing refcount (%d->%d)\n",
+ regdb_refcount+1, regdb_refcount));
+
+ if ( regdb_refcount > 0 )
+ return 0;
+
+ SMB_ASSERT( regdb_refcount >= 0 );
+
+ TALLOC_FREE(regdb);
+ return 0;
+}
+
+WERROR regdb_transaction_start(void)
+{
+ return (dbwrap_transaction_start(regdb) == 0) ?
+ WERR_OK : WERR_REGISTRY_IO_FAILED;
+}
+
+WERROR regdb_transaction_commit(void)
+{
+ return (dbwrap_transaction_commit(regdb) == 0) ?
+ WERR_OK : WERR_REGISTRY_IO_FAILED;
+}
+
+WERROR regdb_transaction_cancel(void)
+{
+ return (dbwrap_transaction_cancel(regdb) == 0) ?
+ WERR_OK : WERR_REGISTRY_IO_FAILED;
+}
+
+/***********************************************************************
+ return the tdb sequence number of the registry tdb.
+ this is an indicator for the content of the registry
+ having changed. it will change upon regdb_init, too, though.
+ ***********************************************************************/
+int regdb_get_seqnum(void)
+{
+ return dbwrap_get_seqnum(regdb);
+}
+
+
+static WERROR regdb_delete_key_with_prefix(struct db_context *db,
+ const char *keyname,
+ const char *prefix)
+{
+ char *path;
+ WERROR werr = WERR_NOT_ENOUGH_MEMORY;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (keyname == NULL) {
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (prefix == NULL) {
+ path = discard_const_p(char, keyname);
+ } else {
+ path = talloc_asprintf(mem_ctx, "%s\\%s", prefix, keyname);
+ if (path == NULL) {
+ goto done;
+ }
+ }
+
+ path = normalize_reg_path(mem_ctx, path);
+ if (path == NULL) {
+ goto done;
+ }
+
+ werr = ntstatus_to_werror(dbwrap_purge_bystring(db, path));
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+
+static WERROR regdb_delete_values(struct db_context *db, const char *keyname)
+{
+ return regdb_delete_key_with_prefix(db, keyname, REG_VALUE_PREFIX);
+}
+
+static WERROR regdb_delete_secdesc(struct db_context *db, const char *keyname)
+{
+ return regdb_delete_key_with_prefix(db, keyname, REG_SECDESC_PREFIX);
+}
+
+static WERROR regdb_delete_subkeylist(struct db_context *db, const char *keyname)
+{
+ return regdb_delete_key_with_prefix(db, keyname, NULL);
+}
+
+
+static WERROR regdb_delete_key_lists(struct db_context *db, const char *keyname)
+{
+ WERROR werr;
+
+ werr = regdb_delete_values(db, keyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, (__location__ " Deleting %s\\%s failed: %s\n",
+ REG_VALUE_PREFIX, keyname, win_errstr(werr)));
+ goto done;
+ }
+
+ werr = regdb_delete_secdesc(db, keyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, (__location__ " Deleting %s\\%s failed: %s\n",
+ REG_SECDESC_PREFIX, keyname, win_errstr(werr)));
+ goto done;
+ }
+
+ werr = regdb_delete_subkeylist(db, keyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, (__location__ " Deleting %s failed: %s\n",
+ keyname, win_errstr(werr)));
+ goto done;
+ }
+
+done:
+ return werr;
+}
+
+/***********************************************************************
+ Add subkey strings to the registry tdb under a defined key
+ fmt is the same format as tdb_pack except this function only supports
+ fstrings
+ ***********************************************************************/
+
+static WERROR regdb_store_keys_internal2(struct db_context *db,
+ const char *key,
+ struct regsubkey_ctr *ctr)
+{
+ TDB_DATA dbuf;
+ uint8_t *buffer = NULL;
+ uint32_t i = 0;
+ uint32_t len, buflen;
+ uint32_t num_subkeys = regsubkey_ctr_numkeys(ctr);
+ char *keyname = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ WERROR werr;
+
+ if (!key) {
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ keyname = talloc_strdup(ctx, key);
+ if (!keyname) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ keyname = normalize_reg_path(ctx, keyname);
+ if (!keyname) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* allocate some initial memory */
+
+ buffer = (uint8_t *)SMB_MALLOC(1024);
+ if (buffer == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ buflen = 1024;
+ len = 0;
+
+ /* store the number of subkeys */
+
+ len += tdb_pack(buffer+len, buflen-len, "d", num_subkeys);
+
+ /* pack all the strings */
+
+ for (i=0; i<num_subkeys; i++) {
+ size_t thistime;
+
+ thistime = tdb_pack(buffer+len, buflen-len, "f",
+ regsubkey_ctr_specific_key(ctr, i));
+ if (len+thistime > buflen) {
+ size_t thistime2;
+ /*
+ * tdb_pack hasn't done anything because of the short
+ * buffer, allocate extra space.
+ */
+ buffer = SMB_REALLOC_ARRAY(buffer, uint8_t,
+ (len+thistime)*2);
+ if(buffer == NULL) {
+ DEBUG(0, ("regdb_store_keys: Failed to realloc "
+ "memory of size [%u]\n",
+ (unsigned int)(len+thistime)*2));
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ buflen = (len+thistime)*2;
+ thistime2 = tdb_pack(
+ buffer+len, buflen-len, "f",
+ regsubkey_ctr_specific_key(ctr, i));
+ if (thistime2 != thistime) {
+ DEBUG(0, ("tdb_pack failed\n"));
+ werr = WERR_CAN_NOT_COMPLETE;
+ goto done;
+ }
+ }
+ len += thistime;
+ }
+
+ /* finally write out the data */
+
+ dbuf.dptr = buffer;
+ dbuf.dsize = len;
+ werr = ntstatus_to_werror(dbwrap_store_bystring(db, keyname, dbuf,
+ TDB_REPLACE));
+
+done:
+ TALLOC_FREE(ctx);
+ SAFE_FREE(buffer);
+ return werr;
+}
+
+/**
+ * Utility function to store a new empty list of
+ * subkeys of given key specified as parent and subkey name
+ * (thereby creating the key).
+ * If the parent keyname is NULL, then the "subkey" is
+ * interpreted as a base key.
+ * If the subkey list does already exist, it is not modified.
+ *
+ * Must be called from within a transaction.
+ */
+static WERROR regdb_store_subkey_list(struct db_context *db, const char *parent,
+ const char *key)
+{
+ WERROR werr;
+ char *path = NULL;
+ struct regsubkey_ctr *subkeys = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (parent == NULL) {
+ path = talloc_strdup(frame, key);
+ } else {
+ path = talloc_asprintf(frame, "%s\\%s", parent, key);
+ }
+ if (!path) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ werr = regsubkey_ctr_init(frame, &subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regdb_fetch_keys_internal(db, path, subkeys);
+ if (W_ERROR_IS_OK(werr)) {
+ /* subkey list exists already - don't modify */
+ goto done;
+ }
+
+ werr = regsubkey_ctr_reinit(subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ /* create a record with 0 subkeys */
+ werr = regdb_store_keys_internal2(db, path, subkeys);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("regdb_store_keys: Failed to store new record for "
+ "key [%s]: %s\n", path, win_errstr(werr)));
+ goto done;
+ }
+
+done:
+ talloc_free(frame);
+ return werr;
+}
+
+/***********************************************************************
+ Store the new subkey record and create any child key records that
+ do not currently exist
+ ***********************************************************************/
+
+struct regdb_store_keys_context {
+ const char *key;
+ struct regsubkey_ctr *ctr;
+};
+
+static NTSTATUS regdb_store_keys_action(struct db_context *db,
+ void *private_data)
+{
+ struct regdb_store_keys_context *store_ctx;
+ WERROR werr;
+ int num_subkeys, i;
+ char *path = NULL;
+ struct regsubkey_ctr *old_subkeys = NULL;
+ char *oldkeyname = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ store_ctx = (struct regdb_store_keys_context *)private_data;
+
+ /*
+ * Re-fetch the old keys inside the transaction
+ */
+
+ werr = regsubkey_ctr_init(mem_ctx, &old_subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regdb_fetch_keys_internal(db, store_ctx->key, old_subkeys);
+ if (!W_ERROR_IS_OK(werr) &&
+ !W_ERROR_EQUAL(werr, WERR_NOT_FOUND))
+ {
+ goto done;
+ }
+
+ /*
+ * Make the store operation as safe as possible without transactions:
+ *
+ * (1) For each subkey removed from ctr compared with old_subkeys:
+ *
+ * (a) First delete the value db entry.
+ *
+ * (b) Next delete the secdesc db record.
+ *
+ * (c) Then delete the subkey list entry.
+ *
+ * (2) Now write the list of subkeys of the parent key,
+ * deleting removed entries and adding new ones.
+ *
+ * (3) Finally create the subkey list entries for the added keys.
+ *
+ * This way if we crash half-way in between deleting the subkeys
+ * and storing the parent's list of subkeys, no old data can pop up
+ * out of the blue when re-adding keys later on.
+ */
+
+ /* (1) delete removed keys' lists (values/secdesc/subkeys) */
+
+ num_subkeys = regsubkey_ctr_numkeys(old_subkeys);
+ for (i=0; i<num_subkeys; i++) {
+ oldkeyname = regsubkey_ctr_specific_key(old_subkeys, i);
+
+ if (regsubkey_ctr_key_exists(store_ctx->ctr, oldkeyname)) {
+ /*
+ * It's still around, don't delete
+ */
+ continue;
+ }
+
+ path = talloc_asprintf(mem_ctx, "%s\\%s", store_ctx->key,
+ oldkeyname);
+ if (!path) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ werr = regdb_delete_key_lists(db, path);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ TALLOC_FREE(path);
+ }
+
+ TALLOC_FREE(old_subkeys);
+
+ /* (2) store the subkey list for the parent */
+
+ werr = regdb_store_keys_internal2(db, store_ctx->key, store_ctx->ctr);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("regdb_store_keys: Failed to store new subkey list "
+ "for parent [%s]: %s\n", store_ctx->key,
+ win_errstr(werr)));
+ goto done;
+ }
+
+ /* (3) now create records for any subkeys that don't already exist */
+
+ num_subkeys = regsubkey_ctr_numkeys(store_ctx->ctr);
+
+ for (i=0; i<num_subkeys; i++) {
+ const char *subkey;
+
+ subkey = regsubkey_ctr_specific_key(store_ctx->ctr, i);
+
+ werr = regdb_store_subkey_list(db, store_ctx->key, subkey);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+ }
+
+ /*
+ * Update the seqnum in the container to possibly
+ * prevent next read from going to disk
+ */
+ werr = regsubkey_ctr_set_seqnum(store_ctx->ctr, dbwrap_get_seqnum(db));
+
+done:
+ talloc_free(mem_ctx);
+ return werror_to_ntstatus(werr);
+}
+
+static bool regdb_store_keys_internal(struct db_context *db, const char *key,
+ struct regsubkey_ctr *ctr)
+{
+ int num_subkeys, old_num_subkeys, i;
+ struct regsubkey_ctr *old_subkeys = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ WERROR werr;
+ bool ret = false;
+ struct regdb_store_keys_context store_ctx;
+
+ if (!regdb_key_exists(db, key)) {
+ goto done;
+ }
+
+ /*
+ * fetch a list of the old subkeys so we can determine if anything has
+ * changed
+ */
+
+ werr = regsubkey_ctr_init(ctx, &old_subkeys);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("regdb_store_keys: talloc() failure!\n"));
+ goto done;
+ }
+
+ werr = regdb_fetch_keys_internal(db, key, old_subkeys);
+ if (!W_ERROR_IS_OK(werr) &&
+ !W_ERROR_EQUAL(werr, WERR_NOT_FOUND))
+ {
+ goto done;
+ }
+
+ num_subkeys = regsubkey_ctr_numkeys(ctr);
+ old_num_subkeys = regsubkey_ctr_numkeys(old_subkeys);
+ if ((num_subkeys && old_num_subkeys) &&
+ (num_subkeys == old_num_subkeys)) {
+
+ for (i = 0; i < num_subkeys; i++) {
+ if (strcmp(regsubkey_ctr_specific_key(ctr, i),
+ regsubkey_ctr_specific_key(old_subkeys, i))
+ != 0)
+ {
+ break;
+ }
+ }
+ if (i == num_subkeys) {
+ /*
+ * Nothing changed, no point to even start a tdb
+ * transaction
+ */
+
+ ret = true;
+ goto done;
+ }
+ }
+
+ TALLOC_FREE(old_subkeys);
+
+ store_ctx.key = key;
+ store_ctx.ctr = ctr;
+
+ werr = regdb_trans_do(db,
+ regdb_store_keys_action,
+ &store_ctx);
+
+ ret = W_ERROR_IS_OK(werr);
+
+done:
+ TALLOC_FREE(ctx);
+
+ return ret;
+}
+
+static bool regdb_store_keys(const char *key, struct regsubkey_ctr *ctr)
+{
+ return regdb_store_keys_internal(regdb, key, ctr);
+}
+
+/**
+ * create a subkey of a given key
+ */
+
+struct regdb_create_subkey_context {
+ const char *key;
+ const char *subkey;
+};
+
+static NTSTATUS regdb_create_subkey_action(struct db_context *db,
+ void *private_data)
+{
+ WERROR werr;
+ struct regdb_create_subkey_context *create_ctx;
+ struct regsubkey_ctr *subkeys;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ create_ctx = (struct regdb_create_subkey_context *)private_data;
+
+ werr = regsubkey_ctr_init(mem_ctx, &subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regdb_fetch_keys_internal(db, create_ctx->key, subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regsubkey_ctr_addkey(subkeys, create_ctx->subkey);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regdb_store_keys_internal2(db, create_ctx->key, subkeys);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, (__location__ " failed to store new subkey list for "
+ "parent key %s: %s\n", create_ctx->key,
+ win_errstr(werr)));
+ }
+
+ werr = regdb_store_subkey_list(db, create_ctx->key, create_ctx->subkey);
+
+done:
+ talloc_free(mem_ctx);
+ return werror_to_ntstatus(werr);
+}
+
+static WERROR regdb_create_subkey_internal(struct db_context *db,
+ const char *key,
+ const char *subkey)
+{
+ WERROR werr;
+ struct regsubkey_ctr *subkeys;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct regdb_create_subkey_context create_ctx;
+
+ if (!regdb_key_exists(db, key)) {
+ werr = WERR_NOT_FOUND;
+ goto done;
+ }
+
+ werr = regsubkey_ctr_init(mem_ctx, &subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regdb_fetch_keys_internal(db, key, subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ if (regsubkey_ctr_key_exists(subkeys, subkey)) {
+ char *newkey;
+
+ newkey = talloc_asprintf(mem_ctx, "%s\\%s", key, subkey);
+ if (newkey == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ if (regdb_key_exists(db, newkey)) {
+ werr = WERR_OK;
+ goto done;
+ }
+ }
+
+ talloc_free(subkeys);
+
+ create_ctx.key = key;
+ create_ctx.subkey = subkey;
+
+ werr = regdb_trans_do(db,
+ regdb_create_subkey_action,
+ &create_ctx);
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static WERROR regdb_create_subkey(const char *key, const char *subkey)
+{
+ return regdb_create_subkey_internal(regdb, key, subkey);
+}
+
+/**
+ * create a base key
+ */
+
+struct regdb_create_basekey_context {
+ const char *key;
+};
+
+static NTSTATUS regdb_create_basekey_action(struct db_context *db,
+ void *private_data)
+{
+ WERROR werr;
+ struct regdb_create_basekey_context *create_ctx;
+
+ create_ctx = (struct regdb_create_basekey_context *)private_data;
+
+ werr = regdb_store_subkey_list(db, NULL, create_ctx->key);
+
+ return werror_to_ntstatus(werr);
+}
+
+static WERROR regdb_create_basekey(struct db_context *db, const char *key)
+{
+ WERROR werr;
+ struct regdb_create_subkey_context create_ctx;
+
+ create_ctx.key = key;
+
+ werr = regdb_trans_do(db,
+ regdb_create_basekey_action,
+ &create_ctx);
+
+ return werr;
+}
+
+/**
+ * create a subkey of a given key
+ */
+
+struct regdb_delete_subkey_context {
+ const char *key;
+ const char *subkey;
+ const char *path;
+ bool lazy;
+};
+
+static NTSTATUS regdb_delete_subkey_action(struct db_context *db,
+ void *private_data)
+{
+ WERROR werr;
+ struct regdb_delete_subkey_context *delete_ctx;
+ struct regsubkey_ctr *subkeys;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ delete_ctx = (struct regdb_delete_subkey_context *)private_data;
+
+ werr = regdb_delete_key_lists(db, delete_ctx->path);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ if (delete_ctx->lazy) {
+ goto done;
+ }
+
+ werr = regsubkey_ctr_init(mem_ctx, &subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regdb_fetch_keys_internal(db, delete_ctx->key, subkeys);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regsubkey_ctr_delkey(subkeys, delete_ctx->subkey);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ werr = regdb_store_keys_internal2(db, delete_ctx->key, subkeys);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, (__location__ " failed to store new subkey_list for "
+ "parent key %s: %s\n", delete_ctx->key,
+ win_errstr(werr)));
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return werror_to_ntstatus(werr);
+}
+
+static WERROR regdb_delete_subkey(const char *key, const char *subkey, bool lazy)
+{
+ WERROR werr;
+ char *path;
+ struct regdb_delete_subkey_context delete_ctx;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (!regdb_key_exists(regdb, key)) {
+ werr = WERR_NOT_FOUND;
+ goto done;
+ }
+
+ path = talloc_asprintf(mem_ctx, "%s\\%s", key, subkey);
+ if (path == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ if (!regdb_key_exists(regdb, path)) {
+ werr = WERR_OK;
+ goto done;
+ }
+
+ delete_ctx.key = key;
+ delete_ctx.subkey = subkey;
+ delete_ctx.path = path;
+ delete_ctx.lazy = lazy;
+
+ werr = regdb_trans_do(regdb,
+ regdb_delete_subkey_action,
+ &delete_ctx);
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static TDB_DATA regdb_fetch_key_internal(struct db_context *db,
+ TALLOC_CTX *mem_ctx, const char *key)
+{
+ char *path = NULL;
+ TDB_DATA data;
+ NTSTATUS status;
+
+ path = normalize_reg_path(mem_ctx, key);
+ if (!path) {
+ return make_tdb_data(NULL, 0);
+ }
+
+ status = dbwrap_fetch_bystring(db, mem_ctx, path, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ data = tdb_null;
+ }
+
+ TALLOC_FREE(path);
+ return data;
+}
+
+
+/**
+ * Check for the existence of a key.
+ *
+ * Existence of a key is authoritatively defined by
+ * the existence of the record that contains the list
+ * of its subkeys.
+ *
+ * Return false, if the record does not match the correct
+ * structure of an initial 4-byte counter and then a
+ * list of the corresponding number of zero-terminated
+ * strings.
+ */
+static bool regdb_key_exists(struct db_context *db, const char *key)
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ TDB_DATA value;
+ bool ret = false;
+ char *path;
+ uint32_t buflen;
+ const char *buf;
+ uint32_t num_items, i;
+ int32_t len;
+
+ if (key == NULL) {
+ goto done;
+ }
+
+ path = normalize_reg_path(mem_ctx, key);
+ if (path == NULL) {
+ DEBUG(0, ("out of memory! (talloc failed)\n"));
+ goto done;
+ }
+
+ if (*path == '\0') {
+ goto done;
+ }
+
+ value = regdb_fetch_key_internal(db, mem_ctx, path);
+ if (value.dptr == NULL) {
+ goto done;
+ }
+
+ if (value.dsize == 0) {
+ DEBUG(10, ("regdb_key_exists: subkeylist-record for key "
+ "[%s] is empty: Could be a deleted record in a "
+ "clustered (ctdb) environment?\n",
+ path));
+ goto done;
+ }
+
+ len = tdb_unpack(value.dptr, value.dsize, "d", &num_items);
+ if (len == (int32_t)-1) {
+ DEBUG(1, ("regdb_key_exists: ERROR: subkeylist-record for key "
+ "[%s] is invalid: Could not parse initial 4-byte "
+ "counter. record data length is %u.\n",
+ path, (unsigned int)value.dsize));
+ goto done;
+ }
+
+ /*
+ * Note: the tdb_unpack check above implies that len <= value.dsize
+ */
+ buflen = value.dsize - len;
+ buf = (const char *)value.dptr + len;
+
+ for (i = 0; i < num_items; i++) {
+ if (buflen == 0) {
+ break;
+ }
+ len = strnlen(buf, buflen) + 1;
+ if (buflen < len) {
+ DEBUG(1, ("regdb_key_exists: ERROR: subkeylist-record "
+ "for key [%s] is corrupt: %u items expected, "
+ "item number %u is not zero terminated.\n",
+ path, num_items, i+1));
+ goto done;
+ }
+
+ buf += len;
+ buflen -= len;
+ }
+
+ if (buflen > 0) {
+ DEBUG(1, ("regdb_key_exists: ERROR: subkeylist-record for key "
+ "[%s] is corrupt: %u items expected and found, but "
+ "the record contains additional %u bytes\n",
+ path, num_items, buflen));
+ goto done;
+ }
+
+ if (i < num_items) {
+ DEBUG(1, ("regdb_key_exists: ERROR: subkeylist-record for key "
+ "[%s] is corrupt: %u items expected, but only %u "
+ "items found.\n",
+ path, num_items, i+1));
+ goto done;
+ }
+
+ ret = true;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+
+/***********************************************************************
+ Retrieve an array of strings containing subkeys. Memory should be
+ released by the caller.
+ ***********************************************************************/
+
+static WERROR regdb_fetch_keys_internal(struct db_context *db, const char *key,
+ struct regsubkey_ctr *ctr)
+{
+ WERROR werr;
+ uint32_t num_items;
+ uint8_t *buf;
+ uint32_t buflen, len;
+ uint32_t i;
+ fstring subkeyname;
+ TALLOC_CTX *frame = talloc_stackframe();
+ TDB_DATA value;
+ int seqnum[2], count;
+
+ DEBUG(11,("regdb_fetch_keys: Enter key => [%s]\n", key ? key : "NULL"));
+
+ if (!regdb_key_exists(db, key)) {
+ DEBUG(10, ("key [%s] not found\n", key));
+ werr = WERR_NOT_FOUND;
+ goto done;
+ }
+
+ werr = regsubkey_ctr_reinit(ctr);
+ W_ERROR_NOT_OK_GOTO_DONE(werr);
+
+ count = 0;
+ ZERO_STRUCT(value);
+ seqnum[0] = dbwrap_get_seqnum(db);
+
+ do {
+ count++;
+ TALLOC_FREE(value.dptr);
+ value = regdb_fetch_key_internal(db, frame, key);
+ seqnum[count % 2] = dbwrap_get_seqnum(db);
+
+ } while (seqnum[0] != seqnum[1]);
+
+ if (count > 1) {
+ DEBUG(5, ("regdb_fetch_keys_internal: it took %d attempts to "
+ "fetch key '%s' with constant seqnum\n",
+ count, key));
+ }
+
+ werr = regsubkey_ctr_set_seqnum(ctr, seqnum[0]);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (value.dsize == 0 || value.dptr == NULL) {
+ DEBUG(10, ("regdb_fetch_keys: no subkeys found for key [%s]\n",
+ key));
+ goto done;
+ }
+
+ buf = value.dptr;
+ buflen = value.dsize;
+ len = tdb_unpack( buf, buflen, "d", &num_items);
+ if (len == (uint32_t)-1) {
+ werr = WERR_NOT_FOUND;
+ goto done;
+ }
+
+ for (i=0; i<num_items; i++) {
+ int this_len;
+
+ this_len = tdb_unpack(buf+len, buflen-len, "f", subkeyname);
+ if (this_len == -1) {
+ DBG_WARNING("Invalid registry data, "
+ "tdb_unpack failed\n");
+ werr = WERR_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+ len += this_len;
+ if (len < this_len) {
+ DBG_WARNING("Invalid registry data, "
+ "integer overflow\n");
+ werr = WERR_INTERNAL_DB_CORRUPTION;
+ goto done;
+ }
+
+ werr = regsubkey_ctr_addkey(ctr, subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(5, ("regdb_fetch_keys: regsubkey_ctr_addkey "
+ "failed: %s\n", win_errstr(werr)));
+ num_items = 0;
+ goto done;
+ }
+ }
+
+ DEBUG(11,("regdb_fetch_keys: Exit [%d] items\n", num_items));
+
+done:
+ TALLOC_FREE(frame);
+ return werr;
+}
+
+static int regdb_fetch_keys(const char *key, struct regsubkey_ctr *ctr)
+{
+ WERROR werr;
+
+ werr = regdb_fetch_keys_internal(regdb, key, ctr);
+ if (!W_ERROR_IS_OK(werr)) {
+ return -1;
+ }
+
+ return regsubkey_ctr_numkeys(ctr);
+}
+
+/****************************************************************************
+ Unpack a list of registry values frem the TDB
+ ***************************************************************************/
+
+static int regdb_unpack_values(struct regval_ctr *values,
+ uint8_t *buf,
+ size_t buflen)
+{
+ int this_len;
+ size_t len = 0;
+ uint32_t type;
+ fstring valuename;
+ uint32_t size;
+ uint8_t *data_p;
+ uint32_t num_values = 0;
+ uint32_t i;
+
+ /* loop and unpack the rest of the registry values */
+
+ this_len = tdb_unpack(buf, buflen, "d", &num_values);
+ if (this_len == -1) {
+ DBG_WARNING("Invalid registry data, "
+ "tdb_unpack failed\n");
+ return -1;
+ }
+ len = this_len;
+
+ for ( i=0; i<num_values; i++ ) {
+ /* unpack the next regval */
+
+ type = REG_NONE;
+ size = 0;
+ data_p = NULL;
+ valuename[0] = '\0';
+ this_len = tdb_unpack(buf+len, buflen-len, "fdB",
+ valuename,
+ &type,
+ &size,
+ &data_p);
+ if (this_len == -1) {
+ DBG_WARNING("Invalid registry data, "
+ "tdb_unpack failed\n");
+ return -1;
+ }
+ len += this_len;
+ if (len < (size_t)this_len) {
+ DBG_WARNING("Invalid registry data, "
+ "integer overflow\n");
+ return -1;
+ }
+
+ regval_ctr_addvalue(values, valuename, type,
+ (uint8_t *)data_p, size);
+ SAFE_FREE(data_p); /* 'B' option to tdb_unpack does a malloc() */
+
+ DEBUG(10, ("regdb_unpack_values: value[%d]: name[%s] len[%d]\n",
+ i, valuename, size));
+ }
+
+ return len;
+}
+
+/****************************************************************************
+ Pack all values in all printer keys
+ ***************************************************************************/
+
+static int regdb_pack_values(struct regval_ctr *values, uint8_t *buf, int buflen)
+{
+ int len = 0;
+ int i;
+ struct regval_blob *val;
+ int num_values;
+
+ if ( !values )
+ return 0;
+
+ num_values = regval_ctr_numvals( values );
+
+ /* pack the number of values first */
+
+ len += tdb_pack( buf+len, buflen-len, "d", num_values );
+
+ /* loop over all values */
+
+ for ( i=0; i<num_values; i++ ) {
+ val = regval_ctr_specific_value( values, i );
+ len += tdb_pack(buf+len, buflen-len, "fdB",
+ regval_name(val),
+ regval_type(val),
+ regval_size(val),
+ regval_data_p(val) );
+ }
+
+ return len;
+}
+
+/***********************************************************************
+ Retrieve an array of strings containing subkeys. Memory should be
+ released by the caller.
+ ***********************************************************************/
+
+static int regdb_fetch_values_internal(struct db_context *db, const char* key,
+ struct regval_ctr *values)
+{
+ char *keystr = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ int ret = 0;
+ TDB_DATA value;
+ WERROR werr;
+ int seqnum[2], count;
+
+ DEBUG(10,("regdb_fetch_values: Looking for values of key [%s]\n", key));
+
+ if (!regdb_key_exists(db, key)) {
+ DEBUG(10, ("regb_fetch_values: key [%s] does not exist\n",
+ key));
+ ret = -1;
+ goto done;
+ }
+
+ keystr = talloc_asprintf(ctx, "%s\\%s", REG_VALUE_PREFIX, key);
+ if (!keystr) {
+ goto done;
+ }
+
+ ZERO_STRUCT(value);
+ count = 0;
+ seqnum[0] = dbwrap_get_seqnum(db);
+
+ do {
+ count++;
+ TALLOC_FREE(value.dptr);
+ value = regdb_fetch_key_internal(db, ctx, keystr);
+ seqnum[count % 2] = dbwrap_get_seqnum(db);
+ } while (seqnum[0] != seqnum[1]);
+
+ if (count > 1) {
+ DEBUG(5, ("regdb_fetch_values_internal: it took %d attempts "
+ "to fetch key '%s' with constant seqnum\n",
+ count, key));
+ }
+
+ werr = regval_ctr_set_seqnum(values, seqnum[0]);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (!value.dptr) {
+ /* all keys have zero values by default */
+ goto done;
+ }
+
+ ret = regdb_unpack_values(values, value.dptr, value.dsize);
+ if (ret == -1) {
+ DBG_WARNING("regdb_unpack_values failed\n");
+ }
+
+ ret = regval_ctr_numvals(values);
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int regdb_fetch_values(const char* key, struct regval_ctr *values)
+{
+ return regdb_fetch_values_internal(regdb, key, values);
+}
+
+static NTSTATUS regdb_store_values_internal(struct db_context *db,
+ const char *key,
+ struct regval_ctr *values)
+{
+ TDB_DATA old_data, data;
+ char *keystr = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ int len;
+ NTSTATUS status;
+ WERROR werr;
+
+ DEBUG(10,("regdb_store_values: Looking for values of key [%s]\n", key));
+
+ if (!regdb_key_exists(db, key)) {
+ status = NT_STATUS_NOT_FOUND;
+ goto done;
+ }
+
+ if (regval_ctr_numvals(values) == 0) {
+ werr = regdb_delete_values(db, key);
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ /*
+ * update the seqnum in the cache to prevent the next read
+ * from going to disk
+ */
+ werr = regval_ctr_set_seqnum(values, dbwrap_get_seqnum(db));
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ ZERO_STRUCT(data);
+
+ len = regdb_pack_values(values, data.dptr, data.dsize);
+ if (len <= 0) {
+ DEBUG(0,("regdb_store_values: unable to pack values. len <= 0\n"));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ data.dptr = talloc_array(ctx, uint8_t, len);
+ data.dsize = len;
+
+ len = regdb_pack_values(values, data.dptr, data.dsize);
+
+ SMB_ASSERT( len == data.dsize );
+
+ keystr = talloc_asprintf(ctx, "%s\\%s", REG_VALUE_PREFIX, key );
+ if (!keystr) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ keystr = normalize_reg_path(ctx, keystr);
+ if (!keystr) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = dbwrap_fetch_bystring(db, ctx, keystr, &old_data);
+
+ if (NT_STATUS_IS_OK(status)
+ && (old_data.dptr != NULL)
+ && (old_data.dsize == data.dsize)
+ && (memcmp(old_data.dptr, data.dptr, data.dsize) == 0))
+ {
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ status = dbwrap_trans_store_bystring(db, keystr, data, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("regdb_store_values_internal: error storing: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ /*
+ * update the seqnum in the cache to prevent the next read
+ * from going to disk
+ */
+ werr = regval_ctr_set_seqnum(values, dbwrap_get_seqnum(db));
+ status = werror_to_ntstatus(werr);
+
+done:
+ TALLOC_FREE(ctx);
+ return status;
+}
+
+struct regdb_store_values_ctx {
+ const char *key;
+ struct regval_ctr *values;
+};
+
+static NTSTATUS regdb_store_values_action(struct db_context *db,
+ void *private_data)
+{
+ NTSTATUS status;
+ struct regdb_store_values_ctx *ctx =
+ (struct regdb_store_values_ctx *)private_data;
+
+ status = regdb_store_values_internal(db, ctx->key, ctx->values);
+
+ return status;
+}
+
+static bool regdb_store_values(const char *key, struct regval_ctr *values)
+{
+ WERROR werr;
+ struct regdb_store_values_ctx ctx;
+
+ ctx.key = key;
+ ctx.values = values;
+
+ werr = regdb_trans_do(regdb, regdb_store_values_action, &ctx);
+
+ return W_ERROR_IS_OK(werr);
+}
+
+static WERROR regdb_get_secdesc(TALLOC_CTX *mem_ctx, const char *key,
+ struct security_descriptor **psecdesc)
+{
+ char *tdbkey;
+ TDB_DATA data;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ WERROR err = WERR_OK;
+
+ DEBUG(10, ("regdb_get_secdesc: Getting secdesc of key [%s]\n", key));
+
+ if (!regdb_key_exists(regdb, key)) {
+ err = WERR_FILE_NOT_FOUND;
+ goto done;
+ }
+
+ tdbkey = talloc_asprintf(tmp_ctx, "%s\\%s", REG_SECDESC_PREFIX, key);
+ if (tdbkey == NULL) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ tdbkey = normalize_reg_path(tmp_ctx, tdbkey);
+ if (tdbkey == NULL) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dbwrap_fetch_bystring(regdb, tmp_ctx, tdbkey, &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ err = WERR_FILE_NOT_FOUND;
+ goto done;
+ }
+
+ status = unmarshall_sec_desc(mem_ctx, (uint8_t *)data.dptr, data.dsize,
+ psecdesc);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
+ err = WERR_NOT_ENOUGH_MEMORY;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ err = WERR_REGISTRY_CORRUPT;
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return err;
+}
+
+struct regdb_set_secdesc_ctx {
+ const char *key;
+ struct security_descriptor *secdesc;
+};
+
+static NTSTATUS regdb_set_secdesc_action(struct db_context *db,
+ void *private_data)
+{
+ char *tdbkey;
+ NTSTATUS status;
+ TDB_DATA tdbdata;
+ struct regdb_set_secdesc_ctx *ctx =
+ (struct regdb_set_secdesc_ctx *)private_data;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ tdbkey = talloc_asprintf(frame, "%s\\%s", REG_SECDESC_PREFIX, ctx->key);
+ if (tdbkey == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ tdbkey = normalize_reg_path(frame, tdbkey);
+ if (tdbkey == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (ctx->secdesc == NULL) {
+ /* assuming a delete */
+ status = dbwrap_delete_bystring(db, tdbkey);
+ goto done;
+ }
+
+ status = marshall_sec_desc(frame, ctx->secdesc, &tdbdata.dptr,
+ &tdbdata.dsize);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dbwrap_store_bystring(db, tdbkey, tdbdata, 0);
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static WERROR regdb_set_secdesc(const char *key,
+ struct security_descriptor *secdesc)
+{
+ WERROR err;
+ struct regdb_set_secdesc_ctx ctx;
+
+ if (!regdb_key_exists(regdb, key)) {
+ err = WERR_FILE_NOT_FOUND;
+ goto done;
+ }
+
+ ctx.key = key;
+ ctx.secdesc = secdesc;
+
+ err = regdb_trans_do(regdb, regdb_set_secdesc_action, &ctx);
+
+done:
+ return err;
+}
+
+static bool regdb_subkeys_need_update(struct regsubkey_ctr *subkeys)
+{
+ return (regdb_get_seqnum() != regsubkey_ctr_get_seqnum(subkeys));
+}
+
+static bool regdb_values_need_update(struct regval_ctr *values)
+{
+ return (regdb_get_seqnum() != regval_ctr_get_seqnum(values));
+}
+
+/*
+ * Table of function pointers for default access
+ */
+
+struct registry_ops regdb_ops = {
+ .fetch_subkeys = regdb_fetch_keys,
+ .fetch_values = regdb_fetch_values,
+ .store_subkeys = regdb_store_keys,
+ .store_values = regdb_store_values,
+ .create_subkey = regdb_create_subkey,
+ .delete_subkey = regdb_delete_subkey,
+ .get_secdesc = regdb_get_secdesc,
+ .set_secdesc = regdb_set_secdesc,
+ .subkeys_need_update = regdb_subkeys_need_update,
+ .values_need_update = regdb_values_need_update
+};
diff --git a/source3/registry/reg_backend_db.h b/source3/registry/reg_backend_db.h
new file mode 100644
index 0000000..6c7fb67
--- /dev/null
+++ b/source3/registry/reg_backend_db.h
@@ -0,0 +1,36 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2007-2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_BACKEND_DB_H
+#define _REG_BACKEND_DB_H
+
+#include "registry.h"
+
+WERROR init_registry_key(const char *add_path);
+WERROR init_registry_data(void);
+WERROR regdb_init(void);
+WERROR regdb_open( void );
+int regdb_close( void );
+WERROR regdb_transaction_start(void);
+WERROR regdb_transaction_commit(void);
+WERROR regdb_transaction_cancel(void);
+int regdb_get_seqnum(void);
+
+#endif /* _REG_BACKEND_DB_H */
diff --git a/source3/registry/reg_backend_hkpt_params.c b/source3/registry/reg_backend_hkpt_params.c
new file mode 100644
index 0000000..9cd6efe
--- /dev/null
+++ b/source3/registry/reg_backend_hkpt_params.c
@@ -0,0 +1,73 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * HKPT parameters registry backend.
+ *
+ * This replaces the former dynamic hkpt parameters overlay.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_perfcount.h"
+#include "reg_objects.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops;
+
+static int hkpt_params_fetch_values(const char *key, struct regval_ctr *regvals)
+{
+ uint32_t base_index;
+ uint32_t buffer_size;
+ char *buffer = NULL;
+
+ /* This is ALMOST the same as perflib_009_params, but HKPT has
+ a "Counters" entry instead of a "Counter" key. <Grrrr> */
+
+ base_index = reg_perfcount_get_base_index();
+ buffer_size = reg_perfcount_get_counter_names(base_index, &buffer);
+ regval_ctr_addvalue(regvals, "Counters", REG_MULTI_SZ, (uint8_t *)buffer,
+ buffer_size);
+
+ if(buffer_size > 0) {
+ SAFE_FREE(buffer);
+ }
+
+ buffer_size = reg_perfcount_get_counter_help(base_index, &buffer);
+ regval_ctr_addvalue(regvals, "Help", REG_MULTI_SZ, (uint8_t *)buffer, buffer_size);
+ if(buffer_size > 0) {
+ SAFE_FREE(buffer);
+ }
+
+ return regval_ctr_numvals( regvals );
+}
+
+static int hkpt_params_fetch_subkeys(const char *key,
+ struct regsubkey_ctr *subkey_ctr)
+{
+ return regdb_ops.fetch_subkeys(key, subkey_ctr);
+}
+
+struct registry_ops hkpt_params_reg_ops = {
+ .fetch_values = hkpt_params_fetch_values,
+ .fetch_subkeys = hkpt_params_fetch_subkeys,
+};
diff --git a/source3/registry/reg_backend_netlogon_params.c b/source3/registry/reg_backend_netlogon_params.c
new file mode 100644
index 0000000..d7cf3c6
--- /dev/null
+++ b/source3/registry/reg_backend_netlogon_params.c
@@ -0,0 +1,60 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Netlogon parameters registry backend.
+ *
+ * This replaces the former dynamic netlogon parameters overlay.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_objects.h"
+#include "passdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops;
+
+static int netlogon_params_fetch_values(const char *key, struct regval_ctr *regvals)
+{
+ uint32_t dwValue;
+
+ if (!pdb_get_account_policy(PDB_POLICY_REFUSE_MACHINE_PW_CHANGE, &dwValue)) {
+ dwValue = 0;
+ }
+
+ regval_ctr_addvalue(regvals, "RefusePasswordChange", REG_DWORD,
+ (uint8_t *)&dwValue, sizeof(dwValue));
+
+ return regval_ctr_numvals(regvals);
+}
+
+static int netlogon_params_fetch_subkeys(const char *key,
+ struct regsubkey_ctr *subkey_ctr)
+{
+ return regdb_ops.fetch_subkeys(key, subkey_ctr);
+}
+
+struct registry_ops netlogon_params_reg_ops = {
+ .fetch_values = netlogon_params_fetch_values,
+ .fetch_subkeys = netlogon_params_fetch_subkeys,
+};
diff --git a/source3/registry/reg_backend_perflib.c b/source3/registry/reg_backend_perflib.c
new file mode 100644
index 0000000..a46c574
--- /dev/null
+++ b/source3/registry/reg_backend_perflib.c
@@ -0,0 +1,110 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * perflib registry backend.
+ *
+ * This is a virtual overlay, dynamically presenting perflib values.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_util_internal.h"
+#include "reg_perfcount.h"
+#include "reg_objects.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops;
+
+#define KEY_PERFLIB_NORM "HKLM\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\PERFLIB"
+#define KEY_PERFLIB_009_NORM "HKLM\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\PERFLIB\\009"
+
+static int perflib_params(struct regval_ctr *regvals)
+{
+ int base_index = -1;
+ int last_counter = -1;
+ int last_help = -1;
+ int version = 0x00010001;
+
+ base_index = reg_perfcount_get_base_index();
+ regval_ctr_addvalue(regvals, "Base Index", REG_DWORD, (uint8_t *)&base_index, sizeof(base_index));
+ last_counter = reg_perfcount_get_last_counter(base_index);
+ regval_ctr_addvalue(regvals, "Last Counter", REG_DWORD, (uint8_t *)&last_counter, sizeof(last_counter));
+ last_help = reg_perfcount_get_last_help(last_counter);
+ regval_ctr_addvalue(regvals, "Last Help", REG_DWORD, (uint8_t *)&last_help, sizeof(last_help));
+ regval_ctr_addvalue(regvals, "Version", REG_DWORD, (uint8_t *)&version, sizeof(version));
+
+ return regval_ctr_numvals( regvals );
+}
+
+static int perflib_009_params(struct regval_ctr *regvals)
+{
+ int base_index;
+ int buffer_size;
+ char *buffer = NULL;
+
+ base_index = reg_perfcount_get_base_index();
+ buffer_size = reg_perfcount_get_counter_names(base_index, &buffer);
+ regval_ctr_addvalue(regvals, "Counter", REG_MULTI_SZ, (uint8_t *)buffer, buffer_size);
+ if(buffer_size > 0)
+ SAFE_FREE(buffer);
+ buffer_size = reg_perfcount_get_counter_help(base_index, &buffer);
+ regval_ctr_addvalue(regvals, "Help", REG_MULTI_SZ, (uint8_t *)buffer, buffer_size);
+ if(buffer_size > 0)
+ SAFE_FREE(buffer);
+
+ return regval_ctr_numvals( regvals );
+}
+
+static int perflib_fetch_values(const char *key, struct regval_ctr *regvals)
+{
+ char *path = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ path = talloc_strdup(ctx, key);
+ if (path == NULL) {
+ return -1;
+ }
+ path = normalize_reg_path(ctx, path);
+ if (path == NULL) {
+ return -1;
+ }
+
+ if (strncmp(path, KEY_PERFLIB_NORM, strlen(path)) == 0) {
+ return perflib_params(regvals);
+ } else if (strncmp(path, KEY_PERFLIB_009_NORM, strlen(path)) == 0) {
+ return perflib_009_params(regvals);
+ } else {
+ return 0;
+ }
+}
+
+static int perflib_fetch_subkeys(const char *key,
+ struct regsubkey_ctr *subkey_ctr)
+{
+ return regdb_ops.fetch_subkeys(key, subkey_ctr);
+}
+
+struct registry_ops perflib_reg_ops = {
+ .fetch_values = perflib_fetch_values,
+ .fetch_subkeys = perflib_fetch_subkeys,
+};
diff --git a/source3/registry/reg_backend_printing.c b/source3/registry/reg_backend_printing.c
new file mode 100644
index 0000000..3b5e7bf
--- /dev/null
+++ b/source3/registry/reg_backend_printing.c
@@ -0,0 +1,281 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (c) Andreas Schneider <asn@samba.org> 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of registry virtual views for printing information */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_util_internal.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops;
+
+/* registry paths used in the print_registry[] */
+#define KEY_CONTROL_PRINTERS "HKLM\\SYSTEM\\CURRENTCONTROLSET\\CONTROL\\PRINT\\PRINTERS"
+#define KEY_WINNT_PRINTERS "HKLM\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\PRINT\\PRINTERS"
+
+/* callback table for various registry paths below the ones we service in this module */
+
+struct reg_dyn_tree {
+ /* full key path in normalized form */
+ const char *path;
+
+ /* callbscks for fetch/store operations */
+ int ( *fetch_subkeys) ( const char *path, struct regsubkey_ctr *subkeys );
+ bool (*store_subkeys) ( const char *path, struct regsubkey_ctr *subkeys );
+ int (*fetch_values) ( const char *path, struct regval_ctr *values );
+ bool (*store_values) ( const char *path, struct regval_ctr *values );
+};
+
+/*********************************************************************
+ *********************************************************************
+ ** "HKLM/SYSTEM/CURRENTCONTROLSET/CONTROL/PRINT/PRINTERS"
+ ** "HKLM/SOFTWARE/MICROSOFT/WINDOWS NT/CURRENTVERSION/PRINT/PRINTERS"
+ *********************************************************************
+ *********************************************************************/
+
+static char *create_printer_registry_path(TALLOC_CTX *mem_ctx, const char *key) {
+ char *path;
+ char *subkey = NULL;
+
+ path = talloc_strdup(mem_ctx, key);
+ if (path == NULL) {
+ return NULL;
+ }
+
+ path = normalize_reg_path(mem_ctx, path);
+ if (path == NULL) {
+ return NULL;
+ }
+
+ if (strncmp(path, KEY_CONTROL_PRINTERS, strlen(KEY_CONTROL_PRINTERS)) == 0) {
+ subkey = reg_remaining_path(mem_ctx, key + strlen(KEY_CONTROL_PRINTERS));
+ if (subkey == NULL) {
+ return NULL;
+ }
+ return talloc_asprintf(mem_ctx, "%s\\%s", KEY_WINNT_PRINTERS, subkey);
+ }
+
+ return NULL;
+}
+
+/*********************************************************************
+ *********************************************************************/
+
+static int key_printers_fetch_keys( const char *key, struct regsubkey_ctr *subkeys )
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *printers_key;
+
+ printers_key = create_printer_registry_path(ctx, key);
+ if (printers_key == NULL) {
+ /* normalize on the 'HKLM\SOFTWARE\....\Print\Printers' key */
+ return regdb_ops.fetch_subkeys(KEY_WINNT_PRINTERS, subkeys);
+ }
+
+ return regdb_ops.fetch_subkeys(printers_key, subkeys);
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static bool key_printers_store_keys( const char *key, struct regsubkey_ctr *subkeys )
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *printers_key;
+
+ printers_key = create_printer_registry_path(ctx, key);
+ if (printers_key == NULL) {
+ /* normalize on the 'HKLM\SOFTWARE\....\Print\Printers' key */
+ return regdb_ops.store_subkeys(KEY_WINNT_PRINTERS, subkeys);
+ }
+
+ return regdb_ops.store_subkeys(printers_key, subkeys);
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static int key_printers_fetch_values(const char *key, struct regval_ctr *values)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *printers_key;
+
+ printers_key = create_printer_registry_path(ctx, key);
+ if (printers_key == NULL) {
+ /* normalize on the 'HKLM\SOFTWARE\....\Print\Printers' key */
+ return regdb_ops.fetch_values(KEY_WINNT_PRINTERS, values);
+ }
+
+ return regdb_ops.fetch_values(printers_key, values);
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static bool key_printers_store_values(const char *key, struct regval_ctr *values)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ char *printers_key;
+
+ printers_key = create_printer_registry_path(ctx, key);
+ if (printers_key == NULL) {
+ /* normalize on the 'HKLM\SOFTWARE\....\Print\Printers' key */
+ return regdb_ops.store_values(KEY_WINNT_PRINTERS, values);
+ }
+
+ return regdb_ops.store_values(printers_key, values);
+}
+
+/**********************************************************************
+ *********************************************************************
+ ** Structure to hold dispatch table of ops for various printer keys.
+ ** Make sure to always store deeper keys along the same path first so
+ ** we ge a more specific match.
+ *********************************************************************
+ *********************************************************************/
+
+static struct reg_dyn_tree print_registry[] = {
+{ KEY_CONTROL_PRINTERS,
+ &key_printers_fetch_keys,
+ &key_printers_store_keys,
+ &key_printers_fetch_values,
+ &key_printers_store_values },
+
+{ NULL, NULL, NULL, NULL, NULL }
+};
+
+
+/**********************************************************************
+ *********************************************************************
+ ** Main reg_printing interface functions
+ *********************************************************************
+ *********************************************************************/
+
+/***********************************************************************
+ Lookup a key in the print_registry table, returning its index.
+ -1 on failure
+ **********************************************************************/
+
+static int match_registry_path(const char *key)
+{
+ int i;
+ char *path = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if ( !key )
+ return -1;
+
+ path = talloc_strdup(ctx, key);
+ if (!path) {
+ return -1;
+ }
+ path = normalize_reg_path(ctx, path);
+ if (!path) {
+ return -1;
+ }
+
+ for ( i=0; print_registry[i].path; i++ ) {
+ if (strncmp( path, print_registry[i].path, strlen(print_registry[i].path) ) == 0 )
+ return i;
+ }
+
+ return -1;
+}
+
+/***********************************************************************
+ **********************************************************************/
+
+static int regprint_fetch_reg_keys( const char *key, struct regsubkey_ctr *subkeys )
+{
+ int i = match_registry_path( key );
+
+ if ( i == -1 )
+ return -1;
+
+ if ( !print_registry[i].fetch_subkeys )
+ return -1;
+
+ return print_registry[i].fetch_subkeys( key, subkeys );
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static bool regprint_store_reg_keys( const char *key, struct regsubkey_ctr *subkeys )
+{
+ int i = match_registry_path( key );
+
+ if ( i == -1 )
+ return False;
+
+ if ( !print_registry[i].store_subkeys )
+ return False;
+
+ return print_registry[i].store_subkeys( key, subkeys );
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static int regprint_fetch_reg_values(const char *key, struct regval_ctr *values)
+{
+ int i = match_registry_path( key );
+
+ if ( i == -1 )
+ return -1;
+
+ /* return 0 values by default since we know the key had
+ to exist because the client opened a handle */
+
+ if ( !print_registry[i].fetch_values )
+ return 0;
+
+ return print_registry[i].fetch_values( key, values );
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static bool regprint_store_reg_values(const char *key, struct regval_ctr *values)
+{
+ int i = match_registry_path( key );
+
+ if ( i == -1 )
+ return False;
+
+ if ( !print_registry[i].store_values )
+ return False;
+
+ return print_registry[i].store_values( key, values );
+}
+
+/*
+ * Table of function pointers for accessing printing data
+ */
+
+struct registry_ops printing_ops = {
+ .fetch_subkeys = regprint_fetch_reg_keys,
+ .fetch_values = regprint_fetch_reg_values,
+ .store_subkeys = regprint_store_reg_keys,
+ .store_values = regprint_store_reg_values,
+};
diff --git a/source3/registry/reg_backend_prod_options.c b/source3/registry/reg_backend_prod_options.c
new file mode 100644
index 0000000..7bd3f32
--- /dev/null
+++ b/source3/registry/reg_backend_prod_options.c
@@ -0,0 +1,68 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Product options registry backend.
+ *
+ * This replaces the former dynamic product options overlay.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_objects.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops;
+
+static int prod_options_fetch_values(const char *key, struct regval_ctr *regvals)
+{
+ const char *value_ascii = "";
+
+ switch (lp_server_role()) {
+ case ROLE_DOMAIN_PDC:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_IPA_DC:
+ value_ascii = "LanmanNT";
+ break;
+ case ROLE_STANDALONE:
+ value_ascii = "ServerNT";
+ break;
+ case ROLE_DOMAIN_MEMBER:
+ value_ascii = "WinNT";
+ break;
+ }
+
+ regval_ctr_addvalue_sz(regvals, "ProductType", value_ascii);
+
+ return regval_ctr_numvals( regvals );
+}
+
+static int prod_options_fetch_subkeys(const char *key,
+ struct regsubkey_ctr *subkey_ctr)
+{
+ return regdb_ops.fetch_subkeys(key, subkey_ctr);
+}
+
+struct registry_ops prod_options_reg_ops = {
+ .fetch_values = prod_options_fetch_values,
+ .fetch_subkeys = prod_options_fetch_subkeys,
+};
diff --git a/source3/registry/reg_backend_shares.c b/source3/registry/reg_backend_shares.c
new file mode 100644
index 0000000..ffe95a6
--- /dev/null
+++ b/source3/registry/reg_backend_shares.c
@@ -0,0 +1,165 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of registry virtual views for printing information */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_objects.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+/**********************************************************************
+ It is safe to assume that every registry path passed into one of
+ the exported functions here begins with KEY_SHARES else
+ these functions would have never been called. This is a small utility
+ function to strip the beginning of the path and make a copy that the
+ caller can modify. Note that the caller is responsible for releasing
+ the memory allocated here.
+ **********************************************************************/
+
+static char* trim_reg_path( const char *path )
+{
+ const char *p;
+ uint16_t key_len = strlen(KEY_SHARES);
+
+ /*
+ * sanity check...this really should never be True.
+ * It is only here to prevent us from accessing outside
+ * the path buffer in the extreme case.
+ */
+
+ if ( strlen(path) < key_len ) {
+ DEBUG(0,("trim_reg_path: Registry path too short! [%s]\n", path));
+ return NULL;
+ }
+
+ p = path + strlen( KEY_SHARES );
+
+ if ( *p == '\\' )
+ p++;
+
+ if ( *p )
+ return SMB_STRDUP(p);
+ else
+ return NULL;
+}
+
+/**********************************************************************
+ Enumerate registry subkey names given a registry path.
+ Caller is responsible for freeing memory to **subkeys
+ *********************************************************************/
+
+static int shares_subkey_info( const char *key, struct regsubkey_ctr *subkey_ctr )
+{
+ char *path;
+ bool top_level = False;
+ int num_subkeys = 0;
+
+ DEBUG(10, ("shares_subkey_info: key=>[%s]\n", key));
+
+ path = trim_reg_path( key );
+
+ /* check to see if we are dealing with the top level key */
+
+ if ( !path )
+ top_level = True;
+
+ if ( top_level ) {
+ num_subkeys = 1;
+ regsubkey_ctr_addkey( subkey_ctr, "Security" );
+ }
+#if 0
+ else
+ num_subkeys = handle_share_subpath( path, subkey_ctr, NULL );
+#endif
+
+ SAFE_FREE( path );
+
+ return num_subkeys;
+}
+
+/**********************************************************************
+ Enumerate registry values given a registry path.
+ Caller is responsible for freeing memory
+ *********************************************************************/
+
+static int shares_value_info(const char *key, struct regval_ctr *val)
+{
+ char *path;
+ bool top_level = False;
+ int num_values = 0;
+
+ DEBUG(10, ("shares_value_info: key=>[%s]\n", key));
+
+ path = trim_reg_path( key );
+
+ /* check to see if we are dealing with the top level key */
+
+ if ( !path )
+ top_level = True;
+
+ /* fill in values from the getprinterdata_printer_server() */
+ if ( top_level )
+ num_values = 0;
+#if 0
+ else
+ num_values = handle_printing_subpath( path, NULL, val );
+#endif
+
+ SAFE_FREE(path);
+
+ return num_values;
+}
+
+/**********************************************************************
+ Stub function which always returns failure since we don't want
+ people storing share information directly via registry calls
+ (for now at least)
+ *********************************************************************/
+
+static bool shares_store_subkey( const char *key, struct regsubkey_ctr *subkeys )
+{
+ return False;
+}
+
+/**********************************************************************
+ Stub function which always returns failure since we don't want
+ people storing share information directly via registry calls
+ (for now at least)
+ *********************************************************************/
+
+static bool shares_store_value(const char *key, struct regval_ctr *val)
+{
+ return False;
+}
+
+/*
+ * Table of function pointers for accessing printing data
+ */
+
+struct registry_ops shares_reg_ops = {
+ .fetch_subkeys = shares_subkey_info,
+ .fetch_values = shares_value_info,
+ .store_subkeys = shares_store_subkey,
+ .store_values = shares_store_value,
+};
+
+
diff --git a/source3/registry/reg_backend_smbconf.c b/source3/registry/reg_backend_smbconf.c
new file mode 100644
index 0000000..001a5f7
--- /dev/null
+++ b/source3/registry/reg_backend_smbconf.c
@@ -0,0 +1,110 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "lib/privileges.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops; /* these are the default */
+
+static int smbconf_fetch_keys( const char *key, struct regsubkey_ctr *subkey_ctr )
+{
+ return regdb_ops.fetch_subkeys(key, subkey_ctr);
+}
+
+static bool smbconf_store_keys( const char *key, struct regsubkey_ctr *subkeys )
+{
+ return regdb_ops.store_subkeys(key, subkeys);
+}
+
+static WERROR smbconf_create_subkey(const char *key, const char *subkey)
+{
+ return regdb_ops.create_subkey(key, subkey);
+}
+
+static WERROR smbconf_delete_subkey(const char *key, const char *subkey, bool lazy)
+{
+ return regdb_ops.delete_subkey(key, subkey, lazy);
+}
+
+static int smbconf_fetch_values(const char *key, struct regval_ctr *val)
+{
+ return regdb_ops.fetch_values(key, val);
+}
+
+static bool smbconf_store_values(const char *key, struct regval_ctr *val)
+{
+ return regdb_ops.store_values(key, val);
+}
+
+static bool smbconf_reg_access_check(const char *keyname, uint32_t requested,
+ uint32_t *granted,
+ const struct security_token *token)
+{
+ if (!security_token_has_privilege(token, SEC_PRIV_DISK_OPERATOR)) {
+ return False;
+ }
+
+ *granted = REG_KEY_ALL;
+ return True;
+}
+
+static WERROR smbconf_get_secdesc(TALLOC_CTX *mem_ctx, const char *key,
+ struct security_descriptor **psecdesc)
+{
+ return regdb_ops.get_secdesc(mem_ctx, key, psecdesc);
+}
+
+static WERROR smbconf_set_secdesc(const char *key,
+ struct security_descriptor *secdesc)
+{
+ return regdb_ops.set_secdesc(key, secdesc);
+}
+
+static bool smbconf_subkeys_need_update(struct regsubkey_ctr *subkeys)
+{
+ return regdb_ops.subkeys_need_update(subkeys);
+}
+
+static bool smbconf_values_need_update(struct regval_ctr *values)
+{
+ return regdb_ops.values_need_update(values);
+}
+
+/*
+ * Table of function pointers for accessing smb.conf data
+ */
+
+struct registry_ops smbconf_reg_ops = {
+ .fetch_subkeys = smbconf_fetch_keys,
+ .fetch_values = smbconf_fetch_values,
+ .store_subkeys = smbconf_store_keys,
+ .store_values = smbconf_store_values,
+ .create_subkey = smbconf_create_subkey,
+ .delete_subkey = smbconf_delete_subkey,
+ .reg_access_check = smbconf_reg_access_check,
+ .get_secdesc = smbconf_get_secdesc,
+ .set_secdesc = smbconf_set_secdesc,
+ .subkeys_need_update = smbconf_subkeys_need_update,
+ .values_need_update = smbconf_values_need_update,
+};
diff --git a/source3/registry/reg_backend_tcpip_params.c b/source3/registry/reg_backend_tcpip_params.c
new file mode 100644
index 0000000..dd132df
--- /dev/null
+++ b/source3/registry/reg_backend_tcpip_params.c
@@ -0,0 +1,54 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * TCP/IP parameters registry backend.
+ *
+ * This replaces the former dynamic tcpip parameters overlay.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_objects.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops regdb_ops;
+
+static int tcpip_params_fetch_values(const char *key, struct regval_ctr *regvals)
+{
+ regval_ctr_addvalue_sz(regvals, "Hostname", myhostname());
+
+ regval_ctr_addvalue_sz(regvals, "Domain", get_mydnsdomname(talloc_tos()));
+
+ return regval_ctr_numvals(regvals);
+}
+
+static int tcpip_params_fetch_subkeys(const char *key,
+ struct regsubkey_ctr *subkey_ctr)
+{
+ return regdb_ops.fetch_subkeys(key, subkey_ctr);
+}
+
+struct registry_ops tcpip_params_reg_ops = {
+ .fetch_values = tcpip_params_fetch_values,
+ .fetch_subkeys = tcpip_params_fetch_subkeys,
+};
diff --git a/source3/registry/reg_cachehook.c b/source3/registry/reg_cachehook.c
new file mode 100644
index 0000000..95e77cc
--- /dev/null
+++ b/source3/registry/reg_cachehook.c
@@ -0,0 +1,147 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002.
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of registry hook cache tree */
+
+#include "includes.h"
+#include "adt_tree.h"
+#include "registry.h"
+#include "reg_cachehook.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+static struct sorted_tree *cache_tree = NULL;
+extern struct registry_ops regdb_ops; /* these are the default */
+
+static WERROR keyname_to_path(TALLOC_CTX *mem_ctx, const char *keyname,
+ char **path)
+{
+ char *tmp_path = NULL;
+
+ if ((keyname == NULL) || (path == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ tmp_path = talloc_asprintf(mem_ctx, "\\%s", keyname);
+ if (tmp_path == NULL) {
+ DEBUG(0, ("talloc_asprintf failed!\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *path = tmp_path;
+
+ return WERR_OK;
+}
+
+/**********************************************************************
+ Initialize the cache tree if it has not been initialized yet.
+ *********************************************************************/
+
+WERROR reghook_cache_init(void)
+{
+ if (cache_tree != NULL) {
+ return WERR_OK;
+ }
+
+ cache_tree = pathtree_init(&regdb_ops);
+ if (cache_tree == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ DEBUG(10, ("reghook_cache_init: new tree with default "
+ "ops %p for key [%s]\n", (void *)&regdb_ops,
+ KEY_TREE_ROOT));
+ return WERR_OK;
+}
+
+/**********************************************************************
+ Add a new registry hook to the cache. Note that the keyname
+ is not in the exact format that a struct sorted_tree expects.
+ *********************************************************************/
+
+WERROR reghook_cache_add(const char *keyname, struct registry_ops *ops)
+{
+ WERROR werr;
+ char *key = NULL;
+
+ if ((keyname == NULL) || (ops == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = keyname_to_path(talloc_tos(), keyname, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ DEBUG(10, ("reghook_cache_add: Adding ops %p for key [%s]\n",
+ (void *)ops, key));
+
+ if (!pathtree_add(cache_tree, key, ops))
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ else
+ werr = WERR_OK;
+
+done:
+ TALLOC_FREE(key);
+ return werr;
+}
+
+/**********************************************************************
+ Find a key in the cache.
+ *********************************************************************/
+
+struct registry_ops *reghook_cache_find(const char *keyname)
+{
+ WERROR werr;
+ char *key = NULL;
+ struct registry_ops *ops = NULL;
+
+ if (keyname == NULL) {
+ return NULL;
+ }
+
+ werr = keyname_to_path(talloc_tos(), keyname, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ DEBUG(10,("reghook_cache_find: Searching for keyname [%s]\n", key));
+
+ ops = (struct registry_ops *)pathtree_find(cache_tree, key);
+
+ DEBUG(10, ("reghook_cache_find: found ops %p for key [%s]\n",
+ ops ? (void *)ops : 0, key));
+
+done:
+ TALLOC_FREE(key);
+
+ return ops;
+}
+
+/**********************************************************************
+ Print out the cache tree structure for debugging.
+ *********************************************************************/
+
+void reghook_dump_cache( int debuglevel )
+{
+ DEBUG(debuglevel,("reghook_dump_cache: Starting cache dump now...\n"));
+
+ pathtree_print_keys( cache_tree, debuglevel );
+}
diff --git a/source3/registry/reg_cachehook.h b/source3/registry/reg_cachehook.h
new file mode 100644
index 0000000..7c901c0
--- /dev/null
+++ b/source3/registry/reg_cachehook.h
@@ -0,0 +1,29 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002.
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_CACHEHOOK_H
+#define _REG_CACHEHOOK_H
+
+WERROR reghook_cache_init(void);
+WERROR reghook_cache_add(const char *keyname, struct registry_ops *ops);
+struct registry_ops *reghook_cache_find(const char *keyname);
+void reghook_dump_cache( int debuglevel );
+
+#endif /* _REG_CACHEHOOK_H */
diff --git a/source3/registry/reg_db.h b/source3/registry/reg_db.h
new file mode 100644
index 0000000..f2f79d2
--- /dev/null
+++ b/source3/registry/reg_db.h
@@ -0,0 +1,37 @@
+/*
+ Parameters for Samba's Internal Registry Database
+
+ Copyright (C) Michael Adam 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _REG_DB_H
+#define _REG_DB_H
+
+#define REG_TDB_FLAGS TDB_SEQNUM
+#define REG_DBWRAP_FLAGS DBWRAP_FLAG_NONE
+
+#define REGDB_VERSION_V1 1 /* first db version with write support */
+#define REGDB_VERSION_V2 2 /* version 2 with normalized keys */
+#define REGDB_VERSION_V3 3 /* different definition of key existence, */
+ /* sorted subkeys cache removed. */
+
+#define REGDB_CODE_VERSION REGDB_VERSION_V3
+
+#define REG_VALUE_PREFIX "SAMBA_REGVAL"
+#define REG_SECDESC_PREFIX "SAMBA_SECDESC"
+#define REG_SORTED_SUBKEYS_PREFIX "SAMBA_SORTED_SUBKEYS"
+
+#endif /* _REG_DB_H */
diff --git a/source3/registry/reg_dispatcher.c b/source3/registry/reg_dispatcher.c
new file mode 100644
index 0000000..ab3fb24
--- /dev/null
+++ b/source3/registry/reg_dispatcher.c
@@ -0,0 +1,264 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2006-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Implementation of registry frontend view functions.
+ * Functions moved from reg_frontend.c to minimize linker deps.
+ */
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "registry.h"
+#include "reg_dispatcher.h"
+#include "../libcli/security/security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+static const struct generic_mapping reg_generic_map =
+ { REG_KEY_READ, REG_KEY_WRITE, REG_KEY_EXECUTE, REG_KEY_ALL };
+
+/********************************************************************
+********************************************************************/
+
+static WERROR construct_registry_sd(TALLOC_CTX *ctx, struct security_descriptor **psd)
+{
+ struct security_ace ace[3];
+ size_t i = 0;
+ struct security_descriptor *sd;
+ struct security_acl *theacl;
+ size_t sd_size;
+
+ /* basic access for Everyone */
+
+ init_sec_ace(&ace[i++], &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ REG_KEY_READ, 0);
+
+ /* Full Access 'BUILTIN\Administrators' */
+
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, REG_KEY_ALL, 0);
+
+ /* Full Access 'NT Authority\System' */
+
+ init_sec_ace(&ace[i++], &global_sid_System, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ REG_KEY_ALL, 0);
+
+ /* create the security descriptor */
+
+ theacl = make_sec_acl(ctx, NT4_ACL_REVISION, i, ace);
+ if (theacl == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ sd = make_sec_desc(ctx, SD_REVISION, SEC_DESC_SELF_RELATIVE,
+ &global_sid_Builtin_Administrators,
+ &global_sid_System, NULL, theacl,
+ &sd_size);
+ if (sd == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *psd = sd;
+ return WERR_OK;
+}
+
+/***********************************************************************
+ High level wrapper function for storing registry subkeys
+ ***********************************************************************/
+
+bool store_reg_keys(struct registry_key_handle *key,
+ struct regsubkey_ctr *subkeys)
+{
+ if (key->ops && key->ops->store_subkeys)
+ return key->ops->store_subkeys(key->name, subkeys);
+
+ return false;
+}
+
+/***********************************************************************
+ High level wrapper function for storing registry values
+ ***********************************************************************/
+
+bool store_reg_values(struct registry_key_handle *key, struct regval_ctr *val)
+{
+ if (key->ops && key->ops->store_values)
+ return key->ops->store_values(key->name, val);
+
+ return false;
+}
+
+WERROR create_reg_subkey(struct registry_key_handle *key, const char *subkey)
+{
+ if (key->ops && key->ops->create_subkey) {
+ return key->ops->create_subkey(key->name, subkey);
+ }
+
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR delete_reg_subkey(struct registry_key_handle *key, const char *subkey, bool lazy)
+{
+ if (key->ops && key->ops->delete_subkey) {
+ return key->ops->delete_subkey(key->name, subkey, lazy);
+ }
+
+ return WERR_NOT_SUPPORTED;
+}
+
+/***********************************************************************
+ High level wrapper function for enumerating registry subkeys
+ Initialize the TALLOC_CTX if necessary
+ ***********************************************************************/
+
+int fetch_reg_keys(struct registry_key_handle *key,
+ struct regsubkey_ctr *subkey_ctr)
+{
+ int result = -1;
+
+ if (key->ops && key->ops->fetch_subkeys)
+ result = key->ops->fetch_subkeys(key->name, subkey_ctr);
+
+ return result;
+}
+
+/***********************************************************************
+ High level wrapper function for enumerating registry values
+ ***********************************************************************/
+
+int fetch_reg_values(struct registry_key_handle *key, struct regval_ctr *val)
+{
+ int result = -1;
+
+ DEBUG(10, ("fetch_reg_values called for key '%s' (ops %p)\n", key->name,
+ (key->ops) ? (void *)key->ops : NULL));
+
+ if (key->ops && key->ops->fetch_values)
+ result = key->ops->fetch_values(key->name, val);
+
+ return result;
+}
+
+/***********************************************************************
+ High level access check for passing the required access mask to the
+ underlying registry backend
+ ***********************************************************************/
+
+bool regkey_access_check(struct registry_key_handle *key, uint32_t requested,
+ uint32_t *granted,
+ const struct security_token *token )
+{
+ struct security_descriptor *sec_desc;
+ NTSTATUS status;
+ WERROR err;
+
+ /* root free-pass, like we have on all other pipes like samr, lsa, etc. */
+ if (root_mode()) {
+ *granted = REG_KEY_ALL;
+ return true;
+ }
+
+ /* use the default security check if the backend has not defined its
+ * own */
+
+ if (key->ops && key->ops->reg_access_check) {
+ return key->ops->reg_access_check(key->name, requested,
+ granted, token);
+ }
+
+ err = regkey_get_secdesc(talloc_tos(), key, &sec_desc);
+
+ if (!W_ERROR_IS_OK(err)) {
+ return false;
+ }
+
+ se_map_generic( &requested, &reg_generic_map );
+
+ status =se_access_check(sec_desc, token, requested, granted);
+ TALLOC_FREE(sec_desc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ return NT_STATUS_IS_OK(status);
+}
+
+WERROR regkey_get_secdesc(TALLOC_CTX *mem_ctx, struct registry_key_handle *key,
+ struct security_descriptor **psecdesc)
+{
+ struct security_descriptor *secdesc;
+ WERROR werr;
+
+ if (key->ops && key->ops->get_secdesc) {
+ werr = key->ops->get_secdesc(mem_ctx, key->name, psecdesc);
+ if (W_ERROR_IS_OK(werr)) {
+ return WERR_OK;
+ }
+ }
+
+ werr = construct_registry_sd(mem_ctx, &secdesc);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ *psecdesc = secdesc;
+ return WERR_OK;
+}
+
+WERROR regkey_set_secdesc(struct registry_key_handle *key,
+ struct security_descriptor *psecdesc)
+{
+ if (key->ops && key->ops->set_secdesc) {
+ return key->ops->set_secdesc(key->name, psecdesc);
+ }
+
+ return WERR_ACCESS_DENIED;
+}
+
+/**
+ * Check whether the in-memory version of the subkyes of a
+ * registry key needs update from disk.
+ */
+bool reg_subkeys_need_update(struct registry_key_handle *key,
+ struct regsubkey_ctr *subkeys)
+{
+ if (key->ops && key->ops->subkeys_need_update)
+ {
+ return key->ops->subkeys_need_update(subkeys);
+ }
+
+ return true;
+}
+
+/**
+ * Check whether the in-memory version of the values of a
+ * registry key needs update from disk.
+ */
+bool reg_values_need_update(struct registry_key_handle *key,
+ struct regval_ctr *values)
+{
+ if (key->ops && key->ops->values_need_update)
+ {
+ return key->ops->values_need_update(values);
+ }
+
+ return true;
+}
+
diff --git a/source3/registry/reg_dispatcher.h b/source3/registry/reg_dispatcher.h
new file mode 100644
index 0000000..76485b3
--- /dev/null
+++ b/source3/registry/reg_dispatcher.h
@@ -0,0 +1,44 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2006-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_DISPATCHER_H
+#define _REG_DISPATCHER_H
+
+bool store_reg_keys(struct registry_key_handle *key,
+ struct regsubkey_ctr *subkeys);
+bool store_reg_values(struct registry_key_handle *key, struct regval_ctr *val);
+WERROR create_reg_subkey(struct registry_key_handle *key, const char *subkey);
+WERROR delete_reg_subkey(struct registry_key_handle *key, const char *subkey, bool lazy);
+int fetch_reg_keys(struct registry_key_handle *key,
+ struct regsubkey_ctr *subkey_ctr);
+int fetch_reg_values(struct registry_key_handle *key, struct regval_ctr *val);
+bool regkey_access_check(struct registry_key_handle *key, uint32_t requested,
+ uint32_t *granted,
+ const struct security_token *token);
+WERROR regkey_get_secdesc(TALLOC_CTX *mem_ctx, struct registry_key_handle *key,
+ struct security_descriptor **psecdesc);
+WERROR regkey_set_secdesc(struct registry_key_handle *key,
+ struct security_descriptor *psecdesc);
+bool reg_subkeys_need_update(struct registry_key_handle *key,
+ struct regsubkey_ctr *subkeys);
+bool reg_values_need_update(struct registry_key_handle *key,
+ struct regval_ctr *values);
+
+#endif /* _REG_DISPATCHER_H */
diff --git a/source3/registry/reg_format.c b/source3/registry/reg_format.c
new file mode 100644
index 0000000..f353f79
--- /dev/null
+++ b/source3/registry/reg_format.c
@@ -0,0 +1,830 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Format dot.reg files
+ * @file reg_format.c
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Sep 2010
+ */
+
+#include "includes.h"
+#include "reg_format.h"
+#include "reg_parse.h"
+#include "reg_parse_internal.h"
+#include "cbuf.h"
+#include "srprs.h"
+#include "registry.h"
+#include "registry/reg_objects.h"
+#include <assert.h>
+
+static void cstr_unescape(char* val)
+{
+ all_string_sub(val, "\\r", "\r", 0);
+ all_string_sub(val, "\\n", "\n", 0);
+ all_string_sub(val, "\\t", "\t", 0);
+ all_string_sub(val, "\\\\", "\\", 0);
+}
+
+/******************************************************************************/
+
+/**
+ * Print value assign to stream.
+ *
+ * @param[out] ost outstream
+ * @param[in] name string
+ *
+ * @return number of bytes written, -1 on error
+ * @see srprs_val_name
+ */
+static int cbuf_print_value_assign(cbuf* ost, const char* name) {
+ size_t ret = 0;
+ int n;
+ if (*name == '\0') {
+ n = cbuf_putc(ost, '@');
+ } else {
+ n = cbuf_print_quoted_string(ost, name);
+ }
+ if (n < 0) {
+ return n;
+ }
+ ret += n;
+
+ n = cbuf_putc(ost, '=');
+ if (n < 0) {
+ return n;
+ }
+ ret += n;
+
+ return ret;
+}
+
+enum fmt_hive {
+ FMT_HIVE_PRESERVE=0,
+ FMT_HIVE_SHORT,
+ FMT_HIVE_LONG
+};
+
+
+struct fmt_key {
+ enum fmt_hive hive_fmt;
+ enum fmt_case hive_case;
+ enum fmt_case key_case;
+ const char* sep;
+};
+
+
+static int
+cbuf_print_hive(cbuf* ost, const char* hive, int len, const struct fmt_key* fmt)
+{
+ if (fmt->hive_fmt != FMT_HIVE_PRESERVE) {
+ const struct hive_info* hinfo = hive_info(hive);
+ if (hinfo == NULL) {
+ DEBUG(0, ("Unknown hive %*s\n", len, hive));
+ } else {
+ switch(fmt->hive_fmt) {
+ case FMT_HIVE_SHORT:
+ hive = hinfo->short_name;
+ len = hinfo->short_name_len;
+ break;
+ case FMT_HIVE_LONG:
+ hive = hinfo->long_name;
+ len = hinfo->long_name_len;
+ break;
+ default:
+ DEBUG(0, ("Unsupported hive format %d\n",
+ (int)fmt->hive_fmt));
+ return -1;
+ }
+ }
+ }
+
+ return cbuf_puts_case(ost, hive, len, fmt->hive_case);
+}
+
+static int
+cbuf_print_keyname(cbuf* ost, const char* key[], int n, const struct fmt_key* fmt)
+{
+ int r;
+ size_t ret = 0;
+ size_t pos = cbuf_getpos(ost);
+ bool hive = true;
+
+ for (; n>0; key++, n--) {
+ const char* start = *key;
+ while(*start != '\0') {
+ const char* end = start;
+ while(*end != '\\' && *end != '\0') {
+ end++;
+ }
+
+ if (hive) {
+ r = cbuf_print_hive(ost, start, end-start, fmt);
+ if (r < 0) {
+ goto fail;
+ }
+
+ ret += r;
+ hive = false;
+ } else {
+ r = cbuf_puts(ost, fmt->sep, -1);
+ if (r < 0) {
+ goto fail;
+ }
+ ret += r;
+
+ r = cbuf_puts_case(ost, start, end-start, fmt->key_case);
+ if (r < 0) {
+ goto fail;
+ }
+ ret += r;
+ }
+
+ while(*end == '\\') {
+ end++;
+ }
+ start = end;
+ }
+ }
+ return ret;
+fail:
+ cbuf_setpos(ost, pos);
+ return r;
+}
+/**@}*/
+
+/**
+ * @defgroup reg_format Format dot.reg file.
+ * @{
+ */
+
+struct reg_format
+{
+ struct reg_parse_callback reg_parse_callback;
+ struct reg_format_callback call;
+ unsigned flags;
+ smb_iconv_t fromUTF16;
+ const char* sep;
+};
+
+int reg_format_value_delete(struct reg_format* f, const char* name)
+{
+ int ret;
+ cbuf* line = cbuf_new(f);
+
+ ret = cbuf_print_value_assign(line, name);
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = cbuf_putc(line, '-');
+ if (ret < 0 ) {
+ goto done;
+ }
+
+ ret = f->call.writeline(f->call.data, cbuf_gets(line, 0));
+done:
+ talloc_free(line);
+ return ret;
+}
+
+/* Todo: write hex if str contains CR or LF */
+static int
+reg_format_value_sz(struct reg_format* f, const char* name, const char* str)
+{
+ int ret;
+ cbuf* line = cbuf_new(f);
+
+ ret = cbuf_print_value_assign(line, name);
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = cbuf_print_quoted_string(line, str);
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = f->call.writeline(f->call.data, cbuf_gets(line, 0));
+
+done:
+ talloc_free(line);
+ return ret;
+}
+
+static int reg_format_value_dw(struct reg_format* f, const char* name, uint32_t dw)
+{
+ int ret;
+ cbuf* line = cbuf_new(f);
+
+ ret = cbuf_print_value_assign(line, name);
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = cbuf_printf(line, "dword:%08x", dw);
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = f->call.writeline(f->call.data, cbuf_gets(line, 0));
+done:
+ talloc_free(line);
+ return ret;
+}
+
+static int reg_format_value_hex(struct reg_format* f, const char* name, uint32_t type,
+ const void* data, size_t len)
+{
+ int n;
+ int cpl=0;
+ int ret=0;
+ const unsigned char* ptr;
+
+ cbuf* line = cbuf_new(f);
+
+ n = cbuf_print_value_assign(line, name);
+ if (n < 0) {
+ ret = n;
+ goto done;
+ }
+
+ cpl += n;
+
+ if (type==REG_BINARY && !(f->flags & REG_FMT_HEX_BIN)) {
+ n=cbuf_puts(line, "hex:", -1);
+ } else {
+ n=cbuf_printf(line, "hex(%x):", type);
+ }
+ if (n < 0) {
+ ret = n;
+ goto done;
+ }
+
+ cpl += n;
+
+ for (ptr=(const unsigned char *)data; len>1; len--,ptr++) {
+ n = cbuf_printf(line, "%02x,", (unsigned)(*ptr));
+ if (n < 0) {
+ return n;
+ }
+ cpl += n;
+
+ if ( cpl > 76 ) {
+ n = cbuf_putc(line, '\\');
+ if (n< 0) {
+ return n;
+ }
+
+ n = f->call.writeline(f->call.data, cbuf_gets(line,0));
+ if (n < 0) {
+ ret = n;
+ goto done;
+ }
+ ret += n;
+
+ cbuf_clear(line);
+ cpl = cbuf_puts(line, " ", -1);
+ if (cpl < 0) {
+ ret = cpl;
+ goto done;
+ }
+ }
+ }
+
+ if ( len > 0 ) {
+ n = cbuf_printf(line, "%02x", (unsigned)(*ptr));
+ if (n < 0) {
+ ret = n;
+ goto done;
+ }
+ cpl += n;
+ }
+
+ n = f->call.writeline(f->call.data, cbuf_gets(line,0));
+ if (n < 0) {
+ ret = n;
+ goto done;
+ }
+ ret += n;
+done:
+ talloc_free(line);
+ return ret;
+}
+
+static bool is_zero_terminated_ucs2(const uint8_t* data, size_t len) {
+ const size_t idx = len/sizeof(smb_ucs2_t);
+ const smb_ucs2_t *str = (const smb_ucs2_t*)data;
+
+ if ((len % sizeof(smb_ucs2_t)) != 0) {
+ return false;
+ }
+
+ if (idx == 0) {
+ return false;
+ }
+
+ return (str[idx-1] == 0);
+}
+
+int reg_format_value(struct reg_format* f, const char* name, uint32_t type,
+ const uint8_t* data, size_t len)
+{
+ int ret = 0;
+ void* mem_ctx = talloc_new(f);
+
+ switch (type) {
+ case REG_SZ:
+ if (!(f->flags & REG_FMT_HEX_SZ)
+ && is_zero_terminated_ucs2(data, len))
+ {
+ char* str = NULL;
+ size_t dlen;
+ if (pull_ucs2_talloc(mem_ctx, &str, (const smb_ucs2_t*)data, &dlen)) {
+ ret = reg_format_value_sz(f, name, str);
+ goto done;
+ } else {
+ DEBUG(0, ("reg_format_value %s: "
+ "pull_ucs2_talloc failed"
+ ", try to write hex\n", name));
+ }
+ }
+ break;
+
+ case REG_DWORD:
+ if (!(f->flags & REG_FMT_HEX_SZ) && (len == sizeof(uint32_t))) {
+ uint32_t dw = IVAL(data,0);
+ ret = reg_format_value_dw(f, name, dw);
+ goto done;
+ }
+ break;
+
+ case REG_MULTI_SZ:
+ case REG_EXPAND_SZ:
+ if (f->fromUTF16 && (f->fromUTF16 != ((smb_iconv_t)-1))) {
+ char* str = NULL;
+ size_t dlen = iconvert_talloc(mem_ctx, f->fromUTF16,
+ (const char*)data, len, &str);
+ if (dlen != -1) {
+ ret = reg_format_value_hex(f, name, type, str, dlen);
+ goto done;
+ } else {
+ DEBUG(0, ("reg_format_value %s: "
+ "iconvert_talloc failed"
+ ", try to write hex\n", name));
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ ret = reg_format_value_hex(f, name, type, data, len);
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+
+int reg_format_comment(struct reg_format* f, const char* txt)
+{
+ int ret;
+ cbuf* line = cbuf_new(f);
+
+ ret = cbuf_putc(line,';');
+ if (ret<0) {
+ goto done;
+ }
+
+ ret = cbuf_puts(line, txt, -1);
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = f->call.writeline(f->call.data, cbuf_gets(line, 0));
+done:
+ talloc_free(line);
+ return ret;
+}
+
+
+/******************************************************************************/
+
+
+
+struct reg_format* reg_format_new(const void* talloc_ctx,
+ struct reg_format_callback cb,
+ const char* str_enc, unsigned flags,
+ const char* sep)
+{
+ static const struct reg_parse_callback reg_parse_callback_default = {
+ .key = (reg_parse_callback_key_t)&reg_format_key,
+ .val = (reg_parse_callback_val_t)&reg_format_value,
+ .val_del = (reg_parse_callback_val_del_t)&reg_format_value_delete,
+ .comment = (reg_parse_callback_comment_t)&reg_format_comment,
+ };
+
+ struct reg_format* f = talloc_zero(talloc_ctx, struct reg_format);
+ if (f == NULL) {
+ return NULL;
+ }
+
+ f->reg_parse_callback = reg_parse_callback_default;
+ f->reg_parse_callback.data = f;
+
+ f->call = cb;
+ f->flags = flags;
+ f->sep = sep;
+
+ if (str_enc && !set_iconv(&f->fromUTF16, str_enc, "UTF-16LE")) {
+ DEBUG(0, ("reg_format_new: failed to set encoding: %s\n",
+ str_enc));
+ goto fail;
+ }
+
+ assert(&f->reg_parse_callback == (struct reg_parse_callback*)f);
+ return f;
+fail:
+ talloc_free(f);
+ return NULL;
+}
+
+int reg_format_set_options(struct reg_format* fmt, const char* options)
+{
+ static const char* DEFAULT ="enc=unix,flags=0,sep=\\";
+
+ int ret = 0;
+ char *key, *val;
+ void* ctx = talloc_new(fmt);
+
+ if (options == NULL) {
+ options = DEFAULT;
+ }
+
+ while (srprs_option(&options, ctx, &key, &val)) {
+ if ((strcmp(key, "enc") == 0) || (strcmp(key, "strenc") == 0)) {
+ if (!set_iconv(&fmt->fromUTF16, val, "UTF-16LE")) {
+ DEBUG(0, ("Failed to set encoding: %s\n", val));
+ ret = -1;
+ }
+ } else if ((strcmp(key, "flags") == 0) && (val != NULL)) {
+ char* end = NULL;
+ if (val != NULL) {
+ fmt->flags = strtol(val, &end, 0);
+ }
+ if ((end==NULL) || (*end != '\0')) {
+ DEBUG(0, ("Invalid flags format: %s\n",
+ val ? val : "<NULL>"));
+ ret = -1;
+ }
+ } else if ((strcmp(key, "sep") == 0) && (val != NULL)) {
+ cstr_unescape(val);
+ fmt->sep = talloc_steal(fmt, val);
+ }
+
+ /* else if (strcmp(key, "hive") == 0) { */
+ /* if (strcmp(val, "short") == 0) { */
+ /* f->hive_fmt = REG_FMT_SHORT_HIVES; */
+ /* } else if (strcmp(val, "long") == 0) { */
+ /* f->hive_fmt = REG_FMT_LONG_HIVES; */
+ /* } else if (strcmp(val, "preserve") == 0) { */
+ /* f->hive_fmt = REG_FMT_PRESERVE_HIVES; */
+ /* } else { */
+ /* DEBUG(0, ("Invalid hive format: %s\n", val)); */
+ /* ret = -1; */
+ /* } */
+ /* } */
+ }
+ talloc_free(ctx);
+ return ret;
+}
+
+int reg_format_key(struct reg_format* f, const char* key[], size_t n, bool del)
+{
+ int ret, r;
+ cbuf* line = cbuf_new(f);
+ struct fmt_key key_fmt = {
+ .key_case = (f->flags >> 4) & 0x0F,
+ .hive_case = (f->flags >> 8) & 0x0F,
+ .hive_fmt = (f->flags >> 12) & 0x0F,
+ .sep = f->sep,
+ };
+
+ ret = cbuf_putc(line, '[');
+ if (ret < 0) {
+ goto done;
+ }
+
+ if (del) {
+ ret = cbuf_putc(line, '-');
+ if (ret < 0) {
+ goto done;
+ }
+ }
+
+ ret = cbuf_print_keyname(line, key, n, &key_fmt);
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = cbuf_putc(line, ']');
+ if (ret < 0) {
+ goto done;
+ }
+
+ ret = f->call.writeline(f->call.data, "");
+ if (ret < 0) {
+ goto done;
+ }
+
+ r = f->call.writeline(f->call.data, cbuf_gets(line, 0));
+ if (r < 0) {
+ ret = r;
+ goto done;
+ }
+ ret += r;
+
+done:
+ talloc_free(line);
+ return ret;
+}
+
+
+int reg_format_registry_key(struct reg_format* f, struct registry_key* key,
+ bool del)
+{
+ const char *knames[1];
+ knames[0] = key->key->name;
+ return reg_format_key(f, knames, 1, del);
+}
+
+int reg_format_registry_value(struct reg_format* f, const char* name,
+ struct registry_value* val)
+{
+ return reg_format_value(f, name, val->type,
+ val->data.data, val->data.length);
+}
+
+int reg_format_regval_blob(struct reg_format* f, const char* name,
+ struct regval_blob* val)
+{
+
+ return reg_format_value(f,
+ name ? name : regval_name(val),
+ regval_type(val),
+ regval_data_p(val),
+ regval_size(val));
+}
+
+/**@}*/
+
+
+struct reg_format_file
+{
+ FILE* file;
+ const char* encoding;
+ smb_iconv_t fromUnix;
+ char* nl;
+ size_t nl_len;
+};
+
+
+static int reg_format_file_close(struct reg_format* fmt)
+{
+ struct reg_format_file* fmt_ctx
+ = (struct reg_format_file*) fmt->call.data;
+ int ret = 0;
+ FILE* file = fmt_ctx->file;
+
+ if (fmt_ctx->encoding) {
+ char buf[32];
+ snprintf(buf, sizeof(buf), "coding: %s", fmt_ctx->encoding);
+ reg_format_comment(fmt, "Local Variables:");
+ reg_format_comment(fmt, buf);
+ reg_format_comment(fmt, "End:");
+ }
+
+ if (file != NULL) {
+ ret = fclose(file);
+ }
+
+ return ret;
+}
+
+static int reg_format_file_writeline(void* ptr, const char* line)
+{
+ size_t size;
+ char* dst=NULL;
+ struct reg_format_file* fmt_ctx = (struct reg_format_file*)ptr;
+ int ret, r;
+
+ size = iconvert_talloc(ptr, fmt_ctx->fromUnix, line, strlen(line), &dst);
+ if (size == -1 ) {
+ DEBUG(0, ("reg_format_file_writeline: iconvert_talloc failed >%s<\n", line));
+ return -1;
+ }
+
+ ret = fwrite(dst, 1, size, fmt_ctx->file);
+ if (ret < 0) {
+ goto done;
+ }
+
+ r = fwrite(fmt_ctx->nl, 1, fmt_ctx->nl_len, fmt_ctx->file);
+ ret = (r < 0) ? r : ret + r;
+
+done:
+ talloc_free(dst);
+ return ret;
+}
+
+struct reg_format_file_opt {
+ const char* head;
+ const char* nl;
+ const char* enc;
+ bool bom;
+ const char* str_enc;
+ unsigned flags;
+ const char* sep;
+};
+
+static struct reg_format_file_opt reg_format_file_opt(void* mem_ctx, const char* opt)
+{
+ static const struct reg_format_file_opt REG4 = {
+ .head = "REGEDIT4",
+ .nl = "\r\n",
+ .enc = "dos",
+ .str_enc = "dos",
+ .bom = false,
+ .flags = (FMT_HIVE_LONG << 12),
+ .sep = "\\",
+ };
+
+ static const struct reg_format_file_opt REG5 = {
+ .head = "Windows Registry Editor Version 5.00",
+ .nl = "\r\n",
+ .enc = "UTF-16LE",
+ .str_enc = "UTF-16LE",
+ .bom = true,
+ .flags = (FMT_HIVE_LONG << 12),
+ .sep = "\\",
+ };
+
+ struct reg_format_file_opt ret = {
+ .head = REG5.head,
+ .nl = "\n",
+ .enc = "unix",
+ .bom = false,
+ .str_enc = "UTF-16LE",
+ .flags = 0,
+ .sep = "\\",
+ };
+
+ void* tmp_ctx = talloc_new(mem_ctx);
+
+ char *key, *val;
+
+ if (opt == NULL) {
+ goto done;
+ }
+
+ while(srprs_option(&opt, tmp_ctx, &key, &val)) {
+ if (strcmp(key, "enc") == 0) {
+ ret.enc = talloc_steal(mem_ctx, val);
+ ret.str_enc = ret.enc;
+ } else if (strcmp(key, "strenc") == 0) {
+ ret.str_enc = talloc_steal(mem_ctx, val);
+ } else if (strcmp(key, "fileenc") == 0) {
+ ret.enc = talloc_steal(mem_ctx, val);
+ } else if ((strcmp(key, "flags") == 0) && (val != NULL)) {
+ char* end = NULL;
+ if (val != NULL) {
+ ret.flags = strtol(val, &end, 0);
+ }
+ if ((end==NULL) || (*end != '\0')) {
+ DEBUG(0, ("Invalid flags format: %s\n",
+ val ? val : "<NULL>"));
+ }
+ } else if ((strcmp(key, "sep") == 0) && (val != NULL)) {
+ cstr_unescape(val);
+ ret.sep = talloc_steal(mem_ctx, val);
+ } else if (strcmp(key, "head") == 0) {
+ cstr_unescape(val);
+ ret.head = talloc_steal(mem_ctx, val);
+ } else if (strcmp(key, "nl") == 0) {
+ cstr_unescape(val);
+ ret.nl = talloc_steal(mem_ctx, val);
+ } else if (strcmp(key, "bom") == 0) {
+ if (val == NULL) {
+ ret.bom = true;
+ } else {
+ ret.bom = atoi(val);
+ }
+ } else if (strcmp(key, "regedit4") == 0) {
+ ret = REG4;
+ } else if (strcmp(key, "regedit5") == 0) {
+ ret = REG5;
+ }
+ }
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+
+struct reg_format* reg_format_file(const void* talloc_ctx,
+ const char* filename,
+ const char* options)
+{
+ struct reg_format_file* fmt_ctx;
+ struct reg_format* fmt;
+ int ret;
+ struct reg_format_file_opt opt;
+
+ struct reg_format_callback reg_format_cb = {
+ .writeline = &reg_format_file_writeline
+ };
+
+ fmt_ctx = talloc_zero(talloc_ctx, struct reg_format_file);
+ if (fmt_ctx == NULL) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ opt = reg_format_file_opt(fmt_ctx, options);
+
+ reg_format_cb.data = fmt_ctx;
+
+ fmt = reg_format_new(talloc_ctx, reg_format_cb,
+ opt.str_enc, opt.flags, opt.sep);
+ if (fmt == NULL) {
+ errno = ENOMEM;
+ talloc_free(fmt_ctx);
+ return NULL;
+ }
+
+ talloc_steal(fmt, fmt_ctx);
+
+ if (!set_iconv(&fmt->fromUTF16, opt.str_enc, "UTF-16LE")) { /* HACK */
+ DEBUG(0, ("reg_format_file: failed to set string encoding %s\n",
+ opt.str_enc));
+ goto fail;
+ }
+
+ if (!set_iconv(&fmt_ctx->fromUnix, opt.enc, "unix")) {
+ DEBUG(0, ("reg_format_file: failed to set file encoding %s\n",
+ opt.enc));
+ goto fail;
+ }
+ fmt_ctx->encoding = talloc_strdup(fmt_ctx, smbreg_get_charset(opt.enc));
+
+ fmt_ctx->file = fopen(filename, "w");
+ if (fmt_ctx->file == NULL) {
+ DEBUG(0, ("reg_format_file: fopen failed: %s\n", strerror(errno)));
+ goto fail;
+ }
+
+ if (setvbuf(fmt_ctx->file, NULL, _IOFBF, 64000) < 0) {
+ DEBUG(0, ("reg_format_file: setvbuf failed: %s\n", strerror(errno)));
+ }
+
+ talloc_set_destructor(fmt, reg_format_file_close);
+
+ fmt_ctx->nl_len = iconvert_talloc(fmt, fmt_ctx->fromUnix, opt.nl, strlen(opt.nl), &fmt_ctx->nl);
+ if (fmt_ctx->nl_len == -1) {
+ DEBUG(0, ("iconvert_talloc failed\n"));
+ goto fail;
+ }
+
+ if (opt.bom) {
+ ret = write_bom(fmt_ctx->file, opt.enc, -1);
+ if (ret < 0) {
+ goto fail;
+ }
+ }
+
+ ret = fmt->call.writeline(fmt->call.data, opt.head);
+ if (ret < 0) {
+ goto fail;
+ }
+
+ return fmt;
+fail:
+ talloc_free(fmt);
+ return NULL;
+}
diff --git a/source3/registry/reg_format.h b/source3/registry/reg_format.h
new file mode 100644
index 0000000..dfeb5b8
--- /dev/null
+++ b/source3/registry/reg_format.h
@@ -0,0 +1,219 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Format registration entries (.reg) files.
+ * A formatter is a talloced incarnation of an opaque struct reg_format.
+ * It is fed with registry key's and value's and emits output by calling
+ * writeline from its reg_format_callback.
+ * @file reg_format.h
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Sep 2010
+ */
+#ifndef __REG_FORMAT_H
+#define __REG_FORMAT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+
+struct registry_key;
+struct registry_value;
+struct regval_blob;
+
+
+/**
+ * A Formatter for registration entries (.reg) files.
+ *
+ * It may be used as a reg_parse_callback, so the following is valid:
+ * @code
+ * reg_parse* p = reg_parse_new(mem_ctx,
+ * (reg_parse_callback)reg_format_new(mem_ctx, cb, NULL, 0, "\\"),
+ * NULL, 0);
+ * @endcode
+ * @see reg_parse
+ */
+typedef struct reg_format reg_format;
+
+/**
+ * Prototype for function called to output a line.
+ *
+ * @param private_data
+ * @param line line to write in UNIX charset
+ *
+ * @return number of characters written, < 0 on error
+ *
+ * @see reg_parse
+ */
+typedef int (*reg_format_callback_writeline_t)(void* private_data,
+ const char* line);
+/**
+ * Type handling the output of a reg_format object.
+ * It contains the functions to call and an opaque data pointer.
+ */
+typedef struct reg_format_callback {
+ /**< Function called to write a line */
+ reg_format_callback_writeline_t writeline;
+ void* data; /**< Private data passed to callback function */
+} reg_format_callback;
+
+/**
+ * Create a new reg_format object.
+ *
+ * @param talloc_ctx the talloc parent
+ * @param cb the output handler
+ * @param str_enc the charset of hex encoded strings (REG_MULTI_SZ, REG_EXAND_SZ) if not UTF-16
+ * @param flags
+ * @param sep the separator for subkeys
+ *
+ * @return a talloc'ed reg_format object, NULL on error
+ */
+reg_format* reg_format_new(const void* talloc_ctx,
+ reg_format_callback cb,
+ const char* str_enc,
+ unsigned flags,
+ const char* sep);
+
+/**
+ * Create a new reg_format object, writing to a file.
+ *
+ * @param talloc_ctx the talloc parent
+ * @param filename the file to write to
+ * @param options
+ *
+ * @return a talloc'ed reg_format object, NULL on error
+ */
+reg_format* reg_format_file(const void* talloc_ctx,
+ const char* filename,
+ const char* options);
+
+/**
+ * Format a registry key given as struct registry_key.
+ * Create/Open or Delete
+ *
+ * @param f the formatter.
+ * @param key the key to output.
+ * @param del whether to format the deletion of the key
+ *
+ * @retval >= 0 on success.
+ */
+int reg_format_registry_key(reg_format* f,
+ struct registry_key* key,
+ bool del);
+
+/**
+ * Format a registry value given as struct registry_value.
+ *
+ * @param f the formatter.
+ * @param name the values name
+ * @param val the values value.
+ *
+ * @retval >= 0 on success.
+ */
+int reg_format_registry_value(reg_format* f,
+ const char* name,
+ struct registry_value* val);
+
+/**
+ * Format a registry value given as struct regval_blob.
+ *
+ * @param f the formatter.
+ * @param name the values name, if NULL use val->valname which is limited in size;
+ * @param val the values value.
+ *
+ * @retval >= 0 on success.
+ */
+int reg_format_regval_blob(reg_format* f,
+ const char* name,
+ struct regval_blob* val);
+
+
+/**
+ * Format deletion of a registry value.
+ *
+ * @param f the formatter.
+ * @param name the values name
+ *
+ * @retval >= 0 on success.
+ *
+ * @see reg_parse_callback_val_del_t
+ */
+int reg_format_value_delete(reg_format* f, const char* name);
+
+/**
+ * Format a comment.
+ *
+ * @param f the formatter.
+ * @param txt the comment in UNIX charset, may not contain newlines.
+ *
+ * @retval >= 0 on success.
+ *
+ * @see reg_parse_callback_comment_t
+ */
+int reg_format_comment(reg_format* f, const char* txt);
+
+
+int reg_format_set_options(reg_format* f, const char* options);
+
+
+/* reg_format flags */
+#define REG_FMT_HEX_SZ 1
+#define REG_FMT_HEX_DW 2
+#define REG_FMT_HEX_BIN 4
+#define REG_FMT_HEX_ALL (REG_FMT_HEX_SZ | REG_FMT_HEX_DW | REG_FMT_HEX_BIN);
+#define REG_FMT_LONG_HIVES 16
+#define REG_FMT_SHORT_HIVES 32
+
+/* lowlevel */
+
+/**
+ * Format a registry key.
+ * Create/Open or Delete
+ *
+ * @param f the formatter
+ * @param key the key to output
+ * @param klen number of elements in key
+ * @param del whether to format the deletion of the key
+ *
+ * @retval >= 0 on success.
+ *
+ * @see reg_parse_callback_key_t
+ */
+int reg_format_key(reg_format* f,
+ const char* key[], size_t klen,
+ bool del);
+
+/**
+ * Format a registry value.
+ *
+ * @param f the formatter
+ * @param name the values name
+ * @param type the values type
+ * @param data the values value
+ * @param len the number of bytes of data
+ *
+ * @retval >= 0 on success.
+ *
+ * @see reg_parse_callback_val_hex_t
+ */
+int reg_format_value(reg_format* f,
+ const char* name, uint32_t type,
+ const uint8_t* data, size_t len);
+
+#endif /* __REG_FORMAT_H */
diff --git a/source3/registry/reg_import.c b/source3/registry/reg_import.c
new file mode 100644
index 0000000..aef8645
--- /dev/null
+++ b/source3/registry/reg_import.c
@@ -0,0 +1,314 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Adapter to use reg_parse with the registry api
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "reg_parse.h"
+#include "reg_import.h"
+#include "registry.h"
+#include "registry/reg_objects.h"
+#include <assert.h>
+
+/* Debuglevel for tracing */
+static const int TL = 2;
+
+struct reg_import {
+ struct reg_parse_callback reg_parse_callback;
+ struct reg_import_callback call;
+ void *open_key;
+};
+
+static int reg_parse_callback_key(struct reg_import *cb_private,
+ const char *key[], size_t n, bool del);
+
+static int reg_parse_callback_val(struct reg_import *cb_private,
+ const char *name, uint32_t type,
+ const uint8_t *data, size_t len);
+
+static int reg_parse_callback_val_registry_value(struct reg_import *cb_private,
+ const char *name,
+ uint32_t type,
+ const uint8_t *data,
+ size_t len);
+
+static int reg_parse_callback_val_regval_blob(struct reg_import *cb_private,
+ const char *name, uint32_t type,
+ const uint8_t *data,
+ size_t len);
+
+static int reg_parse_callback_val_del(struct reg_import *cb_private,
+ const char *name);
+
+static int reg_parse_callback_comment(struct reg_import *cb_private,
+ const char *txt);
+
+
+/*******************************************************************************/
+
+int reg_parse_callback_key(struct reg_import *p,
+ const char *key[], size_t n, bool del)
+{
+ WERROR werr = WERR_OK;
+
+ DEBUG(TL, ("%s: %s\n", __FUNCTION__, key[0]));
+
+ if (p->open_key != NULL) {
+ werr = p->call.closekey(p->call.data, p->open_key);
+ p->open_key = NULL;
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("closekey failed: %s\n", win_errstr(werr)));
+ }
+ }
+
+ if (del) {
+ werr = p->call.deletekey(p->call.data, NULL, key[0]);
+ if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) {
+ /* the key didn't exist, treat as success */
+ werr = WERR_OK;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("deletekey %s failed: %s\n",
+ key[0], win_errstr(werr)));
+ }
+ } else {
+ bool existing;
+ werr = p->call.createkey(p->call.data, NULL, key[0],
+ &p->open_key, &existing);
+ if (W_ERROR_IS_OK(werr)) {
+ DEBUG(TL, ("createkey %s %s\n",
+ existing ? "opened" : "created", key[0]));
+ } else {
+ DEBUG(0, ("createkey %s failed: %s\n",
+ key[0], win_errstr(werr)));
+ }
+ }
+
+ return W_ERROR_IS_OK(werr) ? 0 : -1;
+}
+
+#define DEBUG_ADD_HEX(LEV, PTR, LEN) \
+ do { \
+ int i; \
+ const unsigned char* ptr = (const unsigned char*)PTR; \
+ for (i=0; i<LEN; i++) { \
+ DEBUGADD(LEV, ("'%c'(%02x)%s", \
+ isprint(ptr[i]) ? ptr[i] : '.', \
+ (unsigned)ptr[i], \
+ ((i+1 < LEN) && (i+1)%8) \
+ ? ", " : "\n")); \
+ } \
+ } while(0)
+
+/*----------------------------------------------------------------------------*/
+int reg_parse_callback_val(struct reg_import *p,
+ const char *name, uint32_t type,
+ const uint8_t *data, size_t len)
+{
+ WERROR werr = WERR_OK;
+
+ DEBUG(TL, ("%s(%x): >%s< = [%zx]\n", __FUNCTION__, type, name, len));
+ DEBUG_ADD_HEX(TL, data, len);
+
+ werr = p->call.setval.blob(p->call.data, p->open_key, name, type,
+ data, len);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("setval %s failed: %s\n",
+ name, win_errstr(werr)));
+ }
+
+ return W_ERROR_IS_OK(werr) ? 0 : -1;
+}
+
+/*----------------------------------------------------------------------------*/
+int reg_parse_callback_val_registry_value(struct reg_import *p,
+ const char *name, uint32_t type,
+ const uint8_t *data, size_t len)
+{
+ WERROR werr = WERR_OK;
+ struct registry_value val = {
+ .type = type,
+ .data = data_blob_talloc(p, data, len),
+ };
+
+ DEBUG(TL, ("%s(%x): >%s< = [%zx]\n", __FUNCTION__, type, name, len));
+ DEBUG_ADD_HEX(TL, data, len);
+
+ werr = p->call.setval.registry_value(p->call.data, p->open_key,
+ name, &val);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("setval %s failed: %s\n",
+ name, win_errstr(werr)));
+ }
+
+ data_blob_free(&val.data);
+ return W_ERROR_IS_OK(werr) ? 0 : -1;
+}
+
+/*----------------------------------------------------------------------------*/
+int reg_parse_callback_val_regval_blob(struct reg_import *p,
+ const char *name, uint32_t type,
+ const uint8_t *data, size_t len)
+{
+ WERROR werr = WERR_OK;
+ void* mem_ctx = talloc_new(p);
+ struct regval_blob *v = NULL;
+
+ DEBUG(TL, ("%s(%x): >%s< = [%zx]\n", __FUNCTION__, type, name, len));
+ DEBUG_ADD_HEX(TL, data, len);
+
+ v = regval_compose(mem_ctx, name, type, data, len);
+ if (v == NULL) {
+ DEBUG(0, ("regval_compose %s failed\n", name));
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ werr = p->call.setval.regval_blob(p->call.data, p->open_key, v);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("setval %s failed: %s\n",
+ name, win_errstr(werr)));
+ }
+
+done:
+ talloc_free(mem_ctx);
+
+ return W_ERROR_IS_OK(werr) ? 0 : -1;
+}
+
+
+/*----------------------------------------------------------------------------*/
+
+int reg_parse_callback_val_del(struct reg_import *p,
+ const char *name)
+{
+ WERROR werr = WERR_OK;
+
+ DEBUG(TL, ("%s: %s\n", __FUNCTION__, name));
+
+ werr = p->call.deleteval(p->call.data, p->open_key, name);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("deleteval %s failed: %s\n",
+ name, win_errstr(werr)));
+ }
+
+ return W_ERROR_IS_OK(werr) ? 0 : -1;
+}
+
+
+int reg_parse_callback_comment(struct reg_import *cb_private,
+ const char *txt)
+{
+ DEBUG(TL, ("%s: %s\n", __FUNCTION__, txt));
+ return 0;
+}
+
+/******************************************************************************/
+static WERROR nop_callback_open(void* private_data,
+ void* parent,
+ const char* name,
+ void** key)
+{
+ return WERR_OK;
+}
+
+static WERROR nop_callback_close(void* private_data, void* key)
+{
+ return WERR_OK;
+}
+
+static WERROR nop_callback_create(void* private_data,
+ void* parent,
+ const char* name,
+ void** key,
+ bool* existing)
+{
+ return WERR_OK;
+}
+
+
+static WERROR nop_callback_del(void* private_data,
+ void* parent,
+ const char* name)
+{
+ return WERR_OK;
+}
+
+struct reg_parse_callback *reg_import_adapter(TALLOC_CTX *talloc_ctx,
+ struct reg_import_callback cb)
+{
+ struct reg_parse_callback *ret;
+ struct reg_import *p = talloc_zero(talloc_ctx, struct reg_import);
+ if (p == NULL) {
+ goto fail;
+ }
+ if (cb.openkey == NULL) {
+ cb.openkey = (reg_import_callback_openkey_t)&nop_callback_open;
+ }
+ if (cb.closekey == NULL) {
+ cb.closekey =
+ (reg_import_callback_closekey_t)&nop_callback_close;
+ }
+ if (cb.createkey == NULL) {
+ cb.createkey =
+ (reg_import_callback_createkey_t)&nop_callback_create;
+ }
+ if (cb.deletekey == NULL) {
+ cb.deletekey =
+ (reg_import_callback_deletekey_t)&nop_callback_del;
+
+ }
+ if (cb.deleteval == NULL) {
+ cb.deleteval =
+ (reg_import_callback_deleteval_t)&nop_callback_del;
+ }
+
+ p->call = cb;
+
+ ret = &p->reg_parse_callback;
+ ret->key = (reg_parse_callback_key_t) &reg_parse_callback_key;
+ ret->val_del = (reg_parse_callback_val_del_t) &reg_parse_callback_val_del;
+ ret->comment = (reg_parse_callback_comment_t) &reg_parse_callback_comment;
+ ret->data = p;
+
+ switch (cb.setval_type) {
+ case BLOB:
+ assert(cb.setval.blob != NULL);
+ ret->val = (reg_parse_callback_val_t) &reg_parse_callback_val;
+ break;
+ case REGISTRY_VALUE:
+ assert(cb.setval.registry_value != NULL);
+ ret->val = (reg_parse_callback_val_t) &reg_parse_callback_val_registry_value;
+ break;
+ case REGVAL_BLOB:
+ assert(cb.setval.regval_blob != NULL);
+ ret->val = (reg_parse_callback_val_t) &reg_parse_callback_val_regval_blob;
+ break;
+ case NONE:
+ ret->val = NULL;
+ break;
+ default:
+ assert(false);
+ }
+
+ assert((struct reg_parse_callback *)p == ret);
+ return ret;
+fail:
+ talloc_free(p);
+ return NULL;
+}
diff --git a/source3/registry/reg_import.h b/source3/registry/reg_import.h
new file mode 100644
index 0000000..31e8753
--- /dev/null
+++ b/source3/registry/reg_import.h
@@ -0,0 +1,199 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Adapter to use reg_parse with the registry api
+ * @file reg_import.h
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Jun 2010
+ */
+
+
+#ifndef REG_IMPORT_H
+#define REG_IMPORT_H
+
+#include "reg_parse.h"
+
+struct registry_value;
+struct regval_blob;
+
+/**
+ * Prototype for function called to open a key.
+ *
+ * @param private_data
+ * @param[in] parent the parent of the key to open, may be NULL
+ * @param[in] name the name of the key relative to parent.
+ * @param[out] key the opened key
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_openkey_t) (void* private_data,
+ void* parent,
+ const char* name,
+ void** key);
+
+/**
+ * Prototype for function called to close a key.
+ *
+ * @param private_data
+ * @param key the key to close
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_closekey_t) (void* private_data,
+ void* key);
+
+/**
+ * Prototype for function called to create (or open an existing) key.
+ *
+ * @param private_data
+ * @param[in] parent the parent of the key to create, may be NULL
+ * @param[in] name the name of the key relative to parent.
+ * @param[out] key the opened key
+ * @param[out] existing whether we opened an existing key
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_createkey_t)(void* private_data,
+ void* parent,
+ const char* name,
+ void** key,
+ bool* existing);
+
+/**
+ * Prototype for function called to delete a key.
+ *
+ * @param private_data
+ * @param parent the parent of the key to delete, may be NULL
+ * @param[in] name the name of the key relative to parent.
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_deletekey_t)(void* private_data,
+ void* parent,
+ const char* name);
+
+/**
+ * Prototype for function called to delete a value.
+ *
+ * @param private_data
+ * @param parent the key of the value to delete
+ * @param[in] name the name of the value to delete.
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_deleteval_t)(void* private_data,
+ void* parent,
+ const char* name);
+
+/**
+ * Prototype for function called to set a value.
+ *
+ * @param private_data
+ * @param parent the key of the value to set
+ * @param name the name of the value
+ * @param type the type of the value
+ * @param data the value of the value
+ * @param size the number of bytes of data
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_setval_blob_t)(void* private_data,
+ void* parent,
+ const char* name,
+ uint32_t type,
+ const uint8_t* data,
+ uint32_t size);
+
+/**
+ * Prototype for function called to set a value given as struct registry_value.
+ *
+ * @param private_data
+ * @param parent the key of the value to set
+ * @param name the name of the value
+ * @param val the value of the value
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_setval_registry_value_t) (
+ void* private_data,
+ void* parent,
+ const char* name,
+ const struct registry_value* val);
+
+/**
+ * Prototype for function called to set a value given as struct struct regval_blob.
+ *
+ * @param private_data
+ * @param parent the key of the value to set
+ * @param val the value
+ *
+ * @return WERR_OK on success
+ */
+typedef WERROR (*reg_import_callback_setval_regval_blob_t)(
+ void* private_data,
+ void* parent,
+ const struct regval_blob* val);
+
+/**
+ * Type handling the output of a reg_import object.
+ * It contains the functions to call and an opaque data pointer.
+ */
+struct reg_import_callback {
+ /** Function called to open key */
+ reg_import_callback_openkey_t openkey;
+ /** Function called to close key */
+ reg_import_callback_closekey_t closekey;
+ /** Function called to create or open key */
+ reg_import_callback_createkey_t createkey;
+ /** Function called to delete key */
+ reg_import_callback_deletekey_t deletekey;
+ /** Function called to delete value */
+ reg_import_callback_deleteval_t deleteval;
+
+ /** Function called to set value */
+ union {
+ reg_import_callback_setval_blob_t blob;
+ reg_import_callback_setval_registry_value_t registry_value;
+ reg_import_callback_setval_regval_blob_t regval_blob;
+ } setval;
+ /** Which function is called to set a value */
+ enum {
+ NONE=0, /**< no setval function used */
+ BLOB, /**< @ref reg_import_callback_setval_blob_t blob */
+ REGISTRY_VALUE, /**< @ref reg_import_callback_setval_registry_value_t registry_value */
+ REGVAL_BLOB, /**< @ref reg_import_callback_setval_regval_blob_t regval_blob */
+ } setval_type;
+ void* data; /**< Private data passed to callback function */
+};
+
+
+/**
+ * Create a new reg_import object.
+ * Because its only purpose is to act as an reg_parse_callback the return type
+ * is accordingly.
+ *
+ * @param talloc_ctx the talloc parent
+ * @param cb the output handler
+ *
+ * @return a talloc'ed reg_import object, NULL on error
+ */
+struct reg_parse_callback* reg_import_adapter(TALLOC_CTX *talloc_ctx,
+ struct reg_import_callback cb);
+#endif
diff --git a/source3/registry/reg_init_basic.c b/source3/registry/reg_init_basic.c
new file mode 100644
index 0000000..ea8077f
--- /dev/null
+++ b/source3/registry/reg_init_basic.c
@@ -0,0 +1,67 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Registry helper routines
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_init_basic.h"
+#include "reg_cachehook.h"
+#include "reg_backend_db.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+WERROR registry_init_common(void)
+{
+ WERROR werr;
+
+ werr = regdb_init();
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("Failed to initialize the registry: %s\n",
+ win_errstr(werr)));
+ goto done;
+ }
+
+ werr = reghook_cache_init();
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("Failed to initialize the reghook cache: %s\n",
+ win_errstr(werr)));
+ goto done;
+ }
+
+ /* setup the necessary keys and values */
+
+ werr = init_registry_data();
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("Failed to initialize data in registry!\n"));
+ }
+
+done:
+ return werr;
+}
+
+WERROR registry_init_basic(void)
+{
+ WERROR werr;
+
+ DEBUG(10, ("registry_init_basic called\n"));
+
+ werr = registry_init_common();
+ regdb_close();
+ return werr;
+}
diff --git a/source3/registry/reg_init_basic.h b/source3/registry/reg_init_basic.h
new file mode 100644
index 0000000..4149b5d
--- /dev/null
+++ b/source3/registry/reg_init_basic.h
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Registry helper routines
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_INIT_BASIC_H
+#define _REG_INIT_BASIC_H
+
+WERROR registry_init_common(void);
+WERROR registry_init_basic(void);
+
+#endif /* _REG_INIT_BASIC_H */
diff --git a/source3/registry/reg_init_full.c b/source3/registry/reg_init_full.c
new file mode 100644
index 0000000..ca78370
--- /dev/null
+++ b/source3/registry/reg_init_full.c
@@ -0,0 +1,102 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Initialize the registry with all available backends. */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_cachehook.h"
+#include "reg_backend_db.h"
+#include "reg_init_basic.h"
+#include "reg_init_full.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops printing_ops;
+extern struct registry_ops eventlog_ops;
+extern struct registry_ops shares_reg_ops;
+extern struct registry_ops smbconf_reg_ops;
+extern struct registry_ops netlogon_params_reg_ops;
+extern struct registry_ops prod_options_reg_ops;
+extern struct registry_ops tcpip_params_reg_ops;
+extern struct registry_ops hkpt_params_reg_ops;
+extern struct registry_ops current_version_reg_ops;
+extern struct registry_ops perflib_reg_ops;
+extern struct registry_ops regdb_ops; /* these are the default */
+
+/* array of registry_hook's which are read into a tree for easy access */
+/* #define REG_TDB_ONLY 1 */
+
+struct registry_hook {
+ const char *keyname; /* full path to name of key */
+ struct registry_ops *ops; /* registry function hooks */
+};
+
+struct registry_hook reg_hooks[] = {
+#ifndef REG_TDB_ONLY
+ { KEY_PRINTING "\\Printers", &printing_ops },
+ { KEY_PRINTING_2K, &regdb_ops },
+ { KEY_PRINTING_PORTS, &regdb_ops },
+ { KEY_PCC, &regdb_ops },
+ { KEY_SHARES, &shares_reg_ops },
+ { KEY_SMBCONF, &smbconf_reg_ops },
+ { KEY_NETLOGON_PARAMS, &netlogon_params_reg_ops },
+ { KEY_PROD_OPTIONS, &prod_options_reg_ops },
+ { KEY_TCPIP_PARAMS, &tcpip_params_reg_ops },
+ { KEY_HKPT, &hkpt_params_reg_ops },
+ { KEY_CURRENT_VERSION, &current_version_reg_ops },
+ { KEY_PERFLIB, &perflib_reg_ops },
+#endif
+ { NULL, NULL }
+};
+
+/***********************************************************************
+ Open the registry database and initialize the registry_hook cache
+ with all available backends.
+ ***********************************************************************/
+
+WERROR registry_init_full(void)
+{
+ int i;
+ WERROR werr;
+
+ werr = registry_init_common();
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* build the cache tree of registry hooks */
+
+ for ( i=0; reg_hooks[i].keyname; i++ ) {
+ werr = reghook_cache_add(reg_hooks[i].keyname, reg_hooks[i].ops);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+ }
+
+ if ( DEBUGLEVEL >= 20 )
+ reghook_dump_cache(20);
+
+fail:
+ /* close and let each smbd open up as necessary */
+ regdb_close();
+ return werr;
+}
diff --git a/source3/registry/reg_init_full.h b/source3/registry/reg_init_full.h
new file mode 100644
index 0000000..8ba327e
--- /dev/null
+++ b/source3/registry/reg_init_full.h
@@ -0,0 +1,27 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Registry helper routines
+ *
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_INIT_FULL_H
+#define _REG_INIT_FULL_H
+
+WERROR registry_init_full(void);
+
+#endif /* _REG_INIT_FULL_H */
diff --git a/source3/registry/reg_init_smbconf.c b/source3/registry/reg_init_smbconf.c
new file mode 100644
index 0000000..18ee455
--- /dev/null
+++ b/source3/registry/reg_init_smbconf.c
@@ -0,0 +1,71 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Registry helper routines
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_cachehook.h"
+#include "reg_backend_db.h"
+#include "reg_init_basic.h"
+#include "reg_init_smbconf.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+extern struct registry_ops smbconf_reg_ops;
+
+/*
+ * init the smbconf portion of the registry.
+ * for use in places where not the whole registry is needed,
+ * e.g. utils/net_conf.c and loadparm.c
+ */
+WERROR registry_init_smbconf(const char *keyname)
+{
+ WERROR werr;
+
+ DEBUG(10, ("registry_init_smbconf called\n"));
+
+ if (keyname == NULL) {
+ DEBUG(10, ("registry_init_smbconf: defaulting to key '%s'\n",
+ KEY_SMBCONF));
+ keyname = KEY_SMBCONF;
+ }
+
+ werr = registry_init_common();
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = init_registry_key(keyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("Failed to initialize registry key '%s': %s\n",
+ keyname, win_errstr(werr)));
+ goto done;
+ }
+
+ werr = reghook_cache_add(keyname, &smbconf_reg_ops);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("Failed to add smbconf reghooks to reghook cache: "
+ "%s\n", win_errstr(werr)));
+ goto done;
+ }
+
+done:
+ regdb_close();
+ return werr;
+}
diff --git a/source3/registry/reg_init_smbconf.h b/source3/registry/reg_init_smbconf.h
new file mode 100644
index 0000000..480d7af
--- /dev/null
+++ b/source3/registry/reg_init_smbconf.h
@@ -0,0 +1,27 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Registry helper routines
+ *
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_INIT_SMBCONF_H
+#define _REG_INIT_SMBCONF_H
+
+WERROR registry_init_smbconf(const char *keyname);
+
+#endif /* _REG_INIT_SMBCONF_H */
diff --git a/source3/registry/reg_objects.c b/source3/registry/reg_objects.c
new file mode 100644
index 0000000..20556b1
--- /dev/null
+++ b/source3/registry/reg_objects.c
@@ -0,0 +1,615 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ * Copyright (C) Gerald Carter 2002-2005
+ * Copyright (C) Michael Adam 2007-2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of registry frontend view functions. */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_objects.h"
+#include "util_tdb.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "../libcli/registry/util_reg.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+/* low level structure to contain registry values */
+
+struct regval_blob {
+ fstring valuename;
+ uint32_t type;
+ /* this should be encapsulated in an RPC_DATA_BLOB */
+ uint32_t size; /* in bytes */
+ uint8_t *data_p;
+};
+
+/* container for registry values */
+
+struct regval_ctr {
+ uint32_t num_values;
+ struct regval_blob **values;
+ int seqnum;
+};
+
+struct regsubkey_ctr {
+ uint32_t num_subkeys;
+ char **subkeys;
+ struct db_context *subkeys_hash;
+ int seqnum;
+};
+
+/**********************************************************************
+
+ Note that the struct regsubkey_ctr and struct regval_ctr objects *must* be
+ talloc()'d since the methods use the object pointer as the talloc
+ context for internal private data.
+
+ There is no longer a regval_ctr_init() and regval_ctr_destroy()
+ pair of functions. Simply talloc_zero() and TALLOC_FREE() the
+ object.
+
+ **********************************************************************/
+
+WERROR regsubkey_ctr_init(TALLOC_CTX *mem_ctx, struct regsubkey_ctr **ctr)
+{
+ if (ctr == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ *ctr = talloc_zero(mem_ctx, struct regsubkey_ctr);
+ if (*ctr == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ (*ctr)->subkeys_hash = db_open_rbt(*ctr);
+ if ((*ctr)->subkeys_hash == NULL) {
+ talloc_free(*ctr);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/**
+ * re-initialize the list of subkeys (to the empty list)
+ * in an already allocated regsubkey_ctr
+ */
+
+WERROR regsubkey_ctr_reinit(struct regsubkey_ctr *ctr)
+{
+ if (ctr == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ talloc_free(ctr->subkeys_hash);
+ ctr->subkeys_hash = db_open_rbt(ctr);
+ W_ERROR_HAVE_NO_MEMORY(ctr->subkeys_hash);
+
+ TALLOC_FREE(ctr->subkeys);
+
+ ctr->num_subkeys = 0;
+ ctr->seqnum = 0;
+
+ return WERR_OK;
+}
+
+WERROR regsubkey_ctr_set_seqnum(struct regsubkey_ctr *ctr, int seqnum)
+{
+ if (ctr == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ctr->seqnum = seqnum;
+
+ return WERR_OK;
+}
+
+int regsubkey_ctr_get_seqnum(struct regsubkey_ctr *ctr)
+{
+ if (ctr == NULL) {
+ return -1;
+ }
+
+ return ctr->seqnum;
+}
+
+static WERROR regsubkey_ctr_hash_keyname(struct regsubkey_ctr *ctr,
+ const char *keyname,
+ uint32_t idx)
+{
+ WERROR werr;
+
+ werr = ntstatus_to_werror(dbwrap_store_bystring_upper(ctr->subkeys_hash,
+ keyname,
+ make_tdb_data((uint8_t *)&idx,
+ sizeof(idx)),
+ TDB_REPLACE));
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("error hashing new key '%s' in container: %s\n",
+ keyname, win_errstr(werr)));
+ }
+
+ return werr;
+}
+
+static WERROR regsubkey_ctr_unhash_keyname(struct regsubkey_ctr *ctr,
+ const char *keyname)
+{
+ WERROR werr;
+
+ werr = ntstatus_to_werror(dbwrap_delete_bystring_upper(ctr->subkeys_hash,
+ keyname));
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("error unhashing key '%s' in container: %s\n",
+ keyname, win_errstr(werr)));
+ }
+
+ return werr;
+}
+
+static WERROR regsubkey_ctr_index_for_keyname(struct regsubkey_ctr *ctr,
+ const char *keyname,
+ uint32_t *idx)
+{
+ TDB_DATA data;
+ NTSTATUS status;
+
+ if ((ctr == NULL) || (keyname == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ status = dbwrap_fetch_bystring_upper(ctr->subkeys_hash, ctr, keyname,
+ &data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_FOUND;
+ }
+
+ if (data.dsize != sizeof(*idx)) {
+ talloc_free(data.dptr);
+ return WERR_INVALID_DATATYPE;
+ }
+
+ if (idx != NULL) {
+ memcpy(idx, data.dptr, sizeof(*idx));
+ }
+
+ talloc_free(data.dptr);
+ return WERR_OK;
+}
+
+/***********************************************************************
+ Add a new key to the array
+ **********************************************************************/
+
+WERROR regsubkey_ctr_addkey( struct regsubkey_ctr *ctr, const char *keyname )
+{
+ char **newkeys;
+ WERROR werr;
+
+ if ( !keyname ) {
+ return WERR_OK;
+ }
+
+ /* make sure the keyname is not already there */
+
+ if ( regsubkey_ctr_key_exists( ctr, keyname ) ) {
+ return WERR_OK;
+ }
+
+ if (!(newkeys = talloc_realloc(ctr, ctr->subkeys, char *,
+ ctr->num_subkeys+1))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ctr->subkeys = newkeys;
+
+ if (!(ctr->subkeys[ctr->num_subkeys] = talloc_strdup(ctr->subkeys,
+ keyname ))) {
+ /*
+ * Don't shrink the new array again, this wastes a pointer
+ */
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = regsubkey_ctr_hash_keyname(ctr, keyname, ctr->num_subkeys);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ ctr->num_subkeys++;
+
+ return WERR_OK;
+}
+
+ /***********************************************************************
+ Delete a key from the array
+ **********************************************************************/
+
+WERROR regsubkey_ctr_delkey( struct regsubkey_ctr *ctr, const char *keyname )
+{
+ WERROR werr;
+ uint32_t idx, j;
+
+ if (keyname == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* make sure the keyname is actually already there */
+
+ werr = regsubkey_ctr_index_for_keyname(ctr, keyname, &idx);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ werr = regsubkey_ctr_unhash_keyname(ctr, keyname);
+ W_ERROR_NOT_OK_RETURN(werr);
+
+ /* update if we have any keys left */
+ ctr->num_subkeys--;
+ if (idx < ctr->num_subkeys) {
+ memmove(&ctr->subkeys[idx], &ctr->subkeys[idx+1],
+ sizeof(char *) * (ctr->num_subkeys - idx));
+
+ /* we have to re-hash rest of the array... :-( */
+ for (j = idx; j < ctr->num_subkeys; j++) {
+ werr = regsubkey_ctr_hash_keyname(ctr, ctr->subkeys[j], j);
+ W_ERROR_NOT_OK_RETURN(werr);
+ }
+ }
+
+ return WERR_OK;
+}
+
+/***********************************************************************
+ Check for the existence of a key
+ **********************************************************************/
+
+bool regsubkey_ctr_key_exists( struct regsubkey_ctr *ctr, const char *keyname )
+{
+ WERROR werr;
+
+ if (!ctr->subkeys) {
+ return False;
+ }
+
+ werr = regsubkey_ctr_index_for_keyname(ctr, keyname, NULL);
+ if (!W_ERROR_IS_OK(werr)) {
+ return false;
+ }
+
+ return true;
+}
+
+/***********************************************************************
+ How many keys does the container hold ?
+ **********************************************************************/
+
+uint32_t regsubkey_ctr_numkeys( struct regsubkey_ctr *ctr )
+{
+ return ctr->num_subkeys;
+}
+
+/***********************************************************************
+ Retrieve a specific key string
+ **********************************************************************/
+
+char* regsubkey_ctr_specific_key( struct regsubkey_ctr *ctr, uint32_t key_index )
+{
+ if ( ! (key_index < ctr->num_subkeys) )
+ return NULL;
+
+ return ctr->subkeys[key_index];
+}
+
+/*
+ * Utility functions for struct regval_ctr
+ */
+
+/**
+ * allocate a regval_ctr structure.
+ */
+WERROR regval_ctr_init(TALLOC_CTX *mem_ctx, struct regval_ctr **ctr)
+{
+ if (ctr == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ *ctr = talloc_zero(mem_ctx, struct regval_ctr);
+ if (*ctr == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/***********************************************************************
+ How many keys does the container hold ?
+ **********************************************************************/
+
+uint32_t regval_ctr_numvals(struct regval_ctr *ctr)
+{
+ return ctr->num_values;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+uint8_t* regval_data_p(struct regval_blob *val)
+{
+ return val->data_p;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+uint32_t regval_size(struct regval_blob *val)
+{
+ return val->size;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+char* regval_name(struct regval_blob *val)
+{
+ return val->valuename;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+uint32_t regval_type(struct regval_blob *val)
+{
+ return val->type;
+}
+
+/***********************************************************************
+ Retrieve a pointer to a specific value. Caller should dup the structure
+ since this memory will go away when the ctr is free()'d
+ **********************************************************************/
+
+struct regval_blob *regval_ctr_specific_value(struct regval_ctr *ctr,
+ uint32_t idx)
+{
+ if ( !(idx < ctr->num_values) )
+ return NULL;
+
+ return ctr->values[idx];
+}
+
+/***********************************************************************
+ Check for the existence of a value
+ **********************************************************************/
+
+bool regval_ctr_value_exists(struct regval_ctr *ctr, const char *value)
+{
+ uint32_t i;
+
+ for ( i=0; i<ctr->num_values; i++ ) {
+ if ( strequal( ctr->values[i]->valuename, value) )
+ return True;
+ }
+
+ return False;
+}
+
+/**
+ * Get a value by its name
+ */
+struct regval_blob *regval_ctr_value_byname(struct regval_ctr *ctr,
+ const char *value)
+{
+ uint32_t i;
+
+ for (i = 0; i < ctr->num_values; i++) {
+ if (strequal(ctr->values[i]->valuename, value)) {
+ return ctr->values[i];
+ }
+ }
+
+ return NULL;
+}
+
+
+/***********************************************************************
+ * compose a struct regval_blob from input data
+ **********************************************************************/
+
+struct regval_blob *regval_compose(TALLOC_CTX *ctx, const char *name,
+ uint32_t type,
+ const uint8_t *data_p, size_t size)
+{
+ struct regval_blob *regval = talloc(ctx, struct regval_blob);
+
+ if (regval == NULL) {
+ return NULL;
+ }
+
+ fstrcpy(regval->valuename, name);
+ regval->type = type;
+ if (size) {
+ regval->data_p = (uint8_t *)talloc_memdup(regval, data_p, size);
+ if (!regval->data_p) {
+ TALLOC_FREE(regval);
+ return NULL;
+ }
+ } else {
+ regval->data_p = NULL;
+ }
+ regval->size = size;
+
+ return regval;
+}
+
+/***********************************************************************
+ Add a new registry value to the array
+ **********************************************************************/
+
+int regval_ctr_addvalue(struct regval_ctr *ctr, const char *name, uint32_t type,
+ const uint8_t *data_p, size_t size)
+{
+ if ( !name )
+ return ctr->num_values;
+
+ /* Delete the current value (if it exists) and add the new one */
+
+ regval_ctr_delvalue( ctr, name );
+
+ /* allocate a slot in the array of pointers */
+
+ if ( ctr->num_values == 0 ) {
+ ctr->values = talloc( ctr, struct regval_blob *);
+ } else {
+ ctr->values = talloc_realloc(ctr, ctr->values,
+ struct regval_blob *,
+ ctr->num_values+1);
+ }
+
+ if (!ctr->values) {
+ ctr->num_values = 0;
+ return 0;
+ }
+
+ /* allocate a new value and store the pointer in the array */
+
+ ctr->values[ctr->num_values] = regval_compose(ctr, name, type, data_p,
+ size);
+ if (ctr->values[ctr->num_values] == NULL) {
+ ctr->num_values = 0;
+ return 0;
+ }
+ ctr->num_values++;
+
+ return ctr->num_values;
+}
+
+/***********************************************************************
+ Add a new registry SZ value to the array
+ **********************************************************************/
+
+int regval_ctr_addvalue_sz(struct regval_ctr *ctr, const char *name, const char *data)
+{
+ DATA_BLOB blob;
+
+ if (!push_reg_sz(ctr, &blob, data)) {
+ return -1;
+ }
+
+ return regval_ctr_addvalue(ctr, name, REG_SZ,
+ (const uint8_t *)blob.data,
+ blob.length);
+}
+
+/***********************************************************************
+ Add a new registry MULTI_SZ value to the array
+ **********************************************************************/
+
+int regval_ctr_addvalue_multi_sz(struct regval_ctr *ctr, const char *name, const char **data)
+{
+ DATA_BLOB blob;
+
+ if (!push_reg_multi_sz(ctr, &blob, data)) {
+ return -1;
+ }
+
+ return regval_ctr_addvalue(ctr, name, REG_MULTI_SZ,
+ (const uint8_t *)blob.data,
+ blob.length);
+}
+
+/***********************************************************************
+ Add a new registry value to the array
+ **********************************************************************/
+
+int regval_ctr_copyvalue(struct regval_ctr *ctr, struct regval_blob *val)
+{
+ if ( val ) {
+ regval_ctr_addvalue(ctr, val->valuename, val->type,
+ (uint8_t *)val->data_p, val->size);
+ }
+
+ return ctr->num_values;
+}
+
+/***********************************************************************
+ Delete a single value from the registry container.
+ No need to free memory since it is talloc'd.
+ **********************************************************************/
+
+int regval_ctr_delvalue(struct regval_ctr *ctr, const char *name)
+{
+ uint32_t i;
+
+ for ( i=0; i<ctr->num_values; i++ ) {
+ if ( strequal( ctr->values[i]->valuename, name ) )
+ break;
+ }
+
+ /* just return if we don't find it */
+
+ if ( i == ctr->num_values )
+ return ctr->num_values;
+
+ /* If 'i' was not the last element, just shift everything down one */
+ ctr->num_values--;
+ if ( i < ctr->num_values )
+ memmove(&ctr->values[i], &ctr->values[i+1],
+ sizeof(struct regval_blob*)*(ctr->num_values-i));
+
+ return ctr->num_values;
+}
+
+/***********************************************************************
+ Retrieve single value from the registry container.
+ No need to free memory since it is talloc'd.
+ **********************************************************************/
+
+struct regval_blob* regval_ctr_getvalue(struct regval_ctr *ctr,
+ const char *name)
+{
+ uint32_t i;
+
+ /* search for the value */
+
+ for ( i=0; i<ctr->num_values; i++ ) {
+ if ( strequal( ctr->values[i]->valuename, name ) )
+ return ctr->values[i];
+ }
+
+ return NULL;
+}
+
+int regval_ctr_get_seqnum(struct regval_ctr *ctr)
+{
+ if (ctr == NULL) {
+ return -1;
+ }
+
+ return ctr->seqnum;
+}
+
+WERROR regval_ctr_set_seqnum(struct regval_ctr *ctr, int seqnum)
+{
+ if (ctr == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ctr->seqnum = seqnum;
+
+ return WERR_OK;
+}
diff --git a/source3/registry/reg_objects.h b/source3/registry/reg_objects.h
new file mode 100644
index 0000000..f8a1788
--- /dev/null
+++ b/source3/registry/reg_objects.h
@@ -0,0 +1,74 @@
+/*
+ Samba's Internal Registry objects
+
+ SMB parameters and setup
+ Copyright (C) Gerald Carter 2002-2006.
+ Copyright (C) Michael Adam 2007-2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _REG_OBJECTS_H /* _REG_OBJECTS_H */
+#define _REG_OBJECTS_H
+
+/* low level structure to contain registry values */
+
+struct regval_blob;
+
+/* container for registry values */
+
+struct regval_ctr;
+
+/* container for registry subkey names */
+
+struct regsubkey_ctr;
+
+/* The following definitions come from registry/reg_objects.c */
+
+WERROR regsubkey_ctr_init(TALLOC_CTX *mem_ctx, struct regsubkey_ctr **ctr);
+WERROR regsubkey_ctr_reinit(struct regsubkey_ctr *ctr);
+WERROR regsubkey_ctr_set_seqnum(struct regsubkey_ctr *ctr, int seqnum);
+int regsubkey_ctr_get_seqnum(struct regsubkey_ctr *ctr);
+WERROR regsubkey_ctr_addkey( struct regsubkey_ctr *ctr, const char *keyname );
+WERROR regsubkey_ctr_delkey( struct regsubkey_ctr *ctr, const char *keyname );
+bool regsubkey_ctr_key_exists( struct regsubkey_ctr *ctr, const char *keyname );
+uint32_t regsubkey_ctr_numkeys( struct regsubkey_ctr *ctr );
+char* regsubkey_ctr_specific_key( struct regsubkey_ctr *ctr, uint32_t key_index );
+WERROR regval_ctr_init(TALLOC_CTX *mem_ctx, struct regval_ctr **ctr);
+uint32_t regval_ctr_numvals(struct regval_ctr *ctr);
+uint8_t* regval_data_p(struct regval_blob *val);
+uint32_t regval_size(struct regval_blob *val);
+char* regval_name(struct regval_blob *val);
+uint32_t regval_type(struct regval_blob *val);
+struct regval_blob* regval_ctr_specific_value(struct regval_ctr *ctr,
+ uint32_t idx);
+struct regval_blob *regval_ctr_value_byname(struct regval_ctr *ctr,
+ const char *value);
+bool regval_ctr_value_exists(struct regval_ctr *ctr, const char *value);
+struct regval_blob *regval_compose(TALLOC_CTX *ctx, const char *name,
+ uint32_t type,
+ const uint8_t *data_p, size_t size);
+int regval_ctr_addvalue(struct regval_ctr *ctr, const char *name, uint32_t type,
+ const uint8_t *data_p, size_t size);
+int regval_ctr_addvalue_sz(struct regval_ctr *ctr, const char *name, const char *data);
+int regval_ctr_addvalue_multi_sz(struct regval_ctr *ctr, const char *name, const char **data);
+int regval_ctr_copyvalue(struct regval_ctr *ctr, struct regval_blob *val);
+int regval_ctr_delvalue(struct regval_ctr *ctr, const char *name);
+struct regval_blob* regval_ctr_getvalue(struct regval_ctr *ctr,
+ const char *name);
+int regval_ctr_get_seqnum(struct regval_ctr *ctr);
+WERROR regval_ctr_set_seqnum(struct regval_ctr *ctr, int seqnum);
+
+
+#endif /* _REG_OBJECTS_H */
diff --git a/source3/registry/reg_parse.c b/source3/registry/reg_parse.c
new file mode 100644
index 0000000..b6a7f68
--- /dev/null
+++ b/source3/registry/reg_parse.c
@@ -0,0 +1,1090 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Parser for dot.reg files
+ * @file reg_parse.c
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Jun 2010
+ *
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "cbuf.h"
+#include "srprs.h"
+#include "reg_parse_internal.h"
+#include "reg_parse.h"
+#include "reg_format.h"
+
+#include <stdio.h>
+#include <talloc.h>
+#include <stdbool.h>
+#include <string.h>
+#include <regex.h>
+#include <assert.h>
+#include <stdint.h>
+
+enum reg_parse_state {
+ STATE_DEFAULT,
+ STATE_KEY_OPEN,
+ STATE_VAL_HEX_CONT,
+ STATE_VAL_SZ_CONT
+};
+
+struct reg_parse {
+ struct reg_format_callback reg_format_callback;
+ cbuf* key;
+ cbuf* valname;
+ uint32_t valtype;
+ cbuf* valblob;
+ cbuf* tmp;
+ struct reg_parse_callback call;
+ int ret;
+ int linenum;
+ enum reg_parse_state state;
+ struct reg_parse_options* opt;
+ smb_iconv_t str2UTF16;
+ unsigned flags;
+};
+
+/**
+ * @defgroup action Action
+ * @{
+ */
+static bool act_key(struct reg_parse* p, cbuf* keyname, bool del)
+{
+ const char* name = cbuf_gets(keyname, 0);
+ cbuf_swap(p->key, keyname);
+
+ assert(p->state == STATE_DEFAULT || p->state == STATE_KEY_OPEN);
+ p->state = del ? STATE_DEFAULT : STATE_KEY_OPEN;
+
+ assert(p->call.key);
+ p->ret = p->call.key(p->call.data, &name, 1, del);
+ return p->ret >= 0;
+}
+
+static bool value_callback(struct reg_parse* p)
+{
+ const char* name = cbuf_gets(p->valname,0);
+ const uint8_t* val = (const uint8_t*)cbuf_gets(p->valblob,0);
+ size_t len = cbuf_getpos(p->valblob);
+
+ assert(p->call.val);
+ p->ret = p->call.val(p->call.data, name, p->valtype, val, len);
+ return p->ret >= 0;
+}
+
+static bool act_val_hex(struct reg_parse* p, cbuf* value, bool cont)
+{
+ cbuf_swap(p->valblob, value);
+ assert((p->state == STATE_KEY_OPEN) || (p->state == STATE_VAL_HEX_CONT));
+
+ if (cont) {
+ p->state = STATE_VAL_HEX_CONT;
+ } else {
+ p->state = STATE_KEY_OPEN;
+
+ switch (p->valtype) {
+ case REG_EXPAND_SZ:
+ case REG_MULTI_SZ:
+ if (p->str2UTF16 != NULL) {
+ char* dst = NULL;
+ const char* src = cbuf_gets(p->valblob, 0);
+ const size_t slen = cbuf_getpos(p->valblob);
+ size_t dlen = iconvert_talloc(p,
+ p->str2UTF16,
+ src, slen,
+ &dst);
+ if (dlen != -1) {
+ cbuf_swapptr(p->valblob, &dst, dlen);
+ } else {
+ DEBUG(0, ("iconvert_talloc failed\n"));
+ return false;
+ }
+ talloc_free(dst);
+ }
+ break;
+ default:
+ break;
+ }
+ return value_callback(p);
+ }
+ return true;
+}
+
+static bool act_val_dw(struct reg_parse* p, uint32_t val)
+{
+ assert(p->valtype == REG_DWORD);
+ assert(p->state == STATE_KEY_OPEN);
+
+ cbuf_clear(p->valblob);
+
+ if (cbuf_putdw(p->valblob, val) < 0) {
+ return false;
+ }
+ return value_callback(p);
+}
+
+static bool act_val_sz(struct reg_parse* p, cbuf* value, bool cont)
+{
+ cbuf_swap(p->valblob, value);
+
+ assert(p->valtype == REG_SZ);
+ assert((p->state == STATE_KEY_OPEN) || (p->state == STATE_VAL_SZ_CONT));
+
+ if (cont) {
+ p->state = STATE_VAL_SZ_CONT;
+ } else {
+ char* dst = NULL;
+ size_t dlen;
+ const char* src = cbuf_gets(p->valblob, 0);
+
+ p->state = STATE_KEY_OPEN;
+
+
+ if (convert_string_talloc(p->valblob, CH_UNIX, CH_UTF16LE,
+ src, strlen(src)+1,
+ &dst, &dlen))
+ {
+ cbuf_swapptr(p->valblob, &dst, dlen);
+ } else {
+ DEBUG(0, ("convert_string_talloc failed: >%s<\n"
+ "use it as is\t", src));
+ return false;
+ }
+ talloc_free(dst);
+
+ return value_callback(p);
+ }
+ return true;
+}
+
+static bool act_val_del(struct reg_parse* p)
+{
+ const char* name = cbuf_gets(p->valname, 0);
+
+ assert(p->call.val_del);
+ p->ret = p->call.val_del(p->call.data, name);
+ return p->ret >= 0;
+}
+
+static bool act_comment (struct reg_parse* p, const char* txt)
+{
+ assert(p->call.comment);
+ p->ret = p->call.comment(p->call.data, txt);
+ return p->ret >= 0;
+}
+/**@}*/
+
+
+static int nop_callback_key(void* private_data,
+ const char* key[],
+ size_t klen,
+ bool del)
+{
+ return 0;
+}
+
+static int nop_callback_val(void* private_data,
+ const char* name,
+ uint32_t type,
+ const uint8_t* data,
+ size_t len)
+{
+ return 0;
+}
+
+static int nop_callback_del(void* data, const char* str)
+{
+ return 0;
+}
+
+struct reg_parse* reg_parse_new(const void* ctx,
+ struct reg_parse_callback cb,
+ const char* str_enc, unsigned flags)
+{
+ struct reg_parse* s = talloc_zero(ctx, struct reg_parse);
+ if (s == NULL)
+ return NULL;
+ s->key = cbuf_new(s);
+ s->valname = cbuf_new(s);
+ s->valblob = cbuf_new(s);
+ s->tmp = cbuf_new(s);
+ if ( (s->tmp == NULL) || (s->valblob == NULL)
+ || (s->valname == NULL) || (s->key == NULL) )
+ {
+ goto fail;
+ }
+
+ s->reg_format_callback.writeline = (reg_format_callback_writeline_t)&reg_parse_line;
+ s->reg_format_callback.data = s;
+
+ s->valtype = 0;
+ if (cb.key == NULL) {
+ cb.key = (reg_parse_callback_key_t)&nop_callback_key;
+ }
+ if (cb.val == NULL) {
+ cb.val = (reg_parse_callback_val_t)&nop_callback_val;
+ }
+ if (cb.val_del == NULL) {
+ cb.val_del = (reg_parse_callback_val_del_t)&nop_callback_del;
+ }
+ if (cb.comment == NULL) {
+ cb.comment =
+ (reg_parse_callback_comment_t)&nop_callback_del;
+ }
+
+ s->call = cb;
+ s->linenum = 0;
+ s->state = STATE_DEFAULT;
+ s->flags = flags;
+
+ if (str_enc && !set_iconv(&s->str2UTF16, "UTF-16LE", str_enc)) {
+ DEBUG(0, ("reg_parse_new: failed to set encoding: %s\n",
+ str_enc));
+ goto fail;
+ }
+
+ assert(&s->reg_format_callback == (struct reg_format_callback*)s);
+ return s;
+fail:
+ set_iconv(&s->str2UTF16, NULL, NULL);
+ talloc_free(s);
+ return NULL;
+}
+
+/**
+ * @defgroup parse Parser Primitive
+ * @ingroup internal
+ * @{
+ */
+
+
+static bool srprs_key(const char** ptr, cbuf* key, bool* del)
+{
+ const char* pos = *ptr;
+ const char* closing_bracket_pos = NULL;
+ size_t closing_bracket_idx = 0;
+
+ if (!srprs_skipws(&pos) || !srprs_char(&pos, '[')) {
+ return false;
+ }
+
+ *del = srprs_char(&pos, '-');
+
+ cbuf_clear(key);
+
+ while (true) {
+ while (srprs_charsetinv(&pos, "]\\", key))
+ ;
+
+ switch (*pos) {
+
+ case ']':
+ closing_bracket_idx = cbuf_getpos(key);
+ closing_bracket_pos = pos;
+ cbuf_putc(key, ']');
+ pos++;
+ break;
+
+ case '\\':
+ cbuf_putc(key, '\\');
+ /* n++; */
+ /* cbuf_puts(subkeyidx, cbuf_getpos(key), sizeof(size_t)) */
+ while (srprs_char(&pos,'\\'))
+ ;
+ break;
+
+ case '\0':
+ if (closing_bracket_pos == NULL) {
+ return false;
+ }
+
+ /* remove trailing backslash (if any) */
+ if (*(closing_bracket_pos-1)=='\\') {
+ closing_bracket_idx--;
+ }
+
+ cbuf_setpos(key, closing_bracket_idx);
+ *ptr = closing_bracket_pos+1;
+ return true;
+
+ default:
+ assert(false);
+ }
+ }
+}
+
+static bool srprs_val_name(const char** ptr, cbuf* name)
+{
+ const char* pos = *ptr;
+ const size_t spos = cbuf_getpos(name);
+
+ if ( !srprs_skipws(&pos) ) {
+ goto fail;
+ }
+
+ if ( srprs_char(&pos, '@') ) {
+ cbuf_puts(name, "", -1);
+ }
+ else if (!srprs_quoted_string(&pos, name, NULL)) {
+ goto fail;
+ }
+
+ if (!srprs_skipws(&pos) || !srprs_char(&pos, '=')) {
+ goto fail;
+ }
+
+ *ptr = pos;
+ return true;
+
+fail:
+ cbuf_setpos(name, spos);
+ return false;
+}
+
+static bool srprs_val_dword(const char** ptr, uint32_t* type, uint32_t* val)
+{
+ const char* pos = *ptr;
+
+ if (!srprs_str(&pos, "dword:", -1)) {
+ return false;
+ }
+
+ if (!srprs_hex(&pos, 8, val)) {
+ return false;
+ }
+
+ *type = REG_DWORD;
+ *ptr = pos;
+ return true;
+}
+
+static bool srprs_val_sz(const char** ptr, uint32_t* type, cbuf* val, bool* cont)
+{
+ if (!srprs_quoted_string(ptr, val, cont)) {
+ return false;
+ }
+
+ *type = REG_SZ;
+ return true;
+}
+
+
+static bool srprs_nl_no_eos(const char** ptr, cbuf* str, bool eof)
+{
+ const char* pos = *ptr;
+ const size_t spos = cbuf_getpos(str);
+
+ if( srprs_nl(&pos, str) && (eof || *pos != '\0')) {
+ *ptr = pos;
+ return true;
+ }
+ cbuf_setpos(str, spos);
+ return false;
+}
+
+
+static bool srprs_eol_cont(const char** ptr, bool* cont)
+{
+ const char* pos = *ptr;
+ bool bs = srprs_char(&pos, '\\');
+
+ if (!srprs_eol(&pos, NULL)) {
+ return false;
+ }
+
+ *cont = bs;
+ *ptr = pos;
+ return true;
+}
+
+/* matches the empty string, for zero length lists */
+static bool srprs_val_hex_values(const char** ptr, cbuf* val, bool* cont)
+{
+ const char* pos = *ptr;
+ unsigned u;
+
+ do {
+ if (!srprs_skipws(&pos) || !srprs_hex(&pos, 2, &u) || !srprs_skipws(&pos)) {
+ break;
+ }
+ cbuf_putc(val, (char)u);
+ } while(srprs_char(&pos, ','));
+
+ *ptr = pos;
+
+ if (srprs_skipws(&pos) && srprs_eol_cont(&pos, cont)) {
+ *ptr = pos;
+ }
+
+ return true;
+}
+
+static bool srprs_val_hex(const char** ptr, uint32_t* ptype, cbuf* val,
+ bool* cont)
+{
+ const char* pos = *ptr;
+ uint32_t type;
+
+ if (!srprs_str(&pos, "hex", -1)) {
+ return false;
+ }
+
+ if (srprs_char(&pos, ':')) {
+ type = REG_BINARY;
+ }
+ else if (!srprs_char(&pos, '(') ||
+ !srprs_hex(&pos, 8, &type) ||
+ !srprs_char(&pos,')') ||
+ !srprs_char(&pos, ':'))
+ {
+ return false;
+ }
+
+ if (!srprs_val_hex_values(&pos, val, cont)) {
+ return false;
+ }
+
+ *ptype = type;
+ *ptr = pos;
+ return true;
+}
+
+
+static bool srprs_comment(const char** ptr, cbuf* str)
+{
+ return srprs_char(ptr, ';') && srprs_line(ptr, str);
+}
+
+/**@}*/
+
+int reg_parse_set_options(struct reg_parse* parser, const char* options)
+{
+ static const char* DEFAULT ="enc=unix,flags=0";
+
+ int ret = 0;
+ char *key, *val;
+ void* ctx = talloc_new(parser);
+
+ if (options == NULL) {
+ options = DEFAULT;
+ }
+
+ while (srprs_option(&options, ctx, &key, &val)) {
+ if ((strcmp(key, "enc") == 0) || (strcmp(key, "strenc") == 0)) {
+ } else if ((strcmp(key, "flags") == 0) && (val != NULL)) {
+ char* end = NULL;
+ if (val != NULL) {
+ parser->flags = strtol(val, &end, 0);
+ }
+ if ((end==NULL) || (*end != '\0')) {
+ DEBUG(0, ("Invalid flags format: %s\n",
+ val ? val : "<NULL>"));
+ ret = -1;
+ }
+ }
+ /* else if (strcmp(key, "hive") == 0) { */
+ /* if (strcmp(val, "short") == 0) { */
+ /* f->hive_fmt = REG_FMT_SHORT_HIVES; */
+ /* } else if (strcmp(val, "long") == 0) { */
+ /* f->hive_fmt = REG_FMT_LONG_HIVES; */
+ /* } else if (strcmp(val, "preserve") == 0) { */
+ /* f->hive_fmt = REG_FMT_PRESERVE_HIVES; */
+ /* } else { */
+ /* DEBUG(0, ("Invalid hive format: %s\n", val)); */
+ /* ret = -1; */
+ /* } */
+ /* } */
+ }
+ talloc_free(ctx);
+ return ret;
+}
+
+
+int reg_parse_line(struct reg_parse* parser, const char* line)
+{
+ const char* pos;
+ bool del=false;
+ cbuf* tmp=cbuf_clear(parser->tmp);
+ bool cb_ok = true;
+ bool cont = true;
+
+ if (!line) {
+ return -4;
+ }
+
+ parser->linenum++;
+ pos = line;
+
+ switch (parser->state) {
+ case STATE_VAL_HEX_CONT:
+ if (srprs_val_hex_values(&pos, parser->valblob, &cont)) {
+ cb_ok = act_val_hex(parser, parser->valblob, cont);
+ }
+ goto done;
+ case STATE_VAL_SZ_CONT:
+ if (srprs_quoted_string(&pos, parser->valblob, &cont)) {
+ cb_ok = act_val_sz(parser, parser->valblob, cont);
+ }
+ goto done;
+ default:
+ cont = false;
+ }
+
+ if ( !srprs_skipws(&pos) ) {
+ return -4;
+ }
+
+ /* empty line ?*/
+ if ( srprs_eol(&pos, NULL) ) {
+ return 0;
+ }
+
+ /* key line ?*/
+ else if (srprs_key(&pos, tmp, &del)) {
+ cb_ok = act_key(parser, tmp, del);
+ }
+
+ /* comment line ? */
+ else if (srprs_comment(&pos, tmp)) {
+ cb_ok = act_comment(parser, cbuf_gets(tmp, 0));
+ }
+
+ /* head line */
+ else if ((parser->linenum == 1) && srprs_line(&pos, tmp) ) {
+ /* cb_ok = act_head(parser, cbuf_gets(tmp, 0)); */
+ }
+
+ /* value line ?*/
+ else if (srprs_val_name(&pos, tmp)) {
+ uint32_t dw;
+ cbuf_swap(parser->valname, tmp);
+ cbuf_clear(tmp);
+
+ if (parser->state != STATE_KEY_OPEN) {
+ DEBUG(0, ("value \"%s\" without a key at line: %i\n",
+ cbuf_gets(parser->valname, 0), parser->linenum));
+ return -3;
+ }
+
+ if (!srprs_skipws(&pos)) {
+ return -4;
+ }
+
+ if (srprs_char(&pos, '-')) {
+ cb_ok = act_val_del(parser);
+ }
+ else if (srprs_val_dword(&pos, &parser->valtype, &dw)) {
+ cb_ok = act_val_dw(parser, dw);
+ }
+ else if (srprs_val_sz(&pos, &parser->valtype, tmp, &cont)) {
+ cb_ok = act_val_sz(parser, tmp, cont);
+ }
+ else if (srprs_val_hex(&pos, &parser->valtype, tmp, &cont)){
+ cb_ok = act_val_hex(parser, tmp, cont);
+ }
+ else {
+ DEBUG(0, ("value \"%s\" parse error"
+ "at line: %i pos: %li : %s\n",
+ cbuf_gets(parser->valname, 0), parser->linenum,
+ (long int)(pos-line), pos));
+ return -3;
+ }
+ }
+ else {
+ DEBUG(0, ("unrecognized line %i : %s\n", parser->linenum, line));
+ return -3;
+ }
+
+done:
+ if (!cb_ok)
+ return -2;
+
+ if (!srprs_skipws(&pos) || !srprs_eol(&pos, NULL)) {
+ DEBUG(0, ("trailing garbage at line: %i pos: %li : %s\n",
+ parser->linenum, (long int)(pos-line), pos));
+ return -1;
+ }
+ return 0;
+}
+
+/******************************************************************************/
+/**
+ * @addtogroup misc
+ * @{
+ */
+static bool lookslike_utf16(const char* line, size_t len, bool* little_endian)
+{
+ static const uint16_t M_LE = 0xFF80;
+ static const uint16_t M_BE = 0x80FF;
+ uint16_t mask;
+ bool le;
+
+ size_t l = MIN(len/2, 64);
+ const uint16_t* u = (const uint16_t*)line;
+ size_t i;
+
+ assert(len >= 2);
+
+ if ( u[0] & M_LE ) {
+ le = true;
+ mask = M_LE;
+ } else if ( u[0] & M_BE ) {
+ le = false;
+ mask = M_BE;
+ } else {
+ return false;
+ }
+
+ for (i=1; i<l; i++) {
+ if ( u[i] & mask ) {
+ return false;
+ }
+ }
+
+ *little_endian = le;
+ return true;
+}
+
+static bool lookslike_dos(const char* line, size_t len)
+{
+ size_t i;
+ for (i=0; i<len; i++) {
+ if ( (line[i] == '\0') || (line[i] & 0x80) ) {
+ return false;
+ }
+ if ( (line[i] == '\r') && (i+1 < len) && (line[i+1] == '\n') ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool guess_charset(const char** ptr,
+ size_t* len,
+ const char** file_enc,
+ const char** str_enc)
+{
+ const char* charset = NULL;
+ const char* pos = *ptr;
+
+ if (*len < 4) {
+ return false;
+ }
+
+ if (srprs_bom(&pos, &charset, NULL)) {
+ size_t declen;
+ if (pos < *ptr) {
+ return false;
+ }
+ declen = (pos - *ptr);
+ if (*len < declen) {
+ return false;
+ }
+ *len -= declen;
+ *ptr = pos;
+ if (*file_enc == NULL) {
+ *file_enc = charset;
+ }
+ else if( strcmp(*file_enc, charset) != 0 ) {
+ DEBUG(0, ("file encoding forced to %s\n",
+ *file_enc));
+ }
+ }
+ else if (*file_enc == NULL) {
+ bool le;
+ if (lookslike_utf16(*ptr, *len, &le)) {
+ *file_enc = le ? "UTF-16LE" : "UTF-16BE";
+ }
+ else if (lookslike_dos(*ptr, *len)) {
+ *file_enc = "dos";
+ }
+ else {
+ *file_enc = "unix";
+ }
+ }
+
+ if ((str_enc != NULL) && (*str_enc == NULL)) {
+ *str_enc = ( strncmp(*ptr, "REGEDIT4", 8) == 0)
+ ? *file_enc
+ : "UTF-16LE";
+ }
+
+ return true;
+}
+/**@}*/
+
+struct reg_parse_fd_opt {
+ const char* file_enc;
+ const char* str_enc;
+ unsigned flags;
+ int fail_level;
+};
+
+static struct reg_parse_fd_opt
+reg_parse_fd_opt(void* mem_ctx, const char* options)
+{
+ struct reg_parse_fd_opt ret = {
+ .file_enc = NULL,
+ .str_enc = NULL,
+ .flags = 0,
+ };
+
+ void* ctx = talloc_new(mem_ctx);
+ char *key, *val;
+
+ if (options == NULL) {
+ goto done;
+ }
+
+ while (srprs_option(&options, ctx, &key, &val)) {
+ if (strcmp(key, "enc") == 0) {
+ ret.file_enc = talloc_steal(mem_ctx, val);
+ ret.str_enc = ret.file_enc;
+ } else if (strcmp(key, "strenc") == 0) {
+ ret.str_enc = talloc_steal(mem_ctx, val);
+ } else if (strcmp(key, "fileenc") == 0) {
+ ret.file_enc = talloc_steal(mem_ctx, val);
+ } else if ((strcmp(key, "flags") == 0) && (val != NULL)) {
+ char* end = NULL;
+ if (val != NULL) {
+ ret.flags = strtol(val, &end, 0);
+ }
+ if ((end==NULL) || (*end != '\0')) {
+ DEBUG(0, ("Invalid format \"%s\": %s\n",
+ key, val ? val : "<NULL>"));
+ }
+ } else if ((strcmp(key, "fail") == 0) && (val != NULL)) {
+ char* end = NULL;
+ if (val != NULL) {
+ ret.fail_level = -strtol(val, &end, 0);
+ }
+ if ((end==NULL) || (*end != '\0')) {
+ DEBUG(0, ("Invalid format \"%s\": %s\n",
+ key, val ? val : "<NULL>"));
+ }
+ }
+ }
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+static void display_iconv_error_bytes(const char *inbuf, size_t len)
+{
+ size_t i;
+ for (i = 0; i < 4 && i < len; i++) {
+ DEBUGADD(0, ("<%02x>", (unsigned char)inbuf[i]));
+ }
+ DEBUGADD(0, ("\n"));
+}
+
+int reg_parse_fd(int fd, const struct reg_parse_callback* cb, const char* opts)
+{
+ void* mem_ctx = talloc_stackframe();
+ cbuf* line = cbuf_new(mem_ctx);
+ smb_iconv_t cd = (smb_iconv_t)-1;
+ struct reg_parse* parser = NULL;
+ char buf_in[1024];
+ char buf_out[1025] = { 0 };
+ ssize_t nread;
+ const char* iptr;
+ char* optr;
+ size_t ilen;
+ size_t olen;
+ size_t avail_osize = sizeof(buf_out)-1;
+ size_t space_to_read = sizeof(buf_in);
+ int ret = -1;
+ bool eof = false;
+ size_t linecount = 0;
+
+ struct reg_parse_fd_opt opt = reg_parse_fd_opt(mem_ctx, opts);
+
+ if (cb == NULL) {
+ DBG_ERR("NULL callback\n");
+ ret = -1;
+ goto done;
+ }
+
+ nread = read(fd, buf_in, space_to_read);
+ if (nread < 0) {
+ DBG_ERR("read failed: %s\n", strerror(errno));
+ ret = -1;
+ goto done;
+ }
+ if (nread == 0) {
+ /* Empty file. */
+ eof = true;
+ goto done;
+ }
+
+ iptr = buf_in;
+ ilen = nread;
+
+ if (!guess_charset(&iptr, &ilen,
+ &opt.file_enc, &opt.str_enc))
+ {
+ DBG_ERR("reg_parse_fd: failed to guess encoding\n");
+ ret = -1;
+ goto done;
+ }
+
+ if (ilen == 0) {
+ /* File only contained charset info. */
+ eof = true;
+ ret = -1;
+ goto done;
+ }
+
+ DBG_DEBUG("reg_parse_fd: encoding file: %s str: %s\n",
+ opt.file_enc, opt.str_enc);
+
+
+ if (!set_iconv(&cd, "unix", opt.file_enc)) {
+ DBG_ERR("reg_parse_fd: failed to set file encoding %s\n",
+ opt.file_enc);
+ ret = -1;
+ goto done;
+ }
+
+ parser = reg_parse_new(mem_ctx, *cb, opt.str_enc, opt.flags);
+ if (parser == NULL) {
+ ret = -1;
+ goto done;
+ }
+
+ /* Move input data to start of buf_in. */
+ if (iptr > buf_in) {
+ memmove(buf_in, iptr, ilen);
+ iptr = buf_in;
+ }
+
+ optr = buf_out;
+ /* Leave last byte for null termination. */
+ olen = avail_osize;
+
+ /*
+ * We read from buf_in (iptr), iconv converting into
+ * buf_out (optr).
+ */
+
+ while (!eof) {
+ const char *pos;
+ size_t nconv;
+
+ if (olen == 0) {
+ /* We're out of possible room. */
+ DBG_ERR("no room in output buffer\n");
+ ret = -1;
+ goto done;
+ }
+ nconv = smb_iconv(cd, &iptr, &ilen, &optr, &olen);
+ if (nconv == (size_t)-1) {
+ bool valid_err = false;
+ if (errno == EINVAL) {
+ valid_err = true;
+ }
+ if (errno == E2BIG) {
+ valid_err = true;
+ }
+ if (!valid_err) {
+ DBG_ERR("smb_iconv error in file at line %zu: ",
+ linecount);
+ display_iconv_error_bytes(iptr, ilen);
+ ret = -1;
+ goto done;
+ }
+ /*
+ * For valid errors process the
+ * existing buffer then continue.
+ */
+ }
+
+ /*
+ * We know this is safe as we have an extra
+ * enforced zero byte at the end of buf_out.
+ */
+ *optr = '\0';
+ pos = buf_out;
+
+ while (srprs_line(&pos, line) && srprs_nl_no_eos(&pos, line, eof)) {
+ int retval;
+
+ /* Process all lines we got. */
+ retval = reg_parse_line(parser, cbuf_gets(line, 0));
+ if (retval < opt.fail_level) {
+ DBG_ERR("reg_parse_line %zu fail %d\n",
+ linecount,
+ retval);
+ ret = -1;
+ goto done;
+ }
+ cbuf_clear(line);
+ linecount++;
+ }
+ if (pos > buf_out) {
+ /*
+ * The output data we have
+ * processed starts at buf_out
+ * and ends at pos.
+ * The unprocessed output
+ * data starts at pos and
+ * ends at optr.
+ *
+ * <------ sizeof(buf_out) - 1------------->|0|
+ * <--------- avail_osize------------------>|0|
+ * +----------------------+-------+-----------+
+ * | | | |0|
+ * +----------------------+-------+-----------+
+ * ^ ^ ^
+ * | | |
+ * buf_out pos optr
+ */
+ size_t unprocessed_len;
+
+ /* Paranoia checks. */
+ if (optr < pos) {
+ ret = -1;
+ goto done;
+ }
+ unprocessed_len = optr - pos;
+
+ /* Paranoia checks. */
+ if (avail_osize < unprocessed_len) {
+ ret = -1;
+ goto done;
+ }
+ /* Move down any unprocessed data. */
+ memmove(buf_out, pos, unprocessed_len);
+
+ /*
+ * After the move, reset the output length.
+ *
+ * <------ sizeof(buf_out) - 1------------->|0|
+ * <--------- avail_osize------------------>|0|
+ * +----------------------+-------+-----------+
+ * | | |0|
+ * +----------------------+-------+-----------+
+ * ^ ^
+ * | optr
+ * buf_out
+ */
+ optr = buf_out + unprocessed_len;
+ /*
+ * Calculate the new output space available
+ * for iconv.
+ * We already did the paranoia check for this
+ * arithmetic above.
+ */
+ olen = avail_osize - unprocessed_len;
+ }
+
+ /*
+ * Move any unprocessed data to the start of
+ * the input buffer (buf_in).
+ */
+ if (ilen > 0 && iptr > buf_in) {
+ memmove(buf_in, iptr, ilen);
+ }
+
+ /* Is there any space to read more input ? */
+ if (ilen >= sizeof(buf_in)) {
+ /* No space. Nothing was converted. Error. */
+ DBG_ERR("no space in input buffer\n");
+ ret = -1;
+ goto done;
+ }
+
+ space_to_read = sizeof(buf_in) - ilen;
+
+ /* Read the next chunk from the file. */
+ nread = read(fd, buf_in + ilen, space_to_read);
+ if (nread < 0) {
+ DBG_ERR("read failed: %s\n", strerror(errno));
+ ret = -1;
+ goto done;
+ }
+ if (nread == 0) {
+ /* Empty file. */
+ eof = true;
+ continue;
+ }
+
+ /* Paranoia check. */
+ if (nread + ilen < ilen) {
+ ret = -1;
+ goto done;
+ }
+
+ /* Paranoia check. */
+ if (nread + ilen > sizeof(buf_in)) {
+ ret = -1;
+ goto done;
+ }
+
+ iptr = buf_in;
+ ilen = nread + ilen;
+ }
+
+ ret = 0;
+
+done:
+
+ set_iconv(&cd, NULL, NULL);
+ if (parser) {
+ set_iconv(&parser->str2UTF16, NULL, NULL);
+ }
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+int reg_parse_file(const char* fname, const struct reg_parse_callback* cb,
+ const char* opt)
+{
+ int ret = -1;
+ int fd;
+
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ DEBUG(0, ("reg_parse_file: open %s failed: %s\n", fname,
+ strerror(errno)));
+ return -1;
+ }
+
+ ret = reg_parse_fd(fd, cb, opt);
+
+ close(fd);
+ return ret;
+}
+
+/* static struct registry_key *find_regkey_by_hnd(pipes_struct *p, */
+/* struct policy_handle *hnd) */
+/* { */
+/* struct registry_key *regkey = NULL; */
+
+/* if(!find_policy_by_hnd(p,hnd,(void **)(void *)&regkey)) { */
+/* DEBUG(2,("find_regkey_index_by_hnd: Registry Key not found: ")); */
+/* return NULL; */
+/* } */
+
+/* return regkey; */
+/* } */
diff --git a/source3/registry/reg_parse.h b/source3/registry/reg_parse.h
new file mode 100644
index 0000000..ba02ec6
--- /dev/null
+++ b/source3/registry/reg_parse.h
@@ -0,0 +1,190 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Parser for registration entries (.reg) files.
+ * A parser is a talloced incarnation of an opaque struct reg_parse.
+ * It is fed with the (.reg) file line by line calling @ref reg_parse_line
+ * and emits output by calling functions from its reg_parse_callback.
+ * @file reg_parse.h
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Jun 2010
+ */
+
+#ifndef REG_PARSE_H
+#define REG_PARSE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * Prototype for function called on key found.
+ * The usual action to take is delete the key if del==true, open it if
+ * already existing or create a new one.
+ *
+ * @param private_data
+ * @param key
+ * @param klen number of elements in key
+ * @param del whether to delete the key
+ *
+ * @retval >=0 on success
+ *
+ * @see reg_format_key
+ */
+typedef int (*reg_parse_callback_key_t) (void* private_data,
+ const char* key[],
+ size_t klen,
+ bool del);
+
+/**
+ * Prototype for function called on value found.
+ * The usual action to take is set the value of the last opened key.
+ *
+ * @param private_data
+ * @param name the values name
+ * @param type the values type
+ * @param data the values value
+ * @param len the number of bytes of data
+ *
+ * @retval >=0 on success
+ *
+ * @see reg_format_value
+ */
+typedef int (*reg_parse_callback_val_t) (void* private_data,
+ const char* name,
+ uint32_t type,
+ const uint8_t* data,
+ size_t len);
+
+/**
+ * Prototype for function called on value delete found.
+ * Delete value from the last opened key. It is usually no error if
+ * no such value exist.
+ *
+ * @param private_data
+ * @param name
+ *
+ * @retval >=0 on success
+ *
+ * @see reg_format_value_delete
+ */
+typedef int (*reg_parse_callback_val_del_t) (void* private_data,
+ const char* name);
+
+
+/**
+ * Prototype for function called on comment found.
+ *
+ * @param private_data
+ * @param line comment with marker removed.
+ *
+ * @retval >=0 on success
+ *
+ * @see reg_format_comment
+ */
+typedef int (*reg_parse_callback_comment_t) (void* private_data,
+ const char* line);
+
+/**
+ * Type handling the output of a reg_parse object.
+ * It contains the functions to call and an opaque data pointer.
+ */
+typedef struct reg_parse_callback {
+ reg_parse_callback_key_t key; /**< Function called on key found */
+ reg_parse_callback_val_t val; /**< Function called on value found */
+ /** Function called on value delete found */
+ reg_parse_callback_val_del_t val_del;
+ /** Function called on comment found */
+ reg_parse_callback_comment_t comment;
+ void* data; /**< Private data passed to callback function */
+} reg_parse_callback;
+
+/**
+ * A Parser for a registration entries (.reg) file.
+ *
+ * It may be used as a reg_format_callback, so the following is valid:
+ * @code
+ * reg_format* f = reg_format_new(mem_ctx,
+ * (reg_format_callback)reg_parse_new(mem_ctx, cb, NULL, 0),
+ * NULL, 0, "\\");
+ * @endcode
+ * @see reg_format
+ */
+typedef struct reg_parse reg_parse;
+
+/**
+ * Create a new reg_parse object.
+ *
+ * @param talloc_ctx the talloc parent
+ * @param cb the output handler
+ * @param str_enc the charset of hex encoded strings (REG_MULTI_SZ, REG_EXAND_SZ) if not UTF-16
+ * @param flags
+ *
+ * @return a talloc'ed reg_parse object, NULL on error
+ */
+reg_parse* reg_parse_new(const void* talloc_ctx,
+ reg_parse_callback cb,
+ const char* str_enc,
+ unsigned flags);
+
+/**
+ * Feed one line to the parser.
+ *
+ * @param parser
+ * @param line one line from a (.reg) file, in UNIX charset
+ *
+ * @return 0 on success
+ *
+ * @see reg_format_callback_writeline_t
+ */
+int reg_parse_line(struct reg_parse* parser, const char* line);
+
+
+/**
+ * Parse a (.reg) file, read from a file descriptor.
+ *
+ * @param fd the file descriptor
+ * @param cb the output handler
+ * @param opts
+ *
+ * @return 0 on success
+ */
+int reg_parse_fd(int fd,
+ const reg_parse_callback* cb,
+ const char* opts);
+
+/**
+ * Parse a (.reg) file
+ *
+ * @param filename the file to open
+ * @param cb the output handler
+ * @param opts
+ *
+ * @return 0 on success
+ */
+int reg_parse_file(const char* filename,
+ const reg_parse_callback* cb,
+ const char* opts);
+
+int reg_parse_set_options(reg_parse* parser, const char* opt);
+
+/******************************************************************************/
+
+
+#endif /* REG_PARSE_H */
diff --git a/source3/registry/reg_parse_dox.cfg b/source3/registry/reg_parse_dox.cfg
new file mode 100644
index 0000000..1705350
--- /dev/null
+++ b/source3/registry/reg_parse_dox.cfg
@@ -0,0 +1,1562 @@
+# Doxyfile 1.6.3
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = Registry Import / Export
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it parses.
+# With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this tag.
+# The format is ext=language, where ext is a file extension, and language is one of
+# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
+# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
+# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
+# doxygen. The layout file controls the global structure of the generated output files
+# in an output format independent way. The create the layout file that represents
+# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
+# file name after the option, if omitted DoxygenLayout.xml will be used as the name
+# of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = reg_parse.c reg_parse.h \
+ reg_import.c reg_import.h \
+ reg_format.c reg_format.h \
+ reg_parse_internal.c reg_parse_internal.h \
+ net_registry.c net_rpc_registry.c \
+ cbuf.c cbuf.h \
+ srprs.c srprs.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
+# are set, an additional index file will be generated that can be used as input for
+# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
+# HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
+# For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before the help appears.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = NO
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be implemented using a PHP enabled web server instead of at the web client using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server based approach is that it scales better to large projects and allows full text search. The disadvances is that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, a4wide, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# By default doxygen will write a font called FreeSans.ttf to the output
+# directory and reference it in all dot files that doxygen generates. This
+# font does not include all possible unicode characters however, so when you need
+# these (or just want a differently looking font) you can specify the font name
+# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = FreeSans
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = YES
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
+
+# Local Variables:
+# mode: makefile
+# compile-command: "doxygen reg_parse_dox.cfg"
+# End:
diff --git a/source3/registry/reg_parse_internal.c b/source3/registry/reg_parse_internal.c
new file mode 100644
index 0000000..d68fee6
--- /dev/null
+++ b/source3/registry/reg_parse_internal.c
@@ -0,0 +1,392 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Registry helper routines
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/**
+ * @file reg_parse_internal.h
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Sep 2010
+ * @brief
+ */
+
+#include "reg_parse_internal.h"
+#include "cbuf.h"
+#include "srprs.h"
+#include "registry.h"
+
+size_t iconvert_talloc(const void* ctx,
+ smb_iconv_t cd,
+ const char* src, size_t srclen,
+ char** pdst)
+{
+ size_t dstlen, ret;
+ size_t obytes, ibytes;
+ char *optr, *dst, *tmp;
+ const char* iptr;
+
+ if (cd == NULL || cd == ((smb_iconv_t)-1)) {
+ return -1;
+ }
+
+ dst = *pdst;
+
+ if (dst == NULL) {
+ /*
+ * Allocate an extra two bytes for the
+ * terminating zero.
+ */
+ dstlen = srclen + 2;
+ dst = (char *)talloc_size(ctx, dstlen);
+ if (dst == NULL) {
+ DEBUG(0,("iconver_talloc no mem\n"));
+ return -1;
+ }
+ } else {
+ dstlen = talloc_get_size(dst);
+ }
+convert:
+ iptr = src;
+ ibytes = srclen;
+ optr = dst;
+ obytes = dstlen-2;
+
+ ret = smb_iconv(cd, &iptr, &ibytes, &optr, &obytes);
+
+ if(ret == -1) {
+ const char *reason="unknown error";
+ switch(errno) {
+ case EINVAL:
+ reason="Incomplete multibyte sequence";
+ break;
+ case E2BIG:
+ dstlen = 2*dstlen + 2;
+ tmp = talloc_realloc(ctx, dst, char, dstlen);
+ if (tmp == NULL) {
+ reason="talloc_realloc failed";
+ break;
+ }
+ dst = tmp;
+ goto convert;
+ case EILSEQ:
+ reason="Illegal multibyte sequence";
+ break;
+ }
+ DEBUG(0,("Conversion error: %s(%.80s) %li\n", reason, iptr,
+ (long int)(iptr-src)));
+ talloc_free(dst);
+ return -1;
+ }
+
+ dstlen = (dstlen-2) - obytes;
+
+ SSVAL(dst, dstlen, 0);
+
+ *pdst = dst;
+ return dstlen;
+}
+
+#ifndef HKEY_CURRENT_CONFIG
+#define HKEY_CURRENT_CONFIG 0x80000005
+#endif
+#ifndef HKEY_DYN_DATA
+#define HKEY_DYN_DATA 0x80000006
+#endif
+#ifndef HKEY_PERFORMANCE_TEXT
+#define HKEY_PERFORMANCE_TEXT 0x80000050
+#endif
+#ifndef HKEY_PERFORMANCE_NLSTEXT
+#define HKEY_PERFORMANCE_NLSTEXT 0x80000060
+#endif
+
+#define HIVE_INFO_ENTRY(SHORT,LONG) \
+const struct hive_info HIVE_INFO_##SHORT = { \
+ .handle = LONG, \
+ .short_name = #SHORT, \
+ .short_name_len = sizeof(#SHORT)-1, \
+ .long_name = #LONG, \
+ .long_name_len = sizeof(#LONG)-1, \
+}
+
+HIVE_INFO_ENTRY(HKLM, HKEY_LOCAL_MACHINE);
+HIVE_INFO_ENTRY(HKCU, HKEY_CURRENT_USER);
+HIVE_INFO_ENTRY(HKCR, HKEY_CLASSES_ROOT);
+HIVE_INFO_ENTRY(HKU , HKEY_USERS);
+HIVE_INFO_ENTRY(HKCC, HKEY_CURRENT_CONFIG);
+HIVE_INFO_ENTRY(HKDD, HKEY_DYN_DATA);
+HIVE_INFO_ENTRY(HKPD, HKEY_PERFORMANCE_DATA);
+HIVE_INFO_ENTRY(HKPT, HKEY_PERFORMANCE_TEXT);
+HIVE_INFO_ENTRY(HKPN, HKEY_PERFORMANCE_NLSTEXT);
+#undef HIVE_INFO_ENTRY
+
+const struct hive_info* HIVE_INFO[] = {
+ &HIVE_INFO_HKLM, &HIVE_INFO_HKCU, &HIVE_INFO_HKCR, &HIVE_INFO_HKU,
+ &HIVE_INFO_HKCC, &HIVE_INFO_HKDD, &HIVE_INFO_HKPD, &HIVE_INFO_HKPT,
+ &HIVE_INFO_HKPN, NULL
+};
+
+#define TOINT(A,B) ((int)(A) << 8) + (int)(B)
+
+bool srprs_hive(const char** ptr, const struct hive_info** result)
+{
+ const char* str = *ptr;
+ const struct hive_info* info = NULL;
+ bool long_hive = false;
+
+ if ((toupper(str[0]) != 'H') || (toupper(str[1]) != 'K')
+ || (str[2] == '\0') )
+ {
+ return false;
+ }
+
+ switch ( TOINT(toupper(str[2]), toupper(str[3])) ) {
+ case TOINT('E', 'Y'):
+ if (str[4] == '_') {
+ int i;
+ for (i=0; (info = HIVE_INFO[i]); i++) {
+ if (strncmp(&str[5], &info->long_name[5],
+ info->long_name_len-5) == 0)
+ {
+ long_hive = true;
+ break;
+ }
+ }
+ }
+ break;
+ case TOINT('L', 'M'):
+ info = &HIVE_INFO_HKLM;
+ break;
+ case TOINT('C', 'U'):
+ info = &HIVE_INFO_HKCU;
+ break;
+ case TOINT('C', 'R'):
+ info = &HIVE_INFO_HKCR;
+ break;
+ case TOINT('C', 'C'):
+ info = &HIVE_INFO_HKCC;
+ break;
+ case TOINT('D', 'D'):
+ info = &HIVE_INFO_HKDD;
+ break;
+ case TOINT('P', 'D'):
+ info = &HIVE_INFO_HKPD;
+ break;
+ case TOINT('P', 'T'):
+ info = &HIVE_INFO_HKPT;
+ break;
+ case TOINT('P', 'N'):
+ info = &HIVE_INFO_HKPN;
+ break;
+ default:
+ if (toupper(str[2]) == 'U') {
+ info = &HIVE_INFO_HKU;
+ }
+ break;
+ }
+ if (info != NULL) {
+ if (result != NULL) {
+ *result = info;
+ }
+ *ptr += long_hive ? info->long_name_len : info->short_name_len;
+ return true;
+ }
+ return false;
+}
+
+const struct hive_info* hive_info(const char* name)
+{
+ const struct hive_info* info = NULL;
+ srprs_hive(&name, &info);
+ return info;
+}
+
+const char *smbreg_get_charset(const char *c)
+{
+ if (strcmp(c, "dos") == 0) {
+ return lp_dos_charset();
+ } else if (strcmp(c, "unix") == 0) {
+ return lp_unix_charset();
+ } else {
+ return c;
+ }
+}
+
+bool set_iconv(smb_iconv_t* t, const char* to, const char* from)
+{
+ smb_iconv_t cd = (smb_iconv_t)-1;
+
+ if (to && from) {
+ to = smbreg_get_charset(to);
+ from = smbreg_get_charset(from);
+ cd = smb_iconv_open(to, from);
+ if (cd == ((smb_iconv_t)-1)) {
+ return false;
+ }
+ }
+ if ((*t != (smb_iconv_t)NULL) && (*t != (smb_iconv_t)-1)) {
+ smb_iconv_close(*t);
+ }
+ *t = cd;
+ return true;
+}
+
+/**
+ * Parse option string
+ * @param[in,out] ptr parse position
+ * @param[in] mem_ctx talloc context
+ * @param[out] name ptr 2 value
+ * @param[out] value ptr 2 value
+ * @return true on success
+ */
+bool srprs_option(const char** ptr, const void* mem_ctx, char** name, char** value)
+{
+ const char* pos = *ptr;
+ void* ctx = talloc_new(mem_ctx);
+
+ cbuf* key = cbuf_new(ctx);
+ cbuf* val = NULL;
+
+ while(srprs_charsetinv(&pos, ",= \t\n\r", key))
+ ;
+ if (pos == *ptr) {
+ talloc_free(ctx);
+ return false;
+ }
+
+ if (name != NULL) {
+ *name = talloc_steal(mem_ctx, cbuf_gets(key, 0));
+ }
+
+ if (*pos == '=') {
+ val = cbuf_new(ctx);
+ pos++;
+ if (!srprs_quoted_string(ptr, val, NULL)) {
+ while(srprs_charsetinv(&pos, ", \t\n\r", val))
+ ;
+ }
+ if (value != NULL) {
+ *value = talloc_steal(mem_ctx, cbuf_gets(val, 0));
+ }
+ } else {
+ if (value != NULL) {
+ *value = NULL;
+ }
+ }
+
+ while(srprs_char(&pos, ','))
+ ;
+
+ *ptr = pos;
+ return true;
+}
+
+#define CH_INVALID ((charset_t)-1)
+static const struct {
+ const char* const name;
+ charset_t ctype;
+ int len;
+ uint8_t seq[4];
+} BOM[] = {
+ {"UTF-8", CH_UTF8, 3, {0xEF, 0xBB, 0xBF}},
+ {"UTF-32LE", CH_INVALID, 4, {0xFF, 0xFE, 0x00, 0x00}},
+ {"UTF-16LE", CH_UTF16LE, 2, {0xFF, 0xFE}},
+ {"UTF-16BE", CH_UTF16BE, 2, {0xFE, 0xFF}},
+ {"UTF-32BE", CH_INVALID, 4, {0x00, 0x00, 0xFE, 0xFF}},
+ { .name = NULL }
+};
+
+bool srprs_bom(const char** ptr, const char** name, charset_t* ctype)
+{
+ int i;
+ for (i=0; BOM[i].name; i++) {
+ if (memcmp(*ptr, BOM[i].seq, BOM[i].len) == 0) {
+ break;
+ }
+ }
+
+ if (BOM[i].name != NULL) {
+ DEBUG(0, ("Found Byte Order Mark for : %s\n", BOM[i].name));
+
+ if (name != NULL) {
+ *name = BOM[i].name;
+ }
+
+ if (ctype != NULL) {
+ *ctype = BOM[i].ctype;
+ }
+
+ *ptr += BOM[i].len;
+
+ return true;
+ }
+ return false;
+}
+
+int write_bom(FILE* file, const char* charset, charset_t ctype)
+{
+ int i;
+ if ( charset == NULL ) {
+ for (i=0; BOM[i].name; i++) {
+ if (BOM[i].ctype == ctype) {
+ return fwrite(BOM[i].seq, 1, BOM[i].len, file);
+ }
+ }
+ DEBUG(0, ("No Byte Order Mark for charset_t: %u\n", (unsigned)ctype));
+ } else {
+ for (i=0; BOM[i].name; i++) {
+ if (strcasecmp_m(BOM[i].name, charset) == 0) {
+ return fwrite(BOM[i].seq, 1, BOM[i].len, file);
+ }
+ }
+ DEBUG(0, ("No Byte Order Mark for charset_t: %s\n", charset));
+ }
+ return 0;
+}
+
+
+int cbuf_puts_case(cbuf* s, const char* str, size_t len, enum fmt_case fmt)
+{
+ size_t pos = cbuf_getpos(s);
+ int ret = cbuf_puts(s, str, len);
+ char* ptr = cbuf_gets(s,pos);
+
+ if (ret <= 0) {
+ return ret;
+ }
+
+ switch (fmt) {
+ case FMT_CASE_PRESERVE:
+ break;
+ case FMT_CASE_UPPER:
+ while(*ptr != '\0') {
+ *ptr = toupper(*ptr);
+ ptr++;
+ }
+ break;
+ case FMT_CASE_TITLE:
+ *ptr = toupper(*ptr);
+ ptr++;
+ FALL_THROUGH;
+ case FMT_CASE_LOWER:
+ while(*ptr != '\0') {
+ *ptr = tolower(*ptr);
+ ptr++;
+ }
+ }
+ return ret;
+}
diff --git a/source3/registry/reg_parse_internal.h b/source3/registry/reg_parse_internal.h
new file mode 100644
index 0000000..a896372
--- /dev/null
+++ b/source3/registry/reg_parse_internal.h
@@ -0,0 +1,123 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Registry helper routines
+ *
+ * Copyright (C) Gregor Beck 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Some stuff used by reg_parse and reg_format.
+ * It might be useful elsewhere but need some review of the interfaces.
+ * @file reg_parse_internal.h
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Sep 2010
+ */
+#ifndef __REG_PARSE_INTERNAL_H
+#define __REG_PARSE_INTERNAL_H
+
+#include "includes.h"
+#include "system/iconv.h"
+
+struct cbuf;
+
+#define USE_NATIVE_ICONV
+#if defined USE_NATIVE_ICONV && defined HAVE_NATIVE_ICONV
+# define smb_iconv_t iconv_t
+# define smb_iconv(CD, IPTR, ILEN, OPTR, OLEN) \
+ iconv(CD, discard_const_p(char*, (IPTR)), ILEN, OPTR, OLEN)
+# define smb_iconv_open iconv_open
+# define smb_iconv_close iconv_close
+#endif
+
+size_t iconvert_talloc(const void* ctx,
+ smb_iconv_t cd,
+ const char* src, size_t srclen,
+ char** pdst);
+
+struct hive_info {
+ uint32_t handle;
+ const char* short_name;
+ size_t short_name_len;
+ const char* long_name;
+ size_t long_name_len;
+};
+
+extern const struct hive_info HIVE_INFO_HKLM;
+extern const struct hive_info HIVE_INFO_HKCU;
+extern const struct hive_info HIVE_INFO_HKCR;
+extern const struct hive_info HIVE_INFO_HKU;
+extern const struct hive_info HIVE_INFO_HKCC;
+extern const struct hive_info HIVE_INFO_HKDD;
+extern const struct hive_info HIVE_INFO_HKPD;
+extern const struct hive_info HIVE_INFO_HKPT;
+extern const struct hive_info HIVE_INFO_HKPN;
+
+extern const struct hive_info* HIVE_INFO[];
+
+const struct hive_info* hive_info(const char* name);
+bool srprs_hive(const char** ptr, const struct hive_info** result);
+
+
+
+const char *smbreg_get_charset(const char *c);
+
+bool set_iconv(smb_iconv_t* t, const char* to, const char* from);
+
+/**
+ * Parse option string
+ * @param[in,out] ptr parse position
+ * @param[in] mem_ctx talloc context
+ * @param[out] name ptr 2 value
+ * @param[out] value ptr 2 value
+ * @return true on success
+ */
+bool srprs_option(const char** ptr, const void* mem_ctx, char** name, char** value);
+
+/**
+ * Write Byte Order Mark for \p charset to file.
+ * If \c charset==NULL write BOM for \p ctype.
+ *
+ * @param[in] file file to write to
+ * @param[in] charset
+ * @param[in] ctype
+ *
+ * @return number of bytes written, -1 on error
+ * @todo write to cbuf
+ */
+int write_bom(FILE* file, const char* charset, charset_t ctype);
+
+/**
+ * Parse Byte Order Mark.
+ *
+ * @param[in,out] ptr parse position
+ * @param[out] name name of characterset
+ * @param[out] ctype charset_t
+ *
+ * @return true if found
+ * @ingroup parse bom
+ */
+bool srprs_bom(const char** ptr, const char** name, charset_t* ctype);
+
+enum fmt_case {
+ FMT_CASE_PRESERVE=0,
+ FMT_CASE_UPPER,
+ FMT_CASE_LOWER,
+ FMT_CASE_TITLE
+};
+int cbuf_puts_case(struct cbuf* s, const char* str, size_t len, enum fmt_case fmt);
+
+#endif /* __REG_PARSE_INTERNAL_H */
diff --git a/source3/registry/reg_parse_prs.c b/source3/registry/reg_parse_prs.c
new file mode 100644
index 0000000..8daa7d9
--- /dev/null
+++ b/source3/registry/reg_parse_prs.c
@@ -0,0 +1,453 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba memory buffer functions
+ Copyright (C) Andrew Tridgell 1992-1997
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1997
+ Copyright (C) Jeremy Allison 1999
+ Copyright (C) Andrew Bartlett 2003.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "reg_parse_prs.h"
+#include "rpc_dce.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_PARSE
+
+static const char *tab_depth(int level, int depth)
+{
+ if( CHECK_DEBUGLVL(level) ) {
+ dbgtext("%*s", depth*4, "");
+ }
+ return "";
+}
+
+/*******************************************************************
+ Debug output for parsing info
+
+ XXXX side-effect of this function is to increase the debug depth XXXX.
+
+********************************************************************/
+
+void prs_debug(prs_struct *ps, int depth, const char *desc, const char *fn_name)
+{
+ DEBUG(5+depth, ("%s%06x %s %s\n", tab_depth(5+depth,depth), ps->data_offset, fn_name, desc));
+}
+
+/**
+ * Initialise an expandable parse structure.
+ *
+ * @param size Initial buffer size. If >0, a new buffer will be
+ * created with talloc().
+ *
+ * @return False if allocation fails, otherwise True.
+ **/
+
+bool prs_init(prs_struct *ps, uint32_t size, TALLOC_CTX *ctx, bool io)
+{
+ ZERO_STRUCTP(ps);
+ ps->io = io;
+ ps->bigendian_data = RPC_LITTLE_ENDIAN;
+ ps->align = RPC_PARSE_ALIGN;
+ ps->is_dynamic = False;
+ ps->data_offset = 0;
+ ps->buffer_size = 0;
+ ps->data_p = NULL;
+ ps->mem_ctx = ctx;
+
+ if (size != 0) {
+ ps->buffer_size = size;
+ ps->data_p = (char *)talloc_zero_size(ps->mem_ctx, size);
+ if(ps->data_p == NULL) {
+ DEBUG(0,("prs_init: talloc fail for %u bytes.\n", (unsigned int)size));
+ return False;
+ }
+ ps->is_dynamic = True; /* We own this memory. */
+ } else if (MARSHALLING(ps)) {
+ /* If size is zero and we're marshalling we should allocate memory on demand. */
+ ps->is_dynamic = True;
+ }
+
+ return True;
+}
+
+/*******************************************************************
+ Delete the memory in a parse structure - if we own it.
+
+ NOTE: Contrary to the somewhat confusing naming, this function is not
+ intended for freeing memory allocated by prs_alloc_mem().
+ That memory is also attached to the talloc context given by
+ ps->mem_ctx, but is only freed when that talloc context is
+ freed. prs_mem_free() is used to delete "dynamic" memory
+ allocated in marshalling/unmarshalling.
+ ********************************************************************/
+
+void prs_mem_free(prs_struct *ps)
+{
+ if(ps->is_dynamic) {
+ TALLOC_FREE(ps->data_p);
+ }
+ ps->is_dynamic = False;
+ ps->buffer_size = 0;
+ ps->data_offset = 0;
+}
+
+/*******************************************************************
+ Allocate memory when unmarshalling... Always zero clears.
+ ********************************************************************/
+
+#if defined(PARANOID_MALLOC_CHECKER)
+char *prs_alloc_mem_(prs_struct *ps, size_t size, unsigned int count)
+#else
+char *prs_alloc_mem(prs_struct *ps, size_t size, unsigned int count)
+#endif
+{
+ char *ret = NULL;
+
+ if (size && count) {
+ /* We can't call the type-safe version here. */
+ ret = (char *)_talloc_zero_array(ps->mem_ctx, size, count,
+ "parse_prs");
+ }
+ return ret;
+}
+
+/*******************************************************************
+ Return the current talloc context we're using.
+ ********************************************************************/
+
+TALLOC_CTX *prs_get_mem_context(prs_struct *ps)
+{
+ return ps->mem_ctx;
+}
+
+/*******************************************************************
+ Attempt, if needed, to grow a data buffer.
+ Also depends on the data stream mode (io).
+ ********************************************************************/
+
+bool prs_grow(prs_struct *ps, uint32_t extra_space)
+{
+ uint32_t new_size;
+
+ ps->grow_size = MAX(ps->grow_size, ps->data_offset + extra_space);
+
+ if(ps->data_offset + extra_space <= ps->buffer_size)
+ return True;
+
+ /*
+ * We cannot grow the buffer if we're not reading
+ * into the prs_struct, or if we don't own the memory.
+ */
+
+ if(UNMARSHALLING(ps) || !ps->is_dynamic) {
+ DEBUG(0,("prs_grow: Buffer overflow - unable to expand buffer by %u bytes.\n",
+ (unsigned int)extra_space));
+ return False;
+ }
+
+ /*
+ * Decide how much extra space we really need.
+ */
+
+ extra_space -= (ps->buffer_size - ps->data_offset);
+ if(ps->buffer_size == 0) {
+
+ /*
+ * Start with 128 bytes (arbitrary value), enough for small rpc
+ * requests
+ */
+ new_size = MAX(128, extra_space);
+
+ ps->data_p = (char *)talloc_zero_size(ps->mem_ctx, new_size);
+ if(ps->data_p == NULL) {
+ DEBUG(0,("prs_grow: talloc failure for size %u.\n", (unsigned int)new_size));
+ return False;
+ }
+ } else {
+ /*
+ * If the current buffer size is bigger than the space needed,
+ * just double it, else add extra_space. Always keep 64 bytes
+ * more, so that after we added a large blob we don't have to
+ * realloc immediately again.
+ */
+ new_size = MAX(ps->buffer_size*2,
+ ps->buffer_size + extra_space + 64);
+
+ ps->data_p = talloc_realloc(ps->mem_ctx,
+ ps->data_p,
+ char,
+ new_size);
+ if (ps->data_p == NULL) {
+ DEBUG(0,("prs_grow: Realloc failure for size %u.\n",
+ (unsigned int)new_size));
+ return False;
+ }
+
+ memset(&ps->data_p[ps->buffer_size], '\0', (size_t)(new_size - ps->buffer_size));
+ }
+ ps->buffer_size = new_size;
+
+ return True;
+}
+
+/*******************************************************************
+ Get the data pointer (external interface).
+********************************************************************/
+
+char *prs_data_p(prs_struct *ps)
+{
+ return ps->data_p;
+}
+
+/*******************************************************************
+ Get the current data size (external interface).
+ ********************************************************************/
+
+uint32_t prs_data_size(prs_struct *ps)
+{
+ return ps->buffer_size;
+}
+
+/*******************************************************************
+ Fetch the current offset (external interface).
+ ********************************************************************/
+
+uint32_t prs_offset(prs_struct *ps)
+{
+ return ps->data_offset;
+}
+
+/*******************************************************************
+ Set the current offset (external interface).
+ ********************************************************************/
+
+bool prs_set_offset(prs_struct *ps, uint32_t offset)
+{
+ if ((offset > ps->data_offset)
+ && !prs_grow(ps, offset - ps->data_offset)) {
+ return False;
+ }
+
+ ps->data_offset = offset;
+ return True;
+}
+
+/*******************************************************************
+ Append the data from a buffer into a parse_struct.
+ ********************************************************************/
+
+bool prs_copy_data_in(prs_struct *dst, const char *src, uint32_t len)
+{
+ if (len == 0)
+ return True;
+
+ if(!prs_grow(dst, len))
+ return False;
+
+ memcpy(&dst->data_p[dst->data_offset], src, (size_t)len);
+ dst->data_offset += len;
+
+ return True;
+}
+
+/*******************************************************************
+ Align the data_len to a multiple of align bytes - filling with
+ zeros.
+ ********************************************************************/
+
+bool prs_align(prs_struct *ps)
+{
+ uint32_t mod = ps->data_offset & (ps->align-1);
+
+ if (ps->align != 0 && mod != 0) {
+ uint32_t extra_space = (ps->align - mod);
+ if(!prs_grow(ps, extra_space))
+ return False;
+ memset(&ps->data_p[ps->data_offset], '\0', (size_t)extra_space);
+ ps->data_offset += extra_space;
+ }
+
+ return True;
+}
+
+/******************************************************************
+ Align on a 8 byte boundary
+ *****************************************************************/
+
+bool prs_align_uint64(prs_struct *ps)
+{
+ bool ret;
+ uint8_t old_align = ps->align;
+
+ ps->align = 8;
+ ret = prs_align(ps);
+ ps->align = old_align;
+
+ return ret;
+}
+
+/*******************************************************************
+ Ensure we can read/write to a given offset.
+ ********************************************************************/
+
+char *prs_mem_get(prs_struct *ps, uint32_t extra_size)
+{
+ if(UNMARSHALLING(ps)) {
+ /*
+ * If reading, ensure that we can read the requested size item.
+ */
+ if (ps->data_offset + extra_size > ps->buffer_size) {
+ DEBUG(0,("prs_mem_get: reading data of size %u would overrun "
+ "buffer by %u bytes.\n",
+ (unsigned int)extra_size,
+ (unsigned int)(ps->data_offset + extra_size - ps->buffer_size) ));
+ return NULL;
+ }
+ } else {
+ /*
+ * Writing - grow the buffer if needed.
+ */
+ if(!prs_grow(ps, extra_size))
+ return NULL;
+ }
+ return &ps->data_p[ps->data_offset];
+}
+
+/*******************************************************************
+ Change the struct type.
+ ********************************************************************/
+
+void prs_switch_type(prs_struct *ps, bool io)
+{
+ if ((ps->io ^ io) == True)
+ ps->io=io;
+}
+
+/*******************************************************************
+ Stream a uint16.
+ ********************************************************************/
+
+bool prs_uint16(const char *name, prs_struct *ps, int depth, uint16_t *data16)
+{
+ char *q = prs_mem_get(ps, sizeof(uint16_t));
+ if (q == NULL)
+ return False;
+
+ if (UNMARSHALLING(ps)) {
+ if (ps->bigendian_data)
+ *data16 = RSVAL(q,0);
+ else
+ *data16 = SVAL(q,0);
+ } else {
+ if (ps->bigendian_data)
+ RSSVAL(q,0,*data16);
+ else
+ SSVAL(q,0,*data16);
+ }
+
+ DEBUGADD(5,("%s%04x %s: %04x\n", tab_depth(5,depth), ps->data_offset, name, *data16));
+
+ ps->data_offset += sizeof(uint16_t);
+
+ return True;
+}
+
+/*******************************************************************
+ Stream a uint32.
+ ********************************************************************/
+
+bool prs_uint32(const char *name, prs_struct *ps, int depth, uint32_t *data32)
+{
+ char *q = prs_mem_get(ps, sizeof(uint32_t));
+ if (q == NULL)
+ return False;
+
+ if (UNMARSHALLING(ps)) {
+ if (ps->bigendian_data)
+ *data32 = RIVAL(q,0);
+ else
+ *data32 = IVAL(q,0);
+ } else {
+ if (ps->bigendian_data)
+ RSIVAL(q,0,*data32);
+ else
+ SIVAL(q,0,*data32);
+ }
+
+ DEBUGADD(5,("%s%04x %s: %08x\n", tab_depth(5,depth), ps->data_offset, name, *data32));
+
+ ps->data_offset += sizeof(uint32_t);
+
+ return True;
+}
+
+/*******************************************************************
+ Stream a uint64_struct
+ ********************************************************************/
+bool prs_uint64(const char *name, prs_struct *ps, int depth, uint64_t *data64)
+{
+ if (UNMARSHALLING(ps)) {
+ uint32_t high, low;
+
+ if (!prs_uint32(name, ps, depth+1, &low))
+ return False;
+
+ if (!prs_uint32(name, ps, depth+1, &high))
+ return False;
+
+ *data64 = ((uint64_t)high << 32) + low;
+
+ return True;
+ } else {
+ uint32_t high = (*data64) >> 32, low = (*data64) & 0xFFFFFFFF;
+ return prs_uint32(name, ps, depth+1, &low) &&
+ prs_uint32(name, ps, depth+1, &high);
+ }
+}
+
+/******************************************************************
+ Stream an array of uint8s. Length is number of uint8s.
+ ********************************************************************/
+
+bool prs_uint8s(bool charmode, const char *name, prs_struct *ps, int depth, uint8_t *data8s, int len)
+{
+ int i;
+ char *q = prs_mem_get(ps, len);
+ if (q == NULL)
+ return False;
+
+ if (UNMARSHALLING(ps)) {
+ for (i = 0; i < len; i++)
+ data8s[i] = CVAL(q,i);
+ } else {
+ for (i = 0; i < len; i++)
+ SCVAL(q, i, data8s[i]);
+ }
+
+ DEBUGADD(5,("%s%04x %s: ", tab_depth(5,depth), ps->data_offset ,name));
+ if (charmode)
+ print_asc(5, (unsigned char*)data8s, len);
+ else {
+ for (i = 0; i < len; i++)
+ DEBUGADD(5,("%02x ", data8s[i]));
+ }
+ DEBUGADD(5,("\n"));
+
+ ps->data_offset += len;
+
+ return True;
+}
diff --git a/source3/registry/reg_parse_prs.h b/source3/registry/reg_parse_prs.h
new file mode 100644
index 0000000..ee65f51
--- /dev/null
+++ b/source3/registry/reg_parse_prs.h
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1997
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1997
+ Copyright (C) Paul Ashton 1997
+ Copyright (C) Jeremy Allison 2000-2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _REG_PARSE_PRS_H_
+#define _REG_PARSE_PRS_H_
+
+
+#define prs_init_empty( _ps_, _ctx_, _io_ ) (void) prs_init((_ps_), 0, (_ctx_), (_io_))
+
+typedef struct _prs_struct {
+ bool io; /* parsing in or out of data stream */
+ /*
+ * If the (incoming) data is big-endian. On output we are
+ * always little-endian.
+ */
+ bool bigendian_data;
+ uint8_t align; /* data alignment */
+ bool is_dynamic; /* Do we own this memory or not ? */
+ uint32_t data_offset; /* Current working offset into data. */
+ uint32_t buffer_size; /* Current allocated size of the buffer. */
+ uint32_t grow_size; /* size requested via prs_grow() calls */
+ /* The buffer itself. If "is_dynamic" is true this
+ * MUST BE TALLOC'ed off mem_ctx. */
+ char *data_p;
+ TALLOC_CTX *mem_ctx; /* When unmarshalling, use this.... */
+} prs_struct;
+
+/*
+ * Defines for io member of prs_struct.
+ */
+
+#define MARSHALL 0
+#define UNMARSHALL 1
+
+#define MARSHALLING(ps) (!(ps)->io)
+#define UNMARSHALLING(ps) ((ps)->io)
+
+#define RPC_PARSE_ALIGN 4
+
+void prs_debug(prs_struct *ps, int depth, const char *desc, const char *fn_name);
+bool prs_init(prs_struct *ps, uint32_t size, TALLOC_CTX *ctx, bool io);
+void prs_mem_free(prs_struct *ps);
+char *prs_alloc_mem_(prs_struct *ps, size_t size, unsigned int count);
+char *prs_alloc_mem(prs_struct *ps, size_t size, unsigned int count);
+TALLOC_CTX *prs_get_mem_context(prs_struct *ps);
+bool prs_grow(prs_struct *ps, uint32_t extra_space);
+char *prs_data_p(prs_struct *ps);
+uint32_t prs_data_size(prs_struct *ps);
+uint32_t prs_offset(prs_struct *ps);
+bool prs_set_offset(prs_struct *ps, uint32_t offset);
+bool prs_copy_data_in(prs_struct *dst, const char *src, uint32_t len);
+bool prs_align(prs_struct *ps);
+bool prs_align_uint64(prs_struct *ps);
+char *prs_mem_get(prs_struct *ps, uint32_t extra_size);
+void prs_switch_type(prs_struct *ps, bool io);
+bool prs_uint16(const char *name, prs_struct *ps, int depth, uint16_t *data16);
+bool prs_uint32(const char *name, prs_struct *ps, int depth, uint32_t *data32);
+bool prs_uint64(const char *name, prs_struct *ps, int depth, uint64_t *data64);
+bool prs_uint8s(bool charmode, const char *name, prs_struct *ps, int depth, uint8_t *data8s, int len);
+
+#endif
diff --git a/source3/registry/reg_perfcount.c b/source3/registry/reg_perfcount.c
new file mode 100644
index 0000000..131cc57
--- /dev/null
+++ b/source3/registry/reg_perfcount.c
@@ -0,0 +1,1463 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ *
+ * Copyright (C) Marcin Krzysztof Porwit 2005,
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../librpc/gen_ndr/perfcount.h"
+#include "registry.h"
+#include "reg_perfcount.h"
+#include "../libcli/registry/util_reg.h"
+#include "util_tdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+#define PERFCOUNT_MAX_LEN 256
+
+#define PERFCOUNTDIR "perfmon"
+#define NAMES_DB "names.tdb"
+#define DATA_DB "data.tdb"
+
+struct PERF_OBJECT_TYPE *_reg_perfcount_find_obj(struct PERF_DATA_BLOCK *block, int objind);
+
+/*********************************************************************
+*********************************************************************/
+
+/* returns perfcount path for dbname allocated on talloc_tos */
+static char *counters_directory(const char *dbname)
+{
+ char *dir_path = NULL;
+ char *db_subpath = NULL;
+ char *ret = NULL;
+
+ dir_path = state_path(talloc_tos(), PERFCOUNTDIR);
+ if (dir_path == NULL) {
+ return NULL;
+ }
+
+ if (!directory_create_or_exist(dir_path, 0755)) {
+ TALLOC_FREE(dir_path);
+ return NULL;
+ }
+
+ db_subpath = talloc_asprintf(dir_path, "%s/%s", PERFCOUNTDIR, dbname);
+ if (db_subpath == NULL) {
+ TALLOC_FREE(dir_path);
+ return NULL;
+ }
+
+ ret = state_path(talloc_tos(), db_subpath);
+ TALLOC_FREE(dir_path);
+ return ret;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+uint32_t reg_perfcount_get_base_index(void)
+{
+ char *fname;
+ TDB_CONTEXT *names;
+ TDB_DATA kbuf, dbuf;
+ char key[] = "1";
+ uint32_t retval = 0;
+ char buf[PERFCOUNT_MAX_LEN];
+
+ fname = counters_directory(NAMES_DB);
+ if (fname == NULL) {
+ return 0;
+ }
+
+ names = tdb_open_log(fname, 0, TDB_DEFAULT, O_RDONLY, 0444);
+
+ if ( !names ) {
+ DEBUG(2, ("reg_perfcount_get_base_index: unable to open [%s].\n", fname));
+ TALLOC_FREE(fname);
+ return 0;
+ }
+ /* needs to read the value of key "1" from the counter_names.tdb file, as that is
+ where the total number of counters is stored. We're assuming no holes in the
+ enumeration.
+ The format for the counter_names.tdb file is:
+ key value
+ 1 num_counters
+ 2 perf_counter1
+ 3 perf_counter1_help
+ 4 perf_counter2
+ 5 perf_counter2_help
+ even_num perf_counter<even_num>
+ even_num+1 perf_counter<even_num>_help
+ and so on.
+ So last_counter becomes num_counters*2, and last_help will be last_counter+1 */
+ kbuf = string_tdb_data(key);
+ dbuf = tdb_fetch(names, kbuf);
+ if(dbuf.dptr == NULL)
+ {
+ DEBUG(1, ("reg_perfcount_get_base_index: failed to find key \'1\' in [%s].\n", fname));
+ tdb_close(names);
+ TALLOC_FREE(fname);
+ return 0;
+ }
+
+ tdb_close(names);
+ TALLOC_FREE(fname);
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, dbuf.dptr, dbuf.dsize);
+ retval = (uint32_t)atoi(buf);
+ SAFE_FREE(dbuf.dptr);
+ return retval;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+uint32_t reg_perfcount_get_last_counter(uint32_t base_index)
+{
+ uint32_t retval;
+
+ if(base_index == 0)
+ retval = 0;
+ else
+ retval = base_index * 2;
+
+ return retval;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+uint32_t reg_perfcount_get_last_help(uint32_t last_counter)
+{
+ uint32_t retval;
+
+ if(last_counter == 0)
+ retval = 0;
+ else
+ retval = last_counter + 1;
+
+ return retval;
+}
+
+
+/*********************************************************************
+*********************************************************************/
+
+static uint32_t _reg_perfcount_multi_sz_from_tdb(TDB_CONTEXT *tdb,
+ int keyval,
+ char **retbuf,
+ uint32_t buffer_size)
+{
+ TDB_DATA kbuf, dbuf;
+ char temp[PERFCOUNT_MAX_LEN] = {0};
+ char *buf1 = *retbuf;
+ char *p = NULL;
+ uint32_t working_size = 0;
+ DATA_BLOB name_index, name;
+ bool ok;
+
+ snprintf(temp, sizeof(temp), "%d", keyval);
+ kbuf = string_tdb_data(temp);
+ dbuf = tdb_fetch(tdb, kbuf);
+ if(dbuf.dptr == NULL)
+ {
+ /* If a key isn't there, just bypass it -- this really shouldn't
+ happen unless someone's mucking around with the tdb */
+ DEBUG(3, ("_reg_perfcount_multi_sz_from_tdb: failed to find key [%s] in [%s].\n",
+ temp, tdb_name(tdb)));
+ return buffer_size;
+ }
+ /* First encode the name_index */
+ working_size = (kbuf.dsize + 1)*sizeof(uint16_t);
+ /* SMB_REALLOC frees buf1 on error */
+ p = (char *)SMB_REALLOC(buf1, buffer_size + working_size);
+ if (p == NULL) {
+ buffer_size = 0;
+ return buffer_size;
+ }
+ buf1 = p;
+ ok = push_reg_sz(talloc_tos(), &name_index, (const char *)kbuf.dptr);
+ if (!ok) {
+ SAFE_FREE(buf1);
+ buffer_size = 0;
+ return buffer_size;
+ }
+ memcpy(buf1+buffer_size, (char *)name_index.data, working_size);
+ buffer_size += working_size;
+ /* Now encode the actual name */
+ working_size = (dbuf.dsize + 1)*sizeof(uint16_t);
+ /* SMB_REALLOC frees buf1 on error */
+ p = (char *)SMB_REALLOC(buf1, buffer_size + working_size);
+ if (p == NULL) {
+ buffer_size = 0;
+ return buffer_size;
+ }
+ buf1 = p;
+ memset(temp, 0, sizeof(temp));
+ memcpy(temp, dbuf.dptr, dbuf.dsize);
+ SAFE_FREE(dbuf.dptr);
+ ok = push_reg_sz(talloc_tos(), &name, temp);
+ if (!ok) {
+ SAFE_FREE(buf1);
+ buffer_size = 0;
+ return buffer_size;
+ }
+ memcpy(buf1+buffer_size, (char *)name.data, working_size);
+ buffer_size += working_size;
+
+ *retbuf = buf1;
+
+ return buffer_size;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+uint32_t reg_perfcount_get_counter_help(uint32_t base_index, char **retbuf)
+{
+ char *buf1 = NULL;
+ uint32_t buffer_size = 0;
+ TDB_CONTEXT *names;
+ char *fname;
+ int i;
+
+ if (base_index == 0) {
+ return 0;
+ }
+
+ fname = counters_directory(NAMES_DB);
+ if (fname == NULL) {
+ return 0;
+ }
+
+ names = tdb_open_log(fname, 0, TDB_DEFAULT, O_RDONLY, 0444);
+
+ if (names == NULL) {
+ DEBUG(1, ("reg_perfcount_get_counter_help: unable to open [%s].\n", fname));
+ TALLOC_FREE(fname);
+ return 0;
+ }
+ TALLOC_FREE(fname);
+
+ for(i = 1; i <= base_index; i++)
+ {
+ buffer_size = _reg_perfcount_multi_sz_from_tdb(names, (i*2)+1, retbuf, buffer_size);
+ }
+ tdb_close(names);
+
+ /* Now terminate the MULTI_SZ with a double unicode NULL */
+ buf1 = *retbuf;
+ buf1 = (char *)SMB_REALLOC(buf1, buffer_size + 2);
+ if(!buf1) {
+ buffer_size = 0;
+ } else {
+ buf1[buffer_size++] = '\0';
+ buf1[buffer_size++] = '\0';
+ }
+
+ *retbuf = buf1;
+
+ return buffer_size;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+uint32_t reg_perfcount_get_counter_names(uint32_t base_index, char **retbuf)
+{
+ char *buf1 = NULL;
+ uint32_t buffer_size = 0;
+ TDB_CONTEXT *names;
+ char *fname;
+ int i;
+
+ if (base_index == 0) {
+ return 0;
+ }
+
+ fname = counters_directory(NAMES_DB);
+ if (fname == NULL) {
+ return 0;
+ }
+
+ names = tdb_open_log(fname, 0, TDB_DEFAULT, O_RDONLY, 0444);
+
+ if (names == NULL) {
+ DEBUG(1, ("reg_perfcount_get_counter_names: unable to open [%s].\n", fname));
+ TALLOC_FREE(fname);
+ return 0;
+ }
+ TALLOC_FREE(fname);
+
+ buffer_size = _reg_perfcount_multi_sz_from_tdb(names, 1, retbuf, buffer_size);
+
+ for(i = 1; i <= base_index; i++)
+ {
+ buffer_size = _reg_perfcount_multi_sz_from_tdb(names, i*2, retbuf, buffer_size);
+ }
+ tdb_close(names);
+
+ /* Now terminate the MULTI_SZ with a double unicode NULL */
+ buf1 = *retbuf;
+ buf1 = (char *)SMB_REALLOC(buf1, buffer_size + 2);
+ if(!buf1) {
+ buffer_size = 0;
+ } else {
+ buf1[buffer_size++] = '\0';
+ buf1[buffer_size++] = '\0';
+ }
+
+ *retbuf=buf1;
+
+ return buffer_size;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static void _reg_perfcount_make_key(TDB_DATA *key,
+ char *buf,
+ int buflen,
+ int key_part1,
+ const char *key_part2)
+{
+ memset(buf, 0, buflen);
+ if(key_part2 != NULL)
+ snprintf(buf, buflen,"%d%s", key_part1, key_part2);
+ else
+ snprintf(buf, buflen, "%d", key_part1);
+
+ *key = string_tdb_data(buf);
+
+ return;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_isparent(TDB_DATA data)
+{
+ if(data.dsize > 0)
+ {
+ if(data.dptr[0] == 'p')
+ return True;
+ else
+ return False;
+ }
+ return False;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_ischild(TDB_DATA data)
+{
+ if(data.dsize > 0)
+ {
+ if(data.dptr[0] == 'c')
+ return True;
+ else
+ return False;
+ }
+ return False;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static uint32_t _reg_perfcount_get_numinst(int objInd, TDB_CONTEXT *names)
+{
+ TDB_DATA key, data;
+ char buf[PERFCOUNT_MAX_LEN];
+
+ _reg_perfcount_make_key(&key, buf, PERFCOUNT_MAX_LEN, objInd, "inst");
+ data = tdb_fetch(names, key);
+
+ if(data.dptr == NULL)
+ return (uint32_t)PERF_NO_INSTANCES;
+
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, data.dsize);
+ SAFE_FREE(data.dptr);
+ return (uint32_t)atoi(buf);
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_add_instance(struct PERF_OBJECT_TYPE *obj,
+ TALLOC_CTX *mem_ctx,
+ int instInd,
+ TDB_CONTEXT *names);
+
+static bool _reg_perfcount_add_object(struct PERF_DATA_BLOCK *block,
+ TALLOC_CTX *mem_ctx,
+ int num,
+ TDB_DATA data,
+ TDB_CONTEXT *names)
+{
+ int i;
+ bool success = True;
+ struct PERF_OBJECT_TYPE *obj;
+
+ block->objects = (struct PERF_OBJECT_TYPE *)talloc_realloc(mem_ctx,
+ block->objects,
+ struct PERF_OBJECT_TYPE,
+ block->NumObjectTypes+1);
+ if(block->objects == NULL)
+ return False;
+ obj = &(block->objects[block->NumObjectTypes]);
+ memset((void *)&(block->objects[block->NumObjectTypes]), 0, sizeof(struct PERF_OBJECT_TYPE));
+ block->objects[block->NumObjectTypes].ObjectNameTitleIndex = num;
+ block->objects[block->NumObjectTypes].ObjectNameTitlePointer = 0;
+ block->objects[block->NumObjectTypes].ObjectHelpTitleIndex = num+1;
+ block->objects[block->NumObjectTypes].ObjectHelpTitlePointer = 0;
+ block->objects[block->NumObjectTypes].NumCounters = 0;
+ block->objects[block->NumObjectTypes].DefaultCounter = 0;
+ block->objects[block->NumObjectTypes].NumInstances = _reg_perfcount_get_numinst(num, names);
+ block->objects[block->NumObjectTypes].counters = NULL;
+ block->objects[block->NumObjectTypes].instances = NULL;
+ block->objects[block->NumObjectTypes].counter_data.ByteLength = sizeof(uint32_t);
+ block->objects[block->NumObjectTypes].counter_data.data = NULL;
+ block->objects[block->NumObjectTypes].DetailLevel = PERF_DETAIL_NOVICE;
+ block->NumObjectTypes+=1;
+
+ for(i = 0; i < (int)obj->NumInstances; i++) {
+ success = _reg_perfcount_add_instance(obj, mem_ctx, i, names);
+ }
+
+ return success;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_get_counter_data(TDB_DATA key, TDB_DATA *data)
+{
+ TDB_CONTEXT *counters;
+ char *fname;
+
+ fname = counters_directory(DATA_DB);
+ if (fname == NULL) {
+ return false;
+ }
+
+ counters = tdb_open_log(fname, 0, TDB_DEFAULT, O_RDONLY, 0444);
+
+ if (counters == NULL) {
+ DEBUG(1, ("reg_perfcount_get_counter_data: unable to open [%s].\n", fname));
+ TALLOC_FREE(fname);
+ return False;
+ }
+ TALLOC_FREE(fname);
+
+ *data = tdb_fetch(counters, key);
+
+ tdb_close(counters);
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static uint32_t _reg_perfcount_get_size_field(uint32_t CounterType)
+{
+ uint32_t retval;
+
+ retval = CounterType;
+
+ /* First mask out reserved lower 8 bits */
+ retval = retval & 0xFFFFFF00;
+ retval = retval << 22;
+ retval = retval >> 22;
+
+ return retval;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static uint32_t _reg_perfcount_compute_scale(int64_t data)
+{
+ int scale = 0;
+ if(data == 0)
+ return scale;
+ while(data > 100)
+ {
+ data /= 10;
+ scale--;
+ }
+ while(data < 10)
+ {
+ data *= 10;
+ scale++;
+ }
+
+ return (uint32_t)scale;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_get_counter_info(struct PERF_DATA_BLOCK *block,
+ TALLOC_CTX *mem_ctx,
+ int CounterIndex,
+ struct PERF_OBJECT_TYPE *obj,
+ TDB_CONTEXT *names)
+{
+ TDB_DATA key, data;
+ char buf[PERFCOUNT_MAX_LEN];
+ size_t dsize, padding;
+ long int data32, dbuf[2];
+ int64_t data64;
+ uint32_t counter_size;
+
+ obj->counters[obj->NumCounters].DefaultScale = 0;
+ dbuf[0] = dbuf[1] = 0;
+ padding = 0;
+
+ _reg_perfcount_make_key(&key, buf, PERFCOUNT_MAX_LEN, CounterIndex, "type");
+ data = tdb_fetch(names, key);
+ if(data.dptr == NULL)
+ {
+ DEBUG(3, ("_reg_perfcount_get_counter_info: No type data for counter [%d].\n", CounterIndex));
+ return False;
+ }
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, data.dsize);
+ obj->counters[obj->NumCounters].CounterType = atoi(buf);
+ DEBUG(10, ("_reg_perfcount_get_counter_info: Got type [%d] for counter [%d].\n",
+ obj->counters[obj->NumCounters].CounterType, CounterIndex));
+ SAFE_FREE(data.dptr);
+
+ /* Fetch the actual data */
+ _reg_perfcount_make_key(&key, buf, PERFCOUNT_MAX_LEN, CounterIndex, "");
+ _reg_perfcount_get_counter_data(key, &data);
+ if(data.dptr == NULL)
+ {
+ DEBUG(3, ("_reg_perfcount_get_counter_info: No counter data for counter [%d].\n", CounterIndex));
+ return False;
+ }
+
+ counter_size = _reg_perfcount_get_size_field(obj->counters[obj->NumCounters].CounterType);
+
+ if(counter_size == PERF_SIZE_DWORD)
+ {
+ dsize = sizeof(data32);
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, data.dsize);
+ data32 = strtol(buf, NULL, 0);
+ if((obj->counters[obj->NumCounters].CounterType & 0x00000F00) == PERF_TYPE_NUMBER)
+ obj->counters[obj->NumCounters].DefaultScale = _reg_perfcount_compute_scale((int64_t)data32);
+ else
+ obj->counters[obj->NumCounters].DefaultScale = 0;
+ dbuf[0] = data32;
+ padding = (dsize - (obj->counter_data.ByteLength%dsize)) % dsize;
+ }
+ else if(counter_size == PERF_SIZE_LARGE)
+ {
+ dsize = sizeof(data64);
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, data.dsize);
+ data64 = atof(buf);
+ if((obj->counters[obj->NumCounters].CounterType & 0x00000F00) == PERF_TYPE_NUMBER)
+ obj->counters[obj->NumCounters].DefaultScale = _reg_perfcount_compute_scale(data64);
+ else
+ obj->counters[obj->NumCounters].DefaultScale = 0;
+ memcpy((void *)dbuf, (const void *)&data64, dsize);
+ padding = (dsize - (obj->counter_data.ByteLength%dsize)) % dsize;
+ }
+ else /* PERF_SIZE_VARIABLE_LEN */
+ {
+ dsize = data.dsize;
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, data.dsize);
+ }
+ SAFE_FREE(data.dptr);
+
+ obj->counter_data.ByteLength += dsize + padding;
+ obj->counter_data.data = talloc_realloc(mem_ctx,
+ obj->counter_data.data,
+ uint8_t,
+ obj->counter_data.ByteLength - sizeof(uint32_t));
+ if(obj->counter_data.data == NULL)
+ return False;
+ if(dbuf[0] != 0 || dbuf[1] != 0)
+ {
+ memcpy((void *)(obj->counter_data.data +
+ (obj->counter_data.ByteLength - (sizeof(uint32_t) + dsize))),
+ (const void *)dbuf, dsize);
+ }
+ else
+ {
+ /* Handling PERF_SIZE_VARIABLE_LEN */
+ memcpy((void *)(obj->counter_data.data +
+ (obj->counter_data.ByteLength - (sizeof(uint32_t) + dsize))),
+ (const void *)buf, dsize);
+ }
+ obj->counters[obj->NumCounters].CounterOffset = obj->counter_data.ByteLength - dsize;
+ if(obj->counters[obj->NumCounters].CounterOffset % dsize != 0)
+ {
+ DEBUG(3,("Improperly aligned counter [%d]\n", obj->NumCounters));
+ }
+ obj->counters[obj->NumCounters].CounterSize = dsize;
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+struct PERF_OBJECT_TYPE *_reg_perfcount_find_obj(struct PERF_DATA_BLOCK *block, int objind)
+{
+ int i;
+
+ struct PERF_OBJECT_TYPE *obj = NULL;
+
+ for(i = 0; i < block->NumObjectTypes; i++)
+ {
+ if(block->objects[i].ObjectNameTitleIndex == objind)
+ {
+ obj = &(block->objects[i]);
+ }
+ }
+
+ return obj;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_add_counter(struct PERF_DATA_BLOCK *block,
+ TALLOC_CTX *mem_ctx,
+ int num,
+ TDB_DATA data,
+ TDB_CONTEXT *names)
+{
+ char *begin, *end, *start, *stop;
+ int parent;
+ struct PERF_OBJECT_TYPE *obj;
+ bool success = True;
+ char buf[PERFCOUNT_MAX_LEN];
+
+ obj = NULL;
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, data.dsize);
+ begin = strchr(buf, '[');
+ end = strchr(buf, ']');
+ if(begin == NULL || end == NULL)
+ return False;
+ start = begin+1;
+
+ while(start < end) {
+ stop = strchr(start, ',');
+ if(stop == NULL)
+ stop = end;
+ *stop = '\0';
+ parent = atoi(start);
+
+ obj = _reg_perfcount_find_obj(block, parent);
+ if(obj == NULL) {
+ /* At this point we require that the parent object exist.
+ This can probably be handled better at some later time */
+ DEBUG(3, ("_reg_perfcount_add_counter: Could not find parent object [%d] for counter [%d].\n",
+ parent, num));
+ return False;
+ }
+ obj->counters = (struct PERF_COUNTER_DEFINITION *)talloc_realloc(mem_ctx,
+ obj->counters,
+ struct PERF_COUNTER_DEFINITION,
+ obj->NumCounters+1);
+ if(obj->counters == NULL)
+ return False;
+ memset((void *)&(obj->counters[obj->NumCounters]), 0, sizeof(struct PERF_COUNTER_DEFINITION));
+ obj->counters[obj->NumCounters].CounterNameTitleIndex=num;
+ obj->counters[obj->NumCounters].CounterHelpTitleIndex=num+1;
+ obj->counters[obj->NumCounters].DetailLevel = PERF_DETAIL_NOVICE;
+ obj->counters[obj->NumCounters].ByteLength = sizeof(struct PERF_COUNTER_DEFINITION);
+ success = _reg_perfcount_get_counter_info(block, mem_ctx, num, obj, names);
+ obj->NumCounters += 1;
+ start = stop + 1;
+ }
+
+ /* Handle case of Objects/Counters without any counter data, which would suggest
+ that the required instances are not there yet, so change NumInstances from
+ PERF_NO_INSTANCES to 0 */
+
+ return success;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_get_instance_info(struct PERF_INSTANCE_DEFINITION *inst,
+ TALLOC_CTX *mem_ctx,
+ int instId,
+ struct PERF_OBJECT_TYPE *obj,
+ TDB_CONTEXT *names)
+{
+ TDB_DATA key, data;
+ char buf[PERFCOUNT_MAX_LEN] = {0};
+ char temp[32] = {0};
+ smb_ucs2_t *name = NULL;
+ int pad;
+
+ /* First grab the instance data from the data file */
+ snprintf(temp, sizeof(temp), "i%d", instId);
+ _reg_perfcount_make_key(&key, buf, PERFCOUNT_MAX_LEN, obj->ObjectNameTitleIndex, temp);
+ if (!_reg_perfcount_get_counter_data(key, &data)) {
+ DEBUG(3, ("_reg_perfcount_get_counter_data failed\n"));
+ return false;
+ }
+ if(data.dptr == NULL)
+ {
+ DEBUG(3, ("_reg_perfcount_get_instance_info: No instance data for instance [%s].\n",
+ buf));
+ return False;
+ }
+ inst->counter_data.ByteLength = data.dsize + sizeof(inst->counter_data.ByteLength);
+ inst->counter_data.data = talloc_realloc(mem_ctx,
+ inst->counter_data.data,
+ uint8_t,
+ data.dsize);
+ if(inst->counter_data.data == NULL)
+ return False;
+ memset(inst->counter_data.data, 0, data.dsize);
+ memcpy(inst->counter_data.data, data.dptr, data.dsize);
+ SAFE_FREE(data.dptr);
+
+ /* Fetch instance name */
+ snprintf(temp, sizeof(temp), "i%dname", instId);
+ _reg_perfcount_make_key(&key, buf, PERFCOUNT_MAX_LEN, obj->ObjectNameTitleIndex, temp);
+ data = tdb_fetch(names, key);
+ if(data.dptr == NULL)
+ {
+ /* Not actually an error, but possibly unintended? -- just logging FYI */
+ DEBUG(3, ("_reg_perfcount_get_instance_info: No instance name for instance [%s].\n",
+ buf));
+ inst->NameLength = 0;
+ }
+ else
+ {
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, MIN(PERFCOUNT_MAX_LEN-1,data.dsize));
+ buf[PERFCOUNT_MAX_LEN-1] = '\0';
+ inst->NameLength = rpcstr_push_talloc(mem_ctx, &name, buf);
+ if (inst->NameLength == (uint32_t)-1 || !name) {
+ SAFE_FREE(data.dptr);
+ return False;
+ }
+ inst->data = talloc_realloc(mem_ctx,
+ inst->data,
+ uint8_t,
+ inst->NameLength);
+ if (inst->data == NULL) {
+ SAFE_FREE(data.dptr);
+ return False;
+ }
+ memcpy(inst->data, name, inst->NameLength);
+ SAFE_FREE(data.dptr);
+ }
+
+ inst->ParentObjectTitleIndex = 0;
+ inst->ParentObjectTitlePointer = 0;
+ inst->UniqueID = PERF_NO_UNIQUE_ID;
+ inst->NameOffset = 6 * sizeof(uint32_t);
+
+ inst->ByteLength = inst->NameOffset + inst->NameLength;
+ /* Need to be aligned on a 64-bit boundary here for counter_data */
+ if((pad = (inst->ByteLength % 8)))
+ {
+ pad = 8 - pad;
+ inst->data = talloc_realloc(mem_ctx,
+ inst->data,
+ uint8_t,
+ inst->NameLength + pad);
+ memset(inst->data + inst->NameLength, 0, pad);
+ inst->ByteLength += pad;
+ }
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_add_instance(struct PERF_OBJECT_TYPE *obj,
+ TALLOC_CTX *mem_ctx,
+ int instInd,
+ TDB_CONTEXT *names)
+{
+ struct PERF_INSTANCE_DEFINITION *inst;
+
+ if(obj->instances == NULL) {
+ obj->instances = talloc_realloc(mem_ctx,
+ obj->instances,
+ struct PERF_INSTANCE_DEFINITION,
+ obj->NumInstances);
+ }
+ if(obj->instances == NULL)
+ return False;
+
+ memset(&(obj->instances[instInd]), 0, sizeof(struct PERF_INSTANCE_DEFINITION));
+ inst = &(obj->instances[instInd]);
+ return _reg_perfcount_get_instance_info(inst, mem_ctx, instInd, obj, names);
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static int _reg_perfcount_assemble_global(struct PERF_DATA_BLOCK *block,
+ TALLOC_CTX *mem_ctx,
+ int base_index,
+ TDB_CONTEXT *names)
+{
+ bool success;
+ int i, j, retval = 0;
+ char keybuf[PERFCOUNT_MAX_LEN];
+ TDB_DATA key, data;
+
+ for(i = 1; i <= base_index; i++)
+ {
+ j = i*2;
+ _reg_perfcount_make_key(&key, keybuf, PERFCOUNT_MAX_LEN, j, "rel");
+ data = tdb_fetch(names, key);
+ if(data.dptr != NULL)
+ {
+ if(_reg_perfcount_isparent(data))
+ success = _reg_perfcount_add_object(block, mem_ctx, j, data, names);
+ else if(_reg_perfcount_ischild(data))
+ success = _reg_perfcount_add_counter(block, mem_ctx, j, data, names);
+ else
+ {
+ DEBUG(3, ("Bogus relationship [%s] for counter [%d].\n", data.dptr, j));
+ success = False;
+ }
+ if(success == False)
+ {
+ DEBUG(3, ("_reg_perfcount_assemble_global: Failed to add new relationship for counter [%d].\n", j));
+ retval = -1;
+ }
+ SAFE_FREE(data.dptr);
+ }
+ else
+ DEBUG(3, ("NULL relationship for counter [%d] using key [%s].\n", j, keybuf));
+ }
+ return retval;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_get_64(uint64_t *retval,
+ TDB_CONTEXT *tdb,
+ int key_part1,
+ const char *key_part2)
+{
+ TDB_DATA key, data;
+ char buf[PERFCOUNT_MAX_LEN];
+
+ _reg_perfcount_make_key(&key, buf, PERFCOUNT_MAX_LEN, key_part1, key_part2);
+
+ data = tdb_fetch(tdb, key);
+ if(data.dptr == NULL)
+ {
+ DEBUG(3,("_reg_perfcount_get_64: No data found for key [%s].\n", key.dptr));
+ return False;
+ }
+
+ memset(buf, 0, PERFCOUNT_MAX_LEN);
+ memcpy(buf, data.dptr, data.dsize);
+ SAFE_FREE(data.dptr);
+
+ *retval = atof(buf);
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_init_data_block_perf(struct PERF_DATA_BLOCK *block,
+ TDB_CONTEXT *names)
+{
+ uint64_t PerfFreq, PerfTime, PerfTime100nSec;
+ TDB_CONTEXT *counters;
+ bool status = False;
+ char *fname;
+
+ fname = counters_directory(DATA_DB);
+ if (fname == NULL) {
+ return false;
+ }
+
+ counters = tdb_open_log(fname, 0, TDB_DEFAULT, O_RDONLY, 0444);
+
+ if (counters == NULL) {
+ DEBUG(1, ("reg_perfcount_init_data_block_perf: unable to open [%s].\n", fname));
+ TALLOC_FREE(fname);
+ return False;
+ }
+ TALLOC_FREE(fname);
+
+ status = _reg_perfcount_get_64(&PerfFreq, names, 0, "PerfFreq");
+ if(status == False)
+ {
+ tdb_close(counters);
+ return status;
+ }
+ memcpy((void *)&(block->PerfFreq), (const void *)&PerfFreq, sizeof(PerfFreq));
+
+ status = _reg_perfcount_get_64(&PerfTime, counters, 0, "PerfTime");
+ if(status == False)
+ {
+ tdb_close(counters);
+ return status;
+ }
+ memcpy((void *)&(block->PerfTime), (const void *)&PerfTime, sizeof(PerfTime));
+
+ status = _reg_perfcount_get_64(&PerfTime100nSec, counters, 0, "PerfTime100nSec");
+ if(status == False)
+ {
+ tdb_close(counters);
+ return status;
+ }
+ memcpy((void *)&(block->PerfTime100nSec), (const void *)&PerfTime100nSec, sizeof(PerfTime100nSec));
+
+ tdb_close(counters);
+ return True;
+}
+
+/*******************************************************************
+********************************************************************/
+
+static bool make_systemtime(struct SYSTEMTIME *systime, struct tm *unixtime)
+{
+ systime->year=unixtime->tm_year+1900;
+ systime->month=unixtime->tm_mon+1;
+ systime->dayofweek=unixtime->tm_wday;
+ systime->day=unixtime->tm_mday;
+ systime->hour=unixtime->tm_hour;
+ systime->minute=unixtime->tm_min;
+ systime->second=unixtime->tm_sec;
+ systime->milliseconds=0;
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_init_data_block(struct PERF_DATA_BLOCK *block,
+ TALLOC_CTX *mem_ctx, TDB_CONTEXT *names,
+ bool bigendian_data)
+{
+ smb_ucs2_t *temp = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ time_t tm;
+ size_t sz;
+
+ sz = rpcstr_push_talloc(tmp_ctx, &temp, "PERF");
+ if ((sz == -1) || (temp == NULL)) {
+ goto err_out;
+ }
+ memcpy(block->Signature, temp, strlen_w(temp) *2);
+
+ if(bigendian_data)
+ block->LittleEndian = 0;
+ else
+ block->LittleEndian = 1;
+ block->Version = 1;
+ block->Revision = 1;
+ block->TotalByteLength = 0;
+ block->NumObjectTypes = 0;
+ block->DefaultObject = -1;
+ block->objects = NULL;
+ tm = time(NULL);
+ make_systemtime(&(block->SystemTime), gmtime(&tm));
+ _reg_perfcount_init_data_block_perf(block, names);
+
+ sz = rpcstr_push_talloc(tmp_ctx, &temp, lp_netbios_name());
+ if ((sz == -1) || (temp == NULL)) {
+ goto err_out;
+ }
+ block->SystemNameLength = (strlen_w(temp) * 2) + 2;
+ block->data = talloc_zero_array(mem_ctx, uint8_t, block->SystemNameLength + (8 - (block->SystemNameLength % 8)));
+ if (block->data == NULL) {
+ goto err_out;
+ }
+ memcpy(block->data, temp, block->SystemNameLength);
+ block->SystemNameOffset = sizeof(struct PERF_DATA_BLOCK) - sizeof(block->objects) - sizeof(block->data);
+ block->HeaderLength = block->SystemNameOffset + block->SystemNameLength;
+ /* Make sure to adjust for 64-bit alignment for when we finish writing the system name,
+ so that the PERF_OBJECT_TYPE struct comes out 64-bit aligned */
+ block->HeaderLength += 8 - (block->HeaderLength % 8);
+ talloc_free(tmp_ctx);
+
+ return true;
+
+err_out:
+ talloc_free(tmp_ctx);
+ return false;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static uint32_t _reg_perfcount_perf_data_block_fixup(struct PERF_DATA_BLOCK *block, TALLOC_CTX *mem_ctx)
+{
+ int obj, cnt, inst, pad, i;
+ struct PERF_OBJECT_TYPE *object;
+ struct PERF_INSTANCE_DEFINITION *instance;
+ struct PERF_COUNTER_DEFINITION *counter;
+ struct PERF_COUNTER_BLOCK *counter_data;
+ char *temp = NULL, *src_addr, *dst_addr;
+
+ block->TotalByteLength = 0;
+ object = block->objects;
+ for(obj = 0; obj < block->NumObjectTypes; obj++)
+ {
+ object[obj].TotalByteLength = 0;
+ object[obj].DefinitionLength = 0;
+ instance = object[obj].instances;
+ counter = object[obj].counters;
+ for(cnt = 0; cnt < object[obj].NumCounters; cnt++)
+ {
+ object[obj].TotalByteLength += counter[cnt].ByteLength;
+ object[obj].DefinitionLength += counter[cnt].ByteLength;
+ }
+ if(object[obj].NumInstances != PERF_NO_INSTANCES)
+ {
+ for(inst = 0; inst < object[obj].NumInstances; inst++)
+ {
+ instance = &(object[obj].instances[inst]);
+ object[obj].TotalByteLength += instance->ByteLength;
+ counter_data = &(instance->counter_data);
+ counter = &(object[obj].counters[object[obj].NumCounters - 1]);
+ counter_data->ByteLength = counter->CounterOffset + counter->CounterSize + sizeof(counter_data->ByteLength);
+ temp = talloc_realloc(mem_ctx,
+ temp,
+ char,
+ counter_data->ByteLength- sizeof(counter_data->ByteLength));
+ if (temp == NULL) {
+ return 0;
+ }
+ memset(temp, 0, counter_data->ByteLength - sizeof(counter_data->ByteLength));
+ src_addr = (char *)counter_data->data;
+ for(i = 0; i < object[obj].NumCounters; i++)
+ {
+ counter = &(object[obj].counters[i]);
+ dst_addr = temp + counter->CounterOffset - sizeof(counter_data->ByteLength);
+ memcpy(dst_addr, src_addr, counter->CounterSize);
+ src_addr += counter->CounterSize;
+ }
+ /* Make sure to be 64-bit aligned */
+ if((pad = (counter_data->ByteLength % 8)))
+ {
+ pad = 8 - pad;
+ }
+ counter_data->data = talloc_realloc(mem_ctx,
+ counter_data->data,
+ uint8_t,
+ counter_data->ByteLength - sizeof(counter_data->ByteLength) + pad);
+ if (counter_data->data == NULL) {
+ return 0;
+ }
+ memset(counter_data->data, 0, counter_data->ByteLength - sizeof(counter_data->ByteLength) + pad);
+ memcpy(counter_data->data, temp, counter_data->ByteLength - sizeof(counter_data->ByteLength));
+ counter_data->ByteLength += pad;
+ object[obj].TotalByteLength += counter_data->ByteLength;
+ }
+ }
+ else
+ {
+ /* Need to be 64-bit aligned at the end of the counter_data block, so pad counter_data to a 64-bit boundary,
+ so that the next PERF_OBJECT_TYPE can start on a 64-bit alignment */
+ if((pad = (object[obj].counter_data.ByteLength % 8)))
+ {
+ pad = 8 - pad;
+ object[obj].counter_data.data = talloc_realloc(mem_ctx,
+ object[obj].counter_data.data,
+ uint8_t,
+ object[obj].counter_data.ByteLength + pad);
+ memset((void *)(object[obj].counter_data.data + object[obj].counter_data.ByteLength), 0, pad);
+ object[obj].counter_data.ByteLength += pad;
+ }
+ object[obj].TotalByteLength += object[obj].counter_data.ByteLength;
+ }
+ object[obj].HeaderLength = sizeof(*object) - (sizeof(counter) + sizeof(instance) + sizeof(struct PERF_COUNTER_BLOCK));
+ object[obj].TotalByteLength += object[obj].HeaderLength;
+ object[obj].DefinitionLength += object[obj].HeaderLength;
+
+ block->TotalByteLength += object[obj].TotalByteLength;
+ }
+
+ return block->TotalByteLength;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static uint32_t reg_perfcount_get_perf_data_block(uint32_t base_index,
+ TALLOC_CTX *mem_ctx,
+ struct PERF_DATA_BLOCK *block,
+ const char *object_ids,
+ bool bigendian_data)
+{
+ uint32_t buffer_size = 0;
+ char *fname;
+ TDB_CONTEXT *names;
+ int retval = 0;
+
+ fname = counters_directory(NAMES_DB);
+ if (fname == NULL) {
+ return 0;
+ }
+
+ names = tdb_open_log(fname, 0, TDB_DEFAULT, O_RDONLY, 0444);
+
+ if(names == NULL)
+ {
+ DEBUG(1, ("reg_perfcount_get_perf_data_block: unable to open [%s].\n", fname));
+ TALLOC_FREE(fname);
+ return 0;
+ }
+ TALLOC_FREE(fname);
+
+ if (!_reg_perfcount_init_data_block(block, mem_ctx, names, bigendian_data)) {
+ DEBUG(0, ("_reg_perfcount_init_data_block failed\n"));
+ tdb_close(names);
+ return 0;
+ }
+
+ retval = _reg_perfcount_assemble_global(block, mem_ctx, base_index, names);
+
+ buffer_size = _reg_perfcount_perf_data_block_fixup(block, mem_ctx);
+
+ tdb_close(names);
+
+ if (retval == -1) {
+ return 0;
+ }
+
+ return buffer_size + block->HeaderLength;
+}
+
+/*******************************************************************
+********************************************************************/
+
+static bool smb_io_system_time(const char *desc, prs_struct *ps, int depth, struct SYSTEMTIME *systime)
+{
+ if(!prs_uint16("year", ps, depth, &systime->year))
+ return False;
+ if(!prs_uint16("month", ps, depth, &systime->month))
+ return False;
+ if(!prs_uint16("dayofweek", ps, depth, &systime->dayofweek))
+ return False;
+ if(!prs_uint16("day", ps, depth, &systime->day))
+ return False;
+ if(!prs_uint16("hour", ps, depth, &systime->hour))
+ return False;
+ if(!prs_uint16("minute", ps, depth, &systime->minute))
+ return False;
+ if(!prs_uint16("second", ps, depth, &systime->second))
+ return False;
+ if(!prs_uint16("milliseconds", ps, depth, &systime->milliseconds))
+ return False;
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_marshall_perf_data_block(prs_struct *ps, struct PERF_DATA_BLOCK block, int depth)
+{
+ int i;
+ prs_debug(ps, depth, "", "_reg_perfcount_marshall_perf_data_block");
+ depth++;
+
+ if(!prs_align(ps))
+ return False;
+ for(i = 0; i < 4; i++)
+ {
+ if(!prs_uint16("Signature", ps, depth, &block.Signature[i]))
+ return False;
+ }
+ if(!prs_uint32("Little Endian", ps, depth, &block.LittleEndian))
+ return False;
+ if(!prs_uint32("Version", ps, depth, &block.Version))
+ return False;
+ if(!prs_uint32("Revision", ps, depth, &block.Revision))
+ return False;
+ if(!prs_uint32("TotalByteLength", ps, depth, &block.TotalByteLength))
+ return False;
+ if(!prs_uint32("HeaderLength", ps, depth, &block.HeaderLength))
+ return False;
+ if(!prs_uint32("NumObjectTypes", ps, depth, &block.NumObjectTypes))
+ return False;
+ if(!prs_uint32("DefaultObject", ps, depth, &block.DefaultObject))
+ return False;
+ if(!smb_io_system_time("SystemTime", ps, depth, &block.SystemTime))
+ return False;
+ if(!prs_uint32("Padding", ps, depth, &block.Padding))
+ return False;
+ if(!prs_align_uint64(ps))
+ return False;
+ if(!prs_uint64("PerfTime", ps, depth, &block.PerfTime))
+ return False;
+ if(!prs_uint64("PerfFreq", ps, depth, &block.PerfFreq))
+ return False;
+ if(!prs_uint64("PerfTime100nSec", ps, depth, &block.PerfTime100nSec))
+ return False;
+ if(!prs_uint32("SystemNameLength", ps, depth, &block.SystemNameLength))
+ return False;
+ if(!prs_uint32("SystemNameOffset", ps, depth, &block.SystemNameOffset))
+ return False;
+ /* hack to make sure we're 64-bit aligned at the end of this whole mess */
+ if(!prs_uint8s(False, "SystemName", ps, depth, block.data,
+ block.HeaderLength - block.SystemNameOffset))
+ return False;
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_marshall_perf_counters(prs_struct *ps,
+ struct PERF_OBJECT_TYPE object,
+ int depth)
+{
+ int cnt;
+ struct PERF_COUNTER_DEFINITION counter;
+
+ prs_debug(ps, depth, "", "_reg_perfcount_marshall_perf_counters");
+ depth++;
+
+ for(cnt = 0; cnt < object.NumCounters; cnt++)
+ {
+ counter = object.counters[cnt];
+
+ if(!prs_align(ps))
+ return False;
+ if(!prs_uint32("ByteLength", ps, depth, &counter.ByteLength))
+ return False;
+ if(!prs_uint32("CounterNameTitleIndex", ps, depth, &counter.CounterNameTitleIndex))
+ return False;
+ if(!prs_uint32("CounterNameTitlePointer", ps, depth, &counter.CounterNameTitlePointer))
+ return False;
+ if(!prs_uint32("CounterHelpTitleIndex", ps, depth, &counter.CounterHelpTitleIndex))
+ return False;
+ if(!prs_uint32("CounterHelpTitlePointer", ps, depth, &counter.CounterHelpTitlePointer))
+ return False;
+ if(!prs_uint32("DefaultScale", ps, depth, &counter.DefaultScale))
+ return False;
+ if(!prs_uint32("DetailLevel", ps, depth, &counter.DetailLevel))
+ return False;
+ if(!prs_uint32("CounterType", ps, depth, &counter.CounterType))
+ return False;
+ if(!prs_uint32("CounterSize", ps, depth, &counter.CounterSize))
+ return False;
+ if(!prs_uint32("CounterOffset", ps, depth, &counter.CounterOffset))
+ return False;
+ }
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_marshall_perf_counter_data(prs_struct *ps,
+ struct PERF_COUNTER_BLOCK counter_data,
+ int depth)
+{
+ prs_debug(ps, depth, "", "_reg_perfcount_marshall_perf_counter_data");
+ depth++;
+
+ if(!prs_align_uint64(ps))
+ return False;
+
+ if(!prs_uint32("ByteLength", ps, depth, &counter_data.ByteLength))
+ return False;
+ if(!prs_uint8s(False, "CounterData", ps, depth, counter_data.data, counter_data.ByteLength - sizeof(uint32_t)))
+ return False;
+ if(!prs_align_uint64(ps))
+ return False;
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_marshall_perf_instances(prs_struct *ps,
+ struct PERF_OBJECT_TYPE object,
+ int depth)
+{
+ struct PERF_INSTANCE_DEFINITION instance;
+ int inst;
+
+ prs_debug(ps, depth, "", "_reg_perfcount_marshall_perf_instances");
+ depth++;
+
+ for(inst = 0; inst < object.NumInstances; inst++)
+ {
+ instance = object.instances[inst];
+
+ if(!prs_align(ps))
+ return False;
+ if(!prs_uint32("ByteLength", ps, depth, &instance.ByteLength))
+ return False;
+ if(!prs_uint32("ParentObjectTitleIndex", ps, depth, &instance.ParentObjectTitleIndex))
+ return False;
+ if(!prs_uint32("ParentObjectTitlePointer", ps, depth, &instance.ParentObjectTitlePointer))
+ return False;
+ if(!prs_uint32("UniqueID", ps, depth, &instance.UniqueID))
+ return False;
+ if(!prs_uint32("NameOffset", ps, depth, &instance.NameOffset))
+ return False;
+ if(!prs_uint32("NameLength", ps, depth, &instance.NameLength))
+ return False;
+ if(!prs_uint8s(False, "InstanceName", ps, depth, instance.data,
+ instance.ByteLength - instance.NameOffset))
+ return False;
+ if(_reg_perfcount_marshall_perf_counter_data(ps, instance.counter_data, depth) == False)
+ return False;
+ }
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static bool _reg_perfcount_marshall_perf_objects(prs_struct *ps, struct PERF_DATA_BLOCK block, int depth)
+{
+ int obj;
+
+ struct PERF_OBJECT_TYPE object;
+
+ prs_debug(ps, depth, "", "_reg_perfcount_marshall_perf_objects");
+ depth++;
+
+ for(obj = 0; obj < block.NumObjectTypes; obj++)
+ {
+ object = block.objects[obj];
+
+ if(!prs_align(ps))
+ return False;
+
+ if(!prs_uint32("TotalByteLength", ps, depth, &object.TotalByteLength))
+ return False;
+ if(!prs_uint32("DefinitionLength", ps, depth, &object.DefinitionLength))
+ return False;
+ if(!prs_uint32("HeaderLength", ps, depth, &object.HeaderLength))
+ return False;
+ if(!prs_uint32("ObjectNameTitleIndex", ps, depth, &object.ObjectNameTitleIndex))
+ return False;
+ if(!prs_uint32("ObjectNameTitlePointer", ps, depth, &object.ObjectNameTitlePointer))
+ return False;
+ if(!prs_uint32("ObjectHelpTitleIndex", ps, depth, &object.ObjectHelpTitleIndex))
+ return False;
+ if(!prs_uint32("ObjectHelpTitlePointer", ps, depth, &object.ObjectHelpTitlePointer))
+ return False;
+ if(!prs_uint32("DetailLevel", ps, depth, &object.DetailLevel))
+ return False;
+ if(!prs_uint32("NumCounters", ps, depth, &object.NumCounters))
+ return False;
+ if(!prs_uint32("DefaultCounter", ps, depth, &object.DefaultCounter))
+ return False;
+ if(!prs_uint32("NumInstances", ps, depth, &object.NumInstances))
+ return False;
+ if(!prs_uint32("CodePage", ps, depth, &object.CodePage))
+ return False;
+ if(!prs_align_uint64(ps))
+ return False;
+ if(!prs_uint64("PerfTime", ps, depth, &object.PerfTime))
+ return False;
+ if(!prs_uint64("PerfFreq", ps, depth, &object.PerfFreq))
+ return False;
+
+ /* Now do the counters */
+ /* If no instances, encode counter_data */
+ /* If instances, encode instance plus counter data for each instance */
+ if(_reg_perfcount_marshall_perf_counters(ps, object, depth) == False)
+ return False;
+ if(object.NumInstances == PERF_NO_INSTANCES)
+ {
+ if(_reg_perfcount_marshall_perf_counter_data(ps, object.counter_data, depth) == False)
+ return False;
+ }
+ else
+ {
+ if(_reg_perfcount_marshall_perf_instances(ps, object, depth) == False)
+ return False;
+ }
+ }
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+WERROR reg_perfcount_get_hkpd(prs_struct *ps, uint32_t max_buf_size, uint32_t *outbuf_len, const char *object_ids)
+{
+ /*
+ * For a detailed description of the layout of this structure,
+ * see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/perfmon/base/performance_data_format.asp
+ *
+ * By 2006-11-23 this link did not work anymore, I found something
+ * promising under
+ * http://msdn2.microsoft.com/en-us/library/aa373105.aspx -- vl
+ */
+ struct PERF_DATA_BLOCK block;
+ uint32_t buffer_size, base_index;
+
+ buffer_size = 0;
+ base_index = reg_perfcount_get_base_index();
+ ZERO_STRUCT(block);
+
+ buffer_size = reg_perfcount_get_perf_data_block(base_index, ps->mem_ctx, &block, object_ids, ps->bigendian_data);
+
+ if(buffer_size < max_buf_size)
+ {
+ *outbuf_len = buffer_size;
+
+ if (!_reg_perfcount_marshall_perf_data_block(ps, block, 0))
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ if (!_reg_perfcount_marshall_perf_objects(ps, block, 0))
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ return WERR_OK;
+ }
+ else
+ {
+ *outbuf_len = max_buf_size;
+ if (!_reg_perfcount_marshall_perf_data_block(ps, block, 0))
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ return WERR_INSUFFICIENT_BUFFER;
+ }
+}
diff --git a/source3/registry/reg_perfcount.h b/source3/registry/reg_perfcount.h
new file mode 100644
index 0000000..eb5cb22
--- /dev/null
+++ b/source3/registry/reg_perfcount.h
@@ -0,0 +1,34 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer
+ *
+ * Copyright (C) Marcin Krzysztof Porwit 2005,
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_PERFCOUNT_H
+#define _REG_PERFCOUNT_H
+
+#include "reg_parse_prs.h"
+
+uint32_t reg_perfcount_get_base_index(void);
+uint32_t reg_perfcount_get_last_counter(uint32_t base_index);
+uint32_t reg_perfcount_get_last_help(uint32_t last_counter);
+uint32_t reg_perfcount_get_counter_help(uint32_t base_index, char **retbuf);
+uint32_t reg_perfcount_get_counter_names(uint32_t base_index, char **retbuf);
+WERROR reg_perfcount_get_hkpd(prs_struct *ps, uint32_t max_buf_size, uint32_t *outbuf_len, const char *object_ids);
+
+#endif /* _REG_PERFCOUNT_H */
diff --git a/source3/registry/reg_util_internal.c b/source3/registry/reg_util_internal.c
new file mode 100644
index 0000000..728ad0b
--- /dev/null
+++ b/source3/registry/reg_util_internal.c
@@ -0,0 +1,149 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer (utility functions)
+ * Copyright (C) Gerald Carter 2002-2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of registry frontend view functions. */
+
+#include "includes.h"
+#include "registry.h"
+#include "reg_util_internal.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+/***********************************************************************
+ Utility function for splitting the base path of a registry path off
+ by setting base and new_path to the apprapriate offsets within the
+ path.
+
+ WARNING!! Does modify the original string!
+ ***********************************************************************/
+
+bool reg_split_path(char *path, char **base, char **new_path)
+{
+ char *p;
+
+ *new_path = *base = NULL;
+
+ if (!path) {
+ return false;
+ }
+ *base = path;
+
+ p = strchr(path, '\\');
+
+ if ( p ) {
+ *p = '\0';
+ *new_path = p+1;
+ }
+
+ return true;
+}
+
+/***********************************************************************
+ Utility function for splitting the base path of a registry path off
+ by setting base and new_path to the appropriate offsets within the
+ path.
+
+ WARNING!! Does modify the original string!
+ ***********************************************************************/
+
+bool reg_split_key(char *path, char **base, char **key)
+{
+ char *p;
+
+ *key = *base = NULL;
+
+ if (!path) {
+ return false;
+ }
+
+ *base = path;
+
+ p = strrchr(path, '\\');
+
+ if (p) {
+ *p = '\0';
+ *key = p+1;
+ }
+
+ return true;
+}
+
+/**
+ * The full path to the registry key is used as database key.
+ * Leading and trailing '\' characters are stripped.
+ * Key string is also normalized to UPPER case.
+ */
+
+char *normalize_reg_path(TALLOC_CTX *ctx, const char *keyname )
+{
+ char *p;
+ char *nkeyname;
+
+ /* skip leading '\' chars */
+ while (*keyname == '\\') {
+ keyname++;
+ }
+
+ nkeyname = talloc_strdup(ctx, keyname);
+ if (nkeyname == NULL) {
+ return NULL;
+ }
+
+ /* strip trailing '\' chars */
+ p = strrchr(nkeyname, '\\');
+ while ((p != NULL) && (p[1] == '\0')) {
+ *p = '\0';
+ p = strrchr(nkeyname, '\\');
+ }
+
+ if (!strupper_m(nkeyname)) {
+ TALLOC_FREE(nkeyname);
+ return NULL;
+ }
+
+ return nkeyname;
+}
+
+/**********************************************************************
+ move to next non-delimter character
+*********************************************************************/
+
+char *reg_remaining_path(TALLOC_CTX *ctx, const char *key)
+{
+ char *new_path = NULL;
+ char *p = NULL;
+
+ if (!key || !*key) {
+ return NULL;
+ }
+
+ new_path = talloc_strdup(ctx, key);
+ if (!new_path) {
+ return NULL;
+ }
+ /* normalize_reg_path( new_path ); */
+ if (!(p = strchr(new_path, '\\')) ) {
+ p = new_path;
+ } else {
+ p++;
+ }
+
+ return p;
+}
diff --git a/source3/registry/reg_util_internal.h b/source3/registry/reg_util_internal.h
new file mode 100644
index 0000000..0cb370e
--- /dev/null
+++ b/source3/registry/reg_util_internal.h
@@ -0,0 +1,28 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Virtual Windows Registry Layer (utility functions)
+ * Copyright (C) Gerald Carter 2002-2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_UTIL_H
+#define _REG_UTIL_H
+
+bool reg_split_path(char *path, char **base, char **new_path);
+bool reg_split_key(char *path, char **base, char **key);
+char *normalize_reg_path(TALLOC_CTX *ctx, const char *keyname );
+char *reg_remaining_path(TALLOC_CTX *ctx, const char *key);
+
+#endif /* _REG_UTIL_H */
diff --git a/source3/registry/reg_util_token.c b/source3/registry/reg_util_token.c
new file mode 100644
index 0000000..d599b3a
--- /dev/null
+++ b/source3/registry/reg_util_token.c
@@ -0,0 +1,61 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Registry helper routines
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "reg_util_token.h"
+#include "../libcli/security/security.h"
+
+/*
+ * create a fake token just with enough rights to
+ * locally access the registry:
+ *
+ * - builtin administrators sid
+ * - disk operators privilege
+ */
+NTSTATUS registry_create_admin_token(TALLOC_CTX *mem_ctx,
+ struct security_token **ptoken)
+{
+ NTSTATUS status;
+ struct security_token *token = NULL;
+
+ if (ptoken == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ token = talloc_zero(mem_ctx, struct security_token);
+ if (token == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ security_token_set_privilege(token, SEC_PRIV_DISK_OPERATOR);
+
+ status = add_sid_to_array(token, &global_sid_Builtin_Administrators,
+ &token->sids, &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Error adding builtin administrators sid "
+ "to fake token.\n"));
+ goto done;
+ }
+
+ *ptoken = token;
+
+done:
+ return status;
+}
diff --git a/source3/registry/reg_util_token.h b/source3/registry/reg_util_token.h
new file mode 100644
index 0000000..558c787
--- /dev/null
+++ b/source3/registry/reg_util_token.h
@@ -0,0 +1,26 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Registry helper routines
+ * Copyright (C) Michael Adam 2007
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 3 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REG_UTIL_TOKEN_H
+#define _REG_UTIL_TOKEN_H
+
+NTSTATUS registry_create_admin_token(TALLOC_CTX *mem_ctx,
+ struct security_token **ptoken);
+
+#endif /* _REG_UTIL_TOKEN_H */
diff --git a/source3/registry/regfio.c b/source3/registry/regfio.c
new file mode 100644
index 0000000..e7bb8d1
--- /dev/null
+++ b/source3/registry/regfio.c
@@ -0,0 +1,1993 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Windows NT registry I/O library
+ * Copyright (c) Gerald (Jerry) Carter 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "regfio.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../libcli/security/security_descriptor.h"
+#include "../libcli/security/secdesc.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+/*******************************************************************
+ *
+ * TODO : Right now this code basically ignores classnames.
+ *
+ ******************************************************************/
+
+#if defined(PARANOID_MALLOC_CHECKER)
+#define PRS_ALLOC_MEM(ps, type, count) (type *)prs_alloc_mem_((ps),sizeof(type),(count))
+#else
+#define PRS_ALLOC_MEM(ps, type, count) (type *)prs_alloc_mem((ps),sizeof(type),(count))
+#endif
+
+/*******************************************************************
+ Reads or writes an NTTIME structure.
+********************************************************************/
+
+static bool smb_io_time(const char *desc, NTTIME *nttime, prs_struct *ps, int depth)
+{
+ uint32_t low, high;
+ if (nttime == NULL)
+ return False;
+
+ prs_debug(ps, depth, desc, "smb_io_time");
+ depth++;
+
+ if(!prs_align(ps))
+ return False;
+
+ if (MARSHALLING(ps)) {
+ low = *nttime & 0xFFFFFFFF;
+ high = *nttime >> 32;
+ }
+
+ if(!prs_uint32("low ", ps, depth, &low)) /* low part */
+ return False;
+ if(!prs_uint32("high", ps, depth, &high)) /* high part */
+ return False;
+
+ if (UNMARSHALLING(ps)) {
+ *nttime = (((uint64_t)high << 32) + low);
+ }
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static int write_block( REGF_FILE *file, prs_struct *ps, uint32_t offset )
+{
+ int bytes_written, returned;
+ char *buffer = prs_data_p( ps );
+ uint32_t buffer_size = prs_data_size( ps );
+ SMB_STRUCT_STAT sbuf;
+
+ if ( file->fd == -1 )
+ return -1;
+
+ /* check for end of file */
+
+ if (sys_fstat(file->fd, &sbuf, false)) {
+ DEBUG(0,("write_block: stat() failed! (%s)\n", strerror(errno)));
+ return -1;
+ }
+
+ if ( lseek( file->fd, offset, SEEK_SET ) == -1 ) {
+ DEBUG(0,("write_block: lseek() failed! (%s)\n", strerror(errno) ));
+ return -1;
+ }
+
+ bytes_written = returned = 0;
+ while ( bytes_written < buffer_size ) {
+ if ( (returned = write( file->fd, buffer+bytes_written, buffer_size-bytes_written )) == -1 ) {
+ DEBUG(0,("write_block: write() failed! (%s)\n", strerror(errno) ));
+ return False;
+ }
+
+ bytes_written += returned;
+ }
+
+ return bytes_written;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static int read_block( REGF_FILE *file, prs_struct *ps, uint32_t file_offset, uint32_t block_size )
+{
+ int bytes_read, returned;
+ char *buffer;
+ SMB_STRUCT_STAT sbuf;
+
+ /* check for end of file */
+
+ if (sys_fstat(file->fd, &sbuf, false)) {
+ DEBUG(0,("read_block: stat() failed! (%s)\n", strerror(errno)));
+ return -1;
+ }
+
+ if ( (size_t)file_offset >= sbuf.st_ex_size )
+ return -1;
+
+ /* if block_size == 0, we are parsing HBIN records and need
+ to read some of the header to get the block_size from there */
+
+ if ( block_size == 0 ) {
+ char hdr[0x20];
+
+ if ( lseek( file->fd, file_offset, SEEK_SET ) == -1 ) {
+ DEBUG(0,("read_block: lseek() failed! (%s)\n", strerror(errno) ));
+ return -1;
+ }
+
+ returned = read( file->fd, hdr, 0x20 );
+ if ( (returned == -1) || (returned < 0x20) ) {
+ DEBUG(0,("read_block: failed to read in HBIN header. Is the file corrupt?\n"));
+ return -1;
+ }
+
+ /* make sure this is an hbin header */
+
+ if ( strncmp( hdr, "hbin", HBIN_HDR_SIZE ) != 0 ) {
+ DEBUG(0,("read_block: invalid block header!\n"));
+ return -1;
+ }
+
+ block_size = IVAL( hdr, 0x08 );
+ }
+
+ DEBUG(10,("read_block: block_size == 0x%x\n", block_size ));
+
+ /* set the offset, initialize the buffer, and read the block from disk */
+
+ if ( lseek( file->fd, file_offset, SEEK_SET ) == -1 ) {
+ DEBUG(0,("read_block: lseek() failed! (%s)\n", strerror(errno) ));
+ return -1;
+ }
+
+ if (!prs_init( ps, block_size, file->mem_ctx, UNMARSHALL )) {
+ DEBUG(0,("read_block: prs_init() failed! (%s)\n", strerror(errno) ));
+ return -1;
+ }
+ buffer = prs_data_p( ps );
+ bytes_read = returned = 0;
+
+ while ( bytes_read < block_size ) {
+ if ( (returned = read( file->fd, buffer+bytes_read, block_size-bytes_read )) == -1 ) {
+ DEBUG(0,("read_block: read() failed (%s)\n", strerror(errno) ));
+ return False;
+ }
+ if ( (returned == 0) && (bytes_read < block_size) ) {
+ DEBUG(0,("read_block: not a valid registry file ?\n" ));
+ return False;
+ }
+
+ bytes_read += returned;
+ }
+
+ return bytes_read;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool write_hbin_block( REGF_FILE *file, REGF_HBIN *hbin )
+{
+ if ( !hbin->dirty )
+ return True;
+
+ /* write free space record if any is available */
+
+ if ( hbin->free_off != REGF_OFFSET_NONE ) {
+ uint32_t header = 0xffffffff;
+
+ if ( !prs_set_offset( &hbin->ps, hbin->free_off-sizeof(uint32_t) ) )
+ return False;
+ if ( !prs_uint32( "free_size", &hbin->ps, 0, &hbin->free_size ) )
+ return False;
+ if ( !prs_uint32( "free_header", &hbin->ps, 0, &header ) )
+ return False;
+ }
+
+ hbin->dirty = (write_block( file, &hbin->ps, hbin->file_off ) != -1);
+
+ return hbin->dirty;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool hbin_block_close( REGF_FILE *file, REGF_HBIN *hbin )
+{
+ REGF_HBIN *p;
+
+ /* remove the block from the open list and flush it to disk */
+
+ for ( p=file->block_list; p && p!=hbin; p=p->next )
+ ;
+
+ if ( p == hbin ) {
+ DLIST_REMOVE( file->block_list, hbin );
+ }
+ else
+ DEBUG(0,("hbin_block_close: block not in open list!\n"));
+
+ if ( !write_hbin_block( file, hbin ) )
+ return False;
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool prs_regf_block( const char *desc, prs_struct *ps, int depth, REGF_FILE *file )
+{
+ prs_debug(ps, depth, desc, "prs_regf_block");
+ depth++;
+
+ if ( !prs_uint8s( True, "header", ps, depth, (uint8_t *)file->header, sizeof( file->header )) )
+ return False;
+
+ /* yes, these values are always identical so store them only once */
+
+ if ( !prs_uint32( "unknown1", ps, depth, &file->unknown1 ))
+ return False;
+ if ( !prs_uint32( "unknown1 (again)", ps, depth, &file->unknown1 ))
+ return False;
+
+ /* get the modtime */
+
+ if ( !prs_set_offset( ps, 0x0c ) )
+ return False;
+ if ( !smb_io_time( "modtime", &file->mtime, ps, depth ) )
+ return False;
+
+ /* constants */
+
+ if ( !prs_uint32( "unknown2", ps, depth, &file->unknown2 ))
+ return False;
+ if ( !prs_uint32( "unknown3", ps, depth, &file->unknown3 ))
+ return False;
+ if ( !prs_uint32( "unknown4", ps, depth, &file->unknown4 ))
+ return False;
+ if ( !prs_uint32( "unknown5", ps, depth, &file->unknown5 ))
+ return False;
+
+ /* get file offsets */
+
+ if ( !prs_set_offset( ps, 0x24 ) )
+ return False;
+ if ( !prs_uint32( "data_offset", ps, depth, &file->data_offset ))
+ return False;
+ if ( !prs_uint32( "last_block", ps, depth, &file->last_block ))
+ return False;
+
+ /* one more constant */
+
+ if ( !prs_uint32( "unknown6", ps, depth, &file->unknown6 ))
+ return False;
+
+ /* get the checksum */
+
+ if ( !prs_set_offset( ps, 0x01fc ) )
+ return False;
+ if ( !prs_uint32( "checksum", ps, depth, &file->checksum ))
+ return False;
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool prs_hbin_block( const char *desc, prs_struct *ps, int depth, REGF_HBIN *hbin )
+{
+ uint32_t block_size2;
+
+ prs_debug(ps, depth, desc, "prs_hbin_block");
+ depth++;
+
+ if ( !prs_uint8s( True, "header", ps, depth, (uint8_t*)hbin->header, sizeof( hbin->header )) )
+ return False;
+
+ if ( !prs_uint32( "first_hbin_off", ps, depth, &hbin->first_hbin_off ))
+ return False;
+
+ /* The dosreg.cpp comments say that the block size is at 0x1c.
+ According to a WINXP NTUSER.dat file, this is wrong. The block_size
+ is at 0x08 */
+
+ if ( !prs_uint32( "block_size", ps, depth, &hbin->block_size ))
+ return False;
+
+ block_size2 = hbin->block_size;
+ prs_set_offset( ps, 0x1c );
+ if ( !prs_uint32( "block_size2", ps, depth, &block_size2 ))
+ return False;
+
+ if ( MARSHALLING(ps) )
+ hbin->dirty = True;
+
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool prs_nk_rec( const char *desc, prs_struct *ps, int depth, REGF_NK_REC *nk )
+{
+ uint16_t class_length, name_length;
+ uint32_t start;
+ uint32_t data_size, start_off, end_off;
+ uint32_t unknown_off = REGF_OFFSET_NONE;
+
+ nk->hbin_off = prs_offset( ps );
+ start = nk->hbin_off;
+
+ prs_debug(ps, depth, desc, "prs_nk_rec");
+ depth++;
+
+ /* back up and get the data_size */
+
+ if ( !prs_set_offset( ps, prs_offset(ps)-sizeof(uint32_t)) )
+ return False;
+ start_off = prs_offset( ps );
+ if ( !prs_uint32( "rec_size", ps, depth, &nk->rec_size ))
+ return False;
+
+ if ( !prs_uint8s( True, "header", ps, depth, (uint8_t *)nk->header, sizeof( nk->header )) )
+ return False;
+
+ if ( !prs_uint16( "key_type", ps, depth, &nk->key_type ))
+ return False;
+ if ( !smb_io_time( "mtime", &nk->mtime, ps, depth ))
+ return False;
+
+ if ( !prs_set_offset( ps, start+0x0010 ) )
+ return False;
+ if ( !prs_uint32( "parent_off", ps, depth, &nk->parent_off ))
+ return False;
+ if ( !prs_uint32( "num_subkeys", ps, depth, &nk->num_subkeys ))
+ return False;
+
+ if ( !prs_set_offset( ps, start+0x001c ) )
+ return False;
+ if ( !prs_uint32( "subkeys_off", ps, depth, &nk->subkeys_off ))
+ return False;
+ if ( !prs_uint32( "unknown_off", ps, depth, &unknown_off) )
+ return False;
+
+ if ( !prs_set_offset( ps, start+0x0024 ) )
+ return False;
+ if ( !prs_uint32( "num_values", ps, depth, &nk->num_values ))
+ return False;
+ if ( !prs_uint32( "values_off", ps, depth, &nk->values_off ))
+ return False;
+ if ( !prs_uint32( "sk_off", ps, depth, &nk->sk_off ))
+ return False;
+ if ( !prs_uint32( "classname_off", ps, depth, &nk->classname_off ))
+ return False;
+
+ if ( !prs_uint32( "max_bytes_subkeyname", ps, depth, &nk->max_bytes_subkeyname))
+ return False;
+ if ( !prs_uint32( "max_bytes_subkeyclassname", ps, depth, &nk->max_bytes_subkeyclassname))
+ return False;
+ if ( !prs_uint32( "max_bytes_valuename", ps, depth, &nk->max_bytes_valuename))
+ return False;
+ if ( !prs_uint32( "max_bytes_value", ps, depth, &nk->max_bytes_value))
+ return False;
+ if ( !prs_uint32( "unknown index", ps, depth, &nk->unk_index))
+ return False;
+
+ name_length = nk->keyname ? strlen(nk->keyname) : 0 ;
+ class_length = nk->classname ? strlen(nk->classname) : 0 ;
+ if ( !prs_uint16( "name_length", ps, depth, &name_length ))
+ return False;
+ if ( !prs_uint16( "class_length", ps, depth, &class_length ))
+ return False;
+
+ if ( class_length ) {
+ ;;
+ }
+
+ if ( name_length ) {
+ if ( UNMARSHALLING(ps) ) {
+ if ( !(nk->keyname = PRS_ALLOC_MEM( ps, char, name_length+1 )) )
+ return False;
+ }
+
+ if ( !prs_uint8s( True, "name", ps, depth, (uint8_t *)nk->keyname, name_length) )
+ return False;
+
+ if ( UNMARSHALLING(ps) )
+ nk->keyname[name_length] = '\0';
+ }
+
+ end_off = prs_offset( ps );
+
+ /* data_size must be divisible by 8 and large enough to hold the original record */
+
+ data_size = ((start_off - end_off) & 0xfffffff8 );
+ if ( data_size > nk->rec_size )
+ DEBUG(10,("Encountered reused record (0x%x < 0x%x)\n", data_size, nk->rec_size));
+
+ if ( MARSHALLING(ps) )
+ nk->hbin->dirty = True;
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static uint32_t regf_block_checksum( prs_struct *ps )
+{
+ char *buffer = prs_data_p( ps );
+ uint32_t checksum, x;
+ int i;
+
+ /* XOR of all bytes 0x0000 - 0x01FB */
+
+ checksum = x = 0;
+
+ for ( i=0; i<0x01FB; i+=4 ) {
+ x = IVAL(buffer, i );
+ checksum ^= x;
+ }
+
+ return checksum;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool read_regf_block( REGF_FILE *file )
+{
+ prs_struct ps;
+ uint32_t checksum;
+
+ /* grab the first block from the file */
+
+ if ( read_block( file, &ps, 0, REGF_BLOCKSIZE ) == -1 )
+ return False;
+
+ /* parse the block and verify the checksum */
+
+ if ( !prs_regf_block( "regf_header", &ps, 0, file ) )
+ return False;
+
+ checksum = regf_block_checksum( &ps );
+
+ prs_mem_free( &ps );
+
+ if ( file->checksum != checksum && !file->ignore_checksums) {
+ DEBUG(0,("read_regf_block: invalid checksum\n" ));
+ return False;
+ }
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static REGF_HBIN* read_hbin_block( REGF_FILE *file, off_t offset )
+{
+ REGF_HBIN *hbin;
+ uint32_t record_size, curr_off, block_size, header;
+
+ if ( !(hbin = talloc_zero(file->mem_ctx, REGF_HBIN)) )
+ return NULL;
+ hbin->file_off = offset;
+ hbin->free_off = -1;
+
+ if ( read_block( file, &hbin->ps, offset, 0 ) == -1 )
+ return NULL;
+
+ if ( !prs_hbin_block( "hbin", &hbin->ps, 0, hbin ) )
+ return NULL;
+
+ /* this should be the same thing as hbin->block_size but just in case */
+
+ block_size = prs_data_size( &hbin->ps );
+
+ /* Find the available free space offset. Always at the end,
+ so walk the record list and stop when you get to the end.
+ The end is defined by a record header of 0xffffffff. The
+ previous 4 bytes contains the amount of free space remaining
+ in the hbin block. */
+
+ /* remember that the record_size is in the 4 bytes preceding the record itself */
+
+ if ( !prs_set_offset( &hbin->ps, file->data_offset+HBIN_HDR_SIZE-sizeof(uint32_t) ) )
+ return NULL;
+
+ record_size = 0;
+ header = 0;
+ curr_off = prs_offset( &hbin->ps );
+ while ( header != 0xffffffff ) {
+ /* not done yet so reset the current offset to the
+ next record_size field */
+
+ curr_off = curr_off+record_size;
+
+ /* for some reason the record_size of the last record in
+ an hbin block can extend past the end of the block
+ even though the record fits within the remaining
+ space....aaarrrgggghhhhhh */
+
+ if ( curr_off >= block_size ) {
+ record_size = -1;
+ curr_off = -1;
+ break;
+ }
+
+ if ( !prs_set_offset( &hbin->ps, curr_off) )
+ return NULL;
+
+ if ( !prs_uint32( "rec_size", &hbin->ps, 0, &record_size ) )
+ return NULL;
+ if ( !prs_uint32( "header", &hbin->ps, 0, &header ) )
+ return NULL;
+
+ if (record_size == 0)
+ return NULL;
+
+ if ( record_size & 0x80000000 ) {
+ /* absolute_value(record_size) */
+ record_size = (record_size ^ 0xffffffff) + 1;
+ }
+ }
+
+ /* save the free space offset */
+
+ if ( header == 0xffffffff ) {
+
+ /* account for the fact that the curr_off is 4 bytes behind the actual
+ record header */
+
+ hbin->free_off = curr_off + sizeof(uint32_t);
+ hbin->free_size = record_size;
+ }
+
+ DEBUG(10,("read_hbin_block: free space offset == 0x%x\n", hbin->free_off));
+
+ if ( !prs_set_offset( &hbin->ps, file->data_offset+HBIN_HDR_SIZE ) )
+ return NULL;
+
+ return hbin;
+}
+
+/*******************************************************************
+ Input a random offset and receive the corresponding HBIN
+ block for it
+*******************************************************************/
+
+static bool hbin_contains_offset( REGF_HBIN *hbin, uint32_t offset )
+{
+ if ( !hbin )
+ return False;
+
+ if ( (offset > hbin->first_hbin_off) && (offset < (hbin->first_hbin_off+hbin->block_size)) )
+ return True;
+
+ return False;
+}
+
+/*******************************************************************
+ Input a random offset and receive the corresponding HBIN
+ block for it
+*******************************************************************/
+
+static REGF_HBIN* lookup_hbin_block( REGF_FILE *file, uint32_t offset )
+{
+ REGF_HBIN *hbin = NULL;
+ uint32_t block_off;
+
+ /* start with the open list */
+
+ for ( hbin=file->block_list; hbin; hbin=hbin->next ) {
+ DEBUG(10,("lookup_hbin_block: address = 0x%x [0x%lx]\n", hbin->file_off, (unsigned long)hbin ));
+ if ( hbin_contains_offset( hbin, offset ) )
+ return hbin;
+ }
+
+ if ( !hbin ) {
+ /* start at the beginning */
+
+ block_off = REGF_BLOCKSIZE;
+ do {
+ /* cleanup before the next round */
+ if ( hbin )
+ prs_mem_free( &hbin->ps );
+
+ hbin = read_hbin_block( file, block_off );
+
+ if ( hbin )
+ block_off = hbin->file_off + hbin->block_size;
+
+ } while ( hbin && !hbin_contains_offset( hbin, offset ) );
+ }
+
+ if ( hbin )
+ DLIST_ADD( file->block_list, hbin );
+
+ return hbin;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool prs_hash_rec( const char *desc, prs_struct *ps, int depth, REGF_HASH_REC *hash )
+{
+ prs_debug(ps, depth, desc, "prs_hash_rec");
+ depth++;
+
+ if ( !prs_uint32( "nk_off", ps, depth, &hash->nk_off ))
+ return False;
+ if ( !prs_uint8s( True, "keycheck", ps, depth, hash->keycheck, sizeof( hash->keycheck )) )
+ return False;
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool hbin_prs_lf_records( const char *desc, REGF_HBIN *hbin, int depth, REGF_NK_REC *nk )
+{
+ int i;
+ REGF_LF_REC *lf = &nk->subkeys;
+ uint32_t data_size, start_off, end_off;
+
+ prs_debug(&hbin->ps, depth, desc, "prs_lf_records");
+ depth++;
+
+ /* check if we have anything to do first */
+
+ if ( nk->num_subkeys == 0 )
+ return True;
+
+ /* move to the LF record */
+
+ if ( !prs_set_offset( &hbin->ps, nk->subkeys_off + HBIN_HDR_SIZE - hbin->first_hbin_off ) )
+ return False;
+
+ /* backup and get the data_size */
+
+ if ( !prs_set_offset( &hbin->ps, prs_offset(&hbin->ps)-sizeof(uint32_t)) )
+ return False;
+ start_off = prs_offset( &hbin->ps );
+ if ( !prs_uint32( "rec_size", &hbin->ps, depth, &lf->rec_size ))
+ return False;
+
+ if ( !prs_uint8s( True, "header", &hbin->ps, depth, (uint8_t *)lf->header, sizeof( lf->header )) )
+ return False;
+
+ if ( !prs_uint16( "num_keys", &hbin->ps, depth, &lf->num_keys))
+ return False;
+
+ if ( UNMARSHALLING(&hbin->ps) ) {
+ if (lf->num_keys) {
+ if ( !(lf->hashes = PRS_ALLOC_MEM( &hbin->ps, REGF_HASH_REC, lf->num_keys )) )
+ return False;
+ } else {
+ lf->hashes = NULL;
+ }
+ }
+
+ for ( i=0; i<lf->num_keys; i++ ) {
+ if ( !prs_hash_rec( "hash_rec", &hbin->ps, depth, &lf->hashes[i] ) )
+ return False;
+ }
+
+ end_off = prs_offset( &hbin->ps );
+
+ /* data_size must be divisible by 8 and large enough to hold the original record */
+
+ data_size = ((start_off - end_off) & 0xfffffff8 );
+ if ( data_size > lf->rec_size )
+ DEBUG(10,("Encountered reused record (0x%x < 0x%x)\n", data_size, lf->rec_size));
+
+ if ( MARSHALLING(&hbin->ps) )
+ hbin->dirty = True;
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool hbin_prs_sk_rec( const char *desc, REGF_HBIN *hbin, int depth, REGF_SK_REC *sk )
+{
+ prs_struct *ps = &hbin->ps;
+ uint16_t tag = 0xFFFF;
+ uint32_t data_size, start_off, end_off;
+
+
+ prs_debug(ps, depth, desc, "hbin_prs_sk_rec");
+ depth++;
+
+ if ( !prs_set_offset( &hbin->ps, sk->sk_off + HBIN_HDR_SIZE - hbin->first_hbin_off ) )
+ return False;
+
+ /* backup and get the data_size */
+
+ if ( !prs_set_offset( &hbin->ps, prs_offset(&hbin->ps)-sizeof(uint32_t)) )
+ return False;
+ start_off = prs_offset( &hbin->ps );
+ if ( !prs_uint32( "rec_size", &hbin->ps, depth, &sk->rec_size ))
+ return False;
+
+ if ( !prs_uint8s( True, "header", ps, depth, (uint8_t *)sk->header, sizeof( sk->header )) )
+ return False;
+ if ( !prs_uint16( "tag", ps, depth, &tag))
+ return False;
+
+ if ( !prs_uint32( "prev_sk_off", ps, depth, &sk->prev_sk_off))
+ return False;
+ if ( !prs_uint32( "next_sk_off", ps, depth, &sk->next_sk_off))
+ return False;
+ if ( !prs_uint32( "ref_count", ps, depth, &sk->ref_count))
+ return False;
+ if ( !prs_uint32( "size", ps, depth, &sk->size))
+ return False;
+
+ {
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx = prs_get_mem_context(&hbin->ps);
+ DATA_BLOB blob;
+
+ if (MARSHALLING(&hbin->ps)) {
+ status = marshall_sec_desc(mem_ctx,
+ sk->sec_desc,
+ &blob.data, &blob.length);
+ if (!NT_STATUS_IS_OK(status))
+ return False;
+ if (!prs_copy_data_in(&hbin->ps, (const char *)blob.data, blob.length))
+ return False;
+ } else {
+ blob = data_blob_const(
+ prs_data_p(&hbin->ps) + prs_offset(&hbin->ps),
+ prs_data_size(&hbin->ps) - prs_offset(&hbin->ps)
+ );
+ status = unmarshall_sec_desc(mem_ctx,
+ blob.data, blob.length,
+ &sk->sec_desc);
+ if (!NT_STATUS_IS_OK(status))
+ return False;
+ prs_set_offset(&hbin->ps, blob.length);
+ }
+ }
+
+ end_off = prs_offset( &hbin->ps );
+
+ /* data_size must be divisible by 8 and large enough to hold the original record */
+
+ data_size = ((start_off - end_off) & 0xfffffff8 );
+ if ( data_size > sk->rec_size )
+ DEBUG(10,("Encountered reused record (0x%x < 0x%x)\n", data_size, sk->rec_size));
+
+ if ( MARSHALLING(&hbin->ps) )
+ hbin->dirty = True;
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool hbin_prs_vk_rec( const char *desc, REGF_HBIN *hbin, int depth, REGF_VK_REC *vk, REGF_FILE *file )
+{
+ uint32_t offset;
+ uint16_t name_length;
+ prs_struct *ps = &hbin->ps;
+ uint32_t data_size, start_off, end_off;
+
+ prs_debug(ps, depth, desc, "prs_vk_rec");
+ depth++;
+
+ /* backup and get the data_size */
+
+ if ( !prs_set_offset( &hbin->ps, prs_offset(&hbin->ps)-sizeof(uint32_t)) )
+ return False;
+ start_off = prs_offset( &hbin->ps );
+ if ( !prs_uint32( "rec_size", &hbin->ps, depth, &vk->rec_size ))
+ return False;
+
+ if ( !prs_uint8s( True, "header", ps, depth, (uint8_t *)vk->header, sizeof( vk->header )) )
+ return False;
+
+ if ( MARSHALLING(&hbin->ps) )
+ name_length = strlen(vk->valuename);
+
+ if ( !prs_uint16( "name_length", ps, depth, &name_length ))
+ return False;
+ if ( !prs_uint32( "data_size", ps, depth, &vk->data_size ))
+ return False;
+ if ( !prs_uint32( "data_off", ps, depth, &vk->data_off ))
+ return False;
+ if ( !prs_uint32( "type", ps, depth, &vk->type))
+ return False;
+ if ( !prs_uint16( "flag", ps, depth, &vk->flag))
+ return False;
+
+ offset = prs_offset( ps );
+ offset += 2; /* skip 2 bytes */
+ prs_set_offset( ps, offset );
+
+ /* get the name */
+
+ if ( vk->flag&VK_FLAG_NAME_PRESENT ) {
+
+ if ( UNMARSHALLING(&hbin->ps) ) {
+ if ( !(vk->valuename = PRS_ALLOC_MEM( ps, char, name_length+1 )))
+ return False;
+ }
+ if ( !prs_uint8s( True, "name", ps, depth, (uint8_t *)vk->valuename, name_length ) )
+ return False;
+ }
+
+ end_off = prs_offset( &hbin->ps );
+
+ /* get the data if necessary */
+
+ if ( vk->data_size != 0 ) {
+ bool charmode = False;
+
+ if ( (vk->type == REG_SZ) || (vk->type == REG_MULTI_SZ) )
+ charmode = True;
+
+ /* the data is stored in the offset if the size <= 4 */
+
+ if ( !(vk->data_size & VK_DATA_IN_OFFSET) ) {
+ REGF_HBIN *hblock = hbin;
+ uint32_t data_rec_size;
+
+ if ( UNMARSHALLING(&hbin->ps) ) {
+ if ( !(vk->data = PRS_ALLOC_MEM( ps, uint8_t, vk->data_size) ) )
+ return False;
+ }
+
+ /* this data can be in another hbin */
+ if ( !hbin_contains_offset( hbin, vk->data_off ) ) {
+ if ( !(hblock = lookup_hbin_block( file, vk->data_off )) )
+ return False;
+ }
+ if ( !(prs_set_offset( &hblock->ps, (vk->data_off+HBIN_HDR_SIZE-hblock->first_hbin_off)-sizeof(uint32_t) )) )
+ return False;
+
+ if ( MARSHALLING(&hblock->ps) ) {
+ data_rec_size = ( (vk->data_size+sizeof(uint32_t)) & 0xfffffff8 ) + 8;
+ data_rec_size = ( data_rec_size - 1 ) ^ 0xFFFFFFFF;
+ }
+ if ( !prs_uint32( "data_rec_size", &hblock->ps, depth, &data_rec_size ))
+ return False;
+ if ( !prs_uint8s( charmode, "data", &hblock->ps, depth, vk->data, vk->data_size) )
+ return False;
+
+ if ( MARSHALLING(&hblock->ps) )
+ hblock->dirty = True;
+ }
+ else {
+ if ( !(vk->data = PRS_ALLOC_MEM( ps, uint8_t, 4 ) ) )
+ return False;
+ SIVAL( vk->data, 0, vk->data_off );
+ }
+
+ }
+
+ /* data_size must be divisible by 8 and large enough to hold the original record */
+
+ data_size = ((start_off - end_off ) & 0xfffffff8 );
+ if ( data_size != vk->rec_size )
+ DEBUG(10,("prs_vk_rec: data_size check failed (0x%x < 0x%x)\n", data_size, vk->rec_size));
+
+ if ( MARSHALLING(&hbin->ps) )
+ hbin->dirty = True;
+
+ return True;
+}
+
+/*******************************************************************
+ read a VK record which is contained in the HBIN block stored
+ in the prs_struct *ps.
+*******************************************************************/
+
+static bool hbin_prs_vk_records( const char *desc, REGF_HBIN *hbin, int depth, REGF_NK_REC *nk, REGF_FILE *file )
+{
+ int i;
+ uint32_t record_size;
+
+ prs_debug(&hbin->ps, depth, desc, "prs_vk_records");
+ depth++;
+
+ /* check if we have anything to do first */
+
+ if ( nk->num_values == 0 )
+ return True;
+
+ if ( UNMARSHALLING(&hbin->ps) ) {
+ if ( !(nk->values = PRS_ALLOC_MEM( &hbin->ps, REGF_VK_REC, nk->num_values ) ) )
+ return False;
+ }
+
+ /* convert the offset to something relative to this HBIN block */
+
+ if ( !prs_set_offset( &hbin->ps, nk->values_off+HBIN_HDR_SIZE-hbin->first_hbin_off-sizeof(uint32_t)) )
+ return False;
+
+ if ( MARSHALLING( &hbin->ps) ) {
+ record_size = ( ( nk->num_values * sizeof(uint32_t) ) & 0xfffffff8 ) + 8;
+ record_size = (record_size - 1) ^ 0xFFFFFFFF;
+ }
+
+ if ( !prs_uint32( "record_size", &hbin->ps, depth, &record_size ) )
+ return False;
+
+ for ( i=0; i<nk->num_values; i++ ) {
+ if ( !prs_uint32( "vk_off", &hbin->ps, depth, &nk->values[i].rec_off ) )
+ return False;
+ }
+
+ for ( i=0; i<nk->num_values; i++ ) {
+ REGF_HBIN *sub_hbin = hbin;
+ uint32_t new_offset;
+
+ if ( !hbin_contains_offset( hbin, nk->values[i].rec_off ) ) {
+ sub_hbin = lookup_hbin_block( file, nk->values[i].rec_off );
+ if ( !sub_hbin ) {
+ DEBUG(0,("hbin_prs_vk_records: Failed to find HBIN block containing offset [0x%x]\n",
+ nk->values[i].hbin_off));
+ return False;
+ }
+ }
+
+ new_offset = nk->values[i].rec_off + HBIN_HDR_SIZE - sub_hbin->first_hbin_off;
+ if ( !prs_set_offset( &sub_hbin->ps, new_offset ) )
+ return False;
+ if ( !hbin_prs_vk_rec( "vk_rec", sub_hbin, depth, &nk->values[i], file ) )
+ return False;
+ }
+
+ if ( MARSHALLING(&hbin->ps) )
+ hbin->dirty = True;
+
+
+ return True;
+}
+
+
+/*******************************************************************
+*******************************************************************/
+
+static REGF_SK_REC* find_sk_record_by_offset( REGF_FILE *file, uint32_t offset )
+{
+ REGF_SK_REC *p_sk;
+
+ for ( p_sk=file->sec_desc_list; p_sk; p_sk=p_sk->next ) {
+ if ( p_sk->sk_off == offset )
+ return p_sk;
+ }
+
+ return NULL;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static REGF_SK_REC* find_sk_record_by_sec_desc( REGF_FILE *file, struct security_descriptor *sd )
+{
+ REGF_SK_REC *p;
+
+ for ( p=file->sec_desc_list; p; p=p->next ) {
+ if ( security_descriptor_equal( p->sec_desc, sd ) )
+ return p;
+ }
+
+ /* failure */
+
+ return NULL;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool hbin_prs_key( REGF_FILE *file, REGF_HBIN *hbin, REGF_NK_REC *nk )
+{
+ int depth = 0;
+ REGF_HBIN *sub_hbin;
+
+ prs_debug(&hbin->ps, depth, "", "prs_key");
+ depth++;
+
+ /* get the initial nk record */
+
+ if ( !prs_nk_rec( "nk_rec", &hbin->ps, depth, nk ))
+ return False;
+
+ /* fill in values */
+
+ if ( nk->num_values && (nk->values_off!=REGF_OFFSET_NONE) ) {
+ sub_hbin = hbin;
+ if ( !hbin_contains_offset( hbin, nk->values_off ) ) {
+ sub_hbin = lookup_hbin_block( file, nk->values_off );
+ if ( !sub_hbin ) {
+ DEBUG(0,("hbin_prs_key: Failed to find HBIN block containing value_list_offset [0x%x]\n",
+ nk->values_off));
+ return False;
+ }
+ }
+
+ if ( !hbin_prs_vk_records( "vk_rec", sub_hbin, depth, nk, file ))
+ return False;
+ }
+
+ /* now get subkeys */
+
+ if ( nk->num_subkeys && (nk->subkeys_off!=REGF_OFFSET_NONE) ) {
+ sub_hbin = hbin;
+ if ( !hbin_contains_offset( hbin, nk->subkeys_off ) ) {
+ sub_hbin = lookup_hbin_block( file, nk->subkeys_off );
+ if ( !sub_hbin ) {
+ DEBUG(0,("hbin_prs_key: Failed to find HBIN block containing subkey_offset [0x%x]\n",
+ nk->subkeys_off));
+ return False;
+ }
+ }
+
+ if ( !hbin_prs_lf_records( "lf_rec", sub_hbin, depth, nk ))
+ return False;
+ }
+
+ /* get the to the security descriptor. First look if we have already parsed it */
+
+ if ( (nk->sk_off!=REGF_OFFSET_NONE) && !( nk->sec_desc = find_sk_record_by_offset( file, nk->sk_off )) ) {
+
+ sub_hbin = hbin;
+ if ( !hbin_contains_offset( hbin, nk->sk_off ) ) {
+ sub_hbin = lookup_hbin_block( file, nk->sk_off );
+ if ( !sub_hbin ) {
+ DEBUG(0,("hbin_prs_key: Failed to find HBIN block containing sk_off [0x%x]\n",
+ nk->sk_off));
+ return False;
+ }
+ }
+
+ if ( !(nk->sec_desc = talloc_zero( file->mem_ctx, REGF_SK_REC )) )
+ return False;
+ nk->sec_desc->sk_off = nk->sk_off;
+ if ( !hbin_prs_sk_rec( "sk_rec", sub_hbin, depth, nk->sec_desc ))
+ return False;
+
+ /* add to the list of security descriptors (ref_count has been read from the files) */
+
+ nk->sec_desc->sk_off = nk->sk_off;
+ DLIST_ADD( file->sec_desc_list, nk->sec_desc );
+ }
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool next_record( REGF_HBIN *hbin, const char *hdr, bool *eob )
+{
+ uint8_t header[REC_HDR_SIZE];
+ uint32_t record_size;
+ uint32_t curr_off, block_size;
+ bool found = False;
+ prs_struct *ps = &hbin->ps;
+
+ curr_off = prs_offset( ps );
+ if ( curr_off == 0 )
+ prs_set_offset( ps, HBIN_HEADER_REC_SIZE );
+
+ /* assume that the current offset is at the record header
+ and we need to backup to read the record size */
+
+ curr_off -= sizeof(uint32_t);
+
+ block_size = prs_data_size( ps );
+ record_size = 0;
+ memset( header, 0x0, sizeof(uint8_t)*REC_HDR_SIZE );
+ while ( !found ) {
+
+ curr_off = curr_off+record_size;
+ if ( curr_off >= block_size )
+ break;
+
+ if ( !prs_set_offset( &hbin->ps, curr_off) )
+ return False;
+
+ if ( !prs_uint32( "record_size", ps, 0, &record_size ) )
+ return False;
+ if ( !prs_uint8s( True, "header", ps, 0, header, REC_HDR_SIZE ) )
+ return False;
+
+ if (record_size & 0x80000000) {
+ /* absolute_value(record_size) */
+ record_size = (record_size ^ 0xffffffff) + 1;
+ }
+
+ if (record_size < sizeof(REC_HDR_SIZE)) {
+ return false;
+ }
+
+ if (memcmp(header, hdr, REC_HDR_SIZE) == 0) {
+ found = True;
+ curr_off += sizeof(uint32_t);
+ }
+ }
+
+ /* mark prs_struct as done ( at end ) if no more SK records */
+ /* mark end-of-block as True */
+
+ if ( !found ) {
+ prs_set_offset( &hbin->ps, prs_data_size(&hbin->ps) );
+ *eob = True;
+ return False;
+ }
+
+ if ( !prs_set_offset( ps, curr_off ) )
+ return False;
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool next_nk_record( REGF_FILE *file, REGF_HBIN *hbin, REGF_NK_REC *nk, bool *eob )
+{
+ if ( next_record( hbin, "nk", eob ) && hbin_prs_key( file, hbin, nk ) )
+ return True;
+
+ return False;
+}
+
+/*******************************************************************
+ Initialize the newly created REGF_BLOCK in *file and write the
+ block header to disk
+*******************************************************************/
+
+static bool init_regf_block( REGF_FILE *file )
+{
+ prs_struct ps;
+ bool result = True;
+
+ if ( !prs_init( &ps, REGF_BLOCKSIZE, file->mem_ctx, MARSHALL ) )
+ return False;
+
+ memcpy( file->header, "regf", REGF_HDR_SIZE );
+ file->data_offset = 0x20;
+ file->last_block = 0x1000;
+
+ /* set mod time */
+
+ unix_to_nt_time( &file->mtime, time(NULL) );
+
+ /* hard coded values...no idea what these are ... maybe in time */
+
+ file->unknown1 = 0x2;
+ file->unknown2 = 0x1;
+ file->unknown3 = 0x3;
+ file->unknown4 = 0x0;
+ file->unknown5 = 0x1;
+ file->unknown6 = 0x1;
+
+ /* write header to the buffer */
+
+ if ( !prs_regf_block( "regf_header", &ps, 0, file ) ) {
+ result = False;
+ goto out;
+ }
+
+ /* calculate the checksum, re-marshall data (to include the checksum)
+ and write to disk */
+
+ file->checksum = regf_block_checksum( &ps );
+ prs_set_offset( &ps, 0 );
+ if ( !prs_regf_block( "regf_header", &ps, 0, file ) ) {
+ result = False;
+ goto out;
+ }
+
+ if ( write_block( file, &ps, 0 ) == -1 ) {
+ DEBUG(0,("init_regf_block: Failed to initialize registry header block!\n"));
+ result = False;
+ goto out;
+ }
+
+out:
+ prs_mem_free( &ps );
+
+ return result;
+}
+/*******************************************************************
+ Open the registry file and then read in the REGF block to get the
+ first hbin offset.
+*******************************************************************/
+
+ REGF_FILE* regfio_open( const char *filename, int flags, int mode )
+{
+ REGF_FILE *rb;
+
+ if ( !(rb = SMB_MALLOC_P(REGF_FILE)) ) {
+ DEBUG(0,("ERROR allocating memory\n"));
+ return NULL;
+ }
+ ZERO_STRUCTP( rb );
+ rb->fd = -1;
+ rb->ignore_checksums = false;
+
+ if ( !(rb->mem_ctx = talloc_init( "regfio_open" )) ) {
+ regfio_close( rb );
+ return NULL;
+ }
+
+ rb->open_flags = flags;
+
+ /* open and existing file */
+
+ if ( (rb->fd = open(filename, flags, mode)) == -1 ) {
+ DEBUG(0,("regfio_open: failure to open %s (%s)\n", filename, strerror(errno)));
+ regfio_close( rb );
+ return NULL;
+ }
+
+ /* check if we are creating a new file or overwriting an existing one */
+
+ if ( flags & (O_CREAT|O_TRUNC) ) {
+ if ( !init_regf_block( rb ) ) {
+ DEBUG(0,("regfio_open: Failed to read initial REGF block\n"));
+ regfio_close( rb );
+ return NULL;
+ }
+
+ /* success */
+ return rb;
+ }
+
+ /* read in an existing file */
+
+ if ( !read_regf_block( rb ) ) {
+ DEBUG(0,("regfio_open: Failed to read initial REGF block\n"));
+ regfio_close( rb );
+ return NULL;
+ }
+
+ /* success */
+
+ return rb;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static void regfio_mem_free( REGF_FILE *file )
+{
+ /* free any talloc()'d memory */
+
+ if ( file && file->mem_ctx )
+ talloc_destroy( file->mem_ctx );
+}
+
+/*******************************************************************
+*******************************************************************/
+
+ int regfio_close( REGF_FILE *file )
+{
+ int fd;
+
+ /* cleanup for a file opened for write */
+
+ if ((file->fd != -1) && (file->open_flags & (O_WRONLY|O_RDWR))) {
+ prs_struct ps;
+ REGF_SK_REC *sk;
+
+ /* write of sd list */
+
+ for ( sk=file->sec_desc_list; sk; sk=sk->next ) {
+ hbin_prs_sk_rec( "sk_rec", sk->hbin, 0, sk );
+ }
+
+ /* flush any dirty blocks */
+
+ while ( file->block_list ) {
+ hbin_block_close( file, file->block_list );
+ }
+
+ ZERO_STRUCT( ps );
+
+ unix_to_nt_time( &file->mtime, time(NULL) );
+
+ if ( read_block( file, &ps, 0, REGF_BLOCKSIZE ) != -1 ) {
+ /* now use for writing */
+ prs_switch_type( &ps, MARSHALL );
+
+ /* stream the block once, generate the checksum,
+ and stream it again */
+ prs_set_offset( &ps, 0 );
+ prs_regf_block( "regf_blocK", &ps, 0, file );
+ file->checksum = regf_block_checksum( &ps );
+ prs_set_offset( &ps, 0 );
+ prs_regf_block( "regf_blocK", &ps, 0, file );
+
+ /* now we are ready to write it to disk */
+ if ( write_block( file, &ps, 0 ) == -1 )
+ DEBUG(0,("regfio_close: failed to update the regf header block!\n"));
+ }
+
+ prs_mem_free( &ps );
+ }
+
+ regfio_mem_free( file );
+
+ /* nothing tdo do if there is no open file */
+
+ if (file->fd == -1)
+ return 0;
+
+ fd = file->fd;
+ file->fd = -1;
+ SAFE_FREE( file );
+
+ return close( fd );
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static void regfio_flush( REGF_FILE *file )
+{
+ REGF_HBIN *hbin;
+
+ for ( hbin=file->block_list; hbin; hbin=hbin->next ) {
+ write_hbin_block( file, hbin );
+ }
+}
+
+/*******************************************************************
+ There should be only *one* root key in the registry file based
+ on my experience. --jerry
+*******************************************************************/
+
+REGF_NK_REC* regfio_rootkey( REGF_FILE *file )
+{
+ REGF_NK_REC *nk;
+ REGF_HBIN *hbin;
+ uint32_t offset = REGF_BLOCKSIZE;
+ bool found = False;
+ bool eob;
+
+ if ( !file )
+ return NULL;
+
+ if ( !(nk = talloc_zero( file->mem_ctx, REGF_NK_REC )) ) {
+ DEBUG(0,("regfio_rootkey: talloc() failed!\n"));
+ return NULL;
+ }
+
+ /* scan through the file on HBIN block at a time looking
+ for an NK record with a type == 0x002c.
+ Normally this is the first nk record in the first hbin
+ block (but I'm not assuming that for now) */
+
+ while ( (hbin = read_hbin_block( file, offset )) ) {
+ eob = False;
+
+ while ( !eob) {
+ if ( next_nk_record( file, hbin, nk, &eob ) ) {
+ if ( nk->key_type == NK_TYPE_ROOTKEY ) {
+ found = True;
+ break;
+ }
+ }
+ prs_mem_free( &hbin->ps );
+ }
+
+ if ( found )
+ break;
+
+ offset += hbin->block_size;
+ }
+
+ if ( !found ) {
+ DEBUG(0,("regfio_rootkey: corrupt registry file ? No root key record located\n"));
+ return NULL;
+ }
+
+ DLIST_ADD( file->block_list, hbin );
+
+ return nk;
+}
+
+/*******************************************************************
+ This acts as an iterator over the subkeys defined for a given
+ NK record. Remember that offsets are from the *first* HBIN block.
+*******************************************************************/
+
+ REGF_NK_REC* regfio_fetch_subkey( REGF_FILE *file, REGF_NK_REC *nk )
+{
+ REGF_NK_REC *subkey;
+ REGF_HBIN *hbin;
+ uint32_t nk_offset;
+
+ /* see if there is anything left to report */
+
+ if (nk == NULL ||
+ nk->subkeys.hashes == NULL ||
+ nk->subkey_index >= nk->subkeys.num_keys ||
+ (nk->subkeys_off == REGF_OFFSET_NONE) ||
+ (nk->subkey_index >= nk->num_subkeys)) {
+ return NULL;
+ }
+
+ /* find the HBIN block which should contain the nk record */
+
+ hbin = lookup_hbin_block(file,
+ nk->subkeys.hashes[nk->subkey_index].nk_off);
+ if (hbin == NULL) {
+ DEBUG(0,("hbin_prs_key: Failed to find HBIN block containing offset [0x%x]\n",
+ nk->subkeys.hashes[nk->subkey_index].nk_off));
+ return NULL;
+ }
+
+ nk_offset = nk->subkeys.hashes[nk->subkey_index].nk_off;
+ if ( !prs_set_offset( &hbin->ps, (HBIN_HDR_SIZE + nk_offset - hbin->first_hbin_off) ) )
+ return NULL;
+
+ nk->subkey_index++;
+ if ( !(subkey = talloc_zero( file->mem_ctx, REGF_NK_REC )) )
+ return NULL;
+
+ if ( !hbin_prs_key( file, hbin, subkey ) )
+ return NULL;
+
+ return subkey;
+}
+
+
+/*******************************************************************
+*******************************************************************/
+
+static REGF_HBIN* regf_hbin_allocate( REGF_FILE *file, uint32_t block_size )
+{
+ REGF_HBIN *hbin;
+ SMB_STRUCT_STAT sbuf;
+
+ if ( !(hbin = talloc_zero( file->mem_ctx, REGF_HBIN )) )
+ return NULL;
+
+ memcpy( hbin->header, "hbin", HBIN_HDR_SIZE);
+
+
+ if (sys_fstat(file->fd, &sbuf, false)) {
+ DEBUG(0,("regf_hbin_allocate: stat() failed! (%s)\n", strerror(errno)));
+ return NULL;
+ }
+
+ hbin->file_off = sbuf.st_ex_size;
+
+ hbin->free_off = HBIN_HEADER_REC_SIZE;
+ hbin->free_size = block_size - hbin->free_off + sizeof(uint32_t);
+
+ hbin->block_size = block_size;
+ hbin->first_hbin_off = hbin->file_off - REGF_BLOCKSIZE;
+
+ if ( !prs_init( &hbin->ps, block_size, file->mem_ctx, MARSHALL ) )
+ return NULL;
+
+ if ( !prs_hbin_block( "new_hbin", &hbin->ps, 0, hbin ) )
+ return NULL;
+
+ if ( !write_hbin_block( file, hbin ) )
+ return NULL;
+
+ file->last_block = hbin->file_off;
+
+ return hbin;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static void update_free_space( REGF_HBIN *hbin, uint32_t size_used )
+{
+ hbin->free_off += size_used;
+ hbin->free_size -= size_used;
+
+ if ( hbin->free_off >= hbin->block_size ) {
+ hbin->free_off = REGF_OFFSET_NONE;
+ }
+
+ return;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static REGF_HBIN* find_free_space( REGF_FILE *file, uint32_t size )
+{
+ REGF_HBIN *hbin, *p_hbin;
+ uint32_t block_off;
+ bool cached;
+
+ /* check open block list */
+
+ for ( hbin=file->block_list; hbin!=NULL; hbin=hbin->next ) {
+ /* only check blocks that actually have available space */
+
+ if ( hbin->free_off == REGF_OFFSET_NONE )
+ continue;
+
+ /* check for a large enough available chunk */
+
+ if ( (hbin->block_size - hbin->free_off) >= size ) {
+ DLIST_PROMOTE( file->block_list, hbin );
+ goto done;
+ }
+ }
+
+ /* parse the file until we find a block with
+ enough free space; save the last non-filled hbin */
+
+ block_off = REGF_BLOCKSIZE;
+ do {
+ /* cleanup before the next round */
+ cached = False;
+ if ( hbin )
+ prs_mem_free( &hbin->ps );
+
+ hbin = read_hbin_block( file, block_off );
+
+ if ( hbin ) {
+
+ /* make sure that we don't already have this block in memory */
+
+ for ( p_hbin=file->block_list; p_hbin!=NULL; p_hbin=p_hbin->next ) {
+ if ( p_hbin->file_off == hbin->file_off ) {
+ cached = True;
+ break;
+ }
+ }
+
+ block_off = hbin->file_off + hbin->block_size;
+
+ if ( cached ) {
+ prs_mem_free( &hbin->ps );
+ hbin = NULL;
+ continue;
+ }
+ }
+ /* if (cached block or (new block and not enough free space)) then continue looping */
+ } while ( cached || (hbin && (hbin->free_size < size)) );
+
+ /* no free space; allocate a new one */
+
+ if ( !hbin ) {
+ uint32_t alloc_size;
+
+ /* allocate in multiples of REGF_ALLOC_BLOCK; make sure (size + hbin_header) fits */
+
+ alloc_size = (((size+HBIN_HEADER_REC_SIZE) / REGF_ALLOC_BLOCK ) + 1 ) * REGF_ALLOC_BLOCK;
+
+ if ( !(hbin = regf_hbin_allocate( file, alloc_size )) ) {
+ DEBUG(0,("find_free_space: regf_hbin_allocate() failed!\n"));
+ return NULL;
+ }
+ DLIST_ADD( file->block_list, hbin );
+ }
+
+done:
+ /* set the offset to be ready to write */
+
+ if ( !prs_set_offset( &hbin->ps, hbin->free_off-sizeof(uint32_t) ) )
+ return NULL;
+
+ /* write the record size as a placeholder for now, it should be
+ probably updated by the caller once it all of the data necessary
+ for the record */
+
+ if ( !prs_uint32("allocated_size", &hbin->ps, 0, &size) )
+ return NULL;
+
+ update_free_space( hbin, size );
+
+ return hbin;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static uint32_t sk_record_data_size( struct security_descriptor * sd )
+{
+ uint32_t size, size_mod8;
+
+ size_mod8 = 0;
+
+ /* the record size is sizeof(hdr) + name + static members + data_size_field */
+
+ size = sizeof(uint32_t)*5 + ndr_size_security_descriptor(sd, 0) + sizeof(uint32_t);
+
+ /* multiple of 8 */
+ size_mod8 = size & 0xfffffff8;
+ if ( size_mod8 < size )
+ size_mod8 += 8;
+
+ return size_mod8;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static uint32_t vk_record_data_size( REGF_VK_REC *vk )
+{
+ uint32_t size, size_mod8;
+
+ size_mod8 = 0;
+
+ /* the record size is sizeof(hdr) + name + static members + data_size_field */
+
+ size = REC_HDR_SIZE + (sizeof(uint16_t)*3) + (sizeof(uint32_t)*3) + sizeof(uint32_t);
+
+ if ( vk->valuename )
+ size += strlen(vk->valuename);
+
+ /* multiple of 8 */
+ size_mod8 = size & 0xfffffff8;
+ if ( size_mod8 < size )
+ size_mod8 += 8;
+
+ return size_mod8;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static uint32_t lf_record_data_size( uint32_t num_keys )
+{
+ uint32_t size, size_mod8;
+
+ size_mod8 = 0;
+
+ /* the record size is sizeof(hdr) + num_keys + sizeof of hash_array + data_size_uint32_t */
+
+ size = REC_HDR_SIZE + sizeof(uint16_t) + (sizeof(REGF_HASH_REC) * num_keys) + sizeof(uint32_t);
+
+ /* multiple of 8 */
+ size_mod8 = size & 0xfffffff8;
+ if ( size_mod8 < size )
+ size_mod8 += 8;
+
+ return size_mod8;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static uint32_t nk_record_data_size( REGF_NK_REC *nk )
+{
+ uint32_t size, size_mod8;
+
+ size_mod8 = 0;
+
+ /* the record size is static + length_of_keyname + length_of_classname + data_size_uint32_t */
+
+ size = 0x4c + strlen(nk->keyname) + sizeof(uint32_t);
+
+ if ( nk->classname )
+ size += strlen( nk->classname );
+
+ /* multiple of 8 */
+ size_mod8 = size & 0xfffffff8;
+ if ( size_mod8 < size )
+ size_mod8 += 8;
+
+ return size_mod8;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static bool create_vk_record(REGF_FILE *file, REGF_VK_REC *vk,
+ struct regval_blob *value)
+{
+ char *name = regval_name(value);
+ REGF_HBIN *data_hbin;
+
+ ZERO_STRUCTP( vk );
+
+ memcpy( vk->header, "vk", REC_HDR_SIZE );
+
+ if ( name ) {
+ vk->valuename = talloc_strdup( file->mem_ctx, regval_name(value) );
+ vk->flag = VK_FLAG_NAME_PRESENT;
+ }
+
+ vk->data_size = regval_size( value );
+ vk->type = regval_type( value );
+
+ if ( vk->data_size > sizeof(uint32_t) ) {
+ uint32_t data_size = ( (vk->data_size+sizeof(uint32_t)) & 0xfffffff8 ) + 8;
+
+ vk->data = (uint8_t *)talloc_memdup( file->mem_ctx,
+ regval_data_p(value),
+ vk->data_size );
+ if (vk->data == NULL) {
+ return False;
+ }
+
+ /* go ahead and store the offset....we'll pick this hbin block back up when
+ we stream the data */
+
+ if ((data_hbin = find_free_space(file, data_size )) == NULL) {
+ return False;
+ }
+ vk->data_off = prs_offset( &data_hbin->ps ) + data_hbin->first_hbin_off - HBIN_HDR_SIZE;
+ }
+ else {
+ /* make sure we don't try to copy from a NULL value pointer */
+
+ if ( vk->data_size != 0 )
+ memcpy( &vk->data_off, regval_data_p(value), vk->data_size);
+ vk->data_size |= VK_DATA_IN_OFFSET;
+ }
+
+ return True;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+static int hashrec_cmp( REGF_HASH_REC *h1, REGF_HASH_REC *h2 )
+{
+ return strcasecmp_m( h1->fullname, h2->fullname );
+}
+
+/*******************************************************************
+*******************************************************************/
+
+ REGF_NK_REC* regfio_write_key( REGF_FILE *file, const char *name,
+ struct regval_ctr *values, struct regsubkey_ctr *subkeys,
+ struct security_descriptor *sec_desc, REGF_NK_REC *parent )
+{
+ REGF_NK_REC *nk;
+ REGF_HBIN *vlist_hbin = NULL;
+ uint32_t size;
+
+ if ( !(nk = talloc_zero( file->mem_ctx, REGF_NK_REC )) )
+ return NULL;
+
+ memcpy( nk->header, "nk", REC_HDR_SIZE );
+
+ if ( !parent )
+ nk->key_type = NK_TYPE_ROOTKEY;
+ else
+ nk->key_type = NK_TYPE_NORMALKEY;
+
+ /* store the parent offset (or -1 if a the root key */
+
+ nk->parent_off = parent ? (parent->hbin_off + parent->hbin->file_off - REGF_BLOCKSIZE - HBIN_HDR_SIZE ) : REGF_OFFSET_NONE;
+
+ /* no classname currently */
+
+ nk->classname_off = REGF_OFFSET_NONE;
+ nk->classname = NULL;
+ nk->keyname = talloc_strdup( file->mem_ctx, name );
+
+ /* current modification time */
+
+ unix_to_nt_time( &nk->mtime, time(NULL) );
+
+ /* allocate the record on disk */
+
+ size = nk_record_data_size( nk );
+ nk->rec_size = ( size - 1 ) ^ 0XFFFFFFFF;
+ if ((nk->hbin = find_free_space( file, size )) == NULL) {
+ return NULL;
+ }
+ nk->hbin_off = prs_offset( &nk->hbin->ps );
+
+ /* Update the hash record in the parent */
+
+ if ( parent ) {
+ REGF_HASH_REC *hash = &parent->subkeys.hashes[parent->subkey_index];
+
+ hash->nk_off = prs_offset( &nk->hbin->ps ) + nk->hbin->first_hbin_off - HBIN_HDR_SIZE;
+ memcpy(hash->keycheck, name, MIN(strlen(name),sizeof(uint32_t)));
+ hash->fullname = talloc_strdup( file->mem_ctx, name );
+ parent->subkey_index++;
+
+ /* sort the list by keyname */
+ TYPESAFE_QSORT(parent->subkeys.hashes, parent->subkey_index, hashrec_cmp);
+
+ if ( !hbin_prs_lf_records( "lf_rec", parent->subkeys.hbin, 0, parent ) )
+ return NULL;
+ }
+
+ /* write the security descriptor */
+
+ nk->sk_off = REGF_OFFSET_NONE;
+ if ( sec_desc ) {
+ uint32_t sk_size = sk_record_data_size( sec_desc );
+ REGF_HBIN *sk_hbin;
+
+ /* search for it in the existing list of sd's */
+
+ if ( (nk->sec_desc = find_sk_record_by_sec_desc( file, sec_desc )) == NULL ) {
+ /* not found so add it to the list */
+
+ if (!(sk_hbin = find_free_space( file, sk_size ))) {
+ return NULL;
+ }
+
+ if ( !(nk->sec_desc = talloc_zero( file->mem_ctx, REGF_SK_REC )) )
+ return NULL;
+
+ /* now we have to store the security descriptor in the list and
+ update the offsets */
+
+ memcpy( nk->sec_desc->header, "sk", REC_HDR_SIZE );
+ nk->sec_desc->hbin = sk_hbin;
+ nk->sec_desc->hbin_off = prs_offset( &sk_hbin->ps );
+ nk->sec_desc->sk_off = prs_offset( &sk_hbin->ps ) + sk_hbin->first_hbin_off - HBIN_HDR_SIZE;
+ nk->sec_desc->rec_size = (sk_size-1) ^ 0xFFFFFFFF;
+
+ nk->sec_desc->sec_desc = sec_desc;
+ nk->sec_desc->ref_count = 0;
+
+ /* size value must be self-inclusive */
+ nk->sec_desc->size = ndr_size_security_descriptor(sec_desc, 0)
+ + sizeof(uint32_t);
+
+ DLIST_ADD_END( file->sec_desc_list, nk->sec_desc);
+
+ /* update the offsets for us and the previous sd in the list.
+ if this is the first record, then just set the next and prev
+ offsets to ourself. */
+
+ if ( DLIST_PREV(nk->sec_desc) ) {
+ REGF_SK_REC *prev = DLIST_PREV(nk->sec_desc);
+
+ nk->sec_desc->prev_sk_off = prev->hbin_off + prev->hbin->first_hbin_off - HBIN_HDR_SIZE;
+ prev->next_sk_off = nk->sec_desc->sk_off;
+
+ /* the end must loop around to the front */
+ nk->sec_desc->next_sk_off = file->sec_desc_list->sk_off;
+
+ /* and first must loop around to the tail */
+ file->sec_desc_list->prev_sk_off = nk->sec_desc->sk_off;
+ } else {
+ nk->sec_desc->prev_sk_off = nk->sec_desc->sk_off;
+ nk->sec_desc->next_sk_off = nk->sec_desc->sk_off;
+ }
+ }
+
+ /* bump the reference count +1 */
+
+ nk->sk_off = nk->sec_desc->sk_off;
+ nk->sec_desc->ref_count++;
+ }
+
+ /* write the subkeys */
+
+ nk->subkeys_off = REGF_OFFSET_NONE;
+ if ( (nk->num_subkeys = regsubkey_ctr_numkeys( subkeys )) != 0 ) {
+ uint32_t lf_size = lf_record_data_size( nk->num_subkeys );
+ uint32_t namelen;
+ int i;
+
+ if (!(nk->subkeys.hbin = find_free_space( file, lf_size ))) {
+ return NULL;
+ }
+ nk->subkeys.hbin_off = prs_offset( &nk->subkeys.hbin->ps );
+ nk->subkeys.rec_size = (lf_size-1) ^ 0xFFFFFFFF;
+ nk->subkeys_off = prs_offset( &nk->subkeys.hbin->ps ) + nk->subkeys.hbin->first_hbin_off - HBIN_HDR_SIZE;
+
+ memcpy( nk->subkeys.header, "lf", REC_HDR_SIZE );
+
+ nk->subkeys.num_keys = nk->num_subkeys;
+ if (nk->subkeys.num_keys) {
+ if ( !(nk->subkeys.hashes = talloc_zero_array( file->mem_ctx, REGF_HASH_REC, nk->subkeys.num_keys )) )
+ return NULL;
+ } else {
+ nk->subkeys.hashes = NULL;
+ }
+ nk->subkey_index = 0;
+
+ /* update the max_bytes_subkey{name,classname} fields */
+ for ( i=0; i<nk->num_subkeys; i++ ) {
+ namelen = strlen( regsubkey_ctr_specific_key(subkeys, i) );
+ if ( namelen*2 > nk->max_bytes_subkeyname )
+ nk->max_bytes_subkeyname = namelen * 2;
+ }
+ }
+
+ /* write the values */
+
+ nk->values_off = REGF_OFFSET_NONE;
+ if ( (nk->num_values = regval_ctr_numvals( values )) != 0 ) {
+ uint32_t vlist_size = ( ( nk->num_values * sizeof(uint32_t) ) & 0xfffffff8 ) + 8;
+ int i;
+
+ if (!(vlist_hbin = find_free_space( file, vlist_size ))) {
+ return NULL;
+ }
+ nk->values_off = prs_offset( &vlist_hbin->ps ) + vlist_hbin->first_hbin_off - HBIN_HDR_SIZE;
+
+ if (nk->num_values) {
+ if ( !(nk->values = talloc_array( file->mem_ctx, REGF_VK_REC, nk->num_values )) )
+ return NULL;
+ } else {
+ nk->values = NULL;
+ }
+
+ /* create the vk records */
+
+ for ( i=0; i<nk->num_values; i++ ) {
+ uint32_t vk_size, namelen, datalen;
+ struct regval_blob *r;
+
+ r = regval_ctr_specific_value( values, i );
+ create_vk_record( file, &nk->values[i], r );
+ vk_size = vk_record_data_size( &nk->values[i] );
+ nk->values[i].hbin = find_free_space( file, vk_size );
+ nk->values[i].hbin_off = prs_offset( &nk->values[i].hbin->ps );
+ nk->values[i].rec_size = ( vk_size - 1 ) ^ 0xFFFFFFFF;
+ nk->values[i].rec_off = prs_offset( &nk->values[i].hbin->ps )
+ + nk->values[i].hbin->first_hbin_off
+ - HBIN_HDR_SIZE;
+
+ /* update the max bytes fields if necessary */
+
+ namelen = strlen( regval_name(r) );
+ if ( namelen*2 > nk->max_bytes_valuename )
+ nk->max_bytes_valuename = namelen * 2;
+
+ datalen = regval_size( r );
+ if ( datalen > nk->max_bytes_value )
+ nk->max_bytes_value = datalen;
+ }
+ }
+
+ /* stream the records */
+
+ prs_set_offset( &nk->hbin->ps, nk->hbin_off );
+ if ( !prs_nk_rec( "nk_rec", &nk->hbin->ps, 0, nk ) )
+ return NULL;
+
+ if ( nk->num_values ) {
+ if ( !hbin_prs_vk_records( "vk_records", vlist_hbin, 0, nk, file ) )
+ return NULL;
+ }
+
+
+ regfio_flush( file );
+
+ return nk;
+}
diff --git a/source3/registry/regfio.h b/source3/registry/regfio.h
new file mode 100644
index 0000000..fc77970
--- /dev/null
+++ b/source3/registry/regfio.h
@@ -0,0 +1,233 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Windows NT registry I/O library
+ * Copyright (c) Gerald (Jerry) Carter 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/************************************************************
+ * Most of this information was obtained from
+ * http://www.wednesday.demon.co.uk/dosreg.html
+ * Thanks Nigel!
+ ***********************************************************/
+
+#include "registry/reg_parse_prs.h"
+#include "registry/reg_objects.h"
+
+#ifndef _REGFIO_H
+#define _REGFIO_H
+
+struct regsubkey_ctr;
+
+/* Macros */
+
+#define REGF_BLOCKSIZE 0x1000
+#define REGF_ALLOC_BLOCK 0x1000
+
+/* header sizes for various records */
+
+#define REGF_HDR_SIZE 4
+#define HBIN_HDR_SIZE 4
+#define HBIN_HEADER_REC_SIZE 0x24
+#define REC_HDR_SIZE 2
+
+#define REGF_OFFSET_NONE 0xffffffff
+
+/* Flags for the vk records */
+
+#define VK_FLAG_NAME_PRESENT 0x0001
+#define VK_DATA_IN_OFFSET 0x80000000
+
+/* NK record macros */
+
+#define NK_TYPE_LINKKEY 0x0010
+#define NK_TYPE_NORMALKEY 0x0020
+#define NK_TYPE_ROOTKEY 0x002c
+
+#define HBIN_STORE_REF(x, y) { x->hbin = y; y->ref_count++ };
+#define HBIN_REMOVE_REF(x, y) { x->hbin = NULL; y->ref_count-- /* if the count == 0; we can clean up */ };
+
+
+/* HBIN block */
+struct regf_hbin;
+typedef struct regf_hbin {
+ struct regf_hbin *prev, *next;
+ uint32_t file_off; /* my offset in the registry file */
+ uint32_t free_off; /* offset to free space within the hbin record */
+ uint32_t free_size; /* amount of data left in the block */
+ int ref_count; /* how many active records are pointing to this block (not used currently) */
+
+ char header[HBIN_HDR_SIZE]; /* "hbin" */
+ uint32_t first_hbin_off; /* offset from first hbin block */
+ uint32_t block_size; /* block size of this blockually a multiple of 4096Kb) */
+
+ prs_struct ps; /* data */
+
+ bool dirty; /* has this hbin block been modified? */
+} REGF_HBIN;
+
+/* ??? List -- list of key offsets and hashed names for consistency */
+
+typedef struct {
+ uint32_t nk_off;
+ uint8_t keycheck[sizeof(uint32_t)];
+ char *fullname;
+} REGF_HASH_REC;
+
+typedef struct {
+ REGF_HBIN *hbin; /* pointer to HBIN record (in memory) containing this nk record */
+ uint32_t hbin_off; /* offset from beginning of this hbin block */
+ uint32_t rec_size; /* ((start_offset - end_offset) & 0xfffffff8) */
+
+ char header[REC_HDR_SIZE];
+ uint16_t num_keys;
+ REGF_HASH_REC *hashes;
+} REGF_LF_REC;
+
+/* Key Value */
+
+typedef struct {
+ REGF_HBIN *hbin; /* pointer to HBIN record (in memory) containing this nk record */
+ uint32_t hbin_off; /* offset from beginning of this hbin block */
+ uint32_t rec_size; /* ((start_offset - end_offset) & 0xfffffff8) */
+ uint32_t rec_off; /* offset stored in the value list */
+
+ char header[REC_HDR_SIZE];
+ char *valuename;
+ uint32_t data_size;
+ uint32_t data_off;
+ uint8_t *data;
+ uint32_t type;
+ uint16_t flag;
+} REGF_VK_REC;
+
+
+/* Key Security */
+struct _regf_sk_rec;
+
+typedef struct _regf_sk_rec {
+ struct _regf_sk_rec *next, *prev;
+ REGF_HBIN *hbin; /* pointer to HBIN record (in memory) containing this nk record */
+ uint32_t hbin_off; /* offset from beginning of this hbin block */
+ uint32_t rec_size; /* ((start_offset - end_offset) & 0xfffffff8) */
+
+ uint32_t sk_off; /* offset parsed from NK record used as a key
+ to lookup reference to this SK record */
+
+ char header[REC_HDR_SIZE];
+ uint32_t prev_sk_off;
+ uint32_t next_sk_off;
+ uint32_t ref_count;
+ uint32_t size;
+ struct security_descriptor *sec_desc;
+} REGF_SK_REC;
+
+/* Key Name */
+
+typedef struct {
+ REGF_HBIN *hbin; /* pointer to HBIN record (in memory) containing this nk record */
+ uint32_t hbin_off; /* offset from beginning of this hbin block */
+ uint32_t subkey_index; /* index to next subkey record to return */
+ uint32_t rec_size; /* ((start_offset - end_offset) & 0xfffffff8) */
+
+ /* header information */
+
+ char header[REC_HDR_SIZE];
+ uint16_t key_type;
+ NTTIME mtime;
+ uint32_t parent_off; /* back pointer in registry hive */
+ uint32_t classname_off;
+ char *classname;
+ char *keyname;
+
+ /* max lengths */
+
+ uint32_t max_bytes_subkeyname; /* max subkey name * 2 */
+ uint32_t max_bytes_subkeyclassname; /* max subkey classname length (as if) */
+ uint32_t max_bytes_valuename; /* max valuename * 2 */
+ uint32_t max_bytes_value; /* max value data size */
+
+ /* unknowns */
+
+ uint32_t unk_index; /* nigel says run time index ? */
+
+ /* children */
+
+ uint32_t num_subkeys;
+ uint32_t subkeys_off; /* hash records that point to NK records */
+ uint32_t num_values;
+ uint32_t values_off; /* value lists which point to VK records */
+ uint32_t sk_off; /* offset to SK record */
+
+ /* link in the other records here */
+
+ REGF_LF_REC subkeys;
+ REGF_VK_REC *values;
+ REGF_SK_REC *sec_desc;
+
+} REGF_NK_REC;
+
+/* REGF block */
+
+typedef struct {
+ /* run time information */
+
+ int fd; /* file descriptor */
+ int open_flags; /* flags passed to the open() call */
+ TALLOC_CTX *mem_ctx; /* memory context for run-time file access information */
+ REGF_HBIN *block_list; /* list of open hbin blocks */
+
+ /* file format information */
+
+ char header[REGF_HDR_SIZE]; /* "regf" */
+ uint32_t data_offset; /* offset to record in the first (or any?) hbin block */
+ uint32_t last_block; /* offset to last hbin block in file */
+ uint32_t checksum; /* XOR of bytes 0x0000 - 0x01FB */
+ NTTIME mtime;
+
+ REGF_SK_REC *sec_desc_list; /* list of security descriptors referenced by NK records */
+
+ /* Ignore checksums in input data. Used by fuzzing code to allow more
+ * coverage without having to calculate a valid checksum. The checksums
+ * are merely to detect data corruption and don't provide a security
+ * value.
+ */
+ bool ignore_checksums;
+
+ /* unknowns used to simply writing */
+
+ uint32_t unknown1;
+ uint32_t unknown2;
+ uint32_t unknown3;
+ uint32_t unknown4;
+ uint32_t unknown5;
+ uint32_t unknown6;
+
+} REGF_FILE;
+
+/* Function Declarations */
+
+REGF_FILE* regfio_open( const char *filename, int flags, int mode );
+int regfio_close( REGF_FILE *r );
+
+REGF_NK_REC* regfio_rootkey( REGF_FILE *file );
+REGF_NK_REC* regfio_fetch_subkey( REGF_FILE *file, REGF_NK_REC *nk );
+REGF_NK_REC* regfio_write_key ( REGF_FILE *file, const char *name,
+ struct regval_ctr *values, struct regsubkey_ctr *subkeys,
+ struct security_descriptor *sec_desc, REGF_NK_REC *parent );
+
+
+#endif /* _REGFIO_H */
+
diff --git a/source3/registry/tests/test_regfio.c b/source3/registry/tests/test_regfio.c
new file mode 100644
index 0000000..e835e65
--- /dev/null
+++ b/source3/registry/tests/test_regfio.c
@@ -0,0 +1,186 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) 2019 Michael Hanselmann <public@hansmi.ch>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <cmocka.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "includes.h"
+#include "lib/replace/replace.h"
+#include "system/filesys.h"
+#include "lib/util/samba_util.h"
+#include "registry/regfio.h"
+
+struct test_ctx {
+ char *tmp_regfile;
+ int tmp_regfile_fd;
+ REGF_FILE *rb;
+};
+
+static int setup_context(void **state)
+{
+ struct test_ctx *test_ctx;
+
+ test_ctx = talloc_zero(NULL, struct test_ctx);
+ assert_non_null(test_ctx);
+
+ test_ctx->tmp_regfile_fd = -1;
+
+ *state = test_ctx;
+
+ return 0;
+}
+
+static int setup_context_tempfile(void **state)
+{
+ struct test_ctx *test_ctx;
+ int ret;
+
+ ret = setup_context(state);
+
+ if (ret == 0) {
+ test_ctx = talloc_get_type_abort(*state, struct test_ctx);
+
+ test_ctx->tmp_regfile = talloc_strdup(test_ctx, "/tmp/regfio.XXXXXX");
+ assert_non_null(test_ctx->tmp_regfile);
+
+ test_ctx->tmp_regfile_fd = mkstemp(test_ctx->tmp_regfile);
+ assert_return_code(test_ctx->tmp_regfile_fd, errno);
+ }
+
+ return ret;
+}
+
+static int teardown_context(void **state)
+{
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+
+ if (test_ctx->rb) {
+ regfio_close(test_ctx->rb);
+ }
+
+ if (test_ctx->tmp_regfile) {
+ unlink(test_ctx->tmp_regfile);
+ }
+
+ if (test_ctx->tmp_regfile_fd != -1) {
+ close(test_ctx->tmp_regfile_fd);
+ }
+
+ talloc_free(test_ctx);
+
+ return 0;
+}
+
+static void open_testfile(struct test_ctx *test_ctx, const char *filename)
+{
+ char *path;
+
+ path = talloc_asprintf(test_ctx, "%s/testdata/samba3/%s", SRCDIR, filename);
+ assert_non_null(path);
+
+ test_ctx->rb = regfio_open(path, O_RDONLY, 0600);
+ assert_non_null(test_ctx->rb);
+}
+
+static void test_regfio_open_new_file(void **state)
+{
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+ REGF_NK_REC *root;
+ struct regval_ctr *values;
+ struct regsubkey_ctr *subkeys;
+ WERROR werr;
+
+ test_ctx->rb = regfio_open(test_ctx->tmp_regfile,
+ O_RDWR | O_CREAT | O_TRUNC, 0600);
+ assert_non_null(test_ctx->rb);
+
+ root = regfio_rootkey(test_ctx->rb);
+ assert_null(root);
+
+ werr = regsubkey_ctr_init(NULL, &subkeys);
+ assert_true(W_ERROR_IS_OK(werr));
+
+ werr = regval_ctr_init(subkeys, &values);
+ assert_true(W_ERROR_IS_OK(werr));
+
+ /* Write root key */
+ regfio_write_key(test_ctx->rb, "", values, subkeys, NULL, NULL);
+
+ root = regfio_rootkey(test_ctx->rb);
+ assert_non_null(root);
+ assert_memory_equal(root->header, "nk", sizeof(root->header));
+ assert_int_equal(root->key_type, NK_TYPE_ROOTKEY);
+}
+
+static void test_regfio_corrupt_hbin(void **state)
+{
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+ REGF_NK_REC *root;
+
+ open_testfile(test_ctx, "regfio_corrupt_hbin1.dat");
+
+ root = regfio_rootkey(test_ctx->rb);
+ assert_null(root);
+}
+
+static void test_regfio_corrupt_lf_subkeys(void **state)
+{
+ struct test_ctx *test_ctx =
+ talloc_get_type_abort(*state, struct test_ctx);
+ REGF_NK_REC *root, *subkey;
+
+ open_testfile(test_ctx, "regfio_corrupt_lf_subkeys.dat");
+
+ root = regfio_rootkey(test_ctx->rb);
+ assert_non_null(root);
+
+ root->subkey_index = 0;
+ while ((subkey = regfio_fetch_subkey(test_ctx->rb, root))) {
+ }
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_regfio_open_new_file,
+ setup_context_tempfile,
+ teardown_context),
+ cmocka_unit_test_setup_teardown(test_regfio_corrupt_hbin,
+ setup_context,
+ teardown_context),
+ cmocka_unit_test_setup_teardown(test_regfio_corrupt_lf_subkeys,
+ setup_context,
+ teardown_context),
+ };
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source3/rpc_client/cli_lsarpc.c b/source3/rpc_client/cli_lsarpc.c
new file mode 100644
index 0000000..73b4872
--- /dev/null
+++ b/source3/rpc_client/cli_lsarpc.c
@@ -0,0 +1,850 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+ Copyright (C) Tim Potter 2000-2001,
+ Copyright (C) Andrew Tridgell 1992-1997,2000,
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Jeremy Allison 2005.
+ Copyright (C) Michael Adam 2007.
+ Copyright (C) Guenther Deschner 2008.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "lsa.h"
+
+/** @defgroup lsa LSA - Local Security Architecture
+ * @ingroup rpc_client
+ *
+ * @{
+ **/
+
+/**
+ * @file cli_lsarpc.c
+ *
+ * RPC client routines for the LSA RPC pipe. LSA means "local
+ * security authority", which is half of a password database.
+ **/
+
+NTSTATUS dcerpc_lsa_open_policy(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ bool sec_qos,
+ uint32_t des_access,
+ struct policy_handle *pol,
+ NTSTATUS *result)
+{
+ struct lsa_ObjectAttribute attr = { .len = 0x18, };
+ struct lsa_QosInfo qos;
+ uint16_t system_name = '\\';
+
+ if (sec_qos) {
+ qos.len = 0xc;
+ qos.impersonation_level = 2;
+ qos.context_mode = 1;
+ qos.effective_only = 0;
+
+ attr.sec_qos = &qos;
+ }
+
+ return dcerpc_lsa_OpenPolicy(h,
+ mem_ctx,
+ &system_name,
+ &attr,
+ des_access,
+ pol,
+ result);
+}
+
+/** Open a LSA policy handle
+ *
+ * @param cli Handle on an initialised SMB connection */
+
+NTSTATUS rpccli_lsa_open_policy(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ bool sec_qos, uint32_t des_access,
+ struct policy_handle *pol)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ status = dcerpc_lsa_open_policy(cli->binding_handle,
+ mem_ctx,
+ sec_qos,
+ des_access,
+ pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+NTSTATUS dcerpc_lsa_open_policy2(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ bool sec_qos,
+ uint32_t des_access,
+ struct policy_handle *pol,
+ NTSTATUS *result)
+{
+ struct lsa_ObjectAttribute attr = { .len = 0x18, };
+ struct lsa_QosInfo qos;
+
+ if (sec_qos) {
+ qos.len = 0xc;
+ qos.impersonation_level = 2;
+ qos.context_mode = 1;
+ qos.effective_only = 0;
+
+ attr.sec_qos = &qos;
+ }
+
+ return dcerpc_lsa_OpenPolicy2(h,
+ mem_ctx,
+ srv_name_slash,
+ &attr,
+ des_access,
+ pol,
+ result);
+}
+
+NTSTATUS dcerpc_lsa_open_policy3(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ bool sec_qos,
+ uint32_t des_access,
+ uint32_t *out_version,
+ union lsa_revision_info *out_revision_info,
+ struct policy_handle *pol,
+ NTSTATUS *result)
+{
+ struct lsa_ObjectAttribute attr = { .len = 0x18, };
+ struct lsa_QosInfo qos;
+ union lsa_revision_info in_revision_info = {
+ .info1 = {
+ .revision = 1,
+ },
+ };
+ uint32_t in_version = 1;
+
+ if (sec_qos) {
+ qos.len = 0xc;
+ qos.impersonation_level = 2;
+ qos.context_mode = 1;
+ qos.effective_only = 0;
+
+ attr.sec_qos = &qos;
+ }
+
+ return dcerpc_lsa_OpenPolicy3(h,
+ mem_ctx,
+ srv_name_slash,
+ &attr,
+ des_access,
+ in_version,
+ &in_revision_info,
+ out_version,
+ out_revision_info,
+ pol,
+ result);
+}
+
+NTSTATUS dcerpc_lsa_open_policy_fallback(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ bool sec_qos,
+ uint32_t desired_access,
+ uint32_t *out_version,
+ union lsa_revision_info *out_revision_info,
+ struct policy_handle *pol,
+ NTSTATUS *result)
+{
+ NTSTATUS status;
+
+ status = dcerpc_lsa_open_policy3(h,
+ mem_ctx,
+ srv_name_slash,
+ sec_qos,
+ desired_access,
+ out_version,
+ out_revision_info,
+ pol,
+ result);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+ *out_version = 1;
+ *out_revision_info = (union lsa_revision_info) {
+ .info1 = {
+ .revision = 1,
+ }
+ };
+
+ status = dcerpc_lsa_open_policy2(h,
+ mem_ctx,
+ srv_name_slash,
+ sec_qos,
+ desired_access,
+ pol,
+ result);
+ }
+
+ return status;
+}
+
+/* Lookup a list of sids
+ *
+ * internal version withOUT memory allocation of the target arrays.
+ * this assumes sufficiently sized arrays to store domains, names and types. */
+
+static NTSTATUS dcerpc_lsa_lookup_sids_noalloc(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ TALLOC_CTX *domains_ctx,
+ TALLOC_CTX *names_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ enum lsa_LookupNamesLevel level,
+ char **domains,
+ char **names,
+ enum lsa_SidType *types,
+ bool use_lookupsids3,
+ NTSTATUS *presult)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ struct lsa_SidArray sid_array;
+ struct lsa_RefDomainList *ref_domains = NULL;
+ struct lsa_TransNameArray lsa_names = { .count = 0, };
+ uint32_t count = 0;
+ int i;
+
+ sid_array.num_sids = num_sids;
+ sid_array.sids = talloc_array(mem_ctx, struct lsa_SidPtr, num_sids);
+ if (sid_array.sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i<num_sids; i++) {
+ sid_array.sids[i].sid = dom_sid_dup(mem_ctx, &sids[i]);
+ if (!sid_array.sids[i].sid) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (use_lookupsids3) {
+ struct lsa_TransNameArray2 lsa_names2;
+ uint32_t n;
+
+ ZERO_STRUCT(lsa_names2);
+
+ status = dcerpc_lsa_LookupSids3(h,
+ mem_ctx,
+ &sid_array,
+ &ref_domains,
+ &lsa_names2,
+ level,
+ &count,
+ LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES,
+ LSA_CLIENT_REVISION_2,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_LOOKUP_ERR(result)) {
+ lsa_names.count = lsa_names2.count;
+ lsa_names.names = talloc_array(mem_ctx,
+ struct lsa_TranslatedName,
+ lsa_names.count);
+ if (lsa_names.names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ for (n=0; n < lsa_names.count; n++) {
+ lsa_names.names[n].sid_type = lsa_names2.names[n].sid_type;
+ lsa_names.names[n].name = lsa_names2.names[n].name;
+ lsa_names.names[n].sid_index = lsa_names2.names[n].sid_index;
+ }
+ }
+
+ } else {
+ status = dcerpc_lsa_LookupSids(h,
+ mem_ctx,
+ pol,
+ &sid_array,
+ &ref_domains,
+ &lsa_names,
+ level,
+ &count,
+ &result);
+ }
+
+ DEBUG(10, ("LSA_LOOKUPSIDS returned status: '%s', result: '%s', "
+ "mapped count = %d'\n",
+ nt_errstr(status), nt_errstr(result), count));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (NT_STATUS_LOOKUP_ERR(result)) {
+ *presult = result;
+ return status;
+ }
+
+ /* Return output parameters */
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED) ||
+ (count == 0))
+ {
+ for (i = 0; i < num_sids; i++) {
+ (names)[i] = NULL;
+ (domains)[i] = NULL;
+ (types)[i] = SID_NAME_UNKNOWN;
+ }
+ *presult = NT_STATUS_NONE_MAPPED;
+ return status;
+ }
+
+ for (i = 0; i < num_sids; i++) {
+ const char *name, *dom_name;
+ uint32_t dom_idx;
+
+ if (i >= lsa_names.count) {
+ *presult = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ return status;
+ }
+
+ dom_idx = lsa_names.names[i].sid_index;
+
+ /* Translate optimised name through domain index array */
+
+ if (dom_idx != 0xffffffff) {
+ if (ref_domains == NULL) {
+ *presult = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ return status;
+ }
+ if (dom_idx >= ref_domains->count) {
+ *presult = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ return status;
+ }
+
+ dom_name = ref_domains->domains[dom_idx].name.string;
+ name = lsa_names.names[i].name.string;
+
+ if (name) {
+ (names)[i] = talloc_strdup(names_ctx, name);
+ if ((names)[i] == NULL) {
+ DEBUG(0, ("cli_lsa_lookup_sids_noalloc(): out of memory\n"));
+ *presult = NT_STATUS_UNSUCCESSFUL;
+ return status;
+ }
+ } else {
+ (names)[i] = NULL;
+ }
+ domains[i] = talloc_strdup(domains_ctx,
+ dom_name ? dom_name : "");
+ (types)[i] = lsa_names.names[i].sid_type;
+ if ((domains)[i] == NULL) {
+ DEBUG(0, ("cli_lsa_lookup_sids_noalloc(): out of memory\n"));
+ *presult = NT_STATUS_UNSUCCESSFUL;
+ return status;
+ }
+
+ } else {
+ (names)[i] = NULL;
+ (domains)[i] = NULL;
+ (types)[i] = SID_NAME_UNKNOWN;
+ }
+ }
+
+ *presult = NT_STATUS_OK;
+ return status;
+}
+
+/* Lookup a list of sids
+ *
+ * do it the right way: there is a limit (of 20480 for w2k3) entries
+ * returned by this call. when the sids list contains more entries,
+ * empty lists are returned. This version of lsa_lookup_sids passes
+ * the list of sids in hunks of LOOKUP_SIDS_HUNK_SIZE to the lsa call. */
+
+/* This constant defines the limit of how many sids to look up
+ * in one call (maximum). the limit from the server side is
+ * at 20480 for win2k3, but we keep it at a save 1000 for now. */
+#define LOOKUP_SIDS_HUNK_SIZE 1000
+
+NTSTATUS dcerpc_lsa_lookup_sids_generic(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ enum lsa_LookupNamesLevel level,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes,
+ bool use_lookupsids3,
+ NTSTATUS *presult)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS result = NT_STATUS_OK;
+ int sids_left = 0;
+ int sids_processed = 0;
+ const struct dom_sid *hunk_sids = sids;
+ char **hunk_domains;
+ char **hunk_names;
+ enum lsa_SidType *hunk_types;
+ char **domains = NULL;
+ char **names = NULL;
+ enum lsa_SidType *types = NULL;
+ bool have_mapped = false;
+ bool have_unmapped = false;
+
+ if (num_sids) {
+ domains = talloc_zero_array(mem_ctx, char *, num_sids);
+ if (domains == NULL) {
+ DEBUG(0, ("rpccli_lsa_lookup_sids(): out of memory\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ names = talloc_zero_array(mem_ctx, char *, num_sids);
+ if (names == NULL) {
+ DEBUG(0, ("rpccli_lsa_lookup_sids(): out of memory\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ types = talloc_zero_array(mem_ctx, enum lsa_SidType, num_sids);
+ if (types == NULL) {
+ DEBUG(0, ("rpccli_lsa_lookup_sids(): out of memory\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ sids_left = num_sids;
+ hunk_domains = domains;
+ hunk_names = names;
+ hunk_types = types;
+
+ while (sids_left > 0) {
+ int hunk_num_sids;
+ NTSTATUS hunk_result = NT_STATUS_UNSUCCESSFUL;
+
+ hunk_num_sids = ((sids_left > LOOKUP_SIDS_HUNK_SIZE)
+ ? LOOKUP_SIDS_HUNK_SIZE
+ : sids_left);
+
+ DEBUG(10, ("rpccli_lsa_lookup_sids: processing items "
+ "%d -- %d of %d.\n",
+ sids_processed,
+ sids_processed + hunk_num_sids - 1,
+ num_sids));
+
+ status = dcerpc_lsa_lookup_sids_noalloc(h,
+ mem_ctx,
+ (TALLOC_CTX *)domains,
+ (TALLOC_CTX *)names,
+ pol,
+ hunk_num_sids,
+ hunk_sids,
+ level,
+ hunk_domains,
+ hunk_names,
+ hunk_types,
+ use_lookupsids3,
+ &hunk_result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (!NT_STATUS_IS_OK(hunk_result) &&
+ !NT_STATUS_EQUAL(hunk_result, STATUS_SOME_UNMAPPED) &&
+ !NT_STATUS_EQUAL(hunk_result, NT_STATUS_NONE_MAPPED))
+ {
+ /* An actual error occurred */
+ *presult = hunk_result;
+ goto fail;
+ }
+
+ if (NT_STATUS_IS_OK(hunk_result)) {
+ have_mapped = true;
+ }
+ if (NT_STATUS_EQUAL(hunk_result, NT_STATUS_NONE_MAPPED)) {
+ have_unmapped = true;
+ }
+ if (NT_STATUS_EQUAL(hunk_result, STATUS_SOME_UNMAPPED)) {
+ int i;
+ for (i=0; i<hunk_num_sids; i++) {
+ if (hunk_types[i] == SID_NAME_UNKNOWN) {
+ have_unmapped = true;
+ } else {
+ have_mapped = true;
+ }
+ }
+ }
+
+ sids_left -= hunk_num_sids;
+ sids_processed += hunk_num_sids;
+ hunk_sids += hunk_num_sids;
+ hunk_domains += hunk_num_sids;
+ hunk_names += hunk_num_sids;
+ hunk_types += hunk_num_sids;
+ }
+
+ *pdomains = domains;
+ *pnames = names;
+ *ptypes = types;
+
+ if (!have_mapped) {
+ result = NT_STATUS_NONE_MAPPED;
+ }
+ if (have_unmapped) {
+ result = STATUS_SOME_UNMAPPED;
+ }
+ *presult = result;
+
+ return status;
+
+fail:
+ TALLOC_FREE(domains);
+ TALLOC_FREE(names);
+ TALLOC_FREE(types);
+
+ return status;
+}
+
+NTSTATUS dcerpc_lsa_lookup_sids(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes,
+ NTSTATUS *result)
+{
+ enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_ALL;
+ return dcerpc_lsa_lookup_sids_generic(h,
+ mem_ctx,
+ pol,
+ num_sids,
+ sids,
+ level,
+ pdomains,
+ pnames,
+ ptypes,
+ false,
+ result);
+}
+
+NTSTATUS rpccli_lsa_lookup_sids(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_ALL;
+
+ status = dcerpc_lsa_lookup_sids_generic(cli->binding_handle,
+ mem_ctx,
+ pol,
+ num_sids,
+ sids,
+ level,
+ pdomains,
+ pnames,
+ ptypes,
+ false,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+NTSTATUS dcerpc_lsa_lookup_sids3(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes,
+ NTSTATUS *result)
+{
+ enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_ALL;
+ return dcerpc_lsa_lookup_sids_generic(h,
+ mem_ctx,
+ pol,
+ num_sids,
+ sids,
+ level,
+ pdomains,
+ pnames,
+ ptypes,
+ true,
+ result);
+}
+
+/** Lookup a list of names */
+
+NTSTATUS dcerpc_lsa_lookup_names_generic(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ uint32_t num_names,
+ const char **names,
+ const char ***dom_names,
+ enum lsa_LookupNamesLevel level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types,
+ bool use_lookupnames4,
+ NTSTATUS *presult)
+{
+ NTSTATUS status;
+ struct lsa_String *lsa_names = NULL;
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransSidArray sid_array = { .count = 0, };
+ struct lsa_TransSidArray3 sid_array3 = { .count = 0, };
+ uint32_t count = 0;
+ uint32_t i;
+
+ lsa_names = talloc_array(mem_ctx, struct lsa_String, num_names);
+ if (lsa_names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_names; i++) {
+ init_lsa_String(&lsa_names[i], names[i]);
+ }
+
+ if (use_lookupnames4) {
+ status = dcerpc_lsa_LookupNames4(h,
+ mem_ctx,
+ num_names,
+ lsa_names,
+ &domains,
+ &sid_array3,
+ level,
+ &count,
+ LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES,
+ LSA_CLIENT_REVISION_2,
+ presult);
+ } else {
+ status = dcerpc_lsa_LookupNames(h,
+ mem_ctx,
+ pol,
+ num_names,
+ lsa_names,
+ &domains,
+ &sid_array,
+ level,
+ &count,
+ presult);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(*presult) &&
+ !NT_STATUS_EQUAL(*presult, STATUS_SOME_UNMAPPED)) {
+ /* An actual error occurred */
+ goto done;
+ }
+
+ /* Return output parameters */
+ if (count == 0) {
+ *presult = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ if (num_names) {
+ *sids = talloc_zero_array(mem_ctx, struct dom_sid, num_names);
+ if (*sids == NULL) {
+ DEBUG(0, ("cli_lsa_lookup_sids(): out of memory\n"));
+ *presult = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ *types = talloc_zero_array(mem_ctx, enum lsa_SidType, num_names);
+ if (*types == NULL) {
+ DEBUG(0, ("cli_lsa_lookup_sids(): out of memory\n"));
+ *presult = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (dom_names != NULL) {
+ *dom_names = talloc_zero_array(mem_ctx, const char *, num_names);
+ if (*dom_names == NULL) {
+ DEBUG(0, ("cli_lsa_lookup_sids(): out of memory\n"));
+ *presult = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+ } else {
+ *sids = NULL;
+ *types = NULL;
+ if (dom_names != NULL) {
+ *dom_names = NULL;
+ }
+ }
+
+ for (i = 0; i < num_names; i++) {
+ uint32_t dom_idx;
+ struct dom_sid *sid = &(*sids)[i];
+
+ if (use_lookupnames4) {
+ if (i >= sid_array3.count) {
+ *presult = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ dom_idx = sid_array3.sids[i].sid_index;
+ (*types)[i] = sid_array3.sids[i].sid_type;
+ } else {
+ if (i >= sid_array.count) {
+ *presult = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ dom_idx = sid_array.sids[i].sid_index;
+ (*types)[i] = sid_array.sids[i].sid_type;
+ }
+
+ /* Translate optimised sid through domain index array */
+
+ if (dom_idx == 0xffffffff) {
+ /* Nothing to do, this is unknown */
+ ZERO_STRUCTP(sid);
+ (*types)[i] = SID_NAME_UNKNOWN;
+ continue;
+ }
+ if (domains == NULL) {
+ *presult = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (dom_idx >= domains->count) {
+ *presult = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ if (use_lookupnames4) {
+ sid_copy(sid, sid_array3.sids[i].sid);
+ } else {
+ sid_copy(sid, domains->domains[dom_idx].sid);
+
+ if (sid_array.sids[i].rid != 0xffffffff) {
+ sid_append_rid(sid, sid_array.sids[i].rid);
+ }
+ }
+
+ if (dom_names == NULL) {
+ continue;
+ }
+
+ (*dom_names)[i] = domains->domains[dom_idx].name.string;
+ }
+
+ done:
+ return status;
+}
+
+NTSTATUS dcerpc_lsa_lookup_names(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ uint32_t num_names,
+ const char **names,
+ const char ***dom_names,
+ enum lsa_LookupNamesLevel level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types,
+ NTSTATUS *result)
+{
+ return dcerpc_lsa_lookup_names_generic(h,
+ mem_ctx,
+ pol,
+ num_names,
+ names,
+ dom_names,
+ level,
+ sids,
+ types,
+ false,
+ result);
+}
+
+NTSTATUS rpccli_lsa_lookup_names(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_names,
+ const char **names,
+ const char ***dom_names,
+ int level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ status = dcerpc_lsa_lookup_names(cli->binding_handle,
+ mem_ctx,
+ pol,
+ num_names,
+ names,
+ dom_names,
+ level,
+ sids,
+ types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+NTSTATUS dcerpc_lsa_lookup_names4(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ uint32_t num_names,
+ const char **names,
+ const char ***dom_names,
+ enum lsa_LookupNamesLevel level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types,
+ NTSTATUS *result)
+{
+ return dcerpc_lsa_lookup_names_generic(h,
+ mem_ctx,
+ pol,
+ num_names,
+ names,
+ dom_names,
+ level,
+ sids,
+ types,
+ true,
+ result);
+}
diff --git a/source3/rpc_client/cli_lsarpc.h b/source3/rpc_client/cli_lsarpc.h
new file mode 100644
index 0000000..0a0f399
--- /dev/null
+++ b/source3/rpc_client/cli_lsarpc.h
@@ -0,0 +1,278 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * LSARPC client routines
+ *
+ * Copyright (c) 2000-2001 Tim Potter
+ * Copyright (c) 1992-2000 Andrew Tridgell
+ * Copyright (c) 2002 Rafal Szczesniak
+ * Copyright (c) 2005 Jeremy Allison
+ * Copyright (c) 2007 Michael Adam
+ * Copyright (c) 2008 Guenther Deschner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CLI_LSARPC_H
+#define _CLI_LSARPC_H
+
+/* The following definitions come from rpc_client/cli_lsarpc.c */
+
+/**
+ * @brief Open a LSA policy.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] sec_qos Enable security quality of services.
+ *
+ * @param[in] des_access The desired access rights to be granted.
+ *
+ * @param[out] pol A pointer to a rpc policy handle.
+ *
+ * @param[out] result A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_lsa_open_policy(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ bool sec_qos,
+ uint32_t des_access,
+ struct policy_handle *pol,
+ NTSTATUS *result);
+NTSTATUS rpccli_lsa_open_policy(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ bool sec_qos, uint32_t des_access,
+ struct policy_handle *pol);
+
+/**
+ * @brief Open a LSA policy.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] sec_qos Enable security quality of services.
+ *
+ * @param[in] des_access The desired access rights to be granted.
+ *
+ * @param[out] pol A pointer to a rpc policy handle.
+ *
+ * @param[out] result A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_lsa_open_policy2(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ bool sec_qos,
+ uint32_t des_access,
+ struct policy_handle *pol,
+ NTSTATUS *result);
+
+/**
+ * @brief Open a LSA policy.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] sec_qos Enable security quality of services.
+ *
+ * @param[in] des_access The desired access rights to be granted.
+ *
+ * @param[out] out_version A pointer to an uin32_t to store the version of the
+ * following data structure.
+ *
+ * @param[out] out_revision info A pointer to store the out_revision_info.
+ *
+ * @param[out] pol A pointer to a rpc policy handle.
+ *
+ * @param[out] result A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_lsa_open_policy3(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ bool sec_qos,
+ uint32_t des_access,
+ uint32_t *out_version,
+ union lsa_revision_info *out_revision_info,
+ struct policy_handle *pol,
+ NTSTATUS *result);
+
+/**
+ * @brief Open a LSA policy with fallback to previous version
+ *
+ * This first calls lsa_open_policy3 and falls back to lsa_open_policy2 in case
+ * it isn't implemented.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] sec_qos Enable security quality of services.
+ *
+ * @param[in] des_access The desired access rights to be granted.
+ *
+ * @param[out] out_version A pointer to an uin32_t to store the version of the
+ * following data structure.
+ *
+ * @param[out] out_revision info A pointer to store the out_revision_info.
+ *
+ * @param[out] pol A pointer to a rpc policy handle.
+ *
+ * @param[out] result A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_lsa_open_policy_fallback(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ bool sec_qos,
+ uint32_t desired_access,
+ uint32_t *out_version,
+ union lsa_revision_info *out_revision_info,
+ struct policy_handle *pol,
+ NTSTATUS *result);
+
+/**
+ * @brief Look up the names that correspond to an array of sids.
+ *
+ * @param[in] h The initialized binding handle for a dcerpc connection.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] pol The opened domain policy handle.
+ *
+ * @param[in] num_sids The number of sids in the sids array to look up.
+ *
+ * @param[in] sids The array of sids to look up.
+ *
+ * @param[out] pdomains A pointer to store the refercenced domains.
+ *
+ * @param[out] pnames A pointer to an array for the translated names.
+ *
+ * @param[out] ptypes A pointer to an array for the types of the names.
+ *
+ * @param[out] result A pointer for the conversion result.
+ *
+ * @return A corresponding NTSTATUS error code.
+ */
+NTSTATUS dcerpc_lsa_lookup_sids(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes,
+ NTSTATUS *result);
+NTSTATUS rpccli_lsa_lookup_sids(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes);
+NTSTATUS dcerpc_lsa_lookup_sids_generic(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ enum lsa_LookupNamesLevel level,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes,
+ bool use_lookupsids3,
+ NTSTATUS *presult);
+/**
+ * @brief Look up the names that correspond to an array of sids.
+ *
+ * @param[in] h The initialized binding handle for a dcerpc connection.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] pol The opened domain policy handle.
+ *
+ * @param[in] num_sids The number of sids in the sids array to look up.
+ *
+ * @param[in] sids The array of sids to look up.
+ *
+ * @param[out] pdomains A pointer to store the refercenced domains.
+ *
+ * @param[out] pnames A pointer to an array for the translated names.
+ *
+ * @param[out] ptypes A pointer to an array for the types of the names.
+ *
+ * @param[out] result A pointer for the conversion result.
+ *
+ * @return A corresponding NTSTATUS error code.
+ */
+NTSTATUS dcerpc_lsa_lookup_sids3(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ int num_sids,
+ const struct dom_sid *sids,
+ char ***pdomains,
+ char ***pnames,
+ enum lsa_SidType **ptypes,
+ NTSTATUS *result);
+NTSTATUS dcerpc_lsa_lookup_names(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ uint32_t num_names,
+ const char **names,
+ const char ***dom_names,
+ enum lsa_LookupNamesLevel level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types,
+ NTSTATUS *result);
+NTSTATUS rpccli_lsa_lookup_names(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol, int num_names,
+ const char **names,
+ const char ***dom_names,
+ int level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types);
+
+NTSTATUS dcerpc_lsa_lookup_names4(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ uint32_t num_names,
+ const char **names,
+ const char ***dom_names,
+ enum lsa_LookupNamesLevel level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types,
+ NTSTATUS *result);
+NTSTATUS dcerpc_lsa_lookup_names_generic(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ uint32_t num_names,
+ const char **names,
+ const char ***dom_names,
+ enum lsa_LookupNamesLevel level,
+ struct dom_sid **sids,
+ enum lsa_SidType **types,
+ bool use_lookupnames4,
+ NTSTATUS *presult);
+
+bool fetch_domain_sid( char *domain, char *remote_machine, struct dom_sid *psid);
+
+#endif /* _CLI_LSARPC_H */
diff --git a/source3/rpc_client/cli_mdssvc.c b/source3/rpc_client/cli_mdssvc.c
new file mode 100644
index 0000000..93e032f
--- /dev/null
+++ b/source3/rpc_client/cli_mdssvc.c
@@ -0,0 +1,1214 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight client functions
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_client.h"
+#include "../librpc/gen_ndr/ndr_mdssvc_c.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "rpc_server/mdssvc/dalloc.h"
+#include "rpc_server/mdssvc/marshalling.h"
+#include "cli_mdssvc.h"
+#include "cli_mdssvc_private.h"
+#include "cli_mdssvc_util.h"
+
+struct mdsctx_id mdscli_new_ctx_id(struct mdscli_ctx *mdscli_ctx)
+{
+ mdscli_ctx->ctx_id.id++;
+ return mdscli_ctx->ctx_id;
+}
+
+char *mdscli_get_basepath(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *mdscli_ctx)
+{
+ return talloc_strdup(mem_ctx, mdscli_ctx->mdscmd_open.share_path);
+}
+
+struct mdscli_connect_state {
+ struct tevent_context *ev;
+ struct mdscli_ctx *mdscli_ctx;
+ struct mdssvc_blob response_blob;
+};
+
+static void mdscli_connect_open_done(struct tevent_req *subreq);
+static void mdscli_connect_unknown1_done(struct tevent_req *subreq);
+static void mdscli_connect_fetch_props_done(struct tevent_req *subreq);
+
+struct tevent_req *mdscli_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *bh,
+ const char *share_name,
+ const char *mount_path)
+{
+ struct tevent_req *req = NULL;
+ struct mdscli_connect_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct mdscli_ctx *ctx = NULL;
+
+ req = tevent_req_create(req, &state, struct mdscli_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ ctx = talloc_zero(state, struct mdscli_ctx);
+ if (tevent_req_nomem(ctx, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ *state = (struct mdscli_connect_state) {
+ .ev = ev,
+ .mdscli_ctx = ctx,
+ };
+
+ *ctx = (struct mdscli_ctx) {
+ .bh = bh,
+ .max_fragment_size = 64 * 1024,
+ /*
+ * The connection id is a per tcon value sent by the client,
+ * 0x6b000060 is a value used most of the times for the first
+ * tcon.
+ */
+ .ctx_id.connection = UINT64_C(0x6b000060),
+ };
+
+ subreq = dcerpc_mdssvc_open_send(state,
+ state->ev,
+ ctx->bh,
+ &ctx->dev,
+ &ctx->mdscmd_open.unkn2,
+ &ctx->mdscmd_open.unkn3,
+ mount_path,
+ share_name,
+ ctx->mdscmd_open.share_path,
+ &ctx->ph);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+ tevent_req_set_callback(subreq, mdscli_connect_open_done, req);
+ ctx->async_pending++;
+
+ return req;
+}
+
+static void mdscli_connect_open_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_connect_state *state = tevent_req_data(
+ req, struct mdscli_connect_state);
+ struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
+ size_t share_path_len;
+ NTSTATUS status;
+
+ status = dcerpc_mdssvc_open_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ state->mdscli_ctx->async_pending--;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ share_path_len = strlen(mdscli_ctx->mdscmd_open.share_path);
+ if (share_path_len < 1 || share_path_len > UINT16_MAX) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ mdscli_ctx->mdscmd_open.share_path_len = share_path_len;
+
+ if (mdscli_ctx->mdscmd_open.share_path[share_path_len-1] == '/') {
+ mdscli_ctx->mdscmd_open.share_path[share_path_len-1] = '\0';
+ mdscli_ctx->mdscmd_open.share_path_len--;
+ }
+
+ subreq = dcerpc_mdssvc_unknown1_send(
+ state,
+ state->ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 0,
+ geteuid(),
+ getegid(),
+ &mdscli_ctx->mdscmd_unknown1.status,
+ &mdscli_ctx->flags,
+ &mdscli_ctx->mdscmd_unknown1.unkn7);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, mdscli_connect_unknown1_done, req);
+}
+
+static void mdscli_connect_unknown1_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_connect_state *state = tevent_req_data(
+ req, struct mdscli_connect_state);
+ struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
+ struct mdssvc_blob request_blob;
+ NTSTATUS status;
+
+ status = dcerpc_mdssvc_unknown1_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = mdscli_blob_fetch_props(state,
+ state->mdscli_ctx,
+ &request_blob);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = dcerpc_mdssvc_cmd_send(state,
+ state->ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 0,
+ mdscli_ctx->flags,
+ request_blob,
+ 0,
+ mdscli_ctx->max_fragment_size,
+ 1,
+ mdscli_ctx->max_fragment_size,
+ 0,
+ 0,
+ &mdscli_ctx->mdscmd_cmd.fragment,
+ &state->response_blob,
+ &mdscli_ctx->mdscmd_cmd.unkn9);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, mdscli_connect_fetch_props_done, req);
+ mdscli_ctx->async_pending++;
+ return;
+}
+
+static void mdscli_connect_fetch_props_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_connect_state *state = tevent_req_data(
+ req, struct mdscli_connect_state);
+ struct mdscli_ctx *mdscli_ctx = state->mdscli_ctx;
+ DALLOC_CTX *d = NULL;
+ sl_array_t *path_scope_array = NULL;
+ char *path_scope = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ status = dcerpc_mdssvc_cmd_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ state->mdscli_ctx->async_pending--;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ d = dalloc_new(state);
+ if (tevent_req_nomem(d, req)) {
+ return;
+ }
+
+ ok = sl_unpack(d,
+ (char *)state->response_blob.spotlight_blob,
+ state->response_blob.length);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ path_scope_array = dalloc_value_for_key(d,
+ "DALLOC_CTX", 0,
+ "kMDSStorePathScopes",
+ "sl_array_t");
+ if (path_scope_array == NULL) {
+ DBG_ERR("Missing kMDSStorePathScopes\n");
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ path_scope = dalloc_get(path_scope_array, "char *", 0);
+ if (path_scope == NULL) {
+ DBG_ERR("Missing path in kMDSStorePathScopes\n");
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ mdscli_ctx->path_scope_len = strlen(path_scope);
+ if (mdscli_ctx->path_scope_len < 1 ||
+ mdscli_ctx->path_scope_len > UINT16_MAX)
+ {
+ DBG_ERR("Bad path_scope: %s\n", path_scope);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ mdscli_ctx->path_scope = talloc_strdup(mdscli_ctx, path_scope);
+ if (tevent_req_nomem(mdscli_ctx->path_scope, req)) {
+ return;
+ }
+
+ if (mdscli_ctx->path_scope[mdscli_ctx->path_scope_len-1] == '/') {
+ mdscli_ctx->path_scope[mdscli_ctx->path_scope_len-1] = '\0';
+ mdscli_ctx->path_scope_len--;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS mdscli_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx **mdscli_ctx)
+{
+ struct mdscli_connect_state *state = tevent_req_data(
+ req, struct mdscli_connect_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *mdscli_ctx = talloc_move(mem_ctx, &state->mdscli_ctx);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_connect(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *bh,
+ const char *share_name,
+ const char *mount_path,
+ struct mdscli_ctx **mdscli_ctx)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_connect_send(frame,
+ ev,
+ bh,
+ share_name,
+ mount_path);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = mdscli_connect_recv(req, mem_ctx, mdscli_ctx);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct mdscli_search_state {
+ struct mdscli_search_ctx *search;
+ struct mdssvc_blob request_blob;
+ struct mdssvc_blob response_blob;
+};
+
+static void mdscli_search_cmd_done(struct tevent_req *subreq);
+
+struct tevent_req *mdscli_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_ctx *mdscli_ctx,
+ const char *mds_query,
+ const char *path_scope_in,
+ bool live)
+{
+ struct tevent_req *req = NULL;
+ struct mdscli_search_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct mdscli_search_ctx *search = NULL;
+ char *path_scope = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(req, &state, struct mdscli_search_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ search = talloc_zero(state, struct mdscli_search_ctx);
+ if (tevent_req_nomem(search, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (path_scope_in[0] == '/') {
+ path_scope = talloc_strdup(search, path_scope_in);
+ } else {
+ path_scope = talloc_asprintf(search,
+ "%s/%s",
+ mdscli_ctx->mdscmd_open.share_path,
+ path_scope_in);
+ }
+ if (tevent_req_nomem(path_scope, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ *search = (struct mdscli_search_ctx) {
+ .mdscli_ctx = mdscli_ctx,
+ .ctx_id = mdscli_new_ctx_id(mdscli_ctx),
+ .unique_id = generate_random_u64(),
+ .live = live,
+ .path_scope = path_scope,
+ .mds_query = talloc_strdup(search, mds_query),
+ };
+ if (tevent_req_nomem(search->mds_query, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ *state = (struct mdscli_search_state) {
+ .search = search,
+ };
+
+ status = mdscli_blob_search(state,
+ search,
+ &state->request_blob);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_mdssvc_cmd_send(state,
+ ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 0,
+ mdscli_ctx->flags,
+ state->request_blob,
+ 0,
+ mdscli_ctx->max_fragment_size,
+ 1,
+ mdscli_ctx->max_fragment_size,
+ 0,
+ 0,
+ &mdscli_ctx->mdscmd_cmd.fragment,
+ &state->response_blob,
+ &mdscli_ctx->mdscmd_cmd.unkn9);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, mdscli_search_cmd_done, req);
+ mdscli_ctx->async_pending++;
+ return req;
+}
+
+static void mdscli_search_cmd_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_search_state *state = tevent_req_data(
+ req, struct mdscli_search_state);
+ DALLOC_CTX *d = NULL;
+ uint64_t *uint64p = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ status = dcerpc_mdssvc_cmd_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ state->search->mdscli_ctx->async_pending--;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ d = dalloc_new(state);
+ if (tevent_req_nomem(d, req)) {
+ return;
+ }
+
+ ok = sl_unpack(d,
+ (char *)state->response_blob.spotlight_blob,
+ state->response_blob.length);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ uint64p = dalloc_get(d,
+ "DALLOC_CTX", 0,
+ "uint64_t", 0);
+ if (uint64p == NULL) {
+ DBG_DEBUG("Unexpected mds response: %s", dalloc_dump(d, 0));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (*uint64p != 0) {
+ DBG_DEBUG("Unexpected mds result: 0x%" PRIx64 "\n", *uint64p);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS mdscli_search_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx **search)
+{
+ struct mdscli_search_state *state = tevent_req_data(
+ req, struct mdscli_search_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *search = talloc_move(mem_ctx, &state->search);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_search(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *mdscli_ctx,
+ const char *mds_query,
+ const char *path_scope,
+ bool live,
+ struct mdscli_search_ctx **search)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (mdscli_ctx->async_pending != 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_search_send(frame,
+ ev,
+ mdscli_ctx,
+ mds_query,
+ path_scope,
+ live);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = mdscli_search_recv(req, mem_ctx, search);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct mdscli_get_results_state {
+ struct tevent_context *ev;
+ struct mdscli_search_ctx *search;
+ struct mdssvc_blob request_blob;
+ struct mdssvc_blob response_fragment;
+ DATA_BLOB response_data;
+ uint64_t *cnids;
+ uint32_t fragment;
+};
+
+static void mdscli_get_results_cmd_done(struct tevent_req *subreq);
+
+struct tevent_req *mdscli_get_results_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_search_ctx *search)
+{
+ struct tevent_req *req = NULL;
+ struct mdscli_get_results_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct mdscli_ctx *mdscli_ctx = search->mdscli_ctx;
+ NTSTATUS status;
+
+ req = tevent_req_create(req, &state, struct mdscli_get_results_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct mdscli_get_results_state) {
+ .ev = ev,
+ .search = search,
+ };
+
+ status = mdscli_blob_get_results(state,
+ search,
+ &state->request_blob);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_mdssvc_cmd_send(state,
+ ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 0,
+ mdscli_ctx->flags,
+ state->request_blob,
+ 0,
+ mdscli_ctx->max_fragment_size,
+ 1,
+ mdscli_ctx->max_fragment_size,
+ 0,
+ 0,
+ &state->fragment,
+ &state->response_fragment,
+ &mdscli_ctx->mdscmd_cmd.unkn9);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, mdscli_get_results_cmd_done, req);
+ mdscli_ctx->async_pending++;
+ return req;
+}
+
+static void mdscli_get_results_cmd_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_get_results_state *state = tevent_req_data(
+ req, struct mdscli_get_results_state);
+ struct mdscli_ctx *mdscli_ctx = state->search->mdscli_ctx;
+ size_t oldsize, newsize;
+ DALLOC_CTX *d = NULL;
+ uint64_t *uint64p = NULL;
+ bool search_in_progress = false;
+ sl_cnids_t *cnids = NULL;
+ size_t ncnids;
+ size_t i;
+ NTSTATUS status;
+ bool ok;
+
+ status = dcerpc_mdssvc_cmd_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ state->search->mdscli_ctx->async_pending--;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ oldsize = state->response_data.length;
+ newsize = oldsize + state->response_fragment.length;
+ if (newsize < oldsize) {
+ tevent_req_nterror(req, NT_STATUS_INTEGER_OVERFLOW);
+ return;
+ }
+
+ ok = data_blob_realloc(state, &state->response_data, newsize);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ (void)memcpy(state->response_data.data + oldsize,
+ state->response_fragment.spotlight_blob,
+ state->response_fragment.length);
+
+ TALLOC_FREE(state->response_fragment.spotlight_blob);
+ state->response_fragment.length = 0;
+ state->response_fragment.size = 0;
+
+ if (state->fragment != 0) {
+ subreq = dcerpc_mdssvc_cmd_send(
+ state,
+ state->ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 1,
+ mdscli_ctx->flags,
+ state->request_blob,
+ 0,
+ mdscli_ctx->max_fragment_size,
+ 1,
+ mdscli_ctx->max_fragment_size,
+ 0,
+ 0,
+ &state->fragment,
+ &state->response_fragment,
+ &mdscli_ctx->mdscmd_cmd.unkn9);
+ if (tevent_req_nomem(subreq, req)) {
+ tevent_req_post(req, state->ev);
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ mdscli_get_results_cmd_done,
+ req);
+ mdscli_ctx->async_pending++;
+ return;
+ }
+
+ d = dalloc_new(state);
+ if (tevent_req_nomem(d, req)) {
+ return;
+ }
+
+ ok = sl_unpack(d,
+ (char *)state->response_data.data,
+ state->response_data.length);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ uint64p = dalloc_get(d,
+ "DALLOC_CTX", 0,
+ "uint64_t", 0);
+ if (uint64p == NULL) {
+ DBG_DEBUG("Unexpected mds response: %s", dalloc_dump(d, 0));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (*uint64p == 35) {
+ DBG_DEBUG("Search in progress\n");
+ search_in_progress = true;
+ }
+
+ cnids = dalloc_get(d, "DALLOC_CTX", 0, "sl_cnids_t", 1);
+ if (cnids == NULL) {
+ DBG_DEBUG("cnids error: %s", dalloc_dump(d, 0));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ ncnids = dalloc_size(cnids->ca_cnids);
+ if (ncnids == 0 && !search_in_progress) {
+ tevent_req_nterror(req, NT_STATUS_NO_MORE_MATCHES);
+ return;
+ }
+
+ if (cnids->ca_unkn1 != 0xadd) {
+ /*
+ * Whatever 0xadd means... but it seems to be the standard value
+ * macOS mdssvc returns here.
+ */
+ DBG_DEBUG("unexpected ca_unkn1: %s", dalloc_dump(d, 0));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (cnids->ca_context != state->search->ctx_id.connection ) {
+ DBG_DEBUG("unexpected ca_context: %s", dalloc_dump(d, 0));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ state->cnids = talloc_zero_array(state, uint64_t, ncnids);
+ if (tevent_req_nomem(state->cnids, req)) {
+ return;
+ }
+
+ for (i = 0; i < ncnids; i++) {
+ uint64_t *cnid = NULL;
+
+ cnid = dalloc_get(cnids->ca_cnids, "uint64_t", i);
+ if (cnid == NULL) {
+ DBG_DEBUG("cnids error: %s", dalloc_dump(d, 0));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ state->cnids[i] = *cnid;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS mdscli_get_results_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint64_t **cnids)
+{
+ struct mdscli_get_results_state *state = tevent_req_data(
+ req, struct mdscli_get_results_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *cnids = talloc_move(mem_ctx, &state->cnids);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_get_results(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ uint64_t **_cnids)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (search->mdscli_ctx->async_pending != 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_get_results_send(frame,
+ ev,
+ search);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = mdscli_get_results_recv(req, mem_ctx, _cnids);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct mdscli_get_path_state {
+ struct mdscli_ctx *mdscli_ctx;
+ struct mdssvc_blob request_blob;
+ struct mdssvc_blob response_blob;
+ char *path;
+};
+
+static void mdscli_get_path_done(struct tevent_req *subreq);
+
+struct tevent_req *mdscli_get_path_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_ctx *mdscli_ctx,
+ uint64_t cnid)
+{
+ struct tevent_req *req = NULL;
+ struct mdscli_get_path_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(req, &state, struct mdscli_get_path_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct mdscli_get_path_state) {
+ .mdscli_ctx = mdscli_ctx,
+ };
+
+ status = mdscli_blob_get_path(state,
+ mdscli_ctx,
+ cnid,
+ &state->request_blob);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_mdssvc_cmd_send(state,
+ ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 0,
+ mdscli_ctx->flags,
+ state->request_blob,
+ 0,
+ mdscli_ctx->max_fragment_size,
+ 1,
+ mdscli_ctx->max_fragment_size,
+ 0,
+ 0,
+ &mdscli_ctx->mdscmd_cmd.fragment,
+ &state->response_blob,
+ &mdscli_ctx->mdscmd_cmd.unkn9);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, mdscli_get_path_done, req);
+ mdscli_ctx->async_pending++;
+ return req;
+}
+
+static void mdscli_get_path_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_get_path_state *state = tevent_req_data(
+ req, struct mdscli_get_path_state);
+ DALLOC_CTX *d = NULL;
+ size_t pathlen;
+ size_t prefixlen;
+ char *path = NULL;
+ const char *p = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ status = dcerpc_mdssvc_cmd_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ state->mdscli_ctx->async_pending--;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ d = dalloc_new(state);
+ if (tevent_req_nomem(d, req)) {
+ return;
+ }
+
+ ok = sl_unpack(d,
+ (char *)state->response_blob.spotlight_blob,
+ state->response_blob.length);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ path = dalloc_get(d,
+ "DALLOC_CTX", 0,
+ "DALLOC_CTX", 2,
+ "DALLOC_CTX", 0,
+ "DALLOC_CTX", 1,
+ "char *", 0);
+ if (path == NULL) {
+ DBG_DEBUG("No path in mds response: %s", dalloc_dump(d, 0));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ /* Path is prefixed by /PATHSCOPE/SHARENAME/, strip it */
+ pathlen = strlen(path);
+
+ /*
+ * path_scope_len and share_path_len are already checked to be smaller
+ * then UINT16_MAX so this can't overflow
+ */
+ prefixlen = state->mdscli_ctx->path_scope_len
+ + state->mdscli_ctx->mdscmd_open.share_path_len;
+
+ if (pathlen < prefixlen) {
+ DBG_DEBUG("Bad path: %s\n", path);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ p = path + prefixlen;
+ while (*p == '/') {
+ p++;
+ }
+ if (*p == '\0') {
+ DBG_DEBUG("Bad path: %s\n", path);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ state->path = talloc_strdup(state, p);
+ if (state->path == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ DBG_DEBUG("path: %s\n", state->path);
+
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS mdscli_get_path_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char **path)
+{
+ struct mdscli_get_path_state *state = tevent_req_data(
+ req, struct mdscli_get_path_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *path = talloc_move(mem_ctx, &state->path);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_get_path(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *mdscli_ctx,
+ uint64_t cnid,
+ char **path)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (mdscli_ctx->async_pending != 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_get_path_send(frame, ev, mdscli_ctx, cnid);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = mdscli_get_path_recv(req, mem_ctx, path);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct mdscli_close_search_state {
+ struct mdscli_search_ctx *search;
+ struct mdssvc_blob request_blob;
+ struct mdssvc_blob response_blob;
+};
+
+static void mdscli_close_search_done(struct tevent_req *subreq);
+
+struct tevent_req *mdscli_close_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_search_ctx **search)
+{
+ struct mdscli_ctx *mdscli_ctx = NULL;
+ struct tevent_req *req = NULL;
+ struct mdscli_close_search_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(req, &state, struct mdscli_close_search_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct mdscli_close_search_state) {
+ .search = talloc_move(state, search),
+ };
+ mdscli_ctx = state->search->mdscli_ctx;
+
+ status = mdscli_blob_close_search(state,
+ state->search,
+ &state->request_blob);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_mdssvc_cmd_send(state,
+ ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 0,
+ mdscli_ctx->flags,
+ state->request_blob,
+ 0,
+ mdscli_ctx->max_fragment_size,
+ 1,
+ mdscli_ctx->max_fragment_size,
+ 0,
+ 0,
+ &mdscli_ctx->mdscmd_cmd.fragment,
+ &state->response_blob,
+ &mdscli_ctx->mdscmd_cmd.unkn9);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, mdscli_close_search_done, req);
+ mdscli_ctx->async_pending++;
+ return req;
+}
+
+static void mdscli_close_search_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_close_search_state *state = tevent_req_data(
+ req, struct mdscli_close_search_state);
+ NTSTATUS status;
+
+ status = dcerpc_mdssvc_cmd_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ state->search->mdscli_ctx->async_pending--;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS mdscli_close_search_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS mdscli_close_search(struct mdscli_search_ctx **search)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if ((*search)->mdscli_ctx->async_pending != 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_close_search_send(frame, ev, search);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = mdscli_close_search_recv(req);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct mdscli_disconnect_state {
+ struct mdscli_ctx *mdscli_ctx;
+};
+
+static void mdscli_disconnect_done(struct tevent_req *subreq);
+
+struct tevent_req *mdscli_disconnect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_ctx *mdscli_ctx)
+{
+ struct tevent_req *req = NULL;
+ struct mdscli_disconnect_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(req, &state, struct mdscli_disconnect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct mdscli_disconnect_state) {
+ .mdscli_ctx = mdscli_ctx,
+ };
+
+ subreq = dcerpc_mdssvc_close_send(state,
+ ev,
+ mdscli_ctx->bh,
+ &mdscli_ctx->ph,
+ 0,
+ mdscli_ctx->dev,
+ mdscli_ctx->mdscmd_open.unkn2,
+ 0,
+ &mdscli_ctx->ph,
+ &mdscli_ctx->mdscmd_close.status);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, mdscli_disconnect_done, req);
+ mdscli_ctx->async_pending++;
+ return req;
+}
+
+static void mdscli_disconnect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mdscli_disconnect_state *state = tevent_req_data(
+ req, struct mdscli_disconnect_state);
+ NTSTATUS status;
+
+ status = dcerpc_mdssvc_close_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ state->mdscli_ctx->async_pending--;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS mdscli_disconnect_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS mdscli_disconnect(struct mdscli_ctx *mdscli_ctx)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (mdscli_ctx->async_pending != 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_disconnect_send(frame, ev, mdscli_ctx);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = mdscli_disconnect_recv(req);
+fail:
+ TALLOC_FREE(frame);
+ return status;
+}
diff --git a/source3/rpc_client/cli_mdssvc.h b/source3/rpc_client/cli_mdssvc.h
new file mode 100644
index 0000000..f8b1582
--- /dev/null
+++ b/source3/rpc_client/cli_mdssvc.h
@@ -0,0 +1,97 @@
+/*
+ Unix SMB/CIFS implementation.
+ mdssvc client functions
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MDSCLI_H_
+#define _MDSCLI_H_
+
+struct mdscli_ctx;
+struct mdscli_search_ctx;
+
+struct mdsctx_id mdscli_new_ctx_id(struct mdscli_ctx *mdscli_ctx);
+char *mdscli_get_basepath(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *mdscli_ctx);
+struct mdscli_ctx *mdscli_search_get_ctx(struct mdscli_search_ctx *search);
+
+struct tevent_req *mdscli_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *bh,
+ const char *share_name,
+ const char *mount_path);
+NTSTATUS mdscli_connect_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx **mdscli_ctx);
+NTSTATUS mdscli_connect(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *bh,
+ const char *share_name,
+ const char *mount_path,
+ struct mdscli_ctx **mdscli_ctx);
+
+struct tevent_req *mdscli_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_ctx *mdscli_ctx,
+ const char *mds_query,
+ const char *path_scope,
+ bool live);
+NTSTATUS mdscli_search_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx **search);
+NTSTATUS mdscli_search(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *mdscli_ctx,
+ const char *mds_query,
+ const char *path_scope,
+ bool live,
+ struct mdscli_search_ctx **search);
+
+struct tevent_req *mdscli_get_results_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_search_ctx *search);
+NTSTATUS mdscli_get_results_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint64_t **cnids);
+NTSTATUS mdscli_get_results(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ uint64_t **_cnids);
+
+struct tevent_req *mdscli_get_path_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_ctx *mdscli_ctx,
+ uint64_t cnid);
+NTSTATUS mdscli_get_path_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char **path);
+NTSTATUS mdscli_get_path(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *mdscli_ctx,
+ uint64_t cnid,
+ char **path);
+
+struct tevent_req *mdscli_close_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_search_ctx **_search);
+NTSTATUS mdscli_close_search_recv(struct tevent_req *req);
+NTSTATUS mdscli_close_search(struct mdscli_search_ctx **search);
+
+struct tevent_req *mdscli_disconnect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mdscli_ctx *mdscli_ctx);
+NTSTATUS mdscli_disconnect_recv(struct tevent_req *req);
+NTSTATUS mdscli_disconnect(struct mdscli_ctx *mdscli_ctx);
+
+#endif /* _MDSCLI_H_ */
diff --git a/source3/rpc_client/cli_mdssvc_private.h b/source3/rpc_client/cli_mdssvc_private.h
new file mode 100644
index 0000000..77f300c
--- /dev/null
+++ b/source3/rpc_client/cli_mdssvc_private.h
@@ -0,0 +1,74 @@
+/*
+ Unix SMB/CIFS implementation.
+ mdssvc client functions
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MDSCLI_PRIVATE_H_
+#define _MDSCLI_PRIVATE_H_
+
+struct mdsctx_id {
+ uint64_t id;
+ uint64_t connection;
+};
+
+struct mdscli_ctx {
+ uint64_t async_pending;
+
+ struct dcerpc_binding_handle *bh;
+ struct policy_handle ph;
+
+ struct mdsctx_id ctx_id;
+ size_t max_fragment_size;
+
+ /* Known fields used across multiple commands */
+ uint32_t dev;
+ uint32_t flags;
+
+ /* cmd specific or unknown fields */
+ struct {
+ char share_path[1025];
+ size_t share_path_len;
+ uint32_t unkn2;
+ uint32_t unkn3;
+ } mdscmd_open;
+ struct {
+ uint32_t status;
+ uint32_t unkn7;
+ } mdscmd_unknown1;
+ struct {
+ uint32_t fragment;
+ uint32_t unkn9;
+ } mdscmd_cmd;
+ struct {
+ uint32_t status;
+ } mdscmd_close;
+
+ char *path_scope;
+ size_t path_scope_len;
+};
+
+struct mdscli_search_ctx {
+ struct mdscli_ctx *mdscli_ctx;
+ struct mdsctx_id ctx_id;
+ uint64_t unique_id;
+ bool live;
+ char *path_scope;
+ char *mds_query;
+};
+
+#endif /* _MDSCLI_PRIVATE_H_ */
diff --git a/source3/rpc_client/cli_mdssvc_util.c b/source3/rpc_client/cli_mdssvc_util.c
new file mode 100644
index 0000000..1eaaca7
--- /dev/null
+++ b/source3/rpc_client/cli_mdssvc_util.c
@@ -0,0 +1,551 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight client functions
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_client.h"
+#include "librpc/gen_ndr/mdssvc.h"
+#include "cli_mdssvc.h"
+#include "cli_mdssvc_private.h"
+#include "cli_mdssvc_util.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "rpc_server/mdssvc/dalloc.h"
+#include "rpc_server/mdssvc/marshalling.h"
+
+NTSTATUS mdscli_blob_fetch_props(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *ctx,
+ struct mdssvc_blob *blob)
+{
+ DALLOC_CTX *d = NULL;
+ uint64_t *uint64p = NULL;
+ sl_array_t *array = NULL;
+ sl_array_t *cmd_array = NULL;
+ NTSTATUS status;
+ int ret;
+
+ d = dalloc_new(mem_ctx);
+ if (d == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ array = dalloc_zero(d, sl_array_t);
+ if (array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(d, array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cmd_array = dalloc_zero(d, sl_array_t);
+ if (cmd_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, cmd_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(cmd_array, "fetchPropertiesForContext:");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uint64p = talloc_zero_array(cmd_array, uint64_t, 2);
+ if (uint64p == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_set_name(uint64p, "uint64_t *");
+
+ ret = dalloc_add(cmd_array, uint64p, uint64_t *);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = sl_pack_alloc(mem_ctx, d, blob, ctx->max_fragment_size);
+ TALLOC_FREE(d);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_blob_search(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ struct mdssvc_blob *blob)
+{
+ struct mdscli_ctx *ctx = search->mdscli_ctx;
+ DALLOC_CTX *d = NULL;
+ uint64_t *uint64p = NULL;
+ sl_array_t *array = NULL;
+ sl_array_t *cmd_array = NULL;
+ sl_dict_t *query_dict = NULL;
+ sl_array_t *attr_array = NULL;
+ sl_array_t *scope_array = NULL;
+ double dval;
+ uint64_t uint64val;
+ NTSTATUS status;
+ int ret;
+
+ d = dalloc_new(mem_ctx);
+ if (d == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ array = dalloc_zero(d, sl_array_t);
+ if (array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(d, array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cmd_array = dalloc_zero(d, sl_array_t);
+ if (cmd_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, cmd_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(cmd_array, "openQueryWithParams:forContext:");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uint64p = talloc_zero_array(cmd_array, uint64_t, 2);
+ if (uint64p == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_set_name(uint64p, "uint64_t *");
+
+ uint64p[0] = search->ctx_id.id;
+ uint64p[1] = search->ctx_id.connection;
+
+ ret = dalloc_add(cmd_array, uint64p, uint64_t *);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ query_dict = dalloc_zero(array, sl_dict_t);
+ if (query_dict == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, query_dict, sl_dict_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(query_dict, "kMDQueryBatchFirstDelay");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ dval = 1;
+ ret = dalloc_add_copy(query_dict, &dval, double);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(query_dict, "kMDQueryUniqueId");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = dalloc_add_copy(query_dict, &search->unique_id, uint64_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(query_dict, "kMDAttributeArray");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ attr_array = dalloc_zero(query_dict, sl_array_t);
+ if (attr_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = dalloc_add(query_dict, attr_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = dalloc_stradd(attr_array, "kMDItemFSName");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(query_dict, "kMDQueryBatchFirstCount");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ uint64val = 10;
+ ret = dalloc_add_copy(query_dict, &uint64val, uint64_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(query_dict, "kMDQueryBatchUpdateCount");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ uint64val = 100;
+ ret = dalloc_add_copy(query_dict, &uint64val, uint64_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(query_dict, "kMDQueryString");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = dalloc_stradd(query_dict, search->mds_query);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(query_dict, "kMDScopeArray");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ scope_array = dalloc_zero(query_dict, sl_array_t);
+ if (scope_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = dalloc_add(query_dict, scope_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = dalloc_stradd(scope_array, search->path_scope);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = sl_pack_alloc(mem_ctx, d, blob, ctx->max_fragment_size);
+ TALLOC_FREE(d);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_blob_get_results(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ struct mdssvc_blob *blob)
+{
+ struct mdscli_ctx *ctx = search->mdscli_ctx;
+ DALLOC_CTX *d = NULL;
+ uint64_t *uint64p = NULL;
+ sl_array_t *array = NULL;
+ sl_array_t *cmd_array = NULL;
+ NTSTATUS status;
+ int ret;
+
+ d = dalloc_new(mem_ctx);
+ if (d == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ array = dalloc_zero(d, sl_array_t);
+ if (array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(d, array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cmd_array = dalloc_zero(d, sl_array_t);
+ if (cmd_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, cmd_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(cmd_array, "fetchQueryResultsForContext:");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uint64p = talloc_zero_array(cmd_array, uint64_t, 2);
+ if (uint64p == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_set_name(uint64p, "uint64_t *");
+
+ uint64p[0] = search->ctx_id.id;
+ uint64p[1] = search->ctx_id.connection;
+
+ ret = dalloc_add(cmd_array, uint64p, uint64_t *);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = sl_pack_alloc(mem_ctx, d, blob, ctx->max_fragment_size);
+ TALLOC_FREE(d);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_blob_get_path(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *ctx,
+ uint64_t cnid,
+ struct mdssvc_blob *blob)
+{
+ struct mdsctx_id ctx_id = mdscli_new_ctx_id(ctx);
+ DALLOC_CTX *d = NULL;
+ uint64_t *uint64var = NULL;
+ sl_array_t *array = NULL;
+ sl_array_t *cmd_array = NULL;
+ sl_array_t *attr_array = NULL;
+ sl_cnids_t *cnids = NULL;
+ NTSTATUS status;
+ int ret;
+
+ d = dalloc_new(mem_ctx);
+ if (d == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ array = dalloc_zero(d, sl_array_t);
+ if (array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(d, array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cmd_array = dalloc_zero(d, sl_array_t);
+ if (cmd_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, cmd_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(cmd_array, "fetchAttributes:forOIDArray:context:");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uint64var = talloc_zero_array(cmd_array, uint64_t, 2);
+ if (uint64var == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_set_name(uint64var, "uint64_t *");
+
+ uint64var[0] = ctx_id.id;
+ uint64var[1] = 0;
+
+ ret = dalloc_add(cmd_array, &uint64var[0], uint64_t *);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ attr_array = dalloc_zero(d, sl_array_t);
+ if (attr_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, attr_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(attr_array, "kMDItemPath");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* CNIDs */
+ cnids = talloc_zero(array, sl_cnids_t);
+ if (cnids == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cnids->ca_cnids = dalloc_new(cnids);
+ if (cnids->ca_cnids == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cnids->ca_unkn1 = 0xadd;
+ cnids->ca_context = 0x6b000020;
+
+ ret = dalloc_add_copy(cnids->ca_cnids, &cnid, uint64_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, cnids, sl_cnids_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = sl_pack_alloc(mem_ctx, d, blob, ctx->max_fragment_size);
+ TALLOC_FREE(d);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS mdscli_blob_close_search(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ struct mdssvc_blob *blob)
+{
+ struct mdscli_ctx *ctx = search->mdscli_ctx;
+ DALLOC_CTX *d = NULL;
+ uint64_t *uint64p = NULL;
+ sl_array_t *array = NULL;
+ sl_array_t *cmd_array = NULL;
+ NTSTATUS status;
+ int ret;
+
+ d = dalloc_new(mem_ctx);
+ if (d == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ array = dalloc_zero(d, sl_array_t);
+ if (array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(d, array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cmd_array = dalloc_zero(d, sl_array_t);
+ if (cmd_array == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_add(array, cmd_array, sl_array_t);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = dalloc_stradd(cmd_array, "closeQueryForContext:");
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uint64p = talloc_zero_array(cmd_array, uint64_t, 2);
+ if (uint64p == NULL) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ talloc_set_name(uint64p, "uint64_t *");
+
+ uint64p[0] = search->ctx_id.id;
+ uint64p[1] = search->ctx_id.connection;
+
+ ret = dalloc_add(cmd_array, uint64p, uint64_t *);
+ if (ret != 0) {
+ TALLOC_FREE(d);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = sl_pack_alloc(mem_ctx, d, blob, ctx->max_fragment_size);
+ TALLOC_FREE(d);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/cli_mdssvc_util.h b/source3/rpc_client/cli_mdssvc_util.h
new file mode 100644
index 0000000..3f32475
--- /dev/null
+++ b/source3/rpc_client/cli_mdssvc_util.h
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight client functions
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MDSCLI_UTIL_H_
+#define _MDSCLI_UTIL_H_
+
+NTSTATUS mdscli_blob_fetch_props(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *ctx,
+ struct mdssvc_blob *blob);
+
+NTSTATUS mdscli_blob_search(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ struct mdssvc_blob *blob);
+
+NTSTATUS mdscli_blob_get_results(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ struct mdssvc_blob *blob);
+
+NTSTATUS mdscli_blob_get_path(TALLOC_CTX *mem_ctx,
+ struct mdscli_ctx *mdscli_ctx,
+ uint64_t cnid,
+ struct mdssvc_blob *blob);
+
+NTSTATUS mdscli_blob_close_search(TALLOC_CTX *mem_ctx,
+ struct mdscli_search_ctx *search,
+ struct mdssvc_blob *blob);
+
+#endif /* _MDSCLI_UTIL_H_ */
diff --git a/source3/rpc_client/cli_netlogon.c b/source3/rpc_client/cli_netlogon.c
new file mode 100644
index 0000000..762802d
--- /dev/null
+++ b/source3/rpc_client/cli_netlogon.c
@@ -0,0 +1,860 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT Domain Authentication SMB / MSRPC client
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 1998.
+ Largely re-written by Jeremy Allison (C) 2005.
+ Copyright (C) Guenther Deschner 2008.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "libsmb/libsmb.h"
+#include "rpc_client/rpc_client.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../libcli/auth/netlogon_creds_cli.h"
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "../librpc/gen_ndr/schannel.h"
+#include "rpc_client/cli_netlogon.h"
+#include "rpc_client/util_netlogon.h"
+#include "../libcli/security/security.h"
+#include "lib/param/param.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "util_tdb.h"
+#include "lib/crypto/gnutls_helpers.h"
+
+
+NTSTATUS rpccli_pre_open_netlogon_creds(void)
+{
+ static bool already_open = false;
+ TALLOC_CTX *frame;
+ struct loadparm_context *lp_ctx;
+ char *fname;
+ struct db_context *global_db;
+ NTSTATUS status;
+
+ if (already_open) {
+ return NT_STATUS_OK;
+ }
+
+ frame = talloc_stackframe();
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fname = lpcfg_private_db_path(frame, lp_ctx, "netlogon_creds_cli");
+ if (fname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ global_db = db_open(frame, fname,
+ 0, TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
+ O_RDWR|O_CREAT, 0600, DBWRAP_LOCK_ORDER_2,
+ DBWRAP_FLAG_OPTIMIZE_READONLY_ACCESS);
+ if (global_db == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = netlogon_creds_cli_set_global_db(lp_ctx, &global_db);
+ TALLOC_FREE(frame);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ already_open = true;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS rpccli_create_netlogon_creds(
+ const char *server_computer,
+ const char *server_netbios_domain,
+ const char *server_dns_domain,
+ const char *client_account,
+ enum netr_SchannelType sec_chan_type,
+ struct messaging_context *msg_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct netlogon_creds_cli_context **netlogon_creds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx;
+ NTSTATUS status;
+
+ status = rpccli_pre_open_netlogon_creds();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = netlogon_creds_cli_context_global(lp_ctx,
+ msg_ctx,
+ client_account,
+ sec_chan_type,
+ server_computer,
+ server_netbios_domain,
+ server_dns_domain,
+ mem_ctx, netlogon_creds);
+ TALLOC_FREE(frame);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpccli_create_netlogon_creds_ctx(
+ struct cli_credentials *creds,
+ const char *server_computer,
+ struct messaging_context *msg_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct netlogon_creds_cli_context **creds_ctx)
+{
+ enum netr_SchannelType sec_chan_type;
+ const char *server_netbios_domain;
+ const char *server_dns_domain;
+ const char *client_account;
+
+ sec_chan_type = cli_credentials_get_secure_channel_type(creds);
+ client_account = cli_credentials_get_username(creds);
+ server_netbios_domain = cli_credentials_get_domain(creds);
+ server_dns_domain = cli_credentials_get_realm(creds);
+
+ return rpccli_create_netlogon_creds(server_computer,
+ server_netbios_domain,
+ server_dns_domain,
+ client_account,
+ sec_chan_type,
+ msg_ctx, mem_ctx,
+ creds_ctx);
+}
+
+NTSTATUS rpccli_setup_netlogon_creds_locked(
+ struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *creds_ctx,
+ bool force_reauth,
+ struct cli_credentials *cli_creds,
+ uint32_t *negotiate_flags)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ uint8_t num_nt_hashes = 0;
+ const struct samr_Password *nt_hashes[2] = { NULL, NULL };
+ uint8_t idx_nt_hashes = 0;
+ NTSTATUS status;
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+
+ status = netlogon_creds_cli_get(creds_ctx, frame, &creds);
+ if (NT_STATUS_IS_OK(status)) {
+ const char *action = "using";
+
+ if (force_reauth) {
+ action = "overwrite";
+ }
+
+ if (cli != NULL) {
+ remote_name = smbXcli_conn_remote_name(cli->conn);
+ } else {
+ remote_name = "<UNKNOWN>";
+ }
+
+ DEBUG(5,("%s: %s cached netlogon_creds cli[%s/%s] to %s\n",
+ __FUNCTION__, action,
+ creds->account_name, creds->computer_name,
+ remote_name));
+ if (!force_reauth) {
+ goto done;
+ }
+ TALLOC_FREE(creds);
+ }
+
+ nt_hashes[0] = cli_credentials_get_nt_hash(cli_creds, talloc_tos());
+ if (nt_hashes[0] == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ num_nt_hashes = 1;
+
+ nt_hashes[1] = cli_credentials_get_old_nt_hash(cli_creds,
+ talloc_tos());
+ if (nt_hashes[1] != NULL) {
+ num_nt_hashes = 2;
+ }
+
+ remote_name = smbXcli_conn_remote_name(cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(cli->conn);
+
+ status = cli_rpc_pipe_open_noauth_transport(cli,
+ transport,
+ &ndr_table_netlogon,
+ remote_name,
+ remote_sockaddr,
+ &netlogon_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("%s: failed to open noauth netlogon connection to %s - %s\n",
+ __FUNCTION__,
+ remote_name,
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+ talloc_steal(frame, netlogon_pipe);
+
+ status = netlogon_creds_cli_auth(creds_ctx,
+ netlogon_pipe->binding_handle,
+ num_nt_hashes,
+ nt_hashes,
+ &idx_nt_hashes);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_get(creds_ctx, frame, &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ DEBUG(5,("%s: using new netlogon_creds cli[%s/%s] to %s\n",
+ __FUNCTION__,
+ creds->account_name, creds->computer_name,
+ remote_name));
+
+done:
+ if (negotiate_flags != NULL) {
+ *negotiate_flags = creds->negotiate_flags;
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpccli_setup_netlogon_creds(
+ struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *creds_ctx,
+ bool force_reauth,
+ struct cli_credentials *cli_creds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct netlogon_creds_cli_lck *lck;
+ NTSTATUS status;
+
+ status = netlogon_creds_cli_lck(
+ creds_ctx, NETLOGON_CREDS_CLI_LCK_EXCLUSIVE,
+ frame, &lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("netlogon_creds_cli_lck failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_setup_netlogon_creds_locked(
+ cli, transport, creds_ctx, force_reauth, cli_creds, NULL);
+
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+NTSTATUS rpccli_connect_netlogon(
+ struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *creds_ctx,
+ bool force_reauth,
+ struct cli_credentials *trust_creds,
+ struct rpc_pipe_client **_rpccli)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct netlogon_creds_CredentialState *creds = NULL;
+ enum netlogon_creds_cli_lck_type lck_type;
+ enum netr_SchannelType sec_chan_type;
+ struct netlogon_creds_cli_lck *lck = NULL;
+ uint32_t negotiate_flags;
+ uint8_t found_session_key[16] = {0};
+ bool found_existing_creds = false;
+ bool do_serverauth;
+ struct rpc_pipe_client *rpccli;
+ NTSTATUS status;
+ bool retry = false;
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+
+ sec_chan_type = cli_credentials_get_secure_channel_type(trust_creds);
+ if (sec_chan_type == SEC_CHAN_NULL) {
+ DBG_ERR("secure_channel_type gave SEC_CHAN_NULL\n");
+ status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto fail;
+ }
+
+again:
+
+ /*
+ * See whether we can use existing netlogon_creds or
+ * whether we have to serverauthenticate.
+ */
+ status = netlogon_creds_cli_get(creds_ctx, frame, &creds);
+
+ if (NT_STATUS_IS_OK(status)) {
+ bool cmp = mem_equal_const_time(found_session_key,
+ creds->session_key,
+ sizeof(found_session_key));
+ found_existing_creds = !cmp;
+
+ memcpy(found_session_key,
+ creds->session_key,
+ sizeof(found_session_key));
+
+ TALLOC_FREE(creds);
+ }
+
+ lck_type = (force_reauth || !found_existing_creds) ?
+ NETLOGON_CREDS_CLI_LCK_EXCLUSIVE :
+ NETLOGON_CREDS_CLI_LCK_SHARED;
+
+ status = netlogon_creds_cli_lck(creds_ctx, lck_type, frame, &lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("netlogon_creds_cli_lck failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!found_existing_creds) {
+ /*
+ * Try to find creds under the lock again. Someone
+ * else might have done it for us.
+ */
+ status = netlogon_creds_cli_get(creds_ctx, frame, &creds);
+
+ if (NT_STATUS_IS_OK(status)) {
+ bool cmp = mem_equal_const_time(found_session_key,
+ creds->session_key,
+ sizeof(found_session_key));
+ found_existing_creds = !cmp;
+
+ memcpy(found_session_key, creds->session_key,
+ sizeof(found_session_key));
+
+ TALLOC_FREE(creds);
+ }
+ }
+
+ remote_name = smbXcli_conn_remote_name(cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(cli->conn);
+
+ do_serverauth = force_reauth || !found_existing_creds;
+
+ if (!do_serverauth) {
+ /*
+ * Do the quick schannel bind without a reauth
+ */
+ status = cli_rpc_pipe_open_bind_schannel(cli,
+ &ndr_table_netlogon,
+ transport,
+ creds_ctx,
+ remote_name,
+ remote_sockaddr,
+ &rpccli);
+ if (!retry && NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ DBG_DEBUG("Retrying with serverauthenticate\n");
+ TALLOC_FREE(lck);
+ retry = true;
+ goto again;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("cli_rpc_pipe_open_bind_schannel "
+ "failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+ goto done;
+ }
+
+ if (cli_credentials_is_anonymous(trust_creds)) {
+ DBG_WARNING("get_trust_credential for %s only gave anonymous,"
+ "unable to negotiate NETLOGON credentials\n",
+ netlogon_creds_cli_debug_string(
+ creds_ctx, frame));
+ status = NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ goto fail;
+ }
+
+ status = rpccli_setup_netlogon_creds_locked(
+ cli, transport, creds_ctx, true, trust_creds,
+ &negotiate_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpccli_setup_netlogon_creds failed for %s, "
+ "unable to setup NETLOGON credentials: %s\n",
+ netlogon_creds_cli_debug_string(
+ creds_ctx, frame),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!(negotiate_flags & NETLOGON_NEG_AUTHENTICATED_RPC)) {
+ if (lp_winbind_sealed_pipes() || lp_require_strong_key()) {
+ status = NT_STATUS_DOWNGRADE_DETECTED;
+ DBG_WARNING("Unwilling to make connection to %s"
+ "without connection level security, "
+ "must set 'winbind sealed pipes = false'"
+ " and 'require strong key = false' "
+ "to proceed: %s\n",
+ netlogon_creds_cli_debug_string(
+ creds_ctx, frame),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_rpc_pipe_open_noauth_transport(cli,
+ transport,
+ &ndr_table_netlogon,
+ remote_name,
+ remote_sockaddr,
+ &rpccli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("cli_rpc_pipe_open_noauth_transport "
+ "failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+ goto done;
+ }
+
+ status = cli_rpc_pipe_open_bind_schannel(cli,
+ &ndr_table_netlogon,
+ transport,
+ creds_ctx,
+ remote_name,
+ remote_sockaddr,
+ &rpccli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("cli_rpc_pipe_open_bind_schannel "
+ "failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = netlogon_creds_cli_check(creds_ctx, rpccli->binding_handle,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("netlogon_creds_cli_check failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+done:
+ *_rpccli = rpccli;
+ status = NT_STATUS_OK;
+fail:
+ ZERO_STRUCT(found_session_key);
+ TALLOC_FREE(lck);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/* Logon domain user */
+
+NTSTATUS rpccli_netlogon_password_logon(
+ struct netlogon_creds_cli_context *creds_ctx,
+ struct dcerpc_binding_handle *binding_handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *domain,
+ const char *username,
+ const char *password,
+ const char *workstation,
+ const uint64_t logon_id,
+ enum netr_LogonInfoClass logon_type,
+ uint8_t *authoritative,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ union netr_LogonLevel *logon;
+ uint16_t validation_level = 0;
+ union netr_Validation *validation = NULL;
+ char *workstation_slash = NULL;
+
+ unsigned char local_nt_response[24];
+ unsigned char local_lm_response[24];
+ struct samr_Password lmpassword = {.hash = {0}};
+ struct samr_Password ntpassword = {.hash = {0}};
+ struct netr_ChallengeResponse lm = {0};
+ struct netr_ChallengeResponse nt = {0};
+
+ logon = talloc_zero(frame, union netr_LogonLevel);
+ if (logon == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (workstation == NULL) {
+ workstation = lp_netbios_name();
+ }
+
+ workstation_slash = talloc_asprintf(frame, "\\\\%s", workstation);
+ if (workstation_slash == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Initialise input parameters */
+
+ switch (logon_type) {
+ case NetlogonInteractiveInformation:
+ case NetlogonInteractiveTransitiveInformation: {
+
+ struct netr_PasswordInfo *password_info;
+
+
+ password_info = talloc_zero(frame, struct netr_PasswordInfo);
+ if (password_info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_lm_owf_gen(password, ntpassword.hash, lmpassword.hash);
+
+ password_info->identity_info.domain_name.string = domain;
+ password_info->identity_info.parameter_control = logon_parameters;
+ password_info->identity_info.logon_id = logon_id;
+ password_info->identity_info.account_name.string = username;
+ password_info->identity_info.workstation.string = workstation_slash;
+
+ password_info->lmpassword = lmpassword;
+ password_info->ntpassword = ntpassword;
+
+ logon->password = password_info;
+
+ break;
+ }
+ case NetlogonNetworkInformation:
+ case NetlogonNetworkTransitiveInformation: {
+ struct netr_NetworkInfo *network_info;
+ uint8_t chal[8];
+ int rc;
+
+ ZERO_STRUCT(lm);
+ ZERO_STRUCT(nt);
+
+ network_info = talloc_zero(frame, struct netr_NetworkInfo);
+ if (network_info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ generate_random_buffer(chal, 8);
+
+ SMBencrypt(password, chal, local_lm_response);
+ rc = SMBNTencrypt(password, chal, local_nt_response);
+ if (rc != 0) {
+ TALLOC_FREE(frame);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+
+ lm.length = 24;
+ lm.data = local_lm_response;
+
+ nt.length = 24;
+ nt.data = local_nt_response;
+
+ network_info->identity_info.domain_name.string = domain;
+ network_info->identity_info.parameter_control = logon_parameters;
+ network_info->identity_info.logon_id = logon_id;
+ network_info->identity_info.account_name.string = username;
+ network_info->identity_info.workstation.string = workstation_slash;
+
+ memcpy(network_info->challenge, chal, 8);
+ network_info->nt = nt;
+ network_info->lm = lm;
+
+ logon->network = network_info;
+
+ break;
+ }
+ default:
+ DEBUG(0, ("switch value %d not supported\n",
+ logon_type));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ status = netlogon_creds_cli_LogonSamLogon(creds_ctx,
+ binding_handle,
+ logon_type,
+ logon,
+ mem_ctx,
+ &validation_level,
+ &validation,
+ authoritative,
+ flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+ *_validation_level = validation_level;
+ *_validation = validation;
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Logon domain user with an 'network' SAM logon
+ *
+ * @param info3 Pointer to a NET_USER_INFO_3 already allocated by the caller.
+ **/
+
+
+NTSTATUS rpccli_netlogon_network_logon(
+ struct netlogon_creds_cli_context *creds_ctx,
+ struct dcerpc_binding_handle *binding_handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *username,
+ const char *domain,
+ const char *workstation,
+ const uint64_t logon_id,
+ DATA_BLOB chal,
+ DATA_BLOB lm_response,
+ DATA_BLOB nt_response,
+ enum netr_LogonInfoClass logon_type,
+ uint8_t *authoritative,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ NTSTATUS status;
+ const char *workstation_name_slash;
+ union netr_LogonLevel *logon = NULL;
+ struct netr_NetworkInfo *network_info;
+ uint16_t validation_level = 0;
+ union netr_Validation *validation = NULL;
+ struct netr_ChallengeResponse lm;
+ struct netr_ChallengeResponse nt;
+
+ *_validation = NULL;
+
+ ZERO_STRUCT(lm);
+ ZERO_STRUCT(nt);
+
+ switch (logon_type) {
+ case NetlogonNetworkInformation:
+ case NetlogonNetworkTransitiveInformation:
+ break;
+ default:
+ DEBUG(0, ("switch value %d not supported\n",
+ logon_type));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ logon = talloc_zero(mem_ctx, union netr_LogonLevel);
+ if (!logon) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ network_info = talloc_zero(mem_ctx, struct netr_NetworkInfo);
+ if (!network_info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (workstation == NULL) {
+ workstation = lp_netbios_name();
+ }
+
+ if (workstation[0] != '\\' && workstation[1] != '\\') {
+ workstation_name_slash = talloc_asprintf(mem_ctx, "\\\\%s", workstation);
+ } else {
+ workstation_name_slash = workstation;
+ }
+
+ if (!workstation_name_slash) {
+ DEBUG(0, ("talloc_asprintf failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Initialise input parameters */
+
+ lm.data = lm_response.data;
+ lm.length = lm_response.length;
+ nt.data = nt_response.data;
+ nt.length = nt_response.length;
+
+ network_info->identity_info.domain_name.string = domain;
+ network_info->identity_info.parameter_control = logon_parameters;
+ network_info->identity_info.logon_id = logon_id;
+ network_info->identity_info.account_name.string = username;
+ network_info->identity_info.workstation.string = workstation_name_slash;
+
+ if (chal.length != 8) {
+ DBG_WARNING("Invalid challenge length %zd\n", chal.length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ memcpy(network_info->challenge, chal.data, chal.length);
+ network_info->nt = nt;
+ network_info->lm = lm;
+
+ logon->network = network_info;
+
+ /* Marshall data and send request */
+
+ status = netlogon_creds_cli_LogonSamLogon(creds_ctx,
+ binding_handle,
+ logon_type,
+ logon,
+ mem_ctx,
+ &validation_level,
+ &validation,
+ authoritative,
+ flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *_validation_level = validation_level;
+ *_validation = validation;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpccli_netlogon_interactive_logon(
+ struct netlogon_creds_cli_context *creds_ctx,
+ struct dcerpc_binding_handle *binding_handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *username,
+ const char *domain,
+ const char *workstation,
+ const uint64_t logon_id,
+ DATA_BLOB lm_hash,
+ DATA_BLOB nt_hash,
+ enum netr_LogonInfoClass logon_type,
+ uint8_t *authoritative,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ const char *workstation_name_slash;
+ union netr_LogonLevel *logon = NULL;
+ struct netr_PasswordInfo *password_info = NULL;
+ uint16_t validation_level = 0;
+ union netr_Validation *validation = NULL;
+ struct netr_ChallengeResponse lm;
+ struct netr_ChallengeResponse nt;
+
+ *_validation = NULL;
+
+ ZERO_STRUCT(lm);
+ ZERO_STRUCT(nt);
+
+ switch (logon_type) {
+ case NetlogonInteractiveInformation:
+ case NetlogonInteractiveTransitiveInformation:
+ break;
+ default:
+ DEBUG(0, ("switch value %d not supported\n",
+ logon_type));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ logon = talloc_zero(mem_ctx, union netr_LogonLevel);
+ if (logon == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ password_info = talloc_zero(logon, struct netr_PasswordInfo);
+ if (password_info == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (workstation[0] != '\\' && workstation[1] != '\\') {
+ workstation_name_slash = talloc_asprintf(frame, "\\\\%s", workstation);
+ } else {
+ workstation_name_slash = workstation;
+ }
+
+ if (workstation_name_slash == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Initialise input parameters */
+
+ password_info->identity_info.domain_name.string = domain;
+ password_info->identity_info.parameter_control = logon_parameters;
+ password_info->identity_info.logon_id = logon_id;
+ password_info->identity_info.account_name.string = username;
+ password_info->identity_info.workstation.string = workstation_name_slash;
+
+ if (nt_hash.length != sizeof(password_info->ntpassword.hash)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ memcpy(password_info->ntpassword.hash, nt_hash.data, nt_hash.length);
+ if (lm_hash.length != 0) {
+ if (lm_hash.length != sizeof(password_info->lmpassword.hash)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ memcpy(password_info->lmpassword.hash, lm_hash.data, lm_hash.length);
+ }
+
+ logon->password = password_info;
+
+ /* Marshall data and send request */
+
+ status = netlogon_creds_cli_LogonSamLogon(creds_ctx,
+ binding_handle,
+ logon_type,
+ logon,
+ mem_ctx,
+ &validation_level,
+ &validation,
+ authoritative,
+ flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ *_validation_level = validation_level;
+ *_validation = validation;
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/cli_netlogon.h b/source3/rpc_client/cli_netlogon.h
new file mode 100644
index 0000000..4644925
--- /dev/null
+++ b/source3/rpc_client/cli_netlogon.h
@@ -0,0 +1,111 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT Domain Authentication SMB / MSRPC client
+ Copyright (C) Andrew Tridgell 1992-2000
+ Copyright (C) Jeremy Allison 1998.
+ Largely re-written by Jeremy Allison (C) 2005.
+ Copyright (C) Guenther Deschner 2008.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _RPC_CLIENT_CLI_NETLOGON_H_
+#define _RPC_CLIENT_CLI_NETLOGON_H_
+
+struct cli_state;
+struct messaging_context;
+struct cli_credentials;
+struct netlogon_creds_cli_context;
+struct dcerpc_binding_handle;
+#include "librpc/rpc/rpc_common.h"
+
+/* The following definitions come from rpc_client/cli_netlogon.c */
+
+NTSTATUS rpccli_pre_open_netlogon_creds(void);
+NTSTATUS rpccli_create_netlogon_creds_ctx(
+ struct cli_credentials *creds,
+ const char *server_computer,
+ struct messaging_context *msg_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct netlogon_creds_cli_context **creds_ctx);
+NTSTATUS rpccli_setup_netlogon_creds_locked(
+ struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *creds_ctx,
+ bool force_reauth,
+ struct cli_credentials *cli_creds,
+ uint32_t *negotiate_flags);
+NTSTATUS rpccli_setup_netlogon_creds(
+ struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *creds_ctx,
+ bool force_reauth,
+ struct cli_credentials *cli_creds);
+NTSTATUS rpccli_connect_netlogon(
+ struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *creds_ctx,
+ bool force_reauth,
+ struct cli_credentials *trust_creds,
+ struct rpc_pipe_client **_rpccli);
+NTSTATUS rpccli_netlogon_password_logon(
+ struct netlogon_creds_cli_context *creds,
+ struct dcerpc_binding_handle *binding_handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *domain,
+ const char *username,
+ const char *password,
+ const char *workstation,
+ const uint64_t logon_id,
+ enum netr_LogonInfoClass logon_type,
+ uint8_t *authoritative,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation);
+NTSTATUS rpccli_netlogon_network_logon(
+ struct netlogon_creds_cli_context *creds_ctx,
+ struct dcerpc_binding_handle *binding_handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *username,
+ const char *domain,
+ const char *workstation,
+ const uint64_t logon_id,
+ DATA_BLOB chal,
+ DATA_BLOB lm_response,
+ DATA_BLOB nt_response,
+ enum netr_LogonInfoClass logon_type,
+ uint8_t *authoritative,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation);
+NTSTATUS rpccli_netlogon_interactive_logon(
+ struct netlogon_creds_cli_context *creds_ctx,
+ struct dcerpc_binding_handle *binding_handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *username,
+ const char *domain,
+ const char *workstation,
+ const uint64_t logon_id,
+ DATA_BLOB lm_hash,
+ DATA_BLOB nt_hash,
+ enum netr_LogonInfoClass logon_type,
+ uint8_t *authoritative,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation);
+
+#endif /* _RPC_CLIENT_CLI_NETLOGON_H_ */
diff --git a/source3/rpc_client/cli_pipe.c b/source3/rpc_client/cli_pipe.c
new file mode 100644
index 0000000..b4289e9
--- /dev/null
+++ b/source3/rpc_client/cli_pipe.c
@@ -0,0 +1,3677 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client routines
+ * Largely rewritten by Jeremy Allison 2005.
+ * Heavily modified by Simo Sorce 2010.
+ * Copyright Andrew Bartlett 2011.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "librpc/gen_ndr/ndr_epmapper_c.h"
+#include "../librpc/gen_ndr/ndr_dssetup.h"
+#include "../libcli/auth/schannel.h"
+#include "../libcli/auth/netlogon_creds_cli.h"
+#include "auth_generic.h"
+#include "librpc/gen_ndr/ndr_dcerpc.h"
+#include "librpc/gen_ndr/ndr_netlogon_c.h"
+#include "librpc/gen_ndr/auth.h"
+#include "librpc/rpc/dcerpc.h"
+#include "librpc/rpc/dcerpc_util.h"
+#include "rpc_dce.h"
+#include "cli_pipe.h"
+#include "libsmb/libsmb.h"
+#include "auth/gensec/gensec.h"
+#include "auth/credentials/credentials.h"
+#include "auth/auth_util.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/named_pipe_auth/npa_tstream.h"
+#include "librpc/gen_ndr/ndr_winreg.h"
+#include "local_np.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_CLI
+
+/********************************************************************
+ Pipe description for a DEBUG
+ ********************************************************************/
+static const char *rpccli_pipe_txt(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *cli)
+{
+ char *result = talloc_asprintf(mem_ctx, "host %s", cli->desthost);
+ if (result == NULL) {
+ return "pipe";
+ }
+ return result;
+}
+
+/********************************************************************
+ Rpc pipe call id.
+ ********************************************************************/
+
+static uint32_t get_rpc_call_id(void)
+{
+ static uint32_t call_id = 0;
+ return ++call_id;
+}
+
+/*******************************************************************
+ Use SMBreadX to get rest of one fragment's worth of rpc data.
+ Reads the whole size or give an error message
+ ********************************************************************/
+
+struct rpc_read_state {
+ struct tevent_context *ev;
+ struct rpc_cli_transport *transport;
+ uint8_t *data;
+ size_t size;
+ size_t num_read;
+};
+
+static void rpc_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *rpc_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_cli_transport *transport,
+ uint8_t *data, size_t size)
+{
+ struct tevent_req *req, *subreq;
+ struct rpc_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->transport = transport;
+ state->data = data;
+ state->size = size;
+ state->num_read = 0;
+
+ DBG_INFO("data_to_read: %zu\n", size);
+
+ subreq = transport->read_send(state, ev, (uint8_t *)data, size,
+ transport->priv);
+ if (subreq == NULL) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, rpc_read_done, req);
+ return req;
+
+ fail:
+ TALLOC_FREE(req);
+ return NULL;
+}
+
+static void rpc_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_read_state *state = tevent_req_data(
+ req, struct rpc_read_state);
+ NTSTATUS status;
+ ssize_t received;
+
+ status = state->transport->read_recv(subreq, &received);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->num_read += received;
+ if (state->num_read == state->size) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = state->transport->read_send(state, state->ev,
+ state->data + state->num_read,
+ state->size - state->num_read,
+ state->transport->priv);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_read_done, req);
+}
+
+static NTSTATUS rpc_read_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct rpc_write_state {
+ struct tevent_context *ev;
+ struct rpc_cli_transport *transport;
+ const uint8_t *data;
+ size_t size;
+ size_t num_written;
+};
+
+static void rpc_write_done(struct tevent_req *subreq);
+
+static struct tevent_req *rpc_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_cli_transport *transport,
+ const uint8_t *data, size_t size)
+{
+ struct tevent_req *req, *subreq;
+ struct rpc_write_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->transport = transport;
+ state->data = data;
+ state->size = size;
+ state->num_written = 0;
+
+ DBG_INFO("data_to_write: %zu\n", size);
+
+ subreq = transport->write_send(state, ev, data, size, transport->priv);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_write_done, req);
+ return req;
+}
+
+static void rpc_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_write_state *state = tevent_req_data(
+ req, struct rpc_write_state);
+ NTSTATUS status;
+ ssize_t written;
+
+ status = state->transport->write_recv(subreq, &written);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->num_written += written;
+
+ if (state->num_written == state->size) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = state->transport->write_send(state, state->ev,
+ state->data + state->num_written,
+ state->size - state->num_written,
+ state->transport->priv);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_write_done, req);
+}
+
+static NTSTATUS rpc_write_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+
+/****************************************************************************
+ Try and get a PDU's worth of data from current_pdu. If not, then read more
+ from the wire.
+ ****************************************************************************/
+
+struct get_complete_frag_state {
+ struct tevent_context *ev;
+ struct rpc_pipe_client *cli;
+ uint16_t frag_len;
+ DATA_BLOB *pdu;
+};
+
+static void get_complete_frag_got_header(struct tevent_req *subreq);
+static void get_complete_frag_got_rest(struct tevent_req *subreq);
+
+static struct tevent_req *get_complete_frag_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_pipe_client *cli,
+ DATA_BLOB *pdu)
+{
+ struct tevent_req *req, *subreq;
+ struct get_complete_frag_state *state;
+ size_t received;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct get_complete_frag_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->frag_len = RPC_HEADER_LEN;
+ state->pdu = pdu;
+
+ received = pdu->length;
+ if (received < RPC_HEADER_LEN) {
+ if (!data_blob_realloc(mem_ctx, pdu, RPC_HEADER_LEN)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ subreq = rpc_read_send(state, state->ev,
+ state->cli->transport,
+ pdu->data + received,
+ RPC_HEADER_LEN - received);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, get_complete_frag_got_header,
+ req);
+ return req;
+ }
+
+ state->frag_len = dcerpc_get_frag_length(pdu);
+ if (state->frag_len < RPC_HEADER_LEN) {
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ if (received >= state->frag_len) {
+ /*
+ * Got the whole fragment
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!data_blob_realloc(NULL, pdu, state->frag_len)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = rpc_read_send(
+ state,
+ state->ev,
+ state->cli->transport,
+ pdu->data + received,
+ state->frag_len - received);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, get_complete_frag_got_rest, req);
+ return req;
+}
+
+static void get_complete_frag_got_header(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct get_complete_frag_state *state = tevent_req_data(
+ req, struct get_complete_frag_state);
+ NTSTATUS status;
+
+ status = rpc_read_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->frag_len = dcerpc_get_frag_length(state->pdu);
+ if (state->frag_len < RPC_HEADER_LEN) {
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ if (!data_blob_realloc(NULL, state->pdu, state->frag_len)) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ /*
+ * We're here in this piece of code because we've read exactly
+ * RPC_HEADER_LEN bytes into state->pdu.
+ */
+
+ subreq = rpc_read_send(state, state->ev, state->cli->transport,
+ state->pdu->data + RPC_HEADER_LEN,
+ state->frag_len - RPC_HEADER_LEN);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, get_complete_frag_got_rest, req);
+}
+
+static void get_complete_frag_got_rest(struct tevent_req *subreq)
+{
+ NTSTATUS status = rpc_read_recv(subreq);
+ return tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS get_complete_frag_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************************
+ Do basic authentication checks on an incoming pdu.
+ ****************************************************************************/
+
+static NTSTATUS cli_pipe_validate_current_pdu(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *cli,
+ struct ncacn_packet *pkt,
+ DATA_BLOB *pdu,
+ uint8_t expected_pkt_type,
+ uint32_t call_id,
+ DATA_BLOB *rdata,
+ DATA_BLOB *reply_pdu)
+{
+ const struct dcerpc_response *r = NULL;
+ DATA_BLOB tmp_stub = { .data = NULL };
+ NTSTATUS ret;
+
+ /*
+ * Point the return values at the real data including the RPC
+ * header. Just in case the caller wants it.
+ */
+ *rdata = *pdu;
+
+ if ((pkt->ptype == DCERPC_PKT_BIND_ACK) &&
+ !(pkt->pfc_flags & DCERPC_PFC_FLAG_LAST)) {
+ /*
+ * TODO: do we still need this hack which was introduced
+ * in commit a42afcdcc7ab9aa9ed193ae36d3dbb10843447f0.
+ *
+ * I don't even know what AS/U might be...
+ */
+ DEBUG(5, (__location__ ": bug in server (AS/U?), setting "
+ "fragment first/last ON.\n"));
+ pkt->pfc_flags |= DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST;
+ }
+
+ /* Ensure we have the correct type. */
+ switch (pkt->ptype) {
+ case DCERPC_PKT_BIND_NAK:
+ DEBUG(1, (__location__ ": Bind NACK received from %s!\n",
+ rpccli_pipe_txt(talloc_tos(), cli)));
+
+ ret = dcerpc_verify_ncacn_packet_header(pkt,
+ DCERPC_PKT_BIND_NAK,
+ 0, /* max_auth_info */
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ 0); /* optional flags */
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, (__location__ ": Connection to %s got an unexpected "
+ "RPC packet type - %u, expected %u: %s\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ pkt->ptype, expected_pkt_type,
+ nt_errstr(ret)));
+ NDR_PRINT_DEBUG(ncacn_packet, pkt);
+ return ret;
+ }
+
+ /* Use this for now... */
+ return NT_STATUS_NETWORK_ACCESS_DENIED;
+
+ case DCERPC_PKT_BIND_ACK:
+ ret = dcerpc_verify_ncacn_packet_header(pkt,
+ expected_pkt_type,
+ pkt->u.bind_ack.auth_info.length,
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ DCERPC_PFC_FLAG_CONC_MPX |
+ DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, (__location__ ": Connection to %s got an unexpected "
+ "RPC packet type - %u, expected %u: %s\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ pkt->ptype, expected_pkt_type,
+ nt_errstr(ret)));
+ NDR_PRINT_DEBUG(ncacn_packet, pkt);
+ return ret;
+ }
+
+ break;
+
+ case DCERPC_PKT_ALTER_RESP:
+ ret = dcerpc_verify_ncacn_packet_header(pkt,
+ expected_pkt_type,
+ pkt->u.alter_resp.auth_info.length,
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ DCERPC_PFC_FLAG_CONC_MPX |
+ DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, (__location__ ": Connection to %s got an unexpected "
+ "RPC packet type - %u, expected %u: %s\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ pkt->ptype, expected_pkt_type,
+ nt_errstr(ret)));
+ NDR_PRINT_DEBUG(ncacn_packet, pkt);
+ return ret;
+ }
+
+ break;
+
+ case DCERPC_PKT_RESPONSE:
+
+ r = &pkt->u.response;
+
+ ret = dcerpc_verify_ncacn_packet_header(pkt,
+ expected_pkt_type,
+ r->stub_and_verifier.length,
+ 0, /* required_flags */
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, (__location__ ": Connection to %s got an unexpected "
+ "RPC packet type - %u, expected %u: %s\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ pkt->ptype, expected_pkt_type,
+ nt_errstr(ret)));
+ NDR_PRINT_DEBUG(ncacn_packet, pkt);
+ return ret;
+ }
+
+ tmp_stub.data = r->stub_and_verifier.data;
+ tmp_stub.length = r->stub_and_verifier.length;
+
+ /* Here's where we deal with incoming sign/seal. */
+ ret = dcerpc_check_auth(cli->auth, pkt,
+ &tmp_stub,
+ DCERPC_RESPONSE_LENGTH,
+ pdu);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, (__location__ ": Connection to %s got an unexpected "
+ "RPC packet type - %u, expected %u: %s\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ pkt->ptype, expected_pkt_type,
+ nt_errstr(ret)));
+ NDR_PRINT_DEBUG(ncacn_packet, pkt);
+ return ret;
+ }
+
+ /* Point the return values at the NDR data. */
+ *rdata = tmp_stub;
+
+ DEBUG(10, ("Got pdu len %lu, data_len %lu\n",
+ (long unsigned int)pdu->length,
+ (long unsigned int)rdata->length));
+
+ /*
+ * If this is the first reply, and the allocation hint is
+ * reasonable, try and set up the reply_pdu DATA_BLOB to the
+ * correct size.
+ */
+
+ if ((reply_pdu->length == 0) &&
+ r->alloc_hint && (r->alloc_hint < 15*1024*1024)) {
+ if (!data_blob_realloc(mem_ctx, reply_pdu,
+ r->alloc_hint)) {
+ DEBUG(0, ("reply alloc hint %d too "
+ "large to allocate\n",
+ (int)r->alloc_hint));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ break;
+
+ case DCERPC_PKT_FAULT:
+
+ ret = dcerpc_verify_ncacn_packet_header(pkt,
+ DCERPC_PKT_FAULT,
+ 0, /* max_auth_info */
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ DCERPC_PFC_FLAG_DID_NOT_EXECUTE);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, (__location__ ": Connection to %s got an unexpected "
+ "RPC packet type - %u, expected %u: %s\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ pkt->ptype, expected_pkt_type,
+ nt_errstr(ret)));
+ NDR_PRINT_DEBUG(ncacn_packet, pkt);
+ return ret;
+ }
+
+ DEBUG(1, (__location__ ": RPC fault code %s received "
+ "from %s!\n",
+ dcerpc_errstr(talloc_tos(),
+ pkt->u.fault.status),
+ rpccli_pipe_txt(talloc_tos(), cli)));
+
+ return dcerpc_fault_to_nt_status(pkt->u.fault.status);
+
+ default:
+ DEBUG(0, (__location__ "Unknown packet type %u received "
+ "from %s!\n",
+ (unsigned int)pkt->ptype,
+ rpccli_pipe_txt(talloc_tos(), cli)));
+ return NT_STATUS_RPC_PROTOCOL_ERROR;
+ }
+
+
+ if (pkt->call_id != call_id) {
+ DEBUG(3, (__location__ ": Connection to %s got an unexpected "
+ "RPC call_id - %u, not %u\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ pkt->call_id, call_id));
+ return NT_STATUS_RPC_PROTOCOL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Call a remote api on an arbitrary pipe. takes param, data and setup buffers.
+****************************************************************************/
+
+struct cli_api_pipe_state {
+ struct tevent_context *ev;
+ struct rpc_cli_transport *transport;
+ uint8_t *rdata;
+ uint32_t rdata_len;
+};
+
+static void cli_api_pipe_trans_done(struct tevent_req *subreq);
+static void cli_api_pipe_write_done(struct tevent_req *subreq);
+static void cli_api_pipe_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *cli_api_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_cli_transport *transport,
+ uint8_t *data, size_t data_len,
+ uint32_t max_rdata_len)
+{
+ struct tevent_req *req, *subreq;
+ struct cli_api_pipe_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct cli_api_pipe_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->transport = transport;
+
+ if (max_rdata_len < RPC_HEADER_LEN) {
+ /*
+ * For a RPC reply we always need at least RPC_HEADER_LEN
+ * bytes. We check this here because we will receive
+ * RPC_HEADER_LEN bytes in cli_trans_sock_send_done.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (transport->trans_send != NULL) {
+ subreq = transport->trans_send(state, ev, data, data_len,
+ max_rdata_len, transport->priv);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_api_pipe_trans_done, req);
+ return req;
+ }
+
+ /*
+ * If the transport does not provide a "trans" routine, i.e. for
+ * example the ncacn_ip_tcp transport, do the write/read step here.
+ */
+
+ subreq = rpc_write_send(state, ev, transport, data, data_len);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, cli_api_pipe_write_done, req);
+ return req;
+}
+
+static void cli_api_pipe_trans_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_api_pipe_state *state = tevent_req_data(
+ req, struct cli_api_pipe_state);
+ NTSTATUS status;
+
+ status = state->transport->trans_recv(subreq, state, &state->rdata,
+ &state->rdata_len);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void cli_api_pipe_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_api_pipe_state *state = tevent_req_data(
+ req, struct cli_api_pipe_state);
+ NTSTATUS status;
+
+ status = rpc_write_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->rdata = talloc_array(state, uint8_t, RPC_HEADER_LEN);
+ if (tevent_req_nomem(state->rdata, req)) {
+ return;
+ }
+
+ /*
+ * We don't need to use rpc_read_send here, the upper layer will cope
+ * with a short read, transport->trans_send could also return less
+ * than state->max_rdata_len.
+ */
+ subreq = state->transport->read_send(state, state->ev, state->rdata,
+ RPC_HEADER_LEN,
+ state->transport->priv);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, cli_api_pipe_read_done, req);
+}
+
+static void cli_api_pipe_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cli_api_pipe_state *state = tevent_req_data(
+ req, struct cli_api_pipe_state);
+ NTSTATUS status;
+ ssize_t received;
+
+ status = state->transport->read_recv(subreq, &received);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->rdata_len = received;
+ tevent_req_done(req);
+}
+
+static NTSTATUS cli_api_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **prdata, uint32_t *prdata_len)
+{
+ struct cli_api_pipe_state *state = tevent_req_data(
+ req, struct cli_api_pipe_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *prdata = talloc_move(mem_ctx, &state->rdata);
+ *prdata_len = state->rdata_len;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Send data on an rpc pipe via trans. The data must be the last
+ pdu fragment of an NDR data stream.
+
+ Receive response data from an rpc pipe, which may be large...
+
+ Read the first fragment: unfortunately have to use SMBtrans for the first
+ bit, then SMBreadX for subsequent bits.
+
+ If first fragment received also wasn't the last fragment, continue
+ getting fragments until we _do_ receive the last fragment.
+
+ Request/Response PDU's look like the following...
+
+ |<------------------PDU len----------------------------------------------->|
+ |<-HDR_LEN-->|<--REQ LEN------>|.............|<-AUTH_HDRLEN->|<-AUTH_LEN-->|
+
+ +------------+-----------------+-------------+---------------+-------------+
+ | RPC HEADER | REQ/RESP HEADER | DATA ...... | AUTH_HDR | AUTH DATA |
+ +------------+-----------------+-------------+---------------+-------------+
+
+ Where the presence of the AUTH_HDR and AUTH DATA are dependent on the
+ signing & sealing being negotiated.
+
+ ****************************************************************************/
+
+struct rpc_api_pipe_state {
+ struct tevent_context *ev;
+ struct rpc_pipe_client *cli;
+ uint8_t expected_pkt_type;
+ uint32_t call_id;
+
+ DATA_BLOB incoming_frag;
+ struct ncacn_packet *pkt;
+
+ /* Incoming reply */
+ DATA_BLOB reply_pdu;
+ size_t reply_pdu_offset;
+ uint8_t endianness;
+};
+
+static void rpc_api_pipe_trans_done(struct tevent_req *subreq);
+static void rpc_api_pipe_got_pdu(struct tevent_req *subreq);
+static void rpc_api_pipe_auth3_done(struct tevent_req *subreq);
+
+static struct tevent_req *rpc_api_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_pipe_client *cli,
+ DATA_BLOB *data, /* Outgoing PDU */
+ uint8_t expected_pkt_type,
+ uint32_t call_id)
+{
+ struct tevent_req *req, *subreq;
+ struct rpc_api_pipe_state *state;
+ uint16_t max_recv_frag;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_api_pipe_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->expected_pkt_type = expected_pkt_type;
+ state->call_id = call_id;
+ state->endianness = DCERPC_DREP_LE;
+
+ /*
+ * Ensure we're not sending too much.
+ */
+ if (data->length > cli->max_xmit_frag) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(5,("rpc_api_pipe: %s\n", rpccli_pipe_txt(talloc_tos(), cli)));
+
+ if (state->expected_pkt_type == DCERPC_PKT_AUTH3) {
+ subreq = rpc_write_send(state, ev, cli->transport,
+ data->data, data->length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_auth3_done, req);
+ return req;
+ }
+
+ /* get the header first, then fetch the rest once we have
+ * the frag_length available */
+ max_recv_frag = RPC_HEADER_LEN;
+
+ subreq = cli_api_pipe_send(state, ev, cli->transport,
+ data->data, data->length, max_recv_frag);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_trans_done, req);
+ return req;
+}
+
+static void rpc_api_pipe_auth3_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = rpc_write_recv(subreq);
+ return tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static void rpc_api_pipe_trans_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_api_pipe_state *state = tevent_req_data(
+ req, struct rpc_api_pipe_state);
+ NTSTATUS status;
+ uint8_t *rdata = NULL;
+ uint32_t rdata_len = 0;
+
+ status = cli_api_pipe_recv(subreq, state, &rdata, &rdata_len);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {;
+ DEBUG(5, ("cli_api_pipe failed: %s\n", nt_errstr(status)));
+ return;
+ }
+
+ if (rdata == NULL) {
+ DEBUG(3,("rpc_api_pipe: %s failed to return data.\n",
+ rpccli_pipe_txt(talloc_tos(), state->cli)));
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Move data on state->incoming_frag.
+ */
+ state->incoming_frag.data = talloc_move(state, &rdata);
+ state->incoming_frag.length = rdata_len;
+ if (!state->incoming_frag.data) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ /* Ensure we have enough data for a pdu. */
+ subreq = get_complete_frag_send(state, state->ev, state->cli,
+ &state->incoming_frag);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_got_pdu, req);
+}
+
+static void rpc_api_pipe_got_pdu(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_api_pipe_state *state = tevent_req_data(
+ req, struct rpc_api_pipe_state);
+ NTSTATUS status;
+ DATA_BLOB rdata = { .data = NULL };
+
+ status = get_complete_frag_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(5, ("get_complete_frag failed: %s\n",
+ nt_errstr(status)));
+ return;
+ }
+
+ state->pkt = talloc(state, struct ncacn_packet);
+ if (!state->pkt) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ tevent_req_oom(req);
+ return;
+ }
+
+ status = dcerpc_pull_ncacn_packet(state->pkt,
+ &state->incoming_frag,
+ state->pkt);
+ if (tevent_req_nterror(req, status)) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ return;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(ncacn_packet, state->pkt);
+ }
+
+ status = cli_pipe_validate_current_pdu(state,
+ state->cli, state->pkt,
+ &state->incoming_frag,
+ state->expected_pkt_type,
+ state->call_id,
+ &rdata,
+ &state->reply_pdu);
+
+ DBG_DEBUG("got frag len of %zu at offset %zu: %s\n",
+ state->incoming_frag.length,
+ state->reply_pdu_offset,
+ nt_errstr(status));
+
+ if (state->pkt->ptype != DCERPC_PKT_FAULT && !NT_STATUS_IS_OK(status)) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROTOCOL_ERROR)) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if ((state->pkt->pfc_flags & DCERPC_PFC_FLAG_FIRST)
+ && (state->pkt->drep[0] != DCERPC_DREP_LE)) {
+ /*
+ * Set the data type correctly for big-endian data on the
+ * first packet.
+ */
+ DEBUG(10,("rpc_api_pipe: On %s PDU data format is "
+ "big-endian.\n",
+ rpccli_pipe_txt(talloc_tos(), state->cli)));
+ state->endianness = 0x00; /* BIG ENDIAN */
+ }
+ /*
+ * Check endianness on subsequent packets.
+ */
+ if (state->endianness != state->pkt->drep[0]) {
+ DEBUG(0,("rpc_api_pipe: Error : Endianness changed from %s to "
+ "%s\n",
+ state->endianness?"little":"big",
+ state->pkt->drep[0]?"little":"big"));
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ if (state->reply_pdu_offset + rdata.length > MAX_RPC_DATA_SIZE) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ /* Now copy the data portion out of the pdu into rbuf. */
+ if (state->reply_pdu.length < state->reply_pdu_offset + rdata.length) {
+ if (!data_blob_realloc(NULL, &state->reply_pdu,
+ state->reply_pdu_offset + rdata.length)) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ tevent_req_oom(req);
+ return;
+ }
+ }
+
+ memcpy(state->reply_pdu.data + state->reply_pdu_offset,
+ rdata.data, rdata.length);
+ state->reply_pdu_offset += rdata.length;
+
+ /* reset state->incoming_frag, there is no need to free it,
+ * it will be reallocated to the right size the next time
+ * it is used */
+ state->incoming_frag.length = 0;
+
+ if (state->pkt->pfc_flags & DCERPC_PFC_FLAG_LAST) {
+ /* make sure the pdu length is right now that we
+ * have all the data available (alloc hint may
+ * have allocated more than was actually used) */
+ state->reply_pdu.length = state->reply_pdu_offset;
+ DEBUG(10,("rpc_api_pipe: %s returned %u bytes.\n",
+ rpccli_pipe_txt(talloc_tos(), state->cli),
+ (unsigned)state->reply_pdu.length));
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = get_complete_frag_send(state, state->ev, state->cli,
+ &state->incoming_frag);
+ if (subreq == NULL) {
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now do it sync...
+ */
+ TALLOC_FREE(state->cli->transport);
+ }
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_got_pdu, req);
+}
+
+static NTSTATUS rpc_api_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct ncacn_packet **pkt,
+ DATA_BLOB *reply_pdu)
+{
+ struct rpc_api_pipe_state *state = tevent_req_data(
+ req, struct rpc_api_pipe_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ /* return data to caller and assign it ownership of memory */
+ if (reply_pdu) {
+ reply_pdu->data = talloc_move(mem_ctx, &state->reply_pdu.data);
+ reply_pdu->length = state->reply_pdu.length;
+ state->reply_pdu.length = 0;
+ } else {
+ data_blob_free(&state->reply_pdu);
+ }
+
+ if (pkt) {
+ *pkt = talloc_steal(mem_ctx, state->pkt);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Creates NTLMSSP auth bind.
+ ********************************************************************/
+
+static NTSTATUS create_generic_auth_rpc_bind_req(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *auth_token,
+ bool *client_hdr_signing)
+{
+ struct gensec_security *gensec_security;
+ DATA_BLOB null_blob = { .data = NULL };
+ NTSTATUS status;
+
+ gensec_security = cli->auth->auth_ctx;
+
+ DEBUG(5, ("create_generic_auth_rpc_bind_req: generate first token\n"));
+ status = gensec_update(gensec_security, mem_ctx, null_blob, auth_token);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED))
+ {
+ return status;
+ }
+
+ if (client_hdr_signing == NULL) {
+ return status;
+ }
+
+ if (cli->auth->auth_level < DCERPC_AUTH_LEVEL_PACKET) {
+ *client_hdr_signing = false;
+ return status;
+ }
+
+ *client_hdr_signing = gensec_have_feature(gensec_security,
+ GENSEC_FEATURE_SIGN_PKT_HEADER);
+
+ return status;
+}
+
+/*******************************************************************
+ Creates the internals of a DCE/RPC bind request or alter context PDU.
+ ********************************************************************/
+
+static NTSTATUS create_bind_or_alt_ctx_internal(TALLOC_CTX *mem_ctx,
+ enum dcerpc_pkt_type ptype,
+ uint32_t rpc_call_id,
+ const struct ndr_syntax_id *abstract,
+ const struct ndr_syntax_id *transfer,
+ const DATA_BLOB *auth_info,
+ bool client_hdr_signing,
+ DATA_BLOB *blob)
+{
+ uint16_t auth_len = auth_info->length;
+ NTSTATUS status;
+ struct dcerpc_ctx_list ctx_list = {
+ .context_id = 0,
+ .num_transfer_syntaxes = 1,
+ .abstract_syntax = *abstract,
+ .transfer_syntaxes = (struct ndr_syntax_id *)discard_const(transfer),
+ };
+ union dcerpc_payload u = {
+ .bind.max_xmit_frag = RPC_MAX_PDU_FRAG_LEN,
+ .bind.max_recv_frag = RPC_MAX_PDU_FRAG_LEN,
+ .bind.num_contexts = 1,
+ .bind.ctx_list = &ctx_list,
+ .bind.auth_info = *auth_info,
+ };
+ uint8_t pfc_flags = DCERPC_PFC_FLAG_FIRST | DCERPC_PFC_FLAG_LAST;
+
+ if (auth_len) {
+ auth_len -= DCERPC_AUTH_TRAILER_LENGTH;
+ }
+
+ if (client_hdr_signing) {
+ pfc_flags |= DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN;
+ }
+
+ status = dcerpc_push_ncacn_packet(mem_ctx,
+ ptype, pfc_flags,
+ auth_len,
+ rpc_call_id,
+ &u,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to marshall bind/alter ncacn_packet.\n"));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Creates a DCE/RPC bind request.
+ ********************************************************************/
+
+static NTSTATUS create_rpc_bind_req(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *cli,
+ struct pipe_auth_data *auth,
+ uint32_t rpc_call_id,
+ const struct ndr_syntax_id *abstract,
+ const struct ndr_syntax_id *transfer,
+ DATA_BLOB *rpc_out)
+{
+ DATA_BLOB auth_token = { .data = NULL };
+ DATA_BLOB auth_info = { .data = NULL };
+ NTSTATUS ret;
+
+ if (auth->auth_type != DCERPC_AUTH_TYPE_NONE) {
+ ret = create_generic_auth_rpc_bind_req(
+ cli, mem_ctx, &auth_token, &auth->client_hdr_signing);
+
+ if (!NT_STATUS_IS_OK(ret) &&
+ !NT_STATUS_EQUAL(ret, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return ret;
+ }
+ }
+
+ if (auth_token.length != 0) {
+ ret = dcerpc_push_dcerpc_auth(cli,
+ auth->auth_type,
+ auth->auth_level,
+ 0, /* auth_pad_length */
+ auth->auth_context_id,
+ &auth_token,
+ &auth_info);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return ret;
+ }
+ data_blob_free(&auth_token);
+ }
+
+ ret = create_bind_or_alt_ctx_internal(mem_ctx,
+ DCERPC_PKT_BIND,
+ rpc_call_id,
+ abstract,
+ transfer,
+ &auth_info,
+ auth->client_hdr_signing,
+ rpc_out);
+ data_blob_free(&auth_info);
+
+ return ret;
+}
+
+/*******************************************************************
+ External interface.
+ Does an rpc request on a pipe. Incoming data is NDR encoded in in_data.
+ Reply is NDR encoded in out_data. Splits the data stream into RPC PDU's
+ and deals with signing/sealing details.
+ ********************************************************************/
+
+struct rpc_api_pipe_req_state {
+ struct tevent_context *ev;
+ struct rpc_pipe_client *cli;
+ uint8_t op_num;
+ uint32_t call_id;
+ const DATA_BLOB *req_data;
+ const struct GUID *object_uuid;
+ uint32_t req_data_sent;
+ DATA_BLOB req_trailer;
+ uint32_t req_trailer_sent;
+ bool verify_bitmask1;
+ bool verify_pcontext;
+ DATA_BLOB rpc_out;
+ DATA_BLOB reply_pdu;
+};
+
+static void rpc_api_pipe_req_write_done(struct tevent_req *subreq);
+static void rpc_api_pipe_req_done(struct tevent_req *subreq);
+static NTSTATUS prepare_verification_trailer(struct rpc_api_pipe_req_state *state);
+static NTSTATUS prepare_next_frag(struct rpc_api_pipe_req_state *state,
+ bool *is_last_frag);
+
+static struct tevent_req *rpc_api_pipe_req_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_pipe_client *cli,
+ uint8_t op_num,
+ const struct GUID *object_uuid,
+ const DATA_BLOB *req_data)
+{
+ struct tevent_req *req, *subreq;
+ struct rpc_api_pipe_req_state *state;
+ NTSTATUS status;
+ bool is_last_frag;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct rpc_api_pipe_req_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->op_num = op_num;
+ state->object_uuid = object_uuid;
+ state->req_data = req_data;
+ state->call_id = get_rpc_call_id();
+
+ if (cli->max_xmit_frag < DCERPC_REQUEST_LENGTH
+ + RPC_MAX_SIGN_SIZE) {
+ /* Server is screwed up ! */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = prepare_verification_trailer(state);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = prepare_next_frag(state, &is_last_frag);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (is_last_frag) {
+ subreq = rpc_api_pipe_send(state, ev, state->cli,
+ &state->rpc_out,
+ DCERPC_PKT_RESPONSE,
+ state->call_id);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_req_done, req);
+ } else {
+ subreq = rpc_write_send(state, ev, cli->transport,
+ state->rpc_out.data,
+ state->rpc_out.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_req_write_done,
+ req);
+ }
+ return req;
+}
+
+static NTSTATUS prepare_verification_trailer(struct rpc_api_pipe_req_state *state)
+{
+ struct pipe_auth_data *a = state->cli->auth;
+ struct dcerpc_sec_verification_trailer *t;
+ struct ndr_push *ndr = NULL;
+ enum ndr_err_code ndr_err;
+ size_t align = 0;
+ size_t pad = 0;
+
+ if (a == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (a->auth_level < DCERPC_AUTH_LEVEL_PACKET) {
+ return NT_STATUS_OK;
+ }
+
+ t = talloc_zero(state, struct dcerpc_sec_verification_trailer);
+ if (t == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!a->verified_bitmask1) {
+ t->commands = talloc_realloc(t, t->commands,
+ struct dcerpc_sec_vt,
+ t->count.count + 1);
+ if (t->commands == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ t->commands[t->count.count++] = (struct dcerpc_sec_vt) {
+ .command = DCERPC_SEC_VT_COMMAND_BITMASK1,
+ .u.bitmask1 = (a->client_hdr_signing) ?
+ DCERPC_SEC_VT_CLIENT_SUPPORTS_HEADER_SIGNING :
+ 0,
+ };
+ state->verify_bitmask1 = true;
+ }
+
+ if (!state->cli->verified_pcontext) {
+ t->commands = talloc_realloc(t, t->commands,
+ struct dcerpc_sec_vt,
+ t->count.count + 1);
+ if (t->commands == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ t->commands[t->count.count++] = (struct dcerpc_sec_vt) {
+ .command = DCERPC_SEC_VT_COMMAND_PCONTEXT,
+ .u.pcontext.abstract_syntax =
+ state->cli->abstract_syntax,
+ .u.pcontext.transfer_syntax =
+ state->cli->transfer_syntax,
+ };
+ state->verify_pcontext = true;
+ }
+
+ if (!a->hdr_signing) {
+ t->commands = talloc_realloc(t, t->commands,
+ struct dcerpc_sec_vt,
+ t->count.count + 1);
+ if (t->commands == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ t->commands[t->count.count++] = (struct dcerpc_sec_vt) {
+ .command = DCERPC_SEC_VT_COMMAND_HEADER2,
+ .u.header2.ptype = DCERPC_PKT_REQUEST,
+ .u.header2.drep[0] = DCERPC_DREP_LE,
+ .u.header2.call_id = state->call_id,
+ .u.header2.context_id = 0,
+ .u.header2.opnum = state->op_num,
+ };
+ }
+
+ if (t->count.count == 0) {
+ TALLOC_FREE(t);
+ return NT_STATUS_OK;
+ }
+
+ t->commands[t->count.count - 1].command |= DCERPC_SEC_VT_COMMAND_END;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(dcerpc_sec_verification_trailer, t);
+ }
+
+ ndr = ndr_push_init_ctx(state);
+ if (ndr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ndr_err = ndr_push_dcerpc_sec_verification_trailer(ndr,
+ NDR_SCALARS | NDR_BUFFERS,
+ t);
+ TALLOC_FREE(t);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+ state->req_trailer = ndr_push_blob(ndr);
+
+ align = state->req_data->length & 0x3;
+ if (align > 0) {
+ pad = 4 - align;
+ }
+ if (pad > 0) {
+ bool ok;
+ uint8_t *p;
+ const uint8_t zeros[4] = { 0, };
+
+ ok = data_blob_append(ndr, &state->req_trailer, zeros, pad);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* move the padding to the start */
+ p = state->req_trailer.data;
+ memmove(p + pad, p, state->req_trailer.length - pad);
+ memset(p, 0, pad);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS prepare_next_frag(struct rpc_api_pipe_req_state *state,
+ bool *is_last_frag)
+{
+ size_t auth_len;
+ size_t frag_len;
+ uint8_t flags = 0;
+ size_t pad_len;
+ size_t data_left;
+ size_t data_thistime;
+ size_t trailer_left;
+ size_t trailer_thistime = 0;
+ size_t total_left;
+ size_t total_thistime;
+ NTSTATUS status;
+ bool ok;
+ union dcerpc_payload u;
+
+ data_left = state->req_data->length - state->req_data_sent;
+ trailer_left = state->req_trailer.length - state->req_trailer_sent;
+ total_left = data_left + trailer_left;
+ if ((total_left < data_left) || (total_left < trailer_left)) {
+ /*
+ * overflow
+ */
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ status = dcerpc_guess_sizes(state->cli->auth,
+ DCERPC_REQUEST_LENGTH, total_left,
+ state->cli->max_xmit_frag,
+ &total_thistime,
+ &frag_len, &auth_len, &pad_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (state->req_data_sent == 0) {
+ flags = DCERPC_PFC_FLAG_FIRST;
+ }
+
+ if (total_thistime == total_left) {
+ flags |= DCERPC_PFC_FLAG_LAST;
+ }
+
+ data_thistime = MIN(total_thistime, data_left);
+ if (data_thistime < total_thistime) {
+ trailer_thistime = total_thistime - data_thistime;
+ }
+
+ data_blob_free(&state->rpc_out);
+
+ u = (union dcerpc_payload) {
+ .request.alloc_hint = total_left,
+ .request.context_id = 0,
+ .request.opnum = state->op_num,
+ };
+
+ if (state->object_uuid) {
+ flags |= DCERPC_PFC_FLAG_OBJECT_UUID;
+ u.request.object.object = *state->object_uuid;
+ frag_len += ndr_size_GUID(state->object_uuid, 0);
+ }
+
+ status = dcerpc_push_ncacn_packet(state,
+ DCERPC_PKT_REQUEST,
+ flags,
+ auth_len,
+ state->call_id,
+ &u,
+ &state->rpc_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* explicitly set frag_len here as dcerpc_push_ncacn_packet() can't
+ * compute it right for requests because the auth trailer is missing
+ * at this stage */
+ dcerpc_set_frag_length(&state->rpc_out, frag_len);
+
+ if (data_thistime > 0) {
+ /* Copy in the data. */
+ ok = data_blob_append(NULL, &state->rpc_out,
+ state->req_data->data + state->req_data_sent,
+ data_thistime);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->req_data_sent += data_thistime;
+ }
+
+ if (trailer_thistime > 0) {
+ /* Copy in the verification trailer. */
+ ok = data_blob_append(NULL, &state->rpc_out,
+ state->req_trailer.data + state->req_trailer_sent,
+ trailer_thistime);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->req_trailer_sent += trailer_thistime;
+ }
+
+ switch (state->cli->auth->auth_level) {
+ case DCERPC_AUTH_LEVEL_NONE:
+ case DCERPC_AUTH_LEVEL_CONNECT:
+ break;
+ case DCERPC_AUTH_LEVEL_PACKET:
+ case DCERPC_AUTH_LEVEL_INTEGRITY:
+ case DCERPC_AUTH_LEVEL_PRIVACY:
+ status = dcerpc_add_auth_footer(state->cli->auth, pad_len,
+ &state->rpc_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *is_last_frag = ((flags & DCERPC_PFC_FLAG_LAST) != 0);
+
+ return status;
+}
+
+static void rpc_api_pipe_req_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_api_pipe_req_state *state = tevent_req_data(
+ req, struct rpc_api_pipe_req_state);
+ NTSTATUS status;
+ bool is_last_frag;
+
+ status = rpc_write_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = prepare_next_frag(state, &is_last_frag);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (is_last_frag) {
+ subreq = rpc_api_pipe_send(state, state->ev, state->cli,
+ &state->rpc_out,
+ DCERPC_PKT_RESPONSE,
+ state->call_id);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_req_done, req);
+ } else {
+ subreq = rpc_write_send(state, state->ev,
+ state->cli->transport,
+ state->rpc_out.data,
+ state->rpc_out.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_api_pipe_req_write_done,
+ req);
+ }
+}
+
+static void rpc_api_pipe_req_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_api_pipe_req_state *state = tevent_req_data(
+ req, struct rpc_api_pipe_req_state);
+ NTSTATUS status;
+
+ status = rpc_api_pipe_recv(subreq, state, NULL, &state->reply_pdu);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (state->cli->auth == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ if (state->verify_bitmask1) {
+ state->cli->auth->verified_bitmask1 = true;
+ }
+
+ if (state->verify_pcontext) {
+ state->cli->verified_pcontext = true;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS rpc_api_pipe_req_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ DATA_BLOB *reply_pdu)
+{
+ struct rpc_api_pipe_req_state *state = tevent_req_data(
+ req, struct rpc_api_pipe_req_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ /*
+ * We always have to initialize to reply pdu, even if there is
+ * none. The rpccli_* caller routines expect this.
+ */
+ *reply_pdu = data_blob_null;
+ return status;
+ }
+
+ /* return data to caller and assign it ownership of memory */
+ reply_pdu->data = talloc_move(mem_ctx, &state->reply_pdu.data);
+ reply_pdu->length = state->reply_pdu.length;
+ state->reply_pdu.length = 0;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check the rpc bind acknowledge response.
+****************************************************************************/
+
+static bool check_bind_response(const struct dcerpc_bind_ack *r,
+ const struct ndr_syntax_id *transfer)
+{
+ struct dcerpc_ack_ctx ctx;
+ bool equal;
+
+ if (r->secondary_address_size == 0) {
+ DEBUG(4,("Ignoring length check -- ASU bug (server didn't fill in the pipe name correctly)\n"));
+ }
+
+ if (r->num_results < 1 || !r->ctx_list) {
+ return false;
+ }
+
+ ctx = r->ctx_list[0];
+
+ /* check the transfer syntax */
+ equal = ndr_syntax_id_equal(&ctx.syntax, transfer);
+ if (!equal) {
+ DEBUG(2,("bind_rpc_pipe: transfer syntax differs\n"));
+ return False;
+ }
+
+ if (r->num_results != 0x1 || ctx.result != 0) {
+ DEBUG(2,("bind_rpc_pipe: bind denied results: %d reason: %x\n",
+ r->num_results, ctx.reason.value));
+ }
+
+ DEBUG(5,("check_bind_response: accepted!\n"));
+ return True;
+}
+
+/*******************************************************************
+ Creates a DCE/RPC bind authentication response.
+ This is the packet that is sent back to the server once we
+ have received a BIND-ACK, to finish the third leg of
+ the authentication handshake.
+ ********************************************************************/
+
+static NTSTATUS create_rpc_bind_auth3(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *cli,
+ struct pipe_auth_data *auth,
+ uint32_t rpc_call_id,
+ DATA_BLOB *pauth_blob,
+ DATA_BLOB *rpc_out)
+{
+ NTSTATUS status;
+ union dcerpc_payload u = { .auth3._pad = 0, };
+
+ status = dcerpc_push_dcerpc_auth(mem_ctx,
+ auth->auth_type,
+ auth->auth_level,
+ 0, /* auth_pad_length */
+ auth->auth_context_id,
+ pauth_blob,
+ &u.auth3.auth_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = dcerpc_push_ncacn_packet(mem_ctx,
+ DCERPC_PKT_AUTH3,
+ DCERPC_PFC_FLAG_FIRST |
+ DCERPC_PFC_FLAG_LAST,
+ pauth_blob->length,
+ rpc_call_id,
+ &u,
+ rpc_out);
+ data_blob_free(&u.auth3.auth_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("create_bind_or_alt_ctx_internal: failed to marshall RPC_HDR_RB.\n"));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Creates a DCE/RPC bind alter context authentication request which
+ may contain a spnego auth blob
+ ********************************************************************/
+
+static NTSTATUS create_rpc_alter_context(TALLOC_CTX *mem_ctx,
+ struct pipe_auth_data *auth,
+ uint32_t rpc_call_id,
+ const struct ndr_syntax_id *abstract,
+ const struct ndr_syntax_id *transfer,
+ const DATA_BLOB *pauth_blob, /* spnego auth blob already created. */
+ DATA_BLOB *rpc_out)
+{
+ DATA_BLOB auth_info;
+ NTSTATUS status;
+
+ status = dcerpc_push_dcerpc_auth(mem_ctx,
+ auth->auth_type,
+ auth->auth_level,
+ 0, /* auth_pad_length */
+ auth->auth_context_id,
+ pauth_blob,
+ &auth_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = create_bind_or_alt_ctx_internal(mem_ctx,
+ DCERPC_PKT_ALTER,
+ rpc_call_id,
+ abstract,
+ transfer,
+ &auth_info,
+ false, /* client_hdr_signing */
+ rpc_out);
+ data_blob_free(&auth_info);
+ return status;
+}
+
+/****************************************************************************
+ Do an rpc bind.
+****************************************************************************/
+
+struct rpc_pipe_bind_state {
+ struct tevent_context *ev;
+ struct rpc_pipe_client *cli;
+ DATA_BLOB rpc_out;
+ bool auth3;
+ uint32_t rpc_call_id;
+};
+
+static void rpc_pipe_bind_step_one_done(struct tevent_req *subreq);
+static NTSTATUS rpc_bind_next_send(struct tevent_req *req,
+ struct rpc_pipe_bind_state *state,
+ DATA_BLOB *credentials);
+static NTSTATUS rpc_bind_finish_send(struct tevent_req *req,
+ struct rpc_pipe_bind_state *state,
+ DATA_BLOB *credentials);
+
+struct tevent_req *rpc_pipe_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_pipe_client *cli,
+ struct pipe_auth_data *auth)
+{
+ struct tevent_req *req, *subreq;
+ struct rpc_pipe_bind_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_pipe_bind_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ DEBUG(5,("Bind RPC Pipe: %s auth_type %u, auth_level %u\n",
+ rpccli_pipe_txt(talloc_tos(), cli),
+ (unsigned int)auth->auth_type,
+ (unsigned int)auth->auth_level ));
+
+ state->ev = ev;
+ state->cli = cli;
+ state->rpc_call_id = get_rpc_call_id();
+
+ cli->auth = talloc_move(cli, &auth);
+
+ /* Marshall the outgoing data. */
+ status = create_rpc_bind_req(state, cli,
+ cli->auth,
+ state->rpc_call_id,
+ &cli->abstract_syntax,
+ &cli->transfer_syntax,
+ &state->rpc_out);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = rpc_api_pipe_send(state, ev, cli, &state->rpc_out,
+ DCERPC_PKT_BIND_ACK, state->rpc_call_id);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_pipe_bind_step_one_done, req);
+ return req;
+}
+
+static void rpc_pipe_bind_step_one_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_pipe_bind_state *state = tevent_req_data(
+ req, struct rpc_pipe_bind_state);
+ struct pipe_auth_data *pauth = state->cli->auth;
+ struct gensec_security *gensec_security;
+ struct ncacn_packet *pkt = NULL;
+ struct dcerpc_auth auth;
+ DATA_BLOB auth_token = { .data = NULL };
+ NTSTATUS status;
+
+ status = rpc_api_pipe_recv(subreq, talloc_tos(), &pkt, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(3, ("rpc_pipe_bind: %s bind request returned %s\n",
+ rpccli_pipe_txt(talloc_tos(), state->cli),
+ nt_errstr(status)));
+ return;
+ }
+
+ if (state->auth3) {
+ tevent_req_done(req);
+ return;
+ }
+
+ if (!check_bind_response(&pkt->u.bind_ack, &state->cli->transfer_syntax)) {
+ DEBUG(2, ("rpc_pipe_bind: check_bind_response failed.\n"));
+ tevent_req_nterror(req, NT_STATUS_BUFFER_TOO_SMALL);
+ return;
+ }
+
+ if (pkt->ptype == DCERPC_PKT_BIND_ACK) {
+ if (pkt->pfc_flags & DCERPC_PFC_FLAG_SUPPORT_HEADER_SIGN) {
+ if (pauth->client_hdr_signing) {
+ pauth->hdr_signing = true;
+ }
+ }
+ }
+
+ state->cli->max_xmit_frag = pkt->u.bind_ack.max_xmit_frag;
+
+ if (pauth->auth_type == DCERPC_AUTH_TYPE_NONE) {
+ /* Bind complete. */
+ tevent_req_done(req);
+ return;
+ }
+
+ if (pkt->auth_length == 0) {
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ /* get auth credentials */
+ status = dcerpc_pull_auth_trailer(pkt, talloc_tos(),
+ &pkt->u.bind_ack.auth_info,
+ &auth, NULL, true);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(0, ("Failed to pull dcerpc auth: %s.\n",
+ nt_errstr(status)));
+ return;
+ }
+
+ if (auth.auth_type != pauth->auth_type) {
+ DBG_ERR("Auth type %u mismatch expected %u.\n",
+ auth.auth_type, pauth->auth_type);
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ if (auth.auth_level != pauth->auth_level) {
+ DBG_ERR("Auth level %u mismatch expected %u.\n",
+ auth.auth_level, pauth->auth_level);
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ if (auth.auth_context_id != pauth->auth_context_id) {
+ DBG_ERR("Auth context id %"PRIu32" mismatch "
+ "expected %"PRIu32".\n",
+ auth.auth_context_id,
+ pauth->auth_context_id);
+ tevent_req_nterror(req, NT_STATUS_RPC_PROTOCOL_ERROR);
+ return;
+ }
+
+ /*
+ * For authenticated binds we may need to do 3 or 4 leg binds.
+ */
+
+ if (pauth->auth_type == DCERPC_AUTH_TYPE_NONE) {
+ /* Bind complete. */
+ tevent_req_done(req);
+ return;
+ }
+
+ gensec_security = pauth->auth_ctx;
+
+ status = gensec_update(gensec_security, state,
+ auth.credentials, &auth_token);
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ status = rpc_bind_next_send(req, state,
+ &auth_token);
+ } else if (NT_STATUS_IS_OK(status)) {
+ if (pauth->hdr_signing) {
+ gensec_want_feature(gensec_security,
+ GENSEC_FEATURE_SIGN_PKT_HEADER);
+ }
+
+ if (auth_token.length == 0) {
+ /* Bind complete. */
+ tevent_req_done(req);
+ return;
+ }
+ status = rpc_bind_finish_send(req, state,
+ &auth_token);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ }
+ return;
+}
+
+static NTSTATUS rpc_bind_next_send(struct tevent_req *req,
+ struct rpc_pipe_bind_state *state,
+ DATA_BLOB *auth_token)
+{
+ struct pipe_auth_data *auth = state->cli->auth;
+ struct tevent_req *subreq;
+ NTSTATUS status;
+
+ /* Now prepare the alter context pdu. */
+ data_blob_free(&state->rpc_out);
+
+ status = create_rpc_alter_context(state, auth,
+ state->rpc_call_id,
+ &state->cli->abstract_syntax,
+ &state->cli->transfer_syntax,
+ auth_token,
+ &state->rpc_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ subreq = rpc_api_pipe_send(state, state->ev, state->cli,
+ &state->rpc_out, DCERPC_PKT_ALTER_RESP,
+ state->rpc_call_id);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, rpc_pipe_bind_step_one_done, req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS rpc_bind_finish_send(struct tevent_req *req,
+ struct rpc_pipe_bind_state *state,
+ DATA_BLOB *auth_token)
+{
+ struct pipe_auth_data *auth = state->cli->auth;
+ struct tevent_req *subreq;
+ NTSTATUS status;
+
+ state->auth3 = true;
+
+ /* Now prepare the auth3 context pdu. */
+ data_blob_free(&state->rpc_out);
+
+ status = create_rpc_bind_auth3(state, state->cli, auth,
+ state->rpc_call_id,
+ auth_token,
+ &state->rpc_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ subreq = rpc_api_pipe_send(state, state->ev, state->cli,
+ &state->rpc_out, DCERPC_PKT_AUTH3,
+ state->rpc_call_id);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, rpc_pipe_bind_step_one_done, req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpc_pipe_bind_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS rpc_pipe_bind(struct rpc_pipe_client *cli,
+ struct pipe_auth_data *auth)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_OK;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = rpc_pipe_bind_send(frame, ev, cli, auth);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = rpc_pipe_bind_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+#define RPCCLI_DEFAULT_TIMEOUT 10000 /* 10 seconds. */
+
+unsigned int rpccli_set_timeout(struct rpc_pipe_client *rpc_cli,
+ unsigned int timeout)
+{
+ if (rpc_cli == NULL) {
+ return RPCCLI_DEFAULT_TIMEOUT;
+ }
+
+ if (rpc_cli->binding_handle == NULL) {
+ return RPCCLI_DEFAULT_TIMEOUT;
+ }
+
+ return dcerpc_binding_handle_set_timeout(rpc_cli->binding_handle,
+ timeout);
+}
+
+bool rpccli_is_connected(struct rpc_pipe_client *rpc_cli)
+{
+ if (rpc_cli == NULL) {
+ return false;
+ }
+
+ if (rpc_cli->binding_handle == NULL) {
+ return false;
+ }
+
+ return dcerpc_binding_handle_is_connected(rpc_cli->binding_handle);
+}
+
+struct rpccli_bh_state {
+ struct rpc_pipe_client *rpc_cli;
+};
+
+static bool rpccli_bh_is_connected(struct dcerpc_binding_handle *h)
+{
+ struct rpccli_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct rpccli_bh_state);
+ struct rpc_cli_transport *transport = hs->rpc_cli->transport;
+
+ if (transport == NULL) {
+ return false;
+ }
+
+ if (transport->is_connected == NULL) {
+ return false;
+ }
+
+ return transport->is_connected(transport->priv);
+}
+
+static uint32_t rpccli_bh_set_timeout(struct dcerpc_binding_handle *h,
+ uint32_t timeout)
+{
+ struct rpccli_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct rpccli_bh_state);
+ struct rpc_cli_transport *transport = hs->rpc_cli->transport;
+ unsigned int old;
+
+ if (transport == NULL) {
+ return RPCCLI_DEFAULT_TIMEOUT;
+ }
+
+ if (transport->set_timeout == NULL) {
+ return RPCCLI_DEFAULT_TIMEOUT;
+ }
+
+ old = transport->set_timeout(transport->priv, timeout);
+ if (old == 0) {
+ return RPCCLI_DEFAULT_TIMEOUT;
+ }
+
+ return old;
+}
+
+static void rpccli_bh_auth_info(struct dcerpc_binding_handle *h,
+ enum dcerpc_AuthType *auth_type,
+ enum dcerpc_AuthLevel *auth_level)
+{
+ struct rpccli_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct rpccli_bh_state);
+
+ if (hs->rpc_cli == NULL) {
+ return;
+ }
+
+ if (hs->rpc_cli->auth == NULL) {
+ return;
+ }
+
+ *auth_type = hs->rpc_cli->auth->auth_type;
+ *auth_level = hs->rpc_cli->auth->auth_level;
+}
+
+struct rpccli_bh_raw_call_state {
+ DATA_BLOB in_data;
+ DATA_BLOB out_data;
+ uint32_t out_flags;
+};
+
+static void rpccli_bh_raw_call_done(struct tevent_req *subreq);
+
+static struct tevent_req *rpccli_bh_raw_call_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *h,
+ const struct GUID *object,
+ uint32_t opnum,
+ uint32_t in_flags,
+ const uint8_t *in_data,
+ size_t in_length)
+{
+ struct rpccli_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct rpccli_bh_state);
+ struct tevent_req *req;
+ struct rpccli_bh_raw_call_state *state;
+ bool ok;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct rpccli_bh_raw_call_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->in_data.data = discard_const_p(uint8_t, in_data);
+ state->in_data.length = in_length;
+
+ ok = rpccli_bh_is_connected(h);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = rpc_api_pipe_req_send(state, ev, hs->rpc_cli,
+ opnum, object, &state->in_data);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpccli_bh_raw_call_done, req);
+
+ return req;
+}
+
+static void rpccli_bh_raw_call_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct rpccli_bh_raw_call_state *state =
+ tevent_req_data(req,
+ struct rpccli_bh_raw_call_state);
+ NTSTATUS status;
+
+ state->out_flags = 0;
+
+ /* TODO: support bigendian responses */
+
+ status = rpc_api_pipe_req_recv(subreq, state, &state->out_data);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS rpccli_bh_raw_call_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **out_data,
+ size_t *out_length,
+ uint32_t *out_flags)
+{
+ struct rpccli_bh_raw_call_state *state =
+ tevent_req_data(req,
+ struct rpccli_bh_raw_call_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_data = talloc_move(mem_ctx, &state->out_data.data);
+ *out_length = state->out_data.length;
+ *out_flags = state->out_flags;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct rpccli_bh_disconnect_state {
+ uint8_t _dummy;
+};
+
+static struct tevent_req *rpccli_bh_disconnect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *h)
+{
+ struct rpccli_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct rpccli_bh_state);
+ struct tevent_req *req;
+ struct rpccli_bh_disconnect_state *state;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct rpccli_bh_disconnect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ ok = rpccli_bh_is_connected(h);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * TODO: do a real async disconnect ...
+ *
+ * For now we do it sync...
+ */
+ TALLOC_FREE(hs->rpc_cli->transport);
+ hs->rpc_cli = NULL;
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS rpccli_bh_disconnect_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool rpccli_bh_ref_alloc(struct dcerpc_binding_handle *h)
+{
+ return true;
+}
+
+static void rpccli_bh_do_ndr_print(struct dcerpc_binding_handle *h,
+ ndr_flags_type ndr_flags,
+ const void *_struct_ptr,
+ const struct ndr_interface_call *call)
+{
+ void *struct_ptr = discard_const(_struct_ptr);
+
+ if (DEBUGLEVEL < 10) {
+ return;
+ }
+
+ if (ndr_flags & NDR_IN) {
+ ndr_print_function_debug(call->ndr_print,
+ call->name,
+ ndr_flags,
+ struct_ptr);
+ }
+ if (ndr_flags & NDR_OUT) {
+ ndr_print_function_debug(call->ndr_print,
+ call->name,
+ ndr_flags,
+ struct_ptr);
+ }
+}
+
+static const struct dcerpc_binding_handle_ops rpccli_bh_ops = {
+ .name = "rpccli",
+ .is_connected = rpccli_bh_is_connected,
+ .set_timeout = rpccli_bh_set_timeout,
+ .auth_info = rpccli_bh_auth_info,
+ .raw_call_send = rpccli_bh_raw_call_send,
+ .raw_call_recv = rpccli_bh_raw_call_recv,
+ .disconnect_send = rpccli_bh_disconnect_send,
+ .disconnect_recv = rpccli_bh_disconnect_recv,
+
+ .ref_alloc = rpccli_bh_ref_alloc,
+ .do_ndr_print = rpccli_bh_do_ndr_print,
+};
+
+/* initialise a rpc_pipe_client binding handle */
+struct dcerpc_binding_handle *rpccli_bh_create(struct rpc_pipe_client *c,
+ const struct GUID *object,
+ const struct ndr_interface_table *table)
+{
+ struct dcerpc_binding_handle *h;
+ struct rpccli_bh_state *hs;
+
+ h = dcerpc_binding_handle_create(c,
+ &rpccli_bh_ops,
+ object,
+ table,
+ &hs,
+ struct rpccli_bh_state,
+ __location__);
+ if (h == NULL) {
+ return NULL;
+ }
+ hs->rpc_cli = c;
+
+ return h;
+}
+
+NTSTATUS rpccli_anon_bind_data(TALLOC_CTX *mem_ctx,
+ struct pipe_auth_data **presult)
+{
+ struct pipe_auth_data *result;
+ struct auth_generic_state *auth_generic_ctx;
+ NTSTATUS status;
+
+ result = talloc_zero(mem_ctx, struct pipe_auth_data);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result->auth_type = DCERPC_AUTH_TYPE_NONE;
+ result->auth_level = DCERPC_AUTH_LEVEL_NONE;
+ result->auth_context_id = 0;
+
+ status = auth_generic_client_prepare(result,
+ &auth_generic_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to create auth_generic context: %s\n",
+ nt_errstr(status)));
+ }
+
+ status = auth_generic_set_username(auth_generic_ctx, "");
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set username: %s\n",
+ nt_errstr(status)));
+ }
+
+ status = auth_generic_set_domain(auth_generic_ctx, "");
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set domain: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = gensec_set_credentials(auth_generic_ctx->gensec_security,
+ auth_generic_ctx->credentials);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to set GENSEC credentials: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ talloc_unlink(auth_generic_ctx, auth_generic_ctx->credentials);
+ auth_generic_ctx->credentials = NULL;
+
+ result->auth_ctx = talloc_move(result, &auth_generic_ctx->gensec_security);
+ talloc_free(auth_generic_ctx);
+ *presult = result;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS rpccli_generic_bind_data(TALLOC_CTX *mem_ctx,
+ enum dcerpc_AuthType auth_type,
+ enum dcerpc_AuthLevel auth_level,
+ const char *server,
+ const char *target_service,
+ const char *domain,
+ const char *username,
+ const char *password,
+ enum credentials_use_kerberos use_kerberos,
+ struct netlogon_creds_CredentialState *creds,
+ struct pipe_auth_data **presult)
+{
+ struct auth_generic_state *auth_generic_ctx;
+ struct pipe_auth_data *result;
+ NTSTATUS status;
+
+ result = talloc_zero(mem_ctx, struct pipe_auth_data);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result->auth_type = auth_type;
+ result->auth_level = auth_level;
+ result->auth_context_id = 1;
+
+ status = auth_generic_client_prepare(result,
+ &auth_generic_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = auth_generic_set_username(auth_generic_ctx, username);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = auth_generic_set_domain(auth_generic_ctx, domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = auth_generic_set_password(auth_generic_ctx, password);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = gensec_set_target_service(auth_generic_ctx->gensec_security, target_service);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = gensec_set_target_hostname(auth_generic_ctx->gensec_security, server);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ cli_credentials_set_kerberos_state(auth_generic_ctx->credentials,
+ use_kerberos,
+ CRED_SPECIFIED);
+ cli_credentials_set_netlogon_creds(auth_generic_ctx->credentials, creds);
+
+ status = auth_generic_client_start_by_authtype(auth_generic_ctx, auth_type, auth_level);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ result->auth_ctx = talloc_move(result, &auth_generic_ctx->gensec_security);
+ talloc_free(auth_generic_ctx);
+ *presult = result;
+ return NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(result);
+ return status;
+}
+
+/* This routine steals the creds pointer that is passed in */
+static NTSTATUS rpccli_generic_bind_data_from_creds(TALLOC_CTX *mem_ctx,
+ enum dcerpc_AuthType auth_type,
+ enum dcerpc_AuthLevel auth_level,
+ const char *server,
+ const char *target_service,
+ struct cli_credentials *creds,
+ struct pipe_auth_data **presult)
+{
+ struct auth_generic_state *auth_generic_ctx;
+ struct pipe_auth_data *result;
+ NTSTATUS status;
+
+ result = talloc_zero(mem_ctx, struct pipe_auth_data);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result->auth_type = auth_type;
+ result->auth_level = auth_level;
+ result->auth_context_id = 1;
+
+ status = auth_generic_client_prepare(result,
+ &auth_generic_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = auth_generic_set_creds(auth_generic_ctx, creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = gensec_set_target_service(auth_generic_ctx->gensec_security, target_service);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = gensec_set_target_hostname(auth_generic_ctx->gensec_security, server);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ status = auth_generic_client_start_by_authtype(auth_generic_ctx, auth_type, auth_level);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ result->auth_ctx = talloc_move(result, &auth_generic_ctx->gensec_security);
+ talloc_free(auth_generic_ctx);
+ *presult = result;
+ return NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(result);
+ return status;
+}
+
+NTSTATUS rpccli_ncalrpc_bind_data(TALLOC_CTX *mem_ctx,
+ struct pipe_auth_data **presult)
+{
+ return rpccli_generic_bind_data(mem_ctx,
+ DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM,
+ DCERPC_AUTH_LEVEL_CONNECT,
+ NULL, /* server */
+ "host", /* target_service */
+ NAME_NT_AUTHORITY, /* domain */
+ "SYSTEM",
+ NULL, /* password */
+ CRED_USE_KERBEROS_DISABLED,
+ NULL, /* netlogon_creds_CredentialState */
+ presult);
+}
+
+/**
+ * Create an rpc pipe client struct, connecting to a tcp port.
+ */
+static NTSTATUS rpc_pipe_open_tcp_port(TALLOC_CTX *mem_ctx, const char *host,
+ const struct sockaddr_storage *ss_addr,
+ uint16_t port,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ struct rpc_pipe_client *result;
+ struct sockaddr_storage addr;
+ NTSTATUS status;
+ int fd;
+
+ result = talloc_zero(mem_ctx, struct rpc_pipe_client);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ result->abstract_syntax = table->syntax_id;
+ result->transfer_syntax = ndr_transfer_syntax_ndr;
+
+ result->desthost = talloc_strdup(result, host);
+ if (result->desthost == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ result->srv_name_slash = talloc_asprintf_strupper_m(
+ result, "\\\\%s", result->desthost);
+ if (result->srv_name_slash == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ result->max_xmit_frag = RPC_MAX_PDU_FRAG_LEN;
+
+ if (ss_addr == NULL) {
+ if (!resolve_name(host, &addr, NBT_NAME_SERVER, false)) {
+ status = NT_STATUS_NOT_FOUND;
+ goto fail;
+ }
+ } else {
+ addr = *ss_addr;
+ }
+
+ status = open_socket_out(&addr, port, 60*1000, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ set_socket_options(fd, lp_socket_options());
+
+ status = rpc_transport_sock_init(result, fd, &result->transport);
+ if (!NT_STATUS_IS_OK(status)) {
+ close(fd);
+ goto fail;
+ }
+
+ result->transport->transport = NCACN_IP_TCP;
+
+ result->binding_handle = rpccli_bh_create(result, NULL, table);
+ if (result->binding_handle == NULL) {
+ TALLOC_FREE(result);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(result);
+ return status;
+}
+
+static NTSTATUS rpccli_epm_map_binding(
+ struct dcerpc_binding_handle *epm_connection,
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ char **pendpoint)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ enum dcerpc_transport_t transport =
+ dcerpc_binding_get_transport(binding);
+ enum dcerpc_transport_t res_transport;
+ struct dcerpc_binding *res_binding = NULL;
+ struct epm_twr_t *map_tower = NULL;
+ struct epm_twr_p_t res_towers = { .twr = NULL };
+ struct policy_handle *entry_handle = NULL;
+ uint32_t num_towers = 0;
+ const uint32_t max_towers = 1;
+ const char *endpoint = NULL;
+ char *tmp = NULL;
+ uint32_t result;
+ NTSTATUS status;
+
+ map_tower = talloc_zero(frame, struct epm_twr_t);
+ if (map_tower == NULL) {
+ goto nomem;
+ }
+
+ status = dcerpc_binding_build_tower(
+ frame, binding, &(map_tower->tower));
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_build_tower failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ res_towers.twr = talloc_array(frame, struct epm_twr_t, max_towers);
+ if (res_towers.twr == NULL) {
+ goto nomem;
+ }
+
+ entry_handle = talloc_zero(frame, struct policy_handle);
+ if (entry_handle == NULL) {
+ goto nomem;
+ }
+
+ status = dcerpc_epm_Map(
+ epm_connection,
+ frame,
+ NULL,
+ map_tower,
+ entry_handle,
+ max_towers,
+ &num_towers,
+ &res_towers,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_epm_Map failed: %s\n", nt_errstr(status));
+ goto done;
+ }
+
+ if (result != EPMAPPER_STATUS_OK) {
+ DBG_DEBUG("dcerpc_epm_Map returned %"PRIu32"\n", result);
+ status = NT_STATUS_NOT_FOUND;
+ goto done;
+ }
+
+ if (num_towers != 1) {
+ DBG_DEBUG("dcerpc_epm_Map returned %"PRIu32" towers\n",
+ num_towers);
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_binding_from_tower(
+ frame, &(res_towers.twr->tower), &res_binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_from_tower failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ res_transport = dcerpc_binding_get_transport(res_binding);
+ if (res_transport != transport) {
+ DBG_DEBUG("dcerpc_epm_Map returned transport %d, "
+ "expected %d\n",
+ (int)res_transport,
+ (int)transport);
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ endpoint = dcerpc_binding_get_string_option(res_binding, "endpoint");
+ if (endpoint == NULL) {
+ DBG_DEBUG("dcerpc_epm_Map returned no endpoint\n");
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ tmp = talloc_strdup(mem_ctx, endpoint);
+ if (tmp == NULL) {
+ goto nomem;
+ }
+ *pendpoint = tmp;
+
+ status = NT_STATUS_OK;
+ goto done;
+
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS rpccli_epm_map_interface(
+ struct dcerpc_binding_handle *epm_connection,
+ enum dcerpc_transport_t transport,
+ const struct ndr_syntax_id *iface,
+ TALLOC_CTX *mem_ctx,
+ char **pendpoint)
+{
+ struct dcerpc_binding *binding = NULL;
+ char *endpoint = NULL;
+ NTSTATUS status;
+
+ status = dcerpc_parse_binding(mem_ctx, "", &binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_parse_binding failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dcerpc_binding_set_transport(binding, transport);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_set_transport failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dcerpc_binding_set_abstract_syntax(binding, iface);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_set_abstract_syntax failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = rpccli_epm_map_binding(
+ epm_connection, binding, mem_ctx, &endpoint);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpccli_epm_map_binding failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+ *pendpoint = endpoint;
+
+done:
+ TALLOC_FREE(binding);
+ return status;
+}
+
+/**
+ * Determine the tcp port on which a dcerpc interface is listening
+ * for the ncacn_ip_tcp transport via the endpoint mapper of the
+ * target host.
+ */
+static NTSTATUS rpc_pipe_get_tcp_port(const char *host,
+ const struct sockaddr_storage *addr,
+ const struct ndr_interface_table *table,
+ uint16_t *pport)
+{
+ NTSTATUS status;
+ struct rpc_pipe_client *epm_pipe = NULL;
+ struct pipe_auth_data *auth = NULL;
+ char *endpoint = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if (pport == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (ndr_syntax_id_equal(&table->syntax_id,
+ &ndr_table_epmapper.syntax_id)) {
+ *pport = 135;
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /* open the connection to the endpoint mapper */
+ status = rpc_pipe_open_tcp_port(tmp_ctx, host, addr, 135,
+ &ndr_table_epmapper,
+ &epm_pipe);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpccli_anon_bind_data(tmp_ctx, &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_pipe_bind(epm_pipe, auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpccli_epm_map_interface(
+ epm_pipe->binding_handle,
+ NCACN_IP_TCP,
+ &table->syntax_id,
+ tmp_ctx,
+ &endpoint);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpccli_epm_map_interface failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ *pport = (uint16_t)atoi(endpoint);
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/**
+ * Create a rpc pipe client struct, connecting to a host via tcp.
+ * The port is determined by asking the endpoint mapper on the given
+ * host.
+ */
+static NTSTATUS rpc_pipe_open_tcp(
+ TALLOC_CTX *mem_ctx,
+ const char *host,
+ const struct sockaddr_storage *addr,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ NTSTATUS status;
+ uint16_t port = 0;
+
+ status = rpc_pipe_get_tcp_port(host, addr, table, &port);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return rpc_pipe_open_tcp_port(mem_ctx, host, addr, port,
+ table, presult);
+}
+
+static NTSTATUS rpc_pipe_get_ncalrpc_name(
+ const struct ndr_syntax_id *iface,
+ TALLOC_CTX *mem_ctx,
+ char **psocket_name)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct rpc_pipe_client *epm_pipe = NULL;
+ struct pipe_auth_data *auth = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ bool is_epm;
+
+ is_epm = ndr_syntax_id_equal(iface, &ndr_table_epmapper.syntax_id);
+ if (is_epm) {
+ char *endpoint = talloc_strdup(mem_ctx, "EPMAPPER");
+ if (endpoint == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ *psocket_name = endpoint;
+ goto done;
+ }
+
+ status = rpc_pipe_open_ncalrpc(
+ frame, &ndr_table_epmapper, &epm_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_open_ncalrpc failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = rpccli_anon_bind_data(epm_pipe, &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpccli_anon_bind_data failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = rpc_pipe_bind(epm_pipe, auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_bind failed: %s\n", nt_errstr(status));
+ goto done;
+ }
+
+ status = rpccli_epm_map_interface(
+ epm_pipe->binding_handle,
+ NCALRPC,
+ iface,
+ mem_ctx,
+ psocket_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpccli_epm_map_interface failed: %s\n",
+ nt_errstr(status));
+ }
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/********************************************************************
+ Create a rpc pipe client struct, connecting to a unix domain socket
+ ********************************************************************/
+NTSTATUS rpc_pipe_open_ncalrpc(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ char *socket_name = NULL;
+ struct rpc_pipe_client *result;
+ struct sockaddr_un addr = { .sun_family = AF_UNIX };
+ socklen_t salen = sizeof(addr);
+ int pathlen;
+ NTSTATUS status;
+ int fd = -1;
+
+ result = talloc_zero(mem_ctx, struct rpc_pipe_client);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = rpc_pipe_get_ncalrpc_name(
+ &table->syntax_id, result, &socket_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_get_ncalrpc_name failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ pathlen = snprintf(
+ addr.sun_path,
+ sizeof(addr.sun_path),
+ "%s/%s",
+ lp_ncalrpc_dir(),
+ socket_name);
+ if ((pathlen < 0) || ((size_t)pathlen >= sizeof(addr.sun_path))) {
+ DBG_DEBUG("socket_path for %s too long\n", socket_name);
+ status = NT_STATUS_NAME_TOO_LONG;
+ goto fail;
+ }
+ TALLOC_FREE(socket_name);
+
+ result->abstract_syntax = table->syntax_id;
+ result->transfer_syntax = ndr_transfer_syntax_ndr;
+
+ result->desthost = get_myname(result);
+ if (result->desthost == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ result->srv_name_slash = talloc_asprintf_strupper_m(
+ result, "\\\\%s", result->desthost);
+ if (result->srv_name_slash == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ result->max_xmit_frag = RPC_MAX_PDU_FRAG_LEN;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ if (connect(fd, (struct sockaddr *)(void *)&addr, salen) == -1) {
+ DBG_WARNING("connect(%s) failed: %s\n",
+ addr.sun_path,
+ strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+
+ status = rpc_transport_sock_init(result, fd, &result->transport);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ fd = -1;
+
+ result->transport->transport = NCALRPC;
+
+ result->binding_handle = rpccli_bh_create(result, NULL, table);
+ if (result->binding_handle == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+
+ fail:
+ if (fd != -1) {
+ close(fd);
+ }
+ TALLOC_FREE(result);
+ return status;
+}
+
+NTSTATUS rpc_pipe_open_local_np(
+ TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ const char *remote_client_name,
+ const struct tsocket_address *remote_client_addr,
+ const char *local_server_name,
+ const struct tsocket_address *local_server_addr,
+ const struct auth_session_info *session_info,
+ struct rpc_pipe_client **presult)
+{
+ struct rpc_pipe_client *result = NULL;
+ struct pipe_auth_data *auth = NULL;
+ const char *pipe_name = NULL;
+ struct tstream_context *npa_stream = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ int ret;
+
+ result = talloc_zero(mem_ctx, struct rpc_pipe_client);
+ if (result == NULL) {
+ goto fail;
+ }
+ result->abstract_syntax = table->syntax_id;
+ result->transfer_syntax = ndr_transfer_syntax_ndr;
+ result->max_xmit_frag = RPC_MAX_PDU_FRAG_LEN;
+
+ pipe_name = dcerpc_default_transport_endpoint(
+ result, NCACN_NP, table);
+ if (pipe_name == NULL) {
+ DBG_DEBUG("dcerpc_default_transport_endpoint failed\n");
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
+ }
+
+ if (local_server_name == NULL) {
+ result->desthost = get_myname(result);
+ } else {
+ result->desthost = talloc_strdup(result, local_server_name);
+ }
+ if (result->desthost == NULL) {
+ goto fail;
+ }
+ result->srv_name_slash = talloc_asprintf_strupper_m(
+ result, "\\\\%s", result->desthost);
+ if (result->srv_name_slash == NULL) {
+ goto fail;
+ }
+
+ ret = local_np_connect(
+ pipe_name,
+ NCALRPC,
+ remote_client_name,
+ remote_client_addr,
+ local_server_name,
+ local_server_addr,
+ session_info,
+ true,
+ result,
+ &npa_stream);
+ if (ret != 0) {
+ DBG_DEBUG("local_np_connect for %s and "
+ "user %s\\%s failed: %s\n",
+ pipe_name,
+ session_info->info->domain_name,
+ session_info->info->account_name,
+ strerror(ret));
+ status = map_nt_error_from_unix(ret);
+ goto fail;
+ }
+
+ status = rpc_transport_tstream_init(
+ result, &npa_stream, &result->transport);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_transport_tstream_init failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ result->binding_handle = rpccli_bh_create(result, NULL, table);
+ if (result->binding_handle == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_DEBUG("Failed to create binding handle.\n");
+ goto fail;
+ }
+
+ status = rpccli_anon_bind_data(result, &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpccli_anon_bind_data failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = rpc_pipe_bind(result, auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_bind failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+
+fail:
+ TALLOC_FREE(result);
+ return status;
+}
+
+struct rpc_pipe_client_np_ref {
+ struct cli_state *cli;
+ struct rpc_pipe_client *pipe;
+};
+
+static int rpc_pipe_client_np_ref_destructor(struct rpc_pipe_client_np_ref *np_ref)
+{
+ DLIST_REMOVE(np_ref->cli->pipe_list, np_ref->pipe);
+ return 0;
+}
+
+/****************************************************************************
+ Open a named pipe over SMB to a remote server.
+ *
+ * CAVEAT CALLER OF THIS FUNCTION:
+ * The returned rpc_pipe_client saves a copy of the cli_state cli pointer,
+ * so be sure that this function is called AFTER any structure (vs pointer)
+ * assignment of the cli. In particular, libsmbclient does structure
+ * assignments of cli, which invalidates the data in the returned
+ * rpc_pipe_client if this function is called before the structure assignment
+ * of cli.
+ *
+ ****************************************************************************/
+
+struct rpc_pipe_open_np_state {
+ struct cli_state *cli;
+ const struct ndr_interface_table *table;
+ struct rpc_pipe_client *result;
+};
+
+static void rpc_pipe_open_np_done(struct tevent_req *subreq);
+
+struct tevent_req *rpc_pipe_open_np_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const struct ndr_interface_table *table)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct rpc_pipe_open_np_state *state = NULL;
+ struct rpc_pipe_client *result = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct rpc_pipe_open_np_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli = cli;
+ state->table = table;
+
+ state->result = talloc_zero(state, struct rpc_pipe_client);
+ if (tevent_req_nomem(state->result, req)) {
+ return tevent_req_post(req, ev);
+ }
+ result = state->result;
+
+ result->abstract_syntax = table->syntax_id;
+ result->transfer_syntax = ndr_transfer_syntax_ndr;
+
+ result->desthost = talloc_strdup(
+ result, smbXcli_conn_remote_name(cli->conn));
+ if (tevent_req_nomem(result->desthost, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ result->srv_name_slash = talloc_asprintf_strupper_m(
+ result, "\\\\%s", result->desthost);
+ if (tevent_req_nomem(result->srv_name_slash, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ result->max_xmit_frag = RPC_MAX_PDU_FRAG_LEN;
+
+ subreq = rpc_transport_np_init_send(state, ev, cli, table);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_pipe_open_np_done, req);
+ return req;
+}
+
+static void rpc_pipe_open_np_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_pipe_open_np_state *state = tevent_req_data(
+ req, struct rpc_pipe_open_np_state);
+ struct rpc_pipe_client *result = state->result;
+ struct rpc_pipe_client_np_ref *np_ref = NULL;
+ NTSTATUS status;
+
+ status = rpc_transport_np_init_recv(
+ subreq, result, &result->transport);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ result->transport->transport = NCACN_NP;
+
+ np_ref = talloc(result->transport, struct rpc_pipe_client_np_ref);
+ if (tevent_req_nomem(np_ref, req)) {
+ return;
+ }
+ np_ref->cli = state->cli;
+ np_ref->pipe = result;
+
+ DLIST_ADD(np_ref->cli->pipe_list, np_ref->pipe);
+ talloc_set_destructor(np_ref, rpc_pipe_client_np_ref_destructor);
+
+ result->binding_handle = rpccli_bh_create(result, NULL, state->table);
+ if (tevent_req_nomem(result->binding_handle, req)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS rpc_pipe_open_np_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **_result)
+{
+ struct rpc_pipe_open_np_state *state = tevent_req_data(
+ req, struct rpc_pipe_open_np_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *_result = talloc_move(mem_ctx, &state->result);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpc_pipe_open_np(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(cli);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = rpc_pipe_open_np_send(ev, ev, cli, table);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = rpc_pipe_open_np_recv(req, NULL, presult);
+fail:
+ TALLOC_FREE(req);
+ TALLOC_FREE(ev);
+ return status;
+}
+
+/****************************************************************************
+ Open a pipe to a remote server.
+ ****************************************************************************/
+
+static NTSTATUS cli_rpc_pipe_open(struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ const struct ndr_interface_table *table,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **presult)
+{
+ switch (transport) {
+ case NCACN_IP_TCP:
+ return rpc_pipe_open_tcp(NULL,
+ remote_name,
+ remote_sockaddr,
+ table, presult);
+ case NCACN_NP:
+ return rpc_pipe_open_np(cli, table, presult);
+ default:
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+}
+
+/****************************************************************************
+ Open a named pipe to an SMB server and bind anonymously.
+ ****************************************************************************/
+
+NTSTATUS cli_rpc_pipe_open_noauth_transport(struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ const struct ndr_interface_table *table,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **presult)
+{
+ struct rpc_pipe_client *result;
+ struct pipe_auth_data *auth;
+ NTSTATUS status;
+
+ status = cli_rpc_pipe_open(cli,
+ transport,
+ table,
+ remote_name,
+ remote_sockaddr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = rpccli_anon_bind_data(result, &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("rpccli_anon_bind_data returned %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(result);
+ return status;
+ }
+
+ /*
+ * This is a bit of an abstraction violation due to the fact that an
+ * anonymous bind on an authenticated SMB inherits the user/domain
+ * from the enclosing SMB creds
+ */
+
+ if (transport == NCACN_NP) {
+ struct smbXcli_session *session;
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ session = cli->smb2.session;
+ } else {
+ session = cli->smb1.session;
+ }
+
+ status = smbXcli_session_application_key(session, auth,
+ &auth->transport_session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ auth->transport_session_key = data_blob_null;
+ }
+ }
+
+ status = rpc_pipe_bind(result, auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ int lvl = 0;
+ if (ndr_syntax_id_equal(&table->syntax_id,
+ &ndr_table_dssetup.syntax_id)) {
+ /* non AD domains just don't have this pipe, avoid
+ * level 0 statement in that case - gd */
+ lvl = 3;
+ }
+ DEBUG(lvl, ("cli_rpc_pipe_open_noauth: rpc_pipe_bind for pipe "
+ "%s failed with error %s\n",
+ table->name,
+ nt_errstr(status) ));
+ TALLOC_FREE(result);
+ return status;
+ }
+
+ DEBUG(10,("cli_rpc_pipe_open_noauth: opened pipe %s to machine "
+ "%s and bound anonymously.\n",
+ table->name,
+ result->desthost));
+
+ *presult = result;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+
+NTSTATUS cli_rpc_pipe_open_noauth(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult)
+{
+ const char *remote_name = smbXcli_conn_remote_name(cli->conn);
+ const struct sockaddr_storage *remote_sockaddr =
+ smbXcli_conn_remote_sockaddr(cli->conn);
+
+ return cli_rpc_pipe_open_noauth_transport(cli, NCACN_NP,
+ table,
+ remote_name,
+ remote_sockaddr,
+ presult);
+}
+
+/****************************************************************************
+ Open a named pipe to an SMB server and bind using the mech specified
+
+ This routine references the creds pointer that is passed in
+ ****************************************************************************/
+
+NTSTATUS cli_rpc_pipe_open_with_creds(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ enum dcerpc_AuthType auth_type,
+ enum dcerpc_AuthLevel auth_level,
+ const char *server,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct cli_credentials *creds,
+ struct rpc_pipe_client **presult)
+{
+ struct rpc_pipe_client *result;
+ struct pipe_auth_data *auth = NULL;
+ const char *target_service = table->authservices->names[0];
+ NTSTATUS status;
+
+ status = cli_rpc_pipe_open(cli,
+ transport,
+ table,
+ server,
+ remote_sockaddr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = rpccli_generic_bind_data_from_creds(result,
+ auth_type, auth_level,
+ server, target_service,
+ creds,
+ &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("rpccli_generic_bind_data_from_creds returned %s\n",
+ nt_errstr(status));
+ goto err;
+ }
+
+ status = rpc_pipe_bind(result, auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("cli_rpc_pipe_bind failed with error %s\n",
+ nt_errstr(status));
+ goto err;
+ }
+
+ DBG_DEBUG("opened pipe %s to machine %s and bound as user %s.\n",
+ table->name,
+ result->desthost,
+ cli_credentials_get_unparsed_name(creds, talloc_tos()));
+
+ *presult = result;
+ return NT_STATUS_OK;
+
+ err:
+
+ TALLOC_FREE(result);
+ return status;
+}
+
+NTSTATUS cli_rpc_pipe_open_bind_schannel(
+ struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *netlogon_creds,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **_rpccli)
+{
+ struct rpc_pipe_client *rpccli;
+ struct pipe_auth_data *rpcauth;
+ const char *target_service = table->authservices->names[0];
+ struct cli_credentials *cli_creds;
+ enum dcerpc_AuthLevel auth_level;
+ NTSTATUS status;
+
+ status = cli_rpc_pipe_open(cli,
+ transport,
+ table,
+ remote_name,
+ remote_sockaddr,
+ &rpccli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ auth_level = netlogon_creds_cli_auth_level(netlogon_creds);
+
+ status = netlogon_creds_bind_cli_credentials(
+ netlogon_creds, rpccli, &cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("netlogon_creds_bind_cli_credentials failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(rpccli);
+ return status;
+ }
+
+ status = rpccli_generic_bind_data_from_creds(rpccli,
+ DCERPC_AUTH_TYPE_SCHANNEL,
+ auth_level,
+ rpccli->desthost,
+ target_service,
+ cli_creds,
+ &rpcauth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("rpccli_generic_bind_data_from_creds returned %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(rpccli);
+ return status;
+ }
+
+ status = rpc_pipe_bind(rpccli, rpcauth);
+
+ /* No TALLOC_FREE, gensec takes references */
+ talloc_unlink(rpccli, cli_creds);
+ cli_creds = NULL;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_bind failed with error %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(rpccli);
+ return status;
+ }
+
+ *_rpccli = rpccli;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_rpc_pipe_open_schannel_with_creds(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *netlogon_creds,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **_rpccli)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct rpc_pipe_client *rpccli;
+ struct netlogon_creds_cli_lck *lck;
+ NTSTATUS status;
+
+ status = netlogon_creds_cli_lck(
+ netlogon_creds, NETLOGON_CREDS_CLI_LCK_EXCLUSIVE,
+ frame, &lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("netlogon_creds_cli_lck returned %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = cli_rpc_pipe_open_bind_schannel(cli,
+ table,
+ transport,
+ netlogon_creds,
+ remote_name,
+ remote_sockaddr,
+ &rpccli);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ netlogon_creds_cli_delete_lck(netlogon_creds);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("cli_rpc_pipe_open_bind_schannel failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (ndr_syntax_id_equal(&table->syntax_id,
+ &ndr_table_netlogon.syntax_id)) {
+ status = netlogon_creds_cli_check(netlogon_creds,
+ rpccli->binding_handle,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("netlogon_creds_cli_check failed with %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ DBG_DEBUG("opened pipe %s to machine %s with key %s "
+ "and bound using schannel.\n",
+ table->name, rpccli->desthost,
+ netlogon_creds_cli_debug_string(netlogon_creds, lck));
+
+ TALLOC_FREE(frame);
+
+ *_rpccli = rpccli;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cli_get_session_key(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *cli,
+ DATA_BLOB *session_key)
+{
+ NTSTATUS status;
+ struct pipe_auth_data *a;
+ struct gensec_security *gensec_security;
+ DATA_BLOB sk = { .data = NULL };
+ bool make_dup = false;
+
+ if (!session_key || !cli) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ a = cli->auth;
+
+ if (a == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (cli->auth->auth_type) {
+ case DCERPC_AUTH_TYPE_NONE:
+ sk = data_blob_const(a->transport_session_key.data,
+ a->transport_session_key.length);
+ make_dup = true;
+ break;
+ default:
+ gensec_security = a->auth_ctx;
+ status = gensec_session_key(gensec_security, mem_ctx, &sk);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ make_dup = false;
+ break;
+ }
+
+ if (!sk.data) {
+ return NT_STATUS_NO_USER_SESSION_KEY;
+ }
+
+ if (make_dup) {
+ *session_key = data_blob_dup_talloc(mem_ctx, sk);
+ } else {
+ *session_key = sk;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/cli_pipe.h b/source3/rpc_client/cli_pipe.h
new file mode 100644
index 0000000..d9826ca
--- /dev/null
+++ b/source3/rpc_client/cli_pipe.h
@@ -0,0 +1,143 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * RPC Pipe client routines
+ *
+ * Copyright (c) 2005 Jeremy Allison
+ * Copyright (c) 2010 Simo Sorce
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CLI_PIPE_H
+#define _CLI_PIPE_H
+
+#include "rpc_client/rpc_client.h"
+#include "auth/credentials/credentials.h"
+
+/* The following definitions come from rpc_client/cli_pipe.c */
+
+struct tevent_req *rpc_pipe_bind_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_pipe_client *cli,
+ struct pipe_auth_data *auth);
+
+NTSTATUS rpc_pipe_bind_recv(struct tevent_req *req);
+
+NTSTATUS rpc_pipe_bind(struct rpc_pipe_client *cli,
+ struct pipe_auth_data *auth);
+
+struct tevent_req *rpc_pipe_open_np_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const struct ndr_interface_table *table);
+NTSTATUS rpc_pipe_open_np_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **_result);
+NTSTATUS rpc_pipe_open_np(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult);
+
+unsigned int rpccli_set_timeout(struct rpc_pipe_client *cli,
+ unsigned int timeout);
+
+bool rpccli_is_connected(struct rpc_pipe_client *rpc_cli);
+
+NTSTATUS rpccli_ncalrpc_bind_data(TALLOC_CTX *mem_ctx,
+ struct pipe_auth_data **presult);
+
+NTSTATUS rpccli_anon_bind_data(TALLOC_CTX *mem_ctx,
+ struct pipe_auth_data **presult);
+
+NTSTATUS rpc_pipe_open_ncalrpc(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult);
+
+NTSTATUS rpc_pipe_open_local_np(
+ TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ const char *remote_client_name,
+ const struct tsocket_address *remote_client_addr,
+ const char *local_server_name,
+ const struct tsocket_address *local_server_addr,
+ const struct auth_session_info *session_info,
+ struct rpc_pipe_client **presult);
+
+struct dcerpc_binding_handle *rpccli_bh_create(struct rpc_pipe_client *c,
+ const struct GUID *object,
+ const struct ndr_interface_table *table);
+
+NTSTATUS cli_rpc_pipe_open_noauth(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **presult);
+
+NTSTATUS cli_rpc_pipe_open_noauth_transport(struct cli_state *cli,
+ enum dcerpc_transport_t transport,
+ const struct ndr_interface_table *table,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **presult);
+
+/****************************************************************************
+ Open a named pipe to an SMB server and bind using the mech specified
+
+ This routine steals the creds pointer that is passed in
+ ****************************************************************************/
+
+NTSTATUS cli_rpc_pipe_open_with_creds(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ enum dcerpc_AuthType auth_type,
+ enum dcerpc_AuthLevel auth_level,
+ const char *server,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct cli_credentials *creds,
+ struct rpc_pipe_client **presult);
+
+NTSTATUS cli_rpc_pipe_open_bind_schannel(
+ struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *netlogon_creds,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **_rpccli);
+NTSTATUS cli_rpc_pipe_open_schannel_with_creds(struct cli_state *cli,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ struct netlogon_creds_cli_context *netlogon_creds,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **_rpccli);
+
+NTSTATUS cli_rpc_pipe_open_schannel(struct cli_state *cli,
+ struct messaging_context *msg_ctx,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ const char *domain,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **presult,
+ TALLOC_CTX *mem_ctx,
+ struct netlogon_creds_cli_context **pcreds);
+
+NTSTATUS cli_get_session_key(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *cli,
+ DATA_BLOB *session_key);
+
+#endif /* _CLI_PIPE_H */
+
+/* vim: set ts=8 sw=8 noet cindent ft=c.doxygen: */
diff --git a/source3/rpc_client/cli_pipe_schannel.c b/source3/rpc_client/cli_pipe_schannel.c
new file mode 100644
index 0000000..c33fc5b
--- /dev/null
+++ b/source3/rpc_client/cli_pipe_schannel.c
@@ -0,0 +1,120 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Largely rewritten by Jeremy Allison 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/ndr_schannel.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../libcli/auth/schannel.h"
+#include "rpc_client/cli_netlogon.h"
+#include "rpc_client/cli_pipe.h"
+#include "librpc/rpc/dcerpc.h"
+#include "passdb.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_CLI
+
+/****************************************************************************
+ Open a named pipe to an SMB server and bind using schannel (bind type 68).
+ Fetch the session key ourselves using a temporary netlogon pipe.
+ ****************************************************************************/
+
+NTSTATUS cli_rpc_pipe_open_schannel(struct cli_state *cli,
+ struct messaging_context *msg_ctx,
+ const struct ndr_interface_table *table,
+ enum dcerpc_transport_t transport,
+ const char *domain,
+ const char *remote_name,
+ const struct sockaddr_storage *remote_sockaddr,
+ struct rpc_pipe_client **presult,
+ TALLOC_CTX *mem_ctx,
+ struct netlogon_creds_cli_context **pcreds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct rpc_pipe_client *result = NULL;
+ NTSTATUS status;
+ struct cli_credentials *cli_creds = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds = NULL;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ uint32_t netlogon_flags;
+
+ status = pdb_get_trust_credentials(domain, NULL,
+ frame, &cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_create_netlogon_creds_ctx(cli_creds,
+ remote_name,
+ msg_ctx,
+ frame,
+ &netlogon_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = rpccli_setup_netlogon_creds(cli, transport,
+ netlogon_creds,
+ false, /* force_reauth */
+ cli_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = netlogon_creds_cli_get(netlogon_creds, frame, &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ netlogon_flags = creds->negotiate_flags;
+ TALLOC_FREE(creds);
+
+ if (netlogon_flags & NETLOGON_NEG_AUTHENTICATED_RPC) {
+ status = cli_rpc_pipe_open_schannel_with_creds(cli, table,
+ transport,
+ netlogon_creds,
+ remote_name,
+ remote_sockaddr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ } else {
+ status = cli_rpc_pipe_open_noauth(cli, table, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ *presult = result;
+ if (pcreds != NULL) {
+ *pcreds = talloc_move(mem_ctx, &netlogon_creds);
+ }
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/cli_samr.c b/source3/rpc_client/cli_samr.c
new file mode 100644
index 0000000..f5a6b17
--- /dev/null
+++ b/source3/rpc_client/cli_samr.c
@@ -0,0 +1,654 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+ Copyright (C) Tim Potter 2000-2001,
+ Copyright (C) Andrew Tridgell 1992-1997,2000,
+ Copyright (C) Rafal Szczesniak 2002.
+ Copyright (C) Jeremy Allison 2005.
+ Copyright (C) Guenther Deschner 2008.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_client/rpc_client.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/cli_samr.h"
+#include "rpc_client/init_lsa.h"
+#include "rpc_client/init_samr.h"
+#include "librpc/rpc/dcerpc_samr.h"
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+/* User change password */
+
+NTSTATUS dcerpc_samr_chgpasswd_user(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *user_handle,
+ const char *newpassword,
+ const char *oldpassword,
+ NTSTATUS *presult)
+{
+ NTSTATUS status;
+ int rc;
+ struct samr_Password hash1, hash2, hash3, hash4, hash5, hash6;
+
+ uint8_t old_nt_hash[16] = {0};
+ uint8_t old_lm_hash[16] = {0};
+ uint8_t new_nt_hash[16] = {0};
+ uint8_t new_lm_hash[16] = {0};
+
+ DEBUG(10,("rpccli_samr_chgpasswd_user\n"));
+
+ E_md4hash(oldpassword, old_nt_hash);
+ E_md4hash(newpassword, new_nt_hash);
+
+ E_deshash(oldpassword, old_lm_hash);
+ E_deshash(newpassword, new_lm_hash);
+
+ rc = E_old_pw_hash(new_lm_hash, old_lm_hash, hash1.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+ rc = E_old_pw_hash(old_lm_hash, new_lm_hash, hash2.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+ rc = E_old_pw_hash(new_nt_hash, old_nt_hash, hash3.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+ rc = E_old_pw_hash(old_nt_hash, new_nt_hash, hash4.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+ rc = E_old_pw_hash(old_lm_hash, new_nt_hash, hash5.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+ rc = E_old_pw_hash(old_nt_hash, new_lm_hash, hash6.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+
+ status = dcerpc_samr_ChangePasswordUser(h,
+ mem_ctx,
+ user_handle,
+ true,
+ &hash1,
+ &hash2,
+ true,
+ &hash3,
+ &hash4,
+ true,
+ &hash5,
+ true,
+ &hash6,
+ presult);
+
+done:
+ ZERO_ARRAY(old_nt_hash);
+ ZERO_ARRAY(old_lm_hash);
+ ZERO_ARRAY(new_nt_hash);
+ ZERO_ARRAY(new_lm_hash);
+
+ return status;
+}
+
+NTSTATUS rpccli_samr_chgpasswd_user(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *user_handle,
+ const char *newpassword,
+ const char *oldpassword)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ status = dcerpc_samr_chgpasswd_user(cli->binding_handle,
+ mem_ctx,
+ user_handle,
+ newpassword,
+ oldpassword,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+/* User change password */
+
+NTSTATUS dcerpc_samr_chgpasswd_user2(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword,
+ NTSTATUS *presult)
+{
+ NTSTATUS status;
+ int rc;
+ struct samr_CryptPassword new_nt_password;
+ struct samr_CryptPassword new_lm_password;
+ struct samr_Password old_nt_hash_enc;
+ struct samr_Password old_lanman_hash_enc;
+
+ uint8_t old_nt_hash[16] = { 0 };
+ uint8_t old_lanman_hash[16];
+ uint8_t new_nt_hash[16];
+ uint8_t new_lanman_hash[16];
+ struct lsa_String server, account;
+
+ DATA_BLOB session_key = data_blob_const(old_nt_hash, 16);
+
+ DEBUG(10,("rpccli_samr_chgpasswd_user2\n"));
+
+ init_lsa_String(&server, srv_name_slash);
+ init_lsa_String(&account, username);
+
+ /* Calculate the MD4 hash (NT compatible) of the password */
+ E_md4hash(oldpassword, old_nt_hash);
+ E_md4hash(newpassword, new_nt_hash);
+
+ if (lp_client_lanman_auth() &&
+ E_deshash(newpassword, new_lanman_hash) &&
+ E_deshash(oldpassword, old_lanman_hash)) {
+ /* E_deshash returns false for 'long' passwords (> 14
+ DOS chars). This allows us to match Win2k, which
+ does not store a LM hash for these passwords (which
+ would reduce the effective password length to 14) */
+ status = init_samr_CryptPassword(newpassword,
+ &session_key,
+ &new_lm_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ rc = E_old_pw_hash(new_nt_hash, old_lanman_hash, old_lanman_hash_enc.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+ } else {
+ ZERO_STRUCT(new_lm_password);
+ ZERO_STRUCT(old_lanman_hash_enc);
+ }
+
+ status = init_samr_CryptPassword(newpassword,
+ &session_key,
+ &new_nt_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ rc = E_old_pw_hash(new_nt_hash, old_nt_hash, old_nt_hash_enc.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+
+ status = dcerpc_samr_ChangePasswordUser2(h,
+ mem_ctx,
+ &server,
+ &account,
+ &new_nt_password,
+ &old_nt_hash_enc,
+ true,
+ &new_lm_password,
+ &old_lanman_hash_enc,
+ presult);
+
+done:
+ ZERO_STRUCT(new_nt_password);
+ ZERO_STRUCT(new_lm_password);
+ ZERO_STRUCT(old_nt_hash_enc);
+ ZERO_STRUCT(old_lanman_hash_enc);
+ ZERO_ARRAY(new_nt_hash);
+ ZERO_ARRAY(new_lanman_hash);
+ ZERO_ARRAY(old_nt_hash);
+ ZERO_ARRAY(old_lanman_hash);
+
+ return status;
+}
+
+NTSTATUS rpccli_samr_chgpasswd_user2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ status = dcerpc_samr_chgpasswd_user2(cli->binding_handle,
+ mem_ctx,
+ cli->srv_name_slash,
+ username,
+ newpassword,
+ oldpassword,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+/* User change password given blobs */
+
+NTSTATUS dcerpc_samr_chng_pswd_auth_crap(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ DATA_BLOB new_nt_password_blob,
+ DATA_BLOB old_nt_hash_enc_blob,
+ DATA_BLOB new_lm_password_blob,
+ DATA_BLOB old_lm_hash_enc_blob,
+ NTSTATUS *presult)
+{
+ NTSTATUS status;
+ struct samr_CryptPassword new_nt_password;
+ struct samr_CryptPassword new_lm_password;
+ struct samr_Password old_nt_hash_enc;
+ struct samr_Password old_lm_hash_enc;
+ struct lsa_String server, account;
+
+ DEBUG(10,("rpccli_samr_chng_pswd_auth_crap\n"));
+
+ ZERO_STRUCT(new_nt_password);
+ ZERO_STRUCT(new_lm_password);
+ ZERO_STRUCT(old_nt_hash_enc);
+ ZERO_STRUCT(old_lm_hash_enc);
+
+ init_lsa_String(&server, srv_name_slash);
+ init_lsa_String(&account, username);
+
+ if (new_nt_password_blob.data && new_nt_password_blob.length >= 516) {
+ memcpy(&new_nt_password.data, new_nt_password_blob.data, 516);
+ }
+
+ if (new_lm_password_blob.data && new_lm_password_blob.length >= 516) {
+ memcpy(&new_lm_password.data, new_lm_password_blob.data, 516);
+ }
+
+ if (old_nt_hash_enc_blob.data && old_nt_hash_enc_blob.length >= 16) {
+ memcpy(&old_nt_hash_enc.hash, old_nt_hash_enc_blob.data, 16);
+ }
+
+ if (old_lm_hash_enc_blob.data && old_lm_hash_enc_blob.length >= 16) {
+ memcpy(&old_lm_hash_enc.hash, old_lm_hash_enc_blob.data, 16);
+ }
+
+ status = dcerpc_samr_ChangePasswordUser2(h,
+ mem_ctx,
+ &server,
+ &account,
+ &new_nt_password,
+ &old_nt_hash_enc,
+ true,
+ &new_lm_password,
+ &old_lm_hash_enc,
+ presult);
+
+ return status;
+}
+
+NTSTATUS rpccli_samr_chng_pswd_auth_crap(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *username,
+ DATA_BLOB new_nt_password_blob,
+ DATA_BLOB old_nt_hash_enc_blob,
+ DATA_BLOB new_lm_password_blob,
+ DATA_BLOB old_lm_hash_enc_blob)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ status = dcerpc_samr_chng_pswd_auth_crap(cli->binding_handle,
+ mem_ctx,
+ cli->srv_name_slash,
+ username,
+ new_nt_password_blob,
+ old_nt_hash_enc_blob,
+ new_lm_password_blob,
+ old_lm_hash_enc_blob,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+/* change password 3 */
+
+NTSTATUS dcerpc_samr_chgpasswd_user3(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword,
+ struct samr_DomInfo1 **dominfo1,
+ struct userPwdChangeFailureInformation **reject,
+ NTSTATUS *presult)
+{
+ NTSTATUS status;
+ int rc;
+
+ struct samr_CryptPassword new_nt_password;
+ struct samr_CryptPassword new_lm_password;
+ struct samr_Password old_nt_hash_enc;
+ struct samr_Password old_lanman_hash_enc;
+
+ uint8_t old_nt_hash[16] = { 0 };
+ uint8_t old_lanman_hash[16];
+ uint8_t new_nt_hash[16];
+ uint8_t new_lanman_hash[16];
+
+ struct lsa_String server, account;
+
+ DATA_BLOB session_key = data_blob_const(old_nt_hash, 16);
+
+ DEBUG(10,("rpccli_samr_chgpasswd_user3\n"));
+
+ init_lsa_String(&server, srv_name_slash);
+ init_lsa_String(&account, username);
+
+ /* Calculate the MD4 hash (NT compatible) of the password */
+ E_md4hash(oldpassword, old_nt_hash);
+ E_md4hash(newpassword, new_nt_hash);
+
+ if (lp_client_lanman_auth() &&
+ E_deshash(newpassword, new_lanman_hash) &&
+ E_deshash(oldpassword, old_lanman_hash)) {
+ /* E_deshash returns false for 'long' passwords (> 14
+ DOS chars). This allows us to match Win2k, which
+ does not store a LM hash for these passwords (which
+ would reduce the effective password length to 14) */
+ status = init_samr_CryptPassword(newpassword,
+ &session_key,
+ &new_lm_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ rc = E_old_pw_hash(new_nt_hash, old_lanman_hash, old_lanman_hash_enc.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+ } else {
+ ZERO_STRUCT(new_lm_password);
+ ZERO_STRUCT(old_lanman_hash_enc);
+ }
+
+ status = init_samr_CryptPassword(newpassword,
+ &session_key,
+ &new_nt_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ rc = E_old_pw_hash(new_nt_hash, old_nt_hash, old_nt_hash_enc.hash);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto done;
+ }
+
+ status = dcerpc_samr_ChangePasswordUser3(h,
+ mem_ctx,
+ &server,
+ &account,
+ &new_nt_password,
+ &old_nt_hash_enc,
+ true,
+ &new_lm_password,
+ &old_lanman_hash_enc,
+ NULL,
+ dominfo1,
+ reject,
+ presult);
+
+done:
+ ZERO_STRUCT(new_nt_password);
+ ZERO_STRUCT(new_lm_password);
+ ZERO_STRUCT(old_nt_hash_enc);
+ ZERO_STRUCT(old_lanman_hash_enc);
+ ZERO_ARRAY(new_nt_hash);
+ ZERO_ARRAY(new_lanman_hash);
+ ZERO_ARRAY(old_nt_hash);
+ ZERO_ARRAY(old_lanman_hash);
+
+ return status;
+}
+
+NTSTATUS rpccli_samr_chgpasswd_user3(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword,
+ struct samr_DomInfo1 **dominfo1,
+ struct userPwdChangeFailureInformation **reject)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ status = dcerpc_samr_chgpasswd_user3(cli->binding_handle,
+ mem_ctx,
+ cli->srv_name_slash,
+ username,
+ newpassword,
+ oldpassword,
+ dominfo1,
+ reject,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+NTSTATUS dcerpc_samr_chgpasswd_user4(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ const char *oldpassword,
+ const char *newpassword,
+ NTSTATUS *presult)
+{
+ struct lsa_String server, user_account;
+ uint8_t old_nt_key_data[16] = {0};
+ gnutls_datum_t old_nt_key = {
+ .data = old_nt_key_data,
+ .size = sizeof(old_nt_key),
+ };
+ struct samr_EncryptedPasswordAES pwd_buf = {
+ .cipher_len = 0,
+ };
+ DATA_BLOB iv = {
+ .data = pwd_buf.salt,
+ .length = sizeof(pwd_buf.salt),
+ };
+ gnutls_datum_t iv_datum = {
+ .data = iv.data,
+ .size = iv.length,
+ };
+ uint8_t cek_data[16] = {0};
+ DATA_BLOB cek = {
+ .data = cek_data,
+ .length = sizeof(cek_data),
+ };
+ uint64_t pbkdf2_iterations = 0;
+ uint8_t pw_data[514] = {0};
+ DATA_BLOB plaintext = {
+ .data = pw_data,
+ .length = sizeof(pw_data),
+ };
+ DATA_BLOB ciphertext = data_blob_null;
+ NTSTATUS status;
+ bool ok;
+ int rc;
+
+ generate_nonce_buffer(iv.data, iv.length);
+
+ /* Calculate the MD4 hash (NT compatible) of the password */
+ E_md4hash(oldpassword, old_nt_key_data);
+
+ init_lsa_String(&server, srv_name_slash);
+ init_lsa_String(&user_account, username);
+
+ pbkdf2_iterations = generate_random_u64_range(5000, 1000000);
+
+ rc = gnutls_pbkdf2(GNUTLS_MAC_SHA512,
+ &old_nt_key,
+ &iv_datum,
+ pbkdf2_iterations,
+ cek.data,
+ cek.length);
+ BURN_DATA(old_nt_key_data);
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_WRONG_PASSWORD);
+ return status;
+ }
+
+ ok = encode_pwd_buffer514_from_str(pw_data, newpassword, STR_UNICODE);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = samba_gnutls_aead_aes_256_cbc_hmac_sha512_encrypt(
+ mem_ctx,
+ &plaintext,
+ &cek,
+ &samr_aes256_enc_key_salt,
+ &samr_aes256_mac_key_salt,
+ &iv,
+ &ciphertext,
+ pwd_buf.auth_data);
+ BURN_DATA(pw_data);
+ BURN_DATA(cek_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ pwd_buf.cipher_len = ciphertext.length;
+ pwd_buf.cipher = ciphertext.data;
+ pwd_buf.PBKDF2Iterations = pbkdf2_iterations;
+
+ status = dcerpc_samr_ChangePasswordUser4(h,
+ mem_ctx,
+ &server,
+ &user_account,
+ &pwd_buf,
+ presult);
+ data_blob_free(&ciphertext);
+
+ return status;
+}
+
+/* This function returns the bizarre set of (max_entries, max_size) required
+ for the QueryDisplayInfo RPC to actually work against a domain controller
+ with large (10k and higher) numbers of users. These values were
+ obtained by inspection using ethereal and NT4 running User Manager. */
+
+void dcerpc_get_query_dispinfo_params(int loop_count,
+ uint32_t *max_entries,
+ uint32_t *max_size)
+{
+ switch(loop_count) {
+ case 0:
+ *max_entries = 512;
+ *max_size = 16383;
+ break;
+ case 1:
+ *max_entries = 1024;
+ *max_size = 32766;
+ break;
+ case 2:
+ *max_entries = 2048;
+ *max_size = 65532;
+ break;
+ case 3:
+ *max_entries = 4096;
+ *max_size = 131064;
+ break;
+ default: /* loop_count >= 4 */
+ *max_entries = 4096;
+ *max_size = 131071;
+ break;
+ }
+}
+
+NTSTATUS dcerpc_try_samr_connects(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ uint32_t access_mask,
+ struct policy_handle *connect_pol,
+ NTSTATUS *presult)
+{
+ NTSTATUS status;
+ union samr_ConnectInfo info_in, info_out;
+ struct samr_ConnectInfo1 info1;
+ uint32_t lvl_out = 0;
+
+ ZERO_STRUCT(info1);
+
+ info1.client_version = SAMR_CONNECT_W2K;
+ info_in.info1 = info1;
+
+ status = dcerpc_samr_Connect5(h,
+ mem_ctx,
+ srv_name_slash,
+ access_mask,
+ 1,
+ &info_in,
+ &lvl_out,
+ &info_out,
+ connect_pol,
+ presult);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(*presult)) {
+ return status;
+ }
+
+ status = dcerpc_samr_Connect4(h,
+ mem_ctx,
+ srv_name_slash,
+ SAMR_CONNECT_W2K,
+ access_mask,
+ connect_pol,
+ presult);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(*presult)) {
+ return status;
+ }
+
+ status = dcerpc_samr_Connect2(h,
+ mem_ctx,
+ srv_name_slash,
+ access_mask,
+ connect_pol,
+ presult);
+
+ return status;
+}
+
+/* vim: set ts=8 sw=8 noet cindent: */
diff --git a/source3/rpc_client/cli_samr.h b/source3/rpc_client/cli_samr.h
new file mode 100644
index 0000000..5a4f39c
--- /dev/null
+++ b/source3/rpc_client/cli_samr.h
@@ -0,0 +1,229 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SAMR client routines
+ *
+ * Copyright (c) 2000-2001 Tim Potter
+ * Copyright (c) 1992-2000 Andrew Tridgell
+ * Copyright (c) 2002 Rafal Szczesniak
+ * Copyright (c) 2005 Jeremy Allison
+ * Copyright (c) 2007 Michael Adam
+ * Copyright (c) 2008 Guenther Deschner
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CLI_SAMR_H
+#define _CLI_SAMR_H
+
+/* The following definitions come from rpc_client/cli_samr.c */
+
+/**
+ * @brief Change the password of a user.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] user_handle The password of the user to chang the handle
+ *
+ * @param[in] newpassword The new password to set.
+ *
+ * @param[in] oldpassword The old password for verification
+ *
+ * @param[out] presult A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_samr_chgpasswd_user(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *user_handle,
+ const char *newpassword,
+ const char *oldpassword,
+ NTSTATUS *presult);
+
+NTSTATUS rpccli_samr_chgpasswd_user(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *user_handle,
+ const char *newpassword,
+ const char *oldpassword);
+
+/**
+ * @brief Change the password of a user based on username.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] srv_name_slash The server name with leading slashes.
+ *
+ * @param[in] username The name of the user.
+ *
+ * @param[in] newpassword The new password to set.
+ *
+ * @param[in] oldpassword The old password for verification
+ *
+ * @param[out] presult A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_samr_chgpasswd_user2(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword,
+ NTSTATUS *presult);
+
+NTSTATUS rpccli_samr_chgpasswd_user2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword);
+
+/**
+ * @brief Change the password of a user based on the user name given and using
+ * blobs.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] srv_name_slash The server name with leading slashes.
+ *
+ * @param[in] username The name of the user.
+ *
+ * @param[in] new_nt_password_blob The new password as a encrypted blob.
+ *
+ * @param[in] old_nt_hash_enc_blob The old password as a hash encoded blob.
+ *
+ * @param[in] new_lm_password_blob The new password as a lanman encoded blob.
+ *
+ * @param[in] old_lm_hash_enc_blob The old password as a lanman encoded blob.
+ *
+ * @param[out] presult A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_samr_chng_pswd_auth_crap(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ DATA_BLOB new_nt_password_blob,
+ DATA_BLOB old_nt_hash_enc_blob,
+ DATA_BLOB new_lm_password_blob,
+ DATA_BLOB old_lm_hash_enc_blob,
+ NTSTATUS *presult);
+
+NTSTATUS rpccli_samr_chng_pswd_auth_crap(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *username,
+ DATA_BLOB new_nt_password_blob,
+ DATA_BLOB old_nt_hash_enc_blob,
+ DATA_BLOB new_lm_password_blob,
+ DATA_BLOB old_lm_hash_enc_blob);
+
+/**
+ * @brief
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] srv_name_slash The server name with leading slashes.
+ *
+ * @param[in] username The name of the user.
+ *
+ * @param[in] newpassword The new password to set.
+ *
+ * @param[in] oldpassword The old password to set.
+ *
+ * @param[in] dominfo1 A pointer to hold the domain information.
+ *
+ * @param[in] reject A pointer to store the result of a possible reject.
+ *
+ * @param[out] presult A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_samr_chgpasswd_user3(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword,
+ struct samr_DomInfo1 **dominfo1,
+ struct userPwdChangeFailureInformation **reject,
+ NTSTATUS *presult);
+
+NTSTATUS rpccli_samr_chgpasswd_user3(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *newpassword,
+ const char *oldpassword,
+ struct samr_DomInfo1 **dominfo1,
+ struct userPwdChangeFailureInformation **reject);
+
+NTSTATUS dcerpc_samr_chgpasswd_user4(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ const char *username,
+ const char *oldpassword,
+ const char *newpassword,
+ NTSTATUS *presult);
+
+/**
+ * @brief Create a set of max_entries, max_size for QueryDisplayInfo.
+ *
+ * This function returns a set of (max_entries, max_size) required
+ * for the QueryDisplayInfo RPC to actually work against a domain controller
+ * with large (10k and higher) numbers of users. These values were
+ * obtained by inspection using wireshark and NT4 running User Manager.
+ *
+ * @param[in] loop_count The loop count.
+ *
+ * @param[out] max_entries A pointer to store maximum entries value.
+ *
+ * @param[out] max_size A pointer to store the maximum size value.
+ */
+void dcerpc_get_query_dispinfo_params(int loop_count,
+ uint32_t *max_entries,
+ uint32_t *max_size);
+
+/**
+ * @brief Try if we can connect to samr.
+ *
+ * @param[in] h The dcerpc binding handle to use.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] srv_name_slash The server name with leading slashes.
+ *
+ * @param[in] access_mask The access mask to use to open the connection.
+ *
+ * @param[in] connect_pol A pointer to store the policy handle for the
+ * connection.
+ *
+ * @param[out] presult A pointer for the NDR NTSTATUS error code.
+ *
+ * @return A corresponding NTSTATUS error code for the connection.
+ */
+NTSTATUS dcerpc_try_samr_connects(struct dcerpc_binding_handle *h,
+ TALLOC_CTX *mem_ctx,
+ const char *srv_name_slash,
+ uint32_t access_mask,
+ struct policy_handle *connect_pol,
+ NTSTATUS *presult);
+
+#endif /* _CLI_SAMR_H */
diff --git a/source3/rpc_client/cli_spoolss.c b/source3/rpc_client/cli_spoolss.c
new file mode 100644
index 0000000..094381c
--- /dev/null
+++ b/source3/rpc_client/cli_spoolss.c
@@ -0,0 +1,1006 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Gerald Carter 2001-2005,
+ Copyright (C) Tim Potter 2000-2002,
+ Copyright (C) Andrew Tridgell 1994-2000,
+ Copyright (C) Jean-Francois Micouleau 1999-2000.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "auth/gensec/gensec.h"
+#include "auth/credentials/credentials.h"
+#include "rpc_client/init_spoolss.h"
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_OpenPrinterEx
+**********************************************************************/
+
+WERROR rpccli_spoolss_openprinter_ex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *printername,
+ uint32_t access_desired,
+ struct policy_handle *handle)
+{
+ NTSTATUS status;
+ WERROR werror;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct spoolss_UserLevelCtr userlevel_ctr;
+ struct spoolss_UserLevel1 level1;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct cli_credentials *creds = gensec_get_credentials(cli->auth->auth_ctx);
+
+ ZERO_STRUCT(devmode_ctr);
+
+ werror = spoolss_init_spoolss_UserLevel1(mem_ctx,
+ cli_credentials_get_username(creds),
+ &level1);
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ userlevel_ctr.level = 1;
+ userlevel_ctr.user_info.level1 = &level1;
+
+ status = dcerpc_spoolss_OpenPrinterEx(b, mem_ctx,
+ printername,
+ NULL,
+ devmode_ctr,
+ access_desired,
+ userlevel_ctr,
+ handle,
+ &werror);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ return WERR_OK;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_GetPrinterDriver
+**********************************************************************/
+
+WERROR rpccli_spoolss_getprinterdriver(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *architecture,
+ uint32_t level,
+ uint32_t offered,
+ union spoolss_DriverInfo *info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_GetPrinterDriver(b, mem_ctx,
+ handle,
+ architecture,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_GetPrinterDriver(b, mem_ctx,
+ handle,
+ architecture,
+ level,
+ &buffer,
+ offered,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_GetPrinterDriver2
+**********************************************************************/
+
+WERROR rpccli_spoolss_getprinterdriver2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *architecture,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t client_major_version,
+ uint32_t client_minor_version,
+ union spoolss_DriverInfo *info,
+ uint32_t *server_major_version,
+ uint32_t *server_minor_version)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_GetPrinterDriver2(b, mem_ctx,
+ handle,
+ architecture,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ client_major_version,
+ client_minor_version,
+ info,
+ &needed,
+ server_major_version,
+ server_minor_version,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_GetPrinterDriver2(b, mem_ctx,
+ handle,
+ architecture,
+ level,
+ &buffer,
+ offered,
+ client_major_version,
+ client_minor_version,
+ info,
+ &needed,
+ server_major_version,
+ server_minor_version,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_AddPrinterEx
+**********************************************************************/
+
+WERROR rpccli_spoolss_addprinterex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct spoolss_SetPrinterInfoCtr *info_ctr)
+{
+ WERROR result;
+ NTSTATUS status;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+ struct spoolss_UserLevelCtr userlevel_ctr;
+ struct spoolss_UserLevel1 level1;
+ struct policy_handle handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct cli_credentials *creds = gensec_get_credentials(cli->auth->auth_ctx);
+
+ ZERO_STRUCT(devmode_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ result = spoolss_init_spoolss_UserLevel1(mem_ctx,
+ cli_credentials_get_username(creds),
+ &level1);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ userlevel_ctr.level = 1;
+ userlevel_ctr.user_info.level1 = &level1;
+
+ status = dcerpc_spoolss_AddPrinterEx(b, mem_ctx,
+ cli->srv_name_slash,
+ info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ &userlevel_ctr,
+ &handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return result;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_GetPrinter
+**********************************************************************/
+
+WERROR rpccli_spoolss_getprinter(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t level,
+ uint32_t offered,
+ union spoolss_PrinterInfo *info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ DATA_BLOB buffer;
+ uint32_t needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_GetPrinter(b, mem_ctx,
+ handle,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_GetPrinter(b, mem_ctx,
+ handle,
+ level,
+ &buffer,
+ offered,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_GetJob
+**********************************************************************/
+
+WERROR rpccli_spoolss_getjob(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t job_id,
+ uint32_t level,
+ uint32_t offered,
+ union spoolss_JobInfo *info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_GetJob(b, mem_ctx,
+ handle,
+ job_id,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_GetJob(b, mem_ctx,
+ handle,
+ job_id,
+ level,
+ &buffer,
+ offered,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumForms
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumforms(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_FormInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumForms(b, mem_ctx,
+ handle,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumForms(b, mem_ctx,
+ handle,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumPrintProcessors
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumprintprocessors(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *environment,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PrintProcessorInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumPrintProcessors(b, mem_ctx,
+ servername,
+ environment,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumPrintProcessors(b, mem_ctx,
+ servername,
+ environment,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumPrintProcessorDataTypes
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumprintprocessordatatypes(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *print_processor_name,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PrintProcDataTypesInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumPrintProcessorDataTypes(b, mem_ctx,
+ servername,
+ print_processor_name,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumPrintProcessorDataTypes(b, mem_ctx,
+ servername,
+ print_processor_name,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumPorts
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumports(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PortInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumPorts(b, mem_ctx,
+ servername,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumPorts(b, mem_ctx,
+ servername,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumMonitors
+**********************************************************************/
+
+WERROR rpccli_spoolss_enummonitors(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_MonitorInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumMonitors(b, mem_ctx,
+ servername,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumMonitors(b, mem_ctx,
+ servername,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumJobs
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumjobs(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t firstjob,
+ uint32_t numjobs,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_JobInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumJobs(b, mem_ctx,
+ handle,
+ firstjob,
+ numjobs,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumJobs(b, mem_ctx,
+ handle,
+ firstjob,
+ numjobs,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumPrinterDrivers
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumprinterdrivers(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *server,
+ const char *environment,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_DriverInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumPrinterDrivers(b, mem_ctx,
+ server,
+ environment,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumPrinterDrivers(b, mem_ctx,
+ server,
+ environment,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumPrinters
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumprinters(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ const char *server,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PrinterInfo **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ DATA_BLOB buffer;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (offered > 0) {
+ buffer = data_blob_talloc_zero(mem_ctx, offered);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+ }
+
+ status = dcerpc_spoolss_EnumPrinters(b, mem_ctx,
+ flags,
+ server,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ W_ERROR_HAVE_NO_MEMORY(buffer.data);
+
+ status = dcerpc_spoolss_EnumPrinters(b, mem_ctx,
+ flags,
+ server,
+ level,
+ (offered > 0) ? &buffer : NULL,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_GetPrinterData
+**********************************************************************/
+
+WERROR rpccli_spoolss_getprinterdata(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *value_name,
+ uint32_t offered,
+ enum winreg_Type *type,
+ uint32_t *needed_p,
+ uint8_t **data_p)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ uint8_t *data;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ data = talloc_zero_array(mem_ctx, uint8_t, offered);
+ W_ERROR_HAVE_NO_MEMORY(data);
+
+ status = dcerpc_spoolss_GetPrinterData(b, mem_ctx,
+ handle,
+ value_name,
+ type,
+ data,
+ offered,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_MORE_DATA)) {
+ offered = needed;
+ data = talloc_zero_array(mem_ctx, uint8_t, offered);
+ W_ERROR_HAVE_NO_MEMORY(data);
+
+ status = dcerpc_spoolss_GetPrinterData(b, mem_ctx,
+ handle,
+ value_name,
+ type,
+ data,
+ offered,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ *data_p = data;
+ *needed_p = needed;
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumPrinterKey
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumprinterkey(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *key_name,
+ const char ***key_buffer,
+ uint32_t offered)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ union spoolss_KeyNames _key_buffer;
+ uint32_t _ndr_size;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ status = dcerpc_spoolss_EnumPrinterKey(b, mem_ctx,
+ handle,
+ key_name,
+ &_ndr_size,
+ &_key_buffer,
+ offered,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_MORE_DATA)) {
+ offered = needed;
+ status = dcerpc_spoolss_EnumPrinterKey(b, mem_ctx,
+ handle,
+ key_name,
+ &_ndr_size,
+ &_key_buffer,
+ offered,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ *key_buffer = _key_buffer.string_array;
+
+ return werror;
+}
+
+/**********************************************************************
+ convenience wrapper around rpccli_spoolss_EnumPrinterDataEx
+**********************************************************************/
+
+WERROR rpccli_spoolss_enumprinterdataex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *key_name,
+ uint32_t offered,
+ uint32_t *count,
+ struct spoolss_PrinterEnumValues **info)
+{
+ NTSTATUS status;
+ WERROR werror;
+ uint32_t needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ status = dcerpc_spoolss_EnumPrinterDataEx(b, mem_ctx,
+ handle,
+ key_name,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(werror, WERR_MORE_DATA)) {
+ offered = needed;
+
+ status = dcerpc_spoolss_EnumPrinterDataEx(b, mem_ctx,
+ handle,
+ key_name,
+ offered,
+ count,
+ info,
+ &needed,
+ &werror);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werror;
+}
diff --git a/source3/rpc_client/cli_spoolss.h b/source3/rpc_client/cli_spoolss.h
new file mode 100644
index 0000000..9883874
--- /dev/null
+++ b/source3/rpc_client/cli_spoolss.h
@@ -0,0 +1,153 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Gerald Carter 2001-2005,
+ Copyright (C) Tim Potter 2000-2002,
+ Copyright (C) Andrew Tridgell 1994-2000,
+ Copyright (C) Jean-Francois Micouleau 1999-2000.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _RPC_CLIENT_CLI_SPOOLSS_H_
+#define _RPC_CLIENT_CLI_SPOOLSS_H_
+
+/* The following definitions come from rpc_client/cli_spoolss.c */
+
+WERROR rpccli_spoolss_openprinter_ex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *printername,
+ uint32_t access_desired,
+ struct policy_handle *handle);
+WERROR rpccli_spoolss_getprinterdriver(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *architecture,
+ uint32_t level,
+ uint32_t offered,
+ union spoolss_DriverInfo *info);
+WERROR rpccli_spoolss_getprinterdriver2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *architecture,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t client_major_version,
+ uint32_t client_minor_version,
+ union spoolss_DriverInfo *info,
+ uint32_t *server_major_version,
+ uint32_t *server_minor_version);
+WERROR rpccli_spoolss_addprinterex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct spoolss_SetPrinterInfoCtr *info_ctr);
+WERROR rpccli_spoolss_getprinter(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t level,
+ uint32_t offered,
+ union spoolss_PrinterInfo *info);
+WERROR rpccli_spoolss_getjob(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t job_id,
+ uint32_t level,
+ uint32_t offered,
+ union spoolss_JobInfo *info);
+WERROR rpccli_spoolss_enumforms(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_FormInfo **info);
+WERROR rpccli_spoolss_enumprintprocessors(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *environment,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PrintProcessorInfo **info);
+WERROR rpccli_spoolss_enumprintprocessordatatypes(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *print_processor_name,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PrintProcDataTypesInfo **info);
+WERROR rpccli_spoolss_enumports(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PortInfo **info);
+WERROR rpccli_spoolss_enummonitors(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *servername,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_MonitorInfo **info);
+WERROR rpccli_spoolss_enumjobs(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ uint32_t firstjob,
+ uint32_t numjobs,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_JobInfo **info);
+WERROR rpccli_spoolss_enumprinterdrivers(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *server,
+ const char *environment,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_DriverInfo **info);
+WERROR rpccli_spoolss_enumprinters(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ uint32_t flags,
+ const char *server,
+ uint32_t level,
+ uint32_t offered,
+ uint32_t *count,
+ union spoolss_PrinterInfo **info);
+WERROR rpccli_spoolss_getprinterdata(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *value_name,
+ uint32_t offered,
+ enum winreg_Type *type,
+ uint32_t *needed_p,
+ uint8_t **data_p);
+WERROR rpccli_spoolss_enumprinterkey(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *key_name,
+ const char ***key_buffer,
+ uint32_t offered);
+WERROR rpccli_spoolss_enumprinterdataex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *handle,
+ const char *key_name,
+ uint32_t offered,
+ uint32_t *count,
+ struct spoolss_PrinterEnumValues **info);
+
+#endif /* _RPC_CLIENT_CLI_SPOOLSS_H_ */
diff --git a/source3/rpc_client/cli_winreg.c b/source3/rpc_client/cli_winreg.c
new file mode 100644
index 0000000..a360d1e
--- /dev/null
+++ b/source3/rpc_client/cli_winreg.c
@@ -0,0 +1,975 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * WINREG client routines
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "rpc_client/cli_winreg.h"
+#include "../libcli/registry/util_reg.h"
+
+NTSTATUS dcerpc_winreg_query_dword(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ uint32_t *data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue;
+ enum winreg_Type type = REG_NONE;
+ uint32_t value_len = 0;
+ uint32_t data_size = 0;
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ wvalue.name = value;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ NULL,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (type != REG_DWORD) {
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (data_size != 4) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ blob = data_blob_talloc_zero(mem_ctx, data_size);
+ if (blob.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ value_len = 0;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ blob.data,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (data) {
+ *data = IVAL(blob.data, 0);
+ }
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_query_binary(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ DATA_BLOB *data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue;
+ enum winreg_Type type = REG_NONE;
+ uint32_t value_len = 0;
+ uint32_t data_size = 0;
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ ZERO_STRUCT(wvalue);
+ wvalue.name = value;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ NULL,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (type != REG_BINARY) {
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ blob = data_blob_talloc_zero(mem_ctx, data_size);
+ if (blob.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ value_len = 0;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ blob.data,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (data) {
+ data->data = blob.data;
+ data->length = blob.length;
+ }
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_query_multi_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char ***data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue;
+ enum winreg_Type type = REG_NONE;
+ uint32_t value_len = 0;
+ uint32_t data_size = 0;
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ wvalue.name = value;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ NULL,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (type != REG_MULTI_SZ) {
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ blob = data_blob_talloc_zero(mem_ctx, data_size);
+ if (blob.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ value_len = 0;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ blob.data,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (data) {
+ bool ok;
+
+ ok = pull_reg_multi_sz(mem_ctx, &blob, data);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_query_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char **data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue;
+ enum winreg_Type type = REG_NONE;
+ uint32_t value_len = 0;
+ uint32_t data_size = 0;
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ wvalue.name = value;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ NULL,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (type != REG_SZ) {
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ blob = data_blob_talloc_zero(mem_ctx, data_size);
+ if (blob.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ value_len = 0;
+
+ status = dcerpc_winreg_QueryValue(h,
+ mem_ctx,
+ key_handle,
+ &wvalue,
+ &type,
+ blob.data,
+ &data_size,
+ &value_len,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (data) {
+ bool ok;
+
+ ok = pull_reg_sz(mem_ctx, &blob, data);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_query_sd(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ struct security_descriptor **data,
+ WERROR *pwerr)
+{
+ NTSTATUS status;
+ DATA_BLOB blob;
+
+ status = dcerpc_winreg_query_binary(mem_ctx,
+ h,
+ key_handle,
+ value,
+ &blob,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ return status;
+ }
+
+ if (data) {
+ struct security_descriptor *sd;
+ enum ndr_err_code ndr_err;
+
+ sd = talloc_zero(mem_ctx, struct security_descriptor);
+ if (sd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob,
+ sd,
+ sd,
+ (ndr_pull_flags_fn_t) ndr_pull_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("dcerpc_winreg_query_sd: Failed to marshall "
+ "security descriptor\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *data = sd;
+ }
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_set_dword(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ uint32_t data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue;
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ ZERO_STRUCT(wvalue);
+ wvalue.name = value;
+ blob = data_blob_talloc_zero(mem_ctx, 4);
+ SIVAL(blob.data, 0, data);
+
+ status = dcerpc_winreg_SetValue(h,
+ mem_ctx,
+ key_handle,
+ wvalue,
+ REG_DWORD,
+ blob.data,
+ blob.length,
+ pwerr);
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_set_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char *data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue = { 0, };
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ wvalue.name = value;
+ if (data == NULL) {
+ blob = data_blob_string_const("");
+ } else {
+ if (!push_reg_sz(mem_ctx, &blob, data)) {
+ DEBUG(2, ("dcerpc_winreg_set_sz: Could not marshall "
+ "string %s for %s\n",
+ data, wvalue.name));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = dcerpc_winreg_SetValue(h,
+ mem_ctx,
+ key_handle,
+ wvalue,
+ REG_SZ,
+ blob.data,
+ blob.length,
+ pwerr);
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_set_expand_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char *data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue = { 0, };
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ wvalue.name = value;
+ if (data == NULL) {
+ blob = data_blob_string_const("");
+ } else {
+ if (!push_reg_sz(mem_ctx, &blob, data)) {
+ DEBUG(2, ("dcerpc_winreg_set_expand_sz: Could not marshall "
+ "string %s for %s\n",
+ data, wvalue.name));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = dcerpc_winreg_SetValue(h,
+ mem_ctx,
+ key_handle,
+ wvalue,
+ REG_EXPAND_SZ,
+ blob.data,
+ blob.length,
+ pwerr);
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_set_multi_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char **data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue = { 0, };
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ wvalue.name = value;
+ if (!push_reg_multi_sz(mem_ctx, &blob, data)) {
+ DEBUG(2, ("dcerpc_winreg_set_multi_sz: Could not marshall "
+ "string multi sz for %s\n",
+ wvalue.name));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dcerpc_winreg_SetValue(h,
+ mem_ctx,
+ key_handle,
+ wvalue,
+ REG_MULTI_SZ,
+ blob.data,
+ blob.length,
+ pwerr);
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_set_binary(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ DATA_BLOB *data,
+ WERROR *pwerr)
+{
+ struct winreg_String wvalue = { 0, };
+ NTSTATUS status;
+
+ wvalue.name = value;
+
+ status = dcerpc_winreg_SetValue(h,
+ mem_ctx,
+ key_handle,
+ wvalue,
+ REG_BINARY,
+ data->data,
+ data->length,
+ pwerr);
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_set_sd(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const struct security_descriptor *data,
+ WERROR *pwerr)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ ndr_err = ndr_push_struct_blob(&blob,
+ mem_ctx,
+ data,
+ (ndr_push_flags_fn_t) ndr_push_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2, ("dcerpc_winreg_set_sd: Failed to marshall security "
+ "descriptor\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return dcerpc_winreg_set_binary(mem_ctx,
+ h,
+ key_handle,
+ value,
+ &blob,
+ pwerr);
+}
+
+NTSTATUS dcerpc_winreg_add_multi_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char *data,
+ WERROR *pwerr)
+{
+ const char **a = NULL;
+ const char **p;
+ uint32_t i;
+ NTSTATUS status;
+
+ status = dcerpc_winreg_query_multi_sz(mem_ctx,
+ h,
+ key_handle,
+ value,
+ &a,
+ pwerr);
+
+ /* count the elements */
+ for (p = a, i = 0; p && *p; p++, i++);
+
+ p = talloc_realloc(mem_ctx, a, const char *, i + 2);
+ if (p == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ p[i] = data;
+ p[i + 1] = NULL;
+
+ status = dcerpc_winreg_set_multi_sz(mem_ctx,
+ h,
+ key_handle,
+ value,
+ p,
+ pwerr);
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_enum_keys(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_hnd,
+ uint32_t *pnum_subkeys,
+ const char ***psubkeys,
+ WERROR *pwerr)
+{
+ const char **subkeys;
+ uint32_t num_subkeys, max_subkeylen, max_classlen;
+ uint32_t num_values, max_valnamelen, max_valbufsize;
+ uint32_t i;
+ NTTIME last_changed_time;
+ uint32_t secdescsize;
+ struct winreg_String classname;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(classname);
+
+ status = dcerpc_winreg_QueryInfoKey(h,
+ tmp_ctx,
+ key_hnd,
+ &classname,
+ &num_subkeys,
+ &max_subkeylen,
+ &max_classlen,
+ &num_values,
+ &max_valnamelen,
+ &max_valbufsize,
+ &secdescsize,
+ &last_changed_time,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ goto error;
+ }
+
+ subkeys = talloc_zero_array(tmp_ctx, const char *, num_subkeys + 2);
+ if (subkeys == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ if (num_subkeys == 0) {
+ subkeys[0] = talloc_strdup(subkeys, "");
+ if (subkeys[0] == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ *pnum_subkeys = 0;
+ if (psubkeys) {
+ *psubkeys = talloc_move(mem_ctx, &subkeys);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+ }
+
+ for (i = 0; i < num_subkeys; i++) {
+ char c = '\0';
+ char n = '\0';
+ char *name = NULL;
+ struct winreg_StringBuf class_buf;
+ struct winreg_StringBuf name_buf;
+ NTTIME modtime;
+
+ class_buf.name = &c;
+ class_buf.size = max_classlen + 2;
+ class_buf.length = 0;
+
+ name_buf.name = &n;
+ name_buf.size = max_subkeylen + 2;
+ name_buf.length = 0;
+
+ ZERO_STRUCT(modtime);
+
+ status = dcerpc_winreg_EnumKey(h,
+ tmp_ctx,
+ key_hnd,
+ i,
+ &name_buf,
+ &class_buf,
+ &modtime,
+ pwerr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("dcerpc_winreg_enum_keys: Could not enumerate keys: %s\n",
+ nt_errstr(status)));
+ goto error;
+ }
+
+ if (W_ERROR_EQUAL(*pwerr, WERR_NO_MORE_ITEMS)) {
+ *pwerr = WERR_OK;
+ break;
+ }
+ if (!W_ERROR_IS_OK(*pwerr)) {
+ DEBUG(5, ("dcerpc_winreg_enum_keys: Could not enumerate keys: %s\n",
+ win_errstr(*pwerr)));
+ goto error;
+ }
+
+ if (name_buf.name == NULL) {
+ *pwerr = WERR_INVALID_PARAMETER;
+ goto error;
+ }
+
+ name = talloc_strdup(subkeys, name_buf.name);
+ if (name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ subkeys[i] = name;
+ }
+
+ *pnum_subkeys = num_subkeys;
+ if (psubkeys) {
+ *psubkeys = talloc_move(mem_ctx, &subkeys);
+ }
+
+ error:
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_enumvals(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_hnd,
+ uint32_t *pnum_values,
+ const char ***pnames,
+ enum winreg_Type **_type,
+ DATA_BLOB **pdata,
+ WERROR *pwerr)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ uint32_t num_subkeys = 0, max_subkeylen = 0, max_classlen = 0;
+ uint32_t num_values = 0, max_valnamelen = 0, max_valbufsize = 0;
+ uint32_t secdescsize = 0;
+ uint32_t i;
+ NTTIME last_changed_time = 0;
+ struct winreg_String classname = { .name = NULL };
+
+ const char **enum_names = NULL;
+ enum winreg_Type *enum_types = NULL;
+ DATA_BLOB *enum_data_blobs = NULL;
+
+
+ WERROR result = WERR_OK;
+ NTSTATUS status;
+
+ status = dcerpc_winreg_QueryInfoKey(h,
+ tmp_ctx,
+ key_hnd,
+ &classname,
+ &num_subkeys,
+ &max_subkeylen,
+ &max_classlen,
+ &num_values,
+ &max_valnamelen,
+ &max_valbufsize,
+ &secdescsize,
+ &last_changed_time,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_winreg_enumvals: Could not query info: %s\n",
+ nt_errstr(status)));
+ goto error;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_winreg_enumvals: Could not query info: %s\n",
+ win_errstr(result)));
+ *pwerr = result;
+ goto error;
+ }
+
+ if (num_values == 0) {
+ *pnum_values = 0;
+ TALLOC_FREE(tmp_ctx);
+ *pwerr = WERR_OK;
+ return status;
+ }
+
+ enum_names = talloc_zero_array(tmp_ctx, const char *, num_values);
+
+ if (enum_names == NULL) {
+ *pwerr = WERR_NOT_ENOUGH_MEMORY;
+ goto error;
+ }
+
+ enum_types = talloc_zero_array(tmp_ctx, enum winreg_Type, num_values);
+
+ if (enum_types == NULL) {
+ *pwerr = WERR_NOT_ENOUGH_MEMORY;
+ goto error;
+ }
+
+ enum_data_blobs = talloc_zero_array(tmp_ctx, DATA_BLOB, num_values);
+
+ if (enum_data_blobs == NULL) {
+ *pwerr = WERR_NOT_ENOUGH_MEMORY;
+ goto error;
+ }
+
+ for (i = 0; i < num_values; i++) {
+ const char *name;
+ struct winreg_ValNameBuf name_buf;
+ enum winreg_Type type = REG_NONE;
+ uint8_t *data;
+ uint32_t data_size;
+ uint32_t length;
+ char n = '\0';
+
+
+ name_buf.name = &n;
+ name_buf.size = max_valnamelen + 2;
+ name_buf.length = 0;
+
+ data_size = max_valbufsize;
+ data = NULL;
+ if (data_size) {
+ data = (uint8_t *) TALLOC(tmp_ctx, data_size);
+ }
+ length = 0;
+
+ status = dcerpc_winreg_EnumValue(h,
+ tmp_ctx,
+ key_hnd,
+ i,
+ &name_buf,
+ &type,
+ data,
+ data_size ? &data_size : NULL,
+ &length,
+ &result);
+ if (W_ERROR_EQUAL(result, WERR_NO_MORE_ITEMS) ) {
+ result = WERR_OK;
+ status = NT_STATUS_OK;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_winreg_enumvals: Could not enumerate values: %s\n",
+ nt_errstr(status)));
+ goto error;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_winreg_enumvals: Could not enumerate values: %s\n",
+ win_errstr(result)));
+ *pwerr = result;
+ goto error;
+ }
+
+ if (name_buf.name == NULL) {
+ result = WERR_INVALID_PARAMETER;
+ *pwerr = result;
+ goto error;
+ }
+
+ name = talloc_strdup(enum_names, name_buf.name);
+ if (name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ *pwerr = result;
+ goto error;
+ }
+ /* place name, type and datablob in the enum return params */
+
+ enum_data_blobs[i] = data_blob_talloc(enum_data_blobs, data, length);
+ enum_names[i] = name;
+ enum_types[i] = type;
+
+ }
+ /* move to the main mem context */
+ *pnum_values = num_values;
+ if (pnames) {
+ *pnames = talloc_move(mem_ctx, &enum_names);
+ }
+ /* can this fail in any way? */
+ if (_type) {
+ *_type = talloc_move(mem_ctx, &enum_types);
+ }
+
+ if (pdata){
+ *pdata = talloc_move(mem_ctx, &enum_data_blobs);
+ }
+
+
+ result = WERR_OK;
+
+ error:
+ TALLOC_FREE(tmp_ctx);
+ *pwerr = result;
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_delete_subkeys_recursive(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *hive_handle,
+ uint32_t access_mask,
+ const char *key,
+ WERROR *pwerr)
+{
+ const char **subkeys = NULL;
+ uint32_t num_subkeys = 0;
+ struct policy_handle key_hnd;
+ struct winreg_String wkey = { 0, };
+ WERROR result = WERR_OK;
+ NTSTATUS status;
+ uint32_t i;
+
+ ZERO_STRUCT(key_hnd);
+ wkey.name = key;
+
+ DEBUG(2, ("dcerpc_winreg_delete_subkeys_recursive: delete key %s\n", key));
+ /* open the key */
+ status = dcerpc_winreg_OpenKey(h,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ 0,
+ access_mask,
+ &key_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_winreg_delete_subkeys_recursive: Could not open key %s: %s\n",
+ wkey.name, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_winreg_delete_subkeys_recursive: Could not open key %s: %s\n",
+ wkey.name, win_errstr(result)));
+ *pwerr = result;
+ goto done;
+ }
+
+ status = dcerpc_winreg_enum_keys(mem_ctx,
+ h,
+ &key_hnd,
+ &num_subkeys,
+ &subkeys,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ for (i = 0; i < num_subkeys; i++) {
+ /* create key + subkey */
+ char *subkey = talloc_asprintf(mem_ctx, "%s\\%s", key, subkeys[i]);
+ if (subkey == NULL) {
+ goto done;
+ }
+
+ DEBUG(2, ("dcerpc_winreg_delete_subkeys_recursive: delete subkey %s\n", subkey));
+ status = dcerpc_winreg_delete_subkeys_recursive(mem_ctx,
+ h,
+ hive_handle,
+ access_mask,
+ subkey,
+ &result);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ WERROR ignore;
+ dcerpc_winreg_CloseKey(h, mem_ctx, &key_hnd, &ignore);
+ }
+
+ wkey.name = key;
+
+ status = dcerpc_winreg_DeleteKey(h,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ *pwerr = result;
+ goto done;
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ WERROR ignore;
+
+ dcerpc_winreg_CloseKey(h, mem_ctx, &key_hnd, &ignore);
+ }
+
+ *pwerr = result;
+ return status;
+}
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_client/cli_winreg.h b/source3/rpc_client/cli_winreg.h
new file mode 100644
index 0000000..c8dc3f6
--- /dev/null
+++ b/source3/rpc_client/cli_winreg.h
@@ -0,0 +1,449 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * WINREG client routines
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CLI_WINREG_H
+#define CLI_WINREG_H
+
+/**
+ * @brief Query a key for the specified dword value.
+ *
+ * Get the data that is associated with the named value of a specified registry
+ * open key. This function ensures that the key is a dword and converts it
+ * correctly.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to query.
+ *
+ * @param[out] data A pointer to store the data of the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_query_dword(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ uint32_t *data,
+ WERROR *pwerr);
+
+/**
+ * @brief Query a key for the specified binary value.
+ *
+ * Get the data that is associated with the named value of a specified registry
+ * open key. This function ensures that the key is a binary value.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to query.
+ *
+ * @param[out] data A pointer to store the data of the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_query_binary(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ DATA_BLOB *data,
+ WERROR *pwerr);
+
+/**
+ * @brief Query a key for the specified multi sz value.
+ *
+ * Get the data that is associated with the named value of a specified registry
+ * open key. This function ensures that the key is a multi sz value.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to query.
+ *
+ * @param[out] data A pointer to store the data of the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_query_multi_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char ***data,
+ WERROR *pwerr);
+
+/**
+ * @brief Query a key for the specified sz value.
+ *
+ * Get the data that is associated with the named value of a specified registry
+ * open key. This function ensures that the key is a multi sz value.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to query.
+ *
+ * @param[out] data A pointer to store the data of the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_query_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char **data,
+ WERROR *pwerr);
+
+/**
+ * @brief Query a key for the specified security descriptor.
+ *
+ * Get the data that is associated with the named value of a specified registry
+ * open key. This function ensures that the key is a security descriptor.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to query.
+ *
+ * @param[out] data A pointer to store the data of the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_query_sd(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ struct security_descriptor **data,
+ WERROR *pwerr);
+
+/**
+ * @brief Set a value with the specified dword data.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to set.
+ *
+ * @param[in] data The data to store in the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_set_dword(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ uint32_t data,
+ WERROR *pwerr);
+
+/**
+ * @brief Set a value with the specified sz data.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to set.
+ *
+ * @param[in] data The data to store in the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_set_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char *data,
+ WERROR *pwerr);
+
+/**
+ * @brief Set a value with the specified expand sz data.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to set.
+ *
+ * @param[in] data The data to store in the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_set_expand_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char *data,
+ WERROR *pwerr);
+
+/**
+ * @brief Set a value with the specified multi sz data.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to set.
+ *
+ * @param[in] data The data to store in the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_set_multi_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char **data,
+ WERROR *pwerr);
+
+/**
+ * @brief Set a value with the specified binary data.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to set.
+ *
+ * @param[in] data The data to store in the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_set_binary(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ DATA_BLOB *data,
+ WERROR *pwerr);
+
+/**
+ * @brief Set a value with the specified security descriptor.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to set.
+ *
+ * @param[in] data The security descriptor to store in the value.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_set_sd(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const struct security_descriptor *data,
+ WERROR *pwerr);
+
+/**
+ * @brief Add a value to the multi sz data.
+ *
+ * This reads the multi sz data from the given value and adds the data to the
+ * multi sz. Then it saves it to the registry.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[in] value The name of the value to set.
+ *
+ * @param[in] data The data to add.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_add_multi_sz(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_handle,
+ const char *value,
+ const char *data,
+ WERROR *pwerr);
+
+/**
+ * @brief Enumerate on the given keyhandle to get the subkey names.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] h The binding handle for the rpc connection.
+ *
+ * @param[in] key_handle A handle to a key that MUST have been opened
+ * previously.
+ *
+ * @param[out] pnum_subkeys A pointer to store the number of subkeys.
+ *
+ * @param[out] psubkeys A pointer to store the names of the subkeys.
+ *
+ * @param[out] pwerr A pointer to a WERROR to store result of the query.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+NTSTATUS dcerpc_winreg_enum_keys(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_hnd,
+ uint32_t *pnum_subkeys,
+ const char ***psubkeys,
+ WERROR *pwerr);
+/**
+ * @internal
+ *
+ * @brief Enumerate values of an opened key handle and retrieve the data.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] winreg_handle The binding handle for the rpc connection.
+ *
+ * @param[in] key_hnd The opened key handle.
+ *
+ * @param[out] pnum_values A pointer to store the number of values we found.
+ *
+ * @param[out] pnames A pointer to store all the names of the values we found.
+ *
+ * @param[out] _type A pointer to store all the types corresponding with the
+ * values found.
+ * @param[out] pdata A pointer to store the data corresponding to the values.
+ *
+ * @param[out] pwerr A pointer to the WERROR. WERR_OK on success
+ * WERR_OK on success, the corresponding DOS error
+ * code if something's gone wrong.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+
+NTSTATUS dcerpc_winreg_enumvals(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *key_hnd,
+ uint32_t *pnum_values,
+ const char ***pnames,
+ enum winreg_Type **_type,
+ DATA_BLOB **pdata,
+ WERROR *pwerr);
+
+/**
+ * @internal
+ *
+ * @brief A function to delete a key and its subkeys recursively.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] winreg_handle The binding handle for the rpc connection.
+ *
+ * @param[in] hive_handle A opened hive handle to the key.
+ *
+ * @param[in] access_mask The access mask to access the key.
+ *
+ * @param[in] key The key to delete
+ *
+ * @param[out] WERR_OK on success, the corresponding DOS error
+ * code if something gone wrong.
+ *
+ * @return NT_STATUS_OK on success or a corresponding error if
+ * there was a problem on the connection.
+ */
+
+NTSTATUS dcerpc_winreg_delete_subkeys_recursive(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *hive_handle,
+ uint32_t access_mask,
+ const char *key,
+ WERROR *pwerr);
+
+
+#endif /* CLI_WINREG_H */
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_client/cli_winreg_int.c b/source3/rpc_client/cli_winreg_int.c
new file mode 100644
index 0000000..51cd363
--- /dev/null
+++ b/source3/rpc_client/cli_winreg_int.c
@@ -0,0 +1,323 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * WINREG internal client routines
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "include/registry.h"
+#include "librpc/gen_ndr/ndr_winreg_c.h"
+#include "rpc_client/cli_winreg_int.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "../lib/tsocket/tsocket.h"
+
+/**
+ * Split path into hive name and subkeyname
+ * normalizations performed:
+ * - if the path contains no '\\' characters,
+ * assume that the legacy format of using '/'
+ * as a separator is used and convert '/' to '\\'
+ * - strip trailing '\\' chars
+ */
+static WERROR _split_hive_key(TALLOC_CTX *mem_ctx,
+ const char *path,
+ char **hivename,
+ char **subkeyname)
+{
+ char *p;
+ const char *tmp_subkeyname;
+
+ if ((path == NULL) || (hivename == NULL) || (subkeyname == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (strlen(path) == 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (strchr(path, '\\') == NULL) {
+ *hivename = talloc_string_sub(mem_ctx, path, "/", "\\");
+ } else {
+ *hivename = talloc_strdup(mem_ctx, path);
+ }
+
+ if (*hivename == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* strip trailing '\\' chars */
+ p = strrchr(*hivename, '\\');
+ while ((p != NULL) && (p[1] == '\0')) {
+ *p = '\0';
+ p = strrchr(*hivename, '\\');
+ }
+
+ p = strchr(*hivename, '\\');
+
+ if ((p == NULL) || (*p == '\0')) {
+ /* just the hive - no subkey given */
+ tmp_subkeyname = "";
+ } else {
+ *p = '\0';
+ tmp_subkeyname = p+1;
+ }
+ *subkeyname = talloc_strdup(mem_ctx, tmp_subkeyname);
+ if (*subkeyname == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+static NTSTATUS _winreg_int_openkey(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **h,
+ uint32_t reg_type,
+ const char *key,
+ bool create_key,
+ uint32_t access_mask,
+ struct policy_handle *hive_handle,
+ struct policy_handle *key_handle,
+ WERROR *pwerr)
+{
+ struct tsocket_address *local;
+ struct dcerpc_binding_handle *binding_handle;
+ struct winreg_String wkey, wkeyclass;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ int rc;
+
+ rc = tsocket_address_inet_from_strings(mem_ctx,
+ "ip",
+ "127.0.0.1",
+ 0,
+ &local);
+ if (rc < 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = rpcint_binding_handle(mem_ctx,
+ &ndr_table_winreg,
+ local,
+ NULL,
+ session_info,
+ msg_ctx,
+ &binding_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Could not connect to winreg pipe: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ switch (reg_type) {
+ case HKEY_LOCAL_MACHINE:
+ status = dcerpc_winreg_OpenHKLM(binding_handle,
+ mem_ctx,
+ NULL,
+ access_mask,
+ hive_handle,
+ &result);
+ break;
+ case HKEY_CLASSES_ROOT:
+ status = dcerpc_winreg_OpenHKCR(binding_handle,
+ mem_ctx,
+ NULL,
+ access_mask,
+ hive_handle,
+ &result);
+ break;
+ case HKEY_USERS:
+ status = dcerpc_winreg_OpenHKU(binding_handle,
+ mem_ctx,
+ NULL,
+ access_mask,
+ hive_handle,
+ &result);
+ break;
+ case HKEY_CURRENT_USER:
+ status = dcerpc_winreg_OpenHKCU(binding_handle,
+ mem_ctx,
+ NULL,
+ access_mask,
+ hive_handle,
+ &result);
+ break;
+ case HKEY_PERFORMANCE_DATA:
+ status = dcerpc_winreg_OpenHKPD(binding_handle,
+ mem_ctx,
+ NULL,
+ access_mask,
+ hive_handle,
+ &result);
+ break;
+ default:
+ result = WERR_INVALID_PARAMETER;
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(binding_handle);
+ return status;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(binding_handle);
+ *pwerr = result;
+ return status;
+ }
+
+ ZERO_STRUCT(wkey);
+ wkey.name = key;
+
+ if (create_key) {
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ key_handle,
+ &action,
+ &result);
+ switch (action) {
+ case REG_ACTION_NONE:
+ DEBUG(8, ("dcerpc_winreg_int_openkey: createkey"
+ " did nothing -- huh?\n"));
+ break;
+ case REG_CREATED_NEW_KEY:
+ DEBUG(8, ("dcerpc_winreg_int_openkey: createkey"
+ " created %s\n", key));
+ break;
+ case REG_OPENED_EXISTING_KEY:
+ DEBUG(8, ("dcerpc_winreg_int_openkey: createkey"
+ " opened existing %s\n", key));
+ break;
+ }
+ } else {
+ status = dcerpc_winreg_OpenKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ 0,
+ access_mask,
+ key_handle,
+ &result);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(binding_handle);
+ return status;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(binding_handle);
+ *pwerr = result;
+ return status;
+ }
+
+ *h = binding_handle;
+
+ return status;
+}
+
+NTSTATUS dcerpc_winreg_int_openkey(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **h,
+ const char *key,
+ bool create_key,
+ uint32_t access_mask,
+ struct policy_handle *hive_handle,
+ struct policy_handle *key_handle,
+ WERROR *pwerr)
+{
+ char *hivename = NULL;
+ char *subkey = NULL;
+ uint32_t reg_type;
+ WERROR result;
+
+ result = _split_hive_key(mem_ctx, key, &hivename, &subkey);
+ if (!W_ERROR_IS_OK(result)) {
+ *pwerr = result;
+ return NT_STATUS_OK;
+ }
+
+ if (strequal(hivename, "HKLM") ||
+ strequal(hivename, "HKEY_LOCAL_MACHINE")) {
+ reg_type = HKEY_LOCAL_MACHINE;
+ } else if (strequal(hivename, "HKCR") ||
+ strequal(hivename, "HKEY_CLASSES_ROOT")) {
+ reg_type = HKEY_CLASSES_ROOT;
+ } else if (strequal(hivename, "HKU") ||
+ strequal(hivename, "HKEY_USERS")) {
+ reg_type = HKEY_USERS;
+ } else if (strequal(hivename, "HKCU") ||
+ strequal(hivename, "HKEY_CURRENT_USER")) {
+ reg_type = HKEY_CURRENT_USER;
+ } else if (strequal(hivename, "HKPD") ||
+ strequal(hivename, "HKEY_PERFORMANCE_DATA")) {
+ reg_type = HKEY_PERFORMANCE_DATA;
+ } else {
+ DEBUG(10,("dcerpc_winreg_int_openkey: unrecognised hive key %s\n",
+ key));
+ *pwerr = WERR_INVALID_PARAMETER;
+ return NT_STATUS_OK;
+ }
+
+ return _winreg_int_openkey(mem_ctx,
+ server_info,
+ msg_ctx,
+ h,
+ reg_type,
+ key,
+ create_key,
+ access_mask,
+ hive_handle,
+ key_handle,
+ pwerr);
+}
+
+NTSTATUS dcerpc_winreg_int_hklm_openkey(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **h,
+ const char *key,
+ bool create_key,
+ uint32_t access_mask,
+ struct policy_handle *hive_handle,
+ struct policy_handle *key_handle,
+ WERROR *pwerr)
+{
+ return _winreg_int_openkey(mem_ctx,
+ server_info,
+ msg_ctx,
+ h,
+ HKEY_LOCAL_MACHINE,
+ key,
+ create_key,
+ access_mask,
+ hive_handle,
+ key_handle,
+ pwerr);
+}
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_client/cli_winreg_int.h b/source3/rpc_client/cli_winreg_int.h
new file mode 100644
index 0000000..9584088
--- /dev/null
+++ b/source3/rpc_client/cli_winreg_int.h
@@ -0,0 +1,103 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * WINREG internal client routines
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CLI_WINREG_INT_H
+#define CLI_WINREG_INT_H
+
+struct auth_session_info;
+struct dcerpc_binding_handle;
+
+/**
+ * @brief Connect to the internal winreg server and open the given key.
+ *
+ * The function will create the needed subkeys if they don't exist.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] session_info The supplied server info.
+ *
+ * @param[in] key The key to open. This needs to start with the name
+ * of the hive like HKLM.
+ *
+ * @param[in] create_key Set to true if the key should be created if it
+ * doesn't exist.
+ *
+ * @param[in] access_mask The access mask to open the key.
+ *
+ * @param[out] binding_handle A pointer for the winreg dcerpc binding handle.
+ *
+ * @param[out] hive_handle A policy handle for the opened hive.
+ *
+ * @param[out] key_handle A policy handle for the opened key.
+ *
+ * @return WERR_OK on success, the corresponding DOS error
+ * code if something gone wrong.
+ */
+NTSTATUS dcerpc_winreg_int_openkey(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *server_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **h,
+ const char *key,
+ bool create_key,
+ uint32_t access_mask,
+ struct policy_handle *hive_handle,
+ struct policy_handle *key_handle,
+ WERROR *pwerr);
+
+/**
+ * @brief Connect to the internal winreg server and open the given key.
+ *
+ * The function will create the needed subkeys if they don't exist.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] server_info The supplied server info.
+ *
+ * @param[in] key The key to open.
+ *
+ * @param[in] create_key Set to true if the key should be created if it
+ * doesn't exist.
+ *
+ * @param[in] access_mask The access mask to open the key.
+ *
+ * @param[out] binding_handle A pointer for the winreg dcerpc binding handle.
+ *
+ * @param[out] hive_handle A policy handle for the opened hive.
+ *
+ * @param[out] key_handle A policy handle for the opened key.
+ *
+ * @return WERR_OK on success, the corresponding DOS error
+ * code if something gone wrong.
+ */
+NTSTATUS dcerpc_winreg_int_hklm_openkey(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **h,
+ const char *key,
+ bool create_key,
+ uint32_t access_mask,
+ struct policy_handle *hive_handle,
+ struct policy_handle *key_handle,
+ WERROR *pwerr);
+
+#endif /* CLI_WINREG_INT_H */
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_client/cli_winreg_spoolss.c b/source3/rpc_client/cli_winreg_spoolss.c
new file mode 100644
index 0000000..ca46ae5
--- /dev/null
+++ b/source3/rpc_client/cli_winreg_spoolss.c
@@ -0,0 +1,4729 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SPOOLSS RPC Pipe server / winreg client routines
+ *
+ * Copyright (c) 2010 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "nt_printing.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "secrets.h"
+#include "../libcli/security/security.h"
+#include "rpc_client/cli_winreg.h"
+#include "../libcli/registry/util_reg.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+#include "printing/nt_printing_os2.h"
+#include "rpc_client/init_spoolss.h"
+
+#define TOP_LEVEL_PRINT_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print"
+#define TOP_LEVEL_PRINT_PRINTERS_KEY TOP_LEVEL_PRINT_KEY "\\Printers"
+#define TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY TOP_LEVEL_PRINT_KEY "\\PackageInstallation"
+#define TOP_LEVEL_CONTROL_KEY "SYSTEM\\CurrentControlSet\\Control\\Print"
+#define TOP_LEVEL_CONTROL_FORMS_KEY TOP_LEVEL_CONTROL_KEY "\\Forms"
+
+#define CHECK_ERROR(result) \
+ if (W_ERROR_IS_OK(result)) continue; \
+ if (W_ERROR_EQUAL(result, WERR_NOT_FOUND)) result = WERR_OK; \
+ if (!W_ERROR_IS_OK(result)) break
+
+/* FLAGS, NAME, with, height, left, top, right, bottom */
+static const struct spoolss_FormInfo1 builtin_forms1[] = {
+ { SPOOLSS_FORM_BUILTIN, "Letter", {0x34b5c,0x44368}, {0x0,0x0,0x34b5c,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "Letter Small", {0x34b5c,0x44368}, {0x0,0x0,0x34b5c,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "Tabloid", {0x44368,0x696b8}, {0x0,0x0,0x44368,0x696b8} },
+ { SPOOLSS_FORM_BUILTIN, "Ledger", {0x696b8,0x44368}, {0x0,0x0,0x696b8,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "Legal", {0x34b5c,0x56d10}, {0x0,0x0,0x34b5c,0x56d10} },
+ { SPOOLSS_FORM_BUILTIN, "Statement", {0x221b4,0x34b5c}, {0x0,0x0,0x221b4,0x34b5c} },
+ { SPOOLSS_FORM_BUILTIN, "Executive", {0x2cf56,0x411cc}, {0x0,0x0,0x2cf56,0x411cc} },
+ { SPOOLSS_FORM_BUILTIN, "A3", {0x48828,0x668a0}, {0x0,0x0,0x48828,0x668a0} },
+ { SPOOLSS_FORM_BUILTIN, "A4", {0x33450,0x48828}, {0x0,0x0,0x33450,0x48828} },
+ { SPOOLSS_FORM_BUILTIN, "A4 Small", {0x33450,0x48828}, {0x0,0x0,0x33450,0x48828} },
+ { SPOOLSS_FORM_BUILTIN, "A5", {0x24220,0x33450}, {0x0,0x0,0x24220,0x33450} },
+ { SPOOLSS_FORM_BUILTIN, "B4 (JIS)", {0x3ebe8,0x58de0}, {0x0,0x0,0x3ebe8,0x58de0} },
+ { SPOOLSS_FORM_BUILTIN, "B5 (JIS)", {0x2c6f0,0x3ebe8}, {0x0,0x0,0x2c6f0,0x3ebe8} },
+ { SPOOLSS_FORM_BUILTIN, "Folio", {0x34b5c,0x509d8}, {0x0,0x0,0x34b5c,0x509d8} },
+ { SPOOLSS_FORM_BUILTIN, "Quarto", {0x347d8,0x43238}, {0x0,0x0,0x347d8,0x43238} },
+ { SPOOLSS_FORM_BUILTIN, "10x14", {0x3e030,0x56d10}, {0x0,0x0,0x3e030,0x56d10} },
+ { SPOOLSS_FORM_BUILTIN, "11x17", {0x44368,0x696b8}, {0x0,0x0,0x44368,0x696b8} },
+ { SPOOLSS_FORM_BUILTIN, "Note", {0x34b5c,0x44368}, {0x0,0x0,0x34b5c,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope #9", {0x18079,0x37091}, {0x0,0x0,0x18079,0x37091} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope #10", {0x19947,0x3ae94}, {0x0,0x0,0x19947,0x3ae94} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope #11", {0x1be7c,0x40565}, {0x0,0x0,0x1be7c,0x40565} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope #12", {0x1d74a,0x44368}, {0x0,0x0,0x1d74a,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope #14", {0x1f018,0x47504}, {0x0,0x0,0x1f018,0x47504} },
+ { SPOOLSS_FORM_BUILTIN, "C size sheet", {0x696b8,0x886d0}, {0x0,0x0,0x696b8,0x886d0} },
+ { SPOOLSS_FORM_BUILTIN, "D size sheet", {0x886d0,0xd2d70}, {0x0,0x0,0x886d0,0xd2d70} },
+ { SPOOLSS_FORM_BUILTIN, "E size sheet", {0xd2d70,0x110da0},{0x0,0x0,0xd2d70,0x110da0} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope DL", {0x1adb0,0x35b60}, {0x0,0x0,0x1adb0,0x35b60} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope C5", {0x278d0,0x37e88}, {0x0,0x0,0x278d0,0x37e88} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope C3", {0x4f1a0,0x6fd10}, {0x0,0x0,0x4f1a0,0x6fd10} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope C4", {0x37e88,0x4f1a0}, {0x0,0x0,0x37e88,0x4f1a0} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope C6", {0x1bd50,0x278d0}, {0x0,0x0,0x1bd50,0x278d0} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope C65", {0x1bd50,0x37e88}, {0x0,0x0,0x1bd50,0x37e88} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope B4", {0x3d090,0x562e8}, {0x0,0x0,0x3d090,0x562e8} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope B5", {0x2af80,0x3d090}, {0x0,0x0,0x2af80,0x3d090} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope B6", {0x2af80,0x1e848}, {0x0,0x0,0x2af80,0x1e848} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope", {0x1adb0,0x38270}, {0x0,0x0,0x1adb0,0x38270} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope Monarch", {0x18079,0x2e824}, {0x0,0x0,0x18079,0x2e824} },
+ { SPOOLSS_FORM_BUILTIN, "6 3/4 Envelope", {0x167ab,0x284ec}, {0x0,0x0,0x167ab,0x284ec} },
+ { SPOOLSS_FORM_BUILTIN, "US Std Fanfold", {0x5c3e1,0x44368}, {0x0,0x0,0x5c3e1,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "German Std Fanfold", {0x34b5c,0x4a6a0}, {0x0,0x0,0x34b5c,0x4a6a0} },
+ { SPOOLSS_FORM_BUILTIN, "German Legal Fanfold", {0x34b5c,0x509d8}, {0x0,0x0,0x34b5c,0x509d8} },
+ { SPOOLSS_FORM_BUILTIN, "B4 (ISO)", {0x3d090,0x562e8}, {0x0,0x0,0x3d090,0x562e8} },
+ { SPOOLSS_FORM_BUILTIN, "Japanese Postcard", {0x186a0,0x24220}, {0x0,0x0,0x186a0,0x24220} },
+ { SPOOLSS_FORM_BUILTIN, "9x11", {0x37cf8,0x44368}, {0x0,0x0,0x37cf8,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "10x11", {0x3e030,0x44368}, {0x0,0x0,0x3e030,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "15x11", {0x5d048,0x44368}, {0x0,0x0,0x5d048,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "Envelope Invite", {0x35b60,0x35b60}, {0x0,0x0,0x35b60,0x35b60} },
+ { SPOOLSS_FORM_BUILTIN, "Reserved48", {0x1,0x1}, {0x0,0x0,0x1,0x1} },
+ { SPOOLSS_FORM_BUILTIN, "Reserved49", {0x1,0x1}, {0x0,0x0,0x1,0x1} },
+ { SPOOLSS_FORM_BUILTIN, "Letter Extra", {0x3ae94,0x4a6a0}, {0x0,0x0,0x3ae94,0x4a6a0} },
+ { SPOOLSS_FORM_BUILTIN, "Legal Extra", {0x3ae94,0x5d048}, {0x0,0x0,0x3ae94,0x5d048} },
+ { SPOOLSS_FORM_BUILTIN, "Tabloid Extra", {0x4a6a0,0x6f9f0}, {0x0,0x0,0x4a6a0,0x6f9f0} },
+ { SPOOLSS_FORM_BUILTIN, "A4 Extra", {0x397c2,0x4eb16}, {0x0,0x0,0x397c2,0x4eb16} },
+ { SPOOLSS_FORM_BUILTIN, "Letter Transverse", {0x34b5c,0x44368}, {0x0,0x0,0x34b5c,0x44368} },
+ { SPOOLSS_FORM_BUILTIN, "A4 Transverse", {0x33450,0x48828}, {0x0,0x0,0x33450,0x48828} },
+ { SPOOLSS_FORM_BUILTIN, "Letter Extra Transverse", {0x3ae94,0x4a6a0}, {0x0,0x0,0x3ae94,0x4a6a0} },
+ { SPOOLSS_FORM_BUILTIN, "Super A", {0x376b8,0x56ea0}, {0x0,0x0,0x376b8,0x56ea0} },
+ { SPOOLSS_FORM_BUILTIN, "Super B", {0x4a768,0x76e58}, {0x0,0x0,0x4a768,0x76e58} },
+ { SPOOLSS_FORM_BUILTIN, "Letter Plus", {0x34b5c,0x4eb16}, {0x0,0x0,0x34b5c,0x4eb16} },
+ { SPOOLSS_FORM_BUILTIN, "A4 Plus", {0x33450,0x50910}, {0x0,0x0,0x33450,0x50910} },
+ { SPOOLSS_FORM_BUILTIN, "A5 Transverse", {0x24220,0x33450}, {0x0,0x0,0x24220,0x33450} },
+ { SPOOLSS_FORM_BUILTIN, "B5 (JIS) Transverse", {0x2c6f0,0x3ebe8}, {0x0,0x0,0x2c6f0,0x3ebe8} },
+ { SPOOLSS_FORM_BUILTIN, "A3 Extra", {0x4e9d0,0x6ca48}, {0x0,0x0,0x4e9d0,0x6ca48} },
+ { SPOOLSS_FORM_BUILTIN, "A5 Extra", {0x2a7b0,0x395f8}, {0x0,0x0,0x2a7b0,0x395f8} },
+ { SPOOLSS_FORM_BUILTIN, "B5 (ISO) Extra", {0x31128,0x43620}, {0x0,0x0,0x31128,0x43620} },
+ { SPOOLSS_FORM_BUILTIN, "A2", {0x668a0,0x91050}, {0x0,0x0,0x668a0,0x91050} },
+ { SPOOLSS_FORM_BUILTIN, "A3 Transverse", {0x48828,0x668a0}, {0x0,0x0,0x48828,0x668a0} },
+ { SPOOLSS_FORM_BUILTIN, "A3 Extra Transverse", {0x4e9d0,0x6ca48}, {0x0,0x0,0x4e9d0,0x6ca48} },
+ { SPOOLSS_FORM_BUILTIN, "Japanese Double Postcard", {0x30d40,0x24220}, {0x0,0x0,0x30d40,0x24220} },
+ { SPOOLSS_FORM_BUILTIN, "A6", {0x19a28,0x24220}, {0x0,0x0,0x19a28,0x24220} },
+ { SPOOLSS_FORM_BUILTIN, "Japan Envelope Kaku #2 Rotated", {0x510e0,0x3a980}, {0x0,0x0,0x510e0,0x3a980} },
+ { SPOOLSS_FORM_BUILTIN, "Japan Envelope Kaku #3 Rotated", {0x43a08,0x34bc0}, {0x0,0x0,0x43a08,0x34bc0} },
+ { SPOOLSS_FORM_BUILTIN, "Japan Envelope Chou #3 Rotated", {0x395f8,0x1d4c0}, {0x0,0x0,0x395f8,0x1d4c0} },
+ { SPOOLSS_FORM_BUILTIN, "Japan Envelope Chou #4 Rotated", {0x320c8,0x15f90}, {0x0,0x0,0x320c8,0x15f90} },
+ { SPOOLSS_FORM_BUILTIN, "Letter Rotated", {0x44368,0x34b5c}, {0x0,0x0,0x44368,0x34b5c} },
+ { SPOOLSS_FORM_BUILTIN, "A3 Rotated", {0x668a0,0x48828}, {0x0,0x0,0x668a0,0x48828} },
+ { SPOOLSS_FORM_BUILTIN, "A4 Rotated", {0x48828,0x33450}, {0x0,0x0,0x48828,0x33450} },
+ { SPOOLSS_FORM_BUILTIN, "A5 Rotated", {0x33450,0x24220}, {0x0,0x0,0x33450,0x24220} },
+ { SPOOLSS_FORM_BUILTIN, "B4 (JIS) Rotated", {0x58de0,0x3ebe8}, {0x0,0x0,0x58de0,0x3ebe8} },
+ { SPOOLSS_FORM_BUILTIN, "B5 (JIS) Rotated", {0x3ebe8,0x2c6f0}, {0x0,0x0,0x3ebe8,0x2c6f0} },
+ { SPOOLSS_FORM_BUILTIN, "Japanese Postcard Rotated", {0x24220,0x186a0}, {0x0,0x0,0x24220,0x186a0} },
+ { SPOOLSS_FORM_BUILTIN, "Double Japan Postcard Rotated", {0x24220,0x30d40}, {0x0,0x0,0x24220,0x30d40} },
+ { SPOOLSS_FORM_BUILTIN, "A6 Rotated", {0x24220,0x19a28}, {0x0,0x0,0x24220,0x19a28} },
+ { SPOOLSS_FORM_BUILTIN, "Japanese Envelope Kaku #2", {0x3a980,0x510e0}, {0x0,0x0,0x3a980,0x510e0} },
+ { SPOOLSS_FORM_BUILTIN, "Japanese Envelope Kaku #3", {0x34bc0,0x43a08}, {0x0,0x0,0x34bc0,0x43a08} },
+ { SPOOLSS_FORM_BUILTIN, "Japanese Envelope Chou #3", {0x1d4c0,0x395f8}, {0x0,0x0,0x1d4c0,0x395f8} },
+ { SPOOLSS_FORM_BUILTIN, "Japanese Envelope Chou #4", {0x15f90,0x320c8}, {0x0,0x0,0x15f90,0x320c8} },
+ { SPOOLSS_FORM_BUILTIN, "B6 (JIS)", {0x1f400,0x2c6f0}, {0x0,0x0,0x1f400,0x2c6f0} },
+ { SPOOLSS_FORM_BUILTIN, "B6 (JIS) Rotated", {0x2c6f0,0x1f400}, {0x0,0x0,0x2c6f0,0x1f400} },
+ { SPOOLSS_FORM_BUILTIN, "12x11", {0x4a724,0x443e1}, {0x0,0x0,0x4a724,0x443e1} },
+ { SPOOLSS_FORM_BUILTIN, "Japan Envelope You #4", {0x19a28,0x395f8}, {0x0,0x0,0x19a28,0x395f8} },
+ { SPOOLSS_FORM_BUILTIN, "Japan Envelope You #4 Rotated", {0x395f8,0x19a28}, {0x0,0x0,0x395f8,0x19a28} },
+ { SPOOLSS_FORM_BUILTIN, "PRC 16K", {0x2de60,0x3f7a0}, {0x0,0x0,0x2de60,0x3f7a0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC 32K", {0x1fbd0,0x2cec0}, {0x0,0x0,0x1fbd0,0x2cec0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC 32K(Big)", {0x222e0,0x318f8}, {0x0,0x0,0x222e0,0x318f8} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #1", {0x18e70,0x28488}, {0x0,0x0,0x18e70,0x28488} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #2", {0x18e70,0x2af80}, {0x0,0x0,0x18e70,0x2af80} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #3", {0x1e848,0x2af80}, {0x0,0x0,0x1e848,0x2af80} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #4", {0x1adb0,0x32c80}, {0x0,0x0,0x1adb0,0x32c80} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #5", {0x1adb0,0x35b60}, {0x0,0x0,0x1adb0,0x35b60} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #6", {0x1d4c0,0x38270}, {0x0,0x0,0x1d4c0,0x38270} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #7", {0x27100,0x38270}, {0x0,0x0,0x27100,0x38270} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #8", {0x1d4c0,0x4b708}, {0x0,0x0,0x1d4c0,0x4b708} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #9", {0x37e88,0x4f1a0}, {0x0,0x0,0x37e88,0x4f1a0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #10", {0x4f1a0,0x6fd10}, {0x0,0x0,0x4f1a0,0x6fd10} },
+ { SPOOLSS_FORM_BUILTIN, "PRC 16K Rotated", {0x3f7a0,0x2de60}, {0x0,0x0,0x3f7a0,0x2de60} },
+ { SPOOLSS_FORM_BUILTIN, "PRC 32K Rotated", {0x2cec0,0x1fbd0}, {0x0,0x0,0x2cec0,0x1fbd0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC 32K(Big) Rotated", {0x318f8,0x222e0}, {0x0,0x0,0x318f8,0x222e0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #1 Rotated", {0x28488,0x18e70}, {0x0,0x0,0x28488,0x18e70} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #2 Rotated", {0x2af80,0x18e70}, {0x0,0x0,0x2af80,0x18e70} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #3 Rotated", {0x2af80,0x1e848}, {0x0,0x0,0x2af80,0x1e848} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #4 Rotated", {0x32c80,0x1adb0}, {0x0,0x0,0x32c80,0x1adb0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #5 Rotated", {0x35b60,0x1adb0}, {0x0,0x0,0x35b60,0x1adb0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #6 Rotated", {0x38270,0x1d4c0}, {0x0,0x0,0x38270,0x1d4c0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #7 Rotated", {0x38270,0x27100}, {0x0,0x0,0x38270,0x27100} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #8 Rotated", {0x4b708,0x1d4c0}, {0x0,0x0,0x4b708,0x1d4c0} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #9 Rotated", {0x4f1a0,0x37e88}, {0x0,0x0,0x4f1a0,0x37e88} },
+ { SPOOLSS_FORM_BUILTIN, "PRC Envelope #10 Rotated", {0x6fd10,0x4f1a0}, {0x0,0x0,0x6fd10,0x4f1a0} }
+};
+
+/********************************************************************
+ static helper functions
+********************************************************************/
+
+/****************************************************************************
+ Update the changeid time.
+****************************************************************************/
+/**
+ * @internal
+ *
+ * @brief Update the ChangeID time of a printer.
+ *
+ * This is SO NASTY as some drivers need this to change, others need it
+ * static. This value will change every second, and I must hope that this
+ * is enough..... DON'T CHANGE THIS CODE WITHOUT A TEST MATRIX THE SIZE OF
+ * UTAH ! JRA.
+ *
+ * @return The ChangeID.
+ */
+static uint32_t winreg_printer_rev_changeid(void)
+{
+ struct timeval tv;
+
+ get_process_uptime(&tv);
+
+#if 1 /* JERRY */
+ /* Return changeid as msec since spooler restart */
+ return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+#else
+ /*
+ * This setting seems to work well but is too untested
+ * to replace the above calculation. Left in for experimentation
+ * of the reader --jerry (Tue Mar 12 09:15:05 CST 2002)
+ */
+ return tv.tv_sec * 10 + tv.tv_usec / 100000;
+#endif
+}
+
+static WERROR winreg_printer_openkey(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *binding_handle,
+ const char *path,
+ const char *key,
+ bool create_key,
+ uint32_t access_mask,
+ struct policy_handle *hive_handle,
+ struct policy_handle *key_handle)
+{
+ struct winreg_String wkey, wkeyclass;
+ char *keyname;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+
+ status = dcerpc_winreg_OpenHKLM(binding_handle,
+ mem_ctx,
+ NULL,
+ access_mask,
+ hive_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_printer_openkey: Could not open HKLM hive: %s\n",
+ nt_errstr(status)));
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_openkey: Could not open HKLM hive: %s\n",
+ win_errstr(result)));
+ return result;
+ }
+
+ if (key && *key) {
+ keyname = talloc_asprintf(mem_ctx, "%s\\%s", path, key);
+ } else {
+ keyname = talloc_strdup(mem_ctx, path);
+ }
+ if (keyname == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(wkey);
+ wkey.name = keyname;
+
+ if (create_key) {
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ key_handle,
+ &action,
+ &result);
+ switch (action) {
+ case REG_ACTION_NONE:
+ DEBUG(8, ("winreg_printer_openkey:createkey did nothing -- huh?\n"));
+ break;
+ case REG_CREATED_NEW_KEY:
+ DEBUG(8, ("winreg_printer_openkey: createkey created %s\n", keyname));
+ break;
+ case REG_OPENED_EXISTING_KEY:
+ DEBUG(8, ("winreg_printer_openkey: createkey opened existing %s\n", keyname));
+ break;
+ }
+ } else {
+ status = dcerpc_winreg_OpenKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ 0,
+ access_mask,
+ key_handle,
+ &result);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(hive_handle)) {
+ dcerpc_winreg_CloseKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ &ignore);
+ }
+ ZERO_STRUCTP(hive_handle);
+
+ return result;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR winreg_printer_open_core_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *binding_handle,
+ const char *architecture,
+ const char *key,
+ uint32_t access_mask,
+ struct policy_handle *hive_handle,
+ struct policy_handle *key_handle)
+{
+ struct winreg_String wkey, wkeyclass;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+ const char *path;
+
+ status = dcerpc_winreg_OpenHKLM(binding_handle,
+ mem_ctx,
+ NULL,
+ access_mask,
+ hive_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("winreg_printer_open_core_driver: Could not open HKLM hive: %s\n",
+ nt_errstr(status)));
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0,("winreg_printer_open_core_driver: Could not open HKLM hive: %s\n",
+ win_errstr(result)));
+ return result;
+ }
+
+ ZERO_STRUCT(wkey);
+ wkey.name = TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY;
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ key_handle,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ dcerpc_winreg_CloseKey(binding_handle, mem_ctx, key_handle, &ignore);
+
+ path = talloc_asprintf(mem_ctx, "%s\\%s",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ wkey.name = path;
+
+ status = dcerpc_winreg_CreateKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ key_handle,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ dcerpc_winreg_CloseKey(binding_handle, mem_ctx, key_handle, &ignore);
+
+ path = talloc_asprintf(mem_ctx, "%s\\%s\\CorePrinterDrivers",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ wkey.name = path;
+
+ status = dcerpc_winreg_CreateKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ key_handle,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ dcerpc_winreg_CloseKey(binding_handle, mem_ctx, key_handle, &ignore);
+
+ path = talloc_asprintf(mem_ctx, "%s\\%s\\CorePrinterDrivers\\%s",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture,
+ key);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ wkey.name = path;
+
+ status = dcerpc_winreg_CreateKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ key_handle,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(hive_handle)) {
+ dcerpc_winreg_CloseKey(binding_handle,
+ mem_ctx,
+ hive_handle,
+ &ignore);
+ }
+ ZERO_STRUCTP(hive_handle);
+
+ return result;
+}
+
+/**
+ * @brief Create the registry keyname for the given printer.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] printer The name of the printer to get the registry key.
+ *
+ * @return The registry key or NULL on error.
+ */
+static char *winreg_printer_data_keyname(TALLOC_CTX *mem_ctx, const char *printer) {
+ return talloc_asprintf(mem_ctx, "%s\\%s", TOP_LEVEL_PRINT_PRINTERS_KEY, printer);
+}
+
+static WERROR winreg_printer_opendriver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *drivername,
+ const char *architecture,
+ uint32_t version,
+ uint32_t access_mask,
+ bool create,
+ struct policy_handle *hive_hnd,
+ struct policy_handle *key_hnd)
+{
+ WERROR result;
+ char *key_name;
+
+ key_name = talloc_asprintf(mem_ctx, "%s\\Environments\\%s\\Drivers\\Version-%u",
+ TOP_LEVEL_CONTROL_KEY,
+ architecture, version);
+ if (!key_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_openkey(mem_ctx,
+ winreg_handle,
+ key_name,
+ drivername,
+ create,
+ access_mask,
+ hive_hnd,
+ key_hnd);
+ return result;
+}
+
+static WERROR winreg_enumval_to_dword(TALLOC_CTX *mem_ctx,
+ struct spoolss_PrinterEnumValues *v,
+ const char *valuename, uint32_t *dw)
+{
+ /* just return if it is not the one we are looking for */
+ if (strcmp(valuename, v->value_name) != 0) {
+ return WERR_NOT_FOUND;
+ }
+
+ if (v->type != REG_DWORD) {
+ return WERR_INVALID_DATATYPE;
+ }
+
+ if (v->data_length != 4) {
+ *dw = 0;
+ return WERR_OK;
+ }
+
+ *dw = IVAL(v->data->data, 0);
+ return WERR_OK;
+}
+
+static WERROR winreg_enumval_to_sz(TALLOC_CTX *mem_ctx,
+ struct spoolss_PrinterEnumValues *v,
+ const char *valuename, const char **_str)
+{
+ /* just return if it is not the one we are looking for */
+ if (strcmp(valuename, v->value_name) != 0) {
+ return WERR_NOT_FOUND;
+ }
+
+ if (v->type != REG_SZ) {
+ return WERR_INVALID_DATATYPE;
+ }
+
+ if (v->data_length == 0) {
+ *_str = talloc_strdup(mem_ctx, "");
+ if (*_str == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ return WERR_OK;
+ }
+
+ if (!pull_reg_sz(mem_ctx, v->data, _str)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR winreg_enumval_to_multi_sz(TALLOC_CTX *mem_ctx,
+ struct spoolss_PrinterEnumValues *v,
+ const char *valuename,
+ const char ***array)
+{
+ /* just return if it is not the one we are looking for */
+ if (strcmp(valuename, v->value_name) != 0) {
+ return WERR_NOT_FOUND;
+ }
+
+ if (v->type != REG_MULTI_SZ) {
+ return WERR_INVALID_DATATYPE;
+ }
+
+ if (v->data_length == 0) {
+ *array = talloc_array(mem_ctx, const char *, 1);
+ if (*array == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *array[0] = NULL;
+ return WERR_OK;
+ }
+
+ if (!pull_reg_multi_sz(mem_ctx, v->data, array)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR winreg_printer_write_date(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ struct policy_handle *key_handle,
+ const char *value,
+ NTTIME data)
+{
+ struct winreg_String wvalue = { 0, };
+ DATA_BLOB blob;
+ WERROR result = WERR_OK;
+ NTSTATUS status;
+ const char *str;
+ struct tm *tm;
+ time_t t;
+
+ if (data == 0) {
+ str = talloc_strdup(mem_ctx, "01/01/1601");
+ } else {
+ t = nt_time_to_unix(data);
+ tm = localtime(&t);
+ if (tm == NULL) {
+ return map_werror_from_unix(errno);
+ }
+ str = talloc_asprintf(mem_ctx, "%02d/%02d/%04d",
+ tm->tm_mon + 1, tm->tm_mday, tm->tm_year + 1900);
+ }
+ if (!str) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ wvalue.name = value;
+ if (!push_reg_sz(mem_ctx, &blob, str)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ status = dcerpc_winreg_SetValue(winreg_handle,
+ mem_ctx,
+ key_handle,
+ wvalue,
+ REG_SZ,
+ blob.data,
+ blob.length,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_write_date: Could not set value %s: %s\n",
+ wvalue.name, win_errstr(result)));
+ }
+
+ return result;
+}
+
+static WERROR winreg_printer_date_to_NTTIME(const char *str, NTTIME *data)
+{
+ bool ok;
+
+ ok = spoolss_timestr_to_NTTIME(str, data);
+ if (!ok) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR winreg_printer_write_ver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ struct policy_handle *key_handle,
+ const char *value,
+ uint64_t data)
+{
+ struct winreg_String wvalue = { 0, };
+ DATA_BLOB blob;
+ WERROR result = WERR_OK;
+ NTSTATUS status;
+ char *str;
+
+ /*
+ * this needs to be something like: 6.1.7600.16385
+ */
+ str = talloc_asprintf(mem_ctx, "%u.%u.%u.%u",
+ (unsigned)((data >> 48) & 0xFFFF),
+ (unsigned)((data >> 32) & 0xFFFF),
+ (unsigned)((data >> 16) & 0xFFFF),
+ (unsigned)(data & 0xFFFF));
+ if (!str) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ wvalue.name = value;
+ if (!push_reg_sz(mem_ctx, &blob, str)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ status = dcerpc_winreg_SetValue(winreg_handle,
+ mem_ctx,
+ key_handle,
+ wvalue,
+ REG_SZ,
+ blob.data,
+ blob.length,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_write_date: Could not set value %s: %s\n",
+ wvalue.name, win_errstr(result)));
+ }
+
+ return result;
+}
+
+static WERROR winreg_printer_ver_to_qword(const char *str, uint64_t *data)
+{
+ bool ok;
+
+ ok = spoolss_driver_version_to_qword(str, data);
+ if (!ok) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ Public winreg function for spoolss
+********************************************************************/
+
+WERROR winreg_create_printer(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *sharename)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct spoolss_SetPrinterInfo2 *info2;
+ struct security_descriptor *secdesc;
+ struct winreg_String wkey, wkeyclass;
+ const char *path;
+ const char *subkeys[] = { SPOOL_DSDRIVER_KEY, SPOOL_DSSPOOLER_KEY, SPOOL_PRINTERDATA_KEY };
+ uint32_t i, count = ARRAY_SIZE(subkeys);
+ uint32_t info2_mask = 0;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, sharename);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_create_printer: Skipping, %s already exists\n", path));
+ goto done;
+ } else if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ DEBUG(2, ("winreg_create_printer: Creating default values in %s\n", path));
+ } else if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_create_printer: Could not open key %s: %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ /* Create the main key */
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_create_printer_keys: Could not create key %s: %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &result);
+ }
+
+ /* Create subkeys */
+ for (i = 0; i < count; i++) {
+ NTSTATUS status;
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+
+ ZERO_STRUCT(key_hnd);
+ ZERO_STRUCT(wkey);
+
+ wkey.name = talloc_asprintf(tmp_ctx, "%s\\%s", path, subkeys[i]);
+ if (wkey.name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(winreg_handle,
+ tmp_ctx,
+ &hive_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ &key_hnd,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_create_printer_keys: Could not create key %s: %s\n",
+ wkey.name, win_errstr(result)));
+ goto done;
+ }
+
+ if (strequal(subkeys[i], SPOOL_DSSPOOLER_KEY)) {
+ const char *dnssuffix;
+ const char *longname;
+ const char *uncname;
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_PRINTERNAME,
+ sharename,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_PRINTSHARENAME,
+ sharename,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_SHORTSERVERNAME,
+ lp_netbios_name(),
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* We make the assumption that the netbios name
+ * is the same as the DNS name since the former
+ * will be what we used to join the domain
+ */
+ dnssuffix = get_mydnsdomname(tmp_ctx);
+ if (dnssuffix != NULL && dnssuffix[0] != '\0') {
+ longname = talloc_asprintf(tmp_ctx, "%s.%s", lp_netbios_name(), dnssuffix);
+ } else {
+ longname = talloc_strdup(tmp_ctx, lp_netbios_name());
+ }
+ if (longname == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_SERVERNAME,
+ longname,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ uncname = talloc_asprintf(tmp_ctx, "\\\\%s\\%s",
+ longname, sharename);
+ if (uncname == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_UNCNAME,
+ uncname,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_VERSIONNUMBER,
+ 4,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_PRINTSTARTTIME,
+ 0,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_PRINTENDTIME,
+ 0,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_PRIORITY,
+ 1,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ SPOOL_REG_PRINTKEEPPRINTEDJOBS,
+ 0,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &result);
+ }
+ }
+ info2 = talloc_zero(tmp_ctx, struct spoolss_SetPrinterInfo2);
+ if (info2 == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ info2->printername = sharename;
+ if (info2->printername == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ info2_mask |= SPOOLSS_PRINTER_INFO_PRINTERNAME;
+
+ info2->sharename = sharename;
+ info2_mask |= SPOOLSS_PRINTER_INFO_SHARENAME;
+
+ info2->portname = SAMBA_PRINTER_PORT_NAME;
+ info2_mask |= SPOOLSS_PRINTER_INFO_PORTNAME;
+
+ info2->printprocessor = "winprint";
+ info2_mask |= SPOOLSS_PRINTER_INFO_PRINTPROCESSOR;
+
+ info2->datatype = "RAW";
+ info2_mask |= SPOOLSS_PRINTER_INFO_DATATYPE;
+
+ info2->comment = "";
+ info2_mask |= SPOOLSS_PRINTER_INFO_COMMENT;
+
+ info2->attributes = PRINTER_ATTRIBUTE_SAMBA;
+ info2_mask |= SPOOLSS_PRINTER_INFO_ATTRIBUTES;
+
+ info2->starttime = 0; /* Minutes since 12:00am GMT */
+ info2_mask |= SPOOLSS_PRINTER_INFO_STARTTIME;
+
+ info2->untiltime = 0; /* Minutes since 12:00am GMT */
+ info2_mask |= SPOOLSS_PRINTER_INFO_UNTILTIME;
+
+ info2->priority = 1;
+ info2_mask |= SPOOLSS_PRINTER_INFO_PRIORITY;
+
+ info2->defaultpriority = 1;
+ info2_mask |= SPOOLSS_PRINTER_INFO_DEFAULTPRIORITY;
+
+ result = spoolss_create_default_secdesc(tmp_ctx, &secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ info2_mask |= SPOOLSS_PRINTER_INFO_SECDESC;
+
+ /*
+ * Don't write a default Device Mode to the registry! The Device Mode is
+ * only written to disk with a SetPrinter level 2 or 8.
+ */
+
+ result = winreg_update_printer(tmp_ctx,
+ winreg_handle,
+ sharename,
+ info2_mask,
+ info2,
+ NULL,
+ secdesc);
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_update_printer(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *sharename,
+ uint32_t info2_mask,
+ struct spoolss_SetPrinterInfo2 *info2,
+ struct spoolss_DeviceMode *devmode,
+ struct security_descriptor *secdesc)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ int snum = lp_servicenumber(sharename);
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ char *path;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, sharename);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_update_printer: Could not open key %s: %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_ATTRIBUTES) {
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Attributes",
+ info2->attributes,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+#if 0
+ if (info2_mask & SPOOLSS_PRINTER_INFO_AVERAGEPPM) {
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "AveragePpm",
+ info2->attributes,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+#endif
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_COMMENT) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Description",
+ info2->comment,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_DATATYPE) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Datatype",
+ info2->datatype,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_DEFAULTPRIORITY) {
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Default Priority",
+ info2->defaultpriority,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_DEVMODE) {
+ /*
+ * Some client drivers freak out if there is a NULL devmode
+ * (probably the driver is not checking before accessing
+ * the devmode pointer) --jerry
+ */
+ if (devmode == NULL && lp_default_devmode(snum) && info2 != NULL) {
+ result = spoolss_create_default_devmode(tmp_ctx,
+ info2->printername,
+ &devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (devmode->size != (ndr_size_spoolss_DeviceMode(devmode, 0) - devmode->__driverextra_length)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, tmp_ctx, devmode,
+ (ndr_push_flags_fn_t) ndr_push_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("winreg_update_printer: Failed to marshall device mode\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_binary(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Default DevMode",
+ &blob,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_DRIVERNAME) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Printer Driver",
+ info2->drivername,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_LOCATION) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Location",
+ info2->location,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_PARAMETERS) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Parameters",
+ info2->parameters,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_PORTNAME) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Port",
+ info2->portname,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_PRINTERNAME) {
+ /*
+ * in addprinter: no servername and the printer is the name
+ * in setprinter: servername is \\server
+ * and printer is \\server\\printer
+ *
+ * Samba manages only local printers.
+ * we currently don't support things like i
+ * path=\\other_server\printer
+ *
+ * We only store the printername, not \\server\printername
+ */
+ const char *p = strrchr(info2->printername, '\\');
+ if (p == NULL) {
+ p = info2->printername;
+ } else {
+ p++;
+ }
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Name",
+ p,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_PRINTPROCESSOR) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Print Processor",
+ info2->printprocessor,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_PRIORITY) {
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Priority",
+ info2->priority,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_SECDESC) {
+ /*
+ * We need a security descriptor, if it isn't specified by
+ * AddPrinter{Ex} then create a default descriptor.
+ */
+ if (secdesc == NULL) {
+ result = spoolss_create_default_secdesc(tmp_ctx, &secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+ result = winreg_set_printer_secdesc(tmp_ctx,
+ winreg_handle,
+ sharename,
+ secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_SEPFILE) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Separator File",
+ info2->sepfile,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_SHARENAME) {
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Share Name",
+ info2->sharename,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_STARTTIME) {
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "StartTime",
+ info2->starttime,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_STATUS) {
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Status",
+ info2->status,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2_mask & SPOOLSS_PRINTER_INFO_UNTILTIME) {
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "UntilTime",
+ info2->untiltime,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "ChangeID",
+ winreg_printer_rev_changeid(),
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_printer(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **pinfo2)
+{
+ struct spoolss_PrinterInfo2 *info2;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd = { .handle_type = 0 };
+ struct policy_handle key_hnd = { .handle_type = 0 };
+ struct spoolss_PrinterEnumValues enum_value;
+ struct spoolss_PrinterEnumValues *v = NULL;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ int snum = lp_servicenumber(printer);
+ uint32_t num_values = 0;
+ uint32_t i;
+ char *path;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ const char **enum_names = NULL;
+ enum winreg_Type *enum_types = NULL;
+ DATA_BLOB *enum_data_blobs = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_get_printer: Could not open key %s: %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enumvals(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ &num_values,
+ &enum_names,
+ &enum_types,
+ &enum_data_blobs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)){
+ result = ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_get_printer: Could not enumerate values in %s: %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ result = WERR_NOT_ENOUGH_MEMORY;
+
+ info2 = talloc_zero(tmp_ctx, struct spoolss_PrinterInfo2);
+ if (info2 == NULL) {
+ goto done;
+ }
+
+ info2->servername = talloc_strdup(info2, "");
+ if (info2->servername == NULL) {
+ goto done;
+ }
+ info2->printername = talloc_strdup(info2, "");
+ if (info2->printername == NULL) {
+ goto done;
+ }
+ info2->sharename = talloc_strdup(info2, "");
+ if (info2->sharename == NULL) {
+ goto done;
+ }
+ info2->portname = talloc_strdup(info2, "");
+ if (info2->portname == NULL) {
+ goto done;
+ }
+ info2->drivername = talloc_strdup(info2, "");
+ if (info2->drivername == NULL) {
+ goto done;
+ }
+ info2->comment = talloc_strdup(info2, "");
+ if (info2->comment == NULL) {
+ goto done;
+ }
+ info2->location = talloc_strdup(info2, "");
+ if (info2->location == NULL) {
+ goto done;
+ }
+ info2->sepfile = talloc_strdup(info2, "");
+ if (info2->sepfile == NULL) {
+ goto done;
+ }
+ info2->printprocessor = talloc_strdup(info2, "");
+ if (info2->printprocessor == NULL) {
+ goto done;
+ }
+ info2->datatype = talloc_strdup(info2, "");
+ if (info2->datatype == NULL) {
+ goto done;
+ }
+ info2->parameters = talloc_strdup(info2, "");
+ if (info2->parameters == NULL) {
+ goto done;
+ }
+
+ for (i = 0; i < num_values; i++) {
+ enum_value.value_name = enum_names[i];
+ enum_value.value_name_len = 2*strlen_m_term(enum_names[i]);
+ enum_value.type = enum_types[i];
+ enum_value.data_length = enum_data_blobs[i].length;
+ enum_value.data = NULL;
+ if (enum_value.data_length != 0){
+ enum_value.data = &enum_data_blobs[i];
+ }
+ v = &enum_value;
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Name",
+ &info2->printername);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Share Name",
+ &info2->sharename);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Port",
+ &info2->portname);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Description",
+ &info2->comment);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Location",
+ &info2->location);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Separator File",
+ &info2->sepfile);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Print Processor",
+ &info2->printprocessor);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Datatype",
+ &info2->datatype);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Parameters",
+ &info2->parameters);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info2,
+ v,
+ "Printer Driver",
+ &info2->drivername);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info2,
+ v,
+ "Attributes",
+ &info2->attributes);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info2,
+ v,
+ "Priority",
+ &info2->priority);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info2,
+ v,
+ "Default Priority",
+ &info2->defaultpriority);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info2,
+ v,
+ "StartTime",
+ &info2->starttime);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info2,
+ v,
+ "UntilTime",
+ &info2->untiltime);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info2,
+ v,
+ "Status",
+ &info2->status);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info2,
+ v,
+ "StartTime",
+ &info2->starttime);
+ CHECK_ERROR(result);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_get_printer: winreg_enumval_to_TYPE() failed "
+ "for %s: %s\n",
+ v->value_name,
+ win_errstr(result)));
+ goto done;
+ }
+
+ /* Construct the Device Mode */
+ status = dcerpc_winreg_query_binary(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Default DevMode",
+ &blob,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (W_ERROR_IS_OK(result)) {
+ info2->devmode = talloc_zero(info2, struct spoolss_DeviceMode);
+ if (info2->devmode == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ ndr_err = ndr_pull_struct_blob(&blob,
+ info2->devmode,
+ info2->devmode,
+ (ndr_pull_flags_fn_t) ndr_pull_spoolss_DeviceMode);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0, ("winreg_get_printer: Failed to unmarshall device mode\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+
+ if (info2->devmode == NULL && lp_default_devmode(snum)) {
+ result = spoolss_create_default_devmode(info2,
+ info2->printername,
+ &info2->devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ if (info2->devmode) {
+ info2->devmode->size = ndr_size_spoolss_DeviceMode(info2->devmode, 0) - info2->devmode->driverextra_data.length;
+ }
+
+ result = winreg_get_printer_secdesc(info2,
+ winreg_handle,
+ printer,
+ &info2->secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Fix for OS/2 drivers. */
+ if (get_remote_arch() == RA_OS2) {
+ spoolss_map_to_os2_driver(info2, &info2->drivername);
+ }
+
+ if (pinfo2) {
+ *pinfo2 = talloc_move(mem_ctx, &info2);
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+static WERROR winreg_get_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *path,
+ const char *attribute,
+ struct spoolss_security_descriptor **psecdesc)
+{
+ struct spoolss_security_descriptor *secdesc;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+ WERROR result;
+ WERROR ignore;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ goto create_default;
+ }
+ goto done;
+ }
+
+ status = dcerpc_winreg_query_sd(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ attribute,
+ &secdesc,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ &ignore);
+ }
+
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle,
+ tmp_ctx,
+ &hive_hnd,
+ &ignore);
+ }
+ goto create_default;
+ }
+ goto done;
+ }
+
+ if (psecdesc) {
+ *psecdesc = talloc_move(mem_ctx, &secdesc);
+ }
+
+ result = WERR_OK;
+ goto done;
+
+create_default:
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = spoolss_create_default_secdesc(tmp_ctx, &secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* If security descriptor is owned by S-1-1-0 and winbindd is up,
+ this security descriptor has been created when winbindd was
+ down. Take ownership of security descriptor. */
+ if (dom_sid_equal(secdesc->owner_sid, &global_sid_World)) {
+ struct dom_sid owner_sid;
+
+ /* Change sd owner to workgroup administrator */
+
+ if (secrets_fetch_domain_sid(lp_workgroup(), &owner_sid)) {
+ struct spoolss_security_descriptor *new_secdesc;
+ size_t size;
+
+ /* Create new sd */
+ sid_append_rid(&owner_sid, DOMAIN_RID_ADMINISTRATOR);
+
+ new_secdesc = make_sec_desc(tmp_ctx,
+ secdesc->revision,
+ secdesc->type,
+ &owner_sid,
+ secdesc->group_sid,
+ secdesc->sacl,
+ secdesc->dacl,
+ &size);
+
+ if (new_secdesc == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* Swap with other one */
+ secdesc = new_secdesc;
+ }
+ }
+
+ status = dcerpc_winreg_set_sd(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ attribute,
+ secdesc,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ if (psecdesc) {
+ *psecdesc = talloc_move(mem_ctx, &secdesc);
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_printer_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *sharename,
+ struct spoolss_security_descriptor **psecdesc)
+{
+ WERROR result;
+ char *path;
+
+ path = winreg_printer_data_keyname(mem_ctx, sharename);
+ if (path == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_get_secdesc(mem_ctx, winreg_handle,
+ path,
+ "Security",
+ psecdesc);
+ talloc_free(path);
+
+ return result;
+}
+
+WERROR winreg_get_printserver_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ struct spoolss_security_descriptor **psecdesc)
+{
+ return winreg_get_secdesc(mem_ctx, winreg_handle,
+ TOP_LEVEL_CONTROL_KEY,
+ "ServerSecurityDescriptor",
+ psecdesc);
+}
+
+static WERROR winreg_set_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *path,
+ const char *attribute,
+ const struct spoolss_security_descriptor *secdesc)
+{
+ const struct spoolss_security_descriptor *new_secdesc = secdesc;
+ struct spoolss_security_descriptor *old_secdesc;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+ WERROR result;
+ WERROR ignore;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /*
+ * The old owner and group sids of the security descriptor are not
+ * present when new ACEs are added or removed by changing printer
+ * permissions through NT. If they are NULL in the new security
+ * descriptor then copy them over from the old one.
+ */
+ if (!secdesc->owner_sid || !secdesc->group_sid) {
+ struct dom_sid *owner_sid, *group_sid;
+ struct security_acl *dacl, *sacl;
+ size_t size;
+
+ result = winreg_get_secdesc(tmp_ctx,
+ winreg_handle,
+ path,
+ attribute,
+ &old_secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ /* Pick out correct owner and group sids */
+ owner_sid = secdesc->owner_sid ?
+ secdesc->owner_sid :
+ old_secdesc->owner_sid;
+
+ group_sid = secdesc->group_sid ?
+ secdesc->group_sid :
+ old_secdesc->group_sid;
+
+ dacl = secdesc->dacl ?
+ secdesc->dacl :
+ old_secdesc->dacl;
+
+ sacl = secdesc->sacl ?
+ secdesc->sacl :
+ old_secdesc->sacl;
+
+ /* Make a deep copy of the security descriptor */
+ new_secdesc = make_sec_desc(tmp_ctx,
+ secdesc->revision,
+ secdesc->type,
+ owner_sid,
+ group_sid,
+ sacl,
+ dacl,
+ &size);
+ if (new_secdesc == NULL) {
+ talloc_free(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sd(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ attribute,
+ new_secdesc,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_set_printer_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *sharename,
+ const struct spoolss_security_descriptor *secdesc)
+{
+ char *path;
+ WERROR result;
+
+ path = winreg_printer_data_keyname(mem_ctx, sharename);
+ if (path == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_set_secdesc(mem_ctx, winreg_handle,
+ path,
+ "Security", secdesc);
+ talloc_free(path);
+
+ return result;
+}
+
+WERROR winreg_set_printserver_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const struct spoolss_security_descriptor *secdesc)
+{
+ return winreg_set_secdesc(mem_ctx, winreg_handle,
+ TOP_LEVEL_CONTROL_KEY,
+ "ServerSecurityDescriptor",
+ secdesc);
+}
+
+/* Set printer data over the winreg pipe. */
+WERROR winreg_set_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type type,
+ uint8_t *data,
+ uint32_t data_size)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct winreg_String wvalue = { 0, };
+ char *path;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ DEBUG(8, ("winreg_set_printer_dataex: Open printer key %s, value %s, access_mask: 0x%05x for [%s]\n",
+ key, value, access_mask, printer));
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ key,
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_set_printer_dataex: Could not open key %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ wvalue.name = value;
+ status = dcerpc_winreg_SetValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ wvalue,
+ type,
+ data,
+ data_size,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_set_printer_dataex: Could not set value %s: %s\n",
+ value, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/* Get printer data over a winreg pipe. */
+WERROR winreg_get_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type *type,
+ uint8_t **data,
+ uint32_t *data_size)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct winreg_String wvalue;
+ enum winreg_Type type_in = REG_NONE;
+ char *path;
+ uint8_t *data_in = NULL;
+ uint32_t data_in_size = 0;
+ uint32_t value_len = 0;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_get_printer_dataex: Could not open key %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ wvalue.name = value;
+
+ /*
+ * call QueryValue once with data == NULL to get the
+ * needed memory size to be allocated, then allocate
+ * data buffer and call again.
+ */
+ status = dcerpc_winreg_QueryValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ &wvalue,
+ &type_in,
+ NULL,
+ &data_in_size,
+ &value_len,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_get_printer_dataex: Could not query value %s: %s\n",
+ value, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_get_printer_dataex: Could not query value %s: %s\n",
+ value, win_errstr(result)));
+ goto done;
+ }
+
+ data_in = (uint8_t *) TALLOC(tmp_ctx, data_in_size);
+ if (data_in == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ value_len = 0;
+
+ status = dcerpc_winreg_QueryValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ &wvalue,
+ &type_in,
+ data_in,
+ &data_in_size,
+ &value_len,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_get_printer_dataex: Could not query value %s: %s\n",
+ value, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_get_printer_dataex: Could not query value %s: %s\n",
+ value, win_errstr(result)));
+ goto done;
+ }
+
+ *type = type_in;
+ *data_size = data_in_size;
+ if (data_in_size) {
+ *data = talloc_move(mem_ctx, &data_in);
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/* Enumerate on the values of a given key and provide the data. */
+WERROR winreg_enum_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_values,
+ struct spoolss_PrinterEnumValues **penum_values)
+{
+ uint32_t i;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+
+ struct spoolss_PrinterEnumValues *enum_values = NULL;
+ uint32_t num_values = 0;
+ char *path;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ NTSTATUS status;
+ const char **enum_names = NULL;
+ enum winreg_Type *enum_types = NULL;
+ DATA_BLOB *enum_data_blobs = NULL;
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_enum_printer_dataex: Could not open key %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enumvals(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ &num_values,
+ &enum_names,
+ &enum_types,
+ &enum_data_blobs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)){
+ result = ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_enum_printer_dataex: Could not enumerate values in %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ enum_values = talloc_array(tmp_ctx, struct spoolss_PrinterEnumValues, num_values);
+ if (enum_values == NULL){
+ result = WERR_NOT_ENOUGH_MEMORY;
+ DEBUG(0, ("winreg_enum_printer_dataex: Could not enumerate values in %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ for (i = 0; i < num_values; i++){
+ enum_values[i].value_name = enum_names[i];
+ enum_values[i].value_name_len = strlen_m_term(enum_names[i]) * 2;
+ enum_values[i].type = enum_types[i];
+ enum_values[i].data_length = enum_data_blobs[i].length;
+ enum_values[i].data = NULL;
+
+ if (enum_values[i].data_length != 0){
+ enum_values[i].data = &enum_data_blobs[i];
+ }
+ }
+
+ talloc_steal(enum_values, enum_names);
+ talloc_steal(enum_values, enum_data_blobs);
+
+ *pnum_values = num_values;
+ if (penum_values) {
+ *penum_values = talloc_move(mem_ctx, &enum_values);
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/* Delete printer data over a winreg pipe. */
+WERROR winreg_delete_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ const char *key,
+ const char *value)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct winreg_String wvalue = { 0, };
+ char *path;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ NTSTATUS status;
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_delete_printer_dataex: Could not open key %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ wvalue.name = value;
+ status = dcerpc_winreg_DeleteValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ wvalue,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_delete_printer_dataex: Could not delete value %s: %s\n",
+ value, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/* Enumerate on the subkeys of a given key and provide the data. */
+WERROR winreg_enum_printer_key(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_subkeys,
+ const char ***psubkeys)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ char *path;
+ const char **subkeys = NULL;
+ uint32_t num_subkeys = -1;
+
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ NTSTATUS status;
+
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_enum_printer_key: Could not open key %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enum_keys(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ &num_subkeys,
+ &subkeys,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_enum_printer_key: Could not enumerate subkeys in %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ *pnum_subkeys = num_subkeys;
+ if (psubkeys) {
+ *psubkeys = talloc_move(mem_ctx, &subkeys);
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/* Delete a key with subkeys of a given printer. */
+WERROR winreg_delete_printer_key(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ const char *key)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ char *keyname;
+ char *path;
+ WERROR result;
+ WERROR ignore;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ /* key doesn't exist */
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ result = WERR_OK;
+ goto done;
+ }
+
+ DEBUG(0, ("winreg_delete_printer_key: Could not open key %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &result);
+ }
+
+ if (key == NULL || key[0] == '\0') {
+ keyname = path;
+ } else {
+ keyname = talloc_asprintf(tmp_ctx,
+ "%s\\%s",
+ path,
+ key);
+ if (keyname == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+
+ status = dcerpc_winreg_delete_subkeys_recursive(tmp_ctx,
+ winreg_handle,
+ &hive_hnd,
+ access_mask,
+ keyname,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_delete_printer_key: Could not delete key %s: %s\n",
+ key, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_delete_printer_key: Could not delete key %s: %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_update_changeid(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ char *path;
+ NTSTATUS status;
+ WERROR result;
+ WERROR ignore;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_update_changeid: Could not open key %s: %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "ChangeID",
+ winreg_printer_rev_changeid(),
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_get_changeid(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *printer,
+ uint32_t *pchangeid)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ uint32_t changeid = 0;
+ char *path;
+ NTSTATUS status;
+ WERROR result;
+ WERROR ignore;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = winreg_printer_data_keyname(tmp_ctx, printer);
+ if (path == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ "",
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_printer_get_changeid: Could not open key %s: %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ DEBUG(10, ("winreg_printer_get_changeid: get changeid from %s\n", path));
+
+ status = dcerpc_winreg_query_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "ChangeID",
+ &changeid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ if (pchangeid) {
+ *pchangeid = changeid;
+ }
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+/*
+ * The special behaviour of the spoolss forms is documented at the website:
+ *
+ * Managing Win32 Printserver Forms
+ * http://unixwiz.net/techtips/winspooler-forms.html
+ */
+
+WERROR winreg_printer_addform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ struct spoolss_AddFormInfo1 *form)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct winreg_String wvalue = { 0, };
+ DATA_BLOB blob;
+ uint32_t num_info = 0;
+ union spoolss_FormInfo *info = NULL;
+ uint32_t i;
+ WERROR result;
+ WERROR ignore;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ TOP_LEVEL_CONTROL_FORMS_KEY,
+ "",
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_addform1: Could not open key %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+
+ result = winreg_printer_enumforms1(tmp_ctx, winreg_handle,
+ &num_info, &info);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_addform: Could not enum keys %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+
+ /* If form name already exists or is builtin return ALREADY_EXISTS */
+ for (i = 0; i < num_info; i++) {
+ if (strequal(info[i].info1.form_name, form->form_name)) {
+ result = WERR_FILE_EXISTS;
+ goto done;
+ }
+ }
+
+ wvalue.name = form->form_name;
+
+ blob = data_blob_talloc(tmp_ctx, NULL, 32);
+ SIVAL(blob.data, 0, form->size.width);
+ SIVAL(blob.data, 4, form->size.height);
+ SIVAL(blob.data, 8, form->area.left);
+ SIVAL(blob.data, 12, form->area.top);
+ SIVAL(blob.data, 16, form->area.right);
+ SIVAL(blob.data, 20, form->area.bottom);
+ SIVAL(blob.data, 24, num_info + 1); /* FIXME */
+ SIVAL(blob.data, 28, form->flags);
+
+ status = dcerpc_winreg_SetValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ wvalue,
+ REG_BINARY,
+ blob.data,
+ blob.length,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_printer_addform1: Could not set value %s: %s\n",
+ wvalue.name, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(info);
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_enumforms1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ uint32_t *pnum_info,
+ union spoolss_FormInfo **pinfo)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ union spoolss_FormInfo *info;
+ struct spoolss_PrinterEnumValues *enum_values = NULL;
+ uint32_t num_values = 0;
+ uint32_t num_builtin = ARRAY_SIZE(builtin_forms1);
+ uint32_t i;
+ WERROR result;
+ WERROR ignore;
+ NTSTATUS status;
+ const char **enum_names = NULL;
+ enum winreg_Type *enum_types = NULL;
+ DATA_BLOB *enum_data_blobs = NULL;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ TOP_LEVEL_CONTROL_FORMS_KEY,
+ "",
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ /* key doesn't exist */
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ result = WERR_OK;
+ goto done;
+ }
+
+ DEBUG(0, ("winreg_printer_enumforms1: Could not open key %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enumvals(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ &num_values,
+ &enum_names,
+ &enum_types,
+ &enum_data_blobs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)){
+ result = ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_enumforms1: Could not enumerate values in %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+
+ enum_values = talloc_zero_array(tmp_ctx,
+ struct spoolss_PrinterEnumValues,
+ num_values);
+ if (enum_values == NULL){
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < num_values; i++){
+ enum_values[i].value_name = enum_names[i];
+ enum_values[i].value_name_len = strlen_m_term(enum_names[i]) * 2;
+ enum_values[i].type = enum_types[i];
+ enum_values[i].data_length = enum_data_blobs[i].length;
+ enum_values[i].data = NULL;
+ if (enum_values[i].data_length != 0){
+ enum_values[i].data = &enum_data_blobs[i];
+ }
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_enumforms1: Could not enumerate values in %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+
+ info = talloc_array(tmp_ctx, union spoolss_FormInfo, num_builtin + num_values);
+ if (info == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* Enumerate BUILTIN forms */
+ for (i = 0; i < num_builtin; i++) {
+ info[i].info1 = builtin_forms1[i];
+ }
+
+ /* Enumerate registry forms */
+ for (i = 0; i < num_values; i++) {
+ union spoolss_FormInfo val;
+
+ if (enum_values[i].type != REG_BINARY ||
+ enum_values[i].data_length != 32) {
+ continue;
+ }
+
+ val.info1.form_name = talloc_strdup(info, enum_values[i].value_name);
+ if (val.info1.form_name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ val.info1.size.width = IVAL(enum_values[i].data->data, 0);
+ val.info1.size.height = IVAL(enum_values[i].data->data, 4);
+ val.info1.area.left = IVAL(enum_values[i].data->data, 8);
+ val.info1.area.top = IVAL(enum_values[i].data->data, 12);
+ val.info1.area.right = IVAL(enum_values[i].data->data, 16);
+ val.info1.area.bottom = IVAL(enum_values[i].data->data, 20);
+ /* skip form index IVAL(enum_values[i].data->data, 24)));*/
+ val.info1.flags = (enum spoolss_FormFlags) IVAL(enum_values[i].data->data, 28);
+
+ info[i + num_builtin] = val;
+ }
+
+ *pnum_info = num_builtin + num_values;
+ if (pinfo) {
+ *pinfo = talloc_move(mem_ctx, &info);
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(enum_values);
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_deleteform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *form_name)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct winreg_String wvalue = { 0, };
+ uint32_t num_builtin = ARRAY_SIZE(builtin_forms1);
+ uint32_t i;
+ WERROR result = WERR_OK;
+ WERROR ignore;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ for (i = 0; i < num_builtin; i++) {
+ if (strequal(builtin_forms1[i].form_name, form_name)) {
+ return WERR_INVALID_PARAMETER;
+ }
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ TOP_LEVEL_CONTROL_FORMS_KEY,
+ "",
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_deleteform1: Could not open key %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ result = WERR_INVALID_FORM_NAME;
+ }
+ goto done;
+ }
+
+ wvalue.name = form_name;
+ status = dcerpc_winreg_DeleteValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ wvalue,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* If the value doesn't exist, return WERR_INVALID_FORM_NAME */
+ DEBUG(0, ("winreg_printer_delteform1: Could not delete value %s: %s\n",
+ wvalue.name, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ result = WERR_INVALID_FORM_NAME;
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_setform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *form_name,
+ struct spoolss_AddFormInfo1 *form)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd = { 0, };
+ struct policy_handle key_hnd = { 0, };
+ struct winreg_String wvalue = { 0, };
+ DATA_BLOB blob;
+ uint32_t num_builtin = ARRAY_SIZE(builtin_forms1);
+ uint32_t i;
+ WERROR result;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ for (i = 0; i < num_builtin; i++) {
+ if (strequal(builtin_forms1[i].form_name, form->form_name)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ TOP_LEVEL_CONTROL_FORMS_KEY,
+ "",
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_setform1: Could not open key %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+
+ /* If form_name != form->form_name then we renamed the form */
+ if (strequal(form_name, form->form_name)) {
+ result = winreg_printer_deleteform1(tmp_ctx, winreg_handle,
+ form_name);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_printer_setform1: Could not open key %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+ }
+
+ wvalue.name = form->form_name;
+
+ blob = data_blob_talloc(tmp_ctx, NULL, 32);
+ SIVAL(blob.data, 0, form->size.width);
+ SIVAL(blob.data, 4, form->size.height);
+ SIVAL(blob.data, 8, form->area.left);
+ SIVAL(blob.data, 12, form->area.top);
+ SIVAL(blob.data, 16, form->area.right);
+ SIVAL(blob.data, 20, form->area.bottom);
+ SIVAL(blob.data, 24, 42);
+ SIVAL(blob.data, 28, form->flags);
+
+ status = dcerpc_winreg_SetValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ wvalue,
+ REG_BINARY,
+ blob.data,
+ blob.length,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_printer_setform1: Could not set value %s: %s\n",
+ wvalue.name, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ }
+
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_getform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *form_name,
+ struct spoolss_FormInfo1 *r)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct winreg_String wvalue;
+ enum winreg_Type type_in = REG_NONE;
+ uint8_t *data_in = NULL;
+ uint32_t data_in_size = 0;
+ uint32_t value_len = 0;
+ uint32_t num_builtin = ARRAY_SIZE(builtin_forms1);
+ uint32_t i;
+ WERROR result;
+ WERROR ignore;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ /* check builtin forms first */
+ for (i = 0; i < num_builtin; i++) {
+ if (strequal(builtin_forms1[i].form_name, form_name)) {
+ *r = builtin_forms1[i];
+ return WERR_OK;
+ }
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ TOP_LEVEL_CONTROL_FORMS_KEY,
+ "",
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("winreg_printer_getform1: Could not open key %s: %s\n",
+ TOP_LEVEL_CONTROL_FORMS_KEY, win_errstr(result)));
+ goto done;
+ }
+
+ wvalue.name = form_name;
+
+ /*
+ * call QueryValue once with data == NULL to get the
+ * needed memory size to be allocated, then allocate
+ * data buffer and call again.
+ */
+ status = dcerpc_winreg_QueryValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ &wvalue,
+ &type_in,
+ NULL,
+ &data_in_size,
+ &value_len,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_printer_getform1: Could not query value %s: %s\n",
+ wvalue.name, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ data_in = (uint8_t *) TALLOC(tmp_ctx, data_in_size);
+ if (data_in == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ value_len = 0;
+
+ status = dcerpc_winreg_QueryValue(winreg_handle,
+ tmp_ctx,
+ &key_hnd,
+ &wvalue,
+ &type_in,
+ data_in,
+ &data_in_size,
+ &value_len,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_printer_getform1: Could not query value %s: %s\n",
+ wvalue.name, nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ r->form_name = talloc_strdup(mem_ctx, form_name);
+ if (r->form_name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ r->size.width = IVAL(data_in, 0);
+ r->size.height = IVAL(data_in, 4);
+ r->area.left = IVAL(data_in, 8);
+ r->area.top = IVAL(data_in, 12);
+ r->area.right = IVAL(data_in, 16);
+ r->area.bottom = IVAL(data_in, 20);
+ /* skip index IVAL(data_in, 24)));*/
+ r->flags = (enum spoolss_FormFlags) IVAL(data_in, 28);
+
+ result = WERR_OK;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_add_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ struct spoolss_AddDriverInfoCtr *r,
+ const char **driver_name,
+ uint32_t *driver_version)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct spoolss_DriverInfo8 info8;
+ TALLOC_CTX *tmp_ctx = NULL;
+ NTSTATUS status;
+ WERROR result;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+ ZERO_STRUCT(info8);
+
+ if (!driver_info_ctr_to_info8(r, &info8)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_opendriver(tmp_ctx,
+ winreg_handle,
+ info8.driver_name,
+ info8.architecture,
+ info8.version,
+ access_mask, true,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_add_driver: "
+ "Could not open driver key (%s,%s,%d): %s\n",
+ info8.driver_name, info8.architecture,
+ info8.version, win_errstr(result)));
+ goto done;
+ }
+
+ /* TODO: "Attributes" ? */
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Version",
+ info8.version,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Driver",
+ info8.driver_path,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Data File",
+ info8.data_file,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Configuration File",
+ info8.config_file,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Help File",
+ info8.help_file,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_multi_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Dependent Files",
+ info8.dependent_files,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Monitor",
+ info8.monitor_name,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Datatype",
+ info8.default_datatype,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_multi_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd, "Previous Names",
+ info8.previous_names,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_printer_write_date(tmp_ctx, winreg_handle,
+ &key_hnd, "DriverDate",
+ info8.driver_date);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_printer_write_ver(tmp_ctx, winreg_handle,
+ &key_hnd, "DriverVersion",
+ info8.driver_version);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Manufacturer",
+ info8.manufacturer_name,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "OEM URL",
+ info8.manufacturer_url,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "HardwareID",
+ info8.hardware_id,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Provider",
+ info8.provider,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Print Processor",
+ info8.print_processor,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "VendorSetup",
+ info8.vendor_setup,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_multi_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "Color Profiles",
+ info8.color_profiles,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "InfPath",
+ info8.inf_path,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "PrinterDriverAttributes",
+ info8.printer_driver_attributes,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_multi_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "CoreDependencies",
+ info8.core_driver_dependencies,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_printer_write_date(tmp_ctx, winreg_handle,
+ &key_hnd, "MinInboxDriverVerDate",
+ info8.min_inbox_driver_ver_date);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_printer_write_ver(tmp_ctx, winreg_handle, &key_hnd,
+ "MinInboxDriverVerVersion",
+ info8.min_inbox_driver_ver_version);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ *driver_name = info8.driver_name;
+ *driver_version = info8.version;
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *architecture,
+ const char *driver_name,
+ uint32_t driver_version,
+ struct spoolss_DriverInfo8 **_info8)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct spoolss_DriverInfo8 i8, *info8 = NULL;
+ struct spoolss_PrinterEnumValues *enum_values = NULL;
+ struct spoolss_PrinterEnumValues *v = NULL;
+ uint32_t num_values = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t i;
+ const char **enum_names = NULL;
+ enum winreg_Type *enum_types = NULL;
+ DATA_BLOB *enum_data_blobs = NULL;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+ ZERO_STRUCT(i8);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (driver_version == DRIVER_ANY_VERSION) {
+ /* look for Win2k first and then for NT4 */
+ result = winreg_printer_opendriver(tmp_ctx,
+ winreg_handle,
+ driver_name,
+ architecture,
+ 3,
+ access_mask, false,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ result = winreg_printer_opendriver(tmp_ctx,
+ winreg_handle,
+ driver_name,
+ architecture,
+ 2,
+ access_mask, false,
+ &hive_hnd,
+ &key_hnd);
+ }
+ } else {
+ /* ok normal case */
+ result = winreg_printer_opendriver(tmp_ctx,
+ winreg_handle,
+ driver_name,
+ architecture,
+ driver_version,
+ access_mask, false,
+ &hive_hnd,
+ &key_hnd);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(5, ("winreg_get_driver: "
+ "Could not open driver key (%s,%s,%d): %s\n",
+ driver_name, architecture,
+ driver_version, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enumvals(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ &num_values,
+ &enum_names,
+ &enum_types,
+ &enum_data_blobs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)){
+ result = ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_get_driver: "
+ "Could not enumerate values for (%s,%s,%d): %s\n",
+ driver_name, architecture,
+ driver_version, win_errstr(result)));
+ goto done;
+ }
+
+ enum_values = talloc_zero_array(tmp_ctx,
+ struct spoolss_PrinterEnumValues,
+ num_values);
+ if (enum_values == NULL){
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < num_values; i++){
+ enum_values[i].value_name = enum_names[i];
+ enum_values[i].value_name_len = strlen_m_term(enum_names[i]) * 2;
+ enum_values[i].type = enum_types[i];
+ enum_values[i].data_length = enum_data_blobs[i].length;
+ enum_values[i].data = NULL;
+ if (enum_values[i].data_length != 0){
+ enum_values[i].data = &enum_data_blobs[i];
+ }
+ }
+
+ info8 = talloc_zero(tmp_ctx, struct spoolss_DriverInfo8);
+ if (info8 == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ info8->driver_name = talloc_strdup(info8, driver_name);
+ if (info8->driver_name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ info8->architecture = talloc_strdup(info8, architecture);
+ if (info8->architecture == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = WERR_OK;
+
+ for (i = 0; i < num_values; i++) {
+ const char *tmp_str;
+ uint32_t tmp = 0;
+
+ v = &enum_values[i];
+
+ result = winreg_enumval_to_dword(info8, v,
+ "Version",
+ &tmp);
+ if (W_ERROR_IS_OK(result)) {
+ info8->version = (enum spoolss_DriverOSVersion) tmp;
+ }
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Driver",
+ &info8->driver_path);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Data File",
+ &info8->data_file);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Configuration File",
+ &info8->config_file);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Help File",
+ &info8->help_file);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_multi_sz(info8, v,
+ "Dependent Files",
+ &info8->dependent_files);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Monitor",
+ &info8->monitor_name);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Datatype",
+ &info8->default_datatype);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_multi_sz(info8, v,
+ "Previous Names",
+ &info8->previous_names);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "DriverDate",
+ &tmp_str);
+ if (W_ERROR_IS_OK(result)) {
+ result = winreg_printer_date_to_NTTIME(tmp_str,
+ &info8->driver_date);
+ }
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "DriverVersion",
+ &tmp_str);
+ if (W_ERROR_IS_OK(result)) {
+ result = winreg_printer_ver_to_qword(tmp_str,
+ &info8->driver_version);
+ }
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Manufacturer",
+ &info8->manufacturer_name);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "OEM URL",
+ &info8->manufacturer_url);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "HardwareID",
+ &info8->hardware_id);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Provider",
+ &info8->provider);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "Print Processor",
+ &info8->print_processor);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "VendorSetup",
+ &info8->vendor_setup);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_multi_sz(info8, v,
+ "Color Profiles",
+ &info8->color_profiles);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "InfPath",
+ &info8->inf_path);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_dword(info8, v,
+ "PrinterDriverAttributes",
+ &info8->printer_driver_attributes);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_multi_sz(info8, v,
+ "CoreDependencies",
+ &info8->core_driver_dependencies);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "MinInboxDriverVerDate",
+ &tmp_str);
+ if (W_ERROR_IS_OK(result)) {
+ result = winreg_printer_date_to_NTTIME(tmp_str,
+ &info8->min_inbox_driver_ver_date);
+ }
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(info8, v,
+ "MinInboxDriverVerVersion",
+ &tmp_str);
+ if (W_ERROR_IS_OK(result)) {
+ result = winreg_printer_ver_to_qword(tmp_str,
+ &info8->min_inbox_driver_ver_version);
+ }
+ CHECK_ERROR(result);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_enumval_to_TYPE() failed "
+ "for %s: %s\n", v->value_name,
+ win_errstr(result)));
+ goto done;
+ }
+
+ *_info8 = talloc_steal(mem_ctx, info8);
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_del_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ struct spoolss_DriverInfo8 *info8,
+ uint32_t version)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ TALLOC_CTX *tmp_ctx;
+ char *key_name;
+ WERROR result;
+ NTSTATUS status;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* test that the key exists */
+ result = winreg_printer_opendriver(tmp_ctx,
+ winreg_handle,
+ info8->driver_name,
+ info8->architecture,
+ version,
+ access_mask, false,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ /* key doesn't exist */
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ result = WERR_OK;
+ goto done;
+ }
+
+ DEBUG(5, ("winreg_del_driver: "
+ "Could not open driver (%s,%s,%u): %s\n",
+ info8->driver_name, info8->architecture,
+ version, win_errstr(result)));
+ goto done;
+ }
+
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &result);
+ }
+
+ key_name = talloc_asprintf(tmp_ctx,
+ "%s\\Environments\\%s\\Drivers\\Version-%u\\%s",
+ TOP_LEVEL_CONTROL_KEY,
+ info8->architecture, version,
+ info8->driver_name);
+ if (key_name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_winreg_delete_subkeys_recursive(tmp_ctx,
+ winreg_handle,
+ &hive_hnd,
+ access_mask,
+ key_name,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)){
+ DEBUG(0, ("winreg_del_driver: "
+ "Could not open driver (%s,%s,%u): %s\n",
+ info8->driver_name, info8->architecture,
+ version, nt_errstr(status)));
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_del_driver: "
+ "Could not open driver (%s,%s,%u): %s\n",
+ info8->driver_name, info8->architecture,
+ version, win_errstr(result)));
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_driver_list(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *architecture,
+ uint32_t version,
+ uint32_t *num_drivers,
+ const char ***drivers_p)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ const char **drivers;
+ TALLOC_CTX *tmp_ctx;
+ WERROR result;
+ NTSTATUS status;
+
+ *num_drivers = 0;
+ *drivers_p = NULL;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* use NULL for the driver name so we open the key that is
+ * parent of all drivers for this architecture and version */
+ result = winreg_printer_opendriver(tmp_ctx,
+ winreg_handle,
+ NULL,
+ architecture,
+ version,
+ access_mask, false,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(5, ("winreg_get_driver_list: "
+ "Could not open key (%s,%u): %s\n",
+ architecture, version, win_errstr(result)));
+ result = WERR_OK;
+ goto done;
+ }
+
+ status = dcerpc_winreg_enum_keys(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ num_drivers,
+ &drivers,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_get_driver_list: "
+ "Could not enumerate drivers for (%s,%u): %s\n",
+ architecture, version, win_errstr(result)));
+ goto done;
+ }
+
+ *drivers_p = talloc_steal(mem_ctx, drivers);
+
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_core_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *architecture,
+ const struct GUID *core_driver_guid,
+ struct spoolss_CorePrinterDriver **_core_printer_driver)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct spoolss_CorePrinterDriver *c = NULL;
+ struct spoolss_PrinterEnumValues *enum_values = NULL;
+ struct spoolss_PrinterEnumValues *v = NULL;
+ uint32_t num_values = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+ WERROR result;
+ NTSTATUS status;
+ const char *path = NULL;
+ const char *guid_str = NULL;
+ uint32_t i;
+ const char **enum_names = NULL;
+ enum winreg_Type *enum_types = NULL;
+ DATA_BLOB *enum_data_blobs = NULL;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = talloc_asprintf(tmp_ctx, "%s\\%s\\CorePrinterDrivers",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ guid_str = GUID_string2(tmp_ctx, core_driver_guid);
+ if (guid_str == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ guid_str, /* key */
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(5, ("winreg_get_core_driver: "
+ "Could not open core driver key (%s,%s): %s\n",
+ guid_str, architecture, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enumvals(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ &num_values,
+ &enum_names,
+ &enum_types,
+ &enum_data_blobs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)){
+ result = ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_get_core_driver: "
+ "Could not enumerate values for (%s,%s): %s\n",
+ guid_str, architecture, win_errstr(result)));
+ goto done;
+ }
+
+ enum_values = talloc_zero_array(tmp_ctx,
+ struct spoolss_PrinterEnumValues,
+ num_values);
+ if (enum_values == NULL){
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < num_values; i++){
+ enum_values[i].value_name = enum_names[i];
+ enum_values[i].value_name_len = strlen_m_term(enum_names[i]) * 2;
+ enum_values[i].type = enum_types[i];
+ enum_values[i].data_length = enum_data_blobs[i].length;
+ enum_values[i].data = NULL;
+ if (enum_values[i].data_length != 0){
+ enum_values[i].data = &enum_data_blobs[i];
+ }
+ }
+
+ c = talloc_zero(tmp_ctx, struct spoolss_CorePrinterDriver);
+ if (c == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ c->core_driver_guid = *core_driver_guid;
+
+ result = WERR_OK;
+
+ for (i = 0; i < num_values; i++) {
+ const char *tmp_str;
+
+ v = &enum_values[i];
+
+ result = winreg_enumval_to_sz(c, v,
+ "InfPath",
+ &c->szPackageID);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(c, v,
+ "DriverDate",
+ &tmp_str);
+ if (W_ERROR_IS_OK(result)) {
+ result = winreg_printer_date_to_NTTIME(tmp_str,
+ &c->driver_date);
+ }
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(c, v,
+ "DriverVersion",
+ &tmp_str);
+ if (W_ERROR_IS_OK(result)) {
+ result = winreg_printer_ver_to_qword(tmp_str,
+ &c->driver_version);
+ }
+ CHECK_ERROR(result);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_enumval_to_TYPE() failed "
+ "for %s: %s\n", v->value_name,
+ win_errstr(result)));
+ goto done;
+ }
+
+ *_core_printer_driver = talloc_steal(mem_ctx, c);
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_add_core_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *architecture,
+ const struct spoolss_CorePrinterDriver *r)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ TALLOC_CTX *tmp_ctx = NULL;
+ NTSTATUS status;
+ WERROR result;
+ const char *guid_str;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ guid_str = GUID_string2(tmp_ctx, &r->core_driver_guid);
+ if (guid_str == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_printer_open_core_driver(tmp_ctx,
+ winreg_handle,
+ architecture,
+ guid_str,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_add_core_driver: "
+ "Could not open core driver key (%s,%s): %s\n",
+ guid_str, architecture, win_errstr(result)));
+ goto done;
+ }
+
+ result = winreg_printer_write_date(tmp_ctx, winreg_handle,
+ &key_hnd, "DriverDate",
+ r->driver_date);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_printer_write_ver(tmp_ctx, winreg_handle,
+ &key_hnd, "DriverVersion",
+ r->driver_version);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "InfPath",
+ r->szPackageID,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_add_driver_package(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *package_id,
+ const char *architecture,
+ const char *driver_store_path,
+ const char *cab_path)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ TALLOC_CTX *tmp_ctx = NULL;
+ NTSTATUS status;
+ WERROR result;
+ const char *path;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = talloc_asprintf(tmp_ctx, "%s\\%s\\DriverPackages",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ package_id, /* key */
+ true,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_add_driver_package: "
+ "Could not open driver package key (%s,%s): %s\n",
+ package_id, architecture, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "CabPath",
+ cab_path,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ "DriverStorePath",
+ driver_store_path,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_driver_package(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *package_id,
+ const char *architecture,
+ const char **driver_store_path,
+ const char **cab_path)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct spoolss_PrinterEnumValues *enum_values = NULL;
+ struct spoolss_PrinterEnumValues *v = NULL;
+ uint32_t num_values = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+ WERROR result;
+ NTSTATUS status;
+ const char *path = NULL;
+ uint32_t i;
+ const char **enum_names = NULL;
+ enum winreg_Type *enum_types = NULL;
+ DATA_BLOB *enum_data_blobs = NULL;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = talloc_asprintf(tmp_ctx, "%s\\%s\\DriverPackages",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ package_id, /* key */
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(5, ("winreg_get_driver_package: "
+ "Could not open driver package key (%s,%s): %s\n",
+ package_id, architecture, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enumvals(tmp_ctx,
+ winreg_handle,
+ &key_hnd,
+ &num_values,
+ &enum_names,
+ &enum_types,
+ &enum_data_blobs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)){
+ result = ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_get_driver_package: "
+ "Could not enumerate values for (%s,%s): %s\n",
+ package_id, architecture, win_errstr(result)));
+ goto done;
+ }
+
+ enum_values = talloc_zero_array(tmp_ctx,
+ struct spoolss_PrinterEnumValues,
+ num_values);
+ if (enum_values == NULL){
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < num_values; i++){
+ enum_values[i].value_name = enum_names[i];
+ enum_values[i].value_name_len = strlen_m_term(enum_names[i]) * 2;
+ enum_values[i].type = enum_types[i];
+ enum_values[i].data_length = enum_data_blobs[i].length;
+ enum_values[i].data = NULL;
+ if (enum_values[i].data_length != 0){
+ enum_values[i].data = &enum_data_blobs[i];
+ }
+ }
+
+ result = WERR_OK;
+
+ for (i = 0; i < num_values; i++) {
+
+ v = &enum_values[i];
+
+ result = winreg_enumval_to_sz(mem_ctx, v,
+ "CabPath",
+ cab_path);
+ CHECK_ERROR(result);
+
+ result = winreg_enumval_to_sz(mem_ctx, v,
+ "DriverStorePath",
+ driver_store_path);
+ CHECK_ERROR(result);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("winreg_enumval_to_TYPE() failed "
+ "for %s: %s\n", v->value_name,
+ win_errstr(result)));
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_del_driver_package(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *package_id,
+ const char *architecture)
+{
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ TALLOC_CTX *tmp_ctx;
+ WERROR result;
+ NTSTATUS status;
+ const char *path;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ path = talloc_asprintf(tmp_ctx, "%s\\%s\\DriverPackages",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ result = winreg_printer_openkey(tmp_ctx,
+ winreg_handle,
+ path,
+ package_id, /* key */
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ /* key doesn't exist */
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ result = WERR_OK;
+ goto done;
+ }
+
+ DEBUG(5, ("winreg_del_driver_package: "
+ "Could not open driver package key (%s,%s): %s\n",
+ package_id, architecture, win_errstr(result)));
+ goto done;
+ }
+
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &result);
+ }
+
+ path = talloc_asprintf(tmp_ctx, "%s\\%s\\DriverPackages\\%s",
+ TOP_LEVEL_PRINT_PACKAGEINSTALLATION_KEY,
+ architecture,
+ package_id);
+ if (path == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_winreg_delete_subkeys_recursive(tmp_ctx,
+ winreg_handle,
+ &hive_hnd,
+ access_mask,
+ path,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ DEBUG(5, ("winreg_del_driver_package: "
+ "Could not delete driver package key (%s,%s): %s\n",
+ package_id, architecture, nt_errstr(status)));
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(5, ("winreg_del_driver_package: "
+ "Could not delete driver package key (%s,%s): %s\n",
+ package_id, architecture, win_errstr(result)));
+ goto done;
+ }
+
+ result = WERR_OK;
+done:
+ if (winreg_handle != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(winreg_handle, tmp_ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
diff --git a/source3/rpc_client/cli_winreg_spoolss.h b/source3/rpc_client/cli_winreg_spoolss.h
new file mode 100644
index 0000000..c80ca72
--- /dev/null
+++ b/source3/rpc_client/cli_winreg_spoolss.h
@@ -0,0 +1,720 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SPOOLSS RPC Pipe server / winreg client routines
+ *
+ * Copyright (c) 2010 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_CLIENT_CLI_WINREG_SPOOLSS_H_
+#define _RPC_CLIENT_CLI_WINREG_SPOOLSS_H_
+
+#include "replace.h"
+#include "librpc/gen_ndr/spoolss.h"
+
+struct dcerpc_binding_handle;
+
+enum spoolss_PrinterInfo2Mask {
+ SPOOLSS_PRINTER_INFO_ATTRIBUTES = (int)(0x00000001),
+ SPOOLSS_PRINTER_INFO_AVERAGEPPM = (int)(0x00000002),
+ SPOOLSS_PRINTER_INFO_CJOBS = (int)(0x00000004),
+ SPOOLSS_PRINTER_INFO_COMMENT = (int)(0x00000008),
+ SPOOLSS_PRINTER_INFO_DATATYPE = (int)(0x00000010),
+ SPOOLSS_PRINTER_INFO_DEFAULTPRIORITY = (int)(0x00000020),
+ SPOOLSS_PRINTER_INFO_DEVMODE = (int)(0x00000040),
+ SPOOLSS_PRINTER_INFO_DRIVERNAME = (int)(0x00000080),
+ SPOOLSS_PRINTER_INFO_LOCATION = (int)(0x00000100),
+ SPOOLSS_PRINTER_INFO_NAME = (int)(0x00000200),
+ SPOOLSS_PRINTER_INFO_PARAMETERS = (int)(0x00000400),
+ SPOOLSS_PRINTER_INFO_PORTNAME = (int)(0x00000800),
+ SPOOLSS_PRINTER_INFO_PRINTERNAME = (int)(0x00001000),
+ SPOOLSS_PRINTER_INFO_PRINTPROCESSOR = (int)(0x00002000),
+ SPOOLSS_PRINTER_INFO_PRIORITY = (int)(0x00004000),
+ SPOOLSS_PRINTER_INFO_SECDESC = (int)(0x00008000),
+ SPOOLSS_PRINTER_INFO_SEPFILE = (int)(0x00010000),
+ SPOOLSS_PRINTER_INFO_SERVERNAME = (int)(0x00020000),
+ SPOOLSS_PRINTER_INFO_SHARENAME = (int)(0x00040000),
+ SPOOLSS_PRINTER_INFO_STARTTIME = (int)(0x00080000),
+ SPOOLSS_PRINTER_INFO_STATUS = (int)(0x00100000),
+ SPOOLSS_PRINTER_INFO_UNTILTIME = (int)(0x00200000)
+};
+
+#define SPOOLSS_PRINTER_INFO_ALL SPOOLSS_PRINTER_INFO_ATTRIBUTES | \
+ SPOOLSS_PRINTER_INFO_AVERAGEPPM | \
+ SPOOLSS_PRINTER_INFO_CJOBS | \
+ SPOOLSS_PRINTER_INFO_COMMENT | \
+ SPOOLSS_PRINTER_INFO_DATATYPE | \
+ SPOOLSS_PRINTER_INFO_DEFAULTPRIORITY | \
+ SPOOLSS_PRINTER_INFO_DEVMODE | \
+ SPOOLSS_PRINTER_INFO_DRIVERNAME | \
+ SPOOLSS_PRINTER_INFO_LOCATION | \
+ SPOOLSS_PRINTER_INFO_NAME | \
+ SPOOLSS_PRINTER_INFO_PARAMETERS | \
+ SPOOLSS_PRINTER_INFO_PORTNAME | \
+ SPOOLSS_PRINTER_INFO_PRINTERNAME | \
+ SPOOLSS_PRINTER_INFO_PRINTPROCESSOR | \
+ SPOOLSS_PRINTER_INFO_PRIORITY | \
+ SPOOLSS_PRINTER_INFO_SECDESC | \
+ SPOOLSS_PRINTER_INFO_SEPFILE | \
+ SPOOLSS_PRINTER_INFO_SERVERNAME | \
+ SPOOLSS_PRINTER_INFO_SHARENAME | \
+ SPOOLSS_PRINTER_INFO_STARTTIME | \
+ SPOOLSS_PRINTER_INFO_STATUS | \
+ SPOOLSS_PRINTER_INFO_UNTILTIME
+
+WERROR winreg_create_printer(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *sharename);
+
+/**
+ * @internal
+ *
+ * @brief Update the information of a printer in the registry.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] sharename The share name.
+ *
+ * @param[in] info2_mask A bitmask which defines which values should be set.
+ *
+ * @param[in] info2 A SetPrinterInfo2 structure with the data to set.
+ *
+ * @param[in] devmode A device mode structure with the data to set.
+ *
+ * @param[in] secdesc A security descriptor structure with the data to set.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_update_printer(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *sharename,
+ uint32_t info2_mask,
+ struct spoolss_SetPrinterInfo2 *info2,
+ struct spoolss_DeviceMode *devmode,
+ struct security_descriptor *secdesc);
+
+
+/**
+ * @brief Get the information of a printer stored in the registry.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The name of the printer to get.
+ *
+ * @param[out] pinfo2 A pointer to store a PRINTER_INFO_2 structure.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_get_printer(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **pinfo2);
+
+/**
+ * @brief Get the security descriptor for a printer.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] sharename The share name.
+ *
+ * @param[out] psecdesc A pointer to store the security descriptor.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_get_printer_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *sharename,
+ struct spoolss_security_descriptor **psecdesc);
+
+/**
+ * @brief Get the security descriptor for a printserver.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[out] psecdesc A pointer to store the security descriptor.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_get_printserver_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ struct spoolss_security_descriptor **psecdesc);
+
+/**
+ * @brief Set the security descriptor for a printer.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] sharename The share name.
+ *
+ * @param[in] secdesc The security descriptor to save.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_set_printer_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *sharename,
+ const struct spoolss_security_descriptor *secdesc);
+
+/**
+ * @brief Set the security descriptor for a printserver.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] secdesc The security descriptor to save.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_set_printserver_secdesc(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const struct spoolss_security_descriptor *secdesc);
+
+
+/**
+ * @internal
+ *
+ * @brief Set printer data over the winreg pipe.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @param[in] key The key of the printer data to store the value.
+ *
+ * @param[in] value The value name to save.
+ *
+ * @param[in] type The type of the value to use.
+ *
+ o @param[in] data The data which should be saved under the given value.
+ *
+ * @param[in] data_size The size of the data.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_set_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type type,
+ uint8_t *data,
+ uint32_t data_size);
+
+/**
+ * @internal
+ *
+ * @brief Get printer data over a winreg pipe.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @param[in] key The key of the printer data to get the value.
+ *
+ * @param[in] value The name of the value to query.
+ *
+ * @param[in] type The type of the value to query.
+ *
+ * @param[out] data A pointer to store the data.
+ *
+ * @param[out] data_size A pointer to store the size of the data.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_get_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type *type,
+ uint8_t **data,
+ uint32_t *data_size);
+
+/**
+ * @internal
+ *
+ * @brief Enumerate on the values of a given key and provide the data.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @param[in] key The key of the printer data to get the value.
+ *
+ * @param[out] pnum_values A pointer to store the number of values we found.
+ *
+ * @param[out] penum_values A pointer to store the values and its data.
+ *
+ * @return WERR_OK on success, the corresponding DOS error
+ * code if something gone wrong.
+ */
+WERROR winreg_enum_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_values,
+ struct spoolss_PrinterEnumValues **penum_values);
+
+/**
+ * @internal
+ *
+ * @brief Delete printer data over a winreg pipe.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @param[in] key The key of the printer data to delete.
+ *
+ * @param[in] value The name of the value to delete.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_delete_printer_dataex(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ const char *key,
+ const char *value);
+
+/**
+ * @internal
+ *
+ * @brief Enumerate on the subkeys of a given key and provide the data.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @param[in] key The key of the printer data to get the value.
+ *
+ * @param[out] pnum_subkeys A pointer to store the number of subkeys found.
+ *
+ * @param[in] psubkeys A pointer to an array to store the names of the subkeys
+ * found.
+ *
+ * @return WERR_OK on success, the corresponding DOS error
+ * code if something gone wrong.
+ */
+WERROR winreg_enum_printer_key(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_subkeys,
+ const char ***psubkeys);
+
+/**
+ * @internal
+ *
+ * @brief Delete a key with subkeys of a given printer.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @param[in] key The key of the printer to delete.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_delete_printer_key(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ const char *key);
+
+/**
+ * @brief Update the ChangeID of a printer.
+ *
+ * The ChangeID **must** be increasing over the lifetime of client's spoolss
+ * service in order for the client's cache to show updates.
+ *
+ * If a form is updated of a printer, the we need to update the ChangeID of the
+ * pritner.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_printer_update_changeid(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer);
+
+/**
+ * @brief Get the ChangeID of the given printer.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] printer The printer name.
+ *
+ * @param[in] changeid A pointer to store the changeid.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_printer_get_changeid(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *printer,
+ uint32_t *pchangeid);
+
+/**
+ * @internal
+ *
+ * @brief This function adds a form to the list of available forms that can be
+ * selected for the specified printer.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] form The form to add.
+ *
+ * @return WERR_OK on success.
+ * WERR_ALREADY_EXISTS if the form already exists or is a
+ * builtin form.
+ * A corresponding DOS error is something went wrong.
+ */
+WERROR winreg_printer_addform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct spoolss_AddFormInfo1 *form);
+
+/*
+ * @brief This function enumerates the forms supported by the specified printer.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[out] pnum_info A pointer to store the FormInfo count.
+ *
+ * @param[out] pinfo A pointer to store an array with FormInfo.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_printer_enumforms1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ uint32_t *pnum_info,
+ union spoolss_FormInfo **pinfo);
+
+/**
+ * @brief This function removes a form name from the list of supported forms.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] form_name The name of the form to delete.
+ *
+ * @return WERR_OK on success.
+ * WERR_INVALID_PARAMETER if the form is a builtin form.
+ * WERR_INVALID_FORM_NAME if the form or key doesn't exist.
+ * A corresponding DOS error is something went wrong.
+ */
+WERROR winreg_printer_deleteform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *form_name);
+
+/**
+ * @brief This function sets the form information for the specified printer.
+ *
+ * If one provides both the name in the API call and inside the FormInfo
+ * structure, then the form gets renamed.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] form_name The name of the form to set or rename.
+ *
+ * @param[in] form The FormInfo structure to save.
+ *
+ * @return WERR_OK on success.
+ * WERR_INVALID_PARAMETER if the form is a builtin form.
+ * A corresponding DOS error is something went wrong.
+ */
+WERROR winreg_printer_setform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *form_name,
+ struct spoolss_AddFormInfo1 *form);
+
+/**
+ * @brief This function retrieves information about a specified form.
+ *
+ * @param[in] mem_ctx The talloc memory context to use.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] form_name The name of the form to query.
+ *
+ * @param[out] form A pointer to a form structure to fill out.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_printer_getform1(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *form_name,
+ struct spoolss_FormInfo1 *form);
+
+/**
+ * @brief This function adds a new spool driver
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] r The structure containing the new driver data.
+ *
+ * @param[out] driver_name Returns the driver name.
+ *
+ * @param[out] driver_version Returns the driver version.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+WERROR winreg_add_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct spoolss_AddDriverInfoCtr *r,
+ const char **driver_name,
+ uint32_t *driver_version);
+
+/**
+ * @brief This function gets printer driver information
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] architecture The architecture type.
+ *
+ * @param[in] driver_name The driver name.
+ *
+ * @param[in] driver_version The driver version.
+ *
+ * @param[out] _info8 The structure that holds the full driver information.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_get_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *architecture,
+ const char *driver_name,
+ uint32_t driver_version,
+ struct spoolss_DriverInfo8 **_info8);
+
+/**
+ * @brief This function deletes a printer driver information
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[out] info8 The structure that holds the full driver information.
+ *
+ * @param[in] version The driver type version.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_del_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct spoolss_DriverInfo8 *info8,
+ uint32_t version);
+
+/**
+ * @brief This function gets printer drivers list for the specified
+ * architecture and type version
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] architecture The architecture type.
+ *
+ * @param[in] version The driver version.
+ *
+ * @param[out] num_drivers The number of drivers.
+ *
+ * @param[out] version The drivers names.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_get_driver_list(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *architecture,
+ uint32_t version,
+ uint32_t *num_drivers,
+ const char ***drivers);
+/**
+ * @brief This function gets a core printer driver
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] architecture The architecture type.
+ *
+ * @param[in] core_driver_guid The core driver guid.
+ *
+ * @param[out] core_printer_driver The returned core printer driver definition
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_get_core_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *architecture,
+ const struct GUID *core_driver_guid,
+ struct spoolss_CorePrinterDriver **_core_printer_driver);
+
+/**
+ * @brief This function adds a core printer driver
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] architecture The architecture type.
+ *
+ * @param[in] core_driver_driver The core driver.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_add_core_driver(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *architecture,
+ const struct spoolss_CorePrinterDriver *r);
+
+/**
+ * @brief This function adds a driver package
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] package_id The package ID.
+ *
+ * @param[in] architecture The architecture type.
+ *
+ * @param[in] driver_store_path The local DriverStorePath
+ *
+ * @param[in] cab_path The local CabFile path
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_add_driver_package(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *package_id,
+ const char *architecture,
+ const char *driver_store_path,
+ const char *cab_path);
+
+/**
+ * @brief This function gets a driver package
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] package_id The package ID.
+ *
+ * @param[in] architecture The architecture type.
+ *
+ * @param[in] driver_store_path The pointer to a local DriverStorePath
+ *
+ * @param[in] cab_path The pointer to a local CabFile path
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_get_driver_package(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *package_id,
+ const char *architecture,
+ const char **driver_store_path,
+ const char **cab_path);
+
+/**
+ * @brief This function deletes a driver package
+ *
+ * @param[in] mem_ctx A talloc memory context.
+ *
+ * @param[in] b The dcerpc binding handle
+ *
+ * @param[in] package_id The package ID.
+ *
+ * @param[in] architecture The architecture type.
+ *
+ * @return On success WERR_OK, a corresponding DOS error is
+ * something went wrong.
+ */
+
+WERROR winreg_del_driver_package(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *winreg_handle,
+ const char *package_id,
+ const char *architecture);
+
+#endif /* _RPC_CLIENT_CLI_WINREG_SPOOLSS_H_ */
diff --git a/source3/rpc_client/init_lsa.c b/source3/rpc_client/init_lsa.c
new file mode 100644
index 0000000..2681197
--- /dev/null
+++ b/source3/rpc_client/init_lsa.c
@@ -0,0 +1,60 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_client/init_lsa.h"
+#include "../librpc/gen_ndr/lsa.h"
+
+/*******************************************************************
+ inits a structure.
+********************************************************************/
+
+void init_lsa_String(struct lsa_String *name, const char *s)
+{
+ name->string = s;
+ name->size = 2 * strlen_m(s);
+ name->length = name->size;
+}
+
+/*******************************************************************
+ inits a structure.
+********************************************************************/
+
+void init_lsa_StringLarge(struct lsa_StringLarge *name, const char *s)
+{
+ name->string = s;
+}
+
+/*******************************************************************
+ inits a structure.
+********************************************************************/
+
+void init_lsa_AsciiString(struct lsa_AsciiString *name, const char *s)
+{
+ name->string = s;
+}
+
+/*******************************************************************
+ inits a structure.
+********************************************************************/
+
+void init_lsa_AsciiStringLarge(struct lsa_AsciiStringLarge *name, const char *s)
+{
+ name->string = s;
+}
diff --git a/source3/rpc_client/init_lsa.h b/source3/rpc_client/init_lsa.h
new file mode 100644
index 0000000..e4e5c11
--- /dev/null
+++ b/source3/rpc_client/init_lsa.h
@@ -0,0 +1,35 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_CLIENT_INIT_LSA_H_
+#define _RPC_CLIENT_INIT_LSA_H_
+
+struct lsa_String;
+struct lsa_StringLarge;
+struct lsa_AsciiString;
+struct lsa_AsciiStringLarge;
+
+/* The following definitions come from rpc_client/init_lsa.c */
+
+void init_lsa_String(struct lsa_String *name, const char *s);
+void init_lsa_StringLarge(struct lsa_StringLarge *name, const char *s);
+void init_lsa_AsciiString(struct lsa_AsciiString *name, const char *s);
+void init_lsa_AsciiStringLarge(struct lsa_AsciiStringLarge *name, const char *s);
+
+#endif /* _RPC_CLIENT_INIT_LSA_H_ */
diff --git a/source3/rpc_client/init_samr.c b/source3/rpc_client/init_samr.c
new file mode 100644
index 0000000..52fa2f9
--- /dev/null
+++ b/source3/rpc_client/init_samr.c
@@ -0,0 +1,126 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "rpc_client/init_samr.h"
+#include "librpc/rpc/dcerpc_samr.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+/*************************************************************************
+ inits a samr_CryptPasswordEx structure
+ *************************************************************************/
+
+NTSTATUS init_samr_CryptPasswordEx(const char *pwd,
+ DATA_BLOB *session_key,
+ struct samr_CryptPasswordEx *pwd_buf)
+{
+ return encode_rc4_passwd_buffer(pwd, session_key, pwd_buf);
+}
+
+/*************************************************************************
+ inits a samr_CryptPassword structure
+ *************************************************************************/
+
+NTSTATUS init_samr_CryptPassword(const char *pwd,
+ DATA_BLOB *session_key,
+ struct samr_CryptPassword *pwd_buf)
+{
+ /* samr_CryptPassword */
+ gnutls_cipher_hd_t cipher_hnd = NULL;
+ gnutls_datum_t sess_key = {
+ .data = session_key->data,
+ .size = session_key->length,
+ };
+ bool ok;
+ int rc;
+
+ ok = encode_pw_buffer(pwd_buf->data, pwd, STR_UNICODE);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &sess_key,
+ NULL);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+ rc = gnutls_cipher_encrypt(cipher_hnd,
+ pwd_buf->data,
+ 516);
+ gnutls_cipher_deinit(cipher_hnd);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS init_samr_CryptPasswordAES(TALLOC_CTX *mem_ctx,
+ const char *password,
+ DATA_BLOB *salt,
+ DATA_BLOB *session_key,
+ struct samr_EncryptedPasswordAES *ppwd_buf)
+{
+ uint8_t pw_data[514] = {0};
+ DATA_BLOB plaintext = {
+ .data = pw_data,
+ .length = sizeof(pw_data),
+ };
+ DATA_BLOB ciphertext = data_blob_null;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ bool ok;
+
+ if (ppwd_buf == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ok = encode_pwd_buffer514_from_str(pw_data, password, STR_UNICODE);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ status = samba_gnutls_aead_aes_256_cbc_hmac_sha512_encrypt(
+ mem_ctx,
+ &plaintext,
+ session_key,
+ &samr_aes256_enc_key_salt,
+ &samr_aes256_mac_key_salt,
+ salt,
+ &ciphertext,
+ ppwd_buf->auth_data);
+ BURN_DATA(pw_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ppwd_buf->cipher_len = ciphertext.length;
+ ppwd_buf->cipher = ciphertext.data;
+ ppwd_buf->PBKDF2Iterations = 0;
+
+ SMB_ASSERT(salt->length == sizeof(ppwd_buf->salt));
+ memcpy(ppwd_buf->salt, salt->data, salt->length);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/init_samr.h b/source3/rpc_client/init_samr.h
new file mode 100644
index 0000000..71b4c0e
--- /dev/null
+++ b/source3/rpc_client/init_samr.h
@@ -0,0 +1,54 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_CLIENT_INIT_SAMR_H_
+#define _RPC_CLIENT_INIT_SAMR_H_
+
+/* The following definitions come from rpc_client/init_samr.c */
+
+NTSTATUS init_samr_CryptPasswordEx(const char *pwd,
+ DATA_BLOB *session_key,
+ struct samr_CryptPasswordEx *pwd_buf);
+NTSTATUS init_samr_CryptPassword(const char *pwd,
+ DATA_BLOB *session_key,
+ struct samr_CryptPassword *pwd_buf);
+
+/**
+ * @brief Initialize a AES encrypted password structure.
+ *
+ * This takes a password and a session key and encrypts the password. The
+ * encrypted password is then stored in the encrypted passwors structure.
+ *
+ * @param mem_ctx The memory context to allocate the password buffer on.
+ *
+ * @param password The password to encrypt.
+ *
+ * @param session_key The session key used to encrypt the password.
+ *
+ * @param ppwd_buf A pointer to the talloc allocated password structure.
+ *
+ * @return On success NT_STATUS_OK, an error status code otherwise.
+ */
+NTSTATUS init_samr_CryptPasswordAES(TALLOC_CTX *mem_ctx,
+ const char *password,
+ DATA_BLOB *salt,
+ DATA_BLOB *session_key,
+ struct samr_EncryptedPasswordAES *ppwd_buf);
+
+#endif /* _RPC_CLIENT_INIT_SAMR_H_ */
diff --git a/source3/rpc_client/init_spoolss.c b/source3/rpc_client/init_spoolss.c
new file mode 100644
index 0000000..c341b82
--- /dev/null
+++ b/source3/rpc_client/init_spoolss.c
@@ -0,0 +1,479 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Guenther Deschner 2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "../libcli/security/security.h"
+#include "secrets.h"
+#include "passdb/machine_sid.h"
+
+/*******************************************************************
+********************************************************************/
+
+bool init_systemtime(struct spoolss_Time *r,
+ struct tm *unixtime)
+{
+ if (!r || !unixtime) {
+ return false;
+ }
+
+ r->year = unixtime->tm_year+1900;
+ r->month = unixtime->tm_mon+1;
+ r->day_of_week = unixtime->tm_wday;
+ r->day = unixtime->tm_mday;
+ r->hour = unixtime->tm_hour;
+ r->minute = unixtime->tm_min;
+ r->second = unixtime->tm_sec;
+ r->millisecond = 0;
+
+ return true;
+}
+
+time_t spoolss_Time_to_time_t(const struct spoolss_Time *r)
+{
+ struct tm unixtime = {
+ .tm_year = r->year - 1900,
+ .tm_mon = r->month - 1,
+ .tm_wday = r->day_of_week,
+ .tm_mday = r->day,
+ .tm_hour = r->hour,
+ .tm_min = r->minute,
+ .tm_sec = r->second,
+ };
+
+ return mktime(&unixtime);
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+bool spoolss_timestr_to_NTTIME(const char *str,
+ NTTIME *data)
+{
+ struct tm tm;
+ time_t t;
+
+ if (strequal(str, "01/01/1601")) {
+ *data = 0;
+ return true;
+ }
+
+ ZERO_STRUCT(tm);
+
+ if (sscanf(str, "%d/%d/%d",
+ &tm.tm_mon, &tm.tm_mday, &tm.tm_year) != 3) {
+ return false;
+ }
+ tm.tm_mon -= 1;
+ tm.tm_year -= 1900;
+ tm.tm_isdst = -1;
+
+ t = mktime(&tm);
+ unix_to_nt_time(data, t);
+
+ return true;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+bool spoolss_driver_version_to_qword(const char *str,
+ uint64_t *data)
+{
+ unsigned int v1, v2, v3, v4 = 0;
+
+ if ((sscanf(str, "%u.%u.%u.%u", &v1, &v2, &v3, &v4) != 4) &&
+ (sscanf(str, "%u.%u.%u", &v1, &v2, &v3) != 3))
+ {
+ return false;
+ }
+
+ *data = ((uint64_t)(v1 & 0xFFFF) << 48) +
+ ((uint64_t)(v2 & 0xFFFF) << 32) +
+ ((uint64_t)(v3 & 0xFFFF) << 16) +
+ (uint64_t)(v4 & 0xFFFF);
+
+ return true;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+WERROR pull_spoolss_PrinterData(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *blob,
+ union spoolss_PrinterData *data,
+ enum winreg_Type type)
+{
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_union_blob(blob, mem_ctx, data, type,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_PrinterData);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_GEN_FAILURE;
+ }
+ return WERR_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+WERROR push_spoolss_PrinterData(TALLOC_CTX *mem_ctx, DATA_BLOB *blob,
+ enum winreg_Type type,
+ union spoolss_PrinterData *data)
+{
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_push_union_blob(blob, mem_ctx, data, type,
+ (ndr_push_flags_fn_t)ndr_push_spoolss_PrinterData);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_GEN_FAILURE;
+ }
+ return WERR_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+void spoolss_printerinfo2_to_setprinterinfo2(const struct spoolss_PrinterInfo2 *i,
+ struct spoolss_SetPrinterInfo2 *s)
+{
+ s->servername = i->servername;
+ s->printername = i->printername;
+ s->sharename = i->sharename;
+ s->portname = i->portname;
+ s->drivername = i->drivername;
+ s->comment = i->comment;
+ s->location = i->location;
+ s->devmode_ptr = 0;
+ s->sepfile = i->sepfile;
+ s->printprocessor = i->printprocessor;
+ s->datatype = i->datatype;
+ s->parameters = i->parameters;
+ s->secdesc_ptr = 0;
+ s->attributes = i->attributes;
+ s->priority = i->priority;
+ s->defaultpriority = i->defaultpriority;
+ s->starttime = i->starttime;
+ s->untiltime = i->untiltime;
+ s->status = i->status;
+ s->cjobs = i->cjobs;
+ s->averageppm = i->averageppm;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+bool driver_info_ctr_to_info8(struct spoolss_AddDriverInfoCtr *r,
+ struct spoolss_DriverInfo8 *_info8)
+{
+ struct spoolss_DriverInfo8 info8;
+
+ ZERO_STRUCT(info8);
+
+ switch (r->level) {
+ case 3:
+ info8.version = r->info.info3->version;
+ info8.driver_name = r->info.info3->driver_name;
+ info8.architecture = r->info.info3->architecture;
+ info8.driver_path = r->info.info3->driver_path;
+ info8.data_file = r->info.info3->data_file;
+ info8.config_file = r->info.info3->config_file;
+ info8.help_file = r->info.info3->help_file;
+ info8.monitor_name = r->info.info3->monitor_name;
+ info8.default_datatype = r->info.info3->default_datatype;
+ if (r->info.info3->dependent_files && r->info.info3->dependent_files->string) {
+ info8.dependent_files = r->info.info3->dependent_files->string;
+ }
+ break;
+ case 6:
+ info8.version = r->info.info6->version;
+ info8.driver_name = r->info.info6->driver_name;
+ info8.architecture = r->info.info6->architecture;
+ info8.driver_path = r->info.info6->driver_path;
+ info8.data_file = r->info.info6->data_file;
+ info8.config_file = r->info.info6->config_file;
+ info8.help_file = r->info.info6->help_file;
+ info8.monitor_name = r->info.info6->monitor_name;
+ info8.default_datatype = r->info.info6->default_datatype;
+ if (r->info.info6->dependent_files && r->info.info6->dependent_files->string) {
+ info8.dependent_files = r->info.info6->dependent_files->string;
+ }
+ info8.driver_date = r->info.info6->driver_date;
+ info8.driver_version = r->info.info6->driver_version;
+ info8.manufacturer_name = r->info.info6->manufacturer_name;
+ info8.manufacturer_url = r->info.info6->manufacturer_url;
+ info8.hardware_id = r->info.info6->hardware_id;
+ info8.provider = r->info.info6->provider;
+ break;
+ case 8:
+ info8.version = r->info.info8->version;
+ info8.driver_name = r->info.info8->driver_name;
+ info8.architecture = r->info.info8->architecture;
+ info8.driver_path = r->info.info8->driver_path;
+ info8.data_file = r->info.info8->data_file;
+ info8.config_file = r->info.info8->config_file;
+ info8.help_file = r->info.info8->help_file;
+ info8.monitor_name = r->info.info8->monitor_name;
+ info8.default_datatype = r->info.info8->default_datatype;
+ if (r->info.info8->dependent_files && r->info.info8->dependent_files->string) {
+ info8.dependent_files = r->info.info8->dependent_files->string;
+ }
+ if (r->info.info8->previous_names && r->info.info8->previous_names->string) {
+ info8.previous_names = r->info.info8->previous_names->string;
+ }
+ info8.driver_date = r->info.info8->driver_date;
+ info8.driver_version = r->info.info8->driver_version;
+ info8.manufacturer_name = r->info.info8->manufacturer_name;
+ info8.manufacturer_url = r->info.info8->manufacturer_url;
+ info8.hardware_id = r->info.info8->hardware_id;
+ info8.provider = r->info.info8->provider;
+ info8.print_processor = r->info.info8->print_processor;
+ info8.vendor_setup = r->info.info8->vendor_setup;
+ if (r->info.info8->color_profiles && r->info.info8->color_profiles->string) {
+ info8.color_profiles = r->info.info8->color_profiles->string;
+ }
+ info8.inf_path = r->info.info8->inf_path;
+ info8.printer_driver_attributes = r->info.info8->printer_driver_attributes;
+ if (r->info.info8->core_driver_dependencies && r->info.info8->core_driver_dependencies->string) {
+ info8.core_driver_dependencies = r->info.info8->core_driver_dependencies->string;
+ }
+ info8.min_inbox_driver_ver_date = r->info.info8->min_inbox_driver_ver_date;
+ info8.min_inbox_driver_ver_version = r->info.info8->min_inbox_driver_ver_version;
+ break;
+ default:
+ return false;
+ }
+
+ *_info8 = info8;
+
+ return true;
+}
+
+/****************************************************************************
+ Create and allocate a default devicemode.
+****************************************************************************/
+
+WERROR spoolss_create_default_devmode(TALLOC_CTX *mem_ctx,
+ const char *devicename,
+ struct spoolss_DeviceMode **devmode)
+{
+ struct spoolss_DeviceMode *dm;
+ char *dname;
+
+ dm = talloc_zero(mem_ctx, struct spoolss_DeviceMode);
+ if (dm == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ dname = talloc_asprintf(dm, "%s", devicename);
+ if (dname == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (strlen(dname) > MAXDEVICENAME) {
+ dname[MAXDEVICENAME] = '\0';
+ }
+ dm->devicename = dname;
+
+ dm->formname = talloc_strdup(dm, "Letter");
+ if (dm->formname == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ dm->specversion = DMSPEC_NT4_AND_ABOVE;
+ dm->driverversion = 0x0400;
+ dm->size = 0x00DC;
+ dm->__driverextra_length = 0;
+ dm->fields = DEVMODE_FORMNAME |
+ DEVMODE_TTOPTION |
+ DEVMODE_PRINTQUALITY |
+ DEVMODE_DEFAULTSOURCE |
+ DEVMODE_COPIES |
+ DEVMODE_SCALE |
+ DEVMODE_PAPERSIZE |
+ DEVMODE_ORIENTATION;
+ dm->orientation = DMORIENT_PORTRAIT;
+ dm->papersize = DMPAPER_LETTER;
+ dm->paperlength = 0;
+ dm->paperwidth = 0;
+ dm->scale = 0x64;
+ dm->copies = 1;
+ dm->defaultsource = DMBIN_FORMSOURCE;
+ dm->printquality = DMRES_HIGH; /* 0x0258 */
+ dm->color = DMRES_MONOCHROME;
+ dm->duplex = DMDUP_SIMPLEX;
+ dm->yresolution = 0;
+ dm->ttoption = DMTT_SUBDEV;
+ dm->collate = DMCOLLATE_FALSE;
+ dm->icmmethod = 0;
+ dm->icmintent = 0;
+ dm->mediatype = 0;
+ dm->dithertype = 0;
+
+ dm->logpixels = 0;
+ dm->bitsperpel = 0;
+ dm->pelswidth = 0;
+ dm->pelsheight = 0;
+ dm->displayflags = 0;
+ dm->displayfrequency = 0;
+ dm->reserved1 = 0;
+ dm->reserved2 = 0;
+ dm->panningwidth = 0;
+ dm->panningheight = 0;
+
+ dm->driverextra_data.data = NULL;
+ dm->driverextra_data.length = 0;
+
+ *devmode = dm;
+ return WERR_OK;
+}
+
+WERROR spoolss_create_default_secdesc(TALLOC_CTX *mem_ctx,
+ struct spoolss_security_descriptor **secdesc)
+{
+ struct security_ace ace[7]; /* max number of ace entries */
+ int i = 0;
+ uint32_t sa;
+ struct security_acl *psa = NULL;
+ struct security_descriptor *psd = NULL;
+ struct dom_sid adm_sid;
+ size_t sd_size;
+
+ /* Create an ACE where Everyone is allowed to print */
+
+ sa = PRINTER_ACE_PRINT;
+ init_sec_ace(&ace[i++], &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ sa, SEC_ACE_FLAG_CONTAINER_INHERIT);
+
+ /* Add the domain admins group if we are a DC */
+
+ if ( IS_DC ) {
+ struct dom_sid domadmins_sid;
+
+ sid_compose(&domadmins_sid, get_global_sam_sid(),
+ DOMAIN_RID_ADMINS);
+
+ sa = PRINTER_ACE_FULL_CONTROL;
+ init_sec_ace(&ace[i++], &domadmins_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, sa,
+ SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_INHERIT_ONLY);
+ init_sec_ace(&ace[i++], &domadmins_sid, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ sa, SEC_ACE_FLAG_CONTAINER_INHERIT);
+ }
+ else if (secrets_fetch_domain_sid(lp_workgroup(), &adm_sid)) {
+ sid_append_rid(&adm_sid, DOMAIN_RID_ADMINISTRATOR);
+
+ sa = PRINTER_ACE_FULL_CONTROL;
+ init_sec_ace(&ace[i++], &adm_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, sa,
+ SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_INHERIT_ONLY);
+ init_sec_ace(&ace[i++], &adm_sid, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ sa, SEC_ACE_FLAG_CONTAINER_INHERIT);
+ }
+
+ /* add BUILTIN\Administrators as FULL CONTROL */
+
+ sa = PRINTER_ACE_FULL_CONTROL;
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, sa,
+ SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_INHERIT_ONLY);
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ sa, SEC_ACE_FLAG_CONTAINER_INHERIT);
+
+ /* add BUILTIN\Print Operators as FULL CONTROL */
+
+ sa = PRINTER_ACE_FULL_CONTROL;
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Print_Operators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, sa,
+ SEC_ACE_FLAG_OBJECT_INHERIT | SEC_ACE_FLAG_INHERIT_ONLY);
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Print_Operators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ sa, SEC_ACE_FLAG_CONTAINER_INHERIT);
+
+ /* Make the security descriptor owned by the BUILTIN\Administrators */
+
+ /* The ACL revision number in rpc_secdesc.h differs from the one
+ created by NT when setting ACE entries in printer
+ descriptors. NT4 complains about the property being edited by a
+ NT5 machine. */
+
+ if ((psa = make_sec_acl(mem_ctx, NT4_ACL_REVISION, i, ace)) != NULL) {
+ psd = make_sec_desc(mem_ctx,
+ SD_REVISION,
+ SEC_DESC_SELF_RELATIVE,
+ &global_sid_Builtin_Administrators,
+ &global_sid_Builtin_Administrators,
+ NULL,
+ psa,
+ &sd_size);
+ }
+
+ if (psd == NULL) {
+ DEBUG(0,("construct_default_printer_sd: Failed to make SEC_DESC.\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ DEBUG(4,("construct_default_printer_sdb: size = %u.\n",
+ (unsigned int)sd_size));
+
+ *secdesc = psd;
+
+ return WERR_OK;
+}
+
+const char *spoolss_get_short_filesys_environment(const char *environment)
+{
+ if (strequal(environment, SPOOLSS_ARCHITECTURE_x64)) {
+ return "amd64";
+ } else if (strequal(environment, SPOOLSS_ARCHITECTURE_NT_X86)) {
+ return "x86";
+ } else {
+ return NULL;
+ }
+}
+
+/* Windows 7 and Windows Server 2008 R2 */
+#define GLOBAL_SPOOLSS_CLIENT_OS_MAJOR_DEFAULT 6
+#define GLOBAL_SPOOLSS_CLIENT_OS_MINOR_DEFAULT 1
+#define GLOBAL_SPOOLSS_CLIENT_OS_BUILD_DEFAULT 7007
+
+WERROR spoolss_init_spoolss_UserLevel1(TALLOC_CTX *mem_ctx,
+ const char *username,
+ struct spoolss_UserLevel1 *r)
+{
+ ZERO_STRUCTP(r);
+
+ r->size = 28;
+ r->client = talloc_asprintf(mem_ctx, "\\\\%s", lp_netbios_name());
+ W_ERROR_HAVE_NO_MEMORY(r->client);
+ r->user = talloc_strdup(mem_ctx, username);
+ W_ERROR_HAVE_NO_MEMORY(r->user);
+ r->processor = 0;
+
+ r->major = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss_client", "os_major",
+ GLOBAL_SPOOLSS_CLIENT_OS_MAJOR_DEFAULT);
+ r->minor = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss_client", "os_minor",
+ GLOBAL_SPOOLSS_CLIENT_OS_MINOR_DEFAULT);
+ r->build = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss_client", "os_build",
+ GLOBAL_SPOOLSS_CLIENT_OS_BUILD_DEFAULT);
+
+ return WERR_OK;
+}
diff --git a/source3/rpc_client/init_spoolss.h b/source3/rpc_client/init_spoolss.h
new file mode 100644
index 0000000..062e37b
--- /dev/null
+++ b/source3/rpc_client/init_spoolss.h
@@ -0,0 +1,55 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Guenther Deschner 2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_CLIENT_INIT_SPOOLSS_H_
+#define _RPC_CLIENT_INIT_SPOOLSS_H_
+
+/* The following definitions come from rpc_client/init_spoolss.c */
+
+bool init_systemtime(struct spoolss_Time *r,
+ struct tm *unixtime);
+time_t spoolss_Time_to_time_t(const struct spoolss_Time *r);
+bool spoolss_timestr_to_NTTIME(const char *str,
+ NTTIME *data);
+bool spoolss_driver_version_to_qword(const char *str,
+ uint64_t *data);
+WERROR pull_spoolss_PrinterData(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *blob,
+ union spoolss_PrinterData *data,
+ enum winreg_Type type);
+WERROR push_spoolss_PrinterData(TALLOC_CTX *mem_ctx, DATA_BLOB *blob,
+ enum winreg_Type type,
+ union spoolss_PrinterData *data);
+void spoolss_printerinfo2_to_setprinterinfo2(const struct spoolss_PrinterInfo2 *i,
+ struct spoolss_SetPrinterInfo2 *s);
+bool driver_info_ctr_to_info8(struct spoolss_AddDriverInfoCtr *r,
+ struct spoolss_DriverInfo8 *_info8);
+
+WERROR spoolss_create_default_devmode(TALLOC_CTX *mem_ctx,
+ const char *devicename,
+ struct spoolss_DeviceMode **devmode);
+
+WERROR spoolss_create_default_secdesc(TALLOC_CTX *mem_ctx,
+ struct spoolss_security_descriptor **secdesc);
+const char *spoolss_get_short_filesys_environment(const char *environment);
+WERROR spoolss_init_spoolss_UserLevel1(TALLOC_CTX *mem_ctx,
+ const char *username,
+ struct spoolss_UserLevel1 *r);
+
+#endif /* _RPC_CLIENT_INIT_SPOOLSS_H_ */
diff --git a/source3/rpc_client/local_np.c b/source3/rpc_client/local_np.c
new file mode 100644
index 0000000..10c6434
--- /dev/null
+++ b/source3/rpc_client/local_np.c
@@ -0,0 +1,852 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "source3/include/includes.h"
+#include <spawn.h>
+#include "local_np.h"
+#include "lib/async_req/async_sock.h"
+#include "librpc/gen_ndr/ndr_named_pipe_auth.h"
+#include "libcli/named_pipe_auth/npa_tstream.h"
+#include "libcli/named_pipe_auth/tstream_u32_read.h"
+#include "lib/util/tevent_unix.h"
+#include "auth/auth_util.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/security_token.h"
+#include "nsswitch/winbind_client.h"
+
+/**
+ * @file local_np.c
+ *
+ * Connect to a local named pipe by connecting to
+ * samba-dcerpcd. Start samba-dcerpcd if it isn't
+ * already running.
+ */
+
+extern bool override_logfile;
+
+struct np_sock_connect_state {
+ struct tevent_context *ev;
+ struct samba_sockaddr addr;
+ const struct named_pipe_auth_req *npa_req;
+ struct named_pipe_auth_rep *npa_rep;
+
+ DATA_BLOB npa_blob;
+ struct iovec iov;
+
+ int sock;
+ struct tevent_req *subreq;
+ struct tstream_context *transport;
+ struct tstream_context *npa_stream;
+};
+
+static void np_sock_connect_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static void np_sock_connect_before(void *private_data);
+static void np_sock_connect_after(void *private_data);
+static void np_sock_connect_connected(struct tevent_req *subreq);
+static void np_sock_connect_written(struct tevent_req *subreq);
+static void np_sock_connect_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *np_sock_connect_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *sockpath,
+ const struct named_pipe_auth_req *npa_req)
+{
+ struct tevent_req *req = NULL;
+ struct np_sock_connect_state *state = NULL;
+ size_t len;
+ int ret;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct np_sock_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->npa_req = npa_req;
+ state->sock = -1;
+ state->addr.u.un.sun_family = AF_UNIX;
+
+ state->npa_rep = talloc_zero(state, struct named_pipe_auth_rep);
+ if (tevent_req_nomem(state->npa_rep, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_cleanup_fn(req, np_sock_connect_cleanup);
+
+ state->addr.sa_socklen = sizeof(struct sockaddr_un);
+ len = strlcpy(state->addr.u.un.sun_path,
+ sockpath,
+ sizeof(state->addr.u.un.sun_path));
+ if (len >= sizeof(state->addr.u.un.sun_path)) {
+ tevent_req_error(req, ENAMETOOLONG);
+ return tevent_req_post(req, ev);
+ }
+
+ state->sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (state->sock == -1) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ ret = set_blocking(state->sock, true);
+ if (ret == -1) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ ok = set_close_on_exec(state->sock);
+ if (!ok) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ state->subreq = async_connect_send(
+ state,
+ ev,
+ state->sock,
+ &state->addr.u.sa,
+ state->addr.sa_socklen,
+ np_sock_connect_before,
+ np_sock_connect_after,
+ NULL);
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreq, np_sock_connect_connected, req);
+
+ return req;
+}
+
+static void np_sock_connect_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct np_sock_connect_state *state = tevent_req_data(
+ req, struct np_sock_connect_state);
+
+ TALLOC_FREE(state->subreq);
+ TALLOC_FREE(state->transport);
+
+ if (state->sock != -1) {
+ close(state->sock);
+ state->sock = -1;
+ }
+}
+
+static void np_sock_connect_before(void *private_data)
+{
+ become_root();
+}
+
+static void np_sock_connect_after(void *private_data)
+{
+ unbecome_root();
+}
+
+static void np_sock_connect_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct np_sock_connect_state *state = tevent_req_data(
+ req, struct np_sock_connect_state);
+ enum ndr_err_code ndr_err;
+ int ret, err;
+
+ SMB_ASSERT(subreq == state->subreq);
+
+ ret = async_connect_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ state->subreq = NULL;
+ if (ret == -1) {
+ DBG_DEBUG("async_connect_recv returned %s\n", strerror(err));
+ tevent_req_error(req, err);
+ return;
+ }
+
+ /*
+ * As a quick workaround for bug 15310 we have done the
+ * connect in blocking mode (see np_sock_connect_send()). The
+ * rest of our code expects a nonblocking socket, activate
+ * this after the connect succeeded.
+ */
+ ret = set_blocking(state->sock, false);
+ if (ret == -1) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ ret = tstream_bsd_existing_socket(
+ state, state->sock, &state->transport);
+ if (ret == -1) {
+ err = errno;
+ DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n",
+ strerror(err));
+ tevent_req_error(req, err);
+ return;
+ }
+ state->sock = -1;
+
+ ndr_err = ndr_push_struct_blob(
+ &state->npa_blob,
+ state,
+ state->npa_req,
+ (ndr_push_flags_fn_t)ndr_push_named_pipe_auth_req);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_push_struct_blob failed: %s\n",
+ ndr_errstr(ndr_err));
+ tevent_req_error(req, ndr_map_error2errno(ndr_err));
+ return;
+ }
+ state->iov = (struct iovec) {
+ .iov_base = state->npa_blob.data,
+ .iov_len = state->npa_blob.length,
+ };
+
+ subreq = tstream_writev_send(
+ state, state->ev, state->transport, &state->iov, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, np_sock_connect_written, req);
+}
+
+static void np_sock_connect_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct np_sock_connect_state *state = tevent_req_data(
+ req, struct np_sock_connect_state);
+ int ret, err;
+
+ ret = tstream_writev_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ DBG_DEBUG("tstream_writev_recv returned %s\n", strerror(err));
+ tevent_req_error(req, err);
+ return;
+ }
+
+ subreq = tstream_u32_read_send(
+ state, state->ev, 0x00FFFFFF, state->transport);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, np_sock_connect_read_done, req);
+}
+
+static void np_sock_connect_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct np_sock_connect_state *state = tevent_req_data(
+ req, struct np_sock_connect_state);
+ DATA_BLOB in;
+ int ret;
+ enum ndr_err_code ndr_err;
+
+ ret = tstream_u32_read_recv(subreq, state, &in.data, &in.length);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &in,
+ state->npa_rep,
+ state->npa_rep,
+ (ndr_pull_flags_fn_t)ndr_pull_named_pipe_auth_rep);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_named_pipe_auth_rep failed: %s\n",
+ ndr_errstr(ndr_err));
+ tevent_req_error(req, ndr_map_error2errno(ndr_err));
+ return;
+ }
+ if (state->npa_rep->level != 8) {
+ DBG_DEBUG("npa level = %" PRIu32 ", expected 8\n",
+ state->npa_rep->level);
+ tevent_req_error(req, EIO);
+ return;
+ }
+
+ ret = tstream_npa_existing_stream(state,
+ &state->transport,
+ state->npa_rep->info.info8.file_type,
+ &state->npa_stream);
+ if (ret == -1) {
+ ret = errno;
+ DBG_DEBUG("tstream_npa_existing_stream failed: %s\n",
+ strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static int np_sock_connect_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct tstream_context **stream)
+{
+ struct np_sock_connect_state *state = tevent_req_data(
+ req, struct np_sock_connect_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ tevent_req_received(req);
+ return err;
+ }
+ *stream = talloc_move(mem_ctx, &state->npa_stream);
+ tevent_req_received(req);
+ return 0;
+}
+
+struct start_rpc_host_state {
+ int ready_fd;
+ struct tevent_req *read_ready_req;
+};
+
+static void start_rpc_host_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static void start_rpc_host_ready(struct tevent_req *subreq);
+
+/*
+ * Start samba-dcerpcd and wait for it to report ready.
+ */
+static struct tevent_req *start_rpc_host_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct start_rpc_host_state *state = NULL;
+ int ret;
+ int ready_fds[2] = { -1, -1 };
+ char **argv = NULL;
+ pid_t pid;
+ bool ok;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct start_rpc_host_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ ret = pipe(ready_fds);
+ if (ret == -1) {
+ ret = errno;
+ DBG_DEBUG("pipe() failed: %s\n", strerror(ret));
+ goto fail;
+ }
+
+ ok = smb_set_close_on_exec(ready_fds[0]);
+ if (!ok) {
+ ret = errno;
+ DBG_DEBUG("smb_set_close_on_exec failed: %s\n",
+ strerror(ret));
+ goto fail;
+ }
+
+ argv = str_list_make_empty(mem_ctx);
+ str_list_add_printf(
+ &argv, "%s/samba-dcerpcd", get_dyn_SAMBA_LIBEXECDIR());
+ if (!is_default_dyn_CONFIGFILE()) {
+ str_list_add_printf(
+ &argv, "--configfile=%s", get_dyn_CONFIGFILE());
+ }
+ str_list_add_printf(&argv, "--libexec-rpcds");
+ str_list_add_printf(&argv, "--ready-signal-fd=%d", ready_fds[1]);
+ str_list_add_printf(&argv, "--np-helper");
+ str_list_add_printf(
+ &argv, "--debuglevel=%d", debuglevel_get_class(DBGC_RPC_SRV));
+ if (!is_default_dyn_LOGFILEBASE()) {
+ str_list_add_printf(
+ &argv, "--log-basename=%s", get_dyn_LOGFILEBASE());
+ }
+ if (argv == NULL) {
+ errno = ENOMEM;
+ goto fail;
+ }
+
+ become_root();
+ ret = posix_spawn(&pid, argv[0], NULL, NULL, argv, environ);
+ unbecome_root();
+ if (ret != 0) {
+ DBG_DEBUG("posix_spawn() failed: %s\n", strerror(ret));
+ goto fail;
+ }
+
+ state->ready_fd = ready_fds[0];
+ ready_fds[0] = -1;
+ tevent_req_set_cleanup_fn(req, start_rpc_host_cleanup);
+
+ close(ready_fds[1]);
+ ready_fds[1] = -1;
+
+ subreq = read_packet_send(state, ev, state->ready_fd, 1, NULL, NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, start_rpc_host_ready, req);
+ return req;
+
+fail:
+ if (ready_fds[0] != -1) {
+ close(ready_fds[0]);
+ ready_fds[0] = -1;
+ }
+ if (ready_fds[1] != -1) {
+ close(ready_fds[1]);
+ ready_fds[1] = -1;
+ }
+ tevent_req_error(req, ret);
+ return tevent_req_post(req, ev);
+}
+
+static void start_rpc_host_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct start_rpc_host_state *state = tevent_req_data(
+ req, struct start_rpc_host_state);
+
+ if (state->ready_fd != -1) {
+ close(state->ready_fd);
+ state->ready_fd = -1;
+ }
+}
+
+static void start_rpc_host_ready(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct start_rpc_host_state *state = tevent_req_data(
+ req, struct start_rpc_host_state);
+ uint8_t *buf;
+ int err;
+ ssize_t nread;
+
+ nread = read_packet_recv(subreq, state, &buf, &err);
+ TALLOC_FREE(subreq);
+ if (nread == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ close(state->ready_fd);
+ state->ready_fd = -1;
+
+ tevent_req_done(req);
+}
+
+static int start_rpc_host_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+struct local_np_connect_state {
+ struct tevent_context *ev;
+ const char *socketpath;
+ struct named_pipe_auth_req *npa_req;
+ struct tstream_context *npa_stream;
+};
+
+static void local_np_connect_connected(struct tevent_req *subreq);
+static void local_np_connect_started(struct tevent_req *subreq);
+static void local_np_connect_retried(struct tevent_req *subreq);
+
+/**
+ * @brief Async connect to a local named pipe RPC interface
+ *
+ * Start "samba-dcerpcd" on demand if it does not exist
+ *
+ * @param[in] mem_ctx The memory context to use.
+ * @param[in] ev The tevent context to use.
+ *
+ * @param[in] pipename The raw pipename to connect to without path
+ * @param[in] remote_client_name The client name to transmit
+ * @param[in] remote_client_addr The client addr to transmit
+ * @param[in] local_server_name The server name to transmit
+ * @param[in] local_server_addr The server addr to transmit
+ * @param[in] session_info The authorization info to use
+ * @param[in] need_idle_server Does this need to be an exclusive server?
+ * @return The tevent_req that was started
+ */
+
+struct tevent_req *local_np_connect_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *pipename,
+ enum dcerpc_transport_t transport,
+ const char *remote_client_name,
+ const struct tsocket_address *remote_client_addr,
+ const char *local_server_name,
+ const struct tsocket_address *local_server_addr,
+ const struct auth_session_info *session_info,
+ bool need_idle_server)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct local_np_connect_state *state = NULL;
+ struct named_pipe_auth_req_info8 *i8 = NULL;
+ const char *socket_dir = NULL;
+ char *lower_case_pipename = NULL;
+ struct dom_sid npa_sid = global_sid_Samba_NPA_Flags;
+ uint32_t npa_flags = 0;
+ struct security_token *token = NULL;
+ NTSTATUS status;
+ size_t num_npa_sids;
+ bool ok;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct local_np_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ num_npa_sids =
+ security_token_count_flag_sids(session_info->security_token,
+ &npa_sid,
+ 1,
+ NULL);
+ if (num_npa_sids != 0) {
+ DBG_ERR("ERROR: %zu NPA Flags SIDs have already been "
+ "detected in the security token!\n",
+ num_npa_sids);
+ tevent_req_error(req, EACCES);
+ return tevent_req_post(req, ev);
+ }
+
+ socket_dir = lp_parm_const_string(
+ GLOBAL_SECTION_SNUM, "external_rpc_pipe", "socket_dir",
+ lp_ncalrpc_dir());
+ if (socket_dir == NULL) {
+ DBG_DEBUG("external_rpc_pipe:socket_dir not set\n");
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ lower_case_pipename = strlower_talloc(state, pipename);
+ if (tevent_req_nomem(lower_case_pipename, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Ensure we cannot process a path that exits
+ * the socket_dir.
+ */
+ if (ISDOTDOT(lower_case_pipename) ||
+ (strchr(lower_case_pipename, '/')!=NULL))
+ {
+ DBG_DEBUG("attempt to connect to invalid pipe pathname %s\n",
+ lower_case_pipename);
+ tevent_req_error(req, ENOENT);
+ return tevent_req_post(req, ev);
+ }
+
+ state->socketpath = talloc_asprintf(
+ state, "%s/np/%s", socket_dir, lower_case_pipename);
+ if (tevent_req_nomem(state->socketpath, req)) {
+ return tevent_req_post(req, ev);
+ }
+ TALLOC_FREE(lower_case_pipename);
+
+ state->npa_req = talloc_zero(state, struct named_pipe_auth_req);
+ if (tevent_req_nomem(state->npa_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->npa_req->level = 8;
+
+ i8 = &state->npa_req->info.info8;
+
+ i8->transport = transport;
+
+ /* we don't have "int" in IDL, make sure we don't overflow */
+ SMB_ASSERT(i8->transport == transport);
+
+ if (remote_client_name == NULL) {
+ remote_client_name = get_myname(state->npa_req);
+ if (remote_client_name == NULL) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ }
+ i8->remote_client_name = remote_client_name;
+
+ if (remote_client_addr == NULL) {
+ struct tsocket_address *addr = NULL;
+ int ret = tsocket_address_inet_from_strings(
+ state->npa_req, "ip", NULL, 0, &addr);
+ if (ret != 0) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ remote_client_addr = addr;
+ }
+ i8->remote_client_addr =
+ tsocket_address_inet_addr_string(remote_client_addr,
+ state->npa_req);
+ if (i8->remote_client_addr == NULL) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ i8->remote_client_port = tsocket_address_inet_port(remote_client_addr);
+
+ if (local_server_name == NULL) {
+ local_server_name = remote_client_name;
+ }
+ i8->local_server_name = local_server_name;
+
+ if (local_server_addr == NULL) {
+ struct tsocket_address *addr = NULL;
+ int ret = tsocket_address_inet_from_strings(
+ state->npa_req, "ip", NULL, 0, &addr);
+ if (ret != 0) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ local_server_addr = addr;
+ }
+ i8->local_server_addr =
+ tsocket_address_inet_addr_string(local_server_addr,
+ state->npa_req);
+ if (i8->local_server_addr == NULL) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+ i8->local_server_port = tsocket_address_inet_port(local_server_addr);
+
+ i8->session_info = talloc_zero(state->npa_req,
+ struct auth_session_info_transport);
+ if (tevent_req_nomem(i8->session_info, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ i8->session_info->session_info =
+ copy_session_info(i8->session_info, session_info);
+ if (tevent_req_nomem(i8->session_info->session_info, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (need_idle_server) {
+ npa_flags |= SAMBA_NPA_FLAGS_NEED_IDLE;
+ }
+
+ ok = winbind_env_set();
+ if (ok) {
+ npa_flags |= SAMBA_NPA_FLAGS_WINBIND_OFF;
+ }
+
+ ok = sid_append_rid(&npa_sid, npa_flags);
+ if (!ok) {
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ token = i8->session_info->session_info->security_token;
+
+ status = add_sid_to_array_unique(token,
+ &npa_sid,
+ &token->sids,
+ &token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = np_sock_connect_send(
+ state, state->ev, state->socketpath, state->npa_req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, local_np_connect_connected, req);
+
+ return req;
+}
+
+static void local_np_connect_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct local_np_connect_state *state = tevent_req_data(
+ req, struct local_np_connect_state);
+ int ret;
+
+ ret = np_sock_connect_recv(subreq, state, &state->npa_stream);
+ TALLOC_FREE(subreq);
+
+ if (ret == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ DBG_DEBUG("np_sock_connect failed: %s\n", strerror(ret));
+
+ if (!lp_rpc_start_on_demand_helpers()) {
+ /*
+ * samba-dcerpcd should already be started in
+ * daemon/standalone mode when "rpc start on demand
+ * helpers = false". We are prohibited from starting
+ * on demand as a named-pipe helper.
+ */
+ DBG_ERR("Can't connect to a running samba-dcerpcd. smb.conf "
+ "config prohibits starting as named pipe helper as "
+ "the [global] section contains "
+ "\"rpc start on demand helpers = false\".\n");
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ /*
+ * samba-dcerpcd isn't running. We need to start it.
+ * Note if it doesn't start we treat this as a fatal
+ * error for connecting to the named pipe and don't
+ * keep trying to restart for this connection.
+ */
+ subreq = start_rpc_host_send(state, state->ev);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, local_np_connect_started, req);
+}
+
+static void local_np_connect_started(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct local_np_connect_state *state = tevent_req_data(
+ req, struct local_np_connect_state);
+ int ret;
+
+ ret = start_rpc_host_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ DBG_DEBUG("start_rpc_host_recv failed: %s\n",
+ strerror(ret));
+ return;
+ }
+
+ subreq = np_sock_connect_send(
+ state, state->ev, state->socketpath, state->npa_req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, local_np_connect_retried, req);
+}
+
+static void local_np_connect_retried(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct local_np_connect_state *state = tevent_req_data(
+ req, struct local_np_connect_state);
+ int ret;
+
+ ret = np_sock_connect_recv(subreq, state, &state->npa_stream);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+/**
+ * @brief Receive handle to a local named pipe RPC interface
+ *
+ * @param[in] req The tevent_req that started the operation
+ * @param[in] ev The tevent context to use.
+ * @param[in] mem_ctx The memory context to put pstream on
+ * @param[out] pstream The established connection to the RPC server
+ *
+ * @return 0/errno
+ */
+
+int local_np_connect_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct tstream_context **pstream)
+{
+ struct local_np_connect_state *state = tevent_req_data(
+ req, struct local_np_connect_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ tevent_req_received(req);
+ return err;
+ }
+
+ *pstream = talloc_move(mem_ctx, &state->npa_stream);
+ return 0;
+}
+
+/**
+ * @brief Sync connect to a local named pipe RPC interface
+ *
+ * Start "samba-dcerpcd" on demand if it does not exist
+ *
+ * @param[in] pipename The raw pipename to connect to without path
+ * @param[in] remote_client_name The client name to transmit
+ * @param[in] remote_client_addr The client addr to transmit
+ * @param[in] local_server_name The server name to transmit
+ * @param[in] local_server_addr The server addr to transmit
+ * @param[in] session_info The authorization info to use
+ * @param[in] need_idle_server Does this need to be an exclusive server?
+ * @param[in] mem_ctx The memory context to use.
+ * @param[out] pstream The established connection to the RPC server
+ * @return 0/errno
+ */
+
+int local_np_connect(
+ const char *pipename,
+ enum dcerpc_transport_t transport,
+ const char *remote_client_name,
+ const struct tsocket_address *remote_client_addr,
+ const char *local_server_name,
+ const struct tsocket_address *local_server_addr,
+ const struct auth_session_info *session_info,
+ bool need_idle_server,
+ TALLOC_CTX *mem_ctx,
+ struct tstream_context **pstream)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ int ret = ENOMEM;
+
+ ev = samba_tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = local_np_connect_send(
+ ev,
+ ev,
+ pipename,
+ transport,
+ remote_client_name,
+ remote_client_addr,
+ local_server_name,
+ local_server_addr,
+ session_info,
+ need_idle_server);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_unix(req, ev, &ret)) {
+ goto fail;
+ }
+ ret = local_np_connect_recv(req, mem_ctx, pstream);
+ fail:
+ TALLOC_FREE(req);
+ TALLOC_FREE(ev);
+ return ret;
+}
diff --git a/source3/rpc_client/local_np.h b/source3/rpc_client/local_np.h
new file mode 100644
index 0000000..093faad
--- /dev/null
+++ b/source3/rpc_client/local_np.h
@@ -0,0 +1,56 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _LOCAL_NP_H_
+#define _LOCAL_NP_H_
+
+#include <replace.h>
+#include "lib/tsocket/tsocket.h"
+#include "librpc/rpc/rpc_common.h"
+
+struct auth_session_info;
+
+struct tevent_req *local_np_connect_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *pipename,
+ enum dcerpc_transport_t transport,
+ const char *remote_client_name,
+ const struct tsocket_address *remote_client_addr,
+ const char *local_server_name,
+ const struct tsocket_address *local_server_addr,
+ const struct auth_session_info *session_info,
+ bool need_idle_server);
+
+int local_np_connect_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct tstream_context **pstream);
+
+int local_np_connect(
+ const char *pipename,
+ enum dcerpc_transport_t transport,
+ const char *remote_client_name,
+ const struct tsocket_address *remote_client_addr,
+ const char *local_server_name,
+ const struct tsocket_address *local_server_addr,
+ const struct auth_session_info *session_info,
+ bool need_idle_server,
+ TALLOC_CTX *mem_ctx,
+ struct tstream_context **stream);
+
+#endif /* _LOCAL_NP_H_ */
diff --git a/source3/rpc_client/py_mdscli.c b/source3/rpc_client/py_mdscli.c
new file mode 100644
index 0000000..ccb0342
--- /dev/null
+++ b/source3/rpc_client/py_mdscli.c
@@ -0,0 +1,572 @@
+/*
+ Python interface to cli_mdssvc
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lib/replace/system/python.h"
+#include <pytalloc.h>
+#include "includes.h"
+#include "python/py3compat.h"
+#include "python/modules.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "librpc/rpc/rpc_common.h"
+#include "librpc/rpc/pyrpc_util.h"
+#include "rpc_client/cli_mdssvc.h"
+#include "rpc_client/cli_mdssvc_private.h"
+
+static PyObject *search_get_results(PyObject *self,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char * const kwnames[] = {"pipe", NULL};
+ PyObject *pypipe = NULL;
+ PyObject *result = NULL;
+ dcerpc_InterfaceObject *pipe = NULL;
+ struct tevent_req *req = NULL;
+ struct mdscli_search_ctx *search = NULL;
+ uint64_t *cnids = NULL;
+ size_t i;
+ size_t ncnids;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "O",
+ discard_const_p(char *, kwnames),
+ &pypipe)) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to parse args");
+ goto out;
+ }
+
+ ok = py_check_dcerpc_type(pypipe,
+ "samba.dcerpc.base",
+ "ClientConnection");
+ if (!ok) {
+ goto out;
+ }
+
+ pipe = (dcerpc_InterfaceObject *)pypipe;
+
+ search = pytalloc_get_type(self, struct mdscli_search_ctx);
+ if (search == NULL) {
+ goto out;
+ }
+
+ /*
+ * We must use the async send/recv versions in order to pass the correct
+ * tevent context, here and any other place we call mdscli_*
+ * functions. Using the sync version we would be polling a temporary
+ * event context, but unfortunately the s4 Python RPC bindings dispatch
+ * events through
+ *
+ * dcerpc_bh_raw_call_send()
+ * -> dcerpc_request_send()
+ * -> dcerpc_schedule_io_trigger()
+ * -> dcerpc_send_request()
+ * -> tstream_writev_queue_send()
+ *
+ * on an hardcoded event context allocated via
+ *
+ * py_dcerpc_interface_init_helper()
+ * -> dcerpc_pipe_connect()
+ */
+again:
+ req = mdscli_get_results_send(frame,
+ pipe->ev,
+ search);
+ if (req == NULL) {
+ PyErr_NoMemory();
+ goto out;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, pipe->ev, &status)) {
+ PyErr_SetNTSTATUS(status);
+ goto out;
+ }
+
+ status = mdscli_get_results_recv(req, frame, &cnids);
+ TALLOC_FREE(req);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PENDING)) {
+ sleep(1);
+ goto again;
+ }
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_MATCHES))
+ {
+ PyErr_SetNTSTATUS(status);
+ goto out;
+ }
+
+ result = Py_BuildValue("[]");
+
+ ncnids = talloc_array_length(cnids);
+ for (i = 0; i < ncnids; i++) {
+ char *path = NULL;
+ PyObject *pypath = NULL;
+
+ req = mdscli_get_path_send(frame,
+ pipe->ev,
+ search->mdscli_ctx,
+ cnids[i]);
+ if (req == NULL) {
+ PyErr_NoMemory();
+ Py_DECREF(result);
+ result = NULL;
+ goto out;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, pipe->ev, &status)) {
+ PyErr_SetNTSTATUS(status);
+ Py_DECREF(result);
+ result = NULL;
+ goto out;
+ }
+
+ status = mdscli_get_path_recv(req, frame, &path);
+ TALLOC_FREE(req);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+
+ pypath = PyUnicode_FromString(path);
+ if (pypath == NULL) {
+ PyErr_NoMemory();
+ Py_DECREF(result);
+ result = NULL;
+ goto out;
+ }
+
+ ret = PyList_Append(result, pypath);
+ Py_DECREF(pypath);
+ if (ret == -1) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "list append failed");
+ Py_DECREF(result);
+ result = NULL;
+ goto out;
+ }
+ }
+
+out:
+ talloc_free(frame);
+ return result;
+}
+
+static PyObject *search_close(PyObject *self,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char * const kwnames[] = {"pipe", NULL};
+ PyObject *pypipe = NULL;
+ dcerpc_InterfaceObject *pipe = NULL;
+ struct tevent_req *req = NULL;
+ struct mdscli_search_ctx *search = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "O",
+ discard_const_p(char *, kwnames),
+ &pypipe)) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to parse args");
+ goto fail;
+ }
+
+ ok = py_check_dcerpc_type(pypipe,
+ "samba.dcerpc.base",
+ "ClientConnection");
+ if (!ok) {
+ goto fail;
+ }
+
+ pipe = (dcerpc_InterfaceObject *)pypipe;
+
+ search = pytalloc_get_type(self, struct mdscli_search_ctx);
+ if (search == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_close_search_send(frame,
+ pipe->ev,
+ &search);
+ if (req == NULL) {
+ PyErr_NoMemory();
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, pipe->ev, &status)) {
+ PyErr_SetNTSTATUS(status);
+ goto fail;
+ }
+
+ status = mdscli_close_search_recv(req);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_MATCHES))
+ {
+ PyErr_SetNTSTATUS(status);
+ goto fail;
+ }
+ TALLOC_FREE(req);
+
+ talloc_free(frame);
+ Py_INCREF(Py_None);
+ return Py_None;
+
+fail:
+ talloc_free(frame);
+ return NULL;
+}
+
+static PyMethodDef search_methods[] = {
+ {
+ .ml_name = "get_results",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, search_get_results),
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = "",
+ },
+ {
+ .ml_name = "close",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, search_close),
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = "",
+ },
+ {0},
+};
+
+static PyObject *search_new(PyTypeObject *type,
+ PyObject *args,
+ PyObject *kwds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct mdscli_search_ctx *search = NULL;
+ PyObject *self = NULL;
+
+ search = talloc_zero(frame, struct mdscli_search_ctx);
+ if (search == NULL) {
+ PyErr_NoMemory();
+ talloc_free(frame);
+ return NULL;
+ }
+
+ self = pytalloc_steal(type, search);
+ talloc_free(frame);
+ return self;
+}
+
+static PyTypeObject search_type = {
+ .tp_name = "mdscli.ctx.search",
+ .tp_new = search_new,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = "search([....]) -> mdssvc client search context\n",
+ .tp_methods = search_methods,
+};
+
+static PyObject *conn_sharepath(PyObject *self,
+ PyObject *unused)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct mdscli_ctx *ctx = NULL;
+ char *sharepath = NULL;
+ PyObject *result = NULL;
+
+ ctx = pytalloc_get_type(self, struct mdscli_ctx);
+ if (ctx == NULL) {
+ goto fail;
+ }
+
+ sharepath = mdscli_get_basepath(frame, ctx);
+ if (sharepath == NULL) {
+ PyErr_NoMemory();
+ goto fail;
+ }
+
+ result = PyUnicode_FromString(sharepath);
+
+fail:
+ talloc_free(frame);
+ return result;
+}
+
+static PyObject *conn_search(PyObject *self,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ PyObject *pypipe = NULL;
+ dcerpc_InterfaceObject *pipe = NULL;
+ struct mdscli_ctx *ctx = NULL;
+ PyObject *result = NULL;
+ char *query = NULL;
+ char *basepath = NULL;
+ struct tevent_req *req = NULL;
+ struct mdscli_search_ctx *search = NULL;
+ const char * const kwnames[] = {
+ "pipe", "query", "basepath", NULL
+ };
+ NTSTATUS status;
+ bool ok;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "Oss",
+ discard_const_p(char *, kwnames),
+ &pypipe,
+ &query,
+ &basepath)) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to parse args");
+ goto fail;
+ }
+
+ ok = py_check_dcerpc_type(pypipe,
+ "samba.dcerpc.base",
+ "ClientConnection");
+ if (!ok) {
+ goto fail;
+ }
+
+ pipe = (dcerpc_InterfaceObject *)pypipe;
+
+ ctx = pytalloc_get_type(self, struct mdscli_ctx);
+ if (ctx == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_search_send(frame,
+ pipe->ev,
+ ctx,
+ query,
+ basepath,
+ false);
+ if (req == NULL) {
+ PyErr_NoMemory();
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, pipe->ev, &status)) {
+ PyErr_SetNTSTATUS(status);
+ goto fail;
+ }
+
+ status = mdscli_search_recv(req, frame, &search);
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+ result = pytalloc_steal(&search_type, search);
+
+fail:
+ talloc_free(frame);
+ return result;
+}
+
+static PyObject *conn_disconnect(PyObject *self,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ PyObject *pypipe = NULL;
+ dcerpc_InterfaceObject *pipe = NULL;
+ struct mdscli_ctx *ctx = NULL;
+ struct tevent_req *req = NULL;
+ const char * const kwnames[] = {"pipe", NULL};
+ NTSTATUS status;
+ bool ok;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "O",
+ discard_const_p(char *, kwnames),
+ &pypipe)) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to parse args");
+ goto fail;
+ }
+
+ ok = py_check_dcerpc_type(pypipe,
+ "samba.dcerpc.base",
+ "ClientConnection");
+ if (!ok) {
+ goto fail;
+ }
+
+ pipe = (dcerpc_InterfaceObject *)pypipe;
+
+ ctx = pytalloc_get_type(self, struct mdscli_ctx);
+ if (ctx == NULL) {
+ goto fail;
+ }
+
+ req = mdscli_disconnect_send(frame, pipe->ev, ctx);
+ if (req == NULL) {
+ PyErr_NoMemory();
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, pipe->ev, &status)) {
+ PyErr_SetNTSTATUS(status);
+ goto fail;
+ }
+
+ status = mdscli_disconnect_recv(req);
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+ talloc_free(frame);
+ Py_INCREF(Py_None);
+ return Py_None;
+
+fail:
+ talloc_free(frame);
+ return NULL;
+}
+
+static PyMethodDef conn_methods[] = {
+ {
+ .ml_name = "sharepath",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, conn_sharepath),
+ .ml_flags = METH_NOARGS,
+ .ml_doc = "mdscli.conn.sharepath(...) -> get share basepath",
+ },
+ {
+ .ml_name = "search",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, conn_search),
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = "mdscli.conn.search(...) -> run mdssvc query",
+ },
+ {
+ .ml_name = "disconnect",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, conn_disconnect),
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = "mdscli.conn.disconnect(...) -> disconnect",
+ },
+ {0},
+};
+
+static PyObject *conn_new(PyTypeObject *type,
+ PyObject *args,
+ PyObject *kwargs)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char * const kwnames[] = { "pipe", "share", "mountpoint", NULL };
+ PyObject *pypipe = NULL;
+ dcerpc_InterfaceObject *pipe = NULL;
+ struct tevent_req *req = NULL;
+ char *share = NULL;
+ char *mountpoint = NULL;
+ struct mdscli_ctx *ctx = NULL;
+ PyObject *self = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "Oss",
+ discard_const_p(char *, kwnames),
+ &pypipe,
+ &share,
+ &mountpoint)) {
+ PyErr_SetString(PyExc_RuntimeError, "Failed to parse args");
+ goto fail;
+ }
+
+ ok = py_check_dcerpc_type(pypipe,
+ "samba.dcerpc.base",
+ "ClientConnection");
+ if (!ok) {
+ goto fail;
+ }
+
+ pipe = (dcerpc_InterfaceObject *)pypipe;
+
+ req = mdscli_connect_send(frame,
+ pipe->ev,
+ pipe->binding_handle,
+ share,
+ mountpoint);
+ if (req == NULL) {
+ PyErr_NoMemory();
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, pipe->ev, &status)) {
+ PyErr_SetNTSTATUS(status);
+ goto fail;
+ }
+
+ status = mdscli_connect_recv(req, frame, &ctx);
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+ self = pytalloc_steal(type, ctx);
+
+fail:
+ talloc_free(frame);
+ return self;
+}
+
+static PyTypeObject conn_type = {
+ .tp_name = "mdscli.conn",
+ .tp_new = conn_new,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_doc = "conn([....]) -> mdssvc connection\n",
+ .tp_methods = conn_methods,
+};
+
+static PyMethodDef mdscli_methods[] = {
+ {0},
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "mdscli",
+ .m_doc = "RPC mdssvc client",
+ .m_size = -1,
+ .m_methods = mdscli_methods,
+};
+
+MODULE_INIT_FUNC(mdscli)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ PyObject *m = NULL;
+ int ret;
+
+ ret = pytalloc_BaseObject_PyType_Ready(&conn_type);
+ if (ret < 0) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ ret = pytalloc_BaseObject_PyType_Ready(&search_type);
+ if (ret < 0) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ m = PyModule_Create(&moduledef);
+ if (m == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ Py_INCREF(&conn_type);
+ PyModule_AddObject(m, "conn", (PyObject *)&conn_type);
+
+ Py_INCREF(&search_type);
+ PyModule_AddObject(m, "search", (PyObject *)&search_type);
+
+ TALLOC_FREE(frame);
+ return m;
+}
diff --git a/source3/rpc_client/rpc_client.h b/source3/rpc_client/rpc_client.h
new file mode 100644
index 0000000..f1be075
--- /dev/null
+++ b/source3/rpc_client/rpc_client.h
@@ -0,0 +1,52 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * RPC Pipe client routines
+ *
+ * Copyright (c) 2005 Jeremy Allison
+ * Copyright (c) 2010 Simo Sorce
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _RPC_CLIENT_H
+#define _RPC_CLIENT_H
+
+#include "librpc/gen_ndr/dcerpc.h"
+#include "librpc/rpc/dcerpc.h"
+#include "../librpc/ndr/libndr.h"
+#include "rpc_client/rpc_transport.h"
+
+struct dcerpc_binding_handle;
+
+struct rpc_pipe_client {
+ struct rpc_pipe_client *prev, *next;
+
+ struct rpc_cli_transport *transport;
+ struct dcerpc_binding_handle *binding_handle;
+
+ struct ndr_syntax_id abstract_syntax;
+ struct ndr_syntax_id transfer_syntax;
+ bool verified_pcontext;
+
+ char *desthost;
+ char *srv_name_slash;
+
+ uint16_t max_xmit_frag;
+
+ struct pipe_auth_data *auth;
+};
+
+#endif /* _RPC_CLIENT_H */
diff --git a/source3/rpc_client/rpc_transport.h b/source3/rpc_client/rpc_transport.h
new file mode 100644
index 0000000..f352f60
--- /dev/null
+++ b/source3/rpc_client/rpc_transport.h
@@ -0,0 +1,104 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC client transport
+ * Copyright (C) Volker Lendecke 2009
+ * Copyright (C) Simo Sorce 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_CLIENT_RPC_TRANSPORT_H_
+#define _RPC_CLIENT_RPC_TRANSPORT_H_
+
+#include "librpc/rpc/dcerpc.h"
+
+/**
+ * rpc_cli_transport defines a transport mechanism to ship rpc requests
+ * asynchronously to a server and receive replies
+ */
+
+struct rpc_cli_transport {
+
+ enum dcerpc_transport_t transport;
+
+ /**
+ * Trigger an async read from the server. May return a short read.
+ */
+ struct tevent_req *(*read_send)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint8_t *data, size_t size,
+ void *priv);
+ /**
+ * Get the result from the read_send operation.
+ */
+ NTSTATUS (*read_recv)(struct tevent_req *req, ssize_t *preceived);
+
+ /**
+ * Trigger an async write to the server. May return a short write.
+ */
+ struct tevent_req *(*write_send)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const uint8_t *data, size_t size,
+ void *priv);
+ /**
+ * Get the result from the read_send operation.
+ */
+ NTSTATUS (*write_recv)(struct tevent_req *req, ssize_t *psent);
+
+ /**
+ * This is an optimization for the SMB transport. It models the
+ * TransactNamedPipe API call: Send and receive data in one round
+ * trip. The transport implementation is free to set this to NULL,
+ * cli_pipe.c will fall back to the explicit write/read routines.
+ */
+ struct tevent_req *(*trans_send)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const uint8_t *data, size_t data_len,
+ uint32_t max_rdata_len,
+ void *priv);
+ /**
+ * Get the result from the trans_send operation.
+ */
+ NTSTATUS (*trans_recv)(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **prdata, uint32_t *prdata_len);
+
+ bool (*is_connected)(void *priv);
+ unsigned int (*set_timeout)(void *priv, unsigned int timeout);
+
+ void *priv;
+};
+
+/* The following definitions come from rpc_client/rpc_transport_np.c */
+struct cli_state;
+struct tevent_req *rpc_transport_np_init_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const struct ndr_interface_table *table);
+NTSTATUS rpc_transport_np_init_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_cli_transport **presult);
+
+/* The following definitions come from rpc_client/rpc_transport_sock.c */
+
+NTSTATUS rpc_transport_sock_init(TALLOC_CTX *mem_ctx, int fd,
+ struct rpc_cli_transport **presult);
+
+/* The following definitions come from rpc_client/rpc_transport_tstream.c */
+
+NTSTATUS rpc_transport_tstream_init(TALLOC_CTX *mem_ctx,
+ struct tstream_context **stream,
+ struct rpc_cli_transport **presult);
+struct tstream_context *rpc_transport_get_tstream(
+ struct rpc_cli_transport *transport);
+#endif /* _RPC_CLIENT_RPC_TRANSPORT_H_ */
diff --git a/source3/rpc_client/rpc_transport_np.c b/source3/rpc_client/rpc_transport_np.c
new file mode 100644
index 0000000..21266c3
--- /dev/null
+++ b/source3/rpc_client/rpc_transport_np.c
@@ -0,0 +1,179 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC client transport over named pipes
+ * Copyright (C) Volker Lendecke 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "librpc/rpc/dcerpc_util.h"
+#include "rpc_client/rpc_transport.h"
+#include "librpc/ndr/ndr_table.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "libcli/smb/tstream_smbXcli_np.h"
+#include "client.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_CLI
+
+struct rpc_transport_np_init_state {
+ struct rpc_cli_transport *transport;
+ int retries;
+ struct tevent_context *ev;
+ struct smbXcli_conn *conn;
+ int timeout;
+ struct timeval abs_timeout;
+ const char *pipe_name;
+ struct smbXcli_session *session;
+ struct smbXcli_tcon *tcon;
+ uint16_t pid;
+};
+
+static void rpc_transport_np_init_pipe_open(struct tevent_req *subreq);
+
+struct tevent_req *rpc_transport_np_init_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const struct ndr_interface_table *table)
+{
+ struct tevent_req *req;
+ struct rpc_transport_np_init_state *state;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct rpc_transport_np_init_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ state->tcon = cli->smb2.tcon;
+ state->session = cli->smb2.session;
+ } else {
+ state->tcon = cli->smb1.tcon;
+ state->session = cli->smb1.session;
+ state->pid = cli->smb1.pid;
+ }
+
+ state->ev = ev;
+ state->conn = cli->conn;
+ state->timeout = cli->timeout;
+ state->abs_timeout = timeval_current_ofs_msec(cli->timeout);
+ state->pipe_name = dcerpc_default_transport_endpoint(state, NCACN_NP,
+ table);
+ if (tevent_req_nomem(state->pipe_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ while (state->pipe_name[0] == '\\') {
+ state->pipe_name++;
+ }
+
+ subreq = tstream_smbXcli_np_open_send(state, ev, state->conn,
+ state->session, state->tcon,
+ state->pid, state->timeout,
+ state->pipe_name);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_transport_np_init_pipe_open, req);
+
+ return req;
+}
+
+static void rpc_transport_np_init_pipe_open_retry(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t,
+ void *priv_data)
+{
+ struct tevent_req *subreq;
+ struct tevent_req *req = talloc_get_type(priv_data, struct tevent_req);
+ struct rpc_transport_np_init_state *state = tevent_req_data(
+ req, struct rpc_transport_np_init_state);
+
+ subreq = tstream_smbXcli_np_open_send(state, ev,
+ state->conn,
+ state->session,
+ state->tcon,
+ state->pid,
+ state->timeout,
+ state->pipe_name);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_transport_np_init_pipe_open, req);
+ state->retries++;
+}
+
+static void rpc_transport_np_init_pipe_open(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_transport_np_init_state *state = tevent_req_data(
+ req, struct rpc_transport_np_init_state);
+ NTSTATUS status;
+ struct tstream_context *stream;
+
+ status = tstream_smbXcli_np_open_recv(subreq, state, &stream);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PIPE_NOT_AVAILABLE)
+ && (!timeval_expired(&state->abs_timeout))) {
+ struct tevent_timer *te;
+ /*
+ * Retry on STATUS_PIPE_NOT_AVAILABLE, Windows starts some
+ * servers (FssagentRpc) on demand.
+ */
+ DEBUG(2, ("RPC pipe %s not available, retry %d\n",
+ state->pipe_name, state->retries));
+ te = tevent_add_timer(state->ev, state,
+ timeval_current_ofs_msec(100 * state->retries),
+ rpc_transport_np_init_pipe_open_retry, req);
+ if (tevent_req_nomem(te, req)) {
+ DEBUG(2, ("Failed to create asynchronous "
+ "tevent_timer\n"));
+ }
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = rpc_transport_tstream_init(state,
+ &stream,
+ &state->transport);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS rpc_transport_np_init_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_cli_transport **presult)
+{
+ struct rpc_transport_np_init_state *state = tevent_req_data(
+ req, struct rpc_transport_np_init_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *presult = talloc_move(mem_ctx, &state->transport);
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/rpc_transport_sock.c b/source3/rpc_client/rpc_transport_sock.c
new file mode 100644
index 0000000..a833e44
--- /dev/null
+++ b/source3/rpc_client/rpc_transport_sock.c
@@ -0,0 +1,53 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC client transport over a socket
+ * Copyright (C) Volker Lendecke 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../lib/tsocket/tsocket.h"
+#include "rpc_client/rpc_transport.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_CLI
+
+NTSTATUS rpc_transport_sock_init(TALLOC_CTX *mem_ctx, int fd,
+ struct rpc_cli_transport **presult)
+{
+ struct rpc_cli_transport *result;
+ struct tstream_context *stream;
+ int ret;
+ NTSTATUS status;
+
+ set_blocking(fd, false);
+
+ ret = tstream_bsd_existing_socket(mem_ctx, fd, &stream);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ return status;
+ }
+
+ status = rpc_transport_tstream_init(mem_ctx,
+ &stream,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(stream);
+ return status;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/rpc_transport_tstream.c b/source3/rpc_client/rpc_transport_tstream.c
new file mode 100644
index 0000000..02fc320
--- /dev/null
+++ b/source3/rpc_client/rpc_transport_tstream.c
@@ -0,0 +1,564 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC client transport over tstream
+ * Copyright (C) Simo Sorce 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_client/rpc_transport.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/smb/tstream_smbXcli_np.h"
+#include "cli_pipe.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_CLI
+
+struct rpc_tstream_state {
+ struct tstream_context *stream;
+ struct tevent_queue *read_queue;
+ struct tevent_queue *write_queue;
+ unsigned int timeout;
+};
+
+static void rpc_tstream_disconnect(struct rpc_tstream_state *s)
+{
+ TALLOC_FREE(s->stream);
+}
+
+static bool rpc_tstream_is_connected(void *priv)
+{
+ struct rpc_tstream_state *transp =
+ talloc_get_type_abort(priv, struct rpc_tstream_state);
+ ssize_t ret;
+
+ if (!transp->stream) {
+ return false;
+ }
+
+ if (!tstream_is_smbXcli_np(transp->stream)) {
+ return true;
+ }
+
+ ret = tstream_pending_bytes(transp->stream);
+ if (ret == -1) {
+ return false;
+ }
+
+ return true;
+}
+
+static unsigned int rpc_tstream_set_timeout(void *priv, unsigned int timeout)
+{
+ struct rpc_tstream_state *transp =
+ talloc_get_type_abort(priv, struct rpc_tstream_state);
+ int orig_timeout;
+ bool ok;
+
+ ok = rpc_tstream_is_connected(transp);
+ if (!ok) {
+ return 0;
+ }
+
+ if (tstream_is_smbXcli_np(transp->stream)) {
+ transp->timeout = timeout;
+ return tstream_smbXcli_np_set_timeout(transp->stream, timeout);
+ }
+
+ orig_timeout = transp->timeout;
+
+ transp->timeout = timeout;
+
+ return orig_timeout;
+}
+
+struct rpc_tstream_next_vector_state {
+ uint8_t *buf;
+ size_t len;
+ off_t ofs;
+};
+
+static void rpc_tstream_next_vector_init(
+ struct rpc_tstream_next_vector_state *s,
+ uint8_t *buf, size_t len)
+{
+ *s = (struct rpc_tstream_next_vector_state) {
+ .buf = buf, .len = MIN(len, UINT16_MAX),
+ };
+}
+
+static int rpc_tstream_next_vector(struct tstream_context *stream,
+ void *private_data,
+ TALLOC_CTX *mem_ctx,
+ struct iovec **_vector,
+ size_t *count)
+{
+ struct rpc_tstream_next_vector_state *state =
+ (struct rpc_tstream_next_vector_state *)private_data;
+ struct iovec *vector;
+
+ if (state->ofs == state->len) {
+ *_vector = NULL;
+ *count = 0;
+ return 0;
+ }
+
+ vector = talloc_array(mem_ctx, struct iovec, 1);
+ if (!vector) {
+ return -1;
+ }
+
+ vector[0].iov_base = state->buf;
+ vector[0].iov_len = state->len;
+
+ state->ofs = state->len;
+
+ *_vector = vector;
+ *count = 1;
+ return 0;
+}
+
+struct rpc_tstream_read_state {
+ struct rpc_tstream_state *transp;
+ struct rpc_tstream_next_vector_state next_vector;
+ ssize_t nread;
+};
+
+static void rpc_tstream_read_done(struct tevent_req *subreq);
+
+static struct tevent_req *rpc_tstream_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint8_t *data, size_t size,
+ void *priv)
+{
+ struct rpc_tstream_state *transp =
+ talloc_get_type_abort(priv, struct rpc_tstream_state);
+ struct tevent_req *req, *subreq;
+ struct rpc_tstream_read_state *state;
+ struct timeval endtime;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_tstream_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ if (!rpc_tstream_is_connected(transp)) {
+ NTSTATUS status = NT_STATUS_CONNECTION_DISCONNECTED;
+ if (tstream_is_smbXcli_np(transp->stream)) {
+ status = NT_STATUS_PIPE_DISCONNECTED;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ state->transp = transp;
+ rpc_tstream_next_vector_init(&state->next_vector, data, size);
+
+ subreq = tstream_readv_pdu_queue_send(state, ev,
+ transp->stream,
+ transp->read_queue,
+ rpc_tstream_next_vector,
+ &state->next_vector);
+ if (subreq == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ endtime = timeval_current_ofs_msec(transp->timeout);
+ if (!tevent_req_set_endtime(subreq, ev, endtime)) {
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, rpc_tstream_read_done, req);
+ return req;
+ fail:
+ TALLOC_FREE(req);
+ return NULL;
+}
+
+static void rpc_tstream_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct rpc_tstream_read_state *state =
+ tevent_req_data(req, struct rpc_tstream_read_state);
+ int err;
+
+ state->nread = tstream_readv_pdu_queue_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (state->nread < 0) {
+ rpc_tstream_disconnect(state->transp);
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS rpc_tstream_read_recv(struct tevent_req *req, ssize_t *size)
+{
+ struct rpc_tstream_read_state *state = tevent_req_data(
+ req, struct rpc_tstream_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *size = state->nread;
+ return NT_STATUS_OK;
+}
+
+struct rpc_tstream_write_state {
+ struct tevent_context *ev;
+ struct rpc_tstream_state *transp;
+ struct iovec iov;
+ ssize_t nwritten;
+};
+
+static void rpc_tstream_write_done(struct tevent_req *subreq);
+
+static struct tevent_req *rpc_tstream_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const uint8_t *data, size_t size,
+ void *priv)
+{
+ struct rpc_tstream_state *transp =
+ talloc_get_type_abort(priv, struct rpc_tstream_state);
+ struct tevent_req *req, *subreq;
+ struct rpc_tstream_write_state *state;
+ struct timeval endtime;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_tstream_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ if (!rpc_tstream_is_connected(transp)) {
+ NTSTATUS status = NT_STATUS_CONNECTION_DISCONNECTED;
+ if (tstream_is_smbXcli_np(transp->stream)) {
+ status = NT_STATUS_PIPE_DISCONNECTED;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ state->ev = ev;
+ state->transp = transp;
+ state->iov.iov_base = discard_const_p(void *, data);
+ state->iov.iov_len = size;
+
+ subreq = tstream_writev_queue_send(state, ev,
+ transp->stream,
+ transp->write_queue,
+ &state->iov, 1);
+ if (subreq == NULL) {
+ goto fail;
+ }
+
+ endtime = timeval_current_ofs_msec(transp->timeout);
+ if (!tevent_req_set_endtime(subreq, ev, endtime)) {
+ goto fail;
+ }
+
+ tevent_req_set_callback(subreq, rpc_tstream_write_done, req);
+ return req;
+ fail:
+ TALLOC_FREE(req);
+ return NULL;
+}
+
+static void rpc_tstream_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct rpc_tstream_write_state *state =
+ tevent_req_data(req, struct rpc_tstream_write_state);
+ int err;
+
+ state->nwritten = tstream_writev_queue_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (state->nwritten < 0) {
+ rpc_tstream_disconnect(state->transp);
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS rpc_tstream_write_recv(struct tevent_req *req, ssize_t *sent)
+{
+ struct rpc_tstream_write_state *state =
+ tevent_req_data(req, struct rpc_tstream_write_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *sent = state->nwritten;
+ return NT_STATUS_OK;
+}
+
+struct rpc_tstream_trans_state {
+ struct tevent_context *ev;
+ struct rpc_tstream_state *transp;
+ struct iovec req;
+ uint32_t max_rdata_len;
+ struct iovec rep;
+};
+
+static void rpc_tstream_trans_writev(struct tevent_req *subreq);
+static void rpc_tstream_trans_readv_pdu(struct tevent_req *subreq);
+
+static int rpc_tstream_trans_next_vector(struct tstream_context *stream,
+ void *private_data,
+ TALLOC_CTX *mem_ctx,
+ struct iovec **_vector,
+ size_t *count);
+
+static struct tevent_req *rpc_tstream_trans_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const uint8_t *data, size_t data_len,
+ uint32_t max_rdata_len,
+ void *priv)
+{
+ struct rpc_tstream_state *transp =
+ talloc_get_type_abort(priv, struct rpc_tstream_state);
+ struct tevent_req *req, *subreq;
+ struct rpc_tstream_trans_state *state;
+ struct timeval endtime;
+ bool use_trans = false;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct rpc_tstream_trans_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!rpc_tstream_is_connected(transp)) {
+ NTSTATUS status = NT_STATUS_CONNECTION_DISCONNECTED;
+ if (tstream_is_smbXcli_np(transp->stream)) {
+ status = NT_STATUS_PIPE_DISCONNECTED;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ state->ev = ev;
+ state->transp = transp;
+ state->req.iov_len = data_len;
+ state->req.iov_base = discard_const_p(void *, data);
+ state->max_rdata_len = max_rdata_len;
+
+ endtime = timeval_current_ofs_msec(transp->timeout);
+
+ if (tstream_is_smbXcli_np(transp->stream)) {
+ use_trans = true;
+ }
+ if (tevent_queue_length(transp->write_queue) > 0) {
+ use_trans = false;
+ }
+ if (tevent_queue_length(transp->read_queue) > 0) {
+ use_trans = false;
+ }
+
+ if (use_trans) {
+ tstream_smbXcli_np_use_trans(transp->stream);
+ }
+
+ subreq = tstream_writev_queue_send(state, ev,
+ transp->stream,
+ transp->write_queue,
+ &state->req, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (!tevent_req_set_endtime(subreq, ev, endtime)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_tstream_trans_writev, req);
+
+ subreq = tstream_readv_pdu_queue_send(state, ev,
+ transp->stream,
+ transp->read_queue,
+ rpc_tstream_trans_next_vector,
+ state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (!tevent_req_set_endtime(subreq, ev, endtime)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_tstream_trans_readv_pdu, req);
+
+ return req;
+}
+
+static void rpc_tstream_trans_writev(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct rpc_tstream_trans_state *state =
+ tevent_req_data(req,
+ struct rpc_tstream_trans_state);
+ int ret;
+ int err;
+
+ ret = tstream_writev_queue_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ rpc_tstream_disconnect(state->transp);
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+}
+
+static int rpc_tstream_trans_next_vector(struct tstream_context *stream,
+ void *private_data,
+ TALLOC_CTX *mem_ctx,
+ struct iovec **_vector,
+ size_t *count)
+{
+ struct rpc_tstream_trans_state *state =
+ talloc_get_type_abort(private_data,
+ struct rpc_tstream_trans_state);
+ struct iovec *vector;
+
+ if (state->max_rdata_len == state->rep.iov_len) {
+ *_vector = NULL;
+ *count = 0;
+ return 0;
+ }
+
+ state->rep.iov_base = talloc_array(state, uint8_t,
+ state->max_rdata_len);
+ if (state->rep.iov_base == NULL) {
+ return -1;
+ }
+ state->rep.iov_len = state->max_rdata_len;
+
+ vector = talloc_array(mem_ctx, struct iovec, 1);
+ if (!vector) {
+ return -1;
+ }
+
+ vector[0] = state->rep;
+
+ *_vector = vector;
+ *count = 1;
+ return 0;
+}
+
+static void rpc_tstream_trans_readv_pdu(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct rpc_tstream_trans_state *state =
+ tevent_req_data(req,
+ struct rpc_tstream_trans_state);
+ int ret;
+ int err;
+
+ ret = tstream_readv_pdu_queue_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ rpc_tstream_disconnect(state->transp);
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS rpc_tstream_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint8_t **prdata, uint32_t *prdata_len)
+{
+ struct rpc_tstream_trans_state *state =
+ tevent_req_data(req,
+ struct rpc_tstream_trans_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *prdata = (uint8_t *)talloc_move(mem_ctx, &state->rep.iov_base);
+ *prdata_len = state->rep.iov_len;
+ return NT_STATUS_OK;
+}
+
+/**
+* @brief Initialize a tstream transport facility
+* NOTE: this function will talloc_steal, the stream and the queues.
+*
+* @param mem_ctx - memory context used to allocate the transport
+* @param stream - a ready to use tstream
+* @param presult - the transport structure
+*
+* @return - a NT Status error code.
+*/
+NTSTATUS rpc_transport_tstream_init(TALLOC_CTX *mem_ctx,
+ struct tstream_context **stream,
+ struct rpc_cli_transport **presult)
+{
+ struct rpc_cli_transport *result;
+ struct rpc_tstream_state *state;
+
+ result = talloc(mem_ctx, struct rpc_cli_transport);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state = talloc(result, struct rpc_tstream_state);
+ if (state == NULL) {
+ TALLOC_FREE(result);
+ return NT_STATUS_NO_MEMORY;
+ }
+ result->priv = state;
+
+ state->read_queue = tevent_queue_create(state, "read_queue");
+ if (state->read_queue == NULL) {
+ TALLOC_FREE(result);
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->write_queue = tevent_queue_create(state, "write_queue");
+ if (state->write_queue == NULL) {
+ TALLOC_FREE(result);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->stream = talloc_move(state, stream);
+ state->timeout = 10000; /* 10 seconds. */
+
+ if (tstream_is_smbXcli_np(state->stream)) {
+ result->trans_send = rpc_tstream_trans_send;
+ result->trans_recv = rpc_tstream_trans_recv;
+ } else {
+ result->trans_send = NULL;
+ result->trans_recv = NULL;
+ }
+ result->write_send = rpc_tstream_write_send;
+ result->write_recv = rpc_tstream_write_recv;
+ result->read_send = rpc_tstream_read_send;
+ result->read_recv = rpc_tstream_read_recv;
+ result->is_connected = rpc_tstream_is_connected;
+ result->set_timeout = rpc_tstream_set_timeout;
+
+ *presult = result;
+ return NT_STATUS_OK;
+}
+
+struct tstream_context *rpc_transport_get_tstream(
+ struct rpc_cli_transport *transport)
+{
+ struct rpc_tstream_state *state = talloc_get_type_abort(
+ transport->priv, struct rpc_tstream_state);
+ return state->stream;
+}
diff --git a/source3/rpc_client/util_netlogon.c b/source3/rpc_client/util_netlogon.c
new file mode 100644
index 0000000..52bd40b
--- /dev/null
+++ b/source3/rpc_client/util_netlogon.c
@@ -0,0 +1,449 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "rpc_client/util_netlogon.h"
+
+#define COPY_LSA_STRING(mem_ctx, in, out, name) do { \
+ if (in->name.string) { \
+ out->name.string = talloc_strdup(mem_ctx, in->name.string); \
+ NT_STATUS_HAVE_NO_MEMORY(out->name.string); \
+ } \
+} while (0)
+
+NTSTATUS copy_netr_SamBaseInfo(TALLOC_CTX *mem_ctx,
+ const struct netr_SamBaseInfo *in,
+ struct netr_SamBaseInfo *out)
+{
+ /* first copy all, then realloc pointers */
+ *out = *in;
+
+ COPY_LSA_STRING(mem_ctx, in, out, account_name);
+ COPY_LSA_STRING(mem_ctx, in, out, full_name);
+ COPY_LSA_STRING(mem_ctx, in, out, logon_script);
+ COPY_LSA_STRING(mem_ctx, in, out, profile_path);
+ COPY_LSA_STRING(mem_ctx, in, out, home_directory);
+ COPY_LSA_STRING(mem_ctx, in, out, home_drive);
+
+ if (in->groups.count) {
+ out->groups.rids = (struct samr_RidWithAttribute *)
+ talloc_memdup(mem_ctx, in->groups.rids,
+ (sizeof(struct samr_RidWithAttribute) *
+ in->groups.count));
+ NT_STATUS_HAVE_NO_MEMORY(out->groups.rids);
+ }
+
+ COPY_LSA_STRING(mem_ctx, in, out, logon_server);
+ COPY_LSA_STRING(mem_ctx, in, out, logon_domain);
+
+ if (in->domain_sid) {
+ out->domain_sid = dom_sid_dup(mem_ctx, in->domain_sid);
+ NT_STATUS_HAVE_NO_MEMORY(out->domain_sid);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS copy_netr_SamInfo3(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo3 *in,
+ struct netr_SamInfo3 **pout)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ unsigned int i;
+ NTSTATUS status;
+
+ info3 = talloc_zero(mem_ctx, struct netr_SamInfo3);
+ if (info3 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ status = copy_netr_SamBaseInfo(info3, &in->base, &info3->base);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (in->sidcount) {
+ info3->sidcount = in->sidcount;
+ info3->sids = talloc_array(info3, struct netr_SidAttr,
+ in->sidcount);
+ if (info3->sids == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ for (i = 0; i < in->sidcount; i++) {
+ info3->sids[i].sid = dom_sid_dup(info3->sids,
+ in->sids[i].sid);
+ if (info3->sids[i].sid == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ info3->sids[i].attributes = in->sids[i].attributes;
+ }
+ }
+
+ *pout = info3;
+ info3 = NULL;
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(info3);
+ return status;
+}
+
+NTSTATUS map_validation_to_info3(TALLOC_CTX *mem_ctx,
+ uint16_t validation_level,
+ union netr_Validation *validation,
+ struct netr_SamInfo3 **info3_p)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ struct netr_SamInfo6 *info6 = NULL;
+ NTSTATUS status;
+
+ if (validation == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (validation_level) {
+ case 3:
+ if (validation->sam3 == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = copy_netr_SamInfo3(mem_ctx,
+ validation->sam3,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ break;
+ case 6:
+ if (validation->sam6 == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ info6 = validation->sam6;
+
+ info3 = talloc_zero(mem_ctx, struct netr_SamInfo3);
+ if (info3 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = copy_netr_SamBaseInfo(info3,
+ &info6->base,
+ &info3->base);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info3);
+ return status;
+ }
+
+ if (validation->sam6->sidcount > 0) {
+ int i;
+
+ info3->sidcount = info6->sidcount;
+
+ info3->sids = talloc_array(info3,
+ struct netr_SidAttr,
+ info3->sidcount);
+ if (info3->sids == NULL) {
+ TALLOC_FREE(info3);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < info3->sidcount; i++) {
+ info3->sids[i].sid = dom_sid_dup(
+ info3->sids, info6->sids[i].sid);
+ if (info3->sids[i].sid == NULL) {
+ TALLOC_FREE(info3);
+ return NT_STATUS_NO_MEMORY;
+ }
+ info3->sids[i].attributes =
+ info6->sids[i].attributes;
+ }
+ }
+ break;
+ default:
+ return NT_STATUS_BAD_VALIDATION_CLASS;
+ }
+
+ *info3_p = info3;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS copy_netr_SamInfo6(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo6 *in,
+ struct netr_SamInfo6 **pout)
+{
+ struct netr_SamInfo6 *info6 = NULL;
+ unsigned int i;
+ NTSTATUS status;
+
+ info6 = talloc_zero(mem_ctx, struct netr_SamInfo6);
+ if (info6 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ status = copy_netr_SamBaseInfo(info6, &in->base, &info6->base);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (in->sidcount) {
+ info6->sidcount = in->sidcount;
+ info6->sids = talloc_array(info6, struct netr_SidAttr,
+ in->sidcount);
+ if (info6->sids == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ for (i = 0; i < in->sidcount; i++) {
+ info6->sids[i].sid = dom_sid_dup(info6->sids,
+ in->sids[i].sid);
+ if (info6->sids[i].sid == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ info6->sids[i].attributes = in->sids[i].attributes;
+ }
+ }
+
+ if (in->dns_domainname.string != NULL) {
+ info6->dns_domainname.string = talloc_strdup(info6,
+ in->dns_domainname.string);
+ if (info6->dns_domainname.string == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ if (in->principal_name.string != NULL) {
+ info6->principal_name.string = talloc_strdup(info6,
+ in->principal_name.string);
+ if (info6->principal_name.string == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ *pout = info6;
+ info6 = NULL;
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(info6);
+ return status;
+}
+
+NTSTATUS map_validation_to_info6(TALLOC_CTX *mem_ctx,
+ uint16_t validation_level,
+ union netr_Validation *validation,
+ struct netr_SamInfo6 **info6_p)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ struct netr_SamInfo6 *info6 = NULL;
+ NTSTATUS status;
+
+ if (validation == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (validation_level) {
+ case 3:
+ if (validation->sam3 == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ info3 = validation->sam3;
+
+ info6 = talloc_zero(mem_ctx, struct netr_SamInfo6);
+ if (info6 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = copy_netr_SamBaseInfo(info6,
+ &info3->base,
+ &info6->base);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info6);
+ return status;
+ }
+
+ if (validation->sam3->sidcount > 0) {
+ int i;
+
+ info6->sidcount = info3->sidcount;
+
+ info6->sids = talloc_array(info6,
+ struct netr_SidAttr,
+ info6->sidcount);
+ if (info6->sids == NULL) {
+ TALLOC_FREE(info6);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < info6->sidcount; i++) {
+ info6->sids[i].sid = dom_sid_dup(
+ info6->sids, info3->sids[i].sid);
+ if (info6->sids[i].sid == NULL) {
+ TALLOC_FREE(info6);
+ return NT_STATUS_NO_MEMORY;
+ }
+ info6->sids[i].attributes =
+ info3->sids[i].attributes;
+ }
+ }
+ break;
+ case 6:
+ if (validation->sam6 == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = copy_netr_SamInfo6(mem_ctx,
+ validation->sam6,
+ &info6);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ break;
+ default:
+ return NT_STATUS_BAD_VALIDATION_CLASS;
+ }
+
+ *info6_p = info6;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS map_info3_to_validation(TALLOC_CTX *mem_ctx,
+ struct netr_SamInfo3 *info3,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ union netr_Validation *validation = NULL;
+ NTSTATUS status;
+
+ validation = talloc_zero(mem_ctx, union netr_Validation);
+ if (validation == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = copy_netr_SamInfo3(mem_ctx,
+ info3,
+ &validation->sam3);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(validation);
+ return status;
+ }
+
+ * _validation_level = 3;
+ *_validation = validation;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS map_info6_to_validation(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo6 *info6,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ union netr_Validation *validation = NULL;
+ NTSTATUS status;
+
+ validation = talloc_zero(mem_ctx, union netr_Validation);
+ if (validation == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = copy_netr_SamInfo6(validation,
+ info6,
+ &validation->sam6);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(validation);
+ return status;
+ }
+
+ * _validation_level = 6;
+ *_validation = validation;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS copy_netr_DsRGetDCNameInfo(TALLOC_CTX *mem_ctx,
+ const struct netr_DsRGetDCNameInfo *in,
+ struct netr_DsRGetDCNameInfo **pout)
+{
+ struct netr_DsRGetDCNameInfo *r;
+
+ r = talloc_zero(mem_ctx, struct netr_DsRGetDCNameInfo);
+ if (r == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ r->dc_unc = talloc_strdup(r, in->dc_unc);
+ if (r->dc_unc == NULL) {
+ talloc_free(r);
+ return NT_STATUS_NO_MEMORY;
+ }
+ r->dc_address = talloc_strdup(r, in->dc_address);
+ if (r->dc_address == NULL) {
+ talloc_free(r);
+ return NT_STATUS_NO_MEMORY;
+ }
+ r->dc_address_type = in->dc_address_type;
+ r->domain_guid = in->domain_guid;
+ r->domain_name = talloc_strdup(r, in->domain_name);
+ if (r->domain_name == NULL) {
+ talloc_free(r);
+ return NT_STATUS_NO_MEMORY;
+ }
+ /* forest could be empty */
+ if (in->forest_name != NULL) {
+ r->forest_name = talloc_strdup(r, in->forest_name);
+ if (r->forest_name == NULL) {
+ talloc_free(r);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ r->dc_flags = in->dc_flags;
+ if (in->dc_site_name != NULL) {
+ r->dc_site_name = talloc_strdup(r, in->dc_site_name);
+ if (r->dc_site_name == NULL) {
+ talloc_free(r);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (in->client_site_name != NULL) {
+ r->client_site_name = talloc_strdup(r, in->client_site_name);
+ if (r->client_site_name == NULL) {
+ talloc_free(r);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *pout = r;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_client/util_netlogon.h b/source3/rpc_client/util_netlogon.h
new file mode 100644
index 0000000..85680a9
--- /dev/null
+++ b/source3/rpc_client/util_netlogon.h
@@ -0,0 +1,54 @@
+/*
+ Unix SMB/CIFS implementation.
+ Authentication utility functions
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _RPC_CLIENT_UTIL_NETLOGON_H_
+#define _RPC_CLIENT_UTIL_NETLOGON_H_
+
+/* The following definitions come from rpc_client/util_netlogon.c */
+
+NTSTATUS copy_netr_SamBaseInfo(TALLOC_CTX *mem_ctx,
+ const struct netr_SamBaseInfo *in,
+ struct netr_SamBaseInfo *out);
+NTSTATUS copy_netr_SamInfo3(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo3 *in,
+ struct netr_SamInfo3 **pout);
+NTSTATUS map_validation_to_info3(TALLOC_CTX *mem_ctx,
+ uint16_t validation_level,
+ union netr_Validation *validation,
+ struct netr_SamInfo3 **info3_p);
+NTSTATUS copy_netr_SamInfo6(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo6 *in,
+ struct netr_SamInfo6 **pout);
+NTSTATUS map_validation_to_info6(TALLOC_CTX *mem_ctx,
+ uint16_t validation_level,
+ union netr_Validation *validation,
+ struct netr_SamInfo6 **info6_p);
+NTSTATUS map_info3_to_validation(TALLOC_CTX *mem_ctx,
+ struct netr_SamInfo3 *info3,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation);
+NTSTATUS map_info6_to_validation(TALLOC_CTX *mem_ctx,
+ const struct netr_SamInfo6 *info6,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation);
+NTSTATUS copy_netr_DsRGetDCNameInfo(TALLOC_CTX *mem_ctx,
+ const struct netr_DsRGetDCNameInfo *in,
+ struct netr_DsRGetDCNameInfo **pout);
+
+#endif /* _RPC_CLIENT_UTIL_NETLOGON_H_ */
diff --git a/source3/rpc_client/wsp_cli.c b/source3/rpc_client/wsp_cli.c
new file mode 100644
index 0000000..15b6e36
--- /dev/null
+++ b/source3/rpc_client/wsp_cli.c
@@ -0,0 +1,2221 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Window Search Service
+ *
+ * Copyright (c) Noel Power
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "client.h"
+#include "rpc_client/wsp_cli.h"
+#include "rpc_client/rpc_client.h"
+#include "param/param.h"
+#include "auth/credentials/credentials.h"
+#include <tevent.h>
+#include <util/tevent_ntstatus.h>
+#include "libcli/tstream_binding_handle/tstream_binding_handle.h"
+#include "lib/tsocket/tsocket.h"
+#include "librpc/wsp/wsp_util.h"
+#include "librpc/gen_ndr/ndr_wsp.h"
+#include "rpc_client/cli_pipe.h"
+#include "libcli/smb/smbXcli_base.h"
+
+#define MSG_HDR_SIZE 16
+#define USED 1
+/*
+ * 32-bit Windows XP operating system, 32-bit Windows Server 2003 operating
+ * system, 32-bit Windows Home Server server software, 32-bit Windows Vista
+ * with Windows Search 4.0, 32-bit Windows Server 2003 with Windows
+ * Search 4.0. All of these versions of Windows are running
+ * Windows Search 4.0.
+*/
+
+static const uint32_t CLIENTVERSION = 0x00010700;
+
+/*
+ * DBPROP_CI_SCOPE_FLAGS
+ * containing QUERY_DEEP
+ * QUERY_DEEP (0x1) indicates that files in the scope directory and all
+ * subdirectories are included in the results. If clear, only files in
+ * the scope directory are included in the results.
+ */
+static int32_t scope_flags_vector[] = {0x00000001};
+/*
+ * Search everywhere "\\" is the root scope
+ */
+static const char * root_scope_string_vector[] = {"\\"};
+
+/* sets sensible defaults */
+static void init_wsp_prop(struct wsp_cdbprop *prop)
+{
+ *prop = (struct wsp_cdbprop){0};
+ prop->colid.ekind = DBKIND_GUID_PROPID;
+}
+
+
+static bool create_restriction_array(TALLOC_CTX *ctx,
+ struct wsp_crestriction **pelements,
+ uint32_t nnodes)
+{
+ struct wsp_crestriction *elements = talloc_zero_array(ctx,
+ struct wsp_crestriction,
+ nnodes);
+ if (elements == NULL) {
+ return false;
+ }
+ *pelements = elements;
+ return true;
+}
+
+
+static bool create_noderestriction(TALLOC_CTX *ctx,
+ struct wsp_cnoderestriction *pnode,
+ uint32_t nnodes)
+{
+ bool ok;
+ pnode->cnode = nnodes;
+ ok = create_restriction_array(ctx, &pnode->panode, nnodes);
+ return ok;
+}
+
+static bool fill_sortarray(TALLOC_CTX *ctx, struct wsp_csort **dest,
+ struct wsp_csort *src, uint32_t num)
+{
+ uint32_t i;
+ struct wsp_csort *psort = talloc_zero_array(ctx, struct wsp_csort,
+ num);
+ if (psort == NULL) {
+ return false;
+ }
+ for (i = 0; i < num; i++) {
+ psort[i] = src[i];
+ }
+ *dest = psort;
+ return true;
+}
+
+
+
+static bool set_fullpropspec(TALLOC_CTX *ctx, struct wsp_cfullpropspec *prop,
+ const char* propname, uint32_t kind)
+{
+ struct GUID guid = {0};
+ const struct full_propset_info *prop_info = NULL;
+
+ prop_info = get_propset_info_with_guid(propname, &guid);
+ if (!prop_info) {
+ DBG_ERR("Failed to handle property named %s\n",
+ propname);
+ return false;
+ }
+ prop->guidpropset = guid;
+ prop->ulkind = kind;
+ if (kind == PRSPEC_LPWSTR) {
+ prop->name_or_id.propname.vstring = talloc_strdup(ctx,
+ propname);
+ if (prop->name_or_id.propname.vstring == NULL) {
+ DBG_ERR("out of memory");
+ return false;
+ }
+ prop->name_or_id.propname.len = strlen(propname);
+ } else {
+ prop->name_or_id.prspec = prop_info->id;
+ }
+ return true;
+}
+
+struct binding
+{
+ uint32_t status_off;
+ uint32_t value_off;
+ uint32_t len_off;
+};
+
+static bool set_ctablecolumn(TALLOC_CTX *ctx, struct wsp_ctablecolumn *tablecol,
+ const char* propname, struct binding *offsets,
+ uint32_t value_size)
+{
+ struct wsp_cfullpropspec *prop = &tablecol->propspec;
+
+ if (!set_fullpropspec(ctx, prop, propname, PRSPEC_PROPID)) {
+ return false;
+ }
+ tablecol->vtype =VT_VARIANT ;
+ tablecol->aggregateused = USED;
+ tablecol->valueused = USED;
+ tablecol->valueoffset.value = offsets->value_off;
+ tablecol->valuesize.value = value_size;
+ tablecol->statusused = USED;
+ tablecol->statusoffset.value = offsets->status_off;
+ tablecol->lengthused = USED;
+ tablecol->lengthoffset.value = offsets->len_off;
+ return true;
+}
+
+
+static bool fill_uint32_vec(TALLOC_CTX* ctx,
+ uint32_t **pdest,
+ uint32_t* ivector, uint32_t elems)
+{
+ uint32_t i;
+ uint32_t *dest = talloc_zero_array(ctx, uint32_t, elems);
+ if (dest == NULL) {
+ return false;
+ }
+
+ for ( i = 0; i < elems; i++ ) {
+ dest[ i ] = ivector[ i ];
+ }
+ *pdest = dest;
+ return true;
+}
+
+static bool init_propset1(TALLOC_CTX* tmp_ctx,
+ struct wsp_cdbpropset *propertyset)
+{
+ uint32_t i;
+ GUID_from_string(DBPROPSET_FSCIFRMWRK_EXT,
+ &propertyset->guidpropertyset);
+
+ propertyset->cproperties = 4;
+ propertyset->aprops =
+ talloc_zero_array(tmp_ctx, struct wsp_cdbprop,
+ propertyset->cproperties);
+ if (propertyset->aprops == NULL) {
+ return false;
+ }
+
+ /* initialise first 4 props */
+ for( i = 0; i < propertyset->cproperties; i++) {
+ init_wsp_prop(&propertyset->aprops[i]);
+ }
+
+ /*
+ * see MS-WSP 2.2.1.31.1 & 4.1 Protocol examples, Example 1
+ * and also as seen in various windows network traces
+ * set value prop[0] - 'catalog to search'
+ */
+
+ propertyset->aprops[0].dbpropid = DBPROP_CI_CATALOG_NAME;
+ /* The name of the Catalog to Query */
+ set_variant_lpwstr(tmp_ctx, &propertyset->aprops[0].vvalue,
+ "Windows\\SystemIndex");
+ /*
+ * set value prop[1] 'Regular Query'
+ */
+
+ propertyset->aprops[1].dbpropid = DBPROP_CI_QUERY_TYPE;
+ set_variant_i4(tmp_ctx, &propertyset->aprops[1].vvalue,
+ CINORMAL);
+
+ /*
+ * set value prop[2] 'search subfolders'
+ */
+ propertyset->aprops[2].dbpropid = DBPROP_CI_SCOPE_FLAGS;
+ set_variant_i4_vector(tmp_ctx, &propertyset->aprops[2].vvalue,
+ scope_flags_vector, ARRAY_SIZE(scope_flags_vector));
+
+ /*
+ * set value prop[3] 'root scope'
+ */
+ propertyset->aprops[3].dbpropid = DBPROP_CI_INCLUDE_SCOPES;
+ set_variant_lpwstr_vector(tmp_ctx,
+ &propertyset->aprops[3].vvalue,
+ root_scope_string_vector,
+ ARRAY_SIZE(root_scope_string_vector));
+ return true;
+}
+
+static bool init_propset2(TALLOC_CTX* tmp_ctx,
+ struct wsp_cdbpropset *propertyset,
+ const char* server)
+{
+ uint32_t i;
+
+ GUID_from_string(DBPROPSET_CIFRMWRKCORE_EXT,
+ &propertyset->guidpropertyset);
+
+ propertyset->cproperties = 1;
+ propertyset->aprops =
+ talloc_zero_array(tmp_ctx, struct wsp_cdbprop,
+ propertyset->cproperties);
+ if (propertyset->aprops == NULL) {
+ return false;
+ }
+
+ /* initialise first 1 props */
+ for( i = 0; i < propertyset->cproperties; i++) {
+ init_wsp_prop(&propertyset->aprops[i]);
+ }
+
+ /*
+ * see MS-WSP 2.2.1.31.1 & 4.1 Protocol examples, Example 1
+ * and also as seen in various windows network traces
+ * set value prop[0] - 'machines to search'
+ */
+ propertyset->aprops[0].dbpropid = DBPROP_MACHINE;
+ set_variant_bstr(tmp_ctx, &propertyset->aprops[0].vvalue,
+ server);
+ return true;
+}
+
+static bool init_apropset0(TALLOC_CTX* tmp_ctx,
+ struct wsp_cdbpropset *propertyset)
+{
+ uint32_t i;
+
+ GUID_from_string(DBPROPSET_MSIDXS_ROWSETEXT,
+ &propertyset->guidpropertyset);
+
+ propertyset->cproperties = 7;
+ propertyset->aprops =
+ talloc_zero_array(tmp_ctx, struct wsp_cdbprop,
+ propertyset->cproperties);
+ if (propertyset->aprops == NULL) {
+ return false;
+ }
+
+ /* initialise props */
+ for( i = 0; i < propertyset->cproperties; i++) {
+ init_wsp_prop(&propertyset->aprops[i]);
+ }
+
+ /*
+ * see MS-WSP 2.2.1.31.1 & 4.1 Protocol examples, Example 1
+ * set value prop[0]
+ * MSIDXSPROP_ROWSETQUERYSTATUS - 'ignored'
+ */
+ propertyset->aprops[0].dbpropid = MSIDXSPROP_ROWSETQUERYSTATUS;
+ set_variant_i4(tmp_ctx, &propertyset->aprops[0].vvalue, 0x00000000);
+
+ /*
+ * set value prop[1]
+ * MSIDXSPROP_COMMAND_LOCALE_STRING - 'EN'
+ */
+ propertyset->aprops[1].dbpropid = MSIDXSPROP_COMMAND_LOCALE_STRING;
+ set_variant_bstr(tmp_ctx, &propertyset->aprops[1].vvalue,
+ "en-us");
+
+ /*
+ * set value prop[2]
+ * MSIDXSPROP_QUERY_RESTRICTION - 'ignored'
+ */
+ propertyset->aprops[2].dbpropid = MSIDXSPROP_QUERY_RESTRICTION;
+ set_variant_bstr(tmp_ctx, &propertyset->aprops[2].vvalue,
+ "");
+
+ /*
+ * set value prop[3]
+ * MSIDXSPROP_PARSE_TREE - 'ignored'
+ */
+ propertyset->aprops[3].dbpropid = MSIDXSPROP_PARSE_TREE;
+ set_variant_bstr(tmp_ctx, &propertyset->aprops[3].vvalue,
+ "");
+
+ /*
+ * set value prop[4]
+ * MSIDXSPROP_MAX_RANK - 'ignored'
+ */
+ propertyset->aprops[4].dbpropid = MSIDXSPROP_MAX_RANK;
+ set_variant_i4(tmp_ctx, &propertyset->aprops[4].vvalue, 0x00000000);
+
+ /*
+ * set value prop[5]
+ * MSIDXSPROP_RESULTS_FOUND - 'ignored'
+ */
+ propertyset->aprops[5].dbpropid = MSIDXSPROP_RESULTS_FOUND;
+ set_variant_i4(tmp_ctx, &propertyset->aprops[5].vvalue, 0x00000000);
+
+ /*
+ * set value prop[6]
+ * ? - '' (unknown property id)
+ */
+ propertyset->aprops[6].dbpropid = 0x00000008;
+ set_variant_i4(tmp_ctx, &propertyset->aprops[6].vvalue, 0x00000000);
+ return true;
+}
+
+static bool init_apropset1(TALLOC_CTX* tmp_ctx,
+ struct wsp_cdbpropset *propertyset)
+{
+ uint32_t i;
+ GUID_from_string(DBPROPSET_QUERYEXT,
+ &propertyset->guidpropertyset);
+
+ propertyset->cproperties = 11;
+ propertyset->aprops =
+ talloc_zero_array(tmp_ctx, struct wsp_cdbprop,
+ propertyset->cproperties);
+ if (propertyset->aprops == NULL) {
+ return false;
+ }
+
+ /* init properties */
+ for( i = 0; i < propertyset->cproperties; i++) {
+ init_wsp_prop(&propertyset->aprops[i]);
+ }
+
+ /*
+ * see MS-WSP 2.2.1.31.1 & 4.1 Protocol examples, Example 1
+ * set value prop[0]
+ * DBPROP_USECONTENTINDEX - 'forced use of the full text index
+ * is false.'
+ */
+ propertyset->aprops[0].dbpropid = DBPROP_USECONTENTINDEX;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[0].vvalue, false);
+
+ /*
+ * set value prop[1]
+ * DBPROP_DEFERNONINDEXEDTRIMMING - 'trimming of security
+ * results will not be deferred'
+ */
+ propertyset->aprops[1].dbpropid = DBPROP_DEFERNONINDEXEDTRIMMING;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[1].vvalue, false);
+
+ /*
+ * set value prop[2]
+ * DBPROP_USEEXTENDEDDBTYPES - 'extended DB types are not used'
+ */
+ propertyset->aprops[2].dbpropid = DBPROP_USEEXTENDEDDBTYPES;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[2].vvalue, false);
+
+ /*
+ * set value prop[3]
+ * DBPROP_IGNORENOISEONLYCLAUSES = 'full text clauses consisting
+ * entirely of noise words will
+ * result in an error being returned'
+ */
+ propertyset->aprops[3].dbpropid = DBPROP_IGNORENOISEONLYCLAUSES;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[3].vvalue, false);
+
+ /*
+ * set value prop[4]
+ * DBPROP_GENERICOPTIONS_STRING - 'no generic options set'
+ */
+ propertyset->aprops[4].dbpropid = DBPROP_GENERICOPTIONS_STRING;
+ set_variant_bstr(tmp_ctx, &propertyset->aprops[4].vvalue, "");
+
+ /*
+ * set value prop[5]
+ * DBPROP_DEFERCATALOGVERIFICATION - 'catalog verification is not
+ * deferred.'
+ */
+ propertyset->aprops[5].dbpropid = DBPROP_DEFERCATALOGVERIFICATION;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[5].vvalue, false);
+
+ /*
+ * set value prop[6]
+ * DBPROP_IGNORESBRI - 'query can use the sort-by-rank index
+ * optimization'
+ */
+ propertyset->aprops[6].dbpropid = DBPROP_IGNORESBRI;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[6].vvalue, false);
+
+ /*
+ * set value prop[7]
+ * DBPROP_GENERATEPARSETREE - 'a parse tree is not generated for
+ * debugging.'
+ */
+ propertyset->aprops[7].dbpropid = DBPROP_GENERATEPARSETREE;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[7].vvalue, false);
+
+ /*
+ * set value prop[8]
+ * DBPROP_FREETEXTANYTERM - 'all terms from a FREETEXT clause
+ * appear in every matching document'
+ */
+ propertyset->aprops[8].dbpropid = DBPROP_FREETEXTANYTERM;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[8].vvalue, false);
+ /*
+ * set value prop[9]
+ * DBPROP_FREETEXTUSESTEMMING - 'stemming is not used when interpreting
+ * a FREETEXT clause'
+ */
+ propertyset->aprops[9].dbpropid = DBPROP_FREETEXTUSESTEMMING;
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[9].vvalue, false);
+
+ /*
+ * set value prop[10]
+ * ? - ''
+ */
+ propertyset->aprops[10].dbpropid = 0x0000000f; /* ??? */
+ set_variant_vt_bool(tmp_ctx, &propertyset->aprops[10].vvalue, false);
+ return true;
+}
+
+static bool init_apropset2(TALLOC_CTX* tmp_ctx,
+ struct wsp_cdbpropset *propertyset,
+ const char* server)
+{
+ uint32_t i;
+ GUID_from_string(DBPROPSET_CIFRMWRKCORE_EXT,
+ &propertyset->guidpropertyset);
+
+ propertyset->cproperties = 1;
+ propertyset->aprops =
+ talloc_zero_array(tmp_ctx, struct wsp_cdbprop,
+ propertyset->cproperties);
+ if (propertyset->aprops == NULL) {
+ return false;
+ }
+
+ /* init properties */
+ for( i = 0; i < propertyset->cproperties; i++) {
+ init_wsp_prop(&propertyset->aprops[i]);
+ }
+
+ /*
+ * see MS-WSP 2.2.1.31.1 & 4.1 Protocol examples, Example 1
+ * and also as seen in various windows network traces
+ * set value prop[0]
+ * DBPROP_MACHINE - 'target server'
+ */
+ propertyset->aprops[0].dbpropid = DBPROP_MACHINE;
+ set_variant_bstr(tmp_ctx, &propertyset->aprops[0].vvalue, server);
+ return true;
+}
+
+
+static bool init_apropset3(TALLOC_CTX* tmp_ctx,
+ struct wsp_cdbpropset *propertyset)
+{
+ uint32_t i;
+
+ GUID_from_string(DBPROPSET_FSCIFRMWRK_EXT,
+ &propertyset->guidpropertyset);
+
+ propertyset->cproperties = 3;
+ propertyset->aprops =
+ talloc_zero_array(tmp_ctx, struct wsp_cdbprop,
+ propertyset->cproperties);
+ if (propertyset->aprops == NULL) {
+ return false;
+ }
+
+ /* init properties */
+ for( i = 0; i < propertyset->cproperties; i++) {
+ init_wsp_prop(&propertyset->aprops[i]);
+ }
+
+ /*
+ * see MS-WSP 2.2.1.31.1 & 4.1 Protocol examples, Example 1
+ * and also as seen in various windows network traces
+ * set value prop[0]
+ * DBPROP_CI_INCLUDE_SCOPES - 'search everywhere'
+ */
+ propertyset->aprops[0].dbpropid = DBPROP_CI_INCLUDE_SCOPES;
+ set_variant_array_bstr(tmp_ctx, &propertyset->aprops[0].vvalue,
+ root_scope_string_vector,
+ ARRAY_SIZE(root_scope_string_vector));
+
+ /*
+ * set value prop[1]
+ * DBPROP_CI_SCOPE_FLAGS - 'QUERY_DEEP'
+ */
+ propertyset->aprops[1].dbpropid = DBPROP_CI_SCOPE_FLAGS;
+ set_variant_array_i4(tmp_ctx, &propertyset->aprops[1].vvalue,
+ scope_flags_vector,
+ ARRAY_SIZE(scope_flags_vector));
+
+ /*
+ * set value prop[2]
+ * DBPROP_CI_CATALOG_NAME - 'index to use' (always the same)
+ */
+ propertyset->aprops[2].dbpropid = DBPROP_CI_CATALOG_NAME;
+ set_variant_bstr(tmp_ctx, &propertyset->aprops[2].vvalue,
+ "Windows\\SystemIndex");
+ return true;
+}
+
+bool init_connectin_request(TALLOC_CTX *ctx,
+ struct wsp_request* request,
+ const char* clientmachine,
+ const char* clientuser,
+ const char* server)
+{
+ enum ndr_err_code err;
+ struct connectin_propsets *props = NULL;
+ struct connectin_extpropsets *ext_props = NULL;
+ DATA_BLOB props_blob = data_blob_null;
+ struct ndr_push *ndr_props = NULL;
+ ndr_flags_type ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+ bool result;
+ struct wsp_cpmconnectin *connectin =
+ &request->message.cpmconnect;
+
+ props = talloc_zero(ctx, struct connectin_propsets);
+ if (props == NULL) {
+ result = false;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ ext_props = talloc_zero(ctx, struct connectin_extpropsets) ;
+ if (ext_props == NULL) {
+ result = false;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ request->header.msg = CPMCONNECT;
+ connectin->iclientversion = CLIENTVERSION;
+ /*
+ * hmm just say the client is remote, if we
+ * are talking to windows it is, if not does
+ * it really matter?
+ */
+ connectin->fclientisremote = 0x00000001;
+ connectin->machinename = clientmachine;
+ connectin->username = clientuser;
+ props->cpropsets = 2;
+
+ /* =================== */
+ /* set up PropertySet1 */
+ /* =================== */
+ if (!init_propset1(ctx, &props->propertyset1)) {
+ result = false;
+ DBG_ERR("initialising propset1 failed\n");
+ goto out;
+ }
+
+ /* =================== */
+ /* set up PropertySet2 */
+ /* =================== */
+ if (!init_propset2(ctx, &props->propertyset2, server)) {
+ result = false;
+ DBG_ERR("initialising propset2 failed\n");
+ goto out;
+ }
+
+ /* 4 ExtPropSets */
+ ext_props->cextpropset = 4;
+ ext_props->apropertysets = talloc_zero_array(ctx, struct wsp_cdbpropset,
+ ext_props->cextpropset);
+
+ if (ext_props->apropertysets == NULL) {
+ result = false;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ /* ======================= */
+ /* set up aPropertySets[0] */
+ /* ======================= */
+ if (!init_apropset0(ctx, &ext_props->apropertysets[0])) {
+ result = false;
+ DBG_ERR("initialisation of apropset0 failed\n");
+ goto out;
+ }
+
+ /* ======================= */
+ /* set up aPropertySets[1] */
+ /* ======================= */
+ if (!init_apropset1(ctx, &ext_props->apropertysets[1])) {
+ result = false;
+ DBG_ERR("initialisation of apropset1 failed\n");
+ goto out;
+ }
+
+ /* ======================= */
+ /* set up aPropertySets[2] */
+ /* ======================= */
+ if (!init_apropset2(ctx, &ext_props->apropertysets[2], server)) {
+ result = false;
+ DBG_ERR("initialisation of apropset2 failed\n");
+ goto out;
+ }
+
+ /* ======================= */
+ /* set up aPropertySets[3] */
+ /* ======================= */
+ if (!init_apropset3(ctx, &ext_props->apropertysets[3])) {
+ result = false;
+ DBG_ERR("initialisation of apropset3 failed\n");
+ goto out;
+ }
+
+ /* we also have to fill the opaque blobs that contain the propsets */
+ ndr_props = ndr_push_init_ctx(ctx);
+ if (ndr_props == NULL) {
+ result = false;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ /* first connectin_propsets */
+ err = ndr_push_connectin_propsets(ndr_props, ndr_flags, props);
+ if (err) {
+ DBG_ERR("Failed to push propset, error %d\n", err);
+ result = false;
+ goto out;
+ }
+ props_blob = ndr_push_blob(ndr_props);
+ connectin->cbblob1 = props_blob.length;
+ connectin->propsets = talloc_zero_array(ctx, uint8_t,
+ connectin->cbblob1);
+ if (connectin->propsets == NULL) {
+ result = false;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ memcpy(connectin->propsets, props_blob.data, props_blob.length);
+
+ /* then connectin_extpropsets */
+ TALLOC_FREE(ndr_props);
+ ndr_props = ndr_push_init_ctx(ctx);
+
+ if (ndr_props == NULL) {
+ result = false;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ err = ndr_push_connectin_extpropsets(ndr_props, ndr_flags, ext_props);
+
+ if (err) {
+ DBG_ERR("Failed to push extpropset, error %d\n", err);
+ result = false;
+ goto out;
+ }
+
+ props_blob = ndr_push_blob(ndr_props);
+ connectin->cbblob2 = props_blob.length;
+ connectin->extpropsets = talloc_zero_array(ctx, uint8_t,
+ connectin->cbblob2);
+
+ if (connectin->extpropsets == NULL) {
+ result = false;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ memcpy(connectin->extpropsets, props_blob.data, props_blob.length);
+ TALLOC_FREE(ndr_props);
+ result = true;
+out:
+ return result;
+}
+
+void create_seekat_getrows_request(TALLOC_CTX * ctx,
+ struct wsp_request* request,
+ uint32_t cursor,
+ uint32_t bookmark,
+ uint32_t skip,
+ uint32_t rows,
+ uint32_t cbreserved,
+ uint32_t ulclientbase,
+ uint32_t cbrowwidth,
+ uint32_t fbwdfetch)
+{
+ struct wsp_cpmgetrowsin *getrows =
+ &request->message.cpmgetrows;
+ /* msg type */
+ request->header.msg = CPMGETROWS;
+ /* position */
+ getrows->hcursor = cursor;
+ /* max no. rows to receive */
+ getrows->crowstotransfer = rows;
+ /*
+ * size (length) of row in bytes, determined from value set
+ * by CPMSetBindings message
+ */
+ getrows->cbrowWidth = cbrowwidth;
+ /*
+ * according to we should calculate this (see MS-WSP 3.2.4.2.4)
+ * but it seems window always sets this to the max 16KB limit
+ * (most likely when any row value is variable size e.g. like a
+ * string/path)
+ */
+ getrows->cbreadbuffer = 0x00004000;
+ /*
+ * base value of buffer pointer
+ */
+ getrows->ulclientbase = ulclientbase;
+ getrows->cbreserved = cbreserved;
+ /* fetch rows in forward order */
+ getrows->fbwdfetch = fbwdfetch;
+ /* eRowSeekAt */
+ getrows->etype = EROWSEEKAT;
+ /* we don't handle chapters */
+ getrows->chapt = 0;
+ /* CRowsSeekAt (MS-WSP 2.2.1.37) */
+ getrows->seekdescription.crowseekat.bmkoffset = bookmark;
+ getrows->seekdescription.crowseekat.cskip = skip;
+ getrows->seekdescription.crowseekat.hregion = 0;
+}
+
+static bool extract_rowbuf_variable_type(TALLOC_CTX *ctx,
+ uint16_t type,
+ uint64_t offset,
+ DATA_BLOB *rows_buf, uint32_t len,
+ struct wsp_cbasestoragevariant *val)
+{
+ enum ndr_err_code err;
+ struct ndr_pull *ndr_pull = NULL;
+ ndr_flags_type ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+ DATA_BLOB variant_blob = data_blob_null;
+ if (offset >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" outside buffer range (buf len - %zu)",
+ offset,
+ rows_buf->length);
+ return false;
+ }
+ variant_blob.data = rows_buf->data + offset;
+ variant_blob.length = len;
+ ndr_pull = ndr_pull_init_blob(&variant_blob, ctx);
+
+ if (ndr_pull == NULL) {
+ DBG_ERR("out of memory\n");
+ return false;
+ }
+
+ switch (type) {
+ case VT_LPWSTR: {
+ const char *string = NULL;
+ ndr_set_flags(&ndr_pull->flags, LIBNDR_FLAG_STR_NULLTERM);
+ err = ndr_pull_string(ndr_pull, ndr_flags, &string);
+ if (err) {
+ DBG_ERR("error unmarshalling string from %p\n", variant_blob.data );
+ } else {
+ DBG_INFO("\tstring val ->%s<-\n", string );
+ val->vtype = type;
+ val->vvalue.vt_lpwstr.value = string;
+ }
+ break;
+ }
+ default:
+ DBG_ERR("#FIXME Unhandled variant type %s\n", get_vtype_name(type));
+ break;
+ }
+ return true;
+}
+
+static bool convert_variant_array_to_vector(TALLOC_CTX *ctx,
+ uint64_t count,
+ struct wsp_cbasestoragevariant **variant_array,
+ struct wsp_cbasestoragevariant *outval)
+{
+ uint64_t i;
+ uint16_t vtype;
+ union variant_types vvalue = {0};
+ vtype = variant_array[0]->vtype;
+
+ if (outval == NULL) {
+ return false;
+ }
+
+ if (count) {
+ switch (vtype) {
+ case VT_BSTR:
+ vvalue.vt_bstr_v.vvector_elements = count;
+ vvalue.vt_bstr_v.vvector_data =
+ talloc_zero_array(ctx,
+ struct vt_bstr, count);
+ if (vvalue.vt_bstr_v.vvector_data == NULL) {
+ return false;
+ }
+ break;
+ case VT_LPWSTR:
+ vvalue.vt_lpwstr_v.vvector_elements = count;
+ vvalue.vt_lpwstr_v.vvector_data =
+ talloc_zero_array(ctx,
+ struct vt_lpwstr, count);
+ if (vvalue.vt_lpwstr_v.vvector_data == NULL) {
+ return false;
+ }
+ break;
+ case VT_COMPRESSED_LPWSTR:
+ vvalue.vt_compresseed_lpwstr_v.vvector_elements
+ = count;
+ vvalue.vt_compresseed_lpwstr_v.vvector_data =
+ talloc_zero_array(ctx,
+ struct vt_compressed_lpwstr,
+ count);
+ if (vvalue.vt_compresseed_lpwstr_v.vvector_data == NULL) {
+ return false;
+ }
+ break;
+ default:
+ DBG_ERR("Can't convert array of %s to VECTOR\n",
+ get_vtype_name(vtype));
+ return false;
+ }
+ }
+ for (i = 0; i < count; i++) {
+ if (variant_array[i]->vtype != vtype) {
+ DBG_ERR("array item type %s doesn't match extpected "
+ "type %s\n",
+ get_vtype_name(variant_array[i]->vtype),
+ get_vtype_name(vtype));
+ return false;
+ }
+ switch (variant_array[i]->vtype) {
+ case VT_BSTR:
+ vvalue.vt_bstr_v.vvector_data[i]
+ = variant_array[i]->vvalue.vt_bstr;
+ break;
+ case VT_LPWSTR:
+ vvalue.vt_lpwstr_v.vvector_data[i]
+ = variant_array[i]->vvalue.vt_lpwstr;
+ break;
+ case VT_COMPRESSED_LPWSTR:
+ vvalue.vt_compresseed_lpwstr_v.vvector_data[i]
+ = variant_array[i]->vvalue.vt_compressed_lpwstr;
+ break;
+ default:
+ DBG_ERR("Can't convert array of %s to VECTOR\n",
+ get_vtype_name(vtype));
+ return false;
+ }
+ }
+ outval->vtype = vtype | VT_VECTOR;
+ outval->vvalue = vvalue;
+ return true;
+}
+
+/*
+ * get the addresses in rowbuf of variants to read from
+ * pvec_address will point to addresses,
+ * an array of n elements for a vector or array of 1 element
+ * if non-vector item.
+ *
+ * addresses stored in pvec_address
+ *
+ */
+static enum ndr_err_code extract_variant_addresses(TALLOC_CTX *ctx,
+ struct wsp_ctablevariant *tablevar,
+ bool is_64bit,
+ struct ndr_pull *ndr_pull,
+ ndr_flags_type flags,
+ uint64_t baseaddress,
+ DATA_BLOB *rows_buf,
+ uint64_t *pcount,
+ uint64_t **pvec_address)
+{
+ bool is_vector = tablevar->vtype & VT_VECTOR;
+ uint64_t count;
+ uint64_t addr;
+ uint64_t *vec_address = NULL;
+ enum ndr_err_code err;
+
+ /* read count (only if this is a vector) */
+ if (is_vector) {
+ if (is_64bit) {
+ err = ndr_pull_udlong(ndr_pull,
+ flags,
+ &count);
+ if (err) {
+ DBG_ERR("Failed to extract count\n");
+ goto out;
+ }
+ } else {
+ uint32_t count_32;
+ err = ndr_pull_uint32(ndr_pull,
+ flags,
+ &count_32);
+ if (err) {
+ DBG_ERR("Failed to extract count\n");
+ goto out;
+ }
+ count = (uint64_t)count_32;
+ }
+ } else {
+ count = 1;
+ }
+
+ /* ensure count is at least within buffer range */
+ if (count >= MAX_ROW_BUFF_SIZE || count >= rows_buf->length) {
+ DBG_ERR("count %"PRIu64" either exceeds max buffer size "
+ "or buffer size (%zu)",
+ count, rows_buf->length);
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+
+ /* read address */
+ if (is_64bit) {
+ err = ndr_pull_udlong(ndr_pull,
+ flags,
+ &addr);
+ if (err) {
+ DBG_ERR("Failed to extract address\n");
+ goto out;
+ }
+ } else {
+ uint32_t addr_32;
+ err = ndr_pull_uint32(ndr_pull, flags, &addr_32);
+ if (err) {
+ DBG_ERR("Failed to extract address\n");
+ goto out;
+ }
+ addr = addr_32;
+ }
+
+ if ((addr - baseaddress) >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" outside buffer range "
+ "(buf len - %zu)\n",
+ addr - baseaddress,
+ rows_buf->length);
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+
+ vec_address = talloc_zero_array(ctx,
+ uint64_t, count);
+
+ if (vec_address == NULL) {
+ err = NDR_ERR_ALLOC;
+ goto out;
+ }
+
+ /*
+ * non vector case addr points to value
+ * otherwise addr points to list of addresses
+ * for the values in vector
+ */
+ if (is_vector == false) {
+ vec_address[0] = addr;
+ } else {
+ uint64_t array_offset = addr - baseaddress;
+ uint64_t i;
+ uint32_t intsize;
+
+ if (is_64bit) {
+ intsize = 8;
+ } else {
+ intsize = 4;
+ }
+
+ if (array_offset >= MAX_ROW_BUFF_SIZE
+ || array_offset >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" either exceeds max buf size "
+ "or buffer size (%zu)",
+ array_offset, rows_buf->length);
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+
+ /* addr points to a list of int32 or int64 addresses */
+ for (i = 0; i < count; i++) {
+ /*
+ * read the addresses of the vector elements
+ * note: we can safely convert the uint64_t
+ * values here to uint32_t values as
+ * we are sure they are within range
+ * due to previous checks above.
+ */
+ if (smb_buffer_oob((uint32_t)rows_buf->length,
+ (uint32_t)array_offset,
+ intsize)) {
+ DBG_ERR("offset %"PRIu64" will be outside "
+ "buffer range (buf len - %zu) after "
+ "reading %s address\n",
+ array_offset,
+ rows_buf->length,
+ is_64bit ? "64 bit" : "32 bit");
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+ if (is_64bit) {
+ vec_address[i] =
+ PULL_LE_I64(rows_buf->data,
+ array_offset);
+ } else {
+ vec_address[i] =
+ (uint32_t)PULL_LE_I32(rows_buf->data,
+ array_offset);
+ }
+ array_offset += intsize;
+ }
+ }
+ err = NDR_ERR_SUCCESS;
+ *pcount = count;
+ *pvec_address = vec_address;
+out:
+ return err;
+}
+
+static enum ndr_err_code extract_crowvariant_variable(TALLOC_CTX *ctx,
+ struct wsp_ctablevariant *tablevar,
+ bool is_64bit,
+ struct ndr_pull *ndr_pull,
+ ndr_flags_type flags,
+ uint64_t baseaddress,
+ DATA_BLOB *rows_buf,
+ uint32_t len,
+ struct wsp_cbasestoragevariant *val)
+{
+ enum ndr_err_code err;
+ bool is_vector = tablevar->vtype & VT_VECTOR;
+ uint64_t count = 0;
+
+ uint64_t *vec_address = NULL;
+ struct wsp_cbasestoragevariant **variant_array = NULL;
+ int i;
+
+
+ err = extract_variant_addresses(ctx,
+ tablevar,
+ is_64bit,
+ ndr_pull,
+ flags,
+ baseaddress,
+ rows_buf,
+ &count,
+ &vec_address);
+
+ if (err) {
+ DBG_ERR("Failed to extract address and/or count\n");
+ goto out;
+ }
+
+ variant_array = talloc_zero_array(ctx,
+ struct wsp_cbasestoragevariant*,
+ count);
+
+ if (variant_array == NULL) {
+ err = NDR_ERR_ALLOC;
+ goto out;
+ }
+
+ if (is_vector == false) {
+ variant_array[0] = val;
+ } else {
+ for (i = 0; i < count; i++) {
+ variant_array[i] = talloc_zero(ctx,
+ struct wsp_cbasestoragevariant);
+ if (variant_array[i] == NULL) {
+ err = NDR_ERR_ALLOC;
+ goto out;
+ }
+ }
+ }
+
+ for (i = 0; i < count; i++) {
+ uint32_t tmplen = len;
+ uint64_t buf_offset;
+ buf_offset = vec_address[i] - baseaddress;
+ if (buf_offset >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" outside buffer range "
+ "(buf len - %zu)\n",
+ buf_offset,
+ rows_buf->length);
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+ if (is_64bit
+ && (tablevar->vtype & ~(VT_VECTOR)) == VT_LPWSTR) {
+ /*
+ * we can't trust len if 64 bit mode
+ * (in 32 bit mode the length reported at len offset
+ * seem consistent and correct)
+ * So in this case instead of using the len
+ * at len offset we just use the full buffer
+ * from the point the value is stored at
+ * till the end of the buffer
+ */
+ tmplen = rows_buf->length - buf_offset;
+ }
+ if (!extract_rowbuf_variable_type(ctx,
+ tablevar->vtype & ~VT_VECTOR,
+ buf_offset,
+ rows_buf,
+ tmplen,
+ variant_array[i])) {
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+ }
+
+ if (is_vector) {
+ if (!convert_variant_array_to_vector(ctx,
+ count,
+ variant_array,
+ val)) {
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+ }
+ err = NDR_ERR_SUCCESS;
+out:
+ return err;
+}
+
+static enum ndr_err_code extract_crowvariant(TALLOC_CTX *ctx,
+ struct wsp_ctablevariant *tablevar,
+ bool is_64bit,
+ struct ndr_pull *ndr_pull,
+ ndr_flags_type flags,
+ uint64_t baseaddress,
+ DATA_BLOB *rows_buf, uint32_t len,
+ struct wsp_cbasestoragevariant *val)
+{
+ enum ndr_err_code err = NDR_ERR_SUCCESS;
+ bool is_vector = tablevar->vtype & VT_VECTOR;
+ bool is_array = tablevar->vtype & VT_ARRAY;
+
+ if (is_array) {
+ DBG_ERR("Not handling ARRAYs!!!\n");
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+
+ if (is_variable_size((tablevar->vtype & ~(VT_VECTOR)))) {
+ err = extract_crowvariant_variable(ctx,
+ tablevar,
+ is_64bit,
+ ndr_pull,
+ flags,
+ baseaddress,
+ rows_buf,
+ len,
+ val);
+
+ } else {
+ if (is_vector) {
+ DBG_ERR("Not handling VECTORs of fixed size values!!!\n");
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+ NDR_CHECK(ndr_pull_set_switch_value(ndr_pull,
+ &val->vvalue,
+ tablevar->vtype));
+ NDR_CHECK(ndr_pull_variant_types(ndr_pull, NDR_SCALARS, &val->vvalue));
+ val->vtype = tablevar->vtype;
+ }
+out:
+ return err;
+}
+
+static enum ndr_err_code process_columns(TALLOC_CTX *ctx,
+ bool is_64bit,
+ uint64_t baseaddress,
+ struct wsp_cpmsetbindingsin *bindingin,
+ DATA_BLOB *rows_buf,
+ uint32_t nrow,
+ struct wsp_cbasestoragevariant *cols)
+{
+ uint32_t i;
+ enum ndr_err_code err = NDR_ERR_SUCCESS;
+ struct ndr_pull *ndr_pull = NULL;
+ ndr_flags_type ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+ uint64_t nrow_offset = (uint64_t)nrow * bindingin->brow;
+
+ if (nrow_offset >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" outside buffer range (buf len - %zu)\n",
+ nrow_offset,
+ rows_buf->length);
+ err = NDR_ERR_ALLOC;
+ goto out;
+ }
+
+ /*
+ * process columns, column info is contained in cpmsetbindings
+ * for more information see 'Rows' description MS-WSP 2.2.4.1.2
+ * which describes how the server fills the buffer.
+ */
+ for (i = 0; i < bindingin->ccolumns; i++) {
+ struct wsp_ctablecolumn *tab_col = &bindingin->acolumns[i];
+ DATA_BLOB col_val_blob = data_blob_null;
+ uint64_t val_offset;
+ struct wsp_ctablevariant tablevariant = {0};
+ DBG_INFO("\nRow[%d]Col[%d] property %s type %s\n",nrow, i,
+ prop_from_fullprop(ctx, &tab_col->propspec),
+ get_vtype_name(tab_col->vtype));
+ if (tab_col->statusused) {
+ val_offset = nrow_offset + tab_col->statusoffset.value;
+ if (val_offset >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" outside buffer range "
+ "(buf len - %zu)\n",
+ val_offset,
+ rows_buf->length);
+ err = NDR_ERR_ALLOC;
+ goto out;
+ }
+ DBG_INFO("\n\tstatusoffset 0x%x status is %s\n",
+ tab_col->statusoffset.value,
+ get_store_status(
+ (uint8_t)*(rows_buf->data
+ + val_offset)));
+ }
+ if (tab_col->lengthused) {
+ val_offset = nrow_offset + tab_col->lengthoffset.value;
+ if (val_offset >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" outside buffer range "
+ "(buf len - %zu)\n",
+ val_offset,
+ rows_buf->length);
+ err = NDR_ERR_ALLOC;
+ goto out;
+ }
+ DBG_INFO("\n\tlen offset 0x%x value at length is 0x%x\n",
+ tab_col->lengthoffset.value,
+ PULL_LE_I32(rows_buf->data,
+ val_offset));
+ }
+ if (tab_col->valueused) {
+ uint32_t len = 0;
+ val_offset = nrow_offset + tab_col->valueoffset.value;
+ if (val_offset >= rows_buf->length) {
+ DBG_ERR("offset %"PRIu64" outside buffer range "
+ "(buf len - %zu)\n",
+ val_offset,
+ rows_buf->length);
+ err = NDR_ERR_ALLOC;
+ goto out;
+ }
+ DBG_INFO("\n\tvalueoffset:valuesize 0x%x:0x%x "
+ "crowvariant address = 0x%"PRIx64"\n",
+ tab_col->valueoffset.value,
+ tab_col->valuesize.value,
+ val_offset);
+
+ col_val_blob.data = rows_buf->data + val_offset;
+ col_val_blob.length = tab_col->valuesize.value;
+
+
+ if (tab_col->vtype != VT_VARIANT) {
+ DBG_ERR("Not handling non variant column "
+ "values\n");
+ err = NDR_ERR_VALIDATE;
+ goto out;
+ }
+ ndr_pull = ndr_pull_init_blob(&col_val_blob, ctx);
+ if (ndr_pull == NULL) {
+ err = NDR_ERR_ALLOC;
+ DBG_ERR("out of memory\n");
+ goto out;
+ }
+
+ err = ndr_pull_wsp_ctablevariant(ndr_pull,
+ ndr_flags,
+ &tablevariant);
+ if (err) {
+ DBG_ERR("!!! failed to pull fixed part of variant data for col data\n");
+ goto out;
+ }
+ DBG_INFO("\n");
+ DBG_INFO("\tcrowvariant contains %s \n",
+ get_vtype_name(tablevariant.vtype));
+
+ if (tab_col->lengthused) {
+ /*
+ * it seems the size is what's at
+ * lengthoffset - tab_col->valuesize.value
+ */
+ len = PULL_LE_I32(rows_buf->data,
+ nrow_offset
+ + tab_col->lengthoffset.value);
+ len = len - tab_col->valuesize.value;
+ }
+ err = extract_crowvariant(ctx,
+ &tablevariant,
+ is_64bit,
+ ndr_pull,
+ ndr_flags,
+ baseaddress,
+ rows_buf,
+ len,
+ &cols[i]);
+ }
+ }
+out:
+ return err;
+}
+
+/*
+ * extracts values from rows_buf into rowsarray
+ * based on the information in bindingsin
+ */
+enum ndr_err_code extract_rowsarray(
+ TALLOC_CTX * ctx,
+ DATA_BLOB *rows_buf,
+ bool is_64bit,
+ struct wsp_cpmsetbindingsin *bindingsin,
+ uint32_t cbreserved,
+ uint64_t baseaddress,
+ uint32_t rows,
+ struct wsp_cbasestoragevariant **rowsarray)
+{
+ uint32_t i;
+ enum ndr_err_code err = NDR_ERR_SUCCESS;
+ /*
+ * limit check the size of rows_buf
+ * see MS-WSP 2.2.3.11 which describes the size
+ * of the rows buffer MUST not exceed 0x0004000 bytes.
+ * This limit will ensure we can safely check
+ * limits based on uint32_t offsets
+ */
+
+ if (rows_buf->length > MAX_ROW_BUFF_SIZE) {
+ DBG_ERR("Buffer size 0x%zx exceeds 0x%x max buffer size\n",
+ rows_buf->length, MAX_ROW_BUFF_SIZE);
+ return NDR_ERR_BUFSIZE;
+ }
+
+ for (i = 0; i < rows; i++ ) {
+ struct wsp_cbasestoragevariant *cols =
+ talloc_zero_array(ctx,
+ struct wsp_cbasestoragevariant,
+ bindingsin->ccolumns);
+ uint64_t adjusted_address;
+ if (cols == NULL) {
+ return NDR_ERR_ALLOC;
+ }
+
+ /*
+ * cater for paddingrows (see MS-WSP 2.2.3.12)
+ * Rows buffer starts cbreserved bytes into messages
+ */
+ adjusted_address = baseaddress + cbreserved;
+
+ err = process_columns(ctx,
+ is_64bit,
+ adjusted_address,
+ bindingsin,
+ rows_buf,
+ i,
+ cols);
+ if (err) {
+ break;
+ }
+ rowsarray[i] = cols;
+ }
+ return err;
+}
+
+static bool process_query_node(TALLOC_CTX *ctx,
+ struct wsp_crestriction *crestriction,
+ t_query *node);
+
+static bool process_andornot_node(TALLOC_CTX *ctx,
+ struct wsp_crestriction *crestr,
+ t_query *node,
+ struct wsp_crestriction **left,
+ struct wsp_crestriction **right)
+{
+ struct wsp_cnoderestriction *restriction_node = NULL;
+
+ *left = NULL;
+ *right = NULL;
+
+ restriction_node =
+ &crestr->restriction.cnoderestriction;
+
+ crestr->weight = 1000;
+
+ if (node->type == eAND || node->type == eOR) {
+ if (node->type == eAND) {
+ crestr->ultype = RTAND;
+ } else {
+ crestr->ultype = RTOR;
+ }
+ if (!create_noderestriction(ctx, restriction_node, 2)) {
+ return false;
+ }
+ *left = &restriction_node->panode[0];
+ *right = &restriction_node->panode[1];
+ } else {
+ crestr->ultype = RTNOT;
+ crestr->restriction.restriction.restriction =
+ talloc_zero(ctx, struct wsp_crestriction);
+ if (crestr->restriction.restriction.restriction == NULL) {
+ DBG_ERR("out of memory\n");
+ return false;
+ }
+ crestr =
+ crestr->restriction.restriction.restriction;
+ }
+ if (*left == NULL) {
+ *left = crestr;
+ }
+ if (*right == NULL) {
+ *right = crestr;
+ }
+ return true;
+}
+
+static void process_value_node(TALLOC_CTX *ctx,
+ struct wsp_crestriction *crestriction,
+ t_query *node)
+{
+ *crestriction = *node->restriction;
+}
+
+static bool process_query_node(TALLOC_CTX *ctx,
+ struct wsp_crestriction *crestriction,
+ t_query *node)
+{
+ struct wsp_crestriction *left = NULL, *right = NULL;
+ if (node == NULL) {
+ return true;
+ }
+ switch (node->type) {
+ case eAND:
+ case eOR:
+ case eNOT:
+ if (!process_andornot_node(ctx, crestriction, node,
+ &left, &right)) {
+ return false;
+ }
+ break;
+ case eVALUE:
+ process_value_node(ctx, crestriction, node);
+ break;
+ default:
+ break;
+ }
+ if (!process_query_node(ctx, left, node->left)) {
+ return false;
+ }
+ if (!process_query_node(ctx, right, node->right)) {
+ return false;
+ }
+ return true;
+}
+
+bool create_querysearch_request(TALLOC_CTX * ctx,
+ struct wsp_request* request,
+ t_select_stmt *sql)
+{
+ uint32_t indices[sql->cols->num_cols];
+ uint32_t i;
+ uint32_t j;
+ struct wsp_cpmcreatequeryin *createquery =
+ &request->message.cpmcreatequery;
+
+ for (i = 0; i < sql->cols->num_cols; i++) {
+ indices[i] = i;
+ }
+
+ request->header.msg = CPMCREATEQUERY;
+ createquery->ccolumnsetpresent = 1;
+ createquery->columnset.columnset.count = sql->cols->num_cols;
+ if (!fill_uint32_vec(ctx, &createquery->columnset.columnset.indexes,
+ indices,
+ sql->cols->num_cols)) {
+ return false;
+ }
+
+ /* handle restrictions */
+ createquery->crestrictionpresent = 1;
+ createquery->restrictionarray.restrictionarray.count = 1;
+ createquery->restrictionarray.restrictionarray.ispresent = 1;
+
+ if (!create_restriction_array(ctx,
+ &createquery->restrictionarray.restrictionarray.restrictions,
+ createquery->restrictionarray.restrictionarray.count)) {
+ return false;
+ }
+
+
+ if (!process_query_node(ctx,
+ &createquery->restrictionarray.restrictionarray.restrictions[0],
+ sql->where)) {
+ return false;
+ }
+
+
+ /* handle rest */
+ createquery->csortsetpresent = 1;
+ if (createquery->csortsetpresent) {
+ /* sort on first column */
+ struct wsp_csort data[] = {
+ {0x00000000, 0x00000000, 0x00000000, WSP_DEFAULT_LCID},
+ };
+ struct wsp_csortset *sortset = NULL;
+ struct wsp_cingroupsortaggregsets *aggregsets = NULL;
+
+ aggregsets = &createquery->sortset.groupsortaggregsets;
+ aggregsets->ccount = 1;
+ aggregsets->sortsets =
+ talloc_zero_array(ctx,
+ struct wsp_cingroupsortaggregset,
+ aggregsets->ccount);
+ sortset = &aggregsets->sortsets[0].sortaggregset;
+ sortset->count = ARRAY_SIZE(data);
+ if (!fill_sortarray(ctx,
+ &sortset->sortarray,
+ data,sortset->count)) {
+ return false;
+ }
+ }
+
+ createquery->ccategorizationsetpresent = 0;
+
+ createquery->rowsetproperties.ubooleanoptions = 0x00000203;
+ createquery->rowsetproperties.ulmaxopenrows = 0x00000000;
+ createquery->rowsetproperties.ulmemoryusage = 0x00000000;
+ createquery->rowsetproperties.cmaxresults = 0x00000000;
+ createquery->rowsetproperties.ccmdtimeout = 0x00000005;
+
+ createquery->pidmapper.count = sql->cols->num_cols;
+ createquery->pidmapper.apropspec = talloc_zero_array(ctx,
+ struct wsp_cfullpropspec,
+ createquery->pidmapper.count);
+
+ if (createquery->pidmapper.apropspec == NULL) {
+ DBG_ERR("out of memory\n");
+ return false;
+ }
+
+ for(i = 0, j = 0; i < sql->cols->num_cols; i++) {
+ struct wsp_cfullpropspec *prop =
+ &createquery->pidmapper.apropspec[j];
+ char *propname = sql->cols->cols[i];
+ /*
+ * don't put RowID in pidmapper or windows will reject
+ * the query.
+ */
+ if (strequal(propname, "System.Search.RowID")) {
+ continue;
+ }
+ if (!set_fullpropspec(ctx,
+ prop, sql->cols->cols[i],
+ PRSPEC_PROPID)) {
+ DBG_ERR("Failed to handle property named %s\n",
+ sql->cols->cols[i]);
+ continue;
+ }
+ j++;
+ }
+ createquery->columnset.columnset.count = j;
+ createquery->pidmapper.count = j;
+ createquery->lcid = WSP_DEFAULT_LCID;
+ return true;
+}
+
+static int32_t getNextAddress(int32_t value_off,
+ int32_t status_off,
+ int32_t len_off,
+ int32_t max_value_size)
+{
+ return MAX(MAX(value_off + max_value_size, status_off + 1), len_off + 2);
+}
+
+static void create_binding_offsets(struct binding *binding, int no_cols,
+ int max_value_size)
+{
+ uint32_t buf_addr = 0x0;
+ uint32_t i;
+
+ uint32_t value_off = 0;
+ uint32_t len_off = 0;
+
+ /* initial state this will get incremented to the desired 0x2 */
+ uint32_t status_off = 0x1;
+ uint32_t avail = 0x4;
+ int status_remain = 0x2;
+ int len_remain = -1;
+
+ const static uint32_t WINDOW = 0x8;
+ const static uint32_t LEN_STAT_SIZE = 0x4;
+ for (i = 0; i < no_cols; i++) {
+ buf_addr = buf_addr + WINDOW;
+ value_off = buf_addr;
+
+ if (status_remain <= 0) {
+ if (avail) {
+ status_off = avail;
+ status_remain = LEN_STAT_SIZE;
+ avail = 0;
+ } else {
+ /*
+ * we prepare the address to allocate
+ * another block from here. It will
+ * be allocated automatically when we
+ * re-enter the loop
+ */
+ status_off = getNextAddress(value_off,
+ status_off,
+ len_off,
+ max_value_size) + WINDOW;
+ status_remain = LEN_STAT_SIZE;
+ buf_addr = status_off;
+ avail = buf_addr + LEN_STAT_SIZE;
+ }
+ } else {
+ status_off++;
+ buf_addr = getNextAddress(value_off,
+ status_off,
+ len_off,
+ max_value_size);
+ }
+
+ if (len_remain <= 0) {
+ if (avail) {
+ len_off = avail;
+ len_remain = LEN_STAT_SIZE;
+ avail = 0;
+ } else {
+ /*
+ * we prepare the address to allocate
+ * another block from here. It will
+ * be allocated automatically when we
+ * re-enter the loop
+ */
+ len_off = getNextAddress(value_off,
+ status_off,
+ len_off,
+ max_value_size) + WINDOW;
+ len_remain = LEN_STAT_SIZE;
+ buf_addr = len_off;
+ avail = buf_addr + LEN_STAT_SIZE;
+ }
+ } else {
+ len_off += 0x4;
+ buf_addr = getNextAddress(value_off,
+ status_off,
+ len_off,
+ max_value_size);
+ }
+ status_remain--;
+ len_remain -= LEN_STAT_SIZE;
+ binding[i].value_off = value_off;
+ binding[i].status_off = status_off;
+ binding[i].len_off = len_off;
+ }
+}
+
+static bool fill_bindings(TALLOC_CTX *ctx,
+ struct wsp_cpmsetbindingsin *bindingsin,
+ char **col_names,
+ bool is_64bit)
+{
+ uint32_t i;
+ struct binding *offsets = NULL;
+ uint32_t num_cols;
+ int maxvalue = is_64bit ? 0x18 : 0x10;
+
+ struct wsp_ctablecolumn *tablecols = bindingsin->acolumns;
+ bindingsin->brow = 0x0;
+ num_cols = bindingsin->ccolumns;
+
+ offsets = talloc_zero_array(ctx, struct binding, num_cols);
+
+ if (offsets == NULL) {
+ DBG_ERR("out of memory\n");
+ return false;
+ }
+
+ create_binding_offsets(offsets,
+ num_cols,
+ maxvalue);
+
+ for (i = 0; i < num_cols; i++) {
+ uint32_t max_off;
+ if (!set_ctablecolumn(ctx, &tablecols[i], col_names[i],
+ &offsets[i], maxvalue)) {
+ DBG_ERR("Failed to handle property named %s\n",
+ col_names[i]);
+ continue;
+ }
+ max_off = MAX(offsets[i].value_off + maxvalue,
+ offsets[i].status_off + 1);
+ max_off = MAX(max_off, offsets[i].len_off + 2);
+ if (max_off > bindingsin->brow) {
+ bindingsin->brow = max_off;
+ }
+ }
+ /* important */
+ bindingsin->brow += ndr_align_size(bindingsin->brow,4);
+ return true;
+}
+
+bool create_setbindings_request(TALLOC_CTX * ctx,
+ struct wsp_request* request,
+ t_select_stmt *sql,
+ uint32_t cursor,
+ bool is_64bit)
+{
+ struct wsp_cpmsetbindingsin *bindingsin =
+ &request->message.cpmsetbindings;
+
+ request->header.msg = CPMSETBINDINGSIN;
+ bindingsin->hcursor = cursor;
+ bindingsin->ccolumns = sql->cols->num_cols;
+
+ bindingsin->acolumns = talloc_zero_array(ctx,
+ struct wsp_ctablecolumn,
+ bindingsin->ccolumns);
+
+ if (bindingsin->acolumns == NULL) {
+ DBG_ERR("out of memory\n");
+ return false;
+ }
+
+ if (!fill_bindings(ctx, bindingsin, sql->cols->cols, is_64bit)) {
+ return false;
+ }
+
+ return true;
+}
+
+enum search_kind get_kind(const char* kind_str)
+{
+ enum search_kind result = UNKNOWN;
+ int i;
+ const static struct {
+ const char* str;
+ enum search_kind search_kind;
+ } kind_map[] = {
+ {"Calendar", CALENDAR},
+ {"Communication", COMMUNICATION},
+ {"Contact", CONTACT},
+ {"Document", DOCUMENT},
+ {"Email", EMAIL},
+ {"Feed", FEED},
+ {"Folder", FOLDER},
+ {"Game", GAME},
+ {"InstantMessage", INSTANTMESSAGE},
+ {"Journal", JOURNAL},
+ {"Link", LINK},
+ {"Movie", MOVIE},
+ {"Music", MUSIC},
+ {"Note", NOTE},
+ {"Picture", PICTURE},
+ {"Program", PROGRAM},
+ {"RecordedTV", RECORDEDTV},
+ {"SearchFolder", SEARCHFOLDER},
+ {"Task", TASK},
+ {"Video", VIDEO},
+ {"WebHistory", WEBHISTORY},
+ };
+ for (i = 0; i < ARRAY_SIZE(kind_map); i++) {
+ if (strequal(kind_str, kind_map[i].str)) {
+ result = kind_map[i].search_kind;
+ break;
+ }
+ }
+ return result;
+}
+
+struct wsp_client_ctx
+{
+ struct rpc_pipe_client *rpccli;
+ struct cli_state *cli_state;
+ struct dcerpc_binding_handle *h;
+};
+
+static NTSTATUS wsp_resp_pdu_complete(struct tstream_context *stream,
+ void *private_data,
+ DATA_BLOB blob,
+ size_t *packet_size)
+{
+ ssize_t to_read;
+
+ to_read = tstream_pending_bytes(stream);
+ if (to_read == -1) {
+ return NT_STATUS_IO_DEVICE_ERROR;
+ }
+
+ if (to_read > 0) {
+ *packet_size = blob.length + to_read;
+ return STATUS_MORE_ENTRIES;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS wsp_server_connect(TALLOC_CTX *mem_ctx,
+ const char *servername,
+ struct tevent_context *ev_ctx,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *credentials,
+ struct cli_state *cli,
+ struct wsp_client_ctx **wsp_ctx)
+{
+ struct wsp_client_ctx *ctx = NULL;
+ struct dcerpc_binding_handle *h = NULL;
+ struct tstream_context *stream = NULL;
+ NTSTATUS status;
+
+ bool smb2_or_greater =
+ (lpcfg_client_max_protocol(lp_ctx) >= PROTOCOL_SMB2_02);
+
+ if (!smb2_or_greater) {
+ return NT_STATUS_PROTOCOL_NOT_SUPPORTED;
+ }
+
+ ctx = talloc_zero(mem_ctx, struct wsp_client_ctx);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ctx->cli_state = cli;
+
+
+ status = smb2cli_ioctl_pipe_wait(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "MsFteWds",
+ 1);
+
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("wait for pipe failed: %s)\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = rpc_pipe_open_np(cli,
+ &ndr_table_msftewds,
+ &ctx->rpccli);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to int the pipe)\n");
+ return status;
+ }
+
+ stream = rpc_transport_get_tstream(ctx->rpccli->transport);
+ h = tstream_binding_handle_create(ctx->rpccli,
+ NULL,
+ &stream,
+ MSG_HDR_SIZE,
+ wsp_resp_pdu_complete,
+ ctx, 42280);
+
+ if (!h) {
+ DBG_ERR("failed to create the pipe handle)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ctx->rpccli->binding_handle = h;
+ *wsp_ctx = ctx;
+
+ return status;
+}
+
+static NTSTATUS write_something(TALLOC_CTX* ctx,
+ struct rpc_pipe_client *p,
+ DATA_BLOB *blob_in,
+ DATA_BLOB *blob_out)
+{
+ uint32_t outflags;
+ struct dcerpc_binding_handle *handle = p->binding_handle;
+ NTSTATUS status;
+
+ status = dcerpc_binding_handle_raw_call(handle,
+ NULL,
+ 0,
+ 0,
+ blob_in->data,
+ blob_in->length,
+ ctx,
+ &blob_out->data,
+ &blob_out->length,
+ &outflags);
+ return status;
+}
+
+/* msg is expected to be created on the heap with talloc */
+static enum ndr_err_code parse_blob(TALLOC_CTX *ctx, DATA_BLOB *blob,
+ struct wsp_request *request,
+ struct wsp_response *response,
+ DATA_BLOB *unread)
+{
+ struct ndr_pull *ndr = NULL;
+ enum ndr_err_code err;
+ ndr_flags_type ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+ uint32_t status = 0;
+
+ ndr = ndr_pull_init_blob(blob, ctx);
+
+ if (ndr == NULL) {
+ return NDR_ERR_ALLOC;
+ }
+
+ /* peek at the status */
+ status = PULL_LE_I32(blob->data, 4);
+
+ /* is hard error ?*/
+ if (status & 0x80000000 && blob->length == MSG_HDR_SIZE) {
+ /* just pull the header */
+ err = ndr_pull_wsp_header(ndr, ndr_flags, &response->header);
+ DBG_ERR("error: %s\n", nt_errstr(NT_STATUS(status)));
+ goto out;
+ }
+ err = ndr_pull_wsp_response(ndr, ndr_flags, response);
+ if (err) {
+ DBG_ERR("Failed to pull header from response blob error %d\n", err);
+ goto out;
+ }
+ if (DEBUGLEVEL >=6) {
+ NDR_PRINT_DEBUG(wsp_response, response);
+ }
+ if (response->header.msg == CPMGETROWS) {
+ if (request) {
+ /* point to rows buffer */
+ struct wsp_cpmgetrowsin *getrows =
+ &request->message.cpmgetrows;
+ ndr->offset = getrows->cbreserved;
+ }
+ }
+
+ if (ndr->offset < blob->length) {
+ int bytes = blob->length - ndr->offset;
+ *unread = data_blob_named(blob->data + ndr->offset,
+ bytes, "UNREAD");
+ DBG_WARNING("\nThere are unprocessed bytes (len 0x%x) "
+ "at end of message\n", bytes);
+ }
+
+out:
+ return err;
+}
+
+static void set_msg_checksum(DATA_BLOB *blob, struct wsp_header *hdr)
+{
+ /* point at payload */
+ uint32_t i;
+ uint8_t *buffer = blob->data + MSG_HDR_SIZE;
+ uint32_t buf_size = blob->length - MSG_HDR_SIZE;
+ uint32_t nwords = buf_size/4;
+ uint32_t offset = 0;
+ uint32_t checksum = 0;
+
+ static const uint32_t xor_const = 0x59533959;
+ for(i = 0; i < nwords; i++) {
+ checksum += PULL_LE_I32(buffer, offset);
+ offset += 4;
+ }
+
+ checksum ^= xor_const;
+ checksum -= hdr->msg;
+ hdr->checksum = checksum;
+}
+
+static enum ndr_err_code insert_header_and_checksum(TALLOC_CTX *ctx,
+ DATA_BLOB* blob,
+ struct wsp_header *header)
+{
+ enum ndr_err_code err;
+ ndr_flags_type ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+ struct ndr_push *header_ndr = ndr_push_init_ctx(ctx);
+
+ if (header_ndr == NULL) {
+ return NDR_ERR_ALLOC;
+ }
+
+ if (header->msg == CPMCONNECT
+ || header->msg == CPMCREATEQUERY
+ || header->msg == CPMSETBINDINGSIN
+ || header->msg == CPMGETROWS
+ || header->msg == CPMFETCHVALUE) {
+
+ set_msg_checksum(blob, header);
+ }
+ err = ndr_push_wsp_header(header_ndr, ndr_flags, header);
+ if (err) {
+ DBG_ERR("Failed to push header, error %d\n", err);
+ return err;
+ }
+ memcpy(blob->data, header_ndr->data, MSG_HDR_SIZE);
+ return err;
+}
+
+NTSTATUS wsp_request_response(TALLOC_CTX* ctx,
+ struct wsp_client_ctx *wsp_ctx,
+ struct wsp_request* request,
+ struct wsp_response *response,
+ DATA_BLOB *unread)
+{
+ struct rpc_pipe_client *p = wsp_ctx->rpccli;
+ NTSTATUS status = NT_STATUS_OK;
+
+ ndr_flags_type ndr_flags = NDR_SCALARS | NDR_BUFFERS;
+ struct ndr_push* push_ndr = NULL;
+
+ enum ndr_err_code err;
+
+ DATA_BLOB req_blob;
+ DATA_BLOB resp_blob;
+
+ ZERO_STRUCT(req_blob);
+ ZERO_STRUCT(resp_blob);
+
+ push_ndr = ndr_push_init_ctx(ctx);
+ if (push_ndr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* write message payload first */
+ push_ndr->offset = MSG_HDR_SIZE;
+ DBG_INFO("\n");
+
+ switch(request->header.msg) {
+ case CPMCONNECT:
+ {
+ struct wsp_cpmconnectin *connectin =
+ &request->message.cpmconnect;
+ err = ndr_push_wsp_cpmconnectin(push_ndr, ndr_flags,
+ connectin);
+ break;
+ }
+ case CPMCREATEQUERY:
+ {
+ struct wsp_cpmcreatequeryin* createquery =
+ &request->message.cpmcreatequery;
+ err = ndr_push_wsp_cpmcreatequeryin(push_ndr,
+ ndr_flags,
+ createquery);
+ req_blob = ndr_push_blob(push_ndr);
+ /* we need to set cpmcreatequery.size */
+ createquery->size =
+ req_blob.length - MSG_HDR_SIZE;
+ PUSH_LE_U32(req_blob.data, MSG_HDR_SIZE,
+ createquery->size);
+
+ break;
+ }
+ case CPMSETBINDINGSIN:
+ {
+ struct wsp_cpmsetbindingsin *bindingsin =
+ &request->message.cpmsetbindings;
+ err = ndr_push_wsp_cpmsetbindingsin(push_ndr, ndr_flags,
+ bindingsin);
+ req_blob = ndr_push_blob(push_ndr);
+ /* we need to set cpmsetbindings.bbindingdesc (size) */
+ bindingsin->bbindingdesc =
+ req_blob.length - MSG_HDR_SIZE - 16;
+ PUSH_LE_U32(req_blob.data, MSG_HDR_SIZE + 8,
+ bindingsin->bbindingdesc);
+ break;
+ }
+ case CPMGETROWS:
+ {
+ struct wsp_cpmgetrowsin *getrows =
+ &request->message.cpmgetrows;
+ err = ndr_push_wsp_cpmgetrowsin(push_ndr, ndr_flags,
+ getrows);
+ req_blob = ndr_push_blob(push_ndr);
+ getrows->cbseek = req_blob.length - MSG_HDR_SIZE - 32;
+ /* we need to set cpmgetrowsin.cbseek (size) */
+ PUSH_LE_U32(req_blob.data, MSG_HDR_SIZE + 12,
+ getrows->cbseek);
+ PUSH_LE_U32(req_blob.data, MSG_HDR_SIZE + 16,
+ getrows->cbreserved);
+ break;
+ }
+ case CPMGETQUERYSTATUS:
+ {
+ struct wsp_cpmgetquerystatusin *querystatus =
+ &request->message.cpmgetquerystatus;
+ err = ndr_push_wsp_cpmgetquerystatusin(
+ push_ndr,
+ ndr_flags,
+ querystatus);
+ break;
+ }
+ case CPMGETQUERYSTATUSEX:
+ {
+ struct wsp_cpmgetquerystatusexin *statusexin =
+ &request->message.cpmgetquerystatusex;
+ err = ndr_push_wsp_cpmgetquerystatusexin(
+ push_ndr,
+ ndr_flags,
+ statusexin);
+ break;
+ }
+ case CPMFREECURSOR:
+ {
+ struct wsp_cpmfreecursorin *freecursor =
+ &request->message.cpmfreecursor;
+ err = ndr_push_wsp_cpmfreecursorin(
+ push_ndr,
+ ndr_flags,
+ freecursor);
+ break;
+ }
+ case CPMFETCHVALUE:
+ {
+ struct wsp_cpmfetchvaluein *fetchvalue =
+ &request->message.cpmfetchvalue;
+ err = ndr_push_wsp_cpmfetchvaluein(
+ push_ndr,
+ ndr_flags,
+ fetchvalue);
+ break;
+ }
+
+ case CPMGETAPPROXIMATEPOSITION:
+ {
+ struct wsp_cpmgetapproximatepositionin *position =
+ &request->message.getapproximateposition;
+ err = ndr_push_wsp_cpmgetapproximatepositionin(
+ push_ndr,
+ ndr_flags,
+ position);
+ break;
+ }
+ default:
+ status = NT_STATUS_MESSAGE_NOT_FOUND;
+ goto out;
+ break;
+ }
+ if (err) {
+ DBG_ERR("failed to serialise message! (%d)\n", err);
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ if (!req_blob.data) {
+ req_blob = ndr_push_blob(push_ndr);
+ }
+ err = insert_header_and_checksum(ctx, &req_blob, &request->header);
+
+ DBG_NOTICE("\nsending raw message from client len %d\n", (int)req_blob.length);
+ DBG_NOTICE("\nsending raw message from client\n");
+ DBG_NOTICE( "===============================\n");
+
+ dump_data(5, req_blob.data, req_blob.length);
+
+ status = write_something(ctx, p, &req_blob, &resp_blob);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to write message\n");
+ goto out;
+ }
+ DBG_NOTICE("\nraw response from server\n");
+ DBG_NOTICE( "========================\n");
+ dump_data(5, resp_blob.data, resp_blob.length);
+
+ err = parse_blob(ctx,
+ &resp_blob,
+ request,
+ response,
+ unread);
+ if (err) {
+ DBG_ERR("Failed to parse response error %d\n", err);
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ DBG_NOTICE("response status is 0x%x\n", response->header.status);
+ /* propagate error status to return status */
+ if (response->header.status & 0x80000000) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+out:
+ return status;
+}
+
+struct dcerpc_binding_handle* get_wsp_pipe(struct wsp_client_ctx *ctx)
+{
+ return ctx->rpccli->binding_handle;
+}
diff --git a/source3/rpc_client/wsp_cli.h b/source3/rpc_client/wsp_cli.h
new file mode 100644
index 0000000..3159bd2
--- /dev/null
+++ b/source3/rpc_client/wsp_cli.h
@@ -0,0 +1,114 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Window Search Service
+ *
+ * Copyright (c) Noel Power
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __LIBCLI_WSP_WSP_CLI
+#define __LIBCLI_WSP_WSP_CLI
+
+#include "libcli/wsp/wsp_aqs.h"
+
+enum search_kind {
+ CALENDAR,
+ COMMUNICATION,
+ CONTACT,
+ DOCUMENT,
+ EMAIL,
+ FEED,
+ FOLDER,
+ GAME,
+ INSTANTMESSAGE,
+ JOURNAL,
+ LINK,
+ MOVIE,
+ MUSIC,
+ NOTE,
+ PICTURE,
+ PROGRAM,
+ RECORDEDTV,
+ SEARCHFOLDER,
+ TASK,
+ VIDEO,
+ WEBHISTORY,
+ NONE,
+ UNKNOWN,
+};
+
+enum search_kind get_kind(const char* kind_str);
+
+struct wsp_cpmcreatequeryin;
+struct wsp_cpmsetbindingsin;
+struct wsp_cpmgetrowsin;
+struct dcerpc_binding_handle;
+
+bool init_connectin_request(TALLOC_CTX *ctx,
+ struct wsp_request* request,
+ const char* clientmachine,
+ const char* clientuser,
+ const char* server);
+
+bool create_querysearch_request(TALLOC_CTX * ctx,
+ struct wsp_request* request,
+ t_select_stmt *sql);
+
+bool create_setbindings_request(TALLOC_CTX * ctx,
+ struct wsp_request* request,
+ t_select_stmt *sql,
+ uint32_t cursor,
+ bool is_64bit);
+
+void create_seekat_getrows_request(TALLOC_CTX * ctx,
+ struct wsp_request* request,
+ uint32_t cursor,
+ uint32_t bookmark,
+ uint32_t skip,
+ uint32_t rows,
+ uint32_t cbreserved,
+ uint32_t ulclientbase,
+ uint32_t cbrowwidth,
+ uint32_t fbwdfetch);
+
+enum ndr_err_code extract_rowsarray(TALLOC_CTX * ctx,
+ DATA_BLOB *rows_buf,
+ bool is_64bit,
+ struct wsp_cpmsetbindingsin *bindingsin,
+ uint32_t cbreserved,
+ uint64_t baseaddress,
+ uint32_t rows,
+ struct wsp_cbasestoragevariant **rowsarray);
+
+struct wsp_client_ctx;
+struct cli_credentials;
+
+NTSTATUS wsp_server_connect(TALLOC_CTX *mem_ctx,
+ const char *servername,
+ struct tevent_context *ev_ctx,
+ struct loadparm_context *lp_ctx,
+ struct cli_credentials *credential,
+ struct cli_state *cli,
+ struct wsp_client_ctx **ctx);
+
+/* simple sync api */
+NTSTATUS wsp_request_response(TALLOC_CTX* ctx,
+ struct wsp_client_ctx *wsp_ctx,
+ struct wsp_request* request,
+ struct wsp_response *response,
+ DATA_BLOB *unread);
+
+struct dcerpc_binding_handle* get_wsp_pipe(struct wsp_client_ctx *ctx);
+#endif
diff --git a/source3/rpc_server/dfs/srv_dfs_nt.c b/source3/rpc_server/dfs/srv_dfs_nt.c
new file mode 100644
index 0000000..8eaa59a
--- /dev/null
+++ b/source3/rpc_server/dfs/srv_dfs_nt.c
@@ -0,0 +1,606 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines for Dfs
+ * Copyright (C) Shirish Kalele 2000.
+ * Copyright (C) Jeremy Allison 2001-2007.
+ * Copyright (C) Jelmer Vernooij 2005-2006.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This is the implementation of the dfs pipe. */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_dfs.h"
+#include "librpc/gen_ndr/ndr_dfs_scompat.h"
+#include "msdfs.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "auth.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_MSDFS
+
+/* This function does not return a WERROR or NTSTATUS code but rather 1 if
+ dfs exists, or 0 otherwise. */
+
+void _dfs_GetManagerVersion(struct pipes_struct *p, struct dfs_GetManagerVersion *r)
+{
+ if (lp_host_msdfs()) {
+ *r->out.version = DFS_MANAGER_VERSION_NT4;
+ } else {
+ *r->out.version = (enum dfs_ManagerVersion)0;
+ }
+}
+
+WERROR _dfs_Add(struct pipes_struct *p, struct dfs_Add *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct junction_map *jn = NULL;
+ struct referral *old_referral_list = NULL;
+ bool self_ref = False;
+ size_t consumedcnt = 0;
+ char *altpath = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+ const char *pathnamep = r->in.path;
+
+ if (session_info->unix_token->uid != sec_initial_uid()) {
+ DEBUG(10,("_dfs_add: uid != 0. Access denied.\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ jn = talloc_zero(ctx, struct junction_map);
+ if (!jn) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ DEBUG(5,("init_reply_dfs_add: Request to add %s -> %s\\%s.\n",
+ r->in.path, r->in.server, r->in.share));
+
+ altpath = talloc_asprintf(ctx, "%s\\%s",
+ r->in.server,
+ r->in.share);
+ if (!altpath) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ while (IS_DIRECTORY_SEP(pathnamep[0]) &&
+ IS_DIRECTORY_SEP(pathnamep[1])) {
+ pathnamep++;
+ }
+
+ /* The following call can change the cwd. */
+ status = get_referred_path(ctx,
+ session_info,
+ pathnamep,
+ remote_address,
+ local_address,
+ jn, &consumedcnt, &self_ref);
+ if(!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ jn->referral_count += 1;
+ old_referral_list = jn->referral_list;
+
+ if (jn->referral_count < 1) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ jn->referral_list = talloc_array(ctx, struct referral, jn->referral_count);
+ if(jn->referral_list == NULL) {
+ DEBUG(0,("init_reply_dfs_add: talloc failed for referral list!\n"));
+ return WERR_NERR_DFSINTERNALERROR;
+ }
+
+ if(old_referral_list && jn->referral_list) {
+ memcpy(jn->referral_list, old_referral_list,
+ sizeof(struct referral)*jn->referral_count-1);
+ }
+
+ jn->referral_list[jn->referral_count-1].proximity = 0;
+ jn->referral_list[jn->referral_count-1].ttl = REFERRAL_TTL;
+ jn->referral_list[jn->referral_count-1].alternate_path = altpath;
+
+ if (!create_msdfs_link(jn, session_info)) {
+ return WERR_NERR_DFSCANTCREATEJUNCTIONPOINT;
+ }
+
+ return WERR_OK;
+}
+
+WERROR _dfs_Remove(struct pipes_struct *p, struct dfs_Remove *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct junction_map *jn = NULL;
+ bool self_ref = False;
+ size_t consumedcnt = 0;
+ bool found = False;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *altpath = NULL;
+ NTSTATUS status;
+ const char *pathnamep = r->in.dfs_entry_path;
+
+ if (session_info->unix_token->uid != sec_initial_uid()) {
+ DEBUG(10,("_dfs_remove: uid != 0. Access denied.\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ jn = talloc_zero(ctx, struct junction_map);
+ if (!jn) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (r->in.servername && r->in.sharename) {
+ altpath = talloc_asprintf(ctx, "%s\\%s",
+ r->in.servername,
+ r->in.sharename);
+ if (!altpath) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (!strlower_m(altpath)) {
+ return WERR_INVALID_PARAMETER;
+ }
+ DEBUG(5,("init_reply_dfs_remove: Request to remove %s -> %s\\%s.\n",
+ r->in.dfs_entry_path, r->in.servername, r->in.sharename));
+ }
+
+ while (IS_DIRECTORY_SEP(pathnamep[0]) &&
+ IS_DIRECTORY_SEP(pathnamep[1])) {
+ pathnamep++;
+ }
+
+ status = get_referred_path(ctx,
+ session_info,
+ pathnamep,
+ remote_address,
+ local_address,
+ jn, &consumedcnt, &self_ref);
+ if(!NT_STATUS_IS_OK(status)) {
+ return WERR_NERR_DFSNOSUCHVOLUME;
+ }
+
+ /* if no server-share pair given, remove the msdfs link completely */
+ if(!r->in.servername && !r->in.sharename) {
+ if(!remove_msdfs_link(jn, session_info)) {
+ return WERR_NERR_DFSNOSUCHVOLUME;
+ }
+ } else {
+ size_t i = 0;
+ /* compare each referral in the list with the one to remove */
+ DBG_DEBUG("altpath: .%s. refcnt: %zu\n",
+ altpath,
+ jn->referral_count);
+ for(i=0;i<jn->referral_count;i++) {
+ char *refpath = talloc_strdup(ctx,
+ jn->referral_list[i].alternate_path);
+ if (!refpath) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ trim_char(refpath, '\\', '\\');
+ DEBUG(10,("_dfs_remove: refpath: .%s.\n", refpath));
+ if(strequal(refpath, altpath)) {
+ *(jn->referral_list[i].alternate_path)='\0';
+ DEBUG(10,("_dfs_remove: Removal request matches referral %s\n",
+ refpath));
+ found = True;
+ }
+ }
+
+ if(!found) {
+ return WERR_NERR_DFSNOSUCHSHARE;
+ }
+
+ /* Only one referral, remove it */
+ if(jn->referral_count == 1) {
+ if(!remove_msdfs_link(jn, session_info)) {
+ return WERR_NERR_DFSNOSUCHVOLUME;
+ }
+ } else {
+ if(!create_msdfs_link(jn, session_info)) {
+ return WERR_NERR_DFSCANTCREATEJUNCTIONPOINT;
+ }
+ }
+ }
+
+ return WERR_OK;
+}
+
+static bool init_reply_dfs_info_1(TALLOC_CTX *mem_ctx, struct junction_map* j,struct dfs_Info1* dfs1)
+{
+ dfs1->path = talloc_asprintf(mem_ctx,
+ "\\\\%s\\%s\\%s", lp_netbios_name(),
+ j->service_name, j->volume_name);
+ if (dfs1->path == NULL)
+ return False;
+
+ DEBUG(5,("init_reply_dfs_info_1: initing entrypath: %s\n",dfs1->path));
+ return True;
+}
+
+static bool init_reply_dfs_info_2(TALLOC_CTX *mem_ctx, struct junction_map* j, struct dfs_Info2* dfs2)
+{
+ dfs2->path = talloc_asprintf(mem_ctx,
+ "\\\\%s\\%s\\%s", lp_netbios_name(), j->service_name, j->volume_name);
+ if (dfs2->path == NULL)
+ return False;
+ dfs2->comment = talloc_strdup(mem_ctx, j->comment);
+ dfs2->state = 1; /* set up state of dfs junction as OK */
+ dfs2->num_stores = j->referral_count;
+ return True;
+}
+
+static bool init_reply_dfs_info_3(TALLOC_CTX *mem_ctx, struct junction_map* j, struct dfs_Info3* dfs3)
+{
+ size_t ii;
+ if (j->volume_name[0] == '\0')
+ dfs3->path = talloc_asprintf(mem_ctx, "\\\\%s\\%s",
+ lp_netbios_name(), j->service_name);
+ else
+ dfs3->path = talloc_asprintf(mem_ctx, "\\\\%s\\%s\\%s", lp_netbios_name(),
+ j->service_name, j->volume_name);
+
+ if (dfs3->path == NULL)
+ return False;
+
+ dfs3->comment = talloc_strdup(mem_ctx, j->comment);
+ dfs3->state = 1;
+ dfs3->num_stores = j->referral_count;
+
+ /* also enumerate the stores */
+ if (j->referral_count) {
+ dfs3->stores = talloc_array(mem_ctx, struct dfs_StorageInfo, j->referral_count);
+ if (!dfs3->stores)
+ return False;
+ memset(dfs3->stores, '\0', j->referral_count * sizeof(struct dfs_StorageInfo));
+ } else {
+ dfs3->stores = NULL;
+ }
+
+ for(ii=0;ii<j->referral_count;ii++) {
+ char* p;
+ char *path = NULL;
+ struct dfs_StorageInfo* stor = &(dfs3->stores[ii]);
+ struct referral* ref = &(j->referral_list[ii]);
+
+ path = talloc_strdup(mem_ctx, ref->alternate_path);
+ if (!path) {
+ return False;
+ }
+ trim_char(path,'\\','\0');
+ p = strrchr_m(path,'\\');
+ if(p==NULL) {
+ DEBUG(4,("init_reply_dfs_info_3: invalid path: no \\ found in %s\n",path));
+ continue;
+ }
+ *p = '\0';
+ DBG_INFO("storage %zu: %s.%s\n",ii,path,p+1);
+ stor->state = 2; /* set all stores as ONLINE */
+ stor->server = talloc_strdup(mem_ctx, path);
+ stor->share = talloc_strdup(mem_ctx, p+1);
+ }
+ return True;
+}
+
+static bool init_reply_dfs_info_100(TALLOC_CTX *mem_ctx, struct junction_map* j, struct dfs_Info100* dfs100)
+{
+ dfs100->comment = talloc_strdup(mem_ctx, j->comment);
+ return True;
+}
+
+WERROR _dfs_Enum(struct pipes_struct *p, struct dfs_Enum *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct junction_map *jn = NULL;
+ size_t num_jn = 0;
+ size_t i;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ jn = enum_msdfs_links(ctx, session_info, &num_jn);
+ if (!jn || num_jn == 0) {
+ num_jn = 0;
+ jn = NULL;
+ }
+
+ DEBUG(5,("_dfs_Enum: %u junctions found in Dfs, doing level %d\n",
+ (unsigned int)num_jn, r->in.level));
+
+ *r->out.total = num_jn;
+
+ /* Create the return array */
+ switch (r->in.level) {
+ case 1:
+ if (num_jn) {
+ if ((r->out.info->e.info1->s = talloc_array(ctx, struct dfs_Info1, num_jn)) == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ r->out.info->e.info1->s = NULL;
+ }
+ r->out.info->e.info1->count = num_jn;
+ break;
+ case 2:
+ if (num_jn) {
+ if ((r->out.info->e.info2->s = talloc_array(ctx, struct dfs_Info2, num_jn)) == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ r->out.info->e.info2->s = NULL;
+ }
+ r->out.info->e.info2->count = num_jn;
+ break;
+ case 3:
+ if (num_jn) {
+ if ((r->out.info->e.info3->s = talloc_array(ctx, struct dfs_Info3, num_jn)) == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ r->out.info->e.info3->s = NULL;
+ }
+ r->out.info->e.info3->count = num_jn;
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < num_jn; i++) {
+ switch (r->in.level) {
+ case 1:
+ init_reply_dfs_info_1(ctx, &jn[i], &r->out.info->e.info1->s[i]);
+ break;
+ case 2:
+ init_reply_dfs_info_2(ctx, &jn[i], &r->out.info->e.info2->s[i]);
+ break;
+ case 3:
+ init_reply_dfs_info_3(ctx, &jn[i], &r->out.info->e.info3->s[i]);
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+ }
+
+ return WERR_OK;
+}
+
+WERROR _dfs_GetInfo(struct pipes_struct *p, struct dfs_GetInfo *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ size_t consumedcnt = 0;
+ struct junction_map *jn = NULL;
+ bool self_ref = False;
+ TALLOC_CTX *ctx = talloc_tos();
+ bool ret;
+ NTSTATUS status;
+ const char *pathnamep = r->in.dfs_entry_path;
+
+ jn = talloc_zero(ctx, struct junction_map);
+ if (!jn) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ while (IS_DIRECTORY_SEP(pathnamep[0]) &&
+ IS_DIRECTORY_SEP(pathnamep[1])) {
+ pathnamep++;
+ }
+
+ ret = create_junction(ctx, pathnamep, jn);
+ if (!ret) {
+ return WERR_NERR_DFSNOSUCHSERVER;
+ }
+
+ /* The following call can change the cwd. */
+ status = get_referred_path(ctx,
+ session_info,
+ pathnamep,
+ remote_address,
+ local_address,
+ jn, &consumedcnt, &self_ref);
+ if(!NT_STATUS_IS_OK(status) || consumedcnt < strlen(pathnamep)) {
+ return WERR_NERR_DFSNOSUCHVOLUME;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ r->out.info->info1 = talloc_zero(ctx,struct dfs_Info1);
+ if (!r->out.info->info1) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ret = init_reply_dfs_info_1(ctx, jn, r->out.info->info1);
+ break;
+ case 2:
+ r->out.info->info2 = talloc_zero(ctx,struct dfs_Info2);
+ if (!r->out.info->info2) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ret = init_reply_dfs_info_2(ctx, jn, r->out.info->info2);
+ break;
+ case 3:
+ r->out.info->info3 = talloc_zero(ctx,struct dfs_Info3);
+ if (!r->out.info->info3) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ret = init_reply_dfs_info_3(ctx, jn, r->out.info->info3);
+ break;
+ case 100:
+ r->out.info->info100 = talloc_zero(ctx,struct dfs_Info100);
+ if (!r->out.info->info100) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ ret = init_reply_dfs_info_100(ctx, jn, r->out.info->info100);
+ break;
+ default:
+ r->out.info->info1 = NULL;
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!ret)
+ return WERR_INVALID_PARAMETER;
+
+ return WERR_OK;
+}
+
+WERROR _dfs_SetInfo(struct pipes_struct *p, struct dfs_SetInfo *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_Rename(struct pipes_struct *p, struct dfs_Rename *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_Move(struct pipes_struct *p, struct dfs_Move *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_ManagerGetConfigInfo(struct pipes_struct *p, struct dfs_ManagerGetConfigInfo *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_ManagerSendSiteInfo(struct pipes_struct *p, struct dfs_ManagerSendSiteInfo *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_AddFtRoot(struct pipes_struct *p, struct dfs_AddFtRoot *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_RemoveFtRoot(struct pipes_struct *p, struct dfs_RemoveFtRoot *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_AddStdRoot(struct pipes_struct *p, struct dfs_AddStdRoot *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_RemoveStdRoot(struct pipes_struct *p, struct dfs_RemoveStdRoot *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_ManagerInitialize(struct pipes_struct *p, struct dfs_ManagerInitialize *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_AddStdRootForced(struct pipes_struct *p, struct dfs_AddStdRootForced *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_GetDcAddress(struct pipes_struct *p, struct dfs_GetDcAddress *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_SetDcAddress(struct pipes_struct *p, struct dfs_SetDcAddress *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_FlushFtTable(struct pipes_struct *p, struct dfs_FlushFtTable *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_Add2(struct pipes_struct *p, struct dfs_Add2 *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_Remove2(struct pipes_struct *p, struct dfs_Remove2 *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_EnumEx(struct pipes_struct *p, struct dfs_EnumEx *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _dfs_SetInfo2(struct pipes_struct *p, struct dfs_SetInfo2 *r)
+{
+ /* FIXME: Implement your code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_dfs_scompat.c"
diff --git a/source3/rpc_server/dssetup/srv_dssetup_nt.c b/source3/rpc_server/dssetup/srv_dssetup_nt.c
new file mode 100644
index 0000000..932452b
--- /dev/null
+++ b/source3/rpc_server/dssetup/srv_dssetup_nt.c
@@ -0,0 +1,232 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997.
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997.
+ * Copyright (C) Paul Ashton 1997.
+ * Copyright (C) Jeremy Allison 2001.
+ * Copyright (C) Gerald Carter 2002.
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/gen_ndr/ndr_dssetup.h"
+#include "librpc/gen_ndr/ndr_dssetup_scompat.h"
+#include "secrets.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+/********************************************************************
+ Fill in a dssetup_DsRolePrimaryDomInfoBasic structure
+ ********************************************************************/
+
+static WERROR fill_dsrole_dominfo_basic(TALLOC_CTX *ctx,
+ struct dssetup_DsRolePrimaryDomInfoBasic **info)
+{
+ struct dssetup_DsRolePrimaryDomInfoBasic *basic = NULL;
+ char *dnsdomain = NULL;
+
+ DEBUG(10,("fill_dsrole_dominfo_basic: enter\n"));
+
+ basic = talloc_zero(ctx, struct dssetup_DsRolePrimaryDomInfoBasic);
+ if (!basic) {
+ DEBUG(0,("fill_dsrole_dominfo_basic: out of memory\n"));
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ switch (lp_server_role()) {
+ case ROLE_STANDALONE:
+ basic->role = DS_ROLE_STANDALONE_SERVER;
+ basic->domain = get_global_sam_name();
+ break;
+ case ROLE_DOMAIN_MEMBER:
+ basic->role = DS_ROLE_MEMBER_SERVER;
+ basic->domain = lp_workgroup();
+ break;
+ case ROLE_DOMAIN_BDC:
+ basic->role = DS_ROLE_BACKUP_DC;
+ basic->domain = get_global_sam_name();
+ break;
+ case ROLE_DOMAIN_PDC:
+ case ROLE_IPA_DC:
+ basic->role = DS_ROLE_PRIMARY_DC;
+ basic->domain = get_global_sam_name();
+ break;
+ }
+
+ if (secrets_fetch_domain_guid(lp_workgroup(), &basic->domain_guid)) {
+ basic->flags |= DS_ROLE_PRIMARY_DOMAIN_GUID_PRESENT;
+ }
+
+ /* fill in some additional fields if we are a member of an AD domain */
+
+ if (lp_security() == SEC_ADS) {
+ dnsdomain = talloc_strdup(ctx, lp_realm());
+ if (!dnsdomain) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (!strlower_m(dnsdomain)) {
+ return WERR_INVALID_PARAMETER;
+ }
+ basic->dns_domain = dnsdomain;
+
+ /* FIXME!! We really should fill in the correct forest
+ name. Should get this information from winbindd. */
+ basic->forest = dnsdomain;
+ } else {
+ /* security = domain should not fill in the dns or
+ forest name */
+ basic->dns_domain = NULL;
+ basic->forest = NULL;
+ }
+
+ *info = basic;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ Implement the _dssetup_DsRoleGetPrimaryDomainInformation() call
+ ********************************************************************/
+
+WERROR _dssetup_DsRoleGetPrimaryDomainInformation(struct pipes_struct *p,
+ struct dssetup_DsRoleGetPrimaryDomainInformation *r)
+{
+ WERROR werr = WERR_OK;
+
+ switch (r->in.level) {
+
+ case DS_ROLE_BASIC_INFORMATION: {
+ struct dssetup_DsRolePrimaryDomInfoBasic *basic = NULL;
+ werr = fill_dsrole_dominfo_basic(p->mem_ctx, &basic);
+ if (W_ERROR_IS_OK(werr)) {
+ r->out.info->basic = *basic;
+ }
+ break;
+ }
+ default:
+ DEBUG(0,("_dssetup_DsRoleGetPrimaryDomainInformation: "
+ "Unknown info level [%d]!\n", r->in.level));
+ werr = WERR_INVALID_LEVEL;
+ }
+
+ return werr;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleDnsNameToFlatName(struct pipes_struct *p,
+ struct dssetup_DsRoleDnsNameToFlatName *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleDcAsDc(struct pipes_struct *p,
+ struct dssetup_DsRoleDcAsDc *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleDcAsReplica(struct pipes_struct *p,
+ struct dssetup_DsRoleDcAsReplica *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleDemoteDc(struct pipes_struct *p,
+ struct dssetup_DsRoleDemoteDc *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleGetDcOperationProgress(struct pipes_struct *p,
+ struct dssetup_DsRoleGetDcOperationProgress *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleGetDcOperationResults(struct pipes_struct *p,
+ struct dssetup_DsRoleGetDcOperationResults *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleCancel(struct pipes_struct *p,
+ struct dssetup_DsRoleCancel *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleServerSaveStateForUpgrade(struct pipes_struct *p,
+ struct dssetup_DsRoleServerSaveStateForUpgrade *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleUpgradeDownlevelServer(struct pipes_struct *p,
+ struct dssetup_DsRoleUpgradeDownlevelServer *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _dssetup_DsRoleAbortDownlevelServerUpgrade(struct pipes_struct *p,
+ struct dssetup_DsRoleAbortDownlevelServerUpgrade *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_dssetup_scompat.c"
diff --git a/source3/rpc_server/echo/srv_echo_nt.c b/source3/rpc_server/echo/srv_echo_nt.c
new file mode 100644
index 0000000..d5b3ddc
--- /dev/null
+++ b/source3/rpc_server/echo/srv_echo_nt.c
@@ -0,0 +1,126 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines for rpcecho
+ * Copyright (C) Tim Potter 2003
+ * Copyright (C) Jelmer Vernooij 2006
+ * Copyright (C) Gerald (Jerry) Carter 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This is the interface to the rpcecho pipe. */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/gen_ndr/ndr_echo.h"
+#include "librpc/gen_ndr/ndr_echo_scompat.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+/* Add one to the input and return it */
+
+void _echo_AddOne(struct pipes_struct *p, struct echo_AddOne *r )
+{
+ DEBUG(10, ("_echo_AddOne\n"));
+
+ *r->out.out_data = r->in.in_data + 1;
+}
+
+/* Echo back an array of data */
+
+void _echo_EchoData(struct pipes_struct *p, struct echo_EchoData *r)
+{
+ DEBUG(10, ("_echo_EchoData\n"));
+
+ if ( r->in.len == 0 ) {
+ r->out.out_data = NULL;
+ return;
+ }
+
+ r->out.out_data = talloc_array(p->mem_ctx, uint8_t, r->in.len);
+ memcpy( r->out.out_data, r->in.in_data, r->in.len );
+ return;
+}
+
+/* Sink an array of data */
+
+void _echo_SinkData(struct pipes_struct *p, struct echo_SinkData *r)
+{
+ DEBUG(10, ("_echo_SinkData\n"));
+
+ /* My that was some yummy data! */
+ return;
+}
+
+/* Source an array of data */
+
+void _echo_SourceData(struct pipes_struct *p, struct echo_SourceData *r)
+{
+ uint32_t i;
+
+ DEBUG(10, ("_echo_SourceData\n"));
+
+ if ( r->in.len == 0 ) {
+ r->out.data = NULL;
+ return;
+ }
+
+ r->out.data = talloc_array(p->mem_ctx, uint8_t, r->in.len );
+
+ for (i = 0; i < r->in.len; i++ ) {
+ r->out.data[i] = i & 0xff;
+ }
+
+ return;
+}
+
+void _echo_TestCall(struct pipes_struct *p, struct echo_TestCall *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return;
+}
+
+NTSTATUS _echo_TestCall2(struct pipes_struct *p, struct echo_TestCall2 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_OK;
+}
+
+uint32_t _echo_TestSleep(struct pipes_struct *p, struct echo_TestSleep *r)
+{
+ smb_msleep(r->in.seconds * 1000);
+ return 0;
+}
+
+void _echo_TestEnum(struct pipes_struct *p, struct echo_TestEnum *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return;
+}
+
+void _echo_TestSurrounding(struct pipes_struct *p, struct echo_TestSurrounding *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return;
+}
+
+uint16_t _echo_TestDoublePointer(struct pipes_struct *p, struct echo_TestDoublePointer *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return 0;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_echo_scompat.c"
diff --git a/source3/rpc_server/epmapper/srv_epmapper.c b/source3/rpc_server/epmapper/srv_epmapper.c
new file mode 100644
index 0000000..cf6b268
--- /dev/null
+++ b/source3/rpc_server/epmapper/srv_epmapper.c
@@ -0,0 +1,1063 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Endpoint server for the epmapper pipe
+
+ Copyright (C) 2010-2011 Andreas Schneider <asn@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "../libcli/security/security.h"
+#include "../lib/tsocket/tsocket.h"
+#include "auth.h"
+
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_epmapper.h"
+#include "librpc/gen_ndr/ndr_epmapper_scompat.h"
+#include "rpc_server/rpc_server.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "lib/util/util_tdb.h"
+#include "lib/util/strv.h"
+
+static struct tdb_wrap *epmdb = NULL;
+
+/* handle types for this module */
+enum handle_types {HTYPE_LOOKUP};
+
+typedef uint32_t error_status_t;
+
+/* An endpoint combined with an interface description */
+struct dcesrv_ep_iface {
+ const char *name;
+ struct ndr_syntax_id syntax_id;
+ struct epm_tower ep;
+};
+
+/* A rpc service interface like samr, lsarpc or netlogon */
+struct dcesrv_iface {
+ const char *name;
+ struct ndr_syntax_id syntax_id;
+};
+
+struct dcesrv_iface_list {
+ struct dcesrv_iface_list *next, *prev;
+ struct dcesrv_iface *iface;
+};
+
+/*
+ * An endpoint can serve multiple rpc services interfaces.
+ * For example \\pipe\netlogon can be used by lsarpc and netlogon.
+ */
+struct dcesrv_epm_endpoint {
+ struct dcesrv_epm_endpoint *next, *prev;
+
+ /* The type and the location of the endpoint */
+ struct dcerpc_binding *ep_description;
+
+ /* A list of rpc services able to connect to the endpoint */
+ struct dcesrv_iface_list *iface_list;
+};
+
+struct rpc_eps {
+ struct dcesrv_ep_iface *e;
+ uint32_t count;
+};
+
+struct build_ep_list_state {
+ const struct GUID *uuid;
+ const char *srv_addr;
+ TALLOC_CTX *mem_ctx;
+ struct dcesrv_ep_iface *ifaces;
+};
+
+static bool build_ep_list_fill_iface(
+ TALLOC_CTX *mem_ctx,
+ const struct ndr_syntax_id *syntax_id,
+ const char *endpoint,
+ const char *name,
+ const char *srv_addr,
+ struct dcesrv_ep_iface *dst)
+{
+ struct dcesrv_ep_iface iface = {
+ .syntax_id = *syntax_id,
+ };
+ struct dcerpc_binding *binding = NULL;
+ enum dcerpc_transport_t transport;
+ char *name_dup = NULL;
+ const char *host_addr = NULL;
+ NTSTATUS status;
+
+ /* copy without const for error path TALLOC_FREE */
+ name_dup = talloc_strdup(mem_ctx, name);
+ if (name_dup == NULL) {
+ goto fail;
+ }
+ iface.name = name_dup;
+
+ status = dcerpc_parse_binding(mem_ctx, endpoint, &binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_parse_binding failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = dcerpc_binding_set_abstract_syntax(binding, syntax_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_set_abstract_syntax failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ transport = dcerpc_binding_get_transport(binding);
+ if (transport == NCACN_IP_TCP) {
+ const char *host = NULL;
+
+ host = dcerpc_binding_get_string_option(binding, "host");
+ if (host == NULL) {
+ host_addr = srv_addr;
+ } else if (!is_ipaddress_v4(host)) {
+ host_addr = srv_addr;
+ } else if (strcmp(host, "0.0.0.0") == 0) {
+ host_addr = srv_addr;
+ }
+ }
+
+ if (host_addr != NULL) {
+ status = dcerpc_binding_set_string_option(
+ binding, "host", host_addr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_set_string_option "
+ "failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ }
+
+ status = dcerpc_binding_build_tower(mem_ctx, binding, &iface.ep);
+ TALLOC_FREE(binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_build_tower failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ *dst = iface;
+ return true;
+
+fail:
+ TALLOC_FREE(binding);
+ TALLOC_FREE(name_dup);
+ TALLOC_FREE(iface.ep.floors);
+ return false;
+}
+
+static int build_ep_list_fn(
+ struct tdb_context *tdb,
+ TDB_DATA key,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct build_ep_list_state *state = private_data;
+ struct ndr_syntax_id syntax_id = { .if_version = 0 };
+ const char *name = NULL;
+ char *endpoints = NULL;
+ const char *endpoint = NULL;
+ bool ok;
+
+ if ((key.dsize == 0) || (key.dptr[key.dsize-1] != '\0') ||
+ (value.dsize == 0) || (value.dptr[value.dsize-1] != '\0')) {
+ DBG_DEBUG("Invalid record\n");
+ return 0;
+ }
+
+ ok = ndr_syntax_id_from_string((char *)key.dptr, &syntax_id);
+ if (!ok) {
+ DBG_DEBUG("Invalid interface: %s\n", (char *)key.dptr);
+ return 0;
+ }
+
+ endpoints = (char *)value.dptr;
+ endpoint = endpoints;
+ name = endpoints;
+
+ while ((endpoint = strv_len_next(endpoints, value.dsize, endpoint))) {
+ size_t num_ifaces = talloc_array_length(state->ifaces);
+ struct dcesrv_ep_iface *tmp = NULL;
+
+ if (num_ifaces+1 < num_ifaces) {
+ return 1;
+ }
+
+ tmp = talloc_realloc(
+ state->mem_ctx,
+ state->ifaces,
+ struct dcesrv_ep_iface,
+ num_ifaces+1);
+ if (tmp == NULL) {
+ return 1;
+ }
+ state->ifaces = tmp;
+
+ ok = build_ep_list_fill_iface(
+ state->ifaces,
+ &syntax_id,
+ endpoint,
+ name,
+ state->srv_addr,
+ &state->ifaces[num_ifaces]);
+ if (!ok) {
+ state->ifaces = talloc_realloc(
+ state->mem_ctx,
+ state->ifaces,
+ struct dcesrv_ep_iface,
+ num_ifaces);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Build a list of all interfaces handled by all endpoint servers.
+ */
+static uint32_t build_ep_list(TALLOC_CTX *mem_ctx,
+ const struct GUID *uuid,
+ const char *srv_addr,
+ struct dcesrv_ep_iface **peps)
+{
+ struct build_ep_list_state state = {
+ .mem_ctx = mem_ctx, .uuid = uuid, .srv_addr = srv_addr,
+ };
+ int ret;
+
+ ret = tdb_traverse_read(epmdb->tdb, build_ep_list_fn, &state);
+ if (ret == -1) {
+ DBG_DEBUG("tdb_traverse_read failed\n");
+ return 0;
+ }
+
+ *peps = state.ifaces;
+ return talloc_array_length(*peps);
+}
+
+/*
+ * epm_Insert
+ *
+ * Add the specified entries to an endpoint map.
+ */
+error_status_t _epm_Insert(struct pipes_struct *p,
+ struct epm_Insert *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return EPMAPPER_STATUS_CANT_PERFORM_OP;
+}
+
+/*
+ * epm_Delete
+ *
+ * Delete the specified entries from an endpoint map.
+ */
+error_status_t _epm_Delete(struct pipes_struct *p,
+ struct epm_Delete *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return EPMAPPER_STATUS_CANT_PERFORM_OP;
+}
+
+/*
+ * epm_Lookup
+ *
+ * Lookup entries in an endpoint map.
+ */
+error_status_t _epm_Lookup(struct pipes_struct *p,
+ struct epm_Lookup *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ struct policy_handle *entry_handle;
+ struct rpc_eps *eps;
+ TALLOC_CTX *tmp_ctx;
+ error_status_t rc;
+ uint32_t count = 0;
+ uint32_t num_ents = 0;
+ uint32_t i;
+ bool match = false;
+ bool ok;
+ NTSTATUS status;
+
+ *r->out.num_ents = 0;
+ r->out.entries = NULL;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return EPMAPPER_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5, ("_epm_Lookup: Trying to lookup max. %u entries.\n",
+ r->in.max_ents));
+
+ if (r->in.entry_handle == NULL ||
+ ndr_policy_handle_empty(r->in.entry_handle)) {
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ char *srv_addr = NULL;
+
+ DEBUG(7, ("_epm_Lookup: No entry_handle found, creating it.\n"));
+
+ eps = talloc_zero(tmp_ctx, struct rpc_eps);
+ if (eps == NULL) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (local_address != NULL &&
+ tsocket_address_is_inet(local_address, "ipv4"))
+ {
+ srv_addr = tsocket_address_inet_addr_string(
+ local_address, tmp_ctx);
+ }
+
+ switch (r->in.inquiry_type) {
+ case RPC_C_EP_ALL_ELTS:
+ /*
+ * Return all elements from the endpoint map. The
+ * interface_id, vers_option, and object parameters MUST
+ * be ignored.
+ */
+ eps->count = build_ep_list(eps,
+ NULL,
+ srv_addr,
+ &eps->e);
+ break;
+ case RPC_C_EP_MATCH_BY_IF:
+ /*
+ * Return endpoint map elements that contain the
+ * interface identifier specified by the interface_id
+ * and vers_option values.
+ *
+ * RPC_C_EP_MATCH_BY_IF and RPC_C_EP_MATCH_BY_BOTH
+ * need both the same endpoint list. There is a second
+ * check for the inquiry_type below which differentiates
+ * between them.
+ */
+ case RPC_C_EP_MATCH_BY_BOTH:
+ /*
+ * Return endpoint map elements that contain the
+ * interface identifier and object UUID specified by
+ * interface_id, vers_option, and object.
+ */
+ eps->count = build_ep_list(eps,
+ &r->in.interface_id->uuid,
+ srv_addr,
+ &eps->e);
+ break;
+ case RPC_C_EP_MATCH_BY_OBJ:
+ /*
+ * Return endpoint map elements that contain the object
+ * UUID specified by object.
+ */
+ eps->count = build_ep_list(eps,
+ r->in.object,
+ srv_addr,
+ &eps->e);
+ break;
+ default:
+ rc = EPMAPPER_STATUS_CANT_PERFORM_OP;
+ goto done;
+ }
+
+ if (eps->count == 0) {
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ ok = create_policy_hnd(p, r->out.entry_handle, HTYPE_LOOKUP, eps);
+ if (!ok) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ eps = find_policy_by_hnd(p,
+ r->out.entry_handle,
+ HTYPE_LOOKUP,
+ struct rpc_eps,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+ entry_handle = r->out.entry_handle;
+ } else {
+ DEBUG(7, ("_epm_Lookup: Trying to find entry_handle.\n"));
+
+ eps = find_policy_by_hnd(p,
+ r->in.entry_handle,
+ HTYPE_LOOKUP,
+ struct rpc_eps,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+ entry_handle = r->in.entry_handle;
+ }
+
+ if (eps == NULL || eps->e == NULL) {
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ /* return the next N elements */
+ count = r->in.max_ents;
+ if (count > eps->count) {
+ count = eps->count;
+ }
+
+ DEBUG(5, ("_epm_Lookup: Find %u entries\n", count));
+
+ if (count == 0) {
+ close_policy_hnd(p, entry_handle);
+ ZERO_STRUCTP(r->out.entry_handle);
+
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ r->out.entries = talloc_array(p->mem_ctx, struct epm_entry_t, count);
+ if (r->out.entries == NULL) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < count; i++) {
+ match = false;
+
+ switch (r->in.inquiry_type) {
+ case RPC_C_EP_ALL_ELTS:
+ /*
+ * Return all elements from the endpoint map. The
+ * interface_id, vers_option, and object parameters MUST
+ * be ignored.
+ */
+ match = true;
+ break;
+ case RPC_C_EP_MATCH_BY_IF:
+ /*
+ * Return endpoint map elements that contain the
+ * interface identifier specified by the interface_id
+ * and vers_option values.
+ */
+ if (GUID_equal(&r->in.interface_id->uuid,
+ &eps->e[i].syntax_id.uuid)) {
+ match = true;
+ }
+ break;
+ case RPC_C_EP_MATCH_BY_OBJ:
+ /*
+ * Return endpoint map elements that contain the object
+ * UUID specified by object.
+ */
+ if (GUID_equal(r->in.object,
+ &eps->e[i].syntax_id.uuid)) {
+ match = true;
+ }
+ break;
+ case RPC_C_EP_MATCH_BY_BOTH:
+ /*
+ * Return endpoint map elements that contain the
+ * interface identifier and object UUID specified by
+ * interface_id, vers_option, and object.
+ */
+ if (GUID_equal(&r->in.interface_id->uuid,
+ &eps->e[i].syntax_id.uuid) &&
+ GUID_equal(r->in.object, &eps->e[i].syntax_id.uuid)) {
+ match = true;
+ }
+ break;
+ default:
+ return EPMAPPER_STATUS_CANT_PERFORM_OP;
+ }
+
+ if (match) {
+ if (r->in.inquiry_type == RPC_C_EP_MATCH_BY_IF ||
+ r->in.inquiry_type == RPC_C_EP_MATCH_BY_OBJ) {
+ /* Check interface version */
+
+ match = false;
+ switch (r->in.vers_option) {
+ case RPC_C_VERS_ALL:
+ /*
+ * Return endpoint map elements that
+ * contain the specified interface UUID,
+ * regardless of the version numbers.
+ */
+ match = true;
+ break;
+ case RPC_C_VERS_COMPATIBLE:
+ /*
+ * Return the endpoint map elements that
+ * contain the same major versions of
+ * the specified interface UUID and a
+ * minor version greater than or equal
+ * to the minor version of the specified
+ * UUID.
+ */
+ if (r->in.interface_id->vers_major ==
+ (eps->e[i].syntax_id.if_version >> 16) &&
+ r->in.interface_id->vers_minor <=
+ (eps->e[i].syntax_id.if_version & 0xFFFF)) {
+ match = true;
+ }
+ break;
+ case RPC_C_VERS_EXACT:
+ /*
+ * Return endpoint map elements that
+ * contain the specified version of the
+ * specified interface UUID.
+ */
+ if (r->in.interface_id->vers_major ==
+ (eps->e[i].syntax_id.if_version >> 16) &&
+ r->in.interface_id->vers_minor ==
+ (eps->e[i].syntax_id.if_version & 0xFFFF)) {
+ match = true;
+ }
+ match = true;
+ break;
+ case RPC_C_VERS_MAJOR_ONLY:
+ /*
+ * Return endpoint map elements that
+ * contain the same version of the
+ * specified interface UUID and ignore
+ * the minor version.
+ */
+ if (r->in.interface_id->vers_major ==
+ (eps->e[i].syntax_id.if_version >> 16)) {
+ match = true;
+ }
+ match = true;
+ break;
+ case RPC_C_VERS_UPTO:
+ /*
+ * Return endpoint map elements that
+ * contain a version of the specified
+ * interface UUID less than or equal to
+ * the specified major and minor
+ * version.
+ */
+ if (r->in.interface_id->vers_major >
+ eps->e[i].syntax_id.if_version >> 16) {
+ match = true;
+ } else {
+ if (r->in.interface_id->vers_major ==
+ (eps->e[i].syntax_id.if_version >> 16) &&
+ r->in.interface_id->vers_minor >=
+ (eps->e[i].syntax_id.if_version & 0xFFFF)) {
+ match = true;
+ }
+ }
+ break;
+ default:
+ return EPMAPPER_STATUS_CANT_PERFORM_OP;
+ }
+ }
+ }
+
+ if (match) {
+ ZERO_STRUCT(r->out.entries[num_ents].object);
+
+ DEBUG(10, ("_epm_Lookup: Adding tower for '%s'\n",
+ eps->e[i].name));
+ r->out.entries[num_ents].annotation = talloc_strdup(r->out.entries,
+ eps->e[i].name);
+ r->out.entries[num_ents].tower = talloc(r->out.entries,
+ struct epm_twr_t);
+ if (r->out.entries[num_ents].tower == NULL) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+ r->out.entries[num_ents].tower->tower.floors = talloc_move(r->out.entries[num_ents].tower, &eps->e[i].ep.floors);
+ r->out.entries[num_ents].tower->tower.num_floors = eps->e[i].ep.num_floors;
+ r->out.entries[num_ents].tower->tower_length = 0;
+
+ num_ents++;
+ }
+ } /* end for loop */
+
+ *r->out.num_ents = num_ents;
+
+ eps->count -= count;
+ eps->e += count;
+ if (eps->count == 0) {
+ close_policy_hnd(p, entry_handle);
+ ZERO_STRUCTP(r->out.entry_handle);
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ rc = EPMAPPER_STATUS_OK;
+done:
+ talloc_free(tmp_ctx);
+
+ return rc;
+}
+
+static struct rpc_eps *epm_map_get_towers(
+ TALLOC_CTX *mem_ctx,
+ const struct ndr_syntax_id *iface,
+ enum dcerpc_transport_t transport,
+ const char *local_address)
+{
+ struct ndr_syntax_id_buf idbuf;
+ char *iface_string = ndr_syntax_id_buf_string(iface, &idbuf);
+ struct rpc_eps *eps = NULL;
+ uint8_t *buf = NULL;
+ size_t buflen;
+ char *bindings = NULL;
+ char *binding = NULL;
+ char *name = NULL;
+ NTSTATUS status;
+ int ret;
+
+ DBG_DEBUG("Mapping interface %s\n", iface_string);
+
+ eps = talloc_zero(mem_ctx, struct rpc_eps);
+ if (eps == NULL) {
+ goto fail;
+ }
+
+ ret = tdb_fetch_talloc(
+ epmdb->tdb, string_term_tdb_data(iface_string), eps, &buf);
+ if (ret != 0) {
+ DBG_DEBUG("Could not find epm entry for %s: %s\n",
+ iface_string,
+ strerror(ret));
+ goto fail;
+ }
+ buflen = talloc_array_length(buf);
+
+ if ((buflen < 1) || (buf[buflen-1] != '\0')) {
+ DBG_DEBUG("epm entry for %s invalid\n", iface_string);
+ goto fail;
+ }
+ bindings = (char *)buf;
+
+ name = bindings; /* name comes first */
+ binding = name; /* strv_next will skip name */
+
+ while ((binding = strv_next(bindings, binding)) != NULL) {
+ struct dcerpc_binding *b = NULL;
+ enum dcerpc_transport_t found_transport;
+ struct dcesrv_ep_iface *tmp = NULL, *new_ep = NULL;
+
+ DBG_DEBUG("Found %s for %s\n", binding, name);
+
+ status = dcerpc_parse_binding(mem_ctx, binding, &b);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_parse_binding() for %s failed: %s\n",
+ binding,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ found_transport = dcerpc_binding_get_transport(b);
+ if (found_transport != transport) {
+ DBG_DEBUG("Transport %d does not match %d\n",
+ (int)found_transport,
+ (int)transport);
+ TALLOC_FREE(b);
+ continue;
+ }
+
+ if (found_transport == NCACN_IP_TCP) {
+ status = dcerpc_binding_set_string_option(
+ b, "host", local_address);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not set host: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ }
+
+ status = dcerpc_binding_set_abstract_syntax(b, iface);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not set abstract syntax: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ tmp = talloc_realloc(
+ eps,
+ eps->e,
+ struct dcesrv_ep_iface,
+ eps->count+1);
+ if (tmp == NULL) {
+ goto fail;
+ }
+ eps->e = tmp;
+
+ new_ep = &eps->e[eps->count];
+
+ new_ep->name = talloc_strdup(eps->e, name);
+ if (new_ep->name == NULL) {
+ goto fail;
+ }
+ new_ep->syntax_id = *iface;
+
+ status = dcerpc_binding_build_tower(eps->e, b, &new_ep->ep);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_build_tower failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ eps->count += 1;
+
+ TALLOC_FREE(b);
+ }
+ return eps;
+
+fail:
+ TALLOC_FREE(eps);
+ return NULL;
+}
+
+/*
+ * epm_Map
+ *
+ * Apply some algorithm (using the fields in the map_tower) to an endpoint map
+ * to produce a list of protocol towers.
+ */
+error_status_t _epm_Map(struct pipes_struct *p,
+ struct epm_Map *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ struct policy_handle *entry_handle;
+ enum dcerpc_transport_t transport;
+ struct ndr_syntax_id ifid;
+ struct epm_floor *floors;
+ struct rpc_eps *eps;
+ TALLOC_CTX *tmp_ctx;
+ error_status_t rc;
+ uint32_t count = 0;
+ uint32_t num_towers = 0;
+ uint32_t i;
+ bool ok;
+ NTSTATUS status;
+
+ *r->out.num_towers = 0;
+ r->out.towers = NULL;
+
+ if (r->in.map_tower == NULL || r->in.max_towers == 0 ||
+ r->in.map_tower->tower.num_floors < 3) {
+ return EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ }
+
+ tmp_ctx = talloc_stackframe();
+
+ ZERO_STRUCTP(r->out.entry_handle);
+
+ DEBUG(5, ("_epm_Map: Trying to map max. %u towers.\n",
+ r->in.max_towers));
+
+ /*
+ * A tower has normally up to 6 floors
+ *
+ * +-----------------------------------------------------------------+
+ * | Floor 1 | Provides the RPC interface identifier. (e.g. UUID for |
+ * | | netlogon) |
+ * +---------+-------------------------------------------------------+
+ * | Floor 2 | Transfer syntax (NDR encoded) |
+ * +---------+-------------------------------------------------------+
+ * | Floor 3 | RPC protocol identifier (ncacn_tcp_ip, ncacn_np, ...) |
+ * +---------+-------------------------------------------------------+
+ * | Floor 4 | Port address (e.g. TCP Port: 49156) |
+ * +---------+-------------------------------------------------------+
+ * | Floor 5 | Transport (e.g. IP:192.168.51.10) |
+ * +---------+-------------------------------------------------------+
+ * | Floor 6 | Routing |
+ * +---------+-------------------------------------------------------+
+ */
+ floors = r->in.map_tower->tower.floors;
+
+ /* We accept NDR as the transfer syntax */
+ status = dcerpc_floor_get_uuid_full(&floors[1], &ifid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_floor_get_uuid_full() failed: %s\n",
+ nt_errstr(status));
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ if (floors[1].lhs.protocol != EPM_PROTOCOL_UUID ||
+ !ndr_syntax_id_equal(&ifid, &ndr_transfer_syntax_ndr)) {
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ /* We only talk to sane transports */
+ transport = dcerpc_transport_by_tower(&r->in.map_tower->tower);
+ if (transport == NCA_UNKNOWN) {
+ DEBUG(2, ("epm_Map: Client requested unknown transport with "
+ "levels: "));
+ for (i = 2; i < r->in.map_tower->tower.num_floors; i++) {
+ DEBUG(2, ("%d, ", r->in.map_tower->tower.floors[i].lhs.protocol));
+ }
+ DEBUG(2, ("\n"));
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ if (r->in.entry_handle == NULL ||
+ ndr_policy_handle_empty(r->in.entry_handle)) {
+ const struct tsocket_address *local_addr =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ char *local_address = NULL;
+ struct ndr_syntax_id_buf buf;
+ char *if_string = NULL;
+
+ DEBUG(7, ("_epm_Map: No entry_handle found, creating it.\n"));
+
+ status = dcerpc_floor_get_uuid_full(&floors[0], &ifid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_floor_get_uuid_full() failed: %s\n",
+ nt_errstr(status));
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ if_string = ndr_syntax_id_buf_string(&ifid, &buf);
+
+ DBG_INFO("Mapping interface %s\n", if_string);
+
+ if ((transport == NCACN_IP_TCP) &&
+ tsocket_address_is_inet(local_addr, "ip")) {
+ /*
+ * We don't have the host ip in the epm
+ * database. For NCACN_IP_TCP, add the IP that
+ * the client connected to.
+ */
+ local_address = tsocket_address_inet_addr_string(
+ local_addr, tmp_ctx);
+ }
+
+ eps = epm_map_get_towers(
+ tmp_ctx, &ifid, transport, local_address);
+ if (eps == NULL) {
+ DBG_DEBUG("No bindings found\n");
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ ok = create_policy_hnd(p, r->out.entry_handle, HTYPE_LOOKUP, eps);
+ if (!ok) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ eps = find_policy_by_hnd(p,
+ r->out.entry_handle,
+ HTYPE_LOOKUP,
+ struct rpc_eps,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+ entry_handle = r->out.entry_handle;
+ } else {
+ DEBUG(7, ("_epm_Map: Trying to find entry_handle.\n"));
+
+ eps = find_policy_by_hnd(p,
+ r->in.entry_handle,
+ HTYPE_LOOKUP,
+ struct rpc_eps,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+ entry_handle = r->in.entry_handle;
+ }
+
+ if (eps == NULL || eps->e == NULL) {
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ /* return the next N elements */
+ count = r->in.max_towers;
+ if (count > eps->count) {
+ count = eps->count;
+ }
+
+ if (count == 0) {
+ close_policy_hnd(p, entry_handle);
+ ZERO_STRUCTP(r->out.entry_handle);
+
+ rc = EPMAPPER_STATUS_NO_MORE_ENTRIES;
+ goto done;
+ }
+
+ r->out.towers = talloc_array(p->mem_ctx, struct epm_twr_p_t, count);
+ if (r->out.towers == NULL) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < count; i++) {
+ DEBUG(7, ("_epm_Map: Map tower for '%s'\n",
+ eps->e[i].name));
+
+ r->out.towers[num_towers].twr = talloc(r->out.towers,
+ struct epm_twr_t);
+ if (r->out.towers[num_towers].twr == NULL) {
+ rc = EPMAPPER_STATUS_NO_MEMORY;
+ goto done;
+ }
+ r->out.towers[num_towers].twr->tower.floors = talloc_move(r->out.towers[num_towers].twr, &eps->e[i].ep.floors);
+ r->out.towers[num_towers].twr->tower.num_floors = eps->e[i].ep.num_floors;
+ r->out.towers[num_towers].twr->tower_length = 0;
+
+ num_towers++;
+ }
+
+ *r->out.num_towers = num_towers;
+
+ eps->count -= count;
+ eps->e += count;
+ if (eps->count == 0) {
+ close_policy_hnd(p, entry_handle);
+ ZERO_STRUCTP(r->out.entry_handle);
+ }
+
+ rc = EPMAPPER_STATUS_OK;
+done:
+ talloc_free(tmp_ctx);
+
+ return rc;
+}
+
+/*
+ * epm_LookupHandleFree
+ */
+error_status_t _epm_LookupHandleFree(struct pipes_struct *p,
+ struct epm_LookupHandleFree *r)
+{
+ if (r->in.entry_handle == NULL) {
+ return EPMAPPER_STATUS_OK;
+ }
+
+ if (is_valid_policy_hnd(r->in.entry_handle)) {
+ close_policy_hnd(p, r->in.entry_handle);
+ }
+
+ r->out.entry_handle = r->in.entry_handle;
+
+ return EPMAPPER_STATUS_OK;
+}
+
+
+/*
+ * epm_InqObject
+ *
+ * A client implementation SHOULD NOT call this method. These extensions do not
+ * provide an alternative method.
+ */
+error_status_t _epm_InqObject(struct pipes_struct *p,
+ struct epm_InqObject *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return EPMAPPER_STATUS_CANT_PERFORM_OP;
+}
+
+
+/*
+ * epm_MgmtDelete
+ *
+ * A client implementation SHOULD NOT call this method. These extensions do not
+ * provide an alternative method.
+*/
+error_status_t _epm_MgmtDelete(struct pipes_struct *p,
+ struct epm_MgmtDelete *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return EPMAPPER_STATUS_CANT_PERFORM_OP;
+}
+
+
+/*
+ epm_MapAuth
+*/
+error_status_t _epm_MapAuth(struct pipes_struct *p,
+ struct epm_MapAuth *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return EPMAPPER_STATUS_CANT_PERFORM_OP;
+}
+
+static NTSTATUS epmapper__op_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+#define DCESRV_INTERFACE_EPMAPPER_SHUTDOWN_SERVER \
+ epmapper_shutdown_server
+
+static NTSTATUS epmapper_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ return epmapper__op_shutdown_server(dce_ctx, ep_server);
+}
+
+static NTSTATUS epmapper__op_init_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+static NTSTATUS epmapper_init_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ char *epmdb_path = NULL;
+ NTSTATUS status;
+
+ epmdb_path = lock_path(dce_ctx, "epmdb.tdb");
+ if (epmdb_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ epmdb = tdb_wrap_open(
+ dce_ctx,
+ epmdb_path,
+ 0,
+ TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
+ O_RDONLY,
+ 0644);
+ if (epmdb == NULL) {
+ DBG_DEBUG("Could not open epmdb.tdb: %s\n", strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+ TALLOC_FREE(epmdb_path);
+
+ status = epmapper__op_init_server(dce_ctx, ep_server);
+ return status;
+}
+
+#define DCESRV_INTERFACE_EPMAPPER_INIT_SERVER epmapper_init_server
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_epmapper_scompat.c"
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/eventlog/srv_eventlog_nt.c b/source3/rpc_server/eventlog/srv_eventlog_nt.c
new file mode 100644
index 0000000..a946c57
--- /dev/null
+++ b/source3/rpc_server/eventlog/srv_eventlog_nt.c
@@ -0,0 +1,1047 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Marcin Krzysztof Porwit 2005,
+ * Copyright (C) Brian Moran 2005,
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ * Copyright (C) Guenther Deschner 2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "ntdomain.h"
+#include "lib/eventlog/eventlog.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "rpc_client/cli_winreg_int.h"
+#include "rpc_client/cli_winreg.h"
+#include "smbd/smbd.h"
+#include "auth.h"
+#include "util_tdb.h"
+
+#include "rpc_server/rpc_server.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_eventlog_scompat.h"
+#include "rpc_server/eventlog/srv_eventlog_reg.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#define TOP_LEVEL_EVENTLOG_KEY "SYSTEM\\CurrentControlSet\\Services\\Eventlog"
+
+typedef struct {
+ char *logname;
+ ELOG_TDB *etdb;
+ uint32_t current_record;
+ uint32_t num_records;
+ uint32_t oldest_entry;
+ uint32_t flags;
+ uint32_t access_granted;
+} EVENTLOG_INFO;
+
+/********************************************************************
+ ********************************************************************/
+
+static int eventlog_info_destructor(EVENTLOG_INFO *elog)
+{
+ if (elog->etdb) {
+ elog_close_tdb(elog->etdb, false);
+ }
+ return 0;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static EVENTLOG_INFO *find_eventlog_info_by_hnd( struct pipes_struct * p,
+ struct policy_handle * handle )
+{
+ EVENTLOG_INFO *info;
+ NTSTATUS status;
+
+ info = find_policy_by_hnd(p,
+ handle,
+ DCESRV_HANDLE_ANY,
+ EVENTLOG_INFO,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG( 2,
+ ( "find_eventlog_info_by_hnd: eventlog not found.\n" ) );
+ return NULL;
+ }
+
+ return info;
+}
+
+/********************************************************************
+ Pull the NT ACL from a file on disk or the OpenEventlog() access
+ check. Caller is responsible for freeing the returned security
+ descriptor via TALLOC_FREE(). This is designed for dealing with
+ user space access checks in smbd outside of the VFS. For example,
+ checking access rights in OpenEventlog() or from python.
+
+********************************************************************/
+
+static NTSTATUS get_nt_acl_no_snum(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ const char *fname,
+ uint32_t security_info_wanted,
+ struct security_descriptor **sd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct conn_struct_tos *c = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct smb_filename *pathref_fname = NULL;
+
+ if (!posix_locking_init(false)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = create_conn_struct_tos(global_messaging_context(),
+ -1,
+ "/",
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("create_conn_struct_tos() returned %s.\n",
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = synthetic_pathref(talloc_tos(),
+ c->conn->cwd_fsp,
+ fname,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("synthetic_pathref for file %s returned %s.\n",
+ fname, nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+ status = SMB_VFS_FGET_NT_ACL(pathref_fname->fsp,
+ security_info_wanted,
+ ctx,
+ sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("SMB_VFS_FGET_NT_ACL for file %s returned %s.\n",
+ fname, nt_errstr(status));
+ }
+
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool elog_check_access(EVENTLOG_INFO *info,
+ struct auth_session_info *session_info)
+{
+ const struct security_token *token = session_info->security_token;
+ char *tdbname = elog_tdbname(talloc_tos(), info->logname );
+ struct security_descriptor *sec_desc;
+ struct security_ace *ace;
+ NTSTATUS status;
+
+ if ( !tdbname )
+ return False;
+
+ /* get the security descriptor for the file */
+
+ status = get_nt_acl_no_snum( info,
+ session_info,
+ tdbname,
+ SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL,
+ &sec_desc);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("elog_check_access: Unable to get NT ACL for %s: %s\n",
+ tdbname, nt_errstr(status)));
+ TALLOC_FREE(tdbname);
+ return False;
+ }
+ TALLOC_FREE(tdbname);
+
+ ace = talloc_zero(sec_desc, struct security_ace);
+ if (ace == NULL) {
+ TALLOC_FREE(sec_desc);
+ return false;
+ }
+
+ ace->type = SEC_ACE_TYPE_ACCESS_ALLOWED;
+ ace->flags = 0;
+ ace->access_mask = REG_KEY_ALL;
+ ace->trustee = global_sid_System;
+
+ status = security_descriptor_dacl_add(sec_desc, ace);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sec_desc);
+ return false;
+ }
+
+ /* root free pass */
+
+ if ( geteuid() == sec_initial_uid() ) {
+ DEBUG(5,("elog_check_access: running as root, using system token\n"));
+ token = get_system_token();
+ }
+
+ /* run the check, try for the max allowed */
+
+ status = se_access_check( sec_desc, token, MAXIMUM_ALLOWED_ACCESS,
+ &info->access_granted);
+
+ TALLOC_FREE(sec_desc);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(8,("elog_check_access: se_access_check() return %s\n",
+ nt_errstr(status)));
+ return False;
+ }
+
+ /* we have to have READ permission for a successful open */
+
+ return ( info->access_granted & SEC_FILE_READ_DATA );
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static bool elog_validate_logname( const char *name )
+{
+ int i;
+ const char **elogs = lp_eventlog_list();
+
+ if (!elogs) {
+ return False;
+ }
+
+ for ( i=0; elogs[i]; i++ ) {
+ if ( strequal( name, elogs[i] ) )
+ return True;
+ }
+
+ return False;
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool get_num_records_hook( EVENTLOG_INFO * info )
+{
+ int next_record;
+ int oldest_record;
+
+ if ( !info->etdb ) {
+ DEBUG( 10, ( "No open tdb for %s\n", info->logname ) );
+ return False;
+ }
+
+ /* lock the tdb since we have to get 2 records */
+
+ tdb_lock_bystring_with_timeout( ELOG_TDB_CTX(info->etdb), EVT_NEXT_RECORD, 1 );
+ next_record = tdb_fetch_int32( ELOG_TDB_CTX(info->etdb), EVT_NEXT_RECORD);
+ oldest_record = tdb_fetch_int32( ELOG_TDB_CTX(info->etdb), EVT_OLDEST_ENTRY);
+ tdb_unlock_bystring( ELOG_TDB_CTX(info->etdb), EVT_NEXT_RECORD);
+
+ DEBUG( 8,
+ ( "Oldest Record %d; Next Record %d\n", oldest_record,
+ next_record ) );
+
+ info->num_records = ( next_record - oldest_record );
+ info->oldest_entry = oldest_record;
+
+ return True;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static bool get_oldest_entry_hook( EVENTLOG_INFO * info )
+{
+ /* it's the same thing */
+ return get_num_records_hook( info );
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static NTSTATUS elog_open( struct pipes_struct * p, const char *logname, struct policy_handle *hnd )
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ EVENTLOG_INFO *elog;
+
+ /* first thing is to validate the eventlog name */
+
+ if ( !elog_validate_logname( logname ) )
+ return NT_STATUS_OBJECT_PATH_INVALID;
+
+ if ( !(elog = talloc_zero( NULL, EVENTLOG_INFO )) )
+ return NT_STATUS_NO_MEMORY;
+ talloc_set_destructor(elog, eventlog_info_destructor);
+
+ elog->logname = talloc_strdup( elog, logname );
+
+ /* Open the tdb first (so that we can create any new tdbs if necessary).
+ We have to do this as root and then use an internal access check
+ on the file permissions since you can only have a tdb open once
+ in a single process */
+
+ become_root();
+ elog->etdb = elog_open_tdb( elog->logname, False, False );
+ unbecome_root();
+
+ if ( !elog->etdb ) {
+ /* according to MSDN, if the logfile cannot be found, we should
+ default to the "Application" log */
+
+ if ( !strequal( logname, ELOG_APPL ) ) {
+
+ TALLOC_FREE( elog->logname );
+
+ elog->logname = talloc_strdup( elog, ELOG_APPL );
+
+ /* do the access check */
+ if ( !elog_check_access( elog, session_info) ) {
+ TALLOC_FREE( elog );
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ become_root();
+ elog->etdb = elog_open_tdb( elog->logname, False, False );
+ unbecome_root();
+ }
+
+ if ( !elog->etdb ) {
+ TALLOC_FREE( elog );
+ return NT_STATUS_ACCESS_DENIED; /* ??? */
+ }
+ }
+
+ /* now do the access check. Close the tdb if we fail here */
+
+ if ( !elog_check_access( elog, session_info) ) {
+ TALLOC_FREE( elog );
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* create the policy handle */
+
+ if ( !create_policy_hnd( p, hnd, 0, elog ) ) {
+ TALLOC_FREE(elog);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* set the initial current_record pointer */
+
+ if ( !get_oldest_entry_hook( elog ) ) {
+ DEBUG(3,("elog_open: Successfully opened eventlog but can't "
+ "get any information on internal records!\n"));
+ }
+
+ elog->current_record = elog->oldest_entry;
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static NTSTATUS elog_close( struct pipes_struct *p, struct policy_handle *hnd )
+{
+ if ( !( close_policy_hnd( p, hnd ) ) ) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ *******************************************************************/
+
+static int elog_size( EVENTLOG_INFO *info )
+{
+ if ( !info || !info->etdb ) {
+ DEBUG(0,("elog_size: Invalid info* structure!\n"));
+ return 0;
+ }
+
+ return elog_tdb_size( ELOG_TDB_CTX(info->etdb), NULL, NULL );
+}
+
+/********************************************************************
+ note that this can only be called AFTER the table is constructed,
+ since it uses the table to find the tdb handle
+ ********************************************************************/
+
+static bool sync_eventlog_params(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ EVENTLOG_INFO *info)
+{
+ struct dcerpc_binding_handle *h = NULL;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ uint32_t uiMaxSize = 0;
+ uint32_t uiRetention = 0;
+ char *path = NULL;
+ NTSTATUS status;
+ WERROR wresult = WERR_OK;
+ char *elogname = info->logname;
+ TALLOC_CTX *ctx;
+ bool ret = false;
+
+ ctx = talloc_stackframe();
+ if (ctx == NULL) {
+ return false;
+ }
+
+ DEBUG( 4, ( "sync_eventlog_params with %s\n", elogname ) );
+
+ if ( !info->etdb ) {
+ DEBUG( 4, ( "No open tdb! (%s)\n", info->logname ) );
+ goto done;
+ }
+ /* set reasonable defaults. 512Kb on size and 1 week on time */
+
+ uiMaxSize = 0x80000;
+ uiRetention = 604800;
+
+ /* the general idea is to internally open the registry
+ key and retrieve the values. That way we can continue
+ to use the same fetch/store api that we use in
+ srv_reg_nt.c */
+ path = talloc_asprintf(ctx, "%s\\%s", TOP_LEVEL_EVENTLOG_KEY, elogname);
+ if (!path) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_int_hklm_openkey(ctx,
+ get_session_info_system(),
+ msg_ctx,
+ &h,
+ path,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd,
+ &wresult);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(4,("sync_eventlog_params: Failed to open key [%s] (%s)\n",
+ path, nt_errstr(status)));
+ goto done;
+ }
+ if ( !W_ERROR_IS_OK( wresult ) ) {
+ DEBUG( 4,
+ ( "sync_eventlog_params: Failed to open key [%s] (%s)\n",
+ path, win_errstr( wresult ) ) );
+ goto done;
+ }
+
+ status = dcerpc_winreg_query_dword(ctx,
+ h,
+ &key_hnd,
+ "Retention",
+ &uiRetention,
+ &wresult);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(4, ("Failed to query value \"Retention\": %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(wresult)) {
+ DEBUG(4, ("Failed to query value \"Retention\": %s\n",
+ win_errstr(wresult)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_query_dword(ctx,
+ h,
+ &key_hnd,
+ "MaxSize",
+ &uiMaxSize,
+ &wresult);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(4, ("Failed to query value \"Retention\": %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(wresult)) {
+ DEBUG(4, ("Failed to query value \"MaxSize\": %s\n",
+ win_errstr(wresult)));
+ goto done;
+ }
+
+ tdb_store_int32( ELOG_TDB_CTX(info->etdb), EVT_MAXSIZE, uiMaxSize );
+ tdb_store_int32( ELOG_TDB_CTX(info->etdb), EVT_RETENTION, uiRetention );
+
+ ret = true;
+
+done:
+ if (h != NULL) {
+ WERROR ignore;
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, ctx, &key_hnd, &ignore);
+ }
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ dcerpc_winreg_CloseKey(h, ctx, &hive_hnd, &ignore);
+ }
+ }
+
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/********************************************************************
+ _eventlog_OpenEventLogW
+ ********************************************************************/
+
+NTSTATUS _eventlog_OpenEventLogW(struct pipes_struct *p,
+ struct eventlog_OpenEventLogW *r)
+{
+ EVENTLOG_INFO *info;
+ NTSTATUS result;
+
+ DEBUG( 10,("_eventlog_OpenEventLogW: Server [%s], Log [%s]\n",
+ r->in.servername->string, r->in.logname->string ));
+
+ /* according to MSDN, if the logfile cannot be found, we should
+ default to the "Application" log */
+
+ if ( !NT_STATUS_IS_OK( result = elog_open( p, r->in.logname->string, r->out.handle )) )
+ return result;
+
+ if ( !(info = find_eventlog_info_by_hnd( p, r->out.handle )) ) {
+ DEBUG(0,("_eventlog_OpenEventLogW: eventlog (%s) opened but unable to find handle!\n",
+ r->in.logname->string ));
+ elog_close( p, r->out.handle );
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ DEBUG(10,("_eventlog_OpenEventLogW: Size [%d]\n", elog_size( info )));
+
+ if (!sync_eventlog_params(p->mem_ctx,
+ p->msg_ctx,
+ info)) {
+ elog_close(p, r->out.handle);
+ return NT_STATUS_EVENTLOG_FILE_CORRUPT;
+ }
+ prune_eventlog( ELOG_TDB_CTX(info->etdb) );
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ _eventlog_ClearEventLogW
+ This call still needs some work
+ ********************************************************************/
+/** The windows client seems to be doing something funny with the file name
+ A call like
+ ClearEventLog(handle, "backup_file")
+ on the client side will result in the backup file name looking like this on the
+ server side:
+ \??\${CWD of client}\backup_file
+ If an absolute path gets specified, such as
+ ClearEventLog(handle, "C:\\temp\\backup_file")
+ then it is still mangled by the client into this:
+ \??\C:\temp\backup_file
+ when it is on the wire.
+ I'm not sure where the \?? is coming from, or why the ${CWD} of the client process
+ would be added in given that the backup file gets written on the server side. */
+
+NTSTATUS _eventlog_ClearEventLogW(struct pipes_struct *p,
+ struct eventlog_ClearEventLogW *r)
+{
+ EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle );
+
+ if ( !info )
+ return NT_STATUS_INVALID_HANDLE;
+
+ if (r->in.backupfile && r->in.backupfile->string) {
+
+ DEBUG(8,( "_eventlog_ClearEventLogW: Using [%s] as the backup "
+ "file name for log [%s].\n",
+ r->in.backupfile->string, info->logname ) );
+ }
+
+ /* check for WRITE access to the file */
+
+ if ( !(info->access_granted & SEC_FILE_WRITE_DATA) )
+ return NT_STATUS_ACCESS_DENIED;
+
+ /* Force a close and reopen */
+
+ elog_close_tdb( info->etdb, True );
+ become_root();
+ info->etdb = elog_open_tdb( info->logname, True, False );
+ unbecome_root();
+
+ if ( !info->etdb )
+ return NT_STATUS_ACCESS_DENIED;
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ _eventlog_CloseEventLog
+ ********************************************************************/
+
+NTSTATUS _eventlog_CloseEventLog(struct pipes_struct * p,
+ struct eventlog_CloseEventLog *r)
+{
+ NTSTATUS status;
+
+ status = elog_close( p, r->in.handle );
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ZERO_STRUCTP(r->out.handle);
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ _eventlog_ReadEventLogW
+ ********************************************************************/
+
+NTSTATUS _eventlog_ReadEventLogW(struct pipes_struct *p,
+ struct eventlog_ReadEventLogW *r)
+{
+ EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle );
+ int bytes_left, record_number;
+ uint32_t elog_read_type, elog_read_dir;
+
+ if (!info) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ info->flags = r->in.flags;
+ bytes_left = r->in.number_of_bytes;
+
+ if (!info->etdb) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* check for valid flags. Can't use the sequential and seek flags together */
+
+ elog_read_type = r->in.flags & (EVENTLOG_SEQUENTIAL_READ|EVENTLOG_SEEK_READ);
+ elog_read_dir = r->in.flags & (EVENTLOG_FORWARDS_READ|EVENTLOG_BACKWARDS_READ);
+
+ if (r->in.flags == 0 ||
+ elog_read_type == (EVENTLOG_SEQUENTIAL_READ|EVENTLOG_SEEK_READ) ||
+ elog_read_dir == (EVENTLOG_FORWARDS_READ|EVENTLOG_BACKWARDS_READ))
+ {
+ DEBUG(3,("_eventlog_ReadEventLogW: "
+ "Invalid flags [0x%08x] for ReadEventLog\n",
+ r->in.flags));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* a sequential read should ignore the offset */
+
+ if (elog_read_type & EVENTLOG_SEQUENTIAL_READ) {
+ record_number = info->current_record;
+ } else {
+ record_number = r->in.offset;
+ }
+
+ if (r->in.number_of_bytes == 0) {
+ struct EVENTLOGRECORD *e;
+ e = evlog_pull_record(p->mem_ctx, ELOG_TDB_CTX(info->etdb),
+ record_number);
+ if (!e) {
+ return NT_STATUS_END_OF_FILE;
+ }
+ *r->out.real_size = e->Length;
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ while (bytes_left > 0) {
+
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ struct EVENTLOGRECORD *e;
+
+ e = evlog_pull_record(p->mem_ctx, ELOG_TDB_CTX(info->etdb),
+ record_number);
+ if (!e) {
+ break;
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, e,
+ (ndr_push_flags_fn_t)ndr_push_EVENTLOGRECORD);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(EVENTLOGRECORD, e);
+ }
+
+ if (blob.length > r->in.number_of_bytes) {
+ *r->out.real_size = blob.length;
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ if (*r->out.sent_size + blob.length > r->in.number_of_bytes) {
+ break;
+ }
+
+ bytes_left -= blob.length;
+
+ if (info->flags & EVENTLOG_FORWARDS_READ) {
+ record_number++;
+ } else {
+ record_number--;
+ }
+
+ /* update the eventlog record pointer */
+
+ info->current_record = record_number;
+
+ memcpy(&r->out.data[*(r->out.sent_size)],
+ blob.data, blob.length);
+ *(r->out.sent_size) += blob.length;
+ }
+
+ if (r->in.offset == 0 && record_number == 0 && *r->out.sent_size == 0) {
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ _eventlog_GetOldestRecord
+ ********************************************************************/
+
+NTSTATUS _eventlog_GetOldestRecord(struct pipes_struct *p,
+ struct eventlog_GetOldestRecord *r)
+{
+ EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle );
+
+ if (info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if ( !( get_oldest_entry_hook( info ) ) )
+ return NT_STATUS_ACCESS_DENIED;
+
+ *r->out.oldest_entry = info->oldest_entry;
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+_eventlog_GetNumRecords
+ ********************************************************************/
+
+NTSTATUS _eventlog_GetNumRecords(struct pipes_struct *p,
+ struct eventlog_GetNumRecords *r)
+{
+ EVENTLOG_INFO *info = find_eventlog_info_by_hnd( p, r->in.handle );
+
+ if (info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if ( !( get_num_records_hook( info ) ) )
+ return NT_STATUS_ACCESS_DENIED;
+
+ *r->out.number = info->num_records;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _eventlog_BackupEventLogW(struct pipes_struct *p, struct eventlog_BackupEventLogW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/********************************************************************
+_eventlog_GetLogInformation
+ ********************************************************************/
+
+NTSTATUS _eventlog_GetLogInformation(struct pipes_struct *p,
+ struct eventlog_GetLogInformation *r)
+{
+ EVENTLOG_INFO *info = find_eventlog_info_by_hnd(p, r->in.handle);
+ struct EVENTLOG_FULL_INFORMATION f;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ if (!info) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (r->in.level != 0) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ *r->out.bytes_needed = 4;
+
+ if (r->in.buf_size < 4) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ /* FIXME: this should be retrieved from the handle */
+ f.full = false;
+
+ ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &f,
+ (ndr_push_flags_fn_t)ndr_push_EVENTLOG_FULL_INFORMATION);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(EVENTLOG_FULL_INFORMATION, &f);
+ }
+
+ memcpy(r->out.buffer, blob.data, 4);
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+_eventlog_FlushEventLog
+ ********************************************************************/
+
+NTSTATUS _eventlog_FlushEventLog(struct pipes_struct *p,
+ struct eventlog_FlushEventLog *r)
+{
+ EVENTLOG_INFO *info = find_eventlog_info_by_hnd(p, r->in.handle);
+ if (!info) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ return NT_STATUS_ACCESS_DENIED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static NTSTATUS evlog_report_to_record(TALLOC_CTX *mem_ctx,
+ const struct eventlog_ReportEventW *r,
+ const char *logname,
+ struct EVENTLOGRECORD *e)
+{
+ uint32_t i;
+ ZERO_STRUCTP(e);
+
+ e->TimeGenerated = r->in.timestamp;
+ e->TimeWritten = time(NULL);
+ e->EventID = r->in.event_id;
+ e->EventType = r->in.event_type;
+ e->NumStrings = r->in.num_of_strings;
+ e->EventCategory = r->in.event_category;
+ e->ReservedFlags = r->in.flags;
+ e->DataLength = r->in.data_size;
+ e->SourceName = talloc_strdup(mem_ctx, logname);
+ NT_STATUS_HAVE_NO_MEMORY(e->SourceName);
+ if (r->in.servername->string) {
+ e->Computername = r->in.servername->string;
+ } else {
+ e->Computername = talloc_strdup(mem_ctx, "");
+ NT_STATUS_HAVE_NO_MEMORY(e->Computername);
+ }
+ if (r->in.user_sid) {
+ e->UserSid = *r->in.user_sid;
+ }
+ e->Strings = talloc_array(mem_ctx, const char *, e->NumStrings);
+ NT_STATUS_HAVE_NO_MEMORY(e->Strings);
+
+ for (i=0; i < e->NumStrings; i++) {
+ e->Strings[i] = talloc_strdup(e->Strings,
+ r->in.strings[i]->string);
+ NT_STATUS_HAVE_NO_MEMORY(e->Strings[i]);
+ }
+ e->Data = r->in.data;
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+_eventlog_ReportEventW
+ ********************************************************************/
+
+NTSTATUS _eventlog_ReportEventW(struct pipes_struct *p,
+ struct eventlog_ReportEventW *r)
+{
+ NTSTATUS status;
+ struct EVENTLOGRECORD record;
+
+ EVENTLOG_INFO *info = find_eventlog_info_by_hnd(p, r->in.handle);
+ if (!info) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ status = evlog_report_to_record(p->mem_ctx, r, info->logname, &record);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = evlog_push_record(p->mem_ctx,
+ ELOG_TDB_CTX(info->etdb),
+ &record,
+ r->out.record_number);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+NTSTATUS _eventlog_DeregisterEventSource(struct pipes_struct *p,
+ struct eventlog_DeregisterEventSource *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_ChangeNotify(struct pipes_struct *p,
+ struct eventlog_ChangeNotify *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_RegisterEventSourceW(struct pipes_struct *p,
+ struct eventlog_RegisterEventSourceW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_OpenBackupEventLogW(struct pipes_struct *p,
+ struct eventlog_OpenBackupEventLogW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_ClearEventLogA(struct pipes_struct *p,
+ struct eventlog_ClearEventLogA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_BackupEventLogA(struct pipes_struct *p,
+ struct eventlog_BackupEventLogA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_OpenEventLogA(struct pipes_struct *p,
+ struct eventlog_OpenEventLogA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_RegisterEventSourceA(struct pipes_struct *p,
+ struct eventlog_RegisterEventSourceA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_OpenBackupEventLogA(struct pipes_struct *p,
+ struct eventlog_OpenBackupEventLogA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_ReadEventLogA(struct pipes_struct *p,
+ struct eventlog_ReadEventLogA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_ReportEventA(struct pipes_struct *p,
+ struct eventlog_ReportEventA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_RegisterClusterSvc(struct pipes_struct *p,
+ struct eventlog_RegisterClusterSvc *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_DeregisterClusterSvc(struct pipes_struct *p,
+ struct eventlog_DeregisterClusterSvc *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_WriteClusterEvents(struct pipes_struct *p,
+ struct eventlog_WriteClusterEvents *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _eventlog_ReportEventAndSourceW(struct pipes_struct *p,
+ struct eventlog_ReportEventAndSourceW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+static NTSTATUS eventlog__op_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+#define DCESRV_INTERFACE_EVENTLOG_INIT_SERVER \
+ eventlog_init_server
+
+static NTSTATUS eventlog_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ bool ok;
+
+ ok = eventlog_init_winreg(msg_ctx);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return eventlog__op_init_server(dce_ctx, ep_server);
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_eventlog_scompat.c"
diff --git a/source3/rpc_server/eventlog/srv_eventlog_reg.c b/source3/rpc_server/eventlog/srv_eventlog_reg.c
new file mode 100644
index 0000000..513dd0f
--- /dev/null
+++ b/source3/rpc_server/eventlog/srv_eventlog_reg.c
@@ -0,0 +1,267 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Eventlog RPC server keys initialization
+ *
+ * Copyright (c) 2005 Marcin Krzysztof Porwit
+ * Copyright (c) 2005 Brian Moran
+ * Copyright (c) 2005 Gerald (Jerry) Carter
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "rpc_client/cli_winreg_int.h"
+#include "rpc_client/cli_winreg.h"
+#include "rpc_server/eventlog/srv_eventlog_reg.h"
+#include "auth.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+#define TOP_LEVEL_EVENTLOG_KEY "SYSTEM\\CurrentControlSet\\Services\\Eventlog"
+
+bool eventlog_init_winreg(struct messaging_context *msg_ctx)
+{
+ struct dcerpc_binding_handle *h = NULL;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ uint32_t uiMaxSize = 0x00080000;
+ uint32_t uiRetention = 0x93A80;
+ const char **elogs = lp_eventlog_list();
+ const char **subkeys = NULL;
+ uint32_t num_subkeys = 0;
+ uint32_t i;
+ char *key = NULL;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ bool ok = false;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ DEBUG(3, ("Initialise the eventlog registry keys if needed.\n"));
+
+ key = talloc_strdup(tmp_ctx, TOP_LEVEL_EVENTLOG_KEY);
+
+ status = dcerpc_winreg_int_hklm_openkey(tmp_ctx,
+ get_session_info_system(),
+ msg_ctx,
+ &h,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("eventlog_init_winreg: Could not open %s - %s\n",
+ key, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("eventlog_init_winreg: Could not open %s - %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_enum_keys(tmp_ctx,
+ h,
+ &key_hnd,
+ &num_subkeys,
+ &subkeys,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("eventlog_init_winreg: Could enum keys at %s - %s\n",
+ key, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("eventlog_init_winreg: Could enum keys at %s - %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result);
+ }
+
+ /* create subkeys if they don't exist */
+ while (elogs && *elogs) {
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+ char *evt_tdb = NULL;
+ struct winreg_String wkey;
+ struct winreg_String wkeyclass;
+ bool skip = false;
+
+ for (i = 0; i < num_subkeys; i++) {
+ if (strequal(subkeys[i], *elogs)) {
+ skip = true;
+ }
+ }
+
+ if (skip) {
+ elogs++;
+ continue;
+ }
+
+ ZERO_STRUCT(key_hnd);
+ ZERO_STRUCT(wkey);
+
+ wkey.name = talloc_asprintf(tmp_ctx, "%s\\%s", key, *elogs);
+ if (wkey.name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+
+ status = dcerpc_winreg_CreateKey(h,
+ tmp_ctx,
+ &hive_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ &key_hnd,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ h,
+ &key_hnd,
+ "MaxSize",
+ uiMaxSize,
+ &result);
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ h,
+ &key_hnd,
+ "Retention",
+ uiRetention,
+ &result);
+
+ status = dcerpc_winreg_set_sz(tmp_ctx,
+ h,
+ &key_hnd,
+ "PrimaryModule",
+ *elogs,
+ &result);
+
+ evt_tdb = talloc_asprintf(tmp_ctx,
+ "%%SystemRoot%%\\system32\\config\\%s.tdb",
+ *elogs);
+ if (evt_tdb == NULL) {
+ goto done;
+ }
+ status = dcerpc_winreg_set_expand_sz(tmp_ctx,
+ h,
+ &key_hnd,
+ "File",
+ evt_tdb,
+ &result);
+ TALLOC_FREE(evt_tdb);
+
+ status = dcerpc_winreg_add_multi_sz(tmp_ctx,
+ h,
+ &key_hnd,
+ "Sources",
+ *elogs,
+ &result);
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result);
+ }
+
+ /* sub-subkeys */
+ {
+ uint32_t uiCategoryCount = 0x00000007;
+
+ wkey.name = talloc_asprintf(tmp_ctx,
+ "%s\\%s",
+ wkey.name, *elogs);
+ if (wkey.name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_winreg_CreateKey(h,
+ tmp_ctx,
+ &hive_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ &key_hnd,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("eventlog_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(tmp_ctx,
+ h,
+ &key_hnd,
+ "CategoryCount",
+ uiCategoryCount,
+ &result);
+
+ status = dcerpc_winreg_set_expand_sz(tmp_ctx,
+ h,
+ &key_hnd,
+ "CategoryMessageFile",
+ "%SystemRoot%\\system32\\eventlog.dll",
+ &result);
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result);
+ }
+ }
+
+ elogs++;
+ } /* loop */
+
+ ok = true;
+done:
+ TALLOC_FREE(tmp_ctx);
+ return ok;
+}
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/eventlog/srv_eventlog_reg.h b/source3/rpc_server/eventlog/srv_eventlog_reg.h
new file mode 100644
index 0000000..02c2792
--- /dev/null
+++ b/source3/rpc_server/eventlog/srv_eventlog_reg.h
@@ -0,0 +1,29 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * WINREG client routines
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SRV_EVENTLOG_REG_H
+#define SRV_EVENTLOG_REG_H
+
+bool eventlog_init_winreg(struct messaging_context *msg_ctx);
+
+#endif /* SRV_EVENTLOG_REG_H */
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/fss/srv_fss_agent.c b/source3/rpc_server/fss/srv_fss_agent.c
new file mode 100644
index 0000000..4de600f
--- /dev/null
+++ b/source3/rpc_server/fss/srv_fss_agent.c
@@ -0,0 +1,1776 @@
+/*
+ * File Server Remote VSS Protocol (FSRVP) server
+ *
+ * Copyright (C) David Disseldorp 2012-2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "include/messages.h"
+#include "serverid.h"
+#include "include/auth.h"
+#include "../libcli/security/security.h"
+#include "../libcli/util/hresult.h"
+#include "../lib/smbconf/smbconf.h"
+#include "smbd/proto.h"
+#include "lib/smbconf/smbconf_init.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_fsrvp_scompat.h"
+#include "librpc/gen_ndr/ndr_fsrvp.h"
+#include "rpc_server/rpc_server.h"
+#include "srv_fss_private.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+static struct fss_global fss_global;
+
+/* errmap NTSTATUS->fsrvp */
+static const struct {
+ NTSTATUS status;
+ uint32_t fsrvp_err;
+} ntstatus_to_fsrvp_map[] = {
+ {NT_STATUS_INVALID_SERVER_STATE, FSRVP_E_BAD_STATE},
+ {NT_STATUS_INVALID_DISPOSITION, FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS},
+ {NT_STATUS_NOT_SUPPORTED, FSRVP_E_NOT_SUPPORTED},
+ {NT_STATUS_IO_TIMEOUT, FSRVP_E_WAIT_TIMEOUT},
+ {NT_STATUS_CANT_WAIT, FSRVP_E_WAIT_FAILED},
+ {NT_STATUS_OBJECTID_EXISTS, FSRVP_E_OBJECT_ALREADY_EXISTS},
+ {NT_STATUS_OBJECTID_NOT_FOUND, FSRVP_E_OBJECT_NOT_FOUND},
+ {NT_STATUS_OBJECT_NAME_INVALID, FSRVP_E_BAD_ID},
+};
+
+/* errmap NTSTATUS->hresult */
+static const struct {
+ NTSTATUS status;
+ HRESULT hres;
+} ntstatus_to_hres_map[] = {
+ {NT_STATUS_ACCESS_DENIED, HRES_E_ACCESSDENIED},
+ {NT_STATUS_INVALID_PARAMETER, HRES_E_INVALIDARG},
+ {NT_STATUS_NO_MEMORY, HRES_E_OUTOFMEMORY},
+};
+
+static uint32_t fss_ntstatus_map(NTSTATUS status)
+{
+ size_t i;
+
+ if (NT_STATUS_IS_OK(status))
+ return 0;
+
+ /* check fsrvp specific errors first */
+ for (i = 0; i < ARRAY_SIZE(ntstatus_to_fsrvp_map); i++) {
+ if (NT_STATUS_EQUAL(status, ntstatus_to_fsrvp_map[i].status)) {
+ return ntstatus_to_fsrvp_map[i].fsrvp_err;
+ }
+ }
+ /* fall-back to generic hresult values */
+ for (i = 0; i < ARRAY_SIZE(ntstatus_to_hres_map); i++) {
+ if (NT_STATUS_EQUAL(status, ntstatus_to_hres_map[i].status)) {
+ return HRES_ERROR_V(ntstatus_to_hres_map[i].hres);
+ }
+ }
+
+ return HRES_ERROR_V(HRES_E_FAIL);
+}
+
+static NTSTATUS fss_unc_parse(TALLOC_CTX *mem_ctx,
+ const char *unc,
+ char **_server,
+ char **_share)
+{
+ char *s;
+ char *server;
+ char *share;
+
+ if (unc == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ s = strstr_m(unc, "\\\\");
+ if (s == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ server = talloc_strdup(mem_ctx, s + 2);
+ if (server == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ s = strchr_m(server, '\\');
+ if ((s == NULL) || (s == server)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ *s = '\0';
+ share = s + 1;
+
+ s = strchr_m(share, '\\');
+ if (s != NULL) {
+ /* diskshadow.exe adds a trailing '\' to the share-name */
+ *s = '\0';
+ }
+ if (strlen(share) == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (_server != NULL) {
+ *_server = server;
+ }
+ if (_share != NULL) {
+ *_share = share;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fss_conn_create_tos(struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ int snum,
+ struct connection_struct **conn_out);
+
+/* test if system path exists */
+static bool snap_path_exists(TALLOC_CTX *ctx, struct messaging_context *msg_ctx,
+ struct fss_sc *sc)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ SMB_STRUCT_STAT st;
+ struct connection_struct *conn = NULL;
+ struct smb_filename *smb_fname = NULL;
+ char *service = NULL;
+ char *share;
+ int snum;
+ int ret;
+ NTSTATUS status;
+ bool result = false;
+
+ ZERO_STRUCT(st);
+
+ if ((sc->smaps_count == 0) || (sc->sc_path == NULL)) {
+ goto out;
+ }
+
+ share = sc->smaps->share_name;
+ snum = find_service(frame, share, &service);
+
+ if ((snum == -1) || (service == NULL)) {
+ goto out;
+ }
+
+ status = fss_conn_create_tos(msg_ctx, NULL, snum, &conn);
+ if(!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(service,
+ sc->sc_path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto out;
+ }
+
+ ret = SMB_VFS_STAT(conn, smb_fname);
+ if ((ret == -1) && (errno == ENOENT)) {
+ goto out;
+ }
+ result = true;
+out:
+ TALLOC_FREE(frame);
+ return result;
+}
+
+static NTSTATUS sc_smap_unexpose(struct messaging_context *msg_ctx,
+ struct fss_sc_smap *sc_smap, bool delete_all);
+
+static NTSTATUS fss_prune_stale(struct messaging_context *msg_ctx,
+ const char *db_path)
+{
+ struct fss_sc_set *sc_sets;
+ uint32_t sc_sets_count = 0;
+ struct fss_sc_set *sc_set;
+ struct fss_sc_smap *prunable_sc_smaps = NULL;
+ bool is_modified = false;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ TALLOC_CTX *ctx = talloc_new(NULL);
+
+ if (!ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* work with temporary state for simple cleanup on failure */
+ become_root();
+ status = fss_state_retrieve(ctx, &sc_sets, &sc_sets_count, db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to retrieve fss server state: %s\n",
+ nt_errstr(status)));
+ goto out;
+ }
+
+ /* walk the cache and pick up any entries to be deleted */
+ sc_set = sc_sets;
+ DEBUG(10, ("pruning shared shadow copies\n"));
+ while (sc_set) {
+ struct fss_sc *sc;
+ struct fss_sc_set *sc_set_next = sc_set->next;
+ char *set_id = GUID_string(ctx, &sc_set->id);
+ if (set_id == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ DEBUGADD(10, ("\tprocessing shadow set id %s\n", set_id));
+ sc = sc_set->scs;
+ while (sc) {
+ struct fss_sc_smap *sc_smap;
+ struct fss_sc *sc_next = sc->next;
+ DEBUGADD(10, ("\tprocessing shadow copy path %s\n",
+ sc->sc_path));
+ if (snap_path_exists(ctx, msg_ctx, sc)) {
+ sc = sc_next;
+ continue;
+ }
+
+ /* move missing snapshot state to purge list */
+ sc_smap = sc->smaps;
+ while (sc_smap != NULL) {
+ struct fss_sc_smap *smap_next = sc_smap->next;
+ DLIST_REMOVE(sc->smaps, sc_smap);
+ DLIST_ADD_END(prunable_sc_smaps, sc_smap);
+ sc->smaps_count--;
+ sc_smap = smap_next;
+ }
+
+ DLIST_REMOVE(sc_set->scs, sc);
+ sc_set->scs_count--;
+ is_modified = true;
+ sc = sc_next;
+ }
+ if (sc_set->scs_count == 0) {
+ DLIST_REMOVE(sc_sets, sc_set);
+ sc_sets_count--;
+ }
+ sc_set = sc_set_next;
+ }
+
+ if (is_modified) {
+ /* unexpose all shares in a single transaction */
+ status = sc_smap_unexpose(msg_ctx, prunable_sc_smaps, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* exit without storing updated state */
+ goto out;
+ }
+
+ become_root();
+ status = fss_state_store(ctx, sc_sets, sc_sets_count, db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("pruning failed to store fss server state: %s\n",
+ nt_errstr(status)));
+ goto out;
+ }
+ }
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(ctx);
+ return status;
+}
+
+static NTSTATUS fss_conn_create_tos(struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ int snum,
+ struct connection_struct **conn_out)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct conn_struct_tos *c = NULL;
+ NTSTATUS status;
+
+ status = create_conn_struct_tos(msg_ctx,
+ snum,
+ lp_path(talloc_tos(), lp_sub, snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("failed to create conn for vfs: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = set_conn_force_user_group(c->conn, snum);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed set force user / group\n"));
+ TALLOC_FREE(c);
+ return status;
+ }
+
+ *conn_out = c->conn;
+ return NT_STATUS_OK;
+}
+
+static struct fss_sc_set *sc_set_lookup(struct fss_sc_set *sc_set_head,
+ struct GUID *sc_set_id)
+{
+
+ struct fss_sc_set *sc_set;
+ char *guid_str;
+
+ for (sc_set = sc_set_head; sc_set; sc_set = sc_set->next) {
+ if (GUID_equal(&sc_set->id, sc_set_id)) {
+ return sc_set;
+ }
+ }
+ guid_str = GUID_string(sc_set_head, sc_set_id);
+ DEBUG(4, ("shadow copy set with GUID %s not found\n",
+ guid_str ? guid_str : "NO MEM"));
+ talloc_free(guid_str);
+
+ return NULL;
+}
+
+static struct fss_sc *sc_lookup(struct fss_sc *sc_head, struct GUID *sc_id)
+{
+
+ struct fss_sc *sc;
+ char *guid_str;
+
+ for (sc = sc_head; sc; sc = sc->next) {
+ if (GUID_equal(&sc->id, sc_id)) {
+ return sc;
+ }
+ }
+ guid_str = GUID_string(sc_head, sc_id);
+ DEBUG(4, ("shadow copy with GUID %s not found\n",
+ guid_str ? guid_str : "NO MEM"));
+ talloc_free(guid_str);
+
+ return NULL;
+}
+
+static struct fss_sc *sc_lookup_volname(struct fss_sc *sc_head,
+ const char *volname)
+{
+ struct fss_sc *sc;
+
+ for (sc = sc_head; sc; sc = sc->next) {
+ if (!strcmp(sc->volume_name, volname)) {
+ return sc;
+ }
+ }
+ DEBUG(4, ("shadow copy with base volume %s not found\n", volname));
+ return NULL;
+}
+
+/* lookup is case-insensitive */
+static struct fss_sc_smap *sc_smap_lookup(struct fss_sc_smap *smaps_head,
+ const char *share)
+{
+ struct fss_sc_smap *sc_smap;
+ for (sc_smap = smaps_head; sc_smap; sc_smap = sc_smap->next) {
+ if (!strcasecmp_m(sc_smap->share_name, share)) {
+ return sc_smap;
+ }
+ }
+ DEBUG(4, ("shadow copy share mapping for %s not found\n", share));
+ return NULL;
+}
+
+static void srv_fssa_cleanup(void)
+{
+ talloc_free(fss_global.db_path);
+ talloc_free(fss_global.mem_ctx);
+ ZERO_STRUCT(fss_global);
+}
+
+static NTSTATUS srv_fssa_start(struct messaging_context *msg_ctx)
+{
+ NTSTATUS status;
+ fss_global.mem_ctx = talloc_named_const(NULL, 0,
+ "parent fss rpc server ctx");
+ if (fss_global.mem_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fss_global.db_path = lock_path(talloc_tos(), FSS_DB_NAME);
+ if (fss_global.db_path == NULL) {
+ talloc_free(fss_global.mem_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fss_global.min_vers = FSRVP_RPC_VERSION_1;
+ fss_global.max_vers = FSRVP_RPC_VERSION_1;
+ /*
+ * The server MUST populate the GlobalShadowCopySetTable with the
+ * ShadowCopySet entries read from the configuration store.
+ */
+ if (lp_parm_bool(GLOBAL_SECTION_SNUM, "fss", "prune stale", false)) {
+ fss_prune_stale(msg_ctx, fss_global.db_path);
+ }
+ become_root();
+ status = fss_state_retrieve(fss_global.mem_ctx, &fss_global.sc_sets,
+ &fss_global.sc_sets_count,
+ fss_global.db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to retrieve fss server state: %s\n",
+ nt_errstr(status)));
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Determine whether to process an FSRVP operation from connected user @p.
+ * Windows checks for Administrators or Backup Operators group membership. We
+ * also allow for the SEC_PRIV_BACKUP privilege.
+ */
+static bool fss_permitted(struct pipes_struct *p)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+
+ if (session_info->unix_token->uid == sec_initial_uid()) {
+ DEBUG(6, ("Granting FSRVP op, user started smbd\n"));
+ return true;
+ }
+
+ if (nt_token_check_sid(&global_sid_Builtin_Administrators,
+ session_info->security_token)) {
+ DEBUG(6, ("Granting FSRVP op, administrators group member\n"));
+ return true;
+ }
+ if (nt_token_check_sid(&global_sid_Builtin_Backup_Operators,
+ session_info->security_token)) {
+ DEBUG(6, ("Granting FSRVP op, backup operators group member\n"));
+ return true;
+ }
+ if (security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_BACKUP)) {
+ DEBUG(6, ("Granting FSRVP op, backup privilege present\n"));
+ return true;
+ }
+
+ DEBUG(2, ("FSRVP operation blocked due to lack of backup privilege "
+ "or Administrators/Backup Operators group membership\n"));
+
+ return false;
+}
+
+static void fss_seq_tout_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t,
+ void *private_data)
+{
+ struct GUID *sc_set_id = NULL;
+ struct fss_sc_set *sc_set;
+
+ /*
+ * MS-FSRVP: 3.1.5 Timer Events
+ * Message Sequence Timer elapses: When the Message Sequence Timer
+ * elapses, the server MUST delete the ShadowCopySet in the
+ * GlobalShadowCopySetTable where ShadowCopySet.Status is not equal to
+ * "Recovered", ContextSet MUST be set to FALSE, and the ShadowCopySet
+ * object MUST be freed.
+ */
+ DEBUG(2, ("FSRVP msg seq timeout fired\n"));
+
+ if (private_data == NULL) {
+ DEBUG(4, ("timeout without sc_set\n"));
+ goto out_init_ctx;
+ }
+
+ sc_set_id = talloc_get_type_abort(private_data, struct GUID);
+ sc_set = sc_set_lookup(fss_global.sc_sets, sc_set_id);
+ if (sc_set == NULL) {
+ DEBUG(0, ("timeout for unknown sc_set\n"));
+ goto out_init_ctx;
+ } else if ((sc_set->state == FSS_SC_EXPOSED)
+ || (sc_set->state == FSS_SC_RECOVERED)) {
+ DEBUG(2, ("timeout for finished sc_set %s\n", sc_set->id_str));
+ goto out_init_ctx;
+ }
+ DEBUG(2, ("cleaning up sc_set %s\n", sc_set->id_str));
+ SMB_ASSERT(fss_global.sc_sets_count > 0);
+ DLIST_REMOVE(fss_global.sc_sets, sc_set);
+ fss_global.sc_sets_count--;
+ talloc_free(sc_set);
+
+out_init_ctx:
+ fss_global.ctx_set = false;
+ fss_global.seq_tmr = NULL;
+ talloc_free(sc_set_id);
+}
+
+static void fss_seq_tout_set(TALLOC_CTX *mem_ctx,
+ uint32_t timeout_s,
+ struct fss_sc_set *sc_set,
+ struct tevent_timer **tmr_out)
+{
+ struct tevent_timer *tmr;
+ struct GUID *sc_set_id = NULL;
+ uint32_t tout;
+
+ /* allow changes to timeout for testing/debugging purposes */
+ tout = lp_parm_int(GLOBAL_SECTION_SNUM, "fss",
+ "sequence timeout", timeout_s);
+ if (tout == 0) {
+ DEBUG(2, ("FSRVP message sequence timeout disabled\n"));
+ *tmr_out = NULL;
+ return;
+ }
+
+ if (sc_set) {
+ /* don't use talloc_memdup(), need explicit type for callback */
+ sc_set_id = talloc(mem_ctx, struct GUID);
+ if (sc_set_id == NULL) {
+ smb_panic("no memory");
+ }
+ memcpy(sc_set_id, &sc_set->id, sizeof(*sc_set_id));
+ }
+
+ tmr = tevent_add_timer(global_event_context(),
+ mem_ctx,
+ timeval_current_ofs(tout, 0),
+ fss_seq_tout_handler, sc_set_id);
+ if (tmr == NULL) {
+ talloc_free(sc_set_id);
+ smb_panic("no memory");
+ }
+
+ *tmr_out = tmr;
+}
+
+uint32_t _fss_GetSupportedVersion(struct pipes_struct *p,
+ struct fss_GetSupportedVersion *r)
+{
+ if (!fss_permitted(p)) {
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ *r->out.MinVersion = fss_global.min_vers;
+ *r->out.MaxVersion = fss_global.max_vers;
+
+ return 0;
+}
+
+uint32_t _fss_SetContext(struct pipes_struct *p,
+ struct fss_SetContext *r)
+{
+ if (!fss_permitted(p)) {
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ /* ATTR_AUTO_RECOVERY flag can be applied to any */
+ switch (r->in.Context & (~ATTR_AUTO_RECOVERY)) {
+ case FSRVP_CTX_BACKUP:
+ DEBUG(6, ("fss ctx set backup\n"));
+ break;
+ case FSRVP_CTX_FILE_SHARE_BACKUP:
+ DEBUG(6, ("fss ctx set file share backup\n"));
+ break;
+ case FSRVP_CTX_NAS_ROLLBACK:
+ DEBUG(6, ("fss ctx set nas rollback\n"));
+ break;
+ case FSRVP_CTX_APP_ROLLBACK:
+ DEBUG(6, ("fss ctx set app rollback\n"));
+ break;
+ default:
+ DEBUG(0, ("invalid fss ctx set value: 0x%x\n", r->in.Context));
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ break; /* not reached */
+ }
+
+ fss_global.ctx_set = true;
+ fss_global.cur_ctx = r->in.Context;
+
+ TALLOC_FREE(fss_global.seq_tmr); /* kill timer if running */
+ fss_seq_tout_set(fss_global.mem_ctx, 180, NULL, &fss_global.seq_tmr);
+
+ fss_global.cur_ctx = r->in.Context;
+
+ return 0;
+}
+
+static bool sc_set_active(struct fss_sc_set *sc_set_head)
+{
+
+ struct fss_sc_set *sc_set;
+
+ for (sc_set = sc_set_head; sc_set; sc_set = sc_set->next) {
+ if ((sc_set->state != FSS_SC_EXPOSED)
+ && (sc_set->state != FSS_SC_RECOVERED)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+uint32_t _fss_StartShadowCopySet(struct pipes_struct *p,
+ struct fss_StartShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+ uint32_t ret;
+
+ if (!fss_permitted(p)) {
+ ret = HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ goto err_out;
+ }
+
+ if (!fss_global.ctx_set) {
+ DEBUG(3, ("invalid sequence: start sc set requested without "
+ "prior context set\n"));
+ ret = FSRVP_E_BAD_STATE;
+ goto err_out;
+ }
+
+ /*
+ * At any given time, Windows servers allow only one shadow copy set to
+ * be going through the creation process.
+ */
+ if (sc_set_active(fss_global.sc_sets)) {
+ DEBUG(3, ("StartShadowCopySet called while in progress\n"));
+ ret = FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS;
+ goto err_out;
+ }
+
+ /* stop msg seq timer */
+ TALLOC_FREE(fss_global.seq_tmr);
+
+ sc_set = talloc_zero(fss_global.mem_ctx, struct fss_sc_set);
+ if (sc_set == NULL) {
+ ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ goto err_tmr_restart;
+ }
+
+ sc_set->id = GUID_random(); /* Windows servers ignore client ids */
+ sc_set->id_str = GUID_string(sc_set, &sc_set->id);
+ if (sc_set->id_str == NULL) {
+ ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ goto err_sc_set_free;
+ }
+ sc_set->state = FSS_SC_STARTED;
+ sc_set->context = fss_global.cur_ctx;
+ DLIST_ADD_END(fss_global.sc_sets, sc_set);
+ fss_global.sc_sets_count++;
+ DEBUG(6, ("%s: shadow-copy set %u added\n",
+ sc_set->id_str, fss_global.sc_sets_count));
+
+ /* start msg seq timer */
+ fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr);
+
+ r->out.pShadowCopySetId = &sc_set->id;
+
+ return 0;
+
+err_sc_set_free:
+ talloc_free(sc_set);
+err_tmr_restart:
+ fss_seq_tout_set(fss_global.mem_ctx, 180, NULL, &fss_global.seq_tmr);
+err_out:
+ return ret;
+}
+
+static uint32_t map_share_name(struct fss_sc_smap *sc_smap,
+ const struct fss_sc *sc)
+{
+ bool hidden_base = false;
+
+ if (*(sc_smap->share_name + strlen(sc_smap->share_name) - 1) == '$') {
+ /*
+ * If MappedShare.ShareName ends with a $ character (meaning
+ * that the share is hidden), then the exposed share name will
+ * have the $ suffix appended.
+ * FIXME: turns out Windows doesn't do this, contrary to docs
+ */
+ hidden_base = true;
+ }
+
+ sc_smap->sc_share_name = talloc_asprintf(sc_smap, "%s@{%s}%s",
+ sc_smap->share_name,
+ sc->id_str,
+ hidden_base ? "$" : "");
+ if (sc_smap->sc_share_name == NULL) {
+ return HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ }
+
+ return 0;
+}
+
+static uint32_t map_share_comment(struct fss_sc_smap *sc_smap,
+ const struct fss_sc *sc)
+{
+ char *time_str;
+
+ time_str = http_timestring(sc_smap, sc->create_ts);
+ if (time_str == NULL) {
+ return HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ }
+
+ sc_smap->sc_share_comment = talloc_asprintf(sc_smap, "Shadow copy of %s taken %s",
+ sc_smap->share_name, time_str);
+ if (sc_smap->sc_share_comment == NULL) {
+ return HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ }
+
+ return 0;
+}
+
+uint32_t _fss_AddToShadowCopySet(struct pipes_struct *p,
+ struct fss_AddToShadowCopySet *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ uint32_t ret;
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ struct fss_sc_smap *sc_smap;
+ int snum;
+ char *service;
+ char *base_vol;
+ char *share;
+ char *path_name;
+ struct connection_struct *conn;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (!fss_permitted(p)) {
+ ret = HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ goto err_tmp_free;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ ret = HRES_ERROR_V(HRES_E_INVALIDARG);
+ goto err_tmp_free;
+ }
+
+ status = fss_unc_parse(frame, r->in.ShareName, NULL, &share);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = fss_ntstatus_map(status);
+ goto err_tmp_free;
+ }
+
+ snum = find_service(frame, share, &service);
+ if ((snum == -1) || (service == NULL)) {
+ DEBUG(0, ("share at %s not found\n", r->in.ShareName));
+ ret = HRES_ERROR_V(HRES_E_INVALIDARG);
+ goto err_tmp_free;
+ }
+
+ path_name = lp_path(frame, lp_sub, snum);
+ if (path_name == NULL) {
+ ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ goto err_tmp_free;
+ }
+
+ status = fss_conn_create_tos(p->msg_ctx, session_info, snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ goto err_tmp_free;
+ }
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ ret = HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ goto err_tmp_free;
+ }
+
+ status = SMB_VFS_SNAP_CHECK_PATH(conn, frame, path_name, &base_vol);
+ unbecome_user_without_service();
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = FSRVP_E_NOT_SUPPORTED;
+ goto err_tmp_free;
+ }
+
+ if ((sc_set->state != FSS_SC_STARTED)
+ && (sc_set->state != FSS_SC_ADDED)) {
+ ret = FSRVP_E_BAD_STATE;
+ goto err_tmp_free;
+ }
+
+ /* stop msg seq timer */
+ TALLOC_FREE(fss_global.seq_tmr);
+
+ /*
+ * server MUST look up the ShadowCopy in ShadowCopySet.ShadowCopyList
+ * where ShadowCopy.VolumeName matches the file store on which the
+ * share identified by ShareName is hosted. If an entry is found, the
+ * server MUST fail the call with FSRVP_E_OBJECT_ALREADY_EXISTS.
+ * If no entry is found, the server MUST create a new ShadowCopy
+ * object
+ * XXX Windows appears to allow multiple mappings for the same vol!
+ */
+ sc = sc_lookup_volname(sc_set->scs, base_vol);
+ if (sc != NULL) {
+ ret = FSRVP_E_OBJECT_ALREADY_EXISTS;
+ goto err_tmr_restart;
+ }
+
+ sc = talloc_zero(sc_set, struct fss_sc);
+ if (sc == NULL) {
+ ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ goto err_tmr_restart;
+ }
+ talloc_steal(sc, base_vol);
+ sc->volume_name = base_vol;
+ sc->sc_set = sc_set;
+ sc->create_ts = time(NULL);
+
+ sc->id = GUID_random(); /* Windows servers ignore client ids */
+ sc->id_str = GUID_string(sc, &sc->id);
+ if (sc->id_str == NULL) {
+ ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ goto err_sc_free;
+ }
+
+ sc_smap = talloc_zero(sc, struct fss_sc_smap);
+ if (sc_smap == NULL) {
+ ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ goto err_sc_free;
+ }
+
+ talloc_steal(sc_smap, service);
+ sc_smap->share_name = service;
+ sc_smap->is_exposed = false;
+ /*
+ * generate the sc_smap share name now. It is a unique identifier for
+ * the smap used as a tdb key for state storage.
+ */
+ ret = map_share_name(sc_smap, sc);
+ if (ret) {
+ goto err_sc_free;
+ }
+
+ /* add share map to shadow-copy */
+ DLIST_ADD_END(sc->smaps, sc_smap);
+ sc->smaps_count++;
+ /* add shadow-copy to shadow-copy set */
+ DLIST_ADD_END(sc_set->scs, sc);
+ sc_set->scs_count++;
+ DEBUG(4, ("added volume %s to shadow copy set with GUID %s\n",
+ sc->volume_name, sc_set->id_str));
+
+ /* start the Message Sequence Timer with timeout of 1800 seconds */
+ fss_seq_tout_set(fss_global.mem_ctx, 1800, sc_set, &fss_global.seq_tmr);
+
+ sc_set->state = FSS_SC_ADDED;
+ r->out.pShadowCopyId = &sc->id;
+
+ TALLOC_FREE(frame);
+ return 0;
+
+err_sc_free:
+ talloc_free(sc);
+err_tmr_restart:
+ fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr);
+err_tmp_free:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static NTSTATUS commit_sc_with_conn(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ struct fss_sc *sc,
+ char **base_path,
+ char **snap_path)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ bool rw;
+ struct connection_struct *conn;
+ int snum;
+ char *service;
+
+ snum = find_service(frame, sc->smaps->share_name, &service);
+ if ((snum == -1) || (service == NULL)) {
+ DEBUG(0, ("share at %s not found\n", sc->smaps->share_name));
+ TALLOC_FREE(frame);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = fss_conn_create_tos(msg_ctx, session_info, snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ rw = ((sc->sc_set->context & ATTR_AUTO_RECOVERY) == ATTR_AUTO_RECOVERY);
+ status = SMB_VFS_SNAP_CREATE(conn, mem_ctx,
+ sc->volume_name,
+ &sc->create_ts, rw,
+ base_path, snap_path);
+ unbecome_user_without_service();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("snap create failed: %s\n", nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+uint32_t _fss_CommitShadowCopySet(struct pipes_struct *p,
+ struct fss_CommitShadowCopySet *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ uint32_t commit_count;
+ NTSTATUS status;
+ NTSTATUS saved_status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!fss_permitted(p)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_tmp_free;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_tmp_free;
+ }
+
+ if (sc_set->state != FSS_SC_ADDED) {
+ status = NT_STATUS_INVALID_SERVER_STATE;
+ goto err_tmp_free;
+ }
+
+ /* stop Message Sequence Timer */
+ TALLOC_FREE(fss_global.seq_tmr);
+ sc_set->state = FSS_SC_CREATING;
+ commit_count = 0;
+ saved_status = NT_STATUS_OK;
+ for (sc = sc_set->scs; sc; sc = sc->next) {
+ char *base_path;
+ char *snap_path;
+ status = commit_sc_with_conn(frame, global_event_context(),
+ p->msg_ctx, session_info, sc,
+ &base_path, &snap_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("snap create failed for shadow copy of "
+ "%s\n", sc->volume_name));
+ /* dispatch all scs in set, but retain last error */
+ saved_status = status;
+ continue;
+ }
+ /* XXX set timeout r->in.TimeOutInMilliseconds */
+ commit_count++;
+ DEBUG(10, ("good snap create %d\n",
+ commit_count));
+ sc->sc_path = talloc_steal(sc, snap_path);
+ }
+ if (!NT_STATUS_IS_OK(saved_status)) {
+ status = saved_status;
+ goto err_state_revert;
+ }
+
+ sc_set->state = FSS_SC_COMMITED;
+ become_root();
+ status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets,
+ fss_global.sc_sets_count,
+ fss_global.db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to store fss server state: %s\n",
+ nt_errstr(status)));
+ }
+
+ fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set,
+ &fss_global.seq_tmr);
+ TALLOC_FREE(frame);
+ return 0;
+
+err_state_revert:
+ sc_set->state = FSS_SC_ADDED;
+ fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set,
+ &fss_global.seq_tmr);
+err_tmp_free:
+ TALLOC_FREE(frame);
+ return fss_ntstatus_map(status);
+}
+
+static sbcErr fss_conf_get_share_def(struct smbconf_ctx *fconf_ctx,
+ struct smbconf_ctx *rconf_ctx,
+ TALLOC_CTX *mem_ctx,
+ char *share,
+ struct smbconf_service **service_def)
+{
+ sbcErr cerr;
+ struct smbconf_service *def;
+
+ *service_def = NULL;
+ cerr = smbconf_get_share(fconf_ctx, mem_ctx, share, &def);
+ if (SBC_ERROR_IS_OK(cerr)) {
+ *service_def = def;
+ return SBC_ERR_OK;
+ }
+
+ cerr = smbconf_get_share(rconf_ctx, mem_ctx, share, &def);
+ if (SBC_ERROR_IS_OK(cerr)) {
+ *service_def = def;
+ return SBC_ERR_OK;
+ }
+ return cerr;
+}
+
+/*
+ * Expose a new share using libsmbconf, cloning the existing configuration
+ * from the base share. The base share may be defined in either the registry
+ * or smb.conf.
+ * XXX this is called as root
+ */
+static uint32_t fss_sc_expose(struct smbconf_ctx *fconf_ctx,
+ struct smbconf_ctx *rconf_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct fss_sc *sc)
+{
+ struct fss_sc_smap *sc_smap;
+ uint32_t err = 0;
+
+ for (sc_smap = sc->smaps; sc_smap; sc_smap = sc_smap->next) {
+ sbcErr cerr;
+ struct smbconf_service *base_service = NULL;
+ struct security_descriptor *sd;
+ size_t sd_size;
+
+ cerr = fss_conf_get_share_def(fconf_ctx, rconf_ctx, mem_ctx,
+ sc_smap->share_name, &base_service);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to get base share %s definition: "
+ "%s\n", sc_smap->share_name,
+ sbcErrorString(cerr)));
+ err = HRES_ERROR_V(HRES_E_FAIL);
+ break;
+ }
+
+ /* smap share name already defined when added */
+ err = map_share_comment(sc_smap, sc);
+ if (err) {
+ DEBUG(0, ("failed to map share comment\n"));
+ break;
+ }
+
+ base_service->name = sc_smap->sc_share_name;
+
+ cerr = smbconf_create_set_share(rconf_ctx, base_service);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to create share %s: %s\n",
+ base_service->name, sbcErrorString(cerr)));
+ err = HRES_ERROR_V(HRES_E_FAIL);
+ break;
+ }
+ cerr = smbconf_set_parameter(rconf_ctx, sc_smap->sc_share_name,
+ "path", sc->sc_path);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to set path param: %s\n",
+ sbcErrorString(cerr)));
+ err = HRES_ERROR_V(HRES_E_FAIL);
+ break;
+ }
+ if (sc_smap->sc_share_comment != NULL) {
+ cerr = smbconf_set_parameter(rconf_ctx,
+ sc_smap->sc_share_name,
+ "comment",
+ sc_smap->sc_share_comment);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed to set comment param: %s\n",
+ sbcErrorString(cerr)));
+ err = HRES_ERROR_V(HRES_E_FAIL);
+ break;
+ }
+ }
+ talloc_free(base_service);
+
+ /*
+ * Obtain the base share SD, which also needs to be cloned.
+ * Share SDs are stored in share_info.tdb, so are not covered by
+ * the registry transaction.
+ * The base share SD should be cloned at the time of exposure,
+ * rather than when the snapshot is taken. This matches Windows
+ * Server 2012 behaviour.
+ */
+ sd = get_share_security(mem_ctx, sc_smap->share_name, &sd_size);
+ if (sd == NULL) {
+ DEBUG(2, ("no share SD to clone for %s snapshot\n",
+ sc_smap->share_name));
+ } else {
+ NTSTATUS status;
+ status = set_share_security(sc_smap->sc_share_name, sd);
+ TALLOC_FREE(sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to set %s share SD\n",
+ sc_smap->sc_share_name));
+ err = HRES_ERROR_V(HRES_E_FAIL);
+ break;
+ }
+ }
+ }
+
+ return err;
+}
+
+uint32_t _fss_ExposeShadowCopySet(struct pipes_struct *p,
+ struct fss_ExposeShadowCopySet *r)
+{
+ NTSTATUS status;
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ uint32_t ret;
+ struct smbconf_ctx *fconf_ctx;
+ struct smbconf_ctx *rconf_ctx;
+ sbcErr cerr;
+ char *fconf_path;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!fss_permitted(p)) {
+ ret = HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ goto err_out;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ ret = HRES_ERROR_V(HRES_E_INVALIDARG);
+ goto err_out;
+ }
+
+ if (sc_set->state != FSS_SC_COMMITED) {
+ ret = FSRVP_E_BAD_STATE;
+ goto err_out;
+ }
+
+ /* stop message sequence timer */
+ TALLOC_FREE(fss_global.seq_tmr);
+
+ /*
+ * Prepare to clone the base share definition for the snapshot share.
+ * Create both registry and file conf contexts, as the base share
+ * definition may be located in either. The snapshot share definition
+ * is always written to the registry.
+ */
+ cerr = smbconf_init(frame, &rconf_ctx, "registry");
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed registry smbconf init: %s\n",
+ sbcErrorString(cerr)));
+ ret = HRES_ERROR_V(HRES_E_FAIL);
+ goto err_tmr_restart;
+ }
+ fconf_path = talloc_asprintf(frame, "file:%s", get_dyn_CONFIGFILE());
+ if (fconf_path == NULL) {
+ ret = HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ goto err_tmr_restart;
+ }
+ cerr = smbconf_init(frame, &fconf_ctx, fconf_path);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed %s smbconf init: %s\n",
+ fconf_path, sbcErrorString(cerr)));
+ ret = HRES_ERROR_V(HRES_E_FAIL);
+ goto err_tmr_restart;
+ }
+
+ /* registry IO must be done as root */
+ become_root();
+ cerr = smbconf_transaction_start(rconf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error starting transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = HRES_ERROR_V(HRES_E_FAIL);
+ unbecome_root();
+ goto err_tmr_restart;
+ }
+
+ for (sc = sc_set->scs; sc; sc = sc->next) {
+ ret = fss_sc_expose(fconf_ctx, rconf_ctx, frame, sc);
+ if (ret) {
+ DEBUG(0,("failed to expose shadow copy of %s\n",
+ sc->volume_name));
+ goto err_cancel;
+ }
+ }
+
+ cerr = smbconf_transaction_commit(rconf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error committing transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = HRES_ERROR_V(HRES_E_FAIL);
+ goto err_cancel;
+ }
+ unbecome_root();
+
+ messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0);
+ for (sc = sc_set->scs; sc; sc = sc->next) {
+ struct fss_sc_smap *sm;
+ for (sm = sc->smaps; sm; sm = sm->next)
+ sm->is_exposed = true;
+ }
+ sc_set->state = FSS_SC_EXPOSED;
+ become_root();
+ status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets,
+ fss_global.sc_sets_count, fss_global.db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to store fss server state: %s\n",
+ nt_errstr(status)));
+ }
+ /* start message sequence timer */
+ fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr);
+ TALLOC_FREE(frame);
+ return 0;
+
+err_cancel:
+ smbconf_transaction_cancel(rconf_ctx);
+ unbecome_root();
+err_tmr_restart:
+ fss_seq_tout_set(fss_global.mem_ctx, 180, sc_set, &fss_global.seq_tmr);
+err_out:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+uint32_t _fss_RecoveryCompleteShadowCopySet(struct pipes_struct *p,
+ struct fss_RecoveryCompleteShadowCopySet *r)
+{
+ NTSTATUS status;
+ struct fss_sc_set *sc_set;
+
+ if (!fss_permitted(p)) {
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ if (sc_set->state != FSS_SC_EXPOSED) {
+ return FSRVP_E_BAD_STATE;
+ }
+
+ /* stop msg sequence timer */
+ TALLOC_FREE(fss_global.seq_tmr);
+
+ if (sc_set->context & ATTR_NO_AUTO_RECOVERY) {
+ /* TODO set read-only */
+ }
+
+ sc_set->state = FSS_SC_RECOVERED;
+ fss_global.cur_ctx = 0;
+ fss_global.ctx_set = false;
+
+ become_root();
+ status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets,
+ fss_global.sc_sets_count, fss_global.db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to store fss server state: %s\n",
+ nt_errstr(status)));
+ }
+
+ return 0;
+}
+
+uint32_t _fss_AbortShadowCopySet(struct pipes_struct *p,
+ struct fss_AbortShadowCopySet *r)
+{
+ NTSTATUS status;
+ struct fss_sc_set *sc_set;
+
+ if (!fss_permitted(p)) {
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ DEBUG(6, ("%s: aborting shadow-copy set\n", sc_set->id_str));
+
+ if ((sc_set->state == FSS_SC_COMMITED)
+ || (sc_set->state == FSS_SC_EXPOSED)
+ || (sc_set->state == FSS_SC_RECOVERED)) {
+ return 0;
+ }
+
+ if (sc_set->state == FSS_SC_CREATING) {
+ return FSRVP_E_BAD_STATE;
+ }
+
+ DLIST_REMOVE(fss_global.sc_sets, sc_set);
+ talloc_free(sc_set);
+ fss_global.sc_sets_count--;
+ become_root();
+ status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets,
+ fss_global.sc_sets_count, fss_global.db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to store fss server state: %s\n",
+ nt_errstr(status)));
+ }
+
+ return 0;
+}
+
+uint32_t _fss_IsPathSupported(struct pipes_struct *p,
+ struct fss_IsPathSupported *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ int snum;
+ char *service;
+ char *base_vol;
+ NTSTATUS status;
+ struct connection_struct *conn;
+ char *share;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (!fss_permitted(p)) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ status = fss_unc_parse(frame, r->in.ShareName, NULL, &share);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return fss_ntstatus_map(status);
+ }
+
+ snum = find_service(frame, share, &service);
+ if ((snum == -1) || (service == NULL)) {
+ DEBUG(0, ("share at %s not found\n", r->in.ShareName));
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ status = fss_conn_create_tos(p->msg_ctx, session_info, snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+ status = SMB_VFS_SNAP_CHECK_PATH(conn, frame,
+ lp_path(frame, lp_sub, snum),
+ &base_vol);
+ unbecome_user_without_service();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return FSRVP_E_NOT_SUPPORTED;
+ }
+
+ *r->out.OwnerMachineName = lp_netbios_name();
+ *r->out.SupportedByThisProvider = 1;
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+uint32_t _fss_IsPathShadowCopied(struct pipes_struct *p,
+ struct fss_IsPathShadowCopied *r)
+{
+ if (!fss_permitted(p)) {
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ /* not yet supported */
+ return FSRVP_E_NOT_SUPPORTED;
+}
+
+uint32_t _fss_GetShareMapping(struct pipes_struct *p,
+ struct fss_GetShareMapping *r)
+{
+ NTSTATUS status;
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ struct fss_sc_smap *sc_smap;
+ char *share;
+ struct fssagent_share_mapping_1 *sm_out;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!fss_permitted(p)) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ /*
+ * If ShadowCopySet.Status is not "Exposed", the server SHOULD<9> fail
+ * the call with FSRVP_E_BAD_STATE.
+ * <9> If ShadowCopySet.Status is "Started", "Added",
+ * "CreationInProgress", or "Committed", Windows Server 2012 FSRVP
+ * servers return an error value of 0x80042311.
+ */
+ if ((sc_set->state == FSS_SC_STARTED)
+ || (sc_set->state == FSS_SC_ADDED)
+ || (sc_set->state == FSS_SC_CREATING)
+ || (sc_set->state == FSS_SC_COMMITED)) {
+ TALLOC_FREE(frame);
+ return 0x80042311; /* documented magic value */
+ }
+
+ sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId);
+ if (sc == NULL) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ status = fss_unc_parse(frame, r->in.ShareName, NULL, &share);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return fss_ntstatus_map(status);
+ }
+
+ sc_smap = sc_smap_lookup(sc->smaps, share);
+ if (sc_smap == NULL) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ if (r->in.Level != 1) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ sm_out = talloc_zero(p->mem_ctx, struct fssagent_share_mapping_1);
+ if (sm_out == NULL) {
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ }
+ sm_out->ShadowCopySetId = sc_set->id;
+ sm_out->ShadowCopyId = sc->id;
+ sm_out->ShareNameUNC = talloc_asprintf(sm_out, "\\\\%s\\%s",
+ lp_netbios_name(),
+ sc_smap->share_name);
+ if (sm_out->ShareNameUNC == NULL) {
+ talloc_free(sm_out);
+ TALLOC_FREE(frame);
+ return HRES_ERROR_V(HRES_E_OUTOFMEMORY);
+ }
+ sm_out->ShadowCopyShareName = sc_smap->sc_share_name;
+ unix_to_nt_time(&sm_out->tstamp, sc->create_ts);
+ r->out.ShareMapping->ShareMapping1 = sm_out;
+ TALLOC_FREE(frame);
+
+ /* reset msg sequence timer */
+ TALLOC_FREE(fss_global.seq_tmr);
+ fss_seq_tout_set(fss_global.mem_ctx, 1800, sc_set, &fss_global.seq_tmr);
+
+ return 0;
+}
+
+static NTSTATUS sc_smap_unexpose(struct messaging_context *msg_ctx,
+ struct fss_sc_smap *sc_smap, bool delete_all)
+{
+ NTSTATUS ret;
+ struct smbconf_ctx *conf_ctx;
+ sbcErr cerr;
+ bool is_modified = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ cerr = smbconf_init(frame, &conf_ctx, "registry");
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("failed registry smbconf init: %s\n",
+ sbcErrorString(cerr)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto err_tmp;
+ }
+
+ /* registry IO must be done as root */
+ become_root();
+
+ cerr = smbconf_transaction_start(conf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error starting transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto err_conf;
+ }
+
+ while (sc_smap) {
+ struct fss_sc_smap *sc_map_next = sc_smap->next;
+ if (!smbconf_share_exists(conf_ctx, sc_smap->sc_share_name)) {
+ DEBUG(2, ("no such share: %s\n", sc_smap->sc_share_name));
+ if (!delete_all) {
+ ret = NT_STATUS_OK;
+ goto err_cancel;
+ }
+ sc_smap = sc_map_next;
+ continue;
+ }
+
+ cerr = smbconf_delete_share(conf_ctx, sc_smap->sc_share_name);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error deleting share: %s\n",
+ sbcErrorString(cerr)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto err_cancel;
+ }
+ is_modified = true;
+ sc_smap->is_exposed = false;
+ if (delete_all) {
+ sc_smap = sc_map_next;
+ } else {
+ sc_smap = NULL; /* only process single sc_map entry */
+ }
+ }
+ if (is_modified) {
+ cerr = smbconf_transaction_commit(conf_ctx);
+ if (!SBC_ERROR_IS_OK(cerr)) {
+ DEBUG(0, ("error committing transaction: %s\n",
+ sbcErrorString(cerr)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto err_cancel;
+ }
+ messaging_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0);
+ } else {
+ ret = NT_STATUS_OK;
+ goto err_cancel;
+ }
+ ret = NT_STATUS_OK;
+
+err_conf:
+ talloc_free(conf_ctx);
+ unbecome_root();
+err_tmp:
+ TALLOC_FREE(frame);
+ return ret;
+
+err_cancel:
+ smbconf_transaction_cancel(conf_ctx);
+ talloc_free(conf_ctx);
+ unbecome_root();
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+uint32_t _fss_DeleteShareMapping(struct pipes_struct *p,
+ struct fss_DeleteShareMapping *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct fss_sc_set *sc_set;
+ struct fss_sc *sc;
+ struct fss_sc_smap *sc_smap;
+ char *share;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct connection_struct *conn;
+ int snum;
+ char *service;
+
+ if (!fss_permitted(p)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_tmp_free;
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ /* docs say HRES_E_INVALIDARG */
+ status = NT_STATUS_OBJECTID_NOT_FOUND;
+ goto err_tmp_free;
+ }
+
+ if ((sc_set->state != FSS_SC_EXPOSED)
+ && (sc_set->state != FSS_SC_RECOVERED)) {
+ status = NT_STATUS_INVALID_SERVER_STATE;
+ goto err_tmp_free;
+ }
+
+ sc = sc_lookup(sc_set->scs, &r->in.ShadowCopyId);
+ if (sc == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_tmp_free;
+ }
+
+ status = fss_unc_parse(frame, r->in.ShareName, NULL, &share);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_tmp_free;
+ }
+
+ sc_smap = sc_smap_lookup(sc->smaps, share);
+ if (sc_smap == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err_tmp_free;
+ }
+
+ status = sc_smap_unexpose(p->msg_ctx, sc_smap, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to remove share %s: %s\n",
+ sc_smap->sc_share_name, nt_errstr(status)));
+ goto err_tmp_free;
+ }
+
+ messaging_send_all(p->msg_ctx, MSG_SMB_FORCE_TDIS,
+ sc_smap->sc_share_name,
+ strlen(sc_smap->sc_share_name) + 1);
+
+ if (sc->smaps_count > 1) {
+ /* do not delete the underlying snapshot - still in use */
+ status = NT_STATUS_OK;
+ goto err_tmp_free;
+ }
+
+ snum = find_service(frame, sc_smap->share_name, &service);
+ if ((snum == -1) || (service == NULL)) {
+ DEBUG(0, ("share at %s not found\n", sc_smap->share_name));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_tmp_free;
+ }
+
+ status = fss_conn_create_tos(p->msg_ctx, session_info, snum, &conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_tmp_free;
+ }
+ if (!become_user_without_service_by_session(conn, session_info)) {
+ DEBUG(0, ("failed to become user\n"));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_tmp_free;
+ }
+
+ status = SMB_VFS_SNAP_DELETE(conn, frame, sc->volume_name,
+ sc->sc_path);
+ unbecome_user_without_service();
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_tmp_free;
+ }
+
+ /* XXX set timeout r->in.TimeOutInMilliseconds */
+ DEBUG(6, ("good snap delete\n"));
+ DLIST_REMOVE(sc->smaps, sc_smap);
+ sc->smaps_count--;
+ talloc_free(sc_smap);
+ if (sc->smaps_count == 0) {
+ DLIST_REMOVE(sc_set->scs, sc);
+ sc_set->scs_count--;
+ talloc_free(sc);
+
+ if (sc_set->scs_count == 0) {
+ DLIST_REMOVE(fss_global.sc_sets, sc_set);
+ fss_global.sc_sets_count--;
+ talloc_free(sc_set);
+ }
+ }
+
+ become_root();
+ status = fss_state_store(fss_global.mem_ctx, fss_global.sc_sets,
+ fss_global.sc_sets_count, fss_global.db_path);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to store fss server state: %s\n",
+ nt_errstr(status)));
+ }
+
+ status = NT_STATUS_OK;
+err_tmp_free:
+ TALLOC_FREE(frame);
+ return fss_ntstatus_map(status);
+}
+
+uint32_t _fss_PrepareShadowCopySet(struct pipes_struct *p,
+ struct fss_PrepareShadowCopySet *r)
+{
+ struct fss_sc_set *sc_set;
+
+ if (!fss_permitted(p)) {
+ return HRES_ERROR_V(HRES_E_ACCESSDENIED);
+ }
+
+ sc_set = sc_set_lookup(fss_global.sc_sets, &r->in.ShadowCopySetId);
+ if (sc_set == NULL) {
+ return HRES_ERROR_V(HRES_E_INVALIDARG);
+ }
+
+ if (sc_set->state != FSS_SC_ADDED) {
+ return FSRVP_E_BAD_STATE;
+ }
+
+ /* stop msg sequence timer */
+ TALLOC_FREE(fss_global.seq_tmr);
+
+ /*
+ * Windows Server "8" Beta takes ~60s here, presumably flushing
+ * everything to disk. We may want to do something similar.
+ */
+
+ /* start msg sequence timer, 1800 on success */
+ fss_seq_tout_set(fss_global.mem_ctx, 1800, sc_set, &fss_global.seq_tmr);
+
+ return 0;
+}
+
+static NTSTATUS FileServerVssAgent__op_init_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+static NTSTATUS FileServerVssAgent__op_shutdown_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+#define DCESRV_INTERFACE_FILESERVERVSSAGENT_INIT_SERVER \
+ fileservervssagent_init_server
+
+#define DCESRV_INTERFACE_FILESERVERVSSAGENT_SHUTDOWN_SERVER \
+ fileservervssagent_shutdown_server
+
+static NTSTATUS fileservervssagent_shutdown_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ srv_fssa_cleanup();
+ return FileServerVssAgent__op_shutdown_server(dce_ctx, ep_server);
+}
+
+static NTSTATUS fileservervssagent_init_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ NTSTATUS status;
+ struct messaging_context *msg_ctx = global_messaging_context();
+
+ status = srv_fssa_start(msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return FileServerVssAgent__op_init_server(dce_ctx, ep_server);
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_fsrvp_scompat.c"
diff --git a/source3/rpc_server/fss/srv_fss_private.h b/source3/rpc_server/fss/srv_fss_private.h
new file mode 100644
index 0000000..4db9f98
--- /dev/null
+++ b/source3/rpc_server/fss/srv_fss_private.h
@@ -0,0 +1,92 @@
+/*
+ * File Server Remote VSS Protocol (FSRVP) server state
+ *
+ * Copyright (C) David Disseldorp 2012-2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SRV_FSS_PRIVATE_H_
+#define _SRV_FSS_PRIVATE_H_
+
+#define FSS_DB_NAME "srv_fss.tdb"
+
+struct fss_sc_smap {
+ struct fss_sc_smap *next, *prev;
+ char *share_name; /* name of the base file share */
+ char *sc_share_name; /* share exposing the shadow copy */
+ char *sc_share_comment;
+ bool is_exposed; /* whether shadow copy is exposed */
+};
+
+struct fss_sc {
+ struct fss_sc *next, *prev;
+ struct GUID id; /* GUID of the shadow copy */
+ char *id_str;
+ char *volume_name; /* name uniquely identifying on the
+ * server object store on which this
+ * shadow copy is created. */
+ char *sc_path; /* path exposing the shadow copy */
+ time_t create_ts; /* timestamp of client initiation */
+ struct fss_sc_smap *smaps; /* shares mapped to this shadow copy */
+ uint32_t smaps_count;
+ struct fss_sc_set *sc_set; /* parent shadow copy set */
+};
+
+/*
+ * 3.1.1.2: Per ShadowCopySet
+ * The status of the shadow copy set. This MUST be one of "Started", "Added",
+ * "CreationInProgress", "Committed", "Exposed", or "Recovered".
+ */
+enum fss_sc_state {
+ FSS_SC_STARTED,
+ FSS_SC_ADDED,
+ FSS_SC_CREATING,
+ FSS_SC_COMMITED,
+ FSS_SC_EXPOSED,
+ FSS_SC_RECOVERED,
+};
+struct fss_sc_set {
+ struct fss_sc_set *next, *prev;
+ struct GUID id; /* GUID of the shadow copy set. */
+ char *id_str;
+ enum fss_sc_state state; /* status of the shadow copy set */
+ uint32_t context; /* attributes used for set creation */
+ struct fss_sc *scs; /* list of ShadowCopy objects */
+ uint32_t scs_count;
+};
+
+struct fss_global {
+ TALLOC_CTX *mem_ctx; /* parent mem ctx for sc sets */
+ char *db_path;
+ uint32_t min_vers;
+ uint32_t max_vers;
+ bool ctx_set; /* whether client has set context */
+ uint32_t cur_ctx;
+ struct fss_sc_set *sc_sets;
+ uint32_t sc_sets_count;
+ struct tevent_timer *seq_tmr; /* time to wait between client reqs */
+};
+
+NTSTATUS fss_state_store(TALLOC_CTX *mem_ctx,
+ struct fss_sc_set *sc_sets,
+ uint32_t sc_sets_count,
+ const char *db_path);
+
+NTSTATUS fss_state_retrieve(TALLOC_CTX *mem_ctx,
+ struct fss_sc_set **sc_sets,
+ uint32_t *sc_sets_count,
+ const char *db_path);
+
+#endif /*_SRV_FSS_PRIVATE_H_ */
diff --git a/source3/rpc_server/fss/srv_fss_state.c b/source3/rpc_server/fss/srv_fss_state.c
new file mode 100644
index 0000000..8597c36
--- /dev/null
+++ b/source3/rpc_server/fss/srv_fss_state.c
@@ -0,0 +1,698 @@
+/*
+ * File Server Remote VSS Protocol (FSRVP) persistent server state
+ *
+ * Copyright (C) David Disseldorp 2012-2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "source3/include/includes.h"
+#include <fcntl.h>
+#include "source3/include/util_tdb.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_fsrvp_state.h"
+#include "srv_fss_private.h"
+
+#define FSS_DB_KEY_VERSION "db_version"
+#define FSS_DB_KEY_CONTEXT "context"
+#define FSS_DB_KEY_SC_SET_COUNT "sc_set_count"
+#define FSS_DB_KEY_PFX_SC_SET "sc_set/"
+#define FSS_DB_KEY_PFX_SC "sc/"
+#define FSS_DB_KEY_PFX_SMAP "smap/"
+
+static NTSTATUS fss_state_smap_store(TALLOC_CTX *mem_ctx,
+ struct db_context *db,
+ const char *sc_key_str,
+ struct fss_sc_smap *smap)
+{
+ NTSTATUS status;
+ TDB_DATA val;
+ const char *smap_key_str;
+ struct fsrvp_state_smap smap_state;
+ enum ndr_err_code ndr_ret;
+ DATA_BLOB smap_state_blob;
+
+ /* becomes sc_set/@sc_set_id/sc/@sc_id/smap/@sc_share_name */
+ smap_key_str = talloc_asprintf(mem_ctx, "%s/%s%s", sc_key_str,
+ FSS_DB_KEY_PFX_SMAP,
+ smap->sc_share_name);
+ if (smap_key_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smap_state.share_name = smap->share_name;
+ smap_state.sc_share_name = smap->sc_share_name;
+ /* @smap->sc_share_comment may be null if not exposed. */
+ if (smap->sc_share_comment != NULL) {
+ smap_state.sc_share_comment = smap->sc_share_comment;
+ } else {
+ smap_state.sc_share_comment = "";
+ }
+ smap_state.is_exposed = smap->is_exposed;
+
+ ndr_ret = ndr_push_struct_blob(&smap_state_blob, mem_ctx,
+ &smap_state,
+ (ndr_push_flags_fn_t)ndr_push_fsrvp_state_smap);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ val.dsize = smap_state_blob.length;
+ val.dptr = smap_state_blob.data;
+
+ status = dbwrap_store(db, string_term_tdb_data(smap_key_str), val, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fss_state_sc_store(TALLOC_CTX *mem_ctx,
+ struct db_context *db,
+ const char *sc_set_key_str,
+ struct fss_sc *sc)
+{
+ NTSTATUS status;
+ TDB_DATA val;
+ const char *sc_key_str;
+ struct fsrvp_state_sc sc_state;
+ struct fss_sc_smap *smap;
+ enum ndr_err_code ndr_ret;
+ DATA_BLOB sc_state_blob;
+
+ /* becomes sc_set/@sc_set.id/sc/@sc_id */
+ sc_key_str = talloc_asprintf(mem_ctx, "%s/%s%s", sc_set_key_str,
+ FSS_DB_KEY_PFX_SC, sc->id_str);
+ if (sc_key_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sc_state.id_str = sc->id_str;
+ sc_state.volume_name = sc->volume_name;
+ /* @sc->sc_path may be null if not committed, store empty str */
+ sc_state.sc_path = (sc->sc_path ? sc->sc_path : "");
+ sc_state.create_ts = sc->create_ts;
+ sc_state.smaps_count = sc->smaps_count;
+
+ ndr_ret = ndr_push_struct_blob(&sc_state_blob, mem_ctx,
+ &sc_state,
+ (ndr_push_flags_fn_t)ndr_push_fsrvp_state_sc);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ val.dsize = sc_state_blob.length;
+ val.dptr = sc_state_blob.data;
+
+ status = dbwrap_store(db, string_term_tdb_data(sc_key_str), val, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (smap = sc->smaps; smap; smap = smap->next) {
+ status = fss_state_smap_store(mem_ctx, db, sc_key_str, smap);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fss_state_sc_set_store(TALLOC_CTX *mem_ctx,
+ struct db_context *db,
+ struct fss_sc_set *sc_set)
+{
+ NTSTATUS status;
+ TDB_DATA val;
+ const char *sc_set_key_str;
+ struct fss_sc *sc;
+ struct fsrvp_state_sc_set sc_set_state;
+ DATA_BLOB sc_set_state_blob;
+ enum ndr_err_code ndr_ret;
+
+ sc_set_key_str = talloc_asprintf(mem_ctx, "%s%s",
+ FSS_DB_KEY_PFX_SC_SET,
+ sc_set->id_str);
+ if (sc_set_key_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sc_set_state.id_str = sc_set->id_str;
+ sc_set_state.state = sc_set->state;
+ sc_set_state.context = sc_set->context;
+ sc_set_state.scs_count = sc_set->scs_count;
+
+ ndr_ret = ndr_push_struct_blob(&sc_set_state_blob, mem_ctx,
+ &sc_set_state,
+ (ndr_push_flags_fn_t)ndr_push_fsrvp_state_sc_set);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ val.dsize = sc_set_state_blob.length;
+ val.dptr = sc_set_state_blob.data;
+
+ status = dbwrap_store(db, string_term_tdb_data(sc_set_key_str), val, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (sc = sc_set->scs; sc; sc = sc->next) {
+ status = fss_state_sc_store(mem_ctx, db, sc_set_key_str, sc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * write out the current fsrvp server state to a TDB. This clears any content
+ * currently written to the TDB.
+ */
+_PRIVATE_ NTSTATUS fss_state_store(TALLOC_CTX *mem_ctx,
+ struct fss_sc_set *sc_sets,
+ uint32_t sc_sets_count,
+ const char *db_path)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct db_context *db;
+ NTSTATUS status;
+ int ret;
+ struct fss_sc_set *sc_set;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db = db_open(tmp_ctx, db_path, 0, TDB_DEFAULT, O_RDWR | O_CREAT,
+ 0600, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ DEBUG(0, ("Failed to open fss state database %s\n", db_path));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_ctx_free;
+ }
+
+ ret = dbwrap_wipe(db);
+ if (ret != 0) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_db_free;
+ }
+
+ status = dbwrap_store_int32_bystring(db, FSS_DB_KEY_VERSION,
+ FSRVP_STATE_DB_VERSION);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_db_free;
+ }
+
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_db_free;
+ }
+
+ status = dbwrap_store_int32_bystring(db, FSS_DB_KEY_SC_SET_COUNT,
+ sc_sets_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_trans_cancel;
+ }
+
+ for (sc_set = sc_sets; sc_set; sc_set = sc_set->next) {
+ status = fss_state_sc_set_store(tmp_ctx, db, sc_set);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_trans_cancel;
+ }
+ }
+
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_trans_cancel;
+ }
+
+ talloc_free(db);
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+
+err_trans_cancel:
+ dbwrap_transaction_cancel(db);
+err_db_free:
+ talloc_free(db);
+err_ctx_free:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS fss_state_smap_retrieve(TALLOC_CTX *mem_ctx,
+ TDB_DATA *key,
+ TDB_DATA *val,
+ struct fss_sc_smap **smap_out)
+{
+ struct fss_sc_smap *smap;
+ struct fsrvp_state_smap smap_state;
+ DATA_BLOB smap_state_blob;
+ enum ndr_err_code ndr_ret;
+
+ smap_state_blob.length = val->dsize;
+ smap_state_blob.data = val->dptr;
+
+ ndr_ret = ndr_pull_struct_blob(&smap_state_blob, mem_ctx, &smap_state,
+ (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_smap);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ smap = talloc_zero(mem_ctx, struct fss_sc_smap);
+ if (smap == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smap->share_name = talloc_strdup(smap, smap_state.share_name);
+ if (smap->share_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* store the full path so that the hierarchy can be rebuilt */
+ smap->sc_share_name = talloc_strdup(smap, (char *)key->dptr);
+ if (smap->sc_share_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* sc_share_comment may be empty, keep null in such a case */
+ if (strlen(smap_state.sc_share_comment) > 0) {
+ smap->sc_share_comment = talloc_strdup(smap,
+ smap_state.sc_share_comment);
+ if (smap->sc_share_comment == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ smap->is_exposed = smap_state.is_exposed;
+
+ *smap_out = smap;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fss_state_sc_retrieve(TALLOC_CTX *mem_ctx,
+ TDB_DATA *key,
+ TDB_DATA *val,
+ struct fss_sc **sc_out)
+{
+ struct fss_sc *sc;
+ struct fsrvp_state_sc sc_state;
+ DATA_BLOB sc_state_blob;
+ enum ndr_err_code ndr_ret;
+
+ sc_state_blob.length = val->dsize;
+ sc_state_blob.data = val->dptr;
+
+ ndr_ret = ndr_pull_struct_blob(&sc_state_blob, mem_ctx, &sc_state,
+ (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_sc);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ sc = talloc_zero(mem_ctx, struct fss_sc);
+ if (sc == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* store the full path so that the hierarchy can be rebuilt */
+ sc->id_str = talloc_strdup(sc, (char *)key->dptr);
+ if (sc->id_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sc->volume_name = talloc_strdup(sc, sc_state.volume_name);
+ if (sc->volume_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* sc_path may be empty, keep null in such a case */
+ if (strlen(sc_state.sc_path) > 0) {
+ sc->sc_path = talloc_strdup(sc, sc_state.sc_path);
+ if (sc->sc_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ sc->create_ts = sc_state.create_ts;
+ sc->smaps_count = sc_state.smaps_count;
+
+ *sc_out = sc;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fss_state_sc_set_retrieve(TALLOC_CTX *mem_ctx,
+ TDB_DATA *key,
+ TDB_DATA *val,
+ struct fss_sc_set **sc_set_out)
+{
+ struct fss_sc_set *sc_set;
+ struct fsrvp_state_sc_set sc_set_state;
+ DATA_BLOB sc_set_state_blob;
+ enum ndr_err_code ndr_ret;
+
+ sc_set_state_blob.length = val->dsize;
+ sc_set_state_blob.data = val->dptr;
+
+ ndr_ret = ndr_pull_struct_blob(&sc_set_state_blob, mem_ctx,
+ &sc_set_state,
+ (ndr_pull_flags_fn_t)ndr_pull_fsrvp_state_sc_set);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ sc_set = talloc_zero(mem_ctx, struct fss_sc_set);
+ if (sc_set == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* store the full path so that the hierarchy can be rebuilt */
+ sc_set->id_str = talloc_strdup(sc_set, (char *)key->dptr);
+ if (sc_set->id_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ sc_set->state = sc_set_state.state;
+ sc_set->context = sc_set_state.context;
+ sc_set->scs_count = sc_set_state.scs_count;
+
+ *sc_set_out = sc_set;
+ return NT_STATUS_OK;
+}
+
+struct fss_traverse_state {
+ TALLOC_CTX *mem_ctx;
+ struct fss_sc_smap *smaps;
+ uint32_t smaps_count;
+ struct fss_sc *scs;
+ uint32_t scs_count;
+ struct fss_sc_set *sc_sets;
+ uint32_t sc_sets_count;
+ NTSTATUS (*smap_retrieve)(TALLOC_CTX *mem_ctx,
+ TDB_DATA *key,
+ TDB_DATA *val,
+ struct fss_sc_smap **smap_out);
+ NTSTATUS (*sc_retrieve)(TALLOC_CTX *mem_ctx,
+ TDB_DATA *key,
+ TDB_DATA *val,
+ struct fss_sc **sc_out);
+ NTSTATUS (*sc_set_retrieve)(TALLOC_CTX *mem_ctx,
+ TDB_DATA *key,
+ TDB_DATA *val,
+ struct fss_sc_set **sc_set_out);
+};
+
+static int fss_state_retrieve_traverse(struct db_record *rec,
+ void *private_data)
+{
+ NTSTATUS status;
+ struct fss_traverse_state *trv_state
+ = (struct fss_traverse_state *)private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+
+ /* order of checking is important here */
+ if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SMAP) != NULL) {
+ struct fss_sc_smap *smap;
+ status = trv_state->smap_retrieve(trv_state->mem_ctx,
+ &key, &val, &smap);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ DLIST_ADD_END(trv_state->smaps, smap);
+ trv_state->smaps_count++;
+ } else if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SC) != NULL) {
+ struct fss_sc *sc;
+ status = trv_state->sc_retrieve(trv_state->mem_ctx,
+ &key, &val, &sc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ DLIST_ADD_END(trv_state->scs, sc);
+ trv_state->scs_count++;
+ } else if (strstr((char *)key.dptr, FSS_DB_KEY_PFX_SC_SET) != NULL) {
+ struct fss_sc_set *sc_set;
+ status = trv_state->sc_set_retrieve(trv_state->mem_ctx,
+ &key, &val, &sc_set);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ DLIST_ADD_END(trv_state->sc_sets, sc_set);
+ trv_state->sc_sets_count++;
+ } else {
+ /* global context and db vers */
+ DEBUG(4, ("Ignoring fss srv db entry with key %s\n", key.dptr));
+ }
+
+ return 0;
+}
+
+static bool fss_state_smap_is_child(struct fss_sc *sc,
+ struct fss_sc_smap *smap)
+{
+ return (strstr(smap->sc_share_name, sc->id_str) != NULL);
+}
+
+static NTSTATUS fss_state_hierarchize_smaps(struct fss_traverse_state *trv_state,
+ struct fss_sc *sc)
+{
+ struct fss_sc_smap *smap;
+ struct fss_sc_smap *smap_n;
+ uint32_t smaps_moved = 0;
+
+ for (smap = trv_state->smaps; smap; smap = smap_n) {
+ smap_n = smap->next;
+ if (!fss_state_smap_is_child(sc, smap))
+ continue;
+
+ /* smap mem should be owned by parent sc */
+ talloc_steal(sc, smap);
+ DLIST_REMOVE(trv_state->smaps, smap);
+ trv_state->smaps_count--;
+ DLIST_ADD_END(sc->smaps, smap);
+ smaps_moved++;
+
+ /* last component of the tdb key path is the sc share name */
+ SMB_ASSERT(strrchr(smap->sc_share_name, '/') != NULL);
+ smap->sc_share_name = strrchr(smap->sc_share_name, '/') + 1;
+ }
+
+ if (sc->smaps_count != smaps_moved) {
+ DEBUG(0, ("Inconsistent smaps_count, expected %u, moved %u\n",
+ sc->smaps_count, smaps_moved));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool fss_state_sc_is_child(struct fss_sc_set *sc_set,
+ struct fss_sc *sc)
+{
+ return (strstr(sc->id_str, sc_set->id_str) != NULL);
+}
+
+static NTSTATUS fss_state_hierarchize_scs(struct fss_traverse_state *trv_state,
+ struct fss_sc_set *sc_set)
+{
+ NTSTATUS status;
+ struct fss_sc *sc;
+ struct fss_sc *sc_n;
+ uint32_t scs_moved = 0;
+
+ for (sc = trv_state->scs; sc; sc = sc_n) {
+ sc_n = sc->next;
+ if (!fss_state_sc_is_child(sc_set, sc))
+ continue;
+
+ /* sc mem should be owned by parent sc_set */
+ talloc_steal(sc_set, sc);
+ DLIST_REMOVE(trv_state->scs, sc);
+ trv_state->scs_count--;
+ DLIST_ADD_END(sc_set->scs, sc);
+ scs_moved++;
+
+ sc->sc_set = sc_set;
+
+ /* last component of the tdb key path is the sc GUID str */
+ SMB_ASSERT(strrchr(sc->id_str, '/') != NULL);
+ sc->id_str = strrchr(sc->id_str, '/') + 1;
+
+ status = GUID_from_string(sc->id_str, &sc->id);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = fss_state_hierarchize_smaps(trv_state, sc);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+ }
+
+ if (sc_set->scs_count != scs_moved) {
+ DEBUG(0, ("Inconsistent scs_count, expected %u, moved %u\n",
+ sc_set->scs_count, scs_moved));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_out;
+ }
+
+ return NT_STATUS_OK;
+
+err_out:
+ return status;
+}
+
+static NTSTATUS fss_state_hierarchize(struct fss_traverse_state *trv_state,
+ struct fss_sc_set **sc_sets,
+ uint32_t *sc_sets_count)
+{
+ NTSTATUS status;
+ struct fss_sc_set *sc_set;
+ struct fss_sc_set *sc_set_n;
+ uint32_t i = 0;
+
+ *sc_sets = NULL;
+ for (sc_set = trv_state->sc_sets; sc_set; sc_set = sc_set_n) {
+ sc_set_n = sc_set->next;
+ /* sc_set mem already owned by trv_state->mem_ctx */
+ DLIST_REMOVE(trv_state->sc_sets, sc_set);
+ trv_state->sc_sets_count--;
+ DLIST_ADD_END(*sc_sets, sc_set);
+ i++;
+
+ /* last component of the tdb key path is the sc_set GUID str */
+ SMB_ASSERT(strrchr(sc_set->id_str, '/') != NULL);
+ sc_set->id_str = strrchr(sc_set->id_str, '/') + 1;
+
+ status = GUID_from_string(sc_set->id_str, &sc_set->id);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+
+ status = fss_state_hierarchize_scs(trv_state, sc_set);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_out;
+ }
+ }
+ *sc_sets_count = i;
+ return NT_STATUS_OK;
+
+err_out:
+ return status;
+}
+
+_PRIVATE_ NTSTATUS fss_state_retrieve(TALLOC_CTX *mem_ctx,
+ struct fss_sc_set **sc_sets,
+ uint32_t *sc_sets_count,
+ const char *db_path)
+{
+ struct db_context *db;
+ NTSTATUS status;
+ struct fss_traverse_state trv_state;
+ int err;
+ int rec_count;
+ int vers;
+ *sc_sets = NULL;
+ *sc_sets_count = 0;
+
+ memset(&trv_state, 0, sizeof(trv_state));
+ trv_state.mem_ctx = talloc_new(mem_ctx);
+ if (trv_state.mem_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_out;
+ }
+
+ /* set callbacks for unmarshalling on-disk structures */
+ trv_state.smap_retrieve = fss_state_smap_retrieve;
+ trv_state.sc_retrieve = fss_state_sc_retrieve;
+ trv_state.sc_set_retrieve = fss_state_sc_set_retrieve;
+
+ db = db_open(trv_state.mem_ctx, db_path, 0, TDB_DEFAULT,
+ O_RDONLY, 0600, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ err = errno;
+ if ((db == NULL) && (err == ENOENT)) {
+ DEBUG(4, ("fss state TDB does not exist for retrieval\n"));
+ status = NT_STATUS_OK;
+ goto err_ts_free;
+ } else if (db == NULL) {
+ DEBUG(0, ("Failed to open fss state TDB: %s\n",
+ strerror(err)));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_ts_free;
+ }
+
+ status = dbwrap_fetch_int32_bystring(db, FSS_DB_KEY_VERSION,
+ &vers);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to fetch version from fss state tdb: %s\n",
+ nt_errstr(status)));
+ goto err_db_free;
+ } else if (vers != FSRVP_STATE_DB_VERSION) {
+ DEBUG(0, ("Unsupported fss tdb version %d, expected %d\n",
+ vers, FSRVP_STATE_DB_VERSION));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_db_free;
+ }
+
+ status = dbwrap_traverse_read(db,
+ fss_state_retrieve_traverse,
+ &trv_state,
+ &rec_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_db_free;
+ }
+
+ status = fss_state_hierarchize(&trv_state, sc_sets, sc_sets_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to form fss state hierarchy\n"));
+ goto err_db_free;
+ }
+
+ /* check whether anything was left without a parent */
+ if (trv_state.sc_sets_count != 0) {
+ DEBUG(0, ("%d shadow copy set orphans in %s tdb\n",
+ trv_state.sc_sets_count, db_path));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_db_free;
+ }
+ if (trv_state.scs_count != 0) {
+ DEBUG(0, ("%d shadow copy orphans in %s tdb\n",
+ trv_state.scs_count, db_path));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_db_free;
+ }
+ if (trv_state.smaps_count != 0) {
+ DEBUG(0, ("%d share map orphans in %s tdb\n",
+ trv_state.smaps_count, db_path));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_db_free;
+ }
+ talloc_free(db);
+
+ return NT_STATUS_OK;
+
+err_db_free:
+ talloc_free(db);
+err_ts_free:
+ talloc_free(trv_state.mem_ctx);
+err_out:
+ return status;
+}
diff --git a/source3/rpc_server/initshutdown/srv_initshutdown_nt.c b/source3/rpc_server/initshutdown/srv_initshutdown_nt.c
new file mode 100644
index 0000000..da32cdf
--- /dev/null
+++ b/source3/rpc_server/initshutdown/srv_initshutdown_nt.c
@@ -0,0 +1,84 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997.
+ * Copyright (C) Gerald Carter 2006.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of registry functions. */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/gen_ndr/ndr_initshutdown.h"
+#include "librpc/gen_ndr/ndr_initshutdown_scompat.h"
+#include "librpc/gen_ndr/ndr_winreg.h"
+#include "librpc/gen_ndr/ndr_winreg_scompat.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+
+/*******************************************************************
+ ********************************************************************/
+WERROR _initshutdown_Init(struct pipes_struct *p, struct initshutdown_Init *r)
+{
+ struct winreg_InitiateSystemShutdownEx s;
+
+ s.in.hostname = r->in.hostname;
+ s.in.message = r->in.message;
+ s.in.timeout = r->in.timeout;
+ s.in.force_apps = r->in.force_apps;
+ s.in.do_reboot = r->in.do_reboot;
+ s.in.reason = 0;
+
+ /* thunk down to _winreg_InitiateSystemShutdownEx()
+ (just returns a status) */
+
+ return _winreg_InitiateSystemShutdownEx( p, &s );
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+WERROR _initshutdown_InitEx(struct pipes_struct *p, struct initshutdown_InitEx *r)
+{
+ struct winreg_InitiateSystemShutdownEx s;
+ s.in.hostname = r->in.hostname;
+ s.in.message = r->in.message;
+ s.in.timeout = r->in.timeout;
+ s.in.force_apps = r->in.force_apps;
+ s.in.do_reboot = r->in.do_reboot;
+ s.in.reason = r->in.reason;
+
+ return _winreg_InitiateSystemShutdownEx( p, &s);
+}
+
+
+
+
+/*******************************************************************
+ reg_abort_shutdwon
+ ********************************************************************/
+
+WERROR _initshutdown_Abort(struct pipes_struct *p, struct initshutdown_Abort *r)
+{
+ struct winreg_AbortSystemShutdown s;
+ s.in.server = r->in.server;
+ return _winreg_AbortSystemShutdown( p, &s );
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_initshutdown_scompat.c"
diff --git a/source3/rpc_server/lsa/srv_lsa_nt.c b/source3/rpc_server/lsa/srv_lsa_nt.c
new file mode 100644
index 0000000..e2078c6
--- /dev/null
+++ b/source3/rpc_server/lsa/srv_lsa_nt.c
@@ -0,0 +1,5235 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ * Copyright (C) Paul Ashton 1997,
+ * Copyright (C) Jeremy Allison 2001, 2006.
+ * Copyright (C) Rafal Szczesniak 2002,
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002,
+ * Copyright (C) Simo Sorce 2003.
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ * Copyright (C) Volker Lendecke 2005.
+ * Copyright (C) Guenther Deschner 2008.
+ * Copyright (C) Andrew Bartlett 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This is the implementation of the lsa server code. */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/gen_ndr/ndr_lsa.h"
+#include "librpc/gen_ndr/ndr_lsa_scompat.h"
+#include "secrets.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "../libcli/security/dom_sid.h"
+#include "../librpc/gen_ndr/drsblobs.h"
+#include "../librpc/gen_ndr/ndr_drsblobs.h"
+#include "../libcli/security/dom_sid.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "passdb.h"
+#include "auth.h"
+#include "lib/privileges.h"
+#include "rpc_server/srv_access_check.h"
+#include "../librpc/gen_ndr/ndr_wkssvc.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../libcli/lsarpc/util_lsarpc.h"
+#include "lsa.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/rpc/dcerpc_helper.h"
+#include "lib/param/loadparm.h"
+#include "source3/lib/substitute.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#define MAX_LOOKUP_SIDS 0x5000 /* 20480 */
+
+enum lsa_handle_type {
+ LSA_HANDLE_POLICY_TYPE = 1,
+ LSA_HANDLE_ACCOUNT_TYPE = 2,
+ LSA_HANDLE_TRUST_TYPE = 3,
+ LSA_HANDLE_SECRET_TYPE = 4};
+
+struct lsa_info {
+ struct dom_sid sid;
+ const char *name;
+ uint32_t access;
+ enum lsa_handle_type type;
+ struct security_descriptor *sd;
+};
+
+const struct generic_mapping lsa_account_mapping = {
+ LSA_ACCOUNT_READ,
+ LSA_ACCOUNT_WRITE,
+ LSA_ACCOUNT_EXECUTE,
+ LSA_ACCOUNT_ALL_ACCESS
+};
+
+const struct generic_mapping lsa_policy_mapping = {
+ LSA_POLICY_READ,
+ LSA_POLICY_WRITE,
+ LSA_POLICY_EXECUTE,
+ LSA_POLICY_ALL_ACCESS
+};
+
+const struct generic_mapping lsa_secret_mapping = {
+ LSA_SECRET_READ,
+ LSA_SECRET_WRITE,
+ LSA_SECRET_EXECUTE,
+ LSA_SECRET_ALL_ACCESS
+};
+
+const struct generic_mapping lsa_trusted_domain_mapping = {
+ LSA_TRUSTED_DOMAIN_READ,
+ LSA_TRUSTED_DOMAIN_WRITE,
+ LSA_TRUSTED_DOMAIN_EXECUTE,
+ LSA_TRUSTED_DOMAIN_ALL_ACCESS
+};
+
+/***************************************************************************
+ initialize a lsa_DomainInfo structure.
+ ***************************************************************************/
+
+static void init_dom_query_3(struct lsa_DomainInfo *r,
+ const char *name,
+ struct dom_sid *sid)
+{
+ init_lsa_StringLarge(&r->name, name);
+ r->sid = sid;
+}
+
+/***************************************************************************
+ initialize a lsa_DomainInfo structure.
+ ***************************************************************************/
+
+static void init_dom_query_5(struct lsa_DomainInfo *r,
+ const char *name,
+ struct dom_sid *sid)
+{
+ init_lsa_StringLarge(&r->name, name);
+ r->sid = sid;
+}
+
+/***************************************************************************
+ lookup_lsa_rids. Must be called as root for lookup_name to work.
+ ***************************************************************************/
+
+static NTSTATUS lookup_lsa_rids(TALLOC_CTX *mem_ctx,
+ struct lsa_RefDomainList *ref,
+ struct lsa_TranslatedSid *prid,
+ uint32_t num_entries,
+ struct lsa_String *name,
+ int flags,
+ uint32_t *pmapped_count)
+{
+ uint32_t mapped_count, i;
+
+ SMB_ASSERT(num_entries <= MAX_LOOKUP_SIDS);
+
+ mapped_count = 0;
+ *pmapped_count = 0;
+
+ for (i = 0; i < num_entries; i++) {
+ struct dom_sid sid;
+ uint32_t rid;
+ int dom_idx;
+ const char *full_name;
+ const char *domain;
+ enum lsa_SidType type;
+
+ /* Split name into domain and user component */
+
+ /* follow w2k8 behavior and return the builtin domain when no
+ * input has been passed in */
+
+ if (name[i].string) {
+ full_name = name[i].string;
+ } else {
+ full_name = "BUILTIN";
+ }
+
+ DEBUG(5, ("lookup_lsa_rids: looking up name %s\n", full_name));
+
+ if (!lookup_name(mem_ctx, full_name, flags, &domain, NULL,
+ &sid, &type)) {
+ type = SID_NAME_UNKNOWN;
+ }
+
+ switch (type) {
+ case SID_NAME_USER:
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_DOMAIN:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ DEBUG(5, ("init_lsa_rids: %s found\n", full_name));
+ /* Leave these unchanged */
+ break;
+ default:
+ /* Don't hand out anything but the list above */
+ DEBUG(5, ("init_lsa_rids: %s not found\n", full_name));
+ type = SID_NAME_UNKNOWN;
+ break;
+ }
+
+ rid = 0;
+ dom_idx = -1;
+
+ if (type != SID_NAME_UNKNOWN) {
+ if (type == SID_NAME_DOMAIN) {
+ rid = (uint32_t)-1;
+ } else {
+ sid_split_rid(&sid, &rid);
+ }
+ dom_idx = init_lsa_ref_domain_list(mem_ctx, ref, domain, &sid);
+ mapped_count++;
+ }
+
+ prid[i].sid_type = type;
+ prid[i].rid = rid;
+ prid[i].sid_index = dom_idx;
+ }
+
+ *pmapped_count = mapped_count;
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ lookup_lsa_sids. Must be called as root for lookup_name to work.
+ ***************************************************************************/
+
+static NTSTATUS lookup_lsa_sids(TALLOC_CTX *mem_ctx,
+ struct lsa_RefDomainList *ref,
+ struct lsa_TranslatedSid3 *trans_sids,
+ uint32_t num_entries,
+ struct lsa_String *name,
+ int flags,
+ uint32_t *pmapped_count)
+{
+ uint32_t mapped_count, i;
+
+ SMB_ASSERT(num_entries <= MAX_LOOKUP_SIDS);
+
+ mapped_count = 0;
+ *pmapped_count = 0;
+
+ for (i = 0; i < num_entries; i++) {
+ struct dom_sid sid;
+ uint32_t rid;
+ int dom_idx;
+ const char *full_name;
+ const char *domain;
+ enum lsa_SidType type;
+
+ ZERO_STRUCT(sid);
+
+ /* Split name into domain and user component */
+
+ full_name = name[i].string;
+ if (full_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5, ("lookup_lsa_sids: looking up name %s\n", full_name));
+
+ if (!lookup_name(mem_ctx, full_name, flags, &domain, NULL,
+ &sid, &type)) {
+ type = SID_NAME_UNKNOWN;
+ }
+
+ switch (type) {
+ case SID_NAME_USER:
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_DOMAIN:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ DEBUG(5, ("lookup_lsa_sids: %s found\n", full_name));
+ /* Leave these unchanged */
+ break;
+ default:
+ /* Don't hand out anything but the list above */
+ DEBUG(5, ("lookup_lsa_sids: %s not found\n", full_name));
+ type = SID_NAME_UNKNOWN;
+ break;
+ }
+
+ rid = 0;
+ dom_idx = -1;
+
+ if (type != SID_NAME_UNKNOWN) {
+ struct dom_sid domain_sid;
+ sid_copy(&domain_sid, &sid);
+ sid_split_rid(&domain_sid, &rid);
+ dom_idx = init_lsa_ref_domain_list(mem_ctx, ref, domain, &domain_sid);
+ mapped_count++;
+ }
+
+ /* Initialize the lsa_TranslatedSid3 return. */
+ trans_sids[i].sid_type = type;
+ trans_sids[i].sid = dom_sid_dup(mem_ctx, &sid);
+ trans_sids[i].sid_index = dom_idx;
+ }
+
+ *pmapped_count = mapped_count;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS make_lsa_object_sd(TALLOC_CTX *mem_ctx, struct security_descriptor **sd, size_t *sd_size,
+ const struct generic_mapping *map,
+ struct dom_sid *sid, uint32_t sid_access)
+{
+ struct dom_sid adm_sid;
+ struct security_ace ace[5];
+ size_t i = 0;
+
+ struct security_acl *psa = NULL;
+
+ /* READ|EXECUTE access for Everyone */
+
+ init_sec_ace(&ace[i++], &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ map->generic_execute | map->generic_read, 0);
+
+ /* Add Full Access 'BUILTIN\Administrators' and 'BUILTIN\Account Operators */
+
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0);
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Account_Operators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0);
+
+ /* Add Full Access for Domain Admins */
+ sid_compose(&adm_sid, get_global_sam_sid(), DOMAIN_RID_ADMINS);
+ init_sec_ace(&ace[i++], &adm_sid, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ map->generic_all, 0);
+
+ /* If we have a sid, give it some special access */
+
+ if (sid) {
+ init_sec_ace(&ace[i++], sid, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ sid_access, 0);
+ }
+
+ if((psa = make_sec_acl(mem_ctx, NT4_ACL_REVISION, i, ace)) == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ if((*sd = make_sec_desc(mem_ctx, SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE, &adm_sid, NULL, NULL,
+ psa, sd_size)) == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+
+static NTSTATUS create_lsa_policy_handle(TALLOC_CTX *mem_ctx,
+ struct pipes_struct *p,
+ enum lsa_handle_type type,
+ uint32_t acc_granted,
+ struct dom_sid *sid,
+ const char *name,
+ const struct security_descriptor *sd,
+ struct policy_handle *handle)
+{
+ struct lsa_info *info;
+
+ ZERO_STRUCTP(handle);
+
+ info = talloc_zero(mem_ctx, struct lsa_info);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->type = type;
+ info->access = acc_granted;
+
+ if (sid) {
+ sid_copy(&info->sid, sid);
+ }
+
+ info->name = talloc_strdup(info, name);
+
+ if (sd != NULL) {
+ info->sd = security_descriptor_copy(info, sd);
+ if (info->sd == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!create_policy_hnd(p, handle, type, info)) {
+ talloc_free(info);
+ ZERO_STRUCTP(handle);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_OpenPolicy2
+ ***************************************************************************/
+
+NTSTATUS _lsa_OpenPolicy2(struct pipes_struct *p,
+ struct lsa_OpenPolicy2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *psd = NULL;
+ size_t sd_size;
+ uint32_t des_access = r->in.access_mask;
+ uint32_t acc_granted;
+ NTSTATUS status;
+
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ /* map the generic bits to the lsa policy ones */
+ se_map_generic(&des_access, &lsa_policy_mapping);
+
+ /* get the generic lsa policy SD until we store it */
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size, &lsa_policy_mapping,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, des_access,
+ &acc_granted, "_lsa_OpenPolicy2" );
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = create_lsa_policy_handle(p->mem_ctx, p,
+ LSA_HANDLE_POLICY_TYPE,
+ acc_granted,
+ get_global_sam_sid(),
+ NULL,
+ psd,
+ r->out.handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_OpenPolicy
+ ***************************************************************************/
+
+NTSTATUS _lsa_OpenPolicy(struct pipes_struct *p,
+ struct lsa_OpenPolicy *r)
+{
+ struct lsa_OpenPolicy2 o;
+
+ /* _lsa_OpenPolicy2 will check if this is a NCACN_NP connection */
+
+ o.in.system_name = NULL; /* should be ignored */
+ o.in.attr = r->in.attr;
+ o.in.access_mask = r->in.access_mask;
+
+ o.out.handle = r->out.handle;
+
+ return _lsa_OpenPolicy2(p, &o);
+}
+
+/***************************************************************************
+ _lsa_EnumTrustDom - this needs fixing to do more than return NULL ! JRA.
+ ufff, done :) mimir
+ ***************************************************************************/
+
+NTSTATUS _lsa_EnumTrustDom(struct pipes_struct *p,
+ struct lsa_EnumTrustDom *r)
+{
+ struct lsa_info *info;
+ uint32_t i, count;
+ struct trustdom_info **domains;
+ struct lsa_DomainInfo *entries;
+ NTSTATUS nt_status;
+
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &nt_status);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights */
+ if (!(info->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ become_root();
+ nt_status = pdb_enum_trusteddoms(p->mem_ctx, &count, &domains);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ entries = talloc_zero_array(p->mem_ctx, struct lsa_DomainInfo, count);
+ if (!entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<count; i++) {
+ init_lsa_StringLarge(&entries[i].name, domains[i]->name);
+ entries[i].sid = &domains[i]->sid;
+ }
+
+ if (*r->in.resume_handle >= count) {
+ *r->out.resume_handle = -1;
+ TALLOC_FREE(entries);
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ /* return the rest, limit by max_size. Note that we
+ use the w2k3 element size value of 60 */
+ r->out.domains->count = count - *r->in.resume_handle;
+ r->out.domains->count = MIN(r->out.domains->count,
+ 1+(r->in.max_size/LSA_ENUM_TRUST_DOMAIN_MULTIPLIER));
+
+ r->out.domains->domains = entries + *r->in.resume_handle;
+
+ if (r->out.domains->count < count - *r->in.resume_handle) {
+ *r->out.resume_handle = *r->in.resume_handle + r->out.domains->count;
+ return STATUS_MORE_ENTRIES;
+ }
+
+ /* according to MS-LSAD 3.1.4.7.8 output resume handle MUST
+ * always be larger than the previous input resume handle, in
+ * particular when hitting the last query it is vital to set the
+ * resume handle correctly to avoid infinite client loops, as
+ * seen e.g. with Windows XP SP3 when resume handle is 0 and
+ * status is NT_STATUS_OK - gd */
+
+ *r->out.resume_handle = (uint32_t)-1;
+
+ return NT_STATUS_OK;
+}
+
+#define LSA_AUDIT_NUM_CATEGORIES_NT4 7
+#define LSA_AUDIT_NUM_CATEGORIES_WIN2K 9
+#define LSA_AUDIT_NUM_CATEGORIES LSA_AUDIT_NUM_CATEGORIES_NT4
+
+/***************************************************************************
+ _lsa_QueryInfoPolicy
+ ***************************************************************************/
+
+NTSTATUS _lsa_QueryInfoPolicy(struct pipes_struct *p,
+ struct lsa_QueryInfoPolicy *r)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct lsa_info *handle;
+ struct dom_sid domain_sid;
+ const char *name;
+ struct dom_sid *sid = NULL;
+ union lsa_PolicyInformation *info = NULL;
+ uint32_t acc_required = 0;
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ switch (r->in.level) {
+ case LSA_POLICY_INFO_AUDIT_LOG:
+ case LSA_POLICY_INFO_AUDIT_EVENTS:
+ acc_required = LSA_POLICY_VIEW_AUDIT_INFORMATION;
+ break;
+ case LSA_POLICY_INFO_DOMAIN:
+ acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION;
+ break;
+ case LSA_POLICY_INFO_PD:
+ acc_required = LSA_POLICY_GET_PRIVATE_INFORMATION;
+ break;
+ case LSA_POLICY_INFO_ACCOUNT_DOMAIN:
+ acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION;
+ break;
+ case LSA_POLICY_INFO_ROLE:
+ case LSA_POLICY_INFO_REPLICA:
+ acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION;
+ break;
+ case LSA_POLICY_INFO_QUOTA:
+ acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION;
+ break;
+ case LSA_POLICY_INFO_MOD:
+ case LSA_POLICY_INFO_AUDIT_FULL_SET:
+ /* according to MS-LSAD 3.1.4.4.3 */
+ return NT_STATUS_INVALID_PARAMETER;
+ case LSA_POLICY_INFO_AUDIT_FULL_QUERY:
+ acc_required = LSA_POLICY_VIEW_AUDIT_INFORMATION;
+ break;
+ case LSA_POLICY_INFO_DNS:
+ case LSA_POLICY_INFO_DNS_INT:
+ case LSA_POLICY_INFO_L_ACCOUNT_DOMAIN:
+ acc_required = LSA_POLICY_VIEW_LOCAL_INFORMATION;
+ break;
+ default:
+ break;
+ }
+
+ if (!(handle->access & acc_required)) {
+ /* return NT_STATUS_ACCESS_DENIED; */
+ }
+
+ info = talloc_zero(p->mem_ctx, union lsa_PolicyInformation);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (r->in.level) {
+ /* according to MS-LSAD 3.1.4.4.3 */
+ case LSA_POLICY_INFO_MOD:
+ case LSA_POLICY_INFO_AUDIT_FULL_SET:
+ case LSA_POLICY_INFO_AUDIT_FULL_QUERY:
+ return NT_STATUS_INVALID_PARAMETER;
+ case LSA_POLICY_INFO_AUDIT_LOG:
+ info->audit_log.percent_full = 0;
+ info->audit_log.maximum_log_size = 0;
+ info->audit_log.retention_time = 0;
+ info->audit_log.shutdown_in_progress = 0;
+ info->audit_log.time_to_shutdown = 0;
+ info->audit_log.next_audit_record = 0;
+ status = NT_STATUS_OK;
+ break;
+ case LSA_POLICY_INFO_PD:
+ info->pd.name.string = NULL;
+ status = NT_STATUS_OK;
+ break;
+ case LSA_POLICY_INFO_REPLICA:
+ info->replica.source.string = NULL;
+ info->replica.account.string = NULL;
+ status = NT_STATUS_OK;
+ break;
+ case LSA_POLICY_INFO_QUOTA:
+ info->quota.paged_pool = 0;
+ info->quota.non_paged_pool = 0;
+ info->quota.min_wss = 0;
+ info->quota.max_wss = 0;
+ info->quota.pagefile = 0;
+ info->quota.unknown = 0;
+ status = NT_STATUS_OK;
+ break;
+ case LSA_POLICY_INFO_AUDIT_EVENTS:
+ {
+
+ uint32_t policy_def = LSA_AUDIT_POLICY_ALL;
+
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_VIEW_AUDIT_INFORMATION)) {
+ DEBUG(10,("_lsa_QueryInfoPolicy: insufficient access rights\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* fake info: We audit everything. ;) */
+
+ info->audit_events.auditing_mode = true;
+ info->audit_events.count = LSA_AUDIT_NUM_CATEGORIES;
+ info->audit_events.settings = talloc_zero_array(p->mem_ctx,
+ enum lsa_PolicyAuditPolicy,
+ info->audit_events.count);
+ if (!info->audit_events.settings) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->audit_events.settings[LSA_AUDIT_CATEGORY_ACCOUNT_MANAGEMENT] = policy_def;
+ info->audit_events.settings[LSA_AUDIT_CATEGORY_FILE_AND_OBJECT_ACCESS] = policy_def;
+ info->audit_events.settings[LSA_AUDIT_CATEGORY_LOGON] = policy_def;
+ info->audit_events.settings[LSA_AUDIT_CATEGORY_PROCCESS_TRACKING] = policy_def;
+ info->audit_events.settings[LSA_AUDIT_CATEGORY_SECURITY_POLICY_CHANGES] = policy_def;
+ info->audit_events.settings[LSA_AUDIT_CATEGORY_SYSTEM] = policy_def;
+ info->audit_events.settings[LSA_AUDIT_CATEGORY_USE_OF_USER_RIGHTS] = policy_def;
+
+ break;
+ }
+ case LSA_POLICY_INFO_DOMAIN:
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ /* Request PolicyPrimaryDomainInformation. */
+ switch (lp_server_role()) {
+ case ROLE_DOMAIN_PDC:
+ case ROLE_DOMAIN_BDC:
+ case ROLE_IPA_DC:
+ name = get_global_sam_name();
+ sid = dom_sid_dup(p->mem_ctx, get_global_sam_sid());
+ if (!sid) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ case ROLE_DOMAIN_MEMBER:
+ name = lp_workgroup();
+ /* We need to return the Domain SID here. */
+ if (secrets_fetch_domain_sid(lp_workgroup(), &domain_sid)) {
+ sid = dom_sid_dup(p->mem_ctx, &domain_sid);
+ if (!sid) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ break;
+ case ROLE_STANDALONE:
+ name = lp_workgroup();
+ sid = NULL;
+ break;
+ default:
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+ init_dom_query_3(&info->domain, name, sid);
+ break;
+ case LSA_POLICY_INFO_ACCOUNT_DOMAIN:
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ /* Request PolicyAccountDomainInformation. */
+ name = get_global_sam_name();
+ sid = get_global_sam_sid();
+
+ init_dom_query_5(&info->account_domain, name, sid);
+ break;
+ case LSA_POLICY_INFO_ROLE:
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ switch (lp_server_role()) {
+ case ROLE_DOMAIN_BDC:
+ /*
+ * only a BDC is a backup controller
+ * of the domain, it controls.
+ */
+ info->role.role = LSA_ROLE_BACKUP;
+ break;
+ default:
+ /*
+ * any other role is a primary
+ * of the domain, it controls.
+ */
+ info->role.role = LSA_ROLE_PRIMARY;
+ break;
+ }
+ break;
+ case LSA_POLICY_INFO_DNS:
+ case LSA_POLICY_INFO_DNS_INT: {
+ struct pdb_domain_info *dominfo;
+
+ if ((pdb_capabilities() & PDB_CAP_ADS) == 0) {
+ DEBUG(10, ("Not replying to LSA_POLICY_INFO_DNS "
+ "without ADS passdb backend\n"));
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ dominfo = pdb_get_domain_info(info);
+ if (dominfo == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ init_lsa_StringLarge(&info->dns.name,
+ dominfo->name);
+ init_lsa_StringLarge(&info->dns.dns_domain,
+ dominfo->dns_domain);
+ init_lsa_StringLarge(&info->dns.dns_forest,
+ dominfo->dns_forest);
+ info->dns.domain_guid = dominfo->guid;
+ info->dns.sid = &dominfo->sid;
+ break;
+ }
+ default:
+ DEBUG(0,("_lsa_QueryInfoPolicy: unknown info level in Lsa Query: %d\n",
+ r->in.level));
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ *r->out.info = info;
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_QueryInfoPolicy2
+ ***************************************************************************/
+
+NTSTATUS _lsa_QueryInfoPolicy2(struct pipes_struct *p,
+ struct lsa_QueryInfoPolicy2 *r2)
+{
+ struct lsa_QueryInfoPolicy r;
+
+ if ((pdb_capabilities() & PDB_CAP_ADS) == 0) {
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ ZERO_STRUCT(r);
+ r.in.handle = r2->in.handle;
+ r.in.level = r2->in.level;
+ r.out.info = r2->out.info;
+
+ return _lsa_QueryInfoPolicy(p, &r);
+}
+
+/***************************************************************************
+ _lsa_lookup_sids_internal
+ ***************************************************************************/
+
+static NTSTATUS _lsa_lookup_sids_internal(struct pipes_struct *p,
+ TALLOC_CTX *mem_ctx,
+ uint16_t level, /* input */
+ int num_sids, /* input */
+ struct lsa_SidPtr *sid, /* input */
+ struct lsa_RefDomainList **pp_ref, /* input/output */
+ struct lsa_TranslatedName2 **pp_names,/* input/output */
+ uint32_t *pp_mapped_count) /* input/output */
+{
+ NTSTATUS status;
+ int i;
+ const struct dom_sid **sids = NULL;
+ struct lsa_RefDomainList *ref = NULL;
+ uint32_t mapped_count = 0;
+ struct lsa_dom_info *dom_infos = NULL;
+ struct lsa_name_info *name_infos = NULL;
+ struct lsa_TranslatedName2 *names = NULL;
+
+ *pp_mapped_count = 0;
+ *pp_names = NULL;
+ *pp_ref = NULL;
+
+ if (num_sids == 0) {
+ return NT_STATUS_OK;
+ }
+
+ sids = talloc_array(p->mem_ctx, const struct dom_sid *, num_sids);
+ ref = talloc_zero(p->mem_ctx, struct lsa_RefDomainList);
+
+ if (sids == NULL || ref == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_sids; i++) {
+ sids[i] = sid[i].sid;
+ }
+
+ status = lookup_sids(p->mem_ctx, num_sids, sids, level,
+ &dom_infos, &name_infos);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ names = talloc_array(p->mem_ctx, struct lsa_TranslatedName2, num_sids);
+ if (names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<LSA_REF_DOMAIN_LIST_MULTIPLIER; i++) {
+
+ if (!dom_infos[i].valid) {
+ break;
+ }
+
+ if (init_lsa_ref_domain_list(mem_ctx, ref,
+ dom_infos[i].name,
+ &dom_infos[i].sid) != i) {
+ DEBUG(0, ("Domain %s mentioned twice??\n",
+ dom_infos[i].name));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ for (i=0; i<num_sids; i++) {
+ struct lsa_name_info *name = &name_infos[i];
+
+ if (name->type == SID_NAME_UNKNOWN) {
+ name->dom_idx = -1;
+ /* Unknown sids should return the string
+ * representation of the SID. Windows 2003 behaves
+ * rather erratic here, in many cases it returns the
+ * RID as 8 bytes hex, in others it returns the full
+ * SID. We (Jerry/VL) could not figure out which the
+ * hard cases are, so leave it with the SID. */
+ name->name = dom_sid_string(p->mem_ctx, sids[i]);
+ if (name->name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ mapped_count += 1;
+ }
+
+ names[i].sid_type = name->type;
+ names[i].name.string = name->name;
+ names[i].sid_index = name->dom_idx;
+ names[i].unknown = 0;
+ }
+
+ status = NT_STATUS_NONE_MAPPED;
+ if (mapped_count > 0) {
+ status = (mapped_count < num_sids) ?
+ STATUS_SOME_UNMAPPED : NT_STATUS_OK;
+ }
+
+ DEBUG(10, ("num_sids %d, mapped_count %d, status %s\n",
+ num_sids, mapped_count, nt_errstr(status)));
+
+ *pp_mapped_count = mapped_count;
+ *pp_names = names;
+ *pp_ref = ref;
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_LookupSids
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupSids(struct pipes_struct *p,
+ struct lsa_LookupSids *r)
+{
+ NTSTATUS status;
+ struct lsa_info *handle;
+ int num_sids = r->in.sids->num_sids;
+ uint32_t mapped_count = 0;
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TranslatedName *names_out = NULL;
+ struct lsa_TranslatedName2 *names = NULL;
+ int i;
+
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if ((r->in.level < 1) || (r->in.level > 6)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (num_sids > MAX_LOOKUP_SIDS) {
+ DEBUG(5,("_lsa_LookupSids: limit of %d exceeded, requested %d\n",
+ MAX_LOOKUP_SIDS, num_sids));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ status = _lsa_lookup_sids_internal(p,
+ p->mem_ctx,
+ r->in.level,
+ num_sids,
+ r->in.sids->sids,
+ &domains,
+ &names,
+ &mapped_count);
+
+ /* Only return here when there is a real error.
+ NT_STATUS_NONE_MAPPED is a special case as it indicates that none of
+ the requested sids could be resolved. Older versions of XP (pre SP3)
+ rely that we return with the string representations of those SIDs in
+ that case. If we don't, XP crashes - Guenther
+ */
+
+ if (NT_STATUS_IS_ERR(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ return status;
+ }
+
+ /* Convert from lsa_TranslatedName2 to lsa_TranslatedName */
+ names_out = talloc_array(p->mem_ctx, struct lsa_TranslatedName,
+ num_sids);
+ if (!names_out) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_sids; i++) {
+ names_out[i].sid_type = names[i].sid_type;
+ names_out[i].name = names[i].name;
+ names_out[i].sid_index = names[i].sid_index;
+ }
+
+ *r->out.domains = domains;
+ r->out.names->count = num_sids;
+ r->out.names->names = names_out;
+ *r->out.count = mapped_count;
+
+ return status;
+}
+
+static NTSTATUS _lsa_LookupSids_common(struct pipes_struct *p,
+ struct lsa_LookupSids2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ NTSTATUS status;
+ struct lsa_info *handle;
+ int num_sids = r->in.sids->num_sids;
+ uint32_t mapped_count = 0;
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TranslatedName2 *names = NULL;
+ bool check_policy = true;
+
+ switch (dce_call->pkt.u.request.opnum) {
+ case NDR_LSA_LOOKUPSIDS3:
+ check_policy = false;
+ break;
+ case NDR_LSA_LOOKUPSIDS2:
+ default:
+ check_policy = true;
+ }
+
+ if ((r->in.level < 1) || (r->in.level > 6)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (check_policy) {
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ if (num_sids > MAX_LOOKUP_SIDS) {
+ DEBUG(5,("_lsa_LookupSids2: limit of %d exceeded, requested %d\n",
+ MAX_LOOKUP_SIDS, num_sids));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ status = _lsa_lookup_sids_internal(p,
+ p->mem_ctx,
+ r->in.level,
+ num_sids,
+ r->in.sids->sids,
+ &domains,
+ &names,
+ &mapped_count);
+
+ *r->out.domains = domains;
+ r->out.names->count = num_sids;
+ r->out.names->names = names;
+ *r->out.count = mapped_count;
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_LookupSids2
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupSids2(struct pipes_struct *p,
+ struct lsa_LookupSids2 *r)
+{
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return _lsa_LookupSids_common(p, r);
+}
+
+/***************************************************************************
+ _lsa_LookupSids3
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupSids3(struct pipes_struct *p,
+ struct lsa_LookupSids3 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE;
+ enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
+ struct lsa_LookupSids2 q;
+
+ if (p->transport != NCACN_IP_TCP) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ dcesrv_call_auth_info(dce_call, &auth_type, &auth_level);
+
+ /* No policy handle on this call. Restrict to crypto connections. */
+ if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL ||
+ auth_level < DCERPC_AUTH_LEVEL_INTEGRITY) {
+ DEBUG(1, ("_lsa_LookupSids3: The client %s is not using "
+ "a secure connection over netlogon\n",
+ get_remote_machine_name() ));
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ q.in.handle = NULL;
+ q.in.sids = r->in.sids;
+ q.in.level = r->in.level;
+ q.in.lookup_options = r->in.lookup_options;
+ q.in.client_revision = r->in.client_revision;
+ q.in.names = r->in.names;
+ q.in.count = r->in.count;
+
+ q.out.domains = r->out.domains;
+ q.out.names = r->out.names;
+ q.out.count = r->out.count;
+
+ return _lsa_LookupSids_common(p, &q);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+
+static int lsa_lookup_level_to_flags(enum lsa_LookupNamesLevel level)
+{
+ int flags;
+
+ switch (level) {
+ case LSA_LOOKUP_NAMES_ALL: /* 1 */
+ flags = LOOKUP_NAME_ALL;
+ break;
+ case LSA_LOOKUP_NAMES_DOMAINS_ONLY: /* 2 */
+ flags = LOOKUP_NAME_DOMAIN|LOOKUP_NAME_REMOTE|LOOKUP_NAME_ISOLATED;
+ break;
+ case LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY: /* 3 */
+ flags = LOOKUP_NAME_DOMAIN|LOOKUP_NAME_ISOLATED;
+ break;
+ case LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY: /* 4 */
+ case LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY: /* 5 */
+ case LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2: /* 6 */
+ case LSA_LOOKUP_NAMES_RODC_REFERRAL_TO_FULL_DC: /* 7 */
+ default:
+ flags = LOOKUP_NAME_NONE;
+ break;
+ }
+
+ return flags;
+}
+
+/***************************************************************************
+ _lsa_LookupNames
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupNames(struct pipes_struct *p,
+ struct lsa_LookupNames *r)
+{
+ NTSTATUS status = NT_STATUS_NONE_MAPPED;
+ struct lsa_info *handle;
+ struct lsa_String *names = r->in.names;
+ uint32_t num_entries = r->in.num_names;
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TranslatedSid *rids = NULL;
+ uint32_t mapped_count = 0;
+ int flags = 0;
+
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (num_entries > MAX_LOOKUP_SIDS) {
+ num_entries = MAX_LOOKUP_SIDS;
+ DEBUG(5,("_lsa_LookupNames: truncating name lookup list to %d\n",
+ num_entries));
+ }
+
+ flags = lsa_lookup_level_to_flags(r->in.level);
+
+ domains = talloc_zero(p->mem_ctx, struct lsa_RefDomainList);
+ if (!domains) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (num_entries) {
+ rids = talloc_zero_array(p->mem_ctx, struct lsa_TranslatedSid,
+ num_entries);
+ if (!rids) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ rids = NULL;
+ }
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto done;
+ }
+
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ /* set up the LSA Lookup RIDs response */
+ become_root(); /* lookup_name can require root privs */
+ status = lookup_lsa_rids(p->mem_ctx, domains, rids, num_entries,
+ names, flags, &mapped_count);
+ unbecome_root();
+
+done:
+
+ if (NT_STATUS_IS_OK(status) && (num_entries != 0) ) {
+ if (mapped_count == 0) {
+ status = NT_STATUS_NONE_MAPPED;
+ } else if (mapped_count != num_entries) {
+ status = STATUS_SOME_UNMAPPED;
+ }
+ }
+
+ *r->out.count = mapped_count;
+ *r->out.domains = domains;
+ r->out.sids->sids = rids;
+ r->out.sids->count = num_entries;
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_LookupNames2
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupNames2(struct pipes_struct *p,
+ struct lsa_LookupNames2 *r)
+{
+ NTSTATUS status;
+ struct lsa_LookupNames q;
+ struct lsa_TransSidArray2 *sid_array2 = r->in.sids;
+ struct lsa_TransSidArray *sid_array = NULL;
+ uint32_t i;
+
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ sid_array = talloc_zero(p->mem_ctx, struct lsa_TransSidArray);
+ if (!sid_array) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ q.in.handle = r->in.handle;
+ q.in.num_names = r->in.num_names;
+ q.in.names = r->in.names;
+ q.in.level = r->in.level;
+ q.in.sids = sid_array;
+ q.in.count = r->in.count;
+ /* we do not know what this is for */
+ /* = r->in.unknown1; */
+ /* = r->in.unknown2; */
+
+ q.out.domains = r->out.domains;
+ q.out.sids = sid_array;
+ q.out.count = r->out.count;
+
+ status = _lsa_LookupNames(p, &q);
+
+ sid_array2->count = sid_array->count;
+ sid_array2->sids = talloc_array(p->mem_ctx, struct lsa_TranslatedSid2, sid_array->count);
+ if (!sid_array2->sids) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<sid_array->count; i++) {
+ sid_array2->sids[i].sid_type = sid_array->sids[i].sid_type;
+ sid_array2->sids[i].rid = sid_array->sids[i].rid;
+ sid_array2->sids[i].sid_index = sid_array->sids[i].sid_index;
+ sid_array2->sids[i].unknown = 0;
+ }
+
+ r->out.sids = sid_array2;
+
+ return status;
+}
+
+static NTSTATUS _lsa_LookupNames_common(struct pipes_struct *p,
+ struct lsa_LookupNames3 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ NTSTATUS status;
+ struct lsa_info *handle;
+ struct lsa_String *names = r->in.names;
+ uint32_t num_entries = r->in.num_names;
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TranslatedSid3 *trans_sids = NULL;
+ uint32_t mapped_count = 0;
+ int flags = 0;
+ bool check_policy = true;
+
+ switch (dce_call->pkt.u.request.opnum) {
+ case NDR_LSA_LOOKUPNAMES4:
+ check_policy = false;
+ break;
+ case NDR_LSA_LOOKUPNAMES3:
+ default:
+ check_policy = true;
+ }
+
+ if (num_entries > MAX_LOOKUP_SIDS) {
+ num_entries = MAX_LOOKUP_SIDS;
+ DEBUG(5,("_lsa_LookupNames3: truncating name lookup list to %d\n", num_entries));
+ }
+
+ flags = lsa_lookup_level_to_flags(r->in.level);
+
+ domains = talloc_zero(p->mem_ctx, struct lsa_RefDomainList);
+ if (!domains) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (num_entries) {
+ trans_sids = talloc_zero_array(p->mem_ctx, struct lsa_TranslatedSid3,
+ num_entries);
+ if (!trans_sids) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ trans_sids = NULL;
+ }
+
+ if (check_policy) {
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto done;
+ }
+
+ /* check if the user has enough rights */
+ if (!(handle->access & LSA_POLICY_LOOKUP_NAMES)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ }
+
+ /* set up the LSA Lookup SIDs response */
+ become_root(); /* lookup_name can require root privs */
+ status = lookup_lsa_sids(p->mem_ctx, domains, trans_sids, num_entries,
+ names, flags, &mapped_count);
+ unbecome_root();
+
+done:
+
+ if (NT_STATUS_IS_OK(status)) {
+ if (mapped_count == 0) {
+ status = NT_STATUS_NONE_MAPPED;
+ } else if (mapped_count != num_entries) {
+ status = STATUS_SOME_UNMAPPED;
+ }
+ }
+
+ *r->out.count = mapped_count;
+ *r->out.domains = domains;
+ r->out.sids->sids = trans_sids;
+ r->out.sids->count = num_entries;
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_LookupNames3
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupNames3(struct pipes_struct *p,
+ struct lsa_LookupNames3 *r)
+{
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return _lsa_LookupNames_common(p, r);
+}
+
+/***************************************************************************
+ _lsa_LookupNames4
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupNames4(struct pipes_struct *p,
+ struct lsa_LookupNames4 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE;
+ enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
+ struct lsa_LookupNames3 q;
+
+ if (p->transport != NCACN_IP_TCP) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ dcesrv_call_auth_info(dce_call, &auth_type, &auth_level);
+
+ /* No policy handle on this call. Restrict to crypto connections. */
+ if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL ||
+ auth_level < DCERPC_AUTH_LEVEL_INTEGRITY) {
+ DEBUG(1, ("_lsa_LookupNames4: The client %s is not using "
+ "a secure connection over netlogon\n",
+ get_remote_machine_name()));
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ q.in.handle = NULL;
+ q.in.num_names = r->in.num_names;
+ q.in.names = r->in.names;
+ q.in.level = r->in.level;
+ q.in.lookup_options = r->in.lookup_options;
+ q.in.client_revision = r->in.client_revision;
+ q.in.sids = r->in.sids;
+ q.in.count = r->in.count;
+
+ q.out.domains = r->out.domains;
+ q.out.sids = r->out.sids;
+ q.out.count = r->out.count;
+
+ return _lsa_LookupNames_common(p, &q);
+}
+
+/***************************************************************************
+ _lsa_close. Also weird - needs to check if lsa handle is correct. JRA.
+ ***************************************************************************/
+
+NTSTATUS _lsa_Close(struct pipes_struct *p, struct lsa_Close *r)
+{
+ NTSTATUS status;
+
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ (void)find_policy_by_hnd(p,
+ r->in.handle,
+ DCESRV_HANDLE_ANY,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ close_policy_hnd(p, r->in.handle);
+ ZERO_STRUCTP(r->out.handle);
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+
+static NTSTATUS lsa_lookup_trusted_domain_by_sid(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ struct trustdom_info **info)
+{
+ NTSTATUS status;
+ uint32_t num_domains = 0;
+ struct trustdom_info **domains = NULL;
+ int i;
+
+ status = pdb_enum_trusteddoms(mem_ctx, &num_domains, &domains);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (i=0; i < num_domains; i++) {
+ if (dom_sid_equal(&domains[i]->sid, sid)) {
+ break;
+ }
+ }
+
+ if (i == num_domains) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *info = domains[i];
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+
+static NTSTATUS lsa_lookup_trusted_domain_by_name(TALLOC_CTX *mem_ctx,
+ const char *netbios_domain_name,
+ struct trustdom_info **info_p)
+{
+ NTSTATUS status;
+ struct trustdom_info *info;
+ struct pdb_trusted_domain *td;
+
+ status = pdb_get_trusted_domain(mem_ctx, netbios_domain_name, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ info = talloc(mem_ctx, struct trustdom_info);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->name = talloc_strdup(info, netbios_domain_name);
+ NT_STATUS_HAVE_NO_MEMORY(info->name);
+
+ sid_copy(&info->sid, &td->security_identifier);
+
+ *info_p = info;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_OpenSecret
+ ***************************************************************************/
+
+NTSTATUS _lsa_OpenSecret(struct pipes_struct *p,
+ struct lsa_OpenSecret *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *psd;
+ NTSTATUS status;
+ uint32_t acc_granted;
+
+ (void)find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!r->in.name.string) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &r->in.access_mask);
+
+ /* map the generic bits to the lsa policy ones */
+ se_map_generic(&r->in.access_mask, &lsa_secret_mapping);
+
+ status = pdb_get_secret(p->mem_ctx, r->in.name.string,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0,
+ r->in.access_mask,
+ &acc_granted, "_lsa_OpenSecret");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = create_lsa_policy_handle(p->mem_ctx, p,
+ LSA_HANDLE_SECRET_TYPE,
+ acc_granted,
+ NULL,
+ r->in.name.string,
+ psd,
+ r->out.sec_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_OpenTrustedDomain_base
+ ***************************************************************************/
+
+static NTSTATUS _lsa_OpenTrustedDomain_base(struct pipes_struct *p,
+ uint32_t access_mask,
+ struct trustdom_info *info,
+ struct policy_handle *handle)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *psd = NULL;
+ size_t sd_size;
+ uint32_t acc_granted;
+ NTSTATUS status;
+
+ /* des_access is for the account here, not the policy
+ * handle - so don't check against policy handle. */
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &access_mask);
+
+ /* map the generic bits to the lsa account ones */
+ se_map_generic(&access_mask, &lsa_trusted_domain_mapping);
+
+ /* get the generic lsa account SD until we store it */
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size,
+ &lsa_trusted_domain_mapping,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0,
+ access_mask, &acc_granted,
+ "_lsa_OpenTrustedDomain");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = create_lsa_policy_handle(p->mem_ctx, p,
+ LSA_HANDLE_TRUST_TYPE,
+ acc_granted,
+ &info->sid,
+ info->name,
+ psd,
+ handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_OpenTrustedDomain
+ ***************************************************************************/
+
+NTSTATUS _lsa_OpenTrustedDomain(struct pipes_struct *p,
+ struct lsa_OpenTrustedDomain *r)
+{
+ struct trustdom_info *info = NULL;
+ NTSTATUS status;
+
+ (void)find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ status = lsa_lookup_trusted_domain_by_sid(p->mem_ctx,
+ r->in.sid,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return _lsa_OpenTrustedDomain_base(p, r->in.access_mask, info,
+ r->out.trustdom_handle);
+}
+
+/***************************************************************************
+ _lsa_OpenTrustedDomainByName
+ ***************************************************************************/
+
+NTSTATUS _lsa_OpenTrustedDomainByName(struct pipes_struct *p,
+ struct lsa_OpenTrustedDomainByName *r)
+{
+ struct trustdom_info *info = NULL;
+ NTSTATUS status;
+
+ (void)find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ status = lsa_lookup_trusted_domain_by_name(p->mem_ctx,
+ r->in.name.string,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return _lsa_OpenTrustedDomain_base(p, r->in.access_mask, info,
+ r->out.trustdom_handle);
+}
+
+static NTSTATUS get_trustdom_auth_blob(struct pipes_struct *p,
+ TALLOC_CTX *mem_ctx, DATA_BLOB *auth_blob,
+ struct trustDomainPasswords *auth_struct)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ enum ndr_err_code ndr_err;
+ DATA_BLOB lsession_key;
+ gnutls_cipher_hd_t cipher_hnd = NULL;
+ gnutls_datum_t my_session_key;
+ NTSTATUS status;
+ int rc;
+ bool encrypted;
+
+ encrypted = dcerpc_is_transport_encrypted(session_info);
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED &&
+ !encrypted) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = session_extract_session_key(
+ session_info, &lsession_key, KEY_USE_16BYTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ my_session_key = (gnutls_datum_t) {
+ .data = lsession_key.data,
+ .size = lsession_key.length,
+ };
+
+ GNUTLS_FIPS140_SET_LAX_MODE();
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &my_session_key,
+ NULL);
+ if (rc < 0) {
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto out;
+ }
+
+ rc = gnutls_cipher_decrypt(cipher_hnd,
+ auth_blob->data,
+ auth_blob->length);
+ gnutls_cipher_deinit(cipher_hnd);
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto out;
+ }
+
+ ndr_err = ndr_pull_struct_blob(auth_blob, mem_ctx,
+ auth_struct,
+ (ndr_pull_flags_fn_t)ndr_pull_trustDomainPasswords);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+out:
+ return status;
+}
+
+static NTSTATUS get_trustauth_inout_blob(TALLOC_CTX *mem_ctx,
+ struct trustAuthInOutBlob *iopw,
+ DATA_BLOB *trustauth_blob)
+{
+ enum ndr_err_code ndr_err;
+
+ if (iopw->current.count != iopw->count) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (iopw->previous.count > iopw->current.count) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (iopw->previous.count == 0) {
+ /*
+ * If the previous credentials are not present
+ * we need to make a copy.
+ */
+ iopw->previous = iopw->current;
+ }
+
+ if (iopw->previous.count < iopw->current.count) {
+ struct AuthenticationInformationArray *c = &iopw->current;
+ struct AuthenticationInformationArray *p = &iopw->previous;
+
+ /*
+ * The previous array needs to have the same size
+ * as the current one.
+ *
+ * We may have to fill with TRUST_AUTH_TYPE_NONE
+ * elements.
+ */
+ p->array = talloc_realloc(mem_ctx, p->array,
+ struct AuthenticationInformation,
+ c->count);
+ if (p->array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ while (p->count < c->count) {
+ struct AuthenticationInformation *a =
+ &p->array[p->count++];
+
+ *a = (struct AuthenticationInformation) {
+ .LastUpdateTime = p->array[0].LastUpdateTime,
+ .AuthType = TRUST_AUTH_TYPE_NONE,
+ };
+ }
+ }
+
+ ndr_err = ndr_push_struct_blob(trustauth_blob, mem_ctx,
+ iopw,
+ (ndr_push_flags_fn_t)ndr_push_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_CreateTrustedDomainEx2
+ ***************************************************************************/
+
+NTSTATUS _lsa_CreateTrustedDomainEx2(struct pipes_struct *p,
+ struct lsa_CreateTrustedDomainEx2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct lsa_info *policy;
+ NTSTATUS status;
+ uint32_t acc_granted;
+ struct security_descriptor *psd;
+ size_t sd_size;
+ struct pdb_trusted_domain td;
+ struct trustDomainPasswords auth_struct;
+ DATA_BLOB auth_blob;
+
+ if (!IS_DC) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ policy = find_policy_by_hnd(p,
+ r->in.policy_handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(policy->access & LSA_POLICY_TRUST_ADMIN)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (session_info->unix_token->uid != sec_initial_uid() &&
+ !nt_token_check_domain_rid(
+ session_info->security_token, DOMAIN_RID_ADMINS)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &r->in.access_mask);
+
+ /* map the generic bits to the lsa policy ones */
+ se_map_generic(&r->in.access_mask, &lsa_account_mapping);
+
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size,
+ &lsa_trusted_domain_mapping,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0,
+ r->in.access_mask, &acc_granted,
+ "_lsa_CreateTrustedDomainEx2");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ZERO_STRUCT(td);
+
+ td.domain_name = talloc_strdup(p->mem_ctx,
+ r->in.info->domain_name.string);
+ if (td.domain_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ td.netbios_name = talloc_strdup(p->mem_ctx,
+ r->in.info->netbios_name.string);
+ if (td.netbios_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ sid_copy(&td.security_identifier, r->in.info->sid);
+ td.trust_direction = r->in.info->trust_direction;
+ td.trust_type = r->in.info->trust_type;
+ td.trust_attributes = r->in.info->trust_attributes;
+
+ if (r->in.auth_info_internal->auth_blob.size != 0) {
+ auth_blob.length = r->in.auth_info_internal->auth_blob.size;
+ auth_blob.data = r->in.auth_info_internal->auth_blob.data;
+
+ status = get_trustdom_auth_blob(p, p->mem_ctx, &auth_blob, &auth_struct);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = get_trustauth_inout_blob(p->mem_ctx, &auth_struct.incoming, &td.trust_auth_incoming);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = get_trustauth_inout_blob(p->mem_ctx, &auth_struct.outgoing, &td.trust_auth_outgoing);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ } else {
+ td.trust_auth_incoming.data = NULL;
+ td.trust_auth_incoming.length = 0;
+ td.trust_auth_outgoing.data = NULL;
+ td.trust_auth_outgoing.length = 0;
+ }
+
+ status = pdb_set_trusted_domain(r->in.info->domain_name.string, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = create_lsa_policy_handle(p->mem_ctx, p,
+ LSA_HANDLE_TRUST_TYPE,
+ acc_granted,
+ r->in.info->sid,
+ r->in.info->netbios_name.string,
+ psd,
+ r->out.trustdom_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ pdb_del_trusted_domain(r->in.info->netbios_name.string);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_CreateTrustedDomainEx
+ ***************************************************************************/
+
+NTSTATUS _lsa_CreateTrustedDomainEx(struct pipes_struct *p,
+ struct lsa_CreateTrustedDomainEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/***************************************************************************
+ _lsa_CreateTrustedDomain
+ ***************************************************************************/
+
+NTSTATUS _lsa_CreateTrustedDomain(struct pipes_struct *p,
+ struct lsa_CreateTrustedDomain *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/***************************************************************************
+ _lsa_DeleteTrustedDomain
+ ***************************************************************************/
+
+NTSTATUS _lsa_DeleteTrustedDomain(struct pipes_struct *p,
+ struct lsa_DeleteTrustedDomain *r)
+{
+ NTSTATUS status;
+ struct lsa_info *handle;
+ struct pdb_trusted_domain *td;
+
+ /* find the connection policy handle. */
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(handle->access & LSA_POLICY_TRUST_ADMIN)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pdb_get_trusted_domain_by_sid(p->mem_ctx, r->in.dom_sid, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (td->netbios_name == NULL || *td->netbios_name == '\0') {
+ struct dom_sid_buf buf;
+ DEBUG(10, ("Missing netbios name for trusted domain %s.\n",
+ dom_sid_str_buf(r->in.dom_sid, &buf)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = pdb_del_trusted_domain(td->netbios_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_CloseTrustedDomainEx
+ ***************************************************************************/
+
+NTSTATUS _lsa_CloseTrustedDomainEx(struct pipes_struct *p,
+ struct lsa_CloseTrustedDomainEx *r)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/***************************************************************************
+ _lsa_QueryTrustedDomainInfo
+ ***************************************************************************/
+
+static NTSTATUS pdb_trusted_domain_2_info_ex(TALLOC_CTX *mem_ctx,
+ struct pdb_trusted_domain *td,
+ struct lsa_TrustDomainInfoInfoEx *info_ex)
+{
+ if (td->domain_name == NULL ||
+ td->netbios_name == NULL ||
+ is_null_sid(&td->security_identifier)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ info_ex->domain_name.string = talloc_strdup(mem_ctx, td->domain_name);
+ info_ex->netbios_name.string = talloc_strdup(mem_ctx, td->netbios_name);
+ info_ex->sid = dom_sid_dup(mem_ctx, &td->security_identifier);
+ if (info_ex->domain_name.string == NULL ||
+ info_ex->netbios_name.string == NULL ||
+ info_ex->sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info_ex->trust_direction = td->trust_direction;
+ info_ex->trust_type = td->trust_type;
+ info_ex->trust_attributes = td->trust_attributes;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _lsa_QueryTrustedDomainInfo(struct pipes_struct *p,
+ struct lsa_QueryTrustedDomainInfo *r)
+{
+ NTSTATUS status;
+ struct lsa_info *handle;
+ union lsa_TrustedDomainInfo *info;
+ struct pdb_trusted_domain *td;
+ uint32_t acc_required;
+
+ /* find the connection policy handle. */
+ handle = find_policy_by_hnd(p,
+ r->in.trustdom_handle,
+ LSA_HANDLE_TRUST_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ switch (r->in.level) {
+ case LSA_TRUSTED_DOMAIN_INFO_NAME:
+ acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_CONTROLLERS:
+ acc_required = LSA_TRUSTED_QUERY_CONTROLLERS;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET:
+ acc_required = LSA_TRUSTED_QUERY_POSIX;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_PASSWORD:
+ acc_required = LSA_TRUSTED_QUERY_AUTH;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_BASIC:
+ acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_INFO_EX:
+ acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO:
+ acc_required = LSA_TRUSTED_QUERY_AUTH;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO:
+ acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME |
+ LSA_TRUSTED_QUERY_POSIX |
+ LSA_TRUSTED_QUERY_AUTH;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL:
+ acc_required = LSA_TRUSTED_QUERY_AUTH;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL:
+ acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME |
+ LSA_TRUSTED_QUERY_POSIX |
+ LSA_TRUSTED_QUERY_AUTH;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_INFO_EX2_INTERNAL:
+ acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_2_INTERNAL:
+ acc_required = LSA_TRUSTED_QUERY_DOMAIN_NAME |
+ LSA_TRUSTED_QUERY_POSIX |
+ LSA_TRUSTED_QUERY_AUTH;
+ break;
+ case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES:
+ acc_required = LSA_TRUSTED_QUERY_POSIX;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!(handle->access & acc_required)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pdb_get_trusted_domain_by_sid(p->mem_ctx, &handle->sid, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ info = talloc_zero(p->mem_ctx, union lsa_TrustedDomainInfo);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (r->in.level) {
+ case LSA_TRUSTED_DOMAIN_INFO_NAME:
+ init_lsa_StringLarge(&info->name.netbios_name, td->netbios_name);
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_CONTROLLERS:
+ return NT_STATUS_INVALID_PARAMETER;
+ case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET:
+ info->posix_offset.posix_offset = *td->trust_posix_offset;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_PASSWORD:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ case LSA_TRUSTED_DOMAIN_INFO_BASIC:
+ return NT_STATUS_INVALID_PARAMETER;
+ case LSA_TRUSTED_DOMAIN_INFO_INFO_EX:
+ status = pdb_trusted_domain_2_info_ex(info, td, &info->info_ex);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO:
+ status = pdb_trusted_domain_2_info_ex(info, td,
+ &info->full_info.info_ex);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ info->full_info.posix_offset.posix_offset = *td->trust_posix_offset;
+ status = auth_blob_2_auth_info(p->mem_ctx,
+ td->trust_auth_incoming,
+ td->trust_auth_outgoing,
+ &info->full_info.auth_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ case LSA_TRUSTED_DOMAIN_INFO_INFO_EX2_INTERNAL:
+ return NT_STATUS_INVALID_PARAMETER;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_2_INTERNAL:
+ info->full_info2_internal.posix_offset.posix_offset = *td->trust_posix_offset;
+ status = auth_blob_2_auth_info(p->mem_ctx,
+ td->trust_auth_incoming,
+ td->trust_auth_outgoing,
+ &info->full_info2_internal.auth_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES:
+ info->enc_types.enc_types = *td->supported_enc_type;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *r->out.info = info;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_QueryTrustedDomainInfoBySid
+ ***************************************************************************/
+
+NTSTATUS _lsa_QueryTrustedDomainInfoBySid(struct pipes_struct *p,
+ struct lsa_QueryTrustedDomainInfoBySid *r)
+{
+ NTSTATUS status;
+ struct policy_handle trustdom_handle;
+ struct lsa_OpenTrustedDomain o;
+ struct lsa_QueryTrustedDomainInfo q;
+ struct lsa_Close c;
+
+ o.in.handle = r->in.handle;
+ o.in.sid = r->in.dom_sid;
+ o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ o.out.trustdom_handle = &trustdom_handle;
+
+ status = _lsa_OpenTrustedDomain(p, &o);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ q.in.trustdom_handle = &trustdom_handle;
+ q.in.level = r->in.level;
+ q.out.info = r->out.info;
+
+ status = _lsa_QueryTrustedDomainInfo(p, &q);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ c.in.handle = &trustdom_handle;
+ c.out.handle = &trustdom_handle;
+
+ return _lsa_Close(p, &c);
+}
+
+/***************************************************************************
+ _lsa_QueryTrustedDomainInfoByName
+ ***************************************************************************/
+
+NTSTATUS _lsa_QueryTrustedDomainInfoByName(struct pipes_struct *p,
+ struct lsa_QueryTrustedDomainInfoByName *r)
+{
+ NTSTATUS status;
+ struct policy_handle trustdom_handle;
+ struct lsa_OpenTrustedDomainByName o;
+ struct lsa_QueryTrustedDomainInfo q;
+ struct lsa_Close c;
+
+ o.in.handle = r->in.handle;
+ o.in.name.string = r->in.trusted_domain->string;
+ o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ o.out.trustdom_handle = &trustdom_handle;
+
+ status = _lsa_OpenTrustedDomainByName(p, &o);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return status;
+ }
+
+ q.in.trustdom_handle = &trustdom_handle;
+ q.in.level = r->in.level;
+ q.out.info = r->out.info;
+
+ status = _lsa_QueryTrustedDomainInfo(p, &q);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ c.in.handle = &trustdom_handle;
+ c.out.handle = &trustdom_handle;
+
+ return _lsa_Close(p, &c);
+}
+
+/***************************************************************************
+ _lsa_CreateSecret
+ ***************************************************************************/
+
+NTSTATUS _lsa_CreateSecret(struct pipes_struct *p,
+ struct lsa_CreateSecret *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ struct lsa_info *handle;
+ uint32_t acc_granted;
+ struct security_descriptor *psd;
+ size_t sd_size;
+
+ /* find the connection policy handle. */
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights */
+
+ if (!(handle->access & LSA_POLICY_CREATE_SECRET)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &r->in.access_mask);
+
+ /* map the generic bits to the lsa policy ones */
+ se_map_generic(&r->in.access_mask, &lsa_secret_mapping);
+
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size,
+ &lsa_secret_mapping,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0,
+ r->in.access_mask,
+ &acc_granted, "_lsa_CreateSecret");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!r->in.name.string) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (strlen(r->in.name.string) > 128) {
+ return NT_STATUS_NAME_TOO_LONG;
+ }
+
+ status = pdb_get_secret(p->mem_ctx, r->in.name.string,
+ NULL, NULL, NULL, NULL, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = pdb_set_secret(r->in.name.string, NULL, NULL, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = create_lsa_policy_handle(p->mem_ctx, p,
+ LSA_HANDLE_SECRET_TYPE,
+ acc_granted,
+ NULL,
+ r->in.name.string,
+ psd,
+ r->out.sec_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_SetSecret
+ ***************************************************************************/
+
+NTSTATUS _lsa_SetSecret(struct pipes_struct *p,
+ struct lsa_SetSecret *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ struct lsa_info *info = NULL;
+ DATA_BLOB blob_new, blob_old;
+ DATA_BLOB cleartext_blob_new = data_blob_null;
+ DATA_BLOB cleartext_blob_old = data_blob_null;
+ DATA_BLOB *cleartext_blob_new_p = NULL;
+ DATA_BLOB *cleartext_blob_old_p = NULL;
+ DATA_BLOB session_key;
+
+ info = find_policy_by_hnd(p,
+ r->in.sec_handle,
+ LSA_HANDLE_SECRET_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_SECRET_SET_VALUE)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (r->in.new_val) {
+ blob_new = data_blob_const(r->in.new_val->data,
+ r->in.new_val->length);
+
+ status = sess_decrypt_blob(p->mem_ctx, &blob_new,
+ &session_key,
+ &cleartext_blob_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ cleartext_blob_new_p = &cleartext_blob_new;
+ }
+
+ if (r->in.old_val) {
+ blob_old = data_blob_const(r->in.old_val->data,
+ r->in.old_val->length);
+
+ status = sess_decrypt_blob(p->mem_ctx, &blob_old,
+ &session_key,
+ &cleartext_blob_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ cleartext_blob_old_p = &cleartext_blob_old;
+ }
+
+ status = pdb_set_secret(info->name, cleartext_blob_new_p, cleartext_blob_old_p, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(10,("_lsa_SetSecret: successfully set new secret\n"));
+ dump_data(10, cleartext_blob_new.data, cleartext_blob_new.length);
+ DEBUG(10,("_lsa_SetSecret: successfully set old secret\n"));
+ dump_data(10, cleartext_blob_old.data, cleartext_blob_old.length);
+#endif
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_QuerySecret
+ ***************************************************************************/
+
+NTSTATUS _lsa_QuerySecret(struct pipes_struct *p,
+ struct lsa_QuerySecret *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct lsa_info *info = NULL;
+ DATA_BLOB blob_new, blob_old;
+ DATA_BLOB blob_new_crypt, blob_old_crypt;
+ DATA_BLOB session_key;
+ NTTIME nttime_new, nttime_old;
+ NTSTATUS status;
+
+ info = find_policy_by_hnd(p,
+ r->in.sec_handle,
+ LSA_HANDLE_SECRET_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_SECRET_QUERY_VALUE)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pdb_get_secret(p->mem_ctx, info->name,
+ &blob_new, &nttime_new,
+ &blob_old, &nttime_old,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (r->in.new_val) {
+ if (blob_new.length) {
+ if (!r->out.new_val->buf) {
+ r->out.new_val->buf = talloc_zero(p->mem_ctx, struct lsa_DATA_BUF);
+ }
+ if (!r->out.new_val->buf) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ blob_new_crypt = sess_encrypt_blob(p->mem_ctx, &blob_new,
+ &session_key);
+ if (!blob_new_crypt.length) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ r->out.new_val->buf->data = blob_new_crypt.data;
+ r->out.new_val->buf->length = blob_new_crypt.length;
+ r->out.new_val->buf->size = blob_new_crypt.length;
+ }
+ }
+
+ if (r->in.old_val) {
+ if (blob_old.length) {
+ if (!r->out.old_val->buf) {
+ r->out.old_val->buf = talloc_zero(p->mem_ctx, struct lsa_DATA_BUF);
+ }
+ if (!r->out.old_val->buf) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ blob_old_crypt = sess_encrypt_blob(p->mem_ctx, &blob_old,
+ &session_key);
+ if (!blob_old_crypt.length) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ r->out.old_val->buf->data = blob_old_crypt.data;
+ r->out.old_val->buf->length = blob_old_crypt.length;
+ r->out.old_val->buf->size = blob_old_crypt.length;
+ }
+ }
+
+ if (r->out.new_mtime) {
+ *r->out.new_mtime = nttime_new;
+ }
+
+ if (r->out.old_mtime) {
+ *r->out.old_mtime = nttime_old;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_DeleteObject
+ ***************************************************************************/
+
+NTSTATUS _lsa_DeleteObject(struct pipes_struct *p,
+ struct lsa_DeleteObject *r)
+{
+ NTSTATUS status;
+ struct lsa_info *info = NULL;
+
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ DCESRV_HANDLE_ANY,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & SEC_STD_DELETE)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ switch (info->type) {
+ case LSA_HANDLE_ACCOUNT_TYPE:
+ status = privilege_delete_account(&info->sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("_lsa_DeleteObject: privilege_delete_account gave: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ break;
+ case LSA_HANDLE_TRUST_TYPE:
+ if (!pdb_del_trusteddom_pw(info->name)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ status = NT_STATUS_OK;
+ break;
+ case LSA_HANDLE_SECRET_TYPE:
+ status = pdb_delete_secret(info->name);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ close_policy_hnd(p, r->in.handle);
+ ZERO_STRUCTP(r->out.handle);
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_EnumPrivs
+ ***************************************************************************/
+
+NTSTATUS _lsa_EnumPrivs(struct pipes_struct *p,
+ struct lsa_EnumPrivs *r)
+{
+ struct lsa_info *handle;
+ uint32_t i;
+ uint32_t enum_context = *r->in.resume_handle;
+ int num_privs = num_privileges_in_short_list();
+ struct lsa_PrivEntry *entries = NULL;
+ NTSTATUS status;
+
+ /* remember that the enum_context starts at 0 and not 1 */
+
+ if ( enum_context >= num_privs )
+ return NT_STATUS_NO_MORE_ENTRIES;
+
+ DEBUG(10,("_lsa_EnumPrivs: enum_context:%d total entries:%d\n",
+ enum_context, num_privs));
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights
+ I don't know if it's the right one. not documented. */
+
+ if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ if (num_privs) {
+ entries = talloc_zero_array(p->mem_ctx, struct lsa_PrivEntry, num_privs);
+ if (!entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ entries = NULL;
+ }
+
+ for (i = 0; i < num_privs; i++) {
+ if( i < enum_context) {
+
+ init_lsa_StringLarge(&entries[i].name, NULL);
+
+ entries[i].luid.low = 0;
+ entries[i].luid.high = 0;
+ } else {
+
+ init_lsa_StringLarge(&entries[i].name, sec_privilege_name_from_index(i));
+
+ entries[i].luid.low = sec_privilege_from_index(i);
+ entries[i].luid.high = 0;
+ }
+ }
+
+ enum_context = num_privs;
+
+ *r->out.resume_handle = enum_context;
+ r->out.privs->count = num_privs;
+ r->out.privs->privs = entries;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_LookupPrivDisplayName
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupPrivDisplayName(struct pipes_struct *p,
+ struct lsa_LookupPrivDisplayName *r)
+{
+ struct lsa_info *handle;
+ const char *description;
+ struct lsa_StringLarge *lsa_name;
+ NTSTATUS status;
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights */
+
+ /*
+ * I don't know if it's the right one. not documented.
+ */
+ if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ DEBUG(10,("_lsa_LookupPrivDisplayName: name = %s\n", r->in.name->string));
+
+ description = get_privilege_dispname(r->in.name->string);
+ if (!description) {
+ DEBUG(10,("_lsa_LookupPrivDisplayName: doesn't exist\n"));
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+
+ DEBUG(10,("_lsa_LookupPrivDisplayName: display name = %s\n", description));
+
+ lsa_name = talloc_zero(p->mem_ctx, struct lsa_StringLarge);
+ if (!lsa_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ init_lsa_StringLarge(lsa_name, description);
+
+ *r->out.returned_language_id = r->in.language_id;
+ *r->out.disp_name = lsa_name;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_EnumAccounts
+ ***************************************************************************/
+
+NTSTATUS _lsa_EnumAccounts(struct pipes_struct *p,
+ struct lsa_EnumAccounts *r)
+{
+ struct lsa_info *handle;
+ struct dom_sid *sid_list;
+ int i, j, num_entries;
+ NTSTATUS status;
+ struct lsa_SidPtr *sids = NULL;
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(handle->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ sid_list = NULL;
+ num_entries = 0;
+
+ /* The only way we can currently find out all the SIDs that have been
+ privileged is to scan all privileges */
+
+ status = privilege_enumerate_accounts(&sid_list, &num_entries);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (*r->in.resume_handle >= num_entries) {
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ if (num_entries - *r->in.resume_handle) {
+ sids = talloc_zero_array(p->mem_ctx, struct lsa_SidPtr,
+ num_entries - *r->in.resume_handle);
+ if (!sids) {
+ talloc_free(sid_list);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = *r->in.resume_handle, j = 0; i < num_entries; i++, j++) {
+ sids[j].sid = dom_sid_dup(p->mem_ctx, &sid_list[i]);
+ if (!sids[j].sid) {
+ talloc_free(sid_list);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+
+ talloc_free(sid_list);
+
+ *r->out.resume_handle = num_entries;
+ r->out.sids->num_sids = num_entries;
+ r->out.sids->sids = sids;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_GetUserName
+ ***************************************************************************/
+
+NTSTATUS _lsa_GetUserName(struct pipes_struct *p,
+ struct lsa_GetUserName *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const char *username, *domname;
+ struct lsa_String *account_name = NULL;
+ struct lsa_String *authority_name = NULL;
+
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (r->in.account_name &&
+ *r->in.account_name) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (r->in.authority_name &&
+ *r->in.authority_name) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ /*
+ * I'm 99% sure this is not the right place to do this,
+ * global_sid_Anonymous should probably be put into the token
+ * instead of the guest id -- vl
+ */
+ if (!lookup_sid(p->mem_ctx, &global_sid_Anonymous,
+ &domname, &username, NULL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ username = session_info->unix_info->sanitized_username;
+ domname = session_info->info->domain_name;
+ }
+
+ account_name = talloc(p->mem_ctx, struct lsa_String);
+ if (!account_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ init_lsa_String(account_name, username);
+
+ if (r->out.authority_name) {
+ authority_name = talloc(p->mem_ctx, struct lsa_String);
+ if (!authority_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ init_lsa_String(authority_name, domname);
+ }
+
+ *r->out.account_name = account_name;
+ if (r->out.authority_name) {
+ *r->out.authority_name = authority_name;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_CreateAccount
+ ***************************************************************************/
+
+NTSTATUS _lsa_CreateAccount(struct pipes_struct *p,
+ struct lsa_CreateAccount *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ struct lsa_info *handle;
+ uint32_t acc_granted;
+ struct security_descriptor *psd;
+ size_t sd_size;
+ uint32_t owner_access = (LSA_ACCOUNT_ALL_ACCESS &
+ ~(LSA_ACCOUNT_ADJUST_PRIVILEGES|
+ LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|
+ SEC_STD_DELETE));
+
+ /* find the connection policy handle. */
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights */
+
+ if (!(handle->access & LSA_POLICY_CREATE_ACCOUNT)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &r->in.access_mask);
+
+ /* map the generic bits to the lsa policy ones */
+ se_map_generic(&r->in.access_mask, &lsa_account_mapping);
+
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size,
+ &lsa_account_mapping,
+ r->in.sid, owner_access);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, r->in.access_mask,
+ &acc_granted, "_lsa_CreateAccount");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if ( is_privileged_sid( r->in.sid ) )
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+
+ status = create_lsa_policy_handle(p->mem_ctx, p,
+ LSA_HANDLE_ACCOUNT_TYPE,
+ acc_granted,
+ r->in.sid,
+ NULL,
+ psd,
+ r->out.acct_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return privilege_create_account(r->in.sid);
+}
+
+/***************************************************************************
+ _lsa_OpenAccount
+ ***************************************************************************/
+
+NTSTATUS _lsa_OpenAccount(struct pipes_struct *p,
+ struct lsa_OpenAccount *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *psd = NULL;
+ size_t sd_size;
+ uint32_t des_access = r->in.access_mask;
+ uint32_t acc_granted;
+ uint32_t owner_access = (LSA_ACCOUNT_ALL_ACCESS &
+ ~(LSA_ACCOUNT_ADJUST_PRIVILEGES|
+ LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|
+ SEC_STD_DELETE));
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+ (void)find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* des_access is for the account here, not the policy
+ * handle - so don't check against policy handle. */
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ /* map the generic bits to the lsa account ones */
+ se_map_generic(&des_access, &lsa_account_mapping);
+
+ /* get the generic lsa account SD until we store it */
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size,
+ &lsa_account_mapping,
+ r->in.sid, owner_access);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0, des_access,
+ &acc_granted, "_lsa_OpenAccount" );
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* TODO: Fis the parsing routine before reenabling this check! */
+ #if 0
+ if (!lookup_sid(&handle->sid, dom_name, name, &type))
+ return NT_STATUS_ACCESS_DENIED;
+ #endif
+
+ status = create_lsa_policy_handle(p->mem_ctx, p,
+ LSA_HANDLE_ACCOUNT_TYPE,
+ acc_granted,
+ r->in.sid,
+ NULL,
+ psd,
+ r->out.acct_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_EnumPrivsAccount
+ For a given SID, enumerate all the privilege this account has.
+ ***************************************************************************/
+
+NTSTATUS _lsa_EnumPrivsAccount(struct pipes_struct *p,
+ struct lsa_EnumPrivsAccount *r)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct lsa_info *info=NULL;
+ PRIVILEGE_SET *privileges;
+ struct lsa_PrivilegeSet *priv_set = NULL;
+ struct dom_sid_buf buf;
+
+ /* find the connection policy handle. */
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_ACCOUNT_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_ACCOUNT_VIEW))
+ return NT_STATUS_ACCESS_DENIED;
+
+ status = get_privileges_for_sid_as_set(p->mem_ctx, &privileges, &info->sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *r->out.privs = priv_set = talloc_zero(p->mem_ctx, struct lsa_PrivilegeSet);
+ if (!priv_set) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10,("_lsa_EnumPrivsAccount: %s has %d privileges\n",
+ dom_sid_str_buf(&info->sid, &buf),
+ privileges->count));
+
+ priv_set->count = privileges->count;
+ priv_set->unknown = 0;
+ priv_set->set = talloc_move(priv_set, &privileges->set);
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_GetSystemAccessAccount
+ ***************************************************************************/
+
+NTSTATUS _lsa_GetSystemAccessAccount(struct pipes_struct *p,
+ struct lsa_GetSystemAccessAccount *r)
+{
+ NTSTATUS status;
+ struct lsa_info *info = NULL;
+ struct lsa_EnumPrivsAccount e;
+ struct lsa_PrivilegeSet *privset;
+
+ /* find the connection policy handle. */
+
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_ACCOUNT_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_ACCOUNT_VIEW))
+ return NT_STATUS_ACCESS_DENIED;
+
+ privset = talloc_zero(p->mem_ctx, struct lsa_PrivilegeSet);
+ if (!privset) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ e.in.handle = r->in.handle;
+ e.out.privs = &privset;
+
+ status = _lsa_EnumPrivsAccount(p, &e);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("_lsa_GetSystemAccessAccount: "
+ "failed to call _lsa_EnumPrivsAccount(): %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ /* Samba4 would iterate over the privset to merge the policy mode bits,
+ * not sure samba3 can do the same here, so just return what we did in
+ * the past - gd */
+
+ /*
+ 0x01 -> Log on locally
+ 0x02 -> Access this computer from network
+ 0x04 -> Log on as a batch job
+ 0x10 -> Log on as a service
+
+ they can be ORed together
+ */
+
+ *r->out.access_mask = LSA_POLICY_MODE_INTERACTIVE |
+ LSA_POLICY_MODE_NETWORK;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ update the systemaccount information
+ ***************************************************************************/
+
+NTSTATUS _lsa_SetSystemAccessAccount(struct pipes_struct *p,
+ struct lsa_SetSystemAccessAccount *r)
+{
+ struct lsa_info *info=NULL;
+ NTSTATUS status;
+ GROUP_MAP *map;
+
+ /* find the connection policy handle. */
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_ACCOUNT_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ map = talloc_zero(p->mem_ctx, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!pdb_getgrsid(map, info->sid)) {
+ TALLOC_FREE(map);
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ status = pdb_update_group_mapping_entry(map);
+ TALLOC_FREE(map);
+ return status;
+}
+
+/***************************************************************************
+ _lsa_AddPrivilegesToAccount
+ For a given SID, add some privileges.
+ ***************************************************************************/
+
+NTSTATUS _lsa_AddPrivilegesToAccount(struct pipes_struct *p,
+ struct lsa_AddPrivilegesToAccount *r)
+{
+ struct lsa_info *info = NULL;
+ struct lsa_PrivilegeSet *set = NULL;
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_ACCOUNT_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_ACCOUNT_ADJUST_PRIVILEGES)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ set = r->in.privs;
+
+ if ( !grant_privilege_set( &info->sid, set ) ) {
+ struct dom_sid_buf buf;
+ DEBUG(3,("_lsa_AddPrivilegesToAccount: grant_privilege_set(%s) failed!\n",
+ dom_sid_str_buf(&info->sid, &buf)));
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_RemovePrivilegesFromAccount
+ For a given SID, remove some privileges.
+ ***************************************************************************/
+
+NTSTATUS _lsa_RemovePrivilegesFromAccount(struct pipes_struct *p,
+ struct lsa_RemovePrivilegesFromAccount *r)
+{
+ struct lsa_info *info = NULL;
+ struct lsa_PrivilegeSet *set = NULL;
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_ACCOUNT_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_ACCOUNT_ADJUST_PRIVILEGES)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ set = r->in.privs;
+
+ if ( !revoke_privilege_set( &info->sid, set) ) {
+ struct dom_sid_buf buf;
+ DEBUG(3,("_lsa_RemovePrivilegesFromAccount: revoke_privilege(%s) failed!\n",
+ dom_sid_str_buf(&info->sid, &buf)));
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_LookupPrivName
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupPrivName(struct pipes_struct *p,
+ struct lsa_LookupPrivName *r)
+{
+ struct lsa_info *info = NULL;
+ const char *name;
+ struct lsa_StringLarge *lsa_name;
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_POLICY_VIEW_LOCAL_INFORMATION)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (r->in.luid->high != 0) {
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+
+ name = sec_privilege_name(r->in.luid->low);
+ if (!name) {
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+
+ lsa_name = talloc_zero(p->mem_ctx, struct lsa_StringLarge);
+ if (!lsa_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ lsa_name->string = talloc_strdup(lsa_name, name);
+ if (!lsa_name->string) {
+ TALLOC_FREE(lsa_name);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *r->out.name = lsa_name;
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_QuerySecurity
+ ***************************************************************************/
+
+NTSTATUS _lsa_QuerySecurity(struct pipes_struct *p,
+ struct lsa_QuerySecurity *r)
+{
+ struct lsa_info *handle=NULL;
+ struct security_descriptor *psd = NULL;
+ size_t sd_size = 0;
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ DCESRV_HANDLE_ANY,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ switch (handle->type) {
+ case LSA_HANDLE_POLICY_TYPE:
+ case LSA_HANDLE_ACCOUNT_TYPE:
+ case LSA_HANDLE_TRUST_TYPE:
+ case LSA_HANDLE_SECRET_TYPE:
+ psd = handle->sd;
+ sd_size = ndr_size_security_descriptor(psd, 0);
+ status = NT_STATUS_OK;
+ break;
+ default:
+ status = NT_STATUS_INVALID_HANDLE;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *r->out.sdbuf = make_sec_desc_buf(p->mem_ctx, sd_size, psd);
+ if (!*r->out.sdbuf) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_AddAccountRights
+ ***************************************************************************/
+
+NTSTATUS _lsa_AddAccountRights(struct pipes_struct *p,
+ struct lsa_AddAccountRights *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ int i = 0;
+ uint32_t acc_granted = 0;
+ struct security_descriptor *psd = NULL;
+ size_t sd_size;
+ struct dom_sid sid;
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+ (void)find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* get the generic lsa account SD for this SID until we store it */
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size,
+ &lsa_account_mapping,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * From the MS DOCs. If the sid doesn't exist, ask for LSA_POLICY_CREATE_ACCOUNT
+ * on the policy handle. If it does, ask for
+ * LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|LSA_ACCOUNT_VIEW,
+ * on the account sid. We don't check here so just use the latter. JRA.
+ */
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0,
+ LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|LSA_ACCOUNT_VIEW,
+ &acc_granted, "_lsa_AddAccountRights" );
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* according to an NT4 PDC, you can add privileges to SIDs even without
+ call_lsa_create_account() first. And you can use any arbitrary SID. */
+
+ sid_copy( &sid, r->in.sid );
+
+ for ( i=0; i < r->in.rights->count; i++ ) {
+
+ const char *privname = r->in.rights->names[i].string;
+
+ /* only try to add non-null strings */
+
+ if ( !privname )
+ continue;
+
+ if ( !grant_privilege_by_name( &sid, privname ) ) {
+ DEBUG(2,("_lsa_AddAccountRights: Failed to add privilege [%s]\n",
+ privname ));
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_RemoveAccountRights
+ ***************************************************************************/
+
+NTSTATUS _lsa_RemoveAccountRights(struct pipes_struct *p,
+ struct lsa_RemoveAccountRights *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ int i = 0;
+ struct security_descriptor *psd = NULL;
+ size_t sd_size;
+ struct dom_sid sid;
+ const char *privname = NULL;
+ uint32_t acc_granted = 0;
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+ (void)find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* get the generic lsa account SD for this SID until we store it */
+ status = make_lsa_object_sd(p->mem_ctx, &psd, &sd_size,
+ &lsa_account_mapping,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * From the MS DOCs. We need
+ * LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|LSA_ACCOUNT_VIEW
+ * and DELETE on the account sid.
+ */
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID, 0,
+ LSA_ACCOUNT_ADJUST_PRIVILEGES|LSA_ACCOUNT_ADJUST_SYSTEM_ACCESS|
+ LSA_ACCOUNT_VIEW|SEC_STD_DELETE,
+ &acc_granted, "_lsa_RemoveAccountRights");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ sid_copy( &sid, r->in.sid );
+
+ if ( r->in.remove_all ) {
+ if ( !revoke_all_privileges( &sid ) )
+ return NT_STATUS_ACCESS_DENIED;
+
+ return NT_STATUS_OK;
+ }
+
+ for ( i=0; i < r->in.rights->count; i++ ) {
+
+ privname = r->in.rights->names[i].string;
+
+ /* only try to add non-null strings */
+
+ if ( !privname )
+ continue;
+
+ if ( !revoke_privilege_by_name( &sid, privname ) ) {
+ DEBUG(2,("_lsa_RemoveAccountRights: Failed to revoke privilege [%s]\n",
+ privname ));
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+********************************************************************/
+
+static NTSTATUS init_lsa_right_set(TALLOC_CTX *mem_ctx,
+ struct lsa_RightSet *r,
+ PRIVILEGE_SET *privileges)
+{
+ uint32_t i;
+ const char *privname;
+ const char **privname_array = NULL;
+ size_t num_priv = 0;
+
+ for (i=0; i<privileges->count; i++) {
+ if (privileges->set[i].luid.high) {
+ continue;
+ }
+ privname = sec_privilege_name(privileges->set[i].luid.low);
+ if (privname) {
+ if (!add_string_to_array(mem_ctx, privname,
+ &privname_array, &num_priv)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+
+ if (num_priv) {
+
+ r->names = talloc_zero_array(mem_ctx, struct lsa_StringLarge,
+ num_priv);
+ if (!r->names) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_priv; i++) {
+ init_lsa_StringLarge(&r->names[i], privname_array[i]);
+ }
+
+ r->count = num_priv;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_EnumAccountRights
+ ***************************************************************************/
+
+NTSTATUS _lsa_EnumAccountRights(struct pipes_struct *p,
+ struct lsa_EnumAccountRights *r)
+{
+ NTSTATUS status;
+ struct lsa_info *info = NULL;
+ PRIVILEGE_SET *privileges;
+ struct dom_sid_buf buf;
+
+ /* find the connection policy handle. */
+
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_ACCOUNT_VIEW)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* according to an NT4 PDC, you can add privileges to SIDs even without
+ call_lsa_create_account() first. And you can use any arbitrary SID. */
+
+ /* according to MS-LSAD 3.1.4.5.10 it is required to return
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND if the account sid was not found in
+ * the lsa database */
+
+ status = get_privileges_for_sid_as_set(p->mem_ctx, &privileges, r->in.sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("_lsa_EnumAccountRights: %s has %d privileges\n",
+ dom_sid_str_buf(r->in.sid, &buf),
+ privileges->count));
+
+ status = init_lsa_right_set(p->mem_ctx, r->out.rights, privileges);
+
+ return status;
+}
+
+/***************************************************************************
+ _lsa_LookupPrivValue
+ ***************************************************************************/
+
+NTSTATUS _lsa_LookupPrivValue(struct pipes_struct *p,
+ struct lsa_LookupPrivValue *r)
+{
+ struct lsa_info *info = NULL;
+ const char *name = NULL;
+ NTSTATUS status;
+
+ /* find the connection policy handle. */
+
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_POLICY_LOOKUP_NAMES))
+ return NT_STATUS_ACCESS_DENIED;
+
+ name = r->in.name->string;
+
+ DEBUG(10,("_lsa_lookup_priv_value: name = %s\n", name));
+
+ r->out.luid->low = sec_privilege_id(name);
+ r->out.luid->high = 0;
+ if (r->out.luid->low == SEC_PRIV_INVALID) {
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_EnumAccountsWithUserRight
+ ***************************************************************************/
+
+NTSTATUS _lsa_EnumAccountsWithUserRight(struct pipes_struct *p,
+ struct lsa_EnumAccountsWithUserRight *r)
+{
+ NTSTATUS status;
+ struct lsa_info *info = NULL;
+ struct dom_sid *sids = NULL;
+ int num_sids = 0;
+ uint32_t i;
+ enum sec_privilege privilege;
+
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(info->access & LSA_POLICY_LOOKUP_NAMES)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!r->in.name || !r->in.name->string) {
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+
+ privilege = sec_privilege_id(r->in.name->string);
+ if (privilege == SEC_PRIV_INVALID) {
+ return NT_STATUS_NO_SUCH_PRIVILEGE;
+ }
+
+ status = privilege_enum_sids(privilege, p->mem_ctx,
+ &sids, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->out.sids->num_sids = num_sids;
+ r->out.sids->sids = talloc_array(p->mem_ctx, struct lsa_SidPtr,
+ r->out.sids->num_sids);
+
+ for (i=0; i < r->out.sids->num_sids; i++) {
+ r->out.sids->sids[i].sid = dom_sid_dup(r->out.sids->sids,
+ &sids[i]);
+ if (!r->out.sids->sids[i].sid) {
+ TALLOC_FREE(r->out.sids->sids);
+ r->out.sids->num_sids = 0;
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***************************************************************************
+ _lsa_Delete
+ ***************************************************************************/
+
+NTSTATUS _lsa_Delete(struct pipes_struct *p,
+ struct lsa_Delete *r)
+{
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+static NTSTATUS info_ex_2_pdb_trusted_domain(
+ struct lsa_TrustDomainInfoInfoEx *info_ex,
+ struct pdb_trusted_domain *td)
+{
+ if (info_ex->domain_name.string == NULL ||
+ info_ex->netbios_name.string == NULL ||
+ info_ex->sid == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ td->domain_name = talloc_strdup(td, info_ex->domain_name.string);
+ td->netbios_name = talloc_strdup(td, info_ex->netbios_name.string);
+ sid_copy(&td->security_identifier, info_ex->sid);
+ if (td->domain_name == NULL ||
+ td->netbios_name == NULL ||
+ is_null_sid(&td->security_identifier)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ td->trust_direction = info_ex->trust_direction;
+ td->trust_type = info_ex->trust_type;
+ td->trust_attributes = info_ex->trust_attributes;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS setInfoTrustedDomain_base(struct pipes_struct *p,
+ TALLOC_CTX *mem_ctx,
+ struct lsa_info *policy,
+ enum lsa_TrustDomInfoEnum level,
+ union lsa_TrustedDomainInfo *info)
+{
+ struct lsa_TrustDomainInfoAuthInfoInternal *auth_info_int = NULL;
+ DATA_BLOB auth_blob;
+ struct trustDomainPasswords auth_struct;
+ NTSTATUS nt_status;
+
+ struct pdb_trusted_domain *td;
+ struct pdb_trusted_domain *orig_td;
+
+ td = talloc_zero(mem_ctx, struct pdb_trusted_domain);
+ if (td == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (level) {
+ case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET:
+ if (!(policy->access & LSA_TRUSTED_SET_POSIX)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ td->trust_posix_offset = &info->posix_offset.posix_offset;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_INFO_EX:
+ if (!(policy->access & LSA_TRUSTED_SET_POSIX)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ nt_status = info_ex_2_pdb_trusted_domain(&info->info_ex, td);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO:
+ if (!(policy->access & LSA_TRUSTED_SET_AUTH)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ nt_status = auth_info_2_auth_blob(td, &info->auth_info,
+ &td->trust_auth_incoming,
+ &td->trust_auth_outgoing);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO:
+ if (!(policy->access & (LSA_TRUSTED_SET_AUTH | LSA_TRUSTED_SET_POSIX))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ td->trust_posix_offset = &info->full_info.posix_offset.posix_offset;
+ nt_status = info_ex_2_pdb_trusted_domain(&info->full_info.info_ex,
+ td);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ nt_status = auth_info_2_auth_blob(td,
+ &info->full_info.auth_info,
+ &td->trust_auth_incoming,
+ &td->trust_auth_outgoing);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL:
+ if (!(policy->access & LSA_TRUSTED_SET_AUTH)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ auth_info_int = &info->auth_info_internal;
+ break;
+ case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL:
+ if (!(policy->access & (LSA_TRUSTED_SET_AUTH | LSA_TRUSTED_SET_POSIX))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ td->trust_posix_offset = &info->full_info_internal.posix_offset.posix_offset;
+ nt_status = info_ex_2_pdb_trusted_domain(&info->full_info_internal.info_ex,
+ td);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ auth_info_int = &info->full_info_internal.auth_info;
+ break;
+ case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES:
+ if (!(policy->access & LSA_TRUSTED_SET_POSIX)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ td->supported_enc_type = &info->enc_types.enc_types;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* decode auth_info_int if set */
+ if (auth_info_int) {
+
+ /* now decrypt blob */
+ auth_blob = data_blob_const(auth_info_int->auth_blob.data,
+ auth_info_int->auth_blob.size);
+
+ nt_status = get_trustdom_auth_blob(p, mem_ctx,
+ &auth_blob, &auth_struct);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ } else {
+ memset(&auth_struct, 0, sizeof(auth_struct));
+ }
+
+/* TODO: verify only one object matches the dns/netbios/sid triplet and that
+ * this is the one we already have */
+
+/* TODO: check if the trust direction is changed and we need to add or remove
+ * auth data */
+
+/* TODO: check if trust type shall be changed and return an error in this case
+ * */
+ nt_status = pdb_get_trusted_domain_by_sid(p->mem_ctx, &policy->sid,
+ &orig_td);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+
+ /* TODO: should we fetch previous values from the existing entry
+ * and append them ? */
+ if (auth_struct.incoming.count) {
+ nt_status = get_trustauth_inout_blob(mem_ctx,
+ &auth_struct.incoming,
+ &td->trust_auth_incoming);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ } else {
+ ZERO_STRUCT(td->trust_auth_incoming);
+ }
+
+ if (auth_struct.outgoing.count) {
+ nt_status = get_trustauth_inout_blob(mem_ctx,
+ &auth_struct.outgoing,
+ &td->trust_auth_outgoing);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ } else {
+ ZERO_STRUCT(td->trust_auth_outgoing);
+ }
+
+ nt_status = pdb_set_trusted_domain(orig_td->domain_name, td);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _lsa_SetTrustedDomainInfo(struct pipes_struct *p,
+ struct lsa_SetTrustedDomainInfo *r)
+{
+ NTSTATUS status;
+ struct policy_handle trustdom_handle;
+ struct lsa_OpenTrustedDomain o;
+ struct lsa_SetInformationTrustedDomain s;
+ struct lsa_Close c;
+
+ o.in.handle = r->in.handle;
+ o.in.sid = r->in.dom_sid;
+ o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ o.out.trustdom_handle = &trustdom_handle;
+
+ status = _lsa_OpenTrustedDomain(p, &o);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ s.in.trustdom_handle = &trustdom_handle;
+ s.in.level = r->in.level;
+ s.in.info = r->in.info;
+
+ status = _lsa_SetInformationTrustedDomain(p, &s);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ c.in.handle = &trustdom_handle;
+ c.out.handle = &trustdom_handle;
+
+ return _lsa_Close(p, &c);
+}
+
+NTSTATUS _lsa_SetTrustedDomainInfoByName(struct pipes_struct *p,
+ struct lsa_SetTrustedDomainInfoByName *r)
+{
+ NTSTATUS status;
+ struct policy_handle trustdom_handle;
+ struct lsa_OpenTrustedDomainByName o;
+ struct lsa_SetInformationTrustedDomain s;
+ struct lsa_Close c;
+
+ o.in.handle = r->in.handle;
+ o.in.name.string = r->in.trusted_domain->string;
+ o.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ o.out.trustdom_handle = &trustdom_handle;
+
+ status = _lsa_OpenTrustedDomainByName(p, &o);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return status;
+ }
+
+ s.in.trustdom_handle = &trustdom_handle;
+ s.in.level = r->in.level;
+ s.in.info = r->in.info;
+
+ status = _lsa_SetInformationTrustedDomain(p, &s);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ c.in.handle = &trustdom_handle;
+ c.out.handle = &trustdom_handle;
+
+ return _lsa_Close(p, &c);
+}
+
+NTSTATUS _lsa_SetInformationTrustedDomain(struct pipes_struct *p,
+ struct lsa_SetInformationTrustedDomain *r)
+{
+ struct lsa_info *policy;
+ NTSTATUS status;
+
+ policy = find_policy_by_hnd(p,
+ r->in.trustdom_handle,
+ LSA_HANDLE_TRUST_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ return setInfoTrustedDomain_base(p, p->mem_ctx, policy,
+ r->in.level, r->in.info);
+}
+
+
+/*
+ * From here on the server routines are just dummy ones to make smbd link with
+ * librpc/gen_ndr/srv_lsa.c. These routines are actually never called, we are
+ * pulling the server stubs across one by one.
+ */
+
+NTSTATUS _lsa_SetSecObj(struct pipes_struct *p, struct lsa_SetSecObj *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_ChangePassword(struct pipes_struct *p,
+ struct lsa_ChangePassword *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_SetInfoPolicy(struct pipes_struct *p, struct lsa_SetInfoPolicy *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_ClearAuditLog(struct pipes_struct *p, struct lsa_ClearAuditLog *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_GetQuotasForAccount(struct pipes_struct *p,
+ struct lsa_GetQuotasForAccount *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_SetQuotasForAccount(struct pipes_struct *p,
+ struct lsa_SetQuotasForAccount *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_StorePrivateData(struct pipes_struct *p,
+ struct lsa_StorePrivateData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_RetrievePrivateData(struct pipes_struct *p,
+ struct lsa_RetrievePrivateData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_SetInfoPolicy2(struct pipes_struct *p,
+ struct lsa_SetInfoPolicy2 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_EnumTrustedDomainsEx(struct pipes_struct *p,
+ struct lsa_EnumTrustedDomainsEx *r)
+{
+ struct lsa_info *info;
+ uint32_t count;
+ struct pdb_trusted_domain **domains;
+ struct lsa_TrustDomainInfoInfoEx *entries;
+ int i;
+ NTSTATUS nt_status;
+
+ /* bail out early if pdb backend is not capable of ex trusted domains,
+ * if we don't do that, the client might not call
+ * _lsa_EnumTrustedDomains() afterwards - gd */
+
+ if (!(pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX)) {
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ info = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_POLICY_TYPE,
+ struct lsa_info,
+ &nt_status);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* check if the user has enough rights */
+ if (!(info->access & LSA_POLICY_VIEW_LOCAL_INFORMATION))
+ return NT_STATUS_ACCESS_DENIED;
+
+ become_root();
+ nt_status = pdb_enum_trusted_domains(p->mem_ctx, &count, &domains);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ entries = talloc_zero_array(p->mem_ctx, struct lsa_TrustDomainInfoInfoEx,
+ count);
+ if (!entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<count; i++) {
+ init_lsa_StringLarge(&entries[i].domain_name,
+ domains[i]->domain_name);
+ init_lsa_StringLarge(&entries[i].netbios_name,
+ domains[i]->netbios_name);
+ entries[i].sid = &domains[i]->security_identifier;
+ entries[i].trust_direction = domains[i]->trust_direction;
+ entries[i].trust_type = domains[i]->trust_type;
+ entries[i].trust_attributes = domains[i]->trust_attributes;
+ }
+
+ if (*r->in.resume_handle >= count) {
+ *r->out.resume_handle = -1;
+ TALLOC_FREE(entries);
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ /* return the rest, limit by max_size. Note that we
+ use the w2k3 element size value of 60 */
+ r->out.domains->count = count - *r->in.resume_handle;
+ r->out.domains->count = MIN(r->out.domains->count,
+ (r->in.max_size/LSA_ENUM_TRUST_DOMAIN_EX_MULTIPLIER));
+
+ r->out.domains->domains = entries + *r->in.resume_handle;
+
+ if (r->out.domains->count < count - *r->in.resume_handle) {
+ *r->out.resume_handle = *r->in.resume_handle + r->out.domains->count;
+ return STATUS_MORE_ENTRIES;
+ }
+
+ /* according to MS-LSAD 3.1.4.7.8 output resume handle MUST
+ * always be larger than the previous input resume handle, in
+ * particular when hitting the last query it is vital to set the
+ * resume handle correctly to avoid infinite client loops, as
+ * seen e.g. with Windows XP SP3 when resume handle is 0 and
+ * status is NT_STATUS_OK - gd */
+
+ *r->out.resume_handle = (uint32_t)-1;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _lsa_QueryDomainInformationPolicy(struct pipes_struct *p,
+ struct lsa_QueryDomainInformationPolicy *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_SetDomainInformationPolicy(struct pipes_struct *p,
+ struct lsa_SetDomainInformationPolicy *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_TestCall(struct pipes_struct *p, struct lsa_TestCall *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRWRITE(struct pipes_struct *p, struct lsa_CREDRWRITE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRREAD(struct pipes_struct *p, struct lsa_CREDRREAD *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRENUMERATE(struct pipes_struct *p, struct lsa_CREDRENUMERATE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRWRITEDOMAINCREDENTIALS(struct pipes_struct *p,
+ struct lsa_CREDRWRITEDOMAINCREDENTIALS *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRREADDOMAINCREDENTIALS(struct pipes_struct *p,
+ struct lsa_CREDRREADDOMAINCREDENTIALS *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRDELETE(struct pipes_struct *p, struct lsa_CREDRDELETE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRGETTARGETINFO(struct pipes_struct *p,
+ struct lsa_CREDRGETTARGETINFO *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRPROFILELOADED(struct pipes_struct *p,
+ struct lsa_CREDRPROFILELOADED *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_CREDRGETSESSIONTYPES(struct pipes_struct *p,
+ struct lsa_CREDRGETSESSIONTYPES *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_LSARREGISTERAUDITEVENT(struct pipes_struct *p,
+ struct lsa_LSARREGISTERAUDITEVENT *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_LSARGENAUDITEVENT(struct pipes_struct *p,
+ struct lsa_LSARGENAUDITEVENT *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_LSARUNREGISTERAUDITEVENT(struct pipes_struct *p,
+ struct lsa_LSARUNREGISTERAUDITEVENT *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_lsaRQueryForestTrustInformation(struct pipes_struct *p,
+ struct lsa_lsaRQueryForestTrustInformation *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+#define DNS_CMP_MATCH 0
+#define DNS_CMP_FIRST_IS_CHILD 1
+#define DNS_CMP_SECOND_IS_CHILD 2
+#define DNS_CMP_NO_MATCH 3
+
+/* this function assumes names are well formed DNS names.
+ * it doesn't validate them */
+static int dns_cmp(const char *s1, size_t l1,
+ const char *s2, size_t l2)
+{
+ const char *p1, *p2;
+ size_t t1, t2;
+ int cret;
+
+ if (l1 == l2) {
+ if (strcasecmp_m(s1, s2) == 0) {
+ return DNS_CMP_MATCH;
+ }
+ return DNS_CMP_NO_MATCH;
+ }
+
+ if (l1 > l2) {
+ p1 = s1;
+ p2 = s2;
+ t1 = l1;
+ t2 = l2;
+ cret = DNS_CMP_FIRST_IS_CHILD;
+ } else {
+ p1 = s2;
+ p2 = s1;
+ t1 = l2;
+ t2 = l1;
+ cret = DNS_CMP_SECOND_IS_CHILD;
+ }
+
+ if (p1[t1 - t2 - 1] != '.') {
+ return DNS_CMP_NO_MATCH;
+ }
+
+ if (strcasecmp_m(&p1[t1 - t2], p2) == 0) {
+ return cret;
+ }
+
+ return DNS_CMP_NO_MATCH;
+}
+
+static NTSTATUS make_ft_info(TALLOC_CTX *mem_ctx,
+ struct lsa_ForestTrustInformation *lfti,
+ struct ForestTrustInfo *fti)
+{
+ struct lsa_ForestTrustRecord *lrec;
+ struct ForestTrustInfoRecord *rec;
+ struct lsa_StringLarge *tln;
+ struct lsa_ForestTrustDomainInfo *info;
+ uint32_t i;
+
+ fti->version = 1;
+ fti->count = lfti->count;
+ fti->records = talloc_array(mem_ctx,
+ struct ForestTrustInfoRecordArmor,
+ fti->count);
+ if (!fti->records) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ for (i = 0; i < fti->count; i++) {
+ lrec = lfti->entries[i];
+ rec = &fti->records[i].record;
+
+ rec->flags = lrec->flags;
+ rec->timestamp = lrec->time;
+ rec->type = (enum ForestTrustInfoRecordType)lrec->type;
+
+ switch (lrec->type) {
+ case LSA_FOREST_TRUST_TOP_LEVEL_NAME:
+ case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
+ tln = &lrec->forest_trust_data.top_level_name;
+ rec->data.name.string =
+ talloc_strdup(mem_ctx, tln->string);
+ if (!rec->data.name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ rec->data.name.size = strlen(rec->data.name.string);
+ break;
+ case LSA_FOREST_TRUST_DOMAIN_INFO:
+ info = &lrec->forest_trust_data.domain_info;
+ rec->data.info.sid = *info->domain_sid;
+ rec->data.info.dns_name.string =
+ talloc_strdup(mem_ctx,
+ info->dns_domain_name.string);
+ if (!rec->data.info.dns_name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ rec->data.info.dns_name.size =
+ strlen(rec->data.info.dns_name.string);
+ rec->data.info.netbios_name.string =
+ talloc_strdup(mem_ctx,
+ info->netbios_domain_name.string);
+ if (!rec->data.info.netbios_name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ rec->data.info.netbios_name.size =
+ strlen(rec->data.info.netbios_name.string);
+ break;
+ default:
+ return NT_STATUS_INVALID_DOMAIN_STATE;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS add_collision(struct lsa_ForestTrustCollisionInfo *c_info,
+ uint32_t index, uint32_t collision_type,
+ uint32_t conflict_type, const char *tdo_name);
+
+static NTSTATUS check_ft_info(TALLOC_CTX *mem_ctx,
+ const char *tdo_name,
+ struct ForestTrustInfo *tdo_fti,
+ struct ForestTrustInfo *new_fti,
+ struct lsa_ForestTrustCollisionInfo *c_info)
+{
+ struct ForestTrustInfoRecord *nrec;
+ struct ForestTrustInfoRecord *trec;
+ const char *dns_name;
+ const char *nb_name = NULL;
+ struct dom_sid *sid = NULL;
+ const char *tname = NULL;
+ size_t dns_len = 0;
+ size_t tlen = 0;
+ uint32_t new_fti_idx;
+ uint32_t i;
+ /* use always TDO type, until we understand when Xref can be used */
+ uint32_t collision_type = LSA_FOREST_TRUST_COLLISION_TDO;
+ bool tln_conflict;
+ bool sid_conflict;
+ bool nb_conflict;
+ bool exclusion;
+ bool ex_rule = false;
+ int ret;
+
+ for (new_fti_idx = 0; new_fti_idx < new_fti->count; new_fti_idx++) {
+
+ nrec = &new_fti->records[new_fti_idx].record;
+ dns_name = NULL;
+ tln_conflict = false;
+ sid_conflict = false;
+ nb_conflict = false;
+ exclusion = false;
+
+ switch (nrec->type) {
+ case LSA_FOREST_TRUST_TOP_LEVEL_NAME_EX:
+ /* exclusions do not conflict by definition */
+ break;
+
+ case FOREST_TRUST_TOP_LEVEL_NAME:
+ dns_name = nrec->data.name.string;
+ dns_len = nrec->data.name.size;
+ break;
+
+ case LSA_FOREST_TRUST_DOMAIN_INFO:
+ dns_name = nrec->data.info.dns_name.string;
+ dns_len = nrec->data.info.dns_name.size;
+ nb_name = nrec->data.info.netbios_name.string;
+ sid = &nrec->data.info.sid;
+ break;
+ }
+
+ if (!dns_name) continue;
+
+ /* check if this is already taken and not excluded */
+ for (i = 0; i < tdo_fti->count; i++) {
+ trec = &tdo_fti->records[i].record;
+
+ switch (trec->type) {
+ case FOREST_TRUST_TOP_LEVEL_NAME:
+ ex_rule = false;
+ tname = trec->data.name.string;
+ tlen = trec->data.name.size;
+ break;
+ case FOREST_TRUST_TOP_LEVEL_NAME_EX:
+ ex_rule = true;
+ tname = trec->data.name.string;
+ tlen = trec->data.name.size;
+ break;
+ case FOREST_TRUST_DOMAIN_INFO:
+ ex_rule = false;
+ tname = trec->data.info.dns_name.string;
+ tlen = trec->data.info.dns_name.size;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ret = dns_cmp(dns_name, dns_len, tname, tlen);
+ switch (ret) {
+ case DNS_CMP_MATCH:
+ /* if it matches exclusion,
+ * it doesn't conflict */
+ if (ex_rule) {
+ exclusion = true;
+ break;
+ }
+
+ FALL_THROUGH;
+ case DNS_CMP_FIRST_IS_CHILD:
+ case DNS_CMP_SECOND_IS_CHILD:
+ tln_conflict = true;
+
+ FALL_THROUGH;
+ default:
+ break;
+ }
+
+ /* explicit exclusion, no dns name conflict here */
+ if (exclusion) {
+ tln_conflict = false;
+ }
+
+ if (trec->type != FOREST_TRUST_DOMAIN_INFO) {
+ continue;
+ }
+
+ /* also test for domain info */
+ if (!(trec->flags & LSA_SID_DISABLED_ADMIN) &&
+ dom_sid_compare(&trec->data.info.sid, sid) == 0) {
+ sid_conflict = true;
+ }
+ if (!(trec->flags & LSA_NB_DISABLED_ADMIN) &&
+ strcasecmp_m(trec->data.info.netbios_name.string,
+ nb_name) == 0) {
+ nb_conflict = true;
+ }
+ }
+
+ if (tln_conflict) {
+ (void)add_collision(c_info, new_fti_idx,
+ collision_type,
+ LSA_TLN_DISABLED_CONFLICT,
+ tdo_name);
+ }
+ if (sid_conflict) {
+ (void)add_collision(c_info, new_fti_idx,
+ collision_type,
+ LSA_SID_DISABLED_CONFLICT,
+ tdo_name);
+ }
+ if (nb_conflict) {
+ (void)add_collision(c_info, new_fti_idx,
+ collision_type,
+ LSA_NB_DISABLED_CONFLICT,
+ tdo_name);
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS add_collision(struct lsa_ForestTrustCollisionInfo *c_info,
+ uint32_t idx, uint32_t collision_type,
+ uint32_t conflict_type, const char *tdo_name)
+{
+ struct lsa_ForestTrustCollisionRecord **es;
+ uint32_t i = c_info->count;
+
+ es = talloc_realloc(c_info, c_info->entries,
+ struct lsa_ForestTrustCollisionRecord *, i + 1);
+ if (!es) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c_info->entries = es;
+ c_info->count = i + 1;
+
+ es[i] = talloc(es, struct lsa_ForestTrustCollisionRecord);
+ if (!es[i]) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ es[i]->index = idx;
+ es[i]->type = collision_type;
+ es[i]->flags = conflict_type;
+ es[i]->name.string = talloc_strdup(es[i], tdo_name);
+ if (!es[i]->name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ es[i]->name.size = strlen(es[i]->name.string);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS get_ft_info(TALLOC_CTX *mem_ctx,
+ struct pdb_trusted_domain *td,
+ struct ForestTrustInfo *info)
+{
+ enum ndr_err_code ndr_err;
+
+ if (td->trust_forest_trust_info.length == 0 ||
+ td->trust_forest_trust_info.data == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ ndr_err = ndr_pull_struct_blob_all(&td->trust_forest_trust_info, mem_ctx,
+ info,
+ (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INVALID_DOMAIN_STATE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS own_ft_info(struct pdb_domain_info *dom_info,
+ struct ForestTrustInfo *fti)
+{
+ struct ForestTrustDataDomainInfo *info;
+ struct ForestTrustInfoRecord *rec;
+
+ fti->version = 1;
+ fti->count = 2;
+ fti->records = talloc_array(fti,
+ struct ForestTrustInfoRecordArmor, 2);
+ if (!fti->records) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* TLN info */
+ rec = &fti->records[0].record;
+
+ rec->flags = 0;
+ rec->timestamp = 0;
+ rec->type = FOREST_TRUST_TOP_LEVEL_NAME;
+
+ rec->data.name.string = talloc_strdup(fti, dom_info->dns_forest);
+ if (!rec->data.name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ rec->data.name.size = strlen(rec->data.name.string);
+
+ /* DOMAIN info */
+ rec = &fti->records[1].record;
+
+ rec->flags = 0;
+ rec->timestamp = 0;
+ rec->type = FOREST_TRUST_DOMAIN_INFO;
+
+ info = &rec->data.info;
+
+ info->sid = dom_info->sid;
+ info->dns_name.string = talloc_strdup(fti, dom_info->dns_domain);
+ if (!info->dns_name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ info->dns_name.size = strlen(info->dns_name.string);
+ info->netbios_name.string = talloc_strdup(fti, dom_info->name);
+ if (!info->netbios_name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ info->netbios_name.size = strlen(info->netbios_name.string);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _lsa_lsaRSetForestTrustInformation(struct pipes_struct *p,
+ struct lsa_lsaRSetForestTrustInformation *r)
+{
+ NTSTATUS status;
+ int i;
+ int j;
+ struct lsa_info *handle;
+ uint32_t num_domains;
+ struct pdb_trusted_domain **domains;
+ struct ForestTrustInfo *nfti;
+ struct ForestTrustInfo *fti;
+ struct lsa_ForestTrustCollisionInfo *c_info;
+ struct pdb_domain_info *dom_info;
+ enum ndr_err_code ndr_err;
+
+ if (!IS_DC) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ handle = find_policy_by_hnd(p,
+ r->in.handle,
+ LSA_HANDLE_TRUST_TYPE,
+ struct lsa_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!(handle->access & LSA_TRUSTED_SET_AUTH)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pdb_enum_trusted_domains(p->mem_ctx, &num_domains, &domains);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (num_domains == 0) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ for (i = 0; i < num_domains; i++) {
+ if (domains[i]->domain_name == NULL) {
+ return NT_STATUS_INVALID_DOMAIN_STATE;
+ }
+ if (strcasecmp_m(domains[i]->domain_name,
+ r->in.trusted_domain_name->string) == 0) {
+ break;
+ }
+ }
+ if (i >= num_domains) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ if (!(domains[i]->trust_attributes &
+ LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (r->in.highest_record_type >= LSA_FOREST_TRUST_RECORD_TYPE_LAST) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* The following section until COPY_END is a copy from
+ * source4/rpmc_server/lsa/scesrc_lsa.c */
+ nfti = talloc(p->mem_ctx, struct ForestTrustInfo);
+ if (!nfti) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = make_ft_info(nfti, r->in.forest_trust_info, nfti);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ c_info = talloc_zero(r->out.collision_info,
+ struct lsa_ForestTrustCollisionInfo);
+ if (!c_info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* first check own info, then other domains */
+ fti = talloc(p->mem_ctx, struct ForestTrustInfo);
+ if (!fti) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dom_info = pdb_get_domain_info(p->mem_ctx);
+
+ status = own_ft_info(dom_info, fti);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = check_ft_info(c_info, dom_info->dns_domain, fti, nfti, c_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (j = 0; j < num_domains; j++) {
+ fti = talloc(p->mem_ctx, struct ForestTrustInfo);
+ if (!fti) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = get_ft_info(p->mem_ctx, domains[j], fti);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ continue;
+ }
+ return status;
+ }
+
+ if (domains[j]->domain_name == NULL) {
+ return NT_STATUS_INVALID_DOMAIN_STATE;
+ }
+
+ status = check_ft_info(c_info, domains[j]->domain_name,
+ fti, nfti, c_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (c_info->count != 0) {
+ *r->out.collision_info = c_info;
+ }
+
+ if (r->in.check_only != 0) {
+ return NT_STATUS_OK;
+ }
+
+ /* COPY_END */
+
+ ndr_err = ndr_push_struct_blob(&domains[i]->trust_forest_trust_info,
+ p->mem_ctx, nfti,
+ (ndr_push_flags_fn_t)ndr_push_ForestTrustInfo);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = pdb_set_trusted_domain(domains[i]->domain_name, domains[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _lsa_CREDRRENAME(struct pipes_struct *p,
+ struct lsa_CREDRRENAME *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_LSAROPENPOLICYSCE(struct pipes_struct *p,
+ struct lsa_LSAROPENPOLICYSCE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_LSARADTREGISTERSECURITYEVENTSOURCE(struct pipes_struct *p,
+ struct lsa_LSARADTREGISTERSECURITYEVENTSOURCE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_LSARADTUNREGISTERSECURITYEVENTSOURCE(struct pipes_struct *p,
+ struct lsa_LSARADTUNREGISTERSECURITYEVENTSOURCE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_LSARADTREPORTSECURITYEVENT(struct pipes_struct *p,
+ struct lsa_LSARADTREPORTSECURITYEVENT *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+void _lsa_Opnum82NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum82NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum83NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum83NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum84NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum84NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum85NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum85NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum86NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum86NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum87NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum87NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum88NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum88NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum89NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum89NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum90NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum90NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum91NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum91NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum92NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum92NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum93NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum93NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum94NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum94NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum95NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum95NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum96NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum96NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum97NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum97NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum98NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum98NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum99NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum99NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum100NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum100NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum101NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum101NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum102NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum102NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum103NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum103NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum104NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum104NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum105NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum105NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum106NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum106NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum107NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum107NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum108NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum108NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum109NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum109NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum110NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum110NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum111NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum111NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum112NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum112NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum113NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum113NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum114NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum114NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum115NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum115NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum116NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum116NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum117NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum117NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum118NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum118NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum119NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum119NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum120NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum120NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum121NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum121NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum122NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum122NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum123NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum123NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum124NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum124NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum125NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum125NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum126NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum126NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum127NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum127NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _lsa_Opnum128NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum128NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+/***************************************************************************
+ _lsa_CreateTrustedDomainEx3
+ ***************************************************************************/
+
+NTSTATUS _lsa_CreateTrustedDomainEx3(struct pipes_struct *p,
+ struct lsa_CreateTrustedDomainEx3 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/***************************************************************************
+ _lsa_OpenPolicy3
+ ***************************************************************************/
+
+NTSTATUS _lsa_OpenPolicy3(struct pipes_struct *p,
+ struct lsa_OpenPolicy3 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *psd = NULL;
+ size_t sd_size;
+ uint32_t des_access = r->in.access_mask;
+ uint32_t acc_granted;
+ NTSTATUS status;
+
+ if (p->transport != NCACN_NP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ ZERO_STRUCTP(r->out.handle);
+
+ /*
+ * The attributes have no effect and MUST be ignored, except the
+ * root_dir which MUST be NULL.
+ */
+ if (r->in.attr != NULL && r->in.attr->root_dir != NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (r->in.in_version) {
+ case 1:
+ *r->out.out_version = 1;
+
+ r->out.out_revision_info->info1.revision = 1;
+ /* TODO: Enable as soon as we support it */
+#if 0
+ r->out.out_revision_info->info1.supported_features =
+ LSA_FEATURE_TDO_AUTH_INFO_AES_CIPHER;
+#endif
+
+ break;
+ default:
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* Work out max allowed. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ /* map the generic bits to the lsa policy ones */
+ se_map_generic(&des_access, &lsa_policy_mapping);
+
+ /* get the generic lsa policy SD until we store it */
+ status = make_lsa_object_sd(p->mem_ctx,
+ &psd,
+ &sd_size,
+ &lsa_policy_mapping,
+ NULL,
+ 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = access_check_object(psd,
+ session_info->security_token,
+ SEC_PRIV_INVALID,
+ SEC_PRIV_INVALID,
+ 0,
+ des_access,
+ &acc_granted,
+ "_lsa_OpenPolicy2");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = create_lsa_policy_handle(p->mem_ctx,
+ p,
+ LSA_HANDLE_POLICY_TYPE,
+ acc_granted,
+ get_global_sam_sid(),
+ NULL,
+ psd,
+ r->out.handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+void _lsa_Opnum131NotUsedOnWire(struct pipes_struct *p,
+ struct lsa_Opnum131NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+NTSTATUS _lsa_lsaRQueryForestTrustInformation2(struct pipes_struct *p,
+ struct lsa_lsaRQueryForestTrustInformation2 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS _lsa_lsaRSetForestTrustInformation2(struct pipes_struct *p,
+ struct lsa_lsaRSetForestTrustInformation2 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+#include "librpc/rpc/dcesrv_core.h"
+
+#define DCESRV_INTERFACE_LSARPC_BIND(context, iface) \
+ dcesrv_interface_lsarpc_bind(context, iface)
+
+static NTSTATUS dcesrv_interface_lsarpc_bind(
+ struct dcesrv_connection_context *context,
+ const struct dcesrv_interface *iface)
+{
+ return dcesrv_interface_bind_reject_connect(context, iface);
+}
+
+static NTSTATUS lsarpc__op_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+static const struct dcesrv_interface dcesrv_lsarpc_interface;
+
+#define NCACN_NP_PIPE_NETLOGON "ncacn_np:[\\pipe\\netlogon]"
+#define NCACN_NP_PIPE_LSASS "ncacn_np:[\\pipe\\lsass]"
+
+#define DCESRV_INTERFACE_LSARPC_NCACN_NP_SECONDARY_ENDPOINT \
+ NCACN_NP_PIPE_LSASS
+
+#define DCESRV_INTERFACE_LSARPC_INIT_SERVER \
+ dcesrv_interface_lsarpc_init_server
+
+static NTSTATUS dcesrv_interface_lsarpc_init_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ NTSTATUS ret = dcesrv_interface_register(dce_ctx,
+ NCACN_NP_PIPE_NETLOGON,
+ NCACN_NP_PIPE_LSASS,
+ &dcesrv_lsarpc_interface,
+ NULL);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DBG_ERR("Failed to register endpoint "
+ "'\\pipe\\netlogon'\n");
+ return ret;
+ }
+
+ return lsarpc__op_init_server(dce_ctx, ep_server);
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_lsa_scompat.c"
diff --git a/source3/rpc_server/mdssvc/README b/source3/rpc_server/mdssvc/README
new file mode 100644
index 0000000..7dff83e
--- /dev/null
+++ b/source3/rpc_server/mdssvc/README
@@ -0,0 +1,14 @@
+Introduction:
+=============
+This directory contains source code for the metadata search service
+aka Spotlight.
+
+Bison and flex:
+===============
+Not yet integrated into the waf buildsystem, run these by hand:
+
+$ bison -d -o sparql_parser.c sparql_parser.y
+$ flex -o sparql_lexer.c sparql_lexer.l
+
+or use the bundled Makefile.
+
diff --git a/source3/rpc_server/mdssvc/dalloc.c b/source3/rpc_server/mdssvc/dalloc.c
new file mode 100644
index 0000000..44db9f8
--- /dev/null
+++ b/source3/rpc_server/mdssvc/dalloc.c
@@ -0,0 +1,404 @@
+/*
+ Copyright (c) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include <talloc.h>
+#include "dalloc.h"
+#include "marshalling.h"
+#include "lib/util/charset/charset.h"
+#include "lib/util/talloc_stack.h"
+#include "system/time.h"
+
+/**
+ * Dynamic Datastore
+ **/
+struct dalloc_ctx {
+ void **dd_talloc_array;
+};
+
+void *_dalloc_new(TALLOC_CTX *mem_ctx, const char *type)
+{
+ void *p;
+
+ p = talloc_zero(mem_ctx, DALLOC_CTX);
+ if (p == NULL) {
+ return NULL;
+ }
+ talloc_set_name_const(p, type);
+
+ return p;
+}
+
+int _dalloc_add_talloc_chunk(DALLOC_CTX *dd, void *obj, const char *type, size_t size)
+{
+ size_t array_len = talloc_array_length(dd->dd_talloc_array);
+
+ dd->dd_talloc_array = talloc_realloc(dd,
+ dd->dd_talloc_array,
+ void *,
+ array_len + 1);
+ if (dd->dd_talloc_array == NULL) {
+ return -1;
+ }
+
+ if (size != 0) {
+ void *p;
+
+ p = talloc_named_const(dd->dd_talloc_array, size, type);
+ if (p == NULL) {
+ return -1;
+ }
+ memcpy(p, obj, size);
+ obj = p;
+ } else {
+ _talloc_get_type_abort(obj, type, __location__);
+ }
+
+ dd->dd_talloc_array[array_len] = obj;
+
+ return 0;
+}
+
+/* Get number of elements, returns 0 if the structure is empty or not initialized */
+size_t dalloc_size(const DALLOC_CTX *d)
+{
+ if (d == NULL) {
+ return 0;
+ }
+ return talloc_array_length(d->dd_talloc_array);
+}
+
+/* Return element at position */
+void *dalloc_get_object(const DALLOC_CTX *d, int i)
+{
+ size_t size = dalloc_size(d);
+
+ if (i >= size) {
+ return NULL;
+ }
+
+ return d->dd_talloc_array[i];
+}
+
+/* Return typename of element at position */
+const char *dalloc_get_name(const DALLOC_CTX *d, int i)
+{
+ void *o = dalloc_get_object(d, i);
+
+ if (o == NULL) {
+ return NULL;
+ }
+
+ return talloc_get_name(o);
+}
+
+/*
+ * Get pointer to value from a DALLOC object
+ *
+ * Returns pointer to object from a DALLOC object. Nested object integration
+ * is supported by using the type string "DALLOC_CTX". Any other type string
+ * designates the requested objects type.
+ */
+void *dalloc_get(const DALLOC_CTX *d, ...)
+{
+ int result = 0;
+ void *p = NULL;
+ va_list args;
+ const char *type;
+ int elem;
+
+ va_start(args, d);
+ type = va_arg(args, const char *);
+
+ while (strcmp(type, "DALLOC_CTX") == 0) {
+ elem = va_arg(args, int);
+ if (elem >= talloc_array_length(d->dd_talloc_array)) {
+ result = -1;
+ goto done;
+ }
+ d = d->dd_talloc_array[elem];
+ type = va_arg(args, const char *);
+ }
+
+ elem = va_arg(args, int);
+ if (elem >= talloc_array_length(d->dd_talloc_array)) {
+ result = -1;
+ goto done;
+ }
+
+ p = talloc_check_name(d->dd_talloc_array[elem], type);
+ if (p == NULL) {
+ result = -1;
+ goto done;
+ }
+
+done:
+ va_end(args);
+ if (result != 0) {
+ p = NULL;
+ }
+ return p;
+}
+
+void *dalloc_value_for_key(const DALLOC_CTX *d, ...)
+{
+ int result = 0;
+ void *p = NULL;
+ va_list args;
+ const char *type = NULL;
+ int elem;
+ size_t array_len;
+
+ va_start(args, d);
+ type = va_arg(args, const char *);
+
+ while (strcmp(type, "DALLOC_CTX") == 0) {
+ array_len = talloc_array_length(d->dd_talloc_array);
+ elem = va_arg(args, int);
+ if (elem >= array_len) {
+ result = -1;
+ goto done;
+ }
+ d = d->dd_talloc_array[elem];
+ type = va_arg(args, const char *);
+ }
+
+ array_len = talloc_array_length(d->dd_talloc_array);
+
+ for (elem = 0; elem + 1 < array_len; elem += 2) {
+ if (strcmp(talloc_get_name(d->dd_talloc_array[elem]), "char *") != 0) {
+ result = -1;
+ goto done;
+ }
+ if (strcmp((char *)d->dd_talloc_array[elem],type) == 0) {
+ p = d->dd_talloc_array[elem + 1];
+ break;
+ }
+ }
+ if (p == NULL) {
+ goto done;
+ }
+
+ type = va_arg(args, const char *);
+ if (strcmp(talloc_get_name(p), type) != 0) {
+ p = NULL;
+ }
+
+done:
+ va_end(args);
+ if (result != 0) {
+ p = NULL;
+ }
+ return p;
+}
+
+static char *dalloc_strdup(TALLOC_CTX *mem_ctx, const char *string)
+{
+ char *p;
+
+ p = talloc_strdup(mem_ctx, string);
+ if (p == NULL) {
+ return NULL;
+ }
+ talloc_set_name_const(p, "char *");
+ return p;
+}
+
+int dalloc_stradd(DALLOC_CTX *d, const char *string)
+{
+ int result;
+ char *p;
+
+ p = dalloc_strdup(d, string);
+ if (p == NULL) {
+ return -1;
+ }
+
+ result = dalloc_add(d, p, char *);
+ if (result != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static char *tab_level(TALLOC_CTX *mem_ctx, int level)
+{
+ int i;
+ char *string = talloc_array(mem_ctx, char, level + 1);
+
+ for (i = 0; i < level; i++) {
+ string[i] = '\t';
+ }
+
+ string[i] = '\0';
+ return string;
+}
+
+char *dalloc_dump(DALLOC_CTX *dd, int nestinglevel)
+{
+ const char *type;
+ int n, result;
+ uint64_t i;
+ sl_bool_t bl;
+ sl_time_t t;
+ struct tm *tm;
+ char datestring[256];
+ sl_cnids_t cnids;
+ char *logstring, *nested_logstring;
+ char *tab_string1, *tab_string2;
+ void *p;
+ bool ok;
+ char *utf8string;
+ size_t utf8len;
+
+ tab_string1 = tab_level(dd, nestinglevel);
+ if (tab_string1 == NULL) {
+ return NULL;
+ }
+ tab_string2 = tab_level(dd, nestinglevel + 1);
+ if (tab_string2 == NULL) {
+ return NULL;
+ }
+
+ logstring = talloc_asprintf(dd,
+ "%s%s(#%zu): {\n",
+ tab_string1,
+ talloc_get_name(dd),
+ dalloc_size(dd));
+ if (logstring == NULL) {
+ return NULL;
+ }
+
+ for (n = 0; n < dalloc_size(dd); n++) {
+ type = dalloc_get_name(dd, n);
+ if (type == NULL) {
+ return NULL;
+ }
+ p = dalloc_get_object(dd, n);
+ if (p == NULL) {
+ return NULL;
+ }
+ if (strcmp(type, "DALLOC_CTX") == 0
+ || strcmp(type, "sl_array_t") == 0
+ || strcmp(type, "sl_filemeta_t") == 0
+ || strcmp(type, "sl_dict_t") == 0) {
+ nested_logstring = dalloc_dump(p, nestinglevel + 1);
+ if (nested_logstring == NULL) {
+ return NULL;
+ }
+ logstring = talloc_strdup_append(logstring,
+ nested_logstring);
+ } else if (strcmp(type, "uint64_t") == 0) {
+ memcpy(&i, p, sizeof(uint64_t));
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%suint64_t: 0x%04jx\n",
+ tab_string2, (uintmax_t)i);
+ } else if (strcmp(type, "char *") == 0) {
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%sstring: %s\n",
+ tab_string2,
+ (char *)p);
+ } else if (strcmp(type, "smb_ucs2_t *") == 0) {
+ ok = convert_string_talloc(talloc_tos(),
+ CH_UTF16LE,
+ CH_UTF8,
+ p,
+ talloc_get_size(p),
+ &utf8string,
+ &utf8len);
+ if (!ok) {
+ return NULL;
+ }
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%sUTF16-string: %s\n",
+ tab_string2,
+ utf8string);
+ TALLOC_FREE(utf8string);
+ } else if (strcmp(type, "sl_bool_t") == 0) {
+ memcpy(&bl, p, sizeof(sl_bool_t));
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%sbool: %s\n",
+ tab_string2,
+ bl ? "true" : "false");
+ } else if (strcmp(type, "sl_nil_t") == 0) {
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%snil\n",
+ tab_string2);
+ } else if (strcmp(type, "sl_time_t") == 0) {
+ memcpy(&t, p, sizeof(sl_time_t));
+ tm = localtime(&t.tv_sec);
+ if (tm == NULL) {
+ return NULL;
+ }
+ result = strftime(datestring,
+ sizeof(datestring),
+ "%Y-%m-%d %H:%M:%S", tm);
+ if (result == 0) {
+ return NULL;
+ }
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%ssl_time_t: %s.%06lu\n",
+ tab_string2,
+ datestring,
+ (unsigned long)t.tv_usec);
+ } else if (strcmp(type, "sl_cnids_t") == 0) {
+ memcpy(&cnids, p, sizeof(sl_cnids_t));
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%sCNIDs: unkn1: 0x%" PRIx16 ", unkn2: 0x%" PRIx32 "\n",
+ tab_string2,
+ cnids.ca_unkn1,
+ cnids.ca_context);
+ if (logstring == NULL) {
+ return NULL;
+ }
+ if (cnids.ca_cnids) {
+ nested_logstring = dalloc_dump(
+ cnids.ca_cnids,
+ nestinglevel + 2);
+ if (!nested_logstring) {
+ return NULL;
+ }
+ logstring = talloc_strdup_append(logstring,
+ nested_logstring);
+ }
+ } else {
+ logstring = talloc_asprintf_append(
+ logstring,
+ "%stype: %s\n",
+ tab_string2,
+ type);
+ }
+ if (logstring == NULL) {
+ return NULL;
+ }
+ }
+ logstring = talloc_asprintf_append(logstring,
+ "%s}\n",
+ tab_string1);
+ if (logstring == NULL) {
+ return NULL;
+ }
+ return logstring;
+}
diff --git a/source3/rpc_server/mdssvc/dalloc.h b/source3/rpc_server/mdssvc/dalloc.h
new file mode 100644
index 0000000..69650b8
--- /dev/null
+++ b/source3/rpc_server/mdssvc/dalloc.h
@@ -0,0 +1,165 @@
+/*
+ Copyright (c) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ Typesafe, dynamic object store based on talloc
+
+ Usage
+ =====
+
+ Define some types:
+
+ A key/value store aka dictionary that supports retrieving elements
+ by key:
+
+ typedef dict_t DALLOC_CTX;
+
+ An ordered set that can store different objects which can be
+ retrieved by number:
+
+ typedef set_t DALLOC_CTX;
+
+ Create an dalloc object and add elementes of different type:
+
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ DALLOC_CTX *d = dalloc_new(mem_ctx);
+
+ Store an int value in the object:
+
+ uint64_t i = 1;
+ dalloc_add_copy(d, &i, uint64_t);
+
+ Store a string:
+
+ dalloc_stradd(d, "hello world");
+
+ Add a nested object:
+
+ DALLOC_CTX *nested = dalloc_new(d);
+ dalloc_add(d, nested, DALLOC_CTX);
+
+ Add an int value to the nested object, this can be fetched:
+
+ i = 2;
+ dalloc_add_copy(nested, &i, uint64_t);
+
+ Add a nested set:
+
+ set_t *set = dalloc_zero(nested, set_t);
+ dalloc_add(nested, set, set_t);
+
+ Add an int value to the set:
+
+ i = 3;
+ dalloc_add_copy(set, &i, uint64_t);
+
+ Add a dictionary (key/value store):
+
+ dict_t *dict = dalloc_zero(nested, dict_t);
+ dalloc_add(nested, dict, dict_t);
+
+ Store a string as key in the dict:
+
+ dalloc_stradd(dict, "key");
+
+ Add a value for the key:
+
+ i = 4;
+ dalloc_add_copy(dict, &i, uint64_t);
+
+ Fetching value references
+ =========================
+
+ You can fetch anything that is not a DALLOC_CTXs, because passing
+ "DALLOC_CTXs" as type to the functions dalloc_get() and
+ dalloc_value_for_key() tells the function to step into that object
+ and expect more arguments that specify which element to fetch.
+
+ Get reference to an objects element by position:
+
+ uint64_t *p = dalloc_get(d, "uint64_t", 0);
+
+ p now points to the first int with a value of 1.
+
+ Get reference to the "hello world" string:
+
+ str = dalloc_get(d, "char *", 1);
+
+ You can't fetch a DALLOC_CTX itself:
+
+ nested = dalloc_get(d, "DALLOC_CTX", 2);
+
+ But you can fetch elements from the nested DALLOC_CTX:
+
+ p = dalloc_get(d, "DALLOC_CTX", 2, "uint64_t", 0);
+
+ p now points to the value 2.
+
+ You can fetch types that are typedefd DALLOC_CTXs:
+
+ set = dalloc_get(d, "DALLOC_CTX", 2, "set_t", 1);
+
+ Fetch int from set, must use DALLOC_CTX as type for the set:
+
+ p = dalloc_get(d, "DALLOC_CTX", 2, "DALLOC_CTX", 1, "uint64_t", 0);
+
+ p points to 3.
+
+ Fetch value by key from dictionary:
+
+ p = dalloc_value_for_key(d, "DALLOC_CTX", 2, "DALLOC_CTX", 2, "key");
+
+ p now points to 4.
+*/
+
+#ifndef DALLOC_H
+#define DALLOC_H
+
+#include <talloc.h>
+
+struct dalloc_ctx;
+typedef struct dalloc_ctx DALLOC_CTX;
+
+#define dalloc_new(mem_ctx) (DALLOC_CTX *)_dalloc_new((mem_ctx), "DALLOC_CTX")
+#define dalloc_zero(mem_ctx, type) (type *)_dalloc_new((mem_ctx), #type)
+
+/**
+ * talloc a chunk for obj of required size, copy the obj into the
+ * chunk and add the chunk to the dalloc ctx
+ **/
+#define dalloc_add_copy(d, obj, type) _dalloc_add_talloc_chunk((d), (obj), #type, sizeof(type))
+
+/**
+ * Add a pointer to a talloced object to the dalloc ctx. The object
+ * must be a talloc child of the dalloc ctx.
+ **/
+#define dalloc_add(d, obj, type) _dalloc_add_talloc_chunk((d), (obj), #type, 0)
+
+
+extern void *dalloc_get(const DALLOC_CTX *d, ...);
+extern void *dalloc_value_for_key(const DALLOC_CTX *d, ...);
+extern size_t dalloc_size(const DALLOC_CTX *d);
+extern void *dalloc_get_object(const DALLOC_CTX *d, int i);
+extern const char *dalloc_get_name(const DALLOC_CTX *d, int i);
+extern int dalloc_stradd(DALLOC_CTX *d, const char *string);
+
+extern void *_dalloc_new(TALLOC_CTX *mem_ctx, const char *type);
+extern int _dalloc_add_talloc_chunk(DALLOC_CTX *d, void *obj, const char *type, size_t size);
+
+extern char *dalloc_dump(DALLOC_CTX *dd, int nestinglevel);
+
+#endif /* DALLOC_H */
diff --git a/source3/rpc_server/mdssvc/elasticsearch_mappings.json b/source3/rpc_server/mdssvc/elasticsearch_mappings.json
new file mode 100644
index 0000000..9f68a64
--- /dev/null
+++ b/source3/rpc_server/mdssvc/elasticsearch_mappings.json
@@ -0,0 +1,142 @@
+{
+ "attribute_mappings": {
+ "*": {
+ "type": "fts",
+ "attribute": ""
+ },
+ "kMDItemTextContent": {
+ "type": "str",
+ "attribute": "content"
+ },
+ "_kMDItemGroupId": {
+ "type": "type",
+ "attribute": "file.content_type"
+ },
+ "kMDItemContentType": {
+ "type": "type",
+ "attribute": "file.content_type"
+ },
+ "kMDItemContentTypeTree": {
+ "type": "type",
+ "attribute": "file.content_type"
+ },
+ "kMDItemFSContentChangeDate": {
+ "type": "date",
+ "attribute": "file.last_modified"
+ },
+ "kMDItemFSCreationDate": {
+ "type": "date",
+ "attribute": "file.created"
+ },
+ "kMDItemFSName": {
+ "type": "str",
+ "attribute": "file.filename"
+ },
+ "kMDItemFSOwnerGroupID": {
+ "type": "str",
+ "attribute": "attributes.owner"
+ },
+ "kMDItemFSOwnerUserID": {
+ "type": "str",
+ "attribute": "attributes.group"
+ },
+ "kMDItemFSSize": {
+ "type": "num",
+ "attribute": "file.filesize"
+ },
+ "kMDItemPath": {
+ "type": "str",
+ "attribute": "path.real"
+ },
+ "kMDItemAttributeChangeDate": {
+ "type": "date",
+ "attribute": "file.last_modified"
+ },
+ "kMDItemAuthors": {
+ "type": "str",
+ "attribute": "meta.author"
+ },
+ "kMDItemContentCreationDate": {
+ "type": "date",
+ "attribute": "file.created"
+ },
+ "kMDItemContentModificationDate": {
+ "type": "date",
+ "attribute": "file.last_modified"
+ },
+ "kMDItemCreator": {
+ "type": "str",
+ "attribute": "meta.raw.creator"
+ },
+ "kMDItemDescription": {
+ "type": "str",
+ "attribute": "meta.raw.description"
+ },
+ "kMDItemDisplayName": {
+ "type": "str",
+ "attribute": "file.filename"
+ },
+ "kMDItemDurationSeconds": {
+ "type": "num",
+ "attribute": "meta.raw.xmpDM:duration"
+ },
+ "kMDItemNumberOfPages": {
+ "type": "num",
+ "attribute": "meta.raw.xmpTPg:NPages"
+ },
+ "kMDItemTitle": {
+ "type": "str",
+ "attribute": "meta.title"
+ },
+ "kMDItemAlbum": {
+ "type": "str",
+ "attribute": "meta.raw.xmpDM:album"
+ },
+ "kMDItemBitsPerSample": {
+ "type": "num",
+ "attribute": "meta.raw.tiff:BitsPerSample"
+ },
+ "kMDItemPixelHeight": {
+ "type": "num",
+ "attribute": "meta.raw.Image Height"
+ },
+ "kMDItemPixelWidth": {
+ "type": "num",
+ "attribute": "meta.raw.Image Width"
+ },
+ "kMDItemResolutionHeightDPI": {
+ "type": "num",
+ "attribute": "meta.raw.Y Resolution"
+ },
+ "kMDItemResolutionWidthDPI": {
+ "type": "num",
+ "attribute": "meta.raw.X Resolution"
+ }
+ },
+ "mime_mappings": {
+ "1": "message/rfc822",
+ "2": "text/x-vcard",
+ "6": "text/x-vcard",
+ "7": "video/*",
+ "8": "application/octet-stream",
+ "9": "text/directory",
+ "10": "audio/*",
+ "11": "application/pdf",
+ "12": "application/vnd.oasis.opendocument.presentation",
+ "13": "image/*",
+ "public.content": "message/rfc822 application/pdf application/vnd.oasis.opendocument.presentation image/* text/*",
+ "public.jpeg": "image/jpeg",
+ "public.tiff": "image/tiff",
+ "com.compuserve.gif": "image/gif",
+ "public.png": "image/png",
+ "com.microsoft.bmp": "image/bmp",
+ "public.mp3": "audio/mpeg",
+ "public.mpeg-4-audio": "audio/x-aac",
+ "public.text": "text/*",
+ "public.plain-text": "text/plain",
+ "public.rtf": "text/rtf",
+ "public.html": "text/html",
+ "public.xml": "text/xml",
+ "public.archive": "application/zip application/x-bzip application/x-bzip2 application/x-tar application/x-7z-compressed"
+ }
+}
diff --git a/source3/rpc_server/mdssvc/es_lexer.l b/source3/rpc_server/mdssvc/es_lexer.l
new file mode 100644
index 0000000..4be4225
--- /dev/null
+++ b/source3/rpc_server/mdssvc/es_lexer.l
@@ -0,0 +1,92 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / Elasticsearch backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+%{
+#include "includes.h"
+#include "rpc_server/mdssvc/es_parser.tab.h"
+
+#define YY_NO_INPUT
+#define mdsyylalloc SMB_MALLOC
+#define mdsyylrealloc SMB_REALLOC
+
+static char *strip_quote(const char *phrase);
+%}
+
+%option nounput noyyalloc noyyrealloc prefix="mdsyyl"
+
+ASC [a-zA-Z0-9_\*\:\-\.]
+U [\x80-\xbf]
+U2 [\xc2-\xdf]
+U3 [\xe0-\xef]
+U4 [\xf0-\xf4]
+SPECIAL [\!\#\$\%\&\'\(\)\+\,\.\/\;\<\=\>\?\@\[\]\^\`\{\}\|\~\\]
+ESCHAR [\"\*]
+BLANK [ \t\n]
+
+UANY {ASC}|{U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U}
+UONLY {U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U}
+UPHRASE {UANY}|{SPECIAL}|{BLANK}|\\{ESCHAR}
+
+%%
+InRange return FUNC_INRANGE;
+\$time\.iso return DATE_ISO;
+false {mdsyyllval.bval = false; return BOOLEAN;}
+true {mdsyyllval.bval = true; return BOOLEAN;}
+\" return QUOTE;
+\( return OBRACE;
+\) return CBRACE;
+\&\& return AND;
+\|\| return OR;
+\=\= return EQUAL;
+\!\= return UNEQUAL;
+\= return EQUAL;
+\< return LT;
+\> return GT;
+\, return COMMA;
+{UANY}+ {mdsyyllval.sval = talloc_strdup(talloc_tos(), yytext); return WORD;}
+\"{UPHRASE}+\" {mdsyyllval.sval = strip_quote(yytext); return PHRASE;}
+{BLANK} /* ignore */
+%%
+
+static char *strip_quote(const char *phrase)
+{
+ size_t phrase_len = 0;
+ char *stripped_phrase = NULL;
+
+ if (phrase == NULL) {
+ return NULL;
+ }
+
+ phrase_len = strlen(phrase);
+ if (phrase_len < 2 ||
+ phrase[0] != '\"' ||
+ phrase[phrase_len - 1] != '\"')
+ {
+ return talloc_strdup(talloc_tos(), phrase);
+ }
+
+ phrase++;
+
+ stripped_phrase = talloc_strndup(talloc_tos(), phrase, phrase_len - 2);
+ if (stripped_phrase == NULL) {
+ return NULL;
+ }
+ return stripped_phrase;
+}
diff --git a/source3/rpc_server/mdssvc/es_mapping.c b/source3/rpc_server/mdssvc/es_mapping.c
new file mode 100644
index 0000000..e8d181d
--- /dev/null
+++ b/source3/rpc_server/mdssvc/es_mapping.c
@@ -0,0 +1,241 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / Elasticsearch backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "es_mapping.h"
+
+/*
+ * Escaping of special characters in Lucene query syntax across HTTP and JSON
+ * ==========================================================================
+ *
+ * These characters in Lucene queries need escaping [1]:
+ *
+ * + - & | ! ( ) { } [ ] ^ " ~ * ? : \ /
+ *
+ * Additionally JSON requires escaping of:
+ *
+ * " \
+ *
+ * Characters already escaped by the mdssvc client:
+ *
+ * * " \
+ *
+ * The following table contains the resulting escaped strings, beginning with the
+ * search term, the corresponding Spotlight query and the final string that gets
+ * sent to the target Elasticsearch server.
+ *
+ * string | mdfind | http
+ * -------+--------+------
+ * x!x x!x x\\!x
+ * x&x x&x x\\&x
+ * x+x x+x x\\+x
+ * x-x x-x x\\-x
+ * x.x x.x x\\.x
+ * x<x x<x x\\<x
+ * x>x x>x x\\>x
+ * x=x x=x x\\=x
+ * x?x x?x x\\?x
+ * x[x x[x x\\[x
+ * x]x x]x x\\]x
+ * x^x x^x x\\^x
+ * x{x x{x x\\{x
+ * x}x x}x x\\}x
+ * x|x x|x x\\|x
+ * x x x x x\\ x
+ * x*x x\*x x\\*x
+ * x\x x\\x x\\\\x
+ * x"x x\"x x\\\"x
+ *
+ * Special cases:
+ * x y It's not possible to search for terms including spaces, Spotlight
+ * will search for x OR y.
+ * x(x Search for terms including ( and ) does not work with Spotlight.
+ *
+ * [1] <http://lucene.apache.org/core/8_2_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Escaping_Special_Characters>
+ */
+
+static char *escape_str(TALLOC_CTX *mem_ctx,
+ const char *in,
+ const char *escape_list,
+ const char *escape_exceptions)
+{
+ char *out = NULL;
+ size_t in_len;
+ size_t new_len;
+ size_t in_pos;
+ size_t out_pos = 0;
+
+ if (in == NULL) {
+ return NULL;
+ }
+ in_len = strlen(in);
+
+ if (escape_list == NULL) {
+ escape_list = "";
+ }
+ if (escape_exceptions == NULL) {
+ escape_exceptions = "";
+ }
+
+ /*
+ * Allocate enough space for the worst case: every char needs to be
+ * escaped and requires an additional char.
+ */
+ new_len = (in_len * 2) + 1;
+ if (new_len <= in_len) {
+ return NULL;
+ }
+
+ out = talloc_zero_array(mem_ctx, char, new_len);
+ if (out == NULL) {
+ return NULL;
+ }
+
+ for (in_pos = 0, out_pos = 0; in_pos < in_len; in_pos++, out_pos++) {
+ if (strchr(escape_list, in[in_pos]) != NULL &&
+ strchr(escape_exceptions, in[in_pos]) == NULL)
+ {
+ out[out_pos++] = '\\';
+ }
+ out[out_pos] = in[in_pos];
+ }
+
+ return out;
+}
+
+char *es_escape_str(TALLOC_CTX *mem_ctx,
+ const char *in,
+ const char *exceptions)
+{
+ const char *lucene_escape_list = "+-&|!(){}[]^\"~*?:\\/ ";
+ const char *json_escape_list = "\\\"";
+ char *lucene_escaped = NULL;
+ char *full_escaped = NULL;
+
+ lucene_escaped = escape_str(mem_ctx,
+ in,
+ lucene_escape_list,
+ exceptions);
+ if (lucene_escaped == NULL) {
+ return NULL;
+ }
+
+ full_escaped = escape_str(mem_ctx,
+ lucene_escaped,
+ json_escape_list,
+ NULL);
+ TALLOC_FREE(lucene_escaped);
+ return full_escaped;
+}
+
+struct es_attr_map *es_map_sl_attr(TALLOC_CTX *mem_ctx,
+ json_t *kmd_map,
+ const char *sl_attr)
+{
+ struct es_attr_map *es_map = NULL;
+ const char *typestr = NULL;
+ enum ssm_type type = ssmt_bool;
+ char *es_attr = NULL;
+ size_t i;
+ int cmp;
+ int ret;
+
+ static struct {
+ const char *typestr;
+ enum ssm_type typeval;
+ } ssmt_type_map[] = {
+ {"bool", ssmt_bool},
+ {"num", ssmt_num},
+ {"str", ssmt_str},
+ {"fts", ssmt_fts},
+ {"date", ssmt_date},
+ {"type", ssmt_type},
+ };
+
+ if (sl_attr == NULL) {
+ return NULL;
+ }
+
+ ret = json_unpack(kmd_map,
+ "{s: {s: s}}",
+ sl_attr,
+ "type",
+ &typestr);
+ if (ret != 0) {
+ DBG_DEBUG("No JSON type mapping for [%s]\n", sl_attr);
+ return NULL;
+ }
+
+ ret = json_unpack(kmd_map,
+ "{s: {s: s}}",
+ sl_attr,
+ "attribute",
+ &es_attr);
+ if (ret != 0) {
+ DBG_ERR("No JSON attribute mapping for [%s]\n", sl_attr);
+ return NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(ssmt_type_map); i++) {
+ cmp = strcmp(typestr, ssmt_type_map[i].typestr);
+ if (cmp == 0) {
+ type = ssmt_type_map[i].typeval;
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(ssmt_type_map)) {
+ return NULL;
+ }
+
+ es_map = talloc_zero(mem_ctx, struct es_attr_map);
+ if (es_map == NULL) {
+ return NULL;
+ }
+ es_map->type = type;
+
+ es_map->name = es_escape_str(es_map, es_attr, NULL);
+ if (es_map->name == NULL) {
+ TALLOC_FREE(es_map);
+ return false;
+ }
+
+ return es_map;
+}
+
+const char *es_map_sl_type(json_t *mime_map,
+ const char *sl_type)
+{
+ const char *mime_type = NULL;
+ int ret;
+
+ if (sl_type == NULL) {
+ return NULL;
+ }
+
+ ret = json_unpack(mime_map,
+ "{s: s}",
+ sl_type,
+ &mime_type);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ return mime_type;
+}
diff --git a/source3/rpc_server/mdssvc/es_mapping.h b/source3/rpc_server/mdssvc/es_mapping.h
new file mode 100644
index 0000000..29511b5
--- /dev/null
+++ b/source3/rpc_server/mdssvc/es_mapping.h
@@ -0,0 +1,49 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / Elasticsearch backend
+
+ Copyright (c) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _ES_MAPPING_H_
+#define _ES_MAPPING_H_
+
+#include <jansson.h>
+
+enum ssm_type {
+ ssmt_bool, /* a boolean value */
+ ssmt_num, /* a numeric value */
+ ssmt_str, /* a string value */
+ ssmt_fts, /* a string value */
+ ssmt_date, /* date values */
+ ssmt_type /* kMDItemContentType, requires special mapping */
+};
+
+struct es_attr_map {
+ enum ssm_type type;
+ const char *name;
+};
+
+char *es_escape_str(TALLOC_CTX *mem_ctx,
+ const char *in,
+ const char *exceptions);
+struct es_attr_map *es_map_sl_attr(TALLOC_CTX *mem_ctx,
+ json_t *kmd_map,
+ const char *sl_attr);
+const char *es_map_sl_type(json_t *mime_map,
+ const char *sl_type);
+
+#endif
diff --git a/source3/rpc_server/mdssvc/es_parser.y b/source3/rpc_server/mdssvc/es_parser.y
new file mode 100644
index 0000000..3fbdf93
--- /dev/null
+++ b/source3/rpc_server/mdssvc/es_parser.y
@@ -0,0 +1,686 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / Elasticsearch backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+%{
+ #include "includes.h"
+ #include "rpc_server/mdssvc/mdssvc.h"
+ #include "rpc_server/mdssvc/mdssvc_es.h"
+ #include "rpc_server/mdssvc/es_parser.tab.h"
+ #include "rpc_server/mdssvc/es_mapping.h"
+ #include "lib/util/smb_strtox.h"
+ #include <jansson.h>
+
+ /*
+ * allow building with -O3 -Wp,-D_FORTIFY_SOURCE=2
+ *
+ * /tmp/samba-testbase/.../mdssvc/es_parser.y: In function
+ * ‘mdsyylparse’:
+ * es_parser.tab.c:1124:6: error: assuming pointer wraparound
+ * does not occur when comparing P +- C1 with P +- C2
+ * [-Werror=strict-overflow]
+ *
+ * The generated code in es_parser.tab.c looks like this:
+ *
+ * if (yyss + yystacksize - 1 <= yyssp)
+ */
+ #pragma GCC diagnostic ignored "-Wstrict-overflow"
+
+ #define YYMALLOC SMB_MALLOC
+ #define YYREALLOC SMB_REALLOC
+
+ struct yy_buffer_state;
+ typedef struct yy_buffer_state *YY_BUFFER_STATE;
+ int mdsyyllex(void);
+ void mdsyylerror(char const *);
+ void *mdsyylterminate(void);
+ YY_BUFFER_STATE mdsyyl_scan_string(const char *str);
+ void mdsyyl_delete_buffer(YY_BUFFER_STATE buffer);
+
+ /* forward declarations */
+ static char *isodate_to_sldate(const char *s);
+ static char *map_expr(const struct es_attr_map *attr,
+ char op,
+ const char *val1,
+ const char *val2);
+
+ /* global vars, eg needed by the lexer */
+ struct es_parser_state {
+ TALLOC_CTX *frame;
+ json_t *kmd_map;
+ json_t *mime_map;
+ bool ignore_unknown_attribute;
+ bool ignore_unknown_type;
+ bool type_error;
+ YY_BUFFER_STATE s;
+ const char *result;
+ } *global_es_parser_state;
+%}
+
+%code provides {
+ #include <stdbool.h>
+ #include <jansson.h>
+ #include "rpc_server/mdssvc/mdssvc.h"
+
+ /* 2001-01-01T00:00:00Z - Unix Epoch = SP_RAW_TIME_OFFSET */
+ #define SP_RAW_TIME_OFFSET 978307200
+
+ int mdsyylwrap(void);
+ bool map_spotlight_to_es_query(TALLOC_CTX *mem_ctx,
+ json_t *mappings,
+ const char *path_scope,
+ const char *query_string,
+ char **_es_query);
+}
+
+%union {
+ bool bval;
+ const char *sval;
+ struct es_attr_map *attr_map;
+}
+
+%name-prefix "mdsyyl"
+%expect 1
+%error-verbose
+
+%type <sval> match expr line function value isodate
+%type <attr_map> attribute
+
+%token <sval> WORD PHRASE
+%token <bval> BOOLEAN
+%token FUNC_INRANGE
+%token DATE_ISO
+%token OBRACE CBRACE EQUAL UNEQUAL GT LT COMMA QUOTE
+%left OR
+%left AND
+%%
+
+input:
+/* empty */
+| input line
+;
+
+line:
+expr {
+ if ($1 == NULL) {
+ YYABORT;
+ }
+ if (global_es_parser_state->type_error) {
+ YYABORT;
+ }
+ global_es_parser_state->result = $1;
+}
+;
+
+expr:
+OBRACE expr CBRACE {
+ if ($2 == NULL) {
+ $$ = NULL;
+ } else {
+ $$ = talloc_asprintf(talloc_tos(), "(%s)", $2);
+ if ($$ == NULL) YYABORT;
+ }
+}
+| expr AND expr {
+ if ($1 == NULL && $3 == NULL) {
+ $$ = NULL;
+ } else if ($1 == NULL) {
+ $$ = $3;
+ } else if ($3 == NULL) {
+ $$ = $1;
+ } else {
+ $$ = talloc_asprintf(talloc_tos(), "(%s) AND (%s)", $1, $3);
+ if ($$ == NULL) YYABORT;
+ }
+}
+| expr OR expr {
+ if ($1 == NULL && $3 == NULL) {
+ $$ = NULL;
+ } else if ($1 == NULL) {
+ $$ = $3;
+ } else if ($3 == NULL) {
+ $$ = $1;
+ } else {
+ $$ = talloc_asprintf(talloc_tos(), "%s OR %s", $1, $3);
+ if ($$ == NULL) YYABORT;
+ }
+}
+| match {
+ $$ = $1;
+}
+| BOOLEAN {
+ /*
+ * We can't properly handle these in expressions, fortunately this
+ * is probably only ever used by OS X as sole element in an
+ * expression ie "False" (when Finder window selected our share
+ * but no search string entered yet). Packet traces showed that OS
+ * X Spotlight server then returns a failure (ie -1) which is what
+ * we do here too by calling YYABORT.
+ */
+ YYABORT;
+};
+
+match:
+attribute EQUAL value {
+ if ($1 == NULL) {
+ $$ = NULL;
+ } else {
+ $$ = map_expr($1, '=', $3, NULL);
+ }
+}
+| attribute UNEQUAL value {
+ if ($1 == NULL) {
+ $$ = NULL;
+ } else {
+ $$ = map_expr($1, '!', $3, NULL);
+ }
+}
+| attribute LT value {
+ if ($1 == NULL) {
+ $$ = NULL;
+ } else {
+ $$ = map_expr($1, '<', $3, NULL);
+ }
+}
+| attribute GT value {
+ if ($1 == NULL) {
+ $$ = NULL;
+ } else {
+ $$ = map_expr($1, '>', $3, NULL);
+ }
+}
+| function {
+ $$ = $1;
+}
+| match WORD {
+ $$ = $1;
+};
+
+function:
+FUNC_INRANGE OBRACE attribute COMMA WORD COMMA WORD CBRACE {
+ if ($3 == NULL) {
+ $$ = NULL;
+ } else {
+ $$ = map_expr($3, '~', $5, $7);
+ }
+};
+
+attribute:
+WORD {
+ $$ = es_map_sl_attr(global_es_parser_state->frame,
+ global_es_parser_state->kmd_map,
+ $1);
+ if ($$ == NULL &&
+ !global_es_parser_state->ignore_unknown_attribute)
+ {
+ YYABORT;
+ }
+};
+
+value:
+PHRASE {
+ $$ = $1;
+}
+| isodate {
+ $$ = $1;
+};
+
+isodate:
+DATE_ISO OBRACE WORD CBRACE {
+ $$ = isodate_to_sldate($3);
+ if ($$ == NULL) YYABORT;
+};
+
+%%
+
+/*
+ * Spotlight has two date formats:
+ * - seconds since 2001-01-01 00:00:00Z
+ * - as string "$time.iso(%Y-%m-%dT%H:%M:%SZ)"
+ * This function converts the latter to the former as string, so the parser
+ * can work on a uniform format.
+ */
+static char *isodate_to_sldate(const char *isodate)
+{
+ struct es_parser_state *s = global_es_parser_state;
+ struct tm tm = {};
+ const char *p = NULL;
+ char *tstr = NULL;
+ time_t t;
+
+ p = strptime(isodate, "%Y-%m-%dT%H:%M:%SZ", &tm);
+ if (p == NULL) {
+ DBG_ERR("strptime [%s] failed\n", isodate);
+ return NULL;
+ }
+
+ t = timegm(&tm);
+ t -= SP_RAW_TIME_OFFSET;
+
+ tstr = talloc_asprintf(s->frame, "%jd", (intmax_t)t);
+ if (tstr == NULL) {
+ return NULL;
+ }
+
+ return tstr;
+}
+
+static char *map_type(const struct es_attr_map *attr,
+ char op,
+ const char *val)
+{
+ struct es_parser_state *s = global_es_parser_state;
+ const char *mime_type_list = NULL;
+ char *esc_mime_type_list = NULL;
+ const char *not = NULL;
+ const char *end = NULL;
+ char *es = NULL;
+
+ mime_type_list = es_map_sl_type(s->mime_map, val);
+ if (mime_type_list == NULL) {
+ DBG_DEBUG("Mapping type [%s] failed\n", val);
+ if (!s->ignore_unknown_type) {
+ s->type_error = true;
+ }
+ return NULL;
+ }
+
+ esc_mime_type_list = es_escape_str(s->frame,
+ mime_type_list,
+ "* ");
+ if (esc_mime_type_list == NULL) {
+ return NULL;
+ }
+
+ switch (op) {
+ case '=':
+ not = "";
+ end = "";
+ break;
+ case '!':
+ not = "(NOT ";
+ end = ")";
+ break;
+ default:
+ DBG_ERR("Mapping type [%s] unexpected op [%c]\n", val, op);
+ return NULL;
+ }
+ es = talloc_asprintf(s->frame,
+ "%s%s:(%s)%s",
+ not,
+ attr->name,
+ esc_mime_type_list,
+ end);
+ if (es == NULL) {
+ return NULL;
+ }
+
+ return es;
+}
+
+static char *map_num(const struct es_attr_map *attr,
+ char op,
+ const char *val1,
+ const char *val2)
+{
+ struct es_parser_state *s = global_es_parser_state;
+ char *es = NULL;
+
+ switch (op) {
+ case '>':
+ es = talloc_asprintf(s->frame,
+ "%s:{%s TO *}",
+ attr->name,
+ val1);
+ break;
+ case '<':
+ es = talloc_asprintf(s->frame,
+ "%s:{* TO %s}",
+ attr->name,
+ val1);
+ break;
+ case '~':
+ es = talloc_asprintf(s->frame,
+ "%s:[%s TO %s]",
+ attr->name,
+ val1,
+ val2);
+ break;
+ case '=':
+ es = talloc_asprintf(s->frame,
+ "%s:%s",
+ attr->name,
+ val1);
+ break;
+ case '!':
+ es = talloc_asprintf(s->frame,
+ "(NOT %s:%s)",
+ attr->name,
+ val1);
+ break;
+ default:
+ DBG_ERR("Mapping num unexpected op [%c]\n", op);
+ return NULL;
+ }
+ if (es == NULL) {
+ return NULL;
+ }
+
+ return es;
+}
+
+static char *map_fts(const struct es_attr_map *attr,
+ char op,
+ const char *val)
+{
+ struct es_parser_state *s = global_es_parser_state;
+ const char *not = NULL;
+ const char *end = NULL;
+ char *esval = NULL;
+ char *es = NULL;
+
+ esval = es_escape_str(s->frame, val, "*\\\"");
+ if (esval == NULL) {
+ yyerror("es_escape_str failed");
+ return NULL;
+ }
+
+ switch (op) {
+ case '=':
+ not = "";
+ end = "";
+ break;
+ case '!':
+ not = "(NOT ";
+ end = ")";
+ break;
+ default:
+ DBG_ERR("Mapping fts [%s] unexpected op [%c]\n", val, op);
+ return NULL;
+ }
+ es = talloc_asprintf(s->frame,
+ "%s%s%s",
+ not,
+ esval,
+ end);
+ if (es == NULL) {
+ return NULL;
+ }
+ return es;
+}
+
+static char *map_str(const struct es_attr_map *attr,
+ char op,
+ const char *val)
+{
+ struct es_parser_state *s = global_es_parser_state;
+ char *esval = NULL;
+ char *es = NULL;
+ const char *not = NULL;
+ const char *end = NULL;
+
+ esval = es_escape_str(s->frame, val, "*\\\"");
+ if (esval == NULL) {
+ yyerror("es_escape_str failed");
+ return NULL;
+ }
+
+ switch (op) {
+ case '=':
+ not = "";
+ end = "";
+ break;
+ case '!':
+ not = "(NOT ";
+ end = ")";
+ break;
+ default:
+ DBG_ERR("Mapping string [%s] unexpected op [%c]\n", val, op);
+ return NULL;
+ }
+
+ es = talloc_asprintf(s->frame,
+ "%s%s:%s%s",
+ not,
+ attr->name,
+ esval,
+ end);
+ if (es == NULL) {
+ return NULL;
+ }
+ return es;
+}
+
+/*
+ * Convert Spotlight date seconds since 2001-01-01 00:00:00Z
+ * to a date string in the format %Y-%m-%dT%H:%M:%SZ.
+ */
+static char *map_sldate_to_esdate(TALLOC_CTX *mem_ctx,
+ const char *sldate)
+{
+ struct tm *tm = NULL;
+ char *esdate = NULL;
+ char buf[21];
+ size_t len;
+ time_t t;
+ int error;
+
+ t = (time_t)smb_strtoull(sldate, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ DBG_ERR("smb_strtoull [%s] failed\n", sldate);
+ return NULL;
+ }
+ t += SP_RAW_TIME_OFFSET;
+
+ tm = gmtime(&t);
+ if (tm == NULL) {
+ DBG_ERR("localtime [%s] failed\n", sldate);
+ return NULL;
+ }
+
+ len = strftime(buf, sizeof(buf),
+ "%Y-%m-%dT%H:%M:%SZ", tm);
+ if (len != 20) {
+ DBG_ERR("strftime [%s] failed\n", sldate);
+ return NULL;
+ }
+
+ esdate = es_escape_str(mem_ctx, buf, NULL);
+ if (esdate == NULL) {
+ yyerror("es_escape_str failed");
+ return NULL;
+ }
+ return esdate;
+}
+
+static char *map_date(const struct es_attr_map *attr,
+ char op,
+ const char *sldate1,
+ const char *sldate2)
+{
+ struct es_parser_state *s = global_es_parser_state;
+ char *esdate1 = NULL;
+ char *esdate2 = NULL;
+ char *es = NULL;
+
+ if (op == '~' && sldate2 == NULL) {
+ DBG_ERR("Date range query, but second date is NULL\n");
+ return NULL;
+ }
+
+ esdate1 = map_sldate_to_esdate(s->frame, sldate1);
+ if (esdate1 == NULL) {
+ DBG_ERR("map_sldate_to_esdate [%s] failed\n", sldate1);
+ return NULL;
+ }
+ if (sldate2 != NULL) {
+ esdate2 = map_sldate_to_esdate(s->frame, sldate2);
+ if (esdate2 == NULL) {
+ DBG_ERR("map_sldate_to_esdate [%s] failed\n", sldate2);
+ return NULL;
+ }
+ }
+
+ switch (op) {
+ case '>':
+ es = talloc_asprintf(s->frame,
+ "%s:{%s TO *}",
+ attr->name,
+ esdate1);
+ break;
+ case '<':
+ es = talloc_asprintf(s->frame,
+ "%s:{* TO %s}",
+ attr->name,
+ esdate1);
+ break;
+ case '~':
+ es = talloc_asprintf(s->frame,
+ "%s:[%s TO %s]",
+ attr->name,
+ esdate1,
+ esdate2);
+ break;
+ case '=':
+ es = talloc_asprintf(s->frame,
+ "%s:%s",
+ attr->name,
+ esdate1);
+ break;
+ case '!':
+ es = talloc_asprintf(s->frame,
+ "(NOT %s:%s)",
+ attr->name,
+ esdate1);
+ break;
+ }
+ if (es == NULL) {
+ return NULL;
+ }
+ return es;
+}
+
+static char *map_expr(const struct es_attr_map *attr,
+ char op,
+ const char *val1,
+ const char *val2)
+{
+ char *es = NULL;
+
+ switch (attr->type) {
+ case ssmt_type:
+ es = map_type(attr, op, val1);
+ break;
+ case ssmt_num:
+ es = map_num(attr, op, val1, val2);
+ break;
+ case ssmt_fts:
+ es = map_fts(attr, op, val1);
+ break;
+ case ssmt_str:
+ es = map_str(attr, op, val1);
+ break;
+ case ssmt_date:
+ es = map_date(attr, op, val1, val2);
+ break;
+ default:
+ break;
+ }
+ if (es == NULL) {
+ DBG_DEBUG("Mapping [%s %c %s (%s)] failed\n",
+ attr->name, op, val1, val2 ? val2 : "");
+ return NULL;
+ }
+
+ return es;
+}
+
+void mdsyylerror(const char *str)
+{
+ DBG_ERR("Parser failed: %s\n", str);
+}
+
+int mdsyylwrap(void)
+{
+ return 1;
+}
+
+/**
+ * Map a Spotlight RAW query string to a ES query string
+ **/
+bool map_spotlight_to_es_query(TALLOC_CTX *mem_ctx,
+ json_t *mappings,
+ const char *path_scope,
+ const char *query_string,
+ char **_es_query)
+{
+ struct es_parser_state s = {
+ .frame = talloc_stackframe(),
+ };
+ int result;
+ char *es_query = NULL;
+
+ s.kmd_map = json_object_get(mappings, "attribute_mappings");
+ if (s.kmd_map == NULL) {
+ DBG_ERR("Failed to load attribute_mappings from JSON\n");
+ return false;
+ }
+ s.mime_map = json_object_get(mappings, "mime_mappings");
+ if (s.mime_map == NULL) {
+ DBG_ERR("Failed to load mime_mappings from JSON\n");
+ return false;
+ }
+
+ s.s = mdsyyl_scan_string(query_string);
+ if (s.s == NULL) {
+ DBG_WARNING("Failed to parse [%s]\n", query_string);
+ TALLOC_FREE(s.frame);
+ return false;
+ }
+
+ s.ignore_unknown_attribute = lp_parm_bool(GLOBAL_SECTION_SNUM,
+ "elasticsearch",
+ "ignore unknown attribute",
+ false);
+ s.ignore_unknown_type = lp_parm_bool(GLOBAL_SECTION_SNUM,
+ "elasticsearch",
+ "ignore unknown type",
+ false);
+
+ global_es_parser_state = &s;
+ result = mdsyylparse();
+ global_es_parser_state = NULL;
+ mdsyyl_delete_buffer(s.s);
+
+ if (result != 0) {
+ TALLOC_FREE(s.frame);
+ return false;
+ }
+
+ es_query = talloc_asprintf(mem_ctx,
+ "(%s) AND path.real.fulltext:\\\"%s\\\"",
+ s.result, path_scope);
+ TALLOC_FREE(s.frame);
+ if (es_query == NULL) {
+ return false;
+ }
+
+ *_es_query = es_query;
+ return true;
+}
diff --git a/source3/rpc_server/mdssvc/es_parser_test.c b/source3/rpc_server/mdssvc/es_parser_test.c
new file mode 100644
index 0000000..7d88c67
--- /dev/null
+++ b/source3/rpc_server/mdssvc/es_parser_test.c
@@ -0,0 +1,97 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / ES backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_server/mdssvc/mdssvc.h"
+#include "rpc_server/mdssvc/mdssvc_es.h"
+#include "rpc_server/mdssvc/es_parser.tab.h"
+#include "rpc_server/mdssvc/es_mapping.h"
+
+/*
+ * Examples:
+ *
+ * $ ./spotlight2es '_kMDItemGroupId=="11"'
+ * ...
+ * $ ./spotlight2es '*=="test*"||kMDItemTextContent=="test*"'
+ * ...
+ */
+
+int main(int argc, char **argv)
+{
+ TALLOC_CTX *mem_ctx = NULL;
+ json_t *mappings = NULL;
+ json_error_t json_error;
+ char *default_path = NULL;
+ const char *path = NULL;
+ const char *query_string = NULL;
+ const char *path_scope = NULL;
+ char *es_query = NULL;
+ bool ok;
+
+ if (argc != 2) {
+ printf("usage: %s QUERY\n", argv[0]);
+ return 1;
+ }
+ query_string = argv[1];
+ path_scope = "/foo/bar";
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ mem_ctx = talloc_init("es_parser_test");
+ if (mem_ctx == NULL) {
+ return 1;
+ }
+
+ default_path = talloc_asprintf(mem_ctx,
+ "%s/mdssvc/elasticsearch_mappings.json",
+ get_dyn_SAMBA_DATADIR());
+ if (default_path == NULL) {
+ TALLOC_FREE(mem_ctx);
+ return 1;
+ }
+
+ path = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+ "elasticsearch",
+ "mappings",
+ default_path);
+ if (path == NULL) {
+ TALLOC_FREE(mem_ctx);
+ return 1;
+ }
+
+ mappings = json_load_file(path, 0, &json_error);
+ if (mappings == NULL) {
+ DBG_ERR("Opening mapping file [%s] failed: %s\n",
+ path, strerror(errno));
+ TALLOC_FREE(mem_ctx);
+ return 1;
+ }
+
+ ok = map_spotlight_to_es_query(mem_ctx,
+ mappings,
+ path_scope,
+ query_string,
+ &es_query);
+ printf("%s\n", ok ? es_query : "*mapping failed*");
+
+ json_decref(mappings);
+ talloc_free(mem_ctx);
+ return ok ? 0 : 1;
+}
diff --git a/source3/rpc_server/mdssvc/marshalling.c b/source3/rpc_server/mdssvc/marshalling.c
new file mode 100644
index 0000000..c85fae7
--- /dev/null
+++ b/source3/rpc_server/mdssvc/marshalling.c
@@ -0,0 +1,1422 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines
+
+ Copyright (C) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "dalloc.h"
+#include "marshalling.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+/*
+ * This is used to talloc an array that will hold the table of
+ * contents of a marshalled Spotlight RPC (S-RPC) reply. Each ToC
+ * entry is 8 bytes, so we allocate space for 1024 entries which
+ * should be sufficient for even the largest S-RPC replies.
+ *
+ * The total buffersize for S-RPC packets is typically limited to 64k,
+ * so we can only store so many elements there anyway.
+ */
+#define MAX_SLQ_TOC 1024*64
+#define MAX_SLQ_TOCIDX 1024*8
+#define MAX_SLQ_COUNT 1024*64
+#define MAX_SL_STRLEN 1024
+
+/******************************************************************************
+ * RPC data marshalling and unmarshalling
+ ******************************************************************************/
+
+/* Spotlight epoch is 1.1.2001 00:00 UTC */
+#define SPOTLIGHT_TIME_DELTA 978307200 /* Diff from UNIX epoch to Spotlight epoch */
+
+#define SQ_TYPE_NULL 0x0000
+#define SQ_TYPE_COMPLEX 0x0200
+#define SQ_TYPE_INT64 0x8400
+#define SQ_TYPE_BOOL 0x0100
+#define SQ_TYPE_FLOAT 0x8500
+#define SQ_TYPE_DATA 0x0700
+#define SQ_TYPE_CNIDS 0x8700
+#define SQ_TYPE_UUID 0x0e00
+#define SQ_TYPE_DATE 0x8600
+#define SQ_TYPE_TOC 0x8800
+
+#define SQ_CPX_TYPE_ARRAY 0x0a00
+#define SQ_CPX_TYPE_STRING 0x0c00
+#define SQ_CPX_TYPE_UTF16_STRING 0x1c00
+#define SQ_CPX_TYPE_DICT 0x0d00
+#define SQ_CPX_TYPE_CNIDS 0x1a00
+#define SQ_CPX_TYPE_FILEMETA 0x1b00
+
+struct sl_tag {
+ int type;
+ int count;
+ size_t length;
+ size_t size;
+};
+
+static ssize_t sl_pack_loop(DALLOC_CTX *query, char *buf,
+ ssize_t offset, size_t bufsize,
+ char *toc_buf, int *toc_idx, int *count);
+static ssize_t sl_unpack_loop(DALLOC_CTX *query, const char *buf,
+ ssize_t offset, size_t bufsize,
+ int count, ssize_t toc_offset,
+ int encoding);
+static ssize_t sl_pack(DALLOC_CTX *query, char *buf, size_t bufsize);
+
+/******************************************************************************
+ * Wrapper functions for the *VAL macros with bound checking
+ ******************************************************************************/
+
+static ssize_t sl_push_uint64_val(char *buf,
+ ssize_t offset,
+ size_t max_offset,
+ uint64_t val)
+{
+ if (offset + 8 > max_offset) {
+ DEBUG(1, ("%s: offset: %zd, max_offset: %zu\n",
+ __func__, offset, max_offset));
+ return -1;
+ }
+
+ SBVAL(buf, offset, val);
+ return offset + 8;
+}
+
+static ssize_t sl_pull_uint64_val(const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ uint encoding,
+ uint64_t *presult)
+{
+ uint64_t val;
+
+ if (offset + 8 > bufsize) {
+ DEBUG(1,("%s: buffer overflow\n", __func__));
+ return -1;
+ }
+
+ if (encoding == SL_ENC_LITTLE_ENDIAN) {
+ val = BVAL(buf, offset);
+ } else {
+ val = RBVAL(buf, offset);
+ }
+
+ *presult = val;
+
+ return offset + 8;
+}
+
+/*
+ * Returns the UTF-16 string encoding, by checking the 2-byte byte order mark.
+ * If there is no byte order mark, -1 is returned.
+ */
+static int spotlight_get_utf16_string_encoding(const char *buf, ssize_t offset,
+ size_t query_length, int encoding)
+{
+ int utf16_encoding;
+
+ /* Assumed encoding in absence of a bom is little endian */
+ utf16_encoding = SL_ENC_LITTLE_ENDIAN;
+
+ if (query_length >= 2) {
+ uint8_t le_bom[] = {0xff, 0xfe};
+ uint8_t be_bom[] = {0xfe, 0xff};
+ if (memcmp(le_bom, buf + offset, sizeof(uint16_t)) == 0) {
+ utf16_encoding = SL_ENC_LITTLE_ENDIAN | SL_ENC_UTF_16;
+ } else if (memcmp(be_bom, buf + offset, sizeof(uint16_t)) == 0) {
+ utf16_encoding = SL_ENC_BIG_ENDIAN | SL_ENC_UTF_16;
+ }
+ }
+
+ return utf16_encoding;
+}
+
+/******************************************************************************
+ * marshalling functions
+ ******************************************************************************/
+
+static inline uint64_t sl_pack_tag(uint16_t type, uint16_t size_or_count, uint32_t val)
+{
+ uint64_t tag = ((uint64_t)val << 32) | ((uint64_t)type << 16) | size_or_count;
+ return tag;
+}
+
+static ssize_t sl_pack_float(double d, char *buf, ssize_t offset, size_t bufsize)
+{
+ union {
+ double d;
+ uint64_t w;
+ } ieee_fp_union;
+
+ ieee_fp_union.d = d;
+
+ offset = sl_push_uint64_val(buf, offset, bufsize, sl_pack_tag(SQ_TYPE_FLOAT, 2, 1));
+ if (offset == -1) {
+ return -1;
+ }
+ offset = sl_push_uint64_val(buf, offset, bufsize, ieee_fp_union.w);
+ if (offset == -1) {
+ return -1;
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_uint64(uint64_t u, char *buf, ssize_t offset, size_t bufsize)
+{
+ uint64_t tag;
+
+ tag = sl_pack_tag(SQ_TYPE_INT64, 2, 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+ offset = sl_push_uint64_val(buf, offset, bufsize, u);
+ if (offset == -1) {
+ return -1;
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_uint64_array(uint64_t *u, char *buf, ssize_t offset, size_t bufsize, int *toc_count)
+{
+ int count, i;
+ uint64_t tag;
+
+ count = talloc_array_length(u);
+
+ tag = sl_pack_tag(SQ_TYPE_INT64, count + 1, count);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ for (i = 0; i < count; i++) {
+ offset = sl_push_uint64_val(buf, offset, bufsize, u[i]);
+ if (offset == -1) {
+ return -1;
+ }
+ }
+
+ if (count > 1) {
+ *toc_count += (count - 1);
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_bool(sl_bool_t val, char *buf, ssize_t offset, size_t bufsize)
+{
+ uint64_t tag;
+
+ tag = sl_pack_tag(SQ_TYPE_BOOL, 1, val ? 1 : 0);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_nil(char *buf, ssize_t offset, size_t bufsize)
+{
+ uint64_t tag;
+
+ tag = sl_pack_tag(SQ_TYPE_NULL, 1, 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_date(sl_time_t t, char *buf, ssize_t offset, size_t bufsize)
+{
+ uint64_t data;
+ uint64_t tag;
+ union {
+ double d;
+ uint64_t w;
+ } ieee_fp_union;
+
+ tag = sl_pack_tag(SQ_TYPE_DATE, 2, 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ ieee_fp_union.d = (double)(t.tv_sec - SPOTLIGHT_TIME_DELTA);
+ ieee_fp_union.d += (double)t.tv_usec / 1000000;
+
+ data = ieee_fp_union.w;
+ offset = sl_push_uint64_val(buf, offset, bufsize, data);
+ if (offset == -1) {
+ return -1;
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_uuid(sl_uuid_t *uuid, char *buf, ssize_t offset, size_t bufsize)
+{
+ uint64_t tag;
+
+ tag = sl_pack_tag(SQ_TYPE_UUID, 3, 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ if (offset + 16 > bufsize) {
+ return -1;
+ }
+ memcpy(buf + offset, uuid, 16);
+
+ return offset + 16;
+}
+
+static ssize_t sl_pack_CNID(sl_cnids_t *cnids, char *buf, ssize_t offset,
+ size_t bufsize, char *toc_buf, int *toc_idx)
+{
+ ssize_t result;
+ int len, i;
+ int cnid_count = dalloc_size(cnids->ca_cnids);
+ uint64_t tag;
+ uint64_t id;
+ void *p;
+
+ tag = sl_pack_tag(SQ_CPX_TYPE_CNIDS, offset / 8, 0);
+ result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ *toc_idx += 1;
+
+ len = cnid_count + 1;
+ if (cnid_count > 0) {
+ len ++;
+ }
+
+ /* unknown meaning, but always 8 */
+ tag = sl_pack_tag(SQ_TYPE_CNIDS, len, 8 );
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ if (cnid_count > 0) {
+ tag = sl_pack_tag(cnids->ca_unkn1, cnid_count, cnids->ca_context);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ for (i = 0; i < cnid_count; i++) {
+ p = dalloc_get_object(cnids->ca_cnids, i);
+ if (p == NULL) {
+ return -1;
+ }
+ memcpy(&id, p, sizeof(uint64_t));
+ offset = sl_push_uint64_val(buf, offset, bufsize, id);
+ if (offset == -1) {
+ return -1;
+ }
+ }
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_array(sl_array_t *array, char *buf, ssize_t offset,
+ size_t bufsize, char *toc_buf, int *toc_idx)
+{
+ ssize_t result;
+ int count = dalloc_size(array);
+ int octets = offset / 8;
+ uint64_t tag;
+ int toc_idx_save = *toc_idx;
+
+ tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ *toc_idx += 1;
+
+ offset = sl_pack_loop(array, buf, offset, bufsize - offset, toc_buf, toc_idx, &count);
+
+ tag = sl_pack_tag(SQ_CPX_TYPE_ARRAY, octets, count);
+ result = sl_push_uint64_val(toc_buf, toc_idx_save * 8, MAX_SLQ_TOC, tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack_dict(sl_array_t *dict, char *buf, ssize_t offset,
+ size_t bufsize, char *toc_buf, int *toc_idx, int *count)
+{
+ ssize_t result;
+ uint64_t tag;
+
+ tag = sl_pack_tag(SQ_CPX_TYPE_DICT, offset / 8,
+ dalloc_size(dict));
+ result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ *toc_idx += 1;
+
+ offset = sl_pack_loop(dict, buf, offset, bufsize - offset, toc_buf, toc_idx, count);
+
+ return offset;
+}
+
+static ssize_t sl_pack_filemeta(sl_filemeta_t *fm, char *buf, ssize_t offset,
+ size_t bufsize, char *toc_buf, int *toc_idx)
+{
+ ssize_t result;
+ ssize_t fmlen;
+ ssize_t saveoff = offset;
+ uint64_t tag;
+
+ tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ offset += 8;
+
+ fmlen = sl_pack(fm, buf + offset, bufsize - offset);
+ if (fmlen == -1) {
+ return -1;
+ }
+
+ /*
+ * Check for empty filemeta array, if it's only 40 bytes, it's
+ * only the header but no content
+ */
+ if (fmlen > 40) {
+ offset += fmlen;
+ } else {
+ fmlen = 0;
+ }
+
+ /* unknown meaning, but always 8 */
+ tag = sl_pack_tag(SQ_TYPE_DATA, (fmlen / 8) + 1, 8);
+ result = sl_push_uint64_val(buf, saveoff + 8, bufsize, tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ tag = sl_pack_tag(SQ_CPX_TYPE_FILEMETA, saveoff / 8, fmlen / 8);
+ result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ *toc_idx += 1;
+
+ return offset;
+}
+
+static ssize_t sl_pack_string(char *s, char *buf, ssize_t offset, size_t bufsize,
+ char *toc_buf, int *toc_idx)
+{
+ ssize_t result;
+ size_t len, octets, used_in_last_octet;
+ uint64_t tag;
+
+ len = strlen(s);
+ if (len > MAX_SL_STRLEN) {
+ return -1;
+ }
+ octets = (len + 7) / 8;
+ used_in_last_octet = len % 8;
+ if (used_in_last_octet == 0) {
+ used_in_last_octet = 8;
+ }
+
+ tag = sl_pack_tag(SQ_CPX_TYPE_STRING, offset / 8, used_in_last_octet);
+ result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ *toc_idx += 1;
+
+ tag = sl_pack_tag(SQ_TYPE_DATA, octets + 1, used_in_last_octet);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ if (offset + (octets * 8) > bufsize) {
+ return -1;
+ }
+
+ memset(buf + offset, 0, octets * 8);
+ memcpy(buf + offset, s, len);
+ offset += octets * 8;
+
+ return offset;
+}
+
+static ssize_t sl_pack_string_as_utf16(char *s, char *buf, ssize_t offset,
+ size_t bufsize, char *toc_buf, int *toc_idx)
+{
+ ssize_t result;
+ int utf16_plus_bom_len, octets, used_in_last_octet;
+ char *utf16string = NULL;
+ char bom[] = { 0xff, 0xfe };
+ size_t slen, utf16len;
+ uint64_t tag;
+ bool ok;
+
+ slen = strlen(s);
+ if (slen > MAX_SL_STRLEN) {
+ return -1;
+ }
+
+ ok = convert_string_talloc(talloc_tos(),
+ CH_UTF8,
+ CH_UTF16LE,
+ s,
+ slen,
+ &utf16string,
+ &utf16len);
+ if (!ok) {
+ return -1;
+ }
+
+ utf16_plus_bom_len = utf16len + 2;
+ octets = (utf16_plus_bom_len + 7) / 8;
+ used_in_last_octet = utf16_plus_bom_len % 8;
+ if (used_in_last_octet == 0) {
+ used_in_last_octet = 8;
+ }
+
+ tag = sl_pack_tag(SQ_CPX_TYPE_UTF16_STRING, offset / 8, used_in_last_octet);
+ result = sl_push_uint64_val(toc_buf, *toc_idx * 8, MAX_SLQ_TOC, tag);
+ if (result == -1) {
+ offset = -1;
+ goto done;
+ }
+
+ tag = sl_pack_tag(SQ_TYPE_COMPLEX, 1, *toc_idx + 1);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ goto done;
+ }
+
+ *toc_idx += 1;
+
+ tag = sl_pack_tag(SQ_TYPE_DATA, octets + 1, used_in_last_octet);
+ offset = sl_push_uint64_val(buf, offset, bufsize, tag);
+ if (offset == -1) {
+ goto done;
+ }
+
+ if (offset + (octets * 8) > bufsize) {
+ offset = -1;
+ goto done;
+ }
+
+ memset(buf + offset, 0, octets * 8);
+ memcpy(buf + offset, &bom, sizeof(bom));
+ memcpy(buf + offset + 2, utf16string, utf16len);
+ offset += octets * 8;
+
+done:
+ TALLOC_FREE(utf16string);
+ return offset;
+}
+
+static ssize_t sl_pack_loop(DALLOC_CTX *query, char *buf, ssize_t offset,
+ size_t bufsize, char *toc_buf, int *toc_idx, int *count)
+{
+ const char *type;
+ int n;
+ uint64_t i;
+ sl_bool_t bl;
+ double d;
+ sl_time_t t;
+ void *p;
+
+ for (n = 0; n < dalloc_size(query); n++) {
+
+ type = dalloc_get_name(query, n);
+ if (type == NULL) {
+ return -1;
+ }
+ p = dalloc_get_object(query, n);
+ if (p == NULL) {
+ return -1;
+ }
+
+ if (strcmp(type, "sl_array_t") == 0) {
+ offset = sl_pack_array(p, buf, offset, bufsize,
+ toc_buf, toc_idx);
+ } else if (strcmp(type, "sl_dict_t") == 0) {
+ offset = sl_pack_dict(p, buf, offset, bufsize,
+ toc_buf, toc_idx, count);
+ } else if (strcmp(type, "sl_filemeta_t") == 0) {
+ offset = sl_pack_filemeta(p, buf, offset, bufsize,
+ toc_buf, toc_idx);
+ } else if (strcmp(type, "uint64_t") == 0) {
+ memcpy(&i, p, sizeof(uint64_t));
+ offset = sl_pack_uint64(i, buf, offset, bufsize);
+ } else if (strcmp(type, "uint64_t *") == 0) {
+ offset = sl_pack_uint64_array(p, buf, offset,
+ bufsize, count);
+ } else if (strcmp(type, "char *") == 0) {
+ offset = sl_pack_string(p, buf, offset, bufsize,
+ toc_buf, toc_idx);
+ } else if (strcmp(type, "smb_ucs2_t *") == 0) {
+ offset = sl_pack_string_as_utf16(p, buf, offset, bufsize,
+ toc_buf, toc_idx);
+ } else if (strcmp(type, "sl_bool_t") == 0) {
+ memcpy(&bl, p, sizeof(sl_bool_t));
+ offset = sl_pack_bool(bl, buf, offset, bufsize);
+ } else if (strcmp(type, "double") == 0) {
+ memcpy(&d, p, sizeof(double));
+ offset = sl_pack_float(d, buf, offset, bufsize);
+ } else if (strcmp(type, "sl_nil_t") == 0) {
+ offset = sl_pack_nil(buf, offset, bufsize);
+ } else if (strcmp(type, "sl_time_t") == 0) {
+ memcpy(&t, p, sizeof(sl_time_t));
+ offset = sl_pack_date(t, buf, offset, bufsize);
+ } else if (strcmp(type, "sl_uuid_t") == 0) {
+ offset = sl_pack_uuid(p, buf, offset, bufsize);
+ } else if (strcmp(type, "sl_cnids_t") == 0) {
+ offset = sl_pack_CNID(p, buf, offset,
+ bufsize, toc_buf, toc_idx);
+ } else {
+ DEBUG(1, ("unknown type: %s\n", type));
+ return -1;
+ }
+ if (offset == -1) {
+ DEBUG(1, ("error packing type: %s\n", type));
+ return -1;
+ }
+ }
+
+ return offset;
+}
+
+/******************************************************************************
+ * unmarshalling functions
+ ******************************************************************************/
+
+static ssize_t sl_unpack_tag(const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ uint encoding,
+ struct sl_tag *tag)
+{
+ uint64_t val;
+
+ if (offset + 8 > bufsize) {
+ DEBUG(1,("%s: buffer overflow\n", __func__));
+ return -1;
+ }
+
+ if (encoding == SL_ENC_LITTLE_ENDIAN) {
+ val = BVAL(buf, offset);
+ } else {
+ val = RBVAL(buf, offset);
+ }
+
+ tag->size = (val & 0xffff) * 8;
+ tag->type = (val & 0xffff0000) >> 16;
+ tag->count = val >> 32;
+ tag->length = tag->count * 8;
+
+ if (tag->size > MAX_MDSCMD_SIZE) {
+ DEBUG(1,("%s: size limit %zu\n", __func__, tag->size));
+ return -1;
+ }
+
+ if (tag->length > MAX_MDSCMD_SIZE) {
+ DEBUG(1,("%s: length limit %zu\n", __func__, tag->length));
+ return -1;
+ }
+
+ if (tag->count > MAX_SLQ_COUNT) {
+ DEBUG(1,("%s: count limit %d\n", __func__, tag->count));
+ return -1;
+ }
+
+ return offset + 8;
+}
+
+static int sl_unpack_ints(DALLOC_CTX *query,
+ const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ int encoding)
+{
+ int i, result;
+ struct sl_tag tag;
+ uint64_t query_data64;
+
+ offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ for (i = 0; i < tag.count; i++) {
+ offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64);
+ if (offset == -1) {
+ return -1;
+ }
+ result = dalloc_add_copy(query, &query_data64, uint64_t);
+ if (result != 0) {
+ return -1;
+ }
+ }
+
+ return tag.count;
+}
+
+static int sl_unpack_date(DALLOC_CTX *query,
+ const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ int encoding)
+{
+ int i, result;
+ struct sl_tag tag;
+ uint64_t query_data64;
+ union {
+ double d;
+ uint64_t w;
+ } ieee_fp_union;
+ double fraction;
+ sl_time_t t;
+
+ offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ for (i = 0; i < tag.count; i++) {
+ offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64);
+ if (offset == -1) {
+ return -1;
+ }
+ ieee_fp_union.w = query_data64;
+ fraction = ieee_fp_union.d - (uint64_t)ieee_fp_union.d;
+
+ t = (sl_time_t) {
+ .tv_sec = ieee_fp_union.d + SPOTLIGHT_TIME_DELTA,
+ .tv_usec = fraction * 1000000
+ };
+
+ result = dalloc_add_copy(query, &t, sl_time_t);
+ if (result != 0) {
+ return -1;
+ }
+ }
+
+ return tag.count;
+}
+
+static int sl_unpack_uuid(DALLOC_CTX *query,
+ const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ int encoding)
+{
+ int i, result;
+ sl_uuid_t uuid;
+ struct sl_tag tag;
+
+ offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ for (i = 0; i < tag.count; i++) {
+ if (offset + 16 > bufsize) {
+ DEBUG(1,("%s: buffer overflow\n", __func__));
+ return -1;
+ }
+ memcpy(uuid.sl_uuid, buf + offset, 16);
+ result = dalloc_add_copy(query, &uuid, sl_uuid_t);
+ if (result != 0) {
+ return -1;
+ }
+ offset += 16;
+ }
+
+ return tag.count;
+}
+
+static int sl_unpack_floats(DALLOC_CTX *query,
+ const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ int encoding)
+{
+ int i, result;
+ union {
+ double d;
+ uint32_t w[2];
+ } ieee_fp_union;
+ struct sl_tag tag;
+
+ offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ for (i = 0; i < tag.count; i++) {
+ if (offset + 8 > bufsize) {
+ DEBUG(1,("%s: buffer overflow\n", __func__));
+ return -1;
+ }
+ if (encoding == SL_ENC_LITTLE_ENDIAN) {
+#ifdef WORDS_BIGENDIAN
+ ieee_fp_union.w[0] = IVAL(buf, offset + 4);
+ ieee_fp_union.w[1] = IVAL(buf, offset);
+#else
+ ieee_fp_union.w[0] = IVAL(buf, offset);
+ ieee_fp_union.w[1] = IVAL(buf, offset + 4);
+#endif
+ } else {
+#ifdef WORDS_BIGENDIAN
+ ieee_fp_union.w[0] = RIVAL(buf, offset);
+ ieee_fp_union.w[1] = RIVAL(buf, offset + 4);
+#else
+ ieee_fp_union.w[0] = RIVAL(buf, offset + 4);
+ ieee_fp_union.w[1] = RIVAL(buf, offset);
+#endif
+ }
+ result = dalloc_add_copy(query, &ieee_fp_union.d, double);
+ if (result != 0) {
+ return -1;
+ }
+ offset += 8;
+ }
+
+ return tag.count;
+}
+
+static int sl_unpack_CNID(DALLOC_CTX *query,
+ const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ int length,
+ int encoding)
+{
+ int i, count, result;
+ uint64_t query_data64;
+ sl_cnids_t *cnids;
+
+ cnids = talloc_zero(query, sl_cnids_t);
+ if (cnids == NULL) {
+ return -1;
+ }
+ cnids->ca_cnids = dalloc_new(cnids);
+ if (cnids->ca_cnids == NULL) {
+ return -1;
+ }
+
+ if (length < 8) {
+ return -1;
+ }
+ if (length == 8) {
+ /*
+ * That's permitted, length=8 is an empty CNID array.
+ */
+ result = dalloc_add(query, cnids, sl_cnids_t);
+ if (result != 0) {
+ return -1;
+ }
+ return 0;
+ }
+
+ offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64);
+ if (offset == -1) {
+ return -1;
+ }
+
+ /*
+ * Note: ca_unkn1 and ca_context could be taken from the tag
+ * type and count members, but the fields are packed
+ * differently in this context, so we can't use
+ * sl_unpack_tag().
+ */
+ count = query_data64 & 0xffff;;
+ cnids->ca_unkn1 = (query_data64 & 0xffff0000) >> 16;
+ cnids->ca_context = query_data64 >> 32;
+
+ for (i = 0; i < count; i++) {
+ offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &query_data64);
+ if (offset == -1) {
+ return -1;
+ }
+
+ result = dalloc_add_copy(cnids->ca_cnids, &query_data64, uint64_t);
+ if (result != 0) {
+ return -1;
+ }
+ }
+
+ result = dalloc_add(query, cnids, sl_cnids_t);
+ if (result != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static ssize_t sl_unpack_cpx(DALLOC_CTX *query,
+ const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ int cpx_query_type,
+ int cpx_query_count,
+ ssize_t toc_offset,
+ int encoding)
+{
+ int result;
+ ssize_t roffset = offset;
+ int unicode_encoding;
+ bool mark_exists;
+ char *p;
+ size_t slen, tmp_len;
+ sl_array_t *sl_array;
+ sl_dict_t *sl_dict;
+ sl_filemeta_t *sl_fm;
+ bool ok;
+ struct sl_tag tag;
+
+ switch (cpx_query_type) {
+ case SQ_CPX_TYPE_ARRAY:
+ sl_array = dalloc_zero(query, sl_array_t);
+ if (sl_array == NULL) {
+ return -1;
+ }
+ roffset = sl_unpack_loop(sl_array, buf, offset, bufsize,
+ cpx_query_count, toc_offset, encoding);
+ if (roffset == -1) {
+ return -1;
+ }
+ result = dalloc_add(query, sl_array, sl_array_t);
+ if (result != 0) {
+ return -1;
+ }
+ break;
+
+ case SQ_CPX_TYPE_DICT:
+ sl_dict = dalloc_zero(query, sl_dict_t);
+ if (sl_dict == NULL) {
+ return -1;
+ }
+ roffset = sl_unpack_loop(sl_dict, buf, offset, bufsize,
+ cpx_query_count, toc_offset, encoding);
+ if (roffset == -1) {
+ return -1;
+ }
+ result = dalloc_add(query, sl_dict, sl_dict_t);
+ if (result != 0) {
+ return -1;
+ }
+ break;
+
+ case SQ_CPX_TYPE_STRING:
+ case SQ_CPX_TYPE_UTF16_STRING:
+ offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ if (tag.size < 16) {
+ DEBUG(1,("%s: string buffer too small\n", __func__));
+ return -1;
+ }
+ slen = tag.size - 16 + tag.count;
+ if (slen > MAX_MDSCMD_SIZE) {
+ return -1;
+ }
+
+ if (offset + slen > bufsize) {
+ DEBUG(1,("%s: buffer overflow\n", __func__));
+ return -1;
+ }
+
+ if (cpx_query_type == SQ_CPX_TYPE_STRING) {
+ p = talloc_strndup(query, buf + offset, slen);
+ if (p == NULL) {
+ return -1;
+ }
+ } else {
+ unicode_encoding = spotlight_get_utf16_string_encoding(
+ buf, offset, slen, encoding);
+ mark_exists = (unicode_encoding & SL_ENC_UTF_16) ? true : false;
+ if (unicode_encoding & SL_ENC_BIG_ENDIAN) {
+ DEBUG(1, ("Unsupported big endian UTF16 string\n"));
+ return -1;
+ }
+ slen -= mark_exists ? 2 : 0;
+ ok = convert_string_talloc(
+ query,
+ CH_UTF16LE,
+ CH_UTF8,
+ buf + offset + (mark_exists ? 2 : 0),
+ slen,
+ &p,
+ &tmp_len);
+ if (!ok) {
+ return -1;
+ }
+ }
+
+ result = dalloc_stradd(query, p);
+ if (result != 0) {
+ return -1;
+ }
+ roffset += tag.size;
+ break;
+
+ case SQ_CPX_TYPE_FILEMETA:
+ offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (offset == -1) {
+ return -1;
+ }
+ if (tag.size < 8) {
+ DBG_WARNING("size too mall: %zu\n", tag.size);
+ return -1;
+ }
+
+ sl_fm = dalloc_zero(query, sl_filemeta_t);
+ if (sl_fm == NULL) {
+ return -1;
+ }
+
+ if (tag.size >= 16) {
+ result = sl_unpack(sl_fm,
+ buf + offset,
+ bufsize - offset );
+ if (result == -1) {
+ return -1;
+ }
+ }
+ result = dalloc_add(query, sl_fm, sl_filemeta_t);
+ if (result != 0) {
+ return -1;
+ }
+ roffset += tag.size;
+ break;
+
+ case SQ_CPX_TYPE_CNIDS:
+ offset = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (offset == -1) {
+ return -1;
+ }
+
+ result = sl_unpack_CNID(query, buf, offset, bufsize,
+ tag.size, encoding);
+ if (result == -1) {
+ return -1;
+ }
+ roffset += tag.size;
+ break;
+
+ default:
+ DEBUG(1, ("unknown complex query type: %u\n", cpx_query_type));
+ return -1;
+ }
+
+ return roffset;
+}
+
+static ssize_t sl_unpack_loop(DALLOC_CTX *query,
+ const char *buf,
+ ssize_t offset,
+ size_t bufsize,
+ int count,
+ ssize_t toc_offset,
+ int encoding)
+{
+ int i, toc_index, subcount;
+ uint64_t result;
+
+ while (count > 0) {
+ struct sl_tag tag;
+
+ if (offset >= toc_offset) {
+ return -1;
+ }
+
+ result = sl_unpack_tag(buf, offset, bufsize, encoding, &tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ switch (tag.type) {
+ case SQ_TYPE_COMPLEX: {
+ struct sl_tag cpx_tag;
+
+ if (tag.count < 1) {
+ DEBUG(1,("%s: invalid tag.count: %d\n",
+ __func__, tag.count));
+ return -1;
+ }
+ toc_index = tag.count - 1;
+ if (toc_index > MAX_SLQ_TOCIDX) {
+ DEBUG(1,("%s: toc_index too large: %d\n",
+ __func__, toc_index));
+ return -1;
+ }
+ result = sl_unpack_tag(buf, toc_offset + (toc_index * 8),
+ bufsize, encoding, &cpx_tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ offset = sl_unpack_cpx(query, buf, offset + 8, bufsize, cpx_tag.type,
+ cpx_tag.count, toc_offset, encoding);
+ if (offset == -1) {
+ return -1;
+ }
+ /*
+ * tag.size is not the size here, so we need
+ * to use the offset returned from sl_unpack_cpx()
+ * instead of offset += tag.size;
+ */
+ count--;
+ break;
+ }
+
+ case SQ_TYPE_NULL: {
+ sl_nil_t nil = 0;
+
+ subcount = tag.count;
+ if (subcount < 1 || subcount > count) {
+ return -1;
+ }
+ for (i = 0; i < subcount; i++) {
+ result = dalloc_add_copy(query, &nil, sl_nil_t);
+ if (result != 0) {
+ return -1;
+ }
+ }
+ offset += tag.size;
+ count -= subcount;
+ break;
+ }
+
+ case SQ_TYPE_BOOL: {
+ sl_bool_t b = (tag.count != 0);
+
+ result = dalloc_add_copy(query, &b, sl_bool_t);
+ if (result != 0) {
+ return -1;
+ }
+ offset += tag.size;
+ count--;
+ break;
+ }
+
+ case SQ_TYPE_INT64:
+ subcount = sl_unpack_ints(query, buf, offset, bufsize, encoding);
+ if (subcount < 1 || subcount > count) {
+ return -1;
+ }
+ offset += tag.size;
+ count -= subcount;
+ break;
+
+ case SQ_TYPE_UUID:
+ subcount = sl_unpack_uuid(query, buf, offset, bufsize, encoding);
+ if (subcount < 1 || subcount > count) {
+ return -1;
+ }
+ offset += tag.size;
+ count -= subcount;
+ break;
+
+ case SQ_TYPE_FLOAT:
+ subcount = sl_unpack_floats(query, buf, offset, bufsize, encoding);
+ if (subcount < 1 || subcount > count) {
+ return -1;
+ }
+ offset += tag.size;
+ count -= subcount;
+ break;
+
+ case SQ_TYPE_DATE:
+ subcount = sl_unpack_date(query, buf, offset, bufsize, encoding);
+ if (subcount < 1 || subcount > count) {
+ return -1;
+ }
+ offset += tag.size;
+ count -= subcount;
+ break;
+
+ default:
+ DEBUG(1, ("unknown query type: %d\n", tag.type));
+ return -1;
+ }
+ }
+
+ return offset;
+}
+
+static ssize_t sl_pack(DALLOC_CTX *query, char *buf, size_t bufsize)
+{
+ ssize_t result;
+ char *toc_buf;
+ int toc_index = 0;
+ int toc_count = 0;
+ ssize_t offset, len;
+ uint64_t hdr;
+ uint32_t total_octets;
+ uint32_t data_octets;
+ uint64_t tag;
+
+ memset(buf, 0, bufsize);
+
+ toc_buf = talloc_zero_size(query, MAX_SLQ_TOC + 8);
+ if (toc_buf == NULL) {
+ return -1;
+ }
+
+ offset = sl_pack_loop(query, buf, 16, bufsize, toc_buf + 8, &toc_index, &toc_count);
+ if (offset == -1 || offset < 16) {
+ DEBUG(10,("%s: sl_pack_loop error\n", __func__));
+ return -1;
+ }
+ len = offset - 16;
+
+ /*
+ * Marshalling overview:
+ *
+ * 16 bytes at the start of buf:
+ *
+ * 8 bytes byte order mark
+ * 4 bytes total octets
+ * 4 bytes table of content octets
+ *
+ * x bytes total octets * 8 from sl_pack_loop
+ * x bytes ToC octets * 8 from toc_buf
+ */
+
+ /* Byte-order mark - we are using little endian only for now */
+ memcpy(buf, "432130dm", strlen("432130dm"));
+
+ /*
+ * The data buffer and ToC buffer sizes are enocoded in number
+ * of octets (size / 8), plus one, because the octet encoding
+ * the sizes is included.
+ */
+ data_octets = (len / 8) + 1;
+ total_octets = data_octets + toc_index + 1;
+
+ hdr = total_octets;
+ hdr |= ((uint64_t)data_octets << 32);
+
+ /* HDR */
+ result = sl_push_uint64_val(buf, 8, bufsize, hdr);
+ if (result == -1) {
+ return -1;
+ }
+
+ /*
+ * ToC tag with number of ToC entries plus one, the ToC tag
+ * header.
+ */
+ tag = sl_pack_tag(SQ_TYPE_TOC, toc_index + 1, 0);
+ result = sl_push_uint64_val(toc_buf, 0, MAX_SLQ_TOC, tag);
+ if (result == -1) {
+ return -1;
+ }
+
+ if ((16 + len + ((toc_index + 1 ) * 8)) > bufsize) {
+ DEBUG(1, ("%s: exceeding size limit %zu\n", __func__, bufsize));
+ return -1;
+ }
+
+ memcpy(buf + 16 + len, toc_buf, (toc_index + 1 ) * 8);
+ len += 16 + (toc_index + 1 ) * 8;
+
+ return len;
+}
+
+/******************************************************************************
+ * Global functions for packing und unpacking
+ ******************************************************************************/
+
+NTSTATUS sl_pack_alloc(TALLOC_CTX *mem_ctx,
+ DALLOC_CTX *d,
+ struct mdssvc_blob *b,
+ size_t max_fragment_size)
+{
+ ssize_t len;
+
+ b->spotlight_blob = talloc_zero_array(mem_ctx,
+ uint8_t,
+ max_fragment_size);
+ if (b->spotlight_blob == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ len = sl_pack(d, (char *)b->spotlight_blob, max_fragment_size);
+ if (len == -1) {
+ return NT_STATUS_DATA_ERROR;
+ }
+
+ b->length = len;
+ b->size = len;
+ return NT_STATUS_OK;
+}
+
+bool sl_unpack(DALLOC_CTX *query, const char *buf, size_t bufsize)
+{
+ ssize_t result;
+ ssize_t offset = 0;
+ int encoding;
+ uint64_t hdr;
+ uint32_t total_octets;
+ uint64_t total_bytes;
+ uint32_t data_octets;
+ uint64_t data_bytes;
+ uint64_t toc_offset;
+ struct sl_tag toc_tag;
+
+ if (bufsize > MAX_MDSCMD_SIZE) {
+ return false;
+ }
+
+ if (bufsize < 8) {
+ return false;
+ }
+ if (strncmp(buf + offset, "md031234", 8) == 0) {
+ encoding = SL_ENC_BIG_ENDIAN;
+ } else {
+ encoding = SL_ENC_LITTLE_ENDIAN;
+ }
+ offset += 8;
+
+ offset = sl_pull_uint64_val(buf, offset, bufsize, encoding, &hdr);
+ if (offset == -1) {
+ return false;
+ }
+
+ total_octets = hdr & UINT32_MAX;
+ data_octets = hdr >> 32;
+
+ /*
+ * Both fields contain the number of octets of the
+ * corresponding buffer plus the tag octet. We adjust the
+ * values to match just the number of octets in the buffers.
+ */
+ if (total_octets < 1) {
+ return false;
+ }
+ if (data_octets < 1) {
+ return false;
+ }
+ total_octets--;
+ data_octets--;
+ data_bytes = ((uint64_t)data_octets) * 8;
+ total_bytes = ((uint64_t)total_octets) * 8;
+
+ if (data_bytes >= total_bytes) {
+ DEBUG(1,("%s: data_bytes: %" PRIu64 ", total_bytes: %" PRIu64 "\n",
+ __func__, data_bytes, total_bytes));
+ return false;
+ }
+
+ if (total_bytes > (bufsize - offset)) {
+ return false;
+ }
+
+ toc_offset = data_bytes;
+
+ toc_offset = sl_unpack_tag(buf + offset, toc_offset,
+ bufsize - offset, encoding, &toc_tag);
+ if (toc_offset == -1) {
+ return false;
+ }
+
+ if (toc_tag.type != SQ_TYPE_TOC) {
+ DEBUG(1,("%s: unknown tag type %d\n", __func__, toc_tag.type));
+ return false;
+ }
+
+ /*
+ * Check toc_tag.size even though we don't use it when unmarshalling
+ */
+ if (toc_tag.size > MAX_SLQ_TOC) {
+ DEBUG(1,("%s: bad size %zu\n", __func__, toc_tag.size));
+ return false;
+ }
+ if (toc_tag.size > (total_bytes - data_bytes)) {
+ DEBUG(1,("%s: bad size %zu\n", __func__, toc_tag.size));
+ return false;
+ }
+
+ if (toc_tag.count != 0) {
+ DEBUG(1,("%s: bad count %u\n", __func__, toc_tag.count));
+ return false;
+ }
+
+ /*
+ * We already consumed 16 bytes from the buffer (BOM and size
+ * tag), so we start at buf + offset.
+ */
+ result = sl_unpack_loop(query, buf + offset, 0, bufsize - offset,
+ 1, toc_offset, encoding);
+ if (result == -1) {
+ DEBUG(1,("%s: sl_unpack_loop failed\n", __func__));
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/rpc_server/mdssvc/marshalling.h b/source3/rpc_server/mdssvc/marshalling.h
new file mode 100644
index 0000000..ccc8b44
--- /dev/null
+++ b/source3/rpc_server/mdssvc/marshalling.h
@@ -0,0 +1,63 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines
+
+ Copyright (C) Ralph Boehme 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MDSSVC_MARSHALLING_H
+#define _MDSSVC_MARSHALLING_H
+
+#include "dalloc.h"
+#include "libcli/util/ntstatus.h"
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/mdssvc.h"
+
+#define MAX_SL_FRAGMENT_SIZE 0xFFFFF
+#define MAX_MDSCMD_SIZE 0xFFFFFF
+
+/* Can be ored and used as flags */
+#define SL_ENC_LITTLE_ENDIAN 1
+#define SL_ENC_BIG_ENDIAN 2
+#define SL_ENC_UTF_16 4
+
+typedef DALLOC_CTX sl_array_t; /* an array of elements */
+typedef DALLOC_CTX sl_dict_t; /* an array of key/value elements */
+typedef DALLOC_CTX sl_filemeta_t; /* contains one sl_array_t */
+typedef int sl_nil_t; /* a nil element */
+typedef bool sl_bool_t;
+typedef struct timeval sl_time_t;
+typedef struct {
+ char sl_uuid[16];
+} sl_uuid_t;
+typedef struct {
+ uint16_t ca_unkn1;
+ uint32_t ca_context;
+ DALLOC_CTX *ca_cnids;
+} sl_cnids_t; /* an array of CNIDs */
+
+/******************************************************************************
+ * Function declarations
+ ******************************************************************************/
+
+extern NTSTATUS sl_pack_alloc(TALLOC_CTX *mem_ctx,
+ DALLOC_CTX *d,
+ struct mdssvc_blob *b,
+ size_t max_fragment_size);
+
+extern bool sl_unpack(DALLOC_CTX *query, const char *buf, size_t bufsize);
+
+#endif
diff --git a/source3/rpc_server/mdssvc/mdssvc.c b/source3/rpc_server/mdssvc/mdssvc.c
new file mode 100644
index 0000000..23a3088
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc.c
@@ -0,0 +1,1893 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines
+
+ Copyright (C) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/proto.h"
+#include "librpc/gen_ndr/auth.h"
+#include "dbwrap/dbwrap.h"
+#include "lib/util/dlinklist.h"
+#include "lib/util/util_tdb.h"
+#include "lib/util/time_basic.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/security.h"
+#include "mdssvc.h"
+#include "mdssvc_noindex.h"
+#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER
+#include "mdssvc_tracker.h"
+#endif
+#ifdef HAVE_SPOTLIGHT_BACKEND_ES
+#include "mdssvc_es.h"
+#endif
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+struct slrpc_cmd {
+ const char *name;
+ bool (*function)(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query,
+ DALLOC_CTX *reply);
+};
+
+struct slq_destroy_state {
+ struct tevent_context *ev;
+ struct sl_query *slq;
+};
+
+/*
+ * This is a static global because we may be called multiple times and
+ * we only want one mdssvc_ctx per connection to Tracker.
+ *
+ * The client will bind multiple times to the mdssvc RPC service, once
+ * for every tree connect.
+ */
+static struct mdssvc_ctx *mdssvc_ctx = NULL;
+
+/*
+ * If these functions return an error, they hit something like a non
+ * recoverable talloc error. Most errors are dealt with by returning
+ * an error code in the Spotlight RPC reply.
+ */
+static bool slrpc_fetch_properties(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply);
+static bool slrpc_open_query(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply);
+static bool slrpc_fetch_query_results(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply);
+static bool slrpc_store_attributes(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply);
+static bool slrpc_fetch_attributenames(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply);
+static bool slrpc_fetch_attributes(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply);
+static bool slrpc_close_query(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply);
+
+/************************************************
+ * Misc utility functions
+ ************************************************/
+
+/**
+ * Add requested metadata for a query result element
+ *
+ * This could be rewritten to something more sophisticated like
+ * querying metadata from Tracker.
+ *
+ * If path or sp is NULL, simply add nil values for all attributes.
+ **/
+static bool add_filemeta(struct mds_ctx *mds_ctx,
+ sl_array_t *reqinfo,
+ sl_array_t *fm_array,
+ const char *path,
+ const struct stat_ex *sp)
+{
+ sl_array_t *meta;
+ sl_nil_t nil;
+ int i, metacount, result;
+ uint64_t uint64var;
+ sl_time_t sl_time;
+ char *p;
+ const char *attribute;
+ size_t nfc_len;
+ const char *nfc_path = path;
+ size_t nfd_buf_size;
+ char *nfd_path = NULL;
+ char *dest = NULL;
+ size_t dest_remaining;
+ size_t nconv;
+
+ metacount = dalloc_size(reqinfo);
+ if (metacount == 0 || path == NULL || sp == NULL) {
+ result = dalloc_add_copy(fm_array, &nil, sl_nil_t);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+ }
+
+ meta = dalloc_zero(fm_array, sl_array_t);
+ if (meta == NULL) {
+ return false;
+ }
+
+ nfc_len = strlen(nfc_path);
+ /*
+ * Simple heuristic, strlen by two should give enough room for NFC to
+ * NFD conversion.
+ */
+ nfd_buf_size = nfc_len * 2;
+ nfd_path = talloc_array(meta, char, nfd_buf_size);
+ if (nfd_path == NULL) {
+ return false;
+ }
+ dest = nfd_path;
+ dest_remaining = talloc_array_length(dest);
+
+ nconv = smb_iconv(mds_ctx->ic_nfc_to_nfd,
+ &nfc_path,
+ &nfc_len,
+ &dest,
+ &dest_remaining);
+ if (nconv == (size_t)-1) {
+ return false;
+ }
+
+ for (i = 0; i < metacount; i++) {
+ attribute = dalloc_get_object(reqinfo, i);
+ if (attribute == NULL) {
+ return false;
+ }
+ if (strcmp(attribute, "kMDItemDisplayName") == 0
+ || strcmp(attribute, "kMDItemFSName") == 0) {
+ p = strrchr(nfd_path, '/');
+ if (p) {
+ result = dalloc_stradd(meta, p + 1);
+ if (result != 0) {
+ return false;
+ }
+ }
+ } else if (strcmp(attribute, "kMDItemPath") == 0) {
+ result = dalloc_stradd(meta, nfd_path);
+ if (result != 0) {
+ return false;
+ }
+ } else if (strcmp(attribute, "kMDItemFSSize") == 0) {
+ uint64var = sp->st_ex_size;
+ result = dalloc_add_copy(meta, &uint64var, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ } else if (strcmp(attribute, "kMDItemFSOwnerUserID") == 0) {
+ uint64var = sp->st_ex_uid;
+ result = dalloc_add_copy(meta, &uint64var, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ } else if (strcmp(attribute, "kMDItemFSOwnerGroupID") == 0) {
+ uint64var = sp->st_ex_gid;
+ result = dalloc_add_copy(meta, &uint64var, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ } else if (strcmp(attribute, "kMDItemFSContentChangeDate") == 0 ||
+ strcmp(attribute, "kMDItemContentModificationDate") == 0)
+ {
+ sl_time = convert_timespec_to_timeval(sp->st_ex_mtime);
+ result = dalloc_add_copy(meta, &sl_time, sl_time_t);
+ if (result != 0) {
+ return false;
+ }
+ } else {
+ result = dalloc_add_copy(meta, &nil, sl_nil_t);
+ if (result != 0) {
+ return false;
+ }
+ }
+ }
+
+ result = dalloc_add(fm_array, meta, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+}
+
+static int cnid_comp_fn(const void *p1, const void *p2)
+{
+ const uint64_t *cnid1 = p1, *cnid2 = p2;
+ if (*cnid1 == *cnid2) {
+ return 0;
+ }
+ if (*cnid1 < *cnid2) {
+ return -1;
+ }
+ return 1;
+}
+
+/**
+ * Create a sorted copy of a CNID array
+ **/
+static bool sort_cnids(struct sl_query *slq, const DALLOC_CTX *d)
+{
+ uint64_t *cnids = NULL;
+ int i;
+ const void *p;
+
+ cnids = talloc_array(slq, uint64_t, dalloc_size(d));
+ if (cnids == NULL) {
+ return false;
+ }
+
+ for (i = 0; i < dalloc_size(d); i++) {
+ p = dalloc_get_object(d, i);
+ if (p == NULL) {
+ return NULL;
+ }
+ memcpy(&cnids[i], p, sizeof(uint64_t));
+ }
+ qsort(cnids, dalloc_size(d), sizeof(uint64_t), cnid_comp_fn);
+
+ slq->cnids = cnids;
+ slq->cnids_num = dalloc_size(d);
+
+ return true;
+}
+
+/**
+ * Allocate result handle used in the async Tracker cursor result
+ * handler for storing results
+ **/
+static bool create_result_handle(struct sl_query *slq)
+{
+ sl_nil_t nil = 0;
+ struct sl_rslts *query_results;
+ int result;
+
+ if (slq->query_results) {
+ DEBUG(1, ("unexpected existing result handle\n"));
+ return false;
+ }
+
+ query_results = talloc_zero(slq, struct sl_rslts);
+ if (query_results == NULL) {
+ return false;
+ }
+
+ /* CNIDs */
+ query_results->cnids = talloc_zero(query_results, sl_cnids_t);
+ if (query_results->cnids == NULL) {
+ return false;
+ }
+ query_results->cnids->ca_cnids = dalloc_new(query_results->cnids);
+ if (query_results->cnids->ca_cnids == NULL) {
+ return false;
+ }
+
+ query_results->cnids->ca_unkn1 = 0xadd;
+ if (slq->ctx2 > UINT32_MAX) {
+ DEBUG(1,("64bit ctx2 id too large: 0x%jx\n", (uintmax_t)slq->ctx2));
+ return false;
+ }
+ query_results->cnids->ca_context = (uint32_t)slq->ctx2;
+
+ /* FileMeta */
+ query_results->fm_array = dalloc_zero(query_results, sl_array_t);
+ if (query_results->fm_array == NULL) {
+ return false;
+ }
+
+ /* For some reason the list of results always starts with a nil entry */
+ result = dalloc_add_copy(query_results->fm_array, &nil, sl_nil_t);
+ if (result != 0) {
+ return false;
+ }
+
+ slq->query_results = query_results;
+ return true;
+}
+
+static bool add_results(sl_array_t *array, struct sl_query *slq)
+{
+ sl_filemeta_t *fm;
+ uint64_t status;
+ int result;
+ bool ok;
+
+ /*
+ * Taken from network traces against a macOS SMB Spotlight server: if
+ * the search is not finished yet in the backend macOS returns 0x23,
+ * otherwise 0x0.
+ */
+ if (slq->state >= SLQ_STATE_DONE) {
+ status = 0;
+ } else {
+ status = 0x23;
+ }
+
+ /* FileMeta */
+ fm = dalloc_zero(array, sl_filemeta_t);
+ if (fm == NULL) {
+ return false;
+ }
+
+ result = dalloc_add_copy(array, &status, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(array, slq->query_results->cnids, sl_cnids_t);
+ if (result != 0) {
+ return false;
+ }
+ if (slq->query_results->num_results > 0) {
+ result = dalloc_add(fm, slq->query_results->fm_array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+ }
+ result = dalloc_add(array, fm, sl_filemeta_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* This ensure the results get clean up after been sent to the client */
+ talloc_move(array, &slq->query_results);
+
+ ok = create_result_handle(slq);
+ if (!ok) {
+ DEBUG(1, ("couldn't add result handle\n"));
+ slq->state = SLQ_STATE_ERROR;
+ return false;
+ }
+
+ return true;
+}
+
+static const struct slrpc_cmd *slrpc_cmd_by_name(const char *rpccmd)
+{
+ size_t i;
+ static const struct slrpc_cmd cmds[] = {
+ { "fetchPropertiesForContext:", slrpc_fetch_properties},
+ { "openQueryWithParams:forContext:", slrpc_open_query},
+ { "fetchQueryResultsForContext:", slrpc_fetch_query_results},
+ { "storeAttributes:forOIDArray:context:", slrpc_store_attributes},
+ { "fetchAttributeNamesForOIDArray:context:", slrpc_fetch_attributenames},
+ { "fetchAttributes:forOIDArray:context:", slrpc_fetch_attributes},
+ { "fetchAllAttributes:forOIDArray:context:", slrpc_fetch_attributes},
+ { "closeQueryForContext:", slrpc_close_query},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(cmds); i++) {
+ int cmp;
+
+ cmp = strcmp(cmds[i].name, rpccmd);
+ if (cmp == 0) {
+ return &cmds[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Search the list of active queries given their context ids
+ **/
+static struct sl_query *slq_for_ctx(struct mds_ctx *mds_ctx,
+ uint64_t ctx1, uint64_t ctx2)
+{
+ struct sl_query *q;
+
+ for (q = mds_ctx->query_list; q; q = q->next) {
+ if ((q->ctx1 == ctx1) && (q->ctx2 == ctx2)) {
+ return q;
+ }
+ }
+
+ return NULL;
+}
+
+static int slq_destructor_cb(struct sl_query *slq)
+{
+ SLQ_DEBUG(10, slq, "destroying");
+
+ /* Free all entries before freeing the slq handle! */
+ TALLOC_FREE(slq->entries_ctx);
+ TALLOC_FREE(slq->te);
+
+ if (slq->mds_ctx != NULL) {
+ DLIST_REMOVE(slq->mds_ctx->query_list, slq);
+ slq->mds_ctx = NULL;
+ }
+
+ TALLOC_FREE(slq->backend_private);
+
+ return 0;
+}
+
+/**
+ * Remove talloc_refcounted entry from mapping db
+ *
+ * Multiple queries (via the slq handle) may reference a
+ * sl_inode_path_map entry, when the last reference goes away as the
+ * queries are closed and this gets called to remove the entry from
+ * the db.
+ **/
+static int ino_path_map_destr_cb(struct sl_inode_path_map *entry)
+{
+ NTSTATUS status;
+ TDB_DATA key;
+
+ key = make_tdb_data((uint8_t *)&entry->ino, sizeof(entry->ino));
+
+ status = dbwrap_delete(entry->mds_ctx->ino_path_map, key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to delete record: %s\n", nt_errstr(status)));
+ return -1;
+ }
+
+ DBG_DEBUG("deleted [0x%"PRIx64"] [%s]\n", entry->ino, entry->path);
+ return 0;
+}
+
+/**
+ * Add result to inode->path mapping dbwrap rbt db
+ *
+ * This is necessary as a CNID db substitute, ie we need a way to
+ * simulate unique, constant numerical identifiers for paths with an
+ * API that supports mapping from id to path.
+ *
+ * Entries are talloc'ed of the query, using talloc_reference() if
+ * multiple queries returned the same result. That way we can cleanup
+ * entries by calling talloc_free() on the query slq handles.
+ **/
+
+static bool inode_map_add(struct sl_query *slq,
+ uint64_t ino,
+ const char *path,
+ struct stat_ex *st)
+{
+ NTSTATUS status;
+ struct sl_inode_path_map *entry;
+ TDB_DATA key, value;
+ void *p;
+
+ key = make_tdb_data((uint8_t *)&ino, sizeof(ino));
+ status = dbwrap_fetch(slq->mds_ctx->ino_path_map, slq, key, &value);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * We have one db, so when different parallel queries
+ * return the same file, we have to refcount entries
+ * in the db.
+ */
+
+ if (value.dsize != sizeof(void *)) {
+ DEBUG(1, ("invalid dsize\n"));
+ return false;
+ }
+ memcpy(&p, value.dptr, sizeof(p));
+ entry = talloc_get_type_abort(p, struct sl_inode_path_map);
+
+ DEBUG(10, ("map: %s\n", entry->path));
+
+ entry = talloc_reference(slq->entries_ctx, entry);
+ if (entry == NULL) {
+ DEBUG(1, ("talloc_reference failed\n"));
+ return false;
+ }
+ return true;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(1, ("dbwrap_fetch failed %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ entry = talloc_zero(slq->entries_ctx, struct sl_inode_path_map);
+ if (entry == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return false;
+ }
+
+ entry->ino = ino;
+ entry->mds_ctx = slq->mds_ctx;
+ entry->st = *st;
+ entry->path = talloc_strdup(entry, path);
+ if (entry->path == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ TALLOC_FREE(entry);
+ return false;
+ }
+
+ status = dbwrap_store(slq->mds_ctx->ino_path_map, key,
+ make_tdb_data((void *)&entry, sizeof(void *)), 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to store record: %s\n", nt_errstr(status)));
+ TALLOC_FREE(entry);
+ return false;
+ }
+
+ talloc_set_destructor(entry, ino_path_map_destr_cb);
+
+ return true;
+}
+
+bool mds_add_result(struct sl_query *slq, const char *path)
+{
+ struct smb_filename *smb_fname = NULL;
+ const char *relative = NULL;
+ char *fake_path = NULL;
+ struct stat_ex sb;
+ uint64_t ino64;
+ int result;
+ NTSTATUS status;
+ bool sub;
+ bool ok;
+
+ /*
+ * We're in a tevent callback which means in the case of
+ * running as external RPC service we're running as root and
+ * not as the user.
+ */
+ if (!become_authenticated_pipe_user(slq->mds_ctx->pipe_session_info)) {
+ DBG_ERR("can't become authenticated user: %d\n",
+ slq->mds_ctx->uid);
+ smb_panic("can't become authenticated user");
+ }
+
+ if (geteuid() != slq->mds_ctx->uid) {
+ DBG_ERR("uid mismatch: %d/%d\n", geteuid(), slq->mds_ctx->uid);
+ smb_panic("uid mismatch");
+ }
+
+ /*
+ * We've changed identity to the authenticated pipe user, so
+ * any function exit below must ensure we switch back
+ */
+
+ status = synthetic_pathref(talloc_tos(),
+ slq->mds_ctx->conn->cwd_fsp,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("synthetic_pathref [%s]: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ unbecome_authenticated_pipe_user();
+ return true;
+ }
+
+ sb = smb_fname->st;
+
+ status = smbd_check_access_rights_fsp(slq->mds_ctx->conn->cwd_fsp,
+ smb_fname->fsp,
+ false,
+ FILE_READ_DATA);
+ unbecome_authenticated_pipe_user();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ return true;
+ }
+
+ /* Done with smb_fname now. */
+ TALLOC_FREE(smb_fname);
+
+ ino64 = SMB_VFS_FS_FILE_ID(slq->mds_ctx->conn, &sb);
+
+ if (slq->cnids) {
+ bool found;
+
+ /*
+ * Check whether the found element is in the requested
+ * set of IDs. Note that we're faking CNIDs by using
+ * filesystem inode numbers here
+ */
+ found = bsearch(&ino64,
+ slq->cnids,
+ slq->cnids_num,
+ sizeof(uint64_t),
+ cnid_comp_fn);
+ if (!found) {
+ return true;
+ }
+ }
+
+ sub = subdir_of(slq->mds_ctx->spath,
+ slq->mds_ctx->spath_len,
+ path,
+ &relative);
+ if (!sub) {
+ DBG_ERR("[%s] is not inside [%s]\n",
+ path, slq->mds_ctx->spath);
+ slq->state = SLQ_STATE_ERROR;
+ return false;
+ }
+
+ /*
+ * Add inode number and filemeta to result set, this is what
+ * we return as part of the result set of a query
+ */
+ result = dalloc_add_copy(slq->query_results->cnids->ca_cnids,
+ &ino64,
+ uint64_t);
+ if (result != 0) {
+ DBG_ERR("dalloc error\n");
+ slq->state = SLQ_STATE_ERROR;
+ return false;
+ }
+
+ fake_path = talloc_asprintf(slq,
+ "/%s/%s",
+ slq->mds_ctx->sharename,
+ relative);
+ if (fake_path == NULL) {
+ slq->state = SLQ_STATE_ERROR;
+ return false;
+ }
+
+ ok = add_filemeta(slq->mds_ctx,
+ slq->reqinfo,
+ slq->query_results->fm_array,
+ fake_path,
+ &sb);
+ if (!ok) {
+ DBG_ERR("add_filemeta error\n");
+ TALLOC_FREE(fake_path);
+ slq->state = SLQ_STATE_ERROR;
+ return false;
+ }
+
+ ok = inode_map_add(slq, ino64, fake_path, &sb);
+ TALLOC_FREE(fake_path);
+ if (!ok) {
+ DEBUG(1, ("inode_map_add error\n"));
+ slq->state = SLQ_STATE_ERROR;
+ return false;
+ }
+
+ slq->query_results->num_results++;
+ return true;
+}
+
+/***********************************************************
+ * Spotlight RPC functions
+ ***********************************************************/
+
+static bool slrpc_fetch_properties(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply)
+{
+ sl_dict_t *dict;
+ sl_array_t *array;
+ char *s;
+ uint64_t u;
+ sl_bool_t b;
+ sl_uuid_t uuid;
+ int result;
+
+ dict = dalloc_zero(reply, sl_dict_t);
+ if (dict == NULL) {
+ return false;
+ }
+
+ /* kMDSStoreHasPersistentUUID = false */
+ result = dalloc_stradd(dict, "kMDSStoreHasPersistentUUID");
+ if (result != 0) {
+ return false;
+ }
+ b = false;
+ result = dalloc_add_copy(dict, &b, sl_bool_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSStoreIsBackup = false */
+ result = dalloc_stradd(dict, "kMDSStoreIsBackup");
+ if (result != 0) {
+ return false;
+ }
+ b = false;
+ result = dalloc_add_copy(dict, &b, sl_bool_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSStoreUUID = uuid */
+ result = dalloc_stradd(dict, "kMDSStoreUUID");
+ if (result != 0) {
+ return false;
+ }
+ memcpy(uuid.sl_uuid, "fakeuuidfakeuuid", sizeof(uuid.sl_uuid));
+ result = dalloc_add_copy(dict, &uuid, sl_uuid_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSStoreSupportsVolFS = true */
+ result = dalloc_stradd(dict, "kMDSStoreSupportsVolFS");
+ if (result != 0) {
+ return false;
+ }
+ b = true;
+ result = dalloc_add_copy(dict, &b, sl_bool_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSVolumeUUID = uuid */
+ result = dalloc_stradd(dict, "kMDSVolumeUUID");
+ if (result != 0) {
+ return false;
+ }
+ memcpy(uuid.sl_uuid, "fakeuuidfakeuuid", sizeof(uuid.sl_uuid));
+ result = dalloc_add_copy(dict, &uuid, sl_uuid_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSDiskStoreSpindleNumber = 1 (fake) */
+ result = dalloc_stradd(dict, "kMDSDiskStoreSpindleNumber");
+ if (result != 0) {
+ return false;
+ }
+ u = 1;
+ result = dalloc_add_copy(dict, &u, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSDiskStorePolicy = 3 (whatever that means, taken from OS X) */
+ result = dalloc_stradd(dict, "kMDSDiskStorePolicy");
+ if (result != 0) {
+ return false;
+ }
+ u = 3;
+ result = dalloc_add_copy(dict, &u, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSStoreMetaScopes array */
+ result = dalloc_stradd(dict, "kMDSStoreMetaScopes");
+ if (result != 0) {
+ return false;
+ }
+ array = dalloc_zero(dict, sl_array_t);
+ if (array == NULL) {
+ return NULL;
+ }
+ result = dalloc_stradd(array, "kMDQueryScopeComputer");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_stradd(array, "kMDQueryScopeAllIndexed");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_stradd(array, "kMDQueryScopeComputerIndexed");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(dict, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSStoreDevice = 0x1000003 (whatever that means, taken from OS X) */
+ result = dalloc_stradd(dict, "kMDSStoreDevice");
+ if (result != 0) {
+ return false;
+ }
+ u = 0x1000003;
+ result = dalloc_add_copy(dict, &u, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSStoreSupportsTCC = true (whatever that means, taken from OS X) */
+ result = dalloc_stradd(dict, "kMDSStoreSupportsTCC");
+ if (result != 0) {
+ return false;
+ }
+ b = true;
+ result = dalloc_add_copy(dict, &b, sl_bool_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* kMDSStorePathScopes = ["/"] (whatever that means, taken from OS X) */
+ result = dalloc_stradd(dict, "kMDSStorePathScopes");
+ if (result != 0) {
+ return false;
+ }
+ array = dalloc_zero(dict, sl_array_t);
+ if (array == NULL) {
+ return false;
+ }
+ s = talloc_strdup(dict, "/");
+ if (s == NULL) {
+ return false;
+ }
+ talloc_set_name(s, "smb_ucs2_t *");
+ result = dalloc_add(array, s, smb_ucs2_t *);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(dict, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+
+ result = dalloc_add(reply, dict, sl_dict_t);
+ if (result != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static void slq_close_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct sl_query *slq = talloc_get_type_abort(
+ private_data, struct sl_query);
+ struct mds_ctx *mds_ctx = slq->mds_ctx;
+
+ SLQ_DEBUG(10, slq, "expired");
+
+ TALLOC_FREE(slq);
+
+ if (CHECK_DEBUGLVL(10)) {
+ for (slq = mds_ctx->query_list; slq != NULL; slq = slq->next) {
+ SLQ_DEBUG(10, slq, "pending");
+ }
+ }
+}
+
+/**
+ * Translate a fake scope from the client like /sharename/dir
+ * to the real server-side path, replacing the "/sharename" part
+ * with the absolute server-side path of the share.
+ **/
+static bool mdssvc_real_scope(struct sl_query *slq, const char *fake_scope)
+{
+ size_t sname_len = strlen(slq->mds_ctx->sharename);
+ size_t fake_scope_len = strlen(fake_scope);
+
+ if (fake_scope_len < sname_len + 1) {
+ DBG_ERR("Short scope [%s] for share [%s]\n",
+ fake_scope, slq->mds_ctx->sharename);
+ return false;
+ }
+
+ slq->path_scope = talloc_asprintf(slq,
+ "%s%s",
+ slq->mds_ctx->spath,
+ fake_scope + sname_len + 1);
+ if (slq->path_scope == NULL) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Begin a search query
+ **/
+static bool slrpc_open_query(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply)
+{
+ bool ok;
+ uint64_t sl_result;
+ uint64_t *uint64p;
+ DALLOC_CTX *reqinfo;
+ sl_array_t *array, *path_scope;
+ sl_cnids_t *cnids;
+ struct sl_query *slq = NULL;
+ int result;
+ const char *querystring = NULL;
+ size_t querystring_len;
+ char *dest = NULL;
+ size_t dest_remaining;
+ size_t nconv;
+ char *scope = NULL;
+
+ array = dalloc_zero(reply, sl_array_t);
+ if (array == NULL) {
+ return false;
+ }
+
+ /* Allocate and initialize query object */
+ slq = talloc_zero(mds_ctx, struct sl_query);
+ if (slq == NULL) {
+ return false;
+ }
+ slq->entries_ctx = talloc_named_const(slq, 0, "struct sl_query.entries_ctx");
+ if (slq->entries_ctx == NULL) {
+ TALLOC_FREE(slq);
+ return false;
+ }
+ talloc_set_destructor(slq, slq_destructor_cb);
+ slq->state = SLQ_STATE_NEW;
+ slq->mds_ctx = mds_ctx;
+
+ slq->last_used = timeval_current();
+ slq->start_time = slq->last_used;
+ slq->expire_time = timeval_add(&slq->last_used, MAX_SL_RUNTIME, 0);
+ slq->te = tevent_add_timer(global_event_context(), slq,
+ slq->expire_time, slq_close_timer, slq);
+ if (slq->te == NULL) {
+ DEBUG(1, ("tevent_add_timer failed\n"));
+ goto error;
+ }
+
+ querystring = dalloc_value_for_key(query, "DALLOC_CTX", 0,
+ "DALLOC_CTX", 1,
+ "kMDQueryString",
+ "char *");
+ if (querystring == NULL) {
+ DEBUG(1, ("missing kMDQueryString\n"));
+ goto error;
+ }
+
+ querystring_len = talloc_array_length(querystring);
+
+ slq->query_string = talloc_array(slq, char, querystring_len);
+ if (slq->query_string == NULL) {
+ DEBUG(1, ("out of memory\n"));
+ goto error;
+ }
+ dest = slq->query_string;
+ dest_remaining = talloc_array_length(dest);
+
+ nconv = smb_iconv(mds_ctx->ic_nfd_to_nfc,
+ &querystring,
+ &querystring_len,
+ &dest,
+ &dest_remaining);
+ if (nconv == (size_t)-1) {
+ DBG_ERR("smb_iconv failed for: %s\n", querystring);
+ return false;
+ }
+
+ uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
+ "uint64_t", 1);
+ if (uint64p == NULL) {
+ goto error;
+ }
+ slq->ctx1 = *uint64p;
+ uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
+ "uint64_t", 2);
+ if (uint64p == NULL) {
+ goto error;
+ }
+ slq->ctx2 = *uint64p;
+
+ path_scope = dalloc_value_for_key(query, "DALLOC_CTX", 0,
+ "DALLOC_CTX", 1,
+ "kMDScopeArray",
+ "sl_array_t");
+ if (path_scope == NULL) {
+ DBG_ERR("missing kMDScopeArray\n");
+ goto error;
+ }
+
+ scope = dalloc_get(path_scope, "char *", 0);
+ if (scope == NULL) {
+ scope = dalloc_get(path_scope,
+ "DALLOC_CTX", 0,
+ "char *", 0);
+ }
+ if (scope == NULL) {
+ DBG_ERR("Failed to parse kMDScopeArray\n");
+ goto error;
+ }
+
+ ok = mdssvc_real_scope(slq, scope);
+ if (!ok) {
+ goto error;
+ }
+
+ reqinfo = dalloc_value_for_key(query, "DALLOC_CTX", 0,
+ "DALLOC_CTX", 1,
+ "kMDAttributeArray",
+ "sl_array_t");
+ if (reqinfo == NULL) {
+ DBG_ERR("missing kMDAttributeArray\n");
+ goto error;
+ }
+
+ slq->reqinfo = talloc_steal(slq, reqinfo);
+ DEBUG(10, ("requested attributes: %s", dalloc_dump(reqinfo, 0)));
+
+ cnids = dalloc_value_for_key(query, "DALLOC_CTX", 0,
+ "DALLOC_CTX", 1,
+ "kMDQueryItemArray",
+ "sl_array_t");
+ if (cnids) {
+ ok = sort_cnids(slq, cnids->ca_cnids);
+ if (!ok) {
+ goto error;
+ }
+ }
+
+ ok = create_result_handle(slq);
+ if (!ok) {
+ DEBUG(1, ("create_result_handle error\n"));
+ slq->state = SLQ_STATE_ERROR;
+ goto error;
+ }
+
+ SLQ_DEBUG(10, slq, "new");
+
+ DLIST_ADD(mds_ctx->query_list, slq);
+
+ ok = mds_ctx->backend->search_start(slq);
+ if (!ok) {
+ DBG_ERR("backend search_start failed\n");
+ goto error;
+ }
+
+ sl_result = 0;
+ result = dalloc_add_copy(array, &sl_result, uint64_t);
+ if (result != 0) {
+ goto error;
+ }
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ goto error;
+ }
+ return true;
+
+error:
+ sl_result = UINT64_MAX;
+ TALLOC_FREE(slq);
+ result = dalloc_add_copy(array, &sl_result, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Fetch results of a query
+ **/
+static bool slrpc_fetch_query_results(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query,
+ DALLOC_CTX *reply)
+{
+ bool ok;
+ struct sl_query *slq = NULL;
+ uint64_t *uint64p, ctx1, ctx2;
+ uint64_t status;
+ sl_array_t *array;
+ int result;
+
+ array = dalloc_zero(reply, sl_array_t);
+ if (array == NULL) {
+ return false;
+ }
+
+ /* Get query for context */
+ uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
+ "uint64_t", 1);
+ if (uint64p == NULL) {
+ goto error;
+ }
+ ctx1 = *uint64p;
+
+ uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
+ "uint64_t", 2);
+ if (uint64p == NULL) {
+ goto error;
+ }
+ ctx2 = *uint64p;
+
+ slq = slq_for_ctx(mds_ctx, ctx1, ctx2);
+ if (slq == NULL) {
+ DEBUG(1, ("bad context: [0x%jx,0x%jx]\n",
+ (uintmax_t)ctx1, (uintmax_t)ctx2));
+ goto error;
+ }
+
+ TALLOC_FREE(slq->te);
+ slq->last_used = timeval_current();
+ slq->expire_time = timeval_add(&slq->last_used, MAX_SL_RUNTIME, 0);
+ slq->te = tevent_add_timer(global_event_context(), slq,
+ slq->expire_time, slq_close_timer, slq);
+ if (slq->te == NULL) {
+ DEBUG(1, ("tevent_add_timer failed\n"));
+ goto error;
+ }
+
+ SLQ_DEBUG(10, slq, "fetch");
+
+ switch (slq->state) {
+ case SLQ_STATE_RUNNING:
+ case SLQ_STATE_RESULTS:
+ case SLQ_STATE_FULL:
+ case SLQ_STATE_DONE:
+ ok = add_results(array, slq);
+ if (!ok) {
+ DEBUG(1, ("error adding results\n"));
+ goto error;
+ }
+ if (slq->state == SLQ_STATE_FULL) {
+ slq->state = SLQ_STATE_RUNNING;
+ slq->mds_ctx->backend->search_cont(slq);
+ }
+ break;
+
+ case SLQ_STATE_ERROR:
+ DEBUG(1, ("query in error state\n"));
+ goto error;
+
+ default:
+ DEBUG(1, ("unexpected query state %d\n", slq->state));
+ goto error;
+ }
+
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ goto error;
+ }
+ return true;
+
+error:
+ status = UINT64_MAX;
+ TALLOC_FREE(slq);
+ result = dalloc_add_copy(array, &status, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Store metadata attributes for a CNID
+ **/
+static bool slrpc_store_attributes(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply)
+{
+ uint64_t sl_result;
+ sl_array_t *array;
+ int result;
+
+ array = dalloc_zero(reply, sl_array_t);
+ if (array == NULL) {
+ return false;
+ }
+
+ /*
+ * FIXME: not implemented. Used by the client for eg setting
+ * the modification date of the shared directory which clients
+ * poll indicating changes on the share and cause the client
+ * to refresh view.
+ */
+
+ sl_result = 0;
+ result = dalloc_add_copy(array, &sl_result, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Fetch supported metadata attributes for a CNID
+ **/
+static bool slrpc_fetch_attributenames(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query,
+ DALLOC_CTX *reply)
+{
+ uint64_t id;
+ sl_cnids_t *cnids;
+ sl_array_t *array;
+ uint64_t sl_result;
+ sl_cnids_t *replycnids;
+ sl_array_t *mdattrs;
+ sl_filemeta_t *fmeta;
+ int result;
+ void *p;
+
+ cnids = dalloc_get(query, "DALLOC_CTX", 0, "sl_cnids_t", 1);
+ if (cnids == NULL) {
+ return false;
+ }
+
+ p = dalloc_get_object(cnids->ca_cnids, 0);
+ if (p == NULL) {
+ return NULL;
+ }
+ memcpy(&id, p, sizeof(uint64_t));
+
+ /* Result array */
+ array = dalloc_zero(reply, sl_array_t);
+ if (array == NULL) {
+ return false;
+ }
+
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* Return result value 0 */
+ sl_result = 0;
+ result = dalloc_add_copy(array, &sl_result, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /* Return CNID array */
+ replycnids = talloc_zero(reply, sl_cnids_t);
+ if (replycnids == NULL) {
+ return false;
+ }
+
+ replycnids->ca_cnids = dalloc_new(cnids);
+ if (replycnids->ca_cnids == NULL) {
+ return false;
+ }
+
+ replycnids->ca_unkn1 = 0xfec;
+ replycnids->ca_context = cnids->ca_context;
+ result = dalloc_add_copy(replycnids->ca_cnids, &id, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(array, replycnids, sl_cnids_t);
+ if (result != 0) {
+ return false;
+ }
+
+ /*
+ * FIXME: this should return the real attributes from all
+ * known metadata sources (Tracker and filesystem)
+ */
+ mdattrs = dalloc_zero(reply, sl_array_t);
+ if (mdattrs == NULL) {
+ return false;
+ }
+
+ result = dalloc_stradd(mdattrs, "kMDItemFSName");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_stradd(mdattrs, "kMDItemDisplayName");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_stradd(mdattrs, "kMDItemFSSize");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_stradd(mdattrs, "kMDItemFSOwnerUserID");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_stradd(mdattrs, "kMDItemFSOwnerGroupID");
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_stradd(mdattrs, "kMDItemFSContentChangeDate");
+ if (result != 0) {
+ return false;
+ }
+
+ fmeta = dalloc_zero(reply, sl_filemeta_t);
+ if (fmeta == NULL) {
+ return false;
+ }
+ result = dalloc_add(fmeta, mdattrs, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(array, fmeta, sl_filemeta_t);
+ if (result != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Fetch metadata attribute values for a CNID
+ **/
+static bool slrpc_fetch_attributes(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply)
+{
+ int result;
+ bool ok;
+ sl_array_t *array;
+ sl_cnids_t *cnids;
+ sl_cnids_t *replycnids;
+ sl_array_t *reqinfo;
+ uint64_t ino;
+ uint64_t sl_result;
+ sl_filemeta_t *fm;
+ sl_array_t *fm_array;
+ sl_nil_t nil;
+ char *path = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct stat_ex *sp = NULL;
+ struct sl_inode_path_map *elem = NULL;
+ void *p;
+ TDB_DATA val = tdb_null;
+ NTSTATUS status;
+
+ array = dalloc_zero(reply, sl_array_t);
+ if (array == NULL) {
+ return false;
+ }
+ replycnids = talloc_zero(reply, sl_cnids_t);
+ if (replycnids == NULL) {
+ goto error;
+ }
+ replycnids->ca_cnids = dalloc_new(replycnids);
+ if (replycnids->ca_cnids == NULL) {
+ goto error;
+ }
+ fm = dalloc_zero(array, sl_filemeta_t);
+ if (fm == NULL) {
+ goto error;
+ }
+ fm_array = dalloc_zero(fm, sl_array_t);
+ if (fm_array == NULL) {
+ goto error;
+ }
+ /* For some reason the list of results always starts with a nil entry */
+ result = dalloc_add_copy(fm_array, &nil, sl_nil_t);
+ if (result == -1) {
+ goto error;
+ }
+
+ reqinfo = dalloc_get(query, "DALLOC_CTX", 0, "sl_array_t", 1);
+ if (reqinfo == NULL) {
+ goto error;
+ }
+
+ cnids = dalloc_get(query, "DALLOC_CTX", 0, "sl_cnids_t", 2);
+ if (cnids == NULL) {
+ goto error;
+ }
+ p = dalloc_get_object(cnids->ca_cnids, 0);
+ if (p == NULL) {
+ goto error;
+ }
+ memcpy(&ino, p, sizeof(uint64_t));
+
+ replycnids->ca_unkn1 = 0xfec;
+ replycnids->ca_context = cnids->ca_context;
+ result = dalloc_add_copy(replycnids->ca_cnids, &ino, uint64_t);
+ if (result != 0) {
+ goto error;
+ }
+
+ status = dbwrap_fetch(mds_ctx->ino_path_map, reply,
+ make_tdb_data((void*)&ino, sizeof(uint64_t)),
+ &val);
+ if (NT_STATUS_IS_OK(status)) {
+ if (val.dsize != sizeof(p)) {
+ DBG_ERR("invalid record pointer size: %zd\n", val.dsize);
+ TALLOC_FREE(val.dptr);
+ goto error;
+ }
+
+ memcpy(&p, val.dptr, sizeof(p));
+ elem = talloc_get_type_abort(p, struct sl_inode_path_map);
+ path = elem->path;
+
+ sp = &elem->st;
+ }
+
+ ok = add_filemeta(mds_ctx, reqinfo, fm_array, path, sp);
+ if (!ok) {
+ goto error;
+ }
+
+ sl_result = 0;
+ result = dalloc_add_copy(array, &sl_result, uint64_t);
+ if (result != 0) {
+ goto error;
+ }
+ result = dalloc_add(array, replycnids, sl_cnids_t);
+ if (result != 0) {
+ goto error;
+ }
+ result = dalloc_add(fm, fm_array, sl_array_t);
+ if (result != 0) {
+ goto error;
+ }
+ result = dalloc_add(array, fm, sl_filemeta_t);
+ if (result != 0) {
+ goto error;
+ }
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ goto error;
+ }
+
+ TALLOC_FREE(smb_fname);
+ return true;
+
+error:
+
+ TALLOC_FREE(smb_fname);
+ sl_result = UINT64_MAX;
+ result = dalloc_add_copy(array, &sl_result, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Close a query
+ **/
+static bool slrpc_close_query(struct mds_ctx *mds_ctx,
+ const DALLOC_CTX *query, DALLOC_CTX *reply)
+{
+ struct sl_query *slq = NULL;
+ uint64_t *uint64p, ctx1, ctx2;
+ sl_array_t *array;
+ uint64_t sl_res;
+ int result;
+
+ array = dalloc_zero(reply, sl_array_t);
+ if (array == NULL) {
+ return false;
+ }
+
+ /* Context */
+ uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
+ "uint64_t", 1);
+ if (uint64p == NULL) {
+ goto done;
+ }
+ ctx1 = *uint64p;
+
+ uint64p = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
+ "uint64_t", 2);
+ if (uint64p == NULL) {
+ goto done;
+ }
+ ctx2 = *uint64p;
+
+ /* Get query for context and free it */
+ slq = slq_for_ctx(mds_ctx, ctx1, ctx2);
+ if (slq == NULL) {
+ DEBUG(1, ("bad context: [0x%jx,0x%jx]\n",
+ (uintmax_t)ctx1, (uintmax_t)ctx2));
+ goto done;
+ }
+
+ SLQ_DEBUG(10, slq, "close");
+ TALLOC_FREE(slq);
+
+done:
+ sl_res = UINT64_MAX;
+ result = dalloc_add_copy(array, &sl_res, uint64_t);
+ if (result != 0) {
+ return false;
+ }
+ result = dalloc_add(reply, array, sl_array_t);
+ if (result != 0) {
+ return false;
+ }
+ return true;
+}
+
+static struct mdssvc_ctx *mdssvc_init(struct tevent_context *ev)
+{
+ bool ok;
+
+ if (mdssvc_ctx != NULL) {
+ return mdssvc_ctx;
+ }
+
+ mdssvc_ctx = talloc_zero(ev, struct mdssvc_ctx);
+ if (mdssvc_ctx == NULL) {
+ return NULL;
+ }
+
+ mdssvc_ctx->ev_ctx = ev;
+
+ ok = mdsscv_backend_noindex.init(mdssvc_ctx);
+ if (!ok) {
+ DBG_ERR("backend init failed\n");
+ TALLOC_FREE(mdssvc_ctx);
+ return NULL;
+ }
+
+#ifdef HAVE_SPOTLIGHT_BACKEND_ES
+ ok = mdsscv_backend_es.init(mdssvc_ctx);
+ if (!ok) {
+ DBG_ERR("backend init failed\n");
+ TALLOC_FREE(mdssvc_ctx);
+ return NULL;
+ }
+#endif
+
+#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER
+ ok = mdsscv_backend_tracker.init(mdssvc_ctx);
+ if (!ok) {
+ DBG_ERR("backend init failed\n");
+ TALLOC_FREE(mdssvc_ctx);
+ return NULL;
+ }
+#endif
+
+ return mdssvc_ctx;
+}
+
+/**
+ * Init callbacks at startup
+ *
+ * This gets typically called in the main parent smbd which means we can't
+ * initialize our global state here.
+ **/
+bool mds_init(struct messaging_context *msg_ctx)
+{
+ return true;
+}
+
+bool mds_shutdown(void)
+{
+ bool ok;
+
+ if (mdssvc_ctx == NULL) {
+ return false;
+ }
+
+ ok = mdsscv_backend_noindex.shutdown(mdssvc_ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+#ifdef HAVE_SPOTLIGHT_BACKEND_ES
+ ok = mdsscv_backend_es.shutdown(mdssvc_ctx);
+ if (!ok) {
+ goto fail;
+ }
+#endif
+
+#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER
+ ok = mdsscv_backend_tracker.shutdown(mdssvc_ctx);
+ if (!ok) {
+ goto fail;
+ }
+#endif
+
+ ok = true;
+fail:
+ TALLOC_FREE(mdssvc_ctx);
+ return ok;
+}
+
+/**
+ * Tear down connections and free all resources
+ **/
+static int mds_ctx_destructor_cb(struct mds_ctx *mds_ctx)
+{
+ /*
+ * We need to free query_list before ino_path_map
+ */
+ while (mds_ctx->query_list != NULL) {
+ /*
+ * slq destructor removes element from list.
+ * Don't use TALLOC_FREE()!
+ */
+ talloc_free(mds_ctx->query_list);
+ }
+ TALLOC_FREE(mds_ctx->ino_path_map);
+
+ if (mds_ctx->conn != NULL) {
+ SMB_VFS_DISCONNECT(mds_ctx->conn);
+ conn_free(mds_ctx->conn);
+ }
+
+ ZERO_STRUCTP(mds_ctx);
+
+ return 0;
+}
+
+/**
+ * Initialise a context per RPC bind
+ *
+ * This ends up being called for every tcon, because the client does a
+ * RPC bind for every tcon, so this is actually a per tcon context.
+ **/
+NTSTATUS mds_init_ctx(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ int snum,
+ const char *sharename,
+ const char *path,
+ struct mds_ctx **_mds_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smb_filename conn_basedir;
+ struct mds_ctx *mds_ctx;
+ int backend;
+ int ret;
+ bool ok;
+ smb_iconv_t iconv_hnd = (smb_iconv_t)-1;
+ NTSTATUS status;
+
+ if (!lp_spotlight(snum)) {
+ return NT_STATUS_WRONG_VOLUME;
+ }
+
+ mds_ctx = talloc_zero(mem_ctx, struct mds_ctx);
+ if (mds_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(mds_ctx, mds_ctx_destructor_cb);
+
+ mds_ctx->mdssvc_ctx = mdssvc_init(ev);
+ if (mds_ctx->mdssvc_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ backend = lp_spotlight_backend(snum);
+ switch (backend) {
+ case SPOTLIGHT_BACKEND_NOINDEX:
+ mds_ctx->backend = &mdsscv_backend_noindex;
+ break;
+
+#ifdef HAVE_SPOTLIGHT_BACKEND_ES
+ case SPOTLIGHT_BACKEND_ES:
+ mds_ctx->backend = &mdsscv_backend_es;
+ break;
+#endif
+
+#ifdef HAVE_SPOTLIGHT_BACKEND_TRACKER
+ case SPOTLIGHT_BACKEND_TRACKER:
+ mds_ctx->backend = &mdsscv_backend_tracker;
+ break;
+#endif
+ default:
+ DBG_ERR("Unknown backend %d\n", backend);
+ TALLOC_FREE(mdssvc_ctx);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ iconv_hnd = smb_iconv_open_ex(mds_ctx,
+ "UTF8-NFD",
+ "UTF8-NFC",
+ false);
+ if (iconv_hnd == (smb_iconv_t)-1) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ mds_ctx->ic_nfc_to_nfd = iconv_hnd;
+
+ iconv_hnd = smb_iconv_open_ex(mds_ctx,
+ "UTF8-NFC",
+ "UTF8-NFD",
+ false);
+ if (iconv_hnd == (smb_iconv_t)-1) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ mds_ctx->ic_nfd_to_nfc = iconv_hnd;
+
+ mds_ctx->sharename = talloc_strdup(mds_ctx, sharename);
+ if (mds_ctx->sharename == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ mds_ctx->spath = talloc_strdup(mds_ctx, path);
+ if (mds_ctx->spath == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ mds_ctx->spath_len = strlen(path);
+
+ mds_ctx->snum = snum;
+ mds_ctx->pipe_session_info = session_info;
+
+ if (session_info->security_token->num_sids < 1) {
+ status = NT_STATUS_BAD_LOGON_SESSION_STATE;
+ goto error;
+ }
+ sid_copy(&mds_ctx->sid, &session_info->security_token->sids[0]);
+ mds_ctx->uid = session_info->unix_token->uid;
+
+ mds_ctx->ino_path_map = db_open_rbt(mds_ctx);
+ if (mds_ctx->ino_path_map == NULL) {
+ DEBUG(1,("open inode map db failed\n"));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ status = create_conn_struct_cwd(mds_ctx,
+ ev,
+ msg_ctx,
+ session_info,
+ snum,
+ lp_path(talloc_tos(), lp_sub, snum),
+ &mds_ctx->conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to create conn for vfs: %s\n",
+ nt_errstr(status));
+ goto error;
+ }
+
+ conn_basedir = (struct smb_filename) {
+ .base_name = mds_ctx->conn->connectpath,
+ };
+
+ ret = vfs_ChDir(mds_ctx->conn, &conn_basedir);
+ if (ret != 0) {
+ DBG_ERR("vfs_ChDir [%s] failed: %s\n",
+ conn_basedir.base_name, strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ goto error;
+ }
+
+ ok = mds_ctx->backend->connect(mds_ctx);
+ if (!ok) {
+ DBG_ERR("backend connect failed\n");
+ status = NT_STATUS_CONNECTION_RESET;
+ goto error;
+ }
+
+ *_mds_ctx = mds_ctx;
+ return NT_STATUS_OK;
+
+error:
+ if (mds_ctx->ic_nfc_to_nfd != NULL) {
+ smb_iconv_close(mds_ctx->ic_nfc_to_nfd);
+ }
+ if (mds_ctx->ic_nfd_to_nfc != NULL) {
+ smb_iconv_close(mds_ctx->ic_nfd_to_nfc);
+ }
+
+ TALLOC_FREE(mds_ctx);
+ return status;
+}
+
+/**
+ * Dispatch a Spotlight RPC command
+ **/
+bool mds_dispatch(struct mds_ctx *mds_ctx,
+ struct mdssvc_blob *request_blob,
+ struct mdssvc_blob *response_blob,
+ size_t max_fragment_size)
+{
+ bool ok;
+ int ret;
+ DALLOC_CTX *query = NULL;
+ DALLOC_CTX *reply = NULL;
+ char *rpccmd;
+ const struct slrpc_cmd *slcmd;
+ const struct smb_filename conn_basedir = {
+ .base_name = mds_ctx->conn->connectpath,
+ };
+ NTSTATUS status;
+
+ if (CHECK_DEBUGLVL(10)) {
+ const struct sl_query *slq;
+
+ for (slq = mds_ctx->query_list; slq != NULL; slq = slq->next) {
+ SLQ_DEBUG(10, slq, "pending");
+ }
+ }
+
+ response_blob->length = 0;
+
+ DEBUG(10, ("share path: %s\n", mds_ctx->spath));
+
+ query = dalloc_new(mds_ctx);
+ if (query == NULL) {
+ ok = false;
+ goto cleanup;
+ }
+ reply = dalloc_new(mds_ctx);
+ if (reply == NULL) {
+ ok = false;
+ goto cleanup;
+ }
+
+ ok = sl_unpack(query, (char *)request_blob->spotlight_blob,
+ request_blob->length);
+ if (!ok) {
+ DEBUG(1, ("error unpacking Spotlight RPC blob\n"));
+ goto cleanup;
+ }
+
+ DEBUG(5, ("%s", dalloc_dump(query, 0)));
+
+ rpccmd = dalloc_get(query, "DALLOC_CTX", 0, "DALLOC_CTX", 0,
+ "char *", 0);
+ if (rpccmd == NULL) {
+ DEBUG(1, ("missing primary Spotlight RPC command\n"));
+ ok = false;
+ goto cleanup;
+ }
+
+ DEBUG(10, ("Spotlight RPC cmd: %s\n", rpccmd));
+
+ slcmd = slrpc_cmd_by_name(rpccmd);
+ if (slcmd == NULL) {
+ DEBUG(1, ("unsupported primary Spotlight RPC command %s\n",
+ rpccmd));
+ ok = false;
+ goto cleanup;
+ }
+
+ ret = vfs_ChDir(mds_ctx->conn, &conn_basedir);
+ if (ret != 0) {
+ DBG_ERR("vfs_ChDir [%s] failed: %s\n",
+ conn_basedir.base_name, strerror(errno));
+ ok = false;
+ goto cleanup;
+ }
+
+ ok = slcmd->function(mds_ctx, query, reply);
+ if (!ok) {
+ goto cleanup;
+ }
+
+ DBG_DEBUG("%s", dalloc_dump(reply, 0));
+
+ status = sl_pack_alloc(response_blob,
+ reply,
+ response_blob,
+ max_fragment_size);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("sl_pack_alloc() failed\n");
+ goto cleanup;
+ }
+
+cleanup:
+ talloc_free(query);
+ talloc_free(reply);
+ return ok;
+}
diff --git a/source3/rpc_server/mdssvc/mdssvc.h b/source3/rpc_server/mdssvc/mdssvc.h
new file mode 100644
index 0000000..6d4e684
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc.h
@@ -0,0 +1,168 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines
+
+ Copyright (C) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MDSSVC_H
+#define _MDSSVC_H
+
+#include "dalloc.h"
+#include "marshalling.h"
+#include "lib/util/dlinklist.h"
+#include "librpc/gen_ndr/mdssvc.h"
+
+/*
+ * glib uses TRUE and FALSE which was redefined by "includes.h" to be
+ * unusable, undefine so glib can establish its own working
+ * replacement.
+ */
+#undef TRUE
+#undef FALSE
+
+#define MAX_SL_RESULTS 100
+#define SL_PAGESIZE 50
+#define MAX_SL_RUNTIME 30
+#define MDS_TRACKER_ASYNC_TIMEOUT_MS 250
+
+#define SLQ_DEBUG(lvl, _slq, state) do { if (CHECK_DEBUGLVL(lvl)) { \
+ const struct sl_query *__slq = _slq; \
+ struct timeval_buf start_buf; \
+ const char *start; \
+ struct timeval_buf last_used_buf; \
+ const char *last_used; \
+ struct timeval_buf expire_buf; \
+ const char *expire; \
+ start = timeval_str_buf(&__slq->start_time, false, \
+ true, &start_buf); \
+ last_used = timeval_str_buf(&__slq->last_used, false, \
+ true, &last_used_buf); \
+ expire = timeval_str_buf(&__slq->expire_time, false, \
+ true, &expire_buf); \
+ DEBUG(lvl,("%s slq[0x%jx,0x%jx], start: %s, last_used: %s, " \
+ "expires: %s, query: '%s'\n", state, \
+ (uintmax_t)__slq->ctx1, (uintmax_t)__slq->ctx2, \
+ start, last_used, expire, __slq->query_string)); \
+}} while(0)
+
+/******************************************************************************
+ * Some helper stuff dealing with queries
+ ******************************************************************************/
+
+/* query state */
+typedef enum {
+ SLQ_STATE_NEW, /* Query received from client */
+ SLQ_STATE_RUNNING, /* Query dispatched to Tracker */
+ SLQ_STATE_RESULTS, /* Async Tracker query read */
+ SLQ_STATE_FULL, /* the max amount of result has been queued */
+ SLQ_STATE_DONE, /* Got all results from Tracker */
+ SLQ_STATE_END, /* Query results returned to client */
+ SLQ_STATE_ERROR /* an error happened somewhere */
+} slq_state_t;
+
+/* query structure */
+struct sl_query {
+ struct sl_query *prev, *next; /* list pointers */
+ struct mds_ctx *mds_ctx; /* context handle */
+ void *backend_private; /* search backend private data */
+ slq_state_t state; /* query state */
+ struct timeval start_time; /* Query start time */
+ struct timeval last_used; /* Time of last result fetch */
+ struct timeval expire_time; /* Query expiration time */
+ struct tevent_timer *te; /* query timeout */
+ uint64_t ctx1; /* client context 1 */
+ uint64_t ctx2; /* client context 2 */
+ sl_array_t *reqinfo; /* array with requested metadata */
+ char *query_string; /* the Spotlight query string */
+ uint64_t *cnids; /* restrict query to these CNIDs */
+ size_t cnids_num; /* Size of slq_cnids array */
+ const char *path_scope; /* path to directory to search */
+ struct sl_rslts *query_results; /* query results */
+ TALLOC_CTX *entries_ctx; /* talloc parent of the search results */
+};
+
+struct sl_rslts {
+ int num_results;
+ sl_cnids_t *cnids;
+ sl_array_t *fm_array;
+};
+
+struct sl_inode_path_map {
+ struct mds_ctx *mds_ctx;
+ uint64_t ino;
+ char *path;
+ struct stat_ex st;
+};
+
+/* Per process state */
+struct mdssvc_ctx {
+ struct tevent_context *ev_ctx;
+ void *backend_private;
+};
+
+/* Per tree connect state */
+struct mds_ctx {
+ struct mdssvc_backend *backend;
+ struct mdssvc_ctx *mdssvc_ctx;
+ void *backend_private;
+ struct auth_session_info *pipe_session_info;
+ struct dom_sid sid;
+ uid_t uid;
+ smb_iconv_t ic_nfc_to_nfd;
+ smb_iconv_t ic_nfd_to_nfc;
+ int snum;
+ const char *sharename;
+ const char *spath;
+ size_t spath_len;
+ struct connection_struct *conn;
+ struct sl_query *query_list; /* list of active queries */
+ struct db_context *ino_path_map; /* dbwrap rbt for storing inode->path mappings */
+};
+
+struct mdssvc_backend {
+ bool (*init)(struct mdssvc_ctx *mdssvc_ctx);
+ bool (*connect)(struct mds_ctx *mds_ctx);
+ bool (*search_map)(struct sl_query *slq);
+ bool (*search_start)(struct sl_query *slq);
+ bool (*search_cont)(struct sl_query *slq);
+ bool (*shutdown)(struct mdssvc_ctx *mdssvc_ctx);
+};
+
+/******************************************************************************
+ * Function declarations
+ ******************************************************************************/
+
+/*
+ * mdssvc.c
+ */
+extern bool mds_init(struct messaging_context *msg_ctx);
+extern bool mds_shutdown(void);
+NTSTATUS mds_init_ctx(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ int snum,
+ const char *sharename,
+ const char *path,
+ struct mds_ctx **_mds_ctx);
+extern bool mds_dispatch(struct mds_ctx *mds_ctx,
+ struct mdssvc_blob *request_blob,
+ struct mdssvc_blob *response_blob,
+ size_t max_fragment_size);
+bool mds_add_result(struct sl_query *slq, const char *path);
+
+#endif /* _MDSSVC_H */
diff --git a/source3/rpc_server/mdssvc/mdssvc_es.c b/source3/rpc_server/mdssvc/mdssvc_es.c
new file mode 100644
index 0000000..8460b48
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc_es.c
@@ -0,0 +1,865 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / ES backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/time_basic.h"
+#include "lib/tls/tls.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/http/http.h"
+#include "lib/util/tevent_unix.h"
+#include "credentials.h"
+#include "mdssvc.h"
+#include "mdssvc_es.h"
+#include "rpc_server/mdssvc/es_parser.tab.h"
+
+#include <jansson.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#define MDSSVC_ELASTIC_QUERY_TEMPLATE \
+ "{" \
+ " \"from\": %zu," \
+ " \"size\": %zu," \
+ " \"_source\": [%s]," \
+ " \"query\": {" \
+ " \"query_string\": {" \
+ " \"query\": \"%s\"" \
+ " }" \
+ " }" \
+ "}"
+
+#define MDSSVC_ELASTIC_SOURCES \
+ "\"path.real\""
+
+static bool mdssvc_es_init(struct mdssvc_ctx *mdssvc_ctx)
+{
+ struct mdssvc_es_ctx *mdssvc_es_ctx = NULL;
+ json_error_t json_error;
+ char *default_path = NULL;
+ const char *path = NULL;
+
+ mdssvc_es_ctx = talloc_zero(mdssvc_ctx, struct mdssvc_es_ctx);
+ if (mdssvc_es_ctx == NULL) {
+ return false;
+ }
+ mdssvc_es_ctx->mdssvc_ctx = mdssvc_ctx;
+
+ mdssvc_es_ctx->creds = cli_credentials_init_anon(mdssvc_es_ctx);
+ if (mdssvc_es_ctx->creds == NULL) {
+ TALLOC_FREE(mdssvc_es_ctx);
+ return false;
+ }
+
+ default_path = talloc_asprintf(
+ mdssvc_es_ctx,
+ "%s/mdssvc/elasticsearch_mappings.json",
+ get_dyn_SAMBA_DATADIR());
+ if (default_path == NULL) {
+ TALLOC_FREE(mdssvc_es_ctx);
+ return false;
+ }
+
+ path = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+ "elasticsearch",
+ "mappings",
+ default_path);
+ if (path == NULL) {
+ TALLOC_FREE(mdssvc_es_ctx);
+ return false;
+ }
+
+ mdssvc_es_ctx->mappings = json_load_file(path, 0, &json_error);
+ if (mdssvc_es_ctx->mappings == NULL) {
+ DBG_ERR("Opening mapping file [%s] failed: %s\n",
+ path, json_error.text);
+ TALLOC_FREE(mdssvc_es_ctx);
+ return false;
+ }
+ TALLOC_FREE(default_path);
+
+ mdssvc_ctx->backend_private = mdssvc_es_ctx;
+ return true;
+}
+
+static bool mdssvc_es_shutdown(struct mdssvc_ctx *mdssvc_ctx)
+{
+ return true;
+}
+
+static struct tevent_req *mds_es_connect_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mds_es_ctx *mds_es_ctx);
+static int mds_es_connect_recv(struct tevent_req *req);
+static void mds_es_connected(struct tevent_req *subreq);
+static bool mds_es_next_search_trigger(struct mds_es_ctx *mds_es_ctx);
+static void mds_es_search_set_pending(struct sl_es_search *s);
+static void mds_es_search_unset_pending(struct sl_es_search *s);
+
+static int mds_es_ctx_destructor(struct mds_es_ctx *mds_es_ctx)
+{
+ struct sl_es_search *s = mds_es_ctx->searches;
+
+ /*
+ * The per tree-connect state mds_es_ctx (a child of mds_ctx) is about
+ * to go away and has already freed all waiting searches. If there's a
+ * search remaining that's when the search is already active. Reset the
+ * mds_es_ctx pointer, so we can detect this when the search completes.
+ */
+
+ if (s == NULL) {
+ return 0;
+ }
+
+ s->mds_es_ctx = NULL;
+
+ return 0;
+}
+
+static bool mds_es_connect(struct mds_ctx *mds_ctx)
+{
+ struct mdssvc_es_ctx *mdssvc_es_ctx = talloc_get_type_abort(
+ mds_ctx->mdssvc_ctx->backend_private, struct mdssvc_es_ctx);
+ struct mds_es_ctx *mds_es_ctx = NULL;
+ struct tevent_req *subreq = NULL;
+
+ mds_es_ctx = talloc_zero(mds_ctx, struct mds_es_ctx);
+ if (mds_es_ctx == NULL) {
+ return false;
+ }
+ *mds_es_ctx = (struct mds_es_ctx) {
+ .mdssvc_es_ctx = mdssvc_es_ctx,
+ .mds_ctx = mds_ctx,
+ };
+
+ mds_ctx->backend_private = mds_es_ctx;
+ talloc_set_destructor(mds_es_ctx, mds_es_ctx_destructor);
+
+ subreq = mds_es_connect_send(
+ mds_es_ctx,
+ mdssvc_es_ctx->mdssvc_ctx->ev_ctx,
+ mds_es_ctx);
+ if (subreq == NULL) {
+ TALLOC_FREE(mds_es_ctx);
+ return false;
+ }
+ tevent_req_set_callback(subreq, mds_es_connected, mds_es_ctx);
+ return true;
+}
+
+static void mds_es_connected(struct tevent_req *subreq)
+{
+ struct mds_es_ctx *mds_es_ctx = tevent_req_callback_data(
+ subreq, struct mds_es_ctx);
+ int ret;
+ bool ok;
+
+ ret = mds_es_connect_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ DBG_ERR("HTTP connect failed\n");
+ return;
+ }
+
+ ok = mds_es_next_search_trigger(mds_es_ctx);
+ if (!ok) {
+ DBG_ERR("mds_es_next_search_trigger failed\n");
+ }
+ return;
+}
+
+struct mds_es_connect_state {
+ struct tevent_context *ev;
+ struct mds_es_ctx *mds_es_ctx;
+ struct tevent_queue_entry *qe;
+ const char *server_addr;
+ uint16_t server_port;
+ struct tstream_tls_params *tls_params;
+};
+
+static void mds_es_http_connect_done(struct tevent_req *subreq);
+static void mds_es_http_waited(struct tevent_req *subreq);
+
+static struct tevent_req *mds_es_connect_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct mds_es_ctx *mds_es_ctx)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct mds_es_connect_state *state = NULL;
+ const char *server_addr = NULL;
+ bool use_tls;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct mds_es_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct mds_es_connect_state) {
+ .ev = ev,
+ .mds_es_ctx = mds_es_ctx,
+ };
+
+ server_addr = lp_parm_const_string(
+ mds_es_ctx->mds_ctx->snum,
+ "elasticsearch",
+ "address",
+ "localhost");
+ state->server_addr = talloc_strdup(state, server_addr);
+ if (tevent_req_nomem(state->server_addr, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->server_port = lp_parm_int(
+ mds_es_ctx->mds_ctx->snum,
+ "elasticsearch",
+ "port",
+ 9200);
+
+ use_tls = lp_parm_bool(
+ mds_es_ctx->mds_ctx->snum,
+ "elasticsearch",
+ "use tls",
+ false);
+
+ DBG_DEBUG("Connecting to HTTP%s [%s] port [%"PRIu16"]\n",
+ use_tls ? "S" : "", state->server_addr, state->server_port);
+
+ if (use_tls) {
+ const char *ca_file = lp__tls_cafile();
+ const char *crl_file = lp__tls_crlfile();
+ const char *tls_priority = lp_tls_priority();
+ enum tls_verify_peer_state verify_peer = lp_tls_verify_peer();
+
+ status = tstream_tls_params_client(state,
+ ca_file,
+ crl_file,
+ tls_priority,
+ verify_peer,
+ state->server_addr,
+ &state->tls_params);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed tstream_tls_params_client - %s\n",
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = http_connect_send(state,
+ state->ev,
+ state->server_addr,
+ state->server_port,
+ mds_es_ctx->mdssvc_es_ctx->creds,
+ state->tls_params);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, mds_es_http_connect_done, req);
+ return req;
+}
+
+static void mds_es_http_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mds_es_connect_state *state = tevent_req_data(
+ req, struct mds_es_connect_state);
+ int error;
+
+ error = http_connect_recv(subreq,
+ state->mds_es_ctx,
+ &state->mds_es_ctx->http_conn);
+ TALLOC_FREE(subreq);
+ if (error != 0) {
+ DBG_ERR("HTTP connect failed, retrying...\n");
+
+ subreq = tevent_wakeup_send(
+ state->mds_es_ctx,
+ state->mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx,
+ tevent_timeval_current_ofs(10, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ mds_es_http_waited,
+ req);
+ return;
+ }
+
+ DBG_DEBUG("Connected to HTTP%s [%s] port [%"PRIu16"]\n",
+ state->tls_params ? "S" : "",
+ state->server_addr, state->server_port);
+
+ tevent_req_done(req);
+ return;
+}
+
+static void mds_es_http_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mds_es_connect_state *state = tevent_req_data(
+ req, struct mds_es_connect_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, ETIMEDOUT);
+ return;
+ }
+
+ subreq = mds_es_connect_send(
+ state->mds_es_ctx,
+ state->mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx,
+ state->mds_es_ctx);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, mds_es_connected, state->mds_es_ctx);
+}
+
+static int mds_es_connect_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static void mds_es_reconnect_on_error(struct sl_es_search *s)
+{
+ struct mds_es_ctx *mds_es_ctx = s->mds_es_ctx;
+ struct tevent_req *subreq = NULL;
+
+ if (s->slq != NULL) {
+ s->slq->state = SLQ_STATE_ERROR;
+ }
+
+ DBG_WARNING("Reconnecting HTTP...\n");
+ TALLOC_FREE(mds_es_ctx->http_conn);
+
+ subreq = mds_es_connect_send(
+ mds_es_ctx,
+ mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx,
+ mds_es_ctx);
+ if (subreq == NULL) {
+ DBG_ERR("mds_es_connect_send failed\n");
+ return;
+ }
+ tevent_req_set_callback(subreq, mds_es_connected, mds_es_ctx);
+}
+
+static int search_destructor(struct sl_es_search *s)
+{
+ if (s->mds_es_ctx == NULL) {
+ return 0;
+ }
+ DLIST_REMOVE(s->mds_es_ctx->searches, s);
+ return 0;
+}
+
+static struct tevent_req *mds_es_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sl_es_search *s);
+static int mds_es_search_recv(struct tevent_req *req);
+static void mds_es_search_done(struct tevent_req *subreq);
+
+static bool mds_es_search(struct sl_query *slq)
+{
+ struct mds_es_ctx *mds_es_ctx = talloc_get_type_abort(
+ slq->mds_ctx->backend_private, struct mds_es_ctx);
+ struct sl_es_search *s = NULL;
+ bool ok;
+
+ s = talloc_zero(slq, struct sl_es_search);
+ if (s == NULL) {
+ return false;
+ }
+ *s = (struct sl_es_search) {
+ .ev = mds_es_ctx->mdssvc_es_ctx->mdssvc_ctx->ev_ctx,
+ .mds_es_ctx = mds_es_ctx,
+ .slq = slq,
+ .size = SL_PAGESIZE,
+ };
+
+ /* 0 would mean no limit */
+ s->max = lp_parm_ulonglong(s->slq->mds_ctx->snum,
+ "elasticsearch",
+ "max results",
+ MAX_SL_RESULTS);
+
+ DBG_DEBUG("Spotlight query: '%s'\n", slq->query_string);
+
+ ok = map_spotlight_to_es_query(
+ s,
+ mds_es_ctx->mdssvc_es_ctx->mappings,
+ slq->path_scope,
+ slq->query_string,
+ &s->es_query);
+ if (!ok) {
+ TALLOC_FREE(s);
+ return false;
+ }
+ DBG_DEBUG("Elasticsearch query: '%s'\n", s->es_query);
+
+ slq->backend_private = s;
+ slq->state = SLQ_STATE_RUNNING;
+ DLIST_ADD_END(mds_es_ctx->searches, s);
+ talloc_set_destructor(s, search_destructor);
+
+ return mds_es_next_search_trigger(mds_es_ctx);
+}
+
+static bool mds_es_next_search_trigger(struct mds_es_ctx *mds_es_ctx)
+{
+ struct tevent_req *subreq = NULL;
+ struct sl_es_search *s = mds_es_ctx->searches;
+
+ if (mds_es_ctx->http_conn == NULL) {
+ DBG_DEBUG("Waiting for HTTP connection...\n");
+ return true;
+ }
+ if (s == NULL) {
+ DBG_DEBUG("No pending searches, idling...\n");
+ return true;
+ }
+ if (s->pending) {
+ DBG_DEBUG("Search pending [%p]\n", s);
+ return true;
+ }
+
+ subreq = mds_es_search_send(s, s->ev, s);
+ if (subreq == NULL) {
+ return false;
+ }
+ tevent_req_set_callback(subreq, mds_es_search_done, s);
+ mds_es_search_set_pending(s);
+ return true;
+}
+
+static void mds_es_search_done(struct tevent_req *subreq)
+{
+ struct sl_es_search *s = tevent_req_callback_data(
+ subreq, struct sl_es_search);
+ struct mds_es_ctx *mds_es_ctx = s->mds_es_ctx;
+ struct sl_query *slq = s->slq;
+ int ret;
+ bool ok;
+
+ DBG_DEBUG("Search done for search [%p]\n", s);
+
+ mds_es_search_unset_pending(s);
+
+ if (mds_es_ctx == NULL) {
+ /*
+ * Search connection closed by the user while s was pending.
+ */
+ TALLOC_FREE(s);
+ return;
+ }
+
+ DLIST_REMOVE(mds_es_ctx->searches, s);
+
+ ret = mds_es_search_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ mds_es_reconnect_on_error(s);
+ return;
+ }
+
+ if (slq == NULL) {
+ /*
+ * Closed by the user. Explicitly free "s" here because the
+ * talloc parent slq is already gone.
+ */
+ TALLOC_FREE(s);
+ goto trigger;
+ }
+
+ SLQ_DEBUG(10, slq, "search done");
+
+ if (s->total == 0 || s->from >= s->max) {
+ slq->state = SLQ_STATE_DONE;
+ goto trigger;
+ }
+
+ if (slq->query_results->num_results >= SL_PAGESIZE) {
+ slq->state = SLQ_STATE_FULL;
+ goto trigger;
+ }
+
+ /*
+ * Reschedule this query as there are more results waiting in the
+ * Elasticsearch server and the client result queue has room as
+ * well. But put it at the end of the list of active queries as a simple
+ * heuristic that should ensure all client queries are dispatched to the
+ * server.
+ */
+ DLIST_ADD_END(mds_es_ctx->searches, s);
+
+trigger:
+ ok = mds_es_next_search_trigger(mds_es_ctx);
+ if (!ok) {
+ DBG_ERR("mds_es_next_search_trigger failed\n");
+ }
+}
+
+static void mds_es_search_http_send_done(struct tevent_req *subreq);
+static void mds_es_search_http_read_done(struct tevent_req *subreq);
+
+struct mds_es_search_state {
+ struct tevent_context *ev;
+ struct sl_es_search *s;
+ struct tevent_queue_entry *qe;
+ struct http_request http_request;
+ struct http_request *http_response;
+};
+
+static int mds_es_search_pending_destructor(struct sl_es_search *s)
+{
+ /*
+ * s is a child of slq which may get freed when a user closes a
+ * query. To maintain the HTTP request/response sequence on the HTTP
+ * channel, we keep processing pending requests and free s when we
+ * receive the HTTP response for pending requests.
+ */
+ DBG_DEBUG("Preserving pending search [%p]\n", s);
+ s->slq = NULL;
+ return -1;
+}
+
+static void mds_es_search_set_pending(struct sl_es_search *s)
+{
+ DBG_DEBUG("Set pending [%p]\n", s);
+ SLQ_DEBUG(10, s->slq, "pending");
+
+ s->pending = true;
+ talloc_set_destructor(s, mds_es_search_pending_destructor);
+}
+
+static void mds_es_search_unset_pending(struct sl_es_search *s)
+{
+ DBG_DEBUG("Unset pending [%p]\n", s);
+ if (s->slq != NULL) {
+ SLQ_DEBUG(10, s->slq, "unset pending");
+ }
+
+ s->pending = false;
+ talloc_set_destructor(s, search_destructor);
+}
+
+static struct tevent_req *mds_es_search_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct sl_es_search *s)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct mds_es_search_state *state = NULL;
+ const char *index = NULL;
+ char *elastic_query = NULL;
+ char *uri = NULL;
+ size_t elastic_query_len;
+ char *elastic_query_len_str = NULL;
+ char *hostname = NULL;
+ bool pretty = false;
+
+ req = tevent_req_create(mem_ctx, &state, struct mds_es_search_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct mds_es_search_state) {
+ .ev = ev,
+ .s = s,
+ };
+
+ if (!tevent_req_set_endtime(req, ev, timeval_current_ofs(60, 0))) {
+ return tevent_req_post(req, s->ev);
+ }
+
+ index = lp_parm_const_string(s->slq->mds_ctx->snum,
+ "elasticsearch",
+ "index",
+ "_all");
+ if (tevent_req_nomem(index, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (DEBUGLVL(10)) {
+ pretty = true;
+ }
+
+ uri = talloc_asprintf(state,
+ "/%s/_search%s",
+ index,
+ pretty ? "?pretty" : "");
+ if (tevent_req_nomem(uri, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ elastic_query = talloc_asprintf(state,
+ MDSSVC_ELASTIC_QUERY_TEMPLATE,
+ s->from,
+ s->size,
+ MDSSVC_ELASTIC_SOURCES,
+ s->es_query);
+ if (tevent_req_nomem(elastic_query, req)) {
+ return tevent_req_post(req, ev);
+ }
+ DBG_DEBUG("Elastic query: '%s'\n", elastic_query);
+
+ elastic_query_len = strlen(elastic_query);
+
+ state->http_request = (struct http_request) {
+ .type = HTTP_REQ_POST,
+ .uri = uri,
+ .body = data_blob_const(elastic_query, elastic_query_len),
+ .major = '1',
+ .minor = '1',
+ };
+
+ elastic_query_len_str = talloc_asprintf(state, "%zu", elastic_query_len);
+ if (tevent_req_nomem(elastic_query_len_str, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ hostname = get_myname(state);
+ if (tevent_req_nomem(hostname, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ http_add_header(state, &state->http_request.headers,
+ "Content-Type", "application/json");
+ http_add_header(state, &state->http_request.headers,
+ "Accept", "application/json");
+ http_add_header(state, &state->http_request.headers,
+ "User-Agent", "Samba/mdssvc");
+ http_add_header(state, &state->http_request.headers,
+ "Host", hostname);
+ http_add_header(state, &state->http_request.headers,
+ "Content-Length", elastic_query_len_str);
+
+ subreq = http_send_request_send(state,
+ ev,
+ s->mds_es_ctx->http_conn,
+ &state->http_request);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, mds_es_search_http_send_done, req);
+ return req;
+}
+
+static void mds_es_search_http_send_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mds_es_search_state *state = tevent_req_data(
+ req, struct mds_es_search_state);
+ NTSTATUS status;
+
+ DBG_DEBUG("Sent out search [%p]\n", state->s);
+
+ status = http_send_request_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_error(req, map_errno_from_nt_status(status));
+ return;
+ }
+
+ if (state->s->mds_es_ctx == NULL || state->s->slq == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = http_read_response_send(state,
+ state->ev,
+ state->s->mds_es_ctx->http_conn,
+ SL_PAGESIZE * 8192);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, mds_es_search_http_read_done, req);
+}
+
+static void mds_es_search_http_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct mds_es_search_state *state = tevent_req_data(
+ req, struct mds_es_search_state);
+ struct sl_es_search *s = state->s;
+ struct sl_query *slq = s->slq;
+ json_t *root = NULL;
+ json_t *matches = NULL;
+ json_t *match = NULL;
+ size_t i;
+ json_error_t error;
+ size_t hits;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ DBG_DEBUG("Got response for search [%p]\n", s);
+
+ status = http_read_response_recv(subreq, state, &state->http_response);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("HTTP response failed: %s\n", nt_errstr(status));
+ tevent_req_error(req, map_errno_from_nt_status(status));
+ return;
+ }
+
+ if (slq == NULL || s->mds_es_ctx == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ switch (state->http_response->response_code) {
+ case 200:
+ break;
+ default:
+ DBG_ERR("HTTP server response: %u\n",
+ state->http_response->response_code);
+ goto fail;
+ }
+
+ DBG_DEBUG("JSON response:\n%s\n",
+ talloc_strndup(talloc_tos(),
+ (char *)state->http_response->body.data,
+ state->http_response->body.length));
+
+ root = json_loadb((char *)state->http_response->body.data,
+ state->http_response->body.length,
+ 0,
+ &error);
+ if (root == NULL) {
+ DBG_ERR("json_loadb failed\n");
+ goto fail;
+ }
+
+ if (s->total == 0) {
+ /*
+ * Get the total number of results the first time, format
+ * used by Elasticsearch 7.0 or newer
+ */
+ ret = json_unpack(root, "{s: {s: {s: i}}}",
+ "hits", "total", "value", &s->total);
+ if (ret != 0) {
+ /* Format used before 7.0 */
+ ret = json_unpack(root, "{s: {s: i}}",
+ "hits", "total", &s->total);
+ if (ret != 0) {
+ DBG_ERR("json_unpack failed\n");
+ goto fail;
+ }
+ }
+
+ DBG_DEBUG("Total: %zu\n", s->total);
+
+ if (s->total == 0) {
+ json_decref(root);
+ tevent_req_done(req);
+ return;
+ }
+ }
+
+ if (s->max == 0 || s->max > s->total) {
+ s->max = s->total;
+ }
+
+ ret = json_unpack(root, "{s: {s:o}}",
+ "hits", "hits", &matches);
+ if (ret != 0 || matches == NULL) {
+ DBG_ERR("json_unpack hits failed\n");
+ goto fail;
+ }
+
+ hits = json_array_size(matches);
+ if (hits == 0) {
+ DBG_ERR("Hu?! No results?\n");
+ goto fail;
+ }
+ DBG_DEBUG("Hits: %zu\n", hits);
+
+ for (i = 0; i < hits && s->from + i < s->max; i++) {
+ const char *path = NULL;
+
+ match = json_array_get(matches, i);
+ if (match == NULL) {
+ DBG_ERR("Hu?! No value for index %zu\n", i);
+ goto fail;
+ }
+ ret = json_unpack(match,
+ "{s: {s: {s: s}}}",
+ "_source",
+ "path",
+ "real",
+ &path);
+ if (ret != 0) {
+ DBG_ERR("Missing path.real in JSON result\n");
+ goto fail;
+ }
+
+ ok = mds_add_result(slq, path);
+ if (!ok) {
+ DBG_ERR("error adding result for path: %s\n", path);
+ goto fail;
+ }
+ }
+ json_decref(root);
+
+ s->from += hits;
+ slq->state = SLQ_STATE_RESULTS;
+ tevent_req_done(req);
+ return;
+
+fail:
+ if (root != NULL) {
+ json_decref(root);
+ }
+ slq->state = SLQ_STATE_ERROR;
+ tevent_req_error(req, EINVAL);
+ return;
+}
+
+static int mds_es_search_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static bool mds_es_search_cont(struct sl_query *slq)
+{
+ struct sl_es_search *s = talloc_get_type_abort(
+ slq->backend_private, struct sl_es_search);
+
+ SLQ_DEBUG(10, slq, "continue");
+ DLIST_ADD_END(s->mds_es_ctx->searches, s);
+ return mds_es_next_search_trigger(s->mds_es_ctx);
+}
+
+struct mdssvc_backend mdsscv_backend_es = {
+ .init = mdssvc_es_init,
+ .shutdown = mdssvc_es_shutdown,
+ .connect = mds_es_connect,
+ .search_start = mds_es_search,
+ .search_cont = mds_es_search_cont,
+};
diff --git a/source3/rpc_server/mdssvc/mdssvc_es.h b/source3/rpc_server/mdssvc/mdssvc_es.h
new file mode 100644
index 0000000..19797fa
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc_es.h
@@ -0,0 +1,108 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / HTTP/ES/JSON backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MDSSVC_ES_H_
+#define _MDSSVC_ES_H_
+
+#include <jansson.h>
+
+/*
+ * Some global state
+ */
+struct mdssvc_es_ctx {
+ struct mdssvc_ctx *mdssvc_ctx;
+ struct cli_credentials *creds;
+ json_t *mappings;
+};
+
+/*
+ * Per mdssvc RPC bind state
+ */
+struct mds_es_ctx {
+ /*
+ * Pointer to higher level mds_ctx
+ */
+ struct mds_ctx *mds_ctx;
+
+ /*
+ * Pointer to our global context
+ */
+ struct mdssvc_es_ctx *mdssvc_es_ctx;
+
+ /*
+ * The HTTP connection handle to the ES server
+ */
+ struct http_conn *http_conn;
+
+ /*
+ * List of pending searches
+ */
+ struct sl_es_search *searches;
+};
+
+/* Per search request */
+struct sl_es_search {
+ /*
+ * List pointers
+ */
+ struct sl_es_search *prev, *next;
+
+ /*
+ * Search is being executed. Only the list head can be pending.
+ */
+ bool pending;
+
+ /*
+ * Shorthand to our tevent context
+ */
+ struct tevent_context *ev;
+
+ /*
+ * Pointer to the RPC connection ctx the request is using
+ */
+ struct mds_es_ctx *mds_es_ctx;
+
+ /*
+ * The upper mdssvc.c level query context
+ */
+ struct sl_query *slq;
+
+ /*
+ * Maximum number of results we process and total number of
+ * results of a query.
+ */
+ size_t total;
+ size_t max;
+
+ /*
+ * For paging results
+ */
+ size_t from;
+ size_t size;
+
+ /*
+ * The translated Es query
+ */
+ char *es_query;
+};
+
+extern struct mdssvc_backend mdsscv_backend_es;
+
+#endif /* _MDSSVC_ES_H_ */
diff --git a/source3/rpc_server/mdssvc/mdssvc_noindex.c b/source3/rpc_server/mdssvc/mdssvc_noindex.c
new file mode 100644
index 0000000..ff466af
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc_noindex.c
@@ -0,0 +1,57 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / noindex backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "mdssvc.h"
+
+static bool mdssvc_noindex_init(struct mdssvc_ctx *mdssvc_ctx)
+{
+ return true;
+}
+
+static bool mdssvc_noindex_shutdown(struct mdssvc_ctx *mdssvc_ctx)
+{
+ return true;
+}
+
+static bool mds_noindex_connect(struct mds_ctx *mds_ctx)
+{
+ return true;
+}
+
+static bool mds_noindex_search_start(struct sl_query *slq)
+{
+ slq->state = SLQ_STATE_DONE;
+ return true;
+}
+
+static bool mds_noindex_search_cont(struct sl_query *slq)
+{
+ slq->state = SLQ_STATE_DONE;
+ return true;
+}
+
+struct mdssvc_backend mdsscv_backend_noindex = {
+ .init = mdssvc_noindex_init,
+ .shutdown = mdssvc_noindex_shutdown,
+ .connect = mds_noindex_connect,
+ .search_start = mds_noindex_search_start,
+ .search_cont = mds_noindex_search_cont,
+};
diff --git a/source3/rpc_server/mdssvc/mdssvc_noindex.h b/source3/rpc_server/mdssvc/mdssvc_noindex.h
new file mode 100644
index 0000000..750ee44
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc_noindex.h
@@ -0,0 +1,26 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / noindex backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MDSSVC_NOINDEX_H_
+#define _MDSSVC_NOINDEX_H_
+
+extern struct mdssvc_backend mdsscv_backend_noindex;
+
+#endif /* _MDSSVC_VOID_H_ */
diff --git a/source3/rpc_server/mdssvc/mdssvc_tracker.c b/source3/rpc_server/mdssvc/mdssvc_tracker.c
new file mode 100644
index 0000000..54f391e
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc_tracker.c
@@ -0,0 +1,499 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / Tracker backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/util/time_basic.h"
+#include "mdssvc.h"
+#include "mdssvc_tracker.h"
+#include "lib/tevent_glib_glue.h"
+#include "rpc_server/mdssvc/sparql_parser.tab.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+static struct mdssvc_tracker_ctx *mdssvc_tracker_ctx;
+
+/************************************************
+ * Tracker async callbacks
+ ************************************************/
+
+static void tracker_con_cb(GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ struct mds_tracker_ctx *ctx = NULL;
+ TrackerSparqlConnection *tracker_con = NULL;
+ GError *error = NULL;
+
+ tracker_con = tracker_sparql_connection_get_finish(res, &error);
+ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ /*
+ * If the async request was cancelled, user_data will already be
+ * talloc_free'd, so we must be carefully checking for
+ * G_IO_ERROR_CANCELLED before using user_data.
+ */
+ DBG_ERR("Tracker connection cancelled\n");
+ g_error_free(error);
+ return;
+ }
+ /*
+ * Ok, we're not cancelled, we can now safely use user_data.
+ */
+ ctx = talloc_get_type_abort(user_data, struct mds_tracker_ctx);
+ ctx->async_pending = false;
+ /*
+ * Check error again, above we only checked for G_IO_ERROR_CANCELLED.
+ */
+ if (error) {
+ DBG_ERR("Could not connect to Tracker: %s\n", error->message);
+ g_error_free(error);
+ return;
+ }
+
+ ctx->tracker_con = tracker_con;
+
+ DBG_DEBUG("connected to Tracker\n");
+}
+
+static void tracker_cursor_cb(GObject *object,
+ GAsyncResult *res,
+ gpointer user_data);
+
+static void tracker_query_cb(GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ struct sl_tracker_query *tq = NULL;
+ struct sl_query *slq = NULL;
+ TrackerSparqlConnection *conn = NULL;
+ TrackerSparqlCursor *cursor = NULL;
+ GError *error = NULL;
+
+ conn = TRACKER_SPARQL_CONNECTION(object);
+
+ cursor = tracker_sparql_connection_query_finish(conn, res, &error);
+ /*
+ * If the async request was cancelled, user_data will already be
+ * talloc_free'd, so we must be carefully checking for
+ * G_IO_ERROR_CANCELLED before using user_data.
+ */
+ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ DBG_ERR("Tracker query cancelled\n");
+ if (cursor != NULL) {
+ g_object_unref(cursor);
+ }
+ g_error_free(error);
+ return;
+ }
+ /*
+ * Ok, we're not cancelled, we can now safely use user_data.
+ */
+ tq = talloc_get_type_abort(user_data, struct sl_tracker_query);
+ tq->async_pending = false;
+ slq = tq->slq;
+ /*
+ * Check error again, above we only checked for G_IO_ERROR_CANCELLED.
+ */
+ if (error) {
+ DBG_ERR("Tracker query error: %s\n", error->message);
+ g_error_free(error);
+ slq->state = SLQ_STATE_ERROR;
+ return;
+ }
+
+ tq->cursor = cursor;
+ slq->state = SLQ_STATE_RESULTS;
+
+ tracker_sparql_cursor_next_async(tq->cursor,
+ tq->gcancellable,
+ tracker_cursor_cb,
+ tq);
+ tq->async_pending = true;
+}
+
+static char *tracker_to_unix_path(TALLOC_CTX *mem_ctx, const char *uri)
+{
+ GFile *f = NULL;
+ char *path = NULL;
+ char *talloc_path = NULL;
+
+ f = g_file_new_for_uri(uri);
+ if (f == NULL) {
+ return NULL;
+ }
+
+ path = g_file_get_path(f);
+ g_object_unref(f);
+
+ if (path == NULL) {
+ return NULL;
+ }
+
+ talloc_path = talloc_strdup(mem_ctx, path);
+ g_free(path);
+ if (talloc_path == NULL) {
+ return NULL;
+ }
+
+ return talloc_path;
+}
+
+static void tracker_cursor_cb(GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TrackerSparqlCursor *cursor = NULL;
+ struct sl_tracker_query *tq = NULL;
+ struct sl_query *slq = NULL;
+ const gchar *uri = NULL;
+ GError *error = NULL;
+ char *path = NULL;
+ gboolean more_results;
+ bool ok;
+
+ cursor = TRACKER_SPARQL_CURSOR(object);
+ more_results = tracker_sparql_cursor_next_finish(cursor,
+ res,
+ &error);
+ /*
+ * If the async request was cancelled, user_data will already be
+ * talloc_free'd, so we must be carefully checking for
+ * G_IO_ERROR_CANCELLED before using user_data.
+ */
+ if (error && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_error_free(error);
+ g_object_unref(cursor);
+ return;
+ }
+ /*
+ * Ok, we're not cancelled, we can now safely use user_data.
+ */
+ tq = talloc_get_type_abort(user_data, struct sl_tracker_query);
+ tq->async_pending = false;
+ slq = tq->slq;
+ /*
+ * Check error again, above we only checked for G_IO_ERROR_CANCELLED.
+ */
+ if (error) {
+ DBG_ERR("Tracker cursor: %s\n", error->message);
+ g_error_free(error);
+ slq->state = SLQ_STATE_ERROR;
+ return;
+ }
+
+ SLQ_DEBUG(10, slq, "results");
+
+ if (!more_results) {
+ slq->state = SLQ_STATE_DONE;
+
+ g_object_unref(tq->cursor);
+ tq->cursor = NULL;
+
+ g_object_unref(tq->gcancellable);
+ tq->gcancellable = NULL;
+ return;
+ }
+
+ uri = tracker_sparql_cursor_get_string(tq->cursor, 0, NULL);
+ if (uri == NULL) {
+ DBG_ERR("error fetching Tracker URI\n");
+ slq->state = SLQ_STATE_ERROR;
+ return;
+ }
+
+ path = tracker_to_unix_path(slq->query_results, uri);
+ if (path == NULL) {
+ DBG_ERR("error converting Tracker URI to path: %s\n", uri);
+ slq->state = SLQ_STATE_ERROR;
+ return;
+ }
+
+ ok = mds_add_result(slq, path);
+ if (!ok) {
+ DBG_ERR("error adding result for path: %s\n", uri);
+ slq->state = SLQ_STATE_ERROR;
+ return;
+ }
+
+ if (slq->query_results->num_results >= MAX_SL_RESULTS) {
+ slq->state = SLQ_STATE_FULL;
+ SLQ_DEBUG(10, slq, "full");
+ return;
+ }
+
+ slq->state = SLQ_STATE_RESULTS;
+ SLQ_DEBUG(10, slq, "cursor next");
+
+ tracker_sparql_cursor_next_async(tq->cursor,
+ tq->gcancellable,
+ tracker_cursor_cb,
+ tq);
+ tq->async_pending = true;
+}
+
+/*
+ * This gets called once, even if the backend is not configured by the user
+ */
+static bool mdssvc_tracker_init(struct mdssvc_ctx *mdssvc_ctx)
+{
+ if (mdssvc_tracker_ctx != NULL) {
+ return true;
+ }
+
+#if (GLIB_MAJOR_VERSION < 3) && (GLIB_MINOR_VERSION < 36)
+ g_type_init();
+#endif
+
+ mdssvc_tracker_ctx = talloc_zero(mdssvc_ctx, struct mdssvc_tracker_ctx);
+ if (mdssvc_tracker_ctx == NULL) {
+ return false;
+ }
+ mdssvc_tracker_ctx->mdssvc_ctx = mdssvc_ctx;
+
+ return true;
+}
+
+/*
+ * This gets called per mdscmd_open / tcon. This runs initialisation code that
+ * should only run if the tracker backend is actually used.
+ */
+static bool mdssvc_tracker_prepare(void)
+{
+ if (mdssvc_tracker_ctx->gmain_ctx != NULL) {
+ /*
+ * Assuming everything is setup if gmain_ctx is.
+ */
+ return true;
+ }
+
+ mdssvc_tracker_ctx->gmain_ctx = g_main_context_new();
+ if (mdssvc_tracker_ctx->gmain_ctx == NULL) {
+ DBG_ERR("error from g_main_context_new\n");
+ return false;
+ }
+
+ mdssvc_tracker_ctx->glue = samba_tevent_glib_glue_create(
+ mdssvc_tracker_ctx,
+ mdssvc_tracker_ctx->mdssvc_ctx->ev_ctx,
+ mdssvc_tracker_ctx->gmain_ctx);
+ if (mdssvc_tracker_ctx->glue == NULL) {
+ DBG_ERR("samba_tevent_glib_glue_create failed\n");
+ g_object_unref(mdssvc_tracker_ctx->gmain_ctx);
+ mdssvc_tracker_ctx->gmain_ctx = NULL;
+ return false;
+ }
+
+ return true;
+}
+
+static bool mdssvc_tracker_shutdown(struct mdssvc_ctx *mdssvc_ctx)
+{
+ if (mdssvc_tracker_ctx == NULL) {
+ return true;
+ }
+
+ if (mdssvc_tracker_ctx->gmain_ctx == NULL) {
+ return true;
+ }
+
+ samba_tevent_glib_glue_quit(mdssvc_tracker_ctx->glue);
+ TALLOC_FREE(mdssvc_tracker_ctx->glue);
+
+ g_object_unref(mdssvc_tracker_ctx->gmain_ctx);
+ mdssvc_tracker_ctx->gmain_ctx = NULL;
+ return true;
+}
+
+static int mds_tracker_ctx_destructor(struct mds_tracker_ctx *ctx)
+{
+ /*
+ * Don't g_object_unref() the connection if there's an async request
+ * pending, it's used in the async callback and will be unreferenced
+ * there.
+ */
+ if (ctx->async_pending) {
+ g_cancellable_cancel(ctx->gcancellable);
+ ctx->gcancellable = NULL;
+ return 0;
+ }
+
+ if (ctx->tracker_con == NULL) {
+ return 0;
+ }
+ g_object_unref(ctx->tracker_con);
+ ctx->tracker_con = NULL;
+
+ return 0;
+}
+
+static bool mds_tracker_connect(struct mds_ctx *mds_ctx)
+{
+ struct mds_tracker_ctx *ctx = NULL;
+ bool ok;
+
+ ok = mdssvc_tracker_prepare();
+ if (!ok) {
+ return false;
+ }
+
+ ctx = talloc_zero(mds_ctx, struct mds_tracker_ctx);
+ if (ctx == NULL) {
+ return false;
+ }
+ talloc_set_destructor(ctx, mds_tracker_ctx_destructor);
+
+ ctx->mds_ctx = mds_ctx;
+
+ ctx->gcancellable = g_cancellable_new();
+ if (ctx->gcancellable == NULL) {
+ DBG_ERR("error from g_cancellable_new\n");
+ TALLOC_FREE(ctx);
+ return false;
+ }
+
+ tracker_sparql_connection_get_async(ctx->gcancellable,
+ tracker_con_cb,
+ ctx);
+ ctx->async_pending = true;
+
+ mds_ctx->backend_private = ctx;
+
+ return true;
+}
+
+static int tq_destructor(struct sl_tracker_query *tq)
+{
+ /*
+ * Don't g_object_unref() the cursor if there's an async request
+ * pending, it's used in the async callback and will be unreferenced
+ * there.
+ */
+ if (tq->async_pending) {
+ g_cancellable_cancel(tq->gcancellable);
+ tq->gcancellable = NULL;
+ return 0;
+ }
+
+ if (tq->cursor == NULL) {
+ return 0;
+ }
+ g_object_unref(tq->cursor);
+ tq->cursor = NULL;
+ return 0;
+}
+
+static bool mds_tracker_search_start(struct sl_query *slq)
+{
+ struct mds_tracker_ctx *tmds_ctx = talloc_get_type_abort(
+ slq->mds_ctx->backend_private, struct mds_tracker_ctx);
+ struct sl_tracker_query *tq = NULL;
+ char *escaped_scope = NULL;
+ bool ok;
+
+ if (tmds_ctx->tracker_con == NULL) {
+ DBG_ERR("no connection to Tracker\n");
+ return false;
+ }
+
+ tq = talloc_zero(slq, struct sl_tracker_query);
+ if (tq == NULL) {
+ return false;
+ }
+ tq->slq = slq;
+ talloc_set_destructor(tq, tq_destructor);
+
+ tq->gcancellable = g_cancellable_new();
+ if (tq->gcancellable == NULL) {
+ DBG_ERR("g_cancellable_new() failed\n");
+ goto error;
+ }
+
+ escaped_scope = g_uri_escape_string(
+ slq->path_scope,
+ G_URI_RESERVED_CHARS_ALLOWED_IN_PATH,
+ TRUE);
+ if (escaped_scope == NULL) {
+ goto error;
+ }
+
+ tq->path_scope = talloc_strdup(tq, escaped_scope);
+ g_free(escaped_scope);
+ escaped_scope = NULL;
+ if (tq->path_scope == NULL) {
+ goto error;
+ }
+
+ slq->backend_private = tq;
+
+ ok = map_spotlight_to_sparql_query(slq);
+ if (!ok) {
+ /*
+ * Two cases:
+ *
+ * 1) the query string is "false", the parser returns
+ * an error for that. We're supposed to return -1
+ * here.
+ *
+ * 2) the parsing really failed, in that case we're
+ * probably supposed to return -1 too, this needs
+ * verification though
+ */
+ goto error;
+ }
+
+ DBG_DEBUG("SPARQL query: \"%s\"\n", tq->sparql_query);
+
+ tracker_sparql_connection_query_async(tmds_ctx->tracker_con,
+ tq->sparql_query,
+ tq->gcancellable,
+ tracker_query_cb,
+ tq);
+ tq->async_pending = true;
+
+ slq->state = SLQ_STATE_RUNNING;
+ return true;
+error:
+ g_object_unref(tq->gcancellable);
+ TALLOC_FREE(tq);
+ slq->backend_private = NULL;
+ return false;
+}
+
+static bool mds_tracker_search_cont(struct sl_query *slq)
+{
+ struct sl_tracker_query *tq = talloc_get_type_abort(
+ slq->backend_private, struct sl_tracker_query);
+
+ tracker_sparql_cursor_next_async(tq->cursor,
+ tq->gcancellable,
+ tracker_cursor_cb,
+ tq);
+ tq->async_pending = true;
+
+ return true;
+}
+
+struct mdssvc_backend mdsscv_backend_tracker = {
+ .init = mdssvc_tracker_init,
+ .shutdown = mdssvc_tracker_shutdown,
+ .connect = mds_tracker_connect,
+ .search_start = mds_tracker_search_start,
+ .search_cont = mds_tracker_search_cont,
+};
diff --git a/source3/rpc_server/mdssvc/mdssvc_tracker.h b/source3/rpc_server/mdssvc/mdssvc_tracker.h
new file mode 100644
index 0000000..54a4a33
--- /dev/null
+++ b/source3/rpc_server/mdssvc/mdssvc_tracker.h
@@ -0,0 +1,62 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines / Tracker backend
+
+ Copyright (C) Ralph Boehme 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* allow building with --enable-developer */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#include <gio/gio.h>
+#include <tracker-sparql.h>
+#pragma GCC diagnostic pop
+
+/* Global */
+struct mdssvc_tracker_ctx {
+ struct mdssvc_ctx *mdssvc_ctx;
+ GMainContext *gmain_ctx;
+ struct tevent_glib_glue *glue;
+};
+
+/* Per tree connect state */
+struct mds_tracker_ctx {
+ struct mds_ctx *mds_ctx;
+ GCancellable *gcancellable;
+ bool async_pending;
+ TrackerSparqlConnection *tracker_con;
+};
+
+/* Per query */
+struct sl_tracker_query {
+ struct sl_query *slq;
+ const char *path_scope;
+ const char *sparql_query;
+
+ /*
+ * Notes on the lifetime of cursor: we hold a reference on the object
+ * and have to call g_object_unref(cursor) at the right place. This is
+ * either done in the talloc destructor on a struct sl_tracker_query
+ * talloc object when there are no tracker glib async requests
+ * running. Or in the glib callback after cancelling the glib async
+ * request.
+ */
+ TrackerSparqlCursor *cursor;
+ GCancellable *gcancellable;
+ bool async_pending;
+};
+
+extern struct mdssvc_backend mdsscv_backend_tracker;
diff --git a/source3/rpc_server/mdssvc/sparql_lexer.l b/source3/rpc_server/mdssvc/sparql_lexer.l
new file mode 100644
index 0000000..b638350
--- /dev/null
+++ b/source3/rpc_server/mdssvc/sparql_lexer.l
@@ -0,0 +1,67 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines
+
+ Copyright (C) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+%{
+#include "includes.h"
+#include "rpc_server/mdssvc/sparql_parser.tab.h"
+
+#define YY_NO_INPUT
+%}
+
+%option nounput noyyalloc noyyrealloc prefix="mdsyy"
+
+ASC [a-zA-Z0-9_\*\:\-\.]
+U [\x80-\xbf]
+U2 [\xc2-\xdf]
+U3 [\xe0-\xef]
+U4 [\xf0-\xf4]
+
+UANY {ASC}|{U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U}
+UONLY {U2}{U}|{U3}{U}{U}|{U4}{U}{U}{U}
+
+%%
+InRange return FUNC_INRANGE;
+\$time\.iso return DATE_ISO;
+false {mdsyylval.bval = false; return BOOL;}
+true {mdsyylval.bval = true; return BOOL;}
+\" return QUOTE;
+\( return OBRACE;
+\) return CBRACE;
+\&\& return AND;
+\|\| return OR;
+\=\= return EQUAL;
+\!\= return UNEQUAL;
+\= return EQUAL;
+\< return LT;
+\> return GT;
+\, return COMMA;
+{UANY}+ {mdsyylval.sval = talloc_strdup(talloc_tos(), mdsyytext); return WORD;}
+[ \t\n] /* ignore */
+%%
+
+void *yyalloc(yy_size_t bytes)
+{
+ return SMB_MALLOC(bytes);
+}
+
+void *yyrealloc(void *ptr, yy_size_t bytes)
+{
+ return SMB_REALLOC(ptr, bytes);
+}
diff --git a/source3/rpc_server/mdssvc/sparql_mapping.c b/source3/rpc_server/mdssvc/sparql_mapping.c
new file mode 100644
index 0000000..c71c7a5
--- /dev/null
+++ b/source3/rpc_server/mdssvc/sparql_mapping.c
@@ -0,0 +1,378 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines
+
+ Copyright (C) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "sparql_mapping.h"
+
+const struct sl_attr_map *sl_attr_map_by_spotlight(const char *sl_attr)
+{
+ static const struct sl_attr_map spotlight_sparql_attr_map[] = {
+ {
+ .spotlight_attr = "*",
+ .type = ssmt_fts,
+ .sparql_attr = "fts:match",
+ },
+
+ /* Filesystem metadata */
+ {
+ .spotlight_attr = "kMDItemFSLabel",
+ .type = ssmt_num,
+ .sparql_attr = NULL,
+ },
+ {
+ .spotlight_attr = "kMDItemDisplayName",
+ .type = ssmt_str,
+ .sparql_attr = "nfo:fileName",
+ },
+ {
+ .spotlight_attr = "kMDItemFSName",
+ .type = ssmt_str,
+ .sparql_attr = "nfo:fileName",
+ },
+ {
+ .spotlight_attr = "kMDItemFSContentChangeDate",
+ .type = ssmt_date,
+ .sparql_attr = "nfo:fileLastModified",
+ },
+ {
+ .spotlight_attr = "kMDItemLastUsedDate",
+ .type = ssmt_date,
+ .sparql_attr = "nfo:fileLastAccessed",
+ },
+
+ /* Common metadata */
+ {
+ .spotlight_attr = "kMDItemTextContent",
+ .type = ssmt_fts,
+ .sparql_attr = "fts:match",
+ },
+ {
+ .spotlight_attr = "kMDItemContentCreationDate",
+ .type = ssmt_date,
+ .sparql_attr = "nie:contentCreated",
+ },
+ {
+ .spotlight_attr = "kMDItemContentModificationDate",
+ .type = ssmt_date,
+ .sparql_attr = "nfo:fileLastModified",
+ },
+ {
+ .spotlight_attr = "kMDItemAttributeChangeDate",
+ .type = ssmt_date,
+ .sparql_attr = "nfo:fileLastModified",
+ },
+ {
+ .spotlight_attr = "kMDItemAuthors",
+ .type = ssmt_str,
+ .sparql_attr = "dc:creator",
+ },
+ {
+ .spotlight_attr = "kMDItemCopyright",
+ .type = ssmt_str,
+ .sparql_attr = "nie:copyright",
+ },
+ {
+ .spotlight_attr = "kMDItemCountry",
+ .type = ssmt_str,
+ .sparql_attr = "nco:country",
+ },
+ {
+ .spotlight_attr = "kMDItemCreator",
+ .type = ssmt_str,
+ .sparql_attr = "dc:creator",
+ },
+ {
+ .spotlight_attr = "kMDItemDurationSeconds",
+ .type = ssmt_num,
+ .sparql_attr = "nfo:duration",
+ },
+ {
+ .spotlight_attr = "kMDItemNumberOfPages",
+ .type = ssmt_num,
+ .sparql_attr = "nfo:pageCount",
+ },
+ {
+ .spotlight_attr = "kMDItemTitle",
+ .type = ssmt_str,
+ .sparql_attr = "nie:title",
+ },
+ {
+ .spotlight_attr = "kMDItemCity",
+ .type = ssmt_str,
+ .sparql_attr = "nco:locality",
+ },
+ {
+ .spotlight_attr = "kMDItemCoverage",
+ .type = ssmt_str,
+ .sparql_attr = "nco:locality",
+ },
+ {
+ .spotlight_attr = "_kMDItemGroupId",
+ .type = ssmt_type,
+ .sparql_attr = NULL,
+ },
+ {
+ .spotlight_attr = "kMDItemContentTypeTree",
+ .type = ssmt_type,
+ .sparql_attr = NULL,
+ },
+ {
+ .spotlight_attr = "kMDItemContentType",
+ .type = ssmt_type,
+ .sparql_attr = NULL,
+ },
+
+ /* Image metadata */
+ {
+ .spotlight_attr = "kMDItemPixelWidth",
+ .type = ssmt_num,
+ .sparql_attr = "nfo:width",
+ },
+ {
+ .spotlight_attr = "kMDItemPixelHeight",
+ .type = ssmt_num,
+ .sparql_attr = "nfo:height",
+ },
+ {
+ .spotlight_attr = "kMDItemColorSpace",
+ .type = ssmt_str,
+ .sparql_attr = "nexif:colorSpace",
+ },
+ {
+ .spotlight_attr = "kMDItemBitsPerSample",
+ .type = ssmt_num,
+ .sparql_attr = "nfo:colorDepth",
+ },
+ {
+ .spotlight_attr = "kMDItemFocalLength",
+ .type = ssmt_num,
+ .sparql_attr = "nmm:focalLength",
+ },
+ {
+ .spotlight_attr = "kMDItemISOSpeed",
+ .type = ssmt_num,
+ .sparql_attr = "nmm:isoSpeed",
+ },
+ {
+ .spotlight_attr = "kMDItemOrientation",
+ .type = ssmt_bool,
+ .sparql_attr = "nfo:orientation",
+ },
+ {
+ .spotlight_attr = "kMDItemResolutionWidthDPI",
+ .type = ssmt_num,
+ .sparql_attr = "nfo:horizontalResolution",
+ },
+ {
+ .spotlight_attr = "kMDItemResolutionHeightDPI",
+ .type = ssmt_num,
+ .sparql_attr = "nfo:verticalResolution",
+ },
+ {
+ .spotlight_attr = "kMDItemExposureTimeSeconds",
+ .type = ssmt_num,
+ .sparql_attr = "nmm:exposureTime",
+ },
+
+ /* Audio metadata */
+ {
+ .spotlight_attr = "kMDItemComposer",
+ .type = ssmt_str,
+ .sparql_attr = "nmm:composer",
+ },
+ {
+ .spotlight_attr = "kMDItemMusicalGenre",
+ .type = ssmt_str,
+ .sparql_attr = "nfo:genre",
+ },
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(spotlight_sparql_attr_map); i++) {
+ const struct sl_attr_map *m = &spotlight_sparql_attr_map[i];
+ int cmp;
+
+ cmp = strcmp(m->spotlight_attr, sl_attr);
+ if (cmp == 0) {
+ return m;
+ }
+ }
+
+ return NULL;
+}
+
+const struct sl_type_map *sl_type_map_by_spotlight(const char *sl_type)
+{
+ static const struct sl_type_map spotlight_sparql_type_map[] = {
+ {
+ .spotlight_type = "1",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nmo#Email",
+ },
+ {
+ .spotlight_type = "2",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#Contact",
+ },
+ {
+ .spotlight_type = "3",
+ .type = kMDTypeMapNotSup,
+ .sparql_type = NULL, /*PrefPane*/
+ },
+ {
+ .spotlight_type = "4",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Font",
+ },
+ {
+ .spotlight_type = "5",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Bookmark",
+ },
+ {
+ .spotlight_type = "6",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nco#Contact",
+ },
+ {
+ .spotlight_type = "7",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Video",
+ },
+ {
+ .spotlight_type = "8",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Executable",
+ },
+ {
+ .spotlight_type = "9",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Folder",
+ },
+ {
+ .spotlight_type = "10",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Audio",
+ },
+ {
+ .spotlight_type = "11",
+ .type = kMDTypeMapMime,
+ .sparql_type = "application/pdf",
+ },
+ {
+ .spotlight_type = "12",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Presentation",
+ },
+ {
+ .spotlight_type = "13",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Image",
+ },
+ {
+ .spotlight_type = "public.jpeg",
+ .type = kMDTypeMapMime,
+ .sparql_type = "image/jpeg",
+ },
+ {
+ .spotlight_type = "public.tiff",
+ .type = kMDTypeMapMime,
+ .sparql_type = "image/tiff",
+ },
+ {
+ .spotlight_type = "com.compuserve.gif",
+ .type = kMDTypeMapMime,
+ .sparql_type = "image/gif",
+ },
+ {
+ .spotlight_type = "public.png",
+ .type = kMDTypeMapMime,
+ .sparql_type = "image/png",
+ },
+ {
+ .spotlight_type = "com.microsoft.bmp",
+ .type = kMDTypeMapMime,
+ .sparql_type = "image/bmp",
+ },
+ {
+ .spotlight_type = "public.content",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Document",
+ },
+ {
+ .spotlight_type = "public.mp3",
+ .type = kMDTypeMapMime,
+ .sparql_type = "audio/mpeg",
+ },
+ {
+ .spotlight_type = "public.mpeg-4-audio",
+ .type = kMDTypeMapMime,
+ .sparql_type = "audio/x-aac",
+ },
+ {
+ .spotlight_type = "com.apple.application",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Software",
+ },
+ {
+ .spotlight_type = "public.text",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#TextDocument",
+ },
+ {
+ .spotlight_type = "public.plain-text",
+ .type = kMDTypeMapMime,
+ .sparql_type = "text/plain",
+ },
+ {
+ .spotlight_type = "public.rtf",
+ .type = kMDTypeMapMime,
+ .sparql_type = "text/rtf",
+ },
+ {
+ .spotlight_type = "public.html",
+ .type = kMDTypeMapMime,
+ .sparql_type = "text/html",
+ },
+ {
+ .spotlight_type = "public.xml",
+ .type = kMDTypeMapMime,
+ .sparql_type = "text/xml",
+ },
+ {
+ .spotlight_type = "public.source-code",
+ .type = kMDTypeMapRDF,
+ .sparql_type = "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#SourceCode",
+ },
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(spotlight_sparql_type_map); i++) {
+ const struct sl_type_map *m = &spotlight_sparql_type_map[i];
+ int cmp;
+
+ cmp = strcmp(m->spotlight_type, sl_type);
+ if (cmp == 0) {
+ return m;
+ }
+ }
+
+ return NULL;
+}
diff --git a/source3/rpc_server/mdssvc/sparql_mapping.h b/source3/rpc_server/mdssvc/sparql_mapping.h
new file mode 100644
index 0000000..14fab67
--- /dev/null
+++ b/source3/rpc_server/mdssvc/sparql_mapping.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (c) 2012 Ralph Boehme
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*/
+
+#ifndef SPOTLIGHT_SPARQL_MAP_H
+#define SPOTLIGHT_SPARQL_MAP_H
+
+enum ssm_type {
+ ssmt_bool, /* a boolean value that doesn't requires a SPARQL FILTER */
+ ssmt_num, /* a numeric value that requires a SPARQL FILTER */
+ ssmt_str, /* a string value that requires a SPARQL FILTER */
+ ssmt_fts, /* a string value that will be queried with SPARQL 'fts:match' */
+ ssmt_date, /* date values are handled in a special map function map_daterange() */
+ ssmt_type /* kMDItemContentType, requires special mapping */
+};
+
+struct sl_attr_map {
+ const char *spotlight_attr;
+ enum ssm_type type;
+ const char *sparql_attr;
+};
+
+enum kMDTypeMap {
+ kMDTypeMapNotSup, /* not supported */
+ kMDTypeMapRDF, /* query with rdf:type */
+ kMDTypeMapMime /* query with nie:mimeType */
+};
+
+struct sl_type_map {
+ /*
+ * MD query value of attributes '_kMDItemGroupId' and
+ * 'kMDItemContentTypeTree
+ */
+ const char *spotlight_type;
+
+ /*
+ * Whether SPARQL query must search attribute rdf:type or
+ * nie:mime_Type
+ */
+ enum kMDTypeMap type;
+
+ /* the SPARQL query match string */
+ const char *sparql_type;
+};
+
+const struct sl_attr_map *sl_attr_map_by_spotlight(const char *sl_attr);
+const struct sl_type_map *sl_type_map_by_spotlight(const char *sl_type);
+#endif
diff --git a/source3/rpc_server/mdssvc/sparql_parser.y b/source3/rpc_server/mdssvc/sparql_parser.y
new file mode 100644
index 0000000..68d4d87
--- /dev/null
+++ b/source3/rpc_server/mdssvc/sparql_parser.y
@@ -0,0 +1,483 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main metadata server / Spotlight routines
+
+ Copyright (C) Ralph Boehme 2012-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+%{
+ #include "includes.h"
+ #include "rpc_server/mdssvc/mdssvc.h"
+ #include "rpc_server/mdssvc/mdssvc_tracker.h"
+ #include "rpc_server/mdssvc/sparql_parser.tab.h"
+ #include "rpc_server/mdssvc/sparql_mapping.h"
+
+ #define YYMALLOC SMB_MALLOC
+ #define YYREALLOC SMB_REALLOC
+
+ struct yy_buffer_state;
+ typedef struct yy_buffer_state *YY_BUFFER_STATE;
+ extern int mdsyylex (void);
+ extern void mdsyyerror (char const *);
+ extern void *mdsyyterminate(void);
+ extern YY_BUFFER_STATE mdsyy_scan_string( const char *str);
+ extern void mdsyy_delete_buffer ( YY_BUFFER_STATE buffer );
+
+ /* forward declarations */
+ static const char *map_expr(const char *attr, char op, const char *val);
+ static const char *map_daterange(const char *dateattr,
+ time_t date1, time_t date2);
+ static time_t isodate2unix(const char *s);
+
+ /* global vars, eg needed by the lexer */
+ struct sparql_parser_state {
+ TALLOC_CTX *frame;
+ YY_BUFFER_STATE s;
+ char var;
+ const char *result;
+ } *global_sparql_parser_state;
+%}
+
+%code provides {
+ #include <stdbool.h>
+ #include "rpc_server/mdssvc/mdssvc.h"
+ #define SPRAW_TIME_OFFSET 978307200
+ extern int mdsyywrap(void);
+ extern bool map_spotlight_to_sparql_query(struct sl_query *slq);
+}
+
+%union {
+ int ival;
+ const char *sval;
+ bool bval;
+ time_t tval;
+}
+
+%name-prefix "mdsyy"
+%expect 5
+%error-verbose
+
+%type <sval> match expr line function
+%type <tval> date
+
+%token <sval> WORD
+%token <bval> BOOL
+%token FUNC_INRANGE
+%token DATE_ISO
+%token OBRACE CBRACE EQUAL UNEQUAL GT LT COMMA QUOTE
+%left AND
+%left OR
+%%
+
+input:
+/* empty */
+| input line
+;
+
+line:
+expr {
+ global_sparql_parser_state->result = $1;
+}
+;
+
+expr:
+BOOL {
+ /*
+ * We can't properly handle these in expressions, fortunately this
+ * is probably only ever used by OS X as sole element in an
+ * expression ie "False" (when Finder window selected our share
+ * but no search string entered yet). Packet traces showed that OS
+ * X Spotlight server then returns a failure (ie -1) which is what
+ * we do here too by calling YYABORT.
+ */
+ YYABORT;
+}
+/*
+ * We have "match OR match" and "expr OR expr", because the former is
+ * supposed to catch and coalesque expressions of the form
+ *
+ * MDSattribute1="hello"||MDSattribute2="hello"
+ *
+ * into a single SPARQL expression for the case where both
+ * MDSattribute1 and MDSattribute2 map to the same SPARQL attribute,
+ * which is eg the case for "*" and "kMDItemTextContent" which both
+ * map to SPARQL "fts:match".
+ */
+
+| match OR match {
+ if (strcmp($1, $3) != 0) {
+ $$ = talloc_asprintf(talloc_tos(), "{ %s } UNION { %s }", $1, $3);
+ } else {
+ $$ = talloc_asprintf(talloc_tos(), "%s", $1);
+ }
+}
+| match {
+ $$ = $1;
+}
+| function {
+ $$ = $1;
+}
+| OBRACE expr CBRACE {
+ $$ = talloc_asprintf(talloc_tos(), "%s", $2);
+}
+| expr AND expr {
+ $$ = talloc_asprintf(talloc_tos(), "%s . %s", $1, $3);
+}
+| expr OR expr {
+ if (strcmp($1, $3) != 0) {
+ $$ = talloc_asprintf(talloc_tos(), "{ %s } UNION { %s }", $1, $3);
+ } else {
+ $$ = talloc_asprintf(talloc_tos(), "%s", $1);
+ }
+}
+;
+
+match:
+WORD EQUAL QUOTE WORD QUOTE {
+ $$ = map_expr($1, '=', $4);
+ if ($$ == NULL) YYABORT;
+}
+| WORD UNEQUAL QUOTE WORD QUOTE {
+ $$ = map_expr($1, '!', $4);
+ if ($$ == NULL) YYABORT;
+}
+| WORD LT QUOTE WORD QUOTE {
+ $$ = map_expr($1, '<', $4);
+ if ($$ == NULL) YYABORT;
+}
+| WORD GT QUOTE WORD QUOTE {
+ $$ = map_expr($1, '>', $4);
+ if ($$ == NULL) YYABORT;
+}
+| WORD EQUAL QUOTE WORD QUOTE WORD {
+ $$ = map_expr($1, '=', $4);
+ if ($$ == NULL) YYABORT;
+}
+| WORD UNEQUAL QUOTE WORD QUOTE WORD {
+ $$ = map_expr($1, '!', $4);
+ if ($$ == NULL) YYABORT;
+}
+| WORD LT QUOTE WORD QUOTE WORD {
+ $$ = map_expr($1, '<', $4);
+ if ($$ == NULL) YYABORT;
+}
+| WORD GT QUOTE WORD QUOTE WORD {
+ $$ = map_expr($1, '>', $4);
+ if ($$ == NULL) YYABORT;
+}
+;
+
+function:
+FUNC_INRANGE OBRACE WORD COMMA date COMMA date CBRACE {
+ $$ = map_daterange($3, $5, $7);
+ if ($$ == NULL) YYABORT;
+}
+;
+
+date:
+DATE_ISO OBRACE WORD CBRACE {$$ = isodate2unix($3);}
+| WORD {$$ = atoi($1) + SPRAW_TIME_OFFSET;}
+;
+
+%%
+
+static time_t isodate2unix(const char *s)
+{
+ struct tm tm = {};
+ const char *p;
+
+ p = strptime(s, "%Y-%m-%dT%H:%M:%SZ", &tm);
+ if (p == NULL) {
+ return (time_t)-1;
+ }
+ return mktime(&tm);
+}
+
+static const char *map_daterange(const char *dateattr,
+ time_t date1, time_t date2)
+{
+ struct sparql_parser_state *s = global_sparql_parser_state;
+ int result = 0;
+ char *sparql = NULL;
+ const struct sl_attr_map *p;
+ struct tm *tmp;
+ char buf1[64], buf2[64];
+
+ if (s->var == 'z') {
+ return NULL;
+ }
+
+ tmp = localtime(&date1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ result = strftime(buf1, sizeof(buf1), "%Y-%m-%dT%H:%M:%SZ", tmp);
+ if (result == 0) {
+ return NULL;
+ }
+
+ tmp = localtime(&date2);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ result = strftime(buf2, sizeof(buf2), "%Y-%m-%dT%H:%M:%SZ", tmp);
+ if (result == 0) {
+ return NULL;
+ }
+
+ p = sl_attr_map_by_spotlight(dateattr);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ sparql = talloc_asprintf(talloc_tos(),
+ "?obj %s ?%c FILTER (?%c > '%s' && ?%c < '%s')",
+ p->sparql_attr,
+ s->var,
+ s->var,
+ buf1,
+ s->var,
+ buf2);
+ if (sparql == NULL) {
+ return NULL;
+ }
+
+ s->var++;
+ return sparql;
+}
+
+static char *map_type_search(const char *attr, char op, const char *val)
+{
+ char *result = NULL;
+ const char *sparqlAttr;
+ const struct sl_type_map *p;
+
+ p = sl_type_map_by_spotlight(val);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ switch (p->type) {
+ case kMDTypeMapRDF:
+ sparqlAttr = "rdf:type";
+ break;
+ case kMDTypeMapMime:
+ sparqlAttr = "nie:mimeType";
+ break;
+ default:
+ return NULL;
+ }
+
+ result = talloc_asprintf(talloc_tos(), "?obj %s '%s'",
+ sparqlAttr,
+ p->sparql_type);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ return result;
+}
+
+static const char *map_expr(const char *attr, char op, const char *val)
+{
+ struct sparql_parser_state *s = global_sparql_parser_state;
+ int result = 0;
+ char *sparql = NULL;
+ const struct sl_attr_map *p;
+ time_t t;
+ struct tm *tmp;
+ char buf1[64];
+ char *q;
+ const char *start;
+
+ if (s->var == 'z') {
+ return NULL;
+ }
+
+ p = sl_attr_map_by_spotlight(attr);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ if ((p->type != ssmt_type) && (p->sparql_attr == NULL)) {
+ yyerror("unsupported Spotlight attribute");
+ return NULL;
+ }
+
+ switch (p->type) {
+ case ssmt_bool:
+ sparql = talloc_asprintf(talloc_tos(), "?obj %s '%s'",
+ p->sparql_attr, val);
+ if (sparql == NULL) {
+ return NULL;
+ }
+ break;
+
+ case ssmt_num:
+ sparql = talloc_asprintf(talloc_tos(),
+ "?obj %s ?%c FILTER(?%c %c%c '%s')",
+ p->sparql_attr,
+ s->var,
+ s->var,
+ op,
+ /* append '=' to '!' */
+ op == '!' ? '=' : ' ',
+ val);
+ if (sparql == NULL) {
+ return NULL;
+ }
+ s->var++;
+ break;
+
+ case ssmt_str:
+ q = talloc_strdup(talloc_tos(), "");
+ if (q == NULL) {
+ return NULL;
+ }
+ start = val;
+ while (*val) {
+ if (*val != '*') {
+ val++;
+ continue;
+ }
+ if (val > start) {
+ q = talloc_strndup_append(q, start, val - start);
+ if (q == NULL) {
+ return NULL;
+ }
+ }
+ q = talloc_strdup_append(q, ".*");
+ if (q == NULL) {
+ return NULL;
+ }
+ val++;
+ start = val;
+ }
+ if (val > start) {
+ q = talloc_strndup_append(q, start, val - start);
+ if (q == NULL) {
+ return NULL;
+ }
+ }
+ sparql = talloc_asprintf(talloc_tos(),
+ "?obj %s ?%c "
+ "FILTER(regex(?%c, '^%s$', 'i'))",
+ p->sparql_attr,
+ s->var,
+ s->var,
+ q);
+ TALLOC_FREE(q);
+ if (sparql == NULL) {
+ return NULL;
+ }
+ s->var++;
+ break;
+
+ case ssmt_fts:
+ sparql = talloc_asprintf(talloc_tos(), "?obj %s '%s'",
+ p->sparql_attr, val);
+ if (sparql == NULL) {
+ return NULL;
+ }
+ break;
+
+ case ssmt_date:
+ t = atoi(val) + SPRAW_TIME_OFFSET;
+ tmp = localtime(&t);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ result = strftime(buf1, sizeof(buf1),
+ "%Y-%m-%dT%H:%M:%SZ", tmp);
+ if (result == 0) {
+ return NULL;
+ }
+ sparql = talloc_asprintf(talloc_tos(),
+ "?obj %s ?%c FILTER(?%c %c '%s')",
+ p->sparql_attr,
+ s->var,
+ s->var,
+ op,
+ buf1);
+ if (sparql == NULL) {
+ return NULL;
+ }
+ s->var++;
+ break;
+
+ case ssmt_type:
+ sparql = map_type_search(attr, op, val);
+ if (sparql == NULL) {
+ return NULL;
+ }
+ break;
+
+ default:
+ return NULL;
+ }
+
+ return sparql;
+}
+
+void mdsyyerror(const char *str)
+{
+ DEBUG(1, ("mdsyyerror: %s\n", str));
+}
+
+int mdsyywrap(void)
+{
+ return 1;
+}
+
+/**
+ * Map a Spotlight RAW query string to a SPARQL query string
+ **/
+bool map_spotlight_to_sparql_query(struct sl_query *slq)
+{
+ struct sl_tracker_query *tq = talloc_get_type_abort(
+ slq->backend_private, struct sl_tracker_query);
+ struct sparql_parser_state s = {
+ .frame = talloc_stackframe(),
+ .var = 'a',
+ };
+ int result;
+
+ s.s = mdsyy_scan_string(slq->query_string);
+ if (s.s == NULL) {
+ TALLOC_FREE(s.frame);
+ return false;
+ }
+ global_sparql_parser_state = &s;
+ result = mdsyyparse();
+ global_sparql_parser_state = NULL;
+ mdsyy_delete_buffer(s.s);
+
+ if (result != 0) {
+ TALLOC_FREE(s.frame);
+ return false;
+ }
+
+ tq->sparql_query = talloc_asprintf(slq,
+ "SELECT ?url WHERE { %s . ?obj nie:url ?url . "
+ "FILTER(tracker:uri-is-descendant('file://%s/', ?url)) }",
+ s.result, tq->path_scope);
+ TALLOC_FREE(s.frame);
+ if (tq->sparql_query == NULL) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/rpc_server/mdssvc/sparql_parser_test.c b/source3/rpc_server/mdssvc/sparql_parser_test.c
new file mode 100644
index 0000000..0a0f625
--- /dev/null
+++ b/source3/rpc_server/mdssvc/sparql_parser_test.c
@@ -0,0 +1,47 @@
+#include "includes.h"
+#include "mdssvc.h"
+#include "rpc_server/mdssvc/sparql_parser.tab.h"
+#include "rpc_server/mdssvc/mdssvc_tracker.h"
+
+/*
+ * Examples:
+ *
+ * $ ./spotlight2sparql '_kMDItemGroupId=="11"'
+ * ...
+ * $ ./spotlight2sparql '*=="test*"cwd||kMDItemTextContent=="test*"cwd'
+ * ...
+ */
+
+int main(int argc, char **argv)
+{
+ struct sl_tracker_query *tq = NULL;
+ bool ok;
+ struct sl_query *slq;
+
+ if (argc != 2) {
+ printf("usage: %s QUERY\n", argv[0]);
+ return 1;
+ }
+
+ slq = talloc_zero(NULL, struct sl_query);
+ if (slq == NULL) {
+ printf("talloc error\n");
+ return 1;
+ }
+
+ slq->query_string = argv[1];
+ slq->path_scope = "/foo/bar";
+
+ tq = talloc_zero(slq, struct sl_tracker_query);
+ if (tq == NULL) {
+ printf("talloc error\n");
+ return 1;
+ }
+ slq->backend_private = tq;
+
+ ok = map_spotlight_to_sparql_query(slq);
+ printf("%s\n", ok ? tq->sparql_query : "*mapping failed*");
+
+ talloc_free(slq);
+ return ok ? 0 : 1;
+}
diff --git a/source3/rpc_server/mdssvc/srv_mdssvc_nt.c b/source3/rpc_server/mdssvc/srv_mdssvc_nt.c
new file mode 100644
index 0000000..9a16624
--- /dev/null
+++ b/source3/rpc_server/mdssvc/srv_mdssvc_nt.c
@@ -0,0 +1,319 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines for mdssvc
+ * Copyright (C) Ralph Boehme 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "messages.h"
+#include "ntdomain.h"
+#include "rpc_server/rpc_server.h"
+#include "rpc_server/rpc_config.h"
+#include "rpc_server/mdssvc/srv_mdssvc_nt.h"
+#include "libcli/security/security_token.h"
+#include "libcli/security/dom_sid.h"
+#include "gen_ndr/auth.h"
+#include "mdssvc.h"
+#include "smbd/globals.h"
+
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_mdssvc.h"
+#include "librpc/gen_ndr/ndr_mdssvc_scompat.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+static NTSTATUS create_mdssvc_policy_handle(TALLOC_CTX *mem_ctx,
+ struct pipes_struct *p,
+ int snum,
+ const char *sharename,
+ const char *path,
+ struct policy_handle *handle)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct mds_ctx *mds_ctx;
+ NTSTATUS status;
+
+ ZERO_STRUCTP(handle);
+
+ status = mds_init_ctx(mem_ctx,
+ messaging_tevent_context(p->msg_ctx),
+ p->msg_ctx,
+ session_info,
+ snum,
+ sharename,
+ path,
+ &mds_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("mds_init_ctx() path [%s] failed: %s\n",
+ path, nt_errstr(status));
+ return status;
+ }
+
+ if (!create_policy_hnd(p, handle, 0, mds_ctx)) {
+ talloc_free(mds_ctx);
+ ZERO_STRUCTP(handle);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+void _mdssvc_open(struct pipes_struct *p, struct mdssvc_open *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ char *outpath = discard_const_p(char, r->out.share_path);
+ char *fake_path = NULL;
+ char *path;
+ NTSTATUS status;
+
+ DBG_DEBUG("[%s]\n", r->in.share_name);
+
+ *r->out.device_id = *r->in.device_id;
+ *r->out.unkn2 = *r->in.unkn2;
+ *r->out.unkn3 = *r->in.unkn3;
+ outpath[0] = '\0';
+
+ snum = lp_servicenumber(r->in.share_name);
+ if (!VALID_SNUM(snum)) {
+ return;
+ }
+
+ path = lp_path(talloc_tos(), lp_sub, snum);
+ if (path == NULL) {
+ DBG_ERR("Couldn't create path for %s\n",
+ r->in.share_name);
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return;
+ }
+
+ fake_path = talloc_asprintf(p->mem_ctx, "/%s", r->in.share_name);
+ if (fake_path == NULL) {
+ DBG_ERR("Couldn't create fake share path for %s\n",
+ r->in.share_name);
+ talloc_free(path);
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return;
+ }
+
+ status = create_mdssvc_policy_handle(p->mem_ctx, p,
+ snum,
+ r->in.share_name,
+ path,
+ r->out.handle);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_VOLUME)) {
+ ZERO_STRUCTP(r->out.handle);
+ talloc_free(path);
+ talloc_free(fake_path);
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Couldn't create policy handle for %s\n",
+ r->in.share_name);
+ talloc_free(path);
+ talloc_free(fake_path);
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return;
+ }
+
+ strlcpy(outpath, fake_path, 1024);
+ talloc_free(path);
+ talloc_free(fake_path);
+ return;
+}
+
+void _mdssvc_unknown1(struct pipes_struct *p, struct mdssvc_unknown1 *r)
+{
+ struct mds_ctx *mds_ctx;
+ NTSTATUS status;
+
+ mds_ctx = find_policy_by_hnd(p,
+ r->in.handle,
+ DCESRV_HANDLE_ANY,
+ struct mds_ctx,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (ndr_policy_handle_empty(r->in.handle)) {
+ p->fault_state = 0;
+ } else {
+ p->fault_state = DCERPC_NCA_S_PROTO_ERROR;
+ }
+ *r->out.status = 0;
+ *r->out.flags = 0;
+ *r->out.unkn7 = 0;
+ return;
+ }
+
+ DEBUG(10, ("%s: path: %s\n", __func__, mds_ctx->spath));
+
+ *r->out.status = 0;
+ *r->out.flags = 0x6b000001;
+ *r->out.unkn7 = 0;
+
+ return;
+}
+
+void _mdssvc_cmd(struct pipes_struct *p, struct mdssvc_cmd *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ bool ok;
+ struct mds_ctx *mds_ctx;
+ NTSTATUS status;
+
+ mds_ctx = find_policy_by_hnd(p,
+ r->in.handle,
+ DCESRV_HANDLE_ANY,
+ struct mds_ctx,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (ndr_policy_handle_empty(r->in.handle)) {
+ p->fault_state = 0;
+ } else {
+ p->fault_state = DCERPC_NCA_S_PROTO_ERROR;
+ }
+ r->out.response_blob->size = 0;
+ *r->out.fragment = 0;
+ *r->out.unkn9 = 0;
+ return;
+ }
+
+ DEBUG(10, ("%s: path: %s\n", __func__, mds_ctx->spath));
+
+ ok = security_token_is_sid(session_info->security_token,
+ &mds_ctx->sid);
+ if (!ok) {
+ struct dom_sid_buf buf;
+ DBG_WARNING("not the same sid: %s\n",
+ dom_sid_str_buf(&mds_ctx->sid, &buf));
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return;
+ }
+
+ if (geteuid() != mds_ctx->uid) {
+ DEBUG(0, ("uid mismatch: %d/%d\n", geteuid(), mds_ctx->uid));
+ smb_panic("uid mismatch");
+ }
+
+ if (r->in.request_blob.size > MAX_SL_FRAGMENT_SIZE) {
+ DEBUG(1, ("%s: request size too large\n", __func__));
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return;
+ }
+
+ if (r->in.request_blob.length > MAX_SL_FRAGMENT_SIZE) {
+ DEBUG(1, ("%s: request length too large\n", __func__));
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return;
+ }
+
+ if (r->in.max_fragment_size1 > MAX_SL_FRAGMENT_SIZE) {
+ DEBUG(1, ("%s: request fragment size too large: %u\n",
+ __func__, (unsigned)r->in.max_fragment_size1));
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return;
+ }
+
+ /* We currently don't use fragmentation at the mdssvc RPC layer */
+ *r->out.fragment = 0;
+
+ ok = mds_dispatch(mds_ctx,
+ &r->in.request_blob,
+ r->out.response_blob,
+ r->in.max_fragment_size1);
+ if (ok) {
+ *r->out.unkn9 = 0;
+ } else {
+ /* FIXME: just interpolating from AFP, needs verification */
+ *r->out.unkn9 = UINT32_MAX;
+ }
+
+ return;
+}
+
+void _mdssvc_close(struct pipes_struct *p, struct mdssvc_close *r)
+{
+ struct mds_ctx *mds_ctx;
+ NTSTATUS status;
+
+ mds_ctx = find_policy_by_hnd(p,
+ r->in.in_handle,
+ DCESRV_HANDLE_ANY,
+ struct mds_ctx,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("invalid handle\n");
+ if (ndr_policy_handle_empty(r->in.in_handle)) {
+ p->fault_state = 0;
+ } else {
+ p->fault_state = DCERPC_NCA_S_PROTO_ERROR;
+ }
+ return;
+ }
+
+ DBG_DEBUG("Close mdssvc handle for path: %s\n", mds_ctx->spath);
+ TALLOC_FREE(mds_ctx);
+
+ *r->out.out_handle = *r->in.in_handle;
+ close_policy_hnd(p, r->in.in_handle);
+
+ *r->out.status = 0;
+
+ return;
+}
+
+static NTSTATUS mdssvc__op_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+static NTSTATUS mdssvc__op_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+#define DCESRV_INTERFACE_MDSSVC_INIT_SERVER \
+ mdssvc_init_server
+
+#define DCESRV_INTERFACE_MDSSVC_SHUTDOWN_SERVER \
+ mdssvc_shutdown_server
+
+static NTSTATUS mdssvc_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ bool ok;
+
+ ok = mds_init(msg_ctx);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return mdssvc__op_init_server(dce_ctx, ep_server);
+}
+
+static NTSTATUS mdssvc_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ mds_shutdown();
+
+ return mdssvc__op_shutdown_server(dce_ctx, ep_server);
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_mdssvc_scompat.c"
diff --git a/source3/rpc_server/mdssvc/srv_mdssvc_nt.h b/source3/rpc_server/mdssvc/srv_mdssvc_nt.h
new file mode 100644
index 0000000..8b78f5e
--- /dev/null
+++ b/source3/rpc_server/mdssvc/srv_mdssvc_nt.h
@@ -0,0 +1,27 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * MDSSVC RPC pipe initialisation routines
+ *
+ * Copyright (C) Ralph Boehme 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SRV_MDSSVC_NT_H
+#define _SRV_MDSSVC_NT_H
+
+bool init_service_mdssvc(struct messaging_context *msg_ctx);
+bool shutdown_service_mdssvc(void);
+
+#endif /* _SRV_MDSSVC_NT_H */
diff --git a/source3/rpc_server/mdssvc/test_mdsparser_es.c b/source3/rpc_server/mdssvc/test_mdsparser_es.c
new file mode 100644
index 0000000..02270a9
--- /dev/null
+++ b/source3/rpc_server/mdssvc/test_mdsparser_es.c
@@ -0,0 +1,304 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Copyright (C) Ralph Boehme 2019
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include <setjmp.h>
+#include <cmocka.h>
+#include <jansson.h>
+#include <talloc.h>
+#include "lib/cmdline/cmdline.h"
+#include "libcli/util/ntstatus.h"
+#include "lib/util/samba_util.h"
+#include "lib/torture/torture.h"
+#include "lib/param/param.h"
+#include "rpc_server/mdssvc/es_parser.tab.h"
+
+#define PATH_QUERY_SUBEXPR \
+ " AND path.real.fulltext:\\\"/foo/bar\\\""
+
+static struct {
+ const char *mds;
+ const char *es;
+} map[] = {
+ {
+ "*==\"samba\"",
+ "(samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemTextContent==\"samba\"",
+ "(content:samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "_kMDItemGroupId==\"11\"",
+ "(file.content_type:(application\\\\/pdf))" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemContentType==\"1\"",
+ "(file.content_type:(message\\\\/rfc822))" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemContentType==\"public.content\"",
+ "(file.content_type:(message\\\\/rfc822 application\\\\/pdf application\\\\/vnd.oasis.opendocument.presentation image\\\\/* text\\\\/*))" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemContentTypeTree==\"1\"",
+ "(file.content_type:(message\\\\/rfc822))" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSContentChangeDate==$time.iso(2018-10-01T10:00:00Z)",
+ "(file.last_modified:2018\\\\-10\\\\-01T10\\\\:00\\\\:00Z)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSContentChangeDate==\"1\"",
+ "(file.last_modified:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSCreationDate==\"1\"",
+ "(file.created:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSName==\"samba*\"",
+ "(file.filename:samba*)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSOwnerGroupID==\"0\"",
+ "(attributes.owner:0)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSOwnerUserID==\"0\"",
+ "(attributes.group:0)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSSize==\"1\"",
+ "(file.filesize:1)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemPath==\"/foo/bar\"",
+ "(path.real:\\\\/foo\\\\/bar)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemAttributeChangeDate==\"1\"",
+ "(file.last_modified:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemAuthors==\"Chouka\"",
+ "(meta.author:Chouka)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemContentCreationDate==\"1\"",
+ "(file.created:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemContentModificationDate==\"1\"",
+ "(file.last_modified:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemCreator==\"Chouka\"",
+ "(meta.raw.creator:Chouka)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemDescription==\"Dog\"",
+ "(meta.raw.description:Dog)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemDisplayName==\"Samba\"",
+ "(file.filename:Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemDurationSeconds==\"1\"",
+ "(meta.raw.xmpDM\\\\:duration:1)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemNumberOfPages==\"1\"",
+ "(meta.raw.xmpTPg\\\\:NPages:1)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemTitle==\"Samba\"",
+ "(meta.title:Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemAlbum==\"Red Roses for Me\"",
+ "(meta.raw.xmpDM\\\\:album:Red\\\\ Roses\\\\ for\\\\ Me)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemBitsPerSample==\"1\"",
+ "(meta.raw.tiff\\\\:BitsPerSample:1)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemPixelHeight==\"1\"",
+ "(meta.raw.Image\\\\ Height:1)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemPixelWidth==\"1\"",
+ "(meta.raw.Image\\\\ Width:1)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemResolutionHeightDPI==\"72\"",
+ "(meta.raw.Y\\\\ Resolution:72)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemResolutionWidthDPI==\"72\"",
+ "(meta.raw.X\\\\ Resolution:72)" PATH_QUERY_SUBEXPR
+ },{
+ "*!=\"samba\"",
+ "((NOT samba))" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSSize!=\"1\"",
+ "((NOT file.filesize:1))" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSSize>\"1\"",
+ "(file.filesize:{1 TO *})" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSSize<\"1\"",
+ "(file.filesize:{* TO 1})" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSCreationDate!=\"1\"",
+ "((NOT file.created:2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z))" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSCreationDate>\"1\"",
+ "(file.created:{2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z TO *})" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSCreationDate<\"1\"",
+ "(file.created:{* TO 2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z})" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSName==\"Samba\"||kMDItemTextContent==\"Samba\"",
+ "(file.filename:Samba OR content:Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "kMDItemFSName==\"Samba\"&&kMDItemTextContent==\"Samba\"",
+ "((file.filename:Samba) AND (content:Samba))" PATH_QUERY_SUBEXPR
+ }, {
+ "InRange(kMDItemFSCreationDate,1,2)",
+ "(file.created:[2001\\\\-01\\\\-01T00\\\\:00\\\\:01Z TO 2001\\\\-01\\\\-01T00\\\\:00\\\\:02Z])" PATH_QUERY_SUBEXPR
+ }, {
+ "InRange(kMDItemFSSize,1,2)",
+ "(file.filesize:[1 TO 2])" PATH_QUERY_SUBEXPR
+ }
+};
+
+static struct {
+ const char *mds;
+ const char *es;
+} map_ignore_failures[] = {
+ {
+ "*==\"Samba\"||foo==\"bar\"",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "*==\"Samba\"&&foo==\"bar\"",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "*==\"Samba\"||kMDItemContentType==\"666\"",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "*==\"Samba\"&&kMDItemContentType==\"666\"",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "*==\"Samba\"||foo==\"bar\"||kMDItemContentType==\"666\"",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "*==\"Samba\"&&foo==\"bar\"&&kMDItemContentType==\"666\"",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "foo==\"bar\"||kMDItemContentType==\"666\"||*==\"Samba\"||x!=\"6\"",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "*==\"Samba\"||InRange(foo,1,2)",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }, {
+ "*==\"Samba\"||foo==$time.iso(2018-10-01T10:00:00Z)",
+ "(Samba)" PATH_QUERY_SUBEXPR
+ }
+};
+
+static void test_mdsparser_es(void **state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *path_scope = "/foo/bar";
+ char *es_query = NULL;
+ const char *path = NULL;
+ json_t *mappings = NULL;
+ json_error_t json_error;
+ int i;
+ bool ok;
+
+ path = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+ "elasticsearch",
+ "mappings",
+ NULL);
+ assert_non_null(path);
+
+ mappings = json_load_file(path, 0, &json_error);
+ assert_non_null(mappings);
+
+ for (i = 0; i < ARRAY_SIZE(map); i++) {
+ DBG_DEBUG("Mapping: %s\n", map[i].mds);
+ ok = map_spotlight_to_es_query(frame,
+ mappings,
+ path_scope,
+ map[i].mds,
+ &es_query);
+ assert_true(ok);
+ assert_string_equal(es_query, map[i].es);
+ }
+
+ if (!lp_parm_bool(GLOBAL_SECTION_SNUM,
+ "elasticsearch",
+ "test mapping failures",
+ false))
+ {
+ goto done;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(map_ignore_failures); i++) {
+ DBG_DEBUG("Mapping: %s\n", map_ignore_failures[i].mds);
+ ok = map_spotlight_to_es_query(frame,
+ mappings,
+ path_scope,
+ map_ignore_failures[i].mds,
+ &es_query);
+ assert_true(ok);
+ assert_string_equal(es_query, map_ignore_failures[i].es);
+ }
+
+done:
+ json_decref(mappings);
+ TALLOC_FREE(frame);
+}
+
+int main(int argc, const char *argv[])
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_mdsparser_es),
+ };
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_SAMBA
+ POPT_TABLEEND
+ };
+ poptContext pc;
+ int opt;
+ bool ok;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "1");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "Unknown Option: %c\n", opt);
+ exit(1);
+ }
+ }
+
+ cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/source3/rpc_server/netlogon/srv_netlog_nt.c b/source3/rpc_server/netlogon/srv_netlog_nt.c
new file mode 100644
index 0000000..fa3e597
--- /dev/null
+++ b/source3/rpc_server/netlogon/srv_netlog_nt.c
@@ -0,0 +1,2937 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ * Copyright (C) Paul Ashton 1997.
+ * Copyright (C) Jeremy Allison 1998-2001.
+ * Copyright (C) Andrew Bartlett 2001.
+ * Copyright (C) Guenther Deschner 2008-2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This is the implementation of the netlogon pipe. */
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "ntdomain.h"
+#include "../libcli/auth/schannel.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_netlogon.h"
+#include "librpc/gen_ndr/ndr_netlogon_scompat.h"
+#include "librpc/gen_ndr/ndr_samr_c.h"
+#include "librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "rpc_client/init_lsa.h"
+#include "rpc_client/init_samr.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "../libcli/security/security.h"
+#include "../libcli/security/dom_sid.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "lib/crypto/md4.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "../libcli/registry/util_reg.h"
+#include "passdb.h"
+#include "auth.h"
+#include "messages.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/param/param.h"
+#include "libsmb/dsgetdcname.h"
+#include "lib/util/util_str_escape.h"
+#include "source3/lib/substitute.h"
+#include "librpc/rpc/server/netlogon/schannel_util.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+/*************************************************************************
+ _netr_LogonControl
+ *************************************************************************/
+
+WERROR _netr_LogonControl(struct pipes_struct *p,
+ struct netr_LogonControl *r)
+{
+ struct netr_LogonControl2Ex l;
+
+ switch (r->in.level) {
+ case 1:
+ break;
+ case 2:
+ return WERR_NOT_SUPPORTED;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ switch (r->in.function_code) {
+ case NETLOGON_CONTROL_QUERY:
+ case NETLOGON_CONTROL_REPLICATE:
+ case NETLOGON_CONTROL_SYNCHRONIZE:
+ case NETLOGON_CONTROL_PDC_REPLICATE:
+ case NETLOGON_CONTROL_BREAKPOINT:
+ case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
+ case NETLOGON_CONTROL_TRUNCATE_LOG:
+ break;
+ default:
+ return WERR_NOT_SUPPORTED;
+ }
+
+ l.in.logon_server = r->in.logon_server;
+ l.in.function_code = r->in.function_code;
+ l.in.level = r->in.level;
+ l.in.data = NULL;
+ l.out.query = r->out.query;
+
+ return _netr_LogonControl2Ex(p, &l);
+}
+
+/*************************************************************************
+ _netr_LogonControl2
+ *************************************************************************/
+
+WERROR _netr_LogonControl2(struct pipes_struct *p,
+ struct netr_LogonControl2 *r)
+{
+ struct netr_LogonControl2Ex l;
+
+ l.in.logon_server = r->in.logon_server;
+ l.in.function_code = r->in.function_code;
+ l.in.level = r->in.level;
+ l.in.data = r->in.data;
+ l.out.query = r->out.query;
+
+ return _netr_LogonControl2Ex(p, &l);
+}
+
+/*************************************************************************
+ *************************************************************************/
+
+static bool wb_change_trust_creds(const char *domain, WERROR *tc_status)
+{
+ wbcErr result;
+ struct wbcAuthErrorInfo *error = NULL;
+
+ result = wbcChangeTrustCredentials(domain, &error);
+ switch (result) {
+ case WBC_ERR_WINBIND_NOT_AVAILABLE:
+ return false;
+ case WBC_ERR_DOMAIN_NOT_FOUND:
+ *tc_status = WERR_NO_SUCH_DOMAIN;
+ return true;
+ case WBC_ERR_SUCCESS:
+ *tc_status = WERR_OK;
+ return true;
+ default:
+ break;
+ }
+
+ if (error && error->nt_status != 0) {
+ *tc_status = ntstatus_to_werror(NT_STATUS(error->nt_status));
+ } else {
+ *tc_status = WERR_TRUST_FAILURE;
+ }
+ wbcFreeMemory(error);
+ return true;
+}
+
+/*************************************************************************
+ *************************************************************************/
+
+static bool wb_check_trust_creds(const char *domain, WERROR *tc_status)
+{
+ wbcErr result;
+ struct wbcAuthErrorInfo *error = NULL;
+
+ result = wbcCheckTrustCredentials(domain, &error);
+ switch (result) {
+ case WBC_ERR_WINBIND_NOT_AVAILABLE:
+ return false;
+ case WBC_ERR_DOMAIN_NOT_FOUND:
+ *tc_status = WERR_NO_SUCH_DOMAIN;
+ return true;
+ case WBC_ERR_SUCCESS:
+ *tc_status = WERR_OK;
+ return true;
+ default:
+ break;
+ }
+
+ if (error && error->nt_status != 0) {
+ *tc_status = ntstatus_to_werror(NT_STATUS(error->nt_status));
+ } else {
+ *tc_status = WERR_TRUST_FAILURE;
+ }
+ wbcFreeMemory(error);
+ return true;
+}
+
+/****************************************************************
+ _netr_LogonControl2Ex
+****************************************************************/
+
+WERROR _netr_LogonControl2Ex(struct pipes_struct *p,
+ struct netr_LogonControl2Ex *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ uint32_t flags = 0x0;
+ WERROR pdc_connection_status = WERR_OK;
+ uint32_t logon_attempts = 0x0;
+ WERROR tc_status;
+ fstring dc_name2;
+ const char *dc_name = NULL;
+ struct sockaddr_storage dc_ss;
+ const char *domain = NULL;
+ struct netr_NETLOGON_INFO_1 *info1;
+ struct netr_NETLOGON_INFO_2 *info2;
+ struct netr_NETLOGON_INFO_3 *info3;
+ struct netr_NETLOGON_INFO_4 *info4;
+ const char *fn;
+ NTSTATUS status;
+ struct netr_DsRGetDCNameInfo *dc_info;
+
+ switch (dce_call->pkt.u.request.opnum) {
+ case NDR_NETR_LOGONCONTROL:
+ fn = "_netr_LogonControl";
+ break;
+ case NDR_NETR_LOGONCONTROL2:
+ fn = "_netr_LogonControl2";
+ break;
+ case NDR_NETR_LOGONCONTROL2EX:
+ fn = "_netr_LogonControl2Ex";
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ switch (r->in.function_code) {
+ case NETLOGON_CONTROL_QUERY:
+ break;
+ default:
+ if ((geteuid() != sec_initial_uid()) &&
+ !nt_token_check_domain_rid(
+ session_info->security_token, DOMAIN_RID_ADMINS) &&
+ !nt_token_check_sid(
+ &global_sid_Builtin_Administrators,
+ session_info->security_token))
+ {
+ return WERR_ACCESS_DENIED;
+ }
+ break;
+ }
+
+ tc_status = WERR_NO_SUCH_DOMAIN;
+
+ switch (r->in.function_code) {
+ case NETLOGON_CONTROL_QUERY:
+ switch (r->in.level) {
+ case 1:
+ case 3:
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ tc_status = WERR_OK;
+ break;
+ case NETLOGON_CONTROL_REPLICATE:
+ case NETLOGON_CONTROL_SYNCHRONIZE:
+ case NETLOGON_CONTROL_PDC_REPLICATE:
+ case NETLOGON_CONTROL_BACKUP_CHANGE_LOG:
+ case NETLOGON_CONTROL_BREAKPOINT:
+ case NETLOGON_CONTROL_TRUNCATE_LOG:
+ case NETLOGON_CONTROL_TRANSPORT_NOTIFY:
+ case NETLOGON_CONTROL_FORCE_DNS_REG:
+ case NETLOGON_CONTROL_QUERY_DNS_REG:
+ return WERR_NOT_SUPPORTED;
+
+ case NETLOGON_CONTROL_FIND_USER:
+ if (!r->in.data || !r->in.data->user) {
+ return WERR_NOT_SUPPORTED;
+ }
+ break;
+ case NETLOGON_CONTROL_SET_DBFLAG:
+ if (!r->in.data) {
+ return WERR_NOT_SUPPORTED;
+ }
+ break;
+ case NETLOGON_CONTROL_TC_VERIFY:
+ if (!r->in.data || !r->in.data->domain) {
+ return WERR_NOT_SUPPORTED;
+ }
+
+ if (!wb_check_trust_creds(r->in.data->domain, &tc_status)) {
+ return WERR_NOT_SUPPORTED;
+ }
+ break;
+ case NETLOGON_CONTROL_TC_QUERY:
+ if (!r->in.data || !r->in.data->domain) {
+ return WERR_NOT_SUPPORTED;
+ }
+
+ domain = r->in.data->domain;
+
+ if (!is_trusted_domain(domain)) {
+ break;
+ }
+
+ if (!get_dc_name(domain, NULL, dc_name2, &dc_ss)) {
+ tc_status = WERR_NO_LOGON_SERVERS;
+ break;
+ }
+
+ dc_name = talloc_asprintf(p->mem_ctx, "\\\\%s", dc_name2);
+ if (!dc_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ tc_status = WERR_OK;
+
+ break;
+
+ case NETLOGON_CONTROL_REDISCOVER:
+ if (!r->in.data || !r->in.data->domain) {
+ return WERR_NOT_SUPPORTED;
+ }
+
+ domain = r->in.data->domain;
+
+ if (!is_trusted_domain(domain)) {
+ break;
+ }
+
+ status = dsgetdcname(p->mem_ctx, p->msg_ctx, domain, NULL, NULL,
+ DS_FORCE_REDISCOVERY | DS_RETURN_FLAT_NAME,
+ &dc_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ tc_status = WERR_NO_LOGON_SERVERS;
+ break;
+ }
+
+ dc_name = talloc_asprintf(p->mem_ctx, "\\\\%s", dc_info->dc_unc);
+ if (!dc_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ tc_status = WERR_OK;
+
+ break;
+
+ case NETLOGON_CONTROL_CHANGE_PASSWORD:
+ if (!r->in.data || !r->in.data->domain) {
+ return WERR_NOT_SUPPORTED;
+ }
+
+ if (!wb_change_trust_creds(r->in.data->domain, &tc_status)) {
+ return WERR_NOT_SUPPORTED;
+ }
+ break;
+
+ default:
+ /* no idea what this should be */
+ DEBUG(0,("%s: unimplemented function level [%d]\n",
+ fn, r->in.function_code));
+ return WERR_NOT_SUPPORTED;
+ }
+
+ /* prepare the response */
+
+ switch (r->in.level) {
+ case 1:
+ info1 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_1);
+ W_ERROR_HAVE_NO_MEMORY(info1);
+
+ info1->flags = flags;
+ info1->pdc_connection_status = pdc_connection_status;
+
+ r->out.query->info1 = info1;
+ break;
+ case 2:
+ info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2);
+ W_ERROR_HAVE_NO_MEMORY(info2);
+
+ info2->flags = flags;
+ info2->pdc_connection_status = pdc_connection_status;
+ info2->trusted_dc_name = dc_name;
+ info2->tc_connection_status = tc_status;
+
+ r->out.query->info2 = info2;
+ break;
+ case 3:
+ info3 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_3);
+ W_ERROR_HAVE_NO_MEMORY(info3);
+
+ info3->flags = flags;
+ info3->logon_attempts = logon_attempts;
+
+ r->out.query->info3 = info3;
+ break;
+ case 4:
+ info4 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_4);
+ W_ERROR_HAVE_NO_MEMORY(info4);
+
+ info4->trusted_dc_name = dc_name;
+ info4->trusted_domain_name = r->in.data->domain;
+
+ r->out.query->info4 = info4;
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+/*************************************************************************
+ _netr_NetrEnumerateTrustedDomains
+ *************************************************************************/
+
+NTSTATUS _netr_NetrEnumerateTrustedDomains(struct pipes_struct *p,
+ struct netr_NetrEnumerateTrustedDomains *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ DATA_BLOB blob;
+ size_t num_domains = 0;
+ const char **trusted_domains = NULL;
+ struct lsa_DomainList domain_list;
+ struct dcerpc_binding_handle *h = NULL;
+ struct policy_handle pol;
+ uint32_t enum_ctx = 0;
+ uint32_t max_size = (uint32_t)-1;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ ZERO_STRUCT(pol);
+ DEBUG(6,("_netr_NetrEnumerateTrustedDomains: %d\n", __LINE__));
+
+ status = rpcint_binding_handle(p->mem_ctx,
+ &ndr_table_lsarpc,
+ remote_address,
+ local_address,
+ session_info,
+ p->msg_ctx,
+ &h);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(
+ h,
+ p->mem_ctx,
+ NULL,
+ true,
+ LSA_POLICY_VIEW_LOCAL_INFORMATION,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ do {
+ uint32_t i;
+
+ /* Lookup list of trusted domains */
+ status = dcerpc_lsa_EnumTrustDom(h,
+ p->mem_ctx,
+ &pol,
+ &enum_ctx,
+ &domain_list,
+ max_size,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, NT_STATUS_NO_MORE_ENTRIES) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ status = result;
+ goto out;
+ }
+
+ for (i = 0; i < domain_list.count; i++) {
+ if (!add_string_to_array(p->mem_ctx, domain_list.domains[i].name.string,
+ &trusted_domains, &num_domains)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ if (num_domains > 0) {
+ /* multi sz terminate */
+ trusted_domains = talloc_realloc(p->mem_ctx, trusted_domains, const char *, num_domains + 1);
+ if (trusted_domains == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ trusted_domains[num_domains] = NULL;
+ }
+
+ if (!push_reg_multi_sz(trusted_domains, &blob, trusted_domains)) {
+ TALLOC_FREE(trusted_domains);
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ r->out.trusted_domains_blob->data = blob.data;
+ r->out.trusted_domains_blob->length = blob.length;
+
+ DEBUG(6,("_netr_NetrEnumerateTrustedDomains: %d\n", __LINE__));
+
+ status = NT_STATUS_OK;
+
+ out:
+ if (is_valid_policy_hnd(&pol)) {
+ dcerpc_lsa_Close(h, p->mem_ctx, &pol, &result);
+ }
+
+ return status;
+}
+
+/*************************************************************************
+ *************************************************************************/
+
+static NTSTATUS samr_find_machine_account(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ const char *account_name,
+ uint32_t access_mask,
+ struct dom_sid2 **domain_sid_p,
+ uint32_t *user_rid_p,
+ struct policy_handle *user_handle)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ struct policy_handle connect_handle;
+ struct policy_handle domain_handle = { 0, };
+ struct lsa_String domain_name;
+ struct dom_sid2 *domain_sid;
+ struct lsa_String names;
+ struct samr_Ids rids;
+ struct samr_Ids types;
+ uint32_t rid;
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ lp_netbios_name(),
+ SAMR_ACCESS_CONNECT_TO_SERVER |
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ &connect_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ init_lsa_String(&domain_name, get_global_sam_name());
+
+ status = dcerpc_samr_LookupDomain(b, mem_ctx,
+ &connect_handle,
+ &domain_name,
+ &domain_sid,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_handle,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ domain_sid,
+ &domain_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ init_lsa_String(&names, account_name);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_handle,
+ 1,
+ &names,
+ &rids,
+ &types,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ if (rids.count != 1) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto out;
+ }
+ if (types.count != 1) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ if (types.ids[0] != SID_NAME_USER) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto out;
+ }
+
+ rid = rids.ids[0];
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_handle,
+ access_mask,
+ rid,
+ user_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ if (user_rid_p) {
+ *user_rid_p = rid;
+ }
+
+ if (domain_sid_p) {
+ *domain_sid_p = domain_sid;
+ }
+
+ out:
+ if (is_valid_policy_hnd(&domain_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_handle, &result);
+ }
+ if (is_valid_policy_hnd(&connect_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_handle, &result);
+ }
+
+ return status;
+}
+
+/******************************************************************
+ gets a machine password entry. checks access rights of the host.
+ ******************************************************************/
+
+static NTSTATUS get_md4pw(struct samr_Password *md4pw, const char *mach_acct,
+ enum netr_SchannelType sec_chan_type,
+ struct dom_sid *sid,
+ struct messaging_context *msg_ctx)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct dcerpc_binding_handle *h = NULL;
+ struct tsocket_address *local = NULL;
+ struct policy_handle user_handle = { .handle_type = 0 };
+ uint32_t user_rid = UINT32_MAX;
+ struct dom_sid *domain_sid = NULL;
+ uint32_t acct_ctrl = 0;
+ union samr_UserInfo *info = NULL;
+ struct auth_session_info *session_info = NULL;
+ int rc;
+
+#if 0
+
+ /*
+ * Currently this code is redundant as we already have a filter
+ * by hostname list. What this code really needs to do is to
+ * get a hosts allowed/hosts denied list from the SAM database
+ * on a per user basis, and make the access decision there.
+ * I will leave this code here for now as a reminder to implement
+ * this at a later date. JRA.
+ */
+
+ if (!allow_access(lp_domain_hostsdeny(), lp_domain_hostsallow(),
+ p->client_id.name,
+ p->client_id.addr)) {
+ DEBUG(0,("get_md4pw: Workstation %s denied access to domain\n", mach_acct));
+ return False;
+ }
+#endif /* 0 */
+
+ mem_ctx = talloc_stackframe();
+
+ status = make_session_info_system(mem_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ ZERO_STRUCT(user_handle);
+
+ rc = tsocket_address_inet_from_strings(mem_ctx,
+ "ip",
+ "127.0.0.1",
+ 0,
+ &local);
+ if (rc < 0) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ status = rpcint_binding_handle(mem_ctx,
+ &ndr_table_samr,
+ local,
+ NULL,
+ session_info,
+ msg_ctx,
+ &h);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = samr_find_machine_account(mem_ctx, h, mach_acct,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &domain_sid, &user_rid,
+ &user_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = dcerpc_samr_QueryUserInfo2(h,
+ mem_ctx,
+ &user_handle,
+ UserControlInformation,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ acct_ctrl = info->info16.acct_flags;
+
+ if (acct_ctrl & ACB_DISABLED) {
+ DEBUG(0,("get_md4pw: Workstation %s: account is disabled\n", mach_acct));
+ status = NT_STATUS_ACCOUNT_DISABLED;
+ goto out;
+ }
+
+ if (!(acct_ctrl & ACB_SVRTRUST) &&
+ !(acct_ctrl & ACB_WSTRUST) &&
+ !(acct_ctrl & ACB_DOMTRUST))
+ {
+ DEBUG(0,("get_md4pw: Workstation %s: account is not a trust account\n", mach_acct));
+ status = NT_STATUS_NO_TRUST_SAM_ACCOUNT;
+ goto out;
+ }
+
+ switch (sec_chan_type) {
+ case SEC_CHAN_BDC:
+ if (!(acct_ctrl & ACB_SVRTRUST)) {
+ DEBUG(0,("get_md4pw: Workstation %s: BDC secure channel requested "
+ "but not a server trust account\n", mach_acct));
+ status = NT_STATUS_NO_TRUST_SAM_ACCOUNT;
+ goto out;
+ }
+ break;
+ case SEC_CHAN_WKSTA:
+ if (!(acct_ctrl & ACB_WSTRUST)) {
+ DEBUG(0,("get_md4pw: Workstation %s: WORKSTATION secure channel requested "
+ "but not a workstation trust account\n", mach_acct));
+ status = NT_STATUS_NO_TRUST_SAM_ACCOUNT;
+ goto out;
+ }
+ break;
+ case SEC_CHAN_DOMAIN:
+ if (!(acct_ctrl & ACB_DOMTRUST)) {
+ DEBUG(0,("get_md4pw: Workstation %s: DOMAIN secure channel requested "
+ "but not a interdomain trust account\n", mach_acct));
+ status = NT_STATUS_NO_TRUST_SAM_ACCOUNT;
+ goto out;
+ }
+ break;
+ default:
+ break;
+ }
+
+ become_root();
+ status = dcerpc_samr_QueryUserInfo2(h,
+ mem_ctx,
+ &user_handle,
+ UserInternal1Information,
+ &info,
+ &result);
+ unbecome_root();
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ if (info->info18.nt_pwd_active == 0) {
+ DEBUG(0,("get_md4pw: Workstation %s: account does not have a password\n", mach_acct));
+ status = NT_STATUS_LOGON_FAILURE;
+ goto out;
+ }
+
+ /* samr gives out nthash unencrypted (!) */
+ memcpy(md4pw->hash, info->info18.nt_pwd.hash, 16);
+
+ sid_compose(sid, domain_sid, user_rid);
+
+ out:
+ if (h && is_valid_policy_hnd(&user_handle)) {
+ dcerpc_samr_Close(h, mem_ctx, &user_handle, &result);
+ }
+
+ talloc_free(mem_ctx);
+
+ return status;
+}
+
+/*************************************************************************
+ _netr_ServerReqChallenge
+ *************************************************************************/
+
+NTSTATUS _netr_ServerReqChallenge(struct pipes_struct *p,
+ struct netr_ServerReqChallenge *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct netlogon_server_pipe_state *pipe_state = NULL;
+ NTSTATUS status;
+
+ pipe_state = dcesrv_iface_state_find_conn(
+ dce_call,
+ NETLOGON_SERVER_PIPE_STATE_MAGIC,
+ struct netlogon_server_pipe_state);
+
+ if (pipe_state) {
+ DEBUG(10,("_netr_ServerReqChallenge: new challenge requested. Clearing old state.\n"));
+ talloc_free(pipe_state);
+ }
+
+ pipe_state = talloc(p->mem_ctx, struct netlogon_server_pipe_state);
+ NT_STATUS_HAVE_NO_MEMORY(pipe_state);
+
+ pipe_state->client_challenge = *r->in.credentials;
+
+ netlogon_creds_random_challenge(&pipe_state->server_challenge);
+
+ *r->out.return_credentials = pipe_state->server_challenge;
+
+ status = dcesrv_iface_state_store_conn(
+ dce_call,
+ NETLOGON_SERVER_PIPE_STATE_MAGIC,
+ pipe_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ _netr_ServerAuthenticate
+ Create the initial credentials.
+ *************************************************************************/
+
+NTSTATUS _netr_ServerAuthenticate(struct pipes_struct *p,
+ struct netr_ServerAuthenticate *r)
+{
+ struct netr_ServerAuthenticate3 a;
+ uint32_t negotiate_flags = 0;
+ uint32_t rid;
+
+ a.in.server_name = r->in.server_name;
+ a.in.account_name = r->in.account_name;
+ a.in.secure_channel_type = r->in.secure_channel_type;
+ a.in.computer_name = r->in.computer_name;
+ a.in.credentials = r->in.credentials;
+ a.in.negotiate_flags = &negotiate_flags;
+
+ a.out.return_credentials = r->out.return_credentials;
+ a.out.rid = &rid;
+ a.out.negotiate_flags = &negotiate_flags;
+
+ return _netr_ServerAuthenticate3(p, &a);
+
+}
+
+/*************************************************************************
+ _netr_ServerAuthenticate3
+ *************************************************************************/
+
+NTSTATUS _netr_ServerAuthenticate3(struct pipes_struct *p,
+ struct netr_ServerAuthenticate3 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ NTSTATUS status;
+ uint32_t srv_flgs;
+ /* r->in.negotiate_flags is an aliased pointer to r->out.negotiate_flags,
+ * so use a copy to avoid destroying the client values. */
+ uint32_t in_neg_flags = *r->in.negotiate_flags;
+ const char *fn;
+ struct loadparm_context *lp_ctx = p->dce_call->conn->dce_ctx->lp_ctx;
+ struct dom_sid sid;
+ struct samr_Password mach_pwd;
+ struct netlogon_creds_CredentialState *creds;
+ struct netlogon_server_pipe_state *pipe_state = NULL;
+
+ /* According to Microsoft (see bugid #6099)
+ * Windows 7 looks at the negotiate_flags
+ * returned in this structure *even if the
+ * call fails with access denied* ! So in order
+ * to allow Win7 to connect to a Samba NT style
+ * PDC we set the flags before we know if it's
+ * an error or not.
+ */
+
+ /* 0x000001ff */
+ srv_flgs = NETLOGON_NEG_ACCOUNT_LOCKOUT |
+ NETLOGON_NEG_PERSISTENT_SAMREPL |
+ NETLOGON_NEG_ARCFOUR |
+ NETLOGON_NEG_PROMOTION_COUNT |
+ NETLOGON_NEG_CHANGELOG_BDC |
+ NETLOGON_NEG_FULL_SYNC_REPL |
+ NETLOGON_NEG_MULTIPLE_SIDS |
+ NETLOGON_NEG_REDO |
+ NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL |
+ NETLOGON_NEG_PASSWORD_SET2;
+
+ /* Ensure we support strong (128-bit) keys. */
+ if (in_neg_flags & NETLOGON_NEG_STRONG_KEYS) {
+ srv_flgs |= NETLOGON_NEG_STRONG_KEYS;
+ }
+
+ if (in_neg_flags & NETLOGON_NEG_SUPPORTS_AES) {
+ srv_flgs |= NETLOGON_NEG_SUPPORTS_AES;
+ }
+
+ if (in_neg_flags & NETLOGON_NEG_SCHANNEL) {
+ srv_flgs |= NETLOGON_NEG_SCHANNEL;
+ }
+
+ /*
+ * Support authentication of trusted domains.
+ *
+ * These flags are the minimum required set which works with win2k3
+ * and win2k8.
+ */
+ if (pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX) {
+ srv_flgs |= NETLOGON_NEG_TRANSITIVE_TRUSTS |
+ NETLOGON_NEG_DNS_DOMAIN_TRUSTS |
+ NETLOGON_NEG_CROSS_FOREST_TRUSTS |
+ NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION;
+ }
+
+ /*
+ * If weak crypto is disabled, do not announce that we support RC4.
+ */
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) {
+ srv_flgs &= ~NETLOGON_NEG_ARCFOUR;
+ }
+
+ switch (dce_call->pkt.u.request.opnum) {
+ case NDR_NETR_SERVERAUTHENTICATE:
+ fn = "_netr_ServerAuthenticate";
+ break;
+ case NDR_NETR_SERVERAUTHENTICATE2:
+ fn = "_netr_ServerAuthenticate2";
+ break;
+ case NDR_NETR_SERVERAUTHENTICATE3:
+ fn = "_netr_ServerAuthenticate3";
+ break;
+ default:
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /* We use this as the key to store the creds: */
+ /* r->in.computer_name */
+
+ pipe_state = dcesrv_iface_state_find_conn(
+ dce_call,
+ NETLOGON_SERVER_PIPE_STATE_MAGIC,
+ struct netlogon_server_pipe_state);
+
+ if (!pipe_state) {
+ DEBUG(0,("%s: no challenge sent to client %s\n", fn,
+ r->in.computer_name));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ status = get_md4pw(&mach_pwd,
+ r->in.account_name,
+ r->in.secure_channel_type,
+ &sid, p->msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s: failed to get machine password for "
+ "account %s: %s\n",
+ fn, r->in.account_name, nt_errstr(status) ));
+ /* always return NT_STATUS_ACCESS_DENIED */
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ /* From the client / server challenges and md4 password, generate sess key */
+ /* Check client credentials are valid. */
+ creds = netlogon_creds_server_init(p->mem_ctx,
+ r->in.account_name,
+ r->in.computer_name,
+ r->in.secure_channel_type,
+ &pipe_state->client_challenge,
+ &pipe_state->server_challenge,
+ &mach_pwd,
+ r->in.credentials,
+ r->out.return_credentials,
+ srv_flgs);
+ if (!creds) {
+ DEBUG(0,("%s: netlogon_creds_server_check failed. Rejecting auth "
+ "request from client %s machine account %s\n",
+ fn, r->in.computer_name,
+ r->in.account_name));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ creds->sid = dom_sid_dup(creds, &sid);
+ if (!creds->sid) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* Store off the state so we can continue after client disconnect. */
+ become_root();
+ status = schannel_save_creds_state(p->mem_ctx, lp_ctx, creds);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ ZERO_STRUCTP(r->out.return_credentials);
+ goto out;
+ }
+
+ sid_peek_rid(&sid, r->out.rid);
+
+ status = NT_STATUS_OK;
+
+ out:
+
+ *r->out.negotiate_flags = srv_flgs;
+ return status;
+}
+
+/*************************************************************************
+ _netr_ServerAuthenticate2
+ *************************************************************************/
+
+NTSTATUS _netr_ServerAuthenticate2(struct pipes_struct *p,
+ struct netr_ServerAuthenticate2 *r)
+{
+ struct netr_ServerAuthenticate3 a;
+ uint32_t rid;
+
+ a.in.server_name = r->in.server_name;
+ a.in.account_name = r->in.account_name;
+ a.in.secure_channel_type = r->in.secure_channel_type;
+ a.in.computer_name = r->in.computer_name;
+ a.in.credentials = r->in.credentials;
+ a.in.negotiate_flags = r->in.negotiate_flags;
+
+ a.out.return_credentials = r->out.return_credentials;
+ a.out.rid = &rid;
+ a.out.negotiate_flags = r->out.negotiate_flags;
+
+ return _netr_ServerAuthenticate3(p, &a);
+}
+
+/*************************************************************************
+ *************************************************************************/
+
+static NTSTATUS samr_open_machine_account(
+ struct dcerpc_binding_handle *b,
+ const struct dom_sid *machine_sid,
+ uint32_t access_mask,
+ struct policy_handle *machine_handle)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct policy_handle connect_handle = { .handle_type = 0 };
+ struct policy_handle domain_handle = { .handle_type = 0 };
+ struct dom_sid domain_sid = *machine_sid;
+ uint32_t machine_rid;
+ NTSTATUS result = NT_STATUS_OK;
+ NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
+ bool ok;
+
+ ok = sid_split_rid(&domain_sid, &machine_rid);
+ if (!ok) {
+ goto out;
+ }
+
+ status = dcerpc_samr_Connect2(
+ b,
+ frame,
+ lp_netbios_name(),
+ SAMR_ACCESS_CONNECT_TO_SERVER |
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ &connect_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ status = dcerpc_samr_OpenDomain(
+ b,
+ frame,
+ &connect_handle,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ &domain_sid,
+ &domain_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ status = dcerpc_samr_OpenUser(
+ b,
+ frame,
+ &domain_handle,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ machine_rid,
+ machine_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+out:
+ if ((b != NULL) && is_valid_policy_hnd(&domain_handle)) {
+ dcerpc_samr_Close(b, frame, &domain_handle, &result);
+ }
+ if ((b != NULL) && is_valid_policy_hnd(&connect_handle)) {
+ dcerpc_samr_Close(b, frame, &connect_handle, &result);
+ }
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct _samr_Credentials_t {
+ enum {
+ CRED_TYPE_NT_HASH,
+ CRED_TYPE_PLAIN_TEXT,
+ } cred_type;
+ union {
+ struct samr_Password *nt_hash;
+ const char *password;
+ } creds;
+};
+
+
+static NTSTATUS netr_set_machine_account_password(
+ TALLOC_CTX *mem_ctx,
+ struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const struct dom_sid *machine_sid,
+ struct _samr_Credentials_t *cr)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_OK;
+ struct dcerpc_binding_handle *h = NULL;
+ struct tsocket_address *local;
+ struct policy_handle user_handle = { .handle_type = 0 };
+ uint32_t acct_ctrl;
+ union samr_UserInfo *info;
+ struct samr_UserInfo18 info18;
+ struct samr_UserInfo26 info26;
+ DATA_BLOB in,out;
+ int rc;
+ DATA_BLOB session_key;
+ enum samr_UserInfoLevel infolevel;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ status = session_extract_session_key(session_info,
+ &session_key,
+ KEY_USE_16BYTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ rc = tsocket_address_inet_from_strings(frame,
+ "ip",
+ "127.0.0.1",
+ 0,
+ &local);
+ if (rc < 0) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ status = rpcint_binding_handle(frame,
+ &ndr_table_samr,
+ local,
+ NULL,
+ get_session_info_system(),
+ msg_ctx,
+ &h);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = samr_open_machine_account(
+ h, machine_sid, SEC_FLAG_MAXIMUM_ALLOWED, &user_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = dcerpc_samr_QueryUserInfo2(h,
+ frame,
+ &user_handle,
+ UserControlInformation,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ acct_ctrl = info->info16.acct_flags;
+
+ if (!(acct_ctrl & ACB_WSTRUST ||
+ acct_ctrl & ACB_SVRTRUST ||
+ acct_ctrl & ACB_DOMTRUST)) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto out;
+ }
+
+ if (acct_ctrl & ACB_DISABLED) {
+ status = NT_STATUS_ACCOUNT_DISABLED;
+ goto out;
+ }
+
+ switch(cr->cred_type) {
+ case CRED_TYPE_NT_HASH:
+ ZERO_STRUCT(info18);
+
+ infolevel = UserInternal1Information;
+
+ in = data_blob_const(cr->creds.nt_hash, 16);
+ out = data_blob_talloc_zero(frame, 16);
+ if (out.data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ goto out;
+ }
+ memcpy(info18.nt_pwd.hash, out.data, out.length);
+
+ info18.nt_pwd_active = true;
+
+ info->info18 = info18;
+ break;
+ case CRED_TYPE_PLAIN_TEXT:
+ ZERO_STRUCT(info26);
+
+ infolevel = UserInternal5InformationNew;
+
+ status = init_samr_CryptPasswordEx(cr->creds.password,
+ &session_key,
+ &info26.password);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ info26.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON;
+ info->info26 = info26;
+ break;
+ default:
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ break;
+ }
+
+ status = dcerpc_samr_SetUserInfo2(h,
+ frame,
+ &user_handle,
+ infolevel,
+ info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto out;
+ }
+
+ out:
+ if (h && is_valid_policy_hnd(&user_handle)) {
+ dcerpc_samr_Close(h, frame, &user_handle, &result);
+ }
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+/*************************************************************************
+ _netr_ServerPasswordSet
+ *************************************************************************/
+
+NTSTATUS _netr_ServerPasswordSet(struct pipes_struct *p,
+ struct netr_ServerPasswordSet *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status = NT_STATUS_OK;
+ size_t i;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ struct _samr_Credentials_t cr = { CRED_TYPE_NT_HASH, {0}};
+
+ DEBUG(5,("_netr_ServerPasswordSet: %d\n", __LINE__));
+
+ become_root();
+ status = dcesrv_netr_creds_server_step_check(p->dce_call,
+ p->mem_ctx,
+ r->in.computer_name,
+ r->in.credential,
+ r->out.return_authenticator,
+ &creds);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *computer_name = "<unknown>";
+
+ if (creds != NULL && creds->computer_name != NULL) {
+ computer_name = creds->computer_name;
+ }
+ DEBUG(2,("_netr_ServerPasswordSet: netlogon_creds_server_step failed. Rejecting auth "
+ "request from client %s machine account %s\n",
+ r->in.computer_name, computer_name));
+ TALLOC_FREE(creds);
+ return status;
+ }
+
+ DEBUG(3,("_netr_ServerPasswordSet: Server Password Set by remote machine:[%s] on account [%s]\n",
+ r->in.computer_name, creds->computer_name));
+
+ status = netlogon_creds_des_decrypt(creds, r->in.new_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(100,("_netr_ServerPasswordSet: new given value was :\n"));
+ for(i = 0; i < sizeof(r->in.new_password->hash); i++)
+ DEBUG(100,("%02X ", r->in.new_password->hash[i]));
+ DEBUG(100,("\n"));
+
+ cr.creds.nt_hash = r->in.new_password;
+ status = netr_set_machine_account_password(p->mem_ctx,
+ session_info,
+ p->msg_ctx,
+ creds->sid,
+ &cr);
+ return status;
+}
+
+/****************************************************************
+ _netr_ServerPasswordSet2
+****************************************************************/
+
+NTSTATUS _netr_ServerPasswordSet2(struct pipes_struct *p,
+ struct netr_ServerPasswordSet2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ DATA_BLOB plaintext = data_blob_null;
+ DATA_BLOB new_password = data_blob_null;
+ size_t confounder_len;
+ DATA_BLOB dec_blob = data_blob_null;
+ DATA_BLOB enc_blob = data_blob_null;
+ struct samr_CryptPassword password_buf;
+ struct _samr_Credentials_t cr = { CRED_TYPE_PLAIN_TEXT, {0}};
+ bool ok;
+
+ become_root();
+ status = dcesrv_netr_creds_server_step_check(p->dce_call,
+ p->mem_ctx,
+ r->in.computer_name,
+ r->in.credential,
+ r->out.return_authenticator,
+ &creds);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("netlogon_creds_server_step failed. "
+ "Rejecting auth request from client %s\n",
+ r->in.computer_name);
+ TALLOC_FREE(creds);
+ return status;
+ }
+
+ DBG_NOTICE("Server Password Set2 by remote "
+ "machine:[%s] on account [%s]\n",
+ r->in.computer_name,
+ creds->computer_name != NULL ?
+ creds->computer_name : "<unknown>");
+
+ memcpy(password_buf.data, r->in.new_password->data, 512);
+ SIVAL(password_buf.data, 512, r->in.new_password->length);
+
+ if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) {
+ status = netlogon_creds_aes_decrypt(creds,
+ password_buf.data,
+ 516);
+ } else {
+ status = netlogon_creds_arcfour_crypt(creds,
+ password_buf.data,
+ 516);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(creds);
+ return status;
+ }
+
+ if (!extract_pw_from_buffer(p->mem_ctx, password_buf.data, &new_password)) {
+ DEBUG(2,("_netr_ServerPasswordSet2: unable to extract password "
+ "from a buffer. Rejecting auth request as a wrong password\n"));
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * Make sure the length field was encrypted,
+ * otherwise we are under attack.
+ */
+ if (new_password.length == r->in.new_password->length) {
+ DBG_WARNING("Length[%zu] field not encrypted\n",
+ new_password.length);
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * We don't allow empty passwords for machine accounts.
+ */
+ if (new_password.length < 2) {
+ DBG_WARNING("Empty password Length[%zu]\n",
+ new_password.length);
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * Make sure the confounder part of CryptPassword
+ * buffer was encrypted, otherwise we are under attack.
+ */
+ confounder_len = 512 - new_password.length;
+ enc_blob = data_blob_const(r->in.new_password->data, confounder_len);
+ dec_blob = data_blob_const(password_buf.data, confounder_len);
+ if (confounder_len > 0 && data_blob_equal_const_time(&dec_blob, &enc_blob)) {
+ DBG_WARNING("Confounder buffer not encrypted Length[%zu]\n",
+ confounder_len);
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * Check that the password part was actually encrypted,
+ * otherwise we are under attack.
+ */
+ enc_blob = data_blob_const(r->in.new_password->data + confounder_len,
+ new_password.length);
+ dec_blob = data_blob_const(password_buf.data + confounder_len,
+ new_password.length);
+ if (data_blob_equal_const_time(&dec_blob, &enc_blob)) {
+ DBG_WARNING("Password buffer not encrypted Length[%zu]\n",
+ new_password.length);
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * don't allow zero buffers
+ */
+ if (all_zero(new_password.data, new_password.length)) {
+ DBG_WARNING("Password zero buffer Length[%zu]\n",
+ new_password.length);
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /* Convert from UTF16 -> plaintext. */
+ ok = convert_string_talloc(p->mem_ctx,
+ CH_UTF16,
+ CH_UNIX,
+ new_password.data,
+ new_password.length,
+ &plaintext.data,
+ &plaintext.length);
+ if (!ok) {
+ DBG_WARNING("unable to extract password from a buffer. "
+ "Rejecting auth request as a wrong password\n");
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * We don't allow empty passwords for machine accounts.
+ */
+
+ cr.creds.password = (const char*) plaintext.data;
+ if (strlen(cr.creds.password) == 0) {
+ DBG_WARNING("Empty plaintext password\n");
+ TALLOC_FREE(creds);
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ status = netr_set_machine_account_password(p->mem_ctx,
+ session_info,
+ p->msg_ctx,
+ creds->sid,
+ &cr);
+ TALLOC_FREE(creds);
+ return status;
+}
+
+/*************************************************************************
+ _netr_LogonSamLogoff
+ *************************************************************************/
+
+NTSTATUS _netr_LogonSamLogoff(struct pipes_struct *p,
+ struct netr_LogonSamLogoff *r)
+{
+ NTSTATUS status;
+ struct netlogon_creds_CredentialState *creds;
+
+ become_root();
+ status = dcesrv_netr_creds_server_step_check(p->dce_call,
+ p->mem_ctx,
+ r->in.computer_name,
+ r->in.credential,
+ r->out.return_authenticator,
+ &creds);
+ unbecome_root();
+
+ return status;
+}
+
+static NTSTATUS _netr_LogonSamLogon_check(const struct netr_LogonSamLogonEx *r)
+{
+ switch (r->in.logon_level) {
+ case NetlogonInteractiveInformation:
+ case NetlogonServiceInformation:
+ case NetlogonInteractiveTransitiveInformation:
+ case NetlogonServiceTransitiveInformation:
+ if (r->in.logon->password == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (r->in.validation_level) {
+ case NetlogonValidationSamInfo: /* 2 */
+ case NetlogonValidationSamInfo2: /* 3 */
+ break;
+ case NetlogonValidationSamInfo4: /* 6 */
+ if ((pdb_capabilities() & PDB_CAP_ADS) == 0) {
+ DEBUG(10,("Not adding validation info level 6 "
+ "without ADS passdb backend\n"));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ break;
+ case NetlogonNetworkInformation:
+ case NetlogonNetworkTransitiveInformation:
+ if (r->in.logon->network == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (r->in.validation_level) {
+ case NetlogonValidationSamInfo: /* 2 */
+ case NetlogonValidationSamInfo2: /* 3 */
+ break;
+ case NetlogonValidationSamInfo4: /* 6 */
+ if ((pdb_capabilities() & PDB_CAP_ADS) == 0) {
+ DEBUG(10,("Not adding validation info level 6 "
+ "without ADS passdb backend\n"));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ break;
+
+ case NetlogonGenericInformation:
+ if (r->in.logon->generic == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* we don't support this here */
+ return NT_STATUS_INVALID_PARAMETER;
+#if 0
+ switch (r->in.validation_level) {
+ /* TODO: case NetlogonValidationGenericInfo: 4 */
+ case NetlogonValidationGenericInfo2: /* 5 */
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ break;
+#endif
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ _netr_LogonSamLogon_base
+ *************************************************************************/
+
+static NTSTATUS _netr_LogonSamLogon_base(struct pipes_struct *p,
+ struct netr_LogonSamLogonEx *r,
+ struct netlogon_creds_CredentialState *creds)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ NTSTATUS status = NT_STATUS_OK;
+ union netr_LogonLevel *logon = r->in.logon;
+ const char *nt_username, *nt_domain, *nt_workstation;
+ char *sanitized_username = NULL;
+ struct auth_usersupplied_info *user_info = NULL;
+ struct auth_serversupplied_info *server_info = NULL;
+ struct auth_context *auth_context = NULL;
+ const char *fn;
+ enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE;
+ enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
+ uint16_t opnum = dce_call->pkt.u.request.opnum;
+
+ dcesrv_call_auth_info(dce_call, &auth_type, &auth_level);
+
+#ifdef DEBUG_PASSWORD
+ logon = netlogon_creds_shallow_copy_logon(p->mem_ctx,
+ r->in.logon_level,
+ r->in.logon);
+ if (logon == NULL) {
+ logon = r->in.logon;
+ }
+#endif
+
+ switch (opnum) {
+ case NDR_NETR_LOGONSAMLOGON:
+ fn = "_netr_LogonSamLogon";
+ /*
+ * Already called netr_check_schannel() via
+ * netr_creds_server_step_check()
+ */
+ break;
+ case NDR_NETR_LOGONSAMLOGONWITHFLAGS:
+ fn = "_netr_LogonSamLogonWithFlags";
+ /*
+ * Already called netr_check_schannel() via
+ * netr_creds_server_step_check()
+ */
+ break;
+ case NDR_NETR_LOGONSAMLOGONEX:
+ fn = "_netr_LogonSamLogonEx";
+
+ if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = dcesrv_netr_check_schannel(p->dce_call,
+ creds,
+ auth_type,
+ auth_level,
+ opnum);
+ if (NT_STATUS_IS_ERR(status)) {
+ return status;
+ }
+
+ break;
+ default:
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ *r->out.authoritative = 1; /* authoritative response */
+
+ switch (r->in.validation_level) {
+ case 2:
+ r->out.validation->sam2 = talloc_zero(p->mem_ctx, struct netr_SamInfo2);
+ if (!r->out.validation->sam2) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ case 3:
+ r->out.validation->sam3 = talloc_zero(p->mem_ctx, struct netr_SamInfo3);
+ if (!r->out.validation->sam3) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ case 6:
+ r->out.validation->sam6 = talloc_zero(p->mem_ctx, struct netr_SamInfo6);
+ if (!r->out.validation->sam6) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ default:
+ DEBUG(0,("%s: bad validation_level value %d.\n",
+ fn, (int)r->in.validation_level));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ switch (r->in.logon_level) {
+ case NetlogonInteractiveInformation:
+ case NetlogonServiceInformation:
+ case NetlogonInteractiveTransitiveInformation:
+ case NetlogonServiceTransitiveInformation:
+ nt_username = logon->password->identity_info.account_name.string ?
+ logon->password->identity_info.account_name.string : "";
+ nt_domain = logon->password->identity_info.domain_name.string ?
+ logon->password->identity_info.domain_name.string : "";
+ nt_workstation = logon->password->identity_info.workstation.string ?
+ logon->password->identity_info.workstation.string : "";
+
+ DEBUG(3,("SAM Logon (Interactive). Domain:[%s]. ", lp_workgroup()));
+ break;
+ case NetlogonNetworkInformation:
+ case NetlogonNetworkTransitiveInformation:
+ nt_username = logon->network->identity_info.account_name.string ?
+ logon->network->identity_info.account_name.string : "";
+ nt_domain = logon->network->identity_info.domain_name.string ?
+ logon->network->identity_info.domain_name.string : "";
+ nt_workstation = logon->network->identity_info.workstation.string ?
+ logon->network->identity_info.workstation.string : "";
+
+ DEBUG(3,("SAM Logon (Network). Domain:[%s]. ", lp_workgroup()));
+ break;
+ default:
+ DEBUG(2,("SAM Logon: unsupported switch value\n"));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ } /* end switch */
+
+ DEBUG(3,("User:[%s@%s] Requested Domain:[%s]\n", nt_username, nt_workstation, nt_domain));
+
+ DEBUG(5,("Attempting validation level %d for unmapped username %s.\n",
+ r->in.validation_level, nt_username));
+
+ status = netlogon_creds_decrypt_samlogon_logon(creds,
+ r->in.logon_level,
+ logon);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = make_auth3_context_for_netlogon(talloc_tos(), &auth_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ switch (r->in.logon_level) {
+ case NetlogonNetworkInformation:
+ case NetlogonNetworkTransitiveInformation:
+ {
+ const char *wksname = nt_workstation;
+ const char *workgroup = lp_workgroup();
+ bool ok;
+
+ ok = auth3_context_set_challenge(
+ auth_context, logon->network->challenge, "fixed");
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* For a network logon, the workstation name comes in with two
+ * backslashes in the front. Strip them if they are there. */
+
+ if (*wksname == '\\') wksname++;
+ if (*wksname == '\\') wksname++;
+
+ /* Standard challenge/response authentication */
+ if (!make_user_info_netlogon_network(talloc_tos(),
+ &user_info,
+ nt_username, nt_domain,
+ wksname,
+ remote_address,
+ local_address,
+ logon->network->identity_info.parameter_control,
+ logon->network->lm.data,
+ logon->network->lm.length,
+ logon->network->nt.data,
+ logon->network->nt.length)) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ status = NTLMv2_RESPONSE_verify_netlogon_creds(
+ user_info->client.account_name,
+ user_info->client.domain_name,
+ user_info->password.response.nt,
+ creds, workgroup);
+ }
+ break;
+ }
+ case NetlogonInteractiveInformation:
+ case NetlogonServiceInformation:
+ case NetlogonInteractiveTransitiveInformation:
+ case NetlogonServiceTransitiveInformation:
+
+ /* 'Interactive' authentication, supplies the password in its
+ MD4 form, encrypted with the session key. We will convert
+ this to challenge/response for the auth subsystem to chew
+ on */
+ {
+ uint8_t chal[8];
+
+#ifdef DEBUG_PASSWORD
+ if (logon != r->in.logon) {
+ DEBUG(100,("lm owf password:"));
+ dump_data(100,
+ r->in.logon->password->lmpassword.hash, 16);
+
+ DEBUG(100,("nt owf password:"));
+ dump_data(100,
+ r->in.logon->password->ntpassword.hash, 16);
+ }
+
+ DEBUG(100,("decrypt of lm owf password:"));
+ dump_data(100, logon->password->lmpassword.hash, 16);
+
+ DEBUG(100,("decrypt of nt owf password:"));
+ dump_data(100, logon->password->ntpassword.hash, 16);
+#endif
+
+ auth_get_ntlm_challenge(auth_context, chal);
+
+ if (!make_user_info_netlogon_interactive(talloc_tos(),
+ &user_info,
+ nt_username, nt_domain,
+ nt_workstation,
+ remote_address,
+ local_address,
+ logon->password->identity_info.parameter_control,
+ chal,
+ logon->password->lmpassword.hash,
+ logon->password->ntpassword.hash)) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+ break;
+ }
+ default:
+ DEBUG(2,("SAM Logon: unsupported switch value\n"));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ } /* end switch */
+
+ if ( NT_STATUS_IS_OK(status) ) {
+ status = auth_check_ntlm_password(p->mem_ctx,
+ auth_context,
+ user_info,
+ &server_info,
+ r->out.authoritative);
+ }
+
+ TALLOC_FREE(auth_context);
+ TALLOC_FREE(user_info);
+
+ DEBUG(5,("%s: check_password returned status %s\n",
+ fn, nt_errstr(status)));
+
+ /* Check account and password */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(server_info);
+ return status;
+ }
+
+ if (server_info->guest) {
+ /* We don't like guest domain logons... */
+ DEBUG(5,("%s: Attempted domain logon as GUEST "
+ "denied.\n", fn));
+ TALLOC_FREE(server_info);
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ sanitized_username = talloc_alpha_strcpy(talloc_tos(),
+ nt_username,
+ SAFE_NETBIOS_CHARS "$");
+ if (sanitized_username == NULL) {
+ TALLOC_FREE(server_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ set_current_user_info(sanitized_username,
+ server_info->unix_name,
+ server_info->info3->base.logon_domain.string);
+ TALLOC_FREE(sanitized_username);
+
+ /* This is the point at which, if the login was successful, that
+ the SAM Local Security Authority should record that the user is
+ logged in to the domain. */
+
+ switch (r->in.validation_level) {
+ case 2:
+ status = serverinfo_to_SamInfo2(server_info,
+ r->out.validation->sam2);
+ break;
+ case 3:
+ status = serverinfo_to_SamInfo3(server_info,
+ r->out.validation->sam3);
+ break;
+ case 6: {
+ /* Only allow this if the pipe is protected. */
+ if (auth_level < DCERPC_AUTH_LEVEL_PRIVACY) {
+ DEBUG(0,("netr_Validation6: client %s not using privacy for netlogon\n",
+ get_remote_machine_name()));
+ status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+
+ status = serverinfo_to_SamInfo6(server_info,
+ r->out.validation->sam6);
+ break;
+ }
+ }
+
+ TALLOC_FREE(server_info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = netlogon_creds_encrypt_samlogon_validation(creds,
+ r->in.validation_level,
+ r->out.validation);
+
+ return status;
+}
+
+/****************************************************************
+ _netr_LogonSamLogonWithFlags
+****************************************************************/
+
+NTSTATUS _netr_LogonSamLogonWithFlags(struct pipes_struct *p,
+ struct netr_LogonSamLogonWithFlags *r)
+{
+ NTSTATUS status;
+ struct netlogon_creds_CredentialState *creds;
+ struct netr_LogonSamLogonEx r2;
+ struct netr_Authenticator return_authenticator;
+
+ *r->out.authoritative = true;
+
+ r2.in.server_name = r->in.server_name;
+ r2.in.computer_name = r->in.computer_name;
+ r2.in.logon_level = r->in.logon_level;
+ r2.in.logon = r->in.logon;
+ r2.in.validation_level = r->in.validation_level;
+ r2.in.flags = r->in.flags;
+ r2.out.validation = r->out.validation;
+ r2.out.authoritative = r->out.authoritative;
+ r2.out.flags = r->out.flags;
+
+ status = _netr_LogonSamLogon_check(&r2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ become_root();
+ status = dcesrv_netr_creds_server_step_check(p->dce_call,
+ p->mem_ctx,
+ r->in.computer_name,
+ r->in.credential,
+ &return_authenticator,
+ &creds);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = _netr_LogonSamLogon_base(p, &r2, creds);
+
+ *r->out.return_authenticator = return_authenticator;
+
+ return status;
+}
+
+/*************************************************************************
+ _netr_LogonSamLogon
+ *************************************************************************/
+
+NTSTATUS _netr_LogonSamLogon(struct pipes_struct *p,
+ struct netr_LogonSamLogon *r)
+{
+ NTSTATUS status;
+ struct netr_LogonSamLogonWithFlags r2;
+ uint32_t flags = 0;
+
+ r2.in.server_name = r->in.server_name;
+ r2.in.computer_name = r->in.computer_name;
+ r2.in.credential = r->in.credential;
+ r2.in.logon_level = r->in.logon_level;
+ r2.in.logon = r->in.logon;
+ r2.in.validation_level = r->in.validation_level;
+ r2.in.return_authenticator = r->in.return_authenticator;
+ r2.in.flags = &flags;
+ r2.out.validation = r->out.validation;
+ r2.out.authoritative = r->out.authoritative;
+ r2.out.flags = &flags;
+ r2.out.return_authenticator = r->out.return_authenticator;
+
+ status = _netr_LogonSamLogonWithFlags(p, &r2);
+
+ return status;
+}
+
+/*************************************************************************
+ _netr_LogonSamLogonEx
+ - no credential chaining. Map into net sam logon.
+ *************************************************************************/
+
+NTSTATUS _netr_LogonSamLogonEx(struct pipes_struct *p,
+ struct netr_LogonSamLogonEx *r)
+{
+ NTSTATUS status;
+ struct netlogon_creds_CredentialState *creds = NULL;
+ struct loadparm_context *lp_ctx = p->dce_call->conn->dce_ctx->lp_ctx;
+
+ *r->out.authoritative = true;
+
+ status = _netr_LogonSamLogon_check(r);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ become_root();
+ status = schannel_get_creds_state(p->mem_ctx, lp_ctx,
+ r->in.computer_name, &creds);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = _netr_LogonSamLogon_base(p, r, creds);
+ TALLOC_FREE(creds);
+
+ return status;
+}
+
+/*************************************************************************
+ _ds_enum_dom_trusts
+ *************************************************************************/
+#if 0 /* JERRY -- not correct */
+ NTSTATUS _ds_enum_dom_trusts(struct pipes_struct *p, DS_Q_ENUM_DOM_TRUSTS *q_u,
+ DS_R_ENUM_DOM_TRUSTS *r_u)
+{
+ NTSTATUS status = NT_STATUS_OK;
+
+ /* TODO: According to MSDN, the can only be executed against a
+ DC or domain member running Windows 2000 or later. Need
+ to test against a standalone 2k server and see what it
+ does. A windows 2000 DC includes its own domain in the
+ list. --jerry */
+
+ return status;
+}
+#endif /* JERRY */
+
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_LogonUasLogon(struct pipes_struct *p,
+ struct netr_LogonUasLogon *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_LogonUasLogoff(struct pipes_struct *p,
+ struct netr_LogonUasLogoff *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_DatabaseDeltas(struct pipes_struct *p,
+ struct netr_DatabaseDeltas *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_DatabaseSync(struct pipes_struct *p,
+ struct netr_DatabaseSync *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_AccountDeltas(struct pipes_struct *p,
+ struct netr_AccountDeltas *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_AccountSync(struct pipes_struct *p,
+ struct netr_AccountSync *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+static bool wb_getdcname(TALLOC_CTX *mem_ctx,
+ const char *domain,
+ const char **dcname,
+ uint32_t flags,
+ WERROR *werr)
+{
+ wbcErr result;
+ struct wbcDomainControllerInfo *dc_info = NULL;
+
+ result = wbcLookupDomainController(domain,
+ flags,
+ &dc_info);
+ switch (result) {
+ case WBC_ERR_SUCCESS:
+ break;
+ case WBC_ERR_WINBIND_NOT_AVAILABLE:
+ return false;
+ case WBC_ERR_DOMAIN_NOT_FOUND:
+ *werr = WERR_NO_SUCH_DOMAIN;
+ return true;
+ default:
+ *werr = WERR_DOMAIN_CONTROLLER_NOT_FOUND;
+ return true;
+ }
+
+ *dcname = talloc_strdup(mem_ctx, dc_info->dc_name);
+ wbcFreeMemory(dc_info);
+ if (!*dcname) {
+ *werr = WERR_NOT_ENOUGH_MEMORY;
+ return false;
+ }
+
+ *werr = WERR_OK;
+
+ return true;
+}
+
+/****************************************************************
+ _netr_GetDcName
+****************************************************************/
+
+WERROR _netr_GetDcName(struct pipes_struct *p,
+ struct netr_GetDcName *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t flags;
+ struct netr_DsRGetDCNameInfo *info;
+ bool ret;
+
+ ret = wb_getdcname(p->mem_ctx,
+ r->in.domainname,
+ r->out.dcname,
+ WBC_LOOKUP_DC_IS_FLAT_NAME |
+ WBC_LOOKUP_DC_RETURN_FLAT_NAME |
+ WBC_LOOKUP_DC_PDC_REQUIRED,
+ &werr);
+ if (ret == true) {
+ return werr;
+ }
+
+ flags = DS_PDC_REQUIRED | DS_IS_FLAT_NAME | DS_RETURN_FLAT_NAME;
+
+ status = dsgetdcname(p->mem_ctx,
+ p->msg_ctx,
+ r->in.domainname,
+ NULL,
+ NULL,
+ flags,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ *r->out.dcname = talloc_strdup(p->mem_ctx, info->dc_unc);
+ talloc_free(info);
+ if (!*r->out.dcname) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _netr_GetAnyDCName
+****************************************************************/
+
+WERROR _netr_GetAnyDCName(struct pipes_struct *p,
+ struct netr_GetAnyDCName *r)
+{
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t flags;
+ struct netr_DsRGetDCNameInfo *info;
+ bool ret;
+
+ ret = wb_getdcname(p->mem_ctx,
+ r->in.domainname,
+ r->out.dcname,
+ WBC_LOOKUP_DC_IS_FLAT_NAME |
+ WBC_LOOKUP_DC_RETURN_FLAT_NAME,
+ &werr);
+ if (ret == true) {
+ return werr;
+ }
+
+ flags = DS_IS_FLAT_NAME | DS_RETURN_FLAT_NAME;
+
+ status = dsgetdcname(p->mem_ctx,
+ p->msg_ctx,
+ r->in.domainname,
+ NULL,
+ NULL,
+ flags,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ *r->out.dcname = talloc_strdup(p->mem_ctx, info->dc_unc);
+ talloc_free(info);
+ if (!*r->out.dcname) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_DatabaseSync2(struct pipes_struct *p,
+ struct netr_DatabaseSync2 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_DatabaseRedo(struct pipes_struct *p,
+ struct netr_DatabaseRedo *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsRGetDCName(struct pipes_struct *p,
+ struct netr_DsRGetDCName *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_LogonGetCapabilities(struct pipes_struct *p,
+ struct netr_LogonGetCapabilities *r)
+{
+ struct netlogon_creds_CredentialState *creds;
+ NTSTATUS status;
+
+ switch (r->in.query_level) {
+ case 1:
+ break;
+ case 2:
+ /*
+ * Until we know the details behind KB5028166
+ * just return DCERPC_NCA_S_FAULT_INVALID_TAG
+ * like an unpatched Windows Server.
+ */
+ FALL_THROUGH;
+ default:
+ /*
+ * There would not be a way to marshall the
+ * the response. Which would mean our final
+ * ndr_push would fail an we would return
+ * an RPC-level fault with DCERPC_FAULT_BAD_STUB_DATA.
+ *
+ * But it's important to match a Windows server
+ * especially before KB5028166, see also our bug #15418
+ * Otherwise Windows client would stop talking to us.
+ */
+ p->fault_state = DCERPC_NCA_S_FAULT_INVALID_TAG;
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ become_root();
+ status = dcesrv_netr_creds_server_step_check(p->dce_call,
+ p->mem_ctx,
+ r->in.computer_name,
+ r->in.credential,
+ r->out.return_authenticator,
+ &creds);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->out.capabilities->server_capabilities = creds->negotiate_flags;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_NETRLOGONSETSERVICEBITS(struct pipes_struct *p,
+ struct netr_NETRLOGONSETSERVICEBITS *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_LogonGetTrustRid(struct pipes_struct *p,
+ struct netr_LogonGetTrustRid *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_NETRLOGONCOMPUTESERVERDIGEST(struct pipes_struct *p,
+ struct netr_NETRLOGONCOMPUTESERVERDIGEST *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_NETRLOGONCOMPUTECLIENTDIGEST(struct pipes_struct *p,
+ struct netr_NETRLOGONCOMPUTECLIENTDIGEST *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsRGetDCNameEx(struct pipes_struct *p,
+ struct netr_DsRGetDCNameEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsRGetSiteName(struct pipes_struct *p,
+ struct netr_DsRGetSiteName *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_LogonGetDomainInfo(struct pipes_struct *p,
+ struct netr_LogonGetDomainInfo *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_ServerPasswordGet(struct pipes_struct *p,
+ struct netr_ServerPasswordGet *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_NetrLogonSendToSam(struct pipes_struct *p,
+ struct netr_NetrLogonSendToSam *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsRAddressToSitenamesW(struct pipes_struct *p,
+ struct netr_DsRAddressToSitenamesW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsRGetDCNameEx2(struct pipes_struct *p,
+ struct netr_DsRGetDCNameEx2 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN(struct pipes_struct *p,
+ struct netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_NetrEnumerateTrustedDomainsEx(struct pipes_struct *p,
+ struct netr_NetrEnumerateTrustedDomainsEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsRAddressToSitenamesExW(struct pipes_struct *p,
+ struct netr_DsRAddressToSitenamesExW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsrGetDcSiteCoverageW(struct pipes_struct *p,
+ struct netr_DsrGetDcSiteCoverageW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsrEnumerateDomainTrusts(struct pipes_struct *p,
+ struct netr_DsrEnumerateDomainTrusts *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsrDeregisterDNSHostRecords(struct pipes_struct *p,
+ struct netr_DsrDeregisterDNSHostRecords *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_ServerTrustPasswordsGet(struct pipes_struct *p,
+ struct netr_ServerTrustPasswordsGet *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS fill_forest_trust_array(TALLOC_CTX *mem_ctx,
+ struct lsa_ForestTrustInformation *info)
+{
+ struct lsa_ForestTrustRecord *e;
+ struct pdb_domain_info *dom_info;
+ struct lsa_ForestTrustDomainInfo *domain_info;
+ char **upn_suffixes = NULL;
+ uint32_t num_suffixes = 0;
+ uint32_t i = 0;
+ NTSTATUS status;
+
+ dom_info = pdb_get_domain_info(mem_ctx);
+ if (dom_info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->count = 2;
+
+ become_root();
+ status = pdb_enum_upn_suffixes(info, &num_suffixes, &upn_suffixes);
+ unbecome_root();
+ if (NT_STATUS_IS_OK(status) && (num_suffixes > 0)) {
+ info->count += num_suffixes;
+ }
+
+ info->entries = talloc_array(info, struct lsa_ForestTrustRecord *, info->count);
+ if (info->entries == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ e = talloc(info, struct lsa_ForestTrustRecord);
+ if (e == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ e->flags = 0;
+ e->type = LSA_FOREST_TRUST_TOP_LEVEL_NAME;
+ e->time = 0; /* so far always 0 in trces. */
+ e->forest_trust_data.top_level_name.string = talloc_steal(info,
+ dom_info->dns_forest);
+
+ info->entries[0] = e;
+
+ if (num_suffixes > 0) {
+ for (i = 0; i < num_suffixes ; i++) {
+ e = talloc(info, struct lsa_ForestTrustRecord);
+ if (e == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ e->flags = 0;
+ e->type = LSA_FOREST_TRUST_TOP_LEVEL_NAME;
+ e->time = 0; /* so far always 0 in traces. */
+ e->forest_trust_data.top_level_name.string = upn_suffixes[i];
+ info->entries[1 + i] = e;
+ }
+ }
+
+ e = talloc(info, struct lsa_ForestTrustRecord);
+ if (e == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* TODO: check if disabled and set flags accordingly */
+ e->flags = 0;
+ e->type = LSA_FOREST_TRUST_DOMAIN_INFO;
+ e->time = 0; /* so far always 0 in traces. */
+
+ domain_info = &e->forest_trust_data.domain_info;
+ domain_info->domain_sid = dom_sid_dup(info, &dom_info->sid);
+
+ domain_info->dns_domain_name.string = talloc_steal(info,
+ dom_info->dns_domain);
+ domain_info->netbios_domain_name.string = talloc_steal(info,
+ dom_info->name);
+
+ info->entries[info->count - 1] = e;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _netr_DsRGetForestTrustInformation(struct pipes_struct *p,
+ struct netr_DsRGetForestTrustInformation *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ struct lsa_ForestTrustInformation *info, **info_ptr;
+ enum security_user_level security_level;
+
+ security_level = security_session_user_level(session_info, NULL);
+ if (security_level < SECURITY_USER) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (r->in.flags & (~DS_GFTI_UPDATE_TDO)) {
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_INVALID_FLAGS;
+ }
+
+ if ((r->in.flags & DS_GFTI_UPDATE_TDO) && (lp_server_role() != ROLE_DOMAIN_PDC)) {
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NERR_NOTPRIMARY;
+ }
+
+ if ((r->in.trusted_domain_name == NULL) && (r->in.flags & DS_GFTI_UPDATE_TDO)) {
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* retrieve forest trust information and stop further processing */
+ if (r->in.trusted_domain_name == NULL) {
+ info_ptr = talloc(p->mem_ctx, struct lsa_ForestTrustInformation *);
+ if (info_ptr == NULL) {
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ info = talloc_zero(info_ptr, struct lsa_ForestTrustInformation);
+ if (info == NULL) {
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* Fill forest trust information and expand UPN suffixes list */
+ status = fill_forest_trust_array(p->mem_ctx, info);
+ if (!NT_STATUS_IS_OK(status)) {
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *info_ptr = info;
+ r->out.forest_trust_info = info_ptr;
+
+ return WERR_OK;
+
+ }
+
+ /* TODO: implement remaining parts of DsrGetForestTrustInformation (opnum 43)
+ * when trusted_domain_name is not NULL */
+
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _netr_GetForestTrustInformation
+****************************************************************/
+
+NTSTATUS _netr_GetForestTrustInformation(struct pipes_struct *p,
+ struct netr_GetForestTrustInformation *r)
+{
+ NTSTATUS status;
+ struct netlogon_creds_CredentialState *creds;
+ struct lsa_ForestTrustInformation *info, **info_ptr;
+
+ /* TODO: check server name */
+
+ become_root();
+ status = dcesrv_netr_creds_server_step_check(p->dce_call,
+ p->mem_ctx,
+ r->in.computer_name,
+ r->in.credential,
+ r->out.return_authenticator,
+ &creds);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if ((creds->secure_channel_type != SEC_CHAN_DNS_DOMAIN) &&
+ (creds->secure_channel_type != SEC_CHAN_DOMAIN)) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ info_ptr = talloc(p->mem_ctx, struct lsa_ForestTrustInformation *);
+ if (!info_ptr) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ info = talloc_zero(info_ptr, struct lsa_ForestTrustInformation);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Fill forest trust information, do expand UPN suffixes list */
+ status = fill_forest_trust_array(p->mem_ctx, info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *info_ptr = info;
+ r->out.forest_trust_info = info_ptr;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static NTSTATUS get_password_from_trustAuth(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *trustAuth_blob,
+ struct netlogon_creds_CredentialState *creds,
+ struct samr_Password *current_pw_enc,
+ struct samr_Password *previous_pw_enc)
+{
+ enum ndr_err_code ndr_err;
+ struct trustAuthInOutBlob trustAuth;
+ NTSTATUS status;
+
+ ndr_err = ndr_pull_struct_blob_all(trustAuth_blob, mem_ctx, &trustAuth,
+ (ndr_pull_flags_fn_t)ndr_pull_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (trustAuth.count != 0 && trustAuth.current.count != 0 &&
+ trustAuth.current.array[0].AuthType == TRUST_AUTH_TYPE_CLEAR) {
+ mdfour(current_pw_enc->hash,
+ trustAuth.current.array[0].AuthInfo.clear.password,
+ trustAuth.current.array[0].AuthInfo.clear.size);
+ status = netlogon_creds_des_encrypt(creds, current_pw_enc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+
+ if (trustAuth.previous.count != 0 &&
+ trustAuth.previous.array[0].AuthType == TRUST_AUTH_TYPE_CLEAR) {
+ mdfour(previous_pw_enc->hash,
+ trustAuth.previous.array[0].AuthInfo.clear.password,
+ trustAuth.previous.array[0].AuthInfo.clear.size);
+ status = netlogon_creds_des_encrypt(creds, previous_pw_enc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ ZERO_STRUCTP(previous_pw_enc);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+ _netr_ServerGetTrustInfo
+****************************************************************/
+
+NTSTATUS _netr_ServerGetTrustInfo(struct pipes_struct *p,
+ struct netr_ServerGetTrustInfo *r)
+{
+ NTSTATUS status;
+ struct netlogon_creds_CredentialState *creds;
+ char *account_name;
+ size_t account_name_last;
+ bool trusted;
+ struct netr_TrustInfo *trust_info;
+ struct pdb_trusted_domain *td;
+
+ /* TODO: check server name */
+
+ become_root();
+ status = dcesrv_netr_creds_server_step_check(p->dce_call,
+ p->mem_ctx,
+ r->in.computer_name,
+ r->in.credential,
+ r->out.return_authenticator,
+ &creds);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ account_name = talloc_strdup(p->mem_ctx, r->in.account_name);
+ if (account_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ account_name_last = strlen(account_name);
+ if (account_name_last == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ account_name_last--;
+ if (account_name[account_name_last] == '.') {
+ account_name[account_name_last] = '\0';
+ }
+
+ if ((creds->secure_channel_type != SEC_CHAN_DNS_DOMAIN) &&
+ (creds->secure_channel_type != SEC_CHAN_DOMAIN)) {
+ trusted = false;
+ } else {
+ trusted = true;
+ }
+
+
+ if (trusted) {
+ account_name_last = strlen(account_name);
+ if (account_name_last == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ account_name_last--;
+ if (account_name[account_name_last] == '$') {
+ account_name[account_name_last] = '\0';
+ }
+
+ status = pdb_get_trusted_domain(p->mem_ctx, account_name, &td);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (r->out.trust_info != NULL) {
+ trust_info = talloc_zero(p->mem_ctx, struct netr_TrustInfo);
+ if (trust_info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ trust_info->count = 1;
+
+ trust_info->data = talloc_array(trust_info, uint32_t, 1);
+ if (trust_info->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ trust_info->data[0] = td->trust_attributes;
+
+ *r->out.trust_info = trust_info;
+ }
+
+ if (td->trust_auth_incoming.data == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = get_password_from_trustAuth(p->mem_ctx,
+ &td->trust_auth_incoming,
+ creds,
+ r->out.new_owf_password,
+ r->out.old_owf_password);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ } else {
+/* TODO: look for machine password */
+ ZERO_STRUCTP(r->out.new_owf_password);
+ ZERO_STRUCTP(r->out.old_owf_password);
+
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_Unused47(struct pipes_struct *p,
+ struct netr_Unused47 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _netr_DsrUpdateReadOnlyServerDnsRecords(struct pipes_struct *p,
+ struct netr_DsrUpdateReadOnlyServerDnsRecords *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/*
+ * Define the bind function that will be used by ndr_netlogon_scompat.c,
+ * included at the bottom of this file.
+ */
+#define DCESRV_INTERFACE_NETLOGON_BIND(context, iface) \
+ dcesrv_interface_netlogon_bind(context, iface)
+
+static NTSTATUS dcesrv_interface_netlogon_bind(struct dcesrv_connection_context *context,
+ const struct dcesrv_interface *iface)
+{
+ struct loadparm_context *lp_ctx = context->conn->dce_ctx->lp_ctx;
+ int schannel = lpcfg_server_schannel(lp_ctx);
+ bool schannel_global_required = (schannel == true);
+ bool global_require_seal = lpcfg_server_schannel_require_seal(lp_ctx);
+ static bool warned_global_schannel_once = false;
+ static bool warned_global_seal_once = false;
+
+ if (!schannel_global_required && !warned_global_schannel_once) {
+ /*
+ * We want admins to notice their misconfiguration!
+ */
+ D_ERR("CVE-2020-1472(ZeroLogon): "
+ "Please configure 'server schannel = yes' (the default), "
+ "See https://bugzilla.samba.org/show_bug.cgi?id=14497\n");
+ warned_global_schannel_once = true;
+ }
+
+ if (!global_require_seal && !warned_global_seal_once) {
+ /*
+ * We want admins to notice their misconfiguration!
+ */
+ D_ERR("CVE-2022-38023 (and others): "
+ "Please configure 'server schannel require seal = yes' (the default), "
+ "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n");
+ warned_global_seal_once = true;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_netlogon_scompat.c"
diff --git a/source3/rpc_server/ntsvcs/srv_ntsvcs_nt.c b/source3/rpc_server/ntsvcs/srv_ntsvcs_nt.c
new file mode 100644
index 0000000..cfa2336
--- /dev/null
+++ b/source3/rpc_server/ntsvcs/srv_ntsvcs_nt.c
@@ -0,0 +1,810 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ * Copyright (C) Guenther Deschner 2008,2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_ntsvcs.h"
+#include "librpc/gen_ndr/ndr_ntsvcs_scompat.h"
+#include "services/svc_winreg_glue.h"
+#include "../libcli/registry/util_reg.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+/********************************************************************
+********************************************************************/
+
+static char* get_device_path(TALLOC_CTX *mem_ctx, const char *device )
+{
+ return talloc_asprintf(mem_ctx, "ROOT\\Legacy_%s\\0000", device);
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR _PNP_GetVersion(struct pipes_struct *p,
+ struct PNP_GetVersion *r)
+{
+ *r->out.version = 0x0400; /* no idea what this means */
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR _PNP_GetDeviceListSize(struct pipes_struct *p,
+ struct PNP_GetDeviceListSize *r)
+{
+ char *devicepath;
+
+ if ((r->in.flags & CM_GETIDLIST_FILTER_SERVICE) &&
+ (!r->in.devicename)) {
+ return WERR_CM_INVALID_POINTER;
+ }
+
+ if (!(devicepath = get_device_path(p->mem_ctx, r->in.devicename))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *r->out.size = strlen(devicepath) + 2;
+
+ TALLOC_FREE(devicepath);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _PNP_GetDeviceList
+****************************************************************/
+
+WERROR _PNP_GetDeviceList(struct pipes_struct *p,
+ struct PNP_GetDeviceList *r)
+{
+ char *devicepath;
+ uint32_t size = 0;
+ const char **multi_sz = NULL;
+ DATA_BLOB blob;
+
+ if ((r->in.flags & CM_GETIDLIST_FILTER_SERVICE) &&
+ (!r->in.filter)) {
+ return WERR_CM_INVALID_POINTER;
+ }
+
+ if (!(devicepath = get_device_path(p->mem_ctx, r->in.filter))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ size = strlen(devicepath) + 2;
+
+ if (*r->in.length < size) {
+ return WERR_CM_BUFFER_SMALL;
+ }
+
+ multi_sz = talloc_zero_array(p->mem_ctx, const char *, 2);
+ if (!multi_sz) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ multi_sz[0] = devicepath;
+
+ if (!push_reg_multi_sz(multi_sz, &blob, multi_sz)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (*r->in.length < blob.length/2) {
+ return WERR_CM_BUFFER_SMALL;
+ }
+
+ memcpy(r->out.buffer, blob.data, blob.length);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+_PNP_GetDeviceRegProp
+********************************************************************/
+
+WERROR _PNP_GetDeviceRegProp(struct pipes_struct *p,
+ struct PNP_GetDeviceRegProp *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ char *ptr;
+ const char *result;
+ DATA_BLOB blob;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ switch( r->in.property ) {
+ case DEV_REGPROP_DESC:
+
+ /* just parse the service name from the device path and then
+ lookup the display name */
+ if ( !(ptr = strrchr_m( r->in.devicepath, '\\' )) )
+ return WERR_GEN_FAILURE;
+ *ptr = '\0';
+
+ if ( !(ptr = strrchr_m( r->in.devicepath, '_' )) )
+ return WERR_GEN_FAILURE;
+ ptr++;
+
+ mem_ctx = talloc_stackframe();
+
+ result = svcctl_lookup_dispname(mem_ctx,
+ p->msg_ctx,
+ session_info,
+ ptr);
+ if (result == NULL) {
+ return WERR_GEN_FAILURE;
+ }
+
+ if (!push_reg_sz(mem_ctx, &blob, result)) {
+ talloc_free(mem_ctx);
+ return WERR_GEN_FAILURE;
+ }
+
+ if (*r->in.buffer_size < blob.length) {
+ *r->out.needed = blob.length;
+ *r->out.buffer_size = 0;
+ talloc_free(mem_ctx);
+ return WERR_CM_BUFFER_SMALL;
+ }
+
+ r->out.buffer = (uint8_t *)talloc_memdup(p->mem_ctx, blob.data, blob.length);
+ talloc_free(mem_ctx);
+ if (!r->out.buffer) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *r->out.reg_data_type = REG_SZ; /* always 1...tested using a remove device manager connection */
+ *r->out.buffer_size = blob.length;
+ *r->out.needed = blob.length;
+
+ break;
+
+ default:
+ *r->out.reg_data_type = 0x00437c98; /* ??? */
+ return WERR_CM_NO_SUCH_VALUE;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR _PNP_ValidateDeviceInstance(struct pipes_struct *p,
+ struct PNP_ValidateDeviceInstance *r)
+{
+ /* whatever dude */
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR _PNP_GetHwProfInfo(struct pipes_struct *p,
+ struct PNP_GetHwProfInfo *r)
+{
+ /* steal the incoming buffer */
+
+ r->out.info = r->in.info;
+
+ /* Take the 5th Ammentment */
+
+ return WERR_CM_NO_MORE_HW_PROFILES;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR _PNP_HwProfFlags(struct pipes_struct *p,
+ struct PNP_HwProfFlags *r)
+{
+ /* just nod your head */
+
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_Disconnect(struct pipes_struct *p,
+ struct PNP_Disconnect *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_Connect(struct pipes_struct *p,
+ struct PNP_Connect *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetGlobalState(struct pipes_struct *p,
+ struct PNP_GetGlobalState *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_InitDetection(struct pipes_struct *p,
+ struct PNP_InitDetection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_ReportLogOn(struct pipes_struct *p,
+ struct PNP_ReportLogOn *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetRootDeviceInstance(struct pipes_struct *p,
+ struct PNP_GetRootDeviceInstance *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetRelatedDeviceInstance(struct pipes_struct *p,
+ struct PNP_GetRelatedDeviceInstance *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_EnumerateSubKeys(struct pipes_struct *p,
+ struct PNP_EnumerateSubKeys *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetDepth(struct pipes_struct *p,
+ struct PNP_GetDepth *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_SetDeviceRegProp(struct pipes_struct *p,
+ struct PNP_SetDeviceRegProp *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetClassInstance(struct pipes_struct *p,
+ struct PNP_GetClassInstance *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_CreateKey(struct pipes_struct *p,
+ struct PNP_CreateKey *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_DeleteRegistryKey(struct pipes_struct *p,
+ struct PNP_DeleteRegistryKey *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetClassCount(struct pipes_struct *p,
+ struct PNP_GetClassCount *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetClassName(struct pipes_struct *p,
+ struct PNP_GetClassName *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_DeleteClassKey(struct pipes_struct *p,
+ struct PNP_DeleteClassKey *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetInterfaceDeviceAlias(struct pipes_struct *p,
+ struct PNP_GetInterfaceDeviceAlias *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetInterfaceDeviceList(struct pipes_struct *p,
+ struct PNP_GetInterfaceDeviceList *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetInterfaceDeviceListSize(struct pipes_struct *p,
+ struct PNP_GetInterfaceDeviceListSize *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_RegisterDeviceClassAssociation(struct pipes_struct *p,
+ struct PNP_RegisterDeviceClassAssociation *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_UnregisterDeviceClassAssociation(struct pipes_struct *p,
+ struct PNP_UnregisterDeviceClassAssociation *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetClassRegProp(struct pipes_struct *p,
+ struct PNP_GetClassRegProp *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_SetClassRegProp(struct pipes_struct *p,
+ struct PNP_SetClassRegProp *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_CreateDevInst(struct pipes_struct *p,
+ struct PNP_CreateDevInst *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_DeviceInstanceAction(struct pipes_struct *p,
+ struct PNP_DeviceInstanceAction *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetDeviceStatus(struct pipes_struct *p,
+ struct PNP_GetDeviceStatus *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_SetDeviceProblem(struct pipes_struct *p,
+ struct PNP_SetDeviceProblem *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_DisableDevInst(struct pipes_struct *p,
+ struct PNP_DisableDevInst *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_UninstallDevInst(struct pipes_struct *p,
+ struct PNP_UninstallDevInst *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_AddID(struct pipes_struct *p,
+ struct PNP_AddID *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_RegisterDriver(struct pipes_struct *p,
+ struct PNP_RegisterDriver *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_QueryRemove(struct pipes_struct *p,
+ struct PNP_QueryRemove *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_RequestDeviceEject(struct pipes_struct *p,
+ struct PNP_RequestDeviceEject *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_IsDockStationPresent(struct pipes_struct *p,
+ struct PNP_IsDockStationPresent *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_RequestEjectPC(struct pipes_struct *p,
+ struct PNP_RequestEjectPC *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_AddEmptyLogConf(struct pipes_struct *p,
+ struct PNP_AddEmptyLogConf *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_FreeLogConf(struct pipes_struct *p,
+ struct PNP_FreeLogConf *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetFirstLogConf(struct pipes_struct *p,
+ struct PNP_GetFirstLogConf *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetNextLogConf(struct pipes_struct *p,
+ struct PNP_GetNextLogConf *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetLogConfPriority(struct pipes_struct *p,
+ struct PNP_GetLogConfPriority *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_AddResDes(struct pipes_struct *p,
+ struct PNP_AddResDes *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_FreeResDes(struct pipes_struct *p,
+ struct PNP_FreeResDes *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetNextResDes(struct pipes_struct *p,
+ struct PNP_GetNextResDes *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetResDesData(struct pipes_struct *p,
+ struct PNP_GetResDesData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetResDesDataSize(struct pipes_struct *p,
+ struct PNP_GetResDesDataSize *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_ModifyResDes(struct pipes_struct *p,
+ struct PNP_ModifyResDes *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_DetectResourceLimit(struct pipes_struct *p,
+ struct PNP_DetectResourceLimit *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_QueryResConfList(struct pipes_struct *p,
+ struct PNP_QueryResConfList *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_SetHwProf(struct pipes_struct *p,
+ struct PNP_SetHwProf *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_QueryArbitratorFreeData(struct pipes_struct *p,
+ struct PNP_QueryArbitratorFreeData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_QueryArbitratorFreeSize(struct pipes_struct *p,
+ struct PNP_QueryArbitratorFreeSize *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_RunDetection(struct pipes_struct *p,
+ struct PNP_RunDetection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_RegisterNotification(struct pipes_struct *p,
+ struct PNP_RegisterNotification *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_UnregisterNotification(struct pipes_struct *p,
+ struct PNP_UnregisterNotification *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetCustomDevProp(struct pipes_struct *p,
+ struct PNP_GetCustomDevProp *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetVersionInternal(struct pipes_struct *p,
+ struct PNP_GetVersionInternal *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetBlockedDriverInfo(struct pipes_struct *p,
+ struct PNP_GetBlockedDriverInfo *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+WERROR _PNP_GetServerSideDeviceInstallFlags(struct pipes_struct *p,
+ struct PNP_GetServerSideDeviceInstallFlags *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_ntsvcs_scompat.c"
diff --git a/source3/rpc_server/rpc_config.c b/source3/rpc_server/rpc_config.c
new file mode 100644
index 0000000..af167d8
--- /dev/null
+++ b/source3/rpc_server/rpc_config.c
@@ -0,0 +1,77 @@
+/*
+ Unix SMB/Netbios implementation.
+ Generic infrastructure for RPC Daemons
+ Copyright (C) Simo Sorce 2011
+ Copyright (C) Andreas Schneider 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpc_server/rpc_config.h"
+#include "rpc_server/rpc_server.h"
+#include "lib/param/param.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+static struct dcesrv_context_callbacks srv_callbacks = {
+ .log.successful_authz = dcesrv_log_successful_authz,
+ .auth.gensec_prepare = dcesrv_auth_gensec_prepare,
+ .auth.become_root = become_root,
+ .auth.unbecome_root = unbecome_root,
+ .assoc_group.find = dcesrv_assoc_group_find,
+};
+
+static struct dcesrv_context *global_dcesrv_ctx = NULL;
+
+struct dcesrv_context *global_dcesrv_context(void)
+{
+ NTSTATUS status;
+
+ if (global_dcesrv_ctx == NULL) {
+ struct loadparm_context *lp_ctx = NULL;
+
+ DBG_INFO("Initializing DCE/RPC server context\n");
+
+ lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ smb_panic("No memory");
+ }
+
+ /*
+ * Note we MUST use the NULL context here, not the
+ * autofree context, to avoid side effects in forked
+ * children exiting.
+ */
+ status = dcesrv_init_context(global_event_context(),
+ lp_ctx,
+ &srv_callbacks,
+ &global_dcesrv_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ smb_panic("Failed to init DCE/RPC context");
+ }
+
+ talloc_steal(global_dcesrv_ctx, lp_ctx);
+ }
+
+ return global_dcesrv_ctx;
+}
+
+void global_dcesrv_context_free(void)
+{
+ TALLOC_FREE(global_dcesrv_ctx);
+}
diff --git a/source3/rpc_server/rpc_config.h b/source3/rpc_server/rpc_config.h
new file mode 100644
index 0000000..40d5855
--- /dev/null
+++ b/source3/rpc_server/rpc_config.h
@@ -0,0 +1,30 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SMBD RPC service config
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ * Copyright (C) 2011 Simo Sorce <idra@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_CONFIG_H
+#define _RPC_CONFIG_H
+
+struct dcesrv_context;
+struct dcesrv_context *global_dcesrv_context(void);
+void global_dcesrv_context_free(void);
+
+#endif /* _RPC_CONFIG_H */
diff --git a/source3/rpc_server/rpc_handles.c b/source3/rpc_server/rpc_handles.c
new file mode 100644
index 0000000..928b10e
--- /dev/null
+++ b/source3/rpc_server/rpc_handles.c
@@ -0,0 +1,233 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ * Copyright (C) Jeremy Allison 2001.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "../librpc/gen_ndr/ndr_samr.h"
+#include "auth.h"
+#include "rpc_server/rpc_pipes.h"
+#include "../libcli/security/security.h"
+#include "lib/tsocket/tsocket.h"
+#include "librpc/ndr/ndr_table.h"
+#include "librpc/rpc/dcesrv_core.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+static size_t num_handles = 0;
+
+bool check_open_pipes(void)
+{
+ if (num_handles > 0) {
+ return true;
+ }
+
+ return false;
+}
+
+size_t num_pipe_handles(void)
+{
+ return num_handles;
+}
+
+/****************************************************************************
+ find first available policy slot. creates a policy handle for you.
+
+ If "data_ptr" is given, this must be a talloc'ed object, create_policy_hnd
+ talloc_moves this into the handle. If the policy_hnd is closed,
+ data_ptr is TALLOC_FREE()'ed
+****************************************************************************/
+
+struct hnd_cnt {
+ bool _dummy;
+};
+
+static int hnd_cnt_destructor(struct hnd_cnt *cnt)
+{
+ num_handles--;
+ return 0;
+}
+
+void *create_policy_hnd(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ uint8_t handle_type,
+ void *data_ptr)
+{
+ struct dcesrv_handle *rpc_hnd = NULL;
+ struct hnd_cnt *cnt = NULL;
+
+ rpc_hnd = dcesrv_handle_create(p->dce_call, handle_type);
+ if (rpc_hnd == NULL) {
+ return NULL;
+ }
+
+ cnt = talloc_zero(rpc_hnd, struct hnd_cnt);
+ if (cnt == NULL) {
+ TALLOC_FREE(rpc_hnd);
+ return NULL;
+ }
+ talloc_set_destructor(cnt, hnd_cnt_destructor);
+
+ if (data_ptr != NULL) {
+ rpc_hnd->data = talloc_move(rpc_hnd, &data_ptr);
+ }
+
+ *hnd = rpc_hnd->wire_handle;
+
+ num_handles++;
+
+ return rpc_hnd;
+}
+
+/****************************************************************************
+ find policy by handle - internal version.
+****************************************************************************/
+
+static struct dcesrv_handle *find_policy_by_hnd_internal(
+ struct pipes_struct *p,
+ const struct policy_handle *hnd,
+ uint8_t handle_type,
+ void **data_p)
+{
+ struct dcesrv_handle *h = NULL;
+
+ if (data_p) {
+ *data_p = NULL;
+ }
+
+ /*
+ * Do not pass an empty policy_handle to dcesrv_handle_lookup() or
+ * it will create a new empty handle
+ */
+ if (ndr_policy_handle_empty(hnd)) {
+ p->fault_state = DCERPC_FAULT_CONTEXT_MISMATCH;
+ return NULL;
+ }
+
+ /*
+ * Do not pass handle_type to avoid setting the fault_state in the
+ * pipes_struct if the handle type does not match
+ */
+ h = dcesrv_handle_lookup(p->dce_call, hnd, DCESRV_HANDLE_ANY);
+ if (h == NULL) {
+ p->fault_state = DCERPC_FAULT_CONTEXT_MISMATCH;
+ return NULL;
+ }
+
+ if (handle_type != DCESRV_HANDLE_ANY &&
+ h->wire_handle.handle_type != handle_type) {
+ /* Just return NULL, do not set a fault
+ * state in pipes_struct */
+ return NULL;
+ }
+
+ if (data_p) {
+ *data_p = h->data;
+ }
+
+ return h;
+}
+
+/****************************************************************************
+ find policy by handle
+****************************************************************************/
+
+void *_find_policy_by_hnd(struct pipes_struct *p,
+ const struct policy_handle *hnd,
+ uint8_t handle_type,
+ NTSTATUS *pstatus)
+{
+ struct dcesrv_handle *rpc_hnd = NULL;
+ void *data = NULL;
+
+ rpc_hnd = find_policy_by_hnd_internal(p, hnd, handle_type, &data);
+ if (rpc_hnd == NULL) {
+ *pstatus = NT_STATUS_INVALID_HANDLE;
+ return NULL;
+ }
+
+ *pstatus = NT_STATUS_OK;
+ return data;
+}
+
+/****************************************************************************
+ Close a policy.
+****************************************************************************/
+
+bool close_policy_hnd(struct pipes_struct *p,
+ struct policy_handle *hnd)
+{
+ struct dcesrv_handle *rpc_hnd = NULL;
+
+ rpc_hnd = find_policy_by_hnd_internal(p, hnd, DCESRV_HANDLE_ANY, NULL);
+ if (rpc_hnd == NULL) {
+ DEBUG(3, ("Error closing policy (policy not found)\n"));
+ return false;
+ }
+
+ TALLOC_FREE(rpc_hnd);
+
+ return true;
+}
+
+/*******************************************************************
+Shall we allow access to this rpc? Currently this function
+implements the 'restrict anonymous' setting by denying access to
+anonymous users if the restrict anonymous level is > 0. Further work
+will be checking a security descriptor to determine whether a user
+token has enough access to access the pipe.
+********************************************************************/
+
+bool pipe_access_check(struct pipes_struct *p)
+{
+ /* Don't let anonymous users access this RPC if restrict
+ anonymous > 0 */
+
+ if (lp_restrict_anonymous() > 0) {
+
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_auth *auth_state = dce_call->auth_state;
+ enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE;
+ struct auth_session_info *session_info = NULL;
+ enum security_user_level user_level;
+
+ if (!auth_state->auth_finished) {
+ return false;
+ }
+
+ dcesrv_call_auth_info(dce_call, &auth_type, NULL);
+
+ /* schannel, so we must be ok */
+ if (auth_type == DCERPC_AUTH_TYPE_SCHANNEL) {
+ return True;
+ }
+
+ session_info = dcesrv_call_session_info(dce_call);
+ user_level = security_session_user_level(session_info, NULL);
+
+ if (user_level < SECURITY_USER) {
+ return False;
+ }
+ }
+
+ return True;
+}
diff --git a/source3/rpc_server/rpc_host.c b/source3/rpc_server/rpc_host.c
new file mode 100644
index 0000000..1e891b4
--- /dev/null
+++ b/source3/rpc_server/rpc_host.c
@@ -0,0 +1,2984 @@
+/*
+ * RPC host
+ *
+ * Implements samba-dcerpcd service.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This binary has two usage modes:
+ *
+ * In the normal case when invoked from smbd or winbind it is given a
+ * directory to scan via --libexec-rpcds and will invoke on demand any
+ * binaries it finds there starting with rpcd_ when a named pipe
+ * connection is requested.
+ *
+ * In the second mode it can be started explicitly from system startup
+ * scripts.
+ *
+ * When Samba is set up as an Active Directory Domain Controller the
+ * normal samba binary overrides and provides DCERPC services, whilst
+ * allowing samba-dcerpcd to provide the services that smbd used to
+ * provide in that set-up, such as SRVSVC.
+ *
+ * The second mode can also be useful for use outside of the Samba framework,
+ * for example, use with the Linux kernel SMB2 server ksmbd. In this mode
+ * it behaves like inetd and listens on sockets on behalf of RPC server
+ * implementations.
+ */
+
+#include "replace.h"
+#include <fnmatch.h>
+#include "lib/cmdline/cmdline.h"
+#include "lib/cmdline/closefrom_except.h"
+#include "source3/include/includes.h"
+#include "source3/include/auth.h"
+#include "rpc_sock_helper.h"
+#include "messages.h"
+#include "lib/util_file.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/debug.h"
+#include "lib/util/server_id.h"
+#include "lib/util/util_tdb.h"
+#include "lib/tdb_wrap/tdb_wrap.h"
+#include "lib/async_req/async_sock.h"
+#include "librpc/rpc/dcerpc_util.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/named_pipe_auth/npa_tstream.h"
+#include "librpc/gen_ndr/ndr_rpc_host.h"
+#include "source3/param/loadparm.h"
+#include "source3/lib/global_contexts.h"
+#include "lib/util/strv.h"
+#include "lib/util/pidfile.h"
+#include "source3/rpc_client/cli_pipe.h"
+#include "librpc/gen_ndr/ndr_epmapper.h"
+#include "librpc/gen_ndr/ndr_epmapper_c.h"
+#include "nsswitch/winbind_client.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/security_token.h"
+
+extern bool override_logfile;
+
+struct rpc_server;
+struct rpc_work_process;
+
+/*
+ * samba-dcerpcd state to keep track of rpcd_* servers.
+ */
+struct rpc_host {
+ struct messaging_context *msg_ctx;
+ struct rpc_server **servers;
+ struct tdb_wrap *epmdb;
+
+ int worker_stdin[2];
+
+ bool np_helper;
+
+ /*
+ * If we're started with --np-helper but nobody contacts us,
+ * we need to exit after a while. This will be deleted once
+ * the first real client connects and our self-exit mechanism
+ * when we don't have any worker processes left kicks in.
+ */
+ struct tevent_timer *np_helper_shutdown;
+};
+
+/*
+ * Map a RPC interface to a name. Used when filling the endpoint
+ * mapper database
+ */
+struct rpc_host_iface_name {
+ struct ndr_syntax_id iface;
+ char *name;
+};
+
+/*
+ * rpc_host representation for listening sockets. ncacn_ip_tcp might
+ * listen on multiple explicit IPs, all with the same port.
+ */
+struct rpc_host_endpoint {
+ struct rpc_server *server;
+ struct dcerpc_binding *binding;
+ struct ndr_syntax_id *interfaces;
+ int *fds;
+ size_t num_fds;
+};
+
+/*
+ * Staging area until we sent the socket plus bind to the helper
+ */
+struct rpc_host_pending_client {
+ struct rpc_host_pending_client *prev, *next;
+
+ /*
+ * Pointer for the destructor to remove us from the list of
+ * pending clients
+ */
+ struct rpc_server *server;
+
+ /*
+ * Waiter for client exit before a helper accepted the request
+ */
+ struct tevent_req *hangup_wait;
+
+ /*
+ * Info to pick the worker
+ */
+ struct ncacn_packet *bind_pkt;
+
+ /*
+ * This is what we send down to the worker
+ */
+ int sock;
+ struct rpc_host_client *client;
+};
+
+/*
+ * Representation of one worker process. For each rpcd_* executable
+ * there will be more of than one of these.
+ */
+struct rpc_work_process {
+ pid_t pid;
+
+ /*
+ * !available means:
+ *
+ * Worker forked but did not send its initial status yet (not
+ * yet initialized)
+ *
+ * Worker died, but we did not receive SIGCHLD yet. We noticed
+ * it because we couldn't send it a message.
+ */
+ bool available;
+
+ /*
+ * Incremented by us when sending a client, decremented by
+ * MSG_RPC_HOST_WORKER_STATUS sent by workers whenever a
+ * client exits.
+ */
+ uint32_t num_associations;
+ uint32_t num_connections;
+
+ /*
+ * Send SHUTDOWN to an idle child after a while
+ */
+ struct tevent_timer *exit_timer;
+};
+
+/*
+ * State for a set of running instances of an rpcd_* server executable
+ */
+struct rpc_server {
+ struct rpc_host *host;
+ /*
+ * Index into the rpc_host_state->servers array
+ */
+ uint32_t server_index;
+
+ const char *rpc_server_exe;
+
+ struct rpc_host_endpoint **endpoints;
+ struct rpc_host_iface_name *iface_names;
+
+ size_t max_workers;
+ size_t idle_seconds;
+
+ /*
+ * "workers" can be larger than "max_workers": Internal
+ * connections require an idle worker to avoid deadlocks
+ * between RPC servers: netlogon requires samr, everybody
+ * requires winreg. And if a deep call in netlogon asks for a
+ * samr connection, this must never end up in the same
+ * process. named_pipe_auth_req_info8->need_idle_server is set
+ * in those cases.
+ */
+ struct rpc_work_process *workers;
+
+ struct rpc_host_pending_client *pending_clients;
+};
+
+struct rpc_server_get_endpoints_state {
+ char **argl;
+ char *ncalrpc_endpoint;
+ enum dcerpc_transport_t only_transport;
+
+ struct rpc_host_iface_name *iface_names;
+ struct rpc_host_endpoint **endpoints;
+
+ unsigned long num_workers;
+ unsigned long idle_seconds;
+};
+
+static void rpc_server_get_endpoints_done(struct tevent_req *subreq);
+
+/**
+ * @brief Query interfaces from an rpcd helper
+ *
+ * Spawn a rpcd helper, ask it for the interfaces it serves via
+ * --list-interfaces, parse the output
+ *
+ * @param[in] mem_ctx Memory context for the tevent_req
+ * @param[in] ev Event context to run this on
+ * @param[in] rpc_server_exe Binary to ask with --list-interfaces
+ * @param[in] only_transport Filter out anything but this
+ * @return The tevent_req representing this process
+ */
+
+static struct tevent_req *rpc_server_get_endpoints_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *rpc_server_exe,
+ enum dcerpc_transport_t only_transport)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct rpc_server_get_endpoints_state *state = NULL;
+ const char *progname = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct rpc_server_get_endpoints_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->only_transport = only_transport;
+
+ progname = strrchr(rpc_server_exe, '/');
+ if (progname != NULL) {
+ progname += 1;
+ } else {
+ progname = rpc_server_exe;
+ }
+
+ state->ncalrpc_endpoint = talloc_strdup(state, progname);
+ if (tevent_req_nomem(state->ncalrpc_endpoint, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->argl = talloc_array(state, char *, 4);
+ if (tevent_req_nomem(state->argl, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->argl = str_list_make_empty(state);
+ str_list_add_printf(&state->argl, "%s", rpc_server_exe);
+ str_list_add_printf(&state->argl, "--list-interfaces");
+ str_list_add_printf(
+ &state->argl, "--configfile=%s", get_dyn_CONFIGFILE());
+
+ if (tevent_req_nomem(state->argl, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = file_ploadv_send(state, ev, state->argl, 65536);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_server_get_endpoints_done, req);
+ return req;
+}
+
+/*
+ * Parse a line of format
+ *
+ * 338cd001-2244-31f1-aaaa-900038001003/0x00000001 winreg
+ *
+ * and add it to the "piface_names" array.
+ */
+
+static struct rpc_host_iface_name *rpc_exe_parse_iface_line(
+ TALLOC_CTX *mem_ctx,
+ struct rpc_host_iface_name **piface_names,
+ const char *line)
+{
+ struct rpc_host_iface_name *iface_names = *piface_names;
+ struct rpc_host_iface_name *tmp = NULL, *result = NULL;
+ size_t i, num_ifaces = talloc_array_length(iface_names);
+ struct ndr_syntax_id iface;
+ char *name = NULL;
+ bool ok;
+
+ ok = ndr_syntax_id_from_string(line, &iface);
+ if (!ok) {
+ DBG_WARNING("ndr_syntax_id_from_string() failed for: [%s]\n",
+ line);
+ return NULL;
+ }
+
+ name = strchr(line, ' ');
+ if (name == NULL) {
+ return NULL;
+ }
+ name += 1;
+
+ for (i=0; i<num_ifaces; i++) {
+ result = &iface_names[i];
+
+ if (ndr_syntax_id_equal(&result->iface, &iface)) {
+ return result;
+ }
+ }
+
+ if (num_ifaces + 1 < num_ifaces) {
+ return NULL;
+ }
+
+ name = talloc_strdup(mem_ctx, name);
+ if (name == NULL) {
+ return NULL;
+ }
+
+ tmp = talloc_realloc(
+ mem_ctx,
+ iface_names,
+ struct rpc_host_iface_name,
+ num_ifaces + 1);
+ if (tmp == NULL) {
+ TALLOC_FREE(name);
+ return NULL;
+ }
+ iface_names = tmp;
+
+ result = &iface_names[num_ifaces];
+
+ *result = (struct rpc_host_iface_name) {
+ .iface = iface,
+ .name = talloc_move(iface_names, &name),
+ };
+
+ *piface_names = iface_names;
+
+ return result;
+}
+
+static struct rpc_host_iface_name *rpc_host_iface_names_find(
+ struct rpc_host_iface_name *iface_names,
+ const struct ndr_syntax_id *iface)
+{
+ size_t i, num_iface_names = talloc_array_length(iface_names);
+
+ for (i=0; i<num_iface_names; i++) {
+ struct rpc_host_iface_name *iface_name = &iface_names[i];
+
+ if (ndr_syntax_id_equal(iface, &iface_name->iface)) {
+ return iface_name;
+ }
+ }
+
+ return NULL;
+}
+
+static bool dcerpc_binding_same_endpoint(
+ const struct dcerpc_binding *b1, const struct dcerpc_binding *b2)
+{
+ enum dcerpc_transport_t t1 = dcerpc_binding_get_transport(b1);
+ enum dcerpc_transport_t t2 = dcerpc_binding_get_transport(b2);
+ const char *e1 = NULL, *e2 = NULL;
+ int cmp;
+
+ if (t1 != t2) {
+ return false;
+ }
+
+ e1 = dcerpc_binding_get_string_option(b1, "endpoint");
+ e2 = dcerpc_binding_get_string_option(b2, "endpoint");
+
+ if ((e1 == NULL) && (e2 == NULL)) {
+ return true;
+ }
+ if ((e1 == NULL) || (e2 == NULL)) {
+ return false;
+ }
+ cmp = strcmp(e1, e2);
+ return (cmp == 0);
+}
+
+/**
+ * @brief Filter whether we want to serve an endpoint
+ *
+ * samba-dcerpcd might want to serve all endpoints a rpcd reported to
+ * us via --list-interfaces.
+ *
+ * In member mode, we only serve named pipes. Indicated by NCACN_NP
+ * passed in via "only_transport".
+ *
+ * @param[in] binding Which binding is in question?
+ * @param[in] only_transport Exclusive transport to serve
+ * @return Do we want to serve "binding" from samba-dcerpcd?
+ */
+
+static bool rpc_host_serve_endpoint(
+ struct dcerpc_binding *binding,
+ enum dcerpc_transport_t only_transport)
+{
+ enum dcerpc_transport_t transport =
+ dcerpc_binding_get_transport(binding);
+
+ if (only_transport == NCA_UNKNOWN) {
+ /* no filter around */
+ return true;
+ }
+
+ if (transport != only_transport) {
+ /* filter out */
+ return false;
+ }
+
+ return true;
+}
+
+static struct rpc_host_endpoint *rpc_host_endpoint_find(
+ struct rpc_server_get_endpoints_state *state,
+ const char *binding_string)
+{
+ size_t i, num_endpoints = talloc_array_length(state->endpoints);
+ struct rpc_host_endpoint **tmp = NULL, *ep = NULL;
+ enum dcerpc_transport_t transport;
+ NTSTATUS status;
+ bool serve_this;
+
+ ep = talloc_zero(state, struct rpc_host_endpoint);
+ if (ep == NULL) {
+ goto fail;
+ }
+
+ status = dcerpc_parse_binding(ep, binding_string, &ep->binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_parse_binding(%s) failed: %s\n",
+ binding_string,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ serve_this = rpc_host_serve_endpoint(
+ ep->binding, state->only_transport);
+ if (!serve_this) {
+ goto fail;
+ }
+
+ transport = dcerpc_binding_get_transport(ep->binding);
+
+ if (transport == NCALRPC) {
+ const char *ncalrpc_sock = dcerpc_binding_get_string_option(
+ ep->binding, "endpoint");
+
+ if (ncalrpc_sock == NULL) {
+ /*
+ * generic ncalrpc:, set program-specific
+ * socket name. epmapper will redirect clients
+ * properly.
+ */
+ status = dcerpc_binding_set_string_option(
+ ep->binding,
+ "endpoint",
+ state->ncalrpc_endpoint);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_binding_set_string_option "
+ "failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ }
+ }
+
+ for (i=0; i<num_endpoints; i++) {
+
+ bool ok = dcerpc_binding_same_endpoint(
+ ep->binding, state->endpoints[i]->binding);
+
+ if (ok) {
+ TALLOC_FREE(ep);
+ return state->endpoints[i];
+ }
+ }
+
+ if (num_endpoints + 1 < num_endpoints) {
+ goto fail;
+ }
+
+ tmp = talloc_realloc(
+ state,
+ state->endpoints,
+ struct rpc_host_endpoint *,
+ num_endpoints + 1);
+ if (tmp == NULL) {
+ goto fail;
+ }
+ state->endpoints = tmp;
+ state->endpoints[num_endpoints] = talloc_move(state->endpoints, &ep);
+
+ return state->endpoints[num_endpoints];
+fail:
+ TALLOC_FREE(ep);
+ return NULL;
+}
+
+static bool ndr_interfaces_add_unique(
+ TALLOC_CTX *mem_ctx,
+ struct ndr_syntax_id **pifaces,
+ const struct ndr_syntax_id *iface)
+{
+ struct ndr_syntax_id *ifaces = *pifaces;
+ size_t i, num_ifaces = talloc_array_length(ifaces);
+
+ for (i=0; i<num_ifaces; i++) {
+ if (ndr_syntax_id_equal(iface, &ifaces[i])) {
+ return true;
+ }
+ }
+
+ if (num_ifaces + 1 < num_ifaces) {
+ return false;
+ }
+ ifaces = talloc_realloc(
+ mem_ctx,
+ ifaces,
+ struct ndr_syntax_id,
+ num_ifaces + 1);
+ if (ifaces == NULL) {
+ return false;
+ }
+ ifaces[num_ifaces] = *iface;
+
+ *pifaces = ifaces;
+ return true;
+}
+
+/*
+ * Read the text reply from the rpcd_* process telling us what
+ * endpoints it will serve when asked with --list-interfaces.
+ */
+static void rpc_server_get_endpoints_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_server_get_endpoints_state *state = tevent_req_data(
+ req, struct rpc_server_get_endpoints_state);
+ struct rpc_host_iface_name *iface = NULL;
+ uint8_t *buf = NULL;
+ size_t buflen;
+ char **lines = NULL;
+ int ret, i, num_lines;
+
+ ret = file_ploadv_recv(subreq, state, &buf);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+
+ buflen = talloc_get_size(buf);
+ if (buflen == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ lines = file_lines_parse((char *)buf, buflen, &num_lines, state);
+ if (tevent_req_nomem(lines, req)) {
+ return;
+ }
+
+ if (num_lines < 2) {
+ DBG_DEBUG("Got %d lines, expected at least 2\n", num_lines);
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ state->num_workers = smb_strtoul(
+ lines[0], NULL, 10, &ret, SMB_STR_FULL_STR_CONV);
+ if (ret != 0) {
+ DBG_DEBUG("Could not parse num_workers(%s): %s\n",
+ lines[0],
+ strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+ /*
+ * We need to limit the number of workers in order
+ * to put the worker index into a 16-bit space,
+ * in order to use a 16-bit association group space
+ * per worker.
+ */
+ if (state->num_workers > 65536) {
+ state->num_workers = 65536;
+ }
+
+ state->idle_seconds = smb_strtoul(
+ lines[1], NULL, 10, &ret, SMB_STR_FULL_STR_CONV);
+ if (ret != 0) {
+ DBG_DEBUG("Could not parse idle_seconds (%s): %s\n",
+ lines[1],
+ strerror(ret));
+ tevent_req_error(req, ret);
+ return;
+ }
+
+ DBG_DEBUG("num_workers=%lu, idle_seconds=%lu for %s\n",
+ state->num_workers,
+ state->idle_seconds,
+ state->argl[0]);
+
+ for (i=2; i<num_lines; i++) {
+ char *line = lines[i];
+ struct rpc_host_endpoint *endpoint = NULL;
+ bool ok;
+
+ if (line[0] != ' ') {
+ iface = rpc_exe_parse_iface_line(
+ state, &state->iface_names, line);
+ if (iface == NULL) {
+ DBG_WARNING(
+ "rpc_exe_parse_iface_line failed "
+ "for: [%s] from %s\n",
+ line,
+ state->argl[0]);
+ tevent_req_oom(req);
+ return;
+ }
+ continue;
+ }
+
+ if (iface == NULL) {
+ DBG_DEBUG("Interface GUID line missing\n");
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ endpoint = rpc_host_endpoint_find(state, line+1);
+ if (endpoint == NULL) {
+ DBG_DEBUG("rpc_host_endpoint_find for %s failed\n",
+ line+1);
+ continue;
+ }
+
+ ok = ndr_interfaces_add_unique(
+ endpoint,
+ &endpoint->interfaces,
+ &iface->iface);
+ if (!ok) {
+ DBG_DEBUG("ndr_interfaces_add_unique failed\n");
+ tevent_req_oom(req);
+ return;
+ }
+ }
+
+ tevent_req_done(req);
+}
+
+/**
+ * @brief Receive output from --list-interfaces
+ *
+ * @param[in] req The async req that just finished
+ * @param[in] mem_ctx Where to put the output on
+ * @param[out] endpoints The endpoints to be listened on
+ * @param[out] iface_names Annotation for epm_Lookup's epm_entry_t
+ * @return 0/errno
+ */
+static int rpc_server_get_endpoints_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_host_endpoint ***endpoints,
+ struct rpc_host_iface_name **iface_names,
+ size_t *num_workers,
+ size_t *idle_seconds)
+{
+ struct rpc_server_get_endpoints_state *state = tevent_req_data(
+ req, struct rpc_server_get_endpoints_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ tevent_req_received(req);
+ return err;
+ }
+
+ *endpoints = talloc_move(mem_ctx, &state->endpoints);
+ *iface_names = talloc_move(mem_ctx, &state->iface_names);
+ *num_workers = state->num_workers;
+ *idle_seconds = state->idle_seconds;
+ tevent_req_received(req);
+ return 0;
+}
+
+/*
+ * For NCACN_NP we get the named pipe auth info from smbd, if a client
+ * comes in via TCP or NCALPRC we need to invent it ourselves with
+ * anonymous session info.
+ */
+
+static NTSTATUS rpc_host_generate_npa_info8_from_sock(
+ TALLOC_CTX *mem_ctx,
+ enum dcerpc_transport_t transport,
+ int sock,
+ const struct samba_sockaddr *peer_addr,
+ struct named_pipe_auth_req_info8 **pinfo8)
+{
+ struct named_pipe_auth_req_info8 *info8 = NULL;
+ struct samba_sockaddr local_addr = {
+ .sa_socklen = sizeof(struct sockaddr_storage),
+ };
+ struct tsocket_address *taddr = NULL;
+ char *remote_client_name = NULL;
+ char *remote_client_addr = NULL;
+ char *local_server_name = NULL;
+ char *local_server_addr = NULL;
+ char *(*tsocket_address_to_name_fn)(
+ const struct tsocket_address *addr,
+ TALLOC_CTX *mem_ctx) = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ int ret;
+
+ /*
+ * For NCACN_NP we get the npa info from smbd
+ */
+ SMB_ASSERT((transport == NCACN_IP_TCP) || (transport == NCALRPC));
+
+ tsocket_address_to_name_fn = (transport == NCACN_IP_TCP) ?
+ tsocket_address_inet_addr_string : tsocket_address_unix_path;
+
+ info8 = talloc_zero(mem_ctx, struct named_pipe_auth_req_info8);
+ if (info8 == NULL) {
+ goto fail;
+ }
+ info8->session_info =
+ talloc_zero(info8, struct auth_session_info_transport);
+ if (info8->session_info == NULL) {
+ goto fail;
+ }
+
+ status = make_session_info_anonymous(
+ info8->session_info,
+ &info8->session_info->session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("make_session_info_anonymous failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ ret = tsocket_address_bsd_from_samba_sockaddr(info8,
+ peer_addr,
+ &taddr);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("tsocket_address_bsd_from_samba_sockaddr failed: "
+ "%s\n",
+ strerror(errno));
+ goto fail;
+ }
+ remote_client_addr = tsocket_address_to_name_fn(taddr, info8);
+ if (remote_client_addr == NULL) {
+ DBG_DEBUG("tsocket_address_to_name_fn failed\n");
+ goto nomem;
+ }
+ TALLOC_FREE(taddr);
+
+ remote_client_name = talloc_strdup(info8, remote_client_addr);
+ if (remote_client_name == NULL) {
+ DBG_DEBUG("talloc_strdup failed\n");
+ goto nomem;
+ }
+
+ if (transport == NCACN_IP_TCP) {
+ bool ok = samba_sockaddr_get_port(peer_addr,
+ &info8->remote_client_port);
+ if (!ok) {
+ DBG_DEBUG("samba_sockaddr_get_port failed\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+
+ ret = getsockname(sock, &local_addr.u.sa, &local_addr.sa_socklen);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("getsockname failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ ret = tsocket_address_bsd_from_samba_sockaddr(info8,
+ &local_addr,
+ &taddr);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("tsocket_address_bsd_from_samba_sockaddr failed: "
+ "%s\n",
+ strerror(errno));
+ goto fail;
+ }
+ local_server_addr = tsocket_address_to_name_fn(taddr, info8);
+ if (local_server_addr == NULL) {
+ DBG_DEBUG("tsocket_address_to_name_fn failed\n");
+ goto nomem;
+ }
+ TALLOC_FREE(taddr);
+
+ local_server_name = talloc_strdup(info8, local_server_addr);
+ if (local_server_name == NULL) {
+ DBG_DEBUG("talloc_strdup failed\n");
+ goto nomem;
+ }
+
+ if (transport == NCACN_IP_TCP) {
+ bool ok = samba_sockaddr_get_port(&local_addr,
+ &info8->local_server_port);
+ if (!ok) {
+ DBG_DEBUG("samba_sockaddr_get_port failed\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+
+ if (transport == NCALRPC) {
+ uid_t uid;
+ gid_t gid;
+
+ ret = getpeereid(sock, &uid, &gid);
+ if (ret < 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("getpeereid failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ if (uid == sec_initial_uid()) {
+
+ /*
+ * Indicate "root" to gensec
+ */
+
+ TALLOC_FREE(remote_client_addr);
+ TALLOC_FREE(remote_client_name);
+
+ ret = tsocket_address_unix_from_path(
+ info8,
+ AS_SYSTEM_MAGIC_PATH_TOKEN,
+ &taddr);
+ if (ret == -1) {
+ DBG_DEBUG("tsocket_address_unix_from_path "
+ "failed\n");
+ goto nomem;
+ }
+
+ remote_client_addr =
+ tsocket_address_unix_path(taddr, info8);
+ if (remote_client_addr == NULL) {
+ DBG_DEBUG("tsocket_address_unix_path "
+ "failed\n");
+ goto nomem;
+ }
+ remote_client_name =
+ talloc_strdup(info8, remote_client_addr);
+ if (remote_client_name == NULL) {
+ DBG_DEBUG("talloc_strdup failed\n");
+ goto nomem;
+ }
+ }
+ }
+
+ info8->remote_client_addr = remote_client_addr;
+ info8->remote_client_name = remote_client_name;
+ info8->local_server_addr = local_server_addr;
+ info8->local_server_name = local_server_name;
+
+ *pinfo8 = info8;
+ return NT_STATUS_OK;
+
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ TALLOC_FREE(info8);
+ return status;
+}
+
+struct rpc_host_bind_read_state {
+ struct tevent_context *ev;
+
+ int sock;
+ struct tstream_context *plain;
+ struct tstream_context *npa_stream;
+
+ struct ncacn_packet *pkt;
+ struct rpc_host_client *client;
+};
+
+static void rpc_host_bind_read_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static void rpc_host_bind_read_got_npa(struct tevent_req *subreq);
+static void rpc_host_bind_read_got_bind(struct tevent_req *subreq);
+
+/*
+ * Wait for a bind packet from a client.
+ */
+static struct tevent_req *rpc_host_bind_read_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ enum dcerpc_transport_t transport,
+ int *psock,
+ const struct samba_sockaddr *peer_addr)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct rpc_host_bind_read_state *state = NULL;
+ int rc, sock_dup;
+ NTSTATUS status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct rpc_host_bind_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ state->sock = *psock;
+ *psock = -1;
+
+ tevent_req_set_cleanup_fn(req, rpc_host_bind_read_cleanup);
+
+ state->client = talloc_zero(state, struct rpc_host_client);
+ if (tevent_req_nomem(state->client, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Dup the socket to read the first RPC packet:
+ * tstream_bsd_existing_socket() takes ownership with
+ * autoclose, but we need to send "sock" down to our worker
+ * process later.
+ */
+ sock_dup = dup(state->sock);
+ if (sock_dup == -1) {
+ tevent_req_error(req, errno);
+ return tevent_req_post(req, ev);
+ }
+
+ rc = tstream_bsd_existing_socket(state, sock_dup, &state->plain);
+ if (rc == -1) {
+ DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n",
+ strerror(errno));
+ tevent_req_error(req, errno);
+ close(sock_dup);
+ return tevent_req_post(req, ev);
+ }
+ /* as server we want to fail early */
+ tstream_bsd_fail_readv_first_error(state->plain, true);
+
+ if (transport == NCACN_NP) {
+ subreq = tstream_npa_accept_existing_send(
+ state,
+ ev,
+ state->plain,
+ FILE_TYPE_MESSAGE_MODE_PIPE,
+ 0xff | 0x0400 | 0x0100,
+ 4096);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, rpc_host_bind_read_got_npa, req);
+ return req;
+ }
+
+ status = rpc_host_generate_npa_info8_from_sock(
+ state->client,
+ transport,
+ state->sock,
+ peer_addr,
+ &state->client->npa_info8);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_read_ncacn_packet_send(state, ev, state->plain);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_host_bind_read_got_bind, req);
+ return req;
+}
+
+static void rpc_host_bind_read_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct rpc_host_bind_read_state *state = tevent_req_data(
+ req, struct rpc_host_bind_read_state);
+
+ if ((req_state == TEVENT_REQ_RECEIVED) && (state->sock != -1)) {
+ close(state->sock);
+ state->sock = -1;
+ }
+}
+
+static void rpc_host_bind_read_got_npa(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_host_bind_read_state *state = tevent_req_data(
+ req, struct rpc_host_bind_read_state);
+ struct named_pipe_auth_req_info8 *info8 = NULL;
+ int ret, err;
+
+ ret = tstream_npa_accept_existing_recv(subreq,
+ &err,
+ state,
+ &state->npa_stream,
+ &info8,
+ NULL, /* transport */
+ NULL, /* remote_client_addr */
+ NULL, /* remote_client_name */
+ NULL, /* local_server_addr */
+ NULL, /* local_server_name */
+ NULL); /* session_info */
+ if (ret == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+
+ state->client->npa_info8 = talloc_move(state->client, &info8);
+
+ subreq = dcerpc_read_ncacn_packet_send(
+ state, state->ev, state->npa_stream);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_host_bind_read_got_bind, req);
+}
+
+static void rpc_host_bind_read_got_bind(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_host_bind_read_state *state = tevent_req_data(
+ req, struct rpc_host_bind_read_state);
+ struct ncacn_packet *pkt = NULL;
+ NTSTATUS status;
+
+ status = dcerpc_read_ncacn_packet_recv(
+ subreq,
+ state->client,
+ &pkt,
+ &state->client->bind_packet);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_read_ncacn_packet_recv failed: %s\n",
+ nt_errstr(status));
+ tevent_req_error(req, EINVAL); /* TODO */
+ return;
+ }
+ state->pkt = talloc_move(state, &pkt);
+
+ tevent_req_done(req);
+}
+
+static int rpc_host_bind_read_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ int *sock,
+ struct rpc_host_client **client,
+ struct ncacn_packet **bind_pkt)
+{
+ struct rpc_host_bind_read_state *state = tevent_req_data(
+ req, struct rpc_host_bind_read_state);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ tevent_req_received(req);
+ return err;
+ }
+
+ *sock = state->sock;
+ state->sock = -1;
+
+ *client = talloc_move(mem_ctx, &state->client);
+ *bind_pkt = talloc_move(mem_ctx, &state->pkt);
+ tevent_req_received(req);
+ return 0;
+}
+
+/*
+ * Start the given rpcd_* binary.
+ */
+static int rpc_host_exec_worker(struct rpc_server *server, size_t idx)
+{
+ struct rpc_work_process *worker = &server->workers[idx];
+ char **argv = NULL;
+ int ret = ENOMEM;
+
+ argv = str_list_make_empty(server);
+ str_list_add_printf(
+ &argv, "%s", server->rpc_server_exe);
+ str_list_add_printf(
+ &argv, "--configfile=%s", get_dyn_CONFIGFILE());
+ str_list_add_printf(
+ &argv, "--worker-group=%"PRIu32, server->server_index);
+ str_list_add_printf(
+ &argv, "--worker-index=%zu", idx);
+ str_list_add_printf(
+ &argv, "--debuglevel=%d", debuglevel_get_class(DBGC_RPC_SRV));
+ if (!is_default_dyn_LOGFILEBASE()) {
+ str_list_add_printf(
+ &argv, "--log-basename=%s", get_dyn_LOGFILEBASE());
+ }
+ if (argv == NULL) {
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ worker->pid = fork();
+ if (worker->pid == -1) {
+ ret = errno;
+ goto fail;
+ }
+ if (worker->pid == 0) {
+ /* Child. */
+ close(server->host->worker_stdin[1]);
+ ret = dup2(server->host->worker_stdin[0], 0);
+ if (ret != 0) {
+ exit(1);
+ }
+ execv(argv[0], argv);
+ _exit(1);
+ }
+
+ DBG_DEBUG("Creating worker %s for index %zu: pid=%d\n",
+ server->rpc_server_exe,
+ idx,
+ (int)worker->pid);
+
+ ret = 0;
+fail:
+ TALLOC_FREE(argv);
+ return ret;
+}
+
+/*
+ * Find an rpcd_* worker for an external client, respect server->max_workers
+ */
+static struct rpc_work_process *rpc_host_find_worker(struct rpc_server *server)
+{
+ struct rpc_work_process *worker = NULL;
+ struct rpc_work_process *perfect_worker = NULL;
+ struct rpc_work_process *best_worker = NULL;
+ size_t empty_slot = SIZE_MAX;
+ size_t i;
+
+ for (i=0; i<server->max_workers; i++) {
+ worker = &server->workers[i];
+
+ if (worker->pid == -1) {
+ empty_slot = MIN(empty_slot, i);
+ continue;
+ }
+ if (!worker->available) {
+ continue;
+ }
+ if (worker->num_associations == 0) {
+ /*
+ * We have an idle worker...
+ */
+ perfect_worker = worker;
+ break;
+ }
+ if (best_worker == NULL) {
+ /*
+ * It's busy, but the best so far...
+ */
+ best_worker = worker;
+ continue;
+ }
+ if (worker->num_associations < best_worker->num_associations) {
+ /*
+ * It's also busy, but has less association groups
+ * (logical clients)
+ */
+ best_worker = worker;
+ continue;
+ }
+ if (worker->num_associations > best_worker->num_associations) {
+ /*
+ * It's not better
+ */
+ continue;
+ }
+ /*
+ * Ok, with the same number of association groups
+ * we pick the one with the lowest number of connections
+ */
+ if (worker->num_connections < best_worker->num_connections) {
+ best_worker = worker;
+ continue;
+ }
+ }
+
+ if (perfect_worker != NULL) {
+ return perfect_worker;
+ }
+
+ if (empty_slot < SIZE_MAX) {
+ int ret = rpc_host_exec_worker(server, empty_slot);
+ if (ret != 0) {
+ DBG_WARNING("Could not fork worker: %s\n",
+ strerror(ret));
+ }
+ return NULL;
+ }
+
+ if (best_worker != NULL) {
+ return best_worker;
+ }
+
+ return NULL;
+}
+
+/*
+ * Find an rpcd_* worker for an internal connection, possibly go beyond
+ * server->max_workers
+ */
+static struct rpc_work_process *rpc_host_find_idle_worker(
+ struct rpc_server *server)
+{
+ struct rpc_work_process *worker = NULL, *tmp = NULL;
+ size_t i, num_workers = talloc_array_length(server->workers);
+ size_t empty_slot = SIZE_MAX;
+ int ret;
+
+ for (i=server->max_workers; i<num_workers; i++) {
+ worker = &server->workers[i];
+
+ if (worker->pid == -1) {
+ empty_slot = MIN(empty_slot, i);
+ continue;
+ }
+ if (!worker->available) {
+ continue;
+ }
+ if (worker->num_associations == 0) {
+ return &server->workers[i];
+ }
+ }
+
+ if (empty_slot < SIZE_MAX) {
+ ret = rpc_host_exec_worker(server, empty_slot);
+ if (ret != 0) {
+ DBG_WARNING("Could not fork worker: %s\n",
+ strerror(ret));
+ }
+ return NULL;
+ }
+
+ /*
+ * All workers are busy. We need to expand the number of
+ * workers because we were asked for an idle worker.
+ */
+ if (num_workers >= UINT16_MAX) {
+ /*
+ * The worker index would not fit into 16-bits
+ */
+ return NULL;
+ }
+ tmp = talloc_realloc(
+ server,
+ server->workers,
+ struct rpc_work_process,
+ num_workers+1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ server->workers = tmp;
+
+ server->workers[num_workers] = (struct rpc_work_process) { .pid=-1, };
+
+ ret = rpc_host_exec_worker(server, num_workers);
+ if (ret != 0) {
+ DBG_WARNING("Could not exec worker: %s\n", strerror(ret));
+ }
+
+ return NULL;
+}
+
+/*
+ * Find an rpcd_* process to talk to. Start a new one if necessary.
+ */
+static void rpc_host_distribute_clients(struct rpc_server *server)
+{
+ struct rpc_work_process *worker = NULL;
+ struct rpc_host_pending_client *pending_client = NULL;
+ uint32_t assoc_group_id;
+ DATA_BLOB blob;
+ struct iovec iov;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ const char *client_type = NULL;
+
+again:
+ pending_client = server->pending_clients;
+ if (pending_client == NULL) {
+ DBG_DEBUG("No pending clients\n");
+ return;
+ }
+
+ assoc_group_id = pending_client->bind_pkt->u.bind.assoc_group_id;
+
+ if (assoc_group_id != 0) {
+ size_t num_workers = talloc_array_length(server->workers);
+ uint16_t worker_index = assoc_group_id >> 16;
+
+ client_type = "associated";
+
+ if (worker_index >= num_workers) {
+ DBG_DEBUG("Invalid assoc group id %"PRIu32"\n",
+ assoc_group_id);
+ goto done;
+ }
+ worker = &server->workers[worker_index];
+
+ if ((worker->pid == -1) || !worker->available) {
+ DBG_DEBUG("Requested worker index %"PRIu16": "
+ "pid=%d, available=%d\n",
+ worker_index,
+ (int)worker->pid,
+ (int)worker->available);
+ /*
+ * Pick a random one for a proper bind nack
+ */
+ client_type = "associated+lost";
+ worker = rpc_host_find_worker(server);
+ }
+ } else {
+ struct auth_session_info_transport *session_info =
+ pending_client->client->npa_info8->session_info;
+ uint32_t flags = 0;
+ bool found;
+
+ client_type = "new";
+
+ found = security_token_find_npa_flags(
+ session_info->session_info->security_token,
+ &flags);
+
+ /* fresh assoc group requested */
+ if (found & (flags & SAMBA_NPA_FLAGS_NEED_IDLE)) {
+ client_type = "new+exclusive";
+ worker = rpc_host_find_idle_worker(server);
+ } else {
+ client_type = "new";
+ worker = rpc_host_find_worker(server);
+ }
+ }
+
+ if (worker == NULL) {
+ DBG_DEBUG("No worker found for %s client\n", client_type);
+ return;
+ }
+
+ DLIST_REMOVE(server->pending_clients, pending_client);
+
+ ndr_err = ndr_push_struct_blob(
+ &blob,
+ pending_client,
+ pending_client->client,
+ (ndr_push_flags_fn_t)ndr_push_rpc_host_client);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_rpc_host_client failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ DBG_INFO("Sending %s client %s to %d with "
+ "%"PRIu32" associations and %"PRIu32" connections\n",
+ client_type,
+ server->rpc_server_exe,
+ worker->pid,
+ worker->num_associations,
+ worker->num_connections);
+
+ iov = (struct iovec) {
+ .iov_base = blob.data, .iov_len = blob.length,
+ };
+
+ status = messaging_send_iov(
+ server->host->msg_ctx,
+ pid_to_procid(worker->pid),
+ MSG_RPC_HOST_NEW_CLIENT,
+ &iov,
+ 1,
+ &pending_client->sock,
+ 1);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DBG_DEBUG("worker %d died, sigchld not yet received?\n",
+ worker->pid);
+ DLIST_ADD(server->pending_clients, pending_client);
+ worker->available = false;
+ goto again;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+ if (assoc_group_id == 0) {
+ worker->num_associations += 1;
+ }
+ worker->num_connections += 1;
+ TALLOC_FREE(worker->exit_timer);
+
+ TALLOC_FREE(server->host->np_helper_shutdown);
+
+done:
+ TALLOC_FREE(pending_client);
+}
+
+static int rpc_host_pending_client_destructor(
+ struct rpc_host_pending_client *p)
+{
+ TALLOC_FREE(p->hangup_wait);
+ if (p->sock != -1) {
+ close(p->sock);
+ p->sock = -1;
+ }
+ DLIST_REMOVE(p->server->pending_clients, p);
+ return 0;
+}
+
+/*
+ * Exception condition handler before rpcd_* worker
+ * is handling the socket. Either the client exited or
+ * sent unexpected data after the initial bind.
+ */
+static void rpc_host_client_exited(struct tevent_req *subreq)
+{
+ struct rpc_host_pending_client *pending = tevent_req_callback_data(
+ subreq, struct rpc_host_pending_client);
+ bool ok;
+ int err;
+
+ ok = wait_for_read_recv(subreq, &err);
+
+ TALLOC_FREE(subreq);
+ pending->hangup_wait = NULL;
+
+ if (ok) {
+ DBG_DEBUG("client on sock %d sent data\n", pending->sock);
+ } else {
+ DBG_DEBUG("client exited with %s\n", strerror(err));
+ }
+ TALLOC_FREE(pending);
+}
+
+struct rpc_iface_binding_map {
+ struct ndr_syntax_id iface;
+ char *bindings;
+};
+
+static bool rpc_iface_binding_map_add_endpoint(
+ TALLOC_CTX *mem_ctx,
+ const struct rpc_host_endpoint *ep,
+ struct rpc_host_iface_name *iface_names,
+ struct rpc_iface_binding_map **pmaps)
+{
+ const struct ndr_syntax_id mgmt_iface = {
+ {0xafa8bd80,
+ 0x7d8a,
+ 0x11c9,
+ {0xbe,0xf4},
+ {0x08,0x00,0x2b,0x10,0x29,0x89}
+ },
+ 1.0};
+
+ struct rpc_iface_binding_map *maps = *pmaps;
+ size_t i, num_ifaces = talloc_array_length(ep->interfaces);
+ char *binding_string = NULL;
+ bool ok = false;
+
+ binding_string = dcerpc_binding_string(mem_ctx, ep->binding);
+ if (binding_string == NULL) {
+ return false;
+ }
+
+ for (i=0; i<num_ifaces; i++) {
+ const struct ndr_syntax_id *iface = &ep->interfaces[i];
+ size_t j, num_maps = talloc_array_length(maps);
+ struct rpc_iface_binding_map *map = NULL;
+ char *p = NULL;
+
+ if (ndr_syntax_id_equal(iface, &mgmt_iface)) {
+ /*
+ * mgmt is offered everywhere, don't put it
+ * into epmdb.tdb.
+ */
+ continue;
+ }
+
+ for (j=0; j<num_maps; j++) {
+ map = &maps[j];
+ if (ndr_syntax_id_equal(&map->iface, iface)) {
+ break;
+ }
+ }
+
+ if (j == num_maps) {
+ struct rpc_iface_binding_map *tmp = NULL;
+ struct rpc_host_iface_name *iface_name = NULL;
+
+ iface_name = rpc_host_iface_names_find(
+ iface_names, iface);
+ if (iface_name == NULL) {
+ goto fail;
+ }
+
+ tmp = talloc_realloc(
+ mem_ctx,
+ maps,
+ struct rpc_iface_binding_map,
+ num_maps+1);
+ if (tmp == NULL) {
+ goto fail;
+ }
+ maps = tmp;
+
+ map = &maps[num_maps];
+ *map = (struct rpc_iface_binding_map) {
+ .iface = *iface,
+ .bindings = talloc_move(
+ maps, &iface_name->name),
+ };
+ }
+
+ p = strv_find(map->bindings, binding_string);
+ if (p == NULL) {
+ int ret = strv_add(
+ maps, &map->bindings, binding_string);
+ if (ret != 0) {
+ goto fail;
+ }
+ }
+ }
+
+ ok = true;
+fail:
+ *pmaps = maps;
+ return ok;
+}
+
+static bool rpc_iface_binding_map_add_endpoints(
+ TALLOC_CTX *mem_ctx,
+ struct rpc_host_endpoint **endpoints,
+ struct rpc_host_iface_name *iface_names,
+ struct rpc_iface_binding_map **pbinding_maps)
+{
+ size_t i, num_endpoints = talloc_array_length(endpoints);
+
+ for (i=0; i<num_endpoints; i++) {
+ bool ok = rpc_iface_binding_map_add_endpoint(
+ mem_ctx, endpoints[i], iface_names, pbinding_maps);
+ if (!ok) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool rpc_host_fill_epm_db(
+ struct tdb_wrap *db,
+ struct rpc_host_endpoint **endpoints,
+ struct rpc_host_iface_name *iface_names)
+{
+ struct rpc_iface_binding_map *maps = NULL;
+ size_t i, num_maps;
+ bool ret = false;
+ bool ok;
+
+ ok = rpc_iface_binding_map_add_endpoints(
+ talloc_tos(), endpoints, iface_names, &maps);
+ if (!ok) {
+ goto fail;
+ }
+
+ num_maps = talloc_array_length(maps);
+
+ for (i=0; i<num_maps; i++) {
+ struct rpc_iface_binding_map *map = &maps[i];
+ struct ndr_syntax_id_buf buf;
+ char *keystr = ndr_syntax_id_buf_string(&map->iface, &buf);
+ TDB_DATA value = {
+ .dptr = (uint8_t *)map->bindings,
+ .dsize = talloc_array_length(map->bindings),
+ };
+ int rc;
+
+ rc = tdb_store(
+ db->tdb, string_term_tdb_data(keystr), value, 0);
+ if (rc == -1) {
+ DBG_DEBUG("tdb_store() failed: %s\n",
+ tdb_errorstr(db->tdb));
+ goto fail;
+ }
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(maps);
+ return ret;
+}
+
+struct rpc_server_setup_state {
+ struct rpc_server *server;
+};
+
+static void rpc_server_setup_got_endpoints(struct tevent_req *subreq);
+
+/*
+ * Async initialize state for all possible rpcd_* servers.
+ * Note this does not start them.
+ */
+static struct tevent_req *rpc_server_setup_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_host *host,
+ const char *rpc_server_exe)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct rpc_server_setup_state *state = NULL;
+ struct rpc_server *server = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct rpc_server_setup_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->server = talloc_zero(state, struct rpc_server);
+ if (tevent_req_nomem(state->server, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ server = state->server;
+
+ *server = (struct rpc_server) {
+ .host = host,
+ .server_index = UINT32_MAX,
+ .rpc_server_exe = talloc_strdup(server, rpc_server_exe),
+ };
+ if (tevent_req_nomem(server->rpc_server_exe, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = rpc_server_get_endpoints_send(
+ state,
+ ev,
+ rpc_server_exe,
+ host->np_helper ? NCACN_NP : NCA_UNKNOWN);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_server_setup_got_endpoints, req);
+ return req;
+}
+
+static void rpc_server_setup_got_endpoints(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_server_setup_state *state = tevent_req_data(
+ req, struct rpc_server_setup_state);
+ struct rpc_server *server = state->server;
+ int ret;
+ size_t i, num_endpoints;
+ bool ok;
+
+ ret = rpc_server_get_endpoints_recv(
+ subreq,
+ server,
+ &server->endpoints,
+ &server->iface_names,
+ &server->max_workers,
+ &server->idle_seconds);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+
+ server->workers = talloc_array(
+ server, struct rpc_work_process, server->max_workers);
+ if (tevent_req_nomem(server->workers, req)) {
+ return;
+ }
+
+ for (i=0; i<server->max_workers; i++) {
+ /* mark as not yet created */
+ server->workers[i] = (struct rpc_work_process) { .pid=-1, };
+ }
+
+ num_endpoints = talloc_array_length(server->endpoints);
+
+ for (i=0; i<num_endpoints; i++) {
+ struct rpc_host_endpoint *e = server->endpoints[i];
+ NTSTATUS status;
+ size_t j;
+
+ e->server = server;
+
+ status = dcesrv_create_binding_sockets(
+ e->binding, e, &e->num_fds, &e->fds);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ continue;
+ }
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("dcesrv_create_binding_sockets failed: %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ for (j=0; j<e->num_fds; j++) {
+ ret = listen(e->fds[j], 256);
+ if (ret == -1) {
+ tevent_req_nterror(
+ req, map_nt_error_from_unix(errno));
+ return;
+ }
+ }
+ }
+
+ ok = rpc_host_fill_epm_db(
+ server->host->epmdb, server->endpoints, server->iface_names);
+ if (!ok) {
+ DBG_DEBUG("rpc_host_fill_epm_db failed\n");
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS rpc_server_setup_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, struct rpc_server **server)
+{
+ struct rpc_server_setup_state *state = tevent_req_data(
+ req, struct rpc_server_setup_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *server = talloc_move(mem_ctx, &state->server);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/*
+ * rpcd_* died. Called from SIGCHLD handler.
+ */
+static void rpc_worker_exited(struct rpc_host *host, pid_t pid)
+{
+ size_t i, num_servers = talloc_array_length(host->servers);
+ struct rpc_work_process *worker = NULL;
+ bool found_pid = false;
+ bool have_active_worker = false;
+
+ for (i=0; i<num_servers; i++) {
+ struct rpc_server *server = host->servers[i];
+ size_t j, num_workers;
+
+ if (server == NULL) {
+ /* SIGCHLD for --list-interfaces run */
+ continue;
+ }
+
+ num_workers = talloc_array_length(server->workers);
+
+ for (j=0; j<num_workers; j++) {
+ worker = &server->workers[j];
+ if (worker->pid == pid) {
+ found_pid = true;
+ worker->pid = -1;
+ worker->available = false;
+ }
+
+ if (worker->pid != -1) {
+ have_active_worker = true;
+ }
+ }
+ }
+
+ if (!found_pid) {
+ DBG_WARNING("No worker with PID %d\n", (int)pid);
+ return;
+ }
+
+ if (!have_active_worker && host->np_helper) {
+ /*
+ * We have nothing left to do as an np_helper.
+ * Terminate ourselves (samba-dcerpcd). We will
+ * be restarted on demand anyway.
+ */
+ DBG_DEBUG("Exiting idle np helper\n");
+ exit(0);
+ }
+}
+
+/*
+ * rpcd_* died.
+ */
+static void rpc_host_sigchld(
+ struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct rpc_host *state = talloc_get_type_abort(
+ private_data, struct rpc_host);
+ pid_t pid;
+ int wstatus;
+
+ while ((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) {
+ DBG_DEBUG("pid=%d, wstatus=%d\n", (int)pid, wstatus);
+ rpc_worker_exited(state, pid);
+ }
+}
+
+/*
+ * Idle timer fired for a rcpd_* worker. Ask it to terminate.
+ */
+static void rpc_host_exit_worker(
+ struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct rpc_server *server = talloc_get_type_abort(
+ private_data, struct rpc_server);
+ size_t i, num_workers = talloc_array_length(server->workers);
+
+ /*
+ * Scan for the right worker. We don't have too many of those,
+ * and maintaining an index would be more data structure effort.
+ */
+
+ for (i=0; i<num_workers; i++) {
+ struct rpc_work_process *w = &server->workers[i];
+ NTSTATUS status;
+
+ if (w->exit_timer != te) {
+ continue;
+ }
+ w->exit_timer = NULL;
+
+ SMB_ASSERT(w->num_associations == 0);
+
+ status = messaging_send(
+ server->host->msg_ctx,
+ pid_to_procid(w->pid),
+ MSG_SHUTDOWN,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not send SHUTDOWN msg: %s\n",
+ nt_errstr(status));
+ }
+
+ w->available = false;
+ break;
+ }
+}
+
+/*
+ * rcpd_* worker replied with its status.
+ */
+static void rpc_host_child_status_recv(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct rpc_host *host = talloc_get_type_abort(
+ private_data, struct rpc_host);
+ size_t num_servers = talloc_array_length(host->servers);
+ struct rpc_server *server = NULL;
+ size_t num_workers;
+ pid_t src_pid = procid_to_pid(&server_id);
+ struct rpc_work_process *worker = NULL;
+ struct rpc_worker_status status_message;
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_pull_struct_blob_all_noalloc(
+ data,
+ &status_message,
+ (ndr_pull_flags_fn_t)ndr_pull_rpc_worker_status);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ struct server_id_buf buf;
+ DBG_WARNING("Got invalid message from pid %s\n",
+ server_id_str_buf(server_id, &buf));
+ return;
+ }
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(rpc_worker_status, &status_message);
+ }
+
+ if (status_message.server_index >= num_servers) {
+ DBG_WARNING("Got invalid server_index=%"PRIu32", "
+ "num_servers=%zu\n",
+ status_message.server_index,
+ num_servers);
+ return;
+ }
+
+ server = host->servers[status_message.server_index];
+
+ num_workers = talloc_array_length(server->workers);
+ if (status_message.worker_index >= num_workers) {
+ DBG_WARNING("Got invalid worker_index=%"PRIu32", "
+ "num_workers=%zu\n",
+ status_message.worker_index,
+ num_workers);
+ return;
+ }
+ worker = &server->workers[status_message.worker_index];
+
+ if (src_pid != worker->pid) {
+ DBG_WARNING("Got idx=%"PRIu32" from %d, expected %d\n",
+ status_message.worker_index,
+ (int)src_pid,
+ worker->pid);
+ return;
+ }
+
+ worker->available = true;
+ worker->num_associations = status_message.num_association_groups;
+ worker->num_connections = status_message.num_connections;
+
+ if (worker->num_associations != 0) {
+ TALLOC_FREE(worker->exit_timer);
+ } else {
+ worker->exit_timer = tevent_add_timer(
+ messaging_tevent_context(msg),
+ server->workers,
+ tevent_timeval_current_ofs(server->idle_seconds, 0),
+ rpc_host_exit_worker,
+ server);
+ /* No NULL check, it's not fatal if this does not work */
+ }
+
+ rpc_host_distribute_clients(server);
+}
+
+/*
+ * samba-dcerpcd has been asked to shutdown.
+ * Mark the initial tevent_req as done so we
+ * exit the event loop.
+ */
+static void rpc_host_msg_shutdown(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ tevent_req_done(req);
+}
+
+/*
+ * Only match directory entries starting in rpcd_
+ */
+static int rpcd_filter(const struct dirent *d)
+{
+ int match = fnmatch("rpcd_*", d->d_name, 0);
+ return (match == 0) ? 1 : 0;
+}
+
+/*
+ * Scan the given libexecdir for rpcd_* services
+ * and return them as a strv list.
+ */
+static int rpc_host_list_servers(
+ const char *libexecdir, TALLOC_CTX *mem_ctx, char **pservers)
+{
+ char *servers = NULL;
+ struct dirent **namelist = NULL;
+ int i, num_servers;
+ int ret = ENOMEM;
+
+ num_servers = scandir(libexecdir, &namelist, rpcd_filter, alphasort);
+ if (num_servers == -1) {
+ DBG_DEBUG("scandir failed: %s\n", strerror(errno));
+ return errno;
+ }
+
+ for (i=0; i<num_servers; i++) {
+ char *exe = talloc_asprintf(
+ mem_ctx, "%s/%s", libexecdir, namelist[i]->d_name);
+ if (exe == NULL) {
+ goto fail;
+ }
+
+ ret = strv_add(mem_ctx, &servers, exe);
+ TALLOC_FREE(exe);
+ if (ret != 0) {
+ goto fail;
+ }
+ }
+fail:
+ for (i=0; i<num_servers; i++) {
+ SAFE_FREE(namelist[i]);
+ }
+ SAFE_FREE(namelist);
+
+ if (ret != 0) {
+ TALLOC_FREE(servers);
+ return ret;
+ }
+ *pservers = servers;
+ return 0;
+}
+
+struct rpc_host_endpoint_accept_state {
+ struct tevent_context *ev;
+ struct rpc_host_endpoint *endpoint;
+};
+
+static void rpc_host_endpoint_accept_accepted(struct tevent_req *subreq);
+static void rpc_host_endpoint_accept_got_bind(struct tevent_req *subreq);
+
+/*
+ * Asynchronously wait for a DCERPC connection from a client.
+ */
+static struct tevent_req *rpc_host_endpoint_accept_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_host_endpoint *endpoint)
+{
+ struct tevent_req *req = NULL;
+ struct rpc_host_endpoint_accept_state *state = NULL;
+ size_t i;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct rpc_host_endpoint_accept_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->endpoint = endpoint;
+
+ for (i=0; i<endpoint->num_fds; i++) {
+ struct tevent_req *subreq = NULL;
+
+ subreq = accept_send(state, ev, endpoint->fds[i]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, rpc_host_endpoint_accept_accepted, req);
+ }
+
+ return req;
+}
+
+/*
+ * Accept a DCERPC connection from a client.
+ */
+static void rpc_host_endpoint_accept_accepted(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_host_endpoint_accept_state *state = tevent_req_data(
+ req, struct rpc_host_endpoint_accept_state);
+ struct rpc_host_endpoint *endpoint = state->endpoint;
+ int sock, listen_sock, err;
+ struct samba_sockaddr peer_addr;
+
+ sock = accept_recv(subreq, &listen_sock, &peer_addr, &err);
+ TALLOC_FREE(subreq);
+ if (sock == -1) {
+ /* What to do here? Just ignore the error and retry? */
+ DBG_DEBUG("accept_recv failed: %s\n", strerror(err));
+ tevent_req_error(req, err);
+ return;
+ }
+
+ subreq = accept_send(state, state->ev, listen_sock);
+ if (tevent_req_nomem(subreq, req)) {
+ close(sock);
+ sock = -1;
+ return;
+ }
+ tevent_req_set_callback(
+ subreq, rpc_host_endpoint_accept_accepted, req);
+
+ subreq = rpc_host_bind_read_send(
+ state,
+ state->ev,
+ dcerpc_binding_get_transport(endpoint->binding),
+ &sock,
+ &peer_addr);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(
+ subreq, rpc_host_endpoint_accept_got_bind, req);
+}
+
+/*
+ * Client sent us a DCERPC bind packet.
+ */
+static void rpc_host_endpoint_accept_got_bind(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_host_endpoint_accept_state *state = tevent_req_data(
+ req, struct rpc_host_endpoint_accept_state);
+ struct rpc_host_endpoint *endpoint = state->endpoint;
+ struct rpc_server *server = endpoint->server;
+ struct rpc_host_pending_client *pending = NULL;
+ struct rpc_host_client *client = NULL;
+ struct ncacn_packet *bind_pkt = NULL;
+ int ret;
+ int sock=-1;
+
+ ret = rpc_host_bind_read_recv(
+ subreq, state, &sock, &client, &bind_pkt);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ DBG_DEBUG("rpc_host_bind_read_recv returned %s\n",
+ strerror(ret));
+ goto fail;
+ }
+
+ client->binding = dcerpc_binding_string(client, endpoint->binding);
+ if (client->binding == NULL) {
+ DBG_WARNING("dcerpc_binding_string failed, dropping client\n");
+ goto fail;
+ }
+
+ pending = talloc_zero(server, struct rpc_host_pending_client);
+ if (pending == NULL) {
+ DBG_WARNING("talloc failed, dropping client\n");
+ goto fail;
+ }
+ pending->server = server;
+ pending->sock = sock;
+ pending->bind_pkt = talloc_move(pending, &bind_pkt);
+ pending->client = talloc_move(pending, &client);
+ talloc_set_destructor(pending, rpc_host_pending_client_destructor);
+ sock = -1;
+
+ pending->hangup_wait = wait_for_read_send(
+ pending, state->ev, pending->sock, true);
+ if (pending->hangup_wait == NULL) {
+ DBG_WARNING("wait_for_read_send failed, dropping client\n");
+ TALLOC_FREE(pending);
+ return;
+ }
+ tevent_req_set_callback(
+ pending->hangup_wait, rpc_host_client_exited, pending);
+
+ DLIST_ADD_END(server->pending_clients, pending);
+ rpc_host_distribute_clients(server);
+ return;
+
+fail:
+ TALLOC_FREE(client);
+ if (sock != -1) {
+ close(sock);
+ }
+}
+
+static int rpc_host_endpoint_accept_recv(
+ struct tevent_req *req, struct rpc_host_endpoint **ep)
+{
+ struct rpc_host_endpoint_accept_state *state = tevent_req_data(
+ req, struct rpc_host_endpoint_accept_state);
+
+ *ep = state->endpoint;
+
+ return tevent_req_simple_recv_unix(req);
+}
+
+/*
+ * Full state for samba-dcerpcd. Everything else
+ * is hung off this.
+ */
+struct rpc_host_state {
+ struct tevent_context *ev;
+ struct rpc_host *host;
+
+ bool is_ready;
+ const char *daemon_ready_progname;
+ struct tevent_immediate *ready_signal_immediate;
+ int *ready_signal_fds;
+
+ size_t num_servers;
+ size_t num_prepared;
+};
+
+/*
+ * Tell whoever invoked samba-dcerpcd we're ready to
+ * serve.
+ */
+static void rpc_host_report_readiness(
+ struct tevent_context *ev,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct rpc_host_state *state = talloc_get_type_abort(
+ private_data, struct rpc_host_state);
+ size_t i, num_fds = talloc_array_length(state->ready_signal_fds);
+
+ if (!state->is_ready) {
+ DBG_DEBUG("Not yet ready\n");
+ return;
+ }
+
+ for (i=0; i<num_fds; i++) {
+ uint8_t byte = 0;
+ ssize_t nwritten;
+
+ do {
+ nwritten = write(
+ state->ready_signal_fds[i],
+ (void *)&byte,
+ sizeof(byte));
+ } while ((nwritten == -1) && (errno == EINTR));
+
+ close(state->ready_signal_fds[i]);
+ }
+
+ TALLOC_FREE(state->ready_signal_fds);
+}
+
+/*
+ * Respond to a "are you ready" message.
+ */
+static bool rpc_host_ready_signal_filter(
+ struct messaging_rec *rec, void *private_data)
+{
+ struct rpc_host_state *state = talloc_get_type_abort(
+ private_data, struct rpc_host_state);
+ size_t num_fds = talloc_array_length(state->ready_signal_fds);
+ int *tmp = NULL;
+
+ if (rec->msg_type != MSG_DAEMON_READY_FD) {
+ return false;
+ }
+ if (rec->num_fds != 1) {
+ DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds);
+ return false;
+ }
+
+ if (num_fds + 1 < num_fds) {
+ return false;
+ }
+ tmp = talloc_realloc(state, state->ready_signal_fds, int, num_fds+1);
+ if (tmp == NULL) {
+ return false;
+ }
+ state->ready_signal_fds = tmp;
+
+ state->ready_signal_fds[num_fds] = rec->fds[0];
+ rec->fds[0] = -1;
+
+ tevent_schedule_immediate(
+ state->ready_signal_immediate,
+ state->ev,
+ rpc_host_report_readiness,
+ state);
+
+ return false;
+}
+
+/*
+ * Respond to a "what is your status" message.
+ */
+static bool rpc_host_dump_status_filter(
+ struct messaging_rec *rec, void *private_data)
+{
+ struct rpc_host_state *state = talloc_get_type_abort(
+ private_data, struct rpc_host_state);
+ struct rpc_host *host = state->host;
+ struct rpc_server **servers = host->servers;
+ size_t i, num_servers = talloc_array_length(servers);
+ FILE *f = NULL;
+ int fd;
+
+ if (rec->msg_type != MSG_RPC_DUMP_STATUS) {
+ return false;
+ }
+ if (rec->num_fds != 1) {
+ DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds);
+ return false;
+ }
+
+ fd = dup(rec->fds[0]);
+ if (fd == -1) {
+ DBG_DEBUG("dup(%"PRIi64") failed: %s\n",
+ rec->fds[0],
+ strerror(errno));
+ return false;
+ }
+
+ f = fdopen(fd, "w");
+ if (f == NULL) {
+ DBG_DEBUG("fdopen failed: %s\n", strerror(errno));
+ close(fd);
+ return false;
+ }
+
+ for (i=0; i<num_servers; i++) {
+ struct rpc_server *server = servers[i];
+ size_t j, num_workers = talloc_array_length(server->workers);
+ size_t active_workers = 0;
+
+ for (j=0; j<num_workers; j++) {
+ if (server->workers[j].pid != -1) {
+ active_workers += 1;
+ }
+ }
+
+ fprintf(f,
+ "%s: active_workers=%zu\n",
+ server->rpc_server_exe,
+ active_workers);
+
+ for (j=0; j<num_workers; j++) {
+ struct rpc_work_process *w = &server->workers[j];
+
+ if (w->pid == (pid_t)-1) {
+ continue;
+ }
+
+ fprintf(f,
+ " worker[%zu]: pid=%d, num_associations=%"PRIu32", num_connections=%"PRIu32"\n",
+ j,
+ (int)w->pid,
+ w->num_associations,
+ w->num_connections);
+ }
+ }
+
+ fclose(f);
+
+ return false;
+}
+
+static void rpc_host_server_setup_done(struct tevent_req *subreq);
+static void rpc_host_endpoint_failed(struct tevent_req *subreq);
+
+/*
+ * Async startup for samba-dcerpcd.
+ */
+static struct tevent_req *rpc_host_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ char *servers,
+ int ready_signal_fd,
+ const char *daemon_ready_progname,
+ bool is_np_helper)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct rpc_host_state *state = NULL;
+ struct rpc_host *host = NULL;
+ struct tevent_signal *se = NULL;
+ char *epmdb_path = NULL;
+ char *exe = NULL;
+ size_t i, num_servers = strv_count(servers);
+ NTSTATUS status;
+ int ret;
+
+ req = tevent_req_create(req, &state, struct rpc_host_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->daemon_ready_progname = daemon_ready_progname;
+
+ state->ready_signal_immediate = tevent_create_immediate(state);
+ if (tevent_req_nomem(state->ready_signal_immediate, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (ready_signal_fd != -1) {
+ state->ready_signal_fds = talloc_array(state, int, 1);
+ if (tevent_req_nomem(state->ready_signal_fds, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->ready_signal_fds[0] = ready_signal_fd;
+ }
+
+ state->host = talloc_zero(state, struct rpc_host);
+ if (tevent_req_nomem(state->host, req)) {
+ return tevent_req_post(req, ev);
+ }
+ host = state->host;
+
+ host->msg_ctx = msg_ctx;
+ host->np_helper = is_np_helper;
+
+ ret = pipe(host->worker_stdin);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return tevent_req_post(req, ev);
+ }
+
+ host->servers = talloc_zero_array(
+ host, struct rpc_server *, num_servers);
+ if (tevent_req_nomem(host->servers, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ se = tevent_add_signal(ev, state, SIGCHLD, 0, rpc_host_sigchld, host);
+ if (tevent_req_nomem(se, req)) {
+ return tevent_req_post(req, ev);
+ }
+ BlockSignals(false, SIGCHLD);
+
+ status = messaging_register(
+ msg_ctx,
+ host,
+ MSG_RPC_WORKER_STATUS,
+ rpc_host_child_status_recv);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_register(
+ msg_ctx, req, MSG_SHUTDOWN, rpc_host_msg_shutdown);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = messaging_filtered_read_send(
+ state, ev, msg_ctx, rpc_host_ready_signal_filter, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = messaging_filtered_read_send(
+ state, ev, msg_ctx, rpc_host_dump_status_filter, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ epmdb_path = lock_path(state, "epmdb.tdb");
+ if (tevent_req_nomem(epmdb_path, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ host->epmdb = tdb_wrap_open(
+ host,
+ epmdb_path,
+ 0,
+ TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
+ O_RDWR|O_CREAT,
+ 0644);
+ if (host->epmdb == NULL) {
+ DBG_DEBUG("tdb_wrap_open(%s) failed: %s\n",
+ epmdb_path,
+ strerror(errno));
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ return tevent_req_post(req, ev);
+ }
+ TALLOC_FREE(epmdb_path);
+
+ for (exe = strv_next(servers, exe), i = 0;
+ exe != NULL;
+ exe = strv_next(servers, exe), i++) {
+
+ DBG_DEBUG("server_setup for %s index %zu\n", exe, i);
+
+ subreq = rpc_server_setup_send(
+ state,
+ ev,
+ host,
+ exe);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, rpc_host_server_setup_done, req);
+ }
+
+ return req;
+}
+
+/*
+ * Timer function called after we were initialized but no one
+ * connected. Shutdown.
+ */
+static void rpc_host_shutdown(
+ struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ DBG_DEBUG("Nobody connected -- shutting down\n");
+ tevent_req_done(req);
+}
+
+static void rpc_host_server_setup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_host_state *state = tevent_req_data(
+ req, struct rpc_host_state);
+ struct rpc_server *server = NULL;
+ struct rpc_host *host = state->host;
+ size_t i, num_servers = talloc_array_length(host->servers);
+ NTSTATUS status;
+
+ status = rpc_server_setup_recv(subreq, host, &server);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_server_setup_recv returned %s, ignoring\n",
+ nt_errstr(status));
+ host->servers = talloc_realloc(
+ host,
+ host->servers,
+ struct rpc_server *,
+ num_servers-1);
+ return;
+ }
+
+ server->server_index = state->num_prepared;
+ host->servers[state->num_prepared] = server;
+
+ state->num_prepared += 1;
+
+ if (state->num_prepared < num_servers) {
+ return;
+ }
+
+ for (i=0; i<num_servers; i++) {
+ size_t j, num_endpoints;
+
+ server = host->servers[i];
+ num_endpoints = talloc_array_length(server->endpoints);
+
+ for (j=0; j<num_endpoints; j++) {
+ subreq = rpc_host_endpoint_accept_send(
+ state, state->ev, server->endpoints[j]);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(
+ subreq, rpc_host_endpoint_failed, req);
+ }
+ }
+
+ state->is_ready = true;
+
+ if (state->daemon_ready_progname != NULL) {
+ daemon_ready(state->daemon_ready_progname);
+ }
+
+ if (host->np_helper) {
+ /*
+ * If we're started as an np helper, and no one talks to
+ * us within 10 seconds, just shut ourselves down.
+ */
+ host->np_helper_shutdown = tevent_add_timer(
+ state->ev,
+ state,
+ timeval_current_ofs(10, 0),
+ rpc_host_shutdown,
+ req);
+ if (tevent_req_nomem(host->np_helper_shutdown, req)) {
+ return;
+ }
+ }
+
+ tevent_schedule_immediate(
+ state->ready_signal_immediate,
+ state->ev,
+ rpc_host_report_readiness,
+ state);
+}
+
+/*
+ * Log accept fail on an endpoint.
+ */
+static void rpc_host_endpoint_failed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_host_state *state = tevent_req_data(
+ req, struct rpc_host_state);
+ struct rpc_host_endpoint *endpoint = NULL;
+ char *binding_string = NULL;
+ int ret;
+
+ ret = rpc_host_endpoint_accept_recv(subreq, &endpoint);
+ TALLOC_FREE(subreq);
+
+ binding_string = dcerpc_binding_string(state, endpoint->binding);
+ DBG_DEBUG("rpc_host_endpoint_accept_recv for %s returned %s\n",
+ binding_string,
+ strerror(ret));
+ TALLOC_FREE(binding_string);
+}
+
+static NTSTATUS rpc_host_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static int rpc_host_pidfile_create(
+ struct messaging_context *msg_ctx,
+ const char *progname,
+ int ready_signal_fd)
+{
+ const char *piddir = lp_pid_directory();
+ size_t len = strlen(piddir) + strlen(progname) + 6;
+ char pidFile[len];
+ pid_t existing_pid;
+ int fd, ret;
+
+ snprintf(pidFile,
+ sizeof(pidFile),
+ "%s/%s.pid",
+ piddir, progname);
+
+ ret = pidfile_path_create(pidFile, &fd, &existing_pid);
+ if (ret == 0) {
+ /* leak fd */
+ return 0;
+ }
+
+ if (ret != EAGAIN) {
+ DBG_DEBUG("pidfile_path_create() failed: %s\n",
+ strerror(ret));
+ return ret;
+ }
+
+ DBG_DEBUG("%s pid %d exists\n", progname, (int)existing_pid);
+
+ if (ready_signal_fd != -1) {
+ NTSTATUS status = messaging_send_iov(
+ msg_ctx,
+ pid_to_procid(existing_pid),
+ MSG_DAEMON_READY_FD,
+ NULL,
+ 0,
+ &ready_signal_fd,
+ 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not send ready_signal_fd: %s\n",
+ nt_errstr(status));
+ }
+ }
+
+ return EAGAIN;
+}
+
+static void samba_dcerpcd_stdin_handler(
+ struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ char c;
+
+ if (read(0, &c, 1) != 1) {
+ /* we have reached EOF on stdin, which means the
+ parent has exited. Shutdown the server */
+ tevent_req_done(req);
+ }
+}
+
+/*
+ * samba-dcerpcd microservice startup !
+ */
+int main(int argc, const char *argv[])
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *progname = getprogname();
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev_ctx = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ struct tevent_req *req = NULL;
+ char *servers = NULL;
+ const char *arg = NULL;
+ size_t num_servers;
+ poptContext pc;
+ int ret, err;
+ NTSTATUS status;
+ bool log_stdout;
+ bool ok;
+
+ int libexec_rpcds = 0;
+ int np_helper = 0;
+ int ready_signal_fd = -1;
+
+ struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "libexec-rpcds",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &libexec_rpcds,
+ .descrip = "Use all rpcds in libexec",
+ },
+ {
+ .longName = "ready-signal-fd",
+ .argInfo = POPT_ARG_INT,
+ .arg = &ready_signal_fd,
+ .descrip = "fd to close when initialized",
+ },
+ {
+ .longName = "np-helper",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &np_helper,
+ .descrip = "Internal named pipe server",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DAEMON
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ {
+ const char *fd_params[] = { "ready-signal-fd", };
+
+ closefrom_except_fd_params(
+ 3, ARRAY_SIZE(fd_params), fd_params, argc, argv);
+ }
+
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+ umask(0);
+ sec_init();
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(ENOMEM);
+ }
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(
+ pc, "[OPTIONS] [SERVICE_1 SERVICE_2 .. SERVICE_N]");
+
+ ret = poptGetNextOpt(pc);
+
+ if (ret != -1) {
+ if (ret >= 0) {
+ fprintf(stderr,
+ "\nGot unexpected option %d\n",
+ ret);
+ } else if (ret == POPT_ERROR_BADOPT) {
+ fprintf(stderr,
+ "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0),
+ poptStrerror(ret));
+ } else {
+ fprintf(stderr,
+ "\npoptGetNextOpt returned %s\n",
+ poptStrerror(ret));
+ }
+
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((arg = poptGetArg(pc)) != NULL) {
+ ret = strv_add(frame, &servers, arg);
+ if (ret != 0) {
+ DBG_ERR("strv_add() failed\n");
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ }
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+ if (log_stdout) {
+ setup_logging(progname, DEBUG_STDOUT);
+ } else {
+ setup_logging(progname, DEBUG_FILE);
+ }
+
+ /*
+ * If "rpc start on demand helpers = true" in smb.conf we must
+ * not start as standalone, only on demand from
+ * local_np_connect() functions. Log an error message telling
+ * the admin how to fix and then exit.
+ */
+ if (lp_rpc_start_on_demand_helpers() && np_helper == 0) {
+ DBG_ERR("Cannot start in standalone mode if smb.conf "
+ "[global] setting "
+ "\"rpc start on demand helpers = true\" - "
+ "exiting\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ if (libexec_rpcds != 0) {
+ ret = rpc_host_list_servers(
+ dyn_SAMBA_LIBEXECDIR, frame, &servers);
+ if (ret != 0) {
+ DBG_ERR("Could not list libexec: %s\n",
+ strerror(ret));
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ }
+
+ num_servers = strv_count(servers);
+ if (num_servers == 0) {
+ poptPrintUsage(pc, stderr, 0);
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptFreeContext(pc);
+
+ cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
+
+ if (log_stdout && cmdline_daemon_cfg->fork) {
+ DBG_ERR("Can't log to stdout unless in foreground\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ DBG_ERR("messaging_init() failed\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ ev_ctx = messaging_tevent_context(msg_ctx);
+
+ if (cmdline_daemon_cfg->fork) {
+ become_daemon(
+ true,
+ cmdline_daemon_cfg->no_process_group,
+ log_stdout);
+
+ status = reinit_after_fork(msg_ctx, ev_ctx, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("reinit_after_fork() failed",
+ map_errno_from_nt_status(status));
+ }
+ } else {
+ DBG_DEBUG("Calling daemon_status\n");
+ daemon_status(progname, "Starting process ... ");
+ }
+
+ BlockSignals(true, SIGPIPE);
+
+ dump_core_setup(progname, lp_logfile(frame, lp_sub));
+
+ reopen_logs();
+
+ DBG_STARTUP_NOTICE("%s version %s started.\n%s\n",
+ progname,
+ samba_version_string(),
+ samba_copyright_string());
+
+ (void)winbind_off();
+ ok = init_guest_session_info(frame);
+ (void)winbind_on();
+ if (!ok) {
+ DBG_ERR("init_guest_session_info failed\n");
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ ret = rpc_host_pidfile_create(msg_ctx, progname, ready_signal_fd);
+ if (ret != 0) {
+ DBG_DEBUG("rpc_host_pidfile_create failed: %s\n",
+ strerror(ret));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ req = rpc_host_send(
+ ev_ctx,
+ ev_ctx,
+ msg_ctx,
+ servers,
+ ready_signal_fd,
+ cmdline_daemon_cfg->fork ? NULL : progname,
+ np_helper != 0);
+ if (req == NULL) {
+ DBG_ERR("rpc_host_send failed\n");
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ if (!cmdline_daemon_cfg->fork) {
+ struct stat st;
+ if (fstat(0, &st) != 0) {
+ DBG_DEBUG("fstat(0) failed: %s\n",
+ strerror(errno));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) {
+ tevent_add_fd(
+ ev_ctx,
+ ev_ctx,
+ 0,
+ TEVENT_FD_READ,
+ samba_dcerpcd_stdin_handler,
+ req);
+ }
+ }
+
+ ok = tevent_req_poll_unix(req, ev_ctx, &err);
+ if (!ok) {
+ DBG_ERR("tevent_req_poll_unix failed: %s\n",
+ strerror(err));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ status = rpc_host_recv(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("rpc_host_recv returned %s\n", nt_errstr(status));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ TALLOC_FREE(frame);
+
+ return 0;
+}
diff --git a/source3/rpc_server/rpc_ncacn_np.c b/source3/rpc_server/rpc_ncacn_np.c
new file mode 100644
index 0000000..03618df
--- /dev/null
+++ b/source3/rpc_server/rpc_ncacn_np.c
@@ -0,0 +1,217 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1998,
+ * Largely re-written : 2005
+ * Copyright (C) Jeremy Allison 1998 - 2005
+ * Copyright (C) Simo Sorce 2010
+ * Copyright (C) Andrew Bartlett 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_dce.h"
+#include "../libcli/named_pipe_auth/npa_tstream.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "librpc/gen_ndr/auth.h"
+#include "../auth/auth_sam_reply.h"
+#include "../auth/auth_util.h"
+#include "auth.h"
+#include "rpc_server/rpc_pipes.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/rpc_config.h"
+#include "librpc/ndr/ndr_table.h"
+#include "rpc_server/rpc_server.h"
+#include "librpc/rpc/dcerpc_util.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+struct np_proxy_state {
+ uint16_t file_type;
+ uint16_t device_state;
+ uint64_t allocation_size;
+ struct tstream_context *npipe;
+ struct tevent_queue *read_queue;
+ struct tevent_queue *write_queue;
+};
+
+struct npa_state *npa_state_init(TALLOC_CTX *mem_ctx)
+{
+ struct npa_state *npa;
+
+ npa = talloc_zero(mem_ctx, struct npa_state);
+ if (npa == NULL) {
+ return NULL;
+ }
+
+ npa->read_queue = tevent_queue_create(npa, "npa_cli_read");
+ if (npa->read_queue == NULL) {
+ DEBUG(0, ("tevent_queue_create failed\n"));
+ goto fail;
+ }
+
+ npa->write_queue = tevent_queue_create(npa, "npa_cli_write");
+ if (npa->write_queue == NULL) {
+ DEBUG(0, ("tevent_queue_create failed\n"));
+ goto fail;
+ }
+
+ return npa;
+fail:
+ talloc_free(npa);
+ return NULL;
+}
+
+/**
+ * @brief Create a new DCERPC Binding Handle which uses a local dispatch function.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] ndr_table Normally the ndr_table_<name>.
+ *
+ * @param[in] remote_address The info about the connected client.
+ *
+ * @param[in] serversupplied_info The server supplied authentication function.
+ *
+ * @param[in] msg_ctx The messaging context that can be used by the server
+ *
+ * @param[out] binding_handle A pointer to store the connected
+ * dcerpc_binding_handle
+ *
+ * @return NT_STATUS_OK on success, a corresponding NT status if an
+ * error occurred.
+ *
+ * @code
+ * struct dcerpc_binding_handle *winreg_binding;
+ * NTSTATUS status;
+ *
+ * status = rpcint_binding_handle(tmp_ctx,
+ * &ndr_table_winreg,
+ * p->remote_address,
+ * p->session_info,
+ * p->msg_ctx
+ * &winreg_binding);
+ * @endcode
+ */
+NTSTATUS rpcint_binding_handle(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *ndr_table,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **binding_handle)
+{
+ struct rpc_pipe_client *rpccli = NULL;
+ NTSTATUS status;
+
+ status = rpc_pipe_open_local_np(
+ mem_ctx,
+ ndr_table,
+ NULL,
+ remote_address,
+ NULL,
+ local_address,
+ session_info,
+ &rpccli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_open_local_np failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ *binding_handle = rpccli->binding_handle;
+ return NT_STATUS_OK;
+fail:
+ TALLOC_FREE(rpccli);
+ return status;
+}
+
+/**
+ * @brief Create a new RPC client context which uses a local dispatch function
+ * or a remote transport, depending on rpc_server configuration for the
+ * specific service.
+ *
+ * @param[in] mem_ctx The memory context to use.
+ *
+ * @param[in] abstract_syntax Normally the syntax_id of the autogenerated
+ * ndr_table_<name>.
+ *
+ * @param[in] serversupplied_info The server supplied authentication function.
+ *
+ * @param[in] remote_address The client address information.
+ *
+ * @param[in] msg_ctx The messaging context to use.
+ *
+ * @param[out] presult A pointer to store the connected rpc client pipe.
+ *
+ * @return NT_STATUS_OK on success, a corresponding NT status if an
+ * error occurred.
+ *
+ * @code
+ * struct rpc_pipe_client *winreg_pipe;
+ * NTSTATUS status;
+ *
+ * status = rpc_pipe_open_interface(tmp_ctx,
+ * &ndr_table_winreg.syntax_id,
+ * p->session_info,
+ * remote_address,
+ * &winreg_pipe);
+ * @endcode
+ */
+
+NTSTATUS rpc_pipe_open_interface(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ const struct auth_session_info *session_info,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct messaging_context *msg_ctx,
+ struct rpc_pipe_client **cli_pipe)
+{
+ struct rpc_pipe_client *cli = NULL;
+ NTSTATUS status;
+
+ if (cli_pipe != NULL) {
+ if (rpccli_is_connected(*cli_pipe)) {
+ return NT_STATUS_OK;
+ } else {
+ TALLOC_FREE(*cli_pipe);
+ }
+ }
+
+ status = rpc_pipe_open_local_np(
+ mem_ctx,
+ table,
+ NULL,
+ remote_address,
+ NULL,
+ local_address,
+ session_info,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Could not connect to %s pipe: %s\n",
+ table->name,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (NT_STATUS_IS_OK(status) && cli_pipe != NULL) {
+ *cli_pipe = cli;
+ }
+ return status;
+}
diff --git a/source3/rpc_server/rpc_ncacn_np.h b/source3/rpc_server/rpc_ncacn_np.h
new file mode 100644
index 0000000..acbc5f2
--- /dev/null
+++ b/source3/rpc_server/rpc_ncacn_np.h
@@ -0,0 +1,58 @@
+/*
+ Unix SMB/Netbios implementation.
+ RPC Server Headers
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _RPC_NCACN_NP_H_
+#define _RPC_NCACN_NP_H_
+
+struct dcerpc_binding_handle;
+struct ndr_interface_table;
+struct tsocket_address;
+struct dcesrv_context;
+struct dcesrv_endpoint;
+
+struct npa_state {
+ struct tstream_context *stream;
+
+ struct tevent_queue *read_queue;
+ struct tevent_queue *write_queue;
+
+ uint64_t allocation_size;
+ uint16_t device_state;
+ uint16_t file_type;
+
+ void *private_data;
+};
+
+struct npa_state *npa_state_init(TALLOC_CTX *mem_ctx);
+
+NTSTATUS rpcint_binding_handle(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *ndr_table,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **binding_handle);
+NTSTATUS rpc_pipe_open_interface(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ const struct auth_session_info *session_info,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct messaging_context *msg_ctx,
+ struct rpc_pipe_client **cli_pipe);
+#endif /* _RPC_NCACN_NP_H_ */
diff --git a/source3/rpc_server/rpc_pipes.h b/source3/rpc_server/rpc_pipes.h
new file mode 100644
index 0000000..17922b0
--- /dev/null
+++ b/source3/rpc_server/rpc_pipes.h
@@ -0,0 +1,73 @@
+/*
+ Unix SMB/Netbios implementation.
+ RPC Server Headers
+ Copyright (C) Andrew Tridgell 1992-1997
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1997
+ Copyright (C) Paul Ashton 1997
+ Copyright (C) Jeremy Allison 2000-2004
+ Copyright (C) Simo Sorce 2010-2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _RPC_PIPES_H_
+#define _RPC_PIPES_H_
+
+#include "source3/librpc/rpc/dcerpc.h"
+
+struct tsocket_address;
+struct pipes_struct;
+struct dcesrv_context;
+
+/*
+ * DCE/RPC-specific samba-internal-specific handling of data on
+ * NamedPipes.
+ */
+struct pipes_struct {
+ enum dcerpc_transport_t transport;
+
+ struct messaging_context *msg_ctx;
+
+ /*
+ * Set the DCERPC_FAULT to return.
+ */
+ int fault_state;
+
+ /* This context is used for PDU data and is freed between each pdu.
+ Don't use for pipe state storage. */
+ TALLOC_CTX *mem_ctx;
+
+ /* handle database to use on this pipe. */
+ struct dcesrv_call_state *dce_call;
+};
+
+bool check_open_pipes(void);
+size_t num_pipe_handles(void);
+
+void *create_policy_hnd(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ uint8_t handle_type,
+ void *data_ptr);
+
+void *_find_policy_by_hnd(struct pipes_struct *p,
+ const struct policy_handle *hnd,
+ uint8_t handle_type,
+ NTSTATUS *pstatus);
+#define find_policy_by_hnd(_p, _hnd, _hnd_type, _type, _pstatus) \
+ (_type *)_find_policy_by_hnd((_p), (_hnd), (_hnd_type), (_pstatus));
+
+bool close_policy_hnd(struct pipes_struct *p, struct policy_handle *hnd);
+bool pipe_access_check(struct pipes_struct *p);
+
+#endif /* _RPC_PIPES_H_ */
diff --git a/source3/rpc_server/rpc_server.c b/source3/rpc_server/rpc_server.c
new file mode 100644
index 0000000..a60f429
--- /dev/null
+++ b/source3/rpc_server/rpc_server.c
@@ -0,0 +1,309 @@
+/*
+ Unix SMB/Netbios implementation.
+ Generic infrstructure for RPC Daemons
+ Copyright (C) Simo Sorce 2010
+ Copyright (C) Andrew Bartlett 2011
+ Copyright (C) Andreas Schneider 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "rpc_server/rpc_pipes.h"
+#include "rpc_server/rpc_server.h"
+#include "rpc_server/rpc_config.h"
+#include "rpc_dce.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "librpc/gen_ndr/auth.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/named_pipe_auth/npa_tstream.h"
+#include "../auth/auth_sam_reply.h"
+#include "auth.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "lib/util/idtree_random.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+/* Start listening on the appropriate unix socket and setup all is needed to
+ * dispatch requests to the pipes rpc implementation */
+
+struct dcerpc_ncacn_listen_state {
+ int fd;
+
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+ struct dcesrv_context *dce_ctx;
+ struct dcesrv_endpoint *endpoint;
+ dcerpc_ncacn_termination_fn termination_fn;
+ void *termination_data;
+};
+
+static void ncacn_terminate_connection(struct dcerpc_ncacn_conn *conn,
+ const char *reason);
+
+NTSTATUS dcesrv_auth_gensec_prepare(
+ TALLOC_CTX *mem_ctx,
+ struct dcesrv_call_state *call,
+ struct gensec_security **out,
+ void *private_data)
+{
+ struct gensec_security *gensec = NULL;
+ NTSTATUS status;
+
+ if (out == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = auth_generic_prepare(mem_ctx,
+ call->conn->remote_address,
+ call->conn->local_address,
+ "DCE/RPC",
+ &gensec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to prepare gensec: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ *out = gensec;
+
+ return NT_STATUS_OK;
+}
+
+void dcesrv_log_successful_authz(
+ struct dcesrv_call_state *call,
+ void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct auth4_context *auth4_context = NULL;
+ struct dcesrv_auth *auth = call->auth_state;
+ enum dcerpc_transport_t transport = dcerpc_binding_get_transport(
+ call->conn->endpoint->ep_description);
+ const char *auth_type = derpc_transport_string_by_transport(transport);
+ const char *transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE;
+ NTSTATUS status;
+
+ if (frame == NULL) {
+ DBG_ERR("No memory\n");
+ return;
+ }
+
+ if (transport == NCACN_NP) {
+ transport_protection = AUTHZ_TRANSPORT_PROTECTION_SMB;
+ }
+
+ become_root();
+ status = make_auth4_context(frame, &auth4_context);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Unable to make auth context for authz log.\n");
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ /*
+ * Log the authorization to this RPC interface. This
+ * covered ncacn_np pass-through auth, and anonymous
+ * DCE/RPC (eg epmapper, netlogon etc)
+ */
+ log_successful_authz_event(auth4_context->msg_ctx,
+ auth4_context->lp_ctx,
+ call->conn->remote_address,
+ call->conn->local_address,
+ "DCE/RPC",
+ auth_type,
+ transport_protection,
+ auth->session_info,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_info */);
+
+ auth->auth_audited = true;
+
+ TALLOC_FREE(frame);
+}
+
+static int dcesrv_assoc_group_destructor(struct dcesrv_assoc_group *assoc_group)
+{
+ int ret;
+ ret = idr_remove(assoc_group->dce_ctx->assoc_groups_idr,
+ assoc_group->id);
+ if (ret != 0) {
+ DBG_ERR("Failed to remove assoc_group 0x%08x\n",
+ assoc_group->id);
+ }
+ return 0;
+}
+
+static NTSTATUS dcesrv_assoc_group_new(struct dcesrv_call_state *call)
+{
+ struct dcesrv_connection *conn = call->conn;
+ struct dcesrv_context *dce_ctx = conn->dce_ctx;
+ const struct dcesrv_endpoint *endpoint = conn->endpoint;
+ enum dcerpc_transport_t transport =
+ dcerpc_binding_get_transport(endpoint->ep_description);
+ struct dcesrv_assoc_group *assoc_group = NULL;
+ int id;
+
+ assoc_group = talloc_zero(conn, struct dcesrv_assoc_group);
+ if (assoc_group == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ id = idr_get_new_random(dce_ctx->assoc_groups_idr,
+ assoc_group,
+ 1,
+ UINT16_MAX);
+ if (id == -1) {
+ TALLOC_FREE(assoc_group);
+ DBG_ERR("Out of association groups!\n");
+ return NT_STATUS_RPC_OUT_OF_RESOURCES;
+ }
+
+ assoc_group->transport = transport;
+ assoc_group->id = id;
+ assoc_group->dce_ctx = dce_ctx;
+
+ call->conn->assoc_group = assoc_group;
+
+ talloc_set_destructor(assoc_group, dcesrv_assoc_group_destructor);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dcesrv_assoc_group_reference(struct dcesrv_call_state *call,
+ uint32_t assoc_group_id)
+{
+ struct dcesrv_connection *conn = call->conn;
+ const struct dcesrv_endpoint *endpoint = conn->endpoint;
+ enum dcerpc_transport_t transport =
+ dcerpc_binding_get_transport(endpoint->ep_description);
+ struct dcesrv_assoc_group *assoc_group = NULL;
+ void *id_ptr = NULL;
+
+ /* find an association group given a assoc_group_id */
+ id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, assoc_group_id);
+ if (id_ptr == NULL) {
+ /*
+ * FIXME If the association group is not found it has
+ * been created in other process (preforking daemons).
+ * Until this is properly fixed we just create a new
+ * association group in this process
+ */
+ DBG_NOTICE("Failed to find assoc_group 0x%08x in this "
+ "server process, creating a new one\n",
+ assoc_group_id);
+ return dcesrv_assoc_group_new(call);
+ }
+ assoc_group = talloc_get_type_abort(id_ptr, struct dcesrv_assoc_group);
+
+ if (assoc_group->transport != transport) {
+ const char *at =
+ derpc_transport_string_by_transport(
+ assoc_group->transport);
+ const char *ct =
+ derpc_transport_string_by_transport(
+ transport);
+
+ DBG_NOTICE("assoc_group 0x%08x (transport %s) "
+ "is not available on transport %s\n",
+ assoc_group_id, at, ct);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ conn->assoc_group = talloc_reference(conn, assoc_group);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS dcesrv_assoc_group_find(
+ struct dcesrv_call_state *call,
+ void *private_data)
+{
+ uint32_t assoc_group_id = call->pkt.u.bind.assoc_group_id;
+
+ if (assoc_group_id != 0) {
+ return dcesrv_assoc_group_reference(call, assoc_group_id);
+ }
+
+ /* If not requested by client create a new association group */
+ return dcesrv_assoc_group_new(call);
+}
+
+void dcesrv_transport_terminate_connection(struct dcesrv_connection *dce_conn,
+ const char *reason)
+{
+ struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort(
+ dce_conn->transport.private_data,
+ struct dcerpc_ncacn_conn);
+
+ ncacn_terminate_connection(ncacn_conn, reason);
+}
+
+static void ncacn_terminate_connection(struct dcerpc_ncacn_conn *conn,
+ const char *reason)
+{
+ if (reason == NULL) {
+ reason = "Unknown reason";
+ }
+
+ DBG_NOTICE("Terminating connection - '%s'\n", reason);
+
+ talloc_free(conn);
+}
+
+NTSTATUS dcesrv_endpoint_by_ncacn_np_name(struct dcesrv_context *dce_ctx,
+ const char *pipe_name,
+ struct dcesrv_endpoint **out)
+{
+ struct dcesrv_endpoint *e = NULL;
+
+ for (e = dce_ctx->endpoint_list; e; e = e->next) {
+ enum dcerpc_transport_t transport =
+ dcerpc_binding_get_transport(e->ep_description);
+ const char *endpoint = NULL;
+
+ if (transport != NCACN_NP) {
+ continue;
+ }
+
+ endpoint = dcerpc_binding_get_string_option(e->ep_description,
+ "endpoint");
+ if (endpoint == NULL) {
+ continue;
+ }
+
+ if (strncmp(endpoint, "\\pipe\\", 6) == 0) {
+ endpoint += 6;
+ }
+
+ if (strequal(endpoint, pipe_name)) {
+ *out = e;
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+struct pipes_struct *dcesrv_get_pipes_struct(struct dcesrv_connection *conn)
+{
+ struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort(
+ conn->transport.private_data,
+ struct dcerpc_ncacn_conn);
+
+ return &ncacn_conn->p;
+}
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/rpc_server.h b/source3/rpc_server/rpc_server.h
new file mode 100644
index 0000000..73cd78a
--- /dev/null
+++ b/source3/rpc_server/rpc_server.h
@@ -0,0 +1,72 @@
+/*
+ * RPC Server helper headers
+ * Almost completely rewritten by (C) Jeremy Allison 2005 - 2010
+ * Copyright (C) Simo Sorce <idra@samba.org> - 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_SERVER_H_
+#define _RPC_SERVER_H_
+
+#include "librpc/rpc/rpc_common.h" /* For enum dcerpc_transport_t */
+
+#include "librpc/rpc/dcesrv_core.h"
+#include "rpc_pipes.h"
+
+struct auth_session_info;
+struct cli_credentials;
+
+typedef void (*dcerpc_ncacn_termination_fn)(struct dcesrv_connection *,
+ void *);
+
+struct dcerpc_ncacn_conn {
+ struct dcerpc_ncacn_conn *prev, *next;
+ int sock;
+
+ struct pipes_struct p;
+ dcerpc_ncacn_termination_fn termination_fn;
+ void *termination_data;
+
+ struct dcesrv_endpoint *endpoint;
+
+ char *remote_client_name;
+ char *local_server_name;
+};
+
+void set_incoming_fault(struct pipes_struct *p);
+void process_complete_pdu(struct pipes_struct *p, struct ncacn_packet *pkt);
+
+NTSTATUS dcesrv_auth_gensec_prepare(
+ TALLOC_CTX *mem_ctx,
+ struct dcesrv_call_state *call,
+ struct gensec_security **out,
+ void *private_data);
+void dcesrv_log_successful_authz(
+ struct dcesrv_call_state *call,
+ void *private_data);
+NTSTATUS dcesrv_assoc_group_find(
+ struct dcesrv_call_state *call,
+ void *private_data);
+
+NTSTATUS dcesrv_endpoint_by_ncacn_np_name(struct dcesrv_context *dce_ctx,
+ const char *endpoint,
+ struct dcesrv_endpoint **out);
+
+struct pipes_struct *dcesrv_get_pipes_struct(struct dcesrv_connection *conn);
+
+void dcesrv_transport_terminate_connection(struct dcesrv_connection *dce_conn,
+ const char *reason);
+
+#endif /* _PRC_SERVER_H_ */
diff --git a/source3/rpc_server/rpc_sock_helper.c b/source3/rpc_server/rpc_sock_helper.c
new file mode 100644
index 0000000..364b889
--- /dev/null
+++ b/source3/rpc_server/rpc_sock_helper.c
@@ -0,0 +1,399 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * RPC Socket Helper
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ntdomain.h"
+
+#include "../lib/tsocket/tsocket.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "rpc_server/rpc_sock_helper.h"
+#include "librpc/ndr/ndr_table.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+static NTSTATUS dcesrv_create_ncacn_np_socket(
+ struct dcerpc_binding *b, int *out_fd)
+{
+ char *np_dir = NULL;
+ int fd = -1;
+ NTSTATUS status;
+ const char *endpoint;
+ char *endpoint_normalized = NULL;
+ char *p = NULL;
+
+ endpoint = dcerpc_binding_get_string_option(b, "endpoint");
+ if (endpoint == NULL) {
+ DBG_ERR("Endpoint mandatory for named pipes\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* The endpoint string from IDL can be mixed uppercase and case is
+ * normalized by smbd on connection */
+ endpoint_normalized = strlower_talloc(talloc_tos(), endpoint);
+ if (endpoint_normalized == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* The endpoint string from IDL can be prefixed by \pipe\ */
+ p = endpoint_normalized;
+ if (strncmp(p, "\\pipe\\", 6) == 0) {
+ p += 6;
+ }
+
+ /*
+ * As lp_ncalrpc_dir() should have 0755, but
+ * lp_ncalrpc_dir()/np should have 0700, we need to
+ * create lp_ncalrpc_dir() first.
+ */
+ if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) {
+ status = map_nt_error_from_unix_common(errno);
+ DBG_ERR("Failed to create pipe directory %s - %s\n",
+ lp_ncalrpc_dir(), strerror(errno));
+ goto out;
+ }
+
+ np_dir = talloc_asprintf(talloc_tos(), "%s/np", lp_ncalrpc_dir());
+ if (!np_dir) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("Out of memory\n");
+ goto out;
+ }
+
+ if (!directory_create_or_exist_strict(np_dir, geteuid(), 0700)) {
+ status = map_nt_error_from_unix_common(errno);
+ DBG_ERR("Failed to create pipe directory %s - %s\n",
+ np_dir, strerror(errno));
+ goto out;
+ }
+
+ fd = create_pipe_sock(np_dir, p, 0700);
+ if (fd == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DBG_ERR("Failed to create ncacn_np socket! '%s/%s': %s\n",
+ np_dir, p, strerror(errno));
+ goto out;
+ }
+
+ DBG_DEBUG("Opened pipe socket fd %d for %s\n", fd, p);
+
+ *out_fd = fd;
+
+ status = NT_STATUS_OK;
+
+out:
+ TALLOC_FREE(endpoint_normalized);
+ TALLOC_FREE(np_dir);
+ return status;
+}
+
+/********************************************************************
+ * Start listening on the tcp/ip socket
+ ********************************************************************/
+
+static NTSTATUS dcesrv_create_ncacn_ip_tcp_socket(
+ const struct sockaddr_storage *ifss, uint16_t *port, int *out_fd)
+{
+ int fd = -1;
+
+ if (*port == 0) {
+ static uint16_t low = 0;
+ uint16_t i;
+
+ if (low == 0) {
+ low = lp_rpc_low_port();
+ }
+
+ for (i = low; i <= lp_rpc_high_port(); i++) {
+ fd = open_socket_in(SOCK_STREAM, ifss, i, false);
+ if (fd >= 0) {
+ *port = i;
+ low = i+1;
+ break;
+ }
+ }
+ } else {
+ fd = open_socket_in(SOCK_STREAM, ifss, *port, true);
+ }
+
+ if (fd < 0) {
+ DBG_ERR("Failed to create socket on port %u!\n", *port);
+ return map_nt_error_from_unix(-fd);
+ }
+
+ /* ready to listen */
+ set_socket_options(fd, "SO_KEEPALIVE");
+ set_socket_options(fd, lp_socket_options());
+
+ DBG_DEBUG("Opened ncacn_ip_tcp socket fd %d for port %u\n", fd, *port);
+
+ *out_fd = fd;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS dcesrv_create_ncacn_ip_tcp_sockets(
+ struct dcerpc_binding *b,
+ TALLOC_CTX *mem_ctx,
+ size_t *pnum_fds,
+ int **pfds)
+{
+ uint16_t port = 0;
+ char port_str[11];
+ const char *endpoint = NULL;
+ size_t i = 0, num_fds;
+ int *fds = NULL;
+ struct samba_sockaddr *addrs = NULL;
+ NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
+ bool ok;
+
+ endpoint = dcerpc_binding_get_string_option(b, "endpoint");
+ if (endpoint != NULL) {
+ port = atoi(endpoint);
+ }
+
+ if (lp_interfaces() && lp_bind_interfaces_only()) {
+ num_fds = iface_count();
+ } else {
+ num_fds = 1;
+#ifdef HAVE_IPV6
+ num_fds += 1;
+#endif
+ }
+
+ addrs = talloc_array(mem_ctx, struct samba_sockaddr, num_fds);
+ if (addrs == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ fds = talloc_array(mem_ctx, int, num_fds);
+ if (fds == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ /*
+ * Fill "addrs"
+ */
+
+ if (lp_interfaces() && lp_bind_interfaces_only()) {
+ for (i=0; i<num_fds; i++) {
+ const struct sockaddr_storage *ifss =
+ iface_n_sockaddr_storage(i);
+
+ ok = sockaddr_storage_to_samba_sockaddr(
+ &addrs[i], ifss);
+ if (!ok) {
+ i = 0; /* nothing to close */
+ goto fail;
+ }
+ }
+ } else {
+ struct sockaddr_storage ss = { .ss_family = 0 };
+
+#ifdef HAVE_IPV6
+ ok = interpret_string_addr(
+ &ss, "::", AI_NUMERICHOST|AI_PASSIVE);
+ if (!ok) {
+ goto fail;
+ }
+ ok = sockaddr_storage_to_samba_sockaddr(&addrs[0], &ss);
+ if (!ok) {
+ goto fail;
+ }
+#endif
+ ok = interpret_string_addr(
+ &ss, "0.0.0.0", AI_NUMERICHOST|AI_PASSIVE);
+ if (!ok) {
+ goto fail;
+ }
+
+ /* num_fds set above depending on HAVE_IPV6 */
+ ok = sockaddr_storage_to_samba_sockaddr(
+ &addrs[num_fds-1], &ss);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ for (i=0; i<num_fds; i++) {
+ status = dcesrv_create_ncacn_ip_tcp_socket(
+ &addrs[i].u.ss, &port, &fds[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ samba_sockaddr_set_port(&addrs[i], port);
+ }
+
+ /* Set the port in the endpoint */
+ snprintf(port_str, sizeof(port_str), "%"PRIu16, port);
+
+ status = dcerpc_binding_set_string_option(b, "endpoint", port_str);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to set binding endpoint '%s': %s\n",
+ port_str, nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(addrs);
+
+ *pfds = fds;
+ *pnum_fds = num_fds;
+
+ return NT_STATUS_OK;
+
+fail:
+ while (i > 0) {
+ close(fds[i-1]);
+ i -= 1;
+ }
+ TALLOC_FREE(fds);
+ TALLOC_FREE(addrs);
+ return status;
+}
+
+/********************************************************************
+ * Start listening on the ncalrpc socket
+ ********************************************************************/
+
+static NTSTATUS dcesrv_create_ncalrpc_socket(
+ struct dcerpc_binding *b, int *out_fd)
+{
+ int fd = -1;
+ const char *endpoint = NULL;
+ NTSTATUS status;
+
+ endpoint = dcerpc_binding_get_string_option(b, "endpoint");
+ if (endpoint == NULL) {
+ /*
+ * No identifier specified: use DEFAULT or SMBD.
+ *
+ * When role is AD DC we run two rpc server instances, the one
+ * started by 'samba' and the one embedded in 'smbd'.
+ * Avoid listening in DEFAULT socket for NCALRPC as both
+ * servers will race to accept connections. In this case smbd
+ * will listen in SMBD socket and rpcint binding handle
+ * implementation will pick the right socket to use.
+ *
+ * TODO: DO NOT hardcode this value anywhere else. Rather,
+ * specify no endpoint and let the epmapper worry about it.
+ */
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ endpoint = "SMBD";
+ } else {
+ endpoint = "DEFAULT";
+ }
+ status = dcerpc_binding_set_string_option(
+ b, "endpoint", endpoint);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to set ncalrpc 'endpoint' binding "
+ "string option to '%s': %s\n",
+ endpoint, nt_errstr(status));
+ return status;
+ }
+ }
+
+ if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) {
+ status = map_nt_error_from_unix_common(errno);
+ DBG_ERR("Failed to create ncalrpc directory '%s': %s\n",
+ lp_ncalrpc_dir(), strerror(errno));
+ goto out;
+ }
+
+ fd = create_pipe_sock(lp_ncalrpc_dir(), endpoint, 0755);
+ if (fd == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DBG_ERR("Failed to create ncalrpc socket '%s/%s': %s\n",
+ lp_ncalrpc_dir(), endpoint, strerror(errno));
+ goto out;
+ }
+
+ DBG_DEBUG("Opened ncalrpc socket fd '%d' for '%s/%s'\n",
+ fd, lp_ncalrpc_dir(), endpoint);
+
+ *out_fd = fd;
+
+ return NT_STATUS_OK;
+
+out:
+ return status;
+}
+
+NTSTATUS dcesrv_create_binding_sockets(
+ struct dcerpc_binding *b,
+ TALLOC_CTX *mem_ctx,
+ size_t *pnum_fds,
+ int **pfds)
+{
+ enum dcerpc_transport_t transport = dcerpc_binding_get_transport(b);
+ size_t i, num_fds = 1;
+ int *fds = NULL;
+ NTSTATUS status;
+
+ if ((transport == NCALRPC) || (transport == NCACN_NP)) {
+ fds = talloc(mem_ctx, int);
+ if (fds == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ switch(transport) {
+ case NCALRPC:
+ status = dcesrv_create_ncalrpc_socket(b, fds);
+ break;
+ case NCACN_NP:
+ status = dcesrv_create_ncacn_np_socket(b, fds);
+ break;
+ case NCACN_IP_TCP:
+ status = dcesrv_create_ncacn_ip_tcp_sockets(
+ b, talloc_tos(), &num_fds, &fds);
+ break;
+ default:
+ status = NT_STATUS_NOT_SUPPORTED;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(fds);
+ return status;
+ }
+
+ for (i=0; i<num_fds; i++) {
+ bool ok = smb_set_close_on_exec(fds[i]);
+ if (!ok) {
+ status = map_nt_error_from_unix(errno);
+ break;
+ }
+ }
+ if (i < num_fds) {
+ for (i=0; i<num_fds; i++) {
+ close(fds[i]);
+ }
+ TALLOC_FREE(fds);
+ return status;
+ }
+
+ *pfds = fds;
+ *pnum_fds = num_fds;
+ return NT_STATUS_OK;
+}
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/rpc_sock_helper.h b/source3/rpc_server/rpc_sock_helper.h
new file mode 100644
index 0000000..9c8128a
--- /dev/null
+++ b/source3/rpc_server/rpc_sock_helper.h
@@ -0,0 +1,36 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * RPC Socket Helper
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef _RPC_SOCK_HELPER_H_
+#define _RPC_SOCK_HELPER_H_
+
+#include "rpc_server.h"
+
+NTSTATUS dcesrv_create_binding_sockets(
+ struct dcerpc_binding *b,
+ TALLOC_CTX *mem_ctx,
+ size_t *pnum_fds,
+ int **fds);
+
+#endif /* _RPC_SOCK_HELPER_H_ */
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/rpc_worker.c b/source3/rpc_server/rpc_worker.c
new file mode 100644
index 0000000..bf9671d
--- /dev/null
+++ b/source3/rpc_server/rpc_worker.c
@@ -0,0 +1,1287 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "source3/include/includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "rpc_worker.h"
+#include "rpc_config.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/rpc/dcerpc_util.h"
+#include "source3/librpc/gen_ndr/ndr_rpc_host.h"
+#include "lib/util/debug.h"
+#include "lib/util/fault.h"
+#include "rpc_server.h"
+#include "rpc_pipes.h"
+#include "source3/smbd/proto.h"
+#include "source3/lib/smbd_shim.h"
+#include "source3/lib/global_contexts.h"
+#include "source3/lib/util_procid.h"
+#include "lib/tsocket/tsocket.h"
+#include "libcli/named_pipe_auth/npa_tstream.h"
+#include "libcli/smb/smb_constants.h"
+#include "lib/param/param.h"
+#include "lib/util/idtree_random.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/async_req/async_sock.h"
+#include "lib/util/dlinklist.h"
+#include "source3/include/auth.h"
+#include "nsswitch/winbind_client.h"
+#include "source3/include/messages.h"
+#include "libcli/security/security_token.h"
+#include "libcli/security/dom_sid.h"
+#include "source3/include/proto.h"
+
+/*
+ * This is the generic code that becomes the
+ * template that all rpcd_* instances that
+ * serve DCERPC can use to provide services to samba-dcerpcd.
+ *
+ * The external entry point is:
+ * rpc_worker_main() which takes an argc/argv list
+ * and two functions:
+ *
+ * get_interfaces() - List all interfaces that this server provides
+ * get_servers() - Provide the RPC server implementations
+ *
+ * Each rpcd_* service needs only to provide
+ * the implementations of get_interfaces() and get_servers()
+ * and call rpc_worker_main() from their main() function
+ * to provide services that can be connected to from samba-dcerpcd.
+ */
+
+struct rpc_worker {
+ struct dcerpc_ncacn_conn *conns;
+ struct server_id rpc_host_pid;
+ struct messaging_context *msg_ctx;
+ struct dcesrv_context *dce_ctx;
+
+ struct dcesrv_context_callbacks cb;
+
+ struct rpc_worker_status status;
+
+ bool done;
+};
+
+static void rpc_worker_print_interface(
+ FILE *f, const struct ndr_interface_table *t)
+{
+ const struct ndr_interface_string_array *endpoints = t->endpoints;
+ uint32_t i;
+ struct ndr_syntax_id_buf id_buf;
+
+ fprintf(f,
+ "%s %s\n",
+ ndr_syntax_id_buf_string(&t->syntax_id, &id_buf),
+ t->name);
+
+ for (i=0; i<endpoints->count; i++) {
+ fprintf(f, " %s\n", endpoints->names[i]);
+ }
+}
+
+static NTSTATUS rpc_worker_report_status(struct rpc_worker *worker)
+{
+ uint8_t buf[16];
+ DATA_BLOB blob = { .data = buf, .length = sizeof(buf), };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ worker->status.num_association_groups = worker->dce_ctx->assoc_groups_num;
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(rpc_worker_status, &worker->status);
+ }
+
+ ndr_err = ndr_push_struct_into_fixed_blob(
+ &blob,
+ &worker->status,
+ (ndr_push_flags_fn_t)ndr_push_rpc_worker_status);
+ SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+
+ status = messaging_send(
+ worker->msg_ctx,
+ worker->rpc_host_pid,
+ MSG_RPC_WORKER_STATUS,
+ &blob);
+ return status;
+}
+
+static void rpc_worker_connection_terminated(
+ struct dcesrv_connection *conn, void *private_data)
+{
+ struct rpc_worker *worker = talloc_get_type_abort(
+ private_data, struct rpc_worker);
+ struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort(
+ conn->transport.private_data, struct dcerpc_ncacn_conn);
+ struct dcerpc_ncacn_conn *w = NULL;
+ NTSTATUS status;
+ bool found = false;
+
+ /*
+ * We need to drop the association group reference
+ * explicitly here in order to avoid the order given
+ * by the destructors. rpc_worker_report_status() below,
+ * expects worker->dce_ctx->assoc_groups_num to be updated
+ * already.
+ */
+ if (conn->assoc_group != NULL) {
+ talloc_unlink(conn, conn->assoc_group);
+ conn->assoc_group = NULL;
+ }
+
+ SMB_ASSERT(worker->status.num_connections > 0);
+
+ for (w = worker->conns; w != NULL; w = w->next) {
+ if (w == ncacn_conn) {
+ found = true;
+ break;
+ }
+ }
+ SMB_ASSERT(found);
+
+ DLIST_REMOVE(worker->conns, ncacn_conn);
+
+ worker->status.num_connections -= 1;
+
+ status = rpc_worker_report_status(worker);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_worker_report_status returned %s\n",
+ nt_errstr(status));
+ }
+}
+
+static int dcesrv_connection_destructor(struct dcesrv_connection *conn)
+{
+ struct dcerpc_ncacn_conn *ncacn_conn = talloc_get_type_abort(
+ conn->transport.private_data,
+ struct dcerpc_ncacn_conn);
+
+ if (ncacn_conn->termination_fn != NULL) {
+ ncacn_conn->termination_fn(conn, ncacn_conn->termination_data);
+ }
+
+ return 0;
+}
+
+/*
+ * A new client has been passed to us from samba-dcerpcd.
+ */
+static void rpc_worker_new_client(
+ struct rpc_worker *worker,
+ struct rpc_host_client *client,
+ int sock)
+{
+ struct dcesrv_context *dce_ctx = worker->dce_ctx;
+ struct named_pipe_auth_req_info8 *info8 = client->npa_info8;
+ struct tsocket_address *remote_client_addr = NULL;
+ struct tsocket_address *local_server_addr = NULL;
+ struct dcerpc_binding *b = NULL;
+ enum dcerpc_transport_t transport;
+ struct dcesrv_endpoint *ep = NULL;
+ struct tstream_context *tstream = NULL;
+ struct dcerpc_ncacn_conn *ncacn_conn = NULL;
+ struct dcesrv_connection *dcesrv_conn = NULL;
+ DATA_BLOB buffer = { .data = NULL };
+ struct ncacn_packet *pkt = NULL;
+ struct security_token *token = NULL;
+ uint32_t npa_flags, state_flags;
+ bool found_npa_flags;
+ NTSTATUS status;
+ int ret;
+
+ DBG_DEBUG("Got new conn sock %d for binding %s\n",
+ sock,
+ client->binding);
+
+ status = dcerpc_parse_binding(client, client->binding, &b);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_parse_binding(%s) failed: %s\n",
+ client->binding,
+ nt_errstr(status));
+ goto fail;
+ }
+ transport = dcerpc_binding_get_transport(b);
+
+ status = dcesrv_find_endpoint(dce_ctx, b, &ep);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) &&
+ ((transport == NCACN_IP_TCP) || (transport == NCALRPC)) &&
+ (dcerpc_binding_get_string_option(b, "endpoint") != NULL)) {
+ /*
+ * We have two kinds of servers: Those who explicitly
+ * bind to a port (e.g. 135 for epmapper) and those
+ * who just specify a transport. The client specified
+ * a port (or socket name), but we did not find this
+ * in the list of servers having specified a
+ * port. Retry just matching for the transport,
+ * catching the servers that did not explicitly
+ * specify a port.
+ *
+ * This is not fully correct, what we should do is
+ * that once the port the server listens on has been
+ * finalized we should mark this in the server list,
+ * but for now it works. We don't have the same RPC
+ * interface listening twice on different ports.
+ */
+ struct dcerpc_binding *b_without_port = dcerpc_binding_dup(
+ client, b);
+ if (b_without_port == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ status = dcerpc_binding_set_string_option(
+ b_without_port, "endpoint", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not delete endpoint: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(b_without_port);
+ goto fail;
+ }
+
+ status = dcesrv_find_endpoint(dce_ctx, b_without_port, &ep);
+
+ TALLOC_FREE(b_without_port);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not find endpoint for %s: %s\n",
+ client->binding,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ ncacn_conn = talloc(dce_ctx, struct dcerpc_ncacn_conn);
+ if (ncacn_conn == NULL) {
+ DBG_DEBUG("talloc failed\n");
+ goto fail;
+ }
+ *ncacn_conn = (struct dcerpc_ncacn_conn) {
+ .endpoint = ep,
+ .sock = sock,
+ .termination_fn = rpc_worker_connection_terminated,
+ .termination_data = worker,
+ };
+
+ if (transport == NCALRPC) {
+ ret = tsocket_address_unix_from_path(ncacn_conn,
+ info8->remote_client_addr,
+ &remote_client_addr);
+ if (ret == -1) {
+ DBG_DEBUG("tsocket_address_unix_from_path"
+ "(%s) failed: %s\n",
+ info8->remote_client_addr,
+ strerror(errno));
+ goto fail;
+ }
+
+ ncacn_conn->remote_client_name =
+ talloc_strdup(ncacn_conn, info8->remote_client_name);
+ if (ncacn_conn->remote_client_name == NULL) {
+ DBG_DEBUG("talloc_strdup(%s) failed\n",
+ info8->remote_client_name);
+ goto fail;
+ }
+
+ ret = tsocket_address_unix_from_path(ncacn_conn,
+ info8->local_server_addr,
+ &local_server_addr);
+ if (ret == -1) {
+ DBG_DEBUG("tsocket_address_unix_from_path"
+ "(%s) failed: %s\n",
+ info8->local_server_addr,
+ strerror(errno));
+ goto fail;
+ }
+
+ ncacn_conn->local_server_name =
+ talloc_strdup(ncacn_conn, info8->local_server_name);
+ if (ncacn_conn->local_server_name == NULL) {
+ DBG_DEBUG("talloc_strdup(%s) failed\n",
+ info8->local_server_name);
+ goto fail;
+ }
+ } else {
+ ret = tsocket_address_inet_from_strings(
+ ncacn_conn,
+ "ip",
+ info8->remote_client_addr,
+ info8->remote_client_port,
+ &remote_client_addr);
+ if (ret == -1) {
+ DBG_DEBUG("tsocket_address_inet_from_strings"
+ "(%s, %" PRIu16 ") failed: %s\n",
+ info8->remote_client_addr,
+ info8->remote_client_port,
+ strerror(errno));
+ goto fail;
+ }
+ ncacn_conn->remote_client_name =
+ talloc_strdup(ncacn_conn, info8->remote_client_name);
+ if (ncacn_conn->remote_client_name == NULL) {
+ DBG_DEBUG("talloc_strdup(%s) failed\n",
+ info8->remote_client_name);
+ goto fail;
+ }
+
+ ret = tsocket_address_inet_from_strings(
+ ncacn_conn,
+ "ip",
+ info8->local_server_addr,
+ info8->local_server_port,
+ &local_server_addr);
+ if (ret == -1) {
+ DBG_DEBUG("tsocket_address_inet_from_strings"
+ "(%s, %" PRIu16 ") failed: %s\n",
+ info8->local_server_addr,
+ info8->local_server_port,
+ strerror(errno));
+ goto fail;
+ }
+ ncacn_conn->local_server_name =
+ talloc_strdup(ncacn_conn, info8->local_server_name);
+ if (ncacn_conn->local_server_name == NULL) {
+ DBG_DEBUG("talloc_strdup(%s) failed\n",
+ info8->local_server_name);
+ goto fail;
+ }
+ }
+
+ if (transport == NCACN_NP) {
+ ret = tstream_npa_existing_socket(
+ ncacn_conn,
+ sock,
+ FILE_TYPE_MESSAGE_MODE_PIPE,
+ &tstream);
+ if (ret == -1) {
+ DBG_DEBUG("tstream_npa_existing_socket failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+
+ /*
+ * "transport" so far is implicitly assigned by the
+ * socket that the client connected to, passed in from
+ * samba-dcerpcd via the binding. For NCACN_NP (root
+ * only by unix permissions) we got a
+ * named_pipe_auth_req_info8 where the transport can
+ * be overridden.
+ */
+ transport = info8->transport;
+ } else {
+ ret = tstream_bsd_existing_socket(
+ ncacn_conn, sock, &tstream);
+ if (ret == -1) {
+ DBG_DEBUG("tstream_bsd_existing_socket failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+ /* as server we want to fail early */
+ tstream_bsd_fail_readv_first_error(tstream, true);
+ }
+ sock = -1;
+
+ token = info8->session_info->session_info->security_token;
+
+ if (security_token_is_system(token) && (transport != NCALRPC)) {
+ DBG_DEBUG("System token only allowed on NCALRPC\n");
+ goto fail;
+ }
+
+ state_flags = DCESRV_CALL_STATE_FLAG_MAY_ASYNC;
+
+ found_npa_flags = security_token_find_npa_flags(token, &npa_flags);
+ if (found_npa_flags) {
+ if (npa_flags & SAMBA_NPA_FLAGS_WINBIND_OFF) {
+ state_flags |=
+ DCESRV_CALL_STATE_FLAG_WINBIND_OFF;
+ }
+
+ /*
+ * Delete the flags so that we don't bail in
+ * local_np_connect_send() on subsequent
+ * connects. Once we connect to another RPC service, a
+ * new flags sid will be added if required.
+ */
+ security_token_del_npa_flags(token);
+ }
+
+ ncacn_conn->p.msg_ctx = global_messaging_context();
+ ncacn_conn->p.transport = transport;
+
+ status = dcesrv_endpoint_connect(dce_ctx,
+ ncacn_conn,
+ ep,
+ info8->session_info->session_info,
+ global_event_context(),
+ state_flags,
+ &dcesrv_conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Failed to connect to endpoint: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ talloc_set_destructor(dcesrv_conn, dcesrv_connection_destructor);
+
+ dcesrv_conn->transport.private_data = ncacn_conn;
+ dcesrv_conn->transport.report_output_data =
+ dcesrv_sock_report_output_data;
+ dcesrv_conn->transport.terminate_connection =
+ dcesrv_transport_terminate_connection;
+
+ dcesrv_conn->send_queue = tevent_queue_create(
+ dcesrv_conn, "dcesrv send queue");
+ if (dcesrv_conn->send_queue == NULL) {
+ DBG_DEBUG("tevent_queue_create failed\n");
+ goto fail;
+ }
+
+ dcesrv_conn->stream = talloc_move(dcesrv_conn, &tstream);
+ dcesrv_conn->local_address =
+ talloc_move(dcesrv_conn, &local_server_addr);
+ dcesrv_conn->remote_address =
+ talloc_move(dcesrv_conn, &remote_client_addr);
+
+ if (client->bind_packet.length == 0) {
+ DBG_DEBUG("Expected bind packet\n");
+ goto fail;
+ }
+
+ buffer = (DATA_BLOB) {
+ .data = talloc_move(dcesrv_conn, &client->bind_packet.data),
+ .length = client->bind_packet.length,
+ };
+
+ pkt = talloc(dcesrv_conn, struct ncacn_packet);
+ if (pkt == NULL) {
+ DBG_DEBUG("talloc failed\n");
+ goto fail;
+ }
+
+ status = dcerpc_pull_ncacn_packet(pkt, &buffer, pkt);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_pull_ncacn_packet failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(client);
+
+ DLIST_ADD(worker->conns, ncacn_conn);
+ worker->status.num_connections += 1;
+
+ dcesrv_loop_next_packet(dcesrv_conn, pkt, buffer);
+
+ return;
+fail:
+ TALLOC_FREE(ncacn_conn);
+ TALLOC_FREE(dcesrv_conn);
+ TALLOC_FREE(client);
+ if (sock != -1) {
+ close(sock);
+ }
+
+ /*
+ * Parent thinks it successfully sent us a client. Tell it
+ * that we declined.
+ */
+ status = rpc_worker_report_status(worker);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_worker_report_status returned %s\n",
+ nt_errstr(status));
+ }
+}
+
+/*
+ * New client message processing.
+ */
+static bool rpc_worker_new_client_filter(
+ struct messaging_rec *rec, void *private_data)
+{
+ struct rpc_worker *worker = talloc_get_type_abort(
+ private_data, struct rpc_worker);
+ struct dcesrv_context *dce_ctx = worker->dce_ctx;
+ struct rpc_host_client *client = NULL;
+ enum ndr_err_code ndr_err;
+ int sock;
+
+ if (rec->msg_type != MSG_RPC_HOST_NEW_CLIENT) {
+ return false;
+ }
+
+ if (rec->num_fds != 1) {
+ DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds);
+ return false;
+ }
+
+ client = talloc(dce_ctx, struct rpc_host_client);
+ if (client == NULL) {
+ DBG_DEBUG("talloc failed\n");
+ return false;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &rec->buf,
+ client,
+ client,
+ (ndr_pull_flags_fn_t)ndr_pull_rpc_host_client);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_rpc_host_client failed: %s\n",
+ ndr_errstr(ndr_err));
+ TALLOC_FREE(client);
+ return false;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(rpc_host_client, client);
+ }
+
+ sock = rec->fds[0];
+ rec->fds[0] = -1;
+
+ rpc_worker_new_client(worker, client, sock);
+
+ return false;
+}
+
+/*
+ * Return your status message processing.
+ */
+static bool rpc_worker_status_filter(
+ struct messaging_rec *rec, void *private_data)
+{
+ struct rpc_worker *worker = talloc_get_type_abort(
+ private_data, struct rpc_worker);
+ struct dcerpc_ncacn_conn *conn = NULL;
+ FILE *f = NULL;
+ int fd;
+
+ if (rec->msg_type != MSG_RPC_DUMP_STATUS) {
+ return false;
+ }
+
+ if (rec->num_fds != 1) {
+ DBG_DEBUG("Got %"PRIu8" fds\n", rec->num_fds);
+ return false;
+ }
+
+ fd = dup(rec->fds[0]);
+ if (fd == -1) {
+ DBG_DEBUG("dup(%"PRIi64") failed: %s\n",
+ rec->fds[0],
+ strerror(errno));
+ return false;
+ }
+
+ f = fdopen(fd, "w");
+ if (f == NULL) {
+ DBG_DEBUG("fdopen failed: %s\n", strerror(errno));
+ close(fd);
+ return false;
+ }
+
+ for (conn = worker->conns; conn != NULL; conn = conn->next) {
+ char *endpoint = NULL;
+
+ endpoint = dcerpc_binding_string(
+ conn, conn->endpoint->ep_description);
+
+ fprintf(f,
+ "endpoint=%s client=%s server=%s\n",
+ endpoint ? endpoint : "UNKNOWN",
+ conn->remote_client_name,
+ conn->local_server_name);
+ TALLOC_FREE(endpoint);
+ }
+
+ fclose(f);
+
+ return false;
+}
+
+/*
+ take a reference to an existing association group
+ */
+static struct dcesrv_assoc_group *rpc_worker_assoc_group_reference(
+ struct dcesrv_connection *conn,
+ uint32_t id)
+{
+ const struct dcesrv_endpoint *endpoint = conn->endpoint;
+ enum dcerpc_transport_t transport = dcerpc_binding_get_transport(
+ endpoint->ep_description);
+ struct dcesrv_assoc_group *assoc_group = NULL;
+ void *id_ptr = NULL;
+
+ /* find an association group given a assoc_group_id */
+ id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, id & UINT16_MAX);
+ if (id_ptr == NULL) {
+ DBG_NOTICE("Failed to find assoc_group 0x%08x\n", id);
+ return NULL;
+ }
+ assoc_group = talloc_get_type_abort(id_ptr, struct dcesrv_assoc_group);
+
+ if (assoc_group->transport != transport) {
+ const char *at = derpc_transport_string_by_transport(
+ assoc_group->transport);
+ const char *ct = derpc_transport_string_by_transport(
+ transport);
+
+ DBG_NOTICE("assoc_group 0x%08x (transport %s) "
+ "is not available on transport %s\n",
+ id, at, ct);
+ return NULL;
+ }
+
+ /*
+ * Yes, this is a talloc_reference: The assoc group must be
+ * removed when all connections go. This should be replaced by
+ * adding a linked list of dcesrv_connection structs to the
+ * assoc group.
+ */
+ return talloc_reference(conn, assoc_group);
+}
+
+static int rpc_worker_assoc_group_destructor(
+ struct dcesrv_assoc_group *assoc_group)
+{
+ int ret;
+
+ ret = idr_remove(
+ assoc_group->dce_ctx->assoc_groups_idr,
+ assoc_group->id & UINT16_MAX);
+ if (ret != 0) {
+ DBG_WARNING("Failed to remove assoc_group 0x%08x\n",
+ assoc_group->id);
+ }
+
+ SMB_ASSERT(assoc_group->dce_ctx->assoc_groups_num > 0);
+ assoc_group->dce_ctx->assoc_groups_num -= 1;
+ return 0;
+}
+
+/*
+ allocate a new association group
+ */
+static struct dcesrv_assoc_group *rpc_worker_assoc_group_new(
+ struct dcesrv_connection *conn, uint16_t worker_index)
+{
+ struct dcesrv_context *dce_ctx = conn->dce_ctx;
+ const struct dcesrv_endpoint *endpoint = conn->endpoint;
+ enum dcerpc_transport_t transport = dcerpc_binding_get_transport(
+ endpoint->ep_description);
+ struct dcesrv_assoc_group *assoc_group = NULL;
+ int id;
+
+ assoc_group = talloc_zero(conn, struct dcesrv_assoc_group);
+ if (assoc_group == NULL) {
+ return NULL;
+ }
+
+ /*
+ * We use 16-bit to encode the worker index,
+ * have 16-bits left within the worker to form a
+ * 32-bit association group id.
+ */
+ id = idr_get_new_random(
+ dce_ctx->assoc_groups_idr, assoc_group, 1, UINT16_MAX);
+ if (id == -1) {
+ talloc_free(assoc_group);
+ DBG_WARNING("Out of association groups!\n");
+ return NULL;
+ }
+ assoc_group->id = (((uint32_t)worker_index) << 16) | id;
+ assoc_group->transport = transport;
+ assoc_group->dce_ctx = dce_ctx;
+
+ talloc_set_destructor(assoc_group, rpc_worker_assoc_group_destructor);
+
+ SMB_ASSERT(dce_ctx->assoc_groups_num < UINT16_MAX);
+ dce_ctx->assoc_groups_num += 1;
+
+ return assoc_group;
+}
+
+static NTSTATUS rpc_worker_assoc_group_find(
+ struct dcesrv_call_state *call,
+ void *private_data)
+{
+ struct rpc_worker *w = talloc_get_type_abort(
+ private_data, struct rpc_worker);
+ uint32_t assoc_group_id = call->pkt.u.bind.assoc_group_id;
+
+ if (assoc_group_id != 0) {
+ uint16_t worker_index = (assoc_group_id & 0xffff0000) >> 16;
+ if (worker_index != w->status.worker_index) {
+ DBG_DEBUG("Wrong worker id %"PRIu16", "
+ "expected %"PRIu32"\n",
+ worker_index,
+ w->status.worker_index);
+ return NT_STATUS_NOT_FOUND;
+ }
+ call->conn->assoc_group = rpc_worker_assoc_group_reference(
+ call->conn, assoc_group_id);
+ } else {
+ call->conn->assoc_group = rpc_worker_assoc_group_new(
+ call->conn, w->status.worker_index);
+ }
+
+ if (call->conn->assoc_group == NULL) {
+ /* TODO Return correct status */
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static struct rpc_worker *rpc_worker_new(
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx)
+{
+ struct rpc_worker *worker = NULL;
+
+ worker = talloc_zero(mem_ctx, struct rpc_worker);
+ if (worker == NULL) {
+ return NULL;
+ }
+
+ worker->rpc_host_pid = (struct server_id) { .pid = 0 };
+ worker->msg_ctx = msg_ctx;
+
+ worker->cb = (struct dcesrv_context_callbacks) {
+ .log.successful_authz = dcesrv_log_successful_authz,
+ .auth.gensec_prepare = dcesrv_auth_gensec_prepare,
+ .auth.become_root = become_root,
+ .auth.unbecome_root = unbecome_root,
+ .assoc_group.find = rpc_worker_assoc_group_find,
+ .assoc_group.private_data = worker,
+ };
+
+ worker->dce_ctx = global_dcesrv_context();
+ if (worker->dce_ctx == NULL) {
+ goto fail;
+ }
+ dcesrv_context_set_callbacks(worker->dce_ctx, &worker->cb);
+
+ return worker;
+fail:
+ TALLOC_FREE(worker);
+ return NULL;
+}
+
+static struct dcesrv_context *rpc_worker_dce_ctx(struct rpc_worker *w)
+{
+ return w->dce_ctx;
+}
+
+struct rpc_worker_state {
+ struct tevent_context *ev;
+ struct rpc_worker *w;
+ struct tevent_req *new_client_req;
+ struct tevent_req *status_req;
+ struct tevent_req *finish_req;
+};
+
+static void rpc_worker_done(struct tevent_req *subreq);
+static void rpc_worker_shutdown(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+static struct tevent_req *rpc_worker_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct rpc_worker *w,
+ pid_t rpc_host_pid,
+ int server_index,
+ int worker_index)
+{
+ struct tevent_req *req = NULL;
+ struct rpc_worker_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_worker_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->w = w;
+
+ if ((server_index < 0) || ((unsigned)server_index > UINT32_MAX)) {
+ DBG_ERR("Invalid server index %d\n", server_index);
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+ if ((worker_index < 0) || ((unsigned)worker_index > UINT16_MAX)) {
+ DBG_ERR("Invalid worker index %d\n", worker_index);
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+ w->rpc_host_pid = pid_to_procid(rpc_host_pid);
+
+ w->status = (struct rpc_worker_status) {
+ .server_index = server_index,
+ .worker_index = worker_index,
+ };
+
+ /* Wait for new client messages. */
+ state->new_client_req = messaging_filtered_read_send(
+ w,
+ messaging_tevent_context(w->msg_ctx),
+ w->msg_ctx,
+ rpc_worker_new_client_filter,
+ w);
+ if (tevent_req_nomem(state->new_client_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /* Wait for report your status messages. */
+ state->status_req = messaging_filtered_read_send(
+ w,
+ messaging_tevent_context(w->msg_ctx),
+ w->msg_ctx,
+ rpc_worker_status_filter,
+ w);
+ if (tevent_req_nomem(state->status_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /* Wait for shutdown messages. */
+ status = messaging_register(
+ w->msg_ctx, req, MSG_SHUTDOWN, rpc_worker_shutdown);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_register failed: %s\n",
+ nt_errstr(status));
+ tevent_req_error(req, map_errno_from_nt_status(status));
+ return tevent_req_post(req, ev);
+ }
+
+ state->finish_req = wait_for_read_send(state, ev, 0, false);
+ if (tevent_req_nomem(state->finish_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->finish_req, rpc_worker_done, req);
+
+ rpc_worker_report_status(w);
+
+ return req;
+}
+
+static void rpc_worker_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int err = 0;
+ bool ok;
+
+ ok = wait_for_read_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, err);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void rpc_worker_shutdown(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ tevent_req_done(req);
+}
+
+static int rpc_worker_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static void sig_term_handler(
+ struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ exit(0);
+}
+
+static void sig_hup_handler(
+ struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ change_to_root_user();
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+}
+
+static NTSTATUS register_ep_server(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ NTSTATUS status;
+
+ DBG_DEBUG("Registering server %s\n", ep_server->name);
+
+ status = dcerpc_register_ep_server(ep_server);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ DBG_ERR("Failed to register '%s' endpoint server: %s\n",
+ ep_server->name,
+ nt_errstr(status));
+ return status;
+ }
+
+ status = dcesrv_init_ep_server(dce_ctx, ep_server->name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dcesrv_init_ep_server(%s) failed: %s\n",
+ ep_server->name,
+ nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * @brief Main function for RPC server implementations
+ *
+ * This function provides all that is necessary to run a RPC server
+ * inside the samba-dcerpcd framework. Just pass argv and argc on to
+ * this function.
+ *
+ * The get_interfaces() callback provides the information that is
+ * passed to samba-dcerpcd via --list-interfaces, it should not do any
+ * real RPC server initialization work. Quickly after this function is
+ * called by rpc_worker_main, the process exits again. It should
+ * return the number of interfaces provided.
+ *
+ * get_servers() is called when the process is about to do the real
+ * work. So more heavy-weight initialization should happen here. It
+ * should return NT_STATUS_OK and the number of server implementations provided.
+ *
+ * @param[in] argc argc from main()
+ * @param[in] argv argv from main()
+ * @param[in] get_interfaces List all interfaces that this server provides
+ * @param[in] get_servers Provide the RPC server implementations
+ * @param[in] private_data Passed to the callback functions
+ * @return 0 It should never return except on successful process exit
+ */
+
+int rpc_worker_main(
+ int argc,
+ const char *argv[],
+ const char *daemon_config_name,
+ int num_workers,
+ int idle_seconds,
+ size_t (*get_interfaces)(
+ const struct ndr_interface_table ***ifaces,
+ void *private_data),
+ NTSTATUS (*get_servers)(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***ep_servers,
+ size_t *num_ep_servers,
+ void *private_data),
+ void *private_data)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *progname = getprogname();
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev_ctx = NULL;
+ struct tevent_req *req = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ struct dcesrv_context *dce_ctx = NULL;
+ struct tevent_signal *se = NULL;
+ poptContext pc;
+ int opt;
+ NTSTATUS status;
+ int ret;
+ int worker_group = -1;
+ int worker_index = -1;
+ bool log_stdout;
+ int list_interfaces = 0;
+ struct rpc_worker *worker = NULL;
+ const struct dcesrv_endpoint_server **ep_servers;
+ size_t i, num_servers;
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "list-interfaces",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &list_interfaces,
+ .descrip = "List the interfaces provided",
+ },
+ {
+ .longName = "worker-group",
+ .argInfo = POPT_ARG_INT,
+ .arg = &worker_group,
+ .descrip = "Group index in status message",
+ },
+ {
+ .longName = "worker-index",
+ .argInfo = POPT_ARG_INT,
+ .arg = &worker_index,
+ .descrip = "Worker index in status message",
+ },
+ POPT_COMMON_SAMBA
+ POPT_TABLEEND
+ };
+ static const struct smbd_shim smbd_shim_fns = {
+ .become_authenticated_pipe_user =
+ smbd_become_authenticated_pipe_user,
+ .unbecome_authenticated_pipe_user =
+ smbd_unbecome_authenticated_pipe_user,
+ .become_root = smbd_become_root,
+ .unbecome_root = smbd_unbecome_root,
+ };
+
+ closefrom(3);
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+ umask(0);
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(ENOMEM);
+ }
+
+ pc = samba_popt_get_context(progname, argc, argv, long_options, 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ d_fprintf(stderr,
+ "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0),
+ poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ TALLOC_FREE(frame);
+ exit(1);
+ };
+ poptFreeContext(pc);
+
+ if (list_interfaces != 0) {
+ const struct ndr_interface_table **ifaces = NULL;
+ size_t num_ifaces;
+
+ num_workers = lp_parm_int(
+ -1, daemon_config_name, "num_workers", num_workers);
+ idle_seconds = lp_parm_int(
+ -1, daemon_config_name, "idle_seconds", idle_seconds);
+
+ DBG_DEBUG("daemon=%s, num_workers=%d, idle_seconds=%d\n",
+ daemon_config_name,
+ num_workers,
+ idle_seconds);
+
+ fprintf(stdout, "%d\n%d\n", num_workers, idle_seconds);
+
+ num_ifaces = get_interfaces(&ifaces, private_data);
+
+ for (i=0; i<num_ifaces; i++) {
+ rpc_worker_print_interface(stdout, ifaces[i]);
+ }
+
+ TALLOC_FREE(frame);
+ exit(0);
+ }
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+ if (log_stdout != 0) {
+ setup_logging(argv[0], DEBUG_STDOUT);
+ } else {
+ setup_logging(argv[0], DEBUG_FILE);
+ }
+
+ set_smbd_shim(&smbd_shim_fns);
+
+ dump_core_setup(progname, lp_logfile(talloc_tos(), lp_sub));
+
+ /* POSIX demands that signals are inherited. If the invoking
+ * process has these signals masked, we will have problems, as
+ * we won't receive them. */
+ BlockSignals(False, SIGHUP);
+ BlockSignals(False, SIGUSR1);
+ BlockSignals(False, SIGTERM);
+
+#if defined(SIGFPE)
+ /* we are never interested in SIGFPE */
+ BlockSignals(True,SIGFPE);
+#endif
+ /* We no longer use USR2... */
+#if defined(SIGUSR2)
+ BlockSignals(True, SIGUSR2);
+#endif
+ /* Ignore children - no zombies. */
+ CatchChild();
+
+ reopen_logs();
+
+ DBG_STARTUP_NOTICE("%s version %s started.\n%s\n",
+ progname,
+ samba_version_string(),
+ samba_copyright_string());
+
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ DBG_ERR("global_messaging_context() failed\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ ev_ctx = messaging_tevent_context(msg_ctx);
+
+ worker = rpc_worker_new(ev_ctx, msg_ctx);
+ if (worker == NULL) {
+ DBG_ERR("rpc_worker_new failed\n");
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ dce_ctx = rpc_worker_dce_ctx(worker);
+
+ se = tevent_add_signal(
+ ev_ctx, ev_ctx, SIGTERM, 0, sig_term_handler, NULL);
+ if (se == NULL) {
+ DBG_ERR("tevent_add_signal failed\n");
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ BlockSignals(false, SIGTERM);
+
+ se = tevent_add_signal(
+ ev_ctx, ev_ctx, SIGHUP, 0, sig_hup_handler, NULL);
+ if (se == NULL) {
+ DBG_ERR("tevent_add_signal failed\n");
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ BlockSignals(false, SIGHUP);
+
+ (void)winbind_off();
+ ok = init_guest_session_info(NULL);
+ (void)winbind_on();
+ if (!ok) {
+ DBG_WARNING("init_guest_session_info failed\n");
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ status = init_system_session_info(NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("init_system_session_info failed: %s\n",
+ nt_errstr(status));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ DBG_INFO("Initializing DCE/RPC registered endpoint servers\n");
+
+ status = get_servers(dce_ctx,
+ &ep_servers,
+ &num_servers,
+ private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("get_servers failed: %s\n", nt_errstr(status));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ DBG_DEBUG("get_servers() returned %zu servers\n", num_servers);
+
+ for (i=0; i<num_servers; i++) {
+ status = register_ep_server(dce_ctx, ep_servers[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("register_ep_server failed: %s\n",
+ nt_errstr(status));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ }
+
+ req = rpc_worker_send(
+ ev_ctx, ev_ctx, worker, getppid(), worker_group, worker_index);
+ if (req == NULL) {
+ DBG_ERR("rpc_worker_send failed\n");
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ DBG_DEBUG("%s worker running\n", progname);
+
+ while (tevent_req_is_in_progress(req)) {
+ TALLOC_CTX *loop_frame = NULL;
+
+ loop_frame = talloc_stackframe();
+
+ ret = tevent_loop_once(ev_ctx);
+
+ TALLOC_FREE(loop_frame);
+
+ if (ret != 0) {
+ DBG_WARNING("tevent_req_once() failed: %s\n",
+ strerror(errno));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ }
+
+ status = dcesrv_shutdown_registered_ep_servers(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Shutdown failed with: %s\n",
+ nt_errstr(status));
+ }
+
+ ret = rpc_worker_recv(req);
+ if (ret != 0) {
+ DBG_DEBUG("rpc_worker_recv returned %s\n", strerror(ret));
+ global_messaging_context_free();
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/rpc_server/rpc_worker.h b/source3/rpc_server/rpc_worker.h
new file mode 100644
index 0000000..54cc53f
--- /dev/null
+++ b/source3/rpc_server/rpc_worker.h
@@ -0,0 +1,40 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __RPC_WORKER_H__
+#define __RPC_WORKER_H__
+
+#include "replace.h"
+#include "librpc/rpc/dcesrv_core.h"
+
+int rpc_worker_main(
+ int argc,
+ const char *argv[],
+ const char *daemon_config_name,
+ int num_workers,
+ int idle_seconds,
+ size_t (*get_interfaces)(
+ const struct ndr_interface_table ***ifaces,
+ void *private_data),
+ NTSTATUS (*get_servers)(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***ep_servers,
+ size_t *num_ep_servers,
+ void *private_data),
+ void *private_data);
+
+#endif /* __RPC_WORKER_H__ */
diff --git a/source3/rpc_server/rpcd_classic.c b/source3/rpc_server/rpcd_classic.c
new file mode 100644
index 0000000..2b7e939
--- /dev/null
+++ b/source3/rpc_server/rpcd_classic.c
@@ -0,0 +1,149 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_srvsvc.h"
+#include "librpc/gen_ndr/ndr_srvsvc_scompat.h"
+#include "librpc/gen_ndr/ndr_dfs.h"
+#include "librpc/gen_ndr/ndr_dfs_scompat.h"
+#include "librpc/gen_ndr/ndr_wkssvc.h"
+#include "librpc/gen_ndr/ndr_wkssvc_scompat.h"
+#include "librpc/gen_ndr/ndr_svcctl.h"
+#include "librpc/gen_ndr/ndr_svcctl_scompat.h"
+#include "librpc/gen_ndr/ndr_ntsvcs.h"
+#include "librpc/gen_ndr/ndr_ntsvcs_scompat.h"
+#include "librpc/gen_ndr/ndr_eventlog.h"
+#include "librpc/gen_ndr/ndr_eventlog_scompat.h"
+#include "librpc/gen_ndr/ndr_initshutdown.h"
+#include "librpc/gen_ndr/ndr_initshutdown_scompat.h"
+#include "source3/include/secrets.h"
+#include "locking/share_mode_lock.h"
+#include "source3/smbd/proto.h"
+
+static size_t classic_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_srvsvc,
+ &ndr_table_netdfs,
+ &ndr_table_initshutdown,
+ &ndr_table_svcctl,
+ &ndr_table_ntsvcs,
+ &ndr_table_eventlog,
+ /*
+ * This last item is truncated from the list by the
+ * num_ifaces -= 1 below. Take care when adding new
+ * services.
+ */
+ &ndr_table_wkssvc,
+ };
+ size_t num_ifaces = ARRAY_SIZE(ifaces);
+
+ switch(lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * On the AD DC wkssvc is provided by the 'samba'
+ * binary from source4/
+ */
+ num_ifaces -= 1;
+ break;
+ default:
+ break;
+ }
+
+ *pifaces = ifaces;
+ return num_ifaces;
+
+}
+
+static NTSTATUS classic_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[7] = { NULL };
+ size_t num_servers = ARRAY_SIZE(ep_servers);
+ NTSTATUS status;
+ bool ok;
+
+ ep_servers[0] = srvsvc_get_ep_server();
+ ep_servers[1] = netdfs_get_ep_server();
+ ep_servers[2] = initshutdown_get_ep_server();
+ ep_servers[3] = svcctl_get_ep_server();
+ ep_servers[4] = ntsvcs_get_ep_server();
+ ep_servers[5] = eventlog_get_ep_server();
+ ep_servers[6] = wkssvc_get_ep_server();
+
+ switch(lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * On the AD DC wkssvc is provided by the 'samba'
+ * binary from source4/
+ */
+ num_servers -= 1;
+ break;
+ default:
+ break;
+ }
+
+ ok = secrets_init();
+ if (!ok) {
+ DBG_ERR("secrets_init() failed\n");
+ exit(1);
+ }
+
+ ok = locking_init();
+ if (!ok) {
+ DBG_ERR("locking_init() failed\n");
+ exit(1);
+ }
+
+ status = share_info_db_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_info_db_init failed: %s\n", nt_errstr(status));
+ exit(1);
+ }
+
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ mangle_reset_cache();
+
+ status = dcesrv_register_default_auth_types_machine_principal(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = num_servers;
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_classic",
+ 5,
+ 60,
+ classic_interfaces,
+ classic_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_epmapper.c b/source3/rpc_server/rpcd_epmapper.c
new file mode 100644
index 0000000..9b2cc4f
--- /dev/null
+++ b/source3/rpc_server/rpcd_epmapper.c
@@ -0,0 +1,109 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_epmapper.h"
+#include "librpc/gen_ndr/ndr_epmapper_scompat.h"
+#include "param/loadparm.h"
+#include "libds/common/roles.h"
+
+static size_t epmapper_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_epmapper,
+ };
+ size_t num_ifaces = ARRAY_SIZE(ifaces);
+
+ switch(lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * On the AD DC epmapper is provided by the 'samba'
+ * binary from source4/
+ */
+ num_ifaces = 0;
+ break;
+ default:
+ break;
+ }
+
+ *pifaces = ifaces;
+ return num_ifaces;
+}
+
+static NTSTATUS epmapper_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[] = { NULL };
+ size_t num_servers = ARRAY_SIZE(ep_servers);
+ NTSTATUS status;
+
+ /*
+ * Windows Server 2022 registers the following auth_types
+ * all with an empty principal name:
+ *
+ * principle name for proto 9 (spnego) is ''
+ * principle name for proto 10 (ntlmssp) is ''
+ * principle name for proto 14 is ''
+ * principle name for proto 16 (gssapi_krb5) is ''
+ * principle name for proto 22 is ''
+ * principle name for proto 30 is ''
+ * principle name for proto 31 is ''
+ *
+ * We only register what we also support.
+ */
+ status = dcesrv_register_default_auth_types(dce_ctx, "");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ep_servers[0] = epmapper_get_ep_server();
+
+ switch(lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * On the AD DC epmapper is provided by the 'samba'
+ * binary from source4/
+ */
+ num_servers = 0;
+ break;
+ default:
+ break;
+ }
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = num_servers;
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_epmapper",
+ 1,
+ 10,
+ epmapper_interfaces,
+ epmapper_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_fsrvp.c b/source3/rpc_server/rpcd_fsrvp.c
new file mode 100644
index 0000000..f7db544
--- /dev/null
+++ b/source3/rpc_server/rpcd_fsrvp.c
@@ -0,0 +1,82 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_fsrvp.h"
+#include "librpc/gen_ndr/ndr_fsrvp_scompat.h"
+
+static size_t fsrvp_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_FileServerVssAgent,
+ };
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ /*
+ * For now, don't do shadow copies on the AD DC. This
+ * might change in the future, but there's a
+ * recommendation to split DCs from file servers.
+ *
+ * But then we need to put the snap logic into the ad
+ * dc testenv's smb.conf.
+ */
+ *pifaces = NULL;
+ return 0;
+ }
+
+ *pifaces = ifaces;
+ return ARRAY_SIZE(ifaces);
+}
+
+static NTSTATUS fsrvp_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL };
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ *_ep_servers = NULL;
+ *_num_ep_servers = 0;
+ return NT_STATUS_OK;
+ }
+
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ ep_servers[0] = FileServerVssAgent_get_ep_server();
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = ARRAY_SIZE(ep_servers);
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_fsrvp",
+ 5,
+ 60,
+ fsrvp_interfaces,
+ fsrvp_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_lsad.c b/source3/rpc_server/rpcd_lsad.c
new file mode 100644
index 0000000..d00f704
--- /dev/null
+++ b/source3/rpc_server/rpcd_lsad.c
@@ -0,0 +1,141 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_lsa.h"
+#include "librpc/gen_ndr/ndr_lsa_scompat.h"
+#include "librpc/gen_ndr/ndr_samr.h"
+#include "librpc/gen_ndr/ndr_samr_scompat.h"
+#include "librpc/gen_ndr/ndr_netlogon.h"
+#include "librpc/gen_ndr/ndr_netlogon_scompat.h"
+#include "librpc/gen_ndr/ndr_dssetup.h"
+#include "librpc/gen_ndr/ndr_dssetup_scompat.h"
+#include "source3/include/auth.h"
+#include "source3/include/secrets.h"
+
+static size_t lsad_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_lsarpc,
+ &ndr_table_samr,
+ &ndr_table_dssetup,
+ /*
+ * This last item is truncated from the list by the
+ * num_ifaces -= 1 below for the fileserver. Take
+ * care when adding new services.
+ */
+ &ndr_table_netlogon,
+ };
+ size_t num_ifaces = ARRAY_SIZE(ifaces);
+
+ switch(lp_server_role()) {
+ case ROLE_STANDALONE:
+ case ROLE_DOMAIN_MEMBER:
+ /* no netlogon for non-dc */
+ num_ifaces -= 1;
+ break;
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * All these services are provided by the 'samba'
+ * binary from source4, not this code which is the
+ * source3 / NT4-like "classic" DC implementation
+ */
+ num_ifaces = 0;
+ break;
+ default:
+ break;
+ }
+
+ *pifaces = ifaces;
+ return num_ifaces;
+}
+
+static NTSTATUS lsad_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[4] = { NULL, };
+ size_t num_servers = ARRAY_SIZE(ep_servers);
+ NTSTATUS status;
+ bool ok;
+
+ ep_servers[0] = lsarpc_get_ep_server();
+ ep_servers[1] = samr_get_ep_server();
+ ep_servers[2] = dssetup_get_ep_server();
+ ep_servers[3] = netlogon_get_ep_server();
+
+ ok = secrets_init();
+ if (!ok) {
+ DBG_ERR("secrets_init() failed\n");
+ exit(1);
+ }
+
+ status = dcesrv_register_default_auth_types_machine_principal(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ switch(lp_server_role()) {
+ case ROLE_STANDALONE:
+ case ROLE_DOMAIN_MEMBER:
+ /* no netlogon for non-dc */
+ num_servers -= 1;
+ break;
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * All these services are provided by the 'samba'
+ * binary from source4, not this code which is the
+ * source3 / NT4-like "classic" DC implementation
+ */
+ num_servers = 0;
+ break;
+ default:
+ /*
+ * As DC we also register schannel with an
+ * empty principal
+ */
+ status = dcesrv_auth_type_principal_register(dce_ctx,
+ DCERPC_AUTH_TYPE_SCHANNEL,
+ "");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ }
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = num_servers;
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_lsad",
+ 5,
+ 60,
+ lsad_interfaces,
+ lsad_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_mdssvc.c b/source3/rpc_server/rpcd_mdssvc.c
new file mode 100644
index 0000000..c872aa6
--- /dev/null
+++ b/source3/rpc_server/rpcd_mdssvc.c
@@ -0,0 +1,71 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "source3/locking/proto.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_mdssvc.h"
+#include "librpc/gen_ndr/ndr_mdssvc_scompat.h"
+
+static size_t mdssvc_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_mdssvc,
+ };
+
+ *pifaces = ifaces;
+ return ARRAY_SIZE(ifaces);
+}
+
+static NTSTATUS mdssvc_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL };
+ bool ok;
+
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ ok = posix_locking_init(false);
+ if (!ok) {
+ DBG_ERR("posix_locking_init() failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ep_servers[0] = mdssvc_get_ep_server();
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = ARRAY_SIZE(ep_servers);
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_mdssvc",
+ 5,
+ 60,
+ mdssvc_interfaces,
+ mdssvc_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_rpcecho.c b/source3/rpc_server/rpcd_rpcecho.c
new file mode 100644
index 0000000..5466663
--- /dev/null
+++ b/source3/rpc_server/rpcd_rpcecho.c
@@ -0,0 +1,89 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_echo.h"
+#include "librpc/gen_ndr/ndr_echo_scompat.h"
+#include "param/loadparm.h"
+#include "libds/common/roles.h"
+
+static size_t rpcecho_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_rpcecho,
+ };
+ size_t num_ifaces = ARRAY_SIZE(ifaces);
+
+ switch(lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * On the AD DC rpcecho is provided by the 'samba'
+ * binary from source4/
+ */
+ num_ifaces = 0;
+ break;
+ default:
+ break;
+ }
+
+ *pifaces = ifaces;
+ return num_ifaces;
+}
+
+static NTSTATUS rpcecho_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL };
+ size_t num_servers = ARRAY_SIZE(ep_servers);
+
+ ep_servers[0] = rpcecho_get_ep_server();
+
+ switch(lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ /*
+ * On the AD DC rpcecho is provided by the 'samba'
+ * binary from source4/
+ */
+ num_servers = 0;
+ break;
+ default:
+ break;
+ }
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = num_servers;
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_rpcecho",
+ 1,
+ 1,
+ rpcecho_interfaces,
+ rpcecho_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_spoolss.c b/source3/rpc_server/rpcd_spoolss.c
new file mode 100644
index 0000000..43c7d53
--- /dev/null
+++ b/source3/rpc_server/rpcd_spoolss.c
@@ -0,0 +1,91 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_worker.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_spoolss.h"
+#include "librpc/gen_ndr/ndr_spoolss_scompat.h"
+#include "source3/locking/share_mode_lock.h"
+#include "source3/printing/queue_process.h"
+#include "source3/include/messages.h"
+#include "source3/include/secrets.h"
+#include "source3/smbd/proto.h"
+
+static size_t spoolss_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_spoolss,
+ };
+ *pifaces = ifaces;
+ return ARRAY_SIZE(ifaces);
+}
+
+static NTSTATUS spoolss_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL };
+ struct messaging_context *msg_ctx = global_messaging_context();
+ struct tevent_context *ev_ctx = messaging_tevent_context(msg_ctx);
+ bool ok;
+
+ ep_servers[0] = spoolss_get_ep_server();
+
+ ok = secrets_init();
+ if (!ok) {
+ DBG_ERR("secrets_init() failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ok = locking_init();
+ if (!ok) {
+ DBG_ERR("locking_init() failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ ok = printing_subsystem_init(ev_ctx, msg_ctx, dce_ctx);
+ if (!ok) {
+ DBG_ERR("printing_subsystem_init() failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ mangle_reset_cache();
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = ARRAY_SIZE(ep_servers);
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_spoolss",
+ 5,
+ 60,
+ spoolss_interfaces,
+ spoolss_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_winreg.c b/source3/rpc_server/rpcd_winreg.c
new file mode 100644
index 0000000..44715d8
--- /dev/null
+++ b/source3/rpc_server/rpcd_winreg.c
@@ -0,0 +1,71 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_winreg.h"
+#include "librpc/gen_ndr/ndr_winreg_scompat.h"
+#include "source3/registry/reg_init_full.h"
+
+static size_t winreg_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_winreg,
+ };
+ *pifaces = ifaces;
+ return ARRAY_SIZE(ifaces);
+}
+
+static NTSTATUS winreg_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL };
+ WERROR werr;
+
+ ep_servers[0] = winreg_get_ep_server();
+
+ werr = registry_init_full();
+ if (!W_ERROR_IS_OK(werr)) {
+ DBG_ERR("registry_init_full() failed: %s\n",
+ win_errstr(werr));
+ return werror_to_ntstatus(werr);
+ }
+
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = ARRAY_SIZE(ep_servers);
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_winreg",
+ 5,
+ 60,
+ winreg_interfaces,
+ winreg_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/rpcd_witness.c b/source3/rpc_server/rpcd_witness.c
new file mode 100644
index 0000000..9dcf180
--- /dev/null
+++ b/source3/rpc_server/rpcd_witness.c
@@ -0,0 +1,120 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) 2023 Stefan Metzmacher
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_worker.h"
+#include "librpc/gen_ndr/ndr_witness.h"
+#include "librpc/gen_ndr/ndr_witness_scompat.h"
+
+static size_t witness_interfaces(
+ const struct ndr_interface_table ***pifaces,
+ void *private_data)
+{
+ static const struct ndr_interface_table *ifaces[] = {
+ &ndr_table_witness,
+ };
+
+ if (!lp_clustering()) {
+ /*
+ * Without clustering there's no need for witness.
+ */
+ *pifaces = NULL;
+ return 0;
+ }
+
+ *pifaces = ifaces;
+ return ARRAY_SIZE(ifaces);
+}
+
+static NTSTATUS witness_servers(
+ struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server ***_ep_servers,
+ size_t *_num_ep_servers,
+ void *private_data)
+{
+ static const struct dcesrv_endpoint_server *ep_servers[1] = { NULL };
+ char *principal = NULL;
+ NTSTATUS status;
+
+ if (!lp_clustering()) {
+ /*
+ * Without clustering there's no need for witness.
+ */
+ *_ep_servers = NULL;
+ *_num_ep_servers = 0;
+ return NT_STATUS_OK;
+ }
+
+ principal = talloc_asprintf(talloc_tos(),
+ "cifs/%s",
+ lp_netbios_name());
+ if (principal == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dcesrv_auth_type_principal_register(dce_ctx,
+ DCERPC_AUTH_TYPE_NTLMSSP,
+ principal);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = dcesrv_auth_type_principal_register(dce_ctx,
+ DCERPC_AUTH_TYPE_SPNEGO,
+ principal);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (lp_security() == SEC_ADS) {
+ status = dcesrv_auth_type_principal_register(dce_ctx,
+ DCERPC_AUTH_TYPE_KRB5,
+ principal);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ TALLOC_FREE(principal);
+
+ /*
+ * We prefer NDR64 for witness,
+ * as it's a very simple protocol and
+ * we support it from the beginning,
+ * which means it's well tested.
+ */
+ dce_ctx->preferred_transfer = &ndr_transfer_syntax_ndr64;
+
+ ep_servers[0] = witness_get_ep_server();
+
+ *_ep_servers = ep_servers;
+ *_num_ep_servers = ARRAY_SIZE(ep_servers);
+ return NT_STATUS_OK;
+}
+
+int main(int argc, const char *argv[])
+{
+ return rpc_worker_main(
+ argc,
+ argv,
+ "rpcd_witness",
+ 5,
+ 60,
+ witness_interfaces,
+ witness_servers,
+ NULL);
+}
diff --git a/source3/rpc_server/samr/srv_samr_chgpasswd.c b/source3/rpc_server/samr/srv_samr_chgpasswd.c
new file mode 100644
index 0000000..5fcc08e
--- /dev/null
+++ b/source3/rpc_server/samr/srv_samr_chgpasswd.c
@@ -0,0 +1,1421 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001-2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* These comments regard the code to change the user's unix password: */
+
+/* fork a child process to exec passwd and write to its
+ * tty to change a users password. This is running as the
+ * user who is attempting to change the password.
+ */
+
+/*
+ * This code was copied/borrowed and stolen from various sources.
+ * The primary source was the poppasswd.c from the authors of POPMail. This software
+ * was included as a client to change passwords using the 'passwd' program
+ * on the remote machine.
+ *
+ * This code has been hacked by Bob Nance (nance@niehs.nih.gov) and Evan Patterson
+ * (patters2@niehs.nih.gov) at the National Institute of Environmental Health Sciences
+ * and rights to modify, distribute or incorporate this change to the CAP suite or
+ * using it for any other reason are granted, so long as this disclaimer is left intact.
+ */
+
+/*
+ This code was hacked considerably for inclusion in Samba, primarily
+ by Andrew.Tridgell@anu.edu.au. The biggest change was the addition
+ of the "password chat" option, which allows the easy runtime
+ specification of the expected sequence of events to change a
+ password.
+ */
+
+#include "includes.h"
+#include "system/terminal.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "rpc_server/samr/srv_samr_util.h"
+#include "passdb.h"
+#include "auth.h"
+#include "lib/util/sys_rw.h"
+#include "librpc/rpc/dcerpc_samr.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#ifndef ALLOW_CHANGE_PASSWORD
+#if (defined(HAVE_TERMIOS_H) && defined(HAVE_DUP2) && defined(HAVE_SETSID))
+#define ALLOW_CHANGE_PASSWORD 1
+#endif
+#endif
+
+#if ALLOW_CHANGE_PASSWORD
+
+static int findpty(char **slave)
+{
+ int master = -1;
+ char *line = NULL;
+ DIR *dirp = NULL;
+ const char *dpname;
+
+ *slave = NULL;
+
+#if defined(HAVE_GRANTPT)
+#if defined(HAVE_POSIX_OPENPT)
+ master = posix_openpt(O_RDWR|O_NOCTTY);
+#else
+ /* Try to open /dev/ptmx. If that fails, fall through to old method. */
+ master = open("/dev/ptmx", O_RDWR, 0);
+#endif
+ if (master >= 0) {
+ grantpt(master);
+ unlockpt(master);
+ line = (char *)ptsname(master);
+ if (line) {
+ *slave = SMB_STRDUP(line);
+ }
+
+ if (*slave == NULL) {
+ DEBUG(0,
+ ("findpty: Unable to create master/slave pty pair.\n"));
+ /* Stop fd leak on error. */
+ close(master);
+ return -1;
+ } else {
+ DEBUG(10,
+ ("findpty: Allocated slave pty %s\n", *slave));
+ return (master);
+ }
+ }
+#endif /* HAVE_GRANTPT */
+
+ line = SMB_STRDUP("/dev/ptyXX");
+ if (!line) {
+ return (-1);
+ }
+
+ dirp = opendir("/dev");
+ if (!dirp) {
+ SAFE_FREE(line);
+ return (-1);
+ }
+
+ while ((dpname = readdirname(dirp)) != NULL) {
+ if (strncmp(dpname, "pty", 3) == 0 && strlen(dpname) == 5) {
+ DEBUG(3,
+ ("pty: try to open %s, line was %s\n", dpname,
+ line));
+ line[8] = dpname[3];
+ line[9] = dpname[4];
+ if ((master = open(line, O_RDWR, 0)) >= 0) {
+ DEBUG(3, ("pty: opened %s\n", line));
+ line[5] = 't';
+ *slave = line;
+ closedir(dirp);
+ return (master);
+ }
+ }
+ }
+ closedir(dirp);
+ SAFE_FREE(line);
+ return (-1);
+}
+
+static int dochild(int master, const char *slavedev, const struct passwd *pass,
+ const char *passwordprogram, bool as_root)
+{
+ int slave;
+ struct termios stermios;
+ gid_t gid;
+ uid_t uid;
+ char * const eptrs[1] = { NULL };
+
+ if (pass == NULL)
+ {
+ DEBUG(0,
+ ("dochild: user doesn't exist in the UNIX password database.\n"));
+ return False;
+ }
+
+ gid = pass->pw_gid;
+ uid = pass->pw_uid;
+
+ gain_root_privilege();
+
+ /* Start new session - gets rid of controlling terminal. */
+ if (setsid() < 0)
+ {
+ DEBUG(3,
+ ("Weirdness, couldn't let go of controlling terminal\n"));
+ return (False);
+ }
+
+ /* Open slave pty and acquire as new controlling terminal. */
+ if ((slave = open(slavedev, O_RDWR, 0)) < 0)
+ {
+ DEBUG(3, ("More weirdness, could not open %s\n", slavedev));
+ return (False);
+ }
+#if defined(TIOCSCTTY) && !defined(SUNOS5)
+ /*
+ * On patched Solaris 10 TIOCSCTTY is defined but seems not to work,
+ * see the discussion under
+ * https://bugzilla.samba.org/show_bug.cgi?id=5366.
+ */
+ if (ioctl(slave, TIOCSCTTY, 0) < 0)
+ {
+ DEBUG(3, ("Error in ioctl call for slave pty\n"));
+ /* return(False); */
+ }
+#elif defined(I_PUSH) && defined(I_FIND)
+ if (ioctl(slave, I_FIND, "ptem") == 0) {
+ ioctl(slave, I_PUSH, "ptem");
+ }
+ if (ioctl(slave, I_FIND, "ldterm") == 0) {
+ ioctl(slave, I_PUSH, "ldterm");
+ }
+#endif
+
+ /* Close master. */
+ close(master);
+
+ /* Make slave stdin/out/err of child. */
+
+ if (dup2(slave, STDIN_FILENO) != STDIN_FILENO)
+ {
+ DEBUG(3, ("Could not re-direct stdin\n"));
+ return (False);
+ }
+ if (dup2(slave, STDOUT_FILENO) != STDOUT_FILENO)
+ {
+ DEBUG(3, ("Could not re-direct stdout\n"));
+ return (False);
+ }
+ if (dup2(slave, STDERR_FILENO) != STDERR_FILENO)
+ {
+ DEBUG(3, ("Could not re-direct stderr\n"));
+ return (False);
+ }
+ if (slave > 2)
+ close(slave);
+
+ /* Set proper terminal attributes - no echo, canonical input processing,
+ no map NL to CR/NL on output. */
+
+ if (tcgetattr(0, &stermios) < 0)
+ {
+ DEBUG(3,
+ ("could not read default terminal attributes on pty\n"));
+ return (False);
+ }
+ stermios.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+ stermios.c_lflag |= ICANON;
+#ifdef ONLCR
+ stermios.c_oflag &= ~(ONLCR);
+#endif
+ if (tcsetattr(0, TCSANOW, &stermios) < 0)
+ {
+ DEBUG(3, ("could not set attributes of pty\n"));
+ return (False);
+ }
+
+ /* make us completely into the right uid */
+ if (!as_root)
+ {
+ become_user_permanently(uid, gid);
+ }
+
+ DEBUG(10,
+ ("Invoking '%s' as password change program.\n",
+ passwordprogram));
+
+ /* execl() password-change application */
+ if (execle("/bin/sh", "sh", "-c", passwordprogram, NULL, eptrs) < 0)
+ {
+ DEBUG(3, ("Bad status returned from %s\n", passwordprogram));
+ return (False);
+ }
+ return (True);
+}
+
+static int expect(int master, char *issue, char *expected)
+{
+ char buffer[1024];
+ int attempts, timeout, nread;
+ size_t len;
+ bool match = False;
+
+ for (attempts = 0; attempts < 2; attempts++) {
+ NTSTATUS status;
+ if (!strequal(issue, ".")) {
+ if (lp_passwd_chat_debug())
+ DEBUG(100, ("expect: sending [%s]\n", issue));
+
+ if ((len = sys_write(master, issue, strlen(issue))) != strlen(issue)) {
+ DEBUG(2,("expect: (short) write returned %d\n",
+ (int)len ));
+ return False;
+ }
+ }
+
+ if (strequal(expected, "."))
+ return True;
+
+ /* Initial timeout. */
+ timeout = lp_passwd_chat_timeout() * 1000;
+ nread = 0;
+ buffer[nread] = 0;
+
+ while (True) {
+ status = read_fd_with_timeout(
+ master, buffer + nread, 1,
+ sizeof(buffer) - nread - 1,
+ timeout, &len);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("expect: read error %s\n",
+ nt_errstr(status)));
+ break;
+ }
+ nread += len;
+ buffer[nread] = 0;
+
+ {
+ /* Eat leading/trailing whitespace before match. */
+ char *str = SMB_STRDUP(buffer);
+ if (!str) {
+ DEBUG(2,("expect: ENOMEM\n"));
+ return False;
+ }
+ trim_char(str, ' ', ' ');
+
+ if ((match = unix_wild_match(expected, str)) == True) {
+ /* Now data has started to return, lower timeout. */
+ timeout = lp_passwd_chat_timeout() * 100;
+ }
+ SAFE_FREE(str);
+ }
+ }
+
+ if (lp_passwd_chat_debug())
+ DEBUG(100, ("expect: expected [%s] received [%s] match %s\n",
+ expected, buffer, match ? "yes" : "no" ));
+
+ if (match)
+ break;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("expect: %s\n", nt_errstr(status)));
+ return False;
+ }
+ }
+
+ DEBUG(10,("expect: returning %s\n", match ? "True" : "False" ));
+ return match;
+}
+
+static void pwd_sub(char *buf)
+{
+ all_string_sub(buf, "\\n", "\n", 0);
+ all_string_sub(buf, "\\r", "\r", 0);
+ all_string_sub(buf, "\\s", " ", 0);
+ all_string_sub(buf, "\\t", "\t", 0);
+}
+
+static int talktochild(int master, const char *seq)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int count = 0;
+ char *issue;
+ char *expected;
+
+ issue = talloc_strdup(frame, ".");
+ if (!issue) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ while (next_token_talloc(frame, &seq, &expected, NULL)) {
+ pwd_sub(expected);
+ count++;
+
+ if (!expect(master, issue, expected)) {
+ DEBUG(3, ("Response %d incorrect\n", count));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (!next_token_talloc(frame, &seq, &issue, NULL)) {
+ issue = talloc_strdup(frame, ".");
+ if (!issue) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ }
+ pwd_sub(issue);
+ }
+
+ if (!strequal(issue, ".")) {
+ /* we have one final issue to send */
+ expected = talloc_strdup(frame, ".");
+ if (!expected) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+ if (!expect(master, issue, expected)) {
+ TALLOC_FREE(frame);
+ return False;
+ }
+ }
+ TALLOC_FREE(frame);
+ return (count > 0);
+}
+
+static bool chat_with_program(char *passwordprogram, const struct passwd *pass,
+ char *chatsequence, bool as_root)
+{
+ char *slavedev = NULL;
+ int master;
+ pid_t pid, wpid;
+ int wstat;
+ bool chstat = False;
+ void (*saved_handler)(int);
+
+ if (pass == NULL) {
+ DEBUG(0, ("chat_with_program: user doesn't exist in the UNIX password database.\n"));
+ return False;
+ }
+
+ /* allocate a pseudo-terminal device */
+ if ((master = findpty(&slavedev)) < 0) {
+ DEBUG(3, ("chat_with_program: Cannot Allocate pty for password change: %s\n", pass->pw_name));
+ return (False);
+ }
+
+ /*
+ * We need to temporarily stop CatchChild from eating
+ * SIGCLD signals as it also eats the exit status code. JRA.
+ */
+
+ saved_handler = CatchChildLeaveStatus();
+
+ if ((pid = fork()) < 0) {
+ DEBUG(3, ("chat_with_program: Cannot fork() child for password change: %s\n", pass->pw_name));
+ SAFE_FREE(slavedev);
+ close(master);
+ (void)CatchSignal(SIGCLD, saved_handler);
+ return (False);
+ }
+
+ /* we now have a pty */
+ if (pid > 0) { /* This is the parent process */
+ /* Don't need this anymore in parent. */
+ SAFE_FREE(slavedev);
+
+ if ((chstat = talktochild(master, chatsequence)) == False) {
+ DEBUG(3, ("chat_with_program: Child failed to change password: %s\n", pass->pw_name));
+ kill(pid, SIGKILL); /* be sure to end this process */
+ }
+
+ while ((wpid = waitpid(pid, &wstat, 0)) < 0) {
+ if (errno == EINTR) {
+ errno = 0;
+ continue;
+ }
+ break;
+ }
+
+ if (wpid < 0) {
+ DEBUG(3, ("chat_with_program: The process is no longer waiting!\n\n"));
+ close(master);
+ (void)CatchSignal(SIGCLD, saved_handler);
+ return (False);
+ }
+
+ /*
+ * Go back to ignoring children.
+ */
+ (void)CatchSignal(SIGCLD, saved_handler);
+
+ close(master);
+
+ if (pid != wpid) {
+ DEBUG(3, ("chat_with_program: We were waiting for the wrong process ID\n"));
+ return (False);
+ }
+ if (WIFEXITED(wstat) && (WEXITSTATUS(wstat) != 0)) {
+ DEBUG(3, ("chat_with_program: The process exited with status %d \
+while we were waiting\n", WEXITSTATUS(wstat)));
+ return (False);
+ }
+#if defined(WIFSIGNALLED) && defined(WTERMSIG)
+ else if (WIFSIGNALLED(wstat)) {
+ DEBUG(3, ("chat_with_program: The process was killed by signal %d \
+while we were waiting\n", WTERMSIG(wstat)));
+ return (False);
+ }
+#endif
+ } else {
+ /* CHILD */
+
+ /*
+ * Lose any elevated privileges.
+ */
+ drop_effective_capability(KERNEL_OPLOCK_CAPABILITY);
+ drop_effective_capability(DMAPI_ACCESS_CAPABILITY);
+
+ /* make sure it doesn't freeze */
+ alarm(20);
+
+ if (as_root)
+ become_root();
+
+ DEBUG(3, ("chat_with_program: Dochild for user %s (uid=%d,gid=%d) (as_root = %s)\n", pass->pw_name,
+ (int)getuid(), (int)getgid(), BOOLSTR(as_root) ));
+ chstat = dochild(master, slavedev, pass, passwordprogram, as_root);
+
+ if (as_root)
+ unbecome_root();
+
+ /*
+ * The child should never return from dochild() ....
+ */
+
+ DEBUG(0, ("chat_with_program: Error: dochild() returned %d\n", chstat));
+ exit(1);
+ }
+
+ if (chstat)
+ DEBUG(3, ("chat_with_program: Password change %ssuccessful for user %s\n",
+ (chstat ? "" : "un"), pass->pw_name));
+ return (chstat);
+}
+
+bool chgpasswd(const char *name, const char *rhost, const struct passwd *pass,
+ const char *oldpass, const char *newpass, bool as_root)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *passwordprogram = NULL;
+ char *chatsequence = NULL;
+ size_t i;
+ size_t len;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!oldpass) {
+ oldpass = "";
+ }
+
+ DEBUG(3, ("chgpasswd: Password change (as_root=%s) for user: %s\n", BOOLSTR(as_root), name));
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100, ("chgpasswd: Passwords: old=%s new=%s\n", oldpass, newpass));
+#endif
+
+ /* Take the passed information and test it for minimum criteria */
+
+ /* Password is same as old password */
+ if (strcmp(oldpass, newpass) == 0) {
+ /* don't allow same password */
+ DEBUG(2, ("chgpasswd: Password Change: %s, New password is same as old\n", name)); /* log the attempt */
+ return (False); /* inform the user */
+ }
+
+ /*
+ * Check the old and new passwords don't contain any control
+ * characters.
+ */
+
+ len = strlen(oldpass);
+ for (i = 0; i < len; i++) {
+ if (iscntrl((int)oldpass[i])) {
+ DEBUG(0, ("chgpasswd: oldpass contains control characters (disallowed).\n"));
+ return False;
+ }
+ }
+
+ len = strlen(newpass);
+ for (i = 0; i < len; i++) {
+ if (iscntrl((int)newpass[i])) {
+ DEBUG(0, ("chgpasswd: newpass contains control characters (disallowed).\n"));
+ return False;
+ }
+ }
+
+#ifdef WITH_PAM
+ if (lp_pam_password_change()) {
+ bool ret;
+#ifdef HAVE_SETLOCALE
+ const char *prevlocale = setlocale(LC_ALL, "C");
+#endif
+
+ if (as_root)
+ become_root();
+
+ if (pass) {
+ ret = smb_pam_passchange(pass->pw_name, rhost,
+ oldpass, newpass);
+ } else {
+ ret = smb_pam_passchange(name, rhost, oldpass,
+ newpass);
+ }
+
+ if (as_root)
+ unbecome_root();
+
+#ifdef HAVE_SETLOCALE
+ setlocale(LC_ALL, prevlocale);
+#endif
+
+ return ret;
+ }
+#endif
+
+ /* A non-PAM password change just doesn't make sense without a valid local user */
+
+ if (pass == NULL) {
+ DEBUG(0, ("chgpasswd: user %s doesn't exist in the UNIX password database.\n", name));
+ return false;
+ }
+
+ passwordprogram = lp_passwd_program(ctx, lp_sub);
+ if (!passwordprogram || !*passwordprogram) {
+ DEBUG(2, ("chgpasswd: Null password program - no password changing\n"));
+ return false;
+ }
+ chatsequence = lp_passwd_chat(ctx, lp_sub);
+ if (!chatsequence || !*chatsequence) {
+ DEBUG(2, ("chgpasswd: Null chat sequence - no password changing\n"));
+ return false;
+ }
+
+ if (as_root) {
+ /* The password program *must* contain the user name to work. Fail if not. */
+ if (strstr_m(passwordprogram, "%u") == NULL) {
+ DEBUG(0,("chgpasswd: Running as root the 'passwd program' parameter *MUST* contain \
+the string %%u, and the given string %s does not.\n", passwordprogram ));
+ return false;
+ }
+ }
+
+ passwordprogram = talloc_string_sub(ctx, passwordprogram, "%u", name);
+ if (!passwordprogram) {
+ return false;
+ }
+
+ /* note that we do NOT substitute the %o and %n in the password program
+ as this would open up a security hole where the user could use
+ a new password containing shell escape characters */
+
+ chatsequence = talloc_string_sub(ctx, chatsequence, "%u", name);
+ if (!chatsequence) {
+ return false;
+ }
+ chatsequence = talloc_all_string_sub(ctx,
+ chatsequence,
+ "%o",
+ oldpass);
+ if (!chatsequence) {
+ return false;
+ }
+ chatsequence = talloc_all_string_sub(ctx,
+ chatsequence,
+ "%n",
+ newpass);
+ if (chatsequence == NULL) {
+ return false;
+ }
+ return chat_with_program(passwordprogram,
+ pass,
+ chatsequence,
+ as_root);
+}
+
+#else /* ALLOW_CHANGE_PASSWORD */
+
+bool chgpasswd(const char *name, const struct passwd *pass,
+ const char *oldpass, const char *newpass, bool as_root)
+{
+ DEBUG(0, ("chgpasswd: Unix Password changing not compiled in (user=%s)\n", name));
+ return (False);
+}
+#endif /* ALLOW_CHANGE_PASSWORD */
+
+/***********************************************************
+ Decrypt and verify a user password change.
+
+ The 516 byte long buffers are encrypted with the old NT and
+ old LM passwords, and if the NT passwords are present, both
+ buffers contain a unicode string.
+
+ After decrypting the buffers, check the password is correct by
+ matching the old hashed passwords with the passwords in the passdb.
+
+************************************************************/
+
+static NTSTATUS check_oem_password(const char *user,
+ uchar password_encrypted_with_lm_hash[516],
+ const uchar old_lm_hash_encrypted[16],
+ uchar password_encrypted_with_nt_hash[516],
+ const uchar old_nt_hash_encrypted[16],
+ struct samu *sampass,
+ char **pp_new_passwd)
+{
+ uchar null_pw[16];
+ uchar null_ntpw[16];
+ uint8_t *password_encrypted;
+ const uint8_t *encryption_key;
+ const uint8_t *lanman_pw, *nt_pw;
+ uint32_t acct_ctrl;
+ size_t new_pw_len;
+ uchar new_nt_hash[16];
+ uchar new_lm_hash[16];
+ uchar verifier[16];
+ char no_pw[2];
+
+ bool nt_pass_set = (password_encrypted_with_nt_hash && old_nt_hash_encrypted);
+ bool lm_pass_set = (password_encrypted_with_lm_hash && old_lm_hash_encrypted);
+ enum ntlm_auth_level ntlm_auth_level = lp_ntlm_auth();
+
+ gnutls_cipher_hd_t cipher_hnd = NULL;
+ gnutls_datum_t enc_key;
+ int rc;
+
+ /* this call should be disabled without NTLM auth */
+ if (ntlm_auth_level == NTLM_AUTH_DISABLED) {
+ DBG_WARNING("NTLM password changes not"
+ "permitted by configuration.\n");
+ return NT_STATUS_NTLM_BLOCKED;
+ }
+
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+#if 0
+ /* I am convinced this check here is wrong, it is valid to
+ * change a password of a user that has a disabled account - gd */
+
+ if (acct_ctrl & ACB_DISABLED) {
+ DEBUG(2,("check_lanman_password: account %s disabled.\n", user));
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+#endif
+ if ((acct_ctrl & ACB_PWNOTREQ) && lp_null_passwords()) {
+ /* construct a null password (in case one is needed */
+ no_pw[0] = 0;
+ no_pw[1] = 0;
+ nt_lm_owf_gen(no_pw, null_ntpw, null_pw);
+ lanman_pw = null_pw;
+ nt_pw = null_pw;
+
+ } else {
+ /* save pointers to passwords so we don't have to keep looking them up */
+ if (lp_lanman_auth()) {
+ lanman_pw = pdb_get_lanman_passwd(sampass);
+ } else {
+ lanman_pw = NULL;
+ }
+ nt_pw = pdb_get_nt_passwd(sampass);
+ }
+
+ if (nt_pw && nt_pass_set) {
+ /* IDEAL Case: passwords are in unicode, and we can
+ * read use the password encrypted with the NT hash
+ */
+ password_encrypted = password_encrypted_with_nt_hash;
+ encryption_key = nt_pw;
+ } else if (lanman_pw && lm_pass_set) {
+ /* password may still be in unicode, but use LM hash version */
+ password_encrypted = password_encrypted_with_lm_hash;
+ encryption_key = lanman_pw;
+ } else if (nt_pass_set) {
+ DEBUG(1, ("NT password change supplied for user %s, but we have no NT password to check it with\n",
+ user));
+ return NT_STATUS_WRONG_PASSWORD;
+ } else if (lm_pass_set) {
+ if (lp_lanman_auth()) {
+ DEBUG(1, ("LM password change supplied for user %s, but we have no LanMan password to check it with\n",
+ user));
+ } else {
+ DEBUG(1, ("LM password change supplied for user %s, but we have disabled LanMan authentication\n",
+ user));
+ }
+ return NT_STATUS_WRONG_PASSWORD;
+ } else {
+ DEBUG(1, ("password change requested for user %s, but no password supplied!\n",
+ user));
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * Decrypt the password with the key
+ */
+ enc_key = (gnutls_datum_t) {
+ .data = discard_const_p(unsigned char, encryption_key),
+ .size = 16,
+ };
+
+ GNUTLS_FIPS140_SET_LAX_MODE();
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &enc_key,
+ NULL);
+ if (rc < 0) {
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ }
+
+ rc = gnutls_cipher_decrypt(cipher_hnd,
+ password_encrypted,
+ 516);
+ gnutls_cipher_deinit(cipher_hnd);
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ }
+
+ if (!decode_pw_buffer(talloc_tos(),
+ password_encrypted,
+ pp_new_passwd,
+ &new_pw_len,
+ nt_pass_set ? CH_UTF16 : CH_DOS)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /*
+ * To ensure we got the correct new password, hash it and
+ * use it as a key to test the passed old password.
+ */
+
+ if (nt_pass_set) {
+ /* NT passwords, verify the NT hash. */
+
+ /* Calculate the MD4 hash (NT compatible) of the password */
+ memset(new_nt_hash, '\0', 16);
+ E_md4hash(*pp_new_passwd, new_nt_hash);
+
+ if (nt_pw) {
+ /*
+ * check the NT verifier
+ */
+ rc = E_old_pw_hash(new_nt_hash, nt_pw, verifier);
+ if (rc != 0) {
+ NTSTATUS status = NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER;
+ return gnutls_error_to_ntstatus(rc, status);
+ }
+ if (!mem_equal_const_time(verifier, old_nt_hash_encrypted, 16)) {
+ DEBUG(0, ("check_oem_password: old nt "
+ "password doesn't match.\n"));
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ /* We could check the LM password here, but there is
+ * little point, we already know the password is
+ * correct, and the LM password might not even be
+ * present. */
+
+ /* Further, LM hash generation algorithms
+ * differ with charset, so we could
+ * incorrectly fail a perfectly valid password
+ * change */
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,
+ ("check_oem_password: password %s ok\n", *pp_new_passwd));
+#endif
+ return NT_STATUS_OK;
+ }
+
+ if (lanman_pw) {
+ /*
+ * check the lm verifier
+ */
+ rc = E_old_pw_hash(new_nt_hash, lanman_pw, verifier);
+ if (rc != 0) {
+ NTSTATUS status = NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER;
+ return gnutls_error_to_ntstatus(rc, status);
+ }
+ if (!mem_equal_const_time(verifier, old_lm_hash_encrypted, 16)) {
+ DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,
+ ("check_oem_password: password %s ok\n", *pp_new_passwd));
+#endif
+ return NT_STATUS_OK;
+ }
+ }
+
+ if (lanman_pw && lm_pass_set) {
+
+ E_deshash(*pp_new_passwd, new_lm_hash);
+
+ /*
+ * check the lm verifier
+ */
+ rc = E_old_pw_hash(new_lm_hash, lanman_pw, verifier);
+ if (rc != 0) {
+ NTSTATUS status = NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER;
+ return gnutls_error_to_ntstatus(rc, status);
+ }
+ if (!mem_equal_const_time(verifier, old_lm_hash_encrypted, 16)) {
+ DEBUG(0,("check_oem_password: old lm password doesn't match.\n"));
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(100,
+ ("check_oem_password: password %s ok\n", *pp_new_passwd));
+#endif
+ return NT_STATUS_OK;
+ }
+
+ /* should not be reached */
+ return NT_STATUS_WRONG_PASSWORD;
+}
+
+static bool password_in_history(uint8_t nt_pw[NT_HASH_LEN],
+ uint32_t pw_history_len,
+ const uint8_t *pw_history)
+{
+ int i;
+
+ dump_data(100, nt_pw, NT_HASH_LEN);
+ dump_data(100, pw_history, PW_HISTORY_ENTRY_LEN * pw_history_len);
+
+ for (i=0; i<pw_history_len; i++) {
+ uint8_t new_nt_pw_salted_md5_hash[SALTED_MD5_HASH_LEN];
+ const uint8_t *current_salt;
+ const uint8_t *old_nt_pw_salted_md5_hash;
+
+ current_salt = &pw_history[i*PW_HISTORY_ENTRY_LEN];
+ old_nt_pw_salted_md5_hash = current_salt + PW_HISTORY_SALT_LEN;
+
+ if (all_zero(old_nt_pw_salted_md5_hash, SALTED_MD5_HASH_LEN)) {
+ /* Ignore zero valued entries. */
+ continue;
+ }
+
+ if (all_zero(current_salt, PW_HISTORY_SALT_LEN)) {
+ /*
+ * New format: zero salt and then plain nt hash.
+ * Directly compare the hashes.
+ */
+ if (mem_equal_const_time(nt_pw, old_nt_pw_salted_md5_hash,
+ SALTED_MD5_HASH_LEN))
+ {
+ return true;
+ }
+ } else {
+ gnutls_hash_hd_t hash_hnd = NULL;
+ int rc;
+
+ /*
+ * Old format: md5sum of salted nt hash.
+ * Create salted version of new pw to compare.
+ */
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5);
+ if (rc < 0) {
+ return false;
+ }
+
+ rc = gnutls_hash(hash_hnd, current_salt, 16);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return false;
+ }
+ rc = gnutls_hash(hash_hnd, nt_pw, 16);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return false;
+ }
+ gnutls_hash_deinit(hash_hnd, new_nt_pw_salted_md5_hash);
+
+ if (mem_equal_const_time(new_nt_pw_salted_md5_hash,
+ old_nt_pw_salted_md5_hash,
+ SALTED_MD5_HASH_LEN)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/***********************************************************
+ This routine takes the given password and checks it against
+ the password history. Returns True if this password has been
+ found in the history list.
+************************************************************/
+
+static bool check_passwd_history(struct samu *sampass, const char *plaintext)
+{
+ uchar new_nt_p16[NT_HASH_LEN];
+ const uint8_t *nt_pw;
+ const uint8_t *pwhistory;
+ uint32_t pwHisLen, curr_pwHisLen;
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &pwHisLen);
+ if (pwHisLen == 0) {
+ return False;
+ }
+
+ pwhistory = pdb_get_pw_history(sampass, &curr_pwHisLen);
+ if (!pwhistory || curr_pwHisLen == 0) {
+ return False;
+ }
+
+ /* Only examine the minimum of the current history len and
+ the stored history len. Avoids race conditions. */
+ pwHisLen = MIN(pwHisLen,curr_pwHisLen);
+
+ nt_pw = pdb_get_nt_passwd(sampass);
+
+ E_md4hash(plaintext, new_nt_p16);
+
+ if (mem_equal_const_time(nt_pw, new_nt_p16, NT_HASH_LEN)) {
+ DEBUG(10,("check_passwd_history: proposed new password for user %s is the same as the current password !\n",
+ pdb_get_username(sampass) ));
+ return True;
+ }
+
+ if (password_in_history(new_nt_p16, pwHisLen, pwhistory)) {
+ DEBUG(1,("check_passwd_history: proposed new password for "
+ "user %s found in history list !\n",
+ pdb_get_username(sampass) ));
+ return true;
+ }
+ return false;
+}
+
+/***********************************************************
+************************************************************/
+
+NTSTATUS check_password_complexity(const char *username,
+ const char *fullname,
+ const char *password,
+ enum samPwdChangeReason *samr_reject_reason)
+{
+ TALLOC_CTX *tosctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int check_ret;
+ char *cmd;
+
+ /* Use external script to check password complexity */
+ if ((lp_check_password_script(tosctx, lp_sub) == NULL)
+ || (*(lp_check_password_script(tosctx, lp_sub)) == '\0')){
+ return NT_STATUS_OK;
+ }
+
+ cmd = talloc_string_sub(tosctx, lp_check_password_script(tosctx, lp_sub), "%u",
+ username);
+ if (!cmd) {
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ check_ret = setenv("SAMBA_CPS_ACCOUNT_NAME", username, 1);
+ if (check_ret != 0) {
+ return map_nt_error_from_unix_common(errno);
+ }
+ unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME");
+ if (fullname != NULL) {
+ check_ret = setenv("SAMBA_CPS_FULL_NAME", fullname, 1);
+ } else {
+ unsetenv("SAMBA_CPS_FULL_NAME");
+ }
+ if (check_ret != 0) {
+ return map_nt_error_from_unix_common(errno);
+ }
+ check_ret = smbrunsecret(cmd, password);
+ unsetenv("SAMBA_CPS_ACCOUNT_NAME");
+ unsetenv("SAMBA_CPS_USER_PRINCIPAL_NAME");
+ unsetenv("SAMBA_CPS_FULL_NAME");
+ DEBUG(5,("check_password_complexity: check password script (%s) "
+ "returned [%d]\n", cmd, check_ret));
+ TALLOC_FREE(cmd);
+
+ if (check_ret != 0) {
+ DEBUG(1,("check_password_complexity: "
+ "check password script said new password is not good "
+ "enough!\n"));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAM_PWD_CHANGE_NOT_COMPLEX;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/***********************************************************
+ Code to change the oem password. Changes both the lanman
+ and NT hashes. Old_passwd is almost always NULL.
+ NOTE this function is designed to be called as root. Check the old password
+ is correct before calling. JRA.
+************************************************************/
+
+NTSTATUS change_oem_password(struct samu *hnd, const char *rhost,
+ char *old_passwd, char *new_passwd,
+ bool as_root,
+ enum samPwdChangeReason *samr_reject_reason)
+{
+ uint32_t min_len;
+ uint32_t refuse;
+ TALLOC_CTX *tosctx = talloc_tos();
+ struct passwd *pass = NULL;
+ const char *username = pdb_get_username(hnd);
+ const char *fullname = pdb_get_fullname(hnd);
+ time_t can_change_time = pdb_get_pass_can_change_time(hnd);
+ NTSTATUS status;
+
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
+ }
+
+ /* check to see if the secdesc has previously been set to disallow */
+ if (!pdb_get_pass_can_change(hnd)) {
+ DEBUG(1, ("user %s does not have permissions to change password\n", username));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
+ }
+ return NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+
+ /* check to see if it is a Machine account and if the policy
+ * denies machines to change the password. *
+ * Should we deny also SRVTRUST and/or DOMSTRUST ? .SSS. */
+ if (pdb_get_acct_ctrl(hnd) & ACB_WSTRUST) {
+ if (pdb_get_account_policy(PDB_POLICY_REFUSE_MACHINE_PW_CHANGE, &refuse) && refuse) {
+ DEBUG(1, ("Machine %s cannot change password now, "
+ "denied by Refuse Machine Password Change policy\n",
+ username));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
+ }
+ return NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+ }
+
+ /* removed calculation here, because passdb now calculates
+ based on policy. jmcd */
+ if ((can_change_time != 0) && (time(NULL) < can_change_time)) {
+ DEBUG(1, ("user %s cannot change password now, must "
+ "wait until %s\n", username,
+ http_timestring(tosctx, can_change_time)));
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAM_PWD_CHANGE_NO_ERROR;
+ }
+ return NT_STATUS_ACCOUNT_RESTRICTION;
+ }
+
+ if (pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &min_len) && (str_charnum(new_passwd) < min_len)) {
+ DBG_WARNING("user %s cannot change password - "
+ "password too short\n"
+ " account policy min password len = %"PRIu32"\n",
+ username,
+ min_len);
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAM_PWD_CHANGE_PASSWORD_TOO_SHORT;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+/* return NT_STATUS_PWD_TOO_SHORT; */
+ }
+
+ if (check_passwd_history(hnd,new_passwd)) {
+ if (samr_reject_reason) {
+ *samr_reject_reason = SAM_PWD_CHANGE_PWD_IN_HISTORY;
+ }
+ return NT_STATUS_PASSWORD_RESTRICTION;
+ }
+
+ pass = Get_Pwnam_alloc(tosctx, username);
+ if (!pass) {
+ DEBUG(1, ("change_oem_password: Username %s does not exist in system !?!\n", username));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = check_password_complexity(username,
+ fullname,
+ new_passwd,
+ samr_reject_reason);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(pass);
+ return status;
+ }
+
+ /*
+ * If unix password sync was requested, attempt to change
+ * the /etc/passwd database first. Return failure if this cannot
+ * be done.
+ *
+ * This occurs before the oem change, because we don't want to
+ * update it if chgpasswd failed.
+ *
+ * Conditional on lp_unix_password_sync() because we don't want
+ * to touch the unix db unless we have admin permission.
+ */
+
+ if(lp_unix_password_sync() &&
+ !chgpasswd(username, rhost, pass, old_passwd, new_passwd,
+ as_root)) {
+ TALLOC_FREE(pass);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ TALLOC_FREE(pass);
+
+ if (!pdb_set_plaintext_passwd (hnd, new_passwd)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Now write it into the file. */
+ return pdb_update_sam_account (hnd);
+}
+
+/***********************************************************
+ Code to check and change the OEM hashed password.
+************************************************************/
+
+NTSTATUS pass_oem_change(char *user, const char *rhost,
+ uchar password_encrypted_with_lm_hash[516],
+ const uchar old_lm_hash_encrypted[16],
+ uchar password_encrypted_with_nt_hash[516],
+ const uchar old_nt_hash_encrypted[16],
+ enum samPwdChangeReason *reject_reason)
+{
+ char *new_passwd = NULL;
+ struct samu *sampass = NULL;
+ NTSTATUS nt_status;
+ bool ret = false;
+ bool updated_badpw = false;
+ NTSTATUS update_login_attempts_status;
+ char *mutex_name_by_user = NULL;
+ struct named_mutex *mtx = NULL;
+
+ if (!(sampass = samu_new(NULL))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = pdb_getsampwnam(sampass, user);
+ unbecome_root();
+
+ if (ret == false) {
+ DEBUG(0,("pass_oem_change: getsmbpwnam returned NULL\n"));
+ nt_status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ /* Quit if the account was locked out. */
+ if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) {
+ DEBUG(3,("check_sam_security: Account for user %s was locked out.\n", user));
+ nt_status = NT_STATUS_ACCOUNT_LOCKED_OUT;
+ goto done;
+ }
+
+ nt_status = check_oem_password(user,
+ password_encrypted_with_lm_hash,
+ old_lm_hash_encrypted,
+ password_encrypted_with_nt_hash,
+ old_nt_hash_encrypted,
+ sampass,
+ &new_passwd);
+
+ /*
+ * We must re-load the sam account information under a mutex
+ * lock to ensure we don't miss any concurrent account lockout
+ * changes.
+ */
+
+ /* Clear out old sampass info. */
+ TALLOC_FREE(sampass);
+
+ sampass = samu_new(NULL);
+ if (sampass == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ mutex_name_by_user = talloc_asprintf(NULL,
+ "check_sam_security_mutex_%s",
+ user);
+ if (mutex_name_by_user == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* Grab the named mutex under root with 30 second timeout. */
+ become_root();
+ mtx = grab_named_mutex(NULL, mutex_name_by_user, 30);
+ if (mtx != NULL) {
+ /* Re-load the account information if we got the mutex. */
+ ret = pdb_getsampwnam(sampass, user);
+ }
+ unbecome_root();
+
+ /* Everything from here on until mtx is freed is done under the mutex.*/
+
+ if (mtx == NULL) {
+ DBG_ERR("Acquisition of mutex %s failed "
+ "for user %s\n",
+ mutex_name_by_user,
+ user);
+ nt_status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ if (!ret) {
+ /*
+ * Re-load of account failed. This could only happen if the
+ * user was deleted in the meantime.
+ */
+ DBG_NOTICE("reload of user '%s' in passdb failed.\n",
+ user);
+ nt_status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ /*
+ * Check if the account is now locked out - now under the mutex.
+ * This can happen if the server is under
+ * a password guess attack and the ACB_AUTOLOCK is set by
+ * another process.
+ */
+ if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) {
+ DBG_NOTICE("Account for user %s was locked out.\n", user);
+ nt_status = NT_STATUS_ACCOUNT_LOCKED_OUT;
+ goto done;
+ }
+
+ /*
+ * Notify passdb backend of login success/failure. If not
+ * NT_STATUS_OK the backend doesn't like the login
+ */
+ update_login_attempts_status = pdb_update_login_attempts(sampass,
+ NT_STATUS_IS_OK(nt_status));
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ bool increment_bad_pw_count = false;
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_WRONG_PASSWORD) &&
+ (pdb_get_acct_ctrl(sampass) & ACB_NORMAL) &&
+ NT_STATUS_IS_OK(update_login_attempts_status))
+ {
+ increment_bad_pw_count = true;
+ }
+
+ if (increment_bad_pw_count) {
+ pdb_increment_bad_password_count(sampass);
+ updated_badpw = true;
+ } else {
+ pdb_update_bad_password_count(sampass,
+ &updated_badpw);
+ }
+ } else {
+
+ if ((pdb_get_acct_ctrl(sampass) & ACB_NORMAL) &&
+ (pdb_get_bad_password_count(sampass) > 0)){
+ pdb_set_bad_password_count(sampass, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(sampass, 0, PDB_CHANGED);
+ updated_badpw = true;
+ }
+ }
+
+ if (updated_badpw) {
+ NTSTATUS update_status;
+ become_root();
+ update_status = pdb_update_sam_account(sampass);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(update_status)) {
+ DEBUG(1, ("Failed to modify entry: %s\n",
+ nt_errstr(update_status)));
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto done;
+ }
+
+ /* We've already checked the old password here.... */
+ become_root();
+ nt_status = change_oem_password(sampass, rhost, NULL, new_passwd,
+ True, reject_reason);
+ unbecome_root();
+
+ BURN_STR(new_passwd);
+
+done:
+ TALLOC_FREE(sampass);
+ TALLOC_FREE(mutex_name_by_user);
+ TALLOC_FREE(mtx);
+
+ return nt_status;
+}
+
+NTSTATUS samr_set_password_aes(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *cdk,
+ struct samr_EncryptedPasswordAES *pwbuf,
+ char **new_password_str)
+{
+ DATA_BLOB pw_data = data_blob_null;
+ DATA_BLOB new_password = data_blob_null;
+ const DATA_BLOB ciphertext =
+ data_blob_const(pwbuf->cipher, pwbuf->cipher_len);
+ DATA_BLOB iv = data_blob_const(pwbuf->salt, sizeof(pwbuf->salt));
+ NTSTATUS status;
+ bool ok;
+
+ *new_password_str = NULL;
+
+ status = samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt(
+ mem_ctx,
+ &ciphertext,
+ cdk,
+ &samr_aes256_enc_key_salt,
+ &samr_aes256_mac_key_salt,
+ &iv,
+ pwbuf->auth_data,
+ &pw_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ ok = decode_pwd_string_from_buffer514(mem_ctx,
+ pw_data.data,
+ CH_UTF16,
+ &new_password);
+ TALLOC_FREE(pw_data.data);
+ if (!ok) {
+ DBG_NOTICE("samr: failed to decode password buffer\n");
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ *new_password_str = talloc_strndup(mem_ctx,
+ (char *)new_password.data,
+ new_password.length);
+ TALLOC_FREE(new_password.data);
+ if (*new_password_str == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_keep_secret(*new_password_str);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_server/samr/srv_samr_nt.c b/source3/rpc_server/samr/srv_samr_nt.c
new file mode 100644
index 0000000..d26a8d5
--- /dev/null
+++ b/source3/rpc_server/samr/srv_samr_nt.c
@@ -0,0 +1,7910 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ * Copyright (C) Paul Ashton 1997,
+ * Copyright (C) Marc Jacobsen 1999,
+ * Copyright (C) Jeremy Allison 2001-2008,
+ * Copyright (C) Jean François Micouleau 1998-2001,
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002,
+ * Copyright (C) Gerald (Jerry) Carter 2003-2004,
+ * Copyright (C) Simo Sorce 2003.
+ * Copyright (C) Volker Lendecke 2005.
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This is the implementation of the SAMR code.
+ */
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "../librpc/gen_ndr/ndr_samr.h"
+#include "../librpc/gen_ndr/ndr_samr_scompat.h"
+#include "rpc_server/samr/srv_samr_util.h"
+#include "secrets.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "auth.h"
+#include "rpc_server/srv_access_check.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/base64.h"
+#include "param/param.h"
+#include "librpc/rpc/dcerpc_helper.h"
+#include "librpc/rpc/dcerpc_samr.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#include "lib/global_contexts.h"
+#include "nsswitch/winbind_client.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#define SAMR_USR_RIGHTS_WRITE_PW \
+ ( READ_CONTROL_ACCESS | \
+ SAMR_USER_ACCESS_CHANGE_PASSWORD | \
+ SAMR_USER_ACCESS_SET_LOC_COM)
+#define SAMR_USR_RIGHTS_CANT_WRITE_PW \
+ ( READ_CONTROL_ACCESS | SAMR_USER_ACCESS_SET_LOC_COM )
+
+#define DISP_INFO_CACHE_TIMEOUT 10
+
+#define MAX_SAM_ENTRIES_W2K 0x400 /* 1024 */
+#define MAX_SAM_ENTRIES_W95 50
+
+enum samr_handle {
+ SAMR_HANDLE_CONNECT,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_HANDLE_USER,
+ SAMR_HANDLE_GROUP,
+ SAMR_HANDLE_ALIAS
+};
+
+struct samr_info {
+ uint32_t access_granted;
+ struct dom_sid sid;
+ struct disp_info *disp_info;
+};
+
+typedef struct disp_info {
+ struct dom_sid sid; /* identify which domain this is. */
+ struct pdb_search *users; /* querydispinfo 1 and 4 */
+ struct pdb_search *machines; /* querydispinfo 2 */
+ struct pdb_search *groups; /* querydispinfo 3 and 5, enumgroups */
+ struct pdb_search *aliases; /* enumaliases */
+
+ uint32_t enum_acb_mask;
+ struct pdb_search *enum_users; /* enumusers with a mask */
+
+ struct tevent_timer *cache_timeout_event; /* cache idle timeout
+ * handler. */
+} DISP_INFO;
+
+static const struct generic_mapping sam_generic_mapping = {
+ GENERIC_RIGHTS_SAM_READ,
+ GENERIC_RIGHTS_SAM_WRITE,
+ GENERIC_RIGHTS_SAM_EXECUTE,
+ GENERIC_RIGHTS_SAM_ALL_ACCESS};
+static const struct generic_mapping dom_generic_mapping = {
+ GENERIC_RIGHTS_DOMAIN_READ,
+ GENERIC_RIGHTS_DOMAIN_WRITE,
+ GENERIC_RIGHTS_DOMAIN_EXECUTE,
+ GENERIC_RIGHTS_DOMAIN_ALL_ACCESS};
+static const struct generic_mapping usr_generic_mapping = {
+ GENERIC_RIGHTS_USER_READ,
+ GENERIC_RIGHTS_USER_WRITE,
+ GENERIC_RIGHTS_USER_EXECUTE,
+ GENERIC_RIGHTS_USER_ALL_ACCESS};
+static const struct generic_mapping usr_nopwchange_generic_mapping = {
+ GENERIC_RIGHTS_USER_READ,
+ GENERIC_RIGHTS_USER_WRITE,
+ GENERIC_RIGHTS_USER_EXECUTE & ~SAMR_USER_ACCESS_CHANGE_PASSWORD,
+ GENERIC_RIGHTS_USER_ALL_ACCESS};
+static const struct generic_mapping grp_generic_mapping = {
+ GENERIC_RIGHTS_GROUP_READ,
+ GENERIC_RIGHTS_GROUP_WRITE,
+ GENERIC_RIGHTS_GROUP_EXECUTE,
+ GENERIC_RIGHTS_GROUP_ALL_ACCESS};
+static const struct generic_mapping ali_generic_mapping = {
+ GENERIC_RIGHTS_ALIAS_READ,
+ GENERIC_RIGHTS_ALIAS_WRITE,
+ GENERIC_RIGHTS_ALIAS_EXECUTE,
+ GENERIC_RIGHTS_ALIAS_ALL_ACCESS};
+
+/*******************************************************************
+*******************************************************************/
+static NTSTATUS create_samr_policy_handle(TALLOC_CTX *mem_ctx,
+ struct pipes_struct *p,
+ enum samr_handle type,
+ uint32_t acc_granted,
+ struct dom_sid *sid,
+ struct disp_info *disp_info,
+ struct policy_handle *handle)
+{
+ struct samr_info *info = NULL;
+ bool ok;
+
+ ZERO_STRUCTP(handle);
+
+ info = talloc_zero(mem_ctx, struct samr_info);
+ if (info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info->access_granted = acc_granted;
+
+ if (sid != NULL) {
+ sid_copy(&info->sid, sid);
+ }
+
+ if (disp_info != NULL) {
+ info->disp_info = disp_info;
+ }
+
+ ok = create_policy_hnd(p, handle, type, info);
+ if (!ok) {
+ talloc_free(info);
+ ZERO_STRUCTP(handle);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS samr_handle_access_check(uint32_t access_granted,
+ uint32_t access_required,
+ uint32_t *paccess_granted)
+{
+ if ((access_required & access_granted) != access_required) {
+ if (root_mode()) {
+ DBG_INFO("ACCESS should be DENIED (granted: "
+ "%#010x; required: %#010x) but overwritten "
+ "by euid == 0\n", access_granted,
+ access_required);
+ goto okay;
+ }
+ DBG_NOTICE("ACCESS DENIED (granted: %#010x; required: "
+ "%#010x)\n", access_granted, access_required);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+okay:
+ if (paccess_granted != NULL) {
+ *paccess_granted = access_granted;
+ }
+ return NT_STATUS_OK;
+}
+
+static void *samr_policy_handle_find(struct pipes_struct *p,
+ const struct policy_handle *handle,
+ uint8_t handle_type,
+ uint32_t access_required,
+ uint32_t *access_granted,
+ NTSTATUS *pstatus)
+{
+ struct samr_info *info = NULL;
+ NTSTATUS status;
+
+ info = find_policy_by_hnd(p,
+ handle,
+ handle_type,
+ struct samr_info,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ *pstatus = NT_STATUS_INVALID_HANDLE;
+ return NULL;
+ }
+
+ status = samr_handle_access_check(info->access_granted,
+ access_required,
+ access_granted);
+ if (!NT_STATUS_IS_OK(status)) {
+ *pstatus = status;
+ return NULL;
+ }
+
+ *pstatus = NT_STATUS_OK;
+ return info;
+}
+
+static NTSTATUS make_samr_object_sd( TALLOC_CTX *ctx, struct security_descriptor **psd, size_t *sd_size,
+ const struct generic_mapping *map,
+ struct dom_sid *sid, uint32_t sid_access )
+{
+ struct dom_sid domadmin_sid;
+ struct security_ace ace[5]; /* at most 5 entries */
+ size_t i = 0;
+
+ struct security_acl *psa = NULL;
+
+ /* basic access for Everyone */
+
+ init_sec_ace(&ace[i++], &global_sid_World, SEC_ACE_TYPE_ACCESS_ALLOWED,
+ map->generic_execute | map->generic_read, 0);
+
+ /* add Full Access 'BUILTIN\Administrators' and 'BUILTIN\Account Operators */
+
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0);
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Account_Operators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0);
+
+ /* Add Full Access for Domain Admins if we are a DC */
+
+ if ( IS_DC ) {
+ sid_compose(&domadmin_sid, get_global_sam_sid(),
+ DOMAIN_RID_ADMINS);
+ init_sec_ace(&ace[i++], &domadmin_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, map->generic_all, 0);
+ }
+
+ /* if we have a sid, give it some special access */
+
+ if ( sid ) {
+ init_sec_ace(&ace[i++], sid, SEC_ACE_TYPE_ACCESS_ALLOWED, sid_access, 0);
+ }
+
+ /* create the security descriptor */
+
+ if ((psa = make_sec_acl(ctx, NT4_ACL_REVISION, i, ace)) == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ if ((*psd = make_sec_desc(ctx, SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE, NULL, NULL, NULL,
+ psa, sd_size)) == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Fetch or create a dispinfo struct.
+********************************************************************/
+
+static DISP_INFO *get_samr_dispinfo_by_sid(const struct dom_sid *psid)
+{
+ /*
+ * We do a static cache for DISP_INFO's here. Explanation can be found
+ * in Jeremy's checkin message to r11793:
+ *
+ * Fix the SAMR cache so it works across completely insane
+ * client behaviour (ie.:
+ * open pipe/open SAMR handle/enumerate 0 - 1024
+ * close SAMR handle, close pipe.
+ * open pipe/open SAMR handle/enumerate 1024 - 2048...
+ * close SAMR handle, close pipe.
+ * And on ad-nausium. Amazing.... probably object-oriented
+ * client side programming in action yet again.
+ * This change should *massively* improve performance when
+ * enumerating users from an LDAP database.
+ * Jeremy.
+ *
+ * "Our" and the builtin domain are the only ones where we ever
+ * enumerate stuff, so just cache 2 entries.
+ */
+
+ static struct disp_info *builtin_dispinfo;
+ static struct disp_info *domain_dispinfo;
+
+ /* There are two cases to consider here:
+ 1) The SID is a domain SID and we look for an equality match, or
+ 2) This is an account SID and so we return the DISP_INFO* for our
+ domain */
+
+ if (psid == NULL) {
+ return NULL;
+ }
+
+ if (sid_check_is_builtin(psid) || sid_check_is_in_builtin(psid)) {
+ /*
+ * Necessary only once, but it does not really hurt.
+ */
+ if (builtin_dispinfo == NULL) {
+ builtin_dispinfo = talloc_zero(NULL, struct disp_info);
+ if (builtin_dispinfo == NULL) {
+ return NULL;
+ }
+ }
+ sid_copy(&builtin_dispinfo->sid, &global_sid_Builtin);
+
+ return builtin_dispinfo;
+ }
+
+ if (sid_check_is_our_sam(psid) || sid_check_is_in_our_sam(psid)) {
+ /*
+ * Necessary only once, but it does not really hurt.
+ */
+ if (domain_dispinfo == NULL) {
+ domain_dispinfo = talloc_zero(NULL, struct disp_info);
+ if (domain_dispinfo == NULL) {
+ return NULL;
+ }
+ }
+ sid_copy(&domain_dispinfo->sid, get_global_sam_sid());
+
+ return domain_dispinfo;
+ }
+
+ return NULL;
+}
+
+/*******************************************************************
+ Function to free the per SID data.
+ ********************************************************************/
+
+static void free_samr_cache(DISP_INFO *disp_info)
+{
+ struct dom_sid_buf buf;
+
+ DEBUG(10, ("free_samr_cache: deleting cache for SID %s\n",
+ dom_sid_str_buf(&disp_info->sid, &buf)));
+
+ /* We need to become root here because the paged search might have to
+ * tell the LDAP server we're not interested in the rest anymore. */
+
+ become_root();
+
+ TALLOC_FREE(disp_info->users);
+ TALLOC_FREE(disp_info->machines);
+ TALLOC_FREE(disp_info->groups);
+ TALLOC_FREE(disp_info->aliases);
+ TALLOC_FREE(disp_info->enum_users);
+
+ unbecome_root();
+}
+
+/*******************************************************************
+ Idle event handler. Throw away the disp info cache.
+ ********************************************************************/
+
+static void disp_info_cache_idle_timeout_handler(struct tevent_context *ev_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ DISP_INFO *disp_info = (DISP_INFO *)private_data;
+
+ TALLOC_FREE(disp_info->cache_timeout_event);
+
+ DEBUG(10, ("disp_info_cache_idle_timeout_handler: caching timed "
+ "out\n"));
+ free_samr_cache(disp_info);
+}
+
+/*******************************************************************
+ Setup cache removal idle event handler.
+ ********************************************************************/
+
+static void set_disp_info_cache_timeout(DISP_INFO *disp_info, time_t secs_fromnow)
+{
+ struct dom_sid_buf buf;
+
+ /* Remove any pending timeout and update. */
+
+ TALLOC_FREE(disp_info->cache_timeout_event);
+
+ DEBUG(10,("set_disp_info_cache_timeout: caching enumeration for "
+ "SID %s for %u seconds\n",
+ dom_sid_str_buf(&disp_info->sid, &buf),
+ (unsigned int)secs_fromnow ));
+
+ disp_info->cache_timeout_event = tevent_add_timer(
+ global_event_context(), NULL,
+ timeval_current_ofs(secs_fromnow, 0),
+ disp_info_cache_idle_timeout_handler, (void *)disp_info);
+}
+
+/*******************************************************************
+ Force flush any cache. We do this on any samr_set_xxx call.
+ We must also remove the timeout handler.
+ ********************************************************************/
+
+static void force_flush_samr_cache(const struct dom_sid *sid)
+{
+ struct disp_info *disp_info = get_samr_dispinfo_by_sid(sid);
+
+ if ((disp_info == NULL) || (disp_info->cache_timeout_event == NULL)) {
+ return;
+ }
+
+ DEBUG(10,("force_flush_samr_cache: clearing idle event\n"));
+ TALLOC_FREE(disp_info->cache_timeout_event);
+ free_samr_cache(disp_info);
+}
+
+/*******************************************************************
+ Ensure password info is never given out. Paranioa... JRA.
+ ********************************************************************/
+
+static void samr_clear_sam_passwd(struct samu *sam_pass)
+{
+
+ if (!sam_pass)
+ return;
+
+ /* These now zero out the old password */
+
+ pdb_set_lanman_passwd(sam_pass, NULL, PDB_DEFAULT);
+ pdb_set_nt_passwd(sam_pass, NULL, PDB_DEFAULT);
+}
+
+static uint32_t count_sam_users(struct disp_info *info, uint32_t acct_flags)
+{
+ struct samr_displayentry *entry;
+
+ if (sid_check_is_builtin(&info->sid)) {
+ /* No users in builtin. */
+ return 0;
+ }
+
+ if (info->users == NULL) {
+ info->users = pdb_search_users(info, acct_flags);
+ if (info->users == NULL) {
+ return 0;
+ }
+ }
+ /* Fetch the last possible entry, thus trigger an enumeration */
+ pdb_search_entries(info->users, 0xffffffff, 1, &entry);
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(info, DISP_INFO_CACHE_TIMEOUT);
+
+ return info->users->num_entries;
+}
+
+static uint32_t count_sam_groups(struct disp_info *info)
+{
+ struct samr_displayentry *entry;
+
+ if (sid_check_is_builtin(&info->sid)) {
+ /* No groups in builtin. */
+ return 0;
+ }
+
+ if (info->groups == NULL) {
+ info->groups = pdb_search_groups(info);
+ if (info->groups == NULL) {
+ return 0;
+ }
+ }
+ /* Fetch the last possible entry, thus trigger an enumeration */
+ pdb_search_entries(info->groups, 0xffffffff, 1, &entry);
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(info, DISP_INFO_CACHE_TIMEOUT);
+
+ return info->groups->num_entries;
+}
+
+static uint32_t count_sam_aliases(struct disp_info *info)
+{
+ struct samr_displayentry *entry;
+
+ if (info->aliases == NULL) {
+ info->aliases = pdb_search_aliases(info, &info->sid);
+ if (info->aliases == NULL) {
+ return 0;
+ }
+ }
+ /* Fetch the last possible entry, thus trigger an enumeration */
+ pdb_search_entries(info->aliases, 0xffffffff, 1, &entry);
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(info, DISP_INFO_CACHE_TIMEOUT);
+
+ return info->aliases->num_entries;
+}
+
+/*******************************************************************
+ _samr_Close
+ ********************************************************************/
+
+NTSTATUS _samr_Close(struct pipes_struct *p, struct samr_Close *r)
+{
+ if (!close_policy_hnd(p, r->in.handle)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ ZERO_STRUCTP(r->out.handle);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_OpenDomain
+ ********************************************************************/
+
+NTSTATUS _samr_OpenDomain(struct pipes_struct *p,
+ struct samr_OpenDomain *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *psd = NULL;
+ uint32_t acc_granted;
+ uint32_t des_access = r->in.access_mask;
+ NTSTATUS status;
+ size_t sd_size;
+ uint32_t extra_access = SAMR_DOMAIN_ACCESS_CREATE_USER;
+ struct disp_info *disp_info = NULL;
+
+ /* find the connection policy handle. */
+ (void)samr_policy_handle_find(p,
+ r->in.connect_handle,
+ SAMR_HANDLE_CONNECT,
+ 0,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*check if access can be granted as requested by client. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ make_samr_object_sd( p->mem_ctx, &psd, &sd_size, &dom_generic_mapping, NULL, 0 );
+ se_map_generic( &des_access, &dom_generic_mapping );
+
+ /*
+ * Users with SeAddUser get the ability to manipulate groups
+ * and aliases.
+ */
+ if (security_token_has_privilege(
+ session_info->security_token, SEC_PRIV_ADD_USERS)) {
+ extra_access |= (SAMR_DOMAIN_ACCESS_CREATE_GROUP |
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS |
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT |
+ SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS |
+ SAMR_DOMAIN_ACCESS_CREATE_ALIAS);
+ }
+
+ /*
+ * Users with SeMachineAccount or SeAddUser get additional
+ * SAMR_DOMAIN_ACCESS_CREATE_USER access.
+ */
+
+ status = access_check_object( psd, session_info->security_token,
+ SEC_PRIV_MACHINE_ACCOUNT, SEC_PRIV_ADD_USERS,
+ extra_access, des_access,
+ &acc_granted, "_samr_OpenDomain" );
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ if (!sid_check_is_our_sam(r->in.sid) &&
+ !sid_check_is_builtin(r->in.sid)) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ disp_info = get_samr_dispinfo_by_sid(r->in.sid);
+
+ status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_DOMAIN,
+ acc_granted,
+ r->in.sid,
+ disp_info,
+ r->out.domain_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(5,("_samr_OpenDomain: %d\n", __LINE__));
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_GetUserPwInfo
+ ********************************************************************/
+
+NTSTATUS _samr_GetUserPwInfo(struct pipes_struct *p,
+ struct samr_GetUserPwInfo *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct samr_info *uinfo;
+ enum lsa_SidType sid_type;
+ uint32_t min_password_length = 0;
+ uint32_t password_properties = 0;
+ bool ret = false;
+ NTSTATUS status;
+
+ DEBUG(5,("_samr_GetUserPwInfo: %d\n", __LINE__));
+
+ uinfo = samr_policy_handle_find(p, r->in.user_handle,
+ SAMR_HANDLE_USER,
+ SAMR_USER_ACCESS_GET_ATTRIBUTES,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!sid_check_is_in_our_sam(&uinfo->sid)) {
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ become_root();
+ ret = lookup_sid(p->mem_ctx, &uinfo->sid, NULL, NULL, &sid_type);
+ unbecome_root();
+ if (ret == false) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ switch (sid_type) {
+ case SID_NAME_USER:
+ become_root();
+ pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN,
+ &min_password_length);
+ pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS,
+ &password_properties);
+ unbecome_root();
+
+ if (lp_check_password_script(talloc_tos(), lp_sub)
+ && *lp_check_password_script(talloc_tos(), lp_sub)) {
+ password_properties |= DOMAIN_PASSWORD_COMPLEX;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ r->out.info->min_password_length = min_password_length;
+ r->out.info->password_properties = password_properties;
+
+ DEBUG(5,("_samr_GetUserPwInfo: %d\n", __LINE__));
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_SetSecurity
+ ********************************************************************/
+
+NTSTATUS _samr_SetSecurity(struct pipes_struct *p,
+ struct samr_SetSecurity *r)
+{
+ struct samr_info *uinfo;
+ uint32_t i;
+ struct security_acl *dacl;
+ bool ret;
+ struct samu *sampass=NULL;
+ NTSTATUS status;
+
+ uinfo = samr_policy_handle_find(p,
+ r->in.handle,
+ SAMR_HANDLE_USER,
+ SAMR_USER_ACCESS_SET_ATTRIBUTES,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!(sampass = samu_new( p->mem_ctx))) {
+ DEBUG(0,("No memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get the user record */
+ become_root();
+ ret = pdb_getsampwsid(sampass, &uinfo->sid);
+ unbecome_root();
+
+ if (!ret) {
+ struct dom_sid_buf buf;
+ DEBUG(4, ("User %s not found\n",
+ dom_sid_str_buf(&uinfo->sid, &buf)));
+ TALLOC_FREE(sampass);
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ dacl = r->in.sdbuf->sd->dacl;
+ for (i=0; i < dacl->num_aces; i++) {
+ if (dom_sid_equal(&uinfo->sid, &dacl->aces[i].trustee)) {
+ ret = pdb_set_pass_can_change(sampass,
+ (dacl->aces[i].access_mask &
+ SAMR_USER_ACCESS_CHANGE_PASSWORD) ?
+ True: False);
+ break;
+ }
+ }
+
+ if (!ret) {
+ TALLOC_FREE(sampass);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ become_root();
+ status = pdb_update_sam_account(sampass);
+ unbecome_root();
+
+ TALLOC_FREE(sampass);
+
+ return status;
+}
+
+/*******************************************************************
+ build correct perms based on policies and password times for _samr_query_sec_obj
+*******************************************************************/
+static bool check_change_pw_access(TALLOC_CTX *mem_ctx, struct dom_sid *user_sid)
+{
+ struct samu *sampass=NULL;
+ bool ret;
+
+ if ( !(sampass = samu_new( mem_ctx )) ) {
+ DEBUG(0,("No memory!\n"));
+ return False;
+ }
+
+ become_root();
+ ret = pdb_getsampwsid(sampass, user_sid);
+ unbecome_root();
+
+ if (ret == False) {
+ struct dom_sid_buf buf;
+ DEBUG(4,("User %s not found\n",
+ dom_sid_str_buf(user_sid, &buf)));
+ TALLOC_FREE(sampass);
+ return False;
+ }
+
+ DEBUG(3,("User:[%s]\n", pdb_get_username(sampass) ));
+
+ if (pdb_get_pass_can_change(sampass)) {
+ TALLOC_FREE(sampass);
+ return True;
+ }
+ TALLOC_FREE(sampass);
+ return False;
+}
+
+
+/*******************************************************************
+ _samr_QuerySecurity
+ ********************************************************************/
+
+NTSTATUS _samr_QuerySecurity(struct pipes_struct *p,
+ struct samr_QuerySecurity *r)
+{
+ struct samr_info *info;
+ NTSTATUS status;
+ struct security_descriptor * psd = NULL;
+ size_t sd_size = 0;
+ struct dom_sid_buf buf;
+
+ info = samr_policy_handle_find(p,
+ r->in.handle,
+ SAMR_HANDLE_CONNECT,
+ SEC_STD_READ_CONTROL,
+ NULL,
+ &status);
+ if (NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("_samr_QuerySecurity: querying security on SAM\n"));
+ status = make_samr_object_sd(p->mem_ctx, &psd, &sd_size,
+ &sam_generic_mapping, NULL, 0);
+ goto done;
+ }
+
+ info = samr_policy_handle_find(p,
+ r->in.handle,
+ SAMR_HANDLE_DOMAIN,
+ SEC_STD_READ_CONTROL,
+ NULL,
+ &status);
+ if (NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("_samr_QuerySecurity: querying security on Domain "
+ "with SID: %s\n",
+ dom_sid_str_buf(&info->sid, &buf)));
+ /*
+ * TODO: Builtin probably needs a different SD with restricted
+ * write access
+ */
+ status = make_samr_object_sd(p->mem_ctx, &psd, &sd_size,
+ &dom_generic_mapping, NULL, 0);
+ goto done;
+ }
+
+ info = samr_policy_handle_find(p,
+ r->in.handle,
+ SAMR_HANDLE_USER,
+ SEC_STD_READ_CONTROL,
+ NULL,
+ &status);
+ if (NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("_samr_QuerySecurity: querying security on user "
+ "Object with SID: %s\n",
+ dom_sid_str_buf(&info->sid, &buf)));
+ if (check_change_pw_access(p->mem_ctx, &info->sid)) {
+ status = make_samr_object_sd(
+ p->mem_ctx, &psd, &sd_size,
+ &usr_generic_mapping,
+ &info->sid, SAMR_USR_RIGHTS_WRITE_PW);
+ } else {
+ status = make_samr_object_sd(
+ p->mem_ctx, &psd, &sd_size,
+ &usr_nopwchange_generic_mapping,
+ &info->sid, SAMR_USR_RIGHTS_CANT_WRITE_PW);
+ }
+ goto done;
+ }
+
+ info = samr_policy_handle_find(p,
+ r->in.handle,
+ SAMR_HANDLE_GROUP,
+ SEC_STD_READ_CONTROL,
+ NULL,
+ &status);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * TODO: different SDs have to be generated for aliases groups
+ * and users. Currently all three get a default user SD
+ */
+ DEBUG(10,("_samr_QuerySecurity: querying security on group "
+ "Object with SID: %s\n",
+ dom_sid_str_buf(&info->sid, &buf)));
+ status = make_samr_object_sd(
+ p->mem_ctx, &psd, &sd_size,
+ &usr_nopwchange_generic_mapping,
+ &info->sid, SAMR_USR_RIGHTS_CANT_WRITE_PW);
+ goto done;
+ }
+
+ info = samr_policy_handle_find(p,
+ r->in.handle,
+ SAMR_HANDLE_ALIAS,
+ SEC_STD_READ_CONTROL,
+ NULL,
+ &status);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * TODO: different SDs have to be generated for aliases groups
+ * and users. Currently all three get a default user SD
+ */
+ DEBUG(10,("_samr_QuerySecurity: querying security on alias "
+ "Object with SID: %s\n",
+ dom_sid_str_buf(&info->sid, &buf)));
+ status = make_samr_object_sd(
+ p->mem_ctx, &psd, &sd_size,
+ &usr_nopwchange_generic_mapping,
+ &info->sid, SAMR_USR_RIGHTS_CANT_WRITE_PW);
+ goto done;
+ }
+
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+done:
+ if ((*r->out.sdbuf = make_sec_desc_buf(p->mem_ctx, sd_size, psd)) == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ return status;
+}
+
+/*******************************************************************
+makes a SAM_ENTRY / UNISTR2* structure from a user list.
+********************************************************************/
+
+static NTSTATUS make_user_sam_entry_list(TALLOC_CTX *ctx,
+ struct samr_SamEntry **sam_pp,
+ uint32_t num_entries,
+ uint32_t start_idx,
+ struct samr_displayentry *entries)
+{
+ uint32_t i;
+ struct samr_SamEntry *sam;
+
+ *sam_pp = NULL;
+
+ if (num_entries == 0) {
+ return NT_STATUS_OK;
+ }
+
+ sam = talloc_zero_array(ctx, struct samr_SamEntry, num_entries);
+ if (sam == NULL) {
+ DEBUG(0, ("make_user_sam_entry_list: TALLOC_ZERO failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_entries; i++) {
+#if 0
+ /*
+ * usrmgr expects a non-NULL terminated string with
+ * trust relationships
+ */
+ if (entries[i].acct_flags & ACB_DOMTRUST) {
+ init_unistr2(&uni_temp_name, entries[i].account_name,
+ UNI_FLAGS_NONE);
+ } else {
+ init_unistr2(&uni_temp_name, entries[i].account_name,
+ UNI_STR_TERMINATE);
+ }
+#endif
+ init_lsa_String(&sam[i].name, entries[i].account_name);
+ sam[i].idx = entries[i].rid;
+ }
+
+ *sam_pp = sam;
+
+ return NT_STATUS_OK;
+}
+
+#define MAX_SAM_ENTRIES MAX_SAM_ENTRIES_W2K
+
+/*******************************************************************
+ _samr_EnumDomainUsers
+ ********************************************************************/
+
+NTSTATUS _samr_EnumDomainUsers(struct pipes_struct *p,
+ struct samr_EnumDomainUsers *r)
+{
+ NTSTATUS status;
+ struct samr_info *dinfo;
+ uint32_t num_account;
+ uint32_t enum_context = *r->in.resume_handle;
+ enum remote_arch_types ra_type = get_remote_arch();
+ int max_sam_entries = (ra_type == RA_WIN95) ? MAX_SAM_ENTRIES_W95 : MAX_SAM_ENTRIES_W2K;
+ uint32_t max_entries = max_sam_entries;
+ struct samr_displayentry *entries = NULL;
+ struct samr_SamArray *samr_array = NULL;
+ struct samr_SamEntry *samr_entries = NULL;
+
+ DEBUG(5,("_samr_EnumDomainUsers: %d\n", __LINE__));
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ samr_array = talloc_zero(p->mem_ctx, struct samr_SamArray);
+ if (!samr_array) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *r->out.sam = samr_array;
+
+ if (sid_check_is_builtin(&dinfo->sid)) {
+ /* No users in builtin. */
+ *r->out.resume_handle = *r->in.resume_handle;
+ DEBUG(5,("_samr_EnumDomainUsers: No users in BUILTIN\n"));
+ return status;
+ }
+
+ become_root();
+
+ /* AS ROOT !!!! */
+
+ if ((dinfo->disp_info->enum_users != NULL) &&
+ (dinfo->disp_info->enum_acb_mask != r->in.acct_flags)) {
+ TALLOC_FREE(dinfo->disp_info->enum_users);
+ }
+
+ if (dinfo->disp_info->enum_users == NULL) {
+ dinfo->disp_info->enum_users = pdb_search_users(
+ dinfo->disp_info, r->in.acct_flags);
+ dinfo->disp_info->enum_acb_mask = r->in.acct_flags;
+ }
+
+ if (dinfo->disp_info->enum_users == NULL) {
+ /* END AS ROOT !!!! */
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ num_account = pdb_search_entries(dinfo->disp_info->enum_users,
+ enum_context, max_entries,
+ &entries);
+
+ /* END AS ROOT !!!! */
+
+ unbecome_root();
+
+ if (num_account == 0) {
+ DEBUG(5, ("_samr_EnumDomainUsers: enumeration handle over "
+ "total entries\n"));
+ *r->out.resume_handle = *r->in.resume_handle;
+ return NT_STATUS_OK;
+ }
+
+ status = make_user_sam_entry_list(p->mem_ctx, &samr_entries,
+ num_account, enum_context,
+ entries);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (max_entries <= num_account) {
+ status = STATUS_MORE_ENTRIES;
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT);
+
+ DEBUG(5, ("_samr_EnumDomainUsers: %d\n", __LINE__));
+
+ samr_array->count = num_account;
+ samr_array->entries = samr_entries;
+
+ *r->out.resume_handle = *r->in.resume_handle + num_account;
+ *r->out.num_entries = num_account;
+
+ DEBUG(5,("_samr_EnumDomainUsers: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+makes a SAM_ENTRY / UNISTR2* structure from a group list.
+********************************************************************/
+
+static void make_group_sam_entry_list(TALLOC_CTX *ctx,
+ struct samr_SamEntry **sam_pp,
+ uint32_t num_sam_entries,
+ struct samr_displayentry *entries)
+{
+ struct samr_SamEntry *sam;
+ uint32_t i;
+
+ *sam_pp = NULL;
+
+ if (num_sam_entries == 0) {
+ return;
+ }
+
+ sam = talloc_zero_array(ctx, struct samr_SamEntry, num_sam_entries);
+ if (sam == NULL) {
+ return;
+ }
+
+ for (i = 0; i < num_sam_entries; i++) {
+ /*
+ * JRA. I think this should include the null. TNG does not.
+ */
+ init_lsa_String(&sam[i].name, entries[i].account_name);
+ sam[i].idx = entries[i].rid;
+ }
+
+ *sam_pp = sam;
+}
+
+/*******************************************************************
+ _samr_EnumDomainGroups
+ ********************************************************************/
+
+NTSTATUS _samr_EnumDomainGroups(struct pipes_struct *p,
+ struct samr_EnumDomainGroups *r)
+{
+ NTSTATUS status;
+ struct samr_info *dinfo;
+ struct samr_displayentry *groups;
+ uint32_t num_groups;
+ struct samr_SamArray *samr_array = NULL;
+ struct samr_SamEntry *samr_entries = NULL;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(5,("_samr_EnumDomainGroups: %d\n", __LINE__));
+
+ samr_array = talloc_zero(p->mem_ctx, struct samr_SamArray);
+ if (!samr_array) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *r->out.sam = samr_array;
+
+ if (sid_check_is_builtin(&dinfo->sid)) {
+ /* No groups in builtin. */
+ *r->out.resume_handle = *r->in.resume_handle;
+ DEBUG(5,("_samr_EnumDomainGroups: No groups in BUILTIN\n"));
+ return status;
+ }
+
+ /* the domain group array is being allocated in the function below */
+
+ become_root();
+
+ if (dinfo->disp_info->groups == NULL) {
+ dinfo->disp_info->groups = pdb_search_groups(dinfo->disp_info);
+
+ if (dinfo->disp_info->groups == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ num_groups = pdb_search_entries(dinfo->disp_info->groups,
+ *r->in.resume_handle,
+ MAX_SAM_ENTRIES, &groups);
+ unbecome_root();
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT);
+
+ make_group_sam_entry_list(p->mem_ctx, &samr_entries,
+ num_groups, groups);
+
+ if (MAX_SAM_ENTRIES <= num_groups) {
+ status = STATUS_MORE_ENTRIES;
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ samr_array->count = num_groups;
+ samr_array->entries = samr_entries;
+
+ *r->out.num_entries = num_groups;
+ *r->out.resume_handle = num_groups + *r->in.resume_handle;
+
+ DEBUG(5,("_samr_EnumDomainGroups: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+ _samr_EnumDomainAliases
+ ********************************************************************/
+
+NTSTATUS _samr_EnumDomainAliases(struct pipes_struct *p,
+ struct samr_EnumDomainAliases *r)
+{
+ NTSTATUS status;
+ struct samr_info *dinfo;
+ struct samr_displayentry *aliases;
+ uint32_t num_aliases = 0;
+ struct samr_SamArray *samr_array = NULL;
+ struct samr_SamEntry *samr_entries = NULL;
+ struct dom_sid_buf buf;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(5,("_samr_EnumDomainAliases: sid %s\n",
+ dom_sid_str_buf(&dinfo->sid, &buf)));
+
+ samr_array = talloc_zero(p->mem_ctx, struct samr_SamArray);
+ if (!samr_array) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+
+ if (dinfo->disp_info->aliases == NULL) {
+ dinfo->disp_info->aliases = pdb_search_aliases(
+ dinfo->disp_info, &dinfo->sid);
+ if (dinfo->disp_info->aliases == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ num_aliases = pdb_search_entries(dinfo->disp_info->aliases,
+ *r->in.resume_handle,
+ MAX_SAM_ENTRIES, &aliases);
+ unbecome_root();
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT);
+
+ make_group_sam_entry_list(p->mem_ctx, &samr_entries,
+ num_aliases, aliases);
+
+ DEBUG(5,("_samr_EnumDomainAliases: %d\n", __LINE__));
+
+ if (MAX_SAM_ENTRIES <= num_aliases) {
+ status = STATUS_MORE_ENTRIES;
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ samr_array->count = num_aliases;
+ samr_array->entries = samr_entries;
+
+ *r->out.sam = samr_array;
+ *r->out.num_entries = num_aliases;
+ *r->out.resume_handle = num_aliases + *r->in.resume_handle;
+
+ return status;
+}
+
+/*******************************************************************
+ inits a samr_DispInfoGeneral structure.
+********************************************************************/
+
+static NTSTATUS init_samr_dispinfo_1(TALLOC_CTX *ctx,
+ struct samr_DispInfoGeneral *r,
+ uint32_t num_entries,
+ uint32_t start_idx,
+ struct samr_displayentry *entries)
+{
+ uint32_t i;
+
+ DEBUG(10, ("init_samr_dispinfo_1: num_entries: %d\n", num_entries));
+
+ if (num_entries == 0) {
+ return NT_STATUS_OK;
+ }
+
+ r->count = num_entries;
+
+ r->entries = talloc_zero_array(ctx, struct samr_DispEntryGeneral, num_entries);
+ if (!r->entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_entries ; i++) {
+
+ init_lsa_String(&r->entries[i].account_name,
+ entries[i].account_name);
+
+ init_lsa_String(&r->entries[i].description,
+ entries[i].description);
+
+ init_lsa_String(&r->entries[i].full_name,
+ entries[i].fullname);
+
+ r->entries[i].rid = entries[i].rid;
+ r->entries[i].acct_flags = entries[i].acct_flags;
+ r->entries[i].idx = start_idx+i+1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ inits a samr_DispInfoFull structure.
+********************************************************************/
+
+static NTSTATUS init_samr_dispinfo_2(TALLOC_CTX *ctx,
+ struct samr_DispInfoFull *r,
+ uint32_t num_entries,
+ uint32_t start_idx,
+ struct samr_displayentry *entries)
+{
+ uint32_t i;
+
+ DEBUG(10, ("init_samr_dispinfo_2: num_entries: %d\n", num_entries));
+
+ if (num_entries == 0) {
+ return NT_STATUS_OK;
+ }
+
+ r->count = num_entries;
+
+ r->entries = talloc_zero_array(ctx, struct samr_DispEntryFull, num_entries);
+ if (!r->entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_entries ; i++) {
+
+ init_lsa_String(&r->entries[i].account_name,
+ entries[i].account_name);
+
+ init_lsa_String(&r->entries[i].description,
+ entries[i].description);
+
+ r->entries[i].rid = entries[i].rid;
+ r->entries[i].acct_flags = entries[i].acct_flags;
+ r->entries[i].idx = start_idx+i+1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ inits a samr_DispInfoFullGroups structure.
+********************************************************************/
+
+static NTSTATUS init_samr_dispinfo_3(TALLOC_CTX *ctx,
+ struct samr_DispInfoFullGroups *r,
+ uint32_t num_entries,
+ uint32_t start_idx,
+ struct samr_displayentry *entries)
+{
+ uint32_t i;
+
+ DEBUG(5, ("init_samr_dispinfo_3: num_entries: %d\n", num_entries));
+
+ if (num_entries == 0) {
+ return NT_STATUS_OK;
+ }
+
+ r->count = num_entries;
+
+ r->entries = talloc_zero_array(ctx, struct samr_DispEntryFullGroup, num_entries);
+ if (!r->entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_entries ; i++) {
+
+ init_lsa_String(&r->entries[i].account_name,
+ entries[i].account_name);
+
+ init_lsa_String(&r->entries[i].description,
+ entries[i].description);
+
+ r->entries[i].rid = entries[i].rid;
+ r->entries[i].acct_flags = entries[i].acct_flags;
+ r->entries[i].idx = start_idx+i+1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ inits a samr_DispInfoAscii structure.
+********************************************************************/
+
+static NTSTATUS init_samr_dispinfo_4(TALLOC_CTX *ctx,
+ struct samr_DispInfoAscii *r,
+ uint32_t num_entries,
+ uint32_t start_idx,
+ struct samr_displayentry *entries)
+{
+ uint32_t i;
+
+ DEBUG(5, ("init_samr_dispinfo_4: num_entries: %d\n", num_entries));
+
+ if (num_entries == 0) {
+ return NT_STATUS_OK;
+ }
+
+ r->count = num_entries;
+
+ r->entries = talloc_zero_array(ctx, struct samr_DispEntryAscii, num_entries);
+ if (!r->entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_entries ; i++) {
+
+ init_lsa_AsciiStringLarge(&r->entries[i].account_name,
+ entries[i].account_name);
+
+ r->entries[i].idx = start_idx+i+1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ inits a samr_DispInfoAscii structure.
+********************************************************************/
+
+static NTSTATUS init_samr_dispinfo_5(TALLOC_CTX *ctx,
+ struct samr_DispInfoAscii *r,
+ uint32_t num_entries,
+ uint32_t start_idx,
+ struct samr_displayentry *entries)
+{
+ uint32_t i;
+
+ DEBUG(5, ("init_samr_dispinfo_5: num_entries: %d\n", num_entries));
+
+ if (num_entries == 0) {
+ return NT_STATUS_OK;
+ }
+
+ r->count = num_entries;
+
+ r->entries = talloc_zero_array(ctx, struct samr_DispEntryAscii, num_entries);
+ if (!r->entries) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_entries ; i++) {
+
+ init_lsa_AsciiStringLarge(&r->entries[i].account_name,
+ entries[i].account_name);
+
+ r->entries[i].idx = start_idx+i+1;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_QueryDisplayInfo
+ ********************************************************************/
+
+NTSTATUS _samr_QueryDisplayInfo(struct pipes_struct *p,
+ struct samr_QueryDisplayInfo *r)
+{
+ NTSTATUS status;
+ struct samr_info *dinfo;
+ uint32_t struct_size=0x20; /* W2K always reply that, client doesn't care */
+
+ uint32_t max_entries = r->in.max_entries;
+
+ union samr_DispInfo *disp_info = r->out.info;
+
+ uint32_t temp_size=0;
+ NTSTATUS disp_ret = NT_STATUS_UNSUCCESSFUL;
+ uint32_t num_account = 0;
+ enum remote_arch_types ra_type = get_remote_arch();
+ uint32_t max_sam_entries = (ra_type == RA_WIN95) ?
+ MAX_SAM_ENTRIES_W95 : MAX_SAM_ENTRIES_W2K;
+ struct samr_displayentry *entries = NULL;
+
+ DEBUG(5,("_samr_QueryDisplayInfo: %d\n", __LINE__));
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (sid_check_is_builtin(&dinfo->sid)) {
+ DEBUG(5,("_samr_QueryDisplayInfo: no users in BUILTIN\n"));
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * calculate how many entries we will return.
+ * based on
+ * - the number of entries the client asked
+ * - our limit on that
+ * - the starting point (enumeration context)
+ * - the buffer size the client will accept
+ */
+
+ /*
+ * We are a lot more like W2K. Instead of reading the SAM
+ * each time to find the records we need to send back,
+ * we read it once and link that copy to the sam handle.
+ * For large user list (over the MAX_SAM_ENTRIES)
+ * it's a definitive win.
+ * second point to notice: between enumerations
+ * our sam is now the same as it's a snapshoot.
+ * third point: got rid of the static SAM_USER_21 struct
+ * no more intermediate.
+ * con: it uses much more memory, as a full copy is stored
+ * in memory.
+ *
+ * If you want to change it, think twice and think
+ * of the second point , that's really important.
+ *
+ * JFM, 12/20/2001
+ */
+
+ if ((r->in.level < 1) || (r->in.level > 5)) {
+ DEBUG(0,("_samr_QueryDisplayInfo: Unknown info level (%u)\n",
+ (unsigned int)r->in.level ));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ /* first limit the number of entries we will return */
+ if (r->in.max_entries > max_sam_entries) {
+ DEBUG(5, ("_samr_QueryDisplayInfo: client requested %d "
+ "entries, limiting to %d\n", r->in.max_entries,
+ max_sam_entries));
+ max_entries = max_sam_entries;
+ }
+
+ /* calculate the size and limit on the number of entries we will
+ * return */
+
+ temp_size=max_entries*struct_size;
+
+ if (temp_size > r->in.buf_size) {
+ max_entries = MIN((r->in.buf_size / struct_size),max_entries);
+ DEBUG(5, ("_samr_QueryDisplayInfo: buffer size limits to "
+ "only %d entries\n", max_entries));
+ }
+
+ become_root();
+
+ /* The following done as ROOT. Don't return without unbecome_root(). */
+
+ switch (r->in.level) {
+ case 1:
+ case 4:
+ if (dinfo->disp_info->users == NULL) {
+ dinfo->disp_info->users = pdb_search_users(
+ dinfo->disp_info, ACB_NORMAL);
+ if (dinfo->disp_info->users == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ DEBUG(10,("_samr_QueryDisplayInfo: starting user enumeration at index %u\n",
+ (unsigned int)r->in.start_idx));
+ } else {
+ DEBUG(10,("_samr_QueryDisplayInfo: using cached user enumeration at index %u\n",
+ (unsigned int)r->in.start_idx));
+ }
+
+ num_account = pdb_search_entries(dinfo->disp_info->users,
+ r->in.start_idx, max_entries,
+ &entries);
+ break;
+ case 2:
+ if (dinfo->disp_info->machines == NULL) {
+ dinfo->disp_info->machines = pdb_search_users(
+ dinfo->disp_info, ACB_WSTRUST|ACB_SVRTRUST);
+ if (dinfo->disp_info->machines == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ DEBUG(10,("_samr_QueryDisplayInfo: starting machine enumeration at index %u\n",
+ (unsigned int)r->in.start_idx));
+ } else {
+ DEBUG(10,("_samr_QueryDisplayInfo: using cached machine enumeration at index %u\n",
+ (unsigned int)r->in.start_idx));
+ }
+
+ num_account = pdb_search_entries(dinfo->disp_info->machines,
+ r->in.start_idx, max_entries,
+ &entries);
+ break;
+ case 3:
+ case 5:
+ if (dinfo->disp_info->groups == NULL) {
+ dinfo->disp_info->groups = pdb_search_groups(
+ dinfo->disp_info);
+ if (dinfo->disp_info->groups == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ DEBUG(10,("_samr_QueryDisplayInfo: starting group enumeration at index %u\n",
+ (unsigned int)r->in.start_idx));
+ } else {
+ DEBUG(10,("_samr_QueryDisplayInfo: using cached group enumeration at index %u\n",
+ (unsigned int)r->in.start_idx));
+ }
+
+ num_account = pdb_search_entries(dinfo->disp_info->groups,
+ r->in.start_idx, max_entries,
+ &entries);
+ break;
+ default:
+ unbecome_root();
+ smb_panic("info class changed");
+ break;
+ }
+ unbecome_root();
+
+
+ /* Now create reply structure */
+ switch (r->in.level) {
+ case 1:
+ disp_ret = init_samr_dispinfo_1(p->mem_ctx, &disp_info->info1,
+ num_account, r->in.start_idx,
+ entries);
+ break;
+ case 2:
+ disp_ret = init_samr_dispinfo_2(p->mem_ctx, &disp_info->info2,
+ num_account, r->in.start_idx,
+ entries);
+ break;
+ case 3:
+ disp_ret = init_samr_dispinfo_3(p->mem_ctx, &disp_info->info3,
+ num_account, r->in.start_idx,
+ entries);
+ break;
+ case 4:
+ disp_ret = init_samr_dispinfo_4(p->mem_ctx, &disp_info->info4,
+ num_account, r->in.start_idx,
+ entries);
+ break;
+ case 5:
+ disp_ret = init_samr_dispinfo_5(p->mem_ctx, &disp_info->info5,
+ num_account, r->in.start_idx,
+ entries);
+ break;
+ default:
+ smb_panic("info class changed");
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(disp_ret))
+ return disp_ret;
+
+ if (max_entries <= num_account) {
+ status = STATUS_MORE_ENTRIES;
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT);
+
+ DEBUG(5, ("_samr_QueryDisplayInfo: %d\n", __LINE__));
+
+ *r->out.total_size = num_account * struct_size;
+ *r->out.returned_size = num_account ? temp_size : 0;
+
+ return status;
+}
+
+/****************************************************************
+ _samr_QueryDisplayInfo2
+****************************************************************/
+
+NTSTATUS _samr_QueryDisplayInfo2(struct pipes_struct *p,
+ struct samr_QueryDisplayInfo2 *r)
+{
+ struct samr_QueryDisplayInfo q;
+
+ q.in.domain_handle = r->in.domain_handle;
+ q.in.level = r->in.level;
+ q.in.start_idx = r->in.start_idx;
+ q.in.max_entries = r->in.max_entries;
+ q.in.buf_size = r->in.buf_size;
+
+ q.out.total_size = r->out.total_size;
+ q.out.returned_size = r->out.returned_size;
+ q.out.info = r->out.info;
+
+ return _samr_QueryDisplayInfo(p, &q);
+}
+
+/****************************************************************
+ _samr_QueryDisplayInfo3
+****************************************************************/
+
+NTSTATUS _samr_QueryDisplayInfo3(struct pipes_struct *p,
+ struct samr_QueryDisplayInfo3 *r)
+{
+ struct samr_QueryDisplayInfo q;
+
+ q.in.domain_handle = r->in.domain_handle;
+ q.in.level = r->in.level;
+ q.in.start_idx = r->in.start_idx;
+ q.in.max_entries = r->in.max_entries;
+ q.in.buf_size = r->in.buf_size;
+
+ q.out.total_size = r->out.total_size;
+ q.out.returned_size = r->out.returned_size;
+ q.out.info = r->out.info;
+
+ return _samr_QueryDisplayInfo(p, &q);
+}
+
+/*******************************************************************
+ _samr_QueryAliasInfo
+ ********************************************************************/
+
+NTSTATUS _samr_QueryAliasInfo(struct pipes_struct *p,
+ struct samr_QueryAliasInfo *r)
+{
+ struct samr_info *ainfo;
+ struct acct_info *info;
+ NTSTATUS status;
+ union samr_AliasInfo *alias_info = NULL;
+ const char *alias_name = NULL;
+ const char *alias_description = NULL;
+
+ DEBUG(5,("_samr_QueryAliasInfo: %d\n", __LINE__));
+
+ ainfo = samr_policy_handle_find(p,
+ r->in.alias_handle,
+ SAMR_HANDLE_ALIAS,
+ SAMR_ALIAS_ACCESS_LOOKUP_INFO,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ alias_info = talloc_zero(p->mem_ctx, union samr_AliasInfo);
+ if (!alias_info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ info = talloc_zero(p->mem_ctx, struct acct_info);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ status = pdb_get_aliasinfo(&ainfo->sid, info);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info);
+ return status;
+ }
+
+ alias_name = talloc_steal(r, info->acct_name);
+ alias_description = talloc_steal(r, info->acct_desc);
+ TALLOC_FREE(info);
+
+ switch (r->in.level) {
+ case ALIASINFOALL:
+ alias_info->all.name.string = alias_name;
+ alias_info->all.num_members = 1; /* ??? */
+ alias_info->all.description.string = alias_description;
+ break;
+ case ALIASINFONAME:
+ alias_info->name.string = alias_name;
+ break;
+ case ALIASINFODESCRIPTION:
+ alias_info->description.string = alias_description;
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ *r->out.info = alias_info;
+
+ DEBUG(5,("_samr_QueryAliasInfo: %d\n", __LINE__));
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_LookupNames
+ ********************************************************************/
+
+NTSTATUS _samr_LookupNames(struct pipes_struct *p,
+ struct samr_LookupNames *r)
+{
+ struct samr_info *dinfo;
+ NTSTATUS status;
+ uint32_t *rid;
+ enum lsa_SidType *type;
+ uint32_t i, num_rids = r->in.num_names;
+ struct samr_Ids rids, types;
+ uint32_t num_mapped = 0;
+ struct dom_sid_buf buf;
+
+ DEBUG(5,("_samr_LookupNames: %d\n", __LINE__));
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ 0 /* Don't know the acc_bits yet */,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (num_rids > MAX_SAM_ENTRIES) {
+ num_rids = MAX_SAM_ENTRIES;
+ DEBUG(5,("_samr_LookupNames: truncating entries to %d\n", num_rids));
+ }
+
+ rid = talloc_array(p->mem_ctx, uint32_t, num_rids);
+ NT_STATUS_HAVE_NO_MEMORY(rid);
+
+ type = talloc_array(p->mem_ctx, enum lsa_SidType, num_rids);
+ NT_STATUS_HAVE_NO_MEMORY(type);
+
+ DEBUG(5,("_samr_LookupNames: looking name on SID %s\n",
+ dom_sid_str_buf(&dinfo->sid, &buf)));
+
+ for (i = 0; i < num_rids; i++) {
+
+ status = NT_STATUS_NONE_MAPPED;
+ type[i] = SID_NAME_UNKNOWN;
+
+ rid[i] = 0xffffffff;
+
+ if (sid_check_is_builtin(&dinfo->sid)) {
+ if (lookup_builtin_name(r->in.names[i].string,
+ &rid[i]))
+ {
+ type[i] = SID_NAME_ALIAS;
+ }
+ } else {
+ lookup_global_sam_name(r->in.names[i].string, 0,
+ &rid[i], &type[i]);
+ }
+
+ if (type[i] != SID_NAME_UNKNOWN) {
+ num_mapped++;
+ }
+ }
+
+ if (num_mapped == num_rids) {
+ status = NT_STATUS_OK;
+ } else if (num_mapped == 0) {
+ status = NT_STATUS_NONE_MAPPED;
+ } else {
+ status = STATUS_SOME_UNMAPPED;
+ }
+
+ rids.count = num_rids;
+ rids.ids = rid;
+
+ types.count = num_rids;
+ types.ids = talloc_array(p->mem_ctx, uint32_t, num_rids);
+ NT_STATUS_HAVE_NO_MEMORY(type);
+ for (i = 0; i < num_rids; i++) {
+ types.ids[i] = (type[i] & 0xffffffff);
+ }
+
+ *r->out.rids = rids;
+ *r->out.types = types;
+
+ DEBUG(5,("_samr_LookupNames: %d\n", __LINE__));
+
+ return status;
+}
+
+/****************************************************************
+ _samr_ChangePasswordUser.
+
+ So old it is just not worth implementing
+ because it does not supply a plaintext and so we can't do password
+ complexity checking and cannot update other services that use a
+ plaintext password via passwd chat/pam password change/ldap password
+ sync.
+****************************************************************/
+
+NTSTATUS _samr_ChangePasswordUser(struct pipes_struct *p,
+ struct samr_ChangePasswordUser *r)
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/*******************************************************************
+ _samr_ChangePasswordUser2
+ ********************************************************************/
+
+NTSTATUS _samr_ChangePasswordUser2(struct pipes_struct *p,
+ struct samr_ChangePasswordUser2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ char *user_name = NULL;
+ char *rhost;
+ const char *wks = NULL;
+ bool encrypted;
+
+ DEBUG(5,("_samr_ChangePasswordUser2: %d\n", __LINE__));
+
+ if (!r->in.account->string) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (r->in.server && r->in.server->string) {
+ wks = r->in.server->string;
+ }
+
+ DEBUG(5,("_samr_ChangePasswordUser2: user: %s wks: %s\n", user_name, wks));
+
+ /*
+ * Pass the user through the NT -> unix user mapping
+ * function.
+ */
+
+ (void)map_username(talloc_tos(), r->in.account->string, &user_name);
+ if (!user_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rhost = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (rhost == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ encrypted = dcerpc_is_transport_encrypted(session_info);
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED &&
+ !encrypted) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * UNIX username case mangling not required, pass_oem_change
+ * is case insensitive.
+ */
+
+ status = pass_oem_change(user_name,
+ rhost,
+ r->in.lm_password->data,
+ r->in.lm_verifier->hash,
+ r->in.nt_password->data,
+ r->in.nt_verifier->hash,
+ NULL);
+
+ DEBUG(5,("_samr_ChangePasswordUser2: %d\n", __LINE__));
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ return status;
+}
+
+/****************************************************************
+ _samr_OemChangePasswordUser2
+****************************************************************/
+
+NTSTATUS _samr_OemChangePasswordUser2(struct pipes_struct *p,
+ struct samr_OemChangePasswordUser2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ NTSTATUS status;
+ char *user_name = NULL;
+ const char *wks = NULL;
+ char *rhost;
+ bool encrypted;
+
+ DEBUG(5,("_samr_OemChangePasswordUser2: %d\n", __LINE__));
+
+ if (!r->in.account->string) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (r->in.server && r->in.server->string) {
+ wks = r->in.server->string;
+ }
+
+ DEBUG(5,("_samr_OemChangePasswordUser2: user: %s wks: %s\n", user_name, wks));
+
+ /*
+ * Pass the user through the NT -> unix user mapping
+ * function.
+ */
+
+ (void)map_username(talloc_tos(), r->in.account->string, &user_name);
+ if (!user_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * UNIX username case mangling not required, pass_oem_change
+ * is case insensitive.
+ */
+
+ if (!r->in.hash || !r->in.password) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ rhost = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (rhost == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ encrypted = dcerpc_is_transport_encrypted(session_info);
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED &&
+ !encrypted) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = pass_oem_change(user_name,
+ rhost,
+ r->in.password->data,
+ r->in.hash->hash,
+ 0,
+ 0,
+ NULL);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ DEBUG(5,("_samr_OemChangePasswordUser2: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+ _samr_ChangePasswordUser3
+ ********************************************************************/
+
+NTSTATUS _samr_ChangePasswordUser3(struct pipes_struct *p,
+ struct samr_ChangePasswordUser3 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ NTSTATUS status;
+ char *user_name = NULL;
+ const char *wks = NULL;
+ enum samPwdChangeReason reject_reason;
+ struct samr_DomInfo1 *dominfo = NULL;
+ struct userPwdChangeFailureInformation *reject = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t tmp;
+ char *rhost;
+
+ DEBUG(5,("_samr_ChangePasswordUser3: %d\n", __LINE__));
+
+ if (!r->in.account->string) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (r->in.server && r->in.server->string) {
+ wks = r->in.server->string;
+ }
+
+ DEBUG(5,("_samr_ChangePasswordUser3: user: %s wks: %s\n", user_name, wks));
+
+ /*
+ * Pass the user through the NT -> unix user mapping
+ * function.
+ */
+
+ (void)map_username(talloc_tos(), r->in.account->string, &user_name);
+ if (!user_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rhost = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (rhost == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * UNIX username case mangling not required, pass_oem_change
+ * is case insensitive.
+ */
+
+ status = pass_oem_change(user_name,
+ rhost,
+ r->in.lm_password->data,
+ r->in.lm_verifier->hash,
+ r->in.nt_password->data,
+ r->in.nt_verifier->hash,
+ &reject_reason);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PASSWORD_RESTRICTION) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_ACCOUNT_RESTRICTION)) {
+
+ time_t u_expire, u_min_age;
+ uint32_t account_policy_temp;
+
+ dominfo = talloc_zero(p->mem_ctx, struct samr_DomInfo1);
+ if (!dominfo) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ reject = talloc_zero(p->mem_ctx,
+ struct userPwdChangeFailureInformation);
+ if (!reject) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+
+ /* AS ROOT !!! */
+
+ pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &tmp);
+ dominfo->min_password_length = tmp;
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &tmp);
+ dominfo->password_history_length = tmp;
+
+ pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS,
+ &dominfo->password_properties);
+
+ pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &account_policy_temp);
+ u_expire = account_policy_temp;
+
+ pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, &account_policy_temp);
+ u_min_age = account_policy_temp;
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ unix_to_nt_time_abs((NTTIME *)&dominfo->max_password_age, u_expire);
+ unix_to_nt_time_abs((NTTIME *)&dominfo->min_password_age, u_min_age);
+
+ if (lp_check_password_script(talloc_tos(), lp_sub)
+ && *lp_check_password_script(talloc_tos(), lp_sub)) {
+ dominfo->password_properties |= DOMAIN_PASSWORD_COMPLEX;
+ }
+
+ reject->extendedFailureReason = reject_reason;
+
+ *r->out.dominfo = dominfo;
+ *r->out.reject = reject;
+ }
+
+ DEBUG(5,("_samr_ChangePasswordUser3: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+makes a SAMR_R_LOOKUP_RIDS structure.
+********************************************************************/
+
+static bool make_samr_lookup_rids(TALLOC_CTX *ctx, uint32_t num_names,
+ const char **names,
+ struct lsa_String **lsa_name_array_p)
+{
+ struct lsa_String *lsa_name_array = NULL;
+ uint32_t i;
+
+ *lsa_name_array_p = NULL;
+
+ if (num_names != 0) {
+ lsa_name_array = talloc_zero_array(ctx, struct lsa_String, num_names);
+ if (!lsa_name_array) {
+ return false;
+ }
+ }
+
+ for (i = 0; i < num_names; i++) {
+ DEBUG(10, ("names[%d]:%s\n", i, names[i] && *names[i] ? names[i] : ""));
+ init_lsa_String(&lsa_name_array[i], names[i]);
+ }
+
+ *lsa_name_array_p = lsa_name_array;
+
+ return true;
+}
+
+/*******************************************************************
+ _samr_LookupRids
+ ********************************************************************/
+
+NTSTATUS _samr_LookupRids(struct pipes_struct *p,
+ struct samr_LookupRids *r)
+{
+ struct samr_info *dinfo;
+ NTSTATUS status;
+ const char **names;
+ enum lsa_SidType *attrs = NULL;
+ uint32_t *wire_attrs = NULL;
+ int num_rids = (int)r->in.num_rids;
+ int i;
+ struct lsa_Strings names_array;
+ struct samr_Ids types_array;
+ struct lsa_String *lsa_names = NULL;
+
+ DEBUG(5,("_samr_LookupRids: %d\n", __LINE__));
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ 0 /* Don't know the acc_bits yet */,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (num_rids > 1000) {
+ DEBUG(0, ("Got asked for %d rids (more than 1000) -- according "
+ "to samba4 idl this is not possible\n", num_rids));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (num_rids) {
+ names = talloc_zero_array(p->mem_ctx, const char *, num_rids);
+ attrs = talloc_zero_array(p->mem_ctx, enum lsa_SidType, num_rids);
+ wire_attrs = talloc_zero_array(p->mem_ctx, uint32_t, num_rids);
+
+ if ((names == NULL) || (attrs == NULL) || (wire_attrs==NULL))
+ return NT_STATUS_NO_MEMORY;
+ } else {
+ names = NULL;
+ attrs = NULL;
+ wire_attrs = NULL;
+ }
+
+ become_root(); /* lookup_sid can require root privs */
+ status = pdb_lookup_rids(&dinfo->sid, num_rids, r->in.rids,
+ names, attrs);
+ unbecome_root();
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED) && (num_rids == 0)) {
+ status = NT_STATUS_OK;
+ }
+
+ if (!make_samr_lookup_rids(p->mem_ctx, num_rids, names,
+ &lsa_names)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Convert from enum lsa_SidType to uint32_t for wire format. */
+ for (i = 0; i < num_rids; i++) {
+ wire_attrs[i] = (uint32_t)attrs[i];
+ }
+
+ names_array.count = num_rids;
+ names_array.names = lsa_names;
+
+ types_array.count = num_rids;
+ types_array.ids = wire_attrs;
+
+ *r->out.names = names_array;
+ *r->out.types = types_array;
+
+ DEBUG(5,("_samr_LookupRids: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+ _samr_OpenUser
+********************************************************************/
+
+NTSTATUS _samr_OpenUser(struct pipes_struct *p,
+ struct samr_OpenUser *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct samu *sampass=NULL;
+ struct dom_sid sid;
+ struct samr_info *dinfo;
+ struct security_descriptor *psd = NULL;
+ uint32_t acc_granted;
+ uint32_t des_access = r->in.access_mask;
+ uint32_t extra_access = 0;
+ size_t sd_size;
+ bool ret;
+ NTSTATUS nt_status;
+
+ /* These two privileges, if != SEC_PRIV_INVALID, indicate
+ * privileges that the user must have to complete this
+ * operation in defience of the fixed ACL */
+ enum sec_privilege needed_priv_1, needed_priv_2;
+ NTSTATUS status;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if ( !(sampass = samu_new( p->mem_ctx )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* append the user's RID to it */
+
+ if (!sid_compose(&sid, &dinfo->sid, r->in.rid))
+ return NT_STATUS_NO_SUCH_USER;
+
+ /* check if access can be granted as requested by client. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &usr_generic_mapping, &sid, SAMR_USR_RIGHTS_WRITE_PW);
+ se_map_generic(&des_access, &usr_generic_mapping);
+
+ /*
+ * Get the sampass first as we need to check privileges
+ * based on what kind of user object this is.
+ * But don't reveal info too early if it didn't exist.
+ */
+
+ become_root();
+ ret=pdb_getsampwsid(sampass, &sid);
+ unbecome_root();
+
+ needed_priv_1 = SEC_PRIV_INVALID;
+ needed_priv_2 = SEC_PRIV_INVALID;
+ /*
+ * We do the override access checks on *open*, not at
+ * SetUserInfo time.
+ */
+ if (ret) {
+ uint32_t acb_info = pdb_get_acct_ctrl(sampass);
+
+ if (acb_info & ACB_WSTRUST) {
+ /*
+ * SeMachineAccount is needed to add
+ * GENERIC_RIGHTS_USER_WRITE to a machine
+ * account.
+ */
+ needed_priv_1 = SEC_PRIV_MACHINE_ACCOUNT;
+ }
+ if (acb_info & ACB_NORMAL) {
+ /*
+ * SeAddUsers is needed to add
+ * GENERIC_RIGHTS_USER_WRITE to a normal
+ * account.
+ */
+ needed_priv_1 = SEC_PRIV_ADD_USERS;
+ }
+ /*
+ * Cheat - we have not set a specific privilege for
+ * server (BDC) or domain trust account, so allow
+ * GENERIC_RIGHTS_USER_WRITE if pipe user is in
+ * DOMAIN_RID_ADMINS.
+ */
+ if (acb_info & (ACB_SVRTRUST|ACB_DOMTRUST)) {
+ if (lp_enable_privileges() &&
+ nt_token_check_domain_rid(
+ session_info->security_token,
+ DOMAIN_RID_ADMINS)) {
+ des_access &= ~GENERIC_RIGHTS_USER_WRITE;
+ extra_access = GENERIC_RIGHTS_USER_WRITE;
+ DEBUG(4,("_samr_OpenUser: Allowing "
+ "GENERIC_RIGHTS_USER_WRITE for "
+ "rid admins\n"));
+ }
+ }
+ }
+
+ TALLOC_FREE(sampass);
+
+ nt_status = access_check_object(psd, session_info->security_token,
+ needed_priv_1, needed_priv_2,
+ GENERIC_RIGHTS_USER_WRITE, des_access,
+ &acc_granted, "_samr_OpenUser");
+
+ if ( !NT_STATUS_IS_OK(nt_status) )
+ return nt_status;
+
+ /* check that the SID exists in our domain. */
+ if (ret == False) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /* If we did the rid admins hack above, allow access. */
+ acc_granted |= extra_access;
+
+ status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_USER,
+ acc_granted,
+ &sid,
+ NULL,
+ r->out.user_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ *************************************************************************/
+
+static NTSTATUS init_samr_parameters_string(TALLOC_CTX *mem_ctx,
+ DATA_BLOB *blob,
+ struct lsa_BinaryString **_r)
+{
+ struct lsa_BinaryString *r;
+
+ if (!blob || !_r) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ r = talloc_zero(mem_ctx, struct lsa_BinaryString);
+ if (!r) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ r->array = talloc_zero_array(mem_ctx, uint16_t, blob->length/2);
+ if (!r->array) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memcpy(r->array, blob->data, blob->length);
+ r->size = blob->length;
+ r->length = blob->length;
+
+ if (!r->array) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *_r = r;
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ *************************************************************************/
+
+static struct samr_LogonHours get_logon_hours_from_pdb(TALLOC_CTX *mem_ctx,
+ struct samu *pw)
+{
+ struct samr_LogonHours hours;
+ const int units_per_week = 168;
+
+ ZERO_STRUCT(hours);
+ hours.bits = talloc_array(mem_ctx, uint8_t, units_per_week);
+ if (!hours.bits) {
+ return hours;
+ }
+
+ hours.units_per_week = units_per_week;
+ memset(hours.bits, 0xFF, units_per_week);
+
+ if (pdb_get_hours(pw)) {
+ memcpy(hours.bits, pdb_get_hours(pw),
+ MIN(pdb_get_hours_len(pw), units_per_week));
+ }
+
+ return hours;
+}
+
+/*************************************************************************
+ get_user_info_1.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_1(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo1 *r,
+ struct samu *pw,
+ struct dom_sid *domain_sid)
+{
+ const struct dom_sid *sid_group;
+ uint32_t primary_gid;
+
+ become_root();
+ sid_group = pdb_get_group_sid(pw);
+ unbecome_root();
+
+ if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) {
+ struct dom_sid_buf buf1, buf2;
+
+ DEBUG(0, ("get_user_info_1: User %s has Primary Group SID %s, \n"
+ "which conflicts with the domain sid %s. Failing operation.\n",
+ pdb_get_username(pw),
+ dom_sid_str_buf(sid_group, &buf1),
+ dom_sid_str_buf(domain_sid, &buf2)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw));
+ r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw));
+ r->primary_gid = primary_gid;
+ r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw));
+ r->comment.string = talloc_strdup(mem_ctx, pdb_get_comment(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_2.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_2(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo2 *r,
+ struct samu *pw)
+{
+ r->comment.string = talloc_strdup(mem_ctx, pdb_get_comment(pw));
+ r->reserved.string = NULL;
+ r->country_code = pdb_get_country_code(pw);
+ r->code_page = pdb_get_code_page(pw);
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_3.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_3(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo3 *r,
+ struct samu *pw,
+ struct dom_sid *domain_sid)
+{
+ const struct dom_sid *sid_user, *sid_group;
+ uint32_t rid, primary_gid;
+ struct dom_sid_buf buf1, buf2;
+
+ sid_user = pdb_get_user_sid(pw);
+
+ if (!sid_peek_check_rid(domain_sid, sid_user, &rid)) {
+ DEBUG(0, ("get_user_info_3: User %s has SID %s, \nwhich conflicts with "
+ "the domain sid %s. Failing operation.\n",
+ pdb_get_username(pw),
+ dom_sid_str_buf(sid_user, &buf1),
+ dom_sid_str_buf(domain_sid, &buf2)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ become_root();
+ sid_group = pdb_get_group_sid(pw);
+ unbecome_root();
+
+ if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) {
+ DEBUG(0, ("get_user_info_3: User %s has Primary Group SID %s, \n"
+ "which conflicts with the domain sid %s. Failing operation.\n",
+ pdb_get_username(pw),
+ dom_sid_str_buf(sid_group, &buf1),
+ dom_sid_str_buf(domain_sid, &buf2)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ unix_to_nt_time(&r->last_logon, pdb_get_logon_time(pw));
+ unix_to_nt_time(&r->last_logoff, pdb_get_logoff_time(pw));
+ unix_to_nt_time(&r->last_password_change, pdb_get_pass_last_set_time(pw));
+ unix_to_nt_time(&r->allow_password_change, pdb_get_pass_can_change_time(pw));
+ unix_to_nt_time(&r->force_password_change, pdb_get_pass_must_change_time(pw));
+
+ r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw));
+ r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw));
+ r->home_directory.string= talloc_strdup(mem_ctx, pdb_get_homedir(pw));
+ r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw));
+ r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw));
+ r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw));
+ r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw));
+
+ r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw);
+ r->rid = rid;
+ r->primary_gid = primary_gid;
+ r->acct_flags = pdb_get_acct_ctrl(pw);
+ r->bad_password_count = pdb_get_bad_password_count(pw);
+ r->logon_count = pdb_get_logon_count(pw);
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_4.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_4(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo4 *r,
+ struct samu *pw)
+{
+ r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw);
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_5.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_5(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo5 *r,
+ struct samu *pw,
+ struct dom_sid *domain_sid)
+{
+ const struct dom_sid *sid_user, *sid_group;
+ uint32_t rid, primary_gid;
+ struct dom_sid_buf buf1, buf2;
+
+ sid_user = pdb_get_user_sid(pw);
+
+ if (!sid_peek_check_rid(domain_sid, sid_user, &rid)) {
+ DEBUG(0, ("get_user_info_5: User %s has SID %s, \nwhich conflicts with "
+ "the domain sid %s. Failing operation.\n",
+ pdb_get_username(pw),
+ dom_sid_str_buf(sid_user, &buf1),
+ dom_sid_str_buf(domain_sid, &buf2)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ become_root();
+ sid_group = pdb_get_group_sid(pw);
+ unbecome_root();
+
+ if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) {
+ DEBUG(0, ("get_user_info_5: User %s has Primary Group SID %s, \n"
+ "which conflicts with the domain sid %s. Failing operation.\n",
+ pdb_get_username(pw),
+ dom_sid_str_buf(sid_group, &buf1),
+ dom_sid_str_buf(domain_sid, &buf2)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ unix_to_nt_time(&r->last_logon, pdb_get_logon_time(pw));
+ unix_to_nt_time(&r->last_logoff, pdb_get_logoff_time(pw));
+ unix_to_nt_time(&r->acct_expiry, pdb_get_kickoff_time(pw));
+ unix_to_nt_time(&r->last_password_change, pdb_get_pass_last_set_time(pw));
+
+ r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw));
+ r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw));
+ r->home_directory.string= talloc_strdup(mem_ctx, pdb_get_homedir(pw));
+ r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw));
+ r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw));
+ r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw));
+ r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw));
+ r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw));
+
+ r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw);
+ r->rid = rid;
+ r->primary_gid = primary_gid;
+ r->acct_flags = pdb_get_acct_ctrl(pw);
+ r->bad_password_count = pdb_get_bad_password_count(pw);
+ r->logon_count = pdb_get_logon_count(pw);
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_6.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_6(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo6 *r,
+ struct samu *pw)
+{
+ r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw));
+ r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_7. Safe. Only gives out account_name.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_7(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo7 *r,
+ struct samu *smbpass)
+{
+ r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(smbpass));
+ if (!r->account_name.string) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_8.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_8(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo8 *r,
+ struct samu *pw)
+{
+ r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_9. Only gives out primary group SID.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_9(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo9 *r,
+ struct samu *smbpass)
+{
+ r->primary_gid = pdb_get_group_rid(smbpass);
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_10.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_10(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo10 *r,
+ struct samu *pw)
+{
+ r->home_directory.string= talloc_strdup(mem_ctx, pdb_get_homedir(pw));
+ r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_11.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_11(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo11 *r,
+ struct samu *pw)
+{
+ r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_12.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_12(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo12 *r,
+ struct samu *pw)
+{
+ r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_13.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_13(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo13 *r,
+ struct samu *pw)
+{
+ r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_14.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_14(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo14 *r,
+ struct samu *pw)
+{
+ r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_16. Safe. Only gives out acb bits.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_16(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo16 *r,
+ struct samu *smbpass)
+{
+ r->acct_flags = pdb_get_acct_ctrl(smbpass);
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_17.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_17(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo17 *r,
+ struct samu *pw)
+{
+ unix_to_nt_time(&r->acct_expiry, pdb_get_kickoff_time(pw));
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_18. OK - this is the killer as it gives out password info.
+ Ensure that this is only allowed on an encrypted connection with a root
+ user. JRA.
+ *************************************************************************/
+
+static NTSTATUS get_user_info_18(struct pipes_struct *p,
+ TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo18 *r,
+ struct dom_sid *user_sid)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct samu *smbpass=NULL;
+ bool ret;
+ const uint8_t *nt_pass = NULL;
+ const uint8_t *lm_pass = NULL;
+
+ ZERO_STRUCTP(r);
+
+ if (p->transport != NCALRPC) {
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ if (!security_token_is_system(session_info->security_token)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Do *NOT* do become_root()/unbecome_root() here ! JRA.
+ */
+
+ if ( !(smbpass = samu_new( mem_ctx )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = pdb_getsampwsid(smbpass, user_sid);
+
+ if (ret == False) {
+ struct dom_sid_buf buf;
+ DEBUG(4, ("User %s not found\n",
+ dom_sid_str_buf(user_sid, &buf)));
+ TALLOC_FREE(smbpass);
+ return root_mode() ? NT_STATUS_NO_SUCH_USER : NT_STATUS_ACCESS_DENIED;
+ }
+
+ DEBUG(3,("User:[%s] 0x%x\n", pdb_get_username(smbpass), pdb_get_acct_ctrl(smbpass) ));
+
+ if ( pdb_get_acct_ctrl(smbpass) & ACB_DISABLED) {
+ TALLOC_FREE(smbpass);
+ return NT_STATUS_ACCOUNT_DISABLED;
+ }
+
+ lm_pass = pdb_get_lanman_passwd(smbpass);
+ if (lm_pass != NULL) {
+ memcpy(r->lm_pwd.hash, lm_pass, 16);
+ r->lm_pwd_active = true;
+ }
+
+ nt_pass = pdb_get_nt_passwd(smbpass);
+ if (nt_pass != NULL) {
+ memcpy(r->nt_pwd.hash, nt_pass, 16);
+ r->nt_pwd_active = true;
+ }
+ r->password_expired = 0; /* FIXME */
+
+ TALLOC_FREE(smbpass);
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************************
+ get_user_info_20
+ *************************************************************************/
+
+static NTSTATUS get_user_info_20(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo20 *r,
+ struct samu *sampass)
+{
+ const char *munged_dial = NULL;
+ DATA_BLOB blob;
+ NTSTATUS status;
+ struct lsa_BinaryString *parameters = NULL;
+
+ ZERO_STRUCTP(r);
+
+ munged_dial = pdb_get_munged_dial(sampass);
+
+ DEBUG(3,("User:[%s] has [%s] (length: %d)\n", pdb_get_username(sampass),
+ munged_dial, (int)strlen(munged_dial)));
+
+ if (munged_dial) {
+ blob = base64_decode_data_blob(munged_dial);
+ } else {
+ blob = data_blob_string_const_null("");
+ }
+
+ status = init_samr_parameters_string(mem_ctx, &blob, &parameters);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->parameters = *parameters;
+
+ return NT_STATUS_OK;
+}
+
+
+/*************************************************************************
+ get_user_info_21
+ *************************************************************************/
+
+static NTSTATUS get_user_info_21(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo21 *r,
+ struct samu *pw,
+ struct dom_sid *domain_sid,
+ uint32_t acc_granted)
+{
+ NTSTATUS status;
+ const struct dom_sid *sid_user, *sid_group;
+ uint32_t rid, primary_gid;
+ NTTIME force_password_change;
+ time_t must_change_time;
+ struct lsa_BinaryString *parameters = NULL;
+ const char *munged_dial = NULL;
+ DATA_BLOB blob;
+ struct dom_sid_buf buf1, buf2;
+
+ ZERO_STRUCTP(r);
+
+ sid_user = pdb_get_user_sid(pw);
+
+ if (!sid_peek_check_rid(domain_sid, sid_user, &rid)) {
+ DEBUG(0, ("get_user_info_21: User %s has SID %s, \nwhich conflicts with "
+ "the domain sid %s. Failing operation.\n",
+ pdb_get_username(pw),
+ dom_sid_str_buf(sid_user, &buf1),
+ dom_sid_str_buf(domain_sid, &buf2)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ become_root();
+ sid_group = pdb_get_group_sid(pw);
+ unbecome_root();
+
+ if (!sid_peek_check_rid(domain_sid, sid_group, &primary_gid)) {
+ DEBUG(0, ("get_user_info_21: User %s has Primary Group SID %s, \n"
+ "which conflicts with the domain sid %s. Failing operation.\n",
+ pdb_get_username(pw),
+ dom_sid_str_buf(sid_group, &buf1),
+ dom_sid_str_buf(domain_sid, &buf2)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ unix_to_nt_time(&r->last_logon, pdb_get_logon_time(pw));
+ unix_to_nt_time(&r->last_logoff, pdb_get_logoff_time(pw));
+ unix_to_nt_time(&r->acct_expiry, pdb_get_kickoff_time(pw));
+ unix_to_nt_time(&r->last_password_change, pdb_get_pass_last_set_time(pw));
+ unix_to_nt_time(&r->allow_password_change, pdb_get_pass_can_change_time(pw));
+
+ must_change_time = pdb_get_pass_must_change_time(pw);
+ if (pdb_is_password_change_time_max(must_change_time)) {
+ unix_to_nt_time_abs(&force_password_change, must_change_time);
+ } else {
+ unix_to_nt_time(&force_password_change, must_change_time);
+ }
+
+ munged_dial = pdb_get_munged_dial(pw);
+ if (munged_dial) {
+ blob = base64_decode_data_blob(munged_dial);
+ } else {
+ blob = data_blob_string_const_null("");
+ }
+
+ status = init_samr_parameters_string(mem_ctx, &blob, &parameters);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->force_password_change = force_password_change;
+
+ r->account_name.string = talloc_strdup(mem_ctx, pdb_get_username(pw));
+ r->full_name.string = talloc_strdup(mem_ctx, pdb_get_fullname(pw));
+ r->home_directory.string = talloc_strdup(mem_ctx, pdb_get_homedir(pw));
+ r->home_drive.string = talloc_strdup(mem_ctx, pdb_get_dir_drive(pw));
+ r->logon_script.string = talloc_strdup(mem_ctx, pdb_get_logon_script(pw));
+ r->profile_path.string = talloc_strdup(mem_ctx, pdb_get_profile_path(pw));
+ r->description.string = talloc_strdup(mem_ctx, pdb_get_acct_desc(pw));
+ r->workstations.string = talloc_strdup(mem_ctx, pdb_get_workstations(pw));
+ r->comment.string = talloc_strdup(mem_ctx, pdb_get_comment(pw));
+
+ r->logon_hours = get_logon_hours_from_pdb(mem_ctx, pw);
+ r->parameters = *parameters;
+ r->rid = rid;
+ r->primary_gid = primary_gid;
+ r->acct_flags = pdb_get_acct_ctrl(pw);
+ r->bad_password_count = pdb_get_bad_password_count(pw);
+ r->logon_count = pdb_get_logon_count(pw);
+ r->fields_present = pdb_build_fields_present(pw);
+ r->password_expired = (pdb_get_pass_must_change_time(pw) == 0) ?
+ PASS_MUST_CHANGE_AT_NEXT_LOGON : 0;
+ r->country_code = pdb_get_country_code(pw);
+ r->code_page = pdb_get_code_page(pw);
+ r->lm_password_set = 0;
+ r->nt_password_set = 0;
+
+#if 0
+
+ /*
+ Look at a user on a real NT4 PDC with usrmgr, press
+ 'ok'. Then you will see that fields_present is set to
+ 0x08f827fa. Look at the user immediately after that again,
+ and you will see that 0x00fffff is returned. This solves
+ the problem that you get access denied after having looked
+ at the user.
+ -- Volker
+ */
+
+#endif
+
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_QueryUserInfo
+ ********************************************************************/
+
+NTSTATUS _samr_QueryUserInfo(struct pipes_struct *p,
+ struct samr_QueryUserInfo *r)
+{
+ NTSTATUS status;
+ union samr_UserInfo *user_info = NULL;
+ struct samr_info *uinfo;
+ struct dom_sid domain_sid;
+ uint32_t rid;
+ bool ret = false;
+ struct samu *pwd = NULL;
+ uint32_t acc_required, acc_granted;
+ struct dom_sid_buf buf;
+
+ switch (r->in.level) {
+ case 1: /* UserGeneralInformation */
+ /* USER_READ_GENERAL */
+ acc_required = SAMR_USER_ACCESS_GET_NAME_ETC;
+ break;
+ case 2: /* UserPreferencesInformation */
+ /* USER_READ_PREFERENCES | USER_READ_GENERAL */
+ acc_required = SAMR_USER_ACCESS_GET_LOCALE |
+ SAMR_USER_ACCESS_GET_NAME_ETC;
+ break;
+ case 3: /* UserLogonInformation */
+ /* USER_READ_GENERAL | USER_READ_PREFERENCES | USER_READ_LOGON | USER_READ_ACCOUNT */
+ acc_required = SAMR_USER_ACCESS_GET_NAME_ETC |
+ SAMR_USER_ACCESS_GET_LOCALE |
+ SAMR_USER_ACCESS_GET_LOGONINFO |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES;
+ break;
+ case 4: /* UserLogonHoursInformation */
+ /* USER_READ_LOGON */
+ acc_required = SAMR_USER_ACCESS_GET_LOGONINFO;
+ break;
+ case 5: /* UserAccountInformation */
+ /* USER_READ_GENERAL | USER_READ_PREFERENCES | USER_READ_LOGON | USER_READ_ACCOUNT */
+ acc_required = SAMR_USER_ACCESS_GET_NAME_ETC |
+ SAMR_USER_ACCESS_GET_LOCALE |
+ SAMR_USER_ACCESS_GET_LOGONINFO |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES;
+ break;
+ case 6: /* UserNameInformation */
+ case 7: /* UserAccountNameInformation */
+ case 8: /* UserFullNameInformation */
+ case 9: /* UserPrimaryGroupInformation */
+ case 13: /* UserAdminCommentInformation */
+ /* USER_READ_GENERAL */
+ acc_required = SAMR_USER_ACCESS_GET_NAME_ETC;
+ break;
+ case 10: /* UserHomeInformation */
+ case 11: /* UserScriptInformation */
+ case 12: /* UserProfileInformation */
+ case 14: /* UserWorkStationsInformation */
+ /* USER_READ_LOGON */
+ acc_required = SAMR_USER_ACCESS_GET_LOGONINFO;
+ break;
+ case 16: /* UserControlInformation */
+ case 17: /* UserExpiresInformation */
+ case 20: /* UserParametersInformation */
+ /* USER_READ_ACCOUNT */
+ acc_required = SAMR_USER_ACCESS_GET_ATTRIBUTES;
+ break;
+ case 21: /* UserAllInformation */
+ /* FIXME! - gd */
+ acc_required = SAMR_USER_ACCESS_GET_ATTRIBUTES;
+ break;
+ case 18: /* UserInternal1Information */
+ /* FIXME! - gd */
+ acc_required = SAMR_USER_ACCESS_GET_ATTRIBUTES;
+ break;
+ case 23: /* UserInternal4Information */
+ case 24: /* UserInternal4InformationNew */
+ case 25: /* UserInternal4InformationNew */
+ case 26: /* UserInternal5InformationNew */
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ uinfo = samr_policy_handle_find(p,
+ r->in.user_handle,
+ SAMR_HANDLE_USER,
+ acc_required,
+ &acc_granted,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ domain_sid = uinfo->sid;
+
+ sid_split_rid(&domain_sid, &rid);
+
+ if (!sid_check_is_in_our_sam(&uinfo->sid))
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+
+ DEBUG(5,("_samr_QueryUserInfo: sid:%s\n",
+ dom_sid_str_buf(&uinfo->sid, &buf)));
+
+ user_info = talloc_zero(p->mem_ctx, union samr_UserInfo);
+ if (!user_info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5,("_samr_QueryUserInfo: user info level: %d\n", r->in.level));
+
+ if (!(pwd = samu_new(p->mem_ctx))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = pdb_getsampwsid(pwd, &uinfo->sid);
+ unbecome_root();
+
+ if (ret == false) {
+ DEBUG(4,("User %s not found\n",
+ dom_sid_str_buf(&uinfo->sid, &buf)));
+ TALLOC_FREE(pwd);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ DEBUG(3,("User:[%s]\n", pdb_get_username(pwd)));
+
+ samr_clear_sam_passwd(pwd);
+
+ switch (r->in.level) {
+ case 1:
+ status = get_user_info_1(p->mem_ctx, &user_info->info1, pwd, &domain_sid);
+ break;
+ case 2:
+ status = get_user_info_2(p->mem_ctx, &user_info->info2, pwd);
+ break;
+ case 3:
+ status = get_user_info_3(p->mem_ctx, &user_info->info3, pwd, &domain_sid);
+ break;
+ case 4:
+ status = get_user_info_4(p->mem_ctx, &user_info->info4, pwd);
+ break;
+ case 5:
+ status = get_user_info_5(p->mem_ctx, &user_info->info5, pwd, &domain_sid);
+ break;
+ case 6:
+ status = get_user_info_6(p->mem_ctx, &user_info->info6, pwd);
+ break;
+ case 7:
+ status = get_user_info_7(p->mem_ctx, &user_info->info7, pwd);
+ break;
+ case 8:
+ status = get_user_info_8(p->mem_ctx, &user_info->info8, pwd);
+ break;
+ case 9:
+ status = get_user_info_9(p->mem_ctx, &user_info->info9, pwd);
+ break;
+ case 10:
+ status = get_user_info_10(p->mem_ctx, &user_info->info10, pwd);
+ break;
+ case 11:
+ status = get_user_info_11(p->mem_ctx, &user_info->info11, pwd);
+ break;
+ case 12:
+ status = get_user_info_12(p->mem_ctx, &user_info->info12, pwd);
+ break;
+ case 13:
+ status = get_user_info_13(p->mem_ctx, &user_info->info13, pwd);
+ break;
+ case 14:
+ status = get_user_info_14(p->mem_ctx, &user_info->info14, pwd);
+ break;
+ case 16:
+ status = get_user_info_16(p->mem_ctx, &user_info->info16, pwd);
+ break;
+ case 17:
+ status = get_user_info_17(p->mem_ctx, &user_info->info17, pwd);
+ break;
+ case 18:
+ /* level 18 is special */
+ status = get_user_info_18(p, p->mem_ctx, &user_info->info18,
+ &uinfo->sid);
+ break;
+ case 20:
+ status = get_user_info_20(p->mem_ctx, &user_info->info20, pwd);
+ break;
+ case 21:
+ status = get_user_info_21(p->mem_ctx, &user_info->info21, pwd, &domain_sid, acc_granted);
+ break;
+ default:
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ *r->out.info = user_info;
+
+ done:
+ TALLOC_FREE(pwd);
+
+ DEBUG(5,("_samr_QueryUserInfo: %d\n", __LINE__));
+
+ return status;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_QueryUserInfo2(struct pipes_struct *p,
+ struct samr_QueryUserInfo2 *r)
+{
+ struct samr_QueryUserInfo u;
+
+ u.in.user_handle = r->in.user_handle;
+ u.in.level = r->in.level;
+ u.out.info = r->out.info;
+
+ return _samr_QueryUserInfo(p, &u);
+}
+
+/*******************************************************************
+ _samr_GetGroupsForUser
+ ********************************************************************/
+
+NTSTATUS _samr_GetGroupsForUser(struct pipes_struct *p,
+ struct samr_GetGroupsForUser *r)
+{
+ struct samr_info *uinfo;
+ struct samu *sam_pass=NULL;
+ struct dom_sid *sids;
+ struct samr_RidWithAttribute dom_gid;
+ struct samr_RidWithAttribute *gids = NULL;
+ uint32_t primary_group_rid;
+ uint32_t num_groups = 0;
+ gid_t *unix_gids;
+ uint32_t i, num_gids;
+ bool ret;
+ NTSTATUS result;
+ bool success = False;
+ struct dom_sid_buf buf;
+
+ struct samr_RidWithAttributeArray *rids = NULL;
+
+ /*
+ * from the SID in the request:
+ * we should send back the list of DOMAIN GROUPS
+ * the user is a member of
+ *
+ * and only the DOMAIN GROUPS
+ * no ALIASES !!! neither aliases of the domain
+ * nor aliases of the builtin SID
+ *
+ * JFM, 12/2/2001
+ */
+
+ DEBUG(5,("_samr_GetGroupsForUser: %d\n", __LINE__));
+
+ uinfo = samr_policy_handle_find(p,
+ r->in.user_handle,
+ SAMR_HANDLE_USER,
+ SAMR_USER_ACCESS_GET_GROUPS,
+ NULL,
+ &result);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ rids = talloc_zero(p->mem_ctx, struct samr_RidWithAttributeArray);
+ if (!rids) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!sid_check_is_in_our_sam(&uinfo->sid))
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+
+ if ( !(sam_pass = samu_new( p->mem_ctx )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = pdb_getsampwsid(sam_pass, &uinfo->sid);
+ unbecome_root();
+
+ if (!ret) {
+ DEBUG(10, ("pdb_getsampwsid failed for %s\n",
+ dom_sid_str_buf(&uinfo->sid, &buf)));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ sids = NULL;
+
+ /* make both calls inside the root block */
+ become_root();
+ result = pdb_enum_group_memberships(p->mem_ctx, sam_pass,
+ &sids, &unix_gids, &num_groups);
+ if ( NT_STATUS_IS_OK(result) ) {
+ success = sid_peek_check_rid(get_global_sam_sid(),
+ pdb_get_group_sid(sam_pass),
+ &primary_group_rid);
+ }
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("pdb_enum_group_memberships failed for %s\n",
+ dom_sid_str_buf(&uinfo->sid, &buf)));
+ return result;
+ }
+
+ if ( !success ) {
+ DEBUG(5, ("Group sid %s for user %s not in our domain\n",
+ dom_sid_str_buf(pdb_get_group_sid(sam_pass), &buf),
+ pdb_get_username(sam_pass)));
+ TALLOC_FREE(sam_pass);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ gids = NULL;
+ num_gids = 0;
+
+ dom_gid.attributes = SE_GROUP_DEFAULT_FLAGS;
+ dom_gid.rid = primary_group_rid;
+ ADD_TO_ARRAY(p->mem_ctx, struct samr_RidWithAttribute, dom_gid, &gids, &num_gids);
+
+ for (i=0; i<num_groups; i++) {
+
+ if (!sid_peek_check_rid(get_global_sam_sid(),
+ &(sids[i]), &dom_gid.rid)) {
+ DEBUG(10, ("Found sid %s not in our domain\n",
+ dom_sid_str_buf(&sids[i], &buf)));
+ continue;
+ }
+
+ if (dom_gid.rid == primary_group_rid) {
+ /* We added the primary group directly from the
+ * sam_account. The other SIDs are unique from
+ * enum_group_memberships */
+ continue;
+ }
+
+ ADD_TO_ARRAY(p->mem_ctx, struct samr_RidWithAttribute, dom_gid, &gids, &num_gids);
+ }
+
+ rids->count = num_gids;
+ rids->rids = gids;
+
+ *r->out.rids = rids;
+
+ DEBUG(5,("_samr_GetGroupsForUser: %d\n", __LINE__));
+
+ return result;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static uint32_t samr_get_server_role(void)
+{
+ uint32_t role = ROLE_DOMAIN_PDC;
+
+ if (lp_server_role() == ROLE_DOMAIN_BDC) {
+ role = ROLE_DOMAIN_BDC;
+ }
+
+ return role;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_1(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t account_policy_temp;
+ time_t u_expire, u_min_age;
+
+ become_root();
+
+ /* AS ROOT !!! */
+
+ pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN, &account_policy_temp);
+ r->min_password_length = account_policy_temp;
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &account_policy_temp);
+ r->password_history_length = account_policy_temp;
+
+ pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS,
+ &r->password_properties);
+
+ pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &account_policy_temp);
+ u_expire = account_policy_temp;
+
+ pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, &account_policy_temp);
+ u_min_age = account_policy_temp;
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ unix_to_nt_time_abs((NTTIME *)&r->max_password_age, u_expire);
+ unix_to_nt_time_abs((NTTIME *)&r->min_password_age, u_min_age);
+
+ if (lp_check_password_script(talloc_tos(), lp_sub) && *lp_check_password_script(talloc_tos(), lp_sub)){
+ r->password_properties |= DOMAIN_PASSWORD_COMPLEX;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_2(TALLOC_CTX *mem_ctx,
+ struct samr_DomGeneralInformation *r,
+ struct samr_info *dinfo)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t u_logout;
+ time_t seq_num;
+
+ become_root();
+
+ /* AS ROOT !!! */
+
+ r->num_users = count_sam_users(dinfo->disp_info, ACB_NORMAL);
+ r->num_groups = count_sam_groups(dinfo->disp_info);
+ r->num_aliases = count_sam_aliases(dinfo->disp_info);
+
+ pdb_get_account_policy(PDB_POLICY_TIME_TO_LOGOUT, &u_logout);
+
+ unix_to_nt_time_abs(&r->force_logoff_time, u_logout);
+
+ if (!pdb_get_seq_num(&seq_num)) {
+ seq_num = time(NULL);
+ }
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ r->oem_information.string = lp_server_string(r, lp_sub);
+ r->domain_name.string = lp_workgroup();
+ r->primary.string = lp_netbios_name();
+ r->sequence_num = seq_num;
+ r->domain_server_state = DOMAIN_SERVER_ENABLED;
+ r->role = (enum samr_Role) samr_get_server_role();
+ r->unknown3 = 1;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_3(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo3 *r)
+{
+ uint32_t u_logout;
+
+ become_root();
+
+ /* AS ROOT !!! */
+
+ {
+ uint32_t ul;
+ pdb_get_account_policy(PDB_POLICY_TIME_TO_LOGOUT, &ul);
+ u_logout = (time_t)ul;
+ }
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ unix_to_nt_time_abs(&r->force_logoff_time, u_logout);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_4(TALLOC_CTX *mem_ctx,
+ struct samr_DomOEMInformation *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ r->oem_information.string = lp_server_string(r, lp_sub);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_5(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo5 *r)
+{
+ r->domain_name.string = get_global_sam_name();
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_6(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo6 *r)
+{
+ /* NT returns its own name when a PDC. win2k and later
+ * only the name of the PDC if itself is a BDC (samba4
+ * idl) */
+ r->primary.string = lp_netbios_name();
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_7(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo7 *r)
+{
+ r->role = (enum samr_Role) samr_get_server_role();
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_8(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo8 *r)
+{
+ time_t seq_num;
+
+ become_root();
+
+ /* AS ROOT !!! */
+
+ if (!pdb_get_seq_num(&seq_num)) {
+ seq_num = time(NULL);
+ }
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ r->sequence_num = seq_num;
+ r->domain_create_time = 0;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_9(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo9 *r)
+{
+ r->domain_server_state = DOMAIN_SERVER_ENABLED;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_11(TALLOC_CTX *mem_ctx,
+ struct samr_DomGeneralInformation2 *r,
+ struct samr_info *dinfo)
+{
+ NTSTATUS status;
+ uint32_t account_policy_temp;
+ time_t u_lock_duration, u_reset_time;
+
+ status = query_dom_info_2(mem_ctx, &r->general, dinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* AS ROOT !!! */
+
+ become_root();
+
+ pdb_get_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, &account_policy_temp);
+ u_lock_duration = account_policy_temp;
+ if (u_lock_duration != -1) {
+ u_lock_duration *= 60;
+ }
+
+ pdb_get_account_policy(PDB_POLICY_RESET_COUNT_TIME, &account_policy_temp);
+ u_reset_time = account_policy_temp * 60;
+
+ pdb_get_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, &account_policy_temp);
+ r->lockout_threshold = account_policy_temp;
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ unix_to_nt_time_abs(&r->lockout_duration, u_lock_duration);
+ unix_to_nt_time_abs(&r->lockout_window, u_reset_time);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_12(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *r)
+{
+ uint32_t account_policy_temp;
+ time_t u_lock_duration, u_reset_time;
+
+ become_root();
+
+ /* AS ROOT !!! */
+
+ pdb_get_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, &account_policy_temp);
+ u_lock_duration = account_policy_temp;
+ if (u_lock_duration != -1) {
+ u_lock_duration *= 60;
+ }
+
+ pdb_get_account_policy(PDB_POLICY_RESET_COUNT_TIME, &account_policy_temp);
+ u_reset_time = account_policy_temp * 60;
+
+ pdb_get_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT, &account_policy_temp);
+ r->lockout_threshold = account_policy_temp;
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ unix_to_nt_time_abs(&r->lockout_duration, u_lock_duration);
+ unix_to_nt_time_abs(&r->lockout_window, u_reset_time);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS query_dom_info_13(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo13 *r)
+{
+ time_t seq_num;
+
+ become_root();
+
+ /* AS ROOT !!! */
+
+ if (!pdb_get_seq_num(&seq_num)) {
+ seq_num = time(NULL);
+ }
+
+ /* !AS ROOT */
+
+ unbecome_root();
+
+ r->sequence_num = seq_num;
+ r->domain_create_time = 0;
+ r->modified_count_at_last_promotion = 0;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_QueryDomainInfo
+ ********************************************************************/
+
+NTSTATUS _samr_QueryDomainInfo(struct pipes_struct *p,
+ struct samr_QueryDomainInfo *r)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct samr_info *dinfo;
+ union samr_DomainInfo *dom_info;
+
+ uint32_t acc_required;
+
+ DEBUG(5,("_samr_QueryDomainInfo: %d\n", __LINE__));
+
+ switch (r->in.level) {
+ case 1: /* DomainPasswordInformation */
+ case 12: /* DomainLockoutInformation */
+ /* DOMAIN_READ_PASSWORD_PARAMETERS */
+ acc_required = SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1;
+ break;
+ case 11: /* DomainGeneralInformation2 */
+ /* DOMAIN_READ_PASSWORD_PARAMETERS |
+ * DOMAIN_READ_OTHER_PARAMETERS */
+ acc_required = SAMR_DOMAIN_ACCESS_LOOKUP_INFO_1 |
+ SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2;
+ break;
+ case 2: /* DomainGeneralInformation */
+ case 3: /* DomainLogoffInformation */
+ case 4: /* DomainOemInformation */
+ case 5: /* DomainReplicationInformation */
+ case 6: /* DomainReplicationInformation */
+ case 7: /* DomainServerRoleInformation */
+ case 8: /* DomainModifiedInformation */
+ case 9: /* DomainStateInformation */
+ case 10: /* DomainUasInformation */
+ case 13: /* DomainModifiedInformation2 */
+ /* DOMAIN_READ_OTHER_PARAMETERS */
+ acc_required = SAMR_DOMAIN_ACCESS_LOOKUP_INFO_2;
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ acc_required,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dom_info = talloc_zero(p->mem_ctx, union samr_DomainInfo);
+ if (!dom_info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ status = query_dom_info_1(p->mem_ctx, &dom_info->info1);
+ break;
+ case 2:
+ status = query_dom_info_2(p->mem_ctx, &dom_info->general, dinfo);
+ break;
+ case 3:
+ status = query_dom_info_3(p->mem_ctx, &dom_info->info3);
+ break;
+ case 4:
+ status = query_dom_info_4(p->mem_ctx, &dom_info->oem);
+ break;
+ case 5:
+ status = query_dom_info_5(p->mem_ctx, &dom_info->info5);
+ break;
+ case 6:
+ status = query_dom_info_6(p->mem_ctx, &dom_info->info6);
+ break;
+ case 7:
+ status = query_dom_info_7(p->mem_ctx, &dom_info->info7);
+ break;
+ case 8:
+ status = query_dom_info_8(p->mem_ctx, &dom_info->info8);
+ break;
+ case 9:
+ status = query_dom_info_9(p->mem_ctx, &dom_info->info9);
+ break;
+ case 11:
+ status = query_dom_info_11(p->mem_ctx, &dom_info->general2, dinfo);
+ break;
+ case 12:
+ status = query_dom_info_12(p->mem_ctx, &dom_info->info12);
+ break;
+ case 13:
+ status = query_dom_info_13(p->mem_ctx, &dom_info->info13);
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *r->out.info = dom_info;
+
+ DEBUG(5,("_samr_QueryDomainInfo: %d\n", __LINE__));
+
+ return status;
+}
+
+/* W2k3 seems to use the same check for all 3 objects that can be created via
+ * SAMR, if you try to create for example "Dialup" as an alias it says
+ * "NT_STATUS_USER_EXISTS". This is racy, but we can't really lock the user
+ * database. */
+
+static NTSTATUS can_create(TALLOC_CTX *mem_ctx, const char *new_name)
+{
+ enum lsa_SidType type;
+ bool result;
+
+ DEBUG(10, ("Checking whether [%s] can be created\n", new_name));
+
+ become_root();
+ /* Lookup in our local databases (LOOKUP_NAME_REMOTE not set)
+ * whether the name already exists */
+ result = lookup_name(mem_ctx, new_name, LOOKUP_NAME_LOCAL,
+ NULL, NULL, NULL, &type);
+ unbecome_root();
+
+ if (!result) {
+ DEBUG(10, ("%s does not exist, can create it\n", new_name));
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(5, ("trying to create %s, exists as %s\n",
+ new_name, sid_type_lookup(type)));
+
+ if (type == SID_NAME_DOM_GRP) {
+ return NT_STATUS_GROUP_EXISTS;
+ }
+ if (type == SID_NAME_ALIAS) {
+ return NT_STATUS_ALIAS_EXISTS;
+ }
+
+ /* Yes, the default is NT_STATUS_USER_EXISTS */
+ return NT_STATUS_USER_EXISTS;
+}
+
+/*******************************************************************
+ _samr_CreateUser2
+ ********************************************************************/
+
+NTSTATUS _samr_CreateUser2(struct pipes_struct *p,
+ struct samr_CreateUser2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const char *account = NULL;
+ struct dom_sid sid;
+ uint32_t acb_info = r->in.acct_flags;
+ struct samr_info *dinfo;
+ NTSTATUS nt_status;
+ uint32_t acc_granted;
+ struct security_descriptor *psd;
+ size_t sd_size;
+ /* check this, when giving away 'add computer to domain' privs */
+ uint32_t des_access = GENERIC_RIGHTS_USER_ALL_ACCESS;
+ bool can_add_account = False;
+
+ /* Which privilege is needed to override the ACL? */
+ enum sec_privilege needed_priv = SEC_PRIV_INVALID;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_CREATE_USER,
+ NULL,
+ &nt_status);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ if (sid_check_is_builtin(&dinfo->sid)) {
+ DEBUG(5,("_samr_CreateUser2: Refusing user create in BUILTIN\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!(acb_info == ACB_NORMAL || acb_info == ACB_DOMTRUST ||
+ acb_info == ACB_WSTRUST || acb_info == ACB_SVRTRUST)) {
+ /* Match Win2k, and return NT_STATUS_INVALID_PARAMETER if
+ this parameter is not an account type */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ account = r->in.account_name->string;
+ if (account == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = can_create(p->mem_ctx, account);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ /* determine which user right we need to check based on the acb_info */
+
+ if (root_mode()) {
+ can_add_account = true;
+ } else if (acb_info & ACB_WSTRUST) {
+ needed_priv = SEC_PRIV_MACHINE_ACCOUNT;
+ can_add_account = security_token_has_privilege(
+ session_info->security_token, needed_priv);
+ } else if (acb_info & ACB_NORMAL &&
+ (account[strlen(account)-1] != '$')) {
+ /* usrmgr.exe (and net rpc trustdom add) creates a normal user
+ account for domain trusts and changes the ACB flags later */
+ needed_priv = SEC_PRIV_ADD_USERS;
+ can_add_account = security_token_has_privilege(
+ session_info->security_token, needed_priv);
+ } else if (lp_enable_privileges()) {
+ /* implicit assumption of a BDC or domain trust account here
+ * (we already check the flags earlier) */
+ /* only Domain Admins can add a BDC or domain trust */
+ can_add_account = nt_token_check_domain_rid(
+ session_info->security_token,
+ DOMAIN_RID_ADMINS );
+ }
+
+ DEBUG(5, ("_samr_CreateUser2: %s can add this account : %s\n",
+ uidtoname(session_info->unix_token->uid),
+ can_add_account ? "True":"False" ));
+
+ if (!can_add_account) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /********** BEGIN Admin BLOCK **********/
+
+ (void)winbind_off();
+ become_root();
+ nt_status = pdb_create_user(p->mem_ctx, account, acb_info,
+ r->out.rid);
+ unbecome_root();
+ (void)winbind_on();
+
+ /********** END Admin BLOCK **********/
+
+ /* now check for failure */
+
+ if ( !NT_STATUS_IS_OK(nt_status) )
+ return nt_status;
+
+ /* Get the user's SID */
+
+ sid_compose(&sid, get_global_sam_sid(), *r->out.rid);
+
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &usr_generic_mapping,
+ &sid, SAMR_USR_RIGHTS_WRITE_PW);
+ se_map_generic(&des_access, &usr_generic_mapping);
+
+ /*
+ * JRA - TESTME. We just created this user so we
+ * had rights to create them. Do we need to check
+ * any further access on this object ? Can't we
+ * just assume we have all the rights we need ?
+ */
+
+ nt_status = access_check_object(psd, session_info->security_token,
+ needed_priv, SEC_PRIV_INVALID,
+ GENERIC_RIGHTS_USER_WRITE, des_access,
+ &acc_granted, "_samr_CreateUser2");
+
+ if ( !NT_STATUS_IS_OK(nt_status) ) {
+ return nt_status;
+ }
+
+ nt_status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_USER,
+ acc_granted,
+ &sid,
+ NULL,
+ r->out.user_handle);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ /* After a "set" ensure we have no cached display info. */
+ force_flush_samr_cache(&sid);
+
+ *r->out.access_granted = acc_granted;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_CreateUser(struct pipes_struct *p,
+ struct samr_CreateUser *r)
+{
+ struct samr_CreateUser2 c;
+ uint32_t access_granted;
+
+ c.in.domain_handle = r->in.domain_handle;
+ c.in.account_name = r->in.account_name;
+ c.in.acct_flags = ACB_NORMAL;
+ c.in.access_mask = r->in.access_mask;
+ c.out.user_handle = r->out.user_handle;
+ c.out.access_granted = &access_granted;
+ c.out.rid = r->out.rid;
+
+ return _samr_CreateUser2(p, &c);
+}
+
+/*******************************************************************
+ _samr_Connect
+ ********************************************************************/
+
+NTSTATUS _samr_Connect(struct pipes_struct *p,
+ struct samr_Connect *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ uint32_t acc_granted;
+ uint32_t des_access = r->in.access_mask;
+ NTSTATUS status;
+
+ /* Access check */
+
+ if (!pipe_access_check(p)) {
+ DEBUG(3, ("access denied to _samr_Connect\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* don't give away the farm but this is probably ok. The SAMR_ACCESS_ENUM_DOMAINS
+ was observed from a win98 client trying to enumerate users (when configured
+ user level access control on shares) --jerry */
+
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ se_map_generic( &des_access, &sam_generic_mapping );
+
+ acc_granted = des_access & (SAMR_ACCESS_ENUM_DOMAINS
+ |SAMR_ACCESS_LOOKUP_DOMAIN);
+
+ /* set up the SAMR connect_anon response */
+ status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_CONNECT,
+ acc_granted,
+ NULL,
+ NULL,
+ r->out.connect_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_Connect2
+ ********************************************************************/
+
+NTSTATUS _samr_Connect2(struct pipes_struct *p,
+ struct samr_Connect2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *psd = NULL;
+ uint32_t acc_granted;
+ uint32_t des_access = r->in.access_mask;
+ NTSTATUS nt_status;
+ size_t sd_size;
+ const char *fn = "_samr_Connect2";
+
+ switch (dce_call->pkt.u.request.opnum) {
+ case NDR_SAMR_CONNECT2:
+ fn = "_samr_Connect2";
+ break;
+ case NDR_SAMR_CONNECT3:
+ fn = "_samr_Connect3";
+ break;
+ case NDR_SAMR_CONNECT4:
+ fn = "_samr_Connect4";
+ break;
+ case NDR_SAMR_CONNECT5:
+ fn = "_samr_Connect5";
+ break;
+ }
+
+ DEBUG(5,("%s: %d\n", fn, __LINE__));
+
+ /* Access check */
+
+ if (!pipe_access_check(p)) {
+ DEBUG(3, ("access denied to %s\n", fn));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &sam_generic_mapping, NULL, 0);
+ se_map_generic(&des_access, &sam_generic_mapping);
+
+ nt_status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_INVALID, SEC_PRIV_INVALID,
+ 0, des_access, &acc_granted, fn);
+
+ if ( !NT_STATUS_IS_OK(nt_status) )
+ return nt_status;
+
+ nt_status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_CONNECT,
+ acc_granted,
+ NULL,
+ NULL,
+ r->out.connect_handle);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ DEBUG(5,("%s: %d\n", fn, __LINE__));
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+ _samr_Connect3
+****************************************************************/
+
+NTSTATUS _samr_Connect3(struct pipes_struct *p,
+ struct samr_Connect3 *r)
+{
+ struct samr_Connect2 c;
+
+ c.in.system_name = r->in.system_name;
+ c.in.access_mask = r->in.access_mask;
+ c.out.connect_handle = r->out.connect_handle;
+
+ return _samr_Connect2(p, &c);
+}
+
+/*******************************************************************
+ _samr_Connect4
+ ********************************************************************/
+
+NTSTATUS _samr_Connect4(struct pipes_struct *p,
+ struct samr_Connect4 *r)
+{
+ struct samr_Connect2 c;
+
+ c.in.system_name = r->in.system_name;
+ c.in.access_mask = r->in.access_mask;
+ c.out.connect_handle = r->out.connect_handle;
+
+ return _samr_Connect2(p, &c);
+}
+
+/*******************************************************************
+ _samr_Connect5
+ ********************************************************************/
+
+NTSTATUS _samr_Connect5(struct pipes_struct *p,
+ struct samr_Connect5 *r)
+{
+ NTSTATUS status;
+ struct samr_Connect2 c;
+ struct samr_ConnectInfo1 info1;
+
+ info1.client_version = SAMR_CONNECT_AFTER_W2K;
+ info1.supported_features = 0;
+
+ c.in.system_name = r->in.system_name;
+ c.in.access_mask = r->in.access_mask;
+ c.out.connect_handle = r->out.connect_handle;
+
+ *r->out.level_out = 1;
+
+ status = _samr_Connect2(p, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->out.info_out->info1 = info1;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ _samr_LookupDomain
+ **********************************************************************/
+
+NTSTATUS _samr_LookupDomain(struct pipes_struct *p,
+ struct samr_LookupDomain *r)
+{
+ NTSTATUS status;
+ const char *domain_name;
+ struct dom_sid *sid = NULL;
+ struct dom_sid_buf buf;
+
+ /* win9x user manager likes to use SAMR_ACCESS_ENUM_DOMAINS here.
+ Reverted that change so we will work with RAS servers again */
+
+ (void)samr_policy_handle_find(p,
+ r->in.connect_handle,
+ SAMR_HANDLE_CONNECT,
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ domain_name = r->in.domain_name->string;
+ if (!domain_name) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sid = talloc_zero(p->mem_ctx, struct dom_sid2);
+ if (!sid) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (strequal(domain_name, builtin_domain_name())) {
+ sid_copy(sid, &global_sid_Builtin);
+ } else {
+ if (!secrets_fetch_domain_sid(domain_name, sid)) {
+ status = NT_STATUS_NO_SUCH_DOMAIN;
+ }
+ }
+
+ DEBUG(2,("Returning domain sid for domain %s -> %s\n", domain_name,
+ dom_sid_str_buf(sid, &buf)));
+
+ *r->out.sid = sid;
+
+ return status;
+}
+
+/**********************************************************************
+ _samr_EnumDomains
+ **********************************************************************/
+
+NTSTATUS _samr_EnumDomains(struct pipes_struct *p,
+ struct samr_EnumDomains *r)
+{
+ NTSTATUS status;
+ uint32_t num_entries = 2;
+ struct samr_SamEntry *entry_array = NULL;
+ struct samr_SamArray *sam;
+
+ (void)samr_policy_handle_find(p,
+ r->in.connect_handle,
+ SAMR_HANDLE_CONNECT,
+ SAMR_ACCESS_ENUM_DOMAINS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ sam = talloc_zero(p->mem_ctx, struct samr_SamArray);
+ if (!sam) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ entry_array = talloc_zero_array(p->mem_ctx,
+ struct samr_SamEntry,
+ num_entries);
+ if (!entry_array) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ entry_array[0].idx = 0;
+ init_lsa_String(&entry_array[0].name, get_global_sam_name());
+
+ entry_array[1].idx = 1;
+ init_lsa_String(&entry_array[1].name, "Builtin");
+
+ sam->count = num_entries;
+ sam->entries = entry_array;
+
+ *r->out.sam = sam;
+ *r->out.num_entries = num_entries;
+
+ return status;
+}
+
+/*******************************************************************
+ _samr_OpenAlias
+ ********************************************************************/
+
+NTSTATUS _samr_OpenAlias(struct pipes_struct *p,
+ struct samr_OpenAlias *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct dom_sid sid;
+ uint32_t alias_rid = r->in.rid;
+ struct samr_info *dinfo;
+ struct security_descriptor *psd = NULL;
+ uint32_t acc_granted;
+ uint32_t des_access = r->in.access_mask;
+ size_t sd_size;
+ NTSTATUS status;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* append the alias' RID to it */
+
+ if (!sid_compose(&sid, &dinfo->sid, alias_rid))
+ return NT_STATUS_NO_SUCH_ALIAS;
+
+ /*check if access can be granted as requested by client. */
+
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &ali_generic_mapping, NULL, 0);
+ se_map_generic(&des_access,&ali_generic_mapping);
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_ADD_USERS, SEC_PRIV_INVALID,
+ GENERIC_RIGHTS_ALIAS_ALL_ACCESS,
+ des_access, &acc_granted, "_samr_OpenAlias");
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ {
+ /* Check we actually have the requested alias */
+ enum lsa_SidType type;
+ bool result;
+ gid_t gid;
+
+ become_root();
+ result = lookup_sid(NULL, &sid, NULL, NULL, &type);
+ unbecome_root();
+
+ if (!result || (type != SID_NAME_ALIAS)) {
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ /* make sure there is a mapping */
+
+ if ( !sid_to_gid( &sid, &gid ) ) {
+ return NT_STATUS_NO_SUCH_ALIAS;
+ }
+
+ }
+
+ status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_ALIAS,
+ acc_granted,
+ &sid,
+ NULL,
+ r->out.alias_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ set_user_info_2
+ ********************************************************************/
+
+static NTSTATUS set_user_info_2(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo2 *id2,
+ struct samu *pwd)
+{
+ if (id2 == NULL) {
+ DEBUG(5,("set_user_info_2: NULL id2\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id2_to_sam_passwd(pwd, id2);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_4
+ ********************************************************************/
+
+static NTSTATUS set_user_info_4(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo4 *id4,
+ struct samu *pwd)
+{
+ if (id4 == NULL) {
+ DEBUG(5,("set_user_info_2: NULL id4\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id4_to_sam_passwd(pwd, id4);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_6
+ ********************************************************************/
+
+static NTSTATUS set_user_info_6(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo6 *id6,
+ struct samu *pwd)
+{
+ if (id6 == NULL) {
+ DEBUG(5,("set_user_info_6: NULL id6\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id6_to_sam_passwd(pwd, id6);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_7
+ ********************************************************************/
+
+static NTSTATUS set_user_info_7(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo7 *id7,
+ struct samu *pwd)
+{
+ NTSTATUS rc;
+
+ if (id7 == NULL) {
+ DEBUG(5, ("set_user_info_7: NULL id7\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!id7->account_name.string) {
+ DEBUG(5, ("set_user_info_7: failed to get new username\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* check to see if the new username already exists. Note: we can't
+ reliably lock all backends, so there is potentially the
+ possibility that a user can be created in between this check and
+ the rename. The rename should fail, but may not get the
+ exact same failure status code. I think this is small enough
+ of a window for this type of operation and the results are
+ simply that the rename fails with a slightly different status
+ code (like UNSUCCESSFUL instead of ALREADY_EXISTS). */
+
+ rc = can_create(mem_ctx, id7->account_name.string);
+
+ /* when there is nothing to change, we're done here */
+ if (NT_STATUS_EQUAL(rc, NT_STATUS_USER_EXISTS) &&
+ strequal(id7->account_name.string, pdb_get_username(pwd))) {
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(rc)) {
+ return rc;
+ }
+
+ rc = pdb_rename_sam_account(pwd, id7->account_name.string);
+
+ return rc;
+}
+
+/*******************************************************************
+ set_user_info_8
+ ********************************************************************/
+
+static NTSTATUS set_user_info_8(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo8 *id8,
+ struct samu *pwd)
+{
+ if (id8 == NULL) {
+ DEBUG(5,("set_user_info_8: NULL id8\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id8_to_sam_passwd(pwd, id8);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_10
+ ********************************************************************/
+
+static NTSTATUS set_user_info_10(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo10 *id10,
+ struct samu *pwd)
+{
+ if (id10 == NULL) {
+ DEBUG(5,("set_user_info_8: NULL id10\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id10_to_sam_passwd(pwd, id10);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_11
+ ********************************************************************/
+
+static NTSTATUS set_user_info_11(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo11 *id11,
+ struct samu *pwd)
+{
+ if (id11 == NULL) {
+ DEBUG(5,("set_user_info_11: NULL id11\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id11_to_sam_passwd(pwd, id11);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_12
+ ********************************************************************/
+
+static NTSTATUS set_user_info_12(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo12 *id12,
+ struct samu *pwd)
+{
+ if (id12 == NULL) {
+ DEBUG(5,("set_user_info_12: NULL id12\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id12_to_sam_passwd(pwd, id12);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_13
+ ********************************************************************/
+
+static NTSTATUS set_user_info_13(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo13 *id13,
+ struct samu *pwd)
+{
+ if (id13 == NULL) {
+ DEBUG(5,("set_user_info_13: NULL id13\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id13_to_sam_passwd(pwd, id13);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_14
+ ********************************************************************/
+
+static NTSTATUS set_user_info_14(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo14 *id14,
+ struct samu *pwd)
+{
+ if (id14 == NULL) {
+ DEBUG(5,("set_user_info_14: NULL id14\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id14_to_sam_passwd(pwd, id14);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_16
+ ********************************************************************/
+
+static NTSTATUS set_user_info_16(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo16 *id16,
+ struct samu *pwd)
+{
+ if (id16 == NULL) {
+ DEBUG(5,("set_user_info_16: NULL id16\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id16_to_sam_passwd(pwd, id16);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_17
+ ********************************************************************/
+
+static NTSTATUS set_user_info_17(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo17 *id17,
+ struct samu *pwd)
+{
+ if (id17 == NULL) {
+ DEBUG(5,("set_user_info_17: NULL id17\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id17_to_sam_passwd(pwd, id17);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_18
+ ********************************************************************/
+
+static NTSTATUS set_user_info_18(struct samr_UserInfo18 *id18,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *session_key,
+ struct samu *pwd)
+{
+ int rc;
+
+ if (id18 == NULL) {
+ DEBUG(2, ("set_user_info_18: id18 is NULL\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id18->nt_pwd_active || id18->lm_pwd_active) {
+ if (!session_key->length) {
+ return NT_STATUS_NO_USER_SESSION_KEY;
+ }
+ }
+
+ if (id18->nt_pwd_active) {
+ DATA_BLOB in = data_blob_const(id18->nt_pwd.hash, 16);
+ uint8_t outbuf[16] = { 0, };
+ DATA_BLOB out = data_blob_const(outbuf, sizeof(outbuf));
+
+ rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+
+ if (!pdb_set_nt_passwd(pwd, out.data, PDB_CHANGED)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED);
+ }
+
+ if (id18->lm_pwd_active) {
+ DATA_BLOB in = data_blob_const(id18->lm_pwd.hash, 16);
+ uint8_t outbuf[16] = { 0, };
+ DATA_BLOB out = data_blob_const(outbuf, sizeof(outbuf));
+
+ rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+
+ if (!pdb_set_lanman_passwd(pwd, out.data, PDB_CHANGED)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED);
+ }
+
+ copy_id18_to_sam_passwd(pwd, id18);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_20
+ ********************************************************************/
+
+static NTSTATUS set_user_info_20(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo20 *id20,
+ struct samu *pwd)
+{
+ if (id20 == NULL) {
+ DEBUG(5,("set_user_info_20: NULL id20\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ copy_id20_to_sam_passwd(pwd, id20);
+
+ return pdb_update_sam_account(pwd);
+}
+
+/*******************************************************************
+ set_user_info_21
+ ********************************************************************/
+
+static NTSTATUS set_user_info_21(struct samr_UserInfo21 *id21,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *session_key,
+ struct samu *pwd)
+{
+ NTSTATUS status;
+ int rc;
+
+ if (id21 == NULL) {
+ DEBUG(5, ("set_user_info_21: NULL id21\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id21->fields_present == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id21->fields_present & SAMR_FIELD_LAST_PWD_CHANGE) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (id21->fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) {
+ if (id21->nt_password_set) {
+ DATA_BLOB in = data_blob_const(
+ id21->nt_owf_password.array, 16);
+ uint8_t outbuf[16] = { 0, };
+ DATA_BLOB out = data_blob_const(
+ outbuf, sizeof(outbuf));
+
+ if ((id21->nt_owf_password.length != 16) ||
+ (id21->nt_owf_password.size != 16)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!session_key->length) {
+ return NT_STATUS_NO_USER_SESSION_KEY;
+ }
+
+ rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+
+ pdb_set_nt_passwd(pwd, out.data, PDB_CHANGED);
+ pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED);
+ }
+ }
+
+ if (id21->fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT) {
+ if (id21->lm_password_set) {
+ DATA_BLOB in = data_blob_const(
+ id21->lm_owf_password.array, 16);
+ uint8_t outbuf[16] = { 0, };
+ DATA_BLOB out = data_blob_const(
+ outbuf, sizeof(outbuf));
+
+ if ((id21->lm_owf_password.length != 16) ||
+ (id21->lm_owf_password.size != 16)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!session_key->length) {
+ return NT_STATUS_NO_USER_SESSION_KEY;
+ }
+
+ rc = sess_crypt_blob(&out, &in, session_key, SAMBA_GNUTLS_DECRYPT);
+ if (rc != 0) {
+ return gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+
+ pdb_set_lanman_passwd(pwd, out.data, PDB_CHANGED);
+ pdb_set_pass_last_set_time(pwd, time(NULL), PDB_CHANGED);
+ }
+ }
+
+ /* we need to separately check for an account rename first */
+
+ if (id21->account_name.string &&
+ (!strequal(id21->account_name.string, pdb_get_username(pwd))))
+ {
+
+ /* check to see if the new username already exists. Note: we can't
+ reliably lock all backends, so there is potentially the
+ possibility that a user can be created in between this check and
+ the rename. The rename should fail, but may not get the
+ exact same failure status code. I think this is small enough
+ of a window for this type of operation and the results are
+ simply that the rename fails with a slightly different status
+ code (like UNSUCCESSFUL instead of ALREADY_EXISTS). */
+
+ status = can_create(mem_ctx, id21->account_name.string);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = pdb_rename_sam_account(pwd, id21->account_name.string);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("set_user_info_21: failed to rename account: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ /* set the new username so that later
+ functions can work on the new account */
+ pdb_set_username(pwd, id21->account_name.string, PDB_SET);
+ }
+
+ copy_id21_to_sam_passwd("INFO_21", pwd, id21);
+
+ /*
+ * The funny part about the previous two calls is
+ * that pwd still has the password hashes from the
+ * passdb entry. These have not been updated from
+ * id21. I don't know if they need to be set. --jerry
+ */
+
+ if ( IS_SAM_CHANGED(pwd, PDB_GROUPSID) ) {
+ status = pdb_set_unix_primary_group(mem_ctx, pwd);
+ if ( !NT_STATUS_IS_OK(status) ) {
+ return status;
+ }
+ }
+
+ /* Don't worry about writing out the user account since the
+ primary group SID is generated solely from the user's Unix
+ primary group. */
+
+ /* write the change out */
+ if(!NT_STATUS_IS_OK(status = pdb_update_sam_account(pwd))) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ set_user_info_23
+ ********************************************************************/
+
+static NTSTATUS set_user_info_23(TALLOC_CTX *mem_ctx,
+ struct samr_UserInfo23 *id23,
+ const char *rhost,
+ struct samu *pwd)
+{
+ char *plaintext_buf = NULL;
+ size_t len = 0;
+ uint32_t acct_ctrl;
+ NTSTATUS status;
+
+ if (id23 == NULL) {
+ DEBUG(5, ("set_user_info_23: NULL id23\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id23->info.fields_present == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id23->info.fields_present & SAMR_FIELD_LAST_PWD_CHANGE) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if ((id23->info.fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+ (id23->info.fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT)) {
+
+ DEBUG(5, ("Attempting administrator password change (level 23) for user %s\n",
+ pdb_get_username(pwd)));
+
+ if (!decode_pw_buffer(mem_ctx,
+ id23->password.data,
+ &plaintext_buf,
+ &len,
+ CH_UTF16)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ if (!pdb_set_plaintext_passwd (pwd, plaintext_buf)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ copy_id23_to_sam_passwd(pwd, id23);
+
+ acct_ctrl = pdb_get_acct_ctrl(pwd);
+
+ /* if it's a trust account, don't update /etc/passwd */
+ if ( ( (acct_ctrl & ACB_DOMTRUST) == ACB_DOMTRUST ) ||
+ ( (acct_ctrl & ACB_WSTRUST) == ACB_WSTRUST) ||
+ ( (acct_ctrl & ACB_SVRTRUST) == ACB_SVRTRUST) ) {
+ DEBUG(5, ("Changing trust account. Not updating /etc/passwd\n"));
+ } else if (plaintext_buf) {
+ /* update the UNIX password */
+ if (lp_unix_password_sync() ) {
+ struct passwd *passwd;
+ if (pdb_get_username(pwd) == NULL) {
+ DEBUG(1, ("chgpasswd: User without name???\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ passwd = Get_Pwnam_alloc(pwd, pdb_get_username(pwd));
+ if (passwd == NULL) {
+ DEBUG(1, ("chgpasswd: Username does not exist in system !?!\n"));
+ }
+
+ if(!chgpasswd(pdb_get_username(pwd), rhost,
+ passwd, "", plaintext_buf, True)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ TALLOC_FREE(passwd);
+ }
+ }
+
+ BURN_STR(plaintext_buf);
+
+ if (IS_SAM_CHANGED(pwd, PDB_GROUPSID) &&
+ (!NT_STATUS_IS_OK(status = pdb_set_unix_primary_group(mem_ctx,
+ pwd)))) {
+ return status;
+ }
+
+ if(!NT_STATUS_IS_OK(status = pdb_update_sam_account(pwd))) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ set_user_info_pw
+ ********************************************************************/
+
+static bool set_user_info_pw(uint8_t *pass, const char *rhost, struct samu *pwd)
+{
+ size_t len = 0;
+ char *plaintext_buf = NULL;
+ uint32_t acct_ctrl;
+
+ DEBUG(5, ("Attempting administrator password change for user %s\n",
+ pdb_get_username(pwd)));
+
+ acct_ctrl = pdb_get_acct_ctrl(pwd);
+
+ if (!decode_pw_buffer(talloc_tos(),
+ pass,
+ &plaintext_buf,
+ &len,
+ CH_UTF16)) {
+ return False;
+ }
+
+ if (!pdb_set_plaintext_passwd (pwd, plaintext_buf)) {
+ return False;
+ }
+
+ /* if it's a trust account, don't update /etc/passwd */
+ if ( ( (acct_ctrl & ACB_DOMTRUST) == ACB_DOMTRUST ) ||
+ ( (acct_ctrl & ACB_WSTRUST) == ACB_WSTRUST) ||
+ ( (acct_ctrl & ACB_SVRTRUST) == ACB_SVRTRUST) ) {
+ DEBUG(5, ("Changing trust account or non-unix-user password, not updating /etc/passwd\n"));
+ } else {
+ /* update the UNIX password */
+ if (lp_unix_password_sync()) {
+ struct passwd *passwd;
+
+ if (pdb_get_username(pwd) == NULL) {
+ DEBUG(1, ("chgpasswd: User without name???\n"));
+ return False;
+ }
+
+ passwd = Get_Pwnam_alloc(pwd, pdb_get_username(pwd));
+ if (passwd == NULL) {
+ DEBUG(1, ("chgpasswd: Username does not exist in system !?!\n"));
+ }
+
+ if(!chgpasswd(pdb_get_username(pwd), rhost, passwd,
+ "", plaintext_buf, True)) {
+ return False;
+ }
+ TALLOC_FREE(passwd);
+ }
+ }
+
+ BURN_STR(plaintext_buf);
+
+ DEBUG(5,("set_user_info_pw: pdb_update_pwd()\n"));
+
+ return True;
+}
+
+static bool
+set_user_info_pw_aes(DATA_BLOB *pw_data, const char *rhost, struct samu *pwd)
+{
+ uint32_t acct_ctrl;
+ DATA_BLOB new_password = {
+ .length = 0,
+ };
+ bool ok;
+
+ DBG_NOTICE("Attempting administrator password change for user %s\n",
+ pdb_get_username(pwd));
+
+ acct_ctrl = pdb_get_acct_ctrl(pwd);
+
+ ok = decode_pwd_string_from_buffer514(talloc_tos(),
+ pw_data->data,
+ CH_UTF16,
+ &new_password);
+ if (!ok) {
+ return false;
+ }
+
+ ok = pdb_set_plaintext_passwd(pwd, (char *)new_password.data);
+ if (!ok) {
+ return false;
+ }
+
+ /* if it's a trust account, don't update /etc/passwd */
+ if (((acct_ctrl & ACB_DOMTRUST) == ACB_DOMTRUST) ||
+ ((acct_ctrl & ACB_WSTRUST) == ACB_WSTRUST) ||
+ ((acct_ctrl & ACB_SVRTRUST) == ACB_SVRTRUST)) {
+ DBG_NOTICE("Changing trust account or non-unix-user password, "
+ "not updating /etc/passwd\n");
+ } else {
+ /* update the UNIX password */
+ if (lp_unix_password_sync()) {
+ struct passwd *passwd;
+ const char *username;
+
+ username = pdb_get_username(pwd);
+ if (username == NULL) {
+ DBG_WARNING("User unknown\n");
+ return false;
+ }
+
+ passwd = Get_Pwnam_alloc(pwd, username);
+ if (passwd == NULL) {
+ DBG_WARNING("chgpasswd: Username does not "
+ "exist on system !?!\n");
+ }
+
+ ok = chgpasswd(username,
+ rhost,
+ passwd,
+ "",
+ (char *)new_password.data,
+ true);
+ if (!ok) {
+ return false;
+ }
+ TALLOC_FREE(passwd);
+ }
+ }
+ TALLOC_FREE(new_password.data);
+
+ DBG_NOTICE("pdb_update_pwd()\n");
+
+ return true;
+}
+
+/*******************************************************************
+ set_user_info_24
+ ********************************************************************/
+
+static NTSTATUS set_user_info_24(TALLOC_CTX *mem_ctx,
+ const char *rhost,
+ struct samr_UserInfo24 *id24,
+ struct samu *pwd)
+{
+ NTSTATUS status;
+
+ if (id24 == NULL) {
+ DEBUG(5, ("set_user_info_24: NULL id24\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!set_user_info_pw(id24->password.data, rhost, pwd)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ copy_id24_to_sam_passwd(pwd, id24);
+
+ status = pdb_update_sam_account(pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ set_user_info_25
+ ********************************************************************/
+
+static NTSTATUS set_user_info_25(TALLOC_CTX *mem_ctx,
+ const char *rhost,
+ struct samr_UserInfo25 *id25,
+ struct samu *pwd)
+{
+ NTSTATUS status;
+
+ if (id25 == NULL) {
+ DEBUG(5, ("set_user_info_25: NULL id25\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id25->info.fields_present == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id25->info.fields_present & SAMR_FIELD_LAST_PWD_CHANGE) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if ((id25->info.fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+ (id25->info.fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT)) {
+
+ if (!set_user_info_pw(id25->password.data, rhost, pwd)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+ }
+
+ copy_id25_to_sam_passwd(pwd, id25);
+
+ /* write the change out */
+ if(!NT_STATUS_IS_OK(status = pdb_update_sam_account(pwd))) {
+ return status;
+ }
+
+ /*
+ * We need to "pdb_update_sam_account" before the unix primary group
+ * is set, because the idealx scripts would also change the
+ * sambaPrimaryGroupSid using the ldap replace method. pdb_ldap uses
+ * the delete explicit / add explicit, which would then fail to find
+ * the previous primaryGroupSid value.
+ */
+
+ if ( IS_SAM_CHANGED(pwd, PDB_GROUPSID) ) {
+ status = pdb_set_unix_primary_group(mem_ctx, pwd);
+ if ( !NT_STATUS_IS_OK(status) ) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ set_user_info_26
+ ********************************************************************/
+
+static NTSTATUS set_user_info_26(TALLOC_CTX *mem_ctx,
+ const char *rhost,
+ struct samr_UserInfo26 *id26,
+ struct samu *pwd)
+{
+ NTSTATUS status;
+
+ if (id26 == NULL) {
+ DEBUG(5, ("set_user_info_26: NULL id26\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!set_user_info_pw(id26->password.data, rhost, pwd)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ copy_pwd_expired_to_sam_passwd(pwd, id26->password_expired);
+
+ status = pdb_update_sam_account(pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS set_user_info_31(TALLOC_CTX *mem_ctx,
+ const char *rhost,
+ DATA_BLOB *pw_data,
+ uint8_t password_expired,
+ struct samu *pwd)
+{
+ NTSTATUS status;
+ bool ok;
+
+ if (pw_data->length == 0 || pw_data->length > 514) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ ok = set_user_info_pw_aes(pw_data, rhost, pwd);
+ if (!ok) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ copy_pwd_expired_to_sam_passwd(pwd, password_expired);
+
+ status = pdb_update_sam_account(pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS set_user_info_32(TALLOC_CTX *mem_ctx,
+ const char *rhost,
+ DATA_BLOB *pw_data,
+ struct samr_UserInfo32 *id32,
+ struct samu *pwd)
+{
+ NTSTATUS status;
+ bool ok;
+
+ if (pw_data->length == 0 || pw_data->length > 514) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ if (id32 == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id32->info.fields_present == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (id32->info.fields_present & SAMR_FIELD_LAST_PWD_CHANGE) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if ((id32->info.fields_present & SAMR_FIELD_NT_PASSWORD_PRESENT) ||
+ (id32->info.fields_present & SAMR_FIELD_LM_PASSWORD_PRESENT)) {
+ ok = set_user_info_pw_aes(pw_data, rhost, pwd);
+ if (!ok) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+ }
+
+ copy_id32_to_sam_passwd(pwd, id32);
+
+ status = pdb_update_sam_account(pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We need to "pdb_update_sam_account" before the unix primary group
+ * is set, because the idealx scripts would also change the
+ * sambaPrimaryGroupSid using the ldap replace method. pdb_ldap uses
+ * the delete explicit / add explicit, which would then fail to find
+ * the previous primaryGroupSid value.
+ */
+ if (IS_SAM_CHANGED(pwd, PDB_GROUPSID)) {
+ status = pdb_set_unix_primary_group(mem_ctx, pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************
+**************************************************************/
+
+static uint32_t samr_set_user_info_map_fields_to_access_mask(uint32_t fields)
+{
+ uint32_t acc_required = 0;
+
+ /* USER_ALL_USERNAME */
+ if (fields & SAMR_FIELD_ACCOUNT_NAME)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_FULLNAME */
+ if (fields & SAMR_FIELD_FULL_NAME)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_PRIMARYGROUPID */
+ if (fields & SAMR_FIELD_PRIMARY_GID)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_HOMEDIRECTORY */
+ if (fields & SAMR_FIELD_HOME_DIRECTORY)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_HOMEDIRECTORYDRIVE */
+ if (fields & SAMR_FIELD_HOME_DRIVE)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_SCRIPTPATH */
+ if (fields & SAMR_FIELD_LOGON_SCRIPT)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_PROFILEPATH */
+ if (fields & SAMR_FIELD_PROFILE_PATH)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_ADMINCOMMENT */
+ if (fields & SAMR_FIELD_COMMENT)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_WORKSTATIONS */
+ if (fields & SAMR_FIELD_WORKSTATIONS)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_LOGONHOURS */
+ if (fields & SAMR_FIELD_LOGON_HOURS)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_ACCOUNTEXPIRES */
+ if (fields & SAMR_FIELD_ACCT_EXPIRY)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_USERACCOUNTCONTROL */
+ if (fields & SAMR_FIELD_ACCT_FLAGS)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_PARAMETERS */
+ if (fields & SAMR_FIELD_PARAMETERS)
+ acc_required |= SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ /* USER_ALL_USERCOMMENT */
+ if (fields & SAMR_FIELD_COMMENT)
+ acc_required |= SAMR_USER_ACCESS_SET_LOC_COM;
+ /* USER_ALL_COUNTRYCODE */
+ if (fields & SAMR_FIELD_COUNTRY_CODE)
+ acc_required |= SAMR_USER_ACCESS_SET_LOC_COM;
+ /* USER_ALL_CODEPAGE */
+ if (fields & SAMR_FIELD_CODE_PAGE)
+ acc_required |= SAMR_USER_ACCESS_SET_LOC_COM;
+ /* USER_ALL_NTPASSWORDPRESENT */
+ if (fields & SAMR_FIELD_NT_PASSWORD_PRESENT)
+ acc_required |= SAMR_USER_ACCESS_SET_PASSWORD;
+ /* USER_ALL_LMPASSWORDPRESENT */
+ if (fields & SAMR_FIELD_LM_PASSWORD_PRESENT)
+ acc_required |= SAMR_USER_ACCESS_SET_PASSWORD;
+ /* USER_ALL_PASSWORDEXPIRED */
+ if (fields & SAMR_FIELD_EXPIRED_FLAG)
+ acc_required |= SAMR_USER_ACCESS_SET_PASSWORD;
+
+ return acc_required;
+}
+
+static NTSTATUS arc4_decrypt_data(DATA_BLOB session_key,
+ uint8_t *data,
+ size_t data_size)
+{
+ gnutls_cipher_hd_t cipher_hnd = NULL;
+ gnutls_datum_t my_session_key = {
+ .data = session_key.data,
+ .size = session_key.length,
+ };
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ int rc;
+
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &my_session_key,
+ NULL);
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto out;
+ }
+
+ rc = gnutls_cipher_decrypt(cipher_hnd,
+ data,
+ data_size);
+ gnutls_cipher_deinit(cipher_hnd);
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+out:
+ return status;
+}
+
+/*******************************************************************
+ samr_SetUserInfo
+ ********************************************************************/
+
+NTSTATUS _samr_SetUserInfo(struct pipes_struct *p,
+ struct samr_SetUserInfo *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct samr_info *uinfo;
+ NTSTATUS status;
+ struct samu *pwd = NULL;
+ union samr_UserInfo *info = r->in.info;
+ uint32_t acc_required = 0;
+ uint32_t fields = 0;
+ bool ret;
+ char *rhost;
+ DATA_BLOB session_key;
+ struct dom_sid_buf buf;
+ struct loadparm_context *lp_ctx = NULL;
+ bool encrypted;
+
+ lp_ctx = loadparm_init_s3(p->mem_ctx, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* This is tricky. A WinXP domain join sets
+ (SAMR_USER_ACCESS_SET_PASSWORD|SAMR_USER_ACCESS_SET_ATTRIBUTES|SAMR_USER_ACCESS_GET_ATTRIBUTES)
+ The MMC lusrmgr plugin includes these perms and more in the SamrOpenUser(). But the
+ standard Win32 API calls just ask for SAMR_USER_ACCESS_SET_PASSWORD in the SamrOpenUser().
+ This should be enough for levels 18, 24, 25,& 26. Info level 23 can set more so
+ we'll use the set from the WinXP join as the basis. */
+
+ switch (r->in.level) {
+ case 2: /* UserPreferencesInformation */
+ /* USER_WRITE_ACCOUNT | USER_WRITE_PREFERENCES */
+ acc_required = SAMR_USER_ACCESS_SET_ATTRIBUTES | SAMR_USER_ACCESS_SET_LOC_COM;
+ break;
+ case 4: /* UserLogonHoursInformation */
+ case 6: /* UserNameInformation */
+ case 7: /* UserAccountNameInformation */
+ case 8: /* UserFullNameInformation */
+ case 9: /* UserPrimaryGroupInformation */
+ case 10: /* UserHomeInformation */
+ case 11: /* UserScriptInformation */
+ case 12: /* UserProfileInformation */
+ case 13: /* UserAdminCommentInformation */
+ case 14: /* UserWorkStationsInformation */
+ case 16: /* UserControlInformation */
+ case 17: /* UserExpiresInformation */
+ case 20: /* UserParametersInformation */
+ /* USER_WRITE_ACCOUNT */
+ acc_required = SAMR_USER_ACCESS_SET_ATTRIBUTES;
+ break;
+ case 18: /* UserInternal1Information */
+ /* FIXME: gd, this is a guess */
+ acc_required = SAMR_USER_ACCESS_SET_PASSWORD;
+ break;
+ case 21: /* UserAllInformation */
+ fields = info->info21.fields_present;
+ acc_required = samr_set_user_info_map_fields_to_access_mask(fields);
+ break;
+ case 23: /* UserInternal4Information */
+ fields = info->info23.info.fields_present;
+ acc_required = samr_set_user_info_map_fields_to_access_mask(fields);
+ break;
+ case 25: /* UserInternal4InformationNew */
+ fields = info->info25.info.fields_present;
+ acc_required = samr_set_user_info_map_fields_to_access_mask(fields);
+ break;
+ case 24: /* UserInternal5Information */
+ case 26: /* UserInternal5InformationNew */
+ case 31: /* UserInternal5InformationNew */
+ acc_required = SAMR_USER_ACCESS_SET_PASSWORD;
+ break;
+ case 32:
+ fields = info->info32.info.fields_present;
+ acc_required =
+ samr_set_user_info_map_fields_to_access_mask(fields);
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ uinfo = samr_policy_handle_find(p,
+ r->in.user_handle,
+ SAMR_HANDLE_USER,
+ acc_required,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(5, ("_samr_SetUserInfo: sid:%s, level:%d\n",
+ dom_sid_str_buf(&uinfo->sid, &buf),
+ r->in.level));
+
+ if (info == NULL) {
+ DEBUG(5, ("_samr_SetUserInfo: NULL info level\n"));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ if (!(pwd = samu_new(NULL))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = pdb_getsampwsid(pwd, &uinfo->sid);
+ unbecome_root();
+
+ if (!ret) {
+ TALLOC_FREE(pwd);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ rhost = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (rhost == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* ================ BEGIN Privilege BLOCK ================ */
+
+ become_root();
+
+ /* ok! user info levels (lots: see MSDEV help), off we go... */
+
+ switch (r->in.level) {
+
+ case 2:
+ status = set_user_info_2(p->mem_ctx,
+ &info->info2, pwd);
+ break;
+
+ case 4:
+ status = set_user_info_4(p->mem_ctx,
+ &info->info4, pwd);
+ break;
+
+ case 6:
+ status = set_user_info_6(p->mem_ctx,
+ &info->info6, pwd);
+ break;
+
+ case 7:
+ status = set_user_info_7(p->mem_ctx,
+ &info->info7, pwd);
+ break;
+
+ case 8:
+ status = set_user_info_8(p->mem_ctx,
+ &info->info8, pwd);
+ break;
+
+ case 10:
+ status = set_user_info_10(p->mem_ctx,
+ &info->info10, pwd);
+ break;
+
+ case 11:
+ status = set_user_info_11(p->mem_ctx,
+ &info->info11, pwd);
+ break;
+
+ case 12:
+ status = set_user_info_12(p->mem_ctx,
+ &info->info12, pwd);
+ break;
+
+ case 13:
+ status = set_user_info_13(p->mem_ctx,
+ &info->info13, pwd);
+ break;
+
+ case 14:
+ status = set_user_info_14(p->mem_ctx,
+ &info->info14, pwd);
+ break;
+
+ case 16:
+ status = set_user_info_16(p->mem_ctx,
+ &info->info16, pwd);
+ break;
+
+ case 17:
+ status = set_user_info_17(p->mem_ctx,
+ &info->info17, pwd);
+ break;
+
+ case 18:
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ /* Used by AS/U JRA. */
+ status = set_user_info_18(&info->info18,
+ p->mem_ctx,
+ &session_key,
+ pwd);
+ break;
+
+ case 20:
+ status = set_user_info_20(p->mem_ctx,
+ &info->info20, pwd);
+ break;
+
+ case 21:
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ status = set_user_info_21(&info->info21,
+ p->mem_ctx,
+ &session_key,
+ pwd);
+ break;
+
+ case 23:
+ encrypted =
+ dcerpc_is_transport_encrypted(session_info);
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED &&
+ !encrypted) {
+ status = NT_STATUS_ACCESS_DENIED;
+ break;
+ }
+
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ /*
+ * This can be allowed as it requires a session key
+ * which we only have if we have a SMB session.
+ */
+ GNUTLS_FIPS140_SET_LAX_MODE();
+ status = arc4_decrypt_data(session_key,
+ info->info23.password.data,
+ 516);
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+#ifdef DEBUG_PASSWORD
+ dump_data(100, info->info23.password.data, 516);
+#endif
+
+ status = set_user_info_23(p->mem_ctx,
+ &info->info23,
+ rhost,
+ pwd);
+ break;
+
+ case 24:
+ encrypted =
+ dcerpc_is_transport_encrypted(session_info);
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED &&
+ !encrypted) {
+ status = NT_STATUS_ACCESS_DENIED;
+ break;
+ }
+
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ /*
+ * This can be allowed as it requires a session key
+ * which we only have if we have a SMB session.
+ */
+ GNUTLS_FIPS140_SET_LAX_MODE();
+ status = arc4_decrypt_data(session_key,
+ info->info24.password.data,
+ 516);
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+#ifdef DEBUG_PASSWORD
+ dump_data(100, info->info24.password.data, 516);
+#endif
+
+ status = set_user_info_24(p->mem_ctx,
+ rhost,
+ &info->info24, pwd);
+ break;
+
+ case 25:
+ encrypted =
+ dcerpc_is_transport_encrypted(session_info);
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED &&
+ !encrypted) {
+ status = NT_STATUS_ACCESS_DENIED;
+ break;
+ }
+
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ /*
+ * This can be allowed as it requires a session key
+ * which we only have if we have a SMB session.
+ */
+ GNUTLS_FIPS140_SET_LAX_MODE();
+ status = decode_rc4_passwd_buffer(&session_key,
+ &info->info25.password);
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+#ifdef DEBUG_PASSWORD
+ dump_data(100, info->info25.password.data, 532);
+#endif
+
+ status = set_user_info_25(p->mem_ctx,
+ rhost,
+ &info->info25, pwd);
+ break;
+
+ case 26:
+ encrypted =
+ dcerpc_is_transport_encrypted(session_info);
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED &&
+ !encrypted) {
+ status = NT_STATUS_ACCESS_DENIED;
+ break;
+ }
+
+ status = session_extract_session_key(
+ session_info, &session_key, KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ /*
+ * This can be allowed as it requires a session key
+ * which we only have if we have a SMB session.
+ */
+ GNUTLS_FIPS140_SET_LAX_MODE();
+ status = decode_rc4_passwd_buffer(&session_key,
+ &info->info26.password);
+ GNUTLS_FIPS140_SET_STRICT_MODE();
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+#ifdef DEBUG_PASSWORD
+ dump_data(100, info->info26.password.data, 516);
+#endif
+
+ status = set_user_info_26(p->mem_ctx,
+ rhost,
+ &info->info26, pwd);
+ break;
+ case 31: {
+ DATA_BLOB new_password = data_blob_null;
+ const DATA_BLOB ciphertext = data_blob_const(
+ info->info31.password.cipher,
+ info->info31.password.cipher_len);
+ DATA_BLOB iv = data_blob_const(
+ info->info31.password.salt,
+ sizeof(info->info31.password.salt));
+
+ status = session_extract_session_key(session_info,
+ &session_key,
+ KEY_USE_16BYTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ status =
+ samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt(
+ p->mem_ctx,
+ &ciphertext,
+ &session_key,
+ &samr_aes256_enc_key_salt,
+ &samr_aes256_mac_key_salt,
+ &iv,
+ info->info31.password.auth_data,
+ &new_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("samba_gnutls_aead_aes_256_cbc_hmac_"
+ "sha512_decrypt "
+ "failed with %s\n",
+ nt_errstr(status));
+ status = NT_STATUS_WRONG_PASSWORD;
+ break;
+ }
+
+ status = set_user_info_31(p->mem_ctx,
+ rhost,
+ &new_password,
+ info->info31.password_expired,
+ pwd);
+ data_blob_clear(&new_password);
+
+ break;
+ }
+ case 32: {
+ DATA_BLOB new_password = data_blob_null;
+ const DATA_BLOB ciphertext = data_blob_const(
+ info->info32.password.cipher,
+ info->info32.password.cipher_len);
+ DATA_BLOB iv = data_blob_const(
+ info->info32.password.salt,
+ sizeof(info->info32.password.salt));
+
+ status = session_extract_session_key(session_info,
+ &session_key,
+ KEY_USE_16BYTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ status =
+ samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt(
+ p->mem_ctx,
+ &ciphertext,
+ &session_key,
+ &samr_aes256_enc_key_salt,
+ &samr_aes256_mac_key_salt,
+ &iv,
+ info->info32.password.auth_data,
+ &new_password);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_WRONG_PASSWORD;
+ break;
+ }
+
+ status = set_user_info_32(p->mem_ctx,
+ rhost,
+ &new_password,
+ &info->info32,
+ pwd);
+ data_blob_clear_free(&new_password);
+
+ break;
+ }
+ default:
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ TALLOC_FREE(pwd);
+
+ unbecome_root();
+
+ /* ================ END Privilege BLOCK ================ */
+
+ if (NT_STATUS_IS_OK(status)) {
+ force_flush_samr_cache(&uinfo->sid);
+ }
+
+ return status;
+}
+
+/*******************************************************************
+ _samr_SetUserInfo2
+ ********************************************************************/
+
+NTSTATUS _samr_SetUserInfo2(struct pipes_struct *p,
+ struct samr_SetUserInfo2 *r)
+{
+ struct samr_SetUserInfo q;
+
+ q.in.user_handle = r->in.user_handle;
+ q.in.level = r->in.level;
+ q.in.info = r->in.info;
+
+ return _samr_SetUserInfo(p, &q);
+}
+
+/*********************************************************************
+ _samr_GetAliasMembership
+*********************************************************************/
+
+NTSTATUS _samr_GetAliasMembership(struct pipes_struct *p,
+ struct samr_GetAliasMembership *r)
+{
+ size_t num_alias_rids;
+ uint32_t *alias_rids;
+ struct samr_info *dinfo;
+ size_t i;
+
+ NTSTATUS status;
+
+ struct dom_sid *members;
+
+ DEBUG(5,("_samr_GetAliasMembership: %d\n", __LINE__));
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_LOOKUP_ALIAS
+ | SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!sid_check_is_our_sam(&dinfo->sid) &&
+ !sid_check_is_builtin(&dinfo->sid))
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+
+ if (r->in.sids->num_sids) {
+ members = talloc_array(p->mem_ctx, struct dom_sid, r->in.sids->num_sids);
+
+ if (members == NULL)
+ return NT_STATUS_NO_MEMORY;
+ } else {
+ members = NULL;
+ }
+
+ for (i=0; i<r->in.sids->num_sids; i++)
+ sid_copy(&members[i], r->in.sids->sids[i].sid);
+
+ alias_rids = NULL;
+ num_alias_rids = 0;
+
+ become_root();
+ status = pdb_enum_alias_memberships(p->mem_ctx, &dinfo->sid, members,
+ r->in.sids->num_sids,
+ &alias_rids, &num_alias_rids);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->out.rids->count = num_alias_rids;
+ r->out.rids->ids = alias_rids;
+
+ if (r->out.rids->ids == NULL) {
+ /* Windows domain clients don't accept a NULL ptr here */
+ r->out.rids->ids = talloc_zero(p->mem_ctx, uint32_t);
+ }
+ if (r->out.rids->ids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_GetMembersInAlias
+*********************************************************************/
+
+NTSTATUS _samr_GetMembersInAlias(struct pipes_struct *p,
+ struct samr_GetMembersInAlias *r)
+{
+ struct samr_info *ainfo;
+ NTSTATUS status;
+ size_t i;
+ size_t num_sids = 0;
+ struct lsa_SidPtr *sids = NULL;
+ struct dom_sid *pdb_sids = NULL;
+ struct dom_sid_buf buf;
+
+ ainfo = samr_policy_handle_find(p,
+ r->in.alias_handle,
+ SAMR_HANDLE_ALIAS,
+ SAMR_ALIAS_ACCESS_GET_MEMBERS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ainfo->sid, &buf)));
+
+ become_root();
+ status = pdb_enum_aliasmem(&ainfo->sid, talloc_tos(), &pdb_sids,
+ &num_sids);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (num_sids) {
+ sids = talloc_zero_array(p->mem_ctx, struct lsa_SidPtr, num_sids);
+ if (sids == NULL) {
+ TALLOC_FREE(pdb_sids);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ for (i = 0; i < num_sids; i++) {
+ sids[i].sid = dom_sid_dup(p->mem_ctx, &pdb_sids[i]);
+ if (!sids[i].sid) {
+ TALLOC_FREE(pdb_sids);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ r->out.sids->num_sids = num_sids;
+ r->out.sids->sids = sids;
+
+ TALLOC_FREE(pdb_sids);
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_QueryGroupMember
+*********************************************************************/
+
+NTSTATUS _samr_QueryGroupMember(struct pipes_struct *p,
+ struct samr_QueryGroupMember *r)
+{
+ struct samr_info *ginfo;
+ size_t i, num_members;
+
+ uint32_t *rid=NULL;
+ uint32_t *attr=NULL;
+
+ NTSTATUS status;
+ struct samr_RidAttrArray *rids = NULL;
+ struct dom_sid_buf buf;
+
+ ginfo = samr_policy_handle_find(p,
+ r->in.group_handle,
+ SAMR_HANDLE_GROUP,
+ SAMR_GROUP_ACCESS_GET_MEMBERS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ rids = talloc_zero(p->mem_ctx, struct samr_RidAttrArray);
+ if (!rids) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ginfo->sid, &buf)));
+
+ if (!sid_check_is_in_our_sam(&ginfo->sid)) {
+ DEBUG(3, ("sid %s is not in our domain\n",
+ dom_sid_str_buf(&ginfo->sid, &buf)));
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ DEBUG(10, ("lookup on Domain SID\n"));
+
+ become_root();
+ status = pdb_enum_group_members(p->mem_ctx, &ginfo->sid,
+ &rid, &num_members);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+
+ if (num_members) {
+ attr=talloc_zero_array(p->mem_ctx, uint32_t, num_members);
+ if (attr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ attr = NULL;
+ }
+
+ for (i=0; i<num_members; i++) {
+ attr[i] = SE_GROUP_DEFAULT_FLAGS;
+ }
+
+ rids->count = num_members;
+ rids->attributes = attr;
+ rids->rids = rid;
+
+ *r->out.rids = rids;
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_AddAliasMember
+*********************************************************************/
+
+NTSTATUS _samr_AddAliasMember(struct pipes_struct *p,
+ struct samr_AddAliasMember *r)
+{
+ struct samr_info *ainfo;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ ainfo = samr_policy_handle_find(p,
+ r->in.alias_handle,
+ SAMR_HANDLE_ALIAS,
+ SAMR_ALIAS_ACCESS_ADD_MEMBER,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ainfo->sid, &buf)));
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_add_aliasmem(&ainfo->sid, r->in.sid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ if (NT_STATUS_IS_OK(status)) {
+ force_flush_samr_cache(&ainfo->sid);
+ }
+
+ return status;
+}
+
+/*********************************************************************
+ _samr_DeleteAliasMember
+*********************************************************************/
+
+NTSTATUS _samr_DeleteAliasMember(struct pipes_struct *p,
+ struct samr_DeleteAliasMember *r)
+{
+ struct samr_info *ainfo;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ ainfo = samr_policy_handle_find(p,
+ r->in.alias_handle,
+ SAMR_HANDLE_ALIAS,
+ SAMR_ALIAS_ACCESS_REMOVE_MEMBER,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10, ("_samr_del_aliasmem:sid is %s\n",
+ dom_sid_str_buf(&ainfo->sid, &buf)));
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_del_aliasmem(&ainfo->sid, r->in.sid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ if (NT_STATUS_IS_OK(status)) {
+ force_flush_samr_cache(&ainfo->sid);
+ }
+
+ return status;
+}
+
+/*********************************************************************
+ _samr_AddGroupMember
+*********************************************************************/
+
+NTSTATUS _samr_AddGroupMember(struct pipes_struct *p,
+ struct samr_AddGroupMember *r)
+{
+ struct samr_info *ginfo;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+ uint32_t group_rid;
+
+ ginfo = samr_policy_handle_find(p,
+ r->in.group_handle,
+ SAMR_HANDLE_GROUP,
+ SAMR_GROUP_ACCESS_ADD_MEMBER,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ginfo->sid, &buf)));
+
+ if (!sid_peek_check_rid(get_global_sam_sid(), &ginfo->sid,
+ &group_rid)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_add_groupmem(p->mem_ctx, group_rid, r->in.rid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ force_flush_samr_cache(&ginfo->sid);
+
+ return status;
+}
+
+/*********************************************************************
+ _samr_DeleteGroupMember
+*********************************************************************/
+
+NTSTATUS _samr_DeleteGroupMember(struct pipes_struct *p,
+ struct samr_DeleteGroupMember *r)
+
+{
+ struct samr_info *ginfo;
+ NTSTATUS status;
+ uint32_t group_rid;
+
+ /*
+ * delete the group member named r->in.rid
+ * who is a member of the sid associated with the handle
+ * the rid is a user's rid as the group is a domain group.
+ */
+
+ ginfo = samr_policy_handle_find(p,
+ r->in.group_handle,
+ SAMR_HANDLE_GROUP,
+ SAMR_GROUP_ACCESS_REMOVE_MEMBER,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!sid_peek_check_rid(get_global_sam_sid(), &ginfo->sid,
+ &group_rid)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_del_groupmem(p->mem_ctx, group_rid, r->in.rid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ force_flush_samr_cache(&ginfo->sid);
+
+ return status;
+}
+
+/*********************************************************************
+ _samr_DeleteUser
+*********************************************************************/
+
+NTSTATUS _samr_DeleteUser(struct pipes_struct *p,
+ struct samr_DeleteUser *r)
+{
+ struct samr_info *uinfo;
+ NTSTATUS status;
+ struct samu *sam_pass=NULL;
+ bool ret;
+
+ DEBUG(5, ("_samr_DeleteUser: %d\n", __LINE__));
+
+ uinfo = samr_policy_handle_find(p,
+ r->in.user_handle,
+ SAMR_HANDLE_USER,
+ SEC_STD_DELETE,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!sid_check_is_in_our_sam(&uinfo->sid))
+ return NT_STATUS_CANNOT_DELETE;
+
+ /* check if the user exists before trying to delete */
+ if ( !(sam_pass = samu_new( NULL )) ) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = pdb_getsampwsid(sam_pass, &uinfo->sid);
+ unbecome_root();
+
+ if(!ret) {
+ struct dom_sid_buf buf;
+ DEBUG(5,("_samr_DeleteUser: User %s doesn't exist.\n",
+ dom_sid_str_buf(&uinfo->sid, &buf)));
+ TALLOC_FREE(sam_pass);
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_delete_user(p->mem_ctx, sam_pass);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(5,("_samr_DeleteUser: Failed to delete entry for "
+ "user %s: %s.\n", pdb_get_username(sam_pass),
+ nt_errstr(status)));
+ TALLOC_FREE(sam_pass);
+ return status;
+ }
+
+
+ TALLOC_FREE(sam_pass);
+
+ force_flush_samr_cache(&uinfo->sid);
+
+ if (!close_policy_hnd(p, r->in.user_handle))
+ return NT_STATUS_OBJECT_NAME_INVALID;
+
+ ZERO_STRUCTP(r->out.user_handle);
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_DeleteDomainGroup
+*********************************************************************/
+
+NTSTATUS _samr_DeleteDomainGroup(struct pipes_struct *p,
+ struct samr_DeleteDomainGroup *r)
+{
+ struct samr_info *ginfo;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+ uint32_t group_rid;
+
+ DEBUG(5, ("samr_DeleteDomainGroup: %d\n", __LINE__));
+
+ ginfo = samr_policy_handle_find(p,
+ r->in.group_handle,
+ SAMR_HANDLE_GROUP,
+ SEC_STD_DELETE,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ginfo->sid, &buf)));
+
+ if (!sid_peek_check_rid(get_global_sam_sid(), &ginfo->sid,
+ &group_rid)) {
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_delete_dom_group(p->mem_ctx, group_rid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(5,("_samr_DeleteDomainGroup: Failed to delete mapping "
+ "entry for group %s: %s\n",
+ dom_sid_str_buf(&ginfo->sid, &buf),
+ nt_errstr(status)));
+ return status;
+ }
+
+ force_flush_samr_cache(&ginfo->sid);
+
+ if (!close_policy_hnd(p, r->in.group_handle))
+ return NT_STATUS_OBJECT_NAME_INVALID;
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_DeleteDomAlias
+*********************************************************************/
+
+NTSTATUS _samr_DeleteDomAlias(struct pipes_struct *p,
+ struct samr_DeleteDomAlias *r)
+{
+ struct samr_info *ainfo;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ DEBUG(5, ("_samr_DeleteDomAlias: %d\n", __LINE__));
+
+ ainfo = samr_policy_handle_find(p,
+ r->in.alias_handle,
+ SAMR_HANDLE_ALIAS,
+ SEC_STD_DELETE,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10, ("sid is %s\n", dom_sid_str_buf(&ainfo->sid, &buf)));
+
+ /* Don't let Windows delete builtin groups */
+
+ if ( sid_check_is_in_builtin( &ainfo->sid ) ) {
+ return NT_STATUS_SPECIAL_ACCOUNT;
+ }
+
+ if (!sid_check_is_in_our_sam(&ainfo->sid))
+ return NT_STATUS_NO_SUCH_ALIAS;
+
+ DEBUG(10, ("lookup on Local SID\n"));
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ /* Have passdb delete the alias */
+ status = pdb_delete_alias(&ainfo->sid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ if ( !NT_STATUS_IS_OK(status))
+ return status;
+
+ force_flush_samr_cache(&ainfo->sid);
+
+ if (!close_policy_hnd(p, r->in.alias_handle))
+ return NT_STATUS_OBJECT_NAME_INVALID;
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_CreateDomainGroup
+*********************************************************************/
+
+NTSTATUS _samr_CreateDomainGroup(struct pipes_struct *p,
+ struct samr_CreateDomainGroup *r)
+
+{
+ NTSTATUS status;
+ const char *name;
+ struct samr_info *dinfo;
+ struct dom_sid sid;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_CREATE_GROUP,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!sid_check_is_our_sam(&dinfo->sid)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ name = r->in.name->string;
+ if (name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = can_create(p->mem_ctx, name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ /* check that we successfully create the UNIX group */
+ status = pdb_create_dom_group(p->mem_ctx, name, r->out.rid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ /* check if we should bail out here */
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ sid_compose(&sid, &dinfo->sid, *r->out.rid);
+
+ status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_GROUP,
+ GENERIC_RIGHTS_GROUP_ALL_ACCESS,
+ &sid,
+ NULL,
+ r->out.group_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ force_flush_samr_cache(&dinfo->sid);
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_CreateDomAlias
+*********************************************************************/
+
+NTSTATUS _samr_CreateDomAlias(struct pipes_struct *p,
+ struct samr_CreateDomAlias *r)
+{
+ struct dom_sid info_sid;
+ const char *name = NULL;
+ struct samr_info *dinfo;
+ gid_t gid;
+ NTSTATUS result;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_CREATE_ALIAS,
+ NULL,
+ &result);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ if (!sid_check_is_our_sam(&dinfo->sid)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ name = r->in.alias_name->string;
+
+ result = can_create(p->mem_ctx, name);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ /* Have passdb create the alias */
+ result = pdb_create_alias(name, r->out.rid);
+ unbecome_root();
+
+ /******** END SeAddUsers BLOCK *********/
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("pdb_create_alias failed: %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+
+ sid_compose(&info_sid, &dinfo->sid, *r->out.rid);
+
+ if (!sid_to_gid(&info_sid, &gid)) {
+ DEBUG(10, ("Could not find alias just created\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* check if the group has been successfully created */
+ if ( getgrgid(gid) == NULL ) {
+ DEBUG(1, ("getgrgid(%u) of just created alias failed\n",
+ (unsigned int)gid));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ result = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_ALIAS,
+ GENERIC_RIGHTS_ALIAS_ALL_ACCESS,
+ &info_sid,
+ NULL,
+ r->out.alias_handle);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ force_flush_samr_cache(&info_sid);
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_QueryGroupInfo
+*********************************************************************/
+
+NTSTATUS _samr_QueryGroupInfo(struct pipes_struct *p,
+ struct samr_QueryGroupInfo *r)
+{
+ struct samr_info *ginfo;
+ NTSTATUS status;
+ GROUP_MAP *map;
+ union samr_GroupInfo *info = NULL;
+ bool ret;
+ uint32_t attributes = SE_GROUP_DEFAULT_FLAGS;
+ const char *group_name = NULL;
+ const char *group_description = NULL;
+
+ ginfo = samr_policy_handle_find(p,
+ r->in.group_handle,
+ SAMR_HANDLE_GROUP,
+ SAMR_GROUP_ACCESS_LOOKUP_INFO,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ map = talloc_zero(p->mem_ctx, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = get_domain_group_from_sid(ginfo->sid, map);
+ unbecome_root();
+ if (!ret)
+ return NT_STATUS_INVALID_HANDLE;
+
+ group_name = talloc_move(r, &map->nt_name);
+ group_description = talloc_move(r, &map->comment);
+
+ TALLOC_FREE(map);
+
+ info = talloc_zero(p->mem_ctx, union samr_GroupInfo);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (r->in.level) {
+ case 1: {
+ uint32_t *members;
+ size_t num_members;
+
+ become_root();
+ status = pdb_enum_group_members(
+ p->mem_ctx, &ginfo->sid, &members,
+ &num_members);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ info->all.name.string = group_name;
+ info->all.attributes = attributes;
+ info->all.num_members = num_members;
+ info->all.description.string = group_description;
+ break;
+ }
+ case 2:
+ info->name.string = group_name;
+ break;
+ case 3:
+ info->attributes.attributes = attributes;
+ break;
+ case 4:
+ info->description.string = group_description;
+ break;
+ case 5: {
+ /*
+ uint32_t *members;
+ size_t num_members;
+ */
+
+ /*
+ become_root();
+ status = pdb_enum_group_members(
+ p->mem_ctx, &ginfo->sid, &members,
+ &num_members);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ */
+ info->all2.name.string = group_name;
+ info->all2.attributes = attributes;
+ info->all2.num_members = 0; /* num_members - in w2k3 this is always 0 */
+ info->all2.description.string = group_description;
+
+ break;
+ }
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ *r->out.info = info;
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_SetGroupInfo
+*********************************************************************/
+
+NTSTATUS _samr_SetGroupInfo(struct pipes_struct *p,
+ struct samr_SetGroupInfo *r)
+{
+ struct samr_info *ginfo;
+ GROUP_MAP *map;
+ NTSTATUS status;
+ bool ret;
+
+ ginfo = samr_policy_handle_find(p,
+ r->in.group_handle,
+ SAMR_HANDLE_GROUP,
+ SAMR_GROUP_ACCESS_SET_INFO,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ map = talloc_zero(p->mem_ctx, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ ret = get_domain_group_from_sid(ginfo->sid, map);
+ unbecome_root();
+ if (!ret)
+ return NT_STATUS_NO_SUCH_GROUP;
+
+ switch (r->in.level) {
+ case 2:
+ map->nt_name = talloc_strdup(map,
+ r->in.info->name.string);
+ if (!map->nt_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ case 3:
+ break;
+ case 4:
+ map->comment = talloc_strdup(map,
+ r->in.info->description.string);
+ if (!map->comment) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_update_group_mapping_entry(map);
+ unbecome_root();
+
+ /******** End SeAddUsers BLOCK *********/
+
+ TALLOC_FREE(map);
+
+ if (NT_STATUS_IS_OK(status)) {
+ force_flush_samr_cache(&ginfo->sid);
+ }
+
+ return status;
+}
+
+/*********************************************************************
+ _samr_SetAliasInfo
+*********************************************************************/
+
+NTSTATUS _samr_SetAliasInfo(struct pipes_struct *p,
+ struct samr_SetAliasInfo *r)
+{
+ struct samr_info *ainfo;
+ struct acct_info *info;
+ NTSTATUS status;
+
+ ainfo = samr_policy_handle_find(p,
+ r->in.alias_handle,
+ SAMR_HANDLE_ALIAS,
+ SAMR_ALIAS_ACCESS_SET_INFO,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ info = talloc_zero(p->mem_ctx, struct acct_info);
+ if (!info) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get the current group information */
+
+ become_root();
+ status = pdb_get_aliasinfo(&ainfo->sid, info);
+ unbecome_root();
+
+ if ( !NT_STATUS_IS_OK(status))
+ return status;
+
+ switch (r->in.level) {
+ case ALIASINFONAME:
+ {
+ char *group_name;
+
+ /* We currently do not support renaming groups in the
+ the BUILTIN domain. Refer to util_builtin.c to understand
+ why. The eventually needs to be fixed to be like Windows
+ where you can rename builtin groups, just not delete them */
+
+ if ( sid_check_is_in_builtin( &ainfo->sid ) ) {
+ return NT_STATUS_SPECIAL_ACCOUNT;
+ }
+
+ /* There has to be a valid name (and it has to be different) */
+
+ if ( !r->in.info->name.string )
+ return NT_STATUS_INVALID_PARAMETER;
+
+ /* If the name is the same just reply "ok". Yes this
+ doesn't allow you to change the case of a group name. */
+
+ if (strequal(r->in.info->name.string, info->acct_name)) {
+ return NT_STATUS_OK;
+ }
+
+ talloc_free(info->acct_name);
+ info->acct_name = talloc_strdup(info, r->in.info->name.string);
+ if (!info->acct_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* make sure the name doesn't already exist as a user
+ or local group */
+
+ group_name = talloc_asprintf(p->mem_ctx,
+ "%s\\%s",
+ lp_netbios_name(),
+ info->acct_name);
+ if (group_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = can_create( p->mem_ctx, group_name );
+ talloc_free(group_name);
+ if ( !NT_STATUS_IS_OK( status ) )
+ return status;
+ break;
+ }
+ case ALIASINFODESCRIPTION:
+ TALLOC_FREE(info->acct_desc);
+ if (r->in.info->description.string) {
+ info->acct_desc = talloc_strdup(info,
+ r->in.info->description.string);
+ } else {
+ info->acct_desc = talloc_strdup(info, "");
+ }
+ if (!info->acct_desc) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ /******** BEGIN SeAddUsers BLOCK *********/
+
+ become_root();
+ status = pdb_set_aliasinfo(&ainfo->sid, info);
+ unbecome_root();
+
+ /******** End SeAddUsers BLOCK *********/
+
+ if (NT_STATUS_IS_OK(status))
+ force_flush_samr_cache(&ainfo->sid);
+
+ return status;
+}
+
+/****************************************************************
+ _samr_GetDomPwInfo
+****************************************************************/
+
+NTSTATUS _samr_GetDomPwInfo(struct pipes_struct *p,
+ struct samr_GetDomPwInfo *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t min_password_length = 0;
+ uint32_t password_properties = 0;
+
+ /* Perform access check. Since this rpc does not require a
+ policy handle it will not be caught by the access checks on
+ SAMR_CONNECT or SAMR_CONNECT_ANON. */
+
+ if (!pipe_access_check(p)) {
+ DEBUG(3, ("access denied to _samr_GetDomPwInfo\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ become_root();
+ pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_LEN,
+ &min_password_length);
+ pdb_get_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS,
+ &password_properties);
+ unbecome_root();
+
+ if (lp_check_password_script(talloc_tos(), lp_sub) && *lp_check_password_script(talloc_tos(), lp_sub)) {
+ password_properties |= DOMAIN_PASSWORD_COMPLEX;
+ }
+
+ r->out.info->min_password_length = min_password_length;
+ r->out.info->password_properties = password_properties;
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_OpenGroup
+*********************************************************************/
+
+NTSTATUS _samr_OpenGroup(struct pipes_struct *p,
+ struct samr_OpenGroup *r)
+
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct dom_sid info_sid;
+ struct dom_sid_buf buf;
+ GROUP_MAP *map;
+ struct samr_info *dinfo;
+ struct security_descriptor *psd = NULL;
+ uint32_t acc_granted;
+ uint32_t des_access = r->in.access_mask;
+ size_t sd_size;
+ NTSTATUS status;
+ bool ret;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*check if access can be granted as requested by client. */
+ map_max_allowed_access(session_info->security_token,
+ session_info->unix_token,
+ &des_access);
+
+ make_samr_object_sd(p->mem_ctx, &psd, &sd_size, &grp_generic_mapping, NULL, 0);
+ se_map_generic(&des_access,&grp_generic_mapping);
+
+ status = access_check_object(psd, session_info->security_token,
+ SEC_PRIV_ADD_USERS, SEC_PRIV_INVALID, GENERIC_RIGHTS_GROUP_ALL_ACCESS,
+ des_access, &acc_granted, "_samr_OpenGroup");
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ /* this should not be hard-coded like this */
+
+ if (!sid_check_is_our_sam(&dinfo->sid)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ sid_compose(&info_sid, &dinfo->sid, r->in.rid);
+
+ DEBUG(10, ("_samr_OpenGroup:Opening SID: %s\n",
+ dom_sid_str_buf(&info_sid, &buf)));
+
+ map = talloc_zero(p->mem_ctx, GROUP_MAP);
+ if (!map) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* check if that group really exists */
+ become_root();
+ ret = get_domain_group_from_sid(info_sid, map);
+ unbecome_root();
+ if (!ret)
+ return NT_STATUS_NO_SUCH_GROUP;
+
+ TALLOC_FREE(map);
+
+ status = create_samr_policy_handle(p->mem_ctx,
+ p,
+ SAMR_HANDLE_GROUP,
+ acc_granted,
+ &info_sid,
+ NULL,
+ r->out.group_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ _samr_RemoveMemberFromForeignDomain
+*********************************************************************/
+
+NTSTATUS _samr_RemoveMemberFromForeignDomain(struct pipes_struct *p,
+ struct samr_RemoveMemberFromForeignDomain *r)
+{
+ struct samr_info *dinfo;
+ struct dom_sid_buf buf;
+ NTSTATUS result;
+
+ DEBUG(5,("_samr_RemoveMemberFromForeignDomain: removing SID [%s]\n",
+ dom_sid_str_buf(r->in.sid, &buf)));
+
+ /* Find the policy handle. Open a policy on it. */
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ NULL,
+ &result);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ DEBUG(8, ("_samr_RemoveMemberFromForeignDomain: sid is %s\n",
+ dom_sid_str_buf(&dinfo->sid, &buf)));
+
+ /* we can only delete a user from a group since we don't have
+ nested groups anyways. So in the latter case, just say OK */
+
+ /* TODO: The above comment nowadays is bogus. Since we have nested
+ * groups now, and aliases members are never reported out of the unix
+ * group membership, the "just say OK" makes this call a no-op. For
+ * us. This needs fixing however. */
+
+ /* I've only ever seen this in the wild when deleting a user from
+ * usrmgr.exe. domain_sid is the builtin domain, and the sid to delete
+ * is the user about to be deleted. I very much suspect this is the
+ * only application of this call. To verify this, let people report
+ * other cases. */
+
+ if (!sid_check_is_builtin(&dinfo->sid)) {
+ struct dom_sid_buf buf2;
+ DBG_WARNING("domain_sid = %s, "
+ "global_sam_sid() = %s\n"
+ "please report to "
+ "samba-technical@lists.samba.org!\n",
+ dom_sid_str_buf(&dinfo->sid, &buf),
+ dom_sid_str_buf(get_global_sam_sid(), &buf2));
+ return NT_STATUS_OK;
+ }
+
+ force_flush_samr_cache(&dinfo->sid);
+
+ result = NT_STATUS_OK;
+
+ return result;
+}
+
+/*******************************************************************
+ _samr_QueryDomainInfo2
+ ********************************************************************/
+
+NTSTATUS _samr_QueryDomainInfo2(struct pipes_struct *p,
+ struct samr_QueryDomainInfo2 *r)
+{
+ struct samr_QueryDomainInfo q;
+
+ q.in.domain_handle = r->in.domain_handle;
+ q.in.level = r->in.level;
+
+ q.out.info = r->out.info;
+
+ return _samr_QueryDomainInfo(p, &q);
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS set_dom_info_1(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *r)
+{
+ time_t u_expire, u_min_age;
+
+ u_expire = nt_time_to_unix_abs((NTTIME *)&r->max_password_age);
+ u_min_age = nt_time_to_unix_abs((NTTIME *)&r->min_password_age);
+
+ pdb_set_account_policy(PDB_POLICY_MIN_PASSWORD_LEN,
+ (uint32_t)r->min_password_length);
+ pdb_set_account_policy(PDB_POLICY_PASSWORD_HISTORY,
+ (uint32_t)r->password_history_length);
+ pdb_set_account_policy(PDB_POLICY_USER_MUST_LOGON_TO_CHG_PASS,
+ (uint32_t)r->password_properties);
+ pdb_set_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, (int)u_expire);
+ pdb_set_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, (int)u_min_age);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS set_dom_info_3(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo3 *r)
+{
+ time_t u_logout;
+
+ u_logout = nt_time_to_unix_abs((NTTIME *)&r->force_logoff_time);
+
+ pdb_set_account_policy(PDB_POLICY_TIME_TO_LOGOUT, (int)u_logout);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static NTSTATUS set_dom_info_12(TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *r)
+{
+ time_t u_lock_duration, u_reset_time;
+
+ /*
+ * It is not possible to set lockout_duration < lockout_window.
+ * (The test is the other way around since the negative numbers
+ * are stored...)
+ *
+ * This constraint is documented here for the samr rpc service:
+ * MS-SAMR 3.1.1.6 Attribute Constraints for Originating Updates
+ * http://msdn.microsoft.com/en-us/library/cc245667%28PROT.10%29.aspx
+ *
+ * And here for the ldap backend:
+ * MS-ADTS 3.1.1.5.3.2 Constraints
+ * http://msdn.microsoft.com/en-us/library/cc223462(PROT.10).aspx
+ */
+ if (r->lockout_duration > r->lockout_window) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ u_lock_duration = nt_time_to_unix_abs((NTTIME *)&r->lockout_duration);
+ if (u_lock_duration != -1) {
+ u_lock_duration /= 60;
+ }
+
+ u_reset_time = nt_time_to_unix_abs((NTTIME *)&r->lockout_window)/60;
+
+ pdb_set_account_policy(PDB_POLICY_LOCK_ACCOUNT_DURATION, (int)u_lock_duration);
+ pdb_set_account_policy(PDB_POLICY_RESET_COUNT_TIME, (int)u_reset_time);
+ pdb_set_account_policy(PDB_POLICY_BAD_ATTEMPT_LOCKOUT,
+ (uint32_t)r->lockout_threshold);
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ _samr_SetDomainInfo
+ ********************************************************************/
+
+NTSTATUS _samr_SetDomainInfo(struct pipes_struct *p,
+ struct samr_SetDomainInfo *r)
+{
+ NTSTATUS status;
+ uint32_t acc_required = 0;
+
+ DEBUG(5,("_samr_SetDomainInfo: %d\n", __LINE__));
+
+ switch (r->in.level) {
+ case 1: /* DomainPasswordInformation */
+ case 12: /* DomainLockoutInformation */
+ /* DOMAIN_WRITE_PASSWORD_PARAMETERS */
+ acc_required = SAMR_DOMAIN_ACCESS_SET_INFO_1;
+ break;
+ case 3: /* DomainLogoffInformation */
+ case 4: /* DomainOemInformation */
+ /* DOMAIN_WRITE_OTHER_PARAMETERS */
+ acc_required = SAMR_DOMAIN_ACCESS_SET_INFO_2;
+ break;
+ case 6: /* DomainReplicationInformation */
+ case 9: /* DomainStateInformation */
+ case 7: /* DomainServerRoleInformation */
+ /* DOMAIN_ADMINISTER_SERVER */
+ acc_required = SAMR_DOMAIN_ACCESS_SET_INFO_3;
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ (void)samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ acc_required,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(5,("_samr_SetDomainInfo: level: %d\n", r->in.level));
+
+ switch (r->in.level) {
+ case 1:
+ status = set_dom_info_1(p->mem_ctx, &r->in.info->info1);
+ break;
+ case 3:
+ status = set_dom_info_3(p->mem_ctx, &r->in.info->info3);
+ break;
+ case 4:
+ break;
+ case 6:
+ break;
+ case 7:
+ break;
+ case 9:
+ break;
+ case 12:
+ status = set_dom_info_12(p->mem_ctx, &r->in.info->info12);
+ break;
+ default:
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(5,("_samr_SetDomainInfo: %d\n", __LINE__));
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+ _samr_GetDisplayEnumerationIndex
+****************************************************************/
+
+NTSTATUS _samr_GetDisplayEnumerationIndex(struct pipes_struct *p,
+ struct samr_GetDisplayEnumerationIndex *r)
+{
+ struct samr_info *dinfo;
+ uint32_t max_entries = (uint32_t) -1;
+ uint32_t enum_context = 0;
+ uint32_t i, num_account = 0;
+ struct samr_displayentry *entries = NULL;
+ NTSTATUS status;
+
+ DEBUG(5,("_samr_GetDisplayEnumerationIndex: %d\n", __LINE__));
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if ((r->in.level < 1) || (r->in.level > 3)) {
+ DEBUG(0,("_samr_GetDisplayEnumerationIndex: "
+ "Unknown info level (%u)\n",
+ r->in.level));
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ become_root();
+
+ /* The following done as ROOT. Don't return without unbecome_root(). */
+
+ switch (r->in.level) {
+ case 1:
+ if (dinfo->disp_info->users == NULL) {
+ dinfo->disp_info->users = pdb_search_users(
+ dinfo->disp_info, ACB_NORMAL);
+ if (dinfo->disp_info->users == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: "
+ "starting user enumeration at index %u\n",
+ (unsigned int)enum_context));
+ } else {
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: "
+ "using cached user enumeration at index %u\n",
+ (unsigned int)enum_context));
+ }
+ num_account = pdb_search_entries(dinfo->disp_info->users,
+ enum_context, max_entries,
+ &entries);
+ break;
+ case 2:
+ if (dinfo->disp_info->machines == NULL) {
+ dinfo->disp_info->machines = pdb_search_users(
+ dinfo->disp_info, ACB_WSTRUST|ACB_SVRTRUST);
+ if (dinfo->disp_info->machines == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: "
+ "starting machine enumeration at index %u\n",
+ (unsigned int)enum_context));
+ } else {
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: "
+ "using cached machine enumeration at index %u\n",
+ (unsigned int)enum_context));
+ }
+ num_account = pdb_search_entries(dinfo->disp_info->machines,
+ enum_context, max_entries,
+ &entries);
+ break;
+ case 3:
+ if (dinfo->disp_info->groups == NULL) {
+ dinfo->disp_info->groups = pdb_search_groups(
+ dinfo->disp_info);
+ if (dinfo->disp_info->groups == NULL) {
+ unbecome_root();
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: "
+ "starting group enumeration at index %u\n",
+ (unsigned int)enum_context));
+ } else {
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: "
+ "using cached group enumeration at index %u\n",
+ (unsigned int)enum_context));
+ }
+ num_account = pdb_search_entries(dinfo->disp_info->groups,
+ enum_context, max_entries,
+ &entries);
+ break;
+ default:
+ unbecome_root();
+ smb_panic("info class changed");
+ break;
+ }
+
+ unbecome_root();
+
+ /* Ensure we cache this enumeration. */
+ set_disp_info_cache_timeout(dinfo->disp_info, DISP_INFO_CACHE_TIMEOUT);
+
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: looking for :%s\n",
+ r->in.name->string));
+
+ for (i=0; i<num_account; i++) {
+ if (strequal(entries[i].account_name, r->in.name->string)) {
+ DEBUG(10,("_samr_GetDisplayEnumerationIndex: "
+ "found %s at idx %d\n",
+ r->in.name->string, i));
+ *r->out.idx = i;
+ return NT_STATUS_OK;
+ }
+ }
+
+ /* assuming account_name lives at the very end */
+ *r->out.idx = num_account;
+
+ return NT_STATUS_NO_MORE_ENTRIES;
+}
+
+/****************************************************************
+ _samr_GetDisplayEnumerationIndex2
+****************************************************************/
+
+NTSTATUS _samr_GetDisplayEnumerationIndex2(struct pipes_struct *p,
+ struct samr_GetDisplayEnumerationIndex2 *r)
+{
+ struct samr_GetDisplayEnumerationIndex q;
+
+ q.in.domain_handle = r->in.domain_handle;
+ q.in.level = r->in.level;
+ q.in.name = r->in.name;
+
+ q.out.idx = r->out.idx;
+
+ return _samr_GetDisplayEnumerationIndex(p, &q);
+}
+
+/****************************************************************
+ _samr_RidToSid
+****************************************************************/
+
+NTSTATUS _samr_RidToSid(struct pipes_struct *p,
+ struct samr_RidToSid *r)
+{
+ struct samr_info *dinfo;
+ NTSTATUS status;
+ struct dom_sid sid;
+
+ dinfo = samr_policy_handle_find(p,
+ r->in.domain_handle,
+ SAMR_HANDLE_DOMAIN,
+ 0,
+ NULL,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!sid_compose(&sid, &dinfo->sid, r->in.rid)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *r->out.sid = dom_sid_dup(p->mem_ctx, &sid);
+ if (!*r->out.sid) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static enum samr_ValidationStatus samr_ValidatePassword_Change(TALLOC_CTX *mem_ctx,
+ const struct samr_PwInfo *dom_pw_info,
+ const struct samr_ValidatePasswordReq2 *req,
+ struct samr_ValidatePasswordRepCtr *rep)
+{
+ NTSTATUS status;
+
+ if (req->password.string == NULL) {
+ return SAMR_VALIDATION_STATUS_SUCCESS;
+ }
+ if (strlen(req->password.string) < dom_pw_info->min_password_length) {
+ ZERO_STRUCT(rep->info);
+ return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT;
+ }
+ if (dom_pw_info->password_properties & DOMAIN_PASSWORD_COMPLEX) {
+ status = check_password_complexity(req->account.string,
+ NULL, /* full_name */
+ req->password.string,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ ZERO_STRUCT(rep->info);
+ return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH;
+ }
+ }
+
+ return SAMR_VALIDATION_STATUS_SUCCESS;
+}
+
+/****************************************************************
+****************************************************************/
+
+static enum samr_ValidationStatus samr_ValidatePassword_Reset(TALLOC_CTX *mem_ctx,
+ const struct samr_PwInfo *dom_pw_info,
+ const struct samr_ValidatePasswordReq3 *req,
+ struct samr_ValidatePasswordRepCtr *rep)
+{
+ NTSTATUS status;
+
+ if (req->password.string == NULL) {
+ return SAMR_VALIDATION_STATUS_SUCCESS;
+ }
+ if (strlen(req->password.string) < dom_pw_info->min_password_length) {
+ ZERO_STRUCT(rep->info);
+ return SAMR_VALIDATION_STATUS_PWD_TOO_SHORT;
+ }
+ if (dom_pw_info->password_properties & DOMAIN_PASSWORD_COMPLEX) {
+ status = check_password_complexity(req->account.string,
+ NULL, /* full_name */
+ req->password.string,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ ZERO_STRUCT(rep->info);
+ return SAMR_VALIDATION_STATUS_NOT_COMPLEX_ENOUGH;
+ }
+ }
+
+ return SAMR_VALIDATION_STATUS_SUCCESS;
+}
+
+/****************************************************************
+ _samr_ValidatePassword
+****************************************************************/
+
+NTSTATUS _samr_ValidatePassword(struct pipes_struct *p,
+ struct samr_ValidatePassword *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
+ union samr_ValidatePasswordRep *rep;
+ NTSTATUS status;
+ struct samr_GetDomPwInfo pw;
+ struct samr_PwInfo dom_pw_info;
+
+ if (p->transport != NCACN_IP_TCP && p->transport != NCALRPC) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ dcesrv_call_auth_info(dce_call, NULL, &auth_level);
+
+ if (auth_level != DCERPC_AUTH_LEVEL_PRIVACY) {
+ p->fault_state = DCERPC_FAULT_ACCESS_DENIED;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (r->in.level < 1 || r->in.level > 3) {
+ return NT_STATUS_INVALID_INFO_CLASS;
+ }
+
+ pw.in.domain_name = NULL;
+ pw.out.info = &dom_pw_info;
+
+ status = _samr_GetDomPwInfo(p, &pw);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ rep = talloc_zero(p->mem_ctx, union samr_ValidatePasswordRep);
+ if (!rep) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ status = NT_STATUS_NOT_SUPPORTED;
+ break;
+ case 2:
+ rep->ctr2.status = samr_ValidatePassword_Change(p->mem_ctx,
+ &dom_pw_info,
+ &r->in.req->req2,
+ &rep->ctr2);
+ break;
+ case 3:
+ rep->ctr3.status = samr_ValidatePassword_Reset(p->mem_ctx,
+ &dom_pw_info,
+ &r->in.req->req3,
+ &rep->ctr3);
+ break;
+ default:
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(rep);
+ return status;
+ }
+
+ *r->out.rep = rep;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_Shutdown(struct pipes_struct *p,
+ struct samr_Shutdown *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_SetMemberAttributesOfGroup(struct pipes_struct *p,
+ struct samr_SetMemberAttributesOfGroup *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_TestPrivateFunctionsDomain(struct pipes_struct *p,
+ struct samr_TestPrivateFunctionsDomain *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_TestPrivateFunctionsUser(struct pipes_struct *p,
+ struct samr_TestPrivateFunctionsUser *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_AddMultipleMembersToAlias(struct pipes_struct *p,
+ struct samr_AddMultipleMembersToAlias *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_RemoveMultipleMembersFromAlias(struct pipes_struct *p,
+ struct samr_RemoveMultipleMembersFromAlias *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_SetBootKeyInformation(struct pipes_struct *p,
+ struct samr_SetBootKeyInformation *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_GetBootKeyInformation(struct pipes_struct *p,
+ struct samr_GetBootKeyInformation *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS _samr_SetDsrmPassword(struct pipes_struct *p,
+ struct samr_SetDsrmPassword *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+void _samr_Opnum68NotUsedOnWire(struct pipes_struct *p,
+ struct samr_Opnum68NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _samr_Opnum69NotUsedOnWire(struct pipes_struct *p,
+ struct samr_Opnum69NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _samr_Opnum70NotUsedOnWire(struct pipes_struct *p,
+ struct samr_Opnum70NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _samr_Opnum71NotUsedOnWire(struct pipes_struct *p,
+ struct samr_Opnum71NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+void _samr_Opnum72NotUsedOnWire(struct pipes_struct *p,
+ struct samr_Opnum72NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+NTSTATUS _samr_ChangePasswordUser4(struct pipes_struct *p,
+ struct samr_ChangePasswordUser4 *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ char *rhost = NULL;
+ struct samu *sampass = NULL;
+ char *username = NULL;
+ uint32_t acct_ctrl = 0;
+ const uint8_t *nt_pw = NULL;
+ gnutls_datum_t nt_key;
+ gnutls_datum_t salt = {
+ .data = r->in.password->salt,
+ .size = sizeof(r->in.password->salt),
+ };
+ uint8_t cdk_data[16] = {0};
+ DATA_BLOB cdk = {
+ .data = cdk_data,
+ .length = sizeof(cdk_data),
+ };
+ char *new_passwd = NULL;
+ bool updated_badpw = false;
+ NTSTATUS update_login_attempts_status;
+ char *mutex_name_by_user = NULL;
+ struct named_mutex *mtx = NULL;
+ NTSTATUS status = NT_STATUS_WRONG_PASSWORD;
+ bool ok;
+ int rc;
+
+ r->out.result = NT_STATUS_WRONG_PASSWORD;
+
+ DBG_NOTICE("_samr_ChangePasswordUser4\n");
+
+ if (r->in.account->string == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (r->in.password == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (r->in.password->PBKDF2Iterations < 5000 ||
+ r->in.password->PBKDF2Iterations > 1000000) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ (void)map_username(frame, r->in.account->string, &username);
+ if (username == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rhost = tsocket_address_inet_addr_string(remote_address, frame);
+ if (rhost == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ sampass = samu_new(frame);
+ if (sampass == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ become_root();
+ ok = pdb_getsampwnam(sampass, username);
+ unbecome_root();
+ if (!ok) {
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ acct_ctrl = pdb_get_acct_ctrl(sampass);
+ if (acct_ctrl & ACB_AUTOLOCK) {
+ status = NT_STATUS_ACCOUNT_LOCKED_OUT;
+ goto done;
+ }
+
+ nt_pw = pdb_get_nt_passwd(sampass);
+ nt_key = (gnutls_datum_t){
+ .data = discard_const_p(uint8_t, nt_pw),
+ .size = NT_HASH_LEN,
+ };
+
+ rc = gnutls_pbkdf2(GNUTLS_MAC_SHA512,
+ &nt_key,
+ &salt,
+ r->in.password->PBKDF2Iterations,
+ cdk.data,
+ cdk.length);
+ if (rc < 0) {
+ BURN_DATA(cdk_data);
+ status = NT_STATUS_WRONG_PASSWORD;
+ goto done;
+ }
+
+ status = samr_set_password_aes(frame,
+ &cdk,
+ r->in.password,
+ &new_passwd);
+ BURN_DATA(cdk_data);
+
+ /*
+ * We must re-load the sam account information under a mutex
+ * lock to ensure we don't miss any concurrent account lockout
+ * changes.
+ */
+
+ /* Clear out old sampass info. */
+ TALLOC_FREE(sampass);
+
+ sampass = samu_new(frame);
+ if (sampass == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ mutex_name_by_user = talloc_asprintf(frame,
+ "check_sam_security_mutex_%s",
+ username);
+ if (mutex_name_by_user == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* Grab the named mutex under root with 30 second timeout. */
+ become_root();
+ mtx = grab_named_mutex(frame, mutex_name_by_user, 30);
+ if (mtx != NULL) {
+ /* Re-load the account information if we got the mutex. */
+ ok = pdb_getsampwnam(sampass, username);
+ }
+ unbecome_root();
+
+ /* Everything from here on until mtx is freed is done under the mutex.*/
+
+ if (mtx == NULL) {
+ DBG_ERR("Acquisition of mutex %s failed "
+ "for user %s\n",
+ mutex_name_by_user,
+ username);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ if (!ok) {
+ /*
+ * Re-load of account failed. This could only happen if the
+ * user was deleted in the meantime.
+ */
+ DBG_NOTICE("reload of user '%s' in passdb failed.\n",
+ username);
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ /*
+ * Check if the account is now locked out - now under the mutex.
+ * This can happen if the server is under
+ * a password guess attack and the ACB_AUTOLOCK is set by
+ * another process.
+ */
+ if (pdb_get_acct_ctrl(sampass) & ACB_AUTOLOCK) {
+ DBG_NOTICE("Account for user %s was locked out.\n", username);
+ status = NT_STATUS_ACCOUNT_LOCKED_OUT;
+ goto done;
+ }
+
+ /*
+ * Notify passdb backend of login success/failure. If not
+ * NT_STATUS_OK the backend doesn't like the login
+ */
+ update_login_attempts_status = pdb_update_login_attempts(
+ sampass, NT_STATUS_IS_OK(status));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ bool increment_bad_pw_count = false;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD) &&
+ (pdb_get_acct_ctrl(sampass) & ACB_NORMAL) &&
+ NT_STATUS_IS_OK(update_login_attempts_status))
+ {
+ increment_bad_pw_count = true;
+ }
+
+ if (increment_bad_pw_count) {
+ pdb_increment_bad_password_count(sampass);
+ updated_badpw = true;
+ } else {
+ pdb_update_bad_password_count(sampass,
+ &updated_badpw);
+ }
+ } else {
+ if ((pdb_get_acct_ctrl(sampass) & ACB_NORMAL) &&
+ (pdb_get_bad_password_count(sampass) > 0))
+ {
+ pdb_set_bad_password_count(sampass, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(sampass, 0, PDB_CHANGED);
+ updated_badpw = true;
+ }
+ }
+
+ if (updated_badpw) {
+ NTSTATUS update_status;
+ become_root();
+ update_status = pdb_update_sam_account(sampass);
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(update_status)) {
+ DEBUG(1, ("Failed to modify entry: %s\n",
+ nt_errstr(update_status)));
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ become_root();
+ status = change_oem_password(sampass,
+ rhost,
+ NULL,
+ new_passwd,
+ true,
+ NULL);
+ unbecome_root();
+ TALLOC_FREE(new_passwd);
+
+done:
+ TALLOC_FREE(frame);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return NT_STATUS_WRONG_PASSWORD;
+ }
+
+ return status;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_samr_scompat.c"
diff --git a/source3/rpc_server/samr/srv_samr_util.c b/source3/rpc_server/samr/srv_samr_util.c
new file mode 100644
index 0000000..fa35ce6
--- /dev/null
+++ b/source3/rpc_server/samr/srv_samr_util.c
@@ -0,0 +1,756 @@
+/*
+ Unix SMB/CIFS implementation.
+ SAMR Pipe utility functions.
+
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Gerald (Jerry) Carter 2000-2001
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Stefan (metze) Metzmacher 2002
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/samr.h"
+#include "rpc_server/samr/srv_samr_util.h"
+#include "passdb.h"
+#include "lib/util/base64.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#define STRING_CHANGED (old_string && !new_string) ||\
+ (!old_string && new_string) ||\
+ (old_string && new_string && (strcmp(old_string, new_string) != 0))
+
+#define STRING_CHANGED_NC(s1,s2) ((s1) && !(s2)) ||\
+ (!(s1) && (s2)) ||\
+ ((s1) && (s2) && (strcmp((s1), (s2)) != 0))
+
+/*************************************************************
+ Copies a struct samr_UserInfo2 to a struct samu
+**************************************************************/
+
+void copy_id2_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo2 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_COMMENT |
+ SAMR_FIELD_COUNTRY_CODE |
+ SAMR_FIELD_CODE_PAGE;
+ i.comment = from->comment;
+ i.country_code = from->country_code;
+ i.code_page = from->code_page;
+
+ copy_id21_to_sam_passwd("INFO_2", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo4 to a struct samu
+**************************************************************/
+
+void copy_id4_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo4 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_LOGON_HOURS;
+ i.logon_hours = from->logon_hours;
+
+ copy_id21_to_sam_passwd("INFO_4", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo6 to a struct samu
+**************************************************************/
+
+void copy_id6_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo6 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_ACCOUNT_NAME |
+ SAMR_FIELD_FULL_NAME;
+ i.account_name = from->account_name;
+ i.full_name = from->full_name;
+
+ copy_id21_to_sam_passwd("INFO_6", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo8 to a struct samu
+**************************************************************/
+
+void copy_id8_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo8 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_FULL_NAME;
+ i.full_name = from->full_name;
+
+ copy_id21_to_sam_passwd("INFO_8", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo10 to a struct samu
+**************************************************************/
+
+void copy_id10_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo10 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_HOME_DIRECTORY |
+ SAMR_FIELD_HOME_DRIVE;
+ i.home_directory = from->home_directory;
+ i.home_drive = from->home_drive;
+
+ copy_id21_to_sam_passwd("INFO_10", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo11 to a struct samu
+**************************************************************/
+
+void copy_id11_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo11 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_LOGON_SCRIPT;
+ i.logon_script = from->logon_script;
+
+ copy_id21_to_sam_passwd("INFO_11", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo12 to a struct samu
+**************************************************************/
+
+void copy_id12_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo12 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_PROFILE_PATH;
+ i.profile_path = from->profile_path;
+
+ copy_id21_to_sam_passwd("INFO_12", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo13 to a struct samu
+**************************************************************/
+
+void copy_id13_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo13 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_DESCRIPTION;
+ i.description = from->description;
+
+ copy_id21_to_sam_passwd("INFO_13", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo14 to a struct samu
+**************************************************************/
+
+void copy_id14_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo14 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_WORKSTATIONS;
+ i.workstations = from->workstations;
+
+ copy_id21_to_sam_passwd("INFO_14", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo16 to a struct samu
+**************************************************************/
+
+void copy_id16_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo16 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_ACCT_FLAGS;
+ i.acct_flags = from->acct_flags;
+
+ copy_id21_to_sam_passwd("INFO_16", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo17 to a struct samu
+**************************************************************/
+
+void copy_id17_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo17 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_ACCT_EXPIRY;
+ i.acct_expiry = from->acct_expiry;
+
+ copy_id21_to_sam_passwd("INFO_17", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo18 to a struct samu
+**************************************************************/
+
+void copy_id18_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo18 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_EXPIRED_FLAG;
+ i.password_expired = from->password_expired;
+
+ copy_id21_to_sam_passwd("INFO_18", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo20 to a struct samu
+**************************************************************/
+
+void copy_id20_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo20 *from)
+{
+ DATA_BLOB mung;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ if (from->parameters.array) {
+ const char *old_string;
+ char *new_string = NULL;
+ old_string = pdb_get_munged_dial(to);
+ mung = data_blob_const(from->parameters.array,
+ from->parameters.length);
+
+ if (mung.length != 0) {
+ new_string = base64_encode_data_blob(talloc_tos(),
+ mung);
+ SMB_ASSERT(new_string != NULL);
+ }
+
+ DEBUG(10,("INFO_20 PARAMETERS: %s -> %s\n",
+ old_string, new_string));
+ if (STRING_CHANGED_NC(old_string,new_string)) {
+ pdb_set_munged_dial(to, new_string, PDB_CHANGED);
+ }
+
+ TALLOC_FREE(new_string);
+ }
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo21 to a struct samu
+**************************************************************/
+
+void copy_id21_to_sam_passwd(const char *log_prefix,
+ struct samu *to,
+ struct samr_UserInfo21 *from)
+{
+ time_t unix_time, stored_time;
+ const char *old_string, *new_string;
+ const char *l;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ if (log_prefix) {
+ l = log_prefix;
+ } else {
+ l = "INFO_21";
+ }
+
+ if (from->fields_present & SAMR_FIELD_LAST_LOGON) {
+ unix_time = nt_time_to_unix(from->last_logon);
+ stored_time = pdb_get_logon_time(to);
+ DEBUG(10,("%s SAMR_FIELD_LAST_LOGON: %lu -> %lu\n", l,
+ (long unsigned int)stored_time,
+ (long unsigned int)unix_time));
+ if (stored_time != unix_time) {
+ pdb_set_logon_time(to, unix_time, PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_LAST_LOGOFF) {
+ unix_time = nt_time_to_unix(from->last_logoff);
+ stored_time = pdb_get_logoff_time(to);
+ DEBUG(10,("%s SAMR_FIELD_LAST_LOGOFF: %lu -> %lu\n", l,
+ (long unsigned int)stored_time,
+ (long unsigned int)unix_time));
+ if (stored_time != unix_time) {
+ pdb_set_logoff_time(to, unix_time, PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_ACCT_EXPIRY) {
+ unix_time = nt_time_to_unix(from->acct_expiry);
+ stored_time = pdb_get_kickoff_time(to);
+ DEBUG(10,("%s SAMR_FIELD_ACCT_EXPIRY: %lu -> %lu\n", l,
+ (long unsigned int)stored_time,
+ (long unsigned int)unix_time));
+ if (stored_time != unix_time) {
+ pdb_set_kickoff_time(to, unix_time , PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_LAST_PWD_CHANGE) {
+ unix_time = nt_time_to_unix(from->last_password_change);
+ stored_time = pdb_get_pass_last_set_time(to);
+ DEBUG(10,("%s SAMR_FIELD_LAST_PWD_CHANGE: %lu -> %lu\n", l,
+ (long unsigned int)stored_time,
+ (long unsigned int)unix_time));
+ if (stored_time != unix_time) {
+ pdb_set_pass_last_set_time(to, unix_time, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_ACCOUNT_NAME) &&
+ (from->account_name.string)) {
+ old_string = pdb_get_username(to);
+ new_string = from->account_name.string;
+ DEBUG(10,("%s SAMR_FIELD_ACCOUNT_NAME: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_username(to, new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_FULL_NAME) &&
+ (from->full_name.string)) {
+ old_string = pdb_get_fullname(to);
+ new_string = from->full_name.string;
+ DEBUG(10,("%s SAMR_FIELD_FULL_NAME: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_fullname(to, new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_HOME_DIRECTORY) &&
+ (from->home_directory.string)) {
+ old_string = pdb_get_homedir(to);
+ new_string = from->home_directory.string;
+ DEBUG(10,("%s SAMR_FIELD_HOME_DIRECTORY: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_homedir(to, new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_HOME_DRIVE) &&
+ (from->home_drive.string)) {
+ old_string = pdb_get_dir_drive(to);
+ new_string = from->home_drive.string;
+ DEBUG(10,("%s SAMR_FIELD_HOME_DRIVE: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_dir_drive(to, new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_LOGON_SCRIPT) &&
+ (from->logon_script.string)) {
+ old_string = pdb_get_logon_script(to);
+ new_string = from->logon_script.string;
+ DEBUG(10,("%s SAMR_FIELD_LOGON_SCRIPT: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_logon_script(to , new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_PROFILE_PATH) &&
+ (from->profile_path.string)) {
+ old_string = pdb_get_profile_path(to);
+ new_string = from->profile_path.string;
+ DEBUG(10,("%s SAMR_FIELD_PROFILE_PATH: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_profile_path(to , new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_DESCRIPTION) &&
+ (from->description.string)) {
+ old_string = pdb_get_acct_desc(to);
+ new_string = from->description.string;
+ DEBUG(10,("%s SAMR_FIELD_DESCRIPTION: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_acct_desc(to, new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_WORKSTATIONS) &&
+ (from->workstations.string)) {
+ old_string = pdb_get_workstations(to);
+ new_string = from->workstations.string;
+ DEBUG(10,("%s SAMR_FIELD_WORKSTATIONS: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_workstations(to , new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_COMMENT) &&
+ (from->comment.string)) {
+ old_string = pdb_get_comment(to);
+ new_string = from->comment.string;
+ DEBUG(10,("%s SAMR_FIELD_COMMENT: %s -> %s\n", l,
+ old_string, new_string));
+ if (STRING_CHANGED) {
+ pdb_set_comment(to, new_string, PDB_CHANGED);
+ }
+ }
+
+ if ((from->fields_present & SAMR_FIELD_PARAMETERS) &&
+ (from->parameters.array)) {
+ char *newstr = NULL;
+ DATA_BLOB mung;
+ old_string = pdb_get_munged_dial(to);
+
+ mung = data_blob_const(from->parameters.array,
+ from->parameters.length);
+
+ if (mung.length != 0) {
+ newstr = base64_encode_data_blob(talloc_tos(), mung);
+ SMB_ASSERT(newstr != NULL);
+ }
+ DEBUG(10,("%s SAMR_FIELD_PARAMETERS: %s -> %s\n", l,
+ old_string, newstr));
+ if (STRING_CHANGED_NC(old_string,newstr)) {
+ pdb_set_munged_dial(to, newstr, PDB_CHANGED);
+ }
+
+ TALLOC_FREE(newstr);
+ }
+
+ if (from->fields_present & SAMR_FIELD_RID) {
+ if (from->rid == 0) {
+ DEBUG(10,("%s: Asked to set User RID to 0 !? Skipping change!\n", l));
+ } else if (from->rid != pdb_get_user_rid(to)) {
+ DEBUG(10,("%s SAMR_FIELD_RID: %u -> %u NOT UPDATED!\n", l,
+ pdb_get_user_rid(to), from->rid));
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_PRIMARY_GID) {
+ if (from->primary_gid == 0) {
+ DEBUG(10,("%s: Asked to set Group RID to 0 !? Skipping change!\n", l));
+ } else if (from->primary_gid != pdb_get_group_rid(to)) {
+ DEBUG(10,("%s SAMR_FIELD_PRIMARY_GID: %u -> %u\n", l,
+ pdb_get_group_rid(to), from->primary_gid));
+ pdb_set_group_sid_from_rid(to,
+ from->primary_gid, PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_ACCT_FLAGS) {
+ DEBUG(10,("%s SAMR_FIELD_ACCT_FLAGS: %08X -> %08X\n", l,
+ pdb_get_acct_ctrl(to), from->acct_flags));
+ if (from->acct_flags != pdb_get_acct_ctrl(to)) {
+
+ /* You cannot autolock an unlocked account via
+ * setuserinfo calls, so make sure to remove the
+ * ACB_AUTOLOCK bit here - gd */
+
+ if ((from->acct_flags & ACB_AUTOLOCK) &&
+ !(pdb_get_acct_ctrl(to) & ACB_AUTOLOCK)) {
+ from->acct_flags &= ~ACB_AUTOLOCK;
+ }
+
+ if (!(from->acct_flags & ACB_AUTOLOCK) &&
+ (pdb_get_acct_ctrl(to) & ACB_AUTOLOCK)) {
+ /* We're unlocking a previously locked user. Reset bad password counts.
+ Patch from Jianliang Lu. <Jianliang.Lu@getronics.com> */
+ pdb_set_bad_password_count(to, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(to, 0, PDB_CHANGED);
+ }
+ pdb_set_acct_ctrl(to, from->acct_flags, PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_LOGON_HOURS) {
+ char oldstr[44]; /* hours strings are 42 bytes. */
+ char newstr[44];
+ DEBUG(15,("%s SAMR_FIELD_LOGON_HOURS (units_per_week): %08X -> %08X\n", l,
+ pdb_get_logon_divs(to), from->logon_hours.units_per_week));
+ if (from->logon_hours.units_per_week != pdb_get_logon_divs(to)) {
+ pdb_set_logon_divs(to,
+ from->logon_hours.units_per_week, PDB_CHANGED);
+ }
+
+ DEBUG(15,("%s SAMR_FIELD_LOGON_HOURS (units_per_week/8): %08X -> %08X\n", l,
+ pdb_get_hours_len(to),
+ from->logon_hours.units_per_week/8));
+ if (from->logon_hours.units_per_week/8 != pdb_get_hours_len(to)) {
+ pdb_set_hours_len(to,
+ from->logon_hours.units_per_week/8, PDB_CHANGED);
+ }
+
+ DEBUG(15,("%s SAMR_FIELD_LOGON_HOURS (bits): %s -> %s\n", l,
+ pdb_get_hours(to), from->logon_hours.bits));
+ pdb_sethexhours(oldstr, pdb_get_hours(to));
+ pdb_sethexhours(newstr, from->logon_hours.bits);
+ if (!strequal(oldstr, newstr)) {
+ pdb_set_hours(to, from->logon_hours.bits,
+ from->logon_hours.units_per_week/8,
+ PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_BAD_PWD_COUNT) {
+ DEBUG(10,("%s SAMR_FIELD_BAD_PWD_COUNT: %08X -> %08X\n", l,
+ pdb_get_bad_password_count(to), from->bad_password_count));
+ if (from->bad_password_count != pdb_get_bad_password_count(to)) {
+ pdb_set_bad_password_count(to,
+ from->bad_password_count, PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_NUM_LOGONS) {
+ DEBUG(10,("%s SAMR_FIELD_NUM_LOGONS: %08X -> %08X\n", l,
+ pdb_get_logon_count(to), from->logon_count));
+ if (from->logon_count != pdb_get_logon_count(to)) {
+ pdb_set_logon_count(to, from->logon_count, PDB_CHANGED);
+ }
+ }
+
+ /* If the must change flag is set, the last set time goes to zero.
+ the must change and can change fields also do, but they are
+ calculated from policy, not set from the wire */
+
+ if (from->fields_present & SAMR_FIELD_EXPIRED_FLAG) {
+ DEBUG(10,("%s SAMR_FIELD_EXPIRED_FLAG: %02X\n", l,
+ from->password_expired));
+ if (from->password_expired != 0) {
+ /* Only allow the set_time to zero (which means
+ "User Must Change Password on Next Login"
+ if the user object allows password change. */
+ if (pdb_get_pass_can_change(to)) {
+ pdb_set_pass_last_set_time(to, 0, PDB_CHANGED);
+ } else {
+ DEBUG(10,("%s Disallowing set of 'User Must "
+ "Change Password on Next Login' as "
+ "user object disallows this.\n", l));
+ }
+ } else {
+ /* A subtlety here: some windows commands will
+ clear the expired flag even though it's not
+ set, and we don't want to reset the time
+ in these caess. "net user /dom <user> /active:y"
+ for example, to clear an autolocked acct.
+ We must check to see if it's expired first. jmcd */
+
+ uint32_t pwd_max_age = 0;
+ time_t now = time(NULL);
+
+ pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &pwd_max_age);
+
+ if (pwd_max_age == (uint32_t)-1 || pwd_max_age == 0) {
+ pwd_max_age = get_time_t_max();
+ }
+
+ stored_time = pdb_get_pass_last_set_time(to);
+
+ /* we will only *set* a pwdlastset date when
+ a) the last pwdlastset time was 0 (user was forced to
+ change password).
+ b) the users password has not expired. gd. */
+
+ if ((stored_time == 0) ||
+ ((now - stored_time) > pwd_max_age)) {
+ pdb_set_pass_last_set_time(to, now, PDB_CHANGED);
+ }
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_COUNTRY_CODE) {
+ DEBUG(10,("%s SAMR_FIELD_COUNTRY_CODE: %08X -> %08X\n", l,
+ pdb_get_country_code(to), from->country_code));
+ if (from->country_code != pdb_get_country_code(to)) {
+ pdb_set_country_code(to,
+ from->country_code, PDB_CHANGED);
+ }
+ }
+
+ if (from->fields_present & SAMR_FIELD_CODE_PAGE) {
+ DEBUG(10,("%s SAMR_FIELD_CODE_PAGE: %08X -> %08X\n", l,
+ pdb_get_code_page(to), from->code_page));
+ if (from->code_page != pdb_get_code_page(to)) {
+ pdb_set_code_page(to,
+ from->code_page, PDB_CHANGED);
+ }
+ }
+}
+
+
+/*************************************************************
+ Copies a struct samr_UserInfo23 to a struct samu
+**************************************************************/
+
+void copy_id23_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo23 *from)
+{
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ copy_id21_to_sam_passwd("INFO 23", to, &from->info);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo24 to a struct samu
+**************************************************************/
+
+void copy_id24_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo24 *from)
+{
+ struct samr_UserInfo21 i;
+
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ ZERO_STRUCT(i);
+
+ i.fields_present = SAMR_FIELD_EXPIRED_FLAG;
+ i.password_expired = from->password_expired;
+
+ copy_id21_to_sam_passwd("INFO_24", to, &i);
+}
+
+/*************************************************************
+ Copies a struct samr_UserInfo25 to a struct samu
+**************************************************************/
+
+void copy_id25_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo25 *from)
+{
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ copy_id21_to_sam_passwd("INFO_25", to, &from->info);
+}
+
+void copy_id32_to_sam_passwd(struct samu *to, struct samr_UserInfo32 *from)
+{
+ if (from == NULL || to == NULL) {
+ return;
+ }
+
+ copy_id21_to_sam_passwd("INFO_32", to, &from->info);
+}
+
+void copy_pwd_expired_to_sam_passwd(struct samu *to,
+ uint8_t password_expired)
+{
+ struct samr_UserInfo21 i = {
+ .fields_present = SAMR_FIELD_EXPIRED_FLAG,
+ .password_expired = password_expired,
+ };
+
+ if (to == NULL) {
+ return;
+ }
+
+ copy_id21_to_sam_passwd("INFO_GENERIC", to, &i);
+}
diff --git a/source3/rpc_server/samr/srv_samr_util.h b/source3/rpc_server/samr/srv_samr_util.h
new file mode 100644
index 0000000..5e839ac
--- /dev/null
+++ b/source3/rpc_server/samr/srv_samr_util.h
@@ -0,0 +1,89 @@
+/*
+ Unix SMB/CIFS implementation.
+ SAMR Pipe utility functions.
+
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Gerald (Jerry) Carter 2000-2001
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Stefan (metze) Metzmacher 2002
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* The following definitions come from rpc_server/srv_samr_util.c */
+
+struct samu;
+
+void copy_id2_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo2 *from);
+void copy_id4_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo4 *from);
+void copy_id6_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo6 *from);
+void copy_id8_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo8 *from);
+void copy_id10_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo10 *from);
+void copy_id11_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo11 *from);
+void copy_id12_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo12 *from);
+void copy_id13_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo13 *from);
+void copy_id14_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo14 *from);
+void copy_id16_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo16 *from);
+void copy_id17_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo17 *from);
+void copy_id18_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo18 *from);
+void copy_id20_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo20 *from);
+void copy_id21_to_sam_passwd(const char *log_prefix,
+ struct samu *to,
+ struct samr_UserInfo21 *from);
+void copy_id23_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo23 *from);
+void copy_id24_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo24 *from);
+void copy_id25_to_sam_passwd(struct samu *to,
+ struct samr_UserInfo25 *from);
+void copy_id32_to_sam_passwd(struct samu *to, struct samr_UserInfo32 *from);
+void copy_pwd_expired_to_sam_passwd(struct samu *to,
+ uint8_t password_expired);
+
+/* The following definitions come from rpc_server/srv_samr_chgpasswd.c */
+
+bool chgpasswd(const char *name, const char *rhost, const struct passwd *pass,
+ const char *oldpass, const char *newpass, bool as_root);
+NTSTATUS change_oem_password(struct samu *hnd, const char *rhost,
+ char *old_passwd, char *new_passwd,
+ bool as_root,
+ enum samPwdChangeReason *samr_reject_reason);
+NTSTATUS pass_oem_change(char *user, const char *rhost,
+ uchar password_encrypted_with_lm_hash[516],
+ const uchar old_lm_hash_encrypted[16],
+ uchar password_encrypted_with_nt_hash[516],
+ const uchar old_nt_hash_encrypted[16],
+ enum samPwdChangeReason *reject_reason);
+NTSTATUS check_password_complexity(const char *username,
+ const char *fullname,
+ const char *password,
+ enum samPwdChangeReason *samr_reject_reason);
+NTSTATUS samr_set_password_aes(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *cdk,
+ struct samr_EncryptedPasswordAES *pwbuf,
+ char **new_password_str);
diff --git a/source3/rpc_server/spoolss/iremotewinspool_util.c b/source3/rpc_server/spoolss/iremotewinspool_util.c
new file mode 100644
index 0000000..6b9c726
--- /dev/null
+++ b/source3/rpc_server/spoolss/iremotewinspool_util.c
@@ -0,0 +1,156 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Guenther Deschner 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "librpc/gen_ndr/ndr_winspool.h"
+#include "librpc/gen_ndr/ndr_spoolss.h"
+#include "rpc_server/spoolss/iremotewinspool_util.h"
+
+#define _PAR_MAPPING(NAME) { .iremotewinspool_opcode = NDR_WINSPOOL_ASYNC ##NAME, .spoolss_opcode = NDR_SPOOLSS_ ##NAME }
+#define _PAR_MAPPING_EX(NAME) { .iremotewinspool_opcode = NDR_WINSPOOL_ASYNC ##NAME, .spoolss_opcode = NDR_SPOOLSS_ ##NAME## EX }
+#define _PAR_MAPPING_2(NAME) { .iremotewinspool_opcode = NDR_WINSPOOL_ASYNC ##NAME, .spoolss_opcode = NDR_SPOOLSS_ ##NAME## 2 }
+
+struct {
+ int iremotewinspool_opcode;
+ int spoolss_opcode;
+} proxy_table[] = {
+
+ /* 3.1.4.1. Printer Management Methods */
+
+ _PAR_MAPPING_EX(OPENPRINTER),
+ _PAR_MAPPING_EX(ADDPRINTER),
+ _PAR_MAPPING(DELETEPRINTER),
+ _PAR_MAPPING(SETPRINTER),
+ _PAR_MAPPING(GETPRINTER),
+ _PAR_MAPPING(GETPRINTERDATA),
+ _PAR_MAPPING(GETPRINTERDATAEX),
+ _PAR_MAPPING(SETPRINTERDATA),
+ _PAR_MAPPING(SETPRINTERDATAEX),
+ _PAR_MAPPING(CLOSEPRINTER),
+ _PAR_MAPPING(ENUMPRINTERDATA),
+ _PAR_MAPPING(ENUMPRINTERDATAEX),
+ _PAR_MAPPING(ENUMPRINTERKEY),
+ _PAR_MAPPING(DELETEPRINTERDATA),
+ _PAR_MAPPING(DELETEPRINTERDATAEX),
+ _PAR_MAPPING(DELETEPRINTERKEY),
+ _PAR_MAPPING(SENDRECVBIDIDATA),
+ _PAR_MAPPING(CREATEPRINTERIC),
+ _PAR_MAPPING(PLAYGDISCRIPTONPRINTERIC),
+ _PAR_MAPPING(DELETEPRINTERIC),
+ _PAR_MAPPING(ENUMPRINTERS),
+ _PAR_MAPPING(ADDPERMACHINECONNECTION),
+ _PAR_MAPPING(DELETEPERMACHINECONNECTION),
+ _PAR_MAPPING(ENUMPERMACHINECONNECTIONS),
+ _PAR_MAPPING(RESETPRINTER),
+
+ /* 3.1.4.2. Printer Driver Management Methods */
+
+ _PAR_MAPPING_2(GETPRINTERDRIVER),
+ _PAR_MAPPING_EX(ADDPRINTERDRIVER),
+ _PAR_MAPPING(ENUMPRINTERDRIVERS),
+ _PAR_MAPPING(GETPRINTERDRIVERDIRECTORY),
+ _PAR_MAPPING(DELETEPRINTERDRIVER),
+ _PAR_MAPPING(DELETEPRINTERDRIVEREX),
+ /* No mapping for: RpcAsyncInstallPrinterDriverFromPackage */
+ /* No mapping for: RpcAsyncUploadPrinterDriverPackage */
+ _PAR_MAPPING(GETCOREPRINTERDRIVERS),
+ /* No mapping for: RpcAsyncCorePrinterDriverInstalled */
+ _PAR_MAPPING(GETPRINTERDRIVERPACKAGEPATH),
+ /* No mapping for: RpcAsyncDeletePrinterDriverPackage */
+
+ /* 3.1.4.3. Printer Port Management Methods */
+
+ _PAR_MAPPING(XCVDATA),
+ _PAR_MAPPING(ENUMPORTS),
+ _PAR_MAPPING_EX(ADDPORT),
+ _PAR_MAPPING(SETPORT),
+
+ /* 3.1.4.4. Printer Processor Management Methods */
+
+ _PAR_MAPPING(ADDPRINTPROCESSOR),
+ _PAR_MAPPING(ENUMPRINTPROCESSORS),
+ _PAR_MAPPING(GETPRINTPROCESSORDIRECTORY),
+ _PAR_MAPPING(DELETEPRINTPROCESSOR),
+ _PAR_MAPPING(ENUMPRINTPROCESSORDATATYPES),
+
+ /* 3.1.4.5. Port Monitor Management Methods */
+
+ _PAR_MAPPING(ENUMMONITORS),
+ _PAR_MAPPING(ADDMONITOR),
+ _PAR_MAPPING(DELETEMONITOR),
+
+ /* 3.1.4.6. Form Management Methods */
+
+ _PAR_MAPPING(ADDFORM),
+ _PAR_MAPPING(DELETEFORM),
+ _PAR_MAPPING(GETFORM),
+ _PAR_MAPPING(SETFORM),
+ _PAR_MAPPING(ENUMFORMS),
+
+ /* 3.1.4.7. Job Management Methods */
+
+ _PAR_MAPPING(SETJOB),
+ _PAR_MAPPING(GETJOB),
+ _PAR_MAPPING(ENUMJOBS),
+ _PAR_MAPPING(ADDJOB),
+ _PAR_MAPPING(SCHEDULEJOB),
+
+ /* 3.1.4.8. Job Printing Methods */
+
+ _PAR_MAPPING(STARTDOCPRINTER),
+ _PAR_MAPPING(STARTPAGEPRINTER),
+ _PAR_MAPPING(WRITEPRINTER),
+ _PAR_MAPPING(ENDPAGEPRINTER),
+ _PAR_MAPPING(ENDDOCPRINTER),
+ _PAR_MAPPING(ABORTPRINTER),
+ _PAR_MAPPING(READPRINTER),
+
+ /* 3.1.4.9. Printing Related Notification Methods */
+
+ /* No mapping for: RpcSyncRegisterForRemoteNotifications */
+ /* No mapping for: RpcSyncUnRegisterForRemoteNotifications */
+ /* No mapping for: RpcSyncRefreshRemoteNotifications */
+ /* No mapping for: RpcAsyncGetRemoteNotifications */
+
+ /* 3.1.4.10. Job Named Property Management Methods */
+
+ _PAR_MAPPING(GETJOBNAMEDPROPERTYVALUE),
+ _PAR_MAPPING(SETJOBNAMEDPROPERTY),
+ _PAR_MAPPING(DELETEJOBNAMEDPROPERTY),
+ _PAR_MAPPING(ENUMJOBNAMEDPROPERTIES),
+
+ /* 3.1.4.11. Branch Office Remote Logging Methods */
+
+ _PAR_MAPPING(LOGJOBINFOFORBRANCHOFFICE),
+};
+
+bool iremotewinspool_map_opcode(uint16_t opcode,
+ uint16_t *proxy_opcode)
+{
+ int i;
+
+ for (i = 0; i <ARRAY_SIZE(proxy_table); i++) {
+ if (proxy_table[i].iremotewinspool_opcode == opcode) {
+ *proxy_opcode = proxy_table[i].spoolss_opcode;
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/source3/rpc_server/spoolss/iremotewinspool_util.h b/source3/rpc_server/spoolss/iremotewinspool_util.h
new file mode 100644
index 0000000..e2de82a
--- /dev/null
+++ b/source3/rpc_server/spoolss/iremotewinspool_util.h
@@ -0,0 +1,21 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Guenther Deschner 2016
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+bool iremotewinspool_map_opcode(uint16_t opcode,
+ uint16_t *proxy_opcode);
diff --git a/source3/rpc_server/spoolss/srv_iremotewinspool.c b/source3/rpc_server/spoolss/srv_iremotewinspool.c
new file mode 100644
index 0000000..5bc4228
--- /dev/null
+++ b/source3/rpc_server/spoolss/srv_iremotewinspool.c
@@ -0,0 +1,2382 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * server auto-generated by pidl. DO NOT MODIFY!
+ */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_spoolss.h"
+#include "librpc/gen_ndr/ndr_winspool.h"
+#include "librpc/gen_ndr/ndr_winspool_scompat.h"
+#include "librpc/gen_ndr/ndr_spoolss_scompat.h"
+#include "rpc_server/rpc_config.h"
+#include "rpc_server/rpc_server.h"
+#include "rpc_server/spoolss/iremotewinspool_util.h"
+
+static bool forward_opnum_to_spoolss(uint16_t opnum) {
+ switch (opnum) {
+ case 58: /* winspool_SyncRegisterForRemoteNotifications */
+ case 59: /* winspool_SyncUnRegisterForRemoteNotifications */
+ case 60: /* winspool_SyncRefreshRemoteNotifications */
+ case 61: /* winspool_AsyncGetRemoteNotifications */
+ case 62: /* winspool_AsyncInstallPrinterDriverFromPackage */
+ case 63: /* winspool_AsyncUploadPrinterDriverPackage */
+ case 65: /* winspool_AsyncCorePrinterDriverInstalled */
+ case 67: /* winspool_AsyncDeletePrinterDriverPackage */
+ return false;
+ default:
+ break;
+ }
+ return true;
+}
+
+/* iremotewinspool - dcerpc server boilerplate generated by pidl */
+static NTSTATUS iremotewinspool__op_bind(struct dcesrv_connection_context *context, const struct dcesrv_interface *iface)
+{
+#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_BIND
+ return DCESRV_INTERFACE_IREMOTEWINSPOOL_BIND(context,iface);
+#else
+ return NT_STATUS_OK;
+#endif
+}
+
+static void iremotewinspool__op_unbind(struct dcesrv_connection_context *context, const struct dcesrv_interface *iface)
+{
+#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_UNBIND
+ DCESRV_INTERFACE_IREMOTEWINSPOOL_UNBIND(context, iface);
+#else
+ return;
+#endif
+}
+
+NTSTATUS iremotewinspool__op_ndr_pull(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_pull *pull, void **r)
+{
+ enum ndr_err_code ndr_err;
+ uint16_t opnum = dce_call->pkt.u.request.opnum;
+ uint16_t mapped_opnum;
+
+ dce_call->fault_code = 0;
+
+ if (forward_opnum_to_spoolss(opnum)) {
+ bool ok;
+ ok = iremotewinspool_map_opcode(opnum, &mapped_opnum);
+ if (ok) {
+ dce_call->pkt.u.request.opnum = mapped_opnum;
+ }
+ return spoolss__op_ndr_pull(dce_call, mem_ctx, pull, r);
+ }
+
+ if (opnum >= ndr_table_iremotewinspool.num_calls) {
+ dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR;
+ return NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ *r = talloc_named(mem_ctx, ndr_table_iremotewinspool.calls[opnum].struct_size, "struct %s", ndr_table_iremotewinspool.calls[opnum].name);
+ NT_STATUS_HAVE_NO_MEMORY(*r);
+
+ /* unravel the NDR for the packet */
+ ndr_err = ndr_table_iremotewinspool.calls[opnum].ndr_pull(pull, NDR_IN, *r);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ dce_call->fault_code = DCERPC_FAULT_NDR;
+ return NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS iremotewinspool__op_dispatch_internal(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r, bool rpcint_call)
+{
+ uint16_t opnum = dce_call->pkt.u.request.opnum;
+ struct pipes_struct *p = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ bool impersonated = false;
+ bool ok;
+ struct GUID object_uuid;
+
+ ok = dce_call->pkt.pfc_flags & DCERPC_PFC_FLAG_OBJECT_UUID;
+ if (!ok) {
+ dce_call->fault_code = DCERPC_NCA_S_UNSUPPORTED_TYPE;
+ return NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ status = GUID_from_string(IREMOTEWINSPOOL_OBJECT_GUID, &object_uuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ dce_call->fault_code = DCERPC_NCA_S_UNSUPPORTED_TYPE;
+ return NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ if (!GUID_equal(&dce_call->pkt.u.request.object.object, &object_uuid)) {
+ dce_call->fault_code = DCERPC_NCA_S_UNSUPPORTED_TYPE;
+ return NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ if (forward_opnum_to_spoolss(opnum)) {
+ return spoolss__op_dispatch(dce_call, mem_ctx, r);
+ }
+
+ /* Retrieve pipes struct */
+ p = dcesrv_get_pipes_struct(dce_call->conn);
+ p->dce_call = dce_call;
+ p->mem_ctx = mem_ctx;
+ /* Reset pipes struct fault state */
+ p->fault_state = 0;
+
+ /* Impersonate */
+ if (!rpcint_call) {
+ impersonated = become_authenticated_pipe_user(
+ dce_call->auth_state->session_info);
+ if (!impersonated) {
+ dce_call->fault_code = DCERPC_FAULT_ACCESS_DENIED;
+ status = NT_STATUS_NET_WRITE_FAULT;
+ goto fail;
+ }
+ }
+
+ switch (opnum) {
+ case 0: { /* winspool_AsyncOpenPrinter */
+ struct winspool_AsyncOpenPrinter *r2 = (struct winspool_AsyncOpenPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncOpenPrinter, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pHandle = talloc_zero(r2, struct policy_handle);
+ if (r2->out.pHandle == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncOpenPrinter(p, r2);
+ break;
+ }
+ case 1: { /* winspool_AsyncAddPrinter */
+ struct winspool_AsyncAddPrinter *r2 = (struct winspool_AsyncAddPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinter, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pHandle = talloc_zero(r2, struct policy_handle);
+ if (r2->out.pHandle == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncAddPrinter(p, r2);
+ break;
+ }
+ case 2: { /* winspool_AsyncSetJob */
+ struct winspool_AsyncSetJob *r2 = (struct winspool_AsyncSetJob *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJob, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncSetJob(p, r2);
+ break;
+ }
+ case 3: { /* winspool_AsyncGetJob */
+ struct winspool_AsyncGetJob *r2 = (struct winspool_AsyncGetJob *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJob, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pJob = r2->in.pJob;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetJob(p, r2);
+ break;
+ }
+ case 4: { /* winspool_AsyncEnumJobs */
+ struct winspool_AsyncEnumJobs *r2 = (struct winspool_AsyncEnumJobs *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobs, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pJob = r2->in.pJob;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumJobs(p, r2);
+ break;
+ }
+ case 5: { /* winspool_AsyncAddJob */
+ struct winspool_AsyncAddJob *r2 = (struct winspool_AsyncAddJob *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddJob, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pAddJob = r2->in.pAddJob;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncAddJob(p, r2);
+ break;
+ }
+ case 6: { /* winspool_AsyncScheduleJob */
+ struct winspool_AsyncScheduleJob *r2 = (struct winspool_AsyncScheduleJob *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncScheduleJob, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncScheduleJob(p, r2);
+ break;
+ }
+ case 7: { /* winspool_AsyncDeletePrinter */
+ struct winspool_AsyncDeletePrinter *r2 = (struct winspool_AsyncDeletePrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinter, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrinter(p, r2);
+ break;
+ }
+ case 8: { /* winspool_AsyncSetPrinter */
+ struct winspool_AsyncSetPrinter *r2 = (struct winspool_AsyncSetPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinter, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncSetPrinter(p, r2);
+ break;
+ }
+ case 9: { /* winspool_AsyncGetPrinter */
+ struct winspool_AsyncGetPrinter *r2 = (struct winspool_AsyncGetPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinter, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pPrinter = r2->in.pPrinter;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetPrinter(p, r2);
+ break;
+ }
+ case 10: { /* winspool_AsyncStartDocPrinter */
+ struct winspool_AsyncStartDocPrinter *r2 = (struct winspool_AsyncStartDocPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartDocPrinter, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pJobId = talloc_zero(r2, uint32_t);
+ if (r2->out.pJobId == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncStartDocPrinter(p, r2);
+ break;
+ }
+ case 11: { /* winspool_AsyncStartPagePrinter */
+ struct winspool_AsyncStartPagePrinter *r2 = (struct winspool_AsyncStartPagePrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartPagePrinter, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncStartPagePrinter(p, r2);
+ break;
+ }
+ case 12: { /* winspool_AsyncWritePrinter */
+ struct winspool_AsyncWritePrinter *r2 = (struct winspool_AsyncWritePrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncWritePrinter, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pcWritten = talloc_zero(r2, uint32_t);
+ if (r2->out.pcWritten == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncWritePrinter(p, r2);
+ break;
+ }
+ case 13: { /* winspool_AsyncEndPagePrinter */
+ struct winspool_AsyncEndPagePrinter *r2 = (struct winspool_AsyncEndPagePrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndPagePrinter, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncEndPagePrinter(p, r2);
+ break;
+ }
+ case 14: { /* winspool_AsyncEndDocPrinter */
+ struct winspool_AsyncEndDocPrinter *r2 = (struct winspool_AsyncEndDocPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndDocPrinter, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncEndDocPrinter(p, r2);
+ break;
+ }
+ case 15: { /* winspool_AsyncAbortPrinter */
+ struct winspool_AsyncAbortPrinter *r2 = (struct winspool_AsyncAbortPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAbortPrinter, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncAbortPrinter(p, r2);
+ break;
+ }
+ case 16: { /* winspool_AsyncGetPrinterData */
+ struct winspool_AsyncGetPrinterData *r2 = (struct winspool_AsyncGetPrinterData *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterData, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pType = talloc_zero(r2, uint32_t);
+ if (r2->out.pType == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pData = talloc_zero_array(r2, uint8_t, r2->in.nSize);
+ if (r2->out.pData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetPrinterData(p, r2);
+ break;
+ }
+ case 17: { /* winspool_AsyncGetPrinterDataEx */
+ struct winspool_AsyncGetPrinterDataEx *r2 = (struct winspool_AsyncGetPrinterDataEx *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDataEx, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pType = talloc_zero(r2, uint32_t);
+ if (r2->out.pType == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pData = talloc_zero_array(r2, uint8_t, r2->in.nSize);
+ if (r2->out.pData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetPrinterDataEx(p, r2);
+ break;
+ }
+ case 18: { /* winspool_AsyncSetPrinterData */
+ struct winspool_AsyncSetPrinterData *r2 = (struct winspool_AsyncSetPrinterData *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterData, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncSetPrinterData(p, r2);
+ break;
+ }
+ case 19: { /* winspool_AsyncSetPrinterDataEx */
+ struct winspool_AsyncSetPrinterDataEx *r2 = (struct winspool_AsyncSetPrinterDataEx *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterDataEx, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncSetPrinterDataEx(p, r2);
+ break;
+ }
+ case 20: { /* winspool_AsyncClosePrinter */
+ struct winspool_AsyncClosePrinter *r2 = (struct winspool_AsyncClosePrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncClosePrinter, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.phPrinter = r2->in.phPrinter;
+ r2->out.result = _winspool_AsyncClosePrinter(p, r2);
+ break;
+ }
+ case 21: { /* winspool_AsyncAddForm */
+ struct winspool_AsyncAddForm *r2 = (struct winspool_AsyncAddForm *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddForm, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncAddForm(p, r2);
+ break;
+ }
+ case 22: { /* winspool_AsyncDeleteForm */
+ struct winspool_AsyncDeleteForm *r2 = (struct winspool_AsyncDeleteForm *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteForm, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeleteForm(p, r2);
+ break;
+ }
+ case 23: { /* winspool_AsyncGetForm */
+ struct winspool_AsyncGetForm *r2 = (struct winspool_AsyncGetForm *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetForm, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pForm = r2->in.pForm;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetForm(p, r2);
+ break;
+ }
+ case 24: { /* winspool_AsyncSetForm */
+ struct winspool_AsyncSetForm *r2 = (struct winspool_AsyncSetForm *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetForm, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncSetForm(p, r2);
+ break;
+ }
+ case 25: { /* winspool_AsyncEnumForms */
+ struct winspool_AsyncEnumForms *r2 = (struct winspool_AsyncEnumForms *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumForms, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pForm = r2->in.pForm;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumForms(p, r2);
+ break;
+ }
+ case 26: { /* winspool_AsyncGetPrinterDriver */
+ struct winspool_AsyncGetPrinterDriver *r2 = (struct winspool_AsyncGetPrinterDriver *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriver, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pDriver = r2->in.pDriver;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pdwServerMaxVersion = talloc_zero(r2, uint32_t);
+ if (r2->out.pdwServerMaxVersion == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pdwServerMinVersion = talloc_zero(r2, uint32_t);
+ if (r2->out.pdwServerMinVersion == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetPrinterDriver(p, r2);
+ break;
+ }
+ case 27: { /* winspool_AsyncEnumPrinterData */
+ struct winspool_AsyncEnumPrinterData *r2 = (struct winspool_AsyncEnumPrinterData *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterData, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pValueName = talloc_zero_array(r2, uint16_t, r2->in.cbValueName / 2);
+ if (r2->out.pValueName == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcbValueName = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbValueName == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pType = talloc_zero(r2, uint32_t);
+ if (r2->out.pType == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pData = talloc_zero_array(r2, uint8_t, r2->in.cbData);
+ if (r2->out.pData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcbData = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPrinterData(p, r2);
+ break;
+ }
+ case 28: { /* winspool_AsyncEnumPrinterDataEx */
+ struct winspool_AsyncEnumPrinterDataEx *r2 = (struct winspool_AsyncEnumPrinterDataEx *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDataEx, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pEnumValues = talloc_zero_array(r2, uint8_t, r2->in.cbEnumValues);
+ if (r2->out.pEnumValues == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcbEnumValues = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbEnumValues == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pnEnumValues = talloc_zero(r2, uint32_t);
+ if (r2->out.pnEnumValues == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPrinterDataEx(p, r2);
+ break;
+ }
+ case 29: { /* winspool_AsyncEnumPrinterKey */
+ struct winspool_AsyncEnumPrinterKey *r2 = (struct winspool_AsyncEnumPrinterKey *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterKey, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pSubkey = talloc_zero_array(r2, uint16_t, r2->in.cbSubkey / 2);
+ if (r2->out.pSubkey == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcbSubkey = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbSubkey == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPrinterKey(p, r2);
+ break;
+ }
+ case 30: { /* winspool_AsyncDeletePrinterData */
+ struct winspool_AsyncDeletePrinterData *r2 = (struct winspool_AsyncDeletePrinterData *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterData, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrinterData(p, r2);
+ break;
+ }
+ case 31: { /* winspool_AsyncDeletePrinterDataEx */
+ struct winspool_AsyncDeletePrinterDataEx *r2 = (struct winspool_AsyncDeletePrinterDataEx *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDataEx, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrinterDataEx(p, r2);
+ break;
+ }
+ case 32: { /* winspool_AsyncDeletePrinterKey */
+ struct winspool_AsyncDeletePrinterKey *r2 = (struct winspool_AsyncDeletePrinterKey *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterKey, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrinterKey(p, r2);
+ break;
+ }
+ case 33: { /* winspool_AsyncXcvData */
+ struct winspool_AsyncXcvData *r2 = (struct winspool_AsyncXcvData *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncXcvData, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pdwStatus = r2->in.pdwStatus;
+ r2->out.pOutputData = talloc_zero_array(r2, uint8_t, r2->in.cbOutputData);
+ if (r2->out.pOutputData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcbOutputNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbOutputNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncXcvData(p, r2);
+ break;
+ }
+ case 34: { /* winspool_AsyncSendRecvBidiData */
+ struct winspool_AsyncSendRecvBidiData *r2 = (struct winspool_AsyncSendRecvBidiData *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSendRecvBidiData, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.ppRespData = talloc_zero(r2, struct RPC_BIDI_RESPONSE_CONTAINER *);
+ if (r2->out.ppRespData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncSendRecvBidiData(p, r2);
+ break;
+ }
+ case 35: { /* winspool_AsyncCreatePrinterIC */
+ struct winspool_AsyncCreatePrinterIC *r2 = (struct winspool_AsyncCreatePrinterIC *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCreatePrinterIC, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pHandle = talloc_zero(r2, struct policy_handle);
+ if (r2->out.pHandle == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncCreatePrinterIC(p, r2);
+ break;
+ }
+ case 36: { /* winspool_AsyncPlayGdiScriptOnPrinterIC */
+ struct winspool_AsyncPlayGdiScriptOnPrinterIC *r2 = (struct winspool_AsyncPlayGdiScriptOnPrinterIC *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncPlayGdiScriptOnPrinterIC, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pOut = talloc_zero_array(r2, uint8_t, r2->in.cOut);
+ if (r2->out.pOut == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncPlayGdiScriptOnPrinterIC(p, r2);
+ break;
+ }
+ case 37: { /* winspool_AsyncDeletePrinterIC */
+ struct winspool_AsyncDeletePrinterIC *r2 = (struct winspool_AsyncDeletePrinterIC *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterIC, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.phPrinterIC = r2->in.phPrinterIC;
+ r2->out.result = _winspool_AsyncDeletePrinterIC(p, r2);
+ break;
+ }
+ case 38: { /* winspool_AsyncEnumPrinters */
+ struct winspool_AsyncEnumPrinters *r2 = (struct winspool_AsyncEnumPrinters *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinters, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pPrinterEnum = r2->in.pPrinterEnum;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPrinters(p, r2);
+ break;
+ }
+ case 39: { /* winspool_AsyncAddPrinterDriver */
+ struct winspool_AsyncAddPrinterDriver *r2 = (struct winspool_AsyncAddPrinterDriver *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinterDriver, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncAddPrinterDriver(p, r2);
+ break;
+ }
+ case 40: { /* winspool_AsyncEnumPrinterDrivers */
+ struct winspool_AsyncEnumPrinterDrivers *r2 = (struct winspool_AsyncEnumPrinterDrivers *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDrivers, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pDrivers = r2->in.pDrivers;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPrinterDrivers(p, r2);
+ break;
+ }
+ case 41: { /* winspool_AsyncGetPrinterDriverDirectory */
+ struct winspool_AsyncGetPrinterDriverDirectory *r2 = (struct winspool_AsyncGetPrinterDriverDirectory *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverDirectory, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pDriverDirectory = r2->in.pDriverDirectory;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetPrinterDriverDirectory(p, r2);
+ break;
+ }
+ case 42: { /* winspool_AsyncDeletePrinterDriver */
+ struct winspool_AsyncDeletePrinterDriver *r2 = (struct winspool_AsyncDeletePrinterDriver *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriver, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrinterDriver(p, r2);
+ break;
+ }
+ case 43: { /* winspool_AsyncDeletePrinterDriverEx */
+ struct winspool_AsyncDeletePrinterDriverEx *r2 = (struct winspool_AsyncDeletePrinterDriverEx *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverEx, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrinterDriverEx(p, r2);
+ break;
+ }
+ case 44: { /* winspool_AsyncAddPrintProcessor */
+ struct winspool_AsyncAddPrintProcessor *r2 = (struct winspool_AsyncAddPrintProcessor *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrintProcessor, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncAddPrintProcessor(p, r2);
+ break;
+ }
+ case 45: { /* winspool_AsyncEnumPrintProcessors */
+ struct winspool_AsyncEnumPrintProcessors *r2 = (struct winspool_AsyncEnumPrintProcessors *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessors, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pPrintProcessorInfo = r2->in.pPrintProcessorInfo;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPrintProcessors(p, r2);
+ break;
+ }
+ case 46: { /* winspool_AsyncGetPrintProcessorDirectory */
+ struct winspool_AsyncGetPrintProcessorDirectory *r2 = (struct winspool_AsyncGetPrintProcessorDirectory *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrintProcessorDirectory, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pPrintProcessorDirectory = r2->in.pPrintProcessorDirectory;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetPrintProcessorDirectory(p, r2);
+ break;
+ }
+ case 47: { /* winspool_AsyncEnumPorts */
+ struct winspool_AsyncEnumPorts *r2 = (struct winspool_AsyncEnumPorts *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPorts, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pPort = r2->in.pPort;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPorts(p, r2);
+ break;
+ }
+ case 48: { /* winspool_AsyncEnumMonitors */
+ struct winspool_AsyncEnumMonitors *r2 = (struct winspool_AsyncEnumMonitors *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumMonitors, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pMonitor = r2->in.pMonitor;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumMonitors(p, r2);
+ break;
+ }
+ case 49: { /* winspool_AsyncAddPort */
+ struct winspool_AsyncAddPort *r2 = (struct winspool_AsyncAddPort *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPort, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncAddPort(p, r2);
+ break;
+ }
+ case 50: { /* winspool_AsyncSetPort */
+ struct winspool_AsyncSetPort *r2 = (struct winspool_AsyncSetPort *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPort, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncSetPort(p, r2);
+ break;
+ }
+ case 51: { /* winspool_AsyncAddMonitor */
+ struct winspool_AsyncAddMonitor *r2 = (struct winspool_AsyncAddMonitor *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddMonitor, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncAddMonitor(p, r2);
+ break;
+ }
+ case 52: { /* winspool_AsyncDeleteMonitor */
+ struct winspool_AsyncDeleteMonitor *r2 = (struct winspool_AsyncDeleteMonitor *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteMonitor, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeleteMonitor(p, r2);
+ break;
+ }
+ case 53: { /* winspool_AsyncDeletePrintProcessor */
+ struct winspool_AsyncDeletePrintProcessor *r2 = (struct winspool_AsyncDeletePrintProcessor *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrintProcessor, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrintProcessor(p, r2);
+ break;
+ }
+ case 54: { /* winspool_AsyncEnumPrintProcessorDatatypes */
+ struct winspool_AsyncEnumPrintProcessorDatatypes *r2 = (struct winspool_AsyncEnumPrintProcessorDatatypes *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessorDatatypes, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pDatatypes = r2->in.pDatatypes;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPrintProcessorDatatypes(p, r2);
+ break;
+ }
+ case 55: { /* winspool_AsyncAddPerMachineConnection */
+ struct winspool_AsyncAddPerMachineConnection *r2 = (struct winspool_AsyncAddPerMachineConnection *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPerMachineConnection, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncAddPerMachineConnection(p, r2);
+ break;
+ }
+ case 56: { /* winspool_AsyncDeletePerMachineConnection */
+ struct winspool_AsyncDeletePerMachineConnection *r2 = (struct winspool_AsyncDeletePerMachineConnection *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePerMachineConnection, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePerMachineConnection(p, r2);
+ break;
+ }
+ case 57: { /* winspool_AsyncEnumPerMachineConnections */
+ struct winspool_AsyncEnumPerMachineConnections *r2 = (struct winspool_AsyncEnumPerMachineConnections *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPerMachineConnections, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pPrinterEnum = r2->in.pPrinterEnum;
+ r2->out.pcbNeeded = talloc_zero(r2, uint32_t);
+ if (r2->out.pcbNeeded == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcReturned = talloc_zero(r2, uint32_t);
+ if (r2->out.pcReturned == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumPerMachineConnections(p, r2);
+ break;
+ }
+ case 58: { /* winspool_SyncRegisterForRemoteNotifications */
+ struct winspool_SyncRegisterForRemoteNotifications *r2 = (struct winspool_SyncRegisterForRemoteNotifications *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRegisterForRemoteNotifications, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.phRpcHandle = talloc_zero(r2, struct policy_handle);
+ if (r2->out.phRpcHandle == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_SyncRegisterForRemoteNotifications(p, r2);
+ break;
+ }
+ case 59: { /* winspool_SyncUnRegisterForRemoteNotifications */
+ struct winspool_SyncUnRegisterForRemoteNotifications *r2 = (struct winspool_SyncUnRegisterForRemoteNotifications *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_SyncUnRegisterForRemoteNotifications, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.phRpcHandle = r2->in.phRpcHandle;
+ r2->out.result = _winspool_SyncUnRegisterForRemoteNotifications(p, r2);
+ break;
+ }
+ case 60: { /* winspool_SyncRefreshRemoteNotifications */
+ struct winspool_SyncRefreshRemoteNotifications *r2 = (struct winspool_SyncRefreshRemoteNotifications *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRefreshRemoteNotifications, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.ppNotifyData = talloc_zero(r2, struct winspool_PrintPropertiesCollection *);
+ if (r2->out.ppNotifyData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_SyncRefreshRemoteNotifications(p, r2);
+ break;
+ }
+ case 61: { /* winspool_AsyncGetRemoteNotifications */
+ struct winspool_AsyncGetRemoteNotifications *r2 = (struct winspool_AsyncGetRemoteNotifications *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetRemoteNotifications, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.ppNotifyData = talloc_zero(r2, struct winspool_PrintPropertiesCollection *);
+ if (r2->out.ppNotifyData == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetRemoteNotifications(p, r2);
+ break;
+ }
+ case 62: { /* winspool_AsyncInstallPrinterDriverFromPackage */
+ struct winspool_AsyncInstallPrinterDriverFromPackage *r2 = (struct winspool_AsyncInstallPrinterDriverFromPackage *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncInstallPrinterDriverFromPackage, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncInstallPrinterDriverFromPackage(p, r2);
+ break;
+ }
+ case 63: { /* winspool_AsyncUploadPrinterDriverPackage */
+ struct winspool_AsyncUploadPrinterDriverPackage *r2 = (struct winspool_AsyncUploadPrinterDriverPackage *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncUploadPrinterDriverPackage, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pszDestInfPath = r2->in.pszDestInfPath;
+ r2->out.pcchDestInfPath = r2->in.pcchDestInfPath;
+ r2->out.result = _winspool_AsyncUploadPrinterDriverPackage(p, r2);
+ break;
+ }
+ case 64: { /* winspool_AsyncGetCorePrinterDrivers */
+ struct winspool_AsyncGetCorePrinterDrivers *r2 = (struct winspool_AsyncGetCorePrinterDrivers *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetCorePrinterDrivers, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pCorePrinterDrivers = talloc_zero_array(r2, struct spoolss_CorePrinterDriver, r2->in.cCorePrinterDrivers);
+ if (r2->out.pCorePrinterDrivers == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetCorePrinterDrivers(p, r2);
+ break;
+ }
+ case 65: { /* winspool_AsyncCorePrinterDriverInstalled */
+ struct winspool_AsyncCorePrinterDriverInstalled *r2 = (struct winspool_AsyncCorePrinterDriverInstalled *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCorePrinterDriverInstalled, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pbDriverInstalled = talloc_zero(r2, int32_t);
+ if (r2->out.pbDriverInstalled == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncCorePrinterDriverInstalled(p, r2);
+ break;
+ }
+ case 66: { /* winspool_AsyncGetPrinterDriverPackagePath */
+ struct winspool_AsyncGetPrinterDriverPackagePath *r2 = (struct winspool_AsyncGetPrinterDriverPackagePath *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverPackagePath, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pszDriverPackageCab = r2->in.pszDriverPackageCab;
+ r2->out.pcchRequiredSize = talloc_zero(r2, uint32_t);
+ if (r2->out.pcchRequiredSize == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetPrinterDriverPackagePath(p, r2);
+ break;
+ }
+ case 67: { /* winspool_AsyncDeletePrinterDriverPackage */
+ struct winspool_AsyncDeletePrinterDriverPackage *r2 = (struct winspool_AsyncDeletePrinterDriverPackage *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverPackage, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeletePrinterDriverPackage(p, r2);
+ break;
+ }
+ case 68: { /* winspool_AsyncReadPrinter */
+ struct winspool_AsyncReadPrinter *r2 = (struct winspool_AsyncReadPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncReadPrinter, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pBuf = talloc_zero_array(r2, uint8_t, r2->in.cbBuf);
+ if (r2->out.pBuf == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.pcNoBytesRead = talloc_zero(r2, uint32_t);
+ if (r2->out.pcNoBytesRead == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncReadPrinter(p, r2);
+ break;
+ }
+ case 69: { /* winspool_AsyncResetPrinter */
+ struct winspool_AsyncResetPrinter *r2 = (struct winspool_AsyncResetPrinter *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncResetPrinter, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncResetPrinter(p, r2);
+ break;
+ }
+ case 70: { /* winspool_AsyncGetJobNamedPropertyValue */
+ struct winspool_AsyncGetJobNamedPropertyValue *r2 = (struct winspool_AsyncGetJobNamedPropertyValue *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJobNamedPropertyValue, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pValue = talloc_zero(r2, struct spoolss_PrintPropertyValue);
+ if (r2->out.pValue == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncGetJobNamedPropertyValue(p, r2);
+ break;
+ }
+ case 71: { /* winspool_AsyncSetJobNamedProperty */
+ struct winspool_AsyncSetJobNamedProperty *r2 = (struct winspool_AsyncSetJobNamedProperty *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJobNamedProperty, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncSetJobNamedProperty(p, r2);
+ break;
+ }
+ case 72: { /* winspool_AsyncDeleteJobNamedProperty */
+ struct winspool_AsyncDeleteJobNamedProperty *r2 = (struct winspool_AsyncDeleteJobNamedProperty *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteJobNamedProperty, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncDeleteJobNamedProperty(p, r2);
+ break;
+ }
+ case 73: { /* winspool_AsyncEnumJobNamedProperties */
+ struct winspool_AsyncEnumJobNamedProperties *r2 = (struct winspool_AsyncEnumJobNamedProperties *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobNamedProperties, NDR_IN, r2);
+ }
+ NDR_ZERO_STRUCT(r2->out);
+ r2->out.pcProperties = talloc_zero(r2, uint32_t);
+ if (r2->out.pcProperties == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.ppProperties = talloc_zero(r2, struct spoolss_PrintNamedProperty *);
+ if (r2->out.ppProperties == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ p->fault_state = DCERPC_FAULT_CANT_PERFORM;
+ goto fail;
+ }
+
+ r2->out.result = _winspool_AsyncEnumJobNamedProperties(p, r2);
+ break;
+ }
+ case 74: { /* winspool_AsyncLogJobInfoForBranchOffice */
+ struct winspool_AsyncLogJobInfoForBranchOffice *r2 = (struct winspool_AsyncLogJobInfoForBranchOffice *)r;
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncLogJobInfoForBranchOffice, NDR_IN, r2);
+ }
+ r2->out.result = _winspool_AsyncLogJobInfoForBranchOffice(p, r2);
+ break;
+ }
+ default:
+ dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR;
+ break;
+ }
+
+fail:
+ /* Unimpersonate */
+ if (impersonated) {
+ unbecome_authenticated_pipe_user();
+ }
+
+ p->dce_call = NULL;
+ p->mem_ctx = NULL;
+ /* Check pipes struct fault state */
+ if (p->fault_state != 0) {
+ dce_call->fault_code = p->fault_state;
+ }
+ if (dce_call->fault_code != 0) {
+ status = NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ return status;
+}
+
+NTSTATUS iremotewinspool__op_dispatch(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r)
+{
+ return iremotewinspool__op_dispatch_internal(dce_call, mem_ctx, r, false);
+}
+
+NTSTATUS iremotewinspool__op_reply(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r)
+{
+ uint16_t opnum = dce_call->pkt.u.request.opnum;
+
+ if (forward_opnum_to_spoolss(opnum)) {
+ return spoolss__op_reply(dce_call, mem_ctx, r);
+ }
+
+ switch (opnum) {
+ case 0: { /* winspool_AsyncOpenPrinter */
+ struct winspool_AsyncOpenPrinter *r2 = (struct winspool_AsyncOpenPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncOpenPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncOpenPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncOpenPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 1: { /* winspool_AsyncAddPrinter */
+ struct winspool_AsyncAddPrinter *r2 = (struct winspool_AsyncAddPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 2: { /* winspool_AsyncSetJob */
+ struct winspool_AsyncSetJob *r2 = (struct winspool_AsyncSetJob *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSetJob replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJob, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 3: { /* winspool_AsyncGetJob */
+ struct winspool_AsyncGetJob *r2 = (struct winspool_AsyncGetJob *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetJob replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJob, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 4: { /* winspool_AsyncEnumJobs */
+ struct winspool_AsyncEnumJobs *r2 = (struct winspool_AsyncEnumJobs *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumJobs replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobs, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumJobs\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 5: { /* winspool_AsyncAddJob */
+ struct winspool_AsyncAddJob *r2 = (struct winspool_AsyncAddJob *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddJob replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddJob, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 6: { /* winspool_AsyncScheduleJob */
+ struct winspool_AsyncScheduleJob *r2 = (struct winspool_AsyncScheduleJob *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncScheduleJob replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncScheduleJob, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncScheduleJob\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 7: { /* winspool_AsyncDeletePrinter */
+ struct winspool_AsyncDeletePrinter *r2 = (struct winspool_AsyncDeletePrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 8: { /* winspool_AsyncSetPrinter */
+ struct winspool_AsyncSetPrinter *r2 = (struct winspool_AsyncSetPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSetPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 9: { /* winspool_AsyncGetPrinter */
+ struct winspool_AsyncGetPrinter *r2 = (struct winspool_AsyncGetPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 10: { /* winspool_AsyncStartDocPrinter */
+ struct winspool_AsyncStartDocPrinter *r2 = (struct winspool_AsyncStartDocPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncStartDocPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartDocPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncStartDocPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 11: { /* winspool_AsyncStartPagePrinter */
+ struct winspool_AsyncStartPagePrinter *r2 = (struct winspool_AsyncStartPagePrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncStartPagePrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncStartPagePrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncStartPagePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 12: { /* winspool_AsyncWritePrinter */
+ struct winspool_AsyncWritePrinter *r2 = (struct winspool_AsyncWritePrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncWritePrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncWritePrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncWritePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 13: { /* winspool_AsyncEndPagePrinter */
+ struct winspool_AsyncEndPagePrinter *r2 = (struct winspool_AsyncEndPagePrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEndPagePrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndPagePrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEndPagePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 14: { /* winspool_AsyncEndDocPrinter */
+ struct winspool_AsyncEndDocPrinter *r2 = (struct winspool_AsyncEndDocPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEndDocPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEndDocPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEndDocPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 15: { /* winspool_AsyncAbortPrinter */
+ struct winspool_AsyncAbortPrinter *r2 = (struct winspool_AsyncAbortPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAbortPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAbortPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAbortPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 16: { /* winspool_AsyncGetPrinterData */
+ struct winspool_AsyncGetPrinterData *r2 = (struct winspool_AsyncGetPrinterData *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetPrinterData replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterData, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 17: { /* winspool_AsyncGetPrinterDataEx */
+ struct winspool_AsyncGetPrinterDataEx *r2 = (struct winspool_AsyncGetPrinterDataEx *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetPrinterDataEx replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 18: { /* winspool_AsyncSetPrinterData */
+ struct winspool_AsyncSetPrinterData *r2 = (struct winspool_AsyncSetPrinterData *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSetPrinterData replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterData, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 19: { /* winspool_AsyncSetPrinterDataEx */
+ struct winspool_AsyncSetPrinterDataEx *r2 = (struct winspool_AsyncSetPrinterDataEx *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSetPrinterDataEx replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 20: { /* winspool_AsyncClosePrinter */
+ struct winspool_AsyncClosePrinter *r2 = (struct winspool_AsyncClosePrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncClosePrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncClosePrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncClosePrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 21: { /* winspool_AsyncAddForm */
+ struct winspool_AsyncAddForm *r2 = (struct winspool_AsyncAddForm *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddForm replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddForm, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 22: { /* winspool_AsyncDeleteForm */
+ struct winspool_AsyncDeleteForm *r2 = (struct winspool_AsyncDeleteForm *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeleteForm replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteForm, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeleteForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 23: { /* winspool_AsyncGetForm */
+ struct winspool_AsyncGetForm *r2 = (struct winspool_AsyncGetForm *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetForm replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetForm, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 24: { /* winspool_AsyncSetForm */
+ struct winspool_AsyncSetForm *r2 = (struct winspool_AsyncSetForm *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSetForm replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetForm, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetForm\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 25: { /* winspool_AsyncEnumForms */
+ struct winspool_AsyncEnumForms *r2 = (struct winspool_AsyncEnumForms *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumForms replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumForms, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumForms\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 26: { /* winspool_AsyncGetPrinterDriver */
+ struct winspool_AsyncGetPrinterDriver *r2 = (struct winspool_AsyncGetPrinterDriver *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetPrinterDriver replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriver, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDriver\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 27: { /* winspool_AsyncEnumPrinterData */
+ struct winspool_AsyncEnumPrinterData *r2 = (struct winspool_AsyncEnumPrinterData *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPrinterData replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterData, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 28: { /* winspool_AsyncEnumPrinterDataEx */
+ struct winspool_AsyncEnumPrinterDataEx *r2 = (struct winspool_AsyncEnumPrinterDataEx *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPrinterDataEx replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 29: { /* winspool_AsyncEnumPrinterKey */
+ struct winspool_AsyncEnumPrinterKey *r2 = (struct winspool_AsyncEnumPrinterKey *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPrinterKey replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterKey, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterKey\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 30: { /* winspool_AsyncDeletePrinterData */
+ struct winspool_AsyncDeletePrinterData *r2 = (struct winspool_AsyncDeletePrinterData *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinterData replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterData, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 31: { /* winspool_AsyncDeletePrinterDataEx */
+ struct winspool_AsyncDeletePrinterDataEx *r2 = (struct winspool_AsyncDeletePrinterDataEx *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinterDataEx replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDataEx, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDataEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 32: { /* winspool_AsyncDeletePrinterKey */
+ struct winspool_AsyncDeletePrinterKey *r2 = (struct winspool_AsyncDeletePrinterKey *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinterKey replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterKey, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterKey\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 33: { /* winspool_AsyncXcvData */
+ struct winspool_AsyncXcvData *r2 = (struct winspool_AsyncXcvData *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncXcvData replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncXcvData, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncXcvData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 34: { /* winspool_AsyncSendRecvBidiData */
+ struct winspool_AsyncSendRecvBidiData *r2 = (struct winspool_AsyncSendRecvBidiData *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSendRecvBidiData replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSendRecvBidiData, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSendRecvBidiData\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 35: { /* winspool_AsyncCreatePrinterIC */
+ struct winspool_AsyncCreatePrinterIC *r2 = (struct winspool_AsyncCreatePrinterIC *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncCreatePrinterIC replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCreatePrinterIC, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncCreatePrinterIC\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 36: { /* winspool_AsyncPlayGdiScriptOnPrinterIC */
+ struct winspool_AsyncPlayGdiScriptOnPrinterIC *r2 = (struct winspool_AsyncPlayGdiScriptOnPrinterIC *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncPlayGdiScriptOnPrinterIC replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncPlayGdiScriptOnPrinterIC, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncPlayGdiScriptOnPrinterIC\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 37: { /* winspool_AsyncDeletePrinterIC */
+ struct winspool_AsyncDeletePrinterIC *r2 = (struct winspool_AsyncDeletePrinterIC *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinterIC replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterIC, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterIC\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 38: { /* winspool_AsyncEnumPrinters */
+ struct winspool_AsyncEnumPrinters *r2 = (struct winspool_AsyncEnumPrinters *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPrinters replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinters, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinters\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 39: { /* winspool_AsyncAddPrinterDriver */
+ struct winspool_AsyncAddPrinterDriver *r2 = (struct winspool_AsyncAddPrinterDriver *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddPrinterDriver replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrinterDriver, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPrinterDriver\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 40: { /* winspool_AsyncEnumPrinterDrivers */
+ struct winspool_AsyncEnumPrinterDrivers *r2 = (struct winspool_AsyncEnumPrinterDrivers *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPrinterDrivers replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrinterDrivers, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrinterDrivers\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 41: { /* winspool_AsyncGetPrinterDriverDirectory */
+ struct winspool_AsyncGetPrinterDriverDirectory *r2 = (struct winspool_AsyncGetPrinterDriverDirectory *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetPrinterDriverDirectory replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverDirectory, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDriverDirectory\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 42: { /* winspool_AsyncDeletePrinterDriver */
+ struct winspool_AsyncDeletePrinterDriver *r2 = (struct winspool_AsyncDeletePrinterDriver *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinterDriver replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriver, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDriver\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 43: { /* winspool_AsyncDeletePrinterDriverEx */
+ struct winspool_AsyncDeletePrinterDriverEx *r2 = (struct winspool_AsyncDeletePrinterDriverEx *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinterDriverEx replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverEx, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDriverEx\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 44: { /* winspool_AsyncAddPrintProcessor */
+ struct winspool_AsyncAddPrintProcessor *r2 = (struct winspool_AsyncAddPrintProcessor *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddPrintProcessor replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPrintProcessor, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPrintProcessor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 45: { /* winspool_AsyncEnumPrintProcessors */
+ struct winspool_AsyncEnumPrintProcessors *r2 = (struct winspool_AsyncEnumPrintProcessors *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPrintProcessors replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessors, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrintProcessors\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 46: { /* winspool_AsyncGetPrintProcessorDirectory */
+ struct winspool_AsyncGetPrintProcessorDirectory *r2 = (struct winspool_AsyncGetPrintProcessorDirectory *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetPrintProcessorDirectory replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrintProcessorDirectory, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrintProcessorDirectory\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 47: { /* winspool_AsyncEnumPorts */
+ struct winspool_AsyncEnumPorts *r2 = (struct winspool_AsyncEnumPorts *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPorts replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPorts, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPorts\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 48: { /* winspool_AsyncEnumMonitors */
+ struct winspool_AsyncEnumMonitors *r2 = (struct winspool_AsyncEnumMonitors *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumMonitors replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumMonitors, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumMonitors\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 49: { /* winspool_AsyncAddPort */
+ struct winspool_AsyncAddPort *r2 = (struct winspool_AsyncAddPort *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddPort replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPort, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPort\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 50: { /* winspool_AsyncSetPort */
+ struct winspool_AsyncSetPort *r2 = (struct winspool_AsyncSetPort *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSetPort replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetPort, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetPort\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 51: { /* winspool_AsyncAddMonitor */
+ struct winspool_AsyncAddMonitor *r2 = (struct winspool_AsyncAddMonitor *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddMonitor replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddMonitor, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddMonitor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 52: { /* winspool_AsyncDeleteMonitor */
+ struct winspool_AsyncDeleteMonitor *r2 = (struct winspool_AsyncDeleteMonitor *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeleteMonitor replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteMonitor, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeleteMonitor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 53: { /* winspool_AsyncDeletePrintProcessor */
+ struct winspool_AsyncDeletePrintProcessor *r2 = (struct winspool_AsyncDeletePrintProcessor *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrintProcessor replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrintProcessor, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrintProcessor\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 54: { /* winspool_AsyncEnumPrintProcessorDatatypes */
+ struct winspool_AsyncEnumPrintProcessorDatatypes *r2 = (struct winspool_AsyncEnumPrintProcessorDatatypes *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPrintProcessorDatatypes replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPrintProcessorDatatypes, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPrintProcessorDatatypes\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 55: { /* winspool_AsyncAddPerMachineConnection */
+ struct winspool_AsyncAddPerMachineConnection *r2 = (struct winspool_AsyncAddPerMachineConnection *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncAddPerMachineConnection replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncAddPerMachineConnection, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncAddPerMachineConnection\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 56: { /* winspool_AsyncDeletePerMachineConnection */
+ struct winspool_AsyncDeletePerMachineConnection *r2 = (struct winspool_AsyncDeletePerMachineConnection *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePerMachineConnection replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePerMachineConnection, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePerMachineConnection\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 57: { /* winspool_AsyncEnumPerMachineConnections */
+ struct winspool_AsyncEnumPerMachineConnections *r2 = (struct winspool_AsyncEnumPerMachineConnections *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumPerMachineConnections replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumPerMachineConnections, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumPerMachineConnections\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 58: { /* winspool_SyncRegisterForRemoteNotifications */
+ struct winspool_SyncRegisterForRemoteNotifications *r2 = (struct winspool_SyncRegisterForRemoteNotifications *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_SyncRegisterForRemoteNotifications replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRegisterForRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_SyncRegisterForRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 59: { /* winspool_SyncUnRegisterForRemoteNotifications */
+ struct winspool_SyncUnRegisterForRemoteNotifications *r2 = (struct winspool_SyncUnRegisterForRemoteNotifications *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_SyncUnRegisterForRemoteNotifications replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_SyncUnRegisterForRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_SyncUnRegisterForRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 60: { /* winspool_SyncRefreshRemoteNotifications */
+ struct winspool_SyncRefreshRemoteNotifications *r2 = (struct winspool_SyncRefreshRemoteNotifications *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_SyncRefreshRemoteNotifications replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_SyncRefreshRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_SyncRefreshRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 61: { /* winspool_AsyncGetRemoteNotifications */
+ struct winspool_AsyncGetRemoteNotifications *r2 = (struct winspool_AsyncGetRemoteNotifications *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetRemoteNotifications replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetRemoteNotifications, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetRemoteNotifications\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 62: { /* winspool_AsyncInstallPrinterDriverFromPackage */
+ struct winspool_AsyncInstallPrinterDriverFromPackage *r2 = (struct winspool_AsyncInstallPrinterDriverFromPackage *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncInstallPrinterDriverFromPackage replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncInstallPrinterDriverFromPackage, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncInstallPrinterDriverFromPackage\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 63: { /* winspool_AsyncUploadPrinterDriverPackage */
+ struct winspool_AsyncUploadPrinterDriverPackage *r2 = (struct winspool_AsyncUploadPrinterDriverPackage *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncUploadPrinterDriverPackage replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncUploadPrinterDriverPackage, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncUploadPrinterDriverPackage\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 64: { /* winspool_AsyncGetCorePrinterDrivers */
+ struct winspool_AsyncGetCorePrinterDrivers *r2 = (struct winspool_AsyncGetCorePrinterDrivers *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetCorePrinterDrivers replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetCorePrinterDrivers, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetCorePrinterDrivers\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 65: { /* winspool_AsyncCorePrinterDriverInstalled */
+ struct winspool_AsyncCorePrinterDriverInstalled *r2 = (struct winspool_AsyncCorePrinterDriverInstalled *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncCorePrinterDriverInstalled replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncCorePrinterDriverInstalled, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncCorePrinterDriverInstalled\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 66: { /* winspool_AsyncGetPrinterDriverPackagePath */
+ struct winspool_AsyncGetPrinterDriverPackagePath *r2 = (struct winspool_AsyncGetPrinterDriverPackagePath *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetPrinterDriverPackagePath replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetPrinterDriverPackagePath, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetPrinterDriverPackagePath\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 67: { /* winspool_AsyncDeletePrinterDriverPackage */
+ struct winspool_AsyncDeletePrinterDriverPackage *r2 = (struct winspool_AsyncDeletePrinterDriverPackage *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeletePrinterDriverPackage replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeletePrinterDriverPackage, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeletePrinterDriverPackage\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 68: { /* winspool_AsyncReadPrinter */
+ struct winspool_AsyncReadPrinter *r2 = (struct winspool_AsyncReadPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncReadPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncReadPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncReadPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 69: { /* winspool_AsyncResetPrinter */
+ struct winspool_AsyncResetPrinter *r2 = (struct winspool_AsyncResetPrinter *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncResetPrinter replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncResetPrinter, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncResetPrinter\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 70: { /* winspool_AsyncGetJobNamedPropertyValue */
+ struct winspool_AsyncGetJobNamedPropertyValue *r2 = (struct winspool_AsyncGetJobNamedPropertyValue *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncGetJobNamedPropertyValue replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncGetJobNamedPropertyValue, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncGetJobNamedPropertyValue\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 71: { /* winspool_AsyncSetJobNamedProperty */
+ struct winspool_AsyncSetJobNamedProperty *r2 = (struct winspool_AsyncSetJobNamedProperty *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncSetJobNamedProperty replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncSetJobNamedProperty, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncSetJobNamedProperty\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 72: { /* winspool_AsyncDeleteJobNamedProperty */
+ struct winspool_AsyncDeleteJobNamedProperty *r2 = (struct winspool_AsyncDeleteJobNamedProperty *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncDeleteJobNamedProperty replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncDeleteJobNamedProperty, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncDeleteJobNamedProperty\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 73: { /* winspool_AsyncEnumJobNamedProperties */
+ struct winspool_AsyncEnumJobNamedProperties *r2 = (struct winspool_AsyncEnumJobNamedProperties *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncEnumJobNamedProperties replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncEnumJobNamedProperties, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncEnumJobNamedProperties\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ case 74: { /* winspool_AsyncLogJobInfoForBranchOffice */
+ struct winspool_AsyncLogJobInfoForBranchOffice *r2 = (struct winspool_AsyncLogJobInfoForBranchOffice *)r;
+ if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) {
+ DEBUG(5,("function winspool_AsyncLogJobInfoForBranchOffice replied async\n"));
+ }
+ if (DEBUGLEVEL >= 10 && dce_call->fault_code == 0) {
+ NDR_PRINT_FUNCTION_DEBUG(winspool_AsyncLogJobInfoForBranchOffice, NDR_OUT | NDR_SET_VALUES, r2);
+ }
+ if (dce_call->fault_code != 0) {
+ DBG_WARNING("dcerpc_fault %s in winspool_AsyncLogJobInfoForBranchOffice\n", dcerpc_errstr(mem_ctx, dce_call->fault_code));
+ }
+ break;
+ }
+ default:
+ dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR;
+ break;
+ }
+
+ if (dce_call->fault_code != 0) {
+ return NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS iremotewinspool__op_ndr_push(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_push *push, const void *r)
+{
+ enum ndr_err_code ndr_err;
+ uint16_t opnum = dce_call->pkt.u.request.opnum;
+
+ if (forward_opnum_to_spoolss(opnum)) {
+ return spoolss__op_ndr_push(dce_call, mem_ctx, push, r);
+ }
+
+ ndr_err = ndr_table_iremotewinspool.calls[opnum].ndr_push(push, NDR_OUT, r);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ dce_call->fault_code = DCERPC_FAULT_NDR;
+ return NT_STATUS_NET_WRITE_FAULT;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS iremotewinspool__op_local(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r)
+{
+ return iremotewinspool__op_dispatch_internal(dce_call, mem_ctx, r, true);
+}
+
+static const struct dcesrv_interface dcesrv_iremotewinspool_interface = {
+ .name = "iremotewinspool",
+ .syntax_id = {{0x76f03f96,0xcdfd,0x44fc,{0xa2,0x2c},{0x64,0x95,0x0a,0x00,0x12,0x09}},1.0},
+ .bind = iremotewinspool__op_bind,
+ .unbind = iremotewinspool__op_unbind,
+ .ndr_pull = iremotewinspool__op_ndr_pull,
+ .dispatch = iremotewinspool__op_dispatch,
+ .reply = iremotewinspool__op_reply,
+ .ndr_push = iremotewinspool__op_ndr_push,
+ .local = iremotewinspool__op_local,
+#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_FLAGS
+ .flags = DCESRV_INTERFACE_IREMOTEWINSPOOL_FLAGS
+#else
+ .flags = 0
+#endif
+};
+
+static NTSTATUS iremotewinspool__op_init_server(struct dcesrv_context *dce_ctx, const struct dcesrv_endpoint_server *ep_server)
+{
+ int i;
+ NTSTATUS ret;
+
+#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_NCACN_NP_SECONDARY_ENDPOINT
+ const char *ncacn_np_secondary_endpoint = DCESRV_INTERFACE_IREMOTEWINSPOOL_NCACN_NP_SECONDARY_ENDPOINT;
+#else
+ const char *ncacn_np_secondary_endpoint = NULL;
+#endif
+
+ for (i=0;i<ndr_table_iremotewinspool.endpoints->count;i++) {
+ const char *name = ndr_table_iremotewinspool.endpoints->names[i];
+
+ ret = dcesrv_interface_register(dce_ctx, name, ncacn_np_secondary_endpoint, &dcesrv_iremotewinspool_interface, NULL);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DBG_ERR("Failed to register endpoint '%s'\n",name);
+ return ret;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS iremotewinspool__op_shutdown_server(struct dcesrv_context *dce_ctx, const struct dcesrv_endpoint_server *ep_server)
+{
+ return NT_STATUS_OK;
+}
+
+static bool iremotewinspool__op_interface_by_uuid(struct dcesrv_interface *iface, const struct GUID *uuid, uint32_t if_version)
+{
+ if (dcesrv_iremotewinspool_interface.syntax_id.if_version == if_version && GUID_equal(&dcesrv_iremotewinspool_interface.syntax_id.uuid, uuid)) {
+ memcpy(iface,&dcesrv_iremotewinspool_interface, sizeof(*iface));
+ return true;
+ }
+
+ return false;
+}
+
+static bool iremotewinspool__op_interface_by_name(struct dcesrv_interface *iface, const char *name)
+{
+ if (strcmp(dcesrv_iremotewinspool_interface.name, name)==0) {
+ memcpy(iface, &dcesrv_iremotewinspool_interface, sizeof(*iface));
+ return true;
+ }
+
+ return false;
+}
+
+static const struct dcesrv_endpoint_server iremotewinspool_ep_server = {
+ /* fill in our name */
+ .name = "iremotewinspool",
+
+ /* Initialization flag */
+ .initialized = false,
+
+ /* fill in all the operations */
+#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_INIT_SERVER
+ .init_server = DCESRV_INTERFACE_IREMOTEWINSPOOL_INIT_SERVER,
+#else
+ .init_server = iremotewinspool__op_init_server,
+#endif
+#ifdef DCESRV_INTERFACE_IREMOTEWINSPOOL_SHUTDOWN_SERVER
+ .shutdown_server = DCESRV_INTERFACE_IREMOTEWINSPOOL_SHUTDOWN_SERVER,
+#else
+ .shutdown_server = iremotewinspool__op_shutdown_server,
+#endif
+ .interface_by_uuid = iremotewinspool__op_interface_by_uuid,
+ .interface_by_name = iremotewinspool__op_interface_by_name
+};
+
+const struct dcesrv_endpoint_server *iremotewinspool_get_ep_server(void)
+{
+ return &iremotewinspool_ep_server;
+}
diff --git a/source3/rpc_server/spoolss/srv_iremotewinspool_nt.c b/source3/rpc_server/spoolss/srv_iremotewinspool_nt.c
new file mode 100644
index 0000000..c437192
--- /dev/null
+++ b/source3/rpc_server/spoolss/srv_iremotewinspool_nt.c
@@ -0,0 +1,924 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ endpoint server for the iremotewinspool pipe
+
+ Copyright (C) YOUR NAME HERE YEAR
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/gen_ndr/ndr_winspool.h"
+#include "librpc/gen_ndr/ndr_winspool_scompat.h"
+
+/****************************************************************
+ _winspool_AsyncOpenPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncOpenPrinter(struct pipes_struct *p,
+ struct winspool_AsyncOpenPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncAddPrinter(struct pipes_struct *p,
+ struct winspool_AsyncAddPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSetJob
+****************************************************************/
+
+WERROR _winspool_AsyncSetJob(struct pipes_struct *p,
+ struct winspool_AsyncSetJob *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetJob
+****************************************************************/
+
+WERROR _winspool_AsyncGetJob(struct pipes_struct *p,
+ struct winspool_AsyncGetJob *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumJobs
+****************************************************************/
+
+WERROR _winspool_AsyncEnumJobs(struct pipes_struct *p,
+ struct winspool_AsyncEnumJobs *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddJob
+****************************************************************/
+
+WERROR _winspool_AsyncAddJob(struct pipes_struct *p,
+ struct winspool_AsyncAddJob *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncScheduleJob
+****************************************************************/
+
+WERROR _winspool_AsyncScheduleJob(struct pipes_struct *p,
+ struct winspool_AsyncScheduleJob *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinter
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrinter(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSetPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncSetPrinter(struct pipes_struct *p,
+ struct winspool_AsyncSetPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncGetPrinter(struct pipes_struct *p,
+ struct winspool_AsyncGetPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncStartDocPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncStartDocPrinter(struct pipes_struct *p,
+ struct winspool_AsyncStartDocPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncStartPagePrinter
+****************************************************************/
+
+WERROR _winspool_AsyncStartPagePrinter(struct pipes_struct *p,
+ struct winspool_AsyncStartPagePrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncWritePrinter
+****************************************************************/
+
+WERROR _winspool_AsyncWritePrinter(struct pipes_struct *p,
+ struct winspool_AsyncWritePrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEndPagePrinter
+****************************************************************/
+
+WERROR _winspool_AsyncEndPagePrinter(struct pipes_struct *p,
+ struct winspool_AsyncEndPagePrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEndDocPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncEndDocPrinter(struct pipes_struct *p,
+ struct winspool_AsyncEndDocPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAbortPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncAbortPrinter(struct pipes_struct *p,
+ struct winspool_AsyncAbortPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetPrinterData
+****************************************************************/
+
+WERROR _winspool_AsyncGetPrinterData(struct pipes_struct *p,
+ struct winspool_AsyncGetPrinterData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetPrinterDataEx
+****************************************************************/
+
+WERROR _winspool_AsyncGetPrinterDataEx(struct pipes_struct *p,
+ struct winspool_AsyncGetPrinterDataEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSetPrinterData
+****************************************************************/
+
+WERROR _winspool_AsyncSetPrinterData(struct pipes_struct *p,
+ struct winspool_AsyncSetPrinterData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSetPrinterDataEx
+****************************************************************/
+
+WERROR _winspool_AsyncSetPrinterDataEx(struct pipes_struct *p,
+ struct winspool_AsyncSetPrinterDataEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncClosePrinter
+****************************************************************/
+
+WERROR _winspool_AsyncClosePrinter(struct pipes_struct *p,
+ struct winspool_AsyncClosePrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddForm
+****************************************************************/
+
+WERROR _winspool_AsyncAddForm(struct pipes_struct *p,
+ struct winspool_AsyncAddForm *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeleteForm
+****************************************************************/
+
+WERROR _winspool_AsyncDeleteForm(struct pipes_struct *p,
+ struct winspool_AsyncDeleteForm *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetForm
+****************************************************************/
+
+WERROR _winspool_AsyncGetForm(struct pipes_struct *p,
+ struct winspool_AsyncGetForm *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSetForm
+****************************************************************/
+
+WERROR _winspool_AsyncSetForm(struct pipes_struct *p,
+ struct winspool_AsyncSetForm *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumForms
+****************************************************************/
+
+WERROR _winspool_AsyncEnumForms(struct pipes_struct *p,
+ struct winspool_AsyncEnumForms *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetPrinterDriver
+****************************************************************/
+
+WERROR _winspool_AsyncGetPrinterDriver(struct pipes_struct *p,
+ struct winspool_AsyncGetPrinterDriver *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPrinterData
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPrinterData(struct pipes_struct *p,
+ struct winspool_AsyncEnumPrinterData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPrinterDataEx
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPrinterDataEx(struct pipes_struct *p,
+ struct winspool_AsyncEnumPrinterDataEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPrinterKey
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPrinterKey(struct pipes_struct *p,
+ struct winspool_AsyncEnumPrinterKey *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinterData
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrinterData(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinterData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinterDataEx
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrinterDataEx(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinterDataEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinterKey
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrinterKey(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinterKey *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncXcvData
+****************************************************************/
+
+WERROR _winspool_AsyncXcvData(struct pipes_struct *p,
+ struct winspool_AsyncXcvData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSendRecvBidiData
+****************************************************************/
+
+WERROR _winspool_AsyncSendRecvBidiData(struct pipes_struct *p,
+ struct winspool_AsyncSendRecvBidiData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncCreatePrinterIC
+****************************************************************/
+
+WERROR _winspool_AsyncCreatePrinterIC(struct pipes_struct *p,
+ struct winspool_AsyncCreatePrinterIC *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncPlayGdiScriptOnPrinterIC
+****************************************************************/
+
+WERROR _winspool_AsyncPlayGdiScriptOnPrinterIC(struct pipes_struct *p,
+ struct winspool_AsyncPlayGdiScriptOnPrinterIC *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinterIC
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrinterIC(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinterIC *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPrinters
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPrinters(struct pipes_struct *p,
+ struct winspool_AsyncEnumPrinters *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddPrinterDriver
+****************************************************************/
+
+WERROR _winspool_AsyncAddPrinterDriver(struct pipes_struct *p,
+ struct winspool_AsyncAddPrinterDriver *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPrinterDrivers
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPrinterDrivers(struct pipes_struct *p,
+ struct winspool_AsyncEnumPrinterDrivers *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetPrinterDriverDirectory
+****************************************************************/
+
+WERROR _winspool_AsyncGetPrinterDriverDirectory(struct pipes_struct *p,
+ struct winspool_AsyncGetPrinterDriverDirectory *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinterDriver
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrinterDriver(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinterDriver *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinterDriverEx
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrinterDriverEx(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinterDriverEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddPrintProcessor
+****************************************************************/
+
+WERROR _winspool_AsyncAddPrintProcessor(struct pipes_struct *p,
+ struct winspool_AsyncAddPrintProcessor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPrintProcessors
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPrintProcessors(struct pipes_struct *p,
+ struct winspool_AsyncEnumPrintProcessors *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetPrintProcessorDirectory
+****************************************************************/
+
+WERROR _winspool_AsyncGetPrintProcessorDirectory(struct pipes_struct *p,
+ struct winspool_AsyncGetPrintProcessorDirectory *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPorts
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPorts(struct pipes_struct *p,
+ struct winspool_AsyncEnumPorts *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumMonitors
+****************************************************************/
+
+WERROR _winspool_AsyncEnumMonitors(struct pipes_struct *p,
+ struct winspool_AsyncEnumMonitors *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddPort
+****************************************************************/
+
+WERROR _winspool_AsyncAddPort(struct pipes_struct *p,
+ struct winspool_AsyncAddPort *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSetPort
+****************************************************************/
+
+WERROR _winspool_AsyncSetPort(struct pipes_struct *p,
+ struct winspool_AsyncSetPort *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddMonitor
+****************************************************************/
+
+WERROR _winspool_AsyncAddMonitor(struct pipes_struct *p,
+ struct winspool_AsyncAddMonitor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeleteMonitor
+****************************************************************/
+
+WERROR _winspool_AsyncDeleteMonitor(struct pipes_struct *p,
+ struct winspool_AsyncDeleteMonitor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrintProcessor
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePrintProcessor(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrintProcessor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPrintProcessorDatatypes
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPrintProcessorDatatypes(struct pipes_struct *p,
+ struct winspool_AsyncEnumPrintProcessorDatatypes *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncAddPerMachineConnection
+****************************************************************/
+
+WERROR _winspool_AsyncAddPerMachineConnection(struct pipes_struct *p,
+ struct winspool_AsyncAddPerMachineConnection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePerMachineConnection
+****************************************************************/
+
+WERROR _winspool_AsyncDeletePerMachineConnection(struct pipes_struct *p,
+ struct winspool_AsyncDeletePerMachineConnection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumPerMachineConnections
+****************************************************************/
+
+WERROR _winspool_AsyncEnumPerMachineConnections(struct pipes_struct *p,
+ struct winspool_AsyncEnumPerMachineConnections *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_SyncRegisterForRemoteNotifications
+****************************************************************/
+
+HRESULT _winspool_SyncRegisterForRemoteNotifications(struct pipes_struct *p,
+ struct winspool_SyncRegisterForRemoteNotifications *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_SyncUnRegisterForRemoteNotifications
+****************************************************************/
+
+HRESULT _winspool_SyncUnRegisterForRemoteNotifications(struct pipes_struct *p,
+ struct winspool_SyncUnRegisterForRemoteNotifications *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_SyncRefreshRemoteNotifications
+****************************************************************/
+
+HRESULT _winspool_SyncRefreshRemoteNotifications(struct pipes_struct *p,
+ struct winspool_SyncRefreshRemoteNotifications *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetRemoteNotifications
+****************************************************************/
+
+HRESULT _winspool_AsyncGetRemoteNotifications(struct pipes_struct *p,
+ struct winspool_AsyncGetRemoteNotifications *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncInstallPrinterDriverFromPackage
+****************************************************************/
+
+HRESULT _winspool_AsyncInstallPrinterDriverFromPackage(struct pipes_struct *p,
+ struct winspool_AsyncInstallPrinterDriverFromPackage *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncUploadPrinterDriverPackage
+****************************************************************/
+
+HRESULT _winspool_AsyncUploadPrinterDriverPackage(struct pipes_struct *p,
+ struct winspool_AsyncUploadPrinterDriverPackage *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetCorePrinterDrivers
+****************************************************************/
+
+HRESULT _winspool_AsyncGetCorePrinterDrivers(struct pipes_struct *p,
+ struct winspool_AsyncGetCorePrinterDrivers *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncCorePrinterDriverInstalled
+****************************************************************/
+
+HRESULT _winspool_AsyncCorePrinterDriverInstalled(struct pipes_struct *p,
+ struct winspool_AsyncCorePrinterDriverInstalled *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetPrinterDriverPackagePath
+****************************************************************/
+
+HRESULT _winspool_AsyncGetPrinterDriverPackagePath(struct pipes_struct *p,
+ struct winspool_AsyncGetPrinterDriverPackagePath *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeletePrinterDriverPackage
+****************************************************************/
+
+HRESULT _winspool_AsyncDeletePrinterDriverPackage(struct pipes_struct *p,
+ struct winspool_AsyncDeletePrinterDriverPackage *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncReadPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncReadPrinter(struct pipes_struct *p,
+ struct winspool_AsyncReadPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncResetPrinter
+****************************************************************/
+
+WERROR _winspool_AsyncResetPrinter(struct pipes_struct *p,
+ struct winspool_AsyncResetPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncGetJobNamedPropertyValue
+****************************************************************/
+
+WERROR _winspool_AsyncGetJobNamedPropertyValue(struct pipes_struct *p,
+ struct winspool_AsyncGetJobNamedPropertyValue *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncSetJobNamedProperty
+****************************************************************/
+
+WERROR _winspool_AsyncSetJobNamedProperty(struct pipes_struct *p,
+ struct winspool_AsyncSetJobNamedProperty *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncDeleteJobNamedProperty
+****************************************************************/
+
+WERROR _winspool_AsyncDeleteJobNamedProperty(struct pipes_struct *p,
+ struct winspool_AsyncDeleteJobNamedProperty *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncEnumJobNamedProperties
+****************************************************************/
+
+WERROR _winspool_AsyncEnumJobNamedProperties(struct pipes_struct *p,
+ struct winspool_AsyncEnumJobNamedProperties *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _winspool_AsyncLogJobInfoForBranchOffice
+****************************************************************/
+
+WERROR _winspool_AsyncLogJobInfoForBranchOffice(struct pipes_struct *p,
+ struct winspool_AsyncLogJobInfoForBranchOffice *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
diff --git a/source3/rpc_server/spoolss/srv_spoolss_handle.h b/source3/rpc_server/spoolss/srv_spoolss_handle.h
new file mode 100644
index 0000000..e84037c
--- /dev/null
+++ b/source3/rpc_server/spoolss/srv_spoolss_handle.h
@@ -0,0 +1,77 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000,
+ * Copyright (C) Jeremy Allison 2001-2002,
+ * Copyright (C) Gerald Carter 2000-2004,
+ * Copyright (C) Tim Potter 2001-2002.
+ * Copyright (C) Guenther Deschner 2009-2010.
+ * Copyright (C) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Since the SPOOLSS rpc routines are basically DOS 16-bit calls wrapped
+ up, all the errors returned are DOS errors, not NT status codes. */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/spoolss.h"
+
+struct notify_back_channel;
+
+#define SPLHND_PRINTER 1
+#define SPLHND_SERVER 2
+#define SPLHND_PORTMON_TCP 3
+#define SPLHND_PORTMON_LOCAL 4
+
+/* structure to store the printer handles */
+/* and a reference to what it's pointing to */
+/* and the notify info asked about */
+/* that's the central struct */
+struct printer_handle {
+ struct printer_handle *prev, *next;
+ bool document_started;
+ bool page_started;
+ uint32_t jobid; /* jobid in printing backend */
+ int printer_type;
+ const char *servername;
+ fstring sharename;
+ uint32_t access_granted;
+ struct {
+ uint32_t flags;
+ uint32_t options;
+ fstring localmachine;
+ uint32_t printerlocal;
+ struct spoolss_NotifyOption *option;
+ struct policy_handle cli_hnd;
+ struct notify_back_channel *cli_chan;
+ uint32_t change;
+ /* are we in a FindNextPrinterChangeNotify() call? */
+ bool fnpcn;
+ struct messaging_context *msg_ctx;
+ } notify;
+ struct {
+ fstring machine;
+ fstring user;
+ } client;
+
+ /* devmode sent in the OpenPrinter() call */
+ struct spoolss_DeviceMode *devmode;
+
+ /* TODO cache the printer info2 structure */
+ struct spoolss_PrinterInfo2 *info2;
+
+};
diff --git a/source3/rpc_server/spoolss/srv_spoolss_nt.c b/source3/rpc_server/spoolss/srv_spoolss_nt.c
new file mode 100644
index 0000000..16b8b41
--- /dev/null
+++ b/source3/rpc_server/spoolss/srv_spoolss_nt.c
@@ -0,0 +1,11622 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000,
+ * Copyright (C) Jeremy Allison 2001-2002,
+ * Copyright (C) Gerald Carter 2000-2004,
+ * Copyright (C) Tim Potter 2001-2002.
+ * Copyright (C) Guenther Deschner 2009-2010.
+ * Copyright (C) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Since the SPOOLSS rpc routines are basically DOS 16-bit calls wrapped
+ up, all the errors returned are DOS errors, not NT status codes. */
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "ntdomain.h"
+#include "nt_printing.h"
+#include "srv_spoolss_util.h"
+#include "librpc/gen_ndr/ndr_spoolss.h"
+#include "librpc/gen_ndr/ndr_spoolss_scompat.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/init_spoolss.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "registry.h"
+#include "include/printing.h"
+#include "secrets.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "rpc_misc.h"
+#include "printing/notify.h"
+#include "serverid.h"
+#include "../libcli/registry/util_reg.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "auth.h"
+#include "messages.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "util_tdb.h"
+#include "libsmb/libsmb.h"
+#include "printing/printer_list.h"
+#include "../lib/tsocket/tsocket.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "rpc_server/spoolss/srv_spoolss_handle.h"
+#include "lib/gencache.h"
+#include "rpc_server/rpc_server.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "printing/nt_printing_migrate_internal.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+
+/* macros stolen from s4 spoolss server */
+#define SPOOLSS_BUFFER_UNION(fn,info,level) \
+ ((info)?ndr_size_##fn(info, level, 0):0)
+
+#define SPOOLSS_BUFFER_UNION_ARRAY(mem_ctx,fn,info,level,count) \
+ ((info)?ndr_size_##fn##_info(mem_ctx, level, count, info):0)
+
+#define SPOOLSS_BUFFER_ARRAY(mem_ctx,fn,info,count) \
+ ((info)?ndr_size_##fn##_info(mem_ctx, count, info):0)
+
+#define SPOOLSS_BUFFER_OK(val_true,val_false) ((r->in.offered >= *r->out.needed)?val_true:val_false)
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#ifndef MAX_OPEN_PRINTER_EXS
+#define MAX_OPEN_PRINTER_EXS 50
+#endif
+
+#define GLOBAL_SPOOLSS_OS_MAJOR_DEFAULT 5
+#define GLOBAL_SPOOLSS_OS_MINOR_DEFAULT 2
+#define GLOBAL_SPOOLSS_OS_BUILD_DEFAULT 3790
+#define GLOBAL_SPOOLSS_ARCHITECTURE SPOOLSS_ARCHITECTURE_x64
+
+static struct printer_handle *printers_list;
+
+struct printer_session_counter {
+ struct printer_session_counter *next;
+ struct printer_session_counter *prev;
+
+ int snum;
+ uint32_t counter;
+};
+
+static struct printer_session_counter *counter_list;
+
+struct notify_back_channel {
+ struct notify_back_channel *prev, *next;
+
+ /* associated client */
+ struct sockaddr_storage client_address;
+
+ /* print notify back-channel pipe handle*/
+ struct rpc_pipe_client *cli_pipe;
+ struct cli_state *cli;
+ uint32_t active_connections;
+};
+
+static struct notify_back_channel *back_channels;
+
+/* Map generic permissions to printer object specific permissions */
+
+const struct standard_mapping printer_std_mapping = {
+ PRINTER_READ,
+ PRINTER_WRITE,
+ PRINTER_EXECUTE,
+ PRINTER_ALL_ACCESS
+};
+
+/* Map generic permissions to print server object specific permissions */
+
+const struct standard_mapping printserver_std_mapping = {
+ SERVER_READ,
+ SERVER_WRITE,
+ SERVER_EXECUTE,
+ SERVER_ALL_ACCESS
+};
+
+/* API table for Xcv Monitor functions */
+
+struct xcv_api_table {
+ const char *name;
+ WERROR(*fn) (TALLOC_CTX *mem_ctx, struct security_token *token, DATA_BLOB *in, DATA_BLOB *out, uint32_t *needed);
+};
+
+static void prune_printername_cache(void);
+
+/********************************************************************
+ * Canonicalize servername.
+ ********************************************************************/
+
+static const char *canon_servername(const char *servername)
+{
+ const char *pservername = servername;
+
+ if (servername == NULL) {
+ return "";
+ }
+
+ while (*pservername == '\\') {
+ pservername++;
+ }
+ return pservername;
+}
+
+/* translate between internal status numbers and NT status numbers */
+static int nt_printj_status(int v)
+{
+ switch (v) {
+ case LPQ_QUEUED:
+ return 0;
+ case LPQ_PAUSED:
+ return JOB_STATUS_PAUSED;
+ case LPQ_SPOOLING:
+ return JOB_STATUS_SPOOLING;
+ case LPQ_PRINTING:
+ return JOB_STATUS_PRINTING;
+ case LPQ_ERROR:
+ return JOB_STATUS_ERROR;
+ case LPQ_DELETING:
+ return JOB_STATUS_DELETING;
+ case LPQ_OFFLINE:
+ return JOB_STATUS_OFFLINE;
+ case LPQ_PAPEROUT:
+ return JOB_STATUS_PAPEROUT;
+ case LPQ_PRINTED:
+ return JOB_STATUS_PRINTED;
+ case LPQ_DELETED:
+ return JOB_STATUS_DELETED;
+ case LPQ_BLOCKED:
+ return JOB_STATUS_BLOCKED_DEVQ;
+ case LPQ_USER_INTERVENTION:
+ return JOB_STATUS_USER_INTERVENTION;
+ }
+ return 0;
+}
+
+static int nt_printq_status(int v)
+{
+ switch (v) {
+ case LPQ_PAUSED:
+ return PRINTER_STATUS_PAUSED;
+ case LPQ_QUEUED:
+ case LPQ_SPOOLING:
+ case LPQ_PRINTING:
+ return 0;
+ }
+ return 0;
+}
+
+/***************************************************************************
+ Disconnect from the client
+****************************************************************************/
+
+static void srv_spoolss_replycloseprinter(int snum,
+ struct printer_handle *prn_hnd)
+{
+ WERROR result;
+ NTSTATUS status;
+
+ /*
+ * Tell the specific printing tdb we no longer want messages for this printer
+ * by deregistering our PID.
+ */
+
+ if (!print_notify_deregister_pid(snum)) {
+ DEBUG(0, ("Failed to register our pid for printer %s\n",
+ lp_const_servicename(snum)));
+ }
+
+ /* weird if the test succeeds !!! */
+ if (prn_hnd->notify.cli_chan == NULL ||
+ prn_hnd->notify.cli_chan->cli_pipe == NULL ||
+ prn_hnd->notify.cli_chan->cli_pipe->binding_handle == NULL ||
+ prn_hnd->notify.cli_chan->active_connections == 0) {
+ DEBUG(0, ("Trying to close unexisting backchannel!\n"));
+ DLIST_REMOVE(back_channels, prn_hnd->notify.cli_chan);
+ TALLOC_FREE(prn_hnd->notify.cli_chan);
+ return;
+ }
+
+ status = dcerpc_spoolss_ReplyClosePrinter(
+ prn_hnd->notify.cli_chan->cli_pipe->binding_handle,
+ talloc_tos(),
+ &prn_hnd->notify.cli_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_spoolss_ReplyClosePrinter failed [%s].\n",
+ nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ } else if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("reply_close_printer failed [%s].\n",
+ win_errstr(result)));
+ }
+
+ /* if it's the last connection, deconnect the IPC$ share */
+ if (prn_hnd->notify.cli_chan->active_connections == 1) {
+
+ cli_shutdown(prn_hnd->notify.cli_chan->cli);
+ DLIST_REMOVE(back_channels, prn_hnd->notify.cli_chan);
+ TALLOC_FREE(prn_hnd->notify.cli_chan);
+
+ if (prn_hnd->notify.msg_ctx != NULL) {
+ messaging_deregister(prn_hnd->notify.msg_ctx,
+ MSG_PRINTER_NOTIFY2, NULL);
+ }
+ }
+
+ if (prn_hnd->notify.cli_chan) {
+ prn_hnd->notify.cli_chan->active_connections--;
+ prn_hnd->notify.cli_chan = NULL;
+ }
+}
+
+/****************************************************************************
+ Functions to free a printer entry datastruct.
+****************************************************************************/
+
+static int printer_entry_destructor(struct printer_handle *Printer)
+{
+ if (Printer->notify.cli_chan != NULL &&
+ Printer->notify.cli_chan->active_connections > 0) {
+ int snum = -1;
+
+ switch(Printer->printer_type) {
+ case SPLHND_SERVER:
+ srv_spoolss_replycloseprinter(snum, Printer);
+ break;
+
+ case SPLHND_PRINTER:
+ snum = print_queue_snum(Printer->sharename);
+ if (snum != -1) {
+ srv_spoolss_replycloseprinter(snum, Printer);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ Printer->notify.flags=0;
+ Printer->notify.options=0;
+ Printer->notify.localmachine[0]='\0';
+ Printer->notify.printerlocal=0;
+ TALLOC_FREE(Printer->notify.option);
+ TALLOC_FREE(Printer->devmode);
+
+ /* Remove from the internal list. */
+ DLIST_REMOVE(printers_list, Printer);
+ return 0;
+}
+
+/****************************************************************************
+ find printer index by handle
+****************************************************************************/
+
+static struct printer_handle *find_printer_index_by_hnd(struct pipes_struct *p,
+ struct policy_handle *hnd)
+{
+ struct printer_handle *find_printer = NULL;
+ NTSTATUS status;
+
+ find_printer = find_policy_by_hnd(p,
+ hnd,
+ DCESRV_HANDLE_ANY,
+ struct printer_handle,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2,("find_printer_index_by_hnd: Printer handle not found: %s\n",
+ nt_errstr(status)));
+ return NULL;
+ }
+
+ return find_printer;
+}
+
+/****************************************************************************
+ Close printer index by handle.
+****************************************************************************/
+
+static bool close_printer_handle(struct pipes_struct *p, struct policy_handle *hnd)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd);
+
+ if (!Printer) {
+ DEBUG(2,("close_printer_handle: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(hnd)));
+ return false;
+ }
+
+ close_policy_hnd(p, hnd);
+
+ return true;
+}
+
+/****************************************************************************
+ Delete a printer given a handle.
+****************************************************************************/
+
+static WERROR delete_printer_hook(TALLOC_CTX *ctx, struct security_token *token,
+ const char *sharename,
+ struct messaging_context *msg_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *cmd = lp_deleteprinter_command(talloc_tos(), lp_sub);
+ char *command = NULL;
+ int ret;
+ bool is_print_op = false;
+
+ /* can't fail if we don't try */
+
+ if ( !*cmd )
+ return WERR_OK;
+
+ command = talloc_asprintf(ctx,
+ "%s \"%s\"",
+ cmd, sharename);
+ if (!command) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if ( token )
+ is_print_op = security_token_has_privilege(token, SEC_PRIV_PRINT_OPERATOR);
+
+ DEBUG(10,("Running [%s]\n", command));
+
+ /********** BEGIN SePrintOperatorPrivlege BLOCK **********/
+
+ if ( is_print_op )
+ become_root();
+
+ ret = smbrun(command, NULL, NULL);
+ if (ret == 0) {
+ /* Tell everyone we updated smb.conf. */
+ messaging_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0);
+ }
+
+ if ( is_print_op )
+ unbecome_root();
+
+ /********** END SePrintOperatorPrivlege BLOCK **********/
+
+ DEBUGADD(10,("returned [%d]\n", ret));
+
+ TALLOC_FREE(command);
+
+ if (ret != 0)
+ return WERR_INVALID_HANDLE; /* What to return here? */
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Delete a printer given a handle.
+****************************************************************************/
+
+static WERROR delete_printer_handle(struct pipes_struct *p, struct policy_handle *hnd)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd);
+ WERROR result;
+
+ if (!Printer) {
+ DEBUG(2,("delete_printer_handle: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(hnd)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /*
+ * It turns out that Windows allows delete printer on a handle
+ * opened by an admin user, then used on a pipe handle created
+ * by an anonymous user..... but they're working on security.... riiight !
+ * JRA.
+ */
+
+ if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) {
+ DEBUG(3, ("delete_printer_handle: denied by handle\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* this does not need a become root since the access check has been
+ done on the handle already */
+
+ result = winreg_delete_printer_key_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ Printer->sharename,
+ "");
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3,("Error deleting printer %s\n", Printer->sharename));
+ return WERR_INVALID_HANDLE;
+ }
+
+ result = delete_printer_hook(p->mem_ctx, session_info->security_token,
+ Printer->sharename, p->msg_ctx);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+ prune_printername_cache();
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Return the snum of a printer corresponding to an handle.
+****************************************************************************/
+
+static bool get_printer_snum(struct pipes_struct *p, struct policy_handle *hnd,
+ int *number, struct share_params **params)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd);
+
+ if (!Printer) {
+ DEBUG(2,("get_printer_snum: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(hnd)));
+ return false;
+ }
+
+ switch (Printer->printer_type) {
+ case SPLHND_PRINTER:
+ DEBUG(4,("short name:%s\n", Printer->sharename));
+ *number = print_queue_snum(Printer->sharename);
+ return (*number != -1);
+ case SPLHND_SERVER:
+ return false;
+ default:
+ return false;
+ }
+}
+
+/****************************************************************************
+ Set printer handle type.
+ Check if it's \\server or \\server\printer
+****************************************************************************/
+
+static bool set_printer_hnd_printertype(struct printer_handle *Printer, const char *handlename)
+{
+ DEBUG(3,("Setting printer type=%s\n", handlename));
+
+ /* it's a print server */
+ if (handlename && *handlename=='\\' && *(handlename+1)=='\\' && !strchr_m(handlename+2, '\\')) {
+ DEBUGADD(4,("Printer is a print server\n"));
+ Printer->printer_type = SPLHND_SERVER;
+ }
+ /* it's a printer (set_printer_hnd_name() will handle port monitors */
+ else {
+ DEBUGADD(4,("Printer is a printer\n"));
+ Printer->printer_type = SPLHND_PRINTER;
+ }
+
+ return true;
+}
+
+static void prune_printername_cache_fn(const char *key, const char *value,
+ time_t timeout, void *private_data)
+{
+ gencache_del(key);
+}
+
+static void prune_printername_cache(void)
+{
+ gencache_iterate(prune_printername_cache_fn, NULL, "PRINTERNAME/*");
+}
+
+/****************************************************************************
+ Set printer handle name.. Accept names like \\server, \\server\printer,
+ \\server\SHARE, & "\\server\,XcvMonitor Standard TCP/IP Port" See
+ the MSDN docs regarding OpenPrinter() for details on the XcvData() and
+ XcvDataPort() interface.
+****************************************************************************/
+
+static WERROR set_printer_hnd_name(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct printer_handle *Printer,
+ const char *handlename)
+{
+ int snum;
+ int n_services=lp_numservices();
+ char *aprinter;
+ const char *printername;
+ const char *servername = NULL;
+ fstring sname;
+ bool found = false;
+ struct spoolss_PrinterInfo2 *info2 = NULL;
+ WERROR result;
+ char *p;
+
+ /*
+ * Hopefully nobody names his printers like this. Maybe \ or ,
+ * are illegal in printer names even?
+ */
+ const char printer_not_found[] = "Printer \\, !@#$%^&*( not found";
+ char *cache_key;
+ char *tmp;
+
+ DEBUG(4,("Setting printer name=%s (len=%lu)\n", handlename,
+ (unsigned long)strlen(handlename)));
+
+ aprinter = discard_const_p(char, handlename);
+ if ( *handlename == '\\' ) {
+ servername = canon_servername(handlename);
+ if ( (aprinter = strchr_m( servername, '\\' )) != NULL ) {
+ *aprinter = '\0';
+ aprinter++;
+ }
+ if (!is_myname_or_ipaddr(servername)) {
+ return WERR_INVALID_PRINTER_NAME;
+ }
+ Printer->servername = talloc_asprintf(Printer, "\\\\%s", servername);
+ if (Printer->servername == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (Printer->printer_type == SPLHND_SERVER) {
+ return WERR_OK;
+ }
+
+ if (Printer->printer_type != SPLHND_PRINTER) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ DEBUGADD(5, ("searching for [%s]\n", aprinter));
+
+ p = strchr(aprinter, ',');
+ if (p != NULL) {
+ char *p2 = p;
+ p++;
+ if (*p == ' ') {
+ p++;
+ }
+ if (strncmp(p, "DrvConvert", strlen("DrvConvert")) == 0) {
+ *p2 = '\0';
+ } else if (strncmp(p, "LocalOnly", strlen("LocalOnly")) == 0) {
+ *p2 = '\0';
+ }
+ }
+
+ if (p) {
+ DEBUGADD(5, ("stripped handlename: [%s]\n", aprinter));
+ }
+
+ /* check for the Port Monitor Interface */
+ if ( strequal( aprinter, SPL_XCV_MONITOR_TCPMON ) ) {
+ Printer->printer_type = SPLHND_PORTMON_TCP;
+ fstrcpy(sname, SPL_XCV_MONITOR_TCPMON);
+ found = true;
+ }
+ else if ( strequal( aprinter, SPL_XCV_MONITOR_LOCALMON ) ) {
+ Printer->printer_type = SPLHND_PORTMON_LOCAL;
+ fstrcpy(sname, SPL_XCV_MONITOR_LOCALMON);
+ found = true;
+ }
+
+ cache_key = talloc_asprintf(talloc_tos(), "PRINTERNAME/%s", aprinter);
+ if (cache_key == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /*
+ * With hundreds of printers, the "for" loop iterating all
+ * shares can be quite expensive, as it is done on every
+ * OpenPrinter. The loop maps "aprinter" to "sname", the
+ * result of which we cache in gencache.
+ */
+ if (gencache_get(cache_key, talloc_tos(), &tmp, NULL)) {
+ found = (strcmp(tmp, printer_not_found) != 0);
+ if (!found) {
+ DEBUG(4, ("Printer %s not found\n", aprinter));
+ TALLOC_FREE(tmp);
+ return WERR_INVALID_PRINTER_NAME;
+ }
+ fstrcpy(sname, tmp);
+ TALLOC_FREE(tmp);
+ }
+
+ /* Search all sharenames first as this is easier than pulling
+ the printer_info_2 off of disk. Don't use find_service() since
+ that calls out to map_username() */
+
+ /* do another loop to look for printernames */
+ for (snum = 0; !found && snum < n_services; snum++) {
+ const char *printer = lp_const_servicename(snum);
+
+ /* no point going on if this is not a printer */
+ if (!(lp_snum_ok(snum) && lp_printable(snum))) {
+ continue;
+ }
+
+ /* ignore [printers] share */
+ if (strequal(printer, "printers")) {
+ continue;
+ }
+
+ fstrcpy(sname, printer);
+ if (strequal(aprinter, printer)) {
+ found = true;
+ break;
+ }
+
+ /* no point looking up the printer object if
+ we aren't allowing printername != sharename */
+ if (lp_force_printername(snum)) {
+ continue;
+ }
+
+ result = winreg_get_printer_internal(mem_ctx,
+ session_info,
+ msg_ctx,
+ sname,
+ &info2);
+ if ( !W_ERROR_IS_OK(result) ) {
+ DEBUG(2,("set_printer_hnd_name: failed to lookup printer [%s] -- result [%s]\n",
+ sname, win_errstr(result)));
+ continue;
+ }
+
+ printername = strrchr(info2->printername, '\\');
+ if (printername == NULL) {
+ printername = info2->printername;
+ } else {
+ printername++;
+ }
+
+ if (strequal(printername, aprinter)) {
+ found = true;
+ break;
+ }
+
+ DEBUGADD(10, ("printername: %s\n", printername));
+
+ TALLOC_FREE(info2);
+ }
+
+ if (!found) {
+ gencache_set(cache_key, printer_not_found,
+ time(NULL) + 300);
+ TALLOC_FREE(cache_key);
+ DEBUGADD(4,("Printer not found\n"));
+ return WERR_INVALID_PRINTER_NAME;
+ }
+
+ gencache_set(cache_key, sname, time(NULL) + 300);
+ TALLOC_FREE(cache_key);
+
+ DEBUGADD(4,("set_printer_hnd_name: Printer found: %s -> %s\n", aprinter, sname));
+
+ strlcpy(Printer->sharename, sname, sizeof(Printer->sharename));
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Find first available printer slot. creates a printer handle for you.
+ ****************************************************************************/
+
+static WERROR open_printer_hnd(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ const char *name,
+ uint32_t access_granted)
+{
+ struct printer_handle *new_printer;
+ WERROR result;
+
+ DEBUG(10,("open_printer_hnd: name [%s]\n", name));
+
+ new_printer = talloc_zero(p->mem_ctx, struct printer_handle);
+ if (new_printer == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ talloc_set_destructor(new_printer, printer_entry_destructor);
+
+ /* This also steals the printer_handle on the policy_handle */
+ if (!create_policy_hnd(p, hnd, 0, new_printer)) {
+ TALLOC_FREE(new_printer);
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* Add to the internal list. */
+ DLIST_ADD(printers_list, new_printer);
+
+ new_printer->notify.option=NULL;
+
+ if (!set_printer_hnd_printertype(new_printer, name)) {
+ close_printer_handle(p, hnd);
+ return WERR_INVALID_HANDLE;
+ }
+
+ result = set_printer_hnd_name(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ new_printer, name);
+ if (!W_ERROR_IS_OK(result)) {
+ close_printer_handle(p, hnd);
+ return result;
+ }
+
+ new_printer->access_granted = access_granted;
+
+ DBG_INFO("%d printer handles active\n", (int)num_pipe_handles());
+
+ return WERR_OK;
+}
+
+/***************************************************************************
+ check to see if the client notify handle is monitoring the notification
+ given by (notify_type, notify_field).
+ **************************************************************************/
+
+static bool is_monitoring_event_flags(uint32_t flags, uint16_t notify_type,
+ uint16_t notify_field)
+{
+ return true;
+}
+
+static bool is_monitoring_event(struct printer_handle *p, uint16_t notify_type,
+ uint16_t notify_field)
+{
+ struct spoolss_NotifyOption *option = p->notify.option;
+ uint32_t i, j;
+
+ /*
+ * Flags should always be zero when the change notify
+ * is registered by the client's spooler. A user Win32 app
+ * might use the flags though instead of the NOTIFY_OPTION_INFO
+ * --jerry
+ */
+
+ if (!option) {
+ return false;
+ }
+
+ if (p->notify.flags)
+ return is_monitoring_event_flags(
+ p->notify.flags, notify_type, notify_field);
+
+ for (i = 0; i < option->count; i++) {
+
+ /* Check match for notify_type */
+
+ if (option->types[i].type != notify_type)
+ continue;
+
+ /* Check match for field */
+
+ for (j = 0; j < option->types[i].count; j++) {
+ if (option->types[i].fields[j].field == notify_field) {
+ return true;
+ }
+ }
+ }
+
+ DEBUG(10, ("Open handle for \\\\%s\\%s is not monitoring 0x%02x/0x%02x\n",
+ p->servername, p->sharename, notify_type, notify_field));
+
+ return false;
+}
+
+#define SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(_data, _integer) \
+ _data->data.integer[0] = _integer; \
+ _data->data.integer[1] = 0;
+
+
+#define SETUP_SPOOLSS_NOTIFY_DATA_STRING(_data, _p) \
+ _data->data.string.string = talloc_strdup(mem_ctx, _p); \
+ if (!_data->data.string.string) {\
+ _data->data.string.size = 0; \
+ } \
+ _data->data.string.size = strlen_m_term(_p) * 2;
+
+#define SETUP_SPOOLSS_NOTIFY_DATA_DEVMODE(_data, _devmode) \
+ _data->data.devmode.devmode = _devmode;
+
+static void init_systemtime_buffer(TALLOC_CTX *mem_ctx,
+ struct tm *t,
+ const char **pp,
+ uint32_t *plen)
+{
+ struct spoolss_Time st;
+ uint32_t len = 16;
+ char *p;
+
+ if (!init_systemtime(&st, t)) {
+ return;
+ }
+
+ p = talloc_array(mem_ctx, char, len);
+ if (!p) {
+ return;
+ }
+
+ /*
+ * Systemtime must be linearized as a set of UINT16's.
+ * Fix from Benjamin (Bj) Kuit bj@it.uts.edu.au
+ */
+
+ SSVAL(p, 0, st.year);
+ SSVAL(p, 2, st.month);
+ SSVAL(p, 4, st.day_of_week);
+ SSVAL(p, 6, st.day);
+ SSVAL(p, 8, st.hour);
+ SSVAL(p, 10, st.minute);
+ SSVAL(p, 12, st.second);
+ SSVAL(p, 14, st.millisecond);
+
+ *pp = p;
+ *plen = len;
+}
+
+/* Convert a notification message to a struct spoolss_Notify */
+
+static void notify_one_value(struct spoolss_notify_msg *msg,
+ struct spoolss_Notify *data,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, msg->notify.value[0]);
+}
+
+static void notify_string(struct spoolss_notify_msg *msg,
+ struct spoolss_Notify *data,
+ TALLOC_CTX *mem_ctx)
+{
+ /* The length of the message includes the trailing \0 */
+
+ data->data.string.size = msg->len * 2;
+ data->data.string.string = talloc_strdup(mem_ctx, msg->notify.data);
+ if (!data->data.string.string) {
+ data->data.string.size = 0;
+ return;
+ }
+}
+
+static void notify_system_time(struct spoolss_notify_msg *msg,
+ struct spoolss_Notify *data,
+ TALLOC_CTX *mem_ctx)
+{
+ data->data.string.string = NULL;
+ data->data.string.size = 0;
+
+ if (msg->len != sizeof(time_t)) {
+ DEBUG(5, ("notify_system_time: received wrong sized message (%d)\n",
+ msg->len));
+ return;
+ }
+
+ init_systemtime_buffer(mem_ctx, gmtime((time_t *)msg->notify.data),
+ &data->data.string.string,
+ &data->data.string.size);
+}
+
+struct notify2_message_table {
+ const char *name;
+ void (*fn)(struct spoolss_notify_msg *msg,
+ struct spoolss_Notify *data, TALLOC_CTX *mem_ctx);
+};
+
+static struct notify2_message_table printer_notify_table[] = {
+ /* 0x00 */ { "PRINTER_NOTIFY_FIELD_SERVER_NAME", notify_string },
+ /* 0x01 */ { "PRINTER_NOTIFY_FIELD_PRINTER_NAME", notify_string },
+ /* 0x02 */ { "PRINTER_NOTIFY_FIELD_SHARE_NAME", notify_string },
+ /* 0x03 */ { "PRINTER_NOTIFY_FIELD_PORT_NAME", notify_string },
+ /* 0x04 */ { "PRINTER_NOTIFY_FIELD_DRIVER_NAME", notify_string },
+ /* 0x05 */ { "PRINTER_NOTIFY_FIELD_COMMENT", notify_string },
+ /* 0x06 */ { "PRINTER_NOTIFY_FIELD_LOCATION", notify_string },
+ /* 0x07 */ { "PRINTER_NOTIFY_FIELD_DEVMODE", NULL },
+ /* 0x08 */ { "PRINTER_NOTIFY_FIELD_SEPFILE", notify_string },
+ /* 0x09 */ { "PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR", notify_string },
+ /* 0x0a */ { "PRINTER_NOTIFY_FIELD_PARAMETERS", NULL },
+ /* 0x0b */ { "PRINTER_NOTIFY_FIELD_DATATYPE", notify_string },
+ /* 0x0c */ { "PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NULL },
+ /* 0x0d */ { "PRINTER_NOTIFY_FIELD_ATTRIBUTES", notify_one_value },
+ /* 0x0e */ { "PRINTER_NOTIFY_FIELD_PRIORITY", notify_one_value },
+ /* 0x0f */ { "PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY", NULL },
+ /* 0x10 */ { "PRINTER_NOTIFY_FIELD_START_TIME", NULL },
+ /* 0x11 */ { "PRINTER_NOTIFY_FIELD_UNTIL_TIME", NULL },
+ /* 0x12 */ { "PRINTER_NOTIFY_FIELD_STATUS", notify_one_value },
+};
+
+static struct notify2_message_table job_notify_table[] = {
+ /* 0x00 */ { "JOB_NOTIFY_FIELD_PRINTER_NAME", NULL },
+ /* 0x01 */ { "JOB_NOTIFY_FIELD_MACHINE_NAME", NULL },
+ /* 0x02 */ { "JOB_NOTIFY_FIELD_PORT_NAME", NULL },
+ /* 0x03 */ { "JOB_NOTIFY_FIELD_USER_NAME", notify_string },
+ /* 0x04 */ { "JOB_NOTIFY_FIELD_NOTIFY_NAME", NULL },
+ /* 0x05 */ { "JOB_NOTIFY_FIELD_DATATYPE", NULL },
+ /* 0x06 */ { "JOB_NOTIFY_FIELD_PRINT_PROCESSOR", NULL },
+ /* 0x07 */ { "JOB_NOTIFY_FIELD_PARAMETERS", NULL },
+ /* 0x08 */ { "JOB_NOTIFY_FIELD_DRIVER_NAME", NULL },
+ /* 0x09 */ { "JOB_NOTIFY_FIELD_DEVMODE", NULL },
+ /* 0x0a */ { "JOB_NOTIFY_FIELD_STATUS", notify_one_value },
+ /* 0x0b */ { "JOB_NOTIFY_FIELD_STATUS_STRING", NULL },
+ /* 0x0c */ { "JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NULL },
+ /* 0x0d */ { "JOB_NOTIFY_FIELD_DOCUMENT", notify_string },
+ /* 0x0e */ { "JOB_NOTIFY_FIELD_PRIORITY", NULL },
+ /* 0x0f */ { "JOB_NOTIFY_FIELD_POSITION", NULL },
+ /* 0x10 */ { "JOB_NOTIFY_FIELD_SUBMITTED", notify_system_time },
+ /* 0x11 */ { "JOB_NOTIFY_FIELD_START_TIME", NULL },
+ /* 0x12 */ { "JOB_NOTIFY_FIELD_UNTIL_TIME", NULL },
+ /* 0x13 */ { "JOB_NOTIFY_FIELD_TIME", NULL },
+ /* 0x14 */ { "JOB_NOTIFY_FIELD_TOTAL_PAGES", notify_one_value },
+ /* 0x15 */ { "JOB_NOTIFY_FIELD_PAGES_PRINTED", NULL },
+ /* 0x16 */ { "JOB_NOTIFY_FIELD_TOTAL_BYTES", notify_one_value },
+ /* 0x17 */ { "JOB_NOTIFY_FIELD_BYTES_PRINTED", NULL },
+};
+
+
+/***********************************************************************
+ Allocate talloc context for container object
+ **********************************************************************/
+
+static void notify_msg_ctr_init( SPOOLSS_NOTIFY_MSG_CTR *ctr )
+{
+ if ( !ctr )
+ return;
+
+ ctr->ctx = talloc_init("notify_msg_ctr_init %p", ctr);
+
+ return;
+}
+
+/***********************************************************************
+ release all allocated memory and zero out structure
+ **********************************************************************/
+
+static void notify_msg_ctr_destroy( SPOOLSS_NOTIFY_MSG_CTR *ctr )
+{
+ if ( !ctr )
+ return;
+
+ if ( ctr->ctx )
+ talloc_destroy(ctr->ctx);
+
+ ZERO_STRUCTP(ctr);
+
+ return;
+}
+
+/***********************************************************************
+ **********************************************************************/
+
+static TALLOC_CTX* notify_ctr_getctx( SPOOLSS_NOTIFY_MSG_CTR *ctr )
+{
+ if ( !ctr )
+ return NULL;
+
+ return ctr->ctx;
+}
+
+/***********************************************************************
+ **********************************************************************/
+
+static SPOOLSS_NOTIFY_MSG_GROUP* notify_ctr_getgroup( SPOOLSS_NOTIFY_MSG_CTR *ctr, uint32_t idx )
+{
+ if ( !ctr || !ctr->msg_groups )
+ return NULL;
+
+ if ( idx >= ctr->num_groups )
+ return NULL;
+
+ return &ctr->msg_groups[idx];
+
+}
+
+/***********************************************************************
+ How many groups of change messages do we have ?
+ **********************************************************************/
+
+static uint32_t notify_msg_ctr_numgroups( SPOOLSS_NOTIFY_MSG_CTR *ctr )
+{
+ if ( !ctr )
+ return 0;
+
+ return ctr->num_groups;
+}
+
+/***********************************************************************
+ Add a SPOOLSS_NOTIFY_MSG_CTR to the correct group
+ **********************************************************************/
+
+static int notify_msg_ctr_addmsg( SPOOLSS_NOTIFY_MSG_CTR *ctr, SPOOLSS_NOTIFY_MSG *msg )
+{
+ SPOOLSS_NOTIFY_MSG_GROUP *groups = NULL;
+ SPOOLSS_NOTIFY_MSG_GROUP *msg_grp = NULL;
+ SPOOLSS_NOTIFY_MSG *msg_list = NULL;
+ uint32_t i, new_slot;
+
+ if ( !ctr || !msg )
+ return 0;
+
+ /* loop over all groups looking for a matching printer name */
+
+ for ( i=0; i<ctr->num_groups; i++ ) {
+ if ( strcmp(ctr->msg_groups[i].printername, msg->printer) == 0 )
+ break;
+ }
+
+ /* add a new group? */
+
+ if ( i == ctr->num_groups ) {
+ ctr->num_groups++;
+
+ if ( !(groups = talloc_realloc( ctr->ctx, ctr->msg_groups, SPOOLSS_NOTIFY_MSG_GROUP, ctr->num_groups)) ) {
+ DEBUG(0,("notify_msg_ctr_addmsg: talloc_realloc() failed!\n"));
+ return 0;
+ }
+ ctr->msg_groups = groups;
+
+ /* clear the new entry and set the printer name */
+
+ ZERO_STRUCT( ctr->msg_groups[ctr->num_groups-1] );
+ fstrcpy( ctr->msg_groups[ctr->num_groups-1].printername, msg->printer );
+ }
+
+ /* add the change messages; 'i' is the correct index now regardless */
+
+ msg_grp = &ctr->msg_groups[i];
+
+ msg_grp->num_msgs++;
+
+ if ( !(msg_list = talloc_realloc( ctr->ctx, msg_grp->msgs, SPOOLSS_NOTIFY_MSG, msg_grp->num_msgs )) ) {
+ DEBUG(0,("notify_msg_ctr_addmsg: talloc_realloc() failed for new message [%d]!\n", msg_grp->num_msgs));
+ return 0;
+ }
+ msg_grp->msgs = msg_list;
+
+ new_slot = msg_grp->num_msgs-1;
+ memcpy( &msg_grp->msgs[new_slot], msg, sizeof(SPOOLSS_NOTIFY_MSG) );
+
+ /* need to allocate own copy of data */
+
+ if ( msg->len != 0 )
+ msg_grp->msgs[new_slot].notify.data = (char *)
+ talloc_memdup( ctr->ctx, msg->notify.data, msg->len );
+
+ return ctr->num_groups;
+}
+
+static void construct_info_data(struct spoolss_Notify *info_data,
+ enum spoolss_NotifyType type,
+ uint16_t field, int id);
+
+/***********************************************************************
+ Send a change notification message on all handles which have a call
+ back registered
+ **********************************************************************/
+
+static int build_notify2_messages(TALLOC_CTX *mem_ctx,
+ struct printer_handle *prn_hnd,
+ SPOOLSS_NOTIFY_MSG *messages,
+ uint32_t num_msgs,
+ struct spoolss_Notify **_notifies,
+ size_t *_count)
+{
+ struct spoolss_Notify *notifies;
+ SPOOLSS_NOTIFY_MSG *msg;
+ size_t count = 0;
+ uint32_t id;
+ uint32_t i;
+
+ notifies = talloc_zero_array(mem_ctx,
+ struct spoolss_Notify, num_msgs);
+ if (!notifies) {
+ return ENOMEM;
+ }
+
+ for (i = 0; i < num_msgs; i++) {
+
+ msg = &messages[i];
+
+ /* Are we monitoring this event? */
+
+ if (!is_monitoring_event(prn_hnd, msg->type, msg->field)) {
+ continue;
+ }
+
+ DEBUG(10, ("Sending message type [0x%x] field [0x%2x] "
+ "for printer [%s]\n",
+ msg->type, msg->field, prn_hnd->sharename));
+
+ /*
+ * if the is a printer notification handle and not a job
+ * notification type, then set the id to 0.
+ * Otherwise just use what was specified in the message.
+ *
+ * When registering change notification on a print server
+ * handle we always need to send back the id (snum) matching
+ * the printer for which the change took place.
+ * For change notify registered on a printer handle,
+ * this does not matter and the id should be 0.
+ *
+ * --jerry
+ */
+
+ if ((msg->type == PRINTER_NOTIFY_TYPE) &&
+ (prn_hnd->printer_type == SPLHND_PRINTER)) {
+ id = 0;
+ } else {
+ id = msg->id;
+ }
+
+ /* Convert unix jobid to smb jobid */
+
+ if (msg->flags & SPOOLSS_NOTIFY_MSG_UNIX_JOBID) {
+ id = sysjob_to_jobid(msg->id);
+
+ if (id == -1) {
+ DEBUG(3, ("no such unix jobid %d\n",
+ msg->id));
+ continue;
+ }
+ }
+
+ construct_info_data(&notifies[count],
+ msg->type, msg->field, id);
+
+ switch(msg->type) {
+ case PRINTER_NOTIFY_TYPE:
+ if (printer_notify_table[msg->field].fn) {
+ printer_notify_table[msg->field].fn(msg,
+ &notifies[count], mem_ctx);
+ }
+ break;
+
+ case JOB_NOTIFY_TYPE:
+ if (job_notify_table[msg->field].fn) {
+ job_notify_table[msg->field].fn(msg,
+ &notifies[count], mem_ctx);
+ }
+ break;
+
+ default:
+ DEBUG(5, ("Unknown notification type %d\n",
+ msg->type));
+ continue;
+ }
+
+ count++;
+ }
+
+ *_notifies = notifies;
+ *_count = count;
+
+ return 0;
+}
+
+static int send_notify2_printer(TALLOC_CTX *mem_ctx,
+ struct printer_handle *prn_hnd,
+ SPOOLSS_NOTIFY_MSG_GROUP *msg_group)
+{
+ struct spoolss_Notify *notifies;
+ size_t count = 0;
+ union spoolss_ReplyPrinterInfo info;
+ struct spoolss_NotifyInfo info0;
+ uint32_t reply_result;
+ NTSTATUS status;
+ WERROR werr;
+ int ret;
+
+ /* Is there notification on this handle? */
+ if (prn_hnd->notify.cli_chan == NULL ||
+ prn_hnd->notify.cli_chan->cli_pipe == NULL ||
+ prn_hnd->notify.cli_chan->cli_pipe->binding_handle == NULL ||
+ prn_hnd->notify.cli_chan->active_connections == 0) {
+ return 0;
+ }
+
+ DEBUG(10, ("Client connected! [\\\\%s\\%s]\n",
+ prn_hnd->servername, prn_hnd->sharename));
+
+ /* For this printer? Print servers always receive notifications. */
+ if ((prn_hnd->printer_type == SPLHND_PRINTER) &&
+ (!strequal(msg_group->printername, prn_hnd->sharename))) {
+ return 0;
+ }
+
+ DEBUG(10,("Our printer\n"));
+
+ /* build the array of change notifications */
+ ret = build_notify2_messages(mem_ctx, prn_hnd,
+ msg_group->msgs,
+ msg_group->num_msgs,
+ &notifies, &count);
+ if (ret) {
+ return ret;
+ }
+
+ info0.version = 0x2;
+ info0.flags = count ? 0x00020000 /* ??? */ : PRINTER_NOTIFY_INFO_DISCARDED;
+ info0.count = count;
+ info0.notifies = notifies;
+
+ info.info0 = &info0;
+
+ status = dcerpc_spoolss_RouterReplyPrinterEx(
+ prn_hnd->notify.cli_chan->cli_pipe->binding_handle,
+ mem_ctx,
+ &prn_hnd->notify.cli_hnd,
+ prn_hnd->notify.change, /* color */
+ prn_hnd->notify.flags,
+ &reply_result,
+ 0, /* reply_type, must be 0 */
+ info, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("dcerpc_spoolss_RouterReplyPrinterEx to client: %s "
+ "failed: %s\n",
+ prn_hnd->notify.cli_chan->cli_pipe->srv_name_slash,
+ nt_errstr(status)));
+ werr = ntstatus_to_werror(status);
+ } else if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(1, ("RouterReplyPrinterEx to client: %s "
+ "failed: %s\n",
+ prn_hnd->notify.cli_chan->cli_pipe->srv_name_slash,
+ win_errstr(werr)));
+ }
+ switch (reply_result) {
+ case 0:
+ break;
+ case PRINTER_NOTIFY_INFO_DISCARDED:
+ case PRINTER_NOTIFY_INFO_DISCARDNOTED:
+ case PRINTER_NOTIFY_INFO_COLOR_MISMATCH:
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void send_notify2_changes( SPOOLSS_NOTIFY_MSG_CTR *ctr, uint32_t idx )
+{
+ struct printer_handle *p;
+ TALLOC_CTX *mem_ctx = notify_ctr_getctx( ctr );
+ SPOOLSS_NOTIFY_MSG_GROUP *msg_group = notify_ctr_getgroup( ctr, idx );
+ int ret;
+
+ if ( !msg_group ) {
+ DEBUG(5,("send_notify2_changes() called with no msg group!\n"));
+ return;
+ }
+
+ if (!msg_group->msgs) {
+ DEBUG(5, ("send_notify2_changes() called with no messages!\n"));
+ return;
+ }
+
+ DEBUG(8,("send_notify2_changes: Enter...[%s]\n", msg_group->printername));
+
+ /* loop over all printers */
+
+ for (p = printers_list; p; p = p->next) {
+ ret = send_notify2_printer(mem_ctx, p, msg_group);
+ if (ret) {
+ goto done;
+ }
+ }
+
+done:
+ DEBUG(8,("send_notify2_changes: Exit...\n"));
+ return;
+}
+
+/***********************************************************************
+ **********************************************************************/
+
+static bool notify2_unpack_msg( SPOOLSS_NOTIFY_MSG *msg, struct timeval *tv, void *buf, size_t len )
+{
+
+ uint32_t tv_sec, tv_usec;
+ size_t offset = 0;
+
+ /* Unpack message */
+
+ offset += tdb_unpack((uint8_t *)buf + offset, len - offset, "f",
+ msg->printer);
+
+ offset += tdb_unpack((uint8_t *)buf + offset, len - offset, "ddddddd",
+ &tv_sec, &tv_usec,
+ &msg->type, &msg->field, &msg->id, &msg->len, &msg->flags);
+
+ if (msg->len == 0)
+ tdb_unpack((uint8_t *)buf + offset, len - offset, "dd",
+ &msg->notify.value[0], &msg->notify.value[1]);
+ else
+ tdb_unpack((uint8_t *)buf + offset, len - offset, "B",
+ &msg->len, &msg->notify.data);
+
+ DEBUG(3, ("notify2_unpack_msg: got NOTIFY2 message for printer %s, jobid %u type %d, field 0x%02x, flags 0x%04x\n",
+ msg->printer, (unsigned int)msg->id, msg->type, msg->field, msg->flags));
+
+ tv->tv_sec = tv_sec;
+ tv->tv_usec = tv_usec;
+
+ if (msg->len == 0)
+ DEBUG(3, ("notify2_unpack_msg: value1 = %d, value2 = %d\n", msg->notify.value[0],
+ msg->notify.value[1]));
+ else
+ dump_data(3, (uint8_t *)msg->notify.data, msg->len);
+
+ return true;
+}
+
+/********************************************************************
+ Receive a notify2 message list
+ ********************************************************************/
+
+static void receive_notify2_message_list(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ size_t msg_count, i, num_groups;
+ char *buf = (char *)data->data;
+ char *msg_ptr;
+ size_t msg_len;
+ SPOOLSS_NOTIFY_MSG notify;
+ SPOOLSS_NOTIFY_MSG_CTR messages;
+
+ if (data->length < 4) {
+ DEBUG(0,("receive_notify2_message_list: bad message format (len < 4)!\n"));
+ return;
+ }
+
+ msg_count = IVAL(buf, 0);
+ msg_ptr = buf + 4;
+
+ DEBUG(5, ("receive_notify2_message_list: got %lu messages in list\n", (unsigned long)msg_count));
+
+ if (msg_count == 0) {
+ DEBUG(0,("receive_notify2_message_list: bad message format (msg_count == 0) !\n"));
+ return;
+ }
+
+ /* initialize the container */
+
+ ZERO_STRUCT( messages );
+ notify_msg_ctr_init( &messages );
+
+ /*
+ * build message groups for each printer identified
+ * in a change_notify msg. Remember that a PCN message
+ * includes the handle returned for the srv_spoolss_replyopenprinter()
+ * call. Therefore messages are grouped according to printer handle.
+ */
+
+ for ( i=0; i<msg_count; i++ ) {
+ struct timeval msg_tv;
+
+ if (msg_ptr + 4 - buf > data->length) {
+ DEBUG(0,("receive_notify2_message_list: bad message format (len > buf_size) !\n"));
+ return;
+ }
+
+ msg_len = IVAL(msg_ptr,0);
+ msg_ptr += 4;
+
+ if (msg_ptr + msg_len - buf > data->length) {
+ DEBUG(0,("receive_notify2_message_list: bad message format (bad len) !\n"));
+ return;
+ }
+
+ /* unpack messages */
+
+ ZERO_STRUCT( notify );
+ notify2_unpack_msg( &notify, &msg_tv, msg_ptr, msg_len );
+ msg_ptr += msg_len;
+
+ /* add to correct list in container */
+
+ notify_msg_ctr_addmsg( &messages, &notify );
+
+ /* free memory that might have been allocated by notify2_unpack_msg() */
+
+ if ( notify.len != 0 )
+ SAFE_FREE( notify.notify.data );
+ }
+
+ /* process each group of messages */
+
+ num_groups = notify_msg_ctr_numgroups( &messages );
+ for ( i=0; i<num_groups; i++ )
+ send_notify2_changes( &messages, i );
+
+
+ /* cleanup */
+
+ DEBUG(10,("receive_notify2_message_list: processed %u messages\n",
+ (uint32_t)msg_count ));
+
+ notify_msg_ctr_destroy( &messages );
+
+ return;
+}
+
+/********************************************************************
+ Send a message to ourself about new driver being installed
+ so we can upgrade the information for each printer bound to this
+ driver
+ ********************************************************************/
+
+static bool srv_spoolss_drv_upgrade_printer(const char *drivername,
+ struct messaging_context *msg_ctx)
+{
+ int len = strlen(drivername);
+
+ if (!len)
+ return false;
+
+ DEBUG(10,("srv_spoolss_drv_upgrade_printer: Sending message about driver upgrade [%s]\n",
+ drivername));
+
+ messaging_send_buf(msg_ctx, messaging_server_id(msg_ctx),
+ MSG_PRINTER_DRVUPGRADE,
+ (const uint8_t *)drivername, len+1);
+
+ return true;
+}
+
+void srv_spoolss_cleanup(void)
+{
+ struct printer_session_counter *session_counter;
+
+ for (session_counter = counter_list;
+ session_counter != NULL;
+ session_counter = counter_list) {
+ DLIST_REMOVE(counter_list, session_counter);
+ TALLOC_FREE(session_counter);
+ }
+}
+
+/**********************************************************************
+ callback to receive a MSG_PRINTER_DRVUPGRADE message and iterate
+ over all printers, upgrading ones as necessary
+ This is now *ONLY* called inside the background lpq updater. JRA.
+ **********************************************************************/
+
+void do_drv_upgrade_printer(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ TALLOC_CTX *tmp_ctx;
+ const struct auth_session_info *session_info = get_session_info_system();
+ struct spoolss_PrinterInfo2 *pinfo2;
+ WERROR result;
+ const char *drivername;
+ int snum;
+ int n_services = lp_numservices();
+ struct dcerpc_binding_handle *b = NULL;
+
+ tmp_ctx = talloc_new(NULL);
+ if (!tmp_ctx) return;
+
+ drivername = talloc_strndup(tmp_ctx, (const char *)data->data, data->length);
+ if (!drivername) {
+ DEBUG(0, ("do_drv_upgrade_printer: Out of memory ?!\n"));
+ goto done;
+ }
+
+ DEBUG(10, ("do_drv_upgrade_printer: "
+ "Got message for new driver [%s]\n", drivername));
+
+ /* Iterate the printer list */
+
+ for (snum = 0; snum < n_services; snum++) {
+ if (!lp_snum_ok(snum) || !lp_printable(snum)) {
+ continue;
+ }
+
+ /* ignore [printers] share */
+ if (strequal(lp_const_servicename(snum), "printers")) {
+ continue;
+ }
+
+ if (b == NULL) {
+ result = winreg_printer_binding_handle(tmp_ctx,
+ session_info,
+ msg,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ break;
+ }
+ }
+
+ result = winreg_get_printer(tmp_ctx, b,
+ lp_const_servicename(snum),
+ &pinfo2);
+
+ if (!W_ERROR_IS_OK(result)) {
+ continue;
+ }
+
+ if (!pinfo2->drivername) {
+ continue;
+ }
+
+ if (strcmp(drivername, pinfo2->drivername) != 0) {
+ continue;
+ }
+
+ DEBUG(6,("Updating printer [%s]\n", pinfo2->printername));
+
+ /* all we care about currently is the change_id */
+ result = winreg_printer_update_changeid(tmp_ctx, b,
+ pinfo2->printername);
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3, ("do_drv_upgrade_printer: "
+ "Failed to update changeid [%s]\n",
+ win_errstr(result)));
+ }
+ }
+
+ /* all done */
+done:
+ talloc_free(tmp_ctx);
+}
+
+/********************************************************************
+ Update the cache for all printq's with a registered client
+ connection
+ ********************************************************************/
+
+void update_monitored_printq_cache(struct messaging_context *msg_ctx)
+{
+ struct printer_handle *printer = printers_list;
+ int snum;
+
+ /* loop through all printers and update the cache where
+ a client is connected */
+ while (printer) {
+ if ((printer->printer_type == SPLHND_PRINTER) &&
+ ((printer->notify.cli_chan != NULL) &&
+ (printer->notify.cli_chan->active_connections > 0))) {
+ snum = print_queue_snum(printer->sharename);
+ print_queue_status(msg_ctx, snum, NULL, NULL);
+ }
+
+ printer = printer->next;
+ }
+
+ return;
+}
+
+/****************************************************************
+ _spoolss_OpenPrinter
+****************************************************************/
+
+WERROR _spoolss_OpenPrinter(struct pipes_struct *p,
+ struct spoolss_OpenPrinter *r)
+{
+ struct spoolss_OpenPrinterEx e;
+ struct spoolss_UserLevel1 level1;
+ WERROR werr;
+
+ ZERO_STRUCT(level1);
+
+ e.in.printername = r->in.printername;
+ e.in.datatype = r->in.datatype;
+ e.in.devmode_ctr = r->in.devmode_ctr;
+ e.in.access_mask = r->in.access_mask;
+ e.in.userlevel_ctr.level = 1;
+ e.in.userlevel_ctr.user_info.level1 = &level1;
+
+ e.out.handle = r->out.handle;
+
+ werr = _spoolss_OpenPrinterEx(p, &e);
+
+ if (W_ERROR_EQUAL(werr, WERR_INVALID_PARAMETER)) {
+ /* OpenPrinterEx returns this for a bad
+ * printer name. We must return WERR_INVALID_PRINTER_NAME
+ * instead.
+ */
+ werr = WERR_INVALID_PRINTER_NAME;
+ }
+
+ return werr;
+}
+
+static WERROR copy_devicemode(TALLOC_CTX *mem_ctx,
+ struct spoolss_DeviceMode *orig,
+ struct spoolss_DeviceMode **dest)
+{
+ struct spoolss_DeviceMode *dm;
+
+ dm = talloc(mem_ctx, struct spoolss_DeviceMode);
+ if (!dm) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* copy all values, then duplicate strings and structs */
+ *dm = *orig;
+
+ dm->devicename = talloc_strdup(dm, orig->devicename);
+ if (!dm->devicename) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ dm->formname = talloc_strdup(dm, orig->formname);
+ if (!dm->formname) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (orig->driverextra_data.data) {
+ dm->driverextra_data.data =
+ (uint8_t *) talloc_memdup(dm, orig->driverextra_data.data,
+ orig->driverextra_data.length);
+ if (!dm->driverextra_data.data) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ *dest = dm;
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_OpenPrinterEx
+****************************************************************/
+
+WERROR _spoolss_OpenPrinterEx(struct pipes_struct *p,
+ struct spoolss_OpenPrinterEx *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ int snum;
+ char *raddr;
+ char *rhost;
+ struct printer_handle *Printer=NULL;
+ WERROR result;
+ int rc;
+
+ if (!r->in.printername) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!*r->in.printername) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (r->in.userlevel_ctr.level > 3) {
+ return WERR_INVALID_PARAMETER;
+ }
+ if ((r->in.userlevel_ctr.level == 1 && !r->in.userlevel_ctr.user_info.level1) ||
+ (r->in.userlevel_ctr.level == 2 && !r->in.userlevel_ctr.user_info.level2) ||
+ (r->in.userlevel_ctr.level == 3 && !r->in.userlevel_ctr.user_info.level3)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * The printcap printer share inventory is updated on client
+ * enumeration. For clients that do not perform enumeration prior to
+ * access, such as cupssmbadd, we reinitialise the printer share
+ * inventory on open as well.
+ */
+ become_root();
+ delete_and_reload_printers();
+ unbecome_root();
+
+ /* some sanity check because you can open a printer or a print server */
+ /* aka: \\server\printer or \\server */
+
+ DEBUGADD(3,("checking name: %s\n", r->in.printername));
+
+ result = open_printer_hnd(p, r->out.handle, r->in.printername, 0);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3,("_spoolss_OpenPrinterEx: Cannot open a printer handle "
+ "for printer %s\n", r->in.printername));
+ ZERO_STRUCTP(r->out.handle);
+ return result;
+ }
+
+ Printer = find_printer_index_by_hnd(p, r->out.handle);
+ if ( !Printer ) {
+ DEBUG(0,("_spoolss_OpenPrinterEx: logic error. Can't find printer "
+ "handle we created for printer %s\n", r->in.printername));
+ close_printer_handle(p, r->out.handle);
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * First case: the user is opening the print server:
+ *
+ * Disallow MS AddPrinterWizard if parameter disables it. A Win2k
+ * client 1st tries an OpenPrinterEx with access==0, MUST be allowed.
+ *
+ * Then both Win2k and WinNT clients try an OpenPrinterEx with
+ * SERVER_ALL_ACCESS, which we allow only if the user is root (uid=0)
+ * or if the user is listed in the smb.conf printer admin parameter.
+ *
+ * Then they try OpenPrinterEx with SERVER_READ which we allow. This lets the
+ * client view printer folder, but does not show the MSAPW.
+ *
+ * Note: this test needs code to check access rights here too. Jeremy
+ * could you look at this?
+ *
+ * Second case: the user is opening a printer:
+ * NT doesn't let us connect to a printer if the connecting user
+ * doesn't have print permission.
+ *
+ * Third case: user is opening a Port Monitor
+ * access checks same as opening a handle to the print server.
+ */
+
+ switch (Printer->printer_type )
+ {
+ case SPLHND_SERVER:
+ case SPLHND_PORTMON_TCP:
+ case SPLHND_PORTMON_LOCAL:
+ /* Printserver handles use global struct... */
+
+ snum = -1;
+
+ if (r->in.access_mask & SEC_FLAG_MAXIMUM_ALLOWED) {
+ r->in.access_mask |= SERVER_ACCESS_ADMINISTER;
+ r->in.access_mask |= SERVER_ACCESS_ENUMERATE;
+ }
+
+ /* Map standard access rights to object specific access rights */
+
+ se_map_standard(&r->in.access_mask,
+ &printserver_std_mapping);
+
+ /* Deny any object specific bits that don't apply to print
+ servers (i.e printer and job specific bits) */
+
+ r->in.access_mask &= SEC_MASK_SPECIFIC;
+
+ if (r->in.access_mask &
+ ~(SERVER_ACCESS_ADMINISTER | SERVER_ACCESS_ENUMERATE)) {
+ DEBUG(3, ("access DENIED for non-printserver bits\n"));
+ close_printer_handle(p, r->out.handle);
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Allow admin access */
+
+ if ( r->in.access_mask & SERVER_ACCESS_ADMINISTER )
+ {
+ if (!lp_show_add_printer_wizard()) {
+ close_printer_handle(p, r->out.handle);
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege,
+ and not a printer admin, then fail */
+
+ if ((session_info->unix_token->uid != sec_initial_uid()) &&
+ !security_token_has_privilege(
+ session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR) &&
+ !nt_token_check_sid(&global_sid_Builtin_Print_Operators,
+ session_info->security_token)) {
+ close_printer_handle(p, r->out.handle);
+ ZERO_STRUCTP(r->out.handle);
+ DEBUG(3,("access DENIED as user is not root, "
+ "has no printoperator privilege and is "
+ "not a member of the printoperator builtin group\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ r->in.access_mask = SERVER_ACCESS_ADMINISTER;
+ }
+ else
+ {
+ r->in.access_mask = SERVER_ACCESS_ENUMERATE;
+ }
+
+ DEBUG(4,("Setting print server access = %s\n", (r->in.access_mask == SERVER_ACCESS_ADMINISTER)
+ ? "SERVER_ACCESS_ADMINISTER" : "SERVER_ACCESS_ENUMERATE" ));
+
+ break;
+
+ case SPLHND_PRINTER:
+ /* NT doesn't let us connect to a printer if the connecting user
+ doesn't have print permission. */
+
+ if (!get_printer_snum(p, r->out.handle, &snum, NULL)) {
+ close_printer_handle(p, r->out.handle);
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (r->in.access_mask == SEC_FLAG_MAXIMUM_ALLOWED) {
+ r->in.access_mask = PRINTER_ACCESS_ADMINISTER;
+ }
+
+ se_map_standard(&r->in.access_mask, &printer_std_mapping);
+
+ /* map an empty access mask to the minimum access mask */
+ if (r->in.access_mask == 0x0)
+ r->in.access_mask = PRINTER_ACCESS_USE;
+
+ /*
+ * If we are not serving the printer driver for this printer,
+ * map PRINTER_ACCESS_ADMINISTER to PRINTER_ACCESS_USE. This
+ * will keep NT clients happy --jerry
+ */
+
+ if (lp_use_client_driver(snum)
+ && (r->in.access_mask & PRINTER_ACCESS_ADMINISTER))
+ {
+ r->in.access_mask = PRINTER_ACCESS_USE;
+ }
+
+ /* check smb.conf parameters and the the sec_desc */
+ raddr = tsocket_address_inet_addr_string(remote_address,
+ p->mem_ctx);
+ if (raddr == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ rc = get_remote_hostname(remote_address,
+ &rhost,
+ p->mem_ctx);
+ if (rc < 0) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (strequal(rhost, "UNKNOWN")) {
+ rhost = raddr;
+ }
+
+ if (!allow_access(lp_hosts_deny(snum), lp_hosts_allow(snum),
+ rhost, raddr)) {
+ DEBUG(3, ("access DENIED (hosts allow/deny) for printer open\n"));
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!user_ok_token(session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token, snum) ||
+ !W_ERROR_IS_OK(print_access_check(session_info,
+ p->msg_ctx,
+ snum,
+ r->in.access_mask))) {
+ DEBUG(3, ("access DENIED for printer open\n"));
+ close_printer_handle(p, r->out.handle);
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_ACCESS_DENIED;
+ }
+
+ if ((r->in.access_mask & SEC_MASK_SPECIFIC)& ~(PRINTER_ACCESS_ADMINISTER|PRINTER_ACCESS_USE)) {
+ DEBUG(3, ("access DENIED for printer open - unknown bits\n"));
+ close_printer_handle(p, r->out.handle);
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (r->in.access_mask & PRINTER_ACCESS_ADMINISTER)
+ r->in.access_mask = PRINTER_ACCESS_ADMINISTER;
+ else
+ r->in.access_mask = PRINTER_ACCESS_USE;
+
+ DEBUG(4,("Setting printer access = %s\n", (r->in.access_mask == PRINTER_ACCESS_ADMINISTER)
+ ? "PRINTER_ACCESS_ADMINISTER" : "PRINTER_ACCESS_USE" ));
+
+ winreg_create_printer_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_const_servicename(snum));
+
+ break;
+
+ default:
+ /* sanity check to prevent programmer error */
+ ZERO_STRUCTP(r->out.handle);
+ return WERR_INVALID_HANDLE;
+ }
+
+ Printer->access_granted = r->in.access_mask;
+
+ /*
+ * If the client sent a devmode in the OpenPrinter() call, then
+ * save it here in case we get a job submission on this handle
+ */
+
+ if ((Printer->printer_type != SPLHND_SERVER)
+ && (r->in.devmode_ctr.devmode != NULL)) {
+ copy_devicemode(NULL, r->in.devmode_ctr.devmode,
+ &Printer->devmode);
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_ClosePrinter
+****************************************************************/
+
+WERROR _spoolss_ClosePrinter(struct pipes_struct *p,
+ struct spoolss_ClosePrinter *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ if (Printer && Printer->document_started) {
+ struct spoolss_EndDocPrinter e;
+
+ e.in.handle = r->in.handle;
+
+ _spoolss_EndDocPrinter(p, &e);
+ }
+
+ if (!close_printer_handle(p, r->in.handle))
+ return WERR_INVALID_HANDLE;
+
+ /* clear the returned printer handle. Observed behavior
+ from Win2k server. Don't think this really matters.
+ Previous code just copied the value of the closed
+ handle. --jerry */
+
+ ZERO_STRUCTP(r->out.handle);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinter
+****************************************************************/
+
+WERROR _spoolss_DeletePrinter(struct pipes_struct *p,
+ struct spoolss_DeletePrinter *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ WERROR result;
+ int snum;
+
+ if (Printer && Printer->document_started) {
+ struct spoolss_EndDocPrinter e;
+
+ e.in.handle = r->in.handle;
+
+ _spoolss_EndDocPrinter(p, &e);
+ }
+
+ if (get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ winreg_delete_printer_key_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_const_servicename(snum),
+ "");
+ }
+
+ result = delete_printer_handle(p, r->in.handle);
+
+ return result;
+}
+
+/*******************************************************************
+ * static function to lookup the version id corresponding to an
+ * long architecture string
+ ******************************************************************/
+
+static const int drv_cversion[] = {SPOOLSS_DRIVER_VERSION_9X,
+ SPOOLSS_DRIVER_VERSION_NT35,
+ SPOOLSS_DRIVER_VERSION_NT4,
+ SPOOLSS_DRIVER_VERSION_200X,
+ -1};
+
+static int get_version_id(const char *arch)
+{
+ int i;
+
+ for (i=0; archi_table[i].long_archi != NULL; i++)
+ {
+ if (strcmp(arch, archi_table[i].long_archi) == 0)
+ return (archi_table[i].version);
+ }
+
+ return -1;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinterDriver
+****************************************************************/
+
+WERROR _spoolss_DeletePrinterDriver(struct pipes_struct *p,
+ struct spoolss_DeletePrinterDriver *r)
+{
+
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct spoolss_DriverInfo8 *info = NULL;
+ int version;
+ WERROR status;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int i;
+ bool found;
+
+ /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege,
+ and not a printer admin, then fail */
+
+ if ((session_info->unix_token->uid != sec_initial_uid()) &&
+ !security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (r->in.architecture == NULL || r->in.driver == NULL) {
+ return WERR_INVALID_ENVIRONMENT;
+ }
+
+ /* check that we have a valid driver name first */
+
+ if ((version = get_version_id(r->in.architecture)) == -1) {
+ return WERR_INVALID_ENVIRONMENT;
+ }
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ for (found = false, i = 0; drv_cversion[i] >= 0; i++) {
+ status = winreg_get_driver(tmp_ctx, b,
+ r->in.architecture, r->in.driver,
+ drv_cversion[i], &info);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(5, ("skipping del of driver with version %d\n",
+ drv_cversion[i]));
+ continue;
+ }
+ found = true;
+
+ if (printer_driver_in_use(tmp_ctx, b, info)) {
+ status = WERR_PRINTER_DRIVER_IN_USE;
+ goto done;
+ }
+
+ status = winreg_del_driver(tmp_ctx, b, info, drv_cversion[i]);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0, ("failed del of driver with version %d\n",
+ drv_cversion[i]));
+ goto done;
+ }
+ }
+ if (found == false) {
+ DEBUG(0, ("driver %s not found for deletion\n", r->in.driver));
+ status = WERR_UNKNOWN_PRINTER_DRIVER;
+ } else {
+ status = WERR_OK;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+
+ return status;
+}
+
+static WERROR spoolss_dpd_version(TALLOC_CTX *mem_ctx,
+ struct pipes_struct *p,
+ struct spoolss_DeletePrinterDriverEx *r,
+ struct dcerpc_binding_handle *b,
+ struct spoolss_DriverInfo8 *info)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ WERROR status;
+ bool delete_files;
+
+ if (printer_driver_in_use(mem_ctx, b, info)) {
+ status = WERR_PRINTER_DRIVER_IN_USE;
+ goto done;
+ }
+
+ /*
+ * we have a couple of cases to consider.
+ * (1) Are any files in use? If so and DPD_DELETE_ALL_FILES is set,
+ * then the delete should fail if **any** files overlap with
+ * other drivers
+ * (2) If DPD_DELETE_UNUSED_FILES is set, then delete all
+ * non-overlapping files
+ * (3) If neither DPD_DELETE_ALL_FILES nor DPD_DELETE_UNUSED_FILES
+ * are set, then do not delete any files
+ * Refer to MSDN docs on DeletePrinterDriverEx() for details.
+ */
+
+ delete_files = r->in.delete_flags
+ & (DPD_DELETE_ALL_FILES | DPD_DELETE_UNUSED_FILES);
+
+
+ if (delete_files) {
+ bool in_use = printer_driver_files_in_use(mem_ctx, b, info);
+ if (in_use && (r->in.delete_flags & DPD_DELETE_ALL_FILES)) {
+ status = WERR_PRINTER_DRIVER_IN_USE;
+ goto done;
+ }
+ /*
+ * printer_driver_files_in_use() has trimmed overlapping files
+ * from info so they are not removed on DPD_DELETE_UNUSED_FILES
+ */
+ }
+
+
+ status = winreg_del_driver(mem_ctx, b, info, info->version);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ /*
+ * now delete any associated files if delete_files is
+ * true. Even if this part fails, we return success
+ * because the driver does not exist any more
+ */
+ if (delete_files) {
+ delete_driver_files(session_info, info);
+ }
+
+done:
+ return status;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinterDriverEx
+****************************************************************/
+
+WERROR _spoolss_DeletePrinterDriverEx(struct pipes_struct *p,
+ struct spoolss_DeletePrinterDriverEx *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct spoolss_DriverInfo8 *info = NULL;
+ WERROR status;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int i;
+ bool found;
+
+ /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege,
+ and not a printer admin, then fail */
+
+ if ((session_info->unix_token->uid != sec_initial_uid()) &&
+ !security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (r->in.architecture == NULL || r->in.driver == NULL) {
+ return WERR_INVALID_ENVIRONMENT;
+ }
+
+ /* check that we have a valid driver name first */
+ if (get_version_id(r->in.architecture) == -1) {
+ /* this is what NT returns */
+ return WERR_INVALID_ENVIRONMENT;
+ }
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ for (found = false, i = 0; drv_cversion[i] >= 0; i++) {
+ if ((r->in.delete_flags & DPD_DELETE_SPECIFIC_VERSION)
+ && (drv_cversion[i] != r->in.version)) {
+ continue;
+ }
+
+ /* check if a driver with this version exists before delete */
+ status = winreg_get_driver(tmp_ctx, b,
+ r->in.architecture, r->in.driver,
+ drv_cversion[i], &info);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(5, ("skipping del of driver with version %d\n",
+ drv_cversion[i]));
+ continue;
+ }
+ found = true;
+
+ status = spoolss_dpd_version(tmp_ctx, p, r, b, info);
+ if (!W_ERROR_IS_OK(status)) {
+ DEBUG(0, ("failed to delete driver with version %d\n",
+ drv_cversion[i]));
+ goto done;
+ }
+ }
+ if (found == false) {
+ DEBUG(0, ("driver %s not found for deletion\n", r->in.driver));
+ status = WERR_UNKNOWN_PRINTER_DRIVER;
+ } else {
+ status = WERR_OK;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+
+/********************************************************************
+ GetPrinterData on a printer server Handle.
+********************************************************************/
+
+static WERROR getprinterdata_printer_server(TALLOC_CTX *mem_ctx,
+ const char *value,
+ enum winreg_Type *type,
+ union spoolss_PrinterData *data)
+{
+ DEBUG(8,("getprinterdata_printer_server:%s\n", value));
+
+ if (!strcasecmp_m(value, "W3SvcInstalled")) {
+ *type = REG_DWORD;
+ SIVAL(&data->value, 0, 0x00);
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "BeepEnabled")) {
+ *type = REG_DWORD;
+ SIVAL(&data->value, 0, 0x00);
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "EventLog")) {
+ *type = REG_DWORD;
+ /* formally was 0x1b */
+ SIVAL(&data->value, 0, 0x00);
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "NetPopup")) {
+ *type = REG_DWORD;
+ SIVAL(&data->value, 0, 0x00);
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "MajorVersion")) {
+ *type = REG_DWORD;
+
+ /* Windows NT 4.0 seems to not allow uploading of drivers
+ to a server that reports 0x3 as the MajorVersion.
+ need to investigate more how Win2k gets around this .
+ -- jerry */
+
+ if (RA_WINNT == get_remote_arch()) {
+ SIVAL(&data->value, 0, 0x02);
+ } else {
+ SIVAL(&data->value, 0, 0x03);
+ }
+
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "MinorVersion")) {
+ *type = REG_DWORD;
+ SIVAL(&data->value, 0, 0x00);
+ return WERR_OK;
+ }
+
+ /* REG_BINARY
+ * uint32_t size = 0x114
+ * uint32_t major = 5
+ * uint32_t minor = [0|1]
+ * uint32_t build = [2195|2600]
+ * extra unicode string = e.g. "Service Pack 3"
+ */
+ if (!strcasecmp_m(value, "OSVersion")) {
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ struct spoolss_OSVersion os;
+
+ /*
+ * Set the default OSVersion to:
+ *
+ * Windows Server 2003R2 SP2 (5.2.3790)
+ *
+ * used to be Windows 2000 (5.0.2195)
+ */
+ os.major = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss", "os_major",
+ GLOBAL_SPOOLSS_OS_MAJOR_DEFAULT);
+ os.minor = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss", "os_minor",
+ GLOBAL_SPOOLSS_OS_MINOR_DEFAULT);
+ os.build = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss", "os_build",
+ GLOBAL_SPOOLSS_OS_BUILD_DEFAULT);
+ os.extra_string = ""; /* leave extra string empty */
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, &os,
+ (ndr_push_flags_fn_t)ndr_push_spoolss_OSVersion);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_GEN_FAILURE;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(spoolss_OSVersion, &os);
+ }
+
+ *type = REG_BINARY;
+ data->binary = blob;
+
+ return WERR_OK;
+ }
+
+
+ if (!strcasecmp_m(value, "DefaultSpoolDirectory")) {
+ *type = REG_SZ;
+
+ data->string = talloc_strdup(mem_ctx, SPOOLSS_DEFAULT_SERVER_PATH);
+ W_ERROR_HAVE_NO_MEMORY(data->string);
+
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "Architecture")) {
+ *type = REG_SZ;
+ data->string = talloc_strdup(mem_ctx,
+ lp_parm_const_string(GLOBAL_SECTION_SNUM, "spoolss", "architecture", GLOBAL_SPOOLSS_ARCHITECTURE));
+ W_ERROR_HAVE_NO_MEMORY(data->string);
+
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "DsPresent")) {
+ *type = REG_DWORD;
+
+ /* only show the publish check box if we are a
+ member of a AD domain */
+
+ if (lp_security() == SEC_ADS) {
+ SIVAL(&data->value, 0, 0x01);
+ } else {
+ SIVAL(&data->value, 0, 0x00);
+ }
+ return WERR_OK;
+ }
+
+ if (!strcasecmp_m(value, "DNSMachineName")) {
+ const char *hostname = get_mydnsfullname();
+
+ if (!hostname) {
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ *type = REG_SZ;
+ data->string = talloc_strdup(mem_ctx, hostname);
+ W_ERROR_HAVE_NO_MEMORY(data->string);
+
+ return WERR_OK;
+ }
+
+ *type = REG_NONE;
+
+ return WERR_INVALID_PARAMETER;
+}
+
+/****************************************************************
+ _spoolss_GetPrinterData
+****************************************************************/
+
+WERROR _spoolss_GetPrinterData(struct pipes_struct *p,
+ struct spoolss_GetPrinterData *r)
+{
+ struct spoolss_GetPrinterDataEx r2;
+
+ r2.in.handle = r->in.handle;
+ r2.in.key_name = "PrinterDriverData";
+ r2.in.value_name = r->in.value_name;
+ r2.in.offered = r->in.offered;
+ r2.out.type = r->out.type;
+ r2.out.data = r->out.data;
+ r2.out.needed = r->out.needed;
+
+ return _spoolss_GetPrinterDataEx(p, &r2);
+}
+
+/*********************************************************
+ Connect to the client machine.
+**********************************************************/
+
+static bool spoolss_connect_to_client(struct rpc_pipe_client **pp_pipe, struct cli_state **pp_cli,
+ struct sockaddr_storage *client_ss, const char *remote_machine)
+{
+ NTSTATUS ret;
+ struct sockaddr_storage rm_addr;
+ char addr[INET6_ADDRSTRLEN];
+ struct cli_credentials *anon_creds = NULL;
+
+ if ( is_zero_addr(client_ss) ) {
+ DEBUG(2,("spoolss_connect_to_client: resolving %s\n",
+ remote_machine));
+ if ( !resolve_name( remote_machine, &rm_addr, 0x20, false) ) {
+ DEBUG(2,("spoolss_connect_to_client: Can't resolve address for %s\n", remote_machine));
+ return false;
+ }
+ print_sockaddr(addr, sizeof(addr), &rm_addr);
+ } else {
+ rm_addr = *client_ss;
+ print_sockaddr(addr, sizeof(addr), &rm_addr);
+ DEBUG(5,("spoolss_connect_to_client: Using address %s (no name resolution necessary)\n",
+ addr));
+ }
+
+ if (ismyaddr((struct sockaddr *)(void *)&rm_addr)) {
+ DEBUG(0,("spoolss_connect_to_client: Machine %s is one of our addresses. Cannot add to ourselves.\n",
+ addr));
+ return false;
+ }
+
+ anon_creds = cli_credentials_init_anon(NULL);
+ if (anon_creds == NULL) {
+ DBG_ERR("cli_credentials_init_anon() failed\n");
+ return false;
+ }
+
+ /* setup the connection */
+ ret = cli_full_connection_creds( pp_cli, lp_netbios_name(), remote_machine,
+ &rm_addr, 0, "IPC$", "IPC",
+ anon_creds,
+ CLI_FULL_CONNECTION_IPC);
+ TALLOC_FREE(anon_creds);
+ if ( !NT_STATUS_IS_OK( ret ) ) {
+ DEBUG(2,("spoolss_connect_to_client: connection to [%s] failed!\n",
+ remote_machine ));
+ return false;
+ }
+
+ if ( smbXcli_conn_protocol((*pp_cli)->conn) < PROTOCOL_NT1 ) {
+ DEBUG(0,("spoolss_connect_to_client: machine %s didn't negotiate NT protocol.\n", remote_machine));
+ cli_shutdown(*pp_cli);
+ return false;
+ }
+
+ /*
+ * Ok - we have an anonymous connection to the IPC$ share.
+ * Now start the NT Domain stuff :-).
+ */
+
+ ret = cli_rpc_pipe_open_noauth(*pp_cli, &ndr_table_spoolss, pp_pipe);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(2,("spoolss_connect_to_client: unable to open the spoolss pipe on machine %s. Error was : %s.\n",
+ remote_machine, nt_errstr(ret)));
+ cli_shutdown(*pp_cli);
+ return false;
+ }
+
+ return true;
+}
+
+/***************************************************************************
+ Connect to the client.
+****************************************************************************/
+
+static bool srv_spoolss_replyopenprinter(int snum, const char *printer,
+ uint32_t localprinter,
+ enum winreg_Type type,
+ struct policy_handle *handle,
+ struct notify_back_channel **_chan,
+ struct sockaddr_storage *client_ss,
+ struct messaging_context *msg_ctx)
+{
+ WERROR result;
+ NTSTATUS status;
+ struct notify_back_channel *chan;
+
+ for (chan = back_channels; chan; chan = chan->next) {
+ if (memcmp(&chan->client_address, client_ss,
+ sizeof(struct sockaddr_storage)) == 0) {
+ break;
+ }
+ }
+
+ /*
+ * If it's the first connection, contact the client
+ * and connect to the IPC$ share anonymously
+ */
+ if (!chan) {
+ fstring unix_printer;
+
+ /* the +2 is to strip the leading 2 backslashes */
+ fstrcpy(unix_printer, printer + 2);
+
+ chan = talloc_zero(NULL, struct notify_back_channel);
+ if (!chan) {
+ return false;
+ }
+ chan->client_address = *client_ss;
+
+ if (!spoolss_connect_to_client(&chan->cli_pipe, &chan->cli, client_ss, unix_printer)) {
+ TALLOC_FREE(chan);
+ return false;
+ }
+
+ DLIST_ADD(back_channels, chan);
+
+ messaging_register(msg_ctx, NULL, MSG_PRINTER_NOTIFY2,
+ receive_notify2_message_list);
+ }
+
+ if (chan->cli_pipe == NULL ||
+ chan->cli_pipe->binding_handle == NULL) {
+ DEBUG(0, ("srv_spoolss_replyopenprinter: error - "
+ "NULL %s for printer %s\n",
+ chan->cli_pipe == NULL ?
+ "chan->cli_pipe" : "chan->cli_pipe->binding_handle",
+ printer));
+ return false;
+ }
+
+ /*
+ * Tell the specific printing tdb we want messages for this printer
+ * by registering our PID.
+ */
+
+ if (!print_notify_register_pid(snum)) {
+ DEBUG(0, ("Failed to register our pid for printer %s\n",
+ printer));
+ }
+
+ status = dcerpc_spoolss_ReplyOpenPrinter(chan->cli_pipe->binding_handle,
+ talloc_tos(),
+ printer,
+ localprinter,
+ type,
+ 0,
+ NULL,
+ handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("dcerpc_spoolss_ReplyOpenPrinter returned [%s]\n", nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ } else if (!W_ERROR_IS_OK(result)) {
+ DEBUG(5, ("ReplyOpenPrinter returned [%s]\n", win_errstr(result)));
+ }
+
+ chan->active_connections++;
+ *_chan = chan;
+
+ return (W_ERROR_IS_OK(result));
+}
+
+/****************************************************************
+ ****************************************************************/
+
+static struct spoolss_NotifyOption *dup_spoolss_NotifyOption(TALLOC_CTX *mem_ctx,
+ const struct spoolss_NotifyOption *r)
+{
+ struct spoolss_NotifyOption *option;
+ uint32_t i,k;
+
+ if (!r) {
+ return NULL;
+ }
+
+ option = talloc_zero(mem_ctx, struct spoolss_NotifyOption);
+ if (!option) {
+ return NULL;
+ }
+
+ *option = *r;
+
+ if (!option->count) {
+ return option;
+ }
+
+ option->types = talloc_zero_array(option,
+ struct spoolss_NotifyOptionType, option->count);
+ if (!option->types) {
+ talloc_free(option);
+ return NULL;
+ }
+
+ for (i=0; i < option->count; i++) {
+ option->types[i] = r->types[i];
+
+ if (option->types[i].count) {
+ option->types[i].fields = talloc_zero_array(option,
+ union spoolss_Field, option->types[i].count);
+ if (!option->types[i].fields) {
+ talloc_free(option);
+ return NULL;
+ }
+ for (k=0; k<option->types[i].count; k++) {
+ option->types[i].fields[k] =
+ r->types[i].fields[k];
+ }
+ }
+ }
+
+ return option;
+}
+
+/****************************************************************
+ * _spoolss_RemoteFindFirstPrinterChangeNotifyEx
+ *
+ * before replying OK: status=0 a rpc call is made to the workstation
+ * asking ReplyOpenPrinter
+ *
+ * in fact ReplyOpenPrinter is the changenotify equivalent on the spoolss pipe
+ * called from api_spoolss_rffpcnex
+****************************************************************/
+
+WERROR _spoolss_RemoteFindFirstPrinterChangeNotifyEx(struct pipes_struct *p,
+ struct spoolss_RemoteFindFirstPrinterChangeNotifyEx *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ int snum = -1;
+ struct spoolss_NotifyOption *option = r->in.notify_options;
+ struct sockaddr_storage client_ss;
+ ssize_t client_len;
+
+ /* store the notify value in the printer struct */
+
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_RemoteFindFirstPrinterChangeNotifyEx: "
+ "Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ Printer->notify.flags = r->in.flags;
+ Printer->notify.options = r->in.options;
+ Printer->notify.printerlocal = r->in.printer_local;
+ Printer->notify.msg_ctx = p->msg_ctx;
+
+ TALLOC_FREE(Printer->notify.option);
+ Printer->notify.option = dup_spoolss_NotifyOption(Printer, option);
+
+ fstrcpy(Printer->notify.localmachine, r->in.local_machine);
+
+ /* Connect to the client machine and send a ReplyOpenPrinter */
+
+ if ( Printer->printer_type == SPLHND_SERVER)
+ snum = -1;
+ else if ( (Printer->printer_type == SPLHND_PRINTER) &&
+ !get_printer_snum(p, r->in.handle, &snum, NULL) )
+ return WERR_INVALID_HANDLE;
+
+ DEBUG(10,("_spoolss_RemoteFindFirstPrinterChangeNotifyEx: "
+ "remote_address is %s\n",
+ tsocket_address_string(remote_address, p->mem_ctx)));
+
+ if (!lp_print_notify_backchannel(snum)) {
+ DEBUG(10, ("_spoolss_RemoteFindFirstPrinterChangeNotifyEx: "
+ "backchannel disabled\n"));
+ return WERR_RPC_S_SERVER_UNAVAILABLE;
+ }
+
+ client_len = tsocket_address_bsd_sockaddr(remote_address,
+ (struct sockaddr *) &client_ss,
+ sizeof(struct sockaddr_storage));
+ if (client_len < 0) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if(!srv_spoolss_replyopenprinter(snum, Printer->notify.localmachine,
+ Printer->notify.printerlocal, REG_SZ,
+ &Printer->notify.cli_hnd,
+ &Printer->notify.cli_chan,
+ &client_ss, p->msg_ctx)) {
+ return WERR_RPC_S_SERVER_UNAVAILABLE;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the servername
+ ********************************************************************/
+
+static void spoolss_notify_server_name(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->servername);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the printername (not including the servername).
+ ********************************************************************/
+
+static void spoolss_notify_printer_name(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ /* the notify name should not contain the \\server\ part */
+ const char *p = strrchr(pinfo2->printername, '\\');
+
+ if (!p) {
+ p = pinfo2->printername;
+ } else {
+ p++;
+ }
+
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, p);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the servicename
+ ********************************************************************/
+
+static void spoolss_notify_share_name(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, lp_servicename(talloc_tos(), lp_sub, snum));
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the port name
+ ********************************************************************/
+
+static void spoolss_notify_port_name(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->portname);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the printername
+ * but it doesn't exist, have to see what to do
+ ********************************************************************/
+
+static void spoolss_notify_driver_name(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->drivername);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the comment
+ ********************************************************************/
+
+static void spoolss_notify_comment(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *p;
+
+ if (*pinfo2->comment == '\0') {
+ p = lp_comment(talloc_tos(), lp_sub, snum);
+ } else {
+ p = pinfo2->comment;
+ }
+
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, p);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the comment
+ * location = "Room 1, floor 2, building 3"
+ ********************************************************************/
+
+static void spoolss_notify_location(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ const char *loc = pinfo2->location;
+ NTSTATUS status;
+
+ status = printer_list_get_printer(mem_ctx,
+ pinfo2->sharename,
+ NULL,
+ &loc,
+ NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ if (loc == NULL) {
+ loc = pinfo2->location;
+ }
+ }
+
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, loc);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the device mode
+ * jfm:xxxx don't to it for know but that's a real problem !!!
+ ********************************************************************/
+
+static void spoolss_notify_devmode(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ /* for a dummy implementation we have to zero the fields */
+ SETUP_SPOOLSS_NOTIFY_DATA_DEVMODE(data, NULL);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the separator file name
+ ********************************************************************/
+
+static void spoolss_notify_sepfile(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->sepfile);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the print processor
+ * jfm:xxxx return always winprint to indicate we don't do anything to it
+ ********************************************************************/
+
+static void spoolss_notify_print_processor(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->printprocessor);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the print processor options
+ * jfm:xxxx send an empty string
+ ********************************************************************/
+
+static void spoolss_notify_parameters(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->parameters);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the data type
+ * jfm:xxxx always send RAW as data type
+ ********************************************************************/
+
+static void spoolss_notify_datatype(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, pinfo2->datatype);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the security descriptor
+ * jfm:xxxx send an null pointer to say no security desc
+ * have to implement security before !
+ ********************************************************************/
+
+static void spoolss_notify_security_desc(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ if (pinfo2->secdesc == NULL) {
+ data->data.sd.sd = NULL;
+ } else {
+ data->data.sd.sd = security_descriptor_copy(mem_ctx,
+ pinfo2->secdesc);
+ }
+ data->data.sd.sd_size = ndr_size_security_descriptor(data->data.sd.sd,
+ 0);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the attributes
+ * jfm:xxxx a samba printer is always shared
+ ********************************************************************/
+
+static void spoolss_notify_attributes(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->attributes);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the priority
+ ********************************************************************/
+
+static void spoolss_notify_priority(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->priority);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the default priority
+ ********************************************************************/
+
+static void spoolss_notify_default_priority(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->defaultpriority);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the start time
+ ********************************************************************/
+
+static void spoolss_notify_start_time(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->starttime);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the until time
+ ********************************************************************/
+
+static void spoolss_notify_until_time(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->untiltime);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the status
+ ********************************************************************/
+
+static void spoolss_notify_status(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ print_status_struct status;
+
+ print_queue_length(msg_ctx, snum, &status);
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, status.status);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the number of jobs queued
+ ********************************************************************/
+
+static void spoolss_notify_cjobs(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(
+ data, print_queue_length(msg_ctx, snum, NULL));
+}
+
+/*******************************************************************
+ * fill a notify_info_data with the average ppm
+ ********************************************************************/
+
+static void spoolss_notify_average_ppm(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ /* always respond 8 pages per minutes */
+ /* a little hard ! */
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, pinfo2->averageppm);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with username
+ ********************************************************************/
+
+static void spoolss_notify_username(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, queue->fs_user);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with job status
+ ********************************************************************/
+
+static void spoolss_notify_job_status(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, nt_printj_status(queue->status));
+}
+
+/*******************************************************************
+ * fill a notify_info_data with job name
+ ********************************************************************/
+
+static void spoolss_notify_job_name(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, queue->fs_file);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with job status
+ ********************************************************************/
+
+static void spoolss_notify_job_status_string(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ /*
+ * Now we're returning job status codes we just return a "" here. JRA.
+ */
+
+ const char *p = "";
+
+#if 0 /* NO LONGER NEEDED - JRA. 02/22/2001 */
+ p = "unknown";
+
+ switch (queue->status) {
+ case LPQ_QUEUED:
+ p = "Queued";
+ break;
+ case LPQ_PAUSED:
+ p = ""; /* NT provides the paused string */
+ break;
+ case LPQ_SPOOLING:
+ p = "Spooling";
+ break;
+ case LPQ_PRINTING:
+ p = "Printing";
+ break;
+ }
+#endif /* NO LONGER NEEDED. */
+
+ SETUP_SPOOLSS_NOTIFY_DATA_STRING(data, p);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with job time
+ ********************************************************************/
+
+static void spoolss_notify_job_time(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, 0);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with job size
+ ********************************************************************/
+
+static void spoolss_notify_job_size(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, queue->size);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with page info
+ ********************************************************************/
+static void spoolss_notify_total_pages(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, queue->page_count);
+}
+
+/*******************************************************************
+ * fill a notify_info_data with pages printed info.
+ ********************************************************************/
+static void spoolss_notify_pages_printed(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ /* Add code when back-end tracks this */
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, 0);
+}
+
+/*******************************************************************
+ Fill a notify_info_data with job position.
+ ********************************************************************/
+
+static void spoolss_notify_job_position(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ SETUP_SPOOLSS_NOTIFY_DATA_INTEGER(data, queue->sysjob);
+}
+
+/*******************************************************************
+ Fill a notify_info_data with submitted time.
+ ********************************************************************/
+
+static void spoolss_notify_submitted_time(struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx)
+{
+ data->data.string.string = NULL;
+ data->data.string.size = 0;
+
+ init_systemtime_buffer(mem_ctx, gmtime(&queue->time),
+ &data->data.string.string,
+ &data->data.string.size);
+
+}
+
+struct s_notify_info_data_table
+{
+ enum spoolss_NotifyType type;
+ uint16_t field;
+ const char *name;
+ enum spoolss_NotifyTable variable_type;
+ void (*fn) (struct messaging_context *msg_ctx,
+ int snum, struct spoolss_Notify *data,
+ print_queue_struct *queue,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ TALLOC_CTX *mem_ctx);
+};
+
+/* A table describing the various print notification constants and
+ whether the notification data is a pointer to a variable sized
+ buffer, a one value uint32_t or a two value uint32_t. */
+
+static const struct s_notify_info_data_table notify_info_data_table[] =
+{
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SERVER_NAME, "PRINTER_NOTIFY_FIELD_SERVER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_server_name },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRINTER_NAME, "PRINTER_NOTIFY_FIELD_PRINTER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_printer_name },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SHARE_NAME, "PRINTER_NOTIFY_FIELD_SHARE_NAME", NOTIFY_TABLE_STRING, spoolss_notify_share_name },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PORT_NAME, "PRINTER_NOTIFY_FIELD_PORT_NAME", NOTIFY_TABLE_STRING, spoolss_notify_port_name },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DRIVER_NAME, "PRINTER_NOTIFY_FIELD_DRIVER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_driver_name },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_COMMENT, "PRINTER_NOTIFY_FIELD_COMMENT", NOTIFY_TABLE_STRING, spoolss_notify_comment },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_LOCATION, "PRINTER_NOTIFY_FIELD_LOCATION", NOTIFY_TABLE_STRING, spoolss_notify_location },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DEVMODE, "PRINTER_NOTIFY_FIELD_DEVMODE", NOTIFY_TABLE_DEVMODE, spoolss_notify_devmode },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SEPFILE, "PRINTER_NOTIFY_FIELD_SEPFILE", NOTIFY_TABLE_STRING, spoolss_notify_sepfile },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR, "PRINTER_NOTIFY_FIELD_PRINT_PROCESSOR", NOTIFY_TABLE_STRING, spoolss_notify_print_processor },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PARAMETERS, "PRINTER_NOTIFY_FIELD_PARAMETERS", NOTIFY_TABLE_STRING, spoolss_notify_parameters },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DATATYPE, "PRINTER_NOTIFY_FIELD_DATATYPE", NOTIFY_TABLE_STRING, spoolss_notify_datatype },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR, "PRINTER_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NOTIFY_TABLE_SECURITYDESCRIPTOR, spoolss_notify_security_desc },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_ATTRIBUTES, "PRINTER_NOTIFY_FIELD_ATTRIBUTES", NOTIFY_TABLE_DWORD, spoolss_notify_attributes },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PRIORITY, "PRINTER_NOTIFY_FIELD_PRIORITY", NOTIFY_TABLE_DWORD, spoolss_notify_priority },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY, "PRINTER_NOTIFY_FIELD_DEFAULT_PRIORITY", NOTIFY_TABLE_DWORD, spoolss_notify_default_priority },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_START_TIME, "PRINTER_NOTIFY_FIELD_START_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_start_time },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_UNTIL_TIME, "PRINTER_NOTIFY_FIELD_UNTIL_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_until_time },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_STATUS, "PRINTER_NOTIFY_FIELD_STATUS", NOTIFY_TABLE_DWORD, spoolss_notify_status },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_STATUS_STRING, "PRINTER_NOTIFY_FIELD_STATUS_STRING", NOTIFY_TABLE_STRING, NULL },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_CJOBS, "PRINTER_NOTIFY_FIELD_CJOBS", NOTIFY_TABLE_DWORD, spoolss_notify_cjobs },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_AVERAGE_PPM, "PRINTER_NOTIFY_FIELD_AVERAGE_PPM", NOTIFY_TABLE_DWORD, spoolss_notify_average_ppm },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_TOTAL_PAGES, "PRINTER_NOTIFY_FIELD_TOTAL_PAGES", NOTIFY_TABLE_DWORD, NULL },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_PAGES_PRINTED, "PRINTER_NOTIFY_FIELD_PAGES_PRINTED", NOTIFY_TABLE_DWORD, NULL },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_TOTAL_BYTES, "PRINTER_NOTIFY_FIELD_TOTAL_BYTES", NOTIFY_TABLE_DWORD, NULL },
+{ PRINTER_NOTIFY_TYPE, PRINTER_NOTIFY_FIELD_BYTES_PRINTED, "PRINTER_NOTIFY_FIELD_BYTES_PRINTED", NOTIFY_TABLE_DWORD, NULL },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PRINTER_NAME, "JOB_NOTIFY_FIELD_PRINTER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_printer_name },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_MACHINE_NAME, "JOB_NOTIFY_FIELD_MACHINE_NAME", NOTIFY_TABLE_STRING, spoolss_notify_server_name },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PORT_NAME, "JOB_NOTIFY_FIELD_PORT_NAME", NOTIFY_TABLE_STRING, spoolss_notify_port_name },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_USER_NAME, "JOB_NOTIFY_FIELD_USER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_username },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_NOTIFY_NAME, "JOB_NOTIFY_FIELD_NOTIFY_NAME", NOTIFY_TABLE_STRING, spoolss_notify_username },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DATATYPE, "JOB_NOTIFY_FIELD_DATATYPE", NOTIFY_TABLE_STRING, spoolss_notify_datatype },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PRINT_PROCESSOR, "JOB_NOTIFY_FIELD_PRINT_PROCESSOR", NOTIFY_TABLE_STRING, spoolss_notify_print_processor },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PARAMETERS, "JOB_NOTIFY_FIELD_PARAMETERS", NOTIFY_TABLE_STRING, spoolss_notify_parameters },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DRIVER_NAME, "JOB_NOTIFY_FIELD_DRIVER_NAME", NOTIFY_TABLE_STRING, spoolss_notify_driver_name },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DEVMODE, "JOB_NOTIFY_FIELD_DEVMODE", NOTIFY_TABLE_DEVMODE, spoolss_notify_devmode },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_STATUS, "JOB_NOTIFY_FIELD_STATUS", NOTIFY_TABLE_DWORD, spoolss_notify_job_status },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_STATUS_STRING, "JOB_NOTIFY_FIELD_STATUS_STRING", NOTIFY_TABLE_STRING, spoolss_notify_job_status_string },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR, "JOB_NOTIFY_FIELD_SECURITY_DESCRIPTOR", NOTIFY_TABLE_SECURITYDESCRIPTOR, NULL },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_DOCUMENT, "JOB_NOTIFY_FIELD_DOCUMENT", NOTIFY_TABLE_STRING, spoolss_notify_job_name },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PRIORITY, "JOB_NOTIFY_FIELD_PRIORITY", NOTIFY_TABLE_DWORD, spoolss_notify_priority },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_POSITION, "JOB_NOTIFY_FIELD_POSITION", NOTIFY_TABLE_DWORD, spoolss_notify_job_position },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_SUBMITTED, "JOB_NOTIFY_FIELD_SUBMITTED", NOTIFY_TABLE_TIME, spoolss_notify_submitted_time },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_START_TIME, "JOB_NOTIFY_FIELD_START_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_start_time },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_UNTIL_TIME, "JOB_NOTIFY_FIELD_UNTIL_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_until_time },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_TIME, "JOB_NOTIFY_FIELD_TIME", NOTIFY_TABLE_DWORD, spoolss_notify_job_time },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_TOTAL_PAGES, "JOB_NOTIFY_FIELD_TOTAL_PAGES", NOTIFY_TABLE_DWORD, spoolss_notify_total_pages },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_PAGES_PRINTED, "JOB_NOTIFY_FIELD_PAGES_PRINTED", NOTIFY_TABLE_DWORD, spoolss_notify_pages_printed },
+{ JOB_NOTIFY_TYPE, JOB_NOTIFY_FIELD_TOTAL_BYTES, "JOB_NOTIFY_FIELD_TOTAL_BYTES", NOTIFY_TABLE_DWORD, spoolss_notify_job_size },
+};
+
+/*******************************************************************
+ Return the variable_type of info_data structure.
+********************************************************************/
+
+static enum spoolss_NotifyTable variable_type_of_notify_info_data(enum spoolss_NotifyType type,
+ uint16_t field)
+{
+ int i=0;
+
+ for (i = 0; i < ARRAY_SIZE(notify_info_data_table); i++) {
+ if ( (notify_info_data_table[i].type == type) &&
+ (notify_info_data_table[i].field == field) ) {
+ return notify_info_data_table[i].variable_type;
+ }
+ }
+
+ DEBUG(5, ("invalid notify data type %d/%d\n", type, field));
+
+ return (enum spoolss_NotifyTable) 0;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static bool search_notify(enum spoolss_NotifyType type,
+ uint16_t field,
+ int *value)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(notify_info_data_table); i++) {
+ if (notify_info_data_table[i].type == type &&
+ notify_info_data_table[i].field == field &&
+ notify_info_data_table[i].fn != NULL) {
+ *value = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void construct_info_data(struct spoolss_Notify *info_data,
+ enum spoolss_NotifyType type,
+ uint16_t field, int id)
+{
+ info_data->type = type;
+ info_data->field.field = field;
+ info_data->variable_type = variable_type_of_notify_info_data(type, field);
+ info_data->job_id = id;
+}
+
+/*******************************************************************
+ *
+ * fill a notify_info struct with info asked
+ *
+ ********************************************************************/
+
+static bool construct_notify_printer_info(struct messaging_context *msg_ctx,
+ struct printer_handle *print_hnd,
+ struct spoolss_NotifyInfo *info,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ int snum,
+ const struct spoolss_NotifyOptionType *option_type,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int field_num,j;
+ enum spoolss_NotifyType type;
+ uint16_t field;
+
+ struct spoolss_Notify *current_data;
+
+ type = option_type->type;
+
+ DEBUG(4,("construct_notify_printer_info: Notify type: [%s], number of notify info: [%d] on printer: [%s]\n",
+ (type == PRINTER_NOTIFY_TYPE ? "PRINTER_NOTIFY_TYPE" : "JOB_NOTIFY_TYPE"),
+ option_type->count, lp_servicename(talloc_tos(), lp_sub, snum)));
+
+ for(field_num=0; field_num < option_type->count; field_num++) {
+ field = option_type->fields[field_num].field;
+
+ DEBUG(4,("construct_notify_printer_info: notify [%d]: type [%x], field [%x]\n", field_num, type, field));
+
+ if (!search_notify(type, field, &j) )
+ continue;
+
+ info->notifies = talloc_realloc(info, info->notifies,
+ struct spoolss_Notify,
+ info->count + 1);
+ if (info->notifies == NULL) {
+ DEBUG(2,("construct_notify_printer_info: failed to enlarge buffer info->data!\n"));
+ return false;
+ }
+
+ current_data = &info->notifies[info->count];
+
+ construct_info_data(current_data, type, field, id);
+
+ DEBUG(10, ("construct_notify_printer_info: "
+ "calling [%s] snum=%d printername=[%s])\n",
+ notify_info_data_table[j].name, snum,
+ pinfo2->printername));
+
+ notify_info_data_table[j].fn(msg_ctx, snum, current_data,
+ NULL, pinfo2, mem_ctx);
+
+ info->count++;
+ }
+
+ return true;
+}
+
+/*******************************************************************
+ *
+ * fill a notify_info struct with info asked
+ *
+ ********************************************************************/
+
+static bool construct_notify_jobs_info(struct messaging_context *msg_ctx,
+ print_queue_struct *queue,
+ struct spoolss_NotifyInfo *info,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ int snum,
+ const struct spoolss_NotifyOptionType *option_type,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ int field_num,j;
+ enum spoolss_NotifyType type;
+ uint16_t field;
+ struct spoolss_Notify *current_data;
+
+ DEBUG(4,("construct_notify_jobs_info\n"));
+
+ type = option_type->type;
+
+ DEBUGADD(4,("Notify type: [%s], number of notify info: [%d]\n",
+ (type == PRINTER_NOTIFY_TYPE ? "PRINTER_NOTIFY_TYPE" : "JOB_NOTIFY_TYPE"),
+ option_type->count));
+
+ for(field_num=0; field_num<option_type->count; field_num++) {
+ field = option_type->fields[field_num].field;
+
+ if (!search_notify(type, field, &j) )
+ continue;
+
+ info->notifies = talloc_realloc(info, info->notifies,
+ struct spoolss_Notify,
+ info->count + 1);
+ if (info->notifies == NULL) {
+ DEBUG(2,("construct_notify_jobs_info: failed to enlarge buffer info->data!\n"));
+ return false;
+ }
+
+ current_data=&(info->notifies[info->count]);
+
+ construct_info_data(current_data, type, field, id);
+ notify_info_data_table[j].fn(msg_ctx, snum, current_data,
+ queue, pinfo2, mem_ctx);
+ info->count++;
+ }
+
+ return true;
+}
+
+/*
+ * JFM: The enumeration is not that simple, it's even non obvious.
+ *
+ * let's take an example: I want to monitor the PRINTER SERVER for
+ * the printer's name and the number of jobs currently queued.
+ * So in the NOTIFY_OPTION, I have one NOTIFY_OPTION_TYPE structure.
+ * Its type is PRINTER_NOTIFY_TYPE and it has 2 fields NAME and CJOBS.
+ *
+ * I have 3 printers on the back of my server.
+ *
+ * Now the response is a NOTIFY_INFO structure, with 6 NOTIFY_INFO_DATA
+ * structures.
+ * Number Data Id
+ * 1 printer 1 name 1
+ * 2 printer 1 cjob 1
+ * 3 printer 2 name 2
+ * 4 printer 2 cjob 2
+ * 5 printer 3 name 3
+ * 6 printer 3 name 3
+ *
+ * that's the print server case, the printer case is even worse.
+ */
+
+/*******************************************************************
+ *
+ * enumerate all printers on the printserver
+ * fill a notify_info struct with info asked
+ *
+ ********************************************************************/
+
+static WERROR printserver_notify_info(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ struct spoolss_NotifyInfo *info,
+ TALLOC_CTX *mem_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd);
+ int n_services=lp_numservices();
+ int i;
+ struct spoolss_NotifyOption *option;
+ struct spoolss_NotifyOptionType option_type;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+
+ DEBUG(4,("printserver_notify_info\n"));
+
+ if (!Printer)
+ return WERR_INVALID_HANDLE;
+
+ option = Printer->notify.option;
+
+ info->version = 2;
+ info->notifies = NULL;
+ info->count = 0;
+
+ /* a bug in xp sp2 rc2 causes it to send a fnpcn request without
+ sending a ffpcn() request first */
+
+ if ( !option )
+ return WERR_INVALID_HANDLE;
+
+ for (i=0; i<option->count; i++) {
+ option_type = option->types[i];
+
+ if (option_type.type != PRINTER_NOTIFY_TYPE)
+ continue;
+
+ for (snum = 0; snum < n_services; snum++) {
+ if (!lp_browseable(snum) ||
+ !lp_snum_ok(snum) ||
+ !lp_printable(snum)) {
+ continue; /* skip */
+ }
+
+ /* Maybe we should use the SYSTEM session_info here... */
+ result = winreg_get_printer_internal(mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(4, ("printserver_notify_info: "
+ "Failed to get printer [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, snum)));
+ continue;
+ }
+
+
+ construct_notify_printer_info(p->msg_ctx,
+ Printer, info,
+ pinfo2, snum,
+ &option_type, snum,
+ mem_ctx);
+
+ TALLOC_FREE(pinfo2);
+ }
+ }
+
+#if 0
+ /*
+ * Debugging information, don't delete.
+ */
+
+ DEBUG(1,("dumping the NOTIFY_INFO\n"));
+ DEBUGADD(1,("info->version:[%d], info->flags:[%d], info->count:[%d]\n", info->version, info->flags, info->count));
+ DEBUGADD(1,("num\ttype\tfield\tres\tid\tsize\tenc_type\n"));
+
+ for (i=0; i<info->count; i++) {
+ DEBUGADD(1,("[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\n",
+ i, info->data[i].type, info->data[i].field, info->data[i].reserved,
+ info->data[i].id, info->data[i].size, info->data[i].enc_type));
+ }
+#endif
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ *
+ * fill a notify_info struct with info asked
+ *
+ ********************************************************************/
+
+static WERROR printer_notify_info(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ struct spoolss_NotifyInfo *info,
+ TALLOC_CTX *mem_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, hnd);
+ int i;
+ uint32_t id;
+ struct spoolss_NotifyOption *option;
+ struct spoolss_NotifyOptionType option_type;
+ int count,j;
+ print_queue_struct *queue=NULL;
+ print_status_struct status;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+ struct tdb_print_db *pdb;
+
+ DEBUG(4,("printer_notify_info\n"));
+
+ if (!Printer)
+ return WERR_INVALID_HANDLE;
+
+ option = Printer->notify.option;
+ id = 0x0;
+
+ info->version = 2;
+ info->notifies = NULL;
+ info->count = 0;
+
+ /* a bug in xp sp2 rc2 causes it to send a fnpcn request without
+ sending a ffpcn() request first */
+
+ if ( !option )
+ return WERR_INVALID_HANDLE;
+
+ if (!get_printer_snum(p, hnd, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ pdb = get_print_db_byname(Printer->sharename);
+ if (pdb == NULL) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* Maybe we should use the SYSTEM session_info here... */
+ result = winreg_get_printer_internal(mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum), &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ result = WERR_INVALID_HANDLE;
+ goto err_pdb_drop;
+ }
+
+ /*
+ * When sending a PRINTER_NOTIFY_FIELD_SERVER_NAME we should send the
+ * correct servername.
+ */
+ pinfo2->servername = talloc_strdup(pinfo2, Printer->servername);
+ if (pinfo2->servername == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto err_pdb_drop;
+ }
+
+ for (i = 0; i < option->count; i++) {
+ option_type = option->types[i];
+
+ switch (option_type.type) {
+ case PRINTER_NOTIFY_TYPE:
+ if (construct_notify_printer_info(p->msg_ctx,
+ Printer, info,
+ pinfo2, snum,
+ &option_type, id,
+ mem_ctx)) {
+ id--;
+ }
+ break;
+
+ case JOB_NOTIFY_TYPE:
+
+ count = print_queue_status(p->msg_ctx, snum, &queue,
+ &status);
+
+ for (j = 0; j < count; j++) {
+ uint32_t jobid;
+ jobid = sysjob_to_jobid_pdb(pdb,
+ queue[j].sysjob);
+ if (jobid == (uint32_t)-1) {
+ DEBUG(2, ("ignoring untracked job %d\n",
+ queue[j].sysjob));
+ continue;
+ }
+ /* FIXME check return value */
+ construct_notify_jobs_info(p->msg_ctx,
+ &queue[j], info,
+ pinfo2, snum,
+ &option_type,
+ jobid,
+ mem_ctx);
+ }
+
+ SAFE_FREE(queue);
+ break;
+ }
+ }
+
+ /*
+ * Debugging information, don't delete.
+ */
+ /*
+ DEBUG(1,("dumping the NOTIFY_INFO\n"));
+ DEBUGADD(1,("info->version:[%d], info->flags:[%d], info->count:[%d]\n", info->version, info->flags, info->count));
+ DEBUGADD(1,("num\ttype\tfield\tres\tid\tsize\tenc_type\n"));
+
+ for (i=0; i<info->count; i++) {
+ DEBUGADD(1,("[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\t[%d]\n",
+ i, info->data[i].type, info->data[i].field, info->data[i].reserved,
+ info->data[i].id, info->data[i].size, info->data[i].enc_type));
+ }
+ */
+
+ talloc_free(pinfo2);
+ result = WERR_OK;
+err_pdb_drop:
+ release_print_db(pdb);
+ return result;
+}
+
+/****************************************************************
+ _spoolss_RouterRefreshPrinterChangeNotify
+****************************************************************/
+
+WERROR _spoolss_RouterRefreshPrinterChangeNotify(struct pipes_struct *p,
+ struct spoolss_RouterRefreshPrinterChangeNotify *r)
+{
+ struct spoolss_NotifyInfo *info;
+
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ WERROR result = WERR_INVALID_HANDLE;
+
+ /* we always have a spoolss_NotifyInfo struct */
+ info = talloc_zero(p->mem_ctx, struct spoolss_NotifyInfo);
+ if (!info) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ *r->out.info = info;
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_RouterRefreshPrinterChangeNotify: "
+ "Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ goto done;
+ }
+
+ DEBUG(4,("Printer type %x\n",Printer->printer_type));
+
+ /*
+ * We are now using the change value, and
+ * I should check for PRINTER_NOTIFY_OPTIONS_REFRESH but as
+ * I don't have a global notification system, I'm sending back all the
+ * information even when _NOTHING_ has changed.
+ */
+
+ /* We need to keep track of the change value to send back in
+ RRPCN replies otherwise our updates are ignored. */
+
+ Printer->notify.fnpcn = true;
+
+ if (Printer->notify.cli_chan != NULL &&
+ Printer->notify.cli_chan->active_connections > 0) {
+ DEBUG(10,("_spoolss_RouterRefreshPrinterChangeNotify: "
+ "Saving change value in request [%x]\n",
+ r->in.change_low));
+ Printer->notify.change = r->in.change_low;
+ }
+
+ /* just ignore the spoolss_NotifyOption */
+
+ switch (Printer->printer_type) {
+ case SPLHND_SERVER:
+ result = printserver_notify_info(p, r->in.handle,
+ info, p->mem_ctx);
+ break;
+
+ case SPLHND_PRINTER:
+ result = printer_notify_info(p, r->in.handle,
+ info, p->mem_ctx);
+ break;
+ }
+
+ Printer->notify.fnpcn = false;
+
+done:
+ return result;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static WERROR create_printername(TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *printername,
+ const char **printername_p)
+{
+ /* FIXME: add lp_force_printername() */
+
+ if (servername == NULL) {
+ *printername_p = talloc_strdup(mem_ctx, printername);
+ W_ERROR_HAVE_NO_MEMORY(*printername_p);
+ return WERR_OK;
+ }
+
+ if (servername[0] == '\\' && servername[1] == '\\') {
+ servername += 2;
+ }
+
+ *printername_p = talloc_asprintf(mem_ctx, "\\\\%s\\%s", servername, printername);
+ W_ERROR_HAVE_NO_MEMORY(*printername_p);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static void compose_devicemode_devicename(struct spoolss_DeviceMode *dm,
+ const char *printername)
+{
+ if (dm == NULL) {
+ return;
+ }
+
+ dm->devicename = talloc_strndup(dm, printername,
+ MIN(strlen(printername), 31));
+}
+
+/********************************************************************
+ * construct_printer_info_0
+ * fill a printer_info_0 struct
+ ********************************************************************/
+
+static WERROR construct_printer_info0(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_PrinterInfo2 *info2,
+ const char *servername,
+ struct spoolss_PrinterInfo0 *r,
+ int snum)
+{
+ int count;
+ struct printer_session_counter *session_counter;
+ struct timeval setuptime;
+ print_status_struct status;
+ WERROR result;
+ int os_major, os_minor, os_build;
+ const char *architecture;
+ uint32_t processor_architecture, processor_type;
+
+ result = create_printername(mem_ctx, servername, info2->printername, &r->printername);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ if (servername) {
+ r->servername = talloc_strdup(mem_ctx, servername);
+ W_ERROR_HAVE_NO_MEMORY(r->servername);
+ } else {
+ r->servername = NULL;
+ }
+
+ count = print_queue_length(msg_ctx, snum, &status);
+
+ /* check if we already have a counter for this printer */
+ for (session_counter = counter_list; session_counter; session_counter = session_counter->next) {
+ if (session_counter->snum == snum)
+ break;
+ }
+
+ /* it's the first time, add it to the list */
+ if (session_counter == NULL) {
+ session_counter = talloc_zero(counter_list, struct printer_session_counter);
+ W_ERROR_HAVE_NO_MEMORY(session_counter);
+ session_counter->snum = snum;
+ session_counter->counter = 0;
+ DLIST_ADD(counter_list, session_counter);
+ }
+
+ /* increment it */
+ session_counter->counter++;
+
+ r->cjobs = count;
+ r->total_jobs = 0;
+ r->total_bytes = 0;
+
+ get_startup_time(&setuptime);
+ init_systemtime(&r->time, gmtime(&setuptime.tv_sec));
+
+ /* JFM:
+ * the global_counter should be stored in a TDB as it's common to all the clients
+ * and should be zeroed on samba startup
+ */
+ r->global_counter = session_counter->counter;
+ r->total_pages = 0;
+
+ /* in 2.2 we reported ourselves as 0x0004 and 0x0565 */
+ os_major = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss", "os_major",
+ GLOBAL_SPOOLSS_OS_MAJOR_DEFAULT);
+ os_minor = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss", "os_minor",
+ GLOBAL_SPOOLSS_OS_MINOR_DEFAULT);
+ os_build = lp_parm_int(GLOBAL_SECTION_SNUM,
+ "spoolss", "os_build",
+ GLOBAL_SPOOLSS_OS_BUILD_DEFAULT);
+
+ SCVAL(&r->version, 0, os_major);
+ SCVAL(&r->version, 1, os_minor);
+ SSVAL(&r->version, 2, os_build);
+
+ architecture = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+ "spoolss",
+ "architecture",
+ GLOBAL_SPOOLSS_ARCHITECTURE);
+
+ if (strequal(architecture, SPOOLSS_ARCHITECTURE_x64)) {
+ processor_architecture = PROCESSOR_ARCHITECTURE_AMD64;
+ processor_type = PROCESSOR_AMD_X8664;
+ } else if (strequal(architecture, SPOOLSS_ARCHITECTURE_ARM64)) {
+ processor_architecture = PROCESSOR_ARCHITECTURE_ARM64;
+ processor_type = PROCESSOR_ARM820;
+ } else {
+ processor_architecture = PROCESSOR_ARCHITECTURE_INTEL;
+ processor_type = PROCESSOR_INTEL_PENTIUM;
+ }
+
+ r->free_build = SPOOLSS_RELEASE_BUILD;
+ r->spooling = 0;
+ r->max_spooling = 0;
+ r->session_counter = session_counter->counter;
+ r->num_error_out_of_paper = 0x0;
+ r->num_error_not_ready = 0x0; /* number of print failure */
+ r->job_error = 0x0;
+ r->number_of_processors = 0x1;
+ r->processor_type = processor_type;
+ r->high_part_total_bytes = 0x0;
+
+ /* ChangeID in milliseconds*/
+ winreg_printer_get_changeid_internal(mem_ctx, session_info, msg_ctx,
+ info2->sharename, &r->change_id);
+
+ r->last_error = WERR_OK;
+ r->status = nt_printq_status(status.status);
+ r->enumerate_network_printers = 0x0;
+ r->c_setprinter = 0x0;
+ r->processor_architecture = processor_architecture;
+ r->processor_level = 0x6; /* 6 ???*/
+ r->ref_ic = 0;
+ r->reserved2 = 0;
+ r->reserved3 = 0;
+
+ return WERR_OK;
+}
+
+
+/********************************************************************
+ * construct_printer_info1
+ * fill a spoolss_PrinterInfo1 struct
+********************************************************************/
+
+static WERROR construct_printer_info1(TALLOC_CTX *mem_ctx,
+ const struct spoolss_PrinterInfo2 *info2,
+ uint32_t flags,
+ const char *servername,
+ struct spoolss_PrinterInfo1 *r,
+ int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ WERROR result;
+
+ r->flags = flags;
+
+ if (info2->comment == NULL || info2->comment[0] == '\0') {
+ r->comment = lp_comment(mem_ctx, lp_sub, snum);
+ } else {
+ r->comment = talloc_strdup(mem_ctx, info2->comment); /* saved comment */
+ }
+ W_ERROR_HAVE_NO_MEMORY(r->comment);
+
+ result = create_printername(mem_ctx, servername, info2->printername, &r->name);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ r->description = talloc_asprintf(mem_ctx, "%s,%s,%s",
+ r->name,
+ info2->drivername,
+ r->comment);
+ W_ERROR_HAVE_NO_MEMORY(r->description);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * construct_printer_info2
+ * fill a spoolss_PrinterInfo2 struct
+********************************************************************/
+
+static WERROR construct_printer_info2(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct spoolss_PrinterInfo2 *info2,
+ const char *servername,
+ struct spoolss_PrinterInfo2 *r,
+ int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int count;
+ print_status_struct status;
+ WERROR result;
+
+ count = print_queue_length(msg_ctx, snum, &status);
+
+ if (servername) {
+ r->servername = talloc_strdup(mem_ctx, servername);
+ W_ERROR_HAVE_NO_MEMORY(r->servername);
+ } else {
+ r->servername = NULL;
+ }
+
+ result = create_printername(mem_ctx, servername, info2->printername, &r->printername);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ r->sharename = lp_servicename(mem_ctx, lp_sub, snum);
+ W_ERROR_HAVE_NO_MEMORY(r->sharename);
+ r->portname = talloc_strdup(mem_ctx, info2->portname);
+ W_ERROR_HAVE_NO_MEMORY(r->portname);
+ r->drivername = talloc_strdup(mem_ctx, info2->drivername);
+ W_ERROR_HAVE_NO_MEMORY(r->drivername);
+
+ if (info2->comment[0] == '\0') {
+ r->comment = lp_comment(mem_ctx, lp_sub, snum);
+ } else {
+ r->comment = talloc_strdup(mem_ctx, info2->comment);
+ }
+ W_ERROR_HAVE_NO_MEMORY(r->comment);
+
+ r->location = talloc_strdup(mem_ctx, info2->location);
+ if (info2->location[0] == '\0') {
+ const char *loc = NULL;
+ NTSTATUS nt_status;
+
+ nt_status = printer_list_get_printer(mem_ctx,
+ info2->sharename,
+ NULL,
+ &loc,
+ NULL);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ if (loc != NULL) {
+ r->location = talloc_strdup(mem_ctx, loc);
+ }
+ }
+ }
+ W_ERROR_HAVE_NO_MEMORY(r->location);
+
+ r->sepfile = talloc_strdup(mem_ctx, info2->sepfile);
+ W_ERROR_HAVE_NO_MEMORY(r->sepfile);
+ r->printprocessor = talloc_strdup(mem_ctx, info2->printprocessor);
+ W_ERROR_HAVE_NO_MEMORY(r->printprocessor);
+ r->datatype = talloc_strdup(mem_ctx, info2->datatype);
+ W_ERROR_HAVE_NO_MEMORY(r->datatype);
+ r->parameters = talloc_strdup(mem_ctx, info2->parameters);
+ W_ERROR_HAVE_NO_MEMORY(r->parameters);
+
+ r->attributes = info2->attributes;
+
+ r->priority = info2->priority;
+ r->defaultpriority = info2->defaultpriority;
+ r->starttime = info2->starttime;
+ r->untiltime = info2->untiltime;
+ r->status = nt_printq_status(status.status);
+ r->cjobs = count;
+ r->averageppm = info2->averageppm;
+
+ if (info2->devmode != NULL) {
+ result = copy_devicemode(mem_ctx,
+ info2->devmode,
+ &r->devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+ } else if (lp_default_devmode(snum)) {
+ result = spoolss_create_default_devmode(mem_ctx,
+ info2->printername,
+ &r->devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+ } else {
+ r->devmode = NULL;
+ DEBUG(8,("Returning NULL Devicemode!\n"));
+ }
+
+ compose_devicemode_devicename(r->devmode, r->printername);
+
+ r->secdesc = NULL;
+
+ if (info2->secdesc != NULL) {
+ /* don't use talloc_steal() here unless you do a deep steal of all
+ the SEC_DESC members */
+
+ r->secdesc = security_descriptor_copy(mem_ctx, info2->secdesc);
+ if (r->secdesc == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * construct_printer_info3
+ * fill a spoolss_PrinterInfo3 struct
+ ********************************************************************/
+
+static WERROR construct_printer_info3(TALLOC_CTX *mem_ctx,
+ const struct spoolss_PrinterInfo2 *info2,
+ const char *servername,
+ struct spoolss_PrinterInfo3 *r,
+ int snum)
+{
+ /* These are the components of the SD we are returning. */
+
+ if (info2->secdesc != NULL) {
+ /* don't use talloc_steal() here unless you do a deep steal of all
+ the SEC_DESC members */
+
+ r->secdesc = security_descriptor_copy(mem_ctx, info2->secdesc);
+ if (r->secdesc == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * construct_printer_info4
+ * fill a spoolss_PrinterInfo4 struct
+ ********************************************************************/
+
+static WERROR construct_printer_info4(TALLOC_CTX *mem_ctx,
+ const struct spoolss_PrinterInfo2 *info2,
+ const char *servername,
+ struct spoolss_PrinterInfo4 *r,
+ int snum)
+{
+ WERROR result;
+
+ result = create_printername(mem_ctx, servername, info2->printername, &r->printername);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ if (servername) {
+ r->servername = talloc_strdup(mem_ctx, servername);
+ W_ERROR_HAVE_NO_MEMORY(r->servername);
+ } else {
+ r->servername = NULL;
+ }
+
+ r->attributes = info2->attributes;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * construct_printer_info5
+ * fill a spoolss_PrinterInfo5 struct
+ ********************************************************************/
+
+static WERROR construct_printer_info5(TALLOC_CTX *mem_ctx,
+ const struct spoolss_PrinterInfo2 *info2,
+ const char *servername,
+ struct spoolss_PrinterInfo5 *r,
+ int snum)
+{
+ WERROR result;
+
+ result = create_printername(mem_ctx, servername, info2->printername, &r->printername);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ r->portname = talloc_strdup(mem_ctx, info2->portname);
+ W_ERROR_HAVE_NO_MEMORY(r->portname);
+
+ r->attributes = info2->attributes;
+
+ /*
+ * These two are not used by NT+ according to MSDN. However the values
+ * we saw on Windows Server 2012 and 2016 are always set to the 0xafc8.
+ */
+ r->device_not_selected_timeout = 0xafc8; /* 45 sec */
+ r->transmission_retry_timeout = 0xafc8; /* 45 sec */
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * construct_printer_info_6
+ * fill a spoolss_PrinterInfo6 struct
+ ********************************************************************/
+
+static WERROR construct_printer_info6(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct spoolss_PrinterInfo2 *info2,
+ const char *servername,
+ struct spoolss_PrinterInfo6 *r,
+ int snum)
+{
+ print_status_struct status;
+
+ print_queue_length(msg_ctx, snum, &status);
+
+ r->status = nt_printq_status(status.status);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * construct_printer_info7
+ * fill a spoolss_PrinterInfo7 struct
+ ********************************************************************/
+
+static WERROR construct_printer_info7(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ struct spoolss_PrinterInfo7 *r,
+ int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const struct auth_session_info *session_info;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ char *printer;
+ WERROR werr;
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ session_info = get_session_info_system();
+ SMB_ASSERT(session_info != NULL);
+
+ printer = lp_servicename(tmp_ctx, lp_sub, snum);
+ if (printer == NULL) {
+ DEBUG(0, ("invalid printer snum %d\n", snum));
+ werr = WERR_INVALID_PARAMETER;
+ goto out_tmp_free;
+ }
+
+ if (is_printer_published(tmp_ctx, session_info, msg_ctx,
+ servername, printer, &pinfo2)) {
+ struct GUID guid;
+ char *guidstr;
+ werr = nt_printer_guid_get(tmp_ctx, session_info, msg_ctx,
+ printer, &guid);
+ if (!W_ERROR_IS_OK(werr)) {
+ /*
+ * If we do not have a GUID entry in the registry, then
+ * try to retrieve it from AD and store it now.
+ */
+ werr = nt_printer_guid_retrieve(tmp_ctx, printer,
+ &guid);
+ if (!W_ERROR_IS_OK(werr)) {
+ DBG_NOTICE("Failed to retrieve GUID for "
+ "printer [%s] from AD - %s\n",
+ printer,
+ win_errstr(werr));
+ if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) {
+ /*
+ * If we did not find it in AD, then it
+ * is unpublished and we should reflect
+ * this in the registry and return
+ * success.
+ */
+ DBG_WARNING("Unpublish printer [%s]\n",
+ pinfo2->sharename);
+ nt_printer_publish(tmp_ctx,
+ session_info,
+ msg_ctx,
+ pinfo2,
+ DSPRINT_UNPUBLISH);
+ r->guid = talloc_strdup(mem_ctx, "");
+ r->action = DSPRINT_UNPUBLISH;
+
+ if (r->guid == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ } else {
+ werr = WERR_OK;
+ }
+ }
+ goto out_tmp_free;
+ }
+
+ werr = nt_printer_guid_store(msg_ctx, printer, guid);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(3, ("failed to store printer %s guid\n",
+ printer));
+ }
+ }
+
+ /* [MS-RPRN] section 2.2: must use curly-braced GUIDs */
+ guidstr = GUID_string2(mem_ctx, &guid);
+ if (guidstr == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto out_tmp_free;
+ }
+ /* Convert GUID string to uppercase otherwise printers
+ * are pruned */
+ r->guid = talloc_strdup_upper(mem_ctx, guidstr);
+ r->action = DSPRINT_PUBLISH;
+
+ TALLOC_FREE(guidstr);
+ } else {
+ r->guid = talloc_strdup(mem_ctx, "");
+ r->action = DSPRINT_UNPUBLISH;
+ }
+ if (r->guid == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto out_tmp_free;
+ }
+
+ werr = WERR_OK;
+out_tmp_free:
+ talloc_free(tmp_ctx);
+ return werr;
+}
+
+/********************************************************************
+ * construct_printer_info8
+ * fill a spoolss_PrinterInfo8 struct
+ ********************************************************************/
+
+static WERROR construct_printer_info8(TALLOC_CTX *mem_ctx,
+ const struct spoolss_PrinterInfo2 *info2,
+ const char *servername,
+ struct spoolss_DeviceModeInfo *r,
+ int snum)
+{
+ WERROR result;
+ const char *printername;
+
+ result = create_printername(mem_ctx, servername, info2->printername, &printername);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ if (info2->devmode != NULL) {
+ result = copy_devicemode(mem_ctx,
+ info2->devmode,
+ &r->devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+ } else if (lp_default_devmode(snum)) {
+ result = spoolss_create_default_devmode(mem_ctx,
+ info2->printername,
+ &r->devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+ } else {
+ r->devmode = NULL;
+ DEBUG(8,("Returning NULL Devicemode!\n"));
+ }
+
+ compose_devicemode_devicename(r->devmode, printername);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ Spoolss_enumprinters.
+********************************************************************/
+
+static WERROR enum_all_printers_info_level(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ uint32_t level,
+ uint32_t flags,
+ union spoolss_PrinterInfo **info_p,
+ uint32_t *count_p)
+{
+ int snum;
+ int n_services;
+ union spoolss_PrinterInfo *info = NULL;
+ uint32_t count = 0;
+ WERROR result = WERR_OK;
+ struct dcerpc_binding_handle *b = NULL;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /*
+ * printer shares are updated on client enumeration. The background
+ * printer process updates printer_list.tdb at regular intervals.
+ */
+ become_root();
+ delete_and_reload_printers();
+ unbecome_root();
+
+ n_services = lp_numservices();
+ *count_p = 0;
+ *info_p = NULL;
+
+ for (snum = 0; snum < n_services; snum++) {
+
+ const char *printer;
+ struct spoolss_PrinterInfo2 *info2;
+
+ if (!snum_is_shared_printer(snum)) {
+ continue;
+ }
+
+ printer = lp_const_servicename(snum);
+
+ DEBUG(4,("Found a printer in smb.conf: %s[%x]\n",
+ printer, snum));
+
+ if (b == NULL) {
+ result = winreg_printer_binding_handle(tmp_ctx,
+ session_info,
+ msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+ }
+
+ result = winreg_create_printer(tmp_ctx, b,
+ printer);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ info = talloc_realloc(tmp_ctx, info,
+ union spoolss_PrinterInfo,
+ count + 1);
+ if (!info) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ result = winreg_get_printer(tmp_ctx, b,
+ printer, &info2);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ switch (level) {
+ case 0:
+ result = construct_printer_info0(info, session_info,
+ msg_ctx, info2,
+ servername,
+ &info[count].info0, snum);
+ break;
+ case 1:
+ result = construct_printer_info1(info, info2, flags,
+ servername,
+ &info[count].info1, snum);
+ break;
+ case 2:
+ result = construct_printer_info2(info, msg_ctx, info2,
+ servername,
+ &info[count].info2, snum);
+ break;
+ case 4:
+ result = construct_printer_info4(info, info2,
+ servername,
+ &info[count].info4, snum);
+ break;
+ case 5:
+ result = construct_printer_info5(info, info2,
+ servername,
+ &info[count].info5, snum);
+ break;
+
+ default:
+ result = WERR_INVALID_LEVEL;
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ count++;
+ }
+
+out:
+ if (W_ERROR_IS_OK(result)) {
+ *info_p = talloc_move(mem_ctx, &info);
+ *count_p = count;
+ }
+
+ talloc_free(tmp_ctx);
+
+ return result;
+}
+
+/********************************************************************
+ * handle enumeration of printers at level 0
+ ********************************************************************/
+
+static WERROR enumprinters_level0(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t flags,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ DEBUG(4,("enum_all_printers_info_0\n"));
+
+ return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx,
+ servername, 0, flags, info, count);
+}
+
+
+/********************************************************************
+********************************************************************/
+
+static WERROR enum_all_printers_info_1(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ uint32_t flags,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ DEBUG(4,("enum_all_printers_info_1\n"));
+
+ return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx,
+ servername, 1, flags, info, count);
+}
+
+/********************************************************************
+ enum_all_printers_info_1_local.
+*********************************************************************/
+
+static WERROR enum_all_printers_info_1_local(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ DEBUG(4,("enum_all_printers_info_1_local\n"));
+
+ return enum_all_printers_info_1(mem_ctx, session_info, msg_ctx,
+ servername, PRINTER_ENUM_ICON8, info, count);
+}
+
+/********************************************************************
+ enum_all_printers_info_1_name.
+*********************************************************************/
+
+static WERROR enum_all_printers_info_1_name(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ const char *s = servername;
+
+ DEBUG(4,("enum_all_printers_info_1_name\n"));
+
+ if (servername != NULL &&
+ (servername[0] == '\\') && (servername[1] == '\\')) {
+ s = servername + 2;
+ }
+
+ if (!is_myname_or_ipaddr(s)) {
+ return WERR_INVALID_NAME;
+ }
+
+ return enum_all_printers_info_1(mem_ctx, session_info, msg_ctx,
+ servername, PRINTER_ENUM_ICON8, info, count);
+}
+
+/********************************************************************
+ enum_all_printers_info_1_network.
+*********************************************************************/
+
+static WERROR enum_all_printers_info_1_network(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ const char *s = servername;
+
+ DEBUG(4,("enum_all_printers_info_1_network\n"));
+
+ /* If we respond to a enum_printers level 1 on our name with flags
+ set to PRINTER_ENUM_REMOTE with a list of printers then these
+ printers incorrectly appear in the APW browse list.
+ Specifically the printers for the server appear at the workgroup
+ level where all the other servers in the domain are
+ listed. Windows responds to this call with a
+ WERR_CAN_NOT_COMPLETE so we should do the same. */
+
+ if (servername != NULL &&
+ (servername[0] == '\\') && (servername[1] == '\\')) {
+ s = servername + 2;
+ }
+
+ if (is_myname_or_ipaddr(s)) {
+ return WERR_CAN_NOT_COMPLETE;
+ }
+
+ return enum_all_printers_info_1(mem_ctx, session_info, msg_ctx,
+ servername, PRINTER_ENUM_NAME, info, count);
+}
+
+/********************************************************************
+ * api_spoolss_enumprinters
+ *
+ * called from api_spoolss_enumprinters (see this to understand)
+ ********************************************************************/
+
+static WERROR enum_all_printers_info_2(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ DEBUG(4,("enum_all_printers_info_2\n"));
+
+ return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx,
+ servername, 2, 0, info, count);
+}
+
+/********************************************************************
+ * handle enumeration of printers at level 1
+ ********************************************************************/
+
+static WERROR enumprinters_level1(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t flags,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ /* Not all the flags are equals */
+
+ if (flags & PRINTER_ENUM_LOCAL) {
+ return enum_all_printers_info_1_local(mem_ctx, session_info,
+ msg_ctx, servername, info, count);
+ }
+
+ if (flags & PRINTER_ENUM_NAME) {
+ return enum_all_printers_info_1_name(mem_ctx, session_info,
+ msg_ctx, servername, info,
+ count);
+ }
+
+ if (flags & PRINTER_ENUM_NETWORK) {
+ return enum_all_printers_info_1_network(mem_ctx, session_info,
+ msg_ctx, servername, info,
+ count);
+ }
+
+ return WERR_OK; /* NT4sp5 does that */
+}
+
+/********************************************************************
+ * handle enumeration of printers at level 2
+ ********************************************************************/
+
+static WERROR enumprinters_level2(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t flags,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ if (flags & PRINTER_ENUM_LOCAL) {
+
+ return enum_all_printers_info_2(mem_ctx, session_info, msg_ctx,
+ servername,
+ info, count);
+ }
+
+ if (flags & PRINTER_ENUM_NAME) {
+ if (servername && !is_myname_or_ipaddr(canon_servername(servername))) {
+ return WERR_INVALID_NAME;
+ }
+
+ return enum_all_printers_info_2(mem_ctx, session_info, msg_ctx,
+ servername,
+ info, count);
+ }
+
+ if (flags & PRINTER_ENUM_REMOTE) {
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * handle enumeration of printers at level 4
+ ********************************************************************/
+
+static WERROR enumprinters_level4(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t flags,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ DEBUG(4,("enum_all_printers_info_4\n"));
+
+ return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx,
+ servername, 4, flags, info, count);
+}
+
+
+/********************************************************************
+ * handle enumeration of printers at level 5
+ ********************************************************************/
+
+static WERROR enumprinters_level5(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t flags,
+ const char *servername,
+ union spoolss_PrinterInfo **info,
+ uint32_t *count)
+{
+ DEBUG(4,("enum_all_printers_info_5\n"));
+
+ return enum_all_printers_info_level(mem_ctx, session_info, msg_ctx,
+ servername, 5, flags, info, count);
+}
+
+/****************************************************************
+ _spoolss_EnumPrinters
+****************************************************************/
+
+WERROR _spoolss_EnumPrinters(struct pipes_struct *p,
+ struct spoolss_EnumPrinters *r)
+{
+ const struct auth_session_info *session_info = get_session_info_system();
+ WERROR result;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(4,("_spoolss_EnumPrinters\n"));
+
+ *r->out.needed = 0;
+ *r->out.count = 0;
+ *r->out.info = NULL;
+
+ /*
+ * Level 1:
+ * flags==PRINTER_ENUM_NAME
+ * if name=="" then enumerates all printers
+ * if name!="" then enumerate the printer
+ * flags==PRINTER_ENUM_REMOTE
+ * name is NULL, enumerate printers
+ * Level 2: name!="" enumerates printers, name can't be NULL
+ * Level 3: doesn't exist
+ * Level 4: does a local registry lookup
+ * Level 5: same as Level 2
+ */
+
+ if (r->in.server && r->in.server[0] == '\0') {
+ r->in.server = NULL;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ result = enumprinters_level0(p->mem_ctx, session_info,
+ p->msg_ctx, r->in.flags,
+ r->in.server,
+ r->out.info, r->out.count);
+ break;
+ case 1:
+ result = enumprinters_level1(p->mem_ctx, session_info,
+ p->msg_ctx, r->in.flags,
+ r->in.server,
+ r->out.info, r->out.count);
+ break;
+ case 2:
+ result = enumprinters_level2(p->mem_ctx, session_info,
+ p->msg_ctx, r->in.flags,
+ r->in.server,
+ r->out.info, r->out.count);
+ break;
+ case 4:
+ result = enumprinters_level4(p->mem_ctx, session_info,
+ p->msg_ctx, r->in.flags,
+ r->in.server,
+ r->out.info, r->out.count);
+ break;
+ case 5:
+ result = enumprinters_level5(p->mem_ctx, session_info,
+ p->msg_ctx, r->in.flags,
+ r->in.server,
+ r->out.info, r->out.count);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumPrinters,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************
+ _spoolss_GetPrinter
+****************************************************************/
+
+WERROR _spoolss_GetPrinter(struct pipes_struct *p,
+ struct spoolss_GetPrinter *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ struct spoolss_PrinterInfo2 *info2 = NULL;
+ WERROR result = WERR_OK;
+ int snum;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_info_free;
+ }
+
+ *r->out.needed = 0;
+
+ if (Printer == NULL) {
+ result = WERR_INVALID_HANDLE;
+ goto err_info_free;
+ }
+
+ if (Printer->printer_type == SPLHND_SERVER) {
+
+ struct dcerpc_binding_handle *b;
+
+ if (r->in.level != 3) {
+ result = WERR_INVALID_LEVEL;
+ goto err_info_free;
+ }
+
+ result = winreg_printer_binding_handle(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_info_free;
+ }
+
+ result = winreg_get_printserver_secdesc(p->mem_ctx,
+ b,
+ &r->out.info->info3.secdesc);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_info_free;
+ }
+
+ goto done;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ result = WERR_INVALID_HANDLE;
+ goto err_info_free;
+ }
+
+ result = winreg_get_printer_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_const_servicename(snum),
+ &info2);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_info_free;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ result = construct_printer_info0(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ info2,
+ Printer->servername,
+ &r->out.info->info0,
+ snum);
+ break;
+ case 1:
+ result = construct_printer_info1(p->mem_ctx, info2,
+ PRINTER_ENUM_ICON8,
+ Printer->servername,
+ &r->out.info->info1, snum);
+ break;
+ case 2:
+ result = construct_printer_info2(p->mem_ctx, p->msg_ctx, info2,
+ Printer->servername,
+ &r->out.info->info2, snum);
+ break;
+ case 3:
+ result = construct_printer_info3(p->mem_ctx, info2,
+ Printer->servername,
+ &r->out.info->info3, snum);
+ break;
+ case 4:
+ result = construct_printer_info4(p->mem_ctx, info2,
+ Printer->servername,
+ &r->out.info->info4, snum);
+ break;
+ case 5:
+ result = construct_printer_info5(p->mem_ctx, info2,
+ Printer->servername,
+ &r->out.info->info5, snum);
+ break;
+ case 6:
+ result = construct_printer_info6(p->mem_ctx, p->msg_ctx, info2,
+ Printer->servername,
+ &r->out.info->info6, snum);
+ break;
+ case 7:
+ result = construct_printer_info7(p->mem_ctx, p->msg_ctx,
+ Printer->servername,
+ &r->out.info->info7, snum);
+ break;
+ case 8:
+ result = construct_printer_info8(p->mem_ctx, info2,
+ Printer->servername,
+ &r->out.info->info8, snum);
+ break;
+ default:
+ result = WERR_INVALID_LEVEL;
+ break;
+ }
+ TALLOC_FREE(info2);
+
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("_spoolss_GetPrinter: failed to construct printer info level %d - %s\n",
+ r->in.level, win_errstr(result)));
+ goto err_info_free;
+ }
+ done:
+ *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_PrinterInfo,
+ r->out.info, r->in.level);
+ r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+
+err_info_free:
+ TALLOC_FREE(r->out.info);
+ return result;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+#define FILL_DRIVER_STRING(mem_ctx, in, out) \
+ do { \
+ if (in && strlen(in)) { \
+ out = talloc_strdup(mem_ctx, in); \
+ } else { \
+ out = talloc_strdup(mem_ctx, ""); \
+ } \
+ W_ERROR_HAVE_NO_MEMORY(out); \
+ } while (0);
+
+#define FILL_DRIVER_UNC_STRING(mem_ctx, server, arch, ver, in, out) \
+ do { \
+ if (in && strlen(in)) { \
+ out = talloc_asprintf(mem_ctx, "\\\\%s\\print$\\%s\\%d\\%s", server, get_short_archi(arch), ver, in); \
+ } else { \
+ out = talloc_strdup(mem_ctx, ""); \
+ } \
+ W_ERROR_HAVE_NO_MEMORY(out); \
+ } while (0);
+
+static WERROR string_array_from_driver_info(TALLOC_CTX *mem_ctx,
+ const char **string_array,
+ const char ***presult,
+ const char *cservername,
+ const char *arch,
+ int version)
+{
+ size_t i;
+ size_t num_strings = 0;
+ const char **array = NULL;
+
+ if (string_array == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ for (i=0; string_array[i] && string_array[i][0] != '\0'; i++) {
+ const char *str = NULL;
+
+ if (cservername == NULL || arch == NULL) {
+ FILL_DRIVER_STRING(mem_ctx, string_array[i], str);
+ } else {
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername, arch, version, string_array[i], str);
+ }
+
+ if (!add_string_to_array(mem_ctx, str, &array, &num_strings)) {
+ TALLOC_FREE(array);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (i > 0) {
+ ADD_TO_ARRAY(mem_ctx, const char *, NULL,
+ &array, &num_strings);
+ }
+
+ if (presult != NULL) {
+ *presult = array;
+ } else {
+ talloc_free(array);
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * fill a spoolss_DriverInfo1 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info1(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo1 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+{
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * fill a spoolss_DriverInfo2 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info2(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo2 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+
+{
+ const char *cservername = canon_servername(servername);
+
+ r->version = driver->version;
+
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+ r->architecture = talloc_strdup(mem_ctx, driver->architecture);
+ W_ERROR_HAVE_NO_MEMORY(r->architecture);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->driver_path,
+ r->driver_path);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->data_file,
+ r->data_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->config_file,
+ r->config_file);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * fill a spoolss_DriverInfo3 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info3(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo3 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+{
+ const char *cservername = canon_servername(servername);
+
+ r->version = driver->version;
+
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+ r->architecture = talloc_strdup(mem_ctx, driver->architecture);
+ W_ERROR_HAVE_NO_MEMORY(r->architecture);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->driver_path,
+ r->driver_path);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->data_file,
+ r->data_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->config_file,
+ r->config_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->help_file,
+ r->help_file);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->monitor_name,
+ r->monitor_name);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->default_datatype,
+ r->default_datatype);
+
+ return string_array_from_driver_info(mem_ctx,
+ driver->dependent_files,
+ &r->dependent_files,
+ cservername,
+ driver->architecture,
+ driver->version);
+}
+
+/********************************************************************
+ * fill a spoolss_DriverInfo4 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info4(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo4 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+{
+ const char *cservername = canon_servername(servername);
+ WERROR result;
+
+ r->version = driver->version;
+
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+ r->architecture = talloc_strdup(mem_ctx, driver->architecture);
+ W_ERROR_HAVE_NO_MEMORY(r->architecture);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->driver_path,
+ r->driver_path);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->data_file,
+ r->data_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->config_file,
+ r->config_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->help_file,
+ r->help_file);
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->dependent_files,
+ &r->dependent_files,
+ cservername,
+ driver->architecture,
+ driver->version);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->monitor_name,
+ r->monitor_name);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->default_datatype,
+ r->default_datatype);
+
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->previous_names,
+ &r->previous_names,
+ NULL, NULL, 0);
+
+ return result;
+}
+
+/********************************************************************
+ * fill a spoolss_DriverInfo5 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info5(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo5 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+{
+ const char *cservername = canon_servername(servername);
+
+ r->version = driver->version;
+
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+ r->architecture = talloc_strdup(mem_ctx, driver->architecture);
+ W_ERROR_HAVE_NO_MEMORY(r->architecture);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->driver_path,
+ r->driver_path);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->data_file,
+ r->data_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->config_file,
+ r->config_file);
+
+ r->driver_attributes = 0;
+ r->config_version = 0;
+ r->driver_version = 0;
+
+ return WERR_OK;
+}
+/********************************************************************
+ * fill a spoolss_DriverInfo6 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info6(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo6 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+{
+ const char *cservername = canon_servername(servername);
+ WERROR result;
+
+ r->version = driver->version;
+
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+ r->architecture = talloc_strdup(mem_ctx, driver->architecture);
+ W_ERROR_HAVE_NO_MEMORY(r->architecture);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->driver_path,
+ r->driver_path);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->data_file,
+ r->data_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->config_file,
+ r->config_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->help_file,
+ r->help_file);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->monitor_name,
+ r->monitor_name);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->default_datatype,
+ r->default_datatype);
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->dependent_files,
+ &r->dependent_files,
+ cservername,
+ driver->architecture,
+ driver->version);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->previous_names,
+ &r->previous_names,
+ NULL, NULL, 0);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ r->driver_date = driver->driver_date;
+ r->driver_version = driver->driver_version;
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->manufacturer_name,
+ r->manufacturer_name);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->manufacturer_url,
+ r->manufacturer_url);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->hardware_id,
+ r->hardware_id);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->provider,
+ r->provider);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * fill a spoolss_DriverInfo8 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info8(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo8 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+{
+ const char *cservername = canon_servername(servername);
+ WERROR result;
+
+ r->version = driver->version;
+
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+ r->architecture = talloc_strdup(mem_ctx, driver->architecture);
+ W_ERROR_HAVE_NO_MEMORY(r->architecture);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->driver_path,
+ r->driver_path);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->data_file,
+ r->data_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->config_file,
+ r->config_file);
+
+ FILL_DRIVER_UNC_STRING(mem_ctx, cservername,
+ driver->architecture,
+ driver->version,
+ driver->help_file,
+ r->help_file);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->monitor_name,
+ r->monitor_name);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->default_datatype,
+ r->default_datatype);
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->dependent_files,
+ &r->dependent_files,
+ cservername,
+ driver->architecture,
+ driver->version);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->previous_names,
+ &r->previous_names,
+ NULL, NULL, 0);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ r->driver_date = driver->driver_date;
+ r->driver_version = driver->driver_version;
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->manufacturer_name,
+ r->manufacturer_name);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->manufacturer_url,
+ r->manufacturer_url);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->hardware_id,
+ r->hardware_id);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->provider,
+ r->provider);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->print_processor,
+ r->print_processor);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->vendor_setup,
+ r->vendor_setup);
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->color_profiles,
+ &r->color_profiles,
+ NULL, NULL, 0);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->inf_path,
+ r->inf_path);
+
+ r->printer_driver_attributes = driver->printer_driver_attributes;
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->core_driver_dependencies,
+ &r->core_driver_dependencies,
+ NULL, NULL, 0);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ r->min_inbox_driver_ver_date = driver->min_inbox_driver_ver_date;
+ r->min_inbox_driver_ver_version = driver->min_inbox_driver_ver_version;
+
+ return WERR_OK;
+}
+
+#if 0 /* disabled until marshalling issues are resolved - gd */
+/********************************************************************
+ ********************************************************************/
+
+static WERROR fill_spoolss_DriverFileInfo(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverFileInfo *r,
+ const char *cservername,
+ const char *file_name,
+ enum spoolss_DriverFileType file_type,
+ uint32_t file_version)
+{
+ r->file_name = talloc_asprintf(mem_ctx, "\\\\%s%s",
+ cservername, file_name);
+ W_ERROR_HAVE_NO_MEMORY(r->file_name);
+ r->file_type = file_type;
+ r->file_version = file_version;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static WERROR spoolss_DriverFileInfo_from_driver(TALLOC_CTX *mem_ctx,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *cservername,
+ struct spoolss_DriverFileInfo **info_p,
+ uint32_t *count_p)
+{
+ struct spoolss_DriverFileInfo *info = NULL;
+ uint32_t count = 0;
+ WERROR result;
+ uint32_t i;
+
+ *info_p = NULL;
+ *count_p = 0;
+
+ if (strlen(driver->driver_path)) {
+ info = talloc_realloc(mem_ctx, info,
+ struct spoolss_DriverFileInfo,
+ count + 1);
+ W_ERROR_HAVE_NO_MEMORY(info);
+ result = fill_spoolss_DriverFileInfo(info,
+ &info[count],
+ cservername,
+ driver->driver_path,
+ SPOOLSS_DRIVER_FILE_TYPE_RENDERING,
+ 0);
+ W_ERROR_NOT_OK_RETURN(result);
+ count++;
+ }
+
+ if (strlen(driver->config_file)) {
+ info = talloc_realloc(mem_ctx, info,
+ struct spoolss_DriverFileInfo,
+ count + 1);
+ W_ERROR_HAVE_NO_MEMORY(info);
+ result = fill_spoolss_DriverFileInfo(info,
+ &info[count],
+ cservername,
+ driver->config_file,
+ SPOOLSS_DRIVER_FILE_TYPE_CONFIGURATION,
+ 0);
+ W_ERROR_NOT_OK_RETURN(result);
+ count++;
+ }
+
+ if (strlen(driver->data_file)) {
+ info = talloc_realloc(mem_ctx, info,
+ struct spoolss_DriverFileInfo,
+ count + 1);
+ W_ERROR_HAVE_NO_MEMORY(info);
+ result = fill_spoolss_DriverFileInfo(info,
+ &info[count],
+ cservername,
+ driver->data_file,
+ SPOOLSS_DRIVER_FILE_TYPE_DATA,
+ 0);
+ W_ERROR_NOT_OK_RETURN(result);
+ count++;
+ }
+
+ if (strlen(driver->help_file)) {
+ info = talloc_realloc(mem_ctx, info,
+ struct spoolss_DriverFileInfo,
+ count + 1);
+ W_ERROR_HAVE_NO_MEMORY(info);
+ result = fill_spoolss_DriverFileInfo(info,
+ &info[count],
+ cservername,
+ driver->help_file,
+ SPOOLSS_DRIVER_FILE_TYPE_HELP,
+ 0);
+ W_ERROR_NOT_OK_RETURN(result);
+ count++;
+ }
+
+ for (i=0; driver->dependent_files[i] && driver->dependent_files[i][0] != '\0'; i++) {
+ info = talloc_realloc(mem_ctx, info,
+ struct spoolss_DriverFileInfo,
+ count + 1);
+ W_ERROR_HAVE_NO_MEMORY(info);
+ result = fill_spoolss_DriverFileInfo(info,
+ &info[count],
+ cservername,
+ driver->dependent_files[i],
+ SPOOLSS_DRIVER_FILE_TYPE_OTHER,
+ 0);
+ W_ERROR_NOT_OK_RETURN(result);
+ count++;
+ }
+
+ *info_p = info;
+ *count_p = count;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * fill a spoolss_DriverInfo101 struct
+ ********************************************************************/
+
+static WERROR fill_printer_driver_info101(TALLOC_CTX *mem_ctx,
+ struct spoolss_DriverInfo101 *r,
+ const struct spoolss_DriverInfo8 *driver,
+ const char *servername)
+{
+ const char *cservername = canon_servername(servername);
+ WERROR result;
+
+ r->version = driver->version;
+
+ r->driver_name = talloc_strdup(mem_ctx, driver->driver_name);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+ r->architecture = talloc_strdup(mem_ctx, driver->architecture);
+ W_ERROR_HAVE_NO_MEMORY(r->architecture);
+
+ result = spoolss_DriverFileInfo_from_driver(mem_ctx, driver,
+ cservername,
+ &r->file_info,
+ &r->file_count);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->monitor_name,
+ r->monitor_name);
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->default_datatype,
+ r->default_datatype);
+
+ result = string_array_from_driver_info(mem_ctx,
+ driver->previous_names,
+ &r->previous_names,
+ NULL, NULL, 0);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ r->driver_date = driver->driver_date;
+ r->driver_version = driver->driver_version;
+
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->manufacturer_name,
+ r->manufacturer_name);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->manufacturer_url,
+ r->manufacturer_url);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->hardware_id,
+ r->hardware_id);
+ FILL_DRIVER_STRING(mem_ctx,
+ driver->provider,
+ r->provider);
+
+ return WERR_OK;
+}
+#endif
+/********************************************************************
+ ********************************************************************/
+
+static WERROR construct_printer_driver_info_level(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t level,
+ union spoolss_DriverInfo *r,
+ int snum,
+ const char *servername,
+ const char *architecture,
+ uint32_t version)
+{
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ struct spoolss_DriverInfo8 *driver;
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ if (level == 101) {
+ return WERR_INVALID_LEVEL;
+ }
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ session_info,
+ msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_get_printer(tmp_ctx, b,
+ lp_const_servicename(snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ DBG_ERR("Failed to get printer info2 for [%s]: %s\n",
+ lp_const_servicename(snum), win_errstr(result));
+ result = WERR_INVALID_PRINTER_NAME;
+ goto done;
+ }
+
+ if (pinfo2->drivername == NULL || pinfo2->drivername[0] == '\0') {
+ result = WERR_UNKNOWN_PRINTER_DRIVER;
+ goto done;
+ }
+
+ DBG_INFO("Construct printer driver [%s] for [%s]\n",
+ pinfo2->drivername,
+ pinfo2->sharename);
+
+ result = winreg_get_driver(tmp_ctx, b,
+ architecture,
+ pinfo2->drivername, version, &driver);
+
+ DBG_INFO("winreg_get_driver() status: %s\n",
+ win_errstr(result));
+
+ if (!W_ERROR_IS_OK(result)) {
+ /*
+ * Is this a W2k client ?
+ */
+
+ if (version < 3) {
+ result = WERR_UNKNOWN_PRINTER_DRIVER;
+ goto done;
+ }
+
+ /* Yes - try again with a WinNT driver. */
+ version = 2;
+ result = winreg_get_driver(tmp_ctx, b,
+ architecture,
+ pinfo2->drivername,
+ version, &driver);
+ DEBUG(8,("construct_printer_driver_level: status: %s\n",
+ win_errstr(result)));
+ if (!W_ERROR_IS_OK(result)) {
+ result = WERR_UNKNOWN_PRINTER_DRIVER;
+ goto done;
+ }
+ }
+
+ /* these are allocated on mem_ctx and not tmp_ctx because they are
+ * the 'return value' and need to outlive this call */
+ switch (level) {
+ case 1:
+ result = fill_printer_driver_info1(mem_ctx, &r->info1, driver, servername);
+ break;
+ case 2:
+ result = fill_printer_driver_info2(mem_ctx, &r->info2, driver, servername);
+ break;
+ case 3:
+ result = fill_printer_driver_info3(mem_ctx, &r->info3, driver, servername);
+ break;
+ case 4:
+ result = fill_printer_driver_info4(mem_ctx, &r->info4, driver, servername);
+ break;
+ case 5:
+ result = fill_printer_driver_info5(mem_ctx, &r->info5, driver, servername);
+ break;
+ case 6:
+ result = fill_printer_driver_info6(mem_ctx, &r->info6, driver, servername);
+ break;
+ case 8:
+ result = fill_printer_driver_info8(mem_ctx, &r->info8, driver, servername);
+ break;
+#if 0 /* disabled until marshalling issues are resolved - gd */
+ case 101:
+ result = fill_printer_driver_info101(mem_ctx, &r->info101, driver, servername);
+ break;
+#endif
+ default:
+ result = WERR_INVALID_LEVEL;
+ break;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/****************************************************************
+ _spoolss_GetPrinterDriver2
+****************************************************************/
+
+WERROR _spoolss_GetPrinterDriver2(struct pipes_struct *p,
+ struct spoolss_GetPrinterDriver2 *r)
+{
+ struct printer_handle *printer;
+ WERROR result;
+ uint32_t version = r->in.client_major_version;
+
+ int snum;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_info_free;
+ }
+
+ DEBUG(4,("_spoolss_GetPrinterDriver2\n"));
+
+ if (!(printer = find_printer_index_by_hnd(p, r->in.handle))) {
+ DEBUG(0,("_spoolss_GetPrinterDriver2: invalid printer handle!\n"));
+ result = WERR_INVALID_PRINTER_NAME;
+ goto err_info_free;
+ }
+
+ *r->out.needed = 0;
+ *r->out.server_major_version = 0;
+ *r->out.server_minor_version = 0;
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ result = WERR_INVALID_HANDLE;
+ goto err_info_free;
+ }
+
+ if (r->in.client_major_version == SPOOLSS_DRIVER_VERSION_2012) {
+ DEBUG(3,("_spoolss_GetPrinterDriver2: v4 driver requested, "
+ "downgrading to v3\n"));
+ version = SPOOLSS_DRIVER_VERSION_200X;
+ }
+
+ result = construct_printer_driver_info_level(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ r->in.level, r->out.info,
+ snum, printer->servername,
+ r->in.architecture,
+ version);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_info_free;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_DriverInfo,
+ r->out.info, r->in.level);
+ r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+
+err_info_free:
+ TALLOC_FREE(r->out.info);
+ return result;
+}
+
+
+/****************************************************************
+ _spoolss_StartPagePrinter
+****************************************************************/
+
+WERROR _spoolss_StartPagePrinter(struct pipes_struct *p,
+ struct spoolss_StartPagePrinter *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ if (!Printer) {
+ DEBUG(3,("_spoolss_StartPagePrinter: "
+ "Error in startpageprinter printer handle\n"));
+ return WERR_INVALID_HANDLE;
+ }
+
+ Printer->page_started = true;
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_EndPagePrinter
+****************************************************************/
+
+WERROR _spoolss_EndPagePrinter(struct pipes_struct *p,
+ struct spoolss_EndPagePrinter *r)
+{
+ int snum;
+
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_EndPagePrinter: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL))
+ return WERR_INVALID_HANDLE;
+
+ Printer->page_started = false;
+ print_job_endpage(p->msg_ctx, snum, Printer->jobid);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_StartDocPrinter
+****************************************************************/
+
+WERROR _spoolss_StartDocPrinter(struct pipes_struct *p,
+ struct spoolss_StartDocPrinter *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct spoolss_DocumentInfo1 *info_1;
+ int snum;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ WERROR werr;
+ char *rhost;
+ int rc;
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_StartDocPrinter: "
+ "Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (Printer->jobid) {
+ DEBUG(2, ("_spoolss_StartDocPrinter: "
+ "StartDocPrinter called twice! "
+ "(existing jobid = %d)\n", Printer->jobid));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (r->in.info_ctr->level != 1) {
+ return WERR_INVALID_LEVEL;
+ }
+
+ info_1 = r->in.info_ctr->info.info1;
+
+ /*
+ * a nice thing with NT is it doesn't listen to what you tell it.
+ * when asked to send _only_ RAW data, it tries to send data
+ * in EMF format.
+ *
+ * So I add checks like in NT Server ...
+ */
+
+ if (info_1->datatype) {
+ /*
+ * The v4 driver model used in Windows 8 declares print jobs
+ * intended to bypass the XPS processing layer by setting
+ * datatype to "XPS_PASS" instead of "RAW".
+ */
+ if ((strcmp(info_1->datatype, "RAW") != 0)
+ && (strcmp(info_1->datatype, "XPS_PASS") != 0)) {
+ *r->out.job_id = 0;
+ return WERR_INVALID_DATATYPE;
+ }
+ }
+
+ /* get the share number of the printer */
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ rc = get_remote_hostname(remote_address,
+ &rhost,
+ p->mem_ctx);
+ if (rc < 0) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (strequal(rhost,"UNKNOWN")) {
+ rhost = tsocket_address_inet_addr_string(remote_address,
+ p->mem_ctx);
+ if (rhost == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ werr = print_job_start(session_info,
+ p->msg_ctx,
+ rhost,
+ snum,
+ info_1->document_name,
+ info_1->output_file,
+ Printer->devmode,
+ &Printer->jobid);
+
+ /* An error occurred in print_job_start() so return an appropriate
+ NT error code. */
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ Printer->document_started = true;
+ *r->out.job_id = Printer->jobid;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_EndDocPrinter
+****************************************************************/
+
+WERROR _spoolss_EndDocPrinter(struct pipes_struct *p,
+ struct spoolss_EndDocPrinter *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ NTSTATUS status;
+ int snum;
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_EndDocPrinter: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ Printer->document_started = false;
+ status = print_job_end(p->msg_ctx, snum, Printer->jobid, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("_spoolss_EndDocPrinter: "
+ "print_job_end failed [%s]\n",
+ nt_errstr(status)));
+ }
+
+ Printer->jobid = 0;
+ return ntstatus_to_werror(status);
+}
+
+/****************************************************************
+ _spoolss_WritePrinter
+****************************************************************/
+
+WERROR _spoolss_WritePrinter(struct pipes_struct *p,
+ struct spoolss_WritePrinter *r)
+{
+ ssize_t buffer_written;
+ int snum;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_WritePrinter: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(r->in.handle)));
+ *r->out.num_written = r->in._data_size;
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL))
+ return WERR_INVALID_HANDLE;
+
+ /* print_job_write takes care of checking for PJOB_SMBD_SPOOLING */
+ buffer_written = print_job_write(global_event_context(),p->msg_ctx,
+ snum, Printer->jobid,
+ (const char *)r->in.data.data,
+ (size_t)r->in._data_size);
+ if (buffer_written == (ssize_t)-1) {
+ *r->out.num_written = 0;
+ if (errno == ENOSPC)
+ return WERR_NO_SPOOL_SPACE;
+ else
+ return WERR_ACCESS_DENIED;
+ }
+
+ *r->out.num_written = r->in._data_size;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ * api_spoolss_getprinter
+ * called from the spoolss dispatcher
+ *
+ ********************************************************************/
+
+static WERROR control_printer(struct policy_handle *handle, uint32_t command,
+ struct pipes_struct *p)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ int snum;
+ WERROR errcode = WERR_INVALID_FUNCTION;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, handle);
+
+ if (!Printer) {
+ DEBUG(2,("control_printer: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, handle, &snum, NULL))
+ return WERR_INVALID_HANDLE;
+
+ switch (command) {
+ case SPOOLSS_PRINTER_CONTROL_PAUSE:
+ errcode = print_queue_pause(session_info, p->msg_ctx, snum);
+ break;
+ case SPOOLSS_PRINTER_CONTROL_RESUME:
+ case SPOOLSS_PRINTER_CONTROL_UNPAUSE:
+ errcode = print_queue_resume(session_info, p->msg_ctx, snum);
+ break;
+ case SPOOLSS_PRINTER_CONTROL_PURGE:
+ errcode = print_queue_purge(session_info, p->msg_ctx, snum);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return errcode;
+}
+
+
+/****************************************************************
+ _spoolss_AbortPrinter
+ * From MSDN: "Deletes printer's spool file if printer is configured
+ * for spooling"
+****************************************************************/
+
+WERROR _spoolss_AbortPrinter(struct pipes_struct *p,
+ struct spoolss_AbortPrinter *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ int snum;
+ WERROR errcode = WERR_OK;
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_AbortPrinter: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL))
+ return WERR_INVALID_HANDLE;
+
+ if (!Printer->document_started) {
+ return WERR_SPL_NO_STARTDOC;
+ }
+
+ errcode = print_job_delete(session_info,
+ p->msg_ctx,
+ snum,
+ Printer->jobid);
+
+ return errcode;
+}
+
+/********************************************************************
+ * called by spoolss_api_setprinter
+ * when updating a printer description
+ ********************************************************************/
+
+static WERROR update_printer_sec(struct policy_handle *handle,
+ struct pipes_struct *p,
+ struct sec_desc_buf *secdesc_ctr)
+{
+ struct spoolss_security_descriptor *new_secdesc = NULL;
+ struct spoolss_security_descriptor *old_secdesc = NULL;
+ const char *printer = NULL;
+ WERROR result;
+ int snum = -1;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, handle);
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+ bool ok = false;
+
+ if (!Printer) {
+ DEBUG(2,("update_printer_sec: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(handle)));
+
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ if (secdesc_ctr == NULL) {
+ DEBUG(10,("update_printer_sec: secdesc_ctr is NULL !\n"));
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ switch (Printer->printer_type) {
+ case SPLHND_SERVER:
+ break;
+ case SPLHND_PRINTER:
+ if (!get_printer_snum(p, handle, &snum, NULL)) {
+ DEBUG(2,("update_printer_sec: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(handle)));
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+ printer = lp_const_servicename(snum);
+ break;
+ default:
+ break;
+ }
+
+ /* Check the user has permissions to change the security
+ descriptor. By experimentation with two NT machines, the user
+ requires Full Access to the printer to change security
+ information. */
+
+ switch (Printer->printer_type) {
+ case SPLHND_SERVER:
+ ok = Printer->access_granted == SERVER_ACCESS_ADMINISTER;
+ break;
+ case SPLHND_PRINTER:
+ ok = Printer->access_granted == PRINTER_ACCESS_ADMINISTER;
+ break;
+ default:
+ break;
+ }
+
+ if (!ok) {
+ DEBUG(4,("update_printer_sec: updated denied by printer permissions "
+ "(access_granted: 0x%08x)\n", Printer->access_granted));
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* NT seems to like setting the security descriptor even though
+ nothing may have actually changed. */
+
+ if (printer != NULL) {
+ result = winreg_get_printer_secdesc(tmp_ctx, b,
+ printer,
+ &old_secdesc);
+ } else {
+ result = winreg_get_printserver_secdesc(tmp_ctx, b,
+ &old_secdesc);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2,("update_printer_sec: winreg_get_printer_secdesc_internal() failed\n"));
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ struct dom_sid_buf buf;
+ struct security_acl *the_acl;
+ int i;
+
+ the_acl = old_secdesc->dacl;
+ DEBUG(10, ("old_secdesc_ctr for %s has %d aces:\n",
+ printer, the_acl->num_aces));
+
+ for (i = 0; i < the_acl->num_aces; i++) {
+ DEBUG(10, ("%s 0x%08x\n",
+ dom_sid_str_buf(
+ &the_acl->aces[i].trustee,
+ &buf),
+ the_acl->aces[i].access_mask));
+ }
+
+ the_acl = secdesc_ctr->sd->dacl;
+
+ if (the_acl) {
+ DEBUG(10, ("secdesc_ctr for %s has %d aces:\n",
+ printer, the_acl->num_aces));
+
+ for (i = 0; i < the_acl->num_aces; i++) {
+ DEBUG(10, ("%s 0x%08x\n",
+ dom_sid_str_buf(
+ &the_acl->aces[i].trustee,
+ &buf),
+ the_acl->aces[i].access_mask));
+ }
+ } else {
+ DEBUG(10, ("dacl for secdesc_ctr is NULL\n"));
+ }
+ }
+
+ new_secdesc = sec_desc_merge(tmp_ctx, secdesc_ctr->sd, old_secdesc);
+ if (new_secdesc == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ if (security_descriptor_equal(new_secdesc, old_secdesc)) {
+ result = WERR_OK;
+ goto done;
+ }
+
+ if (printer != NULL) {
+ result = winreg_set_printer_secdesc(tmp_ctx, b,
+ printer,
+ new_secdesc);
+ } else {
+ result = winreg_set_printserver_secdesc(tmp_ctx, b,
+ new_secdesc);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/********************************************************************
+ Canonicalize printer info from a client
+ ********************************************************************/
+
+static bool check_printer_ok(TALLOC_CTX *mem_ctx,
+ struct spoolss_SetPrinterInfo2 *info2,
+ int snum)
+{
+ fstring printername;
+ const char *p;
+
+ DEBUG(5,("check_printer_ok: servername=%s printername=%s sharename=%s "
+ "portname=%s drivername=%s comment=%s location=%s\n",
+ info2->servername, info2->printername, info2->sharename,
+ info2->portname, info2->drivername, info2->comment,
+ info2->location));
+
+ /* we force some elements to "correct" values */
+ info2->servername = talloc_asprintf(mem_ctx, "\\\\%s", lp_netbios_name());
+ if (info2->servername == NULL) {
+ return false;
+ }
+ info2->sharename = talloc_strdup(mem_ctx, lp_const_servicename(snum));
+ if (info2->sharename == NULL) {
+ return false;
+ }
+
+ /* check to see if we allow printername != sharename */
+ if (lp_force_printername(snum)) {
+ info2->printername = talloc_asprintf(mem_ctx, "\\\\%s\\%s",
+ lp_netbios_name(), info2->sharename);
+ } else {
+ /* make sure printername is in \\server\printername format */
+ fstrcpy(printername, info2->printername);
+ p = printername;
+ if ( printername[0] == '\\' && printername[1] == '\\' ) {
+ if ( (p = strchr_m( &printername[2], '\\' )) != NULL )
+ p++;
+ }
+
+ info2->printername = talloc_asprintf(mem_ctx, "\\\\%s\\%s",
+ lp_netbios_name(), p);
+ }
+ if (info2->printername == NULL) {
+ return false;
+ }
+
+ info2->attributes |= PRINTER_ATTRIBUTE_SAMBA;
+ info2->attributes &= ~PRINTER_ATTRIBUTE_NOT_SAMBA;
+
+ return true;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR add_port_hook(TALLOC_CTX *ctx, struct security_token *token, const char *portname, const char *uri)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *cmd = lp_addport_command(talloc_tos(), lp_sub);
+ char *command = NULL;
+ int ret;
+ bool is_print_op = false;
+
+ if ( !*cmd ) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ command = talloc_asprintf(ctx,
+ "%s \"%s\" \"%s\"", cmd, portname, uri );
+ if (!command) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if ( token )
+ is_print_op = security_token_has_privilege(token, SEC_PRIV_PRINT_OPERATOR);
+
+ DEBUG(10,("Running [%s]\n", command));
+
+ /********* BEGIN SePrintOperatorPrivilege **********/
+
+ if ( is_print_op )
+ become_root();
+
+ ret = smbrun(command, NULL, NULL);
+
+ if ( is_print_op )
+ unbecome_root();
+
+ /********* END SePrintOperatorPrivilege **********/
+
+ DEBUGADD(10,("returned [%d]\n", ret));
+
+ TALLOC_FREE(command);
+
+ if ( ret != 0 ) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static bool spoolss_conn_snum_used(struct smbd_server_connection *sconn,
+ int snum)
+{
+ /*
+ * As we do not know if we are embedded in the file server process
+ * or not, we have to pretend that all shares are in use.
+ */
+ return true;
+}
+
+static bool add_printer_hook(TALLOC_CTX *ctx, struct security_token *token,
+ struct spoolss_SetPrinterInfo2 *info2,
+ const char *remote_machine,
+ struct messaging_context *msg_ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *cmd = lp_addprinter_command(talloc_tos(), lp_sub);
+ char **qlines;
+ char *command = NULL;
+ int numlines;
+ int ret;
+ int fd;
+ bool is_print_op = false;
+
+ if (!remote_machine) {
+ return false;
+ }
+
+ command = talloc_asprintf(ctx,
+ "%s \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\"",
+ cmd, info2->printername, info2->sharename,
+ info2->portname, info2->drivername,
+ info2->location, info2->comment, remote_machine);
+ if (!command) {
+ return false;
+ }
+
+ if ( token )
+ is_print_op = security_token_has_privilege(token, SEC_PRIV_PRINT_OPERATOR);
+
+ DEBUG(10,("Running [%s]\n", command));
+
+ /********* BEGIN SePrintOperatorPrivilege **********/
+
+ if ( is_print_op )
+ become_root();
+
+ ret = smbrun(command, &fd, NULL);
+ if (ret == 0) {
+ /* Tell everyone we updated smb.conf. */
+ messaging_send_all(msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0);
+ }
+
+ if ( is_print_op )
+ unbecome_root();
+
+ /********* END SePrintOperatorPrivilege **********/
+
+ DEBUGADD(10,("returned [%d]\n", ret));
+
+ TALLOC_FREE(command);
+
+ if ( ret != 0 ) {
+ if (fd != -1)
+ close(fd);
+ return false;
+ }
+
+ /* reload our services immediately */
+ become_root();
+ reload_services(NULL, spoolss_conn_snum_used, false);
+ unbecome_root();
+
+ numlines = 0;
+ /* Get lines and convert them back to dos-codepage */
+ qlines = fd_lines_load(fd, &numlines, 0, NULL);
+ DEBUGADD(10,("Lines returned = [%d]\n", numlines));
+ close(fd);
+
+ /* Set the portname to what the script says the portname should be. */
+ /* but don't require anything to be return from the script exit a good error code */
+
+ if (numlines) {
+ /* Set the portname to what the script says the portname should be. */
+ info2->portname = talloc_strdup(ctx, qlines[0]);
+ DEBUGADD(6,("Line[0] = [%s]\n", qlines[0]));
+ }
+
+ TALLOC_FREE(qlines);
+ return true;
+}
+
+static WERROR update_dsspooler(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ int snum,
+ struct spoolss_SetPrinterInfo2 *printer,
+ struct spoolss_PrinterInfo2 *old_printer)
+{
+ bool force_update = (old_printer == NULL);
+ const char *dnsdomname;
+ const char *longname;
+ const char *uncname;
+ const char *spooling;
+ DATA_BLOB buffer;
+ WERROR result = WERR_OK;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+ bool ok;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ session_info,
+ msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ if (printer->drivername != NULL &&
+ (force_update ||
+ !strequal(printer->drivername, old_printer->drivername))) {
+ ok = push_reg_sz(tmp_ctx, &buffer, printer->drivername);
+ if (!ok) {
+ DEBUG(0, ("%s data corrupted\n", SPOOL_REG_DRIVERNAME));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_DRIVERNAME,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_DRIVERNAME));
+ goto done;
+ }
+
+ if (!force_update) {
+ DEBUG(10,("update_printer: changing driver [%s]! Sending event!\n",
+ printer->drivername));
+
+ notify_printer_driver(global_event_context(), msg_ctx,
+ snum, printer->drivername ?
+ printer->drivername : "");
+ }
+ }
+
+ if (printer->comment != NULL &&
+ (force_update ||
+ !strequal(printer->comment, old_printer->comment))) {
+ ok = push_reg_sz(tmp_ctx, &buffer, printer->comment);
+ if (!ok) {
+ DEBUG(0, ("comment data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_DESCRIPTION,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_DESCRIPTION));
+ goto done;
+ }
+
+ if (!force_update) {
+ notify_printer_comment(global_event_context(), msg_ctx,
+ snum, printer->comment ?
+ printer->comment : "");
+ }
+ }
+
+ if (printer->sharename != NULL &&
+ (force_update ||
+ !strequal(printer->sharename, old_printer->sharename))) {
+ ok = push_reg_sz(tmp_ctx, &buffer, printer->sharename);
+ if (!ok) {
+ DEBUG(0, ("sharename data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRINTSHARENAME,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTSHARENAME));
+ goto done;
+ }
+
+ if (!force_update) {
+ notify_printer_sharename(global_event_context(),
+ msg_ctx,
+ snum, printer->sharename ?
+ printer->sharename : "");
+ }
+
+ /* name change, purge any cache entries for the old */
+ prune_printername_cache();
+ }
+
+ if (printer->printername != NULL &&
+ (force_update ||
+ !strequal(printer->printername, old_printer->printername))) {
+ const char *p;
+
+ p = strrchr(printer->printername, '\\' );
+ if (p != NULL) {
+ p++;
+ } else {
+ p = printer->printername;
+ }
+
+ ok = push_reg_sz(tmp_ctx, &buffer, p);
+ if (!ok) {
+ DEBUG(0, ("printername data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRINTERNAME,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DBG_ERR("Failed to set %s\n", SPOOL_REG_PRINTERNAME);
+ goto done;
+ }
+
+ if (!force_update) {
+ notify_printer_printername(global_event_context(),
+ msg_ctx, snum, p ? p : "");
+ }
+
+ /* name change, purge any cache entries for the old */
+ prune_printername_cache();
+ }
+
+ if (printer->portname != NULL &&
+ (force_update ||
+ !strequal(printer->portname, old_printer->portname))) {
+ ok = push_reg_sz(tmp_ctx, &buffer, printer->portname);
+ if (!ok) {
+ DEBUG(0, ("portname data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PORTNAME,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PORTNAME));
+ goto done;
+ }
+
+ if (!force_update) {
+ notify_printer_port(global_event_context(),
+ msg_ctx, snum, printer->portname ?
+ printer->portname : "");
+ }
+ }
+
+ if (printer->location != NULL &&
+ (force_update ||
+ !strequal(printer->location, old_printer->location))) {
+ ok = push_reg_sz(tmp_ctx, &buffer, printer->location);
+ if (!ok) {
+ DEBUG(0, ("location data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_LOCATION,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_LOCATION));
+ goto done;
+ }
+
+ if (!force_update) {
+ notify_printer_location(global_event_context(),
+ msg_ctx, snum,
+ printer->location ?
+ printer->location : "");
+ }
+ }
+
+ if (printer->sepfile != NULL &&
+ (force_update ||
+ !strequal(printer->sepfile, old_printer->sepfile))) {
+ ok = push_reg_sz(tmp_ctx, &buffer, printer->sepfile);
+ if (!ok) {
+ DEBUG(0, ("sepfile data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRINTSEPARATORFILE,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTSEPARATORFILE));
+ goto done;
+ }
+
+ if (!force_update) {
+ notify_printer_sepfile(global_event_context(),
+ msg_ctx, snum,
+ printer->sepfile ?
+ printer->sepfile : "");
+ }
+ }
+
+ if (printer->starttime != 0 &&
+ (force_update ||
+ printer->starttime != old_printer->starttime)) {
+ buffer = data_blob_talloc(tmp_ctx, NULL, 4);
+ SIVAL(buffer.data, 0, printer->starttime);
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRINTSTARTTIME,
+ REG_DWORD,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTSTARTTIME));
+ goto done;
+ }
+ }
+
+ if (printer->untiltime != 0 &&
+ (force_update ||
+ printer->untiltime != old_printer->untiltime)) {
+ buffer = data_blob_talloc(tmp_ctx, NULL, 4);
+ SIVAL(buffer.data, 0, printer->untiltime);
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRINTENDTIME,
+ REG_DWORD,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTENDTIME));
+ goto done;
+ }
+ }
+
+ if (force_update || printer->priority != old_printer->priority) {
+ buffer = data_blob_talloc(tmp_ctx, NULL, 4);
+ SIVAL(buffer.data, 0, printer->priority);
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRIORITY,
+ REG_DWORD,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTENDTIME));
+ goto done;
+ }
+ }
+
+ if (force_update || printer->attributes != old_printer->attributes) {
+ buffer = data_blob_talloc(tmp_ctx, NULL, 4);
+ SIVAL(buffer.data, 0, (printer->attributes &
+ PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS));
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRINTKEEPPRINTEDJOBS,
+ REG_DWORD,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_PRINTENDTIME));
+ goto done;
+ }
+
+ switch (printer->attributes & 0x3) {
+ case 0:
+ spooling = SPOOL_REGVAL_PRINTWHILESPOOLING;
+ break;
+ case 1:
+ spooling = SPOOL_REGVAL_PRINTAFTERSPOOLED;
+ break;
+ case 2:
+ spooling = SPOOL_REGVAL_PRINTDIRECT;
+ break;
+ default:
+ spooling = "unknown";
+ }
+ ok = push_reg_sz(tmp_ctx, &buffer, spooling);
+ if (!ok) {
+ DEBUG(0, ("printSpooling data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_PRINTSPOOLING,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ }
+
+ ok = push_reg_sz(tmp_ctx, &buffer, lp_netbios_name());
+ if (!ok) {
+ DEBUG(0, ("shortServerName data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_SHORTSERVERNAME,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_SHORTSERVERNAME));
+ goto done;
+ }
+
+ dnsdomname = get_mydnsfullname();
+ if (dnsdomname != NULL && dnsdomname[0] != '\0') {
+ longname = talloc_strdup(tmp_ctx, dnsdomname);
+ } else {
+ longname = talloc_strdup(tmp_ctx, lp_netbios_name());
+ }
+ if (longname == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ ok = push_reg_sz(tmp_ctx, &buffer, longname);
+ if (!ok) {
+ DEBUG(0, ("longname data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_SERVERNAME,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_SERVERNAME));
+ goto done;
+ }
+
+ uncname = talloc_asprintf(tmp_ctx, "\\\\%s\\%s",
+ lp_netbios_name(), printer->sharename);
+ ok = push_reg_sz(tmp_ctx, &buffer, uncname);
+ if (!ok) {
+ DEBUG(0, ("uncName data corrupted\n"));
+ result = WERR_INVALID_DATA;
+ goto done;
+ }
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ printer->sharename,
+ SPOOL_DSSPOOLER_KEY,
+ SPOOL_REG_UNCNAME,
+ REG_SZ,
+ buffer.data,
+ buffer.length);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("Failed to set %s\n", SPOOL_REG_UNCNAME));
+ goto done;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/********************************************************************
+ * Called by spoolss_api_setprinter
+ * when updating a printer description.
+ ********************************************************************/
+
+static WERROR update_printer(struct pipes_struct *p,
+ struct policy_handle *handle,
+ struct spoolss_SetPrinterInfoCtr *info_ctr,
+ struct spoolss_DeviceMode *devmode)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ uint32_t printer_mask = SPOOLSS_PRINTER_INFO_ALL;
+ struct spoolss_SetPrinterInfo2 *printer = info_ctr->info.info2;
+ struct spoolss_PrinterInfo2 *old_printer;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, handle);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ WERROR result = WERR_OK;
+ TALLOC_CTX *tmp_ctx;
+ struct dcerpc_binding_handle *b;
+
+ DEBUG(8,("update_printer\n"));
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!Printer) {
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ if (!get_printer_snum(p, handle, &snum, NULL)) {
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_get_printer(tmp_ctx, b,
+ lp_const_servicename(snum),
+ &old_printer);
+ if (!W_ERROR_IS_OK(result)) {
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ /* Do sanity check on the requested changes for Samba */
+ if (!check_printer_ok(tmp_ctx, printer, snum)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* FIXME!!! If the driver has changed we really should verify that
+ it is installed before doing much else --jerry */
+
+ /* Check calling user has permission to update printer description */
+ if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) {
+ DEBUG(3, ("update_printer: printer property change denied by handle\n"));
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+ /* Call addprinter hook */
+ /* Check changes to see if this is really needed */
+
+ if (*lp_addprinter_command(talloc_tos(), lp_sub) &&
+ (!strequal(printer->drivername, old_printer->drivername) ||
+ !strequal(printer->comment, old_printer->comment) ||
+ !strequal(printer->portname, old_printer->portname) ||
+ !strequal(printer->location, old_printer->location)) )
+ {
+ char *raddr;
+
+ raddr = tsocket_address_inet_addr_string(remote_address,
+ p->mem_ctx);
+ if (raddr == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* add_printer_hook() will call reload_services() */
+ if (!add_printer_hook(tmp_ctx, session_info->security_token,
+ printer, raddr,
+ p->msg_ctx)) {
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+ }
+
+ result = update_dsspooler(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ snum,
+ printer,
+ old_printer);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ printer_mask &= ~SPOOLSS_PRINTER_INFO_SECDESC;
+
+ if (devmode == NULL) {
+ printer_mask &= ~SPOOLSS_PRINTER_INFO_DEVMODE;
+ }
+ result = winreg_update_printer(tmp_ctx, b,
+ printer->sharename,
+ printer_mask,
+ printer,
+ devmode,
+ NULL);
+
+done:
+ talloc_free(tmp_ctx);
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+static WERROR publish_or_unpublish_printer(struct pipes_struct *p,
+ struct policy_handle *handle,
+ struct spoolss_SetPrinterInfo7 *info7)
+{
+#ifdef HAVE_ADS
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ WERROR result;
+ int snum;
+ struct printer_handle *Printer;
+
+ if ( lp_security() != SEC_ADS ) {
+ return WERR_INVALID_LEVEL;
+ }
+
+ Printer = find_printer_index_by_hnd(p, handle);
+
+ DEBUG(5,("publish_or_unpublish_printer, action = %d\n",info7->action));
+
+ if (!Printer)
+ return WERR_INVALID_HANDLE;
+
+ if (!get_printer_snum(p, handle, &snum, NULL))
+ return WERR_INVALID_HANDLE;
+
+ result = winreg_get_printer_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ nt_printer_publish(pinfo2,
+ get_session_info_system(),
+ p->msg_ctx,
+ pinfo2,
+ info7->action);
+
+ TALLOC_FREE(pinfo2);
+ return WERR_OK;
+#else
+ return WERR_INVALID_LEVEL;
+#endif
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static WERROR update_printer_devmode(struct pipes_struct *p,
+ struct policy_handle *handle,
+ struct spoolss_DeviceMode *devmode)
+{
+ int snum;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, handle);
+ uint32_t info2_mask = SPOOLSS_PRINTER_INFO_DEVMODE;
+
+ DEBUG(8,("update_printer_devmode\n"));
+
+ if (!Printer) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* Check calling user has permission to update printer description */
+ if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) {
+ DEBUG(3, ("update_printer: printer property change denied by handle\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ return winreg_update_printer_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_const_servicename(snum),
+ info2_mask,
+ NULL,
+ devmode,
+ NULL);
+}
+
+
+/****************************************************************
+ _spoolss_SetPrinter
+****************************************************************/
+
+WERROR _spoolss_SetPrinter(struct pipes_struct *p,
+ struct spoolss_SetPrinter *r)
+{
+ WERROR result;
+
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_SetPrinter: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* check the level */
+ switch (r->in.info_ctr->level) {
+ case 0:
+ return control_printer(r->in.handle, r->in.command, p);
+ case 2:
+ result = update_printer(p, r->in.handle,
+ r->in.info_ctr,
+ r->in.devmode_ctr->devmode);
+ if (!W_ERROR_IS_OK(result))
+ return result;
+ if (r->in.secdesc_ctr->sd)
+ result = update_printer_sec(r->in.handle, p,
+ r->in.secdesc_ctr);
+ return result;
+ case 3:
+ return update_printer_sec(r->in.handle, p,
+ r->in.secdesc_ctr);
+ case 4: {
+ struct spoolss_PrinterInfo2 *old_printer;
+ struct spoolss_SetPrinterInfo2 *set_old_printer;
+ struct spoolss_SetPrinterInfoCtr *info_ctr;
+ struct dcerpc_binding_handle *b;
+ int snum;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_INVALID_HANDLE;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_printer(tmp_ctx, b,
+ lp_const_servicename(snum),
+ &old_printer);
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_INVALID_HANDLE;
+ }
+
+ old_printer->servername = talloc_strdup(tmp_ctx, r->in.info_ctr->info.info4->servername);
+ if (old_printer->servername == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ old_printer->printername = talloc_strdup(tmp_ctx, r->in.info_ctr->info.info4->printername);
+ if (old_printer->printername == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ old_printer->attributes = r->in.info_ctr->info.info4->attributes;
+
+ set_old_printer = talloc_zero(tmp_ctx, struct spoolss_SetPrinterInfo2);
+ if (set_old_printer == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ spoolss_printerinfo2_to_setprinterinfo2(old_printer, set_old_printer);
+
+ info_ctr = talloc_zero(tmp_ctx, struct spoolss_SetPrinterInfoCtr);
+ if (info_ctr == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ info_ctr->level = 2;
+ info_ctr->info.info2 = set_old_printer;
+
+ result = update_printer(p, r->in.handle,
+ info_ctr,
+ r->in.devmode_ctr->devmode);
+
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(tmp_ctx);
+ return result;
+ }
+
+ if (r->in.secdesc_ctr->sd) {
+ result = update_printer_sec(r->in.handle, p,
+ r->in.secdesc_ctr);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return result;
+ }
+ case 7:
+ return publish_or_unpublish_printer(p, r->in.handle,
+ r->in.info_ctr->info.info7);
+ case 8:
+ return update_printer_devmode(p, r->in.handle,
+ r->in.devmode_ctr->devmode);
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+}
+
+/****************************************************************
+ _spoolss_FindClosePrinterNotify
+****************************************************************/
+
+WERROR _spoolss_FindClosePrinterNotify(struct pipes_struct *p,
+ struct spoolss_FindClosePrinterNotify *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_FindClosePrinterNotify: "
+ "Invalid handle (%s:%u:%u)\n", OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (Printer->notify.cli_chan != NULL &&
+ Printer->notify.cli_chan->active_connections > 0) {
+ int snum = -1;
+
+ if (Printer->printer_type == SPLHND_PRINTER) {
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+ }
+
+ srv_spoolss_replycloseprinter(snum, Printer);
+ }
+
+ Printer->notify.flags=0;
+ Printer->notify.options=0;
+ Printer->notify.localmachine[0]='\0';
+ Printer->notify.printerlocal=0;
+ TALLOC_FREE(Printer->notify.option);
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_AddJob
+****************************************************************/
+
+WERROR _spoolss_AddJob(struct pipes_struct *p,
+ struct spoolss_AddJob *r)
+{
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* this is what a NT server returns for AddJob. AddJob must fail on
+ * non-local printers */
+
+ if (r->in.level != 1) {
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_INVALID_PARAMETER;
+}
+
+/****************************************************************************
+fill_job_info1
+****************************************************************************/
+
+static WERROR fill_job_info1(TALLOC_CTX *mem_ctx,
+ struct spoolss_JobInfo1 *r,
+ const print_queue_struct *queue,
+ uint32_t jobid,
+ int position, int snum,
+ struct spoolss_PrinterInfo2 *pinfo2)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct tm *t;
+
+ t = gmtime(&queue->time);
+
+ r->job_id = jobid;
+
+ r->printer_name = lp_servicename(mem_ctx, lp_sub, snum);
+ W_ERROR_HAVE_NO_MEMORY(r->printer_name);
+ r->server_name = talloc_strdup(mem_ctx, pinfo2->servername);
+ W_ERROR_HAVE_NO_MEMORY(r->server_name);
+ r->user_name = talloc_strdup(mem_ctx, queue->fs_user);
+ W_ERROR_HAVE_NO_MEMORY(r->user_name);
+ r->document_name = talloc_strdup(mem_ctx, queue->fs_file);
+ W_ERROR_HAVE_NO_MEMORY(r->document_name);
+ r->data_type = talloc_strdup(mem_ctx, "RAW");
+ W_ERROR_HAVE_NO_MEMORY(r->data_type);
+ r->text_status = talloc_strdup(mem_ctx, "");
+ W_ERROR_HAVE_NO_MEMORY(r->text_status);
+
+ r->status = nt_printj_status(queue->status);
+ r->priority = queue->priority;
+ r->position = position;
+ r->total_pages = queue->page_count;
+ r->pages_printed = 0; /* ??? */
+
+ init_systemtime(&r->submitted, t);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+fill_job_info2
+****************************************************************************/
+
+static WERROR fill_job_info2(TALLOC_CTX *mem_ctx,
+ struct spoolss_JobInfo2 *r,
+ const print_queue_struct *queue,
+ uint32_t jobid,
+ int position, int snum,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ struct spoolss_DeviceMode *devmode)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct tm *t;
+
+ t = gmtime(&queue->time);
+
+ r->job_id = jobid;
+
+ r->printer_name = lp_servicename(mem_ctx, lp_sub, snum);
+ W_ERROR_HAVE_NO_MEMORY(r->printer_name);
+ r->server_name = talloc_strdup(mem_ctx, pinfo2->servername);
+ W_ERROR_HAVE_NO_MEMORY(r->server_name);
+ r->user_name = talloc_strdup(mem_ctx, queue->fs_user);
+ W_ERROR_HAVE_NO_MEMORY(r->user_name);
+ r->document_name = talloc_strdup(mem_ctx, queue->fs_file);
+ W_ERROR_HAVE_NO_MEMORY(r->document_name);
+ r->notify_name = talloc_strdup(mem_ctx, queue->fs_user);
+ W_ERROR_HAVE_NO_MEMORY(r->notify_name);
+ r->data_type = talloc_strdup(mem_ctx, "RAW");
+ W_ERROR_HAVE_NO_MEMORY(r->data_type);
+ r->print_processor = talloc_strdup(mem_ctx, "winprint");
+ W_ERROR_HAVE_NO_MEMORY(r->print_processor);
+ r->parameters = talloc_strdup(mem_ctx, "");
+ W_ERROR_HAVE_NO_MEMORY(r->parameters);
+ r->driver_name = talloc_strdup(mem_ctx, pinfo2->drivername);
+ W_ERROR_HAVE_NO_MEMORY(r->driver_name);
+
+ r->devmode = devmode;
+
+ r->text_status = talloc_strdup(mem_ctx, "");
+ W_ERROR_HAVE_NO_MEMORY(r->text_status);
+
+ r->secdesc = NULL;
+
+ r->status = nt_printj_status(queue->status);
+ r->priority = queue->priority;
+ r->position = position;
+ r->start_time = 0;
+ r->until_time = 0;
+ r->total_pages = queue->page_count;
+ r->size = queue->size;
+ init_systemtime(&r->submitted, t);
+ r->time = 0;
+ r->pages_printed = 0; /* ??? */
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ Enumjobs at level 1.
+****************************************************************************/
+
+static WERROR enumjobs_level1(TALLOC_CTX *mem_ctx,
+ const print_queue_struct *queue,
+ uint32_t num_queues, int snum,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ union spoolss_JobInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_JobInfo *info;
+ int i;
+ WERROR result = WERR_OK;
+ uint32_t num_filled;
+ struct tdb_print_db *pdb;
+
+ info = talloc_array(mem_ctx, union spoolss_JobInfo, num_queues);
+ if (info == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ pdb = get_print_db_byname(pinfo2->sharename);
+ if (pdb == NULL) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_info_free;
+ }
+
+ num_filled = 0;
+ for (i = 0; i < num_queues; i++) {
+ uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ DEBUG(4, ("skipping sysjob %d\n", queue[i].sysjob));
+ continue;
+ }
+
+ result = fill_job_info1(info,
+ &info[num_filled].info1,
+ &queue[i],
+ jobid,
+ i,
+ snum,
+ pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_pdb_drop;
+ }
+
+ num_filled++;
+ }
+
+ release_print_db(pdb);
+ *info_p = info;
+ *count = num_filled;
+
+ return WERR_OK;
+
+err_pdb_drop:
+ release_print_db(pdb);
+err_info_free:
+ TALLOC_FREE(info);
+err_out:
+ *count = 0;
+ return result;
+}
+
+/****************************************************************************
+ Enumjobs at level 2.
+****************************************************************************/
+
+static WERROR enumjobs_level2(TALLOC_CTX *mem_ctx,
+ const print_queue_struct *queue,
+ uint32_t num_queues, int snum,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ union spoolss_JobInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_JobInfo *info;
+ int i;
+ WERROR result = WERR_OK;
+ uint32_t num_filled;
+ struct tdb_print_db *pdb;
+
+ info = talloc_array(mem_ctx, union spoolss_JobInfo, num_queues);
+ if (info == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ pdb = get_print_db_byname(pinfo2->sharename);
+ if (pdb == NULL) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_info_free;
+ }
+
+ num_filled = 0;
+ for (i = 0; i< num_queues; i++) {
+ struct spoolss_DeviceMode *devmode;
+ uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ DEBUG(4, ("skipping sysjob %d\n", queue[i].sysjob));
+ continue;
+ }
+
+ result = spoolss_create_default_devmode(info,
+ pinfo2->printername,
+ &devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3, ("Can't proceed w/o a devmode!\n"));
+ goto err_pdb_drop;
+ }
+
+ result = fill_job_info2(info,
+ &info[num_filled].info2,
+ &queue[i],
+ jobid,
+ i,
+ snum,
+ pinfo2,
+ devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_pdb_drop;
+ }
+ num_filled++;
+ }
+
+ release_print_db(pdb);
+ *info_p = info;
+ *count = num_filled;
+
+ return WERR_OK;
+
+err_pdb_drop:
+ release_print_db(pdb);
+err_info_free:
+ TALLOC_FREE(info);
+err_out:
+ *count = 0;
+ return result;
+}
+
+/****************************************************************************
+ Enumjobs at level 3.
+****************************************************************************/
+
+static WERROR enumjobs_level3(TALLOC_CTX *mem_ctx,
+ const print_queue_struct *queue,
+ uint32_t num_queues, int snum,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ union spoolss_JobInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_JobInfo *info;
+ int i;
+ WERROR result = WERR_OK;
+ uint32_t num_filled;
+ struct tdb_print_db *pdb;
+
+ info = talloc_array(mem_ctx, union spoolss_JobInfo, num_queues);
+ if (info == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto err_out;
+ }
+
+ pdb = get_print_db_byname(pinfo2->sharename);
+ if (pdb == NULL) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_info_free;
+ }
+
+ num_filled = 0;
+ for (i = 0; i < num_queues; i++) {
+ uint32_t jobid = sysjob_to_jobid_pdb(pdb, queue[i].sysjob);
+ if (jobid == (uint32_t)-1) {
+ DEBUG(4, ("skipping sysjob %d\n", queue[i].sysjob));
+ continue;
+ }
+
+ info[num_filled].info3.job_id = jobid;
+ /* next_job_id is overwritten on next iteration */
+ info[num_filled].info3.next_job_id = 0;
+ info[num_filled].info3.reserved = 0;
+
+ if (num_filled > 0) {
+ info[num_filled - 1].info3.next_job_id = jobid;
+ }
+ num_filled++;
+ }
+
+ release_print_db(pdb);
+ *info_p = info;
+ *count = num_filled;
+
+ return WERR_OK;
+
+err_info_free:
+ TALLOC_FREE(info);
+err_out:
+ *count = 0;
+ return result;
+}
+
+/****************************************************************
+ _spoolss_EnumJobs
+****************************************************************/
+
+WERROR _spoolss_EnumJobs(struct pipes_struct *p,
+ struct spoolss_EnumJobs *r)
+{
+ WERROR result;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ int snum;
+ print_status_struct prt_status;
+ print_queue_struct *queue = NULL;
+ uint32_t count;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if ((r->in.level != 1) && (r->in.level != 2) && (r->in.level != 3)) {
+ DEBUG(4, ("EnumJobs level %d not supported\n", r->in.level));
+ return WERR_INVALID_LEVEL;
+ }
+
+ DEBUG(4,("_spoolss_EnumJobs\n"));
+
+ *r->out.needed = 0;
+ *r->out.count = 0;
+ *r->out.info = NULL;
+
+ /* lookup the printer snum and tdb entry */
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ result = winreg_get_printer_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_const_servicename(snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ count = print_queue_status(p->msg_ctx, snum, &queue, &prt_status);
+ DEBUGADD(4,("count:[%d], status:[%d], [%s]\n",
+ count, prt_status.status, prt_status.message));
+
+ if (count == 0) {
+ SAFE_FREE(queue);
+ TALLOC_FREE(pinfo2);
+ return WERR_OK;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ result = enumjobs_level1(p->mem_ctx, queue, count, snum,
+ pinfo2, r->out.info, r->out.count);
+ break;
+ case 2:
+ result = enumjobs_level2(p->mem_ctx, queue, count, snum,
+ pinfo2, r->out.info, r->out.count);
+ break;
+ case 3:
+ result = enumjobs_level3(p->mem_ctx, queue, count, snum,
+ pinfo2, r->out.info, r->out.count);
+ break;
+ default:
+ SMB_ASSERT(false); /* level checked on entry */
+ break;
+ }
+
+ SAFE_FREE(queue);
+ TALLOC_FREE(pinfo2);
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumJobs,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************
+ _spoolss_ScheduleJob
+****************************************************************/
+
+WERROR _spoolss_ScheduleJob(struct pipes_struct *p,
+ struct spoolss_ScheduleJob *r)
+{
+ return WERR_OK;
+}
+
+/****************************************************************
+****************************************************************/
+
+static WERROR spoolss_setjob_1(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const char *printer_name,
+ uint32_t job_id,
+ struct spoolss_SetJobInfo1 *r)
+{
+ char *old_doc_name;
+
+ if (!print_job_get_name(mem_ctx, printer_name, job_id, &old_doc_name)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (strequal(old_doc_name, r->document_name)) {
+ return WERR_OK;
+ }
+
+ if (!print_job_set_name(global_event_context(), msg_ctx,
+ printer_name, job_id, r->document_name)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_SetJob
+****************************************************************/
+
+WERROR _spoolss_SetJob(struct pipes_struct *p,
+ struct spoolss_SetJob *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ int snum;
+ WERROR errcode = WERR_INVALID_FUNCTION;
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!print_job_exists(lp_const_servicename(snum), r->in.job_id)) {
+ return WERR_INVALID_PRINTER_NAME;
+ }
+
+ switch (r->in.command) {
+ case SPOOLSS_JOB_CONTROL_CANCEL:
+ case SPOOLSS_JOB_CONTROL_DELETE:
+ errcode = print_job_delete(session_info, p->msg_ctx,
+ snum, r->in.job_id);
+ if (W_ERROR_EQUAL(errcode, WERR_PRINTER_HAS_JOBS_QUEUED)) {
+ errcode = WERR_OK;
+ }
+ break;
+ case SPOOLSS_JOB_CONTROL_PAUSE:
+ errcode = print_job_pause(session_info, p->msg_ctx,
+ snum, r->in.job_id);
+ break;
+ case SPOOLSS_JOB_CONTROL_RESTART:
+ case SPOOLSS_JOB_CONTROL_RESUME:
+ errcode = print_job_resume(session_info, p->msg_ctx,
+ snum, r->in.job_id);
+ break;
+ case SPOOLSS_JOB_CONTROL_NOOP:
+ errcode = WERR_OK;
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (!W_ERROR_IS_OK(errcode)) {
+ return errcode;
+ }
+
+ if (r->in.ctr == NULL) {
+ return errcode;
+ }
+
+ switch (r->in.ctr->level) {
+ case 1:
+ errcode = spoolss_setjob_1(p->mem_ctx, p->msg_ctx,
+ lp_const_servicename(snum),
+ r->in.job_id,
+ r->in.ctr->info.info1);
+ break;
+ case 2:
+ case 3:
+ case 4:
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return errcode;
+}
+
+/****************************************************************************
+ Enumerates all printer drivers by level and architecture.
+****************************************************************************/
+
+static WERROR enumprinterdrivers_level_by_architecture(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ const char *architecture,
+ uint32_t level,
+ union spoolss_DriverInfo **info_p,
+ uint32_t *count_p)
+{
+ int i;
+ uint32_t version;
+ struct spoolss_DriverInfo8 *driver;
+ union spoolss_DriverInfo *info = NULL;
+ uint32_t count = 0;
+ WERROR result = WERR_OK;
+ uint32_t num_drivers;
+ const char **drivers;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ *count_p = 0;
+ *info_p = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ session_info,
+ msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ for (version=0; version<DRIVER_MAX_VERSION; version++) {
+ result = winreg_get_driver_list(tmp_ctx, b,
+ architecture, version,
+ &num_drivers, &drivers);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+ DEBUG(4, ("we have:[%d] drivers in environment"
+ " [%s] and version [%d]\n",
+ num_drivers, architecture, version));
+
+ if (num_drivers != 0) {
+ info = talloc_realloc(tmp_ctx, info,
+ union spoolss_DriverInfo,
+ count + num_drivers);
+ if (!info) {
+ DEBUG(0,("enumprinterdrivers_level_by_architecture: "
+ "failed to enlarge driver info buffer!\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+ }
+
+ for (i = 0; i < num_drivers; i++) {
+ DEBUG(5, ("\tdriver: [%s]\n", drivers[i]));
+
+ result = winreg_get_driver(tmp_ctx, b,
+ architecture, drivers[i],
+ version, &driver);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ switch (level) {
+ case 1:
+ result = fill_printer_driver_info1(info, &info[count+i].info1,
+ driver, servername);
+ break;
+ case 2:
+ result = fill_printer_driver_info2(info, &info[count+i].info2,
+ driver, servername);
+ break;
+ case 3:
+ result = fill_printer_driver_info3(info, &info[count+i].info3,
+ driver, servername);
+ break;
+ case 4:
+ result = fill_printer_driver_info4(info, &info[count+i].info4,
+ driver, servername);
+ break;
+ case 5:
+ result = fill_printer_driver_info5(info, &info[count+i].info5,
+ driver, servername);
+ break;
+ case 6:
+ result = fill_printer_driver_info6(info, &info[count+i].info6,
+ driver, servername);
+ break;
+ case 8:
+ result = fill_printer_driver_info8(info, &info[count+i].info8,
+ driver, servername);
+ break;
+ default:
+ result = WERR_INVALID_LEVEL;
+ break;
+ }
+
+ TALLOC_FREE(driver);
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+ }
+
+ count += num_drivers;
+ TALLOC_FREE(drivers);
+ }
+
+out:
+ if (W_ERROR_IS_OK(result)) {
+ *info_p = talloc_move(mem_ctx, &info);
+ *count_p = count;
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/****************************************************************************
+ Enumerates all printer drivers by level.
+****************************************************************************/
+
+static WERROR enumprinterdrivers_level(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *servername,
+ const char *architecture,
+ uint32_t level,
+ union spoolss_DriverInfo **info_p,
+ uint32_t *count_p)
+{
+ uint32_t a,i;
+ WERROR result = WERR_OK;
+
+ if (strequal(architecture, SPOOLSS_ARCHITECTURE_ALL)) {
+
+ for (a=0; archi_table[a].long_archi != NULL; a++) {
+
+ union spoolss_DriverInfo *info = NULL;
+ uint32_t count = 0;
+
+ result = enumprinterdrivers_level_by_architecture(mem_ctx,
+ session_info,
+ msg_ctx,
+ servername,
+ archi_table[a].long_archi,
+ level,
+ &info,
+ &count);
+ if (!W_ERROR_IS_OK(result)) {
+ continue;
+ }
+
+ for (i=0; i < count; i++) {
+ ADD_TO_ARRAY(mem_ctx, union spoolss_DriverInfo,
+ info[i], info_p, count_p);
+ }
+ }
+
+ return result;
+ }
+
+ return enumprinterdrivers_level_by_architecture(mem_ctx,
+ session_info,
+ msg_ctx,
+ servername,
+ architecture,
+ level,
+ info_p,
+ count_p);
+}
+
+/****************************************************************
+ _spoolss_EnumPrinterDrivers
+****************************************************************/
+
+WERROR _spoolss_EnumPrinterDrivers(struct pipes_struct *p,
+ struct spoolss_EnumPrinterDrivers *r)
+{
+ const char *cservername;
+ WERROR result;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(4,("_spoolss_EnumPrinterDrivers\n"));
+
+ *r->out.needed = 0;
+ *r->out.count = 0;
+ *r->out.info = NULL;
+
+ cservername = canon_servername(r->in.server);
+
+ if (!is_myname_or_ipaddr(cservername)) {
+ return WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+
+ result = enumprinterdrivers_level(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ cservername,
+ r->in.environment,
+ r->in.level,
+ r->out.info,
+ r->out.count);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumPrinterDrivers,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************
+ _spoolss_EnumForms
+****************************************************************/
+
+WERROR _spoolss_EnumForms(struct pipes_struct *p,
+ struct spoolss_EnumForms *r)
+{
+ WERROR result;
+
+ *r->out.count = 0;
+ *r->out.needed = 0;
+ *r->out.info = NULL;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0) ) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(4,("_spoolss_EnumForms\n"));
+ DEBUGADD(5,("Offered buffer size [%d]\n", r->in.offered));
+ DEBUGADD(5,("Info level [%d]\n", r->in.level));
+
+ switch (r->in.level) {
+ case 1:
+ result = winreg_printer_enumforms1_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ r->out.count,
+ r->out.info);
+ break;
+ default:
+ result = WERR_INVALID_LEVEL;
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ if (*r->out.count == 0) {
+ return WERR_NO_MORE_ITEMS;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumForms,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************
+ _spoolss_GetForm
+****************************************************************/
+
+WERROR _spoolss_GetForm(struct pipes_struct *p,
+ struct spoolss_GetForm *r)
+{
+ WERROR result;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ TALLOC_FREE(r->out.info);
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(4,("_spoolss_GetForm\n"));
+ DEBUGADD(5,("Offered buffer size [%d]\n", r->in.offered));
+ DEBUGADD(5,("Info level [%d]\n", r->in.level));
+
+ switch (r->in.level) {
+ case 1:
+ result = winreg_printer_getform1_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ r->in.form_name,
+ &r->out.info->info1);
+ break;
+ default:
+ result = WERR_INVALID_LEVEL;
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(r->out.info);
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_FormInfo,
+ r->out.info, r->in.level);
+ r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR fill_port_1(TALLOC_CTX *mem_ctx,
+ struct spoolss_PortInfo1 *r,
+ const char *name)
+{
+ r->port_name = talloc_strdup(mem_ctx, name);
+ W_ERROR_HAVE_NO_MEMORY(r->port_name);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ TODO: This probably needs distinguish between TCP/IP and Local ports
+ somehow.
+****************************************************************************/
+
+static WERROR fill_port_2(TALLOC_CTX *mem_ctx,
+ struct spoolss_PortInfo2 *r,
+ const char *name)
+{
+ r->port_name = talloc_strdup(mem_ctx, name);
+ W_ERROR_HAVE_NO_MEMORY(r->port_name);
+
+ r->monitor_name = talloc_strdup(mem_ctx, "Local Monitor");
+ W_ERROR_HAVE_NO_MEMORY(r->monitor_name);
+
+ r->description = talloc_strdup(mem_ctx, SPL_LOCAL_PORT);
+ W_ERROR_HAVE_NO_MEMORY(r->description);
+
+ r->port_type = SPOOLSS_PORT_TYPE_WRITE;
+ r->reserved = 0;
+
+ return WERR_OK;
+}
+
+
+/****************************************************************************
+ wrapper around the enum ports command
+****************************************************************************/
+
+static WERROR enumports_hook(TALLOC_CTX *ctx, int *count, char ***lines)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *cmd = lp_enumports_command(talloc_tos(), lp_sub);
+ char **qlines = NULL;
+ char *command = NULL;
+ int numlines;
+ int ret;
+ int fd;
+
+ *count = 0;
+ *lines = NULL;
+
+ /* if no hook then just fill in the default port */
+
+ if ( !*cmd ) {
+ if (!(qlines = talloc_array( NULL, char*, 2 ))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (!(qlines[0] = talloc_strdup(qlines, SAMBA_PRINTER_PORT_NAME ))) {
+ TALLOC_FREE(qlines);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ qlines[1] = NULL;
+ numlines = 1;
+ }
+ else {
+ /* we have a valid enumport command */
+
+ command = talloc_asprintf(ctx, "%s \"%d\"", cmd, 1);
+ if (!command) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ DEBUG(10,("Running [%s]\n", command));
+ ret = smbrun(command, &fd, NULL);
+ DEBUG(10,("Returned [%d]\n", ret));
+ TALLOC_FREE(command);
+ if (ret != 0) {
+ if (fd != -1) {
+ close(fd);
+ }
+ return WERR_ACCESS_DENIED;
+ }
+
+ numlines = 0;
+ qlines = fd_lines_load(fd, &numlines, 0, NULL);
+ DEBUGADD(10,("Lines returned = [%d]\n", numlines));
+ close(fd);
+ }
+
+ *count = numlines;
+ *lines = qlines;
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ enumports level 1.
+****************************************************************************/
+
+static WERROR enumports_level_1(TALLOC_CTX *mem_ctx,
+ union spoolss_PortInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_PortInfo *info = NULL;
+ int i=0;
+ WERROR result = WERR_OK;
+ char **qlines = NULL;
+ int numlines = 0;
+
+ result = enumports_hook(talloc_tos(), &numlines, &qlines );
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ if (numlines) {
+ info = talloc_array(mem_ctx, union spoolss_PortInfo, numlines);
+ if (!info) {
+ DEBUG(10,("Returning WERR_NOT_ENOUGH_MEMORY\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ for (i=0; i<numlines; i++) {
+ DEBUG(6,("Filling port number [%d] with port [%s]\n", i, qlines[i]));
+ result = fill_port_1(info, &info[i].info1, qlines[i]);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+ }
+ }
+ TALLOC_FREE(qlines);
+
+out:
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(info);
+ TALLOC_FREE(qlines);
+ *count = 0;
+ *info_p = NULL;
+ return result;
+ }
+
+ *info_p = info;
+ *count = numlines;
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ enumports level 2.
+****************************************************************************/
+
+static WERROR enumports_level_2(TALLOC_CTX *mem_ctx,
+ union spoolss_PortInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_PortInfo *info = NULL;
+ int i=0;
+ WERROR result = WERR_OK;
+ char **qlines = NULL;
+ int numlines = 0;
+
+ result = enumports_hook(talloc_tos(), &numlines, &qlines );
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ if (numlines) {
+ info = talloc_array(mem_ctx, union spoolss_PortInfo, numlines);
+ if (!info) {
+ DEBUG(10,("Returning WERR_NOT_ENOUGH_MEMORY\n"));
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto out;
+ }
+
+ for (i=0; i<numlines; i++) {
+ DEBUG(6,("Filling port number [%d] with port [%s]\n", i, qlines[i]));
+ result = fill_port_2(info, &info[i].info2, qlines[i]);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+ }
+ }
+ TALLOC_FREE(qlines);
+
+out:
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(info);
+ TALLOC_FREE(qlines);
+ *count = 0;
+ *info_p = NULL;
+ return result;
+ }
+
+ *info_p = info;
+ *count = numlines;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_EnumPorts
+****************************************************************/
+
+WERROR _spoolss_EnumPorts(struct pipes_struct *p,
+ struct spoolss_EnumPorts *r)
+{
+ WERROR result;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(4,("_spoolss_EnumPorts\n"));
+
+ *r->out.count = 0;
+ *r->out.needed = 0;
+ *r->out.info = NULL;
+
+ switch (r->in.level) {
+ case 1:
+ result = enumports_level_1(p->mem_ctx, r->out.info,
+ r->out.count);
+ break;
+ case 2:
+ result = enumports_level_2(p->mem_ctx, r->out.info,
+ r->out.count);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumPorts,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR spoolss_addprinterex_level_2(struct pipes_struct *p,
+ const char *server,
+ struct spoolss_SetPrinterInfoCtr *info_ctr,
+ struct spoolss_DeviceMode *devmode,
+ struct security_descriptor *secdesc,
+ struct spoolss_UserLevelCtr *user_ctr,
+ struct policy_handle *handle)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct spoolss_SetPrinterInfo2 *info2 = info_ctr->info.info2;
+ uint32_t info2_mask = SPOOLSS_PRINTER_INFO_ALL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ WERROR err = WERR_OK;
+
+ /* samba does not have a concept of local, non-shared printers yet, so
+ * make sure we always setup sharename - gd */
+ if ((info2->sharename == NULL || info2->sharename[0] == '\0') &&
+ (info2->printername != NULL && info2->printername[0] != '\0')) {
+ DEBUG(5, ("spoolss_addprinterex_level_2: "
+ "no sharename has been set, setting printername %s as sharename\n",
+ info2->printername));
+ info2->sharename = info2->printername;
+ }
+
+ /* check to see if the printer already exists */
+ if ((snum = print_queue_snum(info2->sharename)) != -1) {
+ DEBUG(5, ("spoolss_addprinterex_level_2: Attempted to add a printer named [%s] when one already existed!\n",
+ info2->sharename));
+ return WERR_PRINTER_ALREADY_EXISTS;
+ }
+
+ if (!lp_force_printername(GLOBAL_SECTION_SNUM)) {
+ if ((snum = print_queue_snum(info2->printername)) != -1) {
+ DEBUG(5, ("spoolss_addprinterex_level_2: Attempted to add a printer named [%s] when one already existed!\n",
+ info2->printername));
+ return WERR_PRINTER_ALREADY_EXISTS;
+ }
+ }
+
+ /* validate printer info struct */
+ if (!info2->printername || strlen(info2->printername) == 0) {
+ return WERR_INVALID_PRINTER_NAME;
+ }
+ if (!info2->portname || strlen(info2->portname) == 0) {
+ return WERR_UNKNOWN_PORT;
+ }
+ if (!info2->drivername || strlen(info2->drivername) == 0) {
+ return WERR_UNKNOWN_PRINTER_DRIVER;
+ }
+ if (!info2->printprocessor || strlen(info2->printprocessor) == 0) {
+ return WERR_UNKNOWN_PRINTPROCESSOR;
+ }
+
+ /* FIXME!!! smbd should check to see if the driver is installed before
+ trying to add a printer like this --jerry */
+
+ if (*lp_addprinter_command(talloc_tos(), lp_sub) ) {
+ char *raddr;
+
+ raddr = tsocket_address_inet_addr_string(remote_address,
+ p->mem_ctx);
+ if (raddr == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if ( !add_printer_hook(p->mem_ctx, session_info->security_token,
+ info2, raddr,
+ p->msg_ctx) ) {
+ return WERR_ACCESS_DENIED;
+ }
+ } else {
+ DEBUG(0,("spoolss_addprinterex_level_2: add printer for printer %s called and no "
+ "smb.conf parameter \"addprinter command\" is defined. This "
+ "parameter must exist for this call to succeed\n",
+ info2->sharename ));
+ }
+
+ if ((snum = print_queue_snum(info2->sharename)) == -1) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* you must be a printer admin to add a new printer */
+ if (!W_ERROR_IS_OK(print_access_check(session_info,
+ p->msg_ctx,
+ snum,
+ PRINTER_ACCESS_ADMINISTER))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /*
+ * Do sanity check on the requested changes for Samba.
+ */
+
+ if (!check_printer_ok(p->mem_ctx, info2, snum)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (devmode == NULL) {
+ info2_mask &= ~SPOOLSS_PRINTER_INFO_DEVMODE;
+ }
+
+ err = update_dsspooler(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ 0,
+ info2,
+ NULL);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ err = winreg_update_printer_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ info2->sharename,
+ info2_mask,
+ info2,
+ devmode,
+ secdesc);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ err = open_printer_hnd(p, handle, info2->printername, PRINTER_ACCESS_ADMINISTER);
+ if (!W_ERROR_IS_OK(err)) {
+ /* Handle open failed - remove addition. */
+ ZERO_STRUCTP(handle);
+ return err;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_AddPrinterEx
+****************************************************************/
+
+WERROR _spoolss_AddPrinterEx(struct pipes_struct *p,
+ struct spoolss_AddPrinterEx *r)
+{
+ switch (r->in.info_ctr->level) {
+ case 1:
+ /* we don't handle yet */
+ /* but I know what to do ... */
+ return WERR_INVALID_LEVEL;
+ case 2:
+ return spoolss_addprinterex_level_2(p, r->in.server,
+ r->in.info_ctr,
+ r->in.devmode_ctr->devmode,
+ r->in.secdesc_ctr->sd,
+ r->in.userlevel_ctr,
+ r->out.handle);
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+}
+
+/****************************************************************
+ _spoolss_AddPrinter
+****************************************************************/
+
+WERROR _spoolss_AddPrinter(struct pipes_struct *p,
+ struct spoolss_AddPrinter *r)
+{
+ struct spoolss_AddPrinterEx a;
+ struct spoolss_UserLevelCtr userlevel_ctr;
+
+ ZERO_STRUCT(userlevel_ctr);
+
+ userlevel_ctr.level = 1;
+
+ a.in.server = r->in.server;
+ a.in.info_ctr = r->in.info_ctr;
+ a.in.devmode_ctr = r->in.devmode_ctr;
+ a.in.secdesc_ctr = r->in.secdesc_ctr;
+ a.in.userlevel_ctr = &userlevel_ctr;
+ a.out.handle = r->out.handle;
+
+ return _spoolss_AddPrinterEx(p, &a);
+}
+
+/****************************************************************
+ _spoolss_AddPrinterDriverEx
+****************************************************************/
+
+WERROR _spoolss_AddPrinterDriverEx(struct pipes_struct *p,
+ struct spoolss_AddPrinterDriverEx *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ WERROR err = WERR_OK;
+ const char *driver_name = NULL;
+ const char *driver_directory = NULL;
+ uint32_t version;
+
+ /*
+ * we only support the semantics of AddPrinterDriver()
+ * i.e. only copy files that are newer than existing ones
+ */
+
+ if (r->in.flags == 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!(r->in.flags & APD_COPY_ALL_FILES) &&
+ !(r->in.flags & APD_COPY_NEW_FILES)) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* FIXME */
+ if (r->in.info_ctr->level != 3 &&
+ r->in.info_ctr->level != 6 &&
+ r->in.info_ctr->level != 8) {
+ DEBUG(0,("%s: level %d not yet implemented\n", __func__,
+ r->in.info_ctr->level));
+ return WERR_INVALID_LEVEL;
+ }
+
+ DEBUG(5,("Cleaning driver's information\n"));
+ err = clean_up_driver_struct(p->mem_ctx,
+ session_info,
+ r->in.info_ctr,
+ r->in.flags,
+ &driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ DBG_ERR("clean_up_driver_struct failed - %s\n",
+ win_errstr(err));
+ goto done;
+ }
+
+ DEBUG(5,("Moving driver to final destination\n"));
+ err = move_driver_to_download_area(session_info,
+ r->in.info_ctr,
+ driver_directory);
+ if (!W_ERROR_IS_OK(err)) {
+ DBG_ERR("move_driver_to_download_area failed - %s\n",
+ win_errstr(err));
+ goto done;
+ }
+
+ err = winreg_add_driver_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ r->in.info_ctr,
+ &driver_name,
+ &version);
+ if (!W_ERROR_IS_OK(err)) {
+ DBG_ERR("winreg_add_driver_internal failed - %s\n",
+ win_errstr(err));
+ goto done;
+ }
+
+ /*
+ * I think this is where the DrvUpgradePrinter() hook would be
+ * be called in a driver's interface DLL on a Windows NT 4.0/2k
+ * server. Right now, we just need to send ourselves a message
+ * to update each printer bound to this driver. --jerry
+ */
+
+ if (!srv_spoolss_drv_upgrade_printer(driver_name, p->msg_ctx)) {
+ DEBUG(0,("%s: Failed to send message about upgrading driver [%s]!\n",
+ __func__, driver_name));
+ }
+
+done:
+ return err;
+}
+
+/****************************************************************
+ _spoolss_AddPrinterDriver
+****************************************************************/
+
+WERROR _spoolss_AddPrinterDriver(struct pipes_struct *p,
+ struct spoolss_AddPrinterDriver *r)
+{
+ struct spoolss_AddPrinterDriverEx a;
+
+ switch (r->in.info_ctr->level) {
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ a.in.servername = r->in.servername;
+ a.in.info_ctr = r->in.info_ctr;
+ a.in.flags = APD_COPY_NEW_FILES;
+
+ return _spoolss_AddPrinterDriverEx(p, &a);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+struct _spoolss_paths {
+ int type;
+ const char *share;
+ const char *dir;
+};
+
+enum { SPOOLSS_DRIVER_PATH, SPOOLSS_PRTPROCS_PATH };
+
+static const struct _spoolss_paths spoolss_paths[]= {
+ { SPOOLSS_DRIVER_PATH, "print$", "DRIVERS" },
+ { SPOOLSS_PRTPROCS_PATH, "prnproc$", "PRTPROCS" }
+};
+
+static WERROR compose_spoolss_server_path(TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *environment,
+ int component,
+ char **path)
+{
+ const char *pservername = NULL;
+ const char *long_archi;
+ const char *short_archi;
+
+ *path = NULL;
+
+ /* environment may be empty */
+ if (environment && strlen(environment)) {
+ long_archi = environment;
+ } else {
+ long_archi = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+ "spoolss", "architecture",
+ GLOBAL_SPOOLSS_ARCHITECTURE);
+ }
+
+ /* servername may be empty */
+ if (servername && strlen(servername)) {
+ pservername = canon_servername(servername);
+
+ if (!is_myname_or_ipaddr(pservername)) {
+ return WERR_INVALID_PARAMETER;
+ }
+ }
+
+ if (!(short_archi = get_short_archi(long_archi))) {
+ return WERR_INVALID_ENVIRONMENT;
+ }
+
+ switch (component) {
+ case SPOOLSS_PRTPROCS_PATH:
+ case SPOOLSS_DRIVER_PATH:
+ if (pservername) {
+ *path = talloc_asprintf(mem_ctx,
+ "\\\\%s\\%s\\%s",
+ pservername,
+ spoolss_paths[component].share,
+ short_archi);
+ } else {
+ *path = talloc_asprintf(mem_ctx, "%s\\%s\\%s",
+ SPOOLSS_DEFAULT_SERVER_PATH,
+ spoolss_paths[component].dir,
+ short_archi);
+ }
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!*path) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR getprinterdriverdir_level_1(TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *environment,
+ struct spoolss_DriverDirectoryInfo1 *r)
+{
+ WERROR werr;
+ char *path = NULL;
+
+ werr = compose_spoolss_server_path(mem_ctx,
+ servername,
+ environment,
+ SPOOLSS_DRIVER_PATH,
+ &path);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ DEBUG(4,("printer driver directory: [%s]\n", path));
+
+ r->directory_name = path;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_GetPrinterDriverDirectory
+****************************************************************/
+
+WERROR _spoolss_GetPrinterDriverDirectory(struct pipes_struct *p,
+ struct spoolss_GetPrinterDriverDirectory *r)
+{
+ WERROR werror;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ TALLOC_FREE(r->out.info);
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(5,("_spoolss_GetPrinterDriverDirectory: level %d\n",
+ r->in.level));
+
+ *r->out.needed = 0;
+
+ /* r->in.level is ignored */
+
+ werror = getprinterdriverdir_level_1(p->mem_ctx,
+ r->in.server,
+ r->in.environment,
+ &r->out.info->info1);
+ if (!W_ERROR_IS_OK(werror)) {
+ TALLOC_FREE(r->out.info);
+ return werror;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_DriverDirectoryInfo,
+ r->out.info, r->in.level);
+ r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************
+ _spoolss_EnumPrinterData
+****************************************************************/
+
+WERROR _spoolss_EnumPrinterData(struct pipes_struct *p,
+ struct spoolss_EnumPrinterData *r)
+{
+ WERROR result;
+ struct spoolss_EnumPrinterDataEx r2;
+ uint32_t count;
+ struct spoolss_PrinterEnumValues *info, *val = NULL;
+ uint32_t needed;
+
+ r2.in.handle = r->in.handle;
+ r2.in.key_name = "PrinterDriverData";
+ r2.in.offered = 0;
+ r2.out.count = &count;
+ r2.out.info = &info;
+ r2.out.needed = &needed;
+
+ result = _spoolss_EnumPrinterDataEx(p, &r2);
+ if (W_ERROR_EQUAL(result, WERR_MORE_DATA)) {
+ r2.in.offered = needed;
+ result = _spoolss_EnumPrinterDataEx(p, &r2);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ /*
+ * The NT machine wants to know the biggest size of value and data
+ *
+ * cf: MSDN EnumPrinterData remark section
+ */
+
+ if (!r->in.value_offered && !r->in.data_offered) {
+ uint32_t biggest_valuesize = 0;
+ uint32_t biggest_datasize = 0;
+ int i, name_length;
+
+ DEBUGADD(6,("Activating NT mega-hack to find sizes\n"));
+
+ for (i=0; i<count; i++) {
+
+ name_length = strlen(info[i].value_name);
+ if (strlen(info[i].value_name) > biggest_valuesize) {
+ biggest_valuesize = name_length;
+ }
+
+ if (info[i].data_length > biggest_datasize) {
+ biggest_datasize = info[i].data_length;
+ }
+
+ DEBUG(6,("current values: [%d], [%d]\n", biggest_valuesize,
+ biggest_datasize));
+ }
+
+ /* the value is an UNICODE string but real_value_size is the length
+ in bytes including the trailing 0 */
+
+ *r->out.value_needed = 2 * (1 + biggest_valuesize);
+ *r->out.data_needed = biggest_datasize;
+
+ DEBUG(6,("final values: [%d], [%d]\n",
+ *r->out.value_needed, *r->out.data_needed));
+
+ return WERR_OK;
+ }
+
+ if (r->in.enum_index < count) {
+ val = &info[r->in.enum_index];
+ }
+
+ if (val == NULL) {
+ /* out_value should default to "" or else NT4 has
+ problems unmarshalling the response */
+
+ if (r->in.value_offered) {
+ *r->out.value_needed = 1;
+ r->out.value_name = talloc_strdup(r, "");
+ if (!r->out.value_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ r->out.value_name = NULL;
+ *r->out.value_needed = 0;
+ }
+
+ /* the data is counted in bytes */
+
+ *r->out.data_needed = r->in.data_offered;
+
+ result = WERR_NO_MORE_ITEMS;
+ } else {
+ /*
+ * the value is:
+ * - counted in bytes in the request
+ * - counted in UNICODE chars in the max reply
+ * - counted in bytes in the real size
+ *
+ * take a pause *before* coding not *during* coding
+ */
+
+ /* name */
+ if (r->in.value_offered) {
+ r->out.value_name = talloc_strdup(r, val->value_name);
+ if (!r->out.value_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ *r->out.value_needed = val->value_name_len;
+ } else {
+ r->out.value_name = NULL;
+ *r->out.value_needed = 0;
+ }
+
+ /* type */
+
+ *r->out.type = val->type;
+
+ /* data - counted in bytes */
+
+ /*
+ * See the section "Dynamically Typed Query Parameters"
+ * in MS-RPRN.
+ */
+
+ if (r->out.data && val->data && val->data->data &&
+ val->data_length && r->in.data_offered) {
+ memcpy(r->out.data, val->data->data,
+ MIN(val->data_length,r->in.data_offered));
+ }
+
+ *r->out.data_needed = val->data_length;
+
+ result = WERR_OK;
+ }
+
+ return result;
+}
+
+/****************************************************************
+ _spoolss_SetPrinterData
+****************************************************************/
+
+WERROR _spoolss_SetPrinterData(struct pipes_struct *p,
+ struct spoolss_SetPrinterData *r)
+{
+ struct spoolss_SetPrinterDataEx r2;
+
+ r2.in.handle = r->in.handle;
+ r2.in.key_name = "PrinterDriverData";
+ r2.in.value_name = r->in.value_name;
+ r2.in.type = r->in.type;
+ r2.in.data = r->in.data;
+ r2.in.offered = r->in.offered;
+
+ return _spoolss_SetPrinterDataEx(p, &r2);
+}
+
+/****************************************************************
+ _spoolss_ResetPrinter
+****************************************************************/
+
+WERROR _spoolss_ResetPrinter(struct pipes_struct *p,
+ struct spoolss_ResetPrinter *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ int snum;
+
+ DEBUG(5,("_spoolss_ResetPrinter\n"));
+
+ /*
+ * All we do is to check to see if the handle and queue is valid.
+ * This call really doesn't mean anything to us because we only
+ * support RAW printing. --jerry
+ */
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_ResetPrinter: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL))
+ return WERR_INVALID_HANDLE;
+
+
+ /* blindly return success */
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinterData
+****************************************************************/
+
+WERROR _spoolss_DeletePrinterData(struct pipes_struct *p,
+ struct spoolss_DeletePrinterData *r)
+{
+ struct spoolss_DeletePrinterDataEx r2;
+
+ r2.in.handle = r->in.handle;
+ r2.in.key_name = "PrinterDriverData";
+ r2.in.value_name = r->in.value_name;
+
+ return _spoolss_DeletePrinterDataEx(p, &r2);
+}
+
+/****************************************************************
+ _spoolss_AddForm
+****************************************************************/
+
+WERROR _spoolss_AddForm(struct pipes_struct *p,
+ struct spoolss_AddForm *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct spoolss_AddFormInfo1 *form;
+ int snum = -1;
+ WERROR status = WERR_OK;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ DEBUG(5,("_spoolss_AddForm\n"));
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_AddForm: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege,
+ and not a printer admin, then fail */
+
+ if ((session_info->unix_token->uid != sec_initial_uid()) &&
+ !security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR)) {
+ DEBUG(2,("_spoolss_Addform: denied by insufficient permissions.\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (r->in.info_ctr->level != 1) {
+ return WERR_INVALID_LEVEL;
+ }
+
+ form = r->in.info_ctr->info.info1;
+ if (!form) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ switch (form->flags) {
+ case SPOOLSS_FORM_USER:
+ case SPOOLSS_FORM_BUILTIN:
+ case SPOOLSS_FORM_PRINTER:
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ status = winreg_printer_addform1(tmp_ctx, b, form);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ /*
+ * ChangeID must always be set if this is a printer
+ */
+ if (Printer->printer_type == SPLHND_PRINTER) {
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ status = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ status = winreg_printer_update_changeid(tmp_ctx, b,
+ lp_const_servicename(snum));
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************
+ _spoolss_DeleteForm
+****************************************************************/
+
+WERROR _spoolss_DeleteForm(struct pipes_struct *p,
+ struct spoolss_DeleteForm *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const char *form_name = r->in.form_name;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ int snum = -1;
+ WERROR status = WERR_OK;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ DEBUG(5,("_spoolss_DeleteForm\n"));
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_DeleteForm: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if ((session_info->unix_token->uid != sec_initial_uid()) &&
+ !security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR)) {
+ DEBUG(2,("_spoolss_DeleteForm: denied by insufficient permissions.\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ status = winreg_printer_deleteform1(tmp_ctx, b, form_name);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ /*
+ * ChangeID must always be set if this is a printer
+ */
+ if (Printer->printer_type == SPLHND_PRINTER) {
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ status = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ status = winreg_printer_update_changeid(tmp_ctx, b,
+ lp_const_servicename(snum));
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************
+ _spoolss_SetForm
+****************************************************************/
+
+WERROR _spoolss_SetForm(struct pipes_struct *p,
+ struct spoolss_SetForm *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct spoolss_AddFormInfo1 *form;
+ const char *form_name = r->in.form_name;
+ int snum = -1;
+ WERROR status = WERR_OK;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ DEBUG(5,("_spoolss_SetForm\n"));
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_SetForm: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* if the user is not root, doesn't have SE_PRINT_OPERATOR privilege,
+ and not a printer admin, then fail */
+
+ if ((session_info->unix_token->uid != sec_initial_uid()) &&
+ !security_token_has_privilege(session_info->security_token,
+ SEC_PRIV_PRINT_OPERATOR)) {
+ DEBUG(2,("_spoolss_SetForm: denied by insufficient permissions.\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (r->in.info_ctr->level != 1) {
+ return WERR_INVALID_LEVEL;
+ }
+
+ form = r->in.info_ctr->info.info1;
+ if (!form) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ status = winreg_printer_setform1(tmp_ctx, b,
+ form_name,
+ form);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ /*
+ * ChangeID must always be set if this is a printer
+ */
+ if (Printer->printer_type == SPLHND_PRINTER) {
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ status = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ status = winreg_printer_update_changeid(tmp_ctx, b,
+ lp_const_servicename(snum));
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************************
+ fill_print_processor1
+****************************************************************************/
+
+static WERROR fill_print_processor1(TALLOC_CTX *mem_ctx,
+ struct spoolss_PrintProcessorInfo1 *r,
+ const char *print_processor_name)
+{
+ r->print_processor_name = talloc_strdup(mem_ctx, print_processor_name);
+ W_ERROR_HAVE_NO_MEMORY(r->print_processor_name);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ enumprintprocessors level 1.
+****************************************************************************/
+
+static WERROR enumprintprocessors_level_1(TALLOC_CTX *mem_ctx,
+ union spoolss_PrintProcessorInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_PrintProcessorInfo *info;
+ WERROR result;
+
+ info = talloc_array(mem_ctx, union spoolss_PrintProcessorInfo, 1);
+ W_ERROR_HAVE_NO_MEMORY(info);
+
+ *count = 1;
+
+ result = fill_print_processor1(info, &info[0].info1, "winprint");
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ out:
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(info);
+ *count = 0;
+ return result;
+ }
+
+ *info_p = info;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_EnumPrintProcessors
+****************************************************************/
+
+WERROR _spoolss_EnumPrintProcessors(struct pipes_struct *p,
+ struct spoolss_EnumPrintProcessors *r)
+{
+ WERROR result;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(5,("_spoolss_EnumPrintProcessors\n"));
+
+ /*
+ * Enumerate the print processors ...
+ *
+ * Just reply with "winprint", to keep NT happy
+ * and I can use my nice printer checker.
+ */
+
+ *r->out.count = 0;
+ *r->out.needed = 0;
+ *r->out.info = NULL;
+
+ if (!get_short_archi(r->in.environment)) {
+ return WERR_INVALID_ENVIRONMENT;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ result = enumprintprocessors_level_1(p->mem_ctx, r->out.info,
+ r->out.count);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumPrintProcessors,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************************
+ fill_printprocdatatype1
+****************************************************************************/
+
+static WERROR fill_printprocdatatype1(TALLOC_CTX *mem_ctx,
+ struct spoolss_PrintProcDataTypesInfo1 *r,
+ const char *name_array)
+{
+ r->name_array = talloc_strdup(mem_ctx, name_array);
+ W_ERROR_HAVE_NO_MEMORY(r->name_array);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ enumprintprocdatatypes level 1.
+****************************************************************************/
+
+static WERROR enumprintprocdatatypes_level_1(TALLOC_CTX *mem_ctx,
+ union spoolss_PrintProcDataTypesInfo **info_p,
+ uint32_t *count)
+{
+ WERROR result;
+ union spoolss_PrintProcDataTypesInfo *info;
+
+ info = talloc_array(mem_ctx, union spoolss_PrintProcDataTypesInfo, 1);
+ W_ERROR_HAVE_NO_MEMORY(info);
+
+ *count = 1;
+
+ result = fill_printprocdatatype1(info, &info[0].info1, "RAW");
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ out:
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(info);
+ *count = 0;
+ return result;
+ }
+
+ *info_p = info;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_EnumPrintProcessorDataTypes
+****************************************************************/
+
+WERROR _spoolss_EnumPrintProcessorDataTypes(struct pipes_struct *p,
+ struct spoolss_EnumPrintProcessorDataTypes *r)
+{
+ WERROR result;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(5,("_spoolss_EnumPrintProcessorDataTypes\n"));
+
+ *r->out.count = 0;
+ *r->out.needed = 0;
+ *r->out.info = NULL;
+
+ if (r->in.print_processor_name == NULL ||
+ !strequal(r->in.print_processor_name, "winprint")) {
+ return WERR_UNKNOWN_PRINTPROCESSOR;
+ }
+
+ switch (r->in.level) {
+ case 1:
+ result = enumprintprocdatatypes_level_1(p->mem_ctx, r->out.info,
+ r->out.count);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumPrintProcessorDataTypes,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************************
+ fill_monitor_1
+****************************************************************************/
+
+static WERROR fill_monitor_1(TALLOC_CTX *mem_ctx,
+ struct spoolss_MonitorInfo1 *r,
+ const char *monitor_name)
+{
+ r->monitor_name = talloc_strdup(mem_ctx, monitor_name);
+ W_ERROR_HAVE_NO_MEMORY(r->monitor_name);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ fill_monitor_2
+****************************************************************************/
+
+static WERROR fill_monitor_2(TALLOC_CTX *mem_ctx,
+ struct spoolss_MonitorInfo2 *r,
+ const char *monitor_name,
+ const char *environment,
+ const char *dll_name)
+{
+ r->monitor_name = talloc_strdup(mem_ctx, monitor_name);
+ W_ERROR_HAVE_NO_MEMORY(r->monitor_name);
+ r->environment = talloc_strdup(mem_ctx, environment);
+ W_ERROR_HAVE_NO_MEMORY(r->environment);
+ r->dll_name = talloc_strdup(mem_ctx, dll_name);
+ W_ERROR_HAVE_NO_MEMORY(r->dll_name);
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ enumprintmonitors level 1.
+****************************************************************************/
+
+static WERROR enumprintmonitors_level_1(TALLOC_CTX *mem_ctx,
+ union spoolss_MonitorInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_MonitorInfo *info;
+ WERROR result = WERR_OK;
+
+ info = talloc_array(mem_ctx, union spoolss_MonitorInfo, 2);
+ W_ERROR_HAVE_NO_MEMORY(info);
+
+ *count = 2;
+
+ result = fill_monitor_1(info, &info[0].info1,
+ SPL_LOCAL_PORT);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ result = fill_monitor_1(info, &info[1].info1,
+ SPL_TCPIP_PORT);
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+out:
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(info);
+ *count = 0;
+ return result;
+ }
+
+ *info_p = info;
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+ enumprintmonitors level 2.
+****************************************************************************/
+
+static WERROR enumprintmonitors_level_2(TALLOC_CTX *mem_ctx,
+ union spoolss_MonitorInfo **info_p,
+ uint32_t *count)
+{
+ union spoolss_MonitorInfo *info;
+ WERROR result = WERR_OK;
+ const char *architecture;
+
+ info = talloc_array(mem_ctx, union spoolss_MonitorInfo, 2);
+ W_ERROR_HAVE_NO_MEMORY(info);
+
+ *count = 2;
+
+ architecture = lp_parm_const_string(GLOBAL_SECTION_SNUM,
+ "spoolss",
+ "architecture",
+ GLOBAL_SPOOLSS_ARCHITECTURE);
+
+ result = fill_monitor_2(info, &info[0].info2,
+ SPL_LOCAL_PORT,
+ architecture,
+ "localmon.dll");
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+ result = fill_monitor_2(info, &info[1].info2,
+ SPL_TCPIP_PORT,
+ architecture,
+ "tcpmon.dll");
+ if (!W_ERROR_IS_OK(result)) {
+ goto out;
+ }
+
+out:
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(info);
+ *count = 0;
+ return result;
+ }
+
+ *info_p = info;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_EnumMonitors
+****************************************************************/
+
+WERROR _spoolss_EnumMonitors(struct pipes_struct *p,
+ struct spoolss_EnumMonitors *r)
+{
+ WERROR result;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(5,("_spoolss_EnumMonitors\n"));
+
+ /*
+ * Enumerate the print monitors ...
+ *
+ * Just reply with "Local Port", to keep NT happy
+ * and I can use my nice printer checker.
+ */
+
+ *r->out.count = 0;
+ *r->out.needed = 0;
+ *r->out.info = NULL;
+
+ switch (r->in.level) {
+ case 1:
+ result = enumprintmonitors_level_1(p->mem_ctx, r->out.info,
+ r->out.count);
+ break;
+ case 2:
+ result = enumprintmonitors_level_2(p->mem_ctx, r->out.info,
+ r->out.count);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION_ARRAY(p->mem_ctx,
+ spoolss_EnumMonitors,
+ *r->out.info, r->in.level,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, 0);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR getjob_level_1(TALLOC_CTX *mem_ctx,
+ const print_queue_struct *queue,
+ int count, int snum,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ uint32_t jobid,
+ int sysjob,
+ struct spoolss_JobInfo1 *r)
+{
+ int i = 0;
+ bool found = false;
+
+ for (i=0; i<count; i++) {
+ if (queue[i].sysjob == sysjob) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found == false) {
+ /* NT treats not found as bad param... yet another bad choice */
+ return WERR_INVALID_PARAMETER;
+ }
+
+ return fill_job_info1(mem_ctx,
+ r,
+ &queue[i],
+ jobid,
+ i,
+ snum,
+ pinfo2);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR getjob_level_2(TALLOC_CTX *mem_ctx,
+ const print_queue_struct *queue,
+ int count, int snum,
+ struct spoolss_PrinterInfo2 *pinfo2,
+ uint32_t jobid,
+ int sysjob,
+ struct spoolss_JobInfo2 *r)
+{
+ int i = 0;
+ bool found = false;
+ struct spoolss_DeviceMode *devmode;
+ WERROR result;
+
+ for (i=0; i<count; i++) {
+ if (queue[i].sysjob == sysjob) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found == false) {
+ /* NT treats not found as bad param... yet another bad
+ choice */
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /*
+ * if the print job does not have a DEVMODE associated with it,
+ * just use the one for the printer. A NULL devicemode is not
+ * a failure condition
+ */
+
+ devmode = print_job_devmode(mem_ctx, lp_const_servicename(snum), jobid);
+ if (!devmode) {
+ result = spoolss_create_default_devmode(mem_ctx,
+ pinfo2->printername,
+ &devmode);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(3, ("Can't proceed w/o a devmode!\n"));
+ return result;
+ }
+ }
+
+ return fill_job_info2(mem_ctx,
+ r,
+ &queue[i],
+ jobid,
+ i,
+ snum,
+ pinfo2,
+ devmode);
+}
+
+/****************************************************************
+ _spoolss_GetJob
+****************************************************************/
+
+WERROR _spoolss_GetJob(struct pipes_struct *p,
+ struct spoolss_GetJob *r)
+{
+ WERROR result = WERR_OK;
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ const char *svc_name;
+ int sysjob;
+ int snum;
+ int count;
+ struct tdb_print_db *pdb;
+ print_queue_struct *queue = NULL;
+ print_status_struct prt_status;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_jinfo_free;
+ }
+
+ DEBUG(5,("_spoolss_GetJob\n"));
+
+ *r->out.needed = 0;
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ result = WERR_INVALID_HANDLE;
+ goto err_jinfo_free;
+ }
+
+ svc_name = lp_const_servicename(snum);
+ if (svc_name == NULL) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_jinfo_free;
+ }
+
+ result = winreg_get_printer_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ svc_name,
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_jinfo_free;
+ }
+
+ pdb = get_print_db_byname(svc_name);
+ if (pdb == NULL) {
+ DEBUG(3, ("failed to get print db for svc %s\n", svc_name));
+ result = WERR_INVALID_PARAMETER;
+ goto err_pinfo_free;
+ }
+
+ sysjob = jobid_to_sysjob_pdb(pdb, r->in.job_id);
+ release_print_db(pdb);
+ if (sysjob == -1) {
+ DEBUG(3, ("no sysjob for spoolss jobid %u\n", r->in.job_id));
+ result = WERR_INVALID_PARAMETER;
+ goto err_pinfo_free;
+ }
+
+ count = print_queue_status(p->msg_ctx, snum, &queue, &prt_status);
+
+ DEBUGADD(4,("count:[%d], prt_status:[%d], [%s]\n",
+ count, prt_status.status, prt_status.message));
+
+ switch (r->in.level) {
+ case 1:
+ result = getjob_level_1(p->mem_ctx,
+ queue, count, snum, pinfo2,
+ r->in.job_id, sysjob,
+ &r->out.info->info1);
+ break;
+ case 2:
+ result = getjob_level_2(p->mem_ctx,
+ queue, count, snum, pinfo2,
+ r->in.job_id, sysjob,
+ &r->out.info->info2);
+ break;
+ default:
+ result = WERR_INVALID_LEVEL;
+ break;
+ }
+
+ SAFE_FREE(queue);
+ TALLOC_FREE(pinfo2);
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_jinfo_free;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_JobInfo, r->out.info,
+ r->in.level);
+ r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+
+err_pinfo_free:
+ TALLOC_FREE(pinfo2);
+err_jinfo_free:
+ TALLOC_FREE(r->out.info);
+ return result;
+}
+
+/****************************************************************
+ _spoolss_GetPrinterDataEx
+****************************************************************/
+
+WERROR _spoolss_GetPrinterDataEx(struct pipes_struct *p,
+ struct spoolss_GetPrinterDataEx *r)
+{
+
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ const char *printer;
+ int snum = 0;
+ WERROR result = WERR_OK;
+ DATA_BLOB blob;
+ enum winreg_Type val_type = REG_NONE;
+ uint8_t *val_data = NULL;
+ uint32_t val_size = 0;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ DEBUG(4,("_spoolss_GetPrinterDataEx\n"));
+
+ DEBUG(10, ("_spoolss_GetPrinterDataEx: key => [%s], value => [%s]\n",
+ r->in.key_name, r->in.value_name));
+
+ /* in case of problem, return some default values */
+
+ *r->out.needed = 0;
+ *r->out.type = REG_NONE;
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_GetPrinterDataEx: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+
+ /* Is the handle to a printer or to the server? */
+
+ if (Printer->printer_type == SPLHND_SERVER) {
+
+ union spoolss_PrinterData data;
+
+ result = getprinterdata_printer_server(tmp_ctx,
+ r->in.value_name,
+ r->out.type,
+ &data);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = push_spoolss_PrinterData(tmp_ctx, &blob,
+ *r->out.type, &data);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ *r->out.needed = blob.length;
+
+ if (r->in.offered >= *r->out.needed) {
+ memcpy(r->out.data, blob.data, blob.length);
+ }
+
+ result = WERR_OK;
+ goto done;
+ }
+
+ /* check to see if the keyname is valid */
+ if (!strlen(r->in.key_name)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ result = WERR_INVALID_HANDLE;
+ goto done;
+ }
+ printer = lp_const_servicename(snum);
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* XP sends this and wants the ChangeID value from PRINTER_INFO_0 */
+ if (strequal(r->in.key_name, SPOOL_PRINTERDATA_KEY) &&
+ strequal(r->in.value_name, "ChangeId")) {
+ *r->out.type = REG_DWORD;
+ *r->out.needed = 4;
+ if (r->in.offered >= *r->out.needed) {
+ uint32_t changeid = 0;
+
+ result = winreg_printer_get_changeid(tmp_ctx, b,
+ printer,
+ &changeid);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ SIVAL(r->out.data, 0, changeid);
+ result = WERR_OK;
+ }
+ goto done;
+ }
+
+ result = winreg_get_printer_dataex(tmp_ctx, b,
+ printer,
+ r->in.key_name,
+ r->in.value_name,
+ &val_type,
+ &val_data,
+ &val_size);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ *r->out.needed = val_size;
+ *r->out.type = val_type;
+
+ if (r->in.offered >= *r->out.needed) {
+ memcpy(r->out.data, val_data, val_size);
+ }
+
+done:
+ /* NOTE: do not replace type when returning WERR_MORE_DATA */
+
+ if (W_ERROR_IS_OK(result)) {
+ result = SPOOLSS_BUFFER_OK(WERR_OK, WERR_MORE_DATA);
+ }
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/****************************************************************
+ _spoolss_SetPrinterDataEx
+****************************************************************/
+
+WERROR _spoolss_SetPrinterDataEx(struct pipes_struct *p,
+ struct spoolss_SetPrinterDataEx *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct spoolss_PrinterInfo2 *pinfo2 = NULL;
+ int snum = 0;
+ WERROR result = WERR_OK;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ char *oid_string;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ DEBUG(4,("_spoolss_SetPrinterDataEx\n"));
+
+ /* From MSDN documentation of SetPrinterDataEx: pass request to
+ SetPrinterData if key is "PrinterDriverData" */
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_SetPrinterDataEx: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (Printer->printer_type == SPLHND_SERVER) {
+ DEBUG(10,("_spoolss_SetPrinterDataEx: "
+ "Not implemented for server handles yet\n"));
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ /*
+ * Access check : NT returns "access denied" if you make a
+ * SetPrinterData call without the necessary privilege.
+ * we were originally returning OK if nothing changed
+ * which made Win2k issue **a lot** of SetPrinterData
+ * when connecting to a printer --jerry
+ */
+
+ if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) {
+ DEBUG(3, ("_spoolss_SetPrinterDataEx: "
+ "change denied by handle access permissions\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = winreg_get_printer(tmp_ctx, b,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ &pinfo2);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* check for OID in valuename */
+
+ oid_string = strchr(r->in.value_name, ',');
+ if (oid_string) {
+ *oid_string = '\0';
+ oid_string++;
+ }
+
+ /* save the registry data */
+
+ result = winreg_set_printer_dataex(tmp_ctx, b,
+ pinfo2->sharename,
+ r->in.key_name,
+ r->in.value_name,
+ r->in.type,
+ r->in.data,
+ r->in.offered);
+
+ if (W_ERROR_IS_OK(result)) {
+ /* save the OID if one was specified */
+ if (oid_string) {
+ char *str = talloc_asprintf(tmp_ctx, "%s\\%s",
+ r->in.key_name, SPOOL_OID_KEY);
+ if (!str) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /*
+ * I'm not checking the status here on purpose. Don't know
+ * if this is right, but I'm returning the status from the
+ * previous set_printer_dataex() call. I have no idea if
+ * this is right. --jerry
+ */
+ winreg_set_printer_dataex(tmp_ctx, b,
+ pinfo2->sharename,
+ str,
+ r->in.value_name,
+ REG_SZ,
+ (uint8_t *) oid_string,
+ strlen(oid_string) + 1);
+ }
+
+ result = winreg_printer_update_changeid(tmp_ctx, b,
+ lp_const_servicename(snum));
+
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinterDataEx
+****************************************************************/
+
+WERROR _spoolss_DeletePrinterDataEx(struct pipes_struct *p,
+ struct spoolss_DeletePrinterDataEx *r)
+{
+ const char *printer;
+ int snum=0;
+ WERROR status = WERR_OK;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+
+ DEBUG(5,("_spoolss_DeletePrinterDataEx\n"));
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_DeletePrinterDataEx: "
+ "Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) {
+ DEBUG(3, ("_spoolss_DeletePrinterDataEx: "
+ "printer properties change denied by handle\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!r->in.value_name || !r->in.key_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+ printer = lp_const_servicename(snum);
+
+ status = winreg_delete_printer_dataex_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ printer,
+ r->in.key_name,
+ r->in.value_name);
+ if (W_ERROR_IS_OK(status)) {
+ status = winreg_printer_update_changeid_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ printer);
+ }
+
+ return status;
+}
+
+/****************************************************************
+ _spoolss_EnumPrinterKey
+****************************************************************/
+
+WERROR _spoolss_EnumPrinterKey(struct pipes_struct *p,
+ struct spoolss_EnumPrinterKey *r)
+{
+ uint32_t num_keys;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ int snum = 0;
+ WERROR result = WERR_FILE_NOT_FOUND;
+ const char **array = NULL;
+ DATA_BLOB blob;
+
+ DEBUG(4,("_spoolss_EnumPrinterKey\n"));
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_EnumPrinterKey: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ result = winreg_enum_printer_key_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_const_servicename(snum),
+ r->in.key_name,
+ &num_keys,
+ &array);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ if (!push_reg_multi_sz(p->mem_ctx, &blob, array)) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ *r->out._ndr_size = r->in.offered / 2;
+ *r->out.needed = blob.length;
+
+ if (r->in.offered < *r->out.needed) {
+ result = WERR_MORE_DATA;
+ } else {
+ result = WERR_OK;
+ r->out.key_buffer->string_array = array;
+ }
+
+ done:
+ if (!W_ERROR_IS_OK(result)) {
+ TALLOC_FREE(array);
+ if (!W_ERROR_EQUAL(result, WERR_MORE_DATA)) {
+ *r->out.needed = 0;
+ }
+ }
+
+ return result;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinterKey
+****************************************************************/
+
+WERROR _spoolss_DeletePrinterKey(struct pipes_struct *p,
+ struct spoolss_DeletePrinterKey *r)
+{
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ int snum=0;
+ WERROR status;
+ const char *printer;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ DEBUG(5,("_spoolss_DeletePrinterKey\n"));
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_DeletePrinterKey: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* if keyname == NULL, return error */
+ if ( !r->in.key_name )
+ return WERR_INVALID_PARAMETER;
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ if (Printer->access_granted != PRINTER_ACCESS_ADMINISTER) {
+ DEBUG(3, ("_spoolss_DeletePrinterKey: "
+ "printer properties change denied by handle\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ printer = lp_const_servicename(snum);
+
+ tmp_ctx = talloc_new(p->mem_ctx);
+ if (!tmp_ctx) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = winreg_printer_binding_handle(tmp_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ &b);
+ if (!W_ERROR_IS_OK(status)) {
+ goto done;
+ }
+
+ /* delete the key and all subkeys */
+ status = winreg_delete_printer_key(tmp_ctx, b,
+ printer,
+ r->in.key_name);
+ if (W_ERROR_IS_OK(status)) {
+ status = winreg_printer_update_changeid(tmp_ctx, b,
+ printer);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/****************************************************************
+ _spoolss_EnumPrinterDataEx
+****************************************************************/
+
+WERROR _spoolss_EnumPrinterDataEx(struct pipes_struct *p,
+ struct spoolss_EnumPrinterDataEx *r)
+{
+ uint32_t count = 0;
+ struct spoolss_PrinterEnumValues *info = NULL;
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ int snum;
+ WERROR result;
+
+ DEBUG(4,("_spoolss_EnumPrinterDataEx\n"));
+
+ *r->out.count = 0;
+ *r->out.needed = 0;
+ *r->out.info = NULL;
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_EnumPrinterDataEx: Invalid handle (%s:%u:%u1<).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /*
+ * first check for a keyname of NULL or "". Win2k seems to send
+ * this a lot and we should send back WERR_INVALID_PARAMETER
+ * no need to spend time looking up the printer in this case.
+ * --jerry
+ */
+
+ if (!strlen(r->in.key_name)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (!get_printer_snum(p, r->in.handle, &snum, NULL)) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* now look for a match on the key name */
+ result = winreg_enum_printer_dataex_internal(p->mem_ctx,
+ get_session_info_system(),
+ p->msg_ctx,
+ lp_const_servicename(snum),
+ r->in.key_name,
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ *r->out.count = count;
+ *r->out.info = info;
+
+ done:
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_ARRAY(p->mem_ctx,
+ spoolss_EnumPrinterDataEx,
+ *r->out.info,
+ *r->out.count);
+ *r->out.info = SPOOLSS_BUFFER_OK(*r->out.info, NULL);
+ *r->out.count = SPOOLSS_BUFFER_OK(*r->out.count, *r->out.count);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_MORE_DATA);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR getprintprocessordirectory_level_1(TALLOC_CTX *mem_ctx,
+ const char *servername,
+ const char *environment,
+ struct spoolss_PrintProcessorDirectoryInfo1 *r)
+{
+ WERROR werr;
+ char *path = NULL;
+
+ werr = compose_spoolss_server_path(mem_ctx,
+ servername,
+ environment,
+ SPOOLSS_PRTPROCS_PATH,
+ &path);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ DEBUG(4,("print processor directory: [%s]\n", path));
+
+ r->directory_name = path;
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_GetPrintProcessorDirectory
+****************************************************************/
+
+WERROR _spoolss_GetPrintProcessorDirectory(struct pipes_struct *p,
+ struct spoolss_GetPrintProcessorDirectory *r)
+{
+ WERROR result;
+ char *prnproc_share = NULL;
+ bool prnproc_share_exists = false;
+ int snum;
+
+ /* that's an [in out] buffer */
+
+ if (!r->in.buffer && (r->in.offered != 0)) {
+ result = WERR_INVALID_PARAMETER;
+ goto err_info_free;
+ }
+
+ DEBUG(5,("_spoolss_GetPrintProcessorDirectory: level %d\n",
+ r->in.level));
+
+ *r->out.needed = 0;
+
+ /* r->in.level is ignored */
+
+ /* We always should reply with a local print processor directory so that
+ * users are not forced to have a [prnproc$] share on the Samba spoolss
+ * server, if users decide to do so, lets announce it though - Guenther */
+
+ snum = find_service(talloc_tos(), "prnproc$", &prnproc_share);
+ if (!prnproc_share) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto err_info_free;
+ }
+ if (snum != -1) {
+ prnproc_share_exists = true;
+ }
+
+ result = getprintprocessordirectory_level_1(p->mem_ctx,
+ prnproc_share_exists ? r->in.server : NULL,
+ r->in.environment,
+ &r->out.info->info1);
+ if (!W_ERROR_IS_OK(result)) {
+ goto err_info_free;
+ }
+
+ *r->out.needed = SPOOLSS_BUFFER_UNION(spoolss_PrintProcessorDirectoryInfo,
+ r->out.info, r->in.level);
+ r->out.info = SPOOLSS_BUFFER_OK(r->out.info, NULL);
+
+ return SPOOLSS_BUFFER_OK(WERR_OK, WERR_INSUFFICIENT_BUFFER);
+
+err_info_free:
+ TALLOC_FREE(r->out.info);
+ return result;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static bool push_monitorui_buf(TALLOC_CTX *mem_ctx, DATA_BLOB *buf,
+ const char *dllname)
+{
+ enum ndr_err_code ndr_err;
+ struct spoolss_MonitorUi ui;
+
+ ui.dll_name = dllname;
+
+ ndr_err = ndr_push_struct_blob(buf, mem_ctx, &ui,
+ (ndr_push_flags_fn_t)ndr_push_spoolss_MonitorUi);
+ if (NDR_ERR_CODE_IS_SUCCESS(ndr_err) && (DEBUGLEVEL >= 10)) {
+ NDR_PRINT_DEBUG(spoolss_MonitorUi, &ui);
+ }
+ return NDR_ERR_CODE_IS_SUCCESS(ndr_err);
+}
+
+/*******************************************************************
+ Streams the monitor UI DLL name in UNICODE
+*******************************************************************/
+
+static WERROR xcvtcp_monitorui(TALLOC_CTX *mem_ctx,
+ struct security_token *token, DATA_BLOB *in,
+ DATA_BLOB *out, uint32_t *needed)
+{
+ const char *dllname = "tcpmonui.dll";
+
+ *needed = (strlen(dllname)+1) * 2;
+
+ if (out->length < *needed) {
+ return WERR_INSUFFICIENT_BUFFER;
+ }
+
+ if (!push_monitorui_buf(mem_ctx, out, dllname)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static bool pull_port_data_1(TALLOC_CTX *mem_ctx,
+ struct spoolss_PortData1 *port1,
+ const DATA_BLOB *buf)
+{
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_struct_blob(buf, mem_ctx, port1,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_PortData1);
+ if (NDR_ERR_CODE_IS_SUCCESS(ndr_err) && (DEBUGLEVEL >= 10)) {
+ NDR_PRINT_DEBUG(spoolss_PortData1, port1);
+ }
+ return NDR_ERR_CODE_IS_SUCCESS(ndr_err);
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static bool pull_port_data_2(TALLOC_CTX *mem_ctx,
+ struct spoolss_PortData2 *port2,
+ const DATA_BLOB *buf)
+{
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_struct_blob(buf, mem_ctx, port2,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_PortData2);
+ if (NDR_ERR_CODE_IS_SUCCESS(ndr_err) && (DEBUGLEVEL >= 10)) {
+ NDR_PRINT_DEBUG(spoolss_PortData2, port2);
+ }
+ return NDR_ERR_CODE_IS_SUCCESS(ndr_err);
+}
+
+/*******************************************************************
+ Create a new TCP/IP port
+*******************************************************************/
+
+static WERROR xcvtcp_addport(TALLOC_CTX *mem_ctx,
+ struct security_token *token, DATA_BLOB *in,
+ DATA_BLOB *out, uint32_t *needed)
+{
+ struct spoolss_PortData1 port1;
+ struct spoolss_PortData2 port2;
+ char *device_uri = NULL;
+ uint32_t version;
+
+ const char *portname;
+ const char *hostaddress;
+ const char *queue;
+ uint32_t port_number;
+ uint32_t protocol;
+
+ /* peek for spoolss_PortData version */
+
+ if (!in || (in->length < (128 + 4))) {
+ return WERR_GEN_FAILURE;
+ }
+
+ version = IVAL(in->data, 128);
+
+ switch (version) {
+ case 1:
+ ZERO_STRUCT(port1);
+
+ if (!pull_port_data_1(mem_ctx, &port1, in)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ portname = port1.portname;
+ hostaddress = port1.hostaddress;
+ queue = port1.queue;
+ protocol = port1.protocol;
+ port_number = port1.port_number;
+
+ break;
+ case 2:
+ ZERO_STRUCT(port2);
+
+ if (!pull_port_data_2(mem_ctx, &port2, in)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ portname = port2.portname;
+ hostaddress = port2.hostaddress;
+ queue = port2.queue;
+ protocol = port2.protocol;
+ port_number = port2.port_number;
+
+ break;
+ default:
+ DEBUG(1,("xcvtcp_addport: "
+ "unknown version of port_data: %d\n", version));
+ return WERR_UNKNOWN_PORT;
+ }
+
+ /* create the device URI and call the add_port_hook() */
+
+ switch (protocol) {
+ case PROTOCOL_RAWTCP_TYPE:
+ device_uri = talloc_asprintf(mem_ctx,
+ "socket://%s:%d/", hostaddress,
+ port_number);
+ break;
+
+ case PROTOCOL_LPR_TYPE:
+ device_uri = talloc_asprintf(mem_ctx,
+ "lpr://%s/%s", hostaddress, queue );
+ break;
+
+ default:
+ return WERR_UNKNOWN_PORT;
+ }
+
+ if (!device_uri) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return add_port_hook(mem_ctx, token, portname, device_uri);
+}
+
+/*******************************************************************
+*******************************************************************/
+
+struct xcv_api_table xcvtcp_cmds[] = {
+ { "MonitorUI", xcvtcp_monitorui },
+ { "AddPort", xcvtcp_addport},
+ { NULL, NULL }
+};
+
+static WERROR process_xcvtcp_command(TALLOC_CTX *mem_ctx,
+ struct security_token *token, const char *command,
+ DATA_BLOB *inbuf,
+ DATA_BLOB *outbuf,
+ uint32_t *needed )
+{
+ int i;
+
+ DEBUG(10,("process_xcvtcp_command: Received command \"%s\"\n", command));
+
+ for ( i=0; xcvtcp_cmds[i].name; i++ ) {
+ if ( strcmp( command, xcvtcp_cmds[i].name ) == 0 )
+ return xcvtcp_cmds[i].fn(mem_ctx, token, inbuf, outbuf, needed);
+ }
+
+ return WERR_INVALID_FUNCTION;
+}
+
+/*******************************************************************
+*******************************************************************/
+#if 0 /* don't support management using the "Local Port" monitor */
+
+static WERROR xcvlocal_monitorui(TALLOC_CTX *mem_ctx,
+ struct security_token *token, DATA_BLOB *in,
+ DATA_BLOB *out, uint32_t *needed)
+{
+ const char *dllname = "localui.dll";
+
+ *needed = (strlen(dllname)+1) * 2;
+
+ if (out->length < *needed) {
+ return WERR_INSUFFICIENT_BUFFER;
+ }
+
+ if (!push_monitorui_buf(mem_ctx, out, dllname)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+*******************************************************************/
+
+struct xcv_api_table xcvlocal_cmds[] = {
+ { "MonitorUI", xcvlocal_monitorui },
+ { NULL, NULL }
+};
+#else
+struct xcv_api_table xcvlocal_cmds[] = {
+ { NULL, NULL }
+};
+#endif
+
+
+
+/*******************************************************************
+*******************************************************************/
+
+static WERROR process_xcvlocal_command(TALLOC_CTX *mem_ctx,
+ struct security_token *token, const char *command,
+ DATA_BLOB *inbuf, DATA_BLOB *outbuf,
+ uint32_t *needed)
+{
+ int i;
+
+ DEBUG(10,("process_xcvlocal_command: Received command \"%s\"\n", command));
+
+ for ( i=0; xcvlocal_cmds[i].name; i++ ) {
+ if ( strcmp( command, xcvlocal_cmds[i].name ) == 0 )
+ return xcvlocal_cmds[i].fn(mem_ctx, token, inbuf, outbuf, needed);
+ }
+ return WERR_INVALID_FUNCTION;
+}
+
+/****************************************************************
+ _spoolss_XcvData
+****************************************************************/
+
+WERROR _spoolss_XcvData(struct pipes_struct *p,
+ struct spoolss_XcvData *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct printer_handle *Printer = find_printer_index_by_hnd(p, r->in.handle);
+ DATA_BLOB out_data = data_blob_null;
+ WERROR werror;
+
+ if (!Printer) {
+ DEBUG(2,("_spoolss_XcvData: Invalid handle (%s:%u:%u).\n",
+ OUR_HANDLE(r->in.handle)));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* Has to be a handle to the TCP/IP port monitor */
+
+ if ( !(Printer->printer_type & (SPLHND_PORTMON_LOCAL|SPLHND_PORTMON_TCP)) ) {
+ DEBUG(2,("_spoolss_XcvData: Call only valid for Port Monitors\n"));
+ return WERR_INVALID_HANDLE;
+ }
+
+ /* requires administrative access to the server */
+
+ if ( !(Printer->access_granted & SERVER_ACCESS_ADMINISTER) ) {
+ DEBUG(2,("_spoolss_XcvData: denied by handle permissions.\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Allocate the outgoing buffer */
+
+ if (r->in.out_data_size) {
+ out_data = data_blob_talloc_zero(p->mem_ctx, r->in.out_data_size);
+ if (out_data.data == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ switch ( Printer->printer_type ) {
+ case SPLHND_PORTMON_TCP:
+ werror = process_xcvtcp_command(p->mem_ctx,
+ session_info->security_token,
+ r->in.function_name,
+ &r->in.in_data, &out_data,
+ r->out.needed);
+ break;
+ case SPLHND_PORTMON_LOCAL:
+ werror = process_xcvlocal_command(p->mem_ctx,
+ session_info->security_token,
+ r->in.function_name,
+ &r->in.in_data, &out_data,
+ r->out.needed);
+ break;
+ default:
+ werror = WERR_INVALID_PRINT_MONITOR;
+ }
+
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ *r->out.status_code = 0;
+
+ if (r->out.out_data && out_data.data && r->in.out_data_size && out_data.length) {
+ memcpy(r->out.out_data, out_data.data,
+ MIN(r->in.out_data_size, out_data.length));
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_AddPrintProcessor
+****************************************************************/
+
+WERROR _spoolss_AddPrintProcessor(struct pipes_struct *p,
+ struct spoolss_AddPrintProcessor *r)
+{
+ /* for now, just indicate success and ignore the add. We'll
+ automatically set the winprint processor for printer
+ entries later. Used to debug the LexMark Optra S 1855 PCL
+ driver --jerry */
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _spoolss_AddPort
+****************************************************************/
+
+WERROR _spoolss_AddPort(struct pipes_struct *p,
+ struct spoolss_AddPort *r)
+{
+ /* do what w2k3 does */
+
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_GetPrinterDriver
+****************************************************************/
+
+WERROR _spoolss_GetPrinterDriver(struct pipes_struct *p,
+ struct spoolss_GetPrinterDriver *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_ReadPrinter
+****************************************************************/
+
+WERROR _spoolss_ReadPrinter(struct pipes_struct *p,
+ struct spoolss_ReadPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_WaitForPrinterChange
+****************************************************************/
+
+WERROR _spoolss_WaitForPrinterChange(struct pipes_struct *p,
+ struct spoolss_WaitForPrinterChange *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_ConfigurePort
+****************************************************************/
+
+WERROR _spoolss_ConfigurePort(struct pipes_struct *p,
+ struct spoolss_ConfigurePort *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeletePort
+****************************************************************/
+
+WERROR _spoolss_DeletePort(struct pipes_struct *p,
+ struct spoolss_DeletePort *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_CreatePrinterIC
+****************************************************************/
+
+WERROR _spoolss_CreatePrinterIC(struct pipes_struct *p,
+ struct spoolss_CreatePrinterIC *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_PlayGDIScriptOnPrinterIC
+****************************************************************/
+
+WERROR _spoolss_PlayGDIScriptOnPrinterIC(struct pipes_struct *p,
+ struct spoolss_PlayGDIScriptOnPrinterIC *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinterIC
+****************************************************************/
+
+WERROR _spoolss_DeletePrinterIC(struct pipes_struct *p,
+ struct spoolss_DeletePrinterIC *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_AddPrinterConnection
+****************************************************************/
+
+WERROR _spoolss_AddPrinterConnection(struct pipes_struct *p,
+ struct spoolss_AddPrinterConnection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeletePrinterConnection
+****************************************************************/
+
+WERROR _spoolss_DeletePrinterConnection(struct pipes_struct *p,
+ struct spoolss_DeletePrinterConnection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_PrinterMessageBox
+****************************************************************/
+
+WERROR _spoolss_PrinterMessageBox(struct pipes_struct *p,
+ struct spoolss_PrinterMessageBox *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_AddMonitor
+****************************************************************/
+
+WERROR _spoolss_AddMonitor(struct pipes_struct *p,
+ struct spoolss_AddMonitor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeleteMonitor
+****************************************************************/
+
+WERROR _spoolss_DeleteMonitor(struct pipes_struct *p,
+ struct spoolss_DeleteMonitor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeletePrintProcessor
+****************************************************************/
+
+WERROR _spoolss_DeletePrintProcessor(struct pipes_struct *p,
+ struct spoolss_DeletePrintProcessor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_AddPrintProvidor
+****************************************************************/
+
+WERROR _spoolss_AddPrintProvidor(struct pipes_struct *p,
+ struct spoolss_AddPrintProvidor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeletePrintProvidor
+****************************************************************/
+
+WERROR _spoolss_DeletePrintProvidor(struct pipes_struct *p,
+ struct spoolss_DeletePrintProvidor *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_FindFirstPrinterChangeNotification
+****************************************************************/
+
+WERROR _spoolss_FindFirstPrinterChangeNotification(struct pipes_struct *p,
+ struct spoolss_FindFirstPrinterChangeNotification *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_FindNextPrinterChangeNotification
+****************************************************************/
+
+WERROR _spoolss_FindNextPrinterChangeNotification(struct pipes_struct *p,
+ struct spoolss_FindNextPrinterChangeNotification *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_RouterFindFirstPrinterChangeNotificationOld
+****************************************************************/
+
+WERROR _spoolss_RouterFindFirstPrinterChangeNotificationOld(struct pipes_struct *p,
+ struct spoolss_RouterFindFirstPrinterChangeNotificationOld *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_ReplyOpenPrinter
+****************************************************************/
+
+WERROR _spoolss_ReplyOpenPrinter(struct pipes_struct *p,
+ struct spoolss_ReplyOpenPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_RouterReplyPrinter
+****************************************************************/
+
+WERROR _spoolss_RouterReplyPrinter(struct pipes_struct *p,
+ struct spoolss_RouterReplyPrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_ReplyClosePrinter
+****************************************************************/
+
+WERROR _spoolss_ReplyClosePrinter(struct pipes_struct *p,
+ struct spoolss_ReplyClosePrinter *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_AddPortEx
+****************************************************************/
+
+WERROR _spoolss_AddPortEx(struct pipes_struct *p,
+ struct spoolss_AddPortEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_RouterFindFirstPrinterChangeNotification
+****************************************************************/
+
+WERROR _spoolss_RouterFindFirstPrinterChangeNotification(struct pipes_struct *p,
+ struct spoolss_RouterFindFirstPrinterChangeNotification *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_SpoolerInit
+****************************************************************/
+
+WERROR _spoolss_SpoolerInit(struct pipes_struct *p,
+ struct spoolss_SpoolerInit *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_ResetPrinterEx
+****************************************************************/
+
+WERROR _spoolss_ResetPrinterEx(struct pipes_struct *p,
+ struct spoolss_ResetPrinterEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_RouterReplyPrinterEx
+****************************************************************/
+
+WERROR _spoolss_RouterReplyPrinterEx(struct pipes_struct *p,
+ struct spoolss_RouterReplyPrinterEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_44
+****************************************************************/
+
+WERROR _spoolss_44(struct pipes_struct *p,
+ struct spoolss_44 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_SetPort
+****************************************************************/
+
+WERROR _spoolss_SetPort(struct pipes_struct *p,
+ struct spoolss_SetPort *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_4a
+****************************************************************/
+
+WERROR _spoolss_4a(struct pipes_struct *p,
+ struct spoolss_4a *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_4b
+****************************************************************/
+
+WERROR _spoolss_4b(struct pipes_struct *p,
+ struct spoolss_4b *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_4c
+****************************************************************/
+
+WERROR _spoolss_4c(struct pipes_struct *p,
+ struct spoolss_4c *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_53
+****************************************************************/
+
+WERROR _spoolss_53(struct pipes_struct *p,
+ struct spoolss_53 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_AddPerMachineConnection
+****************************************************************/
+
+WERROR _spoolss_AddPerMachineConnection(struct pipes_struct *p,
+ struct spoolss_AddPerMachineConnection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeletePerMachineConnection
+****************************************************************/
+
+WERROR _spoolss_DeletePerMachineConnection(struct pipes_struct *p,
+ struct spoolss_DeletePerMachineConnection *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_EnumPerMachineConnections
+****************************************************************/
+
+WERROR _spoolss_EnumPerMachineConnections(struct pipes_struct *p,
+ struct spoolss_EnumPerMachineConnections *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_5a
+****************************************************************/
+
+WERROR _spoolss_5a(struct pipes_struct *p,
+ struct spoolss_5a *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_5b
+****************************************************************/
+
+WERROR _spoolss_5b(struct pipes_struct *p,
+ struct spoolss_5b *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_5c
+****************************************************************/
+
+WERROR _spoolss_5c(struct pipes_struct *p,
+ struct spoolss_5c *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_5d
+****************************************************************/
+
+WERROR _spoolss_5d(struct pipes_struct *p,
+ struct spoolss_5d *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_5e
+****************************************************************/
+
+WERROR _spoolss_5e(struct pipes_struct *p,
+ struct spoolss_5e *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_5f
+****************************************************************/
+
+WERROR _spoolss_5f(struct pipes_struct *p,
+ struct spoolss_5f *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_60
+****************************************************************/
+
+WERROR _spoolss_60(struct pipes_struct *p,
+ struct spoolss_60 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_SendRecvBidiData
+****************************************************************/
+
+WERROR _spoolss_SendRecvBidiData(struct pipes_struct *p,
+ struct spoolss_SendRecvBidiData *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_62
+****************************************************************/
+
+WERROR _spoolss_62(struct pipes_struct *p,
+ struct spoolss_62 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_63
+****************************************************************/
+
+WERROR _spoolss_63(struct pipes_struct *p,
+ struct spoolss_63 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_64
+****************************************************************/
+
+WERROR _spoolss_64(struct pipes_struct *p,
+ struct spoolss_64 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_65
+****************************************************************/
+
+WERROR _spoolss_65(struct pipes_struct *p,
+ struct spoolss_65 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_GetCorePrinterDrivers
+****************************************************************/
+
+HRESULT _spoolss_GetCorePrinterDrivers(struct pipes_struct *p,
+ struct spoolss_GetCorePrinterDrivers *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_67
+****************************************************************/
+
+WERROR _spoolss_67(struct pipes_struct *p,
+ struct spoolss_67 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_GetPrinterDriverPackagePath
+****************************************************************/
+
+HRESULT _spoolss_GetPrinterDriverPackagePath(struct pipes_struct *p,
+ struct spoolss_GetPrinterDriverPackagePath *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return HRES_ERROR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_69
+****************************************************************/
+
+WERROR _spoolss_69(struct pipes_struct *p,
+ struct spoolss_69 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_6a
+****************************************************************/
+
+WERROR _spoolss_6a(struct pipes_struct *p,
+ struct spoolss_6a *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_6b
+****************************************************************/
+
+WERROR _spoolss_6b(struct pipes_struct *p,
+ struct spoolss_6b *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_6c
+****************************************************************/
+
+WERROR _spoolss_6c(struct pipes_struct *p,
+ struct spoolss_6c *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_6d
+****************************************************************/
+
+WERROR _spoolss_6d(struct pipes_struct *p,
+ struct spoolss_6d *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_GetJobNamedPropertyValue
+****************************************************************/
+
+WERROR _spoolss_GetJobNamedPropertyValue(struct pipes_struct *p,
+ struct spoolss_GetJobNamedPropertyValue *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_SetJobNamedProperty
+****************************************************************/
+
+WERROR _spoolss_SetJobNamedProperty(struct pipes_struct *p,
+ struct spoolss_SetJobNamedProperty *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_DeleteJobNamedProperty
+****************************************************************/
+
+WERROR _spoolss_DeleteJobNamedProperty(struct pipes_struct *p,
+ struct spoolss_DeleteJobNamedProperty *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_EnumJobNamedProperties
+****************************************************************/
+
+WERROR _spoolss_EnumJobNamedProperties(struct pipes_struct *p,
+ struct spoolss_EnumJobNamedProperties *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_72
+****************************************************************/
+
+WERROR _spoolss_72(struct pipes_struct *p,
+ struct spoolss_72 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_73
+****************************************************************/
+
+WERROR _spoolss_73(struct pipes_struct *p,
+ struct spoolss_73 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _spoolss_RpcLogJobInfoForBranchOffice
+****************************************************************/
+
+WERROR _spoolss_LogJobInfoForBranchOffice(struct pipes_struct *p,
+ struct spoolss_LogJobInfoForBranchOffice *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+static NTSTATUS spoolss__op_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+static NTSTATUS spoolss__op_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+#define DCESRV_INTERFACE_SPOOLSS_INIT_SERVER \
+ spoolss_init_server
+
+#define DCESRV_INTERFACE_SPOOLSS_SHUTDOWN_SERVER \
+ spoolss_shutdown_server
+
+static NTSTATUS spoolss_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ bool ok;
+
+ /*
+ * Migrate the printers first.
+ */
+ ok = nt_printing_tdb_migrate(msg_ctx);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return spoolss__op_init_server(dce_ctx, ep_server);
+}
+
+static NTSTATUS spoolss_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ srv_spoolss_cleanup();
+
+ return spoolss__op_shutdown_server(dce_ctx, ep_server);
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_spoolss_scompat.c"
diff --git a/source3/rpc_server/spoolss/srv_spoolss_nt.h b/source3/rpc_server/spoolss/srv_spoolss_nt.h
new file mode 100644
index 0000000..d6d141a
--- /dev/null
+++ b/source3/rpc_server/spoolss/srv_spoolss_nt.h
@@ -0,0 +1,40 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-2000,
+ * Copyright (C) Jean François Micouleau 1998-2000,
+ * Copyright (C) Jeremy Allison 2001-2002,
+ * Copyright (C) Gerald Carter 2000-2004,
+ * Copyright (C) Tim Potter 2001-2002.
+ * Copyright (C) Guenther Deschner 2009-2010.
+ * Copyright (C) Andreas Schneider 2010.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_SERVER_SPOOLSS_SRV_SPOOLSS_NT_H_
+#define _RPC_SERVER_SPOOLSS_SRV_SPOOLSS_NT_H_
+
+/* The following definitions come from rpc_server/srv_spoolss_nt.c */
+void srv_spoolss_cleanup(void);
+
+void do_drv_upgrade_printer(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void update_monitored_printq_cache(struct messaging_context *msg_ctx);
+
+#endif /* _RPC_SERVER_SPOOLSS_SRV_SPOOLSS_NT_H_ */
diff --git a/source3/rpc_server/spoolss/srv_spoolss_util.c b/source3/rpc_server/spoolss/srv_spoolss_util.c
new file mode 100644
index 0000000..be3c8fc
--- /dev/null
+++ b/source3/rpc_server/spoolss/srv_spoolss_util.c
@@ -0,0 +1,917 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SPOOLSS RPC Pipe server / winreg client routines
+ *
+ * Copyright (c) 2010 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "../librpc/gen_ndr/ndr_winreg.h"
+#include "srv_spoolss_util.h"
+#include "rpc_client/cli_winreg_spoolss.h"
+
+WERROR winreg_printer_binding_handle(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **winreg_binding_handle)
+{
+ struct tsocket_address *local;
+ NTSTATUS status;
+ int rc;
+
+ rc = tsocket_address_inet_from_strings(mem_ctx,
+ "ip",
+ "127.0.0.1",
+ 0,
+ &local);
+ if (rc < 0) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = rpcint_binding_handle(mem_ctx,
+ &ndr_table_winreg,
+ local,
+ NULL,
+ session_info,
+ msg_ctx,
+ winreg_binding_handle);
+ talloc_free(local);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("winreg_printer_binding_handle: Could not connect to winreg pipe: %s\n",
+ nt_errstr(status)));
+ return ntstatus_to_werror(status);
+ }
+
+ return WERR_OK;
+}
+
+WERROR winreg_delete_printer_key_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_delete_printer_key(tmp_ctx,
+ b,
+ printer,
+ key);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_update_changeid_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_printer_update_changeid(mem_ctx,
+ b,
+ printer);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_get_changeid_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ uint32_t *pchangeid)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_printer_get_changeid(mem_ctx,
+ b,
+ printer,
+ pchangeid);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_printer_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **pinfo2)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_printer(mem_ctx,
+ b,
+ printer,
+ pinfo2);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_create_printer_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_create_printer(mem_ctx,
+ b,
+ sharename);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_update_printer_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ uint32_t info2_mask,
+ struct spoolss_SetPrinterInfo2 *info2,
+ struct spoolss_DeviceMode *devmode,
+ struct security_descriptor *secdesc)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_update_printer(mem_ctx,
+ b,
+ sharename,
+ info2_mask,
+ info2,
+ devmode,
+ secdesc);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_set_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type type,
+ uint8_t *data,
+ uint32_t data_size)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_set_printer_dataex(mem_ctx,
+ b,
+ printer,
+ key,
+ value,
+ type,
+ data,
+ data_size);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_enum_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_values,
+ struct spoolss_PrinterEnumValues **penum_values)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_enum_printer_dataex(mem_ctx,
+ b,
+ printer,
+ key,
+ pnum_values,
+ penum_values);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type *type,
+ uint8_t **data,
+ uint32_t *data_size)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_printer_dataex(mem_ctx,
+ b,
+ printer,
+ key,
+ value,
+ type,
+ data,
+ data_size);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_delete_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ const char *value)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_delete_printer_dataex(mem_ctx,
+ b,
+ printer,
+ key,
+ value);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ const char *driver_name,
+ uint32_t driver_version,
+ struct spoolss_DriverInfo8 **_info8)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_driver(mem_ctx,
+ b,
+ architecture,
+ driver_name,
+ driver_version,
+ _info8);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_driver_list_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ uint32_t version,
+ uint32_t *num_drivers,
+ const char ***drivers_p)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_driver_list(mem_ctx,
+ b,
+ architecture,
+ version,
+ num_drivers,
+ drivers_p);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_del_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_DriverInfo8 *info8,
+ uint32_t version)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_del_driver(mem_ctx,
+ b,
+ info8,
+ version);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_add_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_AddDriverInfoCtr *r,
+ const char **driver_name,
+ uint32_t *driver_version)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_add_driver(mem_ctx,
+ b,
+ r,
+ driver_name,
+ driver_version);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_core_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ const struct GUID *core_driver_guid,
+ struct spoolss_CorePrinterDriver **core_printer_driver)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_core_driver(mem_ctx,
+ b,
+ architecture,
+ core_driver_guid,
+ core_printer_driver);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_add_core_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ const struct spoolss_CorePrinterDriver *core_printer_driver)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_add_core_driver(mem_ctx,
+ b,
+ architecture,
+ core_printer_driver);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_add_driver_package_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *package_id,
+ const char *architecture,
+ const char *driver_store_path,
+ const char *cab_path)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_add_driver_package(mem_ctx,
+ b,
+ package_id,
+ architecture,
+ driver_store_path,
+ cab_path);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_driver_package_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *package_id,
+ const char *architecture,
+ const char **driver_store_path,
+ const char **cab_path)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_driver_package(mem_ctx,
+ b,
+ package_id,
+ architecture,
+ driver_store_path,
+ cab_path);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_del_driver_package_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *package_id,
+ const char *architecture)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_del_driver_package(mem_ctx,
+ b,
+ package_id,
+ architecture);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_get_printer_secdesc_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct spoolss_security_descriptor **psecdesc)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_get_printer_secdesc(mem_ctx,
+ b,
+ sharename,
+ psecdesc);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_set_printer_secdesc_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ const struct spoolss_security_descriptor *secdesc)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_set_printer_secdesc(mem_ctx,
+ b,
+ sharename,
+ secdesc);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_enumforms1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t *pnum_info,
+ union spoolss_FormInfo **pinfo)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_printer_enumforms1(mem_ctx,
+ b,
+ pnum_info,
+ pinfo);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_getform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *form_name,
+ struct spoolss_FormInfo1 *r)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_printer_getform1(mem_ctx,
+ b,
+ form_name,
+ r);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_addform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_AddFormInfo1 *form)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_printer_addform1(mem_ctx,
+ b,
+ form);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_setform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *form_name,
+ struct spoolss_AddFormInfo1 *form)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_printer_setform1(mem_ctx,
+ b,
+ form_name,
+ form);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_printer_deleteform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *form_name)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_printer_deleteform1(mem_ctx,
+ b,
+ form_name);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
+
+WERROR winreg_enum_printer_key_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_subkeys,
+ const char ***psubkeys)
+{
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ result = winreg_printer_binding_handle(tmp_ctx, session_info, msg_ctx, &b);
+ if (!W_ERROR_IS_OK(result)) {
+ talloc_free(tmp_ctx);
+ return result;
+ }
+
+ result = winreg_enum_printer_key(mem_ctx,
+ b,
+ printer,
+ key,
+ pnum_subkeys,
+ psubkeys);
+
+ talloc_free(tmp_ctx);
+ return result;
+}
diff --git a/source3/rpc_server/spoolss/srv_spoolss_util.h b/source3/rpc_server/spoolss/srv_spoolss_util.h
new file mode 100644
index 0000000..a9b3072
--- /dev/null
+++ b/source3/rpc_server/spoolss/srv_spoolss_util.h
@@ -0,0 +1,190 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SPOOLSS RPC Pipe server / winreg client routines
+ *
+ * Copyright (c) 2010 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SRV_SPOOLSS_UITL_H
+#define _SRV_SPOOLSS_UITL_H
+
+struct auth_session_info;
+struct dcerpc_binding_handle;
+
+WERROR winreg_printer_binding_handle(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct dcerpc_binding_handle **winreg_binding_handle);
+
+WERROR winreg_delete_printer_key_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key);
+WERROR winreg_printer_update_changeid_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer);
+WERROR winreg_printer_get_changeid_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ uint32_t *pchangeid);
+WERROR winreg_get_printer_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ struct spoolss_PrinterInfo2 **pinfo2);
+WERROR winreg_create_printer_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename);
+WERROR winreg_update_printer_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ uint32_t info2_mask,
+ struct spoolss_SetPrinterInfo2 *info2,
+ struct spoolss_DeviceMode *devmode,
+ struct security_descriptor *secdesc);
+WERROR winreg_set_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type type,
+ uint8_t *data,
+ uint32_t data_size);
+WERROR winreg_enum_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_values,
+ struct spoolss_PrinterEnumValues **penum_values);
+WERROR winreg_get_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ const char *value,
+ enum winreg_Type *type,
+ uint8_t **data,
+ uint32_t *data_size);
+WERROR winreg_delete_printer_dataex_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ const char *value);
+WERROR winreg_get_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ const char *driver_name,
+ uint32_t driver_version,
+ struct spoolss_DriverInfo8 **_info8);
+WERROR winreg_get_driver_list_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ uint32_t version,
+ uint32_t *num_drivers,
+ const char ***drivers_p);
+WERROR winreg_del_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_DriverInfo8 *info8,
+ uint32_t version);
+WERROR winreg_add_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_AddDriverInfoCtr *r,
+ const char **driver_name,
+ uint32_t *driver_version);
+WERROR winreg_get_printer_secdesc_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ struct spoolss_security_descriptor **psecdesc);
+WERROR winreg_set_printer_secdesc_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *sharename,
+ const struct spoolss_security_descriptor *secdesc);
+WERROR winreg_printer_enumforms1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ uint32_t *pnum_info,
+ union spoolss_FormInfo **pinfo);
+WERROR winreg_printer_getform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *form_name,
+ struct spoolss_FormInfo1 *r);
+WERROR winreg_printer_addform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ struct spoolss_AddFormInfo1 *form);
+WERROR winreg_printer_setform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *form_name,
+ struct spoolss_AddFormInfo1 *form);
+WERROR winreg_printer_deleteform1_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *form_name);
+WERROR winreg_enum_printer_key_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *printer,
+ const char *key,
+ uint32_t *pnum_subkeys,
+ const char ***psubkeys);
+WERROR winreg_get_core_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ const struct GUID *core_driver_guid,
+ struct spoolss_CorePrinterDriver **core_printer_driver);
+WERROR winreg_add_core_driver_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *architecture,
+ const struct spoolss_CorePrinterDriver *core_printer_driver);
+WERROR winreg_add_driver_package_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *package_id,
+ const char *architecture,
+ const char *driver_store_path,
+ const char *cab_path);
+WERROR winreg_get_driver_package_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *package_id,
+ const char *architecture,
+ const char **driver_store_path,
+ const char **cab_path);
+WERROR winreg_del_driver_package_internal(TALLOC_CTX *mem_ctx,
+ const struct auth_session_info *session_info,
+ struct messaging_context *msg_ctx,
+ const char *package_id,
+ const char *architecture);
+#endif /* _SRV_SPOOLSS_UITL_H */
diff --git a/source3/rpc_server/srv_access_check.c b/source3/rpc_server/srv_access_check.c
new file mode 100644
index 0000000..23d9252
--- /dev/null
+++ b/source3/rpc_server/srv_access_check.c
@@ -0,0 +1,168 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ * Copyright (C) Paul Ashton 1997,
+ * Copyright (C) Marc Jacobsen 1999,
+ * Copyright (C) Jeremy Allison 2001-2008,
+ * Copyright (C) Jean François Micouleau 1998-2001,
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002,
+ * Copyright (C) Gerald (Jerry) Carter 2003-2004,
+ * Copyright (C) Simo Sorce 2003.
+ * Copyright (C) Volker Lendecke 2005.
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "rpc_server/srv_access_check.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+/*******************************************************************
+ Checks if access to an object should be granted, and returns that
+ level of access for further checks.
+
+ If the user has either of needed_priv_1 or needed_priv_2 then they
+ get the rights in rights_mask in addition to any calculated rights.
+
+ This handles the unusual case where we need to allow two different
+ privileges to obtain exactly the same rights, which occurs only in
+ SAMR.
+********************************************************************/
+
+NTSTATUS access_check_object( struct security_descriptor *psd, struct security_token *token,
+ enum sec_privilege needed_priv_1, enum sec_privilege needed_priv_2,
+ uint32_t rights_mask,
+ uint32_t des_access, uint32_t *acc_granted,
+ const char *debug )
+{
+ NTSTATUS status = NT_STATUS_ACCESS_DENIED;
+ uint32_t saved_mask = 0;
+ bool priv_granted = false;
+ bool is_system = false;
+ bool is_root = false;
+
+ /* Check if we are are the system token */
+ if (security_token_is_system(token) &&
+ security_token_system_privilege(token)) {
+ is_system = true;
+ }
+
+ /* Check if we are root */
+ if (root_mode()) {
+ is_root = true;
+ }
+
+ /* check privileges; certain SAM access bits should be overridden
+ by privileges (mostly having to do with creating/modifying/deleting
+ users and groups) */
+
+ if ((needed_priv_1 != SEC_PRIV_INVALID && security_token_has_privilege(token, needed_priv_1)) ||
+ (needed_priv_2 != SEC_PRIV_INVALID && security_token_has_privilege(token, needed_priv_2))) {
+ priv_granted = true;
+ saved_mask = (des_access & rights_mask);
+ des_access &= ~saved_mask;
+
+ DEBUG(4,("access_check_object: user rights access mask [0x%x]\n",
+ rights_mask));
+ }
+
+
+ /* check the security descriptor first */
+ status = se_access_check(psd, token, des_access, acc_granted);
+ if (NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (is_system || is_root) {
+ DEBUG(4,
+ ("%s: ACCESS should be DENIED (requested: %#010x)\n"
+ "but overritten by %s\n",
+ debug,
+ des_access,
+ is_root ? "euid == initial uid" : "system token"));
+
+ priv_granted = true;
+ *acc_granted = des_access;
+
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+
+done:
+ if (priv_granted) {
+ /* add in any bits saved during the privilege check (only
+ matters if status is ok) */
+
+ *acc_granted |= rights_mask;
+ }
+
+ DEBUG(4,("%s: access %s (requested: 0x%08x, granted: 0x%08x)\n",
+ debug, NT_STATUS_IS_OK(status) ? "GRANTED" : "DENIED",
+ des_access, *acc_granted));
+
+ return status;
+}
+
+
+/*******************************************************************
+ Map any MAXIMUM_ALLOWED_ACCESS request to a valid access set.
+********************************************************************/
+
+void map_max_allowed_access(const struct security_token *nt_token,
+ const struct security_unix_token *unix_token,
+ uint32_t *pacc_requested)
+{
+ if (!((*pacc_requested) & MAXIMUM_ALLOWED_ACCESS)) {
+ return;
+ }
+ *pacc_requested &= ~MAXIMUM_ALLOWED_ACCESS;
+
+ /* At least try for generic read|execute - Everyone gets that. */
+ *pacc_requested |= GENERIC_READ_ACCESS|GENERIC_EXECUTE_ACCESS;
+
+ /* root gets anything. */
+ if (unix_token->uid == sec_initial_uid()) {
+ *pacc_requested |= GENERIC_ALL_ACCESS;
+ return;
+ }
+
+ /* Full Access for 'BUILTIN\Administrators' and 'BUILTIN\Account Operators */
+
+ if (security_token_has_sid(nt_token, &global_sid_Builtin_Administrators) ||
+ security_token_has_sid(nt_token, &global_sid_Builtin_Account_Operators)) {
+ *pacc_requested |= GENERIC_ALL_ACCESS;
+ return;
+ }
+
+ /* Full access for DOMAIN\Domain Admins. */
+ if ( IS_DC ) {
+ struct dom_sid domadmin_sid;
+ sid_compose(&domadmin_sid, get_global_sam_sid(),
+ DOMAIN_RID_ADMINS);
+ if (security_token_has_sid(nt_token, &domadmin_sid)) {
+ *pacc_requested |= GENERIC_ALL_ACCESS;
+ return;
+ }
+ }
+ /* TODO ! Check privileges. */
+}
diff --git a/source3/rpc_server/srv_access_check.h b/source3/rpc_server/srv_access_check.h
new file mode 100644
index 0000000..4f30989
--- /dev/null
+++ b/source3/rpc_server/srv_access_check.h
@@ -0,0 +1,44 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ * Copyright (C) Paul Ashton 1997,
+ * Copyright (C) Marc Jacobsen 1999,
+ * Copyright (C) Jeremy Allison 2001-2008,
+ * Copyright (C) Jean François Micouleau 1998-2001,
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002,
+ * Copyright (C) Gerald (Jerry) Carter 2003-2004,
+ * Copyright (C) Simo Sorce 2003.
+ * Copyright (C) Volker Lendecke 2005.
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_SERVER_SRV_ACCESS_CHECK_H_
+#define _RPC_SERVER_SRV_ACCESS_CHECK_H_
+
+/* The following definitions come from rpc_server/srv_access_check.c */
+
+NTSTATUS access_check_object( struct security_descriptor *psd, struct security_token *token,
+ enum sec_privilege needed_priv_1, enum sec_privilege needed_priv_2,
+ uint32_t rights_mask,
+ uint32_t des_access, uint32_t *acc_granted,
+ const char *debug );
+void map_max_allowed_access(const struct security_token *nt_token,
+ const struct security_unix_token *unix_token,
+ uint32_t *pacc_requested);
+
+#endif /* _RPC_SERVER_SRV_ACCESS_CHECK_H_ */
diff --git a/source3/rpc_server/srv_pipe_hnd.c b/source3/rpc_server/srv_pipe_hnd.c
new file mode 100644
index 0000000..c73a4c7
--- /dev/null
+++ b/source3/rpc_server/srv_pipe_hnd.c
@@ -0,0 +1,368 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1998,
+ * Largely re-written : 2005
+ * Copyright (C) Jeremy Allison 1998 - 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "fake_file.h"
+#include "rpc_dce.h"
+#include "ntdomain.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "rpc_client/local_np.h"
+#include "rpc_server/rpc_server.h"
+#include "rpc_server/rpc_config.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "librpc/ndr/ndr_table.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+bool fsp_is_np(struct files_struct *fsp)
+{
+ enum FAKE_FILE_TYPE type;
+
+ if ((fsp == NULL) || (fsp->fake_file_handle == NULL)) {
+ return false;
+ }
+
+ type = fsp->fake_file_handle->type;
+
+ return (type == FAKE_FILE_TYPE_NAMED_PIPE_PROXY);
+}
+
+NTSTATUS np_open(TALLOC_CTX *mem_ctx, const char *name,
+ const struct tsocket_address *remote_client_address,
+ const struct tsocket_address *local_server_address,
+ struct auth_session_info *session_info,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ struct dcesrv_context *dce_ctx,
+ struct fake_file_handle **phandle)
+{
+ struct fake_file_handle *handle;
+ struct npa_state *npa = NULL;
+ int ret;
+
+ handle = talloc(mem_ctx, struct fake_file_handle);
+ if (handle == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ npa = npa_state_init(handle);
+ if (npa == NULL) {
+ TALLOC_FREE(handle);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *handle = (struct fake_file_handle) {
+ .type = FAKE_FILE_TYPE_NAMED_PIPE_PROXY,
+ .private_data = npa,
+ };
+
+ ret = local_np_connect(
+ name,
+ NCACN_NP,
+ NULL,
+ remote_client_address,
+ NULL,
+ local_server_address,
+ session_info,
+ false,
+ npa,
+ &npa->stream);
+ if (ret != 0) {
+ DBG_DEBUG("local_np_connect failed: %s\n",
+ strerror(ret));
+ TALLOC_FREE(handle);
+ return map_nt_error_from_unix(ret);
+ }
+
+ *phandle = handle;
+
+ return NT_STATUS_OK;
+}
+
+bool np_read_in_progress(struct fake_file_handle *handle)
+{
+ if (handle->type == FAKE_FILE_TYPE_NAMED_PIPE_PROXY) {
+ struct npa_state *p =
+ talloc_get_type_abort(handle->private_data,
+ struct npa_state);
+ size_t read_count;
+
+ read_count = tevent_queue_length(p->read_queue);
+ if (read_count > 0) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+struct np_write_state {
+ struct tevent_context *ev;
+ struct npa_state *p;
+ struct iovec iov;
+ ssize_t nwritten;
+};
+
+static void np_write_done(struct tevent_req *subreq);
+
+struct tevent_req *np_write_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct fake_file_handle *handle,
+ const uint8_t *data, size_t len)
+{
+ struct tevent_req *req;
+ struct np_write_state *state;
+ struct npa_state *p = NULL;
+ struct tevent_req *subreq = NULL;
+
+ DBG_INFO("len: %zu\n", len);
+ dump_data(50, data, len);
+
+ req = tevent_req_create(mem_ctx, &state, struct np_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (handle->type != FAKE_FILE_TYPE_NAMED_PIPE_PROXY) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ if (len == 0) {
+ state->nwritten = 0;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ p = talloc_get_type_abort(handle->private_data, struct npa_state);
+
+ state->ev = ev;
+ state->p = p;
+ state->iov.iov_base = discard_const_p(void, data);
+ state->iov.iov_len = len;
+
+ subreq = tstream_writev_queue_send(
+ state, ev, p->stream, p->write_queue, &state->iov, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, np_write_done, req);
+ return req;
+}
+
+static void np_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct np_write_state *state = tevent_req_data(
+ req, struct np_write_state);
+ ssize_t received;
+ int err;
+
+ received = tstream_writev_queue_recv(subreq, &err);
+ if (received < 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+ state->nwritten = received;
+ tevent_req_done(req);
+}
+
+NTSTATUS np_write_recv(struct tevent_req *req, ssize_t *pnwritten)
+{
+ struct np_write_state *state = tevent_req_data(
+ req, struct np_write_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pnwritten = state->nwritten;
+ return NT_STATUS_OK;
+}
+
+struct np_ipc_readv_next_vector_state {
+ uint8_t *buf;
+ size_t len;
+ off_t ofs;
+ size_t remaining;
+};
+
+static void np_ipc_readv_next_vector_init(struct np_ipc_readv_next_vector_state *s,
+ uint8_t *buf, size_t len)
+{
+ ZERO_STRUCTP(s);
+
+ s->buf = buf;
+ s->len = MIN(len, UINT16_MAX);
+}
+
+static int np_ipc_readv_next_vector(struct tstream_context *stream,
+ void *private_data,
+ TALLOC_CTX *mem_ctx,
+ struct iovec **_vector,
+ size_t *count)
+{
+ struct np_ipc_readv_next_vector_state *state =
+ (struct np_ipc_readv_next_vector_state *)private_data;
+ struct iovec *vector;
+ ssize_t pending;
+ size_t wanted;
+
+ if (state->ofs == state->len) {
+ *_vector = NULL;
+ *count = 0;
+ return 0;
+ }
+
+ pending = tstream_pending_bytes(stream);
+ if (pending == -1) {
+ return -1;
+ }
+
+ if (pending == 0 && state->ofs != 0) {
+ /* return a short read */
+ *_vector = NULL;
+ *count = 0;
+ return 0;
+ }
+
+ if (pending == 0) {
+ /* we want at least one byte and recheck again */
+ wanted = 1;
+ } else {
+ size_t missing = state->len - state->ofs;
+ if (pending > missing) {
+ /* there's more available */
+ state->remaining = pending - missing;
+ wanted = missing;
+ } else {
+ /* read what we can get and recheck in the next cycle */
+ wanted = pending;
+ }
+ }
+
+ vector = talloc_array(mem_ctx, struct iovec, 1);
+ if (!vector) {
+ return -1;
+ }
+
+ vector[0].iov_base = state->buf + state->ofs;
+ vector[0].iov_len = wanted;
+
+ state->ofs += wanted;
+
+ *_vector = vector;
+ *count = 1;
+ return 0;
+}
+
+struct np_read_state {
+ struct npa_state *p;
+ struct np_ipc_readv_next_vector_state next_vector;
+
+ ssize_t nread;
+ bool is_data_outstanding;
+};
+
+static void np_read_done(struct tevent_req *subreq);
+
+struct tevent_req *np_read_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct fake_file_handle *handle,
+ uint8_t *data, size_t len)
+{
+ struct tevent_req *req;
+ struct np_read_state *state;
+ struct npa_state *p = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct np_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (handle->type != FAKE_FILE_TYPE_NAMED_PIPE_PROXY) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ p = talloc_get_type_abort(handle->private_data, struct npa_state);
+
+ np_ipc_readv_next_vector_init(&state->next_vector, data, len);
+
+ subreq = tstream_readv_pdu_queue_send(
+ state,
+ ev,
+ p->stream,
+ p->read_queue,
+ np_ipc_readv_next_vector,
+ &state->next_vector);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, np_read_done, req);
+ return req;
+}
+
+static void np_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct np_read_state *state = tevent_req_data(
+ req, struct np_read_state);
+ ssize_t ret;
+ int err;
+
+ ret = tstream_readv_pdu_queue_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ state->nread = ret;
+ state->is_data_outstanding = (state->next_vector.remaining > 0);
+
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS np_read_recv(struct tevent_req *req, ssize_t *nread,
+ bool *is_data_outstanding)
+{
+ struct np_read_state *state = tevent_req_data(
+ req, struct np_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ DEBUG(10, ("Received %d bytes. There is %smore data outstanding\n",
+ (int)state->nread, state->is_data_outstanding?"":"no "));
+
+ *nread = state->nread;
+ *is_data_outstanding = state->is_data_outstanding;
+ return NT_STATUS_OK;
+}
diff --git a/source3/rpc_server/srv_pipe_hnd.h b/source3/rpc_server/srv_pipe_hnd.h
new file mode 100644
index 0000000..ba35135
--- /dev/null
+++ b/source3/rpc_server/srv_pipe_hnd.h
@@ -0,0 +1,50 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1998,
+ * Largely re-written : 2005
+ * Copyright (C) Jeremy Allison 1998 - 2005
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_SERVER_SRV_PIPE_HND_H_
+#define _RPC_SERVER_SRV_PIPE_HND_H_
+
+struct tsocket_address;
+struct pipes_struct;
+
+/* The following definitions come from rpc_server/srv_pipe_hnd.c */
+
+bool fsp_is_np(struct files_struct *fsp);
+NTSTATUS np_open(TALLOC_CTX *mem_ctx, const char *name,
+ const struct tsocket_address *remote_client_address,
+ const struct tsocket_address *local_server_address,
+ struct auth_session_info *session_info,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ struct dcesrv_context *dce_ctx,
+ struct fake_file_handle **phandle);
+bool np_read_in_progress(struct fake_file_handle *handle);
+struct tevent_req *np_write_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct fake_file_handle *handle,
+ const uint8_t *data, size_t len);
+NTSTATUS np_write_recv(struct tevent_req *req, ssize_t *pnwritten);
+struct tevent_req *np_read_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct fake_file_handle *handle,
+ uint8_t *data, size_t len);
+NTSTATUS np_read_recv(struct tevent_req *req, ssize_t *nread,
+ bool *is_data_outstanding);
+
+#endif /* _RPC_SERVER_SRV_PIPE_HND_H_ */
diff --git a/source3/rpc_server/srvsvc/srv_srvsvc_nt.c b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
new file mode 100644
index 0000000..29d224c
--- /dev/null
+++ b/source3/rpc_server/srvsvc/srv_srvsvc_nt.c
@@ -0,0 +1,3202 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Jeremy Allison 2001.
+ * Copyright (C) Nigel Williams 2001.
+ * Copyright (C) Gerald (Jerry) Carter 2006.
+ * Copyright (C) Guenther Deschner 2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This is the implementation of the srvsvc pipe. */
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "lib/util/server_id.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_srvsvc.h"
+#include "librpc/gen_ndr/ndr_srvsvc_scompat.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "dbwrap/dbwrap.h"
+#include "session.h"
+#include "../lib/util/util_pw.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "auth.h"
+#include "messages.h"
+#include "serverid.h"
+#include "lib/global_contexts.h"
+#include "source3/lib/substitute.h"
+#include "lib/tsocket/tsocket.h"
+#include "librpc/rpc/dcesrv_core.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+#define MAX_SERVER_DISK_ENTRIES 15
+
+/* Use for enumerating connections, pipes, & files */
+
+struct file_enum_count {
+ TALLOC_CTX *ctx;
+ const char *username;
+ struct srvsvc_NetFileCtr3 *ctr3;
+ struct file_id *fids;
+};
+
+struct sess_file_info {
+ struct srvsvc_NetSessCtr1 *ctr;
+ struct sessionid *session_list;
+ uint32_t resume_handle;
+ uint32_t num_entries;
+};
+
+struct share_file_stat {
+ struct srvsvc_NetConnInfo1 *netconn_arr;
+ struct server_id *svrid_arr;
+ const char *in_sharepath;
+ uint32_t resp_entries;
+ uint32_t total_entries;
+};
+
+struct share_conn_stat {
+ TALLOC_CTX *ctx;
+ const char *sharename;
+ struct server_id *svrid_arr;
+ int count;
+};
+
+/*******************************************************************
+********************************************************************/
+
+static int enum_file_fn(struct file_id id,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ void *private_data)
+{
+ struct file_enum_count *fenum =
+ (struct file_enum_count *)private_data;
+ struct srvsvc_NetFileCtr3 *ctr3 = fenum->ctr3;
+ struct srvsvc_NetFileInfo3 *f;
+ struct file_id *fids = NULL;
+ char *fullpath = NULL;
+ uint32_t permissions;
+ const char *username;
+
+ /* If the pid was not found delete the entry from connections.tdb */
+
+ if ( !process_exists(e->pid) ) {
+ return 0;
+ }
+
+ username = uidtoname(e->uid);
+
+ if ((fenum->username != NULL)
+ && !strequal(username, fenum->username)) {
+ return 0;
+ }
+
+ f = talloc_realloc(
+ fenum->ctx,
+ ctr3->array,
+ struct srvsvc_NetFileInfo3,
+ ctr3->count+1);
+ if ( !f ) {
+ DBG_ERR("realloc failed for %"PRIu32" items\n", ctr3->count+1);
+ return 0;
+ }
+ ctr3->array = f;
+
+ fids = talloc_realloc(
+ fenum->ctx, fenum->fids, struct file_id, ctr3->count+1);
+ if (fids == NULL) {
+ DBG_ERR("realloc failed for %"PRIu32" items\n", ctr3->count+1);
+ return 0;
+ }
+ fids[ctr3->count] = id;
+ fenum->fids = fids;
+
+ if ( strcmp(d->base_name, "." ) == 0 ) {
+ fullpath = talloc_asprintf(
+ fenum->ctx,
+ "C:%s",
+ d->servicepath);
+ } else {
+ fullpath = talloc_asprintf(
+ fenum->ctx,
+ "C:%s/%s%s",
+ d->servicepath,
+ d->base_name,
+ (d->stream_name != NULL) ? d->stream_name : "");
+ }
+ if (!fullpath) {
+ return 0;
+ }
+ string_replace( fullpath, '/', '\\' );
+
+ /* mask out create (what ever that is) */
+ permissions = e->access_mask & (FILE_READ_DATA|FILE_WRITE_DATA);
+
+ /* now fill in the srvsvc_NetFileInfo3 struct */
+
+ ctr3->array[ctr3->count] = (struct srvsvc_NetFileInfo3) {
+ .fid = (((uint32_t)(procid_to_pid(&e->pid))<<16) |
+ e->share_file_id),
+ .permissions = permissions,
+ .path = fullpath,
+ .user = username,
+ };
+
+ ctr3->count++;
+
+ return 0;
+}
+
+/*******************************************************************
+********************************************************************/
+
+static WERROR net_enum_files(TALLOC_CTX *ctx,
+ const char *username,
+ struct srvsvc_NetFileCtr3 **ctr3,
+ uint32_t resume)
+{
+ struct file_enum_count f_enum_cnt = {
+ .ctx = ctx, .username = username, .ctr3 = *ctr3,
+ };
+ uint32_t i;
+
+ share_entry_forall(enum_file_fn, (void *)&f_enum_cnt );
+
+ *ctr3 = f_enum_cnt.ctr3;
+
+ /* need to count the number of locks on a file */
+
+ for (i=0; i<(*ctr3)->count; i++) {
+ struct files_struct fsp = { .file_id = f_enum_cnt.fids[i], };
+ struct byte_range_lock *brl = NULL;
+
+ brl = brl_get_locks(ctx, &fsp);
+ if (brl == NULL) {
+ continue;
+ }
+
+ (*ctr3)->array[i].num_locks = brl_num_locks(brl);
+
+ TALLOC_FREE(brl);
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ Utility function to get the 'type' of a share from an snum.
+ ********************************************************************/
+static enum srvsvc_ShareType get_share_type(int snum)
+{
+ /* work out the share type */
+ enum srvsvc_ShareType type = STYPE_DISKTREE;
+
+ if (lp_printable(snum)) {
+ type = lp_administrative_share(snum)
+ ? STYPE_PRINTQ_HIDDEN : STYPE_PRINTQ;
+ }
+ if (strequal(lp_fstype(snum), "IPC")) {
+ type = lp_administrative_share(snum)
+ ? STYPE_IPC_HIDDEN : STYPE_IPC;
+ }
+ return type;
+}
+
+/*******************************************************************
+ Fill in a share info level 0 structure.
+ ********************************************************************/
+
+static void init_srv_share_info_0(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo0 *r, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ r->name = lp_servicename(talloc_tos(), lp_sub, snum);
+}
+
+/*******************************************************************
+ Fill in a share info level 1 structure.
+ ********************************************************************/
+
+static void init_srv_share_info_1(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo1 *r,
+ int snum)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *net_name = lp_servicename(talloc_tos(), lp_sub, snum);
+ char *remark = lp_comment(p->mem_ctx, lp_sub, snum);
+
+ if (remark) {
+ remark = talloc_sub_full(
+ p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum),
+ get_current_username(), lp_path(talloc_tos(), lp_sub, snum),
+ session_info->unix_token->uid, get_current_username(),
+ "", remark);
+ }
+
+ r->name = net_name;
+ r->type = get_share_type(snum);
+ r->comment = remark ? remark : "";
+}
+
+/*******************************************************************
+ Fill in a share info level 2 structure.
+ ********************************************************************/
+
+static void init_srv_share_info_2(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo2 *r,
+ int snum)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *remark = NULL;
+ char *path = NULL;
+ int max_connections = lp_max_connections(snum);
+ uint32_t max_uses = UINT32_MAX;
+ char *net_name = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ if (max_connections > 0) {
+ max_uses = MIN(max_connections, UINT32_MAX);
+ }
+
+ remark = lp_comment(p->mem_ctx, lp_sub, snum);
+ if (remark) {
+ remark = talloc_sub_full(
+ p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum),
+ get_current_username(), lp_path(talloc_tos(), lp_sub, snum),
+ session_info->unix_token->uid, get_current_username(),
+ "", remark);
+ }
+ path = talloc_asprintf(p->mem_ctx,
+ "C:%s", lp_path(talloc_tos(), lp_sub, snum));
+
+ if (path) {
+ /*
+ * Change / to \\ so that win2k will see it as a valid path.
+ * This was added to enable use of browsing in win2k add
+ * share dialog.
+ */
+
+ string_replace(path, '/', '\\');
+ }
+
+ r->name = net_name;
+ r->type = get_share_type(snum);
+ r->comment = remark ? remark : "";
+ r->permissions = 0;
+ r->max_users = max_uses;
+ r->current_users = 0; /* computed later */
+ r->path = path ? path : "";
+ r->password = "";
+}
+
+/*******************************************************************
+ Map any generic bits to file specific bits.
+********************************************************************/
+
+static void map_generic_share_sd_bits(struct security_descriptor *psd)
+{
+ uint32_t i;
+ struct security_acl *ps_dacl = NULL;
+
+ if (!psd)
+ return;
+
+ ps_dacl = psd->dacl;
+ if (!ps_dacl)
+ return;
+
+ for (i = 0; i < ps_dacl->num_aces; i++) {
+ struct security_ace *psa = &ps_dacl->aces[i];
+ uint32_t orig_mask = psa->access_mask;
+
+ se_map_generic(&psa->access_mask, &file_generic_mapping);
+ psa->access_mask |= orig_mask;
+ }
+}
+
+/*******************************************************************
+ Fill in a share info level 501 structure.
+********************************************************************/
+
+static void init_srv_share_info_501(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo501 *r, int snum)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *net_name = lp_servicename(talloc_tos(), lp_sub, snum);
+ char *remark = lp_comment(p->mem_ctx, lp_sub, snum);
+
+ if (remark) {
+ remark = talloc_sub_full(
+ p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum),
+ get_current_username(), lp_path(talloc_tos(), lp_sub, snum),
+ session_info->unix_token->uid, get_current_username(),
+ "", remark);
+ }
+
+ r->name = net_name;
+ r->type = get_share_type(snum);
+ r->comment = remark ? remark : "";
+
+ /*
+ * According to [MS-SRVS] 2.2.4.25, the flags field is the same as in
+ * level 1005.
+ */
+ r->csc_policy = (lp_csc_policy(snum) << SHARE_1005_CSC_POLICY_SHIFT);
+}
+
+/*******************************************************************
+ Fill in a share info level 502 structure.
+ ********************************************************************/
+
+static void init_srv_share_info_502(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo502 *r, int snum)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *net_name = lp_servicename(talloc_tos(), lp_sub, snum);
+ char *path = NULL;
+ struct security_descriptor *sd = NULL;
+ struct sec_desc_buf *sd_buf = NULL;
+ size_t sd_size = 0;
+ TALLOC_CTX *ctx = p->mem_ctx;
+ char *remark = lp_comment(ctx, lp_sub, snum);
+
+ if (remark) {
+ remark = talloc_sub_full(
+ p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum),
+ get_current_username(), lp_path(talloc_tos(), lp_sub, snum),
+ session_info->unix_token->uid, get_current_username(),
+ "", remark);
+ }
+ path = talloc_asprintf(ctx, "C:%s", lp_path(talloc_tos(), lp_sub, snum));
+ if (path) {
+ /*
+ * Change / to \\ so that win2k will see it as a valid path. This was added to
+ * enable use of browsing in win2k add share dialog.
+ */
+ string_replace(path, '/', '\\');
+ }
+
+ sd = get_share_security(ctx, lp_servicename(talloc_tos(), lp_sub, snum), &sd_size);
+
+ sd_buf = make_sec_desc_buf(p->mem_ctx, sd_size, sd);
+
+ r->name = net_name;
+ r->type = get_share_type(snum);
+ r->comment = remark ? remark : "";
+ r->permissions = 0;
+ r->max_users = (uint32_t)-1;
+ r->current_users = 1; /* ??? */
+ r->path = path ? path : "";
+ r->password = "";
+ r->sd_buf = *sd_buf;
+}
+
+/***************************************************************************
+ Fill in a share info level 1004 structure.
+ ***************************************************************************/
+
+static void init_srv_share_info_1004(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo1004 *r,
+ int snum)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *remark = lp_comment(p->mem_ctx, lp_sub, snum);
+
+ if (remark) {
+ remark = talloc_sub_full(
+ p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum),
+ get_current_username(), lp_path(talloc_tos(), lp_sub, snum),
+ session_info->unix_token->uid, get_current_username(),
+ "", remark);
+ }
+
+ r->comment = remark ? remark : "";
+}
+
+/***************************************************************************
+ Fill in a share info level 1005 structure.
+ ***************************************************************************/
+
+static void init_srv_share_info_1005(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo1005 *r,
+ int snum)
+{
+ uint32_t dfs_flags = 0;
+
+ if (lp_host_msdfs() && lp_msdfs_root(snum)) {
+ dfs_flags |= SHARE_1005_IN_DFS | SHARE_1005_DFS_ROOT;
+ }
+
+ dfs_flags |= lp_csc_policy(snum) << SHARE_1005_CSC_POLICY_SHIFT;
+
+ r->dfs_flags = dfs_flags;
+}
+
+/***************************************************************************
+ Fill in a share info level 1006 structure.
+ ***************************************************************************/
+
+static void init_srv_share_info_1006(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo1006 *r,
+ int snum)
+{
+ r->max_users = (uint32_t)-1;
+}
+
+/***************************************************************************
+ Fill in a share info level 1007 structure.
+ ***************************************************************************/
+
+static void init_srv_share_info_1007(struct pipes_struct *p,
+ struct srvsvc_NetShareInfo1007 *r,
+ int snum)
+{
+ r->flags = 0;
+ r->alternate_directory_name = "";
+}
+
+/*******************************************************************
+ Fill in a share info level 1501 structure.
+ ********************************************************************/
+
+static void init_srv_share_info_1501(struct pipes_struct *p,
+ struct sec_desc_buf **r,
+ int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct security_descriptor *sd;
+ struct sec_desc_buf *sd_buf = NULL;
+ size_t sd_size;
+ TALLOC_CTX *ctx = p->mem_ctx;
+
+ sd = get_share_security(ctx, lp_servicename(talloc_tos(), lp_sub, snum), &sd_size);
+ if (sd) {
+ sd_buf = make_sec_desc_buf(p->mem_ctx, sd_size, sd);
+ }
+
+ *r = sd_buf;
+}
+
+/*******************************************************************
+ True if it ends in '$'.
+ ********************************************************************/
+
+static bool is_hidden_share(int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *net_name = lp_servicename(talloc_tos(), lp_sub, snum);
+
+ return (net_name[strlen(net_name) - 1] == '$') ? True : False;
+}
+
+/*******************************************************************
+ Verify user is allowed to view share, access based enumeration
+********************************************************************/
+static bool is_enumeration_allowed(struct pipes_struct *p,
+ int snum)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (!lp_access_based_share_enum(snum)) {
+ return true;
+ }
+
+ if (!user_ok_token(session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token, snum)) {
+ return false;
+ }
+
+ return share_access_check(session_info->security_token,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ FILE_READ_DATA, NULL);
+}
+
+/****************************************************************************
+ Count an entry against the respective service.
+****************************************************************************/
+
+static int count_for_all_fn(struct smbXsrv_tcon_global0 *tcon, void *udp)
+{
+ union srvsvc_NetShareCtr *ctr = udp;
+
+ /* Only called for level2 */
+ struct srvsvc_NetShareCtr2 *ctr2 = ctr->ctr2;
+
+ uint32_t share_entries = ctr2->count;
+ struct srvsvc_NetShareInfo2 *info2 = ctr2->array;
+ uint32_t i = 0;
+
+ for (i = 0; i < share_entries; i++, info2++) {
+ if (strequal(tcon->share_name, info2->name)) {
+ info2->current_users++;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Count the entries belonging to all services in the connection db.
+****************************************************************************/
+
+static void count_connections_for_all_shares(union srvsvc_NetShareCtr *ctr)
+{
+ NTSTATUS status;
+ status = smbXsrv_tcon_global_traverse(count_for_all_fn, ctr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("count_connections_for_all_shares: traverse of "
+ "smbXsrv_tcon_global.tdb failed - %s\n",
+ nt_errstr(status)));
+ }
+}
+
+/*******************************************************************
+ Fill in a share info structure.
+ ********************************************************************/
+
+static WERROR init_srv_share_info_ctr(struct pipes_struct *p,
+ struct srvsvc_NetShareInfoCtr *info_ctr,
+ uint32_t *resume_handle_p,
+ uint32_t *total_entries,
+ bool all_shares)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t num_entries = 0;
+ uint32_t alloc_entries = 0;
+ int num_services = 0;
+ int snum;
+ TALLOC_CTX *ctx = p->mem_ctx;
+ uint32_t i = 0;
+ uint32_t valid_share_count = 0;
+ bool *allowed = 0;
+ union srvsvc_NetShareCtr ctr;
+ uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0;
+ const char *unix_name = session_info->unix_info->unix_name;
+ int existing_home = -1;
+ int added_home = -1;
+ WERROR ret = WERR_OK;
+
+ DEBUG(5,("init_srv_share_info_ctr\n"));
+
+ /*
+ * We need to make sure to reload the services for the connecting user.
+ * It is possible that we have includes with substitutions.
+ *
+ * include = /etc/samba/%U.conf
+ *
+ * We also need all printers and usershares.
+ *
+ * We need to be root in order to have access to registry shares
+ * and root only smb.conf files.
+ */
+ become_root();
+ lp_kill_all_services();
+ lp_load_with_shares(get_dyn_CONFIGFILE());
+ delete_and_reload_printers();
+ load_usershare_shares(NULL, connections_snum_used);
+ load_registry_shares();
+ existing_home = lp_servicenumber(unix_name);
+ if (existing_home == -1) {
+ added_home = register_homes_share(unix_name);
+ }
+ unbecome_root();
+
+ num_services = lp_numservices();
+
+ allowed = talloc_zero_array(ctx, bool, num_services);
+ if (allowed == NULL) {
+ goto nomem;
+ }
+
+ /* Count the number of entries. */
+ for (snum = 0; snum < num_services; snum++) {
+ if (lp_browseable(snum) && lp_snum_ok(snum) &&
+ lp_allow_local_address(snum, local_address) &&
+ is_enumeration_allowed(p, snum) &&
+ (all_shares || !is_hidden_share(snum))) {
+ DEBUG(10, ("counting service %s\n",
+ lp_servicename(talloc_tos(), lp_sub, snum) ? lp_servicename(talloc_tos(), lp_sub, snum) : "(null)"));
+ allowed[snum] = true;
+ num_entries++;
+ } else {
+ DEBUG(10, ("NOT counting service %s\n",
+ lp_servicename(talloc_tos(), lp_sub, snum) ? lp_servicename(talloc_tos(), lp_sub, snum) : "(null)"));
+ }
+ }
+
+ if (!num_entries || (resume_handle >= num_entries)) {
+ goto done;
+ }
+
+ /* Calculate alloc entries. */
+ alloc_entries = num_entries - resume_handle;
+ switch (info_ctr->level) {
+ case 0:
+ ctr.ctr0 = talloc_zero(ctx, struct srvsvc_NetShareCtr0);
+ if (ctr.ctr0 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr0->count = alloc_entries;
+ ctr.ctr0->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo0, alloc_entries);
+ if (ctr.ctr0->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_0(p, &ctr.ctr0->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 1:
+ ctr.ctr1 = talloc_zero(ctx, struct srvsvc_NetShareCtr1);
+ if (ctr.ctr1 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr1->count = alloc_entries;
+ ctr.ctr1->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1, alloc_entries);
+ if (ctr.ctr1->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_1(p, &ctr.ctr1->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 2:
+ ctr.ctr2 = talloc_zero(ctx, struct srvsvc_NetShareCtr2);
+ if (ctr.ctr2 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr2->count = alloc_entries;
+ ctr.ctr2->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo2, alloc_entries);
+ if (ctr.ctr2->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_2(p, &ctr.ctr2->array[i++], snum);
+ }
+ }
+
+ count_connections_for_all_shares(&ctr);
+ break;
+
+ case 501:
+ ctr.ctr501 = talloc_zero(ctx, struct srvsvc_NetShareCtr501);
+ if (ctr.ctr501 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr501->count = alloc_entries;
+ ctr.ctr501->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo501, alloc_entries);
+ if (ctr.ctr501->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_501(p, &ctr.ctr501->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 502:
+ ctr.ctr502 = talloc_zero(ctx, struct srvsvc_NetShareCtr502);
+ if (ctr.ctr502 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr502->count = alloc_entries;
+ ctr.ctr502->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo502, alloc_entries);
+ if (ctr.ctr502->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_502(p, &ctr.ctr502->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 1004:
+ ctr.ctr1004 = talloc_zero(ctx, struct srvsvc_NetShareCtr1004);
+ if (ctr.ctr1004 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr1004->count = alloc_entries;
+ ctr.ctr1004->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1004, alloc_entries);
+ if (ctr.ctr1004->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_1004(p, &ctr.ctr1004->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 1005:
+ ctr.ctr1005 = talloc_zero(ctx, struct srvsvc_NetShareCtr1005);
+ if (ctr.ctr1005 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr1005->count = alloc_entries;
+ ctr.ctr1005->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1005, alloc_entries);
+ if (ctr.ctr1005->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_1005(p, &ctr.ctr1005->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 1006:
+ ctr.ctr1006 = talloc_zero(ctx, struct srvsvc_NetShareCtr1006);
+ if (ctr.ctr1006 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr1006->count = alloc_entries;
+ ctr.ctr1006->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1006, alloc_entries);
+ if (ctr.ctr1006->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_1006(p, &ctr.ctr1006->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 1007:
+ ctr.ctr1007 = talloc_zero(ctx, struct srvsvc_NetShareCtr1007);
+ if (ctr.ctr1007 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr1007->count = alloc_entries;
+ ctr.ctr1007->array = talloc_zero_array(ctx, struct srvsvc_NetShareInfo1007, alloc_entries);
+ if (ctr.ctr1007->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ init_srv_share_info_1007(p, &ctr.ctr1007->array[i++], snum);
+ }
+ }
+
+ break;
+
+ case 1501:
+ ctr.ctr1501 = talloc_zero(ctx, struct srvsvc_NetShareCtr1501);
+ if (ctr.ctr1501 == NULL) {
+ goto nomem;
+ }
+
+ ctr.ctr1501->count = alloc_entries;
+ ctr.ctr1501->array = talloc_zero_array(ctx, struct sec_desc_buf, alloc_entries);
+ if (ctr.ctr1501->array == NULL) {
+ goto nomem;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (allowed[snum] &&
+ (resume_handle <= (i + valid_share_count++)) ) {
+ struct sec_desc_buf *sd_buf = NULL;
+ init_srv_share_info_1501(p, &sd_buf, snum);
+ ctr.ctr1501->array[i++] = *sd_buf;
+ }
+ }
+
+ break;
+
+ default:
+ DEBUG(5,("init_srv_share_info_ctr: unsupported switch value %d\n",
+ info_ctr->level));
+ ret = WERR_INVALID_LEVEL;
+ goto done;
+ }
+
+ *total_entries = alloc_entries;
+ if (resume_handle_p) {
+ if (all_shares) {
+ *resume_handle_p = (num_entries == 0) ? *resume_handle_p : 0;
+ } else {
+ *resume_handle_p = num_entries;
+ }
+ }
+
+ info_ctr->ctr = ctr;
+ ret = WERR_OK;
+ goto done;
+nomem:
+ ret = WERR_NOT_ENOUGH_MEMORY;
+done:
+ if (added_home != -1) {
+ lp_killservice(added_home);
+ }
+ return ret;
+}
+
+/*******************************************************************
+ fill in a sess info level 0 structure.
+ ********************************************************************/
+
+static WERROR init_srv_sess_info_0(struct pipes_struct *p,
+ struct srvsvc_NetSessCtr0 *ctr0,
+ uint32_t *resume_handle_p,
+ uint32_t *total_entries)
+{
+ struct sessionid *session_list;
+ uint32_t num_entries = 0;
+ uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0;
+ *total_entries = list_sessions(p->mem_ctx, &session_list);
+
+ DEBUG(5,("init_srv_sess_info_0\n"));
+
+ if (ctr0 == NULL) {
+ if (resume_handle_p) {
+ *resume_handle_p = 0;
+ }
+ return WERR_OK;
+ }
+
+ for (; resume_handle < *total_entries; resume_handle++) {
+
+ ctr0->array = talloc_realloc(p->mem_ctx,
+ ctr0->array,
+ struct srvsvc_NetSessInfo0,
+ num_entries+1);
+ W_ERROR_HAVE_NO_MEMORY(ctr0->array);
+
+ ctr0->array[num_entries].client =
+ session_list[resume_handle].remote_machine;
+
+ num_entries++;
+ }
+
+ ctr0->count = num_entries;
+
+ if (resume_handle_p) {
+ if (*resume_handle_p >= *total_entries) {
+ *resume_handle_p = 0;
+ } else {
+ *resume_handle_p = resume_handle;
+ }
+ }
+
+ return WERR_OK;
+}
+
+/***********************************************************************
+ * find out the session on which this file is open and bump up its count
+ **********************************************************************/
+
+static int count_sess_files_fn(struct file_id fid,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ void *data)
+{
+ struct sess_file_info *info = data;
+ uint32_t rh = info->resume_handle;
+ uint32_t i;
+
+ for (i=0; i < info->num_entries; i++) {
+ /* rh+info->num_entries is safe, as we've
+ ensured that:
+ *total_entries > resume_handle &&
+ info->num_entries = *total_entries - resume_handle;
+ inside init_srv_sess_info_1() below.
+ */
+ struct sessionid *sess = &info->session_list[rh + i];
+ if ((e->uid == sess->uid) &&
+ server_id_equal(&e->pid, &sess->pid)) {
+
+ info->ctr->array[i].num_open++;
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/*******************************************************************
+ * count the num of open files on all sessions
+ *******************************************************************/
+
+static void net_count_files_for_all_sess(struct srvsvc_NetSessCtr1 *ctr1,
+ struct sessionid *session_list,
+ uint32_t resume_handle,
+ uint32_t num_entries)
+{
+ struct sess_file_info s_file_info;
+
+ s_file_info.ctr = ctr1;
+ s_file_info.session_list = session_list;
+ s_file_info.resume_handle = resume_handle;
+ s_file_info.num_entries = num_entries;
+
+ share_entry_forall(count_sess_files_fn, &s_file_info);
+}
+
+/*******************************************************************
+ fill in a sess info level 1 structure.
+ ********************************************************************/
+
+static WERROR init_srv_sess_info_1(struct pipes_struct *p,
+ struct srvsvc_NetSessCtr1 *ctr1,
+ uint32_t *resume_handle_p,
+ uint32_t *total_entries)
+{
+ struct sessionid *session_list;
+ uint32_t num_entries = 0;
+ time_t now = time(NULL);
+ uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0;
+
+ ZERO_STRUCTP(ctr1);
+
+ if (ctr1 == NULL) {
+ if (resume_handle_p) {
+ *resume_handle_p = 0;
+ }
+ return WERR_OK;
+ }
+
+ *total_entries = list_sessions(p->mem_ctx, &session_list);
+
+ if (resume_handle >= *total_entries) {
+ if (resume_handle_p) {
+ *resume_handle_p = 0;
+ }
+ return WERR_OK;
+ }
+
+ /* We know num_entries must be positive, due to
+ the check resume_handle >= *total_entries above. */
+
+ num_entries = *total_entries - resume_handle;
+
+ ctr1->array = talloc_zero_array(p->mem_ctx,
+ struct srvsvc_NetSessInfo1,
+ num_entries);
+
+ W_ERROR_HAVE_NO_MEMORY(ctr1->array);
+
+ for (num_entries = 0; resume_handle < *total_entries; num_entries++, resume_handle++) {
+ uint32_t connect_time;
+ bool guest;
+
+ connect_time = (uint32_t)(now - session_list[resume_handle].connect_start);
+ guest = strequal( session_list[resume_handle].username, lp_guest_account() );
+
+ ctr1->array[num_entries].client = session_list[resume_handle].remote_machine;
+ ctr1->array[num_entries].user = session_list[resume_handle].username;
+ ctr1->array[num_entries].num_open = 0;/* computed later */
+ ctr1->array[num_entries].time = connect_time;
+ ctr1->array[num_entries].idle_time = 0;
+ ctr1->array[num_entries].user_flags = guest;
+ }
+
+ ctr1->count = num_entries;
+
+ /* count open files on all sessions in single tdb traversal */
+ net_count_files_for_all_sess(ctr1, session_list,
+ resume_handle_p ? *resume_handle_p : 0,
+ num_entries);
+
+ if (resume_handle_p) {
+ if (*resume_handle_p >= *total_entries) {
+ *resume_handle_p = 0;
+ } else {
+ *resume_handle_p = resume_handle;
+ }
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ find the share connection on which this open exists.
+ ********************************************************************/
+
+static int share_file_fn(struct file_id fid,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ void *data)
+{
+ struct share_file_stat *sfs = data;
+ uint32_t i;
+ uint32_t offset = sfs->total_entries - sfs->resp_entries;
+
+ if (strequal(d->servicepath, sfs->in_sharepath)) {
+ for (i=0; i < sfs->resp_entries; i++) {
+ if (server_id_equal(
+ &e->pid, &sfs->svrid_arr[offset + i])) {
+ sfs->netconn_arr[i].num_open ++;
+ return 0;
+ }
+ }
+ }
+ return 0;
+}
+
+/*******************************************************************
+ count number of open files on given share connections.
+ ********************************************************************/
+
+static void count_share_opens(struct srvsvc_NetConnInfo1 *arr,
+ struct server_id *svrid_arr, char *sharepath,
+ uint32_t resp_entries, uint32_t total_entries)
+{
+ struct share_file_stat sfs;
+
+ sfs.netconn_arr = arr;
+ sfs.svrid_arr = svrid_arr;
+ sfs.in_sharepath = sharepath;
+ sfs.resp_entries = resp_entries;
+ sfs.total_entries = total_entries;
+
+ share_entry_forall(share_file_fn, &sfs);
+}
+
+/****************************************************************************
+ process an entry from the connection db.
+****************************************************************************/
+
+static int share_conn_fn(struct smbXsrv_tcon_global0 *tcon,
+ void *data)
+{
+ struct share_conn_stat *scs = data;
+
+ if (!process_exists(tcon->server_id)) {
+ return 0;
+ }
+
+ if (strequal(tcon->share_name, scs->sharename)) {
+ scs->svrid_arr = talloc_realloc(scs->ctx, scs->svrid_arr,
+ struct server_id,
+ scs->count + 1);
+ if (!scs->svrid_arr) {
+ return 0;
+ }
+
+ scs->svrid_arr[scs->count] = tcon->server_id;
+ scs->count++;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Count the connections to a share. Build an array of serverid's owning these
+ connections.
+****************************************************************************/
+
+static uint32_t count_share_conns(TALLOC_CTX *ctx, const char *sharename,
+ struct server_id **arr)
+{
+ struct share_conn_stat scs;
+ NTSTATUS status;
+
+ scs.ctx = ctx;
+ scs.sharename = sharename;
+ scs.svrid_arr = NULL;
+ scs.count = 0;
+
+ status = smbXsrv_tcon_global_traverse(share_conn_fn, &scs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("count_share_conns: traverse of "
+ "smbXsrv_tcon_global.tdb failed - %s\n",
+ nt_errstr(status)));
+ return 0;
+ }
+
+ *arr = scs.svrid_arr;
+ return scs.count;
+}
+
+/*******************************************************************
+ fill in a conn info level 0 structure.
+ ********************************************************************/
+
+static WERROR init_srv_conn_info_0(struct srvsvc_NetConnCtr0 *ctr0,
+ uint32_t *resume_handle_p,
+ uint32_t *total_entries)
+{
+ uint32_t num_entries = 0;
+ uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0;
+
+ DEBUG(5,("init_srv_conn_info_0\n"));
+
+ if (ctr0 == NULL) {
+ if (resume_handle_p) {
+ *resume_handle_p = 0;
+ }
+ return WERR_OK;
+ }
+
+ *total_entries = 1;
+
+ ZERO_STRUCTP(ctr0);
+
+ for (; resume_handle < *total_entries; resume_handle++) {
+
+ ctr0->array = talloc_realloc(talloc_tos(),
+ ctr0->array,
+ struct srvsvc_NetConnInfo0,
+ num_entries+1);
+ if (!ctr0->array) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ctr0->array[num_entries].conn_id = *total_entries;
+
+ /* move on to creating next connection */
+ num_entries++;
+ }
+
+ ctr0->count = num_entries;
+ *total_entries = num_entries;
+
+ if (resume_handle_p) {
+ if (*resume_handle_p >= *total_entries) {
+ *resume_handle_p = 0;
+ } else {
+ *resume_handle_p = resume_handle;
+ }
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ fill in a conn info level 1 structure.
+ ********************************************************************/
+
+static WERROR init_srv_conn_info_1(const char *name,
+ struct srvsvc_NetConnCtr1 *ctr1,
+ uint32_t *resume_handle_p,
+ uint32_t *total_entries)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t num_entries = 0;
+ int snum = 0;
+ uint32_t resume_handle = resume_handle_p ? *resume_handle_p : 0;
+ char *share_name = NULL;
+ struct server_id *svrid_arr = NULL;
+
+ DEBUG(5,("init_srv_conn_info_1\n"));
+
+ if (ctr1 == NULL) {
+ if (resume_handle_p) {
+ *resume_handle_p = 0;
+ }
+ return WERR_OK;
+ }
+
+ /* check if this is a server name or a share name */
+ if (name && (strlen(name) > 2) && (name[0] == '\\') &&
+ (name[1] == '\\')) {
+
+ /* 'name' is a server name - this part is unimplemented */
+ *total_entries = 1;
+ } else {
+ /* 'name' is a share name */
+ snum = find_service(talloc_tos(), name, &share_name);
+
+ if (!share_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (snum < 0) {
+ return WERR_INVALID_NAME;
+ }
+
+ /*
+ * count the num of connections to this share. Also,
+ * build a list of serverid's that own these
+ * connections. The serverid list is used later to
+ * identify the share connection on which an open exists.
+ */
+
+ *total_entries = count_share_conns(talloc_tos(),
+ share_name,
+ &svrid_arr);
+ }
+
+ if (resume_handle >= *total_entries) {
+ if (resume_handle_p) {
+ *resume_handle_p = 0;
+ }
+ return WERR_OK;
+ }
+
+ /*
+ * We know num_entries must be positive, due to
+ * the check resume_handle >= *total_entries above.
+ */
+
+ num_entries = *total_entries - resume_handle;
+
+ ZERO_STRUCTP(ctr1);
+
+ ctr1->array = talloc_zero_array(talloc_tos(),
+ struct srvsvc_NetConnInfo1,
+ num_entries);
+
+ W_ERROR_HAVE_NO_MEMORY(ctr1->array);
+
+ for (num_entries = 0; resume_handle < *total_entries;
+ num_entries++, resume_handle++) {
+
+ ctr1->array[num_entries].conn_id = *total_entries;
+ ctr1->array[num_entries].conn_type = 0x3;
+
+ /*
+ * if these are connections to a share, we are going to
+ * compute the opens on them later. If it's for the server,
+ * it's unimplemented.
+ */
+
+ if (!share_name) {
+ ctr1->array[num_entries].num_open = 1;
+ }
+
+ ctr1->array[num_entries].num_users = 1;
+ ctr1->array[num_entries].conn_time = 3;
+ ctr1->array[num_entries].user = "dummy_user";
+ ctr1->array[num_entries].share = "IPC$";
+ }
+
+ /* now compute open files on the share connections */
+
+ if (share_name) {
+
+ /*
+ * the locking tdb, which has the open files information,
+ * does not store share name or share (service) number, but
+ * just the share path. So, we can compute open files only
+ * on the share path. If more than one shares are defined
+ * on a share path, open files on all of them are included
+ * in the count.
+ *
+ * To have the correct behavior in case multiple shares
+ * are defined on the same path, changes to tdb records
+ * would be required. That would be lot more effort, so
+ * this seems a good stopgap fix.
+ */
+
+ count_share_opens(ctr1->array, svrid_arr,
+ lp_path(talloc_tos(), lp_sub, snum),
+ num_entries, *total_entries);
+
+ }
+
+ ctr1->count = num_entries;
+ *total_entries = num_entries;
+
+ if (resume_handle_p) {
+ *resume_handle_p = resume_handle;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _srvsvc_NetFileEnum
+*******************************************************************/
+
+WERROR _srvsvc_NetFileEnum(struct pipes_struct *p,
+ struct srvsvc_NetFileEnum *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ TALLOC_CTX *ctx = NULL;
+ struct srvsvc_NetFileCtr3 *ctr3;
+ uint32_t resume_hnd = 0;
+ WERROR werr;
+
+ switch (r->in.info_ctr->level) {
+ case 3:
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ if (!nt_token_check_sid(&global_sid_Builtin_Administrators,
+ session_info->security_token)) {
+ DEBUG(1, ("Enumerating files only allowed for "
+ "administrators\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ ctx = talloc_tos();
+ ctr3 = r->in.info_ctr->ctr.ctr3;
+ if (!ctr3) {
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* TODO -- Windows enumerates
+ (b) active pipes
+ (c) open directories and files */
+
+ werr = net_enum_files(ctx, r->in.user, &ctr3, resume_hnd);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ *r->out.totalentries = ctr3->count;
+ r->out.info_ctr->ctr.ctr3->array = ctr3->array;
+ r->out.info_ctr->ctr.ctr3->count = ctr3->count;
+
+ werr = WERR_OK;
+
+ done:
+ return werr;
+}
+
+/*******************************************************************
+ _srvsvc_NetSrvGetInfo
+********************************************************************/
+
+WERROR _srvsvc_NetSrvGetInfo(struct pipes_struct *p,
+ struct srvsvc_NetSrvGetInfo *r)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ WERROR status = WERR_OK;
+
+ DEBUG(5,("_srvsvc_NetSrvGetInfo: %d\n", __LINE__));
+
+ if (!pipe_access_check(p)) {
+ DEBUG(3, ("access denied to _srvsvc_NetSrvGetInfo\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ switch (r->in.level) {
+
+ /* Technically level 102 should only be available to
+ Administrators but there isn't anything super-secret
+ here, as most of it is made up. */
+
+ case 102: {
+ struct srvsvc_NetSrvInfo102 *info102;
+
+ info102 = talloc(p->mem_ctx, struct srvsvc_NetSrvInfo102);
+ if (!info102) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ info102->platform_id = PLATFORM_ID_NT;
+ info102->server_name = lp_netbios_name();
+ info102->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION;
+ info102->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION;
+ info102->server_type = lp_default_server_announce();
+ info102->comment =
+ string_truncate(lp_server_string(info102, lp_sub),
+ MAX_SERVER_STRING_LENGTH);
+ info102->users = 0xffffffff;
+ info102->disc = 0xf;
+ info102->hidden = 0;
+ info102->announce = 240;
+ info102->anndelta = 3000;
+ info102->licenses = 100000;
+ info102->userpath = "C:\\";
+
+ r->out.info->info102 = info102;
+ break;
+ }
+ case 101: {
+ struct srvsvc_NetSrvInfo101 *info101;
+
+ info101 = talloc(p->mem_ctx, struct srvsvc_NetSrvInfo101);
+ if (!info101) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ info101->platform_id = PLATFORM_ID_NT;
+ info101->server_name = lp_netbios_name();
+ info101->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION;
+ info101->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION;
+ info101->server_type = lp_default_server_announce();
+ info101->comment =
+ string_truncate(lp_server_string(info101, lp_sub),
+ MAX_SERVER_STRING_LENGTH);
+
+ r->out.info->info101 = info101;
+ break;
+ }
+ case 100: {
+ struct srvsvc_NetSrvInfo100 *info100;
+
+ info100 = talloc(p->mem_ctx, struct srvsvc_NetSrvInfo100);
+ if (!info100) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ info100->platform_id = PLATFORM_ID_NT;
+ info100->server_name = lp_netbios_name();
+
+ r->out.info->info100 = info100;
+
+ break;
+ }
+ default:
+ status = WERR_INVALID_LEVEL;
+ break;
+ }
+
+ DEBUG(5,("_srvsvc_NetSrvGetInfo: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+ _srvsvc_NetSrvSetInfo
+********************************************************************/
+
+WERROR _srvsvc_NetSrvSetInfo(struct pipes_struct *p,
+ struct srvsvc_NetSrvSetInfo *r)
+{
+ WERROR status = WERR_OK;
+
+ DEBUG(5,("_srvsvc_NetSrvSetInfo: %d\n", __LINE__));
+
+ /* Set up the net server set info structure. */
+
+ DEBUG(5,("_srvsvc_NetSrvSetInfo: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+ _srvsvc_NetConnEnum
+********************************************************************/
+
+WERROR _srvsvc_NetConnEnum(struct pipes_struct *p,
+ struct srvsvc_NetConnEnum *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ WERROR werr;
+
+ DEBUG(5,("_srvsvc_NetConnEnum: %d\n", __LINE__));
+
+ if (!nt_token_check_sid(&global_sid_Builtin_Administrators,
+ session_info->security_token)) {
+ DEBUG(1, ("Enumerating connections only allowed for "
+ "administrators\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ switch (r->in.info_ctr->level) {
+ case 0:
+ werr = init_srv_conn_info_0(r->in.info_ctr->ctr.ctr0,
+ r->in.resume_handle,
+ r->out.totalentries);
+ break;
+ case 1:
+ werr = init_srv_conn_info_1(r->in.path,
+ r->in.info_ctr->ctr.ctr1,
+ r->in.resume_handle,
+ r->out.totalentries);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ DEBUG(5,("_srvsvc_NetConnEnum: %d\n", __LINE__));
+
+ return werr;
+}
+
+/*******************************************************************
+ _srvsvc_NetSessEnum
+********************************************************************/
+
+WERROR _srvsvc_NetSessEnum(struct pipes_struct *p,
+ struct srvsvc_NetSessEnum *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ WERROR werr;
+
+ DEBUG(5,("_srvsvc_NetSessEnum: %d\n", __LINE__));
+
+ if (!nt_token_check_sid(&global_sid_Builtin_Administrators,
+ session_info->security_token)) {
+ DEBUG(1, ("Enumerating sessions only allowed for "
+ "administrators\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ switch (r->in.info_ctr->level) {
+ case 0:
+ werr = init_srv_sess_info_0(p,
+ r->in.info_ctr->ctr.ctr0,
+ r->in.resume_handle,
+ r->out.totalentries);
+ break;
+ case 1:
+ werr = init_srv_sess_info_1(p,
+ r->in.info_ctr->ctr.ctr1,
+ r->in.resume_handle,
+ r->out.totalentries);
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ DEBUG(5,("_srvsvc_NetSessEnum: %d\n", __LINE__));
+
+ return werr;
+}
+
+/*******************************************************************
+ _srvsvc_NetSessDel
+********************************************************************/
+
+WERROR _srvsvc_NetSessDel(struct pipes_struct *p,
+ struct srvsvc_NetSessDel *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct sessionid *session_list;
+ int num_sessions, snum;
+ const char *username;
+ const char *machine;
+ bool not_root = False;
+ WERROR werr;
+
+ DEBUG(5,("_srvsvc_NetSessDel: %d\n", __LINE__));
+
+ werr = WERR_ACCESS_DENIED;
+
+ /* fail out now if you are not root or not a domain admin */
+
+ if ((session_info->unix_token->uid != sec_initial_uid()) &&
+ ( ! nt_token_check_domain_rid(session_info->security_token,
+ DOMAIN_RID_ADMINS))) {
+
+ goto done;
+ }
+
+ username = r->in.user;
+ machine = r->in.client;
+
+ /* strip leading backslashes if any */
+ if (machine && machine[0] == '\\' && machine[1] == '\\') {
+ machine += 2;
+ }
+
+ num_sessions = find_sessions(p->mem_ctx, username, machine,
+ &session_list);
+
+ for (snum = 0; snum < num_sessions; snum++) {
+
+ NTSTATUS ntstat;
+
+ if (session_info->unix_token->uid != sec_initial_uid()) {
+ not_root = True;
+ become_root();
+ }
+
+ ntstat = messaging_send(p->msg_ctx,
+ session_list[snum].pid,
+ MSG_SHUTDOWN, &data_blob_null);
+
+ if (NT_STATUS_IS_OK(ntstat))
+ werr = WERR_OK;
+
+ if (not_root)
+ unbecome_root();
+ }
+
+ DEBUG(5,("_srvsvc_NetSessDel: %d\n", __LINE__));
+
+done:
+
+ return werr;
+}
+
+/*******************************************************************
+ _srvsvc_NetShareEnumAll
+********************************************************************/
+
+WERROR _srvsvc_NetShareEnumAll(struct pipes_struct *p,
+ struct srvsvc_NetShareEnumAll *r)
+{
+ WERROR werr;
+
+ DEBUG(5,("_srvsvc_NetShareEnumAll: %d\n", __LINE__));
+
+ if (!pipe_access_check(p)) {
+ DEBUG(3, ("access denied to _srvsvc_NetShareEnumAll\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Create the list of shares for the response. */
+ werr = init_srv_share_info_ctr(p,
+ r->in.info_ctr,
+ r->in.resume_handle,
+ r->out.totalentries,
+ true);
+
+ DEBUG(5,("_srvsvc_NetShareEnumAll: %d\n", __LINE__));
+
+ return werr;
+}
+
+/*******************************************************************
+ _srvsvc_NetShareEnum
+********************************************************************/
+
+WERROR _srvsvc_NetShareEnum(struct pipes_struct *p,
+ struct srvsvc_NetShareEnum *r)
+{
+ WERROR werr;
+
+ DEBUG(5,("_srvsvc_NetShareEnum: %d\n", __LINE__));
+
+ if (!pipe_access_check(p)) {
+ DEBUG(3, ("access denied to _srvsvc_NetShareEnum\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Create the list of shares for the response. */
+ werr = init_srv_share_info_ctr(p,
+ r->in.info_ctr,
+ r->in.resume_handle,
+ r->out.totalentries,
+ false);
+
+ DEBUG(5,("_srvsvc_NetShareEnum: %d\n", __LINE__));
+
+ return werr;
+}
+
+/*******************************************************************
+ _srvsvc_NetShareGetInfo
+********************************************************************/
+
+WERROR _srvsvc_NetShareGetInfo(struct pipes_struct *p,
+ struct srvsvc_NetShareGetInfo *r)
+{
+ WERROR status = WERR_OK;
+ char *share_name = NULL;
+ int snum;
+ union srvsvc_NetShareInfo *info = r->out.info;
+
+ DEBUG(5,("_srvsvc_NetShareGetInfo: %d\n", __LINE__));
+
+ if (!r->in.share_name) {
+ return WERR_INVALID_NAME;
+ }
+
+ snum = find_service(talloc_tos(), r->in.share_name, &share_name);
+ if (!share_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (snum < 0) {
+ return WERR_INVALID_NAME;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ info->info0 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo0);
+ W_ERROR_HAVE_NO_MEMORY(info->info0);
+ init_srv_share_info_0(p, info->info0, snum);
+ break;
+ case 1:
+ info->info1 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1);
+ W_ERROR_HAVE_NO_MEMORY(info->info1);
+ init_srv_share_info_1(p, info->info1, snum);
+ break;
+ case 2:
+ info->info2 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo2);
+ W_ERROR_HAVE_NO_MEMORY(info->info2);
+ init_srv_share_info_2(p, info->info2, snum);
+ info->info2->current_users =
+ count_current_connections(info->info2->name, false);
+ break;
+ case 501:
+ info->info501 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo501);
+ W_ERROR_HAVE_NO_MEMORY(info->info501);
+ init_srv_share_info_501(p, info->info501, snum);
+ break;
+ case 502:
+ info->info502 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo502);
+ W_ERROR_HAVE_NO_MEMORY(info->info502);
+ init_srv_share_info_502(p, info->info502, snum);
+ break;
+ case 1004:
+ info->info1004 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1004);
+ W_ERROR_HAVE_NO_MEMORY(info->info1004);
+ init_srv_share_info_1004(p, info->info1004, snum);
+ break;
+ case 1005:
+ info->info1005 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1005);
+ W_ERROR_HAVE_NO_MEMORY(info->info1005);
+ init_srv_share_info_1005(p, info->info1005, snum);
+ break;
+ case 1006:
+ info->info1006 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1006);
+ W_ERROR_HAVE_NO_MEMORY(info->info1006);
+ init_srv_share_info_1006(p, info->info1006, snum);
+ break;
+ case 1007:
+ info->info1007 = talloc(p->mem_ctx, struct srvsvc_NetShareInfo1007);
+ W_ERROR_HAVE_NO_MEMORY(info->info1007);
+ init_srv_share_info_1007(p, info->info1007, snum);
+ break;
+ case 1501:
+ init_srv_share_info_1501(p, &info->info1501, snum);
+ break;
+ default:
+ DEBUG(5,("_srvsvc_NetShareGetInfo: unsupported switch value %d\n",
+ r->in.level));
+ status = WERR_INVALID_LEVEL;
+ break;
+ }
+
+ DEBUG(5,("_srvsvc_NetShareGetInfo: %d\n", __LINE__));
+
+ return status;
+}
+
+/*******************************************************************
+ _srvsvc_NetShareSetInfo. Modify share details.
+********************************************************************/
+
+WERROR _srvsvc_NetShareSetInfo(struct pipes_struct *p,
+ struct srvsvc_NetShareSetInfo *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *command = NULL;
+ char *share_name = NULL;
+ char *comment = NULL;
+ const char *pathname = NULL;
+ int type;
+ int snum;
+ int ret;
+ char *path = NULL;
+ struct security_descriptor *psd = NULL;
+ bool is_disk_op = False;
+ const char *csc_policy = NULL;
+ bool csc_policy_changed = false;
+ const char *csc_policies[] = {"manual", "documents", "programs",
+ "disable"};
+ uint32_t client_csc_policy;
+ int max_connections = 0;
+ TALLOC_CTX *ctx = p->mem_ctx;
+ union srvsvc_NetShareInfo *info = r->in.info;
+
+ DEBUG(5,("_srvsvc_NetShareSetInfo: %d\n", __LINE__));
+
+ if (!r->in.share_name) {
+ return WERR_INVALID_NAME;
+ }
+
+ if (r->out.parm_error) {
+ *r->out.parm_error = 0;
+ }
+
+ if ( strequal(r->in.share_name,"IPC$")
+ || ( lp_enable_asu_support() && strequal(r->in.share_name,"ADMIN$") )
+ || strequal(r->in.share_name,"global") )
+ {
+ DEBUG(5,("_srvsvc_NetShareSetInfo: share %s cannot be "
+ "modified by a remote user.\n",
+ r->in.share_name ));
+ return WERR_ACCESS_DENIED;
+ }
+
+ snum = find_service(talloc_tos(), r->in.share_name, &share_name);
+ if (!share_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* Does this share exist ? */
+ if (snum < 0)
+ return WERR_NERR_NETNAMENOTFOUND;
+
+ /* No change to printer shares. */
+ if (lp_printable(snum))
+ return WERR_ACCESS_DENIED;
+
+ is_disk_op = security_token_has_privilege(
+ session_info->security_token, SEC_PRIV_DISK_OPERATOR);
+
+ /* fail out now if you are not root and not a disk op */
+
+ if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) {
+ DEBUG(2,("_srvsvc_NetShareSetInfo: uid %u doesn't have the "
+ "SeDiskOperatorPrivilege privilege needed to modify "
+ "share %s\n",
+ (unsigned int)session_info->unix_token->uid,
+ share_name ));
+ return WERR_ACCESS_DENIED;
+ }
+
+ max_connections = lp_max_connections(snum);
+ csc_policy = csc_policies[lp_csc_policy(snum)];
+
+ switch (r->in.level) {
+ case 1:
+ pathname = lp_path(ctx, lp_sub, snum);
+ comment = talloc_strdup(ctx, info->info1->comment);
+ type = info->info1->type;
+ psd = NULL;
+ break;
+ case 2:
+ comment = talloc_strdup(ctx, info->info2->comment);
+ pathname = info->info2->path;
+ type = info->info2->type;
+ max_connections = (info->info2->max_users == (uint32_t)-1) ?
+ 0 : info->info2->max_users;
+ psd = NULL;
+ break;
+#if 0
+ /* not supported on set but here for completeness */
+ case 501:
+ comment = talloc_strdup(ctx, info->info501->comment);
+ type = info->info501->type;
+ psd = NULL;
+ break;
+#endif
+ case 502:
+ comment = talloc_strdup(ctx, info->info502->comment);
+ pathname = info->info502->path;
+ type = info->info502->type;
+ psd = info->info502->sd_buf.sd;
+ map_generic_share_sd_bits(psd);
+ break;
+ case 1004:
+ pathname = lp_path(ctx, lp_sub, snum);
+ comment = talloc_strdup(ctx, info->info1004->comment);
+ type = STYPE_DISKTREE;
+ break;
+ case 1005:
+ /* XP re-sets the csc policy even if it wasn't changed by the
+ user, so we must compare it to see if it's what is set in
+ smb.conf, so that we can continue other ops like setting
+ ACLs on a share */
+ client_csc_policy = (info->info1005->dfs_flags &
+ SHARE_1005_CSC_POLICY_MASK) >>
+ SHARE_1005_CSC_POLICY_SHIFT;
+
+ if (client_csc_policy == (uint32_t)lp_csc_policy(snum)) {
+ return WERR_OK;
+ }
+
+ csc_policy = csc_policies[client_csc_policy];
+ csc_policy_changed = true;
+
+ pathname = lp_path(ctx, lp_sub, snum);
+ comment = lp_comment(ctx, lp_sub, snum);
+ type = STYPE_DISKTREE;
+ break;
+ case 1006:
+ case 1007:
+ return WERR_ACCESS_DENIED;
+ case 1501:
+ pathname = lp_path(ctx, lp_sub, snum);
+ comment = lp_comment(ctx, lp_sub, snum);
+ psd = info->info1501->sd;
+ map_generic_share_sd_bits(psd);
+ type = STYPE_DISKTREE;
+ break;
+ default:
+ DEBUG(5,("_srvsvc_NetShareSetInfo: unsupported switch value %d\n",
+ r->in.level));
+ return WERR_INVALID_LEVEL;
+ }
+
+ /* We can only modify disk shares. */
+ if (type != STYPE_DISKTREE) {
+ DEBUG(5,("_srvsvc_NetShareSetInfo: share %s is not a "
+ "disk share\n",
+ share_name ));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (comment == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* Check if the pathname is valid. */
+ if (!(path = valid_share_pathname(p->mem_ctx, pathname ))) {
+ DEBUG(5,("_srvsvc_NetShareSetInfo: invalid pathname %s\n",
+ pathname ));
+ return WERR_BAD_PATHNAME;
+ }
+
+ /* Ensure share name, pathname and comment don't contain '"' characters. */
+ string_replace(share_name, '"', ' ');
+ string_replace(path, '"', ' ');
+ string_replace(comment, '"', ' ');
+
+ DEBUG(10,("_srvsvc_NetShareSetInfo: change share command = %s\n",
+ lp_change_share_command(talloc_tos(), lp_sub) ? lp_change_share_command(talloc_tos(), lp_sub) : "NULL" ));
+
+ /* Only call modify function if something changed. */
+
+ if (strcmp(path, lp_path(talloc_tos(), lp_sub, snum))
+ || strcmp(comment, lp_comment(talloc_tos(), lp_sub, snum))
+ || (lp_max_connections(snum) != max_connections)
+ || csc_policy_changed) {
+
+ if (!lp_change_share_command(talloc_tos(), lp_sub) || !*lp_change_share_command(talloc_tos(), lp_sub)) {
+ DEBUG(10,("_srvsvc_NetShareSetInfo: No change share command\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ command = talloc_asprintf(p->mem_ctx,
+ "%s \"%s\" \"%s\" \"%s\" \"%s\" %d \"%s\"",
+ lp_change_share_command(talloc_tos(), lp_sub),
+ get_dyn_CONFIGFILE(),
+ share_name,
+ path,
+ comment,
+ max_connections,
+ csc_policy);
+ if (!command) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ DEBUG(10,("_srvsvc_NetShareSetInfo: Running [%s]\n", command ));
+
+ /********* BEGIN SeDiskOperatorPrivilege BLOCK *********/
+
+ if (is_disk_op)
+ become_root();
+
+ ret = smbrun(command, NULL, NULL);
+ if (ret == 0) {
+ reload_services(NULL, NULL, false);
+
+ /* Tell everyone we updated smb.conf. */
+ messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED,
+ NULL, 0);
+ }
+
+ if ( is_disk_op )
+ unbecome_root();
+
+ /********* END SeDiskOperatorPrivilege BLOCK *********/
+
+ DEBUG(3,("_srvsvc_NetShareSetInfo: Running [%s] returned (%d)\n",
+ command, ret ));
+
+ TALLOC_FREE(command);
+
+ if ( ret != 0 )
+ return WERR_ACCESS_DENIED;
+ } else {
+ DEBUG(10,("_srvsvc_NetShareSetInfo: No change to share name (%s)\n",
+ share_name ));
+ }
+
+ /* Replace SD if changed. */
+ if (psd) {
+ struct security_descriptor *old_sd;
+ size_t sd_size;
+ NTSTATUS status;
+
+ old_sd = get_share_security(p->mem_ctx, lp_servicename(talloc_tos(), lp_sub, snum), &sd_size);
+
+ if (old_sd && !security_descriptor_equal(old_sd, psd)) {
+ status = set_share_security(share_name, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("_srvsvc_NetShareSetInfo: Failed to change security info in share %s.\n",
+ share_name ));
+ }
+ }
+ }
+
+ DEBUG(5,("_srvsvc_NetShareSetInfo: %d\n", __LINE__));
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _srvsvc_NetShareAdd.
+ Call 'add_share_command "sharename" "pathname"
+ "comment" "max connections = "
+********************************************************************/
+
+WERROR _srvsvc_NetShareAdd(struct pipes_struct *p,
+ struct srvsvc_NetShareAdd *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ char *command = NULL;
+ char *share_name_in = NULL;
+ char *share_name = NULL;
+ char *comment = NULL;
+ char *pathname = NULL;
+ int type;
+ int snum;
+ int ret;
+ char *path;
+ struct security_descriptor *psd = NULL;
+ bool is_disk_op;
+ int max_connections = 0;
+ SMB_STRUCT_STAT st;
+ TALLOC_CTX *ctx = p->mem_ctx;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ DEBUG(5,("_srvsvc_NetShareAdd: %d\n", __LINE__));
+
+ if (r->out.parm_error) {
+ *r->out.parm_error = 0;
+ }
+
+ is_disk_op = security_token_has_privilege(
+ session_info->security_token, SEC_PRIV_DISK_OPERATOR);
+
+ if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!lp_add_share_command(talloc_tos(), lp_sub) || !*lp_add_share_command(talloc_tos(), lp_sub)) {
+ DBG_WARNING("_srvsvc_NetShareAdd: No \"add share command\" parameter set in smb.conf.\n");
+ return WERR_ACCESS_DENIED;
+ }
+
+ switch (r->in.level) {
+ case 0:
+ /* No path. Not enough info in a level 0 to do anything. */
+ return WERR_ACCESS_DENIED;
+ case 1:
+ /* Not enough info in a level 1 to do anything. */
+ return WERR_ACCESS_DENIED;
+ case 2:
+ share_name_in = talloc_strdup(ctx, r->in.info->info2->name);
+ comment = talloc_strdup(ctx, r->in.info->info2->comment);
+ pathname = talloc_strdup(ctx, r->in.info->info2->path);
+ max_connections = (r->in.info->info2->max_users == (uint32_t)-1) ?
+ 0 : r->in.info->info2->max_users;
+ type = r->in.info->info2->type;
+ break;
+ case 501:
+ /* No path. Not enough info in a level 501 to do anything. */
+ return WERR_ACCESS_DENIED;
+ case 502:
+ share_name_in = talloc_strdup(ctx, r->in.info->info502->name);
+ comment = talloc_strdup(ctx, r->in.info->info502->comment);
+ pathname = talloc_strdup(ctx, r->in.info->info502->path);
+ max_connections = (r->in.info->info502->max_users == (uint32_t)-1) ?
+ 0 : r->in.info->info502->max_users;
+ type = r->in.info->info502->type;
+ psd = r->in.info->info502->sd_buf.sd;
+ map_generic_share_sd_bits(psd);
+ break;
+
+ /* none of the following contain share names. NetShareAdd does not have a separate parameter for the share name */
+
+ case 1004:
+ case 1005:
+ case 1006:
+ case 1007:
+ return WERR_ACCESS_DENIED;
+ case 1501:
+ /* DFS only level. */
+ return WERR_ACCESS_DENIED;
+ default:
+ DEBUG(5,("_srvsvc_NetShareAdd: unsupported switch value %d\n",
+ r->in.level));
+ return WERR_INVALID_LEVEL;
+ }
+
+ /* check for invalid share names */
+
+ if (!share_name_in || !validate_net_name(share_name_in,
+ INVALID_SHARENAME_CHARS,
+ strlen(share_name_in))) {
+ DEBUG(5,("_srvsvc_NetShareAdd: Bad sharename \"%s\"\n",
+ share_name_in ? share_name_in : ""));
+ return WERR_INVALID_NAME;
+ }
+
+ if (strequal(share_name_in,"IPC$") || strequal(share_name_in,"global")
+ || (lp_enable_asu_support() &&
+ strequal(share_name_in,"ADMIN$"))) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ snum = find_service(ctx, share_name_in, &share_name);
+ if (!share_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* Share already exists. */
+ if (snum >= 0) {
+ return WERR_FILE_EXISTS;
+ }
+
+ /* We can only add disk shares. */
+ if (type != STYPE_DISKTREE) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* Check if the pathname is valid. */
+ if (!(path = valid_share_pathname(p->mem_ctx, pathname))) {
+ return WERR_BAD_PATHNAME;
+ }
+
+ ret = sys_lstat(path, &st, false);
+ if (ret == -1 && (errno != EACCES)) {
+ /*
+ * If path has any other than permission
+ * problem, return WERR_FILE_NOT_FOUND (as Windows
+ * does.
+ */
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ /* Ensure share name, pathname and comment don't contain '"' characters. */
+ string_replace(share_name_in, '"', ' ');
+ string_replace(share_name, '"', ' ');
+ string_replace(path, '"', ' ');
+ if (comment) {
+ string_replace(comment, '"', ' ');
+ }
+
+ command = talloc_asprintf(ctx,
+ "%s \"%s\" \"%s\" \"%s\" \"%s\" %d",
+ lp_add_share_command(talloc_tos(), lp_sub),
+ get_dyn_CONFIGFILE(),
+ share_name_in,
+ path,
+ comment ? comment : "",
+ max_connections);
+ if (!command) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ DEBUG(10,("_srvsvc_NetShareAdd: Running [%s]\n", command ));
+
+ /********* BEGIN SeDiskOperatorPrivilege BLOCK *********/
+
+ if ( is_disk_op )
+ become_root();
+
+ /* FIXME: use libnetconf here - gd */
+
+ ret = smbrun(command, NULL, NULL);
+ if (ret == 0) {
+ /* Tell everyone we updated smb.conf. */
+ messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0);
+ }
+
+ if ( is_disk_op )
+ unbecome_root();
+
+ /********* END SeDiskOperatorPrivilege BLOCK *********/
+
+ DEBUG(3,("_srvsvc_NetShareAdd: Running [%s] returned (%d)\n",
+ command, ret ));
+
+ TALLOC_FREE(command);
+
+ if ( ret != 0 )
+ return WERR_ACCESS_DENIED;
+
+ if (psd) {
+ NTSTATUS status;
+ /* Note we use share_name here, not share_name_in as
+ we need a canonicalized name for setting security. */
+ status = set_share_security(share_name, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("_srvsvc_NetShareAdd: Failed to add security info to share %s.\n",
+ share_name ));
+ }
+ }
+
+ /*
+ * We don't call reload_services() here, the message will
+ * cause this to be done before the next packet is read
+ * from the client. JRA.
+ */
+
+ DEBUG(5,("_srvsvc_NetShareAdd: %d\n", __LINE__));
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _srvsvc_NetShareDel
+ Call "delete share command" with the share name as
+ a parameter.
+********************************************************************/
+
+WERROR _srvsvc_NetShareDel(struct pipes_struct *p,
+ struct srvsvc_NetShareDel *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ char *command = NULL;
+ char *share_name = NULL;
+ int ret;
+ int snum;
+ bool is_disk_op;
+ TALLOC_CTX *ctx = p->mem_ctx;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ DEBUG(5,("_srvsvc_NetShareDel: %d\n", __LINE__));
+
+ if (!r->in.share_name) {
+ return WERR_NERR_NETNAMENOTFOUND;
+ }
+
+ if ( strequal(r->in.share_name,"IPC$")
+ || ( lp_enable_asu_support() && strequal(r->in.share_name,"ADMIN$") )
+ || strequal(r->in.share_name,"global") )
+ {
+ return WERR_ACCESS_DENIED;
+ }
+
+ snum = find_service(talloc_tos(), r->in.share_name, &share_name);
+ if (!share_name) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (snum < 0) {
+ return WERR_BAD_NET_NAME;
+ }
+
+ /* No change to printer shares. */
+ if (lp_printable(snum))
+ return WERR_ACCESS_DENIED;
+
+ is_disk_op = security_token_has_privilege(
+ session_info->security_token, SEC_PRIV_DISK_OPERATOR);
+
+ if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ if (!lp_delete_share_command(talloc_tos(), lp_sub) || !*lp_delete_share_command(talloc_tos(), lp_sub)) {
+ DBG_WARNING("_srvsvc_NetShareDel: No \"delete share command\" parameter set in smb.conf.\n");
+ return WERR_ACCESS_DENIED;
+ }
+
+ command = talloc_asprintf(ctx,
+ "%s \"%s\" \"%s\"",
+ lp_delete_share_command(talloc_tos(), lp_sub),
+ get_dyn_CONFIGFILE(),
+ share_name);
+ if (!command) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ DEBUG(10,("_srvsvc_NetShareDel: Running [%s]\n", command ));
+
+ /********* BEGIN SeDiskOperatorPrivilege BLOCK *********/
+
+ if ( is_disk_op )
+ become_root();
+
+ ret = smbrun(command, NULL, NULL);
+ if (ret == 0) {
+ /* Tell everyone we updated smb.conf. */
+ messaging_send_all(p->msg_ctx, MSG_SMB_CONF_UPDATED, NULL, 0);
+ }
+
+ if ( is_disk_op )
+ unbecome_root();
+
+ /********* END SeDiskOperatorPrivilege BLOCK *********/
+
+ DEBUG(3,("_srvsvc_NetShareDel: Running [%s] returned (%d)\n", command, ret ));
+
+ if ( ret != 0 )
+ return WERR_ACCESS_DENIED;
+
+ /* Delete the SD in the database. */
+ delete_share_security(share_name);
+
+ lp_killservice(snum);
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _srvsvc_NetShareDelSticky
+********************************************************************/
+
+WERROR _srvsvc_NetShareDelSticky(struct pipes_struct *p,
+ struct srvsvc_NetShareDelSticky *r)
+{
+ struct srvsvc_NetShareDel q;
+
+ DEBUG(5,("_srvsvc_NetShareDelSticky: %d\n", __LINE__));
+
+ q.in.server_unc = r->in.server_unc;
+ q.in.share_name = r->in.share_name;
+ q.in.reserved = r->in.reserved;
+
+ return _srvsvc_NetShareDel(p, &q);
+}
+
+/*******************************************************************
+ _srvsvc_NetRemoteTOD
+********************************************************************/
+
+WERROR _srvsvc_NetRemoteTOD(struct pipes_struct *p,
+ struct srvsvc_NetRemoteTOD *r)
+{
+ struct srvsvc_NetRemoteTODInfo *tod;
+ struct tm *t;
+ time_t unixdate = time(NULL);
+
+ /* We do this call first as if we do it *after* the gmtime call
+ it overwrites the pointed-to values. JRA */
+
+ uint32_t zone = get_time_zone(unixdate)/60;
+
+ DEBUG(5,("_srvsvc_NetRemoteTOD: %d\n", __LINE__));
+
+ if ( !(tod = talloc_zero(p->mem_ctx, struct srvsvc_NetRemoteTODInfo)) )
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ *r->out.info = tod;
+
+ DEBUG(5,("_srvsvc_NetRemoteTOD: %d\n", __LINE__));
+
+ t = gmtime(&unixdate);
+
+ /* set up the */
+ tod->elapsed = unixdate;
+ tod->msecs = 0;
+ tod->hours = t->tm_hour;
+ tod->mins = t->tm_min;
+ tod->secs = t->tm_sec;
+ tod->hunds = 0;
+ tod->timezone = zone;
+ tod->tinterval = 10000;
+ tod->day = t->tm_mday;
+ tod->month = t->tm_mon + 1;
+ tod->year = 1900+t->tm_year;
+ tod->weekday = t->tm_wday;
+
+ DEBUG(5,("_srvsvc_NetRemoteTOD: %d\n", __LINE__));
+
+ return WERR_OK;
+}
+
+/***********************************************************************************
+ _srvsvc_NetGetFileSecurity
+ Win9x NT tools get security descriptor.
+***********************************************************************************/
+
+WERROR _srvsvc_NetGetFileSecurity(struct pipes_struct *p,
+ struct srvsvc_NetGetFileSecurity *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smb_filename *smb_fname = NULL;
+ size_t sd_size;
+ char *servicename = NULL;
+ SMB_STRUCT_STAT st;
+ NTSTATUS nt_status;
+ WERROR werr;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ struct sec_desc_buf *sd_buf = NULL;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ int snum;
+ uint32_t ucf_flags = 0;
+ NTTIME twrp = 0;
+
+ ZERO_STRUCT(st);
+
+ if (!r->in.share) {
+ werr = WERR_NERR_NETNAMENOTFOUND;
+ goto error_exit;
+ }
+ snum = find_service(frame, r->in.share, &servicename);
+ if (!servicename) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto error_exit;
+ }
+ if (snum == -1) {
+ DEBUG(10, ("Could not find service %s\n", servicename));
+ werr = WERR_NERR_NETNAMENOTFOUND;
+ goto error_exit;
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ lp_path(frame, lp_sub, snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(10, ("create_conn_struct failed: %s\n",
+ nt_errstr(nt_status)));
+ werr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+ conn = c->conn;
+
+ nt_status = filename_convert_dirfsp(frame,
+ conn,
+ r->in.file,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ werr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+
+ nt_status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_READ_ATTRIBUTES, /* access_mask */
+ FILE_SHARE_READ|FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(3,("_srvsvc_NetGetFileSecurity: can't open %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ werr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+
+ sd_buf = talloc_zero(p->mem_ctx, struct sec_desc_buf);
+ if (!sd_buf) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto error_exit;
+ }
+
+ nt_status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ (SECINFO_OWNER
+ |SECINFO_GROUP
+ |SECINFO_DACL), sd_buf, &sd_buf->sd);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(3,("_srvsvc_NetGetFileSecurity: Unable to get NT ACL "
+ "for file %s\n", smb_fname_str_dbg(smb_fname)));
+ werr = ntstatus_to_werror(nt_status);
+ TALLOC_FREE(sd_buf);
+ goto error_exit;
+ }
+
+ if (sd_buf->sd->dacl) {
+ sd_buf->sd->dacl->revision = NT4_ACL_REVISION;
+ }
+
+ sd_size = ndr_size_security_descriptor(sd_buf->sd, 0);
+
+ sd_buf->sd_size = sd_size;
+
+ *r->out.sd_buf = sd_buf;
+
+ werr = WERR_OK;
+
+error_exit:
+
+ if (fsp) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+
+ TALLOC_FREE(frame);
+ return werr;
+}
+
+/***********************************************************************************
+ _srvsvc_NetSetFileSecurity
+ Win9x NT tools set security descriptor.
+***********************************************************************************/
+
+WERROR _srvsvc_NetSetFileSecurity(struct pipes_struct *p,
+ struct srvsvc_NetSetFileSecurity *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smb_filename *smb_fname = NULL;
+ char *servicename = NULL;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ SMB_STRUCT_STAT st;
+ NTSTATUS nt_status;
+ WERROR werr;
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ int snum;
+ struct security_descriptor *psd = NULL;
+ uint32_t security_info_sent = 0;
+ uint32_t ucf_flags = 0;
+ NTTIME twrp = 0;
+
+ ZERO_STRUCT(st);
+
+ if (!r->in.share) {
+ werr = WERR_NERR_NETNAMENOTFOUND;
+ goto error_exit;
+ }
+
+ snum = find_service(frame, r->in.share, &servicename);
+ if (!servicename) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto error_exit;
+ }
+
+ if (snum == -1) {
+ DEBUG(10, ("Could not find service %s\n", servicename));
+ werr = WERR_NERR_NETNAMENOTFOUND;
+ goto error_exit;
+ }
+
+ nt_status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ lp_path(frame, lp_sub, snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(10, ("create_conn_struct failed: %s\n",
+ nt_errstr(nt_status)));
+ werr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+ conn = c->conn;
+
+ nt_status = filename_convert_dirfsp(frame,
+ conn,
+ r->in.file,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ werr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+
+ nt_status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_WRITE_ATTRIBUTES, /* access_mask */
+ FILE_SHARE_READ|FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(3,("_srvsvc_NetSetFileSecurity: can't open %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ werr = ntstatus_to_werror(nt_status);
+ goto error_exit;
+ }
+
+ psd = r->in.sd_buf->sd;
+ security_info_sent = r->in.securityinformation;
+
+ nt_status = set_sd(fsp, psd, security_info_sent);
+
+ if (!NT_STATUS_IS_OK(nt_status) ) {
+ DEBUG(3,("_srvsvc_NetSetFileSecurity: Unable to set NT ACL "
+ "on file %s\n", r->in.share));
+ werr = WERR_ACCESS_DENIED;
+ goto error_exit;
+ }
+
+ werr = WERR_OK;
+
+error_exit:
+
+ if (fsp) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+
+ TALLOC_FREE(frame);
+ return werr;
+}
+
+/***********************************************************************************
+ It may be that we want to limit users to creating shares on certain areas of the UNIX file area.
+ We could define areas by mapping Windows style disks to points on the UNIX directory hierarchy.
+ These disks would the disks listed by this function.
+ Users could then create shares relative to these disks. Watch out for moving these disks around.
+ "Nigel Williams" <nigel@veritas.com>.
+***********************************************************************************/
+
+static const char *server_disks[] = {"C:"};
+
+static uint32_t get_server_disk_count(void)
+{
+ return sizeof(server_disks)/sizeof(server_disks[0]);
+}
+
+static uint32_t init_server_disk_enum(uint32_t *resume)
+{
+ uint32_t server_disk_count = get_server_disk_count();
+
+ /*resume can be an offset into the list for now*/
+
+ if(*resume & 0x80000000)
+ *resume = 0;
+
+ if(*resume > server_disk_count)
+ *resume = server_disk_count;
+
+ return server_disk_count - *resume;
+}
+
+static const char *next_server_disk_enum(uint32_t *resume)
+{
+ const char *disk;
+
+ if(init_server_disk_enum(resume) == 0)
+ return NULL;
+
+ disk = server_disks[*resume];
+
+ (*resume)++;
+
+ DEBUG(10, ("next_server_disk_enum: reporting disk %s. resume handle %d.\n", disk, *resume));
+
+ return disk;
+}
+
+/********************************************************************
+ _srvsvc_NetDiskEnum
+********************************************************************/
+
+WERROR _srvsvc_NetDiskEnum(struct pipes_struct *p,
+ struct srvsvc_NetDiskEnum *r)
+{
+ uint32_t i;
+ const char *disk_name;
+ TALLOC_CTX *ctx = p->mem_ctx;
+ WERROR werr;
+ uint32_t resume = r->in.resume_handle ? *r->in.resume_handle : 0;
+
+ werr = WERR_OK;
+
+ *r->out.totalentries = init_server_disk_enum(&resume);
+
+ r->out.info->disks = talloc_zero_array(ctx, struct srvsvc_NetDiskInfo0,
+ MAX_SERVER_DISK_ENTRIES);
+ W_ERROR_HAVE_NO_MEMORY(r->out.info->disks);
+
+ /*allow one struct srvsvc_NetDiskInfo0 for null terminator*/
+
+ r->out.info->count = 0;
+
+ for(i = 0; i < MAX_SERVER_DISK_ENTRIES -1 && (disk_name = next_server_disk_enum(&resume)); i++) {
+
+ r->out.info->count++;
+
+ /*copy disk name into a unicode string*/
+
+ r->out.info->disks[i].disk = talloc_strdup(ctx, disk_name);
+ W_ERROR_HAVE_NO_MEMORY(r->out.info->disks[i].disk);
+ }
+
+ /* add a terminating null string. Is this there if there is more data to come? */
+
+ r->out.info->count++;
+
+ r->out.info->disks[i].disk = talloc_strdup(ctx, "");
+ W_ERROR_HAVE_NO_MEMORY(r->out.info->disks[i].disk);
+
+ if (r->out.resume_handle) {
+ *r->out.resume_handle = resume;
+ }
+
+ return werr;
+}
+
+/********************************************************************
+ _srvsvc_NetNameValidate
+********************************************************************/
+
+WERROR _srvsvc_NetNameValidate(struct pipes_struct *p,
+ struct srvsvc_NetNameValidate *r)
+{
+ switch (r->in.name_type) {
+ case 0x9:
+ if (!validate_net_name(r->in.name, INVALID_SHARENAME_CHARS,
+ strlen_m(r->in.name)))
+ {
+ DEBUG(5,("_srvsvc_NetNameValidate: Bad sharename \"%s\"\n",
+ r->in.name));
+ return WERR_INVALID_NAME;
+ }
+ break;
+
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+********************************************************************/
+
+struct enum_file_close_state {
+ struct srvsvc_NetFileClose *r;
+ struct messaging_context *msg_ctx;
+};
+
+static int enum_file_close_fn(struct file_id id,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ void *private_data)
+{
+ char msg[MSG_SMB_SHARE_MODE_ENTRY_SIZE];
+ struct enum_file_close_state *state =
+ (struct enum_file_close_state *)private_data;
+ uint32_t fid = (((uint32_t)(procid_to_pid(&e->pid))<<16) | e->share_file_id);
+
+ if (fid != state->r->in.fid) {
+ return 0; /* Not this file. */
+ }
+
+ if (!process_exists(e->pid) ) {
+ return 0;
+ }
+
+ /* Ok - send the close message. */
+ DBG_DEBUG("request to close file %s, %s\n", d->servicepath,
+ share_mode_str(talloc_tos(), 0, &id, e));
+
+ share_mode_entry_to_message(msg, &id, e);
+
+ state->r->out.result = ntstatus_to_werror(
+ messaging_send_buf(state->msg_ctx,
+ e->pid, MSG_SMB_CLOSE_FILE,
+ (uint8_t *)msg, sizeof(msg)));
+
+ return 0;
+}
+
+/********************************************************************
+ Close a file given a 32-bit file id.
+********************************************************************/
+
+WERROR _srvsvc_NetFileClose(struct pipes_struct *p,
+ struct srvsvc_NetFileClose *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct enum_file_close_state state;
+ bool is_disk_op;
+
+ DEBUG(5,("_srvsvc_NetFileClose: %d\n", __LINE__));
+
+ is_disk_op = security_token_has_privilege(
+ session_info->security_token, SEC_PRIV_DISK_OPERATOR);
+
+ if (session_info->unix_token->uid != sec_initial_uid() && !is_disk_op) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* enum_file_close_fn sends the close message to
+ * the relevant smbd process. */
+
+ r->out.result = WERR_FILE_NOT_FOUND;
+ state.r = r;
+ state.msg_ctx = p->msg_ctx;
+ share_entry_forall(enum_file_close_fn, &state);
+ return r->out.result;
+}
+
+/********************************************************************
+********************************************************************/
+
+WERROR _srvsvc_NetCharDevEnum(struct pipes_struct *p,
+ struct srvsvc_NetCharDevEnum *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetCharDevGetInfo(struct pipes_struct *p,
+ struct srvsvc_NetCharDevGetInfo *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetCharDevControl(struct pipes_struct *p,
+ struct srvsvc_NetCharDevControl *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetCharDevQEnum(struct pipes_struct *p,
+ struct srvsvc_NetCharDevQEnum *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetCharDevQGetInfo(struct pipes_struct *p,
+ struct srvsvc_NetCharDevQGetInfo *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetCharDevQSetInfo(struct pipes_struct *p,
+ struct srvsvc_NetCharDevQSetInfo *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetCharDevQPurge(struct pipes_struct *p,
+ struct srvsvc_NetCharDevQPurge *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetCharDevQPurgeSelf(struct pipes_struct *p,
+ struct srvsvc_NetCharDevQPurgeSelf *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetFileGetInfo(struct pipes_struct *p,
+ struct srvsvc_NetFileGetInfo *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetShareCheck(struct pipes_struct *p,
+ struct srvsvc_NetShareCheck *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetServerStatisticsGet(struct pipes_struct *p,
+ struct srvsvc_NetServerStatisticsGet *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetTransportAdd(struct pipes_struct *p,
+ struct srvsvc_NetTransportAdd *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetTransportEnum(struct pipes_struct *p,
+ struct srvsvc_NetTransportEnum *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetTransportDel(struct pipes_struct *p,
+ struct srvsvc_NetTransportDel *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetSetServiceBits(struct pipes_struct *p,
+ struct srvsvc_NetSetServiceBits *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetPathType(struct pipes_struct *p,
+ struct srvsvc_NetPathType *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetPathCanonicalize(struct pipes_struct *p,
+ struct srvsvc_NetPathCanonicalize *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetPathCompare(struct pipes_struct *p,
+ struct srvsvc_NetPathCompare *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRPRNAMECANONICALIZE(struct pipes_struct *p,
+ struct srvsvc_NETRPRNAMECANONICALIZE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetPRNameCompare(struct pipes_struct *p,
+ struct srvsvc_NetPRNameCompare *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetShareDelStart(struct pipes_struct *p,
+ struct srvsvc_NetShareDelStart *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetShareDelCommit(struct pipes_struct *p,
+ struct srvsvc_NetShareDelCommit *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetServerTransportAddEx(struct pipes_struct *p,
+ struct srvsvc_NetServerTransportAddEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NetServerSetServiceBitsEx(struct pipes_struct *p,
+ struct srvsvc_NetServerSetServiceBitsEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSGETVERSION(struct pipes_struct *p,
+ struct srvsvc_NETRDFSGETVERSION *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSCREATELOCALPARTITION(struct pipes_struct *p,
+ struct srvsvc_NETRDFSCREATELOCALPARTITION *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSDELETELOCALPARTITION(struct pipes_struct *p,
+ struct srvsvc_NETRDFSDELETELOCALPARTITION *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSSETLOCALVOLUMESTATE(struct pipes_struct *p,
+ struct srvsvc_NETRDFSSETLOCALVOLUMESTATE *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSSETSERVERINFO(struct pipes_struct *p,
+ struct srvsvc_NETRDFSSETSERVERINFO *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSCREATEEXITPOINT(struct pipes_struct *p,
+ struct srvsvc_NETRDFSCREATEEXITPOINT *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSDELETEEXITPOINT(struct pipes_struct *p,
+ struct srvsvc_NETRDFSDELETEEXITPOINT *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSMODIFYPREFIX(struct pipes_struct *p,
+ struct srvsvc_NETRDFSMODIFYPREFIX *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSFIXLOCALVOLUME(struct pipes_struct *p,
+ struct srvsvc_NETRDFSFIXLOCALVOLUME *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRDFSMANAGERREPORTSITEINFO(struct pipes_struct *p,
+ struct srvsvc_NETRDFSMANAGERREPORTSITEINFO *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _srvsvc_NETRSERVERTRANSPORTDELEX(struct pipes_struct *p,
+ struct srvsvc_NETRSERVERTRANSPORTDELEX *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_srvsvc_scompat.c"
diff --git a/source3/rpc_server/svcctl/srv_svcctl_nt.c b/source3/rpc_server/svcctl/srv_svcctl_nt.c
new file mode 100644
index 0000000..c1df2f6
--- /dev/null
+++ b/source3/rpc_server/svcctl/srv_svcctl_nt.c
@@ -0,0 +1,1497 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (C) Marcin Krzysztof Porwit 2005.
+ *
+ * Largely Rewritten (Again) by:
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ * Copyright (C) Guenther Deschner 2008,2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "ntdomain.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "services/services.h"
+#include "services/svc_winreg_glue.h"
+#include "auth.h"
+#include "rpc_server/svcctl/srv_svcctl_nt.h"
+
+#include "rpc_server/rpc_server.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_svcctl.h"
+#include "librpc/gen_ndr/ndr_svcctl_scompat.h"
+#include "srv_svcctl_reg.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+struct service_control_op {
+ const char *name;
+ SERVICE_CONTROL_OPS *ops;
+};
+
+/* handle external services */
+extern SERVICE_CONTROL_OPS rcinit_svc_ops;
+
+/* builtin services (see service_db.c and services/svc_*.c */
+extern SERVICE_CONTROL_OPS spoolss_svc_ops;
+extern SERVICE_CONTROL_OPS netlogon_svc_ops;
+extern SERVICE_CONTROL_OPS winreg_svc_ops;
+extern SERVICE_CONTROL_OPS wins_svc_ops;
+
+/* make sure this number patches the number of builtin
+ SERVICE_CONTROL_OPS structure listed above */
+
+#define SVCCTL_NUM_INTERNAL_SERVICES 4
+
+struct service_control_op *svcctl_ops;
+
+static const struct generic_mapping scm_generic_map =
+ { SC_MANAGER_READ_ACCESS, SC_MANAGER_WRITE_ACCESS, SC_MANAGER_EXECUTE_ACCESS, SC_MANAGER_ALL_ACCESS };
+static const struct generic_mapping svc_generic_map =
+ { SERVICE_READ_ACCESS, SERVICE_WRITE_ACCESS, SERVICE_EXECUTE_ACCESS, SERVICE_ALL_ACCESS };
+
+
+/********************************************************************
+********************************************************************/
+
+bool init_service_op_table( void )
+{
+ const char **service_list = lp_svcctl_list();
+ int num_services = SVCCTL_NUM_INTERNAL_SERVICES + str_list_length( service_list );
+ int i;
+
+ if ( !(svcctl_ops = talloc_array( NULL, struct service_control_op, num_services+1)) ) {
+ DEBUG(0,("init_service_op_table: talloc() failed!\n"));
+ return False;
+ }
+
+ /* services listed in smb.conf get the rc.init interface */
+
+ for ( i=0; service_list && service_list[i]; i++ ) {
+ svcctl_ops[i].name = talloc_strdup( svcctl_ops, service_list[i] );
+ svcctl_ops[i].ops = &rcinit_svc_ops;
+ }
+
+ /* add builtin services */
+
+ svcctl_ops[i].name = talloc_strdup( svcctl_ops, "Spooler" );
+ svcctl_ops[i].ops = &spoolss_svc_ops;
+ i++;
+
+ svcctl_ops[i].name = talloc_strdup( svcctl_ops, "NETLOGON" );
+ svcctl_ops[i].ops = &netlogon_svc_ops;
+ i++;
+
+ svcctl_ops[i].name = talloc_strdup( svcctl_ops, "RemoteRegistry" );
+ svcctl_ops[i].ops = &winreg_svc_ops;
+ i++;
+
+ svcctl_ops[i].name = talloc_strdup( svcctl_ops, "WINS" );
+ svcctl_ops[i].ops = &wins_svc_ops;
+ i++;
+
+ /* NULL terminate the array */
+
+ svcctl_ops[i].name = NULL;
+ svcctl_ops[i].ops = NULL;
+
+ return True;
+}
+
+bool shutdown_service_op_table(void)
+{
+ TALLOC_FREE(svcctl_ops);
+
+ return true;
+}
+
+/********************************************************************
+********************************************************************/
+
+static struct service_control_op* find_service_by_name( const char *name )
+{
+ int i;
+
+ for ( i=0; svcctl_ops[i].name; i++ ) {
+ if ( strequal( name, svcctl_ops[i].name ) )
+ return &svcctl_ops[i];
+ }
+
+ return NULL;
+}
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS svcctl_access_check( struct security_descriptor *sec_desc, struct security_token *token,
+ uint32_t access_desired, uint32_t *access_granted )
+{
+ NTSTATUS status;
+ if ( geteuid() == sec_initial_uid() ) {
+ DEBUG(5,("svcctl_access_check: using root's token\n"));
+ status = get_root_nt_token(&token);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ return se_access_check( sec_desc, token, access_desired, access_granted);
+}
+
+/********************************************************************
+********************************************************************/
+
+static struct security_descriptor* construct_scm_sd( TALLOC_CTX *ctx )
+{
+ struct security_ace ace[2];
+ size_t i = 0;
+ struct security_descriptor *sd;
+ struct security_acl *theacl;
+ size_t sd_size;
+
+ /* basic access for Everyone */
+
+ init_sec_ace(&ace[i++], &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SC_MANAGER_READ_ACCESS, 0);
+
+ /* Full Access 'BUILTIN\Administrators' */
+
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SC_MANAGER_ALL_ACCESS, 0);
+
+
+ /* create the security descriptor */
+
+ if ( !(theacl = make_sec_acl(ctx, NT4_ACL_REVISION, i, ace)) )
+ return NULL;
+
+ if ( !(sd = make_sec_desc(ctx, SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE, NULL, NULL, NULL,
+ theacl, &sd_size)) )
+ return NULL;
+
+ return sd;
+}
+
+/******************************************************************
+ Find a registry key handle and return a SERVICE_INFO
+ *****************************************************************/
+
+static SERVICE_INFO *find_service_info_by_hnd(struct pipes_struct *p,
+ struct policy_handle *hnd)
+{
+ SERVICE_INFO *service_info = NULL;
+ NTSTATUS status;
+
+ service_info = find_policy_by_hnd(p,
+ hnd,
+ DCESRV_HANDLE_ANY,
+ SERVICE_INFO,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2,("find_service_info_by_hnd: handle not found\n"));
+ return NULL;
+ }
+
+ return service_info;
+}
+
+/******************************************************************
+ *****************************************************************/
+
+static WERROR create_open_service_handle(struct pipes_struct *p,
+ struct policy_handle *handle,
+ uint32_t type,
+ const char *service,
+ uint32_t access_granted)
+{
+ SERVICE_INFO *info = NULL;
+ WERROR result = WERR_OK;
+ struct service_control_op *s_op;
+
+ if ( !(info = talloc_zero( NULL, SERVICE_INFO )) )
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ /* the Service Manager has a NULL name */
+
+ info->type = SVC_HANDLE_IS_SCM;
+
+ switch ( type ) {
+ case SVC_HANDLE_IS_SCM:
+ info->type = SVC_HANDLE_IS_SCM;
+ break;
+
+ case SVC_HANDLE_IS_DBLOCK:
+ info->type = SVC_HANDLE_IS_DBLOCK;
+ break;
+
+ case SVC_HANDLE_IS_SERVICE:
+ info->type = SVC_HANDLE_IS_SERVICE;
+
+ /* lookup the SERVICE_CONTROL_OPS */
+
+ if ( !(s_op = find_service_by_name( service )) ) {
+ result = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ info->ops = s_op->ops;
+
+ if ( !(info->name = talloc_strdup( info, s_op->name )) ) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ break;
+
+ default:
+ result = WERR_SERVICE_DOES_NOT_EXIST;
+ goto done;
+ }
+
+ info->access_granted = access_granted;
+
+ /* store the SERVICE_INFO and create an open handle */
+
+ if ( !create_policy_hnd( p, handle, 0, info ) ) {
+ result = WERR_ACCESS_DENIED;
+ goto done;
+ }
+
+done:
+ if ( !W_ERROR_IS_OK(result) )
+ TALLOC_FREE(info);
+
+ return result;
+}
+
+/********************************************************************
+ _svcctl_OpenSCManagerW
+********************************************************************/
+
+WERROR _svcctl_OpenSCManagerW(struct pipes_struct *p,
+ struct svcctl_OpenSCManagerW *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *sec_desc;
+ uint32_t access_granted = 0;
+ NTSTATUS status;
+
+ /* perform access checks */
+
+ if ( !(sec_desc = construct_scm_sd( p->mem_ctx )) )
+ return WERR_NOT_ENOUGH_MEMORY;
+
+ se_map_generic( &r->in.access_mask, &scm_generic_map );
+ status = svcctl_access_check( sec_desc, session_info->security_token,
+ r->in.access_mask, &access_granted );
+ if ( !NT_STATUS_IS_OK(status) )
+ return ntstatus_to_werror( status );
+
+ return create_open_service_handle( p, r->out.handle, SVC_HANDLE_IS_SCM, NULL, access_granted );
+}
+
+/********************************************************************
+ _svcctl_OpenServiceW
+********************************************************************/
+
+WERROR _svcctl_OpenServiceW(struct pipes_struct *p,
+ struct svcctl_OpenServiceW *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct security_descriptor *sec_desc;
+ uint32_t access_granted = 0;
+ NTSTATUS status;
+ const char *service = NULL;
+ WERROR err;
+
+ service = r->in.ServiceName;
+ if (!service) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ DEBUG(5, ("_svcctl_OpenServiceW: Attempting to open Service [%s], \n", service));
+
+ /* based on my tests you can open a service if you have a valid scm handle */
+
+ if ( !find_service_info_by_hnd( p, r->in.scmanager_handle) )
+ return WERR_INVALID_HANDLE;
+
+ /*
+ * Perform access checks. Use the system session_info in order to ensure
+ * that we retrieve the security descriptor
+ */
+ err = svcctl_get_secdesc(p->msg_ctx,
+ get_session_info_system(),
+ service,
+ p->mem_ctx,
+ &sec_desc);
+ if (W_ERROR_EQUAL(err, WERR_FILE_NOT_FOUND)) {
+ DBG_NOTICE("service %s does not exist\n", service);
+ return WERR_SERVICE_DOES_NOT_EXIST;
+ }
+ if (!W_ERROR_IS_OK(err)) {
+ DBG_NOTICE("Failed to get a valid secdesc for %s: %s\n",
+ service, win_errstr(err));
+ return err;
+ }
+
+ se_map_generic( &r->in.access_mask, &svc_generic_map );
+ status = svcctl_access_check( sec_desc, session_info->security_token,
+ r->in.access_mask, &access_granted );
+ if ( !NT_STATUS_IS_OK(status) )
+ return ntstatus_to_werror( status );
+
+ return create_open_service_handle( p, r->out.handle, SVC_HANDLE_IS_SERVICE, service, access_granted );
+}
+
+/********************************************************************
+ _svcctl_CloseServiceHandle
+********************************************************************/
+
+WERROR _svcctl_CloseServiceHandle(struct pipes_struct *p,
+ struct svcctl_CloseServiceHandle *r)
+{
+ if ( !close_policy_hnd( p, r->in.handle ) )
+ return WERR_INVALID_HANDLE;
+
+ ZERO_STRUCTP(r->out.handle);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ _svcctl_GetServiceDisplayNameW
+********************************************************************/
+
+WERROR _svcctl_GetServiceDisplayNameW(struct pipes_struct *p,
+ struct svcctl_GetServiceDisplayNameW *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const char *service;
+ const char *display_name;
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+
+ /* can only use an SCM handle here */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SCM) )
+ return WERR_INVALID_HANDLE;
+
+ service = r->in.service_name;
+
+ display_name = svcctl_lookup_dispname(p->mem_ctx,
+ p->msg_ctx,
+ session_info,
+ service);
+ if (!display_name) {
+ display_name = "";
+ }
+
+ *r->out.display_name = display_name;
+ *r->out.display_name_length = strlen(display_name);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ _svcctl_QueryServiceStatus
+********************************************************************/
+
+WERROR _svcctl_QueryServiceStatus(struct pipes_struct *p,
+ struct svcctl_QueryServiceStatus *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_STATUS) )
+ return WERR_ACCESS_DENIED;
+
+ /* try the service specific status call */
+
+ return info->ops->service_status( info->name, r->out.service_status );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int enumerate_status(TALLOC_CTX *ctx,
+ struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ struct ENUM_SERVICE_STATUSW **status)
+{
+ int num_services = 0;
+ int i;
+ struct ENUM_SERVICE_STATUSW *st;
+ const char *display_name;
+
+ /* just count */
+ while ( svcctl_ops[num_services].name )
+ num_services++;
+
+ if ( !(st = talloc_array( ctx, struct ENUM_SERVICE_STATUSW, num_services )) ) {
+ DEBUG(0,("enumerate_status: talloc() failed!\n"));
+ return -1;
+ }
+
+ for ( i=0; i<num_services; i++ ) {
+ st[i].service_name = talloc_strdup(st, svcctl_ops[i].name );
+
+ display_name = svcctl_lookup_dispname(ctx,
+ msg_ctx,
+ session_info,
+ svcctl_ops[i].name);
+ st[i].display_name = talloc_strdup(st, display_name ? display_name : "");
+
+ svcctl_ops[i].ops->service_status( svcctl_ops[i].name, &st[i].status );
+ }
+
+ *status = st;
+
+ return num_services;
+}
+
+/********************************************************************
+ _svcctl_EnumServicesStatusW
+********************************************************************/
+
+WERROR _svcctl_EnumServicesStatusW(struct pipes_struct *p,
+ struct svcctl_EnumServicesStatusW *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct ENUM_SERVICE_STATUSW *services = NULL;
+ int num_services;
+ int i = 0;
+ size_t buffer_size = 0;
+ WERROR result = WERR_OK;
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+ DATA_BLOB blob = data_blob_null;
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SCM) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_MGR_ENUMERATE_SERVICE) ) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ num_services = enumerate_status(p->mem_ctx,
+ p->msg_ctx,
+ session_info,
+ &services);
+ if (num_services == -1 ) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for ( i=0; i<num_services; i++ ) {
+ buffer_size += ndr_size_ENUM_SERVICE_STATUSW(&services[i], 0);
+ }
+
+ buffer_size += buffer_size % 4;
+
+ if (buffer_size > r->in.offered) {
+ num_services = 0;
+ result = WERR_MORE_DATA;
+ }
+
+ if ( W_ERROR_IS_OK(result) ) {
+
+ enum ndr_err_code ndr_err;
+ struct ndr_push *ndr;
+
+ ndr = ndr_push_init_ctx(p->mem_ctx);
+ if (ndr == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ndr_err = ndr_push_ENUM_SERVICE_STATUSW_array(
+ ndr, num_services, services);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ntstatus_to_werror(ndr_map_error2ntstatus(ndr_err));
+ }
+ blob = ndr_push_blob(ndr);
+ memcpy(r->out.service, blob.data, MIN(blob.length, r->in.offered));
+ }
+
+ *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered;
+ *r->out.services_returned = (uint32_t)num_services;
+ if (r->out.resume_handle) {
+ *r->out.resume_handle = 0;
+ }
+
+ return result;
+}
+
+/********************************************************************
+ _svcctl_StartServiceW
+********************************************************************/
+
+WERROR _svcctl_StartServiceW(struct pipes_struct *p,
+ struct svcctl_StartServiceW *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_SVC_START) )
+ return WERR_ACCESS_DENIED;
+
+ return info->ops->start_service( info->name );
+}
+
+/********************************************************************
+ _svcctl_ControlService
+********************************************************************/
+
+WERROR _svcctl_ControlService(struct pipes_struct *p,
+ struct svcctl_ControlService *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) )
+ return WERR_INVALID_HANDLE;
+
+ switch ( r->in.control ) {
+ case SVCCTL_CONTROL_STOP:
+ if ( !(info->access_granted & SC_RIGHT_SVC_STOP) )
+ return WERR_ACCESS_DENIED;
+
+ return info->ops->stop_service( info->name,
+ r->out.service_status );
+
+ case SVCCTL_CONTROL_INTERROGATE:
+ if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_STATUS) )
+ return WERR_ACCESS_DENIED;
+
+ return info->ops->service_status( info->name,
+ r->out.service_status );
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+}
+
+/********************************************************************
+ _svcctl_EnumDependentServicesW
+********************************************************************/
+
+WERROR _svcctl_EnumDependentServicesW(struct pipes_struct *p,
+ struct svcctl_EnumDependentServicesW *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.service );
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_SVC_ENUMERATE_DEPENDENTS) )
+ return WERR_ACCESS_DENIED;
+
+ switch (r->in.state) {
+ case SERVICE_STATE_ACTIVE:
+ case SERVICE_STATE_INACTIVE:
+ case SERVICE_STATE_ALL:
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* we have to set the outgoing buffer size to the same as the
+ incoming buffer size (even in the case of failure */
+ /* this is done in the autogenerated server already - gd */
+
+ *r->out.needed = r->in.offered;
+
+ /* no dependent services...basically a stub function */
+ *r->out.services_returned = 0;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ _svcctl_QueryServiceStatusEx
+********************************************************************/
+
+WERROR _svcctl_QueryServiceStatusEx(struct pipes_struct *p,
+ struct svcctl_QueryServiceStatusEx *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+ uint32_t buffer_size;
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_STATUS) )
+ return WERR_ACCESS_DENIED;
+
+ /* we have to set the outgoing buffer size to the same as the
+ incoming buffer size (even in the case of failure) */
+ *r->out.needed = r->in.offered;
+
+ switch ( r->in.info_level ) {
+ case SVC_STATUS_PROCESS_INFO:
+ {
+ struct SERVICE_STATUS_PROCESS svc_stat_proc;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+
+ /* Get the status of the service.. */
+ info->ops->service_status( info->name, &svc_stat_proc.status );
+ svc_stat_proc.process_id = getpid();
+ svc_stat_proc.service_flags = 0x0;
+
+ ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &svc_stat_proc,
+ (ndr_push_flags_fn_t)ndr_push_SERVICE_STATUS_PROCESS);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ r->out.buffer = blob.data;
+ buffer_size = sizeof(struct SERVICE_STATUS_PROCESS);
+ break;
+ }
+
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+
+ buffer_size += buffer_size % 4;
+ *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered;
+
+ if (buffer_size > r->in.offered ) {
+ return WERR_INSUFFICIENT_BUFFER;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static WERROR fill_svc_config(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ struct auth_session_info *session_info,
+ const char *name,
+ struct QUERY_SERVICE_CONFIG *config)
+{
+ const char *result = NULL;
+
+ /* now fill in the individual values */
+
+ ZERO_STRUCTP(config);
+
+ config->displayname = svcctl_lookup_dispname(mem_ctx,
+ msg_ctx,
+ session_info,
+ name);
+
+ result = svcctl_get_string_value(mem_ctx,
+ msg_ctx,
+ session_info,
+ name,
+ "ObjectName");
+ if (result != NULL) {
+ config->startname = result;
+ }
+
+ result = svcctl_get_string_value(mem_ctx,
+ msg_ctx,
+ session_info,
+ name,
+ "ImagePath");
+ if (result != NULL) {
+ config->executablepath = result;
+ }
+
+ /* a few hard coded values */
+ /* loadordergroup and dependencies are empty */
+
+ config->tag_id = 0x00000000; /* unassigned loadorder group */
+ config->service_type = SERVICE_TYPE_WIN32_OWN_PROCESS;
+ config->error_control = SVCCTL_SVC_ERROR_NORMAL;
+
+ /* set the start type. NetLogon and WINS are disabled to prevent
+ the client from showing the "Start" button (if of course the services
+ are not running */
+
+ if ( strequal( name, "NETLOGON" ) && ( lp_servicenumber(name) == -1 ) )
+ config->start_type = SVCCTL_DISABLED;
+ else if ( strequal( name, "WINS" ) && ( !lp_we_are_a_wins_server() ))
+ config->start_type = SVCCTL_DISABLED;
+ else
+ config->start_type = SVCCTL_DEMAND_START;
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ _svcctl_QueryServiceConfigW
+********************************************************************/
+
+WERROR _svcctl_QueryServiceConfigW(struct pipes_struct *p,
+ struct svcctl_QueryServiceConfigW *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+ uint32_t buffer_size;
+ WERROR wresult;
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_CONFIG) )
+ return WERR_ACCESS_DENIED;
+
+ /* we have to set the outgoing buffer size to the same as the
+ incoming buffer size (even in the case of failure */
+
+ *r->out.needed = r->in.offered;
+
+ wresult = fill_svc_config(p->mem_ctx,
+ p->msg_ctx,
+ session_info,
+ info->name,
+ r->out.query);
+ if ( !W_ERROR_IS_OK(wresult) )
+ return wresult;
+
+ buffer_size = ndr_size_QUERY_SERVICE_CONFIG(r->out.query, 0);
+ *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered;
+
+ if (buffer_size > r->in.offered ) {
+ ZERO_STRUCTP(r->out.query);
+ return WERR_INSUFFICIENT_BUFFER;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ _svcctl_QueryServiceConfig2W
+********************************************************************/
+
+WERROR _svcctl_QueryServiceConfig2W(struct pipes_struct *p,
+ struct svcctl_QueryServiceConfig2W *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+ uint32_t buffer_size;
+ DATA_BLOB blob = data_blob_null;
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SERVICE) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_SVC_QUERY_CONFIG) )
+ return WERR_ACCESS_DENIED;
+
+ /* we have to set the outgoing buffer size to the same as the
+ incoming buffer size (even in the case of failure */
+ *r->out.needed = r->in.offered;
+
+ switch ( r->in.info_level ) {
+ case SERVICE_CONFIG_DESCRIPTION:
+ {
+ struct SERVICE_DESCRIPTION desc_buf;
+ const char *description;
+ enum ndr_err_code ndr_err;
+
+ description = svcctl_lookup_description(p->mem_ctx,
+ p->msg_ctx,
+ session_info,
+ info->name);
+
+ desc_buf.description = description;
+
+ ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &desc_buf,
+ (ndr_push_flags_fn_t)ndr_push_SERVICE_DESCRIPTION);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ break;
+ }
+ break;
+ case SERVICE_CONFIG_FAILURE_ACTIONS:
+ {
+ struct SERVICE_FAILURE_ACTIONSW actions;
+ enum ndr_err_code ndr_err;
+
+ /* nothing to say...just service the request */
+
+ ZERO_STRUCT( actions );
+
+ ndr_err = ndr_push_struct_blob(&blob, p->mem_ctx, &actions,
+ (ndr_push_flags_fn_t)ndr_push_SERVICE_FAILURE_ACTIONSW);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ break;
+ }
+ break;
+
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ buffer_size = blob.length;
+ buffer_size += buffer_size % 4;
+ *r->out.needed = (buffer_size > r->in.offered) ? buffer_size : r->in.offered;
+
+ if (buffer_size > r->in.offered)
+ return WERR_INSUFFICIENT_BUFFER;
+
+ memcpy(r->out.buffer, blob.data, blob.length);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ _svcctl_LockServiceDatabase
+********************************************************************/
+
+WERROR _svcctl_LockServiceDatabase(struct pipes_struct *p,
+ struct svcctl_LockServiceDatabase *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+
+ /* perform access checks */
+
+ if ( !info || (info->type != SVC_HANDLE_IS_SCM) )
+ return WERR_INVALID_HANDLE;
+
+ if ( !(info->access_granted & SC_RIGHT_MGR_LOCK) )
+ return WERR_ACCESS_DENIED;
+
+ /* Just open a handle. Doesn't actually lock anything */
+
+ return create_open_service_handle( p, r->out.lock, SVC_HANDLE_IS_DBLOCK, NULL, 0 );
+}
+
+/********************************************************************
+ _svcctl_UnlockServiceDatabase
+********************************************************************/
+
+WERROR _svcctl_UnlockServiceDatabase(struct pipes_struct *p,
+ struct svcctl_UnlockServiceDatabase *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.lock );
+
+
+ if ( !info || (info->type != SVC_HANDLE_IS_DBLOCK) )
+ return WERR_INVALID_HANDLE;
+
+ return close_policy_hnd( p, r->out.lock) ? WERR_OK : WERR_INVALID_HANDLE;
+}
+
+/********************************************************************
+ _svcctl_QueryServiceObjectSecurity
+********************************************************************/
+
+WERROR _svcctl_QueryServiceObjectSecurity(struct pipes_struct *p,
+ struct svcctl_QueryServiceObjectSecurity *r)
+{
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+ struct security_descriptor *sec_desc;
+ NTSTATUS status;
+ uint8_t *buffer = NULL;
+ size_t len = 0;
+ WERROR err;
+
+
+ /* only support the SCM and individual services */
+
+ if ( !info || !(info->type & (SVC_HANDLE_IS_SERVICE|SVC_HANDLE_IS_SCM)) )
+ return WERR_INVALID_HANDLE;
+
+ /* check access reights (according to MSDN) */
+
+ if ( !(info->access_granted & SEC_STD_READ_CONTROL) )
+ return WERR_ACCESS_DENIED;
+
+ /* TODO: handle something besides SECINFO_DACL */
+
+ if ( (r->in.security_flags & SECINFO_DACL) != SECINFO_DACL )
+ return WERR_INVALID_PARAMETER;
+
+ /* Lookup the security descriptor and marshall it up for a reply */
+ err = svcctl_get_secdesc(p->msg_ctx,
+ get_session_info_system(),
+ info->name,
+ p->mem_ctx,
+ &sec_desc);
+ if (W_ERROR_EQUAL(err, WERR_FILE_NOT_FOUND)) {
+ DBG_NOTICE("service %s does not exist\n", info->name);
+ return WERR_SERVICE_DOES_NOT_EXIST;
+ }
+ if (!W_ERROR_IS_OK(err)) {
+ DBG_NOTICE("Failed to get a valid secdesc for %s: %s\n",
+ info->name, win_errstr(err));
+ return err;
+ }
+
+ *r->out.needed = ndr_size_security_descriptor(sec_desc, 0);
+
+ if ( *r->out.needed > r->in.offered) {
+ return WERR_INSUFFICIENT_BUFFER;
+ }
+
+ status = marshall_sec_desc(p->mem_ctx, sec_desc, &buffer, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ *r->out.needed = len;
+ memcpy(r->out.buffer, buffer, len);
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ _svcctl_SetServiceObjectSecurity
+********************************************************************/
+
+WERROR _svcctl_SetServiceObjectSecurity(struct pipes_struct *p,
+ struct svcctl_SetServiceObjectSecurity *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ SERVICE_INFO *info = find_service_info_by_hnd( p, r->in.handle );
+ struct security_descriptor *sec_desc = NULL;
+ uint32_t required_access;
+ NTSTATUS status;
+
+ if ( !info || !(info->type & (SVC_HANDLE_IS_SERVICE|SVC_HANDLE_IS_SCM)) )
+ return WERR_INVALID_HANDLE;
+
+ /* can't set the security de4scriptor on the ServiceControlManager */
+
+ if ( info->type == SVC_HANDLE_IS_SCM )
+ return WERR_ACCESS_DENIED;
+
+ /* check the access on the open handle */
+
+ switch ( r->in.security_flags ) {
+ case SECINFO_DACL:
+ required_access = SEC_STD_WRITE_DAC;
+ break;
+
+ case SECINFO_OWNER:
+ case SECINFO_GROUP:
+ required_access = SEC_STD_WRITE_OWNER;
+ break;
+
+ case SECINFO_SACL:
+ return WERR_INVALID_PARAMETER;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if ( !(info->access_granted & required_access) )
+ return WERR_ACCESS_DENIED;
+
+ /* read the security descfriptor */
+
+ status = unmarshall_sec_desc(p->mem_ctx,
+ r->in.buffer,
+ r->in.offered,
+ &sec_desc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ /* store the new SD */
+
+ if (!svcctl_set_secdesc(p->msg_ctx, session_info, info->name, sec_desc))
+ return WERR_ACCESS_DENIED;
+
+ return WERR_OK;
+}
+
+
+WERROR _svcctl_DeleteService(struct pipes_struct *p,
+ struct svcctl_DeleteService *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_SetServiceStatus(struct pipes_struct *p,
+ struct svcctl_SetServiceStatus *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_NotifyBootConfigStatus(struct pipes_struct *p,
+ struct svcctl_NotifyBootConfigStatus *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_SCSetServiceBitsW(struct pipes_struct *p,
+ struct svcctl_SCSetServiceBitsW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_ChangeServiceConfigW(struct pipes_struct *p,
+ struct svcctl_ChangeServiceConfigW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_CreateServiceW(struct pipes_struct *p,
+ struct svcctl_CreateServiceW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_QueryServiceLockStatusW(struct pipes_struct *p,
+ struct svcctl_QueryServiceLockStatusW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_GetServiceKeyNameW(struct pipes_struct *p,
+ struct svcctl_GetServiceKeyNameW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_SCSetServiceBitsA(struct pipes_struct *p,
+ struct svcctl_SCSetServiceBitsA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_ChangeServiceConfigA(struct pipes_struct *p,
+ struct svcctl_ChangeServiceConfigA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_CreateServiceA(struct pipes_struct *p,
+ struct svcctl_CreateServiceA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_EnumDependentServicesA(struct pipes_struct *p,
+ struct svcctl_EnumDependentServicesA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_EnumServicesStatusA(struct pipes_struct *p,
+ struct svcctl_EnumServicesStatusA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_OpenSCManagerA(struct pipes_struct *p,
+ struct svcctl_OpenSCManagerA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_OpenServiceA(struct pipes_struct *p,
+ struct svcctl_OpenServiceA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_QueryServiceConfigA(struct pipes_struct *p,
+ struct svcctl_QueryServiceConfigA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_QueryServiceLockStatusA(struct pipes_struct *p,
+ struct svcctl_QueryServiceLockStatusA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_StartServiceA(struct pipes_struct *p,
+ struct svcctl_StartServiceA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_GetServiceDisplayNameA(struct pipes_struct *p,
+ struct svcctl_GetServiceDisplayNameA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_GetServiceKeyNameA(struct pipes_struct *p,
+ struct svcctl_GetServiceKeyNameA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_GetCurrentGroupeStateW(struct pipes_struct *p,
+ struct svcctl_GetCurrentGroupeStateW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_EnumServiceGroupW(struct pipes_struct *p,
+ struct svcctl_EnumServiceGroupW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_ChangeServiceConfig2A(struct pipes_struct *p,
+ struct svcctl_ChangeServiceConfig2A *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_ChangeServiceConfig2W(struct pipes_struct *p,
+ struct svcctl_ChangeServiceConfig2W *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_QueryServiceConfig2A(struct pipes_struct *p,
+ struct svcctl_QueryServiceConfig2A *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_EnumServicesStatusExA(struct pipes_struct *p,
+ struct svcctl_EnumServicesStatusExA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_EnumServicesStatusExW(struct pipes_struct *p,
+ struct svcctl_EnumServicesStatusExW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _svcctl_SCSendTSMessage(struct pipes_struct *p,
+ struct svcctl_SCSendTSMessage *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/****************************************************************
+ _svcctl_CreateServiceWOW64A
+****************************************************************/
+
+WERROR _svcctl_CreateServiceWOW64A(struct pipes_struct *p,
+ struct svcctl_CreateServiceWOW64A *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _svcctl_CreateServiceWOW64W
+****************************************************************/
+
+WERROR _svcctl_CreateServiceWOW64W(struct pipes_struct *p,
+ struct svcctl_CreateServiceWOW64W *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _Opnum46NotUsedOnWire
+****************************************************************/
+
+void _Opnum46NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum46NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _svcctl_NotifyServiceStatusChange
+****************************************************************/
+
+WERROR _svcctl_NotifyServiceStatusChange(struct pipes_struct *p,
+ struct svcctl_NotifyServiceStatusChange *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _svcctl_GetNotifyResults
+****************************************************************/
+
+WERROR _svcctl_GetNotifyResults(struct pipes_struct *p,
+ struct svcctl_GetNotifyResults *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _svcctl_CloseNotifyHandle
+****************************************************************/
+
+WERROR _svcctl_CloseNotifyHandle(struct pipes_struct *p,
+ struct svcctl_CloseNotifyHandle *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _svcctl_ControlServiceExA
+****************************************************************/
+
+WERROR _svcctl_ControlServiceExA(struct pipes_struct *p,
+ struct svcctl_ControlServiceExA *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _svcctl_ControlServiceExW
+****************************************************************/
+
+WERROR _svcctl_ControlServiceExW(struct pipes_struct *p,
+ struct svcctl_ControlServiceExW *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _Opnum52NotUsedOnWire
+****************************************************************/
+
+void _Opnum52NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum52NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _Opnum53NotUsedOnWire
+****************************************************************/
+
+void _Opnum53NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum53NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _Opnum54NotUsedOnWire
+****************************************************************/
+
+void _Opnum54NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum54NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _Opnum55NotUsedOnWire
+****************************************************************/
+
+void _Opnum55NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum55NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _svcctl_QueryServiceConfigEx
+****************************************************************/
+
+WERROR _svcctl_QueryServiceConfigEx(struct pipes_struct *p,
+ struct svcctl_QueryServiceConfigEx *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _Opnum57NotUsedOnWire
+****************************************************************/
+
+void _Opnum57NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum57NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _Opnum58NotUsedOnWire
+****************************************************************/
+
+void _Opnum58NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum58NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _Opnum59NotUsedOnWire
+****************************************************************/
+
+void _Opnum59NotUsedOnWire(struct pipes_struct *p,
+ struct Opnum59NotUsedOnWire *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+}
+
+
+/****************************************************************
+ _svcctl_CreateWowService
+****************************************************************/
+
+WERROR _svcctl_CreateWowService(struct pipes_struct *p,
+ struct svcctl_CreateWowService *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+
+/****************************************************************
+ _svcctl_OpenSCManager2
+****************************************************************/
+
+WERROR _svcctl_OpenSCManager2(struct pipes_struct *p,
+ struct svcctl_OpenSCManager2 *r)
+{
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+static NTSTATUS svcctl__op_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+static NTSTATUS svcctl__op_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server);
+
+#define DCESRV_INTERFACE_SVCCTL_INIT_SERVER \
+ svcctl_init_server
+
+#define DCESRV_INTERFACE_SVCCTL_SHUTDOWN_SERVER \
+ svcctl_shutdown_server
+
+static NTSTATUS svcctl_init_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ bool ok;
+
+ /* initialize the control hooks */
+ init_service_op_table();
+
+ ok = svcctl_init_winreg(msg_ctx);
+ if (!ok) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return svcctl__op_init_server(dce_ctx, ep_server);
+}
+
+static NTSTATUS svcctl_shutdown_server(struct dcesrv_context *dce_ctx,
+ const struct dcesrv_endpoint_server *ep_server)
+{
+ shutdown_service_op_table();
+
+ return svcctl__op_shutdown_server(dce_ctx, ep_server);
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_svcctl_scompat.c"
diff --git a/source3/rpc_server/svcctl/srv_svcctl_nt.h b/source3/rpc_server/svcctl/srv_svcctl_nt.h
new file mode 100644
index 0000000..dd04927
--- /dev/null
+++ b/source3/rpc_server/svcctl/srv_svcctl_nt.h
@@ -0,0 +1,33 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (C) Marcin Krzysztof Porwit 2005.
+ *
+ * Largely Rewritten (Again) by:
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ * Copyright (C) Guenther Deschner 2008,2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _RPC_SERVER_SVCCTL_SRV_SVCCTL_NT_H_
+#define _RPC_SERVER_SVCCTL_SRV_SVCCTL_NT_H_
+
+/* The following definitions come from rpc_server/srv_svcctl_nt.c */
+
+bool init_service_op_table( void );
+bool shutdown_service_op_table(void);
+
+#endif /* _RPC_SERVER_SVCCTL_SRV_SVCCTL_NT_H_ */
diff --git a/source3/rpc_server/svcctl/srv_svcctl_reg.c b/source3/rpc_server/svcctl/srv_svcctl_reg.c
new file mode 100644
index 0000000..78f6096
--- /dev/null
+++ b/source3/rpc_server/svcctl/srv_svcctl_reg.c
@@ -0,0 +1,678 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SVCCTL RPC server keys initialization
+ *
+ * Copyright (c) 2005 Marcin Krzysztof Porwit
+ * Copyright (c) 2005 Gerald (Jerry) Carter
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "services/services.h"
+#include "services/svc_winreg_glue.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "rpc_client/cli_winreg_int.h"
+#include "rpc_client/cli_winreg.h"
+#include "rpc_server/svcctl/srv_svcctl_reg.h"
+#include "auth.h"
+#include "registry/reg_backend_db.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_REGISTRY
+
+#define TOP_LEVEL_SERVICES_KEY "SYSTEM\\CurrentControlSet\\Services"
+
+struct rcinit_file_information {
+ char *description;
+};
+
+struct service_display_info {
+ const char *servicename;
+ const char *daemon;
+ const char *dispname;
+ const char *description;
+};
+
+static struct service_display_info builtin_svcs[] = {
+ {
+ "Spooler",
+ "smbd",
+ "Print Spooler",
+ "Internal service for spooling files to print devices"
+ },
+ {
+ "NETLOGON",
+ "smbd",
+ "Net Logon",
+ "File service providing access to policy and profile data (not"
+ "remotely manageable)"
+ },
+ {
+ "RemoteRegistry",
+ "smbd",
+ "Remote Registry Service",
+ "Internal service providing remote access to the Samba registry"
+ },
+ {
+ "WINS",
+ "nmbd",
+ "Windows Internet Name Service (WINS)",
+ "Internal service providing a NetBIOS point-to-point name server"
+ "(not remotely manageable)"
+ },
+ { NULL, NULL, NULL, NULL }
+};
+
+static struct service_display_info common_unix_svcs[] = {
+ { "cups", NULL, "Common Unix Printing System","Provides unified printing support for all operating systems" },
+ { "postfix", NULL, "Internet Mail Service", "Provides support for sending and receiving electonic mail" },
+ { "sendmail", NULL, "Internet Mail Service", "Provides support for sending and receiving electonic mail" },
+ { "portmap", NULL, "TCP Port to RPC PortMapper",NULL },
+ { "xinetd", NULL, "Internet Meta-Daemon", NULL },
+ { "inet", NULL, "Internet Meta-Daemon", NULL },
+ { "xntpd", NULL, "Network Time Service", NULL },
+ { "ntpd", NULL, "Network Time Service", NULL },
+ { "lpd", NULL, "BSD Print Spooler", NULL },
+ { "nfsserver", NULL, "Network File Service", NULL },
+ { "cron", NULL, "Scheduling Service", NULL },
+ { "at", NULL, "Scheduling Service", NULL },
+ { "nscd", NULL, "Name Service Cache Daemon", NULL },
+ { "slapd", NULL, "LDAP Directory Service", NULL },
+ { "ldap", NULL, "LDAP DIrectory Service", NULL },
+ { "ypbind", NULL, "NIS Directory Service", NULL },
+ { "courier-imap", NULL, "IMAP4 Mail Service", NULL },
+ { "courier-pop3", NULL, "POP3 Mail Service", NULL },
+ { "named", NULL, "Domain Name Service", NULL },
+ { "bind", NULL, "Domain Name Service", NULL },
+ { "httpd", NULL, "HTTP Server", NULL },
+ { "apache", NULL, "HTTP Server", "Provides s highly scalable and flexible web server "
+ "capable of implementing various protocols including "
+ "but not limited to HTTP" },
+ { "autofs", NULL, "Automounter", NULL },
+ { "squid", NULL, "Web Cache Proxy ", NULL },
+ { "perfcountd", NULL, "Performance Monitoring Daemon", NULL },
+ { "pgsql", NULL, "PgSQL Database Server", "Provides service for SQL database from Postgresql.org" },
+ { "arpwatch", NULL, "ARP Tables watcher", "Provides service for monitoring ARP tables for changes" },
+ { "dhcpd", NULL, "DHCP Server", "Provides service for dynamic host configuration and IP assignment" },
+ { "nwserv", NULL, "NetWare Server Emulator", "Provides service for emulating Novell NetWare 3.12 server" },
+ { "proftpd", NULL, "Professional FTP Server", "Provides high configurable service for FTP connection and "
+ "file transferring" },
+ { "ssh2", NULL, "SSH Secure Shell", "Provides service for secure connection for remote administration" },
+ { "sshd", NULL, "SSH Secure Shell", "Provides service for secure connection for remote administration" },
+ { NULL, NULL, NULL, NULL }
+};
+
+/********************************************************************
+ This is where we do the dirty work of filling in things like the
+ Display name, Description, etc...
+********************************************************************/
+static char *svcctl_get_common_service_dispname(TALLOC_CTX *mem_ctx,
+ const char *servicename)
+{
+ uint32_t i;
+
+ for (i = 0; common_unix_svcs[i].servicename; i++) {
+ if (strequal(servicename, common_unix_svcs[i].servicename)) {
+ char *dispname;
+ dispname = talloc_asprintf(mem_ctx, "%s (%s)",
+ common_unix_svcs[i].dispname,
+ common_unix_svcs[i].servicename);
+ if (dispname == NULL) {
+ return NULL;
+ }
+ return dispname;
+ }
+ }
+
+ return talloc_strdup(mem_ctx, servicename);
+}
+
+/********************************************************************
+********************************************************************/
+static char *svcctl_cleanup_string(TALLOC_CTX *mem_ctx,
+ const char *string)
+{
+ char *clean = NULL;
+ char *begin, *end;
+
+ clean = talloc_strdup(mem_ctx, string);
+ if (clean == NULL) {
+ return NULL;
+ }
+ begin = clean;
+
+ /* trim any beginning whilespace */
+ while (isspace(*begin)) {
+ begin++;
+ }
+
+ if (*begin == '\0') {
+ return NULL;
+ }
+
+ /* trim any trailing whitespace or carriage returns.
+ Start at the end and move backwards */
+
+ end = begin + strlen(begin) - 1;
+
+ while (isspace(*end) || *end=='\n' || *end=='\r') {
+ *end = '\0';
+ end--;
+ }
+
+ return begin;
+}
+
+/********************************************************************
+********************************************************************/
+static bool read_init_file(TALLOC_CTX *mem_ctx,
+ const char *servicename,
+ struct rcinit_file_information **service_info)
+{
+ struct rcinit_file_information *info = NULL;
+ char *filepath = NULL;
+ char str[1024];
+ FILE *f = NULL;
+ char *p = NULL;
+
+ info = talloc_zero(mem_ctx, struct rcinit_file_information);
+ if (info == NULL) {
+ return false;
+ }
+
+ /* attempt the file open */
+
+ filepath = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
+ get_dyn_MODULESDIR(),
+ SVCCTL_SCRIPT_DIR,
+ servicename);
+ if (filepath == NULL) {
+ return false;
+ }
+ f = fopen( filepath, "r" );
+ if (f == NULL) {
+ DEBUG(0,("read_init_file: failed to open [%s]\n", filepath));
+ return false;
+ }
+
+ while ((fgets(str, sizeof(str) - 1, f)) != NULL) {
+ /* ignore everything that is not a full line
+ comment starting with a '#' */
+
+ if (str[0] != '#') {
+ continue;
+ }
+
+ /* Look for a line like '^#.*Description:' */
+
+ p = strstr(str, "Description:");
+ if (p != NULL) {
+ char *desc;
+ size_t len = strlen(p);
+
+ if (len <= 12) {
+ break;
+ }
+
+ desc = svcctl_cleanup_string(mem_ctx, p + 12);
+ if (desc != NULL) {
+ info->description = talloc_strdup(info, desc);
+ }
+ }
+ }
+
+ fclose(f);
+
+ if (info->description == NULL) {
+ info->description = talloc_strdup(info,
+ "External Unix Service");
+ if (info->description == NULL) {
+ return false;
+ }
+ }
+
+ *service_info = info;
+
+ return true;
+}
+
+static bool svcctl_add_service(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *h,
+ struct policy_handle *hive_hnd,
+ const char *key,
+ uint32_t access_mask,
+ const char *name)
+{
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+ struct security_descriptor *sd = NULL;
+ struct policy_handle key_hnd;
+ struct winreg_String wkey;
+ struct winreg_String wkeyclass;
+ char *description = NULL;
+ char *dname = NULL;
+ char *ipath = NULL;
+ bool ok = false;
+ uint32_t i;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+
+ ZERO_STRUCT(key_hnd);
+
+ ZERO_STRUCT(wkey);
+ wkey.name = talloc_asprintf(mem_ctx, "%s\\%s", key, name);
+ if (wkey.name == NULL) {
+ goto done;
+ }
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(h,
+ mem_ctx,
+ hive_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ &key_hnd,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, win_errstr(result)));
+ goto done;
+ }
+
+ /* These values are hardcoded in all QueryServiceConfig() replies.
+ I'm just storing them here for cosmetic purposes */
+ status = dcerpc_winreg_set_dword(mem_ctx,
+ h,
+ &key_hnd,
+ "Start",
+ SVCCTL_AUTO_START,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(mem_ctx,
+ h,
+ &key_hnd,
+ "Type",
+ SERVICE_TYPE_WIN32_OWN_PROCESS,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_dword(mem_ctx,
+ h,
+ &key_hnd,
+ "ErrorControl",
+ SVCCTL_SVC_ERROR_NORMAL,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(mem_ctx,
+ h,
+ &key_hnd,
+ "ObjectName",
+ "LocalSystem",
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ /*
+ * Special considerations for internal services and the DisplayName
+ * value.
+ */
+ for (i = 0; builtin_svcs[i].servicename; i++) {
+ if (strequal(name, builtin_svcs[i].servicename)) {
+ ipath = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
+ get_dyn_MODULESDIR(),
+ SVCCTL_SCRIPT_DIR,
+ builtin_svcs[i].daemon);
+ description = talloc_strdup(mem_ctx, builtin_svcs[i].description);
+ dname = talloc_strdup(mem_ctx, builtin_svcs[i].dispname);
+ break;
+ }
+ }
+
+ /* Default to an external service if we haven't found a match */
+ if (builtin_svcs[i].servicename == NULL) {
+ struct rcinit_file_information *init_info = NULL;
+ char *dispname = NULL;
+
+ ipath = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
+ get_dyn_MODULESDIR(),
+ SVCCTL_SCRIPT_DIR,
+ name);
+
+ /* lookup common unix display names */
+ dispname = svcctl_get_common_service_dispname(mem_ctx, name);
+ dname = talloc_strdup(mem_ctx, dispname ? dispname : "");
+
+ /* get info from init file itself */
+ if (read_init_file(mem_ctx, name, &init_info)) {
+ description = talloc_strdup(mem_ctx,
+ init_info->description);
+ } else {
+ description = talloc_strdup(mem_ctx,
+ "External Unix Service");
+ }
+ }
+
+ if (ipath == NULL || dname == NULL || description == NULL) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(mem_ctx,
+ h,
+ &key_hnd,
+ "DisplayName",
+ dname,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(mem_ctx,
+ h,
+ &key_hnd,
+ "ImagePath",
+ ipath,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sz(mem_ctx,
+ h,
+ &key_hnd,
+ "Description",
+ description,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ sd = svcctl_gen_service_sd(mem_ctx);
+ if (sd == NULL) {
+ DEBUG(0, ("add_new_svc_name: Failed to create default "
+ "sec_desc!\n"));
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, mem_ctx, &key_hnd, &result);
+ }
+ ZERO_STRUCT(key_hnd);
+
+ ZERO_STRUCT(wkey);
+ wkey.name = talloc_asprintf(mem_ctx, "%s\\%s\\Security", key, name);
+ if (wkey.name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(h,
+ mem_ctx,
+ hive_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ &key_hnd,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create key %s: %s\n",
+ wkey.name, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sd(mem_ctx,
+ h,
+ &key_hnd,
+ "Security",
+ sd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg_keys: Could not create value: %s\n",
+ win_errstr(result)));
+ goto done;
+ }
+
+ ok = true;
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, mem_ctx, &key_hnd, &result);
+ }
+
+ return ok;
+}
+
+bool svcctl_init_winreg(struct messaging_context *msg_ctx)
+{
+ struct dcerpc_binding_handle *h = NULL;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ const char **service_list = lp_svcctl_list();
+ const char **subkeys = NULL;
+ uint32_t num_subkeys = 0;
+ char *key = NULL;
+ uint32_t i;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ bool ok = false;
+ TALLOC_CTX *tmp_ctx;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ DEBUG(3, ("Initialise the svcctl registry keys if needed.\n"));
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ key = talloc_strdup(tmp_ctx, TOP_LEVEL_SERVICES_KEY);
+ if (key == NULL) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_int_hklm_openkey(tmp_ctx,
+ get_session_info_system(),
+ msg_ctx,
+ &h,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg: Could not open %s - %s\n",
+ key, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg: Could not open %s - %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ /* get all subkeys */
+ status = dcerpc_winreg_enum_keys(tmp_ctx,
+ h,
+ &key_hnd,
+ &num_subkeys,
+ &subkeys,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_init_winreg: Could not enum keys at %s - %s\n",
+ key, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_init_winreg: Could not enum keys at %s - %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ for (i = 0; builtin_svcs[i].servicename != NULL; i++) {
+ uint32_t j;
+ bool skip = false;
+
+ for (j = 0; j < num_subkeys; j++) {
+ if (strequal(subkeys[i], builtin_svcs[i].servicename)) {
+ skip = true;
+ }
+ }
+
+ if (skip) {
+ continue;
+ }
+
+ ok = svcctl_add_service(tmp_ctx,
+ h,
+ &hive_hnd,
+ key,
+ access_mask,
+ builtin_svcs[i].servicename);
+ if (!ok) {
+ goto done;
+ }
+ }
+
+ for (i = 0; service_list && service_list[i]; i++) {
+ uint32_t j;
+ bool skip = false;
+
+ for (j = 0; j < num_subkeys; j++) {
+ if (strequal(subkeys[i], service_list[i])) {
+ skip = true;
+ }
+ }
+
+ if (skip) {
+ continue;
+ }
+
+ ok = svcctl_add_service(tmp_ctx,
+ h,
+ &hive_hnd,
+ key,
+ access_mask,
+ service_list[i]);
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result);
+ }
+ ZERO_STRUCT(key_hnd);
+
+ if (!ok) {
+ goto done;
+ }
+ }
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result);
+ }
+
+ talloc_free(tmp_ctx);
+ return ok;
+}
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/svcctl/srv_svcctl_reg.h b/source3/rpc_server/svcctl/srv_svcctl_reg.h
new file mode 100644
index 0000000..ab12a03
--- /dev/null
+++ b/source3/rpc_server/svcctl/srv_svcctl_reg.h
@@ -0,0 +1,29 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SVCCTL RPC server keys initialization
+ *
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SRV_SERVICES_REG_H
+#define SRV_SERVICES_REG_H
+
+bool svcctl_init_winreg(struct messaging_context *msg_ctx);
+
+#endif /* SRV_SERVICES_REG_H */
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/rpc_server/winreg/srv_winreg_nt.c b/source3/rpc_server/winreg/srv_winreg_nt.c
new file mode 100644
index 0000000..132213a
--- /dev/null
+++ b/source3/rpc_server/winreg/srv_winreg_nt.c
@@ -0,0 +1,1126 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (C) Gerald Carter 2002-2006.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Implementation of registry functions. */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_winreg.h"
+#include "librpc/gen_ndr/ndr_winreg_scompat.h"
+#include "registry.h"
+#include "registry/reg_api.h"
+#include "registry/reg_perfcount.h"
+#include "rpc_misc.h"
+#include "auth.h"
+#include "lib/privileges.h"
+#include "libcli/security/secdesc.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+enum handle_types { HTYPE_REGVAL, HTYPE_REGKEY };
+
+/******************************************************************
+ Find a registry key handle and return a struct registry_key *
+ *****************************************************************/
+
+static struct registry_key *find_regkey_by_hnd(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ enum handle_types type)
+{
+ struct registry_key *regkey = NULL;
+ NTSTATUS status;
+
+ regkey = find_policy_by_hnd(p,
+ hnd,
+ type,
+ struct registry_key,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2,("find_regkey_index_by_hnd: Registry Key not found: %s\n",
+ nt_errstr(status)));
+ return NULL;
+ }
+
+ return regkey;
+}
+
+/*******************************************************************
+ Function for open a new registry handle and creating a handle
+ Note that P should be valid & hnd should already have space
+
+ When we open a key, we store the full path to the key as
+ HK[LM|U]\<key>\<key>\...
+ *******************************************************************/
+
+static WERROR open_registry_key(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ struct registry_key *parent,
+ const char *subkeyname,
+ uint32_t access_desired)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ WERROR result = WERR_OK;
+ struct registry_key *key;
+
+ if (parent == NULL) {
+ result = reg_openhive(p->mem_ctx, subkeyname, access_desired,
+ session_info->security_token, &key);
+ }
+ else {
+ result = reg_openkey(p->mem_ctx, parent, subkeyname,
+ access_desired, &key);
+ }
+
+ if ( !W_ERROR_IS_OK(result) ) {
+ return result;
+ }
+
+ if ( !create_policy_hnd( p, hnd, HTYPE_REGKEY, key ) ) {
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ Function for open a new registry handle and creating a handle
+ Note that P should be valid & hnd should already have space
+ *******************************************************************/
+
+static bool close_registry_key(struct pipes_struct *p,
+ struct policy_handle *hnd,
+ enum handle_types type)
+{
+ struct registry_key *regkey = find_regkey_by_hnd(p, hnd, type);
+
+ if ( !regkey ) {
+ DEBUG(2,("close_registry_key: Invalid handle (%s:%u:%u)\n",
+ OUR_HANDLE(hnd)));
+ return False;
+ }
+
+ close_policy_hnd(p, hnd);
+
+ return True;
+}
+
+/********************************************************************
+ _winreg_CloseKey
+ ********************************************************************/
+
+WERROR _winreg_CloseKey(struct pipes_struct *p,
+ struct winreg_CloseKey *r)
+{
+ bool ok;
+
+ /* close the policy handle */
+
+ ok = close_registry_key(p, r->in.handle, HTYPE_REGKEY);
+ if (!ok) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ ZERO_STRUCTP(r->out.handle);
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _winreg_OpenHKLM
+ ********************************************************************/
+
+WERROR _winreg_OpenHKLM(struct pipes_struct *p,
+ struct winreg_OpenHKLM *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKLM, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKPD
+ ********************************************************************/
+
+WERROR _winreg_OpenHKPD(struct pipes_struct *p,
+ struct winreg_OpenHKPD *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKPD, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKPT
+ ********************************************************************/
+
+WERROR _winreg_OpenHKPT(struct pipes_struct *p,
+ struct winreg_OpenHKPT *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKPT, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKCR
+ ********************************************************************/
+
+WERROR _winreg_OpenHKCR(struct pipes_struct *p,
+ struct winreg_OpenHKCR *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKCR, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKU
+ ********************************************************************/
+
+WERROR _winreg_OpenHKU(struct pipes_struct *p,
+ struct winreg_OpenHKU *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKU, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKCU
+ ********************************************************************/
+
+WERROR _winreg_OpenHKCU(struct pipes_struct *p,
+ struct winreg_OpenHKCU *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKCU, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKCC
+ ********************************************************************/
+
+WERROR _winreg_OpenHKCC(struct pipes_struct *p,
+ struct winreg_OpenHKCC *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKCC, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKDD
+ ********************************************************************/
+
+WERROR _winreg_OpenHKDD(struct pipes_struct *p,
+ struct winreg_OpenHKDD *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKDD, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenHKPN
+ ********************************************************************/
+
+WERROR _winreg_OpenHKPN(struct pipes_struct *p,
+ struct winreg_OpenHKPN *r)
+{
+ return open_registry_key(p, r->out.handle, NULL, KEY_HKPN, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_OpenKey
+ ********************************************************************/
+
+WERROR _winreg_OpenKey(struct pipes_struct *p,
+ struct winreg_OpenKey *r)
+{
+ struct registry_key *parent = find_regkey_by_hnd(p,
+ r->in.parent_handle,
+ HTYPE_REGKEY);
+
+ if ( !parent )
+ return WERR_INVALID_HANDLE;
+
+ return open_registry_key(p, r->out.handle, parent, r->in.keyname.name, r->in.access_mask);
+}
+
+/*******************************************************************
+ _winreg_QueryValue
+ ********************************************************************/
+
+WERROR _winreg_QueryValue(struct pipes_struct *p,
+ struct winreg_QueryValue *r)
+{
+ WERROR status = WERR_FILE_NOT_FOUND;
+ struct registry_key *regkey = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+ prs_struct prs_hkpd;
+
+ uint8_t *outbuf = NULL;
+ uint32_t outbuf_size = 0;
+
+ bool free_buf = False;
+ bool free_prs = False;
+
+ if ( !regkey )
+ return WERR_INVALID_HANDLE;
+
+ if (r->in.value_name->name == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if ((r->out.data_length == NULL) || (r->out.type == NULL) || (r->out.data_size == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ DEBUG(7,("_winreg_QueryValue: policy key name = [%s]\n", regkey->key->name));
+ DEBUG(7,("_winreg_QueryValue: policy key type = [%08x]\n", regkey->key->type));
+
+ /* Handle QueryValue calls on HKEY_PERFORMANCE_DATA */
+ if(regkey->key->type == REG_KEY_HKPD)
+ {
+ if (strequal(r->in.value_name->name, "Global")) {
+ if (!prs_init(&prs_hkpd, *r->in.data_size, p->mem_ctx, MARSHALL))
+ return WERR_NOT_ENOUGH_MEMORY;
+ status = reg_perfcount_get_hkpd(
+ &prs_hkpd, *r->in.data_size, &outbuf_size, NULL);
+ outbuf = (uint8_t *)prs_hkpd.data_p;
+ free_prs = True;
+ }
+ else if (strequal(r->in.value_name->name, "Counter 009")) {
+ outbuf_size = reg_perfcount_get_counter_names(
+ reg_perfcount_get_base_index(),
+ (char **)(void *)&outbuf);
+ free_buf = True;
+ }
+ else if (strequal(r->in.value_name->name, "Explain 009")) {
+ outbuf_size = reg_perfcount_get_counter_help(
+ reg_perfcount_get_base_index(),
+ (char **)(void *)&outbuf);
+ free_buf = True;
+ }
+ else if (isdigit(r->in.value_name->name[0])) {
+ /* we probably have a request for a specific object
+ * here */
+ if (!prs_init(&prs_hkpd, *r->in.data_size, p->mem_ctx, MARSHALL))
+ return WERR_NOT_ENOUGH_MEMORY;
+ status = reg_perfcount_get_hkpd(
+ &prs_hkpd, *r->in.data_size, &outbuf_size,
+ r->in.value_name->name);
+ outbuf = (uint8_t *)prs_hkpd.data_p;
+ free_prs = True;
+ }
+ else {
+ DEBUG(3,("Unsupported key name [%s] for HKPD.\n",
+ r->in.value_name->name));
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ *r->out.type = REG_BINARY;
+ }
+ else {
+ struct registry_value *val;
+
+ status = reg_queryvalue(p->mem_ctx, regkey, r->in.value_name->name,
+ &val);
+ if (!W_ERROR_IS_OK(status)) {
+
+ DEBUG(10,("_winreg_QueryValue: reg_queryvalue failed with: %s\n",
+ win_errstr(status)));
+
+ if (r->out.data_size) {
+ *r->out.data_size = 0;
+ }
+ if (r->out.data_length) {
+ *r->out.data_length = 0;
+ }
+ return status;
+ }
+
+ outbuf = val->data.data;
+ outbuf_size = val->data.length;
+ *r->out.type = val->type;
+ }
+
+ status = WERR_FILE_NOT_FOUND;
+
+ if (*r->in.data_size < outbuf_size) {
+ *r->out.data_size = outbuf_size;
+ status = r->in.data ? WERR_MORE_DATA : WERR_OK;
+ } else {
+ *r->out.data_length = outbuf_size;
+ *r->out.data_size = outbuf_size;
+ if (r->out.data) {
+ memcpy(r->out.data, outbuf, outbuf_size);
+ }
+ status = WERR_OK;
+ }
+
+ if (free_prs) prs_mem_free(&prs_hkpd);
+ if (free_buf) SAFE_FREE(outbuf);
+
+ return status;
+}
+
+/*****************************************************************************
+ _winreg_QueryInfoKey
+ ****************************************************************************/
+
+WERROR _winreg_QueryInfoKey(struct pipes_struct *p,
+ struct winreg_QueryInfoKey *r)
+{
+ WERROR status = WERR_OK;
+ struct registry_key *regkey = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+
+ if ( !regkey )
+ return WERR_INVALID_HANDLE;
+
+ r->out.classname->name = NULL;
+
+ status = reg_queryinfokey(regkey, r->out.num_subkeys, r->out.max_subkeylen,
+ r->out.max_classlen, r->out.num_values, r->out.max_valnamelen,
+ r->out.max_valbufsize, r->out.secdescsize,
+ r->out.last_changed_time);
+ if (!W_ERROR_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * These calculations account for the registry buffers being
+ * UTF-16. They are inexact at best, but so far they worked.
+ */
+
+ *r->out.max_subkeylen *= 2;
+
+ *r->out.max_valnamelen += 1;
+ *r->out.max_valnamelen *= 2;
+
+ return WERR_OK;
+}
+
+
+/*****************************************************************************
+ _winreg_GetVersion
+ ****************************************************************************/
+
+WERROR _winreg_GetVersion(struct pipes_struct *p,
+ struct winreg_GetVersion *r)
+{
+ struct registry_key *regkey = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+
+ if ( !regkey )
+ return WERR_INVALID_HANDLE;
+
+ return reg_getversion(r->out.version);
+}
+
+
+/*****************************************************************************
+ _winreg_EnumKey
+ ****************************************************************************/
+
+WERROR _winreg_EnumKey(struct pipes_struct *p,
+ struct winreg_EnumKey *r)
+{
+ WERROR err = WERR_OK;
+ struct registry_key *key = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+ char *name;
+
+ if ( !key )
+ return WERR_INVALID_HANDLE;
+
+ if ( !r->in.name || !r->in.keyclass )
+ return WERR_INVALID_PARAMETER;
+
+ DEBUG(8,("_winreg_EnumKey: enumerating key [%s]\n", key->key->name));
+
+ err = reg_enumkey(p->mem_ctx, key, r->in.enum_index, &name,
+ r->out.last_changed_time);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+ r->out.name->name = name;
+ r->out.keyclass->name = "";
+ return WERR_OK;
+}
+
+/*****************************************************************************
+ _winreg_EnumValue
+ ****************************************************************************/
+
+WERROR _winreg_EnumValue(struct pipes_struct *p,
+ struct winreg_EnumValue *r)
+{
+ WERROR err = WERR_OK;
+ struct registry_key *key = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+ char *valname = NULL;
+ struct registry_value *val = NULL;
+
+ if ( !key )
+ return WERR_INVALID_HANDLE;
+
+ if ( !r->in.name )
+ return WERR_INVALID_PARAMETER;
+
+ DEBUG(8,("_winreg_EnumValue: enumerating values for key [%s]\n",
+ key->key->name));
+
+ err = reg_enumvalue(p->mem_ctx, key, r->in.enum_index, &valname, &val);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ if (r->out.name != NULL) {
+ r->out.name->name = valname;
+ }
+
+ if (r->out.type != NULL) {
+ *r->out.type = val->type;
+ }
+
+ if (r->out.value != NULL) {
+ if ((r->out.size == NULL) || (r->out.length == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (val->data.length > *r->out.size) {
+ *r->out.size = val->data.length;
+ return WERR_MORE_DATA;
+ }
+
+ memcpy( r->out.value, val->data.data, val->data.length );
+ }
+
+ if (r->out.length != NULL) {
+ *r->out.length = val->data.length;
+ }
+ if (r->out.size != NULL) {
+ *r->out.size = val->data.length;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _winreg_InitiateSystemShutdown
+ ********************************************************************/
+
+WERROR _winreg_InitiateSystemShutdown(struct pipes_struct *p,
+ struct winreg_InitiateSystemShutdown *r)
+{
+ struct winreg_InitiateSystemShutdownEx s;
+
+ s.in.hostname = r->in.hostname;
+ s.in.message = r->in.message;
+ s.in.timeout = r->in.timeout;
+ s.in.force_apps = r->in.force_apps;
+ s.in.do_reboot = r->in.do_reboot;
+ s.in.reason = 0;
+
+ /* thunk down to _winreg_InitiateSystemShutdownEx()
+ (just returns a status) */
+
+ return _winreg_InitiateSystemShutdownEx( p, &s );
+}
+
+/*******************************************************************
+ _winreg_InitiateSystemShutdownEx
+ ********************************************************************/
+
+#define SHUTDOWN_R_STRING "-r"
+#define SHUTDOWN_F_STRING "-f"
+
+
+WERROR _winreg_InitiateSystemShutdownEx(struct pipes_struct *p,
+ struct winreg_InitiateSystemShutdownEx *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *shutdown_script = NULL;
+ char *chkmsg = NULL;
+ fstring str_timeout;
+ fstring str_reason;
+ fstring do_reboot;
+ fstring f;
+ int ret = -1;
+ bool can_shutdown = false;
+
+ shutdown_script = lp_shutdown_script(p->mem_ctx, lp_sub);
+ if (!shutdown_script) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ if (!*shutdown_script) {
+ return WERR_ACCESS_DENIED;
+ }
+
+ /* pull the message string and perform necessary sanity checks on it */
+
+ if ( r->in.message && r->in.message->string ) {
+ chkmsg = talloc_alpha_strcpy(p->mem_ctx,
+ r->in.message->string,
+ NULL);
+ if (chkmsg == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ fstr_sprintf(str_timeout, "%d", r->in.timeout);
+ fstr_sprintf(do_reboot, r->in.do_reboot ? SHUTDOWN_R_STRING : "");
+ fstr_sprintf(f, r->in.force_apps ? SHUTDOWN_F_STRING : "");
+ fstr_sprintf(str_reason, "%d", r->in.reason );
+
+ shutdown_script = talloc_all_string_sub(p->mem_ctx,
+ shutdown_script, "%z", chkmsg ? chkmsg : "");
+ if (!shutdown_script) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ shutdown_script = talloc_all_string_sub(p->mem_ctx,
+ shutdown_script, "%t", str_timeout);
+ if (!shutdown_script) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ shutdown_script = talloc_all_string_sub(p->mem_ctx,
+ shutdown_script, "%r", do_reboot);
+ if (!shutdown_script) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ shutdown_script = talloc_all_string_sub(p->mem_ctx,
+ shutdown_script, "%f", f);
+ if (!shutdown_script) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ shutdown_script = talloc_all_string_sub(p->mem_ctx,
+ shutdown_script, "%x", str_reason);
+ if (!shutdown_script) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ can_shutdown = security_token_has_privilege(
+ session_info->security_token, SEC_PRIV_REMOTE_SHUTDOWN);
+
+ /* IF someone has privs, run the shutdown script as root. OTHERWISE run it as not root
+ Take the error return from the script and provide it as the Windows return code. */
+
+ /********** BEGIN SeRemoteShutdownPrivilege BLOCK **********/
+
+ if ( can_shutdown )
+ become_root();
+
+ ret = smbrun(shutdown_script, NULL, NULL);
+
+ if ( can_shutdown )
+ unbecome_root();
+
+ /********** END SeRemoteShutdownPrivilege BLOCK **********/
+
+ DEBUG(3,("_reg_shutdown_ex: Running the command `%s' gave %d\n",
+ shutdown_script, ret));
+
+ return (ret == 0) ? WERR_OK : WERR_ACCESS_DENIED;
+}
+
+/*******************************************************************
+ _winreg_AbortSystemShutdown
+ ********************************************************************/
+
+WERROR _winreg_AbortSystemShutdown(struct pipes_struct *p,
+ struct winreg_AbortSystemShutdown *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ const char *abort_shutdown_script = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int ret = -1;
+ bool can_shutdown = false;
+
+ abort_shutdown_script = lp_abort_shutdown_script(talloc_tos(), lp_sub);
+ if (!*abort_shutdown_script)
+ return WERR_ACCESS_DENIED;
+
+ can_shutdown = security_token_has_privilege(
+ session_info->security_token, SEC_PRIV_REMOTE_SHUTDOWN);
+
+ /********** BEGIN SeRemoteShutdownPrivilege BLOCK **********/
+
+ if ( can_shutdown )
+ become_root();
+
+ ret = smbrun(abort_shutdown_script, NULL, NULL);
+
+ if ( can_shutdown )
+ unbecome_root();
+
+ /********** END SeRemoteShutdownPrivilege BLOCK **********/
+
+ DEBUG(3,("_winreg_AbortSystemShutdown: Running the command `%s' gave %d\n",
+ abort_shutdown_script, ret));
+
+ return (ret == 0) ? WERR_OK : WERR_ACCESS_DENIED;
+}
+
+/*******************************************************************
+ _winreg_RestoreKey
+ ********************************************************************/
+
+WERROR _winreg_RestoreKey(struct pipes_struct *p,
+ struct winreg_RestoreKey *r)
+{
+ struct registry_key *regkey = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+
+ if ( !regkey ) {
+ return WERR_INVALID_HANDLE;
+ }
+ return WERR_BAD_PATHNAME;
+}
+
+/*******************************************************************
+ _winreg_SaveKey
+ ********************************************************************/
+
+WERROR _winreg_SaveKey(struct pipes_struct *p,
+ struct winreg_SaveKey *r)
+{
+ struct registry_key *regkey = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+
+ if ( !regkey ) {
+ return WERR_INVALID_HANDLE;
+ }
+ return WERR_BAD_PATHNAME;
+}
+
+/*******************************************************************
+ _winreg_SaveKeyEx
+ ********************************************************************/
+
+WERROR _winreg_SaveKeyEx(struct pipes_struct *p,
+ struct winreg_SaveKeyEx *r)
+{
+ /* fill in your code here if you think this call should
+ do anything */
+
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/*******************************************************************
+ _winreg_CreateKey
+ ********************************************************************/
+
+WERROR _winreg_CreateKey(struct pipes_struct *p,
+ struct winreg_CreateKey *r)
+{
+ struct registry_key *parent = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+ struct registry_key *new_key = NULL;
+ WERROR result = WERR_OK;
+
+ if ( !parent )
+ return WERR_INVALID_HANDLE;
+
+ DEBUG(10, ("_winreg_CreateKey called with parent key '%s' and "
+ "subkey name '%s'\n", parent->key->name, r->in.name.name));
+
+ result = reg_createkey(NULL, parent, r->in.name.name, r->in.access_mask,
+ &new_key, r->out.action_taken);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ if (!create_policy_hnd(p, r->out.new_handle, HTYPE_REGKEY, new_key)) {
+ TALLOC_FREE(new_key);
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _winreg_SetValue
+ ********************************************************************/
+
+WERROR _winreg_SetValue(struct pipes_struct *p,
+ struct winreg_SetValue *r)
+{
+ struct registry_key *key = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+ struct registry_value *val = NULL;
+
+ if ( !key )
+ return WERR_INVALID_HANDLE;
+
+ DEBUG(8,("_winreg_SetValue: Setting value for [%s:%s]\n",
+ key->key->name, r->in.name.name));
+
+ val = talloc_zero(p->mem_ctx, struct registry_value);
+ if (val == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ val->type = r->in.type;
+ val->data = data_blob_talloc(p->mem_ctx, r->in.data, r->in.size);
+
+ return reg_setvalue(key, r->in.name.name, val);
+}
+
+/*******************************************************************
+ _winreg_DeleteKey
+ ********************************************************************/
+
+WERROR _winreg_DeleteKey(struct pipes_struct *p,
+ struct winreg_DeleteKey *r)
+{
+ struct registry_key *parent = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+
+ if ( !parent )
+ return WERR_INVALID_HANDLE;
+
+ return reg_deletekey(parent, r->in.key.name);
+}
+
+
+/*******************************************************************
+ _winreg_DeleteValue
+ ********************************************************************/
+
+WERROR _winreg_DeleteValue(struct pipes_struct *p,
+ struct winreg_DeleteValue *r)
+{
+ struct registry_key *key = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+
+ if ( !key )
+ return WERR_INVALID_HANDLE;
+
+ return reg_deletevalue(key, r->in.value.name);
+}
+
+/*******************************************************************
+ _winreg_GetKeySecurity
+ ********************************************************************/
+
+WERROR _winreg_GetKeySecurity(struct pipes_struct *p,
+ struct winreg_GetKeySecurity *r)
+{
+ struct registry_key *key = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+ WERROR err = WERR_OK;
+ struct security_descriptor *secdesc = NULL;
+ uint8_t *data = NULL;
+ size_t len = 0;
+
+ if ( !key )
+ return WERR_INVALID_HANDLE;
+
+ /* access checks first */
+
+ if ( !(key->key->access_granted & SEC_STD_READ_CONTROL) )
+ return WERR_ACCESS_DENIED;
+
+ err = reg_getkeysecurity(p->mem_ctx, key, &secdesc);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ err = ntstatus_to_werror(marshall_sec_desc(p->mem_ctx, secdesc,
+ &data, &len));
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ if (len > r->out.sd->size) {
+ r->out.sd->size = len;
+ return WERR_INSUFFICIENT_BUFFER;
+ }
+
+ r->out.sd->size = len;
+ r->out.sd->len = len;
+ r->out.sd->data = data;
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _winreg_SetKeySecurity
+ ********************************************************************/
+
+WERROR _winreg_SetKeySecurity(struct pipes_struct *p,
+ struct winreg_SetKeySecurity *r)
+{
+ struct registry_key *key = find_regkey_by_hnd(p,
+ r->in.handle,
+ HTYPE_REGKEY);
+ struct security_descriptor *secdesc = NULL;
+ WERROR err = WERR_OK;
+
+ if ( !key )
+ return WERR_INVALID_HANDLE;
+
+ /* access checks first */
+
+ if ( !(key->key->access_granted & SEC_STD_WRITE_DAC) )
+ return WERR_ACCESS_DENIED;
+
+ err = ntstatus_to_werror(unmarshall_sec_desc(p->mem_ctx, r->in.sd->data,
+ r->in.sd->len, &secdesc));
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ return reg_setkeysecurity(key, secdesc);
+}
+
+/*******************************************************************
+ _winreg_FlushKey
+ ********************************************************************/
+
+WERROR _winreg_FlushKey(struct pipes_struct *p,
+ struct winreg_FlushKey *r)
+{
+ /* I'm just replying OK because there's not a lot
+ here I see to do i --jerry */
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _winreg_UnLoadKey
+ ********************************************************************/
+
+WERROR _winreg_UnLoadKey(struct pipes_struct *p,
+ struct winreg_UnLoadKey *r)
+{
+ /* fill in your code here if you think this call should
+ do anything */
+
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/*******************************************************************
+ _winreg_ReplaceKey
+ ********************************************************************/
+
+WERROR _winreg_ReplaceKey(struct pipes_struct *p,
+ struct winreg_ReplaceKey *r)
+{
+ /* fill in your code here if you think this call should
+ do anything */
+
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/*******************************************************************
+ _winreg_LoadKey
+ ********************************************************************/
+
+WERROR _winreg_LoadKey(struct pipes_struct *p,
+ struct winreg_LoadKey *r)
+{
+ /* fill in your code here if you think this call should
+ do anything */
+
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/*******************************************************************
+ _winreg_NotifyChangeKeyValue
+ ********************************************************************/
+
+WERROR _winreg_NotifyChangeKeyValue(struct pipes_struct *p,
+ struct winreg_NotifyChangeKeyValue *r)
+{
+ return WERR_NOT_SUPPORTED;
+}
+
+/*******************************************************************
+ _winreg_QueryMultipleValues
+ ********************************************************************/
+
+WERROR _winreg_QueryMultipleValues(struct pipes_struct *p,
+ struct winreg_QueryMultipleValues *r)
+{
+ struct winreg_QueryMultipleValues2 r2;
+ uint32_t needed = 0;
+
+ r2.in.key_handle = r->in.key_handle;
+ r2.in.values_in = r->in.values_in;
+ r2.in.num_values = r->in.num_values;
+ r2.in.offered = r->in.buffer_size;
+ r2.in.buffer = r->in.buffer;
+ r2.out.values_out = r->out.values_out;
+ r2.out.needed = &needed;
+ r2.out.buffer = r->out.buffer;
+
+ return _winreg_QueryMultipleValues2(p, &r2);
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static WERROR construct_multiple_entry(TALLOC_CTX *mem_ctx,
+ const char *valuename,
+ uint32_t value_length,
+ uint32_t offset,
+ enum winreg_Type type,
+ struct QueryMultipleValue *r)
+{
+ r->ve_valuename = talloc_zero(mem_ctx, struct winreg_ValNameBuf);
+ if (r->ve_valuename == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ r->ve_valuename->name = talloc_strdup(r->ve_valuename, valuename ? valuename : "");
+ if (r->ve_valuename->name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ r->ve_valuename->size = strlen_m_term(r->ve_valuename->name)*2;
+ r->ve_valuelen = value_length;
+ r->ve_valueptr = offset;
+ r->ve_type = type;
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ _winreg_QueryMultipleValues2
+ ********************************************************************/
+
+WERROR _winreg_QueryMultipleValues2(struct pipes_struct *p,
+ struct winreg_QueryMultipleValues2 *r)
+{
+ struct registry_key *regkey = find_regkey_by_hnd(p,
+ r->in.key_handle,
+ HTYPE_REGKEY);
+ struct registry_value *vals = NULL;
+ const char **names = NULL;
+ uint32_t offset = 0, num_vals = 0;
+ DATA_BLOB result = data_blob_null;
+ uint32_t i = 0;
+ WERROR err = WERR_OK;
+
+ if (!regkey) {
+ return WERR_INVALID_HANDLE;
+ }
+
+ names = talloc_zero_array(p->mem_ctx, const char *, r->in.num_values);
+ if (names == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (i=0; i < r->in.num_values; i++) {
+ if (r->in.values_in[i].ve_valuename &&
+ r->in.values_in[i].ve_valuename->name) {
+ names[i] = talloc_strdup(names,
+ r->in.values_in[i].ve_valuename->name);
+ if (names[i] == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+ }
+
+ err = reg_querymultiplevalues(p->mem_ctx, regkey,
+ r->in.num_values, names,
+ &num_vals, &vals);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ result = data_blob_talloc(p->mem_ctx, NULL, 0);
+
+ for (i=0; i < r->in.num_values; i++) {
+ const char *valuename = NULL;
+
+ if (vals[i].data.length > 0) {
+ if (!data_blob_append(p->mem_ctx, &result,
+ vals[i].data.data,
+ vals[i].data.length)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (r->in.values_in[i].ve_valuename &&
+ r->in.values_in[i].ve_valuename->name) {
+ valuename = r->in.values_in[i].ve_valuename->name;
+ }
+
+ err = construct_multiple_entry(r->out.values_out,
+ valuename,
+ vals[i].data.length,
+ offset,
+ vals[i].type,
+ &r->out.values_out[i]);
+ if (!W_ERROR_IS_OK(err)) {
+ return err;
+ }
+
+ offset += vals[i].data.length;
+ }
+
+ *r->out.needed = result.length;
+
+ if (r->in.num_values != num_vals) {
+ return WERR_FILE_NOT_FOUND;
+ }
+
+ if (*r->in.offered >= *r->out.needed) {
+ if (r->out.buffer) {
+ memcpy(r->out.buffer, result.data, MIN(result.length, *r->in.offered));
+ }
+ return WERR_OK;
+ } else {
+ return WERR_MORE_DATA;
+ }
+}
+
+/*******************************************************************
+ _winreg_DeleteKeyEx
+ ********************************************************************/
+
+WERROR _winreg_DeleteKeyEx(struct pipes_struct *p,
+ struct winreg_DeleteKeyEx *r)
+{
+ /* fill in your code here if you think this call should
+ do anything */
+
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_winreg_scompat.c"
diff --git a/source3/rpc_server/witness/srv_witness_nt.c b/source3/rpc_server/witness/srv_witness_nt.c
new file mode 100644
index 0000000..1d5f720
--- /dev/null
+++ b/source3/rpc_server/witness/srv_witness_nt.c
@@ -0,0 +1,2465 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) 2012,2023 Stefan Metzmacher
+ * Copyright (C) 2015 Guenther Deschner
+ * Copyright (C) 2018 Samuel Cabrero
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "ctdbd_conn.h"
+#include "ctdb/protocol/protocol.h"
+#include "ctdb_srvids.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "lib/global_contexts.h"
+#include "lib/util/util_tdb.h"
+#include "lib/util/util_str_escape.h"
+#include "source3/include/util_tdb.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/param/param.h"
+#include "lib/util/tevent_werror.h"
+#include "lib/tsocket/tsocket.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/auth.h"
+#include "librpc/gen_ndr/ndr_witness.h"
+#include "librpc/gen_ndr/ndr_witness_scompat.h"
+#include "librpc/gen_ndr/ndr_rpcd_witness.h"
+#include "rpc_server/rpc_server.h"
+
+#define SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION 0x01
+
+struct swn_service_interface;
+struct swn_service_registration;
+struct swn_service_async_notify_state;
+
+struct swn_service_globals {
+ struct dcesrv_context *dce_ctx;
+ struct db_context *dce_conn_register;
+
+ const char *server_global_name;
+ uint32_t local_vnn;
+
+ struct {
+ bool valid;
+ uint64_t generation;
+ struct swn_service_interface *list;
+ } interfaces;
+
+ struct {
+ uint32_t unused_timeout_secs;
+ struct swn_service_registration *list;
+ struct db_context *db;
+ } registrations;
+};
+
+struct swn_service_interface {
+ struct swn_service_interface *prev, *next;
+
+ const char *group_name;
+ struct samba_sockaddr addr;
+ enum witness_interfaceInfo_state state;
+ bool local_iface;
+ uint32_t current_vnn;
+ uint64_t change_generation;
+ uint64_t check_generation;
+};
+
+struct swn_service_registration {
+ struct swn_service_registration *prev, *next;
+
+ struct swn_service_globals *swn;
+
+ struct {
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+ struct tevent_req *subreq;
+ } msg;
+
+ struct {
+ struct policy_handle handle;
+ void *ptr;
+ } key;
+
+ struct {
+ enum witness_version version;
+ const char *computer_name;
+ } client;
+
+ const char *net_name;
+ const char *share_name;
+ struct samba_sockaddr ip_address;
+
+ struct {
+ bool triggered;
+ struct witness_notifyResponse *response;
+ WERROR result;
+ } forced_response;
+
+ struct {
+ bool triggered;
+ /*
+ * We only do ip based RESOURCE_CHANGE notifications for now
+ * and it means we do just one notification at a time
+ * and don't need to queue pending notifications.
+ */
+ enum witness_interfaceInfo_state last_ip_state;
+ } change_notification;
+
+ struct {
+ bool triggered;
+ uint32_t new_node;
+ struct samba_sockaddr new_ip;
+ } move_notification;
+
+ struct {
+ bool required;
+ bool triggered;
+ uint32_t new_node;
+ struct samba_sockaddr new_ip;
+ } share_notification;
+
+ struct {
+ bool required;
+ bool triggered;
+ /*
+ * TODO: find how this works on windows and implement
+ * Windows Server 2022 as client doesn't use it...
+ */
+ } ip_notification;
+
+ struct {
+ struct timeval create_time;
+ struct timeval last_time;
+ uint32_t unused_timeout_secs;
+ struct timeval expire_time;
+ struct tevent_timer *timer;
+ } usage;
+
+ struct {
+ /*
+ * In order to let a Windows server 2022
+ * correctly re-register after moving
+ * to a new connection, we force an
+ * unregistration after 5 seconds.
+ *
+ * It means the client gets WERR_NOT_FOUND
+ * from a pending AsyncNotify() and calls
+ * Unregister() (which also gets WERR_NOT_FOUND).
+ * Then the client calls GetInterfaceList()
+ * and RegisterEx() again.
+ */
+ struct tevent_timer *timer;
+ } forced_unregister;
+
+ struct {
+ uint32_t timeout_secs;
+ struct tevent_queue *queue;
+ struct swn_service_async_notify_state *list;
+ } async_notify;
+};
+
+static struct swn_service_globals *swn_globals = NULL;
+
+static int swn_service_globals_destructor(struct swn_service_globals *swn)
+{
+ SMB_ASSERT(swn == swn_globals);
+ swn_globals = NULL;
+
+ while (swn->registrations.list != NULL) {
+ /*
+ * NO TALLOC_FREE() because of DLIST_REMOVE()
+ * in swn_service_registration_destructor()
+ */
+ talloc_free(swn->registrations.list);
+ }
+
+ return 0;
+}
+
+static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state);
+
+static int swn_service_registration_destructor(struct swn_service_registration *reg)
+{
+ struct swn_service_globals *swn = reg->swn;
+ struct GUID_txt_buf key_buf;
+ const char *key_str = GUID_buf_string(&reg->key.handle.uuid, &key_buf);
+ DATA_BLOB key_blob = data_blob_string_const(key_str);
+ TDB_DATA key = make_tdb_data(key_blob.data, key_blob.length);
+ NTSTATUS status;
+
+ tevent_queue_stop(reg->async_notify.queue);
+ while (reg->async_notify.list != NULL) {
+ swn_service_async_notify_reg_destroyed(reg->async_notify.list);
+ }
+
+ status = dbwrap_delete(reg->swn->registrations.db, key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("rpcd_witness_registration: key '%s' delete - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ } else if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("rpcd_witness_registration: key '%s' deleted\n",
+ tdb_data_dbg(key));
+ }
+
+ DLIST_REMOVE(swn->registrations.list, reg);
+ reg->swn = NULL;
+
+ /*
+ * make sure to drop the policy/context handle from
+ * the assoc_group
+ */
+ TALLOC_FREE(reg->key.ptr);
+
+ return 0;
+}
+
+static void swn_service_registration_update_usage(struct swn_service_registration *reg,
+ struct timeval now)
+{
+ uint64_t expire_timeout_secs = 0;
+ uint64_t max_expire_timeout_secs = TIME_T_MAX;
+
+ reg->usage.last_time = now;
+
+ if (max_expire_timeout_secs > reg->usage.last_time.tv_sec) {
+ max_expire_timeout_secs -= reg->usage.last_time.tv_sec;
+ } else {
+ /*
+ * This should never happen unless
+ * a 32 bit system hits its limit
+ */
+ max_expire_timeout_secs = 0;
+ }
+
+ if (tevent_queue_length(reg->async_notify.queue) != 0) {
+ expire_timeout_secs += reg->async_notify.timeout_secs;
+ }
+
+ expire_timeout_secs += reg->usage.unused_timeout_secs;
+ expire_timeout_secs = MIN(expire_timeout_secs, max_expire_timeout_secs);
+
+ reg->usage.expire_time = timeval_add(&reg->usage.last_time,
+ expire_timeout_secs, 0);
+
+ if (expire_timeout_secs == 0) {
+ /*
+ * No timer needed, witness v1
+ * or max_expire_timeout_secs = 0
+ */
+ TALLOC_FREE(reg->usage.timer);
+ }
+
+ if (reg->usage.timer == NULL) {
+ /* no timer to update */
+ reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, };
+ return;
+ }
+
+ tevent_update_timer(reg->usage.timer, reg->usage.expire_time);
+}
+
+static void swn_service_registration_unused(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct swn_service_registration *reg =
+ talloc_get_type_abort(private_data,
+ struct swn_service_registration);
+
+ reg->usage.timer = NULL;
+
+ TALLOC_FREE(reg);
+}
+
+static void swn_service_registration_force_unregister(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct swn_service_registration *reg =
+ talloc_get_type_abort(private_data,
+ struct swn_service_registration);
+
+ reg->forced_unregister.timer = NULL;
+
+ TALLOC_FREE(reg);
+}
+
+static int swn_service_ctdb_ipreallocated(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data);
+
+static NTSTATUS swn_service_init_globals(struct dcesrv_context *dce_ctx)
+{
+ struct swn_service_globals *swn = NULL;
+ char *global_path = NULL;
+ const char *realm = NULL;
+ const char *nbname = NULL;
+ int ret;
+ bool ok;
+
+ if (swn_globals != NULL) {
+ SMB_ASSERT(swn_globals->dce_ctx == dce_ctx);
+ return NT_STATUS_OK;
+ }
+
+ swn = talloc_zero(dce_ctx, struct swn_service_globals);
+ if (swn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ swn->dce_ctx = dce_ctx;
+
+ swn->dce_conn_register = db_open_rbt(swn);
+ if (swn->dce_conn_register == NULL) {
+ TALLOC_FREE(swn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * This contains secret information like client keys!
+ */
+ global_path = lock_path(swn, "rpcd_witness_registration.tdb");
+ if (global_path == NULL) {
+ TALLOC_FREE(swn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ swn->registrations.db = db_open(swn, global_path,
+ 0, /* hash_size */
+ TDB_DEFAULT |
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (swn->registrations.db == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+ TALLOC_FREE(swn);
+
+ return status;
+ }
+ TALLOC_FREE(global_path);
+
+ nbname = lpcfg_netbios_name(dce_ctx->lp_ctx);
+ realm = lpcfg_realm(dce_ctx->lp_ctx);
+ if (realm != NULL && realm[0] != '\0') {
+ char *name = NULL;
+
+ name = talloc_asprintf(swn, "%s.%s", nbname, realm);
+ if (name == NULL) {
+ TALLOC_FREE(swn);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ok = strlower_m(name);
+ if (!ok) {
+ TALLOC_FREE(swn);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ swn->server_global_name = name;
+ } else {
+ swn->server_global_name = talloc_strdup(swn, nbname);
+ if (swn->server_global_name == NULL) {
+ TALLOC_FREE(swn);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ swn->local_vnn = get_my_vnn();
+
+ ret = register_with_ctdbd(messaging_ctdb_connection(),
+ CTDB_SRVID_IPREALLOCATED,
+ swn_service_ctdb_ipreallocated,
+ swn);
+ if (ret != 0) {
+ TALLOC_FREE(swn);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ swn->registrations.unused_timeout_secs = 30;
+
+ talloc_set_destructor(swn, swn_service_globals_destructor);
+ swn_globals = swn;
+ return NT_STATUS_OK;
+}
+
+static struct swn_service_interface *swn_service_interface_by_addr(
+ struct swn_service_globals *swn,
+ const struct samba_sockaddr *addr)
+{
+ struct swn_service_interface *iface = NULL;
+
+ for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) {
+ bool ok;
+
+ ok = sockaddr_equal(&iface->addr.u.sa, &addr->u.sa);
+ if (ok) {
+ return iface;
+ }
+ }
+
+ return NULL;
+}
+
+static void swn_service_interface_changed(struct swn_service_globals *swn,
+ struct swn_service_interface *iface)
+{
+ struct swn_service_registration *reg = NULL;
+ char addr[INET6_ADDRSTRLEN] = { 0, };
+
+ print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss);
+ DBG_NOTICE("addr[%s] state[%u] local_iface[%u] "
+ "current_vnn[%"PRIu32"] generation[%"PRIu64"][%"PRIu64"]\n",
+ addr,
+ iface->state,
+ iface->local_iface,
+ iface->current_vnn,
+ iface->change_generation,
+ iface->check_generation);
+
+ for (reg = swn->registrations.list; reg != NULL; reg = reg->next) {
+ bool match;
+
+ /*
+ * We only check the ip address,
+ * we do not make real use of the group name.
+ */
+
+ match = sockaddr_equal(&reg->ip_address.u.sa,
+ &iface->addr.u.sa);
+ if (!match) {
+ continue;
+ }
+
+ if (reg->change_notification.last_ip_state
+ != WITNESS_STATE_UNAVAILABLE)
+ {
+ /*
+ * Remember the current state unless we already
+ * hit WITNESS_STATE_UNAVAILAVLE before we notified
+ * the client
+ */
+ reg->change_notification.last_ip_state = iface->state;
+ }
+
+ reg->change_notification.triggered = true;
+
+ tevent_queue_start(reg->async_notify.queue);
+ }
+
+ return;
+}
+
+static NTSTATUS swn_service_add_or_update_interface(struct swn_service_globals *swn,
+ const char *group_name,
+ const struct samba_sockaddr *addr,
+ enum witness_interfaceInfo_state state,
+ bool local_iface,
+ uint32_t current_vnn)
+{
+ struct swn_service_interface *iface = NULL;
+ bool changed = false;
+ bool force_unavailable = false;
+ bool filter;
+
+ if (addr->u.sa.sa_family != AF_INET &&
+ addr->u.sa.sa_family != AF_INET6)
+ {
+ /*
+ * We only support ipv4 and ipv6
+ */
+ return NT_STATUS_OK;
+ }
+
+ filter = is_loopback_addr(&addr->u.sa);
+ if (filter) {
+ return NT_STATUS_OK;
+ }
+ filter = is_linklocal_addr(&addr->u.ss);
+ if (filter) {
+ return NT_STATUS_OK;
+ }
+
+ for (iface = swn->interfaces.list; iface != NULL; iface = iface->next) {
+ bool match;
+
+ match = strequal(group_name, iface->group_name);
+ if (!match) {
+ continue;
+ }
+
+ match = sockaddr_equal(&addr->u.sa, &iface->addr.u.sa);
+ if (!match) {
+ continue;
+ }
+
+ break;
+ }
+
+ if (iface == NULL) {
+ iface = talloc_zero(swn, struct swn_service_interface);
+ if (iface == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ iface->group_name = talloc_strdup(iface, group_name);
+ if (iface->group_name == NULL) {
+ TALLOC_FREE(iface);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ iface->addr = *addr;
+
+ iface->state = WITNESS_STATE_UNKNOWN;
+ iface->current_vnn = NONCLUSTER_VNN;
+ DLIST_ADD_END(swn->interfaces.list, iface);
+
+ iface->change_generation = swn->interfaces.generation;
+ }
+
+ if (iface->state != state) {
+ changed = true;
+ iface->state = state;
+ }
+
+ if (iface->current_vnn != current_vnn) {
+ changed = true;
+ if (iface->current_vnn != NONCLUSTER_VNN) {
+ force_unavailable = true;
+ }
+ iface->current_vnn = current_vnn;
+ }
+
+ if (iface->local_iface != local_iface) {
+ changed = true;
+ force_unavailable = true;
+ iface->local_iface = local_iface;
+ }
+
+ iface->check_generation = swn->interfaces.generation;
+
+ if (!changed) {
+ return NT_STATUS_OK;
+ }
+
+ iface->change_generation = swn->interfaces.generation;
+
+ if (force_unavailable) {
+ iface->state = WITNESS_STATE_UNAVAILABLE;
+ }
+
+ swn_service_interface_changed(swn, iface);
+
+ if (force_unavailable) {
+ iface->state = state;
+ }
+
+ return NT_STATUS_OK;
+};
+
+static int swn_service_ctdb_all_ip_cb(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ uint32_t pinned_vnn,
+ uint32_t current_vnn,
+ void *private_data)
+{
+ struct swn_service_globals *swn =
+ talloc_get_type_abort(private_data,
+ struct swn_service_globals);
+ enum witness_interfaceInfo_state state = WITNESS_STATE_UNKNOWN;
+ struct samba_sockaddr addr = {
+ .u = {
+ .ss = *ip,
+ },
+ };
+ NTSTATUS status;
+ bool local_iface = false;
+
+ SMB_ASSERT(swn->local_vnn != NONCLUSTER_VNN);
+
+ if (current_vnn == NONCLUSTER_VNN) {
+ /*
+ * No node hosts this address
+ */
+ state = WITNESS_STATE_UNAVAILABLE;
+ } else {
+ state = WITNESS_STATE_AVAILABLE;
+ }
+
+ if (current_vnn == swn->local_vnn || pinned_vnn == swn->local_vnn) {
+ local_iface = true;
+ }
+
+ status = swn_service_add_or_update_interface(swn,
+ swn->server_global_name,
+ &addr,
+ state,
+ local_iface,
+ current_vnn);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("swn_service_add_or_update_interface() failed: %s\n",
+ nt_errstr(status));
+ return map_errno_from_nt_status(status);
+ }
+
+ return 0;
+}
+
+static NTSTATUS swn_service_reload_interfaces(struct dcesrv_context *dce_ctx)
+{
+ struct swn_service_interface *iface = NULL;
+ struct swn_service_interface *next = NULL;
+ bool include_node_ips = false;
+ bool include_public_ips = true;
+ int ret;
+ NTSTATUS status;
+
+ status = swn_service_init_globals(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (swn_globals->interfaces.valid) {
+ return NT_STATUS_OK;
+ }
+
+ swn_globals->interfaces.generation += 1;
+
+ include_node_ips = lpcfg_parm_bool(dce_ctx->lp_ctx,
+ NULL,
+ "rpcd witness",
+ "include node ips",
+ include_node_ips);
+ include_public_ips = lpcfg_parm_bool(dce_ctx->lp_ctx,
+ NULL,
+ "rpcd witness",
+ "include public ips",
+ include_public_ips);
+
+ ret = ctdbd_all_ip_foreach(messaging_ctdb_connection(),
+ include_node_ips,
+ include_public_ips,
+ swn_service_ctdb_all_ip_cb,
+ swn_globals);
+ if (ret != 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ for (iface = swn_globals->interfaces.list; iface != NULL; iface = next) {
+ next = iface->next;
+
+ if (iface->check_generation == swn_globals->interfaces.generation) {
+ continue;
+ }
+
+ status = swn_service_add_or_update_interface(swn_globals,
+ iface->group_name,
+ &iface->addr,
+ WITNESS_STATE_UNAVAILABLE,
+ iface->local_iface,
+ iface->current_vnn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DLIST_REMOVE(swn_globals->interfaces.list, iface);
+ TALLOC_FREE(iface);
+ }
+
+ /* CTDB_SRVID_IPREALLOCATED is still registered */
+
+ swn_globals->interfaces.valid = true;
+ return NT_STATUS_OK;
+}
+
+static int swn_service_ctdb_ipreallocated(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data)
+{
+ struct swn_service_globals *swn =
+ talloc_get_type_abort(private_data,
+ struct swn_service_globals);
+ NTSTATUS status;
+
+ DBG_DEBUG("PID[%d] swn[%p] IPREALLOCATED\n", getpid(), swn);
+
+ swn->interfaces.valid = false;
+ status = swn_service_reload_interfaces(swn->dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ swn->interfaces.valid = false;
+ return 0;
+ }
+
+ return 0;
+}
+
+struct swn_dcesrv_connection {
+ struct db_context *rbt;
+ struct dcesrv_connection *conn;
+ struct samba_sockaddr cli_addr;
+ struct samba_sockaddr srv_addr;
+ char addr[INET6_ADDRSTRLEN];
+};
+
+static int swn_dcesrv_connection_release_ip(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data)
+{
+ struct swn_dcesrv_connection *sc =
+ talloc_get_type_abort(private_data,
+ struct swn_dcesrv_connection);
+ struct dcesrv_connection *conn = sc->conn;
+ const char *ip = NULL;
+ const char *addr = sc->addr;
+ const char *p = addr;
+
+ if (conn->terminate != NULL) {
+ /* avoid recursion */
+ return 0;
+ }
+
+ if (msglen == 0) {
+ return 0;
+ }
+ if (msg[msglen-1] != '\0') {
+ return 0;
+ }
+
+ ip = (const char *)msg;
+
+ if (strncmp("::ffff:", addr, 7) == 0) {
+ p = addr + 7;
+ }
+
+ DBG_DEBUG("Got release IP message for %s, our address is %s\n", ip, p);
+
+ if ((strcmp(p, ip) == 0) || ((p != addr) && strcmp(addr, ip) == 0)) {
+ DBG_NOTICE("Got release IP message for our IP %s - exiting immediately\n",
+ ip);
+ talloc_free(sc);
+ dcesrv_terminate_connection(conn, "CTDB_SRVID_RELEASE_IP");
+ return EADDRNOTAVAIL;
+ }
+
+ return 0;
+
+}
+
+static int swn_dcesrv_connection_destructor(struct swn_dcesrv_connection *sc)
+{
+ struct ctdbd_connection *cconn = messaging_ctdb_connection();
+ struct dcesrv_connection *conn = sc->conn;
+ uintptr_t conn_ptr = (uintptr_t)conn;
+ NTSTATUS status;
+ TDB_DATA key;
+
+ key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr));
+
+ status = dbwrap_delete(sc->rbt, key);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+ if (cconn == NULL) {
+ return 0;
+ }
+
+ ctdbd_unregister_ips(cconn,
+ &sc->srv_addr.u.ss,
+ &sc->cli_addr.u.ss,
+ swn_dcesrv_connection_release_ip,
+ sc);
+
+ return 0;
+}
+
+static NTSTATUS dcesrv_interface_witness_register_ips(struct dcesrv_connection *conn)
+{
+ struct ctdbd_connection *cconn = messaging_ctdb_connection();
+ struct dcesrv_context *dce_ctx = conn->dce_ctx;
+ const struct tsocket_address *client_address =
+ dcesrv_connection_get_remote_address(conn);
+ const struct tsocket_address *server_address =
+ dcesrv_connection_get_local_address(conn);
+ NTSTATUS status;
+ uintptr_t conn_ptr = (uintptr_t)conn;
+ struct swn_dcesrv_connection *sc = NULL;
+ uintptr_t sc_ptr;
+ const char *addr = NULL;
+ TDB_DATA key;
+ TDB_DATA val;
+ bool exists;
+ int ret;
+
+ status = swn_service_init_globals(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("swn_service_init_globals() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ key = make_tdb_data((uint8_t *)&conn_ptr, sizeof(conn_ptr));
+
+ exists = dbwrap_exists(swn_globals->dce_conn_register, key);
+ if (exists) {
+ /* Already registered */
+ return NT_STATUS_OK;
+ }
+
+ sc = talloc_zero(conn, struct swn_dcesrv_connection);
+ if (sc == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ sc->rbt = swn_globals->dce_conn_register;
+ sc->conn = conn;
+
+ if (tsocket_address_is_inet(client_address, "ip")) {
+ ssize_t sret;
+
+ sret = tsocket_address_bsd_sockaddr(client_address,
+ &sc->cli_addr.u.sa,
+ sizeof(sc->cli_addr.u.ss));
+ if (sret == -1) {
+ TALLOC_FREE(sc);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ sc->cli_addr.sa_socklen = sret;
+ }
+
+ if (tsocket_address_is_inet(server_address, "ip")) {
+ ssize_t sret;
+
+ sret = tsocket_address_bsd_sockaddr(server_address,
+ &sc->srv_addr.u.sa,
+ sizeof(sc->srv_addr.u.ss));
+ if (sret == -1) {
+ TALLOC_FREE(sc);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ sc->srv_addr.sa_socklen = sret;
+ }
+
+ addr = print_sockaddr(sc->addr, sizeof(sc->addr), &sc->srv_addr.u.ss);
+ if (addr == NULL) {
+ TALLOC_FREE(sc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sc_ptr = (uintptr_t)sc;
+ val = make_tdb_data((uint8_t *)&sc_ptr, sizeof(sc_ptr));
+
+ status = dbwrap_store(sc->rbt, key, val, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sc);
+ return status;
+ }
+
+ talloc_set_destructor(sc, swn_dcesrv_connection_destructor);
+
+
+ ret = ctdbd_register_ips(cconn,
+ &sc->srv_addr.u.ss,
+ &sc->cli_addr.u.ss,
+ swn_dcesrv_connection_release_ip,
+ sc);
+ if (ret != 0) {
+ TALLOC_FREE(sc);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+#define DCESRV_INTERFACE_WITNESS_BIND(context, iface) \
+ dcesrv_interface_witness_bind(context, iface)
+static NTSTATUS dcesrv_interface_witness_bind(struct dcesrv_connection_context *context,
+ const struct dcesrv_interface *iface)
+{
+ NTSTATUS status;
+
+ status = dcesrv_interface_witness_register_ips(context->conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * This is not really critical, so we just print
+ * as warning...
+ */
+ DBG_WARNING("dcesrv_interface_witness_register_ips() failed: %s\n",
+ nt_errstr(status));
+ }
+
+ /*
+ * [MS-SWN] Section 7. If the authentication level is not
+ * integrity or privacy level, Windows servers will fail the call
+ * with access denied
+ */
+ return dcesrv_interface_bind_require_integrity(context, iface);
+}
+
+/****************************************************************
+ _witness_GetInterfaceList
+****************************************************************/
+
+WERROR _witness_GetInterfaceList(struct pipes_struct *p,
+ struct witness_GetInterfaceList *r)
+{
+ struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx;
+ struct swn_service_interface *iface = NULL;
+ struct witness_interfaceList *list = NULL;
+ size_t num_interfaces = 0;
+ NTSTATUS status;
+
+ status = swn_service_reload_interfaces(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) {
+ num_interfaces += 1;
+ }
+
+ list = talloc_zero(p->mem_ctx, struct witness_interfaceList);
+ if (list == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ list->interfaces = talloc_zero_array(list,
+ struct witness_interfaceInfo,
+ num_interfaces);
+ if (list->interfaces == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (iface = swn_globals->interfaces.list; iface != NULL; iface = iface->next) {
+ struct witness_interfaceInfo *info =
+ &list->interfaces[list->num_interfaces++];
+ char addr[INET6_ADDRSTRLEN] = { 0, };
+ const char *ipv4 = "0.0.0.0";
+ const char *ipv6 = "::";
+ uint32_t flags = 0;
+
+ print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss);
+ if (iface->addr.u.sa.sa_family == AF_INET) {
+ flags |= WITNESS_INFO_IPv4_VALID;
+ ipv4 = addr;
+ } else if (iface->addr.u.sa.sa_family == AF_INET6) {
+ flags |= WITNESS_INFO_IPv6_VALID;
+ ipv6 = addr;
+ }
+
+ if (!iface->local_iface) {
+ /*
+ * If it's not a local interface
+ * it is able to serve as
+ * witness server
+ */
+ flags |= WITNESS_INFO_WITNESS_IF;
+ }
+
+ info->group_name = talloc_strdup(list, iface->group_name);
+ if (info->group_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ info->version = WITNESS_V2; /* WitnessServiceVersion; */
+ info->state = iface->state;
+ info->ipv4 = talloc_strdup(list, ipv4);
+ if (info->ipv4 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ info->ipv6 = talloc_strdup(list, ipv6);
+ if (info->ipv6 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ info->flags = flags;
+ }
+
+ *r->out.interface_list = list;
+ return WERR_OK;
+}
+
+static bool swn_server_registration_message_filter(struct messaging_rec *rec, void *private_data)
+{
+ struct swn_service_registration *reg = NULL;
+ struct policy_handle context_handle;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ bool match;
+
+ if (rec->msg_type != MSG_RPCD_WITNESS_REGISTRATION_UPDATE) {
+ return false;
+ }
+
+ if (rec->num_fds != 0) {
+ return false;
+ }
+
+ if (rec->buf.length < 20) {
+ return false;
+ }
+
+ reg = talloc_get_type_abort(private_data, struct swn_service_registration);
+
+ blob = data_blob_const(rec->buf.data, 20);
+ ndr_err = ndr_pull_struct_blob_all_noalloc(&blob, &context_handle,
+ (ndr_pull_flags_fn_t)ndr_pull_policy_handle);
+ SMB_ASSERT(NDR_ERR_CODE_IS_SUCCESS(ndr_err));
+
+ match = ndr_policy_handle_equal(&context_handle, &reg->key.handle);
+ if (!match) {
+ return false;
+ }
+
+ return true;
+}
+
+static void swn_server_registration_client_move_to_node(
+ struct swn_service_registration *reg,
+ struct rpcd_witness_registration_update_move_to_node *move)
+{
+ reg->move_notification.triggered = true;
+ reg->move_notification.new_node = move->new_node;
+ reg->move_notification.new_ip = (struct samba_sockaddr) {
+ .sa_socklen = 0,
+ };
+
+ tevent_queue_start(reg->async_notify.queue);
+}
+
+static void swn_server_registration_client_move_to_ip(
+ struct swn_service_registration *reg,
+ const char *new_ip_str)
+{
+ struct samba_sockaddr new_ip = {
+ .sa_socklen = 0,
+ };
+ bool ok;
+
+ ok = is_ipaddress(new_ip_str);
+ if (!ok) {
+ return;
+ }
+ ok = interpret_string_addr(&new_ip.u.ss,
+ new_ip_str,
+ AI_PASSIVE|AI_NUMERICHOST);
+ if (!ok) {
+ return;
+ }
+
+ reg->move_notification.triggered = true;
+ reg->move_notification.new_node = NONCLUSTER_VNN;
+ reg->move_notification.new_ip = new_ip;
+
+ tevent_queue_start(reg->async_notify.queue);
+}
+
+static void swn_server_registration_client_move_to_ipv4(
+ struct swn_service_registration *reg,
+ struct rpcd_witness_registration_update_move_to_ipv4 *move)
+{
+ swn_server_registration_client_move_to_ip(reg, move->new_ipv4);
+}
+
+static void swn_server_registration_client_move_to_ipv6(
+ struct swn_service_registration *reg,
+ struct rpcd_witness_registration_update_move_to_ipv6 *move)
+{
+ swn_server_registration_client_move_to_ip(reg, move->new_ipv6);
+}
+
+static void swn_server_registration_share_move_to_node(
+ struct swn_service_registration *reg,
+ struct rpcd_witness_registration_update_move_to_node *move)
+{
+ if (!reg->share_notification.required) {
+ return;
+ }
+
+ reg->share_notification.triggered = true;
+ reg->share_notification.new_node = move->new_node;
+ reg->share_notification.new_ip = (struct samba_sockaddr) {
+ .sa_socklen = 0,
+ };
+
+ tevent_queue_start(reg->async_notify.queue);
+}
+
+static void swn_server_registration_share_move_to_ip(
+ struct swn_service_registration *reg,
+ const char *new_ip_str)
+{
+ struct samba_sockaddr new_ip = {
+ .sa_socklen = 0,
+ };
+ bool ok;
+
+ ok = is_ipaddress(new_ip_str);
+ if (!ok) {
+ return;
+ }
+ ok = interpret_string_addr(&new_ip.u.ss,
+ new_ip_str,
+ AI_PASSIVE|AI_NUMERICHOST);
+ if (!ok) {
+ return;
+ }
+
+ if (!reg->share_notification.required) {
+ return;
+ }
+
+ reg->share_notification.triggered = true;
+ reg->share_notification.new_node = NONCLUSTER_VNN;
+ reg->share_notification.new_ip = new_ip;
+
+ tevent_queue_start(reg->async_notify.queue);
+}
+
+static void swn_server_registration_share_move_to_ipv4(
+ struct swn_service_registration *reg,
+ struct rpcd_witness_registration_update_move_to_ipv4 *move)
+{
+ swn_server_registration_share_move_to_ip(reg, move->new_ipv4);
+}
+
+static void swn_server_registration_share_move_to_ipv6(
+ struct swn_service_registration *reg,
+ struct rpcd_witness_registration_update_move_to_ipv6 *move)
+{
+ swn_server_registration_share_move_to_ip(reg, move->new_ipv6);
+}
+
+static void swn_server_registration_force_response(
+ struct swn_service_registration *reg,
+ struct rpcd_witness_registration_update_force_response *response)
+{
+ reg->forced_response.triggered = true;
+ reg->forced_response.response = talloc_move(reg, &response->response);
+ reg->forced_response.result = response->result;
+
+ tevent_queue_start(reg->async_notify.queue);
+}
+
+static void swn_server_registration_message_done(struct tevent_req *subreq)
+{
+ struct swn_service_registration *reg =
+ tevent_req_callback_data(subreq,
+ struct swn_service_registration);
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct messaging_rec *rec = NULL;
+ struct rpcd_witness_registration_updateB update_blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ int ret;
+
+ SMB_ASSERT(reg->msg.subreq == subreq);
+ reg->msg.subreq = NULL;
+
+ ret = messaging_filtered_read_recv(subreq, frame, &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(ret);
+ DBG_ERR("messaging_filtered_read_recv() - %s\n",
+ nt_errstr(status));
+ goto wait_for_next;
+ }
+
+ DBG_DEBUG("MSG_RPCD_WITNESS_REGISTRATION_UPDATE: received...\n");
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, frame, &update_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_rpcd_witness_registration_updateB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("ndr_pull_struct_blob - %s\n", nt_errstr(status));
+ goto wait_for_next;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update_blob);
+ }
+
+ switch (update_blob.type) {
+ case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE:
+ swn_server_registration_client_move_to_node(reg,
+ &update_blob.update.client_move_to_node);
+ break;
+ case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV4:
+ swn_server_registration_client_move_to_ipv4(reg,
+ &update_blob.update.client_move_to_ipv4);
+ break;
+ case RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV6:
+ swn_server_registration_client_move_to_ipv6(reg,
+ &update_blob.update.client_move_to_ipv6);
+ break;
+ case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE:
+ swn_server_registration_share_move_to_node(reg,
+ &update_blob.update.share_move_to_node);
+ break;
+ case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV4:
+ swn_server_registration_share_move_to_ipv4(reg,
+ &update_blob.update.share_move_to_ipv4);
+ break;
+ case RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV6:
+ swn_server_registration_share_move_to_ipv6(reg,
+ &update_blob.update.share_move_to_ipv6);
+ break;
+ case RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_UNREGISTER:
+ TALLOC_FREE(reg);
+ TALLOC_FREE(frame);
+ return;
+ case RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_RESPONSE:
+ swn_server_registration_force_response(reg,
+ &update_blob.update.force_response);
+ break;
+ }
+
+wait_for_next:
+ TALLOC_FREE(frame);
+ reg->msg.subreq = messaging_filtered_read_send(reg,
+ reg->msg.ev_ctx,
+ reg->msg.msg_ctx,
+ swn_server_registration_message_filter,
+ reg);
+ if (reg->msg.subreq == NULL) {
+ DBG_ERR("messaging_filtered_read_send() failed\n");
+ return;
+ }
+ tevent_req_set_callback(reg->msg.subreq,
+ swn_server_registration_message_done,
+ reg);
+}
+
+static WERROR swn_server_registration_create(struct swn_service_globals *swn,
+ struct pipes_struct *p,
+ const struct witness_RegisterEx *r,
+ const struct swn_service_interface *iface,
+ struct swn_service_registration **preg)
+{
+ struct swn_service_registration *reg = NULL;
+ const struct tsocket_address *client_address =
+ dcesrv_connection_get_remote_address(p->dce_call->conn);
+ const struct tsocket_address *server_address =
+ dcesrv_connection_get_local_address(p->dce_call->conn);
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(p->dce_call);
+ struct rpcd_witness_registration rg = { .version = 0, };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ struct GUID_txt_buf key_buf = {};
+ const char *key_str = NULL;
+ DATA_BLOB key_blob = { .length = 0, };
+ TDB_DATA key = { .dsize = 0, };
+ DATA_BLOB val_blob = { .length = 0, };
+ TDB_DATA val = { .dsize = 0, };
+
+ /*
+ * [MS-SWN] 3.1.4.5
+ * The server MUST create a WitnessRegistration entry as follows and
+ * insert it into the WitnessRegistrationList.
+ */
+ reg = talloc_zero(p->mem_ctx, struct swn_service_registration);
+ if (reg == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ reg->swn = swn;
+
+ reg->client.version = r->in.version;
+
+ /*
+ * all other string values are checked against
+ * well known expected values.
+ *
+ * So we better escape the client_computer_name
+ * if it contains strange things...
+ */
+ reg->client.computer_name = log_escape(reg, r->in.client_computer_name);
+ if (reg->client.computer_name == NULL) {
+ TALLOC_FREE(reg);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ reg->net_name = talloc_strdup(reg, r->in.net_name);
+ if (reg->net_name == NULL) {
+ TALLOC_FREE(reg);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ reg->ip_address = iface->addr;
+
+ if (r->in.share_name != NULL) {
+ reg->share_name = talloc_strdup(reg, r->in.share_name);
+ if (reg->share_name == NULL) {
+ TALLOC_FREE(reg);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ reg->share_notification.required = true;
+ }
+
+ reg->async_notify.timeout_secs = r->in.timeout;
+ reg->async_notify.queue = tevent_queue_create(reg, "async_notify");
+ if (reg->async_notify.queue == NULL) {
+ TALLOC_FREE(reg);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ tevent_queue_stop(reg->async_notify.queue);
+
+ reg->ip_notification.required = (r->in.flags &
+ WITNESS_REGISTER_IP_NOTIFICATION);
+
+ reg->usage.create_time = p->dce_call->time;
+ reg->usage.unused_timeout_secs =
+ swn_globals->registrations.unused_timeout_secs;
+ /*
+ * swn_service_registration_update_usage() below
+ * will update the timer to its real expire time!
+ */
+ reg->usage.expire_time = (struct timeval) { .tv_sec = TIME_T_MAX, };
+ reg->usage.timer = tevent_add_timer(p->dce_call->event_ctx,
+ reg,
+ reg->usage.expire_time,
+ swn_service_registration_unused,
+ reg);
+ if (reg->usage.timer == NULL) {
+ TALLOC_FREE(reg);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ swn_service_registration_update_usage(reg, reg->usage.create_time);
+
+ reg->msg.ev_ctx = p->dce_call->event_ctx;
+ reg->msg.msg_ctx = p->msg_ctx;
+ reg->msg.subreq = messaging_filtered_read_send(reg,
+ reg->msg.ev_ctx,
+ reg->msg.msg_ctx,
+ swn_server_registration_message_filter,
+ reg);
+ if (reg->msg.subreq == NULL) {
+ TALLOC_FREE(reg);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ tevent_req_set_callback(reg->msg.subreq,
+ swn_server_registration_message_done,
+ reg);
+
+ reg->key.ptr = create_policy_hnd(p, &reg->key.handle,
+ SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION,
+ reg);
+ if (reg->key.ptr == NULL) {
+ TALLOC_FREE(reg);
+ return WERR_NO_SYSTEM_RESOURCES;
+ }
+
+ DLIST_ADD_END(swn_globals->registrations.list, reg);
+ talloc_set_destructor(reg, swn_service_registration_destructor);
+
+ key_str = GUID_buf_string(&reg->key.handle.uuid, &key_buf);
+ key_blob = data_blob_string_const(key_str);
+ key = make_tdb_data(key_blob.data, key_blob.length);
+
+ rg = (struct rpcd_witness_registration) {
+ .version = r->in.version,
+ .net_name = r->in.net_name,
+ .share_name = r->in.share_name,
+ .ip_address = r->in.ip_address,
+ .client_computer_name = reg->client.computer_name,
+ .flags = r->in.flags,
+ .timeout = r->in.timeout,
+ .context_handle = reg->key.handle,
+ .server_id = messaging_server_id(p->msg_ctx),
+ .account_name = session_info->info->account_name,
+ .domain_name = session_info->info->domain_name,
+ .account_sid = session_info->security_token->sids[PRIMARY_USER_SID_INDEX],
+ .local_address = tsocket_address_string(server_address, p->mem_ctx),
+ .remote_address = tsocket_address_string(client_address, p->mem_ctx),
+ .registration_time = timeval_to_nttime(&p->dce_call->time),
+ };
+
+ ndr_err = ndr_push_struct_blob(&val_blob, p->mem_ctx, &rg,
+ (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("rpcd_witness_registration: key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(reg);
+ return WERR_NO_SYSTEM_RESOURCES;
+ }
+ val = make_tdb_data(val_blob.data, val_blob.length);
+
+ status = dbwrap_store(reg->swn->registrations.db, key, val, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("rpcd_witness_registration: key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(reg);
+ return WERR_NO_SYSTEM_RESOURCES;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("rpcd_witness_registration: key '%s' stored\n",
+ tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(rpcd_witness_registration, &rg);
+ }
+
+ *preg = reg;
+ return WERR_OK;
+}
+
+static WERROR swn_server_check_net_name(struct swn_service_globals *swn,
+ const char *net_name)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *stripped_net_name = NULL;
+ char *p = NULL;
+ bool ok;
+
+ ok = strequal(swn->server_global_name, net_name);
+ if (ok) {
+ TALLOC_FREE(frame);
+ return WERR_OK;
+ }
+
+ stripped_net_name = talloc_strdup(frame, net_name);
+ if (stripped_net_name == NULL) {
+ TALLOC_FREE(frame);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ p = strchr(stripped_net_name, '.');
+ if (p != NULL) {
+ *p = '\0';
+ }
+
+ ok = is_myname(stripped_net_name);
+ if (ok) {
+ TALLOC_FREE(frame);
+ return WERR_OK;
+ }
+
+ TALLOC_FREE(frame);
+ return WERR_INVALID_PARAMETER;
+}
+
+/****************************************************************
+ _witness_Register
+****************************************************************/
+
+WERROR _witness_Register(struct pipes_struct *p,
+ struct witness_Register *r)
+{
+ struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx;
+ struct swn_service_registration *reg = NULL;
+ struct samba_sockaddr addr = { .sa_socklen = 0, };
+ struct swn_service_interface *iface = NULL;
+ const struct witness_RegisterEx rex = {
+ .in = {
+ .version = r->in.version,
+ .net_name = r->in.net_name,
+ .ip_address = r->in.ip_address,
+ .client_computer_name = r->in.client_computer_name,
+ },
+ };
+ NTSTATUS status;
+ WERROR werr;
+ bool ok;
+
+ /*
+ * [MS-SWN] 3.1.4.2
+ * If the Version field of the request is not 0x00010001, the server
+ * MUST stop processing the request and return the error code
+ * ERROR_REVISION_MISMATCH
+ */
+ if (r->in.version != WITNESS_V1) {
+ return WERR_REVISION_MISMATCH;
+ }
+
+ /*
+ * [MS-SWN] 3.1.4.2
+ * If NetName, IpAddress or ClientComputerName is NULL, the server
+ * MUST fail the request and return the error code
+ * ERROR_INVALID_PARAMETER
+ */
+ if (r->in.net_name == NULL ||
+ r->in.ip_address == NULL ||
+ r->in.client_computer_name == NULL)
+ {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ status = swn_service_reload_interfaces(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ /*
+ * [MS-SWN] 3.1.4.2
+ * If the NetName parameter is not equal to ServerGlobalName, the
+ * server MUST fail the request and return the error code
+ * ERROR_INVALID_PARAMETER
+ */
+ werr = swn_server_check_net_name(swn_globals, r->in.net_name);
+ if (!W_ERROR_IS_OK(werr)) {
+ DBG_INFO("Invalid net_name[%s], "
+ "server_global_name[%s]: %s\n",
+ log_escape(p->mem_ctx, r->in.net_name),
+ swn_globals->server_global_name,
+ win_errstr(werr));
+ return werr;
+ }
+
+ /*
+ * [MS-SWN] 3.1.4.2
+ * The server MUST enumerate the shares by calling NetrShareEnum as
+ * specified in [MS-SRVS] section 3.1.4.8. In the enumerated list,
+ * if any of the shares has shi*_type set to STYPE_CLUSTER_SOFS, as
+ * specified in [MS-SRVS] section 2.2.2.4, the server MUST search for
+ * an Interface in InterfaceList, where Interface.IPv4Address or
+ * Interface.IPv6Address matches the IpAddress parameter based on its
+ * format. If no matching entry is found, the server MUST fail the
+ * request and return the error code ERROR_INVALID_STATE.
+ *
+ * After clarifying this point with dochelp:
+ * A server only sets the CLUSTER_SOFS, CLUSTER_FS, or CLUSTER_DFS bit
+ * flags in NetrShareEnum when the call is local and never will be set
+ * by remote calls. This point only serves the purpose of identifying
+ * the SOFS shares.
+ * The server returns the error code ERROR_INVALID_STATE if the share
+ * enumeration of SMB share resources fails with any error other than
+ * STATUS_SUCCESS. It’s not the absence of SOFS shares, or just the
+ * call to ShareEnum. When the server enumerates the shares by calling
+ * NetrShareEnum locally, it tries to filter out only shares with
+ * STYPE_CLUSTER_SOFS. The scope of 'If no matching entry is found'
+ * is broader. Even if shares have STYPE_CLUSTER_SOFS, but no match
+ * could be found with the IpAddress, ERROR_INVALID_STATE will be
+ * returned too.
+ *
+ * In a CTDB cluster, all shares in the clustered filesystem are
+ * scale-out. We can skip this check and proceed to find the matching
+ * IP address.
+ */
+ ok = is_ipaddress(r->in.ip_address);
+ if (!ok) {
+ DBG_INFO("Invalid ip_address[%s]\n",
+ log_escape(p->mem_ctx, r->in.ip_address));
+ return WERR_INVALID_STATE;
+ }
+ ok = interpret_string_addr(&addr.u.ss,
+ r->in.ip_address,
+ AI_PASSIVE|AI_NUMERICHOST);
+ if (!ok) {
+ DBG_INFO("Invalid ip_address[%s]\n",
+ log_escape(p->mem_ctx, r->in.ip_address));
+ return WERR_INVALID_STATE;
+ }
+ iface = swn_service_interface_by_addr(swn_globals, &addr);
+ if (iface == NULL) {
+ DBG_INFO("Invalid ip_address[%s]\n",
+ log_escape(p->mem_ctx, r->in.ip_address));
+ return WERR_INVALID_STATE;
+ }
+
+ werr = swn_server_registration_create(swn_globals, p, &rex, iface, &reg);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ *r->out.context_handle = reg->key.handle;
+ return WERR_OK;
+}
+
+
+/****************************************************************
+ _witness_UnRegister
+****************************************************************/
+
+WERROR _witness_UnRegister(struct pipes_struct *p,
+ struct witness_UnRegister *r)
+{
+ bool ok;
+
+ /*
+ * [MS-SWN] 3.1.4.3
+ * The server MUST search for the WitnessRegistration in
+ * WitnessRegistrationList, where WitnessRegistration.RegistrationKey
+ * matches the pContext parameter. If no matching entry is found,
+ * the server SHOULD<4> stop processing the request and return the
+ * error code ERROR_NOT_FOUND.
+ */
+ ok = close_policy_hnd(p, &r->in.context_handle);
+ if (!ok) {
+ if (p->fault_state != 0) {
+ p->fault_state = 0;
+ }
+ return WERR_NOT_FOUND;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************
+ _witness_AsyncNotify
+****************************************************************/
+
+struct swn_service_async_notify_state {
+ struct swn_service_async_notify_state *prev, *next;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ TALLOC_CTX *r_mem_ctx;
+ struct witness_AsyncNotify *r;
+ struct swn_service_registration *reg;
+ struct tevent_queue_entry *qe;
+};
+
+static void swn_service_async_notify_trigger(struct tevent_req *req,
+ void *private_data);
+
+static void swn_service_async_notify_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct swn_service_async_notify_state *state =
+ tevent_req_data(req,
+ struct swn_service_async_notify_state);
+
+ TALLOC_FREE(state->qe);
+
+ if (state->reg != NULL) {
+ DLIST_REMOVE(state->reg->async_notify.list, state);
+ state->reg = NULL;
+ }
+}
+
+static void swn_service_async_notify_reg_destroyed(struct swn_service_async_notify_state *state)
+{
+ swn_service_async_notify_cleanup(state->req, TEVENT_REQ_USER_ERROR);
+ swn_service_async_notify_trigger(state->req, NULL);
+}
+
+static bool swn_service_async_notify_cancel(struct tevent_req *req)
+{
+ return false;
+}
+
+static struct tevent_req *swn_service_async_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ TALLOC_CTX *r_mem_ctx,
+ struct witness_AsyncNotify *r,
+ struct swn_service_registration *reg)
+{
+ struct tevent_req *req = NULL;
+ struct swn_service_async_notify_state *state = NULL;
+ struct timeval now = timeval_current();
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct swn_service_async_notify_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->req = req;
+ state->r_mem_ctx = r_mem_ctx;
+ state->r = r;
+ state->reg = reg;
+
+ /*
+ * triggered changes likely wakeup
+ * more than one waiter, so we better
+ * let all individual waiters go through
+ * a tevent_immediate round.
+ */
+ tevent_req_defer_callback(req, ev);
+
+ tevent_req_set_cleanup_fn(req, swn_service_async_notify_cleanup);
+ tevent_req_set_cancel_fn(req, swn_service_async_notify_cancel);
+
+ if (!reg->forced_response.triggered &&
+ !reg->change_notification.triggered &&
+ !reg->move_notification.triggered &&
+ !reg->share_notification.triggered &&
+ !reg->ip_notification.triggered)
+ {
+ tevent_queue_stop(reg->async_notify.queue);
+ }
+
+ DLIST_ADD_END(reg->async_notify.list, state);
+
+ state->qe = tevent_queue_add_entry(reg->async_notify.queue, ev, req,
+ swn_service_async_notify_trigger,
+ NULL)
+ if (tevent_req_nomem(state->qe, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (reg->async_notify.timeout_secs != 0) {
+ struct timeval endtime;
+ bool ok;
+
+ endtime = timeval_add(&now, reg->async_notify.timeout_secs, 0);
+ ok = tevent_req_set_endtime(req, ev, endtime);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ /*
+ * Once we added the queue entry
+ * swn_service_registration_update_usage()
+ * will adjust the registration expire time...
+ */
+ swn_service_registration_update_usage(state->reg, now);
+
+ /*
+ * Wait for trigger or timeout...
+ */
+ return req;
+}
+
+static void swn_service_async_notify_trigger(struct tevent_req *req,
+ void *private_data)
+{
+ struct swn_service_async_notify_state *state =
+ tevent_req_data(req,
+ struct swn_service_async_notify_state);
+ struct swn_service_registration *reg = state->reg;
+ struct witness_notifyResponse *resp = NULL;
+ WERROR forced_result = WERR_OK;
+ bool defer_forced_unregister = false;
+
+ if (reg == NULL) {
+ tevent_req_werror(req, WERR_NOT_FOUND);
+ return;
+ }
+
+ if (reg->forced_response.triggered) {
+ resp = talloc_move(state, &reg->forced_response.response);
+ forced_result = reg->forced_response.result;
+
+ reg->forced_response.triggered = false;
+ reg->forced_response.result = WERR_OK;
+ goto finished;
+ }
+
+ if (reg->change_notification.triggered) {
+ struct swn_service_globals *swn = reg->swn;
+ const struct swn_service_interface *iface = NULL;
+ union witness_notifyResponse_message *msgs = NULL;
+ char reg_ip[INET6_ADDRSTRLEN] = { 0, };
+ struct witness_ResourceChange *rc = NULL;
+ enum witness_interfaceInfo_state cur_state;
+
+ print_sockaddr(reg_ip, sizeof(reg_ip), &reg->ip_address.u.ss);
+
+ iface = swn_service_interface_by_addr(swn, &reg->ip_address);
+ if (iface != NULL) {
+ cur_state = iface->state;
+ } else {
+ /*
+ * If the interface is no longer in our list
+ * it must be unavailable
+ */
+ cur_state = WITNESS_STATE_UNAVAILABLE;
+ }
+ if (cur_state != WITNESS_STATE_AVAILABLE) {
+ reg->change_notification.last_ip_state = cur_state;
+ }
+
+ resp = talloc_zero(state, struct witness_notifyResponse);
+ if (tevent_req_nomem(resp, req)) {
+ return;
+ }
+
+ msgs = talloc_zero_array(resp,
+ union witness_notifyResponse_message,
+ 1);
+ if (tevent_req_nomem(msgs, req)) {
+ return;
+ }
+
+ resp->type = WITNESS_NOTIFY_RESOURCE_CHANGE;
+ resp->num = 0;
+ resp->messages = msgs;
+
+ rc = &msgs[resp->num].resource_change;
+
+ switch (reg->change_notification.last_ip_state) {
+ case WITNESS_STATE_AVAILABLE:
+ rc->type = WITNESS_RESOURCE_STATE_AVAILABLE;
+ break;
+ case WITNESS_STATE_UNAVAILABLE:
+ rc->type = WITNESS_RESOURCE_STATE_UNAVAILABLE;
+ break;
+ case WITNESS_STATE_UNKNOWN:
+ rc->type = WITNESS_RESOURCE_STATE_UNKNOWN;
+ break;
+ }
+
+ /*
+ * We use the ip address as resource name
+ */
+ rc->name = talloc_strdup(msgs, reg_ip);
+ if (tevent_req_nomem(rc->name, req)) {
+ return;
+ }
+
+ resp->num += 1;
+
+ if (rc->type != WITNESS_RESOURCE_STATE_AVAILABLE) {
+ /*
+ * In order to let a Windows server 2022
+ * correctly re-register after moving
+ * to a new connection, we force an
+ * unregistration after 5 seconds.
+ *
+ * It means the client gets WERR_NOT_FOUND
+ * from a pending AsyncNotify() and calls
+ * Unregister() (which also gets WERR_NOT_FOUND).
+ * Then the client calls GetInterfaceList()
+ * and RegisterEx() again.
+ */
+ defer_forced_unregister = true;
+ }
+
+ if (reg->change_notification.last_ip_state != cur_state) {
+ /*
+ * This means the last_ip_state was *not* available,
+ * and the current_state *is* available.
+ *
+ * keep the queue running and return the available
+ * message in the next run
+ */
+ reg->change_notification.last_ip_state = cur_state;
+ goto finished;
+ }
+
+ reg->change_notification.triggered = false;
+ reg->change_notification.last_ip_state = WITNESS_STATE_UNKNOWN;
+ goto finished;
+ }
+
+ if (reg->move_notification.triggered) {
+ struct swn_service_globals *swn = reg->swn;
+ struct swn_service_interface *iface = NULL;
+ union witness_notifyResponse_message *msgs = NULL;
+ struct witness_IPaddrInfoList *list = NULL;
+ uint32_t num_ips = 0;
+ const uint32_t *new_node = NULL;
+ const struct samba_sockaddr *new_ip = NULL;
+
+ if (reg->move_notification.new_node != NONCLUSTER_VNN) {
+ new_node = &reg->move_notification.new_node;
+ }
+ if (!is_zero_addr(&reg->move_notification.new_ip.u.ss)) {
+ new_ip = &reg->move_notification.new_ip;
+ }
+
+ for (iface = swn->interfaces.list;
+ iface != NULL;
+ iface = iface->next)
+ {
+ if (new_node != NULL &&
+ iface->current_vnn != *new_node)
+ {
+ continue;
+ }
+
+ if (new_ip != NULL &&
+ !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa))
+ {
+ continue;
+ }
+
+ num_ips += 1;
+ }
+
+ if (num_ips == 0) {
+ goto no_moves;
+ }
+
+ resp = talloc_zero(state, struct witness_notifyResponse);
+ if (tevent_req_nomem(resp, req)) {
+ return;
+ }
+
+ msgs = talloc_zero_array(resp,
+ union witness_notifyResponse_message,
+ 1);
+ if (tevent_req_nomem(msgs, req)) {
+ return;
+ }
+
+ list = &msgs[0].client_move;
+ list->addr = talloc_zero_array(msgs,
+ struct witness_IPaddrInfo,
+ num_ips);
+ if (tevent_req_nomem(list->addr, req)) {
+ return;
+ }
+
+ for (iface = swn->interfaces.list;
+ iface != NULL;
+ iface = iface->next)
+ {
+ struct witness_IPaddrInfo *info = &list->addr[list->num];
+ char addr[INET6_ADDRSTRLEN] = { 0, };
+ const char *ipv4 = "0.0.0.0";
+ const char *ipv6 = "::";
+ uint32_t flags = 0;
+ bool is_reg_ip = false;
+
+ if (new_node != NULL &&
+ iface->current_vnn != *new_node)
+ {
+ continue;
+ }
+
+ if (new_ip != NULL &&
+ !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa))
+ {
+ continue;
+ }
+
+ switch (iface->state) {
+ case WITNESS_STATE_AVAILABLE:
+ flags |= WITNESS_IPADDR_ONLINE;
+ break;
+ case WITNESS_STATE_UNAVAILABLE:
+ flags |= WITNESS_IPADDR_OFFLINE;
+ break;
+ case WITNESS_STATE_UNKNOWN:
+ /* We map unknown also to offline */
+ flags |= WITNESS_IPADDR_OFFLINE;
+ break;
+ }
+
+ print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss);
+ if (iface->addr.u.sa.sa_family == AF_INET) {
+ flags |= WITNESS_IPADDR_V4;
+ ipv4 = addr;
+ } else if (iface->addr.u.sa.sa_family == AF_INET6) {
+ flags |= WITNESS_IPADDR_V6;
+ ipv6 = addr;
+ }
+
+ info->ipv4 = talloc_strdup(list, ipv4);
+ if (tevent_req_nomem(info->ipv4, req)) {
+ return;
+ }
+ info->ipv6 = talloc_strdup(list, ipv6);
+ if (tevent_req_nomem(info->ipv6, req)) {
+ return;
+ }
+ info->flags = flags;
+ list->num += 1;
+
+ is_reg_ip = sockaddr_equal(&reg->ip_address.u.sa,
+ &iface->addr.u.sa);
+ if (!is_reg_ip) {
+ /*
+ * In order to let a Windows server 2022
+ * correctly re-register after moving
+ * to a new connection, we force an
+ * unregistration after 5 seconds.
+ *
+ * It means the client gets WERR_NOT_FOUND from
+ * a pending AsyncNotify() and calls
+ * Unregister() (which also gets
+ * WERR_NOT_FOUND). Then the client calls
+ * GetInterfaceList() and RegisterEx() again.
+ */
+ defer_forced_unregister = true;
+ }
+ }
+
+ resp->type = WITNESS_NOTIFY_CLIENT_MOVE;
+ resp->num = talloc_array_length(msgs);
+ resp->messages = msgs;
+
+no_moves:
+ reg->move_notification.triggered = false;
+ if (resp != NULL) {
+ goto finished;
+ }
+ }
+
+ if (reg->share_notification.triggered) {
+ struct swn_service_globals *swn = reg->swn;
+ struct swn_service_interface *iface = NULL;
+ union witness_notifyResponse_message *msgs = NULL;
+ struct witness_IPaddrInfoList *list = NULL;
+ uint32_t num_ips = 0;
+ const uint32_t *new_node = NULL;
+ const struct samba_sockaddr *new_ip = NULL;
+
+ if (reg->share_notification.new_node != NONCLUSTER_VNN) {
+ new_node = &reg->share_notification.new_node;
+ }
+ if (!is_zero_addr(&reg->share_notification.new_ip.u.ss)) {
+ new_ip = &reg->share_notification.new_ip;
+ }
+
+ for (iface = swn->interfaces.list;
+ iface != NULL;
+ iface = iface->next)
+ {
+ if (new_node != NULL &&
+ iface->current_vnn != *new_node)
+ {
+ continue;
+ }
+
+ if (new_ip != NULL &&
+ !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa))
+ {
+ continue;
+ }
+
+ num_ips += 1;
+ }
+
+ if (num_ips == 0) {
+ goto no_share_moves;
+ }
+
+ resp = talloc_zero(state, struct witness_notifyResponse);
+ if (tevent_req_nomem(resp, req)) {
+ return;
+ }
+
+ msgs = talloc_zero_array(resp,
+ union witness_notifyResponse_message,
+ 1);
+ if (tevent_req_nomem(msgs, req)) {
+ return;
+ }
+
+ list = &msgs[0].client_move;
+ list->addr = talloc_zero_array(msgs,
+ struct witness_IPaddrInfo,
+ num_ips);
+ if (tevent_req_nomem(list->addr, req)) {
+ return;
+ }
+
+ for (iface = swn->interfaces.list;
+ iface != NULL;
+ iface = iface->next)
+ {
+ struct witness_IPaddrInfo *info = &list->addr[list->num];
+ char addr[INET6_ADDRSTRLEN] = { 0, };
+ const char *ipv4 = "0.0.0.0";
+ const char *ipv6 = "::";
+ uint32_t flags = 0;
+ bool is_reg_ip = false;
+
+ if (new_node != NULL &&
+ iface->current_vnn != *new_node)
+ {
+ continue;
+ }
+
+ if (new_ip != NULL &&
+ !sockaddr_equal(&new_ip->u.sa, &iface->addr.u.sa))
+ {
+ continue;
+ }
+
+ switch (iface->state) {
+ case WITNESS_STATE_AVAILABLE:
+ flags |= WITNESS_IPADDR_ONLINE;
+ break;
+ case WITNESS_STATE_UNAVAILABLE:
+ flags |= WITNESS_IPADDR_OFFLINE;
+ break;
+ case WITNESS_STATE_UNKNOWN:
+ /* We map unknown also to offline */
+ flags |= WITNESS_IPADDR_OFFLINE;
+ break;
+ }
+
+ print_sockaddr(addr, sizeof(addr), &iface->addr.u.ss);
+ if (iface->addr.u.sa.sa_family == AF_INET) {
+ flags |= WITNESS_IPADDR_V4;
+ ipv4 = addr;
+ } else if (iface->addr.u.sa.sa_family == AF_INET6) {
+ flags |= WITNESS_IPADDR_V6;
+ ipv6 = addr;
+ }
+
+ info->ipv4 = talloc_strdup(list, ipv4);
+ if (tevent_req_nomem(info->ipv4, req)) {
+ return;
+ }
+ info->ipv6 = talloc_strdup(list, ipv6);
+ if (tevent_req_nomem(info->ipv6, req)) {
+ return;
+ }
+ info->flags = flags;
+ list->num += 1;
+
+ is_reg_ip = sockaddr_equal(&reg->ip_address.u.sa,
+ &iface->addr.u.sa);
+ if (!is_reg_ip) {
+ /*
+ * In order to let a Windows server 2022
+ * correctly re-register after moving
+ * to a new connection, we force an
+ * unregistration after 5 seconds.
+ *
+ * It means the client gets WERR_NOT_FOUND from
+ * a pending AsyncNotify() and calls
+ * Unregister() (which also gets
+ * WERR_NOT_FOUND). Then the client calls
+ * GetInterfaceList() and RegisterEx() again.
+ */
+ defer_forced_unregister = true;
+ }
+ }
+
+ resp->type = WITNESS_NOTIFY_SHARE_MOVE;
+ resp->num = talloc_array_length(msgs);
+ resp->messages = msgs;
+
+no_share_moves:
+ reg->share_notification.triggered = false;
+ if (resp != NULL) {
+ goto finished;
+ }
+ }
+
+finished:
+ if (!reg->forced_response.triggered &&
+ !reg->change_notification.triggered &&
+ !reg->move_notification.triggered &&
+ !reg->share_notification.triggered &&
+ !reg->ip_notification.triggered)
+ {
+ tevent_queue_stop(reg->async_notify.queue);
+ }
+
+ if (defer_forced_unregister) {
+ struct tevent_timer *te = NULL;
+
+ /*
+ * In order to let a Windows server 2022
+ * correctly re-register after moving
+ * to a new connection, we force an
+ * unregistration after 5 seconds.
+ *
+ * It means the client gets WERR_NOT_FOUND
+ * from a pending AsyncNotify() and calls
+ * Unregister() (which also gets WERR_NOT_FOUND).
+ * Then the client calls GetInterfaceList()
+ * and RegisterEx() again.
+ */
+ TALLOC_FREE(reg->forced_unregister.timer);
+ te = tevent_add_timer(state->ev,
+ reg,
+ timeval_current_ofs(5,0),
+ swn_service_registration_force_unregister,
+ reg);
+ if (tevent_req_nomem(te, req)) {
+ return;
+ }
+ reg->forced_unregister.timer = te;
+ }
+
+ *state->r->out.response = talloc_move(state->r_mem_ctx, &resp);
+ state->r->out.result = forced_result;
+ if (!W_ERROR_IS_OK(forced_result)) {
+ tevent_req_werror(req, forced_result);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static WERROR swn_service_async_notify_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_werror(req);
+}
+
+struct _witness_AsyncNotify_state {
+ struct dcesrv_call_state *dce_call;
+ struct witness_AsyncNotify *r;
+ struct swn_service_registration *reg;
+ struct tevent_req *subreq;
+};
+
+static bool _witness_AsyncNotify_cancel(struct tevent_req *req);
+static void _witness_AsyncNotify_done(struct tevent_req *subreq);
+
+WERROR _witness_AsyncNotify(struct pipes_struct *p,
+ struct witness_AsyncNotify *r)
+{
+ struct tevent_req *req = NULL;
+ struct _witness_AsyncNotify_state *state = NULL;
+ struct swn_service_registration *reg = NULL;
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+
+ /*
+ * [MS-SWN] 3.1.4.4
+ * The server MUST search for the WitnessRegistration in
+ * WitnessRegistrationList, where WitnessRegistration.RegistrationKey
+ * matches the pContext parameter. If no matching entry is found, the
+ * server MUST fail the request and return the error code
+ * ERROR_NOT_FOUND.
+ */
+ reg = find_policy_by_hnd(p, &r->in.context_handle,
+ SWN_SERVICE_CONTEXT_HANDLE_REGISTRATION,
+ struct swn_service_registration,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (p->fault_state != 0) {
+ p->fault_state = 0;
+ }
+ return WERR_NOT_FOUND;
+ }
+
+ swn_service_registration_update_usage(reg, p->dce_call->time);
+
+ req = tevent_req_create(p->mem_ctx, &state,
+ struct _witness_AsyncNotify_state);
+ if (req == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ state->dce_call = p->dce_call;
+ state->r = r;
+ state->reg = reg;
+
+ tevent_req_set_cancel_fn(req, _witness_AsyncNotify_cancel);
+
+ state->subreq = swn_service_async_notify_send(state,
+ state->dce_call->event_ctx,
+ state->dce_call,
+ state->r,
+ state->reg);
+ if (state->subreq == NULL) {
+ TALLOC_FREE(state);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ tevent_req_set_callback(state->subreq,
+ _witness_AsyncNotify_done,
+ req);
+
+ state->dce_call->subreq = req;
+ state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC;
+ return WERR_EVENT_PENDING; /* hidden by DCESRV_CALL_STATE_FLAG_ASYNC */
+}
+
+static bool _witness_AsyncNotify_cancel(struct tevent_req *req)
+{
+ struct _witness_AsyncNotify_state *state =
+ tevent_req_data(req,
+ struct _witness_AsyncNotify_state);
+ struct dcesrv_call_state *dce_call = state->dce_call;
+
+ SMB_ASSERT(dce_call->subreq == req);
+ dce_call->subreq = NULL;
+
+ TALLOC_FREE(state->subreq);
+
+ if (dce_call->got_orphaned) {
+ dce_call->fault_code = DCERPC_FAULT_SERVER_UNAVAILABLE;
+ } else {
+ dce_call->fault_code = DCERPC_NCA_S_FAULT_CANCEL;
+ }
+ state->r->out.result = WERR_RPC_S_CALL_CANCELLED;
+
+ dcesrv_async_reply(dce_call);
+ return true;
+}
+
+static void _witness_AsyncNotify_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct _witness_AsyncNotify_state *state =
+ tevent_req_data(req,
+ struct _witness_AsyncNotify_state);
+ struct dcesrv_call_state *dce_call = state->dce_call;
+
+ SMB_ASSERT(dce_call->subreq == req);
+ dce_call->subreq = NULL;
+
+ SMB_ASSERT(state->subreq == subreq);
+ state->subreq = NULL;
+
+ state->r->out.result = swn_service_async_notify_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (W_ERROR_EQUAL(state->r->out.result, WERR_NOT_FOUND)) {
+ state->reg = NULL;
+ }
+
+ if (state->reg != NULL &&
+ tevent_queue_length(state->reg->async_notify.queue) == 0)
+ {
+ struct timeval now = timeval_current();
+ swn_service_registration_update_usage(state->reg, now);
+ }
+
+ dcesrv_async_reply(dce_call);
+}
+
+/****************************************************************
+ _witness_RegisterEx
+****************************************************************/
+
+WERROR _witness_RegisterEx(struct pipes_struct *p,
+ struct witness_RegisterEx *r)
+{
+ struct dcesrv_context *dce_ctx = p->dce_call->conn->dce_ctx;
+ struct swn_service_registration *reg = NULL;
+ struct samba_sockaddr addr = { .sa_socklen = 0, };
+ struct swn_service_interface *iface = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ bool ok;
+
+ /*
+ * [MS-SWN] 3.1.4.5
+ * If the Version field of the request is not 0x00020000, the server
+ * MUST stop processing the request and return the error code
+ * ERROR_REVISION_MISMATCH
+ */
+ if (r->in.version != WITNESS_V2) {
+ return WERR_REVISION_MISMATCH;
+ }
+
+ /*
+ * [MS-SWN] 3.1.4.5
+ * If NetName, IpAddress or ClientComputerName is NULL, the server
+ * MUST fail the request and return the error code
+ * ERROR_INVALID_PARAMETER
+ */
+ if (r->in.net_name == NULL ||
+ r->in.ip_address == NULL ||
+ r->in.client_computer_name == NULL)
+ {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ status = swn_service_reload_interfaces(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ /*
+ * [MS-SWN] 3.1.4.5
+ * If the NetName parameter is not equal to ServerGlobalName, the
+ * server MUST fail the request and return the error code
+ * ERROR_INVALID_PARAMETER
+ */
+ werr = swn_server_check_net_name(swn_globals, r->in.net_name);
+ if (!W_ERROR_IS_OK(werr)) {
+ DBG_INFO("Invalid net_name[%s], "
+ "server_global_name[%s]: %s\n",
+ log_escape(p->mem_ctx, r->in.net_name),
+ swn_globals->server_global_name,
+ win_errstr(werr));
+ return werr;
+ }
+
+ /*
+ * [MS-SWN] 3.1.4.5
+ * If ShareName is not NULL, the server MUST enumerate the shares by
+ * calling NetrShareEnum as specified in [MS-SRVS] section 3.1.4.8.
+ * If the enumeration fails or if no shares are returned, the server
+ * MUST return the error code ERROR_INVALID_STATE.
+ *
+ * If none of the shares in the list has shi*_type set to
+ * STYPE_CLUSTER_SOFS as specified in [MS-SRVS] section 3.1.4.8,
+ * the server MUST ignore ShareName.
+ *
+ * In a CTDB cluster, all shares in the clustered filesystem are
+ * scale-out. Check if the provided share name is in a clustered FS
+ */
+ if (r->in.share_name != NULL) {
+ char *save_share = NULL;
+ int cmp;
+
+ /*
+ * For now we allow all shares...
+ *
+ * The main reason is that windows
+ * clients typically connect as
+ * machine account, so things like %U
+ * wouldn't work anyway.
+ *
+ * And in the end it's just a string,
+ * so we just check it's sane.
+ */
+ save_share = log_escape(p->mem_ctx, r->in.share_name);
+ if (save_share == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ cmp = strcmp(save_share, r->in.share_name);
+ if (cmp != 0) {
+ DBG_INFO("Invalid share_name[%s]\n",
+ save_share);
+ return WERR_INVALID_STATE;
+ }
+ TALLOC_FREE(save_share);
+ }
+
+ /*
+ * [MS-SWN] 3.1.4.5
+ * The server MUST search for an Interface in InterfaceList, where
+ * Interface.IPv4Address or Interface.IPv6Address matches the
+ * IpAddress parameter based on its format. If no matching entry is
+ * found, the server MUST fail the request and return the error code
+ * ERROR_INVALID_STATE.
+ */
+ ok = is_ipaddress(r->in.ip_address);
+ if (!ok) {
+ DBG_INFO("Invalid ip_address[%s]\n",
+ log_escape(p->mem_ctx, r->in.ip_address));
+ return WERR_INVALID_STATE;
+ }
+ ok = interpret_string_addr(&addr.u.ss,
+ r->in.ip_address,
+ AI_PASSIVE|AI_NUMERICHOST);
+ if (!ok) {
+ DBG_INFO("Invalid ip_address[%s]\n",
+ log_escape(p->mem_ctx, r->in.ip_address));
+ return WERR_INVALID_STATE;
+ }
+ iface = swn_service_interface_by_addr(swn_globals, &addr);
+ if (iface == NULL) {
+ DBG_INFO("Invalid ip_address[%s]\n",
+ log_escape(p->mem_ctx, r->in.ip_address));
+ return WERR_INVALID_STATE;
+ }
+
+ werr = swn_server_registration_create(swn_globals, p, r, iface, &reg);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ *r->out.context_handle = reg->key.handle;
+ return WERR_OK;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_witness_scompat.c"
diff --git a/source3/rpc_server/wkssvc/srv_wkssvc_nt.c b/source3/rpc_server/wkssvc/srv_wkssvc_nt.c
new file mode 100644
index 0000000..0724dd0
--- /dev/null
+++ b/source3/rpc_server/wkssvc/srv_wkssvc_nt.c
@@ -0,0 +1,964 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ *
+ * Copyright (C) Andrew Tridgell 1992-1997,
+ * Copyright (C) Gerald (Jerry) Carter 2006.
+ * Copyright (C) Guenther Deschner 2007-2008.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* This is the implementation of the wks interface. */
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/libnet_join.h"
+#include "libnet/libnet_join.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "librpc/gen_ndr/ndr_wkssvc.h"
+#include "librpc/gen_ndr/ndr_wkssvc_scompat.h"
+#include "../libcli/security/security.h"
+#include "session.h"
+#include "smbd/smbd.h"
+#include "auth.h"
+#include "krb5_env.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_RPC_SRV
+
+struct dom_usr {
+ char *name;
+ char *domain;
+ time_t login_time;
+};
+
+static int dom_user_cmp(const struct dom_usr *usr1, const struct dom_usr *usr2)
+{
+ /* Called from qsort to compare two domain users in a dom_usr_t array
+ * for sorting by login time. Return >0 if usr1 login time was later
+ * than usr2 login time, <0 if it was earlier */
+ return (usr1->login_time - usr2->login_time);
+}
+
+/*******************************************************************
+ Get a list of the names of all users of this machine who are
+ logged into the domain.
+
+ This should return a list of the users on this machine who are
+ logged into the domain (i.e. have been authenticated by the domain's
+ password server) but that doesn't fit well with the normal Samba
+ scenario where accesses out to the domain are made through smbclient
+ with each such session individually authenticated. So about the best
+ we can do currently is to list sessions of local users connected to
+ this server, which means that to get themself included in the list a
+ local user must create a session to the local samba server by running:
+ smbclient \\\\localhost\\share
+
+ FIXME: find a better way to get local users logged into the domain
+ in this list.
+ ********************************************************************/
+
+static int get_domain_userlist(TALLOC_CTX *mem_ctx, struct dom_usr **pusers)
+{
+ struct sessionid *session_list = NULL;
+ char *machine_name, *p, *nm;
+ const char *sep;
+ struct dom_usr *users, *tmp;
+ int i, num_users, num_sessions;
+
+ sep = lp_winbind_separator();
+ if (!sep) {
+ sep = "\\";
+ }
+
+ num_sessions = list_sessions(mem_ctx, &session_list);
+ if (num_sessions == 0) {
+ *pusers = NULL;
+ return 0;
+ }
+
+ users = talloc_array(mem_ctx, struct dom_usr, num_sessions);
+ if (users == NULL) {
+ TALLOC_FREE(session_list);
+ return ENOMEM;
+ }
+
+ for (i=num_users=0; i<num_sessions; i++) {
+ if (session_list[i].username[0] == '\0' ||
+ session_list[i].remote_machine[0] == '\0') {
+ continue;
+ }
+ p = strpbrk(session_list[i].remote_machine, "./");
+ if (p) {
+ *p = '\0';
+ }
+ machine_name = talloc_asprintf_strupper_m(
+ users, "%s", session_list[i].remote_machine);
+ if (machine_name == NULL) {
+ DEBUG(10, ("talloc_asprintf failed\n"));
+ continue;
+ }
+ if (strcmp(machine_name, lp_netbios_name()) == 0) {
+ p = session_list[i].username;
+ nm = strstr(p, sep);
+ if (nm) {
+ /*
+ * "domain+name" format so split domain and
+ * name components
+ */
+ *nm = '\0';
+ nm += strlen(sep);
+ users[num_users].domain =
+ talloc_asprintf_strupper_m(users,
+ "%s", p);
+ users[num_users].name = talloc_strdup(users,
+ nm);
+ } else {
+ /*
+ * Simple user name so get domain from smb.conf
+ */
+ users[num_users].domain =
+ talloc_strdup(users, lp_workgroup());
+ users[num_users].name = talloc_strdup(users,
+ p);
+ }
+ users[num_users].login_time =
+ session_list[i].connect_start;
+ num_users++;
+ }
+ TALLOC_FREE(machine_name);
+ }
+ TALLOC_FREE(session_list);
+
+ if (num_users == 0) {
+ TALLOC_FREE(users);
+ *pusers = NULL;
+ return 0;
+ }
+
+ tmp = talloc_realloc(mem_ctx, users, struct dom_usr, num_users);
+ if (tmp == NULL) {
+ TALLOC_FREE(users);
+ return ENOMEM;
+ }
+ users = tmp;
+
+ /* Sort the user list by time, oldest first */
+ TYPESAFE_QSORT(users, num_users, dom_user_cmp);
+
+ *pusers = users;
+ return 0;
+}
+
+/*******************************************************************
+ RPC Workstation Service request NetWkstaGetInfo with level 100.
+ Returns to the requester:
+ - The machine name.
+ - The smb version number
+ - The domain name.
+ Returns a filled in wkssvc_NetWkstaInfo100 struct.
+ ********************************************************************/
+
+static struct wkssvc_NetWkstaInfo100 *create_wks_info_100(TALLOC_CTX *mem_ctx)
+{
+ struct wkssvc_NetWkstaInfo100 *info100;
+
+ info100 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo100);
+ if (info100 == NULL) {
+ return NULL;
+ }
+
+ info100->platform_id = PLATFORM_ID_NT; /* unknown */
+ info100->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION;
+ info100->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION;
+
+ info100->server_name = talloc_asprintf_strupper_m(
+ info100, "%s", lp_netbios_name());
+ info100->domain_name = talloc_asprintf_strupper_m(
+ info100, "%s", lp_workgroup());
+
+ return info100;
+}
+
+/*******************************************************************
+ RPC Workstation Service request NetWkstaGetInfo with level 101.
+ Returns to the requester:
+ - As per NetWkstaGetInfo with level 100, plus:
+ - The LANMAN directory path (not currently supported).
+ Returns a filled in wkssvc_NetWkstaInfo101 struct.
+ ********************************************************************/
+
+static struct wkssvc_NetWkstaInfo101 *create_wks_info_101(TALLOC_CTX *mem_ctx)
+{
+ struct wkssvc_NetWkstaInfo101 *info101;
+
+ info101 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo101);
+ if (info101 == NULL) {
+ return NULL;
+ }
+
+ info101->platform_id = PLATFORM_ID_NT; /* unknown */
+ info101->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION;
+ info101->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION;
+
+ info101->server_name = talloc_asprintf_strupper_m(
+ info101, "%s", lp_netbios_name());
+ info101->domain_name = talloc_asprintf_strupper_m(
+ info101, "%s", lp_workgroup());
+ info101->lan_root = "";
+
+ return info101;
+}
+
+/*******************************************************************
+ RPC Workstation Service request NetWkstaGetInfo with level 102.
+ Returns to the requester:
+ - As per NetWkstaGetInfo with level 101, plus:
+ - The number of logged in users.
+ Returns a filled in wkssvc_NetWkstaInfo102 struct.
+ ********************************************************************/
+
+static struct wkssvc_NetWkstaInfo102 *create_wks_info_102(TALLOC_CTX *mem_ctx)
+{
+ struct wkssvc_NetWkstaInfo102 *info102;
+
+ info102 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo102);
+ if (info102 == NULL) {
+ return NULL;
+ }
+
+ info102->platform_id = PLATFORM_ID_NT; /* unknown */
+ info102->version_major = SAMBA_MAJOR_NBT_ANNOUNCE_VERSION;
+ info102->version_minor = SAMBA_MINOR_NBT_ANNOUNCE_VERSION;
+
+ info102->server_name = talloc_asprintf_strupper_m(
+ info102, "%s", lp_netbios_name());
+ info102->domain_name = talloc_asprintf_strupper_m(
+ info102, "%s", lp_workgroup());
+ info102->lan_root = "";
+ info102->logged_on_users = 0;
+
+ return info102;
+}
+
+/********************************************************************
+ Handling for RPC Workstation Service request NetWkstaGetInfo
+ ********************************************************************/
+
+WERROR _wkssvc_NetWkstaGetInfo(struct pipes_struct *p,
+ struct wkssvc_NetWkstaGetInfo *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct dom_sid_buf buf;
+
+ switch (r->in.level) {
+ case 100:
+ /* Level 100 can be allowed from anyone including anonymous
+ * so no access checks are needed for this case */
+ r->out.info->info100 = create_wks_info_100(p->mem_ctx);
+ if (r->out.info->info100 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case 101:
+ /* Level 101 can be allowed from any logged in user */
+ if (!nt_token_check_sid(&global_sid_Authenticated_Users,
+ session_info->security_token)) {
+ DEBUG(1,("User not allowed for NetWkstaGetInfo level "
+ "101\n"));
+ DEBUGADD(3,(" - does not have sid for Authenticated "
+ "Users %s:\n",
+ dom_sid_str_buf(
+ &global_sid_Authenticated_Users,
+ &buf)));
+ security_token_debug(DBGC_CLASS, 3,
+ session_info->security_token);
+ return WERR_ACCESS_DENIED;
+ }
+ r->out.info->info101 = create_wks_info_101(p->mem_ctx);
+ if (r->out.info->info101 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ case 102:
+ /* Level 102 Should only be allowed from a domain administrator */
+ if (!nt_token_check_sid(&global_sid_Builtin_Administrators,
+ session_info->security_token)) {
+ DEBUG(1,("User not allowed for NetWkstaGetInfo level "
+ "102\n"));
+ DEBUGADD(3,(" - does not have sid for Administrators "
+ "group %s, sids are:\n",
+ dom_sid_str_buf(
+ &global_sid_Builtin_Administrators,
+ &buf)));
+ security_token_debug(DBGC_CLASS, 3,
+ session_info->security_token);
+ return WERR_ACCESS_DENIED;
+ }
+ r->out.info->info102 = create_wks_info_102(p->mem_ctx);
+ if (r->out.info->info102 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetWkstaSetInfo(struct pipes_struct *p,
+ struct wkssvc_NetWkstaSetInfo *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ RPC Workstation Service request NetWkstaEnumUsers with level 0:
+ Returns to the requester:
+ - the user names of the logged in users.
+ Returns a filled in wkssvc_NetWkstaEnumUsersCtr0 struct.
+ ********************************************************************/
+
+static struct wkssvc_NetWkstaEnumUsersCtr0 *create_enum_users0(
+ TALLOC_CTX *mem_ctx)
+{
+ struct wkssvc_NetWkstaEnumUsersCtr0 *ctr0;
+
+ ctr0 = talloc(mem_ctx, struct wkssvc_NetWkstaEnumUsersCtr0);
+ if (ctr0 == NULL) {
+ return NULL;
+ }
+
+ ctr0->entries_read = 0;
+ ctr0->user0 = talloc_array(ctr0, struct wkssvc_NetrWkstaUserInfo0, 0);
+ if (ctr0->user0 == NULL) {
+ TALLOC_FREE(ctr0);
+ return NULL;
+ }
+
+ return ctr0;
+}
+
+/********************************************************************
+ RPC Workstation Service request NetWkstaEnumUsers with level 1.
+ Returns to the requester:
+ - the user names of the logged in users,
+ - the domain or machine each is logged into,
+ - the password server that was used to authenticate each,
+ - other domains each user is logged into (not currently supported).
+ Returns a filled in wkssvc_NetWkstaEnumUsersCtr1 struct.
+ ********************************************************************/
+
+static struct wkssvc_NetWkstaEnumUsersCtr1 *create_enum_users1(
+ TALLOC_CTX *mem_ctx)
+{
+ struct wkssvc_NetWkstaEnumUsersCtr1 *ctr1;
+ struct dom_usr *dom_users;
+ const char *pwd_server;
+ char *pwd_tmp;
+ int i, num_dom_users, ret;
+
+ ctr1 = talloc(mem_ctx, struct wkssvc_NetWkstaEnumUsersCtr1);
+ if (ctr1 == NULL) {
+ return NULL;
+ }
+
+ ret = get_domain_userlist(talloc_tos(), &dom_users);
+ if (ret != 0) {
+ TALLOC_FREE(ctr1);
+ errno = ret;
+ return NULL;
+ }
+ num_dom_users = talloc_array_length(dom_users);
+
+ ctr1->user1 = talloc_array(ctr1, struct wkssvc_NetrWkstaUserInfo1,
+ num_dom_users);
+ if (ctr1->user1 == NULL) {
+ TALLOC_FREE(ctr1);
+ TALLOC_FREE(dom_users);
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ pwd_server = "";
+
+ if ((pwd_tmp = talloc_strdup(ctr1->user1, lp_password_server()))) {
+ /* The configured password server is a full DNS name but
+ * for the logon server we need to return just the first
+ * component (machine name) of it in upper-case */
+ char *p = strchr(pwd_tmp, '.');
+ if (p) {
+ *p = '\0';
+ } else {
+ p = pwd_tmp + strlen(pwd_tmp);
+ }
+ while (--p >= pwd_tmp) {
+ *p = toupper(*p);
+ }
+ pwd_server = pwd_tmp;
+ }
+
+ /* Now domain users */
+ for (i=0; i<num_dom_users; i++) {
+ ctr1->user1[i].user_name =
+ talloc_strdup(ctr1->user1, dom_users[i].name);
+ ctr1->user1[i].logon_domain =
+ talloc_strdup(ctr1->user1, dom_users[i].domain);
+ ctr1->user1[i].logon_server = pwd_server;
+
+ ctr1->user1[i++].other_domains = NULL; /* Maybe in future? */
+ }
+
+ ctr1->entries_read = i;
+
+ TALLOC_FREE(dom_users);
+ return ctr1;
+}
+
+/********************************************************************
+ Handling for RPC Workstation Service request NetWkstaEnumUsers
+ (a.k.a Windows NetWkstaUserEnum)
+ ********************************************************************/
+
+WERROR _wkssvc_NetWkstaEnumUsers(struct pipes_struct *p,
+ struct wkssvc_NetWkstaEnumUsers *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+
+ /* This with any level should only be allowed from a domain administrator */
+ if (!nt_token_check_sid(&global_sid_Builtin_Administrators,
+ session_info->security_token)) {
+ struct dom_sid_buf buf;
+ DEBUG(1,("User not allowed for NetWkstaEnumUsers\n"));
+ DEBUGADD(3,(" - does not have sid for Administrators group "
+ "%s\n",
+ dom_sid_str_buf(
+ &global_sid_Builtin_Administrators,
+ &buf)));
+ security_token_debug(
+ DBGC_CLASS, 3, session_info->security_token);
+ return WERR_ACCESS_DENIED;
+ }
+
+ switch (r->in.info->level) {
+ case 0:
+ r->out.info->ctr.user0 = create_enum_users0(p->mem_ctx);
+ if (r->out.info->ctr.user0 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ r->out.info->level = r->in.info->level;
+ *r->out.entries_read = r->out.info->ctr.user0->entries_read;
+ if (r->out.resume_handle != NULL) {
+ *r->out.resume_handle = 0;
+ }
+ break;
+ case 1:
+ r->out.info->ctr.user1 = create_enum_users1(p->mem_ctx);
+ if (r->out.info->ctr.user1 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ r->out.info->level = r->in.info->level;
+ *r->out.entries_read = r->out.info->ctr.user1->entries_read;
+ if (r->out.resume_handle != NULL) {
+ *r->out.resume_handle = 0;
+ }
+ break;
+ default:
+ return WERR_INVALID_LEVEL;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrWkstaUserGetInfo(struct pipes_struct *p,
+ struct wkssvc_NetrWkstaUserGetInfo *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrWkstaUserSetInfo(struct pipes_struct *p,
+ struct wkssvc_NetrWkstaUserSetInfo *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetWkstaTransportEnum(struct pipes_struct *p,
+ struct wkssvc_NetWkstaTransportEnum *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrWkstaTransportAdd(struct pipes_struct *p,
+ struct wkssvc_NetrWkstaTransportAdd *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrWkstaTransportDel(struct pipes_struct *p,
+ struct wkssvc_NetrWkstaTransportDel *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrUseAdd(struct pipes_struct *p,
+ struct wkssvc_NetrUseAdd *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrUseGetInfo(struct pipes_struct *p,
+ struct wkssvc_NetrUseGetInfo *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrUseDel(struct pipes_struct *p,
+ struct wkssvc_NetrUseDel *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrUseEnum(struct pipes_struct *p,
+ struct wkssvc_NetrUseEnum *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrMessageBufferSend(struct pipes_struct *p,
+ struct wkssvc_NetrMessageBufferSend *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrWorkstationStatisticsGet(struct pipes_struct *p,
+ struct wkssvc_NetrWorkstationStatisticsGet *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrLogonDomainNameAdd(struct pipes_struct *p,
+ struct wkssvc_NetrLogonDomainNameAdd *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrLogonDomainNameDel(struct pipes_struct *p,
+ struct wkssvc_NetrLogonDomainNameDel *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrJoinDomain(struct pipes_struct *p,
+ struct wkssvc_NetrJoinDomain *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrUnjoinDomain(struct pipes_struct *p,
+ struct wkssvc_NetrUnjoinDomain *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrRenameMachineInDomain(struct pipes_struct *p,
+ struct wkssvc_NetrRenameMachineInDomain *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrValidateName(struct pipes_struct *p,
+ struct wkssvc_NetrValidateName *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrGetJoinInformation(struct pipes_struct *p,
+ struct wkssvc_NetrGetJoinInformation *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrGetJoinableOus(struct pipes_struct *p,
+ struct wkssvc_NetrGetJoinableOus *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ _wkssvc_NetrJoinDomain2
+ ********************************************************************/
+
+WERROR _wkssvc_NetrJoinDomain2(struct pipes_struct *p,
+ struct wkssvc_NetrJoinDomain2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct libnet_JoinCtx *j = NULL;
+ char *cleartext_pwd = NULL;
+ char *admin_domain = NULL;
+ char *admin_account = NULL;
+ WERROR werr;
+ struct security_token *token = session_info->security_token;
+ NTSTATUS status;
+ DATA_BLOB session_key;
+ bool ok;
+
+ if (!r->in.domain_name) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!r->in.admin_account || !r->in.encrypted_password) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!security_token_has_privilege(token, SEC_PRIV_MACHINE_ACCOUNT) &&
+ !nt_token_check_domain_rid(token, DOMAIN_RID_ADMINS) &&
+ !nt_token_check_sid(&global_sid_Builtin_Administrators, token)) {
+ DEBUG(5,("_wkssvc_NetrJoinDomain2: account doesn't have "
+ "sufficient privileges\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ if ((r->in.join_flags & WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED) ||
+ (r->in.join_flags & WKSSVC_JOIN_FLAGS_JOIN_UNSECURE)) {
+ return WERR_NOT_SUPPORTED;
+ }
+
+ status = session_extract_session_key(session_info,
+ &session_key,
+ KEY_USE_16BYTES);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("_wkssvc_NetrJoinDomain2: no session key %s\n",
+ nt_errstr(status)));
+ return WERR_NO_USER_SESSION_KEY;
+ }
+
+ werr = decode_wkssvc_join_password_buffer(
+ p->mem_ctx, r->in.encrypted_password,
+ &session_key, &cleartext_pwd);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ ok = split_domain_user(p->mem_ctx,
+ r->in.admin_account,
+ &admin_domain,
+ &admin_account);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_init_JoinCtx(p->mem_ctx, &j);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ j->in.domain_name = r->in.domain_name;
+ j->in.account_ou = r->in.account_ou;
+ j->in.join_flags = r->in.join_flags;
+ j->in.admin_account = admin_account;
+ j->in.admin_password = cleartext_pwd;
+ j->in.debug = true;
+ j->in.modify_config = lp_config_backend_is_registry();
+ j->in.msg_ctx = p->msg_ctx;
+
+ become_root();
+ setenv(KRB5_ENV_CCNAME, "MEMORY:_wkssvc_NetrJoinDomain2", 1);
+ werr = libnet_Join(p->mem_ctx, j);
+ unsetenv(KRB5_ENV_CCNAME);
+ unbecome_root();
+
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(5,("_wkssvc_NetrJoinDomain2: libnet_Join failed with: %s\n",
+ j->out.error_string ? j->out.error_string :
+ win_errstr(werr)));
+ }
+
+ TALLOC_FREE(j);
+ return werr;
+}
+
+/********************************************************************
+ _wkssvc_NetrUnjoinDomain2
+ ********************************************************************/
+
+WERROR _wkssvc_NetrUnjoinDomain2(struct pipes_struct *p,
+ struct wkssvc_NetrUnjoinDomain2 *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct auth_session_info *session_info =
+ dcesrv_call_session_info(dce_call);
+ struct libnet_UnjoinCtx *u = NULL;
+ char *cleartext_pwd = NULL;
+ char *admin_domain = NULL;
+ char *admin_account = NULL;
+ WERROR werr;
+ struct security_token *token = session_info->security_token;
+ NTSTATUS status;
+ DATA_BLOB session_key;
+ bool ok;
+
+ if (!r->in.account || !r->in.encrypted_password) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!security_token_has_privilege(token, SEC_PRIV_MACHINE_ACCOUNT) &&
+ !nt_token_check_domain_rid(token, DOMAIN_RID_ADMINS) &&
+ !nt_token_check_sid(&global_sid_Builtin_Administrators, token)) {
+ DEBUG(5,("_wkssvc_NetrUnjoinDomain2: account doesn't have "
+ "sufficient privileges\n"));
+ return WERR_ACCESS_DENIED;
+ }
+
+ status = session_extract_session_key(session_info,
+ &session_key,
+ KEY_USE_16BYTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("_wkssvc_NetrUnjoinDomain2: no session key %s\n",
+ nt_errstr(status)));
+ return WERR_NO_USER_SESSION_KEY;
+ }
+
+ werr = decode_wkssvc_join_password_buffer(
+ p->mem_ctx, r->in.encrypted_password,
+ &session_key, &cleartext_pwd);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ ok = split_domain_user(p->mem_ctx,
+ r->in.account,
+ &admin_domain,
+ &admin_account);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ werr = libnet_init_UnjoinCtx(p->mem_ctx, &u);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ u->in.domain_name = lp_realm();
+ u->in.unjoin_flags = r->in.unjoin_flags |
+ WKSSVC_JOIN_FLAGS_JOIN_TYPE;
+ u->in.admin_account = admin_account;
+ u->in.admin_password = cleartext_pwd;
+ u->in.debug = true;
+ u->in.modify_config = lp_config_backend_is_registry();
+ u->in.msg_ctx = p->msg_ctx;
+
+ become_root();
+ setenv(KRB5_ENV_CCNAME, "MEMORY:_wkssvc_NetrUnjoinDomain2", 1);
+ werr = libnet_Unjoin(p->mem_ctx, u);
+ unsetenv(KRB5_ENV_CCNAME);
+ unbecome_root();
+
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(5,("_wkssvc_NetrUnjoinDomain2: libnet_Unjoin failed with: %s\n",
+ u->out.error_string ? u->out.error_string :
+ win_errstr(werr)));
+ }
+
+ TALLOC_FREE(u);
+ return werr;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrRenameMachineInDomain2(struct pipes_struct *p,
+ struct wkssvc_NetrRenameMachineInDomain2 *r)
+{
+ /* for now just return not supported */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrValidateName2(struct pipes_struct *p,
+ struct wkssvc_NetrValidateName2 *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrGetJoinableOus2(struct pipes_struct *p,
+ struct wkssvc_NetrGetJoinableOus2 *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrAddAlternateComputerName(struct pipes_struct *p,
+ struct wkssvc_NetrAddAlternateComputerName *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrRemoveAlternateComputerName(struct pipes_struct *p,
+ struct wkssvc_NetrRemoveAlternateComputerName *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrSetPrimaryComputername(struct pipes_struct *p,
+ struct wkssvc_NetrSetPrimaryComputername *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+WERROR _wkssvc_NetrEnumerateComputerNames(struct pipes_struct *p,
+ struct wkssvc_NetrEnumerateComputerNames *r)
+{
+ /* FIXME: Add implementation code here */
+ p->fault_state = DCERPC_FAULT_OP_RNG_ERROR;
+ return WERR_NOT_SUPPORTED;
+}
+
+/* include the generated boilerplate */
+#include "librpc/gen_ndr/ndr_wkssvc_scompat.c"
diff --git a/source3/rpc_server/wscript_build b/source3/rpc_server/wscript_build
new file mode 100644
index 0000000..904311a
--- /dev/null
+++ b/source3/rpc_server/wscript_build
@@ -0,0 +1,308 @@
+#!/usr/bin/env python
+
+### RPC_SERVER
+bld.SAMBA3_SUBSYSTEM('rpc',
+ source='',
+ deps='dcerpc-server-core')
+
+bld.SAMBA_BINARY('samba-dcerpcd',
+ source='rpc_host.c',
+ deps='''
+ samba3core
+ CMDLINE_S3
+ dcerpc-binding
+ npa_tstream
+ AUTH_COMMON
+ RPC_SOCK_HELPER
+ NDR_RPC_HOST
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA_LIBRARY('RPC_WORKER',
+ private_library=True,
+ source='''
+ rpc_worker.c
+ ''',
+ deps='''
+ smbd_base
+ CMDLINE_S3
+ NDR_RPC_HOST
+ RPC_SERVER
+ RPC_NCACN_NP
+ npa_tstream
+ ''')
+
+bld.SAMBA3_BINARY('rpcd_rpcecho',
+ source='rpcd_rpcecho.c',
+ deps='''
+ RPC_WORKER
+ RPC_RPCECHO
+ ''',
+ for_selftest=True,
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_BINARY('rpcd_classic',
+ source='rpcd_classic.c',
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ RPC_SERVICE
+ RPC_SOCK_HELPER
+ smbd_base
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_BINARY('rpcd_lsad',
+ source='rpcd_lsad.c',
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ RPC_LSARPC
+ RPC_SAMR
+ RPC_DSSETUP
+ RPC_NETLOGON
+ RPC_SOCK_HELPER
+ smbd_base
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_BINARY('rpcd_winreg',
+ source='rpcd_winreg.c',
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ RPC_WINREG
+ smbd_base
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_BINARY('rpcd_spoolss',
+ source='rpcd_spoolss.c',
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ RPC_SPOOLSS
+ smbd_base
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_BINARY('rpcd_epmapper',
+ source='rpcd_epmapper.c',
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ RPC_EPMAPPER
+ smbd_base
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_BINARY('rpcd_fsrvp',
+ source='rpcd_fsrvp.c',
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ RPC_FSS_AGENT
+ smbd_base
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_BINARY('rpcd_witness',
+ source='rpcd_witness.c',
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ RPC_WITNESS
+ ''',
+ install_path='${SAMBA_LIBEXECDIR}',
+ enabled=bld.env.with_ctdb)
+
+bld.SAMBA3_SUBSYSTEM('RPC_CONFIG',
+ source='rpc_config.c',
+ deps='talloc')
+
+bld.SAMBA3_SUBSYSTEM('RPC_NCACN_NP',
+ source='rpc_ncacn_np.c rpc_handles.c',
+ deps='auth common_auth npa_tstream')
+
+bld.SAMBA3_LIBRARY('RPC_SERVER_LOOP',
+ private_library=True,
+ source='rpc_server.c',
+ deps='''
+ LIBTSOCKET
+ dcerpc-server-core
+ npa_tstream
+ auth
+ RPC_NCACN_NP
+ samba3-util
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('SRV_ACCESS_CHECK',
+ source='srv_access_check.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('RPC_SERVER',
+ source='',
+ deps='''
+ dcerpc-server-core
+ RPC_CONFIG
+ RPC_SERVER_LOOP
+ NDR_NAMED_PIPE_AUTH
+ ''')
+
+### RPC_SERVICES
+bld.SAMBA3_SUBSYSTEM('RPC_DSSETUP',
+ source='''dssetup/srv_dssetup_nt.c''',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('RPC_EPMAPPER',
+ source='''epmapper/srv_epmapper.c''',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('RPC_FSS_STATE',
+ source='''fss/srv_fss_state.c''',
+ deps='samba-util NDR_FSRVP_STATE')
+
+bld.SAMBA3_SUBSYSTEM('RPC_FSS_AGENT',
+ source='''fss/srv_fss_agent.c''',
+ deps='samba-util RPC_FSS_STATE')
+
+bld.SAMBA3_SUBSYSTEM('RPC_EVENTLOG',
+ source='''eventlog/srv_eventlog_nt.c
+ eventlog/srv_eventlog_reg.c''',
+ deps='LIBEVENTLOG LIBCLI_WINREG_INTERNAL')
+
+bld.SAMBA3_SUBSYSTEM('RPC_INITSHUTDOWN',
+ source='''initshutdown/srv_initshutdown_nt.c''',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('RPC_LSARPC',
+ source='''lsa/srv_lsa_nt.c''',
+ deps='SRV_ACCESS_CHECK LIBLSA GNUTLS_HELPERS')
+
+bld.SAMBA3_SUBSYSTEM('RPC_NETDFS',
+ source='''dfs/srv_dfs_nt.c''',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('RPC_NETLOGON',
+ source='''netlogon/srv_netlog_nt.c''',
+ deps='LIBCLI_AUTH DCERPC_SERVER_NETLOGON')
+
+bld.SAMBA3_SUBSYSTEM('RPC_NTSVCS',
+ source='''ntsvcs/srv_ntsvcs_nt.c''',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('RPC_RPCECHO',
+ source='''echo/srv_echo_nt.c''',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('RPC_SAMR',
+ source='''samr/srv_samr_nt.c
+ samr/srv_samr_util.c
+ samr/srv_samr_chgpasswd.c''',
+ deps='PLAINTEXT_AUTH SRV_ACCESS_CHECK DCERPC_HELPER')
+
+bld.SAMBA3_SUBSYSTEM('RPC_SPOOLSS',
+ source='''spoolss/srv_spoolss_nt.c
+ spoolss/srv_spoolss_util.c''',
+ deps='PRINTING PRINTBACKEND LIBCLI_WINREG_INTERNAL')
+
+bld.SAMBA3_SUBSYSTEM('RPC_IREMOTEWINSPOOL',
+ source='''
+ spoolss/srv_iremotewinspool_nt.c
+ spoolss/srv_iremotewinspool.c
+ spoolss/iremotewinspool_util.c
+ ''',
+ deps='RPC_SPOOLSS')
+
+bld.SAMBA3_SUBSYSTEM('RPC_SRVSVC',
+ source='''srvsvc/srv_srvsvc_nt.c''',
+ deps='samba-util tdb')
+
+bld.SAMBA3_SUBSYSTEM('RPC_SVCCTL',
+ source='''svcctl/srv_svcctl_nt.c
+ svcctl/srv_svcctl_reg.c''',
+ deps='SERVICES LIBCLI_WINREG_INTERNAL')
+
+bld.SAMBA3_SUBSYSTEM('RPC_WINREG',
+ source='''winreg/srv_winreg_nt.c''',
+ deps='REG_FULL REGFIO NDR_PERFCOUNT')
+
+bld.SAMBA3_SUBSYSTEM('RPC_WKSSVC',
+ source='''wkssvc/srv_wkssvc_nt.c''',
+ deps='LIBNET')
+
+bld.SAMBA3_SUBSYSTEM('RPC_WITNESS',
+ source='''witness/srv_witness_nt.c''',
+ deps='samba-util samba-cluster-support samba3core',
+ enabled=bld.env.with_ctdb)
+
+bld.SAMBA3_SUBSYSTEM('mdssvc',
+ source='''
+ mdssvc/dalloc.c
+ mdssvc/marshalling.c
+ ''')
+
+rpc_mdssvc_sources = '''
+ mdssvc/mdssvc.c
+ mdssvc/mdssvc_noindex.c
+ mdssvc/srv_mdssvc_nt.c
+ '''
+rpc_mdssvc_deps = 'mdssvc samba-util smbd_base '
+
+if bld.env.spotlight_backend_tracker:
+ rpc_mdssvc_sources += '''
+ mdssvc/mdssvc_tracker.c
+ mdssvc/sparql_mapping.c
+ mdssvc/sparql_parser.y
+ mdssvc/sparql_lexer.l
+ '''
+ rpc_mdssvc_deps += 'tevent-glib-glue ' + bld.env['libtracker']
+
+if bld.env.spotlight_backend_es:
+ rpc_mdssvc_sources += '''
+ mdssvc/mdssvc_es.c
+ mdssvc/es_mapping.c
+ mdssvc/es_parser.y
+ mdssvc/es_lexer.l
+ '''
+ rpc_mdssvc_deps += ' http jansson'
+
+ bld.INSTALL_FILES(bld.env.SAMBA_DATADIR,
+ 'mdssvc/elasticsearch_mappings.json')
+
+bld.SAMBA3_BINARY('rpcd_mdssvc',
+ source='rpcd_mdssvc.c ' + rpc_mdssvc_sources,
+ deps='''
+ CMDLINE_S3
+ RPC_WORKER
+ smbd_base
+ ''' + rpc_mdssvc_deps,
+ install_path='${SAMBA_LIBEXECDIR}')
+
+bld.SAMBA3_SUBSYSTEM('RPC_SERVICE',
+ source='',
+ deps='''
+ rpc
+ RPC_SERVER
+ RPC_SAMR
+ RPC_LSARPC
+ RPC_WINREG
+ RPC_INITSHUTDOWN
+ RPC_DSSETUP
+ RPC_WKSSVC
+ RPC_SVCCTL
+ RPC_NTSVCS
+ RPC_NETLOGON
+ RPC_NETDFS
+ RPC_SRVSVC
+ RPC_IREMOTEWINSPOOL
+ RPC_EVENTLOG
+ RPC_RPCECHO
+ RPC_EPMAPPER
+ RPC_FSS_AGENT
+ ''')
+
+# RPC_DAEMONS
+bld.SAMBA3_SUBSYSTEM('RPC_SOCK_HELPER',
+ source='rpc_sock_helper.c',
+ deps='')
diff --git a/source3/rpcclient/cmd_clusapi.c b/source3/rpcclient/cmd_clusapi.c
new file mode 100644
index 0000000..7f208a1
--- /dev/null
+++ b/source3/rpcclient/cmd_clusapi.c
@@ -0,0 +1,753 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Günther Deschner 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_clusapi_c.h"
+
+static WERROR cmd_clusapi_open_cluster(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR error;
+ struct policy_handle Cluster;
+
+ status = dcerpc_clusapi_OpenCluster(b, mem_ctx,
+ &error,
+ &Cluster);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ printf("successfully opened cluster\n");
+
+ status = dcerpc_clusapi_CloseCluster(b, mem_ctx,
+ &Cluster,
+ &error);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ printf("successfully closed cluster\n");
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_get_cluster_name(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR error;
+ const char *ClusterName;
+ const char *NodeName;
+
+ status = dcerpc_clusapi_GetClusterName(b, mem_ctx,
+ &ClusterName,
+ &NodeName,
+ &error);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ printf("ClusterName: %s\n", ClusterName);
+ printf("NodeName: %s\n", NodeName);
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_get_cluster_version(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR error;
+ uint16_t lpwMajorVersion;
+ uint16_t lpwMinorVersion;
+ uint16_t lpwBuildNumber;
+ const char *lpszVendorId;
+ const char *lpszCSDVersion;
+
+ status = dcerpc_clusapi_GetClusterVersion(b, mem_ctx,
+ &lpwMajorVersion,
+ &lpwMinorVersion,
+ &lpwBuildNumber,
+ &lpszVendorId,
+ &lpszCSDVersion,
+ &error);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ printf("lpwMajorVersion: %d\n", lpwMajorVersion);
+ printf("lpwMinorVersion: %d\n", lpwMinorVersion);
+ printf("lpwBuildNumber: %d\n", lpwBuildNumber);
+ printf("lpszVendorId: %s\n", lpszVendorId);
+ printf("lpszCSDVersion: %s\n", lpszCSDVersion);
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_get_quorum_resource(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR error;
+ const char *lpszResourceName;
+ const char *lpszDeviceName;
+ uint32_t pdwMaxQuorumLogSize;
+ WERROR rpc_status;
+
+ status = dcerpc_clusapi_GetQuorumResource(b, mem_ctx,
+ &lpszResourceName,
+ &lpszDeviceName,
+ &pdwMaxQuorumLogSize,
+ &rpc_status,
+ &error);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ printf("lpszResourceName: %s\n", lpszResourceName);
+ printf("lpszDeviceName: %s\n", lpszDeviceName);
+ printf("pdwMaxQuorumLogSize: %d\n", pdwMaxQuorumLogSize);
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_create_enum(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR error;
+ uint32_t dwType = 1;
+ struct ENUM_LIST *ReturnEnum;
+ WERROR rpc_status;
+
+ if (argc >= 2) {
+ sscanf(argv[1],"%x",&dwType);
+ }
+
+ status = dcerpc_clusapi_CreateEnum(b, mem_ctx,
+ dwType,
+ &ReturnEnum,
+ &rpc_status,
+ &error);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_create_enumex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR error;
+ uint32_t dwType = 1;
+ struct ENUM_LIST *ReturnIdEnum;
+ struct ENUM_LIST *ReturnNameEnum;
+ WERROR rpc_status, ignore;
+ struct policy_handle Cluster;
+
+ status = dcerpc_clusapi_OpenCluster(b, mem_ctx,
+ &error,
+ &Cluster);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ if (argc >= 2) {
+ sscanf(argv[1],"%x",&dwType);
+ }
+
+ status = dcerpc_clusapi_CreateEnumEx(b, mem_ctx,
+ Cluster,
+ dwType,
+ 0,
+ &ReturnIdEnum,
+ &ReturnNameEnum,
+ &rpc_status,
+ &error);
+ dcerpc_clusapi_CloseCluster(b, mem_ctx,
+ &Cluster,
+ &ignore);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(error)) {
+ printf("error: %s\n", win_errstr(error));
+ return error;
+ }
+
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+
+static WERROR cmd_clusapi_open_resource(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ const char *lpszResourceName = "Cluster Name";
+ WERROR Status;
+ struct policy_handle hResource;
+ WERROR rpc_status, ignore;
+
+ if (argc >= 2) {
+ lpszResourceName = argv[1];
+ }
+
+ status = dcerpc_clusapi_OpenResource(b, mem_ctx,
+ lpszResourceName,
+ &Status,
+ &rpc_status,
+ &hResource);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ dcerpc_clusapi_CloseResource(b, mem_ctx,
+ &hResource,
+ &ignore);
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_online_resource(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ const char *lpszResourceName = "Cluster Name";
+ WERROR Status;
+ struct policy_handle hResource;
+ WERROR rpc_status, ignore;
+
+ if (argc >= 2) {
+ lpszResourceName = argv[1];
+ }
+
+ status = dcerpc_clusapi_OpenResource(b, mem_ctx,
+ lpszResourceName,
+ &Status,
+ &rpc_status,
+ &hResource);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ status = dcerpc_clusapi_OnlineResource(b, mem_ctx,
+ hResource,
+ &Status,
+ &rpc_status);
+ dcerpc_clusapi_CloseResource(b, mem_ctx,
+ &hResource,
+ &ignore);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_offline_resource(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ const char *lpszResourceName = "Cluster Name";
+ WERROR Status;
+ struct policy_handle hResource;
+ WERROR rpc_status, ignore;
+
+ if (argc >= 2) {
+ lpszResourceName = argv[1];
+ }
+
+ status = dcerpc_clusapi_OpenResource(b, mem_ctx,
+ lpszResourceName,
+ &Status,
+ &rpc_status,
+ &hResource);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ status = dcerpc_clusapi_OfflineResource(b, mem_ctx,
+ hResource,
+ &Status,
+ &rpc_status);
+ dcerpc_clusapi_CloseResource(b, mem_ctx,
+ &hResource,
+ &ignore);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_get_resource_state(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ const char *lpszResourceName = "Cluster Name";
+ WERROR Status;
+ struct policy_handle hResource;
+ WERROR rpc_status;
+ enum clusapi_ClusterResourceState State;
+ const char *NodeName;
+ const char *GroupName;
+ WERROR result, ignore;
+
+ if (argc >= 2) {
+ lpszResourceName = argv[1];
+ }
+
+ status = dcerpc_clusapi_OpenResource(b, mem_ctx,
+ lpszResourceName,
+ &Status,
+ &rpc_status,
+ &hResource);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ status = dcerpc_clusapi_GetResourceState(b, mem_ctx,
+ hResource,
+ &State,
+ &NodeName,
+ &GroupName,
+ &rpc_status,
+ &result);
+ dcerpc_clusapi_CloseResource(b, mem_ctx,
+ &hResource,
+ &ignore);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_get_cluster_version2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ uint16_t lpwMajorVersion;
+ uint16_t lpwMinorVersion;
+ uint16_t lpwBuildNumber;
+ const char *lpszVendorId;
+ const char *lpszCSDVersion;
+ struct CLUSTER_OPERATIONAL_VERSION_INFO *ppClusterOpVerInfo;
+ WERROR rpc_status;
+ WERROR result;
+
+ status = dcerpc_clusapi_GetClusterVersion2(b, mem_ctx,
+ &lpwMajorVersion,
+ &lpwMinorVersion,
+ &lpwBuildNumber,
+ &lpszVendorId,
+ &lpszCSDVersion,
+ &ppClusterOpVerInfo,
+ &rpc_status,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ printf("result: %s\n", win_errstr(result));
+ return result;
+ }
+
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_pause_node(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ const char *lpszNodeName = "CTDB_NODE_0";
+ WERROR Status;
+ struct policy_handle hNode;
+ WERROR rpc_status;
+ WERROR result, ignore;
+
+ if (argc >= 2) {
+ lpszNodeName = argv[1];
+ }
+
+ status = dcerpc_clusapi_OpenNode(b, mem_ctx,
+ lpszNodeName,
+ &Status,
+ &rpc_status,
+ &hNode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Failed to open node %s\n", lpszNodeName);
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ status = dcerpc_clusapi_PauseNode(b, mem_ctx,
+ hNode,
+ &rpc_status,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf("Failed to pause node %s\n", lpszNodeName);
+ printf("Status: %s\n", win_errstr(result));
+ return result;
+ }
+
+ dcerpc_clusapi_CloseNode(b, mem_ctx,
+ &hNode,
+ &ignore);
+
+ printf("Cluster node %s has been paused\n", lpszNodeName);
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+static WERROR cmd_clusapi_resume_node(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ const char *lpszNodeName = "CTDB_NODE_0";
+ WERROR Status;
+ struct policy_handle hNode;
+ WERROR rpc_status;
+ WERROR result, ignore;
+
+ if (argc >= 2) {
+ lpszNodeName = argv[1];
+ }
+
+ status = dcerpc_clusapi_OpenNode(b, mem_ctx,
+ lpszNodeName,
+ &Status,
+ &rpc_status,
+ &hNode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(Status)) {
+ printf("Failed to open node %s\n", lpszNodeName);
+ printf("Status: %s\n", win_errstr(Status));
+ return Status;
+ }
+
+ status = dcerpc_clusapi_ResumeNode(b, mem_ctx,
+ hNode,
+ &rpc_status,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf("Failed to resume node %s\n", lpszNodeName);
+ printf("Status: %s\n", win_errstr(result));
+ return result;
+ }
+
+ dcerpc_clusapi_CloseNode(b, mem_ctx,
+ &hNode,
+ &ignore);
+
+ printf("Cluster node %s has been resumed\n", lpszNodeName);
+ printf("rpc_status: %s\n", win_errstr(rpc_status));
+
+ return WERR_OK;
+}
+
+
+struct cmd_set clusapi_commands[] = {
+
+ {
+ .name = "CLUSAPI",
+ },
+ {
+ .name = "clusapi_open_cluster",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_open_cluster,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Open cluster",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_get_cluster_name",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_get_cluster_name,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Get cluster name",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_get_cluster_version",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_get_cluster_version,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Get cluster version",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_get_quorum_resource",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_get_quorum_resource,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Get quorum resource",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_create_enum",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_create_enum,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Create enum query",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_create_enumex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_create_enumex,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Create enumex query",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_open_resource",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_open_resource,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Open cluster resource",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_online_resource",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_online_resource,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Set cluster resource online",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_offline_resource",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_offline_resource,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Set cluster resource offline",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_get_resource_state",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_get_resource_state,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Get cluster resource state",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_get_cluster_version2",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_get_cluster_version2,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Get cluster version2",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_pause_node",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_pause_node,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Pause cluster node",
+ .usage = "",
+ },
+ {
+ .name = "clusapi_resume_node",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_clusapi_resume_node,
+ .table = &ndr_table_clusapi,
+ .rpc_pipe = NULL,
+ .description = "Resume cluster node",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_dfs.c b/source3/rpcclient/cmd_dfs.c
new file mode 100644
index 0000000..8177871
--- /dev/null
+++ b/source3/rpcclient/cmd_dfs.c
@@ -0,0 +1,396 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jelmer Vernooij 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_dfs_c.h"
+
+/* Check DFS is supported by the remote server */
+
+static WERROR cmd_dfs_version(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ enum dfs_ManagerVersion version;
+ NTSTATUS result;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 1) {
+ printf("Usage: %s\n", argv[0]);
+ return WERR_OK;
+ }
+
+ result = dcerpc_dfs_GetManagerVersion(b, mem_ctx, &version);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ return ntstatus_to_werror(result);
+ }
+
+ if (version > 0) {
+ printf("dfs is present (%d)\n", version);
+ } else {
+ printf("dfs is not present\n");
+ }
+
+ return WERR_OK;
+}
+
+static WERROR cmd_dfs_add(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS result;
+ WERROR werr;
+ const char *path, *servername, *sharename, *comment;
+ uint32_t flags = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 5) {
+ printf("Usage: %s path servername sharename comment\n",
+ argv[0]);
+ return WERR_OK;
+ }
+
+ path = argv[1];
+ servername = argv[2];
+ sharename = argv[3];
+ comment = argv[4];
+
+ result = dcerpc_dfs_Add(b, mem_ctx, path, servername,
+ sharename, comment, flags, &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return ntstatus_to_werror(result);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_dfs_remove(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS result;
+ WERROR werr;
+ const char *path, *servername, *sharename;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 4) {
+ printf("Usage: %s path servername sharename\n", argv[0]);
+ return WERR_OK;
+ }
+
+ path = argv[1];
+ servername = argv[2];
+ sharename = argv[3];
+
+ result = dcerpc_dfs_Remove(b, mem_ctx, path, servername,
+ sharename, &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return ntstatus_to_werror(result);
+ }
+
+ return werr;
+}
+
+/* Display a DFS_INFO_1 structure */
+
+static void display_dfs_info_1(struct dfs_Info1 *info1)
+{
+ printf("path: %s\n", info1->path);
+}
+
+/* Display a DFS_INFO_2 structure */
+
+static void display_dfs_info_2(struct dfs_Info2 *info2)
+{
+ printf("path: %s\n", info2->path);
+ printf("\tcomment: %s\n", info2->comment);
+
+ printf("\tstate: %d\n", info2->state);
+ printf("\tnum_stores: %d\n", info2->num_stores);
+}
+
+/* Display a DFS_INFO_3 structure */
+
+static void display_dfs_info_3(struct dfs_Info3 *info3)
+{
+ int i;
+
+ printf("path: %s\n", info3->path);
+
+ printf("\tcomment: %s\n", info3->comment);
+
+ printf("\tstate: %d\n", info3->state);
+ printf("\tnum_stores: %d\n", info3->num_stores);
+
+ for (i = 0; i < info3->num_stores; i++) {
+ struct dfs_StorageInfo *dsi = &info3->stores[i];
+
+ printf("\t\tstorage[%d] server: %s\n", i, dsi->server);
+
+ printf("\t\tstorage[%d] share: %s\n", i, dsi->share);
+ }
+}
+
+
+/* Display a DFS_INFO_CTR structure */
+static void display_dfs_info(uint32_t level, union dfs_Info *ctr)
+{
+ switch (level) {
+ case 0x01:
+ display_dfs_info_1(ctr->info1);
+ break;
+ case 0x02:
+ display_dfs_info_2(ctr->info2);
+ break;
+ case 0x03:
+ display_dfs_info_3(ctr->info3);
+ break;
+ default:
+ printf("unsupported info level %d\n",
+ level);
+ break;
+ }
+}
+
+static void display_dfs_enumstruct(struct dfs_EnumStruct *ctr)
+{
+ int i;
+
+ /* count is always the first element, so we can just use info1 here */
+ for (i = 0; i < ctr->e.info1->count; i++) {
+ switch (ctr->level) {
+ case 1: display_dfs_info_1(&ctr->e.info1->s[i]); break;
+ case 2: display_dfs_info_2(&ctr->e.info2->s[i]); break;
+ case 3: display_dfs_info_3(&ctr->e.info3->s[i]); break;
+ default:
+ printf("unsupported info level %d\n",
+ ctr->level);
+ return;
+ }
+ }
+}
+
+/* Enumerate dfs shares */
+
+static WERROR cmd_dfs_enum(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dfs_EnumStruct str;
+ struct dfs_EnumArray1 info1;
+ struct dfs_EnumArray2 info2;
+ struct dfs_EnumArray3 info3;
+ struct dfs_EnumArray4 info4;
+ struct dfs_EnumArray200 info200;
+ struct dfs_EnumArray300 info300;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ NTSTATUS result;
+ WERROR werr;
+ uint32_t total = 0;
+
+ if (argc > 2) {
+ printf("Usage: %s [info_level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ str.level = 1;
+ if (argc == 2)
+ str.level = atoi(argv[1]);
+
+ switch (str.level) {
+ case 1: str.e.info1 = &info1; ZERO_STRUCT(info1); break;
+ case 2: str.e.info2 = &info2; ZERO_STRUCT(info2); break;
+ case 3: str.e.info3 = &info3; ZERO_STRUCT(info3); break;
+ case 4: str.e.info4 = &info4; ZERO_STRUCT(info4); break;
+ case 200: str.e.info200 = &info200; ZERO_STRUCT(info200); break;
+ case 300: str.e.info300 = &info300; ZERO_STRUCT(info300); break;
+ default:
+ printf("Unknown info level %d\n", str.level);
+ return WERR_OK;
+ }
+
+ result = dcerpc_dfs_Enum(b, mem_ctx, str.level, 0xFFFFFFFF, &str,
+ &total, &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return ntstatus_to_werror(result);
+ }
+ if (W_ERROR_IS_OK(werr)) {
+ display_dfs_enumstruct(&str);
+ }
+
+ return werr;
+}
+
+/* Enumerate dfs shares */
+
+static WERROR cmd_dfs_enumex(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dfs_EnumStruct str;
+ struct dfs_EnumArray1 info1;
+ struct dfs_EnumArray2 info2;
+ struct dfs_EnumArray3 info3;
+ struct dfs_EnumArray4 info4;
+ struct dfs_EnumArray200 info200;
+ struct dfs_EnumArray300 info300;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ NTSTATUS result;
+ WERROR werr;
+ uint32_t total = 0;
+
+ if (argc < 2 || argc > 3) {
+ printf("Usage: %s dfs_name [info_level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ str.level = 1;
+
+ if (argc == 3)
+ str.level = atoi(argv[2]);
+
+ switch (str.level) {
+ case 1: str.e.info1 = &info1; ZERO_STRUCT(info1); break;
+ case 2: str.e.info2 = &info2; ZERO_STRUCT(info2); break;
+ case 3: str.e.info3 = &info3; ZERO_STRUCT(info3); break;
+ case 4: str.e.info4 = &info4; ZERO_STRUCT(info4); break;
+ case 200: str.e.info200 = &info200; ZERO_STRUCT(info200); break;
+ case 300: str.e.info300 = &info300; ZERO_STRUCT(info300); break;
+ default:
+ printf("Unknown info level %d\n", str.level);
+ return WERR_OK;
+ }
+
+ result = dcerpc_dfs_EnumEx(b, mem_ctx, argv[1], str.level,
+ 0xFFFFFFFF, &str, &total, &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return ntstatus_to_werror(result);
+ }
+ if (W_ERROR_IS_OK(werr)) {
+ display_dfs_enumstruct(&str);
+ }
+
+ return werr;
+}
+
+
+static WERROR cmd_dfs_getinfo(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS result;
+ WERROR werr;
+ const char *path, *servername, *sharename;
+ uint32_t info_level = 1;
+ union dfs_Info ctr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 4 || argc > 5) {
+ printf("Usage: %s path servername sharename "
+ "[info_level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ path = argv[1];
+ servername = argv[2];
+ sharename = argv[3];
+
+ if (argc == 5)
+ info_level = atoi(argv[4]);
+
+ result = dcerpc_dfs_GetInfo(b, mem_ctx, path, servername,
+ sharename, info_level, &ctr, &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return ntstatus_to_werror(result);
+ }
+ if (W_ERROR_IS_OK(werr)) {
+ display_dfs_info(info_level, &ctr);
+ }
+
+ return werr;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set dfs_commands[] = {
+
+ { .name = "DFS" },
+
+ {
+ .name = "dfsversion",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_dfs_version,
+ .table = &ndr_table_netdfs,
+ .rpc_pipe = NULL,
+ .description = "Query DFS support",
+ .usage = "",
+ },
+ {
+ .name = "dfsadd",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_dfs_add,
+ .table = &ndr_table_netdfs,
+ .rpc_pipe = NULL,
+ .description = "Add a DFS share",
+ .usage = "",
+ },
+ {
+ .name = "dfsremove",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_dfs_remove,
+ .table = &ndr_table_netdfs,
+ .rpc_pipe = NULL,
+ .description = "Remove a DFS share",
+ .usage = "",
+ },
+ {
+ .name = "dfsgetinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_dfs_getinfo,
+ .table = &ndr_table_netdfs,
+ .rpc_pipe = NULL,
+ .description = "Query DFS share info",
+ .usage = "",
+ },
+ {
+ .name = "dfsenum",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_dfs_enum,
+ .table = &ndr_table_netdfs,
+ .rpc_pipe = NULL,
+ .description = "Enumerate dfs shares",
+ .usage = "",
+ },
+ {
+ .name = "dfsenumex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_dfs_enumex,
+ .table = &ndr_table_netdfs,
+ .rpc_pipe = NULL,
+ .description = "Enumerate dfs shares",
+ .usage = "",
+ },
+
+ { .name = NULL }
+};
diff --git a/source3/rpcclient/cmd_drsuapi.c b/source3/rpcclient/cmd_drsuapi.c
new file mode 100644
index 0000000..bf87a26
--- /dev/null
+++ b/source3/rpcclient/cmd_drsuapi.c
@@ -0,0 +1,723 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_drsuapi_c.h"
+
+static WERROR cracknames(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *bind_handle,
+ enum drsuapi_DsNameFormat format_offered,
+ enum drsuapi_DsNameFormat format_desired,
+ int argc,
+ const char **argv,
+ union drsuapi_DsNameCtr *ctr)
+{
+ NTSTATUS status;
+ WERROR werr;
+ int i;
+ uint32_t level = 1;
+ union drsuapi_DsNameRequest req;
+ uint32_t level_out;
+ struct drsuapi_DsNameString *names;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ names = talloc_zero_array(mem_ctx, struct drsuapi_DsNameString, argc);
+ W_ERROR_HAVE_NO_MEMORY(names);
+
+ for (i=0; i<argc; i++) {
+ names[i].str = argv[i];
+ }
+
+ req.req1.codepage = 1252; /* german */
+ req.req1.language = 0x00000407; /* german */
+ req.req1.count = argc;
+ req.req1.names = names;
+ req.req1.format_flags = DRSUAPI_DS_NAME_FLAG_NO_FLAGS;
+ req.req1.format_offered = format_offered;
+ req.req1.format_desired = format_desired;
+
+ status = dcerpc_drsuapi_DsCrackNames(b, mem_ctx,
+ bind_handle,
+ level,
+ &req,
+ &level_out,
+ ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
+static WERROR cmd_drsuapi_cracknames(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR werr;
+ int i;
+
+ struct GUID bind_guid;
+ struct policy_handle bind_handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ union drsuapi_DsNameCtr ctr;
+
+ if (argc < 2) {
+ printf("usage: %s name\n", argv[0]);
+ return WERR_OK;
+ }
+
+ GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid);
+
+ status = dcerpc_drsuapi_DsBind(b, mem_ctx,
+ &bind_guid,
+ NULL,
+ &bind_handle,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ werr = cracknames(cli, mem_ctx,
+ &bind_handle,
+ DRSUAPI_DS_NAME_FORMAT_UNKNOWN,
+ DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
+ 1,
+ argv+1,
+ &ctr);
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto out;
+ }
+
+ for (i=0; i < ctr.ctr1->count; i++) {
+ printf("status: %d\n",
+ ctr.ctr1->array[i].status);
+ printf("dns_domain_name: %s\n",
+ ctr.ctr1->array[i].dns_domain_name);
+ printf("result_name: %s\n",
+ ctr.ctr1->array[i].result_name);
+ }
+
+ out:
+ if (is_valid_policy_hnd(&bind_handle)) {
+ WERROR _werr;
+ dcerpc_drsuapi_DsUnbind(b, mem_ctx, &bind_handle, &_werr);
+ }
+
+ return werr;
+}
+
+static void display_domain_controller_info_01(struct drsuapi_DsGetDCConnection01 *r)
+{
+ printf("client_ip_address:\t%s\n", r->client_ip_address);
+ printf("unknown2:\t%d\n", r->unknown2);
+ printf("connection_time:\t%d\n", r->connection_time);
+ printf("unknown4:\t%d\n", r->unknown4);
+ printf("unknown5:\t%d\n", r->unknown5);
+ printf("unknown6:\t%d\n", r->unknown6);
+ printf("client_account:\t%s\n", r->client_account);
+}
+
+static void display_domain_controller_info_1(struct drsuapi_DsGetDCInfo1 *r)
+{
+ printf("netbios_name:\t%s\n", r->netbios_name);
+ printf("dns_name:\t%s\n", r->dns_name);
+ printf("site_name:\t%s\n", r->site_name);
+ printf("computer_dn:\t%s\n", r->computer_dn);
+ printf("server_dn:\t%s\n", r->server_dn);
+ printf("is_pdc:\t\t%s\n", r->is_pdc ? "true" : "false");
+ printf("is_enabled:\t%s\n", r->is_enabled ? "true" : "false");
+}
+
+static void display_domain_controller_info_2(struct drsuapi_DsGetDCInfo2 *r)
+{
+ printf("netbios_name:\t%s\n", r->netbios_name);
+ printf("dns_name:\t%s\n", r->dns_name);
+ printf("site_name:\t%s\n", r->site_name);
+ printf("site_dn:\t%s\n", r->site_dn);
+ printf("computer_dn:\t%s\n", r->computer_dn);
+ printf("server_dn:\t%s\n", r->server_dn);
+ printf("ntds_dn:\t%s\n", r->ntds_dn);
+ printf("is_pdc:\t\t%s\n", r->is_pdc ? "true" : "false");
+ printf("is_enabled:\t%s\n", r->is_enabled ? "true" : "false");
+ printf("is_gc:\t\t%s\n", r->is_gc ? "true" : "false");
+ printf("site_guid:\t%s\n", GUID_string(talloc_tos(), &r->site_guid));
+ printf("computer_guid:\t%s\n", GUID_string(talloc_tos(), &r->computer_guid));
+ printf("server_guid:\t%s\n", GUID_string(talloc_tos(), &r->server_guid));
+ printf("ntds_guid:\t%s\n", GUID_string(talloc_tos(), &r->ntds_guid));
+}
+
+static void display_domain_controller_info_3(struct drsuapi_DsGetDCInfo3 *r)
+{
+ printf("netbios_name:\t%s\n", r->netbios_name);
+ printf("dns_name:\t%s\n", r->dns_name);
+ printf("site_name:\t%s\n", r->site_name);
+ printf("site_dn:\t%s\n", r->site_dn);
+ printf("computer_dn:\t%s\n", r->computer_dn);
+ printf("server_dn:\t%s\n", r->server_dn);
+ printf("ntds_dn:\t%s\n", r->ntds_dn);
+ printf("is_pdc:\t\t%s\n", r->is_pdc ? "true" : "false");
+ printf("is_enabled:\t%s\n", r->is_enabled ? "true" : "false");
+ printf("is_gc:\t\t%s\n", r->is_gc ? "true" : "false");
+ printf("is_rodc:\t%s\n", r->is_rodc ? "true" : "false");
+ printf("site_guid:\t%s\n", GUID_string(talloc_tos(), &r->site_guid));
+ printf("computer_guid:\t%s\n", GUID_string(talloc_tos(), &r->computer_guid));
+ printf("server_guid:\t%s\n", GUID_string(talloc_tos(), &r->server_guid));
+ printf("ntds_guid:\t%s\n", GUID_string(talloc_tos(), &r->ntds_guid));
+}
+
+static void display_domain_controller_info(int32_t level,
+ union drsuapi_DsGetDCInfoCtr *ctr)
+{
+ int i;
+
+ switch (level) {
+ case DRSUAPI_DC_CONNECTION_CTR_01:
+ for (i=0; i<ctr->ctr01.count; i++) {
+ printf("----------\n");
+ display_domain_controller_info_01(&ctr->ctr01.array[i]);
+ }
+ break;
+ case DRSUAPI_DC_INFO_CTR_1:
+ for (i=0; i<ctr->ctr1.count; i++) {
+ printf("----------\n");
+ display_domain_controller_info_1(&ctr->ctr1.array[i]);
+ }
+ break;
+ case DRSUAPI_DC_INFO_CTR_2:
+ for (i=0; i<ctr->ctr2.count; i++) {
+ printf("----------\n");
+ display_domain_controller_info_2(&ctr->ctr2.array[i]);
+ }
+ break;
+ case DRSUAPI_DC_INFO_CTR_3:
+ for (i=0; i<ctr->ctr3.count; i++) {
+ printf("----------\n");
+ display_domain_controller_info_3(&ctr->ctr3.array[i]);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static WERROR cmd_drsuapi_getdcinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR werr;
+
+ struct GUID bind_guid;
+ struct policy_handle bind_handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ const char *domain = NULL;
+ int32_t level = 1;
+ int32_t level_out;
+ union drsuapi_DsGetDCInfoRequest req;
+ union drsuapi_DsGetDCInfoCtr ctr;
+
+ if (argc < 2) {
+ printf("usage: %s domain [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ domain = argv[1];
+ if (argc >= 3) {
+ level = atoi(argv[2]);
+ }
+
+ GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid);
+
+ status = dcerpc_drsuapi_DsBind(b, mem_ctx,
+ &bind_guid,
+ NULL,
+ &bind_handle,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ req.req1.domain_name = domain;
+ req.req1.level = level;
+
+ status = dcerpc_drsuapi_DsGetDomainControllerInfo(b, mem_ctx,
+ &bind_handle,
+ 1,
+ &req,
+ &level_out,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto out;
+ }
+
+ display_domain_controller_info(level_out, &ctr);
+ out:
+ if (is_valid_policy_hnd(&bind_handle)) {
+ WERROR _werr;
+ dcerpc_drsuapi_DsUnbind(b, mem_ctx, &bind_handle, &_werr);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_drsuapi_writeaccountspn(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR werr;
+
+ struct GUID bind_guid;
+ struct policy_handle bind_handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct drsuapi_DsNameString *spn_names = NULL;
+
+ int i = 0;
+ uint32_t level_out;
+ union drsuapi_DsWriteAccountSpnRequest req;
+ union drsuapi_DsWriteAccountSpnResult result;
+
+ if (argc < 4) {
+ printf("usage: %s [add|replace|delete] dn [spn_names]+\n", argv[0]);
+ return WERR_OK;
+ }
+
+ req.req1.unknown1 = 0; /* Unused, must be 0 */
+ req.req1.object_dn = argv[2];
+ req.req1.count = argc - 3;
+
+ if (strcmp(argv[1], "add") == 0) {
+ req.req1.operation = DRSUAPI_DS_SPN_OPERATION_ADD;
+ } else if (strcmp(argv[1], "replace") == 0) {
+ req.req1.operation = DRSUAPI_DS_SPN_OPERATION_REPLACE;
+ } else if (strcmp(argv[1], "delete") == 0) {
+ req.req1.operation = DRSUAPI_DS_SPN_OPERATION_DELETE;
+ } else {
+ printf("usage: %s [add|replace|delete] dn [spn_names]+\n", argv[0]);
+ return WERR_OK;
+ }
+
+ spn_names = talloc_zero_array(mem_ctx,
+ struct drsuapi_DsNameString,
+ req.req1.count);
+ W_ERROR_HAVE_NO_MEMORY(spn_names);
+
+ for (i=0; i<req.req1.count; i++) {
+ spn_names[i].str = argv[i + 3];
+ }
+
+ req.req1.spn_names = spn_names;
+
+ GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid);
+
+ status = dcerpc_drsuapi_DsBind(b, mem_ctx,
+ &bind_guid,
+ NULL,
+ &bind_handle,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ status = dcerpc_drsuapi_DsWriteAccountSpn(b, mem_ctx,
+ &bind_handle,
+ 1,
+ &req,
+ &level_out,
+ &result,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ goto out;
+ }
+
+ out:
+ if (is_valid_policy_hnd(&bind_handle)) {
+ WERROR _werr;
+ dcerpc_drsuapi_DsUnbind(b, mem_ctx, &bind_handle, &_werr);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_drsuapi_getncchanges(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR werr;
+
+ struct policy_handle bind_handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ struct GUID bind_guid;
+ struct drsuapi_DsBindInfoCtr bind_info;
+ struct drsuapi_DsBindInfo28 info28;
+
+ const char *nc_dn = NULL;
+
+ DATA_BLOB session_key;
+
+ uint32_t level = 8;
+ bool single = false;
+ uint32_t level_out = 0;
+ union drsuapi_DsGetNCChangesRequest req;
+ union drsuapi_DsGetNCChangesCtr ctr;
+ struct drsuapi_DsReplicaObjectIdentifier nc;
+
+ struct drsuapi_DsGetNCChangesCtr1 *ctr1 = NULL;
+ struct drsuapi_DsGetNCChangesCtr6 *ctr6 = NULL;
+ uint32_t out_level = 0;
+ int y;
+
+ uint32_t supported_extensions = 0;
+ uint32_t replica_flags = DRSUAPI_DRS_WRIT_REP |
+ DRSUAPI_DRS_INIT_SYNC |
+ DRSUAPI_DRS_PER_SYNC |
+ DRSUAPI_DRS_GET_ANC |
+ DRSUAPI_DRS_NEVER_SYNCED;
+
+ if (argc > 3) {
+ printf("usage: %s [naming_context_or_object_dn [single]]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ nc_dn = argv[1];
+ }
+
+ if (argc == 3) {
+ if (strequal(argv[2], "single")) {
+ single = true;
+ } else {
+ printf("warning: ignoring unknown argument '%s'\n",
+ argv[2]);
+ }
+ }
+
+ ZERO_STRUCT(info28);
+
+ ZERO_STRUCT(req);
+
+ GUID_from_string(DRSUAPI_DS_BIND_GUID, &bind_guid);
+
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7;
+ info28.supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT;
+ info28.site_guid = GUID_zero();
+ info28.pid = 0;
+ info28.repl_epoch = 0;
+
+ bind_info.length = 28;
+ bind_info.info.info28 = info28;
+
+ status = dcerpc_drsuapi_DsBind(b, mem_ctx,
+ &bind_guid,
+ &bind_info,
+ &bind_handle,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ if (bind_info.length == 24) {
+ supported_extensions = bind_info.info.info24.supported_extensions;
+ } else if (bind_info.length == 28) {
+ supported_extensions = bind_info.info.info28.supported_extensions;
+ } else if (bind_info.length == 32) {
+ supported_extensions = bind_info.info.info32.supported_extensions;
+ } else if (bind_info.length == 48) {
+ supported_extensions = bind_info.info.info48.supported_extensions;
+ } else if (bind_info.length == 52) {
+ supported_extensions = bind_info.info.info52.supported_extensions;
+ }
+
+ if (!nc_dn) {
+
+ union drsuapi_DsNameCtr crack_ctr;
+ const char *name;
+
+ name = talloc_asprintf(mem_ctx, "%s\\", lp_workgroup());
+ W_ERROR_HAVE_NO_MEMORY(name);
+
+ werr = cracknames(cli, mem_ctx,
+ &bind_handle,
+ DRSUAPI_DS_NAME_FORMAT_UNKNOWN,
+ DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
+ 1,
+ &name,
+ &crack_ctr);
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ if (crack_ctr.ctr1->count != 1) {
+ return WERR_NO_SUCH_DOMAIN;
+ }
+
+ if (crack_ctr.ctr1->array[0].status != DRSUAPI_DS_NAME_STATUS_OK) {
+ return WERR_NO_SUCH_DOMAIN;
+ }
+
+ nc_dn = talloc_strdup(mem_ctx, crack_ctr.ctr1->array[0].result_name);
+ W_ERROR_HAVE_NO_MEMORY(nc_dn);
+
+ printf("using: %s\n", nc_dn);
+ }
+
+ nc.dn = nc_dn;
+ nc.guid = GUID_zero();
+ nc.sid = (struct dom_sid) {0};
+
+ if (supported_extensions & DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8) {
+ level = 8;
+ req.req8.naming_context = &nc;
+ req.req8.replica_flags = replica_flags;
+ req.req8.max_object_count = 402;
+ req.req8.max_ndr_size = 402116;
+ if (single) {
+ req.req8.extended_op = DRSUAPI_EXOP_REPL_OBJ;
+ }
+ } else {
+ level = 5;
+ req.req5.naming_context = &nc;
+ req.req5.replica_flags = replica_flags;
+ req.req5.max_object_count = 402;
+ req.req5.max_ndr_size = 402116;
+ if (single) {
+ req.req5.extended_op = DRSUAPI_EXOP_REPL_OBJ;
+ }
+ }
+
+ for (y=0; ;y++) {
+
+ if (level == 8) {
+ DEBUG(1,("start[%d] tmp_higest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)req.req8.highwatermark.tmp_highest_usn,
+ (long long)req.req8.highwatermark.highest_usn));
+ }
+
+ status = dcerpc_drsuapi_DsGetNCChanges(b, mem_ctx,
+ &bind_handle,
+ level,
+ &req,
+ &level_out,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ printf("Failed to get NC Changes: %s",
+ get_friendly_nt_error_msg(status));
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ printf("Failed to get NC Changes: %s",
+ get_friendly_werror_msg(werr));
+ goto out;
+ }
+
+ if (level_out == 1) {
+ out_level = 1;
+ ctr1 = &ctr.ctr1;
+ } else if (level_out == 2 && ctr.ctr2.mszip1.ts) {
+ out_level = 1;
+ ctr1 = &ctr.ctr2.mszip1.ts->ctr1;
+ }
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to get Session Key: %s",
+ nt_errstr(status));
+ return ntstatus_to_werror(status);
+ }
+
+ if (out_level == 1) {
+ DEBUG(1,("end[%d] tmp_highest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)ctr1->new_highwatermark.tmp_highest_usn,
+ (long long)ctr1->new_highwatermark.highest_usn));
+#if 0
+ libnet_dssync_decrypt_attributes(mem_ctx,
+ &session_key,
+ ctr1->first_object);
+#endif
+ if (ctr1->more_data) {
+ req.req5.highwatermark = ctr1->new_highwatermark;
+ continue;
+ }
+ }
+
+ if (level_out == 6) {
+ out_level = 6;
+ ctr6 = &ctr.ctr6;
+ } else if (level_out == 7
+ && ctr.ctr7.level == 6
+ && ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_MSZIP
+ && ctr.ctr7.ctr.mszip6.ts) {
+ out_level = 6;
+ ctr6 = &ctr.ctr7.ctr.mszip6.ts->ctr6;
+ } else if (level_out == 7
+ && ctr.ctr7.level == 6
+ && ctr.ctr7.type == DRSUAPI_COMPRESSION_TYPE_WIN2K3_LZ77_DIRECT2
+ && ctr.ctr7.ctr.xpress6.ts) {
+ out_level = 6;
+ ctr6 = &ctr.ctr7.ctr.xpress6.ts->ctr6;
+ }
+
+ if (out_level == 6) {
+ DEBUG(1,("end[%d] tmp_highest_usn: %llu , highest_usn: %llu\n",y,
+ (long long)ctr6->new_highwatermark.tmp_highest_usn,
+ (long long)ctr6->new_highwatermark.highest_usn));
+#if 0
+ libnet_dssync_decrypt_attributes(mem_ctx,
+ &session_key,
+ ctr6->first_object);
+#endif
+ if (ctr6->more_data) {
+ req.req8.highwatermark = ctr6->new_highwatermark;
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ out:
+ return werr;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set drsuapi_commands[] = {
+
+ {
+ .name = "DRSUAPI",
+ },
+ {
+ .name = "dscracknames",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_drsuapi_cracknames,
+ .table = &ndr_table_drsuapi,
+ .rpc_pipe = NULL,
+ .description = "Crack Name",
+ .usage = "",
+ },
+ {
+ .name = "dsgetdcinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_drsuapi_getdcinfo,
+ .table = &ndr_table_drsuapi,
+ .rpc_pipe = NULL,
+ .description = "Get Domain Controller Info",
+ .usage = "",
+ },
+ {
+ .name = "dsgetncchanges",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_drsuapi_getncchanges,
+ .table = &ndr_table_drsuapi,
+ .rpc_pipe = NULL,
+ .description = "Get NC Changes",
+ .usage = "",
+ },
+ {
+ .name = "dswriteaccountspn",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_drsuapi_writeaccountspn,
+ .table = &ndr_table_drsuapi,
+ .rpc_pipe = NULL,
+ .description = "Write Account SPN",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_dssetup.c b/source3/rpcclient/cmd_dssetup.c
new file mode 100644
index 0000000..611cf63
--- /dev/null
+++ b/source3/rpcclient/cmd_dssetup.c
@@ -0,0 +1,84 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Gerald Carter 2002
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_dssetup_c.h"
+
+/* Look up domain related information on a remote host */
+
+static WERROR cmd_ds_dsrole_getprimarydominfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ union dssetup_DsRoleInfo info;
+
+ status = dcerpc_dssetup_DsRoleGetPrimaryDomainInformation(b, mem_ctx,
+ DS_ROLE_BASIC_INFORMATION,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ printf ("Machine Role = [%d]\n", info.basic.role);
+
+ if (info.basic.flags & DS_ROLE_PRIMARY_DS_RUNNING) {
+ printf("Directory Service is running.\n");
+ printf("Domain is in %s mode.\n",
+ (info.basic.flags & DS_ROLE_PRIMARY_DS_MIXED_MODE) ? "mixed" : "native" );
+ } else {
+ printf("Directory Service not running on server\n");
+ }
+
+ return werr;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set ds_commands[] = {
+
+ {
+ .name = "LSARPC-DS"
+ },
+
+ {
+ .name = "dsroledominfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ds_dsrole_getprimarydominfo,
+ .table = &ndr_table_dssetup,
+ .rpc_pipe = NULL,
+ .description = "Get Primary Domain Information",
+ .usage = ""
+ },
+
+ {
+ .name = NULL,
+ }
+};
diff --git a/source3/rpcclient/cmd_echo.c b/source3/rpcclient/cmd_echo.c
new file mode 100644
index 0000000..6a387ea
--- /dev/null
+++ b/source3/rpcclient/cmd_echo.c
@@ -0,0 +1,232 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Tim Potter 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_echo_c.h"
+
+static NTSTATUS cmd_echo_add_one(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ uint32_t request = 1, response;
+ NTSTATUS status;
+
+ if (argc > 2) {
+ printf("Usage: %s [num]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2) {
+ request = atoi(argv[1]);
+ }
+
+ status = dcerpc_echo_AddOne(b, mem_ctx, request, &response);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ printf("%d + 1 = %d\n", request, response);
+
+done:
+ return status;
+}
+
+static NTSTATUS cmd_echo_data(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ uint32_t size, i;
+ NTSTATUS status;
+ uint8_t *in_data = NULL, *out_data = NULL;
+
+ if (argc != 2) {
+ printf("Usage: %s num\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ size = atoi(argv[1]);
+ if ( (in_data = (uint8_t*)SMB_MALLOC(size)) == NULL ) {
+ printf("Failure to allocate buff of %d bytes\n",
+ size);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ if ( (out_data = (uint8_t*)SMB_MALLOC(size)) == NULL ) {
+ printf("Failure to allocate buff of %d bytes\n",
+ size);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < size; i++) {
+ in_data[i] = i & 0xff;
+ }
+
+ status = dcerpc_echo_EchoData(b, mem_ctx, size, in_data, out_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ for (i = 0; i < size; i++) {
+ if (in_data[i] != out_data[i]) {
+ printf("mismatch at offset %d, %d != %d\n",
+ i, in_data[i], out_data[i]);
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+done:
+ SAFE_FREE(in_data);
+ SAFE_FREE(out_data);
+
+ return status;
+}
+
+static NTSTATUS cmd_echo_source_data(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ uint32_t size, i;
+ NTSTATUS status;
+ uint8_t *out_data = NULL;
+
+ if (argc != 2) {
+ printf("Usage: %s num\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ size = atoi(argv[1]);
+ if ( (out_data = (uint8_t*)SMB_MALLOC(size)) == NULL ) {
+ printf("Failure to allocate buff of %d bytes\n",
+ size);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+
+ status = dcerpc_echo_SourceData(b, mem_ctx, size, out_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ for (i = 0; i < size; i++) {
+ if (out_data && out_data[i] != (i & 0xff)) {
+ printf("mismatch at offset %d, %d != %d\n",
+ i, out_data[i], i & 0xff);
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+
+done:
+
+ SAFE_FREE(out_data);
+ return status;
+}
+
+static NTSTATUS cmd_echo_sink_data(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ uint32_t size, i;
+ NTSTATUS status;
+ uint8_t *in_data = NULL;
+
+ if (argc != 2) {
+ printf("Usage: %s num\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ size = atoi(argv[1]);
+ if ( (in_data = (uint8_t*)SMB_MALLOC(size)) == NULL ) {
+ printf("Failure to allocate buff of %d bytes\n",
+ size);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < size; i++) {
+ in_data[i] = i & 0xff;
+ }
+
+ status = dcerpc_echo_SinkData(b, mem_ctx, size, in_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+done:
+ SAFE_FREE(in_data);
+
+ return status;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set echo_commands[] = {
+
+ {
+ .name = "ECHO",
+ },
+
+ {
+ .name = "echoaddone",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_echo_add_one,
+ .wfn = NULL,
+ .table = &ndr_table_rpcecho,
+ .rpc_pipe = NULL,
+ .description = "Add one to a number",
+ .usage = "",
+ },
+ {
+ .name = "echodata",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_echo_data,
+ .wfn = NULL,
+ .table = &ndr_table_rpcecho,
+ .rpc_pipe = NULL,
+ .description = "Echo data",
+ .usage = "",
+ },
+ {
+ .name = "sinkdata",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_echo_sink_data,
+ .wfn = NULL,
+ .table = &ndr_table_rpcecho,
+ .rpc_pipe = NULL,
+ .description = "Sink data",
+ .usage = "",
+ },
+ {
+ .name = "sourcedata",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_echo_source_data,
+ .wfn = NULL,
+ .table = &ndr_table_rpcecho,
+ .rpc_pipe = NULL,
+ .description = "Source data",
+ .usage = "",
+ },
+ {
+ .name = NULL
+ },
+};
diff --git a/source3/rpcclient/cmd_epmapper.c b/source3/rpcclient/cmd_epmapper.c
new file mode 100644
index 0000000..236f753
--- /dev/null
+++ b/source3/rpcclient/cmd_epmapper.c
@@ -0,0 +1,270 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_epmapper_c.h"
+#include "librpc/ndr/ndr_table.h"
+
+static NTSTATUS cmd_epmapper_map(struct rpc_pipe_client *p,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dcerpc_binding_handle *b = p->binding_handle;
+ struct dcerpc_binding *map_binding;
+ struct epm_twr_t map_tower;
+ struct epm_twr_p_t towers[500];
+ struct policy_handle entry_handle;
+ struct ndr_syntax_id abstract_syntax;
+ uint32_t num_towers;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ uint32_t result;
+ uint32_t i;
+ const struct ndr_interface_list *l;
+ const char *interface_name = "lsarpc";
+ enum dcerpc_transport_t transport = NCACN_NP;
+ bool ok = false;
+ struct GUID object_uuid = GUID_zero();
+
+ if (argc > 4) {
+ d_fprintf(stderr,
+ "Usage: %s [interface_name] [transport] "
+ "[object_uuid]\n",
+ argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc >= 2) {
+ interface_name = argv[1];
+ }
+
+ for (l = ndr_table_list(); l != NULL; l = l->next) {
+
+ ok = strequal(interface_name, l->table->name);
+ if (ok) {
+ abstract_syntax = l->table->syntax_id;
+ break;
+ }
+ }
+
+ if (!ok) {
+ d_fprintf(stderr, "unknown interface: %s\n",
+ interface_name);
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (argc >= 3) {
+ transport = dcerpc_transport_by_name(argv[2]);
+ if (transport == NCA_UNKNOWN) {
+ d_fprintf(stderr, "unknown transport: %s\n",
+ argv[2]);
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ if (argc >= 4) {
+ status = GUID_from_string(argv[3], &object_uuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ /* 127.0.0.1[0] => correct? needed? */
+ status = dcerpc_parse_binding(tmp_ctx, "ncacn_np:127.0.0.1[0]",
+ &map_binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "dcerpc_parse_binding returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dcerpc_binding_set_transport(map_binding, transport);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "dcerpc_binding_set_transport returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dcerpc_binding_set_abstract_syntax(map_binding,
+ &abstract_syntax);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "dcerpc_binding_set_abstract_syntax returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dcerpc_binding_build_tower(tmp_ctx, map_binding,
+ &map_tower.tower);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "dcerpc_binding_build_tower returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ ZERO_STRUCT(towers);
+ ZERO_STRUCT(entry_handle);
+
+ status = dcerpc_epm_Map(
+ b, tmp_ctx, &object_uuid,
+ &map_tower, &entry_handle, ARRAY_SIZE(towers),
+ &num_towers, towers, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "dcerpc_epm_Map returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ if (result != EPMAPPER_STATUS_OK) {
+ d_fprintf(stderr, "epm_Map returned %u (0x%08X)\n",
+ result, result);
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ d_printf("num_tower[%u]\n", num_towers);
+
+ for (i=0; i < num_towers; i++) {
+ struct dcerpc_binding *binding;
+
+ if (towers[i].twr == NULL) {
+ d_fprintf(stderr, "tower[%u] NULL\n", i);
+ break;
+ }
+
+ status = dcerpc_binding_from_tower(tmp_ctx, &towers[i].twr->tower,
+ &binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ d_printf("tower[%u] %s\n", i, dcerpc_binding_string(tmp_ctx, binding));
+ }
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS cmd_epmapper_lookup(struct rpc_pipe_client *p,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dcerpc_binding_handle *b = p->binding_handle;
+ struct policy_handle entry_handle;
+
+ ZERO_STRUCT(entry_handle);
+
+ while (true) {
+ TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx);
+ uint32_t num_entries;
+ struct epm_entry_t entry;
+ NTSTATUS status;
+ char *guid_string;
+ struct dcerpc_binding *binding;
+ uint32_t result;
+
+ status = dcerpc_epm_Lookup(b, tmp_ctx,
+ 0, /* rpc_c_ep_all */
+ NULL,
+ NULL,
+ 0, /* rpc_c_vers_all */
+ &entry_handle,
+ 1, /* max_ents */
+ &num_entries, &entry,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "dcerpc_epm_Lookup returned %s\n",
+ nt_errstr(status));
+ break;
+ }
+
+ if (result == EPMAPPER_STATUS_NO_MORE_ENTRIES) {
+ d_fprintf(stderr, "epm_Lookup no more entries\n");
+ break;
+ }
+
+ if (result != EPMAPPER_STATUS_OK) {
+ d_fprintf(stderr, "epm_Lookup returned %u (0x%08X)\n",
+ result, result);
+ break;
+ }
+
+ if (num_entries != 1) {
+ d_fprintf(stderr, "epm_Lookup returned %d "
+ "entries, expected one\n", (int)num_entries);
+ break;
+ }
+
+ guid_string = GUID_string(tmp_ctx, &entry.object);
+ if (guid_string == NULL) {
+ break;
+ }
+
+ status = dcerpc_binding_from_tower(tmp_ctx, &entry.tower->tower,
+ &binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ d_printf("%s %s: %s\n", guid_string,
+ dcerpc_binding_string(tmp_ctx, binding),
+ entry.annotation);
+
+ TALLOC_FREE(tmp_ctx);
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/* List of commands exported by this module */
+
+struct cmd_set epmapper_commands[] = {
+
+ {
+ .name = "EPMAPPER",
+ },
+
+ {
+ .name = "epmmap",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_epmapper_map,
+ .wfn = NULL,
+ .table = &ndr_table_epmapper,
+ .rpc_pipe = NULL,
+ .description = "Map a binding",
+ .usage = "",
+ },
+ {
+ .name = "epmlookup",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_epmapper_lookup,
+ .wfn = NULL,
+ .table = &ndr_table_epmapper,
+ .rpc_pipe = NULL,
+ .description = "Lookup bindings",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_eventlog.c b/source3/rpcclient/cmd_eventlog.c
new file mode 100644
index 0000000..a22ab8a
--- /dev/null
+++ b/source3/rpcclient/cmd_eventlog.c
@@ -0,0 +1,650 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Günther Deschner 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_eventlog.h"
+#include "../librpc/gen_ndr/ndr_eventlog_c.h"
+#include "rpc_client/init_lsa.h"
+
+static NTSTATUS get_eventlog_handle(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *log,
+ struct policy_handle *handle)
+{
+ NTSTATUS status, result;
+ struct eventlog_OpenUnknown0 unknown0;
+ struct lsa_String logname, servername;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ unknown0.unknown0 = 0x005c;
+ unknown0.unknown1 = 0x0001;
+
+ init_lsa_String(&logname, log);
+ init_lsa_String(&servername, NULL);
+
+ status = dcerpc_eventlog_OpenEventLogW(b, mem_ctx,
+ &unknown0,
+ &logname,
+ &servername,
+ 0x00000001, /* major */
+ 0x00000001, /* minor */
+ handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+static NTSTATUS cmd_eventlog_readlog(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS result = NT_STATUS_OK;
+ struct policy_handle handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ uint32_t flags = EVENTLOG_BACKWARDS_READ |
+ EVENTLOG_SEQUENTIAL_READ;
+ uint32_t offset = 0;
+ uint32_t number_of_bytes = 0;
+ uint8_t *data;
+ uint32_t sent_size = 0;
+ uint32_t real_size = 0;
+
+ if (argc < 2 || argc > 4) {
+ printf("Usage: %s logname [offset] [number_of_bytes]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc >= 3) {
+ offset = atoi(argv[2]);
+ }
+
+ if (argc >= 4) {
+ number_of_bytes = atoi(argv[3]);
+ }
+
+ status = get_eventlog_handle(cli, mem_ctx, argv[1], &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ data = talloc_array(mem_ctx, uint8_t, number_of_bytes);
+ if (data == NULL) {
+ goto done;
+ }
+
+ do {
+
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ struct EVENTLOGRECORD r;
+ uint32_t size = 0;
+ uint32_t pos = 0;
+
+ status = dcerpc_eventlog_ReadEventLogW(b, mem_ctx,
+ &handle,
+ flags,
+ offset,
+ number_of_bytes,
+ data,
+ &sent_size,
+ &real_size,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_BUFFER_TOO_SMALL) &&
+ real_size > 0 ) {
+ number_of_bytes = real_size;
+ data = talloc_realloc(mem_ctx, data, uint8_t, real_size);
+ if (data == NULL) {
+ goto done;
+ }
+ status = dcerpc_eventlog_ReadEventLogW(b, mem_ctx,
+ &handle,
+ flags,
+ offset,
+ number_of_bytes,
+ data,
+ &sent_size,
+ &real_size,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!NT_STATUS_EQUAL(result, NT_STATUS_END_OF_FILE) &&
+ !NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ number_of_bytes = 0;
+
+ size = IVAL(data, pos);
+
+ while (size > 0) {
+
+ blob = data_blob_const(data + pos, size);
+ /* dump_data(0, blob.data, blob.length); */
+ ndr_err = ndr_pull_struct_blob_all(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_EVENTLOGRECORD);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+
+ NDR_PRINT_DEBUG(EVENTLOGRECORD, &r);
+
+ pos += size;
+
+ if (pos + 4 > sent_size) {
+ break;
+ }
+
+ size = IVAL(data, pos);
+ }
+
+ offset++;
+
+ } while (NT_STATUS_IS_OK(result));
+
+ done:
+ dcerpc_eventlog_CloseEventLog(b, mem_ctx, &handle, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_eventlog_numrecords(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ uint32_t number = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ printf("Usage: %s logname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = get_eventlog_handle(cli, mem_ctx, argv[1], &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = dcerpc_eventlog_GetNumRecords(b, mem_ctx,
+ &handle,
+ &number,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf("number of records: %d\n", number);
+
+ done:
+ dcerpc_eventlog_CloseEventLog(b, mem_ctx, &handle, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_eventlog_oldestrecord(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ uint32_t oldest_entry = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ printf("Usage: %s logname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = get_eventlog_handle(cli, mem_ctx, argv[1], &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = dcerpc_eventlog_GetOldestRecord(b, mem_ctx,
+ &handle,
+ &oldest_entry,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf("oldest entry: %d\n", oldest_entry);
+
+ done:
+ dcerpc_eventlog_CloseEventLog(b, mem_ctx, &handle, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_eventlog_reportevent(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ uint16_t num_of_strings = 1;
+ uint32_t data_size = 0;
+ struct lsa_String servername;
+ struct lsa_String *strings;
+ uint8_t *data = NULL;
+ uint32_t record_number = 0;
+ time_t time_written = 0;
+
+ if (argc != 2) {
+ printf("Usage: %s logname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = get_eventlog_handle(cli, mem_ctx, argv[1], &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ strings = talloc_array(mem_ctx, struct lsa_String, num_of_strings);
+ if (!strings) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ init_lsa_String(&strings[0], "test event written by rpcclient\n");
+ init_lsa_String(&servername, NULL);
+
+ status = dcerpc_eventlog_ReportEventW(b, mem_ctx,
+ &handle,
+ time(NULL),
+ EVENTLOG_INFORMATION_TYPE,
+ 0, /* event_category */
+ 0, /* event_id */
+ num_of_strings,
+ data_size,
+ &servername,
+ NULL, /* user_sid */
+ &strings,
+ data,
+ 0, /* flags */
+ &record_number,
+ &time_written,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf("entry: %d written at %s\n", record_number,
+ http_timestring(talloc_tos(), time_written));
+
+ done:
+ dcerpc_eventlog_CloseEventLog(b, mem_ctx, &handle, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_eventlog_reporteventsource(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ uint16_t num_of_strings = 1;
+ uint32_t data_size = 0;
+ struct lsa_String servername, sourcename;
+ struct lsa_String *strings;
+ uint8_t *data = NULL;
+ uint32_t record_number = 0;
+ time_t time_written = 0;
+
+ if (argc != 2) {
+ printf("Usage: %s logname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = get_eventlog_handle(cli, mem_ctx, argv[1], &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ strings = talloc_array(mem_ctx, struct lsa_String, num_of_strings);
+ if (!strings) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ init_lsa_String(&strings[0], "test event written by rpcclient\n");
+ init_lsa_String(&servername, NULL);
+ init_lsa_String(&sourcename, "rpcclient");
+
+ status = dcerpc_eventlog_ReportEventAndSourceW(b, mem_ctx,
+ &handle,
+ time(NULL),
+ EVENTLOG_INFORMATION_TYPE,
+ 0, /* event_category */
+ 0, /* event_id */
+ &sourcename,
+ num_of_strings,
+ data_size,
+ &servername,
+ NULL, /* user_sid */
+ &strings,
+ data,
+ 0, /* flags */
+ &record_number,
+ &time_written,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf("entry: %d written at %s\n", record_number,
+ http_timestring(talloc_tos(), time_written));
+
+ done:
+ dcerpc_eventlog_CloseEventLog(b, mem_ctx, &handle, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_eventlog_registerevsource(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle log_handle;
+ struct lsa_String module_name, reg_module_name;
+ struct eventlog_OpenUnknown0 unknown0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ unknown0.unknown0 = 0x005c;
+ unknown0.unknown1 = 0x0001;
+
+ if (argc != 2) {
+ printf("Usage: %s logname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ init_lsa_String(&module_name, "rpcclient");
+ init_lsa_String(&reg_module_name, NULL);
+
+ status = dcerpc_eventlog_RegisterEventSourceW(b, mem_ctx,
+ &unknown0,
+ &module_name,
+ &reg_module_name,
+ 1, /* major_version */
+ 1, /* minor_version */
+ &log_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ dcerpc_eventlog_DeregisterEventSource(b, mem_ctx, &log_handle, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_eventlog_backuplog(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ struct lsa_String backup_filename;
+ const char *tmp;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 3) {
+ printf("Usage: %s logname backupname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = get_eventlog_handle(cli, mem_ctx, argv[1], &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ tmp = talloc_asprintf(mem_ctx, "\\??\\%s", argv[2]);
+ if (!tmp) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ init_lsa_String(&backup_filename, tmp);
+
+ status = dcerpc_eventlog_BackupEventLogW(b, mem_ctx,
+ &handle,
+ &backup_filename,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ dcerpc_eventlog_CloseEventLog(b, mem_ctx, &handle, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_eventlog_loginfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ uint8_t *buffer = NULL;
+ uint32_t buf_size = 0;
+ uint32_t bytes_needed = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ printf("Usage: %s logname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = get_eventlog_handle(cli, mem_ctx, argv[1], &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ buffer = talloc_array(mem_ctx, uint8_t, bytes_needed);
+ if (buffer == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_eventlog_GetLogInformation(b, mem_ctx,
+ &handle,
+ 0, /* level */
+ buffer,
+ buf_size,
+ &bytes_needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, NT_STATUS_BUFFER_TOO_SMALL)) {
+ goto done;
+ }
+
+ buf_size = bytes_needed;
+ buffer = talloc_realloc(mem_ctx, buffer, uint8_t, bytes_needed);
+ if (buffer == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_eventlog_GetLogInformation(b, mem_ctx,
+ &handle,
+ 0, /* level */
+ buffer,
+ buf_size,
+ &bytes_needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ dcerpc_eventlog_CloseEventLog(b, mem_ctx, &handle, &result);
+
+ return status;
+}
+
+
+struct cmd_set eventlog_commands[] = {
+ {
+ .name = "EVENTLOG",
+ },
+ {
+ .name = "eventlog_readlog",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_readlog,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Read Eventlog",
+ .usage = "",
+ },
+ {
+ .name = "eventlog_numrecord",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_numrecords,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Get number of records",
+ .usage = "",
+ },
+ {
+ .name = "eventlog_oldestrecord",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_oldestrecord,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Get oldest record",
+ .usage = "",
+ },
+ {
+ .name = "eventlog_reportevent",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_reportevent,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Report event",
+ .usage = "",
+ },
+ {
+ .name = "eventlog_reporteventsource",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_reporteventsource,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Report event and source",
+ .usage = "",
+ },
+ {
+ .name = "eventlog_registerevsource",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_registerevsource,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Register event source",
+ .usage = "",
+ },
+ {
+ .name = "eventlog_backuplog",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_backuplog,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Backup Eventlog File",
+ .usage = "",
+ },
+ {
+ .name = "eventlog_loginfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_eventlog_loginfo,
+ .wfn = NULL,
+ .table = &ndr_table_eventlog,
+ .rpc_pipe = NULL,
+ .description = "Get Eventlog Information",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_fss.c b/source3/rpcclient/cmd_fss.c
new file mode 100644
index 0000000..1d7883e
--- /dev/null
+++ b/source3/rpcclient/cmd_fss.c
@@ -0,0 +1,759 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * File Server Remote VSS Protocol (FSRVP) client
+ *
+ * Copyright (C) David Disseldorp 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_fsrvp.h"
+#include "../librpc/gen_ndr/ndr_fsrvp_c.h"
+#include "../libcli/util/hresult.h"
+
+static const struct {
+ uint32_t error_code;
+ const char *error_str;
+} fss_errors[] = {
+ {
+ FSRVP_E_BAD_STATE,
+ "A method call was invalid because of the state of the server."
+ },
+ {
+ FSRVP_E_SHADOW_COPY_SET_IN_PROGRESS,
+ "A call was made to either \'SetContext\' or \'StartShadowCopySet\' while the creation of another shadow copy set is in progress."
+ },
+ {
+ FSRVP_E_NOT_SUPPORTED,
+ "The file store which contains the share to be shadow copied is not supported by the server."
+ },
+ {
+ FSRVP_E_WAIT_TIMEOUT,
+ "The wait for a shadow copy commit or expose operation has timed out."
+ },
+ {
+ FSRVP_E_WAIT_FAILED,
+ "The wait for a shadow copy commit expose operation has failed."
+ },
+ {
+ FSRVP_E_OBJECT_NOT_FOUND,
+ "The specified object does not exist."
+ },
+ {
+ FSRVP_E_UNSUPPORTED_CONTEXT,
+ "The specified context value is invalid."
+ },
+ {
+ FSRVP_E_SHADOWCOPYSET_ID_MISMATCH,
+ "The provided ShadowCopySetId does not exist."
+ },
+};
+
+struct fss_context_map {
+ uint32_t ctx_val;
+ const char *ctx_str;
+ const char *ctx_desc;
+};
+struct fss_context_map ctx_map[] = {
+ {
+ .ctx_val = FSRVP_CTX_BACKUP,
+ .ctx_str = "backup",
+ .ctx_desc = "auto-release, non-persistent shadow-copy.",
+ },
+ {
+ .ctx_val = FSRVP_CTX_FILE_SHARE_BACKUP,
+ .ctx_str = "file_share_backup",
+ .ctx_desc = "auto-release, non-persistent shadow-copy created "
+ "without writer involvement.",
+ },
+ {
+ .ctx_val = FSRVP_CTX_NAS_ROLLBACK,
+ .ctx_str = "nas_rollback",
+ .ctx_desc = "non-auto-release, persistent shadow-copy created "
+ "without writer involvement.",
+ },
+ {
+ .ctx_val = FSRVP_CTX_APP_ROLLBACK,
+ .ctx_str = "app_rollback",
+ .ctx_desc = "non-auto-release, persistent shadow-copy.",
+ },
+ { 0, NULL, NULL },
+};
+
+static const char *get_error_str(uint32_t code)
+{
+ static const char *default_err = "Unknown Error";
+ const char *result = default_err;
+ int i;
+ for (i = 0; i < ARRAY_SIZE(fss_errors); ++i) {
+ if (code == fss_errors[i].error_code) {
+ result = fss_errors[i].error_str;
+ break;
+ }
+ }
+ /* error isn't specific fsrvp one, check hresult errors */
+ if (result == default_err) {
+ const char *hres_err = hresult_errstr_const(HRES_ERROR(code));
+ if (hres_err) {
+ result = hres_err;
+ }
+ }
+ return result;
+};
+
+static bool map_fss_ctx_str(const char *ctx_str,
+ uint32_t *ctx_val)
+{
+ int i;
+
+ for (i = 0; ctx_map[i].ctx_str != NULL; i++) {
+ if (!strcmp(ctx_map[i].ctx_str, ctx_str)) {
+ *ctx_val = ctx_map[i].ctx_val;
+ return true;
+ }
+ }
+ return false;
+}
+
+static void cmd_fss_is_path_sup_usage(const char *script_name)
+{
+ printf("usage: %s [share_name]\n", script_name);
+}
+
+static NTSTATUS cmd_fss_is_path_sup(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ struct fss_IsPathSupported r;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ cmd_fss_is_path_sup_usage(argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ZERO_STRUCT(r);
+ r.in.ShareName = talloc_asprintf(mem_ctx, "%s\\%s\\",
+ cli->srv_name_slash, argv[1]);
+ if (r.in.ShareName == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dcerpc_fss_IsPathSupported_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("IsPathSupported failed with UNC %s\n",
+ r.in.ShareName));
+ return NT_STATUS_UNSUCCESSFUL;
+ } else if (r.out.result) {
+ DEBUG(0, ("failed IsPathSupported response: 0x%x - \"%s\"\n",
+ r.out.result, get_error_str(r.out.result)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ printf("UNC %s %s shadow copy requests\n", r.in.ShareName,
+ *r.out.SupportedByThisProvider ? "supports" : "does not support");
+
+ return NT_STATUS_OK;
+}
+
+static void cmd_fss_get_sup_version_usage(const char *script_name)
+{
+ printf("usage: %s\n", script_name);
+}
+
+static NTSTATUS cmd_fss_get_sup_version(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ struct fss_GetSupportedVersion r;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 1) {
+ cmd_fss_get_sup_version_usage(argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ZERO_STRUCT(r);
+ status = dcerpc_fss_GetSupportedVersion_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status) || (r.out.result != 0)) {
+ DEBUG(0, ("GetSupportedVersion failed: %s result: 0x%x\n",
+ nt_errstr(status), r.out.result));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ printf("server %s supports FSRVP versions from %u to %u\n",
+ cli->desthost, *r.out.MinVersion, *r.out.MaxVersion);
+
+ return NT_STATUS_OK;
+}
+
+static void cmd_fss_create_expose_usage(const char *script_name)
+{
+ int i;
+
+ printf("usage: %s [fss_context] [ro|rw] [share1] <share2> ...\n"
+ "[fss_context] values:\n", script_name);
+ for (i = 0; ctx_map[i].ctx_str != NULL; i++) {
+ printf("\t%s: %s\n", ctx_map[i].ctx_str, ctx_map[i].ctx_desc);
+ }
+}
+
+static NTSTATUS cmd_fss_create_expose_parse(TALLOC_CTX *mem_ctx, int argc,
+ const char **argv,
+ const char *desthost,
+ uint32_t *fss_ctx_val,
+ int *num_maps,
+ struct fssagent_share_mapping_1 **maps)
+{
+ int num_non_share_args = 3;
+ int num_share_args;
+ int i;
+ struct fssagent_share_mapping_1 *map_array;
+
+ if (argc < 4) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!map_fss_ctx_str(argv[1], fss_ctx_val)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!strcmp(argv[2], "rw")) {
+ /* shadow-copy is created as read-write */
+ *fss_ctx_val |= ATTR_AUTO_RECOVERY;
+ } else if (strcmp(argv[2], "ro")) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ num_share_args = argc - num_non_share_args;
+ map_array = talloc_array(mem_ctx, struct fssagent_share_mapping_1,
+ num_share_args);
+ if (map_array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_share_args; i++) {
+ /*
+ * A trailing slash should to be present in the request UNC,
+ * otherwise Windows Server 2012 FSRVP servers don't append
+ * a '$' to exposed hidden share shadow-copies. E.g.
+ * AddToShadowCopySet(UNC=\\server\hidden$)
+ * CommitShadowCopySet()
+ * ExposeShadowCopySet()
+ * -> new share = \\server\hidden$@{ShadowCopy.ShadowCopyId}
+ * But...
+ * AddToShadowCopySet(UNC=\\server\hidden$\)
+ * CommitShadowCopySet()
+ * ExposeShadowCopySet()
+ * -> new share = \\server\hidden$@{ShadowCopy.ShadowCopyId}$
+ */
+ map_array[i].ShareNameUNC = talloc_asprintf(mem_ctx,
+ "\\\\%s\\%s\\",
+ desthost,
+ argv[i + num_non_share_args]);
+ if (map_array[i].ShareNameUNC == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ *num_maps = num_share_args;
+ *maps = map_array;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_fss_abort(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct GUID *sc_set_id)
+{
+ NTSTATUS status;
+ struct fss_AbortShadowCopySet r_scset_abort;
+
+ ZERO_STRUCT(r_scset_abort);
+ r_scset_abort.in.ShadowCopySetId = *sc_set_id;
+ status = dcerpc_fss_AbortShadowCopySet_r(b, mem_ctx, &r_scset_abort);
+ if (!NT_STATUS_IS_OK(status) || (r_scset_abort.out.result != 0)) {
+ DEBUG(0, ("AbortShadowCopySet failed: %s result: 0x%x\n",
+ nt_errstr(status), r_scset_abort.out.result));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_fss_create_expose(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ struct fss_GetSupportedVersion r_version_get;
+ struct fss_SetContext r_context_set;
+ struct fss_StartShadowCopySet r_scset_start;
+ struct fss_PrepareShadowCopySet r_scset_prep;
+ struct fss_CommitShadowCopySet r_scset_commit;
+ struct fss_ExposeShadowCopySet r_scset_expose;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ time_t start_time;
+ TALLOC_CTX *tmp_ctx;
+ uint32_t fss_ctx_val;
+ int num_maps;
+ struct fssagent_share_mapping_1 *req_maps;
+ int i;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cmd_fss_create_expose_parse(tmp_ctx, argc, argv, cli->desthost,
+ &fss_ctx_val, &num_maps, &req_maps);
+ if (!NT_STATUS_IS_OK(status)) {
+ cmd_fss_create_expose_usage(argv[0]);
+ goto err_out;
+ }
+
+ /*
+ * PrepareShadowCopySet & CommitShadowCopySet often exceed the default
+ * 60 second dcerpc request timeout against Windows Server "8" Beta.
+ * ACHTUNG! dcerpc_binding_handle_set_timeout() value is interpreted as
+ * seconds on a source4 transport and as msecs here.
+ */
+ dcerpc_binding_handle_set_timeout(b, 240 * 1000);
+
+ for (i = 0; i < num_maps; i++) {
+ struct fss_IsPathSupported r_pathsupport_get;
+ r_pathsupport_get.in.ShareName = req_maps[i].ShareNameUNC;
+ status = dcerpc_fss_IsPathSupported_r(b, tmp_ctx, &r_pathsupport_get);
+ if (!NT_STATUS_IS_OK(status) || (r_pathsupport_get.out.result != 0)) {
+ DEBUG(0, ("IsPathSupported failed: %s result: 0x%x\n",
+ nt_errstr(status), r_pathsupport_get.out.result));
+ goto err_out;
+ }
+ if (!r_pathsupport_get.out.SupportedByThisProvider) {
+ printf("path %s does not supported shadow-copies\n",
+ req_maps[i].ShareNameUNC);
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto err_out;
+ }
+ }
+
+ ZERO_STRUCT(r_version_get);
+ status = dcerpc_fss_GetSupportedVersion_r(b, tmp_ctx, &r_version_get);
+ if (!NT_STATUS_IS_OK(status) || (r_version_get.out.result != 0)) {
+ DEBUG(0, ("GetSupportedVersion failed: %s result: 0x%x\n",
+ nt_errstr(status), r_version_get.out.result));
+ goto err_out;
+ }
+
+ ZERO_STRUCT(r_context_set);
+ r_context_set.in.Context = fss_ctx_val;
+ status = dcerpc_fss_SetContext_r(b, tmp_ctx, &r_context_set);
+ if (!NT_STATUS_IS_OK(status) || (r_context_set.out.result != 0)) {
+ DEBUG(0, ("SetContext failed: %s result: 0x%x\n",
+ nt_errstr(status), r_context_set.out.result));
+ goto err_out;
+ }
+
+ ZERO_STRUCT(r_scset_start);
+ r_scset_start.in.ClientShadowCopySetId = GUID_random();
+ status = dcerpc_fss_StartShadowCopySet_r(b, tmp_ctx, &r_scset_start);
+ if (!NT_STATUS_IS_OK(status) || (r_scset_start.out.result != 0)) {
+ DEBUG(0, ("StartShadowCopySet failed: %s result: 0x%x\n",
+ nt_errstr(status), r_scset_start.out.result));
+ goto err_out;
+ }
+ printf("%s: shadow-copy set created\n",
+ GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId));
+
+ for (i = 0; i < num_maps; i++) {
+ struct fss_AddToShadowCopySet r_scset_add;
+ r_scset_add.in.ClientShadowCopyId = GUID_random();
+ r_scset_add.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId;
+ r_scset_add.in.ShareName = req_maps[i].ShareNameUNC;
+ status = dcerpc_fss_AddToShadowCopySet_r(b, tmp_ctx, &r_scset_add);
+ if (!NT_STATUS_IS_OK(status) || (r_scset_add.out.result != 0)) {
+ DEBUG(0, ("AddToShadowCopySet failed: %s result: 0x%x\n",
+ nt_errstr(status), r_scset_add.out.result));
+ goto err_sc_set_abort;
+ }
+ printf("%s(%s): %s shadow-copy added to set\n",
+ GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId),
+ GUID_string(tmp_ctx, r_scset_add.out.pShadowCopyId),
+ r_scset_add.in.ShareName);
+ req_maps[i].ShadowCopySetId = *r_scset_start.out.pShadowCopySetId;
+ req_maps[i].ShadowCopyId = *r_scset_add.out.pShadowCopyId;
+ }
+
+ start_time = time_mono(NULL);
+ ZERO_STRUCT(r_scset_prep);
+ r_scset_prep.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId;
+ r_scset_prep.in.TimeOutInMilliseconds = (240 * 1000);
+ status = dcerpc_fss_PrepareShadowCopySet_r(b, tmp_ctx, &r_scset_prep);
+ if (!NT_STATUS_IS_OK(status) || (r_scset_prep.out.result != 0)) {
+ DEBUG(0, ("PrepareShadowCopySet failed: %s result: 0x%x\n",
+ nt_errstr(status), r_scset_prep.out.result));
+ goto err_sc_set_abort;
+ }
+ printf("%s: prepare completed in %llu secs\n",
+ GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId),
+ (long long unsigned int)(time_mono(NULL) - start_time));
+
+ start_time = time_mono(NULL);
+ ZERO_STRUCT(r_scset_commit);
+ r_scset_commit.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId;
+ r_scset_commit.in.TimeOutInMilliseconds = (180 * 1000); /* win8 */
+ status = dcerpc_fss_CommitShadowCopySet_r(b, tmp_ctx, &r_scset_commit);
+ if (!NT_STATUS_IS_OK(status) || (r_scset_commit.out.result != 0)) {
+ DEBUG(0, ("CommitShadowCopySet failed: %s result: 0x%x\n",
+ nt_errstr(status), r_scset_commit.out.result));
+ goto err_sc_set_abort;
+ }
+ printf("%s: commit completed in %llu secs\n",
+ GUID_string(tmp_ctx, r_scset_start.out.pShadowCopySetId),
+ (long long unsigned int)(time_mono(NULL) - start_time));
+
+ ZERO_STRUCT(r_scset_expose);
+ r_scset_expose.in.ShadowCopySetId = *r_scset_start.out.pShadowCopySetId;
+ r_scset_expose.in.TimeOutInMilliseconds = (120 * 1000); /* win8 */
+ status = dcerpc_fss_ExposeShadowCopySet_r(b, tmp_ctx, &r_scset_expose);
+ if (!NT_STATUS_IS_OK(status) || (r_scset_expose.out.result != 0)) {
+ DEBUG(0, ("ExposeShadowCopySet failed: %s result: 0x%x\n",
+ nt_errstr(status), r_scset_expose.out.result));
+ goto err_out;
+ }
+
+ for (i = 0; i < num_maps; i++) {
+ struct fss_GetShareMapping r_sharemap_get;
+ struct fssagent_share_mapping_1 *map;
+ r_sharemap_get.in.ShadowCopyId = req_maps[i].ShadowCopyId;
+ r_sharemap_get.in.ShadowCopySetId = req_maps[i].ShadowCopySetId;
+ r_sharemap_get.in.ShareName = req_maps[i].ShareNameUNC;
+ r_sharemap_get.in.Level = 1;
+ status = dcerpc_fss_GetShareMapping_r(b, tmp_ctx, &r_sharemap_get);
+ if (!NT_STATUS_IS_OK(status) || (r_sharemap_get.out.result != 0)) {
+ DEBUG(0, ("GetShareMapping failed: %s result: 0x%x\n",
+ nt_errstr(status), r_sharemap_get.out.result));
+ goto err_out;
+ }
+ map = r_sharemap_get.out.ShareMapping->ShareMapping1;
+ printf("%s(%s): share %s exposed as a snapshot of %s\n",
+ GUID_string(tmp_ctx, &map->ShadowCopySetId),
+ GUID_string(tmp_ctx, &map->ShadowCopyId),
+ map->ShadowCopyShareName, map->ShareNameUNC);
+ }
+
+ talloc_free(tmp_ctx);
+ return NT_STATUS_OK;
+
+err_sc_set_abort:
+ cmd_fss_abort(tmp_ctx, b, r_scset_start.out.pShadowCopySetId);
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static void cmd_fss_delete_usage(const char *script_name)
+{
+ printf("usage: %s [base_share] [shadow_copy_set_id] [shadow_copy_id]\n",
+ script_name);
+}
+
+static NTSTATUS cmd_fss_delete(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct fss_DeleteShareMapping r_sharemap_del;
+ const char *sc_set_id;
+ const char *sc_id;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ if (argc < 4) {
+ cmd_fss_delete_usage(argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ sc_set_id = argv[2];
+ sc_id = argv[3];
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(r_sharemap_del);
+ r_sharemap_del.in.ShareName = talloc_asprintf(tmp_ctx, "\\\\%s\\%s\\",
+ cli->desthost, argv[1]);
+ if (r_sharemap_del.in.ShareName == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_out;
+ }
+ status = GUID_from_string(sc_set_id, &r_sharemap_del.in.ShadowCopySetId);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Invalid shadow_copy_set_id parameter\n"));
+ goto err_out;
+ }
+ status = GUID_from_string(sc_id, &r_sharemap_del.in.ShadowCopyId);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Invalid shadow_copy_id parameter\n"));
+ goto err_out;
+ }
+ status = dcerpc_fss_DeleteShareMapping_r(b, tmp_ctx, &r_sharemap_del);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("DeleteShareMapping failed\n"));
+ goto err_out;
+ } else if (r_sharemap_del.out.result != 0) {
+ DEBUG(0, ("failed DeleteShareMapping response: 0x%x\n",
+ r_sharemap_del.out.result));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_out;
+ }
+
+ printf("%s(%s): %s shadow-copy deleted\n",
+ sc_set_id, sc_id, r_sharemap_del.in.ShareName);
+
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static void cmd_fss_is_shadow_copied_usage(const char *script_name)
+{
+ printf("usage: %s [share_name]\n", script_name);
+}
+
+static NTSTATUS cmd_fss_is_shadow_copied(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ struct fss_IsPathShadowCopied r;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ cmd_fss_is_shadow_copied_usage(argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ZERO_STRUCT(r);
+ r.in.ShareName = talloc_asprintf(mem_ctx, "%s\\%s\\",
+ cli->srv_name_slash, argv[1]);
+ if (r.in.ShareName == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dcerpc_fss_IsPathShadowCopied_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("IsPathShadowCopied failed with UNC %s\n",
+ r.in.ShareName));
+ return NT_STATUS_UNSUCCESSFUL;
+ } else if (r.out.result) {
+ DEBUG(0, ("failed IsPathShadowCopied response: 0x%x\n",
+ r.out.result));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ printf("UNC %s %s an associated shadow-copy with compatibility 0x%x\n",
+ r.in.ShareName,
+ *r.out.ShadowCopyPresent ? "has" : "does not have",
+ *r.out.ShadowCopyCompatibility);
+
+ return NT_STATUS_OK;
+}
+
+static void cmd_fss_get_mapping_usage(const char *script_name)
+{
+ printf("usage: %s [base_share] [shadow_copy_set_id] [shadow_copy_id]\n",
+ script_name);
+}
+
+static NTSTATUS cmd_fss_get_mapping(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct fss_GetShareMapping r_sharemap_get;
+ const char *sc_set_id;
+ const char *sc_id;
+ struct fssagent_share_mapping_1 *map;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+
+ if (argc < 4) {
+ cmd_fss_get_mapping_usage(argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ sc_set_id = argv[2];
+ sc_id = argv[3];
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(r_sharemap_get);
+ r_sharemap_get.in.ShareName = talloc_asprintf(tmp_ctx, "\\\\%s\\%s\\",
+ cli->desthost, argv[1]);
+ if (r_sharemap_get.in.ShareName == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_out;
+ }
+ status = GUID_from_string(sc_set_id, &r_sharemap_get.in.ShadowCopySetId);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Invalid shadow_copy_set_id parameter\n"));
+ goto err_out;
+ }
+ status = GUID_from_string(sc_id, &r_sharemap_get.in.ShadowCopyId);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Invalid shadow_copy_id parameter\n"));
+ goto err_out;
+ }
+ r_sharemap_get.in.Level = 1;
+ status = dcerpc_fss_GetShareMapping_r(b, tmp_ctx, &r_sharemap_get);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("GetShareMapping failed\n"));
+ goto err_out;
+ } else if (r_sharemap_get.out.result != 0) {
+ DEBUG(0, ("failed GetShareMapping response: 0x%x\n",
+ r_sharemap_get.out.result));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_out;
+ }
+
+ map = r_sharemap_get.out.ShareMapping->ShareMapping1;
+ printf("%s(%s): share %s is a shadow-copy of %s at %s\n",
+ GUID_string(tmp_ctx, &map->ShadowCopySetId),
+ GUID_string(tmp_ctx, &map->ShadowCopyId),
+ map->ShadowCopyShareName, map->ShareNameUNC,
+ nt_time_string(tmp_ctx, map->tstamp));
+
+err_out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static void cmd_fss_recov_complete_usage(const char *script_name)
+{
+ printf("usage: %s [shadow_copy_set_id]\n", script_name);
+}
+
+static NTSTATUS cmd_fss_recov_complete(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ struct fss_RecoveryCompleteShadowCopySet r;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ const char *sc_set_id;
+
+ if (argc != 2) {
+ cmd_fss_recov_complete_usage(argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ sc_set_id = argv[1];
+
+ ZERO_STRUCT(r);
+ status = GUID_from_string(sc_set_id, &r.in.ShadowCopySetId);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Invalid shadow_copy_set_id\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_fss_RecoveryCompleteShadowCopySet_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status) || (r.out.result != 0)) {
+ DEBUG(0, ("RecoveryCompleteShadowCopySet failed: %s "
+ "result: 0x%x\n", nt_errstr(status), r.out.result));
+ return status;
+ }
+ printf("%s: shadow-copy set marked recovery complete\n", sc_set_id);
+
+ return NT_STATUS_OK;
+}
+
+/* List of commands exported by this module */
+struct cmd_set fss_commands[] = {
+
+ {
+ .name = "FSRVP",
+ },
+
+ {
+ .name = "fss_is_path_sup",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_fss_is_path_sup,
+ .table = &ndr_table_FileServerVssAgent,
+ .rpc_pipe = NULL,
+ .description = "Check whether a share supports shadow-copy "
+ "requests",
+ .usage = "",
+ },
+ {
+ .name = "fss_get_sup_version",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_fss_get_sup_version,
+ .table = &ndr_table_FileServerVssAgent,
+ .rpc_pipe = NULL,
+ .description = "Get supported FSRVP version from server",
+ .usage = "",
+ },
+ {
+ .name = "fss_create_expose",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_fss_create_expose,
+ .table = &ndr_table_FileServerVssAgent,
+ .rpc_pipe = NULL,
+ .description = "Request shadow-copy creation and exposure",
+ .usage = "",
+ },
+ {
+ .name = "fss_delete",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_fss_delete,
+ .table = &ndr_table_FileServerVssAgent,
+ .rpc_pipe = NULL,
+ .description = "Request shadow-copy share deletion",
+ .usage = "",
+ },
+ {
+ .name = "fss_has_shadow_copy",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_fss_is_shadow_copied,
+ .table = &ndr_table_FileServerVssAgent,
+ .rpc_pipe = NULL,
+ .description = "Check for an associated share shadow-copy",
+ .usage = "",
+ },
+ {
+ .name = "fss_get_mapping",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_fss_get_mapping,
+ .table = &ndr_table_FileServerVssAgent,
+ .rpc_pipe = NULL,
+ .description = "Get shadow-copy share mapping information",
+ .usage = "",
+ },
+ {
+ .name = "fss_recovery_complete",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_fss_recov_complete,
+ .table = &ndr_table_FileServerVssAgent,
+ .rpc_pipe = NULL,
+ .description = "Flag read-write snapshot as recovery complete, "
+ "allowing further shadow-copy requests",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_iremotewinspool.c b/source3/rpcclient/cmd_iremotewinspool.c
new file mode 100644
index 0000000..5a8096b
--- /dev/null
+++ b/source3/rpcclient/cmd_iremotewinspool.c
@@ -0,0 +1,195 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) 2013-2016 Guenther Deschner <gd@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_winspool.h"
+#include "libsmb/libsmb.h"
+#include "auth/gensec/gensec.h"
+#include "auth/credentials/credentials.h"
+#include "rpc_client/init_spoolss.h"
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_iremotewinspool_async_open_printer(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status;
+ WERROR werror;
+ struct policy_handle hnd;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct spoolss_UserLevelCtr client_info_ctr;
+ struct spoolss_UserLevel1 level1;
+ uint32_t access_mask = PRINTER_ALL_ACCESS;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct GUID uuid;
+ struct winspool_AsyncOpenPrinter r;
+ struct cli_credentials *creds = gensec_get_credentials(cli->auth->auth_ctx);
+
+ if (argc < 2) {
+ printf("Usage: %s <printername> [access_mask]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 3) {
+ sscanf(argv[2], "%x", &access_mask);
+ }
+
+ status = GUID_from_string(IREMOTEWINSPOOL_OBJECT_GUID, &uuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ ZERO_STRUCT(devmode_ctr);
+
+ werror = spoolss_init_spoolss_UserLevel1(mem_ctx,
+ cli_credentials_get_username(creds),
+ &level1);
+ if (!W_ERROR_IS_OK(werror)) {
+ return werror;
+ }
+
+ level1.processor = PROCESSOR_ARCHITECTURE_AMD64;
+
+ client_info_ctr.level = 1;
+ client_info_ctr.user_info.level1 = &level1;
+
+ r.in.pPrinterName = argv[1];
+ r.in.pDatatype = "RAW";
+ r.in.pDevModeContainer = &devmode_ctr;
+ r.in.AccessRequired = access_mask;
+ r.in.pClientInfo = &client_info_ctr;
+ r.out.pHandle = &hnd;
+
+ /* Open the printer handle */
+
+ status = dcerpc_binding_handle_call(b,
+ &uuid,
+ &ndr_table_iremotewinspool,
+ NDR_WINSPOOL_ASYNCOPENPRINTER,
+ mem_ctx,
+ &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(r.out.result)) {
+ return r.out.result;
+ }
+
+ printf("Printer %s opened successfully\n", argv[1]);
+
+ return WERR_OK;
+}
+
+static WERROR cmd_iremotewinspool_async_core_printer_driver_installed(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct GUID uuid, core_printer_driver_guid;
+ struct winspool_AsyncCorePrinterDriverInstalled r;
+ const char *guid_str = SPOOLSS_CORE_PRINT_PACKAGE_FILES_XPSDRV;
+ const char *architecture = SPOOLSS_ARCHITECTURE_x64;
+ int32_t pbDriverInstalled;
+
+ if (argc > 4) {
+ printf("Usage: %s <CORE_PRINTER_DRIVER_GUID> [architecture]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ guid_str = argv[1];
+ }
+
+ if (argc >= 3) {
+ architecture = argv[2];
+ }
+
+ status = GUID_from_string(IREMOTEWINSPOOL_OBJECT_GUID, &uuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ status = GUID_from_string(guid_str, &core_printer_driver_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ r.in.pszServer = NULL;
+ r.in.pszEnvironment = architecture;
+ r.in.CoreDriverGUID = core_printer_driver_guid;
+ r.in.ftDriverDate = 0;
+ r.in.dwlDriverVersion = 0;
+ r.out.pbDriverInstalled = &pbDriverInstalled;
+
+ status = dcerpc_binding_handle_call(b,
+ &uuid,
+ &ndr_table_iremotewinspool,
+ NDR_WINSPOOL_ASYNCCOREPRINTERDRIVERINSTALLED,
+ mem_ctx,
+ &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!HRES_IS_OK(r.out.result)) {
+ return W_ERROR(WIN32_FROM_HRESULT(r.out.result));
+ }
+
+ printf("Core Printer Driver %s is%s installed\n", guid_str,
+ *r.out.pbDriverInstalled ? "" : " NOT");
+
+ return WERR_OK;
+}
+
+/* List of commands exported by this module */
+struct cmd_set iremotewinspool_commands[] = {
+
+ {
+ .name = "IRemoteWinspool",
+ },
+
+ {
+ .name = "winspool_AsyncOpenPrinter",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_iremotewinspool_async_open_printer,
+ .table = &ndr_table_iremotewinspool,
+ .rpc_pipe = NULL,
+ .description = "Open printer handle",
+ .usage = "",
+ },
+
+ {
+ .name = "winspool_AsyncCorePrinterDriverInstalled",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_iremotewinspool_async_core_printer_driver_installed,
+ .table = &ndr_table_iremotewinspool,
+ .rpc_pipe = NULL,
+ .description = "Query Core Printer Driver Installed",
+ .usage = "",
+ },
+
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_lsarpc.c b/source3/rpcclient/cmd_lsarpc.c
new file mode 100644
index 0000000..5374c9a
--- /dev/null
+++ b/source3/rpcclient/cmd_lsarpc.c
@@ -0,0 +1,3000 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Rafal Szczesniak 2002
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+
+/* useful function to allow entering a name instead of a SID and
+ * looking it up automatically */
+static NTSTATUS name_to_sid(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid *sid,
+ const char *name)
+{
+ struct policy_handle pol;
+ enum lsa_SidType *sid_types;
+ NTSTATUS status, result;
+ struct dom_sid *sids;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* maybe its a raw SID */
+ if (strncmp(name, "S-", 2) == 0 &&
+ string_to_sid(sid, name)) {
+ return NT_STATUS_OK;
+ }
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = rpccli_lsa_lookup_names(cli, mem_ctx, &pol, 1, &name, NULL, 1, &sids, &sid_types);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ *sid = sids[0];
+
+done:
+ return status;
+}
+
+static void display_query_info_1(struct lsa_AuditLogInfo *r)
+{
+ d_printf("percent_full:\t%d\n", r->percent_full);
+ d_printf("maximum_log_size:\t%d\n", r->maximum_log_size);
+ d_printf("retention_time:\t%lld\n", (long long)r->retention_time);
+ d_printf("shutdown_in_progress:\t%d\n", r->shutdown_in_progress);
+ d_printf("time_to_shutdown:\t%lld\n", (long long)r->time_to_shutdown);
+ d_printf("next_audit_record:\t%d\n", r->next_audit_record);
+}
+
+static void display_query_info_2(struct lsa_AuditEventsInfo *r)
+{
+ int i;
+ d_printf("Auditing enabled:\t%d\n", r->auditing_mode);
+ d_printf("Auditing categories:\t%d\n", r->count);
+ d_printf("Auditsettings:\n");
+ for (i=0; i<r->count; i++) {
+ const char *val = audit_policy_str(talloc_tos(), r->settings[i]);
+ const char *policy = audit_description_str(i);
+ d_printf("%s:\t%s\n", policy, val);
+ }
+}
+
+static void display_query_info_3(struct lsa_DomainInfo *r)
+{
+ struct dom_sid_buf buf;
+ d_printf("Domain Name: %s\n", r->name.string);
+ d_printf("Domain Sid: %s\n", dom_sid_str_buf(r->sid, &buf));
+}
+
+static void display_query_info_5(struct lsa_DomainInfo *r)
+{
+ struct dom_sid_buf buf;
+ d_printf("Domain Name: %s\n", r->name.string);
+ d_printf("Domain Sid: %s\n", dom_sid_str_buf(r->sid, &buf));
+}
+
+static void display_query_info_10(struct lsa_AuditFullSetInfo *r)
+{
+ d_printf("Shutdown on full: %d\n", r->shutdown_on_full);
+}
+
+static void display_query_info_11(struct lsa_AuditFullQueryInfo *r)
+{
+ d_printf("Shutdown on full: %d\n", r->shutdown_on_full);
+ d_printf("Log is full: %d\n", r->log_is_full);
+}
+
+static void display_query_info_12(struct lsa_DnsDomainInfo *r)
+{
+ struct dom_sid_buf buf;
+ d_printf("Domain NetBios Name: %s\n", r->name.string);
+ d_printf("Domain DNS Name: %s\n", r->dns_domain.string);
+ d_printf("Domain Forest Name: %s\n", r->dns_forest.string);
+ d_printf("Domain Sid: %s\n", dom_sid_str_buf(r->sid, &buf));
+ d_printf("Domain GUID: %s\n", GUID_string(talloc_tos(),
+ &r->domain_guid));
+}
+
+static void display_lsa_query_info(union lsa_PolicyInformation *info,
+ enum lsa_PolicyInfo level)
+{
+ switch (level) {
+ case 1:
+ display_query_info_1(&info->audit_log);
+ break;
+ case 2:
+ display_query_info_2(&info->audit_events);
+ break;
+ case 3:
+ display_query_info_3(&info->domain);
+ break;
+ case 5:
+ display_query_info_5(&info->account_domain);
+ break;
+ case 10:
+ display_query_info_10(&info->auditfullset);
+ break;
+ case 11:
+ display_query_info_11(&info->auditfullquery);
+ break;
+ case 12:
+ display_query_info_12(&info->dns);
+ break;
+ default:
+ printf("can't display info level: %d\n", level);
+ break;
+ }
+}
+
+static NTSTATUS cmd_lsa_query_info_policy(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ uint32_t info_class = 3;
+
+ if (argc > 2) {
+ printf("Usage: %s [info_class]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2)
+ info_class = atoi(argv[1]);
+
+ switch (info_class) {
+ case 12: {
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ status = dcerpc_lsa_open_policy_fallback(
+ b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy2(b, mem_ctx,
+ &pol,
+ info_class,
+ &info,
+ &result);
+ break;
+ }
+ default:
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ info_class,
+ &info,
+ &result);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = result;
+ if (NT_STATUS_IS_OK(result)) {
+ display_lsa_query_info(info, info_class);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ done:
+ return status;
+}
+
+/* Resolve a list of names to a list of sids */
+
+static NTSTATUS cmd_lsa_lookup_names(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dom_sid *sids;
+ enum lsa_SidType *types;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc == 1) {
+ printf("Usage: %s [name1 [name2 [...]]]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ LSA_POLICY_LOOKUP_NAMES,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = rpccli_lsa_lookup_names(cli, mem_ctx, &pol, argc - 1,
+ (const char**)(argv + 1), NULL, 1, &sids, &types);
+
+ if (!NT_STATUS_IS_OK(status) && NT_STATUS_V(status) !=
+ NT_STATUS_V(STATUS_SOME_UNMAPPED))
+ goto done;
+
+ status = NT_STATUS_OK;
+
+ /* Print results */
+
+ for (i = 0; i < (argc - 1); i++) {
+ struct dom_sid_buf sid_str;
+ printf("%s %s (%s: %d)\n",
+ argv[i + 1],
+ dom_sid_str_buf(&sids[i], &sid_str),
+ sid_type_lookup(types[i]),
+ types[i]);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ done:
+ return status;
+}
+
+/* Resolve a list of names to a list of sids */
+
+static NTSTATUS cmd_lsa_lookup_names_level(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dom_sid *sids = NULL;
+ enum lsa_SidType *types = NULL;
+ int i, level;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3) {
+ printf("Usage: %s [level] [name1 [name2 [...]]]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ LSA_POLICY_LOOKUP_NAMES,
+ &pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ level = atoi(argv[1]);
+
+ status = rpccli_lsa_lookup_names(cli, mem_ctx, &pol, argc - 2,
+ (const char**)(argv + 2), NULL, level, &sids, &types);
+
+ if (!NT_STATUS_IS_OK(status) && NT_STATUS_V(status) !=
+ NT_STATUS_V(STATUS_SOME_UNMAPPED))
+ {
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+ /* Print results */
+
+ for (i = 0; i < (argc - 2); i++) {
+ struct dom_sid_buf sid_str;
+ printf("%s %s (%s: %d)\n",
+ argv[i + 2],
+ dom_sid_str_buf(&sids[i], &sid_str),
+ sid_type_lookup(types[i]),
+ types[i]);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ done:
+ return status;
+}
+
+static NTSTATUS cmd_lsa_lookup_names4(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+
+ uint32_t num_names;
+ struct lsa_String *names;
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransSidArray3 sids;
+ uint32_t count = 0;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc == 1) {
+ printf("Usage: %s [name1 [name2 [...]]]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(sids);
+
+ num_names = argc-1;
+ names = talloc_array(mem_ctx, struct lsa_String, num_names);
+ NT_STATUS_HAVE_NO_MEMORY(names);
+
+ for (i=0; i < num_names; i++) {
+ init_lsa_String(&names[i], argv[i+1]);
+ }
+
+ status = dcerpc_lsa_LookupNames4(b, mem_ctx,
+ num_names,
+ names,
+ &domains,
+ &sids,
+ 1,
+ &count,
+ 0,
+ 0,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ if (sids.count != num_names) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ for (i = 0; i < sids.count; i++) {
+ struct dom_sid_buf sid_str;
+ printf("%s %s (%s: %d)\n",
+ argv[i+1],
+ dom_sid_str_buf(sids.sids[i].sid, &sid_str),
+ sid_type_lookup(sids.sids[i].sid_type),
+ sids.sids[i].sid_type);
+ }
+
+ return status;
+}
+
+/* Resolve a list of SIDs to a list of names */
+
+static NTSTATUS cmd_lsa_lookup_sids(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dom_sid *sids;
+ char **domains;
+ char **names;
+ enum lsa_SidType *types;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc == 1) {
+ printf("Usage: %s [sid1 [sid2 [...]]]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ LSA_POLICY_LOOKUP_NAMES,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ /* Convert arguments to sids */
+
+ sids = talloc_array(mem_ctx, struct dom_sid, argc - 1);
+
+ if (!sids) {
+ printf("could not allocate memory for %d sids\n", argc - 1);
+ goto done;
+ }
+
+ for (i = 0; i < argc - 1; i++)
+ if (!string_to_sid(&sids[i], argv[i + 1])) {
+ status = NT_STATUS_INVALID_SID;
+ goto done;
+ }
+
+ /* Lookup the SIDs */
+
+ status = rpccli_lsa_lookup_sids(cli, mem_ctx, &pol, argc - 1, sids,
+ &domains, &names, &types);
+
+ if (!NT_STATUS_IS_OK(status) && NT_STATUS_V(status) !=
+ NT_STATUS_V(STATUS_SOME_UNMAPPED))
+ goto done;
+
+ status = NT_STATUS_OK;
+
+ /* Print results */
+
+ for (i = 0; i < (argc - 1); i++) {
+ struct dom_sid_buf sid_str;
+
+ dom_sid_str_buf(&sids[i], &sid_str);
+ if (types[i] == SID_NAME_DOMAIN) {
+ printf("%s %s (%d)\n", sid_str.buf,
+ domains[i] ? domains[i] : "*unknown*",
+ types[i]);
+ } else {
+ printf("%s %s\\%s (%d)\n", sid_str.buf,
+ domains[i] ? domains[i] : "*unknown*",
+ names[i] ? names[i] : "*unknown*",
+ types[i]);
+ }
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ done:
+ return status;
+}
+
+static NTSTATUS cmd_lsa_lookup_sids_level(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dom_sid *sids = NULL;
+ char **domains = NULL;
+ char **names = NULL;
+ enum lsa_SidType *types = NULL;
+ int i, level;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3) {
+ printf("Usage: %s [level] [sid1 [sid2 [...]]]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ LSA_POLICY_LOOKUP_NAMES,
+ &pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ level = atoi(argv[1]);
+
+ /* Convert arguments to sids */
+
+ sids = talloc_array(mem_ctx, struct dom_sid, argc - 2);
+ if (sids == NULL) {
+ printf("could not allocate memory for %d sids\n", argc - 2);
+ goto done;
+ }
+
+ for (i = 0; i < argc - 2; i++) {
+ if (!string_to_sid(&sids[i], argv[i + 2])) {
+ status = NT_STATUS_INVALID_SID;
+ goto done;
+ }
+ }
+
+ /* Lookup the SIDs */
+
+ status = dcerpc_lsa_lookup_sids_generic(cli->binding_handle,
+ mem_ctx,
+ &pol,
+ argc - 2,
+ sids,
+ level,
+ &domains,
+ &names,
+ &types,
+ false,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = result;
+
+ if (!NT_STATUS_IS_OK(status) && NT_STATUS_V(status) !=
+ NT_STATUS_V(STATUS_SOME_UNMAPPED))
+ {
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+ /* Print results */
+
+ for (i = 0; i < (argc - 2); i++) {
+ struct dom_sid_buf sid_str;
+
+ dom_sid_str_buf(&sids[i], &sid_str);
+ if (types[i] == SID_NAME_DOMAIN) {
+ printf("%s %s (%d)\n", sid_str.buf,
+ domains[i] ? domains[i] : "*unknown*",
+ types[i]);
+ } else {
+ printf("%s %s\\%s (%d)\n", sid_str.buf,
+ domains[i] ? domains[i] : "*unknown*",
+ names[i] ? names[i] : "*unknown*",
+ types[i]);
+ }
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ done:
+ return status;
+}
+
+/* Resolve a list of SIDs to a list of names */
+
+static NTSTATUS cmd_lsa_lookup_sids3(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL, result;
+ int i;
+ struct lsa_SidArray sids;
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransNameArray2 names;
+ uint32_t count = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc == 1) {
+ printf("Usage: %s [sid1 [sid2 [...]]]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(names);
+
+ /* Convert arguments to sids */
+
+ sids.num_sids = argc-1;
+ sids.sids = talloc_array(mem_ctx, struct lsa_SidPtr, sids.num_sids);
+ if (!sids.sids) {
+ printf("could not allocate memory for %d sids\n", sids.num_sids);
+ goto done;
+ }
+
+ for (i = 0; i < sids.num_sids; i++) {
+ sids.sids[i].sid = talloc(sids.sids, struct dom_sid);
+ if (sids.sids[i].sid == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ if (!string_to_sid(sids.sids[i].sid, argv[i+1])) {
+ status = NT_STATUS_INVALID_SID;
+ goto done;
+ }
+ }
+
+ /* Lookup the SIDs */
+ status = dcerpc_lsa_LookupSids3(b, mem_ctx,
+ &sids,
+ &domains,
+ &names,
+ 1,
+ &count,
+ 0,
+ 0,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result) && NT_STATUS_V(result) !=
+ NT_STATUS_V(STATUS_SOME_UNMAPPED)) {
+ status = result;
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+ /* Print results */
+
+ for (i = 0; i < names.count; i++) {
+ struct dom_sid_buf sid_str;
+
+ if (i >= sids.num_sids) {
+ break;
+ }
+ printf("%s %s (%d)\n",
+ dom_sid_str_buf(sids.sids[i].sid, &sid_str),
+ names.names[i].name.string,
+ names.names[i].sid_type);
+ }
+
+ done:
+ return status;
+}
+
+
+/* Enumerate list of trusted domains */
+
+static NTSTATUS cmd_lsa_enum_trust_dom(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct lsa_DomainList domain_list;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* defaults, but may be changed using params */
+ uint32_t enum_ctx = 0;
+ int i;
+ uint32_t max_size = (uint32_t)-1;
+
+ if (argc > 2) {
+ printf("Usage: %s [enum context (0)]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2 && argv[1]) {
+ enum_ctx = atoi(argv[2]);
+ }
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ LSA_POLICY_VIEW_LOCAL_INFORMATION,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = STATUS_MORE_ENTRIES;
+
+ while (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+
+ /* Lookup list of trusted domains */
+
+ status = dcerpc_lsa_EnumTrustDom(b, mem_ctx,
+ &pol,
+ &enum_ctx,
+ &domain_list,
+ max_size,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, NT_STATUS_NO_MORE_ENTRIES) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results: list of names and sids returned in this
+ * response. */
+ for (i = 0; i < domain_list.count; i++) {
+ struct dom_sid_buf sid_str;
+
+ printf("%s %s\n",
+ domain_list.domains[i].name.string ?
+ domain_list.domains[i].name.string : "*unknown*",
+ dom_sid_str_buf(domain_list.domains[i].sid,
+ &sid_str));
+ }
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ done:
+ return status;
+}
+
+/* Enumerates privileges */
+
+static NTSTATUS cmd_lsa_enum_privilege(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct lsa_PrivArray priv_array;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ uint32_t enum_context=0;
+ uint32_t pref_max_length=0x1000;
+ int i;
+
+ if (argc > 3) {
+ printf("Usage: %s [enum context] [max length]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc>=2)
+ enum_context=atoi(argv[1]);
+
+ if (argc==3)
+ pref_max_length=atoi(argv[2]);
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_EnumPrivs(b, mem_ctx,
+ &pol,
+ &enum_context,
+ &priv_array,
+ pref_max_length,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results */
+ printf("found %d privileges\n\n", priv_array.count);
+
+ for (i = 0; i < priv_array.count; i++) {
+ printf("%s \t\t%d:%d (0x%x:0x%x)\n",
+ priv_array.privs[i].name.string ? priv_array.privs[i].name.string : "*unknown*",
+ priv_array.privs[i].luid.high,
+ priv_array.privs[i].luid.low,
+ priv_array.privs[i].luid.high,
+ priv_array.privs[i].luid.low);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ done:
+ return status;
+}
+
+/* Get privilege name */
+
+static NTSTATUS cmd_lsa_get_dispname(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ uint16_t lang_id=0;
+ uint16_t lang_id_sys=0;
+ uint16_t lang_id_desc;
+ struct lsa_String lsa_name;
+ struct lsa_StringLarge *description = NULL;
+
+ if (argc != 2) {
+ printf("Usage: %s privilege name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ init_lsa_String(&lsa_name, argv[1]);
+
+ status = dcerpc_lsa_LookupPrivDisplayName(b, mem_ctx,
+ &pol,
+ &lsa_name,
+ lang_id,
+ lang_id_sys,
+ &description,
+ &lang_id_desc,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results */
+ printf("%s -> %s (language: 0x%x)\n", argv[1], description->string, lang_id_desc);
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ done:
+ return status;
+}
+
+/* Enumerate the LSA SIDS */
+
+static NTSTATUS cmd_lsa_enum_sids(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ uint32_t enum_context=0;
+ uint32_t pref_max_length=0x1000;
+ struct lsa_SidArray sid_array;
+ int i;
+
+ if (argc > 3) {
+ printf("Usage: %s [enum context] [max length]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc>=2)
+ enum_context=atoi(argv[1]);
+
+ if (argc==3)
+ pref_max_length=atoi(argv[2]);
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_EnumAccounts(b, mem_ctx,
+ &pol,
+ &enum_context,
+ &sid_array,
+ pref_max_length,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results */
+ printf("found %d SIDs\n\n", sid_array.num_sids);
+
+ for (i = 0; i < sid_array.num_sids; i++) {
+ struct dom_sid_buf sid_str;
+
+ printf("%s\n",
+ dom_sid_str_buf(sid_array.sids[i].sid, &sid_str));
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ done:
+ return status;
+}
+
+/* Create a new account */
+
+static NTSTATUS cmd_lsa_create_account(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle dom_pol;
+ struct policy_handle user_pol;
+ NTSTATUS status, result;
+ uint32_t des_access = 0x000f000f;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ struct dom_sid sid;
+
+ if (argc != 2 ) {
+ printf("Usage: %s SID\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(cli, mem_ctx, &sid, argv[1]);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_CreateAccount(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ des_access,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf("Account for SID %s successfully created\n\n", argv[1]);
+ status = NT_STATUS_OK;
+
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+ done:
+ return status;
+}
+
+
+/* Enumerate the privileges of an SID */
+
+static NTSTATUS cmd_lsa_enum_privsaccounts(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle dom_pol;
+ struct policy_handle user_pol;
+ NTSTATUS status, result;
+ uint32_t access_desired = 0x000f000f;
+ struct dom_sid sid;
+ struct lsa_PrivilegeSet *privs = NULL;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc != 2 ) {
+ printf("Usage: %s SID\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(cli, mem_ctx, &sid, argv[1]);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_OpenAccount(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ access_desired,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_lsa_EnumPrivsAccount(b, mem_ctx,
+ &user_pol,
+ &privs,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results */
+ printf("found %d privileges for SID %s\n\n", privs->count, argv[1]);
+ printf("high\tlow\tattribute\n");
+
+ for (i = 0; i < privs->count; i++) {
+ printf("%u\t%u\t%u\n",
+ privs->set[i].luid.high,
+ privs->set[i].luid.low,
+ privs->set[i].attribute);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+ done:
+ return status;
+}
+
+
+/* Enumerate the privileges of an SID via LsaEnumerateAccountRights */
+
+static NTSTATUS cmd_lsa_enum_acct_rights(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle dom_pol;
+ NTSTATUS status, result;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ struct lsa_RightSet rights;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ int i;
+
+ if (argc != 2 ) {
+ printf("Usage: %s SID\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(cli, mem_ctx, &sid, argv[1]);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_EnumAccountRights(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ &rights,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf("found %d privileges for SID %s\n", rights.count,
+ dom_sid_str_buf(&sid, &buf));
+
+ for (i = 0; i < rights.count; i++) {
+ printf("\t%s\n", rights.names[i].string);
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+ done:
+ return status;
+}
+
+
+/* add some privileges to a SID via LsaAddAccountRights */
+
+static NTSTATUS cmd_lsa_add_acct_rights(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle dom_pol;
+ NTSTATUS status, result;
+ struct lsa_RightSet rights;
+ struct dom_sid sid;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 3 ) {
+ printf("Usage: %s SID [rights...]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(cli, mem_ctx, &sid, argv[1]);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ rights.count = argc-2;
+ rights.names = talloc_array(mem_ctx, struct lsa_StringLarge,
+ rights.count);
+ if (!rights.names) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<argc-2; i++) {
+ init_lsa_StringLarge(&rights.names[i], argv[i+2]);
+ }
+
+ status = dcerpc_lsa_AddAccountRights(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ &rights,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+ done:
+ return status;
+}
+
+
+/* remove some privileges to a SID via LsaRemoveAccountRights */
+
+static NTSTATUS cmd_lsa_remove_acct_rights(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle dom_pol;
+ NTSTATUS status, result;
+ struct lsa_RightSet rights;
+ struct dom_sid sid;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 3 ) {
+ printf("Usage: %s SID [rights...]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(cli, mem_ctx, &sid, argv[1]);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ rights.count = argc-2;
+ rights.names = talloc_array(mem_ctx, struct lsa_StringLarge,
+ rights.count);
+ if (!rights.names) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<argc-2; i++) {
+ init_lsa_StringLarge(&rights.names[i], argv[i+2]);
+ }
+
+ status = dcerpc_lsa_RemoveAccountRights(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ false,
+ &rights,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+
+ done:
+ return status;
+}
+
+
+/* Get a privilege value given its name */
+
+static NTSTATUS cmd_lsa_lookup_priv_value(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct lsa_LUID luid;
+ struct lsa_String name;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc != 2 ) {
+ printf("Usage: %s name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ status = dcerpc_lsa_LookupPrivValue(b, mem_ctx,
+ &pol,
+ &name,
+ &luid,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results */
+
+ printf("%u:%u (0x%x:0x%x)\n", luid.high, luid.low, luid.high, luid.low);
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ done:
+ return status;
+}
+
+/* Query LSA security object */
+
+static NTSTATUS cmd_lsa_query_secobj(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct sec_desc_buf *sdb;
+ uint32_t sec_info = SECINFO_DACL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 1 || argc > 2) {
+ printf("Usage: %s [sec_info]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2)
+ sscanf(argv[1], "%x", &sec_info);
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QuerySecurity(b, mem_ctx,
+ &pol,
+ sec_info,
+ &sdb,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results */
+
+ display_sec_desc(sdb->sd);
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ done:
+ return status;
+}
+
+static void display_trust_dom_info_4(struct lsa_TrustDomainInfoPassword *p,
+ DATA_BLOB session_key)
+{
+ char *pwd, *pwd_old;
+
+ DATA_BLOB data = data_blob_const(p->password->data, p->password->length);
+ DATA_BLOB data_old = data_blob_const(p->old_password->data, p->old_password->length);
+
+ pwd = sess_decrypt_string(talloc_tos(), &data, &session_key);
+ pwd_old = sess_decrypt_string(talloc_tos(), &data_old, &session_key);
+
+ d_printf("Password:\t%s\n", pwd);
+ d_printf("Old Password:\t%s\n", pwd_old);
+
+ talloc_free(pwd);
+ talloc_free(pwd_old);
+}
+
+static void display_trust_dom_info(TALLOC_CTX *mem_ctx,
+ union lsa_TrustedDomainInfo *info,
+ enum lsa_TrustDomInfoEnum info_class,
+ DATA_BLOB session_key)
+{
+ switch (info_class) {
+ case LSA_TRUSTED_DOMAIN_INFO_PASSWORD:
+ display_trust_dom_info_4(&info->password, session_key);
+ break;
+ default: {
+ const char *str = NULL;
+ str = NDR_PRINT_UNION_STRING(mem_ctx,
+ lsa_TrustedDomainInfo,
+ info_class, info);
+ if (str) {
+ d_printf("%s\n", str);
+ }
+ break;
+ }
+ }
+}
+
+static NTSTATUS cmd_lsa_query_trustdominfobysid(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dom_sid dom_sid;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ union lsa_TrustedDomainInfo *info = NULL;
+ enum lsa_TrustDomInfoEnum info_class = 1;
+ DATA_BLOB session_key;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc > 3 || argc < 2) {
+ printf("Usage: %s [sid] [info_class]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (!string_to_sid(&dom_sid, argv[1]))
+ return NT_STATUS_NO_MEMORY;
+
+ if (argc == 3)
+ info_class = atoi(argv[2]);
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ access_mask,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryTrustedDomainInfoBySid(b, mem_ctx,
+ &pol,
+ &dom_sid,
+ info_class,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not retrieve session key: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ display_trust_dom_info(mem_ctx, info, info_class, session_key);
+
+ done:
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_query_trustdominfobyname(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ union lsa_TrustedDomainInfo *info = NULL;
+ enum lsa_TrustDomInfoEnum info_class = 1;
+ struct lsa_String trusted_domain;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ DATA_BLOB session_key;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc > 3 || argc < 2) {
+ printf("Usage: %s [name] [info_class]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 3)
+ info_class = atoi(argv[2]);
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ access_mask,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ init_lsa_String(&trusted_domain, argv[1]);
+
+ status = dcerpc_lsa_QueryTrustedDomainInfoByName(b, mem_ctx,
+ &pol,
+ &trusted_domain,
+ info_class,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not retrieve session key: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ display_trust_dom_info(mem_ctx, info, info_class, session_key);
+
+ done:
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_set_trustdominfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct policy_handle pol, trustdom_pol;
+ NTSTATUS status, result;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ union lsa_TrustedDomainInfo info;
+ struct dom_sid dom_sid;
+ enum lsa_TrustDomInfoEnum info_class = 1;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc > 4 || argc < 3) {
+ printf("Usage: %s [sid] [info_class] [value]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (!string_to_sid(&dom_sid, argv[1])) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+
+ info_class = atoi(argv[2]);
+
+ switch (info_class) {
+ case 13: /* LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES */
+ info.enc_types.enc_types = atoi(argv[3]);
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ access_mask,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_OpenTrustedDomain(b, mem_ctx,
+ &pol,
+ &dom_sid,
+ access_mask,
+ &trustdom_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_lsa_SetInformationTrustedDomain(b, mem_ctx,
+ &trustdom_pol,
+ info_class,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ done:
+ dcerpc_lsa_Close(b, mem_ctx, &trustdom_pol, &result);
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_query_trustdominfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol, trustdom_pol;
+ NTSTATUS status, result;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ union lsa_TrustedDomainInfo *info = NULL;
+ struct dom_sid dom_sid;
+ enum lsa_TrustDomInfoEnum info_class = 1;
+ DATA_BLOB session_key;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc > 3 || argc < 2) {
+ printf("Usage: %s [sid] [info_class]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (!string_to_sid(&dom_sid, argv[1]))
+ return NT_STATUS_NO_MEMORY;
+
+
+ if (argc == 3)
+ info_class = atoi(argv[2]);
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ access_mask,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_OpenTrustedDomain(b, mem_ctx,
+ &pol,
+ &dom_sid,
+ access_mask,
+ &trustdom_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryTrustedDomainInfo(b, mem_ctx,
+ &trustdom_pol,
+ info_class,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not retrieve session key: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ display_trust_dom_info(mem_ctx, info, info_class, session_key);
+
+ done:
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_get_username(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ const char *servername = cli->desthost;
+ struct lsa_String *account_name = NULL;
+ struct lsa_String *authority_name = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 2) {
+ printf("Usage: %s servername\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_GetUserName(b, mem_ctx,
+ servername,
+ &account_name,
+ &authority_name,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Print results */
+
+ printf("Account Name: %s, Authority Name: %s\n",
+ account_name->string, authority_name ? authority_name->string :
+ "");
+
+ done:
+ return status;
+}
+
+static NTSTATUS cmd_lsa_add_priv(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct policy_handle dom_pol, user_pol;
+ NTSTATUS status, result;
+ struct lsa_PrivilegeSet privs;
+ struct lsa_LUIDAttribute *set = NULL;
+ struct dom_sid sid;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ ZERO_STRUCT(privs);
+
+ if (argc < 3 ) {
+ printf("Usage: %s SID [rights...]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(cli, mem_ctx, &sid, argv[1]);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_OpenAccount(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i=2; i<argc; i++) {
+
+ struct lsa_String priv_name;
+ struct lsa_LUID luid;
+
+ init_lsa_String(&priv_name, argv[i]);
+
+ status = dcerpc_lsa_LookupPrivValue(b, mem_ctx,
+ &dom_pol,
+ &priv_name,
+ &luid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ continue;
+ }
+
+ privs.count++;
+ set = talloc_realloc(mem_ctx, set,
+ struct lsa_LUIDAttribute,
+ privs.count);
+ if (!set) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ set[privs.count-1].luid = luid;
+ set[privs.count-1].attribute = 0;
+ }
+
+ privs.set = set;
+
+ status = dcerpc_lsa_AddPrivilegesToAccount(b, mem_ctx,
+ &user_pol,
+ &privs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &user_pol, &result);
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+ done:
+ return status;
+}
+
+static NTSTATUS cmd_lsa_del_priv(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct policy_handle dom_pol, user_pol;
+ NTSTATUS status, result;
+ struct lsa_PrivilegeSet privs;
+ struct lsa_LUIDAttribute *set = NULL;
+ struct dom_sid sid;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ ZERO_STRUCT(privs);
+
+ if (argc < 3 ) {
+ printf("Usage: %s SID [rights...]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(cli, mem_ctx, &sid, argv[1]);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_OpenAccount(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i=2; i<argc; i++) {
+
+ struct lsa_String priv_name;
+ struct lsa_LUID luid;
+
+ init_lsa_String(&priv_name, argv[i]);
+
+ status = dcerpc_lsa_LookupPrivValue(b, mem_ctx,
+ &dom_pol,
+ &priv_name,
+ &luid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ continue;
+ }
+
+ privs.count++;
+ set = talloc_realloc(mem_ctx, set,
+ struct lsa_LUIDAttribute,
+ privs.count);
+ if (!set) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ set[privs.count-1].luid = luid;
+ set[privs.count-1].attribute = 0;
+ }
+
+ privs.set = set;
+
+
+ status = dcerpc_lsa_RemovePrivilegesFromAccount(b, mem_ctx,
+ &user_pol,
+ false,
+ &privs,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &user_pol, &result);
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+ done:
+ return status;
+}
+
+static NTSTATUS cmd_lsa_create_secret(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle, sec_handle;
+ struct lsa_String name;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 2) {
+ printf("Usage: %s name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &sec_handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ status = dcerpc_lsa_CreateSecret(b, mem_ctx,
+ &handle,
+ name,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &sec_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&sec_handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &sec_handle, &result);
+ }
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_delete_secret(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle, sec_handle;
+ struct lsa_String name;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 2) {
+ printf("Usage: %s name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ status = dcerpc_lsa_OpenSecret(b, mem_ctx,
+ &handle,
+ name,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &sec_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_lsa_DeleteObject(b, mem_ctx,
+ &sec_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&sec_handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &sec_handle, &result);
+ }
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_query_secret(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle, sec_handle;
+ struct lsa_String name;
+ struct lsa_DATA_BUF_PTR new_val;
+ NTTIME new_mtime = 0;
+ struct lsa_DATA_BUF_PTR old_val;
+ NTTIME old_mtime = 0;
+ DATA_BLOB session_key;
+ DATA_BLOB new_blob = data_blob_null;
+ DATA_BLOB old_blob = data_blob_null;
+ char *new_secret, *old_secret;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 2) {
+ printf("Usage: %s name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ status = dcerpc_lsa_OpenSecret(b, mem_ctx,
+ &handle,
+ name,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &sec_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ ZERO_STRUCT(new_val);
+ ZERO_STRUCT(old_val);
+
+ status = dcerpc_lsa_QuerySecret(b, mem_ctx,
+ &sec_handle,
+ &new_val,
+ &new_mtime,
+ &old_val,
+ &old_mtime,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (new_val.buf) {
+ new_blob = data_blob_const(new_val.buf->data, new_val.buf->length);
+ }
+ if (old_val.buf) {
+ old_blob = data_blob_const(old_val.buf->data, old_val.buf->length);
+ }
+
+ new_secret = sess_decrypt_string(mem_ctx, &new_blob, &session_key);
+ old_secret = sess_decrypt_string(mem_ctx, &old_blob, &session_key);
+ if (new_secret) {
+ d_printf("new secret: %s\n", new_secret);
+ }
+ if (old_secret) {
+ d_printf("old secret: %s\n", old_secret);
+ }
+
+ done:
+ if (is_valid_policy_hnd(&sec_handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &sec_handle, &result);
+ }
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_set_secret(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle, sec_handle;
+ struct lsa_String name;
+ struct lsa_DATA_BUF new_val;
+ struct lsa_DATA_BUF old_val;
+ DATA_BLOB enc_key;
+ DATA_BLOB session_key;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 3) {
+ printf("Usage: %s name secret\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ status = dcerpc_lsa_OpenSecret(b, mem_ctx,
+ &handle,
+ name,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &sec_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ ZERO_STRUCT(new_val);
+ ZERO_STRUCT(old_val);
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ enc_key = sess_encrypt_string(argv[2], &session_key);
+
+ new_val.length = enc_key.length;
+ new_val.size = enc_key.length;
+ new_val.data = enc_key.data;
+
+ status = dcerpc_lsa_SetSecret(b, mem_ctx,
+ &sec_handle,
+ &new_val,
+ NULL,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&sec_handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &sec_handle, &result);
+ }
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_retrieve_private_data(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ struct lsa_String name;
+ struct lsa_DATA_BUF *val;
+ DATA_BLOB session_key;
+ DATA_BLOB blob = data_blob_null;
+ char *secret;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 2) {
+ printf("Usage: %s name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ ZERO_STRUCT(val);
+
+ status = dcerpc_lsa_RetrievePrivateData(b, mem_ctx,
+ &handle,
+ &name,
+ &val,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (val) {
+ blob = data_blob_const(val->data, val->length);
+ }
+
+ secret = sess_decrypt_string(mem_ctx, &blob, &session_key);
+ if (secret) {
+ d_printf("secret: %s\n", secret);
+ }
+
+ done:
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_store_private_data(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle;
+ struct lsa_String name;
+ struct lsa_DATA_BUF val;
+ DATA_BLOB session_key;
+ DATA_BLOB enc_key;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 3) {
+ printf("Usage: %s name secret\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ ZERO_STRUCT(val);
+
+ status = cli_get_session_key(mem_ctx, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ enc_key = sess_encrypt_string(argv[2], &session_key);
+
+ val.length = enc_key.length;
+ val.size = enc_key.length;
+ val.data = enc_key.data;
+
+ status = dcerpc_lsa_StorePrivateData(b, mem_ctx,
+ &handle,
+ &name,
+ &val,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_create_trusted_domain(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle, trustdom_handle;
+ struct dom_sid sid;
+ struct lsa_DomainInfo info;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 3) {
+ printf("Usage: %s name sid\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_StringLarge(&info.name, argv[1]);
+ info.sid = &sid;
+ string_to_sid(&sid, argv[2]);
+
+ status = dcerpc_lsa_CreateTrustedDomain(b, mem_ctx,
+ &handle,
+ &info,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &trustdom_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&trustdom_handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &trustdom_handle, &result);
+ }
+
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_lsa_delete_trusted_domain(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle handle, trustdom_handle;
+ struct lsa_String name;
+ struct dom_sid *sid = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 2) {
+ printf("Usage: %s name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &handle,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ status = dcerpc_lsa_OpenTrustedDomainByName(b, mem_ctx,
+ &handle,
+ name,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &trustdom_handle,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ goto delete_object;
+ }
+
+ {
+ uint32_t resume_handle = 0;
+ struct lsa_DomainList domains;
+ int i;
+
+ status = dcerpc_lsa_EnumTrustDom(b, mem_ctx,
+ &handle,
+ &resume_handle,
+ &domains,
+ 0xffff,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i=0; i < domains.count; i++) {
+ if (strequal(domains.domains[i].name.string, argv[1])) {
+ sid = domains.domains[i].sid;
+ break;
+ }
+ }
+
+ if (!sid) {
+ return NT_STATUS_INVALID_SID;
+ }
+ }
+
+ status = dcerpc_lsa_OpenTrustedDomain(b, mem_ctx,
+ &handle,
+ sid,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &trustdom_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ delete_object:
+ status = dcerpc_lsa_DeleteObject(b, mem_ctx,
+ &trustdom_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&trustdom_handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &trustdom_handle, &result);
+ }
+
+ if (is_valid_policy_hnd(&handle)) {
+ dcerpc_lsa_Close(b, mem_ctx, &handle, &result);
+ }
+
+ return status;
+}
+
+
+/* List of commands exported by this module */
+
+struct cmd_set lsarpc_commands[] = {
+
+ {
+ .name = "LSARPC",
+ },
+
+ {
+ .name = "lsaquery",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_query_info_policy,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Query info policy",
+ .usage = "",
+ },
+ {
+ .name = "lookupsids",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_lookup_sids,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Convert SIDs to names",
+ .usage = "",
+ },
+ {
+ .name = "lookupsids3",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_lookup_sids3,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Convert SIDs to names",
+ .usage = "",
+ },
+ {
+ .name = "lookupsids_level",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_lookup_sids_level,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Convert SIDs to names",
+ .usage = "",
+ },
+ {
+ .name = "lookupnames",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_lookup_names,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Convert names to SIDs",
+ .usage = "",
+ },
+ {
+ .name = "lookupnames4",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_lookup_names4,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Convert names to SIDs",
+ .usage = "",
+ },
+ {
+ .name = "lookupnames_level",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_lookup_names_level,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Convert names to SIDs",
+ .usage = "",
+ },
+ {
+ .name = "enumtrust",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_enum_trust_dom,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate trusted domains",
+ .usage = "Usage: [preferred max number] [enum context (0)]",
+ },
+ {
+ .name = "enumprivs",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_enum_privilege,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate privileges",
+ .usage = "",
+ },
+ {
+ .name = "getdispname",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_get_dispname,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Get the privilege name",
+ .usage = "",
+ },
+ {
+ .name = "lsaenumsid",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_enum_sids,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate the LSA SIDS",
+ .usage = "",
+ },
+ {
+ .name = "lsacreateaccount",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_create_account,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Create a new lsa account",
+ .usage = "",
+ },
+ {
+ .name = "lsaenumprivsaccount",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_enum_privsaccounts,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate the privileges of an SID",
+ .usage = "",
+ },
+ {
+ .name = "lsaenumacctrights",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_enum_acct_rights,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate the rights of an SID",
+ .usage = "",
+ },
+ {
+ .name = "lsaaddpriv",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_add_priv,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Assign a privilege to a SID",
+ .usage = "",
+ },
+ {
+ .name = "lsadelpriv",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_del_priv,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Revoke a privilege from a SID",
+ .usage = "",
+ },
+ {
+ .name = "lsaaddacctrights",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_add_acct_rights,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Add rights to an account",
+ .usage = "",
+ },
+ {
+ .name = "lsaremoveacctrights",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_remove_acct_rights,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Remove rights from an account",
+ .usage = "",
+ },
+ {
+ .name = "lsalookupprivvalue",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_lookup_priv_value,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Get a privilege value given its name",
+ .usage = "",
+ },
+ {
+ .name = "lsaquerysecobj",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_query_secobj,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Query LSA security object",
+ .usage = "",
+ },
+ {
+ .name = "lsaquerytrustdominfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_query_trustdominfo,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Query LSA trusted domains info (given a SID)",
+ .usage = "",
+ },
+ {
+ .name = "lsaquerytrustdominfobyname",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_query_trustdominfobyname,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Query LSA trusted domains info (given a name), only works for Windows > 2k",
+ .usage = "",
+ },
+ {
+ .name = "lsaquerytrustdominfobysid",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_query_trustdominfobysid,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Query LSA trusted domains info (given a SID)",
+ .usage = "",
+ },
+ {
+ .name = "lsasettrustdominfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_set_trustdominfo,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Set LSA trusted domain info",
+ .usage = "",
+ },
+ {
+ .name = "getusername",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_get_username,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Get username",
+ .usage = "",
+ },
+ {
+ .name = "createsecret",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_create_secret,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Create Secret",
+ .usage = "",
+ },
+ {
+ .name = "deletesecret",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_delete_secret,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Delete Secret",
+ .usage = "",
+ },
+ {
+ .name = "querysecret",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_query_secret,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Query Secret",
+ .usage = "",
+ },
+ {
+ .name = "setsecret",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_set_secret,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Set Secret",
+ .usage = "",
+ },
+ {
+ .name = "retrieveprivatedata",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_retrieve_private_data,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Retrieve Private Data",
+ .usage = "",
+ },
+ {
+ .name = "storeprivatedata",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_store_private_data,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Store Private Data",
+ .usage = "",
+ },
+ {
+ .name = "createtrustdom",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_create_trusted_domain,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Create Trusted Domain",
+ .usage = "",
+ },
+ {
+ .name = "deletetrustdom",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_lsa_delete_trusted_domain,
+ .wfn = NULL,
+ .table = &ndr_table_lsarpc,
+ .rpc_pipe = NULL,
+ .description = "Delete Trusted Domain",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_netlogon.c b/source3/rpcclient/cmd_netlogon.c
new file mode 100644
index 0000000..bb5b9da
--- /dev/null
+++ b/source3/rpcclient/cmd_netlogon.c
@@ -0,0 +1,1186 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "rpc_client/cli_netlogon.h"
+#include "secrets.h"
+#include "../libcli/auth/netlogon_creds_cli.h"
+#include "rpc_client/util_netlogon.h"
+
+static WERROR cmd_netlogon_logon_ctrl2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr;
+ const char *logon_server = cli->desthost;
+ enum netr_LogonControlCode function_code = NETLOGON_CONTROL_REDISCOVER;
+ uint32_t level = 1;
+ union netr_CONTROL_DATA_INFORMATION data;
+ union netr_CONTROL_QUERY_INFORMATION query;
+ const char *domain = lp_workgroup();
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ int i;
+#define fn_code_level(x, item) { x, #x, #item }
+ struct {
+ enum netr_LogonControlCode code;
+ const char *name;
+ const char *argument;
+ } supported_levels[] = {
+ fn_code_level(NETLOGON_CONTROL_REDISCOVER, domain),
+ fn_code_level(NETLOGON_CONTROL_TC_QUERY, domain),
+ fn_code_level(NETLOGON_CONTROL_TRANSPORT_NOTIFY, domain),
+ fn_code_level(NETLOGON_CONTROL_FIND_USER, user),
+ fn_code_level(NETLOGON_CONTROL_CHANGE_PASSWORD, domain),
+ fn_code_level(NETLOGON_CONTROL_TC_VERIFY, domain),
+ fn_code_level(NETLOGON_CONTROL_SET_DBFLAG, debug_level),
+ {0, 0, 0}
+ };
+#undef fn_code_level
+ if ((argc > 5) || (argc < 2)) {
+ fprintf(stderr, "Usage: %s <logon_server> <function_code> "
+ "<level:1..4> <argument>\n", argv[0]);
+ fprintf(stderr, "Supported combinations:\n");
+ fprintf(stderr, "function_code\targument\n");
+ for(i=0; supported_levels[i].code; i++) {
+ fprintf(stderr, "%7d\t\t%s\t(%s)\n",
+ supported_levels[i].code,
+ supported_levels[i].argument,
+ supported_levels[i].name);
+ }
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ logon_server = argv[1];
+ }
+
+ if (argc >= 3) {
+ function_code = atoi(argv[2]);
+ }
+
+ if (argc >= 4) {
+ level = atoi(argv[3]);
+ }
+
+ if (argc >= 5) {
+ domain = argv[4];
+ }
+
+ switch (function_code) {
+ case NETLOGON_CONTROL_REDISCOVER:
+ case NETLOGON_CONTROL_TC_QUERY:
+ case NETLOGON_CONTROL_CHANGE_PASSWORD:
+ case NETLOGON_CONTROL_TRANSPORT_NOTIFY:
+ case NETLOGON_CONTROL_TC_VERIFY:
+ data.domain = domain;
+ break;
+ case NETLOGON_CONTROL_FIND_USER:
+ data.user = domain;
+ break;
+ case NETLOGON_CONTROL_SET_DBFLAG:
+ data.debug_level = atoi(domain);
+ break;
+ default:
+ break;
+ }
+
+ status = dcerpc_netr_LogonControl2(b, mem_ctx,
+ logon_server,
+ function_code,
+ level,
+ &data,
+ &query,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ /* Display results */
+
+ return werr;
+}
+
+static WERROR cmd_netlogon_getanydcname(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ const char *dcname = NULL;
+ WERROR werr;
+ NTSTATUS status;
+ int old_timeout;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s domainname\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Make sure to wait for our DC's reply */
+ old_timeout = rpccli_set_timeout(cli, 30000); /* 30 seconds. */
+ rpccli_set_timeout(cli, MAX(old_timeout, 30000)); /* At least 30 sec */
+
+ status = dcerpc_netr_GetAnyDCName(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ &dcname,
+ &werr);
+ rpccli_set_timeout(cli, old_timeout);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ /* Display results */
+
+ printf("%s\n", dcname);
+
+ return werr;
+}
+
+static WERROR cmd_netlogon_getdcname(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ const char *dcname = NULL;
+ NTSTATUS status;
+ WERROR werr;
+ int old_timeout;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s domainname\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Make sure to wait for our DC's reply */
+ old_timeout = rpccli_set_timeout(cli, 30000); /* 30 seconds. */
+ rpccli_set_timeout(cli, MAX(30000, old_timeout)); /* At least 30 sec */
+
+ status = dcerpc_netr_GetDcName(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ &dcname,
+ &werr);
+ rpccli_set_timeout(cli, old_timeout);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ /* Display results */
+
+ printf("%s\n", dcname);
+
+ return werr;
+}
+
+static WERROR cmd_netlogon_dsr_getdcname(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS result;
+ WERROR werr = WERR_OK;
+ uint32_t flags = DS_RETURN_DNS_NAME;
+ const char *server_name = cli->desthost;
+ const char *domain_name = NULL;
+ struct GUID domain_guid = GUID_zero();
+ struct GUID site_guid = GUID_zero();
+ struct netr_DsRGetDCNameInfo *info = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s [domain_name] [domain_guid] "
+ "[site_guid] [flags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2)
+ domain_name = argv[1];
+
+ if (argc >= 3) {
+ if (!NT_STATUS_IS_OK(GUID_from_string(argv[2], &domain_guid))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (argc >= 4) {
+ if (!NT_STATUS_IS_OK(GUID_from_string(argv[3], &site_guid))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (argc >= 5)
+ sscanf(argv[4], "%x", &flags);
+
+ result = dcerpc_netr_DsRGetDCName(b, mem_ctx,
+ server_name,
+ domain_name,
+ &domain_guid,
+ &site_guid,
+ flags,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return ntstatus_to_werror(result);
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ d_printf("DsGetDcName gave: %s\n",
+ NDR_PRINT_STRUCT_STRING(mem_ctx, netr_DsRGetDCNameInfo, info));
+ return WERR_OK;
+ }
+
+ printf("rpccli_netlogon_dsr_getdcname returned %s\n",
+ win_errstr(werr));
+
+ return werr;
+}
+
+static WERROR cmd_netlogon_dsr_getdcnameex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ uint32_t flags = DS_RETURN_DNS_NAME;
+ const char *server_name = cli->desthost;
+ const char *domain_name;
+ const char *site_name = NULL;
+ struct GUID domain_guid = GUID_zero();
+ struct netr_DsRGetDCNameInfo *info = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s [domain_name] [domain_guid] "
+ "[site_name] [flags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ domain_name = argv[1];
+
+ if (argc >= 3) {
+ if (!NT_STATUS_IS_OK(GUID_from_string(argv[2], &domain_guid))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (argc >= 4) {
+ site_name = argv[3];
+ }
+
+ if (argc >= 5) {
+ sscanf(argv[4], "%x", &flags);
+ }
+
+ status = dcerpc_netr_DsRGetDCNameEx(b, mem_ctx,
+ server_name,
+ domain_name,
+ &domain_guid,
+ site_name,
+ flags,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ d_printf("DsRGetDCNameEx gave %s\n",
+ NDR_PRINT_STRUCT_STRING(mem_ctx, netr_DsRGetDCNameInfo, info));
+
+ return result;
+}
+
+static WERROR cmd_netlogon_dsr_getdcnameex2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ uint32_t flags = DS_RETURN_DNS_NAME;
+ const char *server_name = cli->desthost;
+ const char *domain_name = NULL;
+ const char *client_account = NULL;
+ uint32_t mask = 0;
+ const char *site_name = NULL;
+ struct GUID domain_guid = GUID_zero();
+ struct netr_DsRGetDCNameInfo *info = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s [client_account] [acb_mask] "
+ "[domain_name] [domain_guid] [site_name] "
+ "[flags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ client_account = argv[1];
+ }
+
+ if (argc >= 3) {
+ mask = atoi(argv[2]);
+ }
+
+ if (argc >= 4) {
+ domain_name = argv[3];
+ }
+
+ if (argc >= 5) {
+ if (!NT_STATUS_IS_OK(GUID_from_string(argv[4], &domain_guid))) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (argc >= 6) {
+ site_name = argv[5];
+ }
+
+ if (argc >= 7) {
+ sscanf(argv[6], "%x", &flags);
+ }
+
+ status = dcerpc_netr_DsRGetDCNameEx2(b, mem_ctx,
+ server_name,
+ client_account,
+ mask,
+ domain_name,
+ &domain_guid,
+ site_name,
+ flags,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ d_printf("DsRGetDCNameEx2 gave %s\n",
+ NDR_PRINT_STRUCT_STRING(mem_ctx, netr_DsRGetDCNameInfo, info));
+
+ return result;
+}
+
+
+static WERROR cmd_netlogon_dsr_getsitename(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ NTSTATUS status;
+ const char *sitename = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s computername\n", argv[0]);
+ return WERR_OK;
+ }
+
+ status = dcerpc_netr_DsRGetSiteName(b, mem_ctx,
+ argv[1],
+ &sitename,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ printf("rpccli_netlogon_dsr_gesitename returned %s\n",
+ nt_errstr(werror_to_ntstatus(werr)));
+ return werr;
+ }
+
+ printf("Computer %s is on Site: %s\n", argv[1], sitename);
+
+ return WERR_OK;
+}
+
+static WERROR cmd_netlogon_logon_ctrl(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr;
+ const char *logon_server = cli->desthost;
+ enum netr_LogonControlCode function_code = 1;
+ uint32_t level = 1;
+ union netr_CONTROL_QUERY_INFORMATION info;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 4) {
+ fprintf(stderr, "Usage: %s <logon_server> <function_code> "
+ "<level>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ logon_server = argv[1];
+ }
+
+ if (argc >= 3) {
+ function_code = atoi(argv[2]);
+ }
+
+ if (argc >= 4) {
+ level = atoi(argv[3]);
+ }
+
+ status = dcerpc_netr_LogonControl(b, mem_ctx,
+ logon_server,
+ function_code,
+ level,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ /* Display results */
+
+ return werr;
+}
+
+/* Log on a domain user */
+
+static NTSTATUS cmd_netlogon_sam_logon(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ int logon_type = NetlogonNetworkInformation;
+ const char *username, *password;
+ uint32_t logon_param = 0;
+ const char *workstation = NULL;
+ struct netr_SamInfo3 *info3 = NULL;
+ uint8_t authoritative = 1;
+ uint32_t flags = 0;
+ uint16_t validation_level;
+ union netr_Validation *validation = NULL;
+ uint64_t logon_id = 0;
+
+ /* Check arguments */
+
+ if (argc < 3 || argc > 6) {
+ fprintf(stderr, "Usage: samlogon <username> <password> [workstation]"
+ "[logon_type (1 or 2)] [logon_parameter]\n");
+ return NT_STATUS_OK;
+ }
+
+ username = argv[1];
+ password = argv[2];
+
+ if (argc >= 4)
+ workstation = argv[3];
+
+ if (argc >= 5)
+ sscanf(argv[4], "%i", &logon_type);
+
+ if (argc == 6)
+ sscanf(argv[5], "%x", &logon_param);
+
+ if (rpcclient_netlogon_creds == NULL) {
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ logon_id = generate_random_u64();
+
+ /* Perform the sam logon */
+
+ result = rpccli_netlogon_password_logon(rpcclient_netlogon_creds,
+ cli->binding_handle,
+ mem_ctx,
+ logon_param,
+ lp_workgroup(),
+ username,
+ password,
+ workstation,
+ logon_id,
+ logon_type,
+ &authoritative,
+ &flags,
+ &validation_level,
+ &validation);
+ if (!NT_STATUS_IS_OK(result))
+ goto done;
+
+ result = map_validation_to_info3(mem_ctx,
+ validation_level,
+ validation,
+ &info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ done:
+ return result;
+}
+
+/* Change the trust account password */
+
+static NTSTATUS cmd_netlogon_change_trust_pw(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ const char *dcname = cli->desthost;
+
+ /* Check arguments */
+
+ if (argc > 1) {
+ fprintf(stderr, "Usage: change_trust_pw");
+ return NT_STATUS_OK;
+ }
+
+ result = trust_pw_change(rpcclient_netlogon_creds,
+ rpcclient_msg_ctx,
+ cli->binding_handle,
+ lp_workgroup(),
+ dcname,
+ true); /* force */
+ if (!NT_STATUS_IS_OK(result))
+ goto done;
+
+ done:
+ return result;
+}
+
+static WERROR cmd_netlogon_gettrustrid(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr = WERR_GEN_FAILURE;
+ const char *server_name = cli->desthost;
+ const char *domain_name = lp_workgroup();
+ uint32_t rid = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 1 || argc > 3) {
+ fprintf(stderr, "Usage: %s <server_name> <domain_name>\n",
+ argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ server_name = argv[1];
+ }
+
+ if (argc >= 3) {
+ domain_name = argv[2];
+ }
+
+ status = dcerpc_netr_LogonGetTrustRid(b, mem_ctx,
+ server_name,
+ domain_name,
+ &rid,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ printf("Rid: %d\n", rid);
+ }
+ done:
+ return werr;
+}
+
+static WERROR cmd_netlogon_dsr_enumtrustdom(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr = WERR_GEN_FAILURE;
+ const char *server_name = cli->desthost;
+ uint32_t trust_flags = NETR_TRUST_FLAG_IN_FOREST;
+ struct netr_DomainTrustList trusts;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 1 || argc > 3) {
+ fprintf(stderr, "Usage: %s <server_name> <trust_flags>\n",
+ argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ server_name = argv[1];
+ }
+
+ if (argc >= 3) {
+ sscanf(argv[2], "%x", &trust_flags);
+ }
+
+ status = dcerpc_netr_DsrEnumerateDomainTrusts(b, mem_ctx,
+ server_name,
+ trust_flags,
+ &trusts,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ int i;
+
+ printf("%d domains returned\n", trusts.count);
+
+ for (i=0; i<trusts.count; i++ ) {
+ printf("%s (%s)\n",
+ trusts.array[i].dns_name,
+ trusts.array[i].netbios_name);
+ }
+ }
+ done:
+ return werr;
+}
+
+static WERROR cmd_netlogon_deregisterdnsrecords(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr = WERR_GEN_FAILURE;
+ const char *server_name = cli->desthost;
+ const char *domain = lp_workgroup();
+ const char *dns_host = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 1 || argc > 4) {
+ fprintf(stderr, "Usage: %s <server_name> <domain_name> "
+ "<dns_host>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ server_name = argv[1];
+ }
+
+ if (argc >= 3) {
+ domain = argv[2];
+ }
+
+ if (argc >= 4) {
+ dns_host = argv[3];
+ }
+
+ status = dcerpc_netr_DsrDeregisterDNSHostRecords(b, mem_ctx,
+ server_name,
+ domain,
+ NULL,
+ NULL,
+ dns_host,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ printf("success\n");
+ }
+ done:
+ return werr;
+}
+
+static WERROR cmd_netlogon_dsr_getforesttrustinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr = WERR_GEN_FAILURE;
+ const char *server_name = cli->desthost;
+ const char *trusted_domain_name = NULL;
+ struct lsa_ForestTrustInformation *info = NULL;
+ uint32_t flags = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 1 || argc > 4) {
+ fprintf(stderr, "Usage: %s <server_name> <trusted_domain_name> "
+ "<flags>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ server_name = argv[1];
+ }
+
+ if (argc >= 3) {
+ trusted_domain_name = argv[2];
+ }
+
+ if (argc >= 4) {
+ sscanf(argv[3], "%x", &flags);
+ }
+
+ status = dcerpc_netr_DsRGetForestTrustInformation(b, mem_ctx,
+ server_name,
+ trusted_domain_name,
+ flags,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ printf("success\n");
+ }
+ done:
+ return werr;
+}
+
+static NTSTATUS cmd_netlogon_enumtrusteddomains(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS result;
+ const char *server_name = cli->desthost;
+ struct netr_Blob blob;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+
+ if (argc < 1 || argc > 3) {
+ fprintf(stderr, "Usage: %s <server_name>\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc >= 2) {
+ server_name = argv[1];
+ }
+
+ status = dcerpc_netr_NetrEnumerateTrustedDomains(b, mem_ctx,
+ server_name,
+ &blob,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf("success\n");
+ dump_data(1, blob.data, blob.length);
+ done:
+ return status;
+}
+
+static WERROR cmd_netlogon_enumtrusteddomainsex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr = WERR_GEN_FAILURE;
+ const char *server_name = cli->desthost;
+ struct netr_DomainTrustList list;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 1 || argc > 3) {
+ fprintf(stderr, "Usage: %s <server_name>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ server_name = argv[1];
+ }
+
+ status = dcerpc_netr_NetrEnumerateTrustedDomainsEx(b, mem_ctx,
+ server_name,
+ &list,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ printf("success\n");
+ }
+ done:
+ return werr;
+}
+
+static WERROR cmd_netlogon_getdcsitecoverage(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr = WERR_GEN_FAILURE;
+ const char *server_name = cli->desthost;
+ struct DcSitesCtr *ctr = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 1 || argc > 3) {
+ fprintf(stderr, "Usage: %s <server_name>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ server_name = argv[1];
+ }
+
+ status = dcerpc_netr_DsrGetDcSiteCoverageW(b, mem_ctx,
+ server_name,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_IS_OK(werr) && ctr->num_sites) {
+ int i;
+ printf("sites covered by this DC: %d\n", ctr->num_sites);
+ for (i=0; i<ctr->num_sites; i++) {
+ printf("%s\n", ctr->sites[i].string);
+ }
+ }
+ done:
+ return werr;
+}
+
+static NTSTATUS cmd_netlogon_capabilities(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct netlogon_creds_cli_lck *lck;
+ union netr_Capabilities capabilities;
+ NTSTATUS status;
+
+ if (argc > 1) {
+ fprintf(stderr, "Usage: %s\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = netlogon_creds_cli_lck(rpcclient_netlogon_creds,
+ NETLOGON_CREDS_CLI_LCK_EXCLUSIVE,
+ mem_ctx, &lck);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "netlogon_creds_cli_lck failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = netlogon_creds_cli_check(rpcclient_netlogon_creds,
+ cli->binding_handle,
+ &capabilities);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "netlogon_creds_cli_check failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ TALLOC_FREE(lck);
+
+ printf("capabilities: 0x%08x\n", capabilities.server_capabilities);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_netlogon_logongetdomaininfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ union netr_WorkstationInfo query;
+ struct netr_WorkstationInformation workstation_info;
+ union netr_DomainInfo *domain_info;
+ NTSTATUS status;
+ char *s;
+
+ if (argc > 1) {
+ fprintf(stderr, "Usage: %s\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(workstation_info);
+
+ query.workstation_info = &workstation_info;
+
+ status = netlogon_creds_cli_LogonGetDomainInfo(rpcclient_netlogon_creds,
+ cli->binding_handle,
+ mem_ctx,
+ 1,
+ &query,
+ &domain_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "netlogon_creds_cli_LogonGetDomainInfo failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ s = NDR_PRINT_STRUCT_STRING(mem_ctx, netr_DomainInformation, domain_info->domain_info);
+ if (s) {
+ printf("%s\n", s);
+ TALLOC_FREE(s);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set netlogon_commands[] = {
+
+ {
+ .name = "NETLOGON",
+ },
+
+ {
+ .name = "logonctrl2",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_logon_ctrl2,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Logon Control 2",
+ .usage = "",
+ },
+ {
+ .name = "getanydcname",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_getanydcname,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get trusted DC name",
+ .usage = "",
+ },
+ {
+ .name = "getdcname",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_getdcname,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get trusted PDC name",
+ .usage = "",
+ },
+ {
+ .name = "dsr_getdcname",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_dsr_getdcname,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get trusted DC name",
+ .usage = "",
+ },
+ {
+ .name = "dsr_getdcnameex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_dsr_getdcnameex,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get trusted DC name",
+ .usage = "",
+ },
+ {
+ .name = "dsr_getdcnameex2",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_dsr_getdcnameex2,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get trusted DC name",
+ .usage = "",
+ },
+ {
+ .name = "dsr_getsitename",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_dsr_getsitename,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get sitename",
+ .usage = "",
+ },
+ {
+ .name = "dsr_getforesttrustinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_dsr_getforesttrustinfo,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get Forest Trust Info",
+ .usage = "",
+ },
+ {
+ .name = "logonctrl",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_logon_ctrl,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Logon Control",
+ .usage = "",
+ },
+ {
+ .name = "samlogon",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_netlogon_sam_logon,
+ .wfn = NULL,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Sam Logon",
+ .usage = "",
+ .use_netlogon_creds = true,
+ },
+ {
+ .name = "change_trust_pw",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_netlogon_change_trust_pw,
+ .wfn = NULL,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Change Trust Account Password",
+ .usage = "",
+ .use_netlogon_creds = true,
+ },
+ {
+ .name = "gettrustrid",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_gettrustrid,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get trust rid",
+ .usage = "",
+ },
+ {
+ .name = "dsr_enumtrustdom",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_dsr_enumtrustdom,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Enumerate trusted domains",
+ .usage = "",
+ },
+ {
+ .name = "dsenumdomtrusts",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_dsr_enumtrustdom,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Enumerate all trusted domains in an AD forest",
+ .usage = "",
+ },
+ {
+ .name = "deregisterdnsrecords",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_deregisterdnsrecords,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Deregister DNS records",
+ .usage = "",
+ },
+ {
+ .name = "netrenumtrusteddomains",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_netlogon_enumtrusteddomains,
+ .wfn = NULL,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Enumerate trusted domains",
+ .usage = "",
+ },
+ {
+ .name = "netrenumtrusteddomainsex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_enumtrusteddomainsex,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Enumerate trusted domains",
+ .usage = "",
+ },
+ {
+ .name = "getdcsitecoverage",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_netlogon_getdcsitecoverage,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Get the Site-Coverage from a DC",
+ .usage = "",
+ },
+ {
+ .name = "capabilities",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_netlogon_capabilities,
+ .wfn = NULL,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Return Capabilities",
+ .usage = "",
+ .use_netlogon_creds = true,
+ },
+ {
+ .name = "logongetdomaininfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_netlogon_logongetdomaininfo,
+ .wfn = NULL,
+ .table = &ndr_table_netlogon,
+ .rpc_pipe = NULL,
+ .description = "Return LogonGetDomainInfo",
+ .usage = "",
+ .use_netlogon_creds = true,
+ },
+ {
+ .name = NULL,
+ }
+};
diff --git a/source3/rpcclient/cmd_ntsvcs.c b/source3/rpcclient/cmd_ntsvcs.c
new file mode 100644
index 0000000..15a2357
--- /dev/null
+++ b/source3/rpcclient/cmd_ntsvcs.c
@@ -0,0 +1,361 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Günther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_ntsvcs_c.h"
+
+static WERROR cmd_ntsvcs_get_version(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ uint16_t version;
+
+ status = dcerpc_PNP_GetVersion(b, mem_ctx,
+ &version, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ printf("version: %d\n", version);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_ntsvcs_validate_dev_inst(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ const char *devicepath = NULL;
+ uint32_t flags = 0;
+
+ if (argc < 2 || argc > 3) {
+ printf("usage: %s [devicepath] <flags>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ devicepath = argv[1];
+
+ if (argc >= 3) {
+ flags = atoi(argv[2]);
+ }
+
+ status = dcerpc_PNP_ValidateDeviceInstance(b, mem_ctx,
+ devicepath,
+ flags,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_ntsvcs_hw_prof_flags(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ const char *devicepath = NULL;
+ uint32_t profile_flags = 0;
+ uint16_t veto_type = 0;
+ const char *unk5 = NULL;
+ const char *unk5a = NULL;
+
+ if (argc < 2) {
+ printf("usage: %s [devicepath]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ devicepath = argv[1];
+
+ status = dcerpc_PNP_HwProfFlags(b, mem_ctx,
+ 0,
+ devicepath,
+ 0,
+ &profile_flags,
+ &veto_type,
+ unk5,
+ &unk5a,
+ 0,
+ 0,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_ntsvcs_get_hw_prof_info(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t idx = 0;
+ struct PNP_HwProfInfo info;
+ uint32_t size = 0, flags = 0;
+
+ ZERO_STRUCT(info);
+
+ status = dcerpc_PNP_GetHwProfInfo(b, mem_ctx,
+ idx,
+ &info,
+ size,
+ flags,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_ntsvcs_get_dev_reg_prop(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ const char *devicepath = NULL;
+ uint32_t property = DEV_REGPROP_DESC;
+ uint32_t reg_data_type = REG_NONE;
+ uint8_t *buffer;
+ uint32_t buffer_size = 0;
+ uint32_t needed = 0;
+ uint32_t flags = 0;
+
+ if (argc < 2) {
+ printf("usage: %s [devicepath] [buffersize]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ devicepath = argv[1];
+
+ if (argc >= 3) {
+ buffer_size = atoi(argv[2]);
+ needed = buffer_size;
+ }
+
+ buffer = talloc_array(mem_ctx, uint8_t, buffer_size);
+ W_ERROR_HAVE_NO_MEMORY(buffer);
+
+ status = dcerpc_PNP_GetDeviceRegProp(b, mem_ctx,
+ devicepath,
+ property,
+ &reg_data_type,
+ buffer,
+ &buffer_size,
+ &needed,
+ flags,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_ntsvcs_get_dev_list_size(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t size = 0;
+ uint32_t flags = 0;
+ const char *filter = NULL;
+
+ if (argc > 3) {
+ printf("usage: %s [filter] [flags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ filter = argv[1];
+ }
+
+ if (argc >= 3) {
+ flags = atoi(argv[2]);
+ }
+
+ status = dcerpc_PNP_GetDeviceListSize(b, mem_ctx,
+ filter,
+ &size,
+ flags,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ printf("size: %d\n", size);
+
+ return werr;
+}
+
+static WERROR cmd_ntsvcs_get_dev_list(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ WERROR werr;
+ const char *filter = NULL;
+ uint16_t *buffer = NULL;
+ uint32_t length = 0;
+ uint32_t flags = 0;
+
+ if (argc > 4) {
+ printf("usage: %s [filter] [length] [flags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ filter = argv[1];
+ }
+
+ if (argc >= 3) {
+ length = atoi(argv[2]);
+ }
+
+ if (argc >= 4) {
+ flags = atoi(argv[3]);
+ }
+
+ buffer = talloc(mem_ctx, uint16_t);
+ if (!buffer) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = dcerpc_PNP_GetDeviceList(b, mem_ctx,
+ filter,
+ buffer,
+ &length,
+ flags,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ printf("devlist needs size: %d\n", length);
+
+ return werr;
+}
+
+struct cmd_set ntsvcs_commands[] = {
+
+ {
+ .name = "NTSVCS",
+ },
+ {
+ .name = "ntsvcs_getversion",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ntsvcs_get_version,
+ .table = &ndr_table_ntsvcs,
+ .rpc_pipe = NULL,
+ .description = "Query NTSVCS version",
+ .usage = "",
+ },
+ {
+ .name = "ntsvcs_validatedevinst",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ntsvcs_validate_dev_inst,
+ .table = &ndr_table_ntsvcs,
+ .rpc_pipe = NULL,
+ .description = "Query NTSVCS device instance",
+ .usage = "",
+ },
+ {
+ .name = "ntsvcs_hwprofflags",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ntsvcs_hw_prof_flags,
+ .table = &ndr_table_ntsvcs,
+ .rpc_pipe = NULL,
+ .description = "Query NTSVCS HW prof flags",
+ .usage = "",
+ },
+ {
+ .name = "ntsvcs_hwprofinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ntsvcs_get_hw_prof_info,
+ .table = &ndr_table_ntsvcs,
+ .rpc_pipe = NULL,
+ .description = "Query NTSVCS HW prof info",
+ .usage = "",
+ },
+ {
+ .name = "ntsvcs_getdevregprop",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ntsvcs_get_dev_reg_prop,
+ .table = &ndr_table_ntsvcs,
+ .rpc_pipe = NULL,
+ .description = "Query NTSVCS device registry property",
+ .usage = "",
+ },
+ {
+ .name = "ntsvcs_getdevlistsize",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ntsvcs_get_dev_list_size,
+ .table = &ndr_table_ntsvcs,
+ .rpc_pipe = NULL,
+ .description = "Query NTSVCS device list size",
+ .usage = "",
+ },
+ {
+ .name = "ntsvcs_getdevlist",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_ntsvcs_get_dev_list,
+ .table = &ndr_table_ntsvcs,
+ .rpc_pipe = NULL,
+ .description = "Query NTSVCS device list",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_samr.c b/source3/rpcclient/cmd_samr.c
new file mode 100644
index 0000000..8106ca9
--- /dev/null
+++ b/source3/rpcclient/cmd_samr.c
@@ -0,0 +1,3941 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Andrew Tridgell 1992-2000,
+ Copyright (C) Luke Kenneth Casson Leighton 1996-2000,
+ Copyright (C) Elrond 2000,
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_samr.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/cli_samr.h"
+#include "rpc_client/init_samr.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "lib/util/smb_strtox.h"
+
+static struct dom_sid domain_sid;
+
+/****************************************************************************
+ display samr_user_info_7 structure
+ ****************************************************************************/
+static void display_samr_user_info_7(struct samr_UserInfo7 *r)
+{
+ printf("\tUser Name :\t%s\n", r->account_name.string);
+}
+
+/****************************************************************************
+ display samr_user_info_9 structure
+ ****************************************************************************/
+static void display_samr_user_info_9(struct samr_UserInfo9 *r)
+{
+ printf("\tPrimary group RID :\tox%x\n", r->primary_gid);
+}
+
+/****************************************************************************
+ display samr_user_info_16 structure
+ ****************************************************************************/
+static void display_samr_user_info_16(struct samr_UserInfo16 *r)
+{
+ printf("\tAcct Flags :\tox%x\n", r->acct_flags);
+}
+
+/****************************************************************************
+ display samr_user_info_20 structure
+ ****************************************************************************/
+static void display_samr_user_info_20(struct samr_UserInfo20 *r)
+{
+ printf("\tRemote Dial :\n");
+ dump_data(0, (uint8_t *)r->parameters.array, r->parameters.length*2);
+}
+
+
+/****************************************************************************
+ display samr_user_info_21 structure
+ ****************************************************************************/
+static void display_samr_user_info_21(struct samr_UserInfo21 *r)
+{
+ printf("\tUser Name :\t%s\n", r->account_name.string);
+ printf("\tFull Name :\t%s\n", r->full_name.string);
+ printf("\tHome Drive :\t%s\n", r->home_directory.string);
+ printf("\tDir Drive :\t%s\n", r->home_drive.string);
+ printf("\tProfile Path:\t%s\n", r->profile_path.string);
+ printf("\tLogon Script:\t%s\n", r->logon_script.string);
+ printf("\tDescription :\t%s\n", r->description.string);
+ printf("\tWorkstations:\t%s\n", r->workstations.string);
+ printf("\tComment :\t%s\n", r->comment.string);
+ printf("\tRemote Dial :\n");
+ dump_data(0, (uint8_t *)r->parameters.array, r->parameters.length*2);
+
+ printf("\tLogon Time :\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(r->last_logon)));
+ printf("\tLogoff Time :\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(r->last_logoff)));
+ printf("\tKickoff Time :\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(r->acct_expiry)));
+ printf("\tPassword last set Time :\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(r->last_password_change)));
+ printf("\tPassword can change Time :\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(r->allow_password_change)));
+ printf("\tPassword must change Time:\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(r->force_password_change)));
+
+ printf("\tunknown_2[0..31]...\n"); /* user passwords? */
+
+ printf("\tuser_rid :\t0x%x\n" , r->rid); /* User ID */
+ printf("\tgroup_rid:\t0x%x\n" , r->primary_gid); /* Group ID */
+ printf("\tacb_info :\t0x%08x\n", r->acct_flags); /* Account Control Info */
+
+ printf("\tfields_present:\t0x%08x\n", r->fields_present); /* 0x00ff ffff */
+ printf("\tlogon_divs:\t%d\n", r->logon_hours.units_per_week); /* 0x0000 00a8 which is 168 which is num hrs in a week */
+ printf("\tbad_password_count:\t0x%08x\n", r->bad_password_count);
+ printf("\tlogon_count:\t0x%08x\n", r->logon_count);
+
+ printf("\tpadding1[0..7]...\n");
+
+ if (r->logon_hours.bits) {
+ printf("\tlogon_hrs[0..%d]...\n", r->logon_hours.units_per_week/8);
+ }
+}
+
+
+static void display_password_properties(uint32_t password_properties)
+{
+ printf("password_properties: 0x%08x\n", password_properties);
+
+ if (password_properties & DOMAIN_PASSWORD_COMPLEX)
+ printf("\tDOMAIN_PASSWORD_COMPLEX\n");
+
+ if (password_properties & DOMAIN_PASSWORD_NO_ANON_CHANGE)
+ printf("\tDOMAIN_PASSWORD_NO_ANON_CHANGE\n");
+
+ if (password_properties & DOMAIN_PASSWORD_NO_CLEAR_CHANGE)
+ printf("\tDOMAIN_PASSWORD_NO_CLEAR_CHANGE\n");
+
+ if (password_properties & DOMAIN_PASSWORD_LOCKOUT_ADMINS)
+ printf("\tDOMAIN_PASSWORD_LOCKOUT_ADMINS\n");
+
+ if (password_properties & DOMAIN_PASSWORD_STORE_CLEARTEXT)
+ printf("\tDOMAIN_PASSWORD_STORE_CLEARTEXT\n");
+
+ if (password_properties & DOMAIN_REFUSE_PASSWORD_CHANGE)
+ printf("\tDOMAIN_REFUSE_PASSWORD_CHANGE\n");
+}
+
+static void display_sam_dom_info_1(struct samr_DomInfo1 *info1)
+{
+ printf("Minimum password length:\t\t\t%d\n",
+ info1->min_password_length);
+ printf("Password uniqueness (remember x passwords):\t%d\n",
+ info1->password_history_length);
+ display_password_properties(info1->password_properties);
+ printf("password expire in:\t\t\t\t%s\n",
+ display_time(info1->max_password_age));
+ printf("Min password age (allow changing in x days):\t%s\n",
+ display_time(info1->min_password_age));
+}
+
+static void display_sam_dom_info_2(struct samr_DomGeneralInformation *general)
+{
+ printf("Domain:\t\t%s\n", general->domain_name.string);
+ printf("Server:\t\t%s\n", general->primary.string);
+ printf("Comment:\t%s\n", general->oem_information.string);
+
+ printf("Total Users:\t%d\n", general->num_users);
+ printf("Total Groups:\t%d\n", general->num_groups);
+ printf("Total Aliases:\t%d\n", general->num_aliases);
+
+ printf("Sequence No:\t%llu\n", (unsigned long long)general->sequence_num);
+
+ printf("Force Logoff:\t%d\n",
+ (int)nt_time_to_unix_abs(&general->force_logoff_time));
+
+ printf("Domain Server State:\t0x%x\n", general->domain_server_state);
+ printf("Server Role:\t%s\n", server_role_str(general->role));
+ printf("Unknown 3:\t0x%x\n", general->unknown3);
+}
+
+static void display_sam_dom_info_3(struct samr_DomInfo3 *info3)
+{
+ printf("Force Logoff:\t%d\n",
+ (int)nt_time_to_unix_abs(&info3->force_logoff_time));
+}
+
+static void display_sam_dom_info_4(struct samr_DomOEMInformation *oem)
+{
+ printf("Comment:\t%s\n", oem->oem_information.string);
+}
+
+static void display_sam_dom_info_5(struct samr_DomInfo5 *info5)
+{
+ printf("Domain:\t\t%s\n", info5->domain_name.string);
+}
+
+static void display_sam_dom_info_6(struct samr_DomInfo6 *info6)
+{
+ printf("Server:\t\t%s\n", info6->primary.string);
+}
+
+static void display_sam_dom_info_7(struct samr_DomInfo7 *info7)
+{
+ printf("Server Role:\t%s\n", server_role_str(info7->role));
+}
+
+static void display_sam_dom_info_8(struct samr_DomInfo8 *info8)
+{
+ printf("Sequence No:\t%llu\n", (unsigned long long)info8->sequence_num);
+ printf("Domain Create Time:\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(info8->domain_create_time)));
+}
+
+static void display_sam_dom_info_9(struct samr_DomInfo9 *info9)
+{
+ printf("Domain Server State:\t0x%x\n", info9->domain_server_state);
+}
+
+static void display_sam_dom_info_12(struct samr_DomInfo12 *info12)
+{
+ printf("Bad password lockout duration: %s\n",
+ display_time(info12->lockout_duration));
+ printf("Reset Lockout after: %s\n",
+ display_time(info12->lockout_window));
+ printf("Lockout after bad attempts: %d\n",
+ info12->lockout_threshold);
+}
+
+static void display_sam_dom_info_13(struct samr_DomInfo13 *info13)
+{
+ printf("Sequence No:\t%llu\n", (unsigned long long)info13->sequence_num);
+ printf("Domain Create Time:\t%s\n",
+ http_timestring(talloc_tos(), nt_time_to_unix(info13->domain_create_time)));
+ printf("Sequence No at last promotion:\t%llu\n",
+ (unsigned long long)info13->modified_count_at_last_promotion);
+}
+
+static void display_sam_info_1(struct samr_DispEntryGeneral *r)
+{
+ printf("index: 0x%x ", r->idx);
+ printf("RID: 0x%x ", r->rid);
+ printf("acb: 0x%08x ", r->acct_flags);
+ printf("Account: %s\t", r->account_name.string);
+ printf("Name: %s\t", r->full_name.string);
+ printf("Desc: %s\n", r->description.string);
+}
+
+static void display_sam_info_2(struct samr_DispEntryFull *r)
+{
+ printf("index: 0x%x ", r->idx);
+ printf("RID: 0x%x ", r->rid);
+ printf("acb: 0x%08x ", r->acct_flags);
+ printf("Account: %s\t", r->account_name.string);
+ printf("Desc: %s\n", r->description.string);
+}
+
+static void display_sam_info_3(struct samr_DispEntryFullGroup *r)
+{
+ printf("index: 0x%x ", r->idx);
+ printf("RID: 0x%x ", r->rid);
+ printf("acb: 0x%08x ", r->acct_flags);
+ printf("Account: %s\t", r->account_name.string);
+ printf("Desc: %s\n", r->description.string);
+}
+
+static void display_sam_info_4(struct samr_DispEntryAscii *r)
+{
+ printf("index: 0x%x ", r->idx);
+ printf("Account: %s\n", r->account_name.string);
+}
+
+static void display_sam_info_5(struct samr_DispEntryAscii *r)
+{
+ printf("index: 0x%x ", r->idx);
+ printf("Account: %s\n", r->account_name.string);
+}
+
+static NTSTATUS rpccli_try_samr_connects(
+ struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ uint32_t access_mask,
+ struct policy_handle *connect_pol)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ uint32_t start_idx = 0;
+ uint32_t i, num_entries;
+ struct samr_SamArray *sam = NULL;
+ struct dom_sid *domsid = NULL;
+
+ status = dcerpc_try_samr_connects(
+ b,
+ mem_ctx,
+ cli->srv_name_slash,
+ access_mask,
+ connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ if (!is_null_sid(&domain_sid)) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Look up the servers domain SID. Just pick the first
+ * non-builtin domain from samr_EnumDomains.
+ */
+
+ status = dcerpc_samr_EnumDomains(
+ b,
+ mem_ctx,
+ connect_pol,
+ &start_idx,
+ &sam,
+ 0xffff,
+ &num_entries,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto fail;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ if (!strequal(sam->entries[i].name.string, "builtin")) {
+ break;
+ }
+ }
+ if (i == num_entries) {
+ status = NT_STATUS_NOT_FOUND;
+ goto fail;
+ }
+
+ status = dcerpc_samr_LookupDomain(
+ b,
+ mem_ctx,
+ connect_pol,
+ &sam->entries[i].name,
+ &domsid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto fail;
+ }
+
+ sid_copy(&domain_sid, domsid);
+ TALLOC_FREE(domsid);
+
+ return NT_STATUS_OK;
+
+fail:
+ dcerpc_samr_Close(b, mem_ctx, connect_pol, &result);
+ return status;
+}
+
+/****************************************************************************
+ ****************************************************************************/
+
+static NTSTATUS get_domain_handle(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *sam,
+ struct policy_handle *connect_pol,
+ uint32_t access_mask,
+ struct dom_sid *_domain_sid,
+ struct policy_handle *domain_pol)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status = NT_STATUS_INVALID_PARAMETER, result;
+
+ if (strcasecmp_m(sam, "domain") == 0) {
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ connect_pol,
+ access_mask,
+ _domain_sid,
+ domain_pol,
+ &result);
+ } else if (strcasecmp_m(sam, "builtin") == 0) {
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ connect_pol,
+ access_mask,
+ discard_const_p(struct dom_sid2, &global_sid_Builtin),
+ domain_pol,
+ &result);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+/**********************************************************************
+ * Query user information
+ */
+static NTSTATUS cmd_samr_query_user(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ NTSTATUS status, result;
+ uint32_t info_level = 21;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ union samr_UserInfo *info = NULL;
+ uint32_t user_rid = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 4)) {
+ printf("Usage: %s rid [info level] [access mask] \n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ sscanf(argv[1], "%i", &user_rid);
+
+ if (argc > 2)
+ sscanf(argv[2], "%i", &info_level);
+
+ if (argc > 3)
+ sscanf(argv[3], "%x", &access_mask);
+
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ user_rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER) &&
+ (user_rid == 0)) {
+
+ /* Probably this was a user name, try lookupnames */
+ struct samr_Ids rids, types;
+ struct lsa_String lsa_acct_name;
+
+ init_lsa_String(&lsa_acct_name, argv[1]);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (NT_STATUS_IS_OK(result)) {
+ if (rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ rids.ids[0],
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+ }
+
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ &user_pol,
+ info_level,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ switch (info_level) {
+ case 7:
+ display_samr_user_info_7(&info->info7);
+ break;
+ case 9:
+ display_samr_user_info_9(&info->info9);
+ break;
+ case 16:
+ display_samr_user_info_16(&info->info16);
+ break;
+ case 20:
+ display_samr_user_info_20(&info->info20);
+ break;
+ case 21:
+ display_samr_user_info_21(&info->info21);
+ break;
+ default:
+ printf("Unsupported infolevel: %d\n", info_level);
+ break;
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+
+done:
+ return status;
+}
+
+/****************************************************************************
+ display group info
+ ****************************************************************************/
+static void display_group_info1(struct samr_GroupInfoAll *info1)
+{
+ printf("\tGroup Name:\t%s\n", info1->name.string);
+ printf("\tDescription:\t%s\n", info1->description.string);
+ printf("\tGroup Attribute:%d\n", info1->attributes);
+ printf("\tNum Members:%d\n", info1->num_members);
+}
+
+/****************************************************************************
+ display group info
+ ****************************************************************************/
+static void display_group_info2(struct lsa_String *info2)
+{
+ printf("\tGroup Description:%s\n", info2->string);
+}
+
+
+/****************************************************************************
+ display group info
+ ****************************************************************************/
+static void display_group_info3(struct samr_GroupInfoAttributes *info3)
+{
+ printf("\tGroup Attribute:%d\n", info3->attributes);
+}
+
+
+/****************************************************************************
+ display group info
+ ****************************************************************************/
+static void display_group_info4(struct lsa_String *info4)
+{
+ printf("\tGroup Description:%s\n", info4->string);
+}
+
+/****************************************************************************
+ display group info
+ ****************************************************************************/
+static void display_group_info5(struct samr_GroupInfoAll *info5)
+{
+ printf("\tGroup Name:\t%s\n", info5->name.string);
+ printf("\tDescription:\t%s\n", info5->description.string);
+ printf("\tGroup Attribute:%d\n", info5->attributes);
+ printf("\tNum Members:%d\n", info5->num_members);
+}
+
+/****************************************************************************
+ display sam sync structure
+ ****************************************************************************/
+static void display_group_info(union samr_GroupInfo *info,
+ enum samr_GroupInfoEnum level)
+{
+ switch (level) {
+ case 1:
+ display_group_info1(&info->all);
+ break;
+ case 2:
+ display_group_info2(&info->name);
+ break;
+ case 3:
+ display_group_info3(&info->attributes);
+ break;
+ case 4:
+ display_group_info4(&info->description);
+ break;
+ case 5:
+ display_group_info5(&info->all2);
+ break;
+ }
+}
+
+/***********************************************************************
+ * Query group information
+ */
+static NTSTATUS cmd_samr_query_group(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, group_pol;
+ NTSTATUS status, result;
+ enum samr_GroupInfoEnum info_level = GROUPINFOALL;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ union samr_GroupInfo *group_info = NULL;
+ uint32_t group_rid;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 4)) {
+ printf("Usage: %s rid [info level] [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ sscanf(argv[1], "%i", &group_rid);
+
+ if (argc > 2)
+ info_level = atoi(argv[2]);
+
+ if (argc > 3)
+ sscanf(argv[3], "%x", &access_mask);
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ group_rid,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryGroupInfo(b, mem_ctx,
+ &group_pol,
+ info_level,
+ &group_info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ display_group_info(group_info, info_level);
+
+ dcerpc_samr_Close(b, mem_ctx, &group_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+done:
+ return status;
+}
+
+/* Query groups a user is a member of */
+
+static NTSTATUS cmd_samr_query_usergroups(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol,
+ domain_pol,
+ user_pol;
+ NTSTATUS status, result;
+ uint32_t user_rid;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ int i;
+ struct samr_RidWithAttributeArray *rid_array = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 3)) {
+ printf("Usage: %s rid [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ sscanf(argv[1], "%i", &user_rid);
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ user_rid,
+ &user_pol,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_GetGroupsForUser(b, mem_ctx,
+ &user_pol,
+ &rid_array,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i = 0; i < rid_array->count; i++) {
+ printf("\tgroup rid:[0x%x] attr:[0x%x]\n",
+ rid_array->rids[i].rid,
+ rid_array->rids[i].attributes);
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+/* Query aliases a user is a member of */
+
+static NTSTATUS cmd_samr_query_useraliases(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ struct dom_sid *sids;
+ uint32_t num_sids;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ int i;
+ struct lsa_SidArray sid_array;
+ struct samr_Ids alias_rids;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3) {
+ printf("Usage: %s builtin|domain sid1 sid2 ...\n", argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sids = NULL;
+ num_sids = 0;
+
+ for (i=2; i<argc; i++) {
+ struct dom_sid tmp_sid;
+ if (!string_to_sid(&tmp_sid, argv[i])) {
+ printf("%s is not a legal SID\n", argv[i]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ result = add_sid_to_array(mem_ctx, &tmp_sid, &sids, &num_sids);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+ }
+
+ if (num_sids) {
+ sid_array.sids = talloc_zero_array(mem_ctx, struct lsa_SidPtr, num_sids);
+ if (sid_array.sids == NULL)
+ return NT_STATUS_NO_MEMORY;
+ } else {
+ sid_array.sids = NULL;
+ }
+
+ for (i=0; i<num_sids; i++) {
+ sid_array.sids[i].sid = dom_sid_dup(mem_ctx, &sids[i]);
+ if (!sid_array.sids[i].sid) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ sid_array.num_sids = num_sids;
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = get_domain_handle(cli, mem_ctx, argv[1],
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_GetAliasMembership(b, mem_ctx,
+ &domain_pol,
+ &sid_array,
+ &alias_rids,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i = 0; i < alias_rids.count; i++) {
+ printf("\tgroup rid:[0x%x]\n", alias_rids.ids[i]);
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+/* Query members of a group */
+
+static NTSTATUS cmd_samr_query_groupmem(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, group_pol;
+ NTSTATUS status, result;
+ uint32_t group_rid;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ int i;
+ unsigned int old_timeout;
+ struct samr_RidAttrArray *rids = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 3)) {
+ printf("Usage: %s rid [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ sscanf(argv[1], "%i", &group_rid);
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ group_rid,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Make sure to wait for our DC's reply */
+ old_timeout = rpccli_set_timeout(cli, 30000); /* 30 seconds. */
+ rpccli_set_timeout(cli, MAX(30000, old_timeout)); /* At least 30 sec */
+
+ status = dcerpc_samr_QueryGroupMember(b, mem_ctx,
+ &group_pol,
+ &rids,
+ &result);
+
+ rpccli_set_timeout(cli, old_timeout);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i = 0; i < rids->count; i++) {
+ printf("\trid:[0x%x] attr:[0x%x]\n", rids->rids[i],
+ rids->attributes[i]);
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &group_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+/* Enumerate domain users */
+
+static NTSTATUS cmd_samr_enum_dom_users(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol;
+ struct policy_handle domain_pol = { 0, };
+ NTSTATUS status, result;
+ uint32_t start_idx, num_dom_users, i;
+ struct samr_SamArray *dom_users = NULL;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ uint32_t acb_mask = ACB_NORMAL;
+ uint32_t size = 0xffff;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 1) || (argc > 4)) {
+ printf("Usage: %s [access_mask] [acb_mask] [size]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 1) {
+ sscanf(argv[1], "%x", &access_mask);
+ }
+
+ if (argc > 2) {
+ sscanf(argv[2], "%x", &acb_mask);
+ }
+
+ if (argc > 3) {
+ sscanf(argv[3], "%x", &size);
+ }
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = get_domain_handle(cli, mem_ctx, "domain",
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Enumerate domain users */
+
+ start_idx = 0;
+
+ do {
+ status = dcerpc_samr_EnumDomainUsers(b, mem_ctx,
+ &domain_pol,
+ &start_idx,
+ acb_mask,
+ &dom_users,
+ size,
+ &num_dom_users,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES)) {
+
+ for (i = 0; i < num_dom_users; i++)
+ printf("user:[%s] rid:[0x%x]\n",
+ dom_users->entries[i].name.string,
+ dom_users->entries[i].idx);
+ }
+
+ } while (NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES));
+
+ done:
+ if (is_valid_policy_hnd(&domain_pol))
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+
+ if (is_valid_policy_hnd(&connect_pol))
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+
+ return status;
+}
+
+/* Enumerate domain groups */
+
+static NTSTATUS cmd_samr_enum_dom_groups(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol;
+ struct policy_handle domain_pol = { 0, };
+ NTSTATUS status, result;
+ uint32_t start_idx, num_dom_groups, i;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct samr_SamArray *dom_groups = NULL;
+ uint32_t size = 0xffff;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 1) || (argc > 3)) {
+ printf("Usage: %s [access_mask] [max_size]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 1) {
+ sscanf(argv[1], "%x", &access_mask);
+ }
+
+ if (argc > 2) {
+ sscanf(argv[2], "%x", &size);
+ }
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = get_domain_handle(cli, mem_ctx, "domain",
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Enumerate domain groups */
+
+ start_idx = 0;
+
+ do {
+ status = dcerpc_samr_EnumDomainGroups(b, mem_ctx,
+ &domain_pol,
+ &start_idx,
+ &dom_groups,
+ size,
+ &num_dom_groups,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES)) {
+
+ for (i = 0; i < num_dom_groups; i++)
+ printf("group:[%s] rid:[0x%x]\n",
+ dom_groups->entries[i].name.string,
+ dom_groups->entries[i].idx);
+ }
+
+ } while (NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES));
+
+ done:
+ if (is_valid_policy_hnd(&domain_pol))
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+
+ if (is_valid_policy_hnd(&connect_pol))
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+
+ return status;
+}
+
+/* Enumerate alias groups */
+
+static NTSTATUS cmd_samr_enum_als_groups(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol;
+ struct policy_handle domain_pol = { 0, };
+ NTSTATUS status, result;
+ uint32_t start_idx, num_als_groups, i;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct samr_SamArray *als_groups = NULL;
+ uint32_t size = 0xffff;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 4)) {
+ printf("Usage: %s builtin|domain [access mask] [max_size]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 2) {
+ sscanf(argv[2], "%x", &access_mask);
+ }
+
+ if (argc > 3) {
+ sscanf(argv[3], "%x", &size);
+ }
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = get_domain_handle(cli, mem_ctx, argv[1],
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Enumerate alias groups */
+
+ start_idx = 0;
+
+ do {
+ status = dcerpc_samr_EnumDomainAliases(b, mem_ctx,
+ &domain_pol,
+ &start_idx,
+ &als_groups,
+ size,
+ &num_als_groups,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES)) {
+
+ for (i = 0; i < num_als_groups; i++)
+ printf("group:[%s] rid:[0x%x]\n",
+ als_groups->entries[i].name.string,
+ als_groups->entries[i].idx);
+ }
+ } while (NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES));
+
+ done:
+ if (is_valid_policy_hnd(&domain_pol))
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+
+ if (is_valid_policy_hnd(&connect_pol))
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+
+ return status;
+}
+
+/* Enumerate domains */
+
+static NTSTATUS cmd_samr_enum_domains(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol;
+ NTSTATUS status, result;
+ uint32_t start_idx, size, num_entries, i;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct samr_SamArray *sam = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 1) || (argc > 2)) {
+ printf("Usage: %s [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 1) {
+ sscanf(argv[1], "%x", &access_mask);
+ }
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ access_mask,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Enumerate domains */
+
+ start_idx = 0;
+ size = 0xffff;
+
+ do {
+ status = dcerpc_samr_EnumDomains(b, mem_ctx,
+ &connect_pol,
+ &start_idx,
+ &sam,
+ size,
+ &num_entries,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_IS_OK(result) ||
+ NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES)) {
+
+ for (i = 0; i < num_entries; i++)
+ printf("name:[%s] idx:[0x%x]\n",
+ sam->entries[i].name.string,
+ sam->entries[i].idx);
+ }
+ } while (NT_STATUS_V(result) == NT_STATUS_V(STATUS_MORE_ENTRIES));
+
+ done:
+ if (is_valid_policy_hnd(&connect_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ }
+
+ return status;
+}
+
+
+/* Query alias membership */
+
+static NTSTATUS cmd_samr_query_aliasmem(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, alias_pol;
+ NTSTATUS status, result;
+ uint32_t alias_rid, i;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct lsa_SidArray sid_array;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 3) || (argc > 4)) {
+ printf("Usage: %s builtin|domain rid [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ sscanf(argv[2], "%i", &alias_rid);
+
+ if (argc > 3)
+ sscanf(argv[3], "%x", &access_mask);
+
+ /* Open SAMR handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open handle on domain */
+
+ status = get_domain_handle(cli, mem_ctx, argv[1],
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open handle on alias */
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ alias_rid,
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_GetMembersInAlias(b, mem_ctx,
+ &alias_pol,
+ &sid_array,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i = 0; i < sid_array.num_sids; i++) {
+ struct dom_sid_buf sid_str;
+
+ printf("\tsid:[%s]\n",
+ dom_sid_str_buf(sid_array.sids[i].sid, &sid_str));
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &alias_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+/* Query alias info */
+
+static NTSTATUS cmd_samr_query_aliasinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, alias_pol;
+ NTSTATUS status, result;
+ uint32_t alias_rid;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ union samr_AliasInfo *info = NULL;
+ enum samr_AliasInfoEnum level = ALIASINFOALL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 3) || (argc > 4)) {
+ printf("Usage: %s builtin|domain rid [level] [access mask]\n",
+ argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ sscanf(argv[2], "%i", &alias_rid);
+
+ if (argc > 2) {
+ level = atoi(argv[3]);
+ }
+
+ if (argc > 3) {
+ sscanf(argv[4], "%x", &access_mask);
+ }
+
+ /* Open SAMR handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open handle on domain */
+
+ status = get_domain_handle(cli, mem_ctx, argv[1],
+ &connect_pol,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open handle on alias */
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ alias_rid,
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryAliasInfo(b, mem_ctx,
+ &alias_pol,
+ level,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ switch (level) {
+ case ALIASINFOALL:
+ printf("Name: %s\n", info->all.name.string);
+ printf("Description: %s\n", info->all.description.string);
+ printf("Num Members: %d\n", info->all.num_members);
+ break;
+ case ALIASINFONAME:
+ printf("Name: %s\n", info->name.string);
+ break;
+ case ALIASINFODESCRIPTION:
+ printf("Description: %s\n", info->description.string);
+ break;
+ default:
+ break;
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &alias_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+
+/* Query delete an alias membership */
+
+static NTSTATUS cmd_samr_delete_alias(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, alias_pol;
+ NTSTATUS status, result;
+ uint32_t alias_rid;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ int error = 0;
+
+ if (argc != 3) {
+ printf("Usage: %s builtin|domain [rid|name]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ alias_rid = smb_strtoul(argv[2], NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+
+ /* Open SAMR handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open handle on domain */
+
+ status = get_domain_handle(cli, mem_ctx, argv[1],
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Open handle on alias */
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ alias_rid,
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result) && (alias_rid == 0)) {
+ /* Probably this was a user name, try lookupnames */
+ struct samr_Ids rids, types;
+ struct lsa_String lsa_acct_name;
+
+ init_lsa_String(&lsa_acct_name, argv[2]);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_IS_OK(result)) {
+ if (rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ rids.ids[0],
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+ }
+
+ status = dcerpc_samr_DeleteDomAlias(b, mem_ctx,
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+/* Query display info */
+
+static NTSTATUS cmd_samr_query_dispinfo_internal(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv,
+ uint32_t opcode)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t start_idx=0, max_entries=250, max_size = 0xffff, num_entries = 0, i;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ uint32_t info_level = 1;
+ union samr_DispInfo info;
+ int loop_count = 0;
+ bool got_params = False; /* Use get_query_dispinfo_params() or not? */
+ uint32_t total_size, returned_size;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 6) {
+ printf("Usage: %s [info level] [start index] [max entries] [max size] [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc >= 2)
+ sscanf(argv[1], "%i", &info_level);
+
+ if (argc >= 3)
+ sscanf(argv[2], "%i", &start_idx);
+
+ if (argc >= 4) {
+ sscanf(argv[3], "%i", &max_entries);
+ got_params = True;
+ }
+
+ if (argc >= 5) {
+ sscanf(argv[4], "%i", &max_size);
+ got_params = True;
+ }
+
+ if (argc >= 6)
+ sscanf(argv[5], "%x", &access_mask);
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Query display info */
+
+ do {
+
+ if (!got_params)
+ dcerpc_get_query_dispinfo_params(
+ loop_count, &max_entries, &max_size);
+
+ switch (opcode) {
+ case NDR_SAMR_QUERYDISPLAYINFO:
+ status = dcerpc_samr_QueryDisplayInfo(b, mem_ctx,
+ &domain_pol,
+ info_level,
+ start_idx,
+ max_entries,
+ max_size,
+ &total_size,
+ &returned_size,
+ &info,
+ &result);
+ break;
+ case NDR_SAMR_QUERYDISPLAYINFO2:
+ status = dcerpc_samr_QueryDisplayInfo2(b, mem_ctx,
+ &domain_pol,
+ info_level,
+ start_idx,
+ max_entries,
+ max_size,
+ &total_size,
+ &returned_size,
+ &info,
+ &result);
+
+ break;
+ case NDR_SAMR_QUERYDISPLAYINFO3:
+ status = dcerpc_samr_QueryDisplayInfo3(b, mem_ctx,
+ &domain_pol,
+ info_level,
+ start_idx,
+ max_entries,
+ max_size,
+ &total_size,
+ &returned_size,
+ &info,
+ &result);
+
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ status = result;
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, NT_STATUS_NO_MORE_ENTRIES) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ break;
+ }
+
+ loop_count++;
+
+ switch (info_level) {
+ case 1:
+ num_entries = info.info1.count;
+ break;
+ case 2:
+ num_entries = info.info2.count;
+ break;
+ case 3:
+ num_entries = info.info3.count;
+ break;
+ case 4:
+ num_entries = info.info4.count;
+ break;
+ case 5:
+ num_entries = info.info5.count;
+ break;
+ default:
+ break;
+ }
+
+ start_idx += num_entries;
+
+ if (num_entries == 0)
+ break;
+
+ for (i = 0; i < num_entries; i++) {
+ switch (info_level) {
+ case 1:
+ display_sam_info_1(&info.info1.entries[i]);
+ break;
+ case 2:
+ display_sam_info_2(&info.info2.entries[i]);
+ break;
+ case 3:
+ display_sam_info_3(&info.info3.entries[i]);
+ break;
+ case 4:
+ display_sam_info_4(&info.info4.entries[i]);
+ break;
+ case 5:
+ display_sam_info_5(&info.info5.entries[i]);
+ break;
+ }
+ }
+ } while ( NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+static NTSTATUS cmd_samr_query_dispinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ return cmd_samr_query_dispinfo_internal(cli, mem_ctx, argc, argv,
+ NDR_SAMR_QUERYDISPLAYINFO);
+}
+
+static NTSTATUS cmd_samr_query_dispinfo2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ return cmd_samr_query_dispinfo_internal(cli, mem_ctx, argc, argv,
+ NDR_SAMR_QUERYDISPLAYINFO2);
+}
+
+static NTSTATUS cmd_samr_query_dispinfo3(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ return cmd_samr_query_dispinfo_internal(cli, mem_ctx, argc, argv,
+ NDR_SAMR_QUERYDISPLAYINFO3);
+}
+
+/* Query domain info */
+
+static NTSTATUS cmd_samr_query_dominfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t switch_level = 2;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ union samr_DomainInfo *info = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 3) {
+ printf("Usage: %s [info level] [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 1)
+ sscanf(argv[1], "%i", &switch_level);
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Query domain info */
+
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ &domain_pol,
+ switch_level,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Display domain info */
+
+ switch (switch_level) {
+ case 1:
+ display_sam_dom_info_1(&info->info1);
+ break;
+ case 2:
+ display_sam_dom_info_2(&info->general);
+ break;
+ case 3:
+ display_sam_dom_info_3(&info->info3);
+ break;
+ case 4:
+ display_sam_dom_info_4(&info->oem);
+ break;
+ case 5:
+ display_sam_dom_info_5(&info->info5);
+ break;
+ case 6:
+ display_sam_dom_info_6(&info->info6);
+ break;
+ case 7:
+ display_sam_dom_info_7(&info->info7);
+ break;
+ case 8:
+ display_sam_dom_info_8(&info->info8);
+ break;
+ case 9:
+ display_sam_dom_info_9(&info->info9);
+ break;
+ case 12:
+ display_sam_dom_info_12(&info->info12);
+ break;
+ case 13:
+ display_sam_dom_info_13(&info->info13);
+ break;
+
+ default:
+ printf("cannot display domain info for switch value %d\n",
+ switch_level);
+ break;
+ }
+
+ done:
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ return status;
+}
+
+/* Create domain user */
+
+static NTSTATUS cmd_samr_create_dom_user(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ NTSTATUS status, result;
+ struct lsa_String acct_name;
+ uint32_t acb_info;
+ uint32_t acct_flags, user_rid;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ uint32_t access_granted = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 3)) {
+ printf("Usage: %s username [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ init_lsa_String(&acct_name, argv[1]);
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Create domain user */
+
+ acb_info = ACB_NORMAL;
+ acct_flags = SEC_GENERIC_READ | SEC_GENERIC_WRITE | SEC_GENERIC_EXECUTE |
+ SEC_STD_WRITE_DAC | SEC_STD_DELETE |
+ SAMR_USER_ACCESS_SET_PASSWORD |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_SET_ATTRIBUTES;
+
+ status = dcerpc_samr_CreateUser2(b, mem_ctx,
+ &domain_pol,
+ &acct_name,
+ acb_info,
+ acct_flags,
+ &user_pol,
+ &access_granted,
+ &user_rid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ done:
+ return status;
+}
+
+/* Create domain group */
+
+static NTSTATUS cmd_samr_create_dom_group(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, group_pol;
+ NTSTATUS status, result;
+ struct lsa_String grp_name;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ uint32_t rid = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 3)) {
+ printf("Usage: %s groupname [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ init_lsa_String(&grp_name, argv[1]);
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Create domain user */
+ status = dcerpc_samr_CreateDomainGroup(b, mem_ctx,
+ &domain_pol,
+ &grp_name,
+ MAXIMUM_ALLOWED_ACCESS,
+ &group_pol,
+ &rid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_Close(b, mem_ctx, &group_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ done:
+ return status;
+}
+
+/* Create domain alias */
+
+static NTSTATUS cmd_samr_create_dom_alias(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, alias_pol;
+ NTSTATUS status, result;
+ struct lsa_String alias_name;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ uint32_t rid = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 3)) {
+ printf("Usage: %s aliasname [access mask]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ init_lsa_String(&alias_name, argv[1]);
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Create domain user */
+
+ status = dcerpc_samr_CreateDomAlias(b, mem_ctx,
+ &domain_pol,
+ &alias_name,
+ MAXIMUM_ALLOWED_ACCESS,
+ &alias_pol,
+ &rid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+
+ status = dcerpc_samr_Close(b, mem_ctx, &alias_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ done:
+ return status;
+}
+
+/* Lookup sam names */
+
+static NTSTATUS cmd_samr_lookup_names(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle connect_pol, domain_pol;
+ uint32_t num_names;
+ struct samr_Ids rids, name_types;
+ int i;
+ struct lsa_String *names = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3) {
+ printf("Usage: %s domain|builtin name1 [name2 [name3] [...]]\n", argv[0]);
+ printf("check on the domain SID: S-1-5-21-x-y-z\n");
+ printf("or check on the builtin SID: S-1-5-32\n");
+ return NT_STATUS_OK;
+ }
+
+ /* Get sam policy and domain handles */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = get_domain_handle(cli, mem_ctx, argv[1],
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Look up names */
+
+ num_names = argc - 2;
+
+ if ((names = talloc_array(mem_ctx, struct lsa_String, num_names)) == NULL) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < num_names; i++) {
+ init_lsa_String(&names[i], argv[i + 2]);
+ }
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ num_names,
+ names,
+ &rids,
+ &name_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (rids.count != num_names) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != num_names) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ /* Display results */
+
+ for (i = 0; i < num_names; i++)
+ printf("name %s: 0x%x (%d)\n", names[i].string, rids.ids[i],
+ name_types.ids[i]);
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+/* Lookup sam rids */
+
+static NTSTATUS cmd_samr_lookup_rids(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle connect_pol, domain_pol;
+ uint32_t num_rids, *rids;
+ struct lsa_Strings names;
+ struct samr_Ids types;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ int i;
+
+ if (argc < 3) {
+ printf("Usage: %s domain|builtin rid1 [rid2 [rid3] [...]]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ /* Get sam policy and domain handles */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = get_domain_handle(cli, mem_ctx, argv[1],
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Look up rids */
+
+ num_rids = argc - 2;
+
+ if ((rids = talloc_array(mem_ctx, uint32_t, num_rids)) == NULL) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i = 0; i < argc - 2; i++)
+ sscanf(argv[i + 2], "%i", &rids[i]);
+
+ status = dcerpc_samr_LookupRids(b, mem_ctx,
+ &domain_pol,
+ num_rids,
+ rids,
+ &names,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = result;
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED))
+ goto done;
+
+ /* Display results */
+ if (num_rids != names.count) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (num_rids != types.count) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ for (i = 0; i < num_rids; i++) {
+ printf("rid 0x%x: %s (%d)\n",
+ rids[i], names.names[i].string, types.ids[i]);
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+/* Delete domain group */
+
+static NTSTATUS cmd_samr_delete_dom_group(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle connect_pol, domain_pol, group_pol;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 3)) {
+ printf("Usage: %s groupname\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ /* Get sam policy and domain handles */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get handle on group */
+
+ {
+ struct samr_Ids group_rids, name_types;
+ struct lsa_String lsa_acct_name;
+
+ init_lsa_String(&lsa_acct_name, argv[1]);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &group_rids,
+ &name_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (group_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ group_rids.ids[0],
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ }
+
+ /* Delete group */
+
+ status = dcerpc_samr_DeleteDomainGroup(b, mem_ctx,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Display results */
+
+ dcerpc_samr_Close(b, mem_ctx, &group_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+
+ done:
+ return status;
+}
+
+/* Delete domain user */
+
+static NTSTATUS cmd_samr_delete_dom_user(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc < 2) || (argc > 3)) {
+ printf("Usage: %s username\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 2)
+ sscanf(argv[2], "%x", &access_mask);
+
+ /* Get sam policy and domain handles */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get handle on user */
+
+ {
+ struct samr_Ids user_rids, name_types;
+ struct lsa_String lsa_acct_name;
+
+ init_lsa_String(&lsa_acct_name, argv[1]);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ user_rids.ids[0],
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ }
+
+ /* Delete user */
+
+ status = dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Display results */
+
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+
+ done:
+ return status;
+}
+
+/**********************************************************************
+ * Query user security object
+ */
+static NTSTATUS cmd_samr_query_sec_obj(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, user_pol, *pol;
+ NTSTATUS status, result;
+ uint32_t sec_info = SECINFO_DACL;
+ uint32_t user_rid = 0;
+ TALLOC_CTX *ctx = NULL;
+ struct sec_desc_buf *sec_desc_buf=NULL;
+ bool domain = False;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ ctx=talloc_init("cmd_samr_query_sec_obj");
+
+ if ((argc < 1) || (argc > 3)) {
+ printf("Usage: %s [rid|-d] [sec_info]\n", argv[0]);
+ printf("\tSpecify rid for security on user, -d for security on domain\n");
+ talloc_destroy(ctx);
+ return NT_STATUS_OK;
+ }
+
+ if (argc > 1) {
+ if (strcmp(argv[1], "-d") == 0)
+ domain = True;
+ else
+ sscanf(argv[1], "%i", &user_rid);
+ }
+
+ if (argc == 3) {
+ sec_info = atoi(argv[2]);
+ }
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (domain || user_rid) {
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ }
+
+ if (user_rid) {
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ user_rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ }
+
+ /* Pick which query pol to use */
+
+ pol = &connect_pol;
+
+ if (domain)
+ pol = &domain_pol;
+
+ if (user_rid)
+ pol = &user_pol;
+
+ /* Query SAM security object */
+
+ status = dcerpc_samr_QuerySecurity(b, mem_ctx,
+ pol,
+ sec_info,
+ &sec_desc_buf,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ display_sec_desc(sec_desc_buf->sd);
+
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+done:
+ talloc_destroy(ctx);
+ return status;
+}
+
+static NTSTATUS cmd_samr_get_usrdom_pwinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ struct samr_PwInfo info;
+ uint32_t rid;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ printf("Usage: %s rid\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ sscanf(argv[1], "%i", &rid);
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_GetUserPwInfo(b, mem_ctx,
+ &user_pol,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = result;
+ if (NT_STATUS_IS_OK(result)) {
+ printf("%s\n",
+ NDR_PRINT_STRUCT_STRING(mem_ctx,
+ samr_PwInfo, &info));
+ }
+
+ done:
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+
+ return status;
+}
+
+static NTSTATUS cmd_samr_get_dom_pwinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ struct lsa_String domain_name;
+ struct samr_PwInfo info;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 1 || argc > 3) {
+ printf("Usage: %s <domain>\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ init_lsa_String(&domain_name, argv[1]);
+
+ status = dcerpc_samr_GetDomPwInfo(b, mem_ctx,
+ &domain_name,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (NT_STATUS_IS_OK(result)) {
+ printf("min_password_length: %d\n", info.min_password_length);
+ display_password_properties(info.password_properties);
+ }
+
+ return result;
+}
+
+/* Look up domain name */
+
+static NTSTATUS cmd_samr_lookup_domain(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct lsa_String domain_name;
+ struct dom_sid *sid = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ printf("Usage: %s domain_name\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ init_lsa_String(&domain_name, argv[1]);
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ access_mask,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_LookupDomain(b, mem_ctx,
+ &connect_pol,
+ &domain_name,
+ &sid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ if (NT_STATUS_IS_OK(result)) {
+ struct dom_sid_buf sid_str;
+ printf("SAMR_LOOKUP_DOMAIN: Domain Name: %s Domain SID: %s\n",
+ argv[1],
+ dom_sid_str_buf(sid, &sid_str));
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+done:
+ return status;
+}
+
+/* Change user password */
+
+static NTSTATUS cmd_samr_chgpasswd(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol;
+ struct policy_handle domain_pol = { 0, };
+ struct policy_handle user_pol = { 0, };
+ NTSTATUS status, result;
+ const char *user, *oldpass, *newpass;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct samr_Ids rids, types;
+ struct lsa_String lsa_acct_name;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3) {
+ printf("Usage: %s username oldpass newpass\n", argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ user = argv[1];
+ oldpass = argv[2];
+ newpass = argv[3];
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, user);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ access_mask,
+ rids.ids[0],
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Change user password */
+ status = rpccli_samr_chgpasswd_user(cli, mem_ctx,
+ &user_pol,
+ newpass,
+ oldpass);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&user_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ }
+ if (is_valid_policy_hnd(&domain_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ }
+ if (is_valid_policy_hnd(&connect_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ }
+
+ return status;
+}
+
+
+/* Change user password */
+
+static NTSTATUS cmd_samr_chgpasswd2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ const char *user, *oldpass, *newpass;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3) {
+ printf("Usage: %s username oldpass newpass\n", argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ user = argv[1];
+ oldpass = argv[2];
+ newpass = argv[3];
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Change user password */
+ status = rpccli_samr_chgpasswd_user2(cli, mem_ctx, user, newpass, oldpass);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ done:
+ return status;
+}
+
+
+/* Change user password */
+
+static NTSTATUS cmd_samr_chgpasswd3(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ const char *user, *oldpass, *newpass;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ struct samr_DomInfo1 *info = NULL;
+ struct userPwdChangeFailureInformation *reject = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 4) {
+ printf("Usage: %s username oldpass newpass\n", argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ user = argv[1];
+ oldpass = argv[2];
+ newpass = argv[3];
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Change user password */
+ status = rpccli_samr_chgpasswd_user3(cli, mem_ctx,
+ user,
+ newpass,
+ oldpass,
+ &info,
+ &reject);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION)) {
+
+ display_sam_dom_info_1(info);
+
+ switch (reject->extendedFailureReason) {
+ case SAM_PWD_CHANGE_PASSWORD_TOO_SHORT:
+ d_printf("SAM_PWD_CHANGE_PASSWORD_TOO_SHORT\n");
+ break;
+ case SAM_PWD_CHANGE_PWD_IN_HISTORY:
+ d_printf("SAM_PWD_CHANGE_PWD_IN_HISTORY\n");
+ break;
+ case SAM_PWD_CHANGE_NOT_COMPLEX:
+ d_printf("SAM_PWD_CHANGE_NOT_COMPLEX\n");
+ break;
+ default:
+ d_printf("unknown reject reason: %d\n",
+ reject->extendedFailureReason);
+ break;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ if (!NT_STATUS_IS_OK(status)) goto done;
+
+ done:
+ return status;
+}
+
+static NTSTATUS cmd_samr_chgpasswd4(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ const char *srv_name_slash = cli->srv_name_slash;
+ const char *user = NULL;
+ const char *oldpass = NULL;
+ const char *newpass = NULL;
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ if (argc < 4) {
+ printf("Usage: %s username oldpass newpass\n", argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ user = argv[1];
+ oldpass = argv[2];
+ newpass = argv[3];
+
+ /* Change user password */
+ status = dcerpc_samr_chgpasswd_user4(b,
+ mem_ctx,
+ srv_name_slash,
+ user,
+ oldpass,
+ newpass,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ }
+
+ return status;
+}
+
+static NTSTATUS cmd_samr_setuserinfo_int(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv,
+ int opcode)
+{
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ NTSTATUS status, result;
+ const char *user, *param;
+ uint32_t access_mask = MAXIMUM_ALLOWED_ACCESS;
+ uint32_t level;
+ uint32_t user_rid;
+ union samr_UserInfo info;
+ struct samr_CryptPassword pwd_buf;
+ struct samr_CryptPasswordEx pwd_buf_ex;
+ struct samr_EncryptedPasswordAES pwd_buf_aes;
+ uint8_t nt_hash[16];
+ uint8_t lm_hash[16];
+ DATA_BLOB session_key;
+ uint8_t salt_data[16];
+ DATA_BLOB salt = {
+ .data = salt_data,
+ .length = sizeof(salt_data),
+ };
+ uint8_t password_expired = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ TALLOC_CTX *frame = NULL;
+ int rc;
+
+ if (argc < 4) {
+ printf("Usage: %s username level password [password_expired]\n",
+ argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ frame = talloc_stackframe();
+
+ user = argv[1];
+ level = atoi(argv[2]);
+ param = argv[3];
+
+ if (argc >= 5) {
+ password_expired = atoi(argv[4]);
+ }
+
+ status = cli_get_session_key(frame, cli, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ generate_nonce_buffer(salt.data, salt.length);
+
+ switch(level) {
+ case 18:
+ case 21:
+ nt_lm_owf_gen(param, nt_hash, lm_hash);
+ break;
+ case 23:
+ case 24:
+ status = init_samr_CryptPassword(param, &session_key, &pwd_buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ break;
+ case 25:
+ case 26:
+ status = init_samr_CryptPasswordEx(param, &session_key, &pwd_buf_ex);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ break;
+ case 31:
+ status = init_samr_CryptPasswordAES(frame,
+ param,
+ &salt,
+ &session_key,
+ &pwd_buf_aes);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (level) {
+ case 18:
+ {
+ DATA_BLOB in,out;
+ in = data_blob_const(nt_hash, 16);
+ out = data_blob_talloc_zero(frame, 16);
+ if (out.data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+ memcpy(nt_hash, out.data, out.length);
+ }
+ {
+ DATA_BLOB in,out;
+ in = data_blob_const(lm_hash, 16);
+ out = data_blob_talloc_zero(frame, 15);
+ if (out.data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+ memcpy(lm_hash, out.data, out.length);
+ }
+
+ memcpy(info.info18.nt_pwd.hash, nt_hash, 16);
+ memcpy(info.info18.lm_pwd.hash, lm_hash, 16);
+ info.info18.nt_pwd_active = true;
+ info.info18.lm_pwd_active = true;
+ info.info18.password_expired = password_expired;
+
+ break;
+ case 21:
+ ZERO_STRUCT(info.info21);
+
+ info.info21.fields_present = SAMR_FIELD_NT_PASSWORD_PRESENT |
+ SAMR_FIELD_LM_PASSWORD_PRESENT;
+ if (argc >= 5) {
+ info.info21.fields_present |= SAMR_FIELD_EXPIRED_FLAG;
+ info.info21.password_expired = password_expired;
+ }
+
+ info.info21.lm_password_set = true;
+ info.info21.lm_owf_password.length = 16;
+ info.info21.lm_owf_password.size = 16;
+
+ info.info21.nt_password_set = true;
+ info.info21.nt_owf_password.length = 16;
+ info.info21.nt_owf_password.size = 16;
+
+ {
+ DATA_BLOB in,out;
+ in = data_blob_const(nt_hash, 16);
+ out = data_blob_talloc_zero(frame, 16);
+ if (out.data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+ info.info21.nt_owf_password.array =
+ (uint16_t *)talloc_memdup(frame, out.data, 16);
+ }
+ {
+ DATA_BLOB in,out;
+ in = data_blob_const(lm_hash, 16);
+ out = data_blob_talloc_zero(frame, 16);
+ rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_ENCRYPT);
+ if (rc != 0) {
+ status = gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+ info.info21.lm_owf_password.array =
+ (uint16_t *)talloc_memdup(frame, out.data, 16);
+ if (out.data == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ break;
+ case 23:
+ ZERO_STRUCT(info.info23);
+
+ info.info23.info.fields_present = SAMR_FIELD_NT_PASSWORD_PRESENT |
+ SAMR_FIELD_LM_PASSWORD_PRESENT;
+ if (argc >= 5) {
+ info.info23.info.fields_present |= SAMR_FIELD_EXPIRED_FLAG;
+ info.info23.info.password_expired = password_expired;
+ }
+
+ info.info23.password = pwd_buf;
+
+ break;
+ case 24:
+ info.info24.password = pwd_buf;
+ info.info24.password_expired = password_expired;
+
+ break;
+ case 25:
+ ZERO_STRUCT(info.info25);
+
+ info.info25.info.fields_present = SAMR_FIELD_NT_PASSWORD_PRESENT |
+ SAMR_FIELD_LM_PASSWORD_PRESENT;
+ if (argc >= 5) {
+ info.info25.info.fields_present |= SAMR_FIELD_EXPIRED_FLAG;
+ info.info25.info.password_expired = password_expired;
+ }
+
+ info.info25.password = pwd_buf_ex;
+
+ break;
+ case 26:
+ info.info26.password = pwd_buf_ex;
+ info.info26.password_expired = password_expired;
+
+ break;
+ case 31:
+ info.info31.password = pwd_buf_aes;
+ info.info31.password_expired = password_expired;
+ break;
+ default:
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ goto done;
+ }
+
+ /* Get sam policy handle */
+
+ status = rpccli_try_samr_connects(cli, frame,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, frame,
+ &connect_pol,
+ access_mask,
+ &domain_sid,
+ &domain_pol,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ user_rid = strtol(user, NULL, 0);
+ if (user_rid) {
+ status = dcerpc_samr_OpenUser(b, frame,
+ &domain_pol,
+ access_mask,
+ user_rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER) ||
+ (user_rid == 0)) {
+
+ /* Probably this was a user name, try lookupnames */
+ struct samr_Ids rids, types;
+ struct lsa_String lsa_acct_name;
+
+ init_lsa_String(&lsa_acct_name, user);
+
+ status = dcerpc_samr_LookupNames(b, frame,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ if (rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, frame,
+ &domain_pol,
+ access_mask,
+ rids.ids[0],
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+ }
+
+ switch (opcode) {
+ case NDR_SAMR_SETUSERINFO:
+ status = dcerpc_samr_SetUserInfo(b, frame,
+ &user_pol,
+ level,
+ &info,
+ &result);
+ break;
+ case NDR_SAMR_SETUSERINFO2:
+ status = dcerpc_samr_SetUserInfo2(b, frame,
+ &user_pol,
+ level,
+ &info,
+ &result);
+ break;
+ default:
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("status: %s\n", nt_errstr(status)));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ DEBUG(0,("result: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+ done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS cmd_samr_setuserinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ return cmd_samr_setuserinfo_int(cli, mem_ctx, argc, argv,
+ NDR_SAMR_SETUSERINFO);
+}
+
+static NTSTATUS cmd_samr_setuserinfo2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ return cmd_samr_setuserinfo_int(cli, mem_ctx, argc, argv,
+ NDR_SAMR_SETUSERINFO2);
+}
+
+static NTSTATUS cmd_samr_get_dispinfo_idx(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ struct policy_handle connect_handle;
+ struct policy_handle domain_handle = { 0, };
+ uint16_t level = 1;
+ struct lsa_String name;
+ uint32_t idx = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2 || argc > 3) {
+ printf("Usage: %s name level\n", argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ init_lsa_String(&name, argv[1]);
+
+ if (argc == 3) {
+ level = atoi(argv[2]);
+ }
+
+ status = rpccli_try_samr_connects(cli, mem_ctx,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &connect_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_handle,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &domain_sid,
+ &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_GetDisplayEnumerationIndex(b, mem_ctx,
+ &domain_handle,
+ level,
+ &name,
+ &idx,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) {
+ printf("idx: %d (0x%08x)\n", idx, idx);
+ }
+ done:
+
+ if (is_valid_policy_hnd(&domain_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_handle, &result);
+ }
+ if (is_valid_policy_hnd(&connect_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_handle, &result);
+ }
+
+ return status;
+
+}
+/* List of commands exported by this module */
+
+struct cmd_set samr_commands[] = {
+
+ {
+ .name = "SAMR",
+ },
+
+ {
+ .name = "queryuser",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_user,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query user info",
+ .usage = "",
+ },
+ {
+ .name = "querygroup",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_group,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query group info",
+ .usage = "",
+ },
+ {
+ .name = "queryusergroups",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_usergroups,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query user groups",
+ .usage = "",
+ },
+ {
+ .name = "queryuseraliases",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_useraliases,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query user aliases",
+ .usage = "",
+ },
+ {
+ .name = "querygroupmem",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_groupmem,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query group membership",
+ .usage = "",
+ },
+ {
+ .name = "queryaliasmem",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_aliasmem,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query alias membership",
+ .usage = "",
+ },
+ {
+ .name = "queryaliasinfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_aliasinfo,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query alias info",
+ .usage = "",
+ },
+ {
+ .name = "deletealias",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_delete_alias,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Delete an alias",
+ .usage = "",
+ },
+ {
+ .name = "querydispinfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_dispinfo,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query display info",
+ .usage = "",
+ },
+ {
+ .name = "querydispinfo2",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_dispinfo2,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query display info",
+ .usage = "",
+ },
+ {
+ .name = "querydispinfo3",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_dispinfo3,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query display info",
+ .usage = "",
+ },
+ {
+ .name = "querydominfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_dominfo,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query domain info",
+ .usage = "",
+ },
+ {
+ .name = "enumdomusers",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_enum_dom_users,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Enumerate domain users",
+ .usage = "",
+ },
+ {
+ .name = "enumdomgroups",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_enum_dom_groups,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Enumerate domain groups",
+ .usage = "",
+ },
+ {
+ .name = "enumalsgroups",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_enum_als_groups,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Enumerate alias groups",
+ .usage = "",
+ },
+ {
+ .name = "enumdomains",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_enum_domains,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Enumerate domains",
+ .usage = "",
+ },
+
+ {
+ .name = "createdomuser",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_create_dom_user,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Create domain user",
+ .usage = "",
+ },
+ {
+ .name = "createdomgroup",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_create_dom_group,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Create domain group",
+ .usage = "",
+ },
+ {
+ .name = "createdomalias",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_create_dom_alias,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Create domain alias",
+ .usage = "",
+ },
+ {
+ .name = "samlookupnames",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_lookup_names,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Look up names",
+ .usage = "",
+ },
+ {
+ .name = "samlookuprids",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_lookup_rids,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Look up names",
+ .usage = "",
+ },
+ {
+ .name = "deletedomgroup",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_delete_dom_group,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Delete domain group",
+ .usage = "",
+ },
+ {
+ .name = "deletedomuser",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_delete_dom_user,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Delete domain user",
+ .usage = "",
+ },
+ {
+ .name = "samquerysecobj",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_query_sec_obj,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Query SAMR security object",
+ .usage = "",
+ },
+ {
+ .name = "getdompwinfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_get_dom_pwinfo,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Retrieve domain password info",
+ .usage = "",
+ },
+ {
+ .name = "getusrdompwinfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_get_usrdom_pwinfo,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Retrieve user domain password info",
+ .usage = "",
+ },
+
+ {
+ .name = "lookupdomain",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_lookup_domain,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Lookup Domain Name",
+ .usage = "",
+ },
+ {
+ .name = "chgpasswd",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_chgpasswd,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Change user password",
+ .usage = "",
+ },
+ {
+ .name = "chgpasswd2",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_chgpasswd2,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Change user password",
+ .usage = "",
+ },
+ {
+ .name = "chgpasswd3",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_chgpasswd3,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Change user password",
+ .usage = "",
+ },
+ {
+ .name = "chgpasswd4",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_chgpasswd4,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Change user password",
+ .usage = "",
+ },
+ {
+ .name = "getdispinfoidx",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_get_dispinfo_idx,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Get Display Information Index",
+ .usage = "",
+ },
+ {
+ .name = "setuserinfo",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_setuserinfo,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Set user info",
+ .usage = "",
+ },
+ {
+ .name = "setuserinfo2",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_samr_setuserinfo2,
+ .wfn = NULL,
+ .table = &ndr_table_samr,
+ .rpc_pipe = NULL,
+ .description = "Set user info2",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_shutdown.c b/source3/rpcclient/cmd_shutdown.c
new file mode 100644
index 0000000..72027a8
--- /dev/null
+++ b/source3/rpcclient/cmd_shutdown.c
@@ -0,0 +1,137 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT Domain Authentication SMB / MSRPC client
+ Copyright (C) Andrew Tridgell 1994-1997,
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1997,
+ Copyright (C) Simo Sorce 2001,
+ Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+
+#if 0 /* don't uncomment this unless you remove the getopt() calls */
+ /* use net rpc shutdown instead */
+
+/****************************************************************************
+nt shutdown init
+****************************************************************************/
+static NTSTATUS cmd_shutdown_init(struct cli_state *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ fstring msg;
+ uint32_t timeout = 20;
+ bool force = False;
+ bool reboot = False;
+ int opt;
+
+ *msg = 0;
+ optind = 0; /* TODO: test if this hack works on other systems too --simo */
+
+ while ((opt = getopt(argc, argv, "m:t:rf")) != EOF)
+ {
+ /*fprintf (stderr, "[%s]\n", argv[argc-1]);*/
+
+ switch (opt)
+ {
+ case 'm':
+ fstrcpy(msg, optarg);
+ /*fprintf (stderr, "[%s|%s]\n", optarg, msg);*/
+ break;
+
+ case 't':
+ timeout = atoi(optarg);
+ /*fprintf (stderr, "[%s|%d]\n", optarg, timeout);*/
+ break;
+
+ case 'r':
+ reboot = True;
+ break;
+
+ case 'f':
+ force = True;
+ break;
+
+ }
+ }
+
+ /* create an entry */
+ result = cli_shutdown_init(cli, mem_ctx, msg, timeout, reboot, force);
+
+ if (NT_STATUS_IS_OK(result))
+ DEBUG(5,("cmd_shutdown_init: query succeeded\n"));
+ else
+ DEBUG(5,("cmd_shutdown_init: query failed\n"));
+
+ return result;
+}
+
+/****************************************************************************
+abort a shutdown
+****************************************************************************/
+static NTSTATUS cmd_shutdown_abort(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ result = cli_shutdown_abort(cli, mem_ctx);
+
+ if (NT_STATUS_IS_OK(result))
+ DEBUG(5,("cmd_shutdown_abort: query succeeded\n"));
+ else
+ DEBUG(5,("cmd_shutdown_abort: query failed\n"));
+
+ return result;
+}
+#endif
+
+
+/* List of commands exported by this module */
+struct cmd_set shutdown_commands[] = {
+
+ {
+ .name = "SHUTDOWN",
+ },
+
+#if 0
+ {
+ .name = "shutdowninit",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_shutdown_init,
+ .wfn = NULL,
+ .table = &ndr_table_initshutdown.syntax_id,
+ .rpc_pipe = "Remote Shutdown (over shutdown pipe)",
+ .description = "syntax: shutdown [-m message] "
+ "[-t timeout] [-r] [-h] [-f] (-r == "
+ "reboot, -h == halt, -f == force)",
+ },
+
+ {
+ .name = "shutdownabort",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_shutdown_abort,
+ .wfn = NULL,
+ .table = &ndr_table_initshutdown.syntax_id,
+ .rpc_pipe = "Abort Shutdown (over shutdown pipe)",
+ .description = "syntax: shutdownabort",
+ },
+#endif
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_spoolss.c b/source3/rpcclient/cmd_spoolss.c
new file mode 100644
index 0000000..f425e75
--- /dev/null
+++ b/source3/rpcclient/cmd_spoolss.c
@@ -0,0 +1,4543 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Gerald Carter 2001-2005
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Andrew Tridgell 1992-1999
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1999
+ Copyright (C) Guenther Deschner 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "nt_printing.h"
+#include "../libcli/security/display_sec.h"
+#include "../libcli/security/security_descriptor.h"
+#include "../libcli/registry/util_reg.h"
+#include "libsmb/libsmb.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/cmdline/cmdline.h"
+
+#define RPCCLIENT_PRINTERNAME(_printername, _cli, _arg) \
+{ \
+ _printername = talloc_asprintf_strupper_m(mem_ctx, "%s\\%s", \
+ _cli->srv_name_slash, _arg); \
+ W_ERROR_HAVE_NO_MEMORY(_printername); \
+}
+
+
+/**
+ * @file
+ *
+ * rpcclient module for SPOOLSS rpc pipe.
+ *
+ * This generally just parses and checks command lines, and then calls
+ * a cli_spoolss function.
+ **/
+
+/****************************************************************************
+ function to do the mapping between the long architecture name and
+ the short one.
+****************************************************************************/
+
+static const char *cmd_spoolss_get_short_archi(const char *long_archi)
+{
+ int i=-1;
+
+ DEBUG(107,("Getting architecture dependent directory\n"));
+ do {
+ i++;
+ } while ( (archi_table[i].long_archi!=NULL ) &&
+ strcasecmp_m(long_archi, archi_table[i].long_archi) );
+
+ if (archi_table[i].long_archi==NULL) {
+ DEBUGADD(10,("Unknown architecture [%s] !\n", long_archi));
+ return NULL;
+ }
+
+ /* this might be client code - but shouldn't this be an fstrcpy etc? */
+
+
+ DEBUGADD(108,("index: [%d]\n", i));
+ DEBUGADD(108,("long architecture: [%s]\n", archi_table[i].long_archi));
+ DEBUGADD(108,("short architecture: [%s]\n", archi_table[i].short_archi));
+
+ return archi_table[i].short_archi;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_open_printer_ex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR werror;
+ struct policy_handle hnd;
+ uint32_t access_mask = PRINTER_ALL_ACCESS;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2) {
+ printf("Usage: %s <printername> [access_mask]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 3) {
+ sscanf(argv[2], "%x", &access_mask);
+ }
+
+ /* Open the printer handle */
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ argv[1],
+ access_mask,
+ &hnd);
+ if (W_ERROR_IS_OK(werror)) {
+ printf("Printer %s opened successfully\n", argv[1]);
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &werror);
+
+ if (!W_ERROR_IS_OK(werror)) {
+ printf("Error closing printer handle! (%s)\n",
+ get_dos_error_msg(werror));
+ }
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_open_printer(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR werror;
+ struct policy_handle hnd;
+ uint32_t access_mask = PRINTER_ALL_ACCESS;
+ NTSTATUS status;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ if (argc < 2) {
+ printf("Usage: %s <printername> [access_mask]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 3) {
+ sscanf(argv[2], "%x", &access_mask);
+ }
+
+ /* Open the printer handle */
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ argv[1],
+ NULL,
+ devmode_ctr,
+ access_mask,
+ &hnd,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (W_ERROR_IS_OK(werror)) {
+ printf("Printer %s opened successfully\n", argv[1]);
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &werror);
+
+ if (!W_ERROR_IS_OK(werror)) {
+ printf("Error closing printer handle! (%s)\n",
+ get_dos_error_msg(werror));
+ }
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info0(struct spoolss_PrinterInfo0 *r)
+{
+ if (!r)
+ return;
+
+ printf("\tprintername:[%s]\n", r->printername);
+ printf("\tservername:[%s]\n", r->servername);
+ printf("\tcjobs:[0x%x]\n", r->cjobs);
+ printf("\ttotal_jobs:[0x%x]\n", r->total_jobs);
+ printf("\ttotal_bytes:[0x%x]\n", r->total_bytes);
+ printf("\t:date: [%d]-[%d]-[%d] (%d)\n", r->time.year, r->time.month,
+ r->time.day, r->time.day_of_week);
+ printf("\t:time: [%d]-[%d]-[%d]-[%d]\n", r->time.hour, r->time.minute,
+ r->time.second, r->time.millisecond);
+
+ printf("\tglobal_counter:[0x%x]\n", r->global_counter);
+ printf("\ttotal_pages:[0x%x]\n", r->total_pages);
+
+ printf("\tversion:[0x%x]\n", r->version);
+ printf("\tfree_build:[0x%x]\n", r->free_build);
+ printf("\tspooling:[0x%x]\n", r->spooling);
+ printf("\tmax_spooling:[0x%x]\n", r->max_spooling);
+ printf("\tsession_counter:[0x%x]\n", r->session_counter);
+ printf("\tnum_error_out_of_paper:[0x%x]\n", r->num_error_out_of_paper);
+ printf("\tnum_error_not_ready:[0x%x]\n", r->num_error_not_ready);
+ printf("\tjob_error:[0x%x]\n", r->job_error);
+ printf("\tnumber_of_processors:[0x%x]\n", r->number_of_processors);
+ printf("\tprocessor_type:[0x%x]\n", r->processor_type);
+ printf("\thigh_part_total_bytes:[0x%x]\n", r->high_part_total_bytes);
+ printf("\tchange_id:[0x%x]\n", r->change_id);
+ printf("\tlast_error: %s\n", win_errstr(r->last_error));
+ printf("\tstatus:[0x%x]\n", r->status);
+ printf("\tenumerate_network_printers:[0x%x]\n", r->enumerate_network_printers);
+ printf("\tc_setprinter:[0x%x]\n", r->c_setprinter);
+ printf("\tprocessor_architecture:[0x%x]\n", r->processor_architecture);
+ printf("\tprocessor_level:[0x%x]\n", r->processor_level);
+ printf("\tref_ic:[0x%x]\n", r->ref_ic);
+ printf("\treserved2:[0x%x]\n", r->reserved2);
+ printf("\treserved3:[0x%x]\n", r->reserved3);
+
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info1(struct spoolss_PrinterInfo1 *r)
+{
+ printf("\tflags:[0x%x]\n", r->flags);
+ printf("\tname:[%s]\n", r->name);
+ printf("\tdescription:[%s]\n", r->description);
+ printf("\tcomment:[%s]\n", r->comment);
+
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info2(struct spoolss_PrinterInfo2 *r)
+{
+ printf("\tservername:[%s]\n", r->servername);
+ printf("\tprintername:[%s]\n", r->printername);
+ printf("\tsharename:[%s]\n", r->sharename);
+ printf("\tportname:[%s]\n", r->portname);
+ printf("\tdrivername:[%s]\n", r->drivername);
+ printf("\tcomment:[%s]\n", r->comment);
+ printf("\tlocation:[%s]\n", r->location);
+ printf("\tsepfile:[%s]\n", r->sepfile);
+ printf("\tprintprocessor:[%s]\n", r->printprocessor);
+ printf("\tdatatype:[%s]\n", r->datatype);
+ printf("\tparameters:[%s]\n", r->parameters);
+ printf("\tattributes:[0x%x]\n", r->attributes);
+ printf("\tpriority:[0x%x]\n", r->priority);
+ printf("\tdefaultpriority:[0x%x]\n", r->defaultpriority);
+ printf("\tstarttime:[0x%x]\n", r->starttime);
+ printf("\tuntiltime:[0x%x]\n", r->untiltime);
+ printf("\tstatus:[0x%x]\n", r->status);
+ printf("\tcjobs:[0x%x]\n", r->cjobs);
+ printf("\taverageppm:[0x%x]\n", r->averageppm);
+
+ if (r->secdesc)
+ display_sec_desc(r->secdesc);
+
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info3(struct spoolss_PrinterInfo3 *r)
+{
+ display_sec_desc(r->secdesc);
+
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info4(struct spoolss_PrinterInfo4 *r)
+{
+ printf("\tservername:[%s]\n", r->servername);
+ printf("\tprintername:[%s]\n", r->printername);
+ printf("\tattributes:[0x%x]\n", r->attributes);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info5(struct spoolss_PrinterInfo5 *r)
+{
+ printf("\tprintername:[%s]\n", r->printername);
+ printf("\tportname:[%s]\n", r->portname);
+ printf("\tattributes:[0x%x]\n", r->attributes);
+ printf("\tdevice_not_selected_timeout:[0x%x]\n", r->device_not_selected_timeout);
+ printf("\ttransmission_retry_timeout:[0x%x]\n", r->transmission_retry_timeout);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info6(struct spoolss_PrinterInfo6 *r)
+{
+ printf("\tstatus:[0x%x]\n", r->status);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_info7(struct spoolss_PrinterInfo7 *r)
+{
+ printf("\tguid:[%s]\n", r->guid);
+ printf("\taction:[0x%x]\n", r->action);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_enum_printers(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ uint32_t level = 1;
+ union spoolss_PrinterInfo *info;
+ uint32_t i, count;
+ const char *name;
+ uint32_t flags = PRINTER_ENUM_LOCAL;
+
+ if (argc > 4) {
+ printf("Usage: %s [level] [name] [flags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ level = atoi(argv[1]);
+ }
+
+ if (argc >= 3) {
+ name = argv[2];
+ } else {
+ name = cli->srv_name_slash;
+ }
+
+ if (argc == 4) {
+ flags = atoi(argv[3]);
+ }
+
+ result = rpccli_spoolss_enumprinters(cli, mem_ctx,
+ flags,
+ name,
+ level,
+ 0,
+ &count,
+ &info);
+ if (W_ERROR_IS_OK(result)) {
+
+ if (!count) {
+ printf ("No printers returned.\n");
+ goto done;
+ }
+
+ for (i = 0; i < count; i++) {
+ switch (level) {
+ case 0:
+ display_print_info0(&info[i].info0);
+ break;
+ case 1:
+ display_print_info1(&info[i].info1);
+ break;
+ case 2:
+ display_print_info2(&info[i].info2);
+ break;
+ case 3:
+ display_print_info3(&info[i].info3);
+ break;
+ case 4:
+ display_print_info4(&info[i].info4);
+ break;
+ case 5:
+ display_print_info5(&info[i].info5);
+ break;
+ case 6:
+ display_print_info6(&info[i].info6);
+ break;
+ default:
+ printf("unknown info level %d\n", level);
+ goto done;
+ }
+ }
+ }
+ done:
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_port_info_1(struct spoolss_PortInfo1 *r)
+{
+ printf("\tPort Name:\t[%s]\n", r->port_name);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_port_info_2(struct spoolss_PortInfo2 *r)
+{
+ printf("\tPort Name:\t[%s]\n", r->port_name);
+ printf("\tMonitor Name:\t[%s]\n", r->monitor_name);
+ printf("\tDescription:\t[%s]\n", r->description);
+ printf("\tPort Type:\t" );
+ if (r->port_type) {
+ int comma = 0; /* hack */
+ printf( "[" );
+ if (r->port_type & SPOOLSS_PORT_TYPE_READ) {
+ printf( "Read" );
+ comma = 1;
+ }
+ if (r->port_type & SPOOLSS_PORT_TYPE_WRITE) {
+ printf( "%sWrite", comma ? ", " : "" );
+ comma = 1;
+ }
+ /* These two have slightly different interpretations
+ on 95/98/ME but I'm disregarding that for now */
+ if (r->port_type & SPOOLSS_PORT_TYPE_REDIRECTED) {
+ printf( "%sRedirected", comma ? ", " : "" );
+ comma = 1;
+ }
+ if (r->port_type & SPOOLSS_PORT_TYPE_NET_ATTACHED) {
+ printf( "%sNet-Attached", comma ? ", " : "" );
+ }
+ printf( "]\n" );
+ } else {
+ printf( "[Unset]\n" );
+ }
+ printf("\tReserved:\t[%d]\n", r->reserved);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_enum_ports(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ uint32_t level = 1;
+ uint32_t count;
+ union spoolss_PortInfo *info;
+
+ if (argc > 2) {
+ printf("Usage: %s [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc == 2) {
+ level = atoi(argv[1]);
+ }
+
+ /* Enumerate ports */
+
+ result = rpccli_spoolss_enumports(cli, mem_ctx,
+ cli->srv_name_slash,
+ level,
+ 0,
+ &count,
+ &info);
+ if (W_ERROR_IS_OK(result)) {
+ int i;
+
+ for (i = 0; i < count; i++) {
+ switch (level) {
+ case 1:
+ display_port_info_1(&info[i].info1);
+ break;
+ case 2:
+ display_port_info_2(&info[i].info2);
+ break;
+ default:
+ printf("unknown info level %d\n", level);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_setprinter(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t info_level = 2;
+ union spoolss_PrinterInfo info;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_SetPrinterInfo2 info2;
+ const char *printername, *comment = NULL;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc == 1 || argc > 3) {
+ printf("Usage: %s printername comment\n", argv[0]);
+
+ return WERR_OK;
+ }
+
+ /* Open a printer handle */
+ if (argc == 3) {
+ comment = argv[2];
+ }
+
+ ZERO_STRUCT(devmode_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ /* get a printer handle */
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ PRINTER_ALL_ACCESS,
+ &pol);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Get printer info */
+ result = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &pol,
+ info_level,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+
+ /* Modify the comment. */
+ spoolss_printerinfo2_to_setprinterinfo2(&info.info2, &info2);
+ info2.comment = comment;
+
+ info_ctr.level = 2;
+ info_ctr.info.info2 = &info2;
+
+ status = dcerpc_spoolss_SetPrinter(b, mem_ctx,
+ &pol,
+ &info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ 0, /* command */
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (W_ERROR_IS_OK(result))
+ printf("Success in setting comment.\n");
+
+ done:
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_setprintername(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t info_level = 2;
+ union spoolss_PrinterInfo info;
+ const char *printername,
+ *new_printername = NULL;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_SetPrinterInfo2 info2;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ if (argc == 1 || argc > 3) {
+ printf("Usage: %s printername new_printername\n", argv[0]);
+
+ return WERR_OK;
+ }
+
+ /* Open a printer handle */
+ if (argc == 3) {
+ new_printername = argv[2];
+ }
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ /* get a printer handle */
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ PRINTER_ALL_ACCESS,
+ &pol);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Get printer info */
+ result = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &pol,
+ info_level,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Modify the printername. */
+ spoolss_printerinfo2_to_setprinterinfo2(&info.info2, &info2);
+ info2.printername = new_printername;
+
+ info_ctr.level = 2;
+ info_ctr.info.info2 = &info2;
+
+ status = dcerpc_spoolss_SetPrinter(b, mem_ctx,
+ &pol,
+ &info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ 0, /* command */
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (W_ERROR_IS_OK(result))
+ printf("Success in setting printername.\n");
+
+ done:
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getprinter(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ WERROR result;
+ uint32_t level = 1;
+ const char *printername;
+ union spoolss_PrinterInfo info;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc == 1 || argc > 3) {
+ printf("Usage: %s <printername> [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Open a printer handle */
+ if (argc == 3) {
+ level = atoi(argv[2]);
+ }
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ /* get a printer handle */
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Get printer info */
+
+ result = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &pol,
+ level,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Display printer info */
+ switch (level) {
+ case 0:
+ display_print_info0(&info.info0);
+ break;
+ case 1:
+ display_print_info1(&info.info1);
+ break;
+ case 2:
+ display_print_info2(&info.info2);
+ break;
+ case 3:
+ display_print_info3(&info.info3);
+ break;
+ case 4:
+ display_print_info4(&info.info4);
+ break;
+ case 5:
+ display_print_info5(&info.info5);
+ break;
+ case 6:
+ display_print_info6(&info.info6);
+ break;
+ case 7:
+ display_print_info7(&info.info7);
+ break;
+ default:
+ printf("unknown info level %d\n", level);
+ break;
+ }
+ done:
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_reg_value(const char *name, enum winreg_Type type, DATA_BLOB blob)
+{
+ const char *text = NULL;
+
+ switch(type) {
+ case REG_DWORD:
+ if (blob.length >= sizeof(uint32_t)) {
+ printf("%s: REG_DWORD: 0x%08x\n", name, IVAL(blob.data,0));
+ } else {
+ printf("%s: REG_DWORD: <invalid>\n", name);
+ }
+ break;
+ case REG_SZ:
+ pull_reg_sz(talloc_tos(), &blob, &text);
+ printf("%s: REG_SZ: %s\n", name, text ? text : "");
+ break;
+ case REG_BINARY: {
+ char *hex = hex_encode_talloc(NULL, blob.data, blob.length);
+ size_t i, len;
+ printf("%s: REG_BINARY:", name);
+ len = strlen(hex);
+ for (i=0; i<len; i++) {
+ if (hex[i] == '\0') {
+ break;
+ }
+ if (i%40 == 0) {
+ putchar('\n');
+ }
+ putchar(hex[i]);
+ }
+ TALLOC_FREE(hex);
+ putchar('\n');
+ break;
+ }
+ case REG_MULTI_SZ: {
+ uint32_t i;
+ const char **values;
+
+ if (!pull_reg_multi_sz(NULL, &blob, &values)) {
+ d_printf("pull_reg_multi_sz failed\n");
+ break;
+ }
+
+ printf("%s: REG_MULTI_SZ: \n", name);
+ for (i=0; values[i] != NULL; i++) {
+ d_printf("%s\n", values[i]);
+ }
+ TALLOC_FREE(values);
+ break;
+ }
+ default:
+ printf("%s: unknown type %d\n", name, type);
+ }
+
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_printer_data(const char *v,
+ enum winreg_Type type,
+ uint8_t *data,
+ uint32_t length)
+{
+ int i;
+ union spoolss_PrinterData r;
+ DATA_BLOB blob = data_blob_const(data, length);
+ WERROR result;
+ enum ndr_err_code ndr_err;
+
+ result = pull_spoolss_PrinterData(talloc_tos(), &blob, &r, type);
+ if (!W_ERROR_IS_OK(result)) {
+ return;
+ }
+
+ switch (type) {
+ case REG_DWORD:
+ printf("%s: REG_DWORD: 0x%08x\n", v, r.value);
+ break;
+ case REG_SZ:
+ printf("%s: REG_SZ: %s\n", v, r.string);
+ break;
+ case REG_BINARY: {
+ char *hex = hex_encode_talloc(NULL,
+ r.binary.data, r.binary.length);
+ size_t len;
+ printf("%s: REG_BINARY:", v);
+ len = strlen(hex);
+ for (i=0; i<len; i++) {
+ if (hex[i] == '\0') {
+ break;
+ }
+ if (i%40 == 0) {
+ putchar('\n');
+ }
+ putchar(hex[i]);
+ }
+ TALLOC_FREE(hex);
+ putchar('\n');
+ putchar('\n');
+
+ if (strequal(v, "OsVersion")) {
+ struct spoolss_OSVersion os;
+ ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &os,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_OSVersion);
+ if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ // add output here;
+ printf("OsMajor: %u\n", os.major);
+ printf("OsMinor: %u\n", os.minor);
+ printf("OsBuild: %u\n", os.build);
+ NDR_PRINT_DEBUG(spoolss_OSVersion, &os);
+ }
+ }
+ if (strequal(v, "OsVersionEx")) {
+ struct spoolss_OSVersionEx os;
+ ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &os,
+ (ndr_pull_flags_fn_t)ndr_pull_spoolss_OSVersionEx);
+ if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ printf("OsMajor: %u\n", os.major);
+ printf("OsMinor: %u\n", os.minor);
+ printf("OsBuild: %u\n", os.build);
+ printf("ServicePackMajor: %u\n", os.service_pack_major);
+ printf("ServicePackMinor: %u\n", os.service_pack_minor);
+ NDR_PRINT_DEBUG(spoolss_OSVersionEx, &os);
+ }
+ }
+ break;
+ }
+ case REG_MULTI_SZ:
+ printf("%s: REG_MULTI_SZ: ", v);
+ for (i=0; r.string_array[i] != NULL; i++) {
+ printf("%s ", r.string_array[i]);
+ }
+ printf("\n");
+ break;
+ default:
+ printf("%s: unknown type 0x%02x:\n", v, type);
+ break;
+ }
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getprinterdata(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ WERROR result;
+ fstring printername;
+ const char *valuename;
+ enum winreg_Type type;
+ uint8_t *data;
+ uint32_t needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 3) {
+ printf("Usage: %s <printername> <valuename>\n", argv[0]);
+ printf("<printername> of . queries print server\n");
+ return WERR_OK;
+ }
+ valuename = argv[2];
+
+ /* Open a printer handle */
+
+ if (strncmp(argv[1], ".", sizeof(".")) == 0)
+ fstrcpy(printername, cli->srv_name_slash);
+ else
+ slprintf(printername, sizeof(printername)-1, "%s\\%s",
+ cli->srv_name_slash, argv[1]);
+
+ /* get a printer handle */
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Get printer info */
+
+ result = rpccli_spoolss_getprinterdata(cli, mem_ctx,
+ &pol,
+ valuename,
+ 0,
+ &type,
+ &needed,
+ &data);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Display printer data */
+
+ display_printer_data(valuename, type, data, needed);
+
+ done:
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getprinterdataex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ WERROR result;
+ NTSTATUS status;
+ fstring printername;
+ const char *valuename, *keyname;
+
+ enum winreg_Type type;
+ uint8_t *data = NULL;
+ uint32_t offered = 0;
+ uint32_t needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 4) {
+ printf("Usage: %s <printername> <keyname> <valuename>\n",
+ argv[0]);
+ printf("<printername> of . queries print server\n");
+ return WERR_OK;
+ }
+ valuename = argv[3];
+ keyname = argv[2];
+
+ /* Open a printer handle */
+
+ if (strncmp(argv[1], ".", sizeof(".")) == 0)
+ fstrcpy(printername, cli->srv_name_slash);
+ else
+ slprintf(printername, sizeof(printername)-1, "%s\\%s",
+ cli->srv_name_slash, argv[1]);
+
+ /* get a printer handle */
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Get printer info */
+
+ data = talloc_zero_array(mem_ctx, uint8_t, offered);
+ if (!data) {
+ goto done;
+ }
+
+ status = dcerpc_spoolss_GetPrinterDataEx(b, mem_ctx,
+ &pol,
+ keyname,
+ valuename,
+ &type,
+ data,
+ offered,
+ &needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (W_ERROR_EQUAL(result, WERR_MORE_DATA)) {
+ offered = needed;
+ data = talloc_zero_array(mem_ctx, uint8_t, offered);
+ if (!data) {
+ goto done;
+ }
+ status = dcerpc_spoolss_GetPrinterDataEx(b, mem_ctx,
+ &pol,
+ keyname,
+ valuename,
+ &type,
+ data,
+ offered,
+ &needed,
+ &result);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Display printer data */
+
+ display_printer_data(valuename, type, data, needed);
+
+
+ done:
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_driver1(struct spoolss_DriverInfo1 *r)
+{
+ if (!r) {
+ return;
+ }
+
+ printf("Printer Driver Info 1:\n");
+ printf("\tDriver Name: [%s]\n", r->driver_name);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_driver2(struct spoolss_DriverInfo2 *r)
+{
+ if (!r) {
+ return;
+ }
+
+ printf("Printer Driver Info 2:\n");
+ printf("\tVersion: [%x]\n", r->version);
+ printf("\tDriver Name: [%s]\n", r->driver_name);
+ printf("\tArchitecture: [%s]\n", r->architecture);
+ printf("\tDriver Path: [%s]\n", r->driver_path);
+ printf("\tDatafile: [%s]\n", r->data_file);
+ printf("\tConfigfile: [%s]\n", r->config_file);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_driver3(struct spoolss_DriverInfo3 *r)
+{
+ int i;
+
+ if (!r) {
+ return;
+ }
+
+ printf("Printer Driver Info 3:\n");
+ printf("\tVersion: [%x]\n", r->version);
+ printf("\tDriver Name: [%s]\n", r->driver_name);
+ printf("\tArchitecture: [%s]\n", r->architecture);
+ printf("\tDriver Path: [%s]\n", r->driver_path);
+ printf("\tDatafile: [%s]\n", r->data_file);
+ printf("\tConfigfile: [%s]\n", r->config_file);
+ printf("\tHelpfile: [%s]\n", r->help_file);
+
+ for (i=0; r->dependent_files && r->dependent_files[i] != NULL; i++) {
+ printf("\tDependentfiles: [%s]\n", r->dependent_files[i]);
+ }
+
+ printf("\tMonitorname: [%s]\n", r->monitor_name);
+ printf("\tDefaultdatatype: [%s]\n", r->default_datatype);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_driver4(struct spoolss_DriverInfo4 *r)
+{
+ int i;
+
+ if (!r) {
+ return;
+ }
+
+ printf("Printer Driver Info 4:\n");
+ printf("\tVersion: [%x]\n", r->version);
+ printf("\tDriver Name: [%s]\n", r->driver_name);
+ printf("\tArchitecture: [%s]\n", r->architecture);
+ printf("\tDriver Path: [%s]\n", r->driver_path);
+ printf("\tDatafile: [%s]\n", r->data_file);
+ printf("\tConfigfile: [%s]\n", r->config_file);
+ printf("\tHelpfile: [%s]\n", r->help_file);
+
+ for (i=0; r->dependent_files && r->dependent_files[i] != NULL; i++) {
+ printf("\tDependentfiles: [%s]\n", r->dependent_files[i]);
+ }
+
+ printf("\tMonitorname: [%s]\n", r->monitor_name);
+ printf("\tDefaultdatatype: [%s]\n", r->default_datatype);
+
+ for (i=0; r->previous_names && r->previous_names[i] != NULL; i++) {
+ printf("\tPrevious Names: [%s]\n", r->previous_names[i]);
+ }
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_driver5(struct spoolss_DriverInfo5 *r)
+{
+ if (!r) {
+ return;
+ }
+
+ printf("Printer Driver Info 5:\n");
+ printf("\tVersion: [%x]\n", r->version);
+ printf("\tDriver Name: [%s]\n", r->driver_name);
+ printf("\tArchitecture: [%s]\n", r->architecture);
+ printf("\tDriver Path: [%s]\n", r->driver_path);
+ printf("\tDatafile: [%s]\n", r->data_file);
+ printf("\tConfigfile: [%s]\n", r->config_file);
+ printf("\tDriver Attributes: [0x%x]\n", r->driver_attributes);
+ printf("\tConfig Version: [0x%x]\n", r->config_version);
+ printf("\tDriver Version: [0x%x]\n", r->driver_version);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_driver6(struct spoolss_DriverInfo6 *r)
+{
+ int i;
+
+ if (!r) {
+ return;
+ }
+
+ printf("Printer Driver Info 6:\n");
+ printf("\tVersion: [%x]\n", r->version);
+ printf("\tDriver Name: [%s]\n", r->driver_name);
+ printf("\tArchitecture: [%s]\n", r->architecture);
+ printf("\tDriver Path: [%s]\n", r->driver_path);
+ printf("\tDatafile: [%s]\n", r->data_file);
+ printf("\tConfigfile: [%s]\n", r->config_file);
+ printf("\tHelpfile: [%s]\n", r->help_file);
+
+ for (i=0; r->dependent_files && r->dependent_files[i] != NULL; i++) {
+ printf("\tDependentfiles: [%s]\n", r->dependent_files[i]);
+ }
+
+ printf("\tMonitorname: [%s]\n", r->monitor_name);
+ printf("\tDefaultdatatype: [%s]\n", r->default_datatype);
+
+ for (i=0; r->previous_names && r->previous_names[i] != NULL; i++) {
+ printf("\tPrevious Names: [%s]\n", r->previous_names[i]);
+ }
+
+ printf("\tDriver Date: [%s]\n", nt_time_string(talloc_tos(), r->driver_date));
+ printf("\tDriver Version: [0x%016llx]\n", (long long unsigned int)r->driver_version);
+ printf("\tManufacturer Name: [%s]\n", r->manufacturer_name);
+ printf("\tManufacturer Url: [%s]\n", r->manufacturer_url);
+ printf("\tHardware ID: [%s]\n", r->hardware_id);
+ printf("\tProvider: [%s]\n", r->provider);
+
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_print_driver8(struct spoolss_DriverInfo8 *r)
+{
+ int i;
+
+ if (!r) {
+ return;
+ }
+
+ printf("Printer Driver Info 8:\n");
+ printf("\tVersion: [%x]\n", r->version);
+ printf("\tDriver Name: [%s]\n", r->driver_name);
+ printf("\tArchitecture: [%s]\n", r->architecture);
+ printf("\tDriver Path: [%s]\n", r->driver_path);
+ printf("\tDatafile: [%s]\n", r->data_file);
+ printf("\tConfigfile: [%s]\n", r->config_file);
+ printf("\tHelpfile: [%s]\n", r->help_file);
+ printf("\tMonitorname: [%s]\n", r->monitor_name);
+ printf("\tDefaultdatatype: [%s]\n", r->default_datatype);
+
+ for (i=0; r->dependent_files && r->dependent_files[i] != NULL; i++) {
+ printf("\tDependentfiles: [%s]\n", r->dependent_files[i]);
+ }
+
+ for (i=0; r->previous_names && r->previous_names[i] != NULL; i++) {
+ printf("\tPrevious Names: [%s]\n", r->previous_names[i]);
+ }
+
+ printf("\tDriver Date: [%s]\n", nt_time_string(talloc_tos(), r->driver_date));
+ printf("\tDriver Version: [0x%016llx]\n", (long long unsigned int)r->driver_version);
+ printf("\tManufacturer Name: [%s]\n", r->manufacturer_name);
+ printf("\tManufacturer Url: [%s]\n", r->manufacturer_url);
+ printf("\tHardware ID: [%s]\n", r->hardware_id);
+ printf("\tProvider: [%s]\n", r->provider);
+ printf("\tPrint Processor: [%s]\n", r->print_processor);
+ printf("\tVendor Setup: [%s]\n", r->vendor_setup);
+ for (i=0; r->color_profiles && r->color_profiles[i] != NULL; i++) {
+ printf("\tColor Profiles: [%s]\n", r->color_profiles[i]);
+ }
+ printf("\tInf Path: [%s]\n", r->inf_path);
+ printf("\tPrinter Driver Attributes: [0x%x]\n", r->printer_driver_attributes);
+ for (i=0; r->core_driver_dependencies && r->core_driver_dependencies[i] != NULL; i++) {
+ printf("\tCore Driver Dependencies: [%s]\n", r->core_driver_dependencies[i]);
+ }
+ printf("\tMin Driver Inbox Driver Version Date: [%s]\n", nt_time_string(talloc_tos(), r->min_inbox_driver_ver_date));
+ printf("\tMin Driver Inbox Driver Version Version: [0x%016llx]\n",
+ (long long unsigned int)r->min_inbox_driver_ver_version);
+
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getdriver(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ WERROR werror;
+ uint32_t level = 3;
+ const char *printername;
+ uint32_t i;
+ bool success = false;
+ union spoolss_DriverInfo info;
+ uint32_t server_major_version;
+ uint32_t server_minor_version;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if ((argc == 1) || (argc > 3)) {
+ printf("Usage: %s <printername> [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* get the arguments need to open the printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ if (argc == 3) {
+ level = atoi(argv[2]);
+ }
+
+ /* Open a printer handle */
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ PRINTER_ACCESS_USE,
+ &pol);
+ if (!W_ERROR_IS_OK(werror)) {
+ printf("Error opening printer handle for %s!\n", printername);
+ return werror;
+ }
+
+ /* loop through and print driver info level for each architecture */
+
+ for (i=0; archi_table[i].long_archi!=NULL; i++) {
+
+ werror = rpccli_spoolss_getprinterdriver2(cli, mem_ctx,
+ &pol,
+ archi_table[i].long_archi,
+ level,
+ 0, /* offered */
+ archi_table[i].version,
+ 2,
+ &info,
+ &server_major_version,
+ &server_minor_version);
+ if (!W_ERROR_IS_OK(werror)) {
+ continue;
+ }
+
+ /* need at least one success */
+
+ success = true;
+
+ printf("\n[%s]\n", archi_table[i].long_archi);
+
+ switch (level) {
+ case 1:
+ display_print_driver1(&info.info1);
+ break;
+ case 2:
+ display_print_driver2(&info.info2);
+ break;
+ case 3:
+ display_print_driver3(&info.info3);
+ break;
+ case 4:
+ display_print_driver4(&info.info4);
+ break;
+ case 5:
+ display_print_driver5(&info.info5);
+ break;
+ case 6:
+ display_print_driver6(&info.info6);
+ break;
+ case 8:
+ display_print_driver8(&info.info8);
+ break;
+ default:
+ printf("unknown info level %d\n", level);
+ break;
+ }
+ }
+
+ /* Cleanup */
+
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ if (success) {
+ werror = WERR_OK;
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR enum_driver_by_architecture(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *architecture,
+ uint32_t level)
+{
+ WERROR werror;
+ uint32_t count = 0;
+ union spoolss_DriverInfo *info = NULL;
+ uint32_t j;
+
+ werror = rpccli_spoolss_enumprinterdrivers(cli, mem_ctx,
+ cli->srv_name_slash,
+ architecture,
+ level,
+ 0,
+ &count,
+ &info);
+
+ if (W_ERROR_EQUAL(werror, WERR_INVALID_ENVIRONMENT)) {
+ printf("Server does not support environment [%s]\n",
+ architecture);
+ return WERR_OK;
+ }
+
+ if (count == 0) {
+ return WERR_OK;
+ }
+
+ if (!W_ERROR_IS_OK(werror)) {
+ printf("Error getting driver for environment [%s] - %s\n",
+ architecture, win_errstr(werror));
+ return werror;
+ }
+
+ printf("\n[%s]\n", architecture);
+
+ switch (level) {
+ case 1:
+ for (j=0; j < count; j++) {
+ display_print_driver1(&info[j].info1);
+ }
+ break;
+ case 2:
+ for (j=0; j < count; j++) {
+ display_print_driver2(&info[j].info2);
+ }
+ break;
+ case 3:
+ for (j=0; j < count; j++) {
+ display_print_driver3(&info[j].info3);
+ }
+ break;
+ case 4:
+ for (j=0; j < count; j++) {
+ display_print_driver4(&info[j].info4);
+ }
+ break;
+ case 5:
+ for (j=0; j < count; j++) {
+ display_print_driver5(&info[j].info5);
+ }
+ break;
+ case 6:
+ for (j=0; j < count; j++) {
+ display_print_driver6(&info[j].info6);
+ }
+ break;
+ case 8:
+ for (j=0; j < count; j++) {
+ display_print_driver8(&info[j].info8);
+ }
+ break;
+ default:
+ printf("unknown info level %d\n", level);
+ return WERR_INVALID_LEVEL;
+ }
+
+ return werror;
+}
+
+static WERROR cmd_spoolss_enum_drivers(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR werror = WERR_OK;
+ uint32_t level = 1;
+ uint32_t i;
+ const char *architecture = NULL;
+
+ if (argc > 3) {
+ printf("Usage: enumdrivers [level] [architecture]\n");
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ level = atoi(argv[1]);
+ }
+
+ if (argc == 3) {
+ architecture = argv[2];
+ }
+
+ if (architecture) {
+ return enum_driver_by_architecture(cli, mem_ctx,
+ architecture,
+ level);
+ }
+
+ /* loop through and print driver info level for each architecture */
+ for (i=0; archi_table[i].long_archi!=NULL; i++) {
+ /* check to see if we already asked for this architecture string */
+
+ if (i>0 && strequal(archi_table[i].long_archi, archi_table[i-1].long_archi)) {
+ continue;
+ }
+
+ werror = enum_driver_by_architecture(cli, mem_ctx,
+ archi_table[i].long_archi,
+ level);
+ if (!W_ERROR_IS_OK(werror)) {
+ break;
+ }
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_printdriverdir_1(struct spoolss_DriverDirectoryInfo1 *r)
+{
+ printf("\tDirectory Name:[%s]\n", r->directory_name);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getdriverdir(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ const char *env = SPOOLSS_ARCHITECTURE_NT_X86;
+ DATA_BLOB buffer;
+ uint32_t offered;
+ union spoolss_DriverDirectoryInfo info;
+ uint32_t needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 2) {
+ printf("Usage: %s [environment]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Get the arguments need to open the printer handle */
+
+ if (argc == 2) {
+ env = argv[1];
+ }
+
+ /* Get the directory. Only use Info level 1 */
+
+ status = dcerpc_spoolss_GetPrinterDriverDirectory(b, mem_ctx,
+ cli->srv_name_slash,
+ env,
+ 1,
+ NULL, /* buffer */
+ 0, /* offered */
+ NULL, /* info */
+ &needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (W_ERROR_EQUAL(result, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+
+ status = dcerpc_spoolss_GetPrinterDriverDirectory(b, mem_ctx,
+ cli->srv_name_slash,
+ env,
+ 1,
+ &buffer,
+ offered,
+ &info,
+ &needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ }
+
+ if (W_ERROR_IS_OK(result)) {
+ display_printdriverdir_1(&info.info1);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getdriverpackagepath(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ HRESULT hresult;
+ NTSTATUS status;
+ const char *env = SPOOLSS_ARCHITECTURE_NT_X86;
+ uint32_t offered;
+ uint32_t needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ const char *package_id = "";
+ const char *cab = NULL;
+
+ if (argc > 4) {
+ printf("Usage: %s [environment] [package_id]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Get the arguments need to open the printer handle */
+
+ if (argc >= 2) {
+ env = argv[1];
+ }
+
+ if (argc == 3) {
+ package_id = argv[2];
+ }
+
+ offered = 1;
+ cab = talloc_zero_array(mem_ctx, char, offered);
+ if (cab == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ status = dcerpc_spoolss_GetPrinterDriverPackagePath(b, mem_ctx,
+ cli->srv_name_slash,
+ env,
+ NULL,
+ package_id,
+ cab,
+ offered,
+ &needed,
+ &hresult);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(W_ERROR(WIN32_FROM_HRESULT(hresult)), WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ cab = talloc_zero_array(mem_ctx, char, offered);
+ if (cab == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ status = dcerpc_spoolss_GetPrinterDriverPackagePath(b, mem_ctx,
+ cli->srv_name_slash,
+ env,
+ NULL,
+ package_id,
+ cab,
+ offered,
+ &needed,
+ &hresult);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ }
+
+ return W_ERROR(WIN32_FROM_HRESULT(hresult));
+}
+
+
+/****************************************************************************
+****************************************************************************/
+
+static void set_drv_info_3_env(TALLOC_CTX *mem_ctx,
+ struct spoolss_AddDriverInfo3 *info,
+ const char *arch)
+{
+
+ int i;
+
+ for (i=0; archi_table[i].long_archi != NULL; i++)
+ {
+ if (strcmp(arch, archi_table[i].short_archi) == 0)
+ {
+ info->version = archi_table[i].version;
+ info->architecture = talloc_strdup(mem_ctx, archi_table[i].long_archi);
+ break;
+ }
+ }
+
+ if (archi_table[i].long_archi == NULL)
+ {
+ DEBUG(0, ("set_drv_info_3_env: Unknown arch [%s]\n", arch));
+ }
+
+ return;
+}
+
+
+/**************************************************************************
+ wrapper for strtok to get the next parameter from a delimited list.
+ Needed to handle the empty parameter string denoted by "NULL"
+ *************************************************************************/
+
+static char *get_driver_3_param(TALLOC_CTX *mem_ctx, char *str,
+ const char *delim, const char **dest,
+ char **saveptr)
+{
+ char *ptr;
+
+ /* get the next token */
+ ptr = strtok_r(str, delim, saveptr);
+
+ /* a string of 'NULL' is used to represent an empty
+ parameter because two consecutive delimiters
+ will not return an empty string. See man strtok(3)
+ for details */
+ if (ptr && (strcasecmp_m(ptr, "NULL") == 0)) {
+ ptr = NULL;
+ }
+
+ if (dest != NULL) {
+ *dest = talloc_strdup(mem_ctx, ptr);
+ }
+
+ return ptr;
+}
+
+/********************************************************************************
+ fill in the members of a spoolss_AddDriverInfo3 struct using a character
+ string in the form of
+ <Long Driver Name>:<Driver File Name>:<Data File Name>:\
+ <Config File Name>:<Help File Name>:<Language Monitor Name>:\
+ <Default Data Type>:<Comma Separated list of Files>
+ *******************************************************************************/
+
+static bool init_drv_info_3_members(TALLOC_CTX *mem_ctx, struct spoolss_AddDriverInfo3 *r,
+ char *args)
+{
+ char *str, *str2;
+ size_t count = 0;
+ char *saveptr = NULL;
+ struct spoolss_StringArray *deps;
+ const char **file_array = NULL;
+ int i;
+
+ /* fill in the UNISTR fields */
+ str = get_driver_3_param(mem_ctx, args, ":", &r->driver_name, &saveptr);
+ str = get_driver_3_param(mem_ctx, NULL, ":", &r->driver_path, &saveptr);
+ str = get_driver_3_param(mem_ctx, NULL, ":", &r->data_file, &saveptr);
+ str = get_driver_3_param(mem_ctx, NULL, ":", &r->config_file, &saveptr);
+ str = get_driver_3_param(mem_ctx, NULL, ":", &r->help_file, &saveptr);
+ str = get_driver_3_param(mem_ctx, NULL, ":", &r->monitor_name, &saveptr);
+ str = get_driver_3_param(mem_ctx, NULL, ":", &r->default_datatype, &saveptr);
+
+ /* <Comma Separated List of Dependent Files> */
+ /* save the beginning of the string */
+ str2 = get_driver_3_param(mem_ctx, NULL, ":", NULL, &saveptr);
+ str = str2;
+
+ /* begin to strip out each filename */
+ str = strtok_r(str, ",", &saveptr);
+
+ /* no dependent files, we are done */
+ if (!str) {
+ return true;
+ }
+
+ deps = talloc_zero(mem_ctx, struct spoolss_StringArray);
+ if (!deps) {
+ return false;
+ }
+
+ while (str != NULL) {
+ bool ok;
+ ok = add_string_to_array(deps, str, &file_array, &count);
+ if (!ok) {
+ return false;
+ }
+ str = strtok_r(NULL, ",", &saveptr);
+ }
+
+ deps->string = talloc_zero_array(deps, const char *, count + 1);
+ if (!deps->string) {
+ return false;
+ }
+
+ for (i=0; i < count; i++) {
+ deps->string[i] = file_array[i];
+ }
+
+ r->dependent_files = deps;
+
+ return true;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_addprinterdriver(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ uint32_t level = 3;
+ struct spoolss_AddDriverInfoCtr info_ctr;
+ struct spoolss_AddDriverInfo3 info3;
+ const char *arch;
+ char *driver_args;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* parse the command arguments */
+ if (argc != 3 && argc != 4)
+ {
+ printf ("Usage: %s <Environment> \\\n", argv[0]);
+ printf ("\t<Long Driver Name>:<Driver File Name>:<Data File Name>:\\\n");
+ printf ("\t<Config File Name>:<Help File Name>:<Language Monitor Name>:\\\n");
+ printf ("\t<Default Data Type>:<Comma Separated list of Files> \\\n");
+ printf ("\t[version]\n");
+
+ return WERR_OK;
+ }
+
+ /* Fill in the spoolss_AddDriverInfo3 struct */
+ ZERO_STRUCT(info3);
+
+ arch = cmd_spoolss_get_short_archi(argv[1]);
+ if (!arch) {
+ printf ("Error Unknown architecture [%s]\n", argv[1]);
+ return WERR_INVALID_PARAMETER;
+ }
+
+ set_drv_info_3_env(mem_ctx, &info3, arch);
+
+ driver_args = talloc_strdup( mem_ctx, argv[2] );
+ if (!init_drv_info_3_members(mem_ctx, &info3, driver_args ))
+ {
+ printf ("Error Invalid parameter list - %s.\n", argv[2]);
+ return WERR_INVALID_PARAMETER;
+ }
+
+ /* if printer driver version specified, override the default version
+ * used by the architecture. This allows installation of Windows
+ * 2000 (version 3) printer drivers. */
+ if (argc == 4)
+ {
+ info3.version = atoi(argv[3]);
+ }
+
+
+ info_ctr.level = level;
+ info_ctr.info.info3 = &info3;
+
+ status = dcerpc_spoolss_AddPrinterDriver(b, mem_ctx,
+ cli->srv_name_slash,
+ &info_ctr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (W_ERROR_IS_OK(result)) {
+ printf ("Printer Driver %s successfully installed.\n",
+ info3.driver_name);
+ }
+
+ return result;
+}
+
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_addprinterex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_SetPrinterInfo2 info2;
+
+ /* parse the command arguments */
+ if (argc != 5)
+ {
+ printf ("Usage: %s <name> <shared name> <driver> <port>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Fill in the DRIVER_INFO_2 struct */
+ ZERO_STRUCT(info2);
+
+ info2.printername = argv[1];
+ info2.drivername = argv[3];
+ info2.sharename = argv[2];
+ info2.portname = argv[4];
+ info2.comment = "Created by rpcclient";
+ info2.printprocessor = "winprint";
+ info2.datatype = "RAW";
+ info2.devmode_ptr = 0;
+ info2.secdesc_ptr = 0;
+ info2.attributes = PRINTER_ATTRIBUTE_SHARED;
+ info2.priority = 0;
+ info2.defaultpriority = 0;
+ info2.starttime = 0;
+ info2.untiltime = 0;
+
+ /* These three fields must not be used by AddPrinter()
+ as defined in the MS Platform SDK documentation..
+ --jerry
+ info2.status = 0;
+ info2.cjobs = 0;
+ info2.averageppm = 0;
+ */
+
+ info_ctr.level = 2;
+ info_ctr.info.info2 = &info2;
+
+ result = rpccli_spoolss_addprinterex(cli, mem_ctx,
+ &info_ctr);
+ if (W_ERROR_IS_OK(result))
+ printf ("Printer %s successfully installed.\n", argv[1]);
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_setdriver(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle pol;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t level = 2;
+ const char *printername;
+ union spoolss_PrinterInfo info;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_SetPrinterInfo2 info2;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ /* parse the command arguments */
+ if (argc != 3)
+ {
+ printf ("Usage: %s <printer> <driver>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ /* Get a printer handle */
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ PRINTER_ALL_ACCESS,
+ &pol);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Get printer info */
+
+ result = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &pol,
+ level,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ printf ("Unable to retrieve printer information!\n");
+ goto done;
+ }
+
+ /* Set the printer driver */
+
+ spoolss_printerinfo2_to_setprinterinfo2(&info.info2, &info2);
+ info2.drivername = argv[2];
+
+ info_ctr.level = 2;
+ info_ctr.info.info2 = &info2;
+
+ status = dcerpc_spoolss_SetPrinter(b, mem_ctx,
+ &pol,
+ &info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ 0, /* command */
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf("SetPrinter call failed!\n");
+ goto done;
+ }
+
+ printf("Successfully set %s to driver %s.\n", argv[1], argv[2]);
+
+done:
+ /* Cleanup */
+
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ return result;
+}
+
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_deletedriverex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result, ret = WERR_UNKNOWN_PRINTER_DRIVER;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ int i;
+ int vers = -1;
+
+ const char *arch = NULL;
+ uint32_t delete_flags = 0;
+
+ /* parse the command arguments */
+ if (argc < 2 || argc > 5) {
+ printf("Usage: %s <driver> [arch] [version] [flags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 3)
+ arch = argv[2];
+ if (argc >= 4) {
+ vers = atoi(argv[3]);
+ delete_flags |= DPD_DELETE_SPECIFIC_VERSION;
+ }
+ if (argc == 5)
+ delete_flags = atoi(argv[4]);
+
+ /* delete the driver for all architectures */
+ for (i=0; archi_table[i].long_archi; i++) {
+
+ if (arch && !strequal(archi_table[i].long_archi, arch))
+ continue;
+
+ if (vers >= 0 && archi_table[i].version != vers)
+ continue;
+
+ /* make the call to remove the driver */
+ status = dcerpc_spoolss_DeletePrinterDriverEx(b, mem_ctx,
+ cli->srv_name_slash,
+ archi_table[i].long_archi,
+ argv[1],
+ delete_flags,
+ archi_table[i].version,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if ( !W_ERROR_IS_OK(result) )
+ {
+ if ( !W_ERROR_EQUAL(result, WERR_UNKNOWN_PRINTER_DRIVER) ) {
+ printf ("Failed to remove driver %s for arch [%s] (version: %d): %s\n",
+ argv[1], archi_table[i].long_archi, archi_table[i].version, win_errstr(result));
+ }
+ }
+ else
+ {
+ printf ("Driver %s and files removed for arch [%s] (version: %d).\n", argv[1],
+ archi_table[i].long_archi, archi_table[i].version);
+ ret = WERR_OK;
+ }
+ }
+
+ return ret;
+}
+
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_deletedriver(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result = WERR_OK;
+ NTSTATUS status;
+ int i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* parse the command arguments */
+ if (argc != 2) {
+ printf ("Usage: %s <driver>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* delete the driver for all architectures */
+ for (i=0; archi_table[i].long_archi; i++) {
+ result = WERR_OK;
+
+ /* make the call to remove the driver */
+ status = dcerpc_spoolss_DeletePrinterDriver(b, mem_ctx,
+ cli->srv_name_slash,
+ archi_table[i].long_archi,
+ argv[1],
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ continue;
+ }
+ if ( !W_ERROR_IS_OK(result) ) {
+ if ( !W_ERROR_EQUAL(result, WERR_UNKNOWN_PRINTER_DRIVER) ) {
+ printf ("Failed to remove driver %s for arch [%s] - error %s!\n",
+ argv[1], archi_table[i].long_archi,
+ win_errstr(result));
+ }
+ } else {
+ printf ("Driver %s removed for arch [%s].\n", argv[1],
+ archi_table[i].long_archi);
+ }
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getprintprocdir(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ const char *environment = SPOOLSS_ARCHITECTURE_NT_X86;
+ DATA_BLOB buffer;
+ uint32_t offered;
+ union spoolss_PrintProcessorDirectoryInfo info = {};
+ uint32_t needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* parse the command arguments */
+ if (argc > 2) {
+ printf ("Usage: %s [environment]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc == 2) {
+ environment = argv[1];
+ }
+
+ status = dcerpc_spoolss_GetPrintProcessorDirectory(b, mem_ctx,
+ cli->srv_name_slash,
+ environment,
+ 1,
+ NULL, /* buffer */
+ 0, /* offered */
+ NULL, /* info */
+ &needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (W_ERROR_EQUAL(result, WERR_INSUFFICIENT_BUFFER)) {
+ offered = needed;
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+
+ status = dcerpc_spoolss_GetPrintProcessorDirectory(b, mem_ctx,
+ cli->srv_name_slash,
+ environment,
+ 1,
+ &buffer,
+ offered,
+ &info,
+ &needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ }
+
+ if (W_ERROR_IS_OK(result) && info.info1.directory_name != NULL) {
+ printf("%s\n", info.info1.directory_name);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_addform(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle handle;
+ WERROR werror;
+ NTSTATUS status;
+ const char *printername;
+ struct spoolss_AddFormInfoCtr info_ctr;
+ struct spoolss_AddFormInfo1 info1;
+ struct spoolss_AddFormInfo2 info2;
+ uint32_t level = 1;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* Parse the command arguments */
+
+ if (argc < 3 || argc > 5) {
+ printf ("Usage: %s <printer> <formname> [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Get a printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ PRINTER_ALL_ACCESS,
+ &handle);
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ /* Dummy up some values for the form data */
+
+ if (argc == 4) {
+ level = atoi(argv[3]);
+ }
+
+ switch (level) {
+ case 1:
+ info1.flags = SPOOLSS_FORM_USER;
+ info1.form_name = argv[2];
+ info1.size.width = 100;
+ info1.size.height = 100;
+ info1.area.left = 0;
+ info1.area.top = 10;
+ info1.area.right = 20;
+ info1.area.bottom = 30;
+
+ info_ctr.level = 1;
+ info_ctr.info.info1 = &info1;
+
+ break;
+ case 2:
+ info2.flags = SPOOLSS_FORM_USER;
+ info2.form_name = argv[2];
+ info2.size.width = 100;
+ info2.size.height = 100;
+ info2.area.left = 0;
+ info2.area.top = 10;
+ info2.area.right = 20;
+ info2.area.bottom = 30;
+ info2.keyword = argv[2];
+ info2.string_type = SPOOLSS_FORM_STRING_TYPE_NONE;
+ info2.mui_dll = NULL;
+ info2.ressource_id = 0;
+ info2.display_name = argv[2];
+ info2.lang_id = 0;
+
+ info_ctr.level = 2;
+ info_ctr.info.info2 = &info2;
+
+ break;
+ default:
+ werror = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* Add the form */
+
+ status = dcerpc_spoolss_AddForm(b, mem_ctx,
+ &handle,
+ &info_ctr,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ werror = ntstatus_to_werror(status);
+ goto done;
+ }
+ done:
+ if (is_valid_policy_hnd(&handle)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &_result);
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_setform(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle handle;
+ WERROR werror;
+ NTSTATUS status;
+ const char *printername;
+ struct spoolss_AddFormInfoCtr info_ctr;
+ struct spoolss_AddFormInfo1 info1;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* Parse the command arguments */
+
+ if (argc != 3) {
+ printf ("Usage: %s <printer> <formname>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Get a printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle);
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ /* Dummy up some values for the form data */
+
+ info1.flags = SPOOLSS_FORM_PRINTER;
+ info1.size.width = 100;
+ info1.size.height = 100;
+ info1.area.left = 0;
+ info1.area.top = 1000;
+ info1.area.right = 2000;
+ info1.area.bottom = 3000;
+ info1.form_name = argv[2];
+
+ info_ctr.info.info1 = &info1;
+ info_ctr.level = 1;
+
+ /* Set the form */
+
+ status = dcerpc_spoolss_SetForm(b, mem_ctx,
+ &handle,
+ argv[2],
+ &info_ctr,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ werror = ntstatus_to_werror(status);
+ goto done;
+ }
+ done:
+ if (is_valid_policy_hnd(&handle)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &_result);
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static const char *get_form_flag(int form_flag)
+{
+ switch (form_flag) {
+ case SPOOLSS_FORM_USER:
+ return "FORM_USER";
+ case SPOOLSS_FORM_BUILTIN:
+ return "FORM_BUILTIN";
+ case SPOOLSS_FORM_PRINTER:
+ return "FORM_PRINTER";
+ default:
+ return "unknown";
+ }
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_form_info1(struct spoolss_FormInfo1 *r)
+{
+ printf("%s\n" \
+ "\tflag: %s (%d)\n" \
+ "\twidth: %d, length: %d\n" \
+ "\tleft: %d, right: %d, top: %d, bottom: %d\n\n",
+ r->form_name, get_form_flag(r->flags), r->flags,
+ r->size.width, r->size.height,
+ r->area.left, r->area.right,
+ r->area.top, r->area.bottom);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_form_info2(struct spoolss_FormInfo2 *r)
+{
+ printf("%s\n" \
+ "\tflag: %s (%d)\n" \
+ "\twidth: %d, length: %d\n" \
+ "\tleft: %d, right: %d, top: %d, bottom: %d\n",
+ r->form_name, get_form_flag(r->flags), r->flags,
+ r->size.width, r->size.height,
+ r->area.left, r->area.right,
+ r->area.top, r->area.bottom);
+ printf("\tkeyword: %s\n", r->keyword);
+ printf("\tstring_type: 0x%08x\n", r->string_type);
+ printf("\tmui_dll: %s\n", r->mui_dll);
+ printf("\tressource_id: 0x%08x\n", r->ressource_id);
+ printf("\tdisplay_name: %s\n", r->display_name);
+ printf("\tlang_id: %d\n", r->lang_id);
+ printf("\n");
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_getform(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct policy_handle handle;
+ WERROR werror;
+ NTSTATUS status;
+ const char *printername;
+ DATA_BLOB buffer;
+ uint32_t offered = 0;
+ union spoolss_FormInfo info;
+ uint32_t needed;
+ uint32_t level = 1;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* Parse the command arguments */
+
+ if (argc < 3 || argc > 5) {
+ printf ("Usage: %s <printer> <formname> [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Get a printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle);
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ if (argc == 4) {
+ level = atoi(argv[3]);
+ }
+
+ /* Get the form */
+
+ status = dcerpc_spoolss_GetForm(b, mem_ctx,
+ &handle,
+ argv[2],
+ level,
+ NULL,
+ offered,
+ &info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ werror = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (W_ERROR_EQUAL(werror, WERR_INSUFFICIENT_BUFFER)) {
+ buffer = data_blob_talloc_zero(mem_ctx, needed);
+ offered = needed;
+ status = dcerpc_spoolss_GetForm(b, mem_ctx,
+ &handle,
+ argv[2],
+ level,
+ &buffer,
+ offered,
+ &info,
+ &needed,
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ werror = ntstatus_to_werror(status);
+ goto done;
+ }
+ }
+
+ if (!W_ERROR_IS_OK(werror)) {
+ goto done;
+ }
+
+ switch (level) {
+ case 1:
+ display_form_info1(&info.info1);
+ break;
+ case 2:
+ display_form_info2(&info.info2);
+ break;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&handle)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &_result);
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_deleteform(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct policy_handle handle;
+ WERROR werror;
+ NTSTATUS status;
+ const char *printername;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* Parse the command arguments */
+
+ if (argc != 3) {
+ printf ("Usage: %s <printer> <formname>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Get a printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle);
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ /* Delete the form */
+
+ status = dcerpc_spoolss_DeleteForm(b, mem_ctx,
+ &handle,
+ argv[2],
+ &werror);
+ if (!NT_STATUS_IS_OK(status)) {
+ werror = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&handle)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &_result);
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_enum_forms(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ struct policy_handle handle;
+ WERROR werror;
+ const char *printername;
+ uint32_t num_forms, level = 1, i;
+ union spoolss_FormInfo *forms;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ /* Parse the command arguments */
+
+ if (argc < 2 || argc > 4) {
+ printf ("Usage: %s <printer> [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Get a printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle);
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ if (argc == 3) {
+ level = atoi(argv[2]);
+ }
+
+ /* Enumerate forms */
+
+ werror = rpccli_spoolss_enumforms(cli, mem_ctx,
+ &handle,
+ level,
+ 0,
+ &num_forms,
+ &forms);
+
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ /* Display output */
+
+ for (i = 0; i < num_forms; i++) {
+ switch (level) {
+ case 1:
+ display_form_info1(&forms[i].info1);
+ break;
+ case 2:
+ display_form_info2(&forms[i].info2);
+ break;
+ }
+ }
+
+ done:
+ if (is_valid_policy_hnd(&handle)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &_result);
+ }
+
+ return werror;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_setprinterdata(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ const char *printername;
+ struct policy_handle pol = { 0, };
+ union spoolss_PrinterInfo info;
+ enum winreg_Type type;
+ union spoolss_PrinterData data;
+ DATA_BLOB blob;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ int error = 0;
+
+ /* parse the command arguments */
+ if (argc < 5) {
+ printf ("Usage: %s <printer> <string|binary|dword|multistring>"
+ " <value> <data>\n",
+ argv[0]);
+ return WERR_OK;
+ }
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ type = REG_NONE;
+
+ if (strequal(argv[2], "string")) {
+ type = REG_SZ;
+ }
+
+ if (strequal(argv[2], "binary")) {
+ type = REG_BINARY;
+ }
+
+ if (strequal(argv[2], "dword")) {
+ type = REG_DWORD;
+ }
+
+ if (strequal(argv[2], "multistring")) {
+ type = REG_MULTI_SZ;
+ }
+
+ if (type == REG_NONE) {
+ printf("Unknown data type: %s\n", argv[2]);
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* get a printer handle */
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ result = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &pol,
+ 0,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ printf("%s\n", current_timestring(mem_ctx, true));
+ printf("\tchange_id (before set)\t:[0x%x]\n", info.info0.change_id);
+
+ /* Set the printer data */
+
+ switch (type) {
+ case REG_SZ:
+ data.string = talloc_strdup(mem_ctx, argv[4]);
+ W_ERROR_HAVE_NO_MEMORY(data.string);
+ break;
+ case REG_DWORD:
+ data.value = smb_strtoul(argv[4],
+ NULL,
+ 10,
+ &error,
+ SMB_STR_STANDARD);
+ if (error != 0) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ break;
+ case REG_BINARY:
+ data.binary = strhex_to_data_blob(mem_ctx, argv[4]);
+ break;
+ case REG_MULTI_SZ: {
+ int i;
+ size_t num_strings;
+ const char **strings = NULL;
+
+ num_strings = 0;
+
+ for (i=4; i<argc; i++) {
+ if (strcmp(argv[i], "NULL") == 0) {
+ argv[i] = "";
+ }
+ if (!add_string_to_array(mem_ctx, argv[i],
+ &strings,
+ &num_strings)) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ }
+ data.string_array = talloc_zero_array(mem_ctx, const char *, num_strings + 1);
+ if (!data.string_array) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ for (i=0; i < num_strings; i++) {
+ data.string_array[i] = strings[i];
+ }
+ break;
+ }
+ default:
+ printf("Unknown data type: %s\n", argv[2]);
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ result = push_spoolss_PrinterData(mem_ctx, &blob, type, &data);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ status = dcerpc_spoolss_SetPrinterData(b, mem_ctx,
+ &pol,
+ argv[3], /* value_name */
+ type,
+ blob.data,
+ blob.length,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf ("Unable to set [%s=%s]!\n", argv[3], argv[4]);
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf ("Unable to set [%s=%s]!\n", argv[3], argv[4]);
+ goto done;
+ }
+ printf("\tSetPrinterData succeeded [%s: %s]\n", argv[3], argv[4]);
+
+ result = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &pol,
+ 0,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ printf("%s\n", current_timestring(mem_ctx, true));
+ printf("\tchange_id (after set)\t:[0x%x]\n", info.info0.change_id);
+
+done:
+ /* cleanup */
+ if (is_valid_policy_hnd(&pol)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &pol, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_job_info1(struct spoolss_JobInfo1 *r)
+{
+ printf("%d: jobid[%d]: %s %s %s %d/%d pages\n", r->position, r->job_id,
+ r->user_name, r->document_name, r->text_status, r->pages_printed,
+ r->total_pages);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_job_info2(struct spoolss_JobInfo2 *r)
+{
+ printf("%d: jobid[%d]: %s %s %s %d/%d pages, %d bytes\n",
+ r->position, r->job_id,
+ r->user_name, r->document_name, r->text_status, r->pages_printed,
+ r->total_pages, r->size);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_job_info3(struct spoolss_JobInfo3 *r)
+{
+ printf("jobid[%d], next_jobid[%d]\n",
+ r->job_id, r->next_job_id);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_job_info4(struct spoolss_JobInfo4 *r)
+{
+ printf("%d: jobid[%d]: %s %s %s %d/%d pages, %d/%d bytes\n",
+ r->position, r->job_id,
+ r->user_name, r->document_name, r->text_status, r->pages_printed,
+ r->total_pages, r->size, r->size_high);
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_enum_jobs(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ uint32_t level = 1, count, i;
+ const char *printername;
+ struct policy_handle hnd;
+ union spoolss_JobInfo *info;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2 || argc > 3) {
+ printf("Usage: %s printername [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc == 3) {
+ level = atoi(argv[2]);
+ }
+
+ /* Open printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hnd);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* Enumerate ports */
+
+ result = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &hnd,
+ 0, /* firstjob */
+ 1000, /* numjobs */
+ level,
+ 0,
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ for (i = 0; i < count; i++) {
+ switch (level) {
+ case 1:
+ display_job_info1(&info[i].info1);
+ break;
+ case 2:
+ display_job_info2(&info[i].info2);
+ break;
+ default:
+ d_printf("unknown info level %d\n", level);
+ break;
+ }
+ }
+
+done:
+ if (is_valid_policy_hnd(&hnd)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_get_job(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ const char *printername;
+ struct policy_handle hnd;
+ uint32_t job_id;
+ uint32_t level = 1;
+ union spoolss_JobInfo info;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3 || argc > 4) {
+ printf("Usage: %s printername job_id [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ job_id = atoi(argv[2]);
+
+ if (argc == 4) {
+ level = atoi(argv[3]);
+ }
+
+ /* Open printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Enumerate ports */
+
+ result = rpccli_spoolss_getjob(cli, mem_ctx,
+ &hnd,
+ job_id,
+ level,
+ 0,
+ &info);
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ switch (level) {
+ case 1:
+ display_job_info1(&info.info1);
+ break;
+ case 2:
+ display_job_info2(&info.info2);
+ break;
+ case 3:
+ display_job_info3(&info.info3);
+ break;
+ case 4:
+ display_job_info4(&info.info4);
+ break;
+ default:
+ d_printf("unknown info level %d\n", level);
+ break;
+ }
+
+done:
+ if (is_valid_policy_hnd(&hnd)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static struct {
+ const char *name;
+ enum spoolss_JobControl val;
+} cmdvals[] = {
+ {"PAUSE", SPOOLSS_JOB_CONTROL_PAUSE},
+ {"RESUME", SPOOLSS_JOB_CONTROL_RESUME},
+ {"CANCEL", SPOOLSS_JOB_CONTROL_CANCEL},
+ {"RESTART", SPOOLSS_JOB_CONTROL_RESTART},
+ {"DELETE", SPOOLSS_JOB_CONTROL_DELETE},
+ {"SEND_TO_PRINTER", SPOOLSS_JOB_CONTROL_SEND_TO_PRINTER},
+ {"EJECTED", SPOOLSS_JOB_CONTROL_LAST_PAGE_EJECTED},
+ {"RETAIN", SPOOLSS_JOB_CONTROL_RETAIN},
+ {"RELEASE", SPOOLSS_JOB_CONTROL_RELEASE}
+};
+
+static enum spoolss_JobControl parse_setjob_command(const char *cmd)
+{
+ int i;
+
+ for (i = 0; i < sizeof(cmdvals)/sizeof(cmdvals[0]); i++) {
+ if (strequal(cmdvals[i].name, cmd)) {
+ return cmdvals[i].val;
+ }
+ }
+ return (enum spoolss_JobControl)atoi(cmd);
+}
+
+static WERROR cmd_spoolss_set_job(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ const char *printername;
+ struct policy_handle hnd;
+ uint32_t job_id;
+ enum spoolss_JobControl command;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 4) {
+ printf("Usage: %s printername job_id command\n", argv[0]);
+ printf("command = [PAUSE|RESUME|CANCEL|RESTART|DELETE|"
+ "SEND_TO_PRINTER|EJECTED|RETAIN|RELEASE]\n");
+ return WERR_OK;
+ }
+
+ job_id = atoi(argv[2]);
+ command = parse_setjob_command(argv[3]);
+
+ /* Open printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Set Job */
+
+ status = dcerpc_spoolss_SetJob(b, mem_ctx,
+ &hnd,
+ job_id,
+ NULL,
+ command,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+done:
+ if (is_valid_policy_hnd(&hnd)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_enum_data(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ const char *printername;
+ struct policy_handle hnd;
+ uint32_t value_needed;
+ enum winreg_Type type;
+ uint32_t data_needed;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct spoolss_EnumPrinterData r;
+
+ if (argc != 2) {
+ printf("Usage: %s printername\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Open printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Enumerate data */
+
+ r.in.handle = &hnd;
+ r.in.enum_index = 0;
+ r.in.value_offered = 0;
+ r.in.data_offered = 0;
+ r.out.value_name = NULL;
+ r.out.value_needed = &value_needed;
+ r.out.type = &type;
+ r.out.data = NULL;
+ r.out.data_needed = &data_needed;
+
+ status = dcerpc_spoolss_EnumPrinterData_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(r.out.result)) {
+ result = r.out.result;
+ goto done;
+ }
+
+ r.in.data_offered = *r.out.data_needed;
+ r.in.value_offered = *r.out.value_needed;
+ r.out.data = talloc_zero_array(mem_ctx, uint8_t, r.in.data_offered);
+ r.out.value_name = talloc_zero_array(mem_ctx, char, r.in.value_offered);
+
+ do {
+
+ status = dcerpc_spoolss_EnumPrinterData_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (W_ERROR_EQUAL(r.out.result, WERR_NO_MORE_ITEMS)) {
+ result = WERR_OK;
+ break;
+ }
+
+ r.in.enum_index++;
+
+ display_reg_value(r.out.value_name, *r.out.type,
+ data_blob_const(r.out.data, r.in.data_offered));
+
+ } while (W_ERROR_IS_OK(r.out.result));
+
+done:
+ if (is_valid_policy_hnd(&hnd)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_enum_data_ex( struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ uint32_t i;
+ const char *printername;
+ struct policy_handle hnd;
+ uint32_t count;
+ struct spoolss_PrinterEnumValues *info;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 3) {
+ printf("Usage: %s printername <keyname>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* Open printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Enumerate subkeys */
+
+ result = rpccli_spoolss_enumprinterdataex(cli, mem_ctx,
+ &hnd,
+ argv[2],
+ 0,
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ for (i=0; i < count; i++) {
+ display_printer_data(info[i].value_name,
+ info[i].type,
+ info[i].data->data,
+ info[i].data->length);
+ }
+
+ done:
+ if (is_valid_policy_hnd(&hnd)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_enum_printerkey(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ const char *printername;
+ const char *keyname = NULL;
+ struct policy_handle hnd;
+ const char **key_buffer = NULL;
+ int i;
+ uint32_t offered = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2 || argc > 4) {
+ printf("Usage: %s printername [keyname] [offered]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 3) {
+ keyname = argv[2];
+ } else {
+ keyname = "";
+ }
+
+ if (argc == 4) {
+ offered = atoi(argv[3]);
+ }
+
+ /* Open printer handle */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Enumerate subkeys */
+
+ result = rpccli_spoolss_enumprinterkey(cli, mem_ctx,
+ &hnd,
+ keyname,
+ &key_buffer,
+ offered);
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ for (i=0; key_buffer && key_buffer[i]; i++) {
+ printf("%s\n", key_buffer[i]);
+ }
+
+ done:
+
+ if (is_valid_policy_hnd(&hnd)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_rffpcnex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ const char *printername;
+ const char *clientname;
+ struct policy_handle hnd = { 0, };
+ WERROR result;
+ NTSTATUS status;
+ struct spoolss_NotifyOption option;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc != 2) {
+ printf("Usage: %s printername\n", argv[0]);
+ result = WERR_OK;
+ goto done;
+ }
+
+ /* Open printer */
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hnd);
+ if (!W_ERROR_IS_OK(result)) {
+ printf("Error opening %s\n", argv[1]);
+ goto done;
+ }
+
+ /* Create spool options */
+
+ option.version = 2;
+ option.count = 2;
+
+ option.types = talloc_array(mem_ctx, struct spoolss_NotifyOptionType, 2);
+ if (option.types == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ option.types[0].type = PRINTER_NOTIFY_TYPE;
+ option.types[0].count = 1;
+ option.types[0].fields = talloc_array(mem_ctx, union spoolss_Field, 1);
+ if (option.types[0].fields == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ option.types[0].fields[0].field = PRINTER_NOTIFY_FIELD_SERVER_NAME;
+
+ option.types[1].type = JOB_NOTIFY_TYPE;
+ option.types[1].count = 1;
+ option.types[1].fields = talloc_array(mem_ctx, union spoolss_Field, 1);
+ if (option.types[1].fields == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ option.types[1].fields[0].field = JOB_NOTIFY_FIELD_PRINTER_NAME;
+
+ clientname = talloc_asprintf(mem_ctx, "\\\\%s", lp_netbios_name());
+ if (!clientname) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ /* Send rffpcnex */
+
+ status = dcerpc_spoolss_RemoteFindFirstPrinterChangeNotifyEx(b, mem_ctx,
+ &hnd,
+ 0,
+ 0,
+ clientname,
+ 123,
+ &option,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf("Error rffpcnex %s\n", argv[1]);
+ goto done;
+ }
+
+done:
+ if (is_valid_policy_hnd(&hnd)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &_result);
+ }
+
+ return result;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static bool compare_printer( struct rpc_pipe_client *cli1, struct policy_handle *hnd1,
+ struct rpc_pipe_client *cli2, struct policy_handle *hnd2 )
+{
+ union spoolss_PrinterInfo info1, info2;
+ WERROR werror;
+ TALLOC_CTX *mem_ctx = talloc_init("compare_printer");
+
+ printf("Retrieving printer propertiesfor %s...", cli1->desthost);
+ werror = rpccli_spoolss_getprinter(cli1, mem_ctx,
+ hnd1,
+ 2,
+ 0,
+ &info1);
+ if ( !W_ERROR_IS_OK(werror) ) {
+ printf("failed (%s)\n", win_errstr(werror));
+ talloc_destroy(mem_ctx);
+ return false;
+ }
+ printf("ok\n");
+
+ printf("Retrieving printer properties for %s...", cli2->desthost);
+ werror = rpccli_spoolss_getprinter(cli2, mem_ctx,
+ hnd2,
+ 2,
+ 0,
+ &info2);
+ if ( !W_ERROR_IS_OK(werror) ) {
+ printf("failed (%s)\n", win_errstr(werror));
+ talloc_destroy(mem_ctx);
+ return false;
+ }
+ printf("ok\n");
+
+ talloc_destroy(mem_ctx);
+
+ return true;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static bool compare_printer_secdesc( struct rpc_pipe_client *cli1, struct policy_handle *hnd1,
+ struct rpc_pipe_client *cli2, struct policy_handle *hnd2 )
+{
+ union spoolss_PrinterInfo info1, info2;
+ WERROR werror;
+ TALLOC_CTX *mem_ctx = talloc_init("compare_printer_secdesc");
+ struct security_descriptor *sd1, *sd2;
+ bool result = true;
+
+
+ printf("Retrieving printer security for %s...", cli1->desthost);
+ werror = rpccli_spoolss_getprinter(cli1, mem_ctx,
+ hnd1,
+ 3,
+ 0,
+ &info1);
+ if ( !W_ERROR_IS_OK(werror) ) {
+ printf("failed (%s)\n", win_errstr(werror));
+ result = false;
+ goto done;
+ }
+ printf("ok\n");
+
+ printf("Retrieving printer security for %s...", cli2->desthost);
+ werror = rpccli_spoolss_getprinter(cli2, mem_ctx,
+ hnd2,
+ 3,
+ 0,
+ &info2);
+ if ( !W_ERROR_IS_OK(werror) ) {
+ printf("failed (%s)\n", win_errstr(werror));
+ result = false;
+ goto done;
+ }
+ printf("ok\n");
+
+
+ printf("++ ");
+
+ sd1 = info1.info3.secdesc;
+ sd2 = info2.info3.secdesc;
+
+ if ( (sd1 != sd2) && ( !sd1 || !sd2 ) ) {
+ printf("NULL secdesc!\n");
+ result = false;
+ goto done;
+ }
+
+ if (!security_descriptor_equal( sd1, sd2 ) ) {
+ printf("Security Descriptors *not* equal!\n");
+ result = false;
+ goto done;
+ }
+
+ printf("Security descriptors match\n");
+
+done:
+ talloc_destroy(mem_ctx);
+ return result;
+}
+
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR cmd_spoolss_printercmp(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ const char *printername;
+ char *printername_path = NULL;
+ struct cli_state *cli_server2 = NULL;
+ struct rpc_pipe_client *cli2 = NULL;
+ struct policy_handle hPrinter1, hPrinter2;
+ NTSTATUS nt_status;
+ WERROR werror;
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ if ( argc != 3 ) {
+ printf("Usage: %s <printer> <server>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ printername = argv[1];
+
+ /* first get the connection to the remote server */
+
+ nt_status = cli_full_connection_creds(&cli_server2, lp_netbios_name(), argv[2],
+ NULL, 0,
+ "IPC$", "IPC",
+ creds,
+ CLI_FULL_CONNECTION_IPC);
+ if ( !NT_STATUS_IS_OK(nt_status) )
+ return WERR_GEN_FAILURE;
+
+ nt_status = cli_rpc_pipe_open_noauth(cli_server2, &ndr_table_spoolss,
+ &cli2);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf("failed to open spoolss pipe on server %s (%s)\n",
+ argv[2], nt_errstr(nt_status));
+ return WERR_GEN_FAILURE;
+ }
+
+ /* now open up both printers */
+
+ RPCCLIENT_PRINTERNAME(printername_path, cli, printername);
+
+ printf("Opening %s...", printername_path);
+
+ werror = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername_path,
+ PRINTER_ALL_ACCESS,
+ &hPrinter1);
+ if ( !W_ERROR_IS_OK(werror) ) {
+ printf("failed (%s)\n", win_errstr(werror));
+ goto done;
+ }
+ printf("ok\n");
+
+ RPCCLIENT_PRINTERNAME(printername_path, cli2, printername);
+
+ printf("Opening %s...", printername_path);
+ werror = rpccli_spoolss_openprinter_ex(cli2, mem_ctx,
+ printername_path,
+ PRINTER_ALL_ACCESS,
+ &hPrinter2);
+ if ( !W_ERROR_IS_OK(werror) ) {
+ printf("failed (%s)\n", win_errstr(werror));
+ goto done;
+ }
+ printf("ok\n");
+
+ compare_printer( cli, &hPrinter1, cli2, &hPrinter2 );
+ compare_printer_secdesc( cli, &hPrinter1, cli2, &hPrinter2 );
+#if 0
+ compare_printerdata( cli_server1, &hPrinter1, cli_server2, &hPrinter2 );
+#endif
+
+
+done:
+ /* cleanup */
+
+ printf("Closing printers...");
+ {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(cli->binding_handle, mem_ctx, &hPrinter1, &_result);
+ dcerpc_spoolss_ClosePrinter(cli2->binding_handle, mem_ctx, &hPrinter2, &_result);
+ }
+ printf("ok\n");
+
+ /* close the second remote connection */
+
+ cli_shutdown( cli_server2 );
+ return WERR_OK;
+}
+
+static void display_proc_info1(struct spoolss_PrintProcessorInfo1 *r)
+{
+ printf("print_processor_name: %s\n", r->print_processor_name);
+}
+
+static WERROR cmd_spoolss_enum_procs(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR werror;
+ const char *environment = SPOOLSS_ARCHITECTURE_NT_X86;
+ uint32_t num_procs, level = 1, i;
+ union spoolss_PrintProcessorInfo *procs;
+
+ /* Parse the command arguments */
+
+ if (argc < 1 || argc > 4) {
+ printf ("Usage: %s [environment] [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ environment = argv[1];
+ }
+
+ if (argc == 3) {
+ level = atoi(argv[2]);
+ }
+
+ /* Enumerate Print Processors */
+
+ werror = rpccli_spoolss_enumprintprocessors(cli, mem_ctx,
+ cli->srv_name_slash,
+ environment,
+ level,
+ 0,
+ &num_procs,
+ &procs);
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ /* Display output */
+
+ for (i = 0; i < num_procs; i++) {
+ switch (level) {
+ case 1:
+ display_proc_info1(&procs[i].info1);
+ break;
+ }
+ }
+
+ done:
+ return werror;
+}
+
+static void display_proc_data_types_info1(struct spoolss_PrintProcDataTypesInfo1 *r)
+{
+ printf("name_array: %s\n", r->name_array);
+}
+
+static WERROR cmd_spoolss_enum_proc_data_types(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR werror;
+ const char *print_processor_name = "winprint";
+ uint32_t num_procs, level = 1, i;
+ union spoolss_PrintProcDataTypesInfo *procs;
+
+ /* Parse the command arguments */
+
+ if (argc < 1 || argc > 4) {
+ printf ("Usage: %s [environment] [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ print_processor_name = argv[1];
+ }
+
+ if (argc == 3) {
+ level = atoi(argv[2]);
+ }
+
+ /* Enumerate Print Processor Data Types */
+
+ werror = rpccli_spoolss_enumprintprocessordatatypes(cli, mem_ctx,
+ cli->srv_name_slash,
+ print_processor_name,
+ level,
+ 0,
+ &num_procs,
+ &procs);
+ if (!W_ERROR_IS_OK(werror))
+ goto done;
+
+ /* Display output */
+
+ for (i = 0; i < num_procs; i++) {
+ switch (level) {
+ case 1:
+ display_proc_data_types_info1(&procs[i].info1);
+ break;
+ }
+ }
+
+ done:
+ return werror;
+}
+
+static void display_monitor1(const struct spoolss_MonitorInfo1 *r)
+{
+ printf("monitor_name: %s\n", r->monitor_name);
+}
+
+static void display_monitor2(const struct spoolss_MonitorInfo2 *r)
+{
+ printf("monitor_name: %s\n", r->monitor_name);
+ printf("environment: %s\n", r->environment);
+ printf("dll_name: %s\n", r->dll_name);
+}
+
+static WERROR cmd_spoolss_enum_monitors(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR werror;
+ uint32_t count, level = 1, i;
+ union spoolss_MonitorInfo *info;
+
+ /* Parse the command arguments */
+
+ if (argc > 2) {
+ printf("Usage: %s [level]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc == 2) {
+ level = atoi(argv[1]);
+ }
+
+ /* Enumerate Print Monitors */
+
+ werror = rpccli_spoolss_enummonitors(cli, mem_ctx,
+ cli->srv_name_slash,
+ level,
+ 0,
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(werror)) {
+ goto done;
+ }
+
+ /* Display output */
+
+ for (i = 0; i < count; i++) {
+ switch (level) {
+ case 1:
+ display_monitor1(&info[i].info1);
+ break;
+ case 2:
+ display_monitor2(&info[i].info2);
+ break;
+ }
+ }
+
+ done:
+ return werror;
+}
+
+static WERROR cmd_spoolss_create_printer_ic(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ struct policy_handle handle, gdi_handle;
+ const char *printername;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_CreatePrinterIC(b, mem_ctx,
+ &handle,
+ &gdi_handle,
+ &devmode_ctr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&gdi_handle)) {
+ WERROR _result;
+ dcerpc_spoolss_DeletePrinterIC(b, mem_ctx, &gdi_handle, &_result);
+ }
+ if (is_valid_policy_hnd(&handle)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &_result);
+ }
+
+ return result;
+}
+
+static WERROR cmd_spoolss_play_gdi_script_on_printer_ic(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ struct policy_handle handle, gdi_handle;
+ const char *printername;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ DATA_BLOB in,out;
+ uint32_t count = 0;
+
+ RPCCLIENT_PRINTERNAME(printername, cli, argv[1]);
+
+ result = rpccli_spoolss_openprinter_ex(cli, mem_ctx,
+ printername,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle);
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_CreatePrinterIC(b, mem_ctx,
+ &handle,
+ &gdi_handle,
+ &devmode_ctr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ in = data_blob_string_const("");
+ out = data_blob_talloc_zero(mem_ctx, 4);
+
+ status = dcerpc_spoolss_PlayGDIScriptOnPrinterIC(b, mem_ctx,
+ &gdi_handle,
+ in.data,
+ in.length,
+ out.data,
+ out.length,
+ 0, /* ul */
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ count = IVAL(out.data, 0);
+
+ out = data_blob_talloc_zero(mem_ctx,
+ count * sizeof(struct UNIVERSAL_FONT_ID) + 4);
+
+ status = dcerpc_spoolss_PlayGDIScriptOnPrinterIC(b, mem_ctx,
+ &gdi_handle,
+ in.data,
+ in.length,
+ out.data,
+ out.length,
+ 0, /* ul */
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ {
+ enum ndr_err_code ndr_err;
+ struct UNIVERSAL_FONT_ID_ctr r;
+
+ ndr_err = ndr_pull_struct_blob(&out, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_UNIVERSAL_FONT_ID_ctr);
+ if (NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NDR_PRINT_DEBUG(UNIVERSAL_FONT_ID_ctr, &r);
+ }
+ }
+
+ done:
+ if (is_valid_policy_hnd(&gdi_handle)) {
+ WERROR _result;
+ dcerpc_spoolss_DeletePrinterIC(b, mem_ctx, &gdi_handle, &_result);
+ }
+ if (is_valid_policy_hnd(&handle)) {
+ WERROR _result;
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &_result);
+ }
+
+ return result;
+}
+
+static WERROR cmd_spoolss_get_core_printer_drivers(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ HRESULT result;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ const char *architecture = SPOOLSS_ARCHITECTURE_x64;
+ struct spoolss_CorePrinterDriver core_printer_drivers;
+ DATA_BLOB blob;
+ bool ok;
+ int i;
+ uint32_t count;
+ const char **array;
+
+ if (argc == 1) {
+ count = 1;
+ array = talloc_zero_array(mem_ctx, const char *, count + 1);
+ if (array == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ array[0] = talloc_strdup(array, SPOOLSS_CORE_PRINT_PACKAGE_FILES_XPSDRV);
+ if (array[0] == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ count = argc -1;
+ array = talloc_zero_array(mem_ctx, const char *, count + 1);
+ if (array == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ for (i = 0; i < argc - 1; i++) {
+ array[i] = talloc_strdup(array, argv[i + 1]);
+ if (array[i] == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+ }
+
+ ok = push_reg_multi_sz(mem_ctx, &blob, array);
+ if (!ok) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = dcerpc_spoolss_GetCorePrinterDrivers(b, mem_ctx,
+ cli->srv_name_slash,
+ architecture,
+ blob.length/2,
+ (uint16_t *)blob.data,
+ count,
+ &core_printer_drivers,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!HRES_IS_OK(result)) {
+ return W_ERROR(WIN32_FROM_HRESULT(result));
+ }
+
+ return WERR_OK;
+}
+
+static WERROR cmd_spoolss_enum_permachineconnections(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR result;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ const char *servername = cli->srv_name_slash;
+ DATA_BLOB in = data_blob_null;
+ struct spoolss_PrinterInfo4 *info;
+ uint32_t needed, count;
+
+ if (argc > 2) {
+ printf("usage: %s [servername]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc > 1) {
+ servername = argv[1];
+ }
+
+ status = dcerpc_spoolss_EnumPerMachineConnections(b, mem_ctx,
+ servername,
+ &in,
+ in.length,
+ &count,
+ &info,
+ &needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_EQUAL(result, WERR_INSUFFICIENT_BUFFER)) {
+ in = data_blob_talloc_zero(mem_ctx, needed);
+ status = dcerpc_spoolss_EnumPerMachineConnections(b, mem_ctx,
+ servername,
+ &in,
+ in.length,
+ &count,
+ &info,
+ &needed,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ }
+
+ return result;
+}
+
+static WERROR cmd_spoolss_add_permachineconnection(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR result;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ const char *servername = cli->srv_name_slash;
+ const char *printername = "Microsoft Print to PDF";
+ const char *printserver = "samba.org";
+ const char *provider = ""; /* refers to Win32spl.dll then */
+ const char *composed_printername;
+
+ if (argc > 5) {
+ printf("usage: %s [servername] [printername] [printserver] [provider]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc > 1) {
+ servername = argv[1];
+ }
+ if (argc > 2) {
+ printername = argv[2];
+ }
+ if (argc > 3) {
+ printserver = argv[3];
+ }
+ if (argc > 4) {
+ provider = argv[4];
+ }
+
+ composed_printername = talloc_asprintf(mem_ctx, "%s\\%s", servername,
+ printername);
+ if (composed_printername == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ status = dcerpc_spoolss_AddPerMachineConnection(b, mem_ctx,
+ servername,
+ composed_printername,
+ printserver,
+ provider,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return result;
+}
+
+static WERROR cmd_spoolss_del_permachineconnection(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR result;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ const char *servername = cli->srv_name_slash;
+ const char *printername = "Microsoft Print to PDF";
+ const char *composed_printername;
+
+ if (argc > 3) {
+ printf("usage: %s [servername] [printername]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc > 1) {
+ servername = argv[1];
+ }
+ if (argc > 2) {
+ printername = argv[2];
+ }
+
+ composed_printername = talloc_asprintf(mem_ctx, "%s\\%s", servername,
+ printername);
+ if (composed_printername == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = dcerpc_spoolss_DeletePerMachineConnection(b, mem_ctx,
+ servername,
+ composed_printername,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return result;
+}
+
+/* List of commands exported by this module */
+struct cmd_set spoolss_commands[] = {
+
+ {
+ .name = "SPOOLSS",
+ },
+
+ {
+ .name = "adddriver",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_addprinterdriver,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Add a print driver",
+ .usage = "",
+ .use_netlogon_creds = false,
+ },
+ {
+ .name = "addprinter",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_addprinterex,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Add a printer",
+ .usage = "",
+ },
+ {
+ .name = "deldriver",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_deletedriver,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Delete a printer driver",
+ .usage = "",
+ },
+ {
+ .name = "deldriverex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_deletedriverex,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Delete a printer driver with files",
+ .usage = "",
+ },
+ {
+ .name = "enumdata",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_data,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate printer data",
+ .usage = "",
+ },
+ {
+ .name = "enumdataex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_data_ex,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate printer data for a key",
+ .usage = "",
+ },
+ {
+ .name = "enumkey",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_printerkey,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate printer keys",
+ .usage = "",
+ },
+ {
+ .name = "enumjobs",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_jobs,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate print jobs",
+ .usage = "",
+ },
+ {
+ .name = "getjob",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_get_job,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get print job",
+ .usage = "",
+ },
+ {
+ .name = "setjob",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_set_job,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Set print job",
+ .usage = "",
+ },
+ {
+ .name = "enumports",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_ports,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate printer ports",
+ .usage = "",
+ },
+ {
+ .name = "enumdrivers",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_drivers,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate installed printer drivers",
+ .usage = "",
+ },
+ {
+ .name = "enumprinters",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_printers,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate printers",
+ .usage = "",
+ },
+ {
+ .name = "getdata",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getprinterdata,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get print driver data",
+ .usage = "",
+ },
+ {
+ .name = "getdataex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getprinterdataex,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get printer driver data with keyname",
+ .usage = "",
+ },
+ {
+ .name = "getdriver",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getdriver,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get print driver information",
+ .usage = "",
+ },
+ {
+ .name = "getdriverdir",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getdriverdir,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get print driver upload directory",
+ .usage = "",
+ },
+ {
+ .name = "getdriverpackagepath",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getdriverpackagepath,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get print driver package download directory",
+ .usage = "",
+ },
+ {
+ .name = "getprinter",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getprinter,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get printer info",
+ .usage = "",
+ },
+ {
+ .name = "openprinter",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_open_printer,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Open printer handle",
+ .usage = "",
+ },
+ {
+ .name = "openprinter_ex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_open_printer_ex,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Open printer handle",
+ .usage = "",
+ },
+ {
+ .name = "setdriver",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_setdriver,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Set printer driver",
+ .usage = "",
+ },
+ {
+ .name = "getprintprocdir",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getprintprocdir,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get print processor directory",
+ .usage = "",
+ },
+ {
+ .name = "addform",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_addform,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Add form",
+ .usage = "",
+ },
+ {
+ .name = "setform",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_setform,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Set form",
+ .usage = "",
+ },
+ {
+ .name = "getform",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_getform,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get form",
+ .usage = "",
+ },
+ {
+ .name = "deleteform",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_deleteform,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Delete form",
+ .usage = "",
+ },
+ {
+ .name = "enumforms",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_forms,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate forms",
+ .usage = "",
+ },
+ {
+ .name = "setprinter",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_setprinter,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Set printer comment",
+ .usage = "",
+ },
+ {
+ .name = "setprintername",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_setprintername,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Set printername",
+ .usage = "",
+ },
+ {
+ .name = "setprinterdata",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_setprinterdata,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Set REG_SZ printer data",
+ .usage = "",
+ },
+ {
+ .name = "rffpcnex",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_rffpcnex,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Rffpcnex test",
+ .usage = "",
+ },
+ {
+ .name = "printercmp",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_printercmp,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Printer comparison test",
+ .usage = "",
+ },
+ {
+ .name = "enumprocs",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_procs,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Print Processors",
+ .usage = "",
+ },
+ {
+ .name = "enumprocdatatypes",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_proc_data_types,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Print Processor Data Types",
+ .usage = "",
+ },
+ {
+ .name = "enummonitors",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_monitors,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Print Monitors",
+ .usage = "",
+ },
+ {
+ .name = "createprinteric",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_create_printer_ic,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Create Printer IC",
+ .usage = "",
+ },
+ {
+ .name = "playgdiscriptonprinteric",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_play_gdi_script_on_printer_ic,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Create Printer IC",
+ .usage = "",
+ },
+ {
+ .name = "getcoreprinterdrivers",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_get_core_printer_drivers,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Get CorePrinterDriver",
+ .usage = "",
+ },
+ {
+ .name = "enumpermachineconnections",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_enum_permachineconnections,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Per Machine Connections",
+ .usage = "",
+ },
+ {
+ .name = "addpermachineconnection",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_add_permachineconnection,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Add Per Machine Connection",
+ .usage = "",
+ },
+ {
+ .name = "delpermachineconnection",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_spoolss_del_permachineconnection,
+ .table = &ndr_table_spoolss,
+ .rpc_pipe = NULL,
+ .description = "Delete Per Machine Connection",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_spotlight.c b/source3/rpcclient/cmd_spotlight.c
new file mode 100644
index 0000000..ba3f61f
--- /dev/null
+++ b/source3/rpcclient/cmd_spotlight.c
@@ -0,0 +1,420 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC Spotlight client
+
+ Copyright (C) Ralph Boehme 2018
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "libsmb/libsmb.h"
+#include "../librpc/gen_ndr/ndr_mdssvc_c.h"
+#include "../rpc_server/mdssvc/mdssvc.h"
+#include "../rpc_server/mdssvc/dalloc.h"
+#include "../rpc_server/mdssvc/marshalling.h"
+
+static NTSTATUS cmd_mdssvc_fetch_properties(
+ struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ uint32_t device_id = 0x2f000045;
+ uint32_t unkn1 = 23;
+ uint32_t unkn2 = 0;
+ struct policy_handle share_handle;
+ char share_path[1025] = { 0 };
+ uint32_t mds_status;
+ uint32_t flags; /* server always returns 0x6b000001 ? */
+ uint32_t unkn3; /* server always returns 0 ? */
+ struct mdssvc_blob request_blob;
+ struct mdssvc_blob response_blob;
+ uint32_t max_fragment_size = 64 * 1024;
+ DALLOC_CTX *d, *mds_reply;
+ uint64_t *uint64var;
+ sl_array_t *array1, *array2;
+ uint32_t unkn4;
+ int result;
+ bool ok;
+
+ if (argc != 3) {
+ printf("Usage: %s SHARENAME MOUNTPATH\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ status = dcerpc_mdssvc_open(b, mem_ctx,
+ &device_id,
+ &unkn1,
+ &unkn2,
+ argv[2],
+ argv[1],
+ share_path,
+ &share_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_mdssvc_unknown1(b, mem_ctx,
+ &share_handle,
+ 0,
+ device_id,
+ unkn1,
+ 0,
+ geteuid(),
+ getegid(),
+ &mds_status,
+ &flags,
+ &unkn3);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ d = dalloc_new(mem_ctx);
+ if (d == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ mds_reply = dalloc_new(mem_ctx);
+ if (mds_reply == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ array1 = dalloc_zero(d, sl_array_t);
+ if (array1 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ array2 = dalloc_zero(d, sl_array_t);
+ if (array2 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ result = dalloc_stradd(array2, "fetchPropertiesForContext:");
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+ uint64var = talloc_zero_array(mem_ctx, uint64_t, 2);
+ if (uint64var == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ talloc_set_name(uint64var, "uint64_t *");
+
+ result = dalloc_add(array2, &uint64var[0], uint64_t *);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ result = dalloc_add(array1, array2, sl_array_t);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+ result = dalloc_add(d, array1, sl_array_t);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ status = sl_pack_alloc(mem_ctx, d, &request_blob, max_fragment_size);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_mdssvc_cmd(b, mem_ctx,
+ &share_handle,
+ 0,
+ device_id,
+ 23,
+ 0,
+ 0x6b000001,
+ request_blob,
+ 0,
+ max_fragment_size,
+ 1,
+ max_fragment_size,
+ 0,
+ 0,
+ &mds_status,
+ &response_blob,
+ &unkn4);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ ok = sl_unpack(mds_reply, (char *)response_blob.spotlight_blob,
+ response_blob.length);
+ if (!ok) {
+ DEBUG(1, ("error unpacking Spotlight RPC blob\n"));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ DEBUG(0, ("%s", dalloc_dump(mds_reply, 0)));
+
+done:
+ return status;
+}
+
+static NTSTATUS cmd_mdssvc_fetch_attributes(
+ struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ NTSTATUS status;
+ uint32_t device_id = 0x2f000045;
+ uint32_t unkn1 = 23;
+ uint32_t unkn2 = 0;
+ struct policy_handle share_handle;
+ char share_path[1025];
+ uint32_t mds_status;
+ uint32_t flags; /* server always returns 0x6b000001 ? */
+ uint32_t unkn3; /* server always returns 0 ? */
+ struct mdssvc_blob request_blob;
+ struct mdssvc_blob response_blob;
+ uint32_t max_fragment_size = 64 * 1024;
+ DALLOC_CTX *d, *mds_reply;
+ uint64_t *uint64var;
+ sl_array_t *array;
+ sl_array_t *cmd_array;
+ sl_array_t *attr_array;
+ sl_cnids_t *cnids;
+ uint64_t cnid;
+ uint32_t unkn4;
+ int result;
+ bool ok;
+
+ if (argc != 4) {
+ printf("Usage: %s SHARENAME MOUNTPATH CNID\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ ok = conv_str_u64(argv[3], &cnid);
+ if (!ok) {
+ printf("Failed to parse: %s\n", argv[3]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_mdssvc_open(b, mem_ctx,
+ &device_id,
+ &unkn1,
+ &unkn2,
+ argv[2],
+ argv[1],
+ share_path,
+ &share_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_mdssvc_unknown1(b, mem_ctx,
+ &share_handle,
+ 0,
+ device_id,
+ unkn1,
+ 0,
+ geteuid(),
+ getegid(),
+ &mds_status,
+ &flags,
+ &unkn3);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ d = dalloc_new(mem_ctx);
+ if (d == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ array = dalloc_zero(d, sl_array_t);
+ if (array == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ result = dalloc_add(d, array, sl_array_t);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ cmd_array = dalloc_zero(d, sl_array_t);
+ if (cmd_array == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ result = dalloc_add(array, cmd_array, sl_array_t);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ result = dalloc_stradd(cmd_array,
+ "fetchAttributes:forOIDArray:context:");
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ uint64var = talloc_zero_array(mem_ctx, uint64_t, 2);
+ if (uint64var == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ talloc_set_name(uint64var, "uint64_t *");
+ uint64var[0] = 0x500a;
+ uint64var[1] = 0;
+
+ result = dalloc_add(cmd_array, &uint64var[0], uint64_t *);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ attr_array = dalloc_zero(d, sl_array_t);
+ if (attr_array == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ result = dalloc_add(array, attr_array, sl_array_t);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ result = dalloc_stradd(attr_array, "kMDItemPath");
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /* CNIDs */
+ cnids = talloc_zero(array, sl_cnids_t);
+ if (cnids == NULL) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+ cnids->ca_cnids = dalloc_new(cnids);
+ if (cnids->ca_cnids == NULL) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ cnids->ca_unkn1 = 0xadd;
+ cnids->ca_context = 0x6b000020;
+
+ result = dalloc_add_copy(cnids->ca_cnids, &cnid, uint64_t);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ result = dalloc_add(array, cnids, sl_cnids_t);
+ if (result != 0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ status = sl_pack_alloc(mem_ctx, d, &request_blob, max_fragment_size);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_mdssvc_cmd(b, mem_ctx,
+ &share_handle,
+ 0,
+ device_id,
+ 23,
+ 0,
+ 0x6b000001,
+ request_blob,
+ 0,
+ max_fragment_size,
+ 1,
+ max_fragment_size,
+ 0,
+ 0,
+ &mds_status,
+ &response_blob,
+ &unkn4);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("dcerpc_mdssvc_cmd failed: %s\n", nt_errstr(status));
+ goto done;
+ }
+
+ if (response_blob.length == 0) {
+ printf("mdssvc returned empty response\n");
+ status = NT_STATUS_RPC_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ mds_reply = dalloc_new(mem_ctx);
+ if (mds_reply == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ok = sl_unpack(mds_reply, (char *)response_blob.spotlight_blob,
+ response_blob.length);
+ if (!ok) {
+ printf("Unpacking Spotlight RPC blob failed\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ printf("%s", dalloc_dump(mds_reply, 0));
+
+done:
+ return status;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set spotlight_commands[] = {
+
+ {
+ .name = "MDSSVC"
+ },
+ {
+ .name = "fetch_properties",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_mdssvc_fetch_properties,
+ .table = &ndr_table_mdssvc,
+ .description = "Fetch connection properties",
+ .usage = "",
+ },
+ {
+ .name = "fetch_attributes",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_mdssvc_fetch_attributes,
+ .table = &ndr_table_mdssvc,
+ .description = "Fetch attributes for a CNID",
+ .usage = "",
+ },
+ {0}
+};
diff --git a/source3/rpcclient/cmd_srvsvc.c b/source3/rpcclient/cmd_srvsvc.c
new file mode 100644
index 0000000..073e51d
--- /dev/null
+++ b/source3/rpcclient/cmd_srvsvc.c
@@ -0,0 +1,1266 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Andrew Tridgell 1992-1999
+ Copyright (C) Luke Kenneth Casson Leighton 1996 - 1999
+ Copyright (C) Tim Potter 2000,2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_srvsvc.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "../libcli/security/display_sec.h"
+#include "lib/util/string_wrappers.h"
+
+/* Display server query info */
+
+static char *get_server_type_str(uint32_t type)
+{
+ static fstring typestr;
+ int i;
+
+ if (type == SV_TYPE_ALL) {
+ fstrcpy(typestr, "All");
+ return typestr;
+ }
+
+ typestr[0] = 0;
+
+ for (i = 0; i < 32; i++) {
+ if (type & ((uint32_t)1 << i)) {
+ switch (1 << i) {
+ case SV_TYPE_WORKSTATION:
+ fstrcat(typestr, "Wk ");
+ break;
+ case SV_TYPE_SERVER:
+ fstrcat(typestr, "Sv ");
+ break;
+ case SV_TYPE_SQLSERVER:
+ fstrcat(typestr, "Sql ");
+ break;
+ case SV_TYPE_DOMAIN_CTRL:
+ fstrcat(typestr, "PDC ");
+ break;
+ case SV_TYPE_DOMAIN_BAKCTRL:
+ fstrcat(typestr, "BDC ");
+ break;
+ case SV_TYPE_TIME_SOURCE:
+ fstrcat(typestr, "Tim ");
+ break;
+ case SV_TYPE_AFP:
+ fstrcat(typestr, "AFP ");
+ break;
+ case SV_TYPE_NOVELL:
+ fstrcat(typestr, "Nov ");
+ break;
+ case SV_TYPE_DOMAIN_MEMBER:
+ fstrcat(typestr, "Dom ");
+ break;
+ case SV_TYPE_PRINTQ_SERVER:
+ fstrcat(typestr, "PrQ ");
+ break;
+ case SV_TYPE_DIALIN_SERVER:
+ fstrcat(typestr, "Din ");
+ break;
+ case SV_TYPE_SERVER_UNIX:
+ fstrcat(typestr, "Unx ");
+ break;
+ case SV_TYPE_NT:
+ fstrcat(typestr, "NT ");
+ break;
+ case SV_TYPE_WFW:
+ fstrcat(typestr, "Wfw ");
+ break;
+ case SV_TYPE_SERVER_MFPN:
+ fstrcat(typestr, "Mfp ");
+ break;
+ case SV_TYPE_SERVER_NT:
+ fstrcat(typestr, "SNT ");
+ break;
+ case SV_TYPE_POTENTIAL_BROWSER:
+ fstrcat(typestr, "PtB ");
+ break;
+ case SV_TYPE_BACKUP_BROWSER:
+ fstrcat(typestr, "BMB ");
+ break;
+ case SV_TYPE_MASTER_BROWSER:
+ fstrcat(typestr, "LMB ");
+ break;
+ case SV_TYPE_DOMAIN_MASTER:
+ fstrcat(typestr, "DMB ");
+ break;
+ case SV_TYPE_SERVER_OSF:
+ fstrcat(typestr, "OSF ");
+ break;
+ case SV_TYPE_SERVER_VMS:
+ fstrcat(typestr, "VMS ");
+ break;
+ case SV_TYPE_WIN95_PLUS:
+ fstrcat(typestr, "W95 ");
+ break;
+ case SV_TYPE_ALTERNATE_XPORT:
+ fstrcat(typestr, "Xpt ");
+ break;
+ case SV_TYPE_LOCAL_LIST_ONLY:
+ fstrcat(typestr, "Dom ");
+ break;
+ case SV_TYPE_DOMAIN_ENUM:
+ fstrcat(typestr, "Loc ");
+ break;
+ }
+ }
+ }
+
+ i = strlen(typestr) - 1;
+
+ if (typestr[i] == ' ')
+ typestr[i] = 0;
+
+ return typestr;
+}
+
+static void display_server(const char *sname, uint32_t type, const char *comment)
+{
+ printf("\t%-15.15s%-20s %s\n", sname, get_server_type_str(type),
+ comment);
+}
+
+static void display_srv_info_101(struct srvsvc_NetSrvInfo101 *r)
+{
+ display_server(r->server_name, r->server_type, r->comment);
+
+ printf("\tplatform_id :\t%d\n", r->platform_id);
+ printf("\tos version :\t%d.%d\n",
+ r->version_major, r->version_minor);
+ printf("\tserver type :\t0x%x\n", r->server_type);
+}
+
+static void display_srv_info_102(struct srvsvc_NetSrvInfo102 *r)
+{
+ display_server(r->server_name, r->server_type, r->comment);
+
+ printf("\tplatform_id :\t%d\n", r->platform_id);
+ printf("\tos version :\t%d.%d\n",
+ r->version_major, r->version_minor);
+ printf("\tserver type :\t0x%x\n", r->server_type);
+
+ printf("\tusers :\t%x\n", r->users);
+ printf("\tdisc, hidden :\t%x, %x\n", r->disc, r->hidden);
+ printf("\tannounce, delta :\t%d, %d\n", r->announce,
+ r->anndelta);
+ printf("\tlicenses :\t%d\n", r->licenses);
+ printf("\tuser path :\t%s\n", r->userpath);
+}
+
+/* Server query info */
+static WERROR cmd_srvsvc_srv_query_info(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ uint32_t info_level = 101;
+ union srvsvc_NetSrvInfo info;
+ WERROR result;
+ NTSTATUS status;
+ const char *server_unc = cli->srv_name_slash;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 3) {
+ printf("Usage: %s [infolevel] [server_unc]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ info_level = atoi(argv[1]);
+ }
+
+ if (argc >= 3) {
+ server_unc = argv[2];
+ }
+
+ status = dcerpc_srvsvc_NetSrvGetInfo(b, mem_ctx,
+ server_unc,
+ info_level,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Display results */
+
+ switch (info_level) {
+ case 101:
+ display_srv_info_101(info.info101);
+ break;
+ case 102:
+ display_srv_info_102(info.info102);
+ break;
+ default:
+ printf("unsupported info level %d\n", info_level);
+ break;
+ }
+
+ done:
+ return result;
+}
+
+static void display_share_info_1(struct srvsvc_NetShareInfo1 *r)
+{
+ printf("netname: %s\n", r->name);
+ printf("\tremark:\t%s\n", r->comment);
+}
+
+static void display_share_info_2(struct srvsvc_NetShareInfo2 *r)
+{
+ printf("netname: %s\n", r->name);
+ printf("\tremark:\t%s\n", r->comment);
+ printf("\tpath:\t%s\n", r->path);
+ printf("\tpassword:\t%s\n", r->password);
+}
+
+static void display_share_info_502(struct srvsvc_NetShareInfo502 *r)
+{
+ printf("netname: %s\n", r->name);
+ printf("\tremark:\t%s\n", r->comment);
+ printf("\tpath:\t%s\n", r->path);
+ printf("\tpassword:\t%s\n", r->password);
+
+ printf("\ttype:\t0x%x\n", r->type);
+ printf("\tperms:\t%d\n", r->permissions);
+ printf("\tmax_uses:\t%d\n", r->max_users);
+ printf("\tnum_uses:\t%d\n", r->current_users);
+
+ if (r->sd_buf.sd)
+ display_sec_desc(r->sd_buf.sd);
+
+}
+
+static void display_share_info_1005(struct srvsvc_NetShareInfo1005 *r)
+{
+ printf("flags: 0x%x\n", r->dfs_flags);
+ printf("csc caching: %u\n",
+ (r->dfs_flags & SHARE_1005_CSC_POLICY_MASK) >>
+ SHARE_1005_CSC_POLICY_SHIFT);
+}
+
+static WERROR cmd_srvsvc_net_share_enum_int(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv,
+ uint32_t opcode)
+{
+ uint32_t info_level = 2;
+ struct srvsvc_NetShareInfoCtr info_ctr;
+ struct srvsvc_NetShareCtr0 ctr0;
+ struct srvsvc_NetShareCtr1 ctr1;
+ struct srvsvc_NetShareCtr2 ctr2;
+ struct srvsvc_NetShareCtr501 ctr501;
+ struct srvsvc_NetShareCtr502 ctr502;
+ struct srvsvc_NetShareCtr1004 ctr1004;
+ struct srvsvc_NetShareCtr1005 ctr1005;
+ struct srvsvc_NetShareCtr1006 ctr1006;
+ struct srvsvc_NetShareCtr1007 ctr1007;
+ struct srvsvc_NetShareCtr1501 ctr1501;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t totalentries = 0;
+ uint32_t count = 0;
+ uint32_t resume_handle = 0;
+ uint32_t *resume_handle_p = NULL;
+ uint32_t preferred_len = 0xffffffff, i;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 3) {
+ printf("Usage: %s [infolevel] [resume_handle]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ info_level = atoi(argv[1]);
+ }
+
+ if (argc == 3) {
+ resume_handle = atoi(argv[2]);
+ resume_handle_p = &resume_handle;
+ }
+
+ ZERO_STRUCT(info_ctr);
+
+ info_ctr.level = info_level;
+
+ switch (info_level) {
+ case 0:
+ ZERO_STRUCT(ctr0);
+ info_ctr.ctr.ctr0 = &ctr0;
+ break;
+ case 1:
+ ZERO_STRUCT(ctr1);
+ info_ctr.ctr.ctr1 = &ctr1;
+ break;
+ case 2:
+ ZERO_STRUCT(ctr2);
+ info_ctr.ctr.ctr2 = &ctr2;
+ break;
+ case 501:
+ ZERO_STRUCT(ctr501);
+ info_ctr.ctr.ctr501 = &ctr501;
+ break;
+ case 502:
+ ZERO_STRUCT(ctr502);
+ info_ctr.ctr.ctr502 = &ctr502;
+ break;
+ case 1004:
+ ZERO_STRUCT(ctr1004);
+ info_ctr.ctr.ctr1004 = &ctr1004;
+ break;
+ case 1005:
+ ZERO_STRUCT(ctr1005);
+ info_ctr.ctr.ctr1005 = &ctr1005;
+ break;
+ case 1006:
+ ZERO_STRUCT(ctr1006);
+ info_ctr.ctr.ctr1006 = &ctr1006;
+ break;
+ case 1007:
+ ZERO_STRUCT(ctr1007);
+ info_ctr.ctr.ctr1007 = &ctr1007;
+ break;
+ case 1501:
+ ZERO_STRUCT(ctr1501);
+ info_ctr.ctr.ctr1501 = &ctr1501;
+ break;
+ }
+
+ switch (opcode) {
+ case NDR_SRVSVC_NETSHAREENUM:
+ status = dcerpc_srvsvc_NetShareEnum(b, mem_ctx,
+ cli->desthost,
+ &info_ctr,
+ preferred_len,
+ &totalentries,
+ resume_handle_p,
+ &result);
+ break;
+ case NDR_SRVSVC_NETSHAREENUMALL:
+ status = dcerpc_srvsvc_NetShareEnumAll(b, mem_ctx,
+ cli->desthost,
+ &info_ctr,
+ preferred_len,
+ &totalentries,
+ resume_handle_p,
+ &result);
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Display results */
+
+ switch (info_level) {
+ case 1:
+ count = info_ctr.ctr.ctr1->count;
+ for (i = 0; i < count; i++)
+ display_share_info_1(&info_ctr.ctr.ctr1->array[i]);
+ break;
+ case 2:
+ count = info_ctr.ctr.ctr2->count;
+ for (i = 0; i < count; i++)
+ display_share_info_2(&info_ctr.ctr.ctr2->array[i]);
+ break;
+ case 502:
+ count = info_ctr.ctr.ctr502->count;
+ for (i = 0; i < count; i++)
+ display_share_info_502(&info_ctr.ctr.ctr502->array[i]);
+ break;
+ default:
+ printf("unsupported info level %d\n", info_level);
+ break;
+ }
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_share_enum(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ return cmd_srvsvc_net_share_enum_int(cli, mem_ctx,
+ argc, argv,
+ NDR_SRVSVC_NETSHAREENUM);
+}
+
+static WERROR cmd_srvsvc_net_share_enum_all(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ return cmd_srvsvc_net_share_enum_int(cli, mem_ctx,
+ argc, argv,
+ NDR_SRVSVC_NETSHAREENUMALL);
+}
+
+static WERROR cmd_srvsvc_net_share_get_info(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ uint32_t info_level = 502;
+ union srvsvc_NetShareInfo info;
+ WERROR result;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2 || argc > 3) {
+ printf("Usage: %s sharename [infolevel 1|2|502|1005]\n",
+ argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc == 3)
+ info_level = atoi(argv[2]);
+
+ status = dcerpc_srvsvc_NetShareGetInfo(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ info_level,
+ &info,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Display results */
+
+ switch (info_level) {
+ case 1:
+ display_share_info_1(info.info1);
+ break;
+ case 2:
+ display_share_info_2(info.info2);
+ break;
+ case 502:
+ display_share_info_502(info.info502);
+ break;
+ case 1005:
+ display_share_info_1005(info.info1005);
+ break;
+ default:
+ printf("unsupported info level %d\n", info_level);
+ break;
+ }
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_share_set_info(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ uint32_t info_level = 502;
+ union srvsvc_NetShareInfo info_get;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t parm_err = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 3) {
+ printf("Usage: %s [sharename] [comment]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ /* retrieve share info */
+ status = dcerpc_srvsvc_NetShareGetInfo(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ info_level,
+ &info_get,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ info_get.info502->comment = argv[2];
+
+ /* set share info */
+ status = dcerpc_srvsvc_NetShareSetInfo(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ info_level,
+ &info_get,
+ &parm_err,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* re-retrieve share info and display */
+ status = dcerpc_srvsvc_NetShareGetInfo(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ info_level,
+ &info_get,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ display_share_info_502(info_get.info502);
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_share_set_dfs_flags(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct srvsvc_NetShareInfo1005 info1005 = { 0 };
+ union srvsvc_NetShareInfo info = { .info1005 = &info1005 };
+ WERROR result;
+ NTSTATUS status;
+ uint32_t parm_err = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 3) {
+ printf("Usage: %s [sharename] [dfsflags]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc > 2) {
+ info.info1005->dfs_flags = strtol(argv[2], NULL, 0);
+ }
+
+ /* set share info */
+ status = dcerpc_srvsvc_NetShareSetInfo(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ 1005,
+ &info,
+ &parm_err,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ /* re-retrieve share info and display */
+ status = dcerpc_srvsvc_NetShareGetInfo(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ 1005,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ return result;
+ }
+
+ display_share_info_1005(info.info1005);
+
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_remote_tod(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct srvsvc_NetRemoteTODInfo *tod = NULL;
+ WERROR result;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 1) {
+ printf("Usage: %s\n", argv[0]);
+ return WERR_OK;
+ }
+
+ status = dcerpc_srvsvc_NetRemoteTOD(b, mem_ctx,
+ cli->srv_name_slash,
+ &tod,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_file_enum(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct srvsvc_NetFileCtr3 ctr3 = { 0 };
+ struct srvsvc_NetFileInfoCtr info_ctr = {
+ .level = 3,
+ .ctr = {
+ .ctr3 = &ctr3,
+ },
+ };
+ WERROR result;
+ NTSTATUS status;
+ uint32_t preferred_len = 0xffff;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 2) {
+ printf("Usage: %s [infolevel]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc == 2) {
+ info_ctr.level = atoi(argv[1]);
+ }
+
+ status = dcerpc_srvsvc_NetFileEnum(b, mem_ctx,
+ cli->desthost,
+ NULL,
+ NULL,
+ &info_ctr,
+ preferred_len,
+ &total_entries,
+ &resume_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ if (info_ctr.level == 3) {
+ struct srvsvc_NetFileCtr3 *ret = info_ctr.ctr.ctr3;
+ uint32_t i;
+
+ for (i=0; i<ret->count; i++) {
+ printf("%s\n", ret->array[i].path);
+ }
+ }
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_name_validate(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ uint32_t name_type = 9;
+ uint32_t flags = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2 || argc > 3) {
+ printf("Usage: %s [sharename] [type]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc == 3) {
+ name_type = atoi(argv[2]);
+ }
+
+ status = dcerpc_srvsvc_NetNameValidate(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ name_type,
+ flags,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_file_get_sec(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ struct sec_desc_buf *sd_buf = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2 || argc > 4) {
+ printf("Usage: %s [sharename] [file]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ status = dcerpc_srvsvc_NetGetFileSecurity(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ argv[2],
+ SECINFO_DACL,
+ &sd_buf,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ display_sec_desc(sd_buf->sd);
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_sess_del(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2 || argc > 4) {
+ printf("Usage: %s [client] [user]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ status = dcerpc_srvsvc_NetSessDel(b, mem_ctx,
+ cli->desthost,
+ argv[1],
+ argv[2],
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_sess_enum(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ WERROR result;
+ NTSTATUS status;
+ struct srvsvc_NetSessInfoCtr info_ctr;
+ struct srvsvc_NetSessCtr0 ctr0;
+ struct srvsvc_NetSessCtr1 ctr1;
+ struct srvsvc_NetSessCtr2 ctr2;
+ struct srvsvc_NetSessCtr10 ctr10;
+ struct srvsvc_NetSessCtr502 ctr502;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ uint32_t *resume_handle_p = NULL;
+ uint32_t level = 1;
+ const char *client = NULL;
+ const char *user = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 6) {
+ printf("Usage: %s [client] [user] [level] [resume_handle]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ client = argv[1];
+ }
+
+ if (argc >= 3) {
+ user = argv[2];
+ }
+
+ if (argc >= 4) {
+ level = atoi(argv[3]);
+ }
+
+ if (argc >= 5) {
+ resume_handle = atoi(argv[4]);
+ resume_handle_p = &resume_handle;
+ }
+
+ ZERO_STRUCT(info_ctr);
+
+ info_ctr.level = level;
+
+ d_printf("trying level: %d\n", level);
+
+ switch (level) {
+ case 0:
+ ZERO_STRUCT(ctr0);
+ info_ctr.ctr.ctr0 = &ctr0;
+ break;
+ case 1:
+ ZERO_STRUCT(ctr1);
+ info_ctr.ctr.ctr1 = &ctr1;
+ break;
+ case 2:
+ ZERO_STRUCT(ctr2);
+ info_ctr.ctr.ctr2 = &ctr2;
+ break;
+ case 10:
+ ZERO_STRUCT(ctr10);
+ info_ctr.ctr.ctr10 = &ctr10;
+ break;
+ case 502:
+ ZERO_STRUCT(ctr502);
+ info_ctr.ctr.ctr502 = &ctr502;
+ break;
+ }
+
+ status = dcerpc_srvsvc_NetSessEnum(b, mem_ctx,
+ cli->desthost,
+ client,
+ user,
+ &info_ctr,
+ 0xffffffff,
+ &total_entries,
+ resume_handle_p,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ d_printf("Received %d entries.\n", total_entries);
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_disk_enum(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct srvsvc_NetDiskInfo info;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ uint32_t level = 0;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 4) {
+ printf("Usage: %s [level] [resume_handle]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ level = atoi(argv[1]);
+ }
+
+ if (argc >= 3) {
+ resume_handle = atoi(argv[2]);
+ }
+
+ ZERO_STRUCT(info);
+
+ status = dcerpc_srvsvc_NetDiskEnum(b, mem_ctx,
+ cli->desthost,
+ level,
+ &info,
+ 0xffffffff,
+ &total_entries,
+ &resume_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_conn_enum(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct srvsvc_NetConnInfoCtr info_ctr;
+ struct srvsvc_NetConnCtr0 ctr0;
+ struct srvsvc_NetConnCtr1 ctr1;
+ WERROR result;
+ NTSTATUS status;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ uint32_t *resume_handle_p = NULL;
+ uint32_t level = 1;
+ const char *path = "IPC$";
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 4) {
+ printf("Usage: %s [level] [path] [resume_handle]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc >= 2) {
+ level = atoi(argv[1]);
+ }
+
+ if (argc >= 3) {
+ path = argv[2];
+ }
+
+ if (argc >= 4) {
+ resume_handle = atoi(argv[3]);
+ resume_handle_p = &resume_handle;
+ }
+
+ ZERO_STRUCT(info_ctr);
+
+ info_ctr.level = level;
+
+ switch (level) {
+ case 0:
+ ZERO_STRUCT(ctr0);
+ info_ctr.ctr.ctr0 = &ctr0;
+ break;
+ case 1:
+ ZERO_STRUCT(ctr1);
+ info_ctr.ctr.ctr1 = &ctr1;
+ break;
+ default:
+ return WERR_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_srvsvc_NetConnEnum(b, mem_ctx,
+ cli->desthost,
+ path,
+ &info_ctr,
+ 0xffffffff,
+ &total_entries,
+ resume_handle_p,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ done:
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_share_add(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct srvsvc_NetShareInfo502 info502 = { 0 };
+ union srvsvc_NetShareInfo info = { .info502 = &info502 };
+ WERROR result;
+ NTSTATUS status;
+ uint32_t max_users = -1, parm_error;
+ struct sec_desc_buf sd_buf = { 0 };
+ const char *path, *share_name, *comment = NULL;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 3 || argc > 5) {
+ printf("Usage: %s path share_name [max_users] [comment]\n",
+ argv[0]);
+ return WERR_OK;
+ }
+
+ path = argv[1];
+ share_name = argv[2];
+
+ if (argc >= 4) {
+ max_users = atoi(argv[3]);
+ }
+
+ if (argc >= 5) {
+ comment = argv[4];
+ }
+
+ info.info502->name = share_name;
+ info.info502->type = STYPE_DISKTREE;
+ info.info502->comment = comment;
+ info.info502->max_users = max_users;
+ info.info502->path = path;
+ info.info502->sd_buf = sd_buf;
+
+ status = dcerpc_srvsvc_NetShareAdd(b, mem_ctx, cli->desthost,
+ 502, &info, &parm_error, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+
+ return result;
+}
+
+static WERROR cmd_srvsvc_net_share_del(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ const char *share_name;
+ WERROR result;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2) {
+ printf("Usage: %s share_name\n", argv[0]);
+ return WERR_OK;
+ }
+
+ share_name = argv[1];
+
+ status = dcerpc_srvsvc_NetShareDel(b, mem_ctx, cli->desthost,
+ share_name, 0, &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ }
+
+ return result;
+}
+
+
+/* List of commands exported by this module */
+
+struct cmd_set srvsvc_commands[] = {
+
+ {
+ .name = "SRVSVC",
+ },
+
+ {
+ .name = "srvinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_srv_query_info,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Server query info",
+ .usage = "",
+ },
+ {
+ .name = "netshareenum",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_share_enum,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate shares",
+ .usage = "",
+ },
+ {
+ .name = "netshareenumall",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_share_enum_all,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate all shares",
+ .usage = "",
+ },
+ {
+ .name = "netsharegetinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_share_get_info,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Get Share Info",
+ .usage = "",
+ },
+ {
+ .name = "netsharesetinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_share_set_info,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Set Share Info",
+ .usage = "",
+ },
+ {
+ .name = "netsharesetdfsflags",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_share_set_dfs_flags,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Set DFS flags",
+ .usage = "",
+ },
+ {
+ .name = "netfileenum",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_file_enum,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate open files",
+ .usage = "",
+ },
+ {
+ .name = "netremotetod",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_remote_tod,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Fetch remote time of day",
+ .usage = "",
+ },
+ {
+ .name = "netnamevalidate",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_name_validate,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Validate sharename",
+ .usage = "",
+ },
+ {
+ .name = "netfilegetsec",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_file_get_sec,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Get File security",
+ .usage = "",
+ },
+ {
+ .name = "netsessdel",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_sess_del,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Delete Session",
+ .usage = "",
+ },
+ {
+ .name = "netsessenum",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_sess_enum,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Sessions",
+ .usage = "",
+ },
+ {
+ .name = "netdiskenum",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_disk_enum,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Disks",
+ .usage = "",
+ },
+ {
+ .name = "netconnenum",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_conn_enum,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Connections",
+ .usage = "",
+ },
+ {
+ .name = "netshareadd",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_share_add,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Add share",
+ .usage = "",
+ },
+ {
+ .name = "netsharedel",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_srvsvc_net_share_del,
+ .table = &ndr_table_srvsvc,
+ .rpc_pipe = NULL,
+ .description = "Delete share",
+ .usage = "",
+ },
+
+ {
+ .name = NULL,
+ }
+};
diff --git a/source3/rpcclient/cmd_unixinfo.c b/source3/rpcclient/cmd_unixinfo.c
new file mode 100644
index 0000000..16f13a2
--- /dev/null
+++ b/source3/rpcclient/cmd_unixinfo.c
@@ -0,0 +1,141 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_unixinfo_c.h"
+#include "libcli/security/dom_sid.h"
+
+static NTSTATUS cmd_unixinfo_uidtosid(
+ struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ uint64_t uid = 0;
+ struct dom_sid sid = { .sid_rev_num = 0, };
+ struct dom_sid_buf buf;
+ NTSTATUS status, result;
+
+ if (argc != 2) {
+ printf("Usage: %s [uid]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+ uid = atoi(argv[1]);
+
+ status = dcerpc_unixinfo_UidToSid(b, mem_ctx, uid, &sid, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "dcerpc_unixinfo_UidToSid failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ fprintf(stderr,
+ "dcerpc_unixinfo_UidToSid returned: %s\n",
+ nt_errstr(result));
+ status = result;
+ goto done;
+ }
+
+ printf("UidToSid(%"PRIu64")=%s\n",
+ uid,
+ dom_sid_str_buf(&sid, &buf));
+
+done:
+ return status;
+}
+
+static NTSTATUS cmd_unixinfo_getpwuid(
+ struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ uint32_t count = 1;
+ uint64_t uids = 0;
+ struct unixinfo_GetPWUidInfo infos = { .homedir = NULL, };
+ NTSTATUS status, result;
+
+ if (argc != 2) {
+ printf("Usage: %s [uid]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+ uids = atoi(argv[1]);
+
+ status = dcerpc_unixinfo_GetPWUid(
+ b, mem_ctx, &count, &uids, &infos, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "dcerpc_unixinfo_GetPWUid failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ fprintf(stderr,
+ "dcerpc_unixinfo_GetPWUid returned: %s\n",
+ nt_errstr(result));
+ return result;
+ }
+
+ printf("status=%s, homedir=%s, shell=%s\n",
+ nt_errstr(infos.status),
+ infos.homedir,
+ infos.shell);
+
+done:
+ return status;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set unixinfo_commands[] = {
+
+ {
+ .name = "UNIXINFO",
+ },
+
+ {
+ .name = "getpwuid",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_unixinfo_getpwuid,
+ .wfn = NULL,
+ .table = &ndr_table_unixinfo,
+ .rpc_pipe = NULL,
+ .description = "Get shell and homedir",
+ .usage = "",
+ },
+ {
+ .name = "uidtosid",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_unixinfo_uidtosid,
+ .wfn = NULL,
+ .table = &ndr_table_unixinfo,
+ .rpc_pipe = NULL,
+ .description = "Convert uid to sid",
+ .usage = "",
+ },
+ {
+ .name = NULL
+ },
+};
diff --git a/source3/rpcclient/cmd_winreg.c b/source3/rpcclient/cmd_winreg.c
new file mode 100644
index 0000000..26fa146
--- /dev/null
+++ b/source3/rpcclient/cmd_winreg.c
@@ -0,0 +1,491 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Guenther Deschner 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "../librpc/gen_ndr/ndr_misc.h"
+
+static WERROR cmd_winreg_enumkeys(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct policy_handle handle;
+ uint32_t enum_index = 0;
+ struct winreg_StringBuf name;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc < 2) {
+ printf("usage: %s [name]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ status = dcerpc_winreg_OpenHKLM(b, mem_ctx,
+ NULL,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ ZERO_STRUCT(name);
+
+ name.name = argv[1];
+ name.length = strlen_m_term_null(name.name)*2;
+ name.size = name.length;
+
+ status = dcerpc_winreg_EnumKey(b, mem_ctx,
+ &handle,
+ enum_index,
+ &name,
+ NULL,
+ NULL,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ return WERR_OK;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static WERROR pull_winreg_Data(TALLOC_CTX *mem_ctx,
+ const DATA_BLOB *blob,
+ union winreg_Data *data,
+ enum winreg_Type type)
+{
+ enum ndr_err_code ndr_err;
+ ndr_err = ndr_pull_union_blob(blob, mem_ctx, data, type,
+ (ndr_pull_flags_fn_t)ndr_pull_winreg_Data);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return WERR_GEN_FAILURE;
+ }
+ return WERR_OK;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+static void display_winreg_data(const char *v,
+ enum winreg_Type type,
+ uint8_t *data,
+ uint32_t length)
+{
+ size_t i;
+ union winreg_Data r;
+ DATA_BLOB blob = data_blob_const(data, length);
+ WERROR result;
+
+ result = pull_winreg_Data(talloc_tos(), &blob, &r, type);
+ if (!W_ERROR_IS_OK(result)) {
+ return;
+ }
+
+ switch (type) {
+ case REG_DWORD:
+ printf("%-20s: REG_DWORD: 0x%08x\n", v, r.value);
+ break;
+ case REG_SZ:
+ printf("%-20s: REG_SZ: %s\n", v, r.string);
+ break;
+ case REG_BINARY: {
+ char *hex = hex_encode_talloc(NULL,
+ r.binary.data, r.binary.length);
+ size_t len;
+ printf("%-20s: REG_BINARY:", v);
+ len = strlen(hex);
+ for (i=0; i<len; i++) {
+ if (hex[i] == '\0') {
+ break;
+ }
+ if (i%40 == 0) {
+ putchar('\n');
+ }
+ putchar(hex[i]);
+ }
+ TALLOC_FREE(hex);
+ putchar('\n');
+ break;
+ }
+ case REG_MULTI_SZ:
+ printf("%-20s: REG_MULTI_SZ: ", v);
+ for (i=0; r.string_array[i] != NULL; i++) {
+ printf("%s ", r.string_array[i]);
+ }
+ printf("\n");
+ break;
+ default:
+ printf("%-20ss: unknown type 0x%02x:\n", v, type);
+ break;
+ }
+}
+
+
+static WERROR cmd_winreg_querymultiplevalues_ex(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv, bool multiplevalues2)
+{
+ NTSTATUS status;
+ WERROR werr;
+ struct policy_handle handle, key_handle;
+ struct winreg_String key_name = { 0, };
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ struct QueryMultipleValue *values_in, *values_out;
+ uint32_t num_values;
+ uint8_t *buffer = NULL;
+ uint32_t i;
+
+
+ if (argc < 2) {
+ printf("usage: %s [key] [value1] [value2] ...\n", argv[0]);
+ return WERR_OK;
+ }
+
+ status = dcerpc_winreg_OpenHKLM(b, mem_ctx,
+ NULL,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ key_name.name = argv[1];
+
+ status = dcerpc_winreg_OpenKey(b, mem_ctx,
+ &handle,
+ key_name,
+ 0, /* options */
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &key_handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ num_values = argc-2;
+
+ values_in = talloc_zero_array(mem_ctx, struct QueryMultipleValue, num_values);
+ if (values_in == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ values_out = talloc_zero_array(mem_ctx, struct QueryMultipleValue, num_values);
+ if (values_out == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (i=0; i < num_values; i++) {
+
+ values_in[i].ve_valuename = talloc_zero(values_in, struct winreg_ValNameBuf);
+ if (values_in[i].ve_valuename == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ values_in[i].ve_valuename->name = talloc_strdup(values_in[i].ve_valuename, argv[i+2]);
+ values_in[i].ve_valuename->length = strlen_m_term_null(values_in[i].ve_valuename->name)*2;
+ values_in[i].ve_valuename->size = values_in[i].ve_valuename->length;
+ }
+
+ if (multiplevalues2) {
+
+ uint32_t offered = 0, needed = 0;
+
+ status = dcerpc_winreg_QueryMultipleValues2(b, mem_ctx,
+ &key_handle,
+ values_in,
+ values_out,
+ num_values,
+ buffer,
+ &offered,
+ &needed,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (W_ERROR_EQUAL(werr, WERR_MORE_DATA)) {
+ offered = needed;
+
+ buffer = talloc_zero_array(mem_ctx, uint8_t, needed);
+ if (buffer == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = dcerpc_winreg_QueryMultipleValues2(b, mem_ctx,
+ &key_handle,
+ values_in,
+ values_out,
+ num_values,
+ buffer,
+ &offered,
+ &needed,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ }
+
+ } else {
+
+ uint32_t buffer_size = 0xff;
+
+ buffer = talloc_zero_array(mem_ctx, uint8_t, buffer_size);
+ if (buffer == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = dcerpc_winreg_QueryMultipleValues(b, mem_ctx,
+ &key_handle,
+ values_in,
+ values_out,
+ num_values,
+ buffer,
+ &buffer_size,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ }
+
+ for (i=0; i < num_values; i++) {
+ if (buffer) {
+ display_winreg_data(values_in[i].ve_valuename->name,
+ values_out[i].ve_type,
+ buffer + values_out[i].ve_valueptr,
+ values_out[i].ve_valuelen);
+ }
+ }
+
+ return WERR_OK;
+}
+
+static WERROR cmd_winreg_querymultiplevalues(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ return cmd_winreg_querymultiplevalues_ex(cli, mem_ctx, argc, argv, false);
+}
+
+static WERROR cmd_winreg_querymultiplevalues2(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ return cmd_winreg_querymultiplevalues_ex(cli, mem_ctx, argc, argv, true);
+}
+
+static WERROR cmd_winreg_enumval(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR werr, ignore;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+ struct policy_handle parent_handle, handle;
+ uint32_t enum_index = 0;
+
+ if (argc < 1 || argc > 3) {
+ printf("usage: %s [name]\n", argv[0]);
+ return WERR_OK;
+ }
+
+ status = dcerpc_winreg_OpenHKLM(b, mem_ctx,
+ NULL,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &parent_handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ if (argc >= 2) {
+
+ struct winreg_String keyname;
+
+ ZERO_STRUCT(keyname);
+
+ keyname.name = argv[1];
+
+ status = dcerpc_winreg_OpenKey(b, mem_ctx,
+ &parent_handle,
+ keyname,
+ 0,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+ } else {
+ handle = parent_handle;
+ }
+
+ do {
+ struct winreg_ValNameBuf name;
+ enum winreg_Type type = REG_NONE;
+ uint32_t size = 0, length = 0;
+ struct winreg_EnumValue r;
+
+ name.name = "";
+ name.size = 1024;
+
+ r.in.handle = &handle;
+ r.in.enum_index = enum_index;
+ r.in.name = &name;
+ r.in.type = &type;
+ r.in.size = &size;
+ r.in.length = &length;
+ r.in.value = talloc_array(mem_ctx, uint8_t, size);
+ if (r.in.value == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+ r.out.name = &name;
+ r.out.type = &type;
+ r.out.size = &size;
+ r.out.length = &length;
+ r.out.value = r.in.value;
+
+ status = dcerpc_winreg_EnumValue_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = r.out.result;
+
+ if (W_ERROR_EQUAL(werr, WERR_MORE_DATA)) {
+ *r.in.size = *r.out.size;
+ r.in.value = talloc_zero_array(mem_ctx, uint8_t, *r.in.size);
+ if (r.in.value == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ status = dcerpc_winreg_EnumValue_r(b, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ werr = r.out.result;
+ }
+ if (!W_ERROR_IS_OK(r.out.result)) {
+ goto done;
+ }
+
+ printf("%02d: ", enum_index++);
+
+ display_winreg_data(r.out.name->name,
+ *r.out.type,
+ r.out.value,
+ *r.out.size);
+
+ } while (W_ERROR_IS_OK(werr));
+
+ done:
+ if (argc >= 2) {
+ dcerpc_winreg_CloseKey(b, mem_ctx, &handle, &ignore);
+ }
+ dcerpc_winreg_CloseKey(b, mem_ctx, &parent_handle, &ignore);
+
+ return werr;
+}
+
+/* List of commands exported by this module */
+
+struct cmd_set winreg_commands[] = {
+
+ {
+ .name = "WINREG",
+ },
+ {
+ .name = "winreg_enumkey",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_winreg_enumkeys,
+ .table = &ndr_table_winreg,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Keys",
+ .usage = "",
+ },
+ {
+ .name = "querymultiplevalues",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_winreg_querymultiplevalues,
+ .table = &ndr_table_winreg,
+ .rpc_pipe = NULL,
+ .description = "Query multiple values",
+ .usage = "",
+ },
+ {
+ .name = "querymultiplevalues2",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_winreg_querymultiplevalues2,
+ .table = &ndr_table_winreg,
+ .rpc_pipe = NULL,
+ .description = "Query multiple values",
+ .usage = "",
+ },
+ {
+ .name = "winreg_enumval",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_winreg_enumval,
+ .table = &ndr_table_winreg,
+ .rpc_pipe = NULL,
+ .description = "Enumerate Values",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ },
+};
diff --git a/source3/rpcclient/cmd_witness.c b/source3/rpcclient/cmd_witness.c
new file mode 100644
index 0000000..987c797
--- /dev/null
+++ b/source3/rpcclient/cmd_witness.c
@@ -0,0 +1,623 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Gregor Beck 2013-2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "librpc/gen_ndr/ndr_witness_c.h"
+#include <popt.h>
+
+/*
+ * We have to use the same connection for each subcommand
+ * for the context handles to be meaningful.
+ */
+static void use_only_one_rpc_pipe_hack(struct rpc_pipe_client *cli);
+
+static WERROR cmd_witness_GetInterfaceList(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR result;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct witness_interfaceList *interface_list = NULL;
+ uint32_t num_interfaces, n;
+ struct witness_interfaceInfo *interfaces;
+
+ use_only_one_rpc_pipe_hack(cli);
+
+ status = dcerpc_witness_GetInterfaceList(cli->binding_handle, frame,
+ &interface_list, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_witness_GetInterfaceList failed, status: %s\n", nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_witness_GetInterfaceList failed, error: %s\n", win_errstr(result)));
+ goto done;
+ }
+
+ SMB_ASSERT(interface_list);
+ interfaces = interface_list->interfaces;
+ num_interfaces = interface_list->num_interfaces;
+
+ for (n=0; n < num_interfaces; n++) {
+ char wif = (interfaces[n].flags & WITNESS_INFO_WITNESS_IF) ? '*' : ' ';
+ char state = 'X';
+
+ if (interfaces[n].state == WITNESS_STATE_AVAILABLE) {
+ state = '+';
+ } else if (interfaces[n].state == WITNESS_STATE_UNAVAILABLE) {
+ state = '-';
+ } else if (interfaces[n].state == WITNESS_STATE_UNKNOWN) {
+ state = '?';
+ }
+
+ d_printf("%c%c %s", wif, state, interfaces[n].group_name);
+
+ if (interfaces[n].flags & WITNESS_INFO_IPv4_VALID) {
+ d_printf(" %s", interfaces[n].ipv4);
+ }
+
+ if (interfaces[n].flags & WITNESS_INFO_IPv6_VALID) {
+ d_printf(" %s", interfaces[n].ipv6);
+ }
+
+ switch (interfaces[n].version) {
+ case WITNESS_V1:
+ d_printf(" V1");
+ break;
+ case WITNESS_V2:
+ d_printf(" V2");
+ break;
+ default:
+ d_printf(" Unsupported Version (0x%08x)", interfaces[n].version);
+ }
+
+ d_printf("\n");
+ }
+
+done:
+ talloc_free(frame);
+ return result;
+}
+
+static WERROR cmd_witness_Register(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ static char hostname[MAXHOSTNAMELEN] = {'\0'};
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct policy_handle hnd;
+ const char *net_name = NULL;
+ const char *ip_addr = NULL;
+ const char *client_name = hostname;
+ long version = WITNESS_V1;
+ int c;
+ poptContext optCon;
+ struct poptOption optionsTable[] = {
+ {
+ .longName = "version",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_LONG|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &version,
+ .val = WITNESS_V2,
+ .descrip = "witness version",
+ .argDescrip = "version"
+ },
+ {
+ .longName = "V1",
+ .shortName = '1',
+ .argInfo = POPT_ARG_LONG|POPT_ARG_VAL,
+ .arg = &version,
+ .val = WITNESS_V1,
+ .descrip = "witness version 1",
+ .argDescrip = NULL
+ },
+ {
+ .longName = "V2",
+ .shortName = '2',
+ .argInfo = POPT_ARG_LONG|POPT_ARG_VAL,
+ .arg = &version,
+ .val = WITNESS_V2,
+ .descrip = "witness version 2",
+ .argDescrip = NULL
+ },
+ {
+ .longName = "net",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &net_name,
+ .val = 0,
+ .descrip = "net name",
+ .argDescrip = NULL
+ },
+ {
+ .longName = "ip",
+ .shortName = 'i',
+ .argInfo = POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &ip_addr,
+ .val = 0,
+ .descrip = "ip address",
+ .argDescrip = NULL
+ },
+ {
+ .longName = "client",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT|POPT_ARGFLAG_OPTIONAL,
+ .arg = &client_name,
+ .val = 0,
+ .descrip = "client name",
+ .argDescrip = NULL
+ },
+ POPT_TABLEEND
+ };
+
+ use_only_one_rpc_pipe_hack(cli);
+
+ if (hostname[0] == '\0') {
+ gethostname (hostname, sizeof(hostname));
+ }
+
+ optCon = poptGetContext(NULL, argc, argv, optionsTable, 0);
+
+ while ((c = poptGetNextOpt(optCon)) >= 0) { }
+
+ if (c < -1) {
+ /* an error occurred during option processing */
+ d_fprintf(stderr, "%s: %s\n",
+ poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
+ poptStrerror(c));
+ goto done;
+ }
+
+ if (argc < 2 || poptPeekArg(optCon) != NULL) {
+ poptPrintHelp(optCon, stderr, 0);
+ goto done;
+ }
+
+ status = dcerpc_witness_Register(cli->binding_handle, frame,
+ &hnd,
+ version,
+ net_name, ip_addr, client_name,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_witness_Register failed, status: %s\n", nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_witness_Register failed, error: %s\n", win_errstr(result)));
+ goto done;
+ }
+
+ d_printf("%x:%s\n", hnd.handle_type, GUID_string(frame, &hnd.uuid));
+
+done:
+ poptFreeContext(optCon);
+ talloc_free(frame);
+ return result;
+}
+
+static WERROR cmd_witness_RegisterEx(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ static char hostname[MAXHOSTNAMELEN] = {'\0'};
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct policy_handle hnd;
+ const char *net_name = NULL;
+ const char *ip_addr = NULL;
+ const char *share_name = NULL;
+ const char *client_name = hostname;
+ long version = WITNESS_V2;
+ long flags = 0;
+ long timeout = 0;
+ int c;
+ poptContext optCon;
+ struct poptOption optionsTable[] = {
+ {
+ .longName = "version",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_LONG|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &version,
+ .val = WITNESS_V2,
+ .descrip = "witness version",
+ .argDescrip = "version"
+ },
+ {
+ .longName = "V1",
+ .shortName = '1',
+ .argInfo = POPT_ARG_LONG|POPT_ARG_VAL,
+ .arg = &version,
+ .val = WITNESS_V1,
+ .descrip = "witness version 1",
+ },
+ {
+ .longName = "V2",
+ .shortName = '2',
+ .argInfo = POPT_ARG_LONG|POPT_ARG_VAL,
+ .arg = &version,
+ .val = WITNESS_V2,
+ .descrip = "witness version 2",
+ },
+ {
+ .longName = "net",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &net_name,
+ .val = 0,
+ .descrip = "net name",
+ },
+ {
+ .longName = "ip",
+ .shortName = 'i',
+ .argInfo = POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &ip_addr,
+ .val = 0,
+ .descrip = "ip address",
+ },
+ {
+ .longName = "share",
+ .shortName = 's',
+ .argInfo = POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &share_name,
+ .val = 0,
+ .descrip = "share name",
+ },
+ {
+ .longName = "client",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING|POPT_ARGFLAG_SHOW_DEFAULT|POPT_ARGFLAG_OPTIONAL,
+ .arg = &client_name,
+ .val = 0,
+ .descrip = "client name",
+ },
+ {
+ .longName = "flags",
+ .shortName = 'f',
+ .argInfo = POPT_ARG_LONG|POPT_ARGFLAG_OR|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &flags,
+ .val = 0,
+ .descrip = "flags",
+ },
+ {
+ .longName = "timeout",
+ .shortName = 't',
+ .argInfo = POPT_ARG_LONG|POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &timeout,
+ .val = 0,
+ .descrip = "timeout",
+ },
+ POPT_TABLEEND
+ };
+
+ use_only_one_rpc_pipe_hack(cli);
+
+ if (hostname[0] == '\0') {
+ gethostname (hostname, sizeof(hostname));
+ }
+
+ optCon = poptGetContext(NULL, argc, argv, optionsTable, 0);
+
+ while ((c = poptGetNextOpt(optCon)) >= 0) { }
+
+ if (c < -1) {
+ /* an error occurred during option processing */
+ d_fprintf(stderr, "%s: %s\n",
+ poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
+ poptStrerror(c));
+ goto done;
+ }
+
+ if (argc < 2 || poptPeekArg(optCon) != NULL) {
+ poptPrintHelp(optCon, stderr, 0);
+ goto done;
+ }
+
+ status = dcerpc_witness_RegisterEx(cli->binding_handle, frame,
+ &hnd,
+ version,
+ net_name, share_name, ip_addr, client_name,
+ flags, timeout,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_witness_RegisterEx failed, status: %s\n", nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_witness_RegisterEx failed, error: %s\n", win_errstr(result)));
+ goto done;
+ }
+
+ d_printf("%x:%s\n", hnd.handle_type, GUID_string(frame, &hnd.uuid));
+
+done:
+ poptFreeContext(optCon);
+ talloc_free(frame);
+ return result;
+}
+
+static bool
+read_context_handle(const char *str, struct policy_handle *hnd)
+{
+ NTSTATUS status;
+ long type;
+ char *pos;
+ struct GUID guid;
+
+ type = strtol(str, &pos, 16);
+ if (*pos != ':') {
+ DEBUG(0, ("read_context_handle: failed to parse type\n"));
+ return false;
+ }
+ status = GUID_from_string(pos+1, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("read_context_handle: failed to parse guid %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ hnd->handle_type = type;
+ hnd->uuid = guid;
+ return true;
+}
+
+static WERROR cmd_witness_UnRegister(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct policy_handle hnd;
+
+ use_only_one_rpc_pipe_hack(cli);
+
+ if (argc != 2) {
+ d_printf("%s <context_handle>\n", argv[0]);
+ goto done;
+ }
+
+ if (!read_context_handle(argv[1], &hnd)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ status = dcerpc_witness_UnRegister(cli->binding_handle, frame,
+ hnd, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_witness_UnRegister failed, status: %s\n", nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_witness_UnRegister failed, error: %s\n", win_errstr(result)));
+ goto done;
+ }
+
+done:
+ talloc_free(frame);
+ return result;
+}
+
+static void print_notify_response_resource_change(struct witness_ResourceChange *r)
+{
+ const char *type_str;
+
+ if (r->type == WITNESS_RESOURCE_STATE_UNKNOWN) {
+ type_str = "Unknown";
+ } else if (r->type == WITNESS_RESOURCE_STATE_AVAILABLE) {
+ type_str = "Available\n";
+ } else if (r->type == WITNESS_RESOURCE_STATE_UNAVAILABLE) {
+ type_str = "Unavailable";
+ } else {
+ type_str = talloc_asprintf(r, "Invalid (%u)", r->type);
+ }
+ d_printf("%s -> %s\n", r->name, type_str);
+}
+
+static void print_notify_response_ip_addr_info_list(struct witness_IPaddrInfoList *r)
+{
+ int i;
+
+ for (i=0; i < r->num; i++) {
+ uint32_t flags = r->addr[i].flags;
+ const char *str4 = r->addr[i].ipv4;
+ const char *str6 = r->addr[i].ipv6;
+
+ d_printf("Flags 0x%08x", flags);
+ if (flags & WITNESS_IPADDR_V4) {
+ d_printf(" %s", str4);
+ }
+ if (flags & WITNESS_IPADDR_V6) {
+ d_printf(" %s", str6);
+ }
+ if (flags & WITNESS_IPADDR_ONLINE) {
+ d_printf(" Online");
+ }
+ if (flags & WITNESS_IPADDR_ONLINE) {
+ d_printf(" Offline");
+ }
+ d_printf("\n");
+ }
+}
+
+static void print_notify_response(union witness_notifyResponse_message *r,
+ uint32_t type)
+{
+ switch (type) {
+ case WITNESS_NOTIFY_RESOURCE_CHANGE:
+ print_notify_response_resource_change(&r->resource_change);
+ break;
+ case WITNESS_NOTIFY_CLIENT_MOVE:
+ case WITNESS_NOTIFY_SHARE_MOVE:
+ case WITNESS_NOTIFY_IP_CHANGE:
+ print_notify_response_ip_addr_info_list(&r->client_move);
+ break;
+ default:
+ break;
+ }
+}
+
+static WERROR cmd_witness_AsyncNotify(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct policy_handle hnd;
+ struct witness_notifyResponse *response = NULL;
+ uint32_t timeout;
+ int i;
+
+ use_only_one_rpc_pipe_hack(cli);
+
+ if (argc != 2) {
+ d_printf("%s <context_handle>\n", argv[0]);
+ goto done;
+ }
+
+ if (!read_context_handle(argv[1], &hnd)) {
+ result = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ timeout = dcerpc_binding_handle_set_timeout(cli->binding_handle, UINT32_MAX);
+ status = dcerpc_witness_AsyncNotify(cli->binding_handle, frame, hnd,
+ &response, &result);
+ dcerpc_binding_handle_set_timeout(cli->binding_handle, timeout);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_witness_AsyncNotify failed, status: %s\n", nt_errstr(status)));
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("dcerpc_witness_AsyncNotify failed, error: %s\n", win_errstr(result)));
+ goto done;
+ }
+
+ if (response == NULL) {
+ d_printf("Got an empty response\n");
+ goto done;
+ }
+
+ switch(response->type) {
+ case WITNESS_NOTIFY_RESOURCE_CHANGE:
+ d_printf("Resource change");
+ break;
+ case WITNESS_NOTIFY_CLIENT_MOVE:
+ d_printf("Client move");
+ break;
+ case WITNESS_NOTIFY_SHARE_MOVE:
+ d_printf("Share move");
+ break;
+ case WITNESS_NOTIFY_IP_CHANGE:
+ d_printf("IP change");
+ break;
+ default:
+ d_printf("Unknown (0x%x)", (int)response->type);
+ }
+ d_printf(" with %d messages\n", response->num);
+
+ for (i=0; i < response->num; i++) {
+ print_notify_response(&response->messages[i], response->type);
+ }
+done:
+ talloc_free(frame);
+ return result;
+}
+
+struct cmd_set witness_commands[] = {
+ {
+ .name = "WITNESS",
+ },
+ {
+ .name = "GetInterfaceList",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = &cmd_witness_GetInterfaceList,
+ .table = &ndr_table_witness,
+ .rpc_pipe = NULL,
+ .description = "List the interfaces to which witness client connections can be made",
+ .usage = "",
+ },
+ {
+ .name = "Register",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = &cmd_witness_Register,
+ .table = &ndr_table_witness,
+ .rpc_pipe = NULL,
+ .description = "Register for resource state change notifications of a NetName and IPAddress",
+ .usage = "",
+ },
+ {
+ .name = "UnRegister",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = &cmd_witness_UnRegister,
+ .table = &ndr_table_witness,
+ .rpc_pipe = NULL,
+ .description = "Unregister for notifications from the server</para></listitem></varlistentry>",
+ .usage = "",
+ },
+ {
+ .name = "AsyncNotify",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = &cmd_witness_AsyncNotify,
+ .table = &ndr_table_witness,
+ .rpc_pipe = NULL,
+ .description = "Request notification of registered resource changes from the server",
+ .usage = "",
+ },
+ {
+ .name = "RegisterEx",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = &cmd_witness_RegisterEx,
+ .table = &ndr_table_witness,
+ .rpc_pipe = NULL,
+ .description = "Register for resource state change notifications of a NetName, ShareName and multiple IPAddresses",
+ .usage = "",
+ },
+ {
+ .name = NULL,
+ }
+};
+
+/*
+ * We have to use the same connection for each subcommand
+ * for the context handles to be meaningful.
+ */
+static void use_only_one_rpc_pipe_hack(struct rpc_pipe_client *cli)
+{
+ struct cmd_set *ptr;
+
+ for (ptr = &witness_commands[0]; ptr->name; ptr++) {
+ ptr->rpc_pipe = cli;
+ }
+}
diff --git a/source3/rpcclient/cmd_wkssvc.c b/source3/rpcclient/cmd_wkssvc.c
new file mode 100644
index 0000000..b1ffd5f
--- /dev/null
+++ b/source3/rpcclient/cmd_wkssvc.c
@@ -0,0 +1,274 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Günther Deschner 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "rpcclient.h"
+#include "../librpc/gen_ndr/ndr_wkssvc_c.h"
+
+static WERROR cmd_wkssvc_wkstagetinfo(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t level = 100;
+ union wkssvc_NetWkstaInfo info;
+ const char *server_name;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 2) {
+ printf("usage: %s <level>\n", argv[0]);
+ return WERR_OK;
+ }
+
+ if (argc > 1) {
+ level = atoi(argv[1]);
+ }
+
+ server_name = cli->desthost;
+
+ status = dcerpc_wkssvc_NetWkstaGetInfo(b, mem_ctx,
+ server_name,
+ level,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_wkssvc_getjoininformation(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ const char *server_name;
+ const char *name_buffer;
+ enum wkssvc_NetJoinStatus name_type;
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ server_name = cli->desthost;
+ name_buffer = "";
+
+ status = dcerpc_wkssvc_NetrGetJoinInformation(b, mem_ctx,
+ server_name,
+ &name_buffer,
+ &name_type,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ printf("%s (%d)\n", name_buffer, name_type);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_wkssvc_messagebuffersend(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ const char *server_name = cli->desthost;
+ const char *message_name = cli->desthost;
+ const char *message_sender_name = cli->desthost;
+ smb_ucs2_t *message_buffer = NULL;
+ size_t message_size = 0;
+ const char *message = "my message";
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ if (argc > 1) {
+ message = argv[1];
+ }
+
+ if (!push_ucs2_talloc(mem_ctx, &message_buffer, message,
+ &message_size))
+ {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = dcerpc_wkssvc_NetrMessageBufferSend(b, mem_ctx,
+ server_name,
+ message_name,
+ message_sender_name,
+ (uint8_t *)message_buffer,
+ message_size,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ return werr;
+}
+
+static WERROR cmd_wkssvc_enumeratecomputernames(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ const char *server_name;
+ enum wkssvc_ComputerNameType name_type = NetAllComputerNames;
+ NTSTATUS status;
+ struct wkssvc_ComputerNamesCtr *ctr = NULL;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ server_name = cli->desthost;
+
+ if (argc >= 2) {
+ name_type = atoi(argv[1]);
+ }
+
+ status = dcerpc_wkssvc_NetrEnumerateComputerNames(b, mem_ctx,
+ server_name,
+ name_type, 0,
+ &ctr,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ int i=0;
+ for (i = 0; i < ctr->count; i++) {
+ printf("name: %d %s\n", i, ctr->computer_name->string);
+ }
+ }
+
+ return werr;
+}
+
+static WERROR cmd_wkssvc_enumerateusers(struct rpc_pipe_client *cli,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ const char *server_name;
+ NTSTATUS status;
+ struct wkssvc_NetWkstaEnumUsersInfo info;
+ WERROR werr;
+ uint32_t i, num_entries, resume_handle;
+ struct dcerpc_binding_handle *b = cli->binding_handle;
+
+ server_name = cli->desthost;
+
+ ZERO_STRUCT(info);
+
+ if (argc >= 2) {
+ info.level = atoi(argv[1]);
+ }
+
+ status = dcerpc_wkssvc_NetWkstaEnumUsers(b, mem_ctx, server_name,
+ &info, 1000, &num_entries,
+ &resume_handle, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werr;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ const char *user = NULL;
+ switch (info.level) {
+ case 0:
+ user = info.ctr.user0->user0[i].user_name;
+ break;
+ case 1:
+ user = talloc_asprintf(
+ talloc_tos(), "%s\\%s",
+ info.ctr.user1->user1[i].logon_domain,
+ info.ctr.user1->user1[i].user_name);
+ break;
+ }
+ printf("%s\n", user ? user : "(null)");
+ }
+
+ return werr;
+}
+
+struct cmd_set wkssvc_commands[] = {
+
+ {
+ .name = "WKSSVC",
+ },
+ {
+ .name = "wkssvc_wkstagetinfo",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_wkssvc_wkstagetinfo,
+ .table = &ndr_table_wkssvc,
+ .rpc_pipe = NULL,
+ .description = "Query WKSSVC Workstation Information",
+ .usage = "",
+ },
+ {
+ .name = "wkssvc_getjoininformation",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_wkssvc_getjoininformation,
+ .table = &ndr_table_wkssvc,
+ .rpc_pipe = NULL,
+ .description = "Query WKSSVC Join Information",
+ .usage = "",
+ },
+ {
+ .name = "wkssvc_messagebuffersend",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_wkssvc_messagebuffersend,
+ .table = &ndr_table_wkssvc,
+ .rpc_pipe = NULL,
+ .description = "Send WKSSVC message",
+ .usage = "",
+ },
+ {
+ .name = "wkssvc_enumeratecomputernames",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_wkssvc_enumeratecomputernames,
+ .table = &ndr_table_wkssvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate WKSSVC computer names",
+ .usage = "",
+ },
+ {
+ .name = "wkssvc_enumerateusers",
+ .returntype = RPC_RTYPE_WERROR,
+ .ntfn = NULL,
+ .wfn = cmd_wkssvc_enumerateusers,
+ .table = &ndr_table_wkssvc,
+ .rpc_pipe = NULL,
+ .description = "Enumerate WKSSVC users",
+ .usage = "",
+ },
+ {
+ .name =NULL,
+ },
+};
diff --git a/source3/rpcclient/rpcclient.c b/source3/rpcclient/rpcclient.c
new file mode 100644
index 0000000..f59bf6b
--- /dev/null
+++ b/source3/rpcclient/rpcclient.c
@@ -0,0 +1,1389 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Tim Potter 2000-2001
+ Copyright (C) Martin Pool 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../libcli/auth/netlogon_creds_cli.h"
+#include "rpcclient.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "rpc_client/cli_netlogon.h"
+#include "../libcli/smbreadline/smbreadline.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "libsmb/libsmb.h"
+#include "auth/gensec/gensec.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "messages.h"
+#include "cmdline_contexts.h"
+#include "../librpc/gen_ndr/ndr_samr.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/param/param.h"
+
+enum pipe_auth_type_spnego {
+ PIPE_AUTH_TYPE_SPNEGO_NONE = 0,
+ PIPE_AUTH_TYPE_SPNEGO_NTLMSSP,
+ PIPE_AUTH_TYPE_SPNEGO_KRB5
+};
+
+static unsigned int timeout = 10000;
+
+struct messaging_context *rpcclient_msg_ctx;
+struct netlogon_creds_cli_context *rpcclient_netlogon_creds;
+static const char *rpcclient_netlogon_domain;
+
+/* List to hold groups of commands.
+ *
+ * Commands are defined in a list of arrays: arrays are easy to
+ * statically declare, and lists are easier to dynamically extend.
+ */
+
+static struct cmd_list {
+ struct cmd_list *prev, *next;
+ struct cmd_set *cmd_set;
+} *cmd_list;
+
+/****************************************************************************
+handle completion of commands for readline
+****************************************************************************/
+static char **completion_fn(const char *text, int start, int end)
+{
+#define MAX_COMPLETIONS 1000
+ char **matches;
+ size_t i, count=0;
+ struct cmd_list *commands = cmd_list;
+
+#if 0 /* JERRY */
+ /* FIXME!!! -- what to do when completing argument? */
+ /* for words not at the start of the line fallback
+ to filename completion */
+ if (start)
+ return NULL;
+#endif
+
+ /* make sure we have a list of valid commands */
+ if (!commands) {
+ return NULL;
+ }
+
+ matches = SMB_MALLOC_ARRAY(char *, MAX_COMPLETIONS);
+ if (!matches) {
+ return NULL;
+ }
+
+ matches[count++] = SMB_STRDUP(text);
+ if (!matches[0]) {
+ SAFE_FREE(matches);
+ return NULL;
+ }
+
+ while (commands && count < MAX_COMPLETIONS-1) {
+ if (!commands->cmd_set) {
+ break;
+ }
+
+ for (i=0; commands->cmd_set[i].name; i++) {
+ if ((strncmp(text, commands->cmd_set[i].name, strlen(text)) == 0) &&
+ (( commands->cmd_set[i].returntype == RPC_RTYPE_NTSTATUS &&
+ commands->cmd_set[i].ntfn ) ||
+ ( commands->cmd_set[i].returntype == RPC_RTYPE_WERROR &&
+ commands->cmd_set[i].wfn))) {
+ matches[count] = SMB_STRDUP(commands->cmd_set[i].name);
+ if (!matches[count]) {
+ for (i = 0; i < count; i++) {
+ SAFE_FREE(matches[count]);
+ }
+ SAFE_FREE(matches);
+ return NULL;
+ }
+ count++;
+ }
+ }
+ commands = commands->next;
+ }
+
+ if (count == 2) {
+ SAFE_FREE(matches[0]);
+ matches[0] = SMB_STRDUP(matches[1]);
+ }
+ matches[count] = NULL;
+ return matches;
+}
+
+static char *next_command (char **cmdstr)
+{
+ char *command;
+ char *p;
+
+ if (!cmdstr || !(*cmdstr))
+ return NULL;
+
+ p = strchr_m(*cmdstr, ';');
+ if (p)
+ *p = '\0';
+ command = SMB_STRDUP(*cmdstr);
+ if (p)
+ *cmdstr = p + 1;
+ else
+ *cmdstr = NULL;
+
+ return command;
+}
+
+static void binding_get_auth_info(
+ const struct dcerpc_binding *b,
+ enum dcerpc_AuthType *_auth_type,
+ enum dcerpc_AuthLevel *_auth_level,
+ enum credentials_use_kerberos *_krb5_state)
+{
+ uint32_t bflags = dcerpc_binding_get_flags(b);
+ enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE;
+ enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE;
+ enum credentials_use_kerberos krb5_state = CRED_USE_KERBEROS_DESIRED;
+
+ if (_krb5_state != NULL) {
+ krb5_state = *_krb5_state;
+ }
+
+ if (bflags & DCERPC_CONNECT) {
+ auth_level = DCERPC_AUTH_LEVEL_CONNECT;
+ }
+ if (bflags & DCERPC_PACKET) {
+ auth_level = DCERPC_AUTH_LEVEL_PACKET;
+ }
+ if (bflags & DCERPC_SIGN) {
+ auth_level = DCERPC_AUTH_LEVEL_INTEGRITY;
+ }
+ if (bflags & DCERPC_SEAL) {
+ auth_level = DCERPC_AUTH_LEVEL_PRIVACY;
+ }
+
+ if (bflags & DCERPC_SCHANNEL) {
+ auth_type = DCERPC_AUTH_TYPE_SCHANNEL;
+ }
+
+ if ((auth_level != DCERPC_AUTH_LEVEL_NONE) &&
+ (auth_type == DCERPC_AUTH_TYPE_NONE)) {
+ auth_type = (krb5_state == CRED_USE_KERBEROS_REQUIRED) ?
+ DCERPC_AUTH_TYPE_KRB5 : DCERPC_AUTH_TYPE_NTLMSSP;
+ }
+
+ if (bflags & DCERPC_AUTH_SPNEGO) {
+ auth_type = DCERPC_AUTH_TYPE_SPNEGO;
+
+ if (bflags & DCERPC_AUTH_NTLM) {
+ krb5_state = CRED_USE_KERBEROS_DISABLED;
+ }
+ if (bflags & DCERPC_AUTH_KRB5) {
+ krb5_state = CRED_USE_KERBEROS_REQUIRED;
+ }
+ }
+
+ if (auth_type != DCERPC_AUTH_TYPE_NONE) {
+ /* If nothing is requested then default to integrity */
+ if (auth_level == DCERPC_AUTH_LEVEL_NONE) {
+ auth_level = DCERPC_AUTH_LEVEL_INTEGRITY;
+ }
+ }
+
+ if (_auth_type != NULL) {
+ *_auth_type = auth_type;
+ }
+ if (_auth_level != NULL) {
+ *_auth_level = auth_level;
+ }
+ if (_krb5_state != NULL) {
+ *_krb5_state = krb5_state;
+ }
+}
+
+/* List the available commands on a given pipe */
+
+static NTSTATUS cmd_listcommands(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct cmd_list *tmp;
+ struct cmd_set *tmp_set;
+ int i;
+
+ /* Usage */
+
+ if (argc != 2) {
+ printf("Usage: %s <pipe>\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ /* Help on one command */
+
+ for (tmp = cmd_list; tmp; tmp = tmp->next)
+ {
+ tmp_set = tmp->cmd_set;
+
+ if (!strcasecmp_m(argv[1], tmp_set->name))
+ {
+ printf("Available commands on the %s pipe:\n\n", tmp_set->name);
+
+ i = 0;
+ tmp_set++;
+ while(tmp_set->name) {
+ printf("%30s", tmp_set->name);
+ tmp_set++;
+ i++;
+ if (i%3 == 0)
+ printf("\n");
+ }
+
+ /* drop out of the loop */
+ break;
+ }
+ }
+ printf("\n\n");
+
+ return NT_STATUS_OK;
+}
+
+/* Display help on commands */
+
+static NTSTATUS cmd_help(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct cmd_list *tmp;
+ struct cmd_set *tmp_set;
+
+ /* Usage */
+
+ if (argc > 2) {
+ printf("Usage: %s [command]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ /* Help on one command */
+
+ if (argc == 2) {
+ for (tmp = cmd_list; tmp; tmp = tmp->next) {
+
+ tmp_set = tmp->cmd_set;
+
+ while(tmp_set->name) {
+ if (strequal(argv[1], tmp_set->name)) {
+ if (tmp_set->usage &&
+ tmp_set->usage[0])
+ printf("%s\n", tmp_set->usage);
+ else
+ printf("No help for %s\n", tmp_set->name);
+
+ return NT_STATUS_OK;
+ }
+
+ tmp_set++;
+ }
+ }
+
+ printf("No such command: %s\n", argv[1]);
+ return NT_STATUS_OK;
+ }
+
+ /* List all commands */
+
+ for (tmp = cmd_list; tmp; tmp = tmp->next) {
+
+ tmp_set = tmp->cmd_set;
+
+ while(tmp_set->name) {
+
+ printf("%15s\t\t%s\n", tmp_set->name,
+ tmp_set->description ? tmp_set->description:
+ "");
+
+ tmp_set++;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* Change the debug level */
+
+static NTSTATUS cmd_debuglevel(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ if (argc > 2) {
+ printf("Usage: %s [debuglevel]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2) {
+ struct loadparm_context *lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", argv[1]);
+ }
+
+ printf("debuglevel is %d\n", DEBUGLEVEL);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_quit(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ exit(0);
+ return NT_STATUS_OK; /* NOTREACHED */
+}
+
+static NTSTATUS cmd_set_ss_level(struct dcerpc_binding *binding)
+{
+ struct cmd_list *tmp;
+ enum dcerpc_AuthType auth_type;
+ enum dcerpc_AuthLevel auth_level;
+
+ /* Close any existing connections not at this level. */
+
+ binding_get_auth_info(binding, &auth_type, &auth_level, NULL);
+
+ for (tmp = cmd_list; tmp; tmp = tmp->next) {
+ struct cmd_set *tmp_set;
+
+ for (tmp_set = tmp->cmd_set; tmp_set->name; tmp_set++) {
+ if (tmp_set->rpc_pipe == NULL) {
+ continue;
+ }
+
+ if ((tmp_set->rpc_pipe->auth->auth_type
+ != auth_type)
+ || (tmp_set->rpc_pipe->auth->auth_level
+ != auth_level)) {
+ TALLOC_FREE(tmp_set->rpc_pipe);
+ tmp_set->rpc_pipe = NULL;
+ }
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_set_transport(struct dcerpc_binding *b)
+{
+ enum dcerpc_transport_t t = dcerpc_binding_get_transport(b);
+ struct cmd_list *tmp;
+
+ /* Close any existing connections not at this level. */
+
+ for (tmp = cmd_list; tmp; tmp = tmp->next) {
+ struct cmd_set *tmp_set;
+
+ for (tmp_set = tmp->cmd_set; tmp_set->name; tmp_set++) {
+ if (tmp_set->rpc_pipe == NULL) {
+ continue;
+ }
+
+ if (tmp_set->rpc_pipe->transport->transport != t) {
+ TALLOC_FREE(tmp_set->rpc_pipe);
+ tmp_set->rpc_pipe = NULL;
+ }
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS binding_reset_auth(struct dcerpc_binding *b)
+{
+ NTSTATUS status = dcerpc_binding_set_flags(
+ b,
+ 0,
+ DCERPC_PACKET|
+ DCERPC_CONNECT|
+ DCERPC_SIGN|
+ DCERPC_SEAL|
+ DCERPC_SCHANNEL|
+ DCERPC_AUTH_SPNEGO|
+ DCERPC_AUTH_KRB5|
+ DCERPC_AUTH_NTLM);
+ return status;
+}
+
+static NTSTATUS binding_set_auth(
+ struct dcerpc_binding *b, const char *level, const char *type)
+{
+ NTSTATUS status;
+
+ status = binding_reset_auth(b);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (level != NULL) {
+ status = dcerpc_binding_set_string_option(b, level, level);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (strequal(type, "SPNEGO")) {
+ status = dcerpc_binding_set_string_option(
+ b, "spnego", "spnego");
+ return status;
+ }
+ if (strequal(type, "NTLMSSP")) {
+ status = dcerpc_binding_set_string_option(b, "ntlm", "ntlm");
+ return status;
+ }
+ if (strequal(type, "NTLMSSP_SPNEGO")) {
+ status = dcerpc_binding_set_string_option(
+ b, "spnego", "spnego");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = dcerpc_binding_set_string_option(b, "ntlm", "ntlm");
+ return status;
+ }
+ if (strequal(type, "KRB5")) {
+ status = dcerpc_binding_set_string_option(b, "krb5", "krb5");
+ return status;
+ }
+ if (strequal(type, "KRB5_SPNEGO")) {
+ status = dcerpc_binding_set_string_option(
+ b, "spnego", "spnego");
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = dcerpc_binding_set_string_option(b, "krb5", "krb5");
+ return status;
+ }
+ if (strequal(type, "SCHANNEL")) {
+ status = dcerpc_binding_set_string_option(
+ b, "schannel", "schannel");
+ return status;
+ }
+
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+static NTSTATUS cmd_set_auth(
+ struct dcerpc_binding *binding,
+ const char *level,
+ const char *display,
+ int argc,
+ const char **argv)
+{
+ const char *p = "[KRB5|KRB5_SPNEGO|NTLMSSP|NTLMSSP_SPNEGO|SCHANNEL]";
+ const char *type = "NTLMSSP";
+ NTSTATUS status;
+
+ if (argc > 2) {
+ printf("Usage: %s %s\n", argv[0], p);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2) {
+ type = argv[1];
+ }
+
+ status = binding_set_auth(binding, level, type);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Usage: %s %s\n", argv[0], p);
+ return status;
+ }
+
+ d_printf("Setting %s - %s: %s\n", type, display, nt_errstr(status));
+
+ status = cmd_set_ss_level(binding);
+ return status;
+}
+
+static NTSTATUS cmd_sign(
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status = cmd_set_auth(binding, "sign", "sign", argc, argv);
+ return status;
+}
+
+static NTSTATUS cmd_seal(
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status = cmd_set_auth(
+ binding, "seal", "sign and seal", argc, argv);
+ return status;
+}
+
+static NTSTATUS cmd_packet(
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status = cmd_set_auth(
+ binding, "packet", "packet", argc, argv);
+ return status;
+}
+
+static NTSTATUS cmd_timeout(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ if (argc > 2) {
+ printf("Usage: %s timeout\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2) {
+ timeout = atoi(argv[1]);
+ }
+
+ printf("timeout is %d\n", timeout);
+
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_none(
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status = binding_reset_auth(binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = cmd_set_ss_level(binding);
+ return status;
+}
+
+static NTSTATUS cmd_schannel(
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **_argv)
+{
+ const char *argv[] = { "schannel", "SCHANNEL" };
+ NTSTATUS status = cmd_set_auth(
+ binding, "seal", "sign and seal", 2, argv);
+ return status;
+}
+
+static NTSTATUS cmd_schannel_sign(
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **_argv)
+{
+ const char *argv[] = { "schannel_sign", "SCHANNEL" };
+ NTSTATUS status = cmd_set_auth(binding, "sign", "sign only", 2, argv);
+ return status;
+}
+
+static NTSTATUS cmd_choose_transport(
+ struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ enum dcerpc_transport_t transport;
+
+ if (argc != 2) {
+ printf("Usage: %s [NCACN_NP|NCACN_IP_TCP]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ transport = dcerpc_transport_by_name(argv[1]);
+ if (transport == NCA_UNKNOWN) {
+ printf("transport type %s unknown\n", argv[1]);
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ if (!((transport == NCACN_IP_TCP) ||
+ (transport == NCACN_NP) ||
+ (transport == NCALRPC))) {
+ printf("transport %s not supported\n", argv[1]);
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = dcerpc_binding_set_transport(binding, transport);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = cmd_set_transport(binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ printf("default transport is now: %s\n", argv[1]);
+
+ return NT_STATUS_OK;
+}
+
+/* Built in rpcclient commands */
+
+static struct cmd_set rpcclient_commands[] = {
+
+ {
+ .name = "GENERAL OPTIONS",
+ },
+
+ {
+ .name = "help",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_help,
+ .description = "Get help on commands",
+ .usage = "[command]",
+ },
+ {
+ .name = "?",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_help,
+ .description = "Get help on commands",
+ .usage = "[command]",
+ },
+ {
+ .name = "debuglevel",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_debuglevel,
+ .description = "Set debug level",
+ .usage = "level",
+ },
+ {
+ .name = "debug",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_debuglevel,
+ .description = "Set debug level",
+ .usage = "level",
+ },
+ {
+ .name = "list",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_listcommands,
+ .description = "List available commands on <pipe>",
+ .usage = "pipe",
+ },
+ {
+ .name = "exit",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_quit,
+ .description = "Exit program",
+ .usage = "",
+ },
+ {
+ .name = "quit",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_quit,
+ .description = "Exit program",
+ .usage = "",
+ },
+ {
+ .name = "sign",
+ .returntype = RPC_RTYPE_BINDING,
+ .bfn = cmd_sign,
+ .description = "Force RPC pipe connections to be signed",
+ .usage = "",
+ },
+ {
+ .name = "seal",
+ .returntype = RPC_RTYPE_BINDING,
+ .bfn = cmd_seal,
+ .description = "Force RPC pipe connections to be sealed",
+ .usage = "",
+ },
+ {
+ .name = "packet",
+ .returntype = RPC_RTYPE_BINDING,
+ .bfn = cmd_packet,
+ .description = "Force RPC pipe connections with packet authentication level",
+ .usage = "",
+ },
+ {
+ .name = "schannel",
+ .returntype = RPC_RTYPE_BINDING,
+ .bfn = cmd_schannel,
+ .description = "Force RPC pipe connections to be sealed with 'schannel'. "
+ "Assumes valid machine account to this domain controller.",
+ .usage = "",
+ },
+ {
+ .name = "schannelsign",
+ .returntype = RPC_RTYPE_BINDING,
+ .bfn = cmd_schannel_sign,
+ .description = "Force RPC pipe connections to be signed (not sealed) with "
+ "'schannel'. Assumes valid machine account to this domain "
+ "controller.",
+ .usage = "",
+ },
+ {
+ .name = "timeout",
+ .returntype = RPC_RTYPE_NTSTATUS,
+ .ntfn = cmd_timeout,
+ .description = "Set timeout (in milliseconds) for RPC operations",
+ .usage = "",
+ },
+ {
+ .name = "transport",
+ .returntype = RPC_RTYPE_BINDING,
+ .bfn = cmd_choose_transport,
+ .description = "Choose ncacn transport for RPC operations",
+ .usage = "",
+ },
+ {
+ .name = "none",
+ .returntype = RPC_RTYPE_BINDING,
+ .bfn = cmd_none,
+ .description = "Force RPC pipe connections to have no special properties",
+ .usage = "",
+ },
+
+ { .name = NULL, },
+};
+
+static struct cmd_set separator_command[] = {
+ {
+ .name = "---------------",
+ .returntype = MAX_RPC_RETURN_TYPE,
+ .description = "----------------------"
+ },
+ { .name = NULL, },
+};
+
+
+/* Various pipe commands */
+
+extern struct cmd_set lsarpc_commands[];
+extern struct cmd_set samr_commands[];
+extern struct cmd_set spoolss_commands[];
+extern struct cmd_set iremotewinspool_commands[];
+extern struct cmd_set netlogon_commands[];
+extern struct cmd_set srvsvc_commands[];
+extern struct cmd_set dfs_commands[];
+extern struct cmd_set ds_commands[];
+extern struct cmd_set echo_commands[];
+extern struct cmd_set epmapper_commands[];
+extern struct cmd_set shutdown_commands[];
+extern struct cmd_set wkssvc_commands[];
+extern struct cmd_set ntsvcs_commands[];
+extern struct cmd_set drsuapi_commands[];
+extern struct cmd_set eventlog_commands[];
+extern struct cmd_set winreg_commands[];
+extern struct cmd_set fss_commands[];
+extern struct cmd_set witness_commands[];
+extern struct cmd_set clusapi_commands[];
+extern struct cmd_set spotlight_commands[];
+extern struct cmd_set unixinfo_commands[];
+
+static struct cmd_set *rpcclient_command_list[] = {
+ rpcclient_commands,
+ lsarpc_commands,
+ ds_commands,
+ samr_commands,
+ spoolss_commands,
+ iremotewinspool_commands,
+ netlogon_commands,
+ srvsvc_commands,
+ dfs_commands,
+ echo_commands,
+ epmapper_commands,
+ shutdown_commands,
+ wkssvc_commands,
+ ntsvcs_commands,
+ drsuapi_commands,
+ eventlog_commands,
+ winreg_commands,
+ fss_commands,
+ witness_commands,
+ clusapi_commands,
+ spotlight_commands,
+ unixinfo_commands,
+ NULL
+};
+
+static void add_command_set(struct cmd_set *cmd_set)
+{
+ struct cmd_list *entry;
+
+ if (!(entry = SMB_MALLOC_P(struct cmd_list))) {
+ DEBUG(0, ("out of memory\n"));
+ return;
+ }
+
+ ZERO_STRUCTP(entry);
+
+ entry->cmd_set = cmd_set;
+ DLIST_ADD(cmd_list, entry);
+}
+
+static NTSTATUS rpccli_ncalrpc_connect(
+ const struct ndr_interface_table *iface,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **prpccli)
+{
+ struct rpc_pipe_client *rpccli = NULL;
+ struct pipe_auth_data *auth = NULL;
+ NTSTATUS status;
+
+ status = rpc_pipe_open_ncalrpc(mem_ctx, iface, &rpccli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_open_ncalrpc failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = rpccli_ncalrpc_bind_data(rpccli, &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpccli_ncalrpc_bind_data failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = rpc_pipe_bind(rpccli, auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("rpc_pipe_bind failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ *prpccli = rpccli;
+ return NT_STATUS_OK;
+fail:
+ TALLOC_FREE(rpccli);
+ return status;
+}
+/**
+ * Call an rpcclient function, passing an argv array.
+ *
+ * @param cmd Command to run, as a single string.
+ **/
+static NTSTATUS do_cmd(struct cli_state *cli,
+ struct cli_credentials *creds,
+ struct cmd_set *cmd_entry,
+ struct dcerpc_binding *binding,
+ int argc, const char **argv)
+{
+ NTSTATUS ntresult;
+ WERROR wresult;
+ enum dcerpc_transport_t transport;
+
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+ struct sockaddr_storage remote_ss = {
+ .ss_family = AF_UNSPEC,
+ };
+
+ transport = dcerpc_binding_get_transport(binding);
+
+ if (cli != NULL) {
+ remote_name = smbXcli_conn_remote_name(cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(cli->conn);
+ } else {
+ const char *remote_host =
+ dcerpc_binding_get_string_option(binding, "host");
+ remote_name = dcerpc_binding_get_string_option(
+ binding, "target_hostname");
+
+ if (remote_host != NULL) {
+ bool ok = interpret_string_addr(
+ &remote_ss, remote_host, 0);
+ if (ok) {
+ remote_sockaddr = &remote_ss;
+ }
+ }
+ }
+
+ /* Open pipe */
+
+ if ((cmd_entry->table != NULL) && (cmd_entry->rpc_pipe == NULL)) {
+ if (transport == NCALRPC) {
+ ntresult = rpccli_ncalrpc_connect(
+ cmd_entry->table, cli, &cmd_entry->rpc_pipe);
+ if (!NT_STATUS_IS_OK(ntresult)) {
+ TALLOC_FREE(mem_ctx);
+ return ntresult;
+ }
+ } else {
+ enum dcerpc_AuthType auth_type;
+ enum dcerpc_AuthLevel auth_level;
+ enum credentials_use_kerberos krb5_state =
+ cli_credentials_get_kerberos_state(creds);
+
+ binding_get_auth_info(
+ binding, &auth_type, &auth_level, &krb5_state);
+
+ switch (auth_type) {
+ case DCERPC_AUTH_TYPE_NONE:
+ ntresult = cli_rpc_pipe_open_noauth_transport(
+ cli, transport,
+ cmd_entry->table,
+ remote_name,
+ remote_sockaddr,
+ &cmd_entry->rpc_pipe);
+ break;
+ case DCERPC_AUTH_TYPE_SPNEGO:
+ case DCERPC_AUTH_TYPE_NTLMSSP:
+ case DCERPC_AUTH_TYPE_KRB5:
+ cli_credentials_set_kerberos_state(creds,
+ krb5_state,
+ CRED_SPECIFIED);
+
+ ntresult = cli_rpc_pipe_open_with_creds(
+ cli, cmd_entry->table,
+ transport,
+ auth_type,
+ auth_level,
+ remote_name,
+ remote_sockaddr,
+ creds,
+ &cmd_entry->rpc_pipe);
+ break;
+ case DCERPC_AUTH_TYPE_SCHANNEL:
+ TALLOC_FREE(rpcclient_netlogon_creds);
+ ntresult = cli_rpc_pipe_open_schannel(
+ cli, rpcclient_msg_ctx,
+ cmd_entry->table,
+ transport,
+ rpcclient_netlogon_domain,
+ remote_name,
+ remote_sockaddr,
+ &cmd_entry->rpc_pipe,
+ rpcclient_msg_ctx,
+ &rpcclient_netlogon_creds);
+ break;
+ default:
+ DEBUG(0, ("Could not initialise %s. Invalid "
+ "auth type %u\n",
+ cmd_entry->table->name,
+ auth_type ));
+ talloc_free(mem_ctx);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ if (!NT_STATUS_IS_OK(ntresult)) {
+ DBG_ERR("Could not initialise %s. "
+ "Error was %s\n",
+ cmd_entry->table->name,
+ nt_errstr(ntresult));
+ talloc_free(mem_ctx);
+ return ntresult;
+ }
+
+ if (rpcclient_netlogon_creds == NULL &&
+ cmd_entry->use_netlogon_creds) {
+ const char *dc_name =
+ cmd_entry->rpc_pipe->desthost;
+ const char *domain = rpcclient_netlogon_domain;
+ struct cli_credentials *trust_creds = NULL;
+
+ ntresult = pdb_get_trust_credentials(
+ domain,
+ NULL,
+ mem_ctx,
+ &trust_creds);
+ if (!NT_STATUS_IS_OK(ntresult)) {
+ DBG_ERR("Failed to fetch trust "
+ "credentials for "
+ "%s to connect to %s: %s\n",
+ domain,
+ cmd_entry->table->name,
+ nt_errstr(ntresult));
+ TALLOC_FREE(cmd_entry->rpc_pipe);
+ talloc_free(mem_ctx);
+ return ntresult;
+ }
+
+ ntresult = rpccli_create_netlogon_creds_ctx(
+ trust_creds,
+ dc_name,
+ rpcclient_msg_ctx,
+ rpcclient_msg_ctx,
+ &rpcclient_netlogon_creds);
+ if (!NT_STATUS_IS_OK(ntresult)) {
+ DBG_ERR("Could not initialise "
+ "credentials for %s.\n",
+ cmd_entry->table->name);
+ TALLOC_FREE(cmd_entry->rpc_pipe);
+ TALLOC_FREE(mem_ctx);
+ return ntresult;
+ }
+
+ ntresult = rpccli_setup_netlogon_creds(
+ cli,
+ NCACN_NP,
+ rpcclient_netlogon_creds,
+ false, /* force_reauth */
+ trust_creds);
+ TALLOC_FREE(trust_creds);
+ if (!NT_STATUS_IS_OK(ntresult)) {
+ DBG_ERR("Could not initialise "
+ "credentials for %s.\n",
+ cmd_entry->table->name);
+ TALLOC_FREE(cmd_entry->rpc_pipe);
+ TALLOC_FREE(rpcclient_netlogon_creds);
+ TALLOC_FREE(mem_ctx);
+ return ntresult;
+ }
+ }
+ }
+ }
+
+ /* Set timeout for new connections */
+ if (cmd_entry->rpc_pipe) {
+ rpccli_set_timeout(cmd_entry->rpc_pipe, timeout);
+ }
+
+ /* Run command */
+
+ if ( cmd_entry->returntype == RPC_RTYPE_NTSTATUS ) {
+ ntresult = cmd_entry->ntfn(cmd_entry->rpc_pipe, mem_ctx, argc, argv);
+ if (!NT_STATUS_IS_OK(ntresult)) {
+ printf("result was %s\n", nt_errstr(ntresult));
+ }
+ } else if (cmd_entry->returntype == RPC_RTYPE_BINDING) {
+ ntresult = cmd_entry->bfn(binding, mem_ctx, argc, argv);
+ if (!NT_STATUS_IS_OK(ntresult)) {
+ printf("result was %s\n", nt_errstr(ntresult));
+ }
+ } else {
+ wresult = cmd_entry->wfn(cmd_entry->rpc_pipe, mem_ctx, argc, argv);
+ /* print out the DOS error */
+ if (!W_ERROR_IS_OK(wresult)) {
+ printf( "result was %s\n", win_errstr(wresult));
+ }
+ ntresult = W_ERROR_IS_OK(wresult)?NT_STATUS_OK:NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* Cleanup */
+
+ talloc_free(mem_ctx);
+
+ return ntresult;
+}
+
+
+/**
+ * Process a command entered at the prompt or as part of -c
+ *
+ * @returns The NTSTATUS from running the command.
+ **/
+static NTSTATUS process_cmd(struct cli_credentials *creds,
+ struct cli_state *cli,
+ struct dcerpc_binding *binding,
+ char *cmd)
+{
+ struct cmd_list *temp_list;
+ NTSTATUS result = NT_STATUS_OK;
+ int ret;
+ int argc;
+ const char **argv = NULL;
+
+ if ((ret = poptParseArgvString(cmd, &argc, &argv)) != 0) {
+ fprintf(stderr, "rpcclient: %s\n", poptStrerror(ret));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+
+ /* Walk through a dlist of arrays of commands. */
+ for (temp_list = cmd_list; temp_list; temp_list = temp_list->next) {
+ struct cmd_set *set = temp_list->cmd_set;
+
+ while (set->name != NULL) {
+ if (!strequal(argv[0], set->name)) {
+ set += 1;
+ continue;
+ }
+
+ if (((set->returntype == RPC_RTYPE_NTSTATUS) &&
+ (set->ntfn == NULL)) ||
+ ((set->returntype == RPC_RTYPE_WERROR) &&
+ (set->wfn == NULL)) ||
+ ((set->returntype == RPC_RTYPE_BINDING) &&
+ (set->bfn == NULL))) {
+ fprintf (stderr, "Invalid command\n");
+ goto out_free;
+ }
+
+ result = do_cmd(
+ cli, creds, set, binding, argc, argv);
+ goto out_free;
+ }
+ }
+
+ if (argv[0]) {
+ printf("command not found: %s\n", argv[0]);
+ }
+
+out_free:
+/* moved to do_cmd()
+ if (!NT_STATUS_IS_OK(result)) {
+ printf("result was %s\n", nt_errstr(result));
+ }
+*/
+
+ /* NOTE: popt allocates the whole argv, including the
+ * strings, as a single block. So a single free is
+ * enough to release it -- we don't free the
+ * individual strings. rtfm. */
+ free(argv);
+
+ return result;
+}
+
+
+/* Main function */
+
+ int main(int argc, char *argv[])
+{
+ const char **const_argv = discard_const_p(const char *, argv);
+ int opt;
+ static char *cmdstr = NULL;
+ const char *server;
+ struct cli_state *cli = NULL;
+ static char *opt_ipaddr=NULL;
+ struct cmd_set **cmd_set;
+ struct sockaddr_storage server_ss;
+ NTSTATUS nt_status;
+ static int opt_port = 0;
+ int result = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint32_t flags = CLI_FULL_CONNECTION_IPC;
+ struct dcerpc_binding *binding = NULL;
+ enum dcerpc_transport_t transport;
+ const char *binding_string = NULL;
+ const char *host;
+ struct cli_credentials *creds = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ bool ok;
+
+ /* make sure the vars that get altered (4th field) are in
+ a fixed location or certain compilers complain */
+ poptContext pc;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {"command", 'c', POPT_ARG_STRING, &cmdstr, 'c', "Execute semicolon separated cmds", "COMMANDS"},
+ {"dest-ip", 'I', POPT_ARG_STRING, &opt_ipaddr, 'I', "Specify destination IP address", "IP"},
+ {"port", 'p', POPT_ARG_INT, &opt_port, 'p', "Specify port number", "PORT"},
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S3
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ smb_init_locale();
+
+ zero_sockaddr(&server_ss);
+
+ setlinebuf(stdout);
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+
+ /* Parse options */
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ const_argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "[OPTION...] BINDING-STRING|HOST\nOptions:");
+
+ if (argc == 1) {
+ poptPrintHelp(pc, stderr, 0);
+ goto done;
+ }
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+
+ case 'I':
+ if (!interpret_string_addr(&server_ss,
+ opt_ipaddr,
+ AI_NUMERICHOST)) {
+ fprintf(stderr, "%s not a valid IP address\n",
+ opt_ipaddr);
+ result = 1;
+ goto done;
+ }
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ /* Get server as remaining unparsed argument. Print usage if more
+ than one unparsed argument is present. */
+
+ server = talloc_strdup(frame, poptGetArg(pc));
+
+ if (!server || poptGetArg(pc)) {
+ poptPrintHelp(pc, stderr, 0);
+ result = 1;
+ goto done;
+ }
+
+ poptFreeContext(pc);
+ samba_cmdline_burn(argc, argv);
+
+ rpcclient_msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
+ creds = samba_cmdline_get_creds();
+
+ /*
+ * Get password
+ * from stdin if necessary
+ */
+
+ if ((server[0] == '/' && server[1] == '/') ||
+ (server[0] == '\\' && server[1] == '\\')) {
+ server += 2;
+ }
+
+ nt_status = dcerpc_parse_binding(frame, server, &binding);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+
+ binding_string = talloc_asprintf(frame, "ncacn_np:%s",
+ strip_hostname(server));
+ if (!binding_string) {
+ result = 1;
+ goto done;
+ }
+
+ nt_status = dcerpc_parse_binding(frame, binding_string, &binding);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ result = -1;
+ goto done;
+ }
+ }
+
+ transport = dcerpc_binding_get_transport(binding);
+
+ if (transport == NCA_UNKNOWN) {
+ transport = NCACN_NP;
+ nt_status = dcerpc_binding_set_transport(binding, transport);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ result = -1;
+ goto done;
+ }
+ }
+
+ host = dcerpc_binding_get_string_option(binding, "host");
+
+ rpcclient_netlogon_domain = cli_credentials_get_domain(creds);
+ if (rpcclient_netlogon_domain == NULL ||
+ rpcclient_netlogon_domain[0] == '\0')
+ {
+ rpcclient_netlogon_domain = lp_workgroup();
+ }
+
+ if (transport == NCACN_NP) {
+ nt_status = cli_full_connection_creds(
+ &cli,
+ lp_netbios_name(),
+ host,
+ opt_ipaddr ? &server_ss : NULL,
+ opt_port,
+ "IPC$",
+ "IPC",
+ creds,
+ flags);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Cannot connect to server. Error was %s\n",
+ nt_errstr(nt_status)));
+ result = 1;
+ goto done;
+ }
+
+ /* Load command lists */
+ cli_set_timeout(cli, timeout);
+ }
+
+#if 0 /* COMMENT OUT FOR TESTING */
+ memset(cmdline_auth_info.password,'X',sizeof(cmdline_auth_info.password));
+#endif
+
+ cmd_set = rpcclient_command_list;
+
+ while(*cmd_set) {
+ add_command_set(*cmd_set);
+ add_command_set(separator_command);
+ cmd_set++;
+ }
+
+ /* Do anything specified with -c */
+ if (cmdstr && cmdstr[0]) {
+ char *cmd;
+ char *p = cmdstr;
+
+ result = 0;
+
+ while((cmd=next_command(&p)) != NULL) {
+ NTSTATUS cmd_result = process_cmd(creds,
+ cli,
+ binding,
+ cmd);
+ SAFE_FREE(cmd);
+ result = NT_STATUS_IS_ERR(cmd_result);
+ }
+
+ goto done;
+ }
+
+ /* Loop around accepting commands */
+
+ while(1) {
+ char *line = NULL;
+
+ line = smb_readline("rpcclient $> ", NULL, completion_fn);
+
+ if (line == NULL) {
+ printf("\n");
+ break;
+ }
+
+ if (line[0] != '\n')
+ process_cmd(creds,
+ cli,
+ binding,
+ line);
+ SAFE_FREE(line);
+ }
+
+done:
+ if (cli != NULL) {
+ cli_shutdown(cli);
+ }
+ netlogon_creds_cli_close_global_db();
+ TALLOC_FREE(rpcclient_msg_ctx);
+ TALLOC_FREE(frame);
+ return result;
+}
diff --git a/source3/rpcclient/rpcclient.h b/source3/rpcclient/rpcclient.h
new file mode 100644
index 0000000..75ac575
--- /dev/null
+++ b/source3/rpcclient/rpcclient.h
@@ -0,0 +1,53 @@
+/*
+ Unix SMB/CIFS implementation.
+ RPC pipe client
+
+ Copyright (C) Tim Potter 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef RPCCLIENT_H
+#define RPCCLIENT_H
+
+#include "rpc_client/cli_pipe.h"
+
+typedef enum {
+ RPC_RTYPE_NTSTATUS = 0,
+ RPC_RTYPE_WERROR,
+ RPC_RTYPE_BINDING,
+ MAX_RPC_RETURN_TYPE
+} RPC_RETURN_TYPE;
+
+struct cmd_set {
+ const char *name;
+ RPC_RETURN_TYPE returntype;
+ NTSTATUS (*ntfn)(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx, int argc,
+ const char **argv);
+ WERROR (*wfn)(struct rpc_pipe_client *cli, TALLOC_CTX *mem_ctx, int argc, const char **argv);
+ NTSTATUS (*bfn)(struct dcerpc_binding *binding,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+ const struct ndr_interface_table *table;
+ struct rpc_pipe_client *rpc_pipe;
+ const char *description;
+ const char *usage;
+ bool use_netlogon_creds;
+};
+
+extern struct messaging_context *rpcclient_msg_ctx;
+extern struct netlogon_creds_cli_context *rpcclient_netlogon_creds;
+
+#endif /* RPCCLIENT_H */
diff --git a/source3/rpcclient/wscript_build b/source3/rpcclient/wscript_build
new file mode 100644
index 0000000..bb3a88c
--- /dev/null
+++ b/source3/rpcclient/wscript_build
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_BINARY('rpcclient',
+ source='''rpcclient.c
+ cmd_lsarpc.c
+ cmd_samr.c
+ cmd_spoolss.c
+ cmd_netlogon.c
+ cmd_srvsvc.c
+ cmd_dfs.c
+ cmd_epmapper.c
+ cmd_dssetup.c
+ cmd_echo.c
+ cmd_shutdown.c
+ cmd_wkssvc.c
+ cmd_ntsvcs.c
+ cmd_drsuapi.c
+ cmd_eventlog.c
+ cmd_winreg.c
+ cmd_fss.c
+ cmd_clusapi.c
+ cmd_witness.c
+ cmd_iremotewinspool.c
+ cmd_spotlight.c
+ cmd_unixinfo.c
+ ''',
+ deps='''
+ talloc
+ CMDLINE_S3
+ cmdline_contexts
+ pdb
+ libsmb
+ smbconf
+ ndr-standard
+ msrpc3
+ SMBREADLINE
+ trusts_util
+ RPC_NDR_WINREG
+ RPC_NDR_ECHO
+ RPC_CLIENT_SCHANNEL
+ DCUTIL
+ LIBCLI_SAMR
+ libcli_lsa3
+ libcli_netlogon3
+ cli_spoolss
+ RPC_NDR_SRVSVC
+ RPC_NDR_WKSSVC
+ RPC_NDR_DSSETUP
+ RPC_NDR_DFS
+ RPC_NDR_DRSUAPI
+ RPC_NDR_NTSVCS
+ RPC_NDR_EVENTLOG
+ INIT_SAMR
+ RPC_NDR_FSRVP
+ RPC_NDR_CLUSAPI
+ RPC_NDR_WITNESS
+ RPC_NDR_WINSPOOL
+ mdssvc
+ RPC_NDR_MDSSVC
+ RPC_NDR_UNIXINFO
+ ''')
diff --git a/source3/script/count_80_col.pl b/source3/script/count_80_col.pl
new file mode 100755
index 0000000..8b22622
--- /dev/null
+++ b/source3/script/count_80_col.pl
@@ -0,0 +1,16 @@
+#!/usr/bin/perl -w
+
+open( INFILE, "$ARGV[0]" ) || die $@;
+
+$count = 0;
+while ( <INFILE> ) {
+ next if ($_ =~ /^#define/);
+ $count++ if (length($_) > 80);
+}
+
+close( INFILE );
+print "$ARGV[0]: $count lines > 80 characters\n" if ($count > 0);
+
+exit( 0 );
+
+
diff --git a/source3/script/creategroup b/source3/script/creategroup
new file mode 100755
index 0000000..1e24867
--- /dev/null
+++ b/source3/script/creategroup
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# Example script for 'add group command'. Handle weird NT group
+# names. First attempt to create the group directly, if that fails
+# then create a random group and print the numeric group id.
+#
+# Note that this is only an example and assumes /dev/urandom.
+#
+# Volker
+
+GROUPNAME="$1"
+ITERS=0
+
+while ! /usr/sbin/groupadd "$GROUPNAME" >/dev/null 2>&1; do
+ # we had difficulties creating that group. Maybe the name was
+ # too weird, or it already existed. Create a random name.
+ GROUPNAME=nt-$(dd if=/dev/urandom bs=16 count=1 2>/dev/null | md5sum | cut -b 1-5)
+ ITERS=$(expr "$ITERS" + 1)
+ if [ "$ITERS" -gt 10 ]; then
+ # Too many attempts
+ exit 1
+ fi
+done
+
+getent group | grep ^"$GROUPNAME": | cut -d : -f 3
diff --git a/source3/script/fix_bool.pl b/source3/script/fix_bool.pl
new file mode 100755
index 0000000..c09645d
--- /dev/null
+++ b/source3/script/fix_bool.pl
@@ -0,0 +1,19 @@
+#!/usr/bin/perl -w
+
+open(INFILE, "$ARGV[0]") || die $@;
+open(OUTFILE, ">$ARGV[0].new") || die $@;
+
+while (<INFILE>) {
+ $_ =~ s/True/true/;
+ $_ =~ s/False/false/;
+ print OUTFILE "$_";
+}
+
+close(INFILE);
+close(OUTFILE);
+
+rename("$ARGV[0].new", "$ARGV[0]") || die @_;
+
+exit(0);
+
+
diff --git a/source3/script/format_indent.sh b/source3/script/format_indent.sh
new file mode 100755
index 0000000..11ef285
--- /dev/null
+++ b/source3/script/format_indent.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# -npro Do no read the '.indent.pro' files.
+# -kr Use K&R formatting rules
+# -i8 Set indentation level to 8 spaces.
+# -ts8 Set tab size to 8 spaces
+# -sob Swallow optional blank lines.
+# -l80 Set the maximum line length at 80 characters.
+# -ss On one-line for and while statements, force a blank before the semicolon
+# -ncs Do not put a space after cast operators.
+
+indent -npro -kr -i8 -ts8 -sob -l80 -ss -ncs "$@"
diff --git a/source3/script/makeunicodecasemap.awk b/source3/script/makeunicodecasemap.awk
new file mode 100644
index 0000000..8424b6c
--- /dev/null
+++ b/source3/script/makeunicodecasemap.awk
@@ -0,0 +1,59 @@
+function reset_vals() {
+ upperstr = "";
+ lowerstr = "";
+ flagstr = "0";
+}
+
+function print_val() {
+ upperstr = $13;
+ lowerstr = $14;
+ if ( upperstr == "" )
+ upperstr = strval;
+ if ( lowerstr == "" )
+ lowerstr = strval;
+
+ if ( $3 == "Lu" )
+ flagstr = sprintf("%s|%s", flagstr, "UNI_UPPER");
+ if ( $3 == "Ll" )
+ flagstr = sprintf("%s|%s", flagstr, "UNI_LOWER");
+ if ( val >= 48 && val <= 57)
+ flagstr = sprintf("%s|%s", flagstr, "UNI_DIGIT");
+ if ((val >= 48 && val <= 57) || (val >= 65 && val <= 70) || (val >=97 && val <= 102))
+ flagstr = sprintf("%s|%s", flagstr, "UNI_XDIGIT");
+ if ( val == 32 || (val >=9 && val <= 13))
+ flagstr = sprintf("%s|%s", flagstr, "UNI_SPACE");
+ if( index(flagstr, "0|") == 1)
+ flagstr = substr(flagstr, 3, length(flagstr) - 2);
+ printf("{ 0x%s, 0x%s, %s }, \t\t\t/* %s %s */\n", lowerstr, upperstr, flagstr, strval, $2);
+ val++;
+ strval=sprintf("%04X", val);
+ reset_vals();
+}
+
+BEGIN {
+ val=0
+ FS=";"
+ strval=sprintf("%04X", val);
+ reset_vals();
+}
+
+{
+ if ( $1 == strval ) {
+ print_val();
+ } else {
+ while ( $1 != strval) {
+ printf("{ 0x%04X, 0x%04X, 0 }, \t\t\t/* %s NOMAP */\n", val, val, strval);
+ val++;
+ strval=sprintf("%04X", val);
+ }
+ print_val();
+ }
+}
+
+END {
+ while ( val < 65536 ) {
+ printf("{ 0x%04X, 0x%04X, 0 }, \t\t\t/* %s NOMAP */\n", val, val, strval);
+ val++;
+ strval=sprintf("%04X", val);
+ }
+}
diff --git a/source3/script/mknissmbpasswd.sh b/source3/script/mknissmbpasswd.sh
new file mode 100755
index 0000000..2fea32c
--- /dev/null
+++ b/source3/script/mknissmbpasswd.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# Copyright (C) 1998 Benny Holmgren
+#
+# Script to import smbpasswd file into the smbpasswd NIS+ table. Reads
+# from stdin the smbpasswd file.
+#
+while true; do
+ read row
+ if [ -z "$row" ]; then
+ break
+ fi
+
+ if [ "$(echo $row | cut -c1)" = "#" ]; then
+ continue
+ fi
+
+ nistbladm -a \
+ name=\"$(echo $row | cut -d: -f1)\" \
+ uid=\"$(echo $row | cut -d: -f2)\" \
+ lmpwd=\"$(echo $row | cut -d: -f3)\" \
+ ntpwd=\"$(echo $row | cut -d: -f4)\" \
+ acb=\"$(echo $row | cut -d: -f5)\" \
+ pwdlset_t=\"$(echo $row | cut -d: -f6)\" \
+ gcos=\"$(echo $row | cut -d: -f7)\" \
+ home=\"$(echo $row | cut -d: -f8)\" \
+ shell=\"$(echo $row | cut -d: -f9)\" smbpasswd.org_dir.$(nisdefaults -d)
+done
diff --git a/source3/script/mknissmbpwdtbl.sh b/source3/script/mknissmbpwdtbl.sh
new file mode 100755
index 0000000..d7291bb
--- /dev/null
+++ b/source3/script/mknissmbpwdtbl.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Copyright (C) 1998 Benny Holmgren
+#
+# Creates smbpasswd table and smb group in NIS+
+#
+
+nistbladm \
+ -D access=og=rmcd,nw= -c \
+ -s : smbpasswd_tbl \
+ name=S,nogw=r \
+ uid=S,nogw=r \
+ user_rid=S,nogw=r \
+ smb_grpid=,nw+r \
+ group_rid=,nw+r \
+ acb=,nw+r \
+ \
+ lmpwd=C,nw=,g=r,o=rm \
+ ntpwd=C,nw=,g=r,o=rm \
+ \
+ logon_t=,nw+r \
+ logoff_t=,nw+r \
+ kick_t=,nw+r \
+ pwdlset_t=,nw+r \
+ pwdlchg_t=,nw+r \
+ pwdmchg_t=,nw+r \
+ \
+ full_name=,nw+r \
+ home_dir=,nw+r \
+ dir_drive=,nw+r \
+ logon_script=,nw+r \
+ profile_path=,nw+r \
+ acct_desc=,nw+r \
+ workstations=,nw+r \
+ \
+ hours=,nw+r \
+ smbpasswd.org_dir.$(nisdefaults -d)
+
+nisgrpadm -c smb.$(nisdefaults -d)
+
+nischgrp smb.$(nisdefaults -d) smbpasswd.org_dir.$(nisdefaults -d)
diff --git a/source3/script/mksmbpasswd.sh b/source3/script/mksmbpasswd.sh
new file mode 100755
index 0000000..119a556
--- /dev/null
+++ b/source3/script/mksmbpasswd.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+awk 'BEGIN {FS=":"
+ printf("#\n# SMB password file.\n#\n")
+ }
+{ printf( "%s:%s:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:[UD ]:LCT-00000000:%s\n", $1, $3, $5) }
+'
diff --git a/source3/script/mksyms.awk b/source3/script/mksyms.awk
new file mode 100644
index 0000000..94a405c
--- /dev/null
+++ b/source3/script/mksyms.awk
@@ -0,0 +1,76 @@
+#
+# mksyms.awk
+#
+# Extract symbols to export from C-header files.
+# output in version-script format for linking shared libraries.
+#
+# Copyright (C) 2008 Michael Adam <obnox@samba.org>
+#
+BEGIN {
+ inheader=0;
+ current_file="";
+ print "#"
+ print "# This file is automatically generated with \"make symbols\". DO NOT EDIT "
+ print "#"
+ print "{"
+ print "\tglobal:"
+}
+
+END {
+ print""
+ print "\tlocal: *;"
+ print "};"
+}
+
+{
+ if (FILENAME!=current_file) {
+ print "\t\t# The following definitions come from",FILENAME
+ current_file=FILENAME
+ }
+ if (inheader) {
+ if (match($0,"[)][^()]*[;][ \t]*$")) {
+ inheader = 0;
+ }
+ next;
+ }
+}
+
+/^static/ || /^[ \t]*typedef/ || !/^[a-zA-Z\_]/ {
+ next;
+}
+
+/^extern[ \t]+[^()]+[;][ \t]*$/ {
+ gsub(/[^ \t]+[ \t]+/, "");
+ sub(/[;][ \t]*$/, "");
+ printf "\t\t%s;\n", $0;
+ next;
+}
+
+# look for function headers:
+{
+ gotstart = 0;
+ if ($0 ~ /^[A-Za-z_][A-Za-z0-9_]+/) {
+ gotstart = 1;
+ }
+ if(!gotstart) {
+ next;
+ }
+}
+
+/[_A-Za-z0-9]+[ \t]*[(].*[)][^()]*;[ \t]*$/ {
+ sub(/[(].*$/, "");
+ gsub(/[^ \t]+[ \t]+/, "");
+ gsub(/^[*]+/, "");
+ printf "\t\t%s;\n",$0;
+ next;
+}
+
+/[_A-Za-z0-9]+[ \t]*[(]/ {
+ inheader=1;
+ sub(/[(].*$/, "");
+ gsub(/[^ \t]+[ \t]+/, "");
+ gsub(/^[*]/, "");
+ printf "\t\t%s;\n",$0;
+ next;
+}
+
diff --git a/source3/script/mksyms.sh b/source3/script/mksyms.sh
new file mode 100755
index 0000000..9a08685
--- /dev/null
+++ b/source3/script/mksyms.sh
@@ -0,0 +1,46 @@
+#! /bin/sh
+
+#
+# mksyms.sh
+#
+# Extract symbols to export from C-header files.
+# output in version-script format for linking shared libraries.
+#
+# This is the shell wrapper for the mksyms.awk core script.
+#
+# Copyright (C) 2008 Michael Adam <obnox@samba.org>
+#
+
+LANG=C
+export LANG
+LC_ALL=C
+export LC_ALL
+LC_COLLATE=C
+export LC_COLLATE
+
+if [ $# -lt 2 ]; then
+ echo "Usage: $0 awk output_file header_files"
+ exit 1
+fi
+
+awk="$1"
+shift
+
+symsfile="$1"
+shift
+symsfile_tmp="$symsfile.$$.tmp~"
+
+proto_src="$(echo "$@" | tr ' ' '\n' | sort | uniq)"
+
+echo creating $symsfile
+
+mkdir -p $(dirname $symsfile)
+
+${awk} -f $(dirname $0)/mksyms.awk $proto_src >$symsfile_tmp
+
+if cmp -s $symsfile $symsfile_tmp 2>/dev/null; then
+ echo "$symsfile unchanged"
+ rm $symsfile_tmp
+else
+ mv $symsfile_tmp $symsfile
+fi
diff --git a/source3/script/samba-log-parser b/source3/script/samba-log-parser
new file mode 100755
index 0000000..a07dfdb
--- /dev/null
+++ b/source3/script/samba-log-parser
@@ -0,0 +1,382 @@
+#!/usr/bin/env python3
+#
+#######################################################################
+#
+# A script to parse samba (especially winbind) logfiles.
+# Trace files should be in a non-syslog format (debug syslog format = no).
+#
+# --traceid ... Specify the traceid of the request to parse
+# --pid ... Specify the pid
+# --breakdown ... Break to separate files per each traceid
+# --merge-by-timestamp ... Merge logs by timestamp
+# --flow ... Show the request/sub-request call flow
+# --flow-compact ... Show the request/sub-request call flow without dcerpc
+#
+#
+# Copyright (c) 2023 Andreas Schneider <asn@samba.org>
+# Copyright (c) 2023 Pavel Filipenský <pfilipen@redhat.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#######################################################################
+#
+# Requires: ???
+
+import sys
+import os
+import re
+from argparse import ArgumentParser
+from collections import defaultdict
+
+# Trace record consists of a trace header followed by one or more text lines.
+#
+# This tool expects trace header format based on these smb.conf parameters:
+#
+# debug syslog format = no
+# debug hires timestamp = yes
+# winbind debug traceid = yes
+#
+# If 'winbind debug traceid = no' is set, then the option --merge-by-timestamp
+# still can be used.
+#
+# Each trace header contains a traceid, which is the main identifier for this
+# tool. A single traceid is either provided via command line option --traceid
+# or a list of traceids is derived from the PID specified via option --pid.
+# Creating and evaluating list of traceids from PID can be tricky:
+# The traceid can appear in a trace record before trace record containing the
+# PID is processed. So when we see a new traceid we are not sure if it belongs
+# to the traced PID.
+# The PID appears only in the main winbind process (log.winbind). If a
+# directory with many log files should be processed, we process the files in
+# random order.
+# It might happen that e.g. log.wb-ADDOMAIN is processed before log.winbind so
+# we do not know the list of traceids yet.
+# To make all this easy we put into memory all trace records and do the final
+# traceid filtering only after all files are read. This can require lot of
+# memory if files are big.
+
+
+def process_file_no_traceid(record_list, fname):
+ with open(fname, "r") as infile:
+ data = infile.readlines()
+ date = ""
+ record_lines = []
+
+ RE_HEADER_NO_TRACEID = re.compile(
+ r"^\[(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}).*")
+ for line in data:
+ header = RE_HEADER_NO_TRACEID.search(line)
+ if header:
+ # Append all previous trace lines of a record
+ if record_lines:
+ record_list.append((date, None, record_lines, fname))
+ record_lines = []
+ # Remember the new date
+ date = header.group(1)
+ record_lines.append(line)
+
+
+def process_file(record_list, traceid_set, fname, opid, otraceid):
+ with open(fname, "r") as infile:
+ data = infile.readlines()
+ pid = None
+ traceid = 0
+ traceid_prev = None
+ undecided_traceid = False
+ date = ""
+ record_lines = []
+
+ # If traceid option was provided the traceid_set will contain just it
+ if otraceid:
+ traceid_set.add(otraceid)
+
+ RE_HEADER = re.compile(
+ r"^\[(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\.\d{6}).*?, .*, "
+ r"traceid=([0-9]+).*\]")
+ RE_INTERFACE_VERSION = re.compile(
+ r"^\s+winbindd_interface_version: \[\S* \((\d+)\)\]")
+ RE_ASYNC_REQUEST = re.compile(
+ r"^\s+process_request_send: "
+ r"\[\S* \((\d+)\)\] Handling async request:")
+ # Example of a header line
+ # [2023/05/01 07:40:45.439049, 3, pid=418844, effective(0, 0), real(0, 0), class=winbind, traceid=37] ../../source3/winbindd/winbindd_misc.c:355(winbindd_interface_version)
+ for line in data:
+ header = RE_HEADER.search(line)
+ if header:
+ # Append all previous trace lines of a record if the traceid is in
+ # the list.
+ if record_lines:
+ record_list.append((date, traceid, record_lines, fname))
+ record_lines = []
+ # Remember the new date and the new traceid
+ date = header.group(1)
+ traceid = header.group(2)
+ if traceid != traceid_prev:
+ traceid_prev = traceid
+ undecided_traceid = True
+ if opid:
+ # Search for lines that identify a new winbind client and the
+ # client PID
+
+ # winbindd_interface_version: [nss_winbind (500725)]: request interface version (version = 32)
+ # process_request_send: [nss_winbind (500725)] Handling async request: SETPWENT
+ interface_version = RE_INTERFACE_VERSION.search(line)
+ async_request = RE_ASYNC_REQUEST.search(line)
+ if interface_version:
+ pid = interface_version.group(1)
+ if undecided_traceid:
+ if pid == opid:
+ traceid_set.add(traceid)
+ undecided_traceid = False
+ if async_request:
+ pid = async_request.group(1)
+ if undecided_traceid:
+ if pid == opid:
+ traceid_set.add(traceid)
+ undecided_traceid = False
+ # For --breakdown add every traceid
+ if not opid and not otraceid:
+ traceid_set.add(traceid)
+
+ record_lines.append(line)
+
+
+def filter_traceids(record_list, traceid_set):
+ llist = []
+ for (d, t, li, f) in record_list:
+ if t in traceid_set:
+ llist.append((d, t, li, f))
+ return llist
+
+
+def filter_flow(record_list):
+ local_list = []
+ for (date, traceid, lines, filename) in record_list:
+ for line in lines:
+ isflow = re.search(r"^(\s+)flow: (.*)", line)
+ if isflow:
+ local_list.append(isflow.group(1) + isflow.group(2))
+ return local_list
+
+
+def filter_flowcompact(flist):
+ local_list = []
+ end_marker = None
+ for fl in flist:
+ if not end_marker:
+ local_list.append(fl)
+ dcerpc_start = re.search(r"^(\s+)-> dcerpc_", fl)
+ if dcerpc_start:
+ end_marker = dcerpc_start.group(1)
+ else:
+ dcerpc_end = re.search(r"^" + end_marker + "<- dcerpc_", fl)
+ if dcerpc_end:
+ end_marker = None
+ local_list.append(fl)
+ return local_list
+
+
+def print_record_list(record_list, file):
+ f_prev = None
+ for (date, traceid, lines, filename) in record_list:
+ # Inform about filename change
+ if filename != f_prev:
+ print("-" * 72, file=file)
+ print("FILE: ", filename, file=file)
+ print("-" * 72, file=file)
+ for line in lines:
+ print(line, end='', file=file)
+ f_prev = filename
+
+# record_list ... list of quadruplets <date, traceid, [trace lines], filename>
+# flow_list ... lines from record_list with 'flow' traces
+# traceid_set ... list of traceids we want to trace
+# with --traceid ... there is a single traceids
+# with --pid ... there are all traceids for the PID
+# with --breakdown ... there are all traceids
+
+
+def setup_parser():
+ parser = ArgumentParser()
+
+ parser.add_argument(
+ "path",
+ type=str,
+ help="logfile or directory"
+ )
+ parser.add_argument(
+ "--traceid",
+ dest="traceid",
+ help="specify the traceid of the trace records",
+ metavar="ID"
+ )
+ parser.add_argument(
+ "--pid",
+ dest="pid",
+ help="specify the pid of winbind client",
+ metavar="PID"
+ )
+ parser.add_argument(
+ "--breakdown",
+ action="store_true",
+ dest="breakdown",
+ default=False,
+ help="breakdown the traces into per traceid files"
+ )
+ parser.add_argument(
+ "--merge-by-timestamp",
+ action="store_true",
+ dest="merge",
+ default=False,
+ help="merge logs by timestamp"
+ )
+ parser.add_argument(
+ "--flow",
+ action="store_true",
+ dest="flow",
+ default=False,
+ help="show the request/sub-request flow traces"
+ )
+ parser.add_argument(
+ "--flow-compact",
+ action="store_true",
+ dest="flowcompact",
+ default=False,
+ help="show the request/sub-request flow traces without dcerpc details"
+ )
+ return parser
+
+
+def main(): # noqa
+ record_list = []
+ flow_list = []
+ traceid_set = set()
+
+ parser = setup_parser()
+ options = parser.parse_args()
+
+ if (not options.traceid and not options.pid and not options.breakdown
+ and not options.merge):
+ print("One of --traceid or --pid is needed"
+ " or --breakdown or --merge-by-timestamp.")
+ sys.exit(1)
+ elif options.traceid and options.pid:
+ print("Only one of --traceid or --pid is allowed.")
+ sys.exit(1)
+ elif options.breakdown and (options.traceid or options.pid):
+ print("--breakdown cannot be combined with --traceid and --pid.")
+ sys.exit(1)
+
+ if options.flow and not options.traceid:
+ print("Option --flow can be used only together with --traceid.")
+ sys.exit(1)
+
+ if options.flowcompact and not options.traceid:
+ print("Option --flow-compact can be used only together with "
+ "--traceid.")
+ sys.exit(1)
+
+ if options.flow and options.flowcompact:
+ print("Only one of --flow or --flow-compact is allowed.")
+ sys.exit(1)
+
+ if not options.path:
+ print("Path to logfile or directory with logs is needed.")
+ sys.exit(1)
+
+ merge_with_no_traceid = (not options.traceid and not options.pid
+ and not options.breakdown) and options.merge
+
+ path = options.path
+ if os.path.isdir(path):
+ for root, dirs, files in os.walk(path):
+ for name in files:
+ if merge_with_no_traceid:
+ process_file_no_traceid(
+ record_list,
+ os.path.join(root, name)
+ )
+ else:
+ process_file(
+ record_list,
+ traceid_set,
+ os.path.join(root, name),
+ options.pid,
+ options.traceid,
+ )
+ elif os.path.isfile(path):
+ if merge_with_no_traceid:
+ process_file_no_traceid(
+ record_list,
+ path
+ )
+ else:
+ process_file(
+ record_list,
+ traceid_set,
+ path,
+ options.pid,
+ options.traceid
+ )
+ else:
+ print(path, "Path is neither file or directory.")
+ sys.exit(1)
+
+ # Sort only using timestamps, no use of traceid
+ if merge_with_no_traceid:
+ record_list.sort()
+ print_record_list(record_list, sys.stdout)
+ sys.exit(0)
+
+ # Keep only records with matching traceids
+ if not options.breakdown:
+ record_list = filter_traceids(record_list, traceid_set)
+
+ if options.breakdown:
+ for traceid in traceid_set:
+ # Full
+ with open("%s.full" % traceid, "w") as full_f:
+ full_l = filter_traceids(record_list, {traceid})
+ if options.merge:
+ full_l.sort()
+ print_record_list(full_l, full_f)
+ # Flow
+ with open("%s.flow" % traceid, "w") as flow_f:
+ flow_l = filter_flow(full_l)
+ for fl in flow_l:
+ print(fl, file=flow_f)
+ # Flow compact
+ with open("%s.flowcompact" % traceid, "w") as flowcompact_f:
+ flowcompact_l = filter_flowcompact(flow_l)
+ for fl in flowcompact_l:
+ print(fl, file=flowcompact_f)
+ elif options.flow:
+ flow_list = filter_flow(record_list)
+ for fl in flow_list:
+ print(fl)
+ elif options.flowcompact:
+ flow_list = filter_flow(record_list)
+ flow_list = filter_flowcompact(flow_list)
+ for fl in flow_list:
+ print(fl)
+ else:
+ if options.merge:
+ record_list.sort()
+ print_record_list(record_list, sys.stdout)
+
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/source3/script/scancvslog.pl b/source3/script/scancvslog.pl
new file mode 100755
index 0000000..c39f911
--- /dev/null
+++ b/source3/script/scancvslog.pl
@@ -0,0 +1,112 @@
+#!/usr/bin/perl
+require"timelocal.pl";
+
+#
+# usage scancvslog.pl logfile starttime tag
+#
+# this will extract all entries from the specified cvs log file
+# that have a date later than or equal to starttime and a tag
+# value of tag. If starttime is not specified, all entries are
+# extracted. If tag is not specified then entries for all
+# branches are extracted. starttime must be specified as
+# "monthname day, year"
+#
+# Example to extract all entries for SAMBA_2_2 branch from the
+# log file named cvs.log
+#
+# scancvslog.pl cvs.log "" SAMBA_2_2
+#
+#
+# To extract all log entries after Jan 10, 1999 (Note month name
+# must be spelled out completely).
+#
+# scancvslog.pl cvs.log "January 10, 1999"
+#
+
+open(INFILE,@ARGV[0]) || die "Unable to open @ARGV[0]\n";
+
+%Monthnum = (
+ "January", 0,
+ "February", 1,
+ "March", 2,
+ "April", 3,
+ "May", 4,
+ "June", 5,
+ "July", 6,
+ "August", 7,
+ "September", 8,
+ "October", 9,
+ "November", 10,
+ "December", 11,
+ "Jan", 0,
+ "Feb", 1,
+ "Mar", 2,
+ "Apr", 3,
+ "May", 4,
+ "Jun", 5,
+ "Jul", 6,
+ "Aug", 7,
+ "Sep", 8,
+ "Oct", 9,
+ "Nov", 10,
+ "Dec", 11
+);
+
+$Starttime = (@ARGV[1]) ? &make_time(@ARGV[1]) : 0;
+$Tagvalue = @ARGV[2];
+
+while (&get_entry) {
+ $_=$Entry[0];
+# get rid of extra white space
+ s/\s+/ /g;
+# get rid of any time string in date
+ s/ \d\d:\d\d:\d\d/,/;
+ s/^Date:\s*\w*\s*(\w*)\s*(\w*),\s*(\w*).*/$1 $2 $3/;
+ $Testtime = &make_time($_);
+ $Testtag = &get_tag;
+ if (($Testtime >= $Starttime) && ($Tagvalue eq $Testtag)) {
+ print join("\n",@Entry),"\n";
+ }
+}
+close(INFILE);
+
+sub make_time {
+ $_ = @_[0];
+ s/,//;
+ ($month, $day, $year) = split(" ",$_);
+ if (($year < 1900)||($day < 1)||($day > 31)||not length($Monthnum{$month})) {
+ print "Bad date format @_[0]\n";
+ print "Date needs to be specified as \"Monthname day, year\"\n";
+ print "eg: \"January 10, 1999\"\n";
+ exit 1;
+ }
+ $year = ($year == 19100) ? 2000 : $year;
+ $month = $Monthnum{$month};
+ $Mytime=&timelocal((0,0,0,$day,$month,$year));
+}
+
+sub get_tag {
+ @Mytag = grep (/Tag:/,@Entry);
+ $_ = @Mytag[0];
+ s/^.*Tag:\s*(\w*).*/$1/;
+ return $_;
+}
+
+sub get_entry {
+ @Entry=();
+ if (not eof(INFILE)) {
+ while (not eof(INFILE)) {
+ $_ = <INFILE>;
+ chomp $_;
+ next if (not ($_));
+ if (/^\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*/) {
+ next if ($#Entry == -1);
+ push(Entry,$_);
+ return @Entry;
+ } else {
+ push(Entry,$_);
+ }
+ }
+ }
+ return @Entry;
+}
diff --git a/source3/script/smbaddshare b/source3/script/smbaddshare
new file mode 100755
index 0000000..704c9f4
--- /dev/null
+++ b/source3/script/smbaddshare
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# smbaddshare
+#
+# Copyright (C) 2015 Christof Schmitt
+#
+# Example script that can be used with the 'add share command' config
+# option. This is mainly intended for use in the Samba selftest suite,
+# please review and adapt it before using elsewhere.
+#
+
+CONF="$1"
+SHARENAME="$2"
+SHAREPATH="$3"
+COMMENT="$4"
+MAX_CONN="$5"
+
+NETCONF="$BINDIR/net --configfile=$CONF conf"
+
+$NETCONF addshare "$SHARENAME" "$SHAREPATH" writeable=no guest_ok=no "$COMMENT"
+RC=$?
+if [ $RC -ne 0 ]; then
+ echo Failure during addshare: rc=$RC
+ exit $RC
+fi
+
+$NETCONF setparm "$SHARENAME" 'max connections' "$MAX_CONN"
+RC=$?
+if [ $RC -ne 0 ]; then
+ echo Failure during setparm for max connections: rc=$RC
+ exit $RC
+fi
diff --git a/source3/script/smbchangeshare b/source3/script/smbchangeshare
new file mode 100755
index 0000000..098e968
--- /dev/null
+++ b/source3/script/smbchangeshare
@@ -0,0 +1,47 @@
+#!/bin/sh
+#
+# smbchangeshare
+#
+# Copyright (C) 2015 Christof Schmitt
+#
+# Example script that can be used with the 'change share command'
+# config option. This is mainly intended for use in the Samba selftest
+# suite, please review and adapt it before using elsewhere.
+#
+
+CONF="$1"
+SHARENAME="$2"
+SHAREPATH="$3"
+COMMENT="$4"
+MAX_CONN="$5"
+CSC_POLICY="$6"
+
+NETCONF="$BINDIR/net --configfile=$CONF conf"
+
+$NETCONF setparm "$SHARENAME" 'path' "$SHAREPATH"
+RC=$?
+if [ $RC -ne 0 ]; then
+ echo Failure during setparm for path: rc=$RC
+ exit $RC
+fi
+
+$NETCONF setparm "$SHARENAME" 'comment' "$COMMENT"
+RC=$?
+if [ $RC -ne 0 ]; then
+ echo Failed during setparm for comment: rc=$RC
+ exit $RC
+fi
+
+$NETCONF setparm "$SHARENAME" 'max connections' "$MAX_CONN"
+RC=$?
+if [ $RC -ne 0 ]; then
+ echo Failure during setparm for max connections: rc=$RC
+ exit $RC
+fi
+
+$NETCONF setparm "$SHARENAME" 'csc policy' "$CSC_POLICY"
+RC=$?
+if [ $RC -ne 0 ]; then
+ echo Failure during setparm for csc policy: rc=$RC
+ exit $RC
+fi
diff --git a/source3/script/smbdeleteshare b/source3/script/smbdeleteshare
new file mode 100755
index 0000000..8f70678
--- /dev/null
+++ b/source3/script/smbdeleteshare
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# smbdeleteshare
+#
+# Copyright (C) 2015 Christof Schmitt
+#
+# Example script that can be used with the 'delete share command'
+# config option. This is mainly intended for use in the Samba selftest
+# suite, please review and adapt it before using elsewhere.
+#
+
+CONF="$1"
+SHARENAME="$2"
+
+NETCONF="$BINDIR/net --configfile=$CONF conf"
+$NETCONF delshare "$SHARENAME"
+RC=$?
+if [ $RC -ne 0 ]; then
+ echo Failure during delshare command: rc=$RC
+ exit $RC
+fi
diff --git a/source3/script/smbtar b/source3/script/smbtar
new file mode 100644
index 0000000..439d085
--- /dev/null
+++ b/source3/script/smbtar
@@ -0,0 +1,181 @@
+#!/bin/sh
+#
+# smbtar script - front end to smbclient
+#
+# Authors: Martin.Kraemer <Martin.Kraemer@mch.sni.de>
+# and Ricky Poulten (ricky@logcam.co.uk)
+#
+# (May need to change shell to ksh for HPUX or OSF for better getopts)
+#
+# sandy nov 3 '98 added -a flag
+#
+# Richard Sharpe, added -c 'tarmode full' so that we back up all files to
+# fix a bug in clitar when a patch was added to stop system and hidden files
+# being backed up.
+
+case $0 in
+# when called by absolute path, assume smbclient is in the same directory
+/*)
+ SMBCLIENT="$(dirname $0)/smbclient"
+ ;;
+*) # you may need to edit this to show where your smbclient is
+ SMBCLIENT="smbclient" ;;
+esac
+
+# These are the default values. You could fill them in if you know what
+# you're doing, but beware: better not store a plain text password!
+server=""
+service="backup" # Default: a service called "backup"
+password=""
+username=$LOGNAME # Default: same user name as in *nix
+verbose="2>/dev/null" # Default: no echo to stdout
+log="-d 2"
+newer=""
+newerarg=""
+blocksize=""
+blocksizearg=""
+clientargs="-c 'tarmode full'"
+tarcmd="c"
+tarargs=""
+cdcmd="\\"
+tapefile=${TAPE-tar.out}
+
+Usage()
+{
+ ex=$1
+ shift
+ echo >&2 "Usage: $(basename $0) [<options>] [<include/exclude files>]
+Function: backup/restore a Windows PC directories to a local tape file
+Options: (Description) (Default)
+ -r Restore from tape file to PC Save from PC to tapefile
+ -i Incremental mode Full backup mode
+ -a Reset archive bit mode Don't reset archive bit
+ -v Verbose mode: echo command Don't echo anything
+ -s <server> Specify PC Server $server
+ -p <password> Specify PC Password $password
+ -x <share> Specify PC Share $service
+ -X Exclude mode Include
+ -N <newer> File for date comparison $(
+ set -- $newer
+ echo $2
+ )
+ -b <blocksize> Specify tape's blocksize $(
+ set -- $blocksize
+ echo $2
+ )
+ -d <dir> Specify a directory in share $cdcmd
+ -l <log> Specify a Samba Log Level $(
+ set -- $log
+ echo $2
+ )
+ -u <user> Specify User Name $username
+ -t <tape> Specify Tape device $tapefile
+"
+ echo >&2 "$@"
+ exit $ex
+}
+
+# echo Params count: $#
+
+# DEC OSF AKA Digital UNIX does not seem to return a value in OPTIND if
+# there are no command line params, so protect us against that ...
+if [ $# = 0 ]; then
+
+ Usage 2 "Please enter a command line parameter!"
+
+fi
+
+while getopts riavl:b:d:N:s:p:x:u:Xt: c; do
+ case $c in
+ r) # [r]estore to Windows (instead of the default "Save from Windows")
+ tarcmd="x"
+ ;;
+ i) # [i]ncremental
+ tarargs=${tarargs}ga
+ clientargs="-c 'tarmode inc'"
+ ;;
+ a) # [a]rchive
+ tarargs=${tarargs}a
+ ;;
+ l) # specify [l]og file
+ log="-d $OPTARG"
+ case "$OPTARG" in
+ [0-9]*) ;;
+ *)
+ echo >&2 "$0: Error, log level not numeric: -l $OPTARG"
+ exit 1
+ ;;
+ esac
+ ;;
+ d) # specify [d]irectory to change to in server's share
+ cdcmd="$OPTARG"
+ ;;
+ N) # compare with a file, test if [n]ewer
+ if [ -f $OPTARG ]; then
+ newer=$OPTARG
+ newerarg="N"
+ else
+ echo >&2 $0: Warning, $OPTARG not found
+ fi
+ ;;
+ X) # Add exclude flag
+ tarargs=${tarargs}X
+ ;;
+ s) # specify [s]erver's share to connect to - this MUST be given.
+ server="$OPTARG"
+ ;;
+ b) # specify [b]locksize
+ blocksize="$OPTARG"
+ case "$OPTARG" in
+ [0-9]*) ;;
+ *)
+ echo >&2 "$0: Error, block size not numeric: -b $OPTARG"
+ exit 1
+ ;;
+ esac
+ blocksizearg="b"
+ ;;
+ p) # specify [p]assword to use
+ password="$OPTARG"
+ ;;
+ x) # specify windows [s]hare to use
+ service="$OPTARG"
+ ;;
+ t) # specify [t]apefile on local host
+ tapefile="$OPTARG"
+ ;;
+ u) # specify [u]sername for connection
+ username="$OPTARG"
+ ;;
+ v) # be [v]erbose and display what's going on
+ verbose=""
+ tarargs=${tarargs}v
+ ;;
+ '?') # any other switch
+ Usage 2 "Invalid switch specified - abort."
+ ;;
+ esac
+done
+
+shift $(expr $OPTIND - 1)
+
+if [ "$server" = "" ] || [ "$service" = "" ]; then
+ Usage 1 "No server or no service specified - abort."
+fi
+
+# if the -v switch is set, the echo the current parameters
+if [ -z "$verbose" ]; then
+ echo "server is $server"
+# echo "share is $service"
+ echo "share is $service\\$cdcmd"
+ echo "tar args is $tarargs"
+# echo "password is $password" # passwords should never be sent to screen
+ echo "tape is $tapefile"
+ echo "blocksize is $blocksize"
+fi
+
+tarargs=${tarargs}${blocksizearg}${newerarg}
+
+eval $SMBCLIENT "'\\\\$server\\$service'" "'$password'" -U "'$username'" \
+ -E $log -D "'$cdcmd'" ${clientargs} \
+ -T${tarcmd}${tarargs} $blocksize $newer $tapefile '${1+"$@"}' $verbose
diff --git a/source3/script/strip_trail_ws.pl b/source3/script/strip_trail_ws.pl
new file mode 100755
index 0000000..c2c39e2
--- /dev/null
+++ b/source3/script/strip_trail_ws.pl
@@ -0,0 +1,18 @@
+#!/usr/bin/perl -w
+
+open( INFILE, "$ARGV[0]" ) || die $@;
+open( OUTFILE, ">$ARGV[0].new" ) || die $@;
+
+while ( <INFILE> ) {
+ $_ =~ s/[ \t\r]*$//;
+ print OUTFILE "$_";
+}
+
+close( INFILE );
+close( OUTFILE );
+
+rename( "$ARGV[0].new", "$ARGV[0]" ) || die @_;
+
+exit( 0 );
+
+
diff --git a/source3/script/tests/dlopen.sh b/source3/script/tests/dlopen.sh
new file mode 100755
index 0000000..a1299b2
--- /dev/null
+++ b/source3/script/tests/dlopen.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Copyright (C) Nalin Dahyabhai <nalin@redhat.com> 2003
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+tempdir=$(mktemp -d /tmp/dlopenXXXXXX)
+test -n "$tempdir" || exit 1
+cat >>$tempdir/dlopen.c <<_EOF
+#include <dlfcn.h>
+#include <stdio.h>
+#include <limits.h>
+#include <sys/stat.h>
+/* Simple program to see if dlopen() would succeed. */
+int main(int argc, char **argv)
+{
+ int i;
+ struct stat st;
+ char buf[PATH_MAX];
+ for (i = 1; i < argc; i++) {
+ if (dlopen(argv[i], RTLD_NOW)) {
+ fprintf(stdout, "dlopen() of \"%s\" succeeded.\n",
+ argv[i]);
+ } else {
+ snprintf(buf, sizeof(buf), "./%s", argv[i]);
+ if ((stat(buf, &st) == 0) && dlopen(buf, RTLD_NOW)) {
+ fprintf(stdout, "dlopen() of \"./%s\" "
+ "succeeded.\n", argv[i]);
+ } else {
+ fprintf(stdout, "dlopen() of \"%s\" failed: "
+ "%s\n", argv[i], dlerror());
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+_EOF
+
+for arg in "$@"; do
+ case "$arg" in
+ "") ;;
+
+ -I* | -D* | -f* | -m* | -g* | -O* | -W*)
+ cflags="$cflags $arg"
+ ;;
+ -l* | -L*)
+ ldflags="$ldflags $arg"
+ ;;
+ /*)
+ modules="$modules $arg"
+ ;;
+ *)
+ modules="$modules $arg"
+ ;;
+ esac
+done
+
+${CC:-gcc} $RPM_OPT_FLAGS $CFLAGS -o $tempdir/dlopen $cflags $tempdir/dlopen.c $ldflags
+
+retval=0
+for module in $modules; do
+ case "$module" in
+ "") ;;
+
+ /*)
+ $tempdir/dlopen "$module"
+ retval=$?
+ ;;
+ *)
+ $tempdir/dlopen ./"$module"
+ retval=$?
+ ;;
+ esac
+done
+
+rm -f $tempdir/dlopen $tempdir/dlopen.c
+rmdir $tempdir
+exit $retval
diff --git a/source3/script/tests/fake_snap.pl b/source3/script/tests/fake_snap.pl
new file mode 100755
index 0000000..d88307e
--- /dev/null
+++ b/source3/script/tests/fake_snap.pl
@@ -0,0 +1,85 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use File::Path qw(rmtree);
+use POSIX ();
+
+sub _untaint_path
+{
+ my ($path) = @_;
+
+ if ($path =~ /^(.*)$/) {
+ return $1;
+ }
+ die "bad path";
+}
+
+sub _create_snapshot
+{
+ my ($base_path) = _untaint_path(shift);
+ my $time_str = POSIX::strftime("%Y.%m.%d-%H.%M.%S" , localtime());
+ my $snap_path = $base_path . "/.snapshots/\@GMT-" . $time_str;
+ my $ret;
+
+ delete @ENV{'BASH_ENV'};
+
+ $ENV{'PATH'} = '/bin:/usr/bin'; # untaint PATH
+ POSIX::mkdir($base_path . "/.snapshots", 0755);
+
+ # add trailing slash to src path to ensure that only contents is copied
+ $ret = system("rsync", "-a", "--exclude=.snapshots/", "${base_path}/",
+ $snap_path);
+ if ($ret != 0) {
+ print STDERR "rsync failed with $ret\n";
+ } else {
+ print "$snap_path\n";
+ }
+
+ return $ret;
+}
+
+sub _delete_snapshot
+{
+ my $base_path = _untaint_path(shift);
+ my $snap_path = _untaint_path(shift);
+
+ # we're doing a recursive delete, so do some sanity checks
+ if ((index($snap_path, $base_path) != 0) || (index($snap_path, ".snapshots") == -1)) {
+ print STDERR "invalid snap_path: $snap_path\n";
+ return -1;
+ }
+
+ $ENV{'PATH'} = '/bin:/usr/bin'; # untaint PATH
+ rmtree($snap_path, {error => \my $err});
+ if (@$err) {
+ for my $diag (@$err) {
+ my ($file, $message) = %$diag;
+ if ($file eq '') {
+ print STDERR "rmtree error: $message\n";
+ } else {
+ print STDERR "rmtree error $file: $message\n";
+ }
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+my $ret;
+my $num_args = $#ARGV + 1;
+my $cmd = shift;
+
+if (($num_args == 2) && ($cmd eq "--check")) {
+ $ret = 0;
+} elsif (($num_args == 2) && ($cmd eq "--create")) {
+ $ret = _create_snapshot($ARGV[0]);
+} elsif (($num_args == 3) && ($cmd eq "--delete")) {
+ $ret = _delete_snapshot($ARGV[0], $ARGV[1]);
+} else {
+ print STDERR "invalid script argument\n";
+ $ret = -1;
+}
+
+exit $ret;
diff --git a/source3/script/tests/full_audit_segfault/run.sh b/source3/script/tests/full_audit_segfault/run.sh
new file mode 100755
index 0000000..a2ba43f
--- /dev/null
+++ b/source3/script/tests/full_audit_segfault/run.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+if [ $# -lt 1 ]; then
+ cat <<EOF
+Usage: run.sh VFSTEST
+EOF
+ exit 1
+fi
+
+TALLOC_FILL_FREE=0
+export TALLOC_FILL_FREE
+
+TESTBASE="$(dirname $0)"
+VFSTEST="$VALGRIND $1"
+shift 1
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+testit "vfstest" "$VFSTEST" -f "$TESTBASE/vfstest.cmd" "$ADDARGS" ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/full_audit_segfault/vfstest.cmd b/source3/script/tests/full_audit_segfault/vfstest.cmd
new file mode 100644
index 0000000..84e93e2
--- /dev/null
+++ b/source3/script/tests/full_audit_segfault/vfstest.cmd
@@ -0,0 +1,3 @@
+load full_audit
+connect
+create_file .
diff --git a/source3/script/tests/getset_quota.py b/source3/script/tests/getset_quota.py
new file mode 100755
index 0000000..39d7024
--- /dev/null
+++ b/source3/script/tests/getset_quota.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+# Unix SMB/CIFS implementation.
+# Tests for smbcquotas
+# Copyright (C) Noel Power 2017
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+import traceback
+import logging
+import os
+
+USER_QUOTAS = 1
+USER_DEFAULT_QUOTAS = 2
+GROUP_QUOTAS = 3
+GROUP_DEFAULT_QUOTAS = 4
+
+#Quota model
+
+class Quota:
+ def __init__(self):
+ self.flags = 0
+ self.quotatype = USER_DEFAULT_QUOTAS
+ self.uid = 0
+ self.usedblocks = 0
+ self.softlimit = 0
+ self.hardlimit = 0
+ self.hardlimit = 0
+ self.usedinodes = 0
+ self.slimitinodes = 0
+ self.hlimitinodes = 0
+
+def quota_to_str(item):
+ result = str(item.flags) + " " + str(item.usedblocks) + " " + str(item.softlimit) + " " + str(item.hardlimit) + " " + str(item.usedinodes) + " " + str(item.slimitinodes) + " " + str(item.hlimitinodes)
+ return result
+
+def quota_to_db_str(item):
+ result = item.uid + " " + str(item.usedblocks) + " " + str(item.softlimit) + " " + str(item.hardlimit) + " " + str(item.usedinodes) + " " + str(item.slimitinodes) + " " + str(item.hlimitinodes)
+ return result
+
+def load_quotas(input_file):
+ fileContents = open(input_file,"r")
+ lineno = 0
+ quotas = []
+ for line in fileContents:
+ if line.strip().startswith("#"):
+ continue
+ content = line.strip().split()
+ quota = Quota()
+ if len(content) < 7:
+ logging.debug("ignoring line %d, doesn't have enough fields\n"%lineno)
+ else:
+ quota.flags = 2
+ quota.uid = content[0]
+ quota.usedblocks = content[1]
+ quota.softlimit = content[2]
+ quota.hardlimit = content[3]
+ quota.usedinodes = content[4]
+ quota.slimitinodes = content[5]
+ quota.hlimitinodes = content[6]
+ quotas.append(quota)
+
+ fileContents.close()
+ return quotas
+
+def set_quotas(quota_list, output_file):
+ filecontents = open(output_file,"w+")
+ if filecontents == None:
+ return False
+ lines = ""
+ for quota in quota_list:
+ next_line = quota_to_db_str(quota)
+ if next_line:
+ lines = lines + next_line + "\n"
+ filecontents.write(lines)
+ filecontents.close()
+ return True
+
+def get_quotas(uid, quota_list):
+ logging.debug("in get_quotas\n")
+ for quota in quota_list:
+ if quota.uid == uid:
+ return quota
+ return None
+
+def main():
+ logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
+ logging.debug("system args passed are %s\n"% str(sys.argv))
+ quota_file_dir = os.path.dirname(sys.argv[0])
+ quota_file_db = os.path.join(quota_file_dir,"quotas.db")
+ logging.debug("quota db is located %s\n", quota_file_db)
+ quota_list = load_quotas(quota_file_db)
+ logging.debug("quotas loaded have %s entries\n", len(quota_list))
+ result = None
+ if len(sys.argv) == 4:
+ # Get Quota
+ directory = sys.argv[1]
+ if sys.argv[2] == "1":
+ query_type = USER_QUOTAS
+ elif sys.argv[2] == "2":
+ query_type = USER_DEFAULT_QUOTAS
+ elif sys.argv[2] == "3":
+ query_type = GROUP_QUOTAS
+ elif sys.argv[2] == "4":
+ query_type = GROUP_DEFAULT_QUOTAS
+ uid = sys.argv[3]
+ quota = get_quotas(uid, quota_list)
+ if quota is None:
+ logging.debug("no result for uid %s"%uid)
+ else:
+ result = quota_to_str(quota)
+ logging.debug("got result for uid %s\n"%uid)
+ if result is None:
+ result = "0 0 0 0 0 0 0"
+ logging.debug("for uid %s returning quotas %s\n"%(uid,result))
+ print("%s"%result)
+ elif len(sys.argv) > 8:
+ # Set Quota
+ quota = Quota()
+ directory = sys.argv[1]
+ quota.query_type = sys.argv[2]
+ quota.uid = sys.argv[3]
+ quota.flags = sys.argv[4]
+ quota.softlimit = sys.argv[5]
+ quota.hardlimit = sys.argv[6]
+ quota.slimitinodes = sys.argv[7]
+ quota.hlimitinodes = sys.argv[8]
+ found = get_quotas(quota.uid, quota_list)
+ if found:
+ found.query_type = quota.query_type
+ found.uid = quota.uid
+ found.flags = quota.flags
+ found.softlimit = quota.softlimit
+ found.hardlimit = quota.hardlimit
+ found.slimitinodes = quota.slimitinodes
+ found.hlimitinodes = quota.hlimitinodes
+ else:
+ quota_list.append(quota)
+ if set_quotas(quota_list,quota_file_db):
+ print ("%s\n"%quota_to_str(quota_list[-1]))
+ return
+if __name__ == '__main__':
+ main()
diff --git a/source3/script/tests/printing/modprinter.pl b/source3/script/tests/printing/modprinter.pl
new file mode 100755
index 0000000..28817db
--- /dev/null
+++ b/source3/script/tests/printing/modprinter.pl
@@ -0,0 +1,140 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use Getopt::Long;
+use Cwd qw(abs_path);
+
+my $opt_help = 0;
+my $opt_smb_conf = undef;
+my $opt_add = 0;
+my $opt_delete = 0;
+
+my $result = GetOptions(
+ 'help|h|?' => \$opt_help,
+ 'smb_conf|s=s' => \$opt_smb_conf,
+ 'add|a' => \$opt_add,
+ 'delete|d' => \$opt_delete
+);
+
+sub usage($;$)
+{
+ my ($ret, $msg) = @_;
+
+ print $msg."\n\n" if defined($msg);
+
+ print "usage:
+
+ --help|-h|-? Show this help.
+
+ --smb_conf|-s <path> Path of the 'smb.conf' file.
+
+ --add|-a 'add' a printer.
+ --delete|-d 'delete' a printer.
+
+ printer_name share_name port_name driver_name location XX remote_machine
+";
+ exit($ret);
+}
+
+usage(1) if (not $result);
+
+usage(0) if ($opt_help);
+
+if (!$opt_add && !$opt_delete) {
+ usage(1, "invalid: neither --add|-a nor --delete|-d set");
+}
+
+if (!$opt_smb_conf) {
+ usage(1, "invalid: no smb.conf file set");
+}
+
+my @argv = @ARGV;
+
+my $printer_name = shift(@argv);
+my $share_name = shift(@argv);
+my $port_name = shift(@argv);
+my $driver_name = shift(@argv);
+my $location = shift(@argv);
+my $win9x_driver_location = shift(@argv);
+my $remote_machine = shift(@argv);
+
+if (!defined($share_name) || length($share_name) == 0) {
+ $share_name = $printer_name;
+}
+
+if (!defined($share_name)) {
+ die "share name not defined";
+}
+
+my $smb_conf_file = $opt_smb_conf;
+if ($smb_conf_file =~ /^(.*)$/) {
+ $smb_conf_file = $1; # untaint file name
+} else {
+ die "Invalid file name $smb_conf_file";
+}
+
+my $tmp = $smb_conf_file.$$;
+
+my $section = undef;
+my $within_section = 0;
+my $found_section = 0;
+
+open(CONFIGFILE_NEW, "+>$tmp") || die "Unable top open conf file $tmp";
+
+open (CONFIGFILE, "+<$smb_conf_file") || die "Unable to open config file $smb_conf_file";
+while (<CONFIGFILE>) {
+ my $line = $_;
+ chomp($_);
+ $_ =~ s/^\s*//;
+ $_ =~ s/\s*$//;
+ if (($_ =~ /^#/) || ($_ =~ /^;/)) {
+ print CONFIGFILE_NEW $line;
+ next;
+ }
+ if ($_ =~ /^\[.*\]$/) {
+ $_ = substr($_, 1, length($_)-2);
+ if (length($_)) {
+ $section = $_;
+ } else {
+ die "invalid section found";
+ }
+ if ($section eq $share_name) {
+ $found_section = 1;
+ if ($opt_add) {
+ exit 0;
+# die("share $share_name already exists\n");
+ }
+ if ($opt_delete) {
+ $within_section = 1;
+ next;
+ }
+ } else {
+ print CONFIGFILE_NEW $line;
+ $within_section = 0;
+ }
+ next;
+ } else {
+ if ($within_section == 1) {
+ next;
+ }
+ print CONFIGFILE_NEW $line;
+ }
+}
+if ($opt_add) {
+ print CONFIGFILE_NEW "[$share_name]\n\tprintable = yes\n\tpath = /tmp\n";
+}
+close (CONFIGFILE);
+close (CONFIGFILE_NEW);
+
+if ($opt_delete && ($found_section == 0)) {
+ die "share $share_name not found";
+}
+
+delete @ENV{"BASH_ENV"};
+
+$ENV{'PATH'} = '/bin:/usr/bin'; # untaint PATH
+system("cp", "$tmp", "$smb_conf_file");
+unlink $tmp;
+
+exit 0;
diff --git a/source3/script/tests/printing/printing_var_exp_lpr_cmd.sh b/source3/script/tests/printing/printing_var_exp_lpr_cmd.sh
new file mode 100755
index 0000000..c25b49a
--- /dev/null
+++ b/source3/script/tests/printing/printing_var_exp_lpr_cmd.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+logfile="${SELFTEST_TMPDIR}/${USER}_printing_var_exp.log"
+
+rm -f "$logfile"
+
+for i in $(seq 1 $#); do
+ eval echo "arg $i: \$$i" >>"$logfile"
+done
diff --git a/source3/script/tests/smbspool_argv_wrapper.c b/source3/script/tests/smbspool_argv_wrapper.c
new file mode 100644
index 0000000..6f84f3b
--- /dev/null
+++ b/source3/script/tests/smbspool_argv_wrapper.c
@@ -0,0 +1,72 @@
+/*
+ Wrapper for smbspool to test Device URI in argv[0]
+
+ Copyright (C) Bryan Mason 2019
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+
+/*
+ * Before calling a backend like smbspool, CUPS will set argv[0] to
+ * the Device URI. This program wraps a program like smbspool and
+ * sets argv[0] to the device URI before exec()ing the actual backend
+ * program.
+ */
+
+int main(int argc, char *argv[], char *envp[])
+{
+ char **new_argv;
+ char *exec_path;
+ int a;
+ int rv;
+/*
+ * Expected parameters:
+ *
+ * smbspool_argv_wrapper smbspool uri job user title copies opts file(s)
+ * argv[0] 1 2 3 4 5 6 7 8
+ *
+ */
+ /* Allocate memory for the new arguments (exit on failure). */
+ new_argv = calloc(argc, sizeof(char *));
+ if (new_argv == 0) {
+ exit(ENOMEM);
+ }
+
+ /* Save the path to the smbspool executable */
+ exec_path = argv[1];
+
+ /*
+ * Shift the rest of the args so smbspool is called with:
+ *
+ * uri job user title copies opts file(s)
+ * argv[0] 1 2 3 4 5 6
+ */
+
+ for (a = 2; a < argc; a++) {
+ new_argv[a-2] = argv[a];
+ }
+
+ /* Execute smbspool with new arguments */
+ rv = execve(exec_path, new_argv, envp);
+ if (rv == -1) {
+ exit(errno);
+ }
+
+ /* Avoid compiler error/warning */
+ return 0;
+}
diff --git a/source3/script/tests/stream-depot/run.sh b/source3/script/tests/stream-depot/run.sh
new file mode 100755
index 0000000..bcc85b6
--- /dev/null
+++ b/source3/script/tests/stream-depot/run.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: run.sh VFSTEST PREFIX
+EOF
+ exit 1
+fi
+
+TESTBASE=$(dirname $0)
+VFSTEST=$1
+PREFIX=$2
+shift 2
+ADDARGS="$*"
+
+VFSTEST_PREFIX=vfstest
+VFSTEST_TMPDIR=$(mktemp -d ${PREFIX}/${VFSTEST_PREFIX}_XXXXXX)
+
+incdir=$(dirname $0)/../../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $VFSTEST_TMPDIR || exit 1
+
+testit "vfstest" $VFSTEST -f $TESTBASE/vfstest.cmd $ADDARGS || failed=$(expr $failed + 1)
+testname=".streams check"
+subunit_start_test $testname
+NUM=$(find .streams | wc -l)
+if [ $NUM -ne 3 ]; then
+ echo "streams_depot left ${NUM} in .streams, expected 3" | subunit_fail_test $testname
+ failed=$(expr $failed + 1)
+else
+ subunit_pass_test $testname
+fi
+
+exit $failed
diff --git a/source3/script/tests/stream-depot/vfstest.cmd b/source3/script/tests/stream-depot/vfstest.cmd
new file mode 100644
index 0000000..1400546
--- /dev/null
+++ b/source3/script/tests/stream-depot/vfstest.cmd
@@ -0,0 +1,5 @@
+connect
+mkdir x
+open x:y RC 0770
+unlink x:y
+rmdir x
diff --git a/source3/script/tests/test_acl_xattr.sh b/source3/script/tests/test_acl_xattr.sh
new file mode 100755
index 0000000..d0eec66
--- /dev/null
+++ b/source3/script/tests/test_acl_xattr.sh
@@ -0,0 +1,156 @@
+#!/bin/sh
+
+# this tests acl_xattr config parameter "ignore system acl"
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 SERVER USERNAME PASSWORD PREFIX SMBCLIENT SMBCACLS
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+USERNAME="$2"
+PASSWORD="$3"
+PREFIX="$4"
+SMBCLIENT="$5"
+SMBCACLS="$6"
+shift 6
+ADDARGS="$*"
+SMBCLIENT="$VALGRIND ${SMBCLIENT} ${ADDARGS}"
+SMBCACLS="$VALGRIND ${SMBCACLS} ${ADDARGS}"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+setup_remote_file()
+{
+ local share=$1
+ local fname="$share.$$"
+ local local_fname=$PREFIX/$fname
+ touch $local_fname
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "rm $fname"
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "ls" | grep "$fname" && exit 1
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "lcd $PREFIX; put $fname" || exit 1
+}
+
+smbcacls_x()
+{
+ local share=$1
+ local fname="$share.$$"
+
+ # skip with SMB1
+ echo "$ADDARGS" | grep mNT1 && exit 0
+
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD "$fname" -x || exit 1
+ mxac=$($SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD "$fname" -x | awk '/Maximum access/ {print $3}')
+
+ echo "mxac: $mxac"
+ if test "$mxac" != "0x1f01ff"; then
+ exit 1
+ fi
+}
+
+nt_affects_posix()
+{
+ local share=$1
+ local expected=$2
+ local b4
+ local af
+ local fname="$share.$$"
+ b4=$($SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "posix; getfacl $fname" 2>/dev/null) || exit 1
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -a "ACL:$SERVER\force_user:ALLOWED/0x0/READ" 2>/dev/null || exit 1
+ af=$($SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "posix; getfacl $fname" 2>/dev/null) || exit 1
+ echo "before: $b4"
+ echo "after: $af"
+ echo "${b4}" | grep -q "^# owner:" || exit 1
+ echo "${af}" | grep -q "^# owner:" || exit 1
+ if test "$expected" = "true"; then
+ test "$b4" != "$af"
+ else
+ test "$b4" = "$af"
+ fi
+}
+
+nt_affects_chown()
+{
+ local share=$1
+ local b4_expected
+ local af_expected
+ local b4_actual
+ local af_actual
+ local fname="$share.$$"
+
+ echo -n "determining uid of $USERNAME..."
+ b4_expected=$(getent passwd $USERNAME) || exit 1
+ b4_expected=$(echo "$b4_expected" | awk -F: '{print $3}')
+ echo "$b4_expected"
+
+ echo -n "determining uid of force_user..."
+ af_expected=$(getent passwd force_user) || exit 1
+ af_expected=$(echo "$af_expected" | awk -F: '{print $3}')
+ echo "$af_expected"
+
+ #basic sanity...
+ test "$b4_expected != $af_expected" || exit 1
+
+ b4_actual=$($SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "posix; getfacl $fname" 2>/dev/null) || exit 1
+ echo "${b4_actual}" | grep -q "^# owner:" || exit 1
+ b4_actual=$(echo "$b4_actual" | sed -rn 's/^# owner: (.*)/\1/p')
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -a "ACL:$SERVER\force_user:ALLOWED/0x0/FULL" || exit 1
+ $SMBCACLS //$SERVER/$share $fname -U force_user%$PASSWORD -C force_user 2>/dev/null || exit 1
+ af_actual=$($SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "posix; getfacl $fname" 2>/dev/null) || exit 1
+ echo "${af_actual}" | grep -q "^# owner:" || exit 1
+ af_actual=$(echo "$af_actual" | sed -rn 's/^# owner: (.*)/\1/p')
+ echo "before: $b4_actual"
+ echo "after: $af_actual"
+ test "$b4_expected" = "$b4_actual" && test "$af_expected" = "$af_actual"
+}
+
+nt_affects_chgrp()
+{
+ local share=$1
+ local b4_expected
+ local af_expected
+ local b4_actual
+ local af_actual
+ local fname="$share.$$"
+
+ echo -n "determining gid of domusers..."
+ b4_expected=$(getent group domusers) || exit 1
+ b4_expected=$(echo "$b4_expected" | awk -F: '{print $3}')
+ echo "$b4_expected"
+
+ echo -n "determining gid of domadmins..."
+ af_expected=$(getent group domadmins) || exit 1
+ af_expected=$(echo "$af_expected" | awk -F: '{print $3}')
+ echo "$af_expected"
+
+ #basic sanity...
+ test "$b4_expected" != "$af_expected" || exit 1
+
+ b4_actual=$($SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "posix; getfacl $fname" 2>/dev/null) || exit 1
+ echo "${b4_actual}" | grep -q "^# group:" || exit 1
+ b4_actual=$(echo "$b4_actual" | sed -rn 's/^# group: (.*)/\1/p')
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -G domadmins 2>/dev/null || exit 1
+ af_actual=$($SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "posix; getfacl $fname" 2>/dev/null) || exit 1
+ echo "${af_actual}" | grep -q "^# group:" || exit 1
+ af_actual=$(echo "$af_actual" | sed -rn 's/^# group: (.*)/\1/p')
+ echo "before: $b4_actual"
+ echo "after: $af_actual"
+ test "$af_expected" != "$b4_actual" && test "$af_expected" = "$af_actual"
+}
+
+testit "setup remote file tmp" setup_remote_file tmp
+testit "setup remote file ign_sysacls" setup_remote_file ign_sysacls
+testit "smbcacls -x" smbcacls_x tmp
+testit "nt_affects_posix tmp" nt_affects_posix tmp "true"
+testit "nt_affects_posix ign_sysacls" nt_affects_posix ign_sysacls "false"
+testit "setup remote file tmp" setup_remote_file tmp
+testit "setup remote file ign_sysacls" setup_remote_file ign_sysacls
+testit "nt_affects_chown tmp" nt_affects_chown tmp
+testit "nt_affects_chown ign_sysacls" nt_affects_chown ign_sysacls
+testit "setup remote file tmp" setup_remote_file tmp
+testit "setup remote file ign_sysacls" setup_remote_file ign_sysacls
+testit "nt_affects_chgrp tmp" nt_affects_chgrp tmp
+testit "nt_affects_chgrp ign_sysacls" nt_affects_chgrp ign_sysacls
diff --git a/source3/script/tests/test_aio_outstanding.sh b/source3/script/tests/test_aio_outstanding.sh
new file mode 100755
index 0000000..a497181
--- /dev/null
+++ b/source3/script/tests/test_aio_outstanding.sh
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+#
+# Test terminating an smbclient connection with outstanding
+# aio requests.
+#
+# Note this is designed to be run against
+# the aio_delay_inject share which is preconfigured
+# with 2 second delays on pread/pwrite.
+
+if [ $# -lt 4 ]; then
+ echo Usage: test_aio_outstanding.sh \
+ SERVERCONFFILE SMBCLIENT IP aio_delay_inject_sharename
+ exit 1
+fi
+
+CONF=$1
+SMBCLIENT=$2
+SERVER=$3
+SHARE=$4
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+#
+# Note if we already have any panics in the smbd log.
+#
+panic_count_0=$(grep -c PANIC $SMBD_TEST_LOG)
+
+# Create the smbclient communication pipes.
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+mkfifo smbclient-stdin smbclient-stdout smbclient-stderr
+
+# Create a large-ish testfile
+rm aio_outstanding_testfile
+head -c 20MB /dev/zero >aio_outstanding_testfile
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \
+ <smbclient-stdin >smbclient-stdout 2>smbclient-stderr &
+CLIENT_PID=$!
+
+sleep 1
+
+exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr
+
+# consume the smbclient startup messages
+head -n 1 <&101
+
+# Ensure we're putting a fresh file.
+echo "del aio_outstanding_testfile" >&100
+echo "put aio_outstanding_testfile" >&100
+
+sleep 2
+
+# Terminate the smbclient write to the aio_delay_inject share whilst
+# we have outstanding writes.
+kill $CLIENT_PID
+
+sleep 1
+
+# Ensure the panic count didn't change.
+#
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14301
+#
+
+panic_count_1=$(grep -c PANIC $SMBD_TEST_LOG)
+
+# Rerun smbclient to remove the testfile on the server.
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr aio_outstanding_testfile
+mkfifo smbclient-stdin smbclient-stdout
+
+${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \
+ <smbclient-stdin >smbclient-stdout &
+
+sleep 1
+
+exec 100>smbclient-stdin 101<smbclient-stdout
+
+echo "del aio_outstanding_testfile" >&100
+echo "exit" >&100
+
+sleep 2
+
+rm -f smbclient-stdin smbclient-stdout aio_outstanding_testfile
+
+testit "check_panic" test $panic_count_0 -eq $panic_count_1 ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_async_req.sh b/source3/script/tests/test_async_req.sh
new file mode 100755
index 0000000..27b4680
--- /dev/null
+++ b/source3/script/tests/test_async_req.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+SOCKET_WRAPPER_IPV4_NETWORK="127.0.0.0"
+export SOCKET_WRAPPER_IPV4_NETWORK
+
+testit "async_connect_send" $VALGRIND $BINDIR/async_connect_send_test ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_bad_auditnames.sh b/source3/script/tests/test_bad_auditnames.sh
new file mode 100755
index 0000000..69ddf14
--- /dev/null
+++ b/source3/script/tests/test_bad_auditnames.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+# this tests a full audit share with bad VFS
+# names will not allow connection.
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15098
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: $0 SERVER SHARE USERNAME PASSWORD SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SHARE="$2"
+USERNAME="$3"
+PASSWORD="$4"
+SMBCLIENT="$5"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir/subunit.sh"
+
+can_connect()
+{
+ $SMBCLIENT //"$SERVER"/"$SHARE" -U"$USERNAME"%"$PASSWORD" -c "ls" | grep "tree connect failed: NT_STATUS_UNSUCCESSFUL" >/dev/null 2>&1
+}
+
+testit "Cannot connect to share $SHARE" can_connect || failed=$((failed + 1))
diff --git a/source3/script/tests/test_bug15435_widelink_dfs.sh b/source3/script/tests/test_bug15435_widelink_dfs.sh
new file mode 100755
index 0000000..f0705ca
--- /dev/null
+++ b/source3/script/tests/test_bug15435_widelink_dfs.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# regression test for dfs access with wide links enabled on dfs share
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_bug15435_widelink_dfs.sh SERVER SERVER_IP USERNAME PASSWORD SMBCLIENT CONFIGURATION <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+smbclient="$5"
+CONFIGURATION="$6"
+shift 6
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+# TEST
+test_smbclient "smbclient as $DOMAIN\\$USERNAME" 'ls' "//$SERVER/msdfs-share-wl" -U$DOMAIN\\$USERNAME%$PASSWORD $ADDARGS -c 'cd msdfs-src1' || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_chdir_cache.sh b/source3/script/tests/test_chdir_cache.sh
new file mode 100755
index 0000000..29f54d1
--- /dev/null
+++ b/source3/script/tests/test_chdir_cache.sh
@@ -0,0 +1,122 @@
+#!/usr/bin/env bash
+#
+# Ensure we get a chdir_current_service error if CHDIR fails with EACCESS
+# for an SMB2 request.
+#
+# BUG:https://bugzilla.samba.org/show_bug.cgi?id=14682
+#
+# Copyright (C) 2021 Jeremy Allison
+
+if [ $# -lt 5 ]; then
+ echo Usage: test_chdir_cache.sh \
+ --configfile=SERVERCONFFILE SMBCLIENT SMBCONTROL SERVER SHARE PREFIX TESTENV
+ exit 1
+fi
+
+CONF=$1
+shift 1
+SMBCLIENT=$1
+shift 1
+SMBCONTROL=$1
+shift 1
+SERVER=$1
+shift 1
+SHARE=$1
+shift 1
+PREFIX=${1}
+shift 1
+TESTENV=${1}
+shift 1
+
+PREFIX_ABS="$(readlink -f "${PREFIX}")"
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+conf_dir=$(dirname ${SERVERCONFFILE})
+
+error_inject_conf=${conf_dir}/error_inject.conf
+rm -f ${error_inject_conf}
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+mkfifo smbclient-stdin smbclient-stdout smbclient-stderr
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \
+ <smbclient-stdin >smbclient-stdout 2>smbclient-stderr &
+CLIENT_PID=$!
+
+log_file="${PREFIX_ABS}/${TESTENV}/smbd_test.log"
+# Add support for "SMBD_DONT_LOG_STDOUT=1"
+if [ -r "${PREFIX_ABS}/${TESTENV}/logs/log.smbd" ]; then
+ log_file="${PREFIX_ABS}/${TESTENV}/logs/log.smbd"
+fi
+
+# Count the number of chdir_current_service: vfs_ChDir.*failed: Permission denied
+# errors that are already in the log (should be zero).
+num_errs=$(grep "chdir_current_service: vfs_ChDir.*failed: Permission denied" ${log_file} | wc -l)
+
+sleep 1
+
+exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr
+
+# consume the smbclient startup messages
+head -n 1 <&101
+
+# Do an 'ls' as ${USER} to make sure we've done a CHDIR into
+# the share directory.
+echo "ls" >&100
+
+# consume the smbclient output
+head -n 4 <&101
+
+# Now change user to user2, and connect to the share.
+# This should leave us in the same share directory.
+echo "logon user2 ${PASSWORD}" >&100
+echo "tcon ${SHARE}" >&100
+
+# consume the smbclient output
+head -n 4 <&101
+
+# Ensure any chdir will give EACCESS.
+echo "error_inject:chdir = EACCES" >${error_inject_conf}
+testit "reload config 1" \
+ "${SMBCONTROL}" "${CONF}" smbd reload-config ||
+ failed=$((failed + 1))
+
+sleep 1
+
+# Do an 'ls' as user2. Changing users should have
+# deleted the CHDIR cache, so we should now see
+# a chdir_current_service: vfs_ChDir.*failed: Permission denied
+# error message in the log.
+echo 'ls' >&100
+
+kill ${CLIENT_PID}
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+
+# Remove the chdir inject.
+rm -f ${error_inject_conf}
+testit "reload config 2" \
+ "${SMBCONTROL}" "${CONF}" smbd reload-config ||
+ failed=$((failed + 1))
+
+# Now look for chdir_current_service: vfs_ChDir.*failed: Permission denied
+# in the smb log. There should be one more than before.
+
+num_errs1=$(grep "chdir_current_service: vfs_ChDir.*failed: Permission denied" ${log_file} | wc -l)
+
+testit "Verify we got at least one chdir error" \
+ test $num_errs1 -gt $num_errs || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_close_denied_share.sh b/source3/script/tests/test_close_denied_share.sh
new file mode 100755
index 0000000..2e6e285
--- /dev/null
+++ b/source3/script/tests/test_close_denied_share.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env bash
+#
+# Test smbcontrol close-denied-share command.
+#
+# Copyright (C) 2020 Volker Lendecke
+
+if [ $# -lt 6 ]; then
+ echo Usage: test_close_denied_share.sh \
+ SERVERCONFFILE SHARESEC SMBCLIENT SMBCONTROL IP SHARE
+ exit 1
+fi
+
+CONF=$1
+SHARESEC=$2
+SMBCLIENT=$3
+SMBCONTROL=$4
+SERVER=$5
+SHARE=$6
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+rm -f smbclient-stdin smbclient-stdout
+mkfifo smbclient-stdin smbclient-stdout
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \
+ <smbclient-stdin >smbclient-stdout &
+CLIENT_PID=$!
+
+sleep 1
+
+exec 100>smbclient-stdin 101<smbclient-stdout
+
+# consume the smbclient startup message
+
+head -n 1 <&101
+
+testit "smbcontrol" ${SMBCONTROL} ${CONF} smbd close-denied-share ${SHARE} ||
+ failed=$(expr $failed + 1)
+sleep 1
+
+echo dir >&100
+
+COUNT=$(head -n 2 <&101 |
+ grep NT_STATUS_NETWORK_NAME_DELETED |
+ wc -l)
+testit "Verify close-denied-share did not kill valid client" \
+ test $COUNT -eq 0 || failed=$(expr $failed + 1)
+
+testit "Deny access" ${SHARESEC} ${CONF} --replace S-1-1-0:DENIED/0x0/FULL \
+ ${SHARE} || failed=$(expr $failed + 1)
+
+testit "smbcontrol" ${SMBCONTROL} ${CONF} smbd close-denied-share ${SHARE} ||
+ failed=$(expr $failed + 1)
+sleep 1
+
+echo dir >&100
+
+COUNT=$(head -n 2 <&101 |
+ grep NT_STATUS_NETWORK_NAME_DELETED |
+ wc -l)
+testit "Verify close-denied-share did kill now-invalid client" \
+ test $COUNT -eq 1 || failed=$(expr $failed + 1)
+
+kill ${CLIENT_PID}
+rm -f smbclient-stdin smbclient-stdout
+
+testit "Allow access" ${SHARESEC} ${CONF} --replace S-1-1-0:ALLOWED/0x0/FULL \
+ ${SHARE} || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_deadtime.sh b/source3/script/tests/test_deadtime.sh
new file mode 100755
index 0000000..3d6368e
--- /dev/null
+++ b/source3/script/tests/test_deadtime.sh
@@ -0,0 +1,67 @@
+#!/usr/bin/env bash
+#
+# Test deadtime parameter
+#
+
+if [ $# -lt 1 ]; then
+ echo Usage: test_deadtime.sh IP
+ exit 1
+fi
+
+server=$1
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+failed=0
+
+smbclient="$BINDIR/smbclient"
+smbcontrol="$BINDIR/smbcontrol"
+
+global_inject_conf=$(dirname $SMB_CONF_PATH)/global_inject.conf
+
+echo "deadtime = 1" >$global_inject_conf
+$smbcontrol smbd reload-config
+
+cd $SELFTEST_TMPDIR || exit 1
+
+# Create the smbclient communication pipes.
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+mkfifo smbclient-stdin smbclient-stdout smbclient-stderr
+
+export CLI_FORCE_INTERACTIVE=1
+export SAMBA_DEPRECATED_SUPPRESS=1
+
+# This gets inherited by smbclient and is required to smbclient doesn't get
+# killed by an unhandled SIGPIPE when writing an SMB2 KEEPALIVE packet to the
+# connection fd that was already closed by the server.
+trap "" SIGPIPE
+
+$smbclient //$server/tmp -U${USER}%${PASSWORD} \
+ <smbclient-stdin >smbclient-stdout 2>smbclient-stderr &
+client_pid=$!
+
+sleep 1
+
+exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr
+
+# consume the smbclient startup message
+head -n 1 <&101
+
+sleep 70
+
+err=$(head -n 1 <&102)
+echo "err: $err"
+
+kill $client_pid
+
+echo "$err" | grep NT_STATUS_CONNECTION_DISCONNECTED
+testit "deadtime" test $? -eq 0 || failed=$(expr $failed + 1)
+
+echo "" >$global_inject_conf
+$smbcontrol smbd reload-config
+
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+
+testok $0 $failed
diff --git a/source3/script/tests/test_delete_stream.sh b/source3/script/tests/test_delete_stream.sh
new file mode 100755
index 0000000..43d591e
--- /dev/null
+++ b/source3/script/tests/test_delete_stream.sh
@@ -0,0 +1,123 @@
+#!/bin/sh
+#
+# this verifies that deleting a stream uses the correct ACL
+# when using vfs_acl_xattr.
+#
+
+if [ $# -lt 9 ]; then
+ echo "Usage: $0 SERVER SERVER_IP USERNAME PASSWORD PREFIX SMBCLIENT SMBCACLS NET SHARE"
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+PREFIX="$5"
+SMBCLIENT="$6"
+SMBCACLS="$7"
+NET="$8"
+SHARE="$9"
+
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+SMBCACLS="$VALGRIND ${SMBCACLS}"
+NET="$VALGRIND ${NET}"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+
+setup_testfile()
+{
+ touch $PREFIX/file
+ echo stream > $PREFIX/stream
+
+ $SMBCLIENT //$SERVER/$SHARE -U $USERNAME%$PASSWORD -c "mkdir dir" || return 1
+ $SMBCLIENT //$SERVER/$SHARE -U $USERNAME%$PASSWORD -c "lcd $PREFIX; put file dir/file" || return 1
+ $SMBCLIENT //$SERVER/$SHARE -U $USERNAME%$PASSWORD -c "lcd $PREFIX; put stream dir/file:stream" || return 1
+
+ rm $PREFIX/file
+ rm $PREFIX/stream
+
+ #
+ # Add full control ACE to the file and an ACL without "DELETE" on the
+ # parent directory
+ #
+
+ $SMBCACLS //$SERVER/$SHARE -U $USERNAME%$PASSWORD -S "ACL:Everyone:ALLOWED/0x0/0x1bf" dir || return 1
+ $SMBCACLS //$SERVER/$SHARE -U $USERNAME%$PASSWORD -a "ACL:Everyone:ALLOWED/0x0/0x101ff" dir/file || return 1
+}
+
+remove_testfile()
+{
+ $SMBCACLS //$SERVER/$SHARE -U $USERNAME%$PASSWORD -S "ACL:Everyone:ALLOWED/0x0/0x101ff" dir/file || return 1
+ $SMBCACLS //$SERVER/$SHARE -U $USERNAME%$PASSWORD -S "ACL:Everyone:ALLOWED/0x0/0x101ff" dir || return 1
+ $SMBCLIENT //$SERVER/$SHARE -U $USERNAME%$PASSWORD -c "rm dir/file" || return 1
+ $SMBCLIENT //$SERVER/$SHARE -U $USERNAME%$PASSWORD -c "rmdir dir" || return 1
+}
+
+set_win_owner()
+{
+ local owner=$1
+
+ $SMBCACLS //$SERVER/$SHARE dir/file -U $USERNAME%$PASSWORD -C "$owner" || return 1
+}
+
+delete_stream()
+{
+ #
+ # Setup a file with a stream where we're not the owner and
+ # have delete rights. Bug 15126 would trigger a fallback to
+ # "acl_xattr:default acl style" because fetching the stored
+ # ACL would fail. The stored ACL allows deleting the stream
+ # but the synthesized default ACL does not, so the deletion
+ # of the stream should work, but it fails if we have the bug.
+ #
+
+ # Now try deleting the stream
+ out=$($SMBCLIENT //$SERVER/$SHARE -U $USERNAME%$PASSWORD -c "wdel 0x20 dir/file:stream") || return 1
+
+ #
+ # Bail out in case we get any sort of NT_STATUS_* error, should be
+ # NT_STATUS_ACCESS_DENIED, but let's not slip through any other error.
+ #
+ echo "$out" | grep NT_STATUS_ && return 1
+
+ return 0
+}
+
+win_owner_is()
+{
+ local expected_owner=$1
+ local actual_owner
+
+ $SMBCACLS //$SERVER/$SHARE dir/file -U $USERNAME%$PASSWORD
+ actual_owner=$($SMBCACLS //$SERVER/$SHARE dir/file -U $USERNAME%$PASSWORD | sed -rn 's/^OWNER:(.*)/\1/p')
+ echo "actual_owner = $actual_owner"
+ if ! test "x$actual_owner" = "x$expected_owner"; then
+ echo "Actual owner of dir/file is $actual_owner', expected $expected_owner"
+ return 1
+ fi
+ return 0
+}
+
+# Create a testfile
+testit "create testfile" setup_testfile $SHARE || exit 1
+
+# Grant SeRestorePrivilege to the user so we can change the owner
+testit "grant SeRestorePrivilege" $NET rpc rights grant $USERNAME SeRestorePrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || exit 1
+
+# We have SeRestorePrivilege, so both give and take ownership must succeed
+testit "give owner with SeRestorePrivilege" set_win_owner "$SERVER\user1" || exit 1
+testit "verify owner" win_owner_is "$SERVER/user1" || exit 1
+
+# Now try to remove the stream on the testfile
+testit "delete stream" delete_stream $SHARE afile || exit 1
+
+# Remove testfile
+testit "remove testfile" remove_testfile $SHARE || exit 1
+
+# Revoke SeRestorePrivilege, give ownership must fail now with NT_STATUS_INVALID_OWNER
+testit "revoke SeRestorePrivilege" $NET rpc rights revoke $USERNAME SeRestorePrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || exit 1
+
+exit 0
diff --git a/source3/script/tests/test_delete_veto_files_only_rmdir.sh b/source3/script/tests/test_delete_veto_files_only_rmdir.sh
new file mode 100755
index 0000000..08f257f
--- /dev/null
+++ b/source3/script/tests/test_delete_veto_files_only_rmdir.sh
@@ -0,0 +1,182 @@
+#!/bin/sh
+#
+# Check smbclient can (or cannot) delete a directory containing dangling symlinks.
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14879
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 SERVER SERVER_IP USERNAME PASSWORD SHAREPATH SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+USERNAME=${3}
+PASSWORD=${4}
+SHAREPATH=${5}
+SMBCLIENT=${6}
+shift 6
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$@"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+rmdir_path="$SHAREPATH/dir"
+
+#
+# Using the share "[delete_veto_files_only]" we CAN delete
+# a directory containing only a dangling symlink.
+#
+test_dangle_symlink_delete_veto_rmdir()
+{
+ local dangle_symlink_path="$rmdir_path/bad_link"
+ local tmpfile=$PREFIX/smbclient.in.$$
+
+ # Create rmdir directory.
+ mkdir -p "$rmdir_path"
+ # Create dangling symlink underneath.
+ ln -s "nowhere-foo" "$dangle_symlink_path"
+
+ cat >"$tmpfile" <<EOF
+cd dir
+ls
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/delete_veto_files_only -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share delete_veto_files_only - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should NOT see the dangling symlink file.
+ echo "$out" | grep bad_link
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "Saw dangling symlink bad_link in share delete_veto_files_only"
+ echo "$out"
+ return 1
+ fi
+
+ # Try and remove the directory, should succeed.
+ cat >"$tmpfile" <<EOF
+rd dir
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/delete_veto_files_only -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share delete_veto_files_only - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should get no NT_STATUS_ errors.
+ echo "$out" | grep NT_STATUS_
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "Got error NT_STATUS_ in share delete_veto_files_only"
+ echo "$out"
+ return 1
+ fi
+
+ return 0
+}
+
+#
+# Using the share "[veto_files_nodelete]" we CANNOT delete
+# a directory containing only a dangling symlink.
+#
+test_dangle_symlink_veto_files_nodelete()
+{
+ local dangle_symlink_path="$rmdir_path/bad_link"
+ local tmpfile=$PREFIX/smbclient.in.$$
+
+ # Create rmdir directory.
+ mkdir -p "$rmdir_path"
+ # Create dangling symlink underneath.
+ ln -s "nowhere-foo" "$dangle_symlink_path"
+
+ cat >"$tmpfile" <<EOF
+cd dir
+ls
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/veto_files_nodelete -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share veto_files_nodelete - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should NOT see the dangling symlink file.
+ echo "$out" | grep bad_link
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "Saw dangling symlink bad_link in share veto_files_nodelete"
+ echo "$out"
+ return 1
+ fi
+
+ # Try and remove the directory, should fail with DIRECTORY_NOT_EMPTY.
+ cat >"$tmpfile" <<EOF
+rd dir
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/veto_files_nodelete -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share veto_files_nodelete - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should get NT_STATUS_DIRECTORY_NOT_EMPTY errors.
+ echo "$out" | grep NT_STATUS_DIRECTORY_NOT_EMPTY
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Should get NT_STATUS_DIRECTORY_NOT_EMPTY in share veto_files_nodelete"
+ echo "$out"
+ return 1
+ fi
+
+ return 0
+}
+
+testit "rmdir can delete directory containing dangling symlink" \
+ test_dangle_symlink_delete_veto_rmdir || failed=$(expr "$failed" + 1)
+
+rm -rf "$rmdir_path"
+
+testit "rmdir cannot delete directory delete_veto_files_no containing dangling symlink" \
+ test_dangle_symlink_veto_files_nodelete || failed=$(expr "$failed" + 1)
+
+rm -rf "$rmdir_path"
+exit "$failed"
diff --git a/source3/script/tests/test_dfree_command.sh b/source3/script/tests/test_dfree_command.sh
new file mode 100755
index 0000000..3ebb50c
--- /dev/null
+++ b/source3/script/tests/test_dfree_command.sh
@@ -0,0 +1,69 @@
+#!/bin/sh
+#
+# Blackbox test for 'dfree command' and smbclient "l"
+# command disk free printout.
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_dfree_command.sh SERVER DOMAIN USERNAME PASSWORD PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=$1
+DOMAIN=$2
+USERNAME=$3
+PASSWORD=$4
+PREFIX=$5
+smbclient=$6
+protocol=$7
+
+shift 7
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+test_smbclient_dfree()
+{
+ name="$1"
+ share="$2"
+ cmd="$3"
+ expected="$4"
+ shift
+ shift
+ shift
+ subunit_start_test "$name"
+ output=$($VALGRIND $smbclient //$SERVER/$share -c "$cmd" "$@" 2>&1)
+ status=$?
+ if [ x$status = x0 ]; then
+ received=$(echo "$output" | awk '/blocks of size/ {print $1, $5, $6}')
+ if [ "$expected" = "$received" ]; then
+ subunit_pass_test "$name"
+ return 0
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return 1
+ fi
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return $status
+ fi
+}
+
+if [ $protocol = "SMB3" ]; then
+ test_smbclient_dfree "Test dfree command share root SMB3" dfree "l" "2000 1024. 20" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+ test_smbclient_dfree "Test dfree command subdir1 SMB3" dfree "cd subdir1; l" "8000 1024. 80" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+ test_smbclient_dfree "Test dfree command subdir2 SMB3" dfree "cd subdir2; l" "32000 1024. 320" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+
+elif [ $protocol = "NT1" ]; then
+ test_smbclient_dfree "Test dfree command share root NT1" dfree "l" "2000 1024. 20" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=NT1 || failed=$(expr $failed + 1)
+ #SMB1 queries disk usage stat on the share's root, regardless of working directory
+ test_smbclient_dfree "Test dfree command subdir1 NT1" dfree "cd subdir1; l" "2000 1024. 20" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=NT1 || failed=$(expr $failed + 1)
+
+else
+ echo "unsupported protocol $protocol" | subunit_fail_test "Test dfree command"
+ failed=$(expr $failed + 1)
+fi
+exit $failed
diff --git a/source3/script/tests/test_dfree_quota.sh b/source3/script/tests/test_dfree_quota.sh
new file mode 100755
index 0000000..9151016
--- /dev/null
+++ b/source3/script/tests/test_dfree_quota.sh
@@ -0,0 +1,296 @@
+#!/bin/sh
+#
+# Blackbox test for disk-free, quota, and their interaction
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_dfree_quota.sh SERVER DOMAIN USERNAME PASSWORD LOCAL_PATH SMBCLIENT SMBCQUOTAS SMBCACLS
+EOF
+ exit 1
+fi
+
+SERVER=$1
+DOMAIN=$2
+USERNAME=$3
+PASSWORD=$4
+ENVDIR=$(dirname $5)
+WORKDIR=$5/dfree
+smbclient=$6
+smbcquotas=$7
+smbcacls=$8
+protocol=$9
+shift 9
+failed=0
+
+CONFFILE=$ENVDIR/lib/dfq.conf
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+sighup_smbd()
+{
+ kill -HUP -$(cat $ENVDIR/pid/smbd.pid)
+}
+
+conf_lines()
+{
+ local uid
+ local gid
+ uid=$(id -u $USERNAME)
+ gid=$(id -g $USERNAME)
+ uid1=$(id -u user1)
+ uid2=$(id -u user2)
+ cat <<ABC
+conf1:df:block size = 512:disk free = 10:disk size = 20
+conf2:df:block size = 1024:disk free = 10:disk size = 20
+conf3:df:block size = 4096:disk free = 750:disk size = 281474976710656
+confq1:u$uid:block size = 4096:hard limit = 750:soft limit = 1000:cur blocks = 10
+confdfq1:df:block size = 4096:disk free = 10:disk size = 20
+confdfq1:u$uid:block size = 4096:hard limit = 750:soft limit = 1000:cur blocks = 10
+confdfq2:df:block size = 4096:disk free = 10:disk size = 20
+confdfq2:u$uid:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 37
+confdfq3:df:block size = 4096:disk free = 10:disk size = 80
+confdfq3:u$uid:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 0
+confdfq4:df:block size = 4096:disk free = 10:disk size = 80
+confdfq4:u$uid:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 37
+slimit:df:block size = 4096:disk free = 10:disk size = 80
+slimit:u$uid:block size = 4096:hard limit = 44:soft limit = 40:cur blocks = 42
+hlimit:df:block size = 4096:disk free = 10:disk size = 80
+hlimit:u$uid:block size = 4096:hard limit = 44:soft limit = 0:cur blocks = 45
+islimit:df:block size = 4096:disk free = 10:disk size = 80
+islimit:u$uid:block size = 4096:hard limit = 44:soft limit = 40:cur blocks = 37:inode soft limit = 30:inode hard limit = 35:cur inodes = 32
+ihlimit:df:block size = 4096:disk free = 10:disk size = 80
+ihlimit:u$uid:block size = 4096:hard limit = 44:soft limit = 40:cur blocks = 37:inode soft limit = 0:inode hard limit = 35:cur inodes = 36
+trygrp1:df:block size = 4096:disk free = 10:disk size = 80
+trygrp1:u$uid:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 41:err = 1
+trygrp1:g$gid:block size = 4096:hard limit = 60:soft limit = 60:cur blocks = 55
+trygrp2:df:block size = 4096:disk free = 10:disk size = 80
+trygrp2:u$uid:block size = 4096:hard limit = 0:soft limit = 0:cur blocks = 41
+trygrp2:g$gid:block size = 4096:hard limit = 60:soft limit = 60:cur blocks = 56
+blksize:df:block size = 512:disk free = 614400:disk size = 614400
+blksize:u$uid:block size = 1024:hard limit = 512000:soft limit = 0:cur blocks = 0
+notenforce:df:block size = 4096:disk free = 10:disk size = 80
+notenforce:u$uid:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 37
+notenforce:udflt:block size = 4096:qflags = 0
+nfs:df:block size = 4096:disk free = 10:disk size = 80
+nfs:u$uid:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 37
+nfs:udflt:nosys = 1
+confdfqp:df:block size = 4096:disk free = 10:disk size = 80
+confdfqp:u$uid1:block size = 4096:hard limit = 40:soft limit = 40:cur blocks = 36
+confdfqp:u$uid2:block size = 4096:hard limit = 41:soft limit = 41:cur blocks = 36
+sgid:stat:sgid = 98765
+sgid:u$uid:block size = 4096:hard limit = 0:soft limit = 0:cur blocks = 80
+sgid:g98765:block size = 4096:hard limit = 50:soft limit = 50:cur blocks = 40
+ABC
+}
+
+setup_1_conf()
+{
+ conf_name="$1"
+ subdir="$2"
+ absdir=$(readlink -f $WORKDIR/$subdir)
+ conf_lines | sed -rn "s/^$conf_name:(.*)/\1/p" | tr ":" "\n" |
+ awk -F '=' -v atdir=$absdir 'NF==1 {section=$1} NF==2 {sub(/\s*$/, "", $1); printf "\tfake_dfq:%s/%s/%s =%s\n", section, $1, atdir, $2}'
+}
+
+setup_conf()
+{
+ rm $CONFFILE
+ touch $CONFFILE
+
+ until [ -z "$1" ]; do
+ setup_1_conf $1 $2 >>$CONFFILE
+ shift
+ shift
+ done
+ sighup_smbd
+ #let it load...
+ sleep .5
+}
+
+test_smbclient_dfree()
+{
+ name="$1"
+ share="$2"
+ dir="$3"
+ confs="$4"
+ expected="$5"
+ shift
+ shift
+ shift
+ shift
+ subunit_start_test "$name"
+ setup_conf $confs
+ output=$($VALGRIND $smbclient //$SERVER/$share -c "cd $dir; l" "$@" 2>&1)
+ status=$?
+ if [ "$status" = "0" ]; then
+ received=$(echo "$output" | awk '/blocks of size/ {print $1, $5, $6}')
+ if [ "$expected" = "$received" ]; then
+ subunit_pass_test "$name"
+ return 0
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return 1
+ fi
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return $status
+ fi
+}
+
+# Issue two queries to different directories in one session to test
+# caching effects
+test_smbclient_dfree_2()
+{
+ name="$1"
+ share="$2"
+ dir1="$3"
+ dir2="$4"
+ confs="$5"
+ expected="$6"
+ subunit_start_test "$name"
+ setup_conf $confs
+ output=$($VALGRIND $smbclient //$SERVER/$share \
+ -c "cd $dir1; du; cd ..; cd $dir2 ; du" "$@" 2>&1)
+ status=$?
+ if [ "$status" = "0" ]; then
+ received=$(echo "$output" |
+ awk '/blocks of size/ {print $1, $5, $6}' |
+ tr '\n' ' ')
+ if [ "$expected" = "$received" ]; then
+ subunit_pass_test "$name"
+ return 0
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return 1
+ fi
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return $status
+ fi
+}
+
+test_smbcquotas()
+{
+ name="$1"
+ conf="$2"
+ user="$3"
+ expected="$4"
+ proto="$5"
+ shift
+ shift
+ shift
+ shift
+ shift
+ subunit_start_test "$name"
+ setup_conf "$conf" "."
+ if [ "$proto" = "smb2" ]; then
+ mproto="-m SMB2"
+ else
+ mproto="-m SMB1"
+ fi
+
+ output=$($VALGRIND $smbcquotas $mproto //$SERVER/dfq "$@" 2>/dev/null | tr '\\' '/')
+ status=$?
+ if [ "$status" = "0" ]; then
+ received=$(echo "$output" | awk "/$SERVER\\/$user/ {printf \"%s%s%s\", \$3, \$4, \$5}")
+ if [ "$expected" = "$received" ]; then
+ subunit_pass_test "$name"
+ return 0
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return 1
+ fi
+ else
+ echo "$output" | subunit_fail_test "$name"
+ return $status
+ fi
+}
+
+if [ $protocol != "SMB3" ] && [ $protocol != "NT1" ]; then
+ echo "unsupported protocol $protocol" | subunit_fail_test "Test dfree quota"
+ failed=$(expr $failed + 1)
+fi
+
+if [ $protocol = "NT1" ]; then
+ setup_conf
+ #basic quota test (SMB1 only)
+ test_smbcquotas "Test user quota" confq1 $USERNAME "40960/4096000/3072000" "smb1" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=NT1 || failed=$(expr $failed + 1)
+ exit $failed
+fi
+
+#basic disk-free tests
+test_smbclient_dfree "Test dfree share root SMB3 no quota" dfq "." "conf1 ." "10 1024. 5" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test dfree subdir SMB3 no quota" dfq "subdir1" "conf1 . conf2 subdir1" "20 1024. 10" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test large disk" dfq "." "conf3 ." "1125899906842624 1024. 3000" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+#basic quota test (SMB2 only)
+test_smbcquotas "Test user quota" confq1 $USERNAME "40960/4096000/3072000" "smb2" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB2 || failed=$(expr $failed + 1)
+
+# Test dfree cache through queries in two different directories
+test_smbclient_dfree_2 "Test dfree cache" dfq_cache "." "subdir1" \
+ "conf1 . conf2 subdir1" "10 1024. 5 20 1024. 10 " \
+ -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 ||
+ failed=$(expr $failed + 1)
+
+#quota limit > disk size, remaining quota > disk free
+test_smbclient_dfree "Test dfree share root df vs quota case 1" dfq "." "confdfq1 ." "80 1024. 40" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+#quota limit > disk size, remaining quota < disk free
+test_smbclient_dfree "Test dfree share root df vs quota case 2" dfq "." "confdfq2 ." "80 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+#quota limit < disk size, remaining quota > disk free
+test_smbclient_dfree "Test dfree share root df vs quota case 3" dfq "." "confdfq3 ." "160 1024. 40" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+#quota limit < disk size, remaining quota < disk free
+test_smbclient_dfree "Test dfree share root df vs quota case 4" dfq "." "confdfq4 ." "160 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test dfree subdir df vs quota case 4" dfq "subdir1" "confdfq4 subdir1" "160 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+
+#quota-->disk free special cases
+test_smbclient_dfree "Test quota->dfree soft limit" dfq "subdir1" "slimit subdir1" "168 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test quota->dfree hard limit" dfq "subdir1" "hlimit subdir1" "180 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test quota->dfree inode soft limit" dfq "subdir1" "islimit subdir1" "148 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test quota->dfree inode hard limit" dfq "subdir1" "ihlimit subdir1" "148 1024. 0" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test quota->dfree err try group" dfq "subdir1" "trygrp1 subdir1" "240 1024. 20" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test quota->dfree no-quota try group" dfq "subdir1" "trygrp2 subdir1" "240 1024. 16" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+
+# sgid on directory
+test_smbclient_dfree "Test quota on sgid directory" dfq "subdir1" \
+ "sgid subdir1" "200 1024. 40" -U$USERNAME%$PASSWORD \
+ --option=clientmaxprotocol=SMB3 ||
+ failed=$(expr $failed + 1)
+
+#block size different in quota and df systems
+test_smbclient_dfree "Test quota->dfree different block size" dfq "subdir1" "blksize subdir1" "307200 1024. 307200" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+
+#quota configured but not enforced
+test_smbclient_dfree "Test dfree share root quota not enforced" dfq "." "notenforce ." "320 1024. 40" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+
+#FS quota not implemented (NFS case)
+test_smbclient_dfree "Test dfree share root FS quota not implemented" dfq "." "nfs ." "160 1024. 12" -U$USERNAME%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+
+#test for dfree when owner is inherited
+#setup two folders with different owners
+rm -rf $WORKDIR/subdir3/*
+for d in / subdir3; do
+ $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -D "ACL:$SERVER\user1:ALLOWED/0x0/FULL" //$SERVER/dfq $d >/dev/null 2>&1
+ $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -a "ACL:$SERVER\user1:ALLOWED/0x0/FULL" //$SERVER/dfq $d || failed=$(expr $failed + 1)
+ $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -D "ACL:$SERVER\user2:ALLOWED/0x0/FULL" //$SERVER/dfq $d >/dev/null 2>&1
+ $VALGRIND $smbcacls -U$USERNAME%$PASSWORD -a "ACL:$SERVER\user2:ALLOWED/0x0/FULL" //$SERVER/dfq $d || failed=$(expr $failed + 1)
+done
+
+$VALGRIND $smbclient //$SERVER/dfq -c "cd subdir3; mkdir user1" -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 >/dev/null 2>&1 || failed=$(expr $failed + 1)
+$VALGRIND $smbclient //$SERVER/dfq -c "cd subdir3; mkdir user2" -Uuser2%$PASSWORD --option=clientmaxprotocol=SMB3 >/dev/null 2>&1 || failed=$(expr $failed + 1)
+#test quotas
+test_smbclient_dfree "Test dfree without inherit owner - user1 at user1" \
+ dfq "subdir3/user1" "confdfqp subdir3/user1 confdfqp subdir3/user2" "160 1024. 16" \
+ -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test dfree without inherit owner - user1 at user2" \
+ dfq "subdir3/user2" "confdfqp subdir3/user1 confdfqp subdir3/user2" "160 1024. 16" \
+ -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test dfree with inherit owner - user1 at user1" \
+ dfq_owner "subdir3/user1" "confdfqp subdir3/user1 confdfqp subdir3/user2" "160 1024. 16" \
+ -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+test_smbclient_dfree "Test dfree with inherit owner - user1 at user2" \
+ dfq_owner "subdir3/user2" "confdfqp subdir3/user1 confdfqp subdir3/user2" "164 1024. 20" \
+ -Uuser1%$PASSWORD --option=clientmaxprotocol=SMB3 || failed=$(expr $failed + 1)
+
+setup_conf
+exit $failed
diff --git a/source3/script/tests/test_dropbox.sh b/source3/script/tests/test_dropbox.sh
new file mode 100755
index 0000000..a920c27
--- /dev/null
+++ b/source3/script/tests/test_dropbox.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Blackbox test for valid users.
+#
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: $0 SERVER DOMAIN USERNAME PASSWORD PREFIX TARGET_ENV SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+DOMAIN=${2}
+USERNAME=${3}
+PASSWORD=${4}
+PREFIX=${5}
+TARGET_ENV=${6}
+SMBCLIENT=${7}
+shift 7
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# Test listing a share with valid users succeeds
+test_dropbox()
+{
+ local filename="wurst.$$"
+ local filename_path="$PREFIX/$filename"
+ local dropbox_path="$PREFIX/$TARGET_ENV/share/$1/dirmode733"
+
+ local tmpfile=$PREFIX/smbclient.in.$$
+
+ echo "wurstbar" >$filename_path
+
+ cat >$tmpfile <<EOF
+lcd $PREFIX
+put $filename dirmode733\\$filename
+quit
+EOF
+
+ # Create dropbox directory and set permissions
+ mkdir -p $dropbox_path
+ chmod 0333 $dropbox_path
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/$1 -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ # Reset dropbox permissions
+ chmod 0755 $dropbox_path
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share $ret"
+ echo "$out"
+
+ return 1
+ fi
+ rm -f $filename_path
+
+ dropped_file="$dropbox_path/$filename"
+ if [ ! -r "$dropped_file" ]; then
+ echo "Failed to drop file $filename"
+ echo "$out"
+ return 1
+ fi
+
+ content=$(cat $dropped_file)
+ if [ "$content" != "wurstbar" ]; then
+ echo "Invalid file content: $content"
+ echo "$out"
+ return 1
+ fi
+
+ return 0
+}
+
+testit "dropbox dirmode 0733" \
+ test_dropbox dropbox ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_durable_handle_reconnect.sh b/source3/script/tests/test_durable_handle_reconnect.sh
new file mode 100755
index 0000000..0ab3297
--- /dev/null
+++ b/source3/script/tests/test_durable_handle_reconnect.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# Test Durable Handle reconnect with injected delay in the disconnect.
+#
+# Copyright (C) 2018 Ralph Boehme
+
+. $(dirname $0)/../../../testprogs/blackbox/subunit.sh
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+delay_inject_conf=$(dirname $SMB_CONF_PATH)/delay_inject.conf
+
+echo 'delay_inject:fntimes = 5000' >$delay_inject_conf
+
+testit "durable_v2_delay.durable_v2_reconnect_delay" $VALGRIND \
+ $BINDIR/smbtorture //$SERVER_IP/delay_inject \
+ -U$USERNAME%$PASSWORD \
+ smb2.durable-v2-delay.durable_v2_reconnect_delay ||
+ failed=$(expr $failed + 1)
+
+SMBD_LOG_FILES="$SMBD_TEST_LOG"
+if [ $SMBD_DONT_LOG_STDOUT -eq 1 ]; then
+ _SMBD_LOG_FILE=$(dirname $SMBD_TEST_LOG)/logs/log.smbd
+ SMBD_LOG_FILES="$SMBD_LOG_FILES $_SMBD_LOG_FILE"
+fi
+
+testit "durable_v2_delay.durable_v2_reconnect_delay_msec" $VALGRIND \
+ $BINDIR/smbtorture //$SERVER_IP/durable \
+ -U$USERNAME%$PASSWORD \
+ smb2.durable-v2-delay.durable_v2_reconnect_delay_msec ||
+ failed=$(expr $failed + 1)
+
+rm $delay_inject_conf
+
+testok $0 $failed
diff --git a/source3/script/tests/test_failure.sh b/source3/script/tests/test_failure.sh
new file mode 100755
index 0000000..7a2c9ae
--- /dev/null
+++ b/source3/script/tests/test_failure.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+#
+# Blackbox test that should fail one of three subtests.
+#
+# Copyright (C) 2011 Michael Adam <obnox@samba.org>
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_failure()
+{
+ false
+}
+
+test_success()
+{
+ true
+}
+
+testit "success" \
+ test_success ||
+ failed=$(expr $failed + 1)
+
+testit "failure" \
+ test_failure ||
+ failed=$(expr $failed + 1)
+
+testit "success" \
+ test_success ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_fakedircreatetimes.sh b/source3/script/tests/test_fakedircreatetimes.sh
new file mode 100755
index 0000000..4ce2bcf
--- /dev/null
+++ b/source3/script/tests/test_fakedircreatetimes.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_fakedircreatetimes.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT ADDARGS
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 7
+ADDARGS="$*"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+
+
+test_fakedircreatetimes()
+{
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/recycle -I$SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ cmd="LC_ALL=C TZ=UTC CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/fakedircreatetimes -I$SERVER_IP $ADDARGS -c 'allinfo \\'"
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed recycle smbclient run with error %s\n" "$ret"
+ return 1
+ fi
+
+ echo "$out" | grep -q "create_time:.*1979 UTC" && {
+ return 0
+ }
+
+ echo "ERROR: create_time does not match 1979 UTC:"
+ echo "$out"
+ return 1
+}
+
+
+testit "fakedircreatetimes" \
+ test_fakedircreatetimes ||
+ failed=$((failed + 1))
+
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_fifo.sh b/source3/script/tests/test_fifo.sh
new file mode 100755
index 0000000..199b0b4
--- /dev/null
+++ b/source3/script/tests/test_fifo.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+#
+# Check smbclient can list a directory containing a fifo.
+#
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: $0 SERVER DOMAIN USERNAME PASSWORD PREFIX TARGET_ENV SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+DOMAIN=${2}
+USERNAME=${3}
+PASSWORD=${4}
+PREFIX=${5}
+TARGET_ENV=${6}
+SMBCLIENT=${7}
+shift 7
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# Test that listing a share with a directory containing a fifo succeeds.
+#
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14816
+#
+test_fifo()
+{
+ local fifo_dir_path="$PREFIX/$TARGET_ENV/share/fifodir"
+ local fifo_path="$fifo_dir_path/fifo_name"
+
+ local tmpfile=$PREFIX/smbclient.in.$$
+
+ cat >$tmpfile <<EOF
+cd fifodir
+ls
+quit
+EOF
+
+ # Create fifo directory.
+ mkdir -p $fifo_dir_path
+ # Create fifo underneath.
+ mkfifo $fifo_path
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/$1 -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ # Remove fifo and containing dir.
+ rm $fifo_path
+ rmdir $fifo_dir_path
+ rm -f $tmpfile
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share containing dir with fifo $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # Check for smbclient timeout (server hung).
+ echo "$out" | grep 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ # Client was disconnected as server timed out.
+ echo "$out"
+ return 1
+ fi
+
+ return 0
+}
+
+testit "list directory containing a fifo" \
+ test_fifo tmp || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_force_close_share.sh b/source3/script/tests/test_force_close_share.sh
new file mode 100755
index 0000000..c9f943a
--- /dev/null
+++ b/source3/script/tests/test_force_close_share.sh
@@ -0,0 +1,115 @@
+#!/usr/bin/env bash
+#
+# Test smbcontrol close-share command.
+#
+# Copyright (C) 2020 Volker Lendecke
+# Copyright (C) 2020 Jeremy Allison
+#
+# Note this is designed to be run against
+# the aio_delay_inject share which is preconfigured
+# with 2 second delays on pread/pwrite.
+
+if [ $# -lt 6 ]; then
+ echo Usage: $0 SERVERCONFFILE SMBCLIENT SMBCONTROL IP aio_delay_inject_sharename PREFIX
+ exit 1
+fi
+
+CONFIGURATION=$1
+smbclient=$2
+SMBCONTROL=$3
+SERVER=$4
+SHARE=$5
+PREFIX=$6
+shift 6
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+failed=0
+
+mkdir -p $PREFIX/private
+
+FIFO_STDIN="$PREFIX/smbclient-stdin"
+FIFO_STDOUT="$PREFIX/smbclient-stdout"
+FIFO_STDERR="$PREFIX/smbclient-stderr"
+TESTFILE="$PREFIX/testfile"
+
+rm -f $FIFO_STDIN $FIFO_STDOUT $FIFO_STDERR $TESTFILE 2>/dev/null
+
+# Create the smbclient communication pipes.
+mkfifo $FIFO_STDIN $FIFO_STDOUT $FIFO_STDERR
+if [ $? -ne 0 ]; then
+ echo "Failed to create fifos"
+ exit 1
+fi
+
+# Create a large-ish testfile
+head -c 100MB /dev/zero >$TESTFILE
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+${smbclient} //${SERVER}/${SHARE} ${CONFIGURATION} -U${USER}%${PASSWORD} \
+ <$FIFO_STDIN >$FIFO_STDOUT 2>$FIFO_STDERR &
+CLIENT_PID=$!
+
+count=0
+while [ 1 ]; do
+ if [ $count -ge 20 ]; then
+ echo "Failed to start smbclient"
+ exit 1
+ fi
+ kill -0 $CLIENT_PID
+ if [ $? -eq 0 ]; then
+ break
+ fi
+ sleep 0.5
+ count=$((count + 1))
+done
+
+exec 100>$FIFO_STDIN 101<$FIFO_STDOUT 102<$FIFO_STDERR
+
+# consume the smbclient startup messages
+head -n 1 <&101
+
+# Ensure we're putting a fresh file.
+echo "lcd $(dirname $TESTFILE)" >&100
+echo "del testfile" >&100
+echo "put testfile" >&100
+
+sleep 0.2
+
+# Close the aio_delay_inject share whilst we have outstanding writes.
+
+testit "smbcontrol" ${SMBCONTROL} ${CONFIGURATION} smbd close-share ${SHARE} ||
+ failed=$(expr $failed + 1)
+
+sleep 0.5
+
+# If we get one or more NT_STATUS_NETWORK_NAME_DELETED
+# or NT_STATUS_INVALID_HANDLE on stderr from the writes we
+# know the server stayed up and didn't crash when the
+# close-share removed the share.
+#
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14301
+#
+COUNT=$(head -n 2 <&102 |
+ grep -e NT_STATUS_NETWORK_NAME_DELETED -e NT_STATUS_INVALID_HANDLE |
+ wc -l)
+
+testit "Verify close-share did cancel the file put" \
+ test $COUNT -ge 1 || failed=$(expr $failed + 1)
+
+kill ${CLIENT_PID}
+
+# Remove the testfile from the server
+test_smbclient "remove_testfile" \
+ 'del testfile; quit' //${SERVER}/${SHARE} -U${USER}%${PASSWORD} ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_force_create_mode.sh b/source3/script/tests/test_force_create_mode.sh
new file mode 100755
index 0000000..289f219
--- /dev/null
+++ b/source3/script/tests/test_force_create_mode.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+#
+# Blackbox test for 'force create mode'
+#
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: $0 SERVER DOMAIN USERNAME PASSWORD PREFIX TARGET_ENV SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+DOMAIN=${2}
+USERNAME=${3}
+PASSWORD=${4}
+PREFIX=${5}
+TARGET_ENV=${6}
+SMBCLIENT=${7}
+shift 7
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_force_create_mode()
+{
+ local filename="wurst.$$"
+ local filename_path="$PREFIX/$filename"
+
+ local tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+
+ echo wurstbar >$filename_path
+
+ cat >$tmpfile <<EOF
+lcd $PREFIX
+put $filename
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/$1 $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "Failed to connect - error: $ret"
+ return 1
+ fi
+ rm -f $filename_path
+
+ share_filename="$PREFIX/$TARGET_ENV/share/$filename"
+ file_perms=$(stat --format=%a $share_filename)
+ if [ "$file_perms" != "664" ]; then
+ echo "Invalid file permissions: $file_perms"
+ return 1
+ fi
+
+ rm -f $share_filename
+
+ return 0
+}
+
+testit "test_mode=0664" \
+ test_force_create_mode create_mode_664 ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_force_group_change.sh b/source3/script/tests/test_force_group_change.sh
new file mode 100755
index 0000000..bf82903
--- /dev/null
+++ b/source3/script/tests/test_force_group_change.sh
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Copyright (c) Jeremy Allison <jra@samba.org>
+# License: GPLv3
+# Regression test for BUG:https://bugzilla.samba.org/show_bug.cgi?id=13690
+
+if [ $# -lt 6 ]; then
+ echo "Usage: test_force_group_change.sh SERVER USERNAME PASSWORD LOCAL_PATH SMBCLIENT SMBCONTROL"
+ exit 1
+fi
+
+SERVER="${1}"
+USERNAME="${2}"
+PASSWORD="${3}"
+LOCAL_PATH="${4}"
+SMBCLIENT="${5}"
+SMBCONTROL="${6}"
+shift 6
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_force_group_change()
+{
+ #
+ # A SMB_CONF variable passed in here is the client smb.conf.
+ # We need to convert to the server.conf file from
+ # the LOCAL_PATH variable.
+ #
+ SERVER_CONFIG=$(dirname $LOCAL_PATH)/lib/server.conf
+ SERVER_CONFIG_SAVE=${SERVER_CONFIG}.bak
+ SERVER_CONFIG_NEW=${SERVER_CONFIG}.new
+ cp $SERVER_CONFIG $SERVER_CONFIG_SAVE
+
+ sed -e 's/#\tforce group = everyone/\tforce group = everyone/' <${SERVER_CONFIG} >${SERVER_CONFIG_NEW}
+
+ tmpfile=$PREFIX/smbclient_force_group_change_commands
+ cat >$tmpfile <<EOF
+ls
+!cp ${SERVER_CONFIG_NEW} ${SERVER_CONFIG}
+!${SMBCONTROL} --configfile=${SERVER_CONFIG} all reload-config
+ls
+!cp ${SERVER_CONFIG_SAVE} ${SERVER_CONFIG}
+!${SMBCONTROL} --configfile=${SERVER_CONFIG} all reload-config
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/force_group_test $CONFIGURATION < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+ rm -f $SERVER_CONFIG_SAVE
+ rm -f $SERVER_CONFIG_NEW
+
+ echo "$out" | grep 'NT_STATUS_CONNECTION_DISCONNECTED'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ # Client was disconnected as server crashed.
+ echo "$out"
+ return 1
+ fi
+
+ return 0
+}
+
+testit "test force group change" \
+ test_force_group_change ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_force_user_unlink.sh b/source3/script/tests/test_force_user_unlink.sh
new file mode 100755
index 0000000..c67b28f
--- /dev/null
+++ b/source3/script/tests/test_force_user_unlink.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Test unlink on share with "force user"
+#
+# Copyright (C) 2021 Ralph Boehme
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+smbclient="$BINDIR/smbclient"
+error_inject_conf=$(dirname ${SMB_CONF_PATH})/error_inject.conf
+failed=0
+
+test_forced_user_can_delete()
+{
+ out=$($smbclient -U $DOMAIN/$USERNAME%$PASSWORD //$SERVER_IP/force_user_error_inject -c "rm dir/file")
+ if [ $? -ne 0 ]; then
+ echo $out
+ return 1
+ fi
+ tmp=$(echo $out | grep NT_STATUS_)
+ if [ $? -eq 0 ]; then
+ return 1
+ fi
+ return 0
+}
+
+echo "error_inject:unlinkat = EACCES" >${error_inject_conf}
+
+$smbclient -U $DOMAIN/$USERNAME%$PASSWORD //$SERVER_IP/force_user_error_inject -c "mkdir dir" || failed=$(expr $failed + 1)
+$smbclient -U $DOMAIN/$USERNAME%$PASSWORD //$SERVER_IP/force_user_error_inject -c "put WHATSNEW.txt dir/file" || failed=$(expr $failed + 1)
+
+testit "test_forced_user_can_delete" test_forced_user_can_delete || failed=$(expr $failed + 1)
+
+rm ${error_inject_conf}
+
+# Clean up after ourselves.
+$smbclient -U $DOMAIN/$USERNAME%$PASSWORD //$SERVER_IP/force_user_error_inject -c "del dir/file; rmdir dir"
+
+testok $0 $failed
diff --git a/source3/script/tests/test_forceuser_validusers.sh b/source3/script/tests/test_forceuser_validusers.sh
new file mode 100755
index 0000000..11c1a5b
--- /dev/null
+++ b/source3/script/tests/test_forceuser_validusers.sh
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Blackbox test for share with force user settings
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_forceuser_validusers.sh SERVER DOMAIN USERNAME PASSWORD LOCAL_PATH SMBCLIENT <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+DOMAIN="$2"
+USERNAME="force_user"
+PASSWORD="$4"
+LOCAL_PATH="$5"
+SMBCLIENT="$6"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 6
+ADDARGS="$*"
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+cd $SELFTEST_TMPDIR || exit 1
+
+run_cmd_nooutput()
+{
+ CMD="$1"
+
+ out=$(eval ${CMD} >TESTOUT 2>&1)
+ if [ $? != 0 ]; then
+ cat TESTOUT
+ rm -f TESTOUT
+ echo "command failed"
+ false
+ return
+ fi
+
+ rm -f TESTOUT
+ true
+ return
+}
+
+test_force_user_valid_users()
+{
+ SMB_SHARE="force_user_valid_users"
+ run_cmd_nooutput "${SMBCLIENT} //${SERVER}/${SMB_SHARE} -U$USERNAME%$PASSWORD -c 'ls'"
+}
+
+# Test
+testit "force user not works when combined with valid users" \
+ test_force_user_valid_users || failed=$(expr $failed + 1)
+
+# Cleanup
+
+# Results
+testok $0 $failed
diff --git a/source3/script/tests/test_fruit_resource_stream.sh b/source3/script/tests/test_fruit_resource_stream.sh
new file mode 100755
index 0000000..7e99ea3
--- /dev/null
+++ b/source3/script/tests/test_fruit_resource_stream.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+
+# this tests copying a file and then deleting it
+# to a share using fruit:resource = stream
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15099
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 SERVER SHARE USERNAME PASSWORD LOCAL_PATH SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SHARE="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+SMBCLIENT="${6}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir/subunit.sh"
+
+failed=0
+
+put_then_delete_file()
+{
+ $SMBCLIENT //"$SERVER"/"$SHARE" -U"$USERNAME"%"$PASSWORD" -c "lcd $LOCAL_PATH; put src dst; rm dst" >/dev/null 2>&1
+}
+
+rm -f "$LOCAL_PATH/src"
+rm -f "$LOCAL_PATH/dst"
+touch "$LOCAL_PATH/src"
+
+testit "resource_stream" put_then_delete_file || failed=$((failed + 1))
+
+rm -f "$LOCAL_PATH/src"
+rm -f "$LOCAL_PATH/dst"
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_give_owner.sh b/source3/script/tests/test_give_owner.sh
new file mode 100755
index 0000000..9d00918
--- /dev/null
+++ b/source3/script/tests/test_give_owner.sh
@@ -0,0 +1,147 @@
+#!/bin/sh
+#
+# this verifies that SEC_STD_WRITE_OWNER only effectively grants take-ownership
+# permissions but NOT give-ownership.
+#
+
+if [ $# -lt 9 ]; then
+ echo "Usage: $0 SERVER SERVER_IP USERNAME PASSWORD PREFIX SMBCLIENT SMBCACLS NET SHARE"
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+PREFIX="$5"
+SMBCLIENT="$6"
+SMBCACLS="$7"
+NET="$8"
+SHARE="$9"
+
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+SMBCACLS="$VALGRIND ${SMBCACLS}"
+NET="$VALGRIND ${NET}"
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+setup_testfile()
+{
+ local share=$1
+ local fname=$2
+ touch $PREFIX/$fname
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "rm $fname"
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "ls" | grep "$fname" && return 1
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "lcd $PREFIX; put $fname" || return 1
+}
+
+remove_testfile()
+{
+ local share=$1
+ local fname=$2
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "rm $fname"
+}
+
+set_win_owner()
+{
+ local share=$1
+ local fname=$2
+ local owner=$3
+ echo "$SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -C '$owner'"
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -C "$owner" || return 1
+}
+
+win_owner_is()
+{
+ local share=$1
+ local fname=$2
+ local expected_owner=$3
+ local actual_owner
+
+ echo "$SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD"
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD
+ actual_owner=$($SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD | sed -rn 's/^OWNER:(.*)/\1/p')
+ echo "actual_owner = $actual_owner"
+ if ! test "x$actual_owner" = "x$expected_owner"; then
+ echo "Actual owner of $share/$fname is [$actual_owner] expected [$expected_owner]"
+ return 1
+ fi
+ return 0
+}
+
+add_ace()
+{
+ local share=$1
+ local fname=$2
+ local ace=$3
+
+ local_ace=$(printf '%s' "$ace" | sed 's|\\|/|')
+
+ # avoid duplicate
+ out=$($SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD)
+ if [ $? -ne 0 ]; then
+ echo "get acl failed"
+ echo "$out"
+ return 1
+ fi
+ echo "Original ACL"
+ echo $out
+ echo "$out" | grep "$local_ace" && return 0
+
+ # add it
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -a "$ace"
+ if [ $? -ne 0 ]; then
+ echo "add acl failed"
+ return 1
+ fi
+
+ # check it's there
+ out=$($SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD)
+ if [ $? -ne 0 ]; then
+ echo "get new acl failed"
+ echo "$out"
+ return 1
+ fi
+ echo "New ACL"
+ echo $out
+ echo "Checking if new ACL has \"$local_ace\""
+ echo "$out" | grep "$local_ace" || return 1
+ echo "ok"
+}
+
+chown_give_fails()
+{
+ local share=$1
+ local fname=$2
+ local user=$3
+ local expected_error=$4
+
+ # this must fail
+ out=$($SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -C "$user") && return 1
+ # it failed, now check it returned the expected error code
+ echo "$out" | grep $expected_error || return 1
+}
+
+# Create a testfile
+testit "create testfile" setup_testfile $SHARE afile || failed=$(expr $failed + 1)
+testit "verify owner" win_owner_is $SHARE afile "$SERVER/$USERNAME" || failed=$(expr $failed + 1)
+
+# Grant SeRestorePrivilege to the user and full rights on the file
+testit "grant SeRestorePrivilege" $NET rpc rights grant $USERNAME SeRestorePrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || failed=$(expr $failed + 1)
+testit "grant full rights" add_ace $SHARE afile "ACL:$SERVER\\$USERNAME:ALLOWED/0x0/FULL" || failed=$(expr $failed + 1)
+
+# We have SeRestorePrivilege, so both give and take ownership must succeed
+testit "give owner with SeRestorePrivilege" set_win_owner $SHARE afile "$SERVER\user1" || failed=$(expr $failed + 1)
+testit "verify owner" win_owner_is $SHARE afile "$SERVER/user1" || failed=$(expr $failed + 1)
+testit "take owner" set_win_owner $SHARE afile "$SERVER\\$USERNAME" || failed=$(expr $failed + 1)
+testit "verify owner" win_owner_is $SHARE afile "$SERVER/$USERNAME" || failed=$(expr $failed + 1)
+
+# Revoke SeRestorePrivilege, give ownership must fail now with NT_STATUS_INVALID_OWNER
+testit "revoke SeRestorePrivilege" $NET rpc rights revoke $USERNAME SeRestorePrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || failed=$(expr $failed + 1)
+testit "give owner without SeRestorePrivilege" chown_give_fails $SHARE afile "$SERVER\user1" NT_STATUS_INVALID_OWNER || failed=$(expr $failed + 1)
+
+testit "delete testfile" remove_testfile $SHARE afile || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_groupmap.sh b/source3/script/tests/test_groupmap.sh
new file mode 100755
index 0000000..a1e9a08
--- /dev/null
+++ b/source3/script/tests/test_groupmap.sh
@@ -0,0 +1,217 @@
+#!/bin/sh
+# test groupmap code tridge@samba.org September 2006
+# note that this needs root access to add unix groups,
+# so this cannot be run on the build farm
+
+testone()
+{
+ echo $*
+ $VALGRIND bin/net groupmap $*
+}
+
+tstart()
+{
+ TBASE=$(date '+%s')
+}
+
+treport()
+{
+ TNOW=$(date '+%s')
+ echo "Took $(expr $TNOW - $TBASE) seconds"
+ TBASE=$TNOW
+}
+
+rm -f $PREFIX_ABS/var/locks/group_mapping.?db
+
+NLOCAL=12
+NGROUP=11
+NBUILTIN=10
+DOMSID=$(bin/net getlocalsid | awk '{print $6}')
+FORSID="S-1-2-3-4-5"
+
+echo "DOMSID $DOMSID"
+echo "FORSID $FORSID"
+
+tstart
+echo "Creating unix groups"
+for i in $(seq 1 1 $NLOCAL); do
+ unixgroup=testlocal$i
+ gid=$(expr 30000 + $i)
+ groupdel $unixgroup 2>/dev/null
+ groupadd -g $gid $unixgroup || exit 1
+done
+for i in $(seq 1 1 $NGROUP); do
+ unixgroup=testgrp$i
+ gid=$(expr 40000 + $i)
+ groupdel $unixgroup 2>/dev/null
+ groupadd -g $gid $unixgroup || exit 1
+done
+for i in $(seq 1 1 $NBUILTIN); do
+ unixgroup=testb$i
+ gid=$(expr 50000 + $i)
+ groupdel $unixgroup 2>/dev/null
+ groupadd -g $gid $unixgroup || exit 1
+done
+date
+
+treport
+
+echo "Creating local groups"
+for i in $(seq 1 1 $NLOCAL); do
+ unixgroup=testlocal$i
+ ntgroup=ntlgrp$i
+ rid=$(expr 10000 + $i)
+ testone add rid=$rid unixgroup=$unixgroup ntgroup=$ntgroup type=local || exit 1
+done
+
+echo "trying a duplicate add"
+testone add rid=10001 unixgroup=testlocal1 ntgroup=foo type=local && exit 1
+
+treport
+
+echo "Creating domain groups"
+for i in $(seq 1 1 $NGROUP); do
+ unixgroup=testgrp$i
+ ntgroup=ntgrp$i
+ rid=$(expr 20000 + $i)
+ testone add rid=$rid unixgroup=$unixgroup ntgroup=$ntgroup type=domain || exit 1
+done
+
+treport
+
+echo "Creating builtin groups"
+for i in $(seq 1 1 $NBUILTIN); do
+ unixgroup=testb$i
+ ntgroup=ntbgrp$i
+ rid=$(expr 30000 + $i)
+ testone add rid=$rid unixgroup=$unixgroup ntgroup=$ntgroup type=builtin || exit 1
+done
+
+treport
+
+echo "Adding domain groups to local groups"
+for i in $(seq 1 1 $NLOCAL); do
+ for j in $(seq 1 1 $i); do
+
+ lrid=$(expr 10000 + $i)
+ drid=$(expr 20000 + $j)
+
+ testone addmem $DOMSID-$lrid $DOMSID-$drid || exit 1
+ (testone listmem $DOMSID-$lrid | sort -r) || exit 1
+ done
+done
+
+echo "trying a duplicate addmem"
+testone addmem $DOMSID-10001 $DOMSID-20001 && exit 1
+
+echo "Adding foreign SIDs to local groups"
+for i in $(seq 1 1 $NLOCAL); do
+ for j in $(seq 1 1 $i); do
+
+ lrid=$(expr 10000 + $i)
+ frid=$(expr 70000 + $j)
+
+ testone addmem $DOMSID-$lrid $FORSID-$frid || exit 1
+ (testone listmem $DOMSID-$lrid | sort -r) || exit 1
+ done
+done
+
+echo "trying a duplicate foreign addmem"
+testone addmem $DOMSID-10001 $FORSID-70001 && exit 1
+
+treport
+
+echo "Listing local group memberships of domain groups"
+for i in $(seq 1 1 $NGROUP); do
+ rid=$(expr 20000 + $i)
+ (testone memberships $DOMSID-$rid | sort -r) || exit 1
+done
+
+echo "Trying memberships on bogus sid"
+testone memberships $DOMSID-999999 || exit 1
+
+treport
+
+testone list | sort
+
+echo "Deleting some domain groups"
+for i in $(seq 2 2 $NGROUP); do
+ drid=$(expr 20000 + $i)
+ testone delete sid=$DOMSID-$drid || exit 1
+done
+
+echo "Trying duplicate domain group delete"
+testone delete sid=$DOMSID-20002 && exit 1
+
+treport
+
+echo "Deleting some local groups"
+for i in $(seq 2 4 $NLOCAL); do
+ lrid=$(expr 10000 + $i)
+ testone delete sid=$DOMSID-$lrid || exit 1
+done
+
+echo "Trying duplicate local group delete"
+testone delete sid=$DOMSID-10002 && exit 1
+
+treport
+
+echo "Modifying some domain groups"
+for i in $(seq 3 2 $NGROUP); do
+ drid=$(expr 20000 + $i)
+ testone modify sid=$DOMSID-$drid comment="newcomment-$i" type=domain || exit 1
+done
+
+treport
+
+testone list | sort
+
+echo "Listing local group memberships"
+for i in $(seq 1 1 $NLOCAL); do
+ rid=$(expr 20000 + $i)
+ (testone memberships $DOMSID-$rid | sort -r) || exit 1
+done
+
+treport
+
+echo "Removing some domain groups from local groups"
+for i in $(seq 1 2 $NLOCAL); do
+ for j in $(seq 1 3 $i); do
+
+ lrid=$(expr 10000 + $i)
+ drid=$(expr 20000 + $j)
+
+ testone delmem $DOMSID-$lrid $DOMSID-$drid || exit 1
+ done
+done
+
+echo "Trying duplicate delmem"
+testone delmem $DOMSID-10001 $DOMSID-20001 && exit 1
+
+treport
+
+echo "Listing local group memberships"
+for i in $(seq 1 1 $NLOCAL); do
+ rid=$(expr 20000 + $i)
+ (testone memberships $DOMSID-$rid | sort -r) || exit 1
+done
+
+treport
+
+echo "Deleting unix groups"
+for i in $(seq 1 1 $NLOCAL); do
+ unixgroup=testlocal$i
+ groupdel $unixgroup 2>/dev/null
+done
+for i in $(seq 1 1 $NGROUP); do
+ unixgroup=testgrp$i
+ groupdel $unixgroup 2>/dev/null
+done
+for i in $(seq 1 1 $NBUILTIN); do
+ unixgroup=testb$i
+ groupdel $unixgroup 2>/dev/null
+done
+
+treport
+
+echo "ALL DONE"
diff --git a/source3/script/tests/test_guest_auth.sh b/source3/script/tests/test_guest_auth.sh
new file mode 100755
index 0000000..fc18114
--- /dev/null
+++ b/source3/script/tests/test_guest_auth.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# Test guest authentication
+#
+# Copyright (C) 2019 Ralph Boehme
+#
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: $0 SERVER SMBCLIENT SMBCONTROL NET CONFIGURATION
+EOF
+ exit 1
+fi
+
+SERVER=$1
+SMBCLIENT=$2
+SMBCONTROL=$3
+NET=$4
+CONFIGURATION=$5
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+SIDS=""
+
+prepare_empty_builtin_guests()
+{
+ TMP=$($NET $CONFIGURATION groupmap listmem S-1-5-32-546 2>&1)
+ bg_exists=$?
+ if [ $bg_exists != 0 ]; then
+ printf "Group map for BUILTIN\\Guests must exist for test\n"
+ return 1
+ fi
+
+ SIDS=$($NET $CONFIGURATION groupmap listmem S-1-5-32-546)
+ if [ $? != 0 ]; then
+ printf "$NET $CONFIGURATION groupmap listmem S-1-5-32-546 failed. Returned:\n"
+ printf "$SIDS\n"
+ return 1
+ fi
+ printf "Got S-1-5-32-546 members:\n$SIDS\n"
+
+ if [ "$SIDS" != "" ]; then
+ for SID in $SIDS; do
+ printf "Deleting member $SID from S-1-5-32-546\n"
+ $NET $CONFIGURATION groupmap delmem S-1-5-32-546 $SID || return 1
+ done
+ fi
+
+ return 0
+}
+
+add_local_guest_to_builtin_guests()
+{
+ if [ "$SIDS" != "" ]; then
+ for SID in $SIDS; do
+ printf "Adding $SID as member to S-1-5-32-546\n"
+ $NET $CONFIGURATION groupmap addmem S-1-5-32-546 $SID || return 1
+ done
+ fi
+}
+
+test_smbclient()
+{
+ $SMBCLIENT -U foo%bar //$SERVER/tmpguest -c exit
+ if [ $? != 0 ]; then
+ printf "smbclient failed\n"
+ return 1
+ fi
+ return 0
+}
+
+testit "smbclient_guest_at_startup" \
+ test_smbclient ||
+ failed=$(expr $failed + 1)
+
+printf "Prepare BUILTIN\\Guests group mapping without members\n"
+
+prepare_empty_builtin_guests || {
+ printf "Setting up BUILTIN\\Guests without members failed\n"
+ exit 1
+}
+
+$SMBCONTROL $CONFIGURATION smbd reload-config || {
+ printf "Reloading parent smbd guest info failed\n"
+ exit 1
+}
+
+testit "smbclient_guest_auth_without_members" \
+ test_smbclient ||
+ failed=$(expr $failed + 1)
+
+# restore config
+add_local_guest_to_builtin_guests
+
+$SMBCONTROL $CONFIGURATION smbd reload-config || {
+ printf "Reloading parent smbd guest info failed\n"
+ exit 1
+}
+
+testit "smbclient_works_after_restored_setup" \
+ test_smbclient ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_homes.sh b/source3/script/tests/test_homes.sh
new file mode 100755
index 0000000..be8e9b2
--- /dev/null
+++ b/source3/script/tests/test_homes.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+
+# Copyright (c) Andreas Schneider <asn@samba.org>
+# License: GPLv3
+
+if [ $# -lt 7 ]; then
+ echo "Usage: test_homes.sh SERVER USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT CONFIGURATION"
+ exit 1
+fi
+
+SERVER="${1}"
+USERNAME="${2}"
+PASSWORD="${3}"
+LOCAL_PATH="${4}"
+PREFIX="${5}"
+SMBCLIENT="${6}"
+CONFIGURATION="${7}"
+shift 7
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_gooduser_home()
+{
+ tmpfile=$PREFIX/smbclient_homes_gooduser_commands
+ cat >$tmpfile <<EOF
+ls
+quit
+EOF
+
+ USERNAME=gooduser
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/$USERNAME $CONFIGURATION < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed to connect error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'Try "help" to get a list of possible commands.'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo 'failed - should get: Try "help" to get a list of possible commands.'
+ return 1
+ fi
+
+ return 0
+}
+
+test_eviluser_home()
+{
+ tmpfile=$PREFIX/smbclient_homes_eviluser_commands
+ cat >$tmpfile <<EOF
+ls
+quit
+EOF
+
+ USERNAME=eviluser
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/$USERNAME $CONFIGURATION < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 1 ]; then
+ echo "$out"
+ echo "The server should reject connecting ret=$ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS_BAD_NETWORK_NAME'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo 'failed - should get: NT_STATUS_BAD_NETWORK_NAME.'
+ return 1
+ fi
+
+ return 0
+}
+
+test_slashuser_home()
+{
+ tmpfile=$PREFIX/smbclient_homes_slashuser_commands
+ cat >$tmpfile <<EOF
+ls
+quit
+EOF
+
+ USERNAME=slashuser
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/$USERNAME $CONFIGURATION < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 1 ]; then
+ echo "$out"
+ echo "The server should reject connecting ret=$ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS_BAD_NETWORK_NAME'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo 'failed - should get: NT_STATUS_BAD_NETWORK_NAME.'
+ return 1
+ fi
+
+ return 0
+}
+
+testit "test gooduser home" \
+ test_gooduser_home ||
+ failed=$(expr $failed + 1)
+
+testit "test eviluser home reject" \
+ test_eviluser_home ||
+ failed=$(expr $failed + 1)
+
+testit "test slashuser home reject" \
+ test_slashuser_home ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_inherit_owner.sh b/source3/script/tests/test_inherit_owner.sh
new file mode 100755
index 0000000..aa61671
--- /dev/null
+++ b/source3/script/tests/test_inherit_owner.sh
@@ -0,0 +1,170 @@
+#!/bin/sh
+
+# this tests "inherit owner" config parameter
+# currently needs to run in SMB1 mode, because it uses UNIX
+# extensions to fetch the UNIX owner of a file.
+
+if [ $# -lt 10 ]; then
+ cat <<EOF
+Usage: $0 SERVER USERNAME PASSWORD PREFIX SMBCLIENT SMBCACLS NET SHARE INH_WIN INH_UNIX <additional args>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+USERNAME="$2"
+PASSWORD="$3"
+PREFIX="$4"
+SMBCLIENT="$5"
+SMBCACLS="$6"
+NET="$7"
+SHARE="$8"
+INH_WIN="$9"
+INH_UNIX="${10}"
+shift 10
+ADDARGS="$*"
+SMBCLIENT="$VALGRIND ${SMBCLIENT} ${ADDARGS}"
+SMBCACLS="$VALGRIND ${SMBCACLS} ${ADDARGS}"
+NET="$VALGRIND ${NET}"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+create_file()
+{
+ local share=$1
+ local fname=$2
+ local rem_dirname=$(dirname $fname)
+ local bname=$(basename $fname)
+ touch $PREFIX/$bname
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "cd $rem_dirname; rm $bname" 2>/dev/null
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "cd $rem_dirname; allinfo $bname" 2>/dev/null | grep "NT_STATUS_OBJECT_NAME_NOT_FOUND" || exit 1
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "lcd $PREFIX; cd $rem_dirname; put $bname" 2>/dev/null || exit 1
+}
+
+create_dir()
+{
+ local share=$1
+ local dname=$2
+ local rem_dirname=$(dirname $dname)
+ local bname=$(basename $dname)
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "cd $rem_dirname; rmdir $bname" 2>/dev/null
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "cd $rem_dirname; allinfo $bname" 2>/dev/null | grep "NT_STATUS_OBJECT_NAME_NOT_FOUND" || exit 1
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "cd $rem_dirname; mkdir $bname" 2>/dev/null || exit 1
+}
+
+cleanup_file()
+{
+ local share=$1
+ local fname=$2
+ local rem_dirname=$(dirname $fname)
+ local bname=$(basename $fname)
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "cd $rem_dirname; rm $bname" 2>/dev/null || exit 1
+}
+
+cleanup_dir()
+{
+ local share=$1
+ local dname=$2
+ local rem_dirname=$(dirname $dname)
+ local bname=$(basename $dname)
+ $SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "cd $rem_dirname; rmdir $bname" 2>/dev/null || exit 1
+}
+
+set_win_owner()
+{
+ local share=$1
+ local fname=$2
+ local owner=$3
+ $SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD -C $owner 2>/dev/null || exit 1
+}
+
+unix_owner_id_is()
+{
+ local share=$1
+ local fname=$2
+ local expected_id=$3
+ local actual_id
+ actual_id=$($SMBCLIENT //$SERVER/$share -U $USERNAME%$PASSWORD -c "posix; getfacl $fname" 2>/dev/null | sed -rn 's/^# owner: (.*)/\1/p')
+ if ! test "x$actual_id" = "x$expected_id"; then
+ echo "Actual uid of $share/$fname is [$actual_id] expected [$expected_id]"
+ exit 1
+ fi
+}
+
+get_unix_id()
+{
+ local user=$1
+ local ent
+ ent=$(getent passwd $user) || exit 1
+ echo "$ent" | awk -F: '{print $3}'
+}
+
+win_owner_is()
+{
+ local share=$1
+ local fname=$2
+ local expected_owner=$3
+ local actual_owner
+ actual_owner=$($SMBCACLS //$SERVER/$share $fname -U $USERNAME%$PASSWORD 2>/dev/null | sed -rn 's/^OWNER:(.*)/\1/p')
+ if ! test "x$actual_owner" = "x$expected_owner"; then
+ echo "Actual owner of $share/$fname is [$actual_owner] expected [$expected_owner]"
+ exit 1
+ fi
+}
+
+default_uid=$(get_unix_id $USERNAME)
+alt_uid=$(get_unix_id force_user)
+
+if [ "$INH_WIN" = "0" ] && [ "$INH_UNIX" = "0" ]; then
+ #default - file owned by creator, change-owner modifies both
+ WIN_OWNER_AFTER_CREATE="$SERVER/$USERNAME"
+ UNIX_OWNER_AFTER_CREATE=$(get_unix_id $USERNAME)
+ WIN_OWNER_AFTER_CHOWN="$SERVER/smbget_user"
+ UNIX_OWNER_AFTER_CHOWN=$(get_unix_id smbget_user)
+ TEST_LABEL="default"
+elif [ "$INH_WIN" = "1" ] && [ "$INH_UNIX" = "1" ]; then
+ #inherit owner=windows and unix - file owned by parent
+ #owner, change-owner modifies both
+ WIN_OWNER_AFTER_CREATE="$SERVER/force_user"
+ UNIX_OWNER_AFTER_CREATE=$(get_unix_id force_user)
+ WIN_OWNER_AFTER_CHOWN="$SERVER/smbget_user"
+ UNIX_OWNER_AFTER_CHOWN=$(get_unix_id smbget_user)
+ TEST_LABEL="both"
+elif [ "$INH_WIN" = "0" ] && [ "$INH_UNIX" = "1" ]; then
+ #inherit owner=unix only - windows owner is creator,
+ #unix owner inherited, upon change-owner only windows
+ #owner is changed
+ WIN_OWNER_AFTER_CREATE="$SERVER/$USERNAME"
+ UNIX_OWNER_AFTER_CREATE=$(get_unix_id force_user)
+ WIN_OWNER_AFTER_CHOWN="$SERVER/smbget_user"
+ UNIX_OWNER_AFTER_CHOWN=$(get_unix_id force_user)
+ TEST_LABEL="unix"
+else
+ echo "Unknown combination INH_WIN=$INH_WIN INH_UNIX=$INH_UNIX"
+ exit 1
+fi
+
+# SETUP
+testit "$TEST_LABEL - setup root dir" create_dir tmp tmp.$$
+testit "grant SeRestorePrivilege" $NET rpc rights grant $USERNAME SeRestorePrivilege -U $USERNAME%$PASSWORD -I $SERVER || exit 1
+testit "$TEST_LABEL - assign default ACL" $SMBCACLS //$SERVER/tmp tmp.$$ -U $USERNAME%$PASSWORD -S "REVISION:1,OWNER:$SERVER\force_user,GROUP:$SERVER\domusers,ACL:Everyone:ALLOWED/0x3/FULL" 2>/dev/null
+# END SETUP
+
+testit "$TEST_LABEL - create subdir under root" create_dir $SHARE tmp.$$/subdir
+testit "$TEST_LABEL - verify subdir win owner" win_owner_is $SHARE tmp.$$/subdir "$WIN_OWNER_AFTER_CREATE"
+testit "$TEST_LABEL - verify subdir unix owner" unix_owner_id_is $SHARE tmp.$$/subdir $UNIX_OWNER_AFTER_CREATE
+testit "$TEST_LABEL - create file under root" create_file $SHARE tmp.$$/afile
+testit "$TEST_LABEL - verify file win owner" win_owner_is $SHARE tmp.$$/afile "$WIN_OWNER_AFTER_CREATE"
+testit "$TEST_LABEL - verify file unix owner" unix_owner_id_is $SHARE tmp.$$/afile $UNIX_OWNER_AFTER_CREATE
+testit "$TEST_LABEL - change dir owner" set_win_owner $SHARE tmp.$$/subdir "$SERVER\smbget_user"
+testit "$TEST_LABEL - verify subdir win owner after change" win_owner_is $SHARE tmp.$$/subdir "$WIN_OWNER_AFTER_CHOWN"
+testit "$TEST_LABEL - verify subdir unix owner after change" unix_owner_id_is $SHARE tmp.$$/subdir $UNIX_OWNER_AFTER_CHOWN
+testit "$TEST_LABEL - change file owner" set_win_owner $SHARE tmp.$$/afile "$SERVER\smbget_user"
+testit "$TEST_LABEL - verify file win owner after change" win_owner_is $SHARE tmp.$$/afile "$WIN_OWNER_AFTER_CHOWN"
+testit "$TEST_LABEL - verify file unix owner after change" unix_owner_id_is $SHARE tmp.$$/afile $UNIX_OWNER_AFTER_CHOWN
+testit "$TEST_LABEL - cleanup subdir" cleanup_dir $SHARE tmp.$$/subdir
+testit "$TEST_LABEL - cleanup file" cleanup_file $SHARE tmp.$$/afile
+testit "$TEST_LABEL - cleanup root" cleanup_dir $SHARE tmp.$$
+
+testit "revoke SeRestorePrivilege" $NET rpc rights revoke $USERNAME SeRestorePrivilege -U $USERNAME%$PASSWORD -I $SERVER || exit 1
diff --git a/source3/script/tests/test_large_acl.sh b/source3/script/tests/test_large_acl.sh
new file mode 100755
index 0000000..4e98c2e
--- /dev/null
+++ b/source3/script/tests/test_large_acl.sh
@@ -0,0 +1,61 @@
+#!/usr/bin/env bash
+#
+# Blackbox test for fetching a large ACL
+#
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: $0 SERVER USERNAME PASSWORD SMBCLIENT SMBCACLS PARAMS
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+USERNAME=${2}
+PASSWORD=${3}
+SMBCLIENT=${4}
+SMBCACLS=${5}
+shift 5
+ADDARGS="$*"
+SMBCLIENT="$VALGRIND ${SMBCLIENT} ${ADDARGS}"
+SMBCACLS="$VALGRIND ${SMBCACLS} ${ADDARGS}"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+cd $SELFTEST_TMPDIR || exit 1
+
+# build a file to work with
+build_files()
+{
+ touch large_acl
+ $SMBCLIENT //$SERVER/acl_xattr_ign_sysacl_windows -U $USERNAME%$PASSWORD -c 'put large_acl' >/dev/null 2>&1
+ rm -rf large_acl >/dev/null
+}
+
+cleanup()
+{
+ $SMBCLIENT //$SERVER/acl_xattr_ign_sysacl_windows -U $USERNAME%$PASSWORD -c 'rm large_acl' >/dev/null 2>&1
+}
+
+build_files
+
+test_large_acl()
+{
+ #An ACL with 200 entries, ~7K
+ new_acl=$(seq 1001 1200 | sed -r -e '1 i\D:(A;;FA;;;WD)' -e 's/(.*)/(A;;FA;;;S-1-5-21-11111111-22222222-33333333-\1)/' | tr -d '\n')
+ $SMBCACLS //$SERVER/acl_xattr_ign_sysacl_windows -U $USERNAME%$PASSWORD --sddl -S $new_acl large_acl
+ actual_acl=$($SMBCACLS //$SERVER/acl_xattr_ign_sysacl_windows -U $USERNAME%$PASSWORD --sddl --numeric large_acl 2>/dev/null | sed -rn 's/.*(D:.*)/\1/p' | tr -d '\n')
+ if [ ! "$new_acl" = "$actual_acl" ]; then
+ echo -e "expected:\n$new_acl\nactual:\n$actual_acl\n"
+ return 1
+ fi
+}
+
+failed=0
+
+testit "able to retrieve a large ACL if VFS supports it" test_large_acl || failed=$(expr $failed + 1)
+
+cleanup
+
+exit $failed
diff --git a/source3/script/tests/test_libwbclient_threads.sh b/source3/script/tests/test_libwbclient_threads.sh
new file mode 100755
index 0000000..4f7ac12
--- /dev/null
+++ b/source3/script/tests/test_libwbclient_threads.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: test_libwbclient_threads.sh DOMAIN USERNAME
+EOF
+ exit 1
+fi
+
+DOMAIN="$1"
+USERNAME="$2"
+shift 2
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+testit "libwbclient-threads" "$BINDIR/stress-nss-libwbclient" "$DOMAIN/$USERNAME"
diff --git a/source3/script/tests/test_list_nt4_trust.sh b/source3/script/tests/test_list_nt4_trust.sh
new file mode 100755
index 0000000..03ee7fc
--- /dev/null
+++ b/source3/script/tests/test_list_nt4_trust.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+failed=0
+
+wbinfo="$BINDIR/wbinfo"
+smbclient="$BINDIR/smbclient"
+
+test_trust_wbinfo_m() {
+ i=0
+ # Give the server some time to list trusted domains
+ while [ $i -lt 10 ] ; do
+ $wbinfo -m --verbose | grep "SAMBA-TEST" && return 0
+ sleep 2
+ i=$((i + 1))
+ done
+ return 1
+}
+
+testit "nt4trust_wbinfo_m" test_trust_wbinfo_m || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_local_s3.sh b/source3/script/tests/test_local_s3.sh
new file mode 100755
index 0000000..9de542a
--- /dev/null
+++ b/source3/script/tests/test_local_s3.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+# this runs the file serving tests that are expected to pass with samba3
+
+if [ $# != 0 ]; then
+ cat <<EOF
+Usage: test_local_s3.sh
+EOF
+ exit 1
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+if test -x $BINDIR/talloctort; then
+ testit "talloctort" $VALGRIND $BINDIR/talloctort ||
+ failed=$(expr $failed + 1)
+else
+ echo "Skipping talloctort"
+fi
+
+testit "replace_testsuite" $VALGRIND $BINDIR/replace_testsuite ||
+ failed=$(expr $failed + 1)
+
+if test -x $BINDIR/tdbtorture; then
+ testit "tdbtorture" $VALGRIND $BINDIR/tdbtorture ||
+ failed=$(expr $failed + 1)
+else
+ echo "Skipping tdbtorture"
+fi
+
+testit "smbconftort" $VALGRIND $BINDIR/smbconftort $CONFIGURATION ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_cache_samlogon.sh b/source3/script/tests/test_net_cache_samlogon.sh
new file mode 100755
index 0000000..671806a
--- /dev/null
+++ b/source3/script/tests/test_net_cache_samlogon.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+#
+# Test 'net cache samlogon' command.
+#
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: $0 SERVER SHARE USER PASS
+EOF
+ exit 1
+fi
+
+SERVER=$1
+SHARE=$2
+USER=$3
+PASS=$4
+smbclient=$BINDIR/smbclient
+
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+# Ensure the samlogon cache is primed
+test_smbclient "Prime samlogon cache" 'exit' //$SERVER/$SHARE -U$USER%$PASS || failed=$(expr $failed + 1)
+
+# Ensure list works and remember the sid and name of the first entry
+testit "net cache samlogon list" $BINDIR/net cache samlogon list || failed=$(expr $failed + 1)
+usersid=$($BINDIR/net cache samlogon list | awk '/^S-/ { print $1 ; exit }')
+username=$($BINDIR/net cache samlogon list | awk '/^S-/ { print $2 ; exit }')
+
+# Test the show command with the sid from the previous list command
+testit "net cache samlogon show $usersid" $BINDIR/net cache samlogon show $usersid || failed=$(expr $failed + 1)
+tmp=$($BINDIR/net cache samlogon show $usersid | awk '/^Name:/ {print $2}')
+testit "net cache samlogon show SID name matches name from list command" test x"$tmp" = x"$username" || failed=$(expr $failed + 1)
+
+testit "net cache samlogon ndrdump $usersid" $BINDIR/net cache samlogon ndrdump $usersid || failed=$(expr $failed + 1)
+tmp=$($BINDIR/net cache samlogon ndrdump $usersid | head -n 1 | grep "netr_SamInfo3: struct netr_SamInfo3")
+retval=$?
+testit "net cache samlogon ndrdump returns netr_SamInfo3 structure" test $retval -eq 0 || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_conf.sh b/source3/script/tests/test_net_conf.sh
new file mode 100755
index 0000000..7a70a9b
--- /dev/null
+++ b/source3/script/tests/test_net_conf.sh
@@ -0,0 +1,1042 @@
+#!/bin/sh
+#
+# Blackbox test for net [rpc] conf.
+#
+# Copyright (C) 2011 Vicentiu Ciorbaru <cvicentiu@gmail.com>
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_net_conf.sh SCRIPTDIR SERVERCONFFILE NET CONFIGURATION [rpc]
+EOF
+ exit 1
+fi
+
+SCRIPTDIR="$1"
+SERVERCONFFILE="$2"
+NET="$3"
+CONFIGURATION="$4"
+RPC="$5"
+
+LOGDIR_PREFIX="conf_test"
+
+# remove old logs:
+for OLDDIR in $(find ${PREFIX} -type d -name "${LOGDIR_PREFIX}_*"); do
+ echo "removing old directory ${OLDDIR}"
+ rm -rf ${OLDDIR}
+done
+
+NET="$VALGRIND ${NET:-$BINDIR/net} $CONFIGURATION"
+DIR=$(mktemp -d ${PREFIX}/${LOGDIR_PREFIX}_XXXXXX)
+LOG=$DIR/log
+
+if test "x${RPC}" = "xrpc"; then
+ NETCMD="${NET} -U${USERNAME}%${PASSWORD} -I ${SERVER_IP} rpc"
+else
+ NETCMD="${NET}"
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+log_print()
+{
+ RC=$?
+ echo "CMD: $*" >>$LOG
+ echo "RC: $RC" >>$LOG
+ return $RC
+ # echo -n .
+}
+
+test_conf_addshare()
+{
+ echo '\nTesting conf addshare' >>$LOG
+ echo ------------------------- >>$LOG
+ echo '\nDropping existing configuration' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ #create a lot of shares
+ for i in $(seq 1 100); do
+ if [ $(($i % 2)) -eq 0 ]; then
+ $NETCMD conf addshare share$i /tmp "writeable=y" "guest_ok=n" \
+ "test comment" >>$DIR/addshare_exp \
+ 2>>$DIR/addshare_exp
+ log_print $NETCMD conf addshare share$i /tmp "writeable=y" "guest_ok=n" \
+ "test comment"
+ else
+ $NETCMD conf addshare share$i /tmp "writeable=n" "guest_ok=y" \
+ "test comment" >>$DIR/addshare_exp \
+ 2>>$DIR/addshare_exp
+ log_print $NETCMD conf addshare share$i /tmp "writeable=n" "guest_ok=y" \
+ "test comment"
+ fi
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+ done
+
+ $NETCMD conf listshares >$DIR/listshares_out
+ log_print $NETCMD conf listshares
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ for i in $(seq 1 100); do
+ grep "share$i" $DIR/listshares_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not found" | tee -a $LOG
+ return 1
+ fi
+ done
+
+ #check the integrity of the shares
+ #if it fails, it can also point to an error in showshare
+ for i in $(seq 1 100); do
+ $NETCMD conf showshare share$i >$DIR/showshare_out
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "path" $DIR/showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not found" | tee -a $LOG
+ return 1
+ fi
+
+ if [ $(($i % 2)) -eq 0 ]; then
+ grep "read only *= *no" $DIR/showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not set correctly" | tee -a $LOG
+ return 1
+ fi
+ else
+ grep "read only *= *yes" $DIR/showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not set correctly" | tee -a $LOG
+ return 1
+ fi
+ fi
+
+ if [ $(($i % 2)) -eq 0 ]; then
+ grep "guest ok *= *no" $DIR/showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not set correctly" | tee -a $LOG
+ return 1
+ fi
+ else
+ grep "guest ok *= *yes" $DIR/showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not set correctly" | tee -a $LOG
+ return 1
+ fi
+ fi
+
+ grep "comment *= *test comment" $DIR/showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not set correctly" | tee -a $LOG
+ return 1
+ fi
+ done
+
+ echo '\nTaking a conf snapshot for later use' >>$LOG
+ $NETCMD conf list >$DIR/conf_import_in
+ log_print $NETCMD conf list
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+}
+
+test_conf_addshare_existing()
+{
+ #try adding an already existing share
+ echo '\nAdding an already existing share' >>$LOG
+ $NETCMD conf addshare share1 /tmp "writeable=n" "guest_ok=y" \
+ "test comment" >>$DIR/addshare_exp \
+ 2>>$DIR/addshare_exp
+ log_print $NETCMD conf addshare share1 /tmp "writeable=n" "guest_ok=y" \
+ "test comment"
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ test -z $(cat $DIR/addshare_exp) && {
+ echo "ERROR: addshare output does not match" >>$LOG
+ return 1
+ }
+
+ return 0
+}
+
+test_conf_addshare_usage()
+{
+ #check to see if command prints usage
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf addshare >$DIR/addshare_usage_exp
+ log_print $NETCMD conf addshare
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf addshare" $DIR/addshare_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_delshare()
+{
+ echo '\nTesting conf delshare' >>$LOG
+ echo ------------------------- >>$LOG
+ echo -n '\n' >>$LOG
+
+ $NETCMD conf delshare share1
+ log_print $NETCMD conf delshare share1
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf listshares >$DIR/listshares_out
+ log_print $NETCMD conf listshares
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "share1$" $DIR/listshares_out >/dev/null 2>>$LOG
+ if [ "$?" = "0" ]; then
+ echo "ERROR: delshare did not delete 'share1'" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_delshare_empty()
+{
+ echo '\nAttempting to delete non_existing share'
+ $NETCMD conf delshare share1
+ log_print $NETCMD conf delshare share1
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+}
+
+test_conf_delshare_usage()
+{
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf delshare >$DIR/delshare_usage_exp
+ log_print $NETCMD conf delshare
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf delshare" $DIR/delshare_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_showshare_case()
+{
+ echo '\nChecking case in net conf shareshare' >>$LOG
+
+ echo '\nDropping existing configuration' >>$LOG
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ for share in UPPERCASE lowercase; do
+
+ log_print $NETCMD conf addshare $share /tmp
+ $NETCMD conf addshare $share /tmp \
+ >>$DIR/case_addshare_exp \
+ 2>>$DIR/case_addshare_exp
+
+ # Lookup share in different case, check that output has
+ # share name in correct case.
+ switch_case=$(echo $share | tr 'A-Za-z' 'a-zA-Z')
+ log_print $NETCMD conf showshare $switch_case
+ $NETCMD conf showshare $switch_case >$DIR/showshare_out
+ test "x$?" = "x0" || {
+ echo 'ERROR: net conf showshare failed.' | tee -a $LOG
+ return 1
+ }
+
+ grep "\[$share\]" $DIR/showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: share not found" | tee -a $LOG
+ return 1
+ fi
+ done
+
+}
+
+test_conf_drop()
+{
+
+ echo '\nTesting conf drop' >>$LOG
+ echo ------------------------- >>$LOG
+ echo '\nDropping existing configuration' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ #check to see if listing the configuration yields a blank file
+ $NETCMD conf list 1>>$DIR/list_out
+ log_print $NETCMD conf list
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ test -z "$(cat $DIR/list_out)" || {
+ echo "ERROR: Expected list output did not match" | tee -a $LOG
+ return 1
+ }
+}
+
+test_conf_drop_empty()
+{
+ #Drop an empty config, see if conf drop fails
+ echo '\nAttempting to drop an empty configuration' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ #check to see if listing the configuration yields a blank file
+ $NETCMD conf list 1>>$DIR/list_out
+ log_print $NETCMD conf list
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ test -z "$(cat $DIR/list_out)" || {
+ echo ERROR:Expected list output did not match >>$LOG
+ return 1
+ }
+}
+
+test_conf_drop_usage()
+{
+ #check to see if command prints usage
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf drop extra_arg >$DIR/drop_usage_exp
+ log_print $NETCMD conf drop extra_arg
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf drop" $DIR/drop_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_setparm()
+{
+ echo '\nTesting conf setparm' >>$LOG
+ echo ------------------------- >>$LOG
+
+ echo '\nDropping existing configuration' >>$LOG
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf setparm share1 "read only" yes
+ log_print $NETCMD conf setparm share1 "read only" yes
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf setparm share1 "path" /tmp/test_path
+ log_print $NETCMD conf setparm share1 "path" /tmp/test_path
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf showshare share1 >$DIR/setparm_showshare_out
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "read only *= *yes" $DIR/setparm_showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setparm did not set correctly" | tee -a $LOG
+ return 1
+ fi
+
+ grep "path *= */tmp/test_path" $DIR/setparm_showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setparm did not set correctly" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_setparm_existing()
+{
+
+ echo '\nSetting already existing param with the same value'
+ $NETCMD conf setparm share1 "read only" yes
+ log_print $NETCMD conf setparm share1 "read only" yes
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf setparm share1 "read only" yes
+ log_print $NETCMD conf setparm share1 "read only" yes
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf showshare share1 >$DIR/setparm_existing_showshare_out
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "read only *= *yes" $DIR/setparm_existing_showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setparm did not set correctly" | tee -a $LOG
+ return 1
+ fi
+
+ $NETCMD conf setparm share1 "read only" no
+ log_print $NETCMD conf setparm share1 "read only" no
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf showshare share1 >$DIR/setparm_existing_showshare_out
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "read only *= *no" $DIR/setparm_existing_showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setparm did not set correctly" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_setparm_forbidden()
+{
+ FORBIDDEN_PARAMS="state directory
+lock directory
+lock dir
+config backend
+include"
+
+ echo '\nTrying to set forbidden parameters' >>$LOG
+
+ echo '\nDropping existing configuration' >>$LOG
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ OLD_IFS="$IFS"
+ IFS='
+'
+ for PARAM in $FORBIDDEN_PARAMS; do
+ IFS="$OLD_IFS"
+ echo "Trying to set parameter '$PARAM'" | tee -a $LOG
+ $NETCMD conf setparm global "$PARAM" "value" >$DIR/setparm_forbidden_out 2>&1
+ log_print $NETCMD conf setparm global \""$PARAM"\" "value"
+ test "x$?" = "x0" && {
+ echo "ERROR: setting forbidden parameter '$PARAM' succeeded" | tee -a $LOG
+ return 1
+ }
+
+ echo "output of net command: " | tee -a $LOG
+ cat $DIR/setparm_forbidden_out | tee -a $LOG
+
+ SEARCH="Parameter '$PARAM' not allowed in registry."
+ grep "$SEARCH" $DIR/setparm_forbidden_out >/dev/null 2>>$LOG
+ test "x$?" = "x0" || {
+ echo "ERROR: expected '$SEARCH'" | tee -a $LOG
+ return 1
+ }
+ done
+
+ IFS="$OLD_IFS"
+ return 0
+}
+
+test_conf_setparm_usage()
+{
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf setparm >$DIR/setparm_usage_exp
+ log_print $NETCMD conf setparm
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf setparm" $DIR/setparm_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setparm no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_delparm_delete_existing()
+{
+ echo '\nTesting conf delparm' >>$LOG
+ echo ------------------------- >>$LOG
+ echo -n '\n' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf addshare share1 /tmp "writeable=y" "guest_ok=n" \
+ "test comment"
+ log_print $NETCMD conf addshare share$i /tmp "writeable=y" "guest_ok=n" \
+ "test comment"
+
+ $NETCMD conf delparm share1 "path"
+ log_print $NETCMD conf delparm share1 "path"
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf showshare share1 >$DIR/delparm_showshare_out
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ #test to see what delparm did delete and how
+ grep "read only *= *no" $DIR/delparm_showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: delparm did not delete correctly" | tee -a $LOG
+ return 1
+ fi
+
+ grep "path *= */tmp" $DIR/delparm_showshare_out >/dev/null 2>>$LOG
+ if [ "$?" = "0" ]; then
+ echo "ERROR: delparm did not delete correctly" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_delparm_delete_non_existing()
+{
+ echo '\nDelete non existing share' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf delparm share1 "path"
+ log_print $NETCMD conf delparm share1 "path"
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+}
+
+test_conf_delparm_usage()
+{
+
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf delparm >$DIR/delparm_usage_exp
+ log_print $NETCMD conf delparm
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf delparm" $DIR/delparm_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: delparm no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+
+}
+
+test_conf_getparm()
+{
+
+ echo '\nTesting conf getparm' >>$LOG
+ echo ------------------------- >>$LOG
+ echo -n '\n' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ $NETCMD conf addshare share1 /tmp/path_test "writeable=n" "guest_ok=n" \
+ "test comment"
+ log_print $NETCMD conf addshare share$i /tmp/path_test "writeable=n" "guest_ok=n" \
+ "test comment"
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf getparm share1 "read only" >$DIR/getparm_out
+ log_print $NETCMD conf getparm share1 "read only"
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf getparm share1 "read only" >$DIR/getparm_out
+ log_print $NETCMD conf getparm share1 "read only"
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ echo yes >$DIR/getparm_exp
+ diff -q $DIR/getparm_out $DIR/getparm_exp >>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: getparm did not print correctly" | tee -a $LOG
+ return 1
+ fi
+
+ $NETCMD conf getparm share1 "path" >$DIR/getparm_out
+ log_print $NETCMD conf getparm share1 "path"
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ echo /tmp/path_test >$DIR/getparm_exp
+ diff -q $DIR/getparm_out $DIR/getparm_exp >>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: getparm did not print correctly" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_getparm_usage()
+{
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf getparm >$DIR/getparm_usage_exp
+ log_print $NETCMD conf getparm
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf getparm" $DIR/getparm_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: getparm no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+
+}
+
+test_conf_getparm_non_existing()
+{
+ echo '\nTesting getparm non existing' >>$LOG
+ $NETCMD conf getparm fictional_share fictional_param
+ log_print $NETCMD conf getparm fictional_share fictional_param
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf getparm share1 fictional_param
+ log_print $NETCMD conf getparm share1 fictional_param
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+}
+
+test_conf_setincludes()
+{
+ echo '\nTesting conf setincludes' >>$LOG
+ echo ------------------------- >>$LOG
+ echo '\nDropping existing configuration' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf addshare tmp_share /tmp
+ log_print $NETCMD conf addshare tmp_share /tmp
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf setincludes tmp_share /tmp/include1 /tmp/include2 /tmp/include3
+ log_print $NETCMD conf setincludes tmp_share /tmp/include1 /tmp/include2 /tmp/include3
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf list >$DIR/setincludes_list_out
+ log_print $NETCMD conf list
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "include *= */tmp/include1$" $DIR/setincludes_list_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setincludes did not set correctly" | tee -a $LOG
+ return 1
+ fi
+
+ grep "include *= */tmp/include2$" $DIR/setincludes_list_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setincludes did not set correctly" | tee -a $LOG
+ return 1
+ fi
+
+ grep "include *= */tmp/include3$" $DIR/setincludes_list_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: setincludes did not set correctly" | tee -a $LOG
+ return 1
+ fi
+
+}
+
+test_conf_setincludes_usage()
+{
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf setincludes >$DIR/setincludes_usage_exp
+ log_print $NETCMD conf setincludes
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf setincludes" $DIR/setincludes_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_getincludes()
+{
+ $NETCMD conf getincludes tmp_share >$DIR/getincludes_out
+ log_print $NETCMD conf getincludes tmp_share
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "include *= */tmp/include1$" $DIR/getincludes_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: getincludes did not print correctly" | tee -a $LOG
+ return 1
+ fi
+
+ grep "include *= */tmp/include2$" $DIR/getincludes_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: getincludes did not print correctly" | tee -a $LOG
+ return 1
+ fi
+ grep "include *= */tmp/include3$" $DIR/getincludes_out >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: getincludes did not print correctly" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_getincludes_usage()
+{
+ $NETCMD conf getincludes >$DIR/getincludes_usage_exp
+ log_print $NETCMD conf getincludes
+
+ grep "$RPC *conf getincludes" $DIR/getincludes_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_delincludes()
+{
+ echo '\nTesting conf delincludes' >>$LOG
+ echo ------------------------- >>$LOG
+
+ $NETCMD conf delincludes tmp_share
+ log_print $NETCMD conf delincludes tmp_share
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf list >$DIR/delincludes_list_out
+ log_print $NETCMD conf list
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ grep "include" $DIR/delincludes_list_out >/dev/null 2>>$LOG
+ if [ "$?" = "0" ]; then
+ echo "ERROR: delincludes did not delete correctly" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_delincludes_empty()
+{
+ $NETCMD conf delincludes tmp_share
+ log_print $NETCMD conf delincludes tmp_share
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf delincludes fictional_share
+ log_print $NETCMD conf delincludes fictional_share
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+ return 0
+}
+
+test_conf_delincludes_usage()
+{
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf delincludes >$DIR/delincludes_usage_exp
+ log_print $NETCMD conf delincludes
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf delincludes" $DIR/delincludes_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_import()
+{
+ echo '\nTesting conf import' >>$LOG
+ echo ------------------------- >>$LOG
+ echo '\nDropping existing configuration' >>$LOG
+
+ $NETCMD conf drop
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf import $DIR/conf_import_in
+ log_print $NETCMD conf drop
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ $NETCMD conf list >$DIR/conf_import_out
+ log_print $NETCMD conf list
+ test "x$?" = "x0" || {
+ echo 'ERROR: RC does not match, expected: 0' | tee -a $LOG
+ return 1
+ }
+
+ diff -q $DIR/conf_import_in $DIR/conf_import_out >>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: import failed" | tee -a $LOG
+ return 1
+ fi
+}
+
+test_conf_import_usage()
+{
+ echo '\nChecking usage' >>$LOG
+ $NETCMD conf import >$DIR/import_usage_exp
+ log_print $NETCMD conf import
+ test "x$?" = "x255" || {
+ echo 'ERROR: RC does not match, expected: 255' | tee -a $LOG
+ return 1
+ }
+
+ grep "$RPC *conf import" $DIR/import_usage_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: conf import no/wrong usage message printed" | tee -a $LOG
+ return 1
+ fi
+}
+
+CONF_FILES=$SERVERCONFFILE
+
+testit "conf_drop" \
+ test_conf_drop ||
+ failed=$(expr $failed + 1)
+
+testit "conf_drop_empty" \
+ test_conf_drop_empty ||
+ failed=$(expr $failed + 1)
+
+testit "conf_drop_usage" \
+ test_conf_drop_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_addshare" \
+ test_conf_addshare ||
+ failed=$(expr $failed + 1)
+
+testit "conf_addshare_existing" \
+ test_conf_addshare_existing ||
+ failed=$(expr $failed + 1)
+
+testit "conf_addshare_usage" \
+ test_conf_addshare_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delshare" \
+ test_conf_delshare ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delshare_empty" \
+ test_conf_delshare_empty ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delshare_usage" \
+ test_conf_delshare_usage ||
+ failed=$(expr $failed + 1)
+
+testit "test_conf_showshare_case" \
+ test_conf_showshare_case ||
+ failed=$(expr $failed + 1)
+
+testit "conf_setparm" \
+ test_conf_setparm ||
+ failed=$(expr $failed + 1)
+
+testit "conf_setparm_existing" \
+ test_conf_setparm_existing ||
+ failed=$(expr $failed + 1)
+
+testit "conf_setparm_forbidden" \
+ test_conf_setparm_forbidden ||
+ failed=$(expr $failed + 1)
+
+testit "conf_setparm_usage" \
+ test_conf_setparm_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delparm_delete_existing" \
+ test_conf_delparm_delete_existing ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delparm_delete_non_existing" \
+ test_conf_delparm_delete_non_existing ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delparm_delete_usage" \
+ test_conf_delparm_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_getparm" \
+ test_conf_getparm ||
+ failed=$(expr $failed + 1)
+
+testit "conf_getparm_usage" \
+ test_conf_getparm_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_setincludes" \
+ test_conf_setincludes ||
+ failed=$(expr $failed + 1)
+
+testit "conf_setincludes_usage" \
+ test_conf_setincludes_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_getincludes" \
+ test_conf_getincludes ||
+ failed=$(expr $failed + 1)
+
+testit "conf_getincludes_usage" \
+ test_conf_getincludes_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delincludes" \
+ test_conf_delincludes ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delincludes_empty" \
+ test_conf_delincludes_usage ||
+ failed=$(expr $failed + 1)
+
+testit "conf_delincludes_usage" \
+ test_conf_delincludes_empty ||
+ failed=$(expr $failed + 1)
+
+testit "conf_import" \
+ test_conf_import ||
+ failed=$(expr $failed + 1)
+
+testit "conf_import_usage" \
+ test_conf_import_usage ||
+ failed=$(expr $failed + 1)
+
+if [ $failed -eq 0 ]; then
+ rm -r $DIR
+fi
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_cred_change.sh b/source3/script/tests/test_net_cred_change.sh
new file mode 100755
index 0000000..7b01673
--- /dev/null
+++ b/source3/script/tests/test_net_cred_change.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+if [ $# -lt 1 ]; then
+ cat <<EOF
+Usage: test_net_cred_change.sh CONFIGURATION
+EOF
+ exit 1
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+testit "1: change machine secret" $VALGRIND $BINDIR/wbinfo --change-secret || failed=$(expr $failed + 1)
+testit "1: validate secret" $VALGRIND $BINDIR/net rpc testjoin "$@" || failed=$(expr $failed + 1)
+testit "2: change machine secret" $VALGRIND $BINDIR/wbinfo --change-secret || failed=$(expr $failed + 1)
+testit "2: validate secret" $VALGRIND $BINDIR/net rpc testjoin "$@" || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_cred_change_at.sh b/source3/script/tests/test_net_cred_change_at.sh
new file mode 100755
index 0000000..47434bc
--- /dev/null
+++ b/source3/script/tests/test_net_cred_change_at.sh
@@ -0,0 +1,33 @@
+#!/usr/bin/env bash
+
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: test_net_cred_change_at.sh CONFIGURATION
+EOF
+ exit 1
+fi
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+# shellcheck source=/dev/null
+. "$incdir/subunit.sh"
+
+test_change_machine_secret_at() {
+ local DC_SERVER
+ local REPL_TARGET
+
+ out=$("$BINDIR/wbinfo" --dc-info SAMBADOMAIN) || return 1
+ echo "$out"
+ echo "$out" | grep localdc && DC_SERVER=localvampiredc && REPL_TARGET=localdc
+ echo "$out" | grep localvampiredc && DC_SERVER=localdc && REPL_TARGET=localvampiredc
+ if [ -z $DC_SERVER ] ; then return 1 ; fi
+
+ $VALGRIND "$BINDIR/wbinfo" --change-secret-at=$DC_SERVER || return 1
+
+ # Force replication
+ $VALGRIND "$BINDIR/samba-tool" drs replicate -U Administrator%locDCpass1 $REPL_TARGET $DC_SERVER DC=samba,DC=example,DC=com
+}
+
+testit "change machine secret at" test_change_machine_secret_at || failed=$(("$failed" + 1))
+testit "validate secret" $VALGRIND "$BINDIR/net rpc testjoin" "$@" || failed=$(("$failed" + 1))
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_net_dom_join_fail_dc.sh b/source3/script/tests/test_net_dom_join_fail_dc.sh
new file mode 100755
index 0000000..4367492
--- /dev/null
+++ b/source3/script/tests/test_net_dom_join_fail_dc.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_net_dom_join_fail_dc.sh USERNAME PASSWORD DOMAIN PREFIX
+EOF
+ exit 1
+fi
+
+DC_USERNAME="$1"
+DC_PASSWORD="$2"
+DOMAIN="$3"
+PREFIX="$4"
+shift 4
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+mkdir -p $PREFIX/private
+testit_expect_failure "net_dom_join_fail_dc" $VALGRIND $BINDIR/net dom join domain=$DOMAIN account=$USERNAME password=$PASSWORD --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private $ADDARGS || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_lookup.sh b/source3/script/tests/test_net_lookup.sh
new file mode 100755
index 0000000..ee18333
--- /dev/null
+++ b/source3/script/tests/test_net_lookup.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+if [ $# != 6 ]; then
+ echo "Usage: $0 SERVER USERNAME PASSWORD NET SAMBA-TOOL DNS-ZONE"
+ exit 1
+fi
+
+SERVER="$1"
+shift 1
+USERNAME="$1"
+shift 1
+PASSWORD="$1"
+shift 1
+NET="$1"
+shift 1
+SAMBATOOL="$1"
+shift 1
+DNSZONE="$1"
+shift 1
+
+SITE="mysite"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+$SAMBATOOL dns add "$SERVER" -U "$USERNAME"%"$PASSWORD" \
+ _msdcs."$DNSZONE" _ldap._tcp."$SITE"._sites.dc \
+ SRV "mydc.$DNSZONE 389 100 100"
+$SAMBATOOL dns add "$SERVER" -U "$USERNAME"%"$PASSWORD" \
+ "$DNSZONE" mydc \
+ A "1.2.3.4"
+
+# global lookup
+testit_grep global 10.53.57.30:389 $NET lookup ldap "$DNSZONE" ||
+ failed=$(expr $failed + 1)
+
+# correct site-aware lookup
+testit_grep site-aware 1.2.3.4:389 $NET lookup ldap "$DNSZONE" "$SITE" ||
+ failed=$(expr $failed + 1)
+
+# lookup with nonexisting site -- global fallback
+testit_grep global 10.53.57.30:389 $NET lookup ldap "$DNSZONE" nosite ||
+ failed=$(expr $failed + 1)
+
+$SAMBATOOL dns delete "$SERVER" -U "$USERNAME"%"$PASSWORD" \
+ "$DNSZONE" mydc \
+ A "1.2.3.4"
+$SAMBATOOL dns delete "$SERVER" -U "$USERNAME"%"$PASSWORD" \
+ _msdcs."$DNSZONE" _ldap._tcp."$SITE"._sites.dc \
+ SRV "mydc.$DNSZONE 389 100 100"
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_machine_account.sh b/source3/script/tests/test_net_machine_account.sh
new file mode 100755
index 0000000..ad1d099
--- /dev/null
+++ b/source3/script/tests/test_net_machine_account.sh
@@ -0,0 +1,34 @@
+#!/bin/sh
+# Reproducer for https://bugzilla.samba.org/show_bug.cgi?id=14908
+
+if [ $# -lt 2 ]; then
+ echo "Usage: $0 NET CONFFILE SERVER_IP"
+ exit 1
+fi
+
+NET="$1"
+shift
+CONFFILE="$1"
+shift
+SERVER_IP="$1"
+shift
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+net_ads_user() {
+ _out=$(UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 \
+ ${VALGRIND} "${NET}" rpc user \
+ --configfile="${CONFFILE}" -S "${SERVER_IP}" -P)
+ _ret=$?
+
+ echo "${_out}"
+
+ return ${_ret}
+}
+
+testit "net_ads_user" net_ads_user || failed=$((failed + 1))
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_misc.sh b/source3/script/tests/test_net_misc.sh
new file mode 100755
index 0000000..3c2c01f
--- /dev/null
+++ b/source3/script/tests/test_net_misc.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+# various tests for the "net" command
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_net_misc.sh SCRIPTDIR SERVERCONFFILE NET CONFIGURATION
+EOF
+ exit 1
+fi
+
+SCRIPTDIR="$1"
+SERVERCONFFILE="$2"
+NET="$3"
+CONFIGURATION="$4"
+
+# optional protocol, default to NT1
+if [ $# -gt 4 ]; then
+ PROTOCOL="$5"
+else
+ PROTOCOL="NT1"
+fi
+
+NET="$VALGRIND ${NET:-$BINDIR/net} $CONFIGURATION"
+NETTIME="${NET} --option=clientmaxprotocol=${PROTOCOL} time"
+NETLOOKUP="${NET} --option=clientmaxprotocol=${PROTOCOL} lookup"
+NETSHARE="${NET} -U${USERNAME}%${PASSWORD} --option=clientmaxprotocol=${PROTOCOL} -S ${SERVER} share"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_time()
+{
+ PARAM="$1"
+
+ ${NETTIME} -S ${SERVER} ${PARAM}
+}
+
+test_lookup()
+{
+ PARAM="$1"
+
+ ${NETLOOKUP} ${PARAM}
+}
+
+test_share()
+{
+ PARAM="$1"
+
+ ${NETSHARE} ${PARAM}
+}
+
+testit "get the time" \
+ test_time ||
+ failed=$(expr $failed + 1)
+
+testit "get the system time" \
+ test_time system ||
+ failed=$(expr $failed + 1)
+
+testit "get the time zone" \
+ test_time zone ||
+ failed=$(expr $failed + 1)
+
+testit "lookup the PDC" \
+ test_lookup pdc ||
+ failed=$(expr $failed + 1)
+
+testit "lookup the master browser" \
+ test_lookup master ||
+ failed=$(expr $failed + 1)
+
+# This test attempts to lookup shares
+testit "lookup share list" \
+ test_share list ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_registry.sh b/source3/script/tests/test_net_registry.sh
new file mode 100755
index 0000000..155533e
--- /dev/null
+++ b/source3/script/tests/test_net_registry.sh
@@ -0,0 +1,411 @@
+#!/bin/sh
+#
+# Blackbox tests for the "net registry" and "net rpc registry" commands.
+#
+# Copyright (C) 2010-2011 Michael Adam <obnox@samba.org>
+# Copyright (C) 2010 Gregor Beck <gbeck@sernet.de>
+#
+# rpc tests are chose by specifying "rpc" as commandline parameter.
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_net_registry.sh SCRIPTDIR SERVERCONFFILE NET CONFIGURATION RPC
+EOF
+ exit 1
+fi
+
+SCRIPTDIR="$1"
+SERVERCONFFILE="$2"
+NET="$3"
+CONFIGURATION="$4"
+RPC="$5"
+
+NET="$VALGRIND ${NET:-$BINDIR/net} $CONFIGURATION"
+
+if test "x${RPC}" = "xrpc"; then
+ NETREG="${NET} -U${USERNAME}%${PASSWORD} -I ${SERVER_IP} rpc registry"
+else
+ NETREG="${NET} registry"
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_enumerate()
+{
+ KEY="$1"
+
+ ${NETREG} enumerate ${KEY}
+}
+
+test_getsd()
+{
+ KEY="$1"
+
+ ${NETREG} getsd ${KEY}
+}
+
+test_enumerate_nonexisting()
+{
+ KEY="$1"
+ ${NETREG} enumerate ${KEY}
+
+ if test "x$?" = "x0"; then
+ echo "ERROR: enumerate succeeded with key '${KEY}'"
+ false
+ else
+ true
+ fi
+}
+
+test_enumerate_no_key()
+{
+ ${NETREG} enumerate
+ if test "x$?" = "x0"; then
+ echo "ERROR: enumerate succeeded without any key specified"
+ false
+ else
+ true
+ fi
+}
+
+test_create_existing()
+{
+ KEY="HKLM"
+ EXPECTED="createkey opened existing ${KEY}"
+
+ OUTPUT=$(${NETREG} createkey ${KEY})
+ if test "x$?" = "x0"; then
+ if test "$OUTPUT" = "$EXPECTED"; then
+ true
+ else
+ echo "got '$OUTPUT', expected '$EXPECTED'"
+ false
+ fi
+ else
+ printf "%s\n" "$OUTPUT"
+ false
+ fi
+}
+
+test_createkey()
+{
+ KEY="$1"
+ BASEKEY=$(dirname $KEY)
+ SUBKEY=$(basename $KEY)
+
+ OUTPUT=$(${NETREG} createkey ${KEY})
+ if test "x$?" != "x0"; then
+ echo "ERROR: createkey ${KEY} failed"
+ echo "output:"
+ printf "%s\n" "$OUTPUT"
+ false
+ return
+ fi
+
+ # check enumerate of basekey lists new key:
+ OUTPUT=$(${NETREG} enumerate ${BASEKEY})
+ if test "x$?" != "x0"; then
+ echo "ERROR: failed to enumerate key '${BASEKEY}'"
+ echo "output:"
+ printf "%s\n" "$OUTPUT"
+ false
+ return
+ fi
+
+ EXPECTED="Keyname = ${SUBKEY}"
+ printf "%s\n" "$OUTPUT" | grep '^Keyname' | grep ${SUBKEY}
+ if test "x$?" != "x0"; then
+ echo "ERROR: did not find expected '$EXPECTED' in output"
+ echo "output:"
+ printf "%s\n" "$OUTPUT"
+ false
+ fi
+
+ # check enumerate of new key works:
+ ${NETREG} enumerate ${KEY}
+}
+
+test_deletekey()
+{
+ KEY="$1"
+ BASEKEY=$(dirname ${KEY})
+ SUBKEY=$(basename ${KEY})
+
+ OUTPUT=$(test_createkey "${KEY}")
+ if test "x$?" != "x0"; then
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ OUTPUT=$(${NETREG} deletekey ${KEY})
+ if test "x$?" != "x0"; then
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ # check enumerate of basekey does not show key anymore:
+ OUTPUT=$(${NETREG} enumerate ${BASEKEY})
+ if test "x$?" != "x0"; then
+ printf "%s\n" "$OUTPUT"
+ false
+ return
+ fi
+
+ UNEXPECTED="Keyname = ${SUBKEY}"
+ printf "%s\n" "$OUTPUT" | grep '^Keyname' | grep ${SUBKEY}
+ if test "x$?" = "x0"; then
+ echo "ERROR: found '$UNEXPECTED' after delete in output"
+ echo "output:"
+ printf "%s\n" "$OUTPUT"
+ false
+ fi
+
+ # check enumerate of key itself does not work anymore:
+ ${NETREG} enumerate ${KEY}
+ if test "x$?" = "x0"; then
+ echo "ERROR: 'enumerate ${KEY}' works after 'deletekey ${KEY}'"
+ false
+ else
+ true
+ fi
+}
+
+test_deletekey_nonexisting()
+{
+ KEY="$1"
+
+ OUTPUT=$(test_deletekey "${KEY}")
+ if test "x$?" != "x0"; then
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ ${NETREG} deletekey "${KEY}"
+ if test "x$?" = "x0"; then
+ echo "ERROR: delete after delete succeeded for key '${KEY}'"
+ false
+ fi
+}
+
+test_createkey_with_subkey()
+{
+ KEY="$1"
+ KEY2=$(dirname ${KEY})
+ SUBKEYNAME2=$(basename ${KEY})
+ BASENAME=$(dirname ${KEY2})
+ SUBKEYNAME1=$(basename ${KEY2})
+
+ OUTPUT=$(${NETREG} createkey ${KEY})
+ if test "x$?" != "x0"; then
+ echo "ERROR: createkey ${KEY} failed"
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ # check we can enumerate to level key
+ OUTPUT=$(${NETREG} enumerate ${KEY})
+ if test "x$?" != "x0"; then
+ echo "ERROR: failed to enumerate '${KEY}' after creation"
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ # clear:
+ ${NETREG} deletekey ${KEY} && ${NETREG} deletekey ${KEY2}
+}
+
+test_deletekey_with_subkey()
+{
+ KEY="$1"
+ KEY2=$(dirname ${KEY})
+
+ OUTPUT=$(${NETREG} createkey ${KEY})
+ if test "x$?" != "x0"; then
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ OUTPUT=$(${NETREG} deletekey ${KEY2})
+
+ if test "x$?" = "x0"; then
+ echo "ERROR: delete of key with subkey succeeded"
+ echo "output:"
+ printf "%s\n" "$OUTPUT"
+ false
+ return
+ fi
+
+ ${NETREG} deletekey ${KEY} && ${NETREG} deletekey ${KEY2}
+}
+
+test_setvalue()
+{
+ KEY="$1"
+ VALNAME="${2#_}"
+ VALTYPE="$3"
+ VALVALUE="$4"
+
+ OUTPUT=$(test_createkey ${KEY})
+ if test "x$?" != "x0"; then
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ OUTPUT=$(${NETREG} setvalue ${KEY} "${VALNAME}" ${VALTYPE} ${VALVALUE})
+ if test "x$?" != "x0"; then
+ echo "ERROR: failed to set value testval in key ${KEY}"
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ OUTPUT=$(${NETREG} getvalueraw ${KEY} "${VALNAME}")
+ if test "x$?" != "x0"; then
+ echo "ERROR: failure calling getvalueraw for key ${KEY}"
+ echo output:
+ printf "%s\n" "${OUTPUT}"
+ false
+ return
+ fi
+
+ if test "x${OUTPUT}" != "x${VALVALUE}"; then
+ echo "ERROR: failure retrieving value ${VALNAME} for key ${KEY}"
+ printf "expected: %s\ngot: %s\n" "${VALVALUE}" "${OUTPUT}"
+ false
+ return
+ fi
+
+}
+
+test_deletevalue()
+{
+ KEY="$1"
+ VALNAME="${2#_}"
+
+ ${NETREG} deletevalue ${KEY} "${VALNAME}"
+}
+
+test_deletevalue_nonexisting()
+{
+ KEY="$1"
+ VALNAME="${2#_}"
+
+ ${NETREG} deletevalue ${KEY} "${VALNAME}"
+ if test "x$?" = "x0"; then
+ echo "ERROR: succeeded deleting value ${VALNAME}"
+ false
+ else
+ true
+ fi
+}
+
+test_setvalue_twice()
+{
+ KEY="$1"
+ VALNAME="${2#_}"
+ VALTYPE1="$3"
+ VALVALUE1="$4"
+ VALTYPE2="$5"
+ VALVALUE2="$6"
+
+ OUTPUT=$(test_setvalue ${KEY} _"${VALNAME}" ${VALTYPE1} ${VALVALUE1})
+ if test "x$?" != "x0"; then
+ echo "ERROR: first setvalue call failed"
+ printf "%s\n" "$OUTPUT"
+ false
+ return
+ fi
+
+ ${NETREG} setvalue ${KEY} "${VALNAME}" ${VALTYPE2} ${VALVALUE2}
+}
+
+testit "enumerate HKLM" \
+ test_enumerate HKLM ||
+ failed=$(expr $failed + 1)
+
+testit "enumerate nonexisting hive" \
+ test_enumerate_nonexisting XYZ ||
+ failed=$(expr $failed + 1)
+
+testit "enumerate without key" \
+ test_enumerate_no_key ||
+ failed=$(expr $failed + 1)
+
+# skip getsd test for registry currently: it fails
+if test "x${RPC}" != "xrpc"; then
+ testit "getsd HKLM" \
+ test_getsd HKLM ||
+ failed=$(expr $failed + 1)
+fi
+
+testit "create existing HKLM" \
+ test_create_existing ||
+ failed=$(expr $failed + 1)
+
+testit "create key" \
+ test_createkey HKLM/testkey ||
+ failed=$(expr $failed + 1)
+
+testit "delete key" \
+ test_deletekey HKLM/testkey ||
+ failed=$(expr $failed + 1)
+
+testit "delete^2 key" \
+ test_deletekey_nonexisting HKLM/testkey ||
+ failed=$(expr $failed + 1)
+
+testit "enumerate nonexisting key" \
+ test_enumerate_nonexisting HKLM/testkey ||
+ failed=$(expr $failed + 1)
+
+testit "create key with subkey" \
+ test_createkey_with_subkey HKLM/testkey/subkey ||
+ failed=$(expr $failed + 1)
+
+testit "delete key with subkey" \
+ test_deletekey_with_subkey HKLM/testkey/subkey ||
+ failed=$(expr $failed + 1)
+
+testit "set value" \
+ test_setvalue HKLM/testkey _testval sz moin ||
+ failed=$(expr $failed + 1)
+
+testit "delete value" \
+ test_deletevalue HKLM/testkey _testval ||
+ failed=$(expr $failed + 1)
+
+testit "delete nonexisting value" \
+ test_deletevalue_nonexisting HKLM/testkey _testval ||
+ failed=$(expr $failed + 1)
+
+testit "set value to different type" \
+ test_setvalue_twice HKLM/testkey testval sz moin dword 42 ||
+ failed=$(expr $failed + 1)
+
+testit "set default value" \
+ test_setvalue HKLM/testkey _"" sz 42 ||
+ failed=$(expr $failed + 1)
+
+testit "delete default value" \
+ test_deletevalue HKLM/testkey _"" ||
+ failed=$(expr $failed + 1)
+
+testit "delete nonexisting default value" \
+ test_deletevalue_nonexisting HKLM/testkey _"" ||
+ failed=$(expr $failed + 1)
+
+testit "delete key with value" \
+ test_deletekey HKLM/testkey ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_registry_check.sh b/source3/script/tests/test_net_registry_check.sh
new file mode 100755
index 0000000..cb0ca17
--- /dev/null
+++ b/source3/script/tests/test_net_registry_check.sh
@@ -0,0 +1,145 @@
+#!/bin/sh
+#
+# Blackbox tests for the "net registry check" command.
+#
+# Copyright (C) 2011 Björn Baumbach <bb@sernet.de>
+
+if [ $# -lt 5 ]; then
+ echo "Usage: test_net_registry_check.sh SCRIPTDIR SERVERCONFFILE NET CONFIGURATION DBWRAP_TOOL"
+ exit 1
+fi
+
+SCRIPTDIR="$1"
+SERVERCONFFILE="$2"
+NET="$3"
+CONFIGURATION="$4"
+DBWRAP_TOOL="$5 --persistent"
+
+NET="$VALGRIND ${NET:-$BINDIR/net} $CONFIGURATION"
+
+NETREG="${NET} registry"
+REGORIG="$(grep 'state directory = ' $SERVERCONFFILE | sed 's/[^=]*=//')/registry.tdb"
+REG=$REGORIG.wip
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# run registry check and filter allowed errors
+regcheck()
+{
+ ALLOWEDERR="Check database:|INFO: version ="
+ ERRSTR=$(${NETREG} check $REG "$@" 2>&1 | egrep -v "$ALLOWEDERR")
+}
+
+# try to repair registry
+regrepair()
+{
+ regcheck -a
+}
+
+# check if $ERRSTR contains expected error
+checkerr()
+{
+ EXPERR=$1
+
+ ERRCNT=$(echo "$ERRSTR" | grep "$EXPERR" | wc -l)
+ return $ERRCNT
+}
+
+regchecknrepair()
+{
+ EXPERR="$1"
+ EXPERRCNT="$2"
+
+ regcheck
+ checkerr "$EXPERR"
+ test "$?" -eq "$ERRCNT" || {
+ echo "Expected $EXPERRCNT of error $EXPERR. Received $ERRCNT"
+ return 1
+ }
+
+ regrepair
+ regcheck
+ test "x$ERRSTR" = "x" || {
+ echo "Error: Can't repair database"
+ return 1
+ }
+}
+
+test_simple()
+(
+ ERRSTR=""
+ cp $REGORIG $REG
+
+ regcheck
+ test "x$ERRSTR" = "x" || {
+ echo $ERRSTR
+ return 1
+ }
+)
+
+test_damage()
+{
+ diff $REGORIG $REG
+}
+
+test_duplicate()
+(
+ ERRSTR=""
+ $DBWRAP_TOOL $REG store 'HKLM/SOFTWARE' hex '02000000534F4654574152450053595354454D00'
+
+ regchecknrepair "Duplicate subkeylist" 1
+)
+
+test_slashes()
+(
+ ERRSTR=""
+ $DBWRAP_TOOL $REG store 'HKLM/SOFTWARE' hex '02000000534F4654574152450053595354454D00'
+
+ regchecknrepair "Unnormal key:" 1
+)
+
+test_uppercase()
+(
+ ERRSTR=""
+ $DBWRAP_TOOL $REG store 'HKLM\Software' hex '02000000534F4654574152450053595354454D00'
+
+ regchecknrepair "Unnormal key:" 1
+)
+
+test_strangeletters()
+(
+ ERRSTR=""
+ $DBWRAP_TOOL $REG store 'HKLM\SOFTWARE' hex '02000000534F4654574FABFABFABFAB354454D00'
+
+ regchecknrepair "Conversion error: Incomplete multibyte sequence" 1
+)
+
+testit "simple" \
+ test_simple ||
+ failed=$(expr $failed + 1)
+
+testit "damages_registry" \
+ test_damage ||
+ failed=$(expr $failed + 1)
+
+testit "duplicate" \
+ test_duplicate ||
+ failed=$(expr $failed + 1)
+
+testit "slashes" \
+ test_slashes ||
+ failed=$(expr $failed + 1)
+
+testit "uppercase" \
+ test_uppercase ||
+ failed=$(expr $failed + 1)
+
+#Can't repair this atm
+#testit "strangeletters" \
+# test_strangeletters || \
+# failed=`expr $failed + 1`
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_registry_import.sh b/source3/script/tests/test_net_registry_import.sh
new file mode 100755
index 0000000..cca566f
--- /dev/null
+++ b/source3/script/tests/test_net_registry_import.sh
@@ -0,0 +1,192 @@
+#!/bin/sh
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_net_registry_import.sh SERVER LOCAL_PATH USERNAME PASSWORD
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+LOCAL_PATH="$2"
+USERNAME="$3"
+PASSWORD="$4"
+shift 4
+ADDARGS="$@"
+
+failed=0
+
+samba_net="$BINDIR/net"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+test_net_registry_import()
+{
+
+ #
+ # Expect:
+ # Found Byte Order Mark for : UTF-16LE
+ #
+ cmd='$VALGRIND $samba_net rpc registry import $LOCAL_PATH/case3b45ccc3b.dat -S$SERVER -U$USERNAME%$PASSWORD $ADDARGS'
+
+ eval echo "$cmd"
+ out=$(eval $cmd 2>&1)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "command failed with output $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'Found Byte Order Mark for : UTF-16LE'
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "$samba_net rpc registry import $LOCAL_PATH/case3b45ccc3b.dat failed - should get 'Found Byte Order Mark for : UTF-16LE'"
+ false
+ return
+ fi
+
+ #
+ # Expect:
+ # reg_parse_fd: smb_iconv error in file at line 0: <bf><77><d4><41>
+ #
+ cmd='$VALGRIND $samba_net rpc registry import $LOCAL_PATH/casecbe8c2427.dat -S$SERVER -U$USERNAME%$PASSWORD $ADDARGS'
+
+ eval echo "$cmd"
+ out=$(eval $cmd 2>&1)
+ ret=$?
+
+ if [ $? != 0 ]; then
+ echo "$out"
+ echo "command failed with output $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'reg_parse_fd: smb_iconv error in file at line 0: <bf><77><d4><41>'
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "$samba_net rpc registry import $LOCAL_PATH/case3b45ccc3b.dat failed - should get 'reg_parse_fd: smb_iconv error in file at line 0: <bf><77><d4><41>'"
+ false
+ return
+ fi
+
+ #
+ # For test3.dat, the parse of the first part of the file is successful,
+ # but fails on upload as we're writing to an unwriteable registry.
+ # Expect:
+ # setval ProductType failed: WERR_REGISTRY_IO_FAILED
+ # reg_parse_fd: reg_parse_line line 21 fail -2
+ # This counts as a success test as the file is parsed, but
+ # the upload failed.
+ #
+ cmd='$VALGRIND $samba_net rpc registry import $LOCAL_PATH/regtest3.dat -S$SERVER -U$USERNAME%$PASSWORD $ADDARGS'
+
+ eval echo "$cmd"
+ out=$(eval $cmd 2>&1)
+ ret=$?
+
+ if [ $? != 0 ]; then
+ echo "$out"
+ echo "command failed with output $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'setval ProductType failed: WERR_REGISTRY_IO_FAILED'
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "$samba_net rpc registry import $LOCAL_PATH/regtest3.dat failed - should get 'setval ProductType failed: WERR_REGISTRY_IO_FAILED'"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'reg_parse_fd: reg_parse_line 20 fail -2'
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "$samba_net rpc registry import $LOCAL_PATH/regtest3.dat failed - should get 'reg_parse_fd: reg_parse_line 20 fail -2'"
+ false
+ return
+ fi
+
+ true
+ return
+}
+
+###########################################################
+# Check net rpc registry import doesn't crash
+###########################################################
+
+rm -f $LOCAL_PATH/case3b45ccc3b.dat
+rm -f $LOCAL_PATH/casecbe8c2427.dat
+rm -f $LOCAL_PATH/regtest3.dat
+
+# Create test cases
+
+base64 -d <<'EOF' | gunzip -c >$LOCAL_PATH/case3b45ccc3b.dat
+H4sIAODLjlwCA/v/L5whkyGPIYUhn6GcoZhBgSGIIZUhHShWzFDCUMRQCRRxBcpmAnn5QL4CQxhQ
+vggomwnk5wH5pgx6DAZAyMvABcbRDB4M3kA9kQzxDD4M/gzODI5AOp7BF0g7A+U8GfyAsjEMwUAV
+wQwhQLYvkOfMUPqRUvDw4yAHnz6OgsELgGlYh8EYCHXA6RnENmLIgbJNGZLh4jEMvEWRee8eXl8u
+//f8N9vK5cVVXP9v2rB+/qYw+3xko5Su8jSiLZ0zwJ4GAO4s/cYABAAA
+EOF
+
+base64 -d <<'EOF' | gunzip -c >$LOCAL_PATH/casecbe8c2427.dat
+H4sIALjPjlwCA2NwZfBliGFwZihlKALCVIY8hhIgLx9MFwHpHIZgoGgJUA2ILmIoY8hkSAayioEi
+OQyJQHW5YLIcqLaIIRsoXgLklwBVgcyIYSgA8oqAOBdsCsiEYoZYBl4GLgYlsG2JDElAc1KB6kCm
+ZYLtTWWoAJIgncVACDE5BajeFkjCeFYMBijQEIuZxUCcDPZZJkNJ3f7yK45/V3S8epR2I14uf+4W
+ee+dz0RXshv4SHxzff2XJYbx0pWaEs+ul5XKF9hlFIu4RG73Lf3rOXHW3NxpuvVnE9Xk7zxv2p3I
+tlLtWjY/i1HIGhdpLy/Gub9nH5jLd/rqdYfv2uumzgq7PIldPY3Labru/65Q/nLJh1oBk/0tT2v2
+eUdbzFg0NfPmamFH421aJxMPhnr7X+y0iRdSX+ex+IJ0Yaf0ahV5440Wj7cbK/jkbSjcNdvpR+WN
+/5Knnn8PjvvD9O/Ws4pXUqG3lbdFrf1846zzcTOFW8yhB3QNZRP6TjOsu1rDvIaHZVfMyYd1Mhev
+ik/a5m36Y85+y63pPmtXb8nOU5Zd0qK0yVJK8a27WqKHSOKaS7wpwULu1TsM94bVGD3xviR0u1Il
+rFHoxeUrm2+6Ke4x2SGitD912ZGfLcmG0xiyIn+bmx0+s+dbXuT8xfl+CgL168yNzYxCgsviz/46
+b7746Wnh8zXZHDof6/yDyxdf31JkzN5YVP4kf/vkvrS1ioauYemc3RIt7znZQvpOy7XO8VU5+KeP
+VXKPXrzr+nMv/v5wkpA7v2TukgqHZ4e6i+Zsjfny6vHdg7+mLFjg/th4m55ppH75HYcLjEa/U4/w
+SeXMTuVXablo/fmJnlPA6T12usz8nBGVKbVzTNqrTJ6d/+Y0y2bGc5MlzgnymUVq/9/PyZ2QxZvR
+4WyR810zd32X5ncJRd/y7VNCd746G/jTTFLTJfHx86dVtlkL02zeCJeYsmkdrXVhtpl7Y5OOyJcD
+DJXA9JPJkA5MT8YMOuA0psNgBExRMLYZgwkSOxnM1kdiG6CQkNTpD0zXGeBc4AJMx7nQFF8MTttA
+8f8VDBoM5gya4NRNtgN0zczNjM1MDCwMLcwMTCwtLYxNjLE4wK5pwpebAAJ05DUABAAA
+EOF
+
+base64 -d <<'EOF' >$LOCAL_PATH/regtest3.dat
+UkVHRURJVDQKCltIS0VZX0xPQ0FMX01BQ0hJTkVdCgpbSEtFWV9MT0NBTF9NQUNISU5FXFNPRlRX
+QVJFXQoKW0hLRVlfTE9DQUxfTUFDSElORVxTT0ZUV0FSRVxNaWNyb3NvZnRdCgpbSEtFWV9MT0NB
+TF9NQUNISU5FXFNPRlRXQVJFXE1pY3Jvc29mdFxXaW5kb3dzIE5UXQoKW0hLRVlfTE9DQUxfTUFD
+SElORVxTT0ZUV0FSRVxNaWNyb3NvZnRcV2luZG93cyBOVFxDdXJyZW50VmVyc2lvbl0KIkN1cnJl
+bnRWZXJzaW9uIj0iNi4xIgoKW0hLRVlfTE9DQUxfTUFDSElORVxTWVNURU1dCgpbSEtFWV9MT0NB
+TF9NQUNISU5FXFNZU1RFTVxDdXJyZW50Q29udHJvbFNldF0KCltIS0VZX0xPQ0FMX01BQ0hJTkVc
+U1lTVEVNXEN1cnJlbnRDb250cm9sU2V0XENvbnRyb2xdCgpbSEtFWV9MT0NBTF9NQUNISU5FXFNZ
+U1RFTVxDdXJyZW50Q29udHJvbFNldFxDb250cm9sXFByb2R1Y3RPcHRpb25zXQoiUHJvZHVjdFR5
+cGUiPSJMYW5tYW5OVCIKCltIS0VZX0xPQ0FMX01BQ0hJTkVcU1lTVEVNXEN1cnJlbnRDb250cm9s
+U2V0XENvbnRyb2xcUHJpbnRdCgpbSEtFWV9MT0NBTF9NQUNISU5FXFNZU1RFTVxDdXJyZW50Q29u
+dHJvbFNldFxDb250cm9sXFRlcm1pbmFsIFNlcnZlcl0KCltIS0VZX0xPQ0FMX01BQ0hJTkVcU1lT
+VEVNXQoKW0hLRVlfTE9DQUxfTUFDSElORVxTWVNURU1cQ3VycmVudENvbnRyb2xTZXRdCgpbSEtF
+WV9MT0NBTF9NQUNISU5FXFNZU1RFTVxDdXJyZW50Q29udHJvbFNldFxTZXJ2aWNlc10KCltIS0VZ
+X0xPQ0FMX01BQ0hJTkVcU1lTVEVNXEN1cnJlbnRDb250cm9sU2V0XFNlcnZpY2VzXE5ldGxvZ29u
+XQoKW0hLRVlfTE9DQUxfTUFDSElORVxTWVNURU1cQ3VycmVudENvbnRyb2xTZXRcU2VydmljZXNc
+TmV0bG9nb25cUGFyYW1ldGVyc10KIlJlZnVzZVBhc3N3b3JkQ2hhbmdlIj1kd29yZDowMDAwMDAw
+MAoKW0hLRVlfTE9DQUxfTUFDSElORVxTWVNURU1cQ3VycmVudENvbnRyb2xTZXRcU2VydmljZXNc
+QWxlcnRlcl0KCltIS0VZX0xPQ0FMX01BQ0hJTkVcU1lTVEVNXEN1cnJlbnRDb250cm9sU2V0XA==
+EOF
+
+testit "Test net rpc registry import" \
+ test_net_registry_import ||
+ failed=$(expr $failed + 1)
+
+# Clean up test cases.
+rm -f $LOCAL_PATH/case3b45ccc3b.dat
+rm -f $LOCAL_PATH/casecbe8c2427.dat
+rm -f $LOCAL_PATH/regtest3.dat
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_registry_roundtrip.sh b/source3/script/tests/test_net_registry_roundtrip.sh
new file mode 100755
index 0000000..e1974c2
--- /dev/null
+++ b/source3/script/tests/test_net_registry_roundtrip.sh
@@ -0,0 +1,158 @@
+#!/bin/sh
+#
+# Blackbox test for net conf/registry roundtrips.
+#
+# Copyright (C) 2010 Gregor Beck <gbeck@sernet.de>
+# Copyright (C) 2011 Michael Adam <obnox@samba.org>
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_net_registry_roundtrip.sh SCRIPTDIR SERVERCONFFILE NET CONFIGURATION RPC
+EOF
+ exit 1
+fi
+
+SCRIPTDIR="$1"
+SERVERCONFFILE="$2"
+NET="$3"
+CONFIGURATION="$4"
+RPC="$5"
+
+NET="$VALGRIND ${NET} $CONFIGURATION"
+
+if test "x${RPC}" = "xrpc"; then
+ NETCMD="${NET} -U${USERNAME}%${PASSWORD} -I ${SERVER_IP} rpc"
+else
+ NETCMD="${NET}"
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+#
+# List of parameters to skip when importing configuration files:
+# They are forbidden in the registry and would lead import to fail.
+#
+SED_INVALID_PARAMS="{
+s/state directory/;&/g
+s/lock directory/;&/g
+s/lock dir/;&/g
+s/config backend/;&/g
+s/include/;&/g
+}"
+
+REGPATH="HKLM\Software\Samba"
+
+conf_roundtrip_step()
+{
+ echo "CMD: $*" >>$LOG
+ "$@" 2>>$LOG
+ RC=$?
+ echo "RC: $RC" >>$LOG
+ test "x$RC" = "x0" || {
+ echo "ERROR: $* failed (RC=$RC)" | tee -a $LOG
+ }
+ return $RC
+ # echo -n .
+}
+
+LOGDIR_PREFIX="conf_roundtrip"
+
+conf_roundtrip()
+(
+ DIR=$(mktemp -d ${PREFIX}/${LOGDIR_PREFIX}_XXXXXX)
+ LOG=$DIR/log
+
+ echo conf_roundtrip $1 >$LOG
+
+ sed -e "$SED_INVALID_PARAMS" $1 >$DIR/conf_in
+
+ conf_roundtrip_step $NETCMD conf drop
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ test -z "$($NETCMD conf list)" 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: conf drop failed" | tee -a $LOG
+ return 1
+ fi
+
+ conf_roundtrip_step $NETCMD conf import $DIR/conf_in
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ conf_roundtrip_step $NETCMD conf list >$DIR/conf_exp
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ grep "\[global\]" $DIR/conf_exp >/dev/null 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: conf import => conf export failed" | tee -a $LOG
+ return 1
+ fi
+
+ conf_roundtrip_step $NETCMD -d10 registry export $REGPATH $DIR/conf_exp.reg
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ conf_roundtrip_step $NETCMD conf drop
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ test -z "$($NETCMD conf list)" 2>>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: conf drop failed" | tee -a $LOG
+ return 1
+ fi
+
+ conf_roundtrip_step $NETCMD registry import $DIR/conf_exp.reg
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ conf_roundtrip_step $NETCMD conf list >$DIR/conf_out
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ diff -q $DIR/conf_out $DIR/conf_exp >>$LOG
+ if [ "$?" = "1" ]; then
+ echo "ERROR: registry import => conf export failed" | tee -a $LOG
+ return 1
+ fi
+
+ conf_roundtrip_step $NETCMD registry export $REGPATH $DIR/conf_out.reg
+ test "x$?" = "x0" || {
+ return 1
+ }
+
+ diff -q $DIR/conf_out.reg $DIR/conf_exp.reg >>$LOG
+ if [ "$?" = "1" ]; then
+ echo "Error: registry import => registry export failed" | tee -a $LOG
+ return 1
+ fi
+ rm -r $DIR
+)
+
+CONF_FILES=$SERVERCONFFILE
+
+# remove old logs:
+for OLDDIR in $(find ${PREFIX} -type d -name "${LOGDIR_PREFIX}_*"); do
+ echo "removing old directory ${OLDDIR}"
+ rm -rf ${OLDDIR}
+done
+
+for conf_file in $CONF_FILES; do
+ testit "conf_roundtrip $conf_file" \
+ conf_roundtrip $conf_file ||
+ failed=$(expr $failed + 1)
+done
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_rpc_join.sh b/source3/script/tests/test_net_rpc_join.sh
new file mode 100755
index 0000000..319d044
--- /dev/null
+++ b/source3/script/tests/test_net_rpc_join.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_net_rpc_join.sh USERNAME PASSWORD SERVER PREFIX
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+SERVER="$3"
+PREFIX="$4"
+shift 4
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+mkdir -p $PREFIX/private
+testit "net_rpc_join" $VALGRIND $BINDIR/net rpc join -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private -U$USERNAME%$PASSWORD $ADDARGS || failed=$(expr $failed + 1)
+testit "net_rpc_testjoin" $VALGRIND $BINDIR/net rpc testjoin -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private $ADDARGS || failed=$(expr $failed + 1)
+testit "net_rpc_changetrustpw" $VALGRIND $BINDIR/net rpc changetrustpw -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private $ADDARGS || failed=$(expr $failed + 1)
+testit "net_rpc_testjoin2" $VALGRIND $BINDIR/net rpc testjoin -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private $ADDARGS || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_rpc_join_creds.sh b/source3/script/tests/test_net_rpc_join_creds.sh
new file mode 100755
index 0000000..24376ef
--- /dev/null
+++ b/source3/script/tests/test_net_rpc_join_creds.sh
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_net_rpc_join_creds.sh DOMAIN USERNAME PASSWORD SERVER PREFIX
+EOF
+ exit 1
+fi
+
+DOMAIN="$1"
+USERNAME="$2"
+PASSWORD="$3"
+SERVER="$4"
+PREFIX="$5"
+shift 5
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+mkdir -p $PREFIX/private
+# Test using a credentials file.
+credsfile=$PREFIX/creds.$$
+printf '%s\n' "username=$USERNAME" "password=$PASSWORD" "domain=$DOMAIN" >"$credsfile"
+testit "net_rpc_join_creds" $VALGRIND $BINDIR/net rpc join -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private -A"$credsfile" $ADDARGS || failed=$(expr $failed + 1)
+testit "net_rpc_testjoin_creds" $VALGRIND $BINDIR/net rpc testjoin -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private $ADDARGS || failed=$(expr $failed + 1)
+testit "net_rpc_changetrustpw_creds" $VALGRIND $BINDIR/net rpc changetrustpw -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private $ADDARGS || failed=$(expr $failed + 1)
+testit "net_rpc_testjoin2_creds" $VALGRIND $BINDIR/net rpc testjoin -S $SERVER --option=netbiosname=netrpcjointest --option=domainlogons=yes --option=privatedir=$PREFIX/private $ADDARGS || failed=$(expr $failed + 1)
+rm -f $credsfile
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_rpc_oldjoin.sh b/source3/script/tests/test_net_rpc_oldjoin.sh
new file mode 100755
index 0000000..d650ead
--- /dev/null
+++ b/source3/script/tests/test_net_rpc_oldjoin.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_net_rpc_oldjoin.sh SERVER PREFIX SMB_CONF_PATH
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+PREFIX="$2"
+SMB_CONF_PATH="$3"
+shift 3
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+maccount="OLDJOINTEST"
+privatedir="$PREFIX/private"
+
+OPTIONS="--configfile $SMB_CONF_PATH --option=netbiosname=$maccount --option=security=domain --option=domainlogons=no --option=privatedir=$privatedir"
+
+test_smbpasswd()
+{
+ account=$1
+
+ echo "set password with smbpasswd"
+
+ cmd='UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $VALGRIND $BINDIR/smbpasswd -L -c $SMB_CONF_PATH -a -m "$account"'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "Failed to change user password $user"
+ return 1
+ fi
+}
+
+testit "mkdir -p $privatedir" mkdir -p $privatedir || failed=$(expr $failed + 1)
+testit "smbpasswd -a -m" \
+ test_smbpasswd $maccount ||
+ failed=$(expr $failed + 1)
+testit "net_rpc_oldjoin" $VALGRIND $BINDIR/net rpc oldjoin -S $SERVER $OPTIONS || failed=$(expr $failed + 1)
+testit "net_rpc_testjoin1" $VALGRIND $BINDIR/net rpc testjoin -S $SERVER $OPTIONS || failed=$(expr $failed + 1)
+testit "net_rpc_changetrustpw" $VALGRIND $BINDIR/net rpc changetrustpw -S $SERVER $OPTIONS || failed=$(expr $failed + 1)
+testit "net_rpc_testjoin2" $VALGRIND $BINDIR/net rpc testjoin -S $SERVER $OPTIONS || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_rpc_share_allowedusers.sh b/source3/script/tests/test_net_rpc_share_allowedusers.sh
new file mode 100755
index 0000000..3365813
--- /dev/null
+++ b/source3/script/tests/test_net_rpc_share_allowedusers.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_net_rpc_share_allowedusers.sh SERVER USERNAME PASSWORD PREFIX
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+USERNAME="$2"
+PASSWORD="$3"
+PREFIX="$4"
+shift 4
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+mkdir -p $PREFIX/private
+net=$BINDIR/net
+# Check for the SID for group "Everyone" as a basic test things are working.
+testit_grep "net_usersidlist" '^ S-1-1-0$' $VALGRIND $net usersidlist $ADDARGS || failed=$(expr $failed + 1)
+# Check "print$" share is listed by default.
+testit_grep "net_rpc_share_allowedusers" '^print\$$' $net usersidlist | $VALGRIND $net rpc share allowedusers -S$SERVER -U$USERNAME%$PASSWORD $ADDARGS || failed=$(expr $failed + 1)
+# Check "print$" share is listed if we ask for it.
+testit_grep "net_rpc_share_allowedusers" '^print\$$' $net usersidlist | $VALGRIND $net rpc share allowedusers -S$SERVER -U$USERNAME%$PASSWORD $ADDARGS - 'print$' || failed=$(expr $failed + 1)
+# Check user "user1" is allowed to read share "tmp".
+testit_grep "net_rpc_share_allowedusers" '^ user1$' $net usersidlist | $VALGRIND $net rpc share allowedusers -S$SERVER -U$USERNAME%$PASSWORD $ADDARGS || failed=$(expr $failed + 1)
+#
+# Subtle extra test for bug https://bugzilla.samba.org/show_bug.cgi?id=13992
+#
+# '^ user1$' must appear MORE THAN ONCE, as it can read more than one
+# share. The previous test found user1, but only once as the bug only
+# allows reading the security descriptor for one share, and we were
+# unlucky that the first share security descriptor returned allows
+# user1 to read from it.
+#
+subunit_start_test "net_rpc_share_allowedusers"
+multi_userout=$($net usersidlist | $VALGRIND $net rpc share allowedusers -S$SERVER -U$USERNAME%$PASSWORD $ADDARGS)
+num_matches=$(echo "$multi_userout" | grep -c '^ user1$')
+if [ "$num_matches" -gt "1" ]; then
+ subunit_pass_test "net_rpc_share_allowedusers"
+else
+ echo "net_rpc_share_allowedusers only found $num_matches shares readable by user1. Should be greater than one.\n"
+ failed=$(expr $failed + 1)
+ echo "$multi_userout" | subunit_fail_test "net_rpc_share_allowedusers"
+fi
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_tdb.sh b/source3/script/tests/test_net_tdb.sh
new file mode 100755
index 0000000..abc8786
--- /dev/null
+++ b/source3/script/tests/test_net_tdb.sh
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# Test 'net tdb' command.
+#
+# Verify that the command returns the correct information in the
+# expected format. The 'dump' option is tested, but the output is not
+# checked, since the internal data structure could change in the
+# future.
+#
+# Copyright (C) 2017 Christof Schmitt
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: $0 SMBCLIENT SERVER SHARE USER PASS CONFIGURATION LOCALPATH LOCKDIR
+EOF
+ exit 1
+fi
+
+SMBCLIENT=$1
+SERVER=$2
+SHARE=$3
+USER=$4
+PASS=$5
+CONFIGURATION=$6
+LOCALPATH=$7
+LOCKDIR=$8
+
+FILENAME=net_tdb_testfile
+
+samba_tdbtool=tdbtool
+if test -x $BINDIR/tdbtool; then
+ samba_tdbtool=$BINDIR/tdbtool
+fi
+
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+touch $LOCALPATH/$FILENAME
+
+printf "open %s\n"'!sleep 10'"\n" ${FILENAME} |
+ $SMBCLIENT //$SERVER/$SHARE -U$USER%$PASS &
+SMBCLIENTPID=$!
+
+# Give smbclient a chance to open the file
+sleep 1
+
+testit "Looking for record key of open file" \
+ $samba_tdbtool $LOCKDIR/locking.tdb hexkeys ||
+ failed=$(expr $failed + 1)
+
+# The assumption here is that only one file is open, so only one
+# record can exist in the database.
+
+# Output of 'tdbtool hexkeys' is in this format:
+#[000] 01 FD 00 00 00 00 00 00 56 02 5C 00 00 00 00 00 ....... V.\....
+#[010] 00 00 00 00 00 00 00 00 .......
+# Select only the hex data, remove space and join every thing together
+key=0x$($samba_tdbtool $LOCKDIR/locking.tdb hexkeys |
+ grep '\[' | cut -c 7-56 | sed -e 's/ //g' | tr -d '\n')
+
+testit "Looking for open file in locking.tdb" \
+ $BINDIR/net $CONFIGURATION tdb locking $key ||
+ failed=$(expr $failed + 1)
+out=$($BINDIR/net $CONFIGURATION tdb locking $key)
+
+out=$($BINDIR/net $CONFIGURATION tdb locking $key |
+ grep 'Share path: ' | sed -e 's/Share path: \+//')
+testit "Verify pathname in output" \
+ test "$out" = "$LOCALPATH" ||
+ failed=$(expr $failed + 1)
+
+out=$($BINDIR/net $CONFIGURATION tdb locking $key |
+ grep 'Name:' | sed -e 's/Name: \+//')
+testit "Verify filename in output" \
+ test "$out" = "$FILENAME" ||
+ failed=$(expr $failed + 1)
+
+out=$($BINDIR/net $CONFIGURATION tdb locking $key |
+ grep 'Number of share modes:' |
+ sed -e 's/Number of share modes: \+//')
+testit "Verify number of share modes in output" \
+ test "$out" = "1" ||
+ failed=$(expr $failed + 1)
+
+testit "Complete record dump" \
+ $BINDIR/net $CONFIGURATION tdb locking $key dump ||
+ failed=$(expr $failed + 1)
+
+$BINDIR/net $CONFIGURATION tdb locking $key dump | grep -q $FILENAME
+RC=$?
+testit "Verify filename in dump output" \
+ test $RC = 0 ||
+ failed=$(expr $failed + 1)
+$BINDIR/net $CONFIGURATION tdb locking $key dump | grep -q $LOCALPATH
+RC=$?
+testit "Verify share path in dump output" \
+ test $RC = 0 ||
+ failed=$(expr $failed + 1)
+
+kill $SMBCLIENTPID
+
+testok $0 $failed
diff --git a/source3/script/tests/test_net_usershare.sh b/source3/script/tests/test_net_usershare.sh
new file mode 100755
index 0000000..bf6334d
--- /dev/null
+++ b/source3/script/tests/test_net_usershare.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_net_usershare.sh SERVER SERVER_IP DOMAIN USERNAME PASSWORD SMBCLIENT <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+smbclient="$5"
+shift 5
+ADDARGS="$@"
+
+failed=0
+
+samba_bindir="$BINDIR"
+samba_net="$samba_bindir/net"
+samba_smbcontrol="$samba_bindir/smbcontrol"
+
+samba_share_dir="$LOCAL_PATH"
+samba_usershare_dir="$samba_share_dir/usershares"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+test_smbclient()
+{
+ name="$1"
+ share="$2"
+ cmd="$3"
+ shift 3
+ echo "test: $name"
+ $VALGRIND $smbclient $CONFIGURATION //$SERVER/$share -c "$cmd" "$@"
+ status=$?
+ if [ x$status = x0 ]; then
+ echo "success: $name"
+ else
+ echo "failure: $name"
+ fi
+ return $status
+}
+
+test_net_usershare()
+{
+ name="$1"
+ cmd="$2"
+ shift
+ shift
+ echo "test: $name"
+ $VALGRIND $samba_net usershare "$cmd" "$@"
+ status=$?
+ if [ x$status = x0 ]; then
+ echo "success: $name"
+ else
+ echo "failure: $name"
+ fi
+ return $status
+}
+
+###########################################################
+# Check if we can add and delete a usershare
+###########################################################
+
+samba_usershare_name="test_usershare_1"
+samba_usershare_path="$samba_usershare_dir/$samba_usershare_name"
+
+testit "create usershare dir for $samba_usershare_name" mkdir --mode=0755 --verbose $samba_usershare_path || failed=$(expr $failed + 1)
+
+test_net_usershare "net usershare add $samba_usershare_name" "add" "$samba_usershare_name" "$samba_usershare_path" "$samba_usershare_name"
+
+test_net_usershare "net usershare info $samba_usershare_name" "info" "$samba_usershare_name"
+
+test_smbclient "smbclient to $samba_usershare_name" "$samba_usershare_name" 'ls' -U$USERNAME%$PASSWORD || failed=$(expr $failed + 1)
+
+# CLEANUP
+test_net_usershare "net usershare delete $samba_usershare_name" "delete" "$samba_usershare_name"
+testit "remove usershare dir for $samba_usershare_name" rm -rf $samba_usershare_path || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_netfileenum.sh b/source3/script/tests/test_netfileenum.sh
new file mode 100755
index 0000000..d343555
--- /dev/null
+++ b/source3/script/tests/test_netfileenum.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+#
+# Test rpcclient netfileenum
+#
+# Copyright (C) 2020 Volker Lendecke
+
+if [ $# -lt 5 ]; then
+ echo Usage: $0 \
+ SMBCLIENT RPCCLIENT NET SERVER SHARE
+ exit 1
+fi
+
+SMBCLIENT="$1"
+shift 1
+RPCCLIENT="$1"
+shift 1
+NET="$1"
+shift 1
+SERVER="$1"
+shift 1
+SHARE="$1"
+shift 1
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+mkfifo smbclient-stdin smbclient-stdout smbclient-stderr
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \
+ <smbclient-stdin >smbclient-stdout 2>smbclient-stderr &
+CLIENT_PID=$!
+
+sleep 1
+
+exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr
+
+# consume the smbclient startup messages
+head -n 1 <&101
+
+FILE=x64
+
+printf "open %s\\n" "$FILE" >&100
+
+sleep 1
+
+testit "Create builtin\\administrators group" \
+ "${NET}" groupmap add \
+ sid=S-1-5-32-544 unixgroup="${USER}"-group type=builtin ||
+ failed=$((failed + 1))
+testit "Add ${USER} to builtin\\administrators" \
+ "${NET}" groupmap addmem S-1-5-32-544 \
+ $("${NET}" lookup name "${USER}" | cut -d' ' -f1) ||
+ failed=$((failed + 1))
+
+"${RPCCLIENT}" "${SERVER}" -U"${USER}"%"${PASSWORD}" -c netfileenum |
+ grep "$FILE"\$
+RC=$?
+testit "netfileenum" test $RC = 0 || failed=$((failed + 1))
+
+kill ${CLIENT_PID}
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+
+testit "Remove ${USER} from builtin\\administrators" \
+ "${NET}" groupmap delmem S-1-5-32-544 \
+ $("${NET}" lookup name "${USER}" | cut -d' ' -f1) ||
+ failed=$((failed + 1))
+testit "Remove builtin\\administrators group" \
+ "${NET}" groupmap delete \
+ sid=S-1-5-32-544 ||
+ failed=$((failed + 1))
+
+testok $0 $failed
diff --git a/source3/script/tests/test_nt4_trust.sh b/source3/script/tests/test_nt4_trust.sh
new file mode 100755
index 0000000..b3d6ca6
--- /dev/null
+++ b/source3/script/tests/test_nt4_trust.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+failed=0
+
+wbinfo="$BINDIR/wbinfo"
+smbclient="$BINDIR/smbclient"
+
+test_trust_wbinfo_m() {
+ i=0
+ # Give the server some time to list trusted domains
+ while [ $i -lt 10 ] ; do
+ $wbinfo -m | grep SAMBA-TEST && return 0
+ sleep 2
+ i=$((i + 1))
+ done
+ return 1
+}
+
+test_trust_smbclient() {
+ $smbclient //$NT4_TRUST_SERVER_IP/tmp -U "$DOMAIN/$DOMAIN_USER%$DOMAIN_USER_PASSWORD" -c quit || return 1
+ return 0
+}
+
+testit "nt4trust_wbinfo_m" test_trust_wbinfo_m || failed=$(expr $failed + 1)
+testit "nt4trust_smbclient" test_trust_smbclient || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_offline.sh b/source3/script/tests/test_offline.sh
new file mode 100755
index 0000000..5e06b1f
--- /dev/null
+++ b/source3/script/tests/test_offline.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+#
+# Blackbox test for the offline VFS module.
+#
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_offline SERVER SERVER_IP DOMAIN USERNAME PASSWORD WORKDIR SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+DOMAIN=${3}
+USERNAME=${4}
+PASSWORD=${5}
+WORKDIR=${6}
+SMBCLIENT=${7}
+shift 7
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+touch $WORKDIR/foo
+
+failed=0
+
+attribs=$($SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/offline" -I $SERVER_IP -c "allinfo foo" | sed -n 's/^attributes:.*(\([^)]*\)).*/\1/p')
+testit "file has offline attribute" test "x$attribs" = "x1000" || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_old_dirlisting.sh b/source3/script/tests/test_old_dirlisting.sh
new file mode 100755
index 0000000..f50a474
--- /dev/null
+++ b/source3/script/tests/test_old_dirlisting.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# This tests listing directories using the SMBSearch call family
+
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: $0 TIMELIMIT SMBCLIENT
+EOF
+ exit 1
+fi
+
+TIMELIMIT="$1"
+shift
+SMBCLIENT="$VALGRIND $1"
+shift
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+# Make sure we don't loop 100% CPU. A normal dir listing should return
+# in less than 3 seconds. At the point of this commit smbclient -c dir
+# | wc returns 43 lines, so checking for 100 lines should be well
+# enough.
+
+count=$($TIMELIMIT 3 $SMBCLIENT //"$SERVER_IP"/tmpguest -m LANMAN1 -U% \
+ -c dir | wc -l)
+
+testit "listing shares with LANMAN1" test ${count} -le 100 ||
+ failed=$((failed + 1))
diff --git a/source3/script/tests/test_open_eintr.sh b/source3/script/tests/test_open_eintr.sh
new file mode 100755
index 0000000..b40b14d
--- /dev/null
+++ b/source3/script/tests/test_open_eintr.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+#
+# Test smbd handling when open returns EINTR
+#
+# Copyright (C) 2020 Volker Lendecke
+
+if [ $# -lt 5 ]; then
+ echo Usage: test_open_eintr.sh \
+ --configfile=SERVERCONFFILE SMBCLIENT SMBCONTROL SERVER SHARE
+ exit 1
+fi
+
+CONF=$1
+shift 1
+SMBCLIENT=$1
+shift 1
+SMBCONTROL=$1
+shift 1
+SERVER=$1
+shift 1
+SHARE=$1
+shift 1
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+error_inject_conf=$(dirname ${SERVERCONFFILE})/error_inject.conf
+>${error_inject_conf}
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+mkfifo smbclient-stdin smbclient-stdout smbclient-stderr
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \
+ <smbclient-stdin >smbclient-stdout 2>smbclient-stderr &
+CLIENT_PID=$!
+
+sleep 1
+
+exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr
+
+# consume the smbclient startup messages
+head -n 1 <&101
+
+echo "error_inject:openat = EINTR" >${error_inject_conf}
+${SMBCONTROL} ${CONF} 0 reload-config
+
+sleep 1
+>${error_inject_conf}
+
+echo 'get badnames/blank.txt -' >&100
+
+sleep 1
+
+>${error_inject_conf}
+${SMBCONTROL} ${CONF} 0 reload-config
+
+head -n 1 <&102 | grep 'getting file' >/dev/null
+GREP_RET=$?
+
+kill ${CLIENT_PID}
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+
+testit "Verify that we could get the file" \
+ test $GREP_RET -eq 0 || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_preserve_case.sh b/source3/script/tests/test_preserve_case.sh
new file mode 100755
index 0000000..c9ca79a
--- /dev/null
+++ b/source3/script/tests/test_preserve_case.sh
@@ -0,0 +1,86 @@
+#!/bin/sh
+#
+# Blackbox test for share with preserve case options
+#
+# https://bugzilla.samba.org/show_bug.cgi?id=10650
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_preserve_case.sh SERVER DOMAIN USERNAME PASSWORD PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=$1
+DOMAIN=$2
+USERNAME=$3
+PASSWORD=$4
+PREFIX=$5
+smbclient=$6
+if [ $# -gt 6 ]; then
+ PROTOCOL_LIST=$7
+ shift 7
+else
+ PROTOCOL_LIST="NT1 SMB2 SMB3"
+ shift 6
+fi
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+cd $SELFTEST_TMPDIR || exit 1
+
+test_smbclient()
+{
+ name="$1"
+ share="$2"
+ cmd="$3"
+ shift
+ shift
+ subunit_start_test "$name"
+ output=$($VALGRIND $smbclient //$SERVER/$share -c "$cmd" "$@" 2>&1)
+ status=$?
+ if [ x$status = x0 ]; then
+ subunit_pass_test "$name"
+ else
+ echo "$output" | subunit_fail_test "$name"
+ fi
+ return $status
+}
+
+SHARE="lowercase"
+
+for PROTOCOL in $PROTOCOL_LIST; do
+ test_smbclient "Test lowercase ls 1 ($PROTOCOL)" $SHARE "ls 1" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ test_smbclient "Test lowercase get 1 ($PROTOCOL)" $SHARE "get 1 LOCAL_1" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ rm -f LOCAL_1
+
+ test_smbclient "Test lowercase ls A ($PROTOCOL)" $SHARE "ls A" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ test_smbclient "Test lowercase get A ($PROTOCOL)" $SHARE "get A LOCAL_A" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ rm -f LOCAL_A
+
+ test_smbclient "Test lowercase ls z ($PROTOCOL)" $SHARE "ls z" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ test_smbclient "Test lowercase get z ($PROTOCOL)" $SHARE "get z LOCAL_Z" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ rm -f LOCAL_Z
+done
+
+SHARE="lowercase-30000"
+
+for PROTOCOL in $PROTOCOL_LIST; do
+ test_smbclient "Test lowercase ls 25839 ($PROTOCOL)" $SHARE "ls 25839" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+
+ test_smbclient "Test lowercase ls 1 ($PROTOCOL)" $SHARE "ls 1" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ test_smbclient "Test lowercase get 1 ($PROTOCOL)" $SHARE "get 1 LOCAL_1" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ rm -f LOCAL_1
+
+ test_smbclient "Test lowercase ls A ($PROTOCOL)" $SHARE "ls A" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ test_smbclient "Test lowercase get A ($PROTOCOL)" $SHARE "get A LOCAL_A" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ rm -f LOCAL_A
+
+ test_smbclient "Test lowercase ls z ($PROTOCOL)" $SHARE "ls z" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ test_smbclient "Test lowercase get z ($PROTOCOL)" $SHARE "get z LOCAL_Z" -U$USERNAME%$PASSWORD -m$PROTOCOL || failed=$(expr $failed + 1)
+ rm -f LOCAL_Z
+done
+
+exit $failed
diff --git a/source3/script/tests/test_printing_var_exp.sh b/source3/script/tests/test_printing_var_exp.sh
new file mode 100755
index 0000000..5729344
--- /dev/null
+++ b/source3/script/tests/test_printing_var_exp.sh
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_printing_var_exp.sh SERVER SERVER_IP DOMAIN USERNAME PASSWORD
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+DOMAIN="$3"
+USERNAME="$4"
+PASSWORD="$5"
+shift 5
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+smbclient="$BINDIR/smbclient"
+rpcclient="$BINDIR/rpcclient"
+
+test_var_expansion()
+{
+ logfile="${SELFTEST_TMPDIR}/${USER}_printing_var_exp.log"
+
+ $smbclient -U $DOMAIN/$USERNAME%$PASSWORD \
+ //$SERVER_IP/print_var_exp \
+ -c "print $SRCDIR/testdata/printing/example.ps"
+ if [ $? -ne 0 ]; then
+ rm -f "$logfile"
+ return 1
+ fi
+ cat "$logfile"
+
+ grep "Windows user: $USERNAME" "$logfile"
+ if [ $? -ne 0 ]; then
+ rm -f "$logfile"
+ return 1
+ fi
+ grep "UNIX user: $USERNAME" "$logfile"
+ if [ $? -ne 0 ]; then
+ rm -f "$logfile"
+ return 1
+ fi
+ grep "Domain: $DOMAIN" "$logfile"
+ if [ $? -ne 0 ]; then
+ rm -f "$logfile"
+ return 1
+ fi
+
+ rm -f "$logfile"
+ return 0
+}
+
+test_empty_queue()
+{
+ # Try several times until the bgqd daemon updates the print queue status
+ tries="3"
+ for i in $(seq 1 $tries); do
+ echo "Try $i"
+ JOBS=$($rpcclient ncacn_np:$SERVER_IP \
+ -U $DOMAIN/$USERNAME%$PASSWORD \
+ -c "enumjobs print_var_exp 2")
+ if [ $? -ne 0 ]; then
+ return 1
+ fi
+ if [[ -z $JOBS ]]; then
+ return 0
+ fi
+ if [[ $i -gt $tries ]]; then
+ echo "Print queue not empty after $tries seconds:"
+ echo $JOBS
+ echo "Queue must be empty before leaving this test or" \
+ "following ones may fail."
+ return 1
+ fi
+ sleep 1
+ done
+ return 0
+}
+
+testit "Test variable expansion for '%U', '%u' and '%D'" \
+ test_var_expansion ||
+ failed=$(expr $failed + 1)
+
+testit "Test queue is empty" \
+ test_empty_queue ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_pthreadpool.sh b/source3/script/tests/test_pthreadpool.sh
new file mode 100755
index 0000000..b3d24f7
--- /dev/null
+++ b/source3/script/tests/test_pthreadpool.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+if [ ! -x $BINDIR/pthreadpooltest ]; then
+ # Some machines don't have /bin/true, simulate it
+ cat >$BINDIR/pthreadpooltest <<EOF
+#!/bin/sh
+exit 0
+EOF
+ chmod +x $BINDIR/pthreadpooltest
+fi
+
+failed=0
+
+testit "pthreadpool" $VALGRIND $BINDIR/pthreadpooltest ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_recycle.sh b/source3/script/tests/test_recycle.sh
new file mode 100755
index 0000000..8c9291f
--- /dev/null
+++ b/source3/script/tests/test_recycle.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_recycle.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT ADDARGS
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 7
+ADDARGS="$*"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+
+#
+# Cleanup function.
+#
+do_cleanup()
+{
+ (
+ #subshell.
+ cd "$share_test_dir" || return
+ rm -f testfile1
+ rm -f testfile2.tmp
+ rm -rf .trash
+ )
+}
+
+#
+# Ensure we start from a clean slate.
+#
+do_cleanup
+
+
+test_recycle()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ echo "
+put $tmpfile testfile1
+put $tmpfile testfile2.tmp
+del testfile1
+del testfile2.tmp
+quit
+" > $tmpfile
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/recycle -I$SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed recycle smbclient run with error %s\n" "$ret"
+ return 1
+ fi
+
+ test -e "$share_test_dir/.trash/testfile1" || {
+ printf ".trash/testfile1 expected to exist but does NOT exist\n"
+ return 1
+ }
+ test -e "$share_test_dir/.trash/testfile2.tmp" && {
+ printf ".trash/testfile2.tmp not expected to exist but DOES exist\n"
+ return 1
+ }
+ perm_want=755
+ perm_is=`stat -c '%a' "$share_test_dir/.trash/"`
+ test "$perm_is" = "$perm_want" || {
+ printf ".trash/ permission should be $perm_want but is $perm_is\n"
+ return 1
+ }
+ return 0
+}
+
+
+testit "recycle" \
+ test_recycle ||
+ failed=$((failed + 1))
+
+#
+# Cleanup.
+do_cleanup
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_registry_share.sh b/source3/script/tests/test_registry_share.sh
new file mode 100755
index 0000000..22e9f73
--- /dev/null
+++ b/source3/script/tests/test_registry_share.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+# Blackbox tests for registry shares
+#
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_registry_share.sh SERVER USERNAME PASSWORD
+EOF
+ exit 1
+fi
+
+SERVER=$1
+USERNAME=$2
+PASSWORD=$3
+shift 3
+failed=0
+
+samba_bindir="$BINDIR"
+samba_srcdir="$SRCDIR"
+smbclient="$samba_bindir/smbclient"
+rpcclient="$samba_bindir/rpcclient"
+
+. $samba_srcdir/testprogs/blackbox/subunit.sh
+. $samba_srcdir/testprogs/blackbox/common_test_fns.inc
+
+test_smbclient \
+ "Test access to registry share [${USERNAME}]" \
+ "ls" "//${SERVER}/registry_share" "-U$USERNAME%$PASSWORD" ||
+ failed=$((failed + 1))
+
+testit_grep_count \
+ "Test for share enum with registry share" \
+ "netname: registry_share" \
+ 1 \
+ ${rpcclient} "ncacn_np:${SERVER}" "-U$USERNAME%$PASSWORD" \
+ -c netshareenum ||
+ failed=$((failed + 1))
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_registry_upgrade.sh b/source3/script/tests/test_registry_upgrade.sh
new file mode 100755
index 0000000..ac4a9db
--- /dev/null
+++ b/source3/script/tests/test_registry_upgrade.sh
@@ -0,0 +1,192 @@
+#!/bin/sh
+#
+# Test for registry upgrades.
+#
+# Copyright (C) 2011 Björn Baumbach <bb@sernet.de>
+
+if [ $# -lt 2 ]; then
+ echo "Usage: test_registry_upgrade.sh NET DBWRAP_TOOL"
+ exit 1
+fi
+
+SCRIPT_DIR=$(dirname $0)
+BASE_DIR="${SCRIPT_DIR}/../../.."
+
+NET="$1"
+DBWRAP_TOOL="$2 --persistent"
+DATADIR="${BASE_DIR}/testdata/samba3"
+WORKSPACE="${SELFTEST_TMPDIR}/registry_upgrade"
+CONFIG_FILE="${WORKSPACE}/smb.conf"
+CONFIGURATION="--configfile=${CONFIG_FILE}"
+
+NETCMD="$NET $CONFIGURATION"
+
+incdir="${BASE_DIR}/testprogs/blackbox"
+. $incdir/subunit.sh
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+REGPATH="HKLM\Software\Samba"
+
+LOGDIR_PREFIX="registry_upgrade"
+
+registry_check()
+(
+ CHECKNO="$1"
+ CHECKDIFF="$2"
+ REGVER=""
+ ALLOWEDERR="INFO: version =|Check database:|overwrite registry format version 0 with 1|no INFO/version found"
+
+ test "x$CHECKNO" = "x0" && {
+ REGVER="--reg-version=1"
+ }
+
+ echo "Registry check $CHECKNO" | tee -a $LOG
+ CHECK="$($NETCMD registry check $REGVER 2>&1)"
+ RC=$?
+
+ ERRORSTR="$(echo "$CHECK" | grep -vE $ALLOWEDERR)"
+ test "x$RC" = "x0" || {
+ echo "upgrade check $CHECKNO failed:" | tee -a $LOG
+ return 1
+ }
+
+ test "x$ERRORSTR" = "x" || {
+ echo "upgrade check $CHECKNO failed:" | tee -a $LOG
+ echo "reason: $CHECK" | tee -a $LOG
+ return 1
+ }
+
+ test "x$CHECKDIFF" = "xcheckdiff" && {
+ $NETCMD registry export 'HKLM' $WORKSPACE/export_${CHECKNO}.reg >>$LOG
+ test "x$?" = "x0" || {
+ echo "Error: 'net registry export HKLM' failed" | tee -a $LOG
+ }
+
+ diff -q $WORKSPACE/export_0.reg $WORKSPACE/export_${CHECKNO}.reg >>$LOG
+ test "x$?" = "x0" || {
+ echo "Error: $WORKSPACE/export_0.reg differs from $WORKSPACE/export_${CHECKNO}.reg" | tee -a $LOG
+ return 1
+ }
+ }
+
+ return 0
+)
+
+registry_upgrade()
+{
+ echo registry_upgrade $1 | tee -a $LOG
+
+ (cat $DATADIR/registry.tdb >$WORKSPACE/registry.tdb) >>$LOG 2>&1
+
+ REGISTRY="${WORKSPACE}/registry.tdb"
+
+ test -e $REGISTRY || {
+ echo "Error: Database file not available" | tee -a $LOG
+ return 1
+ }
+
+ # create config file
+ echo '[global]' >${CONFIG_FILE}
+ echo " state directory = ${WORKSPACE}" >>${CONFIG_FILE}
+ echo " private directory = ${WORKSPACE}" >>${CONFIG_FILE}
+ echo " lock directory = ${WORKSPACE}" >>${CONFIG_FILE}
+
+ # set database INFO/version to 1
+ #$DBWRAP_TOOL $REGISTRY store 'INFO/version' uint32 1
+ #test "x$?" = "x0" || {
+ # echo "Error: Can not set INFO/version" >> $LOG
+ # return 1
+ #}
+
+ # check original registry.tdb
+ echo "$REGISTRY" | tee -a $LOG
+ registry_check 0
+ test "x$?" = "x0" || {
+ echo "Error: initial 'registry_check 0' failed" | tee -a $LOG
+ return 1
+ }
+
+ # trigger upgrade
+ echo "$NETCMD registry enumerate $REGPATH" >>$LOG
+ $NETCMD registry enumerate $REGPATH >>$LOG
+ test "x$?" = "x0" || {
+ echo "Error: 'net registry enumerate $REGPATH' failed" | tee -a $LOG
+ return 1
+ }
+
+ # check upgraded database
+ registry_check 1
+ test "x$?" = "x0" || {
+ echo "Error: 'registry_check 1' after upgrade failed" | tee -a $LOG
+ return 1
+ }
+
+ # export database for diffs
+ $NETCMD registry export 'HKLM' $WORKSPACE/export_0.reg | tee -a $LOG
+ test "x$?" = "x0" || {
+ echo "Error 'net registry export' failed" | tee -a $LOG
+ return 1
+ }
+
+ # remove version string
+ $DBWRAP_TOOL $REGISTRY delete INFO/version | tee -a $LOG
+ test "x$?" = "x0" || {
+ echo "Error: Can not remove INFO/version key from registry" | tee -a $LOG
+ return 1
+ }
+
+ # trigger upgrade on upgraded database
+ echo "$NETCMD registry enumerate $REGPATH" >>$LOG
+ $NETCMD registry enumerate $REGPATH >>$LOG 2>&1
+ test "x$?" = "x0" || {
+ echo "Error: 'net registry enumerate $REGPATH' failed" | tee -a $LOG
+ return 1
+ }
+
+ # check upgraded database again
+ registry_check 2 checkdiff
+ test "x$?" = "x0" || {
+ echo "Error: 'registry_check 2' after upgrade failed" | tee -a $LOG
+ return 1
+ }
+
+ # set database INFO/version to version 2
+ $DBWRAP_TOOL $REGISTRY store 'INFO/version' uint32 2
+ test "x$?" = "x0" || {
+ echo "Error: Can not set INFO/version" | tee -a $LOG
+ return 1
+ }
+
+ # trigger upgrade
+ $NETCMD registry enumerate $REGPATH >>$LOG
+ test "x$?" = "x0" || {
+ echo "Error: 'net registry enumerate $REGPATH' failed" | tee -a $LOG
+ return 1
+ }
+
+ # check upgraded database again
+ registry_check 3 checkdiff
+ test "x$?" = "x0" || {
+ echo "Error: 'registry_check 3' after upgrade failed" | tee -a $LOG
+ return 1
+ }
+}
+
+# remove old workspace
+rm -rf $WORKSPACE
+
+mkdir $WORKSPACE
+
+DIR=$(mktemp -d ${WORKSPACE}/${LOGDIR_PREFIX}_XXXXXX)
+LOG=$DIR/log
+
+testit "registry_upgrade" registry_upgrade || failed=$(expr $failed + 1)
+
+if [ $failed -eq 0 ]; then
+ rm -rf $WORKSPACE
+fi
+
+testok $0 $failed
diff --git a/source3/script/tests/test_resolvconf.sh b/source3/script/tests/test_resolvconf.sh
new file mode 100755
index 0000000..6cf189c
--- /dev/null
+++ b/source3/script/tests/test_resolvconf.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+if [ ! -x $BINDIR/resolvconftest ]; then
+ # Some machines don't have /bin/true, simulate it
+ cat >$BINDIR/resolvconftest <<EOF
+#!/bin/sh
+exit 0
+EOF
+ chmod +x $BINDIR/resolvconftest
+fi
+
+failed=0
+
+testit "resolvconf" $VALGRIND $BINDIR/resolvconftest ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_rofs.sh b/source3/script/tests/test_rofs.sh
new file mode 100755
index 0000000..72901e5
--- /dev/null
+++ b/source3/script/tests/test_rofs.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# Test smbd handling EROFS when creating a file
+# Copyright (C) 2023 Volker Lendecke
+
+if [ $# -ne 4 ]; then
+ echo Usage: $0 SERVERCONFFILE SMBCLIENT SERVER SHARE
+ exit 1
+fi
+
+CONF=$1
+shift 1
+SMBCLIENT=$1
+shift 1
+SERVER=$1
+shift 1
+SHARE=$1
+shift 1
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+error_inject_conf=$(dirname ${SERVERCONFFILE})/error_inject.conf
+echo "error_inject:openat_create = EROFS" >${error_inject_conf}
+
+failed=0
+
+out=$(${SMBCLIENT} //${SERVER}/${SHARE} ${CONF} -U${USER}%${PASSWORD} \
+ -c "put VERSION")
+testit_grep "Expect MEDIA_WRITE_PROTECTED" NT_STATUS_MEDIA_WRITE_PROTECTED \
+ echo "$out" || failed=$(expr $failed + 1)
+
+>${error_inject_conf}
+
+testok $0 $failed
diff --git a/source3/script/tests/test_rpcclient.sh b/source3/script/tests/test_rpcclient.sh
new file mode 100755
index 0000000..4260963
--- /dev/null
+++ b/source3/script/tests/test_rpcclient.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+if [ $# -lt 1 ]; then
+ cat <<EOF
+Usage: test_rpcclient.sh ccache binding <rpcclient commands>
+EOF
+ exit 1
+fi
+
+KRB5CCNAME=$1
+shift 1
+export KRB5CCNAME
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+testit "rpcclient" $VALGRIND $BINDIR/rpcclient $ADDARGS || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_rpcclient_dfs.sh b/source3/script/tests/test_rpcclient_dfs.sh
new file mode 100755
index 0000000..0ae9e50
--- /dev/null
+++ b/source3/script/tests/test_rpcclient_dfs.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Copyright (c) 2022 Pavel Filipenský <pfilipen@redhat.com>
+#
+# Blackbox tests for the rpcclient DFS commands
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_rpcclient_dfs.sh USERNAME PASSWORD SERVER RPCCLIENT
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+SERVER="$3"
+RPCCLIENT="$4"
+
+RPCCLIENTCMD="${VALGRIND} ${RPCCLIENT} ${SERVER} -U${USERNAME}%${PASSWORD}"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "${incdir}"/subunit.sh
+
+failed=0
+
+${RPCCLIENTCMD} -c "dfsversion"
+RC=$?
+testit "dfsversion" test ${RC} -eq 0 || failed=$((failed + 1))
+
+${RPCCLIENTCMD} -c "dfsenum 5"
+RC=$?
+testit "dfsenum" test ${RC} -eq 0 || failed=$((failed + 1))
+
+# This test fails: _dfs_EnumEx() is not implemented on samba RPC server side
+${RPCCLIENTCMD} -c "dfsenumex 5"
+RC=$?
+testit "dfsenumex" test ${RC} -eq 0 || failed=$((failed + 1))
+
+# Every backslash is reduced twice, so we need to enter it 4 times.
+# Rpc server then gets: '\\server\share\path'
+${RPCCLIENTCMD} -c "dfsgetinfo \\\\\\\\${SERVER}\\\\msdfs-share\\\\msdfs-src1 ${SERVER} msdfs-src1"
+RC=$?
+testit "dfsgetinfo" test ${RC} -eq 0 || failed=$((failed + 1))
+
+testok "$0" "${failed}"
diff --git a/source3/script/tests/test_rpcclient_lookup.sh b/source3/script/tests/test_rpcclient_lookup.sh
new file mode 100755
index 0000000..cc49838
--- /dev/null
+++ b/source3/script/tests/test_rpcclient_lookup.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+#
+# Blackbox tests for the rpcclient LSA lookup commands
+#
+# Copyright (C) 2020 Christof Schmitt
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_rpcclient_lookup.sh USERNAME PASSWORD SERVER RPCCLIENT
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+SERVER="$3"
+RPCCLIENT="$4"
+
+RPCCLIENTCMD="$RPCCLIENT $SERVER -U$USERNAME%$PASSWORD"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+$RPCCLIENTCMD -c "lookupsids S-1-1-0"
+RC=$?
+testit "lookupsids" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+$RPCCLIENTCMD -c "lookupsids_level 1 S-1-1-0"
+RC=$?
+testit "lookupsids_level" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+$RPCCLIENTCMD -c "lookupnames Everyone"
+RC=$?
+testit "lookupnames" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+$RPCCLIENTCMD -c "lookupnames_level 1 Everyone"
+RC=$?
+testit "lookupnames_level" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_rpcclient_netsessenum.sh b/source3/script/tests/test_rpcclient_netsessenum.sh
new file mode 100755
index 0000000..1d04543
--- /dev/null
+++ b/source3/script/tests/test_rpcclient_netsessenum.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+#
+# Blackbox tests for the rpcclient srvsvc commands
+#
+# Copyright (C) 2018 Christof Schmitt
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 DOMAIN ADMIN_USER ADMIN_PASSWORD SERVER RPCCLIENT SMBTORTURE3 SHARE
+EOF
+ exit 1
+fi
+
+DOMAIN="$1"
+ADMIN_USER="$2"
+ADMIN_PASSWORD="$3"
+SERVER="$4"
+RPCCLIENT="$5"
+SMBTORTURE3="$6"
+SHARE="$7"
+
+USERPASS="-U$DOMAIN/$ADMIN_USER%$ADMIN_PASSWORD"
+RPCCLIENTCMD="$RPCCLIENT $SERVER $USERPASS"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+#
+# Verify initial number of sessions.
+#
+$RPCCLIENTCMD -c NetSessEnum | grep Received
+RC=$?
+testit "netsessenum" test $RC = 0 || failed=$(expr $failed + 1)
+
+OUT=$($RPCCLIENTCMD -c NetSessEnum | grep Received)
+test "$OUT" = "Received 1 entries."
+RC=$?
+testit "count1" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+#
+# Inject smbd crash
+#
+$SMBTORTURE3 //"$SERVER"/"$SHARE" "$USERPASS" CLEANUP1
+
+#
+# Verify number of sessions after crash
+#
+OUT=$($RPCCLIENTCMD -c NetSessEnum | grep Received)
+test "$OUT" = "Received 1 entries."
+RC=$?
+testit "count2" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_rpcclient_pw_nt_hash.sh b/source3/script/tests/test_rpcclient_pw_nt_hash.sh
new file mode 100755
index 0000000..c1e3660
--- /dev/null
+++ b/source3/script/tests/test_rpcclient_pw_nt_hash.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Blackbox tests for the rpcclient --pw-nt-hash option
+#
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_rpcclient_pw_nt_hash.sh USERNAME PASSWORD SERVER RPCCLIENT
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+SERVER="$3"
+RPCCLIENT="$4"
+
+HASH=$(echo -n $PASSWORD | $PYTHON -c 'import sys, binascii, samba, samba.crypto; sys.stdout.buffer.write(binascii.hexlify(samba.crypto.md4_hash_blob(sys.stdin.buffer.read(1000).decode().encode("UTF-16LE"))))')
+
+RPCCLIENTCMD="$RPCCLIENT $SERVER --pw-nt-hash -U$USERNAME%$HASH -c queryuser"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+testit "rpcclient --pw-nt-hash" $RPCCLIENTCMD || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_rpcclient_samlogon.sh b/source3/script/tests/test_rpcclient_samlogon.sh
new file mode 100755
index 0000000..ff73be5
--- /dev/null
+++ b/source3/script/tests/test_rpcclient_samlogon.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_rpcclient_samlogon.sh USERNAME PASSWORD binding <rpcclient commands>
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+shift 2
+ADDARGS="$@"
+
+rpcclient_samlogon_schannel_seal()
+{
+ $VALGRIND $BINDIR/rpcclient -U% -c "schannel;samlogon '$USERNAME' '$PASSWORD';samlogon '$USERNAME' '$PASSWORD'" "$@"
+}
+
+rpcclient_samlogon_schannel_sign()
+{
+ $VALGRIND $BINDIR/rpcclient -U% -c "schannelsign;samlogon '$USERNAME' '$PASSWORD';samlogon '$USERNAME' '$PASSWORD'" "$@"
+}
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+testit "rpcclient dsenumdomtrusts" $VALGRIND $BINDIR/rpcclient $ADDARGS -U% -c "dsenumdomtrusts" || failed=$(expr $failed + 1)
+testit "rpcclient getdcsitecoverage" $VALGRIND $BINDIR/rpcclient $ADDARGS -U% -c "getdcsitecoverage" || failed=$(expr $failed + 1)
+testit "rpcclient samlogon schannel seal" rpcclient_samlogon_schannel_seal $ADDARGS || failed=$(expr $failed +1)
+testit "rpcclient samlogon schannel sign" rpcclient_samlogon_schannel_sign $ADDARGS || failed=$(expr $failed +1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_rpcclientsrvsvc.sh b/source3/script/tests/test_rpcclientsrvsvc.sh
new file mode 100755
index 0000000..fdade1a
--- /dev/null
+++ b/source3/script/tests/test_rpcclientsrvsvc.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+#
+# Blackbox tests for the rpcclient srvsvc commands
+#
+# Copyright (C) 2015 Christof Schmitt
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_rpcclientsrvsvc.sh USERNAME PASSWORD SERVER RPCCLIENT SHARE1
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+SERVER="$3"
+RPCCLIENT="$4"
+SHARE1="$5"
+
+RPCCLIENTCMD="$RPCCLIENT $SERVER -U$USERNAME%$PASSWORD"
+
+SHARENAME=SRVSVCTEST
+MAX_USERS=5
+COMMENT="share for RPC SRVSVC testing"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# Query path from existing share
+
+$RPCCLIENTCMD -c "netsharegetinfo $SHARE1"
+RC=$?
+testit "getinfo on S$SHARE1" test $RC = 0 || failed=$(expr $failed + 1)
+
+SHAREPATH=$($RPCCLIENTCMD -c "netsharegetinfo '$SHARE1'" |
+ grep path: | sed -e 's/.*path:\t//')
+testit "verifying $SHARE1 path" test -n "$SHAREPATH" ||
+ failed=$(expr $failed + 1)
+
+# Add a new share
+
+$RPCCLIENTCMD -c "netshareadd '$SHAREPATH' '$SHARENAME' '$MAX_USERS' '$COMMENT'"
+RC=$?
+testit "netshareadd" test $RC = 0 || failed=$(expr $failed + 1)
+
+# Verify comment for new share
+
+COMMENT_RET=$($RPCCLIENTCMD -c "netsharegetinfo '$SHARENAME'" |
+ grep remark: | sed -e 's/.*remark:\t//')
+
+test "$COMMENT" = "$COMMENT_RET"
+RC=$?
+testit "verifying comment" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+# Verify share path for new share
+
+SHAREPATH_RET=$($RPCCLIENTCMD -c "netsharegetinfo '$SHARENAME'" |
+ grep path: | sed -e 's/.*path:\t//')
+test "$SHAREPATH" = "$SHAREPATH_RET"
+RC=$?
+testit "verifying share path" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+# Set CSC policy
+
+$RPCCLIENTCMD -c "netsharesetdfsflags '$SHARENAME' 0x30"
+RC=$?
+testit "set csc policy" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+# Query CSC policy
+
+CSC_CACHING_RET=$($RPCCLIENTCMD -c "netsharegetinfo '$SHARENAME' 1005" |
+ grep 'csc caching' | sed -e 's/.*csc caching: //')
+testit "verifying csc policy" test $CSC_CACHING_RET -eq 3 ||
+ failed=$(expr $failed + 1)
+
+# Delete share
+
+$RPCCLIENTCMD -c "netsharedel '$SHARENAME'"
+RC=$?
+testit "deleting share" test $RC -eq 0 || failed=$(expr $failed + 1)
+
+# Verify that query to deleted share fails
+
+$RPCCLIENTCMD -c "netsharegetinfo '$SHARENAME' 1005"
+RC=$?
+testit "querying deleted share" test $RC -eq 1 || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_sacl_set_get.sh b/source3/script/tests/test_sacl_set_get.sh
new file mode 100755
index 0000000..1c78ab2
--- /dev/null
+++ b/source3/script/tests/test_sacl_set_get.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Runs the smbtorture3 SMB2-SACL test
+# that requires SeSecurityPrivilege
+# against Samba.
+#
+
+if [ $# -lt 7 ]; then
+ echo "Usage: $0 SERVER SERVER_IP USERNAME PASSWORD SMBTORTURE3 NET SHARE"
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+SMBTORTURE3="$5"
+NET="$6"
+SHARE="$7"
+
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+sacl_set_get()
+{
+ out=$($SMBTORTURE3 //$SERVER_IP/$SHARE -U $USERNAME%$PASSWORD SMB2-SACL)
+ if [ $? -ne 0 ]; then
+ echo "SMB2-SACL failed"
+ echo "$out"
+ return 1
+ fi
+}
+
+# Grant SeSecurityPrivilege to the user
+testit "grant SeSecurityPrivilege" $NET rpc rights grant $USERNAME SeSecurityPrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || failed=$(expr $failed + 1)
+
+# Run the tests.
+testit "SACL set_get" sacl_set_get || failed=$(expr $failed + 1)
+
+# Revoke SeSecurityPrivilege
+testit "revoke SeSecurityPrivilege" $NET rpc rights revoke $USERNAME SeSecurityPrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_server_addresses.sh b/source3/script/tests/test_server_addresses.sh
new file mode 100755
index 0000000..891c2f2
--- /dev/null
+++ b/source3/script/tests/test_server_addresses.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+#
+# Blackbox test for the server addresses parameter
+#
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+testit_grep_count \
+ "[only_ipv6] invisible over ipv4" "netname: only_ipv6" 0 \
+ bin/rpcclient "$SERVER_IP" -U% -c netshareenumall ||
+ failed=$(expr "$failed" + 1)
+
+testit_grep_count \
+ "[only_ipv6] visible over ipv6" "netname: only_ipv6" 1 \
+ bin/rpcclient "$SERVER_IPV6" -U% -c netshareenumall ||
+ failed=$(expr "$failed" + 1)
+
+testit_expect_failure_grep \
+ "[only_ipv6] inaccessible over ipv4" \
+ "tree connect failed: NT_STATUS_BAD_NETWORK_NAME" \
+ bin/smbclient //"$SERVER_IP"/only_ipv6 -U% -c quit ||
+ failed=$(expr "$failed" + 1)
+
+testit \
+ "[only_ipv6] accessible over ipv6" \
+ bin/smbclient //"$SERVER_IPV6"/only_ipv6 -U% -c quit ||
+ failed=$(expr "$failed" + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_shadow_copy.sh b/source3/script/tests/test_shadow_copy.sh
new file mode 100755
index 0000000..dd6699f
--- /dev/null
+++ b/source3/script/tests/test_shadow_copy.sh
@@ -0,0 +1,458 @@
+#!/usr/bin/env bash
+#
+# Blackbox test for shadow_copy2 VFS.
+#
+
+if [ $# -lt 7 ]; then
+cat <<EOF
+Usage: test_shadow_copy SERVER SERVER_IP DOMAIN USERNAME PASSWORD WORKDIR SMBCLIENT PARAMS
+EOF
+exit 1;
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+DOMAIN=${3}
+USERNAME=${4}
+PASSWORD=${5}
+WORKDIR=${6}
+SMBCLIENT=${7}
+shift 7
+ADDARGS="$*"
+SMBCLIENT="$VALGRIND ${SMBCLIENT} ${ADDARGS}"
+
+incdir=`dirname $0`/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+SNAPSHOTS[0]='@GMT-2015.10.31-19.40.30'
+SNAPSHOTS[1]='@GMT-2016.10.31-19.40.30'
+SNAPSHOTS[2]='@GMT-2017.10.31-19.40.30'
+SNAPSHOTS[3]='@GMT-2018.10.31-19.40.30'
+SNAPSHOTS[4]='@GMT-2019.10.31-19.40.30'
+SNAPSHOTS[5]='@GMT-2020.10.31-19.40.30'
+SNAPSHOTS[6]='@GMT-2021.10.31-19.40.30'
+SNAPSHOTS[7]='@GMT-2022.10.31-19.40.30'
+SNAPSHOTS[8]='@GMT-2023.10.31-19.40.30'
+SNAPSHOTS[9]='@GMT-2024.10.31-19.40.30'
+SNAPSHOTS[10]='@GMT-2010-11-11'
+SNAPSHOTS[11]='@GMT-2011.11.11-11.40.30'
+SNAPSHOTS[12]='snap@GMT-2012.11.11-11.40.30'
+SNAPSHOTS[13]='@GMT-2013.11.11-11_40_33-snap'
+SNAPSHOTS[14]='@GMT-2014.11.11-11.40.30'
+SNAPSHOTS[15]='daily@GMT-2015.11.11-11.40.30'
+SNAPSHOTS[16]='snap_GMT-2016.11.11-11.40.30'
+SNAPSHOTS[17]='sysp_GMT-2017.11.11-11.40.30'
+SNAPSHOTS[18]='monthly@GMT-2018.11.11-11.40.30'
+SNAPSHOTS[19]='straps_GMT-2019.11.11-11.40.33'
+
+# build a hierarchy of files, symlinks, and directories
+build_files()
+{
+ local rootdir
+ local prefix
+ local version
+ local destdir
+ local content
+ rootdir=$1
+ prefix=$2
+ version=$3
+ content=$4
+ if [ -n "$prefix" ] ; then
+ destdir=$rootdir/$prefix
+ else
+ destdir=$rootdir
+ fi
+
+ mkdir -p $destdir
+ if [ "$version" = "latest" ] ; then
+ #non-snapshot files
+ # for non-snapshot version, create legit files
+ # so that wide-link checks focus on snapshot files
+ echo "$content" > $destdir/foo
+ mkdir -p $destdir/bar
+ echo "$content" > $destdir/bar/baz
+ echo "$content" > $destdir/bar/lfoo
+ echo "$content" > $destdir/bar/letcpasswd
+ echo "$content" > $destdir/bar/loutside
+ elif [ "$version" = "fullsnap" ] ; then
+ #snapshot files
+ echo "$content" > $destdir/foo
+ mkdir -p $destdir/bar
+ echo "$content" > $destdir/bar/baz
+ ln -fs ../foo $destdir/bar/lfoo
+ ln -fs /etc/passwd $destdir/bar/letcpasswd
+ ln -fs ../../outside $destdir/bar/loutside
+ echo "$content" > `dirname $destdir`/outside
+ else #subshare snapshot - at bar
+ echo "$content" > $destdir/baz
+ ln -fs ../foo $destdir/lfoo
+ ln -fs /etc/passwd $destdir/letcpasswd
+ ln -fs ../../outside $destdir/loutside
+ echo "$content" > `dirname $destdir`/../outside
+ fi
+
+}
+
+# build a snapshots directory
+build_snapshots()
+{
+ local where #where to build snapshots
+ local prefix #prefix from snapshot dir to share root
+ local start #timestamp index of first snapshot
+ local end #timestamp index of last snapshot
+ local sub #creat a snapshot of subtree of share
+ local snapdir
+ local snapname
+ local i
+ local version
+
+ where=$1
+ prefix=$2
+ start=$3
+ end=$4
+ sub=$5
+
+ snapdir=$where/.snapshots
+ mkdir -p $snapdir
+
+ version="fullsnap"
+ if [ "$sub" = "1" ] ; then
+ version="subsnap"
+ prefix=""
+
+ # a valid link target for an inner symlink -
+ # the link is not broken yet should be blocked
+ # by wide link checks
+ touch $snapdir/foo
+ fi
+
+ for i in `seq $start $end` ; do
+ snapname=${SNAPSHOTS[$i]}
+ mkdir $snapdir/$snapname
+ build_files $snapdir/$snapname "$prefix" $version "$snapname"
+ done
+}
+
+# Test listing previous versions of a file
+test_count_versions()
+{
+ local share
+ local path
+ local expected_count
+ local skip_content
+ local versions
+ local tstamps
+ local tstamp
+ local content
+ local is_dir
+
+ share=$1
+ path=$2
+ expected_count=$3
+ skip_content=$4
+ versions=`$SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "allinfo $path" | grep "^create_time:" | wc -l`
+ if [ "$versions" != "$expected_count" ] ; then
+ echo "expected $expected_count versions of $path, got $versions"
+ return 1
+ fi
+
+ is_dir=0
+ $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "allinfo $path" | grep "^attributes:.*D" && is_dir=1
+ if [ $is_dir = 1 ] ; then
+ skip_content=1
+ fi
+
+ #readable snapshots
+ tstamps=`$SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "allinfo $path" | awk '/^@GMT-/ {snapshot=$1} /^create_time:/ {printf "%s\n", snapshot}'`
+ for tstamp in $tstamps ; do
+ if [ $is_dir = 0 ] ;
+ then
+ if ! $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "get $tstamp\\$path $WORKDIR/foo" ; then
+ echo "Failed getting \\\\$SERVER\\$share\\$tstamp\\$path"
+ return 1
+ fi
+ else
+ if ! $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "ls $tstamp\\$path\\*" ; then
+ echo "Failed listing \\\\$SERVER\\$share\\$tstamp\\$path"
+ return 1
+ fi
+ fi
+
+ #also check the content, but not for wide links
+ if [ "x$skip_content" != "x1" ] ; then
+ content=`cat $WORKDIR/foo`
+ if [ "$content" != "$tstamp" ] ; then
+ echo "incorrect content of \\\\$SERVER\\$share\\$tstamp\\$path expected [$tstamp] got [$content]"
+ return 1
+ fi
+ fi
+ done
+
+ #non-readable snapshots
+ tstamps=`$SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "allinfo $path" | \
+ awk '/^@GMT-/ {if (snapshot!=""){printf "%s\n", snapshot} ; snapshot=$1} /^create_time:/ {snapshot=""} END {if (snapshot!=""){printf "%s\n", snapshot}}'`
+ for tstamp in $tstamps ; do
+ if [ $is_dir = 0 ] ;
+ then
+ if $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "get $tstamp\\$path $WORKDIR/foo" ; then
+ echo "Unexpected success getting \\\\$SERVER\\$share\\$tstamp\\$path"
+ return 1
+ fi
+ else
+ if $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP -c "ls $tstamp\\$path\\*" ; then
+ echo "Unexpected success listing \\\\$SERVER\\$share\\$tstamp\\$path"
+ return 1
+ fi
+ fi
+ done
+}
+
+# Test fetching a previous version of a file
+test_fetch_snap_file()
+{
+ local share
+ local path
+ local snapidx
+
+ share=$1
+ path=$2
+ snapidx=$3
+ $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP \
+ -c "get ${SNAPSHOTS[$snapidx]}/$path $WORKDIR/foo"
+}
+
+# Test fetching a previous version of a file
+test_fetch_snap_dir()
+{
+ local share
+ local path
+ local snapidx
+
+ share=$1
+ path=$2
+ snapidx=$3
+
+ # This first command is not strictly needed, but it causes the snapshots to
+ # appear in a network trace which helps debugging...
+ $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP \
+ -c "allinfo $path"
+
+ $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$share" -I $SERVER_IP \
+ -c "ls ${SNAPSHOTS[$snapidx]}/$path/*"
+}
+
+test_shadow_copy_fixed()
+{
+ local share #share to contact
+ local where #where to place snapshots
+ local prefix #prefix to files inside snapshot
+ local msg
+ local allow_wl
+ local ncopies_allowd
+ local ncopies_blocked
+
+ share=$1
+ where=$2
+ prefix=$3
+ msg=$4
+ allow_wl=$5
+
+ ncopies_allowed=4
+ ncopies_blocked=1
+ if [ -n "$allow_wl" ] ; then
+ ncopies_blocked=4
+ fi
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots $WORKDIR/$where "$prefix" 0 2
+
+ testit "$msg - regular file" \
+ test_count_versions $share foo $ncopies_allowed || \
+ failed=`expr $failed + 1`
+
+ testit "$msg - regular file in subdir" \
+ test_count_versions $share bar/baz $ncopies_allowed || \
+ failed=`expr $failed + 1`
+
+ testit "$msg - regular file in case insensitive subdir" \
+ test_count_versions $share bar/bAz $ncopies_allowed || \
+ failed=`expr $failed + 1`
+
+ testit "$msg - local symlink" \
+ test_count_versions $share bar/lfoo $ncopies_allowed || \
+ failed=`expr $failed + 1`
+
+ testit "$msg - abs symlink outside" \
+ test_count_versions $share bar/letcpasswd $ncopies_blocked 1 || \
+ failed=`expr $failed + 1`
+
+ testit "$msg - rel symlink outside" \
+ test_count_versions $share bar/loutside $ncopies_blocked 1 || \
+ failed=`expr $failed + 1`
+
+ testit "$msg - list directory" \
+ test_count_versions $share bar $ncopies_allowed || \
+ failed=`expr $failed + 1`
+}
+
+test_shadow_copy_everywhere()
+{
+ local share #share to contact
+
+ share=$1
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots "$WORKDIR/mount" "base/share" 0 0
+ build_snapshots "$WORKDIR/mount/base" "share" 1 2
+ build_snapshots "$WORKDIR/mount/base/share" "" 3 5
+ build_snapshots "$WORKDIR/mount/base/share/bar" "" 6 9 1
+
+ testit "snapshots in each dir - regular file" \
+ test_count_versions $share foo 4 || \
+ failed=`expr $failed + 1`
+
+ testit "snapshots in each dir - regular file in subdir" \
+ test_count_versions $share bar/baz 5 || \
+ failed=`expr $failed + 1`
+
+ testit "snapshots in each dir - local symlink (but outside snapshot)" \
+ test_count_versions $share bar/lfoo 1 || \
+ failed=`expr $failed + 1`
+
+ testit "snapshots in each dir - abs symlink outside" \
+ test_count_versions $share bar/letcpasswd 1 || \
+ failed=`expr $failed + 1`
+
+ testit "snapshots in each dir - rel symlink outside" \
+ test_count_versions $share bar/loutside 1 || \
+ failed=`expr $failed + 1`
+
+ #the previous versions of the file bar/lfoo points to are outside its
+ #snapshot, and are not reachable. However, but previous versions
+ #taken at different, non-overlapping times higher up the
+ #hierarchy are still reachable.
+ testit "fetch a previous version of a regular file" \
+ test_fetch_snap_file $share "bar/baz" 6 || \
+ failed=`expr $failed + 1`
+
+ testit "fetch a previous version of a regular file via non-canonical parent path" \
+ test_fetch_snap_file $share "BAR/baz" 6 || \
+ failed=`expr $failed + 1`
+
+ testit "fetch a previous version of a regular file via non-canonical basepath" \
+ test_fetch_snap_file $share "bar/BAZ" 6 || \
+ failed=`expr $failed + 1`
+
+ testit "fetch a previous version of a regular file via non-canonical path" \
+ test_fetch_snap_file $share "BAR/BAZ" 6 || \
+ failed=`expr $failed + 1`
+
+ testit_expect_failure "fetch a (non-existent) previous version of a symlink" \
+ test_fetch_snap_file $share "bar/lfoo" 6 || \
+ failed=`expr $failed + 1`
+
+ testit "fetch a previous version of a symlink via browsing (1)" \
+ test_fetch_snap_file $share "bar/lfoo" 0 || \
+ failed=`expr $failed + 1`
+
+ testit "fetch a previous version of a symlink via browsing (2)" \
+ test_fetch_snap_file $share "bar/lfoo" 1 || \
+ failed=`expr $failed + 1`
+
+ testit "fetch a previous version of a symlink via browsing (3)" \
+ test_fetch_snap_file $share "bar/lfoo" 3 || \
+ failed=`expr $failed + 1`
+
+ testit "list a previous version directory" \
+ test_fetch_snap_dir $share "bar" 6 || \
+ failed=`expr $failed + 1`
+}
+
+test_shadow_copy_format()
+{
+ local share #share to contact
+ local where #where to place snapshots
+ local prefix #prefix to files inside snapshot
+ local ncopies_allowd
+ local msg
+
+ share=$1
+ where=$2
+ prefix=$3
+ ncopies_allowed=$4
+ msg=$5
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots $WORKDIR/$where "$prefix" 10 19
+
+ testit "$msg - regular file" \
+ test_count_versions $share foo $ncopies_allowed 1 || \
+ failed=`expr $failed + 1`
+}
+
+# Test fetching a file where there's no current version of it
+test_missing_basedir()
+{
+ local share
+ local where
+ local prefix
+ local snapidx
+
+ share=$1
+ where=$2
+ prefix=$3
+ snapidx=$4
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots $WORKDIR/$where "$prefix" "$snapidx" "$snapidx"
+
+ (cd "$WORKDIR/$where"/share; mv bar _bar)
+
+ testit "fetch a file without a latest version" \
+ test_fetch_snap_file "$share" "bar/baz" "$snapidx" || \
+ failed=`expr $failed + 1`
+
+ (cd "$WORKDIR/$where"/share; mv _bar bar)
+}
+
+#build "latest" files
+build_files $WORKDIR/mount base/share "latest" "latest"
+
+failed=0
+
+# a test with wide links allowed - also to verify that what's later
+# being blocked is a result of server security measures and not
+# a testing artifact.
+test_shadow_copy_fixed shadow_wl mount base/share "shadow copies with wide links allowed" 1
+
+# tests for a fixed snapshot location
+test_shadow_copy_fixed shadow1 mount base/share "full volume snapshots mounted under volume"
+test_shadow_copy_fixed shadow2 . base/share "full volume snapshots mounted outside volume"
+test_shadow_copy_fixed shadow3 mount/base share "sub volume snapshots mounted under snapshot point"
+test_shadow_copy_fixed shadow4 . share "sub volume snapshots mounted outside"
+test_shadow_copy_fixed shadow5 mount/base/share "" "full volume snapshots and share mounted under volume"
+test_shadow_copy_fixed shadow6 . "" "full volume snapshots and share mounted outside"
+test_shadow_copy_fixed shadow8 . share "logical snapshot layout"
+
+# tests for snapshot everywhere - one snapshot location
+test_shadow_copy_fixed shadow7 mount base/share "'everywhere' full volume snapshots"
+test_shadow_copy_fixed shadow7 mount/base share "'everywhere' sub volume snapshots"
+test_shadow_copy_fixed shadow7 mount/base/share "" "'everywhere' share snapshots"
+
+# a test for snapshots everywhere - multiple snapshot locations
+test_shadow_copy_everywhere shadow7
+
+# tests for testing snapshot selection via shadow:format
+test_shadow_copy_format shadow_fmt0 mount/base share 3 "basic shadow:format test"
+test_shadow_copy_format shadow_fmt1 mount/base share 2 "shadow:format with only date"
+test_shadow_copy_format shadow_fmt2 mount/base share 2 "shadow:format with some prefix"
+test_shadow_copy_format shadow_fmt3 mount/base share 2 "shadow:format with modified format"
+test_shadow_copy_format shadow_fmt4 mount/base share 3 "shadow:format with snapprefix"
+test_shadow_copy_format shadow_fmt5 mount/base share 6 "shadow:format with delimiter"
+test_missing_basedir shadow3 "mount/base" "share" 6
+
+exit $failed
diff --git a/source3/script/tests/test_shadow_copy_torture.sh b/source3/script/tests/test_shadow_copy_torture.sh
new file mode 100755
index 0000000..3a6ba91
--- /dev/null
+++ b/source3/script/tests/test_shadow_copy_torture.sh
@@ -0,0 +1,227 @@
+#!/usr/bin/env bash
+#
+# Blackbox test for shadow_copy2 VFS.
+#
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_shadow_copy SERVER SERVER_IP DOMAIN USERNAME PASSWORD WORKDIR SMBTORTURE SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+DOMAIN=${3}
+USERNAME=${4}
+PASSWORD=${5}
+WORKDIR=${6}
+SMBTORTURE="$VALGRIND ${7}"
+SMBCLIENT="$VALGRIND ${8}"
+shift 7
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+SNAPSHOT="@GMT-2015.10.31-19.40.30"
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+# build a hierarchy of files, symlinks, and directories
+build_files()
+{
+ local destdir
+ destdir=$1
+
+ echo "$content" >$destdir/foo
+
+ mkdir -p $WORKDIR/subdir/
+ touch $WORKDIR/subdir/hardlink
+}
+
+# build a snapshots directory
+build_snapshots()
+{
+ local snapdir
+
+ snapdir=$WORKDIR/.snapshots
+
+ mkdir -p $snapdir/$SNAPSHOT
+
+ build_files $snapdir/$SNAPSHOT
+
+ mkdir -p $snapdir/$SNAPSHOT/subdir
+ ln "$WORKDIR"/subdir/hardlink "$snapdir"/$SNAPSHOT/subdir/hardlink
+}
+
+build_stream_on_snapshot()
+{
+ file=$WORKDIR/.snapshots/$SNAPSHOT/foo
+
+ setfattr -n 'user.DosStream.bar:$DATA' -v baz $file || return 1
+}
+
+test_shadow_copy_write()
+{
+ local msg
+
+ msg=$1
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots
+
+ testit "writing to shadow copy of a file" \
+ $SMBTORTURE \
+ -U$USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ --option="torture:twrp_file=foo" \
+ --option="torture:twrp_snapshot=$SNAPSHOT" \
+ smb2.twrp.write ||
+ failed=$(expr $failed + 1)
+}
+
+test_shadow_copy_stream()
+{
+ local msg
+
+ msg=$1
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots
+ build_stream_on_snapshot || {
+ subunit_start_test msg
+ subunit_skip_test msg <<EOF
+test_shadow_copy_stream needs an fs with xattrs
+EOF
+ return 0
+ }
+
+ testit "reading stream of a shadow copy of a file" \
+ $SMBTORTURE \
+ -U$USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ --option="torture:twrp_file=foo" \
+ --option="torture:twrp_stream=bar" \
+ --option="torture:twrp_stream_size=3" \
+ --option="torture:twrp_snapshot=$SNAPSHOT" \
+ smb2.twrp.stream ||
+ failed=$(expr $failed + 1)
+}
+
+test_shadow_copy_openroot()
+{
+ local msg
+
+ msg=$1
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots
+
+ testit "opening shadow copy root of share" \
+ $SMBTORTURE \
+ -U$USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ --option="torture:twrp_snapshot=$SNAPSHOT" \
+ smb2.twrp.openroot ||
+ failed=$(expr $failed + 1)
+}
+
+test_shadow_copy_fix_inodes()
+{
+ local msg
+
+ msg=$1
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots
+
+ out=$($SMBCLIENT \
+ -U $USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ -c "open $SNAPSHOT/subdir/hardlink") || failed=$(expr $failed + 1)
+ echo $out
+ echo $out | grep "hardlink: for read/write fnum 1" || return 1
+}
+
+build_hiddenfile()
+{
+ local snapdir
+
+ snapdir=$WORKDIR/.snapshots
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots
+
+ touch $WORKDIR/hiddenfile
+
+ # Create a file with hidden attribute
+ $SMBCLIENT -U $USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ -c "put $WORKDIR/hiddenfile hiddenfile; setmode hiddenfile +h"
+ # ...and move it to the snapshot directory
+ mv $WORKDIR/hiddenfile $snapdir/$SNAPSHOT/
+}
+
+test_hiddenfile()
+{
+ build_hiddenfile
+
+ out=$($SMBCLIENT \
+ -U $USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ -c "allinfo $SNAPSHOT/hiddenfile") || return 1
+ echo $out
+ echo $out | grep "attributes: HA (22)" || return 1
+
+ out=$($SMBCLIENT \
+ -U $USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ -c "ls $SNAPSHOT/hiddenfile") || return 1
+ echo $out
+ echo $out | grep "hiddenfile[[:blank:]]*AH" || return 1
+
+ return 0
+}
+
+test_shadow_copy_listdir_fix_inodes()
+{
+ local msg
+
+ msg=$1
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots
+
+ testit "$msg" \
+ $SMBTORTURE \
+ -U$USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ --option="torture:twrp_snapshot=$SNAPSHOT" \
+ smb2.twrp.listdir ||
+ failed=$(expr $failed + 1)
+}
+
+build_files $WORKDIR
+
+# test open for writing and write behaviour of snapshoted files
+test_shadow_copy_write "write behaviour of snapshoted files"
+
+test_shadow_copy_stream "reading stream of snapshotted file"
+
+test_shadow_copy_openroot "opening root of shadow copy share"
+
+testit "fix inodes with hardlink" test_shadow_copy_fix_inodes || failed=$(expr $failed + 1)
+
+testit "Test reading DOS attribute" test_hiddenfile || failed=$(expr $failed + 1)
+
+test_shadow_copy_listdir_fix_inodes "fix inodes when listing directory"
+
+exit $failed
diff --git a/source3/script/tests/test_shareenum.sh b/source3/script/tests/test_shareenum.sh
new file mode 100755
index 0000000..0334243
--- /dev/null
+++ b/source3/script/tests/test_shareenum.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# this tests share enumeration with "access based share enum"
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: $0 SERVER USERNAME PASSWORD RPCCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+USERNAME="$2"
+PASSWORD="$3"
+RPCCLIENT="$4"
+RPCCLIENT="$VALGRIND ${RPCCLIENT}"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+user_see_share()
+{
+ local user=$1
+ local share=$2
+ $RPCCLIENT //$SERVER -U$user%$PASSWORD -c "netshareenumall" | grep $share >/dev/null 2>&1
+}
+
+testit "$USERNAME sees tmp" user_see_share $USERNAME tmp
+testit "$USERNAME sees valid-users-tmp" user_see_share $USERNAME valid-users-tmp
+testit "force_user sees tmp" user_see_share force_user tmp
+testit_expect_failure "force_user does not see valid-users-tmp" user_see_share force_user valid-users-tmp
diff --git a/source3/script/tests/test_sharesec.sh b/source3/script/tests/test_sharesec.sh
new file mode 100755
index 0000000..a083a56
--- /dev/null
+++ b/source3/script/tests/test_sharesec.sh
@@ -0,0 +1,140 @@
+#!/bin/sh
+#
+# Test sharesec command.
+#
+# Verify that changing and querying the security descriptor works. Also
+# ensure that the output format for ACL entries does not change.
+#
+# The test uses well-known SIDs to not require looking up names and SIDs
+#
+# Copyright (C) 2015, 2019 Christof Schmitt
+
+if [ $# -lt 4 ]; then
+ echo Usage: test_sharesec.sh SERVERCONFFILE SHARESEC NET SHARE
+ exit 1
+fi
+
+CONF=$1
+SHARESEC=$2
+NET=$3
+SHARE=$4
+
+CMD="$SHARESEC $CONF $SHARE"
+NET_CMD="$NET $CONF"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+testit "Set new ACL" $CMD --replace S-1-1-0:ALLOWED/0x0/READ ||
+ failed=$(expr $failed + 1)
+testit "Query new ACL" $CMD --view || failed=$(expr $failed + 1)
+COUNT=$($CMD --view | grep ACL: | sed -e 's/^ACL://' | wc -l)
+testit "Verify new ACL count" test $COUNT -eq 1 || failed=$(expr $failed + 1)
+ACL=$($CMD --view | grep ACL: | sed -e 's/^ACL://')
+testit "Verify new ACL" test $ACL = S-1-1-0:ALLOWED/0x0/READ
+
+OWNER=$($CMD --view | grep OWNER:)
+testit "Verify empty OWNER" test "$OWNER" = "OWNER:" ||
+ failed=$(expr $failed + 1)
+GROUP=$($CMD --view | grep GROUP:)
+testit "Verify empty GROUP" test "$GROUP" = "GROUP:" ||
+ failed=$(expr $failed + 1)
+CONTROL=$($CMD --view | grep CONTROL: | sed -e 's/^CONTROL://')
+testit "Verify control flags" test "$CONTROL" = "SR|DP" ||
+ failed=$(expr $failed + 1)
+
+testit "Add second ACL entry" $CMD --add S-1-5-32-544:ALLOWED/0x0/FULL ||
+ failed=$(expr $failed + 1)
+testit "Query ACL with two entries" $CMD --view ||
+ failed=$(expr $failed + 1)
+COUNT=$($CMD --view | grep ACL: | sed -e 's/^ACL://' | wc -l)
+testit "Verify ACL count with two entries" test $COUNT -eq 2 ||
+ failed=$(expr $failed + 1)
+ACL=$($CMD --view | grep S-1-5-32-544 | sed -e 's/^ACL://')
+testit "Verify second ACL entry" test $ACL = S-1-5-32-544:ALLOWED/0x0/FULL ||
+ failed=$(expr $failed + 1)
+
+testit "Modify ACL entry" $CMD --modify S-1-5-32-544:ALLOWED/0x0/CHANGE ||
+ failed=$(expr $failed + 1)
+testit "Verify ACL with two entries after modify" $CMD --view ||
+ failed=$(expr $failed + 1)
+COUNT=$($CMD --view | grep ACL: | sed -e 's/^ACL://' | wc -l)
+testit "Verify ACL count with two entries after modify" test $COUNT -eq 2 ||
+ failed=$(expr $failed + 1)
+ACL=$($CMD --view | grep S-1-5-32-544 | sed -e 's/^ACL://')
+testit "Verify modified entry" test $ACL = S-1-5-32-544:ALLOWED/0x0/CHANGE ||
+ failed=$(expr $failed + 1)
+
+testit "Add deny ACL entry" $CMD --add S-1-5-32-545:DENIED/0x0/CHANGE ||
+ failed=$(expr $failed + 1)
+testit "Query ACL with three entries" $CMD --view ||
+ failed=$(expr $failed + 1)
+COUNT=$($CMD --view | grep ACL: | sed -e 's/^ACL://' | wc -l)
+testit "Verify ACL count with three entries" test $COUNT -eq 3 ||
+ failed=$(expr $failed + 1)
+ACL=$($CMD --view | grep S-1-5-32-545 | sed -e 's/^ACL://')
+testit "Verify DENIED ACL entry" test $ACL = S-1-5-32-545:DENIED/0x0/CHANGE ||
+ failed=$(expr $failed + 1)
+
+testit "Add special ACL entry" $CMD --add S-1-5-32-546:ALLOWED/0x0/RWXDP ||
+ failed=$(expr $failed + 1)
+testit "Query ACL with four entries" $CMD --view ||
+ failed=$(expr $failed + 1)
+COUNT=$($CMD --view | grep ACL: | sed -e 's/^ACL://' | wc -l)
+testit "Verify ACL count with four entries" test $COUNT -eq 4 ||
+ failed=$(expr $failed + 1)
+ACL=$($CMD --view | grep S-1-5-32-546 | sed -e 's/^ACL://')
+testit "Verify special entry" test $ACL = S-1-5-32-546:ALLOWED/0x0/RWXDP ||
+ failed=$(expr $failed + 1)
+
+testit "Remove ACL entry" $CMD --remove S-1-5-32-546:ALLOWED/0x0/RWXDP ||
+ failed=$(expr $failed + 1)
+testit "Query ACL with three entries after removal" $CMD --view ||
+ failed=$(expr $failed + 1)
+COUNT=$($CMD --view | grep ACL: | sed -e 's/^ACL://' | wc -l)
+testit "Verify ACL count after removal" test $COUNT -eq 3 ||
+ failed=$(expr $failed + 1)
+ACL="$($CMD --view | grep S-1-5-32-546)"
+testit "Verify removal" test -e "$ACL" || failed=$(expr $failed + 1)
+
+testit "Set ACL as hex value" $CMD --add S-1-5-32-547:0x1/0x0/0x001F01FF ||
+ failed=$(expr $failed + 1)
+ACL="$($CMD --view | grep S-1-5-32-547 | sed -e 's/^ACL://')"
+testit "Verify numerically set entry" \
+ test "$ACL" = S-1-5-32-547:DENIED/0x0/FULL ||
+ failed=$(expr $failed + 1)
+
+testit "Set ACL as dec value" $CMD --add S-1-5-32-548:1/0/0x001F01FF ||
+ failed=$(expr $failed + 1)
+ACL="$($CMD --view | grep S-1-5-32-548 | sed -e 's/^ACL://')"
+testit "Verify numerically set entry" \
+ test "$ACL" = S-1-5-32-548:DENIED/0x0/FULL ||
+ failed=$(expr $failed + 1)
+
+testit "Set back to default ACL " $CMD --replace S-1-1-0:ALLOWED/0x0/FULL ||
+ failed=$(expr $failed + 1)
+testit "Query standard ACL" $CMD --view ||
+ failed=$(expr $failed + 1)
+COUNT=$($CMD --view | grep ACL: | sed -e 's/^ACL://' | wc -l)
+testit "Verify standard ACL count" test $COUNT -eq 1 ||
+ failed=$(expr $failed + 1)
+ACL=$($CMD --view | grep ACL: | sed -e 's/^ACL://')
+testit "Verify standard ACL" test $ACL = S-1-1-0:ALLOWED/0x0/FULL ||
+ failed=$(expr $failed + 1)
+
+testit "Create new share" $NET_CMD conf addshare tmp_share /tmp ||
+ failed=$(expr $failed + 1)
+testit "Change ACL" $SHARESEC $CONF --replace S-1-1-0:DENIED/0x0/FULL tmp_share ||
+ failed=$(expr $failed + 1)
+testit "Delete share" $NET_CMD conf delshare tmp_share ||
+ failed=$(expr $failed + 1)
+testit "Create share again" $NET_CMD conf addshare tmp_share /tmp ||
+ failed=$(expr $failed + 1)
+ACL=$($SHARESEC $CONF --view tmp_share | grep 'ACL:')
+testit "Check for default ACL" \
+ test "$ACL" = "ACL:S-1-1-0:ALLOWED/0x0/FULL" ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smb1_shadow_copy_torture.sh b/source3/script/tests/test_smb1_shadow_copy_torture.sh
new file mode 100755
index 0000000..3ea3030
--- /dev/null
+++ b/source3/script/tests/test_smb1_shadow_copy_torture.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+#
+# Blackbox test for shadow_copy2 VFS - SMB1 only.
+#
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_shadow_copy SERVER SERVER_IP DOMAIN USERNAME PASSWORD WORKDIR SMBTORTURE
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+DOMAIN=${3}
+USERNAME=${4}
+PASSWORD=${5}
+WORKDIR=${6}
+SMBTORTURE="$VALGRIND ${7}"
+shift 7
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+SNAPSHOT="@GMT-2015.10.31-19.40.30"
+
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+# build a hierarchy of files, symlinks, and directories
+build_files()
+{
+ local destdir
+ destdir=$1
+
+ echo "$content" >$destdir/foo
+}
+
+# build a snapshots directory
+build_snapshots()
+{
+ local snapdir
+
+ snapdir=$WORKDIR/.snapshots
+
+ mkdir -p $snapdir
+ mkdir $snapdir/$SNAPSHOT
+
+ build_files $snapdir/$SNAPSHOT
+}
+
+test_shadow_copy_openroot()
+{
+ local msg
+
+ msg=$1
+
+ #delete snapshots from previous tests
+ find $WORKDIR -name ".snapshots" -exec rm -rf {} \; 1>/dev/null 2>&1
+ build_snapshots
+
+ testit "opening shadow copy root of share over SMB1" \
+ $SMBTORTURE \
+ -U$USERNAME%$PASSWORD \
+ "//$SERVER/shadow_write" \
+ --option="torture:twrp_snapshot=$SNAPSHOT" \
+ base.smb1-twrp-openroot ||
+ failed=$(expr $failed + 1)
+}
+
+build_files $WORKDIR
+
+# test open for writing and write behaviour of snapshoted files
+test_shadow_copy_openroot "opening root of shadow copy share"
+
+exit $failed
diff --git a/source3/script/tests/test_smb1_system_security.sh b/source3/script/tests/test_smb1_system_security.sh
new file mode 100755
index 0000000..dac8897
--- /dev/null
+++ b/source3/script/tests/test_smb1_system_security.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Runs the smbtorture3 SMB1-SYSTEM-SECURITY test
+# that requires SeSecurityPrivilege against Samba.
+#
+
+if [ $# -lt 7 ]; then
+ echo "Usage: $0 SERVER SERVER_IP USERNAME PASSWORD SMBTORTURE3 NET SHARE"
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+SMBTORTURE3="$5"
+NET="$6"
+SHARE="$7"
+
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+smb1_system_security()
+{
+ out=$($SMBTORTURE3 //$SERVER_IP/$SHARE -U $USERNAME%$PASSWORD -mNT1 SMB1-SYSTEM-SECURITY)
+ if [ $? -ne 0 ]; then
+ echo "SMB1-SYSTEM-SECURITY failed"
+ echo "$out"
+ return 1
+ fi
+}
+
+# Grant SeSecurityPrivilege to the user
+testit "grant SeSecurityPrivilege" $NET rpc rights grant $USERNAME SeSecurityPrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || failed=$(expr $failed + 1)
+
+# Run the test.
+testit "smb1-system-security" smb1_system_security || failed=$(expr $failed + 1)
+
+# Revoke SeSecurityPrivilege
+testit "revoke SeSecurityPrivilege" $NET rpc rights revoke $USERNAME SeSecurityPrivilege -U $USERNAME%$PASSWORD -I $SERVER_IP || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_smb2_not_casesensitive.sh b/source3/script/tests/test_smb2_not_casesensitive.sh
new file mode 100755
index 0000000..7e85834
--- /dev/null
+++ b/source3/script/tests/test_smb2_not_casesensitive.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+#
+# Blackbox test for SMB2 case insensitivity
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_smb2_not_casesensitive SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+USERNAME=${3}
+PASSWORD=${4}
+LOCAL_PATH=${5}
+SMBCLIENT=${6}
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# Test a file with different case works over SMB2 and later
+test_access_with_different_case()
+{
+ tmpfile=$LOCAL_PATH/testfile.txt
+ echo "foobar" >$tmpfile
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -mSMB3 -U$USERNAME%$PASSWORD "$SERVER" -I $SERVER_IP -c "ls TeStFiLe.TxT" 2>&1'
+ out=$(eval $cmd)
+ ret=$?
+
+ rm -f $tmpfile
+
+ if [ $ret = 0 ]; then
+ return 0
+ else
+ echo "$out"
+ echo "failed to get file with different case"
+ return 1
+ fi
+}
+
+# Test that a rename causes a conflict works when target name exists in
+# different case
+test_rename()
+{
+ set -x
+ tmpfile=$LOCAL_PATH/torename.txt
+ echo "foobar" >$tmpfile
+ targetfile=$LOCAL_PATH/target.txt
+ touch $targetfile
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -mSMB3 -U$USERNAME%$PASSWORD "$SERVER" -I $SERVER_IP -c "rename ToReNaMe.TxT TaRgEt.txt" 2>&1'
+ out=$(eval $cmd)
+ ret=$?
+
+ rm -f $tmpfile
+ rm -f $targetfile
+ rm -f $LOCAL_PATH/TaRgEt.txt
+
+ if [ $ret = 1 -a -z "${out##*COLLISION*}" ]; then
+ return 0
+ else
+ echo "$out"
+ echo "failed to get file with different case"
+ return 1
+ fi
+}
+
+testit "accessing a file with different case succeeds" \
+ test_access_with_different_case ||
+ failed=$(expr $failed + 1)
+
+testit "renaming a file with different case succeeds" \
+ test_rename ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_smbXsrv_client_cross_node.sh b/source3/script/tests/test_smbXsrv_client_cross_node.sh
new file mode 100755
index 0000000..5b412b2
--- /dev/null
+++ b/source3/script/tests/test_smbXsrv_client_cross_node.sh
@@ -0,0 +1,92 @@
+#!/usr/bin/env bash
+#
+# Test smbd let cluster node 0 destroy the connection,
+# if the client with a specific client-guid connections to node 1
+#
+
+if [ $# -lt 4 ]; then
+ echo Usage: test_smbXsrv_client_cross_node.sh SERVERCONFFILE NODE0 NODE1 SHARENAME
+ exit 1
+fi
+
+CONF=$1
+NODE0=$2
+NODE1=$3
+SHARE=$4
+
+SMBCLIENT="$BINDIR/smbclient"
+SMBSTATUS="$BINDIR/smbstatus"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+test_smbclient()
+{
+ name="$1"
+ server="$2"
+ share="$3"
+ cmd="$4"
+ shift
+ shift
+ subunit_start_test "$name"
+ output=$($VALGRIND $SMBCLIENT //$server/$share -c "$cmd" "$@" 2>&1)
+ status=$?
+ if [ x$status = x0 ]; then
+ subunit_pass_test "$name"
+ else
+ echo "$output" | subunit_fail_test "$name"
+ fi
+ return $status
+}
+
+cd "$SELFTEST_TMPDIR" || exit 1
+
+# Create the smbclient communication pipes.
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+mkfifo smbclient-stdin smbclient-stdout smbclient-stderr
+
+smbstatus_num_sessions()
+{
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$SMBSTATUS" "$CONF" --json | jq -M '.sessions | length'
+}
+
+testit_grep "step1: smbstatus 0 sessions" '^0$' smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+test_smbclient "smbclient against node0[${NODE0}]" "${NODE0}" "${SHARE}" "ls" -U"${DC_USERNAME}"%"${DC_PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ || failed=$(expr $failed + 1)
+
+testit_grep "step2: smbstatus 0 sessions" '^0$' smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+testit "start backgroup smbclient against node0[${NODE0}]" true || failed=$(expr $failed + 1)
+
+# Connect a first time
+${SMBCLIENT} //"${NODE0}"/"${SHARE}" -U"${DC_USERNAME}"%"${DC_PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ <smbclient-stdin >smbclient-stdout 2>smbclient-stderr &
+CLIENT_PID=$!
+
+exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr
+
+testit "sleep 1 second" true || failed=$(expr $failed + 1)
+sleep 1
+
+testit_grep "step3: smbstatus 1 session" '^1$' smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+# Connect a second time
+unset CLI_FORCE_INTERACTIVE
+test_smbclient "smbclient against node1[${NODE1}]" "${NODE1}" "${SHARE}" "ls" -U"${DC_USERNAME}"%"${DC_PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ || failed=$(expr $failed + 1)
+
+kill $CLIENT_PID
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+
+testit_grep "step24: smbstatus 0 sessions" '^0$' smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_smbXsrv_client_ctdb_registered_ips.sh b/source3/script/tests/test_smbXsrv_client_ctdb_registered_ips.sh
new file mode 100755
index 0000000..025a0fa
--- /dev/null
+++ b/source3/script/tests/test_smbXsrv_client_ctdb_registered_ips.sh
@@ -0,0 +1,159 @@
+#!/usr/bin/env bash
+#
+# Test smbd let cleanup registered ip addresses in a multichannel
+# scenario
+#
+
+if [ $# -lt 3 ]; then
+ echo Usage: test_smbXsrv_client_ctdb_registered_ips.sh SERVERCONFFILE CTDB_IFACE_IP SHARENAME
+ exit 1
+fi
+
+CONF=$1
+CTDB_IFACE_IP=$2
+SHARE=$3
+
+SMBCLIENT="$BINDIR/smbclient"
+SMBSTATUS="$BINDIR/smbstatus"
+CTDB="$BINDIR/ctdb"
+TIMELIMIT="$BINDIR/timelimit"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+test_smbclient()
+{
+ name="$1"
+ server="$2"
+ share="$3"
+ cmd="$4"
+ shift
+ shift
+ subunit_start_test "$name"
+ output=$($VALGRIND $SMBCLIENT //$server/$share -c "$cmd" "$@" 2>&1)
+ status=$?
+ if [ x$status = x0 ]; then
+ subunit_pass_test "$name"
+ else
+ echo "$output" | subunit_fail_test "$name"
+ fi
+ return $status
+}
+
+cd "$SELFTEST_TMPDIR" || exit 1
+
+# Create the smbclient communication pipes.
+rm -f smbclient1-stdin smbclient1-stdout smbclient1-stderr
+mkfifo smbclient1-stdin smbclient1-stdout smbclient1-stderr
+rm -f smbclient2-stdin smbclient2-stdout smbclient2-stderr
+mkfifo smbclient2-stdin smbclient2-stdout smbclient2-stderr
+
+smbstatus_num_sessions()
+{
+ # We don't check for died processes
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$SMBSTATUS" "$CONF" --fast --json | jq -M '.sessions | length'
+}
+
+ctdb_add_public_ip()
+{
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$CTDB" addip ${CTDB_IFACE_IP}/24 lo
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$CTDB" ipreallocate
+}
+
+ctdb_ip()
+{
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$CTDB" ip
+}
+
+ctdb_gettickles()
+{
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$CTDB" gettickles ${CTDB_IFACE_IP}
+}
+
+ctdb_reload_public_ips()
+{
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$CTDB" reloadips 0
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$CTDB" ipreallocate
+}
+
+testit_grep_count "step1: smbstatus 0 sessions" '^0$' 1 smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+test_smbclient "step2: smbclient against node0[${CTDB_IFACE_IP}]" "${CTDB_IFACE_IP}" "${SHARE}" "ls" -U"${DC_USERNAME}"%"${DC_PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ || failed=$(expr $failed + 1)
+
+testit_grep_count "step2: smbstatus 0 sessions" '^0$' 1 smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+testit "step3: start backgroup smbclient against node0[${CTDB_IFACE_IP}]" true || failed=$(expr $failed + 1)
+
+# Connect a first time
+${SMBCLIENT} //"${CTDB_IFACE_IP}"/"${SHARE}" -U"${DC_USERNAME}"%"${DC_PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ <smbclient1-stdin >smbclient1-stdout 2>smbclient1-stderr &
+CLIENT1_PID=$!
+
+exec 100>smbclient1-stdin 101<smbclient1-stdout 102<smbclient1-stderr
+
+testit_grep_count "step3: smbclient1-stdout" 'Try "help" to get a list of possible commands.' 1 $TIMELIMIT 15 head -1 smbclient1-stdout || failed=$(expr $failed + 1)
+
+testit_grep_count "step3: smbstatus 1 session" '^1$' 1 smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+testit_grep_count "step3: ctdb_ip" "${CTDB_IFACE_IP}" 0 ctdb_ip || failed=$(expr $failed + 1)
+testit_expect_failure_grep "step3: ctdb_gettickles" "Control GET_TCP_TICKLE_LIST failed" ctdb_gettickles || failed=$(expr $failed + 1)
+
+testit "step4: ctdb_add_public_ip" ctdb_add_public_ip || failed=$(expr $failed + 1)
+
+testit_grep_count "step4: ctdb_ip" "^${CTDB_IFACE_IP} 0\$" 1 ctdb_ip || failed=$(expr $failed + 1)
+testit_grep_count "step4: ctdb_gettickles" "Num connections: 0" 1 ctdb_gettickles || failed=$(expr $failed + 1)
+
+testit "step5: start backgroup 2nd smbclient against node0[${CTDB_IFACE_IP}]" true || failed=$(expr $failed + 1)
+# Connect a second time
+${SMBCLIENT} //"${CTDB_IFACE_IP}"/"${SHARE}" -U"${DC_USERNAME}"%"${DC_PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ <smbclient2-stdin >smbclient2-stdout 2>smbclient2-stderr &
+CLIENT2_PID=$!
+
+exec 200>smbclient2-stdin 201<smbclient2-stdout 202<smbclient2-stderr
+
+testit_grep_count "step5: smbclient2-stdout" 'Try "help" to get a list of possible commands.' 1 $TIMELIMIT 15 head -1 smbclient2-stdout || failed=$(expr $failed + 1)
+
+testit_grep_count "step5: smbstatus 2 session" '^2$' 1 smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+# Only one connection was registered with the public address
+testit_grep_count "step5: ctdb_ip" "^${CTDB_IFACE_IP} 0\$" 1 ctdb_ip || failed=$(expr $failed + 1)
+testit_grep_count "step5: ctdb_gettickles NUM" "Num connections: 1" 1 ctdb_gettickles || failed=$(expr $failed + 1)
+testit_grep_count "step5: ctdb_gettickles DST" "DST: ${CTDB_IFACE_IP}" 1 ctdb_gettickles || failed=$(expr $failed + 1)
+
+unset CLI_FORCE_INTERACTIVE
+
+kill $CLIENT1_PID
+rm -f smbclient1-stdin smbclient1-stdout smbclient1-stderr
+
+testit "step6: sleep 1 second" true || failed=$(expr $failed + 1)
+sleep 1
+
+testit_grep_count "step6: smbstatus 1 session" '^1$' 1 smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+testit_grep_count "step6: ctdb_ip" "^${CTDB_IFACE_IP} 0\$" 1 ctdb_ip || failed=$(expr $failed + 1)
+testit_grep_count "step6: ctdb_gettickles NUM" "Num connections: 1" 1 ctdb_gettickles || failed=$(expr $failed + 1)
+testit_grep_count "step6: ctdb_gettickles DST" "DST: ${CTDB_IFACE_IP}" 1 ctdb_gettickles || failed=$(expr $failed + 1)
+
+testit "step7: ctdb_reload_public_ips" ctdb_reload_public_ips || failed=$(expr $failed + 1)
+
+testit_grep_count "step7: ctdb_ip" "${CTDB_IFACE_IP}" 0 ctdb_ip || failed=$(expr $failed + 1)
+testit_expect_failure_grep "step3: ctdb_gettickles" "Control GET_TCP_TICKLE_LIST failed" ctdb_gettickles || failed=$(expr $failed + 1)
+
+testit "step7: sleep 2 second" true || failed=$(expr $failed + 1)
+sleep 2
+
+testit_grep_count "step7: smbstatus 0 sessions" '^0$' 1 smbstatus_num_sessions || failed=$(expr $failed + 1)
+
+kill $CLIENT2_PID
+rm -f smbclient2-stdin smbclient2-stdout smbclient2-stderr
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_smbXsrv_client_dead_rec.sh b/source3/script/tests/test_smbXsrv_client_dead_rec.sh
new file mode 100755
index 0000000..e9e8f77
--- /dev/null
+++ b/source3/script/tests/test_smbXsrv_client_dead_rec.sh
@@ -0,0 +1,76 @@
+#!/usr/bin/env bash
+#
+# Test smbd doesn't crash if there an existing dead record for a client with a
+# specific client-guid in smbXsrv_client_global.tdb
+#
+
+if [ $# -lt 2 ]; then
+ echo Usage: test_smbXsrv_client_dead_rec.sh SERVERCONFFILE IP SHARENAME
+ exit 1
+fi
+
+CONF=$1
+SERVER=$2
+SHARE=$3
+
+SMBCLIENT="$BINDIR/smbclient"
+SMBSTATUS="$BINDIR/smbstatus"
+
+SMBD_LOG_FILE="$SMBD_TEST_LOG"
+if [ -n "$SMBD_DONT_LOG_STDOUT" ]; then
+ SMBD_LOG_FILE=$(dirname "$SMBD_TEST_LOG")/logs/log.smbd
+fi
+SMBD_LOG_FILE=$(realpath "$SMBD_LOG_FILE")
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+cd "$SELFTEST_TMPDIR" || exit 1
+
+#
+# Note if we already have any panics in the smbd log.
+#
+panic_count_0=$(grep -c PANIC "$SMBD_LOG_FILE")
+
+# Create the smbclient communication pipes.
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+mkfifo smbclient-stdin smbclient-stdout smbclient-stderr
+
+CLI_FORCE_INTERACTIVE=1
+export CLI_FORCE_INTERACTIVE
+
+# Connect a first time
+${SMBCLIENT} //"${SERVER}"/"${SHARE}" -U"${USER}"%"${PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ <smbclient-stdin >smbclient-stdout 2>smbclient-stderr &
+CLIENT_PID=$!
+
+exec 100>smbclient-stdin 101<smbclient-stdout 102<smbclient-stderr
+
+SMBD_PID=$(UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 "$SMBSTATUS" -p "$CONF" | awk '/^[0-9]+/ {print $1}' | sort -u)
+
+# Kill the first connection, leaves dead record in smbXsrv_client_global.tdb
+kill -KILL "$SMBD_PID"
+kill $CLIENT_PID
+
+# Connect a second time
+unset CLI_FORCE_INTERACTIVE
+${SMBCLIENT} //"${SERVER}"/"${SHARE}" -U"${USER}"%"${PASSWORD}" \
+ --option="libsmb:client_guid=6112f7d3-9528-4a2a-8861-0ca129aae6c4" \
+ -c exit
+
+rm -f smbclient-stdin smbclient-stdout smbclient-stderr
+
+#
+# Ensure the panic count didn't change.
+#
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14882
+#
+panic_count_1=$(grep -c PANIC "$SMBD_LOG_FILE")
+
+testit "check_panic" test "$panic_count_0" -eq "$panic_count_1" ||
+ failed=$(expr $failed + 1)
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_smbclient_auth.sh b/source3/script/tests/test_smbclient_auth.sh
new file mode 100755
index 0000000..c6dcfc3
--- /dev/null
+++ b/source3/script/tests/test_smbclient_auth.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# this runs the file serving tests that are expected to pass with samba3 against shares with various options
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_smbclient_auth.sh SERVER SERVER_IP USERNAME PASSWORD SMBCLIENT <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+SMBCLIENT="$5"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 5
+ADDARGS="$*"
+
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+echo "${SERVER_IP}" | grep -q ':.*:' && {
+ # If we have an ipv6 address e.g.
+ # fd00:0000:0000:0000:0000:0000:5357:5f03
+ # we also try
+ # fd00-0000-0000-0000-0000-0000-5357-5f03.ipv6-literal.net
+ IPV6LITERAL=$(echo "${SERVER_IP}.ipv6-literal.net" | sed -e 's!:!-!g' -e 's!%!s!')
+ testit "smbclient //${IPV6LITERAL}/tmpguest as user" $SMBCLIENT //${IPV6LITERAL}/tmpguest $CONFIGURATION -U$USERNAME%$PASSWORD -c quit $ADDARGS || failed=$(expr $failed + 1)
+ testit "smbclient //${IPV6LITERAL}./tmpguest as user" $SMBCLIENT //${IPV6LITERAL}./tmpguest $CONFIGURATION -U$USERNAME%$PASSWORD -c quit $ADDARGS || failed=$(expr $failed + 1)
+}
+testit "smbclient //${SERVER_IP}/tmpguest as user" $SMBCLIENT //${SERVER_IP}/tmpguest $CONFIGURATION -U$USERNAME%$PASSWORD -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+
+testit "smbclient //$SERVER/guestonly as user" $SMBCLIENT //$SERVER/guestonly $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/guestonly as anon" $SMBCLIENT //$SERVER/guestonly $CONFIGURATION -U% -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/tmpguest as user" $SMBCLIENT //$SERVER/tmpguest $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/tmpguest as anon" $SMBCLIENT //$SERVER/tmpguest $CONFIGURATION -U% -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/forceuser as user" $SMBCLIENT //$SERVER/forceuser $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/forceuser as anon" $SMBCLIENT //$SERVER/forceuser $CONFIGURATION -U% -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/forceuser_unixonly as user" $SMBCLIENT //$SERVER/forceuser_unixonly $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/forceuser_wkngroup as user" $SMBCLIENT //$SERVER/forceuser_wkngroup $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/forcegroup as user" $SMBCLIENT //$SERVER/forcegroup $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/forcegroup as anon" $SMBCLIENT //$SERVER/forcegroup $CONFIGURATION -U% -I $SERVER_IP -p 139 -c quit $ADDARGS || failed=$(expr $failed + 1)
+exit $failed
diff --git a/source3/script/tests/test_smbclient_basic.sh b/source3/script/tests/test_smbclient_basic.sh
new file mode 100755
index 0000000..6e2a17c
--- /dev/null
+++ b/source3/script/tests/test_smbclient_basic.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+# this runs the file serving tests that are expected to pass with samba3 against shares with various options
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_smbclient_basic.sh SERVER SERVER_IP DOMAIN USERNAME PASSWORD SMBCLIENT <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+smbclient="$5"
+CONFIGURATION="$6"
+shift 6
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+# TEST using \ as the separator (default)
+test_smbclient "smbclient as $DOMAIN\\$USERNAME" 'ls' "//$SERVER/tmp" -U$DOMAIN\\$USERNAME%$PASSWORD $ADDARGS || failed=$(expr $failed + 1)
+# TEST using / as the separator (default)
+test_smbclient "smbclient as $DOMAIN/$USERNAME" 'ls' "//$SERVER/tmp" -U$DOMAIN/$USERNAME%$PASSWORD $ADDARGS || failed=$(expr $failed + 1)
+
+# TEST using 'winbind separator = +'
+test_smbclient "smbclient as $DOMAIN+$USERNAME" 'ls' "//$SERVER/tmp" -U$DOMAIN+$USERNAME%$PASSWORD $ADDARGS --option=winbindseparator=+ || failed=$(expr $failed + 1)
+
+# TEST using 'winbind separator = +' set in a config file
+smbclient_config="$PREFIX/tmpsmbconf"
+cat >$smbclient_config <<EOF
+[global]
+ include = $(echo $CONFIGURATION | cut -d= -f2)
+ winbind separator = +
+EOF
+
+SAVE_CONFIGURATION="$CONFIGURATION"
+CONFIGURATION="--configfile=$smbclient_config"
+test_smbclient "smbclient as $DOMAIN+$USERNAME" 'ls' "//$SERVER/tmp" -U$DOMAIN+$USERNAME%$PASSWORD $ADDARGS || failed=$(expr $failed + 1)
+CONFIGURATION="$SAVE_CONFIGURATION"
+rm -rf $smbclient_config
+
+exit $failed
diff --git a/source3/script/tests/test_smbclient_encryption.sh b/source3/script/tests/test_smbclient_encryption.sh
new file mode 100755
index 0000000..2cbf5f0
--- /dev/null
+++ b/source3/script/tests/test_smbclient_encryption.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_smbclient_encryption.sh USERNAME PASSWORD SERVER SMBCLIENT TARGET
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+SERVER="$3"
+SMBCLIENT="$VALGRIND $4"
+TARGET="$5"
+shift 5
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+#
+# Server configuration for fileserver:
+#
+# global: 'server smb encrypt = default'
+# enc_desired: 'server smb encrypt = desired'
+# tmpenc: 'server smb encrypt = required'
+# tmp: has the global default 'server smb encrypt'
+#
+# Server configuration for simpleserver:
+#
+# global: 'server smb encrypt = off'
+# enc_desired: 'server smb encrypt = desired'
+# tmpenc: 'server smb encrypt = required'
+# tmp: has the global default 'server smb encrypt'
+#
+
+testit "smbclient.smb3.client.encrypt.desired[//$SERVER/enc_desired]" $SMBCLIENT //$SERVER/enc_desired -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=desired -c 'ls; quit' || failed=$(expr $failed + 1)
+if [ "$TARGET" = "fileserver" ]; then
+ testit "smbclient.smb3.client.encrypt.desired[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=desired -c 'ls; quit' || failed=$(expr $failed + 1)
+elif [ "$TARGET" = "simpleserver" ]; then # Encryption is globally disabled
+ testit_expect_failure "smbclient.smb3.client.encrypt.desired[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=desired -c 'ls; quit' || failed=$(expr $failed + 1)
+fi
+testit "smbclient.smb3.client.encrypt.desired[//$SERVER/tmp]" $SMBCLIENT //$SERVER/tmp -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=desired -c 'ls; quit' || failed=$(expr $failed + 1)
+
+testit "smbclient.smb3.client.encrypt.if_required[//$SERVER/enc_desired]" $SMBCLIENT //$SERVER/enc_desired -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=if_required -c 'ls; quit' || failed=$(expr $failed + 1)
+if [ "$TARGET" = "fileserver" ]; then
+ testit "smbclient.smb3.client.encrypt.if_required[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=if_required -c 'ls; quit' || failed=$(expr $failed + 1)
+elif [ "$TARGET" = "simpleserver" ]; then # Encryption is globally disabled
+ testit_expect_failure "smbclient.smb3.client.encrypt.if_required[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=if_required -c 'ls; quit' || failed=$(expr $failed + 1)
+fi
+testit "smbclient.smb3.client.encrypt.if_required[//$SERVER/tmp]" $SMBCLIENT //$SERVER/tmp -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=if_required -c 'ls; quit' || failed=$(expr $failed + 1)
+
+if [ "$TARGET" = "fileserver" ]; then
+ testit "smbclient.smb3.client.encrypt.required[//$SERVER/enc_desired]" $SMBCLIENT //$SERVER/enc_desired -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=required -c 'ls; quit' || failed=$(expr $failed + 1)
+ testit "smbclient.smb3.client.encrypt.required[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=required -c 'ls; quit' || failed=$(expr $failed + 1)
+ testit "smbclient.smb3.client.encrypt.required[//$SERVER/tmp]" $SMBCLIENT //$SERVER/tmp -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=required -c 'ls; quit' || failed=$(expr $failed + 1)
+elif [ "$TARGET" = "simpleserver" ]; then # Encryption is globally disabled
+ testit_expect_failure "smbclient.smb3.client.encrypt.required[//$SERVER/enc_desired]" $SMBCLIENT //$SERVER/enc_desired -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=required -c 'ls; quit' || failed=$(expr $failed + 1)
+ testit_expect_failure "smbclient.smb3.client.encrypt.required[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=required -c 'ls; quit' || failed=$(expr $failed + 1)
+ testit_expect_failure "smbclient.smb3.client.encrypt.required[//$SERVER/tmp]" $SMBCLIENT //$SERVER/tmp -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=required -c 'ls; quit' || failed=$(expr $failed + 1)
+fi
+
+testit "smbclient.smb3.client.encrypt.off[//$SERVER/enc_desired]" $SMBCLIENT //$SERVER/enc_desired -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=off -c 'ls; quit' || failed=$(expr $failed + 1)
+if [ "$TARGET" = "fileserver" ]; then
+ testit "smbclient.smb3.client.encrypt.off[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=off -c 'ls; quit' || failed=$(expr $failed + 1)
+elif [ "$TARGET" = "simpleserver" ]; then # Encryption is globally disabled
+ testit_expect_failure "smbclient.smb3.client.encrypt.off[//$SERVER/tmpenc]" $SMBCLIENT //$SERVER/tmpenc -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=off -c 'ls; quit' || failed=$(expr $failed + 1)
+fi
+testit "smbclient.smb3.client.encrypt.off[//$SERVER/tmp]" $SMBCLIENT //$SERVER/tmp -U$USERNAME%$PASSWORD -mSMB3 --option=clientsmbencrypt=off -c 'ls; quit' || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_encryption_off.sh b/source3/script/tests/test_smbclient_encryption_off.sh
new file mode 100755
index 0000000..4bd99a0
--- /dev/null
+++ b/source3/script/tests/test_smbclient_encryption_off.sh
@@ -0,0 +1,65 @@
+#!/bin/sh
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_smbclient_encryption_off.sh USERNAME PASSWORD SERVER SMBCLIENT
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+PASSWORD="$2"
+SERVER="$3"
+SMBCLIENT="$VALGRIND $4"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+#
+# Let me introduce you to the shares used in this test:
+#
+# "tmp" has the default "smb encrypt" (which is "enabled")
+# "tmpenc" has "smb encrypt = required"
+# "enc_desired" has "smb encrypt = desired"
+#
+
+# Unencrypted connections should work of course, let's test em to be sure...
+
+# SMB1
+testit "smbclient //$SERVER/enc_desired" $SMBCLIENT -U $USERNAME%$PASSWORD //$SERVER/enc_desired -c quit || failed=$(expr $failed + 1)
+testit "smbclient //$SERVER/tmp" $SMBCLIENT -U $USERNAME%$PASSWORD //$SERVER/tmp -c quit || failed=$(expr $failed + 1)
+# SMB3_02
+testit "smbclient -m smb3_02 //$SERVER/enc_desired" $SMBCLIENT -m smb3_02 -U $USERNAME%$PASSWORD //$SERVER/enc_desired -c quit || failed=$(expr $failed + 1)
+testit "smbclient -m smb3_02 //$SERVER/tmp" $SMBCLIENT -m smb3_02 -U $USERNAME%$PASSWORD //$SERVER/tmp -c quit || failed=$(expr $failed + 1)
+# SMB3_11
+testit "smbclient -m smb3_11 //$SERVER/enc_desired" $SMBCLIENT -m smb3_11 -U $USERNAME%$PASSWORD //$SERVER/enc_desired -c quit || failed=$(expr $failed + 1)
+testit "smbclient -m smb3_11 //$SERVER/tmp" $SMBCLIENT -m smb3_11 -U $USERNAME%$PASSWORD //$SERVER/tmp -c quit || failed=$(expr $failed + 1)
+
+# These tests must fail, as encryption is globally off and in combination with "smb
+# encrypt=required" on the share "tmpenc" the server *must* reject the tcon.
+
+# SMB1
+testit_expect_failure "smbclient //$SERVER/tmpenc" $SMBCLIENT -U $USERNAME%$PASSWORD //$SERVER/tmpenc -c quit || failed=$(expr $failed + 1)
+testit_expect_failure "smbclient --client-protection=encrypt //$SERVER/tmpenc" $SMBCLIENT --client-protection=encrypt -U $USERNAME%$PASSWORD //$SERVER/tmpenc -c quit || failed=$(expr $failed + 1)
+# SMB3_02
+testit_expect_failure "smbclient -m smb3_02 //$SERVER/tmpenc" $SMBCLIENT -m smb3_02 -U $USERNAME%$PASSWORD //$SERVER/tmpenc -c quit || failed=$(expr $failed + 1)
+testit_expect_failure "smbclient --client-protection=encrypt -m smb3_02 //$SERVER/tmpenc" $SMBCLIENT --client-protection=encrypt -m smb3_02 -U $USERNAME%$PASSWORD //$SERVER/tmpenc -c quit || failed=$(expr $failed + 1)
+# SMB3_11
+testit_expect_failure "smbclient -m smb3_11 //$SERVER/tmpenc" $SMBCLIENT -m smb3_11 -U $USERNAME%$PASSWORD //$SERVER/tmpenc -c quit || failed=$(expr $failed + 1)
+testit_expect_failure "smbclient --client-protection=encrypt -m smb3_11 //$SERVER/tmpenc" $SMBCLIENT --client-protection=encrypt -m smb3_11 -U $USERNAME%$PASSWORD //$SERVER/tmpenc -c quit || failed=$(expr $failed + 1)
+
+# These tests must fail, as the client requires encryption and it's off on the server
+
+# SMB1
+testit_expect_failure "smbclient --client-protection=encrypt //$SERVER/enc_desired" $SMBCLIENT --client-protection=encrypt -U $USERNAME%$PASSWORD //$SERVER/enc_desired -c quit || failed=$(expr $failed + 1)
+testit_expect_failure "smbclient --client-protection=encrypt //$SERVER/tmp" $SMBCLIENT --client-protection=encrypt -U $USERNAME%$PASSWORD //$SERVER/tmp -c quit || failed=$(expr $failed + 1)
+# SMB3_02
+testit_expect_failure "smbclient --client-protection=encrypt -m smb3_02 //$SERVER/enc_desired" $SMBCLIENT --client-protection=encrypt -m smb3_02 -U $USERNAME%$PASSWORD //$SERVER/enc_desired -c quit || failed=$(expr $failed + 1)
+testit_expect_failure "smbclient --client-protection=encrypt -m smb3_02 //$SERVER/tmp" $SMBCLIENT --client-protection=encrypt -m smb3_02 -U $USERNAME%$PASSWORD //$SERVER/tmp -c quit || failed=$(expr $failed + 1)
+# SMB3_11
+testit_expect_failure "smbclient --client-protection=encrypt -m smb3_11 //$SERVER/enc_desired" $SMBCLIENT --client-protection=encrypt -m smb3_11 -U $USERNAME%$PASSWORD //$SERVER/enc_desired -c quit || failed=$(expr $failed + 1)
+testit_expect_failure "smbclient --client-protection=encrypt -m smb3_11 //$SERVER/tmp" $SMBCLIENT --client-protection=encrypt -m smb3_11 -U $USERNAME%$PASSWORD //$SERVER/tmp -c quit || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_iconv.sh b/source3/script/tests/test_smbclient_iconv.sh
new file mode 100755
index 0000000..e6cdc5e
--- /dev/null
+++ b/source3/script/tests/test_smbclient_iconv.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# This checks directory listing with a file containing
+# an invalid CP850 conversion name returns NT_STATUS_INVALID_NETWORK_RESPONSE
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_smbclient_iconv.sh SERVER SERVER_IP SHARENAME USERNAME PASSWORD SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+SHARENAME="$3"
+USERNAME="$4"
+PASSWORD="$5"
+SMBCLIENT="$6"
+shift 6
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_smbclient_iconv()
+{
+ normal_smbclient_config="$PREFIX/client/client.conf"
+ smbclient_config="$PREFIX/client/client_cp850_smbconf"
+ cat >$smbclient_config <<EOF
+[global]
+ include = $normal_smbclient_config
+ unix charset = cp850
+ client min protocol = core
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/$SHARENAME --configfile=$smbclient_config "$ADDARGS" -c ls 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ rm -f $smbclient_config
+
+ echo "$out" | grep 'NT_STATUS_INVALID_NETWORK_RESPONSE'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo 'failed - should get: NT_STATUS_INVALID_NETWORK_RESPONSE.'
+ return 1
+ fi
+
+ return 0
+}
+
+testit "bad_iconv smbclient" test_smbclient_iconv || failed=$(expr $failed + 1)
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_kerberos.sh b/source3/script/tests/test_smbclient_kerberos.sh
new file mode 100755
index 0000000..31678d1
--- /dev/null
+++ b/source3/script/tests/test_smbclient_kerberos.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_smbclient_kerberos.sh USERNAME REALM PASSWORD SERVER SMBCLIENT TARGET
+EOF
+ exit 1
+fi
+
+USERNAME="$1"
+REALM=$2
+PASSWORD="$3"
+SERVER="$4"
+smbclient="$5"
+TARGET="$6"
+shift 6
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. ${incdir}/subunit.sh
+. ${incdir}/common_test_fns.inc
+
+failed=0
+
+samba_kinit=kinit
+if test -x ${BINDIR}/samba4kinit; then
+ samba_kinit=${BINDIR}/samba4kinit
+fi
+
+samba_kdestroy=kdestroy
+if test -x ${BINDIR}/samba4kdestroy; then
+ samba_kdestroy=${BINDIR}/samba4kdestroy
+fi
+
+KRB5CCNAME_PATH="${PREFIX}/ccache_smbclient_kerberos"
+KRB5CCNAME="FILE:${KRB5CCNAME_PATH}"
+export KRB5CCNAME
+
+# For ad_dc_fips this should succeed as Kerberos is set to required by default
+test_smbclient "smbclient.smb3.kerberos[//${SERVER}/tmp]" \
+ "ls; quit" //${SERVER}/tmp \
+ -U${USERNAME}%${PASSWORD} -mSMB3 ||
+ failed=$(expr $failed + 1)
+
+test_smbclient "smbclient.smb3.kerberos.required[//${SERVER}/tmp]" \
+ "ls; quit" //${SERVER}/tmp \
+ --use-kerberos=required -U${USERNAME}%${PASSWORD} -mSMB3 ||
+ failed=$(expr $failed + 1)
+
+test_smbclient "smbclient.smb3.kerberos.desired[//${SERVER}/tmp]" \
+ "ls; quit" //${SERVER}/tmp \
+ --use-kerberos=desired -U${USERNAME}%${PASSWORD} -mSMB3 ||
+ failed=$(expr $failed + 1)
+
+if [ "$TARGET" = "ad_dc_fips" ] || [ "$TARGET" = "ad_member_fips" ]; then
+ test_smbclient_expect_failure "smbclient.smb3.kerberos.off[//${SERVER}/tmp]" \
+ "ls; quit" //${SERVER}/tmp \
+ --use-kerberos=off -U${USERNAME}%${PASSWORD} -mSMB3 ||
+ failed=$(expr $failed + 1)
+else
+ test_smbclient "smbclient.smb3.kerberos.off[//${SERVER}/tmp]" \
+ "ls; quit" //${SERVER}/tmp \
+ --use-kerberos=off -U${USERNAME}%${PASSWORD} -mSMB3 ||
+ failed=$(expr $failed + 1)
+fi
+
+kerberos_kinit $samba_kinit ${USERNAME}@${REALM} ${PASSWORD}
+test_smbclient "smbclient.smb3.kerberos.ccache[//${SERVER}/tmp]" \
+ "ls; quit" //${SERVER}/tmp \
+ --use-krb5-ccache=${KRB5CCNAME} -mSMB3 ||
+ failed=$(expr $failed + 1)
+test_smbclient "smbclient.smb3.kerberos.desired[//${SERVER}/tmp]" \
+ "ls; quit" //${SERVER}/tmp \
+ --use-kerberos=desired -U${USERNAME}%${PASSWORD} -mSMB3 ||
+ failed=$(expr $failed + 1)
+
+$samba_kdestroy
+
+rm -rf $KRB5CCNAME_PATH
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_smbclient_krb5.sh b/source3/script/tests/test_smbclient_krb5.sh
new file mode 100755
index 0000000..98fa789
--- /dev/null
+++ b/source3/script/tests/test_smbclient_krb5.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+if [ $# -lt 1 ]; then
+ cat <<EOF
+Usage: test_smbclient_krb5.sh ccache smbclient3 server <smbclient args>
+EOF
+ exit 1
+fi
+
+KRB5CCNAME=$1
+export KRB5CCNAME
+SMBCLIENT3=$2
+SERVER=$3
+shift 3
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+testit "smbclient" $VALGRIND $SMBCLIENT3 //$SERVER/tmp -c 'ls' --use-krb5-ccache=$KRB5CCNAME $ADDARGS || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_large_file.sh b/source3/script/tests/test_smbclient_large_file.sh
new file mode 100755
index 0000000..80816be
--- /dev/null
+++ b/source3/script/tests/test_smbclient_large_file.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+if [ $# -lt 1 ]; then
+ cat <<EOF
+Usage: test_smbclient_large_file.sh ccache smbclient3 server prefix <smbclient args>
+EOF
+ exit 1
+fi
+
+KRB5CCNAME=$1
+export KRB5CCNAME
+SMBCLIENT3=$2
+SERVER=$3
+PREFIX=$4
+shift 4
+ADDARGS="$*"
+
+# Test that a noninteractive smbclient does not prompt
+test_large_write_read()
+{
+
+ cat >$PREFIX/largefile-script <<EOF
+posix
+put $PREFIX/largefile largefile
+get largefile $PREFIX/largefile2
+rm largefile
+quit
+EOF
+
+ cmd='$SMBCLIENT3 //$SERVER/xcopy_share $ADDARGS < $PREFIX/largefile-script 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+
+ if [ $? != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ false
+ return
+ fi
+
+ echo "$out" | grep "getting file" >/dev/null 2>&1
+
+ if [ $? = 0 ]; then
+ true
+ else
+ echo did not get success message
+ false
+ fi
+}
+
+rm -f $PREFIX/largefile
+dd if=/dev/zero of=$PREFIX/largefile seek=$((20 * 1024 * 1024)) count=1 bs=1
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+testit "smbclient large posix write read" test_large_write_read || failed=$(expr $failed + 1)
+
+testit "cmp of read and written files" cmp $PREFIX/largefile $PREFIX/largefile2 || failed=$(expr $failed + 1)
+rm -f $PREFIX/largefile2
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_list_servers.sh b/source3/script/tests/test_smbclient_list_servers.sh
new file mode 100755
index 0000000..237e82f
--- /dev/null
+++ b/source3/script/tests/test_smbclient_list_servers.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+#
+# Ensure we don't get an error smb1cli_req_writev_submit: called for dialect[SMB3_11]
+# when listing servers via -L.
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14939
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_smbclient_list_servers.sh SERVER SERVER_IP USERNAME PASSWORD SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+SMBCLIENT="$5"
+shift 5
+ADDARGS="$@"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir/subunit.sh"
+
+failed=0
+
+test_smbclient_list_servers()
+{
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -L //$SERVER -U$USERNAME%$PASSWORD -I $SERVER_IP -p139 "$ADDARGS" </dev/null 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+
+ echo "$out" | grep 'smb1cli_req_writev_submit:'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo 'failed - should not get: smb1cli_req_writev_submit: error.'
+ return 1
+ fi
+
+ return 0
+}
+
+testit "smb1_list_servers" test_smbclient_list_servers || failed=$((failed + 1))
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_smbclient_log_basename.sh b/source3/script/tests/test_smbclient_log_basename.sh
new file mode 100755
index 0000000..f29009f
--- /dev/null
+++ b/source3/script/tests/test_smbclient_log_basename.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# this test checks whether smbclient can log into -l log-basename
+
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: test_smbclient_log_basename.sh SERVER SMBCLIENT PREFIX <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SMBCLIENT="$2"
+PREFIX="$3"
+shift 3
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+LOG_DIR=$PREFIX/st_log_basename_dir
+
+test_smbclient_log_basename()
+{
+ rm -rf $LOG_DIR
+ mkdir $LOG_DIR
+ cmd='$VALGRIND $SMBCLIENT -l $LOG_DIR -d3 //$SERVER/IPC\$ $CONFIGURATION -U%badpassword -c quit $ADDARGS'
+ out=$(eval $cmd 2>&1)
+ grep 'Client started' $LOG_DIR/log.smbclient
+}
+
+testit "smbclient log-basename" test_smbclient_log_basename || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_machine_auth.sh b/source3/script/tests/test_smbclient_machine_auth.sh
new file mode 100755
index 0000000..c89f848
--- /dev/null
+++ b/source3/script/tests/test_smbclient_machine_auth.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+# this runs the file serving tests that are expected to pass with samba3 against shares with various options
+
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: test_smbclient_machine_auth.sh SERVER SMBCLIENT CONFIGURATION <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SMBCLIENT="$2"
+# This is used by test_smbclient()
+# shellcheck disable=2034
+CONFIGURATION="${3}"
+shift 3
+ADDARGS="$*"
+
+# This is used by test_smbclient()
+# shellcheck disable=2034
+smbclient="${VALGRIND} ${SMBCLIENT}"
+
+incdir="$(dirname "${0}")/../../../testprogs/blackbox"
+. "${incdir}/subunit.sh"
+. "${incdir}/common_test_fns.inc"
+
+failed=0
+
+test_smbclient "smbclient //${SERVER}/tmp" \
+ "quit" "//${SERVER}/tmp" --machine-pass -p 139 "${ADDARGS}" || \
+ failed=$((failed + 1))
+
+# Testing these here helps because we know the machine account isn't already
+# this user/group.
+test_smbclient "smbclient //${SERVER}/forceuser" \
+ "quit" "//${SERVER}/forceuser" --machine-pass -p 139 "${ADDARGS}" || \
+ failed=$((failed + 1))
+
+test_smbclient "smbclient //${SERVER}/forcegroup" \
+ "quit" "//${SERVER}/forcegroup" --machine-pass -p 139 "${ADDARGS}" || \
+ failed=$((failed + 1))
+
+exit ${failed}
diff --git a/source3/script/tests/test_smbclient_mget.sh b/source3/script/tests/test_smbclient_mget.sh
new file mode 100755
index 0000000..5275d50
--- /dev/null
+++ b/source3/script/tests/test_smbclient_mget.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 smbclient3 server share user password directory
+EOF
+ exit 1
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+SMBCLIENT3="$1"
+shift
+SERVER="$1"
+shift
+SHARE="$1"
+shift
+USERNAME="$1"
+shift
+PASSWORD="$1"
+shift
+DIRECTORY="$1"
+shift
+
+cd $SELFTEST_TMPDIR || exit 1
+
+# Can't use "testit" here -- it somehow breaks the -c command passed
+# to smbclient into two, spoiling the "mget"
+
+name="smbclient mget"
+subunit_start_test "$name"
+output=$("$SMBCLIENT3" //"$SERVER"/"$SHARE" \
+ -U"$USERNAME"%"$PASSWORD" -c "recurse;prompt;mget $DIRECTORY")
+status=$?
+if [ x$status = x0 ]; then
+ subunit_pass_test "$name"
+else
+ echo "$output" | subunit_fail_test "$name"
+fi
+
+testit "rm foo" rm "$DIRECTORY"/foo || failed=$(expr $failed + 1)
+testit "rmdir $DIRECTORY" rmdir "$DIRECTORY" || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_netbios_aliases.sh b/source3/script/tests/test_smbclient_netbios_aliases.sh
new file mode 100755
index 0000000..e48b46b
--- /dev/null
+++ b/source3/script/tests/test_smbclient_netbios_aliases.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_smbclient_netbios_aliases.sh smbclient3 SERVER USERNAME PASSWORD PREFIX CONFIGURATION
+EOF
+ exit 1
+fi
+
+smbclient=$1
+SERVER=$2
+USERNAME=$3
+PASSWORD=$4
+PREFIX=$5
+CONFIGURATION=$6
+shift 6
+ADDADS="$@"
+
+samba_bindir="$BINDIR"
+samba_srcdir="$SRCDIR/source4"
+samba_kinit=kinit
+if test -x ${samba_bindir}/samba4kinit; then
+ samba_kinit=${samba_bindir}/samba4kinit
+fi
+
+KRB5CCNAME_PATH="$PREFIX/test_smbclient_netbios_aliases_krb5ccache"
+rm -rf $KRB5CCNAME_PATH
+
+KRB5CCNAME="FILE:$KRB5CCNAME_PATH"
+export KRB5CCNAME
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+testit "kinit" kerberos_kinit ${samba_kinit} ${USERNAME} ${PASSWORD}
+
+test_smbclient "smbclient (krb5)" "ls" "//$SERVER/tmp" --use-krb5-ccache=$KRB5CCNAME || failed=$(expr $failed + 1)
+
+rm -rf $KRB5CCNAME_PATH
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_ntlm.sh b/source3/script/tests/test_smbclient_ntlm.sh
new file mode 100755
index 0000000..1e53b1e
--- /dev/null
+++ b/source3/script/tests/test_smbclient_ntlm.sh
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+# this runs a smbclient based authentication tests
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_smbclient_ntlm.sh SERVER USERNAME PASSWORD MAPTOGUEST SMBCLIENT PROTOCOL CONFIGURATION <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+USERNAME="$2"
+PASSWORD="$3"
+MAPTOGUEST="$4"
+SMBCLIENT="$5"
+PROTOCOL="$6"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+CONFIGURATION=${7}
+shift 7
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+if [ $PROTOCOL != "SMB3" -a $PROTOCOL != "NT1" ]; then
+ cat <<EOF
+Uexpected protocol specified $PROTOCOL
+EOF
+ exit 1
+fi
+
+failed=0
+
+if [ $PROTOCOL = "NT1" ]; then
+ testit "smbclient username.password.NT1OLD" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U$USERNAME%$PASSWORD -mNT1 --option=clientusespnego=no --option=clientntlmv2auth=no -c quit $ADDARGS || failed=$((failed + 1))
+ testit "smbclient username.password.NT1NEW" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U$USERNAME%$PASSWORD -mNT1 -c quit $ADDARGS || failed=$((failed + 1))
+fi
+if [ $PROTOCOL = "SMB3" ]; then
+ testit "smbclient username.password.SMB3" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U$USERNAME%$PASSWORD -mSMB3 -c quit $ADDARGS || failed=$((failed + 1))
+fi
+
+if [ $PROTOCOL = "NT1" ]; then
+ testit "smbclient anonymous.nopassword.NT1OLD" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U% -mNT1 --option=clientusespnego=no --option=clientntlmv2auth=no -c quit $ADDARGS || failed=$((failed + 1))
+ testit "smbclient anonymous.nopassword.NT1NEW" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U% -mNT1 -c quit $ADDARGS || failed=$((failed + 1))
+fi
+if [ $PROTOCOL = "SMB3" ]; then
+ testit "smbclient anonymous.nopassword.SMB3" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U% -mSMB3 -c quit $ADDARGS || failed=$((failed + 1))
+fi
+if test x"${MAPTOGUEST}" = x"never"; then
+ if [ $PROTOCOL = "NT1" ]; then
+ testit_expect_failure "smbclient anonymous.badpassword.NT1NEW.fail" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U%badpassword -mNT1 -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+ if [ $PROTOCOL = "SMB3" ]; then
+ testit_expect_failure "smbclient anonymous.badpassword.SMB3.fail" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U%badpassword -mSMB3 -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+else
+ if [ $PROTOCOL = "NT1" ]; then
+ testit "smbclient anonymous.badpassword.NT1NEW.guest" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U%badpassword -mNT1 -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+ if [ $PROTOCOL = "SMB3" ]; then
+ testit "smbclient anonymous.badpassword.SMB3.guest" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -U%badpassword -mSMB3 -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+
+ if [ $PROTOCOL = "NT1" ]; then
+ testit "smbclient baduser.badpassword.NT1NEW.guest" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -Ubaduser%badpassword -mNT1 -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+ if [ $PROTOCOL = "SMB3" ]; then
+ testit "smbclient baduser.badpassword.SMB3.guest" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -Ubaduser%badpassword -mSMB3 -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+ if [ $PROTOCOL = "NT1" ]; then
+ testit_expect_failure "smbclient baduser.badpassword.NT1OLD.signfail" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -Ubaduser%badpassword -mNT1 --option=clientusespnego=no --option=clientntlmv2auth=no --client-protection=sign -c quit $ADDARGS || failed=$((failed + 1))
+ testit_expect_failure "smbclient baduser.badpassword.NT1NEW.signfail" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -Ubaduser%badpassword -mNT1 --client-protection=sign -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+ if [ $PROTOCOL = "SMB3" ]; then
+ testit_expect_failure "smbclient baduser.badpassword.SMB3.signfail" $SMBCLIENT //$SERVER/IPC\$ $CONFIGURATION -Ubaduser%badpassword -mSMB3 --client-protection=sign -c quit $ADDARGS || failed=$((failed + 1))
+ fi
+fi
+
+exit ${failed}
diff --git a/source3/script/tests/test_smbclient_s3.sh b/source3/script/tests/test_smbclient_s3.sh
new file mode 100755
index 0000000..cbff502
--- /dev/null
+++ b/source3/script/tests/test_smbclient_s3.sh
@@ -0,0 +1,2364 @@
+#!/bin/sh
+
+# this runs the file serving tests that are expected to pass with samba3
+
+if [ $# -lt 13 ]; then
+ cat <<EOF
+Usage: test_smbclient_s3.sh SERVER SERVER_IP DOMAIN USERNAME PASSWORD USERID LOCAL_PATH PREFIX SMBCLIENT WBINFO NET CONFIGURATION PROTOCOL
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+DOMAIN="${3}"
+USERNAME="${4}"
+PASSWORD="${5}"
+USERID="${6}"
+LOCAL_PATH="${7}"
+PREFIX="${8}"
+SMBCLIENT="${9}"
+WBINFO="${10}"
+NET="${11}"
+CONFIGURATION="${12}"
+PROTOCOL="${13}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+WBINFO="$VALGRIND ${WBINFO}"
+shift 13
+RAWARGS="${CONFIGURATION} -m${PROTOCOL}"
+ADDARGS="${RAWARGS} $*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Test that a noninteractive smbclient does not prompt
+test_noninteractive_no_prompt()
+{
+ prompt="smb"
+
+ cmd='echo du | $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+
+ if [ $? != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ return 1
+ fi
+
+ echo "$out" | grep $prompt >/dev/null 2>&1
+
+ if [ $? = 0 ]; then
+ # got a prompt .. fail
+ echo matched interactive prompt in non-interactive mode
+ return 1
+ fi
+
+ return 0
+}
+
+# Test that an interactive smbclient prompts to stdout
+test_interactive_prompt_stdout()
+{
+ prompt="smb"
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+
+ cat >$tmpfile <<EOF
+du
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ return 1
+ fi
+
+ echo "$out" | grep $prompt >/dev/null 2>&1
+
+ if [ $? != 0 ]; then
+ echo failed to match interactive prompt on stdout
+ return 1
+ fi
+
+ return 0
+}
+
+# Test creating a bad symlink and deleting it.
+test_bad_symlink()
+{
+ prompt="posix_unlink deleted file /newname"
+ tmpfile=$PREFIX/smbclient_bad_symlinks_commands
+
+ cat >$tmpfile <<EOF
+posix
+posix_unlink newname
+symlink badname newname
+posix_unlink newname
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed create then delete bad symlink with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt" >/dev/null 2>&1
+
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed create then delete bad symlink - grep failed with $ret"
+ return 1
+ fi
+
+ return 0
+}
+
+# Test creating a good symlink and deleting it by path.
+test_good_symlink()
+{
+ tmpfile=$PREFIX/smbclient.in.$$
+ slink_name="$LOCAL_PATH/slink"
+ slink_target="$LOCAL_PATH/slink_target"
+
+ touch $slink_target
+ ln -s $slink_target $slink_name
+ cat >$tmpfile <<EOF
+del slink
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed delete good symlink with error $ret"
+ rm $slink_target
+ rm $slink_name
+ return 1
+ fi
+
+ if [ ! -e $slink_target ]; then
+ echo "failed delete good symlink - symlink target deleted !"
+ rm $slink_target
+ rm $slink_name
+ return 1
+ fi
+
+ if [ -e $slink_name ]; then
+ echo "failed delete good symlink - symlink still exists"
+ rm $slink_target
+ rm $slink_name
+ return 1
+ fi
+
+ rm $slink_target
+ return 0
+}
+
+# Test writing into a read-only directory (logon as guest) fails.
+test_read_only_dir()
+{
+ prompt="NT_STATUS_ACCESS_DENIED making remote directory"
+ tmpfile=$PREFIX/smbclient.in.$$
+
+ ##
+ ## We can't do this as non-root. We always have rights to
+ ## create the directory.
+ ##
+ if [ "$USERID" != 0 ]; then
+ echo "skipping test_read_only_dir as non-root"
+ return 0
+ fi
+
+ ##
+ ## We can't do this with an encrypted connection. No credentials
+ ## to set up the channel.
+ ##
+ if [ "$ADDARGS" = "-e" ]; then
+ echo "skipping test_read_only_dir with encrypted connection"
+ return 0
+ fi
+
+ cat >$tmpfile <<EOF
+mkdir a_test_dir
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U% "//$SERVER/$1" -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed writing into read-only directory with error $ret"
+
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt" >/dev/null 2>&1
+
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed writing into read-only directory - grep failed with $ret"
+ return 1
+ fi
+
+ return 0
+}
+
+# Test sending a message
+test_message()
+{
+ tmpfile=$PREFIX/message_in.$$
+
+ cat >$tmpfile <<EOF
+Test message from pid $$
+EOF
+
+ cmd='$SMBCLIENT "$@" -U$USERNAME%$PASSWORD -M $SERVER -p 139 $ADDARGS -n msgtest < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed sending message to $SERVER with error $ret"
+ rm -f $tmpfile
+ return 1
+ fi
+
+ # The server writes this into a file message.msgtest, via message.%m to test the % sub code
+ cmd='$SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmpguest -p 139 $ADDARGS -c "get message.msgtest $PREFIX/message_out.$$" 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed getting sent message from $SERVER with error $ret"
+ return 1
+ fi
+
+ if cmp $PREFIX/message_out.$$ $tmpfile; then
+ echo "failed comparison of message from $SERVER"
+ return 1
+ fi
+
+ return 0
+}
+
+# Test reading an owner-only file (logon as guest) fails.
+test_owner_only_file()
+{
+ prompt="NT_STATUS_ACCESS_DENIED opening remote file"
+ tmpfile=$PREFIX/smbclient.in.$$
+
+ ##
+ ## We can't do this as non-root. We always have rights to
+ ## read the file.
+ ##
+ if [ "$USERID" != 0 ]; then
+ echo "skipping test_owner_only_file as non-root"
+ return 0
+ fi
+
+ ##
+ ## We can't do this with an encrypted connection. No credentials
+ ## to set up the channel.
+ ##
+ if [ "$ADDARGS" = "-e" ]; then
+ echo "skipping test_owner_only_file with encrypted connection"
+ return 0
+ fi
+
+ cat >$tmpfile <<EOF
+get unreadable_file
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U% //$SERVER/ro-tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed reading owner-only file with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt" >/dev/null 2>&1
+
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed reading owner-only file - grep failed with $ret"
+ return 1
+ fi
+
+ return 0
+}
+
+# Test accessing an msdfs path.
+test_msdfs_link()
+{
+ tmpfile=$PREFIX/smbclient.in.$$
+ prompt=" msdfs-target "
+
+ cmd='$SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS -m $PROTOCOL -c dir 2>&1'
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing msfds-share\ with error $ret"
+ return 1
+ fi
+
+ cat >$tmpfile <<EOF
+ls
+cd \\msdfs-src1
+ls msdfs-target
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing \\msdfs-src1 link with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt" >/dev/null 2>&1
+
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\msdfs-src1 - grep failed with $ret"
+ return 1
+ fi
+
+ cat >$tmpfile <<EOF
+ls
+cd \\deeppath\\msdfs-src2
+ls msdfs-target
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing \\deeppath\\msdfs-src2 link with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt" >/dev/null 2>&1
+
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\deeppath\\msdfs-src2 - grep failed with $ret"
+ return 1
+ fi
+
+ return 0
+}
+
+# Test recursive listing across msdfs links
+test_msdfs_recursive_dir()
+{
+ tmpfile=$PREFIX/smbclient.in.$$
+
+ cat >$tmpfile <<EOF
+recurse
+dir
+quit
+EOF
+
+ cmd='$SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS -m $PROTOCOL < $tmpfile 2>&1'
+ out=$(eval $cmd)
+ ret="$?"
+
+ if [ "$ret" -ne 0 ]; then
+ echo "$out"
+ echo "failed listing msfds-share\ with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS_OBJECT_PATH_NOT_FOUND listing \widelinks\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\dot\*' > /dev/null 2>&1
+
+ ret="$?"
+ if [ "$ret" -ne 0 ]; then
+ echo "$out"
+ echo "Listing \\msdfs-share recursively did not properly end in symlink recursion"
+ fi
+
+ return 0
+}
+
+# Test doing a normal file rename on an msdfs path.
+test_msdfs_rename()
+{
+ tmpfile="$PREFIX/smbclient.in.$$"
+ filename_src="src.$$"
+ filename_dst="dest.$$"
+ filename_src_path="$PREFIX/$filename_src"
+ rm -f "$filename_src_path"
+ touch "$filename_src_path"
+
+ #
+ # Use both non-force and force rename to
+ # ensure we test both codepaths inside libsmb.
+ #
+ cat >$tmpfile <<EOF
+lcd $PREFIX
+put $filename_src
+ren $filename_src $filename_dst -f
+ren $filename_dst $filename_src
+del $filename_src
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f "$tmpfile"
+ rm -f "$filename_src_path"
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed renaming $filename_src $filename_dst with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "NT_STATUS" >/dev/null 2>&1
+
+ ret="$?"
+ if [ "$ret" -eq 0 ]; then
+ echo "$out"
+ echo "renaming $filename_src $filename_dst got NT_STATUS_ error"
+ return 1
+ fi
+ return 0
+}
+
+# Test doing a normal file hardlink on an msdfs path.
+test_msdfs_hardlink()
+{
+ tmpfile="$PREFIX/smbclient.in.$$"
+ filename_src="src.$$"
+ filename_dst="dest.$$"
+ filename_src_path="$PREFIX/$filename_src"
+ rm -f "$filename_src_path"
+ touch "$filename_src_path"
+
+ cat >$tmpfile <<EOF
+lcd $PREFIX
+put $filename_src
+hardlink $filename_src $filename_dst
+del $filename_src
+del $filename_dst
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f "$tmpfile"
+ rm -f "$filename_src_path"
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed hardlink $filename_src $filename_dst with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "NT_STATUS" >/dev/null 2>&1
+
+ ret="$?"
+ if [ "$ret" -eq 0 ]; then
+ echo "$out"
+ echo "hardlink $filename_src $filename_dst got NT_STATUS_ error"
+ return 1
+ fi
+ return 0
+}
+
+test_msdfs_del()
+{
+ tmpfile="$PREFIX/smbclient.in.$$"
+ filename_src="src.$$"
+ filename_src_path="$PREFIX/$filename_src"
+ rm -f "$filename_src_path"
+ touch "$filename_src_path"
+
+ cat > $tmpfile <<EOF
+lcd $PREFIX
+cd dfshop1
+cd dfshop2
+put $filename_src
+del $filename_src
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=`eval $cmd`
+ ret=$?
+ rm -f "$tmpfile"
+ rm -f "$filename_src_path"
+
+ if [ $ret != 0 ] ; then
+ echo "$out"
+ echo "failed deleting $filename_src with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "NT_STATUS" >/dev/null 2>&1
+
+ ret="$?"
+ if [ "$ret" -eq 0 ] ; then
+ echo "$out"
+ echo "del $filename_src NT_STATUS_ error"
+ return 1
+ fi
+ return 0
+}
+
+test_msdfs_deltree()
+{
+ tmpfile="$PREFIX/smbclient.in.$$"
+ dirname_src="foodir.$$"
+ filename_src="src.$$"
+ filename_src_path="$PREFIX/$filename_src"
+ dirname_src_path="$PREFIX/$dirname"
+ rm -f "$filename_src_path"
+ touch "$filename_src_path"
+
+ cat > $tmpfile <<EOF
+lcd $PREFIX
+cd dfshop1
+cd dfshop2
+mkdir $dirname_src
+cd $dirname_src
+put $filename_src
+cd ..
+deltree $dirname_src
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/msdfs-share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=`eval $cmd`
+ ret=$?
+ rm -f "$tmpfile"
+ rm -f "$filename_src_path"
+ rm -f "$dirname_src_path"
+
+ if [ $ret != 0 ] ; then
+ echo "$out"
+ echo "deltree failed deleting dir $dirname_src with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "NT_STATUS" >/dev/null 2>&1
+
+ ret="$?"
+ if [ "$ret" -eq 0 ] ; then
+ echo "$out"
+ echo "deltree $dirname_src NT_STATUS_ error"
+ return 1
+ fi
+ return 0
+}
+
+# Archive bits are correctly set on file/dir creation and rename.
+test_rename_archive_bit()
+{
+ prompt_file="attributes: A (20)"
+ prompt_dir="attributes: D (10)"
+ tmpfile="$PREFIX/smbclient.in.$$"
+ filename="foo.$$"
+ filename_ren="bar.$$"
+ dirname="foodir.$$"
+ dirname_ren="bardir.$$"
+ filename_path="$PREFIX/$filename"
+ local_name1="$LOCAL_PATH/$filename"
+ local_name2="$LOCAL_PATH/$filename_ren"
+ local_dir_name1="$LOCAL_PATH/$dirname"
+ local_dir_name2="$LOCAL_PATH/$dirname_ren"
+
+ rm -f $filename_path
+ rm -f $local_name1
+ rm -f $local_name2
+
+ # Create a new file, ensure it has 'A' attributes.
+ touch $filename_path
+
+ cat >$tmpfile <<EOF
+lcd $PREFIX
+put $filename
+allinfo $filename
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed creating file $filename with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt_file" >/dev/null 2>&1
+
+ ret=$?
+
+ rm -f $filename_path
+ rm -f $local_name1
+ rm -f $local_name2
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "Attributes incorrect on new file $ret"
+ return 1
+ fi
+
+ # Now check if we remove 'A' and rename, the A comes back.
+ touch $filename_path
+
+ cat >$tmpfile <<EOF
+lcd $PREFIX
+put $filename
+setmode $filename -a
+ren $filename $filename_ren
+allinfo $filename_ren
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed creating file and renaming $filename with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt_file" >/dev/null 2>&1
+
+ ret=$?
+
+ rm -f $filename_path
+ rm -f $local_name1
+ rm -f $local_name2
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "Attributes incorrect on renamed file $ret"
+ return 1
+ fi
+
+ rm -rf $local_dir_name1
+ rm -rf $local_dir_name2
+
+ # Create a new directory, ensure it has 'D' but not 'A' attributes.
+
+ cat >$tmpfile <<EOF
+mkdir $dirname
+allinfo $dirname
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed creating directory $dirname with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt_dir" >/dev/null 2>&1
+
+ ret=$?
+
+ rm -rf $local_dir_name1
+ rm -rf $local_dir_name2
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "Attributes incorrect on new directory $ret"
+ return 1
+ fi
+
+ # Now check if we rename, we still only have 'D' attributes
+
+ cat >$tmpfile <<EOF
+mkdir $dirname
+ren $dirname $dirname_ren
+allinfo $dirname_ren
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed creating directory $dirname and renaming with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "$prompt_dir" >/dev/null 2>&1
+
+ ret=$?
+
+ rm -f $local_name1
+ rm -f $local_name2
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "Attributes incorrect on renamed directory $ret"
+ return 1
+ fi
+
+ return 0
+}
+
+# Test authenticating using the winbind ccache
+test_ccache_access()
+{
+ $WBINFO --ccache-save="${USERNAME}%${PASSWORD}"
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "wbinfo failed to store creds in cache (user='${USERNAME}', pass='${PASSWORD}')"
+ return 1
+ fi
+
+ $SMBCLIENT //$SERVER_IP/tmp --use-winbind-ccache -U "${USERNAME}" $ADDARGS -c quit 2>&1
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "smbclient failed to use cached credentials"
+ return 1
+ fi
+
+ $WBINFO --ccache-save="${USERNAME}%GarBage"
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "wbinfo failed to store creds in cache (user='${USERNAME}', pass='GarBage')"
+ return 1
+ fi
+
+ $SMBCLIENT //$SERVER_IP/tmp --use-winbind-ccache -U "${USERNAME}" $ADDARGS -c quit 2>&1
+ ret=$?
+
+ if [ $ret -eq 0 ]; then
+ echo "smbclient succeeded with wrong cached credentials"
+ return 1
+ fi
+
+ $WBINFO --logoff
+}
+
+# Test authenticating using the winbind ccache
+test_auth_file()
+{
+ tmpfile=$PREFIX/smbclient.in.$$
+ cat >$tmpfile <<EOF
+username=${USERNAME}
+password=${PASSWORD}
+domain=${DOMAIN}
+EOF
+ $SMBCLIENT //$SERVER_IP/tmp --authentication-file=$tmpfile $ADDARGS -c quit 2>&1
+ ret=$?
+ rm $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "smbclient failed to use auth file"
+ return 1
+ fi
+
+ cat >$tmpfile <<EOF
+username=${USERNAME}
+password=xxxx
+domain=${DOMAIN}
+EOF
+ $SMBCLIENT //$SERVER_IP/tmp --authentication-file=$tmpfile $ADDARGS -c quit 2>&1
+ ret=$?
+ rm $tmpfile
+
+ if [ $ret -eq 0 ]; then
+ echo "smbclient succeeded with wrong auth file credentials"
+ return 1
+ fi
+}
+
+# Test doing a directory listing with backup privilege.
+test_backup_privilege_list()
+{
+ tmpfile=$PREFIX/smbclient_backup_privilege_list
+
+ # selftest uses the forward slash as a separator, but "net sam rights
+ # grant" requires the backslash separator
+ USER_TMP=$(printf '%s' "$USERNAME" | tr '/' '\\')
+
+ # If we don't have a DOMAIN component to the username, add it.
+ printf '%s' "$USER_TMP" | grep '\\' 2>&1
+ ret=$?
+ if [ $ret != 0 ]; then
+ priv_username="$DOMAIN\\$USER_TMP"
+ else
+ priv_username="$USER_TMP"
+ fi
+
+ $NET sam rights grant $priv_username SeBackupPrivilege 2>&1
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "Failed to add SeBackupPrivilege to user $priv_username - $ret"
+ return 1
+ fi
+
+ cat >$tmpfile <<EOF
+backup
+ls
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed backup privilege list $ret"
+ return 1
+ fi
+
+ # Now remove all privileges from this SID.
+ $NET sam rights revoke $priv_username SeBackupPrivilege 2>&1
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "failed to remove SeBackupPrivilege from user $priv_username - $ret"
+ return 1
+ fi
+}
+
+# Test accessing an share with bad names (won't convert).
+test_bad_names()
+{
+ # First with SMB1
+
+ if [ $PROTOCOL = "NT1" ]; then
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/badname-tmp -I $SERVER_IP $ADDARGS -m$PROTOCOL -c ls 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing badname-tmp (SMB1) with error $ret"
+ return 1
+ fi
+
+ echo "$out" | wc -l 2>&1 | grep 5
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - grep of number of lines (1) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ \. *D'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - grep (1) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ \.\. *D'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - grep (2) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ blank.txt *N'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - grep (3) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ *$'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - grep (4) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'blocks of size.*blocks available'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - grep (5) failed with $ret"
+ return 1
+ fi
+ fi
+
+ if [ $PROTOCOL = "SMB3" ]; then
+
+ # Now check again with -mSMB3
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/badname-tmp -I $SERVER_IP $ADDARGS -m$PROTOCOL -c ls 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing badname-tmp (SMB3) with error $ret"
+ return 1
+ fi
+
+ echo "$out" | wc -l 2>&1 | grep 5
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - SMB3 grep of number of lines (1) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ \. *D'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - SMB3 grep (1) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ \.\. *D'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - SMB3 grep (2) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ blank.txt *N'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - SMB3 grep (3) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^ *$'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - SMB3 grep (4) failed with $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'blocks of size.*blocks available'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed listing \\badname-tmp - SMB3 grep (5) failed with $ret"
+ return 1
+ fi
+ fi
+}
+
+# Test accessing an share with a name that must be mangled - with acl_xattrs.
+# We know foo:bar gets mangled to FF4GBY~Q with the default name-mangling algorithm (hash2).
+test_mangled_names()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+ls
+cd FF4GBY~Q
+ls
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/manglenames_share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing manglenames_share with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS'
+ ret=$?
+ if [ $ret = 0 ]; then
+ echo "$out"
+ echo "failed - NT_STATUS_XXXX listing \\manglenames_share\\FF4GBY~Q"
+ return 1
+ fi
+}
+
+# Test using scopy to copy a file on the server.
+test_scopy()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ scopy_file=$PREFIX/scopy_file
+
+ rm -f $scopy_file
+ cat >$tmpfile <<EOF
+put ${SMBCLIENT}
+scopy smbclient scopy_file
+lcd ${PREFIX}
+get scopy_file
+del smbclient
+del scopy_file
+quit
+EOF
+ if [ $PROTOCOL = "SMB3" ]; then
+ # First SMB3
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS -m$PROTOOCL < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ out1=$(md5sum ${SMBCLIENT} | sed -e 's/ .*//')
+ out2=$(md5sum ${scopy_file} | sed -e 's/ .*//')
+ rm -f $tmpfile
+ rm -f $scopy_file
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed scopy test (1) with output $ret"
+ return 1
+ fi
+
+ if [ $out1 != $out2 ]; then
+ echo "$out1 $out2"
+ echo "failed md5sum (1)"
+ return 1
+ fi
+ fi
+ #
+ # Now do again using SMB1
+ # to force client-side fallback.
+ #
+
+ if [ $PROTOCOL = "NT1" ]; then
+ cat >$tmpfile <<EOF
+put ${SMBCLIENT}
+scopy smbclient scopy_file
+lcd ${PREFIX}
+get scopy_file
+del smbclient
+del scopy_file
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS -m$PROTOCOL < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ out1=$(md5sum ${SMBCLIENT} | sed -e 's/ .*//')
+ out2=$(md5sum ${scopy_file} | sed -e 's/ .*//')
+ rm -f $tmpfile
+ rm -f $scopy_file
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed scopy test (2) with output $ret"
+ return 1
+ fi
+
+ if [ $out1 != $out2 ]; then
+ echo "$out1 $out2"
+ echo "failed md5sum (2)"
+ return 1
+ fi
+ fi
+}
+
+# Test creating a stream on the root of the share directory filename - :foobar
+test_toplevel_stream()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+put ${PREFIX}/smbclient_interactive_prompt_commands :foobar
+allinfo \\
+setmode \\ -a
+quit
+EOF
+ # Only with SMB3???
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS -mSMB3 < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed creating toplevel stream :foobar with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^stream:.*:foobar'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed creating toplevel stream :foobar"
+ return 1
+ fi
+}
+
+# Test wide links are restricted.
+test_widelinks()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+cd dot
+ls
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/widelinks_share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing widelinks_share with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS'
+ ret=$?
+ if [ $ret = 0 ]; then
+ echo "$out"
+ echo "failed - NT_STATUS_XXXX listing \\widelinks_share\\dot"
+ return 1
+ fi
+
+ cat >$tmpfile <<EOF
+allinfo source
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/widelinks_share -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing widelinks_share with error $ret"
+ return 1
+ fi
+
+ # This should fail with NT_STATUS_OBJECT_NAME_NOT_FOUND
+ echo "$out" | grep 'NT_STATUS_OBJECT_NAME_NOT_FOUND'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed - should get NT_STATUS_OBJECT_NAME_NOT_FOUND listing \\widelinks_share\\source"
+ return 1
+ fi
+}
+
+# Test creating then deleting a stream file doesn't leave a lost-XXXXX directory.
+test_streams_depot_delete()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ rm -rf "$LOCAL_PATH/lost-*"
+
+ cat >$tmpfile <<EOF
+put ${PREFIX}/smbclient_interactive_prompt_commands foo:bar
+del foo
+ls lost*
+quit
+EOF
+ # This only works with SMB3?
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS -mSMB3 < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed creating then deleting foo:bar with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS_NO_SUCH_FILE listing \\lost\*'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "deleting foo:bar left lost-XXX directory"
+ rm -rf "$LOCAL_PATH/lost-*"
+ return 1
+ fi
+}
+
+# Test follow symlinks can't access symlinks
+test_nosymlinks()
+{
+ # Setup test dirs.
+ local_test_dir="$LOCAL_PATH/nosymlinks/test"
+ local_slink_name="$local_test_dir/source"
+ local_slink_target="$local_test_dir/nosymlink_target_file"
+
+ share_test_dir="test"
+ share_foo_dir="$share_test_dir/foo"
+ share_foobar_dir="$share_test_dir/foo/bar"
+ share_target_file="$share_test_dir/foo/bar/testfile"
+
+ rm -rf $local_test_dir
+
+ local_nosymlink_target_file="nosymlink_target_file"
+ echo "$local_slink_target" >$PREFIX/$local_nosymlink_target_file
+
+ local_foobar_target_file="testfile"
+ echo "$share_target_file" >$PREFIX/$local_foobar_target_file
+
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+mkdir $share_test_dir
+mkdir $share_foo_dir
+mkdir $share_foobar_dir
+lcd $PREFIX
+cd /$share_test_dir
+put $local_nosymlink_target_file
+cd /$share_foobar_dir
+put $local_foobar_target_file
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/nosymlinks -I $SERVER_IP $LOCAL_ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+ rm -f $PREFIX/$local_nosymlink_target_file
+ rm -f $PREFIX/$local_foobar_target_file
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing local_symlinks with error $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - got an NT_STATUS error"
+ false
+ return
+ fi
+
+ # Create the symlink locally
+ ln -s $local_slink_target $local_slink_name
+
+ # Getting a file through a symlink name should fail.
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+get test\\source
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/nosymlinks -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing nosymlinks with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS_OBJECT_NAME_NOT_FOUND'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed - should get NT_STATUS_OBJECT_NAME_NOT_FOUND getting \\nosymlinks\\source"
+ return 1
+ fi
+
+ # But we should be able to create and delete directories.
+ cat >$tmpfile <<EOF
+mkdir test\\a
+mkdir test\\a\\b
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/nosymlinks -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing nosymlinks with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - NT_STATUS_XXXX doing mkdir a; mkdir a\\b on \\nosymlinks"
+ return 1
+ fi
+
+ # Ensure regular file/directory access also works.
+ cat >$tmpfile <<EOF
+cd test\\foo\\bar
+ls
+get testfile -
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/nosymlinks -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing nosymlinks with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - NT_STATUS_XXXX doing cd foo\\bar; get testfile on \\nosymlinks"
+ return 1
+ fi
+
+ # CLEANUP
+ rm -f $local_slink_name
+
+ cat >$tmpfile <<EOF
+deltree test
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/nosymlinks -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing nosymlinks with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'NT_STATUS'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - NT_STATUS_XXXX doing cd foo\\bar; get testfile on \\nosymlinks"
+ return 1
+ fi
+}
+
+# Test we can follow normal symlinks.
+# Bug: https://bugzilla.samba.org/show_bug.cgi?id=12860
+# Note - this needs to be tested over SMB3, not SMB1.
+
+test_local_symlinks()
+{
+ # Setup test dirs.
+ LOCAL_RAWARGS="${CONFIGURATION} -mSMB3"
+ LOCAL_ADDARGS="${LOCAL_RAWARGS} $*"
+
+ share_test_dir="test"
+ share_slink_target_dir="$share_test_dir/dir1"
+
+ local_test_dir="$LOCAL_PATH/local_symlinks/$share_test_dir"
+ local_slink_name="$local_test_dir/sym_name"
+ local_slink_target_dir="$local_test_dir/dir1"
+
+ rm -rf $local_test_dir
+
+ # Create the initial directories
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+mkdir $share_test_dir
+mkdir $share_slink_target_dir
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I $SERVER_IP $LOCAL_ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing local_symlinks with error $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - got an NT_STATUS error"
+ false
+ return
+ fi
+
+ # Create the symlink locally
+ ln -s $local_slink_target_dir $local_slink_name
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed - unable to create symlink"
+ ls -la $local_test_dir
+ false
+ return
+ fi
+
+ # Can we cd into the symlink name and ls ?
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+cd $share_test_dir\\sym_name
+ls
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I $SERVER_IP $LOCAL_ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing local_symlinks with error $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - got an NT_STATUS error"
+ false
+ return
+ fi
+
+ # CLEANUP
+ rm -f $local_slink_name
+
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+deltree $share_test_dir
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I $SERVER_IP $LOCAL_ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed accessing local_symlinks with error $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - got an NT_STATUS error"
+ false
+ return
+ fi
+}
+
+#
+# Regression test for CVE-2019-10197
+# we should always get ACCESS_DENIED
+#
+test_noperm_share_regression()
+{
+ cmd='$SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/noperm -I $SERVER_IP $LOCAL_ADDARGS -c "ls;ls" 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed accessing no perm share should not work"
+ return 1
+ fi
+
+ num=$(echo "$out" | grep 'NT_STATUS_ACCESS_DENIED' | wc -l)
+ if [ "$num" -ne "2" ]; then
+ echo "$out"
+ echo "failed num[$num] - two NT_STATUS_ACCESS_DENIED lines expected"
+ return 1
+ fi
+
+ return 0
+}
+
+# Test smbclient deltree command
+test_deltree()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ deltree_dir=$PREFIX/deltree_dir
+
+ rm -rf $deltree_dir
+ cat >$tmpfile <<EOF
+mkdir deltree_dir
+mkdir deltree_dir/foo
+mkdir deltree_dir/foo/bar
+put ${SMBCLIENT} deltree_dir/foo/bar/client
+deltree deltree_dir
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed deltree test with output $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed - got an NT_STATUS error"
+ false
+ return
+ fi
+
+ if [ -d $deltree_dir ]; then
+ echo "deltree did not delete everything"
+ false
+ return
+ fi
+}
+
+# Test smbclient setmode command
+test_setmode()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+
+ cat >$tmpfile <<EOF
+del test_setmode
+put ${SMBCLIENT} test_setmode
+setmode test_setmode +r +s +h +a
+allinfo test_setmode
+setmode test_setmode -rsha
+allinfo test_setmode
+del test_setmode
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed setmode test with output $ret"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'attributes: RHSA'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed - should get attributes: RHSA"
+ false
+ return
+ fi
+
+ echo "$out" | grep 'attributes: (80)'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed - should also get attributes: (80)"
+ false
+ return
+ fi
+}
+
+# Test smbclient utimes command
+test_utimes()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+
+ saved_TZ="$TZ"
+ TZ=UTC
+ export TZ
+ saved_LANG="$LANG"
+ LANG=C
+ export LANG
+
+ cat >$tmpfile <<EOF
+del utimes_test
+put ${SMBCLIENT} utimes_test
+allinfo utimes_test
+utimes utimes_test 2016:02:04-06:19:20 17:01:01-05:10:20 -1 -1
+allinfo utimes_test
+del utimes_test
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ -n "$saved_TZ" ]; then
+ export TZ="$saved_TZ"
+ else
+ unset TZ
+ fi
+ if [ -n "$saved_LANG" ]; then
+ export LANG="$saved_LANG"
+ else
+ unset LANG
+ fi
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed utimes test with output $ret"
+ false
+ return
+ fi
+
+ # Now, we should have 2 identical write_time and change_time
+ # values, but one access_time of Jan 1 05:10:20 AM,
+ # and one create_time of Feb 04 06:19:20 AM 2016
+ out_sorted=$(echo "$out" | sort | uniq)
+ num_create=$(echo "$out_sorted" | grep -c 'create_time:')
+ num_access=$(echo "$out_sorted" | grep -c 'access_time:')
+ num_write=$(echo "$out_sorted" | grep -c 'write_time:')
+ num_change=$(echo "$out_sorted" | grep -c 'change_time:')
+ if [ "$num_create" != "2" ]; then
+ echo "failed - should get two create_time $out"
+ false
+ return
+ fi
+ if [ "$num_access" != "2" ]; then
+ echo "failed - should get two access_time $out"
+ false
+ return
+ fi
+ if [ "$num_write" != "1" ]; then
+ echo "failed - should only get one write_time $out"
+ false
+ return
+ fi
+ if [ "$num_change" != "1" ]; then
+ echo "failed - should only get one change_time $out"
+ false
+ return
+ fi
+
+ # This could be: Sun Jan 1 05:10:20 AM 2017
+ # or : Sun Jan 1 05:10:20 2017 CET
+ echo "$out" | grep 'access_time:.*Sun Jan.*1 05:10:20 .*2017.*'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo
+ echo "failed - should get access_time: Sun Jan 1 05:10:20 [AM] 2017"
+ false
+ return
+ fi
+
+ # This could be: Thu Feb 4 06:19:20 AM 2016
+ # or : Thu Feb 4 06:19:20 2016 CET
+ echo "$out" | grep 'create_time:.*Thu Feb.*4 06:19:20 .*2016.*'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo
+ echo "failed - should get access_time: Thu Feb 4 06:19:20 [AM] 2016"
+ false
+ return
+ fi
+}
+
+# Test smbclient renames with pathnames containing '..'
+test_rename_dotdot()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+
+ cat >$tmpfile <<EOF
+deltree dotdot_test
+mkdir dotdot_test
+cd dotdot_test
+mkdir dir1
+mkdir dir2
+cd dir1
+put ${SMBCLIENT} README
+rename README ..\\dir2\\README
+cd ..
+cd dir2
+allinfo README
+cd \\
+deltree dotdot_test
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed rename_dotdot test with output $ret"
+ false
+ return
+ fi
+
+ # We are allowed to get NT_STATUS_NO_SUCH_FILE listing \dotdot_test
+ # as the top level directory should not exist, but no other errors.
+
+ error_str=$(echo $out | grep NT_STATUS | grep -v "NT_STATUS_NO_SUCH_FILE listing .dotdot_test")
+ if [ "$error_str" != "" ]; then
+ echo "failed - unexpected NT_STATUS error in $out"
+ false
+ return
+ fi
+}
+
+# Test doing a volume command.
+test_volume()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+volume
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed doing volume command with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep '^Volume: |tmp| serial number'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed doing volume command"
+ return 1
+ fi
+}
+
+test_server_os_message()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+ls
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed to connect error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'Try "help" to get a list of possible commands.'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo 'failed - should get: Try "help" to get a list of possible commands.'
+ return 1
+ fi
+
+ return 0
+}
+
+test_server_quiet_message()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+ls
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS --quiet < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed to connect error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep 'Try "help" to get a list of possible commands.'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo 'failed - quiet should skip this message.'
+ return 1
+ fi
+
+ return 0
+}
+
+# Test xattr_stream correctly reports mode.
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=13380
+
+test_stream_directory_xattr()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ #
+ # Test against streams_xattr
+ #
+ cat >$tmpfile <<EOF
+deltree foo
+mkdir foo
+put ${PREFIX}/smbclient_interactive_prompt_commands foo:bar
+setmode foo -a
+allinfo foo:bar
+deltree foo
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/streams_xattr -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed checking attributes on xattr stream foo:bar with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "attributes:.*80"
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed checking attributes on xattr stream foo:bar"
+ return 1
+ fi
+
+ #
+ # Test against streams_depot
+ #
+ cat >$tmpfile <<EOF
+deltree foo
+mkdir foo
+put ${PREFIX}/smbclient_interactive_prompt_commands foo:bar
+setmode foo -a
+allinfo foo:bar
+deltree foo
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed checking attributes on depot stream foo:bar with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "attributes:.*80"
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed checking attributes on depot stream foo:bar"
+ return 1
+ fi
+}
+
+# Test smbclient non-empty rmdir command
+test_del_nedir()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ del_nedir="$LOCAL_PATH/del_nedir"
+
+ rm -rf $del_nedir
+ mkdir $del_nedir
+ touch $del_nedir/afile
+ cat >$tmpfile <<EOF
+rmdir del_nedir
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -rf $del_nedir
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed test_del_nedir test with output $ret"
+ false
+ return
+ fi
+
+ # Should get NT_STATUS_DIRECTORY_NOT_EMPTY error from rmdir
+ echo "$out" | grep 'NT_STATUS_DIRECTORY_NOT_EMPTY'
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_del_nedir failed - should get an NT_STATUS_DIRECTORY_NOT_EMPTY error"
+ false
+ return
+ fi
+}
+
+test_valid_users()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >$tmpfile <<EOF
+ls
+quit
+EOF
+ # User in "valid users" can login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/valid_users $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_users 'User in 'valid users' can login to service' failed - $ret"
+ return 1
+ fi
+
+ # User from ad group in "valid users" can login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/valid_users_group $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_users_group 'User from ad group in 'valid users' can login to service' failed - $ret"
+ return 1
+ fi
+
+ # User from UNIX group in "valid users" can login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/valid_users_unix_group $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_users_unix_group 'User from UNIX group in 'valid users' can login to service' failed - $ret"
+ return 1
+ fi
+
+ # User not in NIS group in "valid users" can't login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/valid_users_nis_group $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ echo "$out" | grep 'NT_STATUS_ACCESS_DENIED'
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_users_nis_group 'User not in NIS group in 'valid users' can't login to service' failed - $ret"
+ return 1
+ fi
+
+ # Check user in UNIX, then in NIS group in "valid users" can login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/valid_users_unix_nis_group $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_users_unix_nis_group 'Check user in UNIX, then in NIS group in 'valid users' can login to service' failed - $ret"
+ return 1
+ fi
+
+ # Check user in NIS, then in UNIX group in "valid users" can login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/valid_users_nis_unix_group $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_users_nis_unix_group 'Check user in NIS, then in UNIX group in 'valid users' can login to service' failed - $ret"
+ return 1
+ fi
+
+ # User not in "invalid users" can login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -Ualice%Secret007 //$SERVER/invalid_users $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:invalid_users 'User not in 'invalid users' can login to service' failed - $ret"
+ return 1
+ fi
+
+ # User in "invalid users" can't login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/invalid_users $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ echo "$out" | grep 'NT_STATUS_ACCESS_DENIED'
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:invalid_users 'User in 'invalid users' can't login to service' failed - $ret"
+ return 1
+ fi
+
+ # User is in "valid and invalid users" can't login to service
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$DC_USERNAME%$DC_PASSWORD //$SERVER/valid_and_invalid_users $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ echo "$out" | grep 'NT_STATUS_ACCESS_DENIED'
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_and_invalid_users 'User is in 'valid and invalid users' can't login to service' failed - $ret"
+ return 1
+ fi
+
+ # 2 Users are in "valid users"
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -Ualice%Secret007 //$SERVER/valid_and_invalid_users $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "test_valid_users:valid_and_invalid_users '2 Users are in 'valid users'' failed - $ret"
+ return 1
+ fi
+
+ return 0
+}
+
+test_smbclient_minus_e_stderr()
+{
+ cmd='$SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -c ls'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ if [ $? != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ return 1
+ fi
+
+ # test smbclient 'ls' command output went to stdout
+ echo "$out" | grep "blocks available" >/dev/null 2>&1
+ if [ $? != 0 ]; then
+ # didn't get output to stdout
+ echo "expected output was NOT output to stdout"
+ return 1
+ fi
+
+ # this time execute ls but redirect stdout alone to /dev/null
+ cmd='$SMBCLIENT -E "$@" -U$USERNAME%$PASSWORD //$SERVER/tmp -c "ls" 2>&1 > /dev/null'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ if [ $? != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ return 1
+ fi
+
+ # test smbclient 'ls' command output went to stderr
+ echo "$out" | grep "blocks available" >/dev/null 2>&1
+ if [ $? != 0 ]; then
+ # didn't get output to stderr
+ echo "expected output was NOT output to stderr"
+ return 1
+ fi
+
+ return 0
+
+}
+
+#
+#
+LOGDIR_PREFIX=test_smbclient_s3
+
+# possibly remove old logdirs:
+
+for OLDDIR in $(find ${PREFIX} -type d -name "${LOGDIR_PREFIX}_*"); do
+ echo "removing old directory ${OLDDIR}"
+ rm -rf ${OLDDIR}
+done
+
+LOGDIR=$(mktemp -d ${PREFIX}/${LOGDIR_PREFIX}_XXXXXX)
+
+testit "smbclient -L $SERVER_IP" $SMBCLIENT -L $SERVER_IP -N -p 139 ${RAWARGS} || failed=$(expr $failed + 1)
+testit "smbclient -L $SERVER -I $SERVER_IP" $SMBCLIENT -L $SERVER -I $SERVER_IP -N -p 139 ${RAWARGS} -c quit || failed=$(expr $failed + 1)
+
+testit "noninteractive smbclient does not prompt" \
+ test_noninteractive_no_prompt ||
+ failed=$(expr $failed + 1)
+
+testit "noninteractive smbclient -l does not prompt" \
+ test_noninteractive_no_prompt -l $LOGDIR ||
+ failed=$(expr $failed + 1)
+
+testit "smbclient output goes to stderr when -E is passed" \
+ test_smbclient_minus_e_stderr ||
+ failed=$(expr $failed + 1)
+
+testit "interactive smbclient prompts on stdout" \
+ test_interactive_prompt_stdout ||
+ failed=$(expr $failed + 1)
+
+testit "interactive smbclient -l prompts on stdout" \
+ test_interactive_prompt_stdout -l $LOGDIR ||
+ failed=$(expr $failed + 1)
+
+testit "creating a bad symlink and deleting it" \
+ test_bad_symlink ||
+ failed=$(expr $failed + 1)
+
+testit "creating a good symlink and deleting it by path" \
+ test_good_symlink ||
+ failed=$(expr $failed + 1)
+
+testit "writing into a read-only directory fails" \
+ test_read_only_dir ro-tmp ||
+ failed=$(expr $failed + 1)
+
+testit "writing into a read-only share fails" \
+ test_read_only_dir valid-users-tmp ||
+ failed=$(expr $failed + 1)
+
+testit "Reading a owner-only file fails" \
+ test_owner_only_file ||
+ failed=$(expr $failed + 1)
+
+testit "Accessing an MS-DFS link" \
+ test_msdfs_link ||
+ failed=$(expr $failed + 1)
+
+testit "Recursive ls across MS-DFS links" \
+ test_msdfs_recursive_dir ||
+ failed=$(expr $failed + 1)
+
+testit "Rename on MS-DFS share" \
+ test_msdfs_rename ||
+ failed=$(expr $failed + 1)
+
+testit "Hardlink on MS-DFS share" \
+ test_msdfs_hardlink ||
+ failed=$(expr $failed + 1)
+
+testit "del on MS-DFS share" \
+ test_msdfs_del || \
+ failed=`expr $failed + 1`
+
+testit "deltree on MS-DFS share" \
+ test_msdfs_deltree || \
+ failed=`expr $failed + 1`
+
+testit "Ensure archive bit is set correctly on file/dir rename" \
+ test_rename_archive_bit ||
+ failed=$(expr $failed + 1)
+
+testit "ccache access works for smbclient" \
+ test_ccache_access ||
+ failed=$(expr $failed + 1)
+
+testit "sending a message to the remote server" \
+ test_message ||
+ failed=$(expr $failed + 1)
+
+testit "using an authentication file" \
+ test_auth_file ||
+ failed=$(expr $failed + 1)
+
+testit "list with backup privilege" \
+ test_backup_privilege_list ||
+ failed=$(expr $failed + 1)
+
+testit "list a share with bad names (won't convert)" \
+ test_bad_names ||
+ failed=$(expr $failed + 1)
+
+testit "list a share with a mangled name + acl_xattr object" \
+ test_mangled_names ||
+ failed=$(expr $failed + 1)
+
+testit "server-side file copy" \
+ test_scopy ||
+ failed=$(expr $failed + 1)
+
+testit "creating a :stream at root of share" \
+ test_toplevel_stream ||
+ failed=$(expr $failed + 1)
+
+testit "Ensure widelinks are restricted" \
+ test_widelinks ||
+ failed=$(expr $failed + 1)
+
+testit "streams_depot can delete correctly" \
+ test_streams_depot_delete ||
+ failed=$(expr $failed + 1)
+
+testit "stream_xattr attributes" \
+ test_stream_directory_xattr ||
+ failed=$(expr $failed + 1)
+
+testit "follow symlinks = no" \
+ test_nosymlinks ||
+ failed=$(expr $failed + 1)
+
+testit "follow local symlinks" \
+ test_local_symlinks ||
+ failed=$(expr $failed + 1)
+
+testit "noperm share regression" \
+ test_noperm_share_regression ||
+ failed=$(expr $failed + 1)
+
+testit "smbclient deltree command" \
+ test_deltree ||
+ failed=$(expr $failed + 1)
+
+testit "server os message" \
+ test_server_os_message ||
+ failed=$(expr $failed + 1)
+
+testit "test server quiet message" \
+ test_server_quiet_message ||
+ failed=$(expr $failed + 1)
+
+testit "setmode test" \
+ test_setmode ||
+ failed=$(expr $failed + 1)
+
+testit "utimes" \
+ test_utimes ||
+ failed=$(expr $failed + 1)
+
+testit "rename_dotdot" \
+ test_rename_dotdot ||
+ failed=$(expr $failed + 1)
+
+testit "volume" \
+ test_volume ||
+ failed=$(expr $failed + 1)
+
+testit "rm -rf $LOGDIR" \
+ rm -rf $LOGDIR ||
+ failed=$(expr $failed + 1)
+
+testit "delete a non empty directory" \
+ test_del_nedir ||
+ failed=$(expr $failed + 1)
+
+testit "valid users" \
+ test_valid_users ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbclient_tarmode.pl b/source3/script/tests/test_smbclient_tarmode.pl
new file mode 100755
index 0000000..fa69d28
--- /dev/null
+++ b/source3/script/tests/test_smbclient_tarmode.pl
@@ -0,0 +1,1723 @@
+#!/usr/bin/perl
+# Unix SMB/CIFS implementation.
+# Test suite for the tar backup mode of smbclient.
+# Copyright (C) Aurélien Aptel 2013
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+=head1 NAME
+
+C<test_smbclient_tarmode.pl> - Test for smbclient tar backup feature
+
+=cut
+
+use v5.10;
+use strict;
+use warnings;
+
+use Archive::Tar;
+use Data::Dumper;
+use Digest::MD5 qw/md5_hex/;
+use File::Path qw/make_path remove_tree/;
+use File::Spec;
+use File::Temp;
+use Getopt::Long;
+use Pod::Usage;
+use Term::ANSIColor;
+
+sub d {print Dumper @_;}
+
+# DEFAULTS
+# 'our' to make them available in the File package
+our $USER = '';
+our $PW = '';
+our $HOST = '';
+our $IP = '';
+our $SHARE = '';
+our $DIR = 'tar_test_dir';
+our $LOCALPATH = '';
+our $TMP = File::Temp->newdir();
+our $BIN = 'smbclient';
+our $SUBUNIT = 0;
+
+my $SELECTED_TEST = '';
+my $LIST_TEST = 0;
+
+my @SMBARGS = ();
+
+our $DEBUG = 0;
+our $VERBOSE = 0;
+my $MAN = 0;
+my $HELP = 0;
+my $CLEAN = 0;
+
+# all tests
+my @TESTS = (
+# ['test helper', \&test_helper],
+ ['create, normal files (no attributes)', \&test_creation_normal, 'normal'],
+ ['create, normal nested files (no attributes)', \&test_creation_normal, 'nested'],
+ ['create, normal files (interactive)', \&test_creation_normal, 'inter'],
+ ['create, large file', \&test_creation_large_file],
+ ['create, long path', \&test_creation_long_path],
+ ['create, incremental with -g', \&test_creation_incremental, '-g'],
+ ['create, incremental with tarmode', \&test_creation_incremental, 'tarmode inc'],
+ ['create, reset archived files with -a', \&test_creation_reset, '-a'],
+ ['create, reset archived files with tarmode', \&test_creation_reset, 'tarmode reset'],
+ ['create, files newer than a file', \&test_creation_newer],
+ ['create, combination of tarmode filter', \&test_creation_attr],
+ ['create, explicit include', \&test_creation_include],
+ ['create, explicit exclude', \&test_creation_exclude],
+ ['create, include w/ filelist (F)', \&test_creation_list],
+ ['create, wildcard simple', \&test_creation_wildcard_simple],
+ ['create, regex', \&test_creation_regex],
+ ['create, multiple backup in session', \&test_creation_multiple],
+ ['extract, normal files', \&test_extraction_normal],
+ ['extract, explicit include', \&test_extraction_include],
+ ['extract, explicit exclude', \&test_extraction_exclude],
+ ['extract, include w/ filelist (F)', \&test_extraction_list],
+ ['extract, regex', \&test_extraction_regex],
+);
+
+=head1 SYNOPSIS
+
+ test_smbclient_tarmode.pl [options] -- [smbclient options]
+
+ Options:
+ -h, --help brief help message
+ --man full documentation
+
+ Environment:
+ -u, --user USER
+ -p, --password PW
+ -n, --name HOST (required)
+ -i, --ip IP
+ -s, --share SHARE (required)
+ -d, --dir PATH
+ sub-path to use on the share
+
+ -l, --local-path PATH (required)
+ path to the root of the samba share on the machine.
+
+ -b, --bin BIN
+ path to the smbclient binary to use
+
+ Test:
+ --list
+ list tests
+
+ --test N
+ --test A-B
+ --test A,B,D-F
+ only run certain tests (accept list and intervals of numbers)
+
+ -v, --verbose
+ be more verbose
+
+ --debug
+ print command and their output (also set -v)
+
+ --subunit
+ print output in subunit format
+
+=cut
+
+GetOptions('u|user=s' => \$USER,
+ 'p|password=s' => \$PW,
+ 'n|name=s' => \$HOST,
+ 'i|ip=s' => \$IP,
+ 's|share=s' => \$SHARE,
+ 'd|dir=s' => \$DIR,
+ 'l|local-path=s' => \$LOCALPATH,
+ 'b|bin=s' => \$BIN,
+
+ 'test=s' => \$SELECTED_TEST,
+ 'list' => \$LIST_TEST,
+
+ 'clean' => \$CLEAN,
+ 'subunit' => \$SUBUNIT,
+ 'debug' => \$DEBUG,
+ 'v|verbose' => \$VERBOSE,
+ 'h|help' => \$HELP,
+ 'man' => \$MAN) or pod2usage(2);
+
+pod2usage(0) if $HELP;
+pod2usage(-exitval => 0, -verbose => 2) if $MAN;
+list_test(), exit 0 if $LIST_TEST;
+pod2usage(1) unless $HOST;
+pod2usage(1) unless $SHARE;
+pod2usage(1) unless $LOCALPATH;
+
+if ($USER xor $PW) {
+ die "Need both user and password when one is provided\n";
+}
+elsif ($USER and $PW) {
+ push @SMBARGS, '-U'.$USER.'%'.$PW;
+}
+else {
+ push @SMBARGS, '-N';
+}
+
+if ($IP) {
+ push @SMBARGS, '-I', $IP;
+}
+
+# remaining arguments are passed to smbclient
+push @SMBARGS, @ARGV;
+
+# path to store the downloaded tarball
+my $TAR = "$TMP/tarmode.tar";
+
+#####
+
+# SANITIZATION
+
+# remove all final slashes from input paths
+$LOCALPATH =~ s{[/\\]+$}{}g;
+$SHARE =~ s{[/\\]+$}{}g;
+$HOST =~ s{[/\\]+$}{}g;
+$DIR =~ s{^\.[/\\]+$}{}g;
+$DIR =~ s{[/\\]+$}{}g;
+
+if (!-d $LOCALPATH) {
+ die "Local path '$LOCALPATH' is not a directory.\n";
+}
+
+if ($CLEAN) {
+ # clean the whole root first
+ remove_tree($LOCALPATH, { keep_root => 1 });
+}
+
+if ($DEBUG) {
+ $VERBOSE = 1;
+}
+
+#####
+
+# RUN TESTS
+
+my @selection = parse_test_string($SELECTED_TEST);
+
+if ($SELECTED_TEST eq '') {
+ run_test(@TESTS);
+} elsif (@selection > 0) {
+ run_test(@selection);
+} else {
+ die "Test selection '$SELECTED_TEST' is invalid\n";
+}
+
+#################################
+
+=head1 DOCUMENTATION
+
+=head2 Defining a test
+
+=over
+
+=item * Create a function C<test_yourtest>
+
+=item * Use the File module, documented below
+
+=item * Use C<smb_tar>, C<smb_client>, C<check_tar> or C<check_remote>
+
+=item * Return number of error
+
+=item * Add function to C<@TESTS>
+
+=back
+
+The function must be placed in the C<@TESTS> list along with a short
+description and optional arguments.
+
+=cut
+
+sub test_creation_newer {
+ my @files;
+ my $dt = 3000;
+
+ # create oldest file at - DT
+ my $oldest = File->new_remote('oldest');
+ $oldest->set_time(time - $dt);
+
+ # create limit file
+ my $limit = File->new_local("$TMP/limit");
+
+ # create newA file at + DT
+ my $newA = File->new_remote('newA');
+ $newA->set_time(time + $dt);
+
+ # create newB file at + DT
+ my $newB = File->new_remote('newB');
+ $newB->set_time(time + $dt);
+
+ # get files newer than limit_file
+ push @files, $newA, $newB;
+
+ smb_tar('', '-TcN', $limit->localpath, $TAR, $DIR);
+ return check_tar($TAR, \@files);
+}
+
+sub test_creation_attr {
+ my @attr = qw/r h s a/;
+ my @all;
+ my @inc;
+ my $err = 0;
+
+ # one normal file
+ my $f = File->new_remote("file-n.txt");
+ push @all, $f;
+
+ # combinations of attributes
+ for my $n (1..@attr) {
+ for (combine(\@attr, $n)) {
+ my @t = @$_;
+ my $fn = "file-" . join('+', @t) . ".txt";
+ my $f = File->new_remote($fn);
+ $f->set_attr(@t);
+ push @all, $f;
+ }
+ }
+
+ @inc = grep { !$_->attr('s') } @all;
+ smb_tar('tarmode nosystem', '-Tc', $TAR, $DIR);
+ $err += check_tar($TAR, \@inc);
+
+ @inc = grep { !$_->attr('h') } @all;
+ smb_tar('tarmode nohidden', '-Tc', $TAR, $DIR);
+ $err += check_tar($TAR, \@inc);
+
+ @inc = grep { !$_->attr_any('h', 's') } @all;
+ smb_tar('tarmode nohidden nosystem', '-Tc', $TAR, $DIR);
+ $err += check_tar($TAR, \@inc);
+
+ @inc = grep { $_->attr('a') && !$_->attr_any('h', 's') } @all;
+ smb_tar('tarmode inc nohidden nosystem', '-Tc', $TAR, $DIR);
+ $err += check_tar($TAR, \@inc);
+ # adjust attr so remote files can be deleted with deltree
+ File::walk(sub { $_->set_attr(qw/n r s h/) }, File::tree($DIR));
+
+ $err;
+}
+
+sub test_creation_reset {
+ my ($mode) = @_;
+
+ my @files;
+ my $n = 3;
+ for (1..$n) {
+ my $f = File->new_remote("file-$_");
+ $f->set_attr('a');
+ push @files, $f;
+ }
+
+ if ($mode =~ /reset/) {
+ smb_tar('tarmode full reset', '-Tc', $TAR, $DIR);
+ } else {
+ smb_tar('', '-Tca', $TAR, $DIR);
+ }
+
+ my $err = check_tar($TAR, \@files);
+ return $err if ($err > 0);
+
+ for my $f (File::list($DIR)) {
+ if ($f->{attr}{a}) {
+ printf " ! %s %s\n", $f->attr_str, $f->remotepath;
+ $err++;
+ }
+ }
+ return $err;
+}
+
+sub test_creation_large_file {
+ my $size = int(15e6); # 15MB
+ my $f = File->new_remote("fat.jpg", 0, $size);
+
+ smb_tar('', '-Tc', $TAR, $DIR);
+ return check_tar($TAR, [$f]);
+}
+
+sub test_creation_long_path {
+ my $d = "a"x130;
+ my @all;
+
+ for (qw( foo/a bar/b )) {
+ push @all, File->new_remote("$d/$_");
+ }
+
+ smb_tar('', '-Tc', $TAR, $DIR);
+ return check_tar($TAR, \@all);
+}
+
+sub test_creation_normal {
+ my ($mode) = @_;
+
+ my $prefix = ($mode =~ /nest/) ? "/foo/bar/bar/" : '';
+ my @files;
+ my $n = 5;
+ for (1..$n) {
+ my $f = File->new_remote($prefix."file-$_");
+ push @files, $f;
+ }
+
+ if ($mode =~ /inter/) {
+ smb_tar("tar c $TAR $DIR", '');
+ } else {
+ smb_tar('tarmode full', '-Tc', $TAR, $DIR);
+ }
+ return check_tar($TAR, \@files);
+}
+
+sub test_creation_incremental {
+ my ($mode) = @_;
+
+ my @files;
+ my $n = 10;
+ for (1..$n) {
+ my $f = File->new_remote("file-$_");
+
+ # set archive bit on ~half of them
+ if ($_ < $n/2) {
+ $f->set_attr('a');
+ push @files, $f;
+ }
+ else {
+ $f->set_attr((qw/n r s h/)[$_ % 4]);
+ }
+ }
+
+ if ($mode =~ /inc/) {
+ smb_tar('tarmode inc', '-Tc', $TAR, $DIR);
+ } else {
+ smb_tar('', '-Tcg', $TAR, $DIR);
+ }
+ my $res = check_tar($TAR, \@files);
+ # adjust attr so remote files can be deleted with deltree
+ File::walk(sub { $_->set_attr(qw/n r s h/) }, File::tree($DIR));
+ return $res
+}
+
+
+sub test_extraction_normal {
+ my @files;
+ my $n = 5;
+ for (1..$n) {
+ my $f = File->new_remote("file-$_");
+ push @files, $f;
+ }
+
+ # store
+ smb_tar('', '-Tc', $TAR, $DIR);
+ my $err = check_tar($TAR, \@files);
+ return $err if $err > 0;
+
+ reset_remote();
+
+ smb_tar('', '-Tx', $TAR);
+ check_remote($DIR, \@files);
+}
+
+sub test_extraction_include {
+ my @all_files;
+ my @inc_files;
+
+ for (qw(file_inc inc/b inc/c inc/dir/foo dir_ex/d zob)) {
+ my $f = File->new_remote($_);
+ push @all_files, $f;
+ push @inc_files, $f if /inc/;
+ }
+
+ # store
+ smb_tar('', '-Tc', $TAR, $DIR);
+ my $err = check_tar($TAR, \@all_files);
+ return $err if $err > 0;
+
+ reset_remote();
+
+ smb_tar('', '-TxI', $TAR, "$DIR/file_inc", "$DIR/inc");
+ check_remote($DIR, \@inc_files);
+}
+
+sub test_extraction_exclude {
+ my @all_files;
+ my @inc_files;
+
+ for (qw(file_exc exc/b exc/c exc/dir/foo dir_ex/d zob)) {
+ my $f = File->new_remote($_);
+ push @all_files, $f;
+ push @inc_files, $f if !/exc/;
+ }
+
+ # store
+ smb_tar('', '-Tc', $TAR, $DIR);
+ my $err = check_tar($TAR, \@all_files);
+ return $err if $err > 0;
+
+ reset_remote();
+
+ smb_tar('', '-TxX', $TAR, "$DIR/file_exc", "$DIR/exc");
+ check_remote($DIR, \@inc_files);
+}
+
+
+sub test_creation_include {
+ my @files;
+
+ for (qw(file_inc inc/b inc/c inc/dir/foo dir_ex/d zob)) {
+ my $f = File->new_remote($_);
+ push @files, $f if /inc/;
+ }
+
+ smb_tar('', '-TcI', $TAR, "$DIR/file_inc", "$DIR/inc");
+ return check_tar($TAR, \@files);
+}
+
+sub test_creation_exclude {
+ my @files;
+
+ for (qw(file_ex ex/b ex/c ex/dir/foo foo/bar zob)) {
+ my $f = File->new_remote($_);
+ push @files, $f if !/ex/;
+ }
+
+ smb_tar('', '-TcX', $TAR, "$DIR/file_ex", "$DIR/ex");
+ return check_tar($TAR, \@files);
+}
+
+sub test_creation_list {
+ my @inc_files;
+
+ for (qw(file_inc inc/b inc/c inc/dir/foo foo/bar zob)) {
+ my $f = File->new_remote($_);
+ push @inc_files, $f if /inc/;
+ }
+
+ my $flist = File->new_local("$TMP/list", file_list(@inc_files));
+ smb_tar('', '-TcF', $TAR, $flist->localpath);
+ return check_tar($TAR, \@inc_files);
+}
+
+sub test_creation_regex {
+ my @exts = qw(jpg exe);
+ my @dirs = ('', "$DIR/");
+ my @all = make_env(\@exts, \@dirs);
+ my $nb;
+ my @inc;
+ my $err = 0;
+
+ # EXCLUSION
+
+ # skip *.exe
+ @inc = grep { $_->remotepath !~ m{exe$} } @all;
+ smb_tar('', '-TcrX', $TAR, '*.exe');
+ $err += check_tar($TAR, \@inc);
+
+ # if the pattern is a path, it doesn't skip anything
+ smb_tar('', '-TcrX', $TAR, "$DIR/*.exe");
+ $err += check_tar($TAR, \@all);
+ smb_tar('', '-TcrX', $TAR, "$DIR/*");
+ $err += check_tar($TAR, \@all);
+ smb_tar('', '-TcrX', $TAR, "$DIR");
+ $err += check_tar($TAR, \@all);
+
+ # no paths => include everything
+ smb_tar('', '-TcrX', $TAR);
+ $err += check_tar($TAR, \@all);
+
+
+ # skip everything
+ smb_tar('', '-TcrX', $TAR, "*.*");
+ $err += check_tar($TAR, []);
+ smb_tar('', '-TcrX', $TAR, "*");
+ $err += check_tar($TAR, []);
+
+ # INCLUSION
+
+ # no paths => include everything
+ smb_tar('', '-Tcr', $TAR);
+ $err += check_tar($TAR, \@all);
+
+ # include everything
+ smb_tar('', '-Tcr', $TAR, '*');
+ $err += check_tar($TAR, \@all);
+
+ # include only .exe at root
+ @inc = grep { $_->remotepath =~ m{^[^/]+exe$}} @all;
+ smb_tar('', '-Tcr', $TAR, '*.exe');
+ $err += check_tar($TAR, \@inc);
+
+ # smb_tar('', '-Tcr', $TAR, "$DIR/*");
+ ## in old version (bug?)
+ # $err += check_tar($TAR, []);
+ ## in rewrite
+ # @inc = grep { $_->remotepath =~ /^$DIR/ } @all;
+ # $err += check_tar($TAR, \@inc);
+
+ $err;
+}
+
+sub test_creation_wildcard_simple {
+ my @exts = qw(jpg exe);
+ my @dirs = ('', "$DIR/");
+ my @all = make_env(\@exts, \@dirs);
+ my $nb;
+ my @inc;
+ my $err = 0;
+
+ @inc = grep { $_->remotepath =~ m{^[^/]+exe$} } @all;
+ smb_tar('', '-Tc', $TAR, "*.exe");
+ $err += check_tar($TAR, \@inc);
+
+ @inc = grep { $_->remotepath =~ m{$DIR/.+exe$} } @all;
+ smb_tar('', '-Tc', $TAR, "$DIR/*.exe");
+ $err += check_tar($TAR, \@inc);
+
+ $err;
+}
+
+# NOT USED
+# helper to test tests
+sub test_helper {
+ my @exts = qw(txt jpg exe);
+ my @dirs = ('', "$DIR/", "$DIR/dir/");
+ my @all = make_env(\@exts, \@dirs);
+ my $nb;
+ my $err = 0;
+ my @inc;
+
+ smb_tar('', '-Tc', $TAR);
+ return 1 if check_tar($TAR, \@all);
+ reset_remote();
+
+ my @exc = grep { $_->remotepath =~ m!/dir/.+exe!} @all;
+ @inc = grep { $_->remotepath !~ m!/dir/.+exe!} @all;
+ smb_tar('', '-TxXr', $TAR, "/$DIR/dir/*.exe");
+ $err += check_remote('/', \@all); # BUG: should be \@inc
+ reset_remote();
+
+ return 0;
+}
+
+sub test_creation_multiple {
+ my @exts = qw(jpg exe);
+ my @dirs = ('', "$DIR/");
+ my @all = make_env(\@exts, \@dirs);
+ my $nb;
+ my @inc;
+ my $err = 0;
+
+ my ($tarA, $tarB) = ("$TMP/a.tar", "$TMP/b.tar");
+ my @incA = grep { $_->remotepath =~ m{^[^/]+exe$} } @all;
+ my @incB = grep { $_->remotepath =~ m{^[^/]+jpg$} } @all;
+
+ my $flistA = File->new_local("$TMP/listA", file_list(@incA))->localpath;
+ my $flistB = File->new_local("$TMP/listB", file_list(@incB))->localpath;
+
+ smb_tar("tar cF $tarA $flistA ; tar cF $tarB $flistB ; quit");
+ $err += check_tar($tarA, \@incA);
+ $err += check_tar($tarB, \@incB);
+
+ $err;
+}
+
+sub test_extraction_regex {
+ my @exts = qw(txt jpg exe);
+ my @dirs = ('', "$DIR/", "$DIR/dir/");
+ my @all = make_env(\@exts, \@dirs);
+ my $nb;
+ my $err = 0;
+ my (@inc, @exc);
+
+ smb_tar('', '-Tc', $TAR);
+ return 1 if check_tar($TAR, \@all);
+ reset_remote();
+
+ # INCLUDE
+
+ # only include file at root
+ @inc = grep { $_->remotepath =~ m!exe!} @all;
+ smb_tar('', '-Txr', $TAR, "*.exe");
+ $err += check_remote('/', \@inc);
+ reset_remote();
+
+ @inc = grep { $_->remotepath =~ m!/dir/.+exe!} @all;
+ smb_tar('', '-Txr', $TAR, "/$DIR/dir/*.exe");
+ $err += check_remote('/', []); # BUG: should be \@inc
+ reset_remote();
+
+ # EXCLUDE
+
+ # exclude file not directly at root
+ @inc = grep { $_->remotepath =~ m!^[^/]+$!} @all;
+ @exc = grep { $_->remotepath !~ m!^[^/]+$!} @all;
+ smb_tar('', '-TxrX', $TAR, map {$_->remotepath} @exc);
+ $err += check_remote('/', \@all); # BUG: should be @inc...
+ reset_remote();
+
+ # exclude only $DIR/dir/*exe
+ @exc = grep { $_->remotepath =~ m!/dir/.+exe!} @all;
+ @inc = grep { $_->remotepath !~ m!/dir/.+exe!} @all;
+ smb_tar('', '-TxXr', $TAR, "/$DIR/dir/*.exe");
+ $err += check_remote('/', \@all); # BUG: should be \@inc
+ reset_remote();
+
+ $err;
+}
+
+sub test_extraction_wildcard {
+ my @exts = qw(txt jpg exe);
+ my @dirs = ('', "$DIR/", "$DIR/dir/");
+ my $nb;
+ my $err = 0;
+
+ for my $dir (@dirs) {
+
+ my @all;
+
+ $nb = 0;
+ for my $dir (@dirs) {
+ for (@exts) {
+ my $fn = $dir . "file$nb." . $_;
+ my $f = File->new_remote($fn, 'ABSPATH');
+ $f->delete_on_destruction(1);
+ push @all, $f;
+ $nb++;
+ }
+ }
+
+
+ my @inc;
+ my $ext = 'exe';
+ my $fn = $dir."file$nb.".$ext;
+ my $pattern = $dir.'*.'.$ext;
+ my $flist;
+
+ # with F
+
+ $flist = File->new_local("$TMP/list", "$pattern\n");
+
+ # store
+ my $re = '^'.$dir.'.*file';
+ @inc = grep { $dir eq '' or $_->remotepath =~ m{$re} } @all;
+ smb_tar('', '-Tc', $TAR, $dir);
+ $err += check_tar($TAR, \@inc);
+
+ reset_remote();
+ my $re2 = '^'.$dir.'file.+exe';
+ @inc = grep { $_->remotepath =~ /$re2/ } @all;
+ smb_tar('', '-TxrF', $TAR, $flist->localpath);
+ $err += check_remote($dir, \@inc);
+
+ reset_remote();
+ }
+
+ $err;
+}
+
+sub test_extraction_list {
+ my @inc_files;
+ my @all_files;
+
+ for (qw(file_inc inc/b inc/c inc/dir/foo foo/bar zob)) {
+ my $f = File->new_remote($_);
+ push @all_files, $f;
+ push @inc_files, $f if /inc/;
+ }
+
+ # store
+ smb_tar('', '-Tc', $TAR, $DIR);
+ my $err = check_tar($TAR, \@all_files);
+ return $err if $err > 0;
+
+ reset_remote();
+
+ my $flist = File->new_local("$TMP/list", file_list(@inc_files));
+ smb_tar('', '-TxF', $TAR, $flist->localpath);
+ return check_remote($DIR, \@inc_files);
+}
+
+#################################
+
+# IMPLEMENTATION
+
+=head2 Useful functions
+
+Here are a list of useful functions and helpers to define tests.
+
+=cut
+
+# list test number and description
+sub list_test {
+ my $i = 0;
+ for (@TESTS) {
+ my ($desc, $f, @args) = @$_;
+ printf "%2d.\t%s\n", $i++, $desc;
+ }
+}
+
+sub run_test {
+ if ($SUBUNIT) {
+ run_test_subunit(@_);
+ } else {
+ run_test_normal(@_);
+ }
+}
+
+sub run_test_normal {
+ for (@_) {
+ my ($desc, $f, @args) = @$_;
+ my $err;
+
+ reset_env();
+ say "TEST: $desc";
+ if ($VERBOSE) {
+ $err = $f->(@args);
+ } else {
+ # turn off STDOUT
+ open my $saveout, ">&STDOUT";
+ open STDOUT, '>', File::Spec->devnull();
+ $err = $f->(@args);
+ open STDOUT, ">&", $saveout;
+ }
+ print_res($err);
+ print "\n";
+ }
+ reset_env();
+}
+
+sub run_test_subunit {
+ for (@_) {
+ my ($desc, $f, @args) = @$_;
+ my $err;
+ my $str = '';
+
+ reset_env();
+ say "test: $desc";
+
+ # capture output in $buf
+ my $buf = '';
+ open my $handle, '>', \$buf;
+ select $handle;
+
+ # check for die() calls
+ eval {
+ $err = $f->(@args);
+ };
+ if ($@) {
+ $str = $@;
+ $err = 1;
+ }
+ close $handle;
+
+ # restore output
+ select STDOUT;
+
+ # result string is output + eventual exception message
+ $str = $buf.$str;
+
+ printf "%s: %s [\n%s]\n", ($err > 0 ? "failure" : "success"), $desc, $str;
+ }
+ reset_env();
+}
+
+sub parse_test_string {
+ my $s = shift;
+ my @tests = ();
+
+ if (!length($s)) {
+ return ();
+ }
+
+ for (split /,/, $s) {
+ if (/^\d+$/) {
+ if ($_ >= @TESTS) {
+ return ();
+ }
+ push @tests, $TESTS[$_];
+ }
+ elsif (/^(\d+)-(\d+)$/) {
+ my ($min, $max) = sort ($1, $2);
+ if ($max >= @TESTS) {
+ return ();
+ }
+
+ for ($min..$max) {
+ push @tests, $TESTS[$_];
+ }
+ }
+ else {
+ return ();
+ }
+ }
+
+ return @tests;
+}
+
+sub print_res {
+ my $err = shift;
+ if ($err) {
+ printf " RES: %s%d ERR%s\n", color('bold red'), $err, color 'reset';
+ } else {
+ printf " RES: %sOK%s\n", color('bold green'), color 'reset';
+ }
+}
+
+sub make_env {
+ my ($exts, $dirs) = @_;
+ my @all;
+ my $nb = 0;
+ for my $dir (@$dirs) {
+ for (@$exts) {
+ my $fn = $dir . "file$nb." . $_;
+ my $f = File->new_remote($fn, 'ABSPATH');
+ $f->delete_on_destruction(1);
+ push @all, $f;
+ $nb++;
+ }
+ }
+
+ @all;
+}
+
+=head3 C<combine ( \@set, $n )>
+
+=head3 C<combine ( ['a', 'b', 'c'], 2 )>
+
+Return a list of all possible I<n>-uplet (or combination of C<$n> element) of C<@set>.
+
+=cut
+sub combine {
+ my ($list, $n) = @_;
+ die "Insufficient list members" if $n > @$list;
+
+ return map [$_], @$list if $n <= 1;
+
+ my @comb;
+
+ for (my $i = 0; $i+$n <= @$list; $i++) {
+ my $val = $list->[$i];
+ my @rest = @$list[$i+1..$#$list];
+ push @comb, [$val, @$_] for combine(\@rest, $n-1);
+ }
+
+ return @comb;
+}
+
+
+=head3 C<reset_remote( )>
+
+Remove all files in the server C<$DIR> (not root)
+
+=cut
+sub reset_remote {
+ # remove_tree($LOCALPATH . '/'. $DIR);
+ # make_path($LOCALPATH . '/'. $DIR);
+ my $DIR;
+ my @names;
+ my $name;
+
+ smb_client_cmd(0, '-c', "deltree ./*");
+
+ # Ensure all files are gone.
+
+ opendir(DIR,$LOCALPATH) or die "Can't open $LOCALPATH\n";
+ @names = readdir(DIR) or die "Unable to read $LOCALPATH\n";
+ closedir(DIR);
+ foreach $name (@names) {
+ next if ($name eq "."); # skip the current directory entry
+ next if ($name eq ".."); # skip the parent directory entry
+ die "$LOCALPATH not empty\n";
+ }
+}
+
+=head3 C<reset_tmp( )>
+
+Remove all files in the temp directory C<$TMP>
+
+=cut
+sub reset_tmp {
+ remove_tree($TMP, { keep_root => 1 });
+}
+
+
+=head3 C<reset_env( )>
+
+Remove both temp and remote (C<$DIR>) files
+
+=cut
+sub reset_env {
+ reset_tmp();
+ reset_remote();
+}
+
+=head3 C<file_list ( @files )>
+
+Make a multiline string of all the files remote path, one path per line.
+
+C<@files> must be a list of C<File> instance.
+
+=cut
+sub file_list {
+ my @files = @_;
+ my $s = '';
+ for (@files) {
+ $s .= $_->remotepath."\n";
+ }
+ return $s;
+}
+
+# remove leading "./"
+sub remove_dot {
+ my $s = shift;
+ $s =~ s{^\./}{};
+ $s;
+}
+
+=head3 C<check_remote( $remotepath, \@files )>
+
+Check if C<$remotepath> has B<exactly> all the C<@files>.
+
+Print a summary on STDOUT.
+
+C<@files> must be a list of C<File> instance.
+
+=cut
+sub check_remote {
+ my ($subpath, $files) = @_;
+ my (%done, %expected);
+ my (@less, @more, @diff);
+
+ for (@$files) {
+ my $fn = remove_dot($_->remotepath);
+ $expected{$fn} = $_;
+ $done{$fn} = 0;
+ }
+
+ my %remote;
+ File::walk(sub { $remote{remove_dot($_->remotepath)} = $_ }, File::tree($subpath));
+
+ for my $rfile (sort keys %remote) {
+
+ # files that shouldn't be there
+ if (!exists $expected{$rfile}) {
+ say " + $rfile";
+ push @more, $rfile;
+ next;
+ }
+
+ # same file multiple times
+ if ($done{$rfile} > 0) {
+ $done{$rfile}++;
+ push @more, $rfile;
+ printf " +%3d %s\n", $done{$rfile}, $rfile;
+ next;
+ }
+
+ $done{$rfile}++;
+
+ # different file
+ my $rmd5 = $remote{$rfile}->md5;
+ if ($expected{$rfile}->md5 ne $rmd5) {
+ say " ! $rfile ($rmd5)";
+ push @diff, $rfile;
+ next;
+ }
+
+ say " $rfile";
+ }
+
+ # file that should have been in tar
+ @less = grep { $done{$_} == 0 } sort keys %done;
+ for (@less) {
+ say " - $_";
+ }
+
+ # summary
+ printf("\t%d files, +%d, -%d, !%d\n",
+ scalar keys %done,
+ scalar @more,
+ scalar @less,
+ scalar @diff);
+ return (@more + @less + @diff); # nb of errors
+}
+
+=head3 C<check_tar( $path_to_tar, \@files )>
+
+Check if the archive C<$path_to_tar> has B<exactly> all the C<@files>.
+
+Print a summary on C<STDOUT>;
+
+C<@files> must be a list of C<File> instance.
+
+=cut
+sub check_tar {
+ my ($tar, $files) = @_;
+ my %done;
+ my (@less, @more, @diff);
+
+ my %h;
+
+
+ if (!-f $tar) {
+ say "no tar file $tar";
+ return 1;
+ }
+
+ for (@$files) {
+ $h{$_->tarpath} = $_;
+ $done{$_->tarpath} = 0;
+ }
+
+ my $total = 0;
+ my $i = Archive::Tar->iter($tar, 1, {md5 => 1});
+ while (my $f = $i->()) {
+ if ($f->has_content) {
+ my $p = $f->full_path;
+
+ # we skip pseudo files of Pax format archives
+ next if ($p =~ m/\/PaxHeader/);
+
+ $total++;
+ $p =~ s{^\./+}{};
+
+ # file that shouldn't be there
+ if (!exists $done{$p}) {
+ push @more, $p;
+ say " + $p";
+ next;
+ }
+
+ # same file multiple times
+ if ($done{$p} > 0) {
+ $done{$p}++;
+ push @more, $p;
+ printf " +%3d %s\n", $done{$p}, $p;
+ next;
+ }
+
+ $done{$p}++;
+
+ # different file
+
+ my $md5 = $f->data;
+ if ($^V lt v5.16) {
+ $md5 = md5_hex($md5);
+ }
+
+ if ($md5 ne $h{$p}->md5) {
+ say " ! $p ($md5)";
+ push @diff, $p;
+ next;
+ }
+
+ say " $p";
+ }
+ }
+
+ # file that should have been in tar
+ @less = grep { $done{$_} == 0 } keys %done;
+ for (sort @less) {
+ say " - $_";
+ }
+
+ # summary
+ printf("\t%d files, +%d, -%d, !%d\n",
+ $total,
+ scalar @more,
+ scalar @less,
+ scalar @diff);
+ return (@more + @less + @diff); # nb of errors
+}
+
+=head3 C<smb_client_cmd( $will_die, @args)>
+
+=head3 C<smb_client_cmd( 0, '-c', 'deltree', $somedir )>
+
+Run smbclient with C<@args> passed as argument and return output.
+
+Each element of C<@args> becomes one escaped argument of smbclient.
+
+Host, share, user, password and the additional arguments provided on
+the command-line are already inserted.
+
+The output contains both the C<STDOUT> and C<STDERR>.
+
+if C<$will_die> then Die if smbclient crashes or exits with an error code.
+otherwise return output
+
+=cut
+sub smb_client_cmd {
+ my ($will_die, @args) = @_;
+
+ my $fullpath = "//$HOST/$SHARE";
+ my $cmd = sprintf("%s %s %s",
+ quotemeta($BIN),
+ quotemeta($fullpath),
+ join(' ', map {quotemeta} (@SMBARGS, @args)));
+
+ if ($DEBUG) {
+ my $tmp = $cmd;
+ $tmp =~ s{\\([./+-])}{$1}g;
+ say color('bold yellow'), $tmp, color('reset');
+ }
+
+ my $out = `$cmd 2>&1`;
+ my $err = $?;
+ my $errstr = '';
+ # handle abnormal exit
+ if ($err == -1) {
+ $errstr = "failed to execute $cmd: $!\n";
+ }
+ elsif ($err & 127) {
+ $errstr = sprintf "child died with signal %d (%s)\n", ($err & 127), $cmd;
+ }
+ elsif ($err >> 8) {
+ $errstr = sprintf "child exited with value %d (%s)\n", ($err >> 8), $cmd;
+ }
+
+ if ($DEBUG) {
+ say $out;
+ }
+
+ if ($err) {
+ if ($will_die) {
+ die "ERROR: $errstr";
+ } else {
+ say "ERROR: $errstr";
+ }
+ }
+ return $out;
+}
+
+=head3 C<smb_client ( @args )>
+
+Run smbclient with C<@args> passed as argument and return output.
+
+Each element of C<@args> becomes one escaped argument of smbclient.
+
+Host, share, user, password and the additional arguments provided on
+the command-line are already inserted.
+
+The output contains both the C<STDOUT> and C<STDERR>.
+
+Die if smbclient crashes or exits with an error code.
+
+=cut
+sub smb_client {
+ my (@args) = @_;
+ return smb_client_cmd(1, @args)
+}
+
+sub smb_cmd {
+ return smb_client('-c', join(' ', @_));
+}
+
+=head3 C<smb_tar( $cmd, @args )>
+
+=head3 C<smb_tar( 'tarmode inc', '-Tc', $TAR, $DIR )>
+
+Run C<$cmd> command and use C<@args> as argument and return output.
+
+Wrapper around C<smb_client> for tar calls.
+
+=cut
+sub smb_tar {
+ my ($cmd, @rest) = @_;
+ printf " CMD: %s\n ARG: %s\n", $cmd, join(' ', @rest);
+ smb_client((length($cmd) ? ('-c', $cmd) : ()), @rest);
+}
+
+=head3 C<random( $min, $max )>
+
+Return integer in C<[ $min ; $max ]>
+
+=cut
+sub random {
+ my ($min, $max) = @_;
+ ($min, $max) = ($max, $min) if ($min > $max);
+ $min + int(rand($max - $min));
+}
+
+#################################
+
+package File;
+
+=head2 The File module
+
+All the test should use the C<File> class. It has nice functions and
+methods to deal with paths, to create random files, to list the
+content of the server, to change attributes, etc.
+
+There are 2 kinds of C<File>: remote and local.
+
+=over
+
+=item * Remote files are accessible on the server.
+
+=item * Local files are not.
+
+=back
+
+Thus, some methods only works on remote files. If they do not make
+sense for local ones, they always return undef.
+
+=cut
+use File::Basename;
+use File::Path qw/make_path remove_tree/;
+use Digest::MD5 qw/md5_hex/;
+use Scalar::Util 'blessed';
+
+=head3 Constructors
+
+=head4 C<< File->new_remote($path [, $abs, $size]) >>
+
+Creates a file accessible on the server at C<$DIR/$path> ie. not at the
+root of the share and write C<$size> random bytes.
+
+If no size is provided, a random size is chosen.
+
+If you want to remove the automatic prefix C<$DIR>, set C<$abs> to 1.
+
+The file is created without any DOS attributes.
+
+If C<$path> contains non-existent directories, they are automatically
+created.
+
+=cut
+sub new_remote {
+ my ($class, $path, $abs, $size) = @_;
+ my ($file, $dir) = fileparse($path);
+
+ $dir = '' if $dir eq './';
+ my $loc;
+
+ if ($abs) {
+ $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+ } else {
+ $dir = cleanpath($main::DIR.'/'.$dir);
+ $loc = cleanpath($main::LOCALPATH.'/'.$dir);
+ }
+
+ make_path($loc);
+
+ my $self = bless {
+ 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/},
+ 'dir' => $dir,
+ 'name' => $file,
+ 'md5' => create_file($loc.'/'.$file, $size),
+ 'remote' => 1,
+ }, $class;
+
+ $self->set_attr();
+
+ $self;
+}
+
+=head4 C<< File->new_local($abs_path [, $data]) >>
+
+Creates a file at C<$abs_path> with $data in it on the system.
+If $data is not provided, fill it with random bytes.
+
+=cut
+sub new_local {
+ my ($class, $path, $data) = @_;
+ my ($file, $dir) = fileparse($path);
+
+ make_path($dir);
+
+ my $md5;
+
+ if (defined $data) {
+ open my $f, '>', $path or die "can't write in $path: $!";
+ print $f $data;
+ close $f;
+ $md5 = md5_hex($data);
+ } else {
+ $md5 = create_file($path);
+ }
+
+ my $self = {
+ 'attr' => {qw/r 0 s 0 h 0 a 0 d 0 n 0/},
+ 'dir' => $dir,
+ 'name' => $file,
+ 'md5' => $md5,
+ 'remote' => 0,
+ };
+
+ bless $self, $class;
+}
+
+=head3 Methods
+
+=head4 C<< $f->localpath >>
+
+Return path on the system eg. F</opt/samba/share/test_tar_mode/file>
+
+=cut
+sub localpath {
+ my $s = shift;
+ if ($s->{remote}) {
+ return cleanpath($main::LOCALPATH.'/'.$s->remotepath);
+ }
+ else {
+ return cleanpath($s->{dir}.'/'.$s->{name});
+ }
+}
+
+=head4 C<< $f->remotepath >>
+
+Return path on the server share.
+
+Return C<undef> if the file is local.
+
+=cut
+sub remotepath {
+ my ($s) = @_;
+ return undef if !$s->{remote};
+ my $r = $s->{dir}.'/'.$s->{name};
+ $r =~ s{^/}{};
+ return cleanpath($r);
+}
+
+
+=head4 C<< $f->remotedir >>
+
+Return the directory path of the file on the server.
+
+Like C<< $f->remotepath >> but without the final file name.
+
+=cut
+sub remotedir {
+ my $s = shift;
+ return undef if !$s->{remote};
+ cleanpath($s->{dir});
+}
+
+=head4 C<< $f->tarpath >>
+
+Return path as it would appear in a tar archive.
+
+Like C<< $f->remotepath >> but prefixed with F<./>
+
+=cut
+sub tarpath {
+ my $s = shift;
+ return undef if !$s->{remote};
+ my $r = $s->remotepath;
+ $r =~ s{^\./+}{};
+ return $r;
+}
+
+=head4 C<< $f->delete_on_destruction( 0 ) >>
+
+=head4 C<< $f->delete_on_destruction( 1 ) >>
+
+By default, a C<File> is not deleted on the filesystem when it is not
+referenced anymore in Perl memory.
+
+When set to 1, the destructor unlink the file if it is not already removed.
+If the C<File> created directories when constructed, it does not remove them.
+
+=cut
+sub delete_on_destruction {
+ my ($s, $delete) = @_;
+ $s->{delete_on_destruction} = $delete;
+}
+
+=head4 C<< $f->set_attr( ) >>
+
+=head4 C<< $f->set_attr( 'a' ) >>
+
+=head4 C<< $f->set_attr( 'a', 'r', 's', 'h' ) >>
+
+Remove all DOS attributes and only set the one provided.
+
+=cut
+sub set_attr {
+ my ($s, @flags) = @_;
+ return undef if !$s->{remote};
+
+ $s->{attr} = {qw/r 0 s 0 h 0 a 0 d 0 n 0/};
+
+ for (@flags) {
+ $s->{attr}{lc($_)} = 1;
+ }
+
+ my $file = $s->{name};
+ my @args;
+ if ($s->remotedir) {
+ push @args, '-D', $s->remotedir;
+ }
+ main::smb_client(@args, '-c', qq{setmode "$file" -rsha});
+ if (@flags && $flags[0] !~ /n/i) {
+ main::smb_client(@args, '-c', qq{setmode "$file" +}.join('', @flags));
+ }
+}
+
+=head4 C<< $f->attr_any( 'a' ) >>
+
+=head4 C<< $f->attr_any( 'a', 's', ... ) >>
+
+Return 1 if the file has any of the DOS attributes provided.
+
+=cut
+sub attr_any {
+ my ($s, @flags) = @_;
+ for (@flags) {
+ return 1 if $s->{attr}{$_};
+ }
+ 0;
+}
+
+
+=head4 C<< $f->attr( 'a' ) >>
+
+=head4 C<< $f->attr( 'a', 's', ... ) >>
+
+Return 1 if the file has all the DOS attributes provided.
+
+=cut
+sub attr {
+ my ($s, @flags) = @_;
+ for (@flags) {
+ return 0 if !$s->{attr}{$_};
+ }
+ 1;
+}
+
+=head4 C<< $f->attr_str >>
+
+Return DOS attributes as a compact string.
+
+ Read-only, hidden, system, archive => "rhsa"
+
+=cut
+sub attr_str {
+ my $s = shift;
+ return undef if !$s->{remote};
+ join('', map {$_ if $s->{attr}{$_}} qw/r h s a d n/);
+}
+
+=head4 C<< $f->set_time($t) >>
+
+Set modification and access time of the file to C<$t>.
+
+C<$t> must be in Epoch time (number of seconds since 1970/1/1).
+
+=cut
+sub set_time {
+ my ($s, $t) = @_;
+ utime $t, $t, $s->localpath;
+}
+
+=head4 C<< $f->md5 >>
+
+Return md5 sum of the file.
+
+The result is cached.
+
+=cut
+sub md5 {
+ my $s = shift;
+
+ if (!$s->{md5}) {
+ open my $h, '<', $s->localpath() or die "can't read ".$s->localpath.": $!";
+ binmode $h;
+ $s->{md5} = Digest::MD5->new->addfile($h)->hexdigest;
+ close $h;
+ }
+
+ return $s->{md5};
+}
+
+sub DESTROY {
+ my $s = shift;
+ if ($s->{delete_on_destruction} && -f $s->localpath) {
+ if ($main::DEBUG) {
+ say "DESTROY ".$s->localpath;
+ }
+ unlink $s->localpath;
+ }
+}
+
+=head3 Functions
+
+=head4 C<< File::walk( \&function, @files) >>
+
+=head4 C<< File::walk( sub { ... }, @files) >>
+
+Iterate on file hierarchy in C<@files> and return accumulated results.
+
+Use C<$_> in the sub to access the current C<File>.
+
+The C<@files> must come from a call to the C<File::tree> function.
+
+=cut
+sub walk {
+ my $fun = \&{shift @_};
+
+ my @res;
+
+ for (@_) {
+ if ($_->{attr}{d}) {
+ push @res, walk($fun, @{$_->{content}});
+ } else {
+ push @res, $fun->($_);
+ }
+ }
+
+ return @res;
+}
+
+=head4 C<< File::list( $remotepath ) >>
+
+Return list of file (C<File> instance) in C<$remotepath>.
+
+C<$remotepath> must be a directory.
+
+=cut
+sub list {
+ my ($path) = @_;
+ $path ||= '/';
+ my @files;
+ my $out = main::smb_client('-D', $path, '-c', 'ls');
+ $path =~ s{^/}{};
+
+ for (split /\n/, $out) {
+ next if !/^ (.+?)\s+([AHSRDN]*)\s+(\d+)\s+(.+)/o;
+ my ($fn, $attr, $size, $date) = ($1, $2, $3, $4);
+ next if $fn =~ /^\.{1,2}$/;
+
+ push @files, bless {
+ 'remote' => 1,
+ 'dir' => $path,
+ 'name' => $fn,
+ 'size' => int($size),
+ 'date' => $date,
+ 'attr' => {
+ # list context returns something different than the
+ # boolean matching result => force scalar context
+ 'a' => scalar ($attr =~ /A/),
+ 'h' => scalar ($attr =~ /H/),
+ 's' => scalar ($attr =~ /S/),
+ 'r' => scalar ($attr =~ /R/),
+ 'd' => scalar ($attr =~ /D/),
+ 'n' => scalar ($attr =~ /N/),
+ },
+ }, 'File';
+ }
+ return @files;
+}
+
+=head4 C<< File::tree( $remotepath ) >>
+
+Return recursive list of file in C<$remotepath>.
+
+C<$remotepath> must be a directory.
+
+Use C<File::walk()> to iterate over all the files.
+
+=cut
+sub tree {
+ my ($d) = @_;
+ my @files;
+
+ if (!defined $d) {
+ @files = list();
+ } elsif (blessed $d) {
+ @files = list($d->remotepath);
+ } else {
+ @files = list($d);
+ }
+
+ for my $f (@files) {
+ if ($f->{attr}{d}) {
+ $f->{content} = [tree($f)];
+ }
+ }
+
+ return @files;
+}
+
+# remove trailing or duplicated slash
+sub cleanpath {
+ my $p = shift;
+ $p =~ s{/+}{/}g;
+ $p =~ s{/$}{};
+ $p;
+}
+
+# create random file at path local path $fn
+sub create_file {
+ my ($fn, $size) = @_;
+ my $buf = '';
+ unlink $fn if -e $fn;
+ $size ||= main::random(512, 1024);
+ $size = int($size);
+ my $md5;
+
+ # try /dev/urandom, faster
+ if (-e '/dev/urandom') {
+ my $cmd = sprintf('head -c %d /dev/urandom | tee %s | md5sum',
+ $size, quotemeta($fn));
+ $md5 = (split / /, `$cmd`)[0];
+ } else {
+ open my $out, '>', $fn or die "can't open $fn: $!\n";
+ binmode $out;
+ for (1..$size) {
+ $buf .= pack('C', main::random(0, 256));
+ }
+ print $out $buf;
+ close $out;
+ $md5 = md5_hex($buf);
+ }
+ return $md5;
+}
+
+
+=head3 Examples
+
+ # create remote file in $DIR/foo/bar
+ my $f = File->new_remote("foo/bar/myfile");
+ say $f->localpath; # /opt/share/$DIR/foo/bar/myfile
+ say $f->remotepath; # $DIR/foo/bar/myfile
+ say $f->remotedir; # $DIR/foo/bar
+
+
+ # same but in root dir
+ my $f = File->new_remote("myfile", 1);
+ say $f->localpath; # /opt/share/myfile
+ say $f->remotepath; # myfile
+ say $f->remotedir; #
+
+
+ # create local random temp file in $TMP
+ my $f = File->new_local("$TMP/temp");
+ say $f->remotepath; # undef because it's not on the server
+
+
+ # same but file contains "hello"
+ my $f = File->new_local("$TMP/temp", "hello");
+
+
+ # list of files in $DIR (1 level)
+ for (File::list($DIR)) {
+ say $_->remotepath;
+ }
+
+
+ # list of all files in dir and subdir of $DIR
+ File::walk(sub { say $_->remotepath }, File::tree($DIR));
+
+=cut
+
+1;
diff --git a/source3/script/tests/test_smbclient_tarmode.sh b/source3/script/tests/test_smbclient_tarmode.sh
new file mode 100755
index 0000000..d48e412
--- /dev/null
+++ b/source3/script/tests/test_smbclient_tarmode.sh
@@ -0,0 +1,197 @@
+#!/bin/sh
+
+# this runs a simple tarmode test
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_smbclient_tarmode.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT [create|extract] <smbclient arguments>
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+LOCAL_PATH="$5"
+PREFIX="$6"
+SMBCLIENT="$7"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 7
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+FAILCOUNT=0
+
+# Check command is available
+have_command()
+{
+ type "$1" >/dev/null 2>&1
+ return $?
+}
+
+# Create a test corpus
+create_test_data()
+{
+
+ local DIR="$1"
+ local BS=1024
+ local NUM_FILES=10
+ local NORND_COUNT=25
+
+ # Bomb if dir exists
+ if [ -e "$DIR" ]; then
+ echo "Test data directory '$DIR' already exists!"
+ false
+ return
+ fi
+
+ if ! mkdir -p "$DIR" >/dev/null 2>&1; then
+ echo "Couldn't create test data directory '$DIR'"
+ false
+ return
+ fi
+
+ local I=1
+ if have_command "od"; then # Use random file sizes
+ local RND_COUNT
+ for RND_COUNT in $(od -An -N$NUM_FILES -tu1 </dev/urandom); do
+ if ! dd if=/dev/urandom of="$DIR/file.$I" bs=$BS count=$RND_COUNT >/dev/null 2>&1; then
+ echo "Couldn't create test file '$DIR/file.$I' (random size)"
+ false
+ return
+ fi
+ I=$(expr $I + 1)
+ done
+ else # Fallback to same file sizes
+ while [ $I -le $NUM_FILES ]; do
+ if ! dd if=/dev/urandom of="$DIR/file.$I" bs=$BS count=$NORND_COUNT >/dev/null 2>&1; then
+ echo "Couldn't create test file '$DIR/file.$I' (static size)"
+ false
+ return
+ fi
+ I=$(expr $I + 1)
+ done
+ fi
+
+ true
+ return
+
+}
+
+# Check that two directories are equivalent (In Data content)
+validate_data()
+{
+ local DIR1="$1"
+ local DIR2="$2"
+
+ diff -r "$DIR1" "$DIR2"
+ return $?
+}
+
+# Test tarmode -Tc
+test_tarmode_creation()
+{
+
+ # Clear temp data
+ rm -rf -- "$PREFIX"/tarmode >/dev/null 2>&1
+ rm -f "$PREFIX"/tarmode.tar >/dev/null 2>&1
+ $SMBCLIENT //$SERVER/tarmode $CONFIGURATION -U$USERNAME%$PASSWORD -c "deltree smbclient_tar"
+
+ # Build the test data
+ if ! create_test_data "$LOCAL_PATH"; then
+ echo "Test data creation failed"
+ false
+ return
+ fi
+
+ # Create tarfile with smbclient
+ if ! $SMBCLIENT //$SERVER/tarmode $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 \
+ $ADDARGS -c "tarmode full" -Tc "$PREFIX/tarmode.tar" "/smbclient_tar"; then
+ echo "Couldn't create tar file with tarmode -Tc"
+ false
+ return
+ fi
+
+ # Extract data to verify - this puts it into $PREFIX/smbclient_tar/
+ # but we must leave it there as it's used to verify in test_tarmode_extraction()
+ if ! tar -xf "$PREFIX/tarmode.tar" -C "$PREFIX"; then
+ echo "Couldn't extract data from created tarfile"
+ false
+ return
+ fi
+
+ # Verify data
+ if ! validate_data "$PREFIX/smbclient_tar" "$LOCAL_PATH"; then
+ echo "Data not equivalent"
+ false
+ return
+ fi
+
+ # Clear temp data
+ rm -rf -- "$PREFIX"/tarmode >/dev/null 2>&1
+ rm -f "$PREFIX"/tarmode.tar >/dev/null 2>&1
+ $SMBCLIENT //$SERVER/tarmode $CONFIGURATION -U$USERNAME%$PASSWORD -c "deltree smbclient_tar"
+ true
+ return
+
+}
+
+# Test tarmode -Tx
+test_tarmode_extraction()
+{
+
+ # Clear temp data
+ rm -rf -- "$PREFIX"/tarmode >/dev/null 2>&1
+ rm -f "$PREFIX"/tarmode.tar >/dev/null 2>&1
+ $SMBCLIENT //$SERVER/tarmode $CONFIGURATION -U$USERNAME%$PASSWORD -c "deltree smbclient_tar"
+
+ # Build the test data
+ if ! create_test_data "$PREFIX/tarmode"; then
+ echo "Test data creation failed"
+ false
+ return
+ fi
+
+ # Create tarfile to extract on client
+ if ! tar -cf "$PREFIX/tarmode.tar" -C "$PREFIX" smbclient_tar; then
+ echo "Couldn't create tar archive"
+ false
+ return
+ fi
+
+ # Extract tarfile with smbclient
+ if ! $SMBCLIENT //$SERVER/tarmode $CONFIGURATION -U$USERNAME%$PASSWORD -I $SERVER_IP -p 139 \
+ $ADDARGS -c "tarmode full" -Tx "$PREFIX/tarmode.tar"; then
+ echo "Couldn't extract tar file with tarmode -Tx"
+ false
+ return
+ fi
+
+ # Verify data
+ if ! validate_data "$PREFIX/smbclient_tar" "$LOCAL_PATH"; then
+ echo "Data not equivalent"
+ false
+ return
+ fi
+
+ # Clear temp data
+ rm -rf -- "$PREFIX"/tarmode >/dev/null 2>&1
+ rm -f "$PREFIX"/tarmode.tar >/dev/null 2>&1
+ $SMBCLIENT //$SERVER/tarmode $CONFIGURATION -U$USERNAME%$PASSWORD -c "deltree smbclient_tar"
+ # Cleanup the verification data created by test_tarmode_creation().
+ rm -rf "$PREFIX"/smbclient_tar >/dev/null 2>&1
+ true
+ return
+
+}
+
+testit "test_tarmode_creation" \
+ test_tarmode_creation || FAILCOUNT=$(expr $FAILCOUNT + 1)
+
+testit "test_tarmode_extraction" \
+ test_tarmode_extraction || FAILCOUNT=$(expr $FAILCOUNT + 1)
+
+testok $0 $FAILCOUNT
diff --git a/source3/script/tests/test_smbcquota.py b/source3/script/tests/test_smbcquota.py
new file mode 100755
index 0000000..02229d7
--- /dev/null
+++ b/source3/script/tests/test_smbcquota.py
@@ -0,0 +1,244 @@
+#!/usr/bin/env python3
+# Unix SMB/CIFS implementation.
+# Tests for smbcquotas
+# Copyright (C) Noel Power 2017
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os, subprocess, sys
+import traceback
+import logging
+import shutil
+
+USER_QUOTAS = 1
+USER_DEFAULT_QUOTAS = 2
+GROUP_QUOTAS = 3
+GROUP_DEFAULT_QUOTAS = 4
+BLOCK_SIZE = 1024
+DEFAULT_SOFTLIM = 2
+DEFAULT_HARDLIM = 4
+
+class test_env:
+ def __init__(self):
+ self.server = None
+ self.domain = None
+ self.username = None
+ self.password = None
+ self.envdir = None
+ self.quota_db = None
+ self.smbcquotas = None
+ self.users = []
+
+class user_info:
+ def __init__(self):
+ self.uid = 0
+ self.username = ""
+ self.softlim = 0
+ self.hardlim = 0
+
+class Quota:
+ def __init__(self):
+ self.flags = 0
+ self.quotatype = USER_DEFAULT_QUOTAS
+ self.uid = 0
+ self.usedblocks = 0
+ self.softlimit = 0
+ self.hardlimit = 0
+ self.hardlimit = 0
+ self.usedinodes = 0
+ self.slimitinodes = 0
+ self.hlimitinodes = 0
+
+def init_quota_db(users, output_file):
+ filecontents = open(output_file,"w+")
+ lines = ""
+ default_values = "0 " + str(DEFAULT_SOFTLIM) + " " + str(DEFAULT_HARDLIM) + " 0 0 0"
+ for user in users:
+ lines = lines + user.uid + " " + default_values + "\n"
+ filecontents.write(lines)
+ filecontents.close()
+
+def load_quotas(input_file):
+ fileContents = open(input_file,"r")
+ lineno = 0
+ quotas = []
+ for line in fileContents:
+ if line.strip().startswith("#"):
+ continue
+ content = line.strip().split()
+ quota = Quota()
+ if len(content) < 7:
+ logging.debug("ignoring line %d, doesn't have enough fields\n"%lineno)
+ else:
+ quota.flags = 2
+ quota.uid = content[0]
+ quota.usedblocks = content[1]
+ quota.softlimit = content[2]
+ quota.hardlimit = content[3]
+ quota.usedinodes = content[4]
+ quota.slimitinodes = content[5]
+ quota.hlimitinodes = content[6]
+ quotas.append(quota)
+
+ fileContents.close()
+ return quotas
+
+def get_quotas(uid, quota_list):
+ for quota in quota_list:
+ if quota.uid == uid:
+ return quota
+ return None
+
+def get_users():
+ output = subprocess.Popen(['getent', 'passwd'],
+ stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+ users = []
+ for line in output:
+ info = line.split(':')
+ if len(info) > 3 and info[0]:
+ user = user_info()
+ user.username = info[0]
+ user.uid = info[2]
+ logging.debug("Adding user ->%s<-\n"%user.username)
+ users.append(user)
+ return users
+
+
+
+def smbcquota_output_to_userinfo(output):
+ infos = []
+ for line in output:
+ if len(line) > 1:
+ username = line.strip(':').split()[0]
+ quota_info = line.split(':')[1].split('/')
+ if len(quota_info) > 2:
+ info = user_info()
+ info.username = username.strip()
+ info.softlim = int(quota_info[1].strip()) / BLOCK_SIZE
+ info.hardlim = int(quota_info[2].strip()) / BLOCK_SIZE
+ infos.append(info)
+ return infos
+
+def check_quota_limits(infos, softlim, hardlim):
+ if len(infos) < 1:
+ logging.debug("no users info to check :-(\n")
+ return False
+ for info in infos:
+ if int(info.softlim) != softlim:
+ logging.debug("expected softlimit %s got ->%s<-\n"%(softlim, info.softlim))
+ return False
+ if int(info.hardlim) != hardlim:
+ logging.debug("expected hardlimit limit %s got %s\n"%(hardlim,info.hardlim))
+ return False
+ return True
+
+class test_base:
+ def __init__(self, env):
+ self.env = env
+ def run(self, protocol):
+ pass
+
+class listtest(test_base):
+ def run(self, protocol):
+ init_quota_db(self.env.users, self.env.quota_db)
+ quotas = load_quotas(self.env.quota_db)
+ args = [self.env.smbcquotas]
+ remaining_args = ['-U' + self.env.username + "%" + self.env.password, '-L', '//' + self.env.server + '/quotadir']
+ if protocol == 'smb2':
+ args.append('-m smb2')
+ args.extend(remaining_args)
+ output = subprocess.Popen([self.env.smbcquotas, '-U' + self.env.username + "%" + self.env.password, '-L', '//' + self.env.server + '/quotadir'], stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+ infos = smbcquota_output_to_userinfo(output)
+ return check_quota_limits(infos, DEFAULT_SOFTLIM, DEFAULT_HARDLIM)
+def get_uid(name, users):
+ for user in users:
+ if user.username == name:
+ return user.uid
+ return None
+
+class gettest(test_base):
+ def run(self, protocol):
+ init_quota_db(self.env.users, self.env.quota_db)
+ quotas = load_quotas(self.env.quota_db)
+ uid = get_uid(self.env.username, self.env.users)
+ output = subprocess.Popen([self.env.smbcquotas, '-U' + self.env.username + "%" + self.env.password, '-u' + self.env.username, '//' + self.env.server + '/quotadir'], stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+ user_infos = smbcquota_output_to_userinfo(output)
+ db_user_info = get_quotas(uid, quotas)
+ # double check, we compare the results from the db file
+ # the quota script the server uses compared to what
+ # smbcquota is telling us
+ return check_quota_limits(user_infos, int(db_user_info.softlimit), int(db_user_info.hardlimit))
+
+class settest(test_base):
+ def run(self, protocol):
+ init_quota_db(self.env.users, self.env.quota_db)
+ quotas = load_quotas(self.env.quota_db)
+ uid = get_uid(self.env.username, self.env.users)
+ old_db_user_info = get_quotas(uid, quotas)
+
+ #increase limits by 2 blocks
+ new_soft_limit = (int(old_db_user_info.softlimit) + 2) * BLOCK_SIZE
+ new_hard_limit = (int(old_db_user_info.hardlimit) + 2) * BLOCK_SIZE
+
+ new_limits = "UQLIM:%s:%d/%d"%(self.env.username, new_soft_limit, new_hard_limit)
+ logging.debug("setting new limits %s"%new_limits)
+
+ output = subprocess.Popen([self.env.smbcquotas, '-U' + self.env.username + "%" + self.env.password, '//' + self.env.server + '/quotadir', '-S', new_limits], stdout=subprocess.PIPE).communicate()[0].decode("utf-8").split('\n')
+ logging.debug("output from smbcquota is %s"%output)
+ user_infos = smbcquota_output_to_userinfo(output)
+ return check_quota_limits(user_infos, new_soft_limit / BLOCK_SIZE, new_hard_limit / BLOCK_SIZE)
+
+# map of tests
+subtest_descriptions = {
+ "list test" : listtest,
+ "get test" : gettest,
+ "set test" : settest
+}
+
+def main():
+ logging.basicConfig(format='%(asctime)s %(message)s', level=logging.DEBUG)
+
+ logging.debug("got args %s\n"%str(sys.argv))
+
+ if len(sys.argv) < 7:
+ logging.debug ("Usage: test_smbcquota.py server domain username password envdir smbcquotas\n")
+ sys.exit(1)
+ env = test_env()
+ env.server = sys.argv[1]
+ env.domain = sys.argv[2]
+ env.username = sys.argv[3]
+ env.password = sys.argv[4]
+ env.envdir = sys.argv[5]
+ env.smbcquotas = sys.argv[6]
+ quota_script = os.path.join(os.path.dirname(sys.argv[0]),
+ "getset_quota.py")
+ #copy the quota script to the environment
+ shutil.copy2(quota_script, env.envdir)
+
+ env.quota_db = os.path.join(env.envdir, "quotas.db")
+ env.users = get_users()
+ for protocol in ['smb1', 'smb2']:
+ for key in subtest_descriptions.keys():
+ test = subtest_descriptions[key](env)
+ logging.debug("running subtest '%s' using protocol '%s'\n"%(key,protocol))
+ result = test.run(protocol)
+ if result == False:
+ logging.debug("subtest '%s' for '%s' failed\n"%(key,protocol))
+ sys.exit(1)
+ else:
+ logging.debug("subtest '%s' for '%s' passed\n"%(key,protocol))
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/source3/script/tests/test_smbcquota.sh b/source3/script/tests/test_smbcquota.sh
new file mode 100755
index 0000000..c001192
--- /dev/null
+++ b/source3/script/tests/test_smbcquota.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+# Unix SMB/CIFS implementation.
+# Tests for smbcquotas
+# Copyright (C) Noel Power 2017
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+#
+# Blackbox test wrapper for smbcquota
+#
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: test_smbcquota.sh SERVER DOMAIN USERNAME PASSWORD LOCAL_PATH SMBCQUOTAS
+EOF
+ exit 1
+fi
+
+SERVER=$1
+DOMAIN=$2
+USERNAME=$3
+PASSWORD=$4
+ENVDIR=$(dirname $5)
+SMBCQUOTAS="$VALGRIND $6"
+shift 6
+
+TEST_SMBCQUOTAS=$(dirname $0)/test_smbcquota.py
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+testit "smbcquotas" ${TEST_SMBCQUOTAS} ${SERVER} ${DOMAIN} ${USERNAME} ${PASSWORD} ${ENVDIR} ${SMBCQUOTAS} || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbd_error.sh b/source3/script/tests/test_smbd_error.sh
new file mode 100755
index 0000000..d39b174
--- /dev/null
+++ b/source3/script/tests/test_smbd_error.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Test smbd with failing chdir system call.
+#
+# Verify that smbd does not panic when the chdir system call is
+# returning an error. ensure that the output format for ACL entries
+#
+# Copyright (C) 2017 Christof Schmitt
+
+. $(dirname $0)/../../../testprogs/blackbox/subunit.sh
+failed=0
+
+if [ $SMBD_DONT_LOG_STDOUT -eq 1 ]; then
+ subunit_start_test "check_panic_0"
+ subunit_skip_test "check_panic_0" <<EOF
+logging to stdout disabled
+EOF
+
+ testok $0 $failed
+fi
+
+error_inject_conf=$(dirname $SMB_CONF_PATH)/error_inject.conf
+
+panic_count_0=$(grep -c PANIC $SMBD_TEST_LOG)
+
+#
+# Verify that a panic in smbd will result in a PANIC message in the log
+#
+
+# As a panic is expected here, also overwrite the default "panic
+# action" in selftest to not start a debugger
+echo 'error_inject:chdir = panic' >$error_inject_conf
+echo '[global]' >>$error_inject_conf
+echo 'panic action = ""' >>$error_inject_conf
+
+testit_expect_failure "smbclient" $VALGRIND \
+ $BINDIR/smbclient //$SERVER_IP/error_inject \
+ -U$USERNAME%$PASSWORD -c dir ||
+ failed=$(expr $failed + 1)
+
+rm $error_inject_conf
+
+panic_count_1=$(grep -c PANIC $SMBD_TEST_LOG)
+
+testit "check_panic_1" test $(expr $panic_count_0 + 1) -eq $panic_count_1 ||
+ failed=$(expr $failed + 1)
+
+#
+# Verify that a failing chdir vfs call does not result in a smbd panic
+#
+
+echo 'error_inject:chdir = ESTALE' >$error_inject_conf
+
+testit_expect_failure "smbclient" $VALGRIND \
+ $BINDIR/smbclient //$SERVER_IP/error_inject \
+ -U$USERNAME%$PASSWORD -c dir ||
+ failed=$(expr $failed + 1)
+
+panic_count_2=$(grep -c PANIC $SMBD_TEST_LOG)
+
+testit "check_panic_2" test $panic_count_1 -eq $panic_count_2 ||
+ failed=$(expr $failed + 1)
+
+rm $error_inject_conf
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbd_no_krb5.sh b/source3/script/tests/test_smbd_no_krb5.sh
new file mode 100755
index 0000000..faa46c9
--- /dev/null
+++ b/source3/script/tests/test_smbd_no_krb5.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+if [ $# -lt 1 ]; then
+ cat <<EOF
+Usage: test_smbd_no_krb5.sh SERVER USERNAME PASSWORD PREFIX
+EOF
+ exit 1
+fi
+
+smbclient=$1
+SERVER=$2
+USERNAME=$3
+PASSWORD=$4
+PREFIX=$5
+shift 5
+
+samba_bindir="$BINDIR"
+samba_kinit=kinit
+if test -x ${samba_bindir}/samba4kinit; then
+ samba_kinit=${samba_bindir}/samba4kinit
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+failed=0
+
+opt="--option=gensec:gse_krb5=yes -U${USERNAME}%${PASSWORD}"
+
+# check kerberos access
+test_smbclient "test_krb5" "ls" "//$SERVER/tmp" $opt --use-kerberos=required || failed=$(expr $failed + 1)
+
+# disable krb5 globally so smbd won't accept it
+global_inject_conf=$(dirname $SMB_CONF_PATH)/global_inject.conf
+echo 'gensec:gse_krb5=no' >$global_inject_conf
+
+# verify that kerberos fails
+test_smbclient_expect_failure "smbd_no_krb5" "ls" "//$SERVER/tmp" --use-kerberos=required $opt || failed=$(expr $failed + 1)
+
+# verify downgrade to ntlmssp
+test_smbclient "test_spnego_downgrade" "ls" "//$SERVER/tmp" $opt --use-kerberos=disabled || failed=$(expr $failed + 1)
+
+echo '' >$global_inject_conf
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbget.sh b/source3/script/tests/test_smbget.sh
new file mode 100755
index 0000000..acc492d
--- /dev/null
+++ b/source3/script/tests/test_smbget.sh
@@ -0,0 +1,619 @@
+#!/usr/bin/env bash
+#
+# Blackbox test for smbget.
+#
+
+if [ $# -lt 8 ]; then
+ cat <<EOF
+Usage: test_smbget SERVER SERVER_IP DOMAIN REALM USERNAME PASSWORD WORKDIR SMBGET
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+DOMAIN=${3}
+REALM=${4}
+USERNAME=${5}
+PASSWORD=${6}
+DOMAIN_USER=${7}
+DOMAIN_USER_PASSWORD=${8}
+WORKDIR=${9}
+SMBGET="$VALGRIND ${10}"
+shift 10
+
+TMPDIR="$SELFTEST_TMPDIR"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. "${incdir}/common_test_fns.inc"
+
+samba_kinit=$(system_or_builddir_binary kinit "${BINDIR}" samba4kinit)
+samba_texpect="${BINDIR}/texpect"
+
+create_test_data()
+{
+ pushd $WORKDIR
+ dd if=/dev/urandom bs=1024 count=128 of=testfile
+ chmod 644 testfile
+ mkdir dir1
+ dd if=/dev/urandom bs=1024 count=128 of=dir1/testfile1
+ mkdir dir2
+ dd if=/dev/urandom bs=1024 count=128 of=dir2/testfile2
+ popd
+}
+
+remove_test_data()
+{
+ pushd $WORKDIR
+ rm -rf dir1 dir2 testfile
+ popd
+}
+
+clear_download_area()
+{
+ rm -rf dir1 dir2 testfile dir001 dir004 readable_file
+}
+
+test_singlefile_guest()
+{
+ clear_download_area
+ echo "$SMBGET --verbose --guest smb://$SERVER_IP/smbget_guest/testfile"
+ $SMBGET --verbose --guest smb://$SERVER_IP/smbget_guest/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+ return 0
+}
+
+test_singlefile_U()
+{
+ clear_download_area
+ $SMBGET --verbose -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+ return 0
+}
+
+test_singlefile_U_UPN()
+{
+ clear_download_area
+
+ ${SMBGET} --verbose -U"${DOMAIN_USER}@${REALM}%${DOMAIN_USER_PASSWORD}" \
+ "smb://${SERVER_IP}/smbget/testfile"
+ ret=${?}
+ if [ ${ret} -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent "${WORKDIR}/testfile" ./testfile
+ ret=${?}
+ if [ ${ret} -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+test_singlefile_U_domain()
+{
+ clear_download_area
+
+ ${SMBGET} --verbose -U"${DOMAIN}/${DOMAIN_USER}%${DOMAIN_USER_PASSWORD}" \
+ "smb://${SERVER_IP}/smbget/testfile"
+ ret=${?}
+ if [ ${ret} -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent "${WORKDIR}/testfile" ./testfile
+ ret=${?}
+ if [ ${ret} -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+test_singlefile_smburl()
+{
+ clear_download_area
+ $SMBGET --workgroup $DOMAIN smb://${DOMAIN_USER}:$DOMAIN_USER_PASSWORD@$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+ return 0
+}
+
+test_singlefile_smburl2()
+{
+ clear_download_area
+ $SMBGET "smb://$DOMAIN;${DOMAIN_USER}:$DOMAIN_USER_PASSWORD@$SERVER_IP/smbget/testfile"
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+ return 0
+}
+
+test_singlefile_smburl_interactive()
+{
+ clear_download_area
+
+ tmpfile="$(mktemp --tmpdir="${TMPDIR}" expect_XXXXXXXXXX)"
+
+ cat >"${tmpfile}" <<EOF
+expect Password for
+send ${DOMAIN_USER_PASSWORD}\n
+EOF
+
+ USER="hanswurst" ${samba_texpect} "${tmpfile}" ${SMBGET} "smb://${DOMAIN};${DOMAIN_USER}@${SERVER_IP}/smbget/testfile"
+ ret=$?
+ rm -f "${tmpfile}"
+ if [ ${ret} -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ ret=$?
+ if [ ${ret} -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+ return 0
+}
+
+test_singlefile_authfile()
+{
+ clear_download_area
+ cat >"${TMPDIR}/authfile" << EOF
+username = ${SERVER}/${USERNAME}
+password = $PASSWORD
+EOF
+ $SMBGET --verbose --authentication-file="${TMPDIR}/authfile" smb://$SERVER_IP/smbget/testfile
+ rc=$?
+ rm -f $TMPDIR/authfile
+ if [ $rc -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+ return 0
+}
+
+test_recursive_U()
+{
+ clear_download_area
+ $SMBGET --verbose --recursive -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent $WORKDIR/testfile ./testfile &&
+ cmp --silent $WORKDIR/dir1/testfile1 ./dir1/testfile1 &&
+ cmp --silent $WORKDIR/dir2/testfile2 ./dir2/testfile2
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+test_recursive_existing_dir()
+{
+ clear_download_area
+ mkdir dir1
+ $SMBGET --verbose --recursive -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent $WORKDIR/testfile ./testfile &&
+ cmp --silent $WORKDIR/dir1/testfile1 ./dir1/testfile1 &&
+ cmp --silent $WORKDIR/dir2/testfile2 ./dir2/testfile2
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+test_recursive_with_empty()
+{ # see Bug 13199
+ clear_download_area
+ # create some additional empty directories
+ mkdir -p $WORKDIR/dir001/dir002/dir003
+ mkdir -p $WORKDIR/dir004/dir005/dir006
+ $SMBGET --verbose --recursive -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/
+ rc=$?
+ rm -rf $WORKDIR/dir001
+ rm -rf $WORKDIR/dir004
+ if [ $rc -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent $WORKDIR/testfile ./testfile &&
+ cmp --silent $WORKDIR/dir1/testfile1 ./dir1/testfile1 &&
+ cmp --silent $WORKDIR/dir2/testfile2 ./dir2/testfile2
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ if [ ! -d dir001/dir002/dir003 ] || [ ! -d dir004/dir005/dir006 ]; then
+ echo 'ERROR: empty directories are not present'
+ return 1
+ fi
+
+ return 0
+}
+
+test_resume()
+{
+ clear_download_area
+ cp $WORKDIR/testfile .
+ truncate -s 1024 testfile
+ $SMBGET --verbose --resume -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+test_resume_modified()
+{
+ clear_download_area
+ dd if=/dev/urandom bs=1024 count=2 of=testfile
+ $SMBGET --verbose --resume -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 1 ]; then
+ echo 'ERROR: RC does not match, expected: 1'
+ return 1
+ fi
+
+ return 0
+}
+
+test_update()
+{
+ clear_download_area
+ $SMBGET --verbose -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ # secondary download should pass
+ $SMBGET --verbose --update -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ echo "modified" >>testfile
+ # touch source to trigger new download
+ sleep 1
+ touch -m $WORKDIR/testfile
+ $SMBGET --verbose --update -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+# Test accessing an msdfs path.
+test_msdfs_link()
+{
+ clear_download_area
+
+ ${SMBGET} --verbose "-U${SERVER}/${USERNAME}%${PASSWORD}" \
+ "smb://${SERVER}/msdfs-share/deeppath/msdfs-src2/readable_file"
+ ret=$?
+ if [ ${ret} -ne 0 ]; then
+ echo "ERROR: smbget failed with ${ret}"
+ return 1
+ fi
+
+ return 0
+}
+
+test_msdfs_link_domain()
+{
+ clear_download_area
+
+ ${SMBGET} --verbose "-U${DOMAIN}/${DOMAIN_USER}%${DOMAIN_USER_PASSWORD}" \
+ "smb://${SERVER}/msdfs-share/deeppath/msdfs-src2/readable_file"
+ ret=$?
+ if [ ${ret} -ne 0 ]; then
+ echo "ERROR: smbget failed with ${ret}"
+ return 1
+ fi
+
+ return 0
+}
+
+test_msdfs_link_upn()
+{
+ clear_download_area
+
+ ${SMBGET} --verbose "-U${DOMAIN_USER}@${REALM}%${DOMAIN_USER_PASSWORD}" \
+ "smb://${SERVER}/msdfs-share/deeppath/msdfs-src2/readable_file"
+ ret=$?
+ if [ ${ret} -ne 0 ]; then
+ echo "ERROR: smbget failed with ${ret}"
+ return 1
+ fi
+
+ return 0
+}
+
+# Tests --limit-rate. Getting the testfile (128K in size) with --limit-rate 100
+# (that is 100KB/s) should take at least 1 sec to complete.
+test_limit_rate()
+{
+ clear_download_area
+ echo "$SMBGET --verbose --guest --limit-rate 100 smb://$SERVER_IP/smbget_guest/testfile"
+ time_begin=$(date +%s)
+ $SMBGET --verbose --guest --limit-rate 100 smb://$SERVER_IP/smbget_guest/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ time_end=$(date +%s)
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+ if [ $((time_end - time_begin)) -lt 1 ]; then
+ echo 'ERROR: It should take at least 1s to transfer 128KB with rate 100KB/s'
+ return 1
+ fi
+ return 0
+}
+
+test_encrypt()
+{
+ clear_download_area
+ $SMBGET --verbose --encrypt -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ clear_download_area
+ $SMBGET --verbose --client-protection=encrypt -U${SERVER}/${USERNAME}%$PASSWORD smb://$SERVER_IP/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+test_kerberos()
+{
+ clear_download_area
+
+ KRB5CCNAME_PATH="${TMPDIR}/smget_krb5ccache"
+ rm -f "${KRB5CCNAME_PATH}"
+
+ KRB5CCNAME="FILE:${KRB5CCNAME_PATH}"
+ export KRB5CCNAME
+ kerberos_kinit "${samba_kinit}" \
+ "${DOMAIN_USER}@${REALM}" "${DOMAIN_USER_PASSWORD}"
+ if [ $? -ne 0 ]; then
+ echo 'Failed to get Kerberos ticket'
+ return 1
+ fi
+
+ $SMBGET --verbose --use-krb5-ccache="${KRB5CCNAME}" \
+ smb://$SERVER/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ rm -f "${KRB5CCNAME_PATH}"
+
+ return 0
+}
+
+test_kerberos_trust()
+{
+ clear_download_area
+
+ $SMBGET --verbose --use-kerberos=required \
+ -U"${TRUST_F_BOTH_USERNAME}@${TRUST_F_BOTH_REALM}%${TRUST_F_BOTH_PASSWORD}" \
+ smb://$SERVER.${REALM}/smbget/testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: RC does not match, expected: 0'
+ return 1
+ fi
+
+ cmp --silent $WORKDIR/testfile ./testfile
+ if [ $? -ne 0 ]; then
+ echo 'ERROR: file content does not match'
+ return 1
+ fi
+
+ return 0
+}
+
+# TODO FIXME
+# This test does not work, as we can't tell the libsmb code that the
+# principal is an enterprice principal. We need support for enterprise
+# principals in kerberos_kinit_password_ext() and a way to pass it via the
+# credenitals structure and commandline options.
+# It works if you do: kinit -E testdenied_upn@${REALM}.upn
+#
+# test_kerberos_upn_denied()
+# {
+# set -x
+# clear_download_area
+#
+# $SMBGET --verbose --use-kerberos=required \
+# -U"testdenied_upn@${REALM}.upn%${DC_PASSWORD}" \
+# "smb://${SERVER}.${REALM}/smbget/testfile" -d10
+# if [ $? -ne 0 ]; then
+# echo 'ERROR: RC does not match, expected: 0'
+# return 1
+# fi
+#
+# cmp --silent $WORKDIR/testfile ./testfile
+# if [ $? -ne 0 ]; then
+# echo 'ERROR: file content does not match'
+# return 1
+# fi
+#
+# return 0
+# }
+
+create_test_data
+
+pushd $TMPDIR
+
+failed=0
+testit "download single file as guest" test_singlefile_guest ||
+ failed=$(expr $failed + 1)
+
+testit "download single file with -U" test_singlefile_U ||
+ failed=$(expr $failed + 1)
+
+testit "download single file with --update and domain" test_singlefile_U_domain ||
+ failed=$((failed + 1))
+
+testit "download single file with --update and UPN" test_singlefile_U_UPN ||
+ failed=$((failed + 1))
+
+testit "download single file with smb URL" test_singlefile_smburl ||
+ failed=$(expr $failed + 1)
+
+testit "download single file with smb URL including domain" \
+ test_singlefile_smburl2 ||
+ failed=$(expr $failed + 1)
+
+testit "download single file with smb URL interactive" \
+ test_singlefile_smburl_interactive ||
+ failed=$(expr $failed + 1)
+
+testit "download single file with authfile" test_singlefile_authfile ||
+ failed=$(expr $failed + 1)
+
+testit "recursive download" test_recursive_U ||
+ failed=$(expr $failed + 1)
+
+testit "recursive download (existing target dir)" test_recursive_existing_dir ||
+ failed=$(expr $failed + 1)
+
+testit "recursive download with empty directories" test_recursive_with_empty ||
+ failed=$(expr $failed + 1)
+
+testit "resume download" test_resume ||
+ failed=$(expr $failed + 1)
+
+testit "resume download (modified file)" test_resume_modified ||
+ failed=$(expr $failed + 1)
+
+testit "update" test_update ||
+ failed=$(expr $failed + 1)
+
+testit "msdfs" test_msdfs_link ||
+ failed=$((failed + 1))
+
+testit "msdfs.domain" test_msdfs_link_domain ||
+ failed=$((failed + 1))
+
+testit "msdfs.upn" test_msdfs_link_upn ||
+ failed=$((failed + 1))
+
+testit "limit rate" test_limit_rate ||
+ failed=$((failed + 1))
+
+testit "encrypt" test_encrypt ||
+ failed=$((failed + 1))
+
+testit "kerberos" test_kerberos ||
+ failed=$((failed + 1))
+
+testit "kerberos_trust" test_kerberos_trust ||
+ failed=$((failed + 1))
+
+# testit "kerberos_upn_denied" test_kerberos_upn_denied ||
+# failed=$((failed + 1))
+
+clear_download_area
+
+popd # TMPDIR
+
+remove_test_data
+
+exit $failed
diff --git a/source3/script/tests/test_smbpasswd.sh b/source3/script/tests/test_smbpasswd.sh
new file mode 100755
index 0000000..b4d4f40
--- /dev/null
+++ b/source3/script/tests/test_smbpasswd.sh
@@ -0,0 +1,133 @@
+#!/bin/sh
+#
+# Blackbox tests for smbpasswd
+#
+# Copyright (c) 2015-2016 Andreas Schneider <asn@samba.org>
+#
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_smbpasswd.sh SERVER USERNAME PASSWORD
+EOF
+ exit 1
+fi
+
+SERVER=$1
+SERVER_IP=$2
+USERNAME=$3
+PASSWORD=$4
+shift 4
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+samba_bindir="$BINDIR"
+samba_srcdir="$SRCDIR"
+
+samba_texpect="$samba_bindir/texpect"
+samba_smbpasswd="$samba_bindir/smbpasswd"
+
+samba_test_user="alice_smbpasswd"
+samba_test_user_pwd="Secret007"
+samba_test_user_new_pwd="Secret008"
+
+create_local_smb_user()
+{
+ user=$1
+ password=$2
+
+ tmpfile=$PREFIX/smbpasswd_create_user_script
+ cat >$tmpfile <<EOF
+expect New SMB password:
+send $password\n
+expect Retype new SMB password:
+send $password\n
+EOF
+
+ cmd='UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $samba_texpect $tmpfile $samba_smbpasswd -c $SMB_CONF_PATH -a $user 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "Failed to create smb user $user"
+ return 1
+ fi
+
+ getent passwd $user
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed to create smb user $user"
+ return 1
+ fi
+}
+
+delete_local_smb_user()
+{
+ user=$1
+
+ # This also deletes the unix account!
+ UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $samba_smbpasswd -c $SMB_CONF_PATH -x $user
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed to delete smb user $user"
+ return 1
+ fi
+}
+
+test_smbpasswd()
+{
+ user=$1
+ oldpwd=$2
+ newpwd=$3
+
+ user_id=$(id -u $user)
+
+ tmpfile=$PREFIX/smbpasswd_change_password_script
+ cat >$tmpfile <<EOF
+expect Old SMB password:
+send $oldpwd\n
+expect New SMB password:
+send $newpwd\n
+expect Retype new SMB password:
+send $newpwd\n
+EOF
+
+ cmd='UID_WRAPPER_INITIAL_RUID=$user_id UID_WRAPPER_INITIAL_EUID=$user_id $samba_texpect $tmpfile $samba_smbpasswd -c $SMB_CONF_PATH -r $SERVER 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+ if [ $ret -ne 0 ]; then
+ echo "Failed to change user password $user"
+ echo "${out}"
+ return 1
+ fi
+
+ prompt="Password changed for user $user"
+ echo "$out" | grep "$prompt" >/dev/null 2>&1
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed to change password for user $user"
+ echo "$out"
+ return 1
+ fi
+}
+
+testit "Create user $samba_test_user" \
+ create_local_smb_user $samba_test_user $samba_test_user_pwd ||
+ failed=$(expr $failed + 1)
+
+testit "Change user password" \
+ test_smbpasswd $samba_test_user $samba_test_user_pwd $samba_test_user_new_pwd ||
+ failed=$(expr $failed + 1)
+
+testit "Delete user $samba_test_user" \
+ delete_local_smb_user $samba_test_user ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_smbspool.sh b/source3/script/tests/test_smbspool.sh
new file mode 100755
index 0000000..2036d57
--- /dev/null
+++ b/source3/script/tests/test_smbspool.sh
@@ -0,0 +1,294 @@
+#!/bin/sh
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_smbspool.sh SERVER SERVER_IP USERNAME PASSWORD TARGET_ENV
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SERVER_IP="$2"
+USERNAME="$3"
+PASSWORD="$4"
+TARGET_ENV="$5"
+shift 5
+ADDARGS="$@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+samba_bindir="$BINDIR"
+samba_vlp="$samba_bindir/vlp"
+samba_smbspool="$samba_bindir/smbspool"
+samba_argv_wrapper="$samba_bindir/smbspool_argv_wrapper"
+samba_smbtorture3="$samba_bindir/smbtorture3"
+samba_smbspool_krb5="$samba_bindir/smbspool_krb5_wrapper"
+
+test_smbspool_noargs()
+{
+ cmd='$1 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed to execute $1"
+ fi
+
+ echo "$out" | grep 'network smb "Unknown" "Windows Printer via SAMBA"'
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "$out"
+ return 1
+ fi
+}
+
+test_smbspool_authinforequired_none()
+{
+ cmd='$samba_smbspool_krb5 smb://$SERVER_IP/print4 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps 2>&1'
+
+ AUTH_INFO_REQUIRED="none"
+ export AUTH_INFO_REQUIRED
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ unset AUTH_INFO_REQUIRED
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed to execute $samba_smbspool_krb5"
+ return 1
+ fi
+
+ return 0
+}
+
+test_smbspool_authinforequired_unknown()
+{
+ cmd='$samba_smbspool_krb5 smb://$SERVER_IP/print4 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps 2>&1'
+
+ # smbspool_krb5_wrapper must ignore AUTH_INFO_REQUIRED unknown to him and pass the task to smbspool
+ # smbspool must fail with NT_STATUS_ACCESS_DENIED (22)
+ # "jjf4wgmsbc0" is just a random string
+ AUTH_INFO_REQUIRED="jjf4wgmsbc0"
+ export AUTH_INFO_REQUIRED
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ unset AUTH_INFO_REQUIRED
+
+ case "$ret" in
+ 2) return 0 ;;
+ *)
+ echo "ret=$ret"
+ echo "$out"
+ echo "failed to test $samba_smbspool_krb5 against unknown value of AUTH_INFO_REQUIRED"
+ return 1
+ ;;
+ esac
+}
+
+#
+# The test environment uses 'vlp' (virtual lp) as the printing backend.
+#
+# When using the vlp backend the print job is only written to the database.
+# The job needs to removed manually using 'vlp lprm' command!
+#
+# This calls the 'vlp' command to check if the print job has been successfully
+# added to the database and also makes sure the temporary print file has been
+# created.
+#
+# The function removes the print job from the vlp database if successful.
+#
+test_vlp_verify()
+{
+ tdbfile="$PREFIX_ABS/$TARGET_ENV/lockdir/vlp.tdb"
+ if [ ! -w $tdbfile ]; then
+ echo "vlp tdbfile $tdbfile doesn't exist or is not writeable!"
+ return 1
+ fi
+
+ cmd='$samba_vlp tdbfile=$tdbfile lpq print1 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "failed to get print queue with $samba_vlp"
+ echo "$out"
+ fi
+
+ jobid=$(echo "$out" | awk '/[0-9]+/ { print $1 };')
+ if [ -z "$jobid" ] || [ $jobid -lt 100 ] || [ $jobid -gt 2000 ]; then
+ echo "Invalid jobid: $jobid"
+ echo "$out"
+ return 1
+ fi
+
+ file=$(echo "$out" | awk '/[0-9]+/ { print $6 };')
+ if [ ! -r $PREFIX_ABS/$TARGET_ENV/share/$file ]; then
+ echo "$file doesn't exist"
+ echo "$out"
+ return 1
+ fi
+
+ $samba_vlp "tdbfile=$tdbfile" lprm print1 $jobid
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "Failed to remove jobid $jobid from $tdbfile"
+ return 1
+ fi
+}
+
+test_delete_on_close()
+{
+ tdbfile="$PREFIX_ABS/$TARGET_ENV/lockdir/vlp.tdb"
+ if [ ! -w $tdbfile ]; then
+ echo "vlp tdbfile $tdbfile doesn't exist or is not writeable!"
+ return 1
+ fi
+
+ cmd='$samba_vlp tdbfile=$tdbfile lpq print1 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "failed to lpq jobs on print1 with $samba_vlp"
+ echo "$out"
+ return 1
+ fi
+
+ num_jobs=$(echo "$out" | wc -l)
+ #
+ # Now run the test DELETE-PRINT from smbtorture3
+ #
+ cmd='$samba_smbtorture3 //$SERVER_IP/print1 -U$USERNAME%$PASSWORD DELETE-PRINT 2>&1'
+ eval echo "$cmd"
+ out_t=$(eval $cmd)
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "failed to run DELETE-PRINT on print1"
+ echo "$out_t"
+ return 1
+ fi
+
+ cmd='$samba_vlp tdbfile=$tdbfile lpq print1 2>&1'
+ eval echo "$cmd"
+ out1=$(eval $cmd)
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "(2) failed to lpq jobs on print1 with $samba_vlp"
+ echo "$out1"
+ return 1
+ fi
+ num_jobs1=$(echo "$out1" | wc -l)
+
+ #
+ # Number of jobs should not change. Job
+ # should not have made it to backend.
+ #
+ if [ "$num_jobs1" -ne "$num_jobs" ]; then
+ echo "delete-on-close fail $num_jobs1 -ne $num_jobs"
+ echo "$out"
+ echo "$out_t"
+ echo "$out1"
+ return 1
+ fi
+
+ return 0
+}
+
+testit "smbspool no args" \
+ test_smbspool_noargs $samba_smbspool ||
+ failed=$(expr $failed + 1)
+
+testit "smbspool_krb5_wrapper no args" \
+ test_smbspool_noargs $samba_smbspool_krb5 ||
+ failed=$(expr $failed + 1)
+
+testit "smbspool_krb5_wrapper AuthInfoRequired=none" \
+ test_smbspool_authinforequired_none ||
+ failed=$(expr $failed + 1)
+
+testit "smbspool_krb5_wrapper AuthInfoRequired=(sth unknown)" \
+ test_smbspool_authinforequired_unknown ||
+ failed=$(expr $failed + 1)
+
+testit "smbspool print example.ps" \
+ $samba_smbspool smb://$USERNAME:$PASSWORD@$SERVER_IP/print1 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps ||
+ failed=$(expr $failed + 1)
+
+testit "vlp verify example.ps" \
+ test_vlp_verify ||
+ failed=$(expr $failed + 1)
+
+testit "smbspool print example.ps via stdin" \
+ $samba_smbspool smb://$USERNAME:$PASSWORD@$SERVER_IP/print1 200 $USERNAME "Testprint" 1 "options" <$SRCDIR/testdata/printing/example.ps ||
+ failed=$(expr $failed + 1)
+
+testit "vlp verify example.ps" \
+ test_vlp_verify ||
+ failed=$(expr $failed + 1)
+
+DEVICE_URI="smb://$USERNAME:$PASSWORD@$SERVER_IP/print1"
+export DEVICE_URI
+testit "smbspool print DEVICE_URI example.ps" \
+ $samba_smbspool 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps ||
+ failed=$(expr $failed + 1)
+unset DEVICE_URI
+
+testit "vlp verify example.ps" \
+ test_vlp_verify ||
+ failed=$(expr $failed + 1)
+
+DEVICE_URI="smb://$USERNAME:$PASSWORD@$SERVER_IP/print1"
+export DEVICE_URI
+testit "smbspool print DEVICE_URI example.ps via stdin" \
+ $samba_smbspool 200 $USERNAME "Testprint" 1 "options" <$SRCDIR/testdata/printing/example.ps ||
+ failed=$(expr $failed + 1)
+unset DEVICE_URI
+
+testit "vlp verify example.ps" \
+ test_vlp_verify ||
+ failed=$(expr $failed + 1)
+
+DEVICE_URI="smb://$USERNAME:$PASSWORD@$SERVER_IP/print1"
+export DEVICE_URI
+testit "smbspool print sanitized Device URI in argv0 example.ps" \
+ $samba_argv_wrapper $samba_smbspool smb://$SERVER_IP/print1 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps ||
+ failed=$(expr $failed + 1)
+unset DEVICE_URI
+
+testit "vlp verify example.ps" \
+ test_vlp_verify ||
+ failed=$(expr $failed + 1)
+
+DEVICE_URI="smb://$USERNAME:$PASSWORD@$SERVER_IP/print1"
+export DEVICE_URI
+testit "smbspool print sanitized Device URI in argv0 example.ps via stdin" \
+ $samba_argv_wrapper $samba_smbspool smb://$SERVER_IP/print1 200 $USERNAME "Testprint" 1 "options" <$SRCDIR/testdata/printing/example.ps ||
+ failed=$(expr $failed + 1)
+unset DEVICE_URI
+
+testit "vlp verify example.ps" \
+ test_vlp_verify ||
+ failed=$(expr $failed + 1)
+
+AUTH_INFO_REQUIRED="username,password"
+export AUTH_INFO_REQUIRED
+testit "smbspool_krb5(username,password) print example.ps" \
+ $samba_smbspool_krb5 smb://$USERNAME:$PASSWORD@$SERVER_IP/print1 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps ||
+ failed=$(expr $failed + 1)
+
+testit "vlp verify example.ps" \
+ test_vlp_verify ||
+ failed=$(expr $failed + 1)
+unset AUTH_INFO_REQUIRED
+
+testit "delete on close" \
+ test_delete_on_close ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_smbspool_krb.sh b/source3/script/tests/test_smbspool_krb.sh
new file mode 100755
index 0000000..a4aeeab
--- /dev/null
+++ b/source3/script/tests/test_smbspool_krb.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+if [ $# -lt 3 ]; then
+ cat <<EOF
+Usage: test_smbspool_krb.sh SERVER USERNAME PASSWORD REALM
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+USERNAME="$2"
+PASSWORD="$3"
+REALM="$4"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+samba_bindir="$BINDIR"
+samba_smbspool="$samba_bindir/smbspool"
+
+samba_kinit=kinit
+if test -x "${BINDIR}/samba4kinit"; then
+ samba_kinit=${BINDIR}/samba4kinit
+fi
+
+samba_kdestroy=kdestroy
+if test -x "${BINDIR}/samba4kdestroy"; then
+ samba_kdestroy=${BINDIR}/samba4kdestroy
+fi
+
+KRB5CCNAME_PATH="${PREFIX}/ccache_smbclient_kerberos"
+KRB5CCNAME="FILE:${KRB5CCNAME_PATH}"
+export KRB5CCNAME
+
+test_smbspool_authinforequired_negotiate()
+{
+ cmd='$samba_smbspool smb://$SERVER/print3 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps 2>&1'
+
+ AUTH_INFO_REQUIRED="negotiate"
+ export AUTH_INFO_REQUIRED
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ unset AUTH_INFO_REQUIRED
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed to execute $samba_smbspool"
+ return 1
+ fi
+
+ return 0
+}
+
+test_smbspool_authinforequired_negative()
+{
+ cmd='$samba_smbspool smb://$SERVER/print3 200 $USERNAME "Testprint" 1 "options" $SRCDIR/testdata/printing/example.ps 2>&1'
+
+ AUTH_INFO_REQUIRED="negotiate"
+ export AUTH_INFO_REQUIRED
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ unset AUTH_INFO_REQUIRED
+
+ if [ $ret = 0 ]; then
+ echo "$out"
+ echo "Unexpected success to execute $samba_smbspool"
+ return 1
+ fi
+
+ return 0
+}
+
+kerberos_kinit "$samba_kinit" "${USERNAME}@${REALM}" "${PASSWORD}"
+testit "smbspool krb5 AuthInfoRequired=negotiate" \
+ test_smbspool_authinforequired_negotiate ||
+ failed=$((failed + 1))
+
+$samba_kdestroy
+rm -rf "$KRB5CCNAME_PATH"
+
+# smbspool should fail after destroying kerberos credentials
+testit "smbspool krb5 AuthInfoRequired=negotiate negative test" \
+ test_smbspool_authinforequired_negative ||
+ failed=$((failed + 1))
+
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_smbstatus.sh b/source3/script/tests/test_smbstatus.sh
new file mode 100755
index 0000000..30ca239
--- /dev/null
+++ b/source3/script/tests/test_smbstatus.sh
@@ -0,0 +1,479 @@
+#!/bin/sh
+
+# This runs smbstatus tests
+
+if [ $# -lt 12 ]; then
+ echo "Usage: test_smbstatus.sh SERVER SERVER_IP DOMAIN USERNAME PASSWORD USERID LOCAL_PATH PREFIX SMBCLIENT CONFIGURATION PROTOCOL"
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+DOMAIN="${3}"
+USERNAME="${4}"
+PASSWORD="${5}"
+USERID="${6}"
+LOCAL_PATH="${7}"
+PREFIX="${8}"
+SMBCLIENT="${9}"
+SMBSTATUS="${10}"
+CONFIGURATION="${11}"
+PROTOCOL="${12}"
+
+shift 12
+
+RAWARGS="${CONFIGURATION} -m${PROTOCOL}"
+ADDARGS="${RAWARGS} $@"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_smbstatus()
+{
+ local cmdfile=$PREFIX/smbclient_commands
+ local tmpfile=$PREFIX/smclient_lock_file
+ local file=smclient_lock_file
+ local cmd=""
+ local ret=0
+ local userid=$(id -u $USERNAME)
+
+ cat >$tmpfile <<EOF
+What a Wurst!
+EOF
+ cat >$cmdfile <<EOF
+lcd $PREFIX_ABS
+put $file
+open $file
+!UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS
+close 1
+rm $file
+quit
+EOF
+
+ cmd="CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS --quiet < $cmdfile 2>&1"
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $cmpfile
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "Failed to run smbclient with error $ret"
+ echo "$out"
+ false
+ return
+ fi
+
+ echo "$out" | grep -c 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "Failed: got an NT_STATUS error!"
+ echo "$out"
+ false
+ return
+ fi
+
+ echo "$out" | grep "${userid}[ ]*DENY_NONE"
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "Failed to find userid in smbstatus locked file output"
+ echo "$out"
+ false
+ return
+ fi
+
+ return 0
+}
+
+test_smbstatus_resolve_uids()
+{
+ local cmdfile=$PREFIX/smbclient_commands
+ local tmpfile=$PREFIX/smclient_lock_file
+ local file=smclient_lock_file
+ local cmd=""
+ local ret=0
+ local userid=$(id -u $USERNAME)
+
+ cat >$tmpfile <<EOF
+What a Wurst!
+EOF
+ cat >$cmdfile <<EOF
+lcd $PREFIX_ABS
+put $file
+open $file
+!UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS --resolve-uids
+close 1
+rm $file
+quit
+EOF
+
+ cmd="CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS --quiet < $cmdfile 2>&1"
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $cmpfile
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "Failed to run smbclient with error $ret"
+ echo "$out"
+ false
+ return
+ fi
+
+ echo "$out" | grep -c 'NT_STATUS_'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "Failed: got an NT_STATUS error!"
+ echo "$out"
+ false
+ return
+ fi
+
+ echo "$out" | grep "${USERNAME}[ ]*DENY_NONE"
+ ret=$?
+ if [ $ret != 0 ]; then
+ echo "Failed to find userid in smbstatus locked file output"
+ echo "$out"
+ false
+ return
+ fi
+
+ return 0
+}
+
+test_smbstatus_output()
+{
+ local cmdfile=$PREFIX/smbclient_commands
+ local tmpfile=$PREFIX/smbclient_lock_file
+ local file=smbclient_lock_file
+ local status_shares=smbstatus_output_shares
+ local status_processes=smbstatus_output_processes
+ local status_locks=smbstatus_output_locks
+
+ cat >$tmpfile <<EOF
+Hello World!
+EOF
+ cat >$cmdfile <<EOF
+lcd $PREFIX_ABS
+put $file
+open $file
+!UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS --shares > $status_shares
+!UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS --processes > $status_processes
+!UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS --locks > $status_locks
+close 1
+rm $file
+quit
+EOF
+
+ cmd="CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS --quiet < $cmdfile 2>&1"
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+
+ rm -f $cmpfile
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "Failed to run smbclient with error $ret"
+ echo "$out"
+ return 1
+ fi
+
+ out=$(cat $PREFIX/$status_processes)
+ echo "$out" | grep -c 'PID *Username'
+ ret=$?
+ if [ $ret -eq 1 ]; then
+ echo "Failed: Could not start smbstatus"
+ echo "$out"
+ return 1
+ fi
+ echo "$out" | grep -c "$USERNAME"
+ ret=$?
+ if [ $ret -eq 1 ]; then
+ echo "Failed: open connection not found"
+ echo "$out"
+ return 1
+ fi
+
+ out=$(cat $PREFIX/$status_shares)
+ echo "$out" | grep -c 'Service *pid'
+ ret=$?
+ if [ $ret -eq 1 ]; then
+ echo "Failed: Could not start smbstatus"
+ echo "$out"
+ return 1
+ fi
+ echo "$out" | grep -c "tmp"
+ ret=$?
+ if [ $ret -eq 1 ]; then
+ echo "Failed: shares not found"
+ echo "$out"
+ return 1
+ fi
+
+ out=$(cat $PREFIX/$status_locks)
+ echo "$out" | grep -c "Locked files:"
+ ret=$?
+ if [ $ret -eq 1 ]; then
+ echo "Failed: locked file not found"
+ echo "$out"
+ return 1
+ fi
+ echo "$out" | grep -c "$file"
+ ret=$?
+ if [ $ret -eq 1 ]; then
+ echo "Failed: wrong file locked"
+ echo "$out"
+ return 1
+ fi
+
+ rm $PREFIX/$status_shares
+ rm $PREFIX/$status_processes
+ rm $PREFIX/$status_locks
+
+ return 0
+}
+
+test_smbstatus_json()
+{
+ local cmdfile=$PREFIX/smbclient_commands
+ local tmpfile=$PREFIX/smbclient_lock_file
+ local file=smbclient_lock_file
+ local status_json=smbstatus_output_json
+ local status_json_long=smbstatus_output_json_long
+
+ cat > $tmpfile <<EOF
+Hello World!
+EOF
+ cat > $cmdfile <<EOF
+lcd $PREFIX_ABS
+put $file
+open $file
+posix
+!UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS --json > $status_json
+!UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS --json -vBN > $status_json_long
+close 1
+rm $file
+quit
+EOF
+
+ cmd="CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/tmp -I $SERVER_IP $ADDARGS --quiet < $cmdfile 2>&1"
+ out=$(eval $cmd)
+ echo $out
+ ret=$?
+
+ rm -f $cmdfile
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "Failed to run smbclient with error $ret"
+ echo "$out"
+ return 1
+ fi
+
+ echo $out | grep -c 'JSON support not available, please install lib Jansson'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ subunit_start_test "test_smbstatus_json"
+ subunit_skip_test "test_smbstatus_json" <<EOF
+Test needs Jansson
+EOF
+ return 0
+ fi
+
+ out=$(cat $PREFIX/$status_json)
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed: Could not print json output with error $ret"
+ echo "$out"
+ return 1
+ fi
+
+ out=$(cat $PREFIX/$status_json | jq ".")
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed: Could not parse json output from smbstatus with error $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # keys in --json
+ expected='["open_files","sessions","smb_conf","tcons","timestamp","version"]'
+ out=$(cat $PREFIX/$status_json | jq keys -c)
+ if [ "$expected" != "$out" ]; then
+ echo "Failed: Unexpected keys in smbstatus --json"
+ echo "Expected: $expected"
+ echo "Output: $out"
+ return 1
+ fi
+
+ # keys in --json -vBN
+ expected='["byte_range_locks","notifies","open_files","sessions","smb_conf","tcons","timestamp","version"]'
+ out=$(cat $PREFIX/$status_json_long | jq keys -c)
+ if [ "$expected" != "$out" ]; then
+ echo "Failed: Unexpected keys in smbstatus --json"
+ echo "Expected: $expected"
+ echo "Output: $out"
+ return 1
+ fi
+
+ # shares information in --json
+ out=$(cat $PREFIX/$status_json | jq ".tcons|.[].machine")
+ if [ "\"$SERVER_IP\"" != "$out" ]; then
+ echo "Failed: Unexpected value for tcons.machine in smbstatus --json"
+ echo "Expected: $SERVER_IP"
+ echo "Output: $out"
+ return 1
+ fi
+ out=$(cat $PREFIX/$status_json | jq ".tcons|.[].service")
+ if [ '"tmp"' != "$out" ]; then
+ echo "Failed: Unexpected value for tcons.service in smbstatus --json"
+ echo "Expected: tmp"
+ echo "Output: $out"
+ return 1
+ fi
+
+ # session information in --json
+ out=$(cat $PREFIX/$status_json | jq ".sessions|.[].username")
+ if [ "\"$USER\"" != "$out" ]; then
+ echo "Failed: Unexpected value for sessions.username in smbstatus --json"
+ echo "Expected: $USER"
+ echo "Output: $out"
+ return 1
+ fi
+ out=$(cat $PREFIX/$status_json | jq -c ".sessions|.[].signing")
+ expected='{"cipher":"AES-128-GMAC","degree":"partial"}'
+ if [ "$expected" != "$out" ]; then
+ echo "Failed: Unexpected value for sessions.signing in smbstatus --json"
+ echo "Expected: partial(AES-128-GMAC)"
+ echo "Output: $out"
+ return 1
+ fi
+ out=$(cat $PREFIX/$status_json | jq ".sessions|.[].remote_machine")
+ if [ "\"$SERVER_IP\"" != "$out" ]; then
+ echo "Failed: Unexpected value for sessions.remote_machine in smbstatus --json"
+ echo "Expected: $SERVER_IP"
+ echo "Output: $out"
+ return 1
+ fi
+
+ # open_files information in --json
+ out=$(cat $PREFIX/$status_json | jq ".open_files|.[].filename")
+ if [ "\"$file\"" != "$out" ]; then
+ echo "Failed: Unexpected value for open_files.denymode in smbstatus --json"
+ echo "Expected: \"$file\""
+ echo "Output: $out"
+ return 1
+ fi
+ out=$(cat $PREFIX/$status_json | jq ".open_files|.[].opens|.[].access_mask.hex")
+ if [ '"0x00000003"' != "$out" ]; then
+ echo "Failed: Unexpected value for open_files.access_mask.hex in smbstatus --json"
+ echo "Expected: 0x00000003"
+ echo "Output: $out"
+ return 1
+ fi
+
+ rm $PREFIX/$status_json
+ rm $PREFIX/$status_json_long
+
+ return 0
+}
+test_smbstatus_json_profile()
+{
+ local status_json=smbstatus_output_json_profile
+
+ cmd="UID_WRAPPER_INITIAL_RUID=0 UID_WRAPPER_INITIAL_EUID=0 $SMBSTATUS --json --profile > $PREFIX/$status_json"
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret -ne 0 ]; then
+ echo "Failed to run smbstatus -jP with error $ret"
+ echo "$out"
+ return 1
+ fi
+
+ echo $out | grep -c 'JSON support not available, please install lib Jansson'
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ subunit_start_test "test_smbstatus_json_profile"
+ subunit_skip_test "test_smbstatus_json_profile" <<EOF
+Test needs Jansson
+EOF
+ return 0
+ fi
+
+ out=$(cat $PREFIX/$status_json)
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed: Could not print json profile output with error $ret"
+ echo "$out"
+ return 1
+ fi
+
+ out=$(cat $PREFIX/$status_json | jq ".")
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed: Could not parse json output from smbstatus -jP with error $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # keys in --json --profile
+ expected='["ACL Calls","NT Transact Calls","SMB Calls","SMB2 Calls","SMBD loop","Stat Cache","System Calls","Trans2 Calls","smb_conf","timestamp","version"]'
+ out=$(cat $PREFIX/$status_json | jq keys -c)
+ if [ "$expected" != "$out" ]; then
+ echo "Failed: Unexpected keys in smbstatus -jP"
+ echo "Expected: $expected"
+ echo "Output: $out"
+ return 1
+ fi
+
+ # keys in ACL Calls
+ expected='["fget_nt_acl","fset_nt_acl","get_nt_acl","get_nt_acl_at"]'
+ out=$(cat $PREFIX/$status_json | jq -c '."ACL Calls"|keys')
+ if [ "$expected" != "$out" ]; then
+ echo "Failed: Unexpected keys in smbstatus -jP"
+ echo "Expected: $expected"
+ echo "Output: $out"
+ return 1
+ fi
+
+ # keys in ACL Calls, fget_nt_acl
+ expected='["count","time"]'
+ out=$(cat $PREFIX/$status_json | jq -c '."ACL Calls"."fget_nt_acl"|keys')
+ if [ "$expected" != "$out" ]; then
+ echo "Failed: Unexpected keys in smbstatus -jP"
+ echo "Expected: $expected"
+ echo "Output: $out"
+ return 1
+ fi
+
+ rm $PREFIX/$status_json
+
+ return 0
+}
+
+testit "plain" \
+ test_smbstatus ||
+ failed=$(expr $failed + 1)
+
+testit "resolve_uids" \
+ test_smbstatus ||
+ failed=$(expr $failed + 1)
+
+testit "test_output" \
+ test_smbstatus_output ||
+ failed=$(expr $failed + 1)
+
+testit "test_json" \
+ test_smbstatus_json || \
+ failed=`expr $failed + 1`
+
+testit "test_json_profile" \
+ test_smbstatus_json_profile || \
+ failed=`expr $failed + 1`
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbtorture_nocrash_s3.sh b/source3/script/tests/test_smbtorture_nocrash_s3.sh
new file mode 100755
index 0000000..b6ef139
--- /dev/null
+++ b/source3/script/tests/test_smbtorture_nocrash_s3.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+. $(dirname $0)/../../../testprogs/blackbox/subunit.sh
+
+# this runs the file serving tests that are expected to pass with samba3
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_smbtorture_s3.sh TEST UNC USERNAME PASSWORD SMBTORTURE <smbtorture args>
+EOF
+ exit 1
+fi
+
+t="$1"
+unc="$2"
+username="$3"
+password="$4"
+SMBTORTURE="$5"
+shift 5
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+panic_count_0=$(grep -c PANIC $SMBD_TEST_LOG)
+
+echo "$panic_count_0" >/tmp/look
+
+failed=0
+testit "smbtorture" $VALGRIND $SMBTORTURE $unc -U"$username"%"$password" $ADDARGS $t || failed=$(expr $failed + 1)
+
+panic_count_1=$(grep -c PANIC $SMBD_TEST_LOG)
+
+echo "$panic_count_1" >>/tmp/look
+
+testit "check_panic" test $panic_count_0 -eq $panic_count_1 || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_smbtorture_s3.sh b/source3/script/tests/test_smbtorture_s3.sh
new file mode 100755
index 0000000..4376f4a
--- /dev/null
+++ b/source3/script/tests/test_smbtorture_s3.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# this runs the file serving tests that are expected to pass with samba3
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_smbtorture_s3.sh TEST UNC USERNAME PASSWORD SMBTORTURE <smbtorture args>
+EOF
+ exit 1
+fi
+
+t="$1"
+unc="$2"
+username="$3"
+password="$4"
+SMBTORTURE="$5"
+shift 5
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+testit "smbtorture" $VALGRIND $SMBTORTURE $unc -U"$username"%"$password" $ADDARGS $t || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_stream_dir_rename.sh b/source3/script/tests/test_stream_dir_rename.sh
new file mode 100755
index 0000000..7ac3194
--- /dev/null
+++ b/source3/script/tests/test_stream_dir_rename.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+#
+# Test a stream can rename a directory once an invalid stream path below it was requested.
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15314
+
+if [ $# -lt 5 ]; then
+ cat <<EOF
+Usage: test_stream_dir_rename.sh SERVER USERNAME PASSWORD PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+USERNAME="${2}"
+PASSWORD="${3}"
+PREFIX="${4}"
+SMBCLIENT="${5}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 5
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+test_stream_xattr_rename()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ #
+ # Test against streams_xattr_nostrict
+ #
+ cat >$tmpfile <<EOF
+deltree stream_xattr_test
+deltree stream_xattr_test1
+mkdir stream_xattr_test
+put ${PREFIX}/smbclient_interactive_prompt_commands stream_xattr_test/file.txt
+get stream_xattr_test/file.txt:abcf
+rename stream_xattr_test stream_xattr_test1
+deltree stream_xattr_test
+deltree stream_xattr_test1
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT "$@" -U$USERNAME%$PASSWORD //$SERVER/streams_xattr_nostrict < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret -ne 0 ]; then
+ echo "$out"
+ echo "failed rename on xattr stream test to test1 with error $ret"
+ return 1
+ fi
+
+ echo "$out" | grep "NT_STATUS_ACCESS_DENIED"
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "$out"
+ echo "failed rename on xattr stream with NT_STATUS_ACCESS_DENIED"
+ return 1
+ fi
+}
+
+testit "stream_rename" \
+ test_stream_xattr_rename ||
+ failed=$((failed + 1))
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_substitutions.sh b/source3/script/tests/test_substitutions.sh
new file mode 100755
index 0000000..aa0b38d
--- /dev/null
+++ b/source3/script/tests/test_substitutions.sh
@@ -0,0 +1,79 @@
+#!/bin/sh
+# Blackbox tests for substitutions
+#
+# Copyright (c) 2016 Andreas Schneider <asn@samba.org>
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_substitutions.sh SERVER USERNAME PASSWORD PREFIX
+EOF
+ exit 1
+fi
+
+SERVER=$1
+USERNAME=$2
+PASSWORD=$3
+PREFIX=$4
+shift 4
+failed=0
+
+samba_bindir="$BINDIR"
+samba_srcdir="$SRCDIR"
+smbclient="$samba_bindir/smbclient"
+rpcclient="$samba_bindir/rpcclient"
+
+. $samba_srcdir/testprogs/blackbox/subunit.sh
+. $samba_srcdir/testprogs/blackbox/common_test_fns.inc
+
+SMB_UNC="//$SERVER/sub_dug"
+
+test_smbclient "Test login to share with substitution (DUG)" \
+ "ls" "$SMB_UNC" "-U$USERNAME%$PASSWORD" || failed=$(expr $failed + 1)
+
+SMB_UNC="//$SERVER/sub_dug2"
+
+test_smbclient "Test login to share with substitution (Dug)" \
+ "ls" "$SMB_UNC" "-U$USERNAME%$PASSWORD" || failed=$(expr $failed + 1)
+
+SMB_UNC="//$SERVER/sub_valid_users"
+
+test_smbclient "Test login to share with substitution for valid users" \
+ "ls" "$SMB_UNC" "-U$USERNAME%$PASSWORD" || failed=$(expr $failed + 1)
+
+SMB_UNC="//$SERVER/sub_valid_users_domain"
+
+test_smbclient "Test login to share with substitution for valid user's domain" \
+ "ls" "$SMB_UNC" "-U$USERNAME%$PASSWORD" || failed=$(expr $failed + 1)
+
+SMB_UNC="//$SERVER/sub_valid_users_group"
+
+test_smbclient "Test login to share with substitution for valid user's UNIX group" \
+ "ls" "$SMB_UNC" "-U$USERNAME%$PASSWORD" || failed=$(expr $failed + 1)
+
+test_smbclient \
+ "Test for login to share with include substitution [${USERNAME}]" \
+ "ls" "//${SERVER}/${USERNAME}_share" "-U$USERNAME%$PASSWORD" ||
+ failed=$((failed + 1))
+
+test_smbclient_expect_failure \
+ "Netative test for login to share with include substitution [${DC_USERNAME}]" \
+ "ls" "//${SERVER}/${USERNAME}_share" "-U$DC_USERNAME%$DC_PASSWORD" ||
+ failed=$((failed + 1))
+
+testit_grep_count \
+ "Test for share enum with include substitution" \
+ "netname: ${USERNAME}_share" \
+ 1 \
+ ${rpcclient} "ncacn_np:${SERVER}" "-U$USERNAME%$PASSWORD" \
+ -c netshareenum ||
+ failed=$((failed + 1))
+
+testit_grep_count \
+ "Negative test for share enum with include substitution" \
+ "netname: ${USERNAME}_share" \
+ 0 \
+ ${rpcclient} "ncacn_np:${SERVER}" "-U$DC_USERNAME%$DC_PASSWORD" \
+ -c netshareenum ||
+ failed=$((failed + 1))
+
+exit $failed
diff --git a/source3/script/tests/test_success.sh b/source3/script/tests/test_success.sh
new file mode 100755
index 0000000..7ba8ddb
--- /dev/null
+++ b/source3/script/tests/test_success.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# Blackbox test that should simply succeed.
+#
+# Copyright (C) 2011 Michael Adam <obnox@samba.org>
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_success()
+{
+ true
+}
+
+testit "success" \
+ test_success ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_symlink_dosmode.sh b/source3/script/tests/test_symlink_dosmode.sh
new file mode 100755
index 0000000..dd6cb6b
--- /dev/null
+++ b/source3/script/tests/test_symlink_dosmode.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_symlink_dosmode.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 6
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+
+rm -rf "$share_test_dir/testdir"
+
+mkdir -p "$share_test_dir/testdir/dir"
+touch "$share_test_dir/testdir/file"
+ln -s "../file" "$share_test_dir/testdir/dir/symlink"
+
+test_symlink_dosmode()
+{
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >"$tmpfile" <<EOF
+ls testdir/dir/*
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I$SERVER_IP < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed accessing local_symlinks with error %s\n" "$ret"
+ return 1
+ fi
+
+ mode=$(printf "%s" "$out" | awk '/symlink/ {print $2}')
+ echo "mode: $mode"
+ if [ x"$mode" != x"N" ] ; then
+ printf "Bad mode: '%s', expected 'N'\n" "$mode"
+ printf "%s\n" "$out"
+ return 1
+ fi
+ return 0
+}
+
+testit "symlink_dosmode" \
+ test_symlink_dosmode ||
+ failed=$((failed + 1))
+
+rm -rf "$share_test_dir/testdir"
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_symlink_rename_smb1_posix.sh b/source3/script/tests/test_symlink_rename_smb1_posix.sh
new file mode 100755
index 0000000..00acfee
--- /dev/null
+++ b/source3/script/tests/test_symlink_rename_smb1_posix.sh
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_symlink_rename_smb1_posix.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 6
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+#
+# These files/directories will be created.
+#
+file_outside_share="/tmp/symlink_rename_test_file.$$"
+dir_outside_share="/tmp/symlink_rename_test_dir.$$"
+file_outside_share_noperms="/tmp/symlink_rename_test_file_noperm.$$"
+dir_outside_share_noperms="/tmp/symlink_rename_test_dir_noperm.$$"
+#
+# These two objects do not exist.
+#
+file_outside_share_noexist="/tmp/symlink_rename_test_noexist.$$"
+dir_outside_share_noexist="/tmp/symlink_rename_test_dir_noexist.$$"
+
+#
+# Cleanup function.
+#
+do_cleanup()
+{
+ (
+ #subshell.
+ cd "$share_test_dir" || return
+ rm -f "file_exists"
+ rm -f "symlink_noexist"
+ rm -f "symlink_file_outside_share"
+ rm -f "symlink_file_outside_share_noexist"
+ rm -f "symlink_dir_outside_share"
+ rm -f "symlink_dir_outside_share_noexist"
+ rm -f "symlink_file_outside_share_noperms"
+ rm -f "symlink_dir_outside_share_noperms"
+ # Links inside share.
+ rm -f "symlink_file_inside_share_noperms"
+ rm -f "file_inside_share_noperms"
+ rm -f "symlink_dir_inside_share_noperms"
+ chmod 755 "dir_inside_share_noperms"
+ rm -rf "dir_inside_share_noperms"
+ )
+ rm -f "$file_outside_share"
+ rm -rf "$dir_outside_share"
+ rm -f "$file_outside_share_noperms"
+ rm -rf "$dir_outside_share_noperms"
+}
+
+#
+# Ensure we start from a clean slate.
+#
+do_cleanup
+
+#
+# Create the test files/directories/symlinks.
+#
+# File/directory explicitly outside share.
+touch "$file_outside_share"
+mkdir "$dir_outside_share"
+# File/directory explicitly outside share with permission denied.
+touch "$file_outside_share_noperms"
+chmod 0 "$file_outside_share_noperms"
+mkdir "$dir_outside_share_noperms"
+chmod 0 "$dir_outside_share_noperms"
+#
+# Create links to these objects inside the share definition.
+(
+ #subshell.
+ cd "$share_test_dir" || return
+ # Source file for all renames. None of these should succeed.
+ touch "file_exists"
+ ln -s "noexist" "symlink_noexist"
+ ln -s "$file_outside_share" "symlink_file_outside_share"
+ ln -s "$file_outside_share_noexist" "symlink_file_outside_share_noexist"
+ ln -s "$dir_outside_share" "symlink_dir_outside_share"
+ ln -s "$dir_outside_share_noexist" "symlink_dir_outside_share_noexist"
+ ln -s "$file_outside_share_noperms" "symlink_file_outside_share_noperms"
+ ln -s "$dir_outside_share_noperms" "symlink_dir_outside_share_noperms"
+ #
+ # Create symlinks to access denied file and directory
+ # objects within the share
+ touch "file_inside_share_noperms"
+ chmod 0 "file_inside_share_noperms"
+ ln -s "file_inside_share_noperms" "symlink_file_inside_share_noperms"
+ mkdir "dir_inside_share_noperms"
+ touch "dir_inside_share_noperms/noperm_file_exists"
+ chmod 0 "dir_inside_share_noperms"
+ ln -s "dir_inside_share_noperms" "symlink_dir_inside_share_noperms"
+)
+
+#
+# smbclient function given command, path, expected error, and posix.
+#
+smbclient_expect_error()
+{
+ filecmd="$1"
+ filename1="$2"
+ filename2="$3"
+ expected_error="$4"
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >"$tmpfile" <<EOF
+posix
+$filecmd $filename1 $filename2
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I$SERVER_IP -mNT1 < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed accessing local_symlinks with error %s\n" "$ret"
+ return 1
+ fi
+
+ if [ "$expected_error" = "NT_STATUS_OK" ]; then
+ printf "%s" "$out" | grep -v "NT_STATUS_"
+ else
+ printf "%s" "$out" | grep "$expected_error"
+ fi
+ ret=$?
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed - should get %s doing posix \"%s %s %s\"\n" "$expected_error" "$filecmd" "$filename1" "$filename2"
+ return 1
+ fi
+}
+
+#
+# SMB1+posix tests.
+#
+test_symlink_rename_SMB1_posix()
+{
+ #
+ # rename commands.
+ # As all the targets exist as symlinks, these should all fail.
+ #
+ smbclient_expect_error "rename" "file_exists" "symlink_noexist" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_file_outside_share" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_file_outside_share_noexist" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_dir_outside_share" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_dir_outside_share_noexist" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_file_outside_share_noperms" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_dir_outside_share_noperms" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_file_inside_share_noperms" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ smbclient_expect_error "rename" "file_exists" "symlink_dir_inside_share_noperms" "NT_STATUS_OBJECT_NAME_COLLISION" || return 1
+ return 0
+}
+
+testit "symlink_rename_SMB1_posix" \
+ test_symlink_rename_SMB1_posix ||
+ failed=$((failed + 1))
+
+#
+# Cleanup.
+do_cleanup
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_symlink_traversal_smb1.sh b/source3/script/tests/test_symlink_traversal_smb1.sh
new file mode 100755
index 0000000..ff38210
--- /dev/null
+++ b/source3/script/tests/test_symlink_traversal_smb1.sh
@@ -0,0 +1,262 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_symlink_traversal_smb1.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 6
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+#
+# These files/directories will be created.
+#
+file_outside_share="/tmp/symlink_traverse_test_file.$$"
+dir_outside_share="/tmp/symlink_traverse_test_dir.$$"
+file_outside_share_noperms="/tmp/symlink_traverse_test_file_noperm.$$"
+dir_outside_share_noperms="/tmp/symlink_traverse_test_dir_noperm.$$"
+#
+# These two objects do not exist.
+#
+file_outside_share_noexist="/tmp/symlink_traverse_test_noexist.$$"
+dir_outside_share_noexist="/tmp/symlink_traverse_test_dir_noexist.$$"
+
+#
+# Cleanup function.
+#
+do_cleanup()
+{
+ (
+ #subshell.
+ cd "$share_test_dir" || return
+ rm -f "file_exists"
+ rm -f "symlink_noexist"
+ rm -f "symlink_file_outside_share"
+ rm -f "symlink_file_outside_share_noexist"
+ rm -f "symlink_dir_outside_share"
+ rm -f "symlink_dir_outside_share_noexist"
+ rm -f "symlink_file_outside_share_noperms"
+ rm -f "symlink_dir_outside_share_noperms"
+ rm -rf "emptydir"
+ # Links inside share.
+ rm -f "symlink_file_inside_share_noperms"
+ rm -f "file_inside_share_noperms"
+ rm -f "symlink_dir_inside_share_noperms"
+ chmod 755 "dir_inside_share_noperms"
+ rm -rf "dir_inside_share_noperms"
+ )
+ rm -f "$file_outside_share"
+ rm -rf "$dir_outside_share"
+ rm -f "$file_outside_share_noperms"
+ rm -rf "$dir_outside_share_noperms"
+}
+
+#
+# Ensure we start from a clean slate.
+#
+do_cleanup
+
+#
+# Create the test files/directories/symlinks.
+#
+# File/directory explicitly outside share.
+touch "$file_outside_share"
+mkdir "$dir_outside_share"
+# File/directory explicitly outside share with permission denied.
+touch "$file_outside_share_noperms"
+chmod 0 "$file_outside_share_noperms"
+mkdir "$dir_outside_share_noperms"
+chmod 0 "$dir_outside_share_noperms"
+#
+# Create links to these objects inside the share definition.
+(
+ #subshell.
+ cd "$share_test_dir" || return
+ touch "file_exists"
+ ln -s "noexist" "symlink_noexist"
+ ln -s "$file_outside_share" "symlink_file_outside_share"
+ ln -s "$file_outside_share_noexist" "symlink_file_outside_share_noexist"
+ ln -s "$dir_outside_share" "symlink_dir_outside_share"
+ ln -s "$dir_outside_share_noexist" "symlink_dir_outside_share_noexist"
+ ln -s "$file_outside_share_noperms" "symlink_file_outside_share_noperms"
+ ln -s "$dir_outside_share_noperms" "symlink_dir_outside_share_noperms"
+ #
+ # Create the identical symlink set underneath "emptydir"
+ mkdir "emptydir"
+ (
+ #subshell
+ cd "emptydir" || return
+ touch "file_exists"
+ ln -s "noexist" "symlink_noexist"
+ ln -s "$file_outside_share" "symlink_file_outside_share"
+ ln -s "$file_outside_share_noexist" "symlink_file_outside_share_noexist"
+ ln -s "$dir_outside_share" "symlink_dir_outside_share"
+ ln -s "$dir_outside_share_noexist" "symlink_dir_outside_share_noexist"
+ ln -s "$file_outside_share_noperms" "symlink_file_outside_share_noperms"
+ ln -s "$dir_outside_share_noperms" "symlink_dir_outside_share_noperms"
+ )
+ #
+ # Create symlinks to access denied file and directory
+ # objects within the share
+ touch "file_inside_share_noperms"
+ chmod 0 "file_inside_share_noperms"
+ ln -s "file_inside_share_noperms" "symlink_file_inside_share_noperms"
+ mkdir "dir_inside_share_noperms"
+ touch "dir_inside_share_noperms/noperm_file_exists"
+ chmod 0 "dir_inside_share_noperms"
+ ln -s "dir_inside_share_noperms" "symlink_dir_inside_share_noperms"
+)
+
+#
+# smbclient function given command, path, expected error, and posix.
+#
+smbclient_expect_error()
+{
+ filecmd="$1"
+ filename1="$2"
+ filename2="$3"
+ expected_error="$4"
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >"$tmpfile" <<EOF
+$filecmd $filename1 $filename2
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I$SERVER_IP -mNT1 < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed accessing local_symlinks with error %s\n" "$ret"
+ return 1
+ fi
+
+ if [ "$expected_error" = "NT_STATUS_OK" ]; then
+ printf "%s" "$out" | grep -v "NT_STATUS_"
+ else
+ printf "%s" "$out" | grep "$expected_error"
+ fi
+ ret=$?
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed - should get %s doing \"%s %s %s\"\n" "$expected_error" "$filecmd" "$filename1" "$filename2"
+ return 1
+ fi
+}
+
+#
+# SMB1 tests.
+#
+test_symlink_traversal_SMB1_onename()
+{
+ name="$1"
+ do_rename="$2"
+ #
+ # get commands.
+ #
+ smbclient_expect_error "get" "$name" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/*" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "get" "$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "get" "emptydir/$name" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/*" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "get" "emptydir/$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ #
+ # ls commands.
+ #
+ smbclient_expect_error "ls" "$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "$name/*" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "ls" "emptydir/$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/*" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+
+ #
+ # del commands.
+ # smbclient internally does a cli_list, so we expect similar errors.
+ #
+ smbclient_expect_error "del" "$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "del" "$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "del" "emptydir/$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "del" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+
+ if [ "$do_rename" = "do rename" ]; then
+ #
+ # rename commands.
+ #
+ smbclient_expect_error "rename" "file_exists" "$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "file_exists" "$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "rename" "file_exists" "emptydir/$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "file_exists" "emptydir/$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ fi
+ return 0
+}
+
+#
+# Check error code returns traversing through different
+# kinds of symlinks over SMB1.
+#
+test_symlink_traversal_SMB1()
+{
+ test_symlink_traversal_SMB1_onename "symlink_noexist" "no rename" || return 1
+ test_symlink_traversal_SMB1_onename "symlink_file_outside_share" "do rename" || return 1
+ test_symlink_traversal_SMB1_onename "symlink_dir_outside_share" "do rename" || return 1
+ test_symlink_traversal_SMB1_onename "symlink_dir_outside_share_noexist" "no rename" || return 1
+ test_symlink_traversal_SMB1_onename "symlink_file_outside_share_noperms" "do rename" || return 1
+ test_symlink_traversal_SMB1_onename "symlink_dir_outside_share_noperms" "do rename" || return 1
+ #
+ # Test paths within share with no permissions.
+ #
+ # Can't 'get' file with no perms or a symlink to it.
+ smbclient_expect_error "get" "file_inside_share_noperms" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ smbclient_expect_error "get" "symlink_file_inside_share_noperms" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ # But can list it and the symlink to it.
+ smbclient_expect_error "ls" "file_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_file_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ # Can't 'get' file inside a directory with no perms or a symlink to it.
+ smbclient_expect_error "get" "dir_inside_share_noperms/noperm_file_exists" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ smbclient_expect_error "get" "symlink_dir_inside_share_noperms/noperm_file_exists" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ # But can list the directory with no perms and the symlink to it.
+ smbclient_expect_error "ls" "dir_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_dir_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+}
+
+testit "symlink_traversal_SMB1" \
+ test_symlink_traversal_SMB1 ||
+ failed=$((failed + 1))
+
+#
+# Cleanup.
+do_cleanup
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_symlink_traversal_smb1_posix.sh b/source3/script/tests/test_symlink_traversal_smb1_posix.sh
new file mode 100755
index 0000000..52d6cfb
--- /dev/null
+++ b/source3/script/tests/test_symlink_traversal_smb1_posix.sh
@@ -0,0 +1,269 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_symlink_traversal_smb1_posix.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 6
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+#
+# These files/directories will be created.
+#
+file_outside_share="/tmp/symlink_traverse_test_file.$$"
+dir_outside_share="/tmp/symlink_traverse_test_dir.$$"
+file_outside_share_noperms="/tmp/symlink_traverse_test_file_noperm.$$"
+dir_outside_share_noperms="/tmp/symlink_traverse_test_dir_noperm.$$"
+#
+# These two objects do not exist.
+#
+file_outside_share_noexist="/tmp/symlink_traverse_test_noexist.$$"
+dir_outside_share_noexist="/tmp/symlink_traverse_test_dir_noexist.$$"
+
+#
+# Cleanup function.
+#
+do_cleanup()
+{
+ (
+ #subshell.
+ cd "$share_test_dir" || return
+ rm -f "file_exists"
+ rm -f "symlink_noexist"
+ rm -f "symlink_file_outside_share"
+ rm -f "symlink_file_outside_share_noexist"
+ rm -f "symlink_dir_outside_share"
+ rm -f "symlink_dir_outside_share_noexist"
+ rm -f "symlink_file_outside_share_noperms"
+ rm -f "symlink_dir_outside_share_noperms"
+ rm -rf "emptydir"
+ # Links inside share.
+ rm -f "symlink_file_inside_share_noperms"
+ rm -f "file_inside_share_noperms"
+ rm -f "symlink_dir_inside_share_noperms"
+ chmod 755 "dir_inside_share_noperms"
+ rm -rf "dir_inside_share_noperms"
+ )
+ rm -f "$file_outside_share"
+ rm -rf "$dir_outside_share"
+ rm -f "$file_outside_share_noperms"
+ rm -rf "$dir_outside_share_noperms"
+}
+
+#
+# Ensure we start from a clean slate.
+#
+do_cleanup
+
+#
+# Create the test files/directories/symlinks.
+#
+# File/directory explicitly outside share.
+touch "$file_outside_share"
+mkdir "$dir_outside_share"
+# File/directory explicitly outside share with permission denied.
+touch "$file_outside_share_noperms"
+chmod 0 "$file_outside_share_noperms"
+mkdir "$dir_outside_share_noperms"
+chmod 0 "$dir_outside_share_noperms"
+#
+# Create links to these objects inside the share definition.
+(
+ #subshell.
+ cd "$share_test_dir" || return
+ touch "file_exists"
+ ln -s "noexist" "symlink_noexist"
+ ln -s "$file_outside_share" "symlink_file_outside_share"
+ ln -s "$file_outside_share_noexist" "symlink_file_outside_share_noexist"
+ ln -s "$dir_outside_share" "symlink_dir_outside_share"
+ ln -s "$dir_outside_share_noexist" "symlink_dir_outside_share_noexist"
+ ln -s "$file_outside_share_noperms" "symlink_file_outside_share_noperms"
+ ln -s "$dir_outside_share_noperms" "symlink_dir_outside_share_noperms"
+ #
+ # Create the identical symlink set underneath "emptydir"
+ mkdir "emptydir"
+ (
+ #subshell
+ cd "emptydir" || return
+ touch "file_exists"
+ ln -s "noexist" "symlink_noexist"
+ ln -s "$file_outside_share" "symlink_file_outside_share"
+ ln -s "$file_outside_share_noexist" "symlink_file_outside_share_noexist"
+ ln -s "$dir_outside_share" "symlink_dir_outside_share"
+ ln -s "$dir_outside_share_noexist" "symlink_dir_outside_share_noexist"
+ ln -s "$file_outside_share_noperms" "symlink_file_outside_share_noperms"
+ ln -s "$dir_outside_share_noperms" "symlink_dir_outside_share_noperms"
+ )
+ #
+ # Create symlinks to access denied file and directory
+ # objects within the share
+ touch "file_inside_share_noperms"
+ chmod 0 "file_inside_share_noperms"
+ ln -s "file_inside_share_noperms" "symlink_file_inside_share_noperms"
+ mkdir "dir_inside_share_noperms"
+ touch "dir_inside_share_noperms/noperm_file_exists"
+ chmod 0 "dir_inside_share_noperms"
+ ln -s "dir_inside_share_noperms" "symlink_dir_inside_share_noperms"
+)
+
+#
+# smbclient function given command, path, expected error, and posix.
+#
+smbclient_expect_error()
+{
+ filecmd="$1"
+ filename1="$2"
+ filename2="$3"
+ expected_error="$4"
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >"$tmpfile" <<EOF
+posix
+$filecmd $filename1 $filename2
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I$SERVER_IP -mNT1 < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed accessing local_symlinks with error %s\n" "$ret"
+ return 1
+ fi
+
+ if [ "$expected_error" = "NT_STATUS_OK" ]; then
+ printf "%s" "$out" | grep -v "NT_STATUS_"
+ else
+ printf "%s" "$out" | grep "$expected_error"
+ fi
+ ret=$?
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed - should get %s doing posix \"%s %s %s\"\n" "$expected_error" "$filecmd" "$filename1" "$filename2"
+ return 1
+ fi
+}
+
+#
+# SMB1+posix tests.
+#
+test_symlink_traversal_SMB1_posix_onename()
+{
+ name="$1"
+ do_rename="$2"
+ #
+ # get commands.
+ #
+ # Remember in SMB1+POSIX, "*" is a perfectly valid pathname component,
+ # and symlinks can be seen, but not necessarily followed.
+ #
+ smbclient_expect_error "get" "$name" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/*" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/*/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "get" "emptydir/$name" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/*" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/*/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ #
+ # ls commands.
+ #
+ smbclient_expect_error "ls" "$name" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "$name/*" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "$name/*/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "ls" "emptydir/$name" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/*" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/*/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ #
+ # SMB1+POSIX stat commands. All symlinks can be stat'ed.
+ #
+ smbclient_expect_error "stat" "$name" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "stat" "emptydir/$name" "" "NT_STATUS_OK" || return 1
+ #
+ # del commands. Under SMB1+POSIX we can legitimately delete symlinks, so don't
+ # try and delete symlink targets, we need them for the later tests.
+ #
+ smbclient_expect_error "del" "$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "del" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+
+ if [ "$do_rename" = "do rename" ]; then
+ #
+ # rename commands. Under SMB1+POSIX we can legitimately rename symlinks, so don't
+ # try and rename symlink targets, we need them for the later tests.
+ #
+ smbclient_expect_error "rename" "file_exists" "$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "rename" "file_exists" "emptydir/$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ fi
+ return 0
+}
+
+#
+# Check error code returns traversing through different
+# kinds of symlinks over SMB1+posix.
+#
+test_symlink_traversal_SMB1_posix()
+{
+ test_symlink_traversal_SMB1_posix_onename "symlink_noexist" "no rename" || return 1
+ test_symlink_traversal_SMB1_posix_onename "symlink_file_outside_share" "do rename" || return 1
+ test_symlink_traversal_SMB1_posix_onename "symlink_dir_outside_share" "do rename" || return 1
+ test_symlink_traversal_SMB1_posix_onename "symlink_dir_outside_share_noexist" "no rename" || return 1
+ test_symlink_traversal_SMB1_posix_onename "symlink_file_outside_share_noperms" "do rename" || return 1
+ test_symlink_traversal_SMB1_posix_onename "symlink_dir_outside_share_noperms" "do rename" || return 1
+ #
+ # Test paths within share with no permissions.
+ #
+ # Can't 'get' file with no perms.
+ smbclient_expect_error "get" "file_inside_share_noperms" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ # In SMB1+POSIX you can't "get" a symlink at all.
+ smbclient_expect_error "get" "symlink_file_inside_share_noperms" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ # But can list it and the symlink to it.
+ smbclient_expect_error "ls" "file_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_file_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ # Can't 'get' file inside a directory with no perms.
+ smbclient_expect_error "get" "dir_inside_share_noperms/noperm_file_exists" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ # In SMB1+POSIX you can't traverse through a symlink that points to a noperm directory.
+ smbclient_expect_error "get" "symlink_dir_inside_share_noperms/noperm_file_exists" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ # But can list the directory with no perms and the symlink to it.
+ smbclient_expect_error "ls" "dir_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_dir_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+}
+
+testit "symlink_traversal_SMB1_posix" \
+ test_symlink_traversal_SMB1_posix ||
+ failed=$((failed + 1))
+
+#
+# Cleanup.
+do_cleanup
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_symlink_traversal_smb2.sh b/source3/script/tests/test_symlink_traversal_smb2.sh
new file mode 100755
index 0000000..38719cc
--- /dev/null
+++ b/source3/script/tests/test_symlink_traversal_smb2.sh
@@ -0,0 +1,382 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_symlink_traversal_smb2.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 6
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+#
+# These files/directories will be created.
+#
+file_outside_share="/tmp/symlink_traverse_test_file.$$"
+dir_outside_share="/tmp/symlink_traverse_test_dir.$$"
+file_outside_share_noperms="/tmp/symlink_traverse_test_file_noperm.$$"
+dir_outside_share_noperms="/tmp/symlink_traverse_test_dir_noperm.$$"
+#
+# These two objects do not exist.
+#
+file_outside_share_noexist="/tmp/symlink_traverse_test_noexist.$$"
+dir_outside_share_noexist="/tmp/symlink_traverse_test_dir_noexist.$$"
+
+#
+# Cleanup function.
+#
+do_cleanup()
+{
+ (
+ #subshell.
+ cd "$share_test_dir" || return
+ rm -f "symlink_to_dot"
+ rm -f "file_exists"
+ rm -f "symlink_to_file_exists"
+ rm -rf "dir_exists"
+ rm -f "symlink_to_dir_exists"
+ rm -f "symlink_noexist"
+ rm -f "symlink_file_outside_share"
+ rm -f "symlink_file_outside_share_noexist"
+ rm -f "symlink_dir_outside_share"
+ rm -f "symlink_dir_outside_share_noexist"
+ rm -f "symlink_file_outside_share_noperms"
+ rm -f "symlink_dir_outside_share_noperms"
+ rm -rf "emptydir"
+ # Links inside share.
+ rm -f "symlink_file_inside_share_noperms"
+ rm -f "file_inside_share_noperms"
+ rm -f "symlink_dir_inside_share_noperms"
+ chmod 755 "dir_inside_share_noperms"
+ rm -rf "dir_inside_share_noperms"
+ )
+ rm -f "$file_outside_share"
+ rm -rf "$dir_outside_share"
+ rm -f "$file_outside_share_noperms"
+ rm -rf "$dir_outside_share_noperms"
+}
+
+#
+# Ensure we start from a clean slate.
+#
+do_cleanup
+
+#
+# Create the test files/directories/symlinks.
+#
+# File/directory explicitly outside share.
+touch "$file_outside_share"
+mkdir "$dir_outside_share"
+# File/directory explicitly outside share with permission denied.
+touch "$file_outside_share_noperms"
+chmod 0 "$file_outside_share_noperms"
+mkdir "$dir_outside_share_noperms"
+chmod 0 "$dir_outside_share_noperms"
+#
+# Create links to these objects inside the share definition.
+(
+ #subshell.
+ cd "$share_test_dir" || return
+ ln -s "." "symlink_to_dot"
+ touch "file_exists"
+ ln -s "file_exists" "symlink_to_file_exists"
+ mkdir "dir_exists"
+ ln -s "dir_exists" "symlink_to_dir_exists"
+ touch "dir_exists/subfile_exists"
+ mkdir "dir_exists/subdir_exists"
+ ln -s "noexist" "symlink_noexist"
+ ln -s "$file_outside_share" "symlink_file_outside_share"
+ ln -s "$file_outside_share_noexist" "symlink_file_outside_share_noexist"
+ ln -s "$dir_outside_share" "symlink_dir_outside_share"
+ ln -s "$dir_outside_share_noexist" "symlink_dir_outside_share_noexist"
+ ln -s "$file_outside_share_noperms" "symlink_file_outside_share_noperms"
+ ln -s "$dir_outside_share_noperms" "symlink_dir_outside_share_noperms"
+ #
+ # Create the identical symlink set underneath "emptydir"
+ mkdir "emptydir"
+ (
+ #subshell
+ cd "emptydir" || return
+ ln -s "." "symlink_to_dot"
+ touch "file_exists"
+ ln -s "file_exists" "symlink_to_file_exists"
+ mkdir "dir_exists"
+ ln -s "dir_exists" "symlink_to_dir_exists"
+ touch "dir_exists/subfile_exists"
+ mkdir "dir_exists/subdir_exists"
+ ln -s "noexist" "symlink_noexist"
+ ln -s "$file_outside_share" "symlink_file_outside_share"
+ ln -s "$file_outside_share_noexist" "symlink_file_outside_share_noexist"
+ ln -s "$dir_outside_share" "symlink_dir_outside_share"
+ ln -s "$dir_outside_share_noexist" "symlink_dir_outside_share_noexist"
+ ln -s "$file_outside_share_noperms" "symlink_file_outside_share_noperms"
+ ln -s "$dir_outside_share_noperms" "symlink_dir_outside_share_noperms"
+ )
+ #
+ # Create symlinks to access denied file and directory
+ # objects within the share
+ touch "file_inside_share_noperms"
+ chmod 0 "file_inside_share_noperms"
+ ln -s "file_inside_share_noperms" "symlink_file_inside_share_noperms"
+ mkdir "dir_inside_share_noperms"
+ touch "dir_inside_share_noperms/noperm_file_exists"
+ ln -s "dir_inside_share_noperms" "symlink_dir_inside_share_noperms"
+ mkdir "dir_inside_share_noperms/noperm_subdir_exists"
+ touch "dir_inside_share_noperms/noperm_subdir_exists/noperm_subdir_file_exists"
+ chmod 0 "dir_inside_share_noperms"
+
+ # Symlink pointing out of the share
+ ln -s "$share_test_dir"a"/etc" x
+)
+
+#
+# smbclient function given command, path, expected error, and posix.
+#
+smbclient_expect_error()
+{
+ filecmd="$1"
+ filename1="$2"
+ filename2="$3"
+ expected_error="$4"
+ tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+ cat >"$tmpfile" <<EOF
+$filecmd $filename1 $filename2
+quit
+EOF
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/local_symlinks -I$SERVER_IP < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed accessing local_symlinks with error %s\n" "$ret"
+ return 1
+ fi
+
+ if [ "$expected_error" = "NT_STATUS_OK" ]; then
+ printf "%s" "$out" | grep -v "NT_STATUS_"
+ else
+ printf "%s" "$out" | grep "$expected_error"
+ fi
+ ret=$?
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed - should get %s doing \"%s %s %s\"\n" "$expected_error" "$filecmd" "$filename1" "$filename2"
+ return 1
+ fi
+}
+
+#
+# SMB2 tests.
+#
+test_symlink_traversal_SMB2_onename()
+{
+ name="$1"
+ do_rename="$2"
+ #
+ # get commands.
+ #
+ smbclient_expect_error "get" "$name" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/noexistsdir/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "$name/*" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "get" "$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "get" "$name/*/noexistsdir/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "get" "emptydir/$name" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/noexistsdir/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "emptydir/$name/*" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "get" "emptydir/$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "get" "emptydir/$name/*/noexistsdir/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ #
+ # ls commands.
+ #
+ smbclient_expect_error "ls" "$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "$name/noexistsdir/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "$name/*" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "ls" "$name/*/noexistsdir/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "ls" "emptydir/$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/noexistsdir/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/*" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/*/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "ls" "emptydir/$name/*/noexistsdir/noexist" "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+
+ #
+ # del commands.
+ # smbclient internally does a cli_list, so we expect similar errors.
+ #
+ smbclient_expect_error "del" "$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "del" "$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "del" "emptydir/$name" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "del" "emptydir/$name/noexist" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+
+ if [ "$do_rename" = "do rename" ]; then
+ #
+ # rename commands.
+ #
+ smbclient_expect_error "rename" "file_exists" "$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "file_exists" "$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_file_exists" "$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_file_exists" "$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "dir_exists" "$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "dir_exists" "$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_dir_exists" "$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_dir_exists" "$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ # Now in subdirectory emptydir
+ smbclient_expect_error "rename" "file_exists" "emptydir/$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "file_exists" "emptydir/$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_file_exists" "emptydir/$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_file_exists" "emptydir/$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "dir_exists" "emptydir/$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "dir_exists" "emptydir/$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_dir_exists" "emptydir/$name" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "rename" "symlink_to_dir_exists" "emptydir/$name/noexist" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ fi
+ return 0
+}
+
+#
+# Check error code returns traversing through different
+# kinds of symlinks over SMB2.
+#
+test_symlink_traversal_SMB2()
+{
+ test_symlink_traversal_SMB2_onename "symlink_noexist" "no rename" || return 1
+ test_symlink_traversal_SMB2_onename "symlink_file_outside_share" "do rename" || return 1
+ test_symlink_traversal_SMB2_onename "symlink_dir_outside_share" "do rename" || return 1
+ test_symlink_traversal_SMB2_onename "symlink_dir_outside_share_noexist" "no rename" || return 1
+ test_symlink_traversal_SMB2_onename "symlink_file_outside_share_noperms" "do rename" || return 1
+ test_symlink_traversal_SMB2_onename "symlink_dir_outside_share_noperms" "do rename" || return 1
+
+ # Note the share has 'follow symlinks = yes'
+ smbclient_expect_error "ls" "." "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "noexist1" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "noexist1/noexist2" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dot" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_to_dot/noexist1" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "symlink_to_dot/noexist1/noexist2" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dot/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "file_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "file_exists/noexist1" "" "NT_STATUS_NOT_A_DIRECTORY" || return 1
+ smbclient_expect_error "ls" "file_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "file_exists/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_file_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_to_file_exists/noexist1" "" "NT_STATUS_NOT_A_DIRECTORY" || return 1
+ smbclient_expect_error "ls" "symlink_to_file_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_file_exists/noexist1/noexist2/noexist" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "dir_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "dir_exists/noexist1" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "dir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "dir_exists/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "dir_exists/subfile_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "dir_exists/subfile_exists/noexist1" "" "NT_STATUS_NOT_A_DIRECTORY" || return 1
+ smbclient_expect_error "ls" "dir_exists/subfile_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "dir_exists/subfile_exists/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "dir_exists/subdir_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "dir_exists/subdir_exists/noexist1" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "dir_exists/subdir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "dir_exists/subdir_exists/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/noexist1" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subfile_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subfile_exists/noexist1" "" "NT_STATUS_NOT_A_DIRECTORY" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subfile_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subfile_exists/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subdir_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subdir_exists/noexist1" "" "NT_STATUS_NO_SUCH_FILE" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subdir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "ls" "symlink_to_dir_exists/subdir_exists/noexist1/noexist2/noexist3" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ smbclient_expect_error "get" "." "" "NT_STATUS_OBJECT_NAME_INVALID" || return 1
+ smbclient_expect_error "get" "noexist1" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dot" "" "NT_STATUS_FILE_IS_A_DIRECTORY" || return 1
+ smbclient_expect_error "get" "symlink_to_dot/noexist1" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dot/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "file_exists" "$dir_outside_share/file_exists" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "get" "file_exists/noexist1" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "file_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_file_exists" "$dir_outside_share/symlink_to_file_exists" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "get" "symlink_to_file_exists/noexist1" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_file_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "dir_exists" "" "NT_STATUS_FILE_IS_A_DIRECTORY" || return 1
+ smbclient_expect_error "get" "dir_exists/noexist1" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "dir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "dir_exists/subfile_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "get" "dir_exists/subfile_exists/noexist1" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "dir_exists/subfile_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "dir_exists/subdir_exists" "" "NT_STATUS_FILE_IS_A_DIRECTORY" || return 1
+ smbclient_expect_error "get" "dir_exists/subdir_exists/noexist1" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "dir_exists/subdir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists" "" "NT_STATUS_FILE_IS_A_DIRECTORY" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/noexist1" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/subfile_exists" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/subfile_exists/noexist1" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/subfile_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/subdir_exists" "" "NT_STATUS_FILE_IS_A_DIRECTORY" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/subdir_exists/noexist1" "" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "symlink_to_dir_exists/subdir_exists/noexist1/noexist2" "" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_expect_error "get" "x/passwd" "passwd" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ #
+ # Test paths within share with no permissions.
+ #
+ # Can't 'get' file with no perms or a symlink to it.
+ smbclient_expect_error "get" "file_inside_share_noperms" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ smbclient_expect_error "get" "symlink_file_inside_share_noperms" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ # But can list it and the symlink to it.
+ smbclient_expect_error "ls" "file_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_file_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ # Can't 'get' file inside a directory with no perms or a symlink to it.
+ smbclient_expect_error "get" "dir_inside_share_noperms/noperm_file_exists" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ smbclient_expect_error "get" "symlink_dir_inside_share_noperms/noperm_file_exists" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ # But can list the directory with no perms and the symlink to it.
+ smbclient_expect_error "ls" "dir_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ smbclient_expect_error "ls" "symlink_dir_inside_share_noperms" "" "NT_STATUS_OK" || return 1
+ # Check that 'get' on non existing subpaths also returns NT_STATUS_ACCESS_DENIED
+ smbclient_expect_error "get" "symlink_dir_inside_share_noperms/noperm_file_exists/_none_" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ smbclient_expect_error "get" "symlink_dir_inside_share_noperms/noperm_subdir_exists/noperm_subdir_file_exists" "" "NT_STATUS_ACCESS_DENIED" || return 1
+ smbclient_expect_error "get" "symlink_dir_inside_share_noperms/noperm_subdir_exists/noperm_subdir_file_exists/_none_" "" "NT_STATUS_ACCESS_DENIED" || return 1
+}
+
+testit "symlink_traversal_SMB2" \
+ test_symlink_traversal_SMB2 ||
+ failed=$((failed + 1))
+
+#
+# Cleanup.
+do_cleanup
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_testparm_s3.sh b/source3/script/tests/test_testparm_s3.sh
new file mode 100755
index 0000000..a11ef85
--- /dev/null
+++ b/source3/script/tests/test_testparm_s3.sh
@@ -0,0 +1,150 @@
+#!/bin/sh
+
+# Tests for lp_load() via testparm.
+#
+# The main purpose (for now) is to test all the special handlers
+# and the macro expansions.
+
+if [ $# -lt 1 ]; then
+ cat <<EOF
+Usage: test_testparm_s3.sh LOCAL_PATH
+EOF
+ exit 1
+fi
+
+LOCAL_PATH="$1"
+
+TEMP_CONFFILE=${LOCAL_PATH}/smb.conf.tmp
+TESTPARM="$VALGRIND ${TESTPARM:-$BINDIR/testparm} --suppress-prompt --skip-logic-checks"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+test_include_expand_macro()
+{
+ MACRO=$1
+ rm -f ${TEMP_CONFFILE}
+ cat >${TEMP_CONFFILE} <<EOF
+[global]
+ include = ${TEMP_CONFFILE}.%${MACRO}
+EOF
+ ${TESTPARM} ${TEMP_CONFFILE}
+}
+
+test_one_global_option()
+{
+ OPTION="$@"
+ rm -f ${TEMP_CONFFILE}
+ cat >${TEMP_CONFFILE} <<EOF
+[global]
+ ${OPTION}
+EOF
+ ${TESTPARM} ${TEMP_CONFFILE}
+}
+
+test_copy()
+{
+ rm -f ${TEMP_CONFFILE}
+ cat >${TEMP_CONFFILE} <<EOF
+[share1]
+ path = /tmp
+ read only = no
+
+[share2]
+ copy = share1
+EOF
+ ${TESTPARM} ${TEMP_CONFFILE}
+}
+
+test_testparm_deprecated()
+{
+ name=$1
+ old_SAMBA_DEPRECATED_SUPPRESS=$SAMBA_DEPRECATED_SUPPRESS
+ SAMBA_DEPRECATED_SUPPRESS=
+ export SAMBA_DEPRECATED_SUPPRESS
+ testit_grep $name 'WARNING: The "lsaovernetlogon" option is deprecated' $VALGRIND ${TESTPARM} ${TEMP_CONFFILE} --option='lsaovernetlogon=true'
+ SAMBA_DEPRECATED_SUPPRESS=$old_SAMBA_DEPRECATED_SUPPRESS
+ export SAMBA_DEPRECATED_SUPPRESS
+}
+
+test_testparm_deprecated_suppress()
+{
+ name=$1
+ subunit_start_test "$name"
+ output=$(SAMBA_DEPRECATED_SUPPRESS=1 $VALGRIND ${TESTPARM} ${TEMP_CONFFILE} --option='lsa over netlogon = true' 2>&1)
+ status=$?
+ if [ "$status" = "0" ]; then
+ echo "$output" | grep --quiet 'WARNING: The "lsa over netlogon " option is deprecated'
+ status=$?
+ if [ "$status" = "1" ]; then
+ subunit_pass_test "$name"
+ else
+ echo $output | subunit_fail_test "$name"
+ fi
+ else
+ echo $output | subunit_fail_test "$name"
+ fi
+}
+
+testit "name resolve order = lmhosts wins host bcast" \
+ test_one_global_option "name resolve order = lmhosts wins host bcast" ||
+ failed=$(expr ${failed} + 1)
+
+testit_expect_failure "name resolve order = bad wins host bcast" \
+ test_one_global_option "name resolve order = bad wins host bcast" ||
+ failed=$(expr ${failed} + 1)
+
+testit_expect_failure "name resolve order = lmhosts bad host bcast" \
+ test_one_global_option "name resolve order = lmhosts bad host bcast" ||
+ failed=$(expr ${failed} + 1)
+
+testit_expect_failure "name resolve order = lmhosts wins bad bcast" \
+ test_one_global_option "name resolve order = lmhosts wins bad bcast" ||
+ failed=$(expr ${failed} + 1)
+
+testit_expect_failure "name resolve order = lmhosts wins host bad" \
+ test_one_global_option "name resolve order = lmhosts wins host bad" ||
+ failed=$(expr ${failed} + 1)
+
+testit "netbios name" \
+ test_one_global_option "netbios name = funky" ||
+ failed=$(expr ${failed} + 1)
+
+testit "netbios aliases" \
+ test_one_global_option "netbios aliases = funky1 funky2 funky3" ||
+ failed=$(expr ${failed} + 1)
+
+testit "netbios scope" \
+ test_one_global_option "netbios scope = abc" ||
+ failed=$(expr ${failed} + 1)
+
+testit "workgroup" \
+ test_one_global_option "workgroup = samba" ||
+ failed=$(expr ${failed} + 1)
+
+testit "display charset" \
+ test_one_global_option "display charset = UTF8" ||
+ failed=$(expr ${failed} + 1)
+
+testit "ldap debug level" \
+ test_one_global_option "ldap debug level = 7" ||
+ failed=$(expr ${failed} + 1)
+
+for LETTER in U G D I i L N M R T a d h m v w V; do
+ testit "include with %${LETTER} macro expansion" \
+ test_include_expand_macro "${LETTER}" ||
+ failed=$(expr ${failed} + 1)
+done
+
+testit "copy" \
+ test_copy ||
+ failed=$(expr ${failed} + 1)
+
+test_testparm_deprecated "test_deprecated_warning_printed"
+test_testparm_deprecated_suppress "test_deprecated_warning_suppressed"
+
+rm -f ${TEMP_CONFFILE}
+
+testok $0 ${failed}
diff --git a/source3/script/tests/test_tevent_glib_glue.sh b/source3/script/tests/test_tevent_glib_glue.sh
new file mode 100755
index 0000000..fa1d105
--- /dev/null
+++ b/source3/script/tests/test_tevent_glib_glue.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+TESTNAME="tevent_glib_glue_test"
+
+if [ ! -x $BINDIR/tevent_glib_glue_test ]; then
+ subunit_start_test "$TESTNAME"
+ subunit_skip_test "$TESTNAME" <<EOF
+Test needs glib2-devel
+EOF
+ testok $0 $failed
+fi
+
+testit "$TESTNAME" $VALGRIND $BINDIR/tevent_glib_glue_test ||
+ failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_timestamps.sh b/source3/script/tests/test_timestamps.sh
new file mode 100755
index 0000000..a158beb
--- /dev/null
+++ b/source3/script/tests/test_timestamps.sh
@@ -0,0 +1,72 @@
+#!/bin/sh
+#
+# This verifies getting and setting timestamps with non-trivial values like 0
+# and < 0 works.
+#
+
+if [ $# -lt 5 ]; then
+ echo "Usage: $0 SERVER_IP USERNAME PASSWORD PREFIX SMBCLIENT"
+ exit 1
+fi
+
+SERVER_IP="$1"
+USERNAME="$2"
+PASSWORD="$3"
+PREFIX="$4"
+SMBCLIENT="$5"
+
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+failed=0
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+export TZ=GMT
+
+setup_testfiles()
+{
+ touch -d "$(date --date=@0)" $PREFIX/time_0
+ touch -d "$(date --date=@-1)" $PREFIX/time_-1
+ touch -d "$(date --date=@-2)" $PREFIX/time_-2
+ touch -t 196801010000 $PREFIX/time_1968
+}
+
+remove_testfiles()
+{
+ rm $PREFIX/time_0
+ rm $PREFIX/time_-1
+ rm $PREFIX/time_-2
+ rm $PREFIX/time_1968
+}
+
+test_time()
+{
+ local file="$1"
+ local expected="$2"
+
+ $SMBCLIENT //$SERVER/tmp -U $USERNAME%$PASSWORD -c "allinfo $file"
+ out=$($SMBCLIENT //$SERVER/tmp -U $USERNAME%$PASSWORD -c "allinfo $file" 2>&1) || return 1
+ echo "smbclient allinfo on $file returned: \"$out\""
+
+ # Ignore create_time as that is synthesized
+ for time in access_time write_time change_time; do
+ echo "$out" | grep "$time" | grep "$expected" || {
+ echo "Expected \"$expected\", got: \"$(echo $out | grep $time)\""
+ return 1
+ }
+ done
+}
+
+#Setup
+testit "create testfiles" setup_testfiles || failed=$(expr $failed + 1)
+
+# Tests
+testit "time=0" test_time time_0 "Thu Jan 1 12:00:00 AM 1970 GMT" || failed=$(expr $failed + 1)
+testit "time=-1" test_time time_-1 "Wed Dec 31 11:59:59 PM 1969 GMT" || failed=$(expr $failed + 1)
+testit "time=-2" test_time time_-2 "Wed Dec 31 11:59:58 PM 1969 GMT" || failed=$(expr $failed + 1)
+testit "time=1968" test_time time_1968 "Mon Jan 1 12:00:00 AM 1968 GMT" || failed=$(expr $failed + 1)
+
+# Cleanup
+testit "delete testfile" remove_testfiles || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_user_in_sharelist.sh b/source3/script/tests/test_user_in_sharelist.sh
new file mode 100755
index 0000000..0069d6d
--- /dev/null
+++ b/source3/script/tests/test_user_in_sharelist.sh
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+if [ $# -lt 2 ]; then
+ echo Usage: $0 RPCCLIENT SERVER
+ exit 1
+fi
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+RPCCLIENT="$1"; shift 1
+SERVER="$1"; shift 1
+
+"${RPCCLIENT}" "${SERVER}" -U"${USER}"%"${PASSWORD}" -c netshareenum |
+ grep "^netname: $USER\$"
+RC=$?
+testit "Verify username is listed in netshareenum due to [homes]" \
+ test $RC = 0 || failed=$((failed+1))
+
+testok $0 $failed
diff --git a/source3/script/tests/test_usernamemap.sh b/source3/script/tests/test_usernamemap.sh
new file mode 100755
index 0000000..334070e
--- /dev/null
+++ b/source3/script/tests/test_usernamemap.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+#
+# Copyright (c) 2022 Pavel Filipenský <pfilipen@redhat.com>
+#
+# Tests for "username map" smb.conf parameter for UNIX groups
+
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: test_usernamemap.sh SERVER SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER="$1"
+SMBCLIENT="$2"
+SMBCLIENT="${VALGRIND} ${SMBCLIENT}"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "${incdir}"/subunit.sh
+
+failed=0
+
+# jackthemapper is mapped to jacknomapper, so we need jacknomapper password
+testit "jackthemapper" "${SMBCLIENT}" //"${SERVER}"/tmp -U"${SERVER}/jackthemapper%nOmApsEcrEt" -c ls || failed=$((failed + 1))
+# jacknomapper is not mapped, so we need jacknomapper password
+testit "jacknomapper" "${SMBCLIENT}" //"${SERVER}"/tmp -U"${SERVER}/jacknomapper%nOmApsEcrEt" -c ls || failed=$((failed + 1))
+
+testok "$0" "${failed}"
diff --git a/source3/script/tests/test_valid_users.sh b/source3/script/tests/test_valid_users.sh
new file mode 100755
index 0000000..659eb6a
--- /dev/null
+++ b/source3/script/tests/test_valid_users.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+#
+# Blackbox test for valid users.
+#
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: valid_users SERVER SERVER_IP DOMAIN USERNAME PASSWORD PREFIX SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+DOMAIN=${3}
+USERNAME=${4}
+PASSWORD=${5}
+PREFIX=${6}
+SMBCLIENT=${7}
+shift 7
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$*"
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+# Test listing a share with valid users succeeds
+test_valid_users_access()
+{
+ tmpfile=$PREFIX/smbclient.in.$$
+ prompt="foo"
+ cat >$tmpfile <<EOF
+ls
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD "//$SERVER/$1" -I $SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "failed accessing share with valid users with error $ret"
+
+ false
+ return
+ fi
+
+ echo "$out" | grep "$prompt" >/dev/null 2>&1
+
+ ret=$?
+ if [ $ret = 0 ]; then
+ # got the correct prompt .. succeed
+ true
+ else
+ echo "$out"
+ echo "failed listing share with valid users"
+ false
+ fi
+}
+
+testit "accessing a valid users share succeeds" \
+ test_valid_users_access valid-users-access ||
+ failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/test_veto_files.sh b/source3/script/tests/test_veto_files.sh
new file mode 100755
index 0000000..201883e
--- /dev/null
+++ b/source3/script/tests/test_veto_files.sh
@@ -0,0 +1,279 @@
+#!/bin/sh
+#
+# Check smbclient cannot get a file that matches a veto files
+# parameter, or inside a directory that matches a veto files
+# parameter.
+#
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15143
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 SERVER SERVER_IP USERNAME PASSWORD SHAREPATH SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+USERNAME=${3}
+PASSWORD=${4}
+SHAREPATH=${5}
+SMBCLIENT=${6}
+shift 6
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+# Used by test_smbclient()
+# shellcheck disable=2034
+smbclient="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$@"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+. "${incdir}/common_test_fns.inc"
+
+failed=0
+
+TMPDIR=${PREFIX_ABS}/$(basename "${0}")
+mkdir -p "${TMPDIR}" || exit 1
+cd "${TMPDIR}" || exit 1
+
+#
+# Cleanup function.
+#
+do_cleanup()
+{
+ (
+ #subshell.
+ rm -rf "$SHAREPATH/dir_1"
+ rm -rf "$SHAREPATH/veto_name_dir"
+ rm -rf "$SHAREPATH/veto_name_dir\"mangle"
+ rm -f "$SHAREPATH/veto_name_file"
+ rm -f "$SHAREPATH/veto_name_file\"mangle"
+ rm -f "${SHAREPATH}/regular_file"
+ rm -f "${SHAREPATH}/.hidden_file"
+ )
+}
+
+#
+# smbclient function given path and expected error.
+#
+smbclient_get_expect_error()
+{
+ filename1="$1"
+ expected_error="$2"
+ tmpfile=${TMPDIR}/smbclient_interactive_prompt_commands
+ cat >"$tmpfile" <<EOF
+get $filename1 got_file
+quit
+EOF
+ rm -f got_file
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/veto_files -I$SERVER_IP < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+ rm -f got_file
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed accessing veto_files share with error %s\n" "$ret"
+ return 1
+ fi
+
+ if [ "$expected_error" = "NT_STATUS_OK" ]; then
+ printf "%s" "$out" | grep "NT_STATUS_" | wc -l | grep '^0$'
+ else
+ printf "%s" "$out" | grep "$expected_error"
+ fi
+ ret=$?
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed - should get %s doing \"get %s got_file\"\n" "$expected_error" "$filename1"
+ return 1
+ fi
+}
+
+smbclient_create_expect_error()
+{
+ filename="$1.$$"
+ expected_error="$2"
+ tmpfile=${TMPDIR}/smbclient_interactive_prompt_commands
+ cat >"$tmpfile" <<EOF
+put $tmpfile $filename
+quit
+EOF
+
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/veto_files -I$SERVER_IP < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+ rm -f "$SHAREPATH/$filename"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed accessing veto_files share with error %s\n" "$ret"
+ return 1
+ fi
+
+ if [ "$expected_error" = "NT_STATUS_OK" ]; then
+ printf "%s" "$out" | grep -c "NT_STATUS_" && false
+ else
+ printf "%s" "$out" | grep "$expected_error"
+ fi
+ ret=$?
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed - should get %s doing \"put %s\"\n" "$expected_error" "$filename"
+ return 1
+ fi
+}
+
+#
+# Using the share "[veto_files]" ensure we
+# cannot fetch a veto'd file or file in a veto'd directory.
+#
+test_get_veto_file()
+{
+ # toplevel
+ smbclient_get_expect_error "veto_name_file" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "veto_name_dir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "veto_name_dir/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ # toplevel mangle names
+ smbclient_get_expect_error "VHXE5P~M" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "VF5SKC~B/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "VF5SKC~B/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ # depth1
+ smbclient_get_expect_error "dir1/veto_name_file" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/veto_name_dir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/veto_name_dir/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ # depth1 mangle names
+ smbclient_get_expect_error "dir1/VHXE5P~M" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/VF5SKC~B/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/VF5SKC~B/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ # depth2
+ smbclient_get_expect_error "dir1/dir2/veto_name_file" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/veto_name_dir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/veto_name_dir/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ # depth2 mangle names
+ smbclient_get_expect_error "dir1/dir2/VHXE5P~M" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/VF5SKC~B/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/VF5SKC~B/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ # depth3
+ smbclient_get_expect_error "dir1/dir2/dir3/veto_name_file" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/dir3/veto_name_dir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/dir3/veto_name_dir/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ # depth3 mangle names
+ smbclient_get_expect_error "dir1/dir2/dir3/VHXE5P~M" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/dir3/VF5SKC~B/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_get_expect_error "dir1/dir2/dir3/VF5SKC~B/testdir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+
+ return 0
+}
+
+test_create_veto_file()
+{
+ # Test creating files
+ smbclient_create_expect_error "veto_name_file" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+ smbclient_create_expect_error "veto_name_dir/file_inside_dir" "NT_STATUS_OBJECT_PATH_NOT_FOUND" || return 1
+ smbclient_create_expect_error "dir1/veto_name_file" "NT_STATUS_OBJECT_NAME_NOT_FOUND" || return 1
+
+ return 0
+}
+
+do_cleanup
+
+echo "regular_file" > "${SHAREPATH}/regular_file"
+echo "hidden_file" > "${SHAREPATH}/.hidden_file"
+
+test_smbclient "download regular file" \
+ "get regular_file" "//${SERVER}/veto_files_nohidden" \
+ -U"${USERNAME}%${PASSWORD}" ||
+ failed=$((failed + 1))
+rm -f regular_file
+test_smbclient_expect_failure "hidden file can't be downloaded" \
+ "get .hidden_file" "//${SERVER}/veto_files_nohidden" \
+ -U"${USERNAME}%${PASSWORD}" ||
+ failed=$((failed + 1))
+test_smbclient "list files" \
+ "ls" "//${SERVER}/veto_files_nohidden" \
+ -U"${USERNAME}%${PASSWORD}" ||
+ failed=$((failed + 1))
+
+do_cleanup
+
+# Using hash2, veto_name_file\"mangle == VHXE5P~M
+# Using hash2, veto_name_dir\"mangle == VF5SKC~B
+
+# I think a depth of 3 should be enough.
+# toplevel
+touch "$SHAREPATH/veto_name_file"
+mkdir "$SHAREPATH/veto_name_dir"
+touch "$SHAREPATH/veto_name_dir/file_inside_dir"
+mkdir "$SHAREPATH/veto_name_dir/testdir"
+touch "$SHAREPATH/veto_name_dir/testdir/file_inside_dir"
+# toplevel mangle names.
+touch "$SHAREPATH/veto_name_file\"mangle"
+mkdir "$SHAREPATH/veto_name_dir\"mangle"
+touch "$SHAREPATH/veto_name_dir\"mangle/file_inside_dir"
+mkdir "$SHAREPATH/veto_name_dir\"mangle/testdir"
+touch "$SHAREPATH/veto_name_dir\"mangle/testdir/file_inside_dir"
+
+#depth1
+mkdir "$SHAREPATH/dir1"
+touch "$SHAREPATH/dir1/veto_name_file"
+mkdir "$SHAREPATH/dir1/veto_name_dir"
+touch "$SHAREPATH/dir1/veto_name_dir/file_inside_dir"
+mkdir "$SHAREPATH/dir1/veto_name_dir/testdir"
+touch "$SHAREPATH/dir1/veto_name_dir/testdir/file_inside_dir"
+# depth1 mangle names.
+touch "$SHAREPATH/dir1/veto_name_file\"mangle"
+mkdir "$SHAREPATH/dir1/veto_name_dir\"mangle"
+touch "$SHAREPATH/dir1/veto_name_dir\"mangle/file_inside_dir"
+mkdir "$SHAREPATH/dir1/veto_name_dir\"mangle/testdir"
+touch "$SHAREPATH/dir1/veto_name_dir\"mangle/testdir/file_inside_dir"
+
+#depth2
+mkdir "$SHAREPATH/dir1/dir2"
+touch "$SHAREPATH/dir1/dir2/veto_name_file"
+mkdir "$SHAREPATH/dir1/dir2/veto_name_dir"
+touch "$SHAREPATH/dir1/dir2/veto_name_dir/file_inside_dir"
+mkdir "$SHAREPATH/dir1/dir2/veto_name_dir/testdir"
+touch "$SHAREPATH/dir1/dir2/veto_name_dir/testdir/file_inside_dir"
+# depth2 mangle names.
+touch "$SHAREPATH/dir1/dir2/veto_name_file\"mangle"
+mkdir "$SHAREPATH/dir1/dir2/veto_name_dir\"mangle"
+touch "$SHAREPATH/dir1/dir2/veto_name_dir\"mangle/file_inside_dir"
+mkdir "$SHAREPATH/dir1/dir2/veto_name_dir\"mangle/testdir"
+touch "$SHAREPATH/dir1/dir2/veto_name_dir\"mangle/testdir/file_inside_dir"
+
+#depth3
+mkdir "$SHAREPATH/dir1/dir2/dir3"
+touch "$SHAREPATH/dir1/dir2/dir3/veto_name_file"
+mkdir "$SHAREPATH/dir1/dir2/dir3/veto_name_dir"
+touch "$SHAREPATH/dir1/dir2/dir3/veto_name_dir/file_inside_dir"
+mkdir "$SHAREPATH/dir1/dir2/dir3/veto_name_dir/testdir"
+touch "$SHAREPATH/dir1/dir2/dir3/veto_name_dir/testdir/file_inside_dir"
+# depth3 mangle names.
+touch "$SHAREPATH/dir1/dir2/dir3/veto_name_file\"mangle"
+mkdir "$SHAREPATH/dir1/dir2/dir3/veto_name_dir\"mangle"
+touch "$SHAREPATH/dir1/dir2/dir3/veto_name_dir\"mangle/file_inside_dir"
+mkdir "$SHAREPATH/dir1/dir2/dir3/veto_name_dir\"mangle/testdir"
+touch "$SHAREPATH/dir1/dir2/dir3/veto_name_dir\"mangle/testdir/file_inside_dir"
+
+testit "create_veto_file" test_create_veto_file || failed=$((failed + 1))
+testit "get_veto_file" test_get_veto_file || failed=$(("$failed" + 1))
+
+do_cleanup
+
+cd "${PREFIX_ABS}" && rm -rf ${TMPDIR}
+
+exit "$failed"
diff --git a/source3/script/tests/test_veto_rmdir.sh b/source3/script/tests/test_veto_rmdir.sh
new file mode 100755
index 0000000..4d7db9d
--- /dev/null
+++ b/source3/script/tests/test_veto_rmdir.sh
@@ -0,0 +1,217 @@
+#!/bin/sh
+#
+# Check smbclient can (or cannot) delete a directory containing veto files.
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=14878
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 SERVER SERVER_IP USERNAME PASSWORD SHAREPATH SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+SERVER_IP=${2}
+USERNAME=${3}
+PASSWORD=${4}
+SHAREPATH=${5}
+SMBCLIENT=${6}
+shift 6
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$@"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+rmdir_path="$SHAREPATH/dir"
+
+test_veto_nodelete_rmdir()
+{
+ local veto_path="$rmdir_path/veto_name1"
+ local msdfs_link_path="$rmdir_path/dfs_link"
+ local tmpfile=$PREFIX/smbclient.in.$$
+
+ # Create rmdir directory.
+ mkdir -p "$rmdir_path"
+ # Create veto file underneath.
+ touch "$veto_path"
+ # Create msdfs link underneath.
+ ln -s "msdfs:$SERVER_IP\\ro-tmp" "$msdfs_link_path"
+
+ cat >"$tmpfile" <<EOF
+cd dir
+ls
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/veto_files_nodelete -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share veto_files_nodelete - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should only see the dfs_link file.
+ echo "$out" | grep dfs_link
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed to see dfs_link in share veto_files_nodelete"
+ echo "$out"
+ return 1
+ fi
+
+ # Now remove the dfs_link file.
+ rm -rf "$msdfs_link_path"
+
+ # Try and remove the directory, should fail with NT_STATUS_DIRECTORY_NOT_EMPTY.
+ cat >"$tmpfile" <<EOF
+rd dir
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/veto_files_nodelete -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share veto_files_nodelete - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should get NT_STATUS_DIRECTORY_NOT_EMPTY.
+ echo "$out" | grep NT_STATUS_DIRECTORY_NOT_EMPTY
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed to get error NT_STATUS_DIRECTORY_NOT_EMPTY in share veto_files_nodelete"
+ echo "$out"
+ return 1
+ fi
+
+ # remove the veto file - directory should now be empty.
+ rm -rf "$veto_path"
+
+ # Try and remove the directory, should now succeed.
+ cat >"$tmpfile" <<EOF
+rd dir
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/veto_files_nodelete -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share veto_files_nodelete - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should get no NT_STATUS_ errors.
+ echo "$out" | grep NT_STATUS_
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "Got error NT_STATUS_ in share veto_files_nodelete"
+ echo "$out"
+ return 1
+ fi
+
+ return 0
+}
+
+test_veto_delete_rmdir()
+{
+ local veto_path="$rmdir_path/veto_name1"
+ local msdfs_link_path="$rmdir_path/dfs_link"
+ local tmpfile=$PREFIX/smbclient.in.$$
+
+ # Create rmdir directory.
+ mkdir -p "$rmdir_path"
+ # Create veto file underneath.
+ touch "$veto_path"
+ # Create msdfs link underneath.
+ ln -s "msdfs:$SERVER_IP\\ro-tmp" "$msdfs_link_path"
+
+ cat >"$tmpfile" <<EOF
+cd dir
+ls
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/veto_files_delete -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share veto_files_delete - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should only see the dfs_link file.
+ echo "$out" | grep dfs_link
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Failed to see dfs_link in share veto_files_delete"
+ echo "$out"
+ return 1
+ fi
+
+ # Now remove the dfs_link file.
+ rm -rf "$msdfs_link_path"
+
+ # Try and remove the directory, should now succeed.
+ cat >"$tmpfile" <<EOF
+rd dir
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/veto_files_delete -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ if [ $ret != 0 ]; then
+ echo "Failed accessing share veto_files_delete - $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should get no NT_STATUS_ errors.
+ echo "$out" | grep NT_STATUS_
+ ret=$?
+ if [ $ret -eq 0 ]; then
+ echo "Got error NT_STATUS_ in share veto_files_delete"
+ echo "$out"
+ return 1
+ fi
+
+ return 0
+}
+
+testit "rmdir cannot delete directory containing a veto file" \
+ test_veto_nodelete_rmdir || failed=$(expr "$failed" + 1)
+
+rm -rf "$rmdir_path"
+
+testit "rmdir can delete directory containing a veto file" \
+ test_veto_delete_rmdir || failed=$(expr "$failed" + 1)
+
+rm -rf "$rmdir_path"
+
+exit "$failed"
diff --git a/source3/script/tests/test_virus_scanner.sh b/source3/script/tests/test_virus_scanner.sh
new file mode 100755
index 0000000..83b50df
--- /dev/null
+++ b/source3/script/tests/test_virus_scanner.sh
@@ -0,0 +1,135 @@
+#!/bin/sh
+# Copyright (c) 2022 Pavel Filipenský <pfilipen@redhat.com>
+# shellcheck disable=1091
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: $0 SERVER_IP SHARE LOCAL_PATH SMBCLIENT
+EOF
+ exit 1
+fi
+
+SERVER_IP=${1}
+SHARE=${2}
+LOCAL_PATH=${3}
+SMBCLIENT=${4}
+
+SMBCLIENT="${VALGRIND} ${SMBCLIENT}"
+
+failed=0
+sharedir="${LOCAL_PATH}/${SHARE}"
+
+incdir="$(dirname "$0")/../../../testprogs/blackbox"
+. "${incdir}/subunit.sh"
+
+check_infected_read()
+{
+ rm -rf "${sharedir:?}"/*
+
+ if ! mkdir "${sharedir}/read1"; then
+ echo "ERROR: Cannot create ${sharedir}/read1"
+ return 1
+ fi
+
+ if ! mkdir "${sharedir}/read1/read2"; then
+ echo "ERROR: Cannot create ${sharedir}/read1/read2"
+ return 1
+ fi
+
+ if ! touch "${sharedir}/read1/read2/infected.txt"; then
+ echo "ERROR: Cannot create ${sharedir}/read1/read2/infected.txt"
+ return 1
+ fi
+
+ ${SMBCLIENT} "//${SERVER_IP}/${SHARE}" -U"${USER}"%"${PASSWORD}" -c "get read1/read2/infected.txt ${sharedir}/read1/read2/infected.download.txt"
+
+ # check that virusfilter:rename prefix/suffix was added
+ if [ ! -f "${sharedir}/read1/read2/virusfilter.infected.txt.infected" ]; then
+ echo "ERROR: ${sharedir}/read1/read2/virusfilter.infected.txt.infected is missing."
+ return 1
+ fi
+
+ # check that file was not downloaded
+ if [ -f "${sharedir}/read1/read2/infected.download.txt" ]; then
+ echo "ERROR: {sharedir}/read1/read2/infected.download.txt should not exist."
+ return 1
+ fi
+
+ rm -rf "${sharedir:?}"/*
+ return 0
+}
+
+check_infected_write()
+{
+ rm -rf "${sharedir:?}"/*
+ smbfile=infected.upload.txt
+ smbfilerenamed="virusfilter.${smbfile}.infected"
+
+ # non empty file is needed
+ # vsf_virusfilter performs a scan only if fsp->fsp_flags.modified
+ if ! echo "Hello Virus!" >"${sharedir}/infected.txt"; then
+ echo "ERROR: Cannot create ${sharedir}/infected.txt"
+ return 1
+ fi
+
+ ${SMBCLIENT} "//${SERVER_IP}/${SHARE}" -U"${USER}"%"${PASSWORD}" -c "put ${sharedir}/infected.txt ${smbfile}"
+
+ # check that virusfilter:rename prefix/suffix was added
+ if [ ! -f "${sharedir}/${smbfilerenamed}" ]; then
+ echo "ERROR: ${sharedir}/${smbfilerenamed} is missing."
+ return 1
+ fi
+
+ # check that file was not uploaded
+ if [ -f "${sharedir}/infected.upload.txt" ]; then
+ echo "ERROR: {sharedir}/${smbfile} should not exist."
+ return 1
+ fi
+
+ return 0
+}
+
+check_healthy_read()
+{
+ rm -rf "${sharedir:?}"/*
+
+ if ! echo "Hello Samba!" >"${sharedir}/healthy.txt"; then
+ echo "ERROR: Cannot create ${sharedir}/healthy.txt"
+ return 1
+ fi
+
+ ${SMBCLIENT} //"${SERVER_IP}"/"${SHARE}" -U"${USER}"%"${PASSWORD}" -c "get healthy.txt ${sharedir}/healthy.download.txt"
+
+ if ! cmp "${sharedir}/healthy.txt" "${sharedir}/healthy.download.txt"; then
+ echo "ERROR: cmp ${sharedir}/healthy.txt ${sharedir}/healthy.download.txt FAILED"
+ return 1
+ fi
+
+ return 0
+}
+
+check_healthy_write()
+{
+ rm -rf "${sharedir:?}"/*
+
+ if ! echo "Hello Samba!" >"${sharedir}/healthy.txt"; then
+ echo "ERROR: Cannot create ${sharedir}/healthy.txt"
+ return 1
+ fi
+
+ ${SMBCLIENT} //"${SERVER_IP}"/"${SHARE}" -U"${USER}"%"${PASSWORD}" -c "put ${sharedir}/healthy.txt healthy.upload.txt"
+
+ if ! cmp "${sharedir}/healthy.txt" "${sharedir}/healthy.upload.txt"; then
+ echo "ERROR: cmp ${sharedir}/healthy.txt ${sharedir}/healthy.upload.txt FAILED"
+ return 1
+ fi
+
+ return 0
+}
+
+testit "check_infected_read" check_infected_read || failed=$((failed + 1))
+testit "check_infected_write" check_infected_write || failed=$((failed + 1))
+testit "check_healthy_read" check_healthy_read || failed=$((failed + 1))
+testit "check_healthy_write" check_healthy_write || failed=$((failed + 1))
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_volume_serial_number.sh b/source3/script/tests/test_volume_serial_number.sh
new file mode 100755
index 0000000..b156d70
--- /dev/null
+++ b/source3/script/tests/test_volume_serial_number.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# This verifies setting the volume serial number parameter for a share works.
+#
+
+if [ $# -lt 5 ]; then
+ echo "Usage: $0 SERVER_IP USERNAME PASSWORD SHARENAME SMBCLIENT"
+ exit 1
+fi
+
+SERVER_IP="$1"
+USERNAME="$2"
+PASSWORD="$3"
+SHARENAME="$4"
+SMBCLIENT="$5"
+
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+failed=0
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir/subunit.sh"
+
+test_serial_number() {
+
+ output=$($SMBCLIENT "//$SERVER_IP/$SHARENAME" -U "$USERNAME%$PASSWORD" -c "volume") || return 1
+ echo "smbclient volume on $SHARENAME returned: \"$output\""
+
+ expected="0xdeadbeef"
+ echo "$output" | grep $expected || {
+ echo "Expected output containing \"$expected\", got: \"$output\""
+ return 1
+ }
+}
+
+testit "volume serial number for share $SHARENAME" test_serial_number || failed=$((failed+1))
+
+exit "$failed"
diff --git a/source3/script/tests/test_wbinfo_lookuprids_cache.sh b/source3/script/tests/test_wbinfo_lookuprids_cache.sh
new file mode 100755
index 0000000..abb078b
--- /dev/null
+++ b/source3/script/tests/test_wbinfo_lookuprids_cache.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+WBINFO="$VALGRIND ${WBINFO:-$BINDIR/wbinfo}"
+samba_tdbtool=tdbtool
+if test -x $BINDIR/tdbtool; then
+ samba_tdbtool=$BINDIR/tdbtool
+fi
+TDBTOOL="${TDBTOOL:-$samba_tdbtool}"
+
+samba_tdbdump=tdbdump
+if test -x $BINDIR/tdbdump; then
+ samba_tdbdump=$BINDIR/tdbdump
+fi
+TDBDUMP="${TDBDUMP:-$samba_tdbdump}"
+
+NET="$VALGRIND ${NET:-$BINDIR/net}"
+
+cache="$LOCK_DIR"/winbindd_cache.tdb
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+testit "flush" "$NET" "cache" "flush" || failed=$(expr $failed + 1)
+testit "lookuprids1" "$WBINFO" "-R" "512,12345" || failed=$(expr $failed + 1)
+
+key=$("$TDBDUMP" "$cache" | grep ^key.*NDR.*/16/ | cut -d\" -f2)
+
+testit "delete" "$TDBTOOL" "$cache" delete "$key"
+testit "lookuprids2" "$WBINFO" "-R" "512,12345" || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_wbinfo_sids2xids.sh b/source3/script/tests/test_wbinfo_sids2xids.sh
new file mode 100755
index 0000000..b07aebf
--- /dev/null
+++ b/source3/script/tests/test_wbinfo_sids2xids.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+WBINFO="$VALGRIND ${WBINFO:-$BINDIR/wbinfo} $CONFIGURATION"
+NET="$VALGRIND ${NET:-$BINDIR/net} $CONFIGURATION"
+TEST_INT=$(dirname $0)/test_wbinfo_sids2xids_int.py
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+testit "sids2xids" ${TEST_INT} ${WBINFO} ${NET} || failed=$(expr $failed + 1)
+
+testok $0 $failed
diff --git a/source3/script/tests/test_wbinfo_sids2xids_int.py b/source3/script/tests/test_wbinfo_sids2xids_int.py
new file mode 100755
index 0000000..4f2715c
--- /dev/null
+++ b/source3/script/tests/test_wbinfo_sids2xids_int.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import subprocess
+from samba.common import get_string
+
+
+if len(sys.argv) != 3:
+ print("Usage: test_wbinfo_sids2xids_int.py wbinfo net")
+ sys.exit(1)
+
+wbinfo = sys.argv[1]
+netcmd = sys.argv[2]
+
+
+def run(cmd):
+ """
+ Run a cmd, return bytes str for py2 and unicode str for py3.
+
+ NOTE: subprocess api always return bytes, in both py2 and py3.
+ """
+ output = subprocess.check_output(cmd).strip()
+ return get_string(output)
+
+
+def flush_cache(sids=None, uids=None, gids=None):
+ if sids is None:
+ sids = []
+ if uids is None:
+ uids = []
+ if gids is None:
+ gids = []
+
+ for sid in sids:
+ os.system(netcmd + (" cache del IDMAP/SID2XID/%s" % (sid)))
+ for uid in uids:
+ os.system(netcmd + (" cache del IDMAP/UID2SID/%s" % (uid)))
+ for gid in gids:
+ os.system(netcmd + (" cache del IDMAP/GID2SID/%s" % (gid)))
+
+
+def fill_cache(inids, idtype='gid'):
+ for inid in inids:
+ if inid is None:
+ continue
+ run([wbinfo, '--%s-to-sid=%s' % (idtype, inid)])
+
+
+domain = run([wbinfo, "--own-domain"])
+domsid = run([wbinfo, "-n", domain + "/"])
+domsid = domsid.split(' ')[0]
+
+# print domain
+# print domsid
+
+sids = [domsid + '-512', 'S-1-5-32-545', domsid + '-513', 'S-1-1-0', 'S-1-3-1', 'S-1-5-1']
+
+flush_cache(sids=sids)
+
+sids2xids = run([wbinfo, '--sids-to-unix-ids=' + ','.join(sids)])
+
+gids = []
+uids = []
+idtypes = []
+
+for line in sids2xids.split('\n'):
+ result = line.split(' ')[2:]
+ idtypes.append(result[0])
+
+ gid = None
+ uid = None
+ if result[0] == 'gid':
+ gid = result[1]
+ elif result[0] == 'uid':
+ uid = result[1]
+ elif result[0] == 'uid/gid':
+ gid = result[1]
+ uid = result[1]
+
+ if gid == '-1':
+ gid = ''
+ gids.append(gid)
+
+ if uid == '-1':
+ uid = ''
+ uids.append(uid)
+
+# Check the list produced by the sids-to-xids call with the
+# singular variant (sid-to-xid) for each sid in turn.
+
+
+def check_singular(sids, ids, idtype='gid'):
+ i = 0
+ for sid in sids:
+ if ids[i] is None:
+ continue
+
+ outid = run([wbinfo, '--sid-to-%s' % idtype, sid])
+ if outid != ids[i]:
+ print("Expected %s, got %s\n" % (outid, ids[i]))
+ flush_cache(sids=sids, uids=uids, gids=gids)
+ sys.exit(1)
+ i += 1
+
+# Check the list produced by the sids-to-xids call with the
+# multiple variant (sid-to-xid) for each sid in turn.
+
+
+def check_multiple(sids, idtypes):
+ sids2xids = run([wbinfo, '--sids-to-unix-ids=' + ','.join(sids)])
+ # print sids2xids
+ i = 0
+ for line in sids2xids.split('\n'):
+ result = line.split(' ')[2:]
+
+ if result[0] != idtypes[i]:
+ print("Expected %s, got %s\n" % (idtypes[i], result[0]))
+ flush_cache(sids=sids, uids=uids, gids=gids)
+ sys.exit(1)
+ i += 1
+
+
+# first round: with filled cache via sid-to-id
+check_singular(sids, gids, 'gid')
+check_singular(sids, uids, 'uid')
+
+# second round: with empty cache
+flush_cache(sids=sids, gids=gids)
+check_singular(sids, gids, 'gid')
+flush_cache(sids=sids, uids=uids)
+check_singular(sids, uids, 'uid')
+
+# third round: with filled cache via uid-to-sid
+flush_cache(sids=uids, uids=uids)
+fill_cache(uids, 'uid')
+check_multiple(sids, idtypes)
+
+# fourth round: with filled cache via gid-to-sid
+flush_cache(sids=sids, gids=gids)
+fill_cache(gids, 'gid')
+check_multiple(sids, idtypes)
+
+# flush the cache so any incorrect mappings don't break other tests
+flush_cache(sids=sids, uids=uids, gids=gids)
+
+sys.exit(0)
diff --git a/source3/script/tests/test_wbinfo_u_large_ad.sh b/source3/script/tests/test_wbinfo_u_large_ad.sh
new file mode 100755
index 0000000..ab5f0ca
--- /dev/null
+++ b/source3/script/tests/test_wbinfo_u_large_ad.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+LDBMODIFY="$VALGRIND ${LDBMODIFY:-$BINDIR/ldbmodify} $CONFIGURATION"
+LDBSEARCH="$VALGRIND ${LDBSEARCH:-$BINDIR/ldbsearch} $CONFIGURATION"
+WBINFO="$VALGRIND ${WBINFO:-$BINDIR/wbinfo} $CONFIGURATION"
+
+NUM_USERS=1234
+
+BASE_DN=$($LDBSEARCH -H ldap://$DC_SERVER -b "" --scope=base defaultNamingContext | awk '/^defaultNamingContext/ {print $2}')
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+seq -w 1 "$NUM_USERS" |
+ xargs -INUM echo -e "dn:cn=large_ad_NUM,cn=users,$BASE_DN\nchangetype:add\nobjectclass:user\nsamaccountname:large_ad_NUM\n" |
+ $LDBMODIFY -H ldap://$DC_SERVER -U "$DOMAIN\Administrator%$DC_PASSWORD"
+
+testit_grep_count \
+ "Make sure $NUM_USERS $DOMAIN users are returned" \
+ "$DOMAIN/large_ad_" \
+ "$NUM_USERS" \
+ ${WBINFO} -u || failed=$(expr $failed + 1)
+
+seq -w 1 "$NUM_USERS" |
+ xargs -INUM echo -e "dn:cn=large_ad_NUM,cn=users,$BASE_DN\nchangetype:delete\n" |
+ $LDBMODIFY -H ldap://$DC_SERVER -U "$DOMAIN\Administrator%$DC_PASSWORD"
+
+testok $0 $failed
diff --git a/source3/script/tests/test_winbind_call_depth_trace.sh b/source3/script/tests/test_winbind_call_depth_trace.sh
new file mode 100755
index 0000000..6d978be
--- /dev/null
+++ b/source3/script/tests/test_winbind_call_depth_trace.sh
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+# Copyright (c) Pavel Filipenský <pfilipensky@samba.org>
+# License: GPLv3
+
+if [ $# -lt 4 ]; then
+ echo "Usage: test_winbind_call_depth_trace SMBCONTROL CONFIGURATION PREFIX TESTENV"
+ exit 1
+fi
+
+SMBCONTROL="${1}"
+CONFIGURATION=${2}
+PREFIX="${3}"
+TESTENV="${4}"
+shift 4
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+PREFIX_ABS="$(readlink -f "${PREFIX}")"
+# Strip from TESTENV the ':local' if present
+TESTENV_SUBDIR=${TESTENV%:*}
+
+LOGFILE="${PREFIX_ABS}/${TESTENV_SUBDIR}/logs/log.winbindd"
+# Add support for "WINBINDD_DONT_LOG_STDOUT=1"
+if [ ! -r "${LOGFILE}" ]; then
+ TEST_LOGFILE="${PREFIX_ABS}/${TESTENV_SUBDIR}/winbindd_test.log"
+ subunit_start_test "test winbind call depth trace"
+ subunit_skip_test "test winbind call depth trace" <<EOF
+Test is skipped, we need $LOGFILE but have only $TEST_LOGFILE which is missing debug headers (they are not printed to stdout).
+EOF
+ exit 0
+fi
+
+saved_level=1
+
+get_winbind_loglevel()
+{
+ s1=$(${SMBCONTROL} "${CONFIGURATION}" winbind debuglevel)
+ # We need to get the all level from output like this:
+ # "PID 664474: all:1 tdb:1 printdrivers:1 lanman:1 smb:1 rpc_parse:1 rpc_srv:1 rpc_cli:1 passdb:1 sam:1..."
+ # 1. remove PID 664474:
+ s2=${s1#PID*: }
+ # "all:1 tdb:1 printdrivers:1 lanman:1 smb:1 rpc_parse:1 rpc_srv:1 rpc_cli:1 passdb"
+ # 2. remove " tdb:1 printdrivers:1 ..."
+ s3=${s2%% *}
+ # "all:1"
+ # 3. remove "all:"
+ saved_level=${s3#all:}
+}
+
+# Example of trace line
+# [2023/01/25 00:20:33.307038, 5, pid=535581, effective(0, 0), real(0, 0), class=winbind, traceid=78, depth=4] ../../source3/winbindd/wb_group_members.c:310(wb_group_members_send)
+test_winbind_call_depth_trace()
+{
+ get_winbind_loglevel
+
+ # If loglevel < 10, set it to 10.
+ if [ "$saved_level" -lt 10 ]; then
+ ${SMBCONTROL} "${CONFIGURATION}" winbind debug 10
+ fi
+
+ COUNT1=$(grep -c wb_group_members_send "$LOGFILE")
+
+ id ADDOMAIN/alice
+ ret=$?
+
+ # Restore loglevel, if it was changed.
+ if [ "$saved_level" -lt 10 ]; then
+ ${SMBCONTROL} "${CONFIGURATION}" winbind debug "$saved_level"
+ fi
+
+ if [ $ret != 0 ]; then
+ echo "Command 'id ADDOMAIN/alice' failed!"
+ return 1
+ fi
+
+ # Check that there are more lines with wb_group_members_send
+ COUNT2=$(grep -c wb_group_members_send "$LOGFILE")
+ if [ "$COUNT1" -eq "$COUNT2" ]; then
+ echo "The number of the trace lines in $LOGFILE has not increased."
+ return 1
+ fi
+
+ # Test that the depth of last line with 'wb_group_members_send' is: depth=4
+ COUNT3=$(grep wb_group_members_send "$LOGFILE" | tail -1 | grep -c depth=4)
+ if [ "$COUNT3" -ne 1 ]; then
+ echo "The last line with wb_group_members_send should have depth=4."
+ return 1
+ fi
+
+ # Test that the indentation of the line below last 'wb_group_members_send' is indented by 2+4*4 spaces:
+ COUNT4=$(grep -A1 wb_group_members_send "$LOGFILE" | tail -1| grep -c '^ WB command group_members start')
+ if [ "$COUNT4" -ne 1 ]; then
+ echo "The line after the last line with wb_group_members_send should be indented by 18 spaces."
+ return 1
+ fi
+
+ return 0
+}
+
+case ${TESTENV} in
+ad_member*)
+ ;;
+*)
+ echo "Test is for ad_member only, but called for ${TESTENV}." | subunit_fail_test "test winbind call depth trace"
+ exit 1;
+esac
+
+testit "test winbind call depth trace" test_winbind_call_depth_trace || failed=$((failed + 1))
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_winbind_ignore_domains.sh b/source3/script/tests/test_winbind_ignore_domains.sh
new file mode 100755
index 0000000..1454eca
--- /dev/null
+++ b/source3/script/tests/test_winbind_ignore_domains.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+
+incdir=$(dirname $0)/../../../testprogs/blackbox
+. $incdir/subunit.sh
+. $incdir/common_test_fns.inc
+
+failed=0
+
+smbclient="$BINDIR/smbclient"
+smbcontrol="$BINDIR/smbcontrol"
+ldbmodify="$BINDIR/ldbmodify"
+ldbsearch="$BINDIR/ldbsearch"
+wbinfo="$BINDIR/wbinfo"
+global_inject_conf=$(dirname $SMB_CONF_PATH)/global_inject.conf
+SERVER_FQDN=$(echo "$SERVER.$REALM" | awk '{print tolower($0)}')
+
+TRUST_BASE_DN=$($ldbsearch -H ldap://$TRUST_SERVER -b "" -s base defaultNamingContext | awk '/^defaultNamingContext/ {print $2}')
+if [ $? -ne 0 ]; then
+ echo "Could not find trusted base DN" | subunit_fail_test "test_idmap_ad"
+ exit 1
+fi
+
+#
+# Add POSIX ids to trusted domain
+#
+add_posix_ids()
+{
+ cat <<EOF | $ldbmodify -H ldap://$TRUST_SERVER \
+ -U "$TRUST_DOMAIN\Administrator%$TRUST_PASSWORD"
+dn: CN=Administrator,CN=Users,$TRUST_BASE_DN
+changetype: modify
+add: uidNumber
+uidNumber: 2500000
+EOF
+
+ cat <<EOF | $ldbmodify -H ldap://$TRUST_SERVER \
+ -U "$TRUST_DOMAIN\Administrator%$TRUST_PASSWORD"
+dn: CN=Domain Users,CN=Users,$TRUST_BASE_DN
+changetype: modify
+add: gidNumber
+gidNumber: 2500001
+EOF
+
+ cat <<EOF | $ldbmodify -H ldap://$TRUST_SERVER \
+ -U "$TRUST_DOMAIN\Administrator%$TRUST_PASSWORD"
+dn: CN=Domain Admins,CN=Users,$TRUST_BASE_DN
+changetype: modify
+add: gidNumber
+gidNumber: 2500002
+EOF
+}
+
+#
+# Remove POSIX ids from trusted domain
+#
+remove_posix_ids()
+{
+ cat <<EOF | $ldbmodify -H ldap://$TRUST_SERVER \
+ -U "$TRUST_DOMAIN\Administrator%$TRUST_PASSWORD"
+dn: CN=Administrator,CN=Users,$TRUST_BASE_DN
+changetype: modify
+delete: uidNumber
+uidNumber: 2500000
+EOF
+
+ cat <<EOF | $ldbmodify -H ldap://$TRUST_SERVER \
+ -U "$TRUST_DOMAIN\Administrator%$TRUST_PASSWORD"
+dn: CN=Domain Users,CN=Users,$TRUST_BASE_DN
+changetype: modify
+delete: gidNumber
+gidNumber: 2500001
+EOF
+
+ cat <<EOF | $ldbmodify -H ldap://$TRUST_SERVER \
+ -U "$TRUST_DOMAIN\Administrator%$TRUST_PASSWORD"
+dn: CN=Domain Admins,CN=Users,$TRUST_BASE_DN
+changetype: modify
+delete: gidNumber
+gidNumber: 2500002
+EOF
+}
+
+add_posix_ids
+
+echo "" >$global_inject_conf
+$smbcontrol winbindd reload-config
+$wbinfo -p
+
+test_smbclient "test_winbind_ignore_domains_ok_ntlm_ip" "ls" "//$SERVER_IP/tmp" -U $TRUST_DOMAIN/$TRUST_USERNAME%$TRUST_PASSWORD || failed=$(expr $failed + 1)
+test_smbclient "test_winbind_ignore_domains_ok_ntlm_fqdn" "ls" "//$SERVER_FQDN/tmp" -U $TRUST_DOMAIN/$TRUST_USERNAME%$TRUST_PASSWORD || failed=$(expr $failed + 1)
+test_smbclient "test_winbind_ignore_domains_ok_krb5" "ls" "//$SERVER_FQDN/tmp" -U $TRUST_USERNAME@$TRUST_REALM%$TRUST_PASSWORD -k || failed=$(expr $failed + 1)
+
+echo "winbind:ignore domains = $TRUST_DOMAIN" >$global_inject_conf
+$smbcontrol winbindd reload-config
+$wbinfo -p
+
+test_smbclient_expect_failure "test_winbind_ignore_domains_fail_ntlm_ip" "ls" "//$SERVER_IP/tmp" -U $TRUST_DOMAIN/$TRUST_USERNAME%$TRUST_PASSWORD || failed=$(expr $failed + 1)
+test_smbclient_expect_failure "test_winbind_ignore_domains_fail_ntlm_fqdn" "ls" "//$SERVER_FQDN/tmp" -U $TRUST_DOMAIN/$TRUST_USERNAME%$TRUST_PASSWORD || failed=$(expr $failed + 1)
+test_smbclient_expect_failure "test_winbind_ignore_domains_fail_krb5" "ls" "//$SERVER_FQDN/tmp" -U $TRUST_USERNAME@$TRUST_REALM%$TRUST_PASSWORD -k || failed=$(expr $failed + 1)
+
+echo "" >$global_inject_conf
+$smbcontrol winbindd reload-config
+$wbinfo -p
+remove_posix_ids
+
+testok $0 $failed
diff --git a/source3/script/tests/test_worm.sh b/source3/script/tests/test_worm.sh
new file mode 100755
index 0000000..f96c8ec
--- /dev/null
+++ b/source3/script/tests/test_worm.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+
+if [ $# -lt 7 ]; then
+ cat <<EOF
+Usage: test_worm.sh SERVER SERVER_IP USERNAME PASSWORD LOCAL_PATH PREFIX SMBCLIENT ADDARGS
+EOF
+ exit 1
+fi
+
+SERVER="${1}"
+SERVER_IP="${2}"
+USERNAME="${3}"
+PASSWORD="${4}"
+LOCAL_PATH="${5}"
+PREFIX="${6}"
+SMBCLIENT="${7}"
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+shift 7
+ADDARGS="$*"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+# Do not let deprecated option warnings muck this up
+SAMBA_DEPRECATED_SUPPRESS=1
+export SAMBA_DEPRECATED_SUPPRESS
+
+# Define the test environment/filenames.
+#
+share_test_dir="$LOCAL_PATH"
+
+#
+# Cleanup function.
+#
+do_cleanup()
+{
+ (
+ #subshell.
+ cd "$share_test_dir" || return
+ rm -f must-be-deleted must-not-be-deleted must-be-deleted-after-ctime-refresh
+ )
+ rm -f $tmpfile
+}
+
+#
+# Ensure we start from a clean slate.
+#
+do_cleanup
+
+tmpfile=$PREFIX/smbclient_interactive_prompt_commands
+
+test_worm()
+{
+ # use echo because helo scripts don't support variables
+ echo "
+put $tmpfile must-be-deleted
+put $tmpfile must-be-deleted-after-ctime-refresh
+put $tmpfile must-not-be-deleted
+del must-be-deleted
+quit" > $tmpfile
+ # make sure the directory is not too old for worm:
+ touch $share_test_dir
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/worm -I$SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+ rm -f "$tmpfile"
+
+ if [ $ret != 0 ]; then
+ printf "%s\n" "$out"
+ printf "failed worm smbclient run with error %s\n" "$ret"
+ return 1
+ fi
+ test -e $share_test_dir/must-be-deleted && {
+ printf "$0: ERROR: must-be-deleted was NOT deleted\n"
+ return 1
+ }
+
+ # now sleep grace_period (1s) and check if worm works properly:
+ sleep 1
+ echo "
+posix
+chmod 700 must-not-be-deleted
+del must-not-be-deleted
+del must-be-deleted-after-ctime-refresh
+quit" > $tmpfile
+ # make sure the directory itself is not too old for worm:
+ touch $share_test_dir
+ # set a fresh ctime by doing a chmod:
+ chmod 644 $share_test_dir/must-be-deleted-after-ctime-refresh
+ cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT -U$USERNAME%$PASSWORD //$SERVER/worm -I$SERVER_IP $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ test -e $share_test_dir/must-not-be-deleted || {
+ printf "$0: ERROR: must-not-be-deleted WAS deleted\n"
+ return 1
+ }
+ # if we're not root, return here:
+ test "$UID" = "0" || {
+ return 0
+ }
+
+ test -e $share_test_dir/must-be-deleted-after-ctime-refresh && {
+ printf "$0: ERROR: must-be-deleted-after-ctime-refresh was NOT deleted\n"
+ return 1
+ }
+ return 0
+}
+
+
+testit "worm" \
+ test_worm ||
+ failed=$((failed + 1))
+
+#
+# Cleanup.
+do_cleanup
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/test_zero_data.sh b/source3/script/tests/test_zero_data.sh
new file mode 100755
index 0000000..62ba856
--- /dev/null
+++ b/source3/script/tests/test_zero_data.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Verify that smbtorture tests for manually testing ZERO_DATA
+#
+# Copyright (C) 2019 Christof Schmitt
+
+if [ $# -lt 4 ]; then
+ cat <<EOF
+Usage: test_zero_data.sh SERVER_IP USERNAME PASSWORD LOCAL_PATH
+EOF
+ exit 1
+fi
+
+SERVER=${1}
+USERNAME=${2}
+PASSWORD=${3}
+LOCAL_PATH=${4}
+
+. $(dirname $0)/../../../testprogs/blackbox/subunit.sh
+failed=0
+
+cd $SELFTEST_TMPDIR || exit 1
+
+TESTDIR=$LOCAL_PATH/zero_data
+
+mkdir -p $TESTDIR
+chmod 777 p $TESTDIR
+
+dd if=/dev/urandom of=$TESTDIR/testfile bs=1024 count=128
+chmod 777 $TESTDIR/testfile
+
+alloc_kb=$(du -k $TESTDIR/testfile | sed -e 's/\t.*//')
+testit "check allocation before zero-data" test $alloc_kb -eq 128 ||
+ failed=$(expr $failed + 1)
+
+testit "set-sparse" $VALGRIND $BINDIR/smbtorture //$SERVER_IP/tmp \
+ -U$USERNAME%$PASSWORD smb2.set-sparse-ioctl \
+ --option=torture:filename=zero_data/testfile ||
+ failed=$(expr $failed + 1)
+
+testit "zero-data" $VALGRIND $BINDIR/smbtorture //$SERVER_IP/tmp \
+ -U$USERNAME%$PASSWORD smb2.zero-data-ioctl \
+ --option=torture:filename=zero_data/testfile \
+ --option=torture:offset=0 \
+ --option=torture:beyond_final_zero=131072 ||
+ failed=$(expr $failed + 1)
+
+alloc_kb=$(du -k $TESTDIR/testfile | sed -e 's/\t.*//')
+testit "check allocation after zero-data" test $alloc_kb -eq 0 ||
+ failed=$(expr $failed + 1)
+
+rm -rf $TESTDIR
+
+testok $0 $failed
diff --git a/source3/script/tests/test_zero_readsize.sh b/source3/script/tests/test_zero_readsize.sh
new file mode 100755
index 0000000..f859599
--- /dev/null
+++ b/source3/script/tests/test_zero_readsize.sh
@@ -0,0 +1,101 @@
+#!/usr/bin/env bash
+#
+# Test setting smb2 max read = 0.
+#
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15306
+#
+
+if [ $# -lt 6 ]; then
+ cat <<EOF
+Usage: $0 SERVERCONFFILE SMBCLIENT SMBCONTROL SERVER SHARE PREFIX
+EOF
+ exit 1
+fi
+
+CONF=${1}
+shift 1
+SMBCLIENT=${1}
+shift 1
+SMBCONTROL=${1}
+shift 1
+SERVER=${1}
+shift 1
+SHARE=${1}
+shift 1
+PREFIX=${1}
+shift 1
+
+SMBCLIENT="$VALGRIND ${SMBCLIENT}"
+ADDARGS="$@"
+
+incdir=$(dirname "$0")/../../../testprogs/blackbox
+. "$incdir"/subunit.sh
+
+failed=0
+
+#
+# Setup function
+#
+do_setup()
+{
+ rm -f "${PREFIX}/zero_read_testfile"
+ rm -f "${PREFIX}/zero_read_testfile_get"
+ dd if=/dev/zero of="${PREFIX}/zero_read_testfile" bs=1024 count=1
+ global_inject_conf="$(dirname "${SERVERCONFFILE}")/global_inject.conf"
+ echo "smb2 max read = 0" >"$global_inject_conf"
+ ${SMBCONTROL} ${CONF} smbd reload-config
+}
+
+do_cleanup()
+{
+ rm -f "${PREFIX}/zero_read_testfile"
+ rm -f "${PREFIX}/zero_read_testfile_get"
+ global_inject_conf="$(dirname "${SERVERCONFFILE}")/global_inject.conf"
+ rm "$global_inject_conf"
+ ${SMBCONTROL} ${CONF} smbd reload-config
+}
+
+test_smb2_zero_readsize()
+{
+ local tmpfile="$PREFIX/smbclient.in.$$"
+
+ cat >"$tmpfile" <<EOF
+lcd $PREFIX
+put zero_read_testfile zero_read_testfile_put
+get zero_read_testfile_put zero_read_testfile_get
+del zero_read_testfile_put
+quit
+EOF
+
+ local cmd='CLI_FORCE_INTERACTIVE=yes $SMBCLIENT //$SERVER/$SHARE -U$USERNAME%$PASSWORD $ADDARGS < $tmpfile 2>&1'
+ eval echo "$cmd"
+ out=$(eval "$cmd")
+ ret=$?
+
+ # Check for smbclient error.
+ # We should have failed the protocol negotiation, returning 1.
+ if [ $ret != 1 ]; then
+ echo "smbclient protocol negotiation succeeded (should have failed) zero read testfile $ret"
+ echo "$out"
+ return 1
+ fi
+
+ # We should get NT_STATUS_INVALID_NETWORK_RESPONSE
+ echo "$out" | grep NT_STATUS_INVALID_NETWORK_RESPONSE
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "Should get NT_STATUS_INVALID_NETWORK_RESPONSE"
+ echo "$out"
+ return 1
+ fi
+ rm "$tmpfile"
+ return 0
+}
+
+do_setup
+
+testit "smb2_zero_readsize" test_smb2_zero_readsize || failed=$((failed + 1))
+
+do_cleanup
+
+testok "$0" "$failed"
diff --git a/source3/script/tests/timelimit.c b/source3/script/tests/timelimit.c
new file mode 100644
index 0000000..886256c
--- /dev/null
+++ b/source3/script/tests/timelimit.c
@@ -0,0 +1,102 @@
+/* run a command with a limited timeout
+ tridge@samba.org, June 2005
+ metze@samba.org, March 2006
+
+ attempt to be as portable as possible (fighting posix all the way)
+*/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+static pid_t child_pid;
+
+static void usage(void)
+{
+ printf("usage: timelimit <time> <command>\n");
+ printf(" SIGUSR1 - passes SIGTERM to command's process group\n");
+ printf(" SIGALRM - passes SIGTERM to command's process group\n");
+ printf(" after 5s SIGKILL will be passed and exit(1)\n");
+ printf(" SIGTERM - passes SIGTERM to command's process group\n");
+ printf(" after 1s SIGKILL will be passed and exit(1)\n");
+}
+
+static void sig_alrm_kill(int sig)
+{
+ fprintf(stderr, "\nMaximum time expired in timelimit - killing\n");
+ kill(-child_pid, SIGKILL);
+ exit(1);
+}
+
+static void sig_alrm_term(int sig)
+{
+ kill(-child_pid, SIGTERM);
+ alarm(5);
+ signal(SIGALRM, sig_alrm_kill);
+}
+
+static void sig_term(int sig)
+{
+ kill(-child_pid, SIGTERM);
+ alarm(1);
+ signal(SIGALRM, sig_alrm_kill);
+}
+
+static void sig_usr1(int sig)
+{
+ kill(-child_pid, SIGTERM);
+}
+
+static void new_process_group(void)
+{
+ if (setpgid(0,0) == -1) {
+ perror("setpgid");
+ exit(1);
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ int maxtime, ret=1;
+
+ if (argc < 3) {
+ usage();
+ exit(1);
+ }
+
+ maxtime = atoi(argv[1]);
+
+ child_pid = fork();
+ if (child_pid == 0) {
+ new_process_group();
+ execvp(argv[2], argv+2);
+ perror(argv[2]);
+ exit(1);
+ }
+
+ signal(SIGTERM, sig_term);
+ signal(SIGINT, sig_term);
+ signal(SIGQUIT, sig_term);
+ signal(SIGUSR1, sig_usr1);
+ signal(SIGALRM, sig_alrm_term);
+ alarm(maxtime);
+
+ do {
+ int status;
+ pid_t pid = wait(&status);
+ if (pid != -1) {
+ ret = WEXITSTATUS(status);
+ } else if (errno == ECHILD) {
+ break;
+ }
+ } while (1);
+
+ kill(-child_pid, SIGKILL);
+
+ exit(ret);
+}
diff --git a/source3/script/tests/vfstest-acl/run.sh b/source3/script/tests/vfstest-acl/run.sh
new file mode 100755
index 0000000..eb32a95
--- /dev/null
+++ b/source3/script/tests/vfstest-acl/run.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: run.sh VFSTEST PREFIX
+EOF
+ exit 1
+fi
+
+TESTBASE=$(dirname $0)
+VFSTEST=$1
+PREFIX=$2
+shift 2
+ADDARGS="$*"
+
+VFSTEST_PREFIX=vfstest
+VFSTEST_TMPDIR=$(mktemp -d ${PREFIX}/${VFSTEST_PREFIX}_XXXXXX)
+
+incdir=$(dirname $0)/../../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $VFSTEST_TMPDIR || exit 1
+
+test_vfstest()
+{
+ cmd='$VFSTEST -f $TESTBASE/vfstest.cmd $ADDARGS '
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ false
+ return
+ fi
+
+ echo "$out" | grep "NT_STATUS_ACCESS_DENIED" >/dev/null 2>&1
+
+ if [ $? = 0 ]; then
+ # got ACCESS_DENIED .. fail
+ echo vfstest got NT_STATUS_ACCESS_DENIED
+ false
+ else
+ true
+ fi
+}
+
+testit "vfstest" test_vfstest || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/vfstest-acl/vfstest.cmd b/source3/script/tests/vfstest-acl/vfstest.cmd
new file mode 100644
index 0000000..6168671
--- /dev/null
+++ b/source3/script/tests/vfstest-acl/vfstest.cmd
@@ -0,0 +1,15 @@
+connect
+open x RC 0700
+sys_acl_get_file . 0
+sys_acl_get_file . 1
+get_nt_acl .
+set_nt_acl . G:DAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)
+get_nt_acl .
+sys_acl_get_file . 0
+sys_acl_get_file . 1
+get_nt_acl x
+sys_acl_get_file x 0
+set_nt_acl x G:DAD:P(A;OICI;0x001f01ff;;;BA)(A;OICI;0x001200a9;;;SO)(A;OICI;0x001f01ff;;;SY)(A;OICI;0x001200a9;;;AU)
+get_nt_acl x
+sys_acl_get_file x 0
+
diff --git a/source3/script/tests/vfstest-catia/run.sh b/source3/script/tests/vfstest-catia/run.sh
new file mode 100755
index 0000000..aca2a60
--- /dev/null
+++ b/source3/script/tests/vfstest-catia/run.sh
@@ -0,0 +1,102 @@
+#!/bin/sh
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: run.sh VFSTEST PREFIX
+EOF
+ exit 1
+fi
+
+TESTBASE=$(dirname $0)
+VFSTEST=$1
+PREFIX=$2
+shift 2
+ADDARGS="$*"
+
+VFSTEST_PREFIX=vfstest
+VFSTEST_TMPDIR=$(mktemp -d ${PREFIX}/${VFSTEST_PREFIX}_XXXXXX)
+
+# We could pass in the --option=... via tests.py as ADDARGS
+# Atm i've chosen to specify them here:
+
+MYARGS1="--option=vfsobjects=catia"
+MYARGS2="--option=catia:mappings=0x22:0xa8,0x2a:0xa4,0x2f:0xf8,0x3a:0xf7,0x3c:0xab,0x3e:0xbb,0x3f:0xbf,0x5c:0xff,0x7c:0xa6,0x20:0xb1"
+
+# vars for the translation test:
+# a) here for unix-to-windows test
+UNIX_FILE="a\\a:a*a?a<a>a|a"
+# translated window file name
+WIN_FILE="aÿa÷a¤a¿a«a»a¦a"
+
+# b) here for windows-to-unix test
+WIN_DIR="dir_aÿa÷a¤a¿a«a»a¦a"
+# translated unix directory name
+UNIX_DIR="dir_a\a:a*a?a<a>a|a"
+
+incdir=$(dirname $0)/../../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $VFSTEST_TMPDIR || exit 1
+
+# create unix file in tmpdir
+touch $UNIX_FILE || exit 1
+
+# test "translate" unix-to-windows
+test_vfstest()
+{
+ cmd='$VFSTEST --debug-stdout -f $TESTBASE/vfstest.cmd $MYARGS1 $MYARGS2 $ADDARGS '
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ false
+ return
+ fi
+
+ echo "$out" | grep $WIN_FILE >/dev/null 2>&1
+
+ if [ $? = 0 ]; then
+ echo "ALL IS WORKING"
+ true
+ else
+ false
+ fi
+}
+
+# test the mkdir call with special windows chars
+# and then check the created unix directory name
+test_vfstest_dir()
+{
+ cmd='$VFSTEST -f $TESTBASE/vfstest1.cmd $MYARGS1 $MYARGS2 $ADDARGS '
+ out=$(eval $cmd)
+ ret=$?
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ false
+ return
+ fi
+
+ NUM=$(find $UNIX_DIR | wc -l)
+ if [ $NUM -ne 1 ]; then
+ echo "Cannot find $UNIX_DIR"
+ false
+ else
+ true
+ fi
+}
+
+testit "vfstest_catia" test_vfstest || failed=$(expr $failed + 1)
+
+if [ $failed = 0 ]; then
+ testit "vfstest1_catia" test_vfstest_dir || failed=$(expr $failed + 1)
+fi
+
+# Cleanup: remove tempdir
+rm -R $VFSTEST_TMPDIR
+
+exit $failed
diff --git a/source3/script/tests/vfstest-catia/vfstest.cmd b/source3/script/tests/vfstest-catia/vfstest.cmd
new file mode 100644
index 0000000..5acecbc
--- /dev/null
+++ b/source3/script/tests/vfstest-catia/vfstest.cmd
@@ -0,0 +1,2 @@
+connect
+translate_name a\a:a*a?a<a>a|a
diff --git a/source3/script/tests/vfstest-catia/vfstest1.cmd b/source3/script/tests/vfstest-catia/vfstest1.cmd
new file mode 100644
index 0000000..ef56159
--- /dev/null
+++ b/source3/script/tests/vfstest-catia/vfstest1.cmd
@@ -0,0 +1,2 @@
+connect
+mkdir dir_aÿa÷a¤a¿a«a»a¦a
diff --git a/source3/script/tests/wb_pad.sh b/source3/script/tests/wb_pad.sh
new file mode 100755
index 0000000..f4dded6
--- /dev/null
+++ b/source3/script/tests/wb_pad.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+#
+# Copyright (C) Guenther Deschner 2008
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses/>.
+
+tempdir=$(mktemp -d /tmp/wb_padXXXXXX)
+test -n "$tempdir" || exit 1
+cat >>$tempdir/wb_pad.c <<_EOF
+#include "nsswitch/winbind_client.h"
+
+int main(int argc, const char **argv)
+{
+ struct winbindd_request req;
+ struct winbindd_response resp;
+
+ if (argc != 2) {
+ printf("usage: %s [req|resp]\n", argv[0]);
+ return 0;
+ }
+
+ if (strcmp(argv[1], "req") == 0) {
+ printf("%d\n", (uint32_t)sizeof(req));
+ }
+ if (strcmp(argv[1], "resp") == 0) {
+ printf("%d\n", (uint32_t)sizeof(resp));
+ }
+
+ return 0;
+}
+_EOF
+
+cleanup()
+{
+ rm -f $tempdir/wb_pad_32 $tempdir/wb_pad_64 $tempdir/wb_pad.c
+ rmdir $tempdir
+}
+
+cflags="-I. -I../ -I./../lib/replace -Iinclude"
+${CC:-gcc} -m32 $RPM_OPT_FLAGS $CFLAGS -o $tempdir/wb_pad_32 $cflags $tempdir/wb_pad.c
+if [ $? -ne 0 ]; then
+ cleanup
+ exit 1
+fi
+${CC:-gcc} -m64 $RPM_OPT_FLAGS $CFLAGS -o $tempdir/wb_pad_64 $cflags $tempdir/wb_pad.c
+if [ $? -ne 0 ]; then
+ cleanup
+ exit 1
+fi
+
+out_64_req=$($tempdir/wb_pad_64 req)
+out_64_resp=$($tempdir/wb_pad_64 resp)
+out_32_req=$($tempdir/wb_pad_32 req)
+out_32_resp=$($tempdir/wb_pad_32 resp)
+
+cleanup
+
+if test "$out_64_req" != "$out_32_req"; then
+ echo "winbind request size differs!"
+ echo "64bit: $out_64_req"
+ echo "32bit: $out_32_req"
+ exit 1
+fi
+
+if test "$out_64_resp" != "$out_32_resp"; then
+ echo "winbind response size differs!"
+ echo "64bit: $out_64_resp"
+ echo "32bit: $out_32_resp"
+ exit 1
+fi
+
+exit 0
diff --git a/source3/script/tests/xattr-tdb-1/run.sh b/source3/script/tests/xattr-tdb-1/run.sh
new file mode 100755
index 0000000..eb32a95
--- /dev/null
+++ b/source3/script/tests/xattr-tdb-1/run.sh
@@ -0,0 +1,52 @@
+#!/bin/sh
+if [ $# -lt 2 ]; then
+ cat <<EOF
+Usage: run.sh VFSTEST PREFIX
+EOF
+ exit 1
+fi
+
+TESTBASE=$(dirname $0)
+VFSTEST=$1
+PREFIX=$2
+shift 2
+ADDARGS="$*"
+
+VFSTEST_PREFIX=vfstest
+VFSTEST_TMPDIR=$(mktemp -d ${PREFIX}/${VFSTEST_PREFIX}_XXXXXX)
+
+incdir=$(dirname $0)/../../../../testprogs/blackbox
+. $incdir/subunit.sh
+
+failed=0
+
+cd $VFSTEST_TMPDIR || exit 1
+
+test_vfstest()
+{
+ cmd='$VFSTEST -f $TESTBASE/vfstest.cmd $ADDARGS '
+ out=$(eval $cmd)
+ ret=$?
+ rm -f $tmpfile
+
+ if [ $ret != 0 ]; then
+ echo "$out"
+ echo "command failed"
+ false
+ return
+ fi
+
+ echo "$out" | grep "NT_STATUS_ACCESS_DENIED" >/dev/null 2>&1
+
+ if [ $? = 0 ]; then
+ # got ACCESS_DENIED .. fail
+ echo vfstest got NT_STATUS_ACCESS_DENIED
+ false
+ else
+ true
+ fi
+}
+
+testit "vfstest" test_vfstest || failed=$(expr $failed + 1)
+
+exit $failed
diff --git a/source3/script/tests/xattr-tdb-1/vfstest.cmd b/source3/script/tests/xattr-tdb-1/vfstest.cmd
new file mode 100644
index 0000000..5eb7158
--- /dev/null
+++ b/source3/script/tests/xattr-tdb-1/vfstest.cmd
@@ -0,0 +1,6 @@
+connect
+open x RC 0700
+setxattr x y z
+listxattr x
+stat x
+unlink x
diff --git a/source3/script/updatesmbpasswd.sh b/source3/script/updatesmbpasswd.sh
new file mode 100755
index 0000000..1d7e0d7
--- /dev/null
+++ b/source3/script/updatesmbpasswd.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+nawk 'BEGIN {FS=":"}
+{
+ if( $0 ~ "^#" ) {
+ print $0
+ } else if( (length($4) == 32) && (($4 ~ "^[0-9A-F]*$") || ($4 ~ "^[X]*$") || ( $4 ~ "^[*]*$"))) {
+ print $0
+ } else {
+ printf( "%s:%s:%s:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:", $1, $2, $3);
+ for(i = 4; i <= NF; i++)
+ printf("%s:", $i)
+ printf("\n")
+ }
+}'
diff --git a/source3/script/wscript_build b/source3/script/wscript_build
new file mode 100644
index 0000000..66acf1c
--- /dev/null
+++ b/source3/script/wscript_build
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+from samba_utils import MODE_755
+
+bld.INSTALL_FILES('${BINDIR}',
+ 'smbtar',
+ chmod=MODE_755, flat=True)
+bld.INSTALL_FILES('${BINDIR}', 'samba-log-parser', chmod=MODE_755, flat=True)
+
+# Callout scripts for use in selftest environment
+bld.SAMBA_SCRIPT('smbaddshare', pattern='smbaddshare', installdir='.')
+bld.SAMBA_SCRIPT('smbchangeshare', pattern='smbchangeshare', installdir='.')
+bld.SAMBA_SCRIPT('smbdeleteshare', pattern='smbdeleteshare', installdir='.')
diff --git a/source3/selftest/ktest-krb5_ccache-2 b/source3/selftest/ktest-krb5_ccache-2
new file mode 100644
index 0000000..1510222
--- /dev/null
+++ b/source3/selftest/ktest-krb5_ccache-2
Binary files differ
diff --git a/source3/selftest/ktest-krb5_ccache-2.txt b/source3/selftest/ktest-krb5_ccache-2.txt
new file mode 100644
index 0000000..4b89959
--- /dev/null
+++ b/source3/selftest/ktest-krb5_ccache-2.txt
@@ -0,0 +1,1574 @@
+pull returned Success
+ CCACHE: struct CCACHE
+ pvno : 0x05 (5)
+ version : 0x04 (4)
+ optional_header : union OPTIONAL_HEADER(case 0x4)
+ v4header: struct V4HEADER
+ v4tags: struct V4TAGS
+ tag: struct V4TAG
+ tag : 0x0001 (1)
+ field : union FIELD(case 0x1)
+ deltatime_tag: struct DELTATIME_TAG
+ kdc_sec_offset : 0
+ kdc_usec_offset : 0
+ further_tags : DATA_BLOB length=0
+ principal: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ cred: struct CREDENTIAL
+ client: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ server: struct PRINCIPAL
+ name_type : 0x00000000 (0)
+ component_count : 0x00000002 (2)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(2)
+ components : 'krbtgt'
+ components : 'KTEST.SAMBA.EXAMPLE.COM'
+ keyblock: struct KEYBLOCK
+ enctype : 0x0017 (23)
+ data : DATA_BLOB length=16
+[0000] 8B 94 0B 31 51 5B F7 A7 15 E9 EE D7 D7 0C 8C 90 ...1Q[.. ........
+ authtime : 0x4d994f6a (1301892970)
+ starttime : 0x4d994f6a (1301892970)
+ endtime : 0x7d440b68 (2101611368)
+ renew_till : 0x7d440b68 (2101611368)
+ is_skey : 0x00 (0)
+ ticket_flags : 0x40e00000 (1088421888)
+ addresses: struct ADDRESSES
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ authdata: struct AUTHDATA
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ ticket : DATA_BLOB length=1032
+[0000] 61 82 04 04 30 82 04 00 A0 03 02 01 05 A1 19 1B a...0... ........
+[0010] 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 .KTEST.S AMBA.EXA
+[0020] 4D 50 4C 45 2E 43 4F 4D A2 2C 30 2A A0 03 02 01 MPLE.COM .,0*....
+[0030] 00 A1 23 30 21 1B 06 6B 72 62 74 67 74 1B 17 4B ..#0!..k rbtgt..K
+[0040] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0050] 4C 45 2E 43 4F 4D A3 82 03 AE 30 82 03 AA A0 03 LE.COM.. ..0.....
+[0060] 02 01 17 A1 03 02 01 01 A2 82 03 9C 04 82 03 98 ........ ........
+[0070] 80 66 8F CF AB 24 9D C8 76 E4 28 F5 25 6B 73 B2 .f...$.. v.(.%ks.
+[0080] 4B 94 ED 09 10 29 05 C4 C0 B8 B9 33 FA C4 46 AB K....).. ...3..F.
+[0090] F4 B5 9E 5B 07 54 D6 58 1D B8 CA 04 41 A6 33 A6 ...[.T.X ....A.3.
+[00A0] 67 9D EB 83 70 65 A9 2D 65 A5 19 8C 55 2A 0F FC g...pe.- e...U*..
+[00B0] 1B BB 7A BD 86 C0 32 06 F2 2F 0A A5 93 E7 D1 1E ..z...2. ./......
+[00C0] 16 C4 27 DD 1F A7 61 03 FF 05 81 EF 49 B7 25 A3 ..'...a. ....I.%.
+[00D0] 6E EA E6 E8 15 E3 10 AF A3 F1 21 B3 D9 C0 67 2F n....... ..!...g/
+[00E0] 0C 0C B7 42 D6 9A 34 8E D4 5E 55 C2 FE 62 03 37 ...B..4. .^U..b.7
+[00F0] A5 58 9B 43 E7 26 E3 71 B2 E5 F1 91 B4 23 8F AC .X.C.&.q .....#..
+[0100] 7A 31 3C 4E B4 94 E4 81 36 98 71 3B 98 7B B7 AB z1<N.... 6.q;.{..
+[0110] D5 AA D3 34 2A 3B C8 D7 61 EE 60 F9 68 9C A0 56 ...4*;.. a.`.h..V
+[0120] 51 E7 85 81 DE EF B9 9F 8B 4A 07 E1 05 93 08 5A Q....... .J.....Z
+[0130] AE B3 92 A5 17 40 B1 1C 42 A9 E4 AD 3C B4 4E D3 .....@.. B...<.N.
+[0140] BE 68 C4 0C 81 C0 AB 2D 3E 81 09 BD 16 82 EB C5 .h.....- >.......
+[0150] 1A 69 EE 8C 4E A4 D8 55 A5 0B 23 0F D0 89 48 C4 .i..N..U ..#...H.
+[0160] 51 FE 32 FD CC F6 71 E1 95 2D CC 1D 0A 0C 8A A2 Q.2...q. .-......
+[0170] 69 58 3B 65 88 53 EC D0 2E E1 C6 CC 6B BC 09 E5 iX;e.S.. ....k...
+[0180] B9 15 27 8B E4 B2 24 18 61 42 BB 8B 09 1B 8A 7B ..'...$. aB.....{
+[0190] 13 D8 51 E1 0B 79 12 48 DE A9 54 04 00 6D DD E6 ..Q..y.H ..T..m..
+[01A0] 5E 03 91 FF C7 6D 0B 7C 91 44 E1 0F C0 7E 32 34 ^....m.| .D...~24
+[01B0] 82 86 94 F7 CD 53 EC 52 38 18 AA ED FF FC 5C 01 .....S.R 8.....\.
+[01C0] D2 EE 99 45 8E 5B E6 B3 46 B0 F6 3B 22 29 EC 11 ...E.[.. F..;")..
+[01D0] 30 6A F6 A1 1F 9E AE 71 E3 A6 E7 3F F3 7D 2B 75 0j.....q ...?.}+u
+[01E0] 70 4D 63 47 5C 18 2C 8B B1 1A 69 B6 C5 46 01 17 pMcG\.,. ..i..F..
+[01F0] 8E 64 3D 47 88 20 1C AA D7 60 32 28 11 60 EA 28 .d=G. .. .`2(.`.(
+[0200] 66 99 4C B1 2A 28 96 BF 18 2A 3E F4 D6 84 E5 A0 f.L.*(.. .*>.....
+[0210] F4 4E E7 F9 54 95 22 96 2A 87 01 CC 3E A7 FF 42 .N..T.". *...>..B
+[0220] 6A A4 4A 3A B9 24 10 65 99 53 58 2A 4E 72 E7 1F j.J:.$.e .SX*Nr..
+[0230] 82 BC BD 3C 6C 9D 33 3A CE C6 6E 72 A2 81 B3 84 ...<l.3: ..nr....
+[0240] 82 DF 3C 1F 76 E5 B8 08 AD 0A 6C 7D 7B D5 0C 46 ..<.v... ..l}{..F
+[0250] 69 A4 F4 E9 9E 3D D7 2D E1 43 D1 7A 52 16 75 56 i....=.- .C.zR.uV
+[0260] 54 83 D5 2A 2F A7 D2 CB 48 FE FF DB AE 46 F2 5B T..*/... H....F.[
+[0270] F4 52 BE C8 5E B1 04 95 52 35 3E 92 E0 02 F7 85 .R..^... R5>.....
+[0280] AB F0 D0 93 08 42 E5 37 19 24 4E C1 AF FC 92 A9 .....B.7 .$N.....
+[0290] B1 27 B1 9A 2A 62 34 F1 DC C0 6B 83 AE C3 74 E8 .'..*b4. ..k...t.
+[02A0] A3 05 DD 82 DD A3 D7 90 A8 E3 9C EB 64 16 23 06 ........ ....d.#.
+[02B0] 5D FB E4 35 7C 22 29 78 E3 3B 75 92 91 0C 9D A1 ]..5|")x .;u.....
+[02C0] 87 7C 2E 82 AE 49 9D 4A 50 A9 C2 D5 85 B0 16 5D .|...I.J P......]
+[02D0] A2 CD B0 DD 29 3F 6F 66 C9 C1 9F 5C F0 B6 FC D2 ....)?of ...\....
+[02E0] 52 BE 7B F0 1F 26 AF 8A FC C3 A6 24 8C C0 10 06 R.{..&.. ...$....
+[02F0] 73 1E 17 9E 6E 6F 32 44 6A DF 82 5D D0 6B 74 CE s...no2D j..].kt.
+[0300] 58 0B 4C 7B EB A1 13 44 B1 3E D8 F8 BA F4 4E 55 X.L{...D .>....NU
+[0310] 71 3D C1 09 D9 E7 97 9A 14 5C 54 7E 57 81 5F 6B q=...... .\T~W._k
+[0320] 30 BE 9A E1 98 29 47 D4 C0 8F 63 0A F8 27 1F CE 0....)G. ..c..'..
+[0330] ED D9 BB 7B 12 24 D0 34 2A 7C F0 F7 77 F4 F1 1D ...{.$.4 *|..w...
+[0340] 4C 5D 75 2D 6B 0D 80 35 82 CC D8 7A 6B FA A0 55 L]u-k..5 ...zk..U
+[0350] 34 CD 87 15 61 38 78 D4 69 0F AA 72 D6 AC FA 99 4...a8x. i..r....
+[0360] BC 70 39 27 A7 25 2E 1B 6F 36 01 FD E9 B4 9A 79 .p9'.%.. o6.....y
+[0370] 6C 19 DD A6 8C 78 B0 40 92 60 58 F0 28 AD 08 78 l....x.@ .`X.(..x
+[0380] 4A 29 06 2C 82 2B 1A E3 91 0B 5F EE D6 B8 66 47 J).,.+.. .._...fG
+[0390] 31 9B A3 DF 9F 79 D7 BB 0E 2C FA 0E C9 66 84 8D 1....y.. .,...f..
+[03A0] FF BA BB 21 27 9E AD 86 84 55 8D 4C 4C 47 D9 5F ...!'... .U.LLG._
+[03B0] B2 7D 26 CA B7 49 3C 9D 1B 67 71 11 3A 8A EB EA .}&..I<. .gq.:...
+[03C0] 0F 15 EB F0 1E 46 F7 A4 34 04 D7 E3 50 67 47 D3 .....F.. 4...PgG.
+[03D0] 66 21 17 77 51 A7 1F 1D 84 3B 7C B1 5D 4E B8 D4 f!.wQ... .;|.]N..
+[03E0] F9 C5 75 06 AA 19 45 1C E9 06 9E AD 23 26 6B 10 ..u...E. ....#&k.
+[03F0] 53 A0 36 D3 58 9F 5E 8C CB A5 F6 BC C9 30 3C BC S.6.X.^. .....0<.
+[0400] AD FF 7C 92 F0 C6 9A 02 ..|.....
+ second_ticket : DATA_BLOB length=0
+ further_creds : DATA_BLOB length=10683
+[0000] 00 00 00 01 00 00 00 01 00 00 00 17 4B 54 45 53 ........ ....KTES
+[0010] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0020] 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 74 72 COM....a dministr
+[0030] 61 74 6F 72 00 00 00 01 00 00 00 02 00 00 00 17 ator.... ........
+[0040] 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D KTEST.SA MBA.EXAM
+[0050] 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 66 73 00 PLE.COM. ...cifs.
+[0060] 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 00 17 ...local ktest6..
+[0070] 00 00 00 10 00 6E A1 B2 31 6D 48 C7 90 72 3A 0C .....n.. 1mH..r:.
+[0080] 4B 8B 83 8C 4D 99 4F 6A 4D 99 50 85 7D 44 0B 68 K...M.Oj M.P.}D.h
+[0090] 00 00 00 00 00 40 28 00 00 00 00 00 00 00 00 00 .....@(. ........
+[00A0] 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 03 02 .....a.. .0......
+[00B0] 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[00C0] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 A.EXAMPL E.COM..0
+[00D0] 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 69 66 73 ........ 0...cifs
+[00E0] 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 A3 82 03 ..localk test6...
+[00F0] AE 30 82 03 AA A0 03 02 01 17 A1 03 02 01 02 A2 .0...... ........
+[0100] 82 03 9C 04 82 03 98 C6 BB 64 A8 31 00 FC 5E 51 ........ .d.1..^Q
+[0110] 3C 87 F8 34 47 3B D0 6F 6F FD 9E A6 91 12 74 2D <..4G;.o o.....t-
+[0120] 44 BB AA 91 A0 2D 46 3E 9E FB FB C4 FB F1 15 FD D....-F> ........
+[0130] BB DA EE 06 A9 20 6A 38 DC 46 06 27 D9 A2 9D 2D ..... j8 .F.'...-
+[0140] 1F FD 0D 7D 8A BB 0A 7C E8 47 17 BC 7B 70 E4 51 ...}...| .G..{p.Q
+[0150] 6A BA 51 68 62 28 4A 1E 51 D1 0D CD 02 55 75 44 j.Qhb(J. Q....UuD
+[0160] 8A B9 C2 84 F4 17 34 92 9B 31 85 9E 43 C1 0C 3A ......4. .1..C..:
+[0170] B2 69 7F 20 1A 18 1F 65 4F C0 20 C9 B5 AF E1 61 .i. ...e O. ....a
+[0180] 8C 90 10 63 26 A6 5D 05 3C CD 29 BB 7B 74 D5 8F ...c&.]. <.).{t..
+[0190] 2C 7F 4B E8 84 24 57 37 8A C6 F7 91 FD 22 9A A5 ,.K..$W7 ....."..
+[01A0] 0D E9 4A 78 93 36 FC A8 8C 8A 27 8A C6 28 4B 7B ..Jx.6.. ..'..(K{
+[01B0] DA 11 42 BC 09 10 81 82 14 0F 9C B8 48 26 91 78 ..B..... ....H&.x
+[01C0] A8 DD 97 6C 24 A1 D2 E8 85 19 B3 D3 85 4D 38 C7 ...l$... .....M8.
+[01D0] 7D 49 55 8E 85 46 E1 EE 7B BA 11 62 63 53 C5 16 }IU..F.. {..bcS..
+[01E0] 4A 0C 1C 99 7C 0E FB 45 1D B4 98 58 67 7E 40 65 J...|..E ...Xg~@e
+[01F0] 4B 48 E2 89 9C 8B C2 B8 39 D1 04 C0 A8 56 E8 A1 KH...... 9....V..
+[0200] 04 7A 7A C9 60 18 A0 29 E2 DC 82 4C 8F 18 CE 2F .zz.`..) ...L.../
+[0210] 14 F0 18 5B 6C FF 85 45 88 73 CB A4 55 08 FC BF ...[l..E .s..U...
+[0220] C7 9F 51 0A DB 2C C1 E3 3C DD F6 F0 A3 2D F1 3B ..Q..,.. <....-.;
+[0230] A0 12 1D FC 2A 67 F5 1A 7F E5 7C 6C FB 8A 18 BD ....*g.. ..|l....
+[0240] D1 5D E5 5E 68 30 AA 58 9E 10 13 E0 26 7E 7D C4 .].^h0.X ....&~}.
+[0250] E1 A5 B6 86 0F 1C 0F 13 A4 5E 5E 6A ED 42 79 31 ........ .^^j.By1
+[0260] BB B3 5F 3A 3F DD CB 63 82 FB 06 AE 12 36 C9 1E .._:?..c .....6..
+[0270] 06 7D 41 82 2E D2 FA 26 EC 17 50 5E D0 DE 26 85 .}A....& ..P^..&.
+[0280] 30 71 BC 45 3B DA 2E 08 8D B2 2A 3C E0 79 8F 77 0q.E;... ..*<.y.w
+[0290] 4C 01 69 7A 09 C7 88 E1 D1 DC FF 78 DB 25 7B B1 L.iz.... ...x.%{.
+[02A0] 3C BB 22 27 80 0D 75 96 18 B6 40 95 6D C8 AB 04 <."'..u. ..@.m...
+[02B0] 05 41 A1 C4 25 71 C4 53 3A A6 9C B2 4D E6 15 2C .A..%q.S :...M..,
+[02C0] B2 47 6C DA A8 7D CC A3 89 8B C9 1E 21 F5 E9 B2 .Gl..}.. ....!...
+[02D0] 42 95 68 28 AF C6 37 22 BA 30 8D 53 FA 08 0D CE B.h(..7" .0.S....
+[02E0] CA 81 61 0D 84 A5 2D 75 BD 41 85 4C 88 56 72 C6 ..a...-u .A.L.Vr.
+[02F0] B6 10 F8 34 CD B2 F4 5C 94 FA 80 90 82 A0 BD 68 ...4...\ .......h
+[0300] EC 08 32 C3 B6 51 1E 3F 67 CB 7B EB 70 83 84 D4 ..2..Q.? g.{.p...
+[0310] CB 52 55 36 61 1E 60 90 5B 6F FE 9A 62 05 CF 26 .RU6a.`. [o..b..&
+[0320] 8E 65 E2 60 4B ED 63 B4 C4 E6 44 B4 2F B0 B8 07 .e.`K.c. ..D./...
+[0330] FE BE 0D 50 E4 56 A4 2E 0D 25 76 0B 0F 44 09 20 ...P.V.. .%v..D.
+[0340] 80 E5 C4 94 63 E0 54 46 1D AB 5E 0B 09 93 B1 30 ....c.TF ..^....0
+[0350] 31 7B 04 DC 23 43 3B DB 7D 39 67 FE 9A 1F C1 08 1{..#C;. }9g.....
+[0360] AF 34 24 F6 74 E4 14 DA 34 8F 61 57 6A 7F 1D 4A .4$.t... 4.aWj..J
+[0370] 88 0A 90 78 93 F1 86 54 DB 22 86 D6 69 0F DF 44 ...x...T ."..i..D
+[0380] 7C D3 6B 9D 41 63 50 98 3A 97 B9 7B 4C 53 E3 85 |.k.AcP. :..{LS..
+[0390] 73 9A C9 08 A0 75 12 50 02 87 B0 CF CC 84 84 D9 s....u.P ........
+[03A0] BC FC 94 79 AF 6A A6 08 FF 19 7E E9 22 9B EC 5C ...y.j.. ..~."..\
+[03B0] C1 6B 1D A4 B4 55 32 5E 23 C3 C0 D4 8B 80 E6 67 .k...U2^ #......g
+[03C0] B1 59 EB 9D 5D 9B AD C6 0E 7D E2 FE B1 24 8A B1 .Y..]... .}...$..
+[03D0] 37 1E 60 7F 83 35 48 32 F7 03 E8 12 E6 21 7C 3D 7.`..5H2 .....!|=
+[03E0] 21 7F 6B 14 31 9C 1A A3 4C 2B 1C 5E EC 34 C1 2D !.k.1... L+.^.4.-
+[03F0] DA 19 6C E6 6D 8D 60 D7 55 9E E6 D0 B5 07 06 72 ..l.m.`. U......r
+[0400] C0 E9 4E 91 94 6B 3E 0B F1 0A 75 4D E8 CB 53 6B ..N..k>. ..uM..Sk
+[0410] 34 A4 2F 96 A5 39 1A 18 6E 27 00 6D 41 B7 D8 F5 4./..9.. n'.mA...
+[0420] 9A E5 01 FC 0B A8 97 56 EE 98 04 1D 98 84 5E 82 .......V ......^.
+[0430] C8 E8 EC 17 D5 FA 96 00 3B E1 98 1C D8 FA 66 A0 ........ ;.....f.
+[0440] DC 32 60 F6 03 46 08 3C E5 16 6F F2 8B 4D 72 9F .2`..F.< ..o..Mr.
+[0450] 0F E0 A9 71 6E 7C AE AA FB A3 4D F1 A1 B6 1B 9F ...qn|.. ..M.....
+[0460] 62 71 E1 2C 82 9B AE E3 07 9B 79 90 F1 C2 69 E5 bq.,.... ..y...i.
+[0470] 7E CB 57 E6 C9 1C 4E A8 C7 12 EA 4F 4C 52 17 03 ~.W...N. ...OLR..
+[0480] AB D4 FD 34 60 F4 7C BE 9E 36 30 37 88 95 61 2E ...4`.|. .607..a.
+[0490] CF 70 AF 22 70 DB E8 AA 6E 3D 30 F7 4D 84 D5 00 .p."p... n=0.M...
+[04A0] 00 00 00 00 00 00 01 00 00 00 01 00 00 00 17 4B ........ .......K
+[04B0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[04C0] 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 LE.COM.. ..admini
+[04D0] 73 74 72 61 74 6F 72 00 00 00 01 00 00 00 02 00 strator. ........
+[04E0] 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 ...KTEST .SAMBA.E
+[04F0] 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 XAMPLE.C OM....ci
+[0500] 66 73 00 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 fs....lo calktest
+[0510] 36 00 17 00 00 00 10 00 6E A1 B2 31 6D 48 C7 90 6....... n..1mH..
+[0520] 72 3A 0C 4B 8B 83 8C 4D 99 4F 6A 4D 99 50 85 7D r:.K...M .OjM.P.}
+[0530] 44 0B 68 00 00 00 00 00 40 28 00 00 00 00 00 00 D.h..... @(......
+[0540] 00 00 00 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 ........ a...0...
+[0550] A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0560] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0570] A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 ..0..... ...0...c
+[0580] 69 66 73 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 ifs..loc alktest6
+[0590] A3 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 03 02 ....0... ........
+[05A0] 01 02 A2 82 03 9C 04 82 03 98 C6 BB 64 A8 31 00 ........ ....d.1.
+[05B0] FC 5E 51 3C 87 F8 34 47 3B D0 6F 6F FD 9E A6 91 .^Q<..4G ;.oo....
+[05C0] 12 74 2D 44 BB AA 91 A0 2D 46 3E 9E FB FB C4 FB .t-D.... -F>.....
+[05D0] F1 15 FD BB DA EE 06 A9 20 6A 38 DC 46 06 27 D9 ........ j8.F.'.
+[05E0] A2 9D 2D 1F FD 0D 7D 8A BB 0A 7C E8 47 17 BC 7B ..-...}. ..|.G..{
+[05F0] 70 E4 51 6A BA 51 68 62 28 4A 1E 51 D1 0D CD 02 p.Qj.Qhb (J.Q....
+[0600] 55 75 44 8A B9 C2 84 F4 17 34 92 9B 31 85 9E 43 UuD..... .4..1..C
+[0610] C1 0C 3A B2 69 7F 20 1A 18 1F 65 4F C0 20 C9 B5 ..:.i. . ..eO. ..
+[0620] AF E1 61 8C 90 10 63 26 A6 5D 05 3C CD 29 BB 7B ..a...c& .].<.).{
+[0630] 74 D5 8F 2C 7F 4B E8 84 24 57 37 8A C6 F7 91 FD t..,.K.. $W7.....
+[0640] 22 9A A5 0D E9 4A 78 93 36 FC A8 8C 8A 27 8A C6 "....Jx. 6....'..
+[0650] 28 4B 7B DA 11 42 BC 09 10 81 82 14 0F 9C B8 48 (K{..B.. .......H
+[0660] 26 91 78 A8 DD 97 6C 24 A1 D2 E8 85 19 B3 D3 85 &.x...l$ ........
+[0670] 4D 38 C7 7D 49 55 8E 85 46 E1 EE 7B BA 11 62 63 M8.}IU.. F..{..bc
+[0680] 53 C5 16 4A 0C 1C 99 7C 0E FB 45 1D B4 98 58 67 S..J...| ..E...Xg
+[0690] 7E 40 65 4B 48 E2 89 9C 8B C2 B8 39 D1 04 C0 A8 ~@eKH... ...9....
+[06A0] 56 E8 A1 04 7A 7A C9 60 18 A0 29 E2 DC 82 4C 8F V...zz.` ..)...L.
+[06B0] 18 CE 2F 14 F0 18 5B 6C FF 85 45 88 73 CB A4 55 ../...[l ..E.s..U
+[06C0] 08 FC BF C7 9F 51 0A DB 2C C1 E3 3C DD F6 F0 A3 .....Q.. ,..<....
+[06D0] 2D F1 3B A0 12 1D FC 2A 67 F5 1A 7F E5 7C 6C FB -.;....* g....|l.
+[06E0] 8A 18 BD D1 5D E5 5E 68 30 AA 58 9E 10 13 E0 26 ....].^h 0.X....&
+[06F0] 7E 7D C4 E1 A5 B6 86 0F 1C 0F 13 A4 5E 5E 6A ED ~}...... ....^^j.
+[0700] 42 79 31 BB B3 5F 3A 3F DD CB 63 82 FB 06 AE 12 By1.._:? ..c.....
+[0710] 36 C9 1E 06 7D 41 82 2E D2 FA 26 EC 17 50 5E D0 6...}A.. ..&..P^.
+[0720] DE 26 85 30 71 BC 45 3B DA 2E 08 8D B2 2A 3C E0 .&.0q.E; .....*<.
+[0730] 79 8F 77 4C 01 69 7A 09 C7 88 E1 D1 DC FF 78 DB y.wL.iz. ......x.
+[0740] 25 7B B1 3C BB 22 27 80 0D 75 96 18 B6 40 95 6D %{.<."'. .u...@.m
+[0750] C8 AB 04 05 41 A1 C4 25 71 C4 53 3A A6 9C B2 4D ....A..% q.S:...M
+[0760] E6 15 2C B2 47 6C DA A8 7D CC A3 89 8B C9 1E 21 ..,.Gl.. }......!
+[0770] F5 E9 B2 42 95 68 28 AF C6 37 22 BA 30 8D 53 FA ...B.h(. .7".0.S.
+[0780] 08 0D CE CA 81 61 0D 84 A5 2D 75 BD 41 85 4C 88 .....a.. .-u.A.L.
+[0790] 56 72 C6 B6 10 F8 34 CD B2 F4 5C 94 FA 80 90 82 Vr....4. ..\.....
+[07A0] A0 BD 68 EC 08 32 C3 B6 51 1E 3F 67 CB 7B EB 70 ..h..2.. Q.?g.{.p
+[07B0] 83 84 D4 CB 52 55 36 61 1E 60 90 5B 6F FE 9A 62 ....RU6a .`.[o..b
+[07C0] 05 CF 26 8E 65 E2 60 4B ED 63 B4 C4 E6 44 B4 2F ..&.e.`K .c...D./
+[07D0] B0 B8 07 FE BE 0D 50 E4 56 A4 2E 0D 25 76 0B 0F ......P. V...%v..
+[07E0] 44 09 20 80 E5 C4 94 63 E0 54 46 1D AB 5E 0B 09 D. ....c .TF..^..
+[07F0] 93 B1 30 31 7B 04 DC 23 43 3B DB 7D 39 67 FE 9A ..01{..# C;.}9g..
+[0800] 1F C1 08 AF 34 24 F6 74 E4 14 DA 34 8F 61 57 6A ....4$.t ...4.aWj
+[0810] 7F 1D 4A 88 0A 90 78 93 F1 86 54 DB 22 86 D6 69 ..J...x. ..T."..i
+[0820] 0F DF 44 7C D3 6B 9D 41 63 50 98 3A 97 B9 7B 4C ..D|.k.A cP.:..{L
+[0830] 53 E3 85 73 9A C9 08 A0 75 12 50 02 87 B0 CF CC S..s.... u.P.....
+[0840] 84 84 D9 BC FC 94 79 AF 6A A6 08 FF 19 7E E9 22 ......y. j....~."
+[0850] 9B EC 5C C1 6B 1D A4 B4 55 32 5E 23 C3 C0 D4 8B ..\.k... U2^#....
+[0860] 80 E6 67 B1 59 EB 9D 5D 9B AD C6 0E 7D E2 FE B1 ..g.Y..] ....}...
+[0870] 24 8A B1 37 1E 60 7F 83 35 48 32 F7 03 E8 12 E6 $..7.`.. 5H2.....
+[0880] 21 7C 3D 21 7F 6B 14 31 9C 1A A3 4C 2B 1C 5E EC !|=!.k.1 ...L+.^.
+[0890] 34 C1 2D DA 19 6C E6 6D 8D 60 D7 55 9E E6 D0 B5 4.-..l.m .`.U....
+[08A0] 07 06 72 C0 E9 4E 91 94 6B 3E 0B F1 0A 75 4D E8 ..r..N.. k>...uM.
+[08B0] CB 53 6B 34 A4 2F 96 A5 39 1A 18 6E 27 00 6D 41 .Sk4./.. 9..n'.mA
+[08C0] B7 D8 F5 9A E5 01 FC 0B A8 97 56 EE 98 04 1D 98 ........ ..V.....
+[08D0] 84 5E 82 C8 E8 EC 17 D5 FA 96 00 3B E1 98 1C D8 .^...... ...;....
+[08E0] FA 66 A0 DC 32 60 F6 03 46 08 3C E5 16 6F F2 8B .f..2`.. F.<..o..
+[08F0] 4D 72 9F 0F E0 A9 71 6E 7C AE AA FB A3 4D F1 A1 Mr....qn |....M..
+[0900] B6 1B 9F 62 71 E1 2C 82 9B AE E3 07 9B 79 90 F1 ...bq.,. .....y..
+[0910] C2 69 E5 7E CB 57 E6 C9 1C 4E A8 C7 12 EA 4F 4C .i.~.W.. .N....OL
+[0920] 52 17 03 AB D4 FD 34 60 F4 7C BE 9E 36 30 37 88 R.....4` .|..607.
+[0930] 95 61 2E CF 70 AF 22 70 DB E8 AA 6E 3D 30 F7 4D .a..p."p ...n=0.M
+[0940] 84 D5 00 00 00 00 00 00 00 01 00 00 00 01 00 00 ........ ........
+[0950] 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[0960] 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D AMPLE.CO M....adm
+[0970] 69 6E 69 73 74 72 61 74 6F 72 00 00 00 01 00 00 inistrat or......
+[0980] 00 02 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[0990] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 A.EXAMPL E.COM...
+[09A0] 04 63 69 66 73 00 00 00 0B 6C 6F 63 61 6C 6B 74 .cifs... .localkt
+[09B0] 65 73 74 36 00 17 00 00 00 10 00 6E A1 B2 31 6D est6.... ...n..1m
+[09C0] 48 C7 90 72 3A 0C 4B 8B 83 8C 4D 99 4F 6A 4D 99 H..r:.K. ..M.OjM.
+[09D0] 50 85 7D 44 0B 68 00 00 00 00 00 40 28 00 00 00 P.}D.h.. ...@(...
+[09E0] 00 00 00 00 00 00 00 00 00 03 FA 61 82 03 F6 30 ........ ...a...0
+[09F0] 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 ........ ....KTES
+[0A00] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0A10] 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 COM..0.. ......0.
+[0A20] 1B 04 63 69 66 73 1B 0B 6C 6F 63 61 6C 6B 74 65 ..cifs.. localkte
+[0A30] 73 74 36 A3 82 03 AE 30 82 03 AA A0 03 02 01 17 st6....0 ........
+[0A40] A1 03 02 01 02 A2 82 03 9C 04 82 03 98 C6 BB 64 ........ .......d
+[0A50] A8 31 00 FC 5E 51 3C 87 F8 34 47 3B D0 6F 6F FD .1..^Q<. .4G;.oo.
+[0A60] 9E A6 91 12 74 2D 44 BB AA 91 A0 2D 46 3E 9E FB ....t-D. ...-F>..
+[0A70] FB C4 FB F1 15 FD BB DA EE 06 A9 20 6A 38 DC 46 ........ ... j8.F
+[0A80] 06 27 D9 A2 9D 2D 1F FD 0D 7D 8A BB 0A 7C E8 47 .'...-.. .}...|.G
+[0A90] 17 BC 7B 70 E4 51 6A BA 51 68 62 28 4A 1E 51 D1 ..{p.Qj. Qhb(J.Q.
+[0AA0] 0D CD 02 55 75 44 8A B9 C2 84 F4 17 34 92 9B 31 ...UuD.. ....4..1
+[0AB0] 85 9E 43 C1 0C 3A B2 69 7F 20 1A 18 1F 65 4F C0 ..C..:.i . ...eO.
+[0AC0] 20 C9 B5 AF E1 61 8C 90 10 63 26 A6 5D 05 3C CD ....a.. .c&.].<.
+[0AD0] 29 BB 7B 74 D5 8F 2C 7F 4B E8 84 24 57 37 8A C6 ).{t..,. K..$W7..
+[0AE0] F7 91 FD 22 9A A5 0D E9 4A 78 93 36 FC A8 8C 8A ...".... Jx.6....
+[0AF0] 27 8A C6 28 4B 7B DA 11 42 BC 09 10 81 82 14 0F '..(K{.. B.......
+[0B00] 9C B8 48 26 91 78 A8 DD 97 6C 24 A1 D2 E8 85 19 ..H&.x.. .l$.....
+[0B10] B3 D3 85 4D 38 C7 7D 49 55 8E 85 46 E1 EE 7B BA ...M8.}I U..F..{.
+[0B20] 11 62 63 53 C5 16 4A 0C 1C 99 7C 0E FB 45 1D B4 .bcS..J. ..|..E..
+[0B30] 98 58 67 7E 40 65 4B 48 E2 89 9C 8B C2 B8 39 D1 .Xg~@eKH ......9.
+[0B40] 04 C0 A8 56 E8 A1 04 7A 7A C9 60 18 A0 29 E2 DC ...V...z z.`..)..
+[0B50] 82 4C 8F 18 CE 2F 14 F0 18 5B 6C FF 85 45 88 73 .L.../.. .[l..E.s
+[0B60] CB A4 55 08 FC BF C7 9F 51 0A DB 2C C1 E3 3C DD ..U..... Q..,..<.
+[0B70] F6 F0 A3 2D F1 3B A0 12 1D FC 2A 67 F5 1A 7F E5 ...-.;.. ..*g....
+[0B80] 7C 6C FB 8A 18 BD D1 5D E5 5E 68 30 AA 58 9E 10 |l.....] .^h0.X..
+[0B90] 13 E0 26 7E 7D C4 E1 A5 B6 86 0F 1C 0F 13 A4 5E ..&~}... .......^
+[0BA0] 5E 6A ED 42 79 31 BB B3 5F 3A 3F DD CB 63 82 FB ^j.By1.. _:?..c..
+[0BB0] 06 AE 12 36 C9 1E 06 7D 41 82 2E D2 FA 26 EC 17 ...6...} A....&..
+[0BC0] 50 5E D0 DE 26 85 30 71 BC 45 3B DA 2E 08 8D B2 P^..&.0q .E;.....
+[0BD0] 2A 3C E0 79 8F 77 4C 01 69 7A 09 C7 88 E1 D1 DC *<.y.wL. iz......
+[0BE0] FF 78 DB 25 7B B1 3C BB 22 27 80 0D 75 96 18 B6 .x.%{.<. "'..u...
+[0BF0] 40 95 6D C8 AB 04 05 41 A1 C4 25 71 C4 53 3A A6 @.m....A ..%q.S:.
+[0C00] 9C B2 4D E6 15 2C B2 47 6C DA A8 7D CC A3 89 8B ..M..,.G l..}....
+[0C10] C9 1E 21 F5 E9 B2 42 95 68 28 AF C6 37 22 BA 30 ..!...B. h(..7".0
+[0C20] 8D 53 FA 08 0D CE CA 81 61 0D 84 A5 2D 75 BD 41 .S...... a...-u.A
+[0C30] 85 4C 88 56 72 C6 B6 10 F8 34 CD B2 F4 5C 94 FA .L.Vr... .4...\..
+[0C40] 80 90 82 A0 BD 68 EC 08 32 C3 B6 51 1E 3F 67 CB .....h.. 2..Q.?g.
+[0C50] 7B EB 70 83 84 D4 CB 52 55 36 61 1E 60 90 5B 6F {.p....R U6a.`.[o
+[0C60] FE 9A 62 05 CF 26 8E 65 E2 60 4B ED 63 B4 C4 E6 ..b..&.e .`K.c...
+[0C70] 44 B4 2F B0 B8 07 FE BE 0D 50 E4 56 A4 2E 0D 25 D./..... .P.V...%
+[0C80] 76 0B 0F 44 09 20 80 E5 C4 94 63 E0 54 46 1D AB v..D. .. ..c.TF..
+[0C90] 5E 0B 09 93 B1 30 31 7B 04 DC 23 43 3B DB 7D 39 ^....01{ ..#C;.}9
+[0CA0] 67 FE 9A 1F C1 08 AF 34 24 F6 74 E4 14 DA 34 8F g......4 $.t...4.
+[0CB0] 61 57 6A 7F 1D 4A 88 0A 90 78 93 F1 86 54 DB 22 aWj..J.. .x...T."
+[0CC0] 86 D6 69 0F DF 44 7C D3 6B 9D 41 63 50 98 3A 97 ..i..D|. k.AcP.:.
+[0CD0] B9 7B 4C 53 E3 85 73 9A C9 08 A0 75 12 50 02 87 .{LS..s. ...u.P..
+[0CE0] B0 CF CC 84 84 D9 BC FC 94 79 AF 6A A6 08 FF 19 ........ .y.j....
+[0CF0] 7E E9 22 9B EC 5C C1 6B 1D A4 B4 55 32 5E 23 C3 ~."..\.k ...U2^#.
+[0D00] C0 D4 8B 80 E6 67 B1 59 EB 9D 5D 9B AD C6 0E 7D .....g.Y ..]....}
+[0D10] E2 FE B1 24 8A B1 37 1E 60 7F 83 35 48 32 F7 03 ...$..7. `..5H2..
+[0D20] E8 12 E6 21 7C 3D 21 7F 6B 14 31 9C 1A A3 4C 2B ...!|=!. k.1...L+
+[0D30] 1C 5E EC 34 C1 2D DA 19 6C E6 6D 8D 60 D7 55 9E .^.4.-.. l.m.`.U.
+[0D40] E6 D0 B5 07 06 72 C0 E9 4E 91 94 6B 3E 0B F1 0A .....r.. N..k>...
+[0D50] 75 4D E8 CB 53 6B 34 A4 2F 96 A5 39 1A 18 6E 27 uM..Sk4. /..9..n'
+[0D60] 00 6D 41 B7 D8 F5 9A E5 01 FC 0B A8 97 56 EE 98 .mA..... .....V..
+[0D70] 04 1D 98 84 5E 82 C8 E8 EC 17 D5 FA 96 00 3B E1 ....^... ......;.
+[0D80] 98 1C D8 FA 66 A0 DC 32 60 F6 03 46 08 3C E5 16 ....f..2 `..F.<..
+[0D90] 6F F2 8B 4D 72 9F 0F E0 A9 71 6E 7C AE AA FB A3 o..Mr... .qn|....
+[0DA0] 4D F1 A1 B6 1B 9F 62 71 E1 2C 82 9B AE E3 07 9B M.....bq .,......
+[0DB0] 79 90 F1 C2 69 E5 7E CB 57 E6 C9 1C 4E A8 C7 12 y...i.~. W...N...
+[0DC0] EA 4F 4C 52 17 03 AB D4 FD 34 60 F4 7C BE 9E 36 .OLR.... .4`.|..6
+[0DD0] 30 37 88 95 61 2E CF 70 AF 22 70 DB E8 AA 6E 3D 07..a..p ."p...n=
+[0DE0] 30 F7 4D 84 D5 00 00 00 00 00 00 00 01 00 00 00 0.M..... ........
+[0DF0] 01 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[0E00] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D .EXAMPLE .COM....
+[0E10] 61 64 6D 69 6E 69 73 74 72 61 74 6F 72 00 00 00 administ rator...
+[0E20] 01 00 00 00 02 00 00 00 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0E30] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0E40] 00 00 00 04 63 69 66 73 00 00 00 0B 4C 4F 43 41 ....cifs ....LOCA
+[0E50] 4C 4B 54 45 53 54 36 00 17 00 00 00 10 1D C8 5E LKTEST6. .......^
+[0E60] 46 48 82 F9 29 DB C6 A6 F1 72 6D 8D E9 4D 99 4F FH..)... .rm..M.O
+[0E70] 6A 4D 99 85 09 7D 44 0B 68 00 00 00 00 00 40 28 jM...}D. h.....@(
+[0E80] 00 00 00 00 00 00 00 00 00 00 00 00 03 FA 61 82 ........ ......a.
+[0E90] 03 F6 30 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B ..0..... .......K
+[0EA0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0EB0] 4C 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 LE.COM.. 0.......
+[0EC0] 15 30 13 1B 04 63 69 66 73 1B 0B 4C 4F 43 41 4C .0...cif s..LOCAL
+[0ED0] 4B 54 45 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 KTEST6.. ..0.....
+[0EE0] 02 01 17 A1 03 02 01 02 A2 82 03 9C 04 82 03 98 ........ ........
+[0EF0] 66 D8 19 46 FA CB 73 2D CF 88 FD 4A EE 07 48 DA f..F..s- ...J..H.
+[0F00] 0E BC 58 30 43 40 A4 9C 00 0F 3B 17 C1 2D F5 9C ..X0C@.. ..;..-..
+[0F10] 3E D9 2F 1D CA 01 9B D7 2E EC D7 70 ED 8B 8B 1B >./..... ...p....
+[0F20] 5E F2 4E EE DD 0F C0 8D 61 E5 D7 0A 56 00 32 B1 ^.N..... a...V.2.
+[0F30] DB 91 37 29 0F 2F 85 EE A8 43 BA A5 B8 D4 19 74 ..7)./.. .C.....t
+[0F40] 33 F0 69 52 E1 58 98 83 D6 16 0B 44 A9 63 9B D4 3.iR.X.. ...D.c..
+[0F50] 4E 6E A7 3E CD 9A 96 4D C4 96 F5 07 6D 29 B6 ED Nn.>...M ....m)..
+[0F60] 2A 62 3D 53 22 33 D1 95 E9 DF 74 4C 2A E2 29 AF *b=S"3.. ..tL*.).
+[0F70] 5B 69 B0 48 2D AD 94 FD A5 1D 54 D8 E2 5E C1 68 [i.H-... ..T..^.h
+[0F80] 6F BA 02 01 79 C3 C9 97 0B 76 66 45 E2 3B 10 17 o...y... .vfE.;..
+[0F90] 95 40 46 E4 85 B9 87 BB CF CF 19 8C 3A C0 EA 38 .@F..... ....:..8
+[0FA0] 3B B9 E9 4B 05 89 E5 27 8C 62 95 BC 0D 65 F0 D2 ;..K...' .b...e..
+[0FB0] C0 5E BC 65 01 D5 0B CB 17 31 0F 06 49 4F A2 4A .^.e.... .1..IO.J
+[0FC0] 70 77 DB BD 92 5B 37 5C EC 06 DF C5 E2 31 C8 40 pw...[7\ .....1.@
+[0FD0] 09 11 68 14 E7 7D CE 54 4F 52 61 31 2C 1C 53 52 ..h..}.T ORa1,.SR
+[0FE0] DB BE D8 95 39 EE 7D C6 CE C8 22 95 92 97 97 3D ....9.}. .."....=
+[0FF0] 5E 66 0F AD DC C2 4E 2E 2B 9F 63 20 30 DF B7 C1 ^f....N. +.c 0...
+[1000] D4 65 AA 6F 2D 10 24 07 20 8D 88 6E 4B 09 04 31 .e.o-.$. ..nK..1
+[1010] B6 A3 EB F7 37 32 0E 0C 73 C6 F6 B8 4D D9 0C 4C ....72.. s...M..L
+[1020] 5B EC 10 6A 51 19 EA 3F FF 46 E7 73 16 A7 1F 33 [..jQ..? .F.s...3
+[1030] 98 7C 9B AD 5A 23 A9 40 7C 0F DF EE 0F AA C7 E8 .|..Z#.@ |.......
+[1040] 63 07 98 3A 4A 0D 18 62 01 21 B2 AE A5 69 B0 C1 c..:J..b .!...i..
+[1050] 15 51 BA 97 D2 C5 42 5B C5 30 38 18 A9 48 AB D7 .Q....B[ .08..H..
+[1060] FC A1 BC 9F 71 E7 EA 18 54 42 DA D6 A4 FC C1 DC ....q... TB......
+[1070] F3 12 30 62 AC 98 E1 7D 2B 34 1E 52 4C 26 67 32 ..0b...} +4.RL&g2
+[1080] D9 44 1A 08 27 0E DA D0 FC 84 66 35 81 D6 EB 98 .D..'... ..f5....
+[1090] 46 6F 1E 47 E0 14 31 BE 47 80 65 AA 0B 20 D6 33 Fo.G..1. G.e.. .3
+[10A0] 36 3B 0D 40 2F 5A 2E 0E 01 BE 00 EB 33 3E 4B 32 6;.@/Z.. ....3>K2
+[10B0] 91 F4 22 96 E5 5F D4 D5 92 94 CC 5B 59 6A 3E D2 ..".._.. ...[Yj>.
+[10C0] FB A0 4F 99 C4 07 8B 6F 2B 14 37 CD 37 44 C0 1F ..O....o +.7.7D..
+[10D0] 80 9C 43 46 F2 5E F4 FE D3 39 70 61 BE 72 5B 3A ..CF.^.. .9pa.r[:
+[10E0] 8F 37 95 78 1E AB D9 E7 E9 DA FC 47 09 81 A0 0D .7.x.... ...G....
+[10F0] 62 E1 F9 34 36 D1 DB E6 98 D8 F4 3E 77 5A 4D E2 b..46... ...>wZM.
+[1100] 5F 20 70 3D 3D 5B 34 D9 FD A8 31 F7 D9 59 F7 A3 _ p==[4. ..1..Y..
+[1110] F0 66 F7 D9 AD 1C CD D5 85 33 A0 87 22 31 D4 F3 .f...... .3.."1..
+[1120] 67 80 68 20 A2 90 72 7A 6F 64 FD 68 82 9E 91 B8 g.h ..rz od.h....
+[1130] E3 F7 6D 6C 38 74 F0 96 A2 F6 25 D7 92 58 14 60 ..ml8t.. ..%..X.`
+[1140] 9F AE 01 4C 0C 09 67 3E 35 67 71 1E 2A 86 21 D3 ...L..g> 5gq.*.!.
+[1150] 60 61 98 16 94 67 0B 52 76 63 93 BD A3 3B A9 F0 `a...g.R vc...;..
+[1160] A2 6A B7 E6 0F 35 64 DA 6A EA 20 A6 3D 94 71 59 .j...5d. j. .=.qY
+[1170] 5E CB B2 D3 F9 4D FE 1B 4B D8 64 C8 3B 7A A8 E6 ^....M.. K.d.;z..
+[1180] D2 D5 76 71 26 D4 5C DA 1A 55 17 F2 16 C9 2F 77 ..vq&.\. .U..../w
+[1190] DB 95 19 48 A5 AC D0 C3 31 9C 0A CC 1B 44 11 6B ...H.... 1....D.k
+[11A0] 7C 88 7A 5D CF 6E 12 DA EF C5 C7 34 1D F4 CC EA |.z].n.. ...4....
+[11B0] 37 24 4B B3 0F C1 A3 F2 29 A0 D8 93 39 C6 16 57 7$K..... )...9..W
+[11C0] D5 BF 57 BF 6C 7E F7 90 E0 EB A3 8B 07 56 9C EC ..W.l~.. .....V..
+[11D0] 15 3E 21 DA A5 7C 00 3C F9 D2 A7 1C 6F 16 25 31 .>!..|.< ....o.%1
+[11E0] C5 28 A7 EA F3 47 31 50 DD E1 ED 0A 93 DB 85 CC .(...G1P ........
+[11F0] 6B 4B 2C 7F E8 F8 2D A9 6D 1D 0A 87 F2 10 8C 82 kK,...-. m.......
+[1200] 2F 9B D4 9B 92 8C 77 40 50 42 1E 42 C4 0A 4F E3 /.....w@ PB.B..O.
+[1210] 6C 6C DC 81 C4 1E BB F0 7D CF 3C 73 22 5B C3 1A ll...... }.<s"[..
+[1220] 97 35 EE 3A CD 6D F3 68 A3 C5 65 7E E9 54 C0 E3 .5.:.m.h ..e~.T..
+[1230] 7D 6A 32 4C D1 3E D0 78 4B BF 18 9F A5 25 4A 92 }j2L.>.x K....%J.
+[1240] 1E 6C 8F 01 D6 59 D7 CF 2E A0 CC 98 F6 75 28 2F .l...Y.. .....u(/
+[1250] F7 2A 70 28 A9 45 1F 75 C2 4E 62 ED D8 C4 A0 8D .*p(.E.u .Nb.....
+[1260] 55 B2 84 1C A4 CE 87 EF 24 EE BC CE 40 09 EB 05 U....... $...@...
+[1270] 0B D1 14 31 50 32 2F B6 A8 97 17 4B A7 95 01 50 ...1P2/. ...K...P
+[1280] 6E 0E 23 49 9C 72 21 91 00 00 00 00 00 00 00 01 n.#I.r!. ........
+[1290] 00 00 00 01 00 00 00 17 4B 54 45 53 54 2E 53 41 ........ KTEST.SA
+[12A0] 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 MBA.EXAM PLE.COM.
+[12B0] 00 00 0D 61 64 6D 69 6E 69 73 74 72 61 74 6F 72 ...admin istrator
+[12C0] 00 00 00 01 00 00 00 02 00 00 00 17 4B 54 45 53 ........ ....KTES
+[12D0] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[12E0] 43 4F 4D 00 00 00 04 63 69 66 73 00 00 00 0B 4C COM....c ifs....L
+[12F0] 4F 43 41 4C 4B 54 45 53 54 36 00 17 00 00 00 10 OCALKTES T6......
+[1300] 1D C8 5E 46 48 82 F9 29 DB C6 A6 F1 72 6D 8D E9 ..^FH..) ....rm..
+[1310] 4D 99 4F 6A 4D 99 85 09 7D 44 0B 68 00 00 00 00 M.OjM... }D.h....
+[1320] 00 40 28 00 00 00 00 00 00 00 00 00 00 00 00 03 .@(..... ........
+[1330] FA 61 82 03 F6 30 82 03 F2 A0 03 02 01 05 A1 19 .a...0.. ........
+[1340] 1B 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[1350] 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 AMPLE.CO M..0....
+[1360] 01 01 A1 15 30 13 1B 04 63 69 66 73 1B 0B 4C 4F ....0... cifs..LO
+[1370] 43 41 4C 4B 54 45 53 54 36 A3 82 03 AE 30 82 03 CALKTEST 6....0..
+[1380] AA A0 03 02 01 17 A1 03 02 01 02 A2 82 03 9C 04 ........ ........
+[1390] 82 03 98 66 D8 19 46 FA CB 73 2D CF 88 FD 4A EE ...f..F. .s-...J.
+[13A0] 07 48 DA 0E BC 58 30 43 40 A4 9C 00 0F 3B 17 C1 .H...X0C @....;..
+[13B0] 2D F5 9C 3E D9 2F 1D CA 01 9B D7 2E EC D7 70 ED -..>./.. ......p.
+[13C0] 8B 8B 1B 5E F2 4E EE DD 0F C0 8D 61 E5 D7 0A 56 ...^.N.. ...a...V
+[13D0] 00 32 B1 DB 91 37 29 0F 2F 85 EE A8 43 BA A5 B8 .2...7). /...C...
+[13E0] D4 19 74 33 F0 69 52 E1 58 98 83 D6 16 0B 44 A9 ..t3.iR. X.....D.
+[13F0] 63 9B D4 4E 6E A7 3E CD 9A 96 4D C4 96 F5 07 6D c..Nn.>. ..M....m
+[1400] 29 B6 ED 2A 62 3D 53 22 33 D1 95 E9 DF 74 4C 2A )..*b=S" 3....tL*
+[1410] E2 29 AF 5B 69 B0 48 2D AD 94 FD A5 1D 54 D8 E2 .).[i.H- .....T..
+[1420] 5E C1 68 6F BA 02 01 79 C3 C9 97 0B 76 66 45 E2 ^.ho...y ....vfE.
+[1430] 3B 10 17 95 40 46 E4 85 B9 87 BB CF CF 19 8C 3A ;...@F.. .......:
+[1440] C0 EA 38 3B B9 E9 4B 05 89 E5 27 8C 62 95 BC 0D ..8;..K. ..'.b...
+[1450] 65 F0 D2 C0 5E BC 65 01 D5 0B CB 17 31 0F 06 49 e...^.e. ....1..I
+[1460] 4F A2 4A 70 77 DB BD 92 5B 37 5C EC 06 DF C5 E2 O.Jpw... [7\.....
+[1470] 31 C8 40 09 11 68 14 E7 7D CE 54 4F 52 61 31 2C 1.@..h.. }.TORa1,
+[1480] 1C 53 52 DB BE D8 95 39 EE 7D C6 CE C8 22 95 92 .SR....9 .}..."..
+[1490] 97 97 3D 5E 66 0F AD DC C2 4E 2E 2B 9F 63 20 30 ..=^f... .N.+.c 0
+[14A0] DF B7 C1 D4 65 AA 6F 2D 10 24 07 20 8D 88 6E 4B ....e.o- .$. ..nK
+[14B0] 09 04 31 B6 A3 EB F7 37 32 0E 0C 73 C6 F6 B8 4D ..1....7 2..s...M
+[14C0] D9 0C 4C 5B EC 10 6A 51 19 EA 3F FF 46 E7 73 16 ..L[..jQ ..?.F.s.
+[14D0] A7 1F 33 98 7C 9B AD 5A 23 A9 40 7C 0F DF EE 0F ..3.|..Z #.@|....
+[14E0] AA C7 E8 63 07 98 3A 4A 0D 18 62 01 21 B2 AE A5 ...c..:J ..b.!...
+[14F0] 69 B0 C1 15 51 BA 97 D2 C5 42 5B C5 30 38 18 A9 i...Q... .B[.08..
+[1500] 48 AB D7 FC A1 BC 9F 71 E7 EA 18 54 42 DA D6 A4 H......q ...TB...
+[1510] FC C1 DC F3 12 30 62 AC 98 E1 7D 2B 34 1E 52 4C .....0b. ..}+4.RL
+[1520] 26 67 32 D9 44 1A 08 27 0E DA D0 FC 84 66 35 81 &g2.D..' .....f5.
+[1530] D6 EB 98 46 6F 1E 47 E0 14 31 BE 47 80 65 AA 0B ...Fo.G. .1.G.e..
+[1540] 20 D6 33 36 3B 0D 40 2F 5A 2E 0E 01 BE 00 EB 33 .36;.@/ Z......3
+[1550] 3E 4B 32 91 F4 22 96 E5 5F D4 D5 92 94 CC 5B 59 >K2..".. _.....[Y
+[1560] 6A 3E D2 FB A0 4F 99 C4 07 8B 6F 2B 14 37 CD 37 j>...O.. ..o+.7.7
+[1570] 44 C0 1F 80 9C 43 46 F2 5E F4 FE D3 39 70 61 BE D....CF. ^...9pa.
+[1580] 72 5B 3A 8F 37 95 78 1E AB D9 E7 E9 DA FC 47 09 r[:.7.x. ......G.
+[1590] 81 A0 0D 62 E1 F9 34 36 D1 DB E6 98 D8 F4 3E 77 ...b..46 ......>w
+[15A0] 5A 4D E2 5F 20 70 3D 3D 5B 34 D9 FD A8 31 F7 D9 ZM._ p== [4...1..
+[15B0] 59 F7 A3 F0 66 F7 D9 AD 1C CD D5 85 33 A0 87 22 Y...f... ....3.."
+[15C0] 31 D4 F3 67 80 68 20 A2 90 72 7A 6F 64 FD 68 82 1..g.h . .rzod.h.
+[15D0] 9E 91 B8 E3 F7 6D 6C 38 74 F0 96 A2 F6 25 D7 92 .....ml8 t....%..
+[15E0] 58 14 60 9F AE 01 4C 0C 09 67 3E 35 67 71 1E 2A X.`...L. .g>5gq.*
+[15F0] 86 21 D3 60 61 98 16 94 67 0B 52 76 63 93 BD A3 .!.`a... g.Rvc...
+[1600] 3B A9 F0 A2 6A B7 E6 0F 35 64 DA 6A EA 20 A6 3D ;...j... 5d.j. .=
+[1610] 94 71 59 5E CB B2 D3 F9 4D FE 1B 4B D8 64 C8 3B .qY^.... M..K.d.;
+[1620] 7A A8 E6 D2 D5 76 71 26 D4 5C DA 1A 55 17 F2 16 z....vq& .\..U...
+[1630] C9 2F 77 DB 95 19 48 A5 AC D0 C3 31 9C 0A CC 1B ./w...H. ...1....
+[1640] 44 11 6B 7C 88 7A 5D CF 6E 12 DA EF C5 C7 34 1D D.k|.z]. n.....4.
+[1650] F4 CC EA 37 24 4B B3 0F C1 A3 F2 29 A0 D8 93 39 ...7$K.. ...)...9
+[1660] C6 16 57 D5 BF 57 BF 6C 7E F7 90 E0 EB A3 8B 07 ..W..W.l ~.......
+[1670] 56 9C EC 15 3E 21 DA A5 7C 00 3C F9 D2 A7 1C 6F V...>!.. |.<....o
+[1680] 16 25 31 C5 28 A7 EA F3 47 31 50 DD E1 ED 0A 93 .%1.(... G1P.....
+[1690] DB 85 CC 6B 4B 2C 7F E8 F8 2D A9 6D 1D 0A 87 F2 ...kK,.. .-.m....
+[16A0] 10 8C 82 2F 9B D4 9B 92 8C 77 40 50 42 1E 42 C4 .../.... .w@PB.B.
+[16B0] 0A 4F E3 6C 6C DC 81 C4 1E BB F0 7D CF 3C 73 22 .O.ll... ...}.<s"
+[16C0] 5B C3 1A 97 35 EE 3A CD 6D F3 68 A3 C5 65 7E E9 [...5.:. m.h..e~.
+[16D0] 54 C0 E3 7D 6A 32 4C D1 3E D0 78 4B BF 18 9F A5 T..}j2L. >.xK....
+[16E0] 25 4A 92 1E 6C 8F 01 D6 59 D7 CF 2E A0 CC 98 F6 %J..l... Y.......
+[16F0] 75 28 2F F7 2A 70 28 A9 45 1F 75 C2 4E 62 ED D8 u(/.*p(. E.u.Nb..
+[1700] C4 A0 8D 55 B2 84 1C A4 CE 87 EF 24 EE BC CE 40 ...U.... ...$...@
+[1710] 09 EB 05 0B D1 14 31 50 32 2F B6 A8 97 17 4B A7 ......1P 2/....K.
+[1720] 95 01 50 6E 0E 23 49 9C 72 21 91 00 00 00 00 00 ..Pn.#I. r!......
+[1730] 00 00 01 00 00 00 01 00 00 00 17 4B 54 45 53 54 ........ ...KTEST
+[1740] 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 .SAMBA.E XAMPLE.C
+[1750] 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 74 72 61 OM....ad ministra
+[1760] 74 6F 72 00 00 00 01 00 00 00 02 00 00 00 17 4B tor..... .......K
+[1770] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[1780] 4C 45 2E 43 4F 4D 00 00 00 04 63 69 66 73 00 00 LE.COM.. ..cifs..
+[1790] 00 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 00 17 00 ..LOCALK TEST6...
+[17A0] 00 00 10 1D C8 5E 46 48 82 F9 29 DB C6 A6 F1 72 .....^FH ..)....r
+[17B0] 6D 8D E9 4D 99 4F 6A 4D 99 85 09 7D 44 0B 68 00 m..M.OjM ...}D.h.
+[17C0] 00 00 00 00 40 28 00 00 00 00 00 00 00 00 00 00 ....@(.. ........
+[17D0] 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 03 02 01 ....a... 0.......
+[17E0] 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[17F0] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 1C .EXAMPLE .COM..0.
+[1800] A0 03 02 01 01 A1 15 30 13 1B 04 63 69 66 73 1B .......0 ...cifs.
+[1810] 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 A3 82 03 AE .LOCALKT EST6....
+[1820] 30 82 03 AA A0 03 02 01 17 A1 03 02 01 02 A2 82 0....... ........
+[1830] 03 9C 04 82 03 98 66 D8 19 46 FA CB 73 2D CF 88 ......f. .F..s-..
+[1840] FD 4A EE 07 48 DA 0E BC 58 30 43 40 A4 9C 00 0F .J..H... X0C@....
+[1850] 3B 17 C1 2D F5 9C 3E D9 2F 1D CA 01 9B D7 2E EC ;..-..>. /.......
+[1860] D7 70 ED 8B 8B 1B 5E F2 4E EE DD 0F C0 8D 61 E5 .p....^. N.....a.
+[1870] D7 0A 56 00 32 B1 DB 91 37 29 0F 2F 85 EE A8 43 ..V.2... 7)./...C
+[1880] BA A5 B8 D4 19 74 33 F0 69 52 E1 58 98 83 D6 16 .....t3. iR.X....
+[1890] 0B 44 A9 63 9B D4 4E 6E A7 3E CD 9A 96 4D C4 96 .D.c..Nn .>...M..
+[18A0] F5 07 6D 29 B6 ED 2A 62 3D 53 22 33 D1 95 E9 DF ..m)..*b =S"3....
+[18B0] 74 4C 2A E2 29 AF 5B 69 B0 48 2D AD 94 FD A5 1D tL*.).[i .H-.....
+[18C0] 54 D8 E2 5E C1 68 6F BA 02 01 79 C3 C9 97 0B 76 T..^.ho. ..y....v
+[18D0] 66 45 E2 3B 10 17 95 40 46 E4 85 B9 87 BB CF CF fE.;...@ F.......
+[18E0] 19 8C 3A C0 EA 38 3B B9 E9 4B 05 89 E5 27 8C 62 ..:..8;. .K...'.b
+[18F0] 95 BC 0D 65 F0 D2 C0 5E BC 65 01 D5 0B CB 17 31 ...e...^ .e.....1
+[1900] 0F 06 49 4F A2 4A 70 77 DB BD 92 5B 37 5C EC 06 ..IO.Jpw ...[7\..
+[1910] DF C5 E2 31 C8 40 09 11 68 14 E7 7D CE 54 4F 52 ...1.@.. h..}.TOR
+[1920] 61 31 2C 1C 53 52 DB BE D8 95 39 EE 7D C6 CE C8 a1,.SR.. ..9.}...
+[1930] 22 95 92 97 97 3D 5E 66 0F AD DC C2 4E 2E 2B 9F "....=^f ....N.+.
+[1940] 63 20 30 DF B7 C1 D4 65 AA 6F 2D 10 24 07 20 8D c 0....e .o-.$. .
+[1950] 88 6E 4B 09 04 31 B6 A3 EB F7 37 32 0E 0C 73 C6 .nK..1.. ..72..s.
+[1960] F6 B8 4D D9 0C 4C 5B EC 10 6A 51 19 EA 3F FF 46 ..M..L[. .jQ..?.F
+[1970] E7 73 16 A7 1F 33 98 7C 9B AD 5A 23 A9 40 7C 0F .s...3.| ..Z#.@|.
+[1980] DF EE 0F AA C7 E8 63 07 98 3A 4A 0D 18 62 01 21 ......c. .:J..b.!
+[1990] B2 AE A5 69 B0 C1 15 51 BA 97 D2 C5 42 5B C5 30 ...i...Q ....B[.0
+[19A0] 38 18 A9 48 AB D7 FC A1 BC 9F 71 E7 EA 18 54 42 8..H.... ..q...TB
+[19B0] DA D6 A4 FC C1 DC F3 12 30 62 AC 98 E1 7D 2B 34 ........ 0b...}+4
+[19C0] 1E 52 4C 26 67 32 D9 44 1A 08 27 0E DA D0 FC 84 .RL&g2.D ..'.....
+[19D0] 66 35 81 D6 EB 98 46 6F 1E 47 E0 14 31 BE 47 80 f5....Fo .G..1.G.
+[19E0] 65 AA 0B 20 D6 33 36 3B 0D 40 2F 5A 2E 0E 01 BE e.. .36; .@/Z....
+[19F0] 00 EB 33 3E 4B 32 91 F4 22 96 E5 5F D4 D5 92 94 ..3>K2.. ".._....
+[1A00] CC 5B 59 6A 3E D2 FB A0 4F 99 C4 07 8B 6F 2B 14 .[Yj>... O....o+.
+[1A10] 37 CD 37 44 C0 1F 80 9C 43 46 F2 5E F4 FE D3 39 7.7D.... CF.^...9
+[1A20] 70 61 BE 72 5B 3A 8F 37 95 78 1E AB D9 E7 E9 DA pa.r[:.7 .x......
+[1A30] FC 47 09 81 A0 0D 62 E1 F9 34 36 D1 DB E6 98 D8 .G....b. .46.....
+[1A40] F4 3E 77 5A 4D E2 5F 20 70 3D 3D 5B 34 D9 FD A8 .>wZM._ p==[4...
+[1A50] 31 F7 D9 59 F7 A3 F0 66 F7 D9 AD 1C CD D5 85 33 1..Y...f .......3
+[1A60] A0 87 22 31 D4 F3 67 80 68 20 A2 90 72 7A 6F 64 .."1..g. h ..rzod
+[1A70] FD 68 82 9E 91 B8 E3 F7 6D 6C 38 74 F0 96 A2 F6 .h...... ml8t....
+[1A80] 25 D7 92 58 14 60 9F AE 01 4C 0C 09 67 3E 35 67 %..X.`.. .L..g>5g
+[1A90] 71 1E 2A 86 21 D3 60 61 98 16 94 67 0B 52 76 63 q.*.!.`a ...g.Rvc
+[1AA0] 93 BD A3 3B A9 F0 A2 6A B7 E6 0F 35 64 DA 6A EA ...;...j ...5d.j.
+[1AB0] 20 A6 3D 94 71 59 5E CB B2 D3 F9 4D FE 1B 4B D8 .=.qY^. ...M..K.
+[1AC0] 64 C8 3B 7A A8 E6 D2 D5 76 71 26 D4 5C DA 1A 55 d.;z.... vq&.\..U
+[1AD0] 17 F2 16 C9 2F 77 DB 95 19 48 A5 AC D0 C3 31 9C ..../w.. .H....1.
+[1AE0] 0A CC 1B 44 11 6B 7C 88 7A 5D CF 6E 12 DA EF C5 ...D.k|. z].n....
+[1AF0] C7 34 1D F4 CC EA 37 24 4B B3 0F C1 A3 F2 29 A0 .4....7$ K.....).
+[1B00] D8 93 39 C6 16 57 D5 BF 57 BF 6C 7E F7 90 E0 EB ..9..W.. W.l~....
+[1B10] A3 8B 07 56 9C EC 15 3E 21 DA A5 7C 00 3C F9 D2 ...V...> !..|.<..
+[1B20] A7 1C 6F 16 25 31 C5 28 A7 EA F3 47 31 50 DD E1 ..o.%1.( ...G1P..
+[1B30] ED 0A 93 DB 85 CC 6B 4B 2C 7F E8 F8 2D A9 6D 1D ......kK ,...-.m.
+[1B40] 0A 87 F2 10 8C 82 2F 9B D4 9B 92 8C 77 40 50 42 ....../. ....w@PB
+[1B50] 1E 42 C4 0A 4F E3 6C 6C DC 81 C4 1E BB F0 7D CF .B..O.ll ......}.
+[1B60] 3C 73 22 5B C3 1A 97 35 EE 3A CD 6D F3 68 A3 C5 <s"[...5 .:.m.h..
+[1B70] 65 7E E9 54 C0 E3 7D 6A 32 4C D1 3E D0 78 4B BF e~.T..}j 2L.>.xK.
+[1B80] 18 9F A5 25 4A 92 1E 6C 8F 01 D6 59 D7 CF 2E A0 ...%J..l ...Y....
+[1B90] CC 98 F6 75 28 2F F7 2A 70 28 A9 45 1F 75 C2 4E ...u(/.* p(.E.u.N
+[1BA0] 62 ED D8 C4 A0 8D 55 B2 84 1C A4 CE 87 EF 24 EE b.....U. ......$.
+[1BB0] BC CE 40 09 EB 05 0B D1 14 31 50 32 2F B6 A8 97 ..@..... .1P2/...
+[1BC0] 17 4B A7 95 01 50 6E 0E 23 49 9C 72 21 91 00 00 .K...Pn. #I.r!...
+[1BD0] 00 00 00 00 00 01 00 00 00 01 00 00 00 17 4B 54 ........ ......KT
+[1BE0] 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C EST.SAMB A.EXAMPL
+[1BF0] 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 E.COM... .adminis
+[1C00] 74 72 61 74 6F 72 00 00 00 01 00 00 00 02 00 00 trator.. ........
+[1C10] 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[1C20] 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 66 AMPLE.CO M....cif
+[1C30] 73 00 00 00 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 s....LOC ALKTEST6
+[1C40] 00 17 00 00 00 10 1D C8 5E 46 48 82 F9 29 DB C6 ........ ^FH..)..
+[1C50] A6 F1 72 6D 8D E9 4D 99 4F 6A 4D 99 85 09 7D 44 ..rm..M. OjM...}D
+[1C60] 0B 68 00 00 00 00 00 40 28 00 00 00 00 00 00 00 .h.....@ (.......
+[1C70] 00 00 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 .......a ...0....
+[1C80] 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 ........ KTEST.SA
+[1C90] 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 MBA.EXAM PLE.COM.
+[1CA0] 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 69 .0...... ..0...ci
+[1CB0] 66 73 1B 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 A3 fs..LOCA LKTEST6.
+[1CC0] 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 03 02 01 ...0.... ........
+[1CD0] 02 A2 82 03 9C 04 82 03 98 66 D8 19 46 FA CB 73 ........ .f..F..s
+[1CE0] 2D CF 88 FD 4A EE 07 48 DA 0E BC 58 30 43 40 A4 -...J..H ...X0C@.
+[1CF0] 9C 00 0F 3B 17 C1 2D F5 9C 3E D9 2F 1D CA 01 9B ...;..-. .>./....
+[1D00] D7 2E EC D7 70 ED 8B 8B 1B 5E F2 4E EE DD 0F C0 ....p... .^.N....
+[1D10] 8D 61 E5 D7 0A 56 00 32 B1 DB 91 37 29 0F 2F 85 .a...V.2 ...7)./.
+[1D20] EE A8 43 BA A5 B8 D4 19 74 33 F0 69 52 E1 58 98 ..C..... t3.iR.X.
+[1D30] 83 D6 16 0B 44 A9 63 9B D4 4E 6E A7 3E CD 9A 96 ....D.c. .Nn.>...
+[1D40] 4D C4 96 F5 07 6D 29 B6 ED 2A 62 3D 53 22 33 D1 M....m). .*b=S"3.
+[1D50] 95 E9 DF 74 4C 2A E2 29 AF 5B 69 B0 48 2D AD 94 ...tL*.) .[i.H-..
+[1D60] FD A5 1D 54 D8 E2 5E C1 68 6F BA 02 01 79 C3 C9 ...T..^. ho...y..
+[1D70] 97 0B 76 66 45 E2 3B 10 17 95 40 46 E4 85 B9 87 ..vfE.;. ..@F....
+[1D80] BB CF CF 19 8C 3A C0 EA 38 3B B9 E9 4B 05 89 E5 .....:.. 8;..K...
+[1D90] 27 8C 62 95 BC 0D 65 F0 D2 C0 5E BC 65 01 D5 0B '.b...e. ..^.e...
+[1DA0] CB 17 31 0F 06 49 4F A2 4A 70 77 DB BD 92 5B 37 ..1..IO. Jpw...[7
+[1DB0] 5C EC 06 DF C5 E2 31 C8 40 09 11 68 14 E7 7D CE \.....1. @..h..}.
+[1DC0] 54 4F 52 61 31 2C 1C 53 52 DB BE D8 95 39 EE 7D TORa1,.S R....9.}
+[1DD0] C6 CE C8 22 95 92 97 97 3D 5E 66 0F AD DC C2 4E ...".... =^f....N
+[1DE0] 2E 2B 9F 63 20 30 DF B7 C1 D4 65 AA 6F 2D 10 24 .+.c 0.. ..e.o-.$
+[1DF0] 07 20 8D 88 6E 4B 09 04 31 B6 A3 EB F7 37 32 0E . ..nK.. 1....72.
+[1E00] 0C 73 C6 F6 B8 4D D9 0C 4C 5B EC 10 6A 51 19 EA .s...M.. L[..jQ..
+[1E10] 3F FF 46 E7 73 16 A7 1F 33 98 7C 9B AD 5A 23 A9 ?.F.s... 3.|..Z#.
+[1E20] 40 7C 0F DF EE 0F AA C7 E8 63 07 98 3A 4A 0D 18 @|...... .c..:J..
+[1E30] 62 01 21 B2 AE A5 69 B0 C1 15 51 BA 97 D2 C5 42 b.!...i. ..Q....B
+[1E40] 5B C5 30 38 18 A9 48 AB D7 FC A1 BC 9F 71 E7 EA [.08..H. .....q..
+[1E50] 18 54 42 DA D6 A4 FC C1 DC F3 12 30 62 AC 98 E1 .TB..... ...0b...
+[1E60] 7D 2B 34 1E 52 4C 26 67 32 D9 44 1A 08 27 0E DA }+4.RL&g 2.D..'..
+[1E70] D0 FC 84 66 35 81 D6 EB 98 46 6F 1E 47 E0 14 31 ...f5... .Fo.G..1
+[1E80] BE 47 80 65 AA 0B 20 D6 33 36 3B 0D 40 2F 5A 2E .G.e.. . 36;.@/Z.
+[1E90] 0E 01 BE 00 EB 33 3E 4B 32 91 F4 22 96 E5 5F D4 .....3>K 2..".._.
+[1EA0] D5 92 94 CC 5B 59 6A 3E D2 FB A0 4F 99 C4 07 8B ....[Yj> ...O....
+[1EB0] 6F 2B 14 37 CD 37 44 C0 1F 80 9C 43 46 F2 5E F4 o+.7.7D. ...CF.^.
+[1EC0] FE D3 39 70 61 BE 72 5B 3A 8F 37 95 78 1E AB D9 ..9pa.r[ :.7.x...
+[1ED0] E7 E9 DA FC 47 09 81 A0 0D 62 E1 F9 34 36 D1 DB ....G... .b..46..
+[1EE0] E6 98 D8 F4 3E 77 5A 4D E2 5F 20 70 3D 3D 5B 34 ....>wZM ._ p==[4
+[1EF0] D9 FD A8 31 F7 D9 59 F7 A3 F0 66 F7 D9 AD 1C CD ...1..Y. ..f.....
+[1F00] D5 85 33 A0 87 22 31 D4 F3 67 80 68 20 A2 90 72 ..3.."1. .g.h ..r
+[1F10] 7A 6F 64 FD 68 82 9E 91 B8 E3 F7 6D 6C 38 74 F0 zod.h... ...ml8t.
+[1F20] 96 A2 F6 25 D7 92 58 14 60 9F AE 01 4C 0C 09 67 ...%..X. `...L..g
+[1F30] 3E 35 67 71 1E 2A 86 21 D3 60 61 98 16 94 67 0B >5gq.*.! .`a...g.
+[1F40] 52 76 63 93 BD A3 3B A9 F0 A2 6A B7 E6 0F 35 64 Rvc...;. ..j...5d
+[1F50] DA 6A EA 20 A6 3D 94 71 59 5E CB B2 D3 F9 4D FE .j. .=.q Y^....M.
+[1F60] 1B 4B D8 64 C8 3B 7A A8 E6 D2 D5 76 71 26 D4 5C .K.d.;z. ...vq&.\
+[1F70] DA 1A 55 17 F2 16 C9 2F 77 DB 95 19 48 A5 AC D0 ..U..../ w...H...
+[1F80] C3 31 9C 0A CC 1B 44 11 6B 7C 88 7A 5D CF 6E 12 .1....D. k|.z].n.
+[1F90] DA EF C5 C7 34 1D F4 CC EA 37 24 4B B3 0F C1 A3 ....4... .7$K....
+[1FA0] F2 29 A0 D8 93 39 C6 16 57 D5 BF 57 BF 6C 7E F7 .)...9.. W..W.l~.
+[1FB0] 90 E0 EB A3 8B 07 56 9C EC 15 3E 21 DA A5 7C 00 ......V. ..>!..|.
+[1FC0] 3C F9 D2 A7 1C 6F 16 25 31 C5 28 A7 EA F3 47 31 <....o.% 1.(...G1
+[1FD0] 50 DD E1 ED 0A 93 DB 85 CC 6B 4B 2C 7F E8 F8 2D P....... .kK,...-
+[1FE0] A9 6D 1D 0A 87 F2 10 8C 82 2F 9B D4 9B 92 8C 77 .m...... ./.....w
+[1FF0] 40 50 42 1E 42 C4 0A 4F E3 6C 6C DC 81 C4 1E BB @PB.B..O .ll.....
+[2000] F0 7D CF 3C 73 22 5B C3 1A 97 35 EE 3A CD 6D F3 .}.<s"[. ..5.:.m.
+[2010] 68 A3 C5 65 7E E9 54 C0 E3 7D 6A 32 4C D1 3E D0 h..e~.T. .}j2L.>.
+[2020] 78 4B BF 18 9F A5 25 4A 92 1E 6C 8F 01 D6 59 D7 xK....%J ..l...Y.
+[2030] CF 2E A0 CC 98 F6 75 28 2F F7 2A 70 28 A9 45 1F ......u( /.*p(.E.
+[2040] 75 C2 4E 62 ED D8 C4 A0 8D 55 B2 84 1C A4 CE 87 u.Nb.... .U......
+[2050] EF 24 EE BC CE 40 09 EB 05 0B D1 14 31 50 32 2F .$...@.. ....1P2/
+[2060] B6 A8 97 17 4B A7 95 01 50 6E 0E 23 49 9C 72 21 ....K... Pn.#I.r!
+[2070] 91 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ........ ........
+[2080] 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 .KTEST.S AMBA.EXA
+[2090] 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 MPLE.COM ....admi
+[20A0] 6E 69 73 74 72 61 74 6F 72 00 00 00 01 00 00 00 nistrato r.......
+[20B0] 02 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[20C0] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 .EXAMPLE .COM....
+[20D0] 68 6F 73 74 00 00 00 0B 6C 6F 63 61 6C 6B 74 65 host.... localkte
+[20E0] 73 74 36 00 17 00 00 00 10 72 47 04 38 B6 E6 F0 st6..... .rG.8...
+[20F0] 44 9E 9F 27 66 E1 69 9C 9A 4D 99 4F 6A 4D 99 90 D..'f.i. .M.OjM..
+[2100] F5 7D 44 0B 68 00 00 00 00 00 40 28 00 00 00 00 .}D.h... ..@(....
+[2110] 00 00 00 00 00 00 00 00 03 FA 61 82 03 F6 30 82 ........ ..a...0.
+[2120] 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 ........ ...KTEST
+[2130] 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 .SAMBA.E XAMPLE.C
+[2140] 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B OM..0... .....0..
+[2150] 04 68 6F 73 74 1B 0B 6C 6F 63 61 6C 6B 74 65 73 .host..l ocalktes
+[2160] 74 36 A3 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 t6....0. ........
+[2170] 03 02 01 02 A2 82 03 9C 04 82 03 98 58 95 95 EB ........ ....X...
+[2180] CB 8F 68 D4 77 43 0F 3B 44 B4 15 DA 40 6D FD E9 ..h.wC.; D...@m..
+[2190] 85 D3 2F CD B5 1E 96 CD F6 E9 67 91 36 08 9E B4 ../..... ..g.6...
+[21A0] B3 47 70 7A B3 4E 82 5A 4F 8E 4B F5 8D 04 E4 5C .Gpz.N.Z O.K....\
+[21B0] C4 D8 0C AF 08 25 F9 C1 64 B2 3A 35 26 E9 B2 72 .....%.. d.:5&..r
+[21C0] 66 B5 E9 81 FC BE 12 1B CC 8A A5 82 31 F6 7F C3 f....... ....1...
+[21D0] 5A 19 A3 31 F2 99 14 1E 64 E4 41 E8 C7 C3 F3 DF Z..1.... d.A.....
+[21E0] F5 65 7D B0 9F DC 5D 25 1D 1A A8 EA AA 88 6D F4 .e}...]% ......m.
+[21F0] 7C 25 9F 53 F6 A6 8F B1 24 AF 98 FE 53 7B 35 3C |%.S.... $...S{5<
+[2200] DB EC 7F 09 74 E9 C4 8D 20 B4 47 08 0E 32 B8 C9 ....t... .G..2..
+[2210] 45 27 12 F9 8E F5 D6 C2 DD 1A 96 0E 68 5F 39 65 E'...... ....h_9e
+[2220] 72 C7 BD 8E 04 0E 13 E1 03 27 AC 50 80 76 E6 7A r....... .'.P.v.z
+[2230] 8E F4 C2 72 4F 68 B3 34 00 A9 54 41 DA FD 96 94 ...rOh.4 ..TA....
+[2240] 29 A1 59 15 2F DB 6C 94 85 49 C5 D0 6D 48 B0 C4 ).Y./.l. .I..mH..
+[2250] 65 D0 95 1D DB 3D 25 D0 75 50 D4 CF FA 2F 71 57 e....=%. uP.../qW
+[2260] BD 6C 1C 59 E1 C3 5B C7 24 95 FF B0 20 EF 6A DB .l.Y..[. $... .j.
+[2270] 79 87 67 91 94 E9 16 E2 BB 74 7A 08 E1 6A 36 5F y.g..... .tz..j6_
+[2280] DF 11 AB 35 9B 3E 32 48 83 89 41 4E 06 BF F9 BB ...5.>2H ..AN....
+[2290] EC E4 D7 6D 77 C4 55 22 DF F7 91 4D CB C5 01 A5 ...mw.U" ...M....
+[22A0] BA 2D 1E 92 76 04 E8 02 2F 5E AF 1C B3 B7 A6 FB .-..v... /^......
+[22B0] 3A 9F D9 7C 6D DA B4 8F 31 00 A5 30 F2 76 72 9B :..|m... 1..0.vr.
+[22C0] 62 97 E0 56 E5 E4 C7 6B 8B FC 84 75 57 66 6E D7 b..V...k ...uWfn.
+[22D0] B7 41 6F 61 F4 5B 0F 87 68 F6 54 02 26 1B 1F B7 .Aoa.[.. h.T.&...
+[22E0] 60 D6 E7 FA 4F C7 DB 35 58 EC 13 21 D4 C6 A1 27 `...O..5 X..!...'
+[22F0] BA E7 82 DF 29 FB 9D 5D E8 35 28 C9 9C 4E D7 BE ....)..] .5(..N..
+[2300] 2F 6D F1 E8 0B 5A 74 C9 93 9F AD 42 24 4B B7 3B /m...Zt. ...B$K.;
+[2310] 38 2A 11 CF F0 BD 85 40 48 D8 9D E7 6B 65 70 42 8*.....@ H...kepB
+[2320] 60 DA 9B 65 CB C8 C5 D7 40 3A 12 DC 64 AF 82 54 `..e.... @:..d..T
+[2330] 34 05 38 4F C6 FB 38 E2 73 A9 89 B7 FC 33 15 85 4.8O..8. s....3..
+[2340] 9E CA E9 E0 89 18 18 84 02 65 B4 74 5B D4 A1 6F ........ .e.t[..o
+[2350] 5F 79 20 CB D7 36 C8 6D 5B 1E 5E 0C 82 16 9F CC _y ..6.m [.^.....
+[2360] 5A 1E 57 C1 B6 94 51 87 A1 3D 12 D4 8B FE 0F 93 Z.W...Q. .=......
+[2370] ED 53 A3 F4 88 3C 35 05 89 FE AF 0B 36 62 E3 2F .S...<5. ....6b./
+[2380] 5C 4A 0E 07 67 39 A3 8E C0 45 07 7F 73 32 BC DE \J..g9.. .E..s2..
+[2390] 2D 00 8B 47 79 3D 1C A1 90 AE B6 8F 83 B2 1B 31 -..Gy=.. .......1
+[23A0] EE E4 F2 C5 C1 4A E2 4A 2F 28 F0 AA 19 43 6A 14 .....J.J /(...Cj.
+[23B0] B1 42 61 90 34 2E EE 3D 16 9F 5D 9F 7A A2 01 7A .Ba.4..= ..].z..z
+[23C0] 4B 96 FA 4D C9 85 1A 75 27 B7 6B FD 4D 7D 9C 65 K..M...u '.k.M}.e
+[23D0] 97 DB 05 CC 76 68 EA 05 5D 5D BB BD 51 4B 5B F2 ....vh.. ]]..QK[.
+[23E0] 48 59 BD 1E AD 56 D4 69 A5 75 CD ED EC B1 3E AB HY...V.i .u....>.
+[23F0] FA B7 F8 8D 4F BE 95 63 38 1C 4C 70 26 C4 3A 21 ....O..c 8.Lp&.:!
+[2400] 80 61 05 3A D4 E2 28 2C 85 01 5A DA FC 10 60 F3 .a.:..(, ..Z...`.
+[2410] 74 0C FD DB 2F 5B 25 4B 14 E4 7D 8A DB 85 12 D2 t.../[%K ..}.....
+[2420] D7 69 CD B5 B1 93 CE E5 E6 4D 57 D3 C2 D3 2E A0 .i...... .MW.....
+[2430] 08 37 09 CD 19 99 09 FA 33 68 4A E0 92 46 21 0C .7...... 3hJ..F!.
+[2440] 99 9F DA 05 15 20 8B 3D 7C 7B CA D6 81 AC AA 83 ..... .= |{......
+[2450] 48 C8 24 4C C8 FC A5 14 2C BC 49 1A 1C 49 61 1D H.$L.... ,.I..Ia.
+[2460] 24 86 42 B1 37 6A C8 3A AC 18 CC C0 50 84 12 48 $.B.7j.: ....P..H
+[2470] 8B 29 0A 49 26 A4 E2 B9 E5 96 E7 37 C3 DE 4C 23 .).I&... ...7..L#
+[2480] D2 D4 62 14 8F 1E 72 39 CF 03 BC A3 00 C7 63 51 ..b...r9 ......cQ
+[2490] A9 6B E4 3E B2 65 A1 A2 BB EC 06 41 85 50 22 02 .k.>.e.. ...A.P".
+[24A0] 46 2F 72 2B 32 1A A4 2D 85 94 02 47 69 8D AD 6D F/r+2..- ...Gi..m
+[24B0] 66 AB D4 E4 29 C8 C7 DA F4 18 31 2A DF 50 6A 05 f...)... ..1*.Pj.
+[24C0] D6 47 26 C4 F9 87 0F 35 24 6E 72 D6 23 7D 3A 94 .G&....5 $nr.#}:.
+[24D0] 14 8D E8 57 AA BA D7 CF A9 2D E7 4C 10 7C D8 0D ...W.... .-.L.|..
+[24E0] 51 30 1F E1 FB E5 E2 6C EE AA 65 2F D8 22 05 67 Q0.....l ..e/.".g
+[24F0] 87 4D 4D D2 11 3D B4 1E AA 20 3F 76 E3 94 93 6D .MM..=.. . ?v...m
+[2500] AC 10 05 AF 09 BD 67 86 C5 83 93 D6 1C D3 81 D9 ......g. ........
+[2510] B1 3B E1 76 00 00 00 00 00 00 00 01 00 00 00 01 .;.v.... ........
+[2520] 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E ....KTES T.SAMBA.
+[2530] 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 EXAMPLE. COM....a
+[2540] 64 6D 69 6E 69 73 74 72 61 74 6F 72 00 00 00 01 dministr ator....
+[2550] 00 00 00 02 00 00 00 17 4B 54 45 53 54 2E 53 41 ........ KTEST.SA
+[2560] 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 MBA.EXAM PLE.COM.
+[2570] 00 00 04 68 6F 73 74 00 00 00 0B 4C 4F 43 41 4C ...host. ...LOCAL
+[2580] 4B 54 45 53 54 36 00 17 00 00 00 10 55 6E 3E FC KTEST6.. ....Un>.
+[2590] E2 F4 40 51 19 E6 6E EB 23 4C 48 8E 4D 99 4F 6A ..@Q..n. #LH.M.Oj
+[25A0] 4D 99 90 FC 7D 44 0B 68 00 00 00 00 00 40 28 00 M...}D.h .....@(.
+[25B0] 00 00 00 00 00 00 00 00 00 00 00 03 FA 61 82 03 ........ .....a..
+[25C0] F6 30 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 .0...... ......KT
+[25D0] 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C EST.SAMB A.EXAMPL
+[25E0] 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 E.COM..0 ........
+[25F0] 30 13 1B 04 68 6F 73 74 1B 0B 4C 4F 43 41 4C 4B 0...host ..LOCALK
+[2600] 54 45 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 02 TEST6... .0......
+[2610] 01 17 A1 03 02 01 02 A2 82 03 9C 04 82 03 98 6E ........ .......n
+[2620] 87 B7 7B 3A 7E EF 4A 1B 29 C9 E3 C4 1F 42 4F 0E ..{:~.J. )....BO.
+[2630] C8 AC AC 4E A2 77 1D DA 93 37 F1 AF DA A3 75 2D ...N.w.. .7....u-
+[2640] 12 8B 40 34 23 0E 8E A9 90 58 46 42 42 39 31 D6 ..@4#... .XFBB91.
+[2650] 03 9E 5D 81 D9 E8 F6 08 2B D9 96 88 8A 2F F1 CC ..]..... +..../..
+[2660] F2 EA 9E 9A 4B 31 B6 04 2D 3D 4C 7F 92 DE 3B 04 ....K1.. -=L...;.
+[2670] 19 EE 28 D0 83 81 C3 46 CD 74 23 4C 14 34 DE 62 ..(....F .t#L.4.b
+[2680] 0A AC E5 12 16 75 E9 A8 4B 32 78 CC 8D AE A2 E5 .....u.. K2x.....
+[2690] 6D E8 09 70 76 52 F5 E5 18 F7 E7 91 15 6A 69 AB m..pvR.. .....ji.
+[26A0] B8 62 DD 80 F5 28 6D DF ED 10 DA AC FB 92 27 CF .b...(m. ......'.
+[26B0] 98 B5 77 9D A5 96 E6 9A CC B9 C3 91 78 22 35 9C ..w..... ....x"5.
+[26C0] A1 13 A3 20 28 D1 16 E5 3E 4A 85 1E 12 0B CA 4D ... (... >J.....M
+[26D0] C6 C8 03 C8 28 2C D8 29 5D 9A 76 4A 92 13 43 56 ....(,.) ].vJ..CV
+[26E0] AF F7 C1 71 25 72 5C 38 75 1C 07 F1 5E 86 05 72 ...q%r\8 u...^..r
+[26F0] 6F 69 95 42 B6 F2 DA A9 91 06 9F B9 54 20 33 A5 oi.B.... ....T 3.
+[2700] 31 60 3B 54 DC 3A 95 34 96 26 07 52 6B 0E 1D 3B 1`;T.:.4 .&.Rk..;
+[2710] D9 F8 48 20 AC CD 05 3B 99 F8 EE DB 83 28 CD C7 ..H ...; .....(..
+[2720] 2F 45 00 7E 2F 0A 65 7A D1 9E 95 4B EE C3 34 93 /E.~/.ez ...K..4.
+[2730] A8 C7 DF 03 8B 14 D0 FC CE 56 90 AC EE 93 C5 D3 ........ .V......
+[2740] F7 12 24 69 0B 20 8D A2 65 87 55 26 2A F9 9A 88 ..$i. .. e.U&*...
+[2750] D7 0D 86 61 D6 92 B6 FE E5 D1 66 F9 1F 9D F4 04 ...a.... ..f.....
+[2760] 48 A6 39 BC 54 20 EA 10 21 E9 6D 30 46 1D C2 1C H.9.T .. !.m0F...
+[2770] A4 E8 B4 63 85 37 27 25 80 52 41 60 C7 A1 32 21 ...c.7'% .RA`..2!
+[2780] 43 90 02 E6 5F 5A E9 4E AF F9 B5 13 BD 42 BD A3 C..._Z.N .....B..
+[2790] A5 4D 10 45 83 4D 92 18 1F C9 CF FB 84 29 89 23 .M.E.M.. .....).#
+[27A0] AC 71 4B 89 1B 52 E5 06 8C 3E 7C 88 CB D3 B3 CF .qK..R.. .>|.....
+[27B0] B9 7A 67 D6 24 F4 AC 00 A6 AD 91 30 9A 95 53 F1 .zg.$... ...0..S.
+[27C0] 48 06 A6 39 DB CF DC 9D C9 55 76 26 5E C1 DB 5D H..9.... .Uv&^..]
+[27D0] B3 5B 3E AE 1A A0 10 BA 82 21 83 44 02 E0 99 33 .[>..... .!.D...3
+[27E0] 40 BA 29 9E 28 E5 73 4C 23 94 A2 4F BF 07 ED 4F @.).(.sL #..O...O
+[27F0] 7C 45 9B 30 C8 41 6B 0A 55 13 6E F5 AD 7A 0C B2 |E.0.Ak. U.n..z..
+[2800] EA FF D0 06 13 4D F3 24 82 7F F6 51 2F 4A 4F 0D .....M.$ ...Q/JO.
+[2810] 37 F8 14 6B E9 E4 82 BB 3A 75 63 63 12 E8 78 6F 7..k.... :ucc..xo
+[2820] 6F FC 6C D3 4B A6 F1 CC 2A F1 7D EB 82 26 2F D0 o.l.K... *.}..&/.
+[2830] A1 8B 3E 9A 71 D7 91 D3 08 E6 FD 62 1B 84 13 2D ..>.q... ...b...-
+[2840] 8E A0 A0 C3 85 78 2F 0D F8 E7 10 FC CB 05 A7 B9 .....x/. ........
+[2850] 9A 33 90 B5 9B 26 E3 23 98 B0 91 4B EB 32 37 D6 .3...&.# ...K.27.
+[2860] F4 ED 61 08 D8 75 CC 03 83 2C 3C CF 21 63 9C F6 ..a..u.. .,<.!c..
+[2870] AF 5B 4F 12 07 74 17 CD 98 BB E7 5E C7 17 2D C4 .[O..t.. ...^..-.
+[2880] 87 A4 74 6D 5E CE DB A3 01 B9 AD 20 73 38 78 22 ..tm^... ... s8x"
+[2890] 3D 45 F5 51 77 C6 47 63 45 61 81 D9 FF 31 90 C4 =E.Qw.Gc Ea...1..
+[28A0] 6F 5A F8 FE 6A 56 5B D4 EE EC 49 C7 A7 51 AE 5C oZ..jV[. ..I..Q.\
+[28B0] 85 53 70 3D 1A 49 83 59 CF 65 58 B3 48 7E 04 9E .Sp=.I.Y .eX.H~..
+[28C0] C7 64 8A 05 73 E3 DC 1A 65 5D 4F 41 01 56 73 90 .d..s... e]OA.Vs.
+[28D0] 61 F3 84 1F FF CF 46 B2 06 46 56 97 93 B9 DB 32 a.....F. .FV....2
+[28E0] 2A 64 8A 48 02 05 84 E9 FA 76 8B 94 96 89 A0 73 *d.H.... .v.....s
+[28F0] 20 75 4D 52 1D 23 13 D1 83 D7 5D 59 23 6A 87 C1 uMR.#.. ..]Y#j..
+[2900] 09 3E 01 3A 28 65 42 8C 35 F1 91 EA 6A 1F 83 0D .>.:(eB. 5...j...
+[2910] 8F 57 69 81 D4 A2 D2 EA 0C BF AF 95 A3 F4 90 15 .Wi..... ........
+[2920] 61 34 F2 6C 8B D0 DA B5 1E 43 AC CE C7 8A 1B 2B a4.l.... .C.....+
+[2930] 29 2B 89 1C C5 53 C8 04 F7 1E 46 72 F3 A8 CE F7 )+...S.. ..Fr....
+[2940] 59 76 55 E7 53 1C A2 9F D8 23 F7 EA 71 B0 74 83 YvU.S... .#..q.t.
+[2950] 71 95 3E DC A6 FA 2D A4 42 13 93 8B 2B FA A2 70 q.>...-. B...+..p
+[2960] 25 21 2D F6 E1 26 56 DF 58 79 25 16 E8 C9 03 EC %!-..&V. Xy%.....
+[2970] 72 5F 35 CF 59 6B E1 AD 85 85 7B AB 78 F2 0D AC r_5.Yk.. ..{.x...
+[2980] AB 89 F2 DA 85 E7 DE 09 77 99 EC 7C F3 97 1F 71 ........ w..|...q
+[2990] 3C DB 09 44 7A 3C 69 E5 03 B0 6D 4D 3B 6B 4C D5 <..Dz<i. ..mM;kL.
+[29A0] AB 52 2F 6F 81 2B 51 5B D2 66 44 1E B7 66 5D 7F .R/o.+Q[ .fD..f].
+[29B0] 09 6A 92 27 27 62 08 00 00 00 00 .j.''b.. ...
+push returned Success
+pull returned Success
+ CCACHE: struct CCACHE
+ pvno : 0x05 (5)
+ version : 0x04 (4)
+ optional_header : union OPTIONAL_HEADER(case 0x4)
+ v4header: struct V4HEADER
+ v4tags: struct V4TAGS
+ tag: struct V4TAG
+ tag : 0x0001 (1)
+ field : union FIELD(case 0x1)
+ deltatime_tag: struct DELTATIME_TAG
+ kdc_sec_offset : 0
+ kdc_usec_offset : 0
+ further_tags : DATA_BLOB length=0
+ principal: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ cred: struct CREDENTIAL
+ client: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ server: struct PRINCIPAL
+ name_type : 0x00000000 (0)
+ component_count : 0x00000002 (2)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(2)
+ components : 'krbtgt'
+ components : 'KTEST.SAMBA.EXAMPLE.COM'
+ keyblock: struct KEYBLOCK
+ enctype : 0x0017 (23)
+ data : DATA_BLOB length=16
+[0000] 8B 94 0B 31 51 5B F7 A7 15 E9 EE D7 D7 0C 8C 90 ...1Q[.. ........
+ authtime : 0x4d994f6a (1301892970)
+ starttime : 0x4d994f6a (1301892970)
+ endtime : 0x7d440b68 (2101611368)
+ renew_till : 0x7d440b68 (2101611368)
+ is_skey : 0x00 (0)
+ ticket_flags : 0x40e00000 (1088421888)
+ addresses: struct ADDRESSES
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ authdata: struct AUTHDATA
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ ticket : DATA_BLOB length=1032
+[0000] 61 82 04 04 30 82 04 00 A0 03 02 01 05 A1 19 1B a...0... ........
+[0010] 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 .KTEST.S AMBA.EXA
+[0020] 4D 50 4C 45 2E 43 4F 4D A2 2C 30 2A A0 03 02 01 MPLE.COM .,0*....
+[0030] 00 A1 23 30 21 1B 06 6B 72 62 74 67 74 1B 17 4B ..#0!..k rbtgt..K
+[0040] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0050] 4C 45 2E 43 4F 4D A3 82 03 AE 30 82 03 AA A0 03 LE.COM.. ..0.....
+[0060] 02 01 17 A1 03 02 01 01 A2 82 03 9C 04 82 03 98 ........ ........
+[0070] 80 66 8F CF AB 24 9D C8 76 E4 28 F5 25 6B 73 B2 .f...$.. v.(.%ks.
+[0080] 4B 94 ED 09 10 29 05 C4 C0 B8 B9 33 FA C4 46 AB K....).. ...3..F.
+[0090] F4 B5 9E 5B 07 54 D6 58 1D B8 CA 04 41 A6 33 A6 ...[.T.X ....A.3.
+[00A0] 67 9D EB 83 70 65 A9 2D 65 A5 19 8C 55 2A 0F FC g...pe.- e...U*..
+[00B0] 1B BB 7A BD 86 C0 32 06 F2 2F 0A A5 93 E7 D1 1E ..z...2. ./......
+[00C0] 16 C4 27 DD 1F A7 61 03 FF 05 81 EF 49 B7 25 A3 ..'...a. ....I.%.
+[00D0] 6E EA E6 E8 15 E3 10 AF A3 F1 21 B3 D9 C0 67 2F n....... ..!...g/
+[00E0] 0C 0C B7 42 D6 9A 34 8E D4 5E 55 C2 FE 62 03 37 ...B..4. .^U..b.7
+[00F0] A5 58 9B 43 E7 26 E3 71 B2 E5 F1 91 B4 23 8F AC .X.C.&.q .....#..
+[0100] 7A 31 3C 4E B4 94 E4 81 36 98 71 3B 98 7B B7 AB z1<N.... 6.q;.{..
+[0110] D5 AA D3 34 2A 3B C8 D7 61 EE 60 F9 68 9C A0 56 ...4*;.. a.`.h..V
+[0120] 51 E7 85 81 DE EF B9 9F 8B 4A 07 E1 05 93 08 5A Q....... .J.....Z
+[0130] AE B3 92 A5 17 40 B1 1C 42 A9 E4 AD 3C B4 4E D3 .....@.. B...<.N.
+[0140] BE 68 C4 0C 81 C0 AB 2D 3E 81 09 BD 16 82 EB C5 .h.....- >.......
+[0150] 1A 69 EE 8C 4E A4 D8 55 A5 0B 23 0F D0 89 48 C4 .i..N..U ..#...H.
+[0160] 51 FE 32 FD CC F6 71 E1 95 2D CC 1D 0A 0C 8A A2 Q.2...q. .-......
+[0170] 69 58 3B 65 88 53 EC D0 2E E1 C6 CC 6B BC 09 E5 iX;e.S.. ....k...
+[0180] B9 15 27 8B E4 B2 24 18 61 42 BB 8B 09 1B 8A 7B ..'...$. aB.....{
+[0190] 13 D8 51 E1 0B 79 12 48 DE A9 54 04 00 6D DD E6 ..Q..y.H ..T..m..
+[01A0] 5E 03 91 FF C7 6D 0B 7C 91 44 E1 0F C0 7E 32 34 ^....m.| .D...~24
+[01B0] 82 86 94 F7 CD 53 EC 52 38 18 AA ED FF FC 5C 01 .....S.R 8.....\.
+[01C0] D2 EE 99 45 8E 5B E6 B3 46 B0 F6 3B 22 29 EC 11 ...E.[.. F..;")..
+[01D0] 30 6A F6 A1 1F 9E AE 71 E3 A6 E7 3F F3 7D 2B 75 0j.....q ...?.}+u
+[01E0] 70 4D 63 47 5C 18 2C 8B B1 1A 69 B6 C5 46 01 17 pMcG\.,. ..i..F..
+[01F0] 8E 64 3D 47 88 20 1C AA D7 60 32 28 11 60 EA 28 .d=G. .. .`2(.`.(
+[0200] 66 99 4C B1 2A 28 96 BF 18 2A 3E F4 D6 84 E5 A0 f.L.*(.. .*>.....
+[0210] F4 4E E7 F9 54 95 22 96 2A 87 01 CC 3E A7 FF 42 .N..T.". *...>..B
+[0220] 6A A4 4A 3A B9 24 10 65 99 53 58 2A 4E 72 E7 1F j.J:.$.e .SX*Nr..
+[0230] 82 BC BD 3C 6C 9D 33 3A CE C6 6E 72 A2 81 B3 84 ...<l.3: ..nr....
+[0240] 82 DF 3C 1F 76 E5 B8 08 AD 0A 6C 7D 7B D5 0C 46 ..<.v... ..l}{..F
+[0250] 69 A4 F4 E9 9E 3D D7 2D E1 43 D1 7A 52 16 75 56 i....=.- .C.zR.uV
+[0260] 54 83 D5 2A 2F A7 D2 CB 48 FE FF DB AE 46 F2 5B T..*/... H....F.[
+[0270] F4 52 BE C8 5E B1 04 95 52 35 3E 92 E0 02 F7 85 .R..^... R5>.....
+[0280] AB F0 D0 93 08 42 E5 37 19 24 4E C1 AF FC 92 A9 .....B.7 .$N.....
+[0290] B1 27 B1 9A 2A 62 34 F1 DC C0 6B 83 AE C3 74 E8 .'..*b4. ..k...t.
+[02A0] A3 05 DD 82 DD A3 D7 90 A8 E3 9C EB 64 16 23 06 ........ ....d.#.
+[02B0] 5D FB E4 35 7C 22 29 78 E3 3B 75 92 91 0C 9D A1 ]..5|")x .;u.....
+[02C0] 87 7C 2E 82 AE 49 9D 4A 50 A9 C2 D5 85 B0 16 5D .|...I.J P......]
+[02D0] A2 CD B0 DD 29 3F 6F 66 C9 C1 9F 5C F0 B6 FC D2 ....)?of ...\....
+[02E0] 52 BE 7B F0 1F 26 AF 8A FC C3 A6 24 8C C0 10 06 R.{..&.. ...$....
+[02F0] 73 1E 17 9E 6E 6F 32 44 6A DF 82 5D D0 6B 74 CE s...no2D j..].kt.
+[0300] 58 0B 4C 7B EB A1 13 44 B1 3E D8 F8 BA F4 4E 55 X.L{...D .>....NU
+[0310] 71 3D C1 09 D9 E7 97 9A 14 5C 54 7E 57 81 5F 6B q=...... .\T~W._k
+[0320] 30 BE 9A E1 98 29 47 D4 C0 8F 63 0A F8 27 1F CE 0....)G. ..c..'..
+[0330] ED D9 BB 7B 12 24 D0 34 2A 7C F0 F7 77 F4 F1 1D ...{.$.4 *|..w...
+[0340] 4C 5D 75 2D 6B 0D 80 35 82 CC D8 7A 6B FA A0 55 L]u-k..5 ...zk..U
+[0350] 34 CD 87 15 61 38 78 D4 69 0F AA 72 D6 AC FA 99 4...a8x. i..r....
+[0360] BC 70 39 27 A7 25 2E 1B 6F 36 01 FD E9 B4 9A 79 .p9'.%.. o6.....y
+[0370] 6C 19 DD A6 8C 78 B0 40 92 60 58 F0 28 AD 08 78 l....x.@ .`X.(..x
+[0380] 4A 29 06 2C 82 2B 1A E3 91 0B 5F EE D6 B8 66 47 J).,.+.. .._...fG
+[0390] 31 9B A3 DF 9F 79 D7 BB 0E 2C FA 0E C9 66 84 8D 1....y.. .,...f..
+[03A0] FF BA BB 21 27 9E AD 86 84 55 8D 4C 4C 47 D9 5F ...!'... .U.LLG._
+[03B0] B2 7D 26 CA B7 49 3C 9D 1B 67 71 11 3A 8A EB EA .}&..I<. .gq.:...
+[03C0] 0F 15 EB F0 1E 46 F7 A4 34 04 D7 E3 50 67 47 D3 .....F.. 4...PgG.
+[03D0] 66 21 17 77 51 A7 1F 1D 84 3B 7C B1 5D 4E B8 D4 f!.wQ... .;|.]N..
+[03E0] F9 C5 75 06 AA 19 45 1C E9 06 9E AD 23 26 6B 10 ..u...E. ....#&k.
+[03F0] 53 A0 36 D3 58 9F 5E 8C CB A5 F6 BC C9 30 3C BC S.6.X.^. .....0<.
+[0400] AD FF 7C 92 F0 C6 9A 02 ..|.....
+ second_ticket : DATA_BLOB length=0
+ further_creds : DATA_BLOB length=10683
+[0000] 00 00 00 01 00 00 00 01 00 00 00 17 4B 54 45 53 ........ ....KTES
+[0010] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0020] 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 74 72 COM....a dministr
+[0030] 61 74 6F 72 00 00 00 01 00 00 00 02 00 00 00 17 ator.... ........
+[0040] 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D KTEST.SA MBA.EXAM
+[0050] 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 66 73 00 PLE.COM. ...cifs.
+[0060] 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 00 17 ...local ktest6..
+[0070] 00 00 00 10 00 6E A1 B2 31 6D 48 C7 90 72 3A 0C .....n.. 1mH..r:.
+[0080] 4B 8B 83 8C 4D 99 4F 6A 4D 99 50 85 7D 44 0B 68 K...M.Oj M.P.}D.h
+[0090] 00 00 00 00 00 40 28 00 00 00 00 00 00 00 00 00 .....@(. ........
+[00A0] 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 03 02 .....a.. .0......
+[00B0] 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[00C0] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 A.EXAMPL E.COM..0
+[00D0] 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 69 66 73 ........ 0...cifs
+[00E0] 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 A3 82 03 ..localk test6...
+[00F0] AE 30 82 03 AA A0 03 02 01 17 A1 03 02 01 02 A2 .0...... ........
+[0100] 82 03 9C 04 82 03 98 C6 BB 64 A8 31 00 FC 5E 51 ........ .d.1..^Q
+[0110] 3C 87 F8 34 47 3B D0 6F 6F FD 9E A6 91 12 74 2D <..4G;.o o.....t-
+[0120] 44 BB AA 91 A0 2D 46 3E 9E FB FB C4 FB F1 15 FD D....-F> ........
+[0130] BB DA EE 06 A9 20 6A 38 DC 46 06 27 D9 A2 9D 2D ..... j8 .F.'...-
+[0140] 1F FD 0D 7D 8A BB 0A 7C E8 47 17 BC 7B 70 E4 51 ...}...| .G..{p.Q
+[0150] 6A BA 51 68 62 28 4A 1E 51 D1 0D CD 02 55 75 44 j.Qhb(J. Q....UuD
+[0160] 8A B9 C2 84 F4 17 34 92 9B 31 85 9E 43 C1 0C 3A ......4. .1..C..:
+[0170] B2 69 7F 20 1A 18 1F 65 4F C0 20 C9 B5 AF E1 61 .i. ...e O. ....a
+[0180] 8C 90 10 63 26 A6 5D 05 3C CD 29 BB 7B 74 D5 8F ...c&.]. <.).{t..
+[0190] 2C 7F 4B E8 84 24 57 37 8A C6 F7 91 FD 22 9A A5 ,.K..$W7 ....."..
+[01A0] 0D E9 4A 78 93 36 FC A8 8C 8A 27 8A C6 28 4B 7B ..Jx.6.. ..'..(K{
+[01B0] DA 11 42 BC 09 10 81 82 14 0F 9C B8 48 26 91 78 ..B..... ....H&.x
+[01C0] A8 DD 97 6C 24 A1 D2 E8 85 19 B3 D3 85 4D 38 C7 ...l$... .....M8.
+[01D0] 7D 49 55 8E 85 46 E1 EE 7B BA 11 62 63 53 C5 16 }IU..F.. {..bcS..
+[01E0] 4A 0C 1C 99 7C 0E FB 45 1D B4 98 58 67 7E 40 65 J...|..E ...Xg~@e
+[01F0] 4B 48 E2 89 9C 8B C2 B8 39 D1 04 C0 A8 56 E8 A1 KH...... 9....V..
+[0200] 04 7A 7A C9 60 18 A0 29 E2 DC 82 4C 8F 18 CE 2F .zz.`..) ...L.../
+[0210] 14 F0 18 5B 6C FF 85 45 88 73 CB A4 55 08 FC BF ...[l..E .s..U...
+[0220] C7 9F 51 0A DB 2C C1 E3 3C DD F6 F0 A3 2D F1 3B ..Q..,.. <....-.;
+[0230] A0 12 1D FC 2A 67 F5 1A 7F E5 7C 6C FB 8A 18 BD ....*g.. ..|l....
+[0240] D1 5D E5 5E 68 30 AA 58 9E 10 13 E0 26 7E 7D C4 .].^h0.X ....&~}.
+[0250] E1 A5 B6 86 0F 1C 0F 13 A4 5E 5E 6A ED 42 79 31 ........ .^^j.By1
+[0260] BB B3 5F 3A 3F DD CB 63 82 FB 06 AE 12 36 C9 1E .._:?..c .....6..
+[0270] 06 7D 41 82 2E D2 FA 26 EC 17 50 5E D0 DE 26 85 .}A....& ..P^..&.
+[0280] 30 71 BC 45 3B DA 2E 08 8D B2 2A 3C E0 79 8F 77 0q.E;... ..*<.y.w
+[0290] 4C 01 69 7A 09 C7 88 E1 D1 DC FF 78 DB 25 7B B1 L.iz.... ...x.%{.
+[02A0] 3C BB 22 27 80 0D 75 96 18 B6 40 95 6D C8 AB 04 <."'..u. ..@.m...
+[02B0] 05 41 A1 C4 25 71 C4 53 3A A6 9C B2 4D E6 15 2C .A..%q.S :...M..,
+[02C0] B2 47 6C DA A8 7D CC A3 89 8B C9 1E 21 F5 E9 B2 .Gl..}.. ....!...
+[02D0] 42 95 68 28 AF C6 37 22 BA 30 8D 53 FA 08 0D CE B.h(..7" .0.S....
+[02E0] CA 81 61 0D 84 A5 2D 75 BD 41 85 4C 88 56 72 C6 ..a...-u .A.L.Vr.
+[02F0] B6 10 F8 34 CD B2 F4 5C 94 FA 80 90 82 A0 BD 68 ...4...\ .......h
+[0300] EC 08 32 C3 B6 51 1E 3F 67 CB 7B EB 70 83 84 D4 ..2..Q.? g.{.p...
+[0310] CB 52 55 36 61 1E 60 90 5B 6F FE 9A 62 05 CF 26 .RU6a.`. [o..b..&
+[0320] 8E 65 E2 60 4B ED 63 B4 C4 E6 44 B4 2F B0 B8 07 .e.`K.c. ..D./...
+[0330] FE BE 0D 50 E4 56 A4 2E 0D 25 76 0B 0F 44 09 20 ...P.V.. .%v..D.
+[0340] 80 E5 C4 94 63 E0 54 46 1D AB 5E 0B 09 93 B1 30 ....c.TF ..^....0
+[0350] 31 7B 04 DC 23 43 3B DB 7D 39 67 FE 9A 1F C1 08 1{..#C;. }9g.....
+[0360] AF 34 24 F6 74 E4 14 DA 34 8F 61 57 6A 7F 1D 4A .4$.t... 4.aWj..J
+[0370] 88 0A 90 78 93 F1 86 54 DB 22 86 D6 69 0F DF 44 ...x...T ."..i..D
+[0380] 7C D3 6B 9D 41 63 50 98 3A 97 B9 7B 4C 53 E3 85 |.k.AcP. :..{LS..
+[0390] 73 9A C9 08 A0 75 12 50 02 87 B0 CF CC 84 84 D9 s....u.P ........
+[03A0] BC FC 94 79 AF 6A A6 08 FF 19 7E E9 22 9B EC 5C ...y.j.. ..~."..\
+[03B0] C1 6B 1D A4 B4 55 32 5E 23 C3 C0 D4 8B 80 E6 67 .k...U2^ #......g
+[03C0] B1 59 EB 9D 5D 9B AD C6 0E 7D E2 FE B1 24 8A B1 .Y..]... .}...$..
+[03D0] 37 1E 60 7F 83 35 48 32 F7 03 E8 12 E6 21 7C 3D 7.`..5H2 .....!|=
+[03E0] 21 7F 6B 14 31 9C 1A A3 4C 2B 1C 5E EC 34 C1 2D !.k.1... L+.^.4.-
+[03F0] DA 19 6C E6 6D 8D 60 D7 55 9E E6 D0 B5 07 06 72 ..l.m.`. U......r
+[0400] C0 E9 4E 91 94 6B 3E 0B F1 0A 75 4D E8 CB 53 6B ..N..k>. ..uM..Sk
+[0410] 34 A4 2F 96 A5 39 1A 18 6E 27 00 6D 41 B7 D8 F5 4./..9.. n'.mA...
+[0420] 9A E5 01 FC 0B A8 97 56 EE 98 04 1D 98 84 5E 82 .......V ......^.
+[0430] C8 E8 EC 17 D5 FA 96 00 3B E1 98 1C D8 FA 66 A0 ........ ;.....f.
+[0440] DC 32 60 F6 03 46 08 3C E5 16 6F F2 8B 4D 72 9F .2`..F.< ..o..Mr.
+[0450] 0F E0 A9 71 6E 7C AE AA FB A3 4D F1 A1 B6 1B 9F ...qn|.. ..M.....
+[0460] 62 71 E1 2C 82 9B AE E3 07 9B 79 90 F1 C2 69 E5 bq.,.... ..y...i.
+[0470] 7E CB 57 E6 C9 1C 4E A8 C7 12 EA 4F 4C 52 17 03 ~.W...N. ...OLR..
+[0480] AB D4 FD 34 60 F4 7C BE 9E 36 30 37 88 95 61 2E ...4`.|. .607..a.
+[0490] CF 70 AF 22 70 DB E8 AA 6E 3D 30 F7 4D 84 D5 00 .p."p... n=0.M...
+[04A0] 00 00 00 00 00 00 01 00 00 00 01 00 00 00 17 4B ........ .......K
+[04B0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[04C0] 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 LE.COM.. ..admini
+[04D0] 73 74 72 61 74 6F 72 00 00 00 01 00 00 00 02 00 strator. ........
+[04E0] 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 ...KTEST .SAMBA.E
+[04F0] 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 XAMPLE.C OM....ci
+[0500] 66 73 00 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 fs....lo calktest
+[0510] 36 00 17 00 00 00 10 00 6E A1 B2 31 6D 48 C7 90 6....... n..1mH..
+[0520] 72 3A 0C 4B 8B 83 8C 4D 99 4F 6A 4D 99 50 85 7D r:.K...M .OjM.P.}
+[0530] 44 0B 68 00 00 00 00 00 40 28 00 00 00 00 00 00 D.h..... @(......
+[0540] 00 00 00 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 ........ a...0...
+[0550] A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0560] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0570] A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 ..0..... ...0...c
+[0580] 69 66 73 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 ifs..loc alktest6
+[0590] A3 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 03 02 ....0... ........
+[05A0] 01 02 A2 82 03 9C 04 82 03 98 C6 BB 64 A8 31 00 ........ ....d.1.
+[05B0] FC 5E 51 3C 87 F8 34 47 3B D0 6F 6F FD 9E A6 91 .^Q<..4G ;.oo....
+[05C0] 12 74 2D 44 BB AA 91 A0 2D 46 3E 9E FB FB C4 FB .t-D.... -F>.....
+[05D0] F1 15 FD BB DA EE 06 A9 20 6A 38 DC 46 06 27 D9 ........ j8.F.'.
+[05E0] A2 9D 2D 1F FD 0D 7D 8A BB 0A 7C E8 47 17 BC 7B ..-...}. ..|.G..{
+[05F0] 70 E4 51 6A BA 51 68 62 28 4A 1E 51 D1 0D CD 02 p.Qj.Qhb (J.Q....
+[0600] 55 75 44 8A B9 C2 84 F4 17 34 92 9B 31 85 9E 43 UuD..... .4..1..C
+[0610] C1 0C 3A B2 69 7F 20 1A 18 1F 65 4F C0 20 C9 B5 ..:.i. . ..eO. ..
+[0620] AF E1 61 8C 90 10 63 26 A6 5D 05 3C CD 29 BB 7B ..a...c& .].<.).{
+[0630] 74 D5 8F 2C 7F 4B E8 84 24 57 37 8A C6 F7 91 FD t..,.K.. $W7.....
+[0640] 22 9A A5 0D E9 4A 78 93 36 FC A8 8C 8A 27 8A C6 "....Jx. 6....'..
+[0650] 28 4B 7B DA 11 42 BC 09 10 81 82 14 0F 9C B8 48 (K{..B.. .......H
+[0660] 26 91 78 A8 DD 97 6C 24 A1 D2 E8 85 19 B3 D3 85 &.x...l$ ........
+[0670] 4D 38 C7 7D 49 55 8E 85 46 E1 EE 7B BA 11 62 63 M8.}IU.. F..{..bc
+[0680] 53 C5 16 4A 0C 1C 99 7C 0E FB 45 1D B4 98 58 67 S..J...| ..E...Xg
+[0690] 7E 40 65 4B 48 E2 89 9C 8B C2 B8 39 D1 04 C0 A8 ~@eKH... ...9....
+[06A0] 56 E8 A1 04 7A 7A C9 60 18 A0 29 E2 DC 82 4C 8F V...zz.` ..)...L.
+[06B0] 18 CE 2F 14 F0 18 5B 6C FF 85 45 88 73 CB A4 55 ../...[l ..E.s..U
+[06C0] 08 FC BF C7 9F 51 0A DB 2C C1 E3 3C DD F6 F0 A3 .....Q.. ,..<....
+[06D0] 2D F1 3B A0 12 1D FC 2A 67 F5 1A 7F E5 7C 6C FB -.;....* g....|l.
+[06E0] 8A 18 BD D1 5D E5 5E 68 30 AA 58 9E 10 13 E0 26 ....].^h 0.X....&
+[06F0] 7E 7D C4 E1 A5 B6 86 0F 1C 0F 13 A4 5E 5E 6A ED ~}...... ....^^j.
+[0700] 42 79 31 BB B3 5F 3A 3F DD CB 63 82 FB 06 AE 12 By1.._:? ..c.....
+[0710] 36 C9 1E 06 7D 41 82 2E D2 FA 26 EC 17 50 5E D0 6...}A.. ..&..P^.
+[0720] DE 26 85 30 71 BC 45 3B DA 2E 08 8D B2 2A 3C E0 .&.0q.E; .....*<.
+[0730] 79 8F 77 4C 01 69 7A 09 C7 88 E1 D1 DC FF 78 DB y.wL.iz. ......x.
+[0740] 25 7B B1 3C BB 22 27 80 0D 75 96 18 B6 40 95 6D %{.<."'. .u...@.m
+[0750] C8 AB 04 05 41 A1 C4 25 71 C4 53 3A A6 9C B2 4D ....A..% q.S:...M
+[0760] E6 15 2C B2 47 6C DA A8 7D CC A3 89 8B C9 1E 21 ..,.Gl.. }......!
+[0770] F5 E9 B2 42 95 68 28 AF C6 37 22 BA 30 8D 53 FA ...B.h(. .7".0.S.
+[0780] 08 0D CE CA 81 61 0D 84 A5 2D 75 BD 41 85 4C 88 .....a.. .-u.A.L.
+[0790] 56 72 C6 B6 10 F8 34 CD B2 F4 5C 94 FA 80 90 82 Vr....4. ..\.....
+[07A0] A0 BD 68 EC 08 32 C3 B6 51 1E 3F 67 CB 7B EB 70 ..h..2.. Q.?g.{.p
+[07B0] 83 84 D4 CB 52 55 36 61 1E 60 90 5B 6F FE 9A 62 ....RU6a .`.[o..b
+[07C0] 05 CF 26 8E 65 E2 60 4B ED 63 B4 C4 E6 44 B4 2F ..&.e.`K .c...D./
+[07D0] B0 B8 07 FE BE 0D 50 E4 56 A4 2E 0D 25 76 0B 0F ......P. V...%v..
+[07E0] 44 09 20 80 E5 C4 94 63 E0 54 46 1D AB 5E 0B 09 D. ....c .TF..^..
+[07F0] 93 B1 30 31 7B 04 DC 23 43 3B DB 7D 39 67 FE 9A ..01{..# C;.}9g..
+[0800] 1F C1 08 AF 34 24 F6 74 E4 14 DA 34 8F 61 57 6A ....4$.t ...4.aWj
+[0810] 7F 1D 4A 88 0A 90 78 93 F1 86 54 DB 22 86 D6 69 ..J...x. ..T."..i
+[0820] 0F DF 44 7C D3 6B 9D 41 63 50 98 3A 97 B9 7B 4C ..D|.k.A cP.:..{L
+[0830] 53 E3 85 73 9A C9 08 A0 75 12 50 02 87 B0 CF CC S..s.... u.P.....
+[0840] 84 84 D9 BC FC 94 79 AF 6A A6 08 FF 19 7E E9 22 ......y. j....~."
+[0850] 9B EC 5C C1 6B 1D A4 B4 55 32 5E 23 C3 C0 D4 8B ..\.k... U2^#....
+[0860] 80 E6 67 B1 59 EB 9D 5D 9B AD C6 0E 7D E2 FE B1 ..g.Y..] ....}...
+[0870] 24 8A B1 37 1E 60 7F 83 35 48 32 F7 03 E8 12 E6 $..7.`.. 5H2.....
+[0880] 21 7C 3D 21 7F 6B 14 31 9C 1A A3 4C 2B 1C 5E EC !|=!.k.1 ...L+.^.
+[0890] 34 C1 2D DA 19 6C E6 6D 8D 60 D7 55 9E E6 D0 B5 4.-..l.m .`.U....
+[08A0] 07 06 72 C0 E9 4E 91 94 6B 3E 0B F1 0A 75 4D E8 ..r..N.. k>...uM.
+[08B0] CB 53 6B 34 A4 2F 96 A5 39 1A 18 6E 27 00 6D 41 .Sk4./.. 9..n'.mA
+[08C0] B7 D8 F5 9A E5 01 FC 0B A8 97 56 EE 98 04 1D 98 ........ ..V.....
+[08D0] 84 5E 82 C8 E8 EC 17 D5 FA 96 00 3B E1 98 1C D8 .^...... ...;....
+[08E0] FA 66 A0 DC 32 60 F6 03 46 08 3C E5 16 6F F2 8B .f..2`.. F.<..o..
+[08F0] 4D 72 9F 0F E0 A9 71 6E 7C AE AA FB A3 4D F1 A1 Mr....qn |....M..
+[0900] B6 1B 9F 62 71 E1 2C 82 9B AE E3 07 9B 79 90 F1 ...bq.,. .....y..
+[0910] C2 69 E5 7E CB 57 E6 C9 1C 4E A8 C7 12 EA 4F 4C .i.~.W.. .N....OL
+[0920] 52 17 03 AB D4 FD 34 60 F4 7C BE 9E 36 30 37 88 R.....4` .|..607.
+[0930] 95 61 2E CF 70 AF 22 70 DB E8 AA 6E 3D 30 F7 4D .a..p."p ...n=0.M
+[0940] 84 D5 00 00 00 00 00 00 00 01 00 00 00 01 00 00 ........ ........
+[0950] 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[0960] 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D AMPLE.CO M....adm
+[0970] 69 6E 69 73 74 72 61 74 6F 72 00 00 00 01 00 00 inistrat or......
+[0980] 00 02 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[0990] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 A.EXAMPL E.COM...
+[09A0] 04 63 69 66 73 00 00 00 0B 6C 6F 63 61 6C 6B 74 .cifs... .localkt
+[09B0] 65 73 74 36 00 17 00 00 00 10 00 6E A1 B2 31 6D est6.... ...n..1m
+[09C0] 48 C7 90 72 3A 0C 4B 8B 83 8C 4D 99 4F 6A 4D 99 H..r:.K. ..M.OjM.
+[09D0] 50 85 7D 44 0B 68 00 00 00 00 00 40 28 00 00 00 P.}D.h.. ...@(...
+[09E0] 00 00 00 00 00 00 00 00 00 03 FA 61 82 03 F6 30 ........ ...a...0
+[09F0] 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 ........ ....KTES
+[0A00] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0A10] 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 COM..0.. ......0.
+[0A20] 1B 04 63 69 66 73 1B 0B 6C 6F 63 61 6C 6B 74 65 ..cifs.. localkte
+[0A30] 73 74 36 A3 82 03 AE 30 82 03 AA A0 03 02 01 17 st6....0 ........
+[0A40] A1 03 02 01 02 A2 82 03 9C 04 82 03 98 C6 BB 64 ........ .......d
+[0A50] A8 31 00 FC 5E 51 3C 87 F8 34 47 3B D0 6F 6F FD .1..^Q<. .4G;.oo.
+[0A60] 9E A6 91 12 74 2D 44 BB AA 91 A0 2D 46 3E 9E FB ....t-D. ...-F>..
+[0A70] FB C4 FB F1 15 FD BB DA EE 06 A9 20 6A 38 DC 46 ........ ... j8.F
+[0A80] 06 27 D9 A2 9D 2D 1F FD 0D 7D 8A BB 0A 7C E8 47 .'...-.. .}...|.G
+[0A90] 17 BC 7B 70 E4 51 6A BA 51 68 62 28 4A 1E 51 D1 ..{p.Qj. Qhb(J.Q.
+[0AA0] 0D CD 02 55 75 44 8A B9 C2 84 F4 17 34 92 9B 31 ...UuD.. ....4..1
+[0AB0] 85 9E 43 C1 0C 3A B2 69 7F 20 1A 18 1F 65 4F C0 ..C..:.i . ...eO.
+[0AC0] 20 C9 B5 AF E1 61 8C 90 10 63 26 A6 5D 05 3C CD ....a.. .c&.].<.
+[0AD0] 29 BB 7B 74 D5 8F 2C 7F 4B E8 84 24 57 37 8A C6 ).{t..,. K..$W7..
+[0AE0] F7 91 FD 22 9A A5 0D E9 4A 78 93 36 FC A8 8C 8A ...".... Jx.6....
+[0AF0] 27 8A C6 28 4B 7B DA 11 42 BC 09 10 81 82 14 0F '..(K{.. B.......
+[0B00] 9C B8 48 26 91 78 A8 DD 97 6C 24 A1 D2 E8 85 19 ..H&.x.. .l$.....
+[0B10] B3 D3 85 4D 38 C7 7D 49 55 8E 85 46 E1 EE 7B BA ...M8.}I U..F..{.
+[0B20] 11 62 63 53 C5 16 4A 0C 1C 99 7C 0E FB 45 1D B4 .bcS..J. ..|..E..
+[0B30] 98 58 67 7E 40 65 4B 48 E2 89 9C 8B C2 B8 39 D1 .Xg~@eKH ......9.
+[0B40] 04 C0 A8 56 E8 A1 04 7A 7A C9 60 18 A0 29 E2 DC ...V...z z.`..)..
+[0B50] 82 4C 8F 18 CE 2F 14 F0 18 5B 6C FF 85 45 88 73 .L.../.. .[l..E.s
+[0B60] CB A4 55 08 FC BF C7 9F 51 0A DB 2C C1 E3 3C DD ..U..... Q..,..<.
+[0B70] F6 F0 A3 2D F1 3B A0 12 1D FC 2A 67 F5 1A 7F E5 ...-.;.. ..*g....
+[0B80] 7C 6C FB 8A 18 BD D1 5D E5 5E 68 30 AA 58 9E 10 |l.....] .^h0.X..
+[0B90] 13 E0 26 7E 7D C4 E1 A5 B6 86 0F 1C 0F 13 A4 5E ..&~}... .......^
+[0BA0] 5E 6A ED 42 79 31 BB B3 5F 3A 3F DD CB 63 82 FB ^j.By1.. _:?..c..
+[0BB0] 06 AE 12 36 C9 1E 06 7D 41 82 2E D2 FA 26 EC 17 ...6...} A....&..
+[0BC0] 50 5E D0 DE 26 85 30 71 BC 45 3B DA 2E 08 8D B2 P^..&.0q .E;.....
+[0BD0] 2A 3C E0 79 8F 77 4C 01 69 7A 09 C7 88 E1 D1 DC *<.y.wL. iz......
+[0BE0] FF 78 DB 25 7B B1 3C BB 22 27 80 0D 75 96 18 B6 .x.%{.<. "'..u...
+[0BF0] 40 95 6D C8 AB 04 05 41 A1 C4 25 71 C4 53 3A A6 @.m....A ..%q.S:.
+[0C00] 9C B2 4D E6 15 2C B2 47 6C DA A8 7D CC A3 89 8B ..M..,.G l..}....
+[0C10] C9 1E 21 F5 E9 B2 42 95 68 28 AF C6 37 22 BA 30 ..!...B. h(..7".0
+[0C20] 8D 53 FA 08 0D CE CA 81 61 0D 84 A5 2D 75 BD 41 .S...... a...-u.A
+[0C30] 85 4C 88 56 72 C6 B6 10 F8 34 CD B2 F4 5C 94 FA .L.Vr... .4...\..
+[0C40] 80 90 82 A0 BD 68 EC 08 32 C3 B6 51 1E 3F 67 CB .....h.. 2..Q.?g.
+[0C50] 7B EB 70 83 84 D4 CB 52 55 36 61 1E 60 90 5B 6F {.p....R U6a.`.[o
+[0C60] FE 9A 62 05 CF 26 8E 65 E2 60 4B ED 63 B4 C4 E6 ..b..&.e .`K.c...
+[0C70] 44 B4 2F B0 B8 07 FE BE 0D 50 E4 56 A4 2E 0D 25 D./..... .P.V...%
+[0C80] 76 0B 0F 44 09 20 80 E5 C4 94 63 E0 54 46 1D AB v..D. .. ..c.TF..
+[0C90] 5E 0B 09 93 B1 30 31 7B 04 DC 23 43 3B DB 7D 39 ^....01{ ..#C;.}9
+[0CA0] 67 FE 9A 1F C1 08 AF 34 24 F6 74 E4 14 DA 34 8F g......4 $.t...4.
+[0CB0] 61 57 6A 7F 1D 4A 88 0A 90 78 93 F1 86 54 DB 22 aWj..J.. .x...T."
+[0CC0] 86 D6 69 0F DF 44 7C D3 6B 9D 41 63 50 98 3A 97 ..i..D|. k.AcP.:.
+[0CD0] B9 7B 4C 53 E3 85 73 9A C9 08 A0 75 12 50 02 87 .{LS..s. ...u.P..
+[0CE0] B0 CF CC 84 84 D9 BC FC 94 79 AF 6A A6 08 FF 19 ........ .y.j....
+[0CF0] 7E E9 22 9B EC 5C C1 6B 1D A4 B4 55 32 5E 23 C3 ~."..\.k ...U2^#.
+[0D00] C0 D4 8B 80 E6 67 B1 59 EB 9D 5D 9B AD C6 0E 7D .....g.Y ..]....}
+[0D10] E2 FE B1 24 8A B1 37 1E 60 7F 83 35 48 32 F7 03 ...$..7. `..5H2..
+[0D20] E8 12 E6 21 7C 3D 21 7F 6B 14 31 9C 1A A3 4C 2B ...!|=!. k.1...L+
+[0D30] 1C 5E EC 34 C1 2D DA 19 6C E6 6D 8D 60 D7 55 9E .^.4.-.. l.m.`.U.
+[0D40] E6 D0 B5 07 06 72 C0 E9 4E 91 94 6B 3E 0B F1 0A .....r.. N..k>...
+[0D50] 75 4D E8 CB 53 6B 34 A4 2F 96 A5 39 1A 18 6E 27 uM..Sk4. /..9..n'
+[0D60] 00 6D 41 B7 D8 F5 9A E5 01 FC 0B A8 97 56 EE 98 .mA..... .....V..
+[0D70] 04 1D 98 84 5E 82 C8 E8 EC 17 D5 FA 96 00 3B E1 ....^... ......;.
+[0D80] 98 1C D8 FA 66 A0 DC 32 60 F6 03 46 08 3C E5 16 ....f..2 `..F.<..
+[0D90] 6F F2 8B 4D 72 9F 0F E0 A9 71 6E 7C AE AA FB A3 o..Mr... .qn|....
+[0DA0] 4D F1 A1 B6 1B 9F 62 71 E1 2C 82 9B AE E3 07 9B M.....bq .,......
+[0DB0] 79 90 F1 C2 69 E5 7E CB 57 E6 C9 1C 4E A8 C7 12 y...i.~. W...N...
+[0DC0] EA 4F 4C 52 17 03 AB D4 FD 34 60 F4 7C BE 9E 36 .OLR.... .4`.|..6
+[0DD0] 30 37 88 95 61 2E CF 70 AF 22 70 DB E8 AA 6E 3D 07..a..p ."p...n=
+[0DE0] 30 F7 4D 84 D5 00 00 00 00 00 00 00 01 00 00 00 0.M..... ........
+[0DF0] 01 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[0E00] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D .EXAMPLE .COM....
+[0E10] 61 64 6D 69 6E 69 73 74 72 61 74 6F 72 00 00 00 administ rator...
+[0E20] 01 00 00 00 02 00 00 00 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0E30] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0E40] 00 00 00 04 63 69 66 73 00 00 00 0B 4C 4F 43 41 ....cifs ....LOCA
+[0E50] 4C 4B 54 45 53 54 36 00 17 00 00 00 10 1D C8 5E LKTEST6. .......^
+[0E60] 46 48 82 F9 29 DB C6 A6 F1 72 6D 8D E9 4D 99 4F FH..)... .rm..M.O
+[0E70] 6A 4D 99 85 09 7D 44 0B 68 00 00 00 00 00 40 28 jM...}D. h.....@(
+[0E80] 00 00 00 00 00 00 00 00 00 00 00 00 03 FA 61 82 ........ ......a.
+[0E90] 03 F6 30 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B ..0..... .......K
+[0EA0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0EB0] 4C 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 LE.COM.. 0.......
+[0EC0] 15 30 13 1B 04 63 69 66 73 1B 0B 4C 4F 43 41 4C .0...cif s..LOCAL
+[0ED0] 4B 54 45 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 KTEST6.. ..0.....
+[0EE0] 02 01 17 A1 03 02 01 02 A2 82 03 9C 04 82 03 98 ........ ........
+[0EF0] 66 D8 19 46 FA CB 73 2D CF 88 FD 4A EE 07 48 DA f..F..s- ...J..H.
+[0F00] 0E BC 58 30 43 40 A4 9C 00 0F 3B 17 C1 2D F5 9C ..X0C@.. ..;..-..
+[0F10] 3E D9 2F 1D CA 01 9B D7 2E EC D7 70 ED 8B 8B 1B >./..... ...p....
+[0F20] 5E F2 4E EE DD 0F C0 8D 61 E5 D7 0A 56 00 32 B1 ^.N..... a...V.2.
+[0F30] DB 91 37 29 0F 2F 85 EE A8 43 BA A5 B8 D4 19 74 ..7)./.. .C.....t
+[0F40] 33 F0 69 52 E1 58 98 83 D6 16 0B 44 A9 63 9B D4 3.iR.X.. ...D.c..
+[0F50] 4E 6E A7 3E CD 9A 96 4D C4 96 F5 07 6D 29 B6 ED Nn.>...M ....m)..
+[0F60] 2A 62 3D 53 22 33 D1 95 E9 DF 74 4C 2A E2 29 AF *b=S"3.. ..tL*.).
+[0F70] 5B 69 B0 48 2D AD 94 FD A5 1D 54 D8 E2 5E C1 68 [i.H-... ..T..^.h
+[0F80] 6F BA 02 01 79 C3 C9 97 0B 76 66 45 E2 3B 10 17 o...y... .vfE.;..
+[0F90] 95 40 46 E4 85 B9 87 BB CF CF 19 8C 3A C0 EA 38 .@F..... ....:..8
+[0FA0] 3B B9 E9 4B 05 89 E5 27 8C 62 95 BC 0D 65 F0 D2 ;..K...' .b...e..
+[0FB0] C0 5E BC 65 01 D5 0B CB 17 31 0F 06 49 4F A2 4A .^.e.... .1..IO.J
+[0FC0] 70 77 DB BD 92 5B 37 5C EC 06 DF C5 E2 31 C8 40 pw...[7\ .....1.@
+[0FD0] 09 11 68 14 E7 7D CE 54 4F 52 61 31 2C 1C 53 52 ..h..}.T ORa1,.SR
+[0FE0] DB BE D8 95 39 EE 7D C6 CE C8 22 95 92 97 97 3D ....9.}. .."....=
+[0FF0] 5E 66 0F AD DC C2 4E 2E 2B 9F 63 20 30 DF B7 C1 ^f....N. +.c 0...
+[1000] D4 65 AA 6F 2D 10 24 07 20 8D 88 6E 4B 09 04 31 .e.o-.$. ..nK..1
+[1010] B6 A3 EB F7 37 32 0E 0C 73 C6 F6 B8 4D D9 0C 4C ....72.. s...M..L
+[1020] 5B EC 10 6A 51 19 EA 3F FF 46 E7 73 16 A7 1F 33 [..jQ..? .F.s...3
+[1030] 98 7C 9B AD 5A 23 A9 40 7C 0F DF EE 0F AA C7 E8 .|..Z#.@ |.......
+[1040] 63 07 98 3A 4A 0D 18 62 01 21 B2 AE A5 69 B0 C1 c..:J..b .!...i..
+[1050] 15 51 BA 97 D2 C5 42 5B C5 30 38 18 A9 48 AB D7 .Q....B[ .08..H..
+[1060] FC A1 BC 9F 71 E7 EA 18 54 42 DA D6 A4 FC C1 DC ....q... TB......
+[1070] F3 12 30 62 AC 98 E1 7D 2B 34 1E 52 4C 26 67 32 ..0b...} +4.RL&g2
+[1080] D9 44 1A 08 27 0E DA D0 FC 84 66 35 81 D6 EB 98 .D..'... ..f5....
+[1090] 46 6F 1E 47 E0 14 31 BE 47 80 65 AA 0B 20 D6 33 Fo.G..1. G.e.. .3
+[10A0] 36 3B 0D 40 2F 5A 2E 0E 01 BE 00 EB 33 3E 4B 32 6;.@/Z.. ....3>K2
+[10B0] 91 F4 22 96 E5 5F D4 D5 92 94 CC 5B 59 6A 3E D2 ..".._.. ...[Yj>.
+[10C0] FB A0 4F 99 C4 07 8B 6F 2B 14 37 CD 37 44 C0 1F ..O....o +.7.7D..
+[10D0] 80 9C 43 46 F2 5E F4 FE D3 39 70 61 BE 72 5B 3A ..CF.^.. .9pa.r[:
+[10E0] 8F 37 95 78 1E AB D9 E7 E9 DA FC 47 09 81 A0 0D .7.x.... ...G....
+[10F0] 62 E1 F9 34 36 D1 DB E6 98 D8 F4 3E 77 5A 4D E2 b..46... ...>wZM.
+[1100] 5F 20 70 3D 3D 5B 34 D9 FD A8 31 F7 D9 59 F7 A3 _ p==[4. ..1..Y..
+[1110] F0 66 F7 D9 AD 1C CD D5 85 33 A0 87 22 31 D4 F3 .f...... .3.."1..
+[1120] 67 80 68 20 A2 90 72 7A 6F 64 FD 68 82 9E 91 B8 g.h ..rz od.h....
+[1130] E3 F7 6D 6C 38 74 F0 96 A2 F6 25 D7 92 58 14 60 ..ml8t.. ..%..X.`
+[1140] 9F AE 01 4C 0C 09 67 3E 35 67 71 1E 2A 86 21 D3 ...L..g> 5gq.*.!.
+[1150] 60 61 98 16 94 67 0B 52 76 63 93 BD A3 3B A9 F0 `a...g.R vc...;..
+[1160] A2 6A B7 E6 0F 35 64 DA 6A EA 20 A6 3D 94 71 59 .j...5d. j. .=.qY
+[1170] 5E CB B2 D3 F9 4D FE 1B 4B D8 64 C8 3B 7A A8 E6 ^....M.. K.d.;z..
+[1180] D2 D5 76 71 26 D4 5C DA 1A 55 17 F2 16 C9 2F 77 ..vq&.\. .U..../w
+[1190] DB 95 19 48 A5 AC D0 C3 31 9C 0A CC 1B 44 11 6B ...H.... 1....D.k
+[11A0] 7C 88 7A 5D CF 6E 12 DA EF C5 C7 34 1D F4 CC EA |.z].n.. ...4....
+[11B0] 37 24 4B B3 0F C1 A3 F2 29 A0 D8 93 39 C6 16 57 7$K..... )...9..W
+[11C0] D5 BF 57 BF 6C 7E F7 90 E0 EB A3 8B 07 56 9C EC ..W.l~.. .....V..
+[11D0] 15 3E 21 DA A5 7C 00 3C F9 D2 A7 1C 6F 16 25 31 .>!..|.< ....o.%1
+[11E0] C5 28 A7 EA F3 47 31 50 DD E1 ED 0A 93 DB 85 CC .(...G1P ........
+[11F0] 6B 4B 2C 7F E8 F8 2D A9 6D 1D 0A 87 F2 10 8C 82 kK,...-. m.......
+[1200] 2F 9B D4 9B 92 8C 77 40 50 42 1E 42 C4 0A 4F E3 /.....w@ PB.B..O.
+[1210] 6C 6C DC 81 C4 1E BB F0 7D CF 3C 73 22 5B C3 1A ll...... }.<s"[..
+[1220] 97 35 EE 3A CD 6D F3 68 A3 C5 65 7E E9 54 C0 E3 .5.:.m.h ..e~.T..
+[1230] 7D 6A 32 4C D1 3E D0 78 4B BF 18 9F A5 25 4A 92 }j2L.>.x K....%J.
+[1240] 1E 6C 8F 01 D6 59 D7 CF 2E A0 CC 98 F6 75 28 2F .l...Y.. .....u(/
+[1250] F7 2A 70 28 A9 45 1F 75 C2 4E 62 ED D8 C4 A0 8D .*p(.E.u .Nb.....
+[1260] 55 B2 84 1C A4 CE 87 EF 24 EE BC CE 40 09 EB 05 U....... $...@...
+[1270] 0B D1 14 31 50 32 2F B6 A8 97 17 4B A7 95 01 50 ...1P2/. ...K...P
+[1280] 6E 0E 23 49 9C 72 21 91 00 00 00 00 00 00 00 01 n.#I.r!. ........
+[1290] 00 00 00 01 00 00 00 17 4B 54 45 53 54 2E 53 41 ........ KTEST.SA
+[12A0] 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 MBA.EXAM PLE.COM.
+[12B0] 00 00 0D 61 64 6D 69 6E 69 73 74 72 61 74 6F 72 ...admin istrator
+[12C0] 00 00 00 01 00 00 00 02 00 00 00 17 4B 54 45 53 ........ ....KTES
+[12D0] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[12E0] 43 4F 4D 00 00 00 04 63 69 66 73 00 00 00 0B 4C COM....c ifs....L
+[12F0] 4F 43 41 4C 4B 54 45 53 54 36 00 17 00 00 00 10 OCALKTES T6......
+[1300] 1D C8 5E 46 48 82 F9 29 DB C6 A6 F1 72 6D 8D E9 ..^FH..) ....rm..
+[1310] 4D 99 4F 6A 4D 99 85 09 7D 44 0B 68 00 00 00 00 M.OjM... }D.h....
+[1320] 00 40 28 00 00 00 00 00 00 00 00 00 00 00 00 03 .@(..... ........
+[1330] FA 61 82 03 F6 30 82 03 F2 A0 03 02 01 05 A1 19 .a...0.. ........
+[1340] 1B 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[1350] 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 AMPLE.CO M..0....
+[1360] 01 01 A1 15 30 13 1B 04 63 69 66 73 1B 0B 4C 4F ....0... cifs..LO
+[1370] 43 41 4C 4B 54 45 53 54 36 A3 82 03 AE 30 82 03 CALKTEST 6....0..
+[1380] AA A0 03 02 01 17 A1 03 02 01 02 A2 82 03 9C 04 ........ ........
+[1390] 82 03 98 66 D8 19 46 FA CB 73 2D CF 88 FD 4A EE ...f..F. .s-...J.
+[13A0] 07 48 DA 0E BC 58 30 43 40 A4 9C 00 0F 3B 17 C1 .H...X0C @....;..
+[13B0] 2D F5 9C 3E D9 2F 1D CA 01 9B D7 2E EC D7 70 ED -..>./.. ......p.
+[13C0] 8B 8B 1B 5E F2 4E EE DD 0F C0 8D 61 E5 D7 0A 56 ...^.N.. ...a...V
+[13D0] 00 32 B1 DB 91 37 29 0F 2F 85 EE A8 43 BA A5 B8 .2...7). /...C...
+[13E0] D4 19 74 33 F0 69 52 E1 58 98 83 D6 16 0B 44 A9 ..t3.iR. X.....D.
+[13F0] 63 9B D4 4E 6E A7 3E CD 9A 96 4D C4 96 F5 07 6D c..Nn.>. ..M....m
+[1400] 29 B6 ED 2A 62 3D 53 22 33 D1 95 E9 DF 74 4C 2A )..*b=S" 3....tL*
+[1410] E2 29 AF 5B 69 B0 48 2D AD 94 FD A5 1D 54 D8 E2 .).[i.H- .....T..
+[1420] 5E C1 68 6F BA 02 01 79 C3 C9 97 0B 76 66 45 E2 ^.ho...y ....vfE.
+[1430] 3B 10 17 95 40 46 E4 85 B9 87 BB CF CF 19 8C 3A ;...@F.. .......:
+[1440] C0 EA 38 3B B9 E9 4B 05 89 E5 27 8C 62 95 BC 0D ..8;..K. ..'.b...
+[1450] 65 F0 D2 C0 5E BC 65 01 D5 0B CB 17 31 0F 06 49 e...^.e. ....1..I
+[1460] 4F A2 4A 70 77 DB BD 92 5B 37 5C EC 06 DF C5 E2 O.Jpw... [7\.....
+[1470] 31 C8 40 09 11 68 14 E7 7D CE 54 4F 52 61 31 2C 1.@..h.. }.TORa1,
+[1480] 1C 53 52 DB BE D8 95 39 EE 7D C6 CE C8 22 95 92 .SR....9 .}..."..
+[1490] 97 97 3D 5E 66 0F AD DC C2 4E 2E 2B 9F 63 20 30 ..=^f... .N.+.c 0
+[14A0] DF B7 C1 D4 65 AA 6F 2D 10 24 07 20 8D 88 6E 4B ....e.o- .$. ..nK
+[14B0] 09 04 31 B6 A3 EB F7 37 32 0E 0C 73 C6 F6 B8 4D ..1....7 2..s...M
+[14C0] D9 0C 4C 5B EC 10 6A 51 19 EA 3F FF 46 E7 73 16 ..L[..jQ ..?.F.s.
+[14D0] A7 1F 33 98 7C 9B AD 5A 23 A9 40 7C 0F DF EE 0F ..3.|..Z #.@|....
+[14E0] AA C7 E8 63 07 98 3A 4A 0D 18 62 01 21 B2 AE A5 ...c..:J ..b.!...
+[14F0] 69 B0 C1 15 51 BA 97 D2 C5 42 5B C5 30 38 18 A9 i...Q... .B[.08..
+[1500] 48 AB D7 FC A1 BC 9F 71 E7 EA 18 54 42 DA D6 A4 H......q ...TB...
+[1510] FC C1 DC F3 12 30 62 AC 98 E1 7D 2B 34 1E 52 4C .....0b. ..}+4.RL
+[1520] 26 67 32 D9 44 1A 08 27 0E DA D0 FC 84 66 35 81 &g2.D..' .....f5.
+[1530] D6 EB 98 46 6F 1E 47 E0 14 31 BE 47 80 65 AA 0B ...Fo.G. .1.G.e..
+[1540] 20 D6 33 36 3B 0D 40 2F 5A 2E 0E 01 BE 00 EB 33 .36;.@/ Z......3
+[1550] 3E 4B 32 91 F4 22 96 E5 5F D4 D5 92 94 CC 5B 59 >K2..".. _.....[Y
+[1560] 6A 3E D2 FB A0 4F 99 C4 07 8B 6F 2B 14 37 CD 37 j>...O.. ..o+.7.7
+[1570] 44 C0 1F 80 9C 43 46 F2 5E F4 FE D3 39 70 61 BE D....CF. ^...9pa.
+[1580] 72 5B 3A 8F 37 95 78 1E AB D9 E7 E9 DA FC 47 09 r[:.7.x. ......G.
+[1590] 81 A0 0D 62 E1 F9 34 36 D1 DB E6 98 D8 F4 3E 77 ...b..46 ......>w
+[15A0] 5A 4D E2 5F 20 70 3D 3D 5B 34 D9 FD A8 31 F7 D9 ZM._ p== [4...1..
+[15B0] 59 F7 A3 F0 66 F7 D9 AD 1C CD D5 85 33 A0 87 22 Y...f... ....3.."
+[15C0] 31 D4 F3 67 80 68 20 A2 90 72 7A 6F 64 FD 68 82 1..g.h . .rzod.h.
+[15D0] 9E 91 B8 E3 F7 6D 6C 38 74 F0 96 A2 F6 25 D7 92 .....ml8 t....%..
+[15E0] 58 14 60 9F AE 01 4C 0C 09 67 3E 35 67 71 1E 2A X.`...L. .g>5gq.*
+[15F0] 86 21 D3 60 61 98 16 94 67 0B 52 76 63 93 BD A3 .!.`a... g.Rvc...
+[1600] 3B A9 F0 A2 6A B7 E6 0F 35 64 DA 6A EA 20 A6 3D ;...j... 5d.j. .=
+[1610] 94 71 59 5E CB B2 D3 F9 4D FE 1B 4B D8 64 C8 3B .qY^.... M..K.d.;
+[1620] 7A A8 E6 D2 D5 76 71 26 D4 5C DA 1A 55 17 F2 16 z....vq& .\..U...
+[1630] C9 2F 77 DB 95 19 48 A5 AC D0 C3 31 9C 0A CC 1B ./w...H. ...1....
+[1640] 44 11 6B 7C 88 7A 5D CF 6E 12 DA EF C5 C7 34 1D D.k|.z]. n.....4.
+[1650] F4 CC EA 37 24 4B B3 0F C1 A3 F2 29 A0 D8 93 39 ...7$K.. ...)...9
+[1660] C6 16 57 D5 BF 57 BF 6C 7E F7 90 E0 EB A3 8B 07 ..W..W.l ~.......
+[1670] 56 9C EC 15 3E 21 DA A5 7C 00 3C F9 D2 A7 1C 6F V...>!.. |.<....o
+[1680] 16 25 31 C5 28 A7 EA F3 47 31 50 DD E1 ED 0A 93 .%1.(... G1P.....
+[1690] DB 85 CC 6B 4B 2C 7F E8 F8 2D A9 6D 1D 0A 87 F2 ...kK,.. .-.m....
+[16A0] 10 8C 82 2F 9B D4 9B 92 8C 77 40 50 42 1E 42 C4 .../.... .w@PB.B.
+[16B0] 0A 4F E3 6C 6C DC 81 C4 1E BB F0 7D CF 3C 73 22 .O.ll... ...}.<s"
+[16C0] 5B C3 1A 97 35 EE 3A CD 6D F3 68 A3 C5 65 7E E9 [...5.:. m.h..e~.
+[16D0] 54 C0 E3 7D 6A 32 4C D1 3E D0 78 4B BF 18 9F A5 T..}j2L. >.xK....
+[16E0] 25 4A 92 1E 6C 8F 01 D6 59 D7 CF 2E A0 CC 98 F6 %J..l... Y.......
+[16F0] 75 28 2F F7 2A 70 28 A9 45 1F 75 C2 4E 62 ED D8 u(/.*p(. E.u.Nb..
+[1700] C4 A0 8D 55 B2 84 1C A4 CE 87 EF 24 EE BC CE 40 ...U.... ...$...@
+[1710] 09 EB 05 0B D1 14 31 50 32 2F B6 A8 97 17 4B A7 ......1P 2/....K.
+[1720] 95 01 50 6E 0E 23 49 9C 72 21 91 00 00 00 00 00 ..Pn.#I. r!......
+[1730] 00 00 01 00 00 00 01 00 00 00 17 4B 54 45 53 54 ........ ...KTEST
+[1740] 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 .SAMBA.E XAMPLE.C
+[1750] 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 74 72 61 OM....ad ministra
+[1760] 74 6F 72 00 00 00 01 00 00 00 02 00 00 00 17 4B tor..... .......K
+[1770] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[1780] 4C 45 2E 43 4F 4D 00 00 00 04 63 69 66 73 00 00 LE.COM.. ..cifs..
+[1790] 00 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 00 17 00 ..LOCALK TEST6...
+[17A0] 00 00 10 1D C8 5E 46 48 82 F9 29 DB C6 A6 F1 72 .....^FH ..)....r
+[17B0] 6D 8D E9 4D 99 4F 6A 4D 99 85 09 7D 44 0B 68 00 m..M.OjM ...}D.h.
+[17C0] 00 00 00 00 40 28 00 00 00 00 00 00 00 00 00 00 ....@(.. ........
+[17D0] 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 03 02 01 ....a... 0.......
+[17E0] 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[17F0] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 1C .EXAMPLE .COM..0.
+[1800] A0 03 02 01 01 A1 15 30 13 1B 04 63 69 66 73 1B .......0 ...cifs.
+[1810] 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 A3 82 03 AE .LOCALKT EST6....
+[1820] 30 82 03 AA A0 03 02 01 17 A1 03 02 01 02 A2 82 0....... ........
+[1830] 03 9C 04 82 03 98 66 D8 19 46 FA CB 73 2D CF 88 ......f. .F..s-..
+[1840] FD 4A EE 07 48 DA 0E BC 58 30 43 40 A4 9C 00 0F .J..H... X0C@....
+[1850] 3B 17 C1 2D F5 9C 3E D9 2F 1D CA 01 9B D7 2E EC ;..-..>. /.......
+[1860] D7 70 ED 8B 8B 1B 5E F2 4E EE DD 0F C0 8D 61 E5 .p....^. N.....a.
+[1870] D7 0A 56 00 32 B1 DB 91 37 29 0F 2F 85 EE A8 43 ..V.2... 7)./...C
+[1880] BA A5 B8 D4 19 74 33 F0 69 52 E1 58 98 83 D6 16 .....t3. iR.X....
+[1890] 0B 44 A9 63 9B D4 4E 6E A7 3E CD 9A 96 4D C4 96 .D.c..Nn .>...M..
+[18A0] F5 07 6D 29 B6 ED 2A 62 3D 53 22 33 D1 95 E9 DF ..m)..*b =S"3....
+[18B0] 74 4C 2A E2 29 AF 5B 69 B0 48 2D AD 94 FD A5 1D tL*.).[i .H-.....
+[18C0] 54 D8 E2 5E C1 68 6F BA 02 01 79 C3 C9 97 0B 76 T..^.ho. ..y....v
+[18D0] 66 45 E2 3B 10 17 95 40 46 E4 85 B9 87 BB CF CF fE.;...@ F.......
+[18E0] 19 8C 3A C0 EA 38 3B B9 E9 4B 05 89 E5 27 8C 62 ..:..8;. .K...'.b
+[18F0] 95 BC 0D 65 F0 D2 C0 5E BC 65 01 D5 0B CB 17 31 ...e...^ .e.....1
+[1900] 0F 06 49 4F A2 4A 70 77 DB BD 92 5B 37 5C EC 06 ..IO.Jpw ...[7\..
+[1910] DF C5 E2 31 C8 40 09 11 68 14 E7 7D CE 54 4F 52 ...1.@.. h..}.TOR
+[1920] 61 31 2C 1C 53 52 DB BE D8 95 39 EE 7D C6 CE C8 a1,.SR.. ..9.}...
+[1930] 22 95 92 97 97 3D 5E 66 0F AD DC C2 4E 2E 2B 9F "....=^f ....N.+.
+[1940] 63 20 30 DF B7 C1 D4 65 AA 6F 2D 10 24 07 20 8D c 0....e .o-.$. .
+[1950] 88 6E 4B 09 04 31 B6 A3 EB F7 37 32 0E 0C 73 C6 .nK..1.. ..72..s.
+[1960] F6 B8 4D D9 0C 4C 5B EC 10 6A 51 19 EA 3F FF 46 ..M..L[. .jQ..?.F
+[1970] E7 73 16 A7 1F 33 98 7C 9B AD 5A 23 A9 40 7C 0F .s...3.| ..Z#.@|.
+[1980] DF EE 0F AA C7 E8 63 07 98 3A 4A 0D 18 62 01 21 ......c. .:J..b.!
+[1990] B2 AE A5 69 B0 C1 15 51 BA 97 D2 C5 42 5B C5 30 ...i...Q ....B[.0
+[19A0] 38 18 A9 48 AB D7 FC A1 BC 9F 71 E7 EA 18 54 42 8..H.... ..q...TB
+[19B0] DA D6 A4 FC C1 DC F3 12 30 62 AC 98 E1 7D 2B 34 ........ 0b...}+4
+[19C0] 1E 52 4C 26 67 32 D9 44 1A 08 27 0E DA D0 FC 84 .RL&g2.D ..'.....
+[19D0] 66 35 81 D6 EB 98 46 6F 1E 47 E0 14 31 BE 47 80 f5....Fo .G..1.G.
+[19E0] 65 AA 0B 20 D6 33 36 3B 0D 40 2F 5A 2E 0E 01 BE e.. .36; .@/Z....
+[19F0] 00 EB 33 3E 4B 32 91 F4 22 96 E5 5F D4 D5 92 94 ..3>K2.. ".._....
+[1A00] CC 5B 59 6A 3E D2 FB A0 4F 99 C4 07 8B 6F 2B 14 .[Yj>... O....o+.
+[1A10] 37 CD 37 44 C0 1F 80 9C 43 46 F2 5E F4 FE D3 39 7.7D.... CF.^...9
+[1A20] 70 61 BE 72 5B 3A 8F 37 95 78 1E AB D9 E7 E9 DA pa.r[:.7 .x......
+[1A30] FC 47 09 81 A0 0D 62 E1 F9 34 36 D1 DB E6 98 D8 .G....b. .46.....
+[1A40] F4 3E 77 5A 4D E2 5F 20 70 3D 3D 5B 34 D9 FD A8 .>wZM._ p==[4...
+[1A50] 31 F7 D9 59 F7 A3 F0 66 F7 D9 AD 1C CD D5 85 33 1..Y...f .......3
+[1A60] A0 87 22 31 D4 F3 67 80 68 20 A2 90 72 7A 6F 64 .."1..g. h ..rzod
+[1A70] FD 68 82 9E 91 B8 E3 F7 6D 6C 38 74 F0 96 A2 F6 .h...... ml8t....
+[1A80] 25 D7 92 58 14 60 9F AE 01 4C 0C 09 67 3E 35 67 %..X.`.. .L..g>5g
+[1A90] 71 1E 2A 86 21 D3 60 61 98 16 94 67 0B 52 76 63 q.*.!.`a ...g.Rvc
+[1AA0] 93 BD A3 3B A9 F0 A2 6A B7 E6 0F 35 64 DA 6A EA ...;...j ...5d.j.
+[1AB0] 20 A6 3D 94 71 59 5E CB B2 D3 F9 4D FE 1B 4B D8 .=.qY^. ...M..K.
+[1AC0] 64 C8 3B 7A A8 E6 D2 D5 76 71 26 D4 5C DA 1A 55 d.;z.... vq&.\..U
+[1AD0] 17 F2 16 C9 2F 77 DB 95 19 48 A5 AC D0 C3 31 9C ..../w.. .H....1.
+[1AE0] 0A CC 1B 44 11 6B 7C 88 7A 5D CF 6E 12 DA EF C5 ...D.k|. z].n....
+[1AF0] C7 34 1D F4 CC EA 37 24 4B B3 0F C1 A3 F2 29 A0 .4....7$ K.....).
+[1B00] D8 93 39 C6 16 57 D5 BF 57 BF 6C 7E F7 90 E0 EB ..9..W.. W.l~....
+[1B10] A3 8B 07 56 9C EC 15 3E 21 DA A5 7C 00 3C F9 D2 ...V...> !..|.<..
+[1B20] A7 1C 6F 16 25 31 C5 28 A7 EA F3 47 31 50 DD E1 ..o.%1.( ...G1P..
+[1B30] ED 0A 93 DB 85 CC 6B 4B 2C 7F E8 F8 2D A9 6D 1D ......kK ,...-.m.
+[1B40] 0A 87 F2 10 8C 82 2F 9B D4 9B 92 8C 77 40 50 42 ....../. ....w@PB
+[1B50] 1E 42 C4 0A 4F E3 6C 6C DC 81 C4 1E BB F0 7D CF .B..O.ll ......}.
+[1B60] 3C 73 22 5B C3 1A 97 35 EE 3A CD 6D F3 68 A3 C5 <s"[...5 .:.m.h..
+[1B70] 65 7E E9 54 C0 E3 7D 6A 32 4C D1 3E D0 78 4B BF e~.T..}j 2L.>.xK.
+[1B80] 18 9F A5 25 4A 92 1E 6C 8F 01 D6 59 D7 CF 2E A0 ...%J..l ...Y....
+[1B90] CC 98 F6 75 28 2F F7 2A 70 28 A9 45 1F 75 C2 4E ...u(/.* p(.E.u.N
+[1BA0] 62 ED D8 C4 A0 8D 55 B2 84 1C A4 CE 87 EF 24 EE b.....U. ......$.
+[1BB0] BC CE 40 09 EB 05 0B D1 14 31 50 32 2F B6 A8 97 ..@..... .1P2/...
+[1BC0] 17 4B A7 95 01 50 6E 0E 23 49 9C 72 21 91 00 00 .K...Pn. #I.r!...
+[1BD0] 00 00 00 00 00 01 00 00 00 01 00 00 00 17 4B 54 ........ ......KT
+[1BE0] 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C EST.SAMB A.EXAMPL
+[1BF0] 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 E.COM... .adminis
+[1C00] 74 72 61 74 6F 72 00 00 00 01 00 00 00 02 00 00 trator.. ........
+[1C10] 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[1C20] 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 66 AMPLE.CO M....cif
+[1C30] 73 00 00 00 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 s....LOC ALKTEST6
+[1C40] 00 17 00 00 00 10 1D C8 5E 46 48 82 F9 29 DB C6 ........ ^FH..)..
+[1C50] A6 F1 72 6D 8D E9 4D 99 4F 6A 4D 99 85 09 7D 44 ..rm..M. OjM...}D
+[1C60] 0B 68 00 00 00 00 00 40 28 00 00 00 00 00 00 00 .h.....@ (.......
+[1C70] 00 00 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 .......a ...0....
+[1C80] 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 ........ KTEST.SA
+[1C90] 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 MBA.EXAM PLE.COM.
+[1CA0] 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 69 .0...... ..0...ci
+[1CB0] 66 73 1B 0B 4C 4F 43 41 4C 4B 54 45 53 54 36 A3 fs..LOCA LKTEST6.
+[1CC0] 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 03 02 01 ...0.... ........
+[1CD0] 02 A2 82 03 9C 04 82 03 98 66 D8 19 46 FA CB 73 ........ .f..F..s
+[1CE0] 2D CF 88 FD 4A EE 07 48 DA 0E BC 58 30 43 40 A4 -...J..H ...X0C@.
+[1CF0] 9C 00 0F 3B 17 C1 2D F5 9C 3E D9 2F 1D CA 01 9B ...;..-. .>./....
+[1D00] D7 2E EC D7 70 ED 8B 8B 1B 5E F2 4E EE DD 0F C0 ....p... .^.N....
+[1D10] 8D 61 E5 D7 0A 56 00 32 B1 DB 91 37 29 0F 2F 85 .a...V.2 ...7)./.
+[1D20] EE A8 43 BA A5 B8 D4 19 74 33 F0 69 52 E1 58 98 ..C..... t3.iR.X.
+[1D30] 83 D6 16 0B 44 A9 63 9B D4 4E 6E A7 3E CD 9A 96 ....D.c. .Nn.>...
+[1D40] 4D C4 96 F5 07 6D 29 B6 ED 2A 62 3D 53 22 33 D1 M....m). .*b=S"3.
+[1D50] 95 E9 DF 74 4C 2A E2 29 AF 5B 69 B0 48 2D AD 94 ...tL*.) .[i.H-..
+[1D60] FD A5 1D 54 D8 E2 5E C1 68 6F BA 02 01 79 C3 C9 ...T..^. ho...y..
+[1D70] 97 0B 76 66 45 E2 3B 10 17 95 40 46 E4 85 B9 87 ..vfE.;. ..@F....
+[1D80] BB CF CF 19 8C 3A C0 EA 38 3B B9 E9 4B 05 89 E5 .....:.. 8;..K...
+[1D90] 27 8C 62 95 BC 0D 65 F0 D2 C0 5E BC 65 01 D5 0B '.b...e. ..^.e...
+[1DA0] CB 17 31 0F 06 49 4F A2 4A 70 77 DB BD 92 5B 37 ..1..IO. Jpw...[7
+[1DB0] 5C EC 06 DF C5 E2 31 C8 40 09 11 68 14 E7 7D CE \.....1. @..h..}.
+[1DC0] 54 4F 52 61 31 2C 1C 53 52 DB BE D8 95 39 EE 7D TORa1,.S R....9.}
+[1DD0] C6 CE C8 22 95 92 97 97 3D 5E 66 0F AD DC C2 4E ...".... =^f....N
+[1DE0] 2E 2B 9F 63 20 30 DF B7 C1 D4 65 AA 6F 2D 10 24 .+.c 0.. ..e.o-.$
+[1DF0] 07 20 8D 88 6E 4B 09 04 31 B6 A3 EB F7 37 32 0E . ..nK.. 1....72.
+[1E00] 0C 73 C6 F6 B8 4D D9 0C 4C 5B EC 10 6A 51 19 EA .s...M.. L[..jQ..
+[1E10] 3F FF 46 E7 73 16 A7 1F 33 98 7C 9B AD 5A 23 A9 ?.F.s... 3.|..Z#.
+[1E20] 40 7C 0F DF EE 0F AA C7 E8 63 07 98 3A 4A 0D 18 @|...... .c..:J..
+[1E30] 62 01 21 B2 AE A5 69 B0 C1 15 51 BA 97 D2 C5 42 b.!...i. ..Q....B
+[1E40] 5B C5 30 38 18 A9 48 AB D7 FC A1 BC 9F 71 E7 EA [.08..H. .....q..
+[1E50] 18 54 42 DA D6 A4 FC C1 DC F3 12 30 62 AC 98 E1 .TB..... ...0b...
+[1E60] 7D 2B 34 1E 52 4C 26 67 32 D9 44 1A 08 27 0E DA }+4.RL&g 2.D..'..
+[1E70] D0 FC 84 66 35 81 D6 EB 98 46 6F 1E 47 E0 14 31 ...f5... .Fo.G..1
+[1E80] BE 47 80 65 AA 0B 20 D6 33 36 3B 0D 40 2F 5A 2E .G.e.. . 36;.@/Z.
+[1E90] 0E 01 BE 00 EB 33 3E 4B 32 91 F4 22 96 E5 5F D4 .....3>K 2..".._.
+[1EA0] D5 92 94 CC 5B 59 6A 3E D2 FB A0 4F 99 C4 07 8B ....[Yj> ...O....
+[1EB0] 6F 2B 14 37 CD 37 44 C0 1F 80 9C 43 46 F2 5E F4 o+.7.7D. ...CF.^.
+[1EC0] FE D3 39 70 61 BE 72 5B 3A 8F 37 95 78 1E AB D9 ..9pa.r[ :.7.x...
+[1ED0] E7 E9 DA FC 47 09 81 A0 0D 62 E1 F9 34 36 D1 DB ....G... .b..46..
+[1EE0] E6 98 D8 F4 3E 77 5A 4D E2 5F 20 70 3D 3D 5B 34 ....>wZM ._ p==[4
+[1EF0] D9 FD A8 31 F7 D9 59 F7 A3 F0 66 F7 D9 AD 1C CD ...1..Y. ..f.....
+[1F00] D5 85 33 A0 87 22 31 D4 F3 67 80 68 20 A2 90 72 ..3.."1. .g.h ..r
+[1F10] 7A 6F 64 FD 68 82 9E 91 B8 E3 F7 6D 6C 38 74 F0 zod.h... ...ml8t.
+[1F20] 96 A2 F6 25 D7 92 58 14 60 9F AE 01 4C 0C 09 67 ...%..X. `...L..g
+[1F30] 3E 35 67 71 1E 2A 86 21 D3 60 61 98 16 94 67 0B >5gq.*.! .`a...g.
+[1F40] 52 76 63 93 BD A3 3B A9 F0 A2 6A B7 E6 0F 35 64 Rvc...;. ..j...5d
+[1F50] DA 6A EA 20 A6 3D 94 71 59 5E CB B2 D3 F9 4D FE .j. .=.q Y^....M.
+[1F60] 1B 4B D8 64 C8 3B 7A A8 E6 D2 D5 76 71 26 D4 5C .K.d.;z. ...vq&.\
+[1F70] DA 1A 55 17 F2 16 C9 2F 77 DB 95 19 48 A5 AC D0 ..U..../ w...H...
+[1F80] C3 31 9C 0A CC 1B 44 11 6B 7C 88 7A 5D CF 6E 12 .1....D. k|.z].n.
+[1F90] DA EF C5 C7 34 1D F4 CC EA 37 24 4B B3 0F C1 A3 ....4... .7$K....
+[1FA0] F2 29 A0 D8 93 39 C6 16 57 D5 BF 57 BF 6C 7E F7 .)...9.. W..W.l~.
+[1FB0] 90 E0 EB A3 8B 07 56 9C EC 15 3E 21 DA A5 7C 00 ......V. ..>!..|.
+[1FC0] 3C F9 D2 A7 1C 6F 16 25 31 C5 28 A7 EA F3 47 31 <....o.% 1.(...G1
+[1FD0] 50 DD E1 ED 0A 93 DB 85 CC 6B 4B 2C 7F E8 F8 2D P....... .kK,...-
+[1FE0] A9 6D 1D 0A 87 F2 10 8C 82 2F 9B D4 9B 92 8C 77 .m...... ./.....w
+[1FF0] 40 50 42 1E 42 C4 0A 4F E3 6C 6C DC 81 C4 1E BB @PB.B..O .ll.....
+[2000] F0 7D CF 3C 73 22 5B C3 1A 97 35 EE 3A CD 6D F3 .}.<s"[. ..5.:.m.
+[2010] 68 A3 C5 65 7E E9 54 C0 E3 7D 6A 32 4C D1 3E D0 h..e~.T. .}j2L.>.
+[2020] 78 4B BF 18 9F A5 25 4A 92 1E 6C 8F 01 D6 59 D7 xK....%J ..l...Y.
+[2030] CF 2E A0 CC 98 F6 75 28 2F F7 2A 70 28 A9 45 1F ......u( /.*p(.E.
+[2040] 75 C2 4E 62 ED D8 C4 A0 8D 55 B2 84 1C A4 CE 87 u.Nb.... .U......
+[2050] EF 24 EE BC CE 40 09 EB 05 0B D1 14 31 50 32 2F .$...@.. ....1P2/
+[2060] B6 A8 97 17 4B A7 95 01 50 6E 0E 23 49 9C 72 21 ....K... Pn.#I.r!
+[2070] 91 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 ........ ........
+[2080] 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 .KTEST.S AMBA.EXA
+[2090] 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 MPLE.COM ....admi
+[20A0] 6E 69 73 74 72 61 74 6F 72 00 00 00 01 00 00 00 nistrato r.......
+[20B0] 02 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[20C0] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 .EXAMPLE .COM....
+[20D0] 68 6F 73 74 00 00 00 0B 6C 6F 63 61 6C 6B 74 65 host.... localkte
+[20E0] 73 74 36 00 17 00 00 00 10 72 47 04 38 B6 E6 F0 st6..... .rG.8...
+[20F0] 44 9E 9F 27 66 E1 69 9C 9A 4D 99 4F 6A 4D 99 90 D..'f.i. .M.OjM..
+[2100] F5 7D 44 0B 68 00 00 00 00 00 40 28 00 00 00 00 .}D.h... ..@(....
+[2110] 00 00 00 00 00 00 00 00 03 FA 61 82 03 F6 30 82 ........ ..a...0.
+[2120] 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 ........ ...KTEST
+[2130] 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 .SAMBA.E XAMPLE.C
+[2140] 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B OM..0... .....0..
+[2150] 04 68 6F 73 74 1B 0B 6C 6F 63 61 6C 6B 74 65 73 .host..l ocalktes
+[2160] 74 36 A3 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 t6....0. ........
+[2170] 03 02 01 02 A2 82 03 9C 04 82 03 98 58 95 95 EB ........ ....X...
+[2180] CB 8F 68 D4 77 43 0F 3B 44 B4 15 DA 40 6D FD E9 ..h.wC.; D...@m..
+[2190] 85 D3 2F CD B5 1E 96 CD F6 E9 67 91 36 08 9E B4 ../..... ..g.6...
+[21A0] B3 47 70 7A B3 4E 82 5A 4F 8E 4B F5 8D 04 E4 5C .Gpz.N.Z O.K....\
+[21B0] C4 D8 0C AF 08 25 F9 C1 64 B2 3A 35 26 E9 B2 72 .....%.. d.:5&..r
+[21C0] 66 B5 E9 81 FC BE 12 1B CC 8A A5 82 31 F6 7F C3 f....... ....1...
+[21D0] 5A 19 A3 31 F2 99 14 1E 64 E4 41 E8 C7 C3 F3 DF Z..1.... d.A.....
+[21E0] F5 65 7D B0 9F DC 5D 25 1D 1A A8 EA AA 88 6D F4 .e}...]% ......m.
+[21F0] 7C 25 9F 53 F6 A6 8F B1 24 AF 98 FE 53 7B 35 3C |%.S.... $...S{5<
+[2200] DB EC 7F 09 74 E9 C4 8D 20 B4 47 08 0E 32 B8 C9 ....t... .G..2..
+[2210] 45 27 12 F9 8E F5 D6 C2 DD 1A 96 0E 68 5F 39 65 E'...... ....h_9e
+[2220] 72 C7 BD 8E 04 0E 13 E1 03 27 AC 50 80 76 E6 7A r....... .'.P.v.z
+[2230] 8E F4 C2 72 4F 68 B3 34 00 A9 54 41 DA FD 96 94 ...rOh.4 ..TA....
+[2240] 29 A1 59 15 2F DB 6C 94 85 49 C5 D0 6D 48 B0 C4 ).Y./.l. .I..mH..
+[2250] 65 D0 95 1D DB 3D 25 D0 75 50 D4 CF FA 2F 71 57 e....=%. uP.../qW
+[2260] BD 6C 1C 59 E1 C3 5B C7 24 95 FF B0 20 EF 6A DB .l.Y..[. $... .j.
+[2270] 79 87 67 91 94 E9 16 E2 BB 74 7A 08 E1 6A 36 5F y.g..... .tz..j6_
+[2280] DF 11 AB 35 9B 3E 32 48 83 89 41 4E 06 BF F9 BB ...5.>2H ..AN....
+[2290] EC E4 D7 6D 77 C4 55 22 DF F7 91 4D CB C5 01 A5 ...mw.U" ...M....
+[22A0] BA 2D 1E 92 76 04 E8 02 2F 5E AF 1C B3 B7 A6 FB .-..v... /^......
+[22B0] 3A 9F D9 7C 6D DA B4 8F 31 00 A5 30 F2 76 72 9B :..|m... 1..0.vr.
+[22C0] 62 97 E0 56 E5 E4 C7 6B 8B FC 84 75 57 66 6E D7 b..V...k ...uWfn.
+[22D0] B7 41 6F 61 F4 5B 0F 87 68 F6 54 02 26 1B 1F B7 .Aoa.[.. h.T.&...
+[22E0] 60 D6 E7 FA 4F C7 DB 35 58 EC 13 21 D4 C6 A1 27 `...O..5 X..!...'
+[22F0] BA E7 82 DF 29 FB 9D 5D E8 35 28 C9 9C 4E D7 BE ....)..] .5(..N..
+[2300] 2F 6D F1 E8 0B 5A 74 C9 93 9F AD 42 24 4B B7 3B /m...Zt. ...B$K.;
+[2310] 38 2A 11 CF F0 BD 85 40 48 D8 9D E7 6B 65 70 42 8*.....@ H...kepB
+[2320] 60 DA 9B 65 CB C8 C5 D7 40 3A 12 DC 64 AF 82 54 `..e.... @:..d..T
+[2330] 34 05 38 4F C6 FB 38 E2 73 A9 89 B7 FC 33 15 85 4.8O..8. s....3..
+[2340] 9E CA E9 E0 89 18 18 84 02 65 B4 74 5B D4 A1 6F ........ .e.t[..o
+[2350] 5F 79 20 CB D7 36 C8 6D 5B 1E 5E 0C 82 16 9F CC _y ..6.m [.^.....
+[2360] 5A 1E 57 C1 B6 94 51 87 A1 3D 12 D4 8B FE 0F 93 Z.W...Q. .=......
+[2370] ED 53 A3 F4 88 3C 35 05 89 FE AF 0B 36 62 E3 2F .S...<5. ....6b./
+[2380] 5C 4A 0E 07 67 39 A3 8E C0 45 07 7F 73 32 BC DE \J..g9.. .E..s2..
+[2390] 2D 00 8B 47 79 3D 1C A1 90 AE B6 8F 83 B2 1B 31 -..Gy=.. .......1
+[23A0] EE E4 F2 C5 C1 4A E2 4A 2F 28 F0 AA 19 43 6A 14 .....J.J /(...Cj.
+[23B0] B1 42 61 90 34 2E EE 3D 16 9F 5D 9F 7A A2 01 7A .Ba.4..= ..].z..z
+[23C0] 4B 96 FA 4D C9 85 1A 75 27 B7 6B FD 4D 7D 9C 65 K..M...u '.k.M}.e
+[23D0] 97 DB 05 CC 76 68 EA 05 5D 5D BB BD 51 4B 5B F2 ....vh.. ]]..QK[.
+[23E0] 48 59 BD 1E AD 56 D4 69 A5 75 CD ED EC B1 3E AB HY...V.i .u....>.
+[23F0] FA B7 F8 8D 4F BE 95 63 38 1C 4C 70 26 C4 3A 21 ....O..c 8.Lp&.:!
+[2400] 80 61 05 3A D4 E2 28 2C 85 01 5A DA FC 10 60 F3 .a.:..(, ..Z...`.
+[2410] 74 0C FD DB 2F 5B 25 4B 14 E4 7D 8A DB 85 12 D2 t.../[%K ..}.....
+[2420] D7 69 CD B5 B1 93 CE E5 E6 4D 57 D3 C2 D3 2E A0 .i...... .MW.....
+[2430] 08 37 09 CD 19 99 09 FA 33 68 4A E0 92 46 21 0C .7...... 3hJ..F!.
+[2440] 99 9F DA 05 15 20 8B 3D 7C 7B CA D6 81 AC AA 83 ..... .= |{......
+[2450] 48 C8 24 4C C8 FC A5 14 2C BC 49 1A 1C 49 61 1D H.$L.... ,.I..Ia.
+[2460] 24 86 42 B1 37 6A C8 3A AC 18 CC C0 50 84 12 48 $.B.7j.: ....P..H
+[2470] 8B 29 0A 49 26 A4 E2 B9 E5 96 E7 37 C3 DE 4C 23 .).I&... ...7..L#
+[2480] D2 D4 62 14 8F 1E 72 39 CF 03 BC A3 00 C7 63 51 ..b...r9 ......cQ
+[2490] A9 6B E4 3E B2 65 A1 A2 BB EC 06 41 85 50 22 02 .k.>.e.. ...A.P".
+[24A0] 46 2F 72 2B 32 1A A4 2D 85 94 02 47 69 8D AD 6D F/r+2..- ...Gi..m
+[24B0] 66 AB D4 E4 29 C8 C7 DA F4 18 31 2A DF 50 6A 05 f...)... ..1*.Pj.
+[24C0] D6 47 26 C4 F9 87 0F 35 24 6E 72 D6 23 7D 3A 94 .G&....5 $nr.#}:.
+[24D0] 14 8D E8 57 AA BA D7 CF A9 2D E7 4C 10 7C D8 0D ...W.... .-.L.|..
+[24E0] 51 30 1F E1 FB E5 E2 6C EE AA 65 2F D8 22 05 67 Q0.....l ..e/.".g
+[24F0] 87 4D 4D D2 11 3D B4 1E AA 20 3F 76 E3 94 93 6D .MM..=.. . ?v...m
+[2500] AC 10 05 AF 09 BD 67 86 C5 83 93 D6 1C D3 81 D9 ......g. ........
+[2510] B1 3B E1 76 00 00 00 00 00 00 00 01 00 00 00 01 .;.v.... ........
+[2520] 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E ....KTES T.SAMBA.
+[2530] 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 EXAMPLE. COM....a
+[2540] 64 6D 69 6E 69 73 74 72 61 74 6F 72 00 00 00 01 dministr ator....
+[2550] 00 00 00 02 00 00 00 17 4B 54 45 53 54 2E 53 41 ........ KTEST.SA
+[2560] 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 MBA.EXAM PLE.COM.
+[2570] 00 00 04 68 6F 73 74 00 00 00 0B 4C 4F 43 41 4C ...host. ...LOCAL
+[2580] 4B 54 45 53 54 36 00 17 00 00 00 10 55 6E 3E FC KTEST6.. ....Un>.
+[2590] E2 F4 40 51 19 E6 6E EB 23 4C 48 8E 4D 99 4F 6A ..@Q..n. #LH.M.Oj
+[25A0] 4D 99 90 FC 7D 44 0B 68 00 00 00 00 00 40 28 00 M...}D.h .....@(.
+[25B0] 00 00 00 00 00 00 00 00 00 00 00 03 FA 61 82 03 ........ .....a..
+[25C0] F6 30 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 .0...... ......KT
+[25D0] 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C EST.SAMB A.EXAMPL
+[25E0] 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 E.COM..0 ........
+[25F0] 30 13 1B 04 68 6F 73 74 1B 0B 4C 4F 43 41 4C 4B 0...host ..LOCALK
+[2600] 54 45 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 02 TEST6... .0......
+[2610] 01 17 A1 03 02 01 02 A2 82 03 9C 04 82 03 98 6E ........ .......n
+[2620] 87 B7 7B 3A 7E EF 4A 1B 29 C9 E3 C4 1F 42 4F 0E ..{:~.J. )....BO.
+[2630] C8 AC AC 4E A2 77 1D DA 93 37 F1 AF DA A3 75 2D ...N.w.. .7....u-
+[2640] 12 8B 40 34 23 0E 8E A9 90 58 46 42 42 39 31 D6 ..@4#... .XFBB91.
+[2650] 03 9E 5D 81 D9 E8 F6 08 2B D9 96 88 8A 2F F1 CC ..]..... +..../..
+[2660] F2 EA 9E 9A 4B 31 B6 04 2D 3D 4C 7F 92 DE 3B 04 ....K1.. -=L...;.
+[2670] 19 EE 28 D0 83 81 C3 46 CD 74 23 4C 14 34 DE 62 ..(....F .t#L.4.b
+[2680] 0A AC E5 12 16 75 E9 A8 4B 32 78 CC 8D AE A2 E5 .....u.. K2x.....
+[2690] 6D E8 09 70 76 52 F5 E5 18 F7 E7 91 15 6A 69 AB m..pvR.. .....ji.
+[26A0] B8 62 DD 80 F5 28 6D DF ED 10 DA AC FB 92 27 CF .b...(m. ......'.
+[26B0] 98 B5 77 9D A5 96 E6 9A CC B9 C3 91 78 22 35 9C ..w..... ....x"5.
+[26C0] A1 13 A3 20 28 D1 16 E5 3E 4A 85 1E 12 0B CA 4D ... (... >J.....M
+[26D0] C6 C8 03 C8 28 2C D8 29 5D 9A 76 4A 92 13 43 56 ....(,.) ].vJ..CV
+[26E0] AF F7 C1 71 25 72 5C 38 75 1C 07 F1 5E 86 05 72 ...q%r\8 u...^..r
+[26F0] 6F 69 95 42 B6 F2 DA A9 91 06 9F B9 54 20 33 A5 oi.B.... ....T 3.
+[2700] 31 60 3B 54 DC 3A 95 34 96 26 07 52 6B 0E 1D 3B 1`;T.:.4 .&.Rk..;
+[2710] D9 F8 48 20 AC CD 05 3B 99 F8 EE DB 83 28 CD C7 ..H ...; .....(..
+[2720] 2F 45 00 7E 2F 0A 65 7A D1 9E 95 4B EE C3 34 93 /E.~/.ez ...K..4.
+[2730] A8 C7 DF 03 8B 14 D0 FC CE 56 90 AC EE 93 C5 D3 ........ .V......
+[2740] F7 12 24 69 0B 20 8D A2 65 87 55 26 2A F9 9A 88 ..$i. .. e.U&*...
+[2750] D7 0D 86 61 D6 92 B6 FE E5 D1 66 F9 1F 9D F4 04 ...a.... ..f.....
+[2760] 48 A6 39 BC 54 20 EA 10 21 E9 6D 30 46 1D C2 1C H.9.T .. !.m0F...
+[2770] A4 E8 B4 63 85 37 27 25 80 52 41 60 C7 A1 32 21 ...c.7'% .RA`..2!
+[2780] 43 90 02 E6 5F 5A E9 4E AF F9 B5 13 BD 42 BD A3 C..._Z.N .....B..
+[2790] A5 4D 10 45 83 4D 92 18 1F C9 CF FB 84 29 89 23 .M.E.M.. .....).#
+[27A0] AC 71 4B 89 1B 52 E5 06 8C 3E 7C 88 CB D3 B3 CF .qK..R.. .>|.....
+[27B0] B9 7A 67 D6 24 F4 AC 00 A6 AD 91 30 9A 95 53 F1 .zg.$... ...0..S.
+[27C0] 48 06 A6 39 DB CF DC 9D C9 55 76 26 5E C1 DB 5D H..9.... .Uv&^..]
+[27D0] B3 5B 3E AE 1A A0 10 BA 82 21 83 44 02 E0 99 33 .[>..... .!.D...3
+[27E0] 40 BA 29 9E 28 E5 73 4C 23 94 A2 4F BF 07 ED 4F @.).(.sL #..O...O
+[27F0] 7C 45 9B 30 C8 41 6B 0A 55 13 6E F5 AD 7A 0C B2 |E.0.Ak. U.n..z..
+[2800] EA FF D0 06 13 4D F3 24 82 7F F6 51 2F 4A 4F 0D .....M.$ ...Q/JO.
+[2810] 37 F8 14 6B E9 E4 82 BB 3A 75 63 63 12 E8 78 6F 7..k.... :ucc..xo
+[2820] 6F FC 6C D3 4B A6 F1 CC 2A F1 7D EB 82 26 2F D0 o.l.K... *.}..&/.
+[2830] A1 8B 3E 9A 71 D7 91 D3 08 E6 FD 62 1B 84 13 2D ..>.q... ...b...-
+[2840] 8E A0 A0 C3 85 78 2F 0D F8 E7 10 FC CB 05 A7 B9 .....x/. ........
+[2850] 9A 33 90 B5 9B 26 E3 23 98 B0 91 4B EB 32 37 D6 .3...&.# ...K.27.
+[2860] F4 ED 61 08 D8 75 CC 03 83 2C 3C CF 21 63 9C F6 ..a..u.. .,<.!c..
+[2870] AF 5B 4F 12 07 74 17 CD 98 BB E7 5E C7 17 2D C4 .[O..t.. ...^..-.
+[2880] 87 A4 74 6D 5E CE DB A3 01 B9 AD 20 73 38 78 22 ..tm^... ... s8x"
+[2890] 3D 45 F5 51 77 C6 47 63 45 61 81 D9 FF 31 90 C4 =E.Qw.Gc Ea...1..
+[28A0] 6F 5A F8 FE 6A 56 5B D4 EE EC 49 C7 A7 51 AE 5C oZ..jV[. ..I..Q.\
+[28B0] 85 53 70 3D 1A 49 83 59 CF 65 58 B3 48 7E 04 9E .Sp=.I.Y .eX.H~..
+[28C0] C7 64 8A 05 73 E3 DC 1A 65 5D 4F 41 01 56 73 90 .d..s... e]OA.Vs.
+[28D0] 61 F3 84 1F FF CF 46 B2 06 46 56 97 93 B9 DB 32 a.....F. .FV....2
+[28E0] 2A 64 8A 48 02 05 84 E9 FA 76 8B 94 96 89 A0 73 *d.H.... .v.....s
+[28F0] 20 75 4D 52 1D 23 13 D1 83 D7 5D 59 23 6A 87 C1 uMR.#.. ..]Y#j..
+[2900] 09 3E 01 3A 28 65 42 8C 35 F1 91 EA 6A 1F 83 0D .>.:(eB. 5...j...
+[2910] 8F 57 69 81 D4 A2 D2 EA 0C BF AF 95 A3 F4 90 15 .Wi..... ........
+[2920] 61 34 F2 6C 8B D0 DA B5 1E 43 AC CE C7 8A 1B 2B a4.l.... .C.....+
+[2930] 29 2B 89 1C C5 53 C8 04 F7 1E 46 72 F3 A8 CE F7 )+...S.. ..Fr....
+[2940] 59 76 55 E7 53 1C A2 9F D8 23 F7 EA 71 B0 74 83 YvU.S... .#..q.t.
+[2950] 71 95 3E DC A6 FA 2D A4 42 13 93 8B 2B FA A2 70 q.>...-. B...+..p
+[2960] 25 21 2D F6 E1 26 56 DF 58 79 25 16 E8 C9 03 EC %!-..&V. Xy%.....
+[2970] 72 5F 35 CF 59 6B E1 AD 85 85 7B AB 78 F2 0D AC r_5.Yk.. ..{.x...
+[2980] AB 89 F2 DA 85 E7 DE 09 77 99 EC 7C F3 97 1F 71 ........ w..|...q
+[2990] 3C DB 09 44 7A 3C 69 E5 03 B0 6D 4D 3B 6B 4C D5 <..Dz<i. ..mM;kL.
+[29A0] AB 52 2F 6F 81 2B 51 5B D2 66 44 1E B7 66 5D 7F .R/o.+Q[ .fD..f].
+[29B0] 09 6A 92 27 27 62 08 00 00 00 00 .j.''b.. ...
+dump OK
diff --git a/source3/selftest/ktest-krb5_ccache-3 b/source3/selftest/ktest-krb5_ccache-3
new file mode 100644
index 0000000..a056beb
--- /dev/null
+++ b/source3/selftest/ktest-krb5_ccache-3
Binary files differ
diff --git a/source3/selftest/ktest-krb5_ccache-3.txt b/source3/selftest/ktest-krb5_ccache-3.txt
new file mode 100644
index 0000000..2d3e669
--- /dev/null
+++ b/source3/selftest/ktest-krb5_ccache-3.txt
@@ -0,0 +1,832 @@
+pull returned Success
+ CCACHE: struct CCACHE
+ pvno : 0x05 (5)
+ version : 0x04 (4)
+ optional_header : union OPTIONAL_HEADER(case 0x4)
+ v4header: struct V4HEADER
+ v4tags: struct V4TAGS
+ tag: struct V4TAG
+ tag : 0x0001 (1)
+ field : union FIELD(case 0x1)
+ deltatime_tag: struct DELTATIME_TAG
+ kdc_sec_offset : 0
+ kdc_usec_offset : 0
+ further_tags : DATA_BLOB length=0
+ principal: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ cred: struct CREDENTIAL
+ client: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ server: struct PRINCIPAL
+ name_type : 0x00000000 (0)
+ component_count : 0x00000002 (2)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(2)
+ components : 'krbtgt'
+ components : 'KTEST.SAMBA.EXAMPLE.COM'
+ keyblock: struct KEYBLOCK
+ enctype : 0x0017 (23)
+ data : DATA_BLOB length=16
+[0000] E5 E4 15 C8 A8 0F 4D 95 F9 1B E3 B9 98 CA A1 7F ......M. ........
+ authtime : 0x4d9b9045 (1302040645)
+ starttime : 0x4d9b9045 (1302040645)
+ endtime : 0x7d464c43 (2101759043)
+ renew_till : 0x7d464c43 (2101759043)
+ is_skey : 0x00 (0)
+ ticket_flags : 0x40e00000 (1088421888)
+ addresses: struct ADDRESSES
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ authdata: struct AUTHDATA
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ ticket : DATA_BLOB length=1032
+[0000] 61 82 04 04 30 82 04 00 A0 03 02 01 05 A1 19 1B a...0... ........
+[0010] 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 .KTEST.S AMBA.EXA
+[0020] 4D 50 4C 45 2E 43 4F 4D A2 2C 30 2A A0 03 02 01 MPLE.COM .,0*....
+[0030] 00 A1 23 30 21 1B 06 6B 72 62 74 67 74 1B 17 4B ..#0!..k rbtgt..K
+[0040] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0050] 4C 45 2E 43 4F 4D A3 82 03 AE 30 82 03 AA A0 03 LE.COM.. ..0.....
+[0060] 02 01 17 A1 03 02 01 01 A2 82 03 9C 04 82 03 98 ........ ........
+[0070] 01 40 48 A6 B8 F0 DA 43 54 A5 18 CF B0 15 CB 68 .@H....C T......h
+[0080] 9F A0 69 44 87 A9 FF 06 25 B9 29 48 59 64 26 48 ..iD.... %.)HYd&H
+[0090] 96 7C 46 6A 79 E5 F0 77 DB 46 6C 20 A1 59 D9 F8 .|Fjy..w .Fl .Y..
+[00A0] 6A 8A 2D B5 D9 EF A4 54 DE 19 20 C0 7B 93 D4 3D j.-....T .. .{..=
+[00B0] ED 72 35 AF 9D 87 75 9E 44 01 A4 6C D9 EA 94 A3 .r5...u. D..l....
+[00C0] 18 C6 42 75 E3 0A 0C 76 9A AE 75 BC A3 02 91 BC ..Bu...v ..u.....
+[00D0] 2D BB 3C 23 73 A6 1A A7 8A 3E 85 42 5D 1F 5D 7D -.<#s... .>.B].]}
+[00E0] 0B 1F C3 88 2A 93 40 F9 E9 18 7D 3F 73 DA AC 1F ....*.@. ..}?s...
+[00F0] E7 7B C3 B8 14 56 C3 63 86 5B AF C9 C3 21 9F 94 .{...V.c .[...!..
+[0100] B4 67 06 60 7F 56 2D F4 C7 22 CD B4 1C 14 B7 5B .g.`.V-. .".....[
+[0110] 26 67 9D 18 28 B5 5D C2 FC 13 B6 CA 9F AB CD 32 &g..(.]. .......2
+[0120] 71 D5 51 5F A2 11 5A 5D 4A B3 3B 1D D1 6B 4F 7D q.Q_..Z] J.;..kO}
+[0130] E9 54 F0 B4 AC 80 DE 27 80 C5 64 3C 0B 22 79 1C .T.....' ..d<."y.
+[0140] 9E D1 58 A1 3E 20 5A 9F E3 34 49 D8 16 C6 6B 2D ..X.> Z. .4I...k-
+[0150] 36 0E E2 C2 3F 44 DE 63 32 DB EB 78 50 A2 6F 37 6...?D.c 2..xP.o7
+[0160] 05 2B 13 D4 31 07 D4 2A C0 53 B1 30 39 79 C3 D8 .+..1..* .S.09y..
+[0170] C4 4C 30 97 E8 F9 DA ED 10 B0 D0 21 71 8B 56 F3 .L0..... ...!q.V.
+[0180] 0F 3A 2D 26 A2 3D AD 70 27 82 95 59 0A D7 7D 4E .:-&.=.p '..Y..}N
+[0190] 2D 76 96 4D 94 70 2A BB 26 3B 7E FC E1 59 5A 55 -v.M.p*. &;~..YZU
+[01A0] 04 A2 DA 27 AD 46 70 45 43 C0 FB C1 42 7F F0 CB ...'.FpE C...B...
+[01B0] 21 D2 CD 54 35 7C 60 13 EE BB BB 60 6B 91 2B BE !..T5|`. ...`k.+.
+[01C0] 91 8A CF 49 29 F8 60 D1 AB A5 51 B5 5E 4B B2 3A ...I).`. ..Q.^K.:
+[01D0] F4 56 3A 89 2D 88 D0 73 08 A6 FB D8 6E B3 B1 4E .V:.-..s ....n..N
+[01E0] D8 90 27 58 D2 53 40 B2 A0 3C 40 4D E9 21 C6 83 ..'X.S@. .<@M.!..
+[01F0] FC 15 14 F0 8C 08 46 C5 29 14 E3 84 CC 2C 56 C9 ......F. )....,V.
+[0200] 20 53 45 34 D0 BE E0 CC F7 F1 15 D4 D4 B1 3C 43 SE4.... ......<C
+[0210] EB 5E 9D 33 07 B4 5B E7 D8 24 B0 EB 7B 27 24 6B .^.3..[. .$..{'$k
+[0220] 2A 90 C9 17 D9 24 CF FD 56 28 D7 73 74 03 2F DA *....$.. V(.st./.
+[0230] C4 E0 B3 78 E4 9A 60 4D 5C C7 F5 CF 9C 14 7C B6 ...x..`M \.....|.
+[0240] 1B 5D 76 D1 E3 73 73 2F 41 BD E3 E7 F0 92 B4 5B .]v..ss/ A......[
+[0250] 07 B4 16 77 DC 3C 28 A4 92 82 C5 7C CA 00 9C 77 ...w.<(. ...|...w
+[0260] B8 28 7F D0 3F EA 2B C1 79 2B 73 FF E0 E0 A5 17 .(..?.+. y+s.....
+[0270] 02 CA 6C B6 02 D2 51 D3 CE 6F 5B 56 E0 7B 38 22 ..l...Q. .o[V.{8"
+[0280] 76 52 48 2D 0A 2F 15 58 A9 FE 03 65 E1 D5 A8 60 vRH-./.X ...e...`
+[0290] E3 5D E6 53 D8 AA 05 D0 90 61 EF B6 28 4A B9 84 .].S.... .a..(J..
+[02A0] 56 79 80 D2 53 08 1D 17 C4 05 4E F8 04 10 2B CF Vy..S... ..N...+.
+[02B0] 08 DD 61 68 27 21 A5 8A C0 35 6A 0A 94 6D 9E FD ..ah'!.. .5j..m..
+[02C0] C9 45 AC E3 4F 60 BB 96 AF D4 4E 71 A9 D9 BE 33 .E..O`.. ..Nq...3
+[02D0] DC 61 8B 14 77 6C A7 72 70 02 65 62 32 9C 8E 53 .a..wl.r p.eb2..S
+[02E0] C9 A3 5B B9 14 3C 00 A2 1D C7 CD 36 5B 5F BE 40 ..[..<.. ...6[_.@
+[02F0] 28 E2 58 0D D1 05 53 78 F0 86 0F 80 1A 6A 1D DC (.X...Sx .....j..
+[0300] D4 CD F2 83 0E 25 E1 60 DB C7 F4 B6 05 4F 0D 11 .....%.` .....O..
+[0310] A4 AE A5 F8 6D 14 CF DF 03 C5 27 75 75 B5 0C F1 ....m... ..'uu...
+[0320] C3 01 F9 A4 FD 2E 0B BD 51 A8 C1 3B DE 48 CF 3A ........ Q..;.H.:
+[0330] CF B3 41 23 9A 9D 0C 79 11 7C 9B D3 71 43 4E 9D ..A#...y .|..qCN.
+[0340] B5 52 19 28 2C A0 4E 0E 8D 7A 84 9A B9 A0 EB FA .R.(,.N. .z......
+[0350] 6E A1 DF B9 2F 6B FE 5E AE 85 D1 6B A2 C5 BE 07 n.../k.^ ...k....
+[0360] E7 D6 33 3A 0F 2B ED FB 30 6F 88 1E F9 09 CC C3 ..3:.+.. 0o......
+[0370] 8F 59 A0 D4 8D 9F A6 08 B0 D3 ED EB 15 13 1B 8E .Y...... ........
+[0380] 19 C6 14 9C 25 E7 E9 EF 5A 67 7B CD 86 C4 D1 51 ....%... Zg{....Q
+[0390] 2B DE 27 30 D9 F5 6E F9 E4 3E CF 42 54 AE 42 61 +.'0..n. .>.BT.Ba
+[03A0] C5 22 B7 AE 51 76 8F 12 83 7F E1 9F 97 D8 31 38 ."..Qv.. ......18
+[03B0] A6 B9 11 B4 E1 BA 19 5B E4 A5 A3 6F 4B B3 03 93 .......[ ...oK...
+[03C0] 4C D6 1E 08 FC 94 D1 C5 7C AA 95 EB 9C 7A C2 57 L....... |....z.W
+[03D0] 60 CA 17 FF 8E 66 80 76 CB 35 46 26 C3 BD CA 83 `....f.v .5F&....
+[03E0] F0 04 08 0D 4C 5D B2 E4 7C 1C 82 28 D7 2C 42 B1 ....L].. |..(.,B.
+[03F0] 36 72 60 5E 26 4A 79 D0 41 94 3C 2C 65 0E 32 18 6r`^&Jy. A.<,e.2.
+[0400] B8 56 26 9D D3 84 78 BB .V&...x.
+ second_ticket : DATA_BLOB length=0
+ further_creds : DATA_BLOB length=4748
+[0000] 00 00 00 01 00 00 00 01 00 00 00 17 4B 54 45 53 ........ ....KTES
+[0010] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0020] 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 74 72 COM....a dministr
+[0030] 61 74 6F 72 00 00 00 01 00 00 00 02 00 00 00 17 ator.... ........
+[0040] 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D KTEST.SA MBA.EXAM
+[0050] 50 4C 45 2E 43 4F 4D 00 00 00 04 68 6F 73 74 00 PLE.COM. ...host.
+[0060] 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 00 17 ...local ktest6..
+[0070] 00 00 00 10 EA 0D 3A 24 41 21 F7 7D 7D A3 C5 BB ......:$ A!.}}...
+[0080] A4 88 F6 17 4D 9B 90 45 4D 9B 90 52 7D 46 4C 43 ....M..E M..R}FLC
+[0090] 00 00 00 00 00 40 28 00 00 00 00 00 00 00 00 00 .....@(. ........
+[00A0] 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 03 02 .....a.. .0......
+[00B0] 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[00C0] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 A.EXAMPL E.COM..0
+[00D0] 1C A0 03 02 01 01 A1 15 30 13 1B 04 68 6F 73 74 ........ 0...host
+[00E0] 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 A3 82 03 ..localk test6...
+[00F0] AE 30 82 03 AA A0 03 02 01 17 A1 03 02 01 03 A2 .0...... ........
+[0100] 82 03 9C 04 82 03 98 44 8B C4 7D BA 9F FE 59 F6 .......D ..}...Y.
+[0110] C1 DF 62 89 02 A4 55 54 AB D6 D6 2E 8B 5E 35 3D ..b...UT .....^5=
+[0120] D9 46 9D 8B 49 93 A6 66 5F 1A 8B 81 AD 09 19 E9 .F..I..f _.......
+[0130] 59 CE 58 18 50 63 4A A6 7D 6F 71 21 51 4A 41 C2 Y.X.PcJ. }oq!QJA.
+[0140] A1 FE B0 D5 0A 3D 38 9F E5 3B 72 A2 7A 59 22 A4 .....=8. .;r.zY".
+[0150] B7 1C A3 8D DB EA 5D A5 E2 D3 1D AE 42 D0 7F 75 ......]. ....B..u
+[0160] B5 E9 ED B5 04 7B 67 1E 28 90 7D 3D 1A 3E F6 62 .....{g. (.}=.>.b
+[0170] D0 A1 56 89 28 76 5C 19 1A FD 66 E5 F2 86 E7 58 ..V.(v\. ..f....X
+[0180] 93 31 90 C5 CD F8 71 96 56 21 15 13 F0 EA C2 CC .1....q. V!......
+[0190] 48 4C B4 50 EF F9 81 44 29 8A 75 C4 31 75 D1 BA HL.P...D ).u.1u..
+[01A0] E2 0B 05 B2 E0 EA 64 3A 11 45 84 3D 69 55 FF E6 ......d: .E.=iU..
+[01B0] 32 7E C9 CA C4 28 E8 40 B6 5E F9 26 0F 09 12 1F 2~...(.@ .^.&....
+[01C0] 1F D4 9C 9A 50 E8 B7 6D F8 4F 55 6E 2A D4 AC 6A ....P..m .OUn*..j
+[01D0] 79 D1 C2 2A 88 99 F8 39 75 36 F1 2D C7 89 0A C6 y..*...9 u6.-....
+[01E0] B4 C7 A1 7B F1 BF 22 87 A4 B2 93 22 54 A1 72 25 ...{..". ..."T.r%
+[01F0] AF 67 FE 20 D5 C8 29 47 28 FF 51 FB F9 4E 2C 17 .g. ..)G (.Q..N,.
+[0200] 10 BE 2E 13 8B 18 BE 3C A3 BE 50 49 A7 65 DD 2E .......< ..PI.e..
+[0210] CC EB D6 0F 47 4E DB 7E 08 D5 F0 37 79 36 8F 24 ....GN.~ ...7y6.$
+[0220] 34 28 86 89 EC A3 84 7F 44 4E 37 03 B5 D8 89 1C 4(...... DN7.....
+[0230] C7 AA AC 42 70 5F 96 73 35 8B 83 D1 16 24 27 C1 ...Bp_.s 5....$'.
+[0240] EC 0E AE 83 59 5A C2 EB C1 91 B6 3D BB 8D 21 49 ....YZ.. ...=..!I
+[0250] 63 41 3C 91 1D E9 01 C2 4F A9 E4 42 C1 FD 54 E3 cA<..... O..B..T.
+[0260] 7B 3B DF 24 3D 98 E9 84 F8 1D 8D CE 4D 85 AC 8A {;.$=... ....M...
+[0270] 12 15 48 C4 DA 1B 3C B8 FC A3 0B AF E2 4D 71 E9 ..H...<. .....Mq.
+[0280] 0A 28 53 DC 4E 6C 23 2C 73 26 50 FE 37 03 BF D1 .(S.Nl#, s&P.7...
+[0290] 5F 8A 39 4F 04 2E 4A CE 3C 90 11 0C DA 84 5C C3 _.9O..J. <.....\.
+[02A0] F8 BE C7 74 ED F4 CF 7E B2 AE 9B 47 D6 2A 1D 93 ...t...~ ...G.*..
+[02B0] 3F A8 8B 51 E9 A3 A0 59 55 DB E3 52 67 E3 DE FF ?..Q...Y U..Rg...
+[02C0] B1 56 74 A0 87 21 99 23 8C 8E D1 92 A6 3D 93 D6 .Vt..!.# .....=..
+[02D0] 4D 5B 84 2B B1 8D DD E4 F7 01 A6 6C 4A DF 3C 6E M[.+.... ...lJ.<n
+[02E0] A0 FA 74 93 BE 18 7C 30 29 9D B8 DB 5F D1 AA B7 ..t...|0 )..._...
+[02F0] 51 7C 2A 90 1A 8B 06 95 E1 80 0D 27 B2 6C 52 1C Q|*..... ...'.lR.
+[0300] C7 D1 E9 16 14 F1 6C 57 48 28 BD 13 B5 83 BA A7 ......lW H(......
+[0310] 75 31 69 52 03 38 69 13 62 ED C6 DC C2 01 C8 F1 u1iR.8i. b.......
+[0320] 45 02 4D 8C 64 CF 96 90 3E C2 08 EC 2B 8D 92 93 E.M.d... >...+...
+[0330] 4B 6D 22 B3 41 DE 85 35 2D 19 09 E5 68 8E 1F 98 Km".A..5 -...h...
+[0340] 1B F2 73 F2 D4 91 08 89 42 0C 05 8B 42 77 6B CC ..s..... B...Bwk.
+[0350] 18 78 43 1A 73 C2 7C E7 C2 23 28 56 F7 A0 19 B3 .xC.s.|. .#(V....
+[0360] 99 A6 25 4F C3 5E 70 EC 78 BB 30 15 36 77 B3 A6 ..%O.^p. x.0.6w..
+[0370] 89 98 B6 A0 85 CC 8F E7 41 40 B5 E0 89 93 25 04 ........ A@....%.
+[0380] B8 1D 0B 06 31 1D C7 30 52 E1 64 29 8C 64 B9 89 ....1..0 R.d).d..
+[0390] 1F 86 5A AD 74 15 1C C8 AF 37 7B 27 E0 C0 DB 73 ..Z.t... .7{'...s
+[03A0] 30 72 65 D3 C0 A5 07 61 E9 0C 07 A1 27 18 8F 50 0re....a ....'..P
+[03B0] DB CE FB 4C DD 75 98 F2 28 D2 76 FF F2 41 9F D5 ...L.u.. (.v..A..
+[03C0] 74 22 8A 03 73 B1 A8 B3 B8 80 93 E5 E2 CD 4B F2 t"..s... ......K.
+[03D0] 6B 99 DF 5B 5B C7 22 69 81 2A 8A CD 2A F9 9D 08 k..[[."i .*..*...
+[03E0] B8 B0 40 77 D3 43 8B AF 40 DD 0C CB 45 E3 88 CB ..@w.C.. @...E...
+[03F0] 06 AA 63 38 EB DD 72 89 03 0E DC 3E 97 3F 16 D4 ..c8..r. ...>.?..
+[0400] 1A 21 40 D8 30 BD B0 B4 04 C2 7A 22 43 15 A2 D8 .!@.0... ..z"C...
+[0410] 2F 08 28 3B 63 26 AA B3 1C B6 FC E4 0B 2A CD 0E /.(;c&.. .....*..
+[0420] A8 7C E8 11 33 03 D3 C5 6C 35 6A 5D 3C 5A 80 1A .|..3... l5j]<Z..
+[0430] BC 1C 54 DE 5C 6A E2 F3 A1 18 8E 47 88 8B 71 11 ..T.\j.. ...G..q.
+[0440] 09 2F 29 88 D9 BB DC 34 09 E1 2F 7E A7 E8 29 DC ./)....4 ../~..).
+[0450] F9 5A 1D 9E C8 A4 CC 52 8A E6 CB 4A 3F F9 77 F7 .Z.....R ...J?.w.
+[0460] 53 64 62 9E 5F E6 D7 F6 43 E6 9C 03 C9 55 B1 CB Sdb._... C....U..
+[0470] 25 40 74 AA E9 AB 34 58 E1 E8 9B B3 1D 9E 83 FD %@t...4X ........
+[0480] 7A BF DC 45 2D A8 9A F8 AF 9C 63 EF 1B 2B 9D CC z..E-... ..c..+..
+[0490] F3 08 74 EC 6E 40 8E 18 62 BD F3 87 66 87 67 00 ..t.n@.. b...f.g.
+[04A0] 00 00 00 00 00 00 01 00 00 00 01 00 00 00 17 4B ........ .......K
+[04B0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[04C0] 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 LE.COM.. ..admini
+[04D0] 73 74 72 61 74 6F 72 00 00 00 01 00 00 00 02 00 strator. ........
+[04E0] 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 ...KTEST .SAMBA.E
+[04F0] 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 XAMPLE.C OM....ci
+[0500] 66 73 00 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 fs....lo calktest
+[0510] 36 00 17 00 00 00 10 92 C6 A1 91 6D 55 01 4E BE 6....... ...mU.N.
+[0520] E4 3F E3 36 B0 D3 28 4D 9B 90 45 4D 9B 90 5A 7D .?.6..(M ..EM..Z}
+[0530] 46 4C 43 00 00 00 00 00 40 28 00 00 00 00 00 00 FLC..... @(......
+[0540] 00 00 00 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 ........ a...0...
+[0550] A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0560] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0570] A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 ..0..... ...0...c
+[0580] 69 66 73 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 ifs..loc alktest6
+[0590] A3 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 03 02 ....0... ........
+[05A0] 01 03 A2 82 03 9C 04 82 03 98 FE 09 00 80 36 35 ........ ......65
+[05B0] D4 6E 71 0C 33 22 36 9E 89 88 32 E3 34 4A 4C BF .nq.3"6. ..2.4JL.
+[05C0] 80 19 81 CC A0 CB 96 DB 31 F7 2A 19 75 DE 0E DA ........ 1.*.u...
+[05D0] D0 18 FA 9E 75 E6 E4 13 C9 BE 3F C0 1B AD 5B 98 ....u... ..?...[.
+[05E0] E9 FC A3 9D 16 FF C8 91 03 AC 8B E6 2D 15 B3 F1 ........ ....-...
+[05F0] 23 4E 25 9E 45 3A F8 8A 19 B7 71 52 A6 92 1C FB #N%.E:.. ..qR....
+[0600] 1F D4 4C 51 AF 9C 0E 73 D9 A8 D8 43 F2 64 71 BC ..LQ...s ...C.dq.
+[0610] AD B1 7B 8F BF 8D FF 72 89 0F 5E B6 C2 E3 C0 01 ..{....r ..^.....
+[0620] 98 41 AD 3F 6E DC 87 F5 9A E6 40 0C 17 0F 75 80 .A.?n... ..@...u.
+[0630] 0C 28 62 06 EB BF F8 69 8C 43 48 38 A8 AE F2 5E .(b....i .CH8...^
+[0640] 45 11 23 FB 6B 85 83 54 BA 60 39 CE 08 00 D1 05 E.#.k..T .`9.....
+[0650] 5F 6F 79 96 30 28 06 DD C7 75 52 8E 3C C4 3F FC _oy.0(.. .uR.<.?.
+[0660] C1 31 28 2C 64 3B D1 7E 2F C2 DB B0 E8 A8 EF C5 .1(,d;.~ /.......
+[0670] F2 DC 43 D0 14 21 C8 D0 D3 15 45 8E 2A 3E 3B 4A ..C..!.. ..E.*>;J
+[0680] 60 25 3D 11 E4 F9 16 02 3E 55 8F CE D2 E9 95 E7 `%=..... >U......
+[0690] B1 C4 8F C4 0B 3E 3C 14 15 28 1A 21 49 15 CE 8E .....><. .(.!I...
+[06A0] 91 5E 98 71 00 1F 29 D3 12 C8 D0 11 4F E7 14 E3 .^.q..). ....O...
+[06B0] 72 1B 61 6D 7B 8A 00 A6 5E 01 01 50 C2 CF 1A A9 r.am{... ^..P....
+[06C0] 34 8C BA 33 9E 62 C5 69 97 6A 24 3D E0 C6 3F C6 4..3.b.i .j$=..?.
+[06D0] F4 36 B1 80 D6 5C 44 19 5B 65 C7 CA 47 DE 4B 65 .6...\D. [e..G.Ke
+[06E0] 41 29 9F F8 EA E8 E0 3B E2 C6 98 9D 58 A4 6C 62 A).....; ....X.lb
+[06F0] EF 25 12 C9 0E 97 CE 9D F0 D8 08 AD 13 73 A6 82 .%...... .....s..
+[0700] C5 54 23 F4 A4 CB 91 35 91 BD 10 B4 04 DD 55 7E .T#....5 ......U~
+[0710] C9 DE AE CB B0 8F C0 D8 28 AE BD 78 64 91 6C AB ........ (..xd.l.
+[0720] CA 36 EA 0E 0E 97 DC 40 ED 26 1D 09 17 28 30 D3 .6.....@ .&...(0.
+[0730] 78 DC F7 D2 9C 78 DA 6F 6F 57 00 B3 FD 8E 75 A1 x....x.o oW....u.
+[0740] 56 98 5C 4B D8 61 A6 0A 89 27 CD 11 BF 7F 79 53 V.\K.a.. .'....yS
+[0750] D9 50 9A 8D EC DD DB BB B8 23 27 0D 20 5B 53 51 .P...... .#'. [SQ
+[0760] 07 C4 26 31 3B D4 DF ED 3C 40 B4 1C 8B 46 E2 A6 ..&1;... <@...F..
+[0770] B7 0F 97 D2 B3 1D 19 FD 13 60 7B 38 E6 37 0C 59 ........ .`{8.7.Y
+[0780] B0 A8 47 5D 32 A5 0C 57 76 EF 2C ED 40 9F BF 4B ..G]2..W v.,.@..K
+[0790] 43 99 3C 68 C4 DE 84 9C A1 36 8C CA CB 2A 08 36 C.<h.... .6...*.6
+[07A0] 4E CD 43 06 9E F8 E7 1D 52 3B 59 37 4F 6F 65 D9 N.C..... R;Y7Ooe.
+[07B0] 2A F9 AD 5A 50 95 71 3F B1 5F C8 8E 2E E9 E4 FE *..ZP.q? ._......
+[07C0] C8 A9 42 2C EE 18 E0 81 3C 00 E2 80 8D 8A 8B 71 ..B,.... <......q
+[07D0] C7 F5 AC 5C 36 1D E0 BC F0 11 57 67 CB 2C BE F6 ...\6... ..Wg.,..
+[07E0] 90 4E F9 90 97 14 1F 0C 9D 5D 4D DF 0D D0 C0 C5 .N...... .]M.....
+[07F0] 08 E7 31 72 8E 35 63 17 8D 8B 3D 49 14 C8 A5 90 ..1r.5c. ..=I....
+[0800] 88 24 AF 75 CA 0A CB 95 8A 2C 70 A6 CE 2F 3F B6 .$.u.... .,p../?.
+[0810] D7 1A 44 AC 05 93 EF 3D 03 C7 C2 8E 0F 31 9F 53 ..D....= .....1.S
+[0820] 67 CA 73 D3 B8 07 76 36 35 6F B5 32 30 38 86 7E g.s...v6 5o.208.~
+[0830] 7E 95 3F DC F4 6F A9 67 0E 15 E8 4A CA 3F 18 0E ~.?..o.g ...J.?..
+[0840] C6 E7 20 22 6B F1 39 6A 9C A6 47 64 81 E4 CB A8 .. "k.9j ..Gd....
+[0850] 31 FF E2 97 13 41 89 45 79 53 2B A8 90 97 DE 7B 1....A.E yS+....{
+[0860] 18 56 95 02 2A 94 D2 7E 5C D0 A0 BC A0 38 D2 BC .V..*..~ \....8..
+[0870] 03 91 F7 35 FE 1A 5E 80 10 13 4E 83 CB F6 D7 8A ...5..^. ..N.....
+[0880] 02 A2 E8 1F D8 9B F1 76 F9 18 66 56 9C 4D 9E BF .......v ..fV.M..
+[0890] 1D F4 66 86 E0 7B 88 EC 9C F7 50 13 7D 34 8A 54 ..f..{.. ..P.}4.T
+[08A0] 7A E1 EC F6 44 12 47 84 7D 16 B4 42 25 E5 A2 CC z...D.G. }..B%...
+[08B0] D8 CA 7A 38 21 85 A3 F8 41 6D 0D AC 1D FA 36 5D ..z8!... Am....6]
+[08C0] 23 EA 20 CC 43 A5 7E D9 25 97 BC 0E 74 F5 3D 98 #. .C.~. %...t.=.
+[08D0] B9 79 C2 65 50 0E 8D E7 7A F3 F3 88 37 A3 40 01 .y.eP... z...7.@.
+[08E0] 96 C6 FC 1D 6E 9E 06 A1 90 A0 78 3C DA 7F E9 C6 ....n... ..x<....
+[08F0] 23 47 70 04 03 EE C2 4A C3 95 07 44 00 BD 29 2A #Gp....J ...D..)*
+[0900] B5 FA 17 1E D6 BC 00 A0 93 55 E0 82 0A AB 04 D4 ........ .U......
+[0910] D5 56 84 2A B2 56 51 05 DB 30 E2 83 5A 75 D3 A8 .V.*.VQ. .0..Zu..
+[0920] 30 B7 3E C4 25 70 A8 34 E4 A2 EB 3E FB D8 2D 10 0.>.%p.4 ...>..-.
+[0930] 72 8E DA 4D 2D 55 EC 49 66 5E 01 96 E4 C1 0C 23 r..M-U.I f^.....#
+[0940] 57 91 00 00 00 00 00 00 00 01 00 00 00 01 00 00 W....... ........
+[0950] 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[0960] 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D AMPLE.CO M....adm
+[0970] 69 6E 69 73 74 72 61 74 6F 72 00 00 00 01 00 00 inistrat or......
+[0980] 00 02 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[0990] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 A.EXAMPL E.COM...
+[09A0] 04 68 6F 73 74 00 00 00 0B 4C 4F 43 41 4C 4B 54 .host... .LOCALKT
+[09B0] 45 53 54 36 00 17 00 00 00 10 9D AE 06 BE 29 E0 EST6.... ......).
+[09C0] F7 9A 46 97 29 E0 69 8E 5A F0 4D 9B 90 45 4D 9B ..F.).i. Z.M..EM.
+[09D0] 90 61 7D 46 4C 43 00 00 00 00 00 40 28 00 00 00 .a}FLC.. ...@(...
+[09E0] 00 00 00 00 00 00 00 00 00 03 FA 61 82 03 F6 30 ........ ...a...0
+[09F0] 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 ........ ....KTES
+[0A00] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0A10] 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 COM..0.. ......0.
+[0A20] 1B 04 68 6F 73 74 1B 0B 4C 4F 43 41 4C 4B 54 45 ..host.. LOCALKTE
+[0A30] 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 02 01 17 ST6....0 ........
+[0A40] A1 03 02 01 03 A2 82 03 9C 04 82 03 98 B9 C5 6E ........ .......n
+[0A50] 77 F9 59 6D 19 F0 A6 56 2F 14 B3 9A A3 17 06 A6 w.Ym...V /.......
+[0A60] AD F5 92 38 6A 1E EA 3D 53 BF 5E 95 13 FF 5D BB ...8j..= S.^...].
+[0A70] 43 4F 51 AE FB 12 3B 06 67 36 91 B9 E0 C4 C4 F3 COQ...;. g6......
+[0A80] 45 A0 48 E6 DC 49 E8 EA 6F 55 D2 3F 79 57 54 FF E.H..I.. oU.?yWT.
+[0A90] 10 8D 89 4A A4 E2 B2 80 FD EE 36 C5 D5 4C D0 97 ...J.... ..6..L..
+[0AA0] B3 EC 96 8B E8 5A 05 F0 13 39 8B 1B B3 C4 32 2A .....Z.. .9....2*
+[0AB0] 9B BB EF 06 C4 1C 53 2F 0A F6 A8 C6 BE 09 57 26 ......S/ ......W&
+[0AC0] B9 39 7B 7B 50 13 2D 6C 52 FF C4 B5 83 28 A8 47 .9{{P.-l R....(.G
+[0AD0] 5A CD 1C DD A7 65 FD 8A 84 2A 10 E7 44 E6 83 E7 Z....e.. .*..D...
+[0AE0] E7 AA B8 E5 0A 8B 7E E1 87 7B 3D C4 9F 68 BD 19 ......~. .{=..h..
+[0AF0] 2B 59 5E 5A 45 0D B5 71 CC A6 C7 03 3C B3 17 D3 +Y^ZE..q ....<...
+[0B00] AF 99 F6 A2 52 A0 99 F7 39 56 B4 33 B4 C5 F4 CC ....R... 9V.3....
+[0B10] 74 34 4C 00 76 26 10 D1 3A 87 6E 6A 52 9B 7A BF t4L.v&.. :.njR.z.
+[0B20] 4E 59 36 32 C5 41 29 CF E1 BF 14 E0 54 BF 4A 25 NY62.A). ....T.J%
+[0B30] 1F 0B 6E 9A 8C 0E 5D 47 A9 64 1B A4 9D 99 A9 09 ..n...]G .d......
+[0B40] 39 14 E7 41 22 98 8C 62 CC E2 B5 91 8E C1 31 EB 9..A"..b ......1.
+[0B50] B2 70 A6 3B 86 FC DD 19 0B 3F 5D C9 B5 1A 95 73 .p.;.... .?]....s
+[0B60] EB 97 89 BE 14 87 85 17 BE 40 F6 80 14 23 4D 66 ........ .@...#Mf
+[0B70] E4 B0 E5 51 46 34 DA 1C C8 CB FF C6 84 A3 DF D2 ...QF4.. ........
+[0B80] DC 00 AF 7B 27 C8 78 44 CB 6E 7B CC 5C 94 1E 7A ...{'.xD .n{.\..z
+[0B90] 95 29 19 F4 14 BE 5C 23 C3 B9 A4 2C 5D 4D F3 61 .)....\# ...,]M.a
+[0BA0] 63 1F D4 FE 37 EE 44 14 06 B7 14 50 B6 74 37 75 c...7.D. ...P.t7u
+[0BB0] 2C AB 06 F0 93 F9 93 34 75 63 44 7E 12 48 D1 F1 ,......4 ucD~.H..
+[0BC0] 06 55 14 11 B9 23 43 CE 01 16 3E 6B A3 BD 23 55 .U...#C. ..>k..#U
+[0BD0] DE 48 5D AF E1 2B 89 E8 E7 C2 E2 34 25 A2 09 4A .H]..+.. ...4%..J
+[0BE0] 1F BE 05 AA DE 4B 08 65 27 4C 9B C7 54 96 C2 FB .....K.e 'L..T...
+[0BF0] E2 CE 53 4A 32 93 8D 0B 44 77 8C D3 65 54 F9 0E ..SJ2... Dw..eT..
+[0C00] 7F 74 1E FE 3D 74 83 0F 2F E7 9F BC A2 B0 2B 25 .t..=t.. /.....+%
+[0C10] BB D2 6F A8 49 C1 3E 9E B5 93 67 74 39 A4 FE 84 ..o.I.>. ..gt9...
+[0C20] 4C 45 5F 30 74 E0 CA 5F F6 46 EC 89 B5 2D C8 14 LE_0t.._ .F...-..
+[0C30] 69 76 BC 93 15 F4 60 30 5F AB EB 02 DD 12 4C 62 iv....`0 _.....Lb
+[0C40] F9 73 F7 01 E1 7F 2A 6F 09 05 BF 3A 3A 7E 69 A3 .s....*o ...::~i.
+[0C50] 7B FC 20 2B D6 CE C0 74 4F BB 29 E4 BE CE 04 9D {. +...t O.).....
+[0C60] 24 D4 98 4A ED 94 A8 81 CD 26 A0 63 EA 09 57 42 $..J.... .&.c..WB
+[0C70] 26 B7 B5 4E B5 CB 45 35 A7 84 D8 74 CA C3 9F FF &..N..E5 ...t....
+[0C80] C8 1E 2A 75 34 01 C5 A7 B4 9D 6F A3 E1 BB 2B F8 ..*u4... ..o...+.
+[0C90] F0 21 D6 77 57 74 2E 80 DB 76 53 01 86 33 17 32 .!.wWt.. .vS..3.2
+[0CA0] 2E 16 E1 8D 89 3A B2 67 ED A3 ED 39 82 87 26 A6 .....:.g ...9..&.
+[0CB0] DB CE 59 84 E4 0A A6 CA 7E 07 98 F7 02 91 6E 56 ..Y..... ~.....nV
+[0CC0] 9F 60 03 D3 88 B0 FF EB 20 CA 9E 5B 37 26 67 00 .`...... ..[7&g.
+[0CD0] CC BD 9D 53 15 31 53 14 FD 9C E1 28 08 CB C4 0B ...S.1S. ...(....
+[0CE0] E3 50 D9 DB 0C E2 E4 F9 44 50 E9 28 6E 01 96 AA .P...... DP.(n...
+[0CF0] C1 D2 4E B2 DE 38 A2 F8 94 32 79 AE 49 64 FB 57 ..N..8.. .2y.Id.W
+[0D00] 50 F6 73 E8 98 43 C6 DD 67 3C 91 AC 97 C9 2E 8C P.s..C.. g<......
+[0D10] 06 59 A1 FC 49 EC 2F BF 6F 64 21 63 ED C8 6C CE .Y..I./. od!c..l.
+[0D20] 37 28 7B 80 7F 5F 85 F6 98 93 C0 66 A8 D6 F1 2C 7({.._.. ...f...,
+[0D30] D8 01 68 B1 C8 EA 82 0D 5B 9B 35 4F 3D B3 47 19 ..h..... [.5O=.G.
+[0D40] 54 7A C6 9F AD D7 54 CF B0 DB 3E 18 BA 2A 39 08 Tz....T. ..>..*9.
+[0D50] 0C C4 98 4B 43 DE 53 68 25 B1 83 93 1D E1 6C BF ...KC.Sh %.....l.
+[0D60] F5 B4 A9 83 17 34 64 8C 2F 91 80 97 4A 48 EC 90 .....4d. /...JH..
+[0D70] BB FA 92 2C 01 80 E4 99 91 0E 67 88 D5 75 AB 7C ...,.... ..g..u.|
+[0D80] 98 59 98 45 C9 11 A9 8C 02 98 91 DE AB A0 FF 45 .Y.E.... .......E
+[0D90] 11 66 6F C5 DE 61 6D C6 DB C9 CA A3 A0 2B B1 73 .fo..am. .....+.s
+[0DA0] 05 85 37 BF AB CA 43 7A 6F 38 C8 BE ED CE 12 49 ..7...Cz o8.....I
+[0DB0] 93 C7 7C 1A 33 60 52 7A 67 67 AA 60 57 7E C8 FF ..|.3`Rz gg.`W~..
+[0DC0] DF 91 91 18 45 74 C0 9E 36 19 BC 42 F9 46 CC 84 ....Et.. 6..B.F..
+[0DD0] 09 2E 8C 59 1A E3 65 51 F4 87 6F 4C 3E 29 38 E6 ...Y..eQ ..oL>)8.
+[0DE0] 77 E8 A9 B7 FA 00 00 00 00 00 00 00 01 00 00 00 w....... ........
+[0DF0] 01 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[0E00] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D .EXAMPLE .COM....
+[0E10] 61 64 6D 69 6E 69 73 74 72 61 74 6F 72 00 00 00 administ rator...
+[0E20] 01 00 00 00 02 00 00 00 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0E30] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0E40] 00 00 00 04 63 69 66 73 00 00 00 0B 4C 4F 43 41 ....cifs ....LOCA
+[0E50] 4C 4B 54 45 53 54 36 00 17 00 00 00 10 01 78 D0 LKTEST6. ......x.
+[0E60] 3B 9B FF F0 88 86 4B 3B FE 41 A9 6B 00 4D 9B 90 ;.....K; .A.k.M..
+[0E70] 45 4D 9B 90 6B 7D 46 4C 43 00 00 00 00 00 40 28 EM..k}FL C.....@(
+[0E80] 00 00 00 00 00 00 00 00 00 00 00 00 03 FA 61 82 ........ ......a.
+[0E90] 03 F6 30 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B ..0..... .......K
+[0EA0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0EB0] 4C 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 LE.COM.. 0.......
+[0EC0] 15 30 13 1B 04 63 69 66 73 1B 0B 4C 4F 43 41 4C .0...cif s..LOCAL
+[0ED0] 4B 54 45 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 KTEST6.. ..0.....
+[0EE0] 02 01 17 A1 03 02 01 03 A2 82 03 9C 04 82 03 98 ........ ........
+[0EF0] CA EA 4D 46 2D D1 E9 58 5D 25 8D 9F DF EA C9 01 ..MF-..X ]%......
+[0F00] B6 08 27 CD 14 85 02 DC 20 C6 51 AA F9 6A B1 CE ..'..... .Q..j..
+[0F10] F5 77 84 BF 9A AC 6B A7 B2 F2 1F 60 BF CB C6 FC .w....k. ...`....
+[0F20] C7 14 B7 41 1C A8 C9 70 7B 86 BC 8E 70 2B 65 4B ...A...p {...p+eK
+[0F30] DC F5 B9 23 F8 08 BF 96 C9 A8 77 F4 54 67 25 F8 ...#.... ..w.Tg%.
+[0F40] 0F A8 C5 D6 D1 BB 46 5E A0 7E D2 98 9C CD AF E0 ......F^ .~......
+[0F50] 82 62 ED 39 D2 FB F2 E8 9B 1B EE E5 B4 1B C9 0A .b.9.... ........
+[0F60] 86 27 52 6E 11 8B D7 AD B4 54 F9 C6 69 8D E0 F1 .'Rn.... .T..i...
+[0F70] CD 63 1C 89 7C 8F B6 A0 71 53 A6 DA B1 66 D2 9D .c..|... qS...f..
+[0F80] D3 4C A8 FB C6 9D 81 74 10 8E 84 D2 3D D8 1C BE .L.....t ....=...
+[0F90] BB 3F F7 BF 91 3E 89 66 43 A1 E0 90 1B 1A 97 FF .?...>.f C.......
+[0FA0] EF CC 35 75 14 62 4F 67 3A 29 F4 F9 C5 2E BE C5 ..5u.bOg :)......
+[0FB0] C2 2B A8 35 22 D9 92 31 1D 49 2A A5 19 AA 08 0F .+.5"..1 .I*.....
+[0FC0] A8 22 0B 68 D2 A2 D7 07 7B 37 1E A3 AC 9B 4F 0A .".h.... {7....O.
+[0FD0] A4 FA 7F 37 6F 3E 35 79 4E 00 4B B6 28 A3 6A E4 ...7o>5y N.K.(.j.
+[0FE0] 0C 95 53 BA E8 41 07 DA BE E9 08 B9 51 24 91 49 ..S..A.. ....Q$.I
+[0FF0] 78 5D 44 12 BC 85 63 81 B8 E0 88 D5 95 0C D3 A8 x]D...c. ........
+[1000] 1D 32 4B E4 A0 C8 A7 7D 3C 97 EE D8 59 AC 3A 21 .2K....} <...Y.:!
+[1010] 09 F2 7A CC D0 4A F3 50 10 DC FC 26 BB C2 6A 8E ..z..J.P ...&..j.
+[1020] 8B 14 2B 2D 50 2E B3 1E 9B D2 69 56 22 F2 48 BD ..+-P... ..iV".H.
+[1030] E9 2E 2F 28 DE 77 67 5F 68 AA 29 05 4B 36 58 40 ../(.wg_ h.).K6X@
+[1040] E5 54 11 C5 4D 68 96 49 9D 53 37 87 5F D2 3A 9B .T..Mh.I .S7._.:.
+[1050] E9 8E 79 BE AE 11 B4 6B AB FD DB 8A F5 A0 9B 29 ..y....k .......)
+[1060] D9 F5 ED CA FA 3F FE 35 FC F4 69 7E E4 D0 44 29 .....?.5 ..i~..D)
+[1070] 48 FF 82 61 26 FC D3 E2 10 EE 14 F7 4A E3 CD F2 H..a&... ....J...
+[1080] 8B BC 8B 43 64 2C DE 40 6E BB E1 56 C0 B6 2C D0 ...Cd,.@ n..V..,.
+[1090] E5 1E E9 B3 FB 38 48 66 ED AF D2 25 D1 35 5C C6 .....8Hf ...%.5\.
+[10A0] F0 4D 36 19 0B EC 33 07 34 D0 27 8D 14 DC 01 45 .M6...3. 4.'....E
+[10B0] DE F8 73 A6 A0 F4 C1 91 9D BD 05 E3 70 25 E1 10 ..s..... ....p%..
+[10C0] 44 F6 4B 46 F7 24 84 BF 20 96 AD 6A 96 94 81 58 D.KF.$.. ..j...X
+[10D0] 80 95 06 92 F5 7F 17 39 3B 32 47 B2 C5 CE 7B 73 .......9 ;2G...{s
+[10E0] CF 53 AE FA D1 9A 60 5A 98 EC 8C FA BD C0 CE 8D .S....`Z ........
+[10F0] C5 27 E6 17 1A 4D 47 D8 3F 5D A9 7C FB 2C B3 05 .'...MG. ?].|.,..
+[1100] 0C 69 20 48 99 80 11 DC 48 AB A7 EA 5B 98 C1 15 .i H.... H...[...
+[1110] 27 AE FA 3E 1E 1E E0 E1 F8 32 C0 54 13 D6 30 34 '..>.... .2.T..04
+[1120] 71 98 26 61 6C 1C C4 C7 4E C4 A6 7E FE A8 B8 89 q.&al... N..~....
+[1130] 2A 70 3C 19 58 8D 57 45 55 83 0A C2 B5 F7 89 0E *p<.X.WE U.......
+[1140] 7B 7A 17 0C CF 6E 08 A5 F7 21 4A 62 81 4F 49 CA {z...n.. .!Jb.OI.
+[1150] E2 ED C2 B4 C7 33 5C BC A1 A0 DE 4E 09 37 BE 24 .....3\. ...N.7.$
+[1160] 62 22 94 55 75 AA 53 DE E0 74 5A B0 B8 E9 BF 2B b".Uu.S. .tZ....+
+[1170] 12 65 2F 90 6B 84 ED 11 AD F7 CE 19 A1 96 E4 1E .e/.k... ........
+[1180] 8C EA C8 81 1B 47 4F 5F B1 5D A5 8B E3 0D 5A 80 .....GO_ .]....Z.
+[1190] 89 EC 4B D9 CE ED E8 67 7F 96 FC 1B EF 65 C2 68 ..K....g .....e.h
+[11A0] 40 F7 20 36 83 58 62 F4 CA 02 F4 5C 0D 46 B1 CB @. 6.Xb. ...\.F..
+[11B0] 50 D2 D8 3D B7 9A 96 48 8C CF EB E6 8C F4 B2 B4 P..=...H ........
+[11C0] 47 C9 34 C9 DC 14 F1 33 1B 6F 9E 65 27 D7 9D 46 G.4....3 .o.e'..F
+[11D0] 1E 91 FF 2E FB 8E 97 5D 17 8F 48 54 7C 3C A0 11 .......] ..HT|<..
+[11E0] 9C AA 77 E9 79 DE 26 D1 F0 7C EA 24 73 BE EC 60 ..w.y.&. .|.$s..`
+[11F0] B4 EE BD ED 0D 0A AB 74 60 6E 46 C0 35 5B 65 1A .......t `nF.5[e.
+[1200] A4 4A 5C 22 AC B9 CD B7 56 06 88 09 FC 48 68 55 .J\".... V....HhU
+[1210] B7 5E 39 72 DF 8A 4C CD 79 74 B0 84 0B 78 DA B2 .^9r..L. yt...x..
+[1220] 55 F8 06 0B 5C 27 06 B3 CA 10 65 6B 04 A3 64 11 U...\'.. ..ek..d.
+[1230] 04 09 DC DF 67 00 70 B1 16 DF 24 E9 27 85 11 91 ....g.p. ..$.'...
+[1240] 31 CB 92 95 50 18 91 08 C2 A1 A3 76 C7 1A FC 64 1...P... ...v...d
+[1250] 9E 2C 3A E7 30 F4 16 0D A0 56 C0 BC D2 FE 2D A0 .,:.0... .V....-.
+[1260] 20 A4 E2 82 AD F0 C5 12 71 09 23 E1 66 52 53 D0 ....... q.#.fRS.
+[1270] 89 30 E7 BE B7 C2 89 F2 1C 7A F6 8E D7 28 F0 A4 .0...... .z...(..
+[1280] 33 46 7C A2 79 66 DE 26 00 00 00 00 3F|.yf.& ....
+push returned Success
+pull returned Success
+ CCACHE: struct CCACHE
+ pvno : 0x05 (5)
+ version : 0x04 (4)
+ optional_header : union OPTIONAL_HEADER(case 0x4)
+ v4header: struct V4HEADER
+ v4tags: struct V4TAGS
+ tag: struct V4TAG
+ tag : 0x0001 (1)
+ field : union FIELD(case 0x1)
+ deltatime_tag: struct DELTATIME_TAG
+ kdc_sec_offset : 0
+ kdc_usec_offset : 0
+ further_tags : DATA_BLOB length=0
+ principal: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ cred: struct CREDENTIAL
+ client: struct PRINCIPAL
+ name_type : 0x00000001 (1)
+ component_count : 0x00000001 (1)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(1)
+ components : 'administrator'
+ server: struct PRINCIPAL
+ name_type : 0x00000000 (0)
+ component_count : 0x00000002 (2)
+ realm : 'KTEST.SAMBA.EXAMPLE.COM'
+ components: ARRAY(2)
+ components : 'krbtgt'
+ components : 'KTEST.SAMBA.EXAMPLE.COM'
+ keyblock: struct KEYBLOCK
+ enctype : 0x0017 (23)
+ data : DATA_BLOB length=16
+[0000] E5 E4 15 C8 A8 0F 4D 95 F9 1B E3 B9 98 CA A1 7F ......M. ........
+ authtime : 0x4d9b9045 (1302040645)
+ starttime : 0x4d9b9045 (1302040645)
+ endtime : 0x7d464c43 (2101759043)
+ renew_till : 0x7d464c43 (2101759043)
+ is_skey : 0x00 (0)
+ ticket_flags : 0x40e00000 (1088421888)
+ addresses: struct ADDRESSES
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ authdata: struct AUTHDATA
+ count : 0x00000000 (0)
+ data: ARRAY(0)
+ ticket : DATA_BLOB length=1032
+[0000] 61 82 04 04 30 82 04 00 A0 03 02 01 05 A1 19 1B a...0... ........
+[0010] 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 .KTEST.S AMBA.EXA
+[0020] 4D 50 4C 45 2E 43 4F 4D A2 2C 30 2A A0 03 02 01 MPLE.COM .,0*....
+[0030] 00 A1 23 30 21 1B 06 6B 72 62 74 67 74 1B 17 4B ..#0!..k rbtgt..K
+[0040] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0050] 4C 45 2E 43 4F 4D A3 82 03 AE 30 82 03 AA A0 03 LE.COM.. ..0.....
+[0060] 02 01 17 A1 03 02 01 01 A2 82 03 9C 04 82 03 98 ........ ........
+[0070] 01 40 48 A6 B8 F0 DA 43 54 A5 18 CF B0 15 CB 68 .@H....C T......h
+[0080] 9F A0 69 44 87 A9 FF 06 25 B9 29 48 59 64 26 48 ..iD.... %.)HYd&H
+[0090] 96 7C 46 6A 79 E5 F0 77 DB 46 6C 20 A1 59 D9 F8 .|Fjy..w .Fl .Y..
+[00A0] 6A 8A 2D B5 D9 EF A4 54 DE 19 20 C0 7B 93 D4 3D j.-....T .. .{..=
+[00B0] ED 72 35 AF 9D 87 75 9E 44 01 A4 6C D9 EA 94 A3 .r5...u. D..l....
+[00C0] 18 C6 42 75 E3 0A 0C 76 9A AE 75 BC A3 02 91 BC ..Bu...v ..u.....
+[00D0] 2D BB 3C 23 73 A6 1A A7 8A 3E 85 42 5D 1F 5D 7D -.<#s... .>.B].]}
+[00E0] 0B 1F C3 88 2A 93 40 F9 E9 18 7D 3F 73 DA AC 1F ....*.@. ..}?s...
+[00F0] E7 7B C3 B8 14 56 C3 63 86 5B AF C9 C3 21 9F 94 .{...V.c .[...!..
+[0100] B4 67 06 60 7F 56 2D F4 C7 22 CD B4 1C 14 B7 5B .g.`.V-. .".....[
+[0110] 26 67 9D 18 28 B5 5D C2 FC 13 B6 CA 9F AB CD 32 &g..(.]. .......2
+[0120] 71 D5 51 5F A2 11 5A 5D 4A B3 3B 1D D1 6B 4F 7D q.Q_..Z] J.;..kO}
+[0130] E9 54 F0 B4 AC 80 DE 27 80 C5 64 3C 0B 22 79 1C .T.....' ..d<."y.
+[0140] 9E D1 58 A1 3E 20 5A 9F E3 34 49 D8 16 C6 6B 2D ..X.> Z. .4I...k-
+[0150] 36 0E E2 C2 3F 44 DE 63 32 DB EB 78 50 A2 6F 37 6...?D.c 2..xP.o7
+[0160] 05 2B 13 D4 31 07 D4 2A C0 53 B1 30 39 79 C3 D8 .+..1..* .S.09y..
+[0170] C4 4C 30 97 E8 F9 DA ED 10 B0 D0 21 71 8B 56 F3 .L0..... ...!q.V.
+[0180] 0F 3A 2D 26 A2 3D AD 70 27 82 95 59 0A D7 7D 4E .:-&.=.p '..Y..}N
+[0190] 2D 76 96 4D 94 70 2A BB 26 3B 7E FC E1 59 5A 55 -v.M.p*. &;~..YZU
+[01A0] 04 A2 DA 27 AD 46 70 45 43 C0 FB C1 42 7F F0 CB ...'.FpE C...B...
+[01B0] 21 D2 CD 54 35 7C 60 13 EE BB BB 60 6B 91 2B BE !..T5|`. ...`k.+.
+[01C0] 91 8A CF 49 29 F8 60 D1 AB A5 51 B5 5E 4B B2 3A ...I).`. ..Q.^K.:
+[01D0] F4 56 3A 89 2D 88 D0 73 08 A6 FB D8 6E B3 B1 4E .V:.-..s ....n..N
+[01E0] D8 90 27 58 D2 53 40 B2 A0 3C 40 4D E9 21 C6 83 ..'X.S@. .<@M.!..
+[01F0] FC 15 14 F0 8C 08 46 C5 29 14 E3 84 CC 2C 56 C9 ......F. )....,V.
+[0200] 20 53 45 34 D0 BE E0 CC F7 F1 15 D4 D4 B1 3C 43 SE4.... ......<C
+[0210] EB 5E 9D 33 07 B4 5B E7 D8 24 B0 EB 7B 27 24 6B .^.3..[. .$..{'$k
+[0220] 2A 90 C9 17 D9 24 CF FD 56 28 D7 73 74 03 2F DA *....$.. V(.st./.
+[0230] C4 E0 B3 78 E4 9A 60 4D 5C C7 F5 CF 9C 14 7C B6 ...x..`M \.....|.
+[0240] 1B 5D 76 D1 E3 73 73 2F 41 BD E3 E7 F0 92 B4 5B .]v..ss/ A......[
+[0250] 07 B4 16 77 DC 3C 28 A4 92 82 C5 7C CA 00 9C 77 ...w.<(. ...|...w
+[0260] B8 28 7F D0 3F EA 2B C1 79 2B 73 FF E0 E0 A5 17 .(..?.+. y+s.....
+[0270] 02 CA 6C B6 02 D2 51 D3 CE 6F 5B 56 E0 7B 38 22 ..l...Q. .o[V.{8"
+[0280] 76 52 48 2D 0A 2F 15 58 A9 FE 03 65 E1 D5 A8 60 vRH-./.X ...e...`
+[0290] E3 5D E6 53 D8 AA 05 D0 90 61 EF B6 28 4A B9 84 .].S.... .a..(J..
+[02A0] 56 79 80 D2 53 08 1D 17 C4 05 4E F8 04 10 2B CF Vy..S... ..N...+.
+[02B0] 08 DD 61 68 27 21 A5 8A C0 35 6A 0A 94 6D 9E FD ..ah'!.. .5j..m..
+[02C0] C9 45 AC E3 4F 60 BB 96 AF D4 4E 71 A9 D9 BE 33 .E..O`.. ..Nq...3
+[02D0] DC 61 8B 14 77 6C A7 72 70 02 65 62 32 9C 8E 53 .a..wl.r p.eb2..S
+[02E0] C9 A3 5B B9 14 3C 00 A2 1D C7 CD 36 5B 5F BE 40 ..[..<.. ...6[_.@
+[02F0] 28 E2 58 0D D1 05 53 78 F0 86 0F 80 1A 6A 1D DC (.X...Sx .....j..
+[0300] D4 CD F2 83 0E 25 E1 60 DB C7 F4 B6 05 4F 0D 11 .....%.` .....O..
+[0310] A4 AE A5 F8 6D 14 CF DF 03 C5 27 75 75 B5 0C F1 ....m... ..'uu...
+[0320] C3 01 F9 A4 FD 2E 0B BD 51 A8 C1 3B DE 48 CF 3A ........ Q..;.H.:
+[0330] CF B3 41 23 9A 9D 0C 79 11 7C 9B D3 71 43 4E 9D ..A#...y .|..qCN.
+[0340] B5 52 19 28 2C A0 4E 0E 8D 7A 84 9A B9 A0 EB FA .R.(,.N. .z......
+[0350] 6E A1 DF B9 2F 6B FE 5E AE 85 D1 6B A2 C5 BE 07 n.../k.^ ...k....
+[0360] E7 D6 33 3A 0F 2B ED FB 30 6F 88 1E F9 09 CC C3 ..3:.+.. 0o......
+[0370] 8F 59 A0 D4 8D 9F A6 08 B0 D3 ED EB 15 13 1B 8E .Y...... ........
+[0380] 19 C6 14 9C 25 E7 E9 EF 5A 67 7B CD 86 C4 D1 51 ....%... Zg{....Q
+[0390] 2B DE 27 30 D9 F5 6E F9 E4 3E CF 42 54 AE 42 61 +.'0..n. .>.BT.Ba
+[03A0] C5 22 B7 AE 51 76 8F 12 83 7F E1 9F 97 D8 31 38 ."..Qv.. ......18
+[03B0] A6 B9 11 B4 E1 BA 19 5B E4 A5 A3 6F 4B B3 03 93 .......[ ...oK...
+[03C0] 4C D6 1E 08 FC 94 D1 C5 7C AA 95 EB 9C 7A C2 57 L....... |....z.W
+[03D0] 60 CA 17 FF 8E 66 80 76 CB 35 46 26 C3 BD CA 83 `....f.v .5F&....
+[03E0] F0 04 08 0D 4C 5D B2 E4 7C 1C 82 28 D7 2C 42 B1 ....L].. |..(.,B.
+[03F0] 36 72 60 5E 26 4A 79 D0 41 94 3C 2C 65 0E 32 18 6r`^&Jy. A.<,e.2.
+[0400] B8 56 26 9D D3 84 78 BB .V&...x.
+ second_ticket : DATA_BLOB length=0
+ further_creds : DATA_BLOB length=4748
+[0000] 00 00 00 01 00 00 00 01 00 00 00 17 4B 54 45 53 ........ ....KTES
+[0010] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0020] 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 73 74 72 COM....a dministr
+[0030] 61 74 6F 72 00 00 00 01 00 00 00 02 00 00 00 17 ator.... ........
+[0040] 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D KTEST.SA MBA.EXAM
+[0050] 50 4C 45 2E 43 4F 4D 00 00 00 04 68 6F 73 74 00 PLE.COM. ...host.
+[0060] 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 00 17 ...local ktest6..
+[0070] 00 00 00 10 EA 0D 3A 24 41 21 F7 7D 7D A3 C5 BB ......:$ A!.}}...
+[0080] A4 88 F6 17 4D 9B 90 45 4D 9B 90 52 7D 46 4C 43 ....M..E M..R}FLC
+[0090] 00 00 00 00 00 40 28 00 00 00 00 00 00 00 00 00 .....@(. ........
+[00A0] 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 A0 03 02 .....a.. .0......
+[00B0] 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[00C0] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D A2 1E 30 A.EXAMPL E.COM..0
+[00D0] 1C A0 03 02 01 01 A1 15 30 13 1B 04 68 6F 73 74 ........ 0...host
+[00E0] 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 A3 82 03 ..localk test6...
+[00F0] AE 30 82 03 AA A0 03 02 01 17 A1 03 02 01 03 A2 .0...... ........
+[0100] 82 03 9C 04 82 03 98 44 8B C4 7D BA 9F FE 59 F6 .......D ..}...Y.
+[0110] C1 DF 62 89 02 A4 55 54 AB D6 D6 2E 8B 5E 35 3D ..b...UT .....^5=
+[0120] D9 46 9D 8B 49 93 A6 66 5F 1A 8B 81 AD 09 19 E9 .F..I..f _.......
+[0130] 59 CE 58 18 50 63 4A A6 7D 6F 71 21 51 4A 41 C2 Y.X.PcJ. }oq!QJA.
+[0140] A1 FE B0 D5 0A 3D 38 9F E5 3B 72 A2 7A 59 22 A4 .....=8. .;r.zY".
+[0150] B7 1C A3 8D DB EA 5D A5 E2 D3 1D AE 42 D0 7F 75 ......]. ....B..u
+[0160] B5 E9 ED B5 04 7B 67 1E 28 90 7D 3D 1A 3E F6 62 .....{g. (.}=.>.b
+[0170] D0 A1 56 89 28 76 5C 19 1A FD 66 E5 F2 86 E7 58 ..V.(v\. ..f....X
+[0180] 93 31 90 C5 CD F8 71 96 56 21 15 13 F0 EA C2 CC .1....q. V!......
+[0190] 48 4C B4 50 EF F9 81 44 29 8A 75 C4 31 75 D1 BA HL.P...D ).u.1u..
+[01A0] E2 0B 05 B2 E0 EA 64 3A 11 45 84 3D 69 55 FF E6 ......d: .E.=iU..
+[01B0] 32 7E C9 CA C4 28 E8 40 B6 5E F9 26 0F 09 12 1F 2~...(.@ .^.&....
+[01C0] 1F D4 9C 9A 50 E8 B7 6D F8 4F 55 6E 2A D4 AC 6A ....P..m .OUn*..j
+[01D0] 79 D1 C2 2A 88 99 F8 39 75 36 F1 2D C7 89 0A C6 y..*...9 u6.-....
+[01E0] B4 C7 A1 7B F1 BF 22 87 A4 B2 93 22 54 A1 72 25 ...{..". ..."T.r%
+[01F0] AF 67 FE 20 D5 C8 29 47 28 FF 51 FB F9 4E 2C 17 .g. ..)G (.Q..N,.
+[0200] 10 BE 2E 13 8B 18 BE 3C A3 BE 50 49 A7 65 DD 2E .......< ..PI.e..
+[0210] CC EB D6 0F 47 4E DB 7E 08 D5 F0 37 79 36 8F 24 ....GN.~ ...7y6.$
+[0220] 34 28 86 89 EC A3 84 7F 44 4E 37 03 B5 D8 89 1C 4(...... DN7.....
+[0230] C7 AA AC 42 70 5F 96 73 35 8B 83 D1 16 24 27 C1 ...Bp_.s 5....$'.
+[0240] EC 0E AE 83 59 5A C2 EB C1 91 B6 3D BB 8D 21 49 ....YZ.. ...=..!I
+[0250] 63 41 3C 91 1D E9 01 C2 4F A9 E4 42 C1 FD 54 E3 cA<..... O..B..T.
+[0260] 7B 3B DF 24 3D 98 E9 84 F8 1D 8D CE 4D 85 AC 8A {;.$=... ....M...
+[0270] 12 15 48 C4 DA 1B 3C B8 FC A3 0B AF E2 4D 71 E9 ..H...<. .....Mq.
+[0280] 0A 28 53 DC 4E 6C 23 2C 73 26 50 FE 37 03 BF D1 .(S.Nl#, s&P.7...
+[0290] 5F 8A 39 4F 04 2E 4A CE 3C 90 11 0C DA 84 5C C3 _.9O..J. <.....\.
+[02A0] F8 BE C7 74 ED F4 CF 7E B2 AE 9B 47 D6 2A 1D 93 ...t...~ ...G.*..
+[02B0] 3F A8 8B 51 E9 A3 A0 59 55 DB E3 52 67 E3 DE FF ?..Q...Y U..Rg...
+[02C0] B1 56 74 A0 87 21 99 23 8C 8E D1 92 A6 3D 93 D6 .Vt..!.# .....=..
+[02D0] 4D 5B 84 2B B1 8D DD E4 F7 01 A6 6C 4A DF 3C 6E M[.+.... ...lJ.<n
+[02E0] A0 FA 74 93 BE 18 7C 30 29 9D B8 DB 5F D1 AA B7 ..t...|0 )..._...
+[02F0] 51 7C 2A 90 1A 8B 06 95 E1 80 0D 27 B2 6C 52 1C Q|*..... ...'.lR.
+[0300] C7 D1 E9 16 14 F1 6C 57 48 28 BD 13 B5 83 BA A7 ......lW H(......
+[0310] 75 31 69 52 03 38 69 13 62 ED C6 DC C2 01 C8 F1 u1iR.8i. b.......
+[0320] 45 02 4D 8C 64 CF 96 90 3E C2 08 EC 2B 8D 92 93 E.M.d... >...+...
+[0330] 4B 6D 22 B3 41 DE 85 35 2D 19 09 E5 68 8E 1F 98 Km".A..5 -...h...
+[0340] 1B F2 73 F2 D4 91 08 89 42 0C 05 8B 42 77 6B CC ..s..... B...Bwk.
+[0350] 18 78 43 1A 73 C2 7C E7 C2 23 28 56 F7 A0 19 B3 .xC.s.|. .#(V....
+[0360] 99 A6 25 4F C3 5E 70 EC 78 BB 30 15 36 77 B3 A6 ..%O.^p. x.0.6w..
+[0370] 89 98 B6 A0 85 CC 8F E7 41 40 B5 E0 89 93 25 04 ........ A@....%.
+[0380] B8 1D 0B 06 31 1D C7 30 52 E1 64 29 8C 64 B9 89 ....1..0 R.d).d..
+[0390] 1F 86 5A AD 74 15 1C C8 AF 37 7B 27 E0 C0 DB 73 ..Z.t... .7{'...s
+[03A0] 30 72 65 D3 C0 A5 07 61 E9 0C 07 A1 27 18 8F 50 0re....a ....'..P
+[03B0] DB CE FB 4C DD 75 98 F2 28 D2 76 FF F2 41 9F D5 ...L.u.. (.v..A..
+[03C0] 74 22 8A 03 73 B1 A8 B3 B8 80 93 E5 E2 CD 4B F2 t"..s... ......K.
+[03D0] 6B 99 DF 5B 5B C7 22 69 81 2A 8A CD 2A F9 9D 08 k..[[."i .*..*...
+[03E0] B8 B0 40 77 D3 43 8B AF 40 DD 0C CB 45 E3 88 CB ..@w.C.. @...E...
+[03F0] 06 AA 63 38 EB DD 72 89 03 0E DC 3E 97 3F 16 D4 ..c8..r. ...>.?..
+[0400] 1A 21 40 D8 30 BD B0 B4 04 C2 7A 22 43 15 A2 D8 .!@.0... ..z"C...
+[0410] 2F 08 28 3B 63 26 AA B3 1C B6 FC E4 0B 2A CD 0E /.(;c&.. .....*..
+[0420] A8 7C E8 11 33 03 D3 C5 6C 35 6A 5D 3C 5A 80 1A .|..3... l5j]<Z..
+[0430] BC 1C 54 DE 5C 6A E2 F3 A1 18 8E 47 88 8B 71 11 ..T.\j.. ...G..q.
+[0440] 09 2F 29 88 D9 BB DC 34 09 E1 2F 7E A7 E8 29 DC ./)....4 ../~..).
+[0450] F9 5A 1D 9E C8 A4 CC 52 8A E6 CB 4A 3F F9 77 F7 .Z.....R ...J?.w.
+[0460] 53 64 62 9E 5F E6 D7 F6 43 E6 9C 03 C9 55 B1 CB Sdb._... C....U..
+[0470] 25 40 74 AA E9 AB 34 58 E1 E8 9B B3 1D 9E 83 FD %@t...4X ........
+[0480] 7A BF DC 45 2D A8 9A F8 AF 9C 63 EF 1B 2B 9D CC z..E-... ..c..+..
+[0490] F3 08 74 EC 6E 40 8E 18 62 BD F3 87 66 87 67 00 ..t.n@.. b...f.g.
+[04A0] 00 00 00 00 00 00 01 00 00 00 01 00 00 00 17 4B ........ .......K
+[04B0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[04C0] 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D 69 6E 69 LE.COM.. ..admini
+[04D0] 73 74 72 61 74 6F 72 00 00 00 01 00 00 00 02 00 strator. ........
+[04E0] 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 ...KTEST .SAMBA.E
+[04F0] 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 04 63 69 XAMPLE.C OM....ci
+[0500] 66 73 00 00 00 0B 6C 6F 63 61 6C 6B 74 65 73 74 fs....lo calktest
+[0510] 36 00 17 00 00 00 10 92 C6 A1 91 6D 55 01 4E BE 6....... ...mU.N.
+[0520] E4 3F E3 36 B0 D3 28 4D 9B 90 45 4D 9B 90 5A 7D .?.6..(M ..EM..Z}
+[0530] 46 4C 43 00 00 00 00 00 40 28 00 00 00 00 00 00 FLC..... @(......
+[0540] 00 00 00 00 00 00 03 FA 61 82 03 F6 30 82 03 F2 ........ a...0...
+[0550] A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0560] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0570] A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 1B 04 63 ..0..... ...0...c
+[0580] 69 66 73 1B 0B 6C 6F 63 61 6C 6B 74 65 73 74 36 ifs..loc alktest6
+[0590] A3 82 03 AE 30 82 03 AA A0 03 02 01 17 A1 03 02 ....0... ........
+[05A0] 01 03 A2 82 03 9C 04 82 03 98 FE 09 00 80 36 35 ........ ......65
+[05B0] D4 6E 71 0C 33 22 36 9E 89 88 32 E3 34 4A 4C BF .nq.3"6. ..2.4JL.
+[05C0] 80 19 81 CC A0 CB 96 DB 31 F7 2A 19 75 DE 0E DA ........ 1.*.u...
+[05D0] D0 18 FA 9E 75 E6 E4 13 C9 BE 3F C0 1B AD 5B 98 ....u... ..?...[.
+[05E0] E9 FC A3 9D 16 FF C8 91 03 AC 8B E6 2D 15 B3 F1 ........ ....-...
+[05F0] 23 4E 25 9E 45 3A F8 8A 19 B7 71 52 A6 92 1C FB #N%.E:.. ..qR....
+[0600] 1F D4 4C 51 AF 9C 0E 73 D9 A8 D8 43 F2 64 71 BC ..LQ...s ...C.dq.
+[0610] AD B1 7B 8F BF 8D FF 72 89 0F 5E B6 C2 E3 C0 01 ..{....r ..^.....
+[0620] 98 41 AD 3F 6E DC 87 F5 9A E6 40 0C 17 0F 75 80 .A.?n... ..@...u.
+[0630] 0C 28 62 06 EB BF F8 69 8C 43 48 38 A8 AE F2 5E .(b....i .CH8...^
+[0640] 45 11 23 FB 6B 85 83 54 BA 60 39 CE 08 00 D1 05 E.#.k..T .`9.....
+[0650] 5F 6F 79 96 30 28 06 DD C7 75 52 8E 3C C4 3F FC _oy.0(.. .uR.<.?.
+[0660] C1 31 28 2C 64 3B D1 7E 2F C2 DB B0 E8 A8 EF C5 .1(,d;.~ /.......
+[0670] F2 DC 43 D0 14 21 C8 D0 D3 15 45 8E 2A 3E 3B 4A ..C..!.. ..E.*>;J
+[0680] 60 25 3D 11 E4 F9 16 02 3E 55 8F CE D2 E9 95 E7 `%=..... >U......
+[0690] B1 C4 8F C4 0B 3E 3C 14 15 28 1A 21 49 15 CE 8E .....><. .(.!I...
+[06A0] 91 5E 98 71 00 1F 29 D3 12 C8 D0 11 4F E7 14 E3 .^.q..). ....O...
+[06B0] 72 1B 61 6D 7B 8A 00 A6 5E 01 01 50 C2 CF 1A A9 r.am{... ^..P....
+[06C0] 34 8C BA 33 9E 62 C5 69 97 6A 24 3D E0 C6 3F C6 4..3.b.i .j$=..?.
+[06D0] F4 36 B1 80 D6 5C 44 19 5B 65 C7 CA 47 DE 4B 65 .6...\D. [e..G.Ke
+[06E0] 41 29 9F F8 EA E8 E0 3B E2 C6 98 9D 58 A4 6C 62 A).....; ....X.lb
+[06F0] EF 25 12 C9 0E 97 CE 9D F0 D8 08 AD 13 73 A6 82 .%...... .....s..
+[0700] C5 54 23 F4 A4 CB 91 35 91 BD 10 B4 04 DD 55 7E .T#....5 ......U~
+[0710] C9 DE AE CB B0 8F C0 D8 28 AE BD 78 64 91 6C AB ........ (..xd.l.
+[0720] CA 36 EA 0E 0E 97 DC 40 ED 26 1D 09 17 28 30 D3 .6.....@ .&...(0.
+[0730] 78 DC F7 D2 9C 78 DA 6F 6F 57 00 B3 FD 8E 75 A1 x....x.o oW....u.
+[0740] 56 98 5C 4B D8 61 A6 0A 89 27 CD 11 BF 7F 79 53 V.\K.a.. .'....yS
+[0750] D9 50 9A 8D EC DD DB BB B8 23 27 0D 20 5B 53 51 .P...... .#'. [SQ
+[0760] 07 C4 26 31 3B D4 DF ED 3C 40 B4 1C 8B 46 E2 A6 ..&1;... <@...F..
+[0770] B7 0F 97 D2 B3 1D 19 FD 13 60 7B 38 E6 37 0C 59 ........ .`{8.7.Y
+[0780] B0 A8 47 5D 32 A5 0C 57 76 EF 2C ED 40 9F BF 4B ..G]2..W v.,.@..K
+[0790] 43 99 3C 68 C4 DE 84 9C A1 36 8C CA CB 2A 08 36 C.<h.... .6...*.6
+[07A0] 4E CD 43 06 9E F8 E7 1D 52 3B 59 37 4F 6F 65 D9 N.C..... R;Y7Ooe.
+[07B0] 2A F9 AD 5A 50 95 71 3F B1 5F C8 8E 2E E9 E4 FE *..ZP.q? ._......
+[07C0] C8 A9 42 2C EE 18 E0 81 3C 00 E2 80 8D 8A 8B 71 ..B,.... <......q
+[07D0] C7 F5 AC 5C 36 1D E0 BC F0 11 57 67 CB 2C BE F6 ...\6... ..Wg.,..
+[07E0] 90 4E F9 90 97 14 1F 0C 9D 5D 4D DF 0D D0 C0 C5 .N...... .]M.....
+[07F0] 08 E7 31 72 8E 35 63 17 8D 8B 3D 49 14 C8 A5 90 ..1r.5c. ..=I....
+[0800] 88 24 AF 75 CA 0A CB 95 8A 2C 70 A6 CE 2F 3F B6 .$.u.... .,p../?.
+[0810] D7 1A 44 AC 05 93 EF 3D 03 C7 C2 8E 0F 31 9F 53 ..D....= .....1.S
+[0820] 67 CA 73 D3 B8 07 76 36 35 6F B5 32 30 38 86 7E g.s...v6 5o.208.~
+[0830] 7E 95 3F DC F4 6F A9 67 0E 15 E8 4A CA 3F 18 0E ~.?..o.g ...J.?..
+[0840] C6 E7 20 22 6B F1 39 6A 9C A6 47 64 81 E4 CB A8 .. "k.9j ..Gd....
+[0850] 31 FF E2 97 13 41 89 45 79 53 2B A8 90 97 DE 7B 1....A.E yS+....{
+[0860] 18 56 95 02 2A 94 D2 7E 5C D0 A0 BC A0 38 D2 BC .V..*..~ \....8..
+[0870] 03 91 F7 35 FE 1A 5E 80 10 13 4E 83 CB F6 D7 8A ...5..^. ..N.....
+[0880] 02 A2 E8 1F D8 9B F1 76 F9 18 66 56 9C 4D 9E BF .......v ..fV.M..
+[0890] 1D F4 66 86 E0 7B 88 EC 9C F7 50 13 7D 34 8A 54 ..f..{.. ..P.}4.T
+[08A0] 7A E1 EC F6 44 12 47 84 7D 16 B4 42 25 E5 A2 CC z...D.G. }..B%...
+[08B0] D8 CA 7A 38 21 85 A3 F8 41 6D 0D AC 1D FA 36 5D ..z8!... Am....6]
+[08C0] 23 EA 20 CC 43 A5 7E D9 25 97 BC 0E 74 F5 3D 98 #. .C.~. %...t.=.
+[08D0] B9 79 C2 65 50 0E 8D E7 7A F3 F3 88 37 A3 40 01 .y.eP... z...7.@.
+[08E0] 96 C6 FC 1D 6E 9E 06 A1 90 A0 78 3C DA 7F E9 C6 ....n... ..x<....
+[08F0] 23 47 70 04 03 EE C2 4A C3 95 07 44 00 BD 29 2A #Gp....J ...D..)*
+[0900] B5 FA 17 1E D6 BC 00 A0 93 55 E0 82 0A AB 04 D4 ........ .U......
+[0910] D5 56 84 2A B2 56 51 05 DB 30 E2 83 5A 75 D3 A8 .V.*.VQ. .0..Zu..
+[0920] 30 B7 3E C4 25 70 A8 34 E4 A2 EB 3E FB D8 2D 10 0.>.%p.4 ...>..-.
+[0930] 72 8E DA 4D 2D 55 EC 49 66 5E 01 96 E4 C1 0C 23 r..M-U.I f^.....#
+[0940] 57 91 00 00 00 00 00 00 00 01 00 00 00 01 00 00 W....... ........
+[0950] 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 ..KTEST. SAMBA.EX
+[0960] 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D 61 64 6D AMPLE.CO M....adm
+[0970] 69 6E 69 73 74 72 61 74 6F 72 00 00 00 01 00 00 inistrat or......
+[0980] 00 02 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 ......KT EST.SAMB
+[0990] 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 A.EXAMPL E.COM...
+[09A0] 04 68 6F 73 74 00 00 00 0B 4C 4F 43 41 4C 4B 54 .host... .LOCALKT
+[09B0] 45 53 54 36 00 17 00 00 00 10 9D AE 06 BE 29 E0 EST6.... ......).
+[09C0] F7 9A 46 97 29 E0 69 8E 5A F0 4D 9B 90 45 4D 9B ..F.).i. Z.M..EM.
+[09D0] 90 61 7D 46 4C 43 00 00 00 00 00 40 28 00 00 00 .a}FLC.. ...@(...
+[09E0] 00 00 00 00 00 00 00 00 00 03 FA 61 82 03 F6 30 ........ ...a...0
+[09F0] 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B 54 45 53 ........ ....KTES
+[0A00] 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E T.SAMBA. EXAMPLE.
+[0A10] 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 15 30 13 COM..0.. ......0.
+[0A20] 1B 04 68 6F 73 74 1B 0B 4C 4F 43 41 4C 4B 54 45 ..host.. LOCALKTE
+[0A30] 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 02 01 17 ST6....0 ........
+[0A40] A1 03 02 01 03 A2 82 03 9C 04 82 03 98 B9 C5 6E ........ .......n
+[0A50] 77 F9 59 6D 19 F0 A6 56 2F 14 B3 9A A3 17 06 A6 w.Ym...V /.......
+[0A60] AD F5 92 38 6A 1E EA 3D 53 BF 5E 95 13 FF 5D BB ...8j..= S.^...].
+[0A70] 43 4F 51 AE FB 12 3B 06 67 36 91 B9 E0 C4 C4 F3 COQ...;. g6......
+[0A80] 45 A0 48 E6 DC 49 E8 EA 6F 55 D2 3F 79 57 54 FF E.H..I.. oU.?yWT.
+[0A90] 10 8D 89 4A A4 E2 B2 80 FD EE 36 C5 D5 4C D0 97 ...J.... ..6..L..
+[0AA0] B3 EC 96 8B E8 5A 05 F0 13 39 8B 1B B3 C4 32 2A .....Z.. .9....2*
+[0AB0] 9B BB EF 06 C4 1C 53 2F 0A F6 A8 C6 BE 09 57 26 ......S/ ......W&
+[0AC0] B9 39 7B 7B 50 13 2D 6C 52 FF C4 B5 83 28 A8 47 .9{{P.-l R....(.G
+[0AD0] 5A CD 1C DD A7 65 FD 8A 84 2A 10 E7 44 E6 83 E7 Z....e.. .*..D...
+[0AE0] E7 AA B8 E5 0A 8B 7E E1 87 7B 3D C4 9F 68 BD 19 ......~. .{=..h..
+[0AF0] 2B 59 5E 5A 45 0D B5 71 CC A6 C7 03 3C B3 17 D3 +Y^ZE..q ....<...
+[0B00] AF 99 F6 A2 52 A0 99 F7 39 56 B4 33 B4 C5 F4 CC ....R... 9V.3....
+[0B10] 74 34 4C 00 76 26 10 D1 3A 87 6E 6A 52 9B 7A BF t4L.v&.. :.njR.z.
+[0B20] 4E 59 36 32 C5 41 29 CF E1 BF 14 E0 54 BF 4A 25 NY62.A). ....T.J%
+[0B30] 1F 0B 6E 9A 8C 0E 5D 47 A9 64 1B A4 9D 99 A9 09 ..n...]G .d......
+[0B40] 39 14 E7 41 22 98 8C 62 CC E2 B5 91 8E C1 31 EB 9..A"..b ......1.
+[0B50] B2 70 A6 3B 86 FC DD 19 0B 3F 5D C9 B5 1A 95 73 .p.;.... .?]....s
+[0B60] EB 97 89 BE 14 87 85 17 BE 40 F6 80 14 23 4D 66 ........ .@...#Mf
+[0B70] E4 B0 E5 51 46 34 DA 1C C8 CB FF C6 84 A3 DF D2 ...QF4.. ........
+[0B80] DC 00 AF 7B 27 C8 78 44 CB 6E 7B CC 5C 94 1E 7A ...{'.xD .n{.\..z
+[0B90] 95 29 19 F4 14 BE 5C 23 C3 B9 A4 2C 5D 4D F3 61 .)....\# ...,]M.a
+[0BA0] 63 1F D4 FE 37 EE 44 14 06 B7 14 50 B6 74 37 75 c...7.D. ...P.t7u
+[0BB0] 2C AB 06 F0 93 F9 93 34 75 63 44 7E 12 48 D1 F1 ,......4 ucD~.H..
+[0BC0] 06 55 14 11 B9 23 43 CE 01 16 3E 6B A3 BD 23 55 .U...#C. ..>k..#U
+[0BD0] DE 48 5D AF E1 2B 89 E8 E7 C2 E2 34 25 A2 09 4A .H]..+.. ...4%..J
+[0BE0] 1F BE 05 AA DE 4B 08 65 27 4C 9B C7 54 96 C2 FB .....K.e 'L..T...
+[0BF0] E2 CE 53 4A 32 93 8D 0B 44 77 8C D3 65 54 F9 0E ..SJ2... Dw..eT..
+[0C00] 7F 74 1E FE 3D 74 83 0F 2F E7 9F BC A2 B0 2B 25 .t..=t.. /.....+%
+[0C10] BB D2 6F A8 49 C1 3E 9E B5 93 67 74 39 A4 FE 84 ..o.I.>. ..gt9...
+[0C20] 4C 45 5F 30 74 E0 CA 5F F6 46 EC 89 B5 2D C8 14 LE_0t.._ .F...-..
+[0C30] 69 76 BC 93 15 F4 60 30 5F AB EB 02 DD 12 4C 62 iv....`0 _.....Lb
+[0C40] F9 73 F7 01 E1 7F 2A 6F 09 05 BF 3A 3A 7E 69 A3 .s....*o ...::~i.
+[0C50] 7B FC 20 2B D6 CE C0 74 4F BB 29 E4 BE CE 04 9D {. +...t O.).....
+[0C60] 24 D4 98 4A ED 94 A8 81 CD 26 A0 63 EA 09 57 42 $..J.... .&.c..WB
+[0C70] 26 B7 B5 4E B5 CB 45 35 A7 84 D8 74 CA C3 9F FF &..N..E5 ...t....
+[0C80] C8 1E 2A 75 34 01 C5 A7 B4 9D 6F A3 E1 BB 2B F8 ..*u4... ..o...+.
+[0C90] F0 21 D6 77 57 74 2E 80 DB 76 53 01 86 33 17 32 .!.wWt.. .vS..3.2
+[0CA0] 2E 16 E1 8D 89 3A B2 67 ED A3 ED 39 82 87 26 A6 .....:.g ...9..&.
+[0CB0] DB CE 59 84 E4 0A A6 CA 7E 07 98 F7 02 91 6E 56 ..Y..... ~.....nV
+[0CC0] 9F 60 03 D3 88 B0 FF EB 20 CA 9E 5B 37 26 67 00 .`...... ..[7&g.
+[0CD0] CC BD 9D 53 15 31 53 14 FD 9C E1 28 08 CB C4 0B ...S.1S. ...(....
+[0CE0] E3 50 D9 DB 0C E2 E4 F9 44 50 E9 28 6E 01 96 AA .P...... DP.(n...
+[0CF0] C1 D2 4E B2 DE 38 A2 F8 94 32 79 AE 49 64 FB 57 ..N..8.. .2y.Id.W
+[0D00] 50 F6 73 E8 98 43 C6 DD 67 3C 91 AC 97 C9 2E 8C P.s..C.. g<......
+[0D10] 06 59 A1 FC 49 EC 2F BF 6F 64 21 63 ED C8 6C CE .Y..I./. od!c..l.
+[0D20] 37 28 7B 80 7F 5F 85 F6 98 93 C0 66 A8 D6 F1 2C 7({.._.. ...f...,
+[0D30] D8 01 68 B1 C8 EA 82 0D 5B 9B 35 4F 3D B3 47 19 ..h..... [.5O=.G.
+[0D40] 54 7A C6 9F AD D7 54 CF B0 DB 3E 18 BA 2A 39 08 Tz....T. ..>..*9.
+[0D50] 0C C4 98 4B 43 DE 53 68 25 B1 83 93 1D E1 6C BF ...KC.Sh %.....l.
+[0D60] F5 B4 A9 83 17 34 64 8C 2F 91 80 97 4A 48 EC 90 .....4d. /...JH..
+[0D70] BB FA 92 2C 01 80 E4 99 91 0E 67 88 D5 75 AB 7C ...,.... ..g..u.|
+[0D80] 98 59 98 45 C9 11 A9 8C 02 98 91 DE AB A0 FF 45 .Y.E.... .......E
+[0D90] 11 66 6F C5 DE 61 6D C6 DB C9 CA A3 A0 2B B1 73 .fo..am. .....+.s
+[0DA0] 05 85 37 BF AB CA 43 7A 6F 38 C8 BE ED CE 12 49 ..7...Cz o8.....I
+[0DB0] 93 C7 7C 1A 33 60 52 7A 67 67 AA 60 57 7E C8 FF ..|.3`Rz gg.`W~..
+[0DC0] DF 91 91 18 45 74 C0 9E 36 19 BC 42 F9 46 CC 84 ....Et.. 6..B.F..
+[0DD0] 09 2E 8C 59 1A E3 65 51 F4 87 6F 4C 3E 29 38 E6 ...Y..eQ ..oL>)8.
+[0DE0] 77 E8 A9 B7 FA 00 00 00 00 00 00 00 01 00 00 00 w....... ........
+[0DF0] 01 00 00 00 17 4B 54 45 53 54 2E 53 41 4D 42 41 .....KTE ST.SAMBA
+[0E00] 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D 00 00 00 0D .EXAMPLE .COM....
+[0E10] 61 64 6D 69 6E 69 73 74 72 61 74 6F 72 00 00 00 administ rator...
+[0E20] 01 00 00 00 02 00 00 00 17 4B 54 45 53 54 2E 53 ........ .KTEST.S
+[0E30] 41 4D 42 41 2E 45 58 41 4D 50 4C 45 2E 43 4F 4D AMBA.EXA MPLE.COM
+[0E40] 00 00 00 04 63 69 66 73 00 00 00 0B 4C 4F 43 41 ....cifs ....LOCA
+[0E50] 4C 4B 54 45 53 54 36 00 17 00 00 00 10 01 78 D0 LKTEST6. ......x.
+[0E60] 3B 9B FF F0 88 86 4B 3B FE 41 A9 6B 00 4D 9B 90 ;.....K; .A.k.M..
+[0E70] 45 4D 9B 90 6B 7D 46 4C 43 00 00 00 00 00 40 28 EM..k}FL C.....@(
+[0E80] 00 00 00 00 00 00 00 00 00 00 00 00 03 FA 61 82 ........ ......a.
+[0E90] 03 F6 30 82 03 F2 A0 03 02 01 05 A1 19 1B 17 4B ..0..... .......K
+[0EA0] 54 45 53 54 2E 53 41 4D 42 41 2E 45 58 41 4D 50 TEST.SAM BA.EXAMP
+[0EB0] 4C 45 2E 43 4F 4D A2 1E 30 1C A0 03 02 01 01 A1 LE.COM.. 0.......
+[0EC0] 15 30 13 1B 04 63 69 66 73 1B 0B 4C 4F 43 41 4C .0...cif s..LOCAL
+[0ED0] 4B 54 45 53 54 36 A3 82 03 AE 30 82 03 AA A0 03 KTEST6.. ..0.....
+[0EE0] 02 01 17 A1 03 02 01 03 A2 82 03 9C 04 82 03 98 ........ ........
+[0EF0] CA EA 4D 46 2D D1 E9 58 5D 25 8D 9F DF EA C9 01 ..MF-..X ]%......
+[0F00] B6 08 27 CD 14 85 02 DC 20 C6 51 AA F9 6A B1 CE ..'..... .Q..j..
+[0F10] F5 77 84 BF 9A AC 6B A7 B2 F2 1F 60 BF CB C6 FC .w....k. ...`....
+[0F20] C7 14 B7 41 1C A8 C9 70 7B 86 BC 8E 70 2B 65 4B ...A...p {...p+eK
+[0F30] DC F5 B9 23 F8 08 BF 96 C9 A8 77 F4 54 67 25 F8 ...#.... ..w.Tg%.
+[0F40] 0F A8 C5 D6 D1 BB 46 5E A0 7E D2 98 9C CD AF E0 ......F^ .~......
+[0F50] 82 62 ED 39 D2 FB F2 E8 9B 1B EE E5 B4 1B C9 0A .b.9.... ........
+[0F60] 86 27 52 6E 11 8B D7 AD B4 54 F9 C6 69 8D E0 F1 .'Rn.... .T..i...
+[0F70] CD 63 1C 89 7C 8F B6 A0 71 53 A6 DA B1 66 D2 9D .c..|... qS...f..
+[0F80] D3 4C A8 FB C6 9D 81 74 10 8E 84 D2 3D D8 1C BE .L.....t ....=...
+[0F90] BB 3F F7 BF 91 3E 89 66 43 A1 E0 90 1B 1A 97 FF .?...>.f C.......
+[0FA0] EF CC 35 75 14 62 4F 67 3A 29 F4 F9 C5 2E BE C5 ..5u.bOg :)......
+[0FB0] C2 2B A8 35 22 D9 92 31 1D 49 2A A5 19 AA 08 0F .+.5"..1 .I*.....
+[0FC0] A8 22 0B 68 D2 A2 D7 07 7B 37 1E A3 AC 9B 4F 0A .".h.... {7....O.
+[0FD0] A4 FA 7F 37 6F 3E 35 79 4E 00 4B B6 28 A3 6A E4 ...7o>5y N.K.(.j.
+[0FE0] 0C 95 53 BA E8 41 07 DA BE E9 08 B9 51 24 91 49 ..S..A.. ....Q$.I
+[0FF0] 78 5D 44 12 BC 85 63 81 B8 E0 88 D5 95 0C D3 A8 x]D...c. ........
+[1000] 1D 32 4B E4 A0 C8 A7 7D 3C 97 EE D8 59 AC 3A 21 .2K....} <...Y.:!
+[1010] 09 F2 7A CC D0 4A F3 50 10 DC FC 26 BB C2 6A 8E ..z..J.P ...&..j.
+[1020] 8B 14 2B 2D 50 2E B3 1E 9B D2 69 56 22 F2 48 BD ..+-P... ..iV".H.
+[1030] E9 2E 2F 28 DE 77 67 5F 68 AA 29 05 4B 36 58 40 ../(.wg_ h.).K6X@
+[1040] E5 54 11 C5 4D 68 96 49 9D 53 37 87 5F D2 3A 9B .T..Mh.I .S7._.:.
+[1050] E9 8E 79 BE AE 11 B4 6B AB FD DB 8A F5 A0 9B 29 ..y....k .......)
+[1060] D9 F5 ED CA FA 3F FE 35 FC F4 69 7E E4 D0 44 29 .....?.5 ..i~..D)
+[1070] 48 FF 82 61 26 FC D3 E2 10 EE 14 F7 4A E3 CD F2 H..a&... ....J...
+[1080] 8B BC 8B 43 64 2C DE 40 6E BB E1 56 C0 B6 2C D0 ...Cd,.@ n..V..,.
+[1090] E5 1E E9 B3 FB 38 48 66 ED AF D2 25 D1 35 5C C6 .....8Hf ...%.5\.
+[10A0] F0 4D 36 19 0B EC 33 07 34 D0 27 8D 14 DC 01 45 .M6...3. 4.'....E
+[10B0] DE F8 73 A6 A0 F4 C1 91 9D BD 05 E3 70 25 E1 10 ..s..... ....p%..
+[10C0] 44 F6 4B 46 F7 24 84 BF 20 96 AD 6A 96 94 81 58 D.KF.$.. ..j...X
+[10D0] 80 95 06 92 F5 7F 17 39 3B 32 47 B2 C5 CE 7B 73 .......9 ;2G...{s
+[10E0] CF 53 AE FA D1 9A 60 5A 98 EC 8C FA BD C0 CE 8D .S....`Z ........
+[10F0] C5 27 E6 17 1A 4D 47 D8 3F 5D A9 7C FB 2C B3 05 .'...MG. ?].|.,..
+[1100] 0C 69 20 48 99 80 11 DC 48 AB A7 EA 5B 98 C1 15 .i H.... H...[...
+[1110] 27 AE FA 3E 1E 1E E0 E1 F8 32 C0 54 13 D6 30 34 '..>.... .2.T..04
+[1120] 71 98 26 61 6C 1C C4 C7 4E C4 A6 7E FE A8 B8 89 q.&al... N..~....
+[1130] 2A 70 3C 19 58 8D 57 45 55 83 0A C2 B5 F7 89 0E *p<.X.WE U.......
+[1140] 7B 7A 17 0C CF 6E 08 A5 F7 21 4A 62 81 4F 49 CA {z...n.. .!Jb.OI.
+[1150] E2 ED C2 B4 C7 33 5C BC A1 A0 DE 4E 09 37 BE 24 .....3\. ...N.7.$
+[1160] 62 22 94 55 75 AA 53 DE E0 74 5A B0 B8 E9 BF 2B b".Uu.S. .tZ....+
+[1170] 12 65 2F 90 6B 84 ED 11 AD F7 CE 19 A1 96 E4 1E .e/.k... ........
+[1180] 8C EA C8 81 1B 47 4F 5F B1 5D A5 8B E3 0D 5A 80 .....GO_ .]....Z.
+[1190] 89 EC 4B D9 CE ED E8 67 7F 96 FC 1B EF 65 C2 68 ..K....g .....e.h
+[11A0] 40 F7 20 36 83 58 62 F4 CA 02 F4 5C 0D 46 B1 CB @. 6.Xb. ...\.F..
+[11B0] 50 D2 D8 3D B7 9A 96 48 8C CF EB E6 8C F4 B2 B4 P..=...H ........
+[11C0] 47 C9 34 C9 DC 14 F1 33 1B 6F 9E 65 27 D7 9D 46 G.4....3 .o.e'..F
+[11D0] 1E 91 FF 2E FB 8E 97 5D 17 8F 48 54 7C 3C A0 11 .......] ..HT|<..
+[11E0] 9C AA 77 E9 79 DE 26 D1 F0 7C EA 24 73 BE EC 60 ..w.y.&. .|.$s..`
+[11F0] B4 EE BD ED 0D 0A AB 74 60 6E 46 C0 35 5B 65 1A .......t `nF.5[e.
+[1200] A4 4A 5C 22 AC B9 CD B7 56 06 88 09 FC 48 68 55 .J\".... V....HhU
+[1210] B7 5E 39 72 DF 8A 4C CD 79 74 B0 84 0B 78 DA B2 .^9r..L. yt...x..
+[1220] 55 F8 06 0B 5C 27 06 B3 CA 10 65 6B 04 A3 64 11 U...\'.. ..ek..d.
+[1230] 04 09 DC DF 67 00 70 B1 16 DF 24 E9 27 85 11 91 ....g.p. ..$.'...
+[1240] 31 CB 92 95 50 18 91 08 C2 A1 A3 76 C7 1A FC 64 1...P... ...v...d
+[1250] 9E 2C 3A E7 30 F4 16 0D A0 56 C0 BC D2 FE 2D A0 .,:.0... .V....-.
+[1260] 20 A4 E2 82 AD F0 C5 12 71 09 23 E1 66 52 53 D0 ....... q.#.fRS.
+[1270] 89 30 E7 BE B7 C2 89 F2 1C 7A F6 8E D7 28 F0 A4 .0...... .z...(..
+[1280] 33 46 7C A2 79 66 DE 26 00 00 00 00 3F|.yf.& ....
+dump OK
diff --git a/source3/selftest/ktest-secrets.tdb b/source3/selftest/ktest-secrets.tdb
new file mode 100644
index 0000000..cf5c3d0
--- /dev/null
+++ b/source3/selftest/ktest-secrets.tdb
Binary files differ
diff --git a/source3/selftest/tests.py b/source3/selftest/tests.py
new file mode 100755
index 0000000..0901c24
--- /dev/null
+++ b/source3/selftest/tests.py
@@ -0,0 +1,1918 @@
+#!/usr/bin/python
+# This script generates a list of testsuites that should be run as part of
+# the Samba 3 test suite.
+
+# The output of this script is parsed by selftest.pl, which then decides
+# which of the tests to actually run. It will, for example, skip all tests
+# listed in selftest/skip or only run a subset during "make quicktest".
+
+# The idea is that this script outputs all of the tests of Samba 3, not
+# just those that are known to pass, and list those that should be skipped
+# or are known to fail in selftest/skip or selftest/samba3-knownfail. This makes it
+# very easy to see what functionality is still missing in Samba 3 and makes
+# it possible to run the testsuite against other servers, such as Samba 4 or
+# Windows that have a different set of features.
+
+# The syntax for a testsuite is "-- TEST --" on a single line, followed
+# by the name of the test, the environment it needs and the command to run, all
+# three separated by newlines. All other lines in the output are considered
+# comments.
+
+import os
+import sys
+import re
+import platform
+sys.path.insert(0, os.path.normpath(os.path.join(os.path.dirname(__file__), "../../selftest")))
+import selftesthelpers
+from selftesthelpers import bindir, srcdir, scriptdir, binpath
+from selftesthelpers import plantestsuite, samba3srcdir
+from selftesthelpers import planpythontestsuite
+from selftesthelpers import smbtorture3, configuration, smbclient3, smbtorture4
+from selftesthelpers import net, wbinfo, dbwrap_tool, rpcclient, python
+from selftesthelpers import smbget, smbcacls, smbcquotas, ntlm_auth3
+from selftesthelpers import valgrindify, smbtorture4_testsuites
+from selftesthelpers import smbtorture4_options
+from selftesthelpers import smbcontrol
+from selftesthelpers import smbstatus
+from selftesthelpers import timelimit
+smbtorture4_options.extend([
+ '--option=torture:sharedelay=100000',
+ '--option=torture:writetimeupdatedelay=500000',
+])
+
+
+def plansmbtorture4testsuite(name, env, options, description='', environ=None):
+ if description == '':
+ modname = "samba3.%s" % (name, )
+ else:
+ modname = "samba3.%s %s" % (name, description)
+
+ selftesthelpers.plansmbtorture4testsuite(
+ name, env, options, target='samba3', modname=modname, environ=environ)
+
+def compare_versions(version1, version2):
+ for i in range(max(len(version1),len(version2))):
+ v1 = version1[i] if i < len(version1) else 0
+ v2 = version2[i] if i < len(version2) else 0
+ if v1 > v2:
+ return 1
+ elif v1 <v2:
+ return -1
+ return 0
+
+# find config.h
+try:
+ config_h = os.environ["CONFIG_H"]
+except KeyError:
+ samba4bindir = bindir()
+ config_h = os.path.join(samba4bindir, "default/include/config.h")
+
+bbdir = os.path.join(srcdir(), "testprogs/blackbox")
+
+# check available features
+config_hash = dict()
+f = open(config_h, 'r')
+try:
+ lines = f.readlines()
+ config_hash = dict((x[0], ' '.join(x[1:]))
+ for x in map(lambda line: line.strip().split(' ')[1:],
+ filter(lambda line: (line[0:7] == '#define') and (len(line.split(' ')) > 2), lines)))
+finally:
+ f.close()
+
+linux_kernel_version = None
+if platform.system() == 'Linux':
+ m = re.search(r'(\d+).(\d+).(\d+)', platform.release())
+ if m:
+ linux_kernel_version = [int(m.group(1)), int(m.group(2)), int(m.group(3))]
+
+have_linux_kernel_oplocks = False
+if "HAVE_KERNEL_OPLOCKS_LINUX" in config_hash:
+ if compare_versions(linux_kernel_version, [5,3,1]) >= 0:
+ have_linux_kernel_oplocks = True
+
+have_inotify = ("HAVE_INOTIFY" in config_hash)
+have_ldwrap = ("HAVE_LDWRAP" in config_hash)
+with_pthreadpool = ("WITH_PTHREADPOOL" in config_hash)
+
+have_cluster_support = "CLUSTER_SUPPORT" in config_hash
+
+def is_module_enabled(module):
+ if module in config_hash["STRING_SHARED_MODULES"]:
+ return True
+ if module in config_hash["STRING_STATIC_MODULES"]:
+ return True
+ return False
+
+plantestsuite("samba3.blackbox.success", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_success.sh")])
+plantestsuite("samba3.blackbox.failure", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_failure.sh")])
+
+plantestsuite("samba3.local_s3", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_local_s3.sh")])
+
+plantestsuite("samba3.blackbox.registry.upgrade", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/test_registry_upgrade.sh"), net, dbwrap_tool])
+
+fileserver_tests = [
+ "FDPASS", "LOCK1", "LOCK2", "LOCK3", "LOCK4", "LOCK5", "LOCK6", "LOCK7",
+ "LOCK9A", "LOCK9B",
+ "LOCK10",
+ "LOCK11",
+ "LOCK12",
+ "LOCK13",
+ "UNLINK", "BROWSE", "ATTR", "TRANS2", "TORTURE",
+ "OPLOCK1", "OPLOCK2", "OPLOCK4", "STREAMERROR",
+ "DIR", "DIR1", "DIR-CREATETIME", "TCON", "TCONDEV", "RW1", "RW2", "RW3", "LARGE_READX", "RW-SIGNING",
+ "OPEN", "XCOPY", "RENAME", "DELETE", "DELETE-LN", "PROPERTIES", "W2K",
+ "TCON2", "IOCTL", "CHKPATH", "FDSESS", "CHAIN1", "CHAIN2", "OWNER-RIGHTS",
+ "CHAIN3", "PIDHIGH", "CLI_SPLICE",
+ "UID-REGRESSION-TEST", "SHORTNAME-TEST",
+ "CASE-INSENSITIVE-CREATE", "SMB2-BASIC", "NTTRANS-FSCTL", "SMB2-NEGPROT",
+ "SMB2-SESSION-REAUTH", "SMB2-SESSION-RECONNECT", "SMB2-FTRUNCATE",
+ "SMB2-ANONYMOUS", "SMB2-DIR-FSYNC",
+ "SMB2-PATH-SLASH",
+ "SMB2-QUOTA1",
+ "CLEANUP1",
+ "CLEANUP2",
+ "CLEANUP4",
+ "DELETE-STREAM",
+ "BAD-NBT-SESSION",
+ "SMB1-WILD-MANGLE-UNLINK",
+ "SMB1-WILD-MANGLE-RENAME"]
+
+for t in fileserver_tests:
+ fileserver_env = "fileserver_smb1"
+ if "SMB2" in t:
+ fileserver_env = "fileserver"
+ plantestsuite("samba3.smbtorture_s3.plain.%s" % t, fileserver_env, [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/tmp', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+ plantestsuite("samba3.smbtorture_s3.crypt_client.%s" % t, "nt4_dc_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/tmp', '$USERNAME', '$PASSWORD', smbtorture3, "-e", "-l $LOCAL_PATH"])
+ if t == "TORTURE":
+ # this is a negative test to verify that the server rejects
+ # access without encryption
+ plantestsuite("samba3.smbtorture_s3.crypt_server.%s" % t, "nt4_dc_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/tmpenc', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+ if t == "CLI_SPLICE":
+ # We must test this against the SMB1 fallback.
+ plantestsuite("samba3.smbtorture_s3.plain.%s" % t, "fileserver_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/tmp', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH", "-mNT1"])
+ plantestsuite("samba3.smbtorture_s3.plain.%s" % t, "ad_dc_ntvfs", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/tmp', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+
+t = "TLDAP"
+plantestsuite("samba3.smbtorture_s3.plain.%s" % t, "ad_dc", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER/tmp', '$DC_USERNAME', '$DC_PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+
+if have_linux_kernel_oplocks:
+ t = "OPLOCK5"
+ plantestsuite("samba3.smbtorture_s3.plain.%s" % t,
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ t,
+ '//$SERVER/tmp',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH",
+ "-mNT1"])
+#
+# RENAME-ACCESS needs to run against a special share - acl_xattr_ign_sysacl_windows
+#
+plantestsuite("samba3.smbtorture_s3.plain.%s" % "RENAME-ACCESS", "nt4_dc_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), "RENAME-ACCESS", '//$SERVER_IP/acl_xattr_ign_sysacl_windows', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+plantestsuite("samba3.smbtorture_s3.crypt_client.%s" % "RENAME-ACCESS", "nt4_dc_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), "RENAME-ACCESS", '//$SERVER_IP/acl_xattr_ign_sysacl_windows', '$USERNAME', '$PASSWORD', smbtorture3, "-e", "-l $LOCAL_PATH"])
+# non-crypt only
+
+tests = ["OPLOCK-CANCEL"]
+for t in tests:
+ plantestsuite("samba3.smbtorture_s3.plain.%s" % t, "nt4_dc", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/tmp', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+
+env = "nt4_dc_smb1"
+tests = ["MANGLE-ILLEGAL"]
+for t in tests:
+ plantestsuite("samba3.smbtorture_s3.plain.%s" % t, env, [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/mangle_illegal', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+
+tests = ["RW1", "RW2", "RW3", "SMB2-BASIC"]
+for t in tests:
+ if t == "SMB2-BASIC":
+ env = "simpleserver"
+ else:
+ env = "fileserver_smb1"
+
+ plantestsuite("samba3.smbtorture_s3.vfs_aio_pthread(%s).%s" % (env, t), env, [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/vfs_aio_pthread', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+ plantestsuite("samba3.smbtorture_s3.vfs_aio_fork(%s).%s" % (env, t), env, [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/vfs_aio_fork', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+
+plantestsuite("samba3.smbtorture_s3.hidenewfiles",
+ "simpleserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'hide-new-files-timeout',
+ '//$SERVER_IP/hidenewfiles',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+plantestsuite("samba3.smbtorture_s3.hidenewfiles_showdirs",
+ "simpleserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'hide-new-files-timeout-showdirs',
+ '//$SERVER_IP/hidenewfiles',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+
+plantestsuite("samba3.smbtorture_s3.smb1.SMB1-TRUNCATED-SESSSETUP",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB1-TRUNCATED-SESSSETUP',
+ '//$SERVER_IP/tmp',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1"])
+
+plantestsuite("samba3.smbtorture_s3.smb1.SMB1-NEGOTIATE-EXIT",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB1-NEGOTIATE-EXIT',
+ '//$SERVER_IP/tmp',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1"])
+
+plantestsuite("samba3.smbtorture_s3.smb1.SMB1-NEGOTIATE-TCON",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB1-NEGOTIATE-TCON',
+ '//$SERVER_IP/tmp',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1"])
+
+#
+# MSDFS attribute tests.
+#
+plantestsuite("samba3.smbtorture_s3.smb2.MSDFS-ATTRIBUTE",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'MSDFS-ATTRIBUTE',
+ '//$SERVER_IP/msdfs-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mSMB2",
+ "-f msdfs-src1"])
+
+plantestsuite("samba3.smbtorture_s3.smb1.MSDFS-ATTRIBUTE",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'MSDFS-ATTRIBUTE',
+ '//$SERVER_IP/msdfs-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1",
+ "-f msdfs-src1"])
+
+#
+# SMB2-DFS-PATHS needs to run against a special share msdfs-pathname-share
+# This is an empty DFS share with no links, used merely to test
+# incoming DFS pathnames and how they map to local paths.
+#
+plantestsuite("samba3.smbtorture_s3.smb2.SMB2-DFS-PATHS",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-DFS-PATHS',
+ '//$SERVER_IP/msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mSMB2"])
+
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15277
+# MacOSX clients send a leading '\\' character for DFS paths.
+#
+plantestsuite("samba3.smbtorture_s3.smb2.SMB2-DFS-FILENAME-LEADING-BACKSLASH",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-DFS-FILENAME-LEADING-BACKSLASH',
+ '//$SERVER_IP/msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mSMB2"])
+
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15422
+# Prevent bad pipenames.
+#
+plantestsuite("samba3.smbtorture_s3.smb2.SMB2-INVALID-PIPENAME",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-INVALID-PIPENAME',
+ '//$SERVER_IP/tmp',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mSMB2"])
+
+#
+# SMB2-NON-DFS-SHARE needs to run against a special share non-msdfs-pathname-share
+# This is an empty non-DFS share with no links, used merely to test
+# incoming DFS pathnames and how they map to local paths. We are testing
+# what happens if we set the FLAGS2_DFS_PATHNAMES and send DFS paths
+# on a non-DFS share.
+#
+plantestsuite("samba3.smbtorture_s3.smb2.SMB2-NON-DFS-SHARE",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-NON-DFS-SHARE',
+ '//$SERVER_IP/non-msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mSMB2"])
+
+#
+# SMB2-DFS-SHARE-NON-DFS-PATH needs to run against a special share msdfs-pathname-share
+# This is an empty DFS share with no links, used merely to test
+# incoming non-DFS pathnames and how they map to local paths.
+#
+plantestsuite("samba3.smbtorture_s3.smb2.SMB2-DFS-SHARE-NON-DFS-PATH",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-DFS-SHARE-NON-DFS-PATH',
+ '//$SERVER_IP/msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mSMB2"])
+
+#
+# SMB1-DFS-PATHS needs to run against a special share msdfs-pathname-share
+# This is an empty DFS share with no links, used merely to test
+# incoming DFS pathnames and how they map to local paths.
+#
+plantestsuite("samba3.smbtorture_s3.smb1.SMB1-DFS-PATHS",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB1-DFS-PATHS',
+ '//$SERVER_IP/msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1"])
+
+#
+# SMB1-DFS-SEARCH-PATHS needs to run against a special share msdfs-pathname-share
+# This is an empty DFS share with no links, used merely to test
+# incoming DFS pathnames and how they map to local paths.
+#
+plantestsuite("samba3.smbtorture_s3.smb1.SMB1-DFS-SEARCH-PATHS",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB1-DFS-SEARCH-PATHS',
+ '//$SERVER_IP/msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1"])
+
+#
+# SMB1-DFS-OPERATIONS needs to run against a special share msdfs-pathname-share
+# This is an empty DFS share with no links, used merely to test
+# incoming DFS pathnames and how they map to local paths.
+#
+plantestsuite("samba3.smbtorture_s3.smb1.SMB1-DFS-OPERATIONS",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB1-DFS-OPERATIONS',
+ '//$SERVER_IP/msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1"])
+#
+# SMB1-DFS-BADPATH needs to run against a special share msdfs-pathname-share
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15419
+#
+plantestsuite("samba3.smbtorture_s3.smb1.SMB1-DFS-BADPATH",
+ "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB1-DFS-BADPATH',
+ '//$SERVER_IP/msdfs-pathname-share',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "-mNT1"])
+
+#
+# SMB2-STREAM-ACL needs to run against a special share - vfs_wo_fruit
+#
+plantestsuite("samba3.smbtorture_s3.plain.%s" % "SMB2-STREAM-ACL",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-STREAM-ACL',
+ '//$SERVER_IP/vfs_wo_fruit',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+
+#
+# SMB2-LIST-DIR-ASYNC needs to run against a special share vfs_aio_pthread_async_dosmode_default1
+#
+plantestsuite("samba3.smbtorture_s3.plain.%s" % "SMB2-LIST-DIR-ASYNC",
+ "simpleserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-LIST-DIR-ASYNC',
+ '//$SERVER_IP/vfs_aio_pthread_async_dosmode_default1',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+#
+# SMB2-DEL-ON-CLOSE-NONEMPTY needs to run against a special fileserver share veto_files_delete
+#
+plantestsuite("samba3.smbtorture_s3.plain.%s" % "SMB2-DEL-ON-CLOSE-NONEMPTY",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-DEL-ON-CLOSE-NONEMPTY',
+ '//$SERVER_IP/veto_files_delete',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+
+#
+# SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-YES needs to run against a special fileserver share delete_yes_unwrite
+#
+plantestsuite("samba3.smbtorture_s3.plain.%s" % "SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-YES",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-YES',
+ '//$SERVER_IP/delete_yes_unwrite',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+
+#
+# SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-NO needs to run against a special fileserver share delete_no_unwrite
+#
+plantestsuite("samba3.smbtorture_s3.plain.%s" % "SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-NO",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ 'SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-NO',
+ '//$SERVER_IP/delete_no_unwrite',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+
+#
+# Test doing an async read + disconnect on a pipe doesn't crash the server.
+# BUG: https://bugzilla.samba.org/show_bug.cgi?id=15423
+#
+plantestsuite("samba3.smbtorture_s3.plain.%s" % "SMB2-PIPE-READ-ASYNC-DISCONNECT",
+ "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_nocrash_s3.sh"),
+ 'SMB2-PIPE-READ-ASYNC-DISCONNECT',
+ '//$SERVER_IP/tmp',
+ '$USERNAME',
+ '$PASSWORD',
+ smbtorture3,
+ "",
+ "-l $LOCAL_PATH"])
+
+shares = [
+ "vfs_aio_pthread_async_dosmode_default1",
+ "vfs_aio_pthread_async_dosmode_default2"
+]
+for s in shares:
+ plantestsuite("samba3.smbtorture_s3.%s(simpleserver).SMB2-BASIC" % s, "simpleserver", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), 'SMB2-BASIC', '//$SERVER_IP/' + s, '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+ t = "smb2.compound_find"
+ plansmbtorture4testsuite(t, "simpleserver", "//%s/%s %s" % ('$SERVER_IP', s, ' -U$USERNAME%$PASSWORD'), description=s)
+
+posix_tests = ["POSIX", "POSIX-APPEND", "POSIX-SYMLINK-ACL", "POSIX-SYMLINK-EA", "POSIX-OFD-LOCK",
+ "POSIX-STREAM-DELETE", "WINDOWS-BAD-SYMLINK", "POSIX-MKDIR",
+ "POSIX-BLOCKING-LOCK",
+ "POSIX-ACL-OPLOCK",
+ "POSIX-ACL-SHAREROOT",
+ "POSIX-LS-WILDCARD",
+ "POSIX-LS-SINGLE",
+ "POSIX-READLINK",
+ "POSIX-STAT",
+ "POSIX-SYMLINK-PARENT",
+ "POSIX-SYMLINK-CHMOD",
+ "POSIX-DIR-DEFAULT-ACL",
+ "POSIX-SYMLINK-RENAME",
+ "POSIX-SYMLINK-GETPATHINFO",
+ "POSIX-SYMLINK-SETPATHINFO",
+ ]
+
+for t in posix_tests:
+ plantestsuite("samba3.smbtorture_s3.plain.%s" % t, "nt4_dc_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/posix_share', '$USERNAME', '$PASSWORD', smbtorture3, "", "-l $LOCAL_PATH"])
+ plantestsuite("samba3.smbtorture_s3.crypt.%s" % t, "nt4_dc_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//$SERVER_IP/posix_share', '$USERNAME', '$PASSWORD', smbtorture3, "-e", "-l $LOCAL_PATH"])
+
+local_tests = [
+ "LOCAL-SUBSTITUTE",
+ "LOCAL-GENCACHE",
+ "LOCAL-BASE64",
+ "LOCAL-RBTREE",
+ "LOCAL-MEMCACHE",
+ "LOCAL-STREAM-NAME",
+ "LOCAL-STR-MATCH-MSWILD",
+ "LOCAL-STR-MATCH-REGEX-SUB1",
+ "LOCAL-string_to_sid",
+ "LOCAL-sid_to_string",
+ "LOCAL-binary_to_sid",
+ "LOCAL-DBTRANS",
+ "LOCAL-TEVENT-POLL",
+ "LOCAL-CONVERT-STRING",
+ "LOCAL-CONV-AUTH-INFO",
+ "LOCAL-IDMAP-TDB-COMMON",
+ "LOCAL-MESSAGING-READ1",
+ "LOCAL-MESSAGING-READ2",
+ "LOCAL-MESSAGING-READ3",
+ "LOCAL-MESSAGING-READ4",
+ "LOCAL-MESSAGING-FDPASS1",
+ "LOCAL-MESSAGING-FDPASS2",
+ "LOCAL-MESSAGING-FDPASS2a",
+ "LOCAL-MESSAGING-FDPASS2b",
+ "LOCAL-MESSAGING-SEND-ALL",
+ "LOCAL-PTHREADPOOL-TEVENT",
+ "LOCAL-CANONICALIZE-PATH",
+ "LOCAL-DBWRAP-WATCH1",
+ "LOCAL-DBWRAP-WATCH2",
+ "LOCAL-DBWRAP-WATCH3",
+ "LOCAL-DBWRAP-WATCH4",
+ "LOCAL-DBWRAP-DO-LOCKED1",
+ "LOCAL-G-LOCK1",
+ "LOCAL-G-LOCK2",
+ "LOCAL-G-LOCK3",
+ "LOCAL-G-LOCK4",
+ "LOCAL-G-LOCK4A",
+ "LOCAL-G-LOCK5",
+ "LOCAL-G-LOCK6",
+ "LOCAL-G-LOCK7",
+ "LOCAL-G-LOCK8",
+ "LOCAL-NAMEMAP-CACHE1",
+ "LOCAL-IDMAP-CACHE1",
+ "LOCAL-TDB-VALIDATE",
+ "LOCAL-hex_encode_buf",
+ "LOCAL-remove_duplicate_addrs2"]
+
+for t in local_tests:
+ plantestsuite("samba3.smbtorture_s3.%s" % t, "none", [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//foo/bar', '""', '""', smbtorture3, ""])
+
+plantestsuite("samba.vfstest.stream_depot", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/stream-depot/run.sh"), binpath("vfstest"), "$PREFIX", configuration])
+plantestsuite("samba.vfstest.xattr-tdb-1", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/xattr-tdb-1/run.sh"), binpath("vfstest"), "$PREFIX", configuration])
+plantestsuite("samba.vfstest.acl", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/vfstest-acl/run.sh"), binpath("vfstest"), "$PREFIX", configuration])
+plantestsuite("samba.vfstest.catia", "nt4_dc:local", [os.path.join(samba3srcdir, "script/tests/vfstest-catia/run.sh"), binpath("vfstest"), "$PREFIX", configuration])
+plantestsuite(
+ "samba.vfstest.full_audit_segfault",
+ "nt4_dc:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/full_audit_segfault/run.sh"),
+ binpath("vfstest"),
+ "$PREFIX",
+ configuration])
+
+plantestsuite("samba3.blackbox.smbclient_basic.NT1", "nt4_dc_schannel", [os.path.join(samba3srcdir, "script/tests/test_smbclient_basic.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, "-mNT1"])
+plantestsuite("samba3.blackbox.smbclient_basic.NT1", "nt4_dc_smb1", [os.path.join(samba3srcdir, "script/tests/test_smbclient_basic.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, "-mNT1"])
+plantestsuite("samba3.blackbox.smbclient_basic.SMB2_02", "nt4_dc_schannel", [os.path.join(samba3srcdir, "script/tests/test_smbclient_basic.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, "-mSMB2_02"])
+plantestsuite("samba3.blackbox.smbclient_basic.SMB2_10", "nt4_dc_schannel", [os.path.join(samba3srcdir, "script/tests/test_smbclient_basic.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, "-mSMB2_10"])
+plantestsuite("samba3.blackbox.smbclient_basic.SMB3_02", "nt4_dc_schannel", [os.path.join(samba3srcdir, "script/tests/test_smbclient_basic.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, "-mSMB3_02"])
+plantestsuite("samba3.blackbox.smbclient_basic.SMB3_11", "nt4_dc_schannel", [os.path.join(samba3srcdir, "script/tests/test_smbclient_basic.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, "-mSMB3_11"])
+
+plantestsuite("samba3.blackbox.smbclient_usernamemap", "ad_member_idmap_nss:local", [os.path.join(samba3srcdir, "script/tests/test_usernamemap.sh"), '$SERVER', smbclient3])
+
+plantestsuite("samba3.blackbox.smbclient_basic", "ad_member", [os.path.join(samba3srcdir, "script/tests/test_smbclient_basic.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration])
+for options in ["", "--option=clientntlmv2auth=no", "--option=clientusespnego=no", "--option=clientusespnego=no --option=clientntlmv2auth=no", "--option=clientntlmv2auth=no --option=clientlanmanauth=yes --max-protocol=LANMAN2", "--option=clientntlmv2auth=no --option=clientlanmanauth=yes --option=clientmaxprotocol=NT1"]:
+ if "NT1" in options or "LANMAN2" in options:
+ env = "nt4_dc_smb1_done"
+ else:
+ env = "nt4_dc"
+ plantestsuite("samba3.blackbox.smbclient_auth.plain.%s" % (options), env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, options])
+
+for env in ["nt4_dc", "nt4_member", "ad_member", "ad_dc", "s4member", "fl2000dc"]:
+ plantestsuite("samba3.blackbox.smbclient_machine_auth.plain", "%s:local" % env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_machine_auth.sh"), '$SERVER', smbclient3, configuration])
+ smb1_env = env
+ if smb1_env == "ad_dc" or smb1_env == "nt4_dc":
+ smb1_env = smb1_env + "_smb1_done"
+ plantestsuite("samba3.blackbox.smbclient_ntlm.plain NT1", smb1_env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_ntlm.sh"), '$SERVER', '$DC_USERNAME', '$DC_PASSWORD', "never", smbclient3, "NT1", configuration])
+ plantestsuite("samba3.blackbox.smbclient_ntlm.plain SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_ntlm.sh"), '$SERVER', '$DC_USERNAME', '$DC_PASSWORD', "never", smbclient3, "SMB3", configuration])
+
+
+plantestsuite("samba3.blackbox.smbclient_log_basename", "ad_dc", [os.path.join(samba3srcdir, "script/tests/test_smbclient_log_basename.sh"), '$SERVER', smbclient3, '$PREFIX', configuration])
+
+for options in ["--option=clientntlmv2auth=no", "--option=clientusespnego=no --option=clientntlmv2auth=no", "--option=clientusespnego=no --option=clientntlmv2auth=no -mNT1", ""]:
+ # don't attempt to run SMB1 tests in nt4_member or ad_member
+ # as these test envs don't support SMB1, use nt4_dc instead
+ environs = ["nt4_member", "ad_member"]
+ if "NT1" in options or "LANMAN2" in options:
+ environs = ["nt4_dc_smb1_done"]
+ for env in environs:
+ plantestsuite("samba3.blackbox.smbclient_auth.plain.%s" % (options), env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, options])
+ plantestsuite("samba3.blackbox.smbclient_auth.plain.%s.member_creds" % (options), env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '$SERVER/$USERNAME', '$PASSWORD', smbclient3, configuration, options])
+
+for env in ["nt4_member", "ad_member"]:
+ plantestsuite("samba3.blackbox.smbclient_auth.empty_domain.domain_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '/$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, options])
+ plantestsuite("samba3.blackbox.smbclient_auth.empty_domain.member_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '/$USERNAME', '$PASSWORD', smbclient3, configuration, options])
+ plantestsuite("samba3.blackbox.smbclient_auth.dot_domain.domain_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', './$DC_USERNAME', '$DC_PASSWORD', smbclient3, configuration, options])
+ plantestsuite("samba3.blackbox.smbclient_auth.dot_domain.member_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', './$USERNAME', '$PASSWORD', smbclient3, configuration, options])
+ plantestsuite("samba3.blackbox.smbclient_auth.upn.domain_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME@$REALM', '$DC_PASSWORD', smbclient3, configuration, options])
+ plantestsuite("samba3.blackbox.smbclient_auth.upn.member_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '$USERNAME@$SERVER', '$PASSWORD', smbclient3, configuration, options])
+
+env = "ad_dc_smb1"
+plantestsuite("samba3.blackbox.smbspool", env, [os.path.join(samba3srcdir, "script/tests/test_smbspool.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD', env])
+
+env = "ad_member_fips"
+plantestsuite("samba3.blackbox.krbsmbspool", env, [os.path.join(samba3srcdir, "script/tests/test_smbspool_krb.sh"), '$SERVER', 'bob', 'Secret007', '$REALM'])
+
+plantestsuite("samba3.blackbox.printing_var_exp", "nt4_dc", [os.path.join(samba3srcdir, "script/tests/test_printing_var_exp.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$DC_USERNAME', '$DC_PASSWORD'])
+
+for env in ["ad_member:local", "nt4_dc:local"]:
+ plantestsuite("samba3.blackbox.smbpasswd", env, [os.path.join(samba3srcdir, "script/tests/test_smbpasswd.sh"), '$SERVER', '$SERVER_IP', '$DC_USERNAME', '$DC_PASSWORD'])
+
+env = "nt4_dc"
+plantestsuite("samba3.blackbox.smbclient_auth.plain.ipv6", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IPV6', '$SERVER/$USERNAME', '$PASSWORD', smbclient3, configuration])
+
+for env in ["nt4_member", "ad_member"]:
+ plantestsuite("samba3.blackbox.net_cred_change", "%s:local" % env, [os.path.join(samba3srcdir, "script/tests/test_net_cred_change.sh"), configuration])
+
+plantestsuite("samba3.blackbox.net_cred_change_at", "ad_member_s3_join:local", [os.path.join(samba3srcdir, "script/tests/test_net_cred_change_at.sh"), configuration, '$DC_SERVER'])
+
+env = "ad_member"
+t = "--krb5auth=$DOMAIN/$DC_USERNAME%$DC_PASSWORD"
+plantestsuite("samba3.wbinfo_simple.%s" % t, "%s:local" % env, [os.path.join(srcdir(), "nsswitch/tests/test_wbinfo_simple.sh"), t])
+plantestsuite("samba3.wbinfo_name_lookup", env,
+ [os.path.join(srcdir(),
+ "nsswitch/tests/test_wbinfo_name_lookup.sh"),
+ '$DOMAIN', '$REALM', '$DC_USERNAME'])
+
+env = "ad_member"
+plantestsuite("samba3.wbinfo_user_info_cached", env,
+ [os.path.join(srcdir(),
+ "nsswitch/tests/test_wbinfo_user_info_cached.sh"),
+ '$DOMAIN', '$REALM', 'joe', 'Secret007', '"Samba Users"', env])
+plantestsuite("samba3.wbinfo_user_info_cached.trustdom", env,
+ [os.path.join(srcdir(),
+ "nsswitch/tests/test_wbinfo_user_info_cached.sh"),
+ '$TRUST_F_BOTH_DOMAIN', '$TRUST_F_BOTH_REALM', 'joe', 'Secret007', '"Samba Users"', env])
+
+env = "ad_member:local"
+plantestsuite("samba3.wbinfo_user_info", env,
+ [os.path.join(srcdir(),
+ "nsswitch/tests/test_wbinfo_user_info.sh"),
+ '$DOMAIN', '$REALM', '$DOMAIN', 'alice', 'alice', 'jane', 'jane.doe', env])
+
+plantestsuite("samba3.winbind_call_depth_trace", env,
+ [os.path.join(srcdir(),
+ "source3/script/tests/test_winbind_call_depth_trace.sh"),
+ smbcontrol, configuration, '$PREFIX', env])
+
+env = "fl2008r2dc:local"
+plantestsuite("samba3.wbinfo_user_info", env,
+ [os.path.join(srcdir(),
+ "nsswitch/tests/test_wbinfo_user_info.sh"),
+ '$TRUST_DOMAIN', '$TRUST_REALM', '$DOMAIN', 'alice', 'alice', 'jane', 'jane.doe', env])
+
+env = "nt4_member:local"
+plantestsuite("samba3.wbinfo_sids_to_xids", env,
+ [os.path.join(srcdir(),
+ "nsswitch/tests/test_wbinfo_sids_to_xids.sh")])
+plantestsuite(
+ "samba.wbinfo_lookuprids_cache",
+ env,
+ [os.path.join(samba3srcdir,
+ "script/tests/test_wbinfo_lookuprids_cache.sh")])
+
+env = "ad_member"
+t = "WBCLIENT-MULTI-PING"
+plantestsuite("samba3.smbtorture_s3.%s" % t, env, [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"), t, '//foo/bar', '""', '""', smbtorture3, ""])
+plantestsuite("samba3.substitutions", env, [os.path.join(samba3srcdir, "script/tests/test_substitutions.sh"), "$SERVER", "alice", "Secret007", "$PREFIX"])
+
+for env in ["maptoguest", "simpleserver"]:
+ plantestsuite("samba3.blackbox.smbclient_auth.plain.local_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', smbclient3, configuration + " --option=clientntlmv2auth=no --option=clientlanmanauth=yes"])
+
+env = "maptoguest"
+plantestsuite("samba3.blackbox.smbclient_auth.plain.bad_username", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_auth.sh"), '$SERVER', '$SERVER_IP', 'notmy$USERNAME', '$PASSWORD', smbclient3, configuration + " --option=clientntlmv2auth=no --option=clientlanmanauth=yes"])
+plantestsuite("samba3.blackbox.smbclient_ntlm.plain.NT1", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_ntlm.sh"), '$SERVER', '$USERNAME', '$PASSWORD', "baduser", smbclient3, "NT1", configuration])
+plantestsuite("samba3.blackbox.smbclient_ntlm.plain.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_ntlm.sh"), '$SERVER', '$USERNAME', '$PASSWORD', "baduser", smbclient3, "SMB3", configuration])
+
+# plain
+env = "nt4_dc_smb1_done"
+plantestsuite("samba3.blackbox.smbclient_s3.NT1.plain", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$DC_USERNAME', '$DC_PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "NT1"])
+env = "nt4_dc"
+plantestsuite("samba3.blackbox.smbclient_s3.SMB3.plain", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$DC_USERNAME', '$DC_PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "SMB3"])
+
+for env in ["nt4_member", "ad_member"]:
+ plantestsuite("samba3.blackbox.smbclient_s3.NT1.plain.member_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$SERVER', '$SERVER/$USERNAME', '$PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "NT1"])
+ plantestsuite("samba3.blackbox.smbclient_s3.SMB3.plain.member_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$SERVER', '$SERVER/$USERNAME', '$PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "SMB3"])
+
+env = "nt4_dc_smb1_done"
+plantestsuite("samba3.blackbox.smbclient_s3.NT1.sign", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$DC_USERNAME', '$DC_PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "NT1", "--client-protection=sign"])
+env = "nt4_dc"
+plantestsuite("samba3.blackbox.smbclient_s3.SMB3.sign", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$DC_USERNAME', '$DC_PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "SMB3", "--client-protection=sign"])
+
+for env in ["nt4_member", "ad_member"]:
+ plantestsuite("samba3.blackbox.smbclient_s3.NT1.sign.member_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$SERVER', '$SERVER/$USERNAME', '$PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "NT1", "--client-protection=sign"])
+ plantestsuite("samba3.blackbox.smbclient_s3.SMB3.sign.member_creds", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$SERVER', '$SERVER/$USERNAME', '$PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "SMB3", "--client-protection=sign"])
+
+env = "nt4_dc_smb1_done"
+# encrypted
+plantestsuite("samba3.blackbox.smbclient_s3.NT1.crypt", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "NT1", "--client-protection=encrypt"])
+env = "nt4_dc"
+plantestsuite("samba3.blackbox.smbclient_s3.SMB3.crypt", env, [os.path.join(samba3srcdir, "script/tests/test_smbclient_s3.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, wbinfo, net, configuration, "SMB3", "--client-protection=encrypt"])
+
+for env in ["fileserver"]:
+ plantestsuite("samba3.blackbox.preserve_case.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_preserve_case.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, "NT1"])
+ plantestsuite("samba3.blackbox.preserve_case.SMB2+", env, [os.path.join(samba3srcdir, "script/tests/test_preserve_case.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, '"SMB2 SMB3"'])
+ plantestsuite("samba3.blackbox.dfree_command.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_dfree_command.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, "NT1"])
+ plantestsuite("samba3.blackbox.dfree_command.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_dfree_command.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, "SMB3"])
+ plantestsuite("samba3.blackbox.dfree_quota.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_dfree_quota.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbclient3, smbcquotas, smbcacls, "NT1"])
+ plantestsuite("samba3.blackbox.dfree_quota.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_dfree_quota.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbclient3, smbcquotas, smbcacls, "SMB3"])
+ plantestsuite("samba3.blackbox.smbcquotas", env, [os.path.join(samba3srcdir, "script/tests/test_smbcquota.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbcquotas])
+ plantestsuite("samba3.blackbox.valid_users", env, [os.path.join(samba3srcdir, "script/tests/test_valid_users.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3])
+ plantestsuite("samba3.blackbox.force_create_mode", env, [os.path.join(samba3srcdir, "script/tests/test_force_create_mode.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$PREFIX', env, smbclient3])
+ plantestsuite("samba3.blackbox.dropbox", env, [os.path.join(samba3srcdir, "script/tests/test_dropbox.sh"), '$SERVER', '$DOMAIN', 'gooduser', '$PASSWORD', '$PREFIX', env, smbclient3])
+ plantestsuite("samba3.blackbox.offline", env, [os.path.join(samba3srcdir, "script/tests/test_offline.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/offline', smbclient3])
+ plantestsuite("samba3.blackbox.recycle", env, [os.path.join(samba3srcdir, "script/tests/test_recycle.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/recycle', '$PREFIX', smbclient3])
+ plantestsuite("samba3.blackbox.fakedircreatetimes", env, [os.path.join(samba3srcdir, "script/tests/test_fakedircreatetimes.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/fakedircreatetimes', '$PREFIX', smbclient3])
+ plantestsuite("samba3.blackbox.shadow_copy2.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_shadow_copy.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/shadow', smbclient3, '-m', 'NT1'])
+ plantestsuite("samba3.blackbox.shadow_copy2.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_shadow_copy.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/shadow', smbclient3, '-m', 'SMB3'])
+ plantestsuite("samba3.blackbox.shadow_copy_torture", env, [os.path.join(samba3srcdir, "script/tests/test_shadow_copy_torture.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/shadow', smbtorture4, smbclient3])
+ plantestsuite("samba3.blackbox.smbclient.forceuser_validusers", env, [os.path.join(samba3srcdir, "script/tests/test_forceuser_validusers.sh"), '$SERVER', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbclient3])
+ plantestsuite("samba3.blackbox.netshareenum", env, [os.path.join(samba3srcdir, "script/tests/test_shareenum.sh"), '$SERVER', '$USERNAME', '$PASSWORD', rpcclient])
+ plantestsuite("samba3.blackbox.acl_xattr.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_acl_xattr.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, '-mNT1'])
+ plantestsuite("samba3.blackbox.acl_xattr.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_acl_xattr.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, '-mSMB3'])
+ plantestsuite("samba3.blackbox.worm.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_worm.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/worm', '$PREFIX', smbclient3, '-mNT1'])
+ plantestsuite("samba3.blackbox.worm.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_worm.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/worm', '$PREFIX', smbclient3, '-mSMB3'])
+ plantestsuite("samba3.blackbox.smb2.not_casesensitive", env, [os.path.join(samba3srcdir, "script/tests/test_smb2_not_casesensitive.sh"), '//$SERVER/tmp', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbclient3])
+ plantestsuite("samba3.blackbox.inherit_owner.default.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_inherit_owner.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'tmp', '0', '0', '-m', 'NT1'])
+ plantestsuite("samba3.blackbox.inherit_owner.default.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_inherit_owner.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'tmp', '0', '0', '-m', 'SMB3'])
+ plantestsuite("samba3.blackbox.inherit_owner.full.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_inherit_owner.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'inherit_owner', '1', '1', '-m', 'NT1'])
+ plantestsuite("samba3.blackbox.inherit_owner.full.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_inherit_owner.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'inherit_owner', '1', '1', '-m', 'SMB3'])
+ plantestsuite("samba3.blackbox.inherit_owner.unix.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_inherit_owner.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'inherit_owner_u', '0', '1', '-m', 'NT1'])
+ plantestsuite("samba3.blackbox.inherit_owner.unix.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_inherit_owner.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'inherit_owner_u', '0', '1', '-m', 'SMB3'])
+ plantestsuite("samba3.blackbox.large_acl.NT1", env + "_smb1_done", [os.path.join(samba3srcdir, "script/tests/test_large_acl.sh"), '$SERVER', '$USERNAME', '$PASSWORD', smbclient3, smbcacls, '-m', 'NT1'])
+ plantestsuite("samba3.blackbox.large_acl.SMB3", env, [os.path.join(samba3srcdir, "script/tests/test_large_acl.sh"), '$SERVER', '$USERNAME', '$PASSWORD', smbclient3, smbcacls, '-m', 'SMB3'])
+ plantestsuite("samba3.blackbox.give_owner", env, [os.path.join(samba3srcdir, "script/tests/test_give_owner.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'tmp'])
+ plantestsuite("samba3.blackbox.delete_stream", env, [os.path.join(samba3srcdir, "script/tests/test_delete_stream.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3, smbcacls, net, 'acl_streams_xattr'])
+ plantestsuite("samba3.blackbox.homes", env, [os.path.join(samba3srcdir, "script/tests/test_homes.sh"), '$SERVER', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', '$PREFIX', smbclient3, configuration])
+ plantestsuite("samba3.blackbox.force_group_change", env,
+ [os.path.join(samba3srcdir, "script/tests/test_force_group_change.sh"),
+ '$SERVER', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbclient3, smbcontrol])
+ plantestsuite("samba3.blackbox.zero-data", env,
+ [os.path.join(samba3srcdir, "script/tests/test_zero_data.sh"),
+ '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH'])
+ plantestsuite("samba3.blackbox.timestamps", env,
+ [os.path.join(samba3srcdir, "script/tests/test_timestamps.sh"),
+ '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH', smbclient3])
+ plantestsuite("samba3.blackbox.volumeserialnumber", env,
+ [os.path.join(samba3srcdir, "script/tests/test_volume_serial_number.sh"),
+ '$SERVER_IP', '$USERNAME', '$PASSWORD', 'volumeserialnumber', smbclient3])
+ plantestsuite("samba3.blackbox.smb1_system_security", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_smb1_system_security.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', smbtorture3, net, 'tmp'])
+ plantestsuite("samba3.blackbox.sacl_get_set", env,
+ [os.path.join(samba3srcdir, "script/tests/test_sacl_set_get.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', smbtorture3, net, 'tmp'])
+ plantestsuite("samba3.blackbox.NT1.shadow_copy_torture", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_smb1_shadow_copy_torture.sh"),
+ '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/shadow', smbtorture4])
+ plantestsuite("samba3.blackbox.smbclient_iconv.SMB2", env,
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_iconv.sh"),
+ '$SERVER', '$SERVER_IP', 'bad_iconv', '$USERNAME', '$PASSWORD', smbclient3])
+ plantestsuite("samba3.blackbox.smbclient_iconv.NT1", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_iconv.sh"),
+ '$SERVER', '$SERVER_IP', 'bad_iconv', '$USERNAME', '$PASSWORD', smbclient3, '-mNT1'])
+ plantestsuite("samba3.blackbox.smbclient_iconv.CORE", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_iconv.sh"),
+ '$SERVER', '$SERVER_IP', 'bad_iconv', '$USERNAME', '$PASSWORD', smbclient3, '-mCORE'])
+ plantestsuite("samba3.blackbox.test_veto_rmdir", env,
+ [os.path.join(samba3srcdir, "script/tests/test_veto_rmdir.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/veto', smbclient3])
+ plantestsuite("samba3.blackbox.test_dangle_rmdir", env,
+ [os.path.join(samba3srcdir, "script/tests/test_delete_veto_files_only_rmdir.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/veto', smbclient3])
+ plantestsuite("samba3.blackbox.test_list_servers.NT1",
+ env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_list_servers.sh"),
+ '$SERVER',
+ '$SERVER_IP',
+ '$USERNAME',
+ '$PASSWORD',
+ smbclient3,
+ "-mNT1"])
+ plantestsuite("samba3.blackbox.test_list_servers.SMB2",
+ env,
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_list_servers.sh"),
+ '$SERVER',
+ '$SERVER_IP',
+ '$USERNAME',
+ '$PASSWORD',
+ smbclient3,
+ "-mSMB3"])
+
+ plantestsuite("samba3.blackbox.test_symlink_traversal.SMB2", env,
+ [os.path.join(samba3srcdir, "script/tests/test_symlink_traversal_smb2.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/local_symlinks',
+ '$PREFIX', smbclient3])
+
+ plantestsuite("samba3.blackbox.test_symlink_traversal.SMB1", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_symlink_traversal_smb1.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/local_symlinks',
+ '$PREFIX', smbclient3])
+
+ plantestsuite("samba3.blackbox.test_symlink_traversal.SMB1.posix", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_symlink_traversal_smb1_posix.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/local_symlinks',
+ '$PREFIX', smbclient3])
+
+ plantestsuite("samba3.blackbox.test_symlink_rename.SMB1.posix", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_symlink_rename_smb1_posix.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/local_symlinks',
+ '$PREFIX', smbclient3])
+
+ plantestsuite("samba3.blackbox.test_veto_files", env,
+ [os.path.join(samba3srcdir, "script/tests/test_veto_files.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/veto', smbclient3])
+
+ plantestsuite("samba3.blackbox.stream_dir_rename", env,
+ [os.path.join(samba3srcdir, "script/tests/test_stream_dir_rename.sh"),
+ '$SERVER', '$USERNAME', '$PASSWORD', '$PREFIX', smbclient3])
+
+ plantestsuite("samba3.blackbox.test_symlink_dosmode", env,
+ [os.path.join(samba3srcdir, "script/tests/test_symlink_dosmode.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', '$LOCAL_PATH/local_symlinks',
+ '$PREFIX', smbclient3])
+ #
+ # tar command tests
+ #
+
+ # Test smbclient/tarmode
+ plantestsuite("samba3.blackbox.smbclient_tarmode.NT1", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_tarmode.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD',
+ '$LOCAL_PATH/tarmode/smbclient_tar', '$PREFIX', smbclient3, configuration, "-mNT1"])
+ plantestsuite("samba3.blackbox.smbclient_tarmode.SMB3", env,
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_tarmode.sh"),
+ '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD',
+ '$LOCAL_PATH/tarmode/smbclient_tar', '$PREFIX', smbclient3, configuration, "-mSMB3"])
+
+ # Test suite for new smbclient/tar with libarchive (GSoC 13)
+ plantestsuite("samba3.blackbox.smbclient_tar.NT1", env + "_smb1_done",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_tarmode.pl"),
+ '-n', '$SERVER', '-i', '$SERVER_IP', '-s', 'tarmode2',
+ '-u', '$USERNAME', '-p', '$PASSWORD', '-l', '$LOCAL_PATH/tarmode2',
+ '-d', 'smbclient_tar.NT1', '-b', smbclient3,
+ '--subunit', '--', configuration, '-mNT1'])
+ plantestsuite("samba3.blackbox.smbclient_tar.SMB3", env,
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_tarmode.pl"),
+ '-n', '$SERVER', '-i', '$SERVER_IP', '-s', 'tarmode2',
+ '-u', '$USERNAME', '-p', '$PASSWORD', '-l', '$LOCAL_PATH/tarmode2',
+ '-d', 'smbclient_tar.SMB3', '-b', smbclient3,
+ '--subunit', '--', configuration, '-mSMB3'])
+ plantestsuite("samba3.blackbox.fifo", env,
+ [os.path.join(samba3srcdir, "script/tests/test_fifo.sh"),
+ '$SERVER', '$DOMAIN', 'gooduser', '$PASSWORD', '$PREFIX', env, smbclient3])
+ plantestsuite("samba3.blackbox.test_full_audit_success_badname", env,
+ [os.path.join(samba3srcdir, "script/tests/test_bad_auditnames.sh"),
+ '$SERVER', 'full_audit_success_bad_name', '$USERNAME', '$PASSWORD', smbclient3])
+ plantestsuite("samba3.blackbox.test_full_audit_fail_badname", env,
+ [os.path.join(samba3srcdir, "script/tests/test_bad_auditnames.sh"),
+ '$SERVER', 'full_audit_fail_bad_name', '$USERNAME', '$PASSWORD', smbclient3])
+ plantestsuite("samba3.blackbox.fruit.resource_stream", env,
+ [os.path.join(samba3srcdir, "script/tests/test_fruit_resource_stream.sh"),
+ '$SERVER', 'fruit_resource_stream', '$USERNAME', '$PASSWORD',
+ '$LOCAL_PATH/fruit_resource_stream', smbclient3])
+
+plantestsuite("samba3.blackbox.smbclient_old_dir", "fileserver_smb1",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_old_dirlisting.sh"),
+ timelimit, smbclient3])
+
+for env in ["fileserver:local"]:
+ plantestsuite("samba3.blackbox.net_usershare", env, [os.path.join(samba3srcdir, "script/tests/test_net_usershare.sh"), '$SERVER', '$SERVER_IP', '$USERNAME', '$PASSWORD', smbclient3])
+
+ plantestsuite("samba3.blackbox.smbstatus", env, [os.path.join(samba3srcdir, "script/tests/test_smbstatus.sh"), '$SERVER', '$SERVER_IP', '$DOMAIN', '$USERNAME', '$PASSWORD', '$USERID', '$LOCAL_PATH', '$PREFIX', smbclient3, smbstatus, configuration, "SMB3"])
+ plantestsuite("samba3.blackbox.net_registry_import", env, [os.path.join(samba3srcdir, "script/tests/test_net_registry_import.sh"), '$SERVER', '$LOCAL_PATH', '$USERNAME', '$PASSWORD'])
+
+env = 'ad_member'
+plantestsuite("samba3.blackbox.smbget",
+ env,
+ [
+ os.path.join(samba3srcdir, "script/tests/test_smbget.sh"),
+ '$SERVER',
+ '$SERVER_IP',
+ '$DOMAIN',
+ '$REALM',
+ 'smbget_user',
+ '$PASSWORD',
+ '$DOMAIN_USER',
+ '$DOMAIN_USER_PASSWORD',
+ '$LOCAL_PATH/smbget',
+ smbget
+ ])
+
+plantestsuite("samba3.blackbox.server_addresses",
+ "simpleserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_server_addresses.sh")])
+
+# TODO encrypted against member, with member creds, and with DC creds
+plantestsuite("samba3.blackbox.net.misc NT1", "ad_dc_smb1_done:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_misc.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration, "NT1"])
+plantestsuite("samba3.blackbox.net.misc SMB3", "ad_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_misc.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration, "SMB3"])
+plantestsuite("samba3.blackbox.net.local.registry", "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_registry.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration])
+plantestsuite("samba3.blackbox.net.registry.check", "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_registry_check.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration, dbwrap_tool])
+plantestsuite("samba3.blackbox.net.rpc.registry", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_net_registry.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration, 'rpc'])
+
+plantestsuite("samba3.blackbox.net.local.registry.roundtrip", "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_registry_roundtrip.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration])
+plantestsuite("samba3.blackbox.net.rpc.registry.roundtrip", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_net_registry_roundtrip.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration, 'rpc'])
+
+plantestsuite("samba3.blackbox.net.local.conf", "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_conf.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration])
+plantestsuite("samba3.blackbox.net.rpc.conf", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_net_conf.sh"),
+ scriptdir, "$SMB_CONF_PATH", net, configuration, 'rpc'])
+
+
+plantestsuite("samba3.blackbox.testparm", "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_testparm_s3.sh"),
+ "$LOCAL_PATH"])
+
+plantestsuite(
+ "samba3.pthreadpool", "none",
+ [os.path.join(samba3srcdir, "script/tests/test_pthreadpool.sh")])
+
+if with_pthreadpool and have_ldwrap:
+ plantestsuite("samba3.pthreadpool_cmocka", "none",
+ [os.path.join(bindir(), "pthreadpooltest_cmocka")])
+
+if with_pthreadpool:
+ plantestsuite("samba3.libwbclient_threads",
+ "nt4_member",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_libwbclient_threads.sh"),
+ "$DOMAIN", "$DC_USERNAME"])
+ plantestsuite("b15464_testcase", "none",
+ [os.path.join(bbdir, "b15464-testcase.sh"),
+ binpath("b15464-testcase"),
+ binpath("plugins/libnss_winbind.so.2")])
+
+plantestsuite("samba3.test_nfs4_acl", "none",
+ [os.path.join(bindir(), "test_nfs4_acls"),
+ "$SMB_CONF_PATH"])
+
+plantestsuite("samba3.test_vfs_full_audit", "none",
+ [os.path.join(bindir(), "test_vfs_full_audit"),
+ "$SMB_CONF_PATH"])
+
+plantestsuite("samba3.test_vfs_posixacl", "none",
+ [os.path.join(bindir(), "test_vfs_posixacl"),
+ "$SMB_CONF_PATH"])
+
+if is_module_enabled("vfs_gpfs"):
+ plantestsuite("samba3.test_vfs_gpfs", "none",
+ [os.path.join(bindir(), "test_vfs_gpfs")])
+
+plantestsuite(
+ "samba3.resolvconf", "none",
+ [os.path.join(samba3srcdir, "script/tests/test_resolvconf.sh")])
+
+plantestsuite("samba3.tevent_glib_glue", "none",
+ [os.path.join(samba3srcdir, "script/tests/test_tevent_glib_glue.sh")])
+
+plantestsuite("samba3.async_req", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_async_req.sh")])
+
+# smbtorture4 tests
+
+base = ["base.attr", "base.charset", "base.chkpath", "base.createx_access", "base.defer_open", "base.delaywrite", "base.delete",
+ "base.deny1", "base.deny2", "base.deny3", "base.denydos", "base.dir1", "base.dir2",
+ "base.disconnect", "base.fdpass", "base.lock",
+ "base.mangle", "base.negnowait", "base.ntdeny1",
+ "base.ntdeny2", "base.open", "base.openattr", "base.properties", "base.rename", "base.rw1",
+ "base.secleak", "base.tcon", "base.tcondev", "base.trans2", "base.unlink", "base.vuid",
+ "base.xcopy", "base.samba3error"]
+
+raw = ["raw.acls", "raw.chkpath", "raw.close", "raw.composite", "raw.context", "raw.eas",
+ "raw.ioctl", "raw.lock", "raw.mkdir", "raw.mux", "raw.notify", "raw.open", "raw.oplock",
+ "raw.qfileinfo", "raw.qfsinfo", "raw.read", "raw.rename", "raw.search", "raw.seek",
+ "raw.sfileinfo.base", "raw.sfileinfo.bug", "raw.streams", "raw.unlink", "raw.write",
+ "raw.samba3hide", "raw.samba3badpath", "raw.sfileinfo.rename", "raw.session",
+ "raw.samba3caseinsensitive", "raw.samba3posixtimedlock",
+ "raw.samba3rootdirfid", "raw.samba3rootdirfid2", "raw.sfileinfo.end-of-file",
+ "raw.bench-oplock", "raw.bench-lock", "raw.bench-open", "raw.bench-tcon",
+ "raw.samba3checkfsp", "raw.samba3closeerr", "raw.samba3oplocklogoff", "raw.samba3badnameblob"]
+
+smb2 = smbtorture4_testsuites("smb2.")
+
+rpc = ["rpc.authcontext",
+ "rpc.samba3.bind",
+ "rpc.samba3.srvsvc",
+ "rpc.samba3.sharesec",
+ "rpc.samba3.spoolss",
+ "rpc.samba3.wkssvc",
+ "rpc.samba3.winreg",
+ "rpc.samba3.getaliasmembership-0",
+ "rpc.samba3.netlogon",
+ "rpc.samba3.sessionkey",
+ "rpc.samba3.getusername",
+ "rpc.samba3.smb1-pipe-name",
+ "rpc.samba3.smb2-pipe-name",
+ "rpc.samba3.smb-reauth1",
+ "rpc.samba3.smb-reauth2",
+ "rpc.samba3.lsa_over_netlogon",
+ "rpc.samba3.pipes_supported_interfaces",
+ "rpc.mgmt",
+ "rpc.svcctl",
+ "rpc.ntsvcs",
+ "rpc.winreg",
+ "rpc.eventlog",
+ "rpc.spoolss.printserver",
+ "rpc.spoolss.win",
+ "rpc.spoolss.notify",
+ "rpc.spoolss.printer",
+ "rpc.spoolss.driver",
+ "rpc.lsa",
+ "rpc.lsa-getuser",
+ "rpc.lsa.lookupsids",
+ "rpc.lsa.lookupnames",
+ "rpc.lsa.privileges",
+ "rpc.lsa.secrets",
+ "rpc.mdssvc",
+ "rpc.samr",
+ "rpc.samr.users",
+ "rpc.samr.users.privileges",
+ "rpc.samr.passwords.default",
+ "rpc.samr.passwords.pwdlastset",
+ "rpc.samr.passwords.lockout",
+ "rpc.samr.passwords.badpwdcount",
+ "rpc.samr.large-dc",
+ "rpc.samr.machine.auth",
+ "rpc.samr.priv",
+ "rpc.samr.passwords.validate",
+ "rpc.samr.handletype",
+ "rpc.netlogon.admin",
+ "rpc.netlogon.zerologon",
+ "rpc.schannel",
+ "rpc.schannel2",
+ "rpc.bench-schannel1",
+ "rpc.schannel_anon_setpw",
+ "rpc.join",
+ "rpc.bind",
+ "rpc.initshutdown",
+ "rpc.wkssvc",
+ "rpc.srvsvc"]
+
+local = ["local.nss"]
+
+idmap = ["idmap.rfc2307", "idmap.alloc", "idmap.rid", "idmap.ad", "idmap.nss"]
+
+rap = ["rap.basic", "rap.rpc", "rap.printing", "rap.sam"]
+
+unix = ["unix.info2", "unix.whoami"]
+
+nbt = ["nbt.dgram"]
+
+vfs = [
+ "vfs.fruit",
+ "vfs.acl_xattr",
+ "vfs.fruit_netatalk",
+ "vfs.fruit_file_id",
+ "vfs.fruit_timemachine",
+ "vfs.fruit_conversion",
+ "vfs.unfruit",
+]
+
+tests = base + raw + smb2 + rpc + unix + local + rap + nbt + idmap + vfs
+
+for t in tests:
+ if t == "base.delaywrite" or t == "base.deny1" or t == "base.deny2":
+ plansmbtorture4testsuite(t, "fileserver_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD --maximum-runtime=900')
+ elif t == "base.createx_access":
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD -k yes --maximum-runtime=900')
+ elif t == "rap.sam":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=doscharset=ISO-8859-1')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=doscharset=ISO-8859-1')
+ elif t == "winbind.pac":
+ plansmbtorture4testsuite(t, "ad_member:local", '//$SERVER/tmp --realm=$REALM --machine-pass --option=torture:addc=$DC_SERVER', description="machine account")
+ elif t == "unix.whoami":
+ plansmbtorture4testsuite(t, "nt4_member:local", '//$SERVER/tmp --machine-pass', description="machine account")
+ plansmbtorture4testsuite(t, "ad_dc_smb1:local", '//$SERVER/tmp --machine-pass', description="machine account")
+ plansmbtorture4testsuite(t, "ad_member:local", '//$SERVER/tmp --machine-pass --option=torture:addc=$DC_SERVER', description="machine account")
+ plansmbtorture4testsuite(t, "ad_dc_smb1:local", '//$SERVER/tmp --machine-pass --option=torture:addc=$DC_SERVER', description="machine account")
+ for env in ["nt4_dc_smb1", "nt4_member"]:
+ plansmbtorture4testsuite(t, env, '//$SERVER/tmp -U$DC_USERNAME%$DC_PASSWORD')
+ plansmbtorture4testsuite(t, env, '//$SERVER/tmpguest -U%', description='anonymous connection')
+ for env in ["ad_dc_smb1", "ad_member"]:
+ plansmbtorture4testsuite(t, env, '//$SERVER/tmp -U$DC_USERNAME@$REALM%$DC_PASSWORD --option=torture:addc=$DC_SERVER')
+ plansmbtorture4testsuite(t, env, '//$SERVER/tmp -k yes -U$DC_USERNAME@$REALM%$DC_PASSWORD --option=torture:addc=$DC_SERVER', description='kerberos connection')
+ plansmbtorture4testsuite(t, env, '//$SERVER/tmpguest -U% --option=torture:addc=$DC_SERVER', description='anonymous connection')
+ plansmbtorture4testsuite(t, env, '//$SERVER/tmp -k no -U$DC_USERNAME@$REALM%$DC_PASSWORD', description='ntlm user@realm')
+ elif t == "raw.samba3posixtimedlock":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmpguest -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc_smb1/share')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/brl_delay_inject1 -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc_smb1/share',
+ description="brl_delay_inject1")
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/brl_delay_inject2 -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc_smb1/share',
+ description="brl_delay_inject2")
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER_IP/tmpguest -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/ad_dc_smb1/share')
+ elif t == "smb2.samba3misc":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmpguest -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/brl_delay_inject1 -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share',
+ description="brl_delay_inject1")
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/brl_delay_inject2 -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share',
+ description="brl_delay_inject2")
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER_IP/tmpguest -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/ad_dc/share')
+ elif t == "raw.chkpath":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmpcase -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER_IP/tmpcase -U$USERNAME%$PASSWORD')
+ elif t == "raw.samba3hide" or t == "raw.samba3checkfsp" or t == "raw.samba3closeerr":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "fileserver_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "raw.session":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD', 'plain')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmpenc -U$USERNAME%$PASSWORD', 'enc')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -k no -U$USERNAME%$PASSWORD', 'ntlm')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -k yes -U$USERNAME%$PASSWORD', 'krb5')
+ elif t == "smb2.session":
+ alice_creds = "--option='torture:user2name=alice' --option='torture:user2password=Secret007'"
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD', 'plain')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmpenc -U$USERNAME%$PASSWORD', 'enc')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -k no -U$USERNAME%$PASSWORD ' + alice_creds, 'ntlm')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -k yes -U$USERNAME%$PASSWORD ' + alice_creds, 'krb5')
+ # Certain tests fail when run against ad_member with MIT kerberos because the private krb5.conf overrides the provisioned lib/krb5.conf,
+ # ad_member_idmap_rid sets "create krb5.conf = no"
+ plansmbtorture4testsuite(t, "ad_member_idmap_rid", '//$SERVER/tmp -k yes -U$DC_USERNAME@$REALM%$DC_PASSWORD', 'krb5')
+ elif t == "smb2.session-require-signing":
+ plansmbtorture4testsuite(t, "ad_member_idmap_rid", '//$SERVER_IP/tmp -U$DC_USERNAME@$REALM%$DC_PASSWORD')
+ elif t == "rpc.lsa":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD', 'over ncacn_np ')
+ plansmbtorture4testsuite(t, "nt4_dc", 'ncacn_ip_tcp:$SERVER_IP -U$USERNAME%$PASSWORD', 'over ncacn_ip_tcp ')
+ elif t.startswith("rpc.lsa."):
+ # This avoids the rpc.lsa.* tests running under ncacn_ip_tcp:
+ # (there is rpc.lsa.secrets fails due to OpenPolicy2 for example)
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "rpc.mdssvc":
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.durable-open" or t == "smb2.durable-v2-open" or t == "smb2.replay" or t == "smb2.durable-v2-delay":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/durable -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER_IP/durable -U$USERNAME%$PASSWORD')
+ elif t == "base.rw1":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/valid-users-tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/write-list-tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "idmap.rfc2307":
+ plantestsuite(t, "ad_member_rfc2307",
+ [os.path.join(samba3srcdir,
+ "../nsswitch/tests/test_idmap_rfc2307.sh"),
+ '$DOMAIN',
+ 'Administrator', '2000000',
+ 'Guest', '2000001',
+ '"Domain Users"', '2000002',
+ 'DnsAdmins', '2000003',
+ '2000005', '35',
+ 'ou=idmap,dc=samba,dc=example,dc=com',
+ '$DC_SERVER', '$DC_USERNAME', '$DC_PASSWORD'])
+ elif t == "idmap.alloc":
+ plantestsuite(t, "ad_member_rfc2307", [os.path.join(samba3srcdir, "../nsswitch/tests/test_idmap_nss.sh"), '$DOMAIN'])
+ elif t == "idmap.nss":
+ plantestsuite(t, "ad_member_idmap_nss:local", [os.path.join(samba3srcdir, "../nsswitch/tests/test_idmap_nss_use_upn.sh")])
+ elif t == "idmap.rid":
+ plantestsuite(t, "ad_member_idmap_rid", [os.path.join(samba3srcdir, "../nsswitch/tests/test_idmap_rid.sh"), '$DOMAIN', '2000000'])
+ plantestsuite(t,
+ "admem_idmap_autorid",
+ [os.path.join(samba3srcdir,
+ "../nsswitch/tests/test_idmap_rid.sh"),
+ '$DOMAIN',
+ '2000000'])
+ elif t == "idmap.ad":
+ plantestsuite(t, "ad_member_idmap_ad", [os.path.join(samba3srcdir, "../nsswitch/tests/test_idmap_ad.sh"), '$DOMAIN', '$DC_SERVER', '$DC_PASSWORD', '$TRUST_DOMAIN', '$TRUST_SERVER', '$TRUST_PASSWORD'])
+ elif t == "raw.acls":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/nfs4acl_simple_40 -U$USERNAME%$PASSWORD', description='nfs4acl_xattr-simple-40')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/nfs4acl_special_40 -U$USERNAME%$PASSWORD', description='nfs4acl_xattr-special-40')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/nfs4acl_simple_41 -U$USERNAME%$PASSWORD', description='nfs4acl_xattr-simple-41')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/nfs4acl_xdr_40 -U$USERNAME%$PASSWORD', description='nfs4acl_xattr-xdr-40')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/nfs4acl_xdr_41 -U$USERNAME%$PASSWORD', description='nfs4acl_xattr-xdr-41')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/nfs4acl_nfs_40 -U$USERNAME%$PASSWORD', description='nfs4acl_xattr-nfs-40')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/nfs4acl_nfs_41 -U$USERNAME%$PASSWORD', description='nfs4acl_xattr-nfs-41')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER_IP/tmpcase -U$USERNAME%$PASSWORD')
+ elif t == "smb2.ioctl":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/fs_specific -U$USERNAME%$PASSWORD', 'fs_specific')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.ioctl-on-stream":
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.lock":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/aio -U$USERNAME%$PASSWORD', 'aio')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "raw.lock" or t == "base.lock":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "raw.read":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/aio -U$USERNAME%$PASSWORD', 'aio')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "raw.search":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+# test the dirsort module.
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmpsort -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "vfs.fruit":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share --option=torture:share2=vfs_wo_fruit', 'metadata_netatalk')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_metadata_stream -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share --option=torture:share2=vfs_wo_fruit', 'metadata_stream')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_stream_depot -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share --option=torture:share2=vfs_wo_fruit_stream_depot', 'streams_depot')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_delete_empty_adfiles -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share --option=torture:share2=vfs_wo_fruit', 'fruit_delete_empty_adfiles')
+ elif t == "vfs.fruit_netatalk":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_xattr -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share')
+ elif t == "vfs.fruit_timemachine":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_timemachine -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share')
+ elif t == "vfs.fruit_file_id":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_zero_fileid -U$USERNAME%$PASSWORD')
+ elif t == "vfs.fruit_conversion":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=torture:share2=vfs_fruit_wipe_intentionally_left_blank_rfork --option=torture:delete_empty_adfiles=false', 'wipe_intentionally_left_blank_rfork')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=torture:share2=vfs_fruit_delete_empty_adfiles --option=torture:delete_empty_adfiles=true', 'delete_empty_adfiles')
+ elif t == "vfs.unfruit":
+ creds = '-U$USERNAME%$PASSWORD'
+ share2 = '--option=torture:share2=tmp'
+ netopt = '--option=torture:net=%s' % net
+ shareopt = '--option=torture:sharename'
+
+ plansmbtorture4testsuite(t, "nt4_dc:local", '//$SERVER_IP/vfs_fruit %s %s %s %s=%s' % (creds, share2, netopt, shareopt, 'vfs_fruit'), 'metadata_netatalk')
+ plansmbtorture4testsuite(t, "nt4_dc:local", '//$SERVER_IP/vfs_fruit_metadata_stream %s %s %s %s=%s' % (creds, share2, netopt, shareopt, 'vfs_fruit_metadata_stream'), 'metadata_stream')
+ plansmbtorture4testsuite(t, "nt4_dc:local", '//$SERVER_IP/vfs_fruit_stream_depot %s %s %s %s=%s' % (creds, share2, netopt, shareopt, 'vfs_fruit_stream_depot'), 'streams_depot')
+ elif t == "rpc.schannel_anon_setpw":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$%', description="anonymous password set")
+ plansmbtorture4testsuite(t, "nt4_dc_schannel", '//$SERVER_IP/tmp -U$%', description="anonymous password set (schannel enforced server-side)")
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$%', description="anonymous password set")
+ elif t == "local.nss":
+ for env in ["nt4_dc:local", "ad_member:local", "nt4_member:local", "ad_dc:local"]:
+ plansmbtorture4testsuite(t,
+ env,
+ '//$SERVER/tmp -U$USERNAME%$PASSWORD',
+ environ = {
+ 'ENVNAME': env,
+ })
+ elif t == "smb2.change_notify_disabled":
+ plansmbtorture4testsuite(t, "simpleserver", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.notify" or t == "raw.notify" or t == "smb2.oplock" or t == "raw.oplock":
+ tmp_env = "nt4_dc"
+ if t == "raw.notify" or t == "raw.oplock":
+ tmp_env = "nt4_dc_smb1"
+ # These tests are a little slower so don't duplicate them with ad_dc
+ plansmbtorture4testsuite(t, tmp_env, '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --client-protection=sign')
+ elif t == "smb2.dosmode":
+ plansmbtorture4testsuite(t, "simpleserver", '//$SERVER/dosmode -U$USERNAME%$PASSWORD')
+ elif t == "smb2.kernel-oplocks":
+ if have_linux_kernel_oplocks:
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER/kernel_oplocks -U$USERNAME%$PASSWORD --option=torture:localdir=$SELFTEST_PREFIX/nt4_dc/share')
+ elif t == "smb2.notify-inotify":
+ if have_inotify:
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "vfs.acl_xattr":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.compound_find":
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER/compound_find -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.compound":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/aio -U$USERNAME%$PASSWORD', 'aio')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.compound_async":
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/aio_delay_inject -U$USERNAME%$PASSWORD')
+ elif t == "smb2.ea":
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER/ea_acl_xattr --option=torture:acl_xattr_name=hackme -U$USERNAME%$PASSWORD')
+ elif t == "rpc.samba3.netlogon" or t == "rpc.samba3.sessionkey":
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=torture:wksname=samba3rpctest')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD --option=torture:wksname=samba3rpctest')
+ elif t == "smb2.streams":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/streams_xattr -U$USERNAME%$PASSWORD', 'streams_xattr')
+ elif t == "smb2.aio_delay":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/aio_delay_inject -U$USERNAME%$PASSWORD')
+ elif t == "smb2.delete-on-close-perms":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/delete_readonly -U$USERNAME%$PASSWORD --option=torture:delete_readonly=true')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.fileid":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/vfs_fruit_xattr -U$USERNAME%$PASSWORD')
+ elif t == "smb2.acls_non_canonical":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/acls_non_canonical -U$USERNAME%$PASSWORD')
+ elif t == "smb2.async_dosmode":
+ plansmbtorture4testsuite("smb2.async_dosmode",
+ "simpleserver",
+ "//$SERVER_IP/async_dosmode_shadow_copy2 -U$USERNAME%$PASSWORD")
+ elif t == "smb2.rename":
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "smb2.twrp":
+ # This is being driven by samba3.blackbox.shadow_copy_torture
+ pass
+ elif t == "smb2.create_no_streams":
+ plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/nfs4acl_simple_40 -U$USERNAME%$PASSWORD')
+ elif t == "rpc.wkssvc":
+ plansmbtorture4testsuite(t, "ad_member", '//$SERVER/tmp -U$DC_USERNAME%$DC_PASSWORD')
+ elif t == "rpc.srvsvc":
+ plansmbtorture4testsuite(t, "ad_member", '//$SERVER/tmp -U$DC_USERNAME%$DC_PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$DC_USERNAME%$DC_PASSWORD')
+ elif t == "rpc.samba3.lsa_over_netlogon":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "rpc.samba3.pipes_supported_interfaces":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ elif t == "rpc.spoolss.notify":
+ plansmbtorture4testsuite(t, "ad_member", '//$SERVER_IP/tmp -U$DC_USERNAME%$DC_PASSWORD')
+ elif (t in base and t != "base.charset") \
+ or (t in rap and t != "rap.printing") \
+ or (t in unix) \
+ or (t in ["rpc.authcontext",
+ "rpc.join",
+ "rpc.samba3.bind",
+ "rpc.samba3.getusername",
+ "rpc.samba3.sharesec",
+ "rpc.samba3.smb1-pipe-name",
+ "rpc.samba3.smb-reauth1",
+ "rpc.samba3.smb-reauth2",
+ "rpc.samba3.spoolss",
+ "rpc.samba3.wkssvc",]) \
+ or (t in ["raw.close",
+ "raw.composite",
+ "raw.eas",
+ "raw.mkdir",
+ "raw.open",
+ "raw.rename",
+ "raw.samba3badnameblob",
+ "raw.samba3badpath",
+ "raw.samba3caseinsensitive",
+ "raw.samba3oplocklogoff",
+ "raw.samba3posixtimedlock",
+ "raw.samba3rootdirfid",
+ "raw.samba3rootdirfid2",
+ "raw.seek",
+ "raw.sfileinfo.bug",
+ "raw.sfileinfo.end-of-file",
+ "raw.sfileinfo.rename",
+ "raw.streams",
+ "raw.unlink",
+ "raw.write",]) :
+ plansmbtorture4testsuite(t, "nt4_dc_smb1", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc_smb1", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t in ["base.mangle", "base.tcon", "raw.mkdir"]:
+ plansmbtorture4testsuite(t, "nt4_dc_smb1_done", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc_smb1_done", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+ elif t == "rpc.samr.passwords.validate":
+ plansmbtorture4testsuite(t, "nt4_dc", 'ncacn_ip_tcp:$SERVER_IP[seal] -U$USERNAME%$PASSWORD', 'over ncacn_ip_tcp ')
+ elif t == "rpc.samr.users.privileges":
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD --option=torture:nt4_dc=true')
+ elif t == "rpc.samr" or t.startswith("rpc.samr."):
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ else:
+ plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
+ plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+
+plantestsuite(
+ "idmap_ad.ticket_expiry",
+ "ad_member_idmap_ad:local",
+ [os.path.join(samba3srcdir, "../nsswitch/tests/test_ticket_expiry.sh"),
+ '$DOMAIN'])
+
+plansmbtorture4testsuite(
+ "notifyd",
+ "fileserver:local",
+ '//foo/bar -U%')
+
+plansmbtorture4testsuite(
+ "smb2.streams",
+ "simpleserver",
+ '//$SERVER/external_streams_depot -U$USERNAME%$PASSWORD')
+
+vfs_io_uring_tests = {
+ "smb2.connect",
+ "smb2.credits",
+ "smb2.rw",
+ "smb2.bench",
+ "smb2.ioctl",
+}
+for t in vfs_io_uring_tests:
+ plansmbtorture4testsuite(t, "fileserver",
+ '//$SERVER_IP/io_uring -U$USERNAME%$PASSWORD',
+ "vfs_io_uring")
+
+test = 'rpc.lsa.lookupsids'
+auth_options = ["", "ntlm", "spnego", "spnego,ntlm", "spnego,smb1", "spnego,smb2"]
+signseal_options = ["", ",connect", ",packet", ",sign", ",seal"]
+endianness_options = ["", ",bigendian"]
+for s in signseal_options:
+ for e in endianness_options:
+ for a in auth_options:
+ binding_string = "ncacn_np:$SERVER[%s%s%s]" % (a, s, e)
+ options = binding_string + " -U$USERNAME%$PASSWORD"
+ if "smb1" in a:
+ plansmbtorture4testsuite(test, "nt4_dc_smb1_done", options, 'over ncacn_np with [%s%s%s] ' % (a, s, e))
+ else:
+ plansmbtorture4testsuite(test, "nt4_dc", options, 'over ncacn_np with [%s%s%s] ' % (a, s, e))
+ plantestsuite(
+ f'samba3.blackbox.rpcclient over ncacn_np with [{a}{s}{e}] ',
+ "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclient.sh"),
+ "none",
+ options + " -c getusername",
+ configuration])
+ plantestsuite(
+ f'samba3.blackbox.rpcclient over ncalrpc with [{a}{s}{e}] ',
+ "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclient.sh"),
+ "none",
+ f'ncalrpc:[{a}{s}{e}] -c epmmap',
+ configuration])
+ if s != ",connect":
+ plantestsuite(
+ f'samba3.blackbox.rpcclient over ncacn_ip_tcp with [{a}{s}{e}] ',
+ "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclient.sh"),
+ "none",
+ f'ncacn_ip_tcp:"$SERVER_IP"[{a}{s}{e}] -c epmmap -U"$USERNAME"%"$PASSWORD"',
+ configuration])
+
+ # We should try more combinations in future, but this is all
+ # the pre-calculated credentials cache supports at the moment
+ #
+ # As the ktest env requires SMB3_00 we need to use "smb2" until
+ # dcerpc client code in smbtorture support autonegotiation
+ # of any smb dialect.
+ e = ""
+ a = "smb2"
+ binding_string = "ncacn_np:$SERVER[%s%s%s]" % (a, s, e)
+ options = binding_string + " --use-krb5-ccache=$PREFIX/ktest/krb5_ccache-2"
+ plansmbtorture4testsuite(test, "ktest", options, 'krb5 with old ccache ncacn_np with [%s%s%s] ' % (a, s, e))
+
+ options = binding_string + " --use-krb5-ccache=$PREFIX/ktest/krb5_ccache-3"
+ plansmbtorture4testsuite(test, "ktest", options, 'krb5 ncacn_np with [%s%s%s] ' % (a, s, e))
+
+ auth_options2 = ["krb5", "spnego,krb5"]
+ for a in auth_options2:
+ binding_string = "ncacn_np:$SERVER[%s%s%s]" % (a, s, e)
+
+ plantestsuite(
+ f'samba3.blackbox.rpcclient krb5 ncacn_np with [{a}{s}{e}] ',
+ "ktest:local",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclient.sh"),
+ "$PREFIX/ktest/krb5_ccache-3",
+ binding_string,
+ "--use-krb5-ccache=$PREFIX/ktest/krb5_ccache-3 -c getusername",
+ configuration])
+
+plantestsuite("samba3.blackbox.rpcclient_samlogon", "ad_member:local", [os.path.join(samba3srcdir, "script/tests/test_rpcclient_samlogon.sh"),
+ "$DC_USERNAME", "$DC_PASSWORD", "ncacn_np:$DC_SERVER", configuration])
+plantestsuite("samba3.blackbox.sharesec", "simpleserver:local",
+ [os.path.join(samba3srcdir, "script/tests/test_sharesec.sh"),
+ configuration, os.path.join(bindir(), "sharesec"),
+ os.path.join(bindir(), "net"), "tmp"])
+
+plantestsuite("samba3.blackbox.close-denied-share", "simpleserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_close_denied_share.sh"),
+ configuration,
+ os.path.join(bindir(), "sharesec"),
+ os.path.join(bindir(), "smbclient"),
+ os.path.join(bindir(), "smbcontrol"),
+ '$SERVER_IP',
+ "tmp"])
+
+plantestsuite("samba3.blackbox.force-close-share", "simpleserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_force_close_share.sh"),
+ configuration,
+ os.path.join(bindir(), "smbclient"),
+ os.path.join(bindir(), "smbcontrol"),
+ '$SERVER_IP',
+ "aio_delay_inject",
+ '$PREFIX/force-close-share'])
+
+plantestsuite("samba3.blackbox.open-eintr", "simpleserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_open_eintr.sh"),
+ configuration,
+ os.path.join(bindir(), "smbclient"),
+ os.path.join(bindir(), "smbcontrol"),
+ '$SERVER_IP',
+ "error_inject"])
+
+plantestsuite("samba3.blackbox.chdir-cache", "simpleserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_chdir_cache.sh"),
+ configuration,
+ os.path.join(bindir(), "smbclient"),
+ os.path.join(bindir(), "smbcontrol"),
+ '$SERVER_IP',
+ "error_inject",
+ '$PREFIX',
+ 'simpleserver'])
+
+plantestsuite("samba3.blackbox.rofs_error", "simpleserver",
+ [os.path.join(samba3srcdir, "script/tests/test_rofs.sh"),
+ configuration,
+ os.path.join(bindir(), "smbclient"),
+ '$SERVER_IP',
+ "error_inject"])
+
+plantestsuite("samba3.blackbox.zero_readsize",
+ "simpleserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_zero_readsize.sh"),
+ configuration,
+ os.path.join(bindir(), "smbclient"),
+ os.path.join(bindir(), "smbcontrol"),
+ '$SERVER_IP',
+ "tmp",
+ "$PREFIX",
+ "-mSMB2"])
+
+plantestsuite("samba3.blackbox.netfileenum", "simpleserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_netfileenum.sh"),
+ os.path.join(bindir(), "smbclient"),
+ os.path.join(bindir(), "rpcclient"),
+ os.path.join(bindir(), "net"),
+ '$SERVER_IP',
+ 'tmp'])
+
+plantestsuite("samba3.blackbox.netshareenum_username", "fileserver",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_user_in_sharelist.sh"),
+ os.path.join(bindir(), "rpcclient"),
+ '$SERVER_IP'])
+
+plantestsuite("samba3.blackbox.net_tdb", "simpleserver:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_tdb.sh"),
+ smbclient3, '$SERVER', 'tmp', '$USERNAME', '$PASSWORD',
+ configuration, '$LOCAL_PATH', '$LOCK_DIR'])
+
+plantestsuite("samba3.blackbox.aio-outstanding", "simpleserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_aio_outstanding.sh"),
+ configuration,
+ os.path.join(bindir(), "smbclient"),
+ '$SERVER_IP',
+ "aio_delay_inject"])
+
+plantestsuite("samba3.blackbox.deadtime", "simpleserver:local",
+ [os.path.join(samba3srcdir, "script/tests/test_deadtime.sh"),
+ '$SERVER_IP'])
+
+plantestsuite("samba3.blackbox.smbd_error", "simpleserver:local",
+ [os.path.join(samba3srcdir, "script/tests/test_smbd_error.sh")])
+
+plantestsuite("samba3.blackbox.smbd_no_krb5", "ad_member:local",
+ [os.path.join(samba3srcdir, "script/tests/test_smbd_no_krb5.sh"),
+ smbclient3, '$SERVER', "$DC_USERNAME", "$DC_PASSWORD", "$PREFIX"])
+
+plantestsuite("samba3.blackbox.winbind_ignore_domain", "ad_member_idmap_ad:local",
+ [os.path.join(samba3srcdir, "script/tests/test_winbind_ignore_domains.sh")])
+
+plantestsuite("samba3.blackbox.durable_v2_delay", "simpleserver:local",
+ [os.path.join(samba3srcdir, "script/tests/test_durable_handle_reconnect.sh")])
+
+plantestsuite("samba3.blackbox.net_cache_samlogon", "ad_member:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_cache_samlogon.sh"),
+ '$SERVER', 'tmp', '$DC_USERNAME', '$DC_PASSWORD'])
+
+plantestsuite("samba3.blackbox.net_rpc_share_allowedusers", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_net_rpc_share_allowedusers.sh"),
+ "$SERVER", "$USERNAME", "$PASSWORD", "$PREFIX/net_rpc_share_allowedusers",
+ configuration])
+
+plantestsuite("samba3.blackbox.net_dom_join_fail_dc", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_net_dom_join_fail_dc.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER", "$PREFIX/net_dom_join_fail_dc",
+ configuration])
+plantestsuite("samba3.blackbox.net_rpc_join", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_net_rpc_join.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER", "$PREFIX/net_rpc_join",
+ configuration])
+plantestsuite("samba3.blackbox.net_rpc_oldjoin", "nt4_dc:local",
+ [os.path.join(samba3srcdir, "script/tests/test_net_rpc_oldjoin.sh"),
+ "$SERVER", "$PREFIX/net_rpc_oldjoin",
+ "$SMB_CONF_PATH"])
+plantestsuite("samba3.blackbox.net_rpc_join_creds", "nt4_dc",
+ [os.path.join(samba3srcdir, "script/tests/test_net_rpc_join_creds.sh"),
+ "$DOMAIN", "$USERNAME", "$PASSWORD", "$SERVER", "$PREFIX/net_rpc_join_creds",
+ configuration])
+
+plantestsuite("samba3.blackbox.rpcclient_srvsvc", "simpleserver",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclientsrvsvc.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER",
+ os.path.join(bindir(), "rpcclient"), "tmp"])
+
+plantestsuite("samba3.blackbox.rpcclient_lookup", "simpleserver",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclient_lookup.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER",
+ os.path.join(bindir(), "rpcclient")])
+
+plantestsuite("samba3.blackbox.rpcclient_dfs", "fileserver:local",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclient_dfs.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER",
+ os.path.join(bindir(), "rpcclient")])
+
+plantestsuite("samba3.blackbox.rpcclient.pw-nt-hash", "simpleserver",
+ [os.path.join(samba3srcdir, "script/tests/test_rpcclient_pw_nt_hash.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER",
+ os.path.join(bindir(), "rpcclient")])
+
+plantestsuite("samba3.blackbox.smbclient.encryption_off", "simpleserver",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_encryption_off.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER",
+ smbclient3])
+
+plantestsuite("samba3.blackbox.smbXsrv_client_dead_rec", "fileserver:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbXsrv_client_dead_rec.sh"),
+ configuration,
+ '$SERVER_IP',
+ "tmp"])
+
+if have_cluster_support:
+ plantestsuite("samba3.blackbox.smbXsrv_client_cross_node", "clusteredmember:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbXsrv_client_cross_node.sh"),
+ configuration,
+ '$CTDB_SERVER_NAME_NODE0', '$CTDB_SERVER_NAME_NODE1',
+ "tmp"])
+ plantestsuite("samba3.blackbox.smbXsrv_client_ctdb_registered_ips", "clusteredmember:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbXsrv_client_ctdb_registered_ips.sh"),
+ configuration,
+ '$CTDB_IFACE_IP',
+ "tmp"])
+ plantestsuite("samba3.blackbox.registry_share", "clusteredmember",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_registry_share.sh"),
+ "$SERVER", '$DC_USERNAME', "$DC_PASSWORD"])
+
+env = 'fileserver'
+plantestsuite("samba3.blackbox.virus_scanner", "%s:local" % (env),
+ [os.path.join(samba3srcdir,
+ "script/tests/test_virus_scanner.sh"),
+ '$SERVER_IP',
+ "virusfilter",
+ '$LOCAL_PATH',
+ smbclient3])
+
+for env in ['fileserver', 'simpleserver']:
+ plantestsuite("samba3.blackbox.smbclient.encryption", env,
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_encryption.sh"),
+ "$USERNAME", "$PASSWORD", "$SERVER",
+ smbclient3, env])
+
+plantestsuite("samba3.blackbox.smbclient.kerberos", 'ad_dc',
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbclient_kerberos.sh"),
+ "alice",
+ "$REALM",
+ "Secret007",
+ "$SERVER",
+ smbclient3,
+ env])
+for env in ['ad_dc_fips', 'ad_member_fips']:
+ plantestsuite("samba3.blackbox.smbclient.kerberos", env,
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbclient_kerberos.sh"),
+ "alice",
+ "$REALM",
+ "Secret007",
+ "$SERVER",
+ smbclient3,
+ env],
+ environ={'GNUTLS_FORCE_FIPS_MODE': '1',
+ 'OPENSSL_FORCE_FIPS_MODE': '1'})
+
+plantestsuite("samba3.blackbox.rpcclient_netsessenum", "ad_member",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_rpcclient_netsessenum.sh"),
+ "$DOMAIN", "$DC_USERNAME", "$DC_PASSWORD", "$SERVER",
+ os.path.join(bindir(), "rpcclient"), smbtorture3, "tmp"])
+
+# The ktest environment uses:
+# server min protocol = SMB3_00
+# client max protocol = SMB3
+options_list = ["", "--client-protection=encrypt"]
+for options in options_list:
+ plantestsuite("samba3.blackbox.smbclient_krb5 old ccache %s" % options, "ktest:local",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_krb5.sh"),
+ "$PREFIX/ktest/krb5_ccache-2",
+ smbclient3, "$SERVER", options, configuration])
+
+ plantestsuite("samba3.blackbox.smbclient_krb5 new ccache %s" % options, "ktest:local",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_krb5.sh"),
+ "$PREFIX/ktest/krb5_ccache-3",
+ smbclient3, "$SERVER", options, configuration])
+
+ plantestsuite("samba3.blackbox.smbclient_large_file %s krb5" % options, "ktest:local",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_large_file.sh"),
+ "$PREFIX/ktest/krb5_ccache-3",
+ smbclient3, "$SERVER", "$PREFIX", options, "--use-krb5-ccache=$PREFIX/ktest/krb5_ccache-3 " + configuration])
+
+options_list = ["-mNT1", "-mNT1 --client-protection=encrypt", "-mSMB3", "-mSMB3 --client-protection=encrypt"]
+for options in options_list:
+ env = "nt4_dc"
+ if "NT1" in options:
+ env = "nt4_dc_smb1_done"
+ plantestsuite("samba3.blackbox.smbclient_large_file %s NTLM" % options, "%s:local" % env,
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_large_file.sh"),
+ "none",
+ smbclient3, "$SERVER", "$PREFIX", options, "-U$USERNAME%$PASSWORD " + configuration])
+
+for alias in ["foo", "bar"]:
+ plantestsuite("samba3.blackbox.smbclient_netbios_aliases [%s]" % alias, "ad_member:local",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_netbios_aliases.sh"),
+ smbclient3, alias, "$DC_USERNAME", "$DC_PASSWORD", "$PREFIX",
+ configuration])
+
+for e in endianness_options:
+ for a in auth_options:
+ for s in signseal_options:
+ binding_string = "ncacn_ip_tcp:$SERVER_IP[%s%s%s]" % (a, s, e)
+ options = binding_string + " -U$USERNAME%$PASSWORD"
+ plansmbtorture4testsuite(test, "nt4_dc", options, 'over ncacn_ip_tcp with [%s%s%s] ' % (a, s, e))
+
+plansmbtorture4testsuite('rpc.epmapper', 'nt4_dc:local', 'ncalrpc: -U$USERNAME%$PASSWORD', 'over ncalrpc')
+plansmbtorture4testsuite('rpc.fsrvp', 'nt4_dc:local', 'ncacn_np:$SERVER_IP -U$USERNAME%$PASSWORD', 'over ncacn_np')
+
+for env in ["ad_member_idmap_rid:local", "maptoguest:local"]:
+ plantestsuite("samba3.blackbox.guest", env,
+ [os.path.join(samba3srcdir, "script/tests/test_guest_auth.sh"),
+ '$SERVER', smbclient3, smbcontrol, net, configuration])
+
+plantestsuite("samba3.blackbox.smbclient-mget",
+ "fileserver",
+ [os.path.join(samba3srcdir, "script/tests/test_smbclient_mget.sh"),
+ smbclient3,
+ "$SERVER",
+ "tmp",
+ "$USERNAME",
+ "$PASSWORD",
+ "valid_users"])
+
+plantestsuite("samba3.blackbox.smbclient-bug15435",
+ "fileserver",
+ [os.path.join(samba3srcdir, "script/tests/test_bug15435_widelink_dfs.sh"),
+ "$SERVER",
+ "$SERVER_IP",
+ "$USERNAME",
+ "$PASSWORD",
+ smbclient3,
+ configuration])
+
+
+if have_cluster_support:
+ t = "readdir-timestamp"
+ plantestsuite(
+ "samba3.smbtorture_s3.plain.%s" % t,
+ "clusteredmember",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_smbtorture_s3.sh"),
+ t,
+ '//foo/bar',
+ '$DOMAIN\\\\$DC_USERNAME',
+ '$DC_PASSWORD',
+ smbtorture3,
+ "",
+ "-b $PREFIX/clusteredmember/unclists/tmp.txt -N 5 -o 10"])
+
+ plantestsuite(
+ "samba3.net_machine_account",
+ "clusteredmember",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_net_machine_account.sh"),
+ "bin/net",
+ "$SERVERCONFFILE",
+ "$SERVER_IP"])
+
+plantestsuite(
+ "samba3.net_lookup_ldap",
+ "ad_dc:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_net_lookup.sh"),
+ '$DC_SERVER',
+ '$DC_USERNAME',
+ '$DC_PASSWORD',
+ "bin/net",
+ "bin/samba-tool",
+ '$DNSNAME'])
+
+plantestsuite("samba3.blackbox.force-user-unlink",
+ "maptoguest:local",
+ [os.path.join(samba3srcdir,
+ "script/tests/test_force_user_unlink.sh")])
+
+plansmbtorture4testsuite(
+ "vfs.fruit_validate_afpinfo", "fileserver",
+ '//$SERVER_IP/vfs_fruit -U$USERNAME%$PASSWORD --option=torture:validate_afpinfo=yes')
+plansmbtorture4testsuite(
+ "vfs.fruit_validate_afpinfo", "fileserver",
+ '//$SERVER_IP/vfs_fruit_zero_fileid -U$USERNAME%$PASSWORD --option=torture:validate_afpinfo=no')
+
+plantestsuite("samba3.blackbox.nt4_trusts",
+ "fl2008r2dc",
+ [os.path.join(samba3srcdir, "script/tests/test_nt4_trust.sh")])
+
+plantestsuite("samba3.blackbox.list_nt4_trusts",
+ "ad_member_idmap_ad",
+ [os.path.join(samba3srcdir, "script/tests/test_list_nt4_trust.sh")])
+
+def planclusteredmembertestsuite(tname, prefix):
+ '''Define a clustered test for the clusteredmember environment'''
+
+ tshare = 'tmp'
+
+ autharg = '-U${DOMAIN}/${DC_USERNAME}%${DC_PASSWORD}'
+ namearg = 'clustered.%s' % tname
+ modnamearg = 'samba3.%s' % namearg
+ extraargs = ''
+
+ prefix = os.path.join(prefix, 'clusteredmember')
+ unclist = os.path.join(prefix, 'unclists/%s.txt' % tshare)
+
+ unclistarg = '--unclist=%s' % unclist
+ sharearg = '//$SERVER_IP/%s' % tshare
+
+ return selftesthelpers.plansmbtorture4testsuite(
+ tname,
+ 'clusteredmember',
+ [extraargs, unclistarg, sharearg, autharg, tname],
+ target='samba3',
+ modname=modnamearg)
+
+
+if have_cluster_support:
+ CLUSTERED_TESTS = [ 'smb2.deny.deny2' ]
+
+ for test in CLUSTERED_TESTS:
+ planclusteredmembertestsuite(test, "$PREFIX")
+
+ CLUSTERED_LOCAL_TESTS = [
+ "ctdbd-conn1",
+ "local-dbwrap-ctdb1"
+ ]
+
+ for t in CLUSTERED_LOCAL_TESTS:
+ plantestsuite(
+ "samba3.%s" % t,
+ "clusteredmember:local",
+ [os.path.join(samba3srcdir, "script/tests/test_smbtorture_s3.sh"),
+ t,
+ '//foo/bar',
+ '""',
+ '""',
+ smbtorture3,
+ "-N 1000 -o 2000"])
+
+planpythontestsuite("fileserver_smb1", "samba.tests.smb3unix")
+planpythontestsuite("fileserver", "samba.tests.reparsepoints")
+planpythontestsuite("fileserver_smb1", "samba.tests.smb2symlink")
+planpythontestsuite("fileserver_smb1", "samba.tests.smb1posix")
diff --git a/source3/services/services.h b/source3/services/services.h
new file mode 100644
index 0000000..f65eed1
--- /dev/null
+++ b/source3/services/services.h
@@ -0,0 +1,54 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB parameters and setup
+ Copyright (C) Andrew Tridgell 1992-1997,
+ Copyright (C) Gerald (Jerry) Carter 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SERVICES_H /* _SERVICES_H */
+#define _SERVICES_H
+
+#include "../librpc/gen_ndr/svcctl.h"
+
+/* where we assume the location of the service control scripts */
+#define SVCCTL_SCRIPT_DIR "svcctl"
+
+/*
+ * dispatch table of functions to handle the =ServiceControl API
+ */
+
+typedef struct {
+ /* functions for enumerating subkeys and values */
+ WERROR (*stop_service)( const char *service, struct SERVICE_STATUS *status );
+ WERROR (*start_service) ( const char *service );
+ WERROR (*service_status)( const char *service, struct SERVICE_STATUS *status );
+} SERVICE_CONTROL_OPS;
+
+/* structure to store the service handle information */
+
+typedef struct _ServiceInfo {
+ uint8_t type;
+ char *name;
+ uint32_t access_granted;
+ SERVICE_CONTROL_OPS *ops;
+} SERVICE_INFO;
+
+#define SVC_HANDLE_IS_SCM 0x0000001
+#define SVC_HANDLE_IS_SERVICE 0x0000002
+#define SVC_HANDLE_IS_DBLOCK 0x0000003
+
+#endif /* _SERICES_H */
+
diff --git a/source3/services/svc_netlogon.c b/source3/services/svc_netlogon.c
new file mode 100644
index 0000000..582cb56
--- /dev/null
+++ b/source3/services/svc_netlogon.c
@@ -0,0 +1,75 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Service Control API Implementation
+ * Copyright (C) Gerald Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "services/services.h"
+
+/* Implementation for internal netlogon service */
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR netlogon_status( const char *service, struct SERVICE_STATUS *service_status )
+{
+ ZERO_STRUCTP( service_status );
+
+ service_status->type = SERVICE_TYPE_WIN32_SHARE_PROCESS;
+ service_status->controls_accepted = SVCCTL_ACCEPT_NONE;
+
+ if ( lp_servicenumber("NETLOGON") != -1 ) {
+ service_status->state = SVCCTL_RUNNING;
+ service_status->win32_exit_code = WERR_SERVICE_NEVER_STARTED;
+ }
+ else
+ service_status->state = SVCCTL_STOPPED;
+
+ return WERR_OK;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR netlogon_stop( const char *service, struct SERVICE_STATUS *service_status )
+{
+ netlogon_status( service, service_status );
+
+ return WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR netlogon_start( const char *service )
+{
+ if ( lp_servicenumber("NETLOGON") == -1 )
+ return WERR_SERVICE_DISABLED;
+
+ return WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+/* struct for svcctl control to manipulate netlogon service */
+
+SERVICE_CONTROL_OPS netlogon_svc_ops = {
+ netlogon_stop,
+ netlogon_start,
+ netlogon_status
+};
diff --git a/source3/services/svc_rcinit.c b/source3/services/svc_rcinit.c
new file mode 100644
index 0000000..7a8ad65
--- /dev/null
+++ b/source3/services/svc_rcinit.c
@@ -0,0 +1,150 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Service Control API Implementation
+ * Copyright (C) Gerald Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "services/services.h"
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR rcinit_stop( const char *service, struct SERVICE_STATUS *status )
+{
+ int ret = -1;
+
+ /*
+ * Disabled due to security concerns and unknown use in the
+ * field -- vl@samba.org
+ */
+#if 0
+ char *command = NULL;
+ int fd;
+
+ if (asprintf(&command, "%s/%s/%s stop",
+ get_dyn_MODULESDIR(), SVCCTL_SCRIPT_DIR, service) < 0) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* we've already performed the access check when the service was opened */
+
+ become_root();
+ ret = smbrun(command, &fd, NULL);
+ unbecome_root();
+
+ DEBUGADD(5, ("rcinit_start: [%s] returned [%d]\n", command, ret));
+ close(fd);
+
+ SAFE_FREE(command);
+
+ ZERO_STRUCTP( status );
+
+ status->type = SERVICE_TYPE_WIN32_SHARE_PROCESS;
+ status->state = (ret == 0 ) ? SVCCTL_STOPPED : SVCCTL_RUNNING;
+ status->controls_accepted = SVCCTL_ACCEPT_STOP |
+ SVCCTL_ACCEPT_SHUTDOWN;
+#endif
+ return ( ret == 0 ) ? WERR_OK : WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR rcinit_start( const char *service )
+{
+ int ret = -1;
+ /*
+ * Disabled due to security concerns and unknown use in the
+ * field -- vl@samba.org
+ */
+#if 0
+ char *command = NULL;
+ int fd;
+
+ if (asprintf(&command, "%s/%s/%s start",
+ get_dyn_MODULESDIR(), SVCCTL_SCRIPT_DIR, service) < 0) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* we've already performed the access check when the service was opened */
+
+ become_root();
+ ret = smbrun(command, &fd, NULL);
+ unbecome_root();
+
+ DEBUGADD(5, ("rcinit_start: [%s] returned [%d]\n", command, ret));
+ close(fd);
+
+ SAFE_FREE(command);
+#endif
+ return ( ret == 0 ) ? WERR_OK : WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR rcinit_status( const char *service, struct SERVICE_STATUS *status )
+{
+ /*
+ * Disabled due to security concerns and unknown use in the
+ * field -- vl@samba.org
+ */
+#if 0
+ char *command = NULL;
+ int ret, fd;
+
+ if (asprintf(&command, "%s/%s/%s status",
+ get_dyn_MODULESDIR(), SVCCTL_SCRIPT_DIR, service) < 0) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* we've already performed the access check when the service was opened */
+ /* assume as return code of 0 means that the service is ok. Anything else
+ is STOPPED */
+
+ become_root();
+ ret = smbrun(command, &fd, NULL);
+ unbecome_root();
+
+ DEBUGADD(5, ("rcinit_start: [%s] returned [%d]\n", command, ret));
+ close(fd);
+
+ SAFE_FREE(command);
+
+ ZERO_STRUCTP( status );
+
+ status->type = SERVICE_TYPE_WIN32_SHARE_PROCESS;
+ status->state = (ret == 0 ) ? SVCCTL_RUNNING : SVCCTL_STOPPED;
+ status->controls_accepted = SVCCTL_ACCEPT_STOP |
+ SVCCTL_ACCEPT_SHUTDOWN;
+
+ return WERR_OK;
+#else
+ return WERR_ACCESS_DENIED;
+#endif
+}
+
+/*********************************************************************
+*********************************************************************/
+
+/* struct for svcctl control to manipulate rcinit service */
+
+SERVICE_CONTROL_OPS rcinit_svc_ops = {
+ rcinit_stop,
+ rcinit_start,
+ rcinit_status
+};
diff --git a/source3/services/svc_spoolss.c b/source3/services/svc_spoolss.c
new file mode 100644
index 0000000..e827013
--- /dev/null
+++ b/source3/services/svc_spoolss.c
@@ -0,0 +1,87 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Service Control API Implementation
+ * Copyright (C) Gerald Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "services/services.h"
+
+/* Implementation for internal spoolss service */
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR spoolss_stop( const char *service, struct SERVICE_STATUS *service_status )
+{
+ ZERO_STRUCTP( service_status );
+
+ lp_set_spoolss_state( SVCCTL_STOPPED );
+
+ service_status->type = SERVICE_TYPE_INTERACTIVE_PROCESS |
+ SERVICE_TYPE_WIN32_OWN_PROCESS;
+ service_status->state = SVCCTL_STOPPED;
+ service_status->controls_accepted = SVCCTL_ACCEPT_STOP;
+
+ DEBUG(6,("spoolss_stop: spooler stopped (not really)\n"));
+
+ return WERR_OK;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR spoolss_start( const char *service )
+{
+ /* see if the smb.conf will support this anyways */
+
+ if ( lp__disable_spoolss() )
+ return WERR_ACCESS_DENIED;
+
+ if (lp_get_spoolss_state() == SVCCTL_RUNNING) {
+ return WERR_SERVICE_ALREADY_RUNNING;
+ }
+
+ lp_set_spoolss_state( SVCCTL_RUNNING );
+
+ return WERR_OK;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR spoolss_status( const char *service, struct SERVICE_STATUS *service_status )
+{
+ ZERO_STRUCTP( service_status );
+
+ service_status->type = SERVICE_TYPE_INTERACTIVE_PROCESS |
+ SERVICE_TYPE_WIN32_OWN_PROCESS;
+ service_status->state = lp_get_spoolss_state();
+ service_status->controls_accepted = SVCCTL_ACCEPT_STOP;
+
+ return WERR_OK;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+/* struct for svcctl control to manipulate spoolss service */
+
+SERVICE_CONTROL_OPS spoolss_svc_ops = {
+ spoolss_stop,
+ spoolss_start,
+ spoolss_status
+};
diff --git a/source3/services/svc_winreg.c b/source3/services/svc_winreg.c
new file mode 100644
index 0000000..04c137f
--- /dev/null
+++ b/source3/services/svc_winreg.c
@@ -0,0 +1,64 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Service Control API Implementation
+ * Copyright (C) Gerald Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "services/services.h"
+
+/* Implementation for internal winreg service */
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR winreg_stop( const char *service, struct SERVICE_STATUS *service_status )
+{
+ return WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR winreg_start( const char *service )
+{
+ return WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR winreg_status( const char *service, struct SERVICE_STATUS *service_status )
+{
+ ZERO_STRUCTP( service_status );
+
+ service_status->type = SERVICE_TYPE_WIN32_SHARE_PROCESS;
+ service_status->controls_accepted = SVCCTL_ACCEPT_NONE;
+ service_status->state = SVCCTL_RUNNING;
+
+ return WERR_OK;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+/* struct for svcctl control to manipulate winreg service */
+
+SERVICE_CONTROL_OPS winreg_svc_ops = {
+ winreg_stop,
+ winreg_start,
+ winreg_status
+};
diff --git a/source3/services/svc_winreg_glue.c b/source3/services/svc_winreg_glue.c
new file mode 100644
index 0000000..50b9897
--- /dev/null
+++ b/source3/services/svc_winreg_glue.c
@@ -0,0 +1,370 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SVC winreg glue
+ *
+ * Copyright (c) 2005 Marcin Krzysztof Porwit
+ * Copyright (c) 2005 Gerald (Jerry) Carter
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "services/services.h"
+#include "services/svc_winreg_glue.h"
+#include "rpc_client/cli_winreg_int.h"
+#include "rpc_client/cli_winreg.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "../libcli/security/security.h"
+
+#define TOP_LEVEL_SERVICES_KEY "SYSTEM\\CurrentControlSet\\Services"
+
+struct security_descriptor* svcctl_gen_service_sd(TALLOC_CTX *mem_ctx)
+{
+ struct security_descriptor *sd = NULL;
+ struct security_acl *theacl = NULL;
+ struct security_ace ace[4];
+ size_t sd_size;
+ size_t i = 0;
+
+ /* Basic access for everyone */
+ init_sec_ace(&ace[i++], &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SERVICE_READ_ACCESS, 0);
+
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Power_Users,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SERVICE_EXECUTE_ACCESS, 0);
+
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Server_Operators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SERVICE_ALL_ACCESS, 0);
+ init_sec_ace(&ace[i++], &global_sid_Builtin_Administrators,
+ SEC_ACE_TYPE_ACCESS_ALLOWED, SERVICE_ALL_ACCESS, 0);
+
+ /* Create the security descriptor */
+ theacl = make_sec_acl(mem_ctx,
+ NT4_ACL_REVISION,
+ i,
+ ace);
+ if (theacl == NULL) {
+ return NULL;
+ }
+
+ sd = make_sec_desc(mem_ctx,
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE,
+ NULL,
+ NULL,
+ NULL,
+ theacl,
+ &sd_size);
+ if (sd == NULL) {
+ return NULL;
+ }
+
+ return sd;
+}
+
+WERROR svcctl_get_secdesc(struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **psd)
+{
+ struct dcerpc_binding_handle *h = NULL;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ struct security_descriptor *sd = NULL;
+ char *key = NULL;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+
+ key = talloc_asprintf(mem_ctx,
+ "%s\\%s\\Security",
+ TOP_LEVEL_SERVICES_KEY, name);
+ if (key == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ status = dcerpc_winreg_int_hklm_openkey(mem_ctx,
+ session_info,
+ msg_ctx,
+ &h,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("svcctl_set_secdesc: Could not open %s - %s\n",
+ key, nt_errstr(status)));
+ return WERR_INTERNAL_ERROR;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("svcctl_set_secdesc: Could not open %s - %s\n",
+ key, win_errstr(result)));
+ return result;
+ }
+
+ status = dcerpc_winreg_query_sd(mem_ctx,
+ h,
+ &key_hnd,
+ "Security",
+ &sd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("svcctl_get_secdesc: error getting value 'Security': "
+ "%s\n", nt_errstr(status)));
+ return WERR_INTERNAL_ERROR;
+ }
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)) {
+ goto fallback_to_default_sd;
+ } else if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("svcctl_get_secdesc: error getting value 'Security': "
+ "%s\n", win_errstr(result)));
+ return result;
+ }
+
+ goto done;
+
+fallback_to_default_sd:
+ DEBUG(6, ("svcctl_get_secdesc: constructing default secdesc for "
+ "service [%s]\n", name));
+ sd = svcctl_gen_service_sd(mem_ctx);
+ if (sd == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+done:
+ *psd = sd;
+ return WERR_OK;
+}
+
+bool svcctl_set_secdesc(struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name,
+ struct security_descriptor *sd)
+{
+ struct dcerpc_binding_handle *h = NULL;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd;
+ struct policy_handle key_hnd = { 0, };
+ char *key = NULL;
+ bool ok = false;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ key = talloc_asprintf(tmp_ctx, "%s\\%s", TOP_LEVEL_SERVICES_KEY, name);
+ if (key == NULL) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_int_hklm_openkey(tmp_ctx,
+ session_info,
+ msg_ctx,
+ &h,
+ key,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("svcctl_set_secdesc: Could not open %s - %s\n",
+ key, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(0, ("svcctl_set_secdesc: Could not open %s - %s\n",
+ key, win_errstr(result)));
+ goto done;
+ }
+
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result);
+ }
+
+ {
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+ struct winreg_String wkey = { 0, };
+ struct winreg_String wkeyclass;
+
+ wkey.name = talloc_asprintf(tmp_ctx, "%s\\Security", key);
+ if (wkey.name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(h,
+ tmp_ctx,
+ &hive_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ access_mask,
+ NULL,
+ &key_hnd,
+ &action,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("svcctl_set_secdesc: Could not create key %s: %s\n",
+ wkey.name, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("svcctl_set_secdesc: Could not create key %s: %s\n",
+ wkey.name, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_set_sd(tmp_ctx,
+ h,
+ &key_hnd,
+ "Security",
+ sd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ ok = true;
+
+done:
+ if (is_valid_policy_hnd(&key_hnd)) {
+ dcerpc_winreg_CloseKey(h, tmp_ctx, &key_hnd, &result);
+ }
+
+ talloc_free(tmp_ctx);
+ return ok;
+}
+
+const char *svcctl_get_string_value(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *key_name,
+ const char *value_name)
+{
+ struct dcerpc_binding_handle *h = NULL;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED;
+ struct policy_handle hive_hnd, key_hnd;
+ const char *data = NULL;
+ char *path = NULL;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+ WERROR result = WERR_OK;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NULL;
+ }
+
+ path = talloc_asprintf(tmp_ctx, "%s\\%s",
+ TOP_LEVEL_SERVICES_KEY, key_name);
+ if (path == NULL) {
+ goto done;
+ }
+
+ status = dcerpc_winreg_int_hklm_openkey(tmp_ctx,
+ session_info,
+ msg_ctx,
+ &h,
+ path,
+ false,
+ access_mask,
+ &hive_hnd,
+ &key_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("svcctl_get_string_value: Could not open %s - %s\n",
+ path, nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(2, ("svcctl_get_string_value: Could not open %s - %s\n",
+ path, win_errstr(result)));
+ goto done;
+ }
+
+ status = dcerpc_winreg_query_sz(mem_ctx,
+ h,
+ &key_hnd,
+ value_name,
+ &data,
+ &result);
+
+done:
+ talloc_free(tmp_ctx);
+ return data;
+}
+
+/********************************************************************
+********************************************************************/
+
+const char *svcctl_lookup_dispname(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name)
+{
+ const char *display_name = NULL;
+
+ display_name = svcctl_get_string_value(mem_ctx,
+ msg_ctx,
+ session_info,
+ name,
+ "DisplayName");
+
+ if (display_name == NULL) {
+ display_name = talloc_strdup(mem_ctx, name);
+ }
+
+ return display_name;
+}
+
+/********************************************************************
+********************************************************************/
+
+const char *svcctl_lookup_description(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name)
+{
+ const char *description = NULL;
+
+ description = svcctl_get_string_value(mem_ctx,
+ msg_ctx,
+ session_info,
+ name,
+ "Description");
+
+ if (description == NULL) {
+ description = talloc_strdup(mem_ctx, "Unix Service");
+ }
+
+ return description;
+}
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/services/svc_winreg_glue.h b/source3/services/svc_winreg_glue.h
new file mode 100644
index 0000000..e013f8d
--- /dev/null
+++ b/source3/services/svc_winreg_glue.h
@@ -0,0 +1,60 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * SVC winreg glue
+ *
+ * Copyright (c) 2005 Marcin Krzysztof Porwit
+ * Copyright (c) 2005 Gerald (Jerry) Carter
+ * Copyright (c) 2011 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SVC_WINREG_GLUE_H
+#define SVC_WINREG_GLUE_H
+
+struct auth_session_info;
+
+struct security_descriptor* svcctl_gen_service_sd(TALLOC_CTX *mem_ctx);
+
+WERROR svcctl_get_secdesc(struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **result);
+
+bool svcctl_set_secdesc(struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name,
+ struct security_descriptor *sd);
+
+const char *svcctl_get_string_value(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *key_name,
+ const char *value_name);
+
+const char *svcctl_lookup_dispname(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name);
+
+const char *svcctl_lookup_description(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ const struct auth_session_info *session_info,
+ const char *name);
+
+#endif /* SVC_WINREG_GLUE_H */
+
+/* vim: set ts=8 sw=8 noet cindent syntax=c.doxygen: */
diff --git a/source3/services/svc_wins.c b/source3/services/svc_wins.c
new file mode 100644
index 0000000..c8d72bc
--- /dev/null
+++ b/source3/services/svc_wins.c
@@ -0,0 +1,72 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Service Control API Implementation
+ * Copyright (C) Gerald Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "services/services.h"
+
+/* Implementation for internal wins service */
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR wins_status( const char *service, struct SERVICE_STATUS *service_status )
+{
+ ZERO_STRUCTP( service_status );
+
+ service_status->type = SERVICE_TYPE_WIN32_OWN_PROCESS;
+ service_status->controls_accepted = SVCCTL_ACCEPT_NONE;
+
+ if ( lp_we_are_a_wins_server() )
+ service_status->state = SVCCTL_RUNNING;
+ else {
+ service_status->state = SVCCTL_STOPPED;
+ service_status->win32_exit_code = WERR_SERVICE_NEVER_STARTED;
+ }
+
+ return WERR_OK;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR wins_stop( const char *service, struct SERVICE_STATUS *service_status )
+{
+ wins_status( service, service_status );
+
+ return WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static WERROR wins_start( const char *service )
+{
+ return WERR_ACCESS_DENIED;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+/* struct for svcctl control to manipulate wins service */
+
+SERVICE_CONTROL_OPS wins_svc_ops = {
+ wins_stop,
+ wins_start,
+ wins_status
+};
diff --git a/source3/smbadduser.in b/source3/smbadduser.in
new file mode 100644
index 0000000..4b96713
--- /dev/null
+++ b/source3/smbadduser.in
@@ -0,0 +1,79 @@
+#!/bin/csh
+#
+# smbadduser - Written by Mike Zakharoff
+#
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+LIBDIR=@libdir@
+PRIVATEDIR=@privatedir@
+CONFIGDIR=@configdir@
+
+unalias *
+set path = ($path /usr/bin)
+
+set smbpasswd = $PRIVATEDIR/smbpasswd
+set user_map = $CONFIGDIR/smbusers
+
+#
+# Set to site specific passwd command
+#
+set passwd = "getent passwd"
+#set passwd = "niscat passwd.org_dir"
+#set passwd = "ypcat passwd"
+
+set line = "----------------------------------------------------------"
+if ($#argv == 0) then
+ echo $line
+ echo "Written: Mike Zakharoff email: michael.j.zakharoff@boeing.com"
+ echo ""
+ echo " 1) Updates $smbpasswd"
+ echo " 2) Updates $user_map"
+ echo " 3) Executes smbpasswd for each new user"
+ echo ""
+ echo "smbadduser unixid:ntid unixid:ntid ..."
+ echo ""
+ echo "Example: smbadduser zak:zakharoffm johns:smithj"
+ echo $line
+ exit 1
+endif
+
+touch $smbpasswd $user_map
+set new = ()
+foreach one ($argv)
+ echo $one | grep ':' >& /dev/null
+ if ($status != 0) then
+ echo "ERROR: Must use unixid:ntid like -> zak:zakharoffm"
+ continue
+ endif
+ set unix = `echo $one | awk -F: '{print $1}'`
+ set ntid = `echo $one | awk -F: '{print $2}'`
+
+ set usr = `eval $passwd | awk -F: '$1==USR {print $1}' USR=$unix`
+ if ($#usr != 1) then
+ echo "ERROR: $unix Not in passwd database SKIPPING..."
+ continue
+ endif
+ set tmp = `cat $smbpasswd | awk -F: '$1==USR {print $1}' USR=$unix`
+ if ($#tmp != 0) then
+ echo "ERROR: $unix is already in $smbpasswd SKIPPING..."
+ continue
+ endif
+
+ echo "Adding: $unix to $smbpasswd"
+ /usr/bin/smbpasswd -a -n $unix
+ if ($unix != $ntid) then
+ echo "Adding: {$unix = $ntid} to $user_map"
+ echo "$unix = $ntid" >> $user_map
+ endif
+ set new = ($new $unix)
+end
+
+#
+# Enter password for new users
+#
+foreach one ($new)
+ echo $line
+ echo "ENTER password for $one"
+ smbpasswd $one
+end
diff --git a/source3/smbd/avahi_register.c b/source3/smbd/avahi_register.c
new file mode 100644
index 0000000..883c862
--- /dev/null
+++ b/source3/smbd/avahi_register.c
@@ -0,0 +1,300 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Register _smb._tcp with avahi
+ *
+ * Copyright (C) Volker Lendecke 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/error.h>
+#include <avahi-common/malloc.h>
+#include <avahi-common/strlst.h>
+
+struct avahi_state_struct {
+ struct AvahiPoll *poll;
+ AvahiClient *client;
+ AvahiEntryGroup *entry_group;
+ uint16_t port;
+};
+
+static void *avahi_allocator_ctx = NULL;
+
+static void * avahi_allocator_malloc(size_t size)
+{
+ return talloc_size(avahi_allocator_ctx, size);
+}
+
+static void avahi_allocator_free(void *p)
+{
+ TALLOC_FREE(p);
+}
+
+static void * avahi_allocator_realloc(void *p, size_t size)
+{
+ return talloc_realloc_size(avahi_allocator_ctx, p, size);
+}
+
+static void * avahi_allocator_calloc(size_t count, size_t size)
+{
+ void *p = talloc_array_size(avahi_allocator_ctx, size, count);
+ if (p) {
+ memset(p, 0, size * count);
+ }
+ return p;
+}
+
+static const struct AvahiAllocator avahi_talloc_allocator = {
+ &avahi_allocator_malloc,
+ &avahi_allocator_free,
+ &avahi_allocator_realloc,
+ &avahi_allocator_calloc
+};
+
+static void avahi_entry_group_callback(AvahiEntryGroup *g,
+ AvahiEntryGroupState status,
+ void *userdata)
+{
+ struct avahi_state_struct *state = talloc_get_type_abort(
+ userdata, struct avahi_state_struct);
+ int error;
+
+ switch (status) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_ESTABLISHED\n");
+ break;
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ error = avahi_client_errno(state->client);
+
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_FAILURE: %s\n",
+ avahi_strerror(error));
+ break;
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_COLLISION\n");
+ break;
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_UNCOMMITED\n");
+ break;
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ DBG_DEBUG("AVAHI_ENTRY_GROUP_REGISTERING\n");
+ break;
+ }
+}
+
+static void avahi_client_callback(AvahiClient *c, AvahiClientState status,
+ void *userdata)
+{
+ struct avahi_state_struct *state = talloc_get_type_abort(
+ userdata, struct avahi_state_struct);
+ int error;
+
+ switch (status) {
+ case AVAHI_CLIENT_S_RUNNING: {
+ int snum;
+ int num_services = lp_numservices();
+ size_t dk = 0;
+ AvahiStringList *adisk = NULL;
+ AvahiStringList *adisk2 = NULL;
+ AvahiStringList *dinfo = NULL;
+ const char *hostname = NULL;
+ enum mdns_name_values mdns_name = lp_mdns_name();
+ const char *model = NULL;
+
+ DBG_DEBUG("AVAHI_CLIENT_S_RUNNING\n");
+
+ switch (mdns_name) {
+ case MDNS_NAME_MDNS:
+ hostname = avahi_client_get_host_name(c);
+ break;
+ case MDNS_NAME_NETBIOS:
+ hostname = lp_netbios_name();
+ break;
+ default:
+ DBG_ERR("Unhandled mdns_name %d\n", mdns_name);
+ return;
+ }
+
+ state->entry_group = avahi_entry_group_new(
+ c, avahi_entry_group_callback, state);
+ if (state->entry_group == NULL) {
+ error = avahi_client_errno(c);
+ DBG_DEBUG("avahi_entry_group_new failed: %s\n",
+ avahi_strerror(error));
+ break;
+ }
+
+ error = avahi_entry_group_add_service(
+ state->entry_group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0, hostname,
+ "_smb._tcp", NULL, NULL, state->port, NULL);
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_add_service failed: %s\n",
+ avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+
+ for (snum = 0; snum < num_services; snum++) {
+ if (lp_snum_ok(snum) &&
+ lp_parm_bool(snum, "fruit", "time machine", false))
+ {
+ adisk2 = avahi_string_list_add_printf(
+ adisk, "dk%zu=adVN=%s,adVF=0x82",
+ dk++, lp_const_servicename(snum));
+ if (adisk2 == NULL) {
+ DBG_DEBUG("avahi_string_list_add_printf"
+ "failed: returned NULL\n");
+ avahi_string_list_free(adisk);
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ adisk = adisk2;
+ adisk2 = NULL;
+ }
+ }
+ if (dk > 0) {
+ adisk2 = avahi_string_list_add(adisk, "sys=adVF=0x100");
+ if (adisk2 == NULL) {
+ DBG_DEBUG("avahi_string_list_add failed: "
+ "returned NULL\n");
+ avahi_string_list_free(adisk);
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ adisk = adisk2;
+ adisk2 = NULL;
+
+ error = avahi_entry_group_add_service_strlst(
+ state->entry_group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0, hostname,
+ "_adisk._tcp", NULL, NULL, 0, adisk);
+ avahi_string_list_free(adisk);
+ adisk = NULL;
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_add_service_strlst "
+ "failed: %s\n", avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ }
+
+ model = lp_parm_const_string(-1, "fruit", "model", "MacSamba");
+
+ dinfo = avahi_string_list_add_printf(NULL, "model=%s", model);
+ if (dinfo == NULL) {
+ DBG_DEBUG("avahi_string_list_add_printf"
+ "failed: returned NULL\n");
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+
+ error = avahi_entry_group_add_service_strlst(
+ state->entry_group, AVAHI_IF_UNSPEC,
+ AVAHI_PROTO_UNSPEC, 0, hostname,
+ "_device-info._tcp", NULL, NULL, 0,
+ dinfo);
+ avahi_string_list_free(dinfo);
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_add_service failed: %s\n",
+ avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+
+ error = avahi_entry_group_commit(state->entry_group);
+ if (error != AVAHI_OK) {
+ DBG_DEBUG("avahi_entry_group_commit failed: %s\n",
+ avahi_strerror(error));
+ avahi_entry_group_free(state->entry_group);
+ state->entry_group = NULL;
+ break;
+ }
+ break;
+ }
+ case AVAHI_CLIENT_FAILURE:
+ error = avahi_client_errno(c);
+
+ DBG_DEBUG("AVAHI_CLIENT_FAILURE: %s\n", avahi_strerror(error));
+
+ if (error != AVAHI_ERR_DISCONNECTED) {
+ break;
+ }
+ avahi_client_free(c);
+ state->client = avahi_client_new(state->poll, AVAHI_CLIENT_NO_FAIL,
+ avahi_client_callback, state,
+ &error);
+ if (state->client == NULL) {
+ DBG_DEBUG("avahi_client_new failed: %s\n",
+ avahi_strerror(error));
+ break;
+ }
+ break;
+ case AVAHI_CLIENT_S_COLLISION:
+ DBG_DEBUG("AVAHI_CLIENT_S_COLLISION\n");
+ break;
+ case AVAHI_CLIENT_S_REGISTERING:
+ DBG_DEBUG("AVAHI_CLIENT_S_REGISTERING\n");
+ break;
+ case AVAHI_CLIENT_CONNECTING:
+ DBG_DEBUG("AVAHI_CLIENT_CONNECTING\n");
+ break;
+ }
+}
+
+void *avahi_start_register(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ uint16_t port)
+{
+ struct avahi_state_struct *state;
+ int error;
+
+ avahi_allocator_ctx = talloc_new(mem_ctx);
+ if (avahi_allocator_ctx == NULL) {
+ return NULL;
+ }
+ avahi_set_allocator(&avahi_talloc_allocator);
+
+ state = talloc(mem_ctx, struct avahi_state_struct);
+ if (state == NULL) {
+ return state;
+ }
+ state->port = port;
+ state->poll = tevent_avahi_poll(state, ev);
+ if (state->poll == NULL) {
+ goto fail;
+ }
+ state->client = avahi_client_new(state->poll, AVAHI_CLIENT_NO_FAIL,
+ avahi_client_callback, state,
+ &error);
+ if (state->client == NULL) {
+ DBG_DEBUG("avahi_client_new failed: %s\n",
+ avahi_strerror(error));
+ goto fail;
+ }
+ return state;
+
+ fail:
+ TALLOC_FREE(state);
+ return NULL;
+}
diff --git a/source3/smbd/blocking.c b/source3/smbd/blocking.c
new file mode 100644
index 0000000..8b41288
--- /dev/null
+++ b/source3/smbd/blocking.c
@@ -0,0 +1,763 @@
+/*
+ Unix SMB/CIFS implementation.
+ Blocking Locking functions
+ Copyright (C) Jeremy Allison 1998-2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/dbwrap/dbwrap_watch.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+NTSTATUS smbd_do_locks_try(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ uint16_t i;
+
+ for (i=0; i<num_locks; i++) {
+ struct smbd_lock_element *e = &locks[i];
+
+ status = do_lock(
+ fsp,
+ locks, /* req_mem_ctx */
+ &e->req_guid,
+ e->smblctx,
+ e->count,
+ e->offset,
+ e->brltype,
+ e->lock_flav,
+ blocking_pid,
+ blocking_smblctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+
+ *blocker_idx = i;
+
+ /*
+ * Undo the locks we successfully got
+ */
+ for (i = i-1; i != UINT16_MAX; i--) {
+ struct smbd_lock_element *e = &locks[i];
+ do_unlock(fsp,
+ e->smblctx,
+ e->count,
+ e->offset,
+ e->lock_flav);
+ }
+
+ return status;
+}
+
+static bool smbd_smb1_fsp_add_blocked_lock_req(
+ struct files_struct *fsp, struct tevent_req *req)
+{
+ size_t num_reqs = talloc_array_length(fsp->blocked_smb1_lock_reqs);
+ struct tevent_req **tmp = NULL;
+
+ tmp = talloc_realloc(
+ fsp,
+ fsp->blocked_smb1_lock_reqs,
+ struct tevent_req *,
+ num_reqs+1);
+ if (tmp == NULL) {
+ return false;
+ }
+ fsp->blocked_smb1_lock_reqs = tmp;
+ fsp->blocked_smb1_lock_reqs[num_reqs] = req;
+ return true;
+}
+
+struct smbd_smb1_do_locks_state {
+ struct tevent_context *ev;
+ struct smb_request *smbreq;
+ struct files_struct *fsp;
+ uint32_t timeout;
+ uint32_t polling_msecs;
+ uint32_t retry_msecs;
+ struct timeval endtime;
+ bool large_offset; /* required for correct cancel */
+ uint16_t num_locks;
+ struct smbd_lock_element *locks;
+ uint16_t blocker;
+ NTSTATUS deny_status;
+};
+
+static void smbd_smb1_do_locks_try(struct tevent_req *req);
+static void smbd_smb1_do_locks_retry(struct tevent_req *subreq);
+static void smbd_smb1_blocked_locks_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static NTSTATUS smbd_smb1_do_locks_check(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx);
+
+static void smbd_smb1_do_locks_setup_timeout(
+ struct smbd_smb1_do_locks_state *state,
+ const struct smbd_lock_element *blocker)
+{
+ struct files_struct *fsp = state->fsp;
+
+ if (!timeval_is_zero(&state->endtime)) {
+ /*
+ * already done
+ */
+ return;
+ }
+
+ if ((state->timeout != 0) && (state->timeout != UINT32_MAX)) {
+ /*
+ * Windows internal resolution for blocking locks
+ * seems to be about 200ms... Don't wait for less than
+ * that. JRA.
+ */
+ state->timeout = MAX(state->timeout, lp_lock_spin_time());
+ }
+
+ if (state->timeout != 0) {
+ goto set_endtime;
+ }
+
+ if (blocker == NULL) {
+ goto set_endtime;
+ }
+
+ if ((blocker->offset >= 0xEF000000) &&
+ ((blocker->offset >> 63) == 0)) {
+ /*
+ * This must be an optimization of an ancient
+ * application bug...
+ */
+ state->timeout = lp_lock_spin_time();
+ }
+
+ if (fsp->fsp_flags.lock_failure_seen &&
+ (blocker->offset == fsp->lock_failure_offset)) {
+ /*
+ * Delay repeated lock attempts on the same
+ * lock. Maybe a more advanced version of the
+ * above check?
+ */
+ DBG_DEBUG("Delaying lock request due to previous "
+ "failure\n");
+ state->timeout = lp_lock_spin_time();
+ }
+
+set_endtime:
+ /*
+ * Note state->timeout might still 0,
+ * but that's ok, as we don't want to retry
+ * in that case.
+ */
+ state->endtime = timeval_add(&state->smbreq->request_time,
+ state->timeout / 1000,
+ (state->timeout % 1000) * 1000);
+}
+
+static void smbd_smb1_do_locks_update_retry_msecs(
+ struct smbd_smb1_do_locks_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms,
+ * we just use half of it to trigger the first retry.
+ *
+ * v_min is in the range of 0.001 to 10 secs
+ * (0.1 secs by default)
+ *
+ * v_max is in the range of 0.01 to 100 secs
+ * (1.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.1, 0.2, 0.3, 0.4, ... 1.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()))/2;
+ uint32_t v_max = 10 * v_min;
+
+ if (state->retry_msecs >= v_max) {
+ state->retry_msecs = v_max;
+ return;
+ }
+
+ state->retry_msecs += v_min;
+}
+
+static void smbd_smb1_do_locks_update_polling_msecs(
+ struct smbd_smb1_do_locks_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms.
+ *
+ * v_min is in the range of 0.002 to 20 secs
+ * (0.2 secs by default)
+ *
+ * v_max is in the range of 0.02 to 200 secs
+ * (2.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.2, 0.4, 0.6, 0.8, ... 2.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()));
+ uint32_t v_max = 10 * v_min;
+
+ if (state->polling_msecs >= v_max) {
+ state->polling_msecs = v_max;
+ return;
+ }
+
+ state->polling_msecs += v_min;
+}
+
+struct tevent_req *smbd_smb1_do_locks_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_request **smbreq, /* talloc_move()d into our state */
+ struct files_struct *fsp,
+ uint32_t lock_timeout,
+ bool large_offset,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb1_do_locks_state *state = NULL;
+ bool ok;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct smbd_smb1_do_locks_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smbreq = talloc_move(state, smbreq);
+ state->fsp = fsp;
+ state->timeout = lock_timeout;
+ state->large_offset = large_offset;
+ state->num_locks = num_locks;
+ state->locks = locks;
+ state->deny_status = NT_STATUS_LOCK_NOT_GRANTED;
+
+ DBG_DEBUG("state=%p, state->smbreq=%p\n", state, state->smbreq);
+
+ if (num_locks == 0 || locks == NULL) {
+ DBG_DEBUG("no locks\n");
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->locks[0].lock_flav == POSIX_LOCK) {
+ /*
+ * SMB1 posix locks always use
+ * NT_STATUS_FILE_LOCK_CONFLICT.
+ */
+ state->deny_status = NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ smbd_smb1_do_locks_try(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ok = smbd_smb1_fsp_add_blocked_lock_req(fsp, req);
+ if (!ok) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_cleanup_fn(req, smbd_smb1_blocked_locks_cleanup);
+ return req;
+}
+
+static void smbd_smb1_blocked_locks_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ struct files_struct *fsp = state->fsp;
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ size_t i;
+
+ DBG_DEBUG("req=%p, state=%p, req_state=%d\n",
+ req,
+ state,
+ (int)req_state);
+
+ if (req_state == TEVENT_REQ_RECEIVED) {
+ DBG_DEBUG("already received\n");
+ return;
+ }
+
+ for (i=0; i<num_blocked; i++) {
+ if (blocked[i] == req) {
+ break;
+ }
+ }
+ SMB_ASSERT(i<num_blocked);
+
+ ARRAY_DEL_ELEMENT(blocked, i, num_blocked);
+
+ fsp->blocked_smb1_lock_reqs = talloc_realloc(
+ fsp, blocked, struct tevent_req *, num_blocked-1);
+}
+
+static NTSTATUS smbd_smb1_do_locks_check_blocked(
+ uint16_t num_blocked,
+ struct smbd_lock_element *blocked,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ uint64_t *blocking_smblctx)
+{
+ uint16_t li;
+
+ for (li=0; li < num_locks; li++) {
+ struct smbd_lock_element *l = &locks[li];
+ uint16_t bi;
+ bool valid;
+
+ valid = byte_range_valid(l->offset, l->count);
+ if (!valid) {
+ return NT_STATUS_INVALID_LOCK_RANGE;
+ }
+
+ for (bi = 0; bi < num_blocked; bi++) {
+ struct smbd_lock_element *b = &blocked[li];
+ bool overlap;
+
+ /* Read locks never conflict. */
+ if (l->brltype == READ_LOCK && b->brltype == READ_LOCK) {
+ continue;
+ }
+
+ overlap = byte_range_overlap(l->offset,
+ l->count,
+ b->offset,
+ b->count);
+ if (!overlap) {
+ continue;
+ }
+
+ *blocker_idx = li;
+ *blocking_smblctx = b->smblctx;
+ return NT_STATUS_LOCK_NOT_GRANTED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb1_do_locks_check(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx)
+{
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ NTSTATUS status;
+ size_t bi;
+
+ /*
+ * We check the pending/blocked requests
+ * from the oldest to the youngest request.
+ *
+ * Note due to the retry logic the current request
+ * might already be in the list.
+ */
+
+ for (bi = 0; bi < num_blocked; bi++) {
+ struct smbd_smb1_do_locks_state *blocked_state =
+ tevent_req_data(blocked[bi],
+ struct smbd_smb1_do_locks_state);
+
+ if (blocked_state->locks == locks) {
+ SMB_ASSERT(blocked_state->num_locks == num_locks);
+
+ /*
+ * We found ourself...
+ */
+ break;
+ }
+
+ status = smbd_smb1_do_locks_check_blocked(
+ blocked_state->num_locks,
+ blocked_state->locks,
+ num_locks,
+ locks,
+ blocker_idx,
+ blocking_smblctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ *blocking_pid = messaging_server_id(
+ fsp->conn->sconn->msg_ctx);
+ return status;
+ }
+ }
+
+ status = smbd_do_locks_try(
+ fsp,
+ num_locks,
+ locks,
+ blocker_idx,
+ blocking_pid,
+ blocking_smblctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb1_do_locks_try(struct tevent_req *req)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ struct files_struct *fsp = state->fsp;
+ struct share_mode_lock *lck;
+ struct timeval endtime = { 0 };
+ struct server_id blocking_pid = { 0 };
+ uint64_t blocking_smblctx = 0;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ bool ok;
+ bool expired;
+
+ lck = get_existing_share_mode_lock(state, fsp->file_id);
+ if (tevent_req_nomem(lck, req)) {
+ DBG_DEBUG("Could not get share mode lock\n");
+ return;
+ }
+
+ status = smbd_smb1_do_locks_check(
+ fsp,
+ state->num_locks,
+ state->locks,
+ &state->blocker,
+ &blocking_pid,
+ &blocking_smblctx);
+ if (NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /*
+ * We got NT_STATUS_RETRY,
+ * we reset polling_msecs so that
+ * that the retries based on LOCK_NOT_GRANTED
+ * will later start with small intervals again.
+ */
+ state->polling_msecs = 0;
+
+ /*
+ * The backend wasn't able to decide yet.
+ * We need to wait even for non-blocking
+ * locks.
+ *
+ * The backend uses blocking_smblctx == UINT64_MAX
+ * to indicate that we should use retry timers.
+ *
+ * It uses blocking_smblctx == 0 to indicate
+ * it will use share_mode_wakeup_waiters()
+ * to wake us. Note that unrelated changes in
+ * locking.tdb may cause retries.
+ */
+
+ if (blocking_smblctx != UINT64_MAX) {
+ SMB_ASSERT(blocking_smblctx == 0);
+ goto setup_retry;
+ }
+
+ smbd_smb1_do_locks_update_retry_msecs(state);
+
+ DBG_DEBUG("Waiting for a backend decision. "
+ "Retry in %"PRIu32" msecs\n",
+ state->retry_msecs);
+
+ /*
+ * We completely ignore state->endtime here
+ * we we'll wait for a backend decision forever.
+ * If the backend is smart enough to implement
+ * some NT_STATUS_RETRY logic, it has to
+ * switch to any other status after in order
+ * to avoid waiting forever.
+ */
+ endtime = timeval_current_ofs_msec(state->retry_msecs);
+ goto setup_retry;
+ }
+ if (!ERROR_WAS_LOCK_DENIED(status)) {
+ goto done;
+ }
+ /*
+ * We got LOCK_NOT_GRANTED, make sure
+ * a following STATUS_RETRY will start
+ * with short intervals again.
+ */
+ state->retry_msecs = 0;
+
+ smbd_smb1_do_locks_setup_timeout(state, &state->locks[state->blocker]);
+ DBG_DEBUG("timeout=%"PRIu32", blocking_smblctx=%"PRIu64"\n",
+ state->timeout,
+ blocking_smblctx);
+
+ /*
+ * The client specified timeout expired
+ * avoid further retries.
+ *
+ * Otherwise keep waiting either waiting
+ * for changes in locking.tdb or the polling
+ * mode timers waiting for posix locks.
+ *
+ * If the endtime is not expired yet,
+ * it means we'll retry after a timeout.
+ * In that case we'll have to return
+ * NT_STATUS_FILE_LOCK_CONFLICT
+ * instead of NT_STATUS_LOCK_NOT_GRANTED.
+ */
+ expired = timeval_expired(&state->endtime);
+ if (expired) {
+ status = state->deny_status;
+ goto done;
+ }
+ state->deny_status = NT_STATUS_FILE_LOCK_CONFLICT;
+
+ endtime = state->endtime;
+
+ if (blocking_smblctx == UINT64_MAX) {
+ struct timeval tmp;
+
+ smbd_smb1_do_locks_update_polling_msecs(state);
+
+ DBG_DEBUG("Blocked on a posix lock. Retry in %"PRIu32" msecs\n",
+ state->polling_msecs);
+
+ tmp = timeval_current_ofs_msec(state->polling_msecs);
+ endtime = timeval_min(&endtime, &tmp);
+ }
+
+setup_retry:
+ subreq = share_mode_watch_send(
+ state, state->ev, lck, blocking_pid);
+ if (tevent_req_nomem(subreq, req)) {
+ goto done;
+ }
+ TALLOC_FREE(lck);
+ tevent_req_set_callback(subreq, smbd_smb1_do_locks_retry, req);
+
+ if (timeval_is_zero(&endtime)) {
+ return;
+ }
+
+ ok = tevent_req_set_endtime(subreq, state->ev, endtime);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ return;
+done:
+ TALLOC_FREE(lck);
+ smbd_smb1_brl_finish_by_req(req, status);
+}
+
+static void smbd_smb1_do_locks_retry(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+
+ DBG_DEBUG("share_mode_watch_recv returned %s\n",
+ nt_errstr(status));
+
+ /*
+ * We ignore any errors here, it's most likely
+ * we just get NT_STATUS_OK or NT_STATUS_IO_TIMEOUT.
+ *
+ * In any case we can just give it a retry.
+ */
+
+ smbd_smb1_do_locks_try(req);
+}
+
+NTSTATUS smbd_smb1_do_locks_recv(struct tevent_req *req)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ NTSTATUS status = NT_STATUS_OK;
+ bool err;
+
+ err = tevent_req_is_nterror(req, &status);
+
+ DBG_DEBUG("err=%d, status=%s\n", (int)err, nt_errstr(status));
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct files_struct *fsp = state->fsp;
+ struct smbd_lock_element *blocker =
+ &state->locks[state->blocker];
+
+ DBG_DEBUG("Setting lock_failure_offset=%"PRIu64"\n",
+ blocker->offset);
+
+ fsp->fsp_flags.lock_failure_seen = true;
+ fsp->lock_failure_offset = blocker->offset;
+ return status;
+ }
+
+ tevent_req_received(req);
+
+ return NT_STATUS_OK;
+}
+
+bool smbd_smb1_do_locks_extract_smbreq(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request **psmbreq)
+{
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+
+ DBG_DEBUG("req=%p, state=%p, state->smbreq=%p\n",
+ req,
+ state,
+ state->smbreq);
+
+ if (state->smbreq == NULL) {
+ return false;
+ }
+ *psmbreq = talloc_move(mem_ctx, &state->smbreq);
+ return true;
+}
+
+void smbd_smb1_brl_finish_by_req(struct tevent_req *req, NTSTATUS status)
+{
+ DBG_DEBUG("req=%p, status=%s\n", req, nt_errstr(status));
+
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ } else {
+ tevent_req_nterror(req, status);
+ }
+}
+
+bool smbd_smb1_brl_finish_by_lock(
+ struct files_struct *fsp,
+ bool large_offset,
+ struct smbd_lock_element lock,
+ NTSTATUS finish_status)
+{
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ size_t i;
+
+ DBG_DEBUG("num_blocked=%zu\n", num_blocked);
+
+ for (i=0; i<num_blocked; i++) {
+ struct tevent_req *req = blocked[i];
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ uint16_t j;
+
+ DBG_DEBUG("i=%zu, req=%p\n", i, req);
+
+ if (state->large_offset != large_offset) {
+ continue;
+ }
+
+ for (j=0; j<state->num_locks; j++) {
+ struct smbd_lock_element *l = &state->locks[j];
+
+ if ((lock.smblctx == l->smblctx) &&
+ (lock.offset == l->offset) &&
+ (lock.count == l->count)) {
+ smbd_smb1_brl_finish_by_req(
+ req, finish_status);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static struct files_struct *smbd_smb1_brl_finish_by_mid_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ struct tevent_req **blocked = fsp->blocked_smb1_lock_reqs;
+ size_t num_blocked = talloc_array_length(blocked);
+ uint64_t mid = *((uint64_t *)private_data);
+ size_t i;
+
+ DBG_DEBUG("fsp=%p, num_blocked=%zu\n", fsp, num_blocked);
+
+ for (i=0; i<num_blocked; i++) {
+ struct tevent_req *req = blocked[i];
+ struct smbd_smb1_do_locks_state *state = tevent_req_data(
+ req, struct smbd_smb1_do_locks_state);
+ struct smb_request *smbreq = state->smbreq;
+
+ if (smbreq->mid == mid) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * This walks the list of fsps, we store the blocked reqs attached to
+ * them. It can be expensive, but this is legacy SMB1 and trying to
+ * remember looking at traces I don't really see many of those calls.
+ */
+
+bool smbd_smb1_brl_finish_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid)
+{
+ struct files_struct *found = files_forall(
+ sconn, smbd_smb1_brl_finish_by_mid_fn, &mid);
+ return (found != NULL);
+}
diff --git a/source3/smbd/close.c b/source3/smbd/close.c
new file mode 100644
index 0000000..bbca474
--- /dev/null
+++ b/source3/smbd/close.c
@@ -0,0 +1,1742 @@
+/*
+ Unix SMB/CIFS implementation.
+ file closing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "smbd/scavenger.h"
+#include "fake_file.h"
+#include "transfer_file.h"
+#include "auth.h"
+#include "messages.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "source3/smbd/dir.h"
+
+/****************************************************************************
+ Run a file if it is a magic script.
+****************************************************************************/
+
+static NTSTATUS check_magic(struct files_struct *fsp)
+{
+ int ret;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *magic_output = NULL;
+ SMB_STRUCT_STAT st;
+ int tmp_fd, outfd;
+ TALLOC_CTX *ctx = NULL;
+ const char *p;
+ struct connection_struct *conn = fsp->conn;
+ char *fname = NULL;
+ NTSTATUS status;
+
+ if (!*lp_magic_script(talloc_tos(), lp_sub, SNUM(conn))) {
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(5,("checking magic for %s\n", fsp_str_dbg(fsp)));
+
+ ctx = talloc_stackframe();
+
+ fname = fsp->fsp_name->base_name;
+
+ if (!(p = strrchr_m(fname,'/'))) {
+ p = fname;
+ } else {
+ p++;
+ }
+
+ if (!strequal(lp_magic_script(talloc_tos(), lp_sub, SNUM(conn)),p)) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ if (*lp_magic_output(talloc_tos(), lp_sub, SNUM(conn))) {
+ magic_output = lp_magic_output(talloc_tos(), lp_sub, SNUM(conn));
+ } else {
+ magic_output = talloc_asprintf(ctx,
+ "%s.out",
+ fname);
+ }
+ if (!magic_output) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* Ensure we don't depend on user's PATH. */
+ p = talloc_asprintf(ctx, "./%s", fname);
+ if (!p) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (chmod(fname, 0755) == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+ ret = smbrun(p, &tmp_fd, NULL);
+ DEBUG(3,("Invoking magic command %s gave %d\n",
+ p,ret));
+
+ unlink(fname);
+ if (ret != 0 || tmp_fd == -1) {
+ if (tmp_fd != -1) {
+ close(tmp_fd);
+ }
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ outfd = open(magic_output, O_CREAT|O_EXCL|O_RDWR, 0600);
+ if (outfd == -1) {
+ int err = errno;
+ close(tmp_fd);
+ status = map_nt_error_from_unix(err);
+ goto out;
+ }
+
+ if (sys_fstat(tmp_fd, &st, false) == -1) {
+ int err = errno;
+ close(tmp_fd);
+ close(outfd);
+ status = map_nt_error_from_unix(err);
+ goto out;
+ }
+
+ if (transfer_file(tmp_fd,outfd,(off_t)st.st_ex_size) == (off_t)-1) {
+ int err = errno;
+ close(tmp_fd);
+ close(outfd);
+ status = map_nt_error_from_unix(err);
+ goto out;
+ }
+ close(tmp_fd);
+ if (close(outfd) == -1) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+ out:
+ TALLOC_FREE(ctx);
+ return status;
+}
+
+/****************************************************************************
+ Delete all streams
+****************************************************************************/
+
+NTSTATUS delete_all_streams(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ struct stream_struct *stream_info = NULL;
+ unsigned int i;
+ unsigned int num_streams = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ status = vfs_fstreaminfo(smb_fname->fsp, talloc_tos(),
+ &num_streams, &stream_info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ DEBUG(10, ("no streams around\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("vfs_fstreaminfo failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ DEBUG(10, ("delete_all_streams found %d streams\n",
+ num_streams));
+
+ if (num_streams == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ int res;
+ struct smb_filename *smb_fname_stream;
+
+ if (strequal(stream_info[i].name, "::$DATA")) {
+ continue;
+ }
+
+ status = synthetic_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname->base_name,
+ stream_info[i].name,
+ NULL,
+ smb_fname->twrp,
+ (smb_fname->flags &
+ ~SMB_FILENAME_POSIX_PATH),
+ &smb_fname_stream);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("talloc_aprintf failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ res = SMB_VFS_UNLINKAT(conn,
+ conn->cwd_fsp,
+ smb_fname_stream,
+ 0);
+
+ if (res == -1) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(10, ("Could not delete stream %s: %s\n",
+ smb_fname_str_dbg(smb_fname_stream),
+ strerror(errno)));
+ TALLOC_FREE(smb_fname_stream);
+ break;
+ }
+ TALLOC_FREE(smb_fname_stream);
+ }
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct has_other_nonposix_opens_state {
+ files_struct *fsp;
+ bool found_another;
+};
+
+static bool has_other_nonposix_opens_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct has_other_nonposix_opens_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+
+ if (e->name_hash != fsp->name_hash) {
+ return false;
+ }
+ if (e->flags & SHARE_MODE_FLAG_POSIX_OPEN) {
+ return false;
+ }
+ if (e->share_file_id == fh_get_gen_id(fsp->fh)) {
+ struct server_id self = messaging_server_id(
+ fsp->conn->sconn->msg_ctx);
+ if (server_id_equal(&self, &e->pid)) {
+ return false;
+ }
+ }
+ if (share_entry_stale_pid(e)) {
+ return false;
+ }
+
+ state->found_another = true;
+ return true;
+}
+
+bool has_other_nonposix_opens(struct share_mode_lock *lck,
+ struct files_struct *fsp)
+{
+ struct has_other_nonposix_opens_state state = { .fsp = fsp };
+ bool ok;
+
+ ok = share_mode_forall_entries(
+ lck, has_other_nonposix_opens_fn, &state);
+ if (!ok) {
+ return false;
+ }
+ return state.found_another;
+}
+
+struct close_share_mode_lock_state {
+ struct share_mode_entry_prepare_state prepare_state;
+ const char *object_type;
+ struct files_struct *fsp;
+ enum file_close_type close_type;
+ bool delete_object;
+ bool got_tokens;
+ const struct security_unix_token *del_token;
+ const struct security_token *del_nt_token;
+ bool reset_delete_on_close;
+ share_mode_entry_prepare_unlock_fn_t cleanup_fn;
+};
+
+static void close_share_mode_lock_prepare(struct share_mode_lock *lck,
+ bool *keep_locked,
+ void *private_data)
+{
+ struct close_share_mode_lock_state *state =
+ (struct close_share_mode_lock_state *)private_data;
+ struct files_struct *fsp = state->fsp;
+ bool normal_close;
+ bool ok;
+
+ /*
+ * By default drop the g_lock again if we leave the
+ * tdb chainlock.
+ */
+ *keep_locked = false;
+
+ if (fsp->oplock_type != NO_OPLOCK) {
+ ok = remove_share_oplock(lck, fsp);
+ if (!ok) {
+ struct file_id_buf buf;
+
+ DBG_ERR("failed to remove share oplock for "
+ "%s %s, %s, %s\n",
+ state->object_type,
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &buf));
+ }
+ }
+
+ if (fsp->fsp_flags.write_time_forced) {
+ NTTIME mtime = share_mode_changed_write_time(lck);
+ struct timespec ts = nt_time_to_full_timespec(mtime);
+
+ DBG_DEBUG("write time forced for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ set_close_write_time(fsp, ts);
+ } else if (fsp->fsp_flags.update_write_time_on_close) {
+ /* Someone had a pending write. */
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ DBG_DEBUG("update to current time for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ /* Update to current time due to "normal" write. */
+ set_close_write_time(fsp, timespec_current());
+ } else {
+ DBG_DEBUG("write time pending for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ /* Update to time set on close call. */
+ set_close_write_time(fsp, fsp->close_write_time);
+ }
+ }
+
+ if (fsp->fsp_flags.initial_delete_on_close &&
+ !is_delete_on_close_set(lck, fsp->name_hash)) {
+ /* Initial delete on close was set and no one else
+ * wrote a real delete on close. */
+
+ fsp->fsp_flags.delete_on_close = true;
+ set_delete_on_close_lck(fsp, lck,
+ fsp->conn->session_info->security_token,
+ fsp->conn->session_info->unix_token);
+ }
+
+ state->delete_object = is_delete_on_close_set(lck, fsp->name_hash) &&
+ !has_other_nonposix_opens(lck, fsp);
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a file.
+ */
+
+ normal_close = (state->close_type == NORMAL_CLOSE || state->close_type == SHUTDOWN_CLOSE);
+ if (!normal_close) {
+ /*
+ * Never try to delete the file/directory for ERROR_CLOSE
+ */
+ state->delete_object = false;
+ }
+
+ if (!state->delete_object) {
+ ok = del_share_mode(lck, fsp);
+ if (!ok) {
+ DBG_ERR("Could not delete share entry for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ }
+ return;
+ }
+
+ /*
+ * We're going to remove the file/directory
+ * so keep the g_lock after the tdb chainlock
+ * is left, so we hold the share_mode_lock
+ * also during the deletion
+ */
+ *keep_locked = true;
+
+ state->got_tokens = get_delete_on_close_token(lck, fsp->name_hash,
+ &state->del_nt_token, &state->del_token);
+ if (state->close_type != ERROR_CLOSE) {
+ SMB_ASSERT(state->got_tokens);
+ }
+}
+
+static void close_share_mode_lock_cleanup(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct close_share_mode_lock_state *state =
+ (struct close_share_mode_lock_state *)private_data;
+ struct files_struct *fsp = state->fsp;
+ bool ok;
+
+ if (state->reset_delete_on_close) {
+ reset_delete_on_close_lck(fsp, lck);
+ }
+
+ ok = del_share_mode(lck, fsp);
+ if (!ok) {
+ DBG_ERR("Could not delete share entry for %s %s\n",
+ state->object_type, fsp_str_dbg(fsp));
+ }
+}
+
+/****************************************************************************
+ Deal with removing a share mode on last close.
+****************************************************************************/
+
+static NTSTATUS close_remove_share_mode(files_struct *fsp,
+ enum file_close_type close_type)
+{
+ connection_struct *conn = fsp->conn;
+ struct close_share_mode_lock_state lck_state = {};
+ bool changed_user = false;
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS tmp_status;
+ NTSTATUS ulstatus;
+ struct file_id id;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_fname = NULL;
+ int ret;
+
+ /* Ensure any pending write time updates are done. */
+ if (fsp->update_write_time_event) {
+ fsp_flush_write_time_update(fsp);
+ }
+
+ /*
+ * Lock the share entries, and determine if we should delete
+ * on close. If so delete whilst the lock is still in effect.
+ * This prevents race conditions with the file being created. JRA.
+ */
+
+ lck_state = (struct close_share_mode_lock_state) {
+ .fsp = fsp,
+ .object_type = "file",
+ .close_type = close_type,
+ };
+
+ status = share_mode_entry_prepare_lock_del(&lck_state.prepare_state,
+ fsp->file_id,
+ close_share_mode_lock_prepare,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_del() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ return status;
+ }
+
+ /* Remove the oplock before potentially deleting the file. */
+ if (fsp->oplock_type != NO_OPLOCK) {
+ release_file_oplock(fsp);
+ }
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a file.
+ */
+
+ if (!lck_state.delete_object) {
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /*
+ * Ok, we have to delete the file
+ */
+ lck_state.cleanup_fn = close_share_mode_lock_cleanup;
+
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close was set "
+ "- deleting file.\n", fsp_str_dbg(fsp)));
+
+ /*
+ * Don't try to update the write time when we delete the file
+ */
+ fsp->fsp_flags.update_write_time_on_close = false;
+
+ if (lck_state.got_tokens &&
+ !unix_token_equal(lck_state.del_token, get_current_utok(conn)))
+ {
+ /* Become the user who requested the delete. */
+
+ DEBUG(5,("close_remove_share_mode: file %s. "
+ "Change user to uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)lck_state.del_token->uid));
+
+ if (!push_sec_ctx()) {
+ smb_panic("close_remove_share_mode: file %s. failed to push "
+ "sec_ctx.\n");
+ }
+
+ set_sec_ctx(lck_state.del_token->uid,
+ lck_state.del_token->gid,
+ lck_state.del_token->ngroups,
+ lck_state.del_token->groups,
+ lck_state.del_nt_token);
+
+ changed_user = true;
+ }
+
+ /* We can only delete the file if the name we have is still valid and
+ hasn't been renamed. */
+
+ tmp_status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(tmp_status)) {
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and stat failed with error %s\n",
+ fsp_str_dbg(fsp), nt_errstr(tmp_status)));
+ /*
+ * Don't save the errno here, we ignore this error
+ */
+ goto done;
+ }
+
+ id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ if (!file_id_equal(&fsp->file_id, &id)) {
+ struct file_id_buf ftmp1, ftmp2;
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and dev and/or inode does not match\n",
+ fsp_str_dbg(fsp)));
+ DEBUG(5,("close_remove_share_mode: file %s. stored file_id %s, "
+ "stat file_id %s\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &ftmp1),
+ file_id_str_buf(id, &ftmp2)));
+ /*
+ * Don't save the errno here, we ignore this error
+ */
+ goto done;
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && !fsp_is_alternate_stream(fsp)) {
+
+ status = delete_all_streams(conn, fsp->fsp_name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("delete_all_streams failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ if (fsp->fsp_flags.kernel_share_modes_taken) {
+ /*
+ * A file system sharemode could block the unlink;
+ * remove filesystem sharemodes first.
+ */
+ ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp, 0, 0);
+ if (ret == -1) {
+ DBG_INFO("Removing file system sharemode for %s "
+ "failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ }
+
+ fsp->fsp_flags.kernel_share_modes_taken = false;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ fsp->fsp_name,
+ &parent_fname,
+ &base_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ base_fname,
+ 0);
+ TALLOC_FREE(parent_fname);
+ base_fname = NULL;
+ if (ret != 0) {
+ /*
+ * This call can potentially fail as another smbd may
+ * have had the file open with delete on close set and
+ * deleted it when its last reference to this file
+ * went away. Hence we log this but not at debug level
+ * zero.
+ */
+
+ DEBUG(5,("close_remove_share_mode: file %s. Delete on close "
+ "was set and unlink failed with error %s\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+
+ status = map_nt_error_from_unix(errno);
+ }
+
+ /* As we now have POSIX opens which can unlink
+ * with other open files we may have taken
+ * this code path with more than one share mode
+ * entry - ensure we only delete once by resetting
+ * the delete on close flag. JRA.
+ */
+
+ fsp->fsp_flags.delete_on_close = false;
+ fsp->fsp_flags.fstat_before_close = false;
+ lck_state.reset_delete_on_close = true;
+
+ done:
+
+ if (changed_user) {
+ /* unbecome user. */
+ pop_sec_ctx();
+ }
+
+ if (fsp->fsp_flags.kernel_share_modes_taken) {
+ /* remove filesystem sharemodes */
+ ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp, 0, 0);
+ if (ret == -1) {
+ DBG_INFO("Removing file system sharemode for "
+ "%s failed: %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ }
+ }
+
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ if (lck_state.delete_object) {
+ /*
+ * Do the notification after we released the share
+ * mode lock. Inside notify_fname we take out another
+ * tdb lock. With ctdb also accessing our databases,
+ * this can lead to deadlocks. Putting this notify
+ * after the TALLOC_FREE(lck) above we avoid locking
+ * two records simultaneously. Notifies are async and
+ * informational only, so calling the notify_fname
+ * without holding the share mode lock should not do
+ * any harm.
+ */
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ fsp->fsp_name->base_name);
+ }
+
+ return status;
+}
+
+void set_close_write_time(struct files_struct *fsp, struct timespec ts)
+{
+ DEBUG(6,("close_write_time: %s" , time_to_asc(convert_timespec_to_time_t(ts))));
+
+ if (is_omit_timespec(&ts)) {
+ return;
+ }
+ fsp->fsp_flags.write_time_forced = false;
+ fsp->fsp_flags.update_write_time_on_close = true;
+ fsp->close_write_time = ts;
+}
+
+static void update_write_time_on_close_share_mode_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct files_struct *fsp =
+ talloc_get_type_abort(private_data,
+ struct files_struct);
+ NTTIME share_mtime = share_mode_changed_write_time(lck);
+
+ /*
+ * On close if we're changing the real file time we
+ * must update it in the open file db too.
+ */
+ share_mode_set_old_write_time(lck, fsp->close_write_time);
+
+ /*
+ * Close write times overwrite sticky write times
+ * so we must replace any sticky write time here.
+ */
+ if (!null_nttime(share_mtime)) {
+ share_mode_set_changed_write_time(lck, fsp->close_write_time);
+ }
+}
+
+static NTSTATUS update_write_time_on_close(struct files_struct *fsp)
+{
+ struct smb_file_time ft;
+ NTSTATUS status;
+
+ init_smb_file_time(&ft);
+
+ if (!(fsp->fsp_flags.update_write_time_on_close)) {
+ return NT_STATUS_OK;
+ }
+
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ fsp->close_write_time = timespec_current();
+ }
+
+ /* Ensure we have a valid stat struct for the source. */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ /* if it doesn't seem to be a real file */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * We're being called after close_remove_share_mode() inside
+ * close_normal_file() so it's quite normal to not have an
+ * existing share. So just ignore the result of
+ * share_mode_do_locked_vfs_denied()...
+ */
+ share_mode_do_locked_vfs_denied(fsp->file_id,
+ update_write_time_on_close_share_mode_fn,
+ fsp);
+
+ ft.mtime = fsp->close_write_time;
+ /* As this is a close based update, we are not directly changing the
+ file attributes from a client call, but indirectly from a write. */
+ status = smb_set_file_time(fsp->conn, fsp, fsp->fsp_name, &ft, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("update_write_time_on_close: smb_set_file_time "
+ "on file %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ return status;
+ }
+
+ return status;
+}
+
+static NTSTATUS ntstatus_keeperror(NTSTATUS s1, NTSTATUS s2)
+{
+ if (!NT_STATUS_IS_OK(s1)) {
+ return s1;
+ }
+ return s2;
+}
+
+static void assert_no_pending_aio(struct files_struct *fsp,
+ enum file_close_type close_type)
+{
+ struct smbXsrv_client *client = global_smbXsrv_client;
+ size_t num_connections_alive;
+ unsigned num_requests = fsp->num_aio_requests;
+
+ if (num_requests == 0) {
+ return;
+ }
+
+ num_connections_alive = smbXsrv_client_valid_connections(client);
+
+ if (close_type == SHUTDOWN_CLOSE && num_connections_alive == 0) {
+ /*
+ * fsp->aio_requests and the contents (fsp->aio_requests[x])
+ * are both independently owned by fsp and are not in a
+ * talloc hierarchy. This allows the fsp->aio_requests array to
+ * be reallocated independently of the array contents so it can
+ * grow on demand.
+ *
+ * This means we must ensure order of deallocation
+ * on a SHUTDOWN_CLOSE by deallocating the fsp->aio_requests[x]
+ * contents first, as their destructors access the
+ * fsp->aio_request array. If we don't deallocate them
+ * first, when fsp is deallocated fsp->aio_requests
+ * could have been deallocated *before* its contents
+ * fsp->aio_requests[x], causing a crash.
+ */
+ while (fsp->num_aio_requests != 0) {
+ /*
+ * NB. We *MUST* use
+ * talloc_free(fsp->aio_requests[0]),
+ * and *NOT* TALLOC_FREE() here, as
+ * TALLOC_FREE(fsp->aio_requests[0])
+ * will overwrite any new contents of
+ * fsp->aio_requests[0] that were
+ * copied into it via the destructor
+ * aio_del_req_from_fsp().
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14515
+ */
+ talloc_free(fsp->aio_requests[0]);
+ }
+ return;
+ }
+
+ DBG_ERR("fsp->num_aio_requests=%u\n", num_requests);
+ smb_panic("can not close with outstanding aio requests");
+ return;
+}
+
+/****************************************************************************
+ Close a file.
+
+ close_type can be NORMAL_CLOSE=0,SHUTDOWN_CLOSE,ERROR_CLOSE.
+ printing and magic scripts are only run on normal close.
+ delete on close is done on normal and shutdown close.
+****************************************************************************/
+
+static NTSTATUS close_normal_file(struct smb_request *req, files_struct *fsp,
+ enum file_close_type close_type)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS tmp;
+ connection_struct *conn = fsp->conn;
+ bool is_durable = false;
+
+ SMB_ASSERT(fsp->fsp_flags.is_fsa);
+
+ assert_no_pending_aio(fsp, close_type);
+
+ while (talloc_array_length(fsp->blocked_smb1_lock_reqs) != 0) {
+ smbd_smb1_brl_finish_by_req(
+ fsp->blocked_smb1_lock_reqs[0],
+ NT_STATUS_RANGE_NOT_LOCKED);
+ }
+
+ /*
+ * If we're flushing on a close we can get a write
+ * error here, we must remember this.
+ */
+
+ if (NT_STATUS_IS_OK(status) && fsp->op != NULL) {
+ is_durable = fsp->op->global->durable;
+ }
+
+ if (close_type != SHUTDOWN_CLOSE) {
+ is_durable = false;
+ }
+
+ if (is_durable) {
+ DATA_BLOB new_cookie = data_blob_null;
+
+ tmp = SMB_VFS_DURABLE_DISCONNECT(fsp,
+ fsp->op->global->backend_cookie,
+ fsp->op,
+ &new_cookie);
+ if (NT_STATUS_IS_OK(tmp)) {
+ struct timeval tv;
+ NTTIME now;
+
+ if (req != NULL) {
+ tv = req->request_time;
+ } else {
+ tv = timeval_current();
+ }
+ now = timeval_to_nttime(&tv);
+
+ data_blob_free(&fsp->op->global->backend_cookie);
+ fsp->op->global->backend_cookie = new_cookie;
+
+ fsp->op->compat = NULL;
+ tmp = smbXsrv_open_close(fsp->op, now);
+ if (!NT_STATUS_IS_OK(tmp)) {
+ DEBUG(1, ("Failed to update smbXsrv_open "
+ "record when disconnecting durable "
+ "handle for file %s: %s - "
+ "proceeding with normal close\n",
+ fsp_str_dbg(fsp), nt_errstr(tmp)));
+ }
+ scavenger_schedule_disconnected(fsp);
+ } else {
+ DEBUG(1, ("Failed to disconnect durable handle for "
+ "file %s: %s - proceeding with normal "
+ "close\n", fsp_str_dbg(fsp), nt_errstr(tmp)));
+ }
+ if (!NT_STATUS_IS_OK(tmp)) {
+ is_durable = false;
+ }
+ }
+
+ if (is_durable) {
+ /*
+ * This is the case where we successfully disconnected
+ * a durable handle and closed the underlying file.
+ * In all other cases, we proceed with a genuine close.
+ */
+ DEBUG(10, ("%s disconnected durable handle for file %s\n",
+ conn->session_info->unix_info->unix_name,
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_OK;
+ }
+
+ if (fsp->op != NULL) {
+ /*
+ * Make sure the handle is not marked as durable anymore
+ */
+ fsp->op->global->durable = false;
+ }
+
+ /* If this is an old DOS or FCB open and we have multiple opens on
+ the same handle we only have one share mode. Ensure we only remove
+ the share mode on the last close. */
+
+ if (fh_get_refcount(fsp->fh) == 1) {
+ /* Should we return on error here... ? */
+ tmp = close_remove_share_mode(fsp, close_type);
+ status = ntstatus_keeperror(status, tmp);
+ }
+
+ locking_close_file(fsp, close_type);
+
+ /*
+ * Ensure pending modtime is set before closing underlying fd.
+ */
+
+ tmp = update_write_time_on_close(fsp);
+ if (NT_STATUS_EQUAL(tmp, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Someone renamed the file or a parent directory containing
+ * this file. We can't do anything about this, eat the error.
+ */
+ tmp = NT_STATUS_OK;
+ }
+ status = ntstatus_keeperror(status, tmp);
+
+ tmp = fd_close(fsp);
+ status = ntstatus_keeperror(status, tmp);
+
+ /* check for magic scripts */
+ if (close_type == NORMAL_CLOSE) {
+ tmp = check_magic(fsp);
+ status = ntstatus_keeperror(status, tmp);
+ }
+
+ DEBUG(2,("%s closed file %s (numopen=%d) %s\n",
+ conn->session_info->unix_info->unix_name, fsp_str_dbg(fsp),
+ conn->num_files_open - 1,
+ nt_errstr(status) ));
+
+ return status;
+}
+/****************************************************************************
+ Function used by reply_rmdir to delete an entire directory
+ tree recursively. Return True on ok, False on fail.
+****************************************************************************/
+
+NTSTATUS recursive_rmdir(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_dname)
+{
+ const char *dname = NULL;
+ char *talloced = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ struct files_struct *dirfsp = NULL;
+ int retval;
+ NTSTATUS status = NT_STATUS_OK;
+
+ SMB_ASSERT(!is_ntfs_stream_smb_fname(smb_dname));
+
+ status = OpenDir(talloc_tos(),
+ conn,
+ smb_dname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+
+ while ((dname = ReadDirName(dir_hnd, &talloced))) {
+ struct smb_filename *atname = NULL;
+ struct smb_filename *smb_dname_full = NULL;
+ char *fullname = NULL;
+ bool do_break = true;
+ int unlink_flags = 0;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ /* Construct the full name. */
+ fullname = talloc_asprintf(ctx,
+ "%s/%s",
+ smb_dname->base_name,
+ dname);
+ if (!fullname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (smb_dname_full == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ if (SMB_VFS_LSTAT(conn, smb_dname_full) != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ if (smb_dname_full->st.st_ex_mode & S_IFDIR) {
+ status = recursive_rmdir(ctx, conn, smb_dname_full);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+ unlink_flags = AT_REMOVEDIR;
+ }
+
+ status = synthetic_pathref(talloc_tos(),
+ dirfsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname_full->twrp,
+ smb_dname_full->flags,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+
+ if (!is_visible_fsp(atname->fsp)) {
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(atname);
+ continue;
+ }
+
+ retval = SMB_VFS_UNLINKAT(conn,
+ dirfsp,
+ atname,
+ unlink_flags);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ /* Successful iteration. */
+ do_break = false;
+
+ err_break:
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(atname);
+ if (do_break) {
+ break;
+ }
+ }
+ TALLOC_FREE(dir_hnd);
+ return status;
+}
+
+/****************************************************************************
+ The internals of the rmdir code - called elsewhere.
+****************************************************************************/
+
+static NTSTATUS rmdir_internals(TALLOC_CTX *ctx, struct files_struct *fsp)
+{
+ struct connection_struct *conn = fsp->conn;
+ struct smb_filename *smb_dname = fsp->fsp_name;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ struct files_struct *dirfsp = NULL;
+ int unlink_flags = 0;
+ NTSTATUS status;
+ int ret;
+
+ SMB_ASSERT(!is_ntfs_stream_smb_fname(smb_dname));
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ fsp->fsp_name,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Todo: use SMB_VFS_STATX() once it's available.
+ */
+
+ /* Might be a symlink. */
+ ret = SMB_VFS_LSTAT(conn, smb_dname);
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ if (S_ISLNK(smb_dname->st.st_ex_mode)) {
+ /* Is what it points to a directory ? */
+ ret = SMB_VFS_STAT(conn, smb_dname);
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+ if (!(S_ISDIR(smb_dname->st.st_ex_mode))) {
+ TALLOC_FREE(parent_fname);
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+ } else {
+ unlink_flags = AT_REMOVEDIR;
+ }
+
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ unlink_flags);
+ if (ret == 0) {
+ TALLOC_FREE(parent_fname);
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ smb_dname->base_name);
+ return NT_STATUS_OK;
+ }
+
+ if (!((errno == ENOTEMPTY) || (errno == EEXIST))) {
+ DEBUG(3,("rmdir_internals: couldn't remove directory %s : "
+ "%s\n", smb_fname_str_dbg(smb_dname),
+ strerror(errno)));
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * Here we know the initial directory unlink failed with
+ * ENOTEMPTY or EEXIST so we know there are objects within.
+ * If we don't have permission to delete files non
+ * visible to the client just fail the directory delete.
+ */
+
+ if (!lp_delete_veto_files(SNUM(conn))) {
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ /*
+ * Check to see if the only thing in this directory are
+ * files non-visible to the client. If not, fail the delete.
+ */
+
+ status = OpenDir(talloc_tos(),
+ conn,
+ smb_dname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Note, we deliberately squash the error here
+ * to avoid leaking information about what we
+ * can't delete.
+ */
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ dirfsp = dir_hnd_fetch_fsp(dir_hnd);
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *smb_dname_full = NULL;
+ struct smb_filename *direntry_fname = NULL;
+ char *fullname = NULL;
+ int retval;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ if (IS_VETO_PATH(conn, dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ fullname = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ smb_dname->base_name,
+ dname);
+
+ if (fullname == NULL) {
+ TALLOC_FREE(talloced);
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (smb_dname_full == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ retval = SMB_VFS_LSTAT(conn, smb_dname_full);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ goto err;
+ }
+
+ if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+ /* Could it be an msdfs link ? */
+ if (lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(conn))) {
+ struct smb_filename *smb_atname;
+ smb_atname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags);
+ if (smb_atname == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+ if (is_msdfs_link(fsp, smb_atname)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(smb_atname);
+ DBG_DEBUG("got msdfs link name %s "
+ "- can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+ TALLOC_FREE(smb_atname);
+ }
+
+ /* Not a DFS link - could it be a dangling symlink ? */
+ retval = SMB_VFS_STAT(conn, smb_dname_full);
+ if (retval == -1 && (errno == ENOENT || errno == ELOOP)) {
+ /*
+ * Dangling symlink.
+ * Allow delete as "delete veto files = yes"
+ */
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ continue;
+ }
+
+ DBG_DEBUG("got symlink name %s - "
+ "can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ /* Not a symlink, get a pathref. */
+ status = synthetic_pathref(talloc_tos(),
+ dirfsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname->twrp,
+ smb_dname->flags,
+ &direntry_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ goto err;
+ }
+
+ if (!is_visible_fsp(direntry_fname->fsp)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+ continue;
+ }
+
+ /*
+ * We found a client visible name.
+ * We cannot delete this directory.
+ */
+ DBG_DEBUG("got name %s - "
+ "can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ goto err;
+ }
+
+ /* Do a recursive delete. */
+ RewindDir(dir_hnd);
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *direntry_fname = NULL;
+ struct smb_filename *smb_dname_full = NULL;
+ char *fullname = NULL;
+ bool do_break = true;
+ int retval;
+
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ fullname = talloc_asprintf(ctx,
+ "%s/%s",
+ smb_dname->base_name,
+ dname);
+
+ if (fullname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (smb_dname_full == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+
+ /*
+ * Todo: use SMB_VFS_STATX() once that's available.
+ */
+
+ retval = SMB_VFS_LSTAT(conn, smb_dname_full);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ /*
+ * We are only dealing with VETO'ed objects
+ * here. If it's a symlink, just delete the
+ * link without caring what it is pointing
+ * to.
+ */
+ if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+ direntry_fname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname->twrp,
+ smb_dname->flags);
+ if (direntry_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_break;
+ }
+ } else {
+ status = synthetic_pathref(talloc_tos(),
+ dirfsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ smb_dname->twrp,
+ smb_dname->flags,
+ &direntry_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+
+ if (!is_visible_fsp(direntry_fname->fsp)) {
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(direntry_fname);
+ continue;
+ }
+ }
+
+ unlink_flags = 0;
+
+ if (smb_dname_full->st.st_ex_mode & S_IFDIR) {
+ status = recursive_rmdir(ctx, conn, smb_dname_full);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_break;
+ }
+ unlink_flags = AT_REMOVEDIR;
+ }
+
+ retval = SMB_VFS_UNLINKAT(conn,
+ dirfsp,
+ direntry_fname,
+ unlink_flags);
+ if (retval != 0) {
+ status = map_nt_error_from_unix(errno);
+ goto err_break;
+ }
+
+ /* Successful iteration. */
+ do_break = false;
+
+ err_break:
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(direntry_fname);
+ if (do_break) {
+ break;
+ }
+ }
+
+ /* If we get here, we know NT_STATUS_IS_OK(status) */
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+ /* Retry the rmdir */
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ AT_REMOVEDIR);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ err:
+
+ TALLOC_FREE(dir_hnd);
+ TALLOC_FREE(parent_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("couldn't remove directory %s : "
+ "%s\n", smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ return status;
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_REMOVED,
+ FILE_NOTIFY_CHANGE_DIR_NAME,
+ smb_dname->base_name);
+
+ return status;
+}
+
+/****************************************************************************
+ Close a directory opened by an NT SMB call.
+****************************************************************************/
+
+static NTSTATUS close_directory(struct smb_request *req, files_struct *fsp,
+ enum file_close_type close_type)
+{
+ connection_struct *conn = fsp->conn;
+ struct close_share_mode_lock_state lck_state = {};
+ bool changed_user = false;
+ NTSTATUS status = NT_STATUS_OK;
+ NTSTATUS status1 = NT_STATUS_OK;
+ NTSTATUS notify_status;
+ NTSTATUS ulstatus;
+
+ SMB_ASSERT(fsp->fsp_flags.is_fsa);
+
+ if (fsp->conn->sconn->using_smb2) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ } else {
+ notify_status = NT_STATUS_OK;
+ }
+
+ assert_no_pending_aio(fsp, close_type);
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a directory also.
+ */
+
+ lck_state = (struct close_share_mode_lock_state) {
+ .fsp = fsp,
+ .object_type = "directory",
+ .close_type = close_type,
+ };
+
+ status = share_mode_entry_prepare_lock_del(&lck_state.prepare_state,
+ fsp->file_id,
+ close_share_mode_lock_prepare,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_del() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * We don't have directory leases yet, so assert it in order
+ * to skip release_file_oplock().
+ */
+ SMB_ASSERT(fsp->oplock_type == NO_OPLOCK);
+
+ /*
+ * NT can set delete_on_close of the last open
+ * reference to a file.
+ */
+
+ if (!lck_state.delete_object) {
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /*
+ * Ok, we have to delete the directory
+ */
+ lck_state.cleanup_fn = close_share_mode_lock_cleanup;
+
+ if (lck_state.got_tokens &&
+ !unix_token_equal(lck_state.del_token, get_current_utok(conn)))
+ {
+ /* Become the user who requested the delete. */
+
+ DBG_INFO("dir %s. Change user to uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)lck_state.del_token->uid);
+
+ if (!push_sec_ctx()) {
+ smb_panic("close_directory: failed to push sec_ctx.\n");
+ }
+
+ set_sec_ctx(lck_state.del_token->uid,
+ lck_state.del_token->gid,
+ lck_state.del_token->ngroups,
+ lck_state.del_token->groups,
+ lck_state.del_nt_token);
+
+ changed_user = true;
+ }
+
+ if ((fsp->conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && !is_ntfs_stream_smb_fname(fsp->fsp_name)) {
+
+ status = delete_all_streams(fsp->conn, fsp->fsp_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("delete_all_streams failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ status = rmdir_internals(talloc_tos(), fsp);
+
+ DEBUG(5,("close_directory: %s. Delete on close was set - "
+ "deleting directory returned %s.\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+
+ /*
+ * Ensure we remove any change notify requests that would
+ * now fail as the directory has been deleted.
+ */
+
+ if (NT_STATUS_IS_OK(status)) {
+ notify_status = NT_STATUS_DELETE_PENDING;
+ }
+
+done:
+ if (changed_user) {
+ /* unbecome user. */
+ pop_sec_ctx();
+ }
+
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ remove_pending_change_notify_requests_by_fid(fsp, notify_status);
+
+ status1 = fd_close(fsp);
+
+ if (!NT_STATUS_IS_OK(status1)) {
+ DEBUG(0, ("Could not close dir! fname=%s, fd=%d, err=%d=%s\n",
+ fsp_str_dbg(fsp), fsp_get_pathref_fd(fsp), errno,
+ strerror(errno)));
+ }
+
+ if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_OK(status1)) {
+ status = status1;
+ }
+ return status;
+}
+
+/****************************************************************************
+ Rundown all SMB-related dependencies of a files struct
+****************************************************************************/
+
+NTSTATUS close_file_smb(struct smb_request *req,
+ struct files_struct *fsp,
+ enum file_close_type close_type)
+{
+ NTSTATUS status;
+
+ /*
+ * This fsp can never be an internal dirfsp. They must
+ * be explicitly closed by TALLOC_FREE of the dir handle.
+ */
+ SMB_ASSERT(!fsp->fsp_flags.is_dirfsp);
+
+ /*
+ * Never call directly on a base fsp
+ */
+ SMB_ASSERT(fsp->stream_fsp == NULL);
+
+ if (fsp->fake_file_handle != NULL) {
+ /*
+ * Named pipes are opened as fake files and
+ * can have pending aio requests. Ensure
+ * we clear out all pending aio on force
+ * shutdown of named pipes also.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15423
+ */
+ assert_no_pending_aio(fsp, close_type);
+ status = close_fake_file(req, fsp);
+ } else if (fsp->print_file != NULL) {
+ /* FIXME: return spool errors */
+ print_spool_end(fsp, close_type);
+ fd_close(fsp);
+ status = NT_STATUS_OK;
+ } else if (!fsp->fsp_flags.is_fsa) {
+ if (close_type == NORMAL_CLOSE) {
+ DBG_ERR("unexpected NORMAL_CLOSE for [%s] "
+ "is_fsa[%u] is_pathref[%u] is_directory[%u]\n",
+ fsp_str_dbg(fsp),
+ fsp->fsp_flags.is_fsa,
+ fsp->fsp_flags.is_pathref,
+ fsp->fsp_flags.is_directory);
+ }
+ SMB_ASSERT(close_type != NORMAL_CLOSE);
+ fd_close(fsp);
+ status = NT_STATUS_OK;
+ } else if (fsp->fsp_flags.is_directory) {
+ status = close_directory(req, fsp, close_type);
+ } else {
+ status = close_normal_file(req, fsp, close_type);
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * fsp was a stream, its base_fsp can't be a stream
+ * as well
+ */
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp->base_fsp));
+
+ /*
+ * There's a 1:1 relationship between fsp and a base_fsp
+ */
+ SMB_ASSERT(fsp->base_fsp->stream_fsp == fsp);
+
+ /*
+ * Make base_fsp look standalone now
+ */
+ fsp->base_fsp->stream_fsp = NULL;
+
+ close_file_free(req, &fsp->base_fsp, close_type);
+ }
+
+ fsp_unbind_smb(req, fsp);
+
+ return status;
+}
+
+NTSTATUS close_file_free(struct smb_request *req,
+ struct files_struct **_fsp,
+ enum file_close_type close_type)
+{
+ struct files_struct *fsp = *_fsp;
+ NTSTATUS status;
+
+ status = close_file_smb(req, fsp, close_type);
+
+ file_free(req, fsp);
+ *_fsp = NULL;
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with an (authorized) message to close a file given the share mode
+ entry.
+****************************************************************************/
+
+void msg_close_file(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ files_struct *fsp = NULL;
+ struct file_id id;
+ struct share_mode_entry e;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ message_to_share_mode_entry(&id, &e, (char *)data->data);
+
+ if(DEBUGLVL(10)) {
+ char *sm_str = share_mode_str(NULL, 0, &id, &e);
+ if (!sm_str) {
+ smb_panic("talloc failed");
+ }
+ DEBUG(10,("msg_close_file: got request to close share mode "
+ "entry %s\n", sm_str));
+ TALLOC_FREE(sm_str);
+ }
+
+ fsp = file_find_dif(sconn, id, e.share_file_id);
+ if (!fsp) {
+ DEBUG(10,("msg_close_file: failed to find file.\n"));
+ return;
+ }
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+}
diff --git a/source3/smbd/conn.c b/source3/smbd/conn.c
new file mode 100644
index 0000000..27a4d27
--- /dev/null
+++ b/source3/smbd/conn.c
@@ -0,0 +1,267 @@
+/*
+ Unix SMB/CIFS implementation.
+ Manage connections_struct structures
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util/bitmap.h"
+
+static void conn_free_internal(connection_struct *conn);
+
+/****************************************************************************
+ * Remove a conn struct from conn->sconn->connections
+ * if not already done.
+****************************************************************************/
+
+static int conn_struct_destructor(connection_struct *conn)
+{
+ if (conn->sconn != NULL) {
+ DLIST_REMOVE(conn->sconn->connections, conn);
+ SMB_ASSERT(conn->sconn->num_connections > 0);
+ conn->sconn->num_connections--;
+ conn->sconn = NULL;
+ }
+ conn_free_internal(conn);
+ return 0;
+}
+
+/****************************************************************************
+ Return the number of open connections.
+****************************************************************************/
+
+int conn_num_open(struct smbd_server_connection *sconn)
+{
+ return sconn->num_connections;
+}
+
+/****************************************************************************
+ Check if a snum is in use.
+****************************************************************************/
+
+bool conn_snum_used(struct smbd_server_connection *sconn,
+ int snum)
+{
+ struct connection_struct *conn;
+
+ for (conn=sconn->connections; conn; conn=conn->next) {
+ if (conn->params->service == snum) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ Find first available connection slot, starting from a random position.
+ The randomisation stops problems with the server dying and clients
+ thinking the server is still available.
+****************************************************************************/
+
+connection_struct *conn_new(struct smbd_server_connection *sconn)
+{
+ connection_struct *conn = NULL;
+
+ conn = talloc_zero(NULL, connection_struct);
+ if (conn == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return NULL;
+ }
+ conn->params = talloc(conn, struct share_params);
+ if (conn->params == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->vuid_cache = talloc_zero(conn, struct vuid_cache);
+ if (conn->vuid_cache == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->connectpath = talloc_strdup(conn, "");
+ if (conn->connectpath == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->cwd_fsp = talloc_zero(conn, struct files_struct);
+ if (conn->cwd_fsp == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->cwd_fsp->fsp_name = synthetic_smb_fname(conn->cwd_fsp,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (conn->cwd_fsp->fsp_name == NULL) {
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->cwd_fsp->fh = fd_handle_create(conn->cwd_fsp);
+ if (conn->cwd_fsp->fh == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ TALLOC_FREE(conn);
+ return NULL;
+ }
+ conn->sconn = sconn;
+ conn->force_group_gid = (gid_t)-1;
+ fsp_set_fd(conn->cwd_fsp, -1);
+ conn->cwd_fsp->fnum = FNUM_FIELD_INVALID;
+ conn->cwd_fsp->conn = conn;
+
+ DLIST_ADD(sconn->connections, conn);
+ sconn->num_connections++;
+
+ /*
+ * Catches the case where someone forgets to call
+ * conn_free().
+ */
+ talloc_set_destructor(conn, conn_struct_destructor);
+ return conn;
+}
+
+/****************************************************************************
+ Clear a vuid out of the connection's vuid cache
+****************************************************************************/
+
+static void conn_clear_vuid_cache(connection_struct *conn, uint64_t vuid)
+{
+ int i;
+
+ for (i=0; i<VUID_CACHE_SIZE; i++) {
+ struct vuid_cache_entry *ent;
+
+ ent = &conn->vuid_cache->array[i];
+
+ if (ent->vuid == vuid) {
+ ent->vuid = UID_FIELD_INVALID;
+ /*
+ * We need to keep conn->session_info around
+ * if it's equal to ent->session_info as a SMBulogoff
+ * is often followed by a SMBtdis (with an invalid
+ * vuid). The debug code (or regular code in
+ * vfs_full_audit) wants to refer to the
+ * conn->session_info pointer to print debug
+ * statements. Theoretically this is a bug,
+ * as once the vuid is gone the session_info
+ * on the conn struct isn't valid any more,
+ * but there's enough code that assumes
+ * conn->session_info is never null that
+ * it's easier to hold onto the old pointer
+ * until we get a new sessionsetupX.
+ * As everything is hung off the
+ * conn pointer as a talloc context we're not
+ * leaking memory here. See bug #6315. JRA.
+ */
+ if (conn->session_info == ent->session_info) {
+ ent->session_info = NULL;
+ } else {
+ TALLOC_FREE(ent->session_info);
+ }
+ ent->read_only = False;
+ ent->share_access = 0;
+ }
+ }
+}
+
+/****************************************************************************
+ Clear a vuid out of the validity cache, and as the 'owner' of a connection.
+
+ Called from invalidate_vuid()
+****************************************************************************/
+
+void conn_clear_vuid_caches(struct smbd_server_connection *sconn, uint64_t vuid)
+{
+ connection_struct *conn;
+
+ for (conn=sconn->connections; conn;conn=conn->next) {
+ if (conn->vuid == vuid) {
+ conn->vuid = UID_FIELD_INVALID;
+ }
+ conn_clear_vuid_cache(conn, vuid);
+ }
+}
+
+/****************************************************************************
+ Free a conn structure - internal part.
+****************************************************************************/
+
+static void conn_free_internal(connection_struct *conn)
+{
+ vfs_handle_struct *handle = NULL, *thandle = NULL;
+ struct trans_state *state = NULL;
+
+ /* Free vfs_connection_struct */
+ handle = conn->vfs_handles;
+ while(handle) {
+ thandle = handle->next;
+ DLIST_REMOVE(conn->vfs_handles, handle);
+ if (handle->free_data)
+ handle->free_data(&handle->data);
+ handle = thandle;
+ }
+
+ /* Free any pending transactions stored on this conn. */
+ for (state = conn->pending_trans; state; state = state->next) {
+ /* state->setup is a talloc child of state. */
+ SAFE_FREE(state->param);
+ SAFE_FREE(state->data);
+ }
+
+ free_namearray(conn->veto_list);
+ free_namearray(conn->hide_list);
+ free_namearray(conn->veto_oplock_list);
+ free_namearray(conn->aio_write_behind_list);
+
+ ZERO_STRUCTP(conn);
+}
+
+/****************************************************************************
+ Free a conn structure.
+****************************************************************************/
+
+void conn_free(connection_struct *conn)
+{
+ TALLOC_FREE(conn);
+}
+
+/*
+ * Correctly initialize a share with case options.
+ */
+void conn_setup_case_options(connection_struct *conn)
+{
+ int snum = conn->params->service;
+
+ if (lp_case_sensitive(snum) == Auto) {
+ /* We will be setting this per packet. Set to be case
+ * insensitive for now. */
+ conn->case_sensitive = false;
+ } else {
+ conn->case_sensitive = (bool)lp_case_sensitive(snum);
+ }
+
+ conn->case_preserve = lp_preserve_case(snum);
+ conn->short_case_preserve = lp_short_preserve_case(snum);
+}
diff --git a/source3/smbd/conn_idle.c b/source3/smbd/conn_idle.c
new file mode 100644
index 0000000..870b2b7
--- /dev/null
+++ b/source3/smbd/conn_idle.c
@@ -0,0 +1,277 @@
+/*
+ Unix SMB/CIFS implementation.
+ Manage connections_struct structures
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "rpc_server/rpc_pipes.h"
+#include "lib/util/tevent_ntstatus.h"
+
+/****************************************************************************
+ Update last used timestamps.
+****************************************************************************/
+
+static void conn_lastused_update(struct smbd_server_connection *sconn,time_t t)
+{
+ struct connection_struct *conn;
+
+ for (conn=sconn->connections; conn; conn=conn->next) {
+ /* Update if connection wasn't idle. */
+ if (conn->lastused != conn->lastused_count) {
+ conn->lastused = t;
+ conn->lastused_count = t;
+ }
+ }
+}
+
+/****************************************************************************
+ Idle inactive connections.
+****************************************************************************/
+
+bool conn_idle_all(struct smbd_server_connection *sconn, time_t t)
+{
+ int deadtime = lp_deadtime()*60;
+ struct connection_struct *conn;
+
+ conn_lastused_update(sconn, t);
+
+ if (deadtime <= 0) {
+ return false;
+ }
+
+ for (conn=sconn->connections;conn;conn=conn->next) {
+ time_t age = t - conn->lastused;
+
+ if (conn->num_files_open > 0 || age < deadtime) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Forcibly unmount a share - async
+ All instances of the parameter 'sharename' share are unmounted.
+ The special sharename '*' forces unmount of all shares.
+****************************************************************************/
+
+static struct tevent_req *conn_force_tdis_send(connection_struct *conn);
+static void conn_force_tdis_done(struct tevent_req *req);
+
+void conn_force_tdis(
+ struct smbd_server_connection *sconn,
+ bool (*check_fn)(struct connection_struct *conn,
+ void *private_data),
+ void *private_data)
+{
+ connection_struct *conn;
+
+ /* SMB1 and SMB 2*/
+ for (conn = sconn->connections; conn; conn = conn->next) {
+ struct smbXsrv_tcon *tcon;
+ bool do_close = false;
+ struct tevent_req *req;
+
+ if (conn->tcon == NULL) {
+ continue;
+ }
+ tcon = conn->tcon;
+
+ if (!NT_STATUS_IS_OK(tcon->status)) {
+ /* In the process of already being disconnected. */
+ continue;
+ }
+
+ do_close = check_fn(conn, private_data);
+ if (!do_close) {
+ continue;
+ }
+
+ req = conn_force_tdis_send(conn);
+ if (req == NULL) {
+ DBG_WARNING("talloc_fail forcing async close of "
+ "share '%s'\n",
+ tcon->global->share_name);
+ continue;
+ }
+
+ DBG_WARNING("Forcing close of "
+ "share '%s' (wire_id=0x%08x)\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id);
+
+ tevent_req_set_callback(req, conn_force_tdis_done, conn);
+ }
+}
+
+struct conn_force_tdis_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void conn_force_tdis_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *conn_force_tdis_send(connection_struct *conn)
+{
+ struct tevent_req *req;
+ struct conn_force_tdis_state *state;
+ struct tevent_req *subreq;
+ files_struct *fsp;
+
+ /* Create this off the NULL context. We must clean up on return. */
+ req = tevent_req_create(NULL, &state,
+ struct conn_force_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "conn_force_tdis_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Make sure that no new request will be able to use this tcon.
+ * This ensures that once all outstanding fsp->aio_requests
+ * on this tcon are done, we are safe to close it.
+ */
+ conn->tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ for (fsp = conn->sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->conn != conn) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it. Not strictly needed, but
+ * doesn't hurt to flag it as closing.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ tevent_req_set_callback(subreq, conn_force_tdis_wait_done, req);
+ return req;
+}
+
+static void conn_force_tdis_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS conn_force_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void conn_force_tdis_done(struct tevent_req *req)
+{
+ connection_struct *conn = tevent_req_callback_data(
+ req, connection_struct);
+ NTSTATUS status;
+ uint64_t vuid = UID_FIELD_INVALID;
+ struct smbXsrv_tcon *tcon = conn->tcon;
+ struct smbd_server_connection *sconn = conn->sconn;
+
+ status = conn_force_tdis_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("conn_force_tdis_recv of share '%s' "
+ "(wire_id=0x%08x) failed: %s\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id,
+ nt_errstr(status));
+ return;
+ }
+
+ if (conn->sconn->using_smb2) {
+ vuid = conn->vuid;
+ }
+
+ DBG_WARNING("Closing "
+ "share '%s' (wire_id=0x%08x)\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id);
+
+ conn = NULL;
+ status = smbXsrv_tcon_disconnect(tcon, vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_tcon_disconnect() of share '%s' "
+ "(wire_id=0x%08x) failed: %s\n",
+ tcon->global->share_name,
+ tcon->global->tcon_wire_id,
+ nt_errstr(status));
+ return;
+ }
+
+ TALLOC_FREE(tcon);
+
+ /*
+ * As we've been awoken, we may have changed
+ * uid in the meantime. Ensure we're still root.
+ */
+ change_to_root_user();
+ /*
+ * Use 'false' in the last parameter (test) to force
+ * a full reload of services. Prevents
+ * reload_services caching the fact it's
+ * been called multiple times in a row.
+ * See BUG: https://bugzilla.samba.org/show_bug.cgi?id=14604
+ * for details.
+ */
+ reload_services(sconn, conn_snum_used, false);
+}
diff --git a/source3/smbd/conn_msg.c b/source3/smbd/conn_msg.c
new file mode 100644
index 0000000..9435d38
--- /dev/null
+++ b/source3/smbd/conn_msg.c
@@ -0,0 +1,148 @@
+/*
+ Unix SMB/CIFS implementation.
+ Manage connections_struct structures
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+/****************************************************************************
+ Receive a smbcontrol message to forcibly unmount a share.
+ The message contains just a share name and all instances of that
+ share are unmounted.
+ The special sharename '*' forces unmount of all shares.
+****************************************************************************/
+
+struct force_tdis_state {
+ const char *sharename;
+};
+
+static bool force_tdis_check(
+ struct connection_struct *conn,
+ void *private_data)
+{
+ struct force_tdis_state *state = private_data;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *servicename = NULL;
+ bool do_close;
+
+ if (strcmp(state->sharename, "*") == 0) {
+ DBG_WARNING("Forcing close of all shares\n");
+ return true;
+ }
+
+ servicename = lp_servicename(talloc_tos(), lp_sub, SNUM(conn));
+ do_close = strequal(servicename, state->sharename);
+
+ TALLOC_FREE(servicename);
+
+ return do_close;
+}
+
+void msg_force_tdis(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct force_tdis_state state = {
+ .sharename = (const char *)data->data,
+ };
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ if ((data->length == 0) || (data->data[data->length-1] != 0)) {
+ DBG_WARNING("Ignoring invalid sharename\n");
+ return;
+ }
+
+ conn_force_tdis(sconn, force_tdis_check, &state);
+}
+
+static bool force_tdis_denied_check(
+ struct connection_struct *conn,
+ void *private_data)
+{
+ bool do_close;
+ uint32_t share_access;
+ bool read_only;
+ NTSTATUS status;
+
+ do_close = force_tdis_check(conn, private_data);
+ if (!do_close) {
+ return false;
+ }
+
+ status = check_user_share_access(
+ conn,
+ conn->session_info,
+ &share_access,
+ &read_only);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("check_user_share_access returned %s\n",
+ nt_errstr(status));
+ return true; /* close the share */
+ }
+
+ if (conn->share_access != share_access) {
+ DBG_DEBUG("share_access changed from %"PRIx32" to %"PRIx32"\n",
+ conn->share_access, share_access);
+ return true; /* close the share */
+ }
+
+ if (conn->read_only != read_only) {
+ DBG_DEBUG("read_only changed from %s to %s\n",
+ conn->read_only ? "true" : "false",
+ read_only ? "true" : "false");
+ return true; /* close the share */
+ }
+
+ /*
+ * all still ok, keep the connection open
+ */
+ return false;
+}
+
+void msg_force_tdis_denied(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct force_tdis_state state = {
+ .sharename = (const char *)data->data,
+ };
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ if ((data->length == 0) || (data->data[data->length-1] != 0)) {
+ DBG_WARNING("Ignoring invalid sharename\n");
+ return;
+ }
+
+ change_to_root_user();
+ reload_services(sconn, conn_snum_used, false);
+
+ conn_force_tdis(sconn, force_tdis_denied_check, &state);
+}
diff --git a/source3/smbd/connection.c b/source3/smbd/connection.c
new file mode 100644
index 0000000..8dea205
--- /dev/null
+++ b/source3/smbd/connection.c
@@ -0,0 +1,97 @@
+/*
+ Unix SMB/CIFS implementation.
+ connection claim routines
+ Copyright (C) Andrew Tridgell 1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "messages.h"
+
+struct count_stat {
+ int curr_connections;
+ const char *name;
+ bool verify;
+};
+
+/****************************************************************************
+ Count the entries belonging to a service in the connection db.
+****************************************************************************/
+
+static int count_fn(struct smbXsrv_tcon_global0 *tcon,
+ void *udp)
+{
+ struct count_stat *cs = (struct count_stat *)udp;
+
+ if (cs->verify && !process_exists(tcon->server_id)) {
+ return 0;
+ }
+
+ if (strequal(tcon->share_name, cs->name)) {
+ cs->curr_connections++;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Claim an entry in the connections database.
+****************************************************************************/
+
+int count_current_connections(const char *sharename, bool verify)
+{
+ struct count_stat cs;
+ NTSTATUS status;
+
+ cs.curr_connections = 0;
+ cs.name = sharename;
+ cs.verify = verify;
+
+ /*
+ * This has a race condition, but locking the chain before hand is worse
+ * as it leads to deadlock.
+ */
+
+ status = smbXsrv_tcon_global_traverse(count_fn, &cs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("count_current_connections: traverse of "
+ "smbXsrv_tcon_global.tdb failed - %s\n",
+ nt_errstr(status)));
+ return 0;
+ }
+
+ return cs.curr_connections;
+}
+
+bool connections_snum_used(struct smbd_server_connection *unused, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int active;
+
+ active = count_current_connections(lp_servicename(talloc_tos(), lp_sub, snum),
+ true);
+ if (active > 0) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/source3/smbd/dfree.c b/source3/smbd/dfree.c
new file mode 100644
index 0000000..89dc112
--- /dev/null
+++ b/source3/smbd/dfree.c
@@ -0,0 +1,296 @@
+/*
+ Unix SMB/CIFS implementation.
+ functions to calculate the free disk space
+ Copyright (C) Andrew Tridgell 1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util_file.h"
+#include "lib/util/memcache.h"
+
+/****************************************************************************
+ Normalise for DOS usage.
+****************************************************************************/
+
+static void disk_norm(uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ /* check if the disk is beyond the max disk size */
+ uint64_t maxdisksize = lp_max_disk_size();
+ if (maxdisksize) {
+ /* convert to blocks - and don't overflow */
+ maxdisksize = ((maxdisksize*1024)/(*bsize))*1024;
+ if (*dsize > maxdisksize) {
+ *dsize = maxdisksize;
+ }
+ if (*dfree > maxdisksize) {
+ *dfree = maxdisksize - 1;
+ }
+ /* the -1 should stop applications getting div by 0
+ errors */
+ }
+}
+
+
+
+/****************************************************************************
+ Return number of 1K blocks available on a path and total number.
+****************************************************************************/
+
+static uint64_t sys_disk_free(connection_struct *conn,
+ struct smb_filename *fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint64_t dfree_retval;
+ uint64_t dfree_q = 0;
+ uint64_t bsize_q = 0;
+ uint64_t dsize_q = 0;
+ const char *dfree_command;
+ static bool dfree_broken = false;
+ char *path = fname->base_name;
+
+ (*dfree) = (*dsize) = 0;
+ (*bsize) = 512;
+
+ /*
+ * If external disk calculation specified, use it.
+ */
+
+ dfree_command = lp_dfree_command(talloc_tos(), lp_sub, SNUM(conn));
+ if (dfree_command && *dfree_command) {
+ const char *p;
+ char **lines = NULL;
+ char **argl = NULL;
+
+ argl = str_list_make_empty(talloc_tos());
+ str_list_add_printf(&argl, "%s", dfree_command);
+ str_list_add_printf(&argl, "%s", path);
+ if (argl == NULL) {
+ return (uint64_t)-1;
+ }
+
+ DBG_NOTICE("Running command '%s %s'\n",
+ dfree_command,
+ path);
+
+ lines = file_lines_ploadv(talloc_tos(), argl, NULL);
+
+ TALLOC_FREE(argl);
+
+ if (lines != NULL) {
+ char *line = lines[0];
+
+ DEBUG (3, ("Read input from dfree, \"%s\"\n", line));
+
+ *dsize = STR_TO_SMB_BIG_UINT(line, &p);
+ while (p && *p && isspace(*p))
+ p++;
+ if (p && *p)
+ *dfree = STR_TO_SMB_BIG_UINT(p, &p);
+ while (p && *p && isspace(*p))
+ p++;
+ if (p && *p)
+ *bsize = STR_TO_SMB_BIG_UINT(p, NULL);
+ else
+ *bsize = 1024;
+ TALLOC_FREE(lines);
+ DEBUG (3, ("Parsed output of dfree, dsize=%u, dfree=%u, bsize=%u\n",
+ (unsigned int)*dsize, (unsigned int)*dfree, (unsigned int)*bsize));
+
+ if (!*dsize)
+ *dsize = 2048;
+ if (!*dfree)
+ *dfree = 1024;
+
+ goto dfree_done;
+ }
+ DBG_ERR("file_lines_load() failed for "
+ "command '%s %s'. Error was : %s\n",
+ dfree_command, path, strerror(errno));
+ }
+
+ if (SMB_VFS_DISK_FREE(conn, fname, bsize, dfree, dsize) ==
+ (uint64_t)-1) {
+ DBG_ERR("VFS disk_free failed. Error was : %s\n",
+ strerror(errno));
+ return (uint64_t)-1;
+ }
+
+ if (disk_quotas(conn, fname, &bsize_q, &dfree_q, &dsize_q)) {
+ uint64_t min_bsize = MIN(*bsize, bsize_q);
+
+ (*dfree) = (*dfree) * (*bsize) / min_bsize;
+ (*dsize) = (*dsize) * (*bsize) / min_bsize;
+ dfree_q = dfree_q * bsize_q / min_bsize;
+ dsize_q = dsize_q * bsize_q / min_bsize;
+
+ (*bsize) = min_bsize;
+ (*dfree) = MIN(*dfree,dfree_q);
+ (*dsize) = MIN(*dsize,dsize_q);
+ }
+
+ /* FIXME : Any reason for this assumption ? */
+ if (*bsize < 256) {
+ DEBUG(5,("disk_free:Warning: bsize == %d < 256 . Changing to assumed correct bsize = 512\n",(int)*bsize));
+ *bsize = 512;
+ }
+
+ if ((*dsize)<1) {
+ if (!dfree_broken) {
+ DEBUG(0,("WARNING: dfree is broken on this system\n"));
+ dfree_broken=true;
+ }
+ *dsize = 20*1024*1024/(*bsize);
+ *dfree = MAX(1,*dfree);
+ }
+
+dfree_done:
+ disk_norm(bsize, dfree, dsize);
+
+ if ((*bsize) < 1024) {
+ dfree_retval = (*dfree)/(1024/(*bsize));
+ } else {
+ dfree_retval = ((*bsize)/1024)*(*dfree);
+ }
+
+ return(dfree_retval);
+}
+
+/****************************************************************************
+ Potentially returned cached dfree info.
+
+ Depending on the file system layout and file system features, the free space
+ information can be different for different sub directories underneath a SMB
+ share. Store the cache information in memcache using the query path as the
+ key to accommodate this.
+****************************************************************************/
+
+struct dfree_cached_info {
+ time_t last_dfree_time;
+ uint64_t dfree_ret;
+ uint64_t bsize;
+ uint64_t dfree;
+ uint64_t dsize;
+};
+
+uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ int dfree_cache_time = lp_dfree_cache_time(SNUM(conn));
+ struct dfree_cached_info *dfc = NULL;
+ struct dfree_cached_info dfc_new = { 0 };
+ uint64_t dfree_ret;
+ char tmpbuf[PATH_MAX];
+ char *full_path = NULL;
+ char *to_free = NULL;
+ char *key_path = NULL;
+ size_t len;
+ DATA_BLOB key, value;
+ bool found;
+
+ if (!dfree_cache_time) {
+ return sys_disk_free(conn, fname, bsize, dfree, dsize);
+ }
+
+ len = full_path_tos(conn->connectpath,
+ fname->base_name,
+ tmpbuf,
+ sizeof(tmpbuf),
+ &full_path,
+ &to_free);
+ if (len == -1) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (VALID_STAT(fname->st) && S_ISREG(fname->st.st_ex_mode)) {
+ /*
+ * In case of a file use the parent directory to reduce number
+ * of cache entries.
+ */
+ bool ok;
+
+ ok = parent_dirname(talloc_tos(),
+ full_path,
+ &key_path,
+ NULL);
+ TALLOC_FREE(to_free); /* We're done with full_path */
+
+ if (!ok) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ /*
+ * key_path is always a talloced object.
+ */
+ to_free = key_path;
+ } else {
+ /*
+ * key_path might not be a talloced object; rely on
+ * to_free set from full_path_tos.
+ */
+ key_path = full_path;
+ }
+
+ key = data_blob_const(key_path, strlen(key_path));
+ found = memcache_lookup(smbd_memcache(),
+ DFREE_CACHE,
+ key,
+ &value);
+ dfc = found ? (struct dfree_cached_info *)value.data : NULL;
+
+ if (dfc && (conn->lastused - dfc->last_dfree_time < dfree_cache_time)) {
+ DBG_DEBUG("Returning dfree cache entry for %s\n", key_path);
+ *bsize = dfc->bsize;
+ *dfree = dfc->dfree;
+ *dsize = dfc->dsize;
+ dfree_ret = dfc->dfree_ret;
+ goto out;
+ }
+
+ dfree_ret = sys_disk_free(conn, fname, bsize, dfree, dsize);
+
+ if (dfree_ret == (uint64_t)-1) {
+ /* Don't cache bad data. */
+ goto out;
+ }
+
+ DBG_DEBUG("Creating dfree cache entry for %s\n", key_path);
+ dfc_new.bsize = *bsize;
+ dfc_new.dfree = *dfree;
+ dfc_new.dsize = *dsize;
+ dfc_new.dfree_ret = dfree_ret;
+ dfc_new.last_dfree_time = conn->lastused;
+ memcache_add(smbd_memcache(),
+ DFREE_CACHE,
+ key,
+ data_blob_const(&dfc_new, sizeof(dfc_new)));
+
+out:
+ TALLOC_FREE(to_free);
+ return dfree_ret;
+}
+
+void flush_dfree_cache(void)
+{
+ memcache_flush(smbd_memcache(), DFREE_CACHE);
+}
diff --git a/source3/smbd/dir.c b/source3/smbd/dir.c
new file mode 100644
index 0000000..49c37cb
--- /dev/null
+++ b/source3/smbd/dir.c
@@ -0,0 +1,1504 @@
+/*
+ Unix SMB/CIFS implementation.
+ Directory handling routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "lib/util/bitmap.h"
+#include "../lib/util/memcache.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "lib/util/string_wrappers.h"
+#include "libcli/smb/reparse.h"
+#include "source3/smbd/dir.h"
+
+/*
+ This module implements directory related functions for Samba.
+*/
+
+/* "Special" directory offsets. */
+#define END_OF_DIRECTORY_OFFSET ((long)-1)
+#define START_OF_DIRECTORY_OFFSET ((long)0)
+#define DOT_DOT_DIRECTORY_OFFSET ((long)0x80000000)
+
+/* Make directory handle internals available. */
+
+struct smb_Dir {
+ connection_struct *conn;
+ DIR *dir;
+ struct smb_filename *dir_smb_fname;
+ unsigned int file_number;
+ bool case_sensitive;
+ files_struct *fsp; /* Back pointer to containing fsp, only
+ set from OpenDir_fsp(). */
+};
+
+struct dptr_struct {
+ struct dptr_struct *next, *prev;
+ int dnum;
+ struct smb_Dir *dir_hnd;
+ char *wcard;
+ uint32_t attr;
+ bool has_wild; /* Set to true if the wcard entry has MS wildcard characters in it. */
+ bool did_stat; /* Optimisation for non-wcard searches. */
+ bool priv; /* Directory handle opened with privilege. */
+ uint32_t counter;
+
+ char *last_name_sent; /* for name-based trans2 resume */
+
+ struct {
+ char *fname;
+ struct smb_filename *smb_fname;
+ uint32_t mode;
+ } overflow;
+};
+
+static NTSTATUS OpenDir_fsp(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd);
+
+static int smb_Dir_destructor(struct smb_Dir *dir_hnd);
+
+#define INVALID_DPTR_KEY (-3)
+
+/****************************************************************************
+ Initialise the dir bitmap.
+****************************************************************************/
+
+bool init_dptrs(struct smbd_server_connection *sconn)
+{
+ if (sconn->searches.dptr_bmap) {
+ return true;
+ }
+
+ sconn->searches.dptr_bmap = bitmap_talloc(
+ sconn, MAX_DIRECTORY_HANDLES);
+
+ if (sconn->searches.dptr_bmap == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Get the struct dptr_struct for a dir index.
+****************************************************************************/
+
+static struct dptr_struct *dptr_get(struct smbd_server_connection *sconn,
+ int key)
+{
+ struct dptr_struct *dptr;
+
+ for (dptr = sconn->searches.dirptrs; dptr != NULL; dptr = dptr->next) {
+ if(dptr->dnum != key) {
+ continue;
+ }
+ DLIST_PROMOTE(sconn->searches.dirptrs, dptr);
+ return dptr;
+ }
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir path for a dir index.
+****************************************************************************/
+
+const char *dptr_path(struct smbd_server_connection *sconn, int key)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, key);
+ if (dptr)
+ return(dptr->dir_hnd->dir_smb_fname->base_name);
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir wcard for a dir index.
+****************************************************************************/
+
+const char *dptr_wcard(struct smbd_server_connection *sconn, int key)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, key);
+ if (dptr)
+ return(dptr->wcard);
+ return(NULL);
+}
+
+/****************************************************************************
+ Get the dir attrib for a dir index.
+****************************************************************************/
+
+uint16_t dptr_attr(struct smbd_server_connection *sconn, int key)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, key);
+ if (dptr)
+ return(dptr->attr);
+ return(0);
+}
+
+/****************************************************************************
+ Close all dptrs for a cnum.
+****************************************************************************/
+
+void dptr_closecnum(connection_struct *conn)
+{
+ struct dptr_struct *dptr, *next;
+ struct smbd_server_connection *sconn = conn->sconn;
+
+ if (sconn == NULL) {
+ return;
+ }
+
+ for(dptr = sconn->searches.dirptrs; dptr; dptr = next) {
+ next = dptr->next;
+ if (dptr->dir_hnd->conn == conn) {
+ /*
+ * Need to make a copy, "dptr" will be gone
+ * after close_file_free() returns
+ */
+ struct files_struct *fsp = dptr->dir_hnd->fsp;
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+}
+
+/****************************************************************************
+ Create a new dir ptr. If the flag old_handle is true then we must allocate
+ from the bitmap range 0 - 255 as old SMBsearch directory handles are only
+ one byte long. If old_handle is false we allocate from the range
+ 256 - MAX_DIRECTORY_HANDLES. We bias the number we return by 1 to ensure
+ a directory handle is never zero.
+ wcard must not be zero.
+****************************************************************************/
+
+NTSTATUS dptr_create(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ bool old_handle,
+ const char *wcard,
+ uint32_t attr,
+ struct dptr_struct **dptr_ret)
+{
+ struct smbd_server_connection *sconn = conn->sconn;
+ struct dptr_struct *dptr = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ DBG_INFO("dir=%s\n", fsp_str_dbg(fsp));
+
+ if (sconn == NULL) {
+ DEBUG(0,("dptr_create: called with fake connection_struct\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!wcard) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = check_any_access_fsp(fsp, SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("dptr_create: directory %s "
+ "not open for LIST access\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+ status = OpenDir_fsp(NULL, conn, fsp, wcard, attr, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dptr = talloc_zero(NULL, struct dptr_struct);
+ if(!dptr) {
+ DEBUG(0,("talloc fail in dptr_create.\n"));
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dptr->dir_hnd = dir_hnd;
+ dptr->wcard = talloc_strdup(dptr, wcard);
+ if (!dptr->wcard) {
+ TALLOC_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if ((req != NULL && req->posix_pathnames) || ISDOT(wcard)) {
+ dptr->has_wild = True;
+ } else {
+ dptr->has_wild = ms_has_wild(dptr->wcard);
+ }
+
+ dptr->attr = attr;
+
+ if (sconn->using_smb2) {
+ goto done;
+ }
+
+ if(old_handle) {
+
+ /*
+ * This is an old-style SMBsearch request. Ensure the
+ * value we return will fit in the range 1-255.
+ */
+
+ dptr->dnum = bitmap_find(sconn->searches.dptr_bmap, 0);
+
+ if(dptr->dnum == -1 || dptr->dnum > 254) {
+ DBG_ERR("returned %d: Error - all old "
+ "dirptrs in use ?\n",
+ dptr->dnum);
+ TALLOC_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_TOO_MANY_OPENED_FILES;
+ }
+ } else {
+
+ /*
+ * This is a new-style trans2 request. Allocate from
+ * a range that will return 256 - MAX_DIRECTORY_HANDLES.
+ */
+
+ dptr->dnum = bitmap_find(sconn->searches.dptr_bmap, 255);
+
+ if(dptr->dnum == -1 || dptr->dnum < 255) {
+ DBG_ERR("returned %d: Error - all new "
+ "dirptrs in use ?\n",
+ dptr->dnum);
+ TALLOC_FREE(dptr);
+ TALLOC_FREE(dir_hnd);
+ return NT_STATUS_TOO_MANY_OPENED_FILES;
+ }
+ }
+
+ bitmap_set(sconn->searches.dptr_bmap, dptr->dnum);
+
+ dptr->dnum += 1; /* Always bias the dnum by one - no zero dnums allowed. */
+
+ DLIST_ADD(sconn->searches.dirptrs, dptr);
+
+done:
+ DBG_INFO("creating new dirptr [%d] for path [%s]\n",
+ dptr->dnum, fsp_str_dbg(fsp));
+
+ *dptr_ret = dptr;
+
+ return NT_STATUS_OK;
+}
+
+
+/****************************************************************************
+ Wrapper functions to access the lower level directory handles.
+****************************************************************************/
+
+void dptr_CloseDir(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = NULL;
+
+ if (fsp->dptr == NULL) {
+ return;
+ }
+ sconn = fsp->conn->sconn;
+
+ /*
+ * The destructor for the struct smb_Dir (fsp->dptr->dir_hnd)
+ * now handles all resource deallocation.
+ */
+
+ DBG_INFO("closing dptr key %d\n", fsp->dptr->dnum);
+
+ if (sconn != NULL && !sconn->using_smb2) {
+ DLIST_REMOVE(sconn->searches.dirptrs, fsp->dptr);
+
+ /*
+ * Free the dnum in the bitmap. Remember the dnum value is
+ * always biased by one with respect to the bitmap.
+ */
+
+ if (!bitmap_query(sconn->searches.dptr_bmap,
+ fsp->dptr->dnum - 1))
+ {
+ DBG_ERR("closing dnum = %d and bitmap not set !\n",
+ fsp->dptr->dnum);
+ }
+
+ bitmap_clear(sconn->searches.dptr_bmap, fsp->dptr->dnum - 1);
+ }
+
+ TALLOC_FREE(fsp->dptr->dir_hnd);
+ TALLOC_FREE(fsp->dptr);
+}
+
+void dptr_RewindDir(struct dptr_struct *dptr)
+{
+ RewindDir(dptr->dir_hnd);
+ dptr->did_stat = false;
+ TALLOC_FREE(dptr->overflow.fname);
+ TALLOC_FREE(dptr->overflow.smb_fname);
+}
+
+unsigned int dptr_FileNumber(struct dptr_struct *dptr)
+{
+ return dptr->dir_hnd->file_number;
+}
+
+bool dptr_has_wild(struct dptr_struct *dptr)
+{
+ return dptr->has_wild;
+}
+
+int dptr_dnum(struct dptr_struct *dptr)
+{
+ return dptr->dnum;
+}
+
+bool dptr_get_priv(struct dptr_struct *dptr)
+{
+ return dptr->priv;
+}
+
+void dptr_set_priv(struct dptr_struct *dptr)
+{
+ dptr->priv = true;
+}
+
+bool dptr_case_sensitive(struct dptr_struct *dptr)
+{
+ return dptr->dir_hnd->case_sensitive;
+}
+
+/****************************************************************************
+ Return the next visible file name, skipping veto'd and invisible files.
+****************************************************************************/
+
+char *dptr_ReadDirName(TALLOC_CTX *ctx, struct dptr_struct *dptr)
+{
+ struct stat_ex st = {
+ .st_ex_nlink = 0,
+ };
+ struct smb_Dir *dir_hnd = dptr->dir_hnd;
+ struct files_struct *dir_fsp = dir_hnd->fsp;
+ struct smb_filename *dir_name = dir_fsp->fsp_name;
+ struct smb_filename smb_fname_base;
+ bool retry_scanning = false;
+ int ret;
+ int flags = 0;
+
+ if (dptr->has_wild) {
+ const char *name_temp = NULL;
+ char *talloced = NULL;
+ name_temp = ReadDirName(dir_hnd, &talloced);
+ if (name_temp == NULL) {
+ return NULL;
+ }
+ if (talloced != NULL) {
+ return talloc_move(ctx, &talloced);
+ }
+ return talloc_strdup(ctx, name_temp);
+ }
+
+ if (dptr->did_stat) {
+ /*
+ * No wildcard, this is not a real directory traverse
+ * but a "stat" call behind a query_directory. We've
+ * been here, nothing else to look at.
+ */
+ return NULL;
+ }
+ dptr->did_stat = true;
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname_base = (struct smb_filename){
+ .base_name = dptr->wcard,
+ .flags = dir_name->flags,
+ .twrp = dir_name->twrp,
+ };
+
+ if (dir_name->flags & SMB_FILENAME_POSIX_PATH) {
+ flags |= AT_SYMLINK_NOFOLLOW;
+ }
+
+ ret = SMB_VFS_FSTATAT(
+ dir_hnd->conn, dir_fsp, &smb_fname_base, &st, flags);
+ if (ret == 0) {
+ return talloc_strdup(ctx, dptr->wcard);
+ }
+
+ /*
+ * If we get any other error than ENOENT or ENOTDIR
+ * then the file exists, we just can't stat it.
+ */
+ if (errno != ENOENT && errno != ENOTDIR) {
+ return talloc_strdup(ctx, dptr->wcard);
+ }
+
+ /*
+ * A scan will find the long version of a mangled name as
+ * wildcard.
+ */
+ retry_scanning |= mangle_is_mangled(dptr->wcard,
+ dir_hnd->conn->params);
+
+ /*
+ * Also retry scanning if the client requested case
+ * insensitive semantics and the file system does not provide
+ * it.
+ */
+ retry_scanning |= (!dir_hnd->case_sensitive &&
+ (dir_hnd->conn->fs_capabilities &
+ FILE_CASE_SENSITIVE_SEARCH));
+
+ if (retry_scanning) {
+ char *found_name = NULL;
+ NTSTATUS status;
+
+ status = get_real_filename_at(dir_fsp,
+ dptr->wcard,
+ ctx,
+ &found_name);
+ if (NT_STATUS_IS_OK(status)) {
+ return found_name;
+ }
+ }
+
+ return NULL;
+}
+
+struct files_struct *dir_hnd_fetch_fsp(struct smb_Dir *dir_hnd)
+{
+ return dir_hnd->fsp;
+}
+
+/****************************************************************************
+ Fetch the fsp associated with the dptr_num.
+****************************************************************************/
+
+files_struct *dptr_fetch_lanman2_fsp(struct smbd_server_connection *sconn,
+ int dptr_num)
+{
+ struct dptr_struct *dptr = dptr_get(sconn, dptr_num);
+ if (dptr == NULL) {
+ return NULL;
+ }
+ DBG_NOTICE("fetching dirptr %d for path %s\n",
+ dptr_num,
+ dptr->dir_hnd->dir_smb_fname->base_name);
+ return dptr->dir_hnd->fsp;
+}
+
+bool smbd_dirptr_get_entry(TALLOC_CTX *ctx,
+ struct dptr_struct *dirptr,
+ const char *mask,
+ uint32_t dirtype,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode_in,
+ bool (*match_fn)(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname),
+ void *private_data,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t *_mode)
+{
+ struct smb_Dir *dir_hnd = dirptr->dir_hnd;
+ connection_struct *conn = dir_hnd->conn;
+ struct smb_filename *dir_fname = dir_hnd->dir_smb_fname;
+ bool posix = (dir_fname->flags & SMB_FILENAME_POSIX_PATH);
+ const bool toplevel = ISDOT(dir_fname->base_name);
+ NTSTATUS status;
+
+ *_smb_fname = NULL;
+ *_mode = 0;
+
+ if (dirptr->overflow.smb_fname != NULL) {
+ *_fname = talloc_move(ctx, &dirptr->overflow.fname);
+ *_smb_fname = talloc_move(ctx, &dirptr->overflow.smb_fname);
+ *_mode = dirptr->overflow.mode;
+ return true;
+ }
+
+ if (dont_descend && (dptr_FileNumber(dirptr) >= 2)) {
+ /*
+ * . and .. were returned first, we're done showing
+ * the directory as empty.
+ */
+ return false;
+ }
+
+ while (true) {
+ char *dname = NULL;
+ char *fname = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t mode = 0;
+ bool get_dosmode = get_dosmode_in;
+ bool toplevel_dotdot;
+ bool visible;
+ bool ok;
+
+ dname = dptr_ReadDirName(ctx, dirptr);
+
+ DBG_DEBUG("dir [%s] dirptr [%p] offset [%u] => "
+ "dname [%s]\n",
+ smb_fname_str_dbg(dir_fname),
+ dirptr,
+ dir_hnd->file_number,
+ dname ? dname : "(finished)");
+
+ if (dname == NULL) {
+ return false;
+ }
+
+ if (IS_VETO_PATH(conn, dname)) {
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ /*
+ * fname may get mangled, dname is never mangled.
+ * Whenever we're accessing the filesystem we use
+ * pathreal which is composed from dname.
+ */
+
+ ok = match_fn(ctx, private_data, dname, mask, &fname);
+ if (!ok) {
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ toplevel_dotdot = toplevel && ISDOTDOT(dname);
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ toplevel_dotdot ? "." : dname,
+ NULL,
+ NULL,
+ dir_fname->twrp,
+ dir_fname->flags);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(dname);
+ return false;
+ }
+
+ /*
+ * UCF_POSIX_PATHNAMES to avoid the readdir fallback
+ * if we get raced between readdir and unlink.
+ */
+ status = openat_pathref_fsp_lcomp(dir_hnd->fsp,
+ smb_fname,
+ UCF_POSIX_PATHNAMES);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not open %s: %s\n",
+ dname,
+ nt_errstr(status));
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ visible = is_visible_fsp(smb_fname->fsp);
+ if (!visible) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ if (!S_ISLNK(smb_fname->st.st_ex_mode)) {
+ goto done;
+ }
+
+ if (lp_host_msdfs() && lp_msdfs_root(SNUM(conn)) &&
+ is_msdfs_link(dir_hnd->fsp, smb_fname))
+ {
+ DBG_INFO("Masquerading msdfs link %s as a directory\n",
+ smb_fname->base_name);
+
+ smb_fname->st.st_ex_mode = (smb_fname->st.st_ex_mode &
+ ~S_IFMT) |
+ S_IFDIR;
+
+ mode = dos_mode_msdfs(conn, dname, &smb_fname->st);
+ get_dosmode = false;
+ ask_sharemode = false;
+ goto done;
+ }
+
+ if (posix) {
+ /*
+ * Posix always wants to see symlinks.
+ */
+ ask_sharemode = false;
+ goto done;
+ }
+
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ /*
+ * Hide symlinks not followed
+ */
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+ /*
+ * We have to find out if it's a dangling
+ * symlink. Use the fat logic behind
+ * openat_pathref_fsp().
+ */
+
+ {
+ struct files_struct *fsp = smb_fname->fsp;
+ smb_fname_fsp_unlink(smb_fname);
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ }
+
+ status = openat_pathref_fsp(dir_hnd->fsp, smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Dangling symlink. Hide.
+ */
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dname);
+ continue;
+ }
+
+done:
+ if (get_dosmode) {
+ mode = fdos_mode(smb_fname->fsp);
+ smb_fname->st = smb_fname->fsp->fsp_name->st;
+ }
+
+ if (!dir_check_ftype(mode, dirtype)) {
+ DBG_INFO("[%s] attribs 0x%" PRIx32 " didn't match "
+ "0x%" PRIx32 "\n",
+ fname,
+ mode,
+ dirtype);
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(dname);
+ TALLOC_FREE(fname);
+ continue;
+ }
+
+ if (ask_sharemode && !S_ISDIR(smb_fname->st.st_ex_mode)) {
+ struct timespec write_time_ts;
+ struct file_id fileid;
+
+ fileid = vfs_file_id_from_sbuf(conn,
+ &smb_fname->st);
+ get_file_infos(fileid, 0, NULL, &write_time_ts);
+ if (!is_omit_timespec(&write_time_ts)) {
+ update_stat_ex_mtime(&smb_fname->st,
+ write_time_ts);
+ }
+ }
+
+ if (toplevel_dotdot) {
+ /*
+ * Ensure posix fileid and sids are hidden
+ */
+ smb_fname->st.st_ex_ino = 0;
+ smb_fname->st.st_ex_dev = 0;
+ smb_fname->st.st_ex_uid = -1;
+ smb_fname->st.st_ex_gid = -1;
+ }
+
+ DBG_NOTICE("mask=[%s] found %s fname=%s (%s)\n",
+ mask,
+ smb_fname_str_dbg(smb_fname),
+ dname,
+ fname);
+
+ TALLOC_FREE(dname);
+
+ *_smb_fname = talloc_move(ctx, &smb_fname);
+ *_fname = fname;
+ *_mode = mode;
+
+ return true;
+ }
+
+ return false;
+}
+
+void smbd_dirptr_push_overflow(struct dptr_struct *dirptr,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t mode)
+{
+ SMB_ASSERT(dirptr->overflow.fname == NULL);
+ SMB_ASSERT(dirptr->overflow.smb_fname == NULL);
+
+ dirptr->overflow.fname = talloc_move(dirptr, _fname);
+ dirptr->overflow.smb_fname = talloc_move(dirptr, _smb_fname);
+ dirptr->overflow.mode = mode;
+}
+
+void smbd_dirptr_set_last_name_sent(struct dptr_struct *dirptr,
+ char **_fname)
+{
+ TALLOC_FREE(dirptr->last_name_sent);
+ dirptr->last_name_sent = talloc_move(dirptr, _fname);
+}
+
+char *smbd_dirptr_get_last_name_sent(struct dptr_struct *dirptr)
+{
+ return dirptr->last_name_sent;
+}
+
+/*******************************************************************
+ Check to see if a user can read an fsp . This is only approximate,
+ it is used as part of the "hide unreadable" option. Don't
+ use it for anything security sensitive.
+********************************************************************/
+
+static bool user_can_read_fsp(struct files_struct *fsp)
+{
+ NTSTATUS status;
+ uint32_t rejected_share_access = 0;
+ uint32_t rejected_mask = 0;
+ struct security_descriptor *sd = NULL;
+ uint32_t access_mask = FILE_READ_DATA|
+ FILE_READ_EA|
+ FILE_READ_ATTRIBUTES|
+ SEC_STD_READ_CONTROL;
+
+ /*
+ * Never hide files from the root user.
+ * We use (uid_t)0 here not sec_initial_uid()
+ * as make test uses a single user context.
+ */
+
+ if (get_current_uid(fsp->conn) == (uid_t)0) {
+ return true;
+ }
+
+ /*
+ * We can't directly use smbd_check_access_rights_fsp()
+ * here, as this implicitly grants FILE_READ_ATTRIBUTES
+ * which the Windows access-based-enumeration code
+ * explicitly checks for on the file security descriptor.
+ * See bug:
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=10252
+ *
+ * and the smb2.acl2.ACCESSBASED test for details.
+ */
+
+ rejected_share_access = access_mask & ~(fsp->conn->share_access);
+ if (rejected_share_access) {
+ DBG_DEBUG("rejected share access 0x%x "
+ "on %s (0x%x)\n",
+ (unsigned int)access_mask,
+ fsp_str_dbg(fsp),
+ (unsigned int)rejected_share_access);
+ return false;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ (SECINFO_OWNER |
+ SECINFO_GROUP |
+ SECINFO_DACL),
+ talloc_tos(),
+ &sd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not get acl "
+ "on %s: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return false;
+ }
+
+ status = se_file_access_check(sd,
+ get_current_nttok(fsp->conn),
+ false,
+ access_mask,
+ &rejected_mask);
+
+ TALLOC_FREE(sd);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ DBG_DEBUG("rejected bits 0x%x read access for %s\n",
+ (unsigned int)rejected_mask,
+ fsp_str_dbg(fsp));
+ return false;
+ }
+ return true;
+}
+
+/*******************************************************************
+ Check to see if a user can write to an fsp.
+ Always return true for directories.
+ This is only approximate,
+ it is used as part of the "hide unwriteable" option. Don't
+ use it for anything security sensitive.
+********************************************************************/
+
+static bool user_can_write_fsp(struct files_struct *fsp)
+{
+ /*
+ * Never hide files from the root user.
+ * We use (uid_t)0 here not sec_initial_uid()
+ * as make test uses a single user context.
+ */
+
+ if (get_current_uid(fsp->conn) == (uid_t)0) {
+ return true;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ return true;
+ }
+
+ return can_write_to_fsp(fsp);
+}
+
+/*******************************************************************
+ Is a file a "special" type ?
+********************************************************************/
+
+static bool file_is_special(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ /*
+ * Never hide files from the root user.
+ * We use (uid_t)0 here not sec_initial_uid()
+ * as make test uses a single user context.
+ */
+
+ if (get_current_uid(conn) == (uid_t)0) {
+ return False;
+ }
+
+ SMB_ASSERT(VALID_STAT(smb_fname->st));
+
+ if (S_ISREG(smb_fname->st.st_ex_mode) ||
+ S_ISDIR(smb_fname->st.st_ex_mode) ||
+ S_ISLNK(smb_fname->st.st_ex_mode))
+ return False;
+
+ return True;
+}
+
+/*******************************************************************
+ Should the file be seen by the client?
+********************************************************************/
+
+bool is_visible_fsp(struct files_struct *fsp)
+{
+ bool hide_unreadable = false;
+ bool hide_unwriteable = false;
+ bool hide_special = false;
+ int hide_new_files_timeout = 0;
+ const char *last_component = NULL;
+
+ /*
+ * If the file does not exist, there's no point checking
+ * the configuration options. We succeed, on the basis that the
+ * checks *might* have passed if the file was present.
+ */
+ if (fsp == NULL) {
+ return true;
+ }
+
+ hide_unreadable = lp_hide_unreadable(SNUM(fsp->conn));
+ hide_unwriteable = lp_hide_unwriteable_files(SNUM(fsp->conn));
+ hide_special = lp_hide_special_files(SNUM(fsp->conn));
+ hide_new_files_timeout = lp_hide_new_files_timeout(SNUM(fsp->conn));
+
+ if (!hide_unreadable &&
+ !hide_unwriteable &&
+ !hide_special &&
+ (hide_new_files_timeout == 0))
+ {
+ return true;
+ }
+
+ fsp = metadata_fsp(fsp);
+
+ /* Get the last component of the base name. */
+ last_component = strrchr_m(fsp->fsp_name->base_name, '/');
+ if (!last_component) {
+ last_component = fsp->fsp_name->base_name;
+ } else {
+ last_component++; /* Go past '/' */
+ }
+
+ if (ISDOT(last_component) || ISDOTDOT(last_component)) {
+ return true; /* . and .. are always visible. */
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * Symlink in POSIX mode or MS-DFS.
+ * We've checked veto files so the
+ * only thing we can check is the
+ * hide_new_files_timeout.
+ */
+ if ((hide_new_files_timeout != 0) &&
+ !S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ double age = timespec_elapsed(
+ &fsp->fsp_name->st.st_ex_mtime);
+
+ if (age < (double)hide_new_files_timeout) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /* Honour _hide unreadable_ option */
+ if (hide_unreadable && !user_can_read_fsp(fsp)) {
+ DBG_DEBUG("file %s is unreadable.\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ /* Honour _hide unwriteable_ option */
+ if (hide_unwriteable && !user_can_write_fsp(fsp)) {
+ DBG_DEBUG("file %s is unwritable.\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ /* Honour _hide_special_ option */
+ if (hide_special && file_is_special(fsp->conn, fsp->fsp_name)) {
+ DBG_DEBUG("file %s is special.\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ if ((hide_new_files_timeout != 0) &&
+ !S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ double age = timespec_elapsed(&fsp->fsp_name->st.st_ex_mtime);
+
+ if (age < (double)hide_new_files_timeout) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static int smb_Dir_destructor(struct smb_Dir *dir_hnd)
+{
+ files_struct *fsp = dir_hnd->fsp;
+
+ SMB_VFS_CLOSEDIR(dir_hnd->conn, dir_hnd->dir);
+ fsp_set_fd(fsp, -1);
+ if (fsp->dptr != NULL) {
+ SMB_ASSERT(fsp->dptr->dir_hnd == dir_hnd);
+ fsp->dptr->dir_hnd = NULL;
+ }
+ dir_hnd->fsp = NULL;
+ return 0;
+}
+
+/*******************************************************************
+ Open a directory.
+********************************************************************/
+
+static int smb_Dir_OpenDir_destructor(struct smb_Dir *dir_hnd)
+{
+ files_struct *fsp = dir_hnd->fsp;
+
+ smb_Dir_destructor(dir_hnd);
+ file_free(NULL, fsp);
+ return 0;
+}
+
+NTSTATUS OpenDir(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd)
+{
+ struct files_struct *fsp = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ status = open_internal_dirfsp(conn,
+ smb_dname,
+ O_RDONLY,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = OpenDir_fsp(mem_ctx, conn, fsp, mask, attr, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * This overwrites the destructor set by OpenDir_fsp() but
+ * smb_Dir_OpenDir_destructor() calls the OpenDir_fsp()
+ * destructor.
+ */
+ talloc_set_destructor(dir_hnd, smb_Dir_OpenDir_destructor);
+
+ *_dir_hnd = dir_hnd;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS OpenDir_from_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd)
+{
+ struct files_struct *fsp = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ status = openat_internal_dir_from_pathref(dirfsp, O_RDONLY, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = OpenDir_fsp(mem_ctx, fsp->conn, fsp, mask, attr, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * This overwrites the destructor set by OpenDir_fsp() but
+ * smb_Dir_OpenDir_destructor() calls the OpenDir_fsp()
+ * destructor.
+ */
+ talloc_set_destructor(dir_hnd, smb_Dir_OpenDir_destructor);
+
+ *_dir_hnd = dir_hnd;
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Open a directory from an fsp.
+********************************************************************/
+
+static NTSTATUS OpenDir_fsp(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ files_struct *fsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd)
+{
+ struct smb_Dir *dir_hnd = talloc_zero(mem_ctx, struct smb_Dir);
+ NTSTATUS status;
+
+ if (!dir_hnd) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!fsp->fsp_flags.is_directory) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto fail;
+ }
+
+ if (fsp_get_io_fd(fsp) == -1) {
+ status = NT_STATUS_INVALID_HANDLE;
+ goto fail;
+ }
+
+ dir_hnd->conn = conn;
+
+ dir_hnd->dir_smb_fname = cp_smb_filename(dir_hnd, fsp->fsp_name);
+ if (!dir_hnd->dir_smb_fname) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ dir_hnd->dir = SMB_VFS_FDOPENDIR(fsp, mask, attr);
+ if (dir_hnd->dir == NULL) {
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+ dir_hnd->fsp = fsp;
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ dir_hnd->case_sensitive = true;
+ } else {
+ dir_hnd->case_sensitive = conn->case_sensitive;
+ }
+
+ talloc_set_destructor(dir_hnd, smb_Dir_destructor);
+
+ *_dir_hnd = dir_hnd;
+ return NT_STATUS_OK;
+
+ fail:
+ TALLOC_FREE(dir_hnd);
+ return status;
+}
+
+
+/*******************************************************************
+ Read from a directory.
+ Return directory entry, current offset, and optional stat information.
+ Don't check for veto or invisible files.
+********************************************************************/
+
+const char *ReadDirName(struct smb_Dir *dir_hnd, char **ptalloced)
+{
+ const char *n;
+ char *talloced = NULL;
+ connection_struct *conn = dir_hnd->conn;
+
+ if (dir_hnd->file_number < 2) {
+ if (dir_hnd->file_number == 0) {
+ n = ".";
+ } else {
+ n = "..";
+ }
+ dir_hnd->file_number++;
+ *ptalloced = NULL;
+ return n;
+ }
+
+ while ((n = vfs_readdirname(conn,
+ dir_hnd->fsp,
+ dir_hnd->dir,
+ &talloced))) {
+ /* Ignore . and .. - we've already returned them. */
+ if (ISDOT(n) || ISDOTDOT(n)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ *ptalloced = talloced;
+ dir_hnd->file_number++;
+ return n;
+ }
+ *ptalloced = NULL;
+ return NULL;
+}
+
+/*******************************************************************
+ Rewind to the start.
+********************************************************************/
+
+void RewindDir(struct smb_Dir *dir_hnd)
+{
+ SMB_VFS_REWINDDIR(dir_hnd->conn, dir_hnd->dir);
+ dir_hnd->file_number = 0;
+}
+
+struct files_below_forall_state {
+ char *dirpath;
+ ssize_t dirpath_len;
+ int (*fn)(struct file_id fid, const struct share_mode_data *data,
+ void *private_data);
+ void *private_data;
+};
+
+static int files_below_forall_fn(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data)
+{
+ struct files_below_forall_state *state = private_data;
+ char tmpbuf[PATH_MAX];
+ char *fullpath, *to_free;
+ ssize_t len;
+
+ len = full_path_tos(data->servicepath, data->base_name,
+ tmpbuf, sizeof(tmpbuf),
+ &fullpath, &to_free);
+ if (len == -1) {
+ return 0;
+ }
+ if (state->dirpath_len >= len) {
+ /*
+ * Filter files above dirpath
+ */
+ goto out;
+ }
+ if (fullpath[state->dirpath_len] != '/') {
+ /*
+ * Filter file that don't have a path separator at the end of
+ * dirpath's length
+ */
+ goto out;
+ }
+
+ if (memcmp(state->dirpath, fullpath, state->dirpath_len) != 0) {
+ /*
+ * Not a parent
+ */
+ goto out;
+ }
+
+ TALLOC_FREE(to_free);
+ return state->fn(fid, data, state->private_data);
+
+out:
+ TALLOC_FREE(to_free);
+ return 0;
+}
+
+static int files_below_forall(connection_struct *conn,
+ const struct smb_filename *dir_name,
+ int (*fn)(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data),
+ void *private_data)
+{
+ struct files_below_forall_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+ int ret;
+ char tmpbuf[PATH_MAX];
+ char *to_free;
+
+ state.dirpath_len = full_path_tos(conn->connectpath,
+ dir_name->base_name,
+ tmpbuf, sizeof(tmpbuf),
+ &state.dirpath, &to_free);
+ if (state.dirpath_len == -1) {
+ return -1;
+ }
+
+ ret = share_mode_forall(files_below_forall_fn, &state);
+ TALLOC_FREE(to_free);
+ return ret;
+}
+
+struct have_file_open_below_state {
+ bool found_one;
+};
+
+static int have_file_open_below_fn(struct file_id fid,
+ const struct share_mode_data *data,
+ void *private_data)
+{
+ struct have_file_open_below_state *state = private_data;
+ state->found_one = true;
+ return 1;
+}
+
+bool have_file_open_below(connection_struct *conn,
+ const struct smb_filename *name)
+{
+ struct have_file_open_below_state state = {
+ .found_one = false,
+ };
+ int ret;
+
+ if (!VALID_STAT(name->st)) {
+ return false;
+ }
+ if (!S_ISDIR(name->st.st_ex_mode)) {
+ return false;
+ }
+
+ ret = files_below_forall(conn, name, have_file_open_below_fn, &state);
+ if (ret == -1) {
+ return false;
+ }
+
+ return state.found_one;
+}
+
+/*****************************************************************
+ Is this directory empty ?
+*****************************************************************/
+
+NTSTATUS can_delete_directory_fsp(files_struct *fsp)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ struct connection_struct *conn = fsp->conn;
+ struct smb_Dir *dir_hnd = NULL;
+
+ status = OpenDir(
+ talloc_tos(), conn, fsp->fsp_name, NULL, 0, &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced))) {
+ struct smb_filename *smb_dname_full = NULL;
+ struct smb_filename *direntry_fname = NULL;
+ char *fullname = NULL;
+ int ret;
+
+ if (ISDOT(dname) || (ISDOTDOT(dname))) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+ if (IS_VETO_PATH(conn, dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ fullname = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ fsp->fsp_name->base_name,
+ dname);
+ if (fullname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ smb_dname_full = synthetic_smb_fname(talloc_tos(),
+ fullname,
+ NULL,
+ NULL,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags);
+ if (smb_dname_full == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ ret = SMB_VFS_LSTAT(conn, smb_dname_full);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ break;
+ }
+
+ if (S_ISLNK(smb_dname_full->st.st_ex_mode)) {
+ /* Could it be an msdfs link ? */
+ if (lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(conn))) {
+ struct smb_filename *smb_dname;
+ smb_dname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags);
+ if (smb_dname == NULL) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+ if (is_msdfs_link(fsp, smb_dname)) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(smb_dname);
+ DBG_DEBUG("got msdfs link name %s "
+ "- can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+ }
+ TALLOC_FREE(smb_dname);
+ }
+ /* Not a DFS link - could it be a dangling symlink ? */
+ ret = SMB_VFS_STAT(conn, smb_dname_full);
+ if (ret == -1 && (errno == ENOENT || errno == ELOOP)) {
+ /*
+ * Dangling symlink.
+ * Allow if "delete veto files = yes"
+ */
+ if (lp_delete_veto_files(SNUM(conn))) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ continue;
+ }
+ }
+ DBG_DEBUG("got symlink name %s - "
+ "can't delete directory %s\n",
+ dname,
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+ }
+
+ /* Not a symlink, get a pathref. */
+ status = synthetic_pathref(talloc_tos(),
+ fsp,
+ dname,
+ NULL,
+ &smb_dname_full->st,
+ fsp->fsp_name->twrp,
+ fsp->fsp_name->flags,
+ &direntry_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ break;
+ }
+
+ if (!is_visible_fsp(direntry_fname->fsp)) {
+ /*
+ * Hidden file.
+ * Allow if "delete veto files = yes"
+ */
+ if (lp_delete_veto_files(SNUM(conn))) {
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+ continue;
+ }
+ }
+
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(fullname);
+ TALLOC_FREE(smb_dname_full);
+ TALLOC_FREE(direntry_fname);
+
+ DBG_DEBUG("got name %s - can't delete\n", dname);
+ status = NT_STATUS_DIRECTORY_NOT_EMPTY;
+ break;
+ }
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(dir_hnd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_RENAME) &&
+ lp_strict_rename(SNUM(conn)) &&
+ have_file_open_below(fsp->conn, fsp->fsp_name))
+ {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/dir.h b/source3/smbd/dir.h
new file mode 100644
index 0000000..d520d13
--- /dev/null
+++ b/source3/smbd/dir.h
@@ -0,0 +1,89 @@
+/*
+ * Unix SMB/Netbios implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SOURCE3_SMBD_DIR_H_
+#define _SOURCE3_SMBD_DIR_H_
+
+#include "includes.h"
+
+struct smb_Dir;
+struct dptr_struct;
+
+NTSTATUS can_delete_directory_fsp(files_struct *fsp);
+struct files_struct *dir_hnd_fetch_fsp(struct smb_Dir *dir_hnd);
+uint16_t dptr_attr(struct smbd_server_connection *sconn, int key);
+bool dptr_case_sensitive(struct dptr_struct *dptr);
+void dptr_closecnum(connection_struct *conn);
+void dptr_CloseDir(files_struct *fsp);
+NTSTATUS dptr_create(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ bool old_handle,
+ const char *wcard,
+ uint32_t attr,
+ struct dptr_struct **dptr_ret);
+int dptr_dnum(struct dptr_struct *dptr);
+files_struct *dptr_fetch_lanman2_fsp(struct smbd_server_connection *sconn,
+ int dptr_num);
+unsigned int dptr_FileNumber(struct dptr_struct *dptr);
+bool dptr_get_priv(struct dptr_struct *dptr);
+bool dptr_has_wild(struct dptr_struct *dptr);
+const char *dptr_path(struct smbd_server_connection *sconn, int key);
+char *dptr_ReadDirName(TALLOC_CTX *ctx, struct dptr_struct *dptr);
+void dptr_RewindDir(struct dptr_struct *dptr);
+void dptr_set_priv(struct dptr_struct *dptr);
+const char *dptr_wcard(struct smbd_server_connection *sconn, int key);
+bool have_file_open_below(connection_struct *conn,
+ const struct smb_filename *name);
+bool init_dptrs(struct smbd_server_connection *sconn);
+bool is_visible_fsp(files_struct *fsp);
+NTSTATUS OpenDir(TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd);
+NTSTATUS OpenDir_from_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *mask,
+ uint32_t attr,
+ struct smb_Dir **_dir_hnd);
+const char *ReadDirName(struct smb_Dir *dir_hnd, char **talloced);
+void RewindDir(struct smb_Dir *dir_hnd);
+bool smbd_dirptr_get_entry(TALLOC_CTX *ctx,
+ struct dptr_struct *dirptr,
+ const char *mask,
+ uint32_t dirtype,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode,
+ bool (*match_fn)(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname),
+ void *private_data,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t *_mode);
+char *smbd_dirptr_get_last_name_sent(struct dptr_struct *dirptr);
+void smbd_dirptr_push_overflow(struct dptr_struct *dirptr,
+ char **_fname,
+ struct smb_filename **_smb_fname,
+ uint32_t mode);
+void smbd_dirptr_set_last_name_sent(struct dptr_struct *dirptr, char **_fname);
+#endif
diff --git a/source3/smbd/dmapi.c b/source3/smbd/dmapi.c
new file mode 100644
index 0000000..1943fe9
--- /dev/null
+++ b/source3/smbd/dmapi.c
@@ -0,0 +1,357 @@
+/*
+ Unix SMB/CIFS implementation.
+ DMAPI Support routines
+
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_DMAPI
+
+#ifndef USE_DMAPI
+
+uint32_t dmapi_file_flags(const char * const path) { return 0; }
+bool dmapi_have_session(void) { return False; }
+const void * dmapi_get_current_session(void) { return NULL; }
+
+#else /* USE_DMAPI */
+
+#ifdef HAVE_XFS_DMAPI_H
+#include <xfs/dmapi.h>
+#elif defined(HAVE_SYS_DMI_H)
+#include <sys/dmi.h>
+#elif defined(HAVE_SYS_JFSDMAPI_H)
+#include <sys/jfsdmapi.h>
+#elif defined(HAVE_SYS_DMAPI_H)
+#include <sys/dmapi.h>
+#elif defined(HAVE_DMAPI_H)
+#include <dmapi.h>
+#endif
+
+#define DMAPI_SESSION_NAME "samba"
+#define DMAPI_TRACE 10
+
+struct smbd_dmapi_context {
+ dm_sessid_t session;
+ unsigned session_num;
+};
+
+/*
+ Initialise DMAPI session. The session is persistent kernel state,
+ so it might already exist, in which case we merely want to
+ reconnect to it. This function should be called as root.
+*/
+static int dmapi_init_session(struct smbd_dmapi_context *ctx)
+{
+ char buf[DM_SESSION_INFO_LEN];
+ size_t buflen;
+ uint nsessions = 5;
+ dm_sessid_t *sessions = NULL;
+ char *version;
+ char *session_name;
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+
+ int i, err;
+
+ if (ctx->session_num == 0) {
+ session_name = talloc_strdup(tmp_ctx, DMAPI_SESSION_NAME);
+ } else {
+ session_name = talloc_asprintf(tmp_ctx, "%s%u", DMAPI_SESSION_NAME,
+ ctx->session_num);
+ }
+
+ if (session_name == NULL) {
+ DEBUG(0,("Out of memory in dmapi_init_session\n"));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+
+ if (dm_init_service(&version) < 0) {
+ DEBUG(0, ("dm_init_service failed - disabling DMAPI\n"));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ ZERO_STRUCT(buf);
+
+ /* Fetch kernel DMAPI sessions until we get any of them */
+ do {
+ dm_sessid_t *new_sessions;
+ nsessions *= 2;
+ new_sessions = talloc_realloc(tmp_ctx, sessions,
+ dm_sessid_t, nsessions);
+ if (new_sessions == NULL) {
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ sessions = new_sessions;
+ err = dm_getall_sessions(nsessions, sessions, &nsessions);
+ } while (err == -1 && errno == E2BIG);
+
+ if (err == -1) {
+ DEBUGADD(DMAPI_TRACE,
+ ("failed to retrieve DMAPI sessions: %s\n",
+ strerror(errno)));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ /* Look through existing kernel DMAPI sessions to find out ours */
+ for (i = 0; i < nsessions; ++i) {
+ err = dm_query_session(sessions[i], sizeof(buf), buf, &buflen);
+ buf[sizeof(buf) - 1] = '\0';
+ if (err == 0 && strcmp(session_name, buf) == 0) {
+ ctx->session = sessions[i];
+ DEBUGADD(DMAPI_TRACE,
+ ("attached to existing DMAPI session "
+ "named '%s'\n", buf));
+ break;
+ }
+ }
+
+ /* No session already defined. */
+ if (ctx->session == DM_NO_SESSION) {
+ err = dm_create_session(DM_NO_SESSION,
+ session_name,
+ &ctx->session);
+ if (err < 0) {
+ DEBUGADD(DMAPI_TRACE,
+ ("failed to create new DMAPI session: %s\n",
+ strerror(errno)));
+ ctx->session = DM_NO_SESSION;
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ DEBUG(0, ("created new DMAPI session named '%s' for %s\n",
+ session_name, version));
+ }
+
+ if (ctx->session != DM_NO_SESSION) {
+ set_effective_capability(DMAPI_ACCESS_CAPABILITY);
+ }
+
+ /*
+ Note that we never end the DMAPI session. It gets re-used if possiblie.
+ DMAPI session is a kernel resource that is usually lives until server reboot
+ and doesn't get destroed when an application finishes.
+
+ However, we free list of references to DMAPI sessions we've got from the kernel
+ as it is not needed anymore once we have found (or created) our session.
+ */
+
+ talloc_free(tmp_ctx);
+ return 0;
+}
+
+/*
+ Return a pointer to our DMAPI session, if available.
+ This assumes that you have called dmapi_have_session() first.
+*/
+const void *dmapi_get_current_session(void)
+{
+ if (!dmapi_ctx) {
+ return NULL;
+ }
+
+ if (dmapi_ctx->session == DM_NO_SESSION) {
+ return NULL;
+ }
+
+ return (void *)&dmapi_ctx->session;
+}
+
+/*
+ dmapi_have_session() must be the first DMAPI call you make in Samba. It will
+ initialize DMAPI, if available, and tell you if you can get a DMAPI session.
+ This should be called in the client-specific child process.
+*/
+
+bool dmapi_have_session(void)
+{
+ if (!dmapi_ctx) {
+ dmapi_ctx = talloc(NULL, struct smbd_dmapi_context);
+ if (!dmapi_ctx) {
+ exit_server("unable to allocate smbd_dmapi_context");
+ }
+ dmapi_ctx->session = DM_NO_SESSION;
+ dmapi_ctx->session_num = 0;
+
+ become_root();
+ dmapi_init_session(dmapi_ctx);
+ unbecome_root();
+
+ }
+
+ return dmapi_ctx->session != DM_NO_SESSION;
+}
+
+/*
+ only call this when you get back an EINVAL error indicating that the
+ session you are using is invalid. This destroys the existing session
+ and creates a new one.
+ */
+bool dmapi_new_session(void)
+{
+ if (dmapi_have_session()) {
+ /* try to destroy the old one - this may not succeed */
+ dm_destroy_session(dmapi_ctx->session);
+ }
+ dmapi_ctx->session = DM_NO_SESSION;
+ become_root();
+ dmapi_ctx->session_num++;
+ dmapi_init_session(dmapi_ctx);
+ unbecome_root();
+ return dmapi_ctx->session != DM_NO_SESSION;
+}
+
+/*
+ only call this when exiting from master smbd process. DMAPI sessions
+ are long-lived kernel resources we ought to share across smbd processes.
+ However, we must free them when all smbd processes are finished to
+ allow other subsystems clean up properly. Not freeing DMAPI session
+ blocks certain HSM implementations from proper shutdown.
+*/
+bool dmapi_destroy_session(void)
+{
+ if (!dmapi_ctx) {
+ return true;
+ }
+ if (dmapi_ctx->session != DM_NO_SESSION) {
+ become_root();
+ if (0 == dm_destroy_session(dmapi_ctx->session)) {
+ dmapi_ctx->session_num--;
+ dmapi_ctx->session = DM_NO_SESSION;
+ } else {
+ DEBUG(0,("Couldn't destroy DMAPI session: %s\n",
+ strerror(errno)));
+ }
+ unbecome_root();
+ }
+ return dmapi_ctx->session == DM_NO_SESSION;
+}
+
+
+/*
+ This is default implementation of dmapi_file_flags() that is
+ called from VFS is_offline() call to know whether file is offline.
+ For GPFS-specific version see modules/vfs_tsmsm.c. It might be
+ that approach on querying existence of a specific attribute that
+ is used in vfs_tsmsm.c will work with other DMAPI-based HSM
+ implementations as well.
+*/
+uint32_t dmapi_file_flags(const char * const path)
+{
+ int err;
+ dm_eventset_t events = {0};
+ uint nevents;
+
+ dm_sessid_t dmapi_session;
+ dm_sessid_t *dmapi_session_ptr;
+ const void *_dmapi_session_ptr;
+ void *dm_handle = NULL;
+ size_t dm_handle_len = 0;
+
+ uint32_t flags = 0;
+
+ _dmapi_session_ptr = dmapi_get_current_session();
+ if (_dmapi_session_ptr == NULL) {
+ return 0;
+ }
+
+ dmapi_session_ptr = discard_const_p(dm_sessid_t, _dmapi_session_ptr);
+ dmapi_session = *dmapi_session_ptr;
+ if (dmapi_session == DM_NO_SESSION) {
+ return 0;
+ }
+
+ /* AIX has DMAPI but no POSIX capabilities support. In this case,
+ * we need to be root to do DMAPI manipulations.
+ */
+#ifndef HAVE_POSIX_CAPABILITIES
+ become_root();
+#endif
+
+ err = dm_path_to_handle(discard_const_p(char, path),
+ &dm_handle, &dm_handle_len);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE, ("dm_path_to_handle(%s): %s\n",
+ path, strerror(errno)));
+
+ if (errno != EPERM) {
+ goto done;
+ }
+
+ /* Linux capabilities are broken in that changing our
+ * user ID will clobber out effective capabilities irrespective
+ * of whether we have set PR_SET_KEEPCAPS. Fortunately, the
+ * capabilities are not removed from our permitted set, so we
+ * can re-acquire them if necessary.
+ */
+
+ set_effective_capability(DMAPI_ACCESS_CAPABILITY);
+
+ err = dm_path_to_handle(discard_const_p(char, path),
+ &dm_handle, &dm_handle_len);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE,
+ ("retrying dm_path_to_handle(%s): %s\n",
+ path, strerror(errno)));
+ goto done;
+ }
+ }
+
+ err = dm_get_eventlist(dmapi_session, dm_handle, dm_handle_len,
+ DM_NO_TOKEN, DM_EVENT_MAX, &events, &nevents);
+ if (err < 0) {
+ DEBUG(DMAPI_TRACE, ("dm_get_eventlist(%s): %s\n",
+ path, strerror(errno)));
+ dm_handle_free(dm_handle, dm_handle_len);
+ goto done;
+ }
+
+ /* We figure that the only reason a DMAPI application would be
+ * interested in trapping read events is that part of the file is
+ * offline.
+ */
+ DEBUG(DMAPI_TRACE, ("DMAPI event list for %s\n", path));
+ if (DMEV_ISSET(DM_EVENT_READ, events)) {
+ flags = FILE_ATTRIBUTE_OFFLINE;
+ }
+
+ dm_handle_free(dm_handle, dm_handle_len);
+
+ if (flags & FILE_ATTRIBUTE_OFFLINE) {
+ DEBUG(DMAPI_TRACE, ("%s is OFFLINE\n", path));
+ }
+
+done:
+
+#ifndef HAVE_POSIX_CAPABILITIES
+ unbecome_root();
+#endif
+
+ return flags;
+}
+
+
+#endif /* USE_DMAPI */
diff --git a/source3/smbd/dnsregister.c b/source3/smbd/dnsregister.c
new file mode 100644
index 0000000..df18900
--- /dev/null
+++ b/source3/smbd/dnsregister.c
@@ -0,0 +1,200 @@
+/*
+ Unix SMB/CIFS implementation.
+ DNS-SD registration
+ Copyright (C) Rishi Srivatsavai 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <includes.h>
+#include "smbd/smbd.h"
+
+/* Uses DNS service discovery (libdns_sd) to
+ * register the SMB service. SMB service is registered
+ * on ".local" domain via Multicast DNS & any
+ * other unicast DNS domains available.
+ *
+ * Users use the smbclient -B (Browse) option to
+ * browse for advertised SMB services.
+ */
+
+#define DNS_REG_RETRY_INTERVAL (5*60) /* in seconds */
+
+#ifdef WITH_DNSSD_SUPPORT
+
+#include <dns_sd.h>
+
+struct dns_reg_state {
+ struct tevent_context *event_ctx;
+ uint16_t port;
+ DNSServiceRef srv_ref;
+ struct tevent_timer *te;
+ int fd;
+ struct tevent_fd *fde;
+};
+
+static int dns_reg_state_destructor(struct dns_reg_state *dns_state)
+{
+ if (dns_state->srv_ref != NULL) {
+ /* Close connection to the mDNS daemon */
+ DNSServiceRefDeallocate(dns_state->srv_ref);
+ dns_state->srv_ref = NULL;
+ }
+
+ /* Clear event handler */
+ TALLOC_FREE(dns_state->te);
+ TALLOC_FREE(dns_state->fde);
+ dns_state->fd = -1;
+
+ return 0;
+}
+
+static void dns_register_smbd_retry(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data);
+static void dns_register_smbd_fde_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+
+static bool dns_register_smbd_schedule(struct dns_reg_state *dns_state,
+ struct timeval tval)
+{
+ dns_reg_state_destructor(dns_state);
+
+ dns_state->te = tevent_add_timer(dns_state->event_ctx,
+ dns_state,
+ tval,
+ dns_register_smbd_retry,
+ dns_state);
+ if (!dns_state->te) {
+ return false;
+ }
+
+ return true;
+}
+
+static void dns_register_smbd_retry(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct dns_reg_state *dns_state = talloc_get_type_abort(private_data,
+ struct dns_reg_state);
+ DNSServiceErrorType err;
+
+ dns_reg_state_destructor(dns_state);
+
+ DEBUG(6, ("registering _smb._tcp service on port %d\n",
+ dns_state->port));
+
+ /* Register service with DNS. Connects with the mDNS
+ * daemon running on the local system to perform DNS
+ * service registration.
+ */
+ err = DNSServiceRegister(&dns_state->srv_ref, 0 /* flags */,
+ kDNSServiceInterfaceIndexAny,
+ NULL /* service name */,
+ "_smb._tcp" /* service type */,
+ NULL /* domain */,
+ "" /* SRV target host name */,
+ htons(dns_state->port),
+ 0 /* TXT record len */,
+ NULL /* TXT record data */,
+ NULL /* callback func */,
+ NULL /* callback context */);
+
+ if (err != kDNSServiceErr_NoError) {
+ /* Failed to register service. Schedule a re-try attempt.
+ */
+ DEBUG(3, ("unable to register with mDNS (err %d)\n", err));
+ goto retry;
+ }
+
+ dns_state->fd = DNSServiceRefSockFD(dns_state->srv_ref);
+ if (dns_state->fd == -1) {
+ goto retry;
+ }
+
+ dns_state->fde = tevent_add_fd(dns_state->event_ctx,
+ dns_state,
+ dns_state->fd,
+ TEVENT_FD_READ,
+ dns_register_smbd_fde_handler,
+ dns_state);
+ if (!dns_state->fde) {
+ goto retry;
+ }
+
+ return;
+ retry:
+ dns_register_smbd_schedule(dns_state,
+ timeval_current_ofs(DNS_REG_RETRY_INTERVAL, 0));
+}
+
+/* Processes reply from mDNS daemon. Returns true if a reply was received */
+static void dns_register_smbd_fde_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct dns_reg_state *dns_state = talloc_get_type_abort(private_data,
+ struct dns_reg_state);
+ DNSServiceErrorType err;
+
+ err = DNSServiceProcessResult(dns_state->srv_ref);
+ if (err != kDNSServiceErr_NoError) {
+ DEBUG(3, ("failed to process mDNS result (err %d), re-trying\n",
+ err));
+ goto retry;
+ }
+
+ talloc_free(dns_state);
+ return;
+
+ retry:
+ dns_register_smbd_schedule(dns_state,
+ timeval_current_ofs(DNS_REG_RETRY_INTERVAL, 0));
+}
+
+bool smbd_setup_mdns_registration(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ uint16_t port)
+{
+ struct dns_reg_state *dns_state;
+
+ dns_state = talloc_zero(mem_ctx, struct dns_reg_state);
+ if (dns_state == NULL) {
+ return false;
+ }
+ dns_state->event_ctx = ev;
+ dns_state->port = port;
+ dns_state->fd = -1;
+
+ talloc_set_destructor(dns_state, dns_reg_state_destructor);
+
+ return dns_register_smbd_schedule(dns_state, timeval_zero());
+}
+
+#else /* WITH_DNSSD_SUPPORT */
+
+bool smbd_setup_mdns_registration(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ uint16_t port)
+{
+ return true;
+}
+
+#endif /* WITH_DNSSD_SUPPORT */
diff --git a/source3/smbd/dosmode.c b/source3/smbd/dosmode.c
new file mode 100644
index 0000000..b4b6955
--- /dev/null
+++ b/source3/smbd/dosmode.c
@@ -0,0 +1,1305 @@
+/*
+ Unix SMB/CIFS implementation.
+ dos mode handling functions
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "globals.h"
+#include "system/filesys.h"
+#include "librpc/gen_ndr/ndr_xattr.h"
+#include "librpc/gen_ndr/ioctl.h"
+#include "../libcli/security/security.h"
+#include "smbd/smbd.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/string_wrappers.h"
+#include "fake_file.h"
+
+static void dos_mode_debug_print(const char *func, uint32_t mode)
+{
+ fstring modestr;
+
+ if (DEBUGLEVEL < DBGLVL_INFO) {
+ return;
+ }
+
+ modestr[0] = '\0';
+
+ if (mode & FILE_ATTRIBUTE_HIDDEN) {
+ fstrcat(modestr, "h");
+ }
+ if (mode & FILE_ATTRIBUTE_READONLY) {
+ fstrcat(modestr, "r");
+ }
+ if (mode & FILE_ATTRIBUTE_SYSTEM) {
+ fstrcat(modestr, "s");
+ }
+ if (mode & FILE_ATTRIBUTE_DIRECTORY) {
+ fstrcat(modestr, "d");
+ }
+ if (mode & FILE_ATTRIBUTE_ARCHIVE) {
+ fstrcat(modestr, "a");
+ }
+ if (mode & FILE_ATTRIBUTE_SPARSE) {
+ fstrcat(modestr, "[sparse]");
+ }
+ if (mode & FILE_ATTRIBUTE_OFFLINE) {
+ fstrcat(modestr, "[offline]");
+ }
+ if (mode & FILE_ATTRIBUTE_COMPRESSED) {
+ fstrcat(modestr, "[compressed]");
+ }
+
+ DBG_INFO("%s returning (0x%x): \"%s\"\n", func, (unsigned)mode,
+ modestr);
+}
+
+static uint32_t filter_mode_by_protocol(uint32_t mode)
+{
+ if (get_Protocol() <= PROTOCOL_LANMAN2) {
+ DEBUG(10,("filter_mode_by_protocol: "
+ "filtering result 0x%x to 0x%x\n",
+ (unsigned int)mode,
+ (unsigned int)(mode & 0x3f) ));
+ mode &= 0x3f;
+ }
+ return mode;
+}
+
+/****************************************************************************
+ Change a dos mode to a unix mode.
+ Base permission for files:
+ if creating file and inheriting (i.e. parent_dir != NULL)
+ apply read/write bits from parent directory.
+ else
+ everybody gets read bit set
+ dos readonly is represented in unix by removing everyone's write bit
+ dos archive is represented in unix by the user's execute bit
+ dos system is represented in unix by the group's execute bit
+ dos hidden is represented in unix by the other's execute bit
+ if !inheriting {
+ Then apply create mask,
+ then add force bits.
+ }
+ Base permission for directories:
+ dos directory is represented in unix by unix's dir bit and the exec bit
+ if !inheriting {
+ Then apply create mask,
+ then add force bits.
+ }
+****************************************************************************/
+
+mode_t unix_mode(connection_struct *conn, int dosmode,
+ const struct smb_filename *smb_fname,
+ struct files_struct *parent_dirfsp)
+{
+ mode_t result = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
+ mode_t dir_mode = 0; /* Mode of the inherit_from directory if
+ * inheriting. */
+
+ if ((dosmode & FILE_ATTRIBUTE_READONLY) &&
+ !lp_store_dos_attributes(SNUM(conn))) {
+ result &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
+ }
+
+ if ((parent_dirfsp != NULL) && lp_inherit_permissions(SNUM(conn))) {
+ struct stat_ex sbuf = { .st_ex_nlink = 0, };
+ int ret;
+
+ DBG_DEBUG("[%s] inheriting from [%s]\n",
+ smb_fname_str_dbg(smb_fname),
+ smb_fname_str_dbg(parent_dirfsp->fsp_name));
+
+ ret = SMB_VFS_FSTAT(parent_dirfsp, &sbuf);
+ if (ret != 0) {
+ DBG_ERR("fstat failed [%s]: %s\n",
+ smb_fname_str_dbg(parent_dirfsp->fsp_name),
+ strerror(errno));
+ return(0); /* *** shouldn't happen! *** */
+ }
+
+ /* Save for later - but explicitly remove setuid bit for safety. */
+ dir_mode = sbuf.st_ex_mode & ~S_ISUID;
+ DEBUG(2,("unix_mode(%s) inherit mode %o\n",
+ smb_fname_str_dbg(smb_fname), (int)dir_mode));
+ /* Clear "result" */
+ result = 0;
+ }
+
+ if (dosmode & FILE_ATTRIBUTE_DIRECTORY) {
+ /* We never make directories read only for the owner as under DOS a user
+ can always create a file in a read-only directory. */
+ result |= (S_IFDIR | S_IWUSR);
+
+ if (dir_mode) {
+ /* Inherit mode of parent directory. */
+ result |= dir_mode;
+ } else {
+ /* Provisionally add all 'x' bits */
+ result |= (S_IXUSR | S_IXGRP | S_IXOTH);
+
+ /* Apply directory mask */
+ result &= lp_directory_mask(SNUM(conn));
+ /* Add in force bits */
+ result |= lp_force_directory_mode(SNUM(conn));
+ }
+ } else {
+ if ((dosmode & FILE_ATTRIBUTE_ARCHIVE) &&
+ lp_map_archive(SNUM(conn))) {
+ result |= S_IXUSR;
+ }
+
+ if ((dosmode & FILE_ATTRIBUTE_SYSTEM) &&
+ lp_map_system(SNUM(conn))) {
+ result |= S_IXGRP;
+ }
+
+ if ((dosmode & FILE_ATTRIBUTE_HIDDEN) &&
+ lp_map_hidden(SNUM(conn))) {
+ result |= S_IXOTH;
+ }
+
+ if (dir_mode) {
+ /* Inherit 666 component of parent directory mode */
+ result |= dir_mode & (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH);
+ } else {
+ /* Apply mode mask */
+ result &= lp_create_mask(SNUM(conn));
+ /* Add in force bits */
+ result |= lp_force_create_mode(SNUM(conn));
+ }
+ }
+
+ DBG_INFO("unix_mode(%s) returning 0%o\n",
+ smb_fname_str_dbg(smb_fname), (int)result);
+
+ return(result);
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+****************************************************************************/
+
+static uint32_t dos_mode_from_sbuf(connection_struct *conn,
+ const struct stat_ex *st,
+ struct files_struct *fsp)
+{
+ int result = 0;
+ enum mapreadonly_options ro_opts =
+ (enum mapreadonly_options)lp_map_readonly(SNUM(conn));
+
+#if defined(UF_IMMUTABLE) && defined(SF_IMMUTABLE)
+ /* if we can find out if a file is immutable we should report it r/o */
+ if (st->st_ex_flags & (UF_IMMUTABLE | SF_IMMUTABLE)) {
+ result |= FILE_ATTRIBUTE_READONLY;
+ }
+#endif
+ if (ro_opts == MAP_READONLY_YES) {
+ /* Original Samba method - map inverse of user "w" bit. */
+ if ((st->st_ex_mode & S_IWUSR) == 0) {
+ result |= FILE_ATTRIBUTE_READONLY;
+ }
+ } else if (ro_opts == MAP_READONLY_PERMISSIONS) {
+ /* smb_fname->fsp can be NULL for an MS-DFS link. */
+ /* Check actual permissions for read-only. */
+ if ((fsp != NULL) && !can_write_to_fsp(fsp)) {
+ result |= FILE_ATTRIBUTE_READONLY;
+ }
+ } /* Else never set the readonly bit. */
+
+ if (MAP_ARCHIVE(conn) && ((st->st_ex_mode & S_IXUSR) != 0)) {
+ result |= FILE_ATTRIBUTE_ARCHIVE;
+ }
+
+ if (MAP_SYSTEM(conn) && ((st->st_ex_mode & S_IXGRP) != 0)) {
+ result |= FILE_ATTRIBUTE_SYSTEM;
+ }
+
+ if (MAP_HIDDEN(conn) && ((st->st_ex_mode & S_IXOTH) != 0)) {
+ result |= FILE_ATTRIBUTE_HIDDEN;
+ }
+
+ if (S_ISDIR(st->st_ex_mode)) {
+ result = FILE_ATTRIBUTE_DIRECTORY |
+ (result & FILE_ATTRIBUTE_READONLY);
+ }
+
+ dos_mode_debug_print(__func__, result);
+
+ return result;
+}
+
+/****************************************************************************
+ Get DOS attributes from an EA.
+ This can also pull the create time into the stat struct inside smb_fname.
+****************************************************************************/
+
+NTSTATUS parse_dos_attribute_blob(struct smb_filename *smb_fname,
+ DATA_BLOB blob,
+ uint32_t *pattr)
+{
+ struct xattr_DOSATTRIB dosattrib;
+ enum ndr_err_code ndr_err;
+ uint32_t dosattr;
+
+ ndr_err = ndr_pull_struct_blob(&blob, talloc_tos(), &dosattrib,
+ (ndr_pull_flags_fn_t)ndr_pull_xattr_DOSATTRIB);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("bad ndr decode "
+ "from EA on file %s: Error = %s\n",
+ smb_fname_str_dbg(smb_fname),
+ ndr_errstr(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ DBG_DEBUG("%s attr = %s\n",
+ smb_fname_str_dbg(smb_fname), dosattrib.attrib_hex);
+
+ switch (dosattrib.version) {
+ case 0xFFFF:
+ dosattr = dosattrib.info.compatinfoFFFF.attrib;
+ break;
+ case 1:
+ dosattr = dosattrib.info.info1.attrib;
+ if (!null_nttime(dosattrib.info.info1.create_time)) {
+ struct timespec create_time =
+ nt_time_to_unix_timespec(
+ dosattrib.info.info1.create_time);
+
+ update_stat_ex_create_time(&smb_fname->st,
+ create_time);
+
+ DBG_DEBUG("file %s case 1 set btime %s",
+ smb_fname_str_dbg(smb_fname),
+ time_to_asc(convert_timespec_to_time_t(
+ create_time)));
+ }
+ break;
+ case 2:
+ dosattr = dosattrib.info.oldinfo2.attrib;
+ /* Don't know what flags to check for this case. */
+ break;
+ case 3:
+ dosattr = dosattrib.info.info3.attrib;
+ if ((dosattrib.info.info3.valid_flags & XATTR_DOSINFO_CREATE_TIME) &&
+ !null_nttime(dosattrib.info.info3.create_time)) {
+ struct timespec create_time =
+ nt_time_to_full_timespec(
+ dosattrib.info.info3.create_time);
+
+ update_stat_ex_create_time(&smb_fname->st,
+ create_time);
+
+ DBG_DEBUG("file %s case 3 set btime %s",
+ smb_fname_str_dbg(smb_fname),
+ time_to_asc(convert_timespec_to_time_t(
+ create_time)));
+ }
+ break;
+ case 4:
+ case 5:
+ {
+ uint32_t info_valid_flags;
+ NTTIME info_create_time;
+
+ if (dosattrib.version == 4) {
+ info_valid_flags = dosattrib.info.info4.valid_flags;
+ info_create_time = dosattrib.info.info4.create_time;
+ dosattr = dosattrib.info.info4.attrib;
+ } else {
+ info_valid_flags = dosattrib.info.info5.valid_flags;
+ info_create_time = dosattrib.info.info5.create_time;
+ dosattr = dosattrib.info.info5.attrib;
+ }
+
+ if ((info_valid_flags & XATTR_DOSINFO_CREATE_TIME) &&
+ !null_nttime(info_create_time))
+ {
+ struct timespec creat_time;
+
+ creat_time = nt_time_to_full_timespec(info_create_time);
+ update_stat_ex_create_time(&smb_fname->st, creat_time);
+
+ DBG_DEBUG("file [%s] creation time [%s]\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_time_string(talloc_tos(), info_create_time));
+ }
+
+ break;
+ }
+ default:
+ DBG_WARNING("Badly formed DOSATTRIB on file %s - %s\n",
+ smb_fname_str_dbg(smb_fname), blob.data);
+ /* Should this be INTERNAL_ERROR? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ dosattr |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+
+ /* FILE_ATTRIBUTE_SPARSE is valid on get but not on set. */
+ *pattr |= (uint32_t)(dosattr & (SAMBA_ATTRIBUTES_MASK|FILE_ATTRIBUTE_SPARSE));
+
+ dos_mode_debug_print(__func__, *pattr);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS fget_ea_dos_attribute(struct files_struct *fsp,
+ uint32_t *pattr)
+{
+ DATA_BLOB blob;
+ ssize_t sizeret;
+ fstring attrstr;
+ NTSTATUS status;
+
+ if (!lp_store_dos_attributes(SNUM(fsp->conn))) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ /* Don't reset pattr to zero as we may already have filename-based attributes we
+ need to preserve. */
+
+ sizeret = SMB_VFS_FGETXATTR(fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ attrstr,
+ sizeof(attrstr));
+ if (sizeret == -1 && ( errno == EPERM || errno == EACCES )) {
+ /* we may also retrieve dos attribs for unreadable files, this
+ is why we'll retry as root. We don't use root in the first
+ run because in cases like NFS, root might have even less
+ rights than the real user
+ */
+ become_root();
+ sizeret = SMB_VFS_FGETXATTR(fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ attrstr,
+ sizeof(attrstr));
+ unbecome_root();
+ }
+ if (sizeret == -1) {
+ DBG_INFO("Cannot get attribute "
+ "from EA on file %s: Error = %s\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ blob.data = (uint8_t *)attrstr;
+ blob.length = sizeret;
+
+ status = parse_dos_attribute_blob(fsp->fsp_name, blob, pattr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Set DOS attributes in an EA.
+ Also sets the create time.
+****************************************************************************/
+
+NTSTATUS set_ea_dos_attribute(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode)
+{
+ struct xattr_DOSATTRIB dosattrib = { .version = 0, };
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = { .data = NULL, };
+ struct timespec btime;
+ int ret;
+
+ if (!lp_store_dos_attributes(SNUM(conn))) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (smb_fname->fsp == NULL) {
+ /* symlink */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ /*
+ * Don't store FILE_ATTRIBUTE_OFFLINE, it's dealt with in
+ * vfs_default via DMAPI if that is enabled.
+ */
+ dosmode &= ~FILE_ATTRIBUTE_OFFLINE;
+
+ dosattrib.version = 5;
+ dosattrib.info.info5.valid_flags = XATTR_DOSINFO_ATTRIB |
+ XATTR_DOSINFO_CREATE_TIME;
+ dosattrib.info.info5.attrib = dosmode;
+ dosattrib.info.info5.create_time = full_timespec_to_nt_time(
+ &smb_fname->st.st_ex_btime);
+
+ DEBUG(10,("set_ea_dos_attributes: set attribute 0x%x, btime = %s on file %s\n",
+ (unsigned int)dosmode,
+ time_to_asc(convert_timespec_to_time_t(smb_fname->st.st_ex_btime)),
+ smb_fname_str_dbg(smb_fname) ));
+
+ ndr_err = ndr_push_struct_blob(
+ &blob, talloc_tos(), &dosattrib,
+ (ndr_push_flags_fn_t)ndr_push_xattr_DOSATTRIB);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(5, ("create_acl_blob: ndr_push_xattr_DOSATTRIB failed: %s\n",
+ ndr_errstr(ndr_err)));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ if (blob.data == NULL || blob.length == 0) {
+ /* Should this be INTERNAL_ERROR? */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = SMB_VFS_FSETXATTR(smb_fname->fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ blob.data, blob.length, 0);
+ if (ret != 0) {
+ NTSTATUS status = NT_STATUS_OK;
+ bool set_dosmode_ok = false;
+
+ if ((errno != EPERM) && (errno != EACCES)) {
+ DBG_INFO("Cannot set "
+ "attribute EA on file %s: Error = %s\n",
+ smb_fname_str_dbg(smb_fname), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* We want DOS semantics, ie allow non owner with write permission to change the
+ bits on a file. Just like file_ntimes below.
+ */
+
+ /* Check if we have write access. */
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = smbd_check_access_rights_fsp(conn->cwd_fsp,
+ smb_fname->fsp,
+ false,
+ FILE_WRITE_ATTRIBUTES);
+ if (NT_STATUS_IS_OK(status)) {
+ set_dosmode_ok = true;
+ }
+
+ if (!set_dosmode_ok && lp_dos_filemode(SNUM(conn))) {
+ set_dosmode_ok = can_write_to_fsp(smb_fname->fsp);
+ }
+
+ if (!set_dosmode_ok) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ become_root();
+ ret = SMB_VFS_FSETXATTR(smb_fname->fsp,
+ SAMBA_XATTR_DOS_ATTRIB,
+ blob.data, blob.length, 0);
+ if (ret == 0) {
+ status = NT_STATUS_OK;
+ }
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /*
+ * We correctly stored the create time.
+ * We *always* set XATTR_DOSINFO_CREATE_TIME,
+ * so now it can no longer be considered
+ * calculated. Make sure to use the value rounded
+ * to NTTIME granularity we've stored in the xattr.
+ */
+ btime = nt_time_to_full_timespec(dosattrib.info.info5.create_time);
+ update_stat_ex_create_time(&smb_fname->st, btime);
+
+ DEBUG(10,("set_ea_dos_attribute: set EA 0x%x on file %s\n",
+ (unsigned int)dosmode,
+ smb_fname_str_dbg(smb_fname)));
+ return NT_STATUS_OK;
+}
+
+static uint32_t
+dos_mode_from_name(connection_struct *conn, const char *name, uint32_t dosmode)
+{
+ const char *p = NULL;
+ uint32_t result = dosmode;
+
+ if (!(result & FILE_ATTRIBUTE_HIDDEN) &&
+ lp_hide_dot_files(SNUM(conn)))
+ {
+ p = strrchr_m(name, '/');
+ if (p) {
+ p++;
+ } else {
+ p = name;
+ }
+
+ /* Only . and .. are not hidden. */
+ if ((p[0] == '.') && !(ISDOT(p) || ISDOTDOT(p))) {
+ result |= FILE_ATTRIBUTE_HIDDEN;
+ }
+ }
+
+ if (!(result & FILE_ATTRIBUTE_HIDDEN) && IS_HIDDEN_PATH(conn, name)) {
+ result |= FILE_ATTRIBUTE_HIDDEN;
+ }
+
+ return result;
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode for an ms dfs link.
+****************************************************************************/
+
+uint32_t dos_mode_msdfs(connection_struct *conn,
+ const char *name,
+ const struct stat_ex *st)
+{
+ uint32_t result = 0;
+
+ DEBUG(8, ("dos_mode_msdfs: %s\n", name));
+
+ if (!VALID_STAT(*st)) {
+ return 0;
+ }
+
+ result = dos_mode_from_name(conn, name, result);
+ result |= dos_mode_from_sbuf(conn, st, NULL);
+
+ if (result == 0) {
+ result = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ result = filter_mode_by_protocol(result);
+
+ /*
+ * Add in that it is a reparse point
+ */
+ result |= FILE_ATTRIBUTE_REPARSE_POINT;
+
+ dos_mode_debug_print(__func__, result);
+
+ return(result);
+}
+
+/*
+ * check whether a file or directory is flagged as compressed.
+ */
+static NTSTATUS dos_mode_check_compressed(struct files_struct *fsp,
+ bool *is_compressed)
+{
+ NTSTATUS status;
+ uint16_t compression_fmt;
+
+ status = SMB_VFS_FGET_COMPRESSION(
+ fsp->conn, talloc_tos(), fsp, &compression_fmt);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (compression_fmt == COMPRESSION_FORMAT_LZNT1) {
+ *is_compressed = true;
+ } else {
+ *is_compressed = false;
+ }
+ return NT_STATUS_OK;
+}
+
+static uint32_t dos_mode_post(uint32_t dosmode,
+ struct files_struct *fsp,
+ const char *func)
+{
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ if (fsp != NULL) {
+ smb_fname = fsp->fsp_name;
+ }
+ SMB_ASSERT(smb_fname != NULL);
+
+ /*
+ * According to MS-FSA a stream name does not have
+ * separate DOS attribute metadata, so we must return
+ * the DOS attribute from the base filename. With one caveat,
+ * a non-default stream name can never be a directory.
+ *
+ * As this is common to all streams data stores, we handle
+ * it here instead of inside all stream VFS modules.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13380
+ */
+
+ if (is_named_stream(smb_fname)) {
+ /* is_ntfs_stream_smb_fname() returns false for a POSIX path. */
+ dosmode &= ~(FILE_ATTRIBUTE_DIRECTORY);
+ }
+
+ if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
+ bool compressed = false;
+
+ status = dos_mode_check_compressed(fsp, &compressed);
+ if (NT_STATUS_IS_OK(status) && compressed) {
+ dosmode |= FILE_ATTRIBUTE_COMPRESSED;
+ }
+ }
+
+ dosmode |= dos_mode_from_name(fsp->conn, smb_fname->base_name, dosmode);
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ dosmode |= FILE_ATTRIBUTE_DIRECTORY;
+ } else if (dosmode == 0) {
+ dosmode = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ dosmode = filter_mode_by_protocol(dosmode);
+
+ dos_mode_debug_print(func, dosmode);
+ return dosmode;
+}
+
+/****************************************************************************
+ Change a unix mode to a dos mode.
+ May also read the create timespec into the stat struct in smb_fname
+ if "store dos attributes" is true.
+****************************************************************************/
+
+uint32_t fdos_mode(struct files_struct *fsp)
+{
+ uint32_t result = 0;
+ NTSTATUS status = NT_STATUS_OK;
+
+ DBG_DEBUG("%s\n", fsp_str_dbg(fsp));
+
+ if (fsp->fake_file_handle != NULL) {
+ return dosmode_from_fake_filehandle(fsp->fake_file_handle);
+ }
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return 0;
+ }
+
+ if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ return FILE_ATTRIBUTE_NORMAL;
+ }
+
+ if (fsp->fsp_name->st.cached_dos_attributes != FILE_ATTRIBUTE_INVALID) {
+ return fsp->fsp_name->st.cached_dos_attributes;
+ }
+
+ /* Get the DOS attributes via the VFS if we can */
+ status = SMB_VFS_FGET_DOS_ATTRIBUTES(fsp->conn,
+ metadata_fsp(fsp),
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Only fall back to using UNIX modes if we get NOT_IMPLEMENTED.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ result |= dos_mode_from_sbuf(fsp->conn,
+ &fsp->fsp_name->st,
+ fsp);
+ }
+ }
+
+ fsp->fsp_name->st.cached_dos_attributes = dos_mode_post(result, fsp, __func__);
+ return fsp->fsp_name->st.cached_dos_attributes;
+}
+
+struct dos_mode_at_state {
+ files_struct *dir_fsp;
+ struct smb_filename *smb_fname;
+ uint32_t dosmode;
+};
+
+static void dos_mode_at_vfs_get_dosmode_done(struct tevent_req *subreq);
+
+struct tevent_req *dos_mode_at_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct dos_mode_at_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ DBG_DEBUG("%s\n", smb_fname_str_dbg(smb_fname));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct dos_mode_at_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct dos_mode_at_state) {
+ .dir_fsp = dir_fsp,
+ .smb_fname = smb_fname,
+ };
+
+ if (!VALID_STAT(smb_fname->st)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb_fname->fsp == NULL) {
+ if (ISDOTDOT(smb_fname->base_name)) {
+ /*
+ * smb_fname->fsp is explicitly closed
+ * for ".." to prevent meta-data leakage.
+ */
+ state->dosmode = FILE_ATTRIBUTE_DIRECTORY;
+ } else {
+ /*
+ * This is a symlink in POSIX context.
+ * FIXME ? Should we move to returning
+ * FILE_ATTRIBUTE_REPARSE_POINT here ?
+ */
+ state->dosmode = FILE_ATTRIBUTE_NORMAL;
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_GET_DOS_ATTRIBUTES_SEND(state,
+ ev,
+ dir_fsp,
+ smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, dos_mode_at_vfs_get_dosmode_done, req);
+
+ return req;
+}
+
+static void dos_mode_at_vfs_get_dosmode_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct dos_mode_at_state *state =
+ tevent_req_data(req,
+ struct dos_mode_at_state);
+ struct vfs_aio_state aio_state;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dir_fsp);
+ SMB_ASSERT(ok);
+
+ status = SMB_VFS_GET_DOS_ATTRIBUTES_RECV(subreq,
+ &aio_state,
+ &state->dosmode);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Both the sync dos_mode() as well as the async
+ * dos_mode_at_[send|recv] have no real error return, the only
+ * unhandled error is when the stat info in smb_fname is not
+ * valid (cf the checks in dos_mode() and dos_mode_at_send().
+ *
+ * If SMB_VFS_GET_DOS_ATTRIBUTES[_SEND|_RECV] fails we must call
+ * dos_mode_post() which also does the mapping of a last resort
+ * from S_IFMT(st_mode).
+ *
+ * Only if we get NT_STATUS_NOT_IMPLEMENTED or
+ * NT_STATUS_NOT_SUPPORTED from a stacked VFS module we must
+ * fallback to sync processing.
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED))
+ {
+ /*
+ * state->dosmode should still be 0, but reset
+ * it to be sure.
+ */
+ state->dosmode = 0;
+ status = NT_STATUS_OK;
+ }
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ state->dosmode = dos_mode_post(state->dosmode,
+ state->smb_fname->fsp,
+ __func__);
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Fall back to sync dos_mode() if we got NOT_IMPLEMENTED.
+ */
+
+ state->dosmode = fdos_mode(state->smb_fname->fsp);
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS dos_mode_at_recv(struct tevent_req *req, uint32_t *dosmode)
+{
+ struct dos_mode_at_state *state =
+ tevent_req_data(req,
+ struct dos_mode_at_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *dosmode = state->dosmode;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ chmod a file - but preserve some bits.
+ If "store dos attributes" is also set it will store the create time
+ from the stat struct in smb_fname (in NTTIME format) in the EA
+ attribute also.
+********************************************************************/
+
+int file_set_dosmode(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode,
+ struct smb_filename *parent_dir,
+ bool newfile)
+{
+ int mask=0;
+ mode_t tmp;
+ mode_t unixmode;
+ int ret = -1;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(conn)) {
+ errno = EROFS;
+ return -1;
+ }
+
+ if (S_ISLNK(smb_fname->st.st_ex_mode)) {
+ /* A symlink in POSIX context, ignore */
+ return 0;
+ }
+
+ if ((S_ISDIR(smb_fname->st.st_ex_mode)) &&
+ (dosmode & FILE_ATTRIBUTE_TEMPORARY))
+ {
+ errno = EINVAL;
+ return -1;
+ }
+
+ dosmode &= SAMBA_ATTRIBUTES_MASK;
+
+ DEBUG(10,("file_set_dosmode: setting dos mode 0x%x on file %s\n",
+ dosmode, smb_fname_str_dbg(smb_fname)));
+
+ if (smb_fname->fsp == NULL) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (smb_fname->fsp->posix_flags & FSP_POSIX_FLAGS_OPEN &&
+ !lp_store_dos_attributes(SNUM(conn)))
+ {
+ return 0;
+ }
+
+ unixmode = smb_fname->st.st_ex_mode;
+
+ get_acl_group_bits(conn, smb_fname->fsp, &smb_fname->st.st_ex_mode);
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode))
+ dosmode |= FILE_ATTRIBUTE_DIRECTORY;
+ else
+ dosmode &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+ /* Store the DOS attributes in an EA by preference. */
+ status = SMB_VFS_FSET_DOS_ATTRIBUTES(conn,
+ metadata_fsp(smb_fname->fsp),
+ dosmode);
+ if (NT_STATUS_IS_OK(status)) {
+ smb_fname->st.cached_dos_attributes = dosmode;
+ ret = 0;
+ goto done;
+ }
+
+ /*
+ * Only fall back to using UNIX modes if
+ * we get NOT_IMPLEMENTED.
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ /* Fall back to UNIX modes. */
+ unixmode = unix_mode(
+ conn,
+ dosmode,
+ smb_fname,
+ parent_dir != NULL ? parent_dir->fsp : NULL);
+
+ /* preserve the file type bits */
+ mask |= S_IFMT;
+
+ /* preserve the s bits */
+ mask |= (S_ISUID | S_ISGID);
+
+ /* preserve the t bit */
+#ifdef S_ISVTX
+ mask |= S_ISVTX;
+#endif
+
+ /* possibly preserve the x bits */
+ if (!MAP_ARCHIVE(conn))
+ mask |= S_IXUSR;
+ if (!MAP_SYSTEM(conn))
+ mask |= S_IXGRP;
+ if (!MAP_HIDDEN(conn))
+ mask |= S_IXOTH;
+
+ unixmode |= (smb_fname->st.st_ex_mode & mask);
+
+ /* if we previously had any r bits set then leave them alone */
+ if ((tmp = smb_fname->st.st_ex_mode & (S_IRUSR|S_IRGRP|S_IROTH))) {
+ unixmode &= ~(S_IRUSR|S_IRGRP|S_IROTH);
+ unixmode |= tmp;
+ }
+
+ /* if we previously had any w bits set then leave them alone
+ whilst adding in the new w bits, if the new mode is not rdonly */
+ if (!(dosmode & FILE_ATTRIBUTE_READONLY)) {
+ unixmode |= (smb_fname->st.st_ex_mode & (S_IWUSR|S_IWGRP|S_IWOTH));
+ }
+
+ /*
+ * From the chmod 2 man page:
+ *
+ * "If the calling process is not privileged, and the group of the file
+ * does not match the effective group ID of the process or one of its
+ * supplementary group IDs, the S_ISGID bit will be turned off, but
+ * this will not cause an error to be returned."
+ *
+ * Simply refuse to do the chmod in this case.
+ */
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode) &&
+ (unixmode & S_ISGID) &&
+ geteuid() != sec_initial_uid() &&
+ !current_user_in_group(conn, smb_fname->st.st_ex_gid))
+ {
+ DEBUG(3,("file_set_dosmode: setgid bit cannot be "
+ "set for directory %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ errno = EPERM;
+ return -1;
+ }
+
+ ret = SMB_VFS_FCHMOD(smb_fname->fsp, unixmode);
+ if (ret == 0) {
+ goto done;
+ }
+
+ if((errno != EPERM) && (errno != EACCES))
+ return -1;
+
+ if(!lp_dos_filemode(SNUM(conn)))
+ return -1;
+
+ /* We want DOS semantics, ie allow non owner with write permission to change the
+ bits on a file. Just like file_ntimes below.
+ */
+
+ if (!can_write_to_fsp(smb_fname->fsp))
+ {
+ errno = EACCES;
+ return -1;
+ }
+
+ become_root();
+ ret = SMB_VFS_FCHMOD(smb_fname->fsp, unixmode);
+ unbecome_root();
+
+done:
+ if (!newfile) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ smb_fname->base_name);
+ }
+ if (ret == 0) {
+ smb_fname->st.st_ex_mode = unixmode;
+ }
+
+ return( ret );
+}
+
+
+NTSTATUS file_set_sparse(connection_struct *conn,
+ files_struct *fsp,
+ bool sparse)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t old_dosmode;
+ uint32_t new_dosmode;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(conn)) {
+ DEBUG(9,("file_set_sparse: fname[%s] set[%u] "
+ "on readonly share[%s]\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ sparse,
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ /*
+ * Windows Server 2008 & 2012 permit FSCTL_SET_SPARSE if any of the
+ * following access flags are granted.
+ */
+ status = check_any_access_fsp(fsp,
+ FILE_WRITE_DATA
+ | FILE_WRITE_ATTRIBUTES
+ | SEC_FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fname[%s] set[%u] "
+ "access_mask[0x%08X] - access denied\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ sparse,
+ fsp->access_mask);
+ return status;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ DEBUG(9, ("invalid attempt to %s sparse flag on dir %s\n",
+ (sparse ? "set" : "clear"),
+ smb_fname_str_dbg(fsp->fsp_name)));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (IS_IPC(conn) || IS_PRINT(conn)) {
+ DEBUG(9, ("attempt to %s sparse flag over invalid conn\n",
+ (sparse ? "set" : "clear")));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * MS-FSA 2.1.1.5 IsSparse
+ *
+ * This is a per stream attribute, but our backends don't
+ * support it a consistent way, therefore just pretend
+ * success and ignore the request.
+ */
+ DBG_DEBUG("Ignoring request to set FILE_ATTRIBUTE_SPARSE on "
+ "[%s]\n", fsp_str_dbg(fsp));
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(10,("file_set_sparse: setting sparse bit %u on file %s\n",
+ sparse, smb_fname_str_dbg(fsp->fsp_name)));
+
+ if (!lp_store_dos_attributes(SNUM(conn))) {
+ return NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ old_dosmode = fdos_mode(fsp);
+
+ if (sparse && !(old_dosmode & FILE_ATTRIBUTE_SPARSE)) {
+ new_dosmode = old_dosmode | FILE_ATTRIBUTE_SPARSE;
+ } else if (!sparse && (old_dosmode & FILE_ATTRIBUTE_SPARSE)) {
+ new_dosmode = old_dosmode & ~FILE_ATTRIBUTE_SPARSE;
+ } else {
+ return NT_STATUS_OK;
+ }
+
+ /* Store the DOS attributes in an EA. */
+ status = SMB_VFS_FSET_DOS_ATTRIBUTES(conn, fsp, new_dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+
+ fsp->fsp_name->st.cached_dos_attributes = new_dosmode;
+ fsp->fsp_flags.is_sparse = sparse;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Wrapper around the VFS ntimes that possibly allows DOS semantics rather
+ than POSIX.
+*******************************************************************/
+
+int file_ntimes(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ int ret = -1;
+
+ errno = 0;
+
+ DBG_INFO("actime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->atime)));
+ DBG_INFO("modtime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->mtime)));
+ DBG_INFO("ctime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->ctime)));
+ DBG_INFO("createtime: %s",
+ time_to_asc(convert_timespec_to_time_t(ft->create_time)));
+
+ /* Don't update the time on read-only shares */
+ /* We need this as set_filetime (which can be called on
+ close and other paths) can end up calling this function
+ without the NEED_WRITE protection. Found by :
+ Leo Weppelman <leo@wau.mis.ah.nl>
+ */
+
+ if (!CAN_WRITE(conn)) {
+ return 0;
+ }
+
+ if (SMB_VFS_FNTIMES(fsp, ft) == 0) {
+ return 0;
+ }
+
+ if((errno != EPERM) && (errno != EACCES)) {
+ return -1;
+ }
+
+ if(!lp_dos_filetimes(SNUM(conn))) {
+ return -1;
+ }
+
+ /* We have permission (given by the Samba admin) to
+ break POSIX semantics and allow a user to change
+ the time on a file they don't own but can write to
+ (as DOS does).
+ */
+
+ /* Check if we have write access. */
+ if (can_write_to_fsp(fsp)) {
+ /* We are allowed to become root and change the filetime. */
+ become_root();
+ ret = SMB_VFS_FNTIMES(fsp, ft);
+ unbecome_root();
+ }
+
+ return ret;
+}
+
+/******************************************************************
+ Force a "sticky" write time on a pathname. This will always be
+ returned on all future write time queries and set on close.
+******************************************************************/
+
+bool set_sticky_write_time_path(struct file_id fileid, struct timespec mtime)
+{
+ if (is_omit_timespec(&mtime)) {
+ return true;
+ }
+
+ if (!set_sticky_write_time(fileid, mtime)) {
+ return false;
+ }
+
+ return true;
+}
+
+/******************************************************************
+ Force a "sticky" write time on an fsp. This will always be
+ returned on all future write time queries and set on close.
+******************************************************************/
+
+bool set_sticky_write_time_fsp(struct files_struct *fsp, struct timespec mtime)
+{
+ if (is_omit_timespec(&mtime)) {
+ return true;
+ }
+
+ fsp->fsp_flags.write_time_forced = true;
+ TALLOC_FREE(fsp->update_write_time_event);
+
+ return set_sticky_write_time_path(fsp->file_id, mtime);
+}
+
+/******************************************************************
+ Set a create time EA.
+******************************************************************/
+
+NTSTATUS set_create_timespec_ea(struct files_struct *fsp,
+ struct timespec create_time)
+{
+ uint32_t dosmode;
+ int ret;
+
+ if (!lp_store_dos_attributes(SNUM(fsp->conn))) {
+ return NT_STATUS_OK;
+ }
+
+ dosmode = fdos_mode(fsp);
+
+ fsp->fsp_name->st.st_ex_btime = create_time;
+ ret = file_set_dosmode(fsp->conn, fsp->fsp_name, dosmode, NULL, false);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ DBG_DEBUG("wrote create time EA for file %s\n",
+ smb_fname_str_dbg(fsp->fsp_name));
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************
+ Return a create time.
+******************************************************************/
+
+struct timespec get_create_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname)
+{
+ if (fsp != NULL) {
+ struct files_struct *meta_fsp = metadata_fsp(fsp);
+ return meta_fsp->fsp_name->st.st_ex_btime;
+ }
+ return smb_fname->st.st_ex_btime;
+}
+
+/******************************************************************
+ Return a change time (may look at EA in future).
+******************************************************************/
+
+struct timespec get_change_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname)
+{
+ return smb_fname->st.st_ex_mtime;
+}
diff --git a/source3/smbd/durable.c b/source3/smbd/durable.c
new file mode 100644
index 0000000..5f55ff6
--- /dev/null
+++ b/source3/smbd/durable.c
@@ -0,0 +1,938 @@
+/*
+ Unix SMB/CIFS implementation.
+ Durable Handle default VFS implementation
+
+ Copyright (C) Stefan Metzmacher 2012
+ Copyright (C) Michael Adam 2012
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "messages.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
+#include "serverid.h"
+#include "fake_file.h"
+#include "locking/leases_db.h"
+
+NTSTATUS vfs_default_durable_cookie(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie_blob)
+{
+ struct connection_struct *conn = fsp->conn;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+
+ if (!lp_durable_handles(SNUM(conn))) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (lp_kernel_share_modes(SNUM(conn))) {
+ /*
+ * We do not support durable handles
+ * if file system sharemodes are used
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (lp_kernel_oplocks(SNUM(conn))) {
+ /*
+ * We do not support durable handles
+ * if kernel oplocks are used
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if ((fsp->current_lock_count > 0) &&
+ lp_posix_locking(fsp->conn->params))
+ {
+ /*
+ * We do not support durable handles
+ * if the handle has posix locks.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * We do not support durable handles
+ * on streams for now.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (is_fake_file(fsp->fsp_name)) {
+ /*
+ * We do not support durable handles
+ * on fake files.
+ */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ ZERO_STRUCT(cookie);
+ cookie.allow_reconnect = false;
+ cookie.id = fsp->file_id;
+ cookie.servicepath = conn->connectpath;
+ cookie.base_name = fsp->fsp_name->base_name;
+ cookie.initial_allocation_size = fsp->initial_allocation_size;
+ cookie.position_information = fh_get_position_information(fsp->fh);
+ cookie.update_write_time_triggered =
+ fsp->fsp_flags.update_write_time_triggered;
+ cookie.update_write_time_on_close =
+ fsp->fsp_flags.update_write_time_on_close;
+ cookie.write_time_forced = fsp->fsp_flags.write_time_forced;
+ cookie.close_write_time = full_timespec_to_nt_time(
+ &fsp->close_write_time);
+
+ cookie.stat_info.st_ex_dev = fsp->fsp_name->st.st_ex_dev;
+ cookie.stat_info.st_ex_ino = fsp->fsp_name->st.st_ex_ino;
+ cookie.stat_info.st_ex_mode = fsp->fsp_name->st.st_ex_mode;
+ cookie.stat_info.st_ex_nlink = fsp->fsp_name->st.st_ex_nlink;
+ cookie.stat_info.st_ex_uid = fsp->fsp_name->st.st_ex_uid;
+ cookie.stat_info.st_ex_gid = fsp->fsp_name->st.st_ex_gid;
+ cookie.stat_info.st_ex_rdev = fsp->fsp_name->st.st_ex_rdev;
+ cookie.stat_info.st_ex_size = fsp->fsp_name->st.st_ex_size;
+ cookie.stat_info.st_ex_atime = fsp->fsp_name->st.st_ex_atime;
+ cookie.stat_info.st_ex_mtime = fsp->fsp_name->st.st_ex_mtime;
+ cookie.stat_info.st_ex_ctime = fsp->fsp_name->st.st_ex_ctime;
+ cookie.stat_info.st_ex_btime = fsp->fsp_name->st.st_ex_btime;
+ cookie.stat_info.st_ex_iflags = fsp->fsp_name->st.st_ex_iflags;
+ cookie.stat_info.st_ex_blksize = fsp->fsp_name->st.st_ex_blksize;
+ cookie.stat_info.st_ex_blocks = fsp->fsp_name->st.st_ex_blocks;
+ cookie.stat_info.st_ex_flags = fsp->fsp_name->st.st_ex_flags;
+
+ ndr_err = ndr_push_struct_blob(cookie_blob, mem_ctx, &cookie,
+ (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ struct connection_struct *conn = fsp->conn;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+ DATA_BLOB new_cookie_blob = data_blob_null;
+ struct share_mode_lock *lck;
+ bool ok;
+
+ *new_cookie = data_blob_null;
+
+ ZERO_STRUCT(cookie);
+
+ ndr_err = ndr_pull_struct_blob(&old_cookie, talloc_tos(), &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!file_id_equal(&fsp->file_id, &cookie.id)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ((fsp_lease_type(fsp) & SMB2_LEASE_HANDLE) == 0) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * For now let it be simple and do not keep
+ * delete on close files durable open
+ */
+ if (fsp->fsp_flags.initial_delete_on_close) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ if (fsp->fsp_flags.delete_on_close) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* Ensure any pending write time updates are done. */
+ if (fsp->update_write_time_event) {
+ fsp_flush_write_time_update(fsp);
+ }
+
+ /*
+ * The above checks are done in mark_share_mode_disconnected() too
+ * but we want to avoid getting the lock if possible
+ */
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck != NULL) {
+ struct smb_file_time ft;
+
+ init_smb_file_time(&ft);
+
+ if (fsp->fsp_flags.write_time_forced) {
+ NTTIME mtime = share_mode_changed_write_time(lck);
+ ft.mtime = nt_time_to_full_timespec(mtime);
+ } else if (fsp->fsp_flags.update_write_time_on_close) {
+ if (is_omit_timespec(&fsp->close_write_time)) {
+ ft.mtime = timespec_current();
+ } else {
+ ft.mtime = fsp->close_write_time;
+ }
+ }
+
+ if (!is_omit_timespec(&ft.mtime)) {
+ round_timespec(conn->ts_res, &ft.mtime);
+ file_ntimes(conn, fsp, &ft);
+ }
+
+ ok = mark_share_mode_disconnected(lck, fsp);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ }
+ }
+ if (lck != NULL) {
+ ok = brl_mark_disconnected(fsp);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ }
+ }
+ if (lck == NULL) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+ TALLOC_FREE(lck);
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ZERO_STRUCT(cookie);
+ cookie.allow_reconnect = true;
+ cookie.id = fsp->file_id;
+ cookie.servicepath = conn->connectpath;
+ cookie.base_name = fsp_str_dbg(fsp);
+ cookie.initial_allocation_size = fsp->initial_allocation_size;
+ cookie.position_information = fh_get_position_information(fsp->fh);
+ cookie.update_write_time_triggered =
+ fsp->fsp_flags.update_write_time_triggered;
+ cookie.update_write_time_on_close =
+ fsp->fsp_flags.update_write_time_on_close;
+ cookie.write_time_forced = fsp->fsp_flags.write_time_forced;
+ cookie.close_write_time = full_timespec_to_nt_time(
+ &fsp->close_write_time);
+
+ cookie.stat_info.st_ex_dev = fsp->fsp_name->st.st_ex_dev;
+ cookie.stat_info.st_ex_ino = fsp->fsp_name->st.st_ex_ino;
+ cookie.stat_info.st_ex_mode = fsp->fsp_name->st.st_ex_mode;
+ cookie.stat_info.st_ex_nlink = fsp->fsp_name->st.st_ex_nlink;
+ cookie.stat_info.st_ex_uid = fsp->fsp_name->st.st_ex_uid;
+ cookie.stat_info.st_ex_gid = fsp->fsp_name->st.st_ex_gid;
+ cookie.stat_info.st_ex_rdev = fsp->fsp_name->st.st_ex_rdev;
+ cookie.stat_info.st_ex_size = fsp->fsp_name->st.st_ex_size;
+ cookie.stat_info.st_ex_atime = fsp->fsp_name->st.st_ex_atime;
+ cookie.stat_info.st_ex_mtime = fsp->fsp_name->st.st_ex_mtime;
+ cookie.stat_info.st_ex_ctime = fsp->fsp_name->st.st_ex_ctime;
+ cookie.stat_info.st_ex_btime = fsp->fsp_name->st.st_ex_btime;
+ cookie.stat_info.st_ex_iflags = fsp->fsp_name->st.st_ex_iflags;
+ cookie.stat_info.st_ex_blksize = fsp->fsp_name->st.st_ex_blksize;
+ cookie.stat_info.st_ex_blocks = fsp->fsp_name->st.st_ex_blocks;
+ cookie.stat_info.st_ex_flags = fsp->fsp_name->st.st_ex_flags;
+
+ ndr_err = ndr_push_struct_blob(&new_cookie_blob, mem_ctx, &cookie,
+ (ndr_push_flags_fn_t)ndr_push_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(&new_cookie_blob);
+ return status;
+ }
+
+ *new_cookie = new_cookie_blob;
+ return NT_STATUS_OK;
+}
+
+
+/**
+ * Check whether a cookie-stored struct info is the same
+ * as a given SMB_STRUCT_STAT, as coming with the fsp.
+ */
+static bool vfs_default_durable_reconnect_check_stat(
+ struct vfs_default_durable_stat *cookie_st,
+ SMB_STRUCT_STAT *fsp_st,
+ const char *name)
+{
+ int ret;
+
+ if (cookie_st->st_ex_mode != fsp_st->st_ex_mode) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_mode",
+ (unsigned long long)cookie_st->st_ex_mode,
+ (unsigned long long)fsp_st->st_ex_mode));
+ return false;
+ }
+
+ if (cookie_st->st_ex_nlink != fsp_st->st_ex_nlink) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_nlink",
+ (unsigned long long)cookie_st->st_ex_nlink,
+ (unsigned long long)fsp_st->st_ex_nlink));
+ return false;
+ }
+
+ if (cookie_st->st_ex_uid != fsp_st->st_ex_uid) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_uid",
+ (unsigned long long)cookie_st->st_ex_uid,
+ (unsigned long long)fsp_st->st_ex_uid));
+ return false;
+ }
+
+ if (cookie_st->st_ex_gid != fsp_st->st_ex_gid) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_gid",
+ (unsigned long long)cookie_st->st_ex_gid,
+ (unsigned long long)fsp_st->st_ex_gid));
+ return false;
+ }
+
+ if (cookie_st->st_ex_rdev != fsp_st->st_ex_rdev) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_rdev",
+ (unsigned long long)cookie_st->st_ex_rdev,
+ (unsigned long long)fsp_st->st_ex_rdev));
+ return false;
+ }
+
+ if (cookie_st->st_ex_size != fsp_st->st_ex_size) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_size",
+ (unsigned long long)cookie_st->st_ex_size,
+ (unsigned long long)fsp_st->st_ex_size));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_atime,
+ &fsp_st->st_ex_atime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_atime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_atime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_atime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_mtime,
+ &fsp_st->st_ex_mtime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_mtime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_mtime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_mtime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_ctime,
+ &fsp_st->st_ex_ctime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_ctime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_ctime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_ctime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ ret = timespec_compare(&cookie_st->st_ex_btime,
+ &fsp_st->st_ex_btime);
+ if (ret != 0) {
+ struct timeval tc, ts;
+ tc = convert_timespec_to_timeval(cookie_st->st_ex_btime);
+ ts = convert_timespec_to_timeval(fsp_st->st_ex_btime);
+
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:'%s' != stat:'%s', "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_btime",
+ timeval_string(talloc_tos(), &tc, true),
+ timeval_string(talloc_tos(), &ts, true)));
+ return false;
+ }
+
+ if (cookie_st->st_ex_iflags != fsp_st->st_ex_iflags) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_calculated_birthtime",
+ (unsigned long long)cookie_st->st_ex_iflags,
+ (unsigned long long)fsp_st->st_ex_iflags));
+ return false;
+ }
+
+ if (cookie_st->st_ex_blksize != fsp_st->st_ex_blksize) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_blksize",
+ (unsigned long long)cookie_st->st_ex_blksize,
+ (unsigned long long)fsp_st->st_ex_blksize));
+ return false;
+ }
+
+ if (cookie_st->st_ex_blocks != fsp_st->st_ex_blocks) {
+ DEBUG(1, ("vfs_default_durable_reconnect (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%llu != stat:%llu, "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_blocks",
+ (unsigned long long)cookie_st->st_ex_blocks,
+ (unsigned long long)fsp_st->st_ex_blocks));
+ return false;
+ }
+
+ if (cookie_st->st_ex_flags != fsp_st->st_ex_flags) {
+ DBG_WARNING(" (%s): "
+ "stat_ex.%s differs: "
+ "cookie:%"PRIu32" != stat:%"PRIu32", "
+ "denying durable reconnect\n",
+ name,
+ "st_ex_flags",
+ cookie_st->st_ex_flags,
+ fsp_st->st_ex_flags);
+ return false;
+ }
+
+ return true;
+}
+
+static bool durable_reconnect_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct share_mode_entry *dst_e = private_data;
+
+ if (dst_e->pid.pid != 0) {
+ DBG_INFO("Found more than one entry, invalidating previous\n");
+ dst_e->pid.pid = 0;
+ return true; /* end the loop through share mode entries */
+ }
+ *dst_e = *e;
+ return false; /* Look at potential other entries */
+}
+
+NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ files_struct **result,
+ DATA_BLOB *new_cookie)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct share_mode_lock *lck;
+ struct share_mode_entry e;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+ bool ok;
+ int ret;
+ struct vfs_open_how how = { .flags = 0, };
+ struct file_id file_id;
+ struct smb_filename *smb_fname = NULL;
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+ DATA_BLOB new_cookie_blob = data_blob_null;
+
+ *result = NULL;
+ *new_cookie = data_blob_null;
+
+ if (!lp_durable_handles(SNUM(conn))) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * the checks for kernel oplocks
+ * and similar things are done
+ * in the vfs_default_durable_cookie()
+ * call below.
+ */
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &old_cookie,
+ talloc_tos(),
+ &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ if (strcmp(cookie.magic, VFS_DEFAULT_DURABLE_COOKIE_MAGIC) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (cookie.version != VFS_DEFAULT_DURABLE_COOKIE_VERSION) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!cookie.allow_reconnect) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (strcmp(cookie.servicepath, conn->connectpath) != 0) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ cookie.base_name,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_LSTAT(conn, smb_fname);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("Unable to lstat stream: %s => %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!S_ISREG(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ if (!file_id_equal(&cookie.id, &file_id)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * 1. check entry in locking.tdb
+ */
+
+ lck = get_existing_share_mode_lock(mem_ctx, file_id);
+ if (lck == NULL) {
+ DEBUG(5, ("vfs_default_durable_reconnect: share-mode lock "
+ "not obtained from db\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ e = (struct share_mode_entry) { .pid.pid = 0 };
+
+ ok = share_mode_forall_entries(lck, durable_reconnect_fn, &e);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_entries failed\n");
+ TALLOC_FREE(lck);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (e.pid.pid == 0) {
+ DBG_WARNING("Did not find a unique valid share mode entry\n");
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!server_id_is_disconnected(&e.pid)) {
+ DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
+ "reconnect for handle that was not marked "
+ "disconnected (e.g. smbd or cluster node died)\n"));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (e.share_file_id != op->global->open_persistent_id) {
+ DBG_INFO("denying durable "
+ "share_file_id changed %"PRIu64" != %"PRIu64" "
+ "(e.g. another client had opened the file)\n",
+ e.share_file_id,
+ op->global->open_persistent_id);
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if ((e.access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) &&
+ !CAN_WRITE(conn))
+ {
+ DEBUG(5, ("vfs_default_durable_reconnect: denying durable "
+ "share[%s] is not writeable anymore\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ TALLOC_FREE(lck);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * 2. proceed with opening file
+ */
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("vfs_default_durable_reconnect: failed to create "
+ "new fsp: %s\n", nt_errstr(status)));
+ TALLOC_FREE(lck);
+ return status;
+ }
+
+ fh_set_private_options(fsp->fh, e.private_options);
+ fsp->file_id = file_id;
+ fsp->file_pid = smb1req->smbpid;
+ fsp->vuid = smb1req->vuid;
+ fsp->open_time = e.time;
+ fsp->access_mask = e.access_mask;
+ fsp->fsp_flags.can_read = ((fsp->access_mask & FILE_READ_DATA) != 0);
+ fsp->fsp_flags.can_write = ((fsp->access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) != 0);
+ fsp->fnum = op->local_id;
+ fsp_set_gen_id(fsp);
+
+ /*
+ * TODO:
+ * Do we need to store the modified flag in the DB?
+ */
+ fsp->fsp_flags.modified = false;
+ /*
+ * no durables for directories
+ */
+ fsp->fsp_flags.is_directory = false;
+ /*
+ * For normal files, can_lock == !is_directory
+ */
+ fsp->fsp_flags.can_lock = true;
+ /*
+ * We do not support aio write behind for smb2
+ */
+ fsp->fsp_flags.aio_write_behind = false;
+ fsp->oplock_type = e.op_type;
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ uint32_t current_state;
+ uint16_t lease_version, epoch;
+
+ /*
+ * Ensure the existing client guid matches the
+ * stored one in the share_mode_entry.
+ */
+ if (!GUID_equal(fsp_client_guid(fsp),
+ &e.client_guid)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = leases_db_get(
+ &e.client_guid,
+ &e.lease_key,
+ &file_id,
+ &current_state, /* current_state */
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ &lease_version, /* lease_version */
+ &epoch); /* epoch */
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ fsp->lease = find_fsp_lease(
+ fsp,
+ &e.lease_key,
+ current_state,
+ lease_version,
+ epoch);
+ if (fsp->lease == NULL) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ fsp->initial_allocation_size = cookie.initial_allocation_size;
+ fh_set_position_information(fsp->fh, cookie.position_information);
+ fsp->fsp_flags.update_write_time_triggered =
+ cookie.update_write_time_triggered;
+ fsp->fsp_flags.update_write_time_on_close =
+ cookie.update_write_time_on_close;
+ fsp->fsp_flags.write_time_forced = cookie.write_time_forced;
+ fsp->close_write_time = nt_time_to_full_timespec(
+ cookie.close_write_time);
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ file_free(smb1req, fsp);
+ DEBUG(0, ("vfs_default_durable_reconnect: "
+ "fsp_set_smb_fname failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ op->compat = fsp;
+ fsp->op = op;
+
+ ok = reset_share_mode_entry(
+ lck,
+ e.pid,
+ e.share_file_id,
+ messaging_server_id(conn->sconn->msg_ctx),
+ smb1req->mid,
+ fh_get_gen_id(fsp->fh));
+ if (!ok) {
+ DBG_DEBUG("Could not set new share_mode_entry values\n");
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ok = brl_reconnect_disconnected(fsp);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ DEBUG(1, ("vfs_default_durable_reconnect: "
+ "failed to reopen brlocks: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ /*
+ * TODO: properly calculate open flags
+ */
+ if (fsp->fsp_flags.can_write && fsp->fsp_flags.can_read) {
+ how.flags = O_RDWR;
+ } else if (fsp->fsp_flags.can_write) {
+ how.flags = O_WRONLY;
+ } else if (fsp->fsp_flags.can_read) {
+ how.flags = O_RDONLY;
+ }
+
+ status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ DEBUG(1, ("vfs_default_durable_reconnect: failed to open "
+ "file: %s\n", nt_errstr(status)));
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ /*
+ * We now check the stat info stored in the cookie against
+ * the current stat data from the file we just opened.
+ * If any detail differs, we deny the durable reconnect,
+ * because in that case it is very likely that someone
+ * opened the file while the handle was disconnected,
+ * which has to be interpreted as an oplock break.
+ */
+
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ if (ret == -1) {
+ NTSTATUS close_status;
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("Unable to fstat stream: %s => %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status)));
+ close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+ if (!file_id_equal(&cookie.id, &file_id)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ (void)fdos_mode(fsp);
+
+ ok = vfs_default_durable_reconnect_check_stat(&cookie.stat_info,
+ &fsp->fsp_name->st,
+ fsp_str_dbg(fsp));
+ if (!ok) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = set_file_oplock(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS close_status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(close_status)) {
+ DBG_ERR("fd_close failed (%s) - leaking file "
+ "descriptor\n", nt_errstr(close_status));
+ }
+ TALLOC_FREE(lck);
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ status = vfs_default_durable_cookie(fsp, mem_ctx, &new_cookie_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ DEBUG(1, ("vfs_default_durable_reconnect: "
+ "vfs_default_durable_cookie - %s\n",
+ nt_errstr(status)));
+ op->compat = NULL;
+ fsp->op = NULL;
+ file_free(smb1req, fsp);
+ return status;
+ }
+
+ smb1req->chain_fsp = fsp;
+ smb1req->smb2req->compat_chain_fsp = fsp;
+
+ DEBUG(10, ("vfs_default_durable_reconnect: opened file '%s'\n",
+ fsp_str_dbg(fsp)));
+
+ TALLOC_FREE(lck);
+
+ fsp->fsp_flags.is_fsa = true;
+
+ *result = fsp;
+ *new_cookie = new_cookie_blob;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/error.c b/source3/smbd/error.c
new file mode 100644
index 0000000..fdea191
--- /dev/null
+++ b/source3/smbd/error.c
@@ -0,0 +1,175 @@
+/*
+ Unix SMB/CIFS implementation.
+ error packet handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+bool use_nt_status(void)
+{
+ return lp_nt_status_support() && (global_client_caps & CAP_STATUS32);
+}
+
+/****************************************************************************
+ Create an error packet. Normally called using the ERROR() macro.
+
+ Setting eclass and ecode to zero and status to a valid NT error will
+ reply with an NT error if the client supports CAP_STATUS32, otherwise
+ it maps to and returns a DOS error if the client doesn't support CAP_STATUS32.
+ This is the normal mode of calling this function via reply_nterror(req, status).
+
+ Setting eclass and ecode to non-zero and status to NT_STATUS_OK (0) will map
+ from a DOS error to an NT error and reply with an NT error if the client
+ supports CAP_STATUS32, otherwise it replies with the given DOS error.
+ This mode is currently not used in the server.
+
+ Setting both eclass, ecode and status to non-zero values allows a non-default
+ mapping from NT error codes to DOS error codes, and will return one or the
+ other depending on the client supporting CAP_STATUS32 or not. This is the
+ path taken by calling reply_botherror(req, eclass, ecode, status);
+
+ Setting status to NT_STATUS_DOS(eclass, ecode) forces DOS errors even if the
+ client supports CAP_STATUS32. This is the path taken to force a DOS error
+ reply by calling reply_force_doserror(req, eclass, ecode).
+
+ Setting status only and eclass to -1 forces NT errors even if the client
+ doesn't support CAP_STATUS32. This mode is currently never used in the
+ server.
+****************************************************************************/
+
+void error_packet_set(char *outbuf, uint8_t eclass, uint32_t ecode, NTSTATUS ntstatus, int line, const char *file)
+{
+ bool force_nt_status = False;
+ bool force_dos_status = False;
+
+ if (eclass == (uint8_t)-1) {
+ force_nt_status = True;
+ } else if (NT_STATUS_IS_DOS(ntstatus)) {
+ force_dos_status = True;
+ }
+
+ if (force_nt_status || (!force_dos_status && lp_nt_status_support() && (global_client_caps & CAP_STATUS32))) {
+ /* We're returning an NT error. */
+ if (NT_STATUS_V(ntstatus) == 0 && eclass) {
+ ntstatus = dos_to_ntstatus(eclass, ecode);
+ }
+ SIVAL(outbuf,smb_rcls,NT_STATUS_V(ntstatus));
+ SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2)|FLAGS2_32_BIT_ERROR_CODES);
+ /* This must not start with the word 'error', as this
+ * is reserved in the subunit stream protocol, causing
+ * false errors to show up when debugging is turned
+ * on */
+ DEBUG(3,("NT error packet at %s(%d) cmd=%d (%s) %s\n",
+ file, line,
+ (int)CVAL(outbuf,smb_com),
+#if defined(WITH_SMB1SERVER)
+ smb_fn_name(CVAL(outbuf,smb_com)),
+#else
+ "",
+#endif
+ nt_errstr(ntstatus)));
+ } else {
+ /* We're returning a DOS error only,
+ * nt_status_to_dos() pulls DOS error codes out of the
+ * NTSTATUS */
+ if (NT_STATUS_IS_DOS(ntstatus) || (eclass == 0 && NT_STATUS_V(ntstatus))) {
+ ntstatus_to_dos(ntstatus, &eclass, &ecode);
+ }
+
+ SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2)&~FLAGS2_32_BIT_ERROR_CODES);
+ SSVAL(outbuf,smb_rcls,eclass);
+ SSVAL(outbuf,smb_err,ecode);
+
+ /* This must not start with the word 'error', as this
+ * is reserved in the subunit stream protocol, causing
+ * false errors to show up when debugging is turned
+ * on */
+ DEBUG(3,("DOS error packet at %s(%d) cmd=%d (%s) eclass=%d ecode=%d\n",
+ file, line,
+ (int)CVAL(outbuf,smb_com),
+#if defined(WITH_SMB1SERVER)
+ smb_fn_name(CVAL(outbuf,smb_com)),
+#else
+ "",
+#endif
+ eclass,
+ ecode));
+ }
+}
+
+size_t error_packet(char *outbuf, uint8_t eclass, uint32_t ecode, NTSTATUS ntstatus, int line, const char *file)
+{
+ size_t outsize = srv_smb1_set_message(outbuf,0,0,True);
+ error_packet_set(outbuf, eclass, ecode, ntstatus, line, file);
+ return outsize;
+}
+
+void reply_nt_error(struct smb_request *req, NTSTATUS ntstatus,
+ int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_smb1_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, 0, 0, ntstatus, line, file);
+}
+
+/****************************************************************************
+ Forces a DOS error on the wire.
+****************************************************************************/
+
+void reply_force_dos_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_smb1_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf,
+ eclass, ecode,
+ NT_STATUS_DOS(eclass, ecode),
+ line,
+ file);
+}
+
+void reply_both_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ NTSTATUS status, int line, const char *file)
+{
+ TALLOC_FREE(req->outbuf);
+ reply_smb1_outbuf(req, 0, 0);
+ error_packet_set((char *)req->outbuf, eclass, ecode, status,
+ line, file);
+}
+
+void reply_openerror(struct smb_request *req, NTSTATUS status)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ /*
+ * We hit an existing file, and if we're returning DOS
+ * error codes OBJECT_NAME_COLLISION would map to
+ * ERRDOS/183, we need to return ERRDOS/80, see bug
+ * 4852.
+ */
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_COLLISION,
+ ERRDOS, ERRfilexists);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_TOO_MANY_OPENED_FILES)) {
+ /* EMFILE always seems to be returned as a DOS error.
+ * See bug 6837. NOTE this forces a DOS error on the wire
+ * even though it's calling reply_nterror(). */
+ reply_force_doserror(req, ERRDOS, ERRnofids);
+ } else {
+ reply_nterror(req, status);
+ }
+}
diff --git a/source3/smbd/fake_file.c b/source3/smbd/fake_file.c
new file mode 100644
index 0000000..32f2595
--- /dev/null
+++ b/source3/smbd/fake_file.c
@@ -0,0 +1,213 @@
+/*
+ Unix SMB/CIFS implementation.
+ FAKE FILE support, for faking up special files windows want access to
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "auth.h"
+
+struct fake_file_type {
+ const char *name;
+ enum FAKE_FILE_TYPE type;
+ void *(*init_pd)(TALLOC_CTX *mem_ctx);
+};
+
+static const struct fake_file_type fake_files[] = {
+#ifdef WITH_QUOTAS
+ {FAKE_FILE_NAME_QUOTA_UNIX, FAKE_FILE_TYPE_QUOTA, init_quota_handle},
+#endif /* WITH_QUOTAS */
+ {NULL, FAKE_FILE_TYPE_NONE, NULL}
+};
+
+/****************************************************************************
+ Create a fake file handle
+****************************************************************************/
+
+static struct fake_file_handle *init_fake_file_handle(enum FAKE_FILE_TYPE type)
+{
+ struct fake_file_handle *fh = NULL;
+ int i;
+
+ for (i=0; fake_files[i].name!=NULL; i++) {
+ if (fake_files[i].type==type) {
+ break;
+ }
+ }
+
+ if (fake_files[i].name == NULL) {
+ return NULL;
+ }
+
+ DEBUG(5,("init_fake_file_handle: for [%s]\n",fake_files[i].name));
+
+ fh = talloc(NULL, struct fake_file_handle);
+ if (fh == NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed.\n"));
+ return NULL;
+ }
+
+ fh->type = type;
+
+ if (fake_files[i].init_pd) {
+ fh->private_data = fake_files[i].init_pd(fh);
+ }
+ return fh;
+}
+
+/****************************************************************************
+ Does this name match a fake filename ?
+****************************************************************************/
+
+enum FAKE_FILE_TYPE is_fake_file_path(const char *path)
+{
+ int i;
+
+ if (!path) {
+ return FAKE_FILE_TYPE_NONE;
+ }
+
+ for (i=0;fake_files[i].name!=NULL;i++) {
+ if (strncmp(path,fake_files[i].name,strlen(fake_files[i].name))==0) {
+ DEBUG(5,("is_fake_file: [%s] is a fake file\n",path));
+ return fake_files[i].type;
+ }
+ }
+
+ return FAKE_FILE_TYPE_NONE;
+}
+
+enum FAKE_FILE_TYPE is_fake_file(const struct smb_filename *smb_fname)
+{
+ char *fname = NULL;
+ NTSTATUS status;
+ enum FAKE_FILE_TYPE ret;
+
+ if (!smb_fname) {
+ return FAKE_FILE_TYPE_NONE;
+ }
+
+ status = get_full_smb_filename(talloc_tos(), smb_fname, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return FAKE_FILE_TYPE_NONE;
+ }
+
+ ret = is_fake_file_path(fname);
+
+ TALLOC_FREE(fname);
+
+ return ret;
+}
+
+uint32_t dosmode_from_fake_filehandle(const struct fake_file_handle *ffh)
+{
+ if (ffh->type != FAKE_FILE_TYPE_QUOTA) {
+ DBG_ERR("Unexpected fake_file_handle: %d\n", ffh->type);
+ log_stack_trace();
+ return FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* This is what Windows 2016 returns */
+ return FILE_ATTRIBUTE_HIDDEN
+ | FILE_ATTRIBUTE_SYSTEM
+ | FILE_ATTRIBUTE_DIRECTORY
+ | FILE_ATTRIBUTE_ARCHIVE;
+}
+
+/****************************************************************************
+ Open a fake quota file with a share mode.
+****************************************************************************/
+
+NTSTATUS open_fake_file(struct smb_request *req, connection_struct *conn,
+ uint64_t current_vuid,
+ enum FAKE_FILE_TYPE fake_file_type,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ files_struct **result)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ /* access check */
+ if (geteuid() != sec_initial_uid()) {
+ DBG_NOTICE("access_denied to service[%s] file[%s] user[%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ smb_fname_str_dbg(smb_fname),
+ conn->session_info->unix_info->unix_name);
+ return NT_STATUS_ACCESS_DENIED;
+
+ }
+
+ status = file_new(req, conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DBG_INFO("fname = %s, %s, access_mask = 0x%"PRIx32"\n",
+ smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ access_mask);
+
+ fsp->conn = conn;
+ fsp_set_fd(fsp, -1);
+ fsp->vuid = current_vuid;
+ fh_set_pos(fsp->fh, -1);
+ fsp->fsp_flags.can_lock = false; /* Should this be true ? - No, JRA */
+ fsp->access_mask = access_mask;
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fake_file_handle = init_fake_file_handle(fake_file_type);
+
+ if (fsp->fake_file_handle==NULL) {
+ file_free(req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = smbd_calculate_access_mask_fsp(conn->cwd_fsp,
+ fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_calculate_access_mask_fsp "
+ "on service[%s] file[%s] returned %s\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ file_free(req, fsp);
+ return status;
+ }
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS close_fake_file(struct smb_request *req, files_struct *fsp)
+{
+ /*
+ * Nothing to do, fake files don't hold any resources
+ */
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/fd_handle.c b/source3/smbd/fd_handle.c
new file mode 100644
index 0000000..eb7fa55
--- /dev/null
+++ b/source3/smbd/fd_handle.c
@@ -0,0 +1,147 @@
+/*
+ Unix SMB/CIFS implementation.
+ fd_handle structure handling
+ Copyright (C) Ralph Boehme 2020
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "fd_handle.h"
+
+struct fd_handle {
+ size_t ref_count;
+ int fd;
+ uint64_t position_information;
+ off_t pos;
+ /*
+ * NT Create options, but we only look at
+ * NTCREATEX_FLAG_DENY_DOS and
+ * NTCREATEX_FLAG_DENY_FCB.
+ */
+ uint32_t private_options;
+ uint64_t gen_id;
+};
+
+static int fd_handle_destructor(struct fd_handle *fh)
+{
+ SMB_ASSERT((fh->fd == -1) || (fh->fd == AT_FDCWD));
+ return 0;
+}
+
+struct fd_handle *fd_handle_create(TALLOC_CTX *mem_ctx)
+{
+ struct fd_handle *fh = NULL;
+
+ fh = talloc_zero(mem_ctx, struct fd_handle);
+ if (fh == NULL) {
+ return NULL;
+ }
+ fh->fd = -1;
+
+ talloc_set_destructor(fh, fd_handle_destructor);
+
+ return fh;
+}
+
+size_t fh_get_refcount(struct fd_handle *fh)
+{
+ return fh->ref_count;
+}
+
+void fh_set_refcount(struct fd_handle *fh, size_t ref_count)
+{
+ fh->ref_count = ref_count;
+}
+
+uint64_t fh_get_position_information(struct fd_handle *fh)
+{
+ return fh->position_information;
+}
+
+void fh_set_position_information(struct fd_handle *fh, uint64_t posinfo)
+{
+ fh->position_information = posinfo;
+}
+
+off_t fh_get_pos(struct fd_handle *fh)
+{
+ return fh->pos;
+}
+
+void fh_set_pos(struct fd_handle *fh, off_t pos)
+{
+ fh->pos = pos;
+}
+
+uint32_t fh_get_private_options(struct fd_handle *fh)
+{
+ return fh->private_options;
+}
+
+void fh_set_private_options(struct fd_handle *fh, uint32_t private_options)
+{
+ fh->private_options = private_options;
+}
+
+uint64_t fh_get_gen_id(struct fd_handle *fh)
+{
+ return fh->gen_id;
+}
+
+void fh_set_gen_id(struct fd_handle *fh, uint64_t gen_id)
+{
+ fh->gen_id = gen_id;
+}
+
+/****************************************************************************
+ Helper functions for working with fsp->fh->fd
+****************************************************************************/
+
+int fsp_get_io_fd(const struct files_struct *fsp)
+{
+ if (fsp->fsp_flags.is_pathref) {
+ DBG_ERR("fsp [%s] is a path referencing fsp\n",
+ fsp_str_dbg(fsp));
+#ifdef DEVELOPER
+ smb_panic("fsp is a pathref");
+#endif
+ return -1;
+ }
+
+ return fsp->fh->fd;
+}
+
+int fsp_get_pathref_fd(const struct files_struct *fsp)
+{
+ return fsp->fh->fd;
+}
+
+void fsp_set_fd(struct files_struct *fsp, int fd)
+{
+ /*
+ * Deliberately allow setting an fd if the existing fd is the
+ * same. This happens if a VFS module assigns the fd to
+ * fsp->fh->fd in its openat VFS function. The canonical place
+ * where the assignment is done is in fd_open(), but some VFS
+ * modules do it anyway.
+ */
+
+ SMB_ASSERT(fsp->fh->fd == -1 ||
+ fsp->fh->fd == fd ||
+ fd == -1 ||
+ fd == AT_FDCWD);
+
+ fsp->fh->fd = fd;
+}
diff --git a/source3/smbd/fd_handle.h b/source3/smbd/fd_handle.h
new file mode 100644
index 0000000..dc0e5e4
--- /dev/null
+++ b/source3/smbd/fd_handle.h
@@ -0,0 +1,49 @@
+/*
+ Unix SMB/CIFS implementation.
+ Files handle structure handling
+ Copyright (C) Ralph Boehme 2020
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef FD_HANDLE_H
+#define FD_HANDLE_H
+
+#include "replace.h"
+#include <talloc.h>
+
+struct fd_handle;
+
+struct fd_handle *fd_handle_create(TALLOC_CTX *mem_ctx);
+
+size_t fh_get_refcount(struct fd_handle *fh);
+void fh_set_refcount(struct fd_handle *fh, size_t ref_count);
+
+uint64_t fh_get_position_information(struct fd_handle *fh);
+void fh_set_position_information(struct fd_handle *fh, uint64_t posinfo);
+
+off_t fh_get_pos(struct fd_handle *fh);
+void fh_set_pos(struct fd_handle *fh, off_t pos);
+
+uint32_t fh_get_private_options(struct fd_handle *fh);
+void fh_set_private_options(struct fd_handle *fh, uint32_t private_options);
+
+uint64_t fh_get_gen_id(struct fd_handle *fh);
+void fh_set_gen_id(struct fd_handle *fh, uint64_t gen_id);
+
+int fsp_get_io_fd(const struct files_struct *fsp);
+int fsp_get_pathref_fd(const struct files_struct *fsp);
+void fsp_set_fd(struct files_struct *fsp, int fd);
+
+#endif
diff --git a/source3/smbd/file_access.c b/source3/smbd/file_access.c
new file mode 100644
index 0000000..9928eb9
--- /dev/null
+++ b/source3/smbd/file_access.c
@@ -0,0 +1,247 @@
+/*
+ Unix SMB/CIFS implementation.
+ Check access to files based on security descriptors.
+ Copyright (C) Jeremy Allison 2005-2006.
+ Copyright (C) Michael Adam 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "smbd/smbd.h"
+#include "source3/smbd/dir.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+/****************************************************************************
+ Actually emulate the in-kernel access checking for delete access. We need
+ this to successfully return ACCESS_DENIED on a file open for delete access.
+****************************************************************************/
+
+bool can_delete_file_in_directory(connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ struct smb_filename *smb_fname_parent = NULL;
+ bool ret;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(conn)) {
+ return False;
+ }
+
+ if (!lp_acl_check_permissions(SNUM(conn))) {
+ /* This option means don't check. */
+ return true;
+ }
+
+ if (get_current_uid(conn) == (uid_t)0) {
+ /* I'm sorry sir, I didn't know you were root... */
+ return true;
+ }
+
+ if (dirfsp != conn->cwd_fsp) {
+ smb_fname_parent = dirfsp->fsp_name;
+ } else {
+ struct smb_filename *atname = NULL;
+ /*
+ * Get a pathref on the parent.
+ */
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &smb_fname_parent,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ }
+
+ SMB_ASSERT(VALID_STAT(smb_fname_parent->st));
+
+ /* fast paths first */
+
+ if (!S_ISDIR(smb_fname_parent->st.st_ex_mode)) {
+ ret = false;
+ goto out;
+ }
+
+#ifdef S_ISVTX
+ /* sticky bit means delete only by owner of file or by root or
+ * by owner of directory. */
+ if (smb_fname_parent->st.st_ex_mode & S_ISVTX) {
+ if (!VALID_STAT(smb_fname->st)) {
+ /* If the file doesn't already exist then
+ * yes we'll be able to delete it. */
+ ret = true;
+ goto out;
+ }
+
+ /*
+ * Patch from SATOH Fumiyasu <fumiyas@miraclelinux.com>
+ * for bug #3348. Don't assume owning sticky bit
+ * directory means write access allowed.
+ * Fail to delete if we're not the owner of the file,
+ * or the owner of the directory as we have no possible
+ * chance of deleting. Otherwise, go on and check the ACL.
+ */
+ if ((get_current_uid(conn) !=
+ smb_fname_parent->st.st_ex_uid) &&
+ (get_current_uid(conn) != smb_fname->st.st_ex_uid)) {
+ DEBUG(10,("can_delete_file_in_directory: not "
+ "owner of file %s or directory %s\n",
+ smb_fname_str_dbg(smb_fname),
+ smb_fname_str_dbg(smb_fname_parent)));
+ ret = false;
+ goto out;
+ }
+ }
+#endif
+
+ /* now for ACL checks */
+
+ /*
+ * There's two ways to get the permission to delete a file: First by
+ * having the DELETE bit on the file itself and second if that does
+ * not help, by the DELETE_CHILD bit on the containing directory.
+ *
+ * Here we only check the directory permissions, we will
+ * check the file DELETE permission separately.
+ */
+
+ ret = NT_STATUS_IS_OK(smbd_check_access_rights_fsp(
+ conn->cwd_fsp,
+ smb_fname_parent->fsp,
+ false,
+ FILE_DELETE_CHILD));
+ out:
+ if (smb_fname_parent != dirfsp->fsp_name) {
+ TALLOC_FREE(smb_fname_parent);
+ }
+ return ret;
+}
+
+/****************************************************************************
+ Userspace check for write access to fsp.
+****************************************************************************/
+
+bool can_write_to_fsp(struct files_struct *fsp)
+{
+ return NT_STATUS_IS_OK(smbd_check_access_rights_fsp(
+ fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ FILE_WRITE_DATA));
+}
+
+/****************************************************************************
+ Check for an existing default Windows ACL on a directory fsp.
+****************************************************************************/
+
+bool directory_has_default_acl_fsp(struct files_struct *fsp)
+{
+ struct security_descriptor *secdesc = NULL;
+ unsigned int i;
+ NTSTATUS status;
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ SECINFO_DACL,
+ talloc_tos(),
+ &secdesc);
+
+ if (!NT_STATUS_IS_OK(status) ||
+ secdesc == NULL ||
+ secdesc->dacl == NULL)
+ {
+ TALLOC_FREE(secdesc);
+ return false;
+ }
+
+ for (i = 0; i < secdesc->dacl->num_aces; i++) {
+ struct security_ace *psa = &secdesc->dacl->aces[i];
+
+ if (psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT))
+ {
+ TALLOC_FREE(secdesc);
+ return true;
+ }
+ }
+ TALLOC_FREE(secdesc);
+ return false;
+}
+
+/****************************************************************************
+ Check if setting delete on close is allowed on this fsp.
+****************************************************************************/
+
+NTSTATUS can_set_delete_on_close(files_struct *fsp, uint32_t dosmode)
+{
+ NTSTATUS status;
+ /*
+ * Only allow delete on close for writable files.
+ */
+
+ if ((dosmode & FILE_ATTRIBUTE_READONLY) &&
+ !lp_delete_readonly(SNUM(fsp->conn))) {
+ DEBUG(10,("can_set_delete_on_close: file %s delete on close "
+ "flag set but file attribute is readonly.\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_CANNOT_DELETE;
+ }
+
+ /*
+ * Only allow delete on close for writable shares.
+ */
+
+ if (!CAN_WRITE(fsp->conn)) {
+ DEBUG(10,("can_set_delete_on_close: file %s delete on "
+ "close flag set but write access denied on share.\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Only allow delete on close for files/directories opened with delete
+ * intent.
+ */
+
+ status = check_any_access_fsp(fsp, DELETE_ACCESS);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("file %s delete on "
+ "close flag set but delete access denied.\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ /* Don't allow delete on close for non-empty directories. */
+ if (fsp->fsp_flags.is_directory) {
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ /* Or the root of a share. */
+ if (ISDOT(fsp->fsp_name->base_name)) {
+ DEBUG(10,("can_set_delete_on_close: can't set delete on "
+ "close for the root of a share.\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return can_delete_directory_fsp(fsp);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/fileio.c b/source3/smbd/fileio.c
new file mode 100644
index 0000000..ed62159
--- /dev/null
+++ b/source3/smbd/fileio.c
@@ -0,0 +1,318 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ read/write to a files_struct
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2000-2002. - write cache.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbprofile.h"
+
+/****************************************************************************
+ Read from a file.
+****************************************************************************/
+
+ssize_t read_file(files_struct *fsp,char *data,off_t pos,size_t n)
+{
+ off_t new_pos;
+ ssize_t ret = 0;
+ bool ok;
+
+ /* you can't read from print files */
+ if (fsp->print_file) {
+ errno = EBADF;
+ return -1;
+ }
+
+ ok = vfs_valid_pread_range(pos, n);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fh_set_pos(fsp->fh, pos);
+
+ if (n > 0) {
+ ret = SMB_VFS_PREAD(fsp,data,n,pos);
+
+ if (ret == -1) {
+ return -1;
+ }
+ }
+
+ DEBUG(10,("read_file (%s): pos = %.0f, size = %lu, returned %lu\n",
+ fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret));
+
+ new_pos = fh_get_pos(fsp->fh) + ret;
+ fh_set_pos(fsp->fh, new_pos);
+ fh_set_position_information(fsp->fh, new_pos);
+
+ return(ret);
+}
+
+/****************************************************************************
+ *Really* write to a file.
+****************************************************************************/
+
+static ssize_t real_write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ off_t pos,
+ size_t n)
+{
+ ssize_t ret;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(pos, n);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (n == 0) {
+ return 0;
+ }
+
+ fh_set_pos(fsp->fh, pos);
+ if (pos &&
+ lp_strict_allocate(SNUM(fsp->conn)) &&
+ !fsp->fsp_flags.is_sparse)
+ {
+ if (vfs_fill_sparse(fsp, pos) == -1) {
+ return -1;
+ }
+ }
+ ret = vfs_pwrite_data(req, fsp, data, n, pos);
+
+ DEBUG(10,("real_write_file (%s): pos = %.0f, size = %lu, returned %ld\n",
+ fsp_str_dbg(fsp), (double)pos, (unsigned long)n, (long)ret));
+
+ if (ret != -1) {
+ off_t new_pos = fh_get_pos(fsp->fh) + ret;
+ fh_set_pos(fsp->fh, new_pos);
+
+/* Yes - this is correct - writes don't update this. JRA. */
+/* Found by Samba4 tests. */
+#if 0
+ fsp->position_information = fsp->pos;
+#endif
+ }
+
+ return ret;
+}
+
+void fsp_flush_write_time_update(struct files_struct *fsp)
+{
+ /*
+ * Note this won't expect any impersonation!
+ * So don't call any SMB_VFS operations here!
+ */
+
+ DEBUG(5, ("Update write time on %s\n", fsp_str_dbg(fsp)));
+
+ trigger_write_time_update_immediate(fsp);
+}
+
+static void update_write_time_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ files_struct *fsp = (files_struct *)private_data;
+ fsp_flush_write_time_update(fsp);
+}
+
+/*********************************************************
+ Schedule a write time update for WRITE_TIME_UPDATE_USEC_DELAY
+ in the future.
+*********************************************************/
+
+void trigger_write_time_update(struct files_struct *fsp)
+{
+ int delay;
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ /* Don't use delayed writes on POSIX files. */
+ return;
+ }
+
+ if (fsp->fsp_flags.write_time_forced) {
+ /* No point - "sticky" write times
+ * in effect.
+ */
+ return;
+ }
+
+ /* We need to remember someone did a write
+ * and update to current time on close. */
+
+ fsp->fsp_flags.update_write_time_on_close = true;
+
+ if (fsp->fsp_flags.update_write_time_triggered) {
+ /*
+ * We only update the write time after 2 seconds
+ * on the first normal write. After that
+ * no other writes affect this until close.
+ */
+ return;
+ }
+ fsp->fsp_flags.update_write_time_triggered = true;
+
+ delay = lp_parm_int(SNUM(fsp->conn),
+ "smbd", "writetimeupdatedelay",
+ WRITE_TIME_UPDATE_USEC_DELAY);
+
+ DEBUG(5, ("Update write time %d usec later on %s\n",
+ delay, fsp_str_dbg(fsp)));
+
+ /* trigger the update 2 seconds later */
+ fsp->update_write_time_event =
+ tevent_add_timer(fsp->conn->sconn->ev_ctx, NULL,
+ timeval_current_ofs_usec(delay),
+ update_write_time_handler, fsp);
+}
+
+void trigger_write_time_update_immediate(struct files_struct *fsp)
+{
+ struct smb_file_time ft;
+
+ init_smb_file_time(&ft);
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ /* Don't use delayed writes on POSIX files. */
+ return;
+ }
+
+ if (fsp->fsp_flags.write_time_forced) {
+ /*
+ * No point - "sticky" write times
+ * in effect.
+ */
+ return;
+ }
+
+ TALLOC_FREE(fsp->update_write_time_event);
+ DEBUG(5, ("Update write time immediate on %s\n",
+ fsp_str_dbg(fsp)));
+
+ /* After an immediate update, reset the trigger. */
+ fsp->fsp_flags.update_write_time_triggered = true;
+ fsp->fsp_flags.update_write_time_on_close = false;
+
+ ft.mtime = timespec_current();
+
+ /* Update the time in the open file db. */
+ (void)set_write_time(fsp->file_id, ft.mtime);
+
+ /* Now set on disk - takes care of notify. */
+ (void)smb_set_file_time(fsp->conn, fsp, fsp->fsp_name, &ft, false);
+}
+
+void mark_file_modified(files_struct *fsp)
+{
+ int dosmode;
+
+ trigger_write_time_update(fsp);
+
+ if (fsp->fsp_flags.modified) {
+ return;
+ }
+
+ fsp->fsp_flags.modified = true;
+
+ if (!(lp_store_dos_attributes(SNUM(fsp->conn)) ||
+ MAP_ARCHIVE(fsp->conn))) {
+ return;
+ }
+
+ dosmode = fdos_mode(fsp);
+ if (dosmode & FILE_ATTRIBUTE_ARCHIVE) {
+ return;
+ }
+ file_set_dosmode(fsp->conn, fsp->fsp_name,
+ dosmode | FILE_ATTRIBUTE_ARCHIVE, NULL, false);
+}
+
+/****************************************************************************
+ Write to a file.
+****************************************************************************/
+
+ssize_t write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ off_t pos,
+ size_t n)
+{
+ ssize_t total_written = 0;
+
+ if (fsp->print_file) {
+ uint32_t t;
+ int ret;
+
+ ret = print_spool_write(fsp, data, n, pos, &t);
+ if (ret) {
+ errno = ret;
+ return -1;
+ }
+ return t;
+ }
+
+ if (!fsp->fsp_flags.can_write) {
+ errno = EPERM;
+ return -1;
+ }
+
+ mark_file_modified(fsp);
+
+ /*
+ * If this file is level II oplocked then we need
+ * to grab the shared memory lock and inform all
+ * other files with a level II lock that they need
+ * to flush their read caches. We keep the lock over
+ * the shared memory area whilst doing this.
+ */
+
+ /* This should actually be improved to span the write. */
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WRITE);
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WRITE);
+
+ total_written = real_write_file(req, fsp, data, pos, n);
+ return total_written;
+}
+
+/*******************************************************************
+sync a file
+********************************************************************/
+
+NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_through)
+{
+ if (fsp_get_io_fd(fsp) == -1)
+ return NT_STATUS_INVALID_HANDLE;
+
+ if (lp_strict_sync(SNUM(conn)) &&
+ (lp_sync_always(SNUM(conn)) || write_through)) {
+ int ret;
+ ret = smb_vfs_fsync_sync(fsp);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/filename.c b/source3/smbd/filename.c
new file mode 100644
index 0000000..f80f67c
--- /dev/null
+++ b/source3/smbd/filename.c
@@ -0,0 +1,1271 @@
+/*
+ Unix SMB/CIFS implementation.
+ filename handling routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1999-2007
+ Copyright (C) Ying Chen 2000
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * New hash table stat cache code added by Ying Chen.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "fake_file.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/smb/reparse.h"
+#include "source3/smbd/dir.h"
+
+uint32_t ucf_flags_from_smb_request(struct smb_request *req)
+{
+ uint32_t ucf_flags = 0;
+
+ if (req == NULL) {
+ return 0;
+ }
+
+ if (req->posix_pathnames) {
+ ucf_flags |= UCF_POSIX_PATHNAMES;
+
+ if (!req->sconn->using_smb2) {
+ ucf_flags |= UCF_LCOMP_LNK_OK;
+ }
+ }
+ if (req->flags2 & FLAGS2_DFS_PATHNAMES) {
+ ucf_flags |= UCF_DFS_PATHNAME;
+ }
+ if (req->flags2 & FLAGS2_REPARSE_PATH) {
+ ucf_flags |= UCF_GMT_PATHNAME;
+ }
+
+ return ucf_flags;
+}
+
+uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition)
+{
+ uint32_t ucf_flags = 0;
+
+ ucf_flags |= ucf_flags_from_smb_request(req);
+
+ switch (create_disposition) {
+ case FILE_OPEN:
+ case FILE_OVERWRITE:
+ break;
+ case FILE_SUPERSEDE:
+ case FILE_CREATE:
+ case FILE_OPEN_IF:
+ case FILE_OVERWRITE_IF:
+ ucf_flags |= UCF_PREP_CREATEFILE;
+ break;
+ }
+
+ return ucf_flags;
+}
+
+/****************************************************************************
+ Mangle the 2nd name and check if it is then equal to the first name.
+****************************************************************************/
+
+static bool mangled_equal(const char *name1,
+ const char *name2,
+ const struct share_params *p)
+{
+ char mname[13];
+
+ if (!name_to_8_3(name2, mname, False, p)) {
+ return False;
+ }
+ return strequal(name1, mname);
+}
+
+/*
+ * Strip a valid @GMT-token from any incoming filename path,
+ * adding any NTTIME encoded in the pathname into the
+ * twrp field of the passed in smb_fname.
+ *
+ * Valid @GMT-tokens look like @GMT-YYYY-MM-DD-HH-MM-SS
+ * at the *start* of a pathname component.
+ *
+ * If twrp is passed in then smb_fname->twrp is set to that
+ * value, and the @GMT-token part of the filename is removed
+ * and does not change the stored smb_fname->twrp.
+ *
+ */
+
+NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname,
+ uint32_t ucf_flags,
+ NTTIME twrp)
+{
+ bool found;
+
+ if (twrp != 0) {
+ smb_fname->twrp = twrp;
+ }
+
+ if (!(ucf_flags & UCF_GMT_PATHNAME)) {
+ return NT_STATUS_OK;
+ }
+
+ found = extract_snapshot_token(smb_fname->base_name, &twrp);
+ if (!found) {
+ return NT_STATUS_OK;
+ }
+
+ if (smb_fname->twrp == 0) {
+ smb_fname->twrp = twrp;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool strnorm(char *s, int case_default)
+{
+ if (case_default == CASE_UPPER)
+ return strupper_m(s);
+ else
+ return strlower_m(s);
+}
+
+/*
+ * Utility function to normalize case on an incoming client filename
+ * if required on this connection struct.
+ * Performs an in-place case conversion guaranteed to stay the same size.
+ */
+
+static NTSTATUS normalize_filename_case(connection_struct *conn,
+ char *filename,
+ uint32_t ucf_flags)
+{
+ bool ok;
+
+ if (ucf_flags & UCF_POSIX_PATHNAMES) {
+ /*
+ * POSIX never normalizes filename case.
+ */
+ return NT_STATUS_OK;
+ }
+ if (!conn->case_sensitive) {
+ return NT_STATUS_OK;
+ }
+ if (conn->case_preserve) {
+ return NT_STATUS_OK;
+ }
+ if (conn->short_case_preserve) {
+ return NT_STATUS_OK;
+ }
+ ok = strnorm(filename, lp_default_case(SNUM(conn)));
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check if two filenames are equal.
+ This needs to be careful about whether we are case sensitive.
+****************************************************************************/
+
+static bool fname_equal(const char *name1, const char *name2,
+ bool case_sensitive)
+{
+ /* Normal filename handling */
+ if (case_sensitive) {
+ return(strcmp(name1,name2) == 0);
+ }
+
+ return(strequal(name1,name2));
+}
+
+static bool sname_equal(const char *name1, const char *name2,
+ bool case_sensitive)
+{
+ bool match;
+ const char *s1 = NULL;
+ const char *s2 = NULL;
+ size_t n1;
+ size_t n2;
+ const char *e1 = NULL;
+ const char *e2 = NULL;
+ char *c1 = NULL;
+ char *c2 = NULL;
+
+ match = fname_equal(name1, name2, case_sensitive);
+ if (match) {
+ return true;
+ }
+
+ if (name1[0] != ':') {
+ return false;
+ }
+ if (name2[0] != ':') {
+ return false;
+ }
+ s1 = &name1[1];
+ e1 = strchr(s1, ':');
+ if (e1 == NULL) {
+ n1 = strlen(s1);
+ } else {
+ n1 = PTR_DIFF(e1, s1);
+ }
+ s2 = &name2[1];
+ e2 = strchr(s2, ':');
+ if (e2 == NULL) {
+ n2 = strlen(s2);
+ } else {
+ n2 = PTR_DIFF(e2, s2);
+ }
+
+ /* Normal filename handling */
+ if (case_sensitive) {
+ return (strncmp(s1, s2, n1) == 0);
+ }
+
+ /*
+ * We can't use strnequal() here
+ * as it takes the number of codepoints
+ * and not the number of bytes.
+ *
+ * So we make a copy before calling
+ * strequal().
+ *
+ * Note that we TALLOC_FREE() in reverse order
+ * in order to avoid memory fragmentation.
+ */
+
+ c1 = talloc_strndup(talloc_tos(), s1, n1);
+ c2 = talloc_strndup(talloc_tos(), s2, n2);
+ if (c1 == NULL || c2 == NULL) {
+ TALLOC_FREE(c2);
+ TALLOC_FREE(c1);
+ return (strncmp(s1, s2, n1) == 0);
+ }
+
+ match = strequal(c1, c2);
+ TALLOC_FREE(c2);
+ TALLOC_FREE(c1);
+ return match;
+}
+
+/****************************************************************************
+ Scan a directory to find a filename, matching without case sensitivity.
+ If the name looks like a mangled name then try via the mangling functions
+****************************************************************************/
+
+NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp,
+ const char *name,
+ bool mangled,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ struct smb_Dir *cur_dir = NULL;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ char *unmangled_name = NULL;
+ NTSTATUS status;
+
+ /* If we have a case-sensitive filesystem, it doesn't do us any
+ * good to search for a name. If a case variation of the name was
+ * there, then the original stat(2) would have found it.
+ */
+ if (!mangled && !(conn->fs_capabilities & FILE_CASE_SENSITIVE_SEARCH)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * The incoming name can be mangled, and if we de-mangle it
+ * here it will not compare correctly against the filename (name2)
+ * read from the directory and then mangled by the name_to_8_3()
+ * call. We need to mangle both names or neither.
+ * (JRA).
+ *
+ * Fix for bug found by Dina Fine. If in case sensitive mode then
+ * the mangle cache is no good (3 letter extension could be wrong
+ * case - so don't demangle in this case - leave as mangled and
+ * allow the mangling of the directory entry read (which is done
+ * case insensitively) to match instead. This will lead to more
+ * false positive matches but we fail completely without it. JRA.
+ */
+
+ if (mangled && !conn->case_sensitive) {
+ mangled = !mangle_lookup_name_from_8_3(talloc_tos(), name,
+ &unmangled_name,
+ conn->params);
+ if (!mangled) {
+ /* Name is now unmangled. */
+ name = unmangled_name;
+ }
+ }
+
+ /* open the directory */
+ status = OpenDir_from_pathref(talloc_tos(), dirfsp, NULL, 0, &cur_dir);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("scan dir didn't open dir [%s]: %s\n",
+ fsp_str_dbg(dirfsp),
+ nt_errstr(status));
+ TALLOC_FREE(unmangled_name);
+ return status;
+ }
+
+ /* now scan for matching names */
+ while ((dname = ReadDirName(cur_dir, &talloced))) {
+
+ /* Is it dot or dot dot. */
+ if (ISDOT(dname) || ISDOTDOT(dname)) {
+ TALLOC_FREE(talloced);
+ continue;
+ }
+
+ /*
+ * At this point dname is the unmangled name.
+ * name is either mangled or not, depending on the state
+ * of the "mangled" variable. JRA.
+ */
+
+ /*
+ * Check mangled name against mangled name, or unmangled name
+ * against unmangled name.
+ */
+
+ if ((mangled && mangled_equal(name,dname,conn->params)) ||
+ fname_equal(name, dname, conn->case_sensitive)) {
+ /* we've found the file, change it's name and return */
+ *found_name = talloc_strdup(mem_ctx, dname);
+ TALLOC_FREE(unmangled_name);
+ TALLOC_FREE(cur_dir);
+ if (!*found_name) {
+ TALLOC_FREE(talloced);
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(talloced);
+ return NT_STATUS_OK;
+ }
+ TALLOC_FREE(talloced);
+ }
+
+ TALLOC_FREE(unmangled_name);
+ TALLOC_FREE(cur_dir);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+/****************************************************************************
+ Wrapper around the vfs get_real_filename and the full directory scan
+ fallback.
+****************************************************************************/
+
+NTSTATUS get_real_filename_at(struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ NTSTATUS status;
+ bool mangled;
+
+ mangled = mangle_is_mangled(name, conn->params);
+
+ if (mangled) {
+ status = get_real_filename_full_scan_at(
+ dirfsp, name, mangled, mem_ctx, found_name);
+ return status;
+ }
+
+ /* Try the vfs first to take advantage of case-insensitive stat. */
+ status = SMB_VFS_GET_REAL_FILENAME_AT(
+ dirfsp->conn, dirfsp, name, mem_ctx, found_name);
+
+ /*
+ * If the case-insensitive stat was successful, or returned an error
+ * other than EOPNOTSUPP then there is no need to fall back on the
+ * full directory scan.
+ */
+ if (NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ return status;
+ }
+
+ status = get_real_filename_full_scan_at(
+ dirfsp, name, mangled, mem_ctx, found_name);
+ return status;
+}
+
+/*
+ * Lightweight function to just get last component
+ * for rename / enumerate directory calls.
+ */
+
+char *get_original_lcomp(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *filename_in,
+ uint32_t ucf_flags)
+{
+ char *last_slash = NULL;
+ char *orig_lcomp;
+ NTSTATUS status;
+
+ last_slash = strrchr(filename_in, '/');
+ if (last_slash != NULL) {
+ orig_lcomp = talloc_strdup(ctx, last_slash+1);
+ } else {
+ orig_lcomp = talloc_strdup(ctx, filename_in);
+ }
+ if (orig_lcomp == NULL) {
+ return NULL;
+ }
+ status = normalize_filename_case(conn, orig_lcomp, ucf_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(orig_lcomp);
+ return NULL;
+ }
+ return orig_lcomp;
+}
+
+/*
+ * Get the correct capitalized stream name hanging off
+ * base_fsp. Equivalent of get_real_filename(), but for streams.
+ */
+static NTSTATUS get_real_stream_name(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *base_fsp,
+ const char *stream_name,
+ char **_found)
+{
+ unsigned int i, num_streams = 0;
+ struct stream_struct *streams = NULL;
+ NTSTATUS status;
+
+ status = vfs_fstreaminfo(
+ base_fsp, talloc_tos(), &num_streams, &streams);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ bool equal = sname_equal(stream_name, streams[i].name, false);
+
+ DBG_DEBUG("comparing [%s] and [%s]: %sequal\n",
+ stream_name,
+ streams[i].name,
+ equal ? "" : "not ");
+
+ if (equal) {
+ *_found = talloc_move(mem_ctx, &streams[i].name);
+ TALLOC_FREE(streams);
+ return NT_STATUS_OK;
+ }
+ }
+
+ TALLOC_FREE(streams);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+static bool filename_split_lcomp(
+ TALLOC_CTX *mem_ctx,
+ const char *name_in,
+ bool posix,
+ char **_dirname,
+ const char **_fname_rel,
+ const char **_streamname)
+{
+ const char *lcomp = NULL;
+ const char *fname_rel = NULL;
+ const char *streamname = NULL;
+ char *dirname = NULL;
+
+ if (name_in[0] == '\0') {
+ fname_rel = ".";
+ dirname = talloc_strdup(mem_ctx, "");
+ if (dirname == NULL) {
+ return false;
+ }
+ goto done;
+ }
+
+ lcomp = strrchr_m(name_in, '/');
+ if (lcomp != NULL) {
+ fname_rel = lcomp+1;
+ dirname = talloc_strndup(mem_ctx, name_in, lcomp - name_in);
+ if (dirname == NULL) {
+ return false;
+ }
+ goto find_stream;
+ }
+
+ /*
+ * No slash, dir is empty
+ */
+ dirname = talloc_strdup(mem_ctx, "");
+ if (dirname == NULL) {
+ return false;
+ }
+
+ if (!posix && (name_in[0] == ':')) {
+ /*
+ * Special case for stream on root directory
+ */
+ fname_rel = ".";
+ streamname = name_in;
+ goto done;
+ }
+
+ fname_rel = name_in;
+
+find_stream:
+ if (!posix) {
+ streamname = strchr_m(fname_rel, ':');
+
+ if (streamname != NULL) {
+ fname_rel = talloc_strndup(
+ mem_ctx,
+ fname_rel,
+ streamname - fname_rel);
+ if (fname_rel == NULL) {
+ TALLOC_FREE(dirname);
+ return false;
+ }
+ }
+ }
+
+done:
+ *_dirname = dirname;
+ *_fname_rel = fname_rel;
+ *_streamname = streamname;
+ return true;
+}
+
+/*
+ * Create the correct capitalization of a file name to be created.
+ */
+static NTSTATUS filename_convert_normalize_new(
+ TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ char *name_in,
+ char **_normalized)
+{
+ char *name = name_in;
+
+ *_normalized = NULL;
+
+ if (!conn->case_preserve ||
+ (mangle_is_8_3(name, false,
+ conn->params) &&
+ !conn->short_case_preserve)) {
+
+ char *normalized = talloc_strdup(mem_ctx, name);
+ if (normalized == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ strnorm(normalized, lp_default_case(SNUM(conn)));
+ name = normalized;
+ }
+
+ if (mangle_is_mangled(name, conn->params)) {
+ bool found;
+ char *unmangled = NULL;
+
+ found = mangle_lookup_name_from_8_3(
+ mem_ctx, name, &unmangled, conn->params);
+ if (found) {
+ name = unmangled;
+ }
+ }
+
+ if (name != name_in) {
+ *_normalized = name;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static const char *previous_slash(const char *name_in, const char *slash)
+{
+ const char *prev = NULL;
+
+ SMB_ASSERT((name_in <= slash) && (slash[0] == '/'));
+
+ prev = strchr_m(name_in, '/');
+
+ if (prev == slash) {
+ /* No previous slash */
+ return NULL;
+ }
+
+ while (true) {
+ const char *next = strchr_m(prev + 1, '/');
+
+ if (next == slash) {
+ return prev;
+ }
+ prev = next;
+ }
+
+ return NULL; /* unreachable */
+}
+
+static char *symlink_target_path(
+ TALLOC_CTX *mem_ctx,
+ const char *name_in,
+ const char *substitute,
+ size_t unparsed)
+{
+ size_t name_in_len = strlen(name_in);
+ const char *p_unparsed = NULL;
+ const char *parent = NULL;
+ char *ret;
+
+ SMB_ASSERT(unparsed <= name_in_len);
+
+ p_unparsed = name_in + (name_in_len - unparsed);
+
+ if (substitute[0] == '/') {
+ ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed);
+ return ret;
+ }
+
+ if (unparsed == 0) {
+ parent = strrchr_m(name_in, '/');
+ } else {
+ parent = previous_slash(name_in, p_unparsed);
+ }
+
+ if (parent == NULL) {
+ ret = talloc_asprintf(mem_ctx, "%s%s", substitute, p_unparsed);
+ } else {
+ ret = talloc_asprintf(mem_ctx,
+ "%.*s/%s%s",
+ (int)(parent - name_in),
+ name_in,
+ substitute,
+ p_unparsed);
+ }
+
+ return ret;
+}
+
+NTSTATUS safe_symlink_target_path(TALLOC_CTX *mem_ctx,
+ const char *connectpath,
+ const char *dir,
+ const char *target,
+ size_t unparsed,
+ char **_relative)
+{
+ char *abs_target = NULL;
+ char *abs_target_canon = NULL;
+ const char *relative = NULL;
+ bool in_share;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ DBG_DEBUG("connectpath [%s] target [%s] unparsed [%zu]\n",
+ connectpath, target, unparsed);
+
+ if (target[0] == '/') {
+ abs_target = talloc_strdup(mem_ctx, target);
+ } else if (dir == NULL) {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ connectpath,
+ target);
+ } else if (dir[0] == '/') {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ dir,
+ target);
+ } else {
+ abs_target = talloc_asprintf(mem_ctx,
+ "%s/%s/%s",
+ connectpath,
+ dir,
+ target);
+ }
+ if (abs_target == NULL) {
+ goto fail;
+ }
+
+ abs_target_canon = canonicalize_absolute_path(abs_target, abs_target);
+ if (abs_target_canon == NULL) {
+ goto fail;
+ }
+
+ DBG_DEBUG("abs_target_canon=%s\n", abs_target_canon);
+
+ in_share = subdir_of(
+ connectpath, strlen(connectpath), abs_target_canon, &relative);
+ if (!in_share) {
+ DBG_DEBUG("wide link to %s\n", abs_target_canon);
+ status = (unparsed != 0) ? NT_STATUS_OBJECT_PATH_NOT_FOUND
+ : NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
+ }
+
+ *_relative = talloc_strdup(mem_ctx, relative);
+ if (*_relative == NULL) {
+ goto fail;
+ }
+
+ status = NT_STATUS_OK;
+fail:
+ TALLOC_FREE(abs_target);
+ return status;
+}
+
+/*
+ * Split up name_in as sent by the client into a directory pathref fsp
+ * and a relative smb_filename.
+ */
+static NTSTATUS filename_convert_dirfsp_nosymlink(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *name_in,
+ uint32_t ucf_flags,
+ NTTIME twrp,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname,
+ struct open_symlink_err **_symlink_err)
+{
+ struct smb_filename *smb_dirname = NULL;
+ struct smb_filename *smb_fname_rel = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct open_symlink_err *symlink_err = NULL;
+ const bool posix = (ucf_flags & UCF_POSIX_PATHNAMES);
+ char *dirname = NULL;
+ const char *fname_rel = NULL;
+ const char *streamname = NULL;
+ char *saved_streamname = NULL;
+ struct files_struct *base_fsp = NULL;
+ bool ok;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+ SMB_ASSERT(!(ucf_flags & UCF_DFS_PATHNAME));
+
+ if (is_fake_file_path(name_in)) {
+ smb_fname = synthetic_smb_fname_split(mem_ctx, name_in, posix);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ smb_fname->st = (SMB_STRUCT_STAT){
+ .st_ex_nlink = 1,
+ .st_ex_mode = S_IFREG | 0644,
+ };
+ smb_fname->st.st_ex_btime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+ smb_fname->st.st_ex_atime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+ smb_fname->st.st_ex_mtime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+ smb_fname->st.st_ex_ctime =
+ (struct timespec){0, SAMBA_UTIME_OMIT};
+
+ *_dirfsp = conn->cwd_fsp;
+ *_smb_fname = smb_fname;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Catch an invalid path of "." before we
+ * call filename_split_lcomp(). We need to
+ * do this as filename_split_lcomp() will
+ * use "." for the missing relative component
+ * when an empty name_in path is sent by
+ * the client.
+ */
+ if (ISDOT(name_in)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ ok = filename_split_lcomp(
+ talloc_tos(),
+ name_in,
+ posix,
+ &dirname,
+ &fname_rel,
+ &streamname);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if ((streamname != NULL) &&
+ ((conn->fs_capabilities & FILE_NAMED_STREAMS) == 0)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ if (!posix) {
+ bool name_has_wild = ms_has_wild(dirname);
+ name_has_wild |= ms_has_wild(fname_rel);
+ if (name_has_wild) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+ }
+
+ if (dirname[0] == '\0') {
+ status = synthetic_pathref(
+ mem_ctx,
+ conn->cwd_fsp,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ posix ? SMB_FILENAME_POSIX_PATH : 0,
+ &smb_dirname);
+ } else {
+ status = normalize_filename_case(conn, dirname, ucf_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("normalize_filename_case %s failed: %s\n",
+ dirname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = openat_pathref_fsp_nosymlink(mem_ctx,
+ conn,
+ conn->cwd_fsp,
+ dirname,
+ twrp,
+ posix,
+ &smb_dirname,
+ &symlink_err);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ size_t name_in_len, dirname_len;
+
+ name_in_len = strlen(name_in);
+ dirname_len = strlen(dirname);
+
+ SMB_ASSERT(name_in_len >= dirname_len);
+
+ symlink_err->unparsed += (name_in_len - dirname_len);
+ *_symlink_err = symlink_err;
+
+ goto fail;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("opening directory %s failed: %s\n",
+ dirname,
+ nt_errstr(status));
+ TALLOC_FREE(dirname);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ /*
+ * Except ACCESS_DENIED, everything else leads
+ * to PATH_NOT_FOUND.
+ */
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ goto fail;
+ }
+
+ if (!VALID_STAT_OF_DIR(smb_dirname->st)) {
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+ smb_dirname->fsp->fsp_flags.is_directory = true;
+
+ /*
+ * Only look at bad last component values
+ * once we know we have a valid directory. That
+ * way we won't confuse error messages from
+ * opening the directory path with error
+ * messages from a bad last component.
+ */
+
+ /* Relative filename can't be empty */
+ if (fname_rel[0] == '\0') {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ /* Relative filename can't be ".." */
+ if (ISDOTDOT(fname_rel)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+ /* Relative name can only be dot if directory is empty. */
+ if (ISDOT(fname_rel) && dirname[0] != '\0') {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ TALLOC_FREE(dirname);
+
+ smb_fname_rel = synthetic_smb_fname(
+ mem_ctx,
+ fname_rel,
+ streamname,
+ NULL,
+ twrp,
+ posix ? SMB_FILENAME_POSIX_PATH : 0);
+ if (smb_fname_rel == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS) &&
+ is_named_stream(smb_fname_rel)) {
+ /*
+ * Find the base_fsp first without the stream.
+ */
+ saved_streamname = smb_fname_rel->stream_name;
+ smb_fname_rel->stream_name = NULL;
+ }
+
+ status = normalize_filename_case(
+ conn, smb_fname_rel->base_name, ucf_flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("normalize_filename_case %s failed: %s\n",
+ smb_fname_rel->base_name,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = openat_pathref_fsp_lcomp(smb_dirname->fsp,
+ smb_fname_rel,
+ ucf_flags);
+
+ if (NT_STATUS_IS_OK(status) && S_ISLNK(smb_fname_rel->st.st_ex_mode)) {
+
+ /*
+ * Upper layers might need the link target. Here we
+ * still have the relname around, get the symlink err.
+ */
+ status = create_open_symlink_err(mem_ctx,
+ smb_dirname->fsp,
+ smb_fname_rel,
+ &symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not read symlink for %s: %s\n",
+ smb_fname_str_dbg(
+ smb_fname_rel->fsp->fsp_name),
+ nt_errstr(status));
+ goto fail;
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ !VALID_STAT(smb_fname_rel->st)) {
+
+ char *normalized = NULL;
+
+ /*
+ * Creating a new file
+ */
+
+ status = filename_convert_normalize_new(
+ smb_fname_rel,
+ conn,
+ smb_fname_rel->base_name,
+ &normalized);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("filename_convert_normalize_new failed: "
+ "%s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (normalized != NULL) {
+ smb_fname_rel->base_name = normalized;
+ }
+
+ smb_fname_rel->stream_name = saved_streamname;
+
+ smb_fname = full_path_from_dirfsp_atname(
+ mem_ctx, smb_dirname->fsp, smb_fname_rel);
+ if (smb_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_OPEN_RESTRICTION)) {
+ /* A vetoed file, pretend it's not there */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (saved_streamname == NULL) {
+ /* smb_fname must be allocated off mem_ctx. */
+ smb_fname = cp_smb_filename(mem_ctx,
+ smb_fname_rel->fsp->fsp_name);
+ if (smb_fname == NULL) {
+ goto fail;
+ }
+ status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ goto done;
+ }
+
+ base_fsp = smb_fname_rel->fsp;
+ smb_fname_fsp_unlink(smb_fname_rel);
+ SET_STAT_INVALID(smb_fname_rel->st);
+
+ smb_fname_rel->stream_name = saved_streamname;
+
+ status = open_stream_pathref_fsp(&base_fsp, smb_fname_rel);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ !conn->case_sensitive) {
+ char *found = NULL;
+
+ status = get_real_stream_name(
+ smb_fname_rel,
+ base_fsp,
+ smb_fname_rel->stream_name,
+ &found);
+
+ if (NT_STATUS_IS_OK(status)) {
+ smb_fname_rel->stream_name = found;
+ found = NULL;
+ status = open_stream_pathref_fsp(
+ &base_fsp, smb_fname_rel);
+ }
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* smb_fname must be allocated off mem_ctx. */
+ smb_fname = cp_smb_filename(mem_ctx,
+ smb_fname_rel->fsp->fsp_name);
+ if (smb_fname == NULL) {
+ goto fail;
+ }
+ status = move_smb_fname_fsp_link(smb_fname, smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ goto done;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Creating a new stream
+ *
+ * We should save the already-open base fsp for
+ * create_file_unixpath() somehow.
+ */
+ smb_fname = full_path_from_dirfsp_atname(
+ mem_ctx, smb_dirname->fsp, smb_fname_rel);
+ if (smb_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ /*
+ * When open_stream_pathref_fsp() returns
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND, smb_fname_rel->fsp
+ * has been set to NULL, so we must free base_fsp separately
+ * to prevent fd-leaks when opening a stream that doesn't
+ * exist.
+ */
+ fd_close(base_fsp);
+ file_free(NULL, base_fsp);
+ base_fsp = NULL;
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+done:
+ *_dirfsp = smb_dirname->fsp;
+ *_smb_fname = smb_fname;
+ *_symlink_err = symlink_err;
+
+ smb_fname_fsp_unlink(smb_fname_rel);
+ TALLOC_FREE(smb_fname_rel);
+ return NT_STATUS_OK;
+
+fail:
+ /*
+ * If open_stream_pathref_fsp() returns an error, smb_fname_rel->fsp
+ * has been set to NULL, so we must free base_fsp separately
+ * to prevent fd-leaks when opening a stream that doesn't
+ * exist.
+ */
+ if (base_fsp != NULL) {
+ fd_close(base_fsp);
+ file_free(NULL, base_fsp);
+ base_fsp = NULL;
+ }
+ TALLOC_FREE(dirname);
+ TALLOC_FREE(smb_dirname);
+ TALLOC_FREE(smb_fname_rel);
+ return status;
+}
+
+NTSTATUS filename_convert_dirfsp(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *name_in,
+ uint32_t ucf_flags,
+ NTTIME twrp,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname)
+{
+ struct open_symlink_err *symlink_err = NULL;
+ NTSTATUS status;
+ char *target = NULL;
+ char *safe_target = NULL;
+ size_t symlink_redirects = 0;
+
+next:
+ if (symlink_redirects > 40) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+
+ status = filename_convert_dirfsp_nosymlink(mem_ctx,
+ conn,
+ name_in,
+ ucf_flags,
+ twrp,
+ _dirfsp,
+ _smb_fname,
+ &symlink_err);
+
+ if (NT_STATUS_IS_OK(status) && S_ISLNK((*_smb_fname)->st.st_ex_mode)) {
+ /*
+ * lcomp is a symlink
+ */
+ if (ucf_flags & UCF_LCOMP_LNK_OK) {
+ TALLOC_FREE(symlink_err);
+ return NT_STATUS_OK;
+ }
+ close_file_free(NULL, _dirfsp, ERROR_CLOSE);
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ return status;
+ }
+
+ /*
+ * If we're on an MSDFS share, see if this is
+ * an MSDFS link.
+ */
+ if (lp_host_msdfs() && lp_msdfs_root(SNUM(conn)) &&
+ strnequal(symlink_err->reparse->substitute_name, "msdfs:", 6))
+ {
+ TALLOC_FREE(*_smb_fname);
+ TALLOC_FREE(symlink_err);
+ return NT_STATUS_PATH_NOT_COVERED;
+ }
+
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ status = (symlink_err->unparsed == 0)
+ ? NT_STATUS_OBJECT_NAME_NOT_FOUND
+ : NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ TALLOC_FREE(symlink_err);
+ return status;
+ }
+
+ /*
+ * Right now, SMB2 and SMB1 always traverse symlinks
+ * within the share. SMB1+POSIX traverses non-terminal
+ * symlinks within the share.
+ *
+ * When we add SMB2+POSIX we need to return
+ * a NT_STATUS_STOPPED_ON_SYMLINK error here, using the
+ * symlink target data read below if SMB2+POSIX has
+ * UCF_POSIX_PATHNAMES set to cause the client to
+ * resolve all symlinks locally.
+ */
+
+ target = symlink_target_path(mem_ctx,
+ name_in,
+ symlink_err->reparse->substitute_name,
+ symlink_err->unparsed);
+ if (target == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = safe_symlink_target_path(mem_ctx,
+ conn->connectpath,
+ NULL,
+ target,
+ symlink_err->unparsed,
+ &safe_target);
+ TALLOC_FREE(symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ name_in = safe_target;
+
+ symlink_redirects += 1;
+
+ goto next;
+}
+
+char *full_path_from_dirfsp_at_basename(TALLOC_CTX *mem_ctx,
+ const struct files_struct *dirfsp,
+ const char *at_base_name)
+{
+ char *path = NULL;
+
+ if (dirfsp == dirfsp->conn->cwd_fsp ||
+ ISDOT(dirfsp->fsp_name->base_name) || at_base_name[0] == '/') {
+ path = talloc_strdup(mem_ctx, at_base_name);
+ } else {
+ path = talloc_asprintf(mem_ctx,
+ "%s/%s",
+ dirfsp->fsp_name->base_name,
+ at_base_name);
+ }
+
+ return path;
+}
+
+/*
+ * Build the full path from a dirfsp and dirfsp relative name
+ */
+struct smb_filename *
+full_path_from_dirfsp_atname(TALLOC_CTX *mem_ctx,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *atname)
+{
+ struct smb_filename *fname = NULL;
+ char *path = NULL;
+
+ path = full_path_from_dirfsp_at_basename(mem_ctx,
+ dirfsp,
+ atname->base_name);
+ if (path == NULL) {
+ return NULL;
+ }
+
+ fname = synthetic_smb_fname(mem_ctx,
+ path,
+ atname->stream_name,
+ &atname->st,
+ atname->twrp,
+ atname->flags);
+ TALLOC_FREE(path);
+ if (fname == NULL) {
+ return NULL;
+ }
+
+ return fname;
+}
diff --git a/source3/smbd/files.c b/source3/smbd/files.c
new file mode 100644
index 0000000..6aad76a
--- /dev/null
+++ b/source3/smbd/files.c
@@ -0,0 +1,2651 @@
+/*
+ Unix SMB/CIFS implementation.
+ Files[] structure handling
+ Copyright (C) Andrew Tridgell 1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "libcli/security/security.h"
+#include "util_tdb.h"
+#include "lib/util/bitmap.h"
+#include "lib/util/strv.h"
+#include "lib/util/memcache.h"
+#include "libcli/smb/reparse.h"
+
+#define FILE_HANDLE_OFFSET 0x1000
+
+static NTSTATUS fsp_attach_smb_fname(struct files_struct *fsp,
+ struct smb_filename **_smb_fname);
+
+/**
+ * create new fsp to be used for file_new or a durable handle reconnect
+ */
+NTSTATUS fsp_new(struct connection_struct *conn, TALLOC_CTX *mem_ctx,
+ files_struct **result)
+{
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ files_struct *fsp = NULL;
+ struct smbd_server_connection *sconn = conn->sconn;
+
+ fsp = talloc_zero(mem_ctx, struct files_struct);
+ if (fsp == NULL) {
+ goto fail;
+ }
+
+ /*
+ * This can't be a child of fsp because the file_handle can be ref'd
+ * when doing a dos/fcb open, which will then share the file_handle
+ * across multiple fsps.
+ */
+ fsp->fh = fd_handle_create(mem_ctx);
+ if (fsp->fh == NULL) {
+ goto fail;
+ }
+
+ fsp->fsp_flags.use_ofd_locks = !lp_smbd_force_process_locks(SNUM(conn));
+#ifndef HAVE_OFD_LOCKS
+ fsp->fsp_flags.use_ofd_locks = false;
+#endif
+
+ fh_set_refcount(fsp->fh, 1);
+ fsp_set_fd(fsp, -1);
+
+ fsp->fnum = FNUM_FIELD_INVALID;
+ fsp->conn = conn;
+ fsp->close_write_time = make_omit_timespec();
+
+ DLIST_ADD(sconn->files, fsp);
+ sconn->num_files += 1;
+
+ conn->num_files_open++;
+
+ DBG_INFO("allocated files structure (%u used)\n",
+ (unsigned int)sconn->num_files);
+
+ *result = fsp;
+ return NT_STATUS_OK;
+
+fail:
+ if (fsp != NULL) {
+ TALLOC_FREE(fsp->fh);
+ }
+ TALLOC_FREE(fsp);
+
+ return status;
+}
+
+void fsp_set_gen_id(files_struct *fsp)
+{
+ static uint64_t gen_id = 1;
+
+ /*
+ * A billion of 64-bit increments per second gives us
+ * more than 500 years of runtime without wrap.
+ */
+ gen_id++;
+ fh_set_gen_id(fsp->fh, gen_id);
+}
+
+/****************************************************************************
+ Find first available file slot.
+****************************************************************************/
+
+NTSTATUS fsp_bind_smb(struct files_struct *fsp, struct smb_request *req)
+{
+ struct smbXsrv_open *op = NULL;
+ NTTIME now;
+ NTSTATUS status;
+
+ if (req == NULL) {
+ DBG_DEBUG("INTERNAL_OPEN_ONLY, skipping smbXsrv_open\n");
+ return NT_STATUS_OK;
+ }
+
+ now = timeval_to_nttime(&fsp->open_time);
+
+ status = smbXsrv_open_create(req->xconn,
+ fsp->conn->session_info,
+ now,
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ fsp->op = op;
+ op->compat = fsp;
+ fsp->fnum = op->local_id;
+
+ fsp->mid = req->mid;
+ req->chain_fsp = fsp;
+
+ DBG_DEBUG("fsp [%s] mid [%" PRIu64"]\n",
+ fsp_str_dbg(fsp), fsp->mid);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS file_new(struct smb_request *req, connection_struct *conn,
+ files_struct **result)
+{
+ struct smbd_server_connection *sconn = conn->sconn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+
+ status = fsp_bind_smb(fsp, req);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fsp_set_gen_id(fsp);
+
+ /*
+ * Create an smb_filename with "" for the base_name. There are very
+ * few NULL checks, so make sure it's initialized with something. to
+ * be safe until an audit can be done.
+ */
+ fsp->fsp_name = synthetic_smb_fname(fsp,
+ "",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (fsp->fsp_name == NULL) {
+ file_free(NULL, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_INFO("new file %s\n", fsp_fnum_dbg(fsp));
+
+ /* A new fsp invalidates the positive and
+ negative fsp_fi_cache as the new fsp is pushed
+ at the start of the list and we search from
+ a cache hit to the *end* of the list. */
+
+ ZERO_STRUCT(sconn->fsp_fi_cache);
+
+ *result = fsp;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS create_internal_fsp(connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ struct files_struct **_fsp)
+{
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ status = file_new(NULL, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Create an internal fsp for an *existing* directory.
+ *
+ * This should only be used by callers in the VFS that need to control the
+ * opening of the directory. Otherwise use open_internal_dirfsp().
+ */
+NTSTATUS create_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ struct files_struct **_fsp)
+{
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ status = create_internal_fsp(conn, smb_dname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->access_mask = FILE_LIST_DIRECTORY;
+ fsp->fsp_flags.is_directory = true;
+ fsp->fsp_flags.is_dirfsp = true;
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Open an internal fsp for an *existing* directory.
+ */
+NTSTATUS open_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ int _open_flags,
+ struct files_struct **_fsp)
+{
+ struct vfs_open_how how = { .flags = _open_flags, };
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ status = create_internal_dirfsp(conn, smb_dname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+#ifdef O_DIRECTORY
+ how.flags |= O_DIRECTORY;
+#endif
+ status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("Could not open fd for %s (%s)\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ if (!S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ DBG_ERR("%s is not a directory!\n",
+ smb_fname_str_dbg(smb_dname));
+ file_free(NULL, fsp);
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Convert a pathref dirfsp into a real fsp. No need to do any cwd
+ * tricks, we just open ".".
+ */
+NTSTATUS openat_internal_dir_from_pathref(
+ struct files_struct *dirfsp,
+ int _open_flags,
+ struct files_struct **_fsp)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ struct smb_filename *smb_dname = dirfsp->fsp_name;
+ struct files_struct *fsp = NULL;
+ char dot[] = ".";
+ struct smb_filename smb_dot = {
+ .base_name = dot,
+ .flags = smb_dname->flags,
+ .twrp = smb_dname->twrp,
+ };
+ struct vfs_open_how how = { .flags = _open_flags, };
+ NTSTATUS status;
+
+ status = create_internal_dirfsp(conn, smb_dname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Pointless for opening ".", but you never know...
+ */
+ how.flags |= O_NOFOLLOW;
+
+ status = fd_openat(dirfsp, &smb_dot, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("fd_openat(\"%s\", \".\") failed: %s\n",
+ fsp_str_dbg(dirfsp),
+ nt_errstr(status));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fsp->fsp_name->st = smb_dname->st;
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+/*
+ * The "link" in the name doesn't imply link in the filesystem
+ * sense. It's a object that "links" together an fsp and an smb_fname
+ * and the link allocated as talloc child of an fsp.
+ *
+ * The link is created for fsps that openat_pathref_fsp() returns in
+ * smb_fname->fsp. When this fsp is freed by file_free() by some caller
+ * somewhere, the destructor fsp_smb_fname_link_destructor() on the link object
+ * will use the link to reset the reference in smb_fname->fsp that is about to
+ * go away.
+ *
+ * This prevents smb_fname_internal_fsp_destructor() from seeing dangling fsp
+ * pointers.
+ */
+
+struct fsp_smb_fname_link {
+ struct fsp_smb_fname_link **smb_fname_link;
+ struct files_struct **smb_fname_fsp;
+};
+
+static int fsp_smb_fname_link_destructor(struct fsp_smb_fname_link *link)
+{
+ if (link->smb_fname_link == NULL) {
+ return 0;
+ }
+
+ *link->smb_fname_link = NULL;
+ *link->smb_fname_fsp = NULL;
+ return 0;
+}
+
+static NTSTATUS fsp_smb_fname_link(struct files_struct *fsp,
+ struct fsp_smb_fname_link **smb_fname_link,
+ struct files_struct **smb_fname_fsp)
+{
+ struct fsp_smb_fname_link *link = NULL;
+
+ SMB_ASSERT(*smb_fname_link == NULL);
+ SMB_ASSERT(*smb_fname_fsp == NULL);
+
+ link = talloc_zero(fsp, struct fsp_smb_fname_link);
+ if (link == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ link->smb_fname_link = smb_fname_link;
+ link->smb_fname_fsp = smb_fname_fsp;
+ *smb_fname_link = link;
+ *smb_fname_fsp = fsp;
+
+ talloc_set_destructor(link, fsp_smb_fname_link_destructor);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Free a link, carefully avoiding to trigger the link destructor
+ */
+static void destroy_fsp_smb_fname_link(struct fsp_smb_fname_link **_link)
+{
+ struct fsp_smb_fname_link *link = *_link;
+
+ if (link == NULL) {
+ return;
+ }
+ talloc_set_destructor(link, NULL);
+ TALLOC_FREE(link);
+ *_link = NULL;
+}
+
+/*
+ * Talloc destructor set on an smb_fname set by openat_pathref_fsp() used to
+ * close the embedded smb_fname->fsp.
+ */
+static int smb_fname_fsp_destructor(struct smb_filename *smb_fname)
+{
+ struct files_struct *fsp = smb_fname->fsp;
+ struct files_struct *base_fsp = NULL;
+ NTSTATUS status;
+ int saved_errno = errno;
+
+ destroy_fsp_smb_fname_link(&smb_fname->fsp_link);
+
+ if (fsp == NULL) {
+ errno = saved_errno;
+ return 0;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ base_fsp = fsp->base_fsp;
+ }
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Closing fd for fsp [%s] failed: %s. "
+ "Please check your filesystem!!!\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ }
+ file_free(NULL, fsp);
+ smb_fname->fsp = NULL;
+
+ if (base_fsp != NULL) {
+ base_fsp->stream_fsp = NULL;
+ status = fd_close(base_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Closing fd for base_fsp [%s] failed: %s. "
+ "Please check your filesystem!!!\n",
+ fsp_str_dbg(base_fsp), nt_errstr(status));
+ }
+ file_free(NULL, base_fsp);
+ }
+
+ errno = saved_errno;
+ return 0;
+}
+
+static NTSTATUS openat_pathref_fullname(
+ struct connection_struct *conn,
+ const struct files_struct *dirfsp,
+ struct files_struct *basefsp,
+ struct smb_filename **full_fname,
+ struct smb_filename *smb_fname,
+ const struct vfs_open_how *how)
+{
+ struct files_struct *fsp = NULL;
+ bool have_dirfsp = (dirfsp != NULL);
+ bool have_basefsp = (basefsp != NULL);
+ NTSTATUS status;
+
+ DBG_DEBUG("smb_fname [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ SMB_ASSERT(smb_fname->fsp == NULL);
+ SMB_ASSERT(have_dirfsp != have_basefsp);
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+ fsp_set_gen_id(fsp);
+ ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+ fsp->fsp_flags.is_pathref = true;
+
+ status = fsp_attach_smb_fname(fsp, full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ fsp_set_base_fsp(fsp, basefsp);
+
+ status = fd_openat(dirfsp, smb_fname, fsp, how);
+ if (!NT_STATUS_IS_OK(status)) {
+
+ smb_fname->st = fsp->fsp_name->st;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK))
+ {
+ /*
+ * streams_xattr return NT_STATUS_NOT_FOUND for
+ * opens of not yet existing streams.
+ *
+ * ELOOP maps to NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * and this will result from a open request from
+ * a POSIX client on a symlink.
+ *
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND is the simple
+ * ENOENT case.
+ *
+ * NT_STATUS_STOPPED_ON_SYMLINK is returned when trying
+ * to open a symlink, our callers are not interested in
+ * this.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ goto fail;
+ }
+
+ /*
+ * fd_openat() has done an FSTAT on the handle
+ * so update the smb_fname stat info with "truth".
+ * from the handle.
+ */
+ smb_fname->st = fsp->fsp_name->st;
+
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ status = fsp_smb_fname_link(fsp,
+ &smb_fname->fsp_link,
+ &smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ DBG_DEBUG("fsp [%s]: OK\n", fsp_str_dbg(fsp));
+
+ talloc_set_destructor(smb_fname, smb_fname_fsp_destructor);
+ return NT_STATUS_OK;
+
+fail:
+ DBG_DEBUG("Opening pathref for [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+
+ fsp_set_base_fsp(fsp, NULL);
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+}
+
+/*
+ * Open an internal O_PATH based fsp for smb_fname. If O_PATH is not
+ * available, open O_RDONLY as root. Both is done in fd_open() ->
+ * non_widelink_open(), triggered by setting fsp->fsp_flags.is_pathref to
+ * true.
+ */
+NTSTATUS openat_pathref_fsp(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname)
+{
+ connection_struct *conn = dirfsp->conn;
+ struct smb_filename *full_fname = NULL;
+ struct smb_filename *base_fname = NULL;
+ struct vfs_open_how how = { .flags = O_RDONLY|O_NONBLOCK, };
+ NTSTATUS status;
+
+ DBG_DEBUG("smb_fname [%s]\n", smb_fname_str_dbg(smb_fname));
+
+ if (smb_fname->fsp != NULL) {
+ /* We already have one for this name. */
+ DBG_DEBUG("smb_fname [%s] already has a pathref fsp.\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OK;
+ }
+
+ if (is_named_stream(smb_fname) &&
+ ((conn->fs_capabilities & FILE_NAMED_STREAMS) == 0)) {
+ DBG_DEBUG("stream open [%s] on non-stream share\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ if (!is_named_stream(smb_fname)) {
+ /*
+ * openat_pathref_fullname() will make "full_fname" a
+ * talloc child of the smb_fname->fsp. Don't use
+ * talloc_tos() to allocate it to avoid making the
+ * talloc stackframe pool long-lived.
+ */
+ full_fname = full_path_from_dirfsp_atname(
+ conn,
+ dirfsp,
+ smb_fname);
+ if (full_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ status = openat_pathref_fullname(
+ conn, dirfsp, NULL, &full_fname, smb_fname, &how);
+ TALLOC_FREE(full_fname);
+ return status;
+ }
+
+ /*
+ * stream open
+ */
+ base_fname = cp_smb_filename_nostream(conn, smb_fname);
+ if (base_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ full_fname = full_path_from_dirfsp_atname(
+ conn, /* no talloc_tos(), see comment above */
+ dirfsp,
+ base_fname);
+ if (full_fname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ status = openat_pathref_fullname(
+ conn, dirfsp, NULL, &full_fname, base_fname, &how);
+ TALLOC_FREE(full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("openat_pathref_fullname() failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = open_stream_pathref_fsp(&base_fname->fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("open_stream_pathref_fsp failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ smb_fname_fsp_unlink(base_fname);
+fail:
+ TALLOC_FREE(base_fname);
+ return status;
+}
+
+/*
+ * Open a stream given an already opened base_fsp. Avoid
+ * non_widelink_open: This is only valid for the case where we have a
+ * valid non-cwd_fsp dirfsp that we can pass to SMB_VFS_OPENAT()
+ */
+NTSTATUS open_stream_pathref_fsp(
+ struct files_struct **_base_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct files_struct *base_fsp = *_base_fsp;
+ connection_struct *conn = base_fsp->conn;
+ struct smb_filename *base_fname = base_fsp->fsp_name;
+ struct smb_filename *full_fname = NULL;
+ struct vfs_open_how how = { .flags = O_RDONLY|O_NONBLOCK, };
+ NTSTATUS status;
+
+ SMB_ASSERT(smb_fname->fsp == NULL);
+ SMB_ASSERT(is_named_stream(smb_fname));
+
+ full_fname = synthetic_smb_fname(
+ conn, /* no talloc_tos(), this will be long-lived */
+ base_fname->base_name,
+ smb_fname->stream_name,
+ &smb_fname->st,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (full_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = openat_pathref_fullname(
+ conn, NULL, base_fsp, &full_fname, smb_fname, &how);
+ TALLOC_FREE(full_fname);
+ return status;
+}
+
+static char *path_to_strv(TALLOC_CTX *mem_ctx, const char *path)
+{
+ char *result = talloc_strdup(mem_ctx, path);
+
+ if (result == NULL) {
+ return NULL;
+ }
+ string_replace(result, '/', '\0');
+ return result;
+}
+
+NTSTATUS readlink_talloc(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ char **_substitute)
+{
+ struct smb_filename null_fname = {
+ .base_name = discard_const_p(char, ""),
+ };
+ char buf[PATH_MAX];
+ ssize_t ret;
+ char *substitute;
+ NTSTATUS status;
+
+ if (smb_relname == NULL) {
+ /*
+ * We have a Linux O_PATH handle in dirfsp and want to
+ * read its value, essentially a freadlink
+ */
+ smb_relname = &null_fname;
+ }
+
+ ret = SMB_VFS_READLINKAT(
+ dirfsp->conn, dirfsp, smb_relname, buf, sizeof(buf));
+ if (ret < 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_READLINKAT() failed: %s\n",
+ strerror(errno));
+ return status;
+ }
+
+ if ((size_t)ret == sizeof(buf)) {
+ /*
+ * Do we need symlink targets longer than PATH_MAX?
+ */
+ DBG_DEBUG("Got full %zu bytes from readlink, too long\n",
+ sizeof(buf));
+ return NT_STATUS_BUFFER_OVERFLOW;
+ }
+
+ substitute = talloc_strndup(mem_ctx, buf, ret);
+ if (substitute == NULL) {
+ DBG_DEBUG("talloc_strndup() failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *_substitute = substitute;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS read_symlink_reparse(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct symlink_reparse_struct **_symlink)
+{
+ struct symlink_reparse_struct *symlink = NULL;
+ NTSTATUS status;
+
+ symlink = talloc_zero(mem_ctx, struct symlink_reparse_struct);
+ if (symlink == NULL) {
+ goto nomem;
+ }
+
+ status = readlink_talloc(
+ symlink, dirfsp, smb_relname, &symlink->substitute_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("readlink_talloc failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ if (symlink->substitute_name[0] == '/') {
+ char *subdir_path = NULL;
+ char *abs_target_canon = NULL;
+ const char *relative = NULL;
+ bool in_share;
+
+ subdir_path = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ dirfsp->conn->connectpath,
+ dirfsp->fsp_name->base_name);
+ if (subdir_path == NULL) {
+ goto nomem;
+ }
+
+ abs_target_canon =
+ canonicalize_absolute_path(talloc_tos(),
+ symlink->substitute_name);
+ if (abs_target_canon == NULL) {
+ goto nomem;
+ }
+
+ in_share = subdir_of(subdir_path,
+ strlen(subdir_path),
+ abs_target_canon,
+ &relative);
+ if (in_share) {
+ TALLOC_FREE(symlink->substitute_name);
+ symlink->substitute_name =
+ talloc_strdup(symlink, relative);
+ if (symlink->substitute_name == NULL) {
+ goto nomem;
+ }
+ }
+ }
+
+ if (!IS_DIRECTORY_SEP(symlink->substitute_name[0])) {
+ symlink->flags |= SYMLINK_FLAG_RELATIVE;
+ }
+
+ *_symlink = symlink;
+ return NT_STATUS_OK;
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ TALLOC_FREE(symlink);
+ return status;
+}
+
+static bool full_path_extend(char **dir, const char *atname)
+{
+ talloc_asprintf_addbuf(dir,
+ "%s%s",
+ (*dir)[0] == '\0' ? "" : "/",
+ atname);
+ return (*dir) != NULL;
+}
+
+NTSTATUS create_open_symlink_err(TALLOC_CTX *mem_ctx,
+ files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct open_symlink_err **_err)
+{
+ struct open_symlink_err *err = NULL;
+ NTSTATUS status;
+
+ err = talloc_zero(mem_ctx, struct open_symlink_err);
+ if (err == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = read_symlink_reparse(err, dirfsp, smb_relname, &err->reparse);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(err);
+ return status;
+ }
+
+ *_err = err;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Create the memcache-key for GETREALFILENAME_CACHE: This supplements
+ * the stat cache for the last component to be looked up. Cache
+ * contents is the correctly capitalized translation of the parameter
+ * "name" as it exists on disk. This is indexed by inode of the dirfsp
+ * and name, and contrary to stat_cahce_lookup() it does not
+ * vfs_stat() the last component. This will be taken care of by an
+ * attempt to do a openat_pathref_fsp().
+ */
+static bool get_real_filename_cache_key(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *name,
+ DATA_BLOB *_key)
+{
+ struct file_id fid = vfs_file_id_from_sbuf(dirfsp->conn,
+ &dirfsp->fsp_name->st);
+ char *upper = NULL;
+ uint8_t *key = NULL;
+ size_t namelen, keylen;
+
+ upper = talloc_strdup_upper(mem_ctx, name);
+ if (upper == NULL) {
+ return false;
+ }
+ namelen = talloc_get_size(upper);
+
+ keylen = namelen + sizeof(fid);
+ if (keylen < sizeof(fid)) {
+ TALLOC_FREE(upper);
+ return false;
+ }
+
+ key = talloc_size(mem_ctx, keylen);
+ if (key == NULL) {
+ TALLOC_FREE(upper);
+ return false;
+ }
+
+ memcpy(key, &fid, sizeof(fid));
+ memcpy(key + sizeof(fid), upper, namelen);
+ TALLOC_FREE(upper);
+
+ *_key = (DATA_BLOB){
+ .data = key,
+ .length = keylen,
+ };
+ return true;
+}
+
+static int smb_vfs_openat_ci(TALLOC_CTX *mem_ctx,
+ bool case_sensitive,
+ struct connection_struct *conn,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_rel,
+ files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ char *orig_base_name = smb_fname_rel->base_name;
+ DATA_BLOB cache_key = {
+ .data = NULL,
+ };
+ DATA_BLOB cache_value = {
+ .data = NULL,
+ };
+ NTSTATUS status;
+ int fd;
+ bool ok;
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+ if ((fd >= 0) || case_sensitive) {
+ return fd;
+ }
+ if (errno != ENOENT) {
+ return -1;
+ }
+
+ if (!lp_stat_cache()) {
+ goto lookup;
+ }
+
+ ok = get_real_filename_cache_key(mem_ctx,
+ dirfsp,
+ orig_base_name,
+ &cache_key);
+ if (!ok) {
+ /*
+ * probably ENOMEM, just bail
+ */
+ errno = ENOMEM;
+ return -1;
+ }
+
+ DO_PROFILE_INC(statcache_lookups);
+
+ ok = memcache_lookup(NULL,
+ GETREALFILENAME_CACHE,
+ cache_key,
+ &cache_value);
+ if (!ok) {
+ DO_PROFILE_INC(statcache_misses);
+ goto lookup;
+ }
+ DO_PROFILE_INC(statcache_hits);
+
+ smb_fname_rel->base_name = talloc_strndup(mem_ctx,
+ (char *)cache_value.data,
+ cache_value.length);
+ if (smb_fname_rel->base_name == NULL) {
+ TALLOC_FREE(cache_key.data);
+ smb_fname_rel->base_name = orig_base_name;
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("veto files rejecting last component %s\n",
+ smb_fname_str_dbg(smb_fname_rel));
+ TALLOC_FREE(cache_key.data);
+ smb_fname_rel->base_name = orig_base_name;
+ errno = EPERM;
+ return -1;
+ }
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+ if (fd >= 0) {
+ TALLOC_FREE(cache_key.data);
+ return fd;
+ }
+
+ memcache_delete(NULL, GETREALFILENAME_CACHE, cache_key);
+
+ /*
+ * For the "new filename" case we need to preserve the
+ * capitalization the client sent us, see
+ * https://bugzilla.samba.org/show_bug.cgi?id=15481
+ */
+ TALLOC_FREE(smb_fname_rel->base_name);
+ smb_fname_rel->base_name = orig_base_name;
+
+lookup:
+
+ status = get_real_filename_at(dirfsp,
+ orig_base_name,
+ mem_ctx,
+ &smb_fname_rel->base_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("get_real_filename_at() failed: %s\n",
+ nt_errstr(status));
+ errno = ENOENT;
+ return -1;
+ }
+
+ if (IS_VETO_PATH(conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("found veto files path component "
+ "%s => %s\n",
+ orig_base_name,
+ smb_fname_rel->base_name);
+ TALLOC_FREE(smb_fname_rel->base_name);
+ smb_fname_rel->base_name = orig_base_name;
+ errno = ENOENT;
+ return -1;
+ }
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, smb_fname_rel, fsp, how);
+
+ if ((fd >= 0) && (cache_key.data != NULL)) {
+ DATA_BLOB value = {
+ .data = (uint8_t *)smb_fname_rel->base_name,
+ .length = strlen(smb_fname_rel->base_name) + 1,
+ };
+
+ memcache_add(NULL, GETREALFILENAME_CACHE, cache_key, value);
+ TALLOC_FREE(cache_key.data);
+ }
+
+ return fd;
+}
+
+NTSTATUS openat_pathref_fsp_nosymlink(TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ struct files_struct *in_dirfsp,
+ const char *path_in,
+ NTTIME twrp,
+ bool posix,
+ struct smb_filename **_smb_fname,
+ struct open_symlink_err **_symlink_err)
+{
+ struct files_struct *dirfsp = in_dirfsp;
+ struct smb_filename full_fname = {
+ .base_name = NULL,
+ .twrp = twrp,
+ .flags = posix ? SMB_FILENAME_POSIX_PATH : 0,
+ };
+ struct smb_filename rel_fname = {
+ .base_name = NULL,
+ .twrp = twrp,
+ .flags = full_fname.flags,
+ };
+ struct smb_filename *result = NULL;
+ struct open_symlink_err *symlink_err = NULL;
+ struct files_struct *fsp = NULL;
+ char *path = NULL, *next = NULL;
+ bool ok, is_toplevel;
+ int fd;
+ NTSTATUS status;
+ struct vfs_open_how how = {
+ .flags = O_NOFOLLOW | O_NONBLOCK,
+ .mode = 0,
+ };
+
+ DBG_DEBUG("path_in=%s\n", path_in);
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_new() failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+ fsp_set_gen_id(fsp);
+ ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+ fsp->fsp_name = &full_fname;
+
+#ifdef O_PATH
+ /*
+ * Add O_PATH manually, doing this by setting
+ * fsp->fsp_flags.is_pathref will make us become_root() in the
+ * non-O_PATH case, which would cause a security problem.
+ */
+ how.flags |= O_PATH;
+#else
+#ifdef O_SEARCH
+ /*
+ * O_SEARCH just checks for the "x" bit. We are traversing
+ * directories, so we don't need the implicit O_RDONLY ("r"
+ * permissions) but only the "x"-permissions requested by
+ * O_SEARCH. We need either O_PATH or O_SEARCH to correctly
+ * function, without either we will incorrectly require also
+ * the "r" bit when traversing the directory hierarchy.
+ */
+ how.flags |= O_SEARCH;
+#endif
+#endif
+
+ is_toplevel = (dirfsp == dirfsp->conn->cwd_fsp);
+ is_toplevel |= ISDOT(dirfsp->fsp_name->base_name);
+
+ full_fname.base_name =
+ talloc_strdup(talloc_tos(),
+ is_toplevel ? "" : dirfsp->fsp_name->base_name);
+ if (full_fname.base_name == NULL) {
+ DBG_DEBUG("talloc_strdup() failed\n");
+ goto nomem;
+ }
+
+ /*
+ * First split the path into individual components.
+ */
+ path = path_to_strv(talloc_tos(), path_in);
+ if (path == NULL) {
+ DBG_DEBUG("path_to_strv() failed\n");
+ goto nomem;
+ }
+
+ /*
+ * First we loop over all components
+ * in order to verify, there's no '.' or '..'
+ */
+ rel_fname.base_name = path;
+ while (rel_fname.base_name != NULL) {
+
+ next = strv_next(path, rel_fname.base_name);
+
+ /*
+ * Path sanitizing further up has cleaned or rejected
+ * empty path components. Assert this here.
+ */
+ SMB_ASSERT(rel_fname.base_name[0] != '\0');
+
+ if (ISDOT(rel_fname.base_name) ||
+ ISDOTDOT(rel_fname.base_name)) {
+ DBG_DEBUG("%s contains a dot\n", path_in);
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+
+ /* Check veto files. */
+ if (IS_VETO_PATH(conn, rel_fname.base_name)) {
+ DBG_DEBUG("%s contains veto files path component %s\n",
+ path_in, rel_fname.base_name);
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto fail;
+ }
+
+ rel_fname.base_name = next;
+ }
+
+ if (conn->open_how_resolve & VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS) {
+
+ /*
+ * Try a direct openat2 with RESOLVE_NO_SYMLINKS to
+ * avoid the openat/close loop further down.
+ */
+
+ rel_fname.base_name = discard_const_p(char, path_in);
+ how.resolve = VFS_OPEN_HOW_RESOLVE_NO_SYMLINKS;
+
+ fd = SMB_VFS_OPENAT(conn, dirfsp, &rel_fname, fsp, &how);
+ if (fd >= 0) {
+ fsp_set_fd(fsp, fd);
+ ok = full_path_extend(&full_fname.base_name,
+ rel_fname.base_name);
+ if (!ok) {
+ goto nomem;
+ }
+ goto done;
+ }
+
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_OPENAT(%s, %s, RESOLVE_NO_SYMLINKS) "
+ "returned %d %s => %s\n",
+ smb_fname_str_dbg(dirfsp->fsp_name), path_in,
+ errno, strerror(errno), nt_errstr(status));
+ SMB_ASSERT(fd == -1);
+ switch (errno) {
+ case ENOSYS:
+ /*
+ * We got ENOSYS, so fallback to the old code
+ * if the kernel doesn't support openat2() yet.
+ */
+ break;
+
+ case ELOOP:
+ case ENOTDIR:
+ /*
+ * For ELOOP we also fallback in order to
+ * return the correct information with
+ * NT_STATUS_STOPPED_ON_SYMLINK.
+ *
+ * O_NOFOLLOW|O_DIRECTORY results in
+ * ENOTDIR instead of ELOOP for the final
+ * component.
+ */
+ break;
+
+ case ENOENT:
+ /*
+ * If we got ENOENT, the filesystem could
+ * be case sensitive. For now we only do
+ * the get_real_filename_at() dance in
+ * the fallback loop below.
+ */
+ break;
+
+ default:
+ goto fail;
+ }
+
+ /*
+ * Just fallback to the openat loop
+ */
+ how.resolve = 0;
+ }
+
+ /*
+ * Now we loop over all components
+ * opening each one and using it
+ * as dirfd for the next one.
+ *
+ * It means we can detect symlinks
+ * within the path.
+ */
+ rel_fname.base_name = path;
+next:
+ next = strv_next(path, rel_fname.base_name);
+
+ fd = smb_vfs_openat_ci(talloc_tos(),
+ posix || conn->case_sensitive,
+ conn,
+ dirfsp,
+ &rel_fname,
+ fsp,
+ &how);
+
+#ifndef O_PATH
+ if ((fd == -1) && (errno == ELOOP)) {
+ int ret;
+
+ /*
+ * openat() hit a symlink. With O_PATH we open the
+ * symlink and get ENOTDIR in the next round, see
+ * below.
+ */
+
+ status = create_open_symlink_err(mem_ctx,
+ dirfsp,
+ &rel_fname,
+ &symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("create_open_symlink_err failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (next != NULL) {
+ size_t parsed = next - path;
+ size_t len = talloc_get_size(path);
+ symlink_err->unparsed = len - parsed;
+ }
+
+ /*
+ * We know rel_fname is a symlink, now fill in the
+ * rest of the metadata for our callers.
+ */
+
+ ret = SMB_VFS_FSTATAT(conn,
+ dirfsp,
+ &rel_fname,
+ &symlink_err->st,
+ AT_SYMLINK_NOFOLLOW);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_FSTATAT(%s/%s) failed: %s\n",
+ fsp_str_dbg(dirfsp),
+ rel_fname.base_name,
+ strerror(errno));
+ TALLOC_FREE(symlink_err);
+ goto fail;
+ }
+
+ if (!S_ISLNK(symlink_err->st.st_ex_mode)) {
+ /*
+ * Hit a race: readlink_talloc() worked before
+ * the fstatat(), but rel_fname changed to
+ * something that's not a symlink.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ TALLOC_FREE(symlink_err);
+ goto fail;
+ }
+
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto fail;
+ }
+#endif
+
+ if ((fd == -1) && (errno == ENOTDIR)) {
+ size_t parsed, len;
+
+ /*
+ * dirfsp does not point at a directory, try a
+ * freadlink.
+ */
+
+ status = create_open_symlink_err(mem_ctx,
+ dirfsp,
+ NULL,
+ &symlink_err);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("create_open_symlink_err failed: %s\n",
+ nt_errstr(status));
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto fail;
+ }
+
+ parsed = rel_fname.base_name - path;
+ len = talloc_get_size(path);
+ symlink_err->unparsed = len - parsed;
+
+ symlink_err->st = dirfsp->fsp_name->st;
+
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto fail;
+ }
+
+ if (fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_OPENAT() failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+ fsp_set_fd(fsp, fd);
+
+ ok = full_path_extend(&full_fname.base_name, rel_fname.base_name);
+ if (!ok) {
+ goto nomem;
+ }
+
+ if (next != NULL) {
+ struct files_struct *tmp = NULL;
+
+ if (dirfsp != in_dirfsp) {
+ fd_close(dirfsp);
+ }
+
+ tmp = dirfsp;
+ dirfsp = fsp;
+
+ if (tmp == in_dirfsp) {
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_new() failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ fsp->fsp_name = &full_fname;
+ } else {
+ fsp = tmp;
+ }
+
+ rel_fname.base_name = next;
+
+ goto next;
+ }
+
+ if (dirfsp != in_dirfsp) {
+ SMB_ASSERT(fsp_get_pathref_fd(dirfsp) != -1);
+ fd_close(dirfsp);
+ dirfsp->fsp_name = NULL;
+ file_free(NULL, dirfsp);
+ dirfsp = NULL;
+ }
+
+done:
+ fsp->fsp_flags.is_pathref = true;
+ fsp->fsp_name = NULL;
+
+ status = fsp_set_smb_fname(fsp, &full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_set_smb_fname() failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("vfs_stat_fsp(%s) failed: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ /*
+ * Last component was a symlink we opened with O_PATH, fail it
+ * here.
+ */
+ status = create_open_symlink_err(mem_ctx,
+ fsp,
+ NULL,
+ &symlink_err);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ symlink_err->st = fsp->fsp_name->st;
+
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto fail;
+ }
+
+ /*
+ * We must correctly set fsp->file_id as code inside
+ * open.c will use this to check if delete_on_close
+ * has been set on the dirfsp.
+ */
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ result = cp_smb_filename(mem_ctx, fsp->fsp_name);
+ if (result == NULL) {
+ DBG_DEBUG("cp_smb_filename() failed\n");
+ goto nomem;
+ }
+
+ status = fsp_smb_fname_link(fsp,
+ &result->fsp_link,
+ &result->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ talloc_set_destructor(result, smb_fname_fsp_destructor);
+
+ *_smb_fname = result;
+
+ DBG_DEBUG("returning %s\n", smb_fname_str_dbg(result));
+
+ return NT_STATUS_OK;
+
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ if (fsp != NULL) {
+ if (fsp_get_pathref_fd(fsp) != -1) {
+ fd_close(fsp);
+ }
+ file_free(NULL, fsp);
+ fsp = NULL;
+ }
+
+ if ((dirfsp != NULL) && (dirfsp != in_dirfsp)) {
+ SMB_ASSERT(fsp_get_pathref_fd(dirfsp) != -1);
+ fd_close(dirfsp);
+ dirfsp->fsp_name = NULL;
+ file_free(NULL, dirfsp);
+ dirfsp = NULL;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ *_symlink_err = symlink_err;
+ }
+
+ TALLOC_FREE(path);
+ return status;
+}
+
+/*
+ * Open smb_fname_rel->fsp as a pathref fsp with a case insensitive
+ * fallback using GETREALFILENAME_CACHE and get_real_filename_at() if
+ * the first attempt based on the filename sent by the client gives
+ * ENOENT.
+ */
+NTSTATUS openat_pathref_fsp_lcomp(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_rel,
+ uint32_t ucf_flags)
+{
+ struct connection_struct *conn = dirfsp->conn;
+ const char *orig_rel_base_name = smb_fname_rel->base_name;
+ struct files_struct *fsp = NULL;
+ struct smb_filename *full_fname = NULL;
+ struct vfs_open_how how = {
+ .flags = O_RDONLY | O_NONBLOCK | O_NOFOLLOW,
+ };
+ NTSTATUS status;
+ int ret, fd;
+
+ /*
+ * Make sure we don't need of the all the magic in
+ * openat_pathref_fsp() with regards non_widelink_open etc.
+ */
+
+ SMB_ASSERT((smb_fname_rel->fsp == NULL) &&
+ (dirfsp != dirfsp->conn->cwd_fsp) &&
+ (strchr_m(smb_fname_rel->base_name, '/') == NULL) &&
+ !is_named_stream(smb_fname_rel));
+
+ SET_STAT_INVALID(smb_fname_rel->st);
+
+ /* Check veto files - only looks at last component. */
+ if (IS_VETO_PATH(dirfsp->conn, smb_fname_rel->base_name)) {
+ DBG_DEBUG("veto files rejecting last component %s\n",
+ smb_fname_str_dbg(smb_fname_rel));
+ return NT_STATUS_NETWORK_OPEN_RESTRICTION;
+ }
+
+ status = fsp_new(conn, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_new() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ GetTimeOfDay(&fsp->open_time);
+ fsp_set_gen_id(fsp);
+ ZERO_STRUCT(conn->sconn->fsp_fi_cache);
+
+ fsp->fsp_flags.is_pathref = true;
+
+ full_fname = full_path_from_dirfsp_atname(conn, dirfsp, smb_fname_rel);
+ if (full_fname == NULL) {
+ DBG_DEBUG("full_path_from_dirfsp_atname(%s/%s) failed\n",
+ dirfsp->fsp_name->base_name,
+ smb_fname_rel->base_name);
+ file_free(NULL, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = fsp_attach_smb_fname(fsp, &full_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_attach_smb_fname(fsp, %s) failed: %s\n",
+ smb_fname_str_dbg(full_fname),
+ nt_errstr(status));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fd = smb_vfs_openat_ci(smb_fname_rel,
+ (ucf_flags & UCF_POSIX_PATHNAMES) ||
+ conn->case_sensitive,
+ conn,
+ dirfsp,
+ smb_fname_rel,
+ fsp,
+ &how);
+
+ if ((fd == -1) && (errno == ENOENT)) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("smb_vfs_openat(%s/%s) failed: %s\n",
+ dirfsp->fsp_name->base_name,
+ smb_fname_rel->base_name,
+ strerror(errno));
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ if (smb_fname_rel->base_name != orig_rel_base_name) {
+ struct smb_filename new_fullname = *smb_fname_rel;
+
+ DBG_DEBUG("rel->base_name changed from %s to %s\n",
+ orig_rel_base_name,
+ smb_fname_rel->base_name);
+
+ new_fullname.base_name = full_path_from_dirfsp_at_basename(
+ talloc_tos(), dirfsp, new_fullname.base_name);
+ if (new_fullname.base_name == NULL) {
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = fsp_set_smb_fname(fsp, &new_fullname);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+ }
+ }
+
+ fsp_set_fd(fsp, fd);
+
+ if (fd >= 0) {
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ } else {
+ ret = SMB_VFS_FSTATAT(fsp->conn,
+ dirfsp,
+ smb_fname_rel,
+ &fsp->fsp_name->st,
+ AT_SYMLINK_NOFOLLOW);
+ }
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("SMB_VFS_%sSTAT(%s/%s) failed: %s\n",
+ (fd >= 0) ? "F" : "",
+ dirfsp->fsp_name->base_name,
+ smb_fname_rel->base_name,
+ strerror(errno));
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &fsp->fsp_name->st);
+
+ smb_fname_rel->st = fsp->fsp_name->st;
+
+ status = fsp_smb_fname_link(fsp,
+ &smb_fname_rel->fsp_link,
+ &smb_fname_rel->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_smb_fname_link() failed: %s\n",
+ nt_errstr(status));
+ fd_close(fsp);
+ file_free(NULL, fsp);
+ return status;
+ }
+
+ DBG_DEBUG("fsp [%s]: OK, fd=%d\n", fsp_str_dbg(fsp), fd);
+
+ talloc_set_destructor(smb_fname_rel, smb_fname_fsp_destructor);
+ return NT_STATUS_OK;
+}
+
+void smb_fname_fsp_unlink(struct smb_filename *smb_fname)
+{
+ talloc_set_destructor(smb_fname, NULL);
+ smb_fname->fsp = NULL;
+ destroy_fsp_smb_fname_link(&smb_fname->fsp_link);
+}
+
+/*
+ * Move any existing embedded fsp refs from the src name to the
+ * destination. It's safe to call this on src smb_fname's that have no embedded
+ * pathref fsp.
+ */
+NTSTATUS move_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ struct smb_filename *smb_fname_src)
+{
+ NTSTATUS status;
+
+ /*
+ * The target should always not be linked yet!
+ */
+ SMB_ASSERT(smb_fname_dst->fsp == NULL);
+ SMB_ASSERT(smb_fname_dst->fsp_link == NULL);
+
+ if (smb_fname_src->fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ status = fsp_smb_fname_link(smb_fname_src->fsp,
+ &smb_fname_dst->fsp_link,
+ &smb_fname_dst->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ talloc_set_destructor(smb_fname_dst, smb_fname_fsp_destructor);
+
+ smb_fname_fsp_unlink(smb_fname_src);
+
+ return NT_STATUS_OK;
+}
+
+static int fsp_ref_no_close_destructor(struct smb_filename *smb_fname)
+{
+ destroy_fsp_smb_fname_link(&smb_fname->fsp_link);
+ return 0;
+}
+
+NTSTATUS reference_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ const struct smb_filename *smb_fname_src)
+{
+ NTSTATUS status;
+
+ /*
+ * The target should always not be linked yet!
+ */
+ SMB_ASSERT(smb_fname_dst->fsp == NULL);
+ SMB_ASSERT(smb_fname_dst->fsp_link == NULL);
+
+ if (smb_fname_src->fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ status = fsp_smb_fname_link(smb_fname_src->fsp,
+ &smb_fname_dst->fsp_link,
+ &smb_fname_dst->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ talloc_set_destructor(smb_fname_dst, fsp_ref_no_close_destructor);
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Create an smb_fname and open smb_fname->fsp pathref
+ **/
+NTSTATUS synthetic_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *base_name,
+ const char *stream_name,
+ const SMB_STRUCT_STAT *psbuf,
+ NTTIME twrp,
+ uint32_t flags,
+ struct smb_filename **_smb_fname)
+{
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ smb_fname = synthetic_smb_fname(mem_ctx,
+ base_name,
+ stream_name,
+ psbuf,
+ twrp,
+ flags);
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = openat_pathref_fsp(dirfsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("opening [%s] failed\n",
+ smb_fname_str_dbg(smb_fname));
+ TALLOC_FREE(smb_fname);
+ return status;
+ }
+
+ *_smb_fname = smb_fname;
+ return NT_STATUS_OK;
+}
+
+/**
+ * Turn a path into a parent pathref and atname
+ *
+ * This returns the parent pathref in _parent and the name relative to it. If
+ * smb_fname was a pathref (ie smb_fname->fsp != NULL), then _atname will be a
+ * pathref as well, ie _atname->fsp will point at the same fsp as
+ * smb_fname->fsp.
+ **/
+NTSTATUS parent_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct smb_filename **_parent,
+ struct smb_filename **_atname)
+{
+ struct smb_filename *parent = NULL;
+ struct smb_filename *atname = NULL;
+ NTSTATUS status;
+
+ status = SMB_VFS_PARENT_PATHNAME(dirfsp->conn,
+ mem_ctx,
+ smb_fname,
+ &parent,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We know that the parent name must
+ * exist, and the name has been canonicalized
+ * even if this was a POSIX pathname.
+ * Ensure that we follow symlinks for
+ * the parent. See the torture test
+ * POSIX-SYMLINK-PARENT for details.
+ */
+ parent->flags &= ~SMB_FILENAME_POSIX_PATH;
+
+ status = openat_pathref_fsp(dirfsp, parent);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(parent);
+ return status;
+ }
+
+ status = reference_smb_fname_fsp_link(atname, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(parent);
+ return status;
+ }
+
+ *_parent = parent;
+ *_atname = atname;
+ return NT_STATUS_OK;
+}
+
+static bool close_file_in_loop(struct files_struct *fsp,
+ enum file_close_type close_type)
+{
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * This is a stream, it can't be a base
+ */
+ SMB_ASSERT(fsp->stream_fsp == NULL);
+ SMB_ASSERT(fsp->base_fsp->stream_fsp == fsp);
+
+ /*
+ * Remove the base<->stream link so that
+ * close_file_free() does not close fsp->base_fsp as
+ * well. This would destroy walking the linked list of
+ * fsps.
+ */
+ fsp->base_fsp->stream_fsp = NULL;
+ fsp->base_fsp = NULL;
+
+ close_file_free(NULL, &fsp, close_type);
+ return NULL;
+ }
+
+ if (fsp->stream_fsp != NULL) {
+ /*
+ * This is the base of a stream.
+ */
+ SMB_ASSERT(fsp->stream_fsp->base_fsp == fsp);
+
+ /*
+ * Remove the base<->stream link. This will make fsp
+ * look like a normal fsp for the next round.
+ */
+ fsp->stream_fsp->base_fsp = NULL;
+ fsp->stream_fsp = NULL;
+
+ /*
+ * Have us called back a second time. In the second
+ * round, "fsp" now looks like a normal fsp.
+ */
+ return false;
+ }
+
+ close_file_free(NULL, &fsp, close_type);
+ return true;
+}
+
+/****************************************************************************
+ Close all open files for a connection.
+****************************************************************************/
+
+struct file_close_conn_state {
+ struct connection_struct *conn;
+ enum file_close_type close_type;
+ bool fsp_left_behind;
+};
+
+static struct files_struct *file_close_conn_fn(
+ struct files_struct *fsp,
+ void *private_data)
+{
+ struct file_close_conn_state *state = private_data;
+ bool did_close;
+
+ if (fsp->conn != state->conn) {
+ return NULL;
+ }
+
+ if (fsp->op != NULL && fsp->op->global->durable) {
+ /*
+ * A tree disconnect closes a durable handle
+ */
+ fsp->op->global->durable = false;
+ }
+
+ did_close = close_file_in_loop(fsp, state->close_type);
+ if (!did_close) {
+ state->fsp_left_behind = true;
+ }
+
+ return NULL;
+}
+
+void file_close_conn(connection_struct *conn, enum file_close_type close_type)
+{
+ struct file_close_conn_state state = { .conn = conn,
+ .close_type = close_type };
+
+ files_forall(conn->sconn, file_close_conn_fn, &state);
+
+ if (state.fsp_left_behind) {
+ state.fsp_left_behind = false;
+ files_forall(conn->sconn, file_close_conn_fn, &state);
+ SMB_ASSERT(!state.fsp_left_behind);
+ }
+}
+
+/****************************************************************************
+ Initialise file structures.
+****************************************************************************/
+
+static int files_max_open_fds;
+
+bool file_init_global(void)
+{
+ int request_max = lp_max_open_files();
+ int real_lim;
+ int real_max;
+
+ if (files_max_open_fds != 0) {
+ return true;
+ }
+
+ /*
+ * Set the max_open files to be the requested
+ * max plus a fudgefactor to allow for the extra
+ * fd's we need such as log files etc...
+ */
+ real_lim = set_maxfiles(request_max + MAX_OPEN_FUDGEFACTOR);
+
+ real_max = real_lim - MAX_OPEN_FUDGEFACTOR;
+
+ if (real_max + FILE_HANDLE_OFFSET + MAX_OPEN_PIPES > 65536) {
+ real_max = 65536 - FILE_HANDLE_OFFSET - MAX_OPEN_PIPES;
+ }
+
+ if (real_max != request_max) {
+ DEBUG(1, ("file_init_global: Information only: requested %d "
+ "open files, %d are available.\n",
+ request_max, real_max));
+ }
+
+ SMB_ASSERT(real_max > 100);
+
+ files_max_open_fds = real_max;
+ return true;
+}
+
+bool file_init(struct smbd_server_connection *sconn)
+{
+ bool ok;
+
+ ok = file_init_global();
+ if (!ok) {
+ return false;
+ }
+
+ sconn->real_max_open_files = files_max_open_fds;
+
+ return true;
+}
+
+/****************************************************************************
+ Close files open by a specified vuid.
+****************************************************************************/
+
+struct file_close_user_state {
+ uint64_t vuid;
+ bool fsp_left_behind;
+};
+
+static struct files_struct *file_close_user_fn(
+ struct files_struct *fsp,
+ void *private_data)
+{
+ struct file_close_user_state *state = private_data;
+ bool did_close;
+
+ if (fsp->vuid != state->vuid) {
+ return NULL;
+ }
+
+ did_close = close_file_in_loop(fsp, SHUTDOWN_CLOSE);
+ if (!did_close) {
+ state->fsp_left_behind = true;
+ }
+
+ return NULL;
+}
+
+void file_close_user(struct smbd_server_connection *sconn, uint64_t vuid)
+{
+ struct file_close_user_state state = { .vuid = vuid };
+
+ files_forall(sconn, file_close_user_fn, &state);
+
+ if (state.fsp_left_behind) {
+ state.fsp_left_behind = false;
+ files_forall(sconn, file_close_user_fn, &state);
+ SMB_ASSERT(!state.fsp_left_behind);
+ }
+}
+
+/*
+ * Walk the files table until "fn" returns non-NULL
+ */
+
+struct files_struct *files_forall(
+ struct smbd_server_connection *sconn,
+ struct files_struct *(*fn)(struct files_struct *fsp,
+ void *private_data),
+ void *private_data)
+{
+ struct files_struct *fsp, *next;
+
+ for (fsp = sconn->files; fsp; fsp = next) {
+ struct files_struct *ret;
+ next = fsp->next;
+ ret = fn(fsp, private_data);
+ if (ret != NULL) {
+ return ret;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Find a fsp given a file descriptor.
+****************************************************************************/
+
+files_struct *file_find_fd(struct smbd_server_connection *sconn, int fd)
+{
+ int count=0;
+ files_struct *fsp;
+
+ for (fsp=sconn->files; fsp; fsp=fsp->next,count++) {
+ if (fsp_get_pathref_fd(fsp) == fd) {
+ if (count > 10) {
+ DLIST_PROMOTE(sconn->files, fsp);
+ }
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Find a fsp given a device, inode and file_id.
+****************************************************************************/
+
+files_struct *file_find_dif(struct smbd_server_connection *sconn,
+ struct file_id id, unsigned long gen_id)
+{
+ int count=0;
+ files_struct *fsp;
+
+ if (gen_id == 0) {
+ return NULL;
+ }
+
+ for (fsp = sconn->files; fsp; fsp = fsp->next,count++) {
+ /*
+ * We can have a fsp->fh->fd == -1 here as it could be a stat
+ * open.
+ */
+ if (!file_id_equal(&fsp->file_id, &id)) {
+ continue;
+ }
+ if (!fsp->fsp_flags.is_fsa) {
+ continue;
+ }
+ if (fh_get_gen_id(fsp->fh) != gen_id) {
+ continue;
+ }
+ if (count > 10) {
+ DLIST_PROMOTE(sconn->files, fsp);
+ }
+ return fsp;
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Find the first fsp given a device and inode.
+ We use a singleton cache here to speed up searching from getfilepathinfo
+ calls.
+****************************************************************************/
+
+files_struct *file_find_di_first(struct smbd_server_connection *sconn,
+ struct file_id id,
+ bool need_fsa)
+{
+ files_struct *fsp;
+
+ if (file_id_equal(&sconn->fsp_fi_cache.id, &id)) {
+ /* Positive or negative cache hit. */
+ return sconn->fsp_fi_cache.fsp;
+ }
+
+ sconn->fsp_fi_cache.id = id;
+
+ for (fsp=sconn->files;fsp;fsp=fsp->next) {
+ if (need_fsa && !fsp->fsp_flags.is_fsa) {
+ continue;
+ }
+ if (file_id_equal(&fsp->file_id, &id)) {
+ /* Setup positive cache. */
+ sconn->fsp_fi_cache.fsp = fsp;
+ return fsp;
+ }
+ }
+
+ /* Setup negative cache. */
+ sconn->fsp_fi_cache.fsp = NULL;
+ return NULL;
+}
+
+/****************************************************************************
+ Find the next fsp having the same device and inode.
+****************************************************************************/
+
+files_struct *file_find_di_next(files_struct *start_fsp,
+ bool need_fsa)
+{
+ files_struct *fsp;
+
+ for (fsp = start_fsp->next;fsp;fsp=fsp->next) {
+ if (need_fsa && !fsp->fsp_flags.is_fsa) {
+ continue;
+ }
+ if (file_id_equal(&fsp->file_id, &start_fsp->file_id)) {
+ return fsp;
+ }
+ }
+
+ return NULL;
+}
+
+struct files_struct *file_find_one_fsp_from_lease_key(
+ struct smbd_server_connection *sconn,
+ const struct smb2_lease_key *lease_key)
+{
+ struct files_struct *fsp;
+
+ for (fsp = sconn->files; fsp; fsp=fsp->next) {
+ if ((fsp->lease != NULL) &&
+ (fsp->lease->lease.lease_key.data[0] ==
+ lease_key->data[0]) &&
+ (fsp->lease->lease.lease_key.data[1] ==
+ lease_key->data[1])) {
+ return fsp;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Find any fsp open with a pathname below that of an already open path.
+****************************************************************************/
+
+bool file_find_subpath(files_struct *dir_fsp)
+{
+ files_struct *fsp;
+ size_t dlen;
+ char *d_fullname = NULL;
+
+ d_fullname = talloc_asprintf(talloc_tos(), "%s/%s",
+ dir_fsp->conn->connectpath,
+ dir_fsp->fsp_name->base_name);
+
+ if (!d_fullname) {
+ return false;
+ }
+
+ dlen = strlen(d_fullname);
+
+ for (fsp=dir_fsp->conn->sconn->files; fsp; fsp=fsp->next) {
+ char *d1_fullname;
+
+ if (fsp == dir_fsp) {
+ continue;
+ }
+
+ d1_fullname = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name);
+
+ /*
+ * If the open file has a path that is a longer
+ * component, then it's a subpath.
+ */
+ if (strnequal(d_fullname, d1_fullname, dlen) &&
+ (d1_fullname[dlen] == '/')) {
+ TALLOC_FREE(d1_fullname);
+ TALLOC_FREE(d_fullname);
+ return true;
+ }
+ TALLOC_FREE(d1_fullname);
+ }
+
+ TALLOC_FREE(d_fullname);
+ return false;
+}
+
+/****************************************************************************
+ Free up a fsp.
+****************************************************************************/
+
+static void fsp_free(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+
+ if (fsp == sconn->fsp_fi_cache.fsp) {
+ ZERO_STRUCT(sconn->fsp_fi_cache);
+ }
+
+ DLIST_REMOVE(sconn->files, fsp);
+ SMB_ASSERT(sconn->num_files > 0);
+ sconn->num_files--;
+
+ TALLOC_FREE(fsp->fake_file_handle);
+
+ if (fh_get_refcount(fsp->fh) == 1) {
+ TALLOC_FREE(fsp->fh);
+ } else {
+ size_t new_refcount = fh_get_refcount(fsp->fh) - 1;
+ fh_set_refcount(fsp->fh, new_refcount);
+ }
+
+ if (fsp->lease != NULL) {
+ if (fsp->lease->ref_count == 1) {
+ TALLOC_FREE(fsp->lease);
+ } else {
+ fsp->lease->ref_count--;
+ }
+ }
+
+ fsp->conn->num_files_open--;
+
+ if (fsp->fsp_name != NULL &&
+ fsp->fsp_name->fsp_link != NULL)
+ {
+ /*
+ * Free fsp_link of fsp->fsp_name. To do this in the correct
+ * talloc destructor order we have to do it here. The
+ * talloc_free() of the link should set the fsp pointer to NULL.
+ */
+ TALLOC_FREE(fsp->fsp_name->fsp_link);
+ SMB_ASSERT(fsp->fsp_name->fsp == NULL);
+ }
+
+ /* this is paranoia, just in case someone tries to reuse the
+ information */
+ ZERO_STRUCTP(fsp);
+
+ /* fsp->fsp_name is a talloc child and is free'd automatically. */
+ TALLOC_FREE(fsp);
+}
+
+/*
+ * Rundown of all smb-related sub-structures of an fsp
+ */
+void fsp_unbind_smb(struct smb_request *req, files_struct *fsp)
+{
+ if (fsp == fsp->conn->cwd_fsp) {
+ return;
+ }
+
+ if (fsp->notify) {
+ size_t len = fsp_fullbasepath(fsp, NULL, 0);
+ char fullpath[len+1];
+
+ fsp_fullbasepath(fsp, fullpath, sizeof(fullpath));
+
+ /*
+ * Avoid /. at the end of the path name. notify can't
+ * deal with it.
+ */
+ if (len > 1 && fullpath[len-1] == '.' &&
+ fullpath[len-2] == '/') {
+ fullpath[len-2] = '\0';
+ }
+
+ notify_remove(fsp->conn->sconn->notify_ctx, fsp, fullpath);
+ TALLOC_FREE(fsp->notify);
+ }
+
+ /* Ensure this event will never fire. */
+ TALLOC_FREE(fsp->update_write_time_event);
+
+ if (fsp->op != NULL) {
+ fsp->op->compat = NULL;
+ }
+ TALLOC_FREE(fsp->op);
+
+ if ((req != NULL) && (fsp == req->chain_fsp)) {
+ req->chain_fsp = NULL;
+ }
+
+ /*
+ * Clear all possible chained fsp
+ * pointers in the SMB2 request queue.
+ */
+ remove_smb2_chained_fsp(fsp);
+}
+
+void file_free(struct smb_request *req, files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ uint64_t fnum = fsp->fnum;
+
+ fsp_unbind_smb(req, fsp);
+
+ /* Drop all remaining extensions. */
+ vfs_remove_all_fsp_extensions(fsp);
+
+ fsp_free(fsp);
+
+ DBG_INFO("freed files structure %"PRIu64" (%zu used)\n",
+ fnum,
+ sconn->num_files);
+}
+
+/****************************************************************************
+ Get an fsp from a packet given a 16 bit fnum.
+****************************************************************************/
+
+files_struct *file_fsp(struct smb_request *req, uint16_t fid)
+{
+ struct smbXsrv_open *op;
+ NTSTATUS status;
+ NTTIME now = 0;
+ files_struct *fsp;
+
+ if (req == NULL) {
+ /*
+ * We should never get here. req==NULL could in theory
+ * only happen from internal opens with a non-zero
+ * root_dir_fid. Internal opens just don't do that, at
+ * least they are not supposed to do so. And if they
+ * start to do so, they better fake up a smb_request
+ * from which we get the right smbd_server_conn. While
+ * this should never happen, let's return NULL here.
+ */
+ return NULL;
+ }
+
+ if (req->chain_fsp != NULL) {
+ if (req->chain_fsp->fsp_flags.closing) {
+ return NULL;
+ }
+ return req->chain_fsp;
+ }
+
+ if (req->xconn == NULL) {
+ return NULL;
+ }
+
+ now = timeval_to_nttime(&req->request_time);
+
+ status = smb1srv_open_lookup(req->xconn,
+ fid, now, &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ fsp = op->compat;
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ if (fsp->fsp_flags.closing) {
+ return NULL;
+ }
+
+ req->chain_fsp = fsp;
+ fsp->fsp_name->st.cached_dos_attributes = FILE_ATTRIBUTE_INVALID;
+ return fsp;
+}
+
+struct files_struct *file_fsp_get(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id)
+{
+ struct smbXsrv_open *op;
+ NTSTATUS status;
+ NTTIME now = 0;
+ struct files_struct *fsp;
+
+ now = timeval_to_nttime(&smb2req->request_time);
+
+ status = smb2srv_open_lookup(smb2req->xconn,
+ persistent_id, volatile_id,
+ now, &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ fsp = op->compat;
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ if (smb2req->tcon == NULL) {
+ return NULL;
+ }
+
+ if (smb2req->tcon->compat != fsp->conn) {
+ return NULL;
+ }
+
+ if (smb2req->session == NULL) {
+ return NULL;
+ }
+
+ if (smb2req->session->global->session_wire_id != fsp->vuid) {
+ return NULL;
+ }
+
+ if (fsp->fsp_flags.closing) {
+ return NULL;
+ }
+
+ fsp->fsp_name->st.cached_dos_attributes = FILE_ATTRIBUTE_INVALID;
+
+ return fsp;
+}
+
+struct files_struct *file_fsp_smb2(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id)
+{
+ struct files_struct *fsp;
+
+ if (smb2req->compat_chain_fsp != NULL) {
+ if (smb2req->compat_chain_fsp->fsp_flags.closing) {
+ return NULL;
+ }
+ smb2req->compat_chain_fsp->fsp_name->st.cached_dos_attributes =
+ FILE_ATTRIBUTE_INVALID;
+ return smb2req->compat_chain_fsp;
+ }
+
+ fsp = file_fsp_get(smb2req, persistent_id, volatile_id);
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ smb2req->compat_chain_fsp = fsp;
+ return fsp;
+}
+
+/****************************************************************************
+ Duplicate the file handle part for a DOS or FCB open.
+****************************************************************************/
+
+NTSTATUS dup_file_fsp(
+ files_struct *from,
+ uint32_t access_mask,
+ files_struct *to)
+{
+ size_t new_refcount;
+
+ /* this can never happen for print files */
+ SMB_ASSERT(from->print_file == NULL);
+
+ TALLOC_FREE(to->fh);
+
+ to->fh = from->fh;
+ new_refcount = fh_get_refcount(to->fh) + 1;
+ fh_set_refcount(to->fh, new_refcount);
+
+ to->file_id = from->file_id;
+ to->initial_allocation_size = from->initial_allocation_size;
+ to->file_pid = from->file_pid;
+ to->vuid = from->vuid;
+ to->open_time = from->open_time;
+ to->access_mask = access_mask;
+ to->oplock_type = from->oplock_type;
+ to->fsp_flags.can_lock = from->fsp_flags.can_lock;
+ to->fsp_flags.can_read = ((access_mask & FILE_READ_DATA) != 0);
+ to->fsp_flags.can_write =
+ CAN_WRITE(from->conn) &&
+ ((access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
+ if (from->fsp_name->twrp != 0) {
+ to->fsp_flags.can_write = false;
+ }
+ to->fsp_flags.modified = from->fsp_flags.modified;
+ to->fsp_flags.is_directory = from->fsp_flags.is_directory;
+ to->fsp_flags.aio_write_behind = from->fsp_flags.aio_write_behind;
+ to->fsp_flags.is_fsa = from->fsp_flags.is_fsa;
+ to->fsp_flags.is_pathref = from->fsp_flags.is_pathref;
+ to->fsp_flags.have_proc_fds = from->fsp_flags.have_proc_fds;
+ to->fsp_flags.is_dirfsp = from->fsp_flags.is_dirfsp;
+
+ return fsp_set_smb_fname(to, from->fsp_name);
+}
+
+/**
+ * Return a jenkins hash of a pathname on a connection.
+ */
+
+NTSTATUS file_name_hash(connection_struct *conn,
+ const char *name, uint32_t *p_name_hash)
+{
+ char tmpbuf[PATH_MAX];
+ char *fullpath, *to_free;
+ ssize_t len;
+ TDB_DATA key;
+
+ /* Set the hash of the full pathname. */
+
+ if (name[0] == '/') {
+ strlcpy(tmpbuf, name, sizeof(tmpbuf));
+ fullpath = tmpbuf;
+ len = strlen(fullpath);
+ to_free = NULL;
+ } else {
+ len = full_path_tos(conn->connectpath,
+ name,
+ tmpbuf,
+ sizeof(tmpbuf),
+ &fullpath,
+ &to_free);
+ }
+ if (len == -1) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ key = (TDB_DATA) { .dptr = (uint8_t *)fullpath, .dsize = len+1 };
+ *p_name_hash = tdb_jenkins_hash(&key);
+
+ DEBUG(10,("file_name_hash: %s hash 0x%x\n",
+ fullpath,
+ (unsigned int)*p_name_hash ));
+
+ TALLOC_FREE(to_free);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsp_attach_smb_fname(struct files_struct *fsp,
+ struct smb_filename **_smb_fname)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smb_filename *smb_fname_new = talloc_move(fsp, _smb_fname);
+ const char *name_str = NULL;
+ uint32_t name_hash = 0;
+ NTSTATUS status;
+
+ name_str = smb_fname_str_dbg(smb_fname_new);
+ if (name_str == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = file_name_hash(fsp->conn,
+ name_str,
+ &name_hash);
+ TALLOC_FREE(frame);
+ name_str = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = fsp_smb_fname_link(fsp,
+ &smb_fname_new->fsp_link,
+ &smb_fname_new->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->name_hash = name_hash;
+ fsp->fsp_name = smb_fname_new;
+ fsp->fsp_name->st.cached_dos_attributes = FILE_ATTRIBUTE_INVALID;
+ *_smb_fname = NULL;
+ return NT_STATUS_OK;
+}
+
+/**
+ * The only way that the fsp->fsp_name field should ever be set.
+ */
+NTSTATUS fsp_set_smb_fname(struct files_struct *fsp,
+ const struct smb_filename *smb_fname_in)
+{
+ struct smb_filename *smb_fname_old = fsp->fsp_name;
+ struct smb_filename *smb_fname_new = NULL;
+ NTSTATUS status;
+
+ smb_fname_new = cp_smb_filename(fsp, smb_fname_in);
+ if (smb_fname_new == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = fsp_attach_smb_fname(fsp, &smb_fname_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname_new);
+ return status;
+ }
+
+ if (smb_fname_old != NULL) {
+ smb_fname_fsp_unlink(smb_fname_old);
+ TALLOC_FREE(smb_fname_old);
+ }
+
+ return NT_STATUS_OK;
+}
+
+size_t fsp_fullbasepath(struct files_struct *fsp, char *buf, size_t buflen)
+{
+ int len = 0;
+
+ if ((buf == NULL) || (buflen == 0)) {
+ return strlen(fsp->conn->connectpath) + 1 +
+ strlen(fsp->fsp_name->base_name);
+ }
+
+ len = snprintf(buf, buflen, "%s/%s", fsp->conn->connectpath,
+ fsp->fsp_name->base_name);
+ SMB_ASSERT(len>0);
+
+ return len;
+}
+
+void fsp_set_base_fsp(struct files_struct *fsp, struct files_struct *base_fsp)
+{
+ SMB_ASSERT(fsp->stream_fsp == NULL);
+ if (base_fsp != NULL) {
+ SMB_ASSERT(base_fsp->base_fsp == NULL);
+ SMB_ASSERT(base_fsp->stream_fsp == NULL);
+ }
+
+ if (fsp->base_fsp != NULL) {
+ SMB_ASSERT(fsp->base_fsp->stream_fsp == fsp);
+ fsp->base_fsp->stream_fsp = NULL;
+ }
+
+ fsp->base_fsp = base_fsp;
+ if (fsp->base_fsp != NULL) {
+ fsp->base_fsp->stream_fsp = fsp;
+ }
+}
+
+bool fsp_is_alternate_stream(const struct files_struct *fsp)
+{
+ return (fsp->base_fsp != NULL);
+}
+
+struct files_struct *metadata_fsp(struct files_struct *fsp)
+{
+ if (fsp_is_alternate_stream(fsp)) {
+ return fsp->base_fsp;
+ }
+ return fsp;
+}
+
+static bool fsp_generic_ask_sharemode(struct files_struct *fsp)
+{
+ if (fsp == NULL) {
+ return false;
+ }
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) {
+ /* Always use filesystem for UNIX mtime query. */
+ return false;
+ }
+
+ return true;
+}
+
+bool fsp_search_ask_sharemode(struct files_struct *fsp)
+{
+ if (!fsp_generic_ask_sharemode(fsp)) {
+ return false;
+ }
+
+ return lp_smbd_search_ask_sharemode(SNUM(fsp->conn));
+}
+
+bool fsp_getinfo_ask_sharemode(struct files_struct *fsp)
+{
+ if (!fsp_generic_ask_sharemode(fsp)) {
+ return false;
+ }
+
+ return lp_smbd_getinfo_ask_sharemode(SNUM(fsp->conn));
+}
diff --git a/source3/smbd/globals.c b/source3/smbd/globals.c
new file mode 100644
index 0000000..9989a73
--- /dev/null
+++ b/source3/smbd/globals.c
@@ -0,0 +1,123 @@
+/*
+ Unix SMB/Netbios implementation.
+ smbd globals
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/memcache.h"
+#include "messages.h"
+#include <tdb.h>
+
+#ifdef USE_DMAPI
+struct smbd_dmapi_context *dmapi_ctx = NULL;
+#endif
+
+const struct mangle_fns *mangle_fns = NULL;
+
+unsigned char *chartest = NULL;
+TDB_CONTEXT *tdb_mangled_cache = NULL;
+
+/*
+ this determines how many characters are used from the original filename
+ in the 8.3 mangled name. A larger value leads to a weaker hash and more collisions.
+ The largest possible value is 6.
+*/
+unsigned mangle_prefix = 0;
+
+bool logged_ioctl_message = false;
+
+time_t last_smb_conf_reload_time = 0;
+pid_t background_lpq_updater_pid = -1;
+
+/****************************************************************************
+ structure to hold a linked list of queued messages.
+ for processing.
+****************************************************************************/
+uint32_t common_flags2 = FLAGS2_LONG_PATH_COMPONENTS|FLAGS2_32_BIT_ERROR_CODES|FLAGS2_EXTENDED_ATTRIBUTES;
+
+struct smb_trans_enc_state *partial_srv_trans_enc_ctx = NULL;
+struct smb_trans_enc_state *srv_trans_enc_ctx = NULL;
+
+/* A stack of security contexts. We include the current context as being
+ the first one, so there is room for another MAX_SEC_CTX_DEPTH more. */
+struct sec_ctx sec_ctx_stack[MAX_SEC_CTX_DEPTH + 1];
+int sec_ctx_stack_ndx = 0;
+bool become_uid_done = false;
+bool become_gid_done = false;
+
+uint32_t global_client_caps = 0;
+
+uint16_t fnf_handle = 257;
+
+/* A stack of current_user connection contexts. */
+struct conn_ctx conn_ctx_stack[MAX_SEC_CTX_DEPTH];
+int conn_ctx_stack_ndx = 0;
+
+struct vfs_init_function_entry *backends = NULL;
+char *sparse_buf = NULL;
+char *LastDir = NULL;
+
+struct smbd_parent_context *am_parent = NULL;
+struct memcache *smbd_memcache_ctx = NULL;
+bool exit_firsttime = true;
+
+struct smbXsrv_client *global_smbXsrv_client = NULL;
+
+struct memcache *smbd_memcache(void)
+{
+ if (!smbd_memcache_ctx) {
+ /*
+ * Note we MUST use the NULL context here, not the
+ * autofree context, to avoid side effects in forked
+ * children exiting.
+ */
+ smbd_memcache_ctx = memcache_init(NULL,
+ lp_max_stat_cache_size()*1024);
+ }
+ if (!smbd_memcache_ctx) {
+ smb_panic("Could not init smbd memcache");
+ }
+
+ return smbd_memcache_ctx;
+}
+
+void smbd_init_globals(void)
+{
+ ZERO_STRUCT(conn_ctx_stack);
+
+ ZERO_STRUCT(sec_ctx_stack);
+}
+
+struct GUID smbd_request_guid(struct smb_request *smb1req, uint16_t idx)
+{
+ struct GUID v = {
+ .time_low = (uint32_t)smb1req->mid,
+ .time_hi_and_version = idx,
+ };
+
+ if (smb1req->smb2req != NULL) {
+ v.time_mid = (uint16_t)smb1req->smb2req->current_idx;
+ } else {
+ v.time_mid = (uint16_t)(uintptr_t)smb1req->vwv;
+ }
+
+ SBVAL((uint8_t *)&v, 8, smb1req->xconn->channel_id);
+
+ return v;
+}
diff --git a/source3/smbd/globals.h b/source3/smbd/globals.h
new file mode 100644
index 0000000..4928d1f
--- /dev/null
+++ b/source3/smbd/globals.h
@@ -0,0 +1,912 @@
+/*
+ Unix SMB/Netbios implementation.
+ smbd globals
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SOURCE3_SMBD_GLOBALS_H_
+#define _SOURCE3_SMBD_GLOBALS_H_
+
+#include "system/select.h"
+#include "librpc/gen_ndr/smbXsrv.h"
+#include "smbprofile.h"
+
+#ifdef USE_DMAPI
+struct smbd_dmapi_context;
+extern struct smbd_dmapi_context *dmapi_ctx;
+#endif
+
+/* A singleton cache to speed up searching by dev/inode. */
+struct fsp_singleton_cache {
+ files_struct *fsp;
+ struct file_id id;
+};
+
+extern const struct mangle_fns *mangle_fns;
+
+extern unsigned char *chartest;
+struct tdb_context;
+extern struct tdb_context *tdb_mangled_cache;
+
+/*
+ this determines how many characters are used from the original filename
+ in the 8.3 mangled name. A larger value leads to a weaker hash and more collisions.
+ The largest possible value is 6.
+*/
+extern unsigned mangle_prefix;
+
+struct msg_state;
+
+extern bool logged_ioctl_message;
+
+extern int trans_num;
+
+extern time_t last_smb_conf_reload_time;
+extern time_t last_printer_reload_time;
+extern pid_t background_lpq_updater_pid;
+
+/****************************************************************************
+ structure to hold a linked list of queued messages.
+ for processing.
+****************************************************************************/
+extern uint32_t common_flags2;
+
+extern struct smb_trans_enc_state *partial_srv_trans_enc_ctx;
+extern struct smb_trans_enc_state *srv_trans_enc_ctx;
+
+struct sec_ctx {
+ struct security_unix_token ut;
+ struct security_token *token;
+};
+/* A stack of security contexts. We include the current context as being
+ the first one, so there is room for another MAX_SEC_CTX_DEPTH more. */
+extern struct sec_ctx sec_ctx_stack[MAX_SEC_CTX_DEPTH + 1];
+extern int sec_ctx_stack_ndx;
+extern bool become_uid_done;
+extern bool become_gid_done;
+
+extern uint32_t global_client_caps;
+
+extern uint16_t fnf_handle;
+
+struct conn_ctx {
+ connection_struct *conn;
+ uint64_t vuid;
+ userdom_struct user_info;
+};
+/* A stack of current_user connection contexts. */
+extern struct conn_ctx conn_ctx_stack[MAX_SEC_CTX_DEPTH];
+extern int conn_ctx_stack_ndx;
+
+struct vfs_init_function_entry;
+extern struct vfs_init_function_entry *backends;
+extern char *sparse_buf;
+extern char *LastDir;
+
+struct smbd_parent_context;
+extern struct smbd_parent_context *am_parent;
+extern struct memcache *smbd_memcache_ctx;
+extern bool exit_firsttime;
+
+struct tstream_context;
+struct smbd_smb2_request;
+
+DATA_BLOB negprot_spnego(TALLOC_CTX *ctx, struct smbXsrv_connection *xconn);
+
+void smbd_lock_socket(struct smbXsrv_connection *xconn);
+void smbd_unlock_socket(struct smbXsrv_connection *xconn);
+
+struct GUID smbd_request_guid(struct smb_request *smb1req, uint16_t idx);
+
+NTSTATUS smbd_do_unlocking(struct smb_request *req,
+ files_struct *fsp,
+ uint16_t num_ulocks,
+ struct smbd_lock_element *ulocks);
+
+NTSTATUS smbd_do_qfilepathinfo(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request *req,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ bool delete_pending,
+ struct timespec write_time_ts,
+ struct ea_list *ea_list,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ char **ppdata,
+ unsigned int *pdata_size);
+
+NTSTATUS smbd_do_setfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ const DATA_BLOB *pdata);
+
+NTSTATUS smbd_do_setfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ char **ppdata, int total_data,
+ int *ret_data_size);
+
+NTSTATUS smbd_do_qfsinfo(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ struct files_struct *fsp,
+ struct smb_filename *smb_fname,
+ char **ppdata,
+ int *ret_data_len);
+
+NTSTATUS smbd_dirptr_lanman2_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ uint16_t flags2,
+ const char *path_mask,
+ uint32_t dirtype,
+ int info_level,
+ int requires_resume_key,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode,
+ uint8_t align,
+ bool do_pad,
+ char **ppdata,
+ char *base_data,
+ char *end_data,
+ int space_remaining,
+ struct smb_filename **smb_fname,
+ int *_last_entry_off,
+ struct ea_list *name_list,
+ struct file_id *file_id);
+
+NTSTATUS smbd_calculate_access_mask_fsp(struct files_struct *dirsfp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t *access_mask_out);
+
+void smbd_notify_cancel_by_smbreq(const struct smb_request *smbreq);
+
+void smbXsrv_connection_disconnect_transport(struct smbXsrv_connection *xconn,
+ NTSTATUS status);
+size_t smbXsrv_client_valid_connections(struct smbXsrv_client *client);
+void smbd_server_connection_terminate_ex(struct smbXsrv_connection *xconn,
+ const char *reason,
+ const char *location);
+#define smbd_server_connection_terminate(xconn, reason) \
+ smbd_server_connection_terminate_ex(xconn, reason, __location__)
+
+void smbd_server_disconnect_client_ex(struct smbXsrv_client *client,
+ const char *reason,
+ const char *location);
+#define smbd_server_disconnect_client(__client, __reason) \
+ smbd_server_disconnect_client_ex(__client, __reason, __location__)
+
+const char *smb2_opcode_name(uint16_t opcode);
+bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size);
+bool smbd_smb2_is_compound(const struct smbd_smb2_request *req);
+bool smbd_smb2_is_last_in_compound(const struct smbd_smb2_request *req);
+
+NTSTATUS smbd_add_connection(struct smbXsrv_client *client, int sock_fd,
+ NTTIME now, struct smbXsrv_connection **_xconn);
+
+NTSTATUS reply_smb2002(struct smb_request *req, uint16_t choice);
+NTSTATUS reply_smb20ff(struct smb_request *req, uint16_t choice);
+NTSTATUS smbd_smb2_process_negprot(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low,
+ const uint8_t *inpdu, size_t size);
+NTSTATUS smb2_multi_protocol_reply_negprot(struct smb_request *req);
+
+DATA_BLOB smbd_smb2_generate_outbody(struct smbd_smb2_request *req, size_t size);
+
+bool smbXsrv_server_multi_channel_enabled(void);
+
+NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ uint8_t error_context_count,
+ DATA_BLOB *info,
+ const char *location);
+#define smbd_smb2_request_error(req, status) \
+ smbd_smb2_request_error_ex(req, status, 0, NULL, __location__)
+NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ DATA_BLOB body, DATA_BLOB *dyn,
+ const char *location);
+#define smbd_smb2_request_done(req, body, dyn) \
+ smbd_smb2_request_done_ex(req, NT_STATUS_OK, body, dyn, __location__)
+
+NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_client *client,
+ struct smbXsrv_open *op,
+ uint8_t oplock_level);
+NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_client *client,
+ uint16_t new_epoch,
+ uint32_t lease_flags,
+ struct smb2_lease_key *lease_key,
+ uint32_t current_lease_state,
+ uint32_t new_lease_state);
+
+NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
+ struct tevent_req *subreq,
+ uint32_t defer_time);
+
+struct smb_request *smbd_smb2_fake_smb_request(struct smbd_smb2_request *req,
+ struct files_struct *fsp);
+size_t smbd_smb2_unread_bytes(struct smbd_smb2_request *req);
+void remove_smb2_chained_fsp(files_struct *fsp);
+
+NTSTATUS smbd_smb2_request_verify_creditcharge(struct smbd_smb2_request *req,
+ uint32_t data_length);
+
+NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req,
+ size_t expected_body_size);
+
+void smb2_request_set_async_internal(struct smbd_smb2_request *req,
+ bool async_internal);
+
+enum protocol_types smbd_smb2_protocol_dialect_match(const uint8_t *indyn,
+ const int dialect_count,
+ uint16_t *dialect);
+NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_sesssetup(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_logoff(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_tcon(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_tdis(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_create(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_close(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_flush(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_read(struct smbd_smb2_request *req);
+NTSTATUS smb2_read_complete(struct tevent_req *req, ssize_t nread, int err);
+NTSTATUS smbd_smb2_request_process_write(struct smbd_smb2_request *req);
+NTSTATUS smb2_write_complete(struct tevent_req *req, ssize_t nwritten, int err);
+NTSTATUS smb2_write_complete_nosync(struct tevent_req *req, ssize_t nwritten,
+ int err);
+NTSTATUS smbd_smb2_request_process_lock(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_ioctl(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_keepalive(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_query_directory(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_notify(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_getinfo(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_setinfo(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req);
+NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req);
+void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data);
+
+struct deferred_open_record;
+
+/* SMB1 -> SMB2 glue. */
+void send_break_message_smb2(files_struct *fsp,
+ uint32_t break_from,
+ uint32_t break_to);
+/* From smbd/smb2_create.c */
+int map_smb2_oplock_levels_to_samba(uint8_t in_oplock_level);
+bool get_deferred_open_message_state_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec);
+bool open_was_deferred_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid);
+void remove_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid);
+bool schedule_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid);
+bool push_deferred_open_message_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval request_time,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec);
+
+struct smbXsrv_client;
+
+struct smbXsrv_preauth {
+ uint8_t sha512_value[64];
+};
+
+struct smbXsrv_connection {
+ struct smbXsrv_connection *prev, *next;
+
+ struct smbXsrv_client *client;
+
+ NTTIME connect_time;
+ uint64_t channel_id;
+ const struct tsocket_address *local_address;
+ const struct tsocket_address *remote_address;
+ const char *remote_hostname;
+ bool has_cluster_movable_ip;
+
+ enum protocol_types protocol;
+
+ struct {
+ NTSTATUS status;
+ bool terminating;
+ struct tevent_queue *shutdown_wait_queue;
+ int sock;
+ struct tevent_fd *fde;
+
+ struct {
+ bool got_session;
+ } nbt;
+ } transport;
+
+ struct {
+ bool force_unacked_timeout;
+ uint64_t unacked_bytes;
+ uint32_t rto_usecs;
+ struct tevent_req *checker_subreq;
+ struct smbd_smb2_send_queue *queue;
+ } ack;
+
+#if defined(WITH_SMB1SERVER)
+ struct {
+ struct {
+ /*
+ * fd for the fcntl lock and process shared
+ * robust mutex to coordinate access to the
+ * client socket. When the system supports
+ * process shared robust mutexes, those are
+ * used. If not, then the fcntl lock will be
+ * used.
+ */
+ int socket_lock_fd;
+#ifdef HAVE_ROBUST_MUTEXES
+ pthread_mutex_t *socket_mutex;
+#endif
+
+ /*
+ * fd for the trusted pipe from
+ * echo handler child
+ */
+ int trusted_fd;
+
+ /*
+ * fde for the trusted_fd
+ */
+ struct tevent_fd *trusted_fde;
+
+ /*
+ * Reference count for the fcntl lock to
+ * allow recursive locks.
+ */
+ int ref_count;
+ } echo_handler;
+
+ struct {
+ bool encrypted_passwords;
+ bool spnego;
+ struct auth4_context *auth_context;
+ bool done;
+ /*
+ * Size of the data we can receive. Set by us.
+ * Can be modified by the max xmit parameter.
+ */
+ int max_recv;
+ } negprot;
+
+ struct {
+ bool done_sesssetup;
+ /*
+ * Size of data we can send to client. Set
+ * by the client for all protocols above CORE.
+ * Set by us for CORE protocol.
+ */
+ int max_send;
+ } sessions;
+ struct smb1_signing_state *signing_state;
+
+ struct {
+ uint16_t client_major;
+ uint16_t client_minor;
+ uint32_t client_cap_low;
+ uint32_t client_cap_high;
+ } unix_info;
+
+ struct msg_state *msg_state;
+ } smb1;
+#endif
+ struct {
+ struct smbd_smb2_request_read_state {
+ struct smbd_smb2_request *req;
+ struct {
+ uint8_t nbt[NBT_HDR_SIZE];
+ } hdr;
+ struct iovec _vector[1];
+ struct iovec *vector;
+ int count;
+ struct msghdr msg;
+ bool doing_receivefile;
+ size_t min_recv_size;
+ size_t pktfull;
+ size_t pktlen;
+ uint8_t *pktbuf;
+ } request_read_state;
+ struct smbd_smb2_send_queue *send_queue;
+ size_t send_queue_len;
+
+ struct {
+ /*
+ * seq_low is the lowest sequence number
+ * we will accept.
+ */
+ uint64_t seq_low;
+ /*
+ * seq_range is the range of credits we have
+ * granted from the sequence windows starting
+ * at seq_low.
+ *
+ * This gets incremented when new credits are
+ * granted and gets decremented when the
+ * lowest sequence number is consumed
+ * (when seq_low gets incremented).
+ */
+ uint16_t seq_range;
+ /*
+ * The number of credits we have currently granted
+ * to the client.
+ *
+ * This gets incremented when new credits are
+ * granted and gets decremented when any credit
+ * is consumed.
+ *
+ * Note: the decrementing is different compared
+ * to seq_range.
+ */
+ uint16_t granted;
+ /*
+ * The maximum number of credits we will ever
+ * grant to the client.
+ *
+ * Typically we will only grant 1/16th of
+ * max_credits.
+ *
+ * This is the "server max credits" parameter.
+ */
+ uint16_t max;
+ /*
+ * a bitmap of size max_credits
+ */
+ struct bitmap *bitmap;
+ bool multicredit;
+ } credits;
+
+ bool allow_2ff;
+ struct {
+ uint32_t capabilities;
+ struct GUID guid;
+ bool guid_verified;
+ uint16_t security_mode;
+ uint16_t num_dialects;
+ uint16_t *dialects;
+ } client;
+ struct {
+ uint32_t capabilities;
+ struct GUID guid;
+ uint16_t security_mode;
+ uint16_t dialect;
+ uint32_t max_trans;
+ uint32_t max_read;
+ uint32_t max_write;
+ uint16_t sign_algo;
+ uint16_t cipher;
+ bool posix_extensions_negotiated;
+ } server;
+
+ struct smbXsrv_preauth preauth;
+
+ struct smbd_smb2_request *requests;
+
+ struct {
+ uint8_t read_body_padding;
+ } smbtorture;
+
+ bool signing_mandatory;
+ } smb2;
+};
+
+const char *smbXsrv_connection_dbg(const struct smbXsrv_connection *xconn);
+
+NTSTATUS smbXsrv_version_global_init(const struct server_id *server_id);
+uint32_t smbXsrv_version_global_current(void);
+
+struct smbXsrv_client_table;
+NTSTATUS smbXsrv_client_global_init(void);
+NTSTATUS smbXsrv_client_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ NTTIME now,
+ struct smbXsrv_client **_client);
+NTSTATUS smbXsrv_client_remove(struct smbXsrv_client *client);
+struct tevent_req *smb2srv_client_mc_negprot_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req);
+NTSTATUS smb2srv_client_mc_negprot_recv(struct tevent_req *req);
+
+NTSTATUS smbXsrv_connection_init_tables(struct smbXsrv_connection *conn,
+ enum protocol_types protocol);
+
+NTSTATUS smbXsrv_session_global_init(struct messaging_context *msg_ctx);
+NTSTATUS smbXsrv_session_create(struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session **_session);
+NTSTATUS smbXsrv_session_add_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_channel_global0 **_c);
+NTSTATUS smbXsrv_session_remove_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *xconn);
+NTSTATUS smbXsrv_session_disconnect_xconn(struct smbXsrv_connection *xconn);
+NTSTATUS smbXsrv_session_update(struct smbXsrv_session *session);
+struct smbXsrv_channel_global0;
+NTSTATUS smbXsrv_session_find_channel(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ struct smbXsrv_channel_global0 **_c);
+NTSTATUS smbXsrv_session_find_auth(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session_auth0 **_a);
+NTSTATUS smbXsrv_session_create_auth(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ struct smbXsrv_session_auth0 **_a);
+struct tevent_req *smb2srv_session_shutdown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_session *session,
+ struct smbd_smb2_request *current_req);
+NTSTATUS smb2srv_session_shutdown_recv(struct tevent_req *req);
+NTSTATUS smbXsrv_session_logoff(struct smbXsrv_session *session);
+NTSTATUS smbXsrv_session_logoff_all(struct smbXsrv_client *client);
+NTSTATUS smb1srv_session_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb1srv_session_lookup(struct smbXsrv_connection *conn,
+ uint16_t vuid, NTTIME now,
+ struct smbXsrv_session **session);
+NTSTATUS smbXsrv_session_info_lookup(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct auth_session_info **si);
+NTSTATUS smb2srv_session_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb2srv_session_lookup_conn(struct smbXsrv_connection *conn,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session);
+NTSTATUS smb2srv_session_lookup_client(struct smbXsrv_client *client,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session);
+NTSTATUS smb2srv_session_lookup_global(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session **session);
+NTSTATUS get_valid_smbXsrv_session(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct smbXsrv_session **session);
+NTSTATUS smbXsrv_session_local_traverse(
+ struct smbXsrv_client *client,
+ int (*caller_cb)(struct smbXsrv_session *session,
+ void *caller_data),
+ void *caller_data);
+struct smbXsrv_session_global0;
+NTSTATUS smbXsrv_session_global_traverse(
+ int (*fn)(struct smbXsrv_session_global0 *, void *),
+ void *private_data);
+struct tevent_req *smb2srv_session_close_previous_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t previous_session_id,
+ uint64_t current_session_id);
+NTSTATUS smb2srv_session_close_previous_recv(struct tevent_req *req);
+
+NTSTATUS smbXsrv_tcon_global_init(void);
+NTSTATUS smbXsrv_tcon_update(struct smbXsrv_tcon *tcon);
+NTSTATUS smbXsrv_tcon_disconnect(struct smbXsrv_tcon *tcon, uint64_t vuid);
+NTSTATUS smb1srv_tcon_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb1srv_tcon_create(struct smbXsrv_connection *conn,
+ uint32_t session_global_id,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon);
+NTSTATUS smb1srv_tcon_lookup(struct smbXsrv_connection *conn,
+ uint16_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon);
+NTSTATUS smb1srv_tcon_disconnect_all(struct smbXsrv_client *client);
+NTSTATUS smb2srv_tcon_table_init(struct smbXsrv_session *session);
+NTSTATUS smb2srv_tcon_create(struct smbXsrv_session *session,
+ uint32_t session_global_id,
+ uint8_t encryption_flags,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon);
+NTSTATUS smb2srv_tcon_lookup(struct smbXsrv_session *session,
+ uint32_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon);
+NTSTATUS smb2srv_tcon_disconnect_all(struct smbXsrv_session *session);
+struct smbXsrv_tcon_global0;
+NTSTATUS smbXsrv_tcon_global_traverse(
+ int (*fn)(struct smbXsrv_tcon_global0 *, void *),
+ void *private_data);
+
+
+bool smbXsrv_is_encrypted(uint8_t encryption_flags);
+bool smbXsrv_is_partially_encrypted(uint8_t encryption_flags);
+bool smbXsrv_set_crypto_flag(uint8_t *flags, uint8_t flag);
+bool smbXsrv_is_signed(uint8_t signing_flags);
+bool smbXsrv_is_partially_signed(uint8_t signing_flags);
+
+struct smbd_smb2_send_queue {
+ struct smbd_smb2_send_queue *prev, *next;
+
+ DATA_BLOB *sendfile_header;
+ uint32_t sendfile_body_size;
+ NTSTATUS *sendfile_status;
+
+ struct msghdr msg;
+ struct iovec *vector;
+ int count;
+
+ struct {
+ struct tevent_req *req;
+ struct timeval timeout;
+ uint64_t required_acked_bytes;
+ } ack;
+
+ TALLOC_CTX *mem_ctx;
+};
+
+struct smbd_smb2_request {
+ struct smbd_smb2_request *prev, *next;
+
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+
+ struct smbd_smb2_send_queue queue_entry;
+
+ /* the session the request operates on, maybe NULL */
+ struct smbXsrv_session *session;
+ uint64_t last_session_id;
+
+ /* the tcon the request operates on, maybe NULL */
+ struct smbXsrv_tcon *tcon;
+ uint32_t last_tid;
+
+ int current_idx;
+ bool do_signing;
+ /* Was the request encrypted? */
+ bool was_encrypted;
+ /* Should we encrypt? */
+ bool do_encryption;
+ struct tevent_timer *async_te;
+ bool compound_related;
+ NTSTATUS compound_create_err;
+
+ /*
+ * Give the implementation of an SMB2 req a way to tell the SMB2 request
+ * processing engine that the internal request is going async, while
+ * preserving synchronous SMB2 behaviour.
+ */
+ bool async_internal;
+
+ /*
+ * the encryption key for the whole
+ * compound chain
+ */
+ struct smb2_signing_key *first_enc_key;
+ /*
+ * the signing key for the last
+ * request/response of a compound chain
+ */
+ struct smb2_signing_key *last_sign_key;
+ struct smbXsrv_preauth *preauth;
+
+ struct timeval request_time;
+
+ SMBPROFILE_IOBYTES_ASYNC_STATE(profile);
+
+ /* fake smb1 request. */
+ struct smb_request *smb1req;
+ struct files_struct *compat_chain_fsp;
+
+ /*
+ * Keep track of whether the outstanding request counters
+ * had been updated in dispatch, so that they need to be
+ * adapted again in reply.
+ */
+ bool request_counters_updated;
+ uint64_t channel_generation;
+
+ /*
+ * The sub request for async backend calls.
+ * This is used for SMB2 Cancel.
+ */
+ struct tevent_req *subreq;
+
+#define SMBD_SMB2_TF_IOV_OFS 0
+#define SMBD_SMB2_HDR_IOV_OFS 1
+#define SMBD_SMB2_BODY_IOV_OFS 2
+#define SMBD_SMB2_DYN_IOV_OFS 3
+
+#define SMBD_SMB2_NUM_IOV_PER_REQ 4
+
+#define SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,ofs) \
+ (&req->dir.vector[(idx)+(ofs)])
+
+#define SMBD_SMB2_IDX_TF_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_TF_IOV_OFS)
+#define SMBD_SMB2_IDX_HDR_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_HDR_IOV_OFS)
+#define SMBD_SMB2_IDX_BODY_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_BODY_IOV_OFS)
+#define SMBD_SMB2_IDX_DYN_IOV(req,dir,idx) \
+ SMBD_SMB2_IOV_IDX_OFS(req,dir,idx,SMBD_SMB2_DYN_IOV_OFS)
+
+#define SMBD_SMB2_IN_TF_IOV(req) SMBD_SMB2_IDX_TF_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_TF_PTR(req) (uint8_t *)(SMBD_SMB2_IN_TF_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_HDR_IOV(req) SMBD_SMB2_IDX_HDR_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_HDR_PTR(req) (uint8_t *)(SMBD_SMB2_IN_HDR_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_BODY_IOV(req) SMBD_SMB2_IDX_BODY_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_BODY_PTR(req) (uint8_t *)(SMBD_SMB2_IN_BODY_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_BODY_LEN(req) (SMBD_SMB2_IN_BODY_IOV(req)->iov_len)
+#define SMBD_SMB2_IN_DYN_IOV(req) SMBD_SMB2_IDX_DYN_IOV(req,in,req->current_idx)
+#define SMBD_SMB2_IN_DYN_PTR(req) (uint8_t *)(SMBD_SMB2_IN_DYN_IOV(req)->iov_base)
+#define SMBD_SMB2_IN_DYN_LEN(req) (SMBD_SMB2_IN_DYN_IOV(req)->iov_len)
+
+#define SMBD_SMB2_OUT_TF_IOV(req) SMBD_SMB2_IDX_TF_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_TF_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_TF_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_HDR_IOV(req) SMBD_SMB2_IDX_HDR_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_HDR_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_HDR_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_BODY_IOV(req) SMBD_SMB2_IDX_BODY_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_BODY_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_BODY_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_BODY_LEN(req) (SMBD_SMB2_OUT_BODY_IOV(req)->iov_len)
+#define SMBD_SMB2_OUT_DYN_IOV(req) SMBD_SMB2_IDX_DYN_IOV(req,out,req->current_idx)
+#define SMBD_SMB2_OUT_DYN_PTR(req) (uint8_t *)(SMBD_SMB2_OUT_DYN_IOV(req)->iov_base)
+#define SMBD_SMB2_OUT_DYN_LEN(req) (SMBD_SMB2_OUT_DYN_IOV(req)->iov_len)
+
+#define SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN (SMB2_HDR_BODY + 0x30)
+
+ struct {
+ /*
+ * vector[0] TRANSPORT HEADER (empty)
+ * .
+ * vector[1] SMB2_TRANSFORM (optional)
+ * vector[2] SMB2
+ * vector[3] fixed body
+ * vector[4] dynamic body
+ * .
+ * .
+ * .
+ * vector[5] SMB2_TRANSFORM (optional)
+ * vector[6] SMB2
+ * vector[7] fixed body
+ * vector[8] dynamic body
+ * .
+ * .
+ * .
+ */
+ struct iovec *vector;
+ int vector_count;
+ struct iovec _vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
+ } in;
+ struct {
+ /* the NBT header is not allocated */
+ uint8_t nbt_hdr[4];
+ /*
+ * vector[0] TRANSPORT HEADER
+ * .
+ * vector[1] SMB2_TRANSFORM (optional)
+ * vector[2] SMB2
+ * vector[3] fixed body
+ * vector[4] dynamic body
+ * .
+ * .
+ * .
+ * vector[5] SMB2_TRANSFORM (empty)
+ * vector[6] SMB2
+ * vector[7] fixed body
+ * vector[8] dynamic body
+ * .
+ * .
+ * .
+ */
+ struct iovec *vector;
+ int vector_count;
+ struct iovec _vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
+#define OUTVEC_ALLOC_SIZE (SMB2_HDR_BODY + 9)
+ uint8_t _hdr[OUTVEC_ALLOC_SIZE];
+ uint8_t _body[0x58];
+ } out;
+};
+
+struct smbd_server_connection;
+
+struct pending_message_list;
+struct pending_auth_data;
+
+struct pthreadpool_tevent;
+struct dcesrv_context;
+
+struct smbd_server_connection {
+ const struct tsocket_address *local_address;
+ const struct tsocket_address *remote_address;
+ const char *remote_hostname;
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+ struct dcesrv_context *dce_ctx;
+ struct notify_context *notify_ctx;
+ bool using_smb2;
+ int trans_num;
+
+ size_t num_users;
+
+ size_t num_connections;
+ struct connection_struct *connections;
+
+ size_t num_files;
+ struct files_struct *files;
+
+ int real_max_open_files;
+ struct fsp_singleton_cache fsp_fi_cache;
+
+ struct pending_message_list *deferred_open_queue;
+
+
+ /* open directory handles. */
+ struct {
+ struct bitmap *dptr_bmap;
+ struct dptr_struct *dirptrs;
+ } searches;
+
+ uint64_t num_requests;
+
+ /* Current number of oplocks we have outstanding. */
+ struct {
+ int32_t exclusive_open;
+ int32_t level_II_open;
+ struct kernel_oplocks *kernel_ops;
+ } oplocks;
+
+ struct notify_mid_map *notify_mid_maps;
+
+ struct pthreadpool_tevent *pool;
+
+ struct smbXsrv_client *client;
+};
+
+extern struct smbXsrv_client *global_smbXsrv_client;
+
+void smbd_init_globals(void);
+
+/****************************************************************************
+ The buffer we keep around whilst an aio request is in process.
+*****************************************************************************/
+
+struct aio_extra {
+ files_struct *fsp;
+ struct smb_request *smbreq;
+ DATA_BLOB outbuf;
+ struct lock_struct lock;
+ size_t nbyte;
+ off_t offset;
+ bool write_through;
+};
+
+#endif /* _SOURCE3_SMBD_GLOBALS_H_ */
diff --git a/source3/smbd/mangle.c b/source3/smbd/mangle.c
new file mode 100644
index 0000000..dbb187a
--- /dev/null
+++ b/source3/smbd/mangle.c
@@ -0,0 +1,153 @@
+/*
+ Unix SMB/CIFS implementation.
+ Name mangling interface
+ Copyright (C) Andrew Tridgell 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "mangle.h"
+
+/* this allows us to add more mangling backends */
+static const struct {
+ const char *name;
+ const struct mangle_fns *(*init_fn)(void);
+} mangle_backends[] = {
+ { "hash", mangle_hash_init },
+ { "hash2", mangle_hash2_init },
+ { "posix", posix_mangle_init },
+ /*{ "tdb", mangle_tdb_init }, */
+ { NULL, NULL }
+};
+
+/*
+ initialise the mangling subsystem
+*/
+static void mangle_init(void)
+{
+ int i;
+ const char *method;
+
+ if (mangle_fns)
+ return;
+
+ method = lp_mangling_method();
+
+ /* find the first mangling method that manages to initialise and
+ matches the "mangling method" parameter */
+ for (i=0; mangle_backends[i].name && !mangle_fns; i++) {
+ if (!method || !*method || strcmp(method, mangle_backends[i].name) == 0) {
+ mangle_fns = mangle_backends[i].init_fn();
+ }
+ }
+
+ if (!mangle_fns) {
+ DEBUG(0,("Failed to initialise mangling system '%s'\n", method));
+ exit_server("mangling init failed");
+ }
+}
+
+
+/*
+ reset the cache. This is called when smb.conf has been reloaded
+*/
+void mangle_reset_cache(void)
+{
+ mangle_init();
+ mangle_fns->reset();
+}
+
+void mangle_change_to_posix(void)
+{
+ mangle_fns = NULL;
+ lp_set_mangling_method("posix");
+ mangle_reset_cache();
+}
+
+/*
+ see if a filename has come out of our mangling code
+*/
+bool mangle_is_mangled(const char *s, const struct share_params *p)
+{
+ return mangle_fns->is_mangled(s, p);
+}
+
+/*
+ see if a filename matches the rules of a 8.3 filename
+*/
+bool mangle_is_8_3(const char *fname, bool check_case,
+ const struct share_params *p)
+{
+ return mangle_fns->is_8_3(fname, check_case, False, p);
+}
+
+bool mangle_is_8_3_wildcards(const char *fname, bool check_case,
+ const struct share_params *p)
+{
+ return mangle_fns->is_8_3(fname, check_case, True, p);
+}
+
+bool mangle_must_mangle(const char *fname,
+ const struct share_params *p)
+{
+ if (lp_mangled_names(p) == MANGLED_NAMES_NO) {
+ return False;
+ }
+ return mangle_fns->must_mangle(fname, p);
+}
+
+/*
+ try to reverse map a 8.3 name to the original filename. This doesn't have to
+ always succeed, as the directory handling code in smbd will scan the directory
+ looking for a matching name if it doesn't. It should succeed most of the time
+ or there will be a huge performance penalty
+*/
+bool mangle_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ return mangle_fns->lookup_name_from_8_3(ctx, in, out, p);
+}
+
+/*
+ mangle a long filename to a 8.3 name.
+ Return True if we did mangle the name (ie. out is filled in).
+ False on error.
+ JRA.
+ */
+
+bool name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ const struct share_params *p)
+{
+ memset(out,'\0',13);
+
+ /* name mangling can be disabled for speed, in which case
+ we just truncate the string */
+ if (lp_mangled_names(p) == MANGLED_NAMES_NO) {
+ strlcpy(out, in, 13);
+ return True;
+ }
+
+ return mangle_fns->name_to_8_3(in,
+ out,
+ cache83,
+ lp_default_case(p->service),
+ p);
+}
diff --git a/source3/smbd/mangle_hash.c b/source3/smbd/mangle_hash.c
new file mode 100644
index 0000000..6a3cb33
--- /dev/null
+++ b/source3/smbd/mangle_hash.c
@@ -0,0 +1,1132 @@
+/*
+ Unix SMB/CIFS implementation.
+ Name mangling
+ Copyright (C) Andrew Tridgell 1992-2002
+ Copyright (C) Simo Sorce 2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "mangle.h"
+#include "util_tdb.h"
+#include "lib/param/loadparm.h"
+
+/* -------------------------------------------------------------------------- **
+ * Other stuff...
+ *
+ * magic_char - This is the magic char used for mangling. It's
+ * global. There is a call to lp_mangling_char() in server.c
+ * that is used to override the initial value.
+ *
+ * MANGLE_BASE - This is the number of characters we use for name mangling.
+ *
+ * basechars - The set characters used for name mangling. This
+ * is static (scope is this file only).
+ *
+ * mangle() - Macro used to select a character from basechars (i.e.,
+ * mangle(n) will return the nth digit, modulo MANGLE_BASE).
+ *
+ * chartest - array 0..255. The index range is the set of all possible
+ * values of a byte. For each byte value, the content is a
+ * two nibble pair. See BASECHAR_MASK below.
+ *
+ * ct_initialized - False until the chartest array has been initialized via
+ * a call to init_chartest().
+ *
+ * BASECHAR_MASK - Masks the upper nibble of a one-byte value.
+ *
+ * isbasecahr() - Given a character, check the chartest array to see
+ * if that character is in the basechars set. This is
+ * faster than using strchr_m().
+ *
+ */
+
+static const char basechars[43]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-!@#$%";
+#define MANGLE_BASE (sizeof(basechars)/sizeof(char)-1)
+
+#define mangle(V) ((char)(basechars[(V) % MANGLE_BASE]))
+#define BASECHAR_MASK 0xf0
+#define isbasechar(C) ( (chartest[ ((C) & 0xff) ]) & BASECHAR_MASK )
+
+/* -------------------------------------------------------------------- */
+
+
+/*******************************************************************
+ Determine if a character is valid in a 8.3 name.
+********************************************************************/
+
+static const uint32_t valid_table[] = {
+ 0x00000000,0x2fff7bfa,0xefffffff,0xefffffff,0x00000001,0x0fffffee,
+ 0xffffffff,0xffffffff,0x00000000,0x00000000,0x00000000,0x01000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0xfffe0000,0xfffe03fb,
+ 0x000003ff,0x00000000,0xffff0002,0xffffffff,0x0002ffff,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x33210000,0x080d0063,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00400008,0x00000802,0x00000000,0x03ff03ff,0x000f0000,0x00000000,
+ 0x00140000,0x00000000,0xe402098d,0x20305fa1,0x00040000,0x00000cc3,
+ 0x000000cc,0x80000020,0x00000000,0x00000000,0x00040000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x000fffff,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x3999900f,0x99999939,0x00000804,0x00000000,
+ 0x00000000,0x300c0003,0x0000c8c0,0x00008000,0x00000060,0x00000000,
+ 0x00000005,0x0000a400,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0xa03fffef,0x00000000,0xfffffffe,0xffffffff,0x781fffff,0xfffffffe,
+ 0xffffffff,0x787fffff,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x02060000,
+ 0x00000000,0x00000000,0x00000000,0x000001f0,0x00000000,0x00000000,
+ 0x01102008,0x084008cc,0x00822600,0x78000000,0x7000c000,0x00000002,
+ 0x00002010,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x43f36f8b,0x9b462542,0xe3e0e82c,0x400a0004,0xdb365f65,0x04497977,
+ 0xe3f0ecd7,0x18c5603a,0x3403e60b,0x37518000,0x7eebe0c8,0x98698200,
+ 0x2d56ad48,0x8060e803,0xad93661c,0xc568c03a,0xc656aa60,0x02403f7e,
+ 0x146183cd,0x21751020,0x07122021,0x40bc3000,0x4562a624,0x0a3060a8,
+ 0x85740217,0x9c840402,0x14157ffb,0x11e27f34,0x22efb665,0x60ff1f75,
+ 0x38403a70,0x676336c3,0x20b24dd9,0x0fc946b0,0x4850bc98,0xa03f8638,
+ 0x98162388,0x5232be49,0xeba422ab,0xc72c00dd,0x26e1a1e7,0x8f0a841b,
+ 0x559e27eb,0x89bfc241,0x85480014,0x084d6361,0xaad07f0c,0x05cfff3e,
+ 0xa803ff1a,0x7b407a41,0x80024745,0x38eb0500,0x1005dc51,0x710c9b34,
+ 0x01000397,0xa4046366,0x005180d0,0x430ac000,0x30c89071,0x58000008,
+ 0xf7000ed9,0x00415f80,0x941000b0,0x62800018,0x09d00240,0x01568200,
+ 0x08015004,0x05101d10,0x001084c1,0x10504025,0x4d8a410f,0xa60d4009,
+ 0x914cab19,0x098121c0,0x0203c485,0x80000672,0x00080b04,0x0009141d,
+ 0x905c49c9,0x16900009,0x22200c65,0x24338412,0x47960c03,0x42250a04,
+ 0xd0880028,0x4f0c4900,0xd3aa14a2,0x3e87d830,0x1f618e04,0x41867ea4,
+ 0x2dbbc390,0x211857ad,0x2a48241e,0x4e041138,0x161b0a40,0x88400d60,
+ 0x9502020a,0x10608221,0x04000243,0x80001444,0x0c040000,0x70000000,
+ 0x00c11a06,0x0c00024a,0x00401a00,0x40451404,0xbdf30029,0x052b0a78,
+ 0xbfa0bba9,0x8379407c,0xe91d12fd,0xc5695bf6,0x444aeff6,0xff022115,
+ 0x402bed63,0x0242d033,0x00131000,0x5dca1b42,0x020000a0,0x2c61a703,
+ 0x8ff24880,0x00000284,0x100d5804,0x0048b200,0x20011894,0x37805004,
+ 0x684d3200,0x68be49ea,0x2e42184c,0x21c9a820,0x80b050b9,0xff7c001e,
+ 0x14e0849a,0x01e028c1,0xac49870e,0xdddb130f,0x89fbbe1a,0x51b2a2e2,
+ 0x32ca5522,0x928b3ec6,0x438f1dbf,0x32986703,0x73c03028,0xa9230811,
+ 0x3a65c000,0x04028fe3,0xa6252c4e,0x00a1bf3d,0x8cd43e3a,0x317c06c9,
+ 0xd52a00e0,0x0edf018b,0x8c22e34b,0xf0911183,0xa7287d94,0x40fbc9ac,
+ 0x07534484,0x44445a90,0x00013fc8,0xf5d40048,0xec5f7701,0x891dc442,
+ 0x49286b83,0xd2424109,0x59fe061d,0x3a221840,0x3b9fb7e4,0xc0eaf003,
+ 0x82021386,0xe4008980,0x10a1b200,0x0cc44b80,0x8944d309,0x48341faf,
+ 0x0c458259,0x0470420a,0x10c8a040,0x44503140,0x01004004,0x05408281,
+ 0x642c0108,0x1a056a30,0x051460a6,0x645690cf,0x31000021,0xcbf09c18,
+ 0x63e2e120,0x01b5104c,0x9a83538c,0x3281b8b2,0x0a84987a,0x0c0233e7,
+ 0xd038d6cd,0x9872e1b1,0xe2848a1e,0x0459c3f4,0x23c2439a,0xd3144845,
+ 0x36400292,0xffbd0241,0xe8f0eb09,0xa5d27dc0,0xd24bc242,0xd0afa47f,
+ 0x34a11aa0,0x0bd88247,0x651bc453,0xc83ad294,0x40c8001e,0x33140e06,
+ 0xb21f615f,0xc0d00088,0xa898a02a,0x166ba1c5,0x85b4af50,0x0604c08b,
+ 0x1e04f933,0xa251056e,0x76380400,0x73b8ed07,0x19324406,0xc8164081,
+ 0x63097c8a,0xaa042984,0xca9c1c24,0x27614e0e,0x830009d0,0xc10c0846,
+ 0x10816011,0x0908540d,0xcc0a000e,0x0c000514,0xa0440430,0x6784008b,
+ 0x8a195288,0x8b18865e,0x41602e59,0x9cbe8c10,0x895c6861,0x00089800,
+ 0x089a8100,0xc1900018,0xf4a14007,0x640d8505,0x0e4d314e,0xff0a4806,
+ 0x2ea81632,0x000b852e,0xca841810,0x696c0e20,0x16000032,0x0390d658,
+ 0x1a6851a0,0x11249000,0x432698e1,0x1fae5d52,0xae280fa0,0x5700fafb,
+ 0x99406408,0xc044c880,0xb1419005,0xa4c48424,0x603a1a34,0xc1949000,
+ 0x003a8246,0xc106180d,0x99100022,0x1511e050,0x00824157,0x022a041a,
+ 0x8930004f,0x446ad813,0xed228aa2,0x400511c0,0x01021000,0x31018808,
+ 0x02044620,0x0f08f800,0xa2008900,0x22020000,0x16108210,0x10400042,
+ 0x126052c0,0x200052f4,0x82308510,0x42021100,0x80b5430a,0xda2070e1,
+ 0x08012040,0xfc653500,0xab0419c1,0x62140286,0x00440087,0x42469085,
+ 0x0a85405c,0x33803207,0xb8c00400,0xc0d0ce30,0x0080c030,0x0da50508,
+ 0x00400a90,0x280c0200,0x40446705,0x41226429,0x000002e8,0x847c4664,
+ 0xde200002,0x4049861d,0xc0000a08,0x20010084,0x10108400,0x01c742cd,
+ 0xd52a703a,0x1d8f9968,0x3e12be50,0x81d9aef5,0x2412cec4,0x732e0828,
+ 0x4b3424ac,0xd41d020c,0x80002a02,0x08110097,0x114411c4,0x7d451786,
+ 0x5e4949dd,0x87914040,0xd8c4254c,0x491444ba,0xc8001b92,0x15800271,
+ 0x0c0000c1,0xc200096a,0x40024800,0xba493021,0x1c802080,0x1008e2ac,
+ 0x00341004,0x841400e3,0x20004020,0x14149810,0x04aa70c2,0x54208688,
+ 0x04130c62,0x20109180,0x02064082,0x54011c40,0xe4e90383,0x84802125,
+ 0x2810e433,0xe60944c0,0x81260a03,0x080112da,0x97906901,0xf8864001,
+ 0x0081e24d,0xa6510a0e,0x81ec011a,0x8441c600,0xb62eadb8,0x8741acef,
+ 0x4b028d54,0x02681161,0x2057bb60,0x043350a0,0xf7b4a8c0,0x01122402,
+ 0x20009ad3,0x00c82271,0x809e2081,0xe1800c8a,0x8151b009,0x40281031,
+ 0x89a52a0e,0x620e69b6,0xd1444425,0x4d548085,0x1fb12c75,0x862dd807,
+ 0x5841d97c,0x226e414e,0x9e088200,0xedb7f80d,0x75668c80,0x08149313,
+ 0xc8040e32,0x6ea6484e,0x66742c4a,0xba0126c0,0x185dd70c,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x05400000,0x813370a0,0x03a54f81,
+ 0x641055ec,0x2344c31a,0x00341462,0x1a090a43,0x13a5187b,0xa8480102,
+ 0xc5440440,0xe2dd8106,0x2d481af0,0x0416b626,0x6e405058,0x31128032,
+ 0x0c0007e4,0x420a8208,0x803b4840,0x87134860,0x3428850d,0xe5290319,
+ 0x870a2345,0x5c1825a9,0xd9c577a6,0x03e85e00,0xa7000081,0x41c6cd54,
+ 0xa2042800,0x2b0ab860,0xda9e0020,0x0e1a08ea,0x11c0427e,0x03768908,
+ 0x01058621,0x98a80004,0xc44846a0,0x20220d05,0x914854a2,0x28d78a01,
+ 0x00087898,0x31221605,0x08804340,0x06a2fa4e,0x92110814,0x9b142002,
+ 0x16432e52,0x90105000,0x85ba0041,0x20203042,0x07a84f0b,0x40802f08,
+ 0x1a930591,0x0601df50,0x3021a202,0x4e800630,0x04c80cc4,0x8001a004,
+ 0xd4316000,0x0a020880,0x00281c00,0x00418e18,0xca106ad0,0x4b00f210,
+ 0x1506274d,0x88900220,0x82a85a00,0x81504549,0x80002004,0x2c088804,
+ 0x000508d1,0x4ac48001,0x0062e0a0,0x0a42008e,0x6a8c3055,0xe0a5090e,
+ 0x42c42906,0x80b34814,0xb330803e,0x733c0102,0x700d1494,0x09400c20,
+ 0xc040301a,0xc094a451,0x05c88dca,0xa40c96c2,0x34040001,0x011000c8,
+ 0xa9cd550d,0x1cda2428,0x48370142,0x120f7a4d,0x452a32b4,0xd20531fb,
+ 0xdc44b894,0x45ca68d7,0x2ed15097,0x42081943,0x9d48d202,0xa0979840,
+ 0x064d5409,0x00000000,0x00000000,0x00000000,0x00000000,0x84800000,
+ 0x04215542,0x17001c06,0x61107624,0xb9ddff87,0x5c0a659f,0x3c11245d,
+ 0x005dadb0,0x00000000,0x00000000,0x00db28d0,0x02000422,0x44080108,
+ 0xac409804,0x90288d0a,0xe0018700,0x00310400,0x82211794,0x10540019,
+ 0x021a2cb2,0x40039c02,0x8804bd60,0x7900080c,0xba3c1628,0xcb088640,
+ 0x90807274,0x0000001e,0xd8000000,0x9c87e188,0x04124034,0x2791ae64,
+ 0xe6fbe86b,0x5366408f,0x537feea6,0xb5e4e3ab,0x0002869f,0x01228548,
+ 0x48004402,0x20a02116,0x02240004,0x00052080,0x01547e00,0x01ac162c,
+ 0x10852a84,0x05308c14,0xfdc3fbc3,0x906060fa,0x40336440,0x96901200,
+ 0x4e834b31,0x418200d4,0x1d6a0129,0x02802080,0x02ad8000,0x9f0c2691,
+ 0x67018044,0x0c24d96f,0x18d02910,0x50215001,0x04d01000,0x02017090,
+ 0x61c30148,0x01000132,0x07190088,0x05620802,0x4c0e0132,0xf0a10405,
+ 0x00000002,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00800000,0x035e8e8d,0x5a0421bd,0x11703488,0x00000026,
+ 0x10000000,0x8804c502,0xf801b815,0x25ed147c,0x3bb0ed60,0x1bd78589,
+ 0x1a627af3,0x0ac50d0c,0x524ae5d1,0x6b0d0490,0x5266a35c,0x16122b57,
+ 0x1101a872,0x00182949,0x10080948,0x886c6000,0x058f916e,0x39903012,
+ 0x49b0f840,0x001b88a0,0x00000000,0x00428500,0x98000058,0x7014ea04,
+ 0x611d1628,0x60005193,0x00a71a24,0x00000000,0x43c00000,0x10187120,
+ 0xa9270172,0x89066004,0x020cc022,0x40810900,0x8ca0602d,0x00000e34,
+ 0x00000000,0x11012100,0xd31a8011,0x0892ec4c,0x85000040,0x1806c7ac,
+ 0x0512e03e,0x00348000,0x80cec008,0x0a126d01,0x08568641,0x0027011e,
+ 0x083d3751,0x4e05e032,0x048401c0,0x01400081,0x00000000,0x00000000,
+ 0x00000000,0x00591aa0,0x882443c8,0xc8001d48,0x72030152,0x04059813,
+ 0x04008280,0x0d148a10,0x02088056,0x2704a040,0x4e000000,0x00000000,
+ 0x00000000,0xa3200000,0xa0ae1902,0xdf002660,0x7b17f010,0x3ad08121,
+ 0x00284180,0x48001003,0x8014cc00,0x00c414cf,0x30202000,0x00000001,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,
+ 0xffffffff,0xffffffff,0x00ffffff,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x000f0000,
+ 0x00000000,0x00000200,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x10000000,0x00000000,0xffffc000,0x00003fff,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
+ 0xfffffffe,0xffffffff,0x7fffffff,0xfffffffe,0xffffffff,0x00000000,
+ 0x00000000,0x0000003f
+};
+
+#if 0
+/*
+ * The following program regenerates the good old valid.dat. Try it
+ * yourself :-)
+ */
+int main(void)
+{
+ int i;
+ for (i=0; i<65536; i++) {
+ char c = (valid_table[i/32] & (1<<(i%32))) ? 1 : 0;
+ write(1, &c, 1);
+ }
+}
+#endif
+
+static bool isvalid83_w(smb_ucs2_t c)
+{
+ uint16_t idx = SVAL(&c, 0);
+ return (valid_table[idx/32] & (1 << (idx%32))) != 0;
+}
+
+static NTSTATUS has_valid_83_chars(const smb_ucs2_t *s, bool allow_wildcards)
+{
+ if (!*s) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!allow_wildcards && ms_has_wild_w(s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ while (*s) {
+ if(!isvalid83_w(*s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ s++;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS has_illegal_chars(const smb_ucs2_t *s, bool allow_wildcards)
+{
+ if (!allow_wildcards && ms_has_wild_w(s)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ while (*s) {
+ if (*s <= 0x1f) {
+ /* Control characters. */
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ switch(*s) {
+ case UCS2_CHAR('\\'):
+ case UCS2_CHAR('/'):
+ case UCS2_CHAR('|'):
+ case UCS2_CHAR(':'):
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ s++;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Duplicate string.
+********************************************************************/
+
+static smb_ucs2_t *strdup_w(const smb_ucs2_t *src)
+{
+ smb_ucs2_t *dest;
+ size_t len = strlen_w(src);
+ dest = SMB_MALLOC_ARRAY(smb_ucs2_t, len + 1);
+ if (!dest) {
+ DEBUG(0,("strdup_w: out of memory!\n"));
+ return NULL;
+ }
+
+ memcpy(dest, src, len * sizeof(smb_ucs2_t));
+ dest[len] = 0;
+ return dest;
+}
+
+/* return False if something fail and
+ * return 2 alloced unicode strings that contain prefix and extension
+ */
+
+static NTSTATUS mangle_get_prefix(const smb_ucs2_t *ucs2_string, smb_ucs2_t **prefix,
+ smb_ucs2_t **extension, bool allow_wildcards)
+{
+ size_t ext_len;
+ smb_ucs2_t *p;
+
+ *extension = 0;
+ *prefix = strdup_w(ucs2_string);
+ if (!*prefix) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if ((p = strrchr_w(*prefix, UCS2_CHAR('.')))) {
+ ext_len = strlen_w(p+1);
+ if ((ext_len > 0) && (ext_len < 4) && (p != *prefix) &&
+ (NT_STATUS_IS_OK(has_valid_83_chars(p+1,allow_wildcards)))) /* check extension */ {
+ *p = 0;
+ *extension = strdup_w(p+1);
+ if (!*extension) {
+ SAFE_FREE(*prefix);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/* ************************************************************************** **
+ * Return NT_STATUS_UNSUCCESSFUL if a name is a special msdos reserved name.
+ * or contains illegal characters.
+ *
+ * Input: fname - String containing the name to be tested.
+ *
+ * Output: NT_STATUS_UNSUCCESSFUL, if the condition above is true.
+ *
+ * Notes: This is a static function called by is_8_3(), below.
+ *
+ * ************************************************************************** **
+ */
+
+static NTSTATUS is_valid_name(const smb_ucs2_t *fname, bool allow_wildcards, bool only_8_3)
+{
+ smb_ucs2_t *str, *p;
+ size_t num_ucs2_chars;
+ NTSTATUS ret = NT_STATUS_OK;
+
+ if (!fname || !*fname)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ /* . and .. are valid names. */
+ if (strcmp_wa(fname, ".")==0 || strcmp_wa(fname, "..")==0)
+ return NT_STATUS_OK;
+
+ if (only_8_3) {
+ ret = has_valid_83_chars(fname, allow_wildcards);
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+ }
+
+ ret = has_illegal_chars(fname, allow_wildcards);
+ if (!NT_STATUS_IS_OK(ret))
+ return ret;
+
+ /* Name can't end in '.' or ' ' */
+ num_ucs2_chars = strlen_w(fname);
+ if (fname[num_ucs2_chars-1] == UCS2_CHAR('.') || fname[num_ucs2_chars-1] == UCS2_CHAR(' ')) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ str = strdup_w(fname);
+
+ /* Truncate copy after the first dot. */
+ p = strchr_w(str, UCS2_CHAR('.'));
+ if (p) {
+ *p = 0;
+ }
+
+ strupper_w(str);
+ p = &str[1];
+
+ switch(str[0])
+ {
+ case UCS2_CHAR('A'):
+ if(strcmp_wa(p, "UX") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('C'):
+ if((strcmp_wa(p, "LOCK$") == 0)
+ || (strcmp_wa(p, "ON") == 0)
+ || (strcmp_wa(p, "OM1") == 0)
+ || (strcmp_wa(p, "OM2") == 0)
+ || (strcmp_wa(p, "OM3") == 0)
+ || (strcmp_wa(p, "OM4") == 0)
+ )
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('L'):
+ if((strcmp_wa(p, "PT1") == 0)
+ || (strcmp_wa(p, "PT2") == 0)
+ || (strcmp_wa(p, "PT3") == 0)
+ )
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('N'):
+ if(strcmp_wa(p, "UL") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ case UCS2_CHAR('P'):
+ if(strcmp_wa(p, "RN") == 0)
+ ret = NT_STATUS_UNSUCCESSFUL;
+ break;
+ default:
+ break;
+ }
+
+ SAFE_FREE(str);
+ return ret;
+}
+
+static NTSTATUS is_8_3_w(const smb_ucs2_t *fname, bool allow_wildcards)
+{
+ smb_ucs2_t *pref = 0, *ext = 0;
+ size_t plen;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+
+ if (!fname || !*fname)
+ return NT_STATUS_INVALID_PARAMETER;
+
+ if (strlen_w(fname) > 12)
+ return NT_STATUS_UNSUCCESSFUL;
+
+ if (strcmp_wa(fname, ".") == 0 || strcmp_wa(fname, "..") == 0)
+ return NT_STATUS_OK;
+
+ /* Name cannot start with '.' */
+ if (*fname == UCS2_CHAR('.'))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ if (!NT_STATUS_IS_OK(is_valid_name(fname, allow_wildcards, True)))
+ goto done;
+
+ if (!NT_STATUS_IS_OK(mangle_get_prefix(fname, &pref, &ext, allow_wildcards)))
+ goto done;
+ plen = strlen_w(pref);
+
+ if (strchr_wa(pref, '.'))
+ goto done;
+ if (plen < 1 || plen > 8)
+ goto done;
+ if (ext && (strlen_w(ext) > 3))
+ goto done;
+
+ ret = NT_STATUS_OK;
+
+done:
+ SAFE_FREE(pref);
+ SAFE_FREE(ext);
+ return ret;
+}
+
+static bool is_8_3(const char *fname, bool check_case, bool allow_wildcards,
+ const struct share_params *p)
+{
+ const char *f;
+ smb_ucs2_t *ucs2name;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ size_t size;
+
+ if (!fname || !*fname)
+ return False;
+ if ((f = strrchr(fname, '/')) == NULL)
+ f = fname;
+ else
+ f++;
+
+ if (strlen(f) > 12)
+ return False;
+
+ if (!push_ucs2_talloc(NULL, &ucs2name, f, &size)) {
+ DEBUG(0,("is_8_3: internal error push_ucs2_talloc() failed!\n"));
+ goto done;
+ }
+
+ ret = is_8_3_w(ucs2name, allow_wildcards);
+
+done:
+ TALLOC_FREE(ucs2name);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ return False;
+ }
+
+ return True;
+}
+
+/* -------------------------------------------------------------------------- **
+ * Functions...
+ */
+
+/* ************************************************************************** **
+ * Initialize the static character test array.
+ *
+ * Input: none
+ *
+ * Output: none
+ *
+ * Notes: This function changes (loads) the contents of the <chartest>
+ * array. The scope of <chartest> is this file.
+ *
+ * ************************************************************************** **
+ */
+
+static void init_chartest( void )
+{
+ const unsigned char *s;
+
+ chartest = SMB_MALLOC_ARRAY(unsigned char, 256);
+
+ SMB_ASSERT(chartest != NULL);
+ memset(chartest, '\0', 256);
+
+ for( s = (const unsigned char *)basechars; *s; s++ ) {
+ chartest[*s] |= BASECHAR_MASK;
+ }
+}
+
+/* ************************************************************************** **
+ * Return True if the name *could be* a mangled name.
+ *
+ * Input: s - A path name - in UNIX pathname format.
+ *
+ * Output: True if the name matches the pattern described below in the
+ * notes, else False.
+ *
+ * Notes: The input name is *not* tested for 8.3 compliance. This must be
+ * done separately. This function returns true if the name contains
+ * a magic character followed by exactly two characters from the
+ * basechars list (above), which in turn are followed either by the
+ * nul (end of string) byte or a dot (extension) or by a '/' (end of
+ * a directory name).
+ *
+ * ************************************************************************** **
+ */
+
+static bool is_mangled(const char *s, const struct share_params *p)
+{
+ char *magic;
+ char magic_char;
+
+ magic_char = lp_mangling_char(p);
+
+ if (chartest == NULL) {
+ init_chartest();
+ }
+
+ magic = strchr_m( s, magic_char );
+ while( magic && magic[1] && magic[2] ) { /* 3 chars, 1st is magic. */
+ if( ('.' == magic[3] || '/' == magic[3] || !(magic[3])) /* Ends with '.' or nul or '/' ? */
+ && isbasechar( toupper_m(magic[1]) ) /* is 2nd char basechar? */
+ && isbasechar( toupper_m(magic[2]) ) ) /* is 3rd char basechar? */
+ return( True ); /* If all above, then true, */
+ magic = strchr_m( magic+1, magic_char ); /* else seek next magic. */
+ }
+ return( False );
+}
+
+/***************************************************************************
+ Initializes or clears the mangled cache.
+***************************************************************************/
+
+static void mangle_reset( void )
+{
+ /* We could close and re-open the tdb here... should we ? The old code did
+ the equivalent... JRA. */
+}
+
+/***************************************************************************
+ Add a mangled name into the cache.
+ If the extension of the raw name maps directly to the
+ extension of the mangled name, then we'll store both names
+ *without* extensions. That way, we can provide consistent
+ reverse mangling for all names that match. The test here is
+ a bit more careful than the one done in earlier versions of
+ mangle.c:
+
+ - the extension must exist on the raw name,
+ - it must be all lower case
+ - it must match the mangled extension (to prove that no
+ mangling occurred).
+ crh 07-Apr-1998
+**************************************************************************/
+
+static void cache_mangled_name( const char mangled_name[13],
+ const char *raw_name )
+{
+ TDB_DATA data_val;
+ char mangled_name_key[13];
+ char *s1 = NULL;
+ char *s2 = NULL;
+
+ /* If the cache isn't initialized, give up. */
+ if( !tdb_mangled_cache )
+ return;
+
+ /* Init the string lengths. */
+ strlcpy(mangled_name_key, mangled_name, sizeof(mangled_name_key));
+
+ /* See if the extensions are unmangled. If so, store the entry
+ * without the extension, thus creating a "group" reverse map.
+ */
+ s1 = strrchr( mangled_name_key, '.' );
+ if( s1 && (s2 = strrchr( raw_name, '.' )) ) {
+ size_t i = 1;
+ while( s1[i] && (tolower_m( s1[i] ) == s2[i]) )
+ i++;
+ if( !s1[i] && !s2[i] ) {
+ /* Truncate at the '.' */
+ *s1 = '\0';
+ /*
+ * DANGER WILL ROBINSON - this
+ * is changing a const string via
+ * an aliased pointer ! Remember to
+ * put it back once we've used it.
+ * JRA
+ */
+ *s2 = '\0';
+ }
+ }
+
+ /* Allocate a new cache entry. If the allocation fails, just return. */
+ data_val = string_term_tdb_data(raw_name);
+ if (tdb_store_bystring(tdb_mangled_cache, mangled_name_key, data_val, TDB_REPLACE) != 0) {
+ DEBUG(0,("cache_mangled_name: Error storing entry %s -> %s\n", mangled_name_key, raw_name));
+ } else {
+ DEBUG(5,("cache_mangled_name: Stored entry %s -> %s\n", mangled_name_key, raw_name));
+ }
+ /* Restore the change we made to the const string. */
+ if (s2) {
+ *s2 = '.';
+ }
+}
+
+/* ************************************************************************** **
+ * Check for a name on the mangled name stack
+ *
+ * Input: s - Input *and* output string buffer.
+ * maxlen - space in i/o string buffer.
+ * Output: True if the name was found in the cache, else False.
+ *
+ * Notes: If a reverse map is found, the function will overwrite the string
+ * space indicated by the input pointer <s>. This is frightening.
+ * It should be rewritten to return NULL if the long name was not
+ * found, and a pointer to the long name if it was found.
+ *
+ * ************************************************************************** **
+ */
+
+static bool lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ TDB_DATA data_val;
+ char *saved_ext = NULL;
+ char *s = talloc_strdup(ctx, in);
+
+ /* If the cache isn't initialized, give up. */
+ if(!s || !tdb_mangled_cache ) {
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ data_val = tdb_fetch_bystring(tdb_mangled_cache, s);
+
+ /* If we didn't find the name *with* the extension, try without. */
+ if(data_val.dptr == NULL || data_val.dsize == 0) {
+ char *ext_start = strrchr( s, '.' );
+ if( ext_start ) {
+ if((saved_ext = talloc_strdup(ctx,ext_start)) == NULL) {
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ *ext_start = '\0';
+ data_val = tdb_fetch_bystring(tdb_mangled_cache, s);
+ /*
+ * At this point s is the name without the
+ * extension. We re-add the extension if saved_ext
+ * is not null, before freeing saved_ext.
+ */
+ }
+ }
+
+ /* Okay, if we haven't found it we're done. */
+ if(data_val.dptr == NULL || data_val.dsize == 0) {
+ TALLOC_FREE(saved_ext);
+ TALLOC_FREE(s);
+ return False;
+ }
+
+ /* If we *did* find it, we need to talloc it on the given ctx. */
+ if (saved_ext) {
+ *out = talloc_asprintf(ctx, "%s%s",
+ (char *)data_val.dptr,
+ saved_ext);
+ } else {
+ *out = talloc_strdup(ctx, (char *)data_val.dptr);
+ }
+
+ TALLOC_FREE(s);
+ TALLOC_FREE(saved_ext);
+ SAFE_FREE(data_val.dptr);
+
+ return *out ? True : False;
+}
+
+/**
+ Check if a string is in "normal" case.
+**/
+
+static bool strisnormal(const char *s, int case_default)
+{
+ if (case_default == CASE_UPPER)
+ return(!strhaslower(s));
+
+ return(!strhasupper(s));
+}
+
+
+/*****************************************************************************
+ Do the actual mangling to 8.3 format.
+*****************************************************************************/
+
+static bool to_8_3(char magic_char, const char *in, char out[13], int default_case)
+{
+ int csum;
+ char *p;
+ char extension[4];
+ char base[9];
+ int baselen = 0;
+ int extlen = 0;
+ char *s = SMB_STRDUP(in);
+
+ extension[0] = 0;
+ base[0] = 0;
+
+ if (!s) {
+ return False;
+ }
+
+ p = strrchr(s,'.');
+ if( p && (strlen(p+1) < (size_t)4) ) {
+ bool all_normal = ( strisnormal(p+1, default_case) ); /* XXXXXXXXX */
+
+ if( all_normal && p[1] != 0 ) {
+ *p = 0;
+ csum = str_checksum( s );
+ *p = '.';
+ } else
+ csum = str_checksum(s);
+ } else
+ csum = str_checksum(s);
+
+ if (!strupper_m( s )) {
+ SAFE_FREE(s);
+ return false;
+ }
+
+ if( p ) {
+ if( p == s )
+ strlcpy( extension, "___", 4);
+ else {
+ *p++ = 0;
+ while( *p && extlen < 3 ) {
+ if ( *p != '.') {
+ extension[extlen++] = p[0];
+ }
+ p++;
+ }
+ extension[extlen] = 0;
+ }
+ }
+
+ p = s;
+
+ while( *p && baselen < 5 ) {
+ if (isbasechar(*p)) {
+ base[baselen++] = p[0];
+ }
+ p++;
+ }
+ base[baselen] = 0;
+
+ csum = csum % (MANGLE_BASE*MANGLE_BASE);
+
+ memcpy(out, base, baselen);
+ out[baselen] = magic_char;
+ out[baselen+1] = mangle( csum/MANGLE_BASE );
+ out[baselen+2] = mangle( csum );
+
+ if( *extension ) {
+ out[baselen+3] = '.';
+ strlcpy(&out[baselen+4], extension, 4);
+ }
+
+ SAFE_FREE(s);
+ return True;
+}
+
+static bool must_mangle(const char *name,
+ const struct share_params *p)
+{
+ smb_ucs2_t *name_ucs2 = NULL;
+ NTSTATUS status;
+ size_t converted_size;
+
+ if (!push_ucs2_talloc(NULL, &name_ucs2, name, &converted_size)) {
+ DEBUG(0, ("push_ucs2_talloc failed!\n"));
+ return False;
+ }
+ status = is_valid_name(name_ucs2, False, False);
+ TALLOC_FREE(name_ucs2);
+ /* We return true if we *must* mangle, so if it's
+ * a valid name (status == OK) then we must return
+ * false. Bug #6939. */
+ return !NT_STATUS_IS_OK(status);
+}
+
+/*****************************************************************************
+ * Convert a filename to DOS format. Return True if successful.
+ * Input: in Incoming name.
+ *
+ * out 8.3 DOS name.
+ *
+ * cache83 - If False, the mangled name cache will not be updated.
+ * This is usually used to prevent that we overwrite
+ * a conflicting cache entry prematurely, i.e. before
+ * we know whether the client is really interested in the
+ * current name. (See PR#13758). UKD.
+ *
+ * ****************************************************************************
+ */
+
+static bool hash_name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ smb_ucs2_t *in_ucs2 = NULL;
+ size_t converted_size;
+ char magic_char;
+
+ magic_char = lp_mangling_char(p);
+
+ DEBUG(5,("hash_name_to_8_3( %s, cache83 = %s)\n", in,
+ cache83 ? "True" : "False"));
+
+ if (!push_ucs2_talloc(NULL, &in_ucs2, in, &converted_size)) {
+ DEBUG(0, ("push_ucs2_talloc failed!\n"));
+ return False;
+ }
+
+ /* If it's already 8.3, just copy. */
+ if (NT_STATUS_IS_OK(is_valid_name(in_ucs2, False, False)) &&
+ NT_STATUS_IS_OK(is_8_3_w(in_ucs2, False))) {
+ TALLOC_FREE(in_ucs2);
+ strlcpy(out, in, 13);
+ return True;
+ }
+
+ TALLOC_FREE(in_ucs2);
+ if (!to_8_3(magic_char, in, out, default_case)) {
+ return False;
+ }
+
+ cache_mangled_name(out, in);
+
+ DEBUG(5,("hash_name_to_8_3(%s) ==> [%s]\n", in, out));
+ return True;
+}
+
+/*
+ the following provides the abstraction layer to make it easier
+ to drop in an alternative mangling implementation
+*/
+static const struct mangle_fns mangle_hash_fns = {
+ mangle_reset,
+ is_mangled,
+ must_mangle,
+ is_8_3,
+ lookup_name_from_8_3,
+ hash_name_to_8_3
+};
+
+/***************************************************************
+ Compute a hash value based on a string key value.
+ The function returns the bucket index number for the hashed key.
+ JRA. Use a djb-algorithm hash for speed.
+***************************************************************/
+
+static unsigned int fast_string_hash(TDB_DATA *key)
+{
+ unsigned int n = 0;
+ const char *p;
+ for (p = (const char *)key->dptr; *p != '\0'; p++) {
+ n = ((n << 5) + n) ^ (unsigned int)(*p);
+ }
+ return n;
+}
+
+/* return the methods for this mangling implementation */
+const struct mangle_fns *mangle_hash_init(void)
+{
+ mangle_reset();
+
+ if (chartest == NULL) {
+ init_chartest();
+ }
+
+ /* Create the in-memory tdb using our custom hash function. */
+ tdb_mangled_cache = tdb_open_ex("mangled_cache", 1031, TDB_INTERNAL,
+ (O_RDWR|O_CREAT), 0644, NULL, fast_string_hash);
+
+ return &mangle_hash_fns;
+}
diff --git a/source3/smbd/mangle_hash2.c b/source3/smbd/mangle_hash2.c
new file mode 100644
index 0000000..0075d3b
--- /dev/null
+++ b/source3/smbd/mangle_hash2.c
@@ -0,0 +1,875 @@
+/*
+ Unix SMB/CIFS implementation.
+ new hash based name mangling implementation
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Simo Sorce 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ this mangling scheme uses the following format
+
+ Annnn~n.AAA
+
+ where nnnnn is a base 36 hash, and A represents characters from the original string
+
+ The hash is taken of the leading part of the long filename, in uppercase
+
+ for simplicity, we only allow ascii characters in 8.3 names
+ */
+
+ /* hash algorithm changed to FNV1 by idra@samba.org (Simo Sorce).
+ * see http://www.isthe.com/chongo/tech/comp/fnv/index.html for a
+ * discussion on Fowler / Noll / Vo (FNV) Hash by one of it's authors
+ */
+
+/*
+ ===============================================================================
+ NOTE NOTE NOTE!!!
+
+ This file deliberately uses non-multibyte string functions in many places. This
+ is *not* a mistake. This code is multi-byte safe, but it gets this property
+ through some very subtle knowledge of the way multi-byte strings are encoded
+ and the fact that this mangling algorithm only supports ascii characters in
+ 8.3 names.
+
+ please don't convert this file to use the *_m() functions!!
+ ===============================================================================
+*/
+
+/*
+ * ============================================================================
+ * Whenever you change anything in the FLAG_ or other fields,
+ * re-initialize the tables char_flags and base_reverse by running the
+ * init_tables() routine once and dump its results. To do this, a
+ * single smbd run with
+ *
+ * #define DYNAMIC_MANGLE_TABLES 1
+ *
+ * and debug level 10 should be sufficient.
+ * ============================================================================
+ */
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/memcache.h"
+#include "mangle.h"
+
+#if 1
+#define M_DEBUG(level, x) DEBUG(level, x)
+#else
+#define M_DEBUG(level, x)
+#endif
+
+/* these flags are used to mark characters in as having particular
+ properties */
+#define FLAG_BASECHAR 1
+#define FLAG_ASCII 2
+#define FLAG_ILLEGAL 4
+#define FLAG_WILDCARD 8
+
+/* the "possible" flags are used as a fast way to find possible DOS
+ reserved filenames */
+#define FLAG_POSSIBLE1 16
+#define FLAG_POSSIBLE2 32
+#define FLAG_POSSIBLE3 64
+#define FLAG_POSSIBLE4 128
+
+#define FNV1_PRIME 0x01000193
+/*the following number is a fnv1 of the string: idra@samba.org 2002 */
+#define FNV1_INIT 0xa6b93095
+
+#define FLAG_CHECK(c, flag) (char_flags[(unsigned char)(c)] & (flag))
+
+/* these are the characters we use in the 8.3 hash. Must be 36 chars long */
+static const char basechars[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+#define base_forward(v) basechars[v]
+
+/* the list of reserved dos names - all of these are illegal */
+static const char * const reserved_names[] =
+{ "AUX", "CON",
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
+ "NUL", "PRN", NULL };
+
+#define DYNAMIC_MANGLE_TABLES 0
+
+#if DYNAMIC_MANGLE_TABLES
+
+/* these tables are used to provide fast tests for characters */
+static unsigned char char_flags[256];
+static unsigned char base_reverse[256];
+
+/* initialise the flags table
+
+ we allow only a very restricted set of characters as 'ascii' in this
+ mangling backend. This isn't a significant problem as modern clients
+ use the 'long' filenames anyway, and those don't have these
+ restrictions.
+*/
+static void init_tables(void)
+{
+ int i;
+
+ memset(char_flags, 0, sizeof(char_flags));
+
+ for (i=1;i<128;i++) {
+ if (i <= 0x1f) {
+ /* Control characters. */
+ char_flags[i] |= FLAG_ILLEGAL;
+ }
+
+ if ((i >= '0' && i <= '9') ||
+ (i >= 'a' && i <= 'z') ||
+ (i >= 'A' && i <= 'Z')) {
+ char_flags[i] |= (FLAG_ASCII | FLAG_BASECHAR);
+ }
+ if (strchr("_-$~", i)) {
+ char_flags[i] |= FLAG_ASCII;
+ }
+
+ if (strchr("*\\/?<>|\":", i)) {
+ char_flags[i] |= FLAG_ILLEGAL;
+ }
+
+ if (strchr("*?\"<>", i)) {
+ char_flags[i] |= FLAG_WILDCARD;
+ }
+ }
+
+ memset(base_reverse, 0, sizeof(base_reverse));
+ for (i=0;i<36;i++) {
+ base_reverse[(unsigned char)base_forward(i)] = i;
+ }
+
+ /* fill in the reserved names flags. These are used as a very
+ fast filter for finding possible DOS reserved filenames */
+ for (i=0; reserved_names[i]; i++) {
+ unsigned char c1, c2, c3, c4;
+
+ c1 = (unsigned char)reserved_names[i][0];
+ c2 = (unsigned char)reserved_names[i][1];
+ c3 = (unsigned char)reserved_names[i][2];
+ c4 = (unsigned char)reserved_names[i][3];
+
+ char_flags[c1] |= FLAG_POSSIBLE1;
+ char_flags[c2] |= FLAG_POSSIBLE2;
+ char_flags[c3] |= FLAG_POSSIBLE3;
+ char_flags[c4] |= FLAG_POSSIBLE4;
+ char_flags[tolower_m(c1)] |= FLAG_POSSIBLE1;
+ char_flags[tolower_m(c2)] |= FLAG_POSSIBLE2;
+ char_flags[tolower_m(c3)] |= FLAG_POSSIBLE3;
+ char_flags[tolower_m(c4)] |= FLAG_POSSIBLE4;
+
+ char_flags[(unsigned char)'.'] |= FLAG_POSSIBLE4;
+ }
+
+#if 0
+ DEBUG(10, ("char_flags\n"));
+ dump_data(10, char_flags, sizeof(char_flags));
+
+ DEBUG(10, ("base_reverse\n"));
+ dump_data(10, base_reverse, sizeof(base_reverse));
+#endif
+}
+
+#else /* DYNAMIC_MANGLE_TABLES */
+
+/*
+ * These tables were initialized by a single run of the above
+ * init_tables() routine, dumping the tables and a simple emacs macro.
+ *
+ * Technically we could leave out the 0's at the end of the array
+ * initializers, but I'll leave it in: less surprise.
+ */
+
+static const uint8_t char_flags[256] = {
+ 0x80, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+ 0x00, 0x00, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x0C, 0x00, 0x00, 0x02, 0x80, 0x04,
+ 0x03, 0x83, 0x83, 0x83, 0x83, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x04, 0x00, 0x0C, 0x00, 0x0C, 0x0C,
+ 0x00, 0x13, 0x03, 0x53, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x83, 0x53, 0x43, 0x53, 0x23,
+ 0x33, 0x03, 0x23, 0x03, 0x43, 0x23, 0x03, 0x03,
+ 0x43, 0x03, 0x03, 0x00, 0x04, 0x00, 0x00, 0x02,
+ 0x00, 0x13, 0x03, 0x53, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x83, 0x53, 0x43, 0x53, 0x23,
+ 0x33, 0x03, 0x23, 0x03, 0x43, 0x23, 0x03, 0x03,
+ 0x43, 0x03, 0x03, 0x00, 0x04, 0x00, 0x02, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t base_reverse[256] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20,
+ 0x21, 0x22, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+#endif /* DYNAMIC_MANGLE_TABLES */
+
+/*
+ hash a string of the specified length. The string does not need to be
+ null terminated
+
+ this hash needs to be fast with a low collision rate (what hash doesn't?)
+*/
+static unsigned int mangle_hash(const char *key, unsigned int length)
+{
+ unsigned int value;
+ unsigned int i;
+ fstring str;
+
+ /* we have to uppercase here to ensure that the mangled name
+ doesn't depend on the case of the long name. Note that this
+ is the only place where we need to use a multi-byte string
+ function */
+ length = MIN(length,sizeof(fstring)-1);
+ strncpy(str, key, length);
+ str[length] = 0;
+ (void)strupper_m(str);
+
+ /* the length of a multi-byte string can change after a strupper_m */
+ length = strlen(str);
+
+ /* Set the initial value from the key size. */
+ for (value = FNV1_INIT, i=0; i < length; i++) {
+ value *= (unsigned int)FNV1_PRIME;
+ value ^= (unsigned int)(str[i]);
+ }
+
+ /* note that we force it to a 31 bit hash, to keep within the limits
+ of the 36^6 mangle space */
+ return value & ~0x80000000;
+}
+
+/*
+ insert an entry into the prefix cache. The string might not be null
+ terminated */
+static void cache_insert(const char *prefix, int length, unsigned int hash)
+{
+ char *str = SMB_STRNDUP(prefix, length);
+
+ if (str == NULL) {
+ return;
+ }
+
+ memcache_add(smbd_memcache(), MANGLE_HASH2_CACHE,
+ data_blob_const(&hash, sizeof(hash)),
+ data_blob_const(str, length+1));
+ SAFE_FREE(str);
+}
+
+/*
+ lookup an entry in the prefix cache. Return NULL if not found.
+*/
+static char *cache_lookup(TALLOC_CTX *mem_ctx, unsigned int hash)
+{
+ DATA_BLOB value;
+
+ if (!memcache_lookup(smbd_memcache(), MANGLE_HASH2_CACHE,
+ data_blob_const(&hash, sizeof(hash)), &value)) {
+ return NULL;
+ }
+
+ SMB_ASSERT((value.length > 0)
+ && (value.data[value.length-1] == '\0'));
+
+ return talloc_strdup(mem_ctx, (char *)value.data);
+}
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+ */
+static bool is_mangled_component(const char *name, size_t len)
+{
+ unsigned int i;
+
+ M_DEBUG(10,("is_mangled_component %s (len %lu) ?\n", name, (unsigned long)len));
+
+ /* check the length */
+ if (len > 12 || len < 8)
+ return False;
+
+ /* the best distinguishing characteristic is the ~ */
+ if (name[6] != '~')
+ return False;
+
+ /* check extension */
+ if (len > 8) {
+ if (name[8] != '.')
+ return False;
+ for (i=9; name[i] && i < len; i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return False;
+ }
+ }
+ }
+
+ /* check lead characters */
+ for (i=0;i<mangle_prefix;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_ASCII)) {
+ return False;
+ }
+ }
+
+ /* check rest of hash */
+ if (! FLAG_CHECK(name[7], FLAG_BASECHAR)) {
+ return False;
+ }
+ for (i=mangle_prefix;i<6;i++) {
+ if (! FLAG_CHECK(name[i], FLAG_BASECHAR)) {
+ return False;
+ }
+ }
+
+ M_DEBUG(10,("is_mangled_component %s (len %lu) -> yes\n", name, (unsigned long)len));
+
+ return True;
+}
+
+
+
+/*
+ determine if a string is possibly in a mangled format, ignoring
+ case
+
+ In this algorithm, mangled names use only pure ascii characters (no
+ multi-byte) so we can avoid doing a UCS2 conversion
+
+ NOTE! This interface must be able to handle a path with unix
+ directory separators. It should return true if any component is
+ mangled
+ */
+static bool is_mangled(const char *name, const struct share_params *parm)
+{
+ const char *p;
+ const char *s;
+
+ M_DEBUG(10,("is_mangled %s ?\n", name));
+
+ for (s=name; (p=strchr(s, '/')); s=p+1) {
+ if (is_mangled_component(s, PTR_DIFF(p, s))) {
+ return True;
+ }
+ }
+
+ /* and the last part ... */
+ return is_mangled_component(s,strlen(s));
+}
+
+
+/*
+ see if a filename is an allowable 8.3 name to return to the client.
+ Note this is not testing if this is a valid Samba mangled name, so
+ the rules are different for is_mangled.
+
+ we are only going to allow ascii characters in 8.3 names, as this
+ simplifies things greatly (it means that we know the string won't
+ get larger when converted from UNIX to DOS formats)
+*/
+
+static const char force_shortname_chars[] = " +,[];=";
+
+static bool is_8_3(const char *name, bool check_case, bool allow_wildcards, const struct share_params *p)
+{
+ int len, i;
+ char *dot_p;
+
+ /* as a special case, the names '.' and '..' are allowable 8.3 names */
+ if (ISDOT(name) || (ISDOTDOT(name))) {
+ return true;
+ }
+
+ /* the simplest test is on the overall length of the
+ filename. Note that we deliberately use the ascii string
+ length (not the multi-byte one) as it is faster, and gives us
+ the result we need in this case. Using strlen_m would not
+ only be slower, it would be incorrect */
+ len = strlen(name);
+ if (len > 12)
+ return False;
+
+ /* find the '.'. Note that once again we use the non-multibyte
+ function */
+ dot_p = strchr(name, '.');
+
+ if (!dot_p) {
+ /* if the name doesn't contain a '.' then its length
+ must be less than 8 */
+ if (len > 8) {
+ return False;
+ }
+ } else {
+ int prefix_len, suffix_len;
+
+ /* if it does contain a dot then the prefix must be <=
+ 8 and the suffix <= 3 in length */
+ prefix_len = PTR_DIFF(dot_p, name);
+ suffix_len = len - (prefix_len+1);
+
+ if (prefix_len > 8 || suffix_len > 3 || suffix_len == 0) {
+ return False;
+ }
+
+ /* a 8.3 name cannot contain more than 1 '.' */
+ if (strchr(dot_p+1, '.')) {
+ return False;
+ }
+ }
+
+ /* the length are all OK. Now check to see if the characters themselves are OK */
+ for (i=0; name[i]; i++) {
+ if (FLAG_CHECK(name[i], FLAG_ILLEGAL)) {
+ return false;
+ }
+ /* note that we may allow wildcard petterns! */
+ if (!allow_wildcards && FLAG_CHECK(name[i], FLAG_WILDCARD)) {
+ return false;
+ }
+ if (((unsigned char)name[i]) > 0x7e) {
+ return false;
+ }
+ if (strchr(force_shortname_chars, name[i])) {
+ return false;
+ }
+ }
+
+ /* it is a good 8.3 name */
+ return True;
+}
+
+
+/*
+ reset the mangling cache on a smb.conf reload. This only really makes sense for
+ mangling backends that have parameters in smb.conf, and as this backend doesn't
+ this is a NULL operation
+*/
+static void mangle_reset(void)
+{
+ /* noop */
+}
+
+
+/*
+ try to find a 8.3 name in the cache, and if found then
+ replace the string with the original long name.
+*/
+static bool lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *name,
+ char **pp_out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ unsigned int hash, multiplier;
+ unsigned int i;
+ char *prefix;
+ char extension[4];
+
+ *pp_out = NULL;
+
+ /* make sure that this is a mangled name from this cache */
+ if (!is_mangled(name, p)) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> not mangled\n", name));
+ return False;
+ }
+
+ /* we need to extract the hash from the 8.3 name */
+ hash = base_reverse[(unsigned char)name[7]];
+ for (multiplier=36, i=5;i>=mangle_prefix;i--) {
+ unsigned int v = base_reverse[(unsigned char)name[i]];
+ hash += multiplier * v;
+ multiplier *= 36;
+ }
+
+ /* now look in the prefix cache for that hash */
+ prefix = cache_lookup(ctx, hash);
+ if (!prefix) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %08X -> not found\n",
+ name, hash));
+ return False;
+ }
+
+ /* we found it - construct the full name */
+ if (name[8] == '.') {
+ strncpy(extension, name+9, 3);
+ extension[3] = 0;
+ } else {
+ extension[0] = 0;
+ }
+
+ if (extension[0]) {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %s.%s\n",
+ name, prefix, extension));
+ *pp_out = talloc_asprintf(ctx, "%s.%s", prefix, extension);
+ } else {
+ M_DEBUG(10,("lookup_name_from_8_3: %s -> %s\n", name, prefix));
+ *pp_out = talloc_strdup(ctx, prefix);
+ }
+
+ TALLOC_FREE(prefix);
+
+ if (!*pp_out) {
+ M_DEBUG(0,("talloc_fail\n"));
+ return False;
+ }
+
+ return True;
+}
+
+/*
+ look for a DOS reserved name
+*/
+static bool is_reserved_name(const char *name)
+{
+ if (FLAG_CHECK(name[0], FLAG_POSSIBLE1) &&
+ FLAG_CHECK(name[1], FLAG_POSSIBLE2) &&
+ FLAG_CHECK(name[2], FLAG_POSSIBLE3) &&
+ FLAG_CHECK(name[3], FLAG_POSSIBLE4)) {
+ /* a likely match, scan the lot */
+ int i;
+ for (i=0; reserved_names[i]; i++) {
+ int len = strlen(reserved_names[i]);
+ /* note that we match on COM1 as well as COM1.foo */
+ if (strnequal(name, reserved_names[i], len) &&
+ (name[len] == '.' || name[len] == 0)) {
+ return True;
+ }
+ }
+ }
+
+ return False;
+}
+
+/*
+ See if a filename is a legal long filename.
+ A filename ending in a '.' is not legal unless it's "." or "..". JRA.
+ A filename ending in ' ' is not legal either. See bug id #2769.
+*/
+
+static bool is_legal_name(const char *name)
+{
+ const char *dot_pos = NULL;
+ bool alldots = True;
+ size_t numdots = 0;
+
+ while (*name) {
+ if (((unsigned int)name[0]) > 128 && (name[1] != 0)) {
+ /* Possible start of mb character. */
+ size_t size = 0;
+ (void)next_codepoint(name, &size);
+ /*
+ * Note that we're only looking for multibyte
+ * encoding here. No encoding with a length > 1
+ * contains invalid characters.
+ */
+ if (size > 1) {
+ /* Was a mb string. */
+ name += size;
+ continue;
+ }
+ }
+
+ if (FLAG_CHECK(name[0], FLAG_ILLEGAL)) {
+ return False;
+ }
+ if (name[0] == '.') {
+ dot_pos = name;
+ numdots++;
+ } else {
+ alldots = False;
+ }
+ if ((name[0] == ' ') && (name[1] == '\0')) {
+ /* Can't end in ' ' */
+ return False;
+ }
+ name++;
+ }
+
+ if (dot_pos) {
+ if (alldots && (numdots == 1 || numdots == 2))
+ return True; /* . or .. is a valid name */
+
+ /* A valid long name cannot end in '.' */
+ if (dot_pos[1] == '\0')
+ return False;
+ }
+ return True;
+}
+
+static bool must_mangle(const char *name,
+ const struct share_params *p)
+{
+ if (is_reserved_name(name)) {
+ return True;
+ }
+ return !is_legal_name(name);
+}
+
+/*
+ the main forward mapping function, which converts a long filename to
+ a 8.3 name
+
+ if cache83 is not set then we don't cache the result
+
+*/
+static bool hash2_name_to_8_3(const char *name,
+ char new_name[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ char *dot_p;
+ char lead_chars[7];
+ char extension[4];
+ unsigned int extension_length, i;
+ unsigned int prefix_len;
+ unsigned int hash, v;
+
+ /* reserved names are handled specially */
+ if (!is_reserved_name(name)) {
+ /* if the name is already a valid 8.3 name then we don't need to
+ * change anything */
+ if (is_legal_name(name) && is_8_3(name, False, False, p)) {
+ strlcpy(new_name, name, 13);
+ return True;
+ }
+ }
+
+ /* find the '.' if any */
+ dot_p = strrchr(name, '.');
+
+ if (dot_p) {
+ /* if the extension contains any illegal characters or
+ is too long or zero length then we treat it as part
+ of the prefix */
+ for (i=0; i<4 && dot_p[i+1]; i++) {
+ if (! FLAG_CHECK(dot_p[i+1], FLAG_ASCII)) {
+ dot_p = NULL;
+ break;
+ }
+ }
+ if (i == 0 || i == 4) {
+ dot_p = NULL;
+ }
+ }
+
+ /* the leading characters in the mangled name is taken from
+ the first characters of the name, if they are ascii otherwise
+ '_' is used
+ */
+ for (i=0;i<mangle_prefix && name[i];i++) {
+ lead_chars[i] = name[i];
+ if (! FLAG_CHECK(lead_chars[i], FLAG_ASCII)) {
+ lead_chars[i] = '_';
+ }
+ lead_chars[i] = toupper_m(lead_chars[i]);
+ }
+ for (;i<mangle_prefix;i++) {
+ lead_chars[i] = '_';
+ }
+
+ /* the prefix is anything up to the first dot */
+ if (dot_p) {
+ prefix_len = PTR_DIFF(dot_p, name);
+ } else {
+ prefix_len = strlen(name);
+ }
+
+ /* the extension of the mangled name is taken from the first 3
+ ascii chars after the dot */
+ extension_length = 0;
+ if (dot_p) {
+ for (i=1; extension_length < 3 && dot_p[i]; i++) {
+ char c = dot_p[i];
+ if (FLAG_CHECK(c, FLAG_ASCII)) {
+ extension[extension_length++] =
+ toupper_m(c);
+ }
+ }
+ }
+
+ /* find the hash for this prefix */
+ v = hash = mangle_hash(name, prefix_len);
+
+ /* now form the mangled name. */
+ for (i=0;i<mangle_prefix;i++) {
+ new_name[i] = lead_chars[i];
+ }
+ new_name[7] = base_forward(v % 36);
+ new_name[6] = '~';
+ for (i=5; i>=mangle_prefix; i--) {
+ v = v / 36;
+ new_name[i] = base_forward(v % 36);
+ }
+
+ /* add the extension */
+ if (extension_length) {
+ new_name[8] = '.';
+ memcpy(&new_name[9], extension, extension_length);
+ new_name[9+extension_length] = 0;
+ } else {
+ new_name[8] = 0;
+ }
+
+ if (cache83) {
+ /* put it in the cache */
+ cache_insert(name, prefix_len, hash);
+ }
+
+ M_DEBUG(10,("hash2_name_to_8_3: %s -> %08X -> %s (cache=%d)\n",
+ name, hash, new_name, cache83));
+
+ return True;
+}
+
+/*
+ the following provides the abstraction layer to make it easier
+ to drop in an alternative mangling implementation */
+static const struct mangle_fns mangle_hash2_fns = {
+ mangle_reset,
+ is_mangled,
+ must_mangle,
+ is_8_3,
+ lookup_name_from_8_3,
+ hash2_name_to_8_3
+};
+
+/* return the methods for this mangling implementation */
+const struct mangle_fns *mangle_hash2_init(void)
+{
+ /* the mangle prefix can only be in the mange 1 to 6 */
+ mangle_prefix = lp_mangle_prefix();
+ if (mangle_prefix > 6) {
+ mangle_prefix = 6;
+ }
+ if (mangle_prefix < 1) {
+ mangle_prefix = 1;
+ }
+
+#if DYNAMIC_MANGLE_TABLES
+ init_tables();
+#endif
+ mangle_reset();
+
+ return &mangle_hash2_fns;
+}
+
+static void posix_mangle_reset(void)
+{;}
+
+static bool posix_is_mangled(const char *s, const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_must_mangle(const char *s, const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_is_8_3(const char *fname,
+ bool check_case,
+ bool allow_wildcards,
+ const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p)
+{
+ return False;
+}
+
+static bool posix_name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ int default_case,
+ const struct share_params *p)
+{
+ memset(out, '\0', 13);
+ return True;
+}
+
+/* POSIX paths backend - no mangle. */
+static const struct mangle_fns posix_mangle_fns = {
+ posix_mangle_reset,
+ posix_is_mangled,
+ posix_must_mangle,
+ posix_is_8_3,
+ posix_lookup_name_from_8_3,
+ posix_name_to_8_3
+};
+
+const struct mangle_fns *posix_mangle_init(void)
+{
+ return &posix_mangle_fns;
+}
diff --git a/source3/smbd/msdfs.c b/source3/smbd/msdfs.c
new file mode 100644
index 0000000..4a322d1
--- /dev/null
+++ b/source3/smbd/msdfs.c
@@ -0,0 +1,1774 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ MSDFS services for Samba
+ Copyright (C) Shirish Kalele 2000
+ Copyright (C) Jeremy Allison 2007
+ Copyright (C) Robin McCorkell 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#define DBGC_CLASS DBGC_MSDFS
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "msdfs.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "lib/param/loadparm.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/ndr_dfsblobs.h"
+#include "lib/tsocket/tsocket.h"
+#include "lib/global_contexts.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/**********************************************************************
+ Parse a DFS pathname of the form(s)
+
+ \hostname\service - self referral
+ \hostname\service\remainingpath - Windows referral path
+
+ FIXME! Should we also parse:
+ \hostname\service/remainingpath - POSIX referral path
+ as currently nothing uses this ?
+
+ into the dfs_path components. Strict form.
+
+ Checks DFS path starts with separator.
+ Checks hostname is ours.
+ Ensures servicename (share) is sent, and
+ if so, terminates the name or is followed by
+ \pathname.
+
+ If returned, remainingpath is untouched. Caller must call
+ check_path_syntax() on it.
+
+ Called by all non-fileserver processing (DFS RPC, FSCTL_DFS_GET_REFERRALS)
+ etc. Errors out on any inconsistency in the path.
+**********************************************************************/
+
+static NTSTATUS parse_dfs_path_strict(TALLOC_CTX *ctx,
+ const char *pathname,
+ char **_hostname,
+ char **_servicename,
+ char **_remaining_path)
+{
+ char *pathname_local = NULL;
+ char *p = NULL;
+ const char *hostname = NULL;
+ const char *servicename = NULL;
+ const char *reqpath = NULL;
+ bool my_hostname = false;
+ NTSTATUS status;
+
+ DBG_DEBUG("path = |%s|\n", pathname);
+
+ pathname_local = talloc_strdup(talloc_tos(), pathname);
+ if (pathname_local == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ /*
+ * parse_dfs_path_strict() is called from
+ * get_referred_path() and create_junction()
+ * which use Windows DFS paths of \server\share.
+ */
+
+ /*
+ * Strict DFS paths *must* start with the
+ * path separator '\\'.
+ */
+
+ if (pathname_local[0] != '\\') {
+ DBG_ERR("path %s doesn't start with \\\n",
+ pathname_local);
+ status = NT_STATUS_NOT_FOUND;
+ goto out;
+ }
+
+ /* Now tokenize. */
+ /* Parse out hostname. */
+ p = strchr(pathname_local + 1, '\\');
+ if (p == NULL) {
+ DBG_ERR("can't parse hostname from path %s\n",
+ pathname_local);
+ status = NT_STATUS_NOT_FOUND;
+ goto out;
+ }
+ *p = '\0';
+ hostname = &pathname_local[1];
+
+ DBG_DEBUG("hostname: %s\n", hostname);
+
+ /* Is this really our hostname ? */
+ my_hostname = is_myname_or_ipaddr(hostname);
+ if (!my_hostname) {
+ DBG_ERR("Hostname %s is not ours.\n",
+ hostname);
+ status = NT_STATUS_NOT_FOUND;
+ goto out;
+ }
+
+ servicename = p + 1;
+
+ /*
+ * Find the end of servicename by looking for
+ * a directory separator character. The character
+ * should be '\\' for a Windows path.
+ * If there is no separator, then this is a self-referral
+ * of "\server\share".
+ */
+
+ p = strchr(servicename, '\\');
+ if (p != NULL) {
+ *p = '\0';
+ }
+
+ DBG_DEBUG("servicename: %s\n", servicename);
+
+ if (p == NULL) {
+ /* Client sent self referral "\server\share". */
+ reqpath = "";
+ } else {
+ /* Step past the '\0' we just replaced '\\' with. */
+ reqpath = p + 1;
+ }
+
+ DBG_DEBUG("rest of the path: %s\n", reqpath);
+
+ if (_hostname != NULL) {
+ *_hostname = talloc_strdup(ctx, hostname);
+ if (*_hostname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ if (_servicename != NULL) {
+ *_servicename = talloc_strdup(ctx, servicename);
+ if (*_servicename == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ if (_remaining_path != NULL) {
+ *_remaining_path = talloc_strdup(ctx, reqpath);
+ if (*_remaining_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(pathname_local);
+ return status;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer, for use in
+ applications (such as the python bindings), that do not want the
+ global working directory changed under them.
+
+ SMB_VFS_CONNECT requires root privileges.
+*********************************************************/
+
+static NTSTATUS create_conn_struct_as_root(TALLOC_CTX *ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ connection_struct **pconn,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info)
+{
+ connection_struct *conn;
+ char *connpath;
+ const char *vfs_user;
+ struct smbd_server_connection *sconn;
+ const char *servicename = lp_const_servicename(snum);
+ bool ok;
+
+ sconn = talloc_zero(ctx, struct smbd_server_connection);
+ if (sconn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sconn->ev_ctx = ev;
+ sconn->msg_ctx = msg;
+
+ conn = conn_new(sconn);
+ if (conn == NULL) {
+ TALLOC_FREE(sconn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Now we have conn, we need to make sconn a child of conn,
+ * for a proper talloc tree */
+ talloc_steal(conn, sconn);
+
+ if (snum == -1 && servicename == NULL) {
+ servicename = "Unknown Service (snum == -1)";
+ }
+
+ connpath = talloc_strdup(conn, path);
+ if (!connpath) {
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+ connpath = talloc_string_sub(conn,
+ connpath,
+ "%S",
+ servicename);
+ if (!connpath) {
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* needed for smbd_vfs_init() */
+
+ conn->params->service = snum;
+ conn->cnum = TID_FIELD_INVALID;
+
+ SMB_ASSERT(session_info != NULL);
+
+ conn->session_info = copy_session_info(conn, session_info);
+ if (conn->session_info == NULL) {
+ DBG_ERR("copy_serverinfo failed\n");
+ TALLOC_FREE(conn);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* unix_info could be NULL in session_info */
+ if (conn->session_info->unix_info != NULL) {
+ vfs_user = conn->session_info->unix_info->unix_name;
+ } else {
+ vfs_user = get_current_username();
+ }
+
+ conn_setup_case_options(conn);
+
+ set_conn_connectpath(conn, connpath);
+
+ /*
+ * New code to check if there's a share security descriptor
+ * added from NT server manager. This is done after the
+ * smb.conf checks are done as we need a uid and token. JRA.
+ *
+ */
+ share_access_check(conn->session_info->security_token,
+ servicename,
+ MAXIMUM_ALLOWED_ACCESS,
+ &conn->share_access);
+
+ if ((conn->share_access & FILE_WRITE_DATA) == 0) {
+ if ((conn->share_access & FILE_READ_DATA) == 0) {
+ /* No access, read or write. */
+ DBG_WARNING("connection to %s "
+ "denied due to security "
+ "descriptor.\n",
+ servicename);
+ conn_free(conn);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ conn->read_only = true;
+ }
+
+ if (!smbd_vfs_init(conn)) {
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DEBUG(0,("create_conn_struct: smbd_vfs_init failed.\n"));
+ conn_free(conn);
+ return status;
+ }
+
+ /* this must be the first filesystem operation that we do */
+ if (SMB_VFS_CONNECT(conn, servicename, vfs_user) < 0) {
+ DEBUG(0,("VFS connect failed!\n"));
+ conn_free(conn);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ ok = canonicalize_connect_path(conn);
+ if (!ok) {
+ DBG_ERR("Failed to canonicalize sharepath\n");
+ conn_free(conn);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ conn->fs_capabilities = SMB_VFS_FS_CAPABILITIES(conn, &conn->ts_res);
+ conn->tcon_done = true;
+ *pconn = talloc_move(ctx, &conn);
+
+ return NT_STATUS_OK;
+}
+
+static int conn_struct_tos_destructor(struct conn_struct_tos *c)
+{
+ if (c->oldcwd_fname != NULL) {
+ vfs_ChDir(c->conn, c->oldcwd_fname);
+ TALLOC_FREE(c->oldcwd_fname);
+ }
+ SMB_VFS_DISCONNECT(c->conn);
+ conn_free(c->conn);
+ return 0;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer, for use in
+ applications (such as the python bindings), that do not want the
+ global working directory changed under them.
+
+ SMB_VFS_CONNECT requires root privileges.
+ This temporary uses become_root() and unbecome_root().
+
+ But further impersonation has to be cone by the caller.
+*********************************************************/
+NTSTATUS create_conn_struct_tos(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c)
+{
+ struct conn_struct_tos *c = NULL;
+ struct tevent_context *ev = NULL;
+ NTSTATUS status;
+
+ *_c = NULL;
+
+ c = talloc_zero(talloc_tos(), struct conn_struct_tos);
+ if (c == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ev = samba_tevent_context_init(c);
+ if (ev == NULL) {
+ TALLOC_FREE(c);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ become_root();
+ status = create_conn_struct_as_root(c,
+ ev,
+ msg,
+ &c->conn,
+ snum,
+ path,
+ session_info);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(c);
+ return status;
+ }
+
+ talloc_set_destructor(c, conn_struct_tos_destructor);
+
+ *_c = c;
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer.
+ Note: this performs a vfs connect and CHANGES CWD !!!! JRA.
+
+ See also the comment for create_conn_struct_tos() above!
+
+ The CWD change is reverted by the destructor of
+ conn_struct_tos when the current talloc_tos() is destroyed.
+*********************************************************/
+NTSTATUS create_conn_struct_tos_cwd(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c)
+{
+ struct conn_struct_tos *c = NULL;
+ struct smb_filename smb_fname_connectpath = {0};
+ NTSTATUS status;
+
+ *_c = NULL;
+
+ status = create_conn_struct_tos(msg,
+ snum,
+ path,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Windows seems to insist on doing trans2getdfsreferral() calls on
+ * the IPC$ share as the anonymous user. If we try to chdir as that
+ * user we will fail.... WTF ? JRA.
+ */
+
+ c->oldcwd_fname = vfs_GetWd(c, c->conn);
+ if (c->oldcwd_fname == NULL) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3, ("vfs_GetWd failed: %s\n", strerror(errno)));
+ TALLOC_FREE(c);
+ return status;
+ }
+
+ smb_fname_connectpath = (struct smb_filename) {
+ .base_name = c->conn->connectpath
+ };
+
+ if (vfs_ChDir(c->conn, &smb_fname_connectpath) != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_NOTICE("Can't ChDir to new conn path %s. "
+ "Error was %s\n",
+ c->conn->connectpath, strerror(errno));
+ TALLOC_FREE(c->oldcwd_fname);
+ TALLOC_FREE(c);
+ return status;
+ }
+
+ *_c = c;
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Fake up a connection struct for the VFS layer.
+ This takes an TALLOC_CTX and tevent_context from the
+ caller and the resulting connection_struct is stable
+ across the lifetime of mem_ctx and ev.
+
+ Note: this performs a vfs connect and changes cwd.
+
+ See also the comment for create_conn_struct_tos() above!
+*********************************************************/
+
+NTSTATUS create_conn_struct_cwd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ const struct auth_session_info *session_info,
+ int snum,
+ const char *path,
+ struct connection_struct **c)
+{
+ NTSTATUS status;
+
+ become_root();
+ status = create_conn_struct_as_root(mem_ctx,
+ ev,
+ msg,
+ c,
+ snum,
+ path,
+ session_info);
+ unbecome_root();
+ return status;
+}
+
+static void shuffle_strlist(char **list, int count)
+{
+ int i;
+ uint32_t r;
+ char *tmp;
+
+ for (i = count; i > 1; i--) {
+ r = generate_random() % i;
+
+ tmp = list[i-1];
+ list[i-1] = list[r];
+ list[r] = tmp;
+ }
+}
+
+/**********************************************************************
+ Parse the contents of a symlink to verify if it is an msdfs referral
+ A valid referral is of the form:
+
+ msdfs:server1\share1,server2\share2
+ msdfs:server1\share1\pathname,server2\share2\pathname
+ msdfs:server1/share1,server2/share2
+ msdfs:server1/share1/pathname,server2/share2/pathname.
+
+ Note that the alternate paths returned here must be of the canonicalized
+ form:
+
+ \server\share or
+ \server\share\path\to\file,
+
+ even in posix path mode. This is because we have no knowledge if the
+ server we're referring to understands posix paths.
+ **********************************************************************/
+
+bool parse_msdfs_symlink(TALLOC_CTX *ctx,
+ bool shuffle_referrals,
+ const char *target,
+ struct referral **ppreflist,
+ size_t *prefcount)
+{
+ char *temp = NULL;
+ char *prot;
+ char **alt_path = NULL;
+ size_t count = 0, i;
+ struct referral *reflist = NULL;
+ char *saveptr;
+
+ temp = talloc_strdup(ctx, target);
+ if (!temp) {
+ return false;
+ }
+ prot = strtok_r(temp, ":", &saveptr);
+ if (!prot) {
+ DEBUG(0,("parse_msdfs_symlink: invalid path !\n"));
+ TALLOC_FREE(temp);
+ return false;
+ }
+
+ alt_path = talloc_array(ctx, char *, MAX_REFERRAL_COUNT);
+ if (!alt_path) {
+ TALLOC_FREE(temp);
+ return false;
+ }
+
+ /* parse out the alternate paths */
+ while((count<MAX_REFERRAL_COUNT) &&
+ ((alt_path[count] = strtok_r(NULL, ",", &saveptr)) != NULL)) {
+ count++;
+ }
+
+ /* shuffle alternate paths */
+ if (shuffle_referrals) {
+ shuffle_strlist(alt_path, count);
+ }
+
+ DBG_DEBUG("count=%zu\n", count);
+
+ if (count) {
+ reflist = talloc_zero_array(ctx,
+ struct referral, count);
+ if(reflist == NULL) {
+ TALLOC_FREE(temp);
+ TALLOC_FREE(alt_path);
+ return false;
+ }
+ } else {
+ reflist = NULL;
+ }
+
+ for(i=0;i<count;i++) {
+ char *p;
+
+ /* Canonicalize link target.
+ * Replace all /'s in the path by a \ */
+ string_replace(alt_path[i], '/', '\\');
+
+ /* Remove leading '\\'s */
+ p = alt_path[i];
+ while (*p && (*p == '\\')) {
+ p++;
+ }
+
+ reflist[i].alternate_path = talloc_asprintf(reflist,
+ "\\%s",
+ p);
+ if (!reflist[i].alternate_path) {
+ TALLOC_FREE(temp);
+ TALLOC_FREE(alt_path);
+ TALLOC_FREE(reflist);
+ return false;
+ }
+
+ reflist[i].proximity = 0;
+ reflist[i].ttl = REFERRAL_TTL;
+ DBG_DEBUG("Created alt path: %s\n",
+ reflist[i].alternate_path);
+ }
+
+ if (ppreflist != NULL) {
+ *ppreflist = reflist;
+ } else {
+ TALLOC_FREE(reflist);
+ }
+ if (prefcount != NULL) {
+ *prefcount = count;
+ }
+ TALLOC_FREE(temp);
+ TALLOC_FREE(alt_path);
+ return true;
+}
+
+/**********************************************************************
+ Returns true if the unix path is a valid msdfs symlink.
+**********************************************************************/
+
+bool is_msdfs_link(struct files_struct *dirfsp,
+ struct smb_filename *atname)
+{
+ NTSTATUS status = SMB_VFS_READ_DFS_PATHAT(dirfsp->conn,
+ talloc_tos(),
+ dirfsp,
+ atname,
+ NULL,
+ NULL);
+ return (NT_STATUS_IS_OK(status));
+}
+
+/*****************************************************************
+ Used by other functions to decide if a dfs path is remote,
+ and to get the list of referred locations for that remote path.
+
+ consumedcntp: how much of the dfs path is being redirected. the client
+ should try the remaining path on the redirected server.
+*****************************************************************/
+
+static NTSTATUS dfs_path_lookup(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *dfspath, /* Incoming complete dfs path */
+ const char *reqpath, /* Parsed out remaining path. */
+ uint32_t ucf_flags,
+ size_t *consumedcntp,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ NTSTATUS status;
+ struct smb_filename *parent_smb_fname = NULL;
+ struct smb_filename *smb_fname_rel = NULL;
+ NTTIME twrp = 0;
+ char *local_pathname = NULL;
+ char *last_component = NULL;
+ char *atname = NULL;
+ size_t removed_components = 0;
+ bool posix = (ucf_flags & UCF_POSIX_PATHNAMES);
+ char *p = NULL;
+ char *canon_dfspath = NULL;
+
+ DBG_DEBUG("Conn path = %s reqpath = %s\n", conn->connectpath, reqpath);
+
+ local_pathname = talloc_strdup(ctx, reqpath);
+ if (local_pathname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* We know reqpath isn't a DFS path. */
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(local_pathname, &twrp);
+ ucf_flags &= ~UCF_GMT_PATHNAME;
+ }
+
+ /*
+ * We should have been given a DFS path to resolve.
+ * This should return NT_STATUS_PATH_NOT_COVERED.
+ *
+ * Do a pathname walk, stripping off components
+ * until we get NT_STATUS_OK instead of
+ * NT_STATUS_PATH_NOT_COVERED.
+ *
+ * Fail on any other error.
+ */
+
+ for (;;) {
+ TALLOC_CTX *frame = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname_walk = NULL;
+
+ TALLOC_FREE(parent_smb_fname);
+
+ /*
+ * Use a local stackframe as filename_convert_dirfsp()
+ * opens handles on the last two components in the path.
+ * Allow these to be freed as we step back through
+ * the local_pathname.
+ */
+ frame = talloc_stackframe();
+ status = filename_convert_dirfsp(frame,
+ conn,
+ local_pathname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname_walk);
+ /* If we got a name, save it. */
+ if (smb_fname_walk != NULL) {
+ parent_smb_fname = talloc_move(ctx, &smb_fname_walk);
+ }
+ TALLOC_FREE(frame);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_PATH_NOT_COVERED)) {
+ /*
+ * For any other status than NT_STATUS_PATH_NOT_COVERED
+ * (including NT_STATUS_OK) we exit the walk.
+ * If it's an error we catch it outside the loop.
+ */
+ break;
+ }
+
+ /* Step back one component and save it off as last_component. */
+ TALLOC_FREE(last_component);
+ p = strrchr(local_pathname, '/');
+ if (p == NULL) {
+ /*
+ * We removed all components.
+ * Go around once more to make
+ * sure we can open the root '\0'.
+ */
+ last_component = talloc_strdup(ctx, local_pathname);
+ *local_pathname = '\0';
+ } else {
+ last_component = talloc_strdup(ctx, p+1);
+ *p = '\0';
+ }
+ if (last_component == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /* Integer wrap check. */
+ if (removed_components + 1 < removed_components) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ removed_components++;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfspath = %s. reqpath = %s. Error %s.\n",
+ dfspath,
+ reqpath,
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (parent_smb_fname->fsp == NULL) {
+ /* Unable to open parent. */
+ DBG_DEBUG("dfspath = %s. reqpath = %s. "
+ "Unable to open parent directory (%s).\n",
+ dfspath,
+ reqpath,
+ smb_fname_str_dbg(parent_smb_fname));
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+
+ if (removed_components == 0) {
+ /*
+ * We never got NT_STATUS_PATH_NOT_COVERED.
+ * There was no DFS redirect.
+ */
+ DBG_DEBUG("dfspath = %s. reqpath = %s. "
+ "No removed components.\n",
+ dfspath,
+ reqpath);
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+
+ /*
+ * One of the removed_components was the MSDFS link
+ * at the end. We need to count this in the resolved
+ * path below, so remove one from removed_components.
+ */
+ removed_components--;
+
+ /*
+ * Now parent_smb_fname->fsp is the parent directory dirfsp,
+ * last_component is the untranslated MS-DFS link name.
+ * Search for it in the parent directory to get the real
+ * filename on disk.
+ */
+ status = get_real_filename_at(parent_smb_fname->fsp,
+ last_component,
+ ctx,
+ &atname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfspath = %s. reqpath = %s "
+ "get_real_filename_at(%s, %s) error (%s)\n",
+ dfspath,
+ reqpath,
+ smb_fname_str_dbg(parent_smb_fname),
+ last_component,
+ nt_errstr(status));
+ goto out;
+ }
+
+ smb_fname_rel = synthetic_smb_fname(ctx,
+ atname,
+ NULL,
+ NULL,
+ twrp,
+ posix ? SMB_FILENAME_POSIX_PATH : 0);
+ if (smb_fname_rel == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* Get the referral to return. */
+ status = SMB_VFS_READ_DFS_PATHAT(conn,
+ ctx,
+ parent_smb_fname->fsp,
+ smb_fname_rel,
+ ppreflist,
+ preferral_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dfspath = %s. reqpath = %s. "
+ "SMB_VFS_READ_DFS_PATHAT(%s, %s) error (%s)\n",
+ dfspath,
+ reqpath,
+ smb_fname_str_dbg(parent_smb_fname),
+ smb_fname_str_dbg(smb_fname_rel),
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * Now we must work out how much of the
+ * given pathname we consumed.
+ */
+ canon_dfspath = talloc_strdup(ctx, dfspath);
+ if (!canon_dfspath) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /* Canonicalize the raw dfspath. */
+ string_replace(canon_dfspath, '\\', '/');
+
+ /*
+ * reqpath comes out of parse_dfs_path(), so it has
+ * no trailing backslash. Make sure that canon_dfspath hasn't either.
+ */
+ trim_char(canon_dfspath, 0, '/');
+
+ DBG_DEBUG("Unconsumed path: %s\n", canon_dfspath);
+
+ while (removed_components > 0) {
+ p = strrchr(canon_dfspath, '/');
+ if (p != NULL) {
+ *p = '\0';
+ }
+ removed_components--;
+ if (p == NULL && removed_components != 0) {
+ DBG_ERR("Component mismatch. path = %s, "
+ "%zu components left\n",
+ canon_dfspath,
+ removed_components);
+ status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto out;
+ }
+ }
+ *consumedcntp = strlen(canon_dfspath);
+ DBG_DEBUG("Path consumed: %s (%zu)\n", canon_dfspath, *consumedcntp);
+ status = NT_STATUS_OK;
+
+ out:
+
+ TALLOC_FREE(parent_smb_fname);
+ TALLOC_FREE(local_pathname);
+ TALLOC_FREE(last_component);
+ TALLOC_FREE(atname);
+ TALLOC_FREE(smb_fname_rel);
+ TALLOC_FREE(canon_dfspath);
+ return status;
+}
+
+/**********************************************************************
+ Return a self referral.
+**********************************************************************/
+
+static NTSTATUS self_ref(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn,
+ size_t *consumedcntp,
+ bool *self_referralp)
+{
+ struct referral *ref;
+
+ *self_referralp = True;
+
+ jucn->referral_count = 1;
+ if((ref = talloc_zero(ctx, struct referral)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ref->alternate_path = talloc_strdup(ctx, dfs_path);
+ if (!ref->alternate_path) {
+ TALLOC_FREE(ref);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ref->proximity = 0;
+ ref->ttl = REFERRAL_TTL;
+ jucn->referral_list = ref;
+ *consumedcntp = strlen(dfs_path);
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ Gets valid referrals for a dfs path and fills up the
+ junction_map structure.
+**********************************************************************/
+
+NTSTATUS get_referred_path(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ const char *dfs_path,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct junction_map *jucn,
+ size_t *consumedcntp,
+ bool *self_referralp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct conn_struct_tos *c = NULL;
+ struct connection_struct *conn = NULL;
+ char *servicename = NULL;
+ char *reqpath = NULL;
+ int snum;
+ NTSTATUS status = NT_STATUS_NOT_FOUND;
+
+ *self_referralp = False;
+
+ status = parse_dfs_path_strict(
+ frame,
+ dfs_path,
+ NULL, /* hostname */
+ &servicename,
+ &reqpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* Path referrals are always non-POSIX. */
+ status = check_path_syntax(reqpath, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ jucn->service_name = talloc_strdup(ctx, servicename);
+ jucn->volume_name = talloc_strdup(ctx, reqpath);
+ if (!jucn->service_name || !jucn->volume_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Verify the share is a dfs root */
+ snum = lp_servicenumber(jucn->service_name);
+ if(snum < 0) {
+ char *service_name = NULL;
+ if ((snum = find_service(ctx, jucn->service_name, &service_name)) < 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NOT_FOUND;
+ }
+ if (!service_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ TALLOC_FREE(jucn->service_name);
+ jucn->service_name = talloc_strdup(ctx, service_name);
+ if (!jucn->service_name) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (!lp_msdfs_root(snum) && (*lp_msdfs_proxy(talloc_tos(), lp_sub, snum) == '\0')) {
+ DEBUG(3,("get_referred_path: |%s| in dfs path %s is not "
+ "a dfs root.\n",
+ servicename, dfs_path));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /*
+ * Self referrals are tested with a anonymous IPC connection and
+ * a GET_DFS_REFERRAL call to \\server\share. (which means
+ * dp.reqpath[0] points to an empty string). create_conn_struct cd's
+ * into the directory and will fail if it cannot (as the anonymous
+ * user). Cope with this.
+ */
+
+ if (reqpath[0] == '\0') {
+ char *tmp;
+ struct referral *ref;
+ size_t refcount;
+
+ if (*lp_msdfs_proxy(talloc_tos(), lp_sub, snum) == '\0') {
+ TALLOC_FREE(frame);
+ return self_ref(ctx,
+ dfs_path,
+ jucn,
+ consumedcntp,
+ self_referralp);
+ }
+
+ /*
+ * It's an msdfs proxy share. Redirect to
+ * the configured target share.
+ */
+
+ tmp = talloc_asprintf(frame, "msdfs:%s",
+ lp_msdfs_proxy(frame, lp_sub, snum));
+ if (tmp == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!parse_msdfs_symlink(ctx,
+ lp_msdfs_shuffle_referrals(snum),
+ tmp,
+ &ref,
+ &refcount)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ jucn->referral_count = refcount;
+ jucn->referral_list = ref;
+ *consumedcntp = strlen(dfs_path);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ lp_path(frame, lp_sub, snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+ conn = c->conn;
+
+ /*
+ * TODO
+ *
+ * The remote and local address should be passed down to
+ * create_conn_struct_cwd.
+ */
+ if (conn->sconn->remote_address == NULL) {
+ conn->sconn->remote_address =
+ tsocket_address_copy(remote_address, conn->sconn);
+ if (conn->sconn->remote_address == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (conn->sconn->local_address == NULL) {
+ conn->sconn->local_address =
+ tsocket_address_copy(local_address, conn->sconn);
+ if (conn->sconn->local_address == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = dfs_path_lookup(ctx,
+ conn,
+ dfs_path,
+ reqpath,
+ 0, /* ucf_flags */
+ consumedcntp,
+ &jucn->referral_list,
+ &jucn->referral_count);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("No valid referrals for path %s (%s)\n",
+ dfs_path,
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/******************************************************************
+ Set up the DFS referral for the dfs pathname. This call returns
+ the amount of the path covered by this server, and where the
+ client should be redirected to. This is the meat of the
+ TRANS2_GET_DFS_REFERRAL call.
+******************************************************************/
+
+int setup_dfs_referral(connection_struct *orig_conn,
+ const char *dfs_path,
+ int max_referral_level,
+ char **ppdata, NTSTATUS *pstatus)
+{
+ char *pdata = *ppdata;
+ int reply_size = 0;
+ struct dfs_GetDFSReferral *r;
+ DATA_BLOB blob = data_blob_null;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ r = talloc_zero(talloc_tos(), struct dfs_GetDFSReferral);
+ if (r == NULL) {
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ r->in.req.max_referral_level = max_referral_level;
+ r->in.req.servername = talloc_strdup(r, dfs_path);
+ if (r->in.req.servername == NULL) {
+ talloc_free(r);
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return -1;
+ }
+
+ status = SMB_VFS_GET_DFS_REFERRALS(orig_conn, r);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(r);
+ *pstatus = status;
+ return -1;
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, r,
+ r->out.resp,
+ (ndr_push_flags_fn_t)ndr_push_dfs_referral_resp);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ TALLOC_FREE(r);
+ *pstatus = NT_STATUS_INVALID_PARAMETER;
+ return -1;
+ }
+
+ pdata = (char *)SMB_REALLOC(pdata, blob.length);
+ if(pdata == NULL) {
+ TALLOC_FREE(r);
+ DEBUG(0,("referral setup:"
+ "malloc failed for Realloc!\n"));
+ return -1;
+ }
+ *ppdata = pdata;
+ reply_size = blob.length;
+ memcpy(pdata, blob.data, blob.length);
+ TALLOC_FREE(r);
+
+ *pstatus = NT_STATUS_OK;
+ return reply_size;
+}
+
+/**********************************************************************
+ The following functions are called by the NETDFS RPC pipe functions
+ **********************************************************************/
+
+/*********************************************************************
+ Creates a junction structure from a DFS pathname
+**********************************************************************/
+
+bool create_junction(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ char *servicename = NULL;
+ char *reqpath = NULL;
+ NTSTATUS status;
+
+ status = parse_dfs_path_strict(
+ ctx,
+ dfs_path,
+ NULL,
+ &servicename,
+ &reqpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ return False;
+ }
+
+ /* Check for a non-DFS share */
+ snum = lp_servicenumber(servicename);
+
+ if(snum < 0 || !lp_msdfs_root(snum)) {
+ DEBUG(4,("create_junction: %s is not an msdfs root.\n",
+ servicename));
+ return False;
+ }
+
+ /* Junction create paths are always non-POSIX. */
+ status = check_path_syntax(reqpath, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ jucn->service_name = talloc_strdup(ctx, servicename);
+ jucn->volume_name = talloc_strdup(ctx, reqpath);
+ jucn->comment = lp_comment(ctx, lp_sub, snum);
+
+ if (!jucn->service_name || !jucn->volume_name || ! jucn->comment) {
+ return False;
+ }
+ return True;
+}
+
+/**********************************************************************
+ Forms a valid Unix pathname from the junction
+ **********************************************************************/
+
+static bool junction_to_local_path_tos(const struct junction_map *jucn,
+ struct auth_session_info *session_info,
+ char **pp_path_out,
+ connection_struct **conn_out)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct conn_struct_tos *c = NULL;
+ int snum;
+ char *path_out = NULL;
+ NTSTATUS status;
+
+ snum = lp_servicenumber(jucn->service_name);
+ if(snum < 0) {
+ return False;
+ }
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ lp_path(talloc_tos(), lp_sub, snum),
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return False;
+ }
+
+ path_out = talloc_asprintf(c,
+ "%s/%s",
+ lp_path(talloc_tos(), lp_sub, snum),
+ jucn->volume_name);
+ if (path_out == NULL) {
+ TALLOC_FREE(c);
+ return False;
+ }
+ *pp_path_out = path_out;
+ *conn_out = c->conn;
+ return True;
+}
+
+/*
+ * Create a msdfs string in Samba format we can store
+ * in a filesystem object (currently a symlink).
+ */
+
+char *msdfs_link_string(TALLOC_CTX *ctx,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ char *refpath = NULL;
+ bool insert_comma = false;
+ char *msdfs_link = NULL;
+ size_t i;
+
+ /* Form the msdfs_link contents */
+ msdfs_link = talloc_strdup(ctx, "msdfs:");
+ if (msdfs_link == NULL) {
+ goto err;
+ }
+
+ for( i= 0; i < referral_count; i++) {
+ refpath = talloc_strdup(ctx, reflist[i].alternate_path);
+
+ if (refpath == NULL) {
+ goto err;
+ }
+
+ /* Alternate paths always use Windows separators. */
+ trim_char(refpath, '\\', '\\');
+ if (*refpath == '\0') {
+ if (i == 0) {
+ insert_comma = false;
+ }
+ continue;
+ }
+ if (i > 0 && insert_comma) {
+ msdfs_link = talloc_asprintf_append_buffer(msdfs_link,
+ ",%s",
+ refpath);
+ } else {
+ msdfs_link = talloc_asprintf_append_buffer(msdfs_link,
+ "%s",
+ refpath);
+ }
+
+ if (msdfs_link == NULL) {
+ goto err;
+ }
+
+ if (!insert_comma) {
+ insert_comma = true;
+ }
+
+ TALLOC_FREE(refpath);
+ }
+
+ return msdfs_link;
+
+ err:
+
+ TALLOC_FREE(refpath);
+ TALLOC_FREE(msdfs_link);
+ return NULL;
+}
+
+bool create_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *path = NULL;
+ connection_struct *conn;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ bool ok;
+ NTSTATUS status;
+ bool ret = false;
+
+ ok = junction_to_local_path_tos(jucn, session_info, &path, &conn);
+ if (!ok) {
+ goto out;
+ }
+
+ if (!CAN_WRITE(conn)) {
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum = lp_servicenumber(jucn->service_name);
+
+ DBG_WARNING("Can't create DFS entry on read-only share %s\n",
+ lp_servicename(frame, lp_sub, snum));
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto out;
+ }
+
+ status = parent_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_DFS_PATHAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ jucn->referral_list,
+ jucn->referral_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ int retval = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ 0);
+ if (retval != 0) {
+ goto out;
+ }
+ }
+ status = SMB_VFS_CREATE_DFS_PATHAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ jucn->referral_list,
+ jucn->referral_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("SMB_VFS_CREATE_DFS_PATHAT failed "
+ "%s - Error: %s\n",
+ path,
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ ret = true;
+
+out:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+bool remove_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *path = NULL;
+ connection_struct *conn;
+ bool ret = False;
+ struct smb_filename *smb_fname;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ NTSTATUS status;
+ bool ok;
+ int retval;
+
+ ok = junction_to_local_path_tos(jucn, session_info, &path, &conn);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (!CAN_WRITE(conn)) {
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum = lp_servicenumber(jucn->service_name);
+
+ DBG_WARNING("Can't remove DFS entry on read-only share %s\n",
+ lp_servicename(frame, lp_sub, snum));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return false;
+ }
+
+ status = parent_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ retval = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ 0);
+ if (retval == 0) {
+ ret = True;
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/*********************************************************************
+ Return the number of DFS links at the root of this share.
+*********************************************************************/
+
+static size_t count_dfs_links(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ int snum)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ size_t cnt = 0;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ const char *connect_path = lp_path(frame, lp_sub, snum);
+ const char *msdfs_proxy = lp_msdfs_proxy(frame, lp_sub, snum);
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ NTSTATUS status;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+
+ if(*connect_path == '\0') {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /*
+ * Fake up a connection struct for the VFS layer.
+ */
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ connect_path,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("create_conn_struct failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return 0;
+ }
+ conn = c->conn;
+
+ /* Count a link for the msdfs root - convention */
+ cnt = 1;
+
+ /* No more links if this is an msdfs proxy. */
+ if (*msdfs_proxy != '\0') {
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto out;
+ }
+
+ /* Now enumerate all dfs links */
+ status = OpenDir(frame,
+ conn,
+ smb_fname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *smb_dname =
+ synthetic_smb_fname(frame,
+ dname,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_dname == NULL) {
+ goto out;
+ }
+ if (is_msdfs_link(dir_hnd_fetch_fsp(dir_hnd), smb_dname)) {
+ if (cnt + 1 < cnt) {
+ cnt = 0;
+ goto out;
+ }
+ cnt++;
+ }
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(smb_dname);
+ }
+
+out:
+ TALLOC_FREE(frame);
+ return cnt;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+static int form_junctions(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ int snum,
+ struct junction_map *jucn,
+ size_t jn_remain)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ size_t cnt = 0;
+ const char *dname = NULL;
+ char *talloced = NULL;
+ const char *connect_path = lp_path(frame, lp_sub, snum);
+ char *service_name = lp_servicename(frame, lp_sub, snum);
+ const char *msdfs_proxy = lp_msdfs_proxy(frame, lp_sub, snum);
+ struct conn_struct_tos *c = NULL;
+ connection_struct *conn = NULL;
+ struct referral *ref = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_Dir *dir_hnd = NULL;
+ NTSTATUS status;
+
+ if (jn_remain == 0) {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ if(*connect_path == '\0') {
+ TALLOC_FREE(frame);
+ return 0;
+ }
+
+ /*
+ * Fake up a connection struct for the VFS layer.
+ */
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ connect_path,
+ session_info,
+ &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("create_conn_struct failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return 0;
+ }
+ conn = c->conn;
+
+ /* form a junction for the msdfs root - convention
+ DO NOT REMOVE THIS: NT clients will not work with us
+ if this is not present
+ */
+ jucn[cnt].service_name = talloc_strdup(ctx,service_name);
+ jucn[cnt].volume_name = talloc_strdup(ctx, "");
+ if (!jucn[cnt].service_name || !jucn[cnt].volume_name) {
+ goto out;
+ }
+ jucn[cnt].comment = "";
+ jucn[cnt].referral_count = 1;
+
+ ref = jucn[cnt].referral_list = talloc_zero(ctx, struct referral);
+ if (jucn[cnt].referral_list == NULL) {
+ goto out;
+ }
+
+ ref->proximity = 0;
+ ref->ttl = REFERRAL_TTL;
+ if (*msdfs_proxy != '\0') {
+ ref->alternate_path = talloc_strdup(ctx,
+ msdfs_proxy);
+ } else {
+ ref->alternate_path = talloc_asprintf(ctx,
+ "\\\\%s\\%s",
+ get_local_machine_name(),
+ service_name);
+ }
+
+ if (!ref->alternate_path) {
+ goto out;
+ }
+ cnt++;
+
+ /* Don't enumerate if we're an msdfs proxy. */
+ if (*msdfs_proxy != '\0') {
+ goto out;
+ }
+
+ smb_fname = synthetic_smb_fname(frame,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto out;
+ }
+
+ /* Now enumerate all dfs links */
+ status = OpenDir(frame,
+ conn,
+ smb_fname,
+ NULL,
+ 0,
+ &dir_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ errno = map_errno_from_nt_status(status);
+ goto out;
+ }
+
+ while ((dname = ReadDirName(dir_hnd, &talloced)) != NULL) {
+ struct smb_filename *smb_dname = NULL;
+
+ if (cnt >= jn_remain) {
+ DEBUG(2, ("form_junctions: ran out of MSDFS "
+ "junction slots\n"));
+ TALLOC_FREE(talloced);
+ goto out;
+ }
+ smb_dname = synthetic_smb_fname(talloc_tos(),
+ dname,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_dname == NULL) {
+ TALLOC_FREE(talloced);
+ goto out;
+ }
+
+ status = SMB_VFS_READ_DFS_PATHAT(conn,
+ ctx,
+ conn->cwd_fsp,
+ smb_dname,
+ &jucn[cnt].referral_list,
+ &jucn[cnt].referral_count);
+
+ if (NT_STATUS_IS_OK(status)) {
+ jucn[cnt].service_name = talloc_strdup(ctx,
+ service_name);
+ jucn[cnt].volume_name = talloc_strdup(ctx, dname);
+ if (!jucn[cnt].service_name || !jucn[cnt].volume_name) {
+ TALLOC_FREE(talloced);
+ goto out;
+ }
+ jucn[cnt].comment = "";
+ cnt++;
+ }
+ TALLOC_FREE(talloced);
+ TALLOC_FREE(smb_dname);
+ }
+
+out:
+ TALLOC_FREE(frame);
+ return cnt;
+}
+
+struct junction_map *enum_msdfs_links(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ size_t *p_num_jn)
+{
+ struct junction_map *jn = NULL;
+ int i=0;
+ size_t jn_count = 0;
+ int sharecount = 0;
+
+ *p_num_jn = 0;
+ if(!lp_host_msdfs()) {
+ return NULL;
+ }
+
+ /* Ensure all the usershares are loaded. */
+ become_root();
+ load_registry_shares();
+ sharecount = load_usershare_shares(NULL, connections_snum_used);
+ unbecome_root();
+
+ for(i=0;i < sharecount;i++) {
+ if(lp_msdfs_root(i)) {
+ jn_count += count_dfs_links(ctx, session_info, i);
+ }
+ }
+ if (jn_count == 0) {
+ return NULL;
+ }
+ jn = talloc_array(ctx, struct junction_map, jn_count);
+ if (!jn) {
+ return NULL;
+ }
+ for(i=0; i < sharecount; i++) {
+ if (*p_num_jn >= jn_count) {
+ break;
+ }
+ if(lp_msdfs_root(i)) {
+ *p_num_jn += form_junctions(ctx,
+ session_info,
+ i,
+ &jn[*p_num_jn],
+ jn_count - *p_num_jn);
+ }
+ }
+ return jn;
+}
diff --git a/source3/smbd/notify.c b/source3/smbd/notify.c
new file mode 100644
index 0000000..156fbf7
--- /dev/null
+++ b/source3/smbd/notify.c
@@ -0,0 +1,964 @@
+/*
+ Unix SMB/CIFS implementation.
+ change notify handling
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Jeremy Allison 1994-1998
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/ndr_notify.h"
+#include "librpc/gen_ndr/ndr_file_id.h"
+#include "libcli/security/privileges.h"
+#include "libcli/security/security.h"
+
+struct notify_change_event {
+ struct timespec when;
+ uint32_t action;
+ const char *name;
+};
+
+struct notify_change_buf {
+ /*
+ * Filters for reinitializing after notifyd has been restarted
+ */
+ uint32_t filter;
+ uint32_t subdir_filter;
+
+ /*
+ * If no requests are pending, changes are queued here. Simple array,
+ * we only append.
+ */
+
+ uint32_t max_buffer_size;
+
+ /*
+ * num_changes == -1 means that we have got a catch-all change, when
+ * asked we just return NT_STATUS_OK without specific changes.
+ */
+ int num_changes;
+ struct notify_change_event *changes;
+
+ /*
+ * If no changes are around requests are queued here. Using a linked
+ * list, because we have to append at the end and delete from the top.
+ */
+ struct notify_change_request *requests;
+};
+
+struct notify_change_request {
+ struct notify_change_request *prev, *next;
+ struct files_struct *fsp; /* backpointer for cancel by mid */
+ struct smb_request *req;
+ uint32_t filter;
+ uint32_t max_param;
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len);
+ struct notify_mid_map *mid_map;
+ void *backend_data;
+};
+
+static void notify_fsp(files_struct *fsp, struct timespec when,
+ uint32_t action, const char *name);
+
+bool change_notify_fsp_has_changes(struct files_struct *fsp)
+{
+ if (fsp == NULL) {
+ return false;
+ }
+
+ if (fsp->notify == NULL) {
+ return false;
+ }
+
+ if (fsp->notify->num_changes == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * For NTCancel, we need to find the notify_change_request indexed by
+ * mid. Separate list here.
+ */
+
+struct notify_mid_map {
+ struct notify_mid_map *prev, *next;
+ struct notify_change_request *req;
+ uint64_t mid;
+};
+
+static bool notify_change_record_identical(struct notify_change_event *c1,
+ struct notify_change_event *c2)
+{
+ /* Note this is deliberately case sensitive. */
+ if (c1->action == c2->action &&
+ strcmp(c1->name, c2->name) == 0) {
+ return True;
+ }
+ return False;
+}
+
+static int compare_notify_change_events(const void *p1, const void *p2)
+{
+ const struct notify_change_event *e1 = p1;
+ const struct notify_change_event *e2 = p2;
+
+ return timespec_compare(&e1->when, &e2->when);
+}
+
+static bool notify_marshall_changes(int num_changes,
+ uint32_t max_offset,
+ struct notify_change_event *changes,
+ DATA_BLOB *final_blob)
+{
+ int i;
+
+ if (num_changes == -1) {
+ return false;
+ }
+
+ /*
+ * Sort the notifies by timestamp when the event happened to avoid
+ * coalescing and thus dropping events.
+ */
+
+ qsort(changes, num_changes,
+ sizeof(*changes), compare_notify_change_events);
+
+ for (i=0; i<num_changes; i++) {
+ enum ndr_err_code ndr_err;
+ struct notify_change_event *c;
+ struct FILE_NOTIFY_INFORMATION m;
+ DATA_BLOB blob;
+ uint16_t pad = 0;
+
+ /* Coalesce any identical records. */
+ while (i+1 < num_changes &&
+ notify_change_record_identical(&changes[i],
+ &changes[i+1])) {
+ i++;
+ }
+
+ c = &changes[i];
+
+ m.FileName1 = c->name;
+ m.FileNameLength = strlen_m(c->name)*2;
+ m.Action = c->action;
+
+ m._pad = data_blob_null;
+
+ /*
+ * Offset to next entry, only if there is one
+ */
+
+ if (i == (num_changes-1)) {
+ m.NextEntryOffset = 0;
+ } else {
+ if ((m.FileNameLength % 4) == 2) {
+ m._pad = data_blob_const(&pad, 2);
+ }
+ m.NextEntryOffset =
+ ndr_size_FILE_NOTIFY_INFORMATION(&m, 0);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &m,
+ (ndr_push_flags_fn_t)ndr_push_FILE_NOTIFY_INFORMATION);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(FILE_NOTIFY_INFORMATION, &m);
+ }
+
+ if (!data_blob_append(talloc_tos(), final_blob,
+ blob.data, blob.length)) {
+ data_blob_free(&blob);
+ return false;
+ }
+
+ data_blob_free(&blob);
+
+ if (final_blob->length > max_offset) {
+ /* Too much data for client. */
+ DEBUG(10, ("Client only wanted %d bytes, trying to "
+ "marshall %d bytes\n", (int)max_offset,
+ (int)final_blob->length));
+ return False;
+ }
+ }
+
+ return True;
+}
+
+/****************************************************************************
+ Setup the common parts of the return packet and send it.
+*****************************************************************************/
+
+void change_notify_reply(struct smb_request *req,
+ NTSTATUS error_code,
+ uint32_t max_param,
+ struct notify_change_buf *notify_buf,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len))
+{
+ DATA_BLOB blob = data_blob_null;
+
+ if (!NT_STATUS_IS_OK(error_code)) {
+ reply_fn(req, error_code, NULL, 0);
+ return;
+ }
+
+ if (notify_buf == NULL) {
+ reply_fn(req, NT_STATUS_OK, NULL, 0);
+ return;
+ }
+
+ max_param = MIN(max_param, notify_buf->max_buffer_size);
+
+ if (!notify_marshall_changes(notify_buf->num_changes, max_param,
+ notify_buf->changes, &blob)) {
+ /*
+ * We exceed what the client is willing to accept. Send
+ * nothing.
+ */
+ data_blob_free(&blob);
+ }
+
+ reply_fn(req, NT_STATUS_OK, blob.data, blob.length);
+
+ data_blob_free(&blob);
+
+ TALLOC_FREE(notify_buf->changes);
+ notify_buf->num_changes = 0;
+}
+
+struct notify_fsp_state {
+ struct files_struct *notified_fsp;
+ struct timespec when;
+ const struct notify_event *e;
+};
+
+static struct files_struct *notify_fsp_cb(struct files_struct *fsp,
+ void *private_data)
+{
+ struct notify_fsp_state *state = private_data;
+
+ if (fsp == state->notified_fsp) {
+ DBG_DEBUG("notify_callback called for %s\n", fsp_str_dbg(fsp));
+ notify_fsp(fsp, state->when, state->e->action, state->e->path);
+ return fsp;
+ }
+
+ return NULL;
+}
+
+void notify_callback(struct smbd_server_connection *sconn,
+ void *private_data, struct timespec when,
+ const struct notify_event *e)
+{
+ struct notify_fsp_state state = {
+ .notified_fsp = private_data, .when = when, .e = e
+ };
+ files_forall(sconn, notify_fsp_cb, &state);
+}
+
+NTSTATUS change_notify_create(struct files_struct *fsp,
+ uint32_t max_buffer_size,
+ uint32_t filter,
+ bool recursive)
+{
+ size_t len = fsp_fullbasepath(fsp, NULL, 0);
+ char fullpath[len+1];
+ NTSTATUS status = NT_STATUS_NOT_IMPLEMENTED;
+
+ /*
+ * Setting a changenotify needs READ/LIST access
+ * on the directory handle.
+ */
+ status = check_any_access_fsp(fsp, SEC_DIR_LIST);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->notify != NULL) {
+ DEBUG(1, ("change_notify_create: fsp->notify != NULL, "
+ "fname = %s\n", fsp->fsp_name->base_name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!(fsp->notify = talloc_zero(NULL, struct notify_change_buf))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->notify->filter = filter;
+ fsp->notify->subdir_filter = recursive ? filter : 0;
+ fsp->notify->max_buffer_size = max_buffer_size;
+
+ fsp_fullbasepath(fsp, fullpath, sizeof(fullpath));
+
+ /*
+ * Avoid /. at the end of the path name. notify can't deal with it.
+ */
+ if (len > 1 && fullpath[len-1] == '.' && fullpath[len-2] == '/') {
+ fullpath[len-2] = '\0';
+ }
+
+ if ((fsp->notify->filter != 0) ||
+ (fsp->notify->subdir_filter != 0)) {
+ status = notify_add(fsp->conn->sconn->notify_ctx,
+ fullpath, fsp->notify->filter,
+ fsp->notify->subdir_filter, fsp);
+ }
+
+ return status;
+}
+
+NTSTATUS change_notify_add_request(struct smb_request *req,
+ uint32_t max_param,
+ uint32_t filter, bool recursive,
+ struct files_struct *fsp,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len))
+{
+ struct notify_change_request *request = NULL;
+ struct notify_mid_map *map = NULL;
+ struct smbd_server_connection *sconn = req->sconn;
+
+ DEBUG(10, ("change_notify_add_request: Adding request for %s: "
+ "max_param = %d\n", fsp_str_dbg(fsp), (int)max_param));
+
+ if (!(request = talloc(NULL, struct notify_change_request))
+ || !(map = talloc(request, struct notify_mid_map))) {
+ TALLOC_FREE(request);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ request->mid_map = map;
+ map->req = request;
+
+ request->req = talloc_move(request, &req);
+ request->max_param = max_param;
+ request->filter = filter;
+ request->fsp = fsp;
+ request->reply_fn = reply_fn;
+ request->backend_data = NULL;
+
+ DLIST_ADD_END(fsp->notify->requests, request);
+
+ map->mid = request->req->mid;
+ DLIST_ADD(sconn->notify_mid_maps, map);
+
+ return NT_STATUS_OK;
+}
+
+static void change_notify_remove_request(struct smbd_server_connection *sconn,
+ struct notify_change_request *remove_req)
+{
+ files_struct *fsp;
+ struct notify_change_request *req;
+
+ /*
+ * Paranoia checks, the fsp referenced must must have the request in
+ * its list of pending requests
+ */
+
+ fsp = remove_req->fsp;
+ SMB_ASSERT(fsp->notify != NULL);
+
+ for (req = fsp->notify->requests; req; req = req->next) {
+ if (req == remove_req) {
+ break;
+ }
+ }
+
+ if (req == NULL) {
+ smb_panic("notify_req not found in fsp's requests");
+ }
+
+ DLIST_REMOVE(fsp->notify->requests, req);
+ DLIST_REMOVE(sconn->notify_mid_maps, req->mid_map);
+ TALLOC_FREE(req);
+}
+
+static void smbd_notify_cancel_by_map(struct notify_mid_map *map)
+{
+ struct smb_request *smbreq = map->req->req;
+ struct smbd_server_connection *sconn = smbreq->sconn;
+ struct smbd_smb2_request *smb2req = smbreq->smb2req;
+ NTSTATUS notify_status = NT_STATUS_CANCELLED;
+
+ if (smb2req != NULL) {
+ NTSTATUS sstatus;
+
+ if (smb2req->session == NULL) {
+ sstatus = NT_STATUS_USER_SESSION_DELETED;
+ } else {
+ sstatus = smb2req->session->status;
+ }
+
+ if (NT_STATUS_EQUAL(sstatus, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ sstatus = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(sstatus)) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ } else if (smb2req->tcon == NULL) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ } else if (!NT_STATUS_IS_OK(smb2req->tcon->status)) {
+ notify_status = NT_STATUS_NOTIFY_CLEANUP;
+ }
+ }
+
+ change_notify_reply(smbreq, notify_status,
+ 0, NULL, map->req->reply_fn);
+ change_notify_remove_request(sconn, map->req);
+}
+
+/****************************************************************************
+ Delete entries by mid from the change notify pending queue. Always send reply.
+*****************************************************************************/
+
+bool remove_pending_change_notify_requests_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid)
+{
+ struct notify_mid_map *map;
+
+ for (map = sconn->notify_mid_maps; map; map = map->next) {
+ if (map->mid == mid) {
+ break;
+ }
+ }
+
+ if (map == NULL) {
+ return false;
+ }
+
+ smbd_notify_cancel_by_map(map);
+ return true;
+}
+
+void smbd_notify_cancel_by_smbreq(const struct smb_request *smbreq)
+{
+ struct smbd_server_connection *sconn = smbreq->sconn;
+ struct notify_mid_map *map;
+
+ for (map = sconn->notify_mid_maps; map; map = map->next) {
+ if (map->req->req == smbreq) {
+ break;
+ }
+ }
+
+ if (map == NULL) {
+ return;
+ }
+
+ smbd_notify_cancel_by_map(map);
+}
+
+static struct files_struct *smbd_notify_cancel_deleted_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ struct file_id *fid = talloc_get_type_abort(
+ private_data, struct file_id);
+
+ if (file_id_equal(&fsp->file_id, fid)) {
+ remove_pending_change_notify_requests_by_fid(
+ fsp, NT_STATUS_DELETE_PENDING);
+ }
+ return NULL;
+}
+
+void smbd_notify_cancel_deleted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+ struct file_id *fid;
+ enum ndr_err_code ndr_err;
+
+ fid = talloc(talloc_tos(), struct file_id);
+ if (fid == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ data, fid, fid, (ndr_pull_flags_fn_t)ndr_pull_file_id);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(10, ("%s: ndr_pull_file_id failed: %s\n", __func__,
+ ndr_errstr(ndr_err)));
+ goto done;
+ }
+
+ files_forall(sconn, smbd_notify_cancel_deleted_fn, fid);
+
+done:
+ TALLOC_FREE(fid);
+}
+
+static struct files_struct *smbd_notifyd_reregister(struct files_struct *fsp,
+ void *private_data)
+{
+ DBG_DEBUG("reregister %s\n", fsp->fsp_name->base_name);
+
+ if ((fsp->conn->sconn->notify_ctx != NULL) &&
+ (fsp->notify != NULL) &&
+ ((fsp->notify->filter != 0) ||
+ (fsp->notify->subdir_filter != 0))) {
+ size_t len = fsp_fullbasepath(fsp, NULL, 0);
+ char fullpath[len+1];
+
+ NTSTATUS status;
+
+ fsp_fullbasepath(fsp, fullpath, sizeof(fullpath));
+ if (len > 1 && fullpath[len-1] == '.' &&
+ fullpath[len-2] == '/') {
+ fullpath[len-2] = '\0';
+ }
+
+ status = notify_add(fsp->conn->sconn->notify_ctx,
+ fullpath, fsp->notify->filter,
+ fsp->notify->subdir_filter, fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("notify_add failed: %s\n",
+ nt_errstr(status));
+ }
+ }
+ return NULL;
+}
+
+void smbd_notifyd_restarted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+
+ TALLOC_FREE(sconn->notify_ctx);
+
+ sconn->notify_ctx = notify_init(sconn, sconn->msg_ctx,
+ sconn, notify_callback);
+ if (sconn->notify_ctx == NULL) {
+ DBG_DEBUG("notify_init failed\n");
+ return;
+ }
+
+ files_forall(sconn, smbd_notifyd_reregister, sconn->notify_ctx);
+}
+
+/****************************************************************************
+ Delete entries by fnum from the change notify pending queue.
+*****************************************************************************/
+
+void remove_pending_change_notify_requests_by_fid(files_struct *fsp,
+ NTSTATUS status)
+{
+ if (fsp->notify == NULL) {
+ return;
+ }
+
+ while (fsp->notify->requests != NULL) {
+ change_notify_reply(fsp->notify->requests->req,
+ status, 0, NULL,
+ fsp->notify->requests->reply_fn);
+ change_notify_remove_request(fsp->conn->sconn,
+ fsp->notify->requests);
+ }
+}
+
+void notify_fname(connection_struct *conn, uint32_t action, uint32_t filter,
+ const char *path)
+{
+ struct notify_context *notify_ctx = conn->sconn->notify_ctx;
+
+ if (path[0] == '.' && path[1] == '/') {
+ path += 2;
+ }
+
+ notify_trigger(notify_ctx, action, filter, conn->connectpath, path);
+}
+
+static bool user_can_stat_name_under_fsp(files_struct *fsp, const char *name)
+{
+ uint32_t rights;
+ struct smb_filename *fname = NULL;
+ char *filepath = NULL;
+ NTSTATUS status;
+ char *p = NULL;
+
+ /*
+ * Assume we get filepath (relative to the share)
+ * like this:
+ *
+ * 'dir1/dir2/dir3/file'
+ *
+ * We start with LIST and TRAVERSE on the
+ * direct parent ('dir1/dir2/dir3')
+ *
+ * Then we switch to just TRAVERSE for
+ * the rest: 'dir1/dir2', 'dir1', '.'
+ *
+ * For a file in the share root, we'll have
+ * 'file'
+ * and would just check '.' with LIST and TRAVERSE.
+ *
+ * It's important to always check '.' as the last step,
+ * which means we check the permissions of the share root
+ * directory.
+ */
+
+ if (ISDOT(fsp->fsp_name->base_name)) {
+ filepath = talloc_strdup(talloc_tos(), name);
+ } else {
+ filepath = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ fsp->fsp_name->base_name,
+ name);
+ }
+ if (filepath == NULL) {
+ DBG_ERR("Memory allocation failed\n");
+ return false;
+ }
+
+ rights = SEC_DIR_LIST|SEC_DIR_TRAVERSE;
+ p = strrchr_m(filepath, '/');
+ /*
+ * Check each path component, excluding the share root.
+ *
+ * We could check all components including root using
+ * a do { .. } while() loop, but IMHO the logic is clearer
+ * having the share root check separately afterwards.
+ */
+ while (p != NULL) {
+ *p = '\0';
+ status = synthetic_pathref(talloc_tos(),
+ fsp->conn->cwd_fsp,
+ filepath,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("synthetic_pathref failed for %s, error %s\n",
+ filepath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+
+ status = smbd_check_access_rights_fsp(fsp->conn->cwd_fsp,
+ fname->fsp,
+ false,
+ rights);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Access rights for %s/%s: %s\n",
+ fsp->conn->connectpath,
+ filepath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+
+ TALLOC_FREE(fname);
+ rights = SEC_DIR_TRAVERSE;
+ p = strrchr_m(filepath, '/');
+ }
+
+ TALLOC_FREE(filepath);
+
+ /* Finally check share root. */
+ filepath = talloc_strdup(talloc_tos(), ".");
+ if (filepath == NULL) {
+ DBG_ERR("Memory allocation failed\n");
+ return false;
+ }
+ status = synthetic_pathref(talloc_tos(),
+ fsp->conn->cwd_fsp,
+ filepath,
+ NULL,
+ NULL,
+ 0,
+ 0,
+ &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("synthetic_pathref failed for %s, error %s\n",
+ filepath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+ status = smbd_check_access_rights_fsp(fsp->conn->cwd_fsp,
+ fname->fsp,
+ false,
+ rights);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("TRAVERSE access rights for %s failed with %s\n",
+ fsp->conn->connectpath,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return false;
+ }
+ TALLOC_FREE(fname);
+ TALLOC_FREE(filepath);
+ return true;
+}
+
+static void notify_fsp(files_struct *fsp, struct timespec when,
+ uint32_t action, const char *name)
+{
+ struct notify_change_event *change, *changes;
+ char *tmp;
+
+ if (fsp->notify == NULL) {
+ /*
+ * Nobody is waiting, don't queue
+ */
+ return;
+ }
+
+ if (lp_honor_change_notify_privilege(SNUM(fsp->conn))) {
+ bool has_sec_change_notify_privilege;
+ bool expose = false;
+
+ has_sec_change_notify_privilege = security_token_has_privilege(
+ fsp->conn->session_info->security_token,
+ SEC_PRIV_CHANGE_NOTIFY);
+
+ if (has_sec_change_notify_privilege) {
+ expose = true;
+ } else {
+ bool ok;
+
+ ok = become_user_without_service_by_fsp(fsp);
+ if (ok) {
+ expose = user_can_stat_name_under_fsp(fsp, name);
+ unbecome_user_without_service();
+ }
+ }
+ DBG_DEBUG("has_sec_change_notify_privilege=%s "
+ "expose=%s for %s notify %s\n",
+ has_sec_change_notify_privilege ? "true" : "false",
+ expose ? "true" : "false",
+ fsp->fsp_name->base_name, name);
+ if (!expose) {
+ return;
+ }
+ }
+
+ /*
+ * Someone has triggered a notify previously, queue the change for
+ * later.
+ */
+
+ if ((fsp->notify->num_changes > 1000) || (name == NULL)) {
+ /*
+ * The real number depends on the client buf, just provide a
+ * guard against a DoS here. If name == NULL the CN backend is
+ * alerting us to a problem. Possibly dropped events. Clear
+ * queued changes and send the catch-all response to the client
+ * if a request is pending.
+ */
+ TALLOC_FREE(fsp->notify->changes);
+ fsp->notify->num_changes = -1;
+ if (fsp->notify->requests != NULL) {
+ change_notify_reply(fsp->notify->requests->req,
+ NT_STATUS_OK,
+ fsp->notify->requests->max_param,
+ fsp->notify,
+ fsp->notify->requests->reply_fn);
+ change_notify_remove_request(fsp->conn->sconn,
+ fsp->notify->requests);
+ }
+ return;
+ }
+
+ /* If we've exceeded the server side queue or received a NULL name
+ * from the underlying CN implementation, don't queue up any more
+ * requests until we can send a catch-all response to the client */
+ if (fsp->notify->num_changes == -1) {
+ return;
+ }
+
+ if (!(changes = talloc_realloc(
+ fsp->notify, fsp->notify->changes,
+ struct notify_change_event,
+ fsp->notify->num_changes+1))) {
+ DEBUG(0, ("talloc_realloc failed\n"));
+ return;
+ }
+
+ fsp->notify->changes = changes;
+
+ change = &(fsp->notify->changes[fsp->notify->num_changes]);
+
+ if (!(tmp = talloc_strdup(changes, name))) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ return;
+ }
+
+ string_replace(tmp, '/', '\\');
+ change->name = tmp;
+
+ change->when = when;
+ change->action = action;
+ fsp->notify->num_changes += 1;
+
+ if (fsp->notify->requests == NULL) {
+ /*
+ * Nobody is waiting, so don't send anything. The ot
+ */
+ return;
+ }
+
+ if (action == NOTIFY_ACTION_OLD_NAME) {
+ /*
+ * We have to send the two rename events in one reply. So hold
+ * the first part back.
+ */
+ return;
+ }
+
+ /*
+ * Someone is waiting for the change, trigger the reply immediately.
+ *
+ * TODO: do we have to walk the lists of requests pending?
+ */
+
+ change_notify_reply(fsp->notify->requests->req,
+ NT_STATUS_OK,
+ fsp->notify->requests->max_param,
+ fsp->notify,
+ fsp->notify->requests->reply_fn);
+
+ change_notify_remove_request(fsp->conn->sconn, fsp->notify->requests);
+}
+
+char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32_t filter)
+{
+ char *result = NULL;
+
+ result = talloc_strdup(mem_ctx, "");
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (filter & FILE_NOTIFY_CHANGE_FILE_NAME) {
+ result = talloc_asprintf_append(result, "FILE_NAME|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_DIR_NAME) {
+ result = talloc_asprintf_append(result, "DIR_NAME|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_ATTRIBUTES) {
+ result = talloc_asprintf_append(result, "ATTRIBUTES|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_SIZE) {
+ result = talloc_asprintf_append(result, "SIZE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_LAST_WRITE) {
+ result = talloc_asprintf_append(result, "LAST_WRITE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_LAST_ACCESS) {
+ result = talloc_asprintf_append(result, "LAST_ACCESS|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_CREATION) {
+ result = talloc_asprintf_append(result, "CREATION|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_EA) {
+ result = talloc_asprintf_append(result, "EA|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_SECURITY) {
+ result = talloc_asprintf_append(result, "SECURITY|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_NAME) {
+ result = talloc_asprintf_append(result, "STREAM_NAME|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_SIZE) {
+ result = talloc_asprintf_append(result, "STREAM_SIZE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+ if (filter & FILE_NOTIFY_CHANGE_STREAM_WRITE) {
+ result = talloc_asprintf_append(result, "STREAM_WRITE|");
+ if (result == NULL) {
+ return NULL;
+ }
+ }
+
+ if (*result == '\0') return result;
+
+ result[strlen(result)-1] = '\0';
+ return result;
+}
+
+struct sys_notify_context *sys_notify_context_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct sys_notify_context *ctx;
+
+ if (!(ctx = talloc(mem_ctx, struct sys_notify_context))) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ ctx->ev = ev;
+ ctx->private_data = NULL;
+ return ctx;
+}
diff --git a/source3/smbd/notify_fam.c b/source3/smbd/notify_fam.c
new file mode 100644
index 0000000..2cf1e35
--- /dev/null
+++ b/source3/smbd/notify_fam.c
@@ -0,0 +1,307 @@
+/*
+ * FAM file notification support.
+ *
+ * Copyright (c) James Peach 2005
+ * Copyright (c) Volker Lendecke 2007
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "librpc/gen_ndr/notify.h"
+
+#include <fam.h>
+
+#if !defined(HAVE_FAM_H_FAMCODES_TYPEDEF)
+/* Gamin provides this typedef which means we can't use 'enum FAMCodes' as per
+ * every other FAM implementation. Phooey.
+ */
+typedef enum FAMCodes FAMCodes;
+#endif
+
+/* NOTE: There are multiple versions of FAM floating around the net, each with
+ * slight differences from the original SGI FAM implementation. In this file,
+ * we rely only on the SGI features and do not assume any extensions. For
+ * example, we do not look at FAMErrno, because it is not set by the original
+ * implementation.
+ *
+ * Random FAM links:
+ * http://oss.sgi.com/projects/fam/
+ * http://savannah.nongnu.org/projects/fam/
+ * http://sourceforge.net/projects/bsdfam/
+ */
+
+/* ------------------------------------------------------------------------- */
+
+struct fam_watch_context {
+ struct fam_watch_context *prev, *next;
+ FAMConnection *fam_connection;
+ struct FAMRequest fr;
+ struct sys_notify_context *sys_ctx;
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter);
+ void *private_data;
+ uint32_t mask; /* the inotify mask */
+ uint32_t filter; /* the windows completion filter */
+ const char *path;
+};
+
+
+/*
+ * We want one FAM connection per smbd, not one per tcon.
+ */
+static FAMConnection fam_connection;
+static bool fam_connection_initialized = False;
+
+static struct fam_watch_context *fam_notify_list;
+static void fam_handler(struct tevent_context *event_ctx,
+ struct tevent_fd *fd_event,
+ uint16_t flags,
+ void *private_data);
+
+static NTSTATUS fam_open_connection(FAMConnection *fam_conn,
+ struct tevent_context *event_ctx)
+{
+ int res;
+ char *name;
+
+ ZERO_STRUCTP(fam_conn);
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+
+
+#ifdef HAVE_FAMNOEXISTS
+ /* We should honor outside setting of the GAM_CLIENT_ID. */
+ setenv("GAM_CLIENT_ID","SAMBA",0);
+#endif
+
+ if (asprintf(&name, "smbd (%lu)", (unsigned long)getpid()) == -1) {
+ DEBUG(0, ("No memory\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ res = FAMOpen2(fam_conn, name);
+
+#ifdef HAVE_FAMNOEXISTS
+ /*
+ * This reduces the chatter between GAMIN and samba making the pair
+ * much more reliable.
+ */
+ FAMNoExists(fam_conn);
+#endif
+
+ SAFE_FREE(name);
+
+ if (res < 0) {
+ DEBUG(10, ("FAM file change notifications not available: %s\n",
+ FamErrlist[-res]));
+ /*
+ * No idea how to get NT_STATUS from a FAM result
+ */
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+ return NT_STATUS_UNEXPECTED_IO_ERROR;
+ }
+
+ if (tevent_add_fd(event_ctx, event_ctx,
+ FAMCONNECTION_GETFD(fam_conn),
+ TEVENT_FD_READ, fam_handler,
+ (void *)fam_conn) == NULL) {
+ DEBUG(0, ("event_add_fd failed\n"));
+ FAMClose(fam_conn);
+ FAMCONNECTION_GETFD(fam_conn) = -1;
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void fam_reopen(FAMConnection *fam_conn,
+ struct tevent_context *event_ctx,
+ struct fam_watch_context *notify_list)
+{
+ struct fam_watch_context *ctx;
+
+ DEBUG(5, ("Re-opening FAM connection\n"));
+
+ FAMClose(fam_conn);
+
+ if (!NT_STATUS_IS_OK(fam_open_connection(fam_conn, event_ctx))) {
+ DEBUG(5, ("Re-opening fam connection failed\n"));
+ return;
+ }
+
+ for (ctx = notify_list; ctx; ctx = ctx->next) {
+ FAMMonitorDirectory(fam_conn, ctx->path, &ctx->fr, NULL);
+ }
+}
+
+static void fam_handler(struct tevent_context *event_ctx,
+ struct tevent_fd *fd_event,
+ uint16_t flags,
+ void *private_data)
+{
+ FAMConnection *fam_conn = (FAMConnection *)private_data;
+ FAMEvent fam_event;
+ struct fam_watch_context *ctx;
+ struct notify_event ne;
+
+ if (FAMPending(fam_conn) == 0) {
+ DEBUG(10, ("fam_handler called but nothing pending\n"));
+ return;
+ }
+
+ if (FAMNextEvent(fam_conn, &fam_event) != 1) {
+ DEBUG(5, ("FAMNextEvent returned an error\n"));
+ TALLOC_FREE(fd_event);
+ fam_reopen(fam_conn, event_ctx, fam_notify_list);
+ return;
+ }
+
+ DEBUG(10, ("Got FAMCode %d for %s\n", fam_event.code,
+ fam_event.filename));
+
+ switch (fam_event.code) {
+ case FAMChanged:
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ break;
+ case FAMCreated:
+ ne.action = NOTIFY_ACTION_ADDED;
+ break;
+ case FAMDeleted:
+ ne.action = NOTIFY_ACTION_REMOVED;
+ break;
+ default:
+ DEBUG(10, ("Ignoring code FAMCode %d for file %s\n",
+ (int)fam_event.code, fam_event.filename));
+ return;
+ }
+
+ for (ctx = fam_notify_list; ctx; ctx = ctx->next) {
+ if (memcmp(&fam_event.fr, &ctx->fr, sizeof(FAMRequest)) == 0) {
+ break;
+ }
+ }
+
+ if (ctx == NULL) {
+ DEBUG(5, ("Discarding event for file %s\n",
+ fam_event.filename));
+ return;
+ }
+
+ if ((ne.path = strrchr_m(fam_event.filename, '\\')) == NULL) {
+ ne.path = fam_event.filename;
+ }
+ ne.dir = ctx->path;
+
+ ctx->callback(ctx->sys_ctx, ctx->private_data, &ne, UINT32_MAX);
+}
+
+static int fam_watch_context_destructor(struct fam_watch_context *ctx)
+{
+ if (FAMCONNECTION_GETFD(ctx->fam_connection) != -1) {
+ FAMCancelMonitor(&fam_connection, &ctx->fr);
+ }
+ DLIST_REMOVE(fam_notify_list, ctx);
+ return 0;
+}
+
+/*
+ add a watch. The watch is removed when the caller calls
+ talloc_free() on *handle
+*/
+int fam_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p)
+{
+ const uint32_t fam_mask = (FILE_NOTIFY_CHANGE_FILE_NAME|
+ FILE_NOTIFY_CHANGE_DIR_NAME);
+ struct fam_watch_context *watch;
+ void **handle = (void **)handle_p;
+
+ *handle = NULL;
+
+ if ((*filter & fam_mask) == 0) {
+ DEBUG(10, ("filter = %u, ignoring in FAM\n", *filter));
+ return 0;
+ }
+
+ if (!fam_connection_initialized) {
+ if (!NT_STATUS_IS_OK(fam_open_connection(&fam_connection,
+ ctx->ev))) {
+ /*
+ * Just let smbd do all the work itself
+ */
+ return 0;
+ }
+ fam_connection_initialized = True;
+ }
+
+ if (!(watch = talloc(mem_ctx, struct fam_watch_context))) {
+ return ENOMEM;
+ }
+
+ watch->fam_connection = &fam_connection;
+
+ watch->callback = callback;
+ watch->private_data = private_data;
+ watch->sys_ctx = ctx;
+
+ watch->path = talloc_strdup(watch, path);
+ if (watch->path == NULL) {
+ DEBUG(0, ("talloc_asprintf failed\n"));
+ TALLOC_FREE(watch);
+ return ENOMEM;
+ }
+
+ /*
+ * The FAM module in this early state will only take care of
+ * FAMCreated and FAMDeleted events, Leave the rest to notifyd
+ */
+
+ watch->filter = fam_mask;
+ *filter &= ~fam_mask;
+
+ DLIST_ADD(fam_notify_list, watch);
+ talloc_set_destructor(watch, fam_watch_context_destructor);
+
+ /*
+ * Only directories monitored so far
+ */
+
+ if (FAMCONNECTION_GETFD(watch->fam_connection) != -1) {
+ FAMMonitorDirectory(watch->fam_connection, watch->path,
+ &watch->fr, NULL);
+ }
+ else {
+ /*
+ * If the re-open is successful, this will establish the
+ * FAMMonitor from the list
+ */
+ fam_reopen(watch->fam_connection, ctx->ev, fam_notify_list);
+ }
+
+ *handle = watch;
+
+ return 0;
+}
diff --git a/source3/smbd/notify_inotify.c b/source3/smbd/notify_inotify.c
new file mode 100644
index 0000000..81fafc2
--- /dev/null
+++ b/source3/smbd/notify_inotify.c
@@ -0,0 +1,457 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Andrew Tridgell 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ notify implementation using inotify
+*/
+
+#include "includes.h"
+#include "../librpc/gen_ndr/notify.h"
+#include "smbd/smbd.h"
+#include "lib/util/sys_rw_data.h"
+
+#include <sys/inotify.h>
+
+/* glibc < 2.5 headers don't have these defines */
+#ifndef IN_ONLYDIR
+#define IN_ONLYDIR 0x01000000
+#endif
+#ifndef IN_MASK_ADD
+#define IN_MASK_ADD 0x20000000
+#endif
+
+struct inotify_private {
+ struct sys_notify_context *ctx;
+ int fd;
+ struct inotify_watch_context *watches;
+};
+
+struct inotify_watch_context {
+ struct inotify_watch_context *next, *prev;
+ struct inotify_private *in;
+ int wd;
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter);
+ void *private_data;
+ uint32_t mask; /* the inotify mask */
+ uint32_t filter; /* the windows completion filter */
+ const char *path;
+};
+
+
+/*
+ map from a change notify mask to a inotify mask. Remove any bits
+ which we can handle
+*/
+static const struct {
+ uint32_t notify_mask;
+ uint32_t inotify_mask;
+} inotify_mapping[] = {
+ {FILE_NOTIFY_CHANGE_FILE_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
+ {FILE_NOTIFY_CHANGE_DIR_NAME, IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO},
+ {FILE_NOTIFY_CHANGE_ATTRIBUTES, IN_ATTRIB|IN_MOVED_TO|IN_MOVED_FROM|IN_MODIFY},
+ {FILE_NOTIFY_CHANGE_LAST_WRITE, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_LAST_ACCESS, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_EA, IN_ATTRIB},
+ {FILE_NOTIFY_CHANGE_SECURITY, IN_ATTRIB}
+};
+
+static uint32_t inotify_map(uint32_t *filter)
+{
+ size_t i;
+ uint32_t out=0;
+ for (i=0;i<ARRAY_SIZE(inotify_mapping);i++) {
+ if (inotify_mapping[i].notify_mask & *filter) {
+ out |= inotify_mapping[i].inotify_mask;
+ *filter &= ~inotify_mapping[i].notify_mask;
+ }
+ }
+ return out;
+}
+
+/*
+ * Map inotify mask back to filter. This returns all filters that
+ * could have created the inotify watch.
+ */
+static uint32_t inotify_map_mask_to_filter(uint32_t mask)
+{
+ size_t i;
+ uint32_t filter = 0;
+
+ for (i = 0; i < ARRAY_SIZE(inotify_mapping); i++) {
+ if (inotify_mapping[i].inotify_mask & mask) {
+ filter |= inotify_mapping[i].notify_mask;
+ }
+ }
+
+ if (mask & IN_ISDIR) {
+ filter &= ~FILE_NOTIFY_CHANGE_FILE_NAME;
+ } else {
+ filter &= ~FILE_NOTIFY_CHANGE_DIR_NAME;
+ }
+
+ return filter;
+}
+
+/*
+ destroy the inotify private context
+*/
+static int inotify_destructor(struct inotify_private *in)
+{
+ close(in->fd);
+ return 0;
+}
+
+
+/*
+ see if a particular event from inotify really does match a requested
+ notify event in SMB
+*/
+static bool filter_match(struct inotify_watch_context *w,
+ struct inotify_event *e)
+{
+ bool ok;
+
+ DEBUG(10, ("filter_match: e->mask=%x, w->mask=%x, w->filter=%x\n",
+ e->mask, w->mask, w->filter));
+
+ if ((e->mask & w->mask) == 0) {
+ /* this happens because inotify_add_watch() coalesces watches on the same
+ path, oring their masks together */
+ return False;
+ }
+
+ /* SMB separates the filters for files and directories */
+ if (e->mask & IN_ISDIR) {
+ ok = ((w->filter & FILE_NOTIFY_CHANGE_DIR_NAME) != 0);
+ return ok;
+ }
+
+ if ((e->mask & IN_ATTRIB) &&
+ (w->filter & (FILE_NOTIFY_CHANGE_ATTRIBUTES|
+ FILE_NOTIFY_CHANGE_LAST_WRITE|
+ FILE_NOTIFY_CHANGE_LAST_ACCESS|
+ FILE_NOTIFY_CHANGE_EA|
+ FILE_NOTIFY_CHANGE_SECURITY))) {
+ return True;
+ }
+ if ((e->mask & IN_MODIFY) &&
+ (w->filter & FILE_NOTIFY_CHANGE_ATTRIBUTES)) {
+ return True;
+ }
+
+ ok = ((w->filter & FILE_NOTIFY_CHANGE_FILE_NAME) != 0);
+ return ok;
+}
+
+
+
+/*
+ dispatch one inotify event
+
+ the cookies are used to correctly handle renames
+*/
+static void inotify_dispatch(struct inotify_private *in,
+ struct inotify_event *e,
+ int prev_wd,
+ uint32_t prev_cookie,
+ struct inotify_event *e2)
+{
+ struct inotify_watch_context *w, *next;
+ struct notify_event ne;
+ uint32_t filter;
+
+ DEBUG(10, ("inotify_dispatch called with mask=%x, name=[%s]\n",
+ e->mask, e->len ? e->name : ""));
+
+ /* ignore extraneous events, such as unmount and IN_IGNORED events */
+ if ((e->mask & (IN_ATTRIB|IN_MODIFY|IN_CREATE|IN_DELETE|
+ IN_MOVED_FROM|IN_MOVED_TO)) == 0) {
+ return;
+ }
+
+ /* map the inotify mask to a action. This gets complicated for
+ renames */
+ if (e->mask & IN_CREATE) {
+ ne.action = NOTIFY_ACTION_ADDED;
+ } else if (e->mask & IN_DELETE) {
+ ne.action = NOTIFY_ACTION_REMOVED;
+ } else if (e->mask & IN_MOVED_FROM) {
+ if (e2 != NULL && e2->cookie == e->cookie &&
+ e2->wd == e->wd) {
+ ne.action = NOTIFY_ACTION_OLD_NAME;
+ } else {
+ ne.action = NOTIFY_ACTION_REMOVED;
+ }
+ } else if (e->mask & IN_MOVED_TO) {
+ if ((e->cookie == prev_cookie) && (e->wd == prev_wd)) {
+ ne.action = NOTIFY_ACTION_NEW_NAME;
+ } else {
+ ne.action = NOTIFY_ACTION_ADDED;
+ }
+ } else {
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ }
+ ne.path = e->name;
+
+ filter = inotify_map_mask_to_filter(e->mask);
+
+ DBG_DEBUG("ne.action = %d, ne.path = %s, filter = %d\n",
+ ne.action, ne.path, filter);
+
+ /* find any watches that have this watch descriptor */
+ for (w=in->watches;w;w=next) {
+ next = w->next;
+ if (w->wd == e->wd && filter_match(w, e)) {
+ ne.dir = w->path;
+ w->callback(in->ctx, w->private_data, &ne, filter);
+ }
+ }
+
+ if ((ne.action == NOTIFY_ACTION_NEW_NAME) &&
+ ((e->mask & IN_ISDIR) == 0)) {
+
+ /*
+ * SMB expects a file rename to generate three events, two for
+ * the rename and the other for a modify of the
+ * destination. Strange!
+ */
+
+ ne.action = NOTIFY_ACTION_MODIFIED;
+ e->mask = IN_ATTRIB;
+
+ for (w=in->watches;w;w=next) {
+ next = w->next;
+ if (w->wd == e->wd && filter_match(w, e) &&
+ !(w->filter & FILE_NOTIFY_CHANGE_CREATION)) {
+ ne.dir = w->path;
+ w->callback(in->ctx, w->private_data, &ne,
+ filter);
+ }
+ }
+ }
+}
+
+/*
+ called when the kernel has some events for us
+*/
+static void inotify_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct inotify_private *in = talloc_get_type(private_data,
+ struct inotify_private);
+ int bufsize = 0;
+ struct inotify_event *e0, *e;
+ uint32_t prev_cookie=0;
+ int prev_wd = -1;
+ ssize_t ret;
+
+ /*
+ we must use FIONREAD as we cannot predict the length of the
+ filenames, and thus can't know how much to allocate
+ otherwise
+ */
+ if (ioctl(in->fd, FIONREAD, &bufsize) != 0 ||
+ bufsize == 0) {
+ DEBUG(0,("No data on inotify fd?!\n"));
+ TALLOC_FREE(fde);
+ return;
+ }
+
+ e0 = e = (struct inotify_event *)TALLOC_SIZE(in, bufsize + 1);
+ if (e == NULL) return;
+ ((uint8_t *)e)[bufsize] = '\0';
+
+ ret = read_data(in->fd, e0, bufsize);
+ if (ret != bufsize) {
+ DEBUG(0, ("Failed to read all inotify data - %s\n",
+ strerror(errno)));
+ talloc_free(e0);
+ /* the inotify fd will now be out of sync,
+ * can't keep reading data off it */
+ TALLOC_FREE(fde);
+ return;
+ }
+
+ /* we can get more than one event in the buffer */
+ while (e && (bufsize >= sizeof(*e))) {
+ struct inotify_event *e2 = NULL;
+ bufsize -= e->len + sizeof(*e);
+ if (bufsize >= sizeof(*e)) {
+ e2 = (struct inotify_event *)(e->len + sizeof(*e) + (char *)e);
+ }
+ inotify_dispatch(in, e, prev_wd, prev_cookie, e2);
+ prev_wd = e->wd;
+ prev_cookie = e->cookie;
+ e = e2;
+ }
+
+ talloc_free(e0);
+}
+
+/*
+ setup the inotify handle - called the first time a watch is added on
+ this context
+*/
+static int inotify_setup(struct sys_notify_context *ctx)
+{
+ struct inotify_private *in;
+ struct tevent_fd *fde;
+
+ in = talloc(ctx, struct inotify_private);
+ if (in == NULL) {
+ return ENOMEM;
+ }
+
+ in->fd = inotify_init();
+ if (in->fd == -1) {
+ int ret = errno;
+ DEBUG(0, ("Failed to init inotify - %s\n", strerror(ret)));
+ talloc_free(in);
+ return ret;
+ }
+ in->ctx = ctx;
+ in->watches = NULL;
+
+ ctx->private_data = in;
+ talloc_set_destructor(in, inotify_destructor);
+
+ /* add a event waiting for the inotify fd to be readable */
+ fde = tevent_add_fd(ctx->ev, in, in->fd, TEVENT_FD_READ,
+ inotify_handler, in);
+ if (fde == NULL) {
+ ctx->private_data = NULL;
+ TALLOC_FREE(in);
+ return ENOMEM;
+ }
+ return 0;
+}
+
+/*
+ destroy a watch
+*/
+static int watch_destructor(struct inotify_watch_context *w)
+{
+ struct inotify_private *in = w->in;
+ int wd = w->wd;
+ DLIST_REMOVE(w->in->watches, w);
+
+ for (w=in->watches;w;w=w->next) {
+ if (w->wd == wd) {
+ /*
+ * Another inotify_watch_context listens on this path,
+ * leave the kernel level watch in place
+ */
+ return 0;
+ }
+ }
+
+ DEBUG(10, ("Deleting inotify watch %d\n", wd));
+ if (inotify_rm_watch(in->fd, wd) == -1) {
+ DEBUG(1, ("inotify_rm_watch returned %s\n", strerror(errno)));
+ }
+ return 0;
+}
+
+
+/*
+ add a watch. The watch is removed when the caller calls
+ talloc_free() on *handle
+*/
+int inotify_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p)
+{
+ struct inotify_private *in;
+ uint32_t mask;
+ struct inotify_watch_context *w;
+ uint32_t orig_filter = *filter;
+ void **handle = (void **)handle_p;
+
+ /* maybe setup the inotify fd */
+ if (ctx->private_data == NULL) {
+ int ret;
+ ret = inotify_setup(ctx);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ in = talloc_get_type(ctx->private_data, struct inotify_private);
+
+ mask = inotify_map(filter);
+ if (mask == 0) {
+ /* this filter can't be handled by inotify */
+ return EINVAL;
+ }
+
+ /* using IN_MASK_ADD allows us to cope with inotify() returning the same
+ watch descriptor for multiple watches on the same path */
+ mask |= (IN_MASK_ADD | IN_ONLYDIR);
+
+ w = talloc(mem_ctx, struct inotify_watch_context);
+ if (w == NULL) {
+ *filter = orig_filter;
+ return ENOMEM;
+ }
+
+ w->in = in;
+ w->callback = callback;
+ w->private_data = private_data;
+ w->mask = mask;
+ w->filter = orig_filter;
+ w->path = talloc_strdup(w, path);
+ if (w->path == NULL) {
+ *filter = orig_filter;
+ TALLOC_FREE(w);
+ return ENOMEM;
+ }
+
+ /* get a new watch descriptor for this path */
+ w->wd = inotify_add_watch(in->fd, path, mask);
+ if (w->wd == -1) {
+ int err = errno;
+ *filter = orig_filter;
+ TALLOC_FREE(w);
+ DEBUG(1, ("inotify_add_watch returned %s\n", strerror(err)));
+ return err;
+ }
+
+ DEBUG(10, ("inotify_add_watch for %s mask %x returned wd %d\n",
+ path, mask, w->wd));
+
+ (*handle) = w;
+
+ DLIST_ADD(in->watches, w);
+
+ /* the caller frees the handle to stop watching */
+ talloc_set_destructor(w, watch_destructor);
+
+ return 0;
+}
diff --git a/source3/smbd/notify_msg.c b/source3/smbd/notify_msg.c
new file mode 100644
index 0000000..0ea992c
--- /dev/null
+++ b/source3/smbd/notify_msg.c
@@ -0,0 +1,247 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "librpc/gen_ndr/notify.h"
+#include "librpc/gen_ndr/messaging.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "lib/util/server_id.h"
+#include "messages.h"
+#include "proto.h"
+#include "globals.h"
+#include "tdb.h"
+#include "util_tdb.h"
+#include "lib/util/server_id_db.h"
+#include "smbd/notifyd/notifyd.h"
+
+struct notify_context {
+ struct server_id notifyd;
+ struct messaging_context *msg_ctx;
+
+ struct smbd_server_connection *sconn;
+ void (*callback)(struct smbd_server_connection *sconn,
+ void *private_data, struct timespec when,
+ const struct notify_event *ctx);
+};
+
+static void notify_handler(struct messaging_context *msg, void *private_data,
+ uint32_t msg_type, struct server_id src,
+ DATA_BLOB *data);
+static int notify_context_destructor(struct notify_context *ctx);
+
+struct notify_context *notify_init(
+ TALLOC_CTX *mem_ctx, struct messaging_context *msg,
+ struct smbd_server_connection *sconn,
+ void (*callback)(struct smbd_server_connection *sconn,
+ void *, struct timespec,
+ const struct notify_event *))
+{
+ struct server_id_db *names_db;
+ struct notify_context *ctx;
+ NTSTATUS status;
+
+ ctx = talloc(mem_ctx, struct notify_context);
+ if (ctx == NULL) {
+ return NULL;
+ }
+ ctx->msg_ctx = msg;
+
+ ctx->sconn = sconn;
+ ctx->callback = callback;
+
+ names_db = messaging_names_db(msg);
+ if (!server_id_db_lookup_one(names_db, "notify-daemon",
+ &ctx->notifyd)) {
+ DBG_WARNING("No notify daemon around\n");
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+
+ {
+ struct server_id_buf tmp;
+ DBG_DEBUG("notifyd=%s\n",
+ server_id_str_buf(ctx->notifyd, &tmp));
+ }
+
+ if (callback != NULL) {
+ status = messaging_register(msg, ctx, MSG_PVFS_NOTIFY,
+ notify_handler);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("messaging_register failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+ }
+
+ talloc_set_destructor(ctx, notify_context_destructor);
+
+ return ctx;
+}
+
+static int notify_context_destructor(struct notify_context *ctx)
+{
+ if (ctx->callback != NULL) {
+ messaging_deregister(ctx->msg_ctx, MSG_PVFS_NOTIFY, ctx);
+ }
+
+ return 0;
+}
+
+static void notify_handler(struct messaging_context *msg, void *private_data,
+ uint32_t msg_type, struct server_id src,
+ DATA_BLOB *data)
+{
+ struct notify_context *ctx = talloc_get_type_abort(
+ private_data, struct notify_context);
+ struct notify_event_msg *event_msg;
+ struct notify_event event;
+
+ if (data->length < offsetof(struct notify_event_msg, path) + 1) {
+ DBG_WARNING("message too short: %zu\n", data->length);
+ return;
+ }
+ if (data->data[data->length-1] != 0) {
+ DBG_WARNING("path not 0-terminated\n");
+ return;
+ }
+
+ event_msg = (struct notify_event_msg *)data->data;
+
+ event.action = event_msg->action;
+ event.path = event_msg->path;
+ event.private_data = event_msg->private_data;
+
+ DBG_DEBUG("Got notify_event action=%"PRIu32", private_data=%p, "
+ "path=%s\n",
+ event.action,
+ event.private_data,
+ event.path);
+
+ ctx->callback(ctx->sconn, event.private_data, event_msg->when, &event);
+}
+
+NTSTATUS notify_add(struct notify_context *ctx,
+ const char *path, uint32_t filter, uint32_t subdir_filter,
+ void *private_data)
+{
+ struct notify_rec_change_msg msg = {};
+ struct iovec iov[2];
+ size_t pathlen;
+ NTSTATUS status;
+
+ if (ctx == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ DBG_DEBUG("path=[%s], filter=%"PRIu32", subdir_filter=%"PRIu32", "
+ "private_data=%p\n",
+ path,
+ filter,
+ subdir_filter,
+ private_data);
+
+ pathlen = strlen(path)+1;
+
+ clock_gettime_mono(&msg.instance.creation_time);
+ msg.instance.filter = filter;
+ msg.instance.subdir_filter = subdir_filter;
+ msg.instance.private_data = private_data;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = discard_const_p(char, path);
+ iov[1].iov_len = pathlen;
+
+ status = messaging_send_iov(
+ ctx->msg_ctx, ctx->notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS notify_remove(struct notify_context *ctx, void *private_data,
+ char *path)
+{
+ struct notify_rec_change_msg msg = {};
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ /* see if change notify is enabled at all */
+ if (ctx == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ msg.instance.private_data = private_data;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = path;
+ iov[1].iov_len = strlen(path)+1;
+
+ status = messaging_send_iov(
+ ctx->msg_ctx, ctx->notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+
+ return status;
+}
+
+void notify_trigger(struct notify_context *ctx,
+ uint32_t action, uint32_t filter,
+ const char *dir, const char *name)
+{
+ struct notify_trigger_msg msg;
+ struct iovec iov[4];
+ char slash = '/';
+
+ DBG_DEBUG("notify_trigger called action=0x%"PRIx32", "
+ "filter=0x%"PRIx32", dir=%s, name=%s\n",
+ action,
+ filter,
+ dir,
+ name);
+
+ if (ctx == NULL) {
+ return;
+ }
+
+ msg.when = timespec_current();
+ msg.action = action;
+ msg.filter = filter;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_trigger_msg, path);
+ iov[1].iov_base = discard_const_p(char, dir);
+ iov[1].iov_len = strlen(dir);
+ iov[2].iov_base = &slash;
+ iov[2].iov_len = 1;
+ iov[3].iov_base = discard_const_p(char, name);
+ iov[3].iov_len = strlen(name)+1;
+
+ messaging_send_iov(
+ ctx->msg_ctx, ctx->notifyd, MSG_SMB_NOTIFY_TRIGGER,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+}
diff --git a/source3/smbd/notifyd/fcn_wait.c b/source3/smbd/notifyd/fcn_wait.c
new file mode 100644
index 0000000..e32240d
--- /dev/null
+++ b/source3/smbd/notifyd/fcn_wait.c
@@ -0,0 +1,270 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "fcn_wait.h"
+#include "notifyd.h"
+#include "lib/util/tevent_ntstatus.h"
+
+struct fcn_event {
+ struct fcn_event *prev, *next;
+ struct notify_event_msg msg;
+};
+
+struct fcn_wait_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct server_id notifyd;
+ const char *path;
+
+ struct tevent_req *recv_subreq;
+
+ struct fcn_event *events;
+};
+
+static bool fcn_wait_cancel(struct tevent_req *req);
+static void fcn_wait_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state);
+static bool fcn_wait_filter(struct messaging_rec *rec, void *private_data);
+static void fcn_wait_done(struct tevent_req *subreq);
+
+struct tevent_req *fcn_wait_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *path,
+ uint32_t filter,
+ uint32_t subdir_filter)
+{
+ struct tevent_req *req = NULL;
+ struct fcn_wait_state *state = NULL;
+ struct notify_rec_change_msg msg = {
+ .instance.filter = filter,
+ .instance.subdir_filter = subdir_filter,
+ };
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct fcn_wait_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->notifyd = notifyd;
+ state->path = path;
+
+ state->recv_subreq = messaging_filtered_read_send(
+ state, ev, msg_ctx, fcn_wait_filter, req);
+ if (tevent_req_nomem(state->recv_subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->recv_subreq, fcn_wait_done, req);
+ tevent_req_set_cleanup_fn(req, fcn_wait_cleanup);
+
+ clock_gettime_mono(&msg.instance.creation_time);
+ msg.instance.private_data = state;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = discard_const_p(char, path);
+ iov[1].iov_len = strlen(path)+1;
+
+ status = messaging_send_iov(
+ msg_ctx, /* msg_ctx */
+ notifyd, /* dst */
+ MSG_SMB_NOTIFY_REC_CHANGE, /* mst_type */
+ iov, /* iov */
+ ARRAY_SIZE(iov), /* iovlen */
+ NULL, /* fds */
+ 0); /* num_fds */
+ if (tevent_req_nterror(req, status)) {
+ DBG_DEBUG("messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_cancel_fn(req, fcn_wait_cancel);
+
+ return req;
+}
+
+static bool fcn_wait_cancel(struct tevent_req *req)
+{
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ struct notify_rec_change_msg msg = {
+ .instance.filter = 0, /* filter==0 is a delete msg */
+ .instance.subdir_filter = 0,
+ };
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ clock_gettime_mono(&msg.instance.creation_time);
+ msg.instance.private_data = state;
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = discard_const_p(char, state->path);
+ iov[1].iov_len = strlen(state->path)+1;
+
+ status = messaging_send_iov(
+ state->msg_ctx, /* msg_ctx */
+ state->notifyd, /* dst */
+ MSG_SMB_NOTIFY_REC_CHANGE, /* mst_type */
+ iov, /* iov */
+ ARRAY_SIZE(iov), /* iovlen */
+ NULL, /* fds */
+ 0); /* num_fds */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ fcn_wait_cleanup(req, 0); /* fcn_wait_cleanup ignores req_state */
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_nterror(req, NT_STATUS_CANCELLED);
+
+ return true;
+}
+
+static void fcn_wait_cleanup(
+ struct tevent_req *req, enum tevent_req_state req_state)
+{
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ TALLOC_FREE(state->recv_subreq);
+}
+
+static bool fcn_wait_filter(struct messaging_rec *rec, void *private_data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ struct notify_event_msg msg = { .action = 0 };
+ struct fcn_event *evt = NULL;
+
+ if (rec->msg_type != MSG_PVFS_NOTIFY) {
+ DBG_DEBUG("Ignoring msg %"PRIu32"\n", rec->msg_type);
+ return false;
+ }
+
+ /*
+ * We need at least the trailing '\0' for the path
+ */
+ if (rec->buf.length < (offsetof(struct notify_event_msg, path) + 1)) {
+ DBG_DEBUG("Ignoring short (%zu) msg\n", rec->buf.length);
+ return false;
+ }
+ if (rec->buf.data[rec->buf.length-1] != '\0') {
+ DBG_DEBUG("Expected 0-terminated path\n");
+ return false;
+ }
+
+ memcpy(&msg, rec->buf.data, sizeof(msg));
+
+ if (msg.private_data != state) {
+ DBG_DEBUG("Got private_data=%p, expected %p\n",
+ msg.private_data,
+ state);
+ return false;
+ }
+
+ evt = talloc_memdup(state, rec->buf.data, rec->buf.length);
+ if (evt == NULL) {
+ DBG_DEBUG("talloc_memdup failed\n");
+ return false;
+ }
+ talloc_set_name_const(evt, "struct fcn_event");
+
+ /*
+ * TODO: Sort by timestamp
+ */
+
+ DLIST_ADD_END(state->events, evt);
+
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+
+ return false;
+}
+
+static void fcn_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = messaging_filtered_read_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ DBG_DEBUG("messaging_filtered_read failed: %s\n",
+ strerror(ret));
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+
+ /*
+ * We should never have gotten here, all work is done from the
+ * filter function.
+ */
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+}
+
+NTSTATUS fcn_wait_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct timespec *when,
+ uint32_t *action,
+ char **path)
+{
+ struct fcn_wait_state *state = tevent_req_data(
+ req, struct fcn_wait_state);
+ struct fcn_event *evt = NULL;
+ NTSTATUS status;
+
+ if (!tevent_req_is_in_progress(req) &&
+ tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ evt = state->events;
+ if (evt == NULL) {
+ return NT_STATUS_RETRY;
+ }
+
+ if (path != NULL) {
+ *path = talloc_strdup(mem_ctx, evt->msg.path);
+ if ((*path) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (when != NULL) {
+ *when = evt->msg.when;
+ }
+ if (action != NULL) {
+ *action = evt->msg.action;
+ }
+
+ DLIST_REMOVE(state->events, evt);
+
+ if (state->events != NULL) {
+ tevent_req_defer_callback(req, state->ev);
+ tevent_req_notify_callback(req);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/notifyd/fcn_wait.h b/source3/smbd/notifyd/fcn_wait.h
new file mode 100644
index 0000000..daadee4
--- /dev/null
+++ b/source3/smbd/notifyd/fcn_wait.h
@@ -0,0 +1,38 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NOTIFYD_FCN_WAIT_H__
+#define __NOTIFYD_FCN_WAIT_H__
+
+#include "replace.h"
+#include "messages.h"
+#include "librpc/gen_ndr/server_id.h"
+
+struct tevent_req *fcn_wait_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *path,
+ uint32_t filter,
+ uint32_t subdir_filter);
+NTSTATUS fcn_wait_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct timespec *when,
+ uint32_t *action,
+ char **path);
+
+#endif
diff --git a/source3/smbd/notifyd/notifyd.c b/source3/smbd/notifyd/notifyd.c
new file mode 100644
index 0000000..ca303bd
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd.c
@@ -0,0 +1,1428 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <tevent.h>
+#include "notifyd_private.h"
+#include "lib/util/server_id.h"
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/notify.h"
+#include "librpc/gen_ndr/messaging.h"
+#include "librpc/gen_ndr/server_id.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "messages.h"
+#include "tdb.h"
+#include "util_tdb.h"
+#include "notifyd.h"
+#include "lib/util/server_id_db.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "ctdbd_conn.h"
+#include "ctdb_srvids.h"
+#include "server_id_db_util.h"
+#include "lib/util/iov_buf.h"
+#include "messages_util.h"
+
+#ifdef CLUSTER_SUPPORT
+#include "ctdb_protocol.h"
+#endif
+
+struct notifyd_peer;
+
+/*
+ * All of notifyd's state
+ */
+
+struct notifyd_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct ctdbd_connection *ctdbd_conn;
+
+ /*
+ * Database of everything clients show interest in. Indexed by
+ * absolute path. The database keys are not 0-terminated
+ * to allow the critical operation, notifyd_trigger, to walk
+ * the structure from the top without adding intermediate 0s.
+ * The database records contain an array of
+ *
+ * struct notifyd_instance
+ *
+ * to be maintained and parsed by notifyd_parse_entry()
+ */
+ struct db_context *entries;
+
+ /*
+ * In the cluster case, this is the place where we store a log
+ * of all MSG_SMB_NOTIFY_REC_CHANGE messages. We just 1:1
+ * forward them to our peer notifyd's in the cluster once a
+ * second or when the log grows too large.
+ */
+
+ struct messaging_reclog *log;
+
+ /*
+ * Array of companion notifyd's in a cluster. Every notifyd
+ * broadcasts its messaging_reclog to every other notifyd in
+ * the cluster. This is done by making ctdb send a message to
+ * srvid CTDB_SRVID_SAMBA_NOTIFY_PROXY with destination node
+ * number CTDB_BROADCAST_CONNECTED. Everybody in the cluster who
+ * had called register_with_ctdbd this srvid will receive the
+ * broadcasts.
+ *
+ * Database replication happens via these broadcasts. Also,
+ * they serve as liveness indication. If a notifyd receives a
+ * broadcast from an unknown peer, it will create one for this
+ * srvid. Also when we don't hear anything from a peer for a
+ * while, we will discard it.
+ */
+
+ struct notifyd_peer **peers;
+ size_t num_peers;
+
+ sys_notify_watch_fn sys_notify_watch;
+ struct sys_notify_context *sys_notify_ctx;
+};
+
+struct notifyd_peer {
+ struct notifyd_state *state;
+ struct server_id pid;
+ uint64_t rec_index;
+ struct db_context *db;
+ time_t last_broadcast;
+};
+
+static void notifyd_rec_change(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+static void notifyd_trigger(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+static void notifyd_get_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+
+#ifdef CLUSTER_SUPPORT
+static void notifyd_got_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data);
+static void notifyd_broadcast_reclog(struct ctdbd_connection *ctdbd_conn,
+ struct server_id src,
+ struct messaging_reclog *log);
+#endif
+static void notifyd_sys_callback(struct sys_notify_context *ctx,
+ void *private_data, struct notify_event *ev,
+ uint32_t filter);
+
+#ifdef CLUSTER_SUPPORT
+static struct tevent_req *notifyd_broadcast_reclog_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct ctdbd_connection *ctdbd_conn, struct server_id src,
+ struct messaging_reclog *log);
+static int notifyd_broadcast_reclog_recv(struct tevent_req *req);
+
+static struct tevent_req *notifyd_clean_peers_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct notifyd_state *notifyd);
+static int notifyd_clean_peers_recv(struct tevent_req *req);
+#endif
+
+static int sys_notify_watch_dummy(
+ TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p)
+{
+ void **handle = handle_p;
+ *handle = NULL;
+ return 0;
+}
+
+#ifdef CLUSTER_SUPPORT
+static void notifyd_broadcast_reclog_finished(struct tevent_req *subreq);
+static void notifyd_clean_peers_finished(struct tevent_req *subreq);
+static int notifyd_snoop_broadcast(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data);
+#endif
+
+struct tevent_req *notifyd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct ctdbd_connection *ctdbd_conn,
+ sys_notify_watch_fn sys_notify_watch,
+ struct sys_notify_context *sys_notify_ctx)
+{
+ struct tevent_req *req;
+#ifdef CLUSTER_SUPPORT
+ struct tevent_req *subreq;
+#endif
+ struct notifyd_state *state;
+ struct server_id_db *names_db;
+ NTSTATUS status;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state, struct notifyd_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->ctdbd_conn = ctdbd_conn;
+
+ if (sys_notify_watch == NULL) {
+ sys_notify_watch = sys_notify_watch_dummy;
+ }
+
+ state->sys_notify_watch = sys_notify_watch;
+ state->sys_notify_ctx = sys_notify_ctx;
+
+ state->entries = db_open_rbt(state);
+ if (tevent_req_nomem(state->entries, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_REC_CHANGE,
+ notifyd_rec_change);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_TRIGGER,
+ notifyd_trigger);
+ if (tevent_req_nterror(req, status)) {
+ goto deregister_rec_change;
+ }
+
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_GET_DB,
+ notifyd_get_db);
+ if (tevent_req_nterror(req, status)) {
+ goto deregister_trigger;
+ }
+
+ names_db = messaging_names_db(msg_ctx);
+
+ ret = server_id_db_set_exclusive(names_db, "notify-daemon");
+ if (ret != 0) {
+ DBG_DEBUG("server_id_db_add failed: %s\n",
+ strerror(ret));
+ tevent_req_error(req, ret);
+ goto deregister_get_db;
+ }
+
+ if (ctdbd_conn == NULL) {
+ /*
+ * No cluster around, skip the database replication
+ * engine
+ */
+ return req;
+ }
+
+#ifdef CLUSTER_SUPPORT
+ status = messaging_register(msg_ctx, state, MSG_SMB_NOTIFY_DB,
+ notifyd_got_db);
+ if (tevent_req_nterror(req, status)) {
+ goto deregister_get_db;
+ }
+
+ state->log = talloc_zero(state, struct messaging_reclog);
+ if (tevent_req_nomem(state->log, req)) {
+ goto deregister_db;
+ }
+
+ subreq = notifyd_broadcast_reclog_send(
+ state->log, ev, ctdbd_conn,
+ messaging_server_id(msg_ctx),
+ state->log);
+ if (tevent_req_nomem(subreq, req)) {
+ goto deregister_db;
+ }
+ tevent_req_set_callback(subreq,
+ notifyd_broadcast_reclog_finished,
+ req);
+
+ subreq = notifyd_clean_peers_send(state, ev, state);
+ if (tevent_req_nomem(subreq, req)) {
+ goto deregister_db;
+ }
+ tevent_req_set_callback(subreq, notifyd_clean_peers_finished,
+ req);
+
+ ret = register_with_ctdbd(ctdbd_conn,
+ CTDB_SRVID_SAMBA_NOTIFY_PROXY,
+ notifyd_snoop_broadcast, state);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ goto deregister_db;
+ }
+#endif
+
+ return req;
+
+#ifdef CLUSTER_SUPPORT
+deregister_db:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_DB, state);
+#endif
+deregister_get_db:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_GET_DB, state);
+deregister_trigger:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_TRIGGER, state);
+deregister_rec_change:
+ messaging_deregister(msg_ctx, MSG_SMB_NOTIFY_REC_CHANGE, state);
+ return tevent_req_post(req, ev);
+}
+
+#ifdef CLUSTER_SUPPORT
+
+static void notifyd_broadcast_reclog_finished(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = notifyd_broadcast_reclog_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_error(req, ret);
+}
+
+static void notifyd_clean_peers_finished(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = notifyd_clean_peers_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_error(req, ret);
+}
+
+#endif
+
+int notifyd_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static bool notifyd_apply_rec_change(
+ const struct server_id *client,
+ const char *path, size_t pathlen,
+ const struct notify_instance *chg,
+ struct db_context *entries,
+ sys_notify_watch_fn sys_notify_watch,
+ struct sys_notify_context *sys_notify_ctx,
+ struct messaging_context *msg_ctx)
+{
+ struct db_record *rec = NULL;
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances;
+ size_t i;
+ struct notifyd_instance *instance = NULL;
+ TDB_DATA value;
+ NTSTATUS status;
+ bool ok = false;
+
+ if (pathlen == 0) {
+ DBG_WARNING("pathlen==0\n");
+ return false;
+ }
+ if (path[pathlen-1] != '\0') {
+ DBG_WARNING("path not 0-terminated\n");
+ return false;
+ }
+
+ DBG_DEBUG("path=%s, filter=%"PRIu32", subdir_filter=%"PRIu32", "
+ "private_data=%p\n",
+ path,
+ chg->filter,
+ chg->subdir_filter,
+ chg->private_data);
+
+ rec = dbwrap_fetch_locked(
+ entries, entries,
+ make_tdb_data((const uint8_t *)path, pathlen-1));
+
+ if (rec == NULL) {
+ DBG_WARNING("dbwrap_fetch_locked failed\n");
+ goto fail;
+ }
+
+ num_instances = 0;
+ value = dbwrap_record_get_value(rec);
+
+ if (value.dsize != 0) {
+ if (!notifyd_parse_entry(value.dptr, value.dsize, NULL,
+ &num_instances)) {
+ goto fail;
+ }
+ }
+
+ /*
+ * Overallocate by one instance to avoid a realloc when adding
+ */
+ instances = talloc_array(rec, struct notifyd_instance,
+ num_instances + 1);
+ if (instances == NULL) {
+ DBG_WARNING("talloc failed\n");
+ goto fail;
+ }
+
+ if (value.dsize != 0) {
+ memcpy(instances, value.dptr, value.dsize);
+ }
+
+ for (i=0; i<num_instances; i++) {
+ instance = &instances[i];
+
+ if (server_id_equal(&instance->client, client) &&
+ (instance->instance.private_data == chg->private_data)) {
+ break;
+ }
+ }
+
+ if (i < num_instances) {
+ instance->instance = *chg;
+ } else {
+ /*
+ * We've overallocated for one instance
+ */
+ instance = &instances[num_instances];
+
+ *instance = (struct notifyd_instance) {
+ .client = *client,
+ .instance = *chg,
+ .internal_filter = chg->filter,
+ .internal_subdir_filter = chg->subdir_filter
+ };
+
+ num_instances += 1;
+ }
+
+ if ((instance->instance.filter != 0) ||
+ (instance->instance.subdir_filter != 0)) {
+ int ret;
+
+ TALLOC_FREE(instance->sys_watch);
+
+ ret = sys_notify_watch(entries, sys_notify_ctx, path,
+ &instance->internal_filter,
+ &instance->internal_subdir_filter,
+ notifyd_sys_callback, msg_ctx,
+ &instance->sys_watch);
+ if (ret != 0) {
+ DBG_WARNING("sys_notify_watch for [%s] returned %s\n",
+ path, strerror(errno));
+ }
+ }
+
+ if ((instance->instance.filter == 0) &&
+ (instance->instance.subdir_filter == 0)) {
+ /* This is a delete request */
+ TALLOC_FREE(instance->sys_watch);
+ *instance = instances[num_instances-1];
+ num_instances -= 1;
+ }
+
+ DBG_DEBUG("%s has %zu instances\n", path, num_instances);
+
+ if (num_instances == 0) {
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_record_delete returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ } else {
+ value = make_tdb_data(
+ (uint8_t *)instances,
+ sizeof(struct notifyd_instance) * num_instances);
+
+ status = dbwrap_record_store(rec, value, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_record_store returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ }
+
+ ok = true;
+fail:
+ TALLOC_FREE(rec);
+ return ok;
+}
+
+static void notifyd_sys_callback(struct sys_notify_context *ctx,
+ void *private_data, struct notify_event *ev,
+ uint32_t filter)
+{
+ struct messaging_context *msg_ctx = talloc_get_type_abort(
+ private_data, struct messaging_context);
+ struct notify_trigger_msg msg;
+ struct iovec iov[4];
+ char slash = '/';
+
+ msg = (struct notify_trigger_msg) {
+ .when = timespec_current(),
+ .action = ev->action,
+ .filter = filter,
+ };
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_trigger_msg, path);
+ iov[1].iov_base = discard_const_p(char, ev->dir);
+ iov[1].iov_len = strlen(ev->dir);
+ iov[2].iov_base = &slash;
+ iov[2].iov_len = 1;
+ iov[3].iov_base = discard_const_p(char, ev->path);
+ iov[3].iov_len = strlen(ev->path)+1;
+
+ messaging_send_iov(
+ msg_ctx, messaging_server_id(msg_ctx),
+ MSG_SMB_NOTIFY_TRIGGER, iov, ARRAY_SIZE(iov), NULL, 0);
+}
+
+static bool notifyd_parse_rec_change(uint8_t *buf, size_t bufsize,
+ struct notify_rec_change_msg **pmsg,
+ size_t *pathlen)
+{
+ struct notify_rec_change_msg *msg;
+
+ if (bufsize < offsetof(struct notify_rec_change_msg, path) + 1) {
+ DBG_WARNING("message too short, ignoring: %zu\n", bufsize);
+ return false;
+ }
+
+ *pmsg = msg = (struct notify_rec_change_msg *)buf;
+ *pathlen = bufsize - offsetof(struct notify_rec_change_msg, path);
+
+ DBG_DEBUG("Got rec_change_msg filter=%"PRIu32", "
+ "subdir_filter=%"PRIu32", private_data=%p, path=%.*s\n",
+ msg->instance.filter,
+ msg->instance.subdir_filter,
+ msg->instance.private_data,
+ (int)(*pathlen),
+ msg->path);
+
+ return true;
+}
+
+static void notifyd_rec_change(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id_buf idbuf;
+ struct notify_rec_change_msg *msg;
+ size_t pathlen;
+ bool ok;
+ struct notify_instance instance;
+
+ DBG_DEBUG("Got %zu bytes from %s\n", data->length,
+ server_id_str_buf(src, &idbuf));
+
+ ok = notifyd_parse_rec_change(data->data, data->length,
+ &msg, &pathlen);
+ if (!ok) {
+ return;
+ }
+
+ memcpy(&instance, &msg->instance, sizeof(instance)); /* avoid SIGBUS */
+
+ ok = notifyd_apply_rec_change(
+ &src, msg->path, pathlen, &instance,
+ state->entries, state->sys_notify_watch, state->sys_notify_ctx,
+ state->msg_ctx);
+ if (!ok) {
+ DBG_DEBUG("notifyd_apply_rec_change failed, ignoring\n");
+ return;
+ }
+
+ if ((state->log == NULL) || (state->ctdbd_conn == NULL)) {
+ return;
+ }
+
+#ifdef CLUSTER_SUPPORT
+ {
+
+ struct messaging_rec **tmp;
+ struct messaging_reclog *log;
+ struct iovec iov = { .iov_base = data->data, .iov_len = data->length };
+
+ log = state->log;
+
+ tmp = talloc_realloc(log, log->recs, struct messaging_rec *,
+ log->num_recs+1);
+ if (tmp == NULL) {
+ DBG_WARNING("talloc_realloc failed, ignoring\n");
+ return;
+ }
+ log->recs = tmp;
+
+ log->recs[log->num_recs] = messaging_rec_create(
+ log->recs, src, messaging_server_id(msg_ctx),
+ msg_type, &iov, 1, NULL, 0);
+
+ if (log->recs[log->num_recs] == NULL) {
+ DBG_WARNING("messaging_rec_create failed, ignoring\n");
+ return;
+ }
+
+ log->num_recs += 1;
+
+ if (log->num_recs >= 100) {
+ /*
+ * Don't let the log grow too large
+ */
+ notifyd_broadcast_reclog(state->ctdbd_conn,
+ messaging_server_id(msg_ctx), log);
+ }
+
+ }
+#endif
+}
+
+struct notifyd_trigger_state {
+ struct messaging_context *msg_ctx;
+ struct notify_trigger_msg *msg;
+ bool recursive;
+ bool covered_by_sys_notify;
+};
+
+static void notifyd_trigger_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data);
+
+static void notifyd_trigger(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id my_id = messaging_server_id(msg_ctx);
+ struct notifyd_trigger_state tstate;
+ const char *path;
+ const char *p, *next_p;
+
+ if (data->length < offsetof(struct notify_trigger_msg, path) + 1) {
+ DBG_WARNING("message too short, ignoring: %zu\n",
+ data->length);
+ return;
+ }
+ if (data->data[data->length-1] != 0) {
+ DBG_WARNING("path not 0-terminated, ignoring\n");;
+ return;
+ }
+
+ tstate.msg_ctx = msg_ctx;
+
+ tstate.covered_by_sys_notify = (src.vnn == my_id.vnn);
+ tstate.covered_by_sys_notify &= !server_id_equal(&src, &my_id);
+
+ tstate.msg = (struct notify_trigger_msg *)data->data;
+ path = tstate.msg->path;
+
+ DBG_DEBUG("Got trigger_msg action=%"PRIu32", filter=%"PRIu32", "
+ "path=%s\n",
+ tstate.msg->action,
+ tstate.msg->filter,
+ path);
+
+ if (path[0] != '/') {
+ DBG_WARNING("path %s does not start with /, ignoring\n",
+ path);
+ return;
+ }
+
+ for (p = strchr(path+1, '/'); p != NULL; p = next_p) {
+ ptrdiff_t path_len = p - path;
+ TDB_DATA key;
+ uint32_t i;
+
+ next_p = strchr(p+1, '/');
+ tstate.recursive = (next_p != NULL);
+
+ DBG_DEBUG("Trying path %.*s\n", (int)path_len, path);
+
+ key = (TDB_DATA) { .dptr = discard_const_p(uint8_t, path),
+ .dsize = path_len };
+
+ dbwrap_parse_record(state->entries, key,
+ notifyd_trigger_parser, &tstate);
+
+ if (state->peers == NULL) {
+ continue;
+ }
+
+ if (src.vnn != my_id.vnn) {
+ continue;
+ }
+
+ for (i=0; i<state->num_peers; i++) {
+ if (state->peers[i]->db == NULL) {
+ /*
+ * Inactive peer, did not get a db yet
+ */
+ continue;
+ }
+ dbwrap_parse_record(state->peers[i]->db, key,
+ notifyd_trigger_parser, &tstate);
+ }
+ }
+}
+
+static void notifyd_send_delete(struct messaging_context *msg_ctx,
+ TDB_DATA key,
+ struct notifyd_instance *instance);
+
+static void notifyd_trigger_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+
+{
+ struct notifyd_trigger_state *tstate = private_data;
+ struct notify_event_msg msg = { .action = tstate->msg->action,
+ .when = tstate->msg->when };
+ struct iovec iov[2];
+ size_t path_len = key.dsize;
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+
+ if (!notifyd_parse_entry(data.dptr, data.dsize, &instances,
+ &num_instances)) {
+ DBG_DEBUG("Could not parse notifyd_entry\n");
+ return;
+ }
+
+ DBG_DEBUG("Found %zu instances for %.*s\n",
+ num_instances,
+ (int)key.dsize,
+ (char *)key.dptr);
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_event_msg, path);
+ iov[1].iov_base = tstate->msg->path + path_len + 1;
+ iov[1].iov_len = strlen((char *)(iov[1].iov_base)) + 1;
+
+ for (i=0; i<num_instances; i++) {
+ struct notifyd_instance *instance = &instances[i];
+ struct server_id_buf idbuf;
+ uint32_t i_filter;
+ NTSTATUS status;
+
+ if (tstate->covered_by_sys_notify) {
+ if (tstate->recursive) {
+ i_filter = instance->internal_subdir_filter;
+ } else {
+ i_filter = instance->internal_filter;
+ }
+ } else {
+ if (tstate->recursive) {
+ i_filter = instance->instance.subdir_filter;
+ } else {
+ i_filter = instance->instance.filter;
+ }
+ }
+
+ if ((i_filter & tstate->msg->filter) == 0) {
+ continue;
+ }
+
+ msg.private_data = instance->instance.private_data;
+
+ status = messaging_send_iov(
+ tstate->msg_ctx, instance->client,
+ MSG_PVFS_NOTIFY, iov, ARRAY_SIZE(iov), NULL, 0);
+
+ DBG_DEBUG("messaging_send_iov to %s returned %s\n",
+ server_id_str_buf(instance->client, &idbuf),
+ nt_errstr(status));
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ procid_is_local(&instance->client)) {
+ /*
+ * That process has died
+ */
+ notifyd_send_delete(tstate->msg_ctx, key, instance);
+ continue;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ }
+ }
+}
+
+/*
+ * Send a delete request to ourselves to properly discard a notify
+ * record for an smbd that has died.
+ */
+
+static void notifyd_send_delete(struct messaging_context *msg_ctx,
+ TDB_DATA key,
+ struct notifyd_instance *instance)
+{
+ struct notify_rec_change_msg msg = {
+ .instance.private_data = instance->instance.private_data
+ };
+ uint8_t nul = 0;
+ struct iovec iov[3];
+ int ret;
+
+ /*
+ * Send a rec_change to ourselves to delete a dead entry
+ */
+
+ iov[0] = (struct iovec) {
+ .iov_base = &msg,
+ .iov_len = offsetof(struct notify_rec_change_msg, path) };
+ iov[1] = (struct iovec) { .iov_base = key.dptr, .iov_len = key.dsize };
+ iov[2] = (struct iovec) { .iov_base = &nul, .iov_len = sizeof(nul) };
+
+ ret = messaging_send_iov_from(
+ msg_ctx, instance->client, messaging_server_id(msg_ctx),
+ MSG_SMB_NOTIFY_REC_CHANGE, iov, ARRAY_SIZE(iov), NULL, 0);
+
+ if (ret != 0) {
+ DBG_WARNING("messaging_send_iov_from returned %s\n",
+ strerror(ret));
+ }
+}
+
+static void notifyd_get_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id_buf id1, id2;
+ NTSTATUS status;
+ uint64_t rec_index = UINT64_MAX;
+ uint8_t index_buf[sizeof(uint64_t)];
+ size_t dbsize;
+ uint8_t *buf;
+ struct iovec iov[2];
+
+ dbsize = dbwrap_marshall(state->entries, NULL, 0);
+
+ buf = talloc_array(talloc_tos(), uint8_t, dbsize);
+ if (buf == NULL) {
+ DBG_WARNING("talloc_array(%zu) failed\n", dbsize);
+ return;
+ }
+
+ dbsize = dbwrap_marshall(state->entries, buf, dbsize);
+
+ if (dbsize != talloc_get_size(buf)) {
+ DBG_DEBUG("dbsize changed: %zu->%zu\n",
+ talloc_get_size(buf),
+ dbsize);
+ TALLOC_FREE(buf);
+ return;
+ }
+
+ if (state->log != NULL) {
+ rec_index = state->log->rec_index;
+ }
+ SBVAL(index_buf, 0, rec_index);
+
+ iov[0] = (struct iovec) { .iov_base = index_buf,
+ .iov_len = sizeof(index_buf) };
+ iov[1] = (struct iovec) { .iov_base = buf,
+ .iov_len = dbsize };
+
+ DBG_DEBUG("Sending %zu bytes to %s->%s\n",
+ iov_buflen(iov, ARRAY_SIZE(iov)),
+ server_id_str_buf(messaging_server_id(msg_ctx), &id1),
+ server_id_str_buf(src, &id2));
+
+ status = messaging_send_iov(msg_ctx, src, MSG_SMB_NOTIFY_DB,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ TALLOC_FREE(buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ }
+}
+
+#ifdef CLUSTER_SUPPORT
+
+static int notifyd_add_proxy_syswatches(struct db_record *rec,
+ void *private_data);
+
+static void notifyd_got_db(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id src, DATA_BLOB *data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct notifyd_peer *p = NULL;
+ struct server_id_buf idbuf;
+ NTSTATUS status;
+ int count;
+ size_t i;
+
+ for (i=0; i<state->num_peers; i++) {
+ if (server_id_equal(&src, &state->peers[i]->pid)) {
+ p = state->peers[i];
+ break;
+ }
+ }
+
+ if (p == NULL) {
+ DBG_DEBUG("Did not find peer for db from %s\n",
+ server_id_str_buf(src, &idbuf));
+ return;
+ }
+
+ if (data->length < 8) {
+ DBG_DEBUG("Got short db length %zu from %s\n", data->length,
+ server_id_str_buf(src, &idbuf));
+ TALLOC_FREE(p);
+ return;
+ }
+
+ p->rec_index = BVAL(data->data, 0);
+
+ p->db = db_open_rbt(p);
+ if (p->db == NULL) {
+ DBG_DEBUG("db_open_rbt failed\n");
+ TALLOC_FREE(p);
+ return;
+ }
+
+ status = dbwrap_unmarshall(p->db, data->data + 8,
+ data->length - 8);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_unmarshall returned %s for db %s\n",
+ nt_errstr(status),
+ server_id_str_buf(src, &idbuf));
+ TALLOC_FREE(p);
+ return;
+ }
+
+ dbwrap_traverse_read(p->db, notifyd_add_proxy_syswatches, state,
+ &count);
+
+ DBG_DEBUG("Database from %s contained %d records\n",
+ server_id_str_buf(src, &idbuf),
+ count);
+}
+
+static void notifyd_broadcast_reclog(struct ctdbd_connection *ctdbd_conn,
+ struct server_id src,
+ struct messaging_reclog *log)
+{
+ enum ndr_err_code ndr_err;
+ uint8_t msghdr[MESSAGE_HDR_LENGTH];
+ DATA_BLOB blob;
+ struct iovec iov[2];
+ int ret;
+
+ if (log == NULL) {
+ return;
+ }
+
+ DBG_DEBUG("rec_index=%"PRIu64", num_recs=%"PRIu32"\n",
+ log->rec_index,
+ log->num_recs);
+
+ message_hdr_put(msghdr, MSG_SMB_NOTIFY_REC_CHANGES, src,
+ (struct server_id) {0 });
+ iov[0] = (struct iovec) { .iov_base = msghdr,
+ .iov_len = sizeof(msghdr) };
+
+ ndr_err = ndr_push_struct_blob(
+ &blob, log, log,
+ (ndr_push_flags_fn_t)ndr_push_messaging_reclog);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_messaging_recs failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+ iov[1] = (struct iovec) { .iov_base = blob.data,
+ .iov_len = blob.length };
+
+ ret = ctdbd_messaging_send_iov(
+ ctdbd_conn, CTDB_BROADCAST_CONNECTED,
+ CTDB_SRVID_SAMBA_NOTIFY_PROXY, iov, ARRAY_SIZE(iov));
+ TALLOC_FREE(blob.data);
+ if (ret != 0) {
+ DBG_WARNING("ctdbd_messaging_send failed: %s\n",
+ strerror(ret));
+ goto done;
+ }
+
+ log->rec_index += 1;
+
+done:
+ log->num_recs = 0;
+ TALLOC_FREE(log->recs);
+}
+
+struct notifyd_broadcast_reclog_state {
+ struct tevent_context *ev;
+ struct ctdbd_connection *ctdbd_conn;
+ struct server_id src;
+ struct messaging_reclog *log;
+};
+
+static void notifyd_broadcast_reclog_next(struct tevent_req *subreq);
+
+static struct tevent_req *notifyd_broadcast_reclog_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct ctdbd_connection *ctdbd_conn, struct server_id src,
+ struct messaging_reclog *log)
+{
+ struct tevent_req *req, *subreq;
+ struct notifyd_broadcast_reclog_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct notifyd_broadcast_reclog_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->ctdbd_conn = ctdbd_conn;
+ state->src = src;
+ state->log = log;
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, notifyd_broadcast_reclog_next, req);
+ return req;
+}
+
+static void notifyd_broadcast_reclog_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notifyd_broadcast_reclog_state *state = tevent_req_data(
+ req, struct notifyd_broadcast_reclog_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ notifyd_broadcast_reclog(state->ctdbd_conn, state->src, state->log);
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(1000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notifyd_broadcast_reclog_next, req);
+}
+
+static int notifyd_broadcast_reclog_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+struct notifyd_clean_peers_state {
+ struct tevent_context *ev;
+ struct notifyd_state *notifyd;
+};
+
+static void notifyd_clean_peers_next(struct tevent_req *subreq);
+
+static struct tevent_req *notifyd_clean_peers_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct notifyd_state *notifyd)
+{
+ struct tevent_req *req, *subreq;
+ struct notifyd_clean_peers_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct notifyd_clean_peers_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->notifyd = notifyd;
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(30000));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, notifyd_clean_peers_next, req);
+ return req;
+}
+
+static void notifyd_clean_peers_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notifyd_clean_peers_state *state = tevent_req_data(
+ req, struct notifyd_clean_peers_state);
+ struct notifyd_state *notifyd = state->notifyd;
+ size_t i;
+ bool ok;
+ time_t now = time(NULL);
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ i = 0;
+ while (i < notifyd->num_peers) {
+ struct notifyd_peer *p = notifyd->peers[i];
+
+ if ((now - p->last_broadcast) > 60) {
+ struct server_id_buf idbuf;
+
+ /*
+ * Haven't heard for more than 60 seconds. Call this
+ * peer dead
+ */
+
+ DBG_DEBUG("peer %s died\n",
+ server_id_str_buf(p->pid, &idbuf));
+ /*
+ * This implicitly decrements notifyd->num_peers
+ */
+ TALLOC_FREE(p);
+ } else {
+ i += 1;
+ }
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ timeval_current_ofs_msec(30000));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notifyd_clean_peers_next, req);
+}
+
+static int notifyd_clean_peers_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static int notifyd_add_proxy_syswatches(struct db_record *rec,
+ void *private_data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct db_context *db = dbwrap_record_get_db(rec);
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA value = dbwrap_record_get_value(rec);
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+ char path[key.dsize+1];
+ bool ok;
+
+ memcpy(path, key.dptr, key.dsize);
+ path[key.dsize] = '\0';
+
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
+ &num_instances);
+ if (!ok) {
+ DBG_WARNING("Could not parse notifyd entry for %s\n", path);
+ return 0;
+ }
+
+ for (i=0; i<num_instances; i++) {
+ struct notifyd_instance *instance = &instances[i];
+ uint32_t filter = instance->instance.filter;
+ uint32_t subdir_filter = instance->instance.subdir_filter;
+ int ret;
+
+ /*
+ * This is a remote database. Pointers that we were
+ * given don't make sense locally. Initialize to NULL
+ * in case sys_notify_watch fails.
+ */
+ instances[i].sys_watch = NULL;
+
+ ret = state->sys_notify_watch(
+ db, state->sys_notify_ctx, path,
+ &filter, &subdir_filter,
+ notifyd_sys_callback, state->msg_ctx,
+ &instance->sys_watch);
+ if (ret != 0) {
+ DBG_WARNING("inotify_watch returned %s\n",
+ strerror(errno));
+ }
+ }
+
+ return 0;
+}
+
+static int notifyd_db_del_syswatches(struct db_record *rec, void *private_data)
+{
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA value = dbwrap_record_get_value(rec);
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+ bool ok;
+
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
+ &num_instances);
+ if (!ok) {
+ DBG_WARNING("Could not parse notifyd entry for %.*s\n",
+ (int)key.dsize, (char *)key.dptr);
+ return 0;
+ }
+ for (i=0; i<num_instances; i++) {
+ TALLOC_FREE(instances[i].sys_watch);
+ }
+ return 0;
+}
+
+static int notifyd_peer_destructor(struct notifyd_peer *p)
+{
+ struct notifyd_state *state = p->state;
+ size_t i;
+
+ if (p->db != NULL) {
+ dbwrap_traverse_read(p->db, notifyd_db_del_syswatches,
+ NULL, NULL);
+ }
+
+ for (i = 0; i<state->num_peers; i++) {
+ if (p == state->peers[i]) {
+ state->peers[i] = state->peers[state->num_peers-1];
+ state->num_peers -= 1;
+ break;
+ }
+ }
+ return 0;
+}
+
+static struct notifyd_peer *notifyd_peer_new(
+ struct notifyd_state *state, struct server_id pid)
+{
+ struct notifyd_peer *p, **tmp;
+
+ tmp = talloc_realloc(state, state->peers, struct notifyd_peer *,
+ state->num_peers+1);
+ if (tmp == NULL) {
+ return NULL;
+ }
+ state->peers = tmp;
+
+ p = talloc_zero(state->peers, struct notifyd_peer);
+ if (p == NULL) {
+ return NULL;
+ }
+ p->state = state;
+ p->pid = pid;
+
+ state->peers[state->num_peers] = p;
+ state->num_peers += 1;
+
+ talloc_set_destructor(p, notifyd_peer_destructor);
+
+ return p;
+}
+
+static void notifyd_apply_reclog(struct notifyd_peer *peer,
+ const uint8_t *msg, size_t msglen)
+{
+ struct notifyd_state *state = peer->state;
+ DATA_BLOB blob = { .data = discard_const_p(uint8_t, msg),
+ .length = msglen };
+ struct server_id_buf idbuf;
+ struct messaging_reclog *log;
+ enum ndr_err_code ndr_err;
+ uint32_t i;
+
+ if (peer->db == NULL) {
+ /*
+ * No db yet
+ */
+ return;
+ }
+
+ log = talloc(peer, struct messaging_reclog);
+ if (log == NULL) {
+ DBG_DEBUG("talloc failed\n");
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob, log, log,
+ (ndr_pull_flags_fn_t)ndr_pull_messaging_reclog);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_messaging_reclog failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto fail;
+ }
+
+ DBG_DEBUG("Got %"PRIu32" recs index %"PRIu64" from %s\n",
+ log->num_recs,
+ log->rec_index,
+ server_id_str_buf(peer->pid, &idbuf));
+
+ if (log->rec_index != peer->rec_index) {
+ DBG_INFO("Got rec index %"PRIu64" from %s, "
+ "expected %"PRIu64"\n",
+ log->rec_index,
+ server_id_str_buf(peer->pid, &idbuf),
+ peer->rec_index);
+ goto fail;
+ }
+
+ for (i=0; i<log->num_recs; i++) {
+ struct messaging_rec *r = log->recs[i];
+ struct notify_rec_change_msg *chg;
+ size_t pathlen;
+ bool ok;
+ struct notify_instance instance;
+
+ ok = notifyd_parse_rec_change(r->buf.data, r->buf.length,
+ &chg, &pathlen);
+ if (!ok) {
+ DBG_INFO("notifyd_parse_rec_change failed\n");
+ goto fail;
+ }
+
+ /* avoid SIGBUS */
+ memcpy(&instance, &chg->instance, sizeof(instance));
+
+ ok = notifyd_apply_rec_change(&r->src, chg->path, pathlen,
+ &instance, peer->db,
+ state->sys_notify_watch,
+ state->sys_notify_ctx,
+ state->msg_ctx);
+ if (!ok) {
+ DBG_INFO("notifyd_apply_rec_change failed\n");
+ goto fail;
+ }
+ }
+
+ peer->rec_index += 1;
+ peer->last_broadcast = time(NULL);
+
+ TALLOC_FREE(log);
+ return;
+
+fail:
+ DBG_DEBUG("Dropping peer %s\n",
+ server_id_str_buf(peer->pid, &idbuf));
+ TALLOC_FREE(peer);
+}
+
+/*
+ * Receive messaging_reclog (log of MSG_SMB_NOTIFY_REC_CHANGE
+ * messages) broadcasts by other notifyds. Several cases:
+ *
+ * We don't know the source. This creates a new peer. Creating a peer
+ * involves asking the peer for its full database. We assume ordered
+ * messages, so the new database will arrive before the next broadcast
+ * will.
+ *
+ * We know the source and the log index matches. We will apply the log
+ * locally to our peer's db as if we had received it from a local
+ * client.
+ *
+ * We know the source but the log index does not match. This means we
+ * lost a message. We just drop the whole peer and wait for the next
+ * broadcast, which will then trigger a fresh database pull.
+ */
+
+static int notifyd_snoop_broadcast(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data)
+{
+ struct notifyd_state *state = talloc_get_type_abort(
+ private_data, struct notifyd_state);
+ struct server_id my_id = messaging_server_id(state->msg_ctx);
+ struct notifyd_peer *p;
+ uint32_t i;
+ uint32_t msg_type;
+ struct server_id src, dst;
+ struct server_id_buf idbuf;
+ NTSTATUS status;
+
+ if (msglen < MESSAGE_HDR_LENGTH) {
+ DBG_DEBUG("Got short broadcast\n");
+ return 0;
+ }
+ message_hdr_get(&msg_type, &src, &dst, msg);
+
+ if (msg_type != MSG_SMB_NOTIFY_REC_CHANGES) {
+ DBG_DEBUG("Got message %"PRIu32", ignoring\n", msg_type);
+ return 0;
+ }
+ if (server_id_equal(&src, &my_id)) {
+ DBG_DEBUG("Ignoring my own broadcast\n");
+ return 0;
+ }
+
+ DBG_DEBUG("Got MSG_SMB_NOTIFY_REC_CHANGES from %s\n",
+ server_id_str_buf(src, &idbuf));
+
+ for (i=0; i<state->num_peers; i++) {
+ if (server_id_equal(&state->peers[i]->pid, &src)) {
+
+ DBG_DEBUG("Applying changes to peer %"PRIu32"\n", i);
+
+ notifyd_apply_reclog(state->peers[i],
+ msg + MESSAGE_HDR_LENGTH,
+ msglen - MESSAGE_HDR_LENGTH);
+ return 0;
+ }
+ }
+
+ DBG_DEBUG("Creating new peer for %s\n",
+ server_id_str_buf(src, &idbuf));
+
+ p = notifyd_peer_new(state, src);
+ if (p == NULL) {
+ DBG_DEBUG("notifyd_peer_new failed\n");
+ return 0;
+ }
+
+ status = messaging_send_buf(state->msg_ctx, src, MSG_SMB_NOTIFY_GET_DB,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_buf failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(p);
+ return 0;
+ }
+
+ return 0;
+}
+#endif
diff --git a/source3/smbd/notifyd/notifyd.h b/source3/smbd/notifyd/notifyd.h
new file mode 100644
index 0000000..5ef8c4c
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd.h
@@ -0,0 +1,145 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NOTIFYD_NOTIFYD_H__
+#define __NOTIFYD_NOTIFYD_H__
+
+#include "includes.h"
+#include "librpc/gen_ndr/notify.h"
+#include "librpc/gen_ndr/messaging.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "messages.h"
+#include "tdb.h"
+#include "util_tdb.h"
+
+/*
+ * Filechangenotify based on asynchronous messages
+ *
+ * smbds talk to local notify daemons to inform them about paths they are
+ * interested in. They also tell local notify daemons about changes they have
+ * done to the file system. There's two message types from smbd to
+ * notifyd. The first is used to inform notifyd about changes in notify
+ * interest. These are only sent from smbd to notifyd if the SMB client issues
+ * FileChangeNotify requests.
+ */
+
+/*
+ * The notifyd implementation is designed to cope with multiple daemons taking
+ * care of just a subset of smbds. The goal is to minimize the traffic between
+ * the notify daemons. The idea behind this is a samba/ctdb cluster, but it
+ * could also be used to spread the load of notifyd instances on a single
+ * node, should this become a bottleneck. The following diagram illustrates
+ * the setup. The numbers in the boxes are node:process ids.
+ *
+ * +-----------+ +-----------+
+ * |notifyd 0:5|------------------|notifyd 1:6|
+ * +-----------+ +-----------+
+ * / | \ / \
+ * / | \ / \
+ * +--------+ | +--------+ +--------+ +--------+
+ * |smbd 0:1| | |smbd 0:4| |smbd 1:7| |smbd 1:2|
+ * +--------+ | +--------+ +--------+ +--------+
+ * |
+ * +---------+
+ * |smbd 0:20|
+ * +---------+
+ *
+ * Suppose 0:1 and 0:4 are interested in changes for /foo and 0:20 creates the
+ * file /foo/bar, if everything fully connected, 0:20 would have to send two
+ * local messages, one to 0:1 and one to 0:4. With the notifyd design, 0:20
+ * only has to send one message, it lets notifyd 0:5 do the hard work to
+ * multicast the change to 0:1 and 0:4.
+ *
+ * Now lets assume 1:7 on the other node creates /foo/baz. It tells its
+ * notifyd 1:6 about this change. All 1:6 will know about is that its peer
+ * notifyd 0:5 is interested in the change. Thus it forwards the event to 0:5,
+ * which sees it as if it came from just another local event creator. 0:5 will
+ * multicast the change to 0:1 and 0:4. To prevent notify loops, the message
+ * from 1:6 to 0:5 will carry a "proxied" flag, so that 0:5 will only forward
+ * the event to local clients.
+ */
+
+/*
+ * Data that notifyd maintains per smbd notify instance
+ */
+struct notify_instance {
+ struct timespec creation_time;
+ uint32_t filter;
+ uint32_t subdir_filter;
+ void *private_data;
+};
+
+/* MSG_SMB_NOTIFY_REC_CHANGE payload */
+struct notify_rec_change_msg {
+ struct notify_instance instance;
+ char path[];
+};
+
+/*
+ * The second message from smbd to notifyd is sent whenever an smbd makes a
+ * file system change. It tells notifyd to inform all interested parties about
+ * that change. This is the message that needs to be really fast in smbd
+ * because it is called a lot.
+ */
+
+/* MSG_SMB_NOTIFY_TRIGGER payload */
+struct notify_trigger_msg {
+ struct timespec when;
+ uint32_t action;
+ uint32_t filter;
+ char path[];
+};
+
+/*
+ * In response to a MSG_SMB_NOTIFY_TRIGGER message notifyd walks its database
+ * and sends out the following message to all interested clients
+ */
+
+/* MSG_PVFS_NOTIFY payload */
+struct notify_event_msg {
+ struct timespec when;
+ void *private_data;
+ uint32_t action;
+ char path[];
+};
+
+struct sys_notify_context;
+struct ctdbd_connection;
+
+typedef int (*sys_notify_watch_fn)(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p);
+
+struct tevent_req *notifyd_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct ctdbd_connection *ctdbd_conn,
+ sys_notify_watch_fn sys_notify_watch,
+ struct sys_notify_context *sys_notify_ctx);
+int notifyd_recv(struct tevent_req *req);
+
+#endif
diff --git a/source3/smbd/notifyd/notifyd_db.c b/source3/smbd/notifyd/notifyd_db.c
new file mode 100644
index 0000000..1822861
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_db.c
@@ -0,0 +1,165 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "lib/util/debug.h"
+#include "lib/util/server_id_db.h"
+#include "notifyd_private.h"
+#include "notifyd_db.h"
+
+struct notifyd_parse_db_state {
+ bool (*fn)(const char *path,
+ struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data);
+ void *private_data;
+};
+
+static bool notifyd_parse_db_parser(TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct notifyd_parse_db_state *state = private_data;
+ char path[key.dsize+1];
+ struct notifyd_instance *instances = NULL;
+ size_t num_instances = 0;
+ size_t i;
+ bool ok;
+
+ memcpy(path, key.dptr, key.dsize);
+ path[key.dsize] = 0;
+
+ ok = notifyd_parse_entry(value.dptr, value.dsize, &instances,
+ &num_instances);
+ if (!ok) {
+ DBG_DEBUG("Could not parse entry for path %s\n", path);
+ return true;
+ }
+
+ for (i=0; i<num_instances; i++) {
+ ok = state->fn(path, instances[i].client,
+ &instances[i].instance,
+ state->private_data);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static NTSTATUS notifyd_parse_db(
+ const uint8_t *buf,
+ size_t buflen,
+ uint64_t *log_index,
+ bool (*fn)(const char *path,
+ struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data),
+ void *private_data)
+{
+ struct notifyd_parse_db_state state = {
+ .fn = fn, .private_data = private_data
+ };
+ NTSTATUS status;
+
+ if (buflen < 8) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ *log_index = BVAL(buf, 0);
+
+ buf += 8;
+ buflen -= 8;
+
+ status = dbwrap_parse_marshall_buf(
+ buf, buflen, notifyd_parse_db_parser, &state);
+ return status;
+}
+
+NTSTATUS notify_walk(struct messaging_context *msg_ctx,
+ bool (*fn)(const char *path, struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data),
+ void *private_data)
+{
+ struct server_id_db *names_db = NULL;
+ struct server_id notifyd = { .pid = 0, };
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ struct messaging_rec *rec = NULL;
+ uint64_t log_idx;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ names_db = messaging_names_db(msg_ctx);
+ ok = server_id_db_lookup_one(names_db, "notify-daemon", &notifyd);
+ if (!ok) {
+ DBG_WARNING("No notify daemon around\n");
+ return NT_STATUS_SERVER_UNAVAILABLE;
+ }
+
+ ev = samba_tevent_context_init(msg_ctx);
+ if (ev == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ req = messaging_read_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY_DB);
+ if (req == NULL) {
+ TALLOC_FREE(ev);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ok = tevent_req_set_endtime(req, ev, timeval_current_ofs(10, 0));
+ if (!ok) {
+ TALLOC_FREE(ev);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = messaging_send_buf(
+ msg_ctx, notifyd, MSG_SMB_NOTIFY_GET_DB, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_buf failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ev);
+ return status;
+ }
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_DEBUG("tevent_req_poll failed\n");
+ TALLOC_FREE(ev);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ret = messaging_read_recv(req, ev, &rec);
+ if (ret != 0) {
+ DBG_DEBUG("messaging_read_recv failed: %s\n",
+ strerror(ret));
+ TALLOC_FREE(ev);
+ return map_nt_error_from_unix(ret);
+ }
+
+ status = notifyd_parse_db(
+ rec->buf.data, rec->buf.length, &log_idx, fn, private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("notifyd_parse_db failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ev);
+ return status;
+ }
+
+ TALLOC_FREE(ev);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/notifyd/notifyd_db.h b/source3/smbd/notifyd/notifyd_db.h
new file mode 100644
index 0000000..bd9e226
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_db.h
@@ -0,0 +1,27 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NOTIFYD_NOTIFYD_DB_H__
+#define __NOTIFYD_NOTIFYD_DB_H__
+#include "replace.h"
+#include "notifyd.h"
+
+NTSTATUS notify_walk(struct messaging_context *msg_ctx,
+ bool (*fn)(const char *path, struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data),
+ void *private_data);
+
+#endif
diff --git a/source3/smbd/notifyd/notifyd_entry.c b/source3/smbd/notifyd/notifyd_entry.c
new file mode 100644
index 0000000..539010d
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_entry.c
@@ -0,0 +1,42 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "lib/util/debug.h"
+#include "notifyd_private.h"
+
+/*
+ * Parse an entry in the notifyd_context->entries database
+ */
+
+bool notifyd_parse_entry(
+ uint8_t *buf,
+ size_t buflen,
+ struct notifyd_instance **instances,
+ size_t *num_instances)
+{
+ if ((buflen % sizeof(struct notifyd_instance)) != 0) {
+ DBG_WARNING("invalid buffer size: %zu\n", buflen);
+ return false;
+ }
+
+ if (instances != NULL) {
+ *instances = (struct notifyd_instance *)buf;
+ }
+ if (num_instances != NULL) {
+ *num_instances = buflen / sizeof(struct notifyd_instance);
+ }
+ return true;
+}
diff --git a/source3/smbd/notifyd/notifyd_private.h b/source3/smbd/notifyd/notifyd_private.h
new file mode 100644
index 0000000..36c08f4
--- /dev/null
+++ b/source3/smbd/notifyd/notifyd_private.h
@@ -0,0 +1,49 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NOTIFYD_PRIVATE_H__
+#define __NOTIFYD_PRIVATE_H__
+
+#include "replace.h"
+#include "lib/util/server_id.h"
+#include "notifyd.h"
+
+/*
+ * notifyd's representation of a notify instance
+ */
+struct notifyd_instance {
+ struct server_id client;
+ struct notify_instance instance;
+
+ void *sys_watch; /* inotify/fam/etc handle */
+
+ /*
+ * Filters after sys_watch took responsibility of some bits
+ */
+ uint32_t internal_filter;
+ uint32_t internal_subdir_filter;
+};
+
+/*
+ * Parse an entry in the notifyd_context->entries database
+ */
+
+bool notifyd_parse_entry(
+ uint8_t *buf,
+ size_t buflen,
+ struct notifyd_instance **instances,
+ size_t *num_instances);
+
+#endif
diff --git a/source3/smbd/notifyd/notifydd.c b/source3/smbd/notifyd/notifydd.c
new file mode 100644
index 0000000..b27a4ab
--- /dev/null
+++ b/source3/smbd/notifyd/notifydd.c
@@ -0,0 +1,98 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "notifyd.h"
+#include "lib/messages_ctdb.h"
+#include <tevent.h>
+#include "lib/util/tevent_unix.h"
+#include "lib/param/param.h"
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *frame;
+ struct loadparm_context *lp_ctx = NULL;
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct tevent_req *req;
+ int err, ret;
+ bool ok;
+
+ talloc_enable_leak_report_full();
+
+ frame = talloc_stackframe();
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ fprintf(stderr,
+ "Failed to initialise the global parameter structure.\n");
+ return 1;
+ }
+
+ setup_logging("notifyd", DEBUG_DEFAULT_STDOUT);
+ lpcfg_set_cmdline(lp_ctx, "log level", "10");
+
+ ok = lp_load_initial_only(get_dyn_CONFIGFILE());
+ if (!ok) {
+ fprintf(stderr, "Can't load %s - run testparm to debug it\n",
+ get_dyn_CONFIGFILE());
+ return 1;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ fprintf(stderr, "samba_tevent_context_init failed\n");
+ return 1;
+ }
+
+ msg = messaging_init(ev, ev);
+ if (msg == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ return 1;
+ }
+
+ if (!lp_load_global(get_dyn_CONFIGFILE())) {
+ fprintf(stderr, "Can't load %s - run testparm to debug it\n",
+ get_dyn_CONFIGFILE());
+ return 1;
+ }
+
+ req = notifyd_send(ev, ev, msg, messaging_ctdb_connection(),
+ NULL, NULL);
+ if (req == NULL) {
+ fprintf(stderr, "notifyd_send failed\n");
+ return 1;
+ }
+
+ ok = tevent_req_poll_unix(req, ev, &err);
+ if (!ok) {
+ fprintf(stderr, "tevent_req_poll_unix failed: %s\n",
+ strerror(err));
+ return 1;
+ }
+
+ ret = notifyd_recv(req);
+
+ printf("notifyd_recv returned %d (%s)\n", ret,
+ ret ? strerror(ret) : "ok");
+
+ TALLOC_FREE(frame);
+
+ return 0;
+}
diff --git a/source3/smbd/notifyd/test_notifyd.c b/source3/smbd/notifyd/test_notifyd.c
new file mode 100644
index 0000000..431da9b
--- /dev/null
+++ b/source3/smbd/notifyd/test_notifyd.c
@@ -0,0 +1,347 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "fcn_wait.h"
+#include "notifyd.h"
+#include "notifyd_db.h"
+#include "messages.h"
+#include "lib/util/server_id.h"
+#include "lib/util/server_id_db.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/torture/torture.h"
+#include "torture/local/proto.h"
+#include "lib/param/loadparm.h"
+#include "source3/param/loadparm.h"
+#include "source4/torture/smbtorture.h"
+
+struct fcn_test_state {
+ struct tevent_req *fcn_req;
+ bool got_trigger;
+};
+
+static void fcn_test_done(struct tevent_req *subreq);
+
+static struct tevent_req *fcn_test_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *fcn_path,
+ uint32_t fcn_filter,
+ uint32_t fcn_subdir_filter,
+ const char *trigger_path,
+ uint32_t trigger_action,
+ uint32_t trigger_filter)
+{
+ struct tevent_req *req = NULL;
+ struct fcn_test_state *state = NULL;
+ struct notify_trigger_msg msg;
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct fcn_test_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->fcn_req = fcn_wait_send(
+ state,
+ ev,
+ msg_ctx,
+ notifyd,
+ fcn_path,
+ fcn_filter,
+ fcn_subdir_filter);
+ if (tevent_req_nomem(state->fcn_req, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->fcn_req, fcn_test_done, req);
+
+ msg = (struct notify_trigger_msg) {
+ .when = timespec_current(),
+ .action = trigger_action,
+ .filter = trigger_filter,
+ };
+ iov[0] = (struct iovec) {
+ .iov_base = &msg,
+ .iov_len = offsetof(struct notify_trigger_msg, path),
+ };
+ iov[1] = (struct iovec) {
+ .iov_base = discard_const_p(char, trigger_path),
+ .iov_len = strlen(trigger_path)+1,
+ };
+
+ status = messaging_send_iov(
+ msg_ctx,
+ notifyd,
+ MSG_SMB_NOTIFY_TRIGGER,
+ iov,
+ ARRAY_SIZE(iov),
+ NULL,
+ 0);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void fcn_test_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fcn_test_state *state = tevent_req_data(
+ req, struct fcn_test_state);
+ NTSTATUS status;
+ bool ok;
+
+ SMB_ASSERT(subreq == state->fcn_req);
+
+ status = fcn_wait_recv(subreq, NULL, NULL, NULL, NULL);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED)) {
+ TALLOC_FREE(subreq);
+ state->fcn_req = NULL;
+ tevent_req_done(req);
+ return;
+ }
+
+ if (tevent_req_nterror(req, status)) {
+ TALLOC_FREE(subreq);
+ state->fcn_req = NULL;
+ return;
+ }
+
+ state->got_trigger = true;
+
+ ok = tevent_req_cancel(subreq);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+}
+
+static NTSTATUS fcn_test_recv(struct tevent_req *req, bool *got_trigger)
+{
+ struct fcn_test_state *state = tevent_req_data(
+ req, struct fcn_test_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ if (got_trigger != NULL) {
+ *got_trigger = state->got_trigger;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fcn_test(
+ struct messaging_context *msg_ctx,
+ struct server_id notifyd,
+ const char *fcn_path,
+ uint32_t fcn_filter,
+ uint32_t fcn_subdir_filter,
+ const char *trigger_path,
+ uint32_t trigger_action,
+ uint32_t trigger_filter,
+ bool *got_trigger)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(msg_ctx);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = fcn_test_send(
+ ev,
+ ev,
+ msg_ctx,
+ notifyd,
+ fcn_path,
+ fcn_filter,
+ fcn_subdir_filter,
+ trigger_path,
+ trigger_action,
+ trigger_filter);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = fcn_test_recv(req, got_trigger);
+fail:
+ TALLOC_FREE(ev);
+ return status;
+}
+
+static bool test_notifyd_trigger1(struct torture_context *tctx)
+{
+ struct messaging_context *msg_ctx = NULL;
+ struct server_id_db *names = NULL;
+ struct server_id notifyd;
+ NTSTATUS status;
+ bool got_trigger = false;
+ bool ok;
+
+ /*
+ * Basic filechangenotify test: Wait for /home, trigger on
+ * /home/foo, check an event was received
+ */
+
+ lp_load_global(tctx->lp_ctx->szConfigFile);
+
+ msg_ctx = messaging_init(tctx, tctx->ev);
+ torture_assert_not_null(tctx, msg_ctx, "messaging_init");
+
+ names = messaging_names_db(msg_ctx);
+ ok = server_id_db_lookup_one(names, "notify-daemon", &notifyd);
+ torture_assert(tctx, ok, "server_id_db_lookup_one");
+
+ status = fcn_test(
+ msg_ctx,
+ notifyd,
+ "/home",
+ UINT32_MAX,
+ UINT32_MAX,
+ "/home/foo",
+ UINT32_MAX,
+ UINT32_MAX,
+ &got_trigger);
+ torture_assert_ntstatus_ok(tctx, status, "fcn_test");
+ torture_assert(tctx, got_trigger, "got_trigger");
+
+ return true;
+}
+
+struct notifyd_have_state {
+ struct server_id self;
+ bool found;
+};
+
+static bool notifyd_have_fn(
+ const char *path,
+ struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data)
+{
+ struct notifyd_have_state *state = private_data;
+ state->found |= server_id_equal(&server, &state->self);
+ return true;
+}
+
+static bool notifyd_have_self(struct messaging_context *msg_ctx)
+{
+ struct notifyd_have_state state = {
+ .self = messaging_server_id(msg_ctx),
+ };
+ NTSTATUS status;
+
+ status = notify_walk(msg_ctx, notifyd_have_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ return state.found;
+}
+
+static bool test_notifyd_dbtest1(struct torture_context *tctx)
+{
+ struct tevent_context *ev = tctx->ev;
+ struct messaging_context *msg_ctx = NULL;
+ struct tevent_req *req = NULL;
+ struct server_id_db *names = NULL;
+ struct server_id notifyd;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure fcn_wait_send adds us to the notifyd internal
+ * database and that cancelling the fcn request removes us
+ * again.
+ */
+
+ lp_load_global(tctx->lp_ctx->szConfigFile);
+
+ msg_ctx = messaging_init(tctx, ev);
+ torture_assert_not_null(tctx, msg_ctx, "messaging_init");
+
+ names = messaging_names_db(msg_ctx);
+ ok = server_id_db_lookup_one(names, "notify-daemon", &notifyd);
+ torture_assert(tctx, ok, "server_id_db_lookup_one");
+
+ req = fcn_wait_send(
+ msg_ctx, ev, msg_ctx, notifyd, "/x", UINT32_MAX, UINT32_MAX);
+ torture_assert_not_null(tctx, req, "fcn_wait_send");
+
+ ok = notifyd_have_self(msg_ctx);
+ torture_assert(tctx, ok, "notifyd_have_self");
+
+ ok = tevent_req_cancel(req);
+ torture_assert(tctx, ok, "tevent_req_cancel");
+
+ ok = tevent_req_poll(req, ev);
+ torture_assert(tctx, ok, "tevent_req_poll");
+
+ status = fcn_wait_recv(req, NULL, NULL, NULL, NULL);
+ torture_assert_ntstatus_equal(
+ tctx, status, NT_STATUS_CANCELLED, "fcn_wait_recv");
+ TALLOC_FREE(req);
+
+ ok = notifyd_have_self(msg_ctx);
+ torture_assert(tctx, !ok, "tevent_req_poll");
+ TALLOC_FREE(msg_ctx);
+
+ return true;
+}
+
+NTSTATUS torture_notifyd_init(TALLOC_CTX *mem_ctx);
+NTSTATUS torture_notifyd_init(TALLOC_CTX *mem_ctx)
+{
+ struct torture_suite *suite = NULL;
+ struct torture_tcase *tcase = NULL;
+ bool ok;
+
+ suite = torture_suite_create(mem_ctx, "notifyd");
+ if (suite == NULL) {
+ goto fail;
+ }
+
+ tcase = torture_suite_add_simple_test(
+ suite, "trigger1", test_notifyd_trigger1);
+ if (tcase == NULL) {
+ goto fail;
+ }
+
+ tcase = torture_suite_add_simple_test(
+ suite, "dbtest1", test_notifyd_dbtest1);
+ if (tcase == NULL) {
+ goto fail;
+ }
+ suite->description = "notifyd unit tests";
+
+ ok = torture_register_suite(mem_ctx, suite);
+ if (!ok) {
+ goto fail;
+ }
+ return NT_STATUS_OK;
+fail:
+ TALLOC_FREE(suite);
+ return NT_STATUS_NO_MEMORY;
+}
diff --git a/source3/smbd/notifyd/tests.c b/source3/smbd/notifyd/tests.c
new file mode 100644
index 0000000..6bcce6a
--- /dev/null
+++ b/source3/smbd/notifyd/tests.c
@@ -0,0 +1,118 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "notifyd.h"
+#include "messages.h"
+#include "lib/util/server_id_db.h"
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct server_id_db *names;
+ struct server_id notifyd;
+ struct tevent_req *req;
+ unsigned i;
+ bool ok;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <smb.conf-file>\n", argv[0]);
+ exit(1);
+ }
+
+ setup_logging(argv[0], DEBUG_STDOUT);
+ lp_load_global(argv[1]);
+
+ ev = tevent_context_init(NULL);
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ exit(1);
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ exit(1);
+ }
+
+ names = messaging_names_db(msg_ctx);
+
+ ok = server_id_db_lookup_one(names, "notify-daemon", &notifyd);
+ if (!ok) {
+ fprintf(stderr, "no notifyd\n");
+ exit(1);
+ }
+
+ for (i=0; i<50000; i++) {
+ struct notify_rec_change_msg msg = {
+ .instance.filter = UINT32_MAX,
+ .instance.subdir_filter = UINT32_MAX
+ };
+ char path[64];
+ size_t len;
+ struct iovec iov[2];
+ NTSTATUS status;
+
+ len = snprintf(path, sizeof(path), "/tmp%u", i);
+
+ iov[0].iov_base = &msg;
+ iov[0].iov_len = offsetof(struct notify_rec_change_msg, path);
+ iov[1].iov_base = path;
+ iov[1].iov_len = len+1;
+
+ status = messaging_send_iov(
+ msg_ctx, notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+
+ msg.instance.filter = 0;
+ msg.instance.subdir_filter = 0;
+
+ status = messaging_send_iov(
+ msg_ctx, notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "messaging_send_iov returned %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+ }
+
+ req = messaging_read_send(ev, ev, msg_ctx, MSG_PONG);
+ if (req == NULL) {
+ fprintf(stderr, "messaging_read_send failed\n");
+ exit(1);
+ }
+ messaging_send_buf(msg_ctx, notifyd, MSG_PING, NULL, 0);
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ fprintf(stderr, "tevent_req_poll failed\n");
+ exit(1);
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/smbd/notifyd/wscript_build b/source3/smbd/notifyd/wscript_build
new file mode 100644
index 0000000..6880a31
--- /dev/null
+++ b/source3/smbd/notifyd/wscript_build
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_SUBSYSTEM('fcn_wait',
+ source='fcn_wait.c',
+ deps='samba3core')
+
+bld.SAMBA3_SUBSYSTEM('notifyd_db',
+ source='notifyd_entry.c notifyd_db.c',
+ deps='samba-debug dbwrap errors3')
+
+bld.SAMBA3_SUBSYSTEM('notifyd',
+ source='notifyd.c',
+ deps='''
+ util_tdb
+ TDB_LIB
+ messages_util
+ notifyd_db
+ ''')
+
+bld.SAMBA3_BINARY('notifyd-tests',
+ source='tests.c',
+ install=False,
+ deps='''
+ smbconf
+ ''')
+
+bld.SAMBA3_BINARY('notifydd',
+ source='notifydd.c',
+ install=False,
+ deps='''notifyd
+ smbconf
+ ''')
+
+TORTURE_NOTIFYD_SOURCE='test_notifyd.c'
+TORTURE_NOTIFYD_DEPS='fcn_wait notifyd_db'
+
+bld.SAMBA_MODULE('TORTURE_NOTIFYD',
+ source=TORTURE_NOTIFYD_SOURCE,
+ subsystem='smbtorture',
+ init_function='torture_notifyd_init',
+ deps=TORTURE_NOTIFYD_DEPS,
+ internal_module=True,
+ enabled=bld.PYTHON_BUILD_IS_ENABLED()
+ )
diff --git a/source3/smbd/ntquotas.c b/source3/smbd/ntquotas.c
new file mode 100644
index 0000000..5705a4f
--- /dev/null
+++ b/source3/smbd/ntquotas.c
@@ -0,0 +1,265 @@
+/*
+ Unix SMB/CIFS implementation.
+ NT QUOTA support
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "../lib/util/util_pw.h"
+#include "system/passwd.h"
+#include "passdb/lookup_sid.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/dom_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+static uint64_t limit_nt2unix(uint64_t in, uint64_t bsize)
+{
+ uint64_t ret = (uint64_t)0;
+
+ ret = (uint64_t)(in/bsize);
+ if (in>0 && ret==0) {
+ /* we have to make sure that a overflow didn't set NO_LIMIT */
+ ret = (uint64_t)1;
+ }
+
+ if (in == SMB_NTQUOTAS_NO_LIMIT)
+ ret = SMB_QUOTAS_NO_LIMIT;
+ else if (in == SMB_NTQUOTAS_NO_SPACE)
+ ret = SMB_QUOTAS_NO_SPACE;
+ else if (in == SMB_NTQUOTAS_NO_ENTRY)
+ ret = SMB_QUOTAS_NO_LIMIT;
+
+ return ret;
+}
+
+static uint64_t limit_unix2nt(uint64_t in, uint64_t bsize)
+{
+ uint64_t ret = (uint64_t)0;
+
+ ret = (uint64_t)(in*bsize);
+
+ return ret;
+}
+
+NTSTATUS vfs_get_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype,
+ struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt)
+{
+ int ret;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+ struct smb_filename *smb_fname_cwd = NULL;
+ int saved_errno = 0;
+
+ ZERO_STRUCT(D);
+
+ if (!fsp || !fsp->conn || !qt) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ZERO_STRUCT(*qt);
+
+ id.uid = -1;
+
+ if (psid && !sid_to_uid(psid, &id.uid)) {
+ struct dom_sid_buf buf;
+ DEBUG(0,("sid_to_uid: failed, SID[%s]\n",
+ dom_sid_str_buf(psid, &buf)));
+ return NT_STATUS_NO_SUCH_USER;
+ }
+
+ smb_fname_cwd = synthetic_smb_fname(talloc_tos(),
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname_cwd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_GET_QUOTA(fsp->conn, smb_fname_cwd, qtype, id, &D);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+ TALLOC_FREE(smb_fname_cwd);
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+
+ if (psid)
+ qt->sid = *psid;
+
+ if (ret!=0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ qt->usedspace = (uint64_t)D.curblocks*D.bsize;
+ qt->softlim = limit_unix2nt(D.softlimit, D.bsize);
+ qt->hardlim = limit_unix2nt(D.hardlimit, D.bsize);
+ qt->qflags = D.qflags;
+
+ return NT_STATUS_OK;
+}
+
+int vfs_set_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype, struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt)
+{
+ int ret;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+ ZERO_STRUCT(D);
+
+ if (!fsp||!fsp->conn||!qt)
+ return (-1);
+
+ id.uid = -1;
+
+ D.bsize = (uint64_t)QUOTABLOCK_SIZE;
+
+ D.softlimit = limit_nt2unix(qt->softlim,D.bsize);
+ D.hardlimit = limit_nt2unix(qt->hardlim,D.bsize);
+ D.qflags = qt->qflags;
+
+ if (psid && !sid_to_uid(psid, &id.uid)) {
+ struct dom_sid_buf buf;
+ DEBUG(0,("sid_to_uid: failed, SID[%s]\n",
+ dom_sid_str_buf(psid, &buf)));
+ }
+
+ ret = SMB_VFS_SET_QUOTA(fsp->conn, qtype, id, &D);
+
+ return ret;
+}
+
+static bool already_in_quota_list(SMB_NTQUOTA_LIST *qt_list, uid_t uid)
+{
+ SMB_NTQUOTA_LIST *tmp_list = NULL;
+
+ if (!qt_list)
+ return False;
+
+ for (tmp_list=qt_list;tmp_list!=NULL;tmp_list=tmp_list->next) {
+ if (tmp_list->uid == uid) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+int vfs_get_user_ntquota_list(files_struct *fsp, SMB_NTQUOTA_LIST **qt_list)
+{
+ struct passwd *usr;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ if (!fsp||!fsp->conn||!qt_list)
+ return (-1);
+
+ *qt_list = NULL;
+
+ if ((mem_ctx=talloc_init("SMB_USER_QUOTA_LIST"))==NULL) {
+ DEBUG(0,("talloc_init() failed\n"));
+ return (-1);
+ }
+
+ setpwent();
+ while ((usr = getpwent()) != NULL) {
+ SMB_NTQUOTA_STRUCT tmp_qt;
+ SMB_NTQUOTA_LIST *tmp_list_ent;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ ZERO_STRUCT(tmp_qt);
+
+ if (already_in_quota_list((*qt_list),usr->pw_uid)) {
+ DEBUG(5,("record for uid[%ld] already in the list\n",(long)usr->pw_uid));
+ continue;
+ }
+
+ uid_to_sid(&sid, usr->pw_uid);
+
+ status =
+ vfs_get_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &tmp_qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("failed getting quota for uid[%ld] - %s\n",
+ (long)usr->pw_uid, nt_errstr(status)));
+ continue;
+ }
+ if (tmp_qt.softlim == 0 && tmp_qt.hardlim == 0) {
+ DEBUG(5,("no quota entry for sid[%s] path[%s]\n",
+ dom_sid_str_buf(&sid, &buf),
+ fsp->conn->connectpath));
+ continue;
+ }
+
+ DEBUG(15,("quota entry for id[%s] path[%s]\n",
+ dom_sid_str_buf(&sid, &buf),
+ fsp->conn->connectpath));
+
+ if ((tmp_list_ent=talloc_zero(mem_ctx,SMB_NTQUOTA_LIST))==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ *qt_list = NULL;
+ talloc_destroy(mem_ctx);
+ return (-1);
+ }
+
+ if ((tmp_list_ent->quotas=talloc_zero(mem_ctx,SMB_NTQUOTA_STRUCT))==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ *qt_list = NULL;
+ talloc_destroy(mem_ctx);
+ return (-1);
+ }
+
+ tmp_list_ent->uid = usr->pw_uid;
+ memcpy(tmp_list_ent->quotas,&tmp_qt,sizeof(tmp_qt));
+ tmp_list_ent->mem_ctx = mem_ctx;
+
+ DLIST_ADD((*qt_list),tmp_list_ent);
+
+ }
+ endpwent();
+
+ if (*qt_list == NULL) {
+ TALLOC_FREE(mem_ctx);
+ }
+ return 0;
+}
+
+static int quota_handle_destructor(SMB_NTQUOTA_HANDLE *handle)
+{
+ free_ntquota_list(&handle->quota_list);
+ return 0;
+}
+
+void *init_quota_handle(TALLOC_CTX *mem_ctx)
+{
+ SMB_NTQUOTA_HANDLE *qt_handle;
+
+ if (!mem_ctx)
+ return NULL;
+
+ qt_handle = talloc_zero(mem_ctx,SMB_NTQUOTA_HANDLE);
+ if (qt_handle==NULL) {
+ DEBUG(0,("TALLOC_ZERO() failed\n"));
+ return NULL;
+ }
+
+ talloc_set_destructor(qt_handle, quota_handle_destructor);
+ return (void *)qt_handle;
+}
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
new file mode 100644
index 0000000..95034b1
--- /dev/null
+++ b/source3/smbd/open.c
@@ -0,0 +1,6676 @@
+/*
+ Unix SMB/CIFS implementation.
+ file opening and share modes
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2001-2004
+ Copyright (C) Volker Lendecke 2005
+ Copyright (C) Ralph Boehme 2017
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "../librpc/gen_ndr/ioctl.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "serverid.h"
+#include "messages.h"
+#include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "locking/leases_db.h"
+#include "librpc/gen_ndr/ndr_leases_db.h"
+#include "lib/util/time_basic.h"
+#include "source3/smbd/dir.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+struct deferred_open_record {
+ struct smbXsrv_connection *xconn;
+ uint64_t mid;
+
+ bool async_open;
+
+ /*
+ * Timer for async opens, needed because they don't use a watch on
+ * a locking.tdb record. This is currently only used for real async
+ * opens and just terminates smbd if the async open times out.
+ */
+ struct tevent_timer *te;
+
+ /*
+ * For the samba kernel oplock case we use both a timeout and
+ * a watch on locking.tdb. This way in case it's smbd holding
+ * the kernel oplock we get directly notified for the retry
+ * once the kernel oplock is properly broken. Store the req
+ * here so that it can be timely discarded once the timer
+ * above fires.
+ */
+ struct tevent_req *watch_req;
+};
+
+/****************************************************************************
+ If the requester wanted DELETE_ACCESS and was rejected because
+ the file ACL didn't include DELETE_ACCESS, see if the parent ACL
+ overrides this.
+****************************************************************************/
+
+static bool parent_override_delete(connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t rejected_mask)
+{
+ if ((access_mask & DELETE_ACCESS) &&
+ (rejected_mask & DELETE_ACCESS) &&
+ can_delete_file_in_directory(conn,
+ dirfsp,
+ smb_fname))
+ {
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ Check if we have open rights.
+****************************************************************************/
+
+static NTSTATUS smbd_check_access_rights_fname(
+ struct connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t do_not_check_mask)
+{
+ uint32_t rejected_share_access;
+ uint32_t effective_access;
+
+ rejected_share_access = access_mask & ~(conn->share_access);
+
+ if (rejected_share_access) {
+ DBG_DEBUG("rejected share access 0x%"PRIx32" on "
+ "%s (0x%"PRIx32")\n",
+ access_mask,
+ smb_fname_str_dbg(smb_fname),
+ rejected_share_access);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ effective_access = access_mask & ~do_not_check_mask;
+ if (effective_access == 0) {
+ DBG_DEBUG("do_not_check_mask override on %s. Granting 0x%x for free.\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)access_mask);
+ return NT_STATUS_OK;
+ }
+
+ if (!use_privs && get_current_uid(conn) == (uid_t)0) {
+ /* I'm sorry sir, I didn't know you were root... */
+ DBG_DEBUG("root override on %s. Granting 0x%x\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)access_mask);
+ return NT_STATUS_OK;
+ }
+
+ if ((access_mask & DELETE_ACCESS) &&
+ !lp_acl_check_permissions(SNUM(conn)))
+ {
+ DBG_DEBUG("Not checking ACL on DELETE_ACCESS on file %s. "
+ "Granting 0x%"PRIx32"\n",
+ smb_fname_str_dbg(smb_fname),
+ access_mask);
+ return NT_STATUS_OK;
+ }
+
+ if (access_mask == DELETE_ACCESS &&
+ VALID_STAT(smb_fname->st) &&
+ S_ISLNK(smb_fname->st.st_ex_mode))
+ {
+ /* We can always delete a symlink. */
+ DBG_DEBUG("Not checking ACL on DELETE_ACCESS on symlink %s.\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS smbd_check_access_rights_sd(
+ struct connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct security_descriptor *sd,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t do_not_check_mask)
+{
+ uint32_t rejected_mask = access_mask;
+ NTSTATUS status;
+
+ if (sd == NULL) {
+ goto access_denied;
+ }
+
+ status = se_file_access_check(sd,
+ get_current_nttok(conn),
+ use_privs,
+ (access_mask & ~do_not_check_mask),
+ &rejected_mask);
+
+ DBG_DEBUG("File [%s] requesting [0x%"PRIx32"] "
+ "returning [0x%"PRIx32"] (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ access_mask,
+ rejected_mask,
+ nt_errstr(status));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("acl for %s is:\n",
+ smb_fname_str_dbg(smb_fname));
+ NDR_PRINT_DEBUG(security_descriptor, sd);
+ }
+ }
+
+ TALLOC_FREE(sd);
+
+ if (NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED))
+ {
+ return status;
+ }
+
+ /* Here we know status == NT_STATUS_ACCESS_DENIED. */
+
+access_denied:
+
+ if ((access_mask & FILE_WRITE_ATTRIBUTES) &&
+ (rejected_mask & FILE_WRITE_ATTRIBUTES) &&
+ !lp_store_dos_attributes(SNUM(conn)) &&
+ (lp_map_readonly(SNUM(conn)) ||
+ lp_map_archive(SNUM(conn)) ||
+ lp_map_hidden(SNUM(conn)) ||
+ lp_map_system(SNUM(conn))))
+ {
+ rejected_mask &= ~FILE_WRITE_ATTRIBUTES;
+
+ DBG_DEBUG("overrode FILE_WRITE_ATTRIBUTES on file %s\n",
+ smb_fname_str_dbg(smb_fname));
+ }
+
+ if (parent_override_delete(conn,
+ dirfsp,
+ smb_fname,
+ access_mask,
+ rejected_mask))
+ {
+ /*
+ * Were we trying to do an open for delete and didn't get DELETE
+ * access. Check if the directory allows DELETE_CHILD.
+ * See here:
+ * http://blogs.msdn.com/oldnewthing/archive/2004/06/04/148426.aspx
+ * for details.
+ */
+
+ rejected_mask &= ~DELETE_ACCESS;
+
+ DBG_DEBUG("Overrode DELETE_ACCESS on file %s\n",
+ smb_fname_str_dbg(smb_fname));
+ }
+
+ if (rejected_mask != 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_check_access_rights_fsp(struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask)
+{
+ struct security_descriptor *sd = NULL;
+ uint32_t do_not_check_mask = 0;
+ NTSTATUS status;
+
+ /* Cope with fake/printer fsp's. */
+ if (fsp->fake_file_handle != NULL || fsp->print_file != NULL) {
+ if ((fsp->access_mask & access_mask) != access_mask) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is a POSIX open on a symlink. For the pathname
+ * version of this function we used to return the st_mode
+ * bits turned into an NT ACL. For a symlink the mode bits
+ * are always rwxrwxrwx which means the pathname version always
+ * returned NT_STATUS_OK for a symlink. For the handle reference
+ * to a symlink use the handle access bits.
+ */
+ if ((fsp->access_mask & access_mask) != access_mask) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * If we can access the path to this file, by
+ * default we have FILE_READ_ATTRIBUTES from the
+ * containing directory. See the section:
+ * "Algorithm to Check Access to an Existing File"
+ * in MS-FSA.pdf.
+ *
+ * se_file_access_check() also takes care of
+ * owner WRITE_DAC and READ_CONTROL.
+ */
+ do_not_check_mask = FILE_READ_ATTRIBUTES;
+
+ /*
+ * Samba 3.6 and earlier granted execute access even
+ * if the ACL did not contain execute rights.
+ * Samba 4.0 is more correct and checks it.
+ * The compatibility mode allows one to skip this check
+ * to smoothen upgrades.
+ */
+ if (lp_acl_allow_execute_always(SNUM(fsp->conn))) {
+ do_not_check_mask |= FILE_EXECUTE;
+ }
+
+ status = smbd_check_access_rights_fname(fsp->conn,
+ fsp->fsp_name,
+ use_privs,
+ access_mask,
+ do_not_check_mask);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return status;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ (SECINFO_OWNER |
+ SECINFO_GROUP |
+ SECINFO_DACL),
+ talloc_tos(),
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not get acl on %s: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return status;
+ }
+
+ return smbd_check_access_rights_sd(fsp->conn,
+ dirfsp,
+ fsp->fsp_name,
+ sd,
+ use_privs,
+ access_mask,
+ do_not_check_mask);
+}
+
+/*
+ * Given an fsp that represents a parent directory,
+ * check if the requested access can be granted.
+ */
+NTSTATUS check_parent_access_fsp(struct files_struct *fsp,
+ uint32_t access_mask)
+{
+ NTSTATUS status;
+ struct security_descriptor *parent_sd = NULL;
+ uint32_t access_granted = 0;
+ struct share_mode_lock *lck = NULL;
+ uint32_t name_hash;
+ bool delete_on_close_set;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (get_current_uid(fsp->conn) == (uid_t)0) {
+ /* I'm sorry sir, I didn't know you were root... */
+ DBG_DEBUG("root override on %s. Granting 0x%x\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)access_mask);
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(fsp,
+ SECINFO_DACL,
+ frame,
+ &parent_sd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("SMB_VFS_FGET_NT_ACL failed for "
+ "%s with error %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * If we can access the path to this file, by
+ * default we have FILE_READ_ATTRIBUTES from the
+ * containing directory. See the section:
+ * "Algorithm to Check Access to an Existing File"
+ * in MS-FSA.pdf.
+ *
+ * se_file_access_check() also takes care of
+ * owner WRITE_DAC and READ_CONTROL.
+ */
+ status = se_file_access_check(parent_sd,
+ get_current_nttok(fsp->conn),
+ false,
+ (access_mask & ~FILE_READ_ATTRIBUTES),
+ &access_granted);
+ if(!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("access check "
+ "on directory %s for mask 0x%x returned (0x%x) %s\n",
+ fsp_str_dbg(fsp),
+ access_mask,
+ access_granted,
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (!(access_mask & (SEC_DIR_ADD_FILE | SEC_DIR_ADD_SUBDIR))) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+ if (!lp_check_parent_directory_delete_on_close(SNUM(fsp->conn))) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ /* Check if the directory has delete-on-close set */
+ status = file_name_hash(fsp->conn,
+ fsp->fsp_name->base_name,
+ &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * Don't take a lock here. We just need a snapshot
+ * of the current state of delete on close and this is
+ * called in a codepath where we may already have a lock
+ * (and we explicitly can't hold 2 locks at the same time
+ * as that may deadlock).
+ */
+ lck = fetch_share_mode_unlocked(frame, fsp->file_id);
+ if (lck == NULL) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ delete_on_close_set = is_delete_on_close_set(lck, name_hash);
+ if (delete_on_close_set) {
+ status = NT_STATUS_DELETE_PENDING;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+out:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Ensure when opening a base file for a stream open that we have permissions
+ to do so given the access mask on the base file.
+****************************************************************************/
+
+static NTSTATUS check_base_file_access(struct files_struct *fsp,
+ uint32_t access_mask)
+{
+ NTSTATUS status;
+
+ status = smbd_calculate_access_mask_fsp(fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("smbd_calculate_access_mask "
+ "on file %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (access_mask & (FILE_WRITE_DATA|FILE_APPEND_DATA)) {
+ uint32_t dosattrs;
+ if (!CAN_WRITE(fsp->conn)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ dosattrs = fdos_mode(fsp);
+ if (dosattrs & FILE_ATTRIBUTE_READONLY) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ return smbd_check_access_rights_fsp(fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ access_mask);
+}
+
+static NTSTATUS chdir_below_conn(
+ TALLOC_CTX *mem_ctx,
+ connection_struct *conn,
+ const char *connectpath,
+ size_t connectpath_len,
+ struct smb_filename *dir_fname,
+ struct smb_filename **_oldwd_fname)
+{
+ struct smb_filename *oldwd_fname = NULL;
+ struct smb_filename *smb_fname_dot = NULL;
+ struct smb_filename *real_fname = NULL;
+ const char *relative = NULL;
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ if (!ISDOT(dir_fname->base_name)) {
+
+ oldwd_fname = vfs_GetWd(talloc_tos(), conn);
+ if (oldwd_fname == NULL) {
+ status = map_nt_error_from_unix(errno);
+ goto out;
+ }
+
+ /* Pin parent directory in place. */
+ ret = vfs_ChDir(conn, dir_fname);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("chdir to %s failed: %s\n",
+ dir_fname->base_name,
+ strerror(errno));
+ goto out;
+ }
+ }
+
+ smb_fname_dot = synthetic_smb_fname(
+ talloc_tos(),
+ ".",
+ NULL,
+ NULL,
+ dir_fname->twrp,
+ dir_fname->flags);
+ if (smb_fname_dot == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ real_fname = SMB_VFS_REALPATH(conn, talloc_tos(), smb_fname_dot);
+ if (real_fname == NULL) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("realpath in %s failed: %s\n",
+ dir_fname->base_name,
+ strerror(errno));
+ goto out;
+ }
+ TALLOC_FREE(smb_fname_dot);
+
+ ok = subdir_of(connectpath,
+ connectpath_len,
+ real_fname->base_name,
+ &relative);
+ if (ok) {
+ TALLOC_FREE(real_fname);
+ *_oldwd_fname = oldwd_fname;
+ return NT_STATUS_OK;
+ }
+
+ DBG_NOTICE("Bad access attempt: %s is a symlink "
+ "outside the share path\n"
+ "conn_rootdir =%s\n"
+ "resolved_name=%s\n",
+ dir_fname->base_name,
+ connectpath,
+ real_fname->base_name);
+ TALLOC_FREE(real_fname);
+
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+out:
+ if (oldwd_fname != NULL) {
+ ret = vfs_ChDir(conn, oldwd_fname);
+ SMB_ASSERT(ret == 0);
+ TALLOC_FREE(oldwd_fname);
+ }
+
+ return status;
+}
+
+/*
+ * Get the symlink target of dirfsp/symlink_name, making sure the
+ * target is below connection_path.
+ */
+
+static NTSTATUS symlink_target_below_conn(
+ TALLOC_CTX *mem_ctx,
+ const char *connection_path,
+ struct files_struct *fsp,
+ struct files_struct *dirfsp,
+ struct smb_filename *symlink_name,
+ char **_target)
+{
+ char *target = NULL;
+ char *absolute = NULL;
+ NTSTATUS status;
+
+ if (fsp_get_pathref_fd(fsp) != -1) {
+ /*
+ * fsp is an O_PATH open, Linux does a "freadlink"
+ * with an empty name argument to readlinkat
+ */
+ status = readlink_talloc(talloc_tos(), fsp, NULL, &target);
+ } else {
+ status = readlink_talloc(
+ talloc_tos(), dirfsp, symlink_name, &target);
+ }
+
+ status = safe_symlink_target_path(talloc_tos(),
+ connection_path,
+ dirfsp->fsp_name->base_name,
+ target,
+ 0,
+ &absolute);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("safe_symlink_target_path() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (absolute[0] == '\0') {
+ /*
+ * special case symlink to share root: "." is our
+ * share root filename
+ */
+ TALLOC_FREE(absolute);
+ absolute = talloc_strdup(talloc_tos(), ".");
+ if (absolute == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_target = absolute;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Non-widelink open.
+****************************************************************************/
+
+static NTSTATUS non_widelink_open(const struct files_struct *dirfsp,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ const struct vfs_open_how *_how)
+{
+ struct connection_struct *conn = fsp->conn;
+ const char *connpath = SMB_VFS_CONNECTPATH(conn, dirfsp, smb_fname);
+ size_t connpath_len;
+ NTSTATUS status = NT_STATUS_OK;
+ int fd = -1;
+ char *orig_smb_fname_base = smb_fname->base_name;
+ struct smb_filename *orig_fsp_name = fsp->fsp_name;
+ struct smb_filename *smb_fname_rel = NULL;
+ struct smb_filename *oldwd_fname = NULL;
+ struct smb_filename *parent_dir_fname = NULL;
+ struct vfs_open_how how = *_how;
+ char *target = NULL;
+ size_t link_depth = 0;
+ int ret;
+
+ SMB_ASSERT(!fsp_is_alternate_stream(fsp));
+
+ if (connpath == NULL) {
+ /*
+ * This can happen with shadow_copy2 if the snapshot
+ * path is not found
+ */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ connpath_len = strlen(connpath);
+
+again:
+ if (smb_fname->base_name[0] == '/') {
+ int cmp = strcmp(connpath, smb_fname->base_name);
+ if (cmp == 0) {
+ smb_fname->base_name = talloc_strdup(smb_fname, "");
+ if (smb_fname->base_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ }
+
+ if (dirfsp == conn->cwd_fsp) {
+
+ status = SMB_VFS_PARENT_PATHNAME(fsp->conn,
+ talloc_tos(),
+ smb_fname,
+ &parent_dir_fname,
+ &smb_fname_rel);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = chdir_below_conn(
+ talloc_tos(),
+ conn,
+ connpath,
+ connpath_len,
+ parent_dir_fname,
+ &oldwd_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /* Setup fsp->fsp_name to be relative to cwd */
+ fsp->fsp_name = smb_fname_rel;
+ } else {
+ /*
+ * fsp->fsp_name is unchanged as it is already correctly
+ * relative to conn->cwd.
+ */
+ smb_fname_rel = smb_fname;
+ }
+
+ {
+ /*
+ * Assert nobody can step in with a symlink on the
+ * path, there is no path anymore and we'll use
+ * O_NOFOLLOW to open.
+ */
+ char *slash = strchr_m(smb_fname_rel->base_name, '/');
+ SMB_ASSERT(slash == NULL);
+ }
+
+ how.flags |= O_NOFOLLOW;
+
+ fd = SMB_VFS_OPENAT(conn,
+ dirfsp,
+ smb_fname_rel,
+ fsp,
+ &how);
+ fsp_set_fd(fsp, fd); /* This preserves errno */
+
+ if (fd == -1) {
+ status = map_nt_error_from_unix(errno);
+
+ if (errno == ENOENT) {
+ goto out;
+ }
+
+ /*
+ * ENOENT makes it worthless retrying with a
+ * stat, we know for sure the file does not
+ * exist. For everything else we want to know
+ * what's there.
+ */
+ ret = SMB_VFS_FSTATAT(
+ fsp->conn,
+ dirfsp,
+ smb_fname_rel,
+ &fsp->fsp_name->st,
+ AT_SYMLINK_NOFOLLOW);
+
+ if (ret == -1) {
+ /*
+ * Keep the original error. Otherwise we would
+ * mask for example EROFS for open(O_CREAT),
+ * turning it into ENOENT.
+ */
+ goto out;
+ }
+ } else {
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ }
+
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("fstat[at](%s) failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno));
+ goto out;
+ }
+
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+ orig_fsp_name->st = fsp->fsp_name->st;
+
+ if (!S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ goto out;
+ }
+
+ /*
+ * Found a symlink to follow in user space
+ */
+
+ if (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH) {
+ /* Never follow symlinks on posix open. */
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto out;
+ }
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ /* Explicitly no symlinks. */
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto out;
+ }
+
+ link_depth += 1;
+ if (link_depth >= 40) {
+ status = NT_STATUS_STOPPED_ON_SYMLINK;
+ goto out;
+ }
+
+ fsp->fsp_name = orig_fsp_name;
+
+ status = symlink_target_below_conn(
+ talloc_tos(),
+ connpath,
+ fsp,
+ discard_const_p(files_struct, dirfsp),
+ smb_fname_rel,
+ &target);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("symlink_target_below_conn() failed: %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * Close what openat(O_PATH) potentially left behind
+ */
+ fd_close(fsp);
+
+ if (smb_fname->base_name != orig_smb_fname_base) {
+ TALLOC_FREE(smb_fname->base_name);
+ }
+ smb_fname->base_name = target;
+
+ if (oldwd_fname != NULL) {
+ ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+
+ /*
+ * And do it all again... As smb_fname is not relative to the passed in
+ * dirfsp anymore, we pass conn->cwd_fsp as dirfsp to
+ * non_widelink_open() to trigger the chdir(parentdir) logic.
+ */
+ dirfsp = conn->cwd_fsp;
+
+ goto again;
+
+ out:
+ fsp->fsp_name = orig_fsp_name;
+ smb_fname->base_name = orig_smb_fname_base;
+
+ TALLOC_FREE(parent_dir_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ }
+
+ if (oldwd_fname != NULL) {
+ ret = vfs_ChDir(conn, oldwd_fname);
+ if (ret == -1) {
+ smb_panic("unable to get back to old directory\n");
+ }
+ TALLOC_FREE(oldwd_fname);
+ }
+ return status;
+}
+
+/****************************************************************************
+ fd support routines - attempt to do a dos_open.
+****************************************************************************/
+
+NTSTATUS fd_openat(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how)
+{
+ struct vfs_open_how how = *_how;
+ struct connection_struct *conn = fsp->conn;
+ NTSTATUS status = NT_STATUS_OK;
+ bool fsp_is_stream = fsp_is_alternate_stream(fsp);
+ bool smb_fname_is_stream = is_named_stream(smb_fname);
+
+ SMB_ASSERT(fsp_is_stream == smb_fname_is_stream);
+
+ /*
+ * Never follow symlinks on a POSIX client. The
+ * client should be doing this.
+ */
+
+ if ((fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) || !lp_follow_symlinks(SNUM(conn))) {
+ how.flags |= O_NOFOLLOW;
+ }
+
+ if (fsp_is_stream) {
+ int fd;
+
+ fd = SMB_VFS_OPENAT(
+ conn,
+ NULL, /* stream open is relative to fsp->base_fsp */
+ smb_fname,
+ fsp,
+ &how);
+ if (fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ }
+ fsp_set_fd(fsp, fd);
+
+ if (fd != -1) {
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("vfs_stat_fsp failed: %s\n",
+ nt_errstr(status));
+ fd_close(fsp);
+ }
+ }
+
+ return status;
+ }
+
+ /*
+ * Only follow symlinks within a share
+ * definition.
+ */
+ status = non_widelink_open(dirfsp, fsp, smb_fname, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_TOO_MANY_OPENED_FILES)) {
+ static time_t last_warned = 0L;
+
+ if (time((time_t *) NULL) > last_warned) {
+ DEBUG(0,("Too many open files, unable "
+ "to open more! smbd's max "
+ "open files = %d\n",
+ lp_max_open_files()));
+ last_warned = time((time_t *) NULL);
+ }
+ }
+
+ DBG_DEBUG("name %s, flags = 0%o mode = 0%o, fd = %d. %s\n",
+ smb_fname_str_dbg(smb_fname),
+ how.flags,
+ (int)how.mode,
+ fsp_get_pathref_fd(fsp),
+ nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("name %s, flags = 0%o mode = 0%o, fd = %d\n",
+ smb_fname_str_dbg(smb_fname),
+ how.flags,
+ (int)how.mode,
+ fsp_get_pathref_fd(fsp));
+
+ return status;
+}
+
+/****************************************************************************
+ Close the file associated with a fsp.
+****************************************************************************/
+
+NTSTATUS fd_close(files_struct *fsp)
+{
+ NTSTATUS stat_status = NT_STATUS_OK;
+ int ret;
+
+ if (fsp == fsp->conn->cwd_fsp) {
+ return NT_STATUS_OK;
+ }
+
+ if (fsp->fsp_flags.fstat_before_close) {
+ /*
+ * capture status, if failure
+ * continue close processing
+ * and return status
+ */
+ stat_status = vfs_stat_fsp(fsp);
+ }
+
+ if (fsp->dptr) {
+ dptr_CloseDir(fsp);
+ }
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * Either a directory where the dptr_CloseDir() already closed
+ * the fd or a stat open.
+ */
+ return NT_STATUS_OK;
+ }
+ if (fh_get_refcount(fsp->fh) > 1) {
+ return NT_STATUS_OK; /* Shared handle. Only close last reference. */
+ }
+
+ ret = SMB_VFS_CLOSE(fsp);
+ fsp_set_fd(fsp, -1);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ return stat_status;
+}
+
+/****************************************************************************
+ Change the ownership of a file to that of the parent directory.
+ Do this by fd if possible.
+****************************************************************************/
+
+static void change_file_owner_to_parent_fsp(struct files_struct *parent_fsp,
+ struct files_struct *fsp)
+{
+ int ret;
+
+ if (parent_fsp->fsp_name->st.st_ex_uid == fsp->fsp_name->st.st_ex_uid) {
+ /* Already this uid - no need to change. */
+ DBG_DEBUG("file %s is already owned by uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)fsp->fsp_name->st.st_ex_uid);
+ return;
+ }
+
+ become_root();
+ ret = SMB_VFS_FCHOWN(fsp,
+ parent_fsp->fsp_name->st.st_ex_uid,
+ (gid_t)-1);
+ unbecome_root();
+ if (ret == -1) {
+ DBG_ERR("failed to fchown "
+ "file %s to parent directory uid %u. Error "
+ "was %s\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid,
+ strerror(errno));
+ } else {
+ DBG_DEBUG("changed new file %s to "
+ "parent directory uid %u.\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid);
+ /* Ensure the uid entry is updated. */
+ fsp->fsp_name->st.st_ex_uid =
+ parent_fsp->fsp_name->st.st_ex_uid;
+ }
+}
+
+static NTSTATUS change_dir_owner_to_parent_fsp(struct files_struct *parent_fsp,
+ struct files_struct *fsp)
+{
+ NTSTATUS status;
+ int ret;
+
+ if (parent_fsp->fsp_name->st.st_ex_uid == fsp->fsp_name->st.st_ex_uid) {
+ /* Already this uid - no need to change. */
+ DBG_DEBUG("directory %s is already owned by uid %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)fsp->fsp_name->st.st_ex_uid);
+ return NT_STATUS_OK;
+ }
+
+ become_root();
+ ret = SMB_VFS_FCHOWN(fsp,
+ parent_fsp->fsp_name->st.st_ex_uid,
+ (gid_t)-1);
+ unbecome_root();
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_ERR("failed to chown "
+ "directory %s to parent directory uid %u. "
+ "Error was %s\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid,
+ nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("changed ownership of new "
+ "directory %s to parent directory uid %u.\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)parent_fsp->fsp_name->st.st_ex_uid);
+
+ /* Ensure the uid entry is updated. */
+ fsp->fsp_name->st.st_ex_uid = parent_fsp->fsp_name->st.st_ex_uid;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Open a file - returning a guaranteed ATOMIC indication of if the
+ file was created or not.
+****************************************************************************/
+
+static NTSTATUS fd_open_atomic(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how,
+ bool *file_created)
+{
+ struct vfs_open_how how = *_how;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS retry_status;
+ bool file_existed = VALID_STAT(smb_fname->st);
+
+ if (!(how.flags & O_CREAT)) {
+ /*
+ * We're not creating the file, just pass through.
+ */
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+ *file_created = false;
+ return status;
+ }
+
+ if (how.flags & O_EXCL) {
+ /*
+ * Fail if already exists, just pass through.
+ */
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+
+ /*
+ * Here we've opened with O_CREAT|O_EXCL. If that went
+ * NT_STATUS_OK, we *know* we created this file.
+ */
+ *file_created = NT_STATUS_IS_OK(status);
+
+ return status;
+ }
+
+ /*
+ * Now it gets tricky. We have O_CREAT, but not O_EXCL.
+ * To know absolutely if we created the file or not,
+ * we can never call O_CREAT without O_EXCL. So if
+ * we think the file existed, try without O_CREAT|O_EXCL.
+ * If we think the file didn't exist, try with
+ * O_CREAT|O_EXCL.
+ *
+ * The big problem here is dangling symlinks. Opening
+ * without O_NOFOLLOW means both bad symlink
+ * and missing path return -1, ENOENT from open(). As POSIX
+ * is pathname based it's not possible to tell
+ * the difference between these two cases in a
+ * non-racy way, so change to try only two attempts before
+ * giving up.
+ *
+ * We don't have this problem for the O_NOFOLLOW
+ * case as it just returns NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * mapped from the ELOOP POSIX error.
+ */
+
+ if (file_existed) {
+ how.flags = _how->flags & ~(O_CREAT);
+ retry_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ } else {
+ how.flags = _how->flags | O_EXCL;
+ retry_status = NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+ if (NT_STATUS_IS_OK(status)) {
+ *file_created = !file_existed;
+ return NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(status, retry_status)) {
+
+ file_existed = !file_existed;
+
+ DBG_DEBUG("File %s %s. Retry.\n",
+ fsp_str_dbg(fsp),
+ file_existed ? "existed" : "did not exist");
+
+ if (file_existed) {
+ how.flags = _how->flags & ~(O_CREAT);
+ } else {
+ how.flags = _how->flags | O_EXCL;
+ }
+
+ status = fd_openat(dirfsp, smb_fname, fsp, &how);
+ }
+
+ *file_created = (NT_STATUS_IS_OK(status) && !file_existed);
+ return status;
+}
+
+static NTSTATUS reopen_from_fsp(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how,
+ bool *p_file_created)
+{
+ NTSTATUS status;
+ int old_fd;
+
+ if (fsp->fsp_flags.have_proc_fds &&
+ ((old_fd = fsp_get_pathref_fd(fsp)) != -1)) {
+
+ struct sys_proc_fd_path_buf buf;
+ struct smb_filename proc_fname = (struct smb_filename){
+ .base_name = sys_proc_fd_path(old_fd, &buf),
+ };
+ mode_t mode = fsp->fsp_name->st.st_ex_mode;
+ int new_fd;
+
+ SMB_ASSERT(fsp->fsp_flags.is_pathref);
+
+ if (S_ISLNK(mode)) {
+ return NT_STATUS_STOPPED_ON_SYMLINK;
+ }
+ if (!(S_ISREG(mode) || S_ISDIR(mode))) {
+ return NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED;
+ }
+
+ fsp->fsp_flags.is_pathref = false;
+
+ new_fd = SMB_VFS_OPENAT(fsp->conn,
+ fsp->conn->cwd_fsp,
+ &proc_fname,
+ fsp,
+ how);
+ if (new_fd == -1) {
+ status = map_nt_error_from_unix(errno);
+ fd_close(fsp);
+ return status;
+ }
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp_set_fd(fsp, new_fd);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Close the existing pathref fd and set the fsp flag
+ * is_pathref to false so we get a "normal" fd this time.
+ */
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->fsp_flags.is_pathref = false;
+
+ status = fd_open_atomic(dirfsp, smb_fname, fsp, how, p_file_created);
+ return status;
+}
+
+/****************************************************************************
+ Open a file.
+****************************************************************************/
+
+static NTSTATUS open_file(
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_atname,
+ files_struct *fsp,
+ const struct vfs_open_how *_how,
+ uint32_t access_mask, /* client requested access mask. */
+ uint32_t open_access_mask, /* what we're actually using in the open. */
+ uint32_t private_flags,
+ bool *p_file_created)
+{
+ connection_struct *conn = fsp->conn;
+ struct smb_filename *smb_fname = fsp->fsp_name;
+ struct vfs_open_how how = *_how;
+ NTSTATUS status = NT_STATUS_OK;
+ bool file_existed = VALID_STAT(fsp->fsp_name->st);
+ const uint32_t need_fd_mask =
+ FILE_READ_DATA |
+ FILE_WRITE_DATA |
+ FILE_APPEND_DATA |
+ FILE_EXECUTE |
+ SEC_FLAG_SYSTEM_SECURITY;
+ bool creating = !file_existed && (how.flags & O_CREAT);
+ bool open_fd = false;
+ bool posix_open = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+
+ /*
+ * Catch early an attempt to open an existing
+ * directory as a file.
+ */
+ if (file_existed && S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ /*
+ * This little piece of insanity is inspired by the
+ * fact that an NT client can open a file for O_RDONLY,
+ * but set the create disposition to FILE_EXISTS_TRUNCATE.
+ * If the client *can* write to the file, then it expects to
+ * truncate the file, even though it is opening for readonly.
+ * Quicken uses this stupid trick in backup file creation...
+ * Thanks *greatly* to "David W. Chapman Jr." <dwcjr@inethouston.net>
+ * for helping track this one down. It didn't bite us in 2.0.x
+ * as we always opened files read-write in that release. JRA.
+ */
+
+ if (((how.flags & O_ACCMODE) == O_RDONLY) && (how.flags & O_TRUNC)) {
+ DBG_DEBUG("truncate requested on read-only open for file %s\n",
+ smb_fname_str_dbg(smb_fname));
+ how.flags = (how.flags & ~O_ACCMODE) | O_RDWR;
+ }
+
+ /* Check permissions */
+
+ /*
+ * This code was changed after seeing a client open request
+ * containing the open mode of (DENY_WRITE/read-only) with
+ * the 'create if not exist' bit set. The previous code
+ * would fail to open the file read only on a read-only share
+ * as it was checking the flags parameter directly against O_RDONLY,
+ * this was failing as the flags parameter was set to O_RDONLY|O_CREAT.
+ * JRA.
+ */
+
+ if (!CAN_WRITE(conn)) {
+ /* It's a read-only share - fail if we wanted to write. */
+ if ((how.flags & O_ACCMODE) != O_RDONLY ||
+ (how.flags & O_TRUNC) || (how.flags & O_APPEND)) {
+ DEBUG(3,("Permission denied opening %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ /*
+ * We don't want to write - but we must make sure that
+ * O_CREAT doesn't create the file if we have write
+ * access into the directory.
+ */
+ how.flags &= ~(O_CREAT | O_EXCL);
+ }
+
+ if ((open_access_mask & need_fd_mask) || creating ||
+ (how.flags & O_TRUNC)) {
+ open_fd = true;
+ }
+
+ if (open_fd) {
+ int ret;
+
+#if defined(O_NONBLOCK) && defined(S_ISFIFO)
+ /*
+ * We would block on opening a FIFO with no one else on the
+ * other end. Do what we used to do and add O_NONBLOCK to the
+ * open flags. JRA.
+ */
+
+ if (file_existed && S_ISFIFO(smb_fname->st.st_ex_mode)) {
+ how.flags |= O_NONBLOCK;
+ }
+#endif
+
+ if (!posix_open) {
+ const char *wild = smb_fname->base_name;
+ /*
+ * Don't open files with Microsoft wildcard characters.
+ */
+ if (fsp_is_alternate_stream(fsp)) {
+ /*
+ * wildcard characters are allowed in stream
+ * names only test the basefilename
+ */
+ wild = fsp->base_fsp->fsp_name->base_name;
+ }
+
+ if (ms_has_wild(wild)) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+
+ /* Can we access this file ? */
+ if (!fsp_is_alternate_stream(fsp)) {
+ /* Only do this check on non-stream open. */
+ if (file_existed) {
+ status = smbd_check_access_rights_fsp(
+ dirfsp,
+ fsp,
+ false,
+ open_access_mask);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_check_access_rights_fsp"
+ " on file %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ }
+
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ return status;
+ }
+
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ DEBUG(10, ("open_file: "
+ "file %s vanished since we "
+ "checked for existence.\n",
+ smb_fname_str_dbg(smb_fname)));
+ file_existed = false;
+ SET_STAT_INVALID(fsp->fsp_name->st);
+ }
+ }
+
+ if (!file_existed) {
+ if (!(how.flags & O_CREAT)) {
+ /* File didn't exist and no O_CREAT. */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ status = check_parent_access_fsp(
+ dirfsp,
+ SEC_DIR_ADD_FILE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("check_parent_access_fsp on "
+ "directory %s for file %s "
+ "returned %s\n",
+ smb_fname_str_dbg(
+ dirfsp->fsp_name),
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ return status;
+ }
+ }
+ }
+
+ /*
+ * Actually do the open - if O_TRUNC is needed handle it
+ * below under the share mode lock.
+ */
+ how.flags &= ~O_TRUNC;
+ status = reopen_from_fsp(dirfsp,
+ smb_fname_atname,
+ fsp,
+ &how,
+ p_file_created);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_STOPPED_ON_SYMLINK)) {
+ /*
+ * Non-O_PATH reopen that hit a race
+ * condition: Someone has put a symlink where
+ * we used to have a file. Can't happen with
+ * O_PATH and reopening from /proc/self/fd/ or
+ * equivalent.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("Error opening file %s (%s) (in_flags=%d) "
+ "(flags=%d)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status),
+ _how->flags,
+ how.flags);
+ return status;
+ }
+
+ if (how.flags & O_NONBLOCK) {
+ /*
+ * GPFS can return ETIMEDOUT for pread on
+ * nonblocking file descriptors when files
+ * migrated to tape need to be recalled. I
+ * could imagine this happens elsewhere
+ * too. With blocking file descriptors this
+ * does not happen.
+ */
+ ret = vfs_set_blocking(fsp, true);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_WARNING("Could not set fd to blocking: "
+ "%s\n", strerror(errno));
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ if (*p_file_created) {
+ /* We created this file. */
+
+ bool need_re_stat = false;
+ /* Do all inheritance work after we've
+ done a successful fstat call and filled
+ in the stat struct in fsp->fsp_name. */
+
+ /* Inherit the ACL if required */
+ if (lp_inherit_permissions(SNUM(conn))) {
+ inherit_access_posix_acl(conn,
+ dirfsp,
+ smb_fname,
+ how.mode);
+ need_re_stat = true;
+ }
+
+ /* Change the owner if required. */
+ if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
+ change_file_owner_to_parent_fsp(dirfsp, fsp);
+ need_re_stat = true;
+ }
+
+ if (need_re_stat) {
+ status = vfs_stat_fsp(fsp);
+ /*
+ * If we have an fd, this stat should succeed.
+ */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Error doing fstat on open "
+ "file %s (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_ADDED,
+ FILE_NOTIFY_CHANGE_FILE_NAME,
+ smb_fname->base_name);
+ }
+ } else {
+ if (!file_existed) {
+ /* File must exist for a stat open. */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (S_ISLNK(smb_fname->st.st_ex_mode) &&
+ !posix_open)
+ {
+ /*
+ * Don't allow stat opens on symlinks directly unless
+ * it's a POSIX open. Match the return code from
+ * openat_pathref_fsp().
+ */
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!fsp->fsp_flags.is_pathref) {
+ /*
+ * There is only one legit case where end up here:
+ * openat_pathref_fsp() failed to open a symlink, so the
+ * fsp was created by fsp_new() which doesn't set
+ * is_pathref. Other than that, we should always have a
+ * pathref fsp at this point. The subsequent checks
+ * assert this.
+ */
+ if (!(smb_fname->flags & SMB_FILENAME_POSIX_PATH)) {
+ DBG_ERR("[%s] is not a POSIX pathname\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (!S_ISLNK(smb_fname->st.st_ex_mode)) {
+ DBG_ERR("[%s] is not a symlink\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (fsp_get_pathref_fd(fsp) != -1) {
+ DBG_ERR("fd for [%s] is not -1: fd [%d]\n",
+ smb_fname_str_dbg(smb_fname),
+ fsp_get_pathref_fd(fsp));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ /*
+ * Access to streams is checked by checking the basefile and
+ * that has already been checked by check_base_file_access()
+ * in create_file_unixpath().
+ */
+ if (!fsp_is_alternate_stream(fsp)) {
+ status = smbd_check_access_rights_fsp(dirfsp,
+ fsp,
+ false,
+ open_access_mask);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) &&
+ posix_open &&
+ S_ISLNK(smb_fname->st.st_ex_mode)) {
+ /* This is a POSIX stat open for delete
+ * or rename on a symlink that points
+ * nowhere. Allow. */
+ DEBUG(10,("open_file: allowing POSIX "
+ "open on bad symlink %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ status = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_check_access_rights_fsp on file "
+ "%s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return status;
+ }
+ }
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ fsp->vuid = req ? req->vuid : UID_FIELD_INVALID;
+ fsp->file_pid = req ? req->smbpid : 0;
+ fsp->fsp_flags.can_lock = true;
+ fsp->fsp_flags.can_read = ((access_mask & FILE_READ_DATA) != 0);
+ fsp->fsp_flags.can_write =
+ CAN_WRITE(conn) &&
+ ((access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) != 0);
+ if (fsp->fsp_name->twrp != 0) {
+ fsp->fsp_flags.can_write = false;
+ }
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = false;
+ if (is_in_path(smb_fname->base_name,
+ conn->aio_write_behind_list,
+ posix_open ? true : conn->case_sensitive)) {
+ fsp->fsp_flags.aio_write_behind = true;
+ }
+
+ DEBUG(2,("%s opened file %s read=%s write=%s (numopen=%d)\n",
+ conn->session_info->unix_info->unix_name,
+ smb_fname_str_dbg(smb_fname),
+ BOOLSTR(fsp->fsp_flags.can_read),
+ BOOLSTR(fsp->fsp_flags.can_write),
+ conn->num_files_open));
+
+ return NT_STATUS_OK;
+}
+
+static bool mask_conflict(
+ uint32_t new_access,
+ uint32_t existing_access,
+ uint32_t access_mask,
+ uint32_t new_sharemode,
+ uint32_t existing_sharemode,
+ uint32_t sharemode_mask)
+{
+ bool want_access = (new_access & access_mask);
+ bool allow_existing = (existing_sharemode & sharemode_mask);
+ bool have_access = (existing_access & access_mask);
+ bool allow_new = (new_sharemode & sharemode_mask);
+
+ if (want_access && !allow_existing) {
+ DBG_DEBUG("Access request 0x%"PRIx32"/0x%"PRIx32" conflicts "
+ "with existing sharemode 0x%"PRIx32"/0x%"PRIx32"\n",
+ new_access,
+ access_mask,
+ existing_sharemode,
+ sharemode_mask);
+ return true;
+ }
+ if (have_access && !allow_new) {
+ DBG_DEBUG("Sharemode request 0x%"PRIx32"/0x%"PRIx32" conflicts "
+ "with existing access 0x%"PRIx32"/0x%"PRIx32"\n",
+ new_sharemode,
+ sharemode_mask,
+ existing_access,
+ access_mask);
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ Check if we can open a file with a share mode.
+ Returns True if conflict, False if not.
+****************************************************************************/
+
+static const uint32_t conflicting_access =
+ FILE_WRITE_DATA|
+ FILE_APPEND_DATA|
+ FILE_READ_DATA|
+ FILE_EXECUTE|
+ DELETE_ACCESS;
+
+static bool share_conflict(uint32_t e_access_mask,
+ uint32_t e_share_access,
+ uint32_t access_mask,
+ uint32_t share_access)
+{
+ bool conflict;
+
+ DBG_DEBUG("existing access_mask = 0x%"PRIx32", "
+ "existing share access = 0x%"PRIx32", "
+ "access_mask = 0x%"PRIx32", "
+ "share_access = 0x%"PRIx32"\n",
+ e_access_mask,
+ e_share_access,
+ access_mask,
+ share_access);
+
+ if ((e_access_mask & conflicting_access) == 0) {
+ DBG_DEBUG("No conflict due to "
+ "existing access_mask = 0x%"PRIx32"\n",
+ e_access_mask);
+ return false;
+ }
+ if ((access_mask & conflicting_access) == 0) {
+ DBG_DEBUG("No conflict due to access_mask = 0x%"PRIx32"\n",
+ access_mask);
+ return false;
+ }
+
+ conflict = mask_conflict(
+ access_mask, e_access_mask, FILE_WRITE_DATA | FILE_APPEND_DATA,
+ share_access, e_share_access, FILE_SHARE_WRITE);
+ conflict |= mask_conflict(
+ access_mask, e_access_mask, FILE_READ_DATA | FILE_EXECUTE,
+ share_access, e_share_access, FILE_SHARE_READ);
+ conflict |= mask_conflict(
+ access_mask, e_access_mask, DELETE_ACCESS,
+ share_access, e_share_access, FILE_SHARE_DELETE);
+
+ DBG_DEBUG("conflict=%s\n", conflict ? "true" : "false");
+ return conflict;
+}
+
+#if defined(DEVELOPER)
+
+struct validate_my_share_entries_state {
+ struct smbd_server_connection *sconn;
+ struct file_id fid;
+ struct server_id self;
+};
+
+static bool validate_my_share_entries_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct validate_my_share_entries_state *state = private_data;
+ files_struct *fsp;
+
+ if (!server_id_equal(&state->self, &e->pid)) {
+ return false;
+ }
+
+ if (e->op_mid == 0) {
+ /* INTERNAL_OPEN_ONLY */
+ return false;
+ }
+
+ fsp = file_find_dif(state->sconn, state->fid, e->share_file_id);
+ if (!fsp) {
+ DBG_ERR("PANIC : %s\n",
+ share_mode_str(talloc_tos(), 0, &state->fid, e));
+ smb_panic("validate_my_share_entries: Cannot match a "
+ "share entry with an open file\n");
+ }
+
+ if (((uint16_t)fsp->oplock_type) != e->op_type) {
+ goto panic;
+ }
+
+ return false;
+
+ panic:
+ {
+ char *str;
+ DBG_ERR("validate_my_share_entries: PANIC : %s\n",
+ share_mode_str(talloc_tos(), 0, &state->fid, e));
+ str = talloc_asprintf(talloc_tos(),
+ "validate_my_share_entries: "
+ "file %s, oplock_type = 0x%x, op_type = 0x%x\n",
+ fsp->fsp_name->base_name,
+ (unsigned int)fsp->oplock_type,
+ (unsigned int)e->op_type);
+ smb_panic(str);
+ }
+
+ return false;
+}
+#endif
+
+/**
+ * Allowed access mask for stat opens relevant to oplocks
+ **/
+bool is_oplock_stat_open(uint32_t access_mask)
+{
+ const uint32_t stat_open_bits =
+ (SYNCHRONIZE_ACCESS|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES);
+
+ return (((access_mask & stat_open_bits) != 0) &&
+ ((access_mask & ~stat_open_bits) == 0));
+}
+
+/**
+ * Allowed access mask for stat opens relevant to leases
+ **/
+bool is_lease_stat_open(uint32_t access_mask)
+{
+ const uint32_t stat_open_bits =
+ (SYNCHRONIZE_ACCESS|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES|
+ READ_CONTROL_ACCESS);
+
+ return (((access_mask & stat_open_bits) != 0) &&
+ ((access_mask & ~stat_open_bits) == 0));
+}
+
+struct has_delete_on_close_state {
+ bool ret;
+};
+
+static bool has_delete_on_close_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct has_delete_on_close_state *state = private_data;
+ state->ret = !share_entry_stale_pid(e);
+ return state->ret;
+}
+
+static bool has_delete_on_close(struct share_mode_lock *lck,
+ uint32_t name_hash)
+{
+ struct has_delete_on_close_state state = { .ret = false };
+ bool ok;
+
+ if (!is_delete_on_close_set(lck, name_hash)) {
+ return false;
+ }
+
+ ok= share_mode_forall_entries(lck, has_delete_on_close_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ return false;
+ }
+ return state.ret;
+}
+
+static void share_mode_flags_restrict(
+ struct share_mode_lock *lck,
+ uint32_t access_mask,
+ uint32_t share_mode,
+ uint32_t lease_type)
+{
+ uint32_t existing_access_mask, existing_share_mode;
+ uint32_t existing_lease_type;
+
+ share_mode_flags_get(
+ lck,
+ &existing_access_mask,
+ &existing_share_mode,
+ &existing_lease_type);
+
+ existing_access_mask |= access_mask;
+ if (access_mask & conflicting_access) {
+ existing_share_mode &= share_mode;
+ }
+ existing_lease_type |= lease_type;
+
+ share_mode_flags_set(
+ lck,
+ existing_access_mask,
+ existing_share_mode,
+ existing_lease_type,
+ NULL);
+}
+
+/****************************************************************************
+ Deal with share modes
+ Invariant: Share mode must be locked on entry and exit.
+ Returns -1 on error, or number of share modes on success (may be zero).
+****************************************************************************/
+
+struct open_mode_check_state {
+ struct file_id fid;
+ uint32_t access_mask;
+ uint32_t share_access;
+ uint32_t lease_type;
+};
+
+static bool open_mode_check_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct open_mode_check_state *state = private_data;
+ bool disconnected, stale;
+ uint32_t access_mask, share_access, lease_type;
+
+ disconnected = server_id_is_disconnected(&e->pid);
+ if (disconnected) {
+ return false;
+ }
+
+ access_mask = state->access_mask | e->access_mask;
+ share_access = state->share_access;
+ if (e->access_mask & conflicting_access) {
+ share_access &= e->share_access;
+ }
+ lease_type = state->lease_type | get_lease_type(e, state->fid);
+
+ if ((access_mask == state->access_mask) &&
+ (share_access == state->share_access) &&
+ (lease_type == state->lease_type)) {
+ return false;
+ }
+
+ stale = share_entry_stale_pid(e);
+ if (stale) {
+ return false;
+ }
+
+ state->access_mask = access_mask;
+ state->share_access = share_access;
+ state->lease_type = lease_type;
+
+ return false;
+}
+
+static NTSTATUS open_mode_check(connection_struct *conn,
+ struct file_id fid,
+ struct share_mode_lock *lck,
+ uint32_t access_mask,
+ uint32_t share_access)
+{
+ struct open_mode_check_state state;
+ bool ok, conflict;
+ bool modified = false;
+
+ if (is_oplock_stat_open(access_mask)) {
+ /* Stat open that doesn't trigger oplock breaks or share mode
+ * checks... ! JRA. */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Check if the share modes will give us access.
+ */
+
+#if defined(DEVELOPER)
+ {
+ struct validate_my_share_entries_state validate_state = {
+ .sconn = conn->sconn,
+ .fid = fid,
+ .self = messaging_server_id(conn->sconn->msg_ctx),
+ };
+ ok = share_mode_forall_entries(
+ lck, validate_my_share_entries_fn, &validate_state);
+ SMB_ASSERT(ok);
+ }
+#endif
+
+ share_mode_flags_get(
+ lck, &state.access_mask, &state.share_access, NULL);
+
+ conflict = share_conflict(
+ state.access_mask,
+ state.share_access,
+ access_mask,
+ share_access);
+ if (!conflict) {
+ DBG_DEBUG("No conflict due to share_mode_flags access\n");
+ return NT_STATUS_OK;
+ }
+
+ state = (struct open_mode_check_state) {
+ .fid = fid,
+ .share_access = (FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE),
+ };
+
+ /*
+ * Walk the share mode array to recalculate d->flags
+ */
+
+ ok = share_mode_forall_entries(lck, open_mode_check_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ share_mode_flags_set(
+ lck,
+ state.access_mask,
+ state.share_access,
+ state.lease_type,
+ &modified);
+ if (!modified) {
+ /*
+ * We only end up here if we had a sharing violation
+ * from d->flags and have recalculated it.
+ */
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ conflict = share_conflict(
+ state.access_mask,
+ state.share_access,
+ access_mask,
+ share_access);
+ if (!conflict) {
+ DBG_DEBUG("No conflict due to share_mode_flags access\n");
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_SHARING_VIOLATION;
+}
+
+/*
+ * Send a break message to the oplock holder and delay the open for
+ * our client.
+ */
+
+NTSTATUS send_break_message(struct messaging_context *msg_ctx,
+ const struct file_id *id,
+ const struct share_mode_entry *exclusive,
+ uint16_t break_to)
+{
+ struct oplock_break_message msg = {
+ .id = *id,
+ .share_file_id = exclusive->share_file_id,
+ .break_to = break_to,
+ };
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ if (DEBUGLVL(10)) {
+ struct server_id_buf buf;
+ DBG_DEBUG("Sending break message to %s\n",
+ server_id_str_buf(exclusive->pid, &buf));
+ NDR_PRINT_DEBUG(oplock_break_message, &msg);
+ }
+
+ ndr_err = ndr_push_struct_blob(
+ &blob,
+ talloc_tos(),
+ &msg,
+ (ndr_push_flags_fn_t)ndr_push_oplock_break_message);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("ndr_push_oplock_break_message failed: %s\n",
+ ndr_errstr(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ status = messaging_send(
+ msg_ctx, exclusive->pid, MSG_SMB_BREAK_REQUEST, &blob);
+ TALLOC_FREE(blob.data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not send oplock break message: %s\n",
+ nt_errstr(status)));
+ }
+
+ return status;
+}
+
+struct validate_oplock_types_state {
+ bool valid;
+ bool batch;
+ bool ex_or_batch;
+ bool level2;
+ bool no_oplock;
+ uint32_t num_non_stat_opens;
+};
+
+static bool validate_oplock_types_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct validate_oplock_types_state *state = private_data;
+
+ if (e->op_mid == 0) {
+ /* INTERNAL_OPEN_ONLY */
+ return false;
+ }
+
+ if (e->op_type == NO_OPLOCK && is_oplock_stat_open(e->access_mask)) {
+ /*
+ * We ignore stat opens in the table - they always
+ * have NO_OPLOCK and never get or cause breaks. JRA.
+ */
+ return false;
+ }
+
+ state->num_non_stat_opens += 1;
+
+ if (BATCH_OPLOCK_TYPE(e->op_type)) {
+ /* batch - can only be one. */
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale batch oplock\n");
+ return false;
+ }
+ if (state->ex_or_batch ||
+ state->batch ||
+ state->level2 ||
+ state->no_oplock) {
+ DBG_ERR("Bad batch oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->batch = true;
+ }
+
+ if (EXCLUSIVE_OPLOCK_TYPE(e->op_type)) {
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale duplicate oplock\n");
+ return false;
+ }
+ /* Exclusive or batch - can only be one. */
+ if (state->ex_or_batch ||
+ state->level2 ||
+ state->no_oplock) {
+ DBG_ERR("Bad exclusive or batch oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->ex_or_batch = true;
+ }
+
+ if (LEVEL_II_OPLOCK_TYPE(e->op_type)) {
+ if (state->batch || state->ex_or_batch) {
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale LevelII oplock\n");
+ return false;
+ }
+ DBG_DEBUG("Bad levelII oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->level2 = true;
+ }
+
+ if (e->op_type == NO_OPLOCK) {
+ if (state->batch || state->ex_or_batch) {
+ if (share_entry_stale_pid(e)) {
+ DBG_DEBUG("Found stale NO_OPLOCK entry\n");
+ return false;
+ }
+ DBG_ERR("Bad no oplock entry\n");
+ state->valid = false;
+ return true;
+ }
+ state->no_oplock = true;
+ }
+
+ return false;
+}
+
+/*
+ * Do internal consistency checks on the share mode for a file.
+ */
+
+static bool validate_oplock_types(struct share_mode_lock *lck)
+{
+ struct validate_oplock_types_state state = { .valid = true };
+ static bool skip_validation;
+ bool validate;
+ bool ok;
+
+ if (skip_validation) {
+ return true;
+ }
+
+ validate = lp_parm_bool(-1, "smbd", "validate_oplock_types", false);
+ if (!validate) {
+ DBG_DEBUG("smbd:validate_oplock_types not set to yes\n");
+ skip_validation = true;
+ return true;
+ }
+
+ ok = share_mode_forall_entries(lck, validate_oplock_types_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ return false;
+ }
+ if (!state.valid) {
+ DBG_DEBUG("Got invalid oplock configuration\n");
+ return false;
+ }
+
+ if ((state.batch || state.ex_or_batch) &&
+ (state.num_non_stat_opens != 1)) {
+ DBG_WARNING("got batch (%d) or ex (%d) non-exclusively "
+ "(%"PRIu32")\n",
+ (int)state.batch,
+ (int)state.ex_or_batch,
+ state.num_non_stat_opens);
+ return false;
+ }
+
+ return true;
+}
+
+static bool is_same_lease(const files_struct *fsp,
+ const struct share_mode_entry *e,
+ const struct smb2_lease *lease)
+{
+ if (e->op_type != LEASE_OPLOCK) {
+ return false;
+ }
+ if (lease == NULL) {
+ return false;
+ }
+
+ return smb2_lease_equal(fsp_client_guid(fsp),
+ &lease->lease_key,
+ &e->client_guid,
+ &e->lease_key);
+}
+
+static bool file_has_brlocks(files_struct *fsp)
+{
+ struct byte_range_lock *br_lck;
+
+ br_lck = brl_get_locks_readonly(fsp);
+ if (!br_lck)
+ return false;
+
+ return (brl_num_locks(br_lck) > 0);
+}
+
+struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
+ const struct smb2_lease_key *key,
+ uint32_t current_state,
+ uint16_t lease_version,
+ uint16_t lease_epoch)
+{
+ struct files_struct *fsp;
+
+ /*
+ * TODO: Measure how expensive this loop is with thousands of open
+ * handles...
+ */
+
+ for (fsp = file_find_di_first(new_fsp->conn->sconn, new_fsp->file_id, true);
+ fsp != NULL;
+ fsp = file_find_di_next(fsp, true)) {
+
+ if (fsp == new_fsp) {
+ continue;
+ }
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ continue;
+ }
+ if (smb2_lease_key_equal(&fsp->lease->lease.lease_key, key)) {
+ fsp->lease->ref_count += 1;
+ return fsp->lease;
+ }
+ }
+
+ /* Not found - must be leased in another smbd. */
+ new_fsp->lease = talloc_zero(new_fsp->conn->sconn, struct fsp_lease);
+ if (new_fsp->lease == NULL) {
+ return NULL;
+ }
+ new_fsp->lease->ref_count = 1;
+ new_fsp->lease->sconn = new_fsp->conn->sconn;
+ new_fsp->lease->lease.lease_key = *key;
+ new_fsp->lease->lease.lease_state = current_state;
+ /*
+ * We internally treat all leases as V2 and update
+ * the epoch, but when sending breaks it matters if
+ * the requesting lease was v1 or v2.
+ */
+ new_fsp->lease->lease.lease_version = lease_version;
+ new_fsp->lease->lease.lease_epoch = lease_epoch;
+ return new_fsp->lease;
+}
+
+static NTSTATUS try_lease_upgrade(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ bool do_upgrade;
+ uint32_t current_state, breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ uint32_t existing, requested;
+ NTSTATUS status;
+
+ status = leases_db_get(
+ client_guid,
+ &lease->lease_key,
+ &fsp->file_id,
+ &current_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ fsp->lease = find_fsp_lease(
+ fsp,
+ &lease->lease_key,
+ current_state,
+ lease_version,
+ epoch);
+ if (fsp->lease == NULL) {
+ DEBUG(1, ("Did not find existing lease for file %s\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Upgrade only if the requested lease is a strict upgrade.
+ */
+ existing = current_state;
+ requested = lease->lease_state;
+
+ /*
+ * Tricky: This test makes sure that "requested" is a
+ * strict bitwise superset of "existing".
+ */
+ do_upgrade = ((existing & requested) == existing);
+
+ /*
+ * Upgrade only if there's a change.
+ */
+ do_upgrade &= (granted != existing);
+
+ /*
+ * Upgrade only if other leases don't prevent what was asked
+ * for.
+ */
+ do_upgrade &= (granted == requested);
+
+ /*
+ * only upgrade if we are not in breaking state
+ */
+ do_upgrade &= !breaking;
+
+ DEBUG(10, ("existing=%"PRIu32", requested=%"PRIu32", "
+ "granted=%"PRIu32", do_upgrade=%d\n",
+ existing, requested, granted, (int)do_upgrade));
+
+ if (do_upgrade) {
+ NTSTATUS set_status;
+
+ current_state = granted;
+ epoch += 1;
+
+ set_status = leases_db_set(
+ client_guid,
+ &lease->lease_key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ return set_status;
+ }
+ }
+
+ fsp_lease_update(fsp);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS grant_new_fsp_lease(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct GUID *client_guid,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ NTSTATUS status;
+
+ fsp->lease = talloc_zero(fsp->conn->sconn, struct fsp_lease);
+ if (fsp->lease == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ fsp->lease->ref_count = 1;
+ fsp->lease->sconn = fsp->conn->sconn;
+ fsp->lease->lease.lease_version = lease->lease_version;
+ fsp->lease->lease.lease_key = lease->lease_key;
+ fsp->lease->lease.lease_state = granted;
+ fsp->lease->lease.lease_epoch = lease->lease_epoch + 1;
+
+ status = leases_db_add(client_guid,
+ &lease->lease_key,
+ &fsp->file_id,
+ fsp->lease->lease.lease_state,
+ fsp->lease->lease.lease_version,
+ fsp->lease->lease.lease_epoch,
+ fsp->conn->connectpath,
+ fsp->fsp_name->base_name,
+ fsp->fsp_name->stream_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("%s: leases_db_add failed: %s\n", __func__,
+ nt_errstr(status)));
+ TALLOC_FREE(fsp->lease);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /*
+ * We used to set lck->data->modified=true here without
+ * actually modifying lck->data, triggering a needless
+ * writeback of lck->data.
+ *
+ * Apart from that writeback, setting modified=true has the
+ * effect of triggering all waiters for this file to
+ * retry. This only makes sense if any blocking condition
+ * (i.e. waiting for a lease to be downgraded or removed) is
+ * gone. This routine here only adds a lease, so it will never
+ * free up resources that blocked waiters can now claim. So
+ * that second effect also does not matter in this
+ * routine. Thus setting lck->data->modified=true does not
+ * need to be done here.
+ */
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS grant_fsp_lease(struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ const struct smb2_lease *lease,
+ uint32_t granted)
+{
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ NTSTATUS status;
+
+ status = try_lease_upgrade(fsp, lck, client_guid, lease, granted);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = grant_new_fsp_lease(
+ fsp, lck, client_guid, lease, granted);
+ }
+
+ return status;
+}
+
+static int map_lease_type_to_oplock(uint32_t lease_type)
+{
+ int result = NO_OPLOCK;
+
+ switch (lease_type) {
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE:
+ result = BATCH_OPLOCK|EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_WRITE:
+ result = EXCLUSIVE_OPLOCK;
+ break;
+ case SMB2_LEASE_READ|SMB2_LEASE_HANDLE:
+ case SMB2_LEASE_READ:
+ result = LEVEL_II_OPLOCK;
+ break;
+ }
+
+ return result;
+}
+
+struct delay_for_oplock_state {
+ struct files_struct *fsp;
+ const struct smb2_lease *lease;
+ bool will_overwrite;
+ uint32_t delay_mask;
+ bool first_open_attempt;
+ bool got_handle_lease;
+ bool got_oplock;
+ bool have_other_lease;
+ uint32_t total_lease_types;
+ bool delay;
+};
+
+static bool delay_for_oplock_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct delay_for_oplock_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+ const struct smb2_lease *lease = state->lease;
+ bool e_is_lease = (e->op_type == LEASE_OPLOCK);
+ uint32_t e_lease_type = SMB2_LEASE_NONE;
+ uint32_t break_to;
+ bool lease_is_breaking = false;
+
+ if (e_is_lease) {
+ NTSTATUS status;
+
+ if (lease != NULL) {
+ bool our_lease = is_same_lease(fsp, e, lease);
+ if (our_lease) {
+ DBG_DEBUG("Ignoring our own lease\n");
+ return false;
+ }
+ }
+
+ status = leases_db_get(
+ &e->client_guid,
+ &e->lease_key,
+ &fsp->file_id,
+ &e_lease_type, /* current_state */
+ &lease_is_breaking,
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+
+ /*
+ * leases_db_get() can return NT_STATUS_NOT_FOUND
+ * if the share_mode_entry e is stale and the
+ * lease record was already removed. In this case return
+ * false so the traverse continues.
+ */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND) &&
+ share_entry_stale_pid(e))
+ {
+ struct GUID_txt_buf guid_strbuf;
+ struct file_id_buf file_id_strbuf;
+ DBG_DEBUG("leases_db_get for client_guid [%s] "
+ "lease_key [%"PRIu64"/%"PRIu64"] "
+ "file_id [%s] failed for stale "
+ "share_mode_entry\n",
+ GUID_buf_string(&e->client_guid, &guid_strbuf),
+ e->lease_key.data[0],
+ e->lease_key.data[1],
+ file_id_str_buf(fsp->file_id, &file_id_strbuf));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf guid_strbuf;
+ struct file_id_buf file_id_strbuf;
+ DBG_ERR("leases_db_get for client_guid [%s] "
+ "lease_key [%"PRIu64"/%"PRIu64"] "
+ "file_id [%s] failed: %s\n",
+ GUID_buf_string(&e->client_guid, &guid_strbuf),
+ e->lease_key.data[0],
+ e->lease_key.data[1],
+ file_id_str_buf(fsp->file_id, &file_id_strbuf),
+ nt_errstr(status));
+ smb_panic("leases_db_get() failed");
+ }
+ } else {
+ e_lease_type = get_lease_type(e, fsp->file_id);
+ }
+
+ if (((e_lease_type & ~state->total_lease_types) != 0) &&
+ !share_entry_stale_pid(e))
+ {
+ state->total_lease_types |= e_lease_type;
+ }
+
+ if (!state->got_handle_lease &&
+ ((e_lease_type & SMB2_LEASE_HANDLE) != 0) &&
+ !share_entry_stale_pid(e)) {
+ state->got_handle_lease = true;
+ }
+
+ if (!state->got_oplock &&
+ (e->op_type != LEASE_OPLOCK) &&
+ !share_entry_stale_pid(e)) {
+ state->got_oplock = true;
+ }
+
+ if (!state->have_other_lease &&
+ !is_same_lease(fsp, e, lease) &&
+ !share_entry_stale_pid(e)) {
+ state->have_other_lease = true;
+ }
+
+ if (e_is_lease && is_lease_stat_open(fsp->access_mask)) {
+ return false;
+ }
+
+ break_to = e_lease_type & ~state->delay_mask;
+
+ if (state->will_overwrite) {
+ break_to &= ~(SMB2_LEASE_HANDLE|SMB2_LEASE_READ);
+ }
+
+ DBG_DEBUG("e_lease_type %u, will_overwrite: %u\n",
+ (unsigned)e_lease_type,
+ (unsigned)state->will_overwrite);
+
+ if ((e_lease_type & ~break_to) == 0) {
+ if (lease_is_breaking) {
+ state->delay = true;
+ }
+ return false;
+ }
+
+ if (share_entry_stale_pid(e)) {
+ return false;
+ }
+
+ if (state->will_overwrite) {
+ /*
+ * If we break anyway break to NONE directly.
+ * Otherwise vfs_set_filelen() will trigger the
+ * break.
+ */
+ break_to &= ~(SMB2_LEASE_READ|SMB2_LEASE_WRITE);
+ }
+
+ if (!e_is_lease) {
+ /*
+ * Oplocks only support breaking to R or NONE.
+ */
+ break_to &= ~(SMB2_LEASE_HANDLE|SMB2_LEASE_WRITE);
+ }
+
+ DBG_DEBUG("breaking from %d to %d\n",
+ (int)e_lease_type,
+ (int)break_to);
+ send_break_message(
+ fsp->conn->sconn->msg_ctx, &fsp->file_id, e, break_to);
+ if (e_lease_type & state->delay_mask) {
+ state->delay = true;
+ }
+ if (lease_is_breaking && !state->first_open_attempt) {
+ state->delay = true;
+ }
+
+ return false;
+};
+
+static NTSTATUS delay_for_oplock(files_struct *fsp,
+ int oplock_request,
+ const struct smb2_lease *lease,
+ struct share_mode_lock *lck,
+ bool have_sharing_violation,
+ uint32_t create_disposition,
+ bool first_open_attempt,
+ int *poplock_type,
+ uint32_t *pgranted)
+{
+ struct delay_for_oplock_state state = {
+ .fsp = fsp,
+ .lease = lease,
+ .first_open_attempt = first_open_attempt,
+ };
+ uint32_t requested;
+ uint32_t granted;
+ int oplock_type;
+ bool ok;
+
+ *poplock_type = NO_OPLOCK;
+ *pgranted = 0;
+
+ if (fsp->fsp_flags.is_directory) {
+ /*
+ * No directory leases yet
+ */
+ SMB_ASSERT(oplock_request == NO_OPLOCK);
+ if (have_sharing_violation) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ return NT_STATUS_OK;
+ }
+
+ if (oplock_request == LEASE_OPLOCK) {
+ if (lease == NULL) {
+ /*
+ * The SMB2 layer should have checked this
+ */
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ requested = lease->lease_state;
+ } else {
+ requested = map_oplock_to_lease_type(
+ oplock_request & ~SAMBA_PRIVATE_OPLOCK_MASK);
+ }
+
+ share_mode_flags_get(lck, NULL, NULL, &state.total_lease_types);
+
+ if (is_oplock_stat_open(fsp->access_mask)) {
+ goto grant;
+ }
+
+ state.delay_mask = have_sharing_violation ?
+ SMB2_LEASE_HANDLE : SMB2_LEASE_WRITE;
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE:
+ case FILE_OVERWRITE_IF:
+ state.will_overwrite = true;
+ break;
+ default:
+ state.will_overwrite = false;
+ break;
+ }
+
+ state.total_lease_types = SMB2_LEASE_NONE;
+ ok = share_mode_forall_entries(lck, delay_for_oplock_fn, &state);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (state.delay) {
+ return NT_STATUS_RETRY;
+ }
+
+grant:
+ if (have_sharing_violation) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ granted = requested;
+
+ if (oplock_request == LEASE_OPLOCK) {
+ if (lp_kernel_oplocks(SNUM(fsp->conn))) {
+ DEBUG(10, ("No lease granted because kernel oplocks are enabled\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if ((granted & (SMB2_LEASE_READ|SMB2_LEASE_WRITE)) == 0) {
+ DEBUG(10, ("No read or write lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if (granted == SMB2_LEASE_WRITE) {
+ DEBUG(10, ("pure write lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ if (granted == (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+ DEBUG(10, ("write and handle lease requested\n"));
+ granted = SMB2_LEASE_NONE;
+ }
+ }
+
+ if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) {
+ DBG_DEBUG("file %s has byte range locks\n",
+ fsp_str_dbg(fsp));
+ granted &= ~SMB2_LEASE_READ;
+ }
+
+ if (state.have_other_lease) {
+ /*
+ * Can grant only one writer
+ */
+ granted &= ~SMB2_LEASE_WRITE;
+ }
+
+ if ((granted & SMB2_LEASE_READ) && !(granted & SMB2_LEASE_WRITE)) {
+ bool allow_level2 =
+ (global_client_caps & CAP_LEVEL_II_OPLOCKS) &&
+ lp_level2_oplocks(SNUM(fsp->conn));
+
+ if (!allow_level2) {
+ granted = SMB2_LEASE_NONE;
+ }
+ }
+
+ if (oplock_request == LEASE_OPLOCK) {
+ if (state.got_oplock) {
+ granted &= ~SMB2_LEASE_HANDLE;
+ }
+
+ oplock_type = LEASE_OPLOCK;
+ } else {
+ if (state.got_handle_lease) {
+ granted = SMB2_LEASE_NONE;
+ }
+
+ /*
+ * Reflect possible downgrades from:
+ * - map_lease_type_to_oplock() => "RH" to just LEVEL_II
+ */
+ oplock_type = map_lease_type_to_oplock(granted);
+ granted = map_oplock_to_lease_type(oplock_type);
+ }
+
+ state.total_lease_types |= granted;
+
+ {
+ uint32_t acc, sh, ls;
+ share_mode_flags_get(lck, &acc, &sh, &ls);
+ ls = state.total_lease_types;
+ share_mode_flags_set(lck, acc, sh, ls, NULL);
+ }
+
+ DBG_DEBUG("oplock type 0x%x granted (%s%s%s)(0x%x), on file %s, "
+ "requested 0x%x (%s%s%s)(0x%x) => total (%s%s%s)(0x%x)\n",
+ fsp->oplock_type,
+ granted & SMB2_LEASE_READ ? "R":"",
+ granted & SMB2_LEASE_WRITE ? "W":"",
+ granted & SMB2_LEASE_HANDLE ? "H":"",
+ granted,
+ fsp_str_dbg(fsp),
+ oplock_request,
+ requested & SMB2_LEASE_READ ? "R":"",
+ requested & SMB2_LEASE_WRITE ? "W":"",
+ requested & SMB2_LEASE_HANDLE ? "H":"",
+ requested,
+ state.total_lease_types & SMB2_LEASE_READ ? "R":"",
+ state.total_lease_types & SMB2_LEASE_WRITE ? "W":"",
+ state.total_lease_types & SMB2_LEASE_HANDLE ? "H":"",
+ state.total_lease_types);
+
+ *poplock_type = oplock_type;
+ *pgranted = granted;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS handle_share_mode_lease(
+ files_struct *fsp,
+ struct share_mode_lock *lck,
+ uint32_t create_disposition,
+ uint32_t access_mask,
+ uint32_t share_access,
+ int oplock_request,
+ const struct smb2_lease *lease,
+ bool first_open_attempt,
+ int *poplock_type,
+ uint32_t *pgranted)
+{
+ bool sharing_violation = false;
+ NTSTATUS status;
+
+ *poplock_type = NO_OPLOCK;
+ *pgranted = 0;
+
+ status = open_mode_check(
+ fsp->conn, fsp->file_id, lck, access_mask, share_access);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ sharing_violation = true;
+ status = NT_STATUS_OK; /* handled later */
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (oplock_request == INTERNAL_OPEN_ONLY) {
+ if (sharing_violation) {
+ DBG_DEBUG("Sharing violation for internal open\n");
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ /*
+ * Internal opens never do oplocks or leases. We don't
+ * need to go through delay_for_oplock().
+ */
+ return NT_STATUS_OK;
+ }
+
+ status = delay_for_oplock(
+ fsp,
+ oplock_request,
+ lease,
+ lck,
+ sharing_violation,
+ create_disposition,
+ first_open_attempt,
+ poplock_type,
+ pgranted);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool request_timed_out(struct smb_request *req, struct timeval timeout)
+{
+ struct timeval now, end_time;
+ GetTimeOfDay(&now);
+ end_time = timeval_sum(&req->request_time, &timeout);
+ return (timeval_compare(&end_time, &now) < 0);
+}
+
+struct defer_open_state {
+ struct smbXsrv_connection *xconn;
+ uint64_t mid;
+};
+
+static void defer_open_done(struct tevent_req *req);
+
+/**
+ * Defer an open and watch a locking.tdb record
+ *
+ * This defers an open that gets rescheduled once the locking.tdb record watch
+ * is triggered by a change to the record.
+ *
+ * It is used to defer opens that triggered an oplock break and for the SMB1
+ * sharing violation delay.
+ **/
+static void defer_open(struct share_mode_lock *lck,
+ struct timeval timeout,
+ struct smb_request *req,
+ struct file_id id)
+{
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval abs_timeout;
+ struct defer_open_state *watch_state;
+ struct tevent_req *watch_req;
+ struct timeval_buf tvbuf1, tvbuf2;
+ struct file_id_buf fbuf;
+ bool ok;
+
+ abs_timeout = timeval_sum(&req->request_time, &timeout);
+
+ DBG_DEBUG("request time [%s] timeout [%s] mid [%" PRIu64 "] "
+ "file_id [%s]\n",
+ timeval_str_buf(&req->request_time, false, true, &tvbuf1),
+ timeval_str_buf(&abs_timeout, false, true, &tvbuf2),
+ req->mid,
+ file_id_str_buf(id, &fbuf));
+
+ open_rec = talloc_zero(NULL, struct deferred_open_record);
+ if (open_rec == NULL) {
+ TALLOC_FREE(lck);
+ exit_server("talloc failed");
+ }
+
+ watch_state = talloc(open_rec, struct defer_open_state);
+ if (watch_state == NULL) {
+ exit_server("talloc failed");
+ }
+ watch_state->xconn = req->xconn;
+ watch_state->mid = req->mid;
+
+ DBG_DEBUG("deferring mid %" PRIu64 "\n", req->mid);
+
+ watch_req = share_mode_watch_send(
+ watch_state,
+ req->sconn->ev_ctx,
+ lck,
+ (struct server_id){0});
+ if (watch_req == NULL) {
+ exit_server("Could not watch share mode record");
+ }
+ tevent_req_set_callback(watch_req, defer_open_done, watch_state);
+
+ ok = tevent_req_set_endtime(watch_req, req->sconn->ev_ctx, abs_timeout);
+ if (!ok) {
+ exit_server("tevent_req_set_endtime failed");
+ }
+
+ ok = push_deferred_open_message_smb(req, timeout, id, open_rec);
+ if (!ok) {
+ TALLOC_FREE(lck);
+ exit_server("push_deferred_open_message_smb failed");
+ }
+}
+
+static void defer_open_done(struct tevent_req *req)
+{
+ struct defer_open_state *state = tevent_req_callback_data(
+ req, struct defer_open_state);
+ NTSTATUS status;
+ bool ret;
+
+ status = share_mode_watch_recv(req, NULL, NULL);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("dbwrap_watched_watch_recv returned %s\n",
+ nt_errstr(status)));
+ /*
+ * Even if it failed, retry anyway. TODO: We need a way to
+ * tell a re-scheduled open about that error.
+ */
+ }
+
+ DEBUG(10, ("scheduling mid %llu\n", (unsigned long long)state->mid));
+
+ ret = schedule_deferred_open_message_smb(state->xconn, state->mid);
+ SMB_ASSERT(ret);
+ TALLOC_FREE(state);
+}
+
+/**
+ * Actually attempt the kernel oplock polling open.
+ */
+
+static void poll_open_fn(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct deferred_open_record *open_rec = talloc_get_type_abort(
+ private_data, struct deferred_open_record);
+ bool ok;
+
+ TALLOC_FREE(open_rec->watch_req);
+
+ ok = schedule_deferred_open_message_smb(
+ open_rec->xconn, open_rec->mid);
+ if (!ok) {
+ exit_server("schedule_deferred_open_message_smb failed");
+ }
+ DBG_DEBUG("timer fired. Retrying open !\n");
+}
+
+static void poll_open_done(struct tevent_req *subreq);
+
+struct poll_open_setup_watcher_state {
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev_ctx;
+ struct tevent_req *watch_req;
+};
+
+static void poll_open_setup_watcher_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct poll_open_setup_watcher_state *state =
+ (struct poll_open_setup_watcher_state *)private_data;
+
+ if (!validate_oplock_types(lck)) {
+ smb_panic("validate_oplock_types failed");
+ }
+
+ state->watch_req = share_mode_watch_send(
+ state->mem_ctx,
+ state->ev_ctx,
+ lck,
+ (struct server_id) {0});
+ if (state->watch_req == NULL) {
+ DBG_WARNING("share_mode_watch_send failed\n");
+ return;
+ }
+}
+
+/**
+ * Reschedule an open for 1 second from now, if not timed out.
+ **/
+static bool setup_poll_open(
+ struct smb_request *req,
+ const struct file_id *id,
+ struct timeval max_timeout,
+ struct timeval interval)
+{
+ static struct file_id zero_id = {};
+ bool ok;
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval endtime, next_interval;
+ struct file_id_buf ftmp;
+
+ if (request_timed_out(req, max_timeout)) {
+ return false;
+ }
+
+ open_rec = talloc_zero(NULL, struct deferred_open_record);
+ if (open_rec == NULL) {
+ DBG_WARNING("talloc failed\n");
+ return false;
+ }
+ open_rec->xconn = req->xconn;
+ open_rec->mid = req->mid;
+
+ /*
+ * Make sure open_rec->te does not come later than the
+ * request's maximum endtime.
+ */
+
+ endtime = timeval_sum(&req->request_time, &max_timeout);
+ next_interval = timeval_current_ofs(interval.tv_sec, interval.tv_usec);
+ next_interval = timeval_min(&endtime, &next_interval);
+
+ open_rec->te = tevent_add_timer(
+ req->sconn->ev_ctx,
+ open_rec,
+ next_interval,
+ poll_open_fn,
+ open_rec);
+ if (open_rec->te == NULL) {
+ DBG_WARNING("tevent_add_timer failed\n");
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+
+ if (id != NULL) {
+ struct poll_open_setup_watcher_state wstate = {
+ .mem_ctx = open_rec,
+ .ev_ctx = req->sconn->ev_ctx,
+ };
+ NTSTATUS status;
+
+ status = share_mode_do_locked_vfs_denied(*id,
+ poll_open_setup_watcher_fn,
+ &wstate);
+ if (NT_STATUS_IS_OK(status)) {
+ if (wstate.watch_req == NULL) {
+ DBG_WARNING("share_mode_watch_send failed\n");
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+ open_rec->watch_req = wstate.watch_req;
+ tevent_req_set_callback(open_rec->watch_req,
+ poll_open_done,
+ open_rec);
+ } else if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DBG_WARNING("share_mode_do_locked_vfs_denied failed - %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+ } else {
+ id = &zero_id;
+ }
+
+ ok = push_deferred_open_message_smb(req, max_timeout, *id, open_rec);
+ if (!ok) {
+ DBG_WARNING("push_deferred_open_message_smb failed\n");
+ TALLOC_FREE(open_rec);
+ return false;
+ }
+
+ DBG_DEBUG("poll request time [%s] mid [%" PRIu64 "] file_id [%s]\n",
+ timeval_string(talloc_tos(), &req->request_time, false),
+ req->mid,
+ file_id_str_buf(*id, &ftmp));
+
+ return true;
+}
+
+static void poll_open_done(struct tevent_req *subreq)
+{
+ struct deferred_open_record *open_rec = tevent_req_callback_data(
+ subreq, struct deferred_open_record);
+ NTSTATUS status;
+ bool ok;
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ open_rec->watch_req = NULL;
+ TALLOC_FREE(open_rec->te);
+
+ DBG_DEBUG("dbwrap_watched_watch_recv returned %s\n",
+ nt_errstr(status));
+
+ ok = schedule_deferred_open_message_smb(
+ open_rec->xconn, open_rec->mid);
+ if (!ok) {
+ exit_server("schedule_deferred_open_message_smb failed");
+ }
+}
+
+bool defer_smb1_sharing_violation(struct smb_request *req)
+{
+ bool ok;
+ int timeout_usecs;
+
+ if (!lp_defer_sharing_violations()) {
+ return false;
+ }
+
+ /*
+ * Try every 200msec up to (by default) one second. To be
+ * precise, according to behaviour note <247> in [MS-CIFS],
+ * the server tries 5 times. But up to one second should be
+ * close enough.
+ */
+
+ timeout_usecs = lp_parm_int(
+ SNUM(req->conn),
+ "smbd",
+ "sharedelay",
+ SHARING_VIOLATION_USEC_WAIT);
+
+ ok = setup_poll_open(
+ req,
+ NULL,
+ (struct timeval) { .tv_usec = timeout_usecs },
+ (struct timeval) { .tv_usec = 200000 });
+ return ok;
+}
+
+/****************************************************************************
+ On overwrite open ensure that the attributes match.
+****************************************************************************/
+
+static bool open_match_attributes(connection_struct *conn,
+ uint32_t old_dos_attr,
+ uint32_t new_dos_attr,
+ mode_t new_unx_mode,
+ mode_t *returned_unx_mode)
+{
+ uint32_t noarch_old_dos_attr, noarch_new_dos_attr;
+
+ noarch_old_dos_attr = (old_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
+ noarch_new_dos_attr = (new_dos_attr & ~FILE_ATTRIBUTE_ARCHIVE);
+
+ if((noarch_old_dos_attr == 0 && noarch_new_dos_attr != 0) ||
+ (noarch_old_dos_attr != 0 && ((noarch_old_dos_attr & noarch_new_dos_attr) == noarch_old_dos_attr))) {
+ *returned_unx_mode = new_unx_mode;
+ } else {
+ *returned_unx_mode = (mode_t)0;
+ }
+
+ DEBUG(10,("open_match_attributes: old_dos_attr = 0x%x, "
+ "new_dos_attr = 0x%x "
+ "returned_unx_mode = 0%o\n",
+ (unsigned int)old_dos_attr,
+ (unsigned int)new_dos_attr,
+ (unsigned int)*returned_unx_mode ));
+
+ /* If we're mapping SYSTEM and HIDDEN ensure they match. */
+ if (lp_map_system(SNUM(conn)) || lp_store_dos_attributes(SNUM(conn))) {
+ if ((old_dos_attr & FILE_ATTRIBUTE_SYSTEM) &&
+ !(new_dos_attr & FILE_ATTRIBUTE_SYSTEM)) {
+ return False;
+ }
+ }
+ if (lp_map_hidden(SNUM(conn)) || lp_store_dos_attributes(SNUM(conn))) {
+ if ((old_dos_attr & FILE_ATTRIBUTE_HIDDEN) &&
+ !(new_dos_attr & FILE_ATTRIBUTE_HIDDEN)) {
+ return False;
+ }
+ }
+ return True;
+}
+
+static void schedule_defer_open(struct share_mode_lock *lck,
+ struct file_id id,
+ struct smb_request *req)
+{
+ /* This is a relative time, added to the absolute
+ request_time value to get the absolute timeout time.
+ Note that if this is the second or greater time we enter
+ this codepath for this particular request mid then
+ request_time is left as the absolute time of the *first*
+ time this request mid was processed. This is what allows
+ the request to eventually time out. */
+
+ struct timeval timeout;
+
+ /* Normally the smbd we asked should respond within
+ * OPLOCK_BREAK_TIMEOUT seconds regardless of whether
+ * the client did, give twice the timeout as a safety
+ * measure here in case the other smbd is stuck
+ * somewhere else. */
+
+ timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
+
+ if (request_timed_out(req, timeout)) {
+ return;
+ }
+
+ defer_open(lck, timeout, req, id);
+}
+
+/****************************************************************************
+ Reschedule an open call that went asynchronous.
+****************************************************************************/
+
+static void schedule_async_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ exit_server("async open timeout");
+}
+
+static void schedule_async_open(struct smb_request *req)
+{
+ struct deferred_open_record *open_rec = NULL;
+ struct timeval timeout = timeval_set(20, 0);
+ bool ok;
+
+ if (request_timed_out(req, timeout)) {
+ return;
+ }
+
+ open_rec = talloc_zero(NULL, struct deferred_open_record);
+ if (open_rec == NULL) {
+ exit_server("deferred_open_record_create failed");
+ }
+ open_rec->async_open = true;
+
+ ok = push_deferred_open_message_smb(
+ req, timeout, (struct file_id){0}, open_rec);
+ if (!ok) {
+ exit_server("push_deferred_open_message_smb failed");
+ }
+
+ open_rec->te = tevent_add_timer(req->sconn->ev_ctx,
+ req,
+ timeval_current_ofs(20, 0),
+ schedule_async_open_timer,
+ open_rec);
+ if (open_rec->te == NULL) {
+ exit_server("tevent_add_timer failed");
+ }
+}
+
+static NTSTATUS check_and_store_share_mode(
+ struct files_struct *fsp,
+ struct smb_request *req,
+ struct share_mode_lock *lck,
+ uint32_t create_disposition,
+ uint32_t access_mask,
+ uint32_t share_access,
+ int oplock_request,
+ const struct smb2_lease *lease,
+ bool first_open_attempt)
+{
+ NTSTATUS status;
+ int oplock_type = NO_OPLOCK;
+ uint32_t granted_lease = 0;
+ const struct smb2_lease_key *lease_key = NULL;
+ bool delete_on_close;
+ bool ok;
+
+ /* Get the types we need to examine. */
+ if (!validate_oplock_types(lck)) {
+ smb_panic("validate_oplock_types failed");
+ }
+
+ delete_on_close = has_delete_on_close(lck, fsp->name_hash);
+ if (delete_on_close) {
+ return NT_STATUS_DELETE_PENDING;
+ }
+
+ status = handle_share_mode_lease(fsp,
+ lck,
+ create_disposition,
+ access_mask,
+ share_access,
+ oplock_request,
+ lease,
+ first_open_attempt,
+ &oplock_type,
+ &granted_lease);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ schedule_defer_open(lck, fsp->file_id, req);
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (oplock_type == LEASE_OPLOCK) {
+ lease_key = &lease->lease_key;
+ }
+
+ share_mode_flags_restrict(lck, access_mask, share_access, 0);
+
+ ok = set_share_mode(lck,
+ fsp,
+ get_current_uid(fsp->conn),
+ req ? req->mid : 0,
+ oplock_type,
+ lease_key,
+ share_access,
+ access_mask);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (oplock_type == LEASE_OPLOCK) {
+ status = grant_fsp_lease(fsp, lck, lease, granted_lease);
+ if (!NT_STATUS_IS_OK(status)) {
+ del_share_mode(lck, fsp);
+ return status;
+ }
+
+ DBG_DEBUG("lease_state=%d\n", fsp->lease->lease.lease_state);
+ }
+
+ fsp->oplock_type = oplock_type;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Work out what access_mask to use from what the client sent us.
+****************************************************************************/
+
+static NTSTATUS smbd_calculate_maximum_allowed_access_fsp(
+ struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t *p_access_mask)
+{
+ struct security_descriptor *sd = NULL;
+ uint32_t access_granted = 0;
+ uint32_t dosattrs;
+ NTSTATUS status;
+
+ /* Cope with symlinks */
+ if (fsp == NULL || fsp_get_pathref_fd(fsp) == -1) {
+ *p_access_mask = FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+
+ /* Cope with fake/printer fsp's. */
+ if (fsp->fake_file_handle != NULL || fsp->print_file != NULL) {
+ *p_access_mask = FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+
+ if (!use_privs && (get_current_uid(fsp->conn) == (uid_t)0)) {
+ *p_access_mask |= FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(fsp),
+ (SECINFO_OWNER |
+ SECINFO_GROUP |
+ SECINFO_DACL),
+ talloc_tos(),
+ &sd);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * File did not exist
+ */
+ *p_access_mask = FILE_GENERIC_ALL;
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Could not get acl on file %s: %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * If we can access the path to this file, by
+ * default we have FILE_READ_ATTRIBUTES from the
+ * containing directory. See the section:
+ * "Algorithm to Check Access to an Existing File"
+ * in MS-FSA.pdf.
+ *
+ * se_file_access_check()
+ * also takes care of owner WRITE_DAC and READ_CONTROL.
+ */
+ status = se_file_access_check(sd,
+ get_current_nttok(fsp->conn),
+ use_privs,
+ (*p_access_mask & ~FILE_READ_ATTRIBUTES),
+ &access_granted);
+
+ TALLOC_FREE(sd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Status %s on file %s: "
+ "when calculating maximum access\n",
+ nt_errstr(status),
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ *p_access_mask = (access_granted | FILE_READ_ATTRIBUTES);
+
+ if (!(access_granted & DELETE_ACCESS)) {
+ if (can_delete_file_in_directory(fsp->conn,
+ dirfsp,
+ fsp->fsp_name)) {
+ *p_access_mask |= DELETE_ACCESS;
+ }
+ }
+
+ dosattrs = fdos_mode(fsp);
+ if ((dosattrs & FILE_ATTRIBUTE_READONLY) || !CAN_WRITE(fsp->conn)) {
+ *p_access_mask &= ~(FILE_GENERIC_WRITE | DELETE_ACCESS);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_calculate_access_mask_fsp(struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask,
+ uint32_t *access_mask_out)
+{
+ NTSTATUS status;
+ uint32_t orig_access_mask = access_mask;
+ uint32_t rejected_share_access;
+
+ if (access_mask & SEC_MASK_INVALID) {
+ DBG_DEBUG("access_mask [%8x] contains invalid bits\n",
+ access_mask);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Convert GENERIC bits to specific bits.
+ */
+
+ se_map_generic(&access_mask, &file_generic_mapping);
+
+ /* Calculate MAXIMUM_ALLOWED_ACCESS if requested. */
+ if (access_mask & MAXIMUM_ALLOWED_ACCESS) {
+
+ status = smbd_calculate_maximum_allowed_access_fsp(
+ dirfsp,
+ fsp,
+ use_privs,
+ &access_mask);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ access_mask &= fsp->conn->share_access;
+ }
+
+ rejected_share_access = access_mask & ~(fsp->conn->share_access);
+
+ if (rejected_share_access) {
+ DBG_INFO("Access denied on file %s: "
+ "rejected by share access mask[0x%08X] "
+ "orig[0x%08X] mapped[0x%08X] reject[0x%08X]\n",
+ fsp_str_dbg(fsp),
+ fsp->conn->share_access,
+ orig_access_mask, access_mask,
+ rejected_share_access);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ *access_mask_out = access_mask;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Remove the deferred open entry under lock.
+****************************************************************************/
+
+/****************************************************************************
+ Return true if this is a state pointer to an asynchronous create.
+****************************************************************************/
+
+bool is_deferred_open_async(const struct deferred_open_record *rec)
+{
+ return rec->async_open;
+}
+
+static bool clear_ads(uint32_t create_disposition)
+{
+ bool ret = false;
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE_IF:
+ case FILE_OVERWRITE:
+ ret = true;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static int disposition_to_open_flags(uint32_t create_disposition)
+{
+ int ret = 0;
+
+ /*
+ * Currently we're using FILE_SUPERSEDE as the same as
+ * FILE_OVERWRITE_IF but they really are
+ * different. FILE_SUPERSEDE deletes an existing file
+ * (requiring delete access) then recreates it.
+ */
+
+ switch (create_disposition) {
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE_IF:
+ /*
+ * If file exists replace/overwrite. If file doesn't
+ * exist create.
+ */
+ ret = O_CREAT|O_TRUNC;
+ break;
+
+ case FILE_OPEN:
+ /*
+ * If file exists open. If file doesn't exist error.
+ */
+ ret = 0;
+ break;
+
+ case FILE_OVERWRITE:
+ /*
+ * If file exists overwrite. If file doesn't exist
+ * error.
+ */
+ ret = O_TRUNC;
+ break;
+
+ case FILE_CREATE:
+ /*
+ * If file exists error. If file doesn't exist create.
+ */
+ ret = O_CREAT|O_EXCL;
+ break;
+
+ case FILE_OPEN_IF:
+ /*
+ * If file exists open. If file doesn't exist create.
+ */
+ ret = O_CREAT;
+ break;
+ }
+ return ret;
+}
+
+static int calculate_open_access_flags(uint32_t access_mask,
+ uint32_t private_flags,
+ NTTIME twrp)
+{
+ bool need_write, need_read;
+
+ /*
+ * Note that we ignore the append flag as append does not
+ * mean the same thing under DOS and Unix.
+ */
+
+ if (twrp != 0) {
+ /*
+ * Pave over the user requested mode and force O_RDONLY for the
+ * file handle. Windows allows opening a VSS file with O_RDWR,
+ * even though actual writes on the handle will fail.
+ */
+ return O_RDONLY;
+ }
+
+ need_write = (access_mask & (FILE_WRITE_DATA | FILE_APPEND_DATA));
+ if (!need_write) {
+ return O_RDONLY;
+ }
+
+ /* DENY_DOS opens are always underlying read-write on the
+ file handle, no matter what the requested access mask
+ says. */
+
+ need_read =
+ ((private_flags & NTCREATEX_FLAG_DENY_DOS) ||
+ access_mask & (FILE_READ_ATTRIBUTES|FILE_READ_DATA|
+ FILE_READ_EA|FILE_EXECUTE));
+
+ if (!need_read) {
+ return O_WRONLY;
+ }
+ return O_RDWR;
+}
+
+struct open_ntcreate_lock_state {
+ struct share_mode_entry_prepare_state prepare_state;
+ struct files_struct *fsp;
+ const char *object_type;
+ struct smb_request *req;
+ uint32_t create_disposition;
+ uint32_t access_mask;
+ uint32_t share_access;
+ int oplock_request;
+ const struct smb2_lease *lease;
+ bool first_open_attempt;
+ bool keep_locked;
+ NTSTATUS status;
+ struct timespec write_time;
+ share_mode_entry_prepare_unlock_fn_t cleanup_fn;
+};
+
+static void open_ntcreate_lock_add_entry(struct share_mode_lock *lck,
+ bool *keep_locked,
+ void *private_data)
+{
+ struct open_ntcreate_lock_state *state =
+ (struct open_ntcreate_lock_state *)private_data;
+
+ /*
+ * By default drop the g_lock again if we leave the
+ * tdb chainlock.
+ */
+ *keep_locked = false;
+
+ state->status = check_and_store_share_mode(state->fsp,
+ state->req,
+ lck,
+ state->create_disposition,
+ state->access_mask,
+ state->share_access,
+ state->oplock_request,
+ state->lease,
+ state->first_open_attempt);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return;
+ }
+
+ state->write_time = get_share_mode_write_time(lck);
+
+ /*
+ * keep the g_lock while existing the tdb chainlock,
+ * we we're asked to, which mean we'll keep
+ * the share_mode_lock during object creation,
+ * or setting delete on close.
+ */
+ *keep_locked = state->keep_locked;
+}
+
+static void open_ntcreate_lock_cleanup_oplock(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct open_ntcreate_lock_state *state =
+ (struct open_ntcreate_lock_state *)private_data;
+ bool ok;
+
+ ok = remove_share_oplock(lck, state->fsp);
+ if (!ok) {
+ DBG_ERR("Could not remove oplock for %s %s\n",
+ state->object_type, fsp_str_dbg(state->fsp));
+ }
+}
+
+static void open_ntcreate_lock_cleanup_entry(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct open_ntcreate_lock_state *state =
+ (struct open_ntcreate_lock_state *)private_data;
+ bool ok;
+
+ ok = del_share_mode(lck, state->fsp);
+ if (!ok) {
+ DBG_ERR("Could not delete share entry for %s %s\n",
+ state->object_type, fsp_str_dbg(state->fsp));
+ }
+}
+
+static void possibly_set_archive(struct connection_struct *conn,
+ struct files_struct *fsp,
+ struct smb_filename *smb_fname,
+ struct smb_filename *parent_dir_fname,
+ int info,
+ uint32_t dosattrs,
+ mode_t *unx_mode)
+{
+ bool set_archive = false;
+ int ret;
+
+ if (info == FILE_WAS_OPENED) {
+ return;
+ }
+
+ /* Overwritten files should be initially set as archive */
+ if ((info == FILE_WAS_OVERWRITTEN && lp_map_archive(SNUM(conn)))) {
+ set_archive = true;
+ } else if (lp_store_dos_attributes(SNUM(conn))) {
+ set_archive = true;
+ }
+ if (!set_archive) {
+ return;
+ }
+
+ ret = file_set_dosmode(conn,
+ smb_fname,
+ dosattrs | FILE_ATTRIBUTE_ARCHIVE,
+ parent_dir_fname,
+ true);
+ if (ret != 0) {
+ return;
+ }
+ *unx_mode = smb_fname->st.st_ex_mode;
+}
+
+/****************************************************************************
+ Open a file with a share mode. Passed in an already created files_struct *.
+****************************************************************************/
+
+static NTSTATUS open_file_ntcreate(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t access_mask, /* access bits (FILE_READ_DATA etc.) */
+ uint32_t share_access, /* share constants (FILE_SHARE_READ etc) */
+ uint32_t create_disposition, /* FILE_OPEN_IF etc. */
+ uint32_t create_options, /* options such as delete on close. */
+ uint32_t new_dos_attributes, /* attributes used for new file. */
+ int oplock_request, /* internal Samba oplock codes. */
+ const struct smb2_lease *lease,
+ /* Information (FILE_EXISTS etc.) */
+ uint32_t private_flags, /* Samba specific flags. */
+ struct smb_filename *parent_dir_fname, /* parent. */
+ struct smb_filename *smb_fname_atname, /* atname relative to parent. */
+ int *pinfo,
+ files_struct *fsp)
+{
+ struct smb_filename *smb_fname = fsp->fsp_name;
+ int flags=0;
+ bool file_existed = VALID_STAT(smb_fname->st);
+ bool def_acl = False;
+ bool posix_open = False;
+ bool new_file_created = False;
+ bool first_open_attempt = true;
+ bool is_twrp = (smb_fname_atname->twrp != 0);
+ NTSTATUS fsp_open = NT_STATUS_ACCESS_DENIED;
+ mode_t new_unx_mode = (mode_t)0;
+ mode_t unx_mode = (mode_t)0;
+ int info;
+ uint32_t existing_dos_attributes = 0;
+ struct open_ntcreate_lock_state lck_state = {};
+ bool keep_locked = false;
+ uint32_t open_access_mask = access_mask;
+ NTSTATUS status;
+ SMB_STRUCT_STAT saved_stat = smb_fname->st;
+ struct timespec old_write_time;
+ bool setup_poll = false;
+ NTSTATUS ulstatus;
+
+ if (conn->printer) {
+ /*
+ * Printers are handled completely differently.
+ * Most of the passed parameters are ignored.
+ */
+
+ if (pinfo) {
+ *pinfo = FILE_WAS_CREATED;
+ }
+
+ DBG_DEBUG("printer open fname=%s\n",
+ smb_fname_str_dbg(smb_fname));
+
+ if (!req) {
+ DBG_ERR("printer open without an SMB request!\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ return print_spool_open(fsp, smb_fname->base_name,
+ req->vuid);
+ }
+
+ if (new_dos_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ posix_open = True;
+ unx_mode = (mode_t)(new_dos_attributes & ~FILE_FLAG_POSIX_SEMANTICS);
+ new_dos_attributes = 0;
+ } else {
+ /* Windows allows a new file to be created and
+ silently removes a FILE_ATTRIBUTE_DIRECTORY
+ sent by the client. Do the same. */
+
+ new_dos_attributes &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+ /* We add FILE_ATTRIBUTE_ARCHIVE to this as this mode is only used if the file is
+ * created new. */
+ unx_mode = unix_mode(
+ conn,
+ new_dos_attributes | FILE_ATTRIBUTE_ARCHIVE,
+ smb_fname,
+ parent_dir_fname->fsp);
+ }
+
+ DEBUG(10, ("open_file_ntcreate: fname=%s, dos_attrs=0x%x "
+ "access_mask=0x%x share_access=0x%x "
+ "create_disposition = 0x%x create_options=0x%x "
+ "unix mode=0%o oplock_request=%d private_flags = 0x%x\n",
+ smb_fname_str_dbg(smb_fname), new_dos_attributes,
+ access_mask, share_access, create_disposition,
+ create_options, (unsigned int)unx_mode, oplock_request,
+ (unsigned int)private_flags));
+
+ if (req == NULL) {
+ /* Ensure req == NULL means INTERNAL_OPEN_ONLY */
+ SMB_ASSERT(oplock_request == INTERNAL_OPEN_ONLY);
+ } else {
+ /* And req != NULL means no INTERNAL_OPEN_ONLY */
+ SMB_ASSERT(((oplock_request & INTERNAL_OPEN_ONLY) == 0));
+ }
+
+ /*
+ * Only non-internal opens can be deferred at all
+ */
+
+ if (req) {
+ struct deferred_open_record *open_rec;
+ if (get_deferred_open_message_state(req, NULL, &open_rec)) {
+
+ /* If it was an async create retry, the file
+ didn't exist. */
+
+ if (is_deferred_open_async(open_rec)) {
+ SET_STAT_INVALID(smb_fname->st);
+ file_existed = false;
+ }
+
+ /* Ensure we don't reprocess this message. */
+ remove_deferred_open_message_smb(req->xconn, req->mid);
+
+ first_open_attempt = false;
+ }
+ }
+
+ if (!posix_open) {
+ new_dos_attributes &= SAMBA_ATTRIBUTES_MASK;
+ if (file_existed) {
+ /*
+ * Only use stored DOS attributes for checks
+ * against requested attributes (below via
+ * open_match_attributes()), cf bug #11992
+ * for details. -slow
+ */
+ uint32_t attr = 0;
+
+ status = SMB_VFS_FGET_DOS_ATTRIBUTES(
+ conn,
+ metadata_fsp(smb_fname->fsp),
+ &attr);
+ if (NT_STATUS_IS_OK(status)) {
+ existing_dos_attributes = attr;
+ }
+ }
+ }
+
+ /* ignore any oplock requests if oplocks are disabled */
+ if (!lp_oplocks(SNUM(conn)) ||
+ IS_VETO_OPLOCK_PATH(conn, smb_fname->base_name)) {
+ /* Mask off everything except the private Samba bits. */
+ oplock_request &= SAMBA_PRIVATE_OPLOCK_MASK;
+ }
+
+ /* this is for OS/2 long file names - say we don't support them */
+ if (req != NULL && !req->posix_pathnames &&
+ strstr(smb_fname->base_name,".+,;=[].")) {
+ /* OS/2 Workplace shell fix may be main code stream in a later
+ * release. */
+ DEBUG(5,("open_file_ntcreate: OS/2 long filenames are not "
+ "supported.\n"));
+ if (use_nt_status()) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return NT_STATUS_DOS(ERRDOS, ERRcannotopen);
+ }
+
+ switch( create_disposition ) {
+ case FILE_OPEN:
+ /* If file exists open. If file doesn't exist error. */
+ if (!file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_OPEN "
+ "requested for file %s and file "
+ "doesn't exist.\n",
+ smb_fname_str_dbg(smb_fname)));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ break;
+
+ case FILE_OVERWRITE:
+ /* If file exists overwrite. If file doesn't exist
+ * error. */
+ if (!file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_OVERWRITE "
+ "requested for file %s and file "
+ "doesn't exist.\n",
+ smb_fname_str_dbg(smb_fname) ));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (is_twrp) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ break;
+
+ case FILE_CREATE:
+ /* If file exists error. If file doesn't exist
+ * create. */
+ if (file_existed) {
+ DEBUG(5,("open_file_ntcreate: FILE_CREATE "
+ "requested for file %s and file "
+ "already exists.\n",
+ smb_fname_str_dbg(smb_fname)));
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+ if (is_twrp) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ break;
+
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE_IF:
+ if (is_twrp) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ break;
+ case FILE_OPEN_IF:
+ if (is_twrp) {
+ if (!file_existed) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ create_disposition = FILE_OPEN;
+ }
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = disposition_to_open_flags(create_disposition);
+
+ /* We only care about matching attributes on file exists and
+ * overwrite. */
+
+ if (!posix_open && file_existed &&
+ ((create_disposition == FILE_OVERWRITE) ||
+ (create_disposition == FILE_OVERWRITE_IF))) {
+ if (!open_match_attributes(conn, existing_dos_attributes,
+ new_dos_attributes,
+ unx_mode, &new_unx_mode)) {
+ DEBUG(5,("open_file_ntcreate: attributes mismatch "
+ "for file %s (%x %x) (0%o, 0%o)\n",
+ smb_fname_str_dbg(smb_fname),
+ existing_dos_attributes,
+ new_dos_attributes,
+ (unsigned int)smb_fname->st.st_ex_mode,
+ (unsigned int)unx_mode ));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ status = smbd_calculate_access_mask_fsp(parent_dir_fname->fsp,
+ smb_fname->fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_calculate_access_mask_fsp "
+ "on file %s returned %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ return status;
+ }
+
+ open_access_mask = access_mask;
+
+ if (flags & O_TRUNC) {
+ open_access_mask |= FILE_WRITE_DATA; /* This will cause oplock breaks. */
+ }
+
+ if (file_existed) {
+ /*
+ * stat opens on existing files don't get oplocks.
+ * They can get leases.
+ *
+ * Note that we check for stat open on the *open_access_mask*,
+ * i.e. the access mask we actually used to do the open,
+ * not the one the client asked for (which is in
+ * fsp->access_mask). This is due to the fact that
+ * FILE_OVERWRITE and FILE_OVERWRITE_IF add in O_TRUNC,
+ * which adds FILE_WRITE_DATA to open_access_mask.
+ */
+ if (is_oplock_stat_open(open_access_mask) && lease == NULL) {
+ oplock_request = NO_OPLOCK;
+ }
+ }
+
+ DEBUG(10, ("open_file_ntcreate: fname=%s, after mapping "
+ "access_mask=0x%x\n", smb_fname_str_dbg(smb_fname),
+ access_mask));
+
+ /*
+ * Note that we ignore the append flag as append does not
+ * mean the same thing under DOS and Unix.
+ */
+
+ flags |= calculate_open_access_flags(access_mask,
+ private_flags,
+ smb_fname->twrp);
+
+ /*
+ * Currently we only look at FILE_WRITE_THROUGH for create options.
+ */
+
+#if defined(O_SYNC)
+ if ((create_options & FILE_WRITE_THROUGH) && lp_strict_sync(SNUM(conn))) {
+ flags |= O_SYNC;
+ }
+#endif /* O_SYNC */
+
+ if (posix_open && (access_mask & FILE_APPEND_DATA)) {
+ flags |= O_APPEND;
+ }
+
+ if (!posix_open && !CAN_WRITE(conn)) {
+ /*
+ * We should really return a permission denied error if either
+ * O_CREAT or O_TRUNC are set, but for compatibility with
+ * older versions of Samba we just AND them out.
+ */
+ flags &= ~(O_CREAT | O_TRUNC);
+ }
+
+ /*
+ * With kernel oplocks the open breaking an oplock
+ * blocks until the oplock holder has given up the
+ * oplock or closed the file. We prevent this by always
+ * trying to open the file with O_NONBLOCK (see "man
+ * fcntl" on Linux).
+ *
+ * If a process that doesn't use the smbd open files
+ * database or communication methods holds a kernel
+ * oplock we must periodically poll for available open
+ * using O_NONBLOCK.
+ */
+ flags |= O_NONBLOCK;
+
+ /*
+ * Ensure we can't write on a read-only share or file.
+ */
+
+ if (((flags & O_ACCMODE) != O_RDONLY) && file_existed &&
+ (!CAN_WRITE(conn) ||
+ (existing_dos_attributes & FILE_ATTRIBUTE_READONLY))) {
+ DEBUG(5,("open_file_ntcreate: write access requested for "
+ "file %s on read only %s\n",
+ smb_fname_str_dbg(smb_fname),
+ !CAN_WRITE(conn) ? "share" : "file" ));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (VALID_STAT(smb_fname->st)) {
+ /*
+ * Only try and create a file id before open
+ * for an existing file. For a file being created
+ * this won't do anything useful until the file
+ * exists and has a valid stat struct.
+ */
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ }
+ fh_set_private_options(fsp->fh, private_flags);
+ fsp->access_mask = open_access_mask; /* We change this to the
+ * requested access_mask after
+ * the open is done. */
+ if (posix_open) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
+
+ if ((create_options & FILE_DELETE_ON_CLOSE) && (flags & O_CREAT) &&
+ !file_existed) {
+ /* Delete on close semantics for new files. */
+ status = can_set_delete_on_close(fsp,
+ new_dos_attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ /*
+ * Ensure we pay attention to default ACLs on directories if required.
+ */
+
+ if ((flags & O_CREAT) && lp_inherit_acls(SNUM(conn)) &&
+ (def_acl = directory_has_default_acl_fsp(parent_dir_fname->fsp))) {
+ unx_mode = (0777 & lp_create_mask(SNUM(conn)));
+ }
+
+ DEBUG(4,
+ ("calling open_file with flags=0x%X mode=0%o, "
+ "access_mask = 0x%x, open_access_mask = 0x%x\n",
+ (unsigned int)flags,
+ (unsigned int)unx_mode,
+ (unsigned int)access_mask,
+ (unsigned int)open_access_mask));
+
+ {
+ struct vfs_open_how how = {
+ .flags = flags,
+ .mode = unx_mode,
+ };
+
+ if (create_options & FILE_OPEN_FOR_BACKUP_INTENT) {
+ how.resolve |= VFS_OPEN_HOW_WITH_BACKUP_INTENT;
+ }
+
+ fsp_open = open_file(req,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
+ fsp,
+ &how,
+ access_mask,
+ open_access_mask,
+ private_flags,
+ &new_file_created);
+ }
+ if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_NETWORK_BUSY)) {
+ if (file_existed && S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) {
+ DEBUG(10, ("FIFO busy\n"));
+ return NT_STATUS_NETWORK_BUSY;
+ }
+ if (req == NULL) {
+ DEBUG(10, ("Internal open busy\n"));
+ return NT_STATUS_NETWORK_BUSY;
+ }
+ /*
+ * This handles the kernel oplock case:
+ *
+ * the file has an active kernel oplock and the open() returned
+ * EWOULDBLOCK/EAGAIN which maps to NETWORK_BUSY.
+ *
+ * "Samba locking.tdb oplocks" are handled below after acquiring
+ * the sharemode lock with get_share_mode_lock().
+ */
+ setup_poll = true;
+ }
+
+ if (NT_STATUS_EQUAL(fsp_open, NT_STATUS_RETRY)) {
+ /*
+ * EINTR from the open(2) syscall. Just setup a retry
+ * in a bit. We can't use the sys_write() tight retry
+ * loop here, as we might have to actually deal with
+ * lease-break signals to avoid a deadlock.
+ */
+ setup_poll = true;
+ }
+
+ if (setup_poll) {
+ /*
+ * Retry once a second. If there's a share_mode_lock
+ * around, also wait for it in case it was smbd
+ * holding that kernel oplock that can quickly tell us
+ * the oplock got removed.
+ */
+
+ setup_poll_open(
+ req,
+ &fsp->file_id,
+ timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0),
+ timeval_set(1, 0));
+
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+
+ if (!NT_STATUS_IS_OK(fsp_open)) {
+ bool wait_for_aio = NT_STATUS_EQUAL(
+ fsp_open, NT_STATUS_MORE_PROCESSING_REQUIRED);
+ if (wait_for_aio) {
+ schedule_async_open(req);
+ }
+ return fsp_open;
+ }
+
+ if (new_file_created) {
+ /*
+ * As we atomically create using O_CREAT|O_EXCL,
+ * then if new_file_created is true, then
+ * file_existed *MUST* have been false (even
+ * if the file was previously detected as being
+ * there).
+ */
+ file_existed = false;
+ }
+
+ if (file_existed && !check_same_dev_ino(&saved_stat, &smb_fname->st)) {
+ /*
+ * The file did exist, but some other (local or NFS)
+ * process either renamed/unlinked and re-created the
+ * file with different dev/ino after we walked the path,
+ * but before we did the open. We could retry the
+ * open but it's a rare enough case it's easier to
+ * just fail the open to prevent creating any problems
+ * in the open file db having the wrong dev/ino key.
+ */
+ fd_close(fsp);
+ DBG_WARNING("file %s - dev/ino mismatch. "
+ "Old (dev=%ju, ino=%ju). "
+ "New (dev=%ju, ino=%ju). Failing open "
+ "with NT_STATUS_ACCESS_DENIED.\n",
+ smb_fname_str_dbg(smb_fname),
+ (uintmax_t)saved_stat.st_ex_dev,
+ (uintmax_t)saved_stat.st_ex_ino,
+ (uintmax_t)smb_fname->st.st_ex_dev,
+ (uintmax_t)smb_fname->st.st_ex_ino);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ old_write_time = smb_fname->st.st_ex_mtime;
+
+ /*
+ * Deal with the race condition where two smbd's detect the
+ * file doesn't exist and do the create at the same time. One
+ * of them will win and set a share mode, the other (ie. this
+ * one) should check if the requested share mode for this
+ * create is allowed.
+ */
+
+ /*
+ * Now the file exists and fsp is successfully opened,
+ * fsp->dev and fsp->inode are valid and should replace the
+ * dev=0,inode=0 from a non existent file. Spotted by
+ * Nadav Danieli <nadavd@exanet.com>. JRA.
+ */
+
+ if (new_file_created) {
+ info = FILE_WAS_CREATED;
+ } else {
+ if (flags & O_TRUNC) {
+ info = FILE_WAS_OVERWRITTEN;
+ } else {
+ info = FILE_WAS_OPENED;
+ }
+ }
+
+ /*
+ * If we created a new file, overwrite an existing one
+ * or going to delete it later, we should keep
+ * the share_mode_lock (g_lock) until we call
+ * share_mode_entry_prepare_unlock()
+ */
+ if (info != FILE_WAS_OPENED) {
+ keep_locked = true;
+ } else if (create_options & FILE_DELETE_ON_CLOSE) {
+ keep_locked = true;
+ }
+
+ lck_state = (struct open_ntcreate_lock_state) {
+ .fsp = fsp,
+ .object_type = "file",
+ .req = req,
+ .create_disposition = create_disposition,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .oplock_request = oplock_request,
+ .lease = lease,
+ .first_open_attempt = first_open_attempt,
+ .keep_locked = keep_locked,
+ };
+
+ status = share_mode_entry_prepare_lock_add(&lck_state.prepare_state,
+ fsp->file_id,
+ conn->connectpath,
+ smb_fname,
+ &old_write_time,
+ open_ntcreate_lock_add_entry,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_add() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+
+ status = lck_state.status;
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ /*
+ * From here we need to use 'goto unlock;' instead of return !!!
+ */
+
+ if (fsp->oplock_type != NO_OPLOCK && fsp->oplock_type != LEASE_OPLOCK) {
+ /*
+ * Now ask for kernel oplocks
+ * and cleanup on failure.
+ */
+ status = set_file_oplock(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Could not get the kernel oplock
+ */
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_oplock;
+ fsp->oplock_type = NO_OPLOCK;
+ }
+ }
+
+ /* Should we atomically (to the client at least) truncate ? */
+ if ((!new_file_created) && (flags & O_TRUNC) &&
+ (S_ISREG(fsp->fsp_name->st.st_ex_mode))) {
+ int ret;
+
+ ret = SMB_VFS_FTRUNCATE(fsp, 0);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_SIZE
+ | FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ /*
+ * We have the share entry *locked*.....
+ */
+
+ /* Delete streams if create_disposition requires it */
+ if (!new_file_created &&
+ clear_ads(create_disposition) &&
+ !fsp_is_alternate_stream(fsp)) {
+ status = delete_all_streams(conn, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+ }
+
+ if (!fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1 &&
+ lp_kernel_share_modes(SNUM(conn)))
+ {
+ int ret;
+ /*
+ * Beware: streams implementing VFS modules may
+ * implement streams in a way that fsp will have the
+ * basefile open in the fsp fd, so lacking a distinct
+ * fd for the stream the file-system sharemode will
+ * apply on the basefile which is wrong. The actual
+ * check is deferred to the VFS module implementing
+ * the file-system sharemode call.
+ */
+ ret = SMB_VFS_FILESYSTEM_SHAREMODE(fsp,
+ share_access,
+ access_mask);
+ if (ret == -1){
+ status = NT_STATUS_SHARING_VIOLATION;
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+
+ fsp->fsp_flags.kernel_share_modes_taken = true;
+ }
+
+ /*
+ * At this point onwards, we can guarantee that the share entry
+ * is locked, whether we created the file or not, and that the
+ * deny mode is compatible with all current opens.
+ */
+
+ /*
+ * According to Samba4, SEC_FILE_READ_ATTRIBUTE is always granted,
+ * but we don't have to store this - just ignore it on access check.
+ */
+ if (conn->sconn->using_smb2) {
+ /*
+ * SMB2 doesn't return it (according to Microsoft tests).
+ * Test Case: TestSuite_ScenarioNo009GrantedAccessTestS0
+ * File created with access = 0x7 (Read, Write, Delete)
+ * Query Info on file returns 0x87 (Read, Write, Delete, Read Attributes)
+ */
+ fsp->access_mask = access_mask;
+ } else {
+ /* But SMB1 does. */
+ fsp->access_mask = access_mask | FILE_READ_ATTRIBUTES;
+ }
+
+ if (pinfo) {
+ *pinfo = info;
+ }
+
+ /* Handle strange delete on close create semantics. */
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ if (!new_file_created) {
+ status = can_set_delete_on_close(fsp,
+ existing_dos_attributes);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Remember to delete the mode we just added. */
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+ }
+ /* Note that here we set the *initial* delete on close flag,
+ not the regular one. The magic gets handled in close. */
+ fsp->fsp_flags.initial_delete_on_close = true;
+ }
+
+ possibly_set_archive(conn,
+ fsp,
+ smb_fname,
+ parent_dir_fname,
+ info,
+ new_dos_attributes,
+ &smb_fname->st.st_ex_mode);
+
+ /* Determine sparse flag. */
+ if (posix_open) {
+ /* POSIX opens are sparse by default. */
+ fsp->fsp_flags.is_sparse = true;
+ } else {
+ fsp->fsp_flags.is_sparse =
+ (existing_dos_attributes & FILE_ATTRIBUTE_SPARSE);
+ }
+
+ /*
+ * Take care of inherited ACLs on created files - if default ACL not
+ * selected.
+ */
+
+ if (!posix_open && new_file_created && !def_acl) {
+ if (unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
+ "attributes of file %s to 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)unx_mode);
+ }
+ }
+
+ } else if (new_unx_mode) {
+ /*
+ * We only get here in the case of:
+ *
+ * a). Not a POSIX open.
+ * b). File already existed.
+ * c). File was overwritten.
+ * d). Requested DOS attributes didn't match
+ * the DOS attributes on the existing file.
+ *
+ * In that case new_unx_mode has been set
+ * equal to the calculated mode (including
+ * possible inheritance of the mode from the
+ * containing directory).
+ *
+ * Note this mode was calculated with the
+ * DOS attribute FILE_ATTRIBUTE_ARCHIVE added,
+ * so the mode change here is suitable for
+ * an overwritten file.
+ */
+
+ if (new_unx_mode != smb_fname->st.st_ex_mode) {
+ int ret = SMB_VFS_FCHMOD(fsp, new_unx_mode);
+ if (ret == -1) {
+ DBG_INFO("failed to reset "
+ "attributes of file %s to 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)new_unx_mode);
+ }
+ }
+ }
+
+ /*
+ * Deal with other opens having a modified write time.
+ */
+ if (fsp_getinfo_ask_sharemode(fsp) &&
+ !is_omit_timespec(&lck_state.write_time))
+ {
+ update_stat_ex_mtime(&fsp->fsp_name->st, lck_state.write_time);
+ }
+
+ status = NT_STATUS_OK;
+
+unlock:
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS mkdir_internal(connection_struct *conn,
+ struct smb_filename *parent_dir_fname, /* parent. */
+ struct smb_filename *smb_fname_atname, /* atname relative to parent. */
+ struct smb_filename *smb_dname, /* full pathname from root of share. */
+ uint32_t file_attributes,
+ struct files_struct *fsp)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ mode_t mode;
+ NTSTATUS status;
+ bool posix_open = false;
+ bool need_re_stat = false;
+ uint32_t access_mask = SEC_DIR_ADD_SUBDIR;
+ struct vfs_open_how how = { .flags = O_RDONLY|O_DIRECTORY, };
+ int ret;
+
+ if (!CAN_WRITE(conn) || (access_mask & ~(conn->share_access))) {
+ DEBUG(5,("mkdir_internal: failing share access "
+ "%s\n", lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ posix_open = true;
+ mode = (mode_t)(file_attributes & ~FILE_FLAG_POSIX_SEMANTICS);
+ } else {
+ mode = unix_mode(conn,
+ FILE_ATTRIBUTE_DIRECTORY,
+ smb_dname,
+ parent_dir_fname->fsp);
+ }
+
+ status = check_parent_access_fsp(parent_dir_fname->fsp, access_mask);
+ if(!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("check_parent_access_fsp "
+ "on directory %s for path %s returned %s\n",
+ smb_fname_str_dbg(parent_dir_fname),
+ smb_dname->base_name,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (lp_inherit_acls(SNUM(conn))) {
+ if (directory_has_default_acl_fsp(parent_dir_fname->fsp)) {
+ mode = (0777 & lp_directory_mask(SNUM(conn)));
+ }
+ }
+
+ ret = SMB_VFS_MKDIRAT(conn,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
+ mode);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * Make this a pathref fsp for now. open_directory() will reopen as a
+ * full fsp.
+ */
+ fsp->fsp_flags.is_pathref = true;
+
+ status = fd_openat(parent_dir_fname->fsp, smb_fname_atname, fsp, &how);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Ensure we're checking for a symlink here.... */
+ /* We don't want to get caught by a symlink racer. */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("Could not stat directory '%s' just created: %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(status)));
+ return status;
+ }
+
+ if (!S_ISDIR(smb_dname->st.st_ex_mode)) {
+ DEBUG(0, ("Directory '%s' just created is not a directory !\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ if (lp_store_dos_attributes(SNUM(conn))) {
+ file_set_dosmode(conn,
+ smb_dname,
+ file_attributes | FILE_ATTRIBUTE_DIRECTORY,
+ parent_dir_fname,
+ true);
+ }
+
+ if (lp_inherit_permissions(SNUM(conn))) {
+ inherit_access_posix_acl(conn, parent_dir_fname->fsp,
+ smb_dname, mode);
+ need_re_stat = true;
+ }
+
+ if (!posix_open) {
+ /*
+ * Check if high bits should have been set,
+ * then (if bits are missing): add them.
+ * Consider bits automagically set by UNIX, i.e. SGID bit from parent
+ * dir.
+ */
+ if ((mode & ~(S_IRWXU|S_IRWXG|S_IRWXO)) &&
+ (mode & ~smb_dname->st.st_ex_mode)) {
+ SMB_VFS_FCHMOD(fsp,
+ (smb_dname->st.st_ex_mode |
+ (mode & ~smb_dname->st.st_ex_mode)));
+ need_re_stat = true;
+ }
+ }
+
+ /* Change the owner if required. */
+ if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO) {
+ change_dir_owner_to_parent_fsp(parent_dir_fname->fsp,
+ fsp);
+ need_re_stat = true;
+ }
+
+ if (need_re_stat) {
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("Could not stat directory '%s' just created: %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(status)));
+ return status;
+ }
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_ADDED, FILE_NOTIFY_CHANGE_DIR_NAME,
+ smb_dname->base_name);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Open a directory from an NT SMB call.
+****************************************************************************/
+
+static NTSTATUS open_directory(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ struct smb_filename *parent_dir_fname,
+ struct smb_filename *smb_fname_atname,
+ int *pinfo,
+ struct files_struct *fsp)
+{
+ struct smb_filename *smb_dname = fsp->fsp_name;
+ bool dir_existed = VALID_STAT(smb_dname->st);
+ struct open_ntcreate_lock_state lck_state = {};
+ bool keep_locked = false;
+ NTSTATUS status;
+ struct timespec mtimespec;
+ int info = 0;
+ uint32_t need_fd_access;
+ NTSTATUS ulstatus;
+
+ if (is_ntfs_stream_smb_fname(smb_dname)) {
+ DEBUG(2, ("open_directory: %s is a stream name!\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ if (!(file_attributes & FILE_FLAG_POSIX_SEMANTICS)) {
+ /* Ensure we have a directory attribute. */
+ file_attributes |= FILE_ATTRIBUTE_DIRECTORY;
+ }
+
+ DBG_INFO("opening directory %s, access_mask = 0x%"PRIx32", "
+ "share_access = 0x%"PRIx32" create_options = 0x%"PRIx32", "
+ "create_disposition = 0x%"PRIx32", "
+ "file_attributes = 0x%"PRIx32"\n",
+ smb_fname_str_dbg(smb_dname),
+ access_mask,
+ share_access,
+ create_options,
+ create_disposition,
+ file_attributes);
+
+ status = smbd_calculate_access_mask_fsp(parent_dir_fname->fsp,
+ smb_dname->fsp,
+ false,
+ access_mask,
+ &access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_calculate_access_mask_fsp "
+ "on file %s returned %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ return status;
+ }
+
+ if ((access_mask & SEC_FLAG_SYSTEM_SECURITY) &&
+ !security_token_has_privilege(get_current_nttok(conn),
+ SEC_PRIV_SECURITY)) {
+ DEBUG(10, ("open_directory: open on %s "
+ "failed - SEC_FLAG_SYSTEM_SECURITY denied.\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_PRIVILEGE_NOT_HELD;
+ }
+
+ switch( create_disposition ) {
+ case FILE_OPEN:
+
+ if (!dir_existed) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ info = FILE_WAS_OPENED;
+ break;
+
+ case FILE_CREATE:
+
+ /* If directory exists error. If directory doesn't
+ * exist create. */
+
+ if (dir_existed) {
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ DEBUG(2, ("open_directory: unable to create "
+ "%s. Error was %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (smb_fname_atname->twrp != 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ status = mkdir_internal(conn,
+ parent_dir_fname,
+ smb_fname_atname,
+ smb_dname,
+ file_attributes,
+ fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("open_directory: unable to create "
+ "%s. Error was %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ info = FILE_WAS_CREATED;
+ break;
+
+ case FILE_OPEN_IF:
+ /*
+ * If directory exists open. If directory doesn't
+ * exist create.
+ */
+
+ if (dir_existed) {
+ status = NT_STATUS_OK;
+ info = FILE_WAS_OPENED;
+ } else {
+ if (smb_fname_atname->twrp != 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+ status = mkdir_internal(conn,
+ parent_dir_fname,
+ smb_fname_atname,
+ smb_dname,
+ file_attributes,
+ fsp);
+
+ if (NT_STATUS_IS_OK(status)) {
+ info = FILE_WAS_CREATED;
+ } else {
+ int ret;
+ /* Cope with create race. */
+ if (!NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_COLLISION)) {
+ DEBUG(2, ("open_directory: unable to create "
+ "%s. Error was %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status)));
+ return status;
+ }
+
+ /*
+ * If mkdir_internal() returned
+ * NT_STATUS_OBJECT_NAME_COLLISION
+ * we still must lstat the path.
+ */
+ ret = SMB_VFS_FSTATAT(
+ conn,
+ parent_dir_fname->fsp,
+ smb_fname_atname,
+ &smb_dname->st,
+ AT_SYMLINK_NOFOLLOW);
+ if (ret == -1) {
+ DEBUG(2, ("Could not stat "
+ "directory '%s' just "
+ "opened: %s\n",
+ smb_fname_str_dbg(
+ smb_dname),
+ strerror(errno)));
+ return map_nt_error_from_unix(
+ errno);
+ }
+
+ info = FILE_WAS_OPENED;
+ }
+ }
+
+ break;
+
+ case FILE_SUPERSEDE:
+ case FILE_OVERWRITE:
+ case FILE_OVERWRITE_IF:
+ default:
+ DEBUG(5,("open_directory: invalid create_disposition "
+ "0x%x for directory %s\n",
+ (unsigned int)create_disposition,
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if(!S_ISDIR(smb_dname->st.st_ex_mode)) {
+ DEBUG(5,("open_directory: %s is not a directory !\n",
+ smb_fname_str_dbg(smb_dname)));
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ /*
+ * Setup the files_struct for it.
+ */
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_dname->st);
+ fsp->vuid = req ? req->vuid : UID_FIELD_INVALID;
+ fsp->file_pid = req ? req->smbpid : 0;
+ fsp->fsp_flags.can_lock = false;
+ fsp->fsp_flags.can_read = false;
+ fsp->fsp_flags.can_write = false;
+
+ fh_set_private_options(fsp->fh, 0);
+ /*
+ * According to Samba4, SEC_FILE_READ_ATTRIBUTE is always granted,
+ */
+ fsp->access_mask = access_mask | FILE_READ_ATTRIBUTES;
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = true;
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ fsp->posix_flags |= FSP_POSIX_FLAGS_ALL;
+ }
+
+ /* Don't store old timestamps for directory
+ handles in the internal database. We don't
+ update them in there if new objects
+ are created in the directory. Currently
+ we only update timestamps on file writes.
+ See bug #9870.
+ */
+ mtimespec = make_omit_timespec();
+
+ /*
+ * Obviously for FILE_LIST_DIRECTORY we need to reopen to get an fd
+ * usable for reading a directory. SMB2_FLUSH may be called on
+ * directories opened with FILE_ADD_FILE and FILE_ADD_SUBDIRECTORY so
+ * for those we need to reopen as well.
+ */
+ need_fd_access =
+ FILE_LIST_DIRECTORY |
+ FILE_ADD_FILE |
+ FILE_ADD_SUBDIRECTORY;
+
+ if (access_mask & need_fd_access) {
+ struct vfs_open_how how = {
+ .flags = O_RDONLY | O_DIRECTORY,
+ };
+ bool file_created;
+
+ status = reopen_from_fsp(parent_dir_fname->fsp,
+ smb_fname_atname,
+ fsp,
+ &how,
+ &file_created);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("Could not open fd for [%s]: %s\n",
+ smb_fname_str_dbg(smb_dname),
+ nt_errstr(status));
+ return status;
+ }
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ if(!S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ DEBUG(5,("open_directory: %s is not a directory !\n",
+ smb_fname_str_dbg(smb_dname)));
+ fd_close(fsp);
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ /* Ensure there was no race condition. We need to check
+ * dev/inode but not permissions, as these can change
+ * legitimately */
+ if (!check_same_dev_ino(&smb_dname->st, &fsp->fsp_name->st)) {
+ DEBUG(5,("open_directory: stat struct differs for "
+ "directory %s.\n",
+ smb_fname_str_dbg(smb_dname)));
+ fd_close(fsp);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (info == FILE_WAS_OPENED) {
+ status = smbd_check_access_rights_fsp(parent_dir_fname->fsp,
+ fsp,
+ false,
+ access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("smbd_check_access_rights_fsp on "
+ "file %s failed with %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+ }
+
+ /*
+ * If we created a new directory or going to delete it later,
+ * we should keep * the share_mode_lock (g_lock) until we call
+ * share_mode_entry_prepare_unlock()
+ */
+ if (info != FILE_WAS_OPENED) {
+ keep_locked = true;
+ } else if (create_options & FILE_DELETE_ON_CLOSE) {
+ keep_locked = true;
+ }
+
+ lck_state = (struct open_ntcreate_lock_state) {
+ .fsp = fsp,
+ .object_type = "directory",
+ .req = req,
+ .create_disposition = create_disposition,
+ .access_mask = access_mask,
+ .share_access = share_access,
+ .oplock_request = NO_OPLOCK,
+ .lease = NULL,
+ .first_open_attempt = true,
+ .keep_locked = keep_locked,
+ };
+
+ status = share_mode_entry_prepare_lock_add(&lck_state.prepare_state,
+ fsp->file_id,
+ conn->connectpath,
+ smb_dname,
+ &mtimespec,
+ open_ntcreate_lock_add_entry,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_entry_prepare_lock_add() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(status));
+ fd_close(fsp);
+ return status;
+ }
+
+ status = lck_state.status;
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ /*
+ * From here we need to use 'goto unlock;' instead of return !!!
+ */
+
+ /* For directories the delete on close bit at open time seems
+ always to be honored on close... See test 19 in Samba4 BASE-DELETE. */
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ status = can_set_delete_on_close(fsp, 0);
+ if (!NT_STATUS_IS_OK(status) && !NT_STATUS_EQUAL(status, NT_STATUS_DIRECTORY_NOT_EMPTY)) {
+ lck_state.cleanup_fn =
+ open_ntcreate_lock_cleanup_entry;
+ goto unlock;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Note that here we set the *initial* delete on close flag,
+ not the regular one. The magic gets handled in close. */
+ fsp->fsp_flags.initial_delete_on_close = true;
+ }
+ }
+
+ /*
+ * Deal with other opens having a modified write time.
+ */
+ if (!is_omit_timespec(&lck_state.write_time)) {
+ update_stat_ex_mtime(&fsp->fsp_name->st, lck_state.write_time);
+ }
+
+ if (pinfo) {
+ *pinfo = info;
+ }
+
+ status = NT_STATUS_OK;
+
+unlock:
+ ulstatus = share_mode_entry_prepare_unlock(&lck_state.prepare_state,
+ lck_state.cleanup_fn,
+ &lck_state);
+ if (!NT_STATUS_IS_OK(ulstatus)) {
+ DBG_ERR("share_mode_entry_prepare_unlock() failed for %s - %s\n",
+ smb_fname_str_dbg(smb_dname), nt_errstr(ulstatus));
+ smb_panic("share_mode_entry_prepare_unlock() failed!");
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS create_directory(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_dname)
+{
+ NTSTATUS status;
+ files_struct *fsp;
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* fname */
+ FILE_READ_ATTRIBUTES, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Receive notification that one of our open files has been renamed by another
+ smbd process.
+****************************************************************************/
+
+void msg_file_was_renamed(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct file_rename_message *msg = NULL;
+ enum ndr_err_code ndr_err;
+ files_struct *fsp;
+ struct smb_filename *smb_fname = NULL;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ msg = talloc(talloc_tos(), struct file_rename_message);
+ if (msg == NULL) {
+ DBG_WARNING("talloc failed\n");
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ data,
+ msg,
+ msg,
+ (ndr_pull_flags_fn_t)ndr_pull_file_rename_message);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_oplock_break_message failed: %s\n",
+ ndr_errstr(ndr_err));
+ goto out;
+ }
+ if (DEBUGLEVEL >= 10) {
+ struct server_id_buf buf;
+ DBG_DEBUG("Got rename message from %s\n",
+ server_id_str_buf(src, &buf));
+ NDR_PRINT_DEBUG(file_rename_message, msg);
+ }
+
+ /* stream_name must always be NULL if there is no stream. */
+ if ((msg->stream_name != NULL) && (msg->stream_name[0] == '\0')) {
+ msg->stream_name = NULL;
+ }
+
+ smb_fname = synthetic_smb_fname(msg,
+ msg->base_name,
+ msg->stream_name,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ DBG_DEBUG("synthetic_smb_fname failed\n");
+ goto out;
+ }
+
+ fsp = file_find_dif(sconn, msg->id, msg->share_file_id);
+ if (fsp == NULL) {
+ DBG_DEBUG("fsp not found\n");
+ goto out;
+ }
+
+ if (strcmp(fsp->conn->connectpath, msg->servicepath) == 0) {
+ SMB_STRUCT_STAT fsp_orig_sbuf;
+ NTSTATUS status;
+ DBG_DEBUG("renaming file %s from %s -> %s\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname));
+
+ /*
+ * The incoming smb_fname here has an
+ * invalid stat struct from synthetic_smb_fname()
+ * above.
+ * Preserve the existing stat from the
+ * open fsp after fsp_set_smb_fname()
+ * overwrites with the invalid stat.
+ *
+ * (We could just copy this into
+ * smb_fname->st, but keep this code
+ * identical to the fix in rename_open_files()
+ * for clarity.
+ *
+ * We will do an fstat before returning
+ * any of this metadata to the client anyway.
+ */
+ fsp_orig_sbuf = fsp->fsp_name->st;
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("fsp_set_smb_fname failed: %s\n",
+ nt_errstr(status));
+ }
+ fsp->fsp_name->st = fsp_orig_sbuf;
+ } else {
+ /* TODO. JRA. */
+ /*
+ * Now we have the complete path we can work out if
+ * this is actually within this share and adjust
+ * newname accordingly.
+ */
+ DBG_DEBUG("share mismatch (sharepath %s not sharepath %s) "
+ "%s from %s -> %s\n",
+ fsp->conn->connectpath,
+ msg->servicepath,
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname));
+ }
+ out:
+ TALLOC_FREE(msg);
+}
+
+/*
+ * If a main file is opened for delete, all streams need to be checked for
+ * !FILE_SHARE_DELETE. Do this by opening with DELETE_ACCESS.
+ * If that works, delete them all by setting the delete on close and close.
+ */
+
+static NTSTATUS open_streams_for_delete(connection_struct *conn,
+ const struct smb_filename *smb_fname)
+{
+ struct stream_struct *stream_info = NULL;
+ files_struct **streams = NULL;
+ int j;
+ unsigned int i, num_streams = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct smb_filename *pathref = NULL;
+ NTSTATUS status;
+
+ if (smb_fname->fsp == NULL) {
+ struct smb_filename *tmp = NULL;
+ status = synthetic_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &tmp);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)
+ || NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DBG_DEBUG("no streams around\n");
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+ DBG_DEBUG("synthetic_pathref failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ pathref = tmp;
+ } else {
+ pathref = smb_fname;
+ }
+ status = vfs_fstreaminfo(pathref->fsp, talloc_tos(),
+ &num_streams, &stream_info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)
+ || NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DEBUG(10, ("no streams around\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("vfs_fstreaminfo failed: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ DEBUG(10, ("open_streams_for_delete found %d streams\n",
+ num_streams));
+
+ if (num_streams == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ streams = talloc_array(talloc_tos(), files_struct *, num_streams);
+ if (streams == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ for (i=0; i<num_streams; i++) {
+ struct smb_filename *smb_fname_cp;
+
+ if (strequal(stream_info[i].name, "::$DATA")) {
+ streams[i] = NULL;
+ continue;
+ }
+
+ smb_fname_cp = synthetic_smb_fname(talloc_tos(),
+ smb_fname->base_name,
+ stream_info[i].name,
+ NULL,
+ smb_fname->twrp,
+ (smb_fname->flags &
+ ~SMB_FILENAME_POSIX_PATH));
+ if (smb_fname_cp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_cp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Unable to open stream [%s]: %s\n",
+ smb_fname_str_dbg(smb_fname_cp),
+ nt_errstr(status));
+ TALLOC_FREE(smb_fname_cp);
+ break;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname_cp, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ (FILE_SHARE_READ | /* share_access */
+ FILE_SHARE_WRITE | FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &streams[i], /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Could not open stream %s: %s\n",
+ smb_fname_str_dbg(smb_fname_cp),
+ nt_errstr(status)));
+
+ TALLOC_FREE(smb_fname_cp);
+ break;
+ }
+ TALLOC_FREE(smb_fname_cp);
+ }
+
+ /*
+ * don't touch the variable "status" beyond this point :-)
+ */
+
+ for (j = i-1 ; j >= 0; j--) {
+ if (streams[j] == NULL) {
+ continue;
+ }
+
+ DEBUG(10, ("Closing stream # %d, %s\n", j,
+ fsp_str_dbg(streams[j])));
+ close_file_free(NULL, &streams[j], NORMAL_CLOSE);
+ }
+
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*********************************************************************
+ Create a default ACL by inheriting from the parent. If no inheritance
+ from the parent available, don't set anything. This will leave the actual
+ permissions the new file or directory already got from the filesystem
+ as the NT ACL when read.
+*********************************************************************/
+
+static NTSTATUS inherit_new_acl(files_struct *dirfsp, files_struct *fsp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct security_descriptor *parent_desc = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct security_descriptor *psd = NULL;
+ const struct dom_sid *owner_sid = NULL;
+ const struct dom_sid *group_sid = NULL;
+ uint32_t security_info_sent = (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL);
+ struct security_token *token = fsp->conn->session_info->security_token;
+ bool inherit_owner =
+ (lp_inherit_owner(SNUM(fsp->conn)) == INHERIT_OWNER_WINDOWS_AND_UNIX);
+ bool inheritable_components = false;
+ bool try_builtin_administrators = false;
+ const struct dom_sid *BA_U_sid = NULL;
+ const struct dom_sid *BA_G_sid = NULL;
+ bool try_system = false;
+ const struct dom_sid *SY_U_sid = NULL;
+ const struct dom_sid *SY_G_sid = NULL;
+ size_t size = 0;
+ bool ok;
+
+ status = SMB_VFS_FGET_NT_ACL(dirfsp,
+ (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL),
+ frame,
+ &parent_desc);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ inheritable_components = sd_has_inheritable_components(parent_desc,
+ fsp->fsp_flags.is_directory);
+
+ if (!inheritable_components && !inherit_owner) {
+ TALLOC_FREE(frame);
+ /* Nothing to inherit and not setting owner. */
+ return NT_STATUS_OK;
+ }
+
+ /* Create an inherited descriptor from the parent. */
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("inherit_new_acl: parent acl for %s is:\n",
+ fsp_str_dbg(fsp) ));
+ NDR_PRINT_DEBUG(security_descriptor, parent_desc);
+ }
+
+ /* Inherit from parent descriptor if "inherit owner" set. */
+ if (inherit_owner) {
+ owner_sid = parent_desc->owner_sid;
+ group_sid = parent_desc->group_sid;
+ }
+
+ if (owner_sid == NULL) {
+ if (security_token_has_builtin_administrators(token)) {
+ try_builtin_administrators = true;
+ } else if (security_token_is_system(token)) {
+ try_builtin_administrators = true;
+ try_system = true;
+ }
+ }
+
+ if (group_sid == NULL &&
+ token->num_sids == PRIMARY_GROUP_SID_INDEX)
+ {
+ if (security_token_is_system(token)) {
+ try_builtin_administrators = true;
+ try_system = true;
+ }
+ }
+
+ if (try_builtin_administrators) {
+ struct unixid ids = { .id = 0 };
+
+ ok = sids_to_unixids(&global_sid_Builtin_Administrators, 1, &ids);
+ if (ok) {
+ switch (ids.type) {
+ case ID_TYPE_BOTH:
+ BA_U_sid = &global_sid_Builtin_Administrators;
+ BA_G_sid = &global_sid_Builtin_Administrators;
+ break;
+ case ID_TYPE_UID:
+ BA_U_sid = &global_sid_Builtin_Administrators;
+ break;
+ case ID_TYPE_GID:
+ BA_G_sid = &global_sid_Builtin_Administrators;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (try_system) {
+ struct unixid ids = { .id = 0 };
+
+ ok = sids_to_unixids(&global_sid_System, 1, &ids);
+ if (ok) {
+ switch (ids.type) {
+ case ID_TYPE_BOTH:
+ SY_U_sid = &global_sid_System;
+ SY_G_sid = &global_sid_System;
+ break;
+ case ID_TYPE_UID:
+ SY_U_sid = &global_sid_System;
+ break;
+ case ID_TYPE_GID:
+ SY_G_sid = &global_sid_System;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (owner_sid == NULL) {
+ owner_sid = BA_U_sid;
+ }
+
+ if (owner_sid == NULL) {
+ owner_sid = SY_U_sid;
+ }
+
+ if (group_sid == NULL) {
+ group_sid = SY_G_sid;
+ }
+
+ if (try_system && group_sid == NULL) {
+ group_sid = BA_G_sid;
+ }
+
+ if (owner_sid == NULL) {
+ owner_sid = &token->sids[PRIMARY_USER_SID_INDEX];
+ }
+ if (group_sid == NULL) {
+ if (token->num_sids == PRIMARY_GROUP_SID_INDEX) {
+ group_sid = &token->sids[PRIMARY_USER_SID_INDEX];
+ } else {
+ group_sid = &token->sids[PRIMARY_GROUP_SID_INDEX];
+ }
+ }
+
+ status = se_create_child_secdesc(frame,
+ &psd,
+ &size,
+ parent_desc,
+ owner_sid,
+ group_sid,
+ fsp->fsp_flags.is_directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /* If inheritable_components == false,
+ se_create_child_secdesc()
+ creates a security descriptor with a NULL dacl
+ entry, but with SEC_DESC_DACL_PRESENT. We need
+ to remove that flag. */
+
+ if (!inheritable_components) {
+ security_info_sent &= ~SECINFO_DACL;
+ psd->type &= ~SEC_DESC_DACL_PRESENT;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("inherit_new_acl: child acl for %s is:\n",
+ fsp_str_dbg(fsp) ));
+ NDR_PRINT_DEBUG(security_descriptor, psd);
+ }
+
+ if (inherit_owner) {
+ /* We need to be root to force this. */
+ become_root();
+ }
+ status = SMB_VFS_FSET_NT_ACL(metadata_fsp(fsp),
+ security_info_sent,
+ psd);
+ if (inherit_owner) {
+ unbecome_root();
+ }
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * If we already have a lease, it must match the new file id. [MS-SMB2]
+ * 3.3.5.9.8 speaks about INVALID_PARAMETER if an already used lease key is
+ * used for a different file name.
+ */
+
+struct lease_match_state {
+ /* Input parameters. */
+ TALLOC_CTX *mem_ctx;
+ const char *servicepath;
+ const struct smb_filename *fname;
+ bool file_existed;
+ struct file_id id;
+ /* Return parameters. */
+ uint32_t num_file_ids;
+ struct file_id *ids;
+ NTSTATUS match_status;
+};
+
+/*************************************************************
+ File doesn't exist but this lease key+guid is already in use.
+
+ This is only allowable in the dynamic share case where the
+ service path must be different.
+
+ There is a small race condition here in the multi-connection
+ case where a client sends two create calls on different connections,
+ where the file doesn't exist and one smbd creates the leases_db
+ entry first, but this will get fixed by the multichannel cleanup
+ when all identical client_guids get handled by a single smbd.
+**************************************************************/
+
+static void lease_match_parser_new_file(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ struct lease_match_state *state)
+{
+ uint32_t i;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+ if (strequal(state->servicepath, f->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ return;
+ }
+ }
+
+ /* Dynamic share case. Break leases on all other files. */
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
+ }
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
+}
+
+static void lease_match_parser(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data)
+{
+ struct lease_match_state *state =
+ (struct lease_match_state *)private_data;
+ uint32_t i;
+
+ if (!state->file_existed) {
+ /*
+ * Deal with name mismatch or
+ * possible dynamic share case separately
+ * to make code clearer.
+ */
+ lease_match_parser_new_file(num_files,
+ files,
+ state);
+ return;
+ }
+
+ /* File existed. */
+ state->match_status = NT_STATUS_OK;
+
+ for (i = 0; i < num_files; i++) {
+ const struct leases_db_file *f = &files[i];
+
+ /* Everything should be the same. */
+ if (!file_id_equal(&state->id, &f->id)) {
+ /*
+ * The client asked for a lease on a
+ * file that doesn't match the file_id
+ * in the database.
+ *
+ * Maybe this is a dynamic share, i.e.
+ * a share where the servicepath is
+ * different for different users (e.g.
+ * the [HOMES] share.
+ *
+ * If the servicepath is different, but the requested
+ * file name + stream name is the same then this is
+ * a dynamic share, the client is using the same share
+ * name and doesn't know that the underlying servicepath
+ * is different. It was expecting a lease on the
+ * same file. Return NT_STATUS_OPLOCK_NOT_GRANTED
+ * to break leases
+ *
+ * Otherwise the client has messed up, or is
+ * testing our error codes, so return
+ * NT_STATUS_INVALID_PARAMETER.
+ */
+ if (!strequal(f->servicepath, state->servicepath) &&
+ strequal(f->base_name, state->fname->base_name) &&
+ strequal(f->stream_name, state->fname->stream_name))
+ {
+ /*
+ * Name is the same but servicepath is
+ * different, dynamic share. Break leases.
+ */
+ state->match_status =
+ NT_STATUS_OPLOCK_NOT_GRANTED;
+ } else {
+ state->match_status =
+ NT_STATUS_INVALID_PARAMETER;
+ }
+ break;
+ }
+ if (!strequal(f->servicepath, state->servicepath)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->base_name, state->fname->base_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ if (!strequal(f->stream_name, state->fname->stream_name)) {
+ state->match_status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(state->match_status)) {
+ /*
+ * Common case - just opening another handle on a
+ * file on a non-dynamic share.
+ */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->match_status, NT_STATUS_INVALID_PARAMETER)) {
+ /* Mismatched path. Error back to client. */
+ return;
+ }
+
+ /*
+ * File id mismatch. Dynamic share case NT_STATUS_OPLOCK_NOT_GRANTED.
+ * Don't allow leases.
+ */
+
+ state->match_status = leases_db_copy_file_ids(state->mem_ctx,
+ num_files,
+ files,
+ &state->ids);
+ if (!NT_STATUS_IS_OK(state->match_status)) {
+ return;
+ }
+
+ state->num_file_ids = num_files;
+ state->match_status = NT_STATUS_OPLOCK_NOT_GRANTED;
+ return;
+}
+
+struct lease_match_break_state {
+ struct messaging_context *msg_ctx;
+ const struct smb2_lease_key *lease_key;
+ struct file_id id;
+
+ bool found_lease;
+ uint16_t version;
+ uint16_t epoch;
+};
+
+static bool lease_match_break_fn(
+ struct share_mode_entry *e,
+ void *private_data)
+{
+ struct lease_match_break_state *state = private_data;
+ bool stale, equal;
+ uint32_t e_lease_type = SMB2_LEASE_NONE;
+ NTSTATUS status;
+
+ stale = share_entry_stale_pid(e);
+ if (stale) {
+ return false;
+ }
+
+ equal = smb2_lease_key_equal(&e->lease_key, state->lease_key);
+ if (!equal) {
+ return false;
+ }
+
+ status = leases_db_get(
+ &e->client_guid,
+ &e->lease_key,
+ &state->id,
+ &e_lease_type, /* current_state */
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ &state->version, /* lease_version */
+ &state->epoch); /* epoch */
+ if (NT_STATUS_IS_OK(status)) {
+ state->found_lease = true;
+ } else {
+ DBG_WARNING("Could not find version/epoch: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (e_lease_type == SMB2_LEASE_NONE) {
+ return false;
+ }
+ send_break_message(state->msg_ctx, &state->id, e, SMB2_LEASE_NONE);
+
+ /*
+ * Windows 7 and 8 lease clients are broken in that they will
+ * not respond to lease break requests whilst waiting for an
+ * outstanding open request on that lease handle on the same
+ * TCP connection, due to holding an internal inode lock.
+ *
+ * This means we can't reschedule ourselves here, but must
+ * return from the create.
+ *
+ * Work around:
+ *
+ * Send the breaks and then return SMB2_LEASE_NONE in the
+ * lease handle to cause them to acknowledge the lease
+ * break. Consultation with Microsoft engineering confirmed
+ * this approach is safe.
+ */
+
+ return false;
+}
+
+static void lease_match_fid_fn(struct share_mode_lock *lck,
+ void *private_data)
+{
+ bool ok;
+
+ ok = share_mode_forall_leases(lck, lease_match_break_fn, private_data);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_leases failed\n");
+ }
+}
+
+static NTSTATUS lease_match(connection_struct *conn,
+ struct smb_request *req,
+ const struct smb2_lease_key *lease_key,
+ const char *servicepath,
+ const struct smb_filename *fname,
+ uint16_t *p_version,
+ uint16_t *p_epoch)
+{
+ struct smbd_server_connection *sconn = req->sconn;
+ TALLOC_CTX *tos = talloc_tos();
+ struct lease_match_state state = {
+ .mem_ctx = tos,
+ .servicepath = servicepath,
+ .fname = fname,
+ .match_status = NT_STATUS_OK
+ };
+ uint32_t i;
+ NTSTATUS status;
+
+ state.file_existed = VALID_STAT(fname->st);
+ if (state.file_existed) {
+ state.id = vfs_file_id_from_sbuf(conn, &fname->st);
+ }
+
+ status = leases_db_parse(&sconn->client->global->client_guid,
+ lease_key, lease_match_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Not found or error means okay: We can make the lease pass
+ */
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(state.match_status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ /*
+ * Anything but NT_STATUS_OPLOCK_NOT_GRANTED, let the caller
+ * deal with it.
+ */
+ return state.match_status;
+ }
+
+ /* We have to break all existing leases. */
+ for (i = 0; i < state.num_file_ids; i++) {
+ struct lease_match_break_state break_state = {
+ .msg_ctx = conn->sconn->msg_ctx,
+ .lease_key = lease_key,
+ };
+
+ if (file_id_equal(&state.ids[i], &state.id)) {
+ /* Don't need to break our own file. */
+ continue;
+ }
+
+ break_state.id = state.ids[i];
+
+ status = share_mode_do_locked_vfs_denied(break_state.id,
+ lease_match_fid_fn,
+ &break_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Race condition - file already closed. */
+ continue;
+ }
+
+ if (break_state.found_lease) {
+ *p_version = break_state.version;
+ *p_epoch = break_state.epoch;
+ }
+ }
+ /*
+ * Ensure we don't grant anything more so we
+ * never upgrade.
+ */
+ return NT_STATUS_OPLOCK_NOT_GRANTED;
+}
+
+/*
+ * Wrapper around open_file_ntcreate and open_directory
+ */
+
+static NTSTATUS create_file_unixpath(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+
+ files_struct **result,
+ int *pinfo)
+{
+ struct smb2_lease none_lease;
+ int info = FILE_WAS_OPENED;
+ files_struct *base_fsp = NULL;
+ files_struct *fsp = NULL;
+ bool free_fsp_on_error = false;
+ NTSTATUS status;
+ int ret;
+ struct smb_filename *parent_dir_fname = NULL;
+ struct smb_filename *smb_fname_atname = NULL;
+
+ DBG_DEBUG("access_mask = 0x%"PRIx32" "
+ "file_attributes = 0x%"PRIx32" "
+ "share_access = 0x%"PRIx32" "
+ "create_disposition = 0x%"PRIx32" "
+ "create_options = 0x%"PRIx32" "
+ "oplock_request = 0x%"PRIx32" "
+ "private_flags = 0x%"PRIx32" "
+ "ea_list = %p, "
+ "sd = %p, "
+ "fname = %s\n",
+ access_mask,
+ file_attributes,
+ share_access,
+ create_disposition,
+ create_options,
+ oplock_request,
+ private_flags,
+ ea_list,
+ sd,
+ smb_fname_str_dbg(smb_fname));
+
+ if (create_options & FILE_OPEN_BY_FILE_ID) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ goto fail;
+ }
+
+ if (create_options & NTCREATEX_OPTIONS_INVALID_PARAM_MASK) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (!(create_options & FILE_OPEN_REPARSE_POINT) &&
+ (smb_fname->fsp != NULL) && /* new files don't have an fsp */
+ VALID_STAT(smb_fname->fsp->fsp_name->st))
+ {
+ mode_t type = (smb_fname->fsp->fsp_name->st.st_ex_mode &
+ S_IFMT);
+
+ switch (type) {
+ case S_IFREG:
+ FALL_THROUGH;
+ case S_IFDIR:
+ break;
+ case S_IFLNK:
+ /*
+ * We should never get this far with a symlink
+ * "as such". Report as not existing.
+ */
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto fail;
+ default:
+ status = NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED;
+ goto fail;
+ }
+ }
+
+ if (req == NULL) {
+ oplock_request |= INTERNAL_OPEN_ONLY;
+ }
+
+ if (lease != NULL) {
+ uint16_t epoch = lease->lease_epoch;
+ uint16_t version = lease->lease_version;
+
+ if (req == NULL) {
+ DBG_WARNING("Got lease on internal open\n");
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ status = lease_match(conn,
+ req,
+ &lease->lease_key,
+ conn->connectpath,
+ smb_fname,
+ &version,
+ &epoch);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_NOT_GRANTED)) {
+ /* Dynamic share file. No leases and update epoch... */
+ none_lease = *lease;
+ none_lease.lease_state = SMB2_LEASE_NONE;
+ none_lease.lease_epoch = epoch;
+ none_lease.lease_version = version;
+ lease = &none_lease;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && (access_mask & DELETE_ACCESS)
+ && !is_named_stream(smb_fname)) {
+ /*
+ * We can't open a file with DELETE access if any of the
+ * streams is open without FILE_SHARE_DELETE
+ */
+ status = open_streams_for_delete(conn, smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (access_mask & SEC_FLAG_SYSTEM_SECURITY) {
+ bool ok;
+
+ ok = security_token_has_privilege(get_current_nttok(conn),
+ SEC_PRIV_SECURITY);
+ if (!ok) {
+ DBG_DEBUG("open on %s failed - "
+ "SEC_FLAG_SYSTEM_SECURITY denied.\n",
+ smb_fname_str_dbg(smb_fname));
+ status = NT_STATUS_PRIVILEGE_NOT_HELD;
+ goto fail;
+ }
+
+ if (conn->sconn->using_smb2 &&
+ (access_mask == SEC_FLAG_SYSTEM_SECURITY))
+ {
+ /*
+ * No other bits set. Windows SMB2 refuses this.
+ * See smbtorture3 SMB2-SACL test.
+ *
+ * Note this is an SMB2-only behavior,
+ * smbtorture3 SMB1-SYSTEM-SECURITY already tests
+ * that SMB1 allows this.
+ */
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ }
+ }
+
+ /*
+ * Files or directories can't be opened DELETE_ON_CLOSE without
+ * delete access.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13358
+ */
+ if ((create_options & FILE_DELETE_ON_CLOSE) &&
+ ((access_mask & DELETE_ACCESS) == 0)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && is_named_stream(smb_fname))
+ {
+ uint32_t base_create_disposition;
+ struct smb_filename *smb_fname_base = NULL;
+ uint32_t base_privflags;
+
+ if (create_options & FILE_DIRECTORY_FILE) {
+ DBG_DEBUG("Can't open a stream as directory\n");
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto fail;
+ }
+
+ switch (create_disposition) {
+ case FILE_OPEN:
+ base_create_disposition = FILE_OPEN;
+ break;
+ default:
+ base_create_disposition = FILE_OPEN_IF;
+ break;
+ }
+
+ smb_fname_base = cp_smb_filename_nostream(
+ talloc_tos(), smb_fname);
+
+ if (smb_fname_base == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ /*
+ * We may be creating the basefile as part of creating the
+ * stream, so it's legal if the basefile doesn't exist at this
+ * point, the create_file_unixpath() below will create it. But
+ * if the basefile exists we want a handle so we can fstat() it.
+ */
+
+ ret = vfs_stat(conn, smb_fname_base);
+ if (ret == -1 && errno != ENOENT) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(smb_fname_base);
+ goto fail;
+ }
+ if (ret == 0) {
+ status = openat_pathref_fsp(conn->cwd_fsp,
+ smb_fname_base);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("open_smb_fname_fsp [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname_base),
+ nt_errstr(status));
+ TALLOC_FREE(smb_fname_base);
+ goto fail;
+ }
+
+ /*
+ * https://bugzilla.samba.org/show_bug.cgi?id=10229
+ * We need to check if the requested access mask
+ * could be used to open the underlying file (if
+ * it existed), as we're passing in zero for the
+ * access mask to the base filename.
+ */
+ status = check_base_file_access(smb_fname_base->fsp,
+ access_mask);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Permission check "
+ "for base %s failed: "
+ "%s\n", smb_fname->base_name,
+ nt_errstr(status)));
+ TALLOC_FREE(smb_fname_base);
+ goto fail;
+ }
+ }
+
+ base_privflags = NTCREATEX_FLAG_STREAM_BASEOPEN;
+
+ /* Open the base file. */
+ status = create_file_unixpath(conn,
+ NULL,
+ dirfsp,
+ smb_fname_base,
+ 0,
+ FILE_SHARE_READ
+ | FILE_SHARE_WRITE
+ | FILE_SHARE_DELETE,
+ base_create_disposition,
+ 0,
+ 0,
+ 0,
+ NULL,
+ 0,
+ base_privflags,
+ NULL,
+ NULL,
+ &base_fsp,
+ NULL);
+ TALLOC_FREE(smb_fname_base);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("create_file_unixpath for base %s failed: "
+ "%s\n", smb_fname->base_name,
+ nt_errstr(status)));
+ goto fail;
+ }
+ }
+
+ if (smb_fname->fsp != NULL) {
+
+ fsp = smb_fname->fsp;
+
+ /*
+ * We're about to use smb_fname->fsp for the fresh open.
+ *
+ * Every fsp passed in via smb_fname->fsp already
+ * holds a fsp->fsp_name. If it is already this
+ * fsp->fsp_name that we got passed in as our input
+ * argument smb_fname, these two are assumed to have
+ * the same lifetime: Every fsp hangs of "conn", and
+ * fsp->fsp_name is its talloc child.
+ */
+
+ if (smb_fname != smb_fname->fsp->fsp_name) {
+ /*
+ * "smb_fname" is temporary in this case, but
+ * the destructor of smb_fname would also tear
+ * down the fsp we're about to use. Unlink
+ * them from each other.
+ */
+ smb_fname_fsp_unlink(smb_fname);
+
+ /*
+ * "fsp" is ours now
+ */
+ free_fsp_on_error = true;
+ }
+
+ status = fsp_bind_smb(fsp, req);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ struct files_struct *tmp_base_fsp = fsp->base_fsp;
+
+ fsp_set_base_fsp(fsp, NULL);
+
+ fd_close(tmp_base_fsp);
+ file_free(NULL, tmp_base_fsp);
+ }
+ } else {
+ /*
+ * No fsp passed in that we can use, create one
+ */
+ status = file_new(req, conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ free_fsp_on_error = true;
+
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ SMB_ASSERT(fsp->fsp_name->fsp != NULL);
+ SMB_ASSERT(fsp->fsp_name->fsp == fsp);
+
+ if (base_fsp) {
+ /*
+ * We're opening the stream element of a
+ * base_fsp we already opened. Set up the
+ * base_fsp pointer.
+ */
+ fsp_set_base_fsp(fsp, base_fsp);
+ }
+
+ if (dirfsp != NULL) {
+ status = SMB_VFS_PARENT_PATHNAME(
+ conn,
+ talloc_tos(),
+ smb_fname,
+ &parent_dir_fname,
+ &smb_fname_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ } else {
+ /*
+ * Get a pathref on the parent. We can re-use this for
+ * multiple calls to check parent ACLs etc. to avoid
+ * pathname calls.
+ */
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_dir_fname,
+ &smb_fname_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ dirfsp = parent_dir_fname->fsp;
+ status = fsp_set_smb_fname(dirfsp, parent_dir_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ /*
+ * If it's a request for a directory open, deal with it separately.
+ */
+
+ if (create_options & FILE_DIRECTORY_FILE) {
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /* Can't open a temp directory. IFS kit test. */
+ if (!(file_attributes & FILE_FLAG_POSIX_SEMANTICS) &&
+ (file_attributes & FILE_ATTRIBUTE_TEMPORARY)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ /*
+ * We will get a create directory here if the Win32
+ * app specified a security descriptor in the
+ * CreateDirectory() call.
+ */
+
+ oplock_request = 0;
+ status = open_directory(conn,
+ req,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ dirfsp->fsp_name,
+ smb_fname_atname,
+ &info,
+ fsp);
+ } else {
+
+ /*
+ * Ordinary file case.
+ */
+
+ if (allocation_size) {
+ fsp->initial_allocation_size = smb_roundup(fsp->conn,
+ allocation_size);
+ }
+
+ status = open_file_ntcreate(conn,
+ req,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ private_flags,
+ dirfsp->fsp_name,
+ smb_fname_atname,
+ &info,
+ fsp);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+
+ /* A stream open never opens a directory */
+
+ if (base_fsp) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto fail;
+ }
+
+ /*
+ * Fail the open if it was explicitly a non-directory
+ * file.
+ */
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto fail;
+ }
+
+ oplock_request = 0;
+ status = open_directory(conn,
+ req,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ dirfsp->fsp_name,
+ smb_fname_atname,
+ &info,
+ fsp);
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ fsp->fsp_flags.is_fsa = true;
+
+ if ((ea_list != NULL) &&
+ ((info == FILE_WAS_CREATED) || (info == FILE_WAS_OVERWRITTEN))) {
+ status = set_ea(conn, fsp, ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (!fsp->fsp_flags.is_directory &&
+ S_ISDIR(fsp->fsp_name->st.st_ex_mode))
+ {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto fail;
+ }
+
+ /* Save the requested allocation size. */
+ if ((info == FILE_WAS_CREATED) || (info == FILE_WAS_OVERWRITTEN)) {
+ if ((allocation_size > (uint64_t)fsp->fsp_name->st.st_ex_size)
+ && !(fsp->fsp_flags.is_directory))
+ {
+ fsp->initial_allocation_size = smb_roundup(
+ fsp->conn, allocation_size);
+ if (vfs_allocate_file_space(
+ fsp, fsp->initial_allocation_size) == -1) {
+ status = NT_STATUS_DISK_FULL;
+ goto fail;
+ }
+ } else {
+ fsp->initial_allocation_size = smb_roundup(
+ fsp->conn, (uint64_t)fsp->fsp_name->st.st_ex_size);
+ }
+ } else {
+ fsp->initial_allocation_size = 0;
+ }
+
+ if ((info == FILE_WAS_CREATED) &&
+ lp_nt_acl_support(SNUM(conn)) &&
+ !fsp_is_alternate_stream(fsp)) {
+ if (sd != NULL) {
+ /*
+ * According to the MS documentation, the only time the security
+ * descriptor is applied to the opened file is iff we *created* the
+ * file; an existing file stays the same.
+ *
+ * Also, it seems (from observation) that you can open the file with
+ * any access mask but you can still write the sd. We need to override
+ * the granted access before we call set_sd
+ * Patch for bug #2242 from Tom Lackemann <cessnatomny@yahoo.com>.
+ */
+
+ uint32_t sec_info_sent;
+ uint32_t saved_access_mask = fsp->access_mask;
+
+ sec_info_sent = get_sec_info(sd);
+
+ fsp->access_mask = FILE_GENERIC_ALL;
+
+ if (sec_info_sent & (SECINFO_OWNER|
+ SECINFO_GROUP|
+ SECINFO_DACL|
+ SECINFO_SACL)) {
+ status = set_sd(fsp, sd, sec_info_sent);
+ }
+
+ fsp->access_mask = saved_access_mask;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ } else if (lp_inherit_acls(SNUM(conn))) {
+ /* Inherit from parent. Errors here are not fatal. */
+ status = inherit_new_acl(dirfsp, fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("inherit_new_acl: failed for %s with %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status) ));
+ }
+ }
+ }
+
+ if ((conn->fs_capabilities & FILE_FILE_COMPRESSION)
+ && (create_options & FILE_NO_COMPRESSION)
+ && (info == FILE_WAS_CREATED)) {
+ status = SMB_VFS_SET_COMPRESSION(conn, fsp, fsp,
+ COMPRESSION_FORMAT_NONE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to disable compression: %s\n",
+ nt_errstr(status)));
+ }
+ }
+
+ DEBUG(10, ("create_file_unixpath: info=%d\n", info));
+
+ *result = fsp;
+ if (pinfo != NULL) {
+ *pinfo = info;
+ }
+
+ smb_fname->st = fsp->fsp_name->st;
+
+ TALLOC_FREE(parent_dir_fname);
+
+ return NT_STATUS_OK;
+
+ fail:
+ DEBUG(10, ("create_file_unixpath: %s\n", nt_errstr(status)));
+
+ if (fsp != NULL) {
+ /*
+ * The close_file below will close
+ * fsp->base_fsp.
+ */
+ base_fsp = NULL;
+ close_file_smb(req, fsp, ERROR_CLOSE);
+ if (free_fsp_on_error) {
+ file_free(req, fsp);
+ fsp = NULL;
+ }
+ }
+ if (base_fsp != NULL) {
+ close_file_free(req, &base_fsp, ERROR_CLOSE);
+ }
+
+ TALLOC_FREE(parent_dir_fname);
+
+ return status;
+}
+
+NTSTATUS create_file_default(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ int info = FILE_WAS_OPENED;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ bool stream_name = false;
+ struct smb2_create_blob *posx = NULL;
+
+ DBG_DEBUG("access_mask = 0x%" PRIu32
+ " file_attributes = 0x%" PRIu32
+ " share_access = 0x%" PRIu32
+ " create_disposition = 0x%" PRIu32
+ " create_options = 0x%" PRIu32
+ " oplock_request = 0x%" PRIu32
+ " private_flags = 0x%" PRIu32
+ " ea_list = %p, sd = %p, fname = %s\n",
+ access_mask,
+ file_attributes,
+ share_access,
+ create_disposition,
+ create_options,
+ oplock_request,
+ private_flags,
+ ea_list,
+ sd,
+ smb_fname_str_dbg(smb_fname));
+
+ if (req != NULL) {
+ /*
+ * Remember the absolute time of the original request
+ * with this mid. We'll use it later to see if this
+ * has timed out.
+ */
+ get_deferred_open_message_state(req, &req->request_time, NULL);
+ }
+
+ /*
+ * Check to see if this is a mac fork of some kind.
+ */
+
+ stream_name = is_ntfs_stream_smb_fname(smb_fname);
+ if (stream_name) {
+ enum FAKE_FILE_TYPE fake_file_type;
+
+ fake_file_type = is_fake_file(smb_fname);
+
+ if (req != NULL && fake_file_type != FAKE_FILE_TYPE_NONE) {
+
+ /*
+ * Here we go! support for changing the disk quotas
+ * --metze
+ *
+ * We need to fake up to open this MAGIC QUOTA file
+ * and return a valid FID.
+ *
+ * w2k close this file directly after opening xp
+ * also tries a QUERY_FILE_INFO on the file and then
+ * close it
+ */
+ status = open_fake_file(req, conn, req->vuid,
+ fake_file_type, smb_fname,
+ access_mask, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ ZERO_STRUCT(smb_fname->st);
+ goto done;
+ }
+
+ if (!(conn->fs_capabilities & FILE_NAMED_STREAMS)) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto fail;
+ }
+ }
+
+ if (is_ntfs_default_stream_smb_fname(smb_fname)) {
+ int ret;
+ /* We have to handle this error here. */
+ if (create_options & FILE_DIRECTORY_FILE) {
+ status = NT_STATUS_NOT_A_DIRECTORY;
+ goto fail;
+ }
+ ret = vfs_stat(conn, smb_fname);
+ if (ret == 0 && VALID_STAT_OF_DIR(smb_fname->st)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto fail;
+ }
+ }
+
+ posx = smb2_create_blob_find(
+ in_context_blobs, SMB2_CREATE_TAG_POSIX);
+ if (posx != NULL) {
+ uint32_t wire_mode_bits = 0;
+ mode_t mode_bits = 0;
+ SMB_STRUCT_STAT sbuf = { 0 };
+ enum perm_type ptype =
+ (create_options & FILE_DIRECTORY_FILE) ?
+ PERM_NEW_DIR : PERM_NEW_FILE;
+
+ if (posx->data.length != 4) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ wire_mode_bits = IVAL(posx->data.data, 0);
+ status = unix_perms_from_wire(
+ conn, &sbuf, wire_mode_bits, ptype, &mode_bits);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ /*
+ * Remove type info from mode, leaving only the
+ * permissions and setuid/gid bits.
+ */
+ mode_bits &= ~S_IFMT;
+
+ file_attributes = (FILE_FLAG_POSIX_SEMANTICS | mode_bits);
+ }
+
+ status = create_file_unixpath(conn,
+ req,
+ dirfsp,
+ smb_fname,
+ access_mask,
+ share_access,
+ create_disposition,
+ create_options,
+ file_attributes,
+ oplock_request,
+ lease,
+ allocation_size,
+ private_flags,
+ sd,
+ ea_list,
+ &fsp,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ done:
+ DEBUG(10, ("create_file: info=%d\n", info));
+
+ *result = fsp;
+ if (pinfo != NULL) {
+ *pinfo = info;
+ }
+ return NT_STATUS_OK;
+
+ fail:
+ DEBUG(10, ("create_file: %s\n", nt_errstr(status)));
+
+ if (fsp != NULL) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ }
+ return status;
+}
diff --git a/source3/smbd/oplock_linux.c b/source3/smbd/oplock_linux.c
new file mode 100644
index 0000000..3ed2001
--- /dev/null
+++ b/source3/smbd/oplock_linux.c
@@ -0,0 +1,243 @@
+/*
+ Unix SMB/CIFS implementation.
+ kernel oplock processing for Linux
+ Copyright (C) Andrew Tridgell 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+
+#ifndef RT_SIGNAL_LEASE
+#define RT_SIGNAL_LEASE (SIGRTMIN+1)
+#endif
+
+/*
+ * Call to set the kernel lease signal handler
+ */
+int linux_set_lease_sighandler(int fd)
+{
+ if (fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
+ DBG_NOTICE("Failed to set signal handler for kernel lease\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ Call SETLEASE. If we get EACCES then we try setting up the right capability and
+ try again.
+ Use the SMB_VFS_LINUX_SETLEASE instead of this call directly.
+****************************************************************************/
+
+int linux_setlease(int fd, int leasetype)
+{
+ int ret;
+ int saved_errno = 0;
+
+ /*
+ * Ensure the lease owner is root to allow
+ * correct delivery of lease-break signals.
+ */
+
+ become_root();
+
+ /* First set the signal handler. */
+ if (linux_set_lease_sighandler(fd) == -1) {
+ saved_errno = errno;
+ ret = -1;
+ goto out;
+ }
+ ret = fcntl(fd, F_SETLEASE, leasetype);
+ if (ret == -1) {
+ saved_errno = errno;
+ }
+
+ out:
+
+ unbecome_root();
+
+ if (ret == -1) {
+ errno = saved_errno;
+ }
+ return ret;
+}
+
+/****************************************************************************
+ * Deal with the Linux kernel <--> smbd
+ * oplock break protocol.
+****************************************************************************/
+
+static void linux_oplock_signal_handler(struct tevent_context *ev_ctx,
+ struct tevent_signal *se,
+ int signum, int count,
+ void *_info, void *private_data)
+{
+ struct kernel_oplocks *ctx =
+ talloc_get_type_abort(private_data,
+ struct kernel_oplocks);
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(ctx->private_data,
+ struct smbd_server_connection);
+ siginfo_t *info = (siginfo_t *)_info;
+ int fd = info->si_fd;
+ files_struct *fsp;
+
+ fsp = file_find_fd(sconn, fd);
+ if (fsp == NULL) {
+ DBG_ERR("linux_oplock_signal_handler: failed to find fsp for file fd=%d (file was closed ?)\n", fd );
+ return;
+ }
+ break_kernel_oplock(sconn->msg_ctx, fsp);
+}
+
+/****************************************************************************
+ Attempt to set an kernel oplock on a file.
+****************************************************************************/
+
+static bool linux_set_kernel_oplock(struct kernel_oplocks *ctx,
+ files_struct *fsp, int oplock_type)
+{
+ struct file_id_buf idbuf;
+
+ if ( SMB_VFS_LINUX_SETLEASE(fsp, F_WRLCK) == -1) {
+ DBG_NOTICE("Refused oplock on file %s, "
+ "fd = %d, file_id = %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ fsp_get_io_fd(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ strerror(errno));
+ return False;
+ }
+
+ DBG_NOTICE("got kernel oplock on file %s, "
+ "file_id = %s gen_id = %"PRIu64"\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fh_get_gen_id(fsp->fh));
+
+ return True;
+}
+
+/****************************************************************************
+ Release a kernel oplock on a file.
+****************************************************************************/
+
+static void linux_release_kernel_oplock(struct kernel_oplocks *ctx,
+ files_struct *fsp, int oplock_type)
+{
+ struct file_id_buf idbuf;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ /*
+ * Check and print out the current kernel
+ * oplock state of this file.
+ */
+ int state = fcntl(fsp_get_io_fd(fsp), F_GETLEASE, 0);
+ dbgtext("linux_release_kernel_oplock: file %s, file_id = %s "
+ "gen_id = %"PRIu64" has kernel oplock state "
+ "of %x.\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fh_get_gen_id(fsp->fh),
+ state);
+ }
+
+ /*
+ * Remove the kernel oplock on this file.
+ */
+ if ( SMB_VFS_LINUX_SETLEASE(fsp, F_UNLCK) == -1) {
+ if (DEBUGLVL(DBGLVL_ERR)) {
+ dbgtext("linux_release_kernel_oplock: Error when "
+ "removing kernel oplock on file " );
+ dbgtext("%s, file_id = %s, gen_id = %"PRIu64". "
+ "Error was %s\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fh_get_gen_id(fsp->fh),
+ strerror(errno));
+ }
+ }
+}
+
+/****************************************************************************
+ See if the kernel supports oplocks.
+****************************************************************************/
+
+static bool linux_oplocks_available(void)
+{
+ int fd, ret;
+ fd = open("/dev/null", O_RDONLY);
+ if (fd == -1)
+ return False; /* uggh! */
+ ret = fcntl(fd, F_GETLEASE, 0);
+ close(fd);
+ return ret == F_UNLCK;
+}
+
+/****************************************************************************
+ Setup kernel oplocks.
+****************************************************************************/
+
+static const struct kernel_oplocks_ops linux_koplocks = {
+ .set_oplock = linux_set_kernel_oplock,
+ .release_oplock = linux_release_kernel_oplock,
+};
+
+struct kernel_oplocks *linux_init_kernel_oplocks(struct smbd_server_connection *sconn)
+{
+ struct kernel_oplocks *ctx;
+ struct tevent_signal *se;
+
+ if (!linux_oplocks_available()) {
+ DBG_NOTICE("Linux kernel oplocks not available\n");
+ return NULL;
+ }
+
+ ctx = talloc_zero(sconn, struct kernel_oplocks);
+ if (!ctx) {
+ DBG_ERR("Linux Kernel oplocks talloc_Zero failed\n");
+ return NULL;
+ }
+
+ ctx->ops = &linux_koplocks;
+ ctx->private_data = sconn;
+
+ se = tevent_add_signal(sconn->ev_ctx,
+ ctx,
+ RT_SIGNAL_LEASE, SA_SIGINFO,
+ linux_oplock_signal_handler,
+ ctx);
+ if (!se) {
+ DBG_ERR("Failed to setup RT_SIGNAL_LEASE handler\n");
+ TALLOC_FREE(ctx);
+ return NULL;
+ }
+
+ DBG_NOTICE("Linux kernel oplocks enabled\n");
+
+ return ctx;
+}
+#else
+ void oplock_linux_dummy(void);
+
+ void oplock_linux_dummy(void) {}
+#endif /* HAVE_KERNEL_OPLOCKS_LINUX */
diff --git a/source3/smbd/password.c b/source3/smbd/password.c
new file mode 100644
index 0000000..9709a51
--- /dev/null
+++ b/source3/smbd/password.c
@@ -0,0 +1,91 @@
+/*
+ Unix SMB/CIFS implementation.
+ Password and authentication handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "auth.h"
+#include "../libcli/security/security.h"
+
+/****************************************************************************
+ Invalidate a uid.
+****************************************************************************/
+
+void invalidate_vuid(struct smbd_server_connection *sconn, uint64_t vuid)
+{
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ status = get_valid_smbXsrv_session(sconn->client, vuid, &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ session_yield(session);
+
+ SMB_ASSERT(sconn->num_users > 0);
+ sconn->num_users--;
+
+ /* clear the vuid from the 'cache' on each connection, and
+ from the vuid 'owner' of connections */
+ conn_clear_vuid_caches(sconn, vuid);
+}
+
+int register_homes_share(const char *username)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int result;
+ struct passwd *pwd;
+
+ result = lp_servicenumber(username);
+ if (result != -1) {
+ DEBUG(3, ("Using static (or previously created) service for "
+ "user '%s'; path = '%s'\n", username,
+ lp_path(talloc_tos(), lp_sub, result)));
+ return result;
+ }
+
+ pwd = Get_Pwnam_alloc(talloc_tos(), username);
+
+ if ((pwd == NULL) || (pwd->pw_dir[0] == '\0')) {
+ DEBUG(3, ("No home directory defined for user '%s'\n",
+ username));
+ TALLOC_FREE(pwd);
+ return -1;
+ }
+
+ if (strequal(pwd->pw_dir, "/")) {
+ DBG_NOTICE("Invalid home directory defined for user '%s'\n",
+ username);
+ TALLOC_FREE(pwd);
+ return -1;
+ }
+
+ DEBUG(3, ("Adding homes service for user '%s' using home directory: "
+ "'%s'\n", username, pwd->pw_dir));
+
+ result = add_home_service(username, username, pwd->pw_dir);
+
+ TALLOC_FREE(pwd);
+ return result;
+}
diff --git a/source3/smbd/posix_acls.c b/source3/smbd/posix_acls.c
new file mode 100644
index 0000000..d275bdb
--- /dev/null
+++ b/source3/smbd/posix_acls.c
@@ -0,0 +1,4862 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT Security Descriptor / Unix permission conversion.
+ Copyright (C) Jeremy Allison 1994-2009.
+ Copyright (C) Andreas Gruenbacher 2002.
+ Copyright (C) Simo Sorce <idra@samba.org> 2009.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+#include "../libcli/security/security.h"
+#include "trans2.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../librpc/gen_ndr/idmap.h"
+#include "../librpc/gen_ndr/ndr_smb_acl.h"
+#include "lib/param/loadparm.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+/****************************************************************************
+ Data structures representing the internal ACE format.
+****************************************************************************/
+
+enum ace_owner {UID_ACE, GID_ACE, WORLD_ACE};
+enum ace_attribute {ALLOW_ACE, DENY_ACE}; /* Used for incoming NT ACLS. */
+
+typedef struct canon_ace {
+ struct canon_ace *next, *prev;
+ SMB_ACL_TAG_T type;
+ mode_t perms; /* Only use S_I(R|W|X)USR mode bits here. */
+ struct dom_sid trustee;
+ enum ace_owner owner_type;
+ enum ace_attribute attr;
+ struct unixid unix_ug;
+ uint8_t ace_flags; /* From windows ACE entry. */
+} canon_ace;
+
+#define ALL_ACE_PERMS (S_IRUSR|S_IWUSR|S_IXUSR)
+
+/*
+ * EA format of user.SAMBA_PAI (Samba_Posix_Acl_Interitance)
+ * attribute on disk - version 1.
+ * All values are little endian.
+ *
+ * | 1 | 1 | 2 | 2 | ....
+ * +------+------+-------------+---------------------+-------------+--------------------+
+ * | vers | flag | num_entries | num_default_entries | ..entries.. | default_entries... |
+ * +------+------+-------------+---------------------+-------------+--------------------+
+ *
+ * Entry format is :
+ *
+ * | 1 | 4 |
+ * +------+-------------------+
+ * | value| uid/gid or world |
+ * | type | value |
+ * +------+-------------------+
+ *
+ * Version 2 format. Stores extra Windows metadata about an ACL.
+ *
+ * | 1 | 2 | 2 | 2 | ....
+ * +------+----------+-------------+---------------------+-------------+--------------------+
+ * | vers | ace | num_entries | num_default_entries | ..entries.. | default_entries... |
+ * | 2 | type | | | | |
+ * +------+----------+-------------+---------------------+-------------+--------------------+
+ *
+ * Entry format is :
+ *
+ * | 1 | 1 | 4 |
+ * +------+------+-------------------+
+ * | ace | value| uid/gid or world |
+ * | flag | type | value |
+ * +------+-------------------+------+
+ *
+ */
+
+#define PAI_VERSION_OFFSET 0
+
+#define PAI_V1_FLAG_OFFSET 1
+#define PAI_V1_NUM_ENTRIES_OFFSET 2
+#define PAI_V1_NUM_DEFAULT_ENTRIES_OFFSET 4
+#define PAI_V1_ENTRIES_BASE 6
+#define PAI_V1_ACL_FLAG_PROTECTED 0x1
+#define PAI_V1_ENTRY_LENGTH 5
+
+#define PAI_V1_VERSION 1
+
+#define PAI_V2_TYPE_OFFSET 1
+#define PAI_V2_NUM_ENTRIES_OFFSET 3
+#define PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET 5
+#define PAI_V2_ENTRIES_BASE 7
+#define PAI_V2_ENTRY_LENGTH 6
+
+#define PAI_V2_VERSION 2
+
+/*
+ * In memory format of user.SAMBA_PAI attribute.
+ */
+
+struct pai_entry {
+ struct pai_entry *next, *prev;
+ uint8_t ace_flags;
+ enum ace_owner owner_type;
+ struct unixid unix_ug;
+};
+
+struct pai_val {
+ uint16_t sd_type;
+ unsigned int num_entries;
+ struct pai_entry *entry_list;
+ unsigned int num_def_entries;
+ struct pai_entry *def_entry_list;
+};
+
+/************************************************************************
+ Return a uint32_t of the pai_entry principal.
+************************************************************************/
+
+static uint32_t get_pai_entry_val(struct pai_entry *paie)
+{
+ switch (paie->owner_type) {
+ case UID_ACE:
+ DEBUG(10,("get_pai_entry_val: uid = %u\n", (unsigned int)paie->unix_ug.id ));
+ return (uint32_t)paie->unix_ug.id;
+ case GID_ACE:
+ DEBUG(10,("get_pai_entry_val: gid = %u\n", (unsigned int)paie->unix_ug.id ));
+ return (uint32_t)paie->unix_ug.id;
+ case WORLD_ACE:
+ default:
+ DEBUG(10,("get_pai_entry_val: world ace\n"));
+ return (uint32_t)-1;
+ }
+}
+
+/************************************************************************
+ Return a uint32_t of the entry principal.
+************************************************************************/
+
+static uint32_t get_entry_val(canon_ace *ace_entry)
+{
+ switch (ace_entry->owner_type) {
+ case UID_ACE:
+ DEBUG(10,("get_entry_val: uid = %u\n", (unsigned int)ace_entry->unix_ug.id ));
+ return (uint32_t)ace_entry->unix_ug.id;
+ case GID_ACE:
+ DEBUG(10,("get_entry_val: gid = %u\n", (unsigned int)ace_entry->unix_ug.id ));
+ return (uint32_t)ace_entry->unix_ug.id;
+ case WORLD_ACE:
+ default:
+ DEBUG(10,("get_entry_val: world ace\n"));
+ return (uint32_t)-1;
+ }
+}
+
+/************************************************************************
+ Create the on-disk format (always v2 now). Caller must free.
+************************************************************************/
+
+static char *create_pai_buf_v2(canon_ace *file_ace_list,
+ canon_ace *dir_ace_list,
+ uint16_t sd_type,
+ size_t *store_size)
+{
+ char *pai_buf = NULL;
+ canon_ace *ace_list = NULL;
+ char *entry_offset = NULL;
+ unsigned int num_entries = 0;
+ unsigned int num_def_entries = 0;
+ unsigned int i;
+
+ for (ace_list = file_ace_list; ace_list; ace_list = ace_list->next) {
+ num_entries++;
+ }
+
+ for (ace_list = dir_ace_list; ace_list; ace_list = ace_list->next) {
+ num_def_entries++;
+ }
+
+ DEBUG(10,("create_pai_buf_v2: num_entries = %u, num_def_entries = %u\n", num_entries, num_def_entries ));
+
+ *store_size = PAI_V2_ENTRIES_BASE +
+ ((num_entries + num_def_entries)*PAI_V2_ENTRY_LENGTH);
+
+ pai_buf = talloc_array(talloc_tos(), char, *store_size);
+ if (!pai_buf) {
+ return NULL;
+ }
+
+ /* Set up the header. */
+ memset(pai_buf, '\0', PAI_V2_ENTRIES_BASE);
+ SCVAL(pai_buf,PAI_VERSION_OFFSET,PAI_V2_VERSION);
+ SSVAL(pai_buf,PAI_V2_TYPE_OFFSET, sd_type);
+ SSVAL(pai_buf,PAI_V2_NUM_ENTRIES_OFFSET,num_entries);
+ SSVAL(pai_buf,PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET,num_def_entries);
+
+ DEBUG(10,("create_pai_buf_v2: sd_type = 0x%x\n",
+ (unsigned int)sd_type ));
+
+ entry_offset = pai_buf + PAI_V2_ENTRIES_BASE;
+
+ i = 0;
+ for (ace_list = file_ace_list; ace_list; ace_list = ace_list->next) {
+ uint8_t type_val = (uint8_t)ace_list->owner_type;
+ uint32_t entry_val = get_entry_val(ace_list);
+
+ SCVAL(entry_offset,0,ace_list->ace_flags);
+ SCVAL(entry_offset,1,type_val);
+ SIVAL(entry_offset,2,entry_val);
+ DEBUG(10,("create_pai_buf_v2: entry %u [0x%x] [0x%x] [0x%x]\n",
+ i,
+ (unsigned int)ace_list->ace_flags,
+ (unsigned int)type_val,
+ (unsigned int)entry_val ));
+ i++;
+ entry_offset += PAI_V2_ENTRY_LENGTH;
+ }
+
+ for (ace_list = dir_ace_list; ace_list; ace_list = ace_list->next) {
+ uint8_t type_val = (uint8_t)ace_list->owner_type;
+ uint32_t entry_val = get_entry_val(ace_list);
+
+ SCVAL(entry_offset,0,ace_list->ace_flags);
+ SCVAL(entry_offset,1,type_val);
+ SIVAL(entry_offset,2,entry_val);
+ DEBUG(10,("create_pai_buf_v2: entry %u [0x%x] [0x%x] [0x%x]\n",
+ i,
+ (unsigned int)ace_list->ace_flags,
+ (unsigned int)type_val,
+ (unsigned int)entry_val ));
+ i++;
+ entry_offset += PAI_V2_ENTRY_LENGTH;
+ }
+
+ return pai_buf;
+}
+
+/************************************************************************
+ Store the user.SAMBA_PAI attribute on disk.
+************************************************************************/
+
+static void store_inheritance_attributes(files_struct *fsp,
+ canon_ace *file_ace_list,
+ canon_ace *dir_ace_list,
+ uint16_t sd_type)
+{
+ int ret;
+ size_t store_size;
+ char *pai_buf;
+
+ if (!lp_map_acl_inherit(SNUM(fsp->conn))) {
+ return;
+ }
+
+ pai_buf = create_pai_buf_v2(file_ace_list, dir_ace_list,
+ sd_type, &store_size);
+
+ ret = SMB_VFS_FSETXATTR(fsp, SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, store_size, 0);
+
+ TALLOC_FREE(pai_buf);
+
+ DEBUG(10,("store_inheritance_attribute: type 0x%x for file %s\n",
+ (unsigned int)sd_type,
+ fsp_str_dbg(fsp)));
+
+ if (ret == -1 && !no_acl_syscall_error(errno)) {
+ DEBUG(1,("store_inheritance_attribute: Error %s\n", strerror(errno) ));
+ }
+}
+
+/************************************************************************
+ Delete the in memory inheritance info.
+************************************************************************/
+
+static void free_inherited_info(struct pai_val *pal)
+{
+ if (pal) {
+ struct pai_entry *paie, *paie_next;
+ for (paie = pal->entry_list; paie; paie = paie_next) {
+ paie_next = paie->next;
+ TALLOC_FREE(paie);
+ }
+ for (paie = pal->def_entry_list; paie; paie = paie_next) {
+ paie_next = paie->next;
+ TALLOC_FREE(paie);
+ }
+ TALLOC_FREE(pal);
+ }
+}
+
+/************************************************************************
+ Get any stored ACE flags.
+************************************************************************/
+
+static uint16_t get_pai_flags(struct pai_val *pal, canon_ace *ace_entry, bool default_ace)
+{
+ struct pai_entry *paie;
+
+ if (!pal) {
+ return 0;
+ }
+
+ /* If the entry exists it is inherited. */
+ for (paie = (default_ace ? pal->def_entry_list : pal->entry_list); paie; paie = paie->next) {
+ if (ace_entry->owner_type == paie->owner_type &&
+ get_entry_val(ace_entry) == get_pai_entry_val(paie))
+ return paie->ace_flags;
+ }
+ return 0;
+}
+
+/************************************************************************
+ Ensure an attribute just read is valid - v1.
+************************************************************************/
+
+static bool check_pai_ok_v1(const char *pai_buf, size_t pai_buf_data_size)
+{
+ uint16_t num_entries;
+ uint16_t num_def_entries;
+
+ if (pai_buf_data_size < PAI_V1_ENTRIES_BASE) {
+ /* Corrupted - too small. */
+ return false;
+ }
+
+ if (CVAL(pai_buf,PAI_VERSION_OFFSET) != PAI_V1_VERSION) {
+ return false;
+ }
+
+ num_entries = SVAL(pai_buf,PAI_V1_NUM_ENTRIES_OFFSET);
+ num_def_entries = SVAL(pai_buf,PAI_V1_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ /* Check the entry lists match. */
+ /* Each entry is 5 bytes (type plus 4 bytes of uid or gid). */
+
+ if (((num_entries + num_def_entries)*PAI_V1_ENTRY_LENGTH) +
+ PAI_V1_ENTRIES_BASE != pai_buf_data_size) {
+ return false;
+ }
+
+ return true;
+}
+
+/************************************************************************
+ Ensure an attribute just read is valid - v2.
+************************************************************************/
+
+static bool check_pai_ok_v2(const char *pai_buf, size_t pai_buf_data_size)
+{
+ uint16_t num_entries;
+ uint16_t num_def_entries;
+
+ if (pai_buf_data_size < PAI_V2_ENTRIES_BASE) {
+ /* Corrupted - too small. */
+ return false;
+ }
+
+ if (CVAL(pai_buf,PAI_VERSION_OFFSET) != PAI_V2_VERSION) {
+ return false;
+ }
+
+ num_entries = SVAL(pai_buf,PAI_V2_NUM_ENTRIES_OFFSET);
+ num_def_entries = SVAL(pai_buf,PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ /* Check the entry lists match. */
+ /* Each entry is 6 bytes (flags + type + 4 bytes of uid or gid). */
+
+ if (((num_entries + num_def_entries)*PAI_V2_ENTRY_LENGTH) +
+ PAI_V2_ENTRIES_BASE != pai_buf_data_size) {
+ return false;
+ }
+
+ return true;
+}
+
+/************************************************************************
+ Decode the owner.
+************************************************************************/
+
+static bool get_pai_owner_type(struct pai_entry *paie, const char *entry_offset)
+{
+ paie->owner_type = (enum ace_owner)CVAL(entry_offset,0);
+ switch( paie->owner_type) {
+ case UID_ACE:
+ paie->unix_ug.type = ID_TYPE_UID;
+ paie->unix_ug.id = (uid_t)IVAL(entry_offset,1);
+ DEBUG(10,("get_pai_owner_type: uid = %u\n",
+ (unsigned int)paie->unix_ug.id ));
+ break;
+ case GID_ACE:
+ paie->unix_ug.type = ID_TYPE_GID;
+ paie->unix_ug.id = (gid_t)IVAL(entry_offset,1);
+ DEBUG(10,("get_pai_owner_type: gid = %u\n",
+ (unsigned int)paie->unix_ug.id ));
+ break;
+ case WORLD_ACE:
+ paie->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ paie->unix_ug.id = -1;
+ DEBUG(10,("get_pai_owner_type: world ace\n"));
+ break;
+ default:
+ DEBUG(10,("get_pai_owner_type: unknown type %u\n",
+ (unsigned int)paie->owner_type ));
+ return false;
+ }
+ return true;
+}
+
+/************************************************************************
+ Process v2 entries.
+************************************************************************/
+
+static const char *create_pai_v1_entries(struct pai_val *paiv,
+ const char *entry_offset,
+ bool def_entry)
+{
+ unsigned int i;
+
+ for (i = 0; i < paiv->num_entries; i++) {
+ struct pai_entry *paie = talloc(talloc_tos(), struct pai_entry);
+ if (!paie) {
+ return NULL;
+ }
+
+ paie->ace_flags = SEC_ACE_FLAG_INHERITED_ACE;
+ if (!get_pai_owner_type(paie, entry_offset)) {
+ TALLOC_FREE(paie);
+ return NULL;
+ }
+
+ if (!def_entry) {
+ DLIST_ADD(paiv->entry_list, paie);
+ } else {
+ DLIST_ADD(paiv->def_entry_list, paie);
+ }
+ entry_offset += PAI_V1_ENTRY_LENGTH;
+ }
+ return entry_offset;
+}
+
+/************************************************************************
+ Convert to in-memory format from version 1.
+************************************************************************/
+
+static struct pai_val *create_pai_val_v1(const char *buf, size_t size)
+{
+ const char *entry_offset;
+ struct pai_val *paiv = NULL;
+
+ if (!check_pai_ok_v1(buf, size)) {
+ return NULL;
+ }
+
+ paiv = talloc(talloc_tos(), struct pai_val);
+ if (!paiv) {
+ return NULL;
+ }
+
+ memset(paiv, '\0', sizeof(struct pai_val));
+
+ paiv->sd_type = (CVAL(buf,PAI_V1_FLAG_OFFSET) == PAI_V1_ACL_FLAG_PROTECTED) ?
+ SEC_DESC_DACL_PROTECTED : 0;
+
+ paiv->num_entries = SVAL(buf,PAI_V1_NUM_ENTRIES_OFFSET);
+ paiv->num_def_entries = SVAL(buf,PAI_V1_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ entry_offset = buf + PAI_V1_ENTRIES_BASE;
+
+ DEBUG(10,("create_pai_val: num_entries = %u, num_def_entries = %u\n",
+ paiv->num_entries, paiv->num_def_entries ));
+
+ entry_offset = create_pai_v1_entries(paiv, entry_offset, false);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+ entry_offset = create_pai_v1_entries(paiv, entry_offset, true);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+
+ return paiv;
+}
+
+/************************************************************************
+ Process v2 entries.
+************************************************************************/
+
+static const char *create_pai_v2_entries(struct pai_val *paiv,
+ unsigned int num_entries,
+ const char *entry_offset,
+ bool def_entry)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_entries; i++) {
+ struct pai_entry *paie = talloc(talloc_tos(), struct pai_entry);
+ if (!paie) {
+ return NULL;
+ }
+
+ paie->ace_flags = CVAL(entry_offset,0);
+
+ if (!get_pai_owner_type(paie, entry_offset+1)) {
+ TALLOC_FREE(paie);
+ return NULL;
+ }
+ if (!def_entry) {
+ DLIST_ADD(paiv->entry_list, paie);
+ } else {
+ DLIST_ADD(paiv->def_entry_list, paie);
+ }
+ entry_offset += PAI_V2_ENTRY_LENGTH;
+ }
+ return entry_offset;
+}
+
+/************************************************************************
+ Convert to in-memory format from version 2.
+************************************************************************/
+
+static struct pai_val *create_pai_val_v2(const char *buf, size_t size)
+{
+ const char *entry_offset;
+ struct pai_val *paiv = NULL;
+
+ if (!check_pai_ok_v2(buf, size)) {
+ return NULL;
+ }
+
+ paiv = talloc(talloc_tos(), struct pai_val);
+ if (!paiv) {
+ return NULL;
+ }
+
+ memset(paiv, '\0', sizeof(struct pai_val));
+
+ paiv->sd_type = SVAL(buf,PAI_V2_TYPE_OFFSET);
+
+ paiv->num_entries = SVAL(buf,PAI_V2_NUM_ENTRIES_OFFSET);
+ paiv->num_def_entries = SVAL(buf,PAI_V2_NUM_DEFAULT_ENTRIES_OFFSET);
+
+ entry_offset = buf + PAI_V2_ENTRIES_BASE;
+
+ DEBUG(10,("create_pai_val_v2: sd_type = 0x%x num_entries = %u, num_def_entries = %u\n",
+ (unsigned int)paiv->sd_type,
+ paiv->num_entries, paiv->num_def_entries ));
+
+ entry_offset = create_pai_v2_entries(paiv, paiv->num_entries,
+ entry_offset, false);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+ entry_offset = create_pai_v2_entries(paiv, paiv->num_def_entries,
+ entry_offset, true);
+ if (entry_offset == NULL) {
+ free_inherited_info(paiv);
+ return NULL;
+ }
+
+ return paiv;
+}
+
+/************************************************************************
+ Convert to in-memory format - from either version 1 or 2.
+************************************************************************/
+
+static struct pai_val *create_pai_val(const char *buf, size_t size)
+{
+ if (size < 1) {
+ return NULL;
+ }
+ if (CVAL(buf,PAI_VERSION_OFFSET) == PAI_V1_VERSION) {
+ return create_pai_val_v1(buf, size);
+ } else if (CVAL(buf,PAI_VERSION_OFFSET) == PAI_V2_VERSION) {
+ return create_pai_val_v2(buf, size);
+ } else {
+ return NULL;
+ }
+}
+
+/************************************************************************
+ Load the user.SAMBA_PAI attribute.
+************************************************************************/
+
+static struct pai_val *fload_inherited_info(files_struct *fsp)
+{
+ char *pai_buf;
+ size_t pai_buf_size = 1024;
+ struct pai_val *paiv = NULL;
+ ssize_t ret;
+
+ if (!lp_map_acl_inherit(SNUM(fsp->conn))) {
+ return NULL;
+ }
+
+ if ((pai_buf = talloc_array(talloc_tos(), char, pai_buf_size)) == NULL) {
+ return NULL;
+ }
+
+ do {
+ ret = SMB_VFS_FGETXATTR(fsp, SAMBA_POSIX_INHERITANCE_EA_NAME,
+ pai_buf, pai_buf_size);
+ if (ret == -1) {
+ if (errno != ERANGE) {
+ break;
+ }
+ /* Buffer too small - enlarge it. */
+ pai_buf_size *= 2;
+ TALLOC_FREE(pai_buf);
+ if (pai_buf_size > 1024*1024) {
+ return NULL; /* Limit malloc to 1mb. */
+ }
+ if ((pai_buf = talloc_array(talloc_tos(), char, pai_buf_size)) == NULL)
+ return NULL;
+ }
+ } while (ret == -1);
+
+ DEBUG(10,("load_inherited_info: ret = %lu for file %s\n",
+ (unsigned long)ret, fsp_str_dbg(fsp)));
+
+ if (ret == -1) {
+ /* No attribute or not supported. */
+#if defined(ENOATTR)
+ if (errno != ENOATTR)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#else
+ if (errno != ENOSYS)
+ DEBUG(10,("load_inherited_info: Error %s\n", strerror(errno) ));
+#endif
+ TALLOC_FREE(pai_buf);
+ return NULL;
+ }
+
+ paiv = create_pai_val(pai_buf, ret);
+
+ if (paiv) {
+ DEBUG(10,("load_inherited_info: ACL type is 0x%x for file %s\n",
+ (unsigned int)paiv->sd_type, fsp_str_dbg(fsp)));
+ }
+
+ TALLOC_FREE(pai_buf);
+ return paiv;
+}
+
+/****************************************************************************
+ Functions to manipulate the internal ACE format.
+****************************************************************************/
+
+/****************************************************************************
+ Count a linked list of canonical ACE entries.
+****************************************************************************/
+
+static size_t count_canon_ace_list( canon_ace *l_head )
+{
+ size_t count = 0;
+ canon_ace *ace;
+
+ for (ace = l_head; ace; ace = ace->next)
+ count++;
+
+ return count;
+}
+
+/****************************************************************************
+ Free a linked list of canonical ACE entries.
+****************************************************************************/
+
+static void free_canon_ace_list( canon_ace *l_head )
+{
+ canon_ace *list, *next;
+
+ for (list = l_head; list; list = next) {
+ next = list->next;
+ DLIST_REMOVE(l_head, list);
+ TALLOC_FREE(list);
+ }
+}
+
+/****************************************************************************
+ Function to duplicate a canon_ace entry.
+****************************************************************************/
+
+static canon_ace *dup_canon_ace( canon_ace *src_ace)
+{
+ canon_ace *dst_ace = talloc(talloc_tos(), canon_ace);
+
+ if (dst_ace == NULL)
+ return NULL;
+
+ *dst_ace = *src_ace;
+ dst_ace->prev = dst_ace->next = NULL;
+ return dst_ace;
+}
+
+/****************************************************************************
+ Print out a canon ace.
+****************************************************************************/
+
+static void print_canon_ace(canon_ace *pace, int num)
+{
+ struct dom_sid_buf buf;
+ dbgtext( "canon_ace index %d. Type = %s ", num, pace->attr == ALLOW_ACE ? "allow" : "deny" );
+ dbgtext( "SID = %s ", dom_sid_str_buf(&pace->trustee, &buf));
+ if (pace->owner_type == UID_ACE) {
+ dbgtext( "uid %u ", (unsigned int)pace->unix_ug.id);
+ } else if (pace->owner_type == GID_ACE) {
+ dbgtext( "gid %u ", (unsigned int)pace->unix_ug.id);
+ } else
+ dbgtext( "other ");
+ switch (pace->type) {
+ case SMB_ACL_USER:
+ dbgtext( "SMB_ACL_USER ");
+ break;
+ case SMB_ACL_USER_OBJ:
+ dbgtext( "SMB_ACL_USER_OBJ ");
+ break;
+ case SMB_ACL_GROUP:
+ dbgtext( "SMB_ACL_GROUP ");
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ dbgtext( "SMB_ACL_GROUP_OBJ ");
+ break;
+ case SMB_ACL_OTHER:
+ dbgtext( "SMB_ACL_OTHER ");
+ break;
+ default:
+ dbgtext( "MASK " );
+ break;
+ }
+
+ dbgtext( "ace_flags = 0x%x ", (unsigned int)pace->ace_flags);
+ dbgtext( "perms ");
+ dbgtext( "%c", pace->perms & S_IRUSR ? 'r' : '-');
+ dbgtext( "%c", pace->perms & S_IWUSR ? 'w' : '-');
+ dbgtext( "%c\n", pace->perms & S_IXUSR ? 'x' : '-');
+}
+
+/****************************************************************************
+ Print out a canon ace list.
+****************************************************************************/
+
+static void print_canon_ace_list(const char *name, canon_ace *ace_list)
+{
+ int count = 0;
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext( "print_canon_ace_list: %s\n", name );
+ for (;ace_list; ace_list = ace_list->next, count++)
+ print_canon_ace(ace_list, count );
+ }
+}
+
+/****************************************************************************
+ Map POSIX ACL perms to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits).
+****************************************************************************/
+
+static mode_t convert_permset_to_mode_t(SMB_ACL_PERMSET_T permset)
+{
+ mode_t ret = 0;
+
+ ret |= (sys_acl_get_perm(permset, SMB_ACL_READ) ? S_IRUSR : 0);
+ ret |= (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? S_IWUSR : 0);
+ ret |= (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? S_IXUSR : 0);
+
+ return ret;
+}
+
+/****************************************************************************
+ Map generic UNIX permissions to canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits).
+****************************************************************************/
+
+mode_t unix_perms_to_acl_perms(mode_t mode, int r_mask, int w_mask, int x_mask)
+{
+ mode_t ret = 0;
+
+ if (mode & r_mask)
+ ret |= S_IRUSR;
+ if (mode & w_mask)
+ ret |= S_IWUSR;
+ if (mode & x_mask)
+ ret |= S_IXUSR;
+
+ return ret;
+}
+
+/****************************************************************************
+ Map canon_ace permissions (a mode_t containing only S_(R|W|X)USR bits) to
+ an SMB_ACL_PERMSET_T.
+****************************************************************************/
+
+int map_acl_perms_to_permset(mode_t mode, SMB_ACL_PERMSET_T *p_permset)
+{
+ if (sys_acl_clear_perms(*p_permset) == -1)
+ return -1;
+ if (mode & S_IRUSR) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_READ) == -1)
+ return -1;
+ }
+ if (mode & S_IWUSR) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_WRITE) == -1)
+ return -1;
+ }
+ if (mode & S_IXUSR) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_EXECUTE) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+/****************************************************************************
+ Function to create owner and group SIDs from a SMB_STRUCT_STAT.
+****************************************************************************/
+
+static void create_file_sids(const SMB_STRUCT_STAT *psbuf,
+ struct dom_sid *powner_sid,
+ struct dom_sid *pgroup_sid)
+{
+ uid_to_sid( powner_sid, psbuf->st_ex_uid );
+ gid_to_sid( pgroup_sid, psbuf->st_ex_gid );
+}
+
+/****************************************************************************
+ Merge aces with a common UID or GID - if both are allow or deny, OR the permissions together and
+ delete the second one. If the first is deny, mask the permissions off and delete the allow
+ if the permissions become zero, delete the deny if the permissions are non zero.
+****************************************************************************/
+
+static void merge_aces( canon_ace **pp_list_head, bool dir_acl)
+{
+ canon_ace *l_head = *pp_list_head;
+ canon_ace *curr_ace_outer;
+ canon_ace *curr_ace_outer_next;
+
+ /*
+ * First, merge allow entries with identical SIDs, and deny entries
+ * with identical SIDs.
+ */
+
+ for (curr_ace_outer = l_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) {
+ canon_ace *curr_ace;
+ canon_ace *curr_ace_next;
+
+ curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */
+
+ for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) {
+ bool can_merge = false;
+
+ curr_ace_next = curr_ace->next; /* Save the link in case of delete. */
+
+ /* For file ACLs we can merge if the SIDs and ALLOW/DENY
+ * types are the same. For directory acls we must also
+ * ensure the POSIX ACL types are the same.
+ *
+ * For the IDMAP_BOTH case, we must not merge
+ * the UID and GID ACE values for same SID
+ */
+
+ if (!dir_acl) {
+ can_merge = (curr_ace->unix_ug.id == curr_ace_outer->unix_ug.id &&
+ curr_ace->owner_type == curr_ace_outer->owner_type &&
+ (curr_ace->attr == curr_ace_outer->attr));
+ } else {
+ can_merge = (curr_ace->unix_ug.id == curr_ace_outer->unix_ug.id &&
+ curr_ace->owner_type == curr_ace_outer->owner_type &&
+ (curr_ace->type == curr_ace_outer->type) &&
+ (curr_ace->attr == curr_ace_outer->attr));
+ }
+
+ if (can_merge) {
+ if( DEBUGLVL( 10 )) {
+ dbgtext("merge_aces: Merging ACE's\n");
+ print_canon_ace( curr_ace_outer, 0);
+ print_canon_ace( curr_ace, 0);
+ }
+
+ /* Merge two allow or two deny ACE's. */
+
+ /* Theoretically we shouldn't merge a dir ACE if
+ * one ACE has the CI flag set, and the other
+ * ACE has the OI flag set, but this is rare
+ * enough we can ignore it. */
+
+ curr_ace_outer->perms |= curr_ace->perms;
+ curr_ace_outer->ace_flags |= curr_ace->ace_flags;
+ DLIST_REMOVE(l_head, curr_ace);
+ TALLOC_FREE(curr_ace);
+ curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */
+ }
+ }
+ }
+
+ /*
+ * Now go through and mask off allow permissions with deny permissions.
+ * We can delete either the allow or deny here as we know that each SID
+ * appears only once in the list.
+ */
+
+ for (curr_ace_outer = l_head; curr_ace_outer; curr_ace_outer = curr_ace_outer_next) {
+ canon_ace *curr_ace;
+ canon_ace *curr_ace_next;
+
+ curr_ace_outer_next = curr_ace_outer->next; /* Save the link in case we delete. */
+
+ for (curr_ace = curr_ace_outer->next; curr_ace; curr_ace = curr_ace_next) {
+
+ curr_ace_next = curr_ace->next; /* Save the link in case of delete. */
+
+ /*
+ * Subtract ACE's with different entries. Due to the ordering constraints
+ * we've put on the ACL, we know the deny must be the first one.
+ */
+
+ if (curr_ace->unix_ug.id == curr_ace_outer->unix_ug.id &&
+ (curr_ace->owner_type == curr_ace_outer->owner_type) &&
+ (curr_ace_outer->attr == DENY_ACE) && (curr_ace->attr == ALLOW_ACE)) {
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("merge_aces: Masking ACE's\n");
+ print_canon_ace( curr_ace_outer, 0);
+ print_canon_ace( curr_ace, 0);
+ }
+
+ curr_ace->perms &= ~curr_ace_outer->perms;
+
+ if (curr_ace->perms == 0) {
+
+ /*
+ * The deny overrides the allow. Remove the allow.
+ */
+
+ DLIST_REMOVE(l_head, curr_ace);
+ TALLOC_FREE(curr_ace);
+ curr_ace_outer_next = curr_ace_outer->next; /* We may have deleted the link. */
+
+ } else {
+
+ /*
+ * Even after removing permissions, there
+ * are still allow permissions - delete the deny.
+ * It is safe to delete the deny here,
+ * as we are guaranteed by the deny first
+ * ordering that all the deny entries for
+ * this SID have already been merged into one
+ * before we can get to an allow ace.
+ */
+
+ DLIST_REMOVE(l_head, curr_ace_outer);
+ TALLOC_FREE(curr_ace_outer);
+ break;
+ }
+ }
+
+ } /* end for curr_ace */
+ } /* end for curr_ace_outer */
+
+ /* We may have modified the list. */
+
+ *pp_list_head = l_head;
+}
+
+/****************************************************************************
+ Map canon_ace perms to permission bits NT.
+ The attr element is not used here - we only process deny entries on set,
+ not get. Deny entries are implicit on get with ace->perms = 0.
+****************************************************************************/
+
+uint32_t map_canon_ace_perms(int snum,
+ enum security_ace_type *pacl_type,
+ mode_t perms,
+ bool directory_ace)
+{
+ uint32_t nt_mask = 0;
+
+ *pacl_type = SEC_ACE_TYPE_ACCESS_ALLOWED;
+
+ if (lp_acl_map_full_control(snum) && ((perms & ALL_ACE_PERMS) == ALL_ACE_PERMS)) {
+ if (directory_ace) {
+ nt_mask = UNIX_DIRECTORY_ACCESS_RWX;
+ } else {
+ nt_mask = (UNIX_ACCESS_RWX & ~DELETE_ACCESS);
+ }
+ } else if ((perms & ALL_ACE_PERMS) == (mode_t)0) {
+ /*
+ * Windows NT refuses to display ACEs with no permissions in them (but
+ * they are perfectly legal with Windows 2000). If the ACE has empty
+ * permissions we cannot use 0, so we use the otherwise unused
+ * WRITE_OWNER permission, which we ignore when we set an ACL.
+ * We abstract this into a #define of UNIX_ACCESS_NONE to allow this
+ * to be changed in the future.
+ */
+
+ nt_mask = 0;
+ } else {
+ if (directory_ace) {
+ nt_mask |= ((perms & S_IRUSR) ? UNIX_DIRECTORY_ACCESS_R : 0 );
+ nt_mask |= ((perms & S_IWUSR) ? UNIX_DIRECTORY_ACCESS_W : 0 );
+ nt_mask |= ((perms & S_IXUSR) ? UNIX_DIRECTORY_ACCESS_X : 0 );
+ } else {
+ nt_mask |= ((perms & S_IRUSR) ? UNIX_ACCESS_R : 0 );
+ nt_mask |= ((perms & S_IWUSR) ? UNIX_ACCESS_W : 0 );
+ nt_mask |= ((perms & S_IXUSR) ? UNIX_ACCESS_X : 0 );
+ }
+ }
+
+ if ((perms & S_IWUSR) && lp_dos_filemode(snum)) {
+ nt_mask |= (SEC_STD_WRITE_DAC|SEC_STD_WRITE_OWNER|DELETE_ACCESS);
+ }
+
+ DEBUG(10,("map_canon_ace_perms: Mapped (UNIX) %x to (NT) %x\n",
+ (unsigned int)perms, (unsigned int)nt_mask ));
+
+ return nt_mask;
+}
+
+/****************************************************************************
+ Map NT perms to a UNIX mode_t.
+****************************************************************************/
+
+#define FILE_SPECIFIC_READ_BITS (FILE_READ_DATA|FILE_READ_EA)
+#define FILE_SPECIFIC_WRITE_BITS (FILE_WRITE_DATA|FILE_APPEND_DATA|FILE_WRITE_EA)
+#define FILE_SPECIFIC_EXECUTE_BITS (FILE_EXECUTE)
+
+static mode_t map_nt_perms( uint32_t *mask, int type)
+{
+ mode_t mode = 0;
+
+ switch(type) {
+ case S_IRUSR:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IRUSR|S_IWUSR|S_IXUSR;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRUSR : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWUSR : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXUSR : 0;
+ }
+ break;
+ case S_IRGRP:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IRGRP|S_IWGRP|S_IXGRP;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IRGRP : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWGRP : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXGRP : 0;
+ }
+ break;
+ case S_IROTH:
+ if((*mask) & GENERIC_ALL_ACCESS)
+ mode = S_IROTH|S_IWOTH|S_IXOTH;
+ else {
+ mode |= ((*mask) & (GENERIC_READ_ACCESS|FILE_SPECIFIC_READ_BITS)) ? S_IROTH : 0;
+ mode |= ((*mask) & (GENERIC_WRITE_ACCESS|FILE_SPECIFIC_WRITE_BITS)) ? S_IWOTH : 0;
+ mode |= ((*mask) & (GENERIC_EXECUTE_ACCESS|FILE_SPECIFIC_EXECUTE_BITS)) ? S_IXOTH : 0;
+ }
+ break;
+ }
+
+ return mode;
+}
+
+/****************************************************************************
+ Unpack a struct security_descriptor into a UNIX owner and group.
+****************************************************************************/
+
+static NTSTATUS unpack_nt_owners(struct connection_struct *conn,
+ uid_t *puser, gid_t *pgrp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ *puser = (uid_t)-1;
+ *pgrp = (gid_t)-1;
+
+ if(security_info_sent == 0) {
+ DEBUG(0,("unpack_nt_owners: no security info sent !\n"));
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Validate the owner and group SID's.
+ */
+
+ DEBUG(5,("unpack_nt_owners: validating owner_sids.\n"));
+
+ /*
+ * Don't immediately fail if the owner sid cannot be validated.
+ * This may be a group chown only set.
+ */
+
+ if (security_info_sent & SECINFO_OWNER) {
+ if (!sid_to_uid(psd->owner_sid, puser)) {
+ if (lp_force_unknown_acl_user(SNUM(conn))) {
+ /* this allows take ownership to work
+ * reasonably */
+ *puser = get_current_uid(conn);
+ } else {
+ struct dom_sid_buf buf;
+ DBG_NOTICE("unable to validate"
+ " owner sid for %s\n",
+ dom_sid_str_buf(psd->owner_sid,
+ &buf));
+ return NT_STATUS_INVALID_OWNER;
+ }
+ }
+ DEBUG(3,("unpack_nt_owners: owner sid mapped to uid %u\n",
+ (unsigned int)*puser ));
+ }
+
+ /*
+ * Don't immediately fail if the group sid cannot be validated.
+ * This may be an owner chown only set.
+ */
+
+ if (security_info_sent & SECINFO_GROUP) {
+ if (!sid_to_gid(psd->group_sid, pgrp)) {
+ if (lp_force_unknown_acl_user(SNUM(conn))) {
+ /* this allows take group ownership to work
+ * reasonably */
+ *pgrp = get_current_gid(conn);
+ } else {
+ DEBUG(3,("unpack_nt_owners: unable to validate"
+ " group sid.\n"));
+ return NT_STATUS_INVALID_OWNER;
+ }
+ }
+ DEBUG(3,("unpack_nt_owners: group sid mapped to gid %u\n",
+ (unsigned int)*pgrp));
+ }
+
+ DEBUG(5,("unpack_nt_owners: owner_sids validated.\n"));
+
+ return NT_STATUS_OK;
+}
+
+
+static void trim_ace_perms(canon_ace *pace)
+{
+ pace->perms = pace->perms & (S_IXUSR|S_IWUSR|S_IRUSR);
+}
+
+static void ensure_minimal_owner_ace_perms(const bool is_directory,
+ canon_ace *pace)
+{
+ pace->perms |= S_IRUSR;
+ if (is_directory) {
+ pace->perms |= (S_IWUSR|S_IXUSR);
+ }
+}
+
+/****************************************************************************
+ Check if a given uid/SID is in a group gid/SID. This is probably very
+ expensive and will need optimisation. A *lot* of optimisation :-). JRA.
+****************************************************************************/
+
+static bool uid_entry_in_group(connection_struct *conn, canon_ace *uid_ace, canon_ace *group_ace )
+{
+ bool is_sid = false;
+ bool has_sid = false;
+ struct security_token *security_token = NULL;
+
+ /* "Everyone" always matches every uid. */
+
+ if (dom_sid_equal(&group_ace->trustee, &global_sid_World))
+ return True;
+
+ security_token = conn->session_info->security_token;
+ /* security_token should not be NULL */
+ SMB_ASSERT(security_token);
+ is_sid = security_token_is_sid(security_token,
+ &uid_ace->trustee);
+ if (is_sid) {
+ has_sid = security_token_has_sid(security_token,
+ &group_ace->trustee);
+
+ if (has_sid) {
+ return true;
+ }
+ }
+
+ /*
+ * if it's the current user, we already have the unix token
+ * and don't need to do the complex user_in_group_sid() call
+ */
+ if (uid_ace->unix_ug.id == get_current_uid(conn)) {
+ const struct security_unix_token *curr_utok = NULL;
+ size_t i;
+
+ if (group_ace->unix_ug.id == get_current_gid(conn)) {
+ return True;
+ }
+
+ curr_utok = get_current_utok(conn);
+ for (i=0; i < curr_utok->ngroups; i++) {
+ if (group_ace->unix_ug.id == curr_utok->groups[i]) {
+ return True;
+ }
+ }
+ }
+
+ /*
+ * user_in_group_sid() uses create_token_from_sid()
+ * which creates an artificial NT token given just a username,
+ * so this is not reliable for users from foreign domains
+ * exported by winbindd!
+ */
+ return user_sid_in_group_sid(&uid_ace->trustee, &group_ace->trustee);
+}
+
+/****************************************************************************
+ A well formed POSIX file or default ACL has at least 3 entries, a
+ SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ.
+ In addition, the owner must always have at least read access.
+ When using this call on get_acl, the pst struct is valid and contains
+ the mode of the file.
+****************************************************************************/
+
+static bool ensure_canon_entry_valid_on_get(connection_struct *conn,
+ canon_ace **pp_ace,
+ const struct dom_sid *pfile_owner_sid,
+ const struct dom_sid *pfile_grp_sid,
+ const SMB_STRUCT_STAT *pst)
+{
+ canon_ace *pace;
+ bool got_user = false;
+ bool got_group = false;
+ bool got_other = false;
+
+ for (pace = *pp_ace; pace; pace = pace->next) {
+ if (pace->type == SMB_ACL_USER_OBJ) {
+ got_user = true;
+ } else if (pace->type == SMB_ACL_GROUP_OBJ) {
+ got_group = true;
+ } else if (pace->type == SMB_ACL_OTHER) {
+ got_other = true;
+ }
+ }
+
+ if (!got_user) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("malloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_USER_OBJ;
+ pace->owner_type = UID_ACE;
+ pace->unix_ug.type = ID_TYPE_UID;
+ pace->unix_ug.id = pst->st_ex_uid;
+ pace->trustee = *pfile_owner_sid;
+ pace->attr = ALLOW_ACE;
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IRUSR, S_IWUSR, S_IXUSR);
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ if (!got_group) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("malloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP_OBJ;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pst->st_ex_gid;
+ pace->trustee = *pfile_grp_sid;
+ pace->attr = ALLOW_ACE;
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IRGRP, S_IWGRP, S_IXGRP);
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ if (!got_other) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("malloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_OTHER;
+ pace->owner_type = WORLD_ACE;
+ pace->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ pace->unix_ug.id = -1;
+ pace->trustee = global_sid_World;
+ pace->attr = ALLOW_ACE;
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IROTH, S_IWOTH, S_IXOTH);
+ DLIST_ADD(*pp_ace, pace);
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ A well formed POSIX file or default ACL has at least 3 entries, a
+ SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ.
+ In addition, the owner must always have at least read access.
+ When using this call on set_acl, the pst struct has
+ been modified to have a mode containing the default for this file or directory
+ type.
+****************************************************************************/
+
+static bool ensure_canon_entry_valid_on_set(connection_struct *conn,
+ canon_ace **pp_ace,
+ bool is_default_acl,
+ const struct share_params *params,
+ const bool is_directory,
+ const struct dom_sid *pfile_owner_sid,
+ const struct dom_sid *pfile_grp_sid,
+ const SMB_STRUCT_STAT *pst)
+{
+ canon_ace *pace;
+ canon_ace *pace_user = NULL;
+ canon_ace *pace_group = NULL;
+ canon_ace *pace_other = NULL;
+ bool got_duplicate_user = false;
+ bool got_duplicate_group = false;
+
+ for (pace = *pp_ace; pace; pace = pace->next) {
+ trim_ace_perms(pace);
+ if (pace->type == SMB_ACL_USER_OBJ) {
+ ensure_minimal_owner_ace_perms(is_directory, pace);
+ pace_user = pace;
+ } else if (pace->type == SMB_ACL_GROUP_OBJ) {
+ pace_group = pace;
+ } else if (pace->type == SMB_ACL_OTHER) {
+ pace_other = pace;
+ }
+ }
+
+ if (!pace_user) {
+ canon_ace *pace_iter;
+
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_USER_OBJ;
+ pace->owner_type = UID_ACE;
+ pace->unix_ug.type = ID_TYPE_UID;
+ pace->unix_ug.id = pst->st_ex_uid;
+ pace->trustee = *pfile_owner_sid;
+ pace->attr = ALLOW_ACE;
+ /* Start with existing user permissions, principle of least
+ surprises for the user. */
+ pace->perms = unix_perms_to_acl_perms(pst->st_ex_mode, S_IRUSR, S_IWUSR, S_IXUSR);
+
+ /* See if the owning user is in any of the other groups in
+ the ACE, or if there's a matching user entry (by uid
+ or in the case of ID_TYPE_BOTH by SID).
+ If so, OR in the permissions from that entry. */
+
+
+ for (pace_iter = *pp_ace; pace_iter; pace_iter = pace_iter->next) {
+ if (pace_iter->type == SMB_ACL_USER &&
+ pace_iter->unix_ug.id == pace->unix_ug.id) {
+ pace->perms |= pace_iter->perms;
+ } else if (pace_iter->type == SMB_ACL_GROUP_OBJ || pace_iter->type == SMB_ACL_GROUP) {
+ if (dom_sid_equal(&pace->trustee, &pace_iter->trustee)) {
+ pace->perms |= pace_iter->perms;
+ } else if (uid_entry_in_group(conn, pace, pace_iter)) {
+ pace->perms |= pace_iter->perms;
+ }
+ }
+ }
+
+ if (pace->perms == 0) {
+ /* If we only got an "everyone" perm, just use that. */
+ if (pace_other)
+ pace->perms = pace_other->perms;
+ }
+
+ /*
+ * Ensure we have default parameters for the
+ * user (owner) even on default ACLs.
+ */
+ ensure_minimal_owner_ace_perms(is_directory, pace);
+
+ DLIST_ADD(*pp_ace, pace);
+ pace_user = pace;
+ }
+
+ if (!pace_group) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP_OBJ;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pst->st_ex_gid;
+ pace->trustee = *pfile_grp_sid;
+ pace->attr = ALLOW_ACE;
+
+ /* If we only got an "everyone" perm, just use that. */
+ if (pace_other) {
+ pace->perms = pace_other->perms;
+ } else {
+ pace->perms = 0;
+ }
+
+ DLIST_ADD(*pp_ace, pace);
+ pace_group = pace;
+ }
+
+ if (!pace_other) {
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_OTHER;
+ pace->owner_type = WORLD_ACE;
+ pace->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ pace->unix_ug.id = -1;
+ pace->trustee = global_sid_World;
+ pace->attr = ALLOW_ACE;
+ pace->perms = 0;
+
+ DLIST_ADD(*pp_ace, pace);
+ pace_other = pace;
+ }
+
+ /* Ensure when setting a POSIX ACL, that the uid for a
+ SMB_ACL_USER_OBJ ACE (the owner ACE entry) has a duplicate
+ permission entry as an SMB_ACL_USER, and a gid for a
+ SMB_ACL_GROUP_OBJ ACE (the primary group ACE entry) also has
+ a duplicate permission entry as an SMB_ACL_GROUP. If not,
+ then if the ownership or group ownership of this file or
+ directory gets changed, the user or group can lose their
+ access. */
+
+ for (pace = *pp_ace; pace; pace = pace->next) {
+ if (pace->type == SMB_ACL_USER &&
+ pace->unix_ug.id == pace_user->unix_ug.id) {
+ /* Already got one. */
+ got_duplicate_user = true;
+ } else if (pace->type == SMB_ACL_GROUP &&
+ pace->unix_ug.id == pace_group->unix_ug.id) {
+ /* Already got one. */
+ got_duplicate_group = true;
+ } else if ((pace->type == SMB_ACL_GROUP)
+ && (dom_sid_equal(&pace->trustee, &pace_user->trustee))) {
+ /* If the SID owning the file appears
+ * in a group entry, then we have
+ * enough duplication, they will still
+ * have access */
+ got_duplicate_user = true;
+ }
+ }
+
+ /* If the SID is equal for the user and group that we need
+ to add the duplicate for, add only the group */
+ if (!got_duplicate_user && !got_duplicate_group
+ && dom_sid_equal(&pace_group->trustee,
+ &pace_user->trustee)) {
+ /* Add a duplicate SMB_ACL_GROUP entry, this
+ * will cover the owning SID as well, as it
+ * will always be mapped to both a uid and
+ * gid. */
+
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP;;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pace_group->unix_ug.id;
+ pace->trustee = pace_group->trustee;
+ pace->attr = pace_group->attr;
+ pace->perms = pace_group->perms;
+
+ DLIST_ADD(*pp_ace, pace);
+
+ /* We're done here, make sure the
+ statements below are not executed. */
+ got_duplicate_user = true;
+ got_duplicate_group = true;
+ }
+
+ if (!got_duplicate_user) {
+ /* Add a duplicate SMB_ACL_USER entry. */
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_USER;;
+ pace->owner_type = UID_ACE;
+ pace->unix_ug.type = ID_TYPE_UID;
+ pace->unix_ug.id = pace_user->unix_ug.id;
+ pace->trustee = pace_user->trustee;
+ pace->attr = pace_user->attr;
+ pace->perms = pace_user->perms;
+
+ DLIST_ADD(*pp_ace, pace);
+
+ got_duplicate_user = true;
+ }
+
+ if (!got_duplicate_group) {
+ /* Add a duplicate SMB_ACL_GROUP entry. */
+ if ((pace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ DEBUG(0,("talloc fail.\n"));
+ return false;
+ }
+
+ ZERO_STRUCTP(pace);
+ pace->type = SMB_ACL_GROUP;;
+ pace->owner_type = GID_ACE;
+ pace->unix_ug.type = ID_TYPE_GID;
+ pace->unix_ug.id = pace_group->unix_ug.id;
+ pace->trustee = pace_group->trustee;
+ pace->attr = pace_group->attr;
+ pace->perms = pace_group->perms;
+
+ DLIST_ADD(*pp_ace, pace);
+
+ got_duplicate_group = true;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Check if a POSIX ACL has the required SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ entries.
+ If it does not have them, check if there are any entries where the trustee is the
+ file owner or the owning group, and map these to SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ.
+ Note we must not do this to default directory ACLs.
+****************************************************************************/
+
+static void check_owning_objs(canon_ace *ace, struct dom_sid *pfile_owner_sid, struct dom_sid *pfile_grp_sid)
+{
+ bool got_user_obj, got_group_obj;
+ canon_ace *current_ace;
+ int i, entries;
+
+ entries = count_canon_ace_list(ace);
+ got_user_obj = False;
+ got_group_obj = False;
+
+ for (i=0, current_ace = ace; i < entries; i++, current_ace = current_ace->next) {
+ if (current_ace->type == SMB_ACL_USER_OBJ)
+ got_user_obj = True;
+ else if (current_ace->type == SMB_ACL_GROUP_OBJ)
+ got_group_obj = True;
+ }
+ if (got_user_obj && got_group_obj) {
+ DEBUG(10,("check_owning_objs: ACL had owning user/group entries.\n"));
+ return;
+ }
+
+ for (i=0, current_ace = ace; i < entries; i++, current_ace = current_ace->next) {
+ if (!got_user_obj && current_ace->owner_type == UID_ACE &&
+ dom_sid_equal(&current_ace->trustee, pfile_owner_sid)) {
+ current_ace->type = SMB_ACL_USER_OBJ;
+ got_user_obj = True;
+ }
+ if (!got_group_obj && current_ace->owner_type == GID_ACE &&
+ dom_sid_equal(&current_ace->trustee, pfile_grp_sid)) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ got_group_obj = True;
+ }
+ }
+ if (!got_user_obj)
+ DEBUG(10,("check_owning_objs: ACL is missing an owner entry.\n"));
+ if (!got_group_obj)
+ DEBUG(10,("check_owning_objs: ACL is missing an owning group entry.\n"));
+}
+
+static bool add_current_ace_to_acl(files_struct *fsp, struct security_ace *psa,
+ canon_ace **file_ace, canon_ace **dir_ace,
+ bool *got_file_allow, bool *got_dir_allow,
+ bool *all_aces_are_inherit_only,
+ canon_ace *current_ace)
+{
+
+ /*
+ * Map the given NT permissions into a UNIX mode_t containing only
+ * S_I(R|W|X)USR bits.
+ */
+
+ current_ace->perms |= map_nt_perms( &psa->access_mask, S_IRUSR);
+ current_ace->attr = (psa->type == SEC_ACE_TYPE_ACCESS_ALLOWED) ? ALLOW_ACE : DENY_ACE;
+
+ /* Store the ace_flag. */
+ current_ace->ace_flags = psa->flags;
+
+ /*
+ * Now add the created ace to either the file list, the directory
+ * list, or both. We *MUST* preserve the order here (hence we use
+ * DLIST_ADD_END) as NT ACLs are order dependent.
+ */
+
+ if (fsp->fsp_flags.is_directory) {
+
+ /*
+ * We can only add to the default POSIX ACE list if the ACE is
+ * designed to be inherited by both files and directories.
+ */
+
+ if ((psa->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) ==
+ (SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+
+ canon_ace *current_dir_ace = current_ace;
+ DLIST_ADD_END(*dir_ace, current_ace);
+
+ /*
+ * Note if this was an allow ace. We can't process
+ * any further deny ace's after this.
+ */
+
+ if (current_ace->attr == ALLOW_ACE)
+ *got_dir_allow = True;
+
+ if ((current_ace->attr == DENY_ACE) && *got_dir_allow) {
+ DEBUG(0,("add_current_ace_to_acl: "
+ "malformed ACL in "
+ "inheritable ACL! Deny entry "
+ "after Allow entry. Failing "
+ "to set on file %s.\n",
+ fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("add_current_ace_to_acl: adding dir ACL:\n");
+ print_canon_ace( current_ace, 0);
+ }
+
+ /*
+ * If this is not an inherit only ACE we need to add a duplicate
+ * to the file acl.
+ */
+
+ if (!(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) {
+ canon_ace *dup_ace = dup_canon_ace(current_ace);
+
+ if (!dup_ace) {
+ DEBUG(0,("add_current_ace_to_acl: malloc fail !\n"));
+ return False;
+ }
+
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the dir_ace list.
+ */
+ current_ace = dup_ace;
+ /* We've essentially split this ace into two,
+ * and added the ace with inheritance request
+ * bits to the directory ACL. Drop those bits for
+ * the ACE we're adding to the file list. */
+ current_ace->ace_flags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+ } else {
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the dir_ace list.
+ */
+ current_ace = NULL;
+ }
+
+ /*
+ * current_ace is now either owned by file_ace
+ * or is NULL. We can safely operate on current_dir_ace
+ * to treat mapping for default acl entries differently
+ * than access acl entries.
+ */
+
+ if (current_dir_ace->owner_type == UID_ACE) {
+ /*
+ * We already decided above this is a uid,
+ * for default acls ace's only CREATOR_OWNER
+ * maps to ACL_USER_OBJ. All other uid
+ * ace's are ACL_USER.
+ */
+ if (dom_sid_equal(&current_dir_ace->trustee,
+ &global_sid_Creator_Owner)) {
+ current_dir_ace->type = SMB_ACL_USER_OBJ;
+ } else {
+ current_dir_ace->type = SMB_ACL_USER;
+ }
+ }
+
+ if (current_dir_ace->owner_type == GID_ACE) {
+ /*
+ * We already decided above this is a gid,
+ * for default acls ace's only CREATOR_GROUP
+ * maps to ACL_GROUP_OBJ. All other uid
+ * ace's are ACL_GROUP.
+ */
+ if (dom_sid_equal(&current_dir_ace->trustee,
+ &global_sid_Creator_Group)) {
+ current_dir_ace->type = SMB_ACL_GROUP_OBJ;
+ } else {
+ current_dir_ace->type = SMB_ACL_GROUP;
+ }
+ }
+ }
+ }
+
+ /*
+ * Only add to the file ACL if not inherit only.
+ */
+
+ if (current_ace && !(psa->flags & SEC_ACE_FLAG_INHERIT_ONLY)) {
+ DLIST_ADD_END(*file_ace, current_ace);
+
+ /*
+ * Note if this was an allow ace. We can't process
+ * any further deny ace's after this.
+ */
+
+ if (current_ace->attr == ALLOW_ACE)
+ *got_file_allow = True;
+
+ if ((current_ace->attr == DENY_ACE) && *got_file_allow) {
+ DEBUG(0,("add_current_ace_to_acl: malformed "
+ "ACL in file ACL ! Deny entry after "
+ "Allow entry. Failing to set on file "
+ "%s.\n", fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("add_current_ace_to_acl: adding file ACL:\n");
+ print_canon_ace( current_ace, 0);
+ }
+ *all_aces_are_inherit_only = False;
+ /*
+ * We must not free current_ace here as its
+ * pointer is now owned by the file_ace list.
+ */
+ current_ace = NULL;
+ }
+
+ /*
+ * Free if ACE was not added.
+ */
+
+ TALLOC_FREE(current_ace);
+ return true;
+}
+
+/****************************************************************************
+ Unpack a struct security_descriptor into two canonical ace lists.
+****************************************************************************/
+
+static bool create_canon_ace_lists(files_struct *fsp,
+ const SMB_STRUCT_STAT *pst,
+ struct dom_sid *pfile_owner_sid,
+ struct dom_sid *pfile_grp_sid,
+ canon_ace **ppfile_ace,
+ canon_ace **ppdir_ace,
+ const struct security_acl *dacl)
+{
+ bool all_aces_are_inherit_only = (fsp->fsp_flags.is_directory);
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ canon_ace *current_ace = NULL;
+ bool got_dir_allow = False;
+ bool got_file_allow = False;
+ uint32_t i, j;
+
+ *ppfile_ace = NULL;
+ *ppdir_ace = NULL;
+
+ /*
+ * Convert the incoming ACL into a more regular form.
+ */
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ struct security_ace *psa = &dacl->aces[i];
+
+ if((psa->type != SEC_ACE_TYPE_ACCESS_ALLOWED) && (psa->type != SEC_ACE_TYPE_ACCESS_DENIED)) {
+ DEBUG(3,("create_canon_ace_lists: unable to set anything but an ALLOW or DENY ACE.\n"));
+ return False;
+ }
+ }
+
+ /*
+ * Deal with the fact that NT 4.x re-writes the canonical format
+ * that we return for default ACLs. If a directory ACE is identical
+ * to a inherited directory ACE then NT changes the bits so that the
+ * first ACE is set to OI|IO and the second ACE for this SID is set
+ * to CI. We need to repair this. JRA.
+ */
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ struct security_ace *psa1 = &dacl->aces[i];
+
+ for (j = i + 1; j < dacl->num_aces; j++) {
+ struct security_ace *psa2 = &dacl->aces[j];
+
+ if (psa1->access_mask != psa2->access_mask)
+ continue;
+
+ if (!dom_sid_equal(&psa1->trustee, &psa2->trustee))
+ continue;
+
+ /*
+ * Ok - permission bits and SIDs are equal.
+ * Check if flags were re-written.
+ */
+
+ if (psa1->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+
+ psa1->flags |= (psa2->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT));
+ psa2->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT);
+
+ } else if (psa2->flags & SEC_ACE_FLAG_INHERIT_ONLY) {
+
+ psa2->flags |= (psa1->flags & (SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT));
+ psa1->flags &= ~(SEC_ACE_FLAG_CONTAINER_INHERIT|SEC_ACE_FLAG_OBJECT_INHERIT);
+
+ }
+ }
+ }
+
+ for(i = 0; i < dacl->num_aces; i++) {
+ struct security_ace *psa = &dacl->aces[i];
+
+ /*
+ * Create a canon_ace entry representing this NT DACL ACE.
+ */
+
+ if ((current_ace = talloc(talloc_tos(), canon_ace)) == NULL) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DEBUG(0,("create_canon_ace_lists: malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(current_ace);
+
+ sid_copy(&current_ace->trustee, &psa->trustee);
+
+ /*
+ * Try and work out if the SID is a user or group
+ * as we need to flag these differently for POSIX.
+ * Note what kind of a POSIX ACL this should map to.
+ */
+
+ if( dom_sid_equal(&current_ace->trustee, &global_sid_World)) {
+ current_ace->owner_type = WORLD_ACE;
+ current_ace->unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ current_ace->unix_ug.id = -1;
+ current_ace->type = SMB_ACL_OTHER;
+ } else if (dom_sid_equal(&current_ace->trustee, &global_sid_Creator_Owner)) {
+ current_ace->owner_type = UID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_UID;
+ current_ace->unix_ug.id = pst->st_ex_uid;
+ current_ace->type = SMB_ACL_USER_OBJ;
+
+ /*
+ * The Creator Owner entry only specifies inheritable permissions,
+ * never access permissions. WinNT doesn't always set the ACE to
+ * INHERIT_ONLY, though.
+ */
+
+ psa->flags |= SEC_ACE_FLAG_INHERIT_ONLY;
+
+ } else if (dom_sid_equal(&current_ace->trustee, &global_sid_Creator_Group)) {
+ current_ace->owner_type = GID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_GID;
+ current_ace->unix_ug.id = pst->st_ex_gid;
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+
+ /*
+ * The Creator Group entry only specifies inheritable permissions,
+ * never access permissions. WinNT doesn't always set the ACE to
+ * INHERIT_ONLY, though.
+ */
+ psa->flags |= SEC_ACE_FLAG_INHERIT_ONLY;
+
+ } else {
+ struct unixid unixid;
+
+ if (!sids_to_unixids(&current_ace->trustee, 1, &unixid)) {
+ struct dom_sid_buf buf;
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ TALLOC_FREE(current_ace);
+ DBG_ERR("sids_to_unixids failed for %s "
+ "(allocation failure)\n",
+ dom_sid_str_buf(&current_ace->trustee,
+ &buf));
+ return false;
+ }
+
+ if (unixid.type == ID_TYPE_BOTH) {
+ /*
+ * We must add both a user and group
+ * entry POSIX_ACL.
+ * This is due to the fact that in POSIX
+ * user entries are more specific than
+ * groups.
+ */
+ current_ace->owner_type = UID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_UID;
+ current_ace->unix_ug.id = unixid.id;
+ current_ace->type =
+ (unixid.id == pst->st_ex_uid) ?
+ SMB_ACL_USER_OBJ :
+ SMB_ACL_USER;
+
+ /* Add the user object to the posix ACL,
+ and proceed to the group mapping
+ below. This handles the talloc_free
+ of current_ace if not added for some
+ reason */
+ if (!add_current_ace_to_acl(fsp,
+ psa,
+ &file_ace,
+ &dir_ace,
+ &got_file_allow,
+ &got_dir_allow,
+ &all_aces_are_inherit_only,
+ current_ace)) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return false;
+ }
+
+ if ((current_ace = talloc(talloc_tos(),
+ canon_ace)) == NULL) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DEBUG(0,("create_canon_ace_lists: "
+ "malloc fail.\n"));
+ return False;
+ }
+
+ ZERO_STRUCTP(current_ace);
+
+ sid_copy(&current_ace->trustee, &psa->trustee);
+
+ current_ace->unix_ug.type = ID_TYPE_GID;
+ current_ace->unix_ug.id = unixid.id;
+ current_ace->owner_type = GID_ACE;
+ /* If it's the primary group, this is a
+ group_obj, not a group. */
+ if (current_ace->unix_ug.id == pst->st_ex_gid) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_GROUP;
+ }
+
+ } else if (unixid.type == ID_TYPE_UID) {
+ current_ace->owner_type = UID_ACE;
+ current_ace->unix_ug.type = ID_TYPE_UID;
+ current_ace->unix_ug.id = unixid.id;
+ /* If it's the owning user, this is a user_obj,
+ not a user. */
+ if (current_ace->unix_ug.id == pst->st_ex_uid) {
+ current_ace->type = SMB_ACL_USER_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_USER;
+ }
+ } else if (unixid.type == ID_TYPE_GID) {
+ current_ace->unix_ug.type = ID_TYPE_GID;
+ current_ace->unix_ug.id = unixid.id;
+ current_ace->owner_type = GID_ACE;
+ /* If it's the primary group, this is a
+ group_obj, not a group. */
+ if (current_ace->unix_ug.id == pst->st_ex_gid) {
+ current_ace->type = SMB_ACL_GROUP_OBJ;
+ } else {
+ current_ace->type = SMB_ACL_GROUP;
+ }
+ } else {
+ struct dom_sid_buf buf;
+ /*
+ * Silently ignore map failures in non-mappable SIDs (NT Authority, BUILTIN etc).
+ */
+
+ if (non_mappable_sid(&psa->trustee)) {
+ DBG_DEBUG("ignoring "
+ "non-mappable SID %s\n",
+ dom_sid_str_buf(
+ &psa->trustee,
+ &buf));
+ TALLOC_FREE(current_ace);
+ continue;
+ }
+
+ if (lp_force_unknown_acl_user(SNUM(fsp->conn))) {
+ DBG_DEBUG("ignoring unknown or "
+ "foreign SID %s\n",
+ dom_sid_str_buf(
+ &psa->trustee,
+ &buf));
+ TALLOC_FREE(current_ace);
+ continue;
+ }
+
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ DBG_ERR("unable to map SID %s to uid or "
+ "gid.\n",
+ dom_sid_str_buf(&current_ace->trustee,
+ &buf));
+ TALLOC_FREE(current_ace);
+ return false;
+ }
+ }
+
+ /* handles the talloc_free of current_ace if not added for some reason */
+ if (!add_current_ace_to_acl(fsp, psa, &file_ace, &dir_ace,
+ &got_file_allow, &got_dir_allow,
+ &all_aces_are_inherit_only, current_ace)) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return false;
+ }
+ }
+
+ if (fsp->fsp_flags.is_directory && all_aces_are_inherit_only) {
+ /*
+ * Windows 2000 is doing one of these weird 'inherit acl'
+ * traverses to conserve NTFS ACL resources. Just pretend
+ * there was no DACL sent. JRA.
+ */
+
+ DEBUG(10,("create_canon_ace_lists: Win2k inherit acl traverse. Ignoring DACL.\n"));
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ file_ace = NULL;
+ dir_ace = NULL;
+ } else {
+ /*
+ * Check if we have SMB_ACL_USER_OBJ and SMB_ACL_GROUP_OBJ entries in
+ * the file ACL. If we don't have them, check if any SMB_ACL_USER/SMB_ACL_GROUP
+ * entries can be converted to *_OBJ. Don't do this for the default
+ * ACL, we will create them separately for this if needed inside
+ * ensure_canon_entry_valid_on_set().
+ */
+ if (file_ace) {
+ check_owning_objs(file_ace, pfile_owner_sid, pfile_grp_sid);
+ }
+ }
+
+ *ppfile_ace = file_ace;
+ *ppdir_ace = dir_ace;
+
+ return True;
+}
+
+/****************************************************************************
+ ASCII art time again... JRA :-).
+
+ We have 4 cases to process when moving from an NT ACL to a POSIX ACL. Firstly,
+ we insist the ACL is in canonical form (ie. all DENY entries precede ALLOW
+ entries). Secondly, the merge code has ensured that all duplicate SID entries for
+ allow or deny have been merged, so the same SID can only appear once in the deny
+ list or once in the allow list.
+
+ We then process as follows :
+
+ ---------------------------------------------------------------------------
+ First pass - look for a Everyone DENY entry.
+
+ If it is deny all (rwx) trunate the list at this point.
+ Else, walk the list from this point and use the deny permissions of this
+ entry as a mask on all following allow entries. Finally, delete
+ the Everyone DENY entry (we have applied it to everything possible).
+
+ In addition, in this pass we remove any DENY entries that have
+ no permissions (ie. they are a DENY nothing).
+ ---------------------------------------------------------------------------
+ Second pass - only deal with deny user entries.
+
+ DENY user1 (perms XXX)
+
+ new_perms = 0
+ for all following allow group entries where user1 is in group
+ new_perms |= group_perms;
+
+ user1 entry perms = new_perms & ~ XXX;
+
+ Convert the deny entry to an allow entry with the new perms and
+ push to the end of the list. Note if the user was in no groups
+ this maps to a specific allow nothing entry for this user.
+
+ The common case from the NT ACL chooser (userX deny all) is
+ optimised so we don't do the group lookup - we just map to
+ an allow nothing entry.
+
+ What we're doing here is inferring the allow permissions the
+ person setting the ACE on user1 wanted by looking at the allow
+ permissions on the groups the user is currently in. This will
+ be a snapshot, depending on group membership but is the best
+ we can do and has the advantage of failing closed rather than
+ open.
+ ---------------------------------------------------------------------------
+ Third pass - only deal with deny group entries.
+
+ DENY group1 (perms XXX)
+
+ for all following allow user entries where user is in group1
+ user entry perms = user entry perms & ~ XXX;
+
+ If there is a group Everyone allow entry with permissions YYY,
+ convert the group1 entry to an allow entry and modify its
+ permissions to be :
+
+ new_perms = YYY & ~ XXX
+
+ and push to the end of the list.
+
+ If there is no group Everyone allow entry then convert the
+ group1 entry to a allow nothing entry and push to the end of the list.
+
+ Note that the common case from the NT ACL chooser (groupX deny all)
+ cannot be optimised here as we need to modify user entries who are
+ in the group to change them to a deny all also.
+
+ What we're doing here is modifying the allow permissions of
+ user entries (which are more specific in POSIX ACLs) to mask
+ out the explicit deny set on the group they are in. This will
+ be a snapshot depending on current group membership but is the
+ best we can do and has the advantage of failing closed rather
+ than open.
+ ---------------------------------------------------------------------------
+ Fourth pass - cope with cumulative permissions.
+
+ for all allow user entries, if there exists an allow group entry with
+ more permissive permissions, and the user is in that group, rewrite the
+ allow user permissions to contain both sets of permissions.
+
+ Currently the code for this is #ifdef'ed out as these semantics make
+ no sense to me. JRA.
+ ---------------------------------------------------------------------------
+
+ Note we *MUST* do the deny user pass first as this will convert deny user
+ entries into allow user entries which can then be processed by the deny
+ group pass.
+
+ The above algorithm took a *lot* of thinking about - hence this
+ explanation :-). JRA.
+****************************************************************************/
+
+/****************************************************************************
+ Process a canon_ace list entries. This is very complex code. We need
+ to go through and remove the "deny" permissions from any allow entry that matches
+ the id of this entry. We have already refused any NT ACL that wasn't in correct
+ order (DENY followed by ALLOW). If any allow entry ends up with zero permissions,
+ we just remove it (to fail safe). We have already removed any duplicate ace
+ entries. Treat an "Everyone" DENY_ACE as a special case - use it to mask all
+ allow entries.
+****************************************************************************/
+
+static void process_deny_list(connection_struct *conn, canon_ace **pp_ace_list )
+{
+ canon_ace *ace_list = *pp_ace_list;
+ canon_ace *curr_ace = NULL;
+ canon_ace *curr_ace_next = NULL;
+
+ /* Pass 1 above - look for an Everyone, deny entry. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->perms == (mode_t)0) {
+
+ /* Deny nothing entry - delete. */
+
+ DLIST_REMOVE(ace_list, curr_ace);
+ continue;
+ }
+
+ if (!dom_sid_equal(&curr_ace->trustee, &global_sid_World))
+ continue;
+
+ /* JRATEST - assert. */
+ SMB_ASSERT(curr_ace->owner_type == WORLD_ACE);
+
+ if (curr_ace->perms == ALL_ACE_PERMS) {
+
+ /*
+ * Optimisation. This is a DENY_ALL to Everyone. Truncate the
+ * list at this point including this entry.
+ */
+
+ canon_ace *prev_entry = DLIST_PREV(curr_ace);
+
+ free_canon_ace_list( curr_ace );
+ if (prev_entry)
+ DLIST_REMOVE(ace_list, prev_entry);
+ else {
+ /* We deleted the entire list. */
+ ace_list = NULL;
+ }
+ break;
+ }
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ /*
+ * Only mask off allow entries.
+ */
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ allow_ace_p->perms &= ~curr_ace->perms;
+ }
+
+ /*
+ * Now it's been applied, remove it.
+ */
+
+ DLIST_REMOVE(ace_list, curr_ace);
+ }
+
+ /* Pass 2 above - deal with deny user entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ mode_t new_perms = (mode_t)0;
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->owner_type != UID_ACE)
+ continue;
+
+ if (curr_ace->perms == ALL_ACE_PERMS) {
+
+ /*
+ * Optimisation - this is a deny everything to this user.
+ * Convert to an allow nothing and push to the end of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ curr_ace->perms = (mode_t)0;
+ DLIST_DEMOTE(ace_list, curr_ace);
+ continue;
+ }
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* We process GID_ACE and WORLD_ACE entries only. */
+
+ if (allow_ace_p->owner_type == UID_ACE)
+ continue;
+
+ if (uid_entry_in_group(conn, curr_ace, allow_ace_p))
+ new_perms |= allow_ace_p->perms;
+ }
+
+ /*
+ * Convert to a allow entry, modify the perms and push to the end
+ * of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ curr_ace->perms = (new_perms & ~curr_ace->perms);
+ DLIST_DEMOTE(ace_list, curr_ace);
+ }
+
+ /* Pass 3 above - deal with deny group entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+ canon_ace *allow_everyone_p = NULL;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != DENY_ACE)
+ continue;
+
+ if (curr_ace->owner_type != GID_ACE)
+ continue;
+
+ for (allow_ace_p = curr_ace->next; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* Store a pointer to the Everyone allow, if it exists. */
+ if (allow_ace_p->owner_type == WORLD_ACE)
+ allow_everyone_p = allow_ace_p;
+
+ /* We process UID_ACE entries only. */
+
+ if (allow_ace_p->owner_type != UID_ACE)
+ continue;
+
+ /* Mask off the deny group perms. */
+
+ if (uid_entry_in_group(conn, allow_ace_p, curr_ace))
+ allow_ace_p->perms &= ~curr_ace->perms;
+ }
+
+ /*
+ * Convert the deny to an allow with the correct perms and
+ * push to the end of the list.
+ */
+
+ curr_ace->attr = ALLOW_ACE;
+ if (allow_everyone_p)
+ curr_ace->perms = allow_everyone_p->perms & ~curr_ace->perms;
+ else
+ curr_ace->perms = (mode_t)0;
+ DLIST_DEMOTE(ace_list, curr_ace);
+ }
+
+ /* Doing this fourth pass allows Windows semantics to be layered
+ * on top of POSIX semantics. I'm not sure if this is desirable.
+ * For example, in W2K ACLs there is no way to say, "Group X no
+ * access, user Y full access" if user Y is a member of group X.
+ * This seems completely broken semantics to me.... JRA.
+ */
+
+#if 0
+ /* Pass 4 above - deal with allow entries. */
+
+ for (curr_ace = ace_list; curr_ace; curr_ace = curr_ace_next) {
+ canon_ace *allow_ace_p;
+
+ curr_ace_next = curr_ace->next; /* So we can't lose the link. */
+
+ if (curr_ace->attr != ALLOW_ACE)
+ continue;
+
+ if (curr_ace->owner_type != UID_ACE)
+ continue;
+
+ for (allow_ace_p = ace_list; allow_ace_p; allow_ace_p = allow_ace_p->next) {
+
+ if (allow_ace_p->attr != ALLOW_ACE)
+ continue;
+
+ /* We process GID_ACE entries only. */
+
+ if (allow_ace_p->owner_type != GID_ACE)
+ continue;
+
+ /* OR in the group perms. */
+
+ if (uid_entry_in_group(conn, curr_ace, allow_ace_p))
+ curr_ace->perms |= allow_ace_p->perms;
+ }
+ }
+#endif
+
+ *pp_ace_list = ace_list;
+}
+
+/****************************************************************************
+ Unpack a struct security_descriptor into two canonical ace lists. We don't depend on this
+ succeeding.
+****************************************************************************/
+
+static bool unpack_canon_ace(files_struct *fsp,
+ const SMB_STRUCT_STAT *pst,
+ struct dom_sid *pfile_owner_sid,
+ struct dom_sid *pfile_grp_sid,
+ canon_ace **ppfile_ace,
+ canon_ace **ppdir_ace,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ bool ok;
+
+ *ppfile_ace = NULL;
+ *ppdir_ace = NULL;
+
+ if(security_info_sent == 0) {
+ DEBUG(0,("unpack_canon_ace: no security info sent !\n"));
+ return False;
+ }
+
+ /*
+ * If no DACL then this is a chown only security descriptor.
+ */
+
+ if(!(security_info_sent & SECINFO_DACL) || !psd->dacl)
+ return True;
+
+ /*
+ * Now go through the DACL and create the canon_ace lists.
+ */
+
+ if (!create_canon_ace_lists(fsp, pst, pfile_owner_sid, pfile_grp_sid,
+ &file_ace, &dir_ace, psd->dacl)) {
+ return False;
+ }
+
+ if ((file_ace == NULL) && (dir_ace == NULL)) {
+ /* W2K traverse DACL set - ignore. */
+ return True;
+ }
+
+ /*
+ * Go through the canon_ace list and merge entries
+ * belonging to identical users of identical allow or deny type.
+ * We can do this as all deny entries come first, followed by
+ * all allow entries (we have mandated this before accepting this acl).
+ */
+
+ print_canon_ace_list( "file ace - before merge", file_ace);
+ merge_aces( &file_ace, false);
+
+ print_canon_ace_list( "dir ace - before merge", dir_ace);
+ merge_aces( &dir_ace, true);
+
+ /*
+ * NT ACLs are order dependent. Go through the acl lists and
+ * process DENY entries by masking the allow entries.
+ */
+
+ print_canon_ace_list( "file ace - before deny", file_ace);
+ process_deny_list(fsp->conn, &file_ace);
+
+ print_canon_ace_list( "dir ace - before deny", dir_ace);
+ process_deny_list(fsp->conn, &dir_ace);
+
+ /*
+ * A well formed POSIX file or default ACL has at least 3 entries, a
+ * SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER_OBJ
+ * and optionally a mask entry. Ensure this is the case.
+ */
+
+ print_canon_ace_list( "file ace - before valid", file_ace);
+
+ ok = ensure_canon_entry_valid_on_set(
+ fsp->conn,
+ &file_ace,
+ false,
+ fsp->conn->params,
+ fsp->fsp_flags.is_directory,
+ pfile_owner_sid,
+ pfile_grp_sid,
+ pst);
+ if (!ok) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+
+ print_canon_ace_list( "dir ace - before valid", dir_ace);
+
+ if (dir_ace != NULL) {
+ ok = ensure_canon_entry_valid_on_set(
+ fsp->conn,
+ &dir_ace,
+ true,
+ fsp->conn->params,
+ fsp->fsp_flags.is_directory,
+ pfile_owner_sid,
+ pfile_grp_sid,
+ pst);
+ if (!ok) {
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ return False;
+ }
+ }
+
+ print_canon_ace_list( "file ace - return", file_ace);
+ print_canon_ace_list( "dir ace - return", dir_ace);
+
+ *ppfile_ace = file_ace;
+ *ppdir_ace = dir_ace;
+ return True;
+
+}
+
+/******************************************************************************
+ When returning permissions, try and fit NT display
+ semantics if possible. Note the the canon_entries here must have been malloced.
+ The list format should be - first entry = owner, followed by group and other user
+ entries, last entry = other.
+
+ Note that this doesn't exactly match the NT semantics for an ACL. As POSIX entries
+ are not ordered, and match on the most specific entry rather than walking a list,
+ then a simple POSIX permission of rw-r--r-- should really map to 5 entries,
+
+ Entry 0: owner : deny all except read and write.
+ Entry 1: owner : allow read and write.
+ Entry 2: group : deny all except read.
+ Entry 3: group : allow read.
+ Entry 4: Everyone : allow read.
+
+ But NT cannot display this in their ACL editor !
+********************************************************************************/
+
+static void arrange_posix_perms(const char *filename, canon_ace **pp_list_head)
+{
+ canon_ace *l_head = *pp_list_head;
+ canon_ace *owner_ace = NULL;
+ canon_ace *other_ace = NULL;
+ canon_ace *ace = NULL;
+
+ for (ace = l_head; ace; ace = ace->next) {
+ if (ace->type == SMB_ACL_USER_OBJ)
+ owner_ace = ace;
+ else if (ace->type == SMB_ACL_OTHER) {
+ /* Last ace - this is "other" */
+ other_ace = ace;
+ }
+ }
+
+ if (!owner_ace || !other_ace) {
+ DEBUG(0,("arrange_posix_perms: Invalid POSIX permissions for file %s, missing owner or other.\n",
+ filename ));
+ return;
+ }
+
+ /*
+ * The POSIX algorithm applies to owner first, and other last,
+ * so ensure they are arranged in this order.
+ */
+
+ if (owner_ace) {
+ DLIST_PROMOTE(l_head, owner_ace);
+ }
+
+ if (other_ace) {
+ DLIST_DEMOTE(l_head, other_ace);
+ }
+
+ /* We have probably changed the head of the list. */
+
+ *pp_list_head = l_head;
+}
+
+/****************************************************************************
+ Create a linked list of canonical ACE entries.
+****************************************************************************/
+
+static canon_ace *canonicalise_acl(struct connection_struct *conn,
+ const char *fname, SMB_ACL_T posix_acl,
+ const SMB_STRUCT_STAT *psbuf,
+ const struct dom_sid *powner, const struct dom_sid *pgroup, struct pai_val *pal, SMB_ACL_TYPE_T the_acl_type)
+{
+ mode_t acl_mask = (S_IRUSR|S_IWUSR|S_IXUSR);
+ canon_ace *l_head = NULL;
+ canon_ace *ace = NULL;
+ canon_ace *next_ace = NULL;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ bool is_default_acl = (the_acl_type == SMB_ACL_TYPE_DEFAULT);
+ SMB_ACL_ENTRY_T entry;
+ size_t ace_count;
+
+ while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ struct dom_sid sid;
+ struct unixid unix_ug;
+ enum ace_owner owner_type;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) == -1)
+ continue;
+
+ if (sys_acl_get_permset(entry, &permset) == -1)
+ continue;
+
+ /* Decide which SID to use based on the ACL type. */
+ switch(tagtype) {
+ case SMB_ACL_USER_OBJ:
+ /* Get the SID from the owner. */
+ sid_copy(&sid, powner);
+ unix_ug.type = ID_TYPE_UID;
+ unix_ug.id = psbuf->st_ex_uid;
+ owner_type = UID_ACE;
+ break;
+ case SMB_ACL_USER:
+ {
+ uid_t *puid = (uid_t *)sys_acl_get_qualifier(entry);
+ if (puid == NULL) {
+ DEBUG(0,("canonicalise_acl: Failed to get uid.\n"));
+ continue;
+ }
+ uid_to_sid( &sid, *puid);
+ unix_ug.type = ID_TYPE_UID;
+ unix_ug.id = *puid;
+ owner_type = UID_ACE;
+ break;
+ }
+ case SMB_ACL_GROUP_OBJ:
+ /* Get the SID from the owning group. */
+ sid_copy(&sid, pgroup);
+ unix_ug.type = ID_TYPE_GID;
+ unix_ug.id = psbuf->st_ex_gid;
+ owner_type = GID_ACE;
+ break;
+ case SMB_ACL_GROUP:
+ {
+ gid_t *pgid = (gid_t *)sys_acl_get_qualifier(entry);
+ if (pgid == NULL) {
+ DEBUG(0,("canonicalise_acl: Failed to get gid.\n"));
+ continue;
+ }
+ gid_to_sid( &sid, *pgid);
+ unix_ug.type = ID_TYPE_GID;
+ unix_ug.id = *pgid;
+ owner_type = GID_ACE;
+ break;
+ }
+ case SMB_ACL_MASK:
+ acl_mask = convert_permset_to_mode_t(permset);
+ continue; /* Don't count the mask as an entry. */
+ case SMB_ACL_OTHER:
+ /* Use the Everyone SID */
+ sid = global_sid_World;
+ unix_ug.type = ID_TYPE_NOT_SPECIFIED;
+ unix_ug.id = -1;
+ owner_type = WORLD_ACE;
+ break;
+ default:
+ DEBUG(0,("canonicalise_acl: Unknown tagtype %u\n", (unsigned int)tagtype));
+ continue;
+ }
+
+ /*
+ * Add this entry to the list.
+ */
+
+ if ((ace = talloc(talloc_tos(), canon_ace)) == NULL)
+ goto fail;
+
+ *ace = (canon_ace) {
+ .type = tagtype,
+ .perms = convert_permset_to_mode_t(permset),
+ .attr = ALLOW_ACE,
+ .trustee = sid,
+ .unix_ug = unix_ug,
+ .owner_type = owner_type
+ };
+ ace->ace_flags = get_pai_flags(pal, ace, is_default_acl);
+
+ DLIST_ADD(l_head, ace);
+ }
+
+ /*
+ * This next call will ensure we have at least a user/group/world set.
+ */
+
+ if (!ensure_canon_entry_valid_on_get(conn, &l_head,
+ powner, pgroup,
+ psbuf))
+ goto fail;
+
+ /*
+ * Now go through the list, masking the permissions with the
+ * acl_mask. Ensure all DENY Entries are at the start of the list.
+ */
+
+ DEBUG(10,("canonicalise_acl: %s ace entries before arrange :\n", is_default_acl ? "Default" : "Access"));
+
+ for ( ace_count = 0, ace = l_head; ace; ace = next_ace, ace_count++) {
+ next_ace = ace->next;
+
+ /* Masks are only applied to entries other than USER_OBJ and OTHER. */
+ if (ace->type != SMB_ACL_OTHER && ace->type != SMB_ACL_USER_OBJ)
+ ace->perms &= acl_mask;
+
+ if (ace->perms == 0) {
+ DLIST_PROMOTE(l_head, ace);
+ }
+
+ if( DEBUGLVL( 10 ) ) {
+ print_canon_ace(ace, ace_count);
+ }
+ }
+
+ arrange_posix_perms(fname,&l_head );
+
+ print_canon_ace_list( "canonicalise_acl: ace entries after arrange", l_head );
+
+ return l_head;
+
+ fail:
+
+ free_canon_ace_list(l_head);
+ return NULL;
+}
+
+/****************************************************************************
+ Check if the current user group list contains a given group.
+****************************************************************************/
+
+bool current_user_in_group(connection_struct *conn, gid_t gid)
+{
+ uint32_t i;
+ const struct security_unix_token *utok = get_current_utok(conn);
+
+ for (i = 0; i < utok->ngroups; i++) {
+ if (utok->groups[i] == gid) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+/****************************************************************************
+ Should we override a deny ? Check 'acl group control' and 'dos filemode'.
+****************************************************************************/
+
+static bool acl_group_override_fsp(files_struct *fsp)
+{
+ if ((errno != EPERM) && (errno != EACCES)) {
+ return false;
+ }
+
+ /* file primary group == user primary or supplementary group */
+ if (lp_acl_group_control(SNUM(fsp->conn)) &&
+ current_user_in_group(fsp->conn, fsp->fsp_name->st.st_ex_gid)) {
+ return true;
+ }
+
+ /* user has writeable permission */
+ if (lp_dos_filemode(SNUM(fsp->conn)) && can_write_to_fsp(fsp)) {
+ return true;
+ }
+
+ return false;
+}
+
+/****************************************************************************
+ Attempt to apply an ACL to a file or directory.
+****************************************************************************/
+
+static bool set_canon_ace_list(files_struct *fsp,
+ canon_ace *the_ace,
+ bool default_ace,
+ const SMB_STRUCT_STAT *psbuf,
+ bool *pacl_set_support)
+{
+ bool ret = False;
+ SMB_ACL_T the_acl = sys_acl_init(talloc_tos());
+ canon_ace *p_ace;
+ int i;
+ SMB_ACL_ENTRY_T mask_entry;
+ bool got_mask_entry = False;
+ SMB_ACL_PERMSET_T mask_permset;
+ SMB_ACL_TYPE_T the_acl_type = (default_ace ? SMB_ACL_TYPE_DEFAULT : SMB_ACL_TYPE_ACCESS);
+ bool needs_mask = False;
+ int sret;
+
+ /* Use the psbuf that was passed in. */
+ if (psbuf != &fsp->fsp_name->st) {
+ fsp->fsp_name->st = *psbuf;
+ }
+
+#if defined(POSIX_ACL_NEEDS_MASK)
+ /* HP-UX always wants to have a mask (called "class" there). */
+ needs_mask = True;
+#endif
+
+ if (the_acl == NULL) {
+ DEBUG(0, ("sys_acl_init failed to allocate an ACL\n"));
+ return false;
+ }
+
+ if( DEBUGLVL( 10 )) {
+ dbgtext("set_canon_ace_list: setting ACL:\n");
+ for (i = 0, p_ace = the_ace; p_ace; p_ace = p_ace->next, i++ ) {
+ print_canon_ace( p_ace, i);
+ }
+ }
+
+ for (i = 0, p_ace = the_ace; p_ace; p_ace = p_ace->next, i++ ) {
+ SMB_ACL_ENTRY_T the_entry;
+ SMB_ACL_PERMSET_T the_permset;
+
+ /*
+ * ACLs only "need" an ACL_MASK entry if there are any named user or
+ * named group entries. But if there is an ACL_MASK entry, it applies
+ * to ACL_USER, ACL_GROUP, and ACL_GROUP_OBJ entries. Set the mask
+ * so that it doesn't deny (i.e., mask off) any permissions.
+ */
+
+ if (p_ace->type == SMB_ACL_USER || p_ace->type == SMB_ACL_GROUP) {
+ needs_mask = True;
+ }
+
+ /*
+ * Get the entry for this ACE.
+ */
+
+ if (sys_acl_create_entry(&the_acl, &the_entry) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (p_ace->type == SMB_ACL_MASK) {
+ mask_entry = the_entry;
+ got_mask_entry = True;
+ }
+
+ /*
+ * Ok - we now know the ACL calls should be working, don't
+ * allow fallback to chmod.
+ */
+
+ *pacl_set_support = True;
+
+ /*
+ * Initialise the entry from the canon_ace.
+ */
+
+ /*
+ * First tell the entry what type of ACE this is.
+ */
+
+ if (sys_acl_set_tag_type(the_entry, p_ace->type) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set tag type on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /*
+ * Only set the qualifier (user or group id) if the entry is a user
+ * or group id ACE.
+ */
+
+ if ((p_ace->type == SMB_ACL_USER) || (p_ace->type == SMB_ACL_GROUP)) {
+ if (sys_acl_set_qualifier(the_entry,(void *)&p_ace->unix_ug.id) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set qualifier on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ /*
+ * Convert the mode_t perms in the canon_ace to a POSIX permset.
+ */
+
+ if (sys_acl_get_permset(the_entry, &the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to get permset on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (map_acl_perms_to_permset(p_ace->perms, &the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create permset for mode (%u) on entry %d. (%s)\n",
+ (unsigned int)p_ace->perms, i, strerror(errno) ));
+ goto fail;
+ }
+
+ /*
+ * ..and apply them to the entry.
+ */
+
+ if (sys_acl_set_permset(the_entry, the_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to add permset on entry %d. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if( DEBUGLVL( 10 ))
+ print_canon_ace( p_ace, i);
+
+ }
+
+ if (needs_mask && !got_mask_entry) {
+ if (sys_acl_create_entry(&the_acl, &mask_entry) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create mask entry. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (sys_acl_set_tag_type(mask_entry, SMB_ACL_MASK) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to set tag type on mask entry. (%s)\n",strerror(errno) ));
+ goto fail;
+ }
+
+ if (sys_acl_get_permset(mask_entry, &mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to get mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (map_acl_perms_to_permset(S_IRUSR|S_IWUSR|S_IXUSR, &mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to create mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+
+ if (sys_acl_set_permset(mask_entry, mask_permset) == -1) {
+ DEBUG(0,("set_canon_ace_list: Failed to add mask permset. (%s)\n", strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ /*
+ * Finally apply it to the file or directory.
+ */
+ sret = SMB_VFS_SYS_ACL_SET_FD(fsp, the_acl_type, the_acl);
+ if (sret == -1) {
+ /*
+ * Some systems allow all the above calls and only fail with no ACL support
+ * when attempting to apply the acl. HPUX with HFS is an example of this. JRA.
+ */
+ if (no_acl_syscall_error(errno)) {
+ *pacl_set_support = false;
+ }
+
+ if (acl_group_override_fsp(fsp)) {
+ DBG_DEBUG("acl group control on and current user in "
+ "file [%s] primary group.\n",
+ fsp_str_dbg(fsp));
+
+ become_root();
+ sret = SMB_VFS_SYS_ACL_SET_FD(fsp,
+ the_acl_type,
+ the_acl);
+ unbecome_root();
+ if (sret == 0) {
+ ret = true;
+ }
+ }
+
+ if (ret == false) {
+ DBG_WARNING("sys_acl_set_file on file [%s]: (%s)\n",
+ fsp_str_dbg(fsp), strerror(errno));
+ goto fail;
+ }
+ }
+
+ ret = True;
+
+ fail:
+
+ if (the_acl != NULL) {
+ TALLOC_FREE(the_acl);
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+
+****************************************************************************/
+
+SMB_ACL_T free_empty_sys_acl(connection_struct *conn, SMB_ACL_T the_acl)
+{
+ SMB_ACL_ENTRY_T entry;
+
+ if (!the_acl)
+ return NULL;
+ if (sys_acl_get_entry(the_acl, SMB_ACL_FIRST_ENTRY, &entry) != 1) {
+ TALLOC_FREE(the_acl);
+ return NULL;
+ }
+ return the_acl;
+}
+
+/****************************************************************************
+ Convert a canon_ace to a generic 3 element permission - if possible.
+****************************************************************************/
+
+#define MAP_PERM(p,mask,result) (((p) & (mask)) ? (result) : 0 )
+
+static bool convert_canon_ace_to_posix_perms( files_struct *fsp, canon_ace *file_ace_list, mode_t *posix_perms)
+{
+ size_t ace_count = count_canon_ace_list(file_ace_list);
+ canon_ace *ace_p;
+ canon_ace *owner_ace = NULL;
+ canon_ace *group_ace = NULL;
+ canon_ace *other_ace = NULL;
+
+ if (ace_count > 5) {
+ DEBUG(3,("convert_canon_ace_to_posix_perms: Too many ACE "
+ "entries for file %s to convert to posix perms.\n",
+ fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ for (ace_p = file_ace_list; ace_p; ace_p = ace_p->next) {
+ if (ace_p->owner_type == UID_ACE)
+ owner_ace = ace_p;
+ else if (ace_p->owner_type == GID_ACE)
+ group_ace = ace_p;
+ else if (ace_p->owner_type == WORLD_ACE)
+ other_ace = ace_p;
+ }
+
+ if (!owner_ace || !group_ace || !other_ace) {
+ DEBUG(3,("convert_canon_ace_to_posix_perms: Can't get "
+ "standard entries for file %s.\n", fsp_str_dbg(fsp)));
+ return False;
+ }
+
+ /*
+ * Ensure all ACE entries are owner, group or other.
+ * We can't set if there are any other SIDs.
+ */
+ for (ace_p = file_ace_list; ace_p; ace_p = ace_p->next) {
+ if (ace_p == owner_ace || ace_p == group_ace ||
+ ace_p == other_ace) {
+ continue;
+ }
+ if (ace_p->owner_type == UID_ACE) {
+ if (ace_p->unix_ug.id != owner_ace->unix_ug.id) {
+ DEBUG(3,("Invalid uid %u in ACE for file %s.\n",
+ (unsigned int)ace_p->unix_ug.id,
+ fsp_str_dbg(fsp)));
+ return false;
+ }
+ } else if (ace_p->owner_type == GID_ACE) {
+ if (ace_p->unix_ug.id != group_ace->unix_ug.id) {
+ DEBUG(3,("Invalid gid %u in ACE for file %s.\n",
+ (unsigned int)ace_p->unix_ug.id,
+ fsp_str_dbg(fsp)));
+ return false;
+ }
+ } else {
+ /*
+ * There should be no duplicate WORLD_ACE entries.
+ */
+
+ DEBUG(3,("Invalid type %u, uid %u in "
+ "ACE for file %s.\n",
+ (unsigned int)ace_p->owner_type,
+ (unsigned int)ace_p->unix_ug.id,
+ fsp_str_dbg(fsp)));
+ return false;
+ }
+ }
+
+ *posix_perms = (mode_t)0;
+
+ *posix_perms |= owner_ace->perms;
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IRUSR, S_IRGRP);
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IWUSR, S_IWGRP);
+ *posix_perms |= MAP_PERM(group_ace->perms, S_IXUSR, S_IXGRP);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IRUSR, S_IROTH);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IWUSR, S_IWOTH);
+ *posix_perms |= MAP_PERM(other_ace->perms, S_IXUSR, S_IXOTH);
+
+ /* The owner must have at least read access. */
+
+ *posix_perms |= S_IRUSR;
+ if (fsp->fsp_flags.is_directory)
+ *posix_perms |= (S_IWUSR|S_IXUSR);
+
+ DEBUG(10,("convert_canon_ace_to_posix_perms: converted u=%o,g=%o,w=%o "
+ "to perm=0%o for file %s.\n", (int)owner_ace->perms,
+ (int)group_ace->perms, (int)other_ace->perms,
+ (int)*posix_perms, fsp_str_dbg(fsp)));
+
+ return True;
+}
+
+/****************************************************************************
+ Incoming NT ACLs on a directory can be split into a default POSIX acl (CI|OI|IO) and
+ a normal POSIX acl. Win2k needs these split acls re-merging into one ACL
+ with CI|OI set so it is inherited and also applies to the directory.
+ Based on code from "Jim McDonough" <jmcd@us.ibm.com>.
+****************************************************************************/
+
+static size_t merge_default_aces( struct security_ace *nt_ace_list, size_t num_aces)
+{
+ size_t i, j;
+
+ for (i = 0; i < num_aces; i++) {
+ for (j = i+1; j < num_aces; j++) {
+ uint32_t i_flags_ni = (nt_ace_list[i].flags & ~SEC_ACE_FLAG_INHERITED_ACE);
+ uint32_t j_flags_ni = (nt_ace_list[j].flags & ~SEC_ACE_FLAG_INHERITED_ACE);
+ bool i_inh = (nt_ace_list[i].flags & SEC_ACE_FLAG_INHERITED_ACE) ? True : False;
+ bool j_inh = (nt_ace_list[j].flags & SEC_ACE_FLAG_INHERITED_ACE) ? True : False;
+
+ /* We know the lower number ACE's are file entries. */
+ if ((nt_ace_list[i].type == nt_ace_list[j].type) &&
+ (nt_ace_list[i].size == nt_ace_list[j].size) &&
+ (nt_ace_list[i].access_mask == nt_ace_list[j].access_mask) &&
+ dom_sid_equal(&nt_ace_list[i].trustee, &nt_ace_list[j].trustee) &&
+ (i_inh == j_inh) &&
+ (i_flags_ni == 0) &&
+ (j_flags_ni == (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY))) {
+ /*
+ * W2K wants to have access allowed zero access ACE's
+ * at the end of the list. If the mask is zero, merge
+ * the non-inherited ACE onto the inherited ACE.
+ */
+
+ if (nt_ace_list[i].access_mask == 0) {
+ nt_ace_list[j].flags = SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ (i_inh ? SEC_ACE_FLAG_INHERITED_ACE : 0);
+ ARRAY_DEL_ELEMENT(nt_ace_list, i, num_aces);
+
+ DEBUG(10,("merge_default_aces: Merging zero access ACE %u onto ACE %u.\n",
+ (unsigned int)i, (unsigned int)j ));
+
+ /*
+ * If we remove the i'th element, we
+ * should decrement i so that we don't
+ * skip over the succeeding element.
+ */
+ i--;
+ num_aces--;
+ break;
+ } else {
+ /*
+ * These are identical except for the flags.
+ * Merge the inherited ACE onto the non-inherited ACE.
+ */
+
+ nt_ace_list[i].flags = SEC_ACE_FLAG_OBJECT_INHERIT|SEC_ACE_FLAG_CONTAINER_INHERIT|
+ (i_inh ? SEC_ACE_FLAG_INHERITED_ACE : 0);
+ ARRAY_DEL_ELEMENT(nt_ace_list, j, num_aces);
+
+ DEBUG(10,("merge_default_aces: Merging ACE %u onto ACE %u.\n",
+ (unsigned int)j, (unsigned int)i ));
+
+ /*
+ * If we remove the j'th element, we
+ * should decrement j and continue
+ * around the loop, so as not to skip
+ * subsequent elements.
+ */
+ j--;
+ num_aces--;
+ }
+ }
+ }
+ }
+
+ return num_aces;
+}
+
+
+/****************************************************************************
+ Reply to query a security descriptor from an fsp. If it succeeds it allocates
+ the space for the return elements and returns the size needed to return the
+ security descriptor. This should be the only external function needed for
+ the UNIX style get ACL.
+****************************************************************************/
+
+static NTSTATUS posix_get_nt_acl_common(struct connection_struct *conn,
+ const char *name,
+ const SMB_STRUCT_STAT *sbuf,
+ struct pai_val *pal,
+ SMB_ACL_T posix_acl,
+ SMB_ACL_T def_acl,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid;
+ struct dom_sid group_sid;
+ size_t sd_size = 0;
+ struct security_acl *psa = NULL;
+ size_t num_acls = 0;
+ size_t num_def_acls = 0;
+ size_t num_aces = 0;
+ canon_ace *file_ace = NULL;
+ canon_ace *dir_ace = NULL;
+ struct security_ace *nt_ace_list = NULL;
+ struct security_descriptor *psd = NULL;
+
+ /*
+ * Get the owner, group and world SIDs.
+ */
+
+ create_file_sids(sbuf, &owner_sid, &group_sid);
+
+ if (security_info & SECINFO_DACL) {
+
+ /*
+ * In the optimum case Creator Owner and Creator Group would be used for
+ * the ACL_USER_OBJ and ACL_GROUP_OBJ entries, respectively, but this
+ * would lead to usability problems under Windows: The Creator entries
+ * are only available in browse lists of directories and not for files;
+ * additionally the identity of the owning group couldn't be determined.
+ * We therefore use those identities only for Default ACLs.
+ */
+
+ /* Create the canon_ace lists. */
+ file_ace = canonicalise_acl(conn, name, posix_acl, sbuf,
+ &owner_sid, &group_sid, pal,
+ SMB_ACL_TYPE_ACCESS);
+
+ /* We must have *some* ACLS. */
+
+ if (count_canon_ace_list(file_ace) == 0) {
+ DEBUG(0,("get_nt_acl : No ACLs on file (%s) !\n", name));
+ goto done;
+ }
+
+ if (S_ISDIR(sbuf->st_ex_mode) && def_acl) {
+ dir_ace = canonicalise_acl(conn, name, def_acl,
+ sbuf,
+ &global_sid_Creator_Owner,
+ &global_sid_Creator_Group,
+ pal, SMB_ACL_TYPE_DEFAULT);
+ }
+
+ /*
+ * Create the NT ACE list from the canonical ace lists.
+ */
+
+ {
+ canon_ace *ace;
+ enum security_ace_type nt_acl_type;
+
+ num_acls = count_canon_ace_list(file_ace);
+ num_def_acls = count_canon_ace_list(dir_ace);
+
+ nt_ace_list = talloc_zero_array(
+ talloc_tos(), struct security_ace,
+ num_acls + num_def_acls);
+
+ if (nt_ace_list == NULL) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for nt_ace_list.\n"));
+ goto done;
+ }
+
+ /*
+ * Create the NT ACE list from the canonical ace lists.
+ */
+
+ for (ace = file_ace; ace != NULL; ace = ace->next) {
+ uint32_t acc = map_canon_ace_perms(SNUM(conn),
+ &nt_acl_type,
+ ace->perms,
+ S_ISDIR(sbuf->st_ex_mode));
+ init_sec_ace(&nt_ace_list[num_aces++],
+ &ace->trustee,
+ nt_acl_type,
+ acc,
+ ace->ace_flags);
+ }
+
+ for (ace = dir_ace; ace != NULL; ace = ace->next) {
+ uint32_t acc = map_canon_ace_perms(SNUM(conn),
+ &nt_acl_type,
+ ace->perms,
+ S_ISDIR(sbuf->st_ex_mode));
+ init_sec_ace(&nt_ace_list[num_aces++],
+ &ace->trustee,
+ nt_acl_type,
+ acc,
+ ace->ace_flags |
+ SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT|
+ SEC_ACE_FLAG_INHERIT_ONLY);
+ }
+
+ /*
+ * Merge POSIX default ACLs and normal ACLs into one NT ACE.
+ * Win2K needs this to get the inheritance correct when replacing ACLs
+ * on a directory tree. Based on work by Jim @ IBM.
+ */
+
+ num_aces = merge_default_aces(nt_ace_list, num_aces);
+ }
+
+ if (num_aces) {
+ if((psa = make_sec_acl( talloc_tos(), NT4_ACL_REVISION, num_aces, nt_ace_list)) == NULL) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for acl.\n"));
+ goto done;
+ }
+ }
+ } /* security_info & SECINFO_DACL */
+
+ psd = make_standard_sec_desc(mem_ctx,
+ (security_info & SECINFO_OWNER) ? &owner_sid : NULL,
+ (security_info & SECINFO_GROUP) ? &group_sid : NULL,
+ psa,
+ &sd_size);
+
+ if(!psd) {
+ DEBUG(0,("get_nt_acl: Unable to malloc space for security descriptor.\n"));
+ sd_size = 0;
+ goto done;
+ }
+
+ /*
+ * Windows 2000: The DACL_PROTECTED flag in the security
+ * descriptor marks the ACL as non-inheriting, i.e., no
+ * ACEs from higher level directories propagate to this
+ * ACL. In the POSIX ACL model permissions are only
+ * inherited at file create time, so ACLs never contain
+ * any ACEs that are inherited dynamically. The DACL_PROTECTED
+ * flag doesn't seem to bother Windows NT.
+ * Always set this if map acl inherit is turned off.
+ */
+ if (pal == NULL || !lp_map_acl_inherit(SNUM(conn))) {
+ psd->type |= SEC_DESC_DACL_PROTECTED;
+ } else {
+ psd->type |= pal->sd_type;
+ }
+
+ if (psd->dacl) {
+ dacl_sort_into_canonical_order(psd->dacl->aces, (unsigned int)psd->dacl->num_aces);
+ }
+
+ *ppdesc = psd;
+
+ done:
+
+ if (posix_acl) {
+ TALLOC_FREE(posix_acl);
+ }
+ if (def_acl) {
+ TALLOC_FREE(def_acl);
+ }
+ free_canon_ace_list(file_ace);
+ free_canon_ace_list(dir_ace);
+ free_inherited_info(pal);
+ TALLOC_FREE(nt_ace_list);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS posix_fget_nt_acl(struct files_struct *fsp, uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ SMB_STRUCT_STAT sbuf;
+ SMB_ACL_T posix_acl = NULL;
+ SMB_ACL_T def_acl = NULL;
+ struct pai_val *pal;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ *ppdesc = NULL;
+
+ DEBUG(10,("posix_fget_nt_acl: called for file %s\n",
+ fsp_str_dbg(fsp)));
+
+ /* Get the stat struct for the owner info. */
+ if(SMB_VFS_FSTAT(fsp, &sbuf) != 0) {
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* Get the ACL from the fd. */
+ posix_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_ACCESS,
+ frame);
+
+ /* If it's a directory get the default POSIX ACL. */
+ if(fsp->fsp_flags.is_directory) {
+ def_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ frame);
+ def_acl = free_empty_sys_acl(fsp->conn, def_acl);
+ }
+
+ pal = fload_inherited_info(fsp);
+
+ status = posix_get_nt_acl_common(fsp->conn, fsp->fsp_name->base_name,
+ &sbuf, pal, posix_acl, def_acl,
+ security_info, mem_ctx, ppdesc);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/****************************************************************************
+ Try to chown a file. We will be able to chown it under the following conditions.
+
+ 1) If we have root privileges, then it will just work.
+ 2) If we have SeRestorePrivilege we can change the user + group to any other user.
+ 3) If we have SeTakeOwnershipPrivilege we can change the user to the current user.
+ 4) If we have write permission to the file and dos_filemodes is set
+ then allow chown to the currently authenticated user.
+****************************************************************************/
+
+static NTSTATUS try_chown(files_struct *fsp, uid_t uid, gid_t gid)
+{
+ NTSTATUS status;
+ int ret;
+
+ if(!CAN_WRITE(fsp->conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ /* Case (1). */
+ ret = SMB_VFS_FCHOWN(fsp, uid, gid);
+ if (ret == 0) {
+ return NT_STATUS_OK;
+ }
+
+ /* Case (2) / (3) */
+ if (lp_enable_privileges()) {
+ bool has_take_ownership_priv = security_token_has_privilege(
+ get_current_nttok(fsp->conn),
+ SEC_PRIV_TAKE_OWNERSHIP);
+ bool has_restore_priv = security_token_has_privilege(
+ get_current_nttok(fsp->conn),
+ SEC_PRIV_RESTORE);
+
+ if (has_restore_priv) {
+ ; /* Case (2) */
+ } else if (has_take_ownership_priv) {
+ /* Case (3) */
+ if (uid == get_current_uid(fsp->conn)) {
+ gid = (gid_t)-1;
+ } else {
+ has_take_ownership_priv = false;
+ }
+ }
+
+ if (has_take_ownership_priv || has_restore_priv) {
+ status = NT_STATUS_OK;
+ become_root();
+ ret = SMB_VFS_FCHOWN(fsp, uid, gid);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+ unbecome_root();
+ return status;
+ }
+ }
+
+ /* Case (4). */
+ /* If "dos filemode" isn't set, we're done. */
+ if (!lp_dos_filemode(SNUM(fsp->conn))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ /*
+ * If we have a writable handle, obviously we
+ * can write to the file.
+ */
+ if (!fsp->fsp_flags.can_write) {
+ /*
+ * If we don't have a writable handle, we
+ * need to read the ACL on the file to
+ * see if we can write to it.
+ */
+ if (!can_write_to_fsp(fsp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ /* only allow chown to the current user. This is more secure,
+ and also copes with the case where the SID in a take ownership ACL is
+ a local SID on the users workstation
+ */
+ if (uid != get_current_uid(fsp->conn)) {
+ return NT_STATUS_INVALID_OWNER;
+ }
+
+ status = NT_STATUS_OK;
+ become_root();
+ /* Keep the current file gid the same. */
+ ret = SMB_VFS_FCHOWN(fsp, uid, (gid_t)-1);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ }
+ unbecome_root();
+
+ return status;
+}
+
+/*
+ * Check whether a chown is needed and if so, attempt the chown
+ * A returned error indicates that the chown failed.
+ * NT_STATUS_OK with did_chown == false indicates that the chown was skipped.
+ * NT_STATUS_OK with did_chown == true indicates that the chown succeeded
+ */
+NTSTATUS chown_if_needed(files_struct *fsp, uint32_t security_info_sent,
+ const struct security_descriptor *psd,
+ bool *did_chown)
+{
+ NTSTATUS status;
+ uid_t uid = (uid_t)-1;
+ gid_t gid = (gid_t)-1;
+
+ status = unpack_nt_owners(fsp->conn, &uid, &gid, security_info_sent, psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (((uid == (uid_t)-1) || (fsp->fsp_name->st.st_ex_uid == uid)) &&
+ ((gid == (gid_t)-1) || (fsp->fsp_name->st.st_ex_gid == gid))) {
+ /*
+ * Skip chown
+ */
+ *did_chown = false;
+ return NT_STATUS_OK;
+ }
+
+ DBG_NOTICE("chown %s. uid = %u, gid = %u.\n",
+ fsp_str_dbg(fsp), (unsigned int) uid, (unsigned int)gid);
+
+ status = try_chown(fsp, uid, gid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("chown %s, %u, %u failed. Error = %s.\n",
+ fsp_str_dbg(fsp), (unsigned int) uid,
+ (unsigned int)gid, nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * Recheck the current state of the file, which may have changed.
+ * (owner and suid/sgid bits, for instance)
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *did_chown = true;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to set a security descriptor on an fsp. security_info_sent is the
+ description of the following NT ACL.
+ This should be the only external function needed for the UNIX style set ACL.
+ We make a copy of psd_orig as internal functions modify the elements inside
+ it, even though it's a const pointer.
+****************************************************************************/
+
+NTSTATUS set_nt_acl(files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd_orig)
+{
+ connection_struct *conn = fsp->conn;
+ struct dom_sid file_owner_sid;
+ struct dom_sid file_grp_sid;
+ canon_ace *file_ace_list = NULL;
+ canon_ace *dir_ace_list = NULL;
+ bool acl_perms = False;
+ mode_t orig_mode = (mode_t)0;
+ NTSTATUS status;
+ bool set_acl_as_root = false;
+ bool acl_set_support = false;
+ bool ret = false;
+ struct security_descriptor *psd = NULL;
+
+ DEBUG(10,("set_nt_acl: called for file %s\n",
+ fsp_str_dbg(fsp)));
+
+ if (!CAN_WRITE(conn)) {
+ DEBUG(10,("set acl rejected on read-only share\n"));
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ if (psd_orig == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * MS NFS mode, here's the deal: the client merely wants to
+ * modify the mode, but roundtripping get_acl/set/acl would
+ * add additional POSIX ACEs. So in case we get a request
+ * containing a MS NFS mode SID, we do nothing here.
+ */
+ if (security_descriptor_with_ms_nfs(psd_orig)) {
+ return NT_STATUS_OK;
+ }
+
+ psd = security_descriptor_copy(talloc_tos(), psd_orig);
+ if (psd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Get the current state of the file.
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Save the original element we check against. */
+ orig_mode = fsp->fsp_name->st.st_ex_mode;
+
+ /*
+ * Unpack the user/group/world id's.
+ */
+
+ /* POSIX can't cope with missing owner/group. */
+ if ((security_info_sent & SECINFO_OWNER) && (psd->owner_sid == NULL)) {
+ security_info_sent &= ~SECINFO_OWNER;
+ }
+ if ((security_info_sent & SECINFO_GROUP) && (psd->group_sid == NULL)) {
+ security_info_sent &= ~SECINFO_GROUP;
+ }
+
+ /* If UNIX owner is inherited and Windows isn't, then
+ * setting the UNIX owner based on Windows owner conflicts
+ * with the inheritance rule
+ */
+ if (lp_inherit_owner(SNUM(conn)) == INHERIT_OWNER_UNIX_ONLY) {
+ security_info_sent &= ~SECINFO_OWNER;
+ }
+
+ /*
+ * Do we need to chown ? If so this must be done first as the incoming
+ * CREATOR_OWNER acl will be relative to the *new* owner, not the old.
+ * Noticed by Simo.
+ *
+ * If we successfully chowned, we know we must be able to set
+ * the acl, so do it as root (set_acl_as_root).
+ */
+ status = chown_if_needed(fsp, security_info_sent, psd, &set_acl_as_root);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ create_file_sids(&fsp->fsp_name->st, &file_owner_sid, &file_grp_sid);
+
+ if((security_info_sent & SECINFO_DACL) &&
+ (psd->type & SEC_DESC_DACL_PRESENT) &&
+ (psd->dacl == NULL)) {
+ struct security_ace ace[3];
+
+ /* We can't have NULL DACL in POSIX.
+ Use owner/group/Everyone -> full access. */
+
+ init_sec_ace(&ace[0],
+ &file_owner_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ GENERIC_ALL_ACCESS,
+ 0);
+ init_sec_ace(&ace[1],
+ &file_grp_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ GENERIC_ALL_ACCESS,
+ 0);
+ init_sec_ace(&ace[2],
+ &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ GENERIC_ALL_ACCESS,
+ 0);
+ psd->dacl = make_sec_acl(talloc_tos(),
+ NT4_ACL_REVISION,
+ 3,
+ ace);
+ if (psd->dacl == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ security_acl_map_generic(psd->dacl, &file_generic_mapping);
+ }
+
+ acl_perms = unpack_canon_ace(fsp, &fsp->fsp_name->st, &file_owner_sid,
+ &file_grp_sid, &file_ace_list,
+ &dir_ace_list, security_info_sent, psd);
+
+ /* Ignore W2K traverse DACL set. */
+ if (!file_ace_list && !dir_ace_list) {
+ return NT_STATUS_OK;
+ }
+
+ if (!acl_perms) {
+ DEBUG(3,("set_nt_acl: cannot set permissions\n"));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /*
+ * Only change security if we got a DACL.
+ */
+
+ if(!(security_info_sent & SECINFO_DACL) || (psd->dacl == NULL)) {
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Try using the POSIX ACL set first. Fall back to chmod if
+ * we have no ACL support on this filesystem.
+ */
+
+ if (acl_perms && file_ace_list) {
+ if (set_acl_as_root) {
+ become_root();
+ }
+ ret = set_canon_ace_list(fsp, file_ace_list, false,
+ &fsp->fsp_name->st, &acl_set_support);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if (acl_set_support && ret == false) {
+ DEBUG(3,("set_nt_acl: failed to set file acl on file "
+ "%s (%s).\n", fsp_str_dbg(fsp),
+ strerror(errno)));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ if (acl_perms && acl_set_support && fsp->fsp_flags.is_directory) {
+ if (dir_ace_list) {
+ if (set_acl_as_root) {
+ become_root();
+ }
+ ret = set_canon_ace_list(fsp, dir_ace_list, true,
+ &fsp->fsp_name->st,
+ &acl_set_support);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if (ret == false) {
+ DEBUG(3,("set_nt_acl: failed to set default "
+ "acl on directory %s (%s).\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ } else {
+ int sret = -1;
+
+ /*
+ * No default ACL - delete one if it exists.
+ */
+
+ if (set_acl_as_root) {
+ become_root();
+ }
+ sret = SMB_VFS_SYS_ACL_DELETE_DEF_FD(fsp);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if (sret == -1) {
+ if (acl_group_override_fsp(fsp)) {
+ DEBUG(5,("set_nt_acl: acl group "
+ "control on and current user "
+ "in file %s primary group. "
+ "Override delete_def_acl\n",
+ fsp_str_dbg(fsp)));
+
+ become_root();
+ sret =
+ SMB_VFS_SYS_ACL_DELETE_DEF_FD(fsp);
+ unbecome_root();
+ }
+
+ if (sret == -1) {
+ DBG_NOTICE("sys_acl_delete_def_fd for "
+ "directory %s failed (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ }
+
+ if (acl_set_support) {
+ if (set_acl_as_root) {
+ become_root();
+ }
+ store_inheritance_attributes(fsp,
+ file_ace_list,
+ dir_ace_list,
+ psd->type);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ }
+
+ /*
+ * If we cannot set using POSIX ACLs we fall back to checking if we need to chmod.
+ */
+
+ if(!acl_set_support && acl_perms) {
+ mode_t posix_perms;
+
+ if (!convert_canon_ace_to_posix_perms( fsp, file_ace_list, &posix_perms)) {
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ DEBUG(3,("set_nt_acl: failed to convert file acl to "
+ "posix permissions for file %s.\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (orig_mode != posix_perms) {
+ int sret = -1;
+
+ DEBUG(3,("set_nt_acl: chmod %s. perms = 0%o.\n",
+ fsp_str_dbg(fsp), (unsigned int)posix_perms));
+
+ if (set_acl_as_root) {
+ become_root();
+ }
+ sret = SMB_VFS_FCHMOD(fsp, posix_perms);
+ if (set_acl_as_root) {
+ unbecome_root();
+ }
+ if(sret == -1) {
+ if (acl_group_override_fsp(fsp)) {
+ DEBUG(5,("set_nt_acl: acl group "
+ "control on and current user "
+ "in file %s primary group. "
+ "Override chmod\n",
+ fsp_str_dbg(fsp)));
+
+ become_root();
+ sret = SMB_VFS_FCHMOD(fsp, posix_perms);
+ unbecome_root();
+ }
+
+ if (sret == -1) {
+ DEBUG(3,("set_nt_acl: chmod %s, 0%o "
+ "failed. Error = %s.\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)posix_perms,
+ strerror(errno)));
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ }
+ }
+
+ free_canon_ace_list(file_ace_list);
+ free_canon_ace_list(dir_ace_list);
+
+ /* Ensure the stat struct in the fsp is correct. */
+ status = vfs_stat_fsp(fsp);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Get the actual group bits stored on a file with an ACL. Has no effect if
+ the file has no ACL. Needed in dosmode code where the stat() will return
+ the mask bits, not the real group bits, for a file with an ACL.
+****************************************************************************/
+
+int get_acl_group_bits(connection_struct *conn,
+ struct files_struct *fsp,
+ mode_t *mode )
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ SMB_ACL_T posix_acl;
+ int result = -1;
+
+ posix_acl = SMB_VFS_SYS_ACL_GET_FD(metadata_fsp(fsp),
+ SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+ if (posix_acl == (SMB_ACL_T)NULL)
+ return -1;
+
+ while (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) ==-1)
+ break;
+
+ if (tagtype == SMB_ACL_GROUP_OBJ) {
+ if (sys_acl_get_permset(entry, &permset) == -1) {
+ break;
+ } else {
+ *mode &= ~(S_IRGRP|S_IWGRP|S_IXGRP);
+ *mode |= (sys_acl_get_perm(permset, SMB_ACL_READ) ? S_IRGRP : 0);
+ *mode |= (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? S_IWGRP : 0);
+ *mode |= (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? S_IXGRP : 0);
+ result = 0;
+ break;
+ }
+ }
+ }
+ TALLOC_FREE(posix_acl);
+ return result;
+}
+
+/****************************************************************************
+ Do a chmod by setting the ACL USER_OBJ, GROUP_OBJ and OTHER bits in an ACL
+ and set the mask to rwx. Needed to preserve complex ACLs set by NT.
+****************************************************************************/
+
+static int chmod_acl_internals(SMB_ACL_T posix_acl, mode_t mode)
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ int num_entries = 0;
+
+ while ( sys_acl_get_entry(posix_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ mode_t perms;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) == -1)
+ return -1;
+
+ if (sys_acl_get_permset(entry, &permset) == -1)
+ return -1;
+
+ num_entries++;
+
+ switch(tagtype) {
+ case SMB_ACL_USER_OBJ:
+ perms = unix_perms_to_acl_perms(mode, S_IRUSR, S_IWUSR, S_IXUSR);
+ break;
+ case SMB_ACL_GROUP_OBJ:
+ perms = unix_perms_to_acl_perms(mode, S_IRGRP, S_IWGRP, S_IXGRP);
+ break;
+ case SMB_ACL_MASK:
+ /*
+ * FIXME: The ACL_MASK entry permissions should really be set to
+ * the union of the permissions of all ACL_USER,
+ * ACL_GROUP_OBJ, and ACL_GROUP entries. That's what
+ * acl_calc_mask() does, but Samba ACLs doesn't provide it.
+ */
+ perms = S_IRUSR|S_IWUSR|S_IXUSR;
+ break;
+ case SMB_ACL_OTHER:
+ perms = unix_perms_to_acl_perms(mode, S_IROTH, S_IWOTH, S_IXOTH);
+ break;
+ default:
+ continue;
+ }
+
+ if (map_acl_perms_to_permset(perms, &permset) == -1)
+ return -1;
+
+ if (sys_acl_set_permset(entry, permset) == -1)
+ return -1;
+ }
+
+ /*
+ * If this is a simple 3 element ACL or no elements then it's a standard
+ * UNIX permission set. Just use chmod...
+ */
+
+ if ((num_entries == 3) || (num_entries == 0))
+ return -1;
+
+ return 0;
+}
+
+/****************************************************************************
+ Get the access ACL of FROM, do a chmod by setting the ACL USER_OBJ,
+ GROUP_OBJ and OTHER bits in an ACL and set the mask to rwx. Set the
+ resulting ACL on TO. Note that name is in UNIX character set.
+****************************************************************************/
+
+static int copy_access_posix_acl(struct files_struct *from,
+ struct files_struct *to,
+ mode_t mode)
+{
+ SMB_ACL_T posix_acl = NULL;
+ int ret = -1;
+
+ posix_acl = SMB_VFS_SYS_ACL_GET_FD(
+ from, SMB_ACL_TYPE_ACCESS, talloc_tos());
+ if (posix_acl == NULL) {
+ return -1;
+ }
+
+ ret = chmod_acl_internals(posix_acl, mode);
+ if (ret == -1) {
+ goto done;
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(to, SMB_ACL_TYPE_ACCESS, posix_acl);
+
+ done:
+
+ TALLOC_FREE(posix_acl);
+ return ret;
+}
+
+/****************************************************************************
+ Check for an existing default POSIX ACL on a directory.
+****************************************************************************/
+
+static bool directory_has_default_posix_acl(struct files_struct *dirfsp)
+{
+ SMB_ACL_T def_acl = SMB_VFS_SYS_ACL_GET_FD(
+ dirfsp, SMB_ACL_TYPE_DEFAULT, talloc_tos());
+ bool has_acl = False;
+ SMB_ACL_ENTRY_T entry;
+
+ if (def_acl != NULL && (sys_acl_get_entry(def_acl, SMB_ACL_FIRST_ENTRY, &entry) == 1)) {
+ has_acl = True;
+ }
+
+ if (def_acl) {
+ TALLOC_FREE(def_acl);
+ }
+ return has_acl;
+}
+
+/****************************************************************************
+ If the parent directory has no default ACL but it does have an Access ACL,
+ inherit this Access ACL to file name.
+****************************************************************************/
+
+int inherit_access_posix_acl(connection_struct *conn,
+ struct files_struct *inherit_from_dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ int ret;
+
+ if (directory_has_default_posix_acl(inherit_from_dirfsp))
+ return 0;
+
+ ret = copy_access_posix_acl(
+ inherit_from_dirfsp, smb_fname->fsp, mode);
+ return ret;
+}
+
+/****************************************************************************
+ Map from wire type to permset.
+****************************************************************************/
+
+static bool unix_ex_wire_to_permset(connection_struct *conn, unsigned char wire_perm, SMB_ACL_PERMSET_T *p_permset)
+{
+ if (wire_perm & ~(SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE)) {
+ return False;
+ }
+
+ if (sys_acl_clear_perms(*p_permset) == -1) {
+ return False;
+ }
+
+ if (wire_perm & SMB_POSIX_ACL_READ) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_READ) == -1) {
+ return False;
+ }
+ }
+ if (wire_perm & SMB_POSIX_ACL_WRITE) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_WRITE) == -1) {
+ return False;
+ }
+ }
+ if (wire_perm & SMB_POSIX_ACL_EXECUTE) {
+ if (sys_acl_add_perm(*p_permset, SMB_ACL_EXECUTE) == -1) {
+ return False;
+ }
+ }
+ return True;
+}
+
+/****************************************************************************
+ Map from wire type to tagtype.
+****************************************************************************/
+
+static bool unix_ex_wire_to_tagtype(unsigned char wire_tt, SMB_ACL_TAG_T *p_tt)
+{
+ switch (wire_tt) {
+ case SMB_POSIX_ACL_USER_OBJ:
+ *p_tt = SMB_ACL_USER_OBJ;
+ break;
+ case SMB_POSIX_ACL_USER:
+ *p_tt = SMB_ACL_USER;
+ break;
+ case SMB_POSIX_ACL_GROUP_OBJ:
+ *p_tt = SMB_ACL_GROUP_OBJ;
+ break;
+ case SMB_POSIX_ACL_GROUP:
+ *p_tt = SMB_ACL_GROUP;
+ break;
+ case SMB_POSIX_ACL_MASK:
+ *p_tt = SMB_ACL_MASK;
+ break;
+ case SMB_POSIX_ACL_OTHER:
+ *p_tt = SMB_ACL_OTHER;
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+/****************************************************************************
+ Create a new POSIX acl from wire permissions.
+ FIXME ! How does the share mask/mode fit into this.... ?
+****************************************************************************/
+
+static SMB_ACL_T create_posix_acl_from_wire(connection_struct *conn,
+ uint16_t num_acls,
+ const char *pdata,
+ TALLOC_CTX *mem_ctx)
+{
+ unsigned int i;
+ SMB_ACL_T the_acl = sys_acl_init(mem_ctx);
+
+ if (the_acl == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; i < num_acls; i++) {
+ SMB_ACL_ENTRY_T the_entry;
+ SMB_ACL_PERMSET_T the_permset;
+ SMB_ACL_TAG_T tag_type;
+
+ if (sys_acl_create_entry(&the_acl, &the_entry) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to create entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (!unix_ex_wire_to_tagtype(CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)), &tag_type)) {
+ DEBUG(0,("create_posix_acl_from_wire: invalid wire tagtype %u on entry %u.\n",
+ CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)), i ));
+ goto fail;
+ }
+
+ if (sys_acl_set_tag_type(the_entry, tag_type) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set tagtype on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /* Get the permset pointer from the new ACL entry. */
+ if (sys_acl_get_permset(the_entry, &the_permset) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to get permset on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ /* Map from wire to permissions. */
+ if (!unix_ex_wire_to_permset(conn, CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+1), &the_permset)) {
+ DEBUG(0,("create_posix_acl_from_wire: invalid permset %u on entry %u.\n",
+ CVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE) + 1), i ));
+ goto fail;
+ }
+
+ /* Now apply to the new ACL entry. */
+ if (sys_acl_set_permset(the_entry, the_permset) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to add permset on entry %u. (%s)\n",
+ i, strerror(errno) ));
+ goto fail;
+ }
+
+ if (tag_type == SMB_ACL_USER) {
+ uint32_t uidval = IVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ uid_t uid = (uid_t)uidval;
+ if (sys_acl_set_qualifier(the_entry,(void *)&uid) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set uid %u on entry %u. (%s)\n",
+ (unsigned int)uid, i, strerror(errno) ));
+ goto fail;
+ }
+ }
+
+ if (tag_type == SMB_ACL_GROUP) {
+ uint32_t gidval = IVAL(pdata,(i*SMB_POSIX_ACL_ENTRY_SIZE)+2);
+ gid_t gid = (uid_t)gidval;
+ if (sys_acl_set_qualifier(the_entry,(void *)&gid) == -1) {
+ DEBUG(0,("create_posix_acl_from_wire: Failed to set gid %u on entry %u. (%s)\n",
+ (unsigned int)gid, i, strerror(errno) ));
+ goto fail;
+ }
+ }
+ }
+
+ return the_acl;
+
+ fail:
+
+ if (the_acl != NULL) {
+ TALLOC_FREE(the_acl);
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Calls from UNIX extensions - Default POSIX ACL set.
+ If num_def_acls == 0 and not a directory just return. If it is a directory
+ and num_def_acls == 0 then remove the default acl. Else set the default acl
+ on the directory.
+****************************************************************************/
+
+NTSTATUS set_unix_posix_default_acl(connection_struct *conn,
+ files_struct *fsp,
+ uint16_t num_def_acls,
+ const char *pdata)
+{
+ SMB_ACL_T def_acl = NULL;
+ NTSTATUS status;
+ int ret;
+
+ if (!fsp->fsp_flags.is_directory) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!num_def_acls) {
+ /* Remove the default ACL. */
+ ret = SMB_VFS_SYS_ACL_DELETE_DEF_FD(fsp);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_delete_def_fd failed on "
+ "directory %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ return status;
+ }
+ return NT_STATUS_OK;
+ }
+
+ def_acl = create_posix_acl_from_wire(conn,
+ num_def_acls,
+ pdata,
+ talloc_tos());
+ if (def_acl == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ def_acl);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_set_file failed on directory %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ TALLOC_FREE(def_acl);
+ return status;
+ }
+
+ DBG_DEBUG("set default acl for file %s\n",
+ fsp_str_dbg(fsp));
+ TALLOC_FREE(def_acl);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Remove an ACL from a file. As we don't have acl_delete_entry() available
+ we must read the current acl and copy all entries except MASK, USER and GROUP
+ to a new acl, then set that. This (at least on Linux) causes any ACL to be
+ removed.
+ FIXME ! How does the share mask/mode fit into this.... ?
+****************************************************************************/
+
+static NTSTATUS remove_posix_acl(connection_struct *conn,
+ files_struct *fsp)
+{
+ SMB_ACL_T file_acl = NULL;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+ /* Create a new ACL with only 3 entries, u/g/w. */
+ SMB_ACL_T new_file_acl = NULL;
+ SMB_ACL_ENTRY_T user_ent = NULL;
+ SMB_ACL_ENTRY_T group_ent = NULL;
+ SMB_ACL_ENTRY_T other_ent = NULL;
+ NTSTATUS status;
+ int ret;
+
+ new_file_acl = sys_acl_init(talloc_tos());
+ if (new_file_acl == NULL) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to init new ACL with 3 entries "
+ "for file %s %s.\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ /* Now create the u/g/w entries. */
+ ret = sys_acl_create_entry(&new_file_acl, &user_ent);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to create user entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ ret = sys_acl_set_tag_type(user_ent, SMB_ACL_USER_OBJ);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to set user entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ ret = sys_acl_create_entry(&new_file_acl, &group_ent);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to create group entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ ret = sys_acl_set_tag_type(group_ent, SMB_ACL_GROUP_OBJ);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to set group entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ ret = sys_acl_create_entry(&new_file_acl, &other_ent);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to create other entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ ret = sys_acl_set_tag_type(other_ent, SMB_ACL_OTHER);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("Failed to set other entry for file %s. (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ /* Get the current file ACL. */
+ file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+
+ if (file_acl == NULL) {
+ status = map_nt_error_from_unix(errno);
+ /* This is only returned if an error occurred. Even for a file with
+ no acl a u/g/w acl should be returned. */
+ DBG_INFO("failed to get ACL from file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ while ( sys_acl_get_entry(file_acl, entry_id, &entry) == 1) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ ret = sys_acl_get_tag_type(entry, &tagtype);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to get tagtype from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ ret = sys_acl_get_permset(entry, &permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to get permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ if (tagtype == SMB_ACL_USER_OBJ) {
+ ret = sys_acl_set_permset(user_ent, permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to set permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ } else if (tagtype == SMB_ACL_GROUP_OBJ) {
+ ret = sys_acl_set_permset(group_ent, permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to set permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ } else if (tagtype == SMB_ACL_OTHER) {
+ ret = sys_acl_set_permset(other_ent, permset);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("failed to set permset from ACL "
+ "on file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+ }
+ }
+
+ /* Set the new empty file ACL. */
+ ret = SMB_VFS_SYS_ACL_SET_FD(fsp, SMB_ACL_TYPE_ACCESS, new_file_acl);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_set_file failed on %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ goto done;
+ }
+
+ status = NT_STATUS_OK;
+
+ done:
+
+ TALLOC_FREE(file_acl);
+ TALLOC_FREE(new_file_acl);
+ return status;
+}
+
+/****************************************************************************
+ Calls from UNIX extensions - POSIX ACL set.
+ If num_def_acls == 0 then read/modify/write acl after removing all entries
+ except SMB_ACL_USER_OBJ, SMB_ACL_GROUP_OBJ, SMB_ACL_OTHER.
+****************************************************************************/
+
+NTSTATUS set_unix_posix_acl(connection_struct *conn,
+ files_struct *fsp,
+ uint16_t num_acls,
+ const char *pdata)
+{
+ SMB_ACL_T file_acl = NULL;
+ int ret;
+ NTSTATUS status;
+
+ if (!num_acls) {
+ /* Remove the ACL from the file. */
+ return remove_posix_acl(conn, fsp);
+ }
+
+ file_acl = create_posix_acl_from_wire(conn,
+ num_acls,
+ pdata,
+ talloc_tos());
+ if (file_acl == NULL) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(fsp, SMB_ACL_TYPE_ACCESS, file_acl);
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_INFO("acl_set_file failed on %s (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ TALLOC_FREE(file_acl);
+ return status;
+ }
+
+ DBG_DEBUG("set acl for file %s\n",
+ fsp_str_dbg(fsp));
+
+ TALLOC_FREE(file_acl);
+ return NT_STATUS_OK;
+}
+
+int posix_sys_acl_blob_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname_in,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+ /* Initialise this to zero, in a portable way */
+ struct smb_acl_wrapper acl_wrapper = {
+ 0
+ };
+ struct smb_filename *smb_fname = cp_smb_filename_nostream(frame,
+ smb_fname_in);
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ ret = smb_vfs_call_stat(handle, smb_fname);
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ acl_wrapper.owner = smb_fname->st.st_ex_uid;
+ acl_wrapper.group = smb_fname->st.st_ex_gid;
+ acl_wrapper.mode = smb_fname->st.st_ex_mode;
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(blob, mem_ctx,
+ &acl_wrapper,
+ (ndr_push_flags_fn_t)ndr_push_smb_acl_wrapper))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ *blob_description = talloc_strdup(mem_ctx, "posix_acl");
+ if (!*blob_description) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+int posix_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ SMB_STRUCT_STAT sbuf;
+ TALLOC_CTX *frame;
+ struct smb_acl_wrapper acl_wrapper = { 0 };
+ int ret;
+
+ frame = talloc_stackframe();
+
+ acl_wrapper.access_acl = smb_vfs_call_sys_acl_get_fd(handle,
+ fsp,
+ SMB_ACL_TYPE_ACCESS,
+ frame);
+
+ if (fsp->fsp_flags.is_directory) {
+ acl_wrapper.default_acl = smb_vfs_call_sys_acl_get_fd(handle,
+ fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ frame);
+ }
+
+ ret = smb_vfs_call_fstat(handle, fsp, &sbuf);
+ if (ret == -1) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ acl_wrapper.owner = sbuf.st_ex_uid;
+ acl_wrapper.group = sbuf.st_ex_gid;
+ acl_wrapper.mode = sbuf.st_ex_mode;
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_push_struct_blob(blob, mem_ctx,
+ &acl_wrapper,
+ (ndr_push_flags_fn_t)ndr_push_smb_acl_wrapper))) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ *blob_description = talloc_strdup(mem_ctx, "posix_acl");
+ if (!*blob_description) {
+ errno = EINVAL;
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+}
+
+static NTSTATUS make_default_acl_posix(TALLOC_CTX *ctx,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid, group_sid;
+ size_t size = 0;
+ struct security_ace aces[4];
+ uint32_t access_mask = 0;
+ mode_t mode = psbuf->st_ex_mode;
+ struct security_acl *new_dacl = NULL;
+ int idx = 0;
+
+ DBG_DEBUG("file %s mode = 0%o\n",name, (int)mode);
+
+ uid_to_sid(&owner_sid, psbuf->st_ex_uid);
+ gid_to_sid(&group_sid, psbuf->st_ex_gid);
+
+ /*
+ We provide up to 4 ACEs
+ - Owner
+ - Group
+ - Everyone
+ - NT System
+ */
+
+ if (mode & S_IRUSR) {
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_ALL;
+ } else {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ }
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_WRITE | SEC_STD_DELETE;
+ }
+
+ init_sec_ace(&aces[idx],
+ &owner_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+
+ access_mask = 0;
+ if (mode & S_IRGRP) {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ if (mode & S_IWGRP) {
+ /* note that delete is not granted - this matches posix behaviour */
+ access_mask |= SEC_RIGHTS_FILE_WRITE;
+ }
+ if (access_mask) {
+ init_sec_ace(&aces[idx],
+ &group_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+ }
+
+ access_mask = 0;
+ if (mode & S_IROTH) {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ if (mode & S_IWOTH) {
+ access_mask |= SEC_RIGHTS_FILE_WRITE;
+ }
+ if (access_mask) {
+ init_sec_ace(&aces[idx],
+ &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+ }
+
+ init_sec_ace(&aces[idx],
+ &global_sid_System,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ SEC_RIGHTS_FILE_ALL,
+ 0);
+ idx++;
+
+ new_dacl = make_sec_acl(ctx,
+ NT4_ACL_REVISION,
+ idx,
+ aces);
+
+ if (!new_dacl) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *ppdesc = make_sec_desc(ctx,
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE|SEC_DESC_DACL_PRESENT,
+ &owner_sid,
+ &group_sid,
+ NULL,
+ new_dacl,
+ &size);
+ if (!*ppdesc) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS make_default_acl_windows(TALLOC_CTX *ctx,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid, group_sid;
+ size_t size = 0;
+ struct security_ace aces[4];
+ uint32_t access_mask = 0;
+ mode_t mode = psbuf->st_ex_mode;
+ struct security_acl *new_dacl = NULL;
+ int idx = 0;
+
+ DBG_DEBUG("file [%s] mode [0%o]\n", name, (int)mode);
+
+ uid_to_sid(&owner_sid, psbuf->st_ex_uid);
+ gid_to_sid(&group_sid, psbuf->st_ex_gid);
+
+ /*
+ * We provide 2 ACEs:
+ * - Owner
+ * - NT System
+ */
+
+ if (mode & S_IRUSR) {
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_ALL;
+ } else {
+ access_mask |= SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ }
+ }
+ if (mode & S_IWUSR) {
+ access_mask |= SEC_RIGHTS_FILE_WRITE | SEC_STD_DELETE;
+ }
+
+ init_sec_ace(&aces[idx],
+ &owner_sid,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ access_mask,
+ 0);
+ idx++;
+
+ init_sec_ace(&aces[idx],
+ &global_sid_System,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ SEC_RIGHTS_FILE_ALL,
+ 0);
+ idx++;
+
+ new_dacl = make_sec_acl(ctx,
+ NT4_ACL_REVISION,
+ idx,
+ aces);
+
+ if (!new_dacl) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *ppdesc = make_sec_desc(ctx,
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE|SEC_DESC_DACL_PRESENT,
+ &owner_sid,
+ &group_sid,
+ NULL,
+ new_dacl,
+ &size);
+ if (!*ppdesc) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS make_default_acl_everyone(TALLOC_CTX *ctx,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ struct dom_sid owner_sid, group_sid;
+ size_t size = 0;
+ struct security_ace aces[1];
+ mode_t mode = psbuf->st_ex_mode;
+ struct security_acl *new_dacl = NULL;
+ int idx = 0;
+
+ DBG_DEBUG("file [%s] mode [0%o]\n", name, (int)mode);
+
+ uid_to_sid(&owner_sid, psbuf->st_ex_uid);
+ gid_to_sid(&group_sid, psbuf->st_ex_gid);
+
+ /*
+ * We provide one ACEs: full access for everyone
+ */
+
+ init_sec_ace(&aces[idx],
+ &global_sid_World,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ SEC_RIGHTS_FILE_ALL,
+ 0);
+ idx++;
+
+ new_dacl = make_sec_acl(ctx,
+ NT4_ACL_REVISION,
+ idx,
+ aces);
+
+ if (!new_dacl) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *ppdesc = make_sec_desc(ctx,
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE|SEC_DESC_DACL_PRESENT,
+ &owner_sid,
+ &group_sid,
+ NULL,
+ new_dacl,
+ &size);
+ if (!*ppdesc) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ return NT_STATUS_OK;
+}
+
+static const struct enum_list default_acl_style_list[] = {
+ {DEFAULT_ACL_POSIX, "posix"},
+ {DEFAULT_ACL_WINDOWS, "windows"},
+ {DEFAULT_ACL_EVERYONE, "everyone"},
+};
+
+const struct enum_list *get_default_acl_style_list(void)
+{
+ return default_acl_style_list;
+}
+
+NTSTATUS make_default_filesystem_acl(
+ TALLOC_CTX *ctx,
+ enum default_acl_style acl_style,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc)
+{
+ NTSTATUS status;
+
+ switch (acl_style) {
+ case DEFAULT_ACL_POSIX:
+ status = make_default_acl_posix(ctx, name, psbuf, ppdesc);
+ break;
+
+ case DEFAULT_ACL_WINDOWS:
+ status = make_default_acl_windows(ctx, name, psbuf, ppdesc);
+ break;
+
+ case DEFAULT_ACL_EVERYONE:
+ status = make_default_acl_everyone(ctx, name, psbuf, ppdesc);
+ break;
+
+ default:
+ DBG_ERR("unknown acl style %d\n", acl_style);
+ status = NT_STATUS_INTERNAL_ERROR;
+ break;
+ }
+
+ return status;
+}
diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h
new file mode 100644
index 0000000..d18afe3
--- /dev/null
+++ b/source3/smbd/proto.h
@@ -0,0 +1,1224 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Main SMB server routines
+ *
+ * Copyright (C) Andrew Tridgell 1992-2002,2006
+ * Copyright (C) Jeremy Allison 1992-2010
+ * Copyright (C) Volker Lendecke 1993-2009
+ * Copyright (C) John H Terpstra 1995-1998
+ * Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ * Copyright (C) Paul Ashton 1997-1998
+ * Copyright (C) Tim Potter 1999-2000
+ * Copyright (C) T.D.Lee@durham.ac.uk 1999
+ * Copyright (C) Ying Chen 2000
+ * Copyright (C) Shirish Kalele 2000
+ * Copyright (C) Andrew Bartlett 2001-2003
+ * Copyright (C) Alexander Bokovoy 2002,2005
+ * Copyright (C) Simo Sorce 2001-2002,2009
+ * Copyright (C) Andreas Gruenbacher 2002
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ * Copyright (C) Martin Pool 2002
+ * Copyright (C) Luke Howard 2003
+ * Copyright (C) Stefan (metze) Metzmacher 2003,2009
+ * Copyright (C) Steve French 2005
+ * Copyright (C) Gerald (Jerry) Carter 2006
+ * Copyright (C) James Peach 2006-2007
+ * Copyright (C) Jelmer Vernooij 2002-2003
+ * Copyright (C) Michael Adam 2007
+ * Copyright (C) Rishi Srivatsavai 2007
+ * Copyright (C) Tim Prouty 2009
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SMBD_PROTO_H_
+#define _SMBD_PROTO_H_
+
+struct smbXsrv_client;
+struct smbXsrv_connection;
+struct dcesrv_context;
+
+/* The following definitions come from smbd/smb2_signing.c */
+
+bool srv_init_signing(struct smbXsrv_connection *conn);
+
+/* The following definitions come from smbd/aio.c */
+
+struct aio_extra;
+bool aio_write_through_requested(struct aio_extra *aio_ex);
+NTSTATUS schedule_smb2_aio_read(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ TALLOC_CTX *ctx,
+ DATA_BLOB *preadbuf,
+ off_t startpos,
+ size_t smb_maxcnt);
+NTSTATUS schedule_aio_smb2_write(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ uint64_t in_offset,
+ DATA_BLOB in_data,
+ bool write_through);
+bool cancel_smb2_aio(struct smb_request *smbreq);
+bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req);
+struct aio_extra *create_aio_extra(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ size_t buflen);
+struct tevent_req *pwrite_fsync_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset,
+ bool write_through);
+ssize_t pwrite_fsync_recv(struct tevent_req *req, int *perr);
+
+/* The following definitions come from smbd/blocking.c */
+
+NTSTATUS smbd_do_locks_try(
+ struct files_struct *fsp,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks,
+ uint16_t *blocker_idx,
+ struct server_id *blocking_pid,
+ uint64_t *blocking_smblctx);
+struct tevent_req *smbd_smb1_do_locks_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smb_request **smbreq, /* talloc_move()d into our state */
+ struct files_struct *fsp,
+ uint32_t lock_timeout,
+ bool large_offset,
+ uint16_t num_locks,
+ struct smbd_lock_element *locks);
+NTSTATUS smbd_smb1_do_locks_recv(struct tevent_req *req);
+bool smbd_smb1_do_locks_extract_smbreq(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request **psmbreq);
+void smbd_smb1_brl_finish_by_req(struct tevent_req *req, NTSTATUS status);
+bool smbd_smb1_brl_finish_by_lock(
+ struct files_struct *fsp,
+ bool large_offset,
+ struct smbd_lock_element lock,
+ NTSTATUS finish_status);
+bool smbd_smb1_brl_finish_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid);
+
+/* The following definitions come from smbd/close.c */
+
+void set_close_write_time(struct files_struct *fsp, struct timespec ts);
+NTSTATUS close_file_smb(struct smb_request *req,
+ struct files_struct *fsp,
+ enum file_close_type close_type);
+NTSTATUS close_file_free(struct smb_request *req,
+ struct files_struct **_fsp,
+ enum file_close_type close_type);
+void msg_close_file(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+NTSTATUS delete_all_streams(connection_struct *conn,
+ const struct smb_filename *smb_fname);
+NTSTATUS recursive_rmdir(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_dname);
+bool has_other_nonposix_opens(struct share_mode_lock *lck,
+ struct files_struct *fsp);
+
+/* The following definitions come from smbd/conn.c */
+
+int conn_num_open(struct smbd_server_connection *sconn);
+bool conn_snum_used(struct smbd_server_connection *sconn, int snum);
+connection_struct *conn_new(struct smbd_server_connection *sconn);
+bool conn_idle_all(struct smbd_server_connection *sconn, time_t t);
+void conn_clear_vuid_caches(struct smbd_server_connection *sconn, uint64_t vuid);
+void conn_free(connection_struct *conn);
+void conn_setup_case_options(connection_struct *conn);
+void conn_force_tdis(
+ struct smbd_server_connection *sconn,
+ bool (*check_fn)(struct connection_struct *conn,
+ void *private_data),
+ void *private_data);
+void msg_force_tdis(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void msg_force_tdis_denied(
+ struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+/* The following definitions come from smbd/connection.c */
+
+int count_current_connections(const char *sharename, bool verify);
+bool connections_snum_used(struct smbd_server_connection *unused, int snum);
+
+/* The following definitions come from smbd/dfree.c */
+
+uint64_t get_dfree_info(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
+void flush_dfree_cache(void);
+
+/* The following definitions come from smbd/dmapi.c */
+
+const void *dmapi_get_current_session(void);
+bool dmapi_have_session(void);
+bool dmapi_new_session(void);
+bool dmapi_destroy_session(void);
+uint32_t dmapi_file_flags(const char * const path);
+
+/* The following definitions come from smbd/dnsregister.c */
+
+bool smbd_setup_mdns_registration(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ uint16_t port);
+
+/* The following definitions come from smbd/dosmode.c */
+
+mode_t unix_mode(connection_struct *conn, int dosmode,
+ const struct smb_filename *smb_fname,
+ struct files_struct *parent_dirfsp);
+uint32_t dos_mode_msdfs(connection_struct *conn,
+ const char *name,
+ const struct stat_ex *st);
+uint32_t fdos_mode(struct files_struct *fsp);
+struct tevent_req *dos_mode_at_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname);
+NTSTATUS dos_mode_at_recv(struct tevent_req *req, uint32_t *dosmode);
+int file_set_dosmode(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode,
+ struct smb_filename *parent_dir,
+ bool newfile);
+NTSTATUS file_set_sparse(connection_struct *conn,
+ struct files_struct *fsp,
+ bool sparse);
+int file_ntimes(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_file_time *ft);
+bool set_sticky_write_time_path(struct file_id fileid, struct timespec mtime);
+bool set_sticky_write_time_fsp(struct files_struct *fsp,
+ struct timespec mtime);
+
+NTSTATUS fget_ea_dos_attribute(struct files_struct *fsp,
+ uint32_t *pattr);
+NTSTATUS set_ea_dos_attribute(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ uint32_t dosmode);
+
+NTSTATUS set_create_timespec_ea(struct files_struct *fsp,
+ struct timespec create_time);
+
+struct timespec get_create_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname);
+
+struct timespec get_change_timespec(connection_struct *conn,
+ struct files_struct *fsp,
+ const struct smb_filename *smb_fname);
+
+NTSTATUS parse_dos_attribute_blob(struct smb_filename *smb_fname,
+ DATA_BLOB blob,
+ uint32_t *pattr);
+
+/* The following definitions come from smbd/error.c */
+
+bool use_nt_status(void);
+void error_packet_set(char *outbuf, uint8_t eclass, uint32_t ecode, NTSTATUS ntstatus, int line, const char *file);
+size_t error_packet(char *outbuf,
+ uint8_t eclass,
+ uint32_t ecode,
+ NTSTATUS ntstatus,
+ int line,
+ const char *file);
+void reply_nt_error(struct smb_request *req, NTSTATUS ntstatus,
+ int line, const char *file);
+void reply_force_dos_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ int line, const char *file);
+void reply_both_error(struct smb_request *req, uint8_t eclass, uint32_t ecode,
+ NTSTATUS status, int line, const char *file);
+void reply_openerror(struct smb_request *req, NTSTATUS status);
+
+/* The following definitions come from smbd/file_access.c */
+
+bool can_delete_file_in_directory(connection_struct *conn,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname);
+bool can_write_to_fsp(struct files_struct *fsp);
+bool directory_has_default_acl_fsp(struct files_struct *fsp);
+NTSTATUS can_set_delete_on_close(files_struct *fsp, uint32_t dosmode);
+
+/* The following definitions come from smbd/fileio.c */
+
+ssize_t read_file(files_struct *fsp,char *data,off_t pos,size_t n);
+void fsp_flush_write_time_update(struct files_struct *fsp);
+void trigger_write_time_update(struct files_struct *fsp);
+void trigger_write_time_update_immediate(struct files_struct *fsp);
+void mark_file_modified(files_struct *fsp);
+ssize_t write_file(struct smb_request *req,
+ files_struct *fsp,
+ const char *data,
+ off_t pos,
+ size_t n);
+NTSTATUS sync_file(connection_struct *conn, files_struct *fsp, bool write_through);
+
+/* The following definitions come from smbd/filename.c */
+
+uint32_t ucf_flags_from_smb_request(struct smb_request *req);
+uint32_t filename_create_ucf_flags(struct smb_request *req, uint32_t create_disposition);
+NTSTATUS canonicalize_snapshot_path(struct smb_filename *smb_fname,
+ uint32_t ucf_flags,
+ NTTIME twrp);
+NTSTATUS get_real_filename_full_scan_at(struct files_struct *dirfsp,
+ const char *name,
+ bool mangled,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+char *get_original_lcomp(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ const char *filename_in,
+ uint32_t ucf_flags);
+NTSTATUS get_real_filename_at(struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name);
+
+/* The following definitions come from smbd/files.c */
+
+NTSTATUS fsp_new(struct connection_struct *conn, TALLOC_CTX *mem_ctx,
+ files_struct **result);
+void fsp_set_gen_id(files_struct *fsp);
+NTSTATUS file_new(struct smb_request *req, connection_struct *conn,
+ files_struct **result);
+NTSTATUS fsp_bind_smb(struct files_struct *fsp, struct smb_request *req);
+void file_close_conn(connection_struct *conn, enum file_close_type close_type);
+bool file_init_global(void);
+bool file_init(struct smbd_server_connection *sconn);
+void file_close_user(struct smbd_server_connection *sconn, uint64_t vuid);
+struct files_struct *files_forall(
+ struct smbd_server_connection *sconn,
+ struct files_struct *(*fn)(struct files_struct *fsp,
+ void *private_data),
+ void *private_data);
+files_struct *file_find_fd(struct smbd_server_connection *sconn, int fd);
+files_struct *file_find_dif(struct smbd_server_connection *sconn,
+ struct file_id id, unsigned long gen_id);
+files_struct *file_find_di_first(struct smbd_server_connection *sconn,
+ struct file_id id,
+ bool need_fsa);
+files_struct *file_find_di_next(files_struct *start_fsp,
+ bool need_fsa);
+struct files_struct *file_find_one_fsp_from_lease_key(
+ struct smbd_server_connection *sconn,
+ const struct smb2_lease_key *lease_key);
+bool file_find_subpath(files_struct *dir_fsp);
+void fsp_unbind_smb(struct smb_request *req, files_struct *fsp);
+void file_free(struct smb_request *req, files_struct *fsp);
+files_struct *file_fsp(struct smb_request *req, uint16_t fid);
+struct files_struct *file_fsp_get(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id);
+struct files_struct *file_fsp_smb2(struct smbd_smb2_request *smb2req,
+ uint64_t persistent_id,
+ uint64_t volatile_id);
+NTSTATUS dup_file_fsp(
+ files_struct *from,
+ uint32_t access_mask,
+ files_struct *to);
+NTSTATUS file_name_hash(connection_struct *conn,
+ const char *name, uint32_t *p_name_hash);
+NTSTATUS fsp_set_smb_fname(struct files_struct *fsp,
+ const struct smb_filename *smb_fname_in);
+size_t fsp_fullbasepath(struct files_struct *fsp, char *buf, size_t buflen);
+void fsp_set_base_fsp(struct files_struct *fsp, struct files_struct *base_fsp);
+bool fsp_is_alternate_stream(const struct files_struct *fsp);
+struct files_struct *metadata_fsp(struct files_struct *fsp);
+bool fsp_search_ask_sharemode(struct files_struct *fsp);
+bool fsp_getinfo_ask_sharemode(struct files_struct *fsp);
+
+NTSTATUS create_internal_fsp(connection_struct *conn,
+ const struct smb_filename *smb_fname,
+ struct files_struct **_fsp);
+NTSTATUS create_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ struct files_struct **_fsp);
+
+NTSTATUS open_internal_dirfsp(connection_struct *conn,
+ const struct smb_filename *smb_dname,
+ int open_flags,
+ struct files_struct **_fsp);
+NTSTATUS openat_internal_dir_from_pathref(
+ struct files_struct *dirfsp,
+ int open_flags,
+ struct files_struct **_fsp);
+
+NTSTATUS openat_pathref_fsp(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname);
+NTSTATUS open_stream_pathref_fsp(
+ struct files_struct **_base_fsp,
+ struct smb_filename *smb_fname);
+
+struct symlink_reparse_struct;
+
+struct open_symlink_err {
+ struct stat_ex st;
+ size_t unparsed;
+ struct symlink_reparse_struct *reparse;
+};
+
+NTSTATUS create_open_symlink_err(TALLOC_CTX *mem_ctx,
+ files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct open_symlink_err **_err);
+
+NTSTATUS openat_pathref_fsp_nosymlink(TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ struct files_struct *dirfsp,
+ const char *path_in,
+ NTTIME twrp,
+ bool posix,
+ struct smb_filename **_smb_fname,
+ struct open_symlink_err **_symlink_err);
+NTSTATUS openat_pathref_fsp_lcomp(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname_rel,
+ uint32_t ucf_flags);
+NTSTATUS readlink_talloc(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ char **_substitute);
+
+struct symlink_reparse_struct;
+
+NTSTATUS read_symlink_reparse(
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_relname,
+ struct symlink_reparse_struct **_symlink);
+
+void smb_fname_fsp_unlink(struct smb_filename *smb_fname);
+
+NTSTATUS move_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ struct smb_filename *smb_fname_src);
+
+NTSTATUS reference_smb_fname_fsp_link(struct smb_filename *smb_fname_dst,
+ const struct smb_filename *smb_fname_src);
+
+NTSTATUS synthetic_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const char *base_name,
+ const char *stream_name,
+ const SMB_STRUCT_STAT *psbuf,
+ NTTIME twrp,
+ uint32_t flags,
+ struct smb_filename **_smb_fname);
+
+NTSTATUS parent_pathref(TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct smb_filename **_parent,
+ struct smb_filename **_atname);
+
+/* The following definitions come from smbd/smb2_ipc.c */
+
+NTSTATUS nt_status_np_pipe(NTSTATUS status);
+
+/* The following definitions come from smbd/mangle.c */
+
+void mangle_reset_cache(void);
+void mangle_change_to_posix(void);
+bool mangle_is_mangled(const char *s, const struct share_params *p);
+bool mangle_is_8_3(const char *fname, bool check_case,
+ const struct share_params *p);
+bool mangle_is_8_3_wildcards(const char *fname, bool check_case,
+ const struct share_params *p);
+bool mangle_must_mangle(const char *fname,
+ const struct share_params *p);
+bool mangle_lookup_name_from_8_3(TALLOC_CTX *ctx,
+ const char *in,
+ char **out, /* talloced on the given context. */
+ const struct share_params *p);
+bool name_to_8_3(const char *in,
+ char out[13],
+ bool cache83,
+ const struct share_params *p);
+
+/* The following definitions come from smbd/mangle_hash.c */
+
+const struct mangle_fns *mangle_hash_init(void);
+
+/* The following definitions come from smbd/mangle_hash2.c */
+
+const struct mangle_fns *mangle_hash2_init(void);
+const struct mangle_fns *posix_mangle_init(void);
+
+/* The following definitions come from smbd/msdfs.c */
+
+bool parse_msdfs_symlink(TALLOC_CTX *ctx,
+ bool shuffle_referrals,
+ const char *target,
+ struct referral **preflist,
+ size_t *refcount);
+bool is_msdfs_link(struct files_struct *dirfsp,
+ struct smb_filename *smb_fname);
+struct junction_map;
+NTSTATUS get_referred_path(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ const char *dfs_path,
+ const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct junction_map *jucn,
+ size_t *consumedcntp,
+ bool *self_referralp);
+int setup_dfs_referral(connection_struct *orig_conn,
+ const char *dfs_path,
+ int max_referral_level,
+ char **ppdata, NTSTATUS *pstatus);
+bool create_junction(TALLOC_CTX *ctx,
+ const char *dfs_path,
+ struct junction_map *jucn);
+struct referral;
+char *msdfs_link_string(TALLOC_CTX *ctx,
+ const struct referral *reflist,
+ size_t referral_count);
+bool create_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info);
+bool remove_msdfs_link(const struct junction_map *jucn,
+ struct auth_session_info *session_info);
+
+struct junction_map *enum_msdfs_links(TALLOC_CTX *ctx,
+ struct auth_session_info *session_info,
+ size_t *p_num_jn);
+struct connection_struct;
+struct smb_filename;
+
+NTSTATUS create_conn_struct_cwd(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ const struct auth_session_info *session_info,
+ int snum,
+ const char *path,
+ struct connection_struct **c);
+struct conn_struct_tos {
+ struct connection_struct *conn;
+ struct smb_filename *oldcwd_fname;
+};
+NTSTATUS create_conn_struct_tos(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c);
+NTSTATUS create_conn_struct_tos_cwd(struct messaging_context *msg,
+ int snum,
+ const char *path,
+ const struct auth_session_info *session_info,
+ struct conn_struct_tos **_c);
+
+/* The following definitions come from smbd/notify.c */
+
+bool change_notify_fsp_has_changes(struct files_struct *fsp);
+void change_notify_reply(struct smb_request *req,
+ NTSTATUS error_code,
+ uint32_t max_param,
+ struct notify_change_buf *notify_buf,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len));
+void notify_callback(struct smbd_server_connection *sconn,
+ void *private_data, struct timespec when,
+ const struct notify_event *e);
+NTSTATUS change_notify_create(struct files_struct *fsp,
+ uint32_t max_buffer_size,
+ uint32_t filter,
+ bool recursive);
+NTSTATUS change_notify_add_request(struct smb_request *req,
+ uint32_t max_param,
+ uint32_t filter, bool recursive,
+ struct files_struct *fsp,
+ void (*reply_fn)(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len));
+void smbd_notify_cancel_deleted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data);
+void smbd_notifyd_restarted(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data);
+bool remove_pending_change_notify_requests_by_mid(
+ struct smbd_server_connection *sconn, uint64_t mid);
+void remove_pending_change_notify_requests_by_fid(files_struct *fsp,
+ NTSTATUS status);
+void notify_fname(connection_struct *conn, uint32_t action, uint32_t filter,
+ const char *path);
+char *notify_filter_string(TALLOC_CTX *mem_ctx, uint32_t filter);
+struct sys_notify_context *sys_notify_context_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev);
+
+/* The following definitions come from smbd/notify_inotify.c */
+
+int inotify_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p);
+
+int fam_watch(TALLOC_CTX *mem_ctx,
+ struct sys_notify_context *ctx,
+ const char *path,
+ uint32_t *filter,
+ uint32_t *subdir_filter,
+ void (*callback)(struct sys_notify_context *ctx,
+ void *private_data,
+ struct notify_event *ev,
+ uint32_t filter),
+ void *private_data,
+ void *handle_p);
+
+
+/* The following definitions come from smbd/notify_internal.c */
+
+struct notify_context *notify_init(
+ TALLOC_CTX *mem_ctx, struct messaging_context *msg,
+ struct smbd_server_connection *sconn,
+ void (*callback)(struct smbd_server_connection *sconn,
+ void *, struct timespec,
+ const struct notify_event *));
+NTSTATUS notify_add(struct notify_context *ctx,
+ const char *path, uint32_t filter, uint32_t subdir_filter,
+ void *private_data);
+NTSTATUS notify_remove(struct notify_context *ctx, void *private_data,
+ char *path);
+void notify_trigger(struct notify_context *notify,
+ uint32_t action, uint32_t filter,
+ const char *dir, const char *path);
+
+/* The following definitions come from smbd/ntquotas.c */
+
+NTSTATUS vfs_get_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype,
+ struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt);
+int vfs_set_ntquota(files_struct *fsp, enum SMB_QUOTA_TYPE qtype, struct dom_sid *psid, SMB_NTQUOTA_STRUCT *qt);
+int vfs_get_user_ntquota_list(files_struct *fsp, SMB_NTQUOTA_LIST **qt_list);
+void *init_quota_handle(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from smbd/smb2_nttrans.c */
+
+NTSTATUS set_sd(files_struct *fsp, struct security_descriptor *psd,
+ uint32_t security_info_sent);
+NTSTATUS set_sd_blob(files_struct *fsp, uint8_t *data, uint32_t sd_len,
+ uint32_t security_info_sent);
+NTSTATUS copy_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct files_struct *dst_dirfsp,
+ struct smb_filename *smb_fname_dst,
+ uint32_t attrs);
+NTSTATUS smbd_do_query_security_desc(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ uint32_t security_info_wanted,
+ uint32_t max_data_count,
+ uint8_t **ppmarshalled_sd,
+ size_t *psd_size);
+#ifdef HAVE_SYS_QUOTAS
+
+struct smb2_query_quota_info;
+
+NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ bool restart_scan,
+ bool return_single,
+ uint32_t sid_list_length,
+ DATA_BLOB *sidbuffer,
+ uint32_t max_data_count,
+ uint8_t **p_data,
+ uint32_t *p_data_size);
+#endif
+
+/* The following definitions come from smbd/open.c */
+
+NTSTATUS smbd_check_access_rights_fsp(struct files_struct *dirfsp,
+ struct files_struct *fsp,
+ bool use_privs,
+ uint32_t access_mask);
+NTSTATUS check_parent_access_fsp(struct files_struct *fsp,
+ uint32_t access_mask);
+NTSTATUS fd_openat(const struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ files_struct *fsp,
+ const struct vfs_open_how *how);
+NTSTATUS fd_close(files_struct *fsp);
+bool is_oplock_stat_open(uint32_t access_mask);
+bool is_lease_stat_open(uint32_t access_mask);
+NTSTATUS send_break_message(struct messaging_context *msg_ctx,
+ const struct file_id *id,
+ const struct share_mode_entry *exclusive,
+ uint16_t break_to);
+struct deferred_open_record;
+bool is_deferred_open_async(const struct deferred_open_record *rec);
+bool defer_smb1_sharing_violation(struct smb_request *req);
+NTSTATUS create_directory(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_dname);
+void msg_file_was_renamed(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
+ const struct smb2_lease_key *key,
+ uint32_t current_state,
+ uint16_t lease_version,
+ uint16_t lease_epoch);
+NTSTATUS create_file_default(connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename * smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs);
+
+/* The following definitions come from smbd/oplock.c */
+
+uint32_t get_lease_type(struct share_mode_entry *e, struct file_id id);
+
+void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp);
+NTSTATUS set_file_oplock(files_struct *fsp);
+void release_file_oplock(files_struct *fsp);
+bool remove_oplock(files_struct *fsp);
+bool downgrade_oplock(files_struct *fsp);
+bool fsp_lease_update(struct files_struct *fsp);
+NTSTATUS downgrade_lease(struct smbXsrv_client *client,
+ uint32_t num_file_ids,
+ const struct file_id *ids,
+ const struct smb2_lease_key *key,
+ uint32_t lease_state);
+void contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type);
+void contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type);
+void smbd_contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type);
+void smbd_contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type);
+void share_mode_entry_to_message(char *msg, const struct file_id *id,
+ const struct share_mode_entry *e);
+void message_to_share_mode_entry(struct file_id *id,
+ struct share_mode_entry *e,
+ const char *msg);
+bool init_oplocks(struct smbd_server_connection *sconn);
+void init_kernel_oplocks(struct smbd_server_connection *sconn);
+
+/* The following definitions come from smbd/oplock_linux.c */
+
+int linux_set_lease_sighandler(int fd);
+int linux_setlease(int fd, int leasetype);
+struct kernel_oplocks *linux_init_kernel_oplocks(struct smbd_server_connection *sconn);
+
+/* The following definitions come from smbd/password.c */
+
+void invalidate_vuid(struct smbd_server_connection *sconn, uint64_t vuid);
+int register_homes_share(const char *username);
+
+/* The following definitions come from smbd/pipes.c */
+
+NTSTATUS open_np_file(struct smb_request *smb_req, const char *name,
+ struct files_struct **pfsp);
+
+/* The following definitions come from smbd/posix_acls.c */
+
+mode_t unix_perms_to_acl_perms(mode_t mode, int r_mask, int w_mask, int x_mask);
+int map_acl_perms_to_permset(mode_t mode, SMB_ACL_PERMSET_T *p_permset);
+uint32_t map_canon_ace_perms(int snum,
+ enum security_ace_type *pacl_type,
+ mode_t perms,
+ bool directory_ace);
+bool current_user_in_group(connection_struct *conn, gid_t gid);
+SMB_ACL_T free_empty_sys_acl(connection_struct *conn, SMB_ACL_T the_acl);
+NTSTATUS posix_fget_nt_acl(struct files_struct *fsp, uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc);
+NTSTATUS chown_if_needed(files_struct *fsp, uint32_t security_info_sent,
+ const struct security_descriptor *psd,
+ bool *did_chown);
+NTSTATUS set_nt_acl(files_struct *fsp, uint32_t security_info_sent, const struct security_descriptor *psd);
+int get_acl_group_bits(connection_struct *conn,
+ struct files_struct *fsp,
+ mode_t *mode);
+int inherit_access_posix_acl(connection_struct *conn,
+ struct files_struct *inherit_from_dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode);
+NTSTATUS set_unix_posix_default_acl(connection_struct *conn,
+ files_struct *fsp,
+ uint16_t num_def_acls, const char *pdata);
+NTSTATUS set_unix_posix_acl(connection_struct *conn, files_struct *fsp,
+ uint16_t num_acls,
+ const char *pdata);
+int posix_sys_acl_blob_get_file(vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob);
+int posix_sys_acl_blob_get_fd(vfs_handle_struct *handle,
+ files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob);
+
+enum default_acl_style {DEFAULT_ACL_POSIX, DEFAULT_ACL_WINDOWS, DEFAULT_ACL_EVERYONE};
+
+const struct enum_list *get_default_acl_style_list(void);
+
+NTSTATUS make_default_filesystem_acl(
+ TALLOC_CTX *ctx,
+ enum default_acl_style acl_style,
+ const char *name,
+ const SMB_STRUCT_STAT *psbuf,
+ struct security_descriptor **ppdesc);
+
+/* The following definitions come from smbd/smb2_process.c */
+
+#if !defined(WITH_SMB1SERVER)
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt);
+#endif
+size_t srv_smb1_set_message(char *buf,
+ size_t num_words,
+ size_t num_bytes,
+ bool zero);
+NTSTATUS read_packet_remainder(int fd, char *buffer,
+ unsigned int timeout, ssize_t len);
+NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel);
+void remove_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid);
+bool schedule_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid);
+bool open_was_deferred(struct smbXsrv_connection *xconn, uint64_t mid);
+bool get_deferred_open_message_state(struct smb_request *smbreq,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec);
+bool push_deferred_open_message_smb(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec);
+bool create_smb1_outbuf(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ const uint8_t *inbuf, char **outbuf,
+ uint8_t num_words, uint32_t num_bytes);
+void construct_smb1_reply_common_req(struct smb_request *req, char *outbuf);
+void reply_smb1_outbuf(struct smb_request *req, uint8_t num_words, uint32_t num_bytes);
+void process_smb(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted);
+void smbd_process(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ int sock_fd,
+ bool interactive);
+bool valid_smb1_header(const uint8_t *inbuf);
+bool init_smb1_request(struct smb_request *req,
+ struct smbd_server_connection *sconn,
+ struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf,
+ size_t unread_bytes, bool encrypted,
+ uint32_t seqnum);
+
+/* The following definitions come from smbd/quotas.c */
+
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize);
+
+/* The following definitions come from smbd/smb2_reply.c */
+
+NTSTATUS check_path_syntax(char *path, bool posix);
+NTSTATUS smb1_strip_dfs_path(TALLOC_CTX *mem_ctx,
+ uint32_t *ucf_flags,
+ char **in_path);
+NTSTATUS smb2_strip_dfs_path(const char *in_path, const char **out_path);
+size_t srvstr_get_path(TALLOC_CTX *ctx,
+ const char *inbuf,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err);
+size_t srvstr_get_path_posix(TALLOC_CTX *ctx,
+ const char *inbuf,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err);
+size_t srvstr_get_path_req(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ char **pp_dest, const char *src, int flags,
+ NTSTATUS *err);
+size_t srvstr_pull_req_talloc(TALLOC_CTX *ctx, struct smb_request *req,
+ char **dest, const uint8_t *src, int flags);
+bool check_fsp_open(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp);
+bool check_fsp(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp);
+bool check_fsp_ntquota_handle(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp);
+void reply_special(struct smbXsrv_connection *xconn, char *inbuf, size_t inbuf_size);
+NTSTATUS unlink_internals(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t dirtype,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname);
+ssize_t fake_sendfile(struct smbXsrv_connection *xconn, files_struct *fsp,
+ off_t startpos, size_t nread);
+ssize_t sendfile_short_send(struct smbXsrv_connection *xconn,
+ files_struct *fsp,
+ ssize_t nread,
+ size_t headersize,
+ size_t smb_maxcnt);
+NTSTATUS rename_internals_fsp(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_dst_in,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists);
+NTSTATUS rename_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists,
+ uint32_t access_mask);
+NTSTATUS copy_file(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ uint32_t new_create_disposition);
+uint64_t get_lock_offset(const uint8_t *data, int data_offset,
+ bool large_file_format);
+
+/* The following definitions come from smbd/seal.c */
+
+bool is_encrypted_packet(const uint8_t *inbuf);
+void srv_free_enc_buffer(struct smbXsrv_connection *xconn, char *buf);
+NTSTATUS srv_decrypt_buffer(struct smbXsrv_connection *xconn, char *buf);
+NTSTATUS srv_encrypt_buffer(struct smbXsrv_connection *xconn, char *buf,
+ char **buf_out);
+NTSTATUS srv_request_encryption_setup(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size);
+NTSTATUS srv_encryption_start(connection_struct *conn);
+void server_encryption_shutdown(struct smbXsrv_connection *xconn);
+
+/* The following definitions come from smbd/sec_ctx.c */
+
+bool unix_token_equal(const struct security_unix_token *t1, const struct security_unix_token *t2);
+bool push_sec_ctx(void);
+void set_sec_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups, const struct security_token *token);
+void set_root_sec_ctx(void);
+bool pop_sec_ctx(void);
+void init_sec_ctx(void);
+const struct security_token *sec_ctx_active_token(void);
+
+/* The following definitions come from smbd/server.c */
+
+struct memcache *smbd_memcache(void);
+bool snum_is_shared_printer(int snum);
+void delete_and_reload_printers(void);
+bool reload_services(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int),
+ bool test);
+
+/* The following definitions come from smbd/server_exit.c */
+
+void smbd_exit_server(const char *reason) _NORETURN_;
+void smbd_exit_server_cleanly(const char *const reason) _NORETURN_;
+
+/* The following definitions come from smbd/smb2_service.c */
+
+bool set_conn_connectpath(connection_struct *conn, const char *connectpath);
+bool canonicalize_connect_path(connection_struct *conn);
+NTSTATUS set_conn_force_user_group(connection_struct *conn, int snum);
+bool chdir_current_service(connection_struct *conn);
+void load_registry_shares(void);
+int add_home_service(const char *service, const char *username, const char *homedir);
+int find_service(TALLOC_CTX *ctx, const char *service, char **p_service_out);
+connection_struct *make_connection_smb2(struct smbd_smb2_request *req,
+ struct smbXsrv_tcon *tcon,
+ int snum,
+ const char *pdev,
+ NTSTATUS *pstatus);
+NTSTATUS make_connection_snum(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ int snum,
+ struct smbXsrv_session *session,
+ const char *pdev);
+void close_cnum(connection_struct *conn,
+ uint64_t vuid,
+ enum file_close_type close_type);
+
+/* The following definitions come from smbd/session.c */
+struct sessionid;
+struct smbXsrv_session;
+bool session_init(void);
+bool session_claim(struct smbXsrv_session *session);
+void session_yield(struct smbXsrv_session *session);
+int list_sessions(TALLOC_CTX *mem_ctx, struct sessionid **session_list);
+int find_sessions(TALLOC_CTX *mem_ctx, const char *username,
+ const char *machine, struct sessionid **session_list);
+
+/* The following definitions come from smbd/share_access.c */
+
+bool token_contains_name_in_list(const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct security_token *token,
+ const char **list);
+bool user_ok_token(const char *username, const char *domain,
+ const struct security_token *token, int snum);
+bool is_share_read_only_for_token(const char *username,
+ const char *domain,
+ const struct security_token *token,
+ connection_struct *conn);
+
+/* The following definitions come from smbd/srvstr.c */
+
+NTSTATUS srvstr_push_fn(const char *base_ptr, uint16_t smb_flags2, void *dest,
+ const char *src, int dest_len, int flags, size_t *ret_len);
+
+/* The following definitions come from smbd/statvfs.c */
+
+int sys_statvfs(const char *path, struct vfs_statvfs_struct *statbuf);
+
+/* The following definitions come from smbd/trans2.c */
+
+char *store_file_unix_basic(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf);
+char *store_file_unix_basic_info2(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf);
+NTSTATUS smb_set_file_disposition_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname);
+NTSTATUS refuse_symlink_fsp(const struct files_struct *fsp);
+NTSTATUS check_any_access_fsp(struct files_struct *fsp,
+ uint32_t access_requested);
+uint64_t smb_roundup(connection_struct *conn, uint64_t val);
+bool samba_private_attr_name(const char *unix_ea_name);
+NTSTATUS get_ea_value_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ const char *ea_name,
+ struct ea_struct *pea);
+NTSTATUS get_ea_names_from_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ char ***pnames,
+ size_t *pnum_names);
+NTSTATUS set_ea(connection_struct *conn, files_struct *fsp,
+ struct ea_list *ea_list);
+unsigned char *create_volume_objectid(connection_struct *conn, unsigned char objid[16]);
+NTSTATUS hardlink_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ bool overwrite_if_exists,
+ const struct smb_filename *smb_fname_old,
+ struct smb_filename *smb_fname_new);
+NTSTATUS smb_set_file_time(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ struct smb_file_time *ft,
+ bool setting_write_time);
+NTSTATUS smb_set_file_size(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ const SMB_STRUCT_STAT *psbuf,
+ off_t size,
+ bool fail_after_createfile);
+
+bool map_info2_flags_to_sbuf(const SMB_STRUCT_STAT *psbuf,
+ const uint32_t smb_fflags,
+ const uint32_t smb_fmask,
+ int *stat_fflags);
+
+enum perm_type {
+ PERM_NEW_FILE,
+ PERM_NEW_DIR,
+ PERM_EXISTING_FILE,
+ PERM_EXISTING_DIR
+};
+
+NTSTATUS unix_perms_from_wire(connection_struct *conn,
+ const SMB_STRUCT_STAT *psbuf,
+ uint32_t perms,
+ enum perm_type ptype,
+ mode_t *ret_perms);
+struct ea_list *read_ea_list(TALLOC_CTX *ctx, const char *pdata,
+ size_t data_size);
+unsigned int estimate_ea_size(files_struct *fsp);
+NTSTATUS smb_set_fsquota(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ const DATA_BLOB *qdata);
+
+/* The following definitions come from smbd/uid.c */
+
+bool change_to_guest(void);
+NTSTATUS check_user_share_access(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint32_t *p_share_access,
+ bool *p_readonly_share);
+bool change_to_user_and_service(connection_struct *conn, uint64_t vuid);
+bool change_to_user_and_service_by_fsp(struct files_struct *fsp);
+bool smbd_change_to_root_user(void);
+bool smbd_become_authenticated_pipe_user(struct auth_session_info *session_info);
+bool smbd_unbecome_authenticated_pipe_user(void);
+void become_root(void);
+void unbecome_root(void);
+void smbd_become_root(void);
+void smbd_unbecome_root(void);
+bool become_user_without_service(connection_struct *conn, uint64_t vuid);
+bool become_user_without_service_by_fsp(struct files_struct *fsp);
+bool become_user_without_service_by_session(connection_struct *conn,
+ const struct auth_session_info *session_info);
+bool unbecome_user_without_service(void);
+uid_t get_current_uid(connection_struct *conn);
+gid_t get_current_gid(connection_struct *conn);
+const struct security_unix_token *get_current_utok(connection_struct *conn);
+const struct security_token *get_current_nttok(connection_struct *conn);
+
+/* The following definitions come from smbd/utmp.c */
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *id_str, int id_num);
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *id_str, int id_num);
+
+/* The following definitions come from smbd/vfs.c */
+
+bool vfs_init_custom(connection_struct *conn, const char *vfs_object);
+bool smbd_vfs_init(connection_struct *conn);
+NTSTATUS vfs_file_exist(connection_struct *conn, struct smb_filename *smb_fname);
+bool vfs_valid_pread_range(off_t offset, size_t length);
+bool vfs_valid_pwrite_range(off_t offset, size_t length);
+ssize_t vfs_pwrite_data(struct smb_request *req,
+ files_struct *fsp,
+ const char *buffer,
+ size_t N,
+ off_t offset);
+int vfs_allocate_file_space(files_struct *fsp, uint64_t len);
+int vfs_set_filelen(files_struct *fsp, off_t len);
+int vfs_slow_fallocate(files_struct *fsp, off_t offset, off_t len);
+int vfs_fill_sparse(files_struct *fsp, off_t len);
+int vfs_set_blocking(files_struct *fsp, bool set);
+off_t vfs_transfer_file(files_struct *in, files_struct *out, off_t n);
+const char *vfs_readdirname(connection_struct *conn,
+ struct files_struct *dirfsp,
+ DIR *d,
+ char **talloced);
+int vfs_ChDir(connection_struct *conn,
+ const struct smb_filename *smb_fname);
+struct smb_filename *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn);
+int vfs_stat(struct connection_struct *conn,
+ struct smb_filename *smb_fname);
+int vfs_stat_smb_basename(struct connection_struct *conn,
+ const struct smb_filename *smb_fname_in,
+ SMB_STRUCT_STAT *psbuf);
+NTSTATUS vfs_stat_fsp(files_struct *fsp);
+NTSTATUS vfs_fstreaminfo(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams);
+void init_smb_file_time(struct smb_file_time *ft);
+int vfs_fake_fd(void);
+int vfs_fake_fd_close(int fd);
+
+/* The following definitions come from smbd/avahi_register.c */
+
+void *avahi_start_register(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ uint16_t port);
+
+/* The following definitions come from smbd/smb2_create.c */
+
+NTSTATUS vfs_default_durable_cookie(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie_blob);
+NTSTATUS vfs_default_durable_disconnect(struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie);
+NTSTATUS vfs_default_durable_reconnect(struct connection_struct *conn,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ files_struct **result,
+ DATA_BLOB *new_cookie);
+
+struct smb3_file_posix_information;
+void smb3_file_posix_information_init(
+ connection_struct *conn,
+ const struct stat_ex *st,
+ uint32_t reparse_tag,
+ uint32_t dos_attributes,
+ struct smb3_file_posix_information *dst);
+
+#endif /* _SMBD_PROTO_H_ */
diff --git a/source3/smbd/pysmbd.c b/source3/smbd/pysmbd.c
new file mode 100644
index 0000000..e1f889d
--- /dev/null
+++ b/source3/smbd/pysmbd.c
@@ -0,0 +1,1305 @@
+/*
+ Unix SMB/CIFS implementation.
+ Set NT and POSIX ACLs and other VFS operations from Python
+
+ Copyrigyt (C) Andrew Bartlett 2012
+ Copyright (C) Jeremy Allison 1994-2009.
+ Copyright (C) Andreas Gruenbacher 2002.
+ Copyright (C) Simo Sorce <idra@samba.org> 2009.
+ Copyright (C) Simo Sorce 2002
+ Copyright (C) Eric Lorimer 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lib/replace/system/python.h"
+#include "includes.h"
+#include "python/py3compat.h"
+#include "python/modules.h"
+#include "smbd/smbd.h"
+#include "libcli/util/pyerrors.h"
+#include "librpc/rpc/pyrpc_util.h"
+#include <pytalloc.h>
+#include "system/filesys.h"
+#include "passdb.h"
+#include "secrets.h"
+#include "auth.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ACLS
+
+#ifdef O_DIRECTORY
+#define DIRECTORY_FLAGS O_RDONLY|O_DIRECTORY
+#else
+/* POSIX allows us to open a directory with O_RDONLY. */
+#define DIRECTORY_FLAGS O_RDONLY
+#endif
+
+
+static connection_struct *get_conn_tos(
+ const char *service,
+ const struct auth_session_info *session_info)
+{
+ struct conn_struct_tos *c = NULL;
+ int snum = -1;
+ NTSTATUS status;
+ char *cwd = NULL;
+ struct smb_filename cwd_fname = {0};
+ int ret;
+
+ if (!posix_locking_init(false)) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (service) {
+ snum = lp_servicenumber(service);
+ if (snum == -1) {
+ PyErr_SetString(PyExc_RuntimeError, "unknown service");
+ return NULL;
+ }
+ }
+
+ /*
+ * Make sure that session unix info is filled,
+ * which is required by vfs operations.
+ */
+ if (session_info->unix_info == NULL) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Session unix info not initialized");
+ return NULL;
+ }
+ if (session_info->unix_info->unix_name == NULL) {
+ PyErr_SetString(PyExc_RuntimeError,
+ "Session unix info not available");
+ return NULL;
+ }
+
+ status = create_conn_struct_tos(NULL,
+ snum,
+ "/",
+ session_info,
+ &c);
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+
+ /* Ignore read-only and share restrictions */
+ c->conn->read_only = false;
+ c->conn->share_access = SEC_RIGHTS_FILE_ALL;
+
+ /* Provided by libreplace if not present. Always mallocs. */
+ cwd = get_current_dir_name();
+ if (cwd == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ cwd_fname.base_name = cwd;
+ /*
+ * We need to call vfs_ChDir() to initialize
+ * conn->cwd_fsp correctly. Change directory
+ * to current directory (so no change for process).
+ */
+ ret = vfs_ChDir(c->conn, &cwd_fname);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ SAFE_FREE(cwd);
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+ }
+
+ SAFE_FREE(cwd);
+
+ return c->conn;
+}
+
+static int set_sys_acl_conn(const char *fname,
+ SMB_ACL_TYPE_T acltype,
+ SMB_ACL_T theacl, connection_struct *conn)
+{
+ int ret;
+ struct smb_filename *smb_fname = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return -1;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ ret = SMB_VFS_SYS_ACL_SET_FD(smb_fname->fsp, acltype, theacl);
+
+ status = fd_close(smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ errno = map_errno_from_nt_status(status);
+ return -1;
+ }
+
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+
+static NTSTATUS init_files_struct(TALLOC_CTX *mem_ctx,
+ const char *fname,
+ struct connection_struct *conn,
+ int flags,
+ struct files_struct **_fsp)
+{
+ struct vfs_open_how how = { .flags = flags, .mode = 0644 };
+ struct smb_filename *smb_fname = NULL;
+ int fd;
+ mode_t saved_umask;
+ struct files_struct *fsp;
+ struct files_struct *fspcwd = NULL;
+ NTSTATUS status;
+
+ fsp = talloc_zero(mem_ctx, struct files_struct);
+ if (fsp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->fh = fd_handle_create(fsp);
+ if (fsp->fh == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->conn = conn;
+
+ smb_fname = synthetic_smb_fname_split(fsp,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fsp_name = smb_fname;
+
+ status = vfs_at_fspcwd(fsp, conn, &fspcwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * we want total control over the permissions on created files,
+ * so set our umask to 0 (this matters if flags contains O_CREAT)
+ */
+ saved_umask = umask(0);
+
+ fd = SMB_VFS_OPENAT(conn,
+ fspcwd,
+ smb_fname,
+ fsp,
+ &how);
+
+ umask(saved_umask);
+
+ if (fd == -1) {
+ int err = errno;
+ if (err == ENOENT) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ fsp_set_fd(fsp, fd);
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* If we have an fd, this stat should succeed. */
+ DEBUG(0,("Error doing fstat on open file %s (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status) ));
+ return status;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ fsp->vuid = UID_FIELD_INVALID;
+ fsp->file_pid = 0;
+ fsp->fsp_flags.can_lock = true;
+ fsp->fsp_flags.can_read = true;
+ fsp->fsp_flags.can_write = true;
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = S_ISDIR(smb_fname->st.st_ex_mode);
+
+ *_fsp = fsp;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS set_nt_acl_conn(const char *fname,
+ uint32_t security_info_sent, const struct security_descriptor *sd,
+ connection_struct *conn)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct files_struct *fsp = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ /* first, try to open it as a file with flag O_RDWR */
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ O_RDWR,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status) && errno == EISDIR) {
+ /* if fail, try to open as dir */
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ DIRECTORY_FLAGS,
+ &fsp);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_files_struct failed: %s\n",
+ nt_errstr(status));
+ if (fsp != NULL) {
+ fd_close(fsp);
+ }
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = SMB_VFS_FSET_NT_ACL(metadata_fsp(fsp), security_info_sent, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("set_nt_acl_conn: fset_nt_acl returned %s.\n", nt_errstr(status)));
+ }
+
+ fd_close(fsp);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS get_nt_acl_conn(TALLOC_CTX *mem_ctx,
+ const char *fname,
+ connection_struct *conn,
+ uint32_t security_info_wanted,
+ struct security_descriptor **sd)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct smb_filename *smb_fname = NULL;
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(smb_fname->fsp),
+ security_info_wanted,
+ mem_ctx,
+ sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("fget_nt_acl_at returned %s.\n",
+ nt_errstr(status));
+ }
+
+ status = fd_close(smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+static int set_acl_entry_perms(SMB_ACL_ENTRY_T entry, mode_t perm_mask)
+{
+ SMB_ACL_PERMSET_T perms = NULL;
+
+ if (sys_acl_get_permset(entry, &perms) != 0) {
+ return -1;
+ }
+
+ if (sys_acl_clear_perms(perms) != 0) {
+ return -1;
+ }
+
+ if ((perm_mask & SMB_ACL_READ) != 0 &&
+ sys_acl_add_perm(perms, SMB_ACL_READ) != 0) {
+ return -1;
+ }
+
+ if ((perm_mask & SMB_ACL_WRITE) != 0 &&
+ sys_acl_add_perm(perms, SMB_ACL_WRITE) != 0) {
+ return -1;
+ }
+
+ if ((perm_mask & SMB_ACL_EXECUTE) != 0 &&
+ sys_acl_add_perm(perms, SMB_ACL_EXECUTE) != 0) {
+ return -1;
+ }
+
+ if (sys_acl_set_permset(entry, perms) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static SMB_ACL_T make_simple_acl(TALLOC_CTX *mem_ctx,
+ gid_t gid,
+ mode_t chmod_mode)
+{
+ mode_t mode = SMB_ACL_READ|SMB_ACL_WRITE|SMB_ACL_EXECUTE;
+
+ mode_t mode_user = (chmod_mode & 0700) >> 6;
+ mode_t mode_group = (chmod_mode & 070) >> 3;
+ mode_t mode_other = chmod_mode & 07;
+ SMB_ACL_ENTRY_T entry;
+ SMB_ACL_T acl = sys_acl_init(mem_ctx);
+
+ if (!acl) {
+ return NULL;
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_USER_OBJ) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_user) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_GROUP_OBJ) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_group) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_OTHER) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_other) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (gid != -1) {
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_GROUP) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_qualifier(entry, &gid) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode_group) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+ }
+
+ if (sys_acl_create_entry(&acl, &entry) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (sys_acl_set_tag_type(entry, SMB_ACL_MASK) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ if (set_acl_entry_perms(entry, mode) != 0) {
+ TALLOC_FREE(acl);
+ return NULL;
+ }
+
+ return acl;
+}
+
+/*
+ set a simple ACL on a file, as a test
+ */
+static PyObject *py_smbd_set_simple_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "mode",
+ "session_info",
+ "gid",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ int ret;
+ int mode, gid = -1;
+ SMB_ACL_T acl;
+ TALLOC_CTX *frame;
+ connection_struct *conn;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO|iz",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &mode,
+ &py_session,
+ &gid,
+ &service))
+ return NULL;
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+
+ acl = make_simple_acl(frame, gid, mode);
+ if (acl == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ ret = set_sys_acl_conn(fname, SMB_ACL_TYPE_ACCESS, acl, conn);
+
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ errno = ret;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ TALLOC_FREE(frame);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ chown a file
+ */
+static PyObject *py_smbd_chown(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "uid",
+ "gid",
+ "session_info",
+ "service",
+ NULL
+ };
+ connection_struct *conn;
+ int ret;
+ NTSTATUS status;
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ int uid, gid;
+ TALLOC_CTX *frame;
+ struct files_struct *fsp = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siiO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &uid,
+ &gid,
+ &py_session,
+ &service))
+ return NULL;
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ frame = talloc_stackframe();
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ /* first, try to open it as a file with flag O_RDWR */
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ O_RDWR,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status) && errno == EISDIR) {
+ /* if fail, try to open as dir */
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ DIRECTORY_FLAGS,
+ &fsp);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_files_struct failed: %s\n",
+ nt_errstr(status));
+ if (fsp != NULL) {
+ fd_close(fsp);
+ }
+ TALLOC_FREE(frame);
+ /*
+ * The following macro raises a python
+ * error then returns NULL.
+ */
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+ }
+
+ ret = SMB_VFS_FCHOWN(fsp, uid, gid);
+ if (ret != 0) {
+ int saved_errno = errno;
+ fd_close(fsp);
+ TALLOC_FREE(frame);
+ errno = saved_errno;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ fd_close(fsp);
+ TALLOC_FREE(frame);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ unlink a file
+ */
+static PyObject *py_smbd_unlink(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "session_info",
+ "service",
+ NULL
+ };
+ connection_struct *conn;
+ int ret;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *at_fname = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ char *fname, *service = NULL;
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+
+ frame = talloc_stackframe();
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &py_session ,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return PyErr_NoMemory();
+ }
+
+ status = parent_pathref(frame,
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &at_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return PyErr_NoMemory();
+ }
+
+ ret = SMB_VFS_UNLINKAT(conn,
+ parent_fname->fsp,
+ at_fname,
+ 0);
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ errno = ret;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ TALLOC_FREE(frame);
+
+ Py_RETURN_NONE;
+}
+
+/*
+ check if we have ACL support
+ */
+static PyObject *py_smbd_have_posix_acls(PyObject *self,
+ PyObject *Py_UNUSED(ignored))
+{
+#ifdef HAVE_POSIX_ACLS
+ return PyBool_FromLong(true);
+#else
+ return PyBool_FromLong(false);
+#endif
+}
+
+/*
+ set the NT ACL on a file
+ */
+static PyObject *py_smbd_set_nt_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "security_info_sent",
+ "sd",
+ "session_info",
+ "service",
+ NULL
+ };
+
+ NTSTATUS status;
+ char *fname, *service = NULL;
+ int security_info_sent;
+ PyObject *py_sd;
+ struct security_descriptor *sd;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ connection_struct *conn;
+ TALLOC_CTX *frame;
+
+ frame = talloc_stackframe();
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siOO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &security_info_sent,
+ &py_sd,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_sd, "samba.dcerpc.security", "descriptor")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ sd = pytalloc_get_type(py_sd, struct security_descriptor);
+
+ status = set_nt_acl_conn(fname, security_info_sent, sd, conn);
+ TALLOC_FREE(frame);
+ if (NT_STATUS_IS_ERR(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * This will show up as a FileNotFoundError in python.
+ */
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, fname);
+ } else {
+ PyErr_SetNTSTATUS(status);
+ }
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+/*
+ Return the NT ACL on a file
+ */
+static PyObject *py_smbd_get_nt_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "security_info_wanted",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ int security_info_wanted;
+ PyObject *py_sd;
+ struct security_descriptor *sd;
+ TALLOC_CTX *frame = talloc_stackframe();
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ connection_struct *conn;
+ NTSTATUS status;
+ int ret = 1;
+
+ ret = PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "siO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &security_info_wanted,
+ &py_session,
+ &service);
+ if (!ret) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(
+ PyExc_TypeError,
+ "Expected auth_session_info for "
+ "session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = get_nt_acl_conn(frame, fname, conn, security_info_wanted, &sd);
+ if (NT_STATUS_IS_ERR(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * This will show up as a FileNotFoundError in python,
+ * from which samba-tool can at least produce a short
+ * message containing the problematic filename.
+ */
+ PyErr_SetFromErrnoWithFilename(PyExc_OSError, fname);
+ } else {
+ PyErr_SetNTSTATUS(status);
+ }
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ py_sd = py_return_ndr_struct("samba.dcerpc.security", "descriptor", sd, sd);
+
+ TALLOC_FREE(frame);
+
+ return py_sd;
+}
+
+/*
+ set the posix (or similar) ACL on a file
+ */
+static PyObject *py_smbd_set_sys_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "acl_type",
+ "acl",
+ "session_info",
+ "service",
+ NULL
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret;
+ char *fname, *service = NULL;
+ PyObject *py_acl;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ struct smb_acl_t *acl;
+ int acl_type;
+ connection_struct *conn;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siOO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &acl_type,
+ &py_acl,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_acl, "samba.dcerpc.smb_acl", "t")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ acl = pytalloc_get_type(py_acl, struct smb_acl_t);
+
+ ret = set_sys_acl_conn(fname, acl_type, acl, conn);
+ if (ret != 0) {
+ TALLOC_FREE(frame);
+ errno = ret;
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ TALLOC_FREE(frame);
+ Py_RETURN_NONE;
+}
+
+/*
+ Return the posix (or similar) ACL on a file
+ */
+static PyObject *py_smbd_get_sys_acl(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "acl_type",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname;
+ PyObject *py_acl;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ struct smb_acl_t *acl;
+ int acl_type;
+ TALLOC_CTX *frame = talloc_stackframe();
+ connection_struct *conn;
+ char *service = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "siO|z",
+ discard_const_p(char *, kwnames),
+ &fname,
+ &acl_type,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_fname = synthetic_smb_fname_split(frame,
+ fname,
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ acl = SMB_VFS_SYS_ACL_GET_FD(smb_fname->fsp, acl_type, frame);
+ if (!acl) {
+ TALLOC_FREE(frame);
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+
+ status = fd_close(smb_fname->fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ PyErr_SetNTSTATUS(status);
+ return NULL;
+ }
+
+ py_acl = py_return_ndr_struct("samba.dcerpc.smb_acl", "t", acl, acl);
+
+ TALLOC_FREE(frame);
+
+ return py_acl;
+}
+
+static PyObject *py_smbd_mkdir(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct connection_struct *conn = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_name = NULL;
+ NTSTATUS status;
+ int ret;
+ mode_t saved_umask;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "sO|z",
+ discard_const_p(char *,
+ kwnames),
+ &fname,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ fname,
+ NULL,
+ NULL,
+ 0,
+ lp_posix_pathnames() ?
+ SMB_FILENAME_POSIX_PATH : 0);
+
+ if (smb_fname == NULL) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &base_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ /* we want total control over the permissions on created files,
+ so set our umask to 0 */
+ saved_umask = umask(0);
+
+ ret = SMB_VFS_MKDIRAT(conn,
+ parent_fname->fsp,
+ base_name,
+ 00755);
+
+ umask(saved_umask);
+
+ if (ret == -1) {
+ DBG_ERR("mkdirat error=%d (%s)\n", errno, strerror(errno));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ TALLOC_FREE(frame);
+ Py_RETURN_NONE;
+}
+
+
+/*
+ Create an empty file
+ */
+static PyObject *py_smbd_create_file(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ const char * const kwnames[] = {
+ "fname",
+ "session_info",
+ "service",
+ NULL
+ };
+ char *fname, *service = NULL;
+ PyObject *py_session = Py_None;
+ struct auth_session_info *session_info = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct connection_struct *conn = NULL;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwargs,
+ "sO|z",
+ discard_const_p(char *,
+ kwnames),
+ &fname,
+ &py_session,
+ &service)) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_session,
+ "samba.dcerpc.auth",
+ "session_info")) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+ session_info = pytalloc_get_type(py_session,
+ struct auth_session_info);
+ if (session_info == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected auth_session_info for session_info argument got %s",
+ pytalloc_get_name(py_session));
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ conn = get_conn_tos(service, session_info);
+ if (!conn) {
+ TALLOC_FREE(frame);
+ return NULL;
+ }
+
+ status = init_files_struct(frame,
+ fname,
+ conn,
+ O_CREAT|O_EXCL|O_RDWR,
+ &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("init_files_struct failed: %s\n",
+ nt_errstr(status));
+ } else if (fsp != NULL) {
+ fd_close(fsp);
+ }
+
+ TALLOC_FREE(frame);
+ PyErr_NTSTATUS_NOT_OK_RAISE(status);
+ Py_RETURN_NONE;
+}
+
+
+static PyMethodDef py_smbd_methods[] = {
+ { "have_posix_acls",
+ (PyCFunction)py_smbd_have_posix_acls, METH_NOARGS,
+ NULL },
+ { "set_simple_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_set_simple_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "set_nt_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_set_nt_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "get_nt_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_get_nt_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "get_sys_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_get_sys_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "set_sys_acl",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_set_sys_acl),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "chown",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_chown),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "unlink",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_unlink),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "mkdir",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_mkdir),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ { "create_file",
+ PY_DISCARD_FUNC_SIG(PyCFunction, py_smbd_create_file),
+ METH_VARARGS|METH_KEYWORDS,
+ NULL },
+ {0}
+};
+
+void initsmbd(void);
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "smbd",
+ .m_doc = "Python bindings for the smbd file server.",
+ .m_size = -1,
+ .m_methods = py_smbd_methods,
+};
+
+MODULE_INIT_FUNC(smbd)
+{
+ PyObject *m = NULL;
+
+ m = PyModule_Create(&moduledef);
+ return m;
+}
diff --git a/source3/smbd/quotas.c b/source3/smbd/quotas.c
new file mode 100644
index 0000000..40fb3ee
--- /dev/null
+++ b/source3/smbd/quotas.c
@@ -0,0 +1,497 @@
+/*
+ Unix SMB/CIFS implementation.
+ support for quotas
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+/*
+ * This is one of the most system dependent parts of Samba, and its
+ * done a little differently. Each system has its own way of doing
+ * things :-(
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/filesys.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_QUOTA
+
+#ifndef HAVE_SYS_QUOTAS
+
+/* just a quick hack because sysquotas.h is included before linux/quota.h */
+#ifdef QUOTABLOCK_SIZE
+#undef QUOTABLOCK_SIZE
+#endif
+
+#ifdef WITH_QUOTAS
+
+#if defined(SUNOS5) /* Solaris */
+
+#include <fcntl.h>
+#include <sys/param.h>
+#include <sys/fs/ufs_quota.h>
+#include <sys/mnttab.h>
+#include <sys/mntent.h>
+
+/****************************************************************************
+ Allows querying of remote hosts for quotas on NFS mounted shares.
+ Supports normal NFS and AMD mounts.
+ Alan Romeril <a.romeril@ic.ac.uk> July 2K.
+****************************************************************************/
+
+#include <rpc/rpc.h>
+#include <rpc/types.h>
+#include <rpcsvc/rquota.h>
+#include <rpc/nettype.h>
+#include <rpc/xdr.h>
+
+static int my_xdr_getquota_rslt(XDR *xdrsp, struct getquota_rslt *gqr)
+{
+ int quotastat;
+
+ if (!xdr_int(xdrsp, &quotastat)) {
+ DEBUG(6,("nfs_quotas: Status bad or zero\n"));
+ return 0;
+ }
+ gqr->status = quotastat;
+
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsize)) {
+ DEBUG(6,("nfs_quotas: Block size bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_bool(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_active)) {
+ DEBUG(6,("nfs_quotas: Active bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bhardlimit)) {
+ DEBUG(6,("nfs_quotas: Hardlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_bsoftlimit)) {
+ DEBUG(6,("nfs_quotas: Softlimit bad or zero\n"));
+ return 0;
+ }
+ if (!xdr_int(xdrsp, &gqr->getquota_rslt_u.gqr_rquota.rq_curblocks)) {
+ DEBUG(6,("nfs_quotas: Currentblocks bad or zero\n"));
+ return 0;
+ }
+ return (1);
+}
+
+static int my_xdr_getquota_args(XDR *xdrsp, struct getquota_args *args)
+{
+ if (!xdr_string(xdrsp, &args->gqa_pathp, RQ_PATHLEN ))
+ return(0);
+ if (!xdr_int(xdrsp, &args->gqa_uid))
+ return(0);
+ return (1);
+}
+
+/* Restricted to SUNOS5 for the moment, I haven`t access to others to test. */
+static bool nfs_quotas(char *nfspath, uid_t euser_id, uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ uid_t uid = euser_id;
+ struct dqblk D;
+ char *mnttype = nfspath;
+ CLIENT *clnt;
+ struct getquota_rslt gqr;
+ struct getquota_args args;
+ char *cutstr, *pathname, *host, *testpath;
+ int len;
+ static struct timeval timeout = {2,0};
+ enum clnt_stat clnt_stat;
+ bool ret = True;
+
+ *bsize = *dfree = *dsize = (uint64_t)0;
+
+ len=strcspn(mnttype, ":");
+ pathname=strstr(mnttype, ":");
+ cutstr = (char *) SMB_MALLOC(len+1);
+ if (!cutstr)
+ return False;
+
+ memset(cutstr, '\0', len+1);
+ host = strncat(cutstr,mnttype, sizeof(char) * len );
+ DEBUG(5,("nfs_quotas: looking for mount on \"%s\"\n", cutstr));
+ DEBUG(5,("nfs_quotas: of path \"%s\"\n", mnttype));
+ testpath=strchr_m(mnttype, ':');
+ args.gqa_pathp = testpath+1;
+ args.gqa_uid = uid;
+
+ DEBUG(5,("nfs_quotas: Asking for host \"%s\" rpcprog \"%i\" rpcvers \"%i\" network \"%s\"\n", host, RQUOTAPROG, RQUOTAVERS, "udp"));
+
+ if ((clnt = clnt_create(host, RQUOTAPROG, RQUOTAVERS, "udp")) == NULL) {
+ ret = False;
+ goto out;
+ }
+
+ clnt->cl_auth = authunix_create_default();
+ DEBUG(9,("nfs_quotas: auth_success\n"));
+
+ clnt_stat=clnt_call(clnt, RQUOTAPROC_GETQUOTA, my_xdr_getquota_args, (caddr_t)&args, my_xdr_getquota_rslt, (caddr_t)&gqr, timeout);
+
+ if (clnt_stat != RPC_SUCCESS) {
+ DEBUG(9,("nfs_quotas: clnt_call fail\n"));
+ ret = False;
+ goto out;
+ }
+
+ /*
+ * gqr.status returns 1 if quotas exist, 2 if there is
+ * no quota set, and 3 if no permission to get the quota.
+ * If 3, return something sensible.
+ */
+
+ switch (gqr.status) {
+ case 1:
+ DEBUG(9,("nfs_quotas: Good quota data\n"));
+ D.dqb_bsoftlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit;
+ D.dqb_bhardlimit = gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit;
+ D.dqb_curblocks = gqr.getquota_rslt_u.gqr_rquota.rq_curblocks;
+ break;
+
+ case 2:
+ case 3:
+ D.dqb_bsoftlimit = 1;
+ D.dqb_curblocks = 1;
+ DEBUG(9,("nfs_quotas: Remote Quotas returned \"%i\" \n", gqr.status));
+ break;
+
+ default:
+ DEBUG(9, ("nfs_quotas: Unknown Remote Quota Status \"%i\"\n",
+ gqr.status));
+ ret = false;
+ goto out;
+ }
+
+ DEBUG(10,("nfs_quotas: Let`s look at D a bit closer... status \"%i\" bsize \"%i\" active? \"%i\" bhard \"%i\" bsoft \"%i\" curb \"%i\" \n",
+ gqr.status,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsize,
+ gqr.getquota_rslt_u.gqr_rquota.rq_active,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bhardlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_bsoftlimit,
+ gqr.getquota_rslt_u.gqr_rquota.rq_curblocks));
+
+ *bsize = gqr.getquota_rslt_u.gqr_rquota.rq_bsize;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+
+ out:
+
+ if (clnt) {
+ if (clnt->cl_auth)
+ auth_destroy(clnt->cl_auth);
+ clnt_destroy(clnt);
+ }
+
+ DEBUG(5,("nfs_quotas: For path \"%s\" returning bsize %.0f, dfree %.0f, dsize %.0f\n",args.gqa_pathp,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ SAFE_FREE(cutstr);
+ DEBUG(10,("nfs_quotas: End of nfs_quotas\n" ));
+ return ret;
+}
+
+/****************************************************************************
+try to get the disk space from disk quotas (SunOS & Solaris2 version)
+Quota code by Peter Urbanec (amiga@cse.unsw.edu.au).
+****************************************************************************/
+
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ uid_t euser_id;
+ int ret;
+ struct dqblk D;
+ struct quotctl command;
+ int file;
+ struct mnttab mnt;
+ char *name = NULL;
+ FILE *fd;
+ SMB_STRUCT_STAT sbuf;
+ SMB_DEV_T devno;
+ bool found = false;
+ const char *path = fname->base_name;
+
+ euser_id = geteuid();
+
+ devno = fname->st.st_ex_dev;
+ DEBUG(5,("disk_quotas: looking for path \"%s\" devno=%x\n",
+ path, (unsigned int)devno));
+ if ((fd = fopen(MNTTAB, "r")) == NULL) {
+ return false;
+ }
+
+ while (getmntent(fd, &mnt) == 0) {
+ if (sys_stat(mnt.mnt_mountp, &sbuf, false) == -1) {
+ continue;
+ }
+
+ DEBUG(5,("disk_quotas: testing \"%s\" devno=%x\n",
+ mnt.mnt_mountp, (unsigned int)devno));
+
+ /* quotas are only on vxfs, UFS or NFS */
+ if ((sbuf.st_ex_dev == devno) && (
+ strcmp( mnt.mnt_fstype, MNTTYPE_UFS ) == 0 ||
+ strcmp( mnt.mnt_fstype, "nfs" ) == 0 ||
+ strcmp( mnt.mnt_fstype, "vxfs" ) == 0 )) {
+ found = true;
+ name = talloc_asprintf(talloc_tos(),
+ "%s/quotas",
+ mnt.mnt_mountp);
+ break;
+ }
+ }
+
+ fclose(fd);
+ if (!found) {
+ return false;
+ }
+
+ if (!name) {
+ return false;
+ }
+ become_root();
+
+ if (strcmp(mnt.mnt_fstype, "nfs") == 0) {
+ bool retval;
+ DEBUG(5,("disk_quotas: looking for mountpath (NFS) \"%s\"\n",
+ mnt.mnt_special));
+ retval = nfs_quotas(mnt.mnt_special,
+ euser_id, bsize, dfree, dsize);
+ unbecome_root();
+ return retval;
+ }
+
+ DEBUG(5,("disk_quotas: looking for quotas file \"%s\"\n", name));
+ if((file=open(name, O_RDONLY,0))<0) {
+ unbecome_root();
+ return false;
+ }
+ command.op = Q_GETQUOTA;
+ command.uid = euser_id;
+ command.addr = (caddr_t) &D;
+ ret = ioctl(file, Q_QUOTACTL, &command);
+ close(file);
+
+ unbecome_root();
+
+ if (ret < 0) {
+ DEBUG(5,("disk_quotas ioctl (Solaris) failed. Error = %s\n",
+ strerror(errno) ));
+
+ return false;
+ }
+
+ /* If softlimit is zero, set it equal to hardlimit.
+ */
+
+ if (D.dqb_bsoftlimit==0) {
+ D.dqb_bsoftlimit = D.dqb_bhardlimit;
+ }
+
+ /* Use softlimit to determine disk space. A user exceeding the quota
+ * is told that there's no space left. Writes might actually work for
+ * a bit if the hardlimit is set higher than softlimit. Effectively
+ * the disk becomes made of rubber latex and begins to expand to
+ * accommodate the user :-)
+ */
+
+ if (D.dqb_bsoftlimit==0)
+ return(False);
+ *bsize = DEV_BSIZE;
+ *dsize = D.dqb_bsoftlimit;
+
+ if (D.dqb_curblocks > D.dqb_bsoftlimit) {
+ *dfree = 0;
+ *dsize = D.dqb_curblocks;
+ } else {
+ *dfree = D.dqb_bsoftlimit - D.dqb_curblocks;
+ }
+
+ DEBUG(5,("disk_quotas for path \"%s\" returning "
+ "bsize %.0f, dfree %.0f, dsize %.0f\n",
+ path,(double)*bsize,(double)*dfree,(double)*dsize));
+
+ return true;
+}
+
+#endif /* Solaris */
+
+#else /* WITH_QUOTAS */
+
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ (*bsize) = 512; /* This value should be ignored */
+
+ /* And just to be sure we set some values that hopefully */
+ /* will be larger that any possible real-world value */
+ (*dfree) = (uint64_t)-1;
+ (*dsize) = (uint64_t)-1;
+
+ /* As we have select not to use quotas, always fail */
+ return false;
+}
+#endif /* WITH_QUOTAS */
+
+#else /* HAVE_SYS_QUOTAS */
+/* wrapper to the new sys_quota interface
+ this file should be removed later
+ */
+bool disk_quotas(connection_struct *conn, struct smb_filename *fname,
+ uint64_t *bsize, uint64_t *dfree, uint64_t *dsize)
+{
+ int r;
+ SMB_DISK_QUOTA D;
+ unid_t id;
+
+ /*
+ * First of all, check whether user quota is
+ * enforced. If the call fails, assume it is
+ * not enforced.
+ */
+ ZERO_STRUCT(D);
+ id.uid = -1;
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_USER_FS_QUOTA_TYPE,
+ id, &D);
+ if (r == -1 && errno != ENOSYS) {
+ goto try_group_quota;
+ }
+ if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
+ goto try_group_quota;
+ }
+
+ ZERO_STRUCT(D);
+ id.uid = geteuid();
+
+ /* if new files created under this folder get this
+ * folder's UID, then available space is governed by
+ * the quota of the folder's UID, not the creating user.
+ */
+ if (lp_inherit_owner(SNUM(conn)) != INHERIT_OWNER_NO &&
+ id.uid != fname->st.st_ex_uid && id.uid != sec_initial_uid()) {
+ int save_errno;
+
+ id.uid = fname->st.st_ex_uid;
+ become_root();
+ r = SMB_VFS_GET_QUOTA(conn, fname,
+ SMB_USER_QUOTA_TYPE, id, &D);
+ save_errno = errno;
+ unbecome_root();
+ errno = save_errno;
+ } else {
+ r = SMB_VFS_GET_QUOTA(conn, fname,
+ SMB_USER_QUOTA_TYPE, id, &D);
+ }
+
+ if (r == -1) {
+ goto try_group_quota;
+ }
+
+ *bsize = D.bsize;
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.softlimit && D.curblocks >= D.softlimit) ||
+ (D.hardlimit && D.curblocks >= D.hardlimit) ||
+ (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
+ (D.ihardlimit && D.curinodes>=D.ihardlimit)
+ ) {
+ *dfree = 0;
+ *dsize = D.curblocks;
+ } else if (D.softlimit==0 && D.hardlimit==0) {
+ goto try_group_quota;
+ } else {
+ if (D.softlimit == 0) {
+ D.softlimit = D.hardlimit;
+ }
+ *dfree = D.softlimit - D.curblocks;
+ *dsize = D.softlimit;
+ }
+
+ return True;
+
+try_group_quota:
+ /*
+ * First of all, check whether group quota is
+ * enforced. If the call fails, assume it is
+ * not enforced.
+ */
+ ZERO_STRUCT(D);
+ id.gid = -1;
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_FS_QUOTA_TYPE,
+ id, &D);
+ if (r == -1 && errno != ENOSYS) {
+ return false;
+ }
+ if (r == 0 && (D.qflags & QUOTAS_DENY_DISK) == 0) {
+ return false;
+ }
+
+ ZERO_STRUCT(D);
+
+ /*
+ * If new files created under this folder get this folder's
+ * GID, then available space is governed by the quota of the
+ * folder's GID, not the primary group of the creating user.
+ */
+ if (VALID_STAT(fname->st) &&
+ S_ISDIR(fname->st.st_ex_mode) &&
+ fname->st.st_ex_mode & S_ISGID) {
+ id.gid = fname->st.st_ex_gid;
+ become_root();
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id,
+ &D);
+ unbecome_root();
+ } else {
+ id.gid = getegid();
+ r = SMB_VFS_GET_QUOTA(conn, fname, SMB_GROUP_QUOTA_TYPE, id,
+ &D);
+ }
+
+ if (r == -1) {
+ return False;
+ }
+
+ *bsize = D.bsize;
+ /* Use softlimit to determine disk space, except when it has been exceeded */
+ if (
+ (D.softlimit && D.curblocks >= D.softlimit) ||
+ (D.hardlimit && D.curblocks >= D.hardlimit) ||
+ (D.isoftlimit && D.curinodes >= D.isoftlimit) ||
+ (D.ihardlimit && D.curinodes>=D.ihardlimit)
+ ) {
+ *dfree = 0;
+ *dsize = D.curblocks;
+ } else if (D.softlimit==0 && D.hardlimit==0) {
+ return False;
+ } else {
+ if (D.softlimit == 0) {
+ D.softlimit = D.hardlimit;
+ }
+ *dfree = D.softlimit - D.curblocks;
+ *dsize = D.softlimit;
+ }
+
+ return (True);
+}
+#endif /* HAVE_SYS_QUOTAS */
diff --git a/source3/smbd/scavenger.c b/source3/smbd/scavenger.c
new file mode 100644
index 0000000..40b2fe5
--- /dev/null
+++ b/source3/smbd/scavenger.c
@@ -0,0 +1,731 @@
+/*
+ Unix SMB/CIFS implementation.
+ smbd scavenger daemon
+
+ Copyright (C) Gregor Beck 2013
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "messages.h"
+#include "serverid.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "smbd/scavenger.h"
+#include "locking/share_mode_lock.h"
+#include "locking/leases_db.h"
+#include "locking/proto.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "lib/util/server_id.h"
+#include "lib/util/util_process.h"
+#include "lib/util/sys_rw_data.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SCAVENGER
+
+struct smbd_scavenger_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct server_id parent_id;
+ struct server_id *scavenger_id;
+ bool am_scavenger;
+};
+
+static struct smbd_scavenger_state *smbd_scavenger_state = NULL;
+
+struct scavenger_message {
+ struct file_id file_id;
+ uint64_t open_persistent_id;
+ NTTIME until;
+};
+
+static int smbd_scavenger_main(struct smbd_scavenger_state *state)
+{
+ struct server_id_buf tmp1, tmp2;
+
+ DEBUG(10, ("scavenger: %s started, parent: %s\n",
+ server_id_str_buf(*state->scavenger_id, &tmp1),
+ server_id_str_buf(state->parent_id, &tmp2)));
+
+ while (true) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret;
+
+ ret = tevent_loop_once(state->ev);
+ if (ret != 0) {
+ DEBUG(2, ("tevent_loop_once failed: %s\n",
+ strerror(errno)));
+ TALLOC_FREE(frame);
+ return 1;
+ }
+
+ DEBUG(10, ("scavenger: %s event loop iteration\n",
+ server_id_str_buf(*state->scavenger_id, &tmp1)));
+ TALLOC_FREE(frame);
+ }
+
+ return 0;
+}
+
+static void smbd_scavenger_done(struct tevent_context *event_ctx, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct smbd_scavenger_state *state = talloc_get_type_abort(
+ private_data, struct smbd_scavenger_state);
+ struct server_id_buf tmp;
+
+ DEBUG(2, ("scavenger: %s died\n",
+ server_id_str_buf(*state->scavenger_id, &tmp)));
+
+ TALLOC_FREE(state->scavenger_id);
+}
+
+static void smbd_scavenger_parent_dead(struct tevent_context *event_ctx,
+ struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct smbd_scavenger_state *state = talloc_get_type_abort(
+ private_data, struct smbd_scavenger_state);
+ struct server_id_buf tmp1, tmp2;
+
+ DEBUG(2, ("scavenger: %s parent %s died\n",
+ server_id_str_buf(*state->scavenger_id, &tmp1),
+ server_id_str_buf(state->parent_id, &tmp2)));
+
+ exit_server_cleanly("smbd_scavenger_parent_dead");
+}
+
+static void scavenger_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ exit_server_cleanly("termination signal");
+}
+
+static void scavenger_setup_sig_term_handler(struct tevent_context *ev_ctx)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(ev_ctx,
+ ev_ctx,
+ SIGTERM, 0,
+ scavenger_sig_term_handler,
+ NULL);
+ if (se == NULL) {
+ exit_server("failed to setup SIGTERM handler");
+ }
+}
+
+static bool smbd_scavenger_running(struct smbd_scavenger_state *state)
+{
+ if (state->scavenger_id == NULL) {
+ return false;
+ }
+
+ return serverid_exists(state->scavenger_id);
+}
+
+static int smbd_scavenger_server_id_destructor(struct server_id *id)
+{
+ return 0;
+}
+
+static bool scavenger_say_hello(int fd, struct server_id self)
+{
+ ssize_t ret;
+ struct server_id_buf tmp;
+
+ ret = write_data(fd, &self, sizeof(self));
+ if (ret == -1) {
+ DEBUG(2, ("Failed to write to pipe: %s\n", strerror(errno)));
+ return false;
+ }
+ if (ret < sizeof(self)) {
+ DBG_WARNING("Could not write serverid\n");
+ return false;
+ }
+
+ DEBUG(4, ("scavenger_say_hello: self[%s]\n",
+ server_id_str_buf(self, &tmp)));
+ return true;
+}
+
+static bool scavenger_wait_hello(int fd, struct server_id *child)
+{
+ struct server_id_buf tmp;
+ ssize_t ret;
+
+ ret = read_data(fd, child, sizeof(struct server_id));
+ if (ret == -1) {
+ DEBUG(2, ("Failed to read from pipe: %s\n",
+ strerror(errno)));
+ return false;
+ }
+ if (ret < sizeof(struct server_id)) {
+ DBG_WARNING("Could not read serverid\n");
+ return false;
+ }
+
+ DEBUG(4, ("scavenger_say_hello: child[%s]\n",
+ server_id_str_buf(*child, &tmp)));
+ return true;
+}
+
+static bool smbd_scavenger_start(struct smbd_scavenger_state *state)
+{
+ struct server_id self = messaging_server_id(state->msg);
+ struct tevent_fd *fde = NULL;
+ int fds[2];
+ int ret;
+ bool ok;
+
+ SMB_ASSERT(server_id_equal(&state->parent_id, &self));
+
+ if (smbd_scavenger_running(state)) {
+ struct server_id_buf tmp;
+ DEBUG(10, ("scavenger %s already running\n",
+ server_id_str_buf(*state->scavenger_id,
+ &tmp)));
+ return true;
+ }
+
+ if (state->scavenger_id != NULL) {
+ struct server_id_buf tmp;
+ DEBUG(10, ("scavenger zombie %s, cleaning up\n",
+ server_id_str_buf(*state->scavenger_id,
+ &tmp)));
+ TALLOC_FREE(state->scavenger_id);
+ }
+
+ state->scavenger_id = talloc_zero(state, struct server_id);
+ if (state->scavenger_id == NULL) {
+ DEBUG(2, ("Out of memory\n"));
+ goto fail;
+ }
+ talloc_set_destructor(state->scavenger_id,
+ smbd_scavenger_server_id_destructor);
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
+ if (ret == -1) {
+ DEBUG(2, ("socketpair failed: %s\n", strerror(errno)));
+ goto fail;
+ }
+
+ smb_set_close_on_exec(fds[0]);
+ smb_set_close_on_exec(fds[1]);
+
+ ret = fork();
+ if (ret == -1) {
+ int err = errno;
+ close(fds[0]);
+ close(fds[1]);
+ DEBUG(0, ("fork failed: %s\n", strerror(err)));
+ goto fail;
+ }
+
+ if (ret == 0) {
+ /* child */
+
+ NTSTATUS status;
+
+ close(fds[0]);
+
+ status = smbd_reinit_after_fork(state->msg, state->ev,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("reinit_after_fork failed: %s\n",
+ nt_errstr(status)));
+ exit_server("reinit_after_fork failed");
+ return false;
+ }
+
+ process_set_title("smbd-scavenger", "scavenger");
+ reopen_logs();
+
+ state->am_scavenger = true;
+ *state->scavenger_id = messaging_server_id(state->msg);
+
+ scavenger_setup_sig_term_handler(state->ev);
+
+ ok = scavenger_say_hello(fds[1], *state->scavenger_id);
+ if (!ok) {
+ DEBUG(2, ("scavenger_say_hello failed\n"));
+ exit_server("scavenger_say_hello failed");
+ return false;
+ }
+
+ fde = tevent_add_fd(state->ev, state->scavenger_id,
+ fds[1], TEVENT_FD_READ,
+ smbd_scavenger_parent_dead, state);
+ if (fde == NULL) {
+ DEBUG(2, ("tevent_add_fd(smbd_scavenger_parent_dead) "
+ "failed\n"));
+ exit_server("tevent_add_fd(smbd_scavenger_parent_dead) "
+ "failed");
+ return false;
+ }
+ tevent_fd_set_auto_close(fde);
+
+ ret = smbd_scavenger_main(state);
+
+ DEBUG(10, ("scavenger ended: %d\n", ret));
+ exit_server_cleanly("scavenger ended");
+ return false;
+ }
+
+ /* parent */
+ close(fds[1]);
+
+ ok = scavenger_wait_hello(fds[0], state->scavenger_id);
+ if (!ok) {
+ close(fds[0]);
+ goto fail;
+ }
+
+ fde = tevent_add_fd(state->ev, state->scavenger_id,
+ fds[0], TEVENT_FD_READ,
+ smbd_scavenger_done, state);
+ if (fde == NULL) {
+ close(fds[0]);
+ goto fail;
+ }
+ tevent_fd_set_auto_close(fde);
+
+ return true;
+fail:
+ TALLOC_FREE(state->scavenger_id);
+ return false;
+}
+
+static void scavenger_add_timer(struct smbd_scavenger_state *state,
+ struct scavenger_message *msg);
+
+static void smbd_scavenger_msg(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct smbd_scavenger_state *state =
+ talloc_get_type_abort(private_data,
+ struct smbd_scavenger_state);
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct server_id self = messaging_server_id(msg_ctx);
+ struct scavenger_message *msg = NULL;
+ struct server_id_buf tmp1, tmp2;
+
+ DEBUG(10, ("smbd_scavenger_msg: %s got message from %s\n",
+ server_id_str_buf(self, &tmp1),
+ server_id_str_buf(src, &tmp2)));
+
+ if (server_id_equal(&state->parent_id, &self)) {
+ NTSTATUS status;
+
+ if (!smbd_scavenger_running(state) &&
+ !smbd_scavenger_start(state))
+ {
+ DEBUG(2, ("Failed to start scavenger\n"));
+ goto done;
+ }
+ DEBUG(10, ("forwarding message to scavenger\n"));
+
+ status = messaging_send(msg_ctx,
+ *state->scavenger_id, msg_type, data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("forwarding message to scavenger failed: "
+ "%s\n", nt_errstr(status)));
+ goto done;
+ }
+ goto done;
+ }
+
+ if (!state->am_scavenger) {
+ DEBUG(10, ("im not the scavenger: ignore message\n"));
+ goto done;
+ }
+
+ if (!server_id_equal(&state->parent_id, &src)) {
+ DEBUG(10, ("scavenger: ignore spurious message\n"));
+ goto done;
+ }
+
+ DEBUG(10, ("scavenger: got a message\n"));
+ msg = (struct scavenger_message*)data->data;
+ scavenger_add_timer(state, msg);
+done:
+ talloc_free(frame);
+}
+
+bool smbd_scavenger_init(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct tevent_context *ev)
+{
+ struct smbd_scavenger_state *state;
+ NTSTATUS status;
+
+ if (smbd_scavenger_state) {
+ DEBUG(10, ("smbd_scavenger_init called again\n"));
+ return true;
+ }
+
+ state = talloc_zero(mem_ctx, struct smbd_scavenger_state);
+ if (state == NULL) {
+ DEBUG(2, ("Out of memory\n"));
+ return false;
+ }
+
+ state->msg = msg;
+ state->ev = ev;
+ state->parent_id = messaging_server_id(msg);
+
+ status = messaging_register(msg, state, MSG_SMB_SCAVENGER,
+ smbd_scavenger_msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("failed to register message handler: %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ smbd_scavenger_state = state;
+ return true;
+fail:
+ talloc_free(state);
+ return false;
+}
+
+void scavenger_schedule_disconnected(struct files_struct *fsp)
+{
+ NTSTATUS status;
+ struct server_id self = messaging_server_id(fsp->conn->sconn->msg_ctx);
+ struct timeval disconnect_time, until;
+ uint64_t timeout_usec;
+ struct scavenger_message msg;
+ DATA_BLOB msg_blob;
+ struct server_id_buf tmp;
+ struct file_id_buf idbuf;
+
+ if (fsp->op == NULL) {
+ return;
+ }
+ nttime_to_timeval(&disconnect_time, fsp->op->global->disconnect_time);
+ timeout_usec = UINT64_C(1000) * fsp->op->global->durable_timeout_msec;
+ until = timeval_add(&disconnect_time,
+ timeout_usec / 1000000,
+ timeout_usec % 1000000);
+
+ ZERO_STRUCT(msg);
+ msg.file_id = fsp->file_id;
+ msg.open_persistent_id = fsp->op->global->open_persistent_id;
+ msg.until = timeval_to_nttime(&until);
+
+ DEBUG(10, ("smbd: %s mark file %s as disconnected at %s with timeout "
+ "at %s in %fs\n",
+ server_id_str_buf(self, &tmp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ timeval_string(talloc_tos(), &disconnect_time, true),
+ timeval_string(talloc_tos(), &until, true),
+ fsp->op->global->durable_timeout_msec/1000.0));
+
+ SMB_ASSERT(server_id_is_disconnected(&fsp->op->global->server_id));
+ SMB_ASSERT(!server_id_equal(&self, &smbd_scavenger_state->parent_id));
+ SMB_ASSERT(!smbd_scavenger_state->am_scavenger);
+
+ msg_blob = data_blob_const(&msg, sizeof(msg));
+ DEBUG(10, ("send message to scavenger\n"));
+
+ status = messaging_send(smbd_scavenger_state->msg,
+ smbd_scavenger_state->parent_id,
+ MSG_SMB_SCAVENGER,
+ &msg_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct server_id_buf tmp1, tmp2;
+ DEBUG(2, ("Failed to send message to parent smbd %s "
+ "from %s: %s\n",
+ server_id_str_buf(smbd_scavenger_state->parent_id,
+ &tmp1),
+ server_id_str_buf(self, &tmp2),
+ nt_errstr(status)));
+ }
+}
+
+struct scavenger_timer_context {
+ struct smbd_scavenger_state *state;
+ struct scavenger_message msg;
+};
+
+struct cleanup_disconnected_state {
+ struct file_id fid;
+ struct share_mode_lock *lck;
+ uint64_t open_persistent_id;
+ size_t num_disconnected;
+ bool found_connected;
+};
+
+static bool cleanup_disconnected_lease(struct share_mode_entry *e,
+ void *private_data)
+{
+ struct cleanup_disconnected_state *state = private_data;
+ NTSTATUS status;
+
+ status = leases_db_del(&e->client_guid, &e->lease_key, &state->fid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("leases_db_del failed: %s\n",
+ nt_errstr(status));
+ }
+
+ return false;
+}
+
+static bool share_mode_find_connected_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct cleanup_disconnected_state *state = private_data;
+ bool disconnected;
+
+ disconnected = server_id_is_disconnected(&e->pid);
+ if (!disconnected) {
+ char *name = share_mode_filename(talloc_tos(), state->lck);
+ struct file_id_buf tmp1;
+ struct server_id_buf tmp2;
+ DBG_INFO("file (file-id='%s', servicepath='%s', name='%s') "
+ "is used by server %s ==> do not cleanup\n",
+ file_id_str_buf(state->fid, &tmp1),
+ share_mode_servicepath(state->lck),
+ name,
+ server_id_str_buf(e->pid, &tmp2));
+ TALLOC_FREE(name);
+ state->found_connected = true;
+ return true;
+ }
+
+ if (state->open_persistent_id != e->share_file_id) {
+ char *name = share_mode_filename(talloc_tos(), state->lck);
+ struct file_id_buf tmp;
+ DBG_INFO("entry for file "
+ "(file-id='%s', servicepath='%s', name='%s') "
+ "has share_file_id %"PRIu64" but expected "
+ "%"PRIu64"==> do not cleanup\n",
+ file_id_str_buf(state->fid, &tmp),
+ share_mode_servicepath(state->lck),
+ name,
+ e->share_file_id,
+ state->open_persistent_id);
+ TALLOC_FREE(name);
+ state->found_connected = true;
+ return true;
+ }
+
+ state->num_disconnected += 1;
+
+ return false;
+}
+
+static bool cleanup_disconnected_share_mode_entry_fn(
+ struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct cleanup_disconnected_state *state = private_data;
+
+ bool disconnected;
+
+ disconnected = server_id_is_disconnected(&e->pid);
+ if (!disconnected) {
+ char *name = share_mode_filename(talloc_tos(), state->lck);
+ struct file_id_buf tmp1;
+ struct server_id_buf tmp2;
+ DBG_ERR("file (file-id='%s', servicepath='%s', name='%s') "
+ "is used by server %s ==> internal error\n",
+ file_id_str_buf(state->fid, &tmp1),
+ share_mode_servicepath(state->lck),
+ name,
+ server_id_str_buf(e->pid, &tmp2));
+ TALLOC_FREE(name);
+ smb_panic(__location__);
+ }
+
+ /*
+ * Setting e->stale = true is
+ * the indication to delete the entry.
+ */
+ e->stale = true;
+ return false;
+}
+
+static bool share_mode_cleanup_disconnected(
+ struct file_id fid, uint64_t open_persistent_id)
+{
+ struct cleanup_disconnected_state state = {
+ .fid = fid,
+ .open_persistent_id = open_persistent_id
+ };
+ bool ret = false;
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *name = NULL;
+ struct file_id_buf idbuf;
+ bool ok;
+
+ state.lck = get_existing_share_mode_lock(frame, fid);
+ if (state.lck == NULL) {
+ DBG_INFO("Could not fetch share mode entry for %s\n",
+ file_id_str_buf(fid, &idbuf));
+ goto done;
+ }
+ name = share_mode_filename(frame, state.lck);
+
+ ok = share_mode_forall_entries(
+ state.lck, share_mode_find_connected_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("share_mode_forall_entries failed\n");
+ goto done;
+ }
+ if (state.found_connected) {
+ DBG_DEBUG("Found connected entry\n");
+ goto done;
+ }
+
+ ok = share_mode_forall_leases(
+ state.lck, cleanup_disconnected_lease, &state);
+ if (!ok) {
+ DBG_DEBUG("failed to clean up leases associated "
+ "with file (file-id='%s', servicepath='%s', "
+ "name='%s') and open_persistent_id %"PRIu64" "
+ "==> do not cleanup\n",
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+ goto done;
+ }
+
+ ok = brl_cleanup_disconnected(fid, open_persistent_id);
+ if (!ok) {
+ DBG_DEBUG("failed to clean up byte range locks associated "
+ "with file (file-id='%s', servicepath='%s', "
+ "name='%s') and open_persistent_id %"PRIu64" "
+ "==> do not cleanup\n",
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+ goto done;
+ }
+
+ DBG_DEBUG("cleaning up %zu entries for file "
+ "(file-id='%s', servicepath='%s', name='%s') "
+ "from open_persistent_id %"PRIu64"\n",
+ state.num_disconnected,
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+
+ ok = share_mode_forall_entries(
+ state.lck, cleanup_disconnected_share_mode_entry_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("failed to clean up %zu entries associated "
+ "with file (file-id='%s', servicepath='%s', "
+ "name='%s') and open_persistent_id %"PRIu64" "
+ "==> do not cleanup\n",
+ state.num_disconnected,
+ file_id_str_buf(fid, &idbuf),
+ share_mode_servicepath(state.lck),
+ name,
+ open_persistent_id);
+ goto done;
+ }
+
+ ret = true;
+done:
+ talloc_free(frame);
+ return ret;
+}
+
+static void scavenger_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t, void *data)
+{
+ struct scavenger_timer_context *ctx =
+ talloc_get_type_abort(data, struct scavenger_timer_context);
+ struct file_id_buf idbuf;
+ NTSTATUS status;
+ bool ok;
+
+ DBG_DEBUG("do cleanup for file %s at %s\n",
+ file_id_str_buf(ctx->msg.file_id, &idbuf),
+ timeval_string(talloc_tos(), &t, true));
+
+ ok = share_mode_cleanup_disconnected(ctx->msg.file_id,
+ ctx->msg.open_persistent_id);
+ if (!ok) {
+ DBG_WARNING("Failed to cleanup share modes and byte range "
+ "locks for file %s open %"PRIu64"\n",
+ file_id_str_buf(ctx->msg.file_id, &idbuf),
+ ctx->msg.open_persistent_id);
+ }
+
+ status = smbXsrv_open_cleanup(ctx->msg.open_persistent_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Failed to cleanup open global for file %s open "
+ "%"PRIu64": %s\n",
+ file_id_str_buf(ctx->msg.file_id, &idbuf),
+ ctx->msg.open_persistent_id,
+ nt_errstr(status));
+ }
+}
+
+static void scavenger_add_timer(struct smbd_scavenger_state *state,
+ struct scavenger_message *msg)
+{
+ struct tevent_timer *te;
+ struct scavenger_timer_context *ctx;
+ struct timeval until;
+ struct file_id_buf idbuf;
+
+ nttime_to_timeval(&until, msg->until);
+
+ DBG_DEBUG("schedule file %s for cleanup at %s\n",
+ file_id_str_buf(msg->file_id, &idbuf),
+ timeval_string(talloc_tos(), &until, true));
+
+ ctx = talloc_zero(state, struct scavenger_timer_context);
+ if (ctx == NULL) {
+ DEBUG(2, ("Failed to talloc_zero(scavenger_timer_context)\n"));
+ return;
+ }
+
+ ctx->state = state;
+ ctx->msg = *msg;
+
+ te = tevent_add_timer(state->ev,
+ state,
+ until,
+ scavenger_timer,
+ ctx);
+ if (te == NULL) {
+ DEBUG(2, ("Failed to add scavenger_timer event\n"));
+ talloc_free(ctx);
+ return;
+ }
+
+ /* delete context after handler was running */
+ talloc_steal(te, ctx);
+}
diff --git a/source3/smbd/scavenger.h b/source3/smbd/scavenger.h
new file mode 100644
index 0000000..966c80d
--- /dev/null
+++ b/source3/smbd/scavenger.h
@@ -0,0 +1,31 @@
+/*
+ Unix SMB/CIFS implementation.
+ smbd scavenger daemon
+
+ Copyright (C) Gregor Beck 2013
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SCAVENGER_H_
+#define _SCAVENGER_H_
+
+
+bool smbd_scavenger_init(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct tevent_context *ev);
+
+void scavenger_schedule_disconnected(struct files_struct *fsp);
+
+#endif
diff --git a/source3/smbd/seal.c b/source3/smbd/seal.c
new file mode 100644
index 0000000..8a0dbeb
--- /dev/null
+++ b/source3/smbd/seal.c
@@ -0,0 +1,313 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Transport encryption (sealing) code - server code.
+ Copyright (C) Jeremy Allison 2007.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_seal.h"
+#include "auth.h"
+#include "libsmb/libsmb.h"
+#include "../lib/tsocket/tsocket.h"
+#include "auth/gensec/gensec.h"
+
+/******************************************************************************
+ Server side encryption.
+******************************************************************************/
+
+/******************************************************************************
+ Return global enc context - this must change if we ever do multiple contexts.
+******************************************************************************/
+
+static uint16_t srv_enc_ctx(const struct smb_trans_enc_state *es)
+{
+ return es->enc_ctx_num;
+}
+
+/******************************************************************************
+ Is this an incoming encrypted packet ?
+******************************************************************************/
+
+bool is_encrypted_packet(const uint8_t *inbuf)
+{
+ NTSTATUS status;
+ uint16_t enc_num;
+
+ /* Ignore non-session messages or non 0xFF'E' messages. */
+ if(CVAL(inbuf,0)
+ || (smb_len(inbuf) < 8)
+ || !(inbuf[4] == 0xFF && inbuf[5] == 'E')) {
+ return false;
+ }
+
+ status = get_enc_ctx_num(inbuf, &enc_num);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ /* Encrypted messages are 0xFF'E'<ctx> */
+ if (srv_trans_enc_ctx && enc_num == srv_enc_ctx(srv_trans_enc_ctx)) {
+ return true;
+ }
+ return false;
+}
+
+/******************************************************************************
+ Create an gensec_security and ensure pointer copy is correct.
+******************************************************************************/
+
+static NTSTATUS make_auth_gensec(const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct smb_trans_enc_state *es)
+{
+ NTSTATUS status;
+
+ status = auth_generic_prepare(es, remote_address,
+ local_address,
+ "SMB encryption",
+ &es->gensec_security);
+ if (!NT_STATUS_IS_OK(status)) {
+ return nt_status_squash(status);
+ }
+
+ gensec_want_feature(es->gensec_security, GENSEC_FEATURE_SEAL);
+
+ /*
+ * We could be accessing the secrets.tdb or krb5.keytab file here.
+ * ensure we have permissions to do so.
+ */
+ become_root();
+
+ status = gensec_start_mech_by_oid(es->gensec_security, GENSEC_OID_SPNEGO);
+
+ unbecome_root();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return nt_status_squash(status);
+ }
+
+ return status;
+}
+
+/******************************************************************************
+ Create a server encryption context.
+******************************************************************************/
+
+static NTSTATUS make_srv_encryption_context(const struct tsocket_address *remote_address,
+ const struct tsocket_address *local_address,
+ struct smb_trans_enc_state **pp_es)
+{
+ NTSTATUS status;
+ struct smb_trans_enc_state *es;
+
+ *pp_es = NULL;
+
+ ZERO_STRUCTP(partial_srv_trans_enc_ctx);
+ es = talloc_zero(NULL, struct smb_trans_enc_state);
+ if (!es) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = make_auth_gensec(remote_address,
+ local_address,
+ es);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(es);
+ return status;
+ }
+ *pp_es = es;
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Free an encryption-allocated buffer.
+******************************************************************************/
+
+void srv_free_enc_buffer(struct smbXsrv_connection *xconn, char *buf)
+{
+ /* We know this is an smb buffer, and we
+ * didn't malloc, only copy, for a keepalive,
+ * so ignore non-session messages. */
+
+ if(CVAL(buf,0)) {
+ return;
+ }
+
+ if (srv_trans_enc_ctx) {
+ common_free_enc_buffer(srv_trans_enc_ctx, buf);
+ }
+}
+
+/******************************************************************************
+ Decrypt an incoming buffer.
+******************************************************************************/
+
+NTSTATUS srv_decrypt_buffer(struct smbXsrv_connection *xconn, char *buf)
+{
+ /* Ignore non-session messages. */
+ if(CVAL(buf,0)) {
+ return NT_STATUS_OK;
+ }
+
+ if (srv_trans_enc_ctx) {
+ return common_decrypt_buffer(srv_trans_enc_ctx, buf);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Encrypt an outgoing buffer. Return the encrypted pointer in buf_out.
+******************************************************************************/
+
+NTSTATUS srv_encrypt_buffer(struct smbXsrv_connection *xconn, char *buf,
+ char **buf_out)
+{
+ *buf_out = buf;
+
+ /* Ignore non-session messages. */
+ if(CVAL(buf,0)) {
+ return NT_STATUS_OK;
+ }
+
+ if (srv_trans_enc_ctx) {
+ return common_encrypt_buffer(srv_trans_enc_ctx, buf, buf_out);
+ }
+ /* Not encrypting. */
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Do the SPNEGO encryption negotiation. Parameters are in/out.
+******************************************************************************/
+
+NTSTATUS srv_request_encryption_setup(connection_struct *conn,
+ unsigned char **ppdata,
+ size_t *p_data_size,
+ unsigned char **pparam,
+ size_t *p_param_size)
+{
+ NTSTATUS status;
+ DATA_BLOB blob = data_blob_const(*ppdata, *p_data_size);
+ DATA_BLOB response = data_blob_null;
+ struct smb_trans_enc_state *es;
+
+ SAFE_FREE(*pparam);
+ *p_param_size = 0;
+
+ if (!partial_srv_trans_enc_ctx) {
+ /* This is the initial step. */
+ status = make_srv_encryption_context(conn->sconn->remote_address,
+ conn->sconn->local_address,
+ &partial_srv_trans_enc_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ es = partial_srv_trans_enc_ctx;
+ if (!es || es->gensec_security == NULL) {
+ TALLOC_FREE(partial_srv_trans_enc_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Second step. */
+ become_root();
+ status = gensec_update(es->gensec_security,
+ talloc_tos(),
+ blob, &response);
+ unbecome_root();
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(partial_srv_trans_enc_ctx);
+ return nt_status_squash(status);
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Return the context we're using for this encryption state. */
+ if (!(*pparam = SMB_MALLOC_ARRAY(unsigned char, 2))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SSVAL(*pparam, 0, es->enc_ctx_num);
+ *p_param_size = 2;
+ }
+
+ /* Return the raw blob. */
+ SAFE_FREE(*ppdata);
+ *ppdata = (unsigned char *)smb_memdup(response.data, response.length);
+ if ((*ppdata) == NULL && response.length > 0)
+ return NT_STATUS_NO_MEMORY;
+ *p_data_size = response.length;
+ data_blob_free(&response);
+ return status;
+}
+
+/******************************************************************************
+ Negotiation was successful - turn on server-side encryption.
+******************************************************************************/
+
+static NTSTATUS check_enc_good(struct smb_trans_enc_state *es)
+{
+ if (!es) {
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ if (!gensec_have_feature(es->gensec_security, GENSEC_FEATURE_SIGN)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!gensec_have_feature(es->gensec_security, GENSEC_FEATURE_SEAL)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Negotiation was successful - turn on server-side encryption.
+******************************************************************************/
+
+NTSTATUS srv_encryption_start(connection_struct *conn)
+{
+ NTSTATUS status;
+
+ /* Check that we are really doing sign+seal. */
+ status = check_enc_good(partial_srv_trans_enc_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* Throw away the context we're using currently (if any). */
+ TALLOC_FREE(srv_trans_enc_ctx);
+
+ /* Steal the partial pointer. Deliberate shallow copy. */
+ srv_trans_enc_ctx = partial_srv_trans_enc_ctx;
+ srv_trans_enc_ctx->enc_on = true;
+
+ partial_srv_trans_enc_ctx = NULL;
+
+ DEBUG(1,("srv_encryption_start: context negotiated\n"));
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Shutdown all server contexts.
+******************************************************************************/
+
+void server_encryption_shutdown(struct smbXsrv_connection *xconn)
+{
+ TALLOC_FREE(partial_srv_trans_enc_ctx);
+ TALLOC_FREE(srv_trans_enc_ctx);
+}
diff --git a/source3/smbd/sec_ctx.c b/source3/smbd/sec_ctx.c
new file mode 100644
index 0000000..dc06acd
--- /dev/null
+++ b/source3/smbd/sec_ctx.c
@@ -0,0 +1,513 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Tim Potter 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security_token.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "../lib/util/setid.h"
+
+extern struct current_user current_user;
+
+/****************************************************************************
+ Are two UNIX tokens equal ?
+****************************************************************************/
+
+bool unix_token_equal(const struct security_unix_token *t1, const struct security_unix_token *t2)
+{
+ if (t1->uid != t2->uid || t1->gid != t2->gid ||
+ t1->ngroups != t2->ngroups) {
+ return false;
+ }
+ if (memcmp(t1->groups, t2->groups,
+ t1->ngroups*sizeof(gid_t)) != 0) {
+ return false;
+ }
+ return true;
+}
+
+/****************************************************************************
+ Become the specified uid.
+****************************************************************************/
+
+static bool become_uid(uid_t uid)
+{
+ /* Check for dodgy uid values */
+
+ if (uid == (uid_t)-1 ||
+ ((sizeof(uid_t) == 2) && (uid == (uid_t)65535))) {
+ if (!become_uid_done) {
+ DEBUG(1,("WARNING: using uid %d is a security risk\n",
+ (int)uid));
+ become_uid_done = true;
+ }
+ }
+
+ /* Set effective user id */
+
+ set_effective_uid(uid);
+
+ return True;
+}
+
+/****************************************************************************
+ Become the specified gid.
+****************************************************************************/
+
+static bool become_gid(gid_t gid)
+{
+ /* Check for dodgy gid values */
+
+ if (gid == (gid_t)-1 || ((sizeof(gid_t) == 2) &&
+ (gid == (gid_t)65535))) {
+ if (!become_gid_done) {
+ DEBUG(1,("WARNING: using gid %d is a security risk\n",
+ (int)gid));
+ become_gid_done = true;
+ }
+ }
+
+ /* Set effective group id */
+
+ set_effective_gid(gid);
+ return True;
+}
+
+/****************************************************************************
+ Drop back to root privileges in order to change to another user.
+****************************************************************************/
+
+static void gain_root(void)
+{
+ if (non_root_mode()) {
+ return;
+ }
+
+ if (geteuid() != 0) {
+ set_effective_uid(0);
+
+ if (geteuid() != 0) {
+ DEBUG(0,
+ ("Warning: You appear to have a trapdoor "
+ "uid system\n"));
+ }
+ }
+
+ if (getegid() != 0) {
+ set_effective_gid(0);
+
+ if (getegid() != 0) {
+ DEBUG(0,
+ ("Warning: You appear to have a trapdoor "
+ "gid system\n"));
+ }
+ }
+}
+
+/****************************************************************************
+ Get the list of current groups.
+****************************************************************************/
+
+static int get_current_groups(gid_t gid, uint32_t *p_ngroups, gid_t **p_groups)
+{
+ int i;
+ int ngroups;
+ gid_t *groups = NULL;
+
+ (*p_ngroups) = 0;
+ (*p_groups) = NULL;
+
+ /* this looks a little strange, but is needed to cope with
+ systems that put the current egid in the group list
+ returned from getgroups() (tridge) */
+ save_re_gid();
+ set_effective_gid(gid);
+ samba_setgid(gid);
+
+ ngroups = sys_getgroups(0, NULL);
+ if (ngroups <= 0) {
+ goto fail;
+ }
+
+ if((groups = SMB_MALLOC_ARRAY(gid_t, ngroups+1)) == NULL) {
+ DEBUG(0,("setup_groups malloc fail !\n"));
+ goto fail;
+ }
+
+ if ((ngroups = sys_getgroups(ngroups,groups)) == -1) {
+ goto fail;
+ }
+
+ restore_re_gid();
+
+ (*p_ngroups) = ngroups;
+ (*p_groups) = groups;
+
+ DEBUG( 4, ( "get_current_groups: user is in %u groups: ", ngroups));
+ for (i = 0; i < ngroups; i++ ) {
+ DEBUG( 4, ( "%s%d", (i ? ", " : ""), (int)groups[i] ) );
+ }
+ DEBUG( 4, ( "\n" ) );
+
+ return ngroups;
+
+fail:
+ SAFE_FREE(groups);
+ restore_re_gid();
+ return -1;
+}
+
+/****************************************************************************
+ Create a new security context on the stack. It is the same as the old
+ one. User changes are done using the set_sec_ctx() function.
+****************************************************************************/
+
+bool push_sec_ctx(void)
+{
+ struct sec_ctx *ctx_p;
+
+ START_PROFILE(push_sec_ctx);
+
+ /* Check we don't overflow our stack */
+
+ if (sec_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) {
+ DEBUG(0, ("Security context stack overflow!\n"));
+ smb_panic("Security context stack overflow!");
+ }
+
+ /* Store previous user context */
+
+ sec_ctx_stack_ndx++;
+
+ ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ ctx_p->ut.uid = geteuid();
+ ctx_p->ut.gid = getegid();
+
+ DEBUG(4, ("push_sec_ctx(%u, %u) : sec_ctx_stack_ndx = %d\n",
+ (unsigned int)ctx_p->ut.uid, (unsigned int)ctx_p->ut.gid, sec_ctx_stack_ndx ));
+
+ ctx_p->token = security_token_duplicate(NULL,
+ sec_ctx_stack[sec_ctx_stack_ndx-1].token);
+
+ ctx_p->ut.ngroups = sys_getgroups(0, NULL);
+
+ if (ctx_p->ut.ngroups != 0) {
+ if (!(ctx_p->ut.groups = SMB_MALLOC_ARRAY(gid_t, ctx_p->ut.ngroups))) {
+ DEBUG(0, ("Out of memory in push_sec_ctx()\n"));
+ TALLOC_FREE(ctx_p->token);
+ return False;
+ }
+
+ sys_getgroups(ctx_p->ut.ngroups, ctx_p->ut.groups);
+ } else {
+ ctx_p->ut.groups = NULL;
+ }
+
+ END_PROFILE(push_sec_ctx);
+
+ return True;
+}
+
+#ifndef HAVE_DARWIN_INITGROUPS
+/****************************************************************************
+ Become the specified uid and gid.
+****************************************************************************/
+
+static bool become_id(uid_t uid, gid_t gid)
+{
+ return become_gid(gid) && become_uid(uid);
+}
+
+/****************************************************************************
+ Change UNIX security context. Calls panic if not successful so no return value.
+****************************************************************************/
+/* Normal credential switch path. */
+
+static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups)
+{
+ /* Start context switch */
+ gain_root();
+#ifdef HAVE_SETGROUPS
+ if (sys_setgroups(gid, ngroups, groups) != 0 && !non_root_mode()) {
+ smb_panic("sys_setgroups failed");
+ }
+#endif
+ become_id(uid, gid);
+ /* end context switch */
+}
+
+#else /* HAVE_DARWIN_INITGROUPS */
+
+/* The Darwin groups implementation is a little unusual. The list of
+* groups in the kernel credential is not exhaustive, but more like
+* a cache. The full group list is held in userspace and checked
+* dynamically.
+*
+* This is an optional mechanism, and setgroups(2) opts out
+* of it. That is, if you call setgroups, then the list of groups you
+* set are the only groups that are ever checked. This is not what we
+* want. We want to opt in to the dynamic resolution mechanism, so we
+* need to specify the uid of the user whose group list (cache) we are
+* setting.
+*
+* The Darwin rules are:
+* 1. Thou shalt setegid, initgroups and seteuid IN THAT ORDER
+* 2. Thou shalt not pass more that NGROUPS_MAX to initgroups
+* 3. Thou shalt leave the first entry in the groups list well alone
+*/
+
+#include <sys/syscall.h>
+
+static void set_unix_security_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups)
+{
+ int max = NGROUPS_MAX;
+
+ /* Start context switch */
+ gain_root();
+
+ become_gid(gid);
+
+
+ if (syscall(SYS_initgroups, (ngroups > max) ? max : ngroups,
+ groups, uid) == -1 && !non_root_mode()) {
+ DEBUG(0, ("WARNING: failed to set group list "
+ "(%d groups) for UID %d: %s\n",
+ ngroups, uid, strerror(errno)));
+ smb_panic("sys_setgroups failed");
+ }
+
+ become_uid(uid);
+ /* end context switch */
+}
+
+#endif /* HAVE_DARWIN_INITGROUPS */
+
+/****************************************************************************
+ Set the current security context to a given user.
+****************************************************************************/
+
+static void set_sec_ctx_internal(uid_t uid, gid_t gid,
+ int ngroups, gid_t *groups,
+ const struct security_token *token)
+{
+ struct sec_ctx *ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Set the security context */
+
+ DEBUG(4, ("setting sec ctx (%u, %u) - sec_ctx_stack_ndx = %d\n",
+ (unsigned int)uid, (unsigned int)gid, sec_ctx_stack_ndx));
+
+ security_token_debug(DBGC_CLASS, 5, token);
+ debug_unix_user_token(DBGC_CLASS, 5, uid, gid, ngroups, groups);
+
+ /* Change uid, gid and supplementary group list. */
+ set_unix_security_ctx(uid, gid, ngroups, groups);
+
+ ctx_p->ut.ngroups = ngroups;
+
+ SAFE_FREE(ctx_p->ut.groups);
+ if (token && (token == ctx_p->token)) {
+ smb_panic("DUPLICATE_TOKEN");
+ }
+
+ TALLOC_FREE(ctx_p->token);
+
+ if (ngroups) {
+ ctx_p->ut.groups = (gid_t *)smb_xmemdup(groups,
+ sizeof(gid_t) * ngroups);
+ } else {
+ ctx_p->ut.groups = NULL;
+ }
+
+ if (token) {
+ ctx_p->token = security_token_duplicate(NULL, token);
+ if (!ctx_p->token) {
+ smb_panic("security_token_duplicate failed");
+ }
+ } else {
+ ctx_p->token = NULL;
+ }
+
+ ctx_p->ut.uid = uid;
+ ctx_p->ut.gid = gid;
+
+ /* Update current_user stuff */
+
+ current_user.ut.uid = uid;
+ current_user.ut.gid = gid;
+ current_user.ut.ngroups = ngroups;
+ current_user.ut.groups = groups;
+ current_user.nt_user_token = ctx_p->token;
+
+ /*
+ * Delete any ChDir cache. We can't assume
+ * the new uid has access to current working
+ * directory.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14682
+ */
+ SAFE_FREE(LastDir);
+}
+
+void set_sec_ctx(uid_t uid, gid_t gid, int ngroups, gid_t *groups, const struct security_token *token)
+{
+ START_PROFILE(set_sec_ctx);
+ set_sec_ctx_internal(uid, gid, ngroups, groups, token);
+ END_PROFILE(set_sec_ctx);
+}
+
+/****************************************************************************
+ Become root context.
+****************************************************************************/
+
+void set_root_sec_ctx(void)
+{
+ /* May need to worry about supplementary groups at some stage */
+
+ START_PROFILE(set_root_sec_ctx);
+ set_sec_ctx_internal(0, 0, 0, NULL, NULL);
+ END_PROFILE(set_root_sec_ctx);
+}
+
+/****************************************************************************
+ Pop a security context from the stack.
+****************************************************************************/
+
+bool pop_sec_ctx(void)
+{
+ struct sec_ctx *ctx_p;
+ struct sec_ctx *prev_ctx_p;
+
+ START_PROFILE(pop_sec_ctx);
+
+ /* Check for stack underflow */
+
+ if (sec_ctx_stack_ndx == 0) {
+ DEBUG(0, ("Security context stack underflow!\n"));
+ smb_panic("Security context stack underflow!");
+ }
+
+ ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Clear previous user info */
+
+ ctx_p->ut.uid = (uid_t)-1;
+ ctx_p->ut.gid = (gid_t)-1;
+
+ SAFE_FREE(ctx_p->ut.groups);
+ ctx_p->ut.ngroups = 0;
+
+ TALLOC_FREE(ctx_p->token);
+
+ /* Pop back previous user */
+
+ sec_ctx_stack_ndx--;
+
+ prev_ctx_p = &sec_ctx_stack[sec_ctx_stack_ndx];
+
+ /* Change uid, gid and supplementary group list. */
+ set_unix_security_ctx(prev_ctx_p->ut.uid,
+ prev_ctx_p->ut.gid,
+ prev_ctx_p->ut.ngroups,
+ prev_ctx_p->ut.groups);
+
+ /* Update current_user stuff */
+
+ current_user.ut.uid = prev_ctx_p->ut.uid;
+ current_user.ut.gid = prev_ctx_p->ut.gid;
+ current_user.ut.ngroups = prev_ctx_p->ut.ngroups;
+ current_user.ut.groups = prev_ctx_p->ut.groups;
+ current_user.nt_user_token = prev_ctx_p->token;
+
+ END_PROFILE(pop_sec_ctx);
+
+ DEBUG(4, ("pop_sec_ctx (%u, %u) - sec_ctx_stack_ndx = %d\n",
+ (unsigned int)geteuid(), (unsigned int)getegid(), sec_ctx_stack_ndx));
+
+ return True;
+}
+
+/* Initialise the security context system */
+
+void init_sec_ctx(void)
+{
+ int i;
+ struct sec_ctx *ctx_p;
+
+ /* Initialise security context stack */
+
+ memset(sec_ctx_stack, 0, sizeof(struct sec_ctx) * MAX_SEC_CTX_DEPTH);
+
+ for (i = 0; i < MAX_SEC_CTX_DEPTH; i++) {
+ sec_ctx_stack[i].ut.uid = (uid_t)-1;
+ sec_ctx_stack[i].ut.gid = (gid_t)-1;
+ }
+
+ /* Initialise first level of stack. It is the current context */
+ ctx_p = &sec_ctx_stack[0];
+
+ ctx_p->ut.uid = geteuid();
+ ctx_p->ut.gid = getegid();
+
+ get_current_groups(ctx_p->ut.gid, &ctx_p->ut.ngroups, &ctx_p->ut.groups);
+
+ ctx_p->token = NULL; /* Maps to guest user. */
+
+ /* Initialise current_user global */
+
+ current_user.ut.uid = ctx_p->ut.uid;
+ current_user.ut.gid = ctx_p->ut.gid;
+ current_user.ut.ngroups = ctx_p->ut.ngroups;
+ current_user.ut.groups = ctx_p->ut.groups;
+
+ /* The conn and vuid are usually taken care of by other modules.
+ We initialise them here. */
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+ current_user.nt_user_token = NULL;
+}
+
+/*************************************************************
+ Called when we're inside a become_root() temporary escalation
+ of privileges and the nt_user_token is NULL. Return the last
+ active token on the context stack. We know there is at least
+ one valid non-NULL token on the stack so panic if we underflow.
+*************************************************************/
+
+const struct security_token *sec_ctx_active_token(void)
+{
+ int stack_index = sec_ctx_stack_ndx;
+ struct sec_ctx *ctx_p = &sec_ctx_stack[stack_index];
+
+ while (ctx_p->token == NULL) {
+ stack_index--;
+ if (stack_index < 0) {
+ DEBUG(0, ("Security context active token "
+ "stack underflow!\n"));
+ smb_panic("Security context active token "
+ "stack underflow!");
+ }
+ ctx_p = &sec_ctx_stack[stack_index];
+ }
+ return ctx_p->token;
+}
diff --git a/source3/smbd/server.c b/source3/smbd/server.c
new file mode 100644
index 0000000..42abfa9
--- /dev/null
+++ b/source3/smbd/server.c
@@ -0,0 +1,2136 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002-2003
+ Copyright (C) Volker Lendecke 1993-2007
+ Copyright (C) Jeremy Allison 1993-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "lib/util/close_low_fd.h"
+#include "lib/cmdline/cmdline.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "registry/reg_init_full.h"
+#include "libcli/auth/schannel.h"
+#include "secrets.h"
+#include "../lib/util/memcache.h"
+#include "ctdbd_conn.h"
+#include "lib/util/util_process.h"
+#include "util_cluster.h"
+#include "printing/queue_process.h"
+#include "rpc_server/rpc_config.h"
+#include "passdb.h"
+#include "auth.h"
+#include "messages.h"
+#include "messages_ctdb.h"
+#include "smbprofile.h"
+#include "lib/id_cache.h"
+#include "lib/param/param.h"
+#include "lib/background.h"
+#include "../lib/util/pidfile.h"
+#include "lib/smbd_shim.h"
+#include "scavenger.h"
+#include "locking/leases_db.h"
+#include "smbd/notifyd/notifyd.h"
+#include "smbd/smbd_cleanupd.h"
+#include "lib/util/sys_rw.h"
+#include "cleanupdb.h"
+#include "g_lock.h"
+#include "lib/global_contexts.h"
+#include "source3/lib/substitute.h"
+
+#ifdef CLUSTER_SUPPORT
+#include "ctdb_protocol.h"
+#endif
+
+struct smbd_open_socket;
+struct smbd_child_pid;
+
+struct smbd_parent_context {
+ bool interactive;
+
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+
+ /* the list of listening sockets */
+ struct smbd_open_socket *sockets;
+
+ /* the list of current child processes */
+ struct smbd_child_pid *children;
+ size_t num_children;
+
+ struct server_id cleanupd;
+ struct server_id notifyd;
+
+ struct tevent_timer *cleanup_te;
+};
+
+struct smbd_open_socket {
+ struct smbd_open_socket *prev, *next;
+ struct smbd_parent_context *parent;
+ int fd;
+ struct tevent_fd *fde;
+};
+
+struct smbd_child_pid {
+ struct smbd_child_pid *prev, *next;
+ pid_t pid;
+};
+
+/*******************************************************************
+ What to do when smb.conf is updated.
+ ********************************************************************/
+
+static NTSTATUS messaging_send_to_children(struct messaging_context *msg_ctx,
+ uint32_t msg_type, DATA_BLOB* data);
+
+static void smbd_parent_conf_updated(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ bool ok;
+
+ DEBUG(10,("smbd_parent_conf_updated: Got message saying smb.conf was "
+ "updated. Reloading.\n"));
+ change_to_root_user();
+ reload_services(NULL, NULL, false);
+
+ ok = reinit_guest_session_info(NULL);
+ if (!ok) {
+ DBG_ERR("Failed to reinit guest info\n");
+ }
+ messaging_send_to_children(msg, MSG_SMB_CONF_UPDATED, NULL);
+}
+
+/****************************************************************************
+ Send a SIGTERM to our process group.
+*****************************************************************************/
+
+static void killkids(void)
+{
+ if(am_parent) kill(0,SIGTERM);
+}
+
+static void msg_exit_server(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ DEBUG(3, ("got a SHUTDOWN message\n"));
+ exit_server_cleanly(NULL);
+}
+
+#ifdef DEVELOPER
+static void msg_inject_fault(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ int sig;
+ struct server_id_buf tmp;
+
+ if (data->length != sizeof(sig)) {
+ DEBUG(0, ("Process %s sent bogus signal injection request\n",
+ server_id_str_buf(src, &tmp)));
+ return;
+ }
+
+ sig = *(int *)data->data;
+ if (sig == -1) {
+ exit_server("internal error injected");
+ return;
+ }
+
+#ifdef HAVE_STRSIGNAL
+ DEBUG(0, ("Process %s requested injection of signal %d (%s)\n",
+ server_id_str_buf(src, &tmp), sig, strsignal(sig)));
+#else
+ DEBUG(0, ("Process %s requested injection of signal %d\n",
+ server_id_str_buf(src, &tmp), sig));
+#endif
+
+ kill(getpid(), sig);
+}
+#endif /* DEVELOPER */
+
+#if defined(DEVELOPER) || defined(ENABLE_SELFTEST)
+/*
+ * Sleep for the specified number of seconds.
+ */
+static void msg_sleep(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ unsigned int seconds;
+ struct server_id_buf tmp;
+
+ if (data->length != sizeof(seconds)) {
+ DBG_ERR("Process %s sent bogus sleep request\n",
+ server_id_str_buf(src, &tmp));
+ return;
+ }
+
+ seconds = *(unsigned int *)data->data;
+ DBG_ERR("Process %s request a sleep of %u seconds\n",
+ server_id_str_buf(src, &tmp),
+ seconds);
+ sleep(seconds);
+ DBG_ERR("Restarting after %u second sleep requested by process %s\n",
+ seconds,
+ server_id_str_buf(src, &tmp));
+}
+#endif /* DEVELOPER */
+
+static NTSTATUS messaging_send_to_children(struct messaging_context *msg_ctx,
+ uint32_t msg_type, DATA_BLOB* data)
+{
+ NTSTATUS status;
+ struct smbd_parent_context *parent = am_parent;
+ struct smbd_child_pid *child;
+
+ if (parent == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ for (child = parent->children; child != NULL; child = child->next) {
+ status = messaging_send(parent->msg_ctx,
+ pid_to_procid(child->pid),
+ msg_type, data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send(%d) failed: %s\n",
+ (int)child->pid, nt_errstr(status));
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static void smb_parent_send_to_children(struct messaging_context *ctx,
+ void* data,
+ uint32_t msg_type,
+ struct server_id srv_id,
+ DATA_BLOB* msg_data)
+{
+ messaging_send_to_children(ctx, msg_type, msg_data);
+}
+
+/*
+ * Parent smbd process sets its own debug level first and then
+ * sends a message to all the smbd children to adjust their debug
+ * level to that of the parent.
+ */
+
+static void smbd_msg_debug(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ debug_message(msg_ctx, private_data, MSG_DEBUG, server_id, data);
+
+ messaging_send_to_children(msg_ctx, MSG_DEBUG, data);
+}
+
+static void smbd_parent_id_cache_kill(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB* data)
+{
+ const char *msg = (data && data->data)
+ ? (const char *)data->data : "<NULL>";
+ struct id_cache_ref id;
+
+ if (!id_cache_ref_parse(msg, &id)) {
+ DEBUG(0, ("Invalid ?ID: %s\n", msg));
+ return;
+ }
+
+ id_cache_delete_from_cache(&id);
+
+ messaging_send_to_children(msg_ctx, msg_type, data);
+}
+
+static void smbd_parent_id_cache_delete(struct messaging_context *ctx,
+ void* data,
+ uint32_t msg_type,
+ struct server_id srv_id,
+ DATA_BLOB* msg_data)
+{
+ id_cache_delete_message(ctx, data, msg_type, srv_id, msg_data);
+
+ messaging_send_to_children(ctx, msg_type, msg_data);
+}
+
+static void add_child_pid(struct smbd_parent_context *parent,
+ pid_t pid)
+{
+ struct smbd_child_pid *child;
+
+ child = talloc_zero(parent, struct smbd_child_pid);
+ if (child == NULL) {
+ DEBUG(0, ("Could not add child struct -- malloc failed\n"));
+ return;
+ }
+ child->pid = pid;
+ DLIST_ADD(parent->children, child);
+ parent->num_children += 1;
+}
+
+static void smb_tell_num_children(struct messaging_context *ctx, void *data,
+ uint32_t msg_type, struct server_id srv_id,
+ DATA_BLOB *msg_data)
+{
+ uint8_t buf[sizeof(uint32_t)];
+
+ if (am_parent) {
+ SIVAL(buf, 0, am_parent->num_children);
+ messaging_send_buf(ctx, srv_id, MSG_SMB_NUM_CHILDREN,
+ buf, sizeof(buf));
+ }
+}
+
+static void notifyd_stopped(struct tevent_req *req);
+
+static struct tevent_req *notifyd_req(struct messaging_context *msg_ctx,
+ struct tevent_context *ev)
+{
+ struct tevent_req *req;
+ sys_notify_watch_fn sys_notify_watch = NULL;
+ struct sys_notify_context *sys_notify_ctx = NULL;
+ struct ctdbd_connection *ctdbd_conn = NULL;
+
+ if (lp_kernel_change_notify()) {
+
+#ifdef HAVE_INOTIFY
+ if (lp_parm_bool(-1, "notify", "inotify", true)) {
+ sys_notify_watch = inotify_watch;
+ }
+#endif
+
+#ifdef HAVE_FAM
+ if (lp_parm_bool(-1, "notify", "fam",
+ (sys_notify_watch == NULL))) {
+ sys_notify_watch = fam_watch;
+ }
+#endif
+ }
+
+ if (sys_notify_watch != NULL) {
+ sys_notify_ctx = sys_notify_context_create(msg_ctx, ev);
+ if (sys_notify_ctx == NULL) {
+ return NULL;
+ }
+ }
+
+ if (lp_clustering()) {
+ ctdbd_conn = messaging_ctdb_connection();
+ }
+
+ req = notifyd_send(msg_ctx, ev, msg_ctx, ctdbd_conn,
+ sys_notify_watch, sys_notify_ctx);
+ if (req == NULL) {
+ TALLOC_FREE(sys_notify_ctx);
+ return NULL;
+ }
+ tevent_req_set_callback(req, notifyd_stopped, msg_ctx);
+
+ return req;
+}
+
+static void notifyd_stopped(struct tevent_req *req)
+{
+ int ret;
+
+ ret = notifyd_recv(req);
+ TALLOC_FREE(req);
+ DEBUG(1, ("notifyd stopped: %s\n", strerror(ret)));
+}
+
+static void notifyd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *pvt)
+{
+ DBG_NOTICE("notifyd: Reloading services after SIGHUP\n");
+ reload_services(NULL, NULL, false);
+ reopen_logs();
+}
+
+static bool smbd_notifyd_init(struct messaging_context *msg, bool interactive,
+ struct server_id *ppid)
+{
+ struct tevent_context *ev = messaging_tevent_context(msg);
+ struct tevent_req *req;
+ struct tevent_signal *se = NULL;
+ pid_t pid;
+ NTSTATUS status;
+ bool ok;
+
+ if (interactive) {
+ req = notifyd_req(msg, ev);
+ return (req != NULL);
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ DEBUG(1, ("%s: fork failed: %s\n", __func__,
+ strerror(errno)));
+ return false;
+ }
+
+ if (pid != 0) {
+ if (am_parent != NULL) {
+ add_child_pid(am_parent, pid);
+ }
+ *ppid = pid_to_procid(pid);
+ return true;
+ }
+
+ status = smbd_reinit_after_fork(msg, ev, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("%s: reinit_after_fork failed: %s\n",
+ __func__, nt_errstr(status)));
+ exit(1);
+ }
+
+ process_set_title("smbd-notifyd", "notifyd");
+
+ reopen_logs();
+
+ /* Set up sighup handler for notifyd */
+ se = tevent_add_signal(ev,
+ ev,
+ SIGHUP, 0,
+ notifyd_sig_hup_handler,
+ NULL);
+ if (!se) {
+ DEBUG(0, ("failed to setup notifyd SIGHUP handler\n"));
+ exit(1);
+ }
+
+ req = notifyd_req(msg, ev);
+ if (req == NULL) {
+ exit(1);
+ }
+ tevent_req_set_callback(req, notifyd_stopped, msg);
+
+ /* Block those signals that we are not handling */
+ BlockSignals(True, SIGUSR1);
+
+ messaging_send(msg, pid_to_procid(getppid()), MSG_SMB_NOTIFY_STARTED,
+ NULL);
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_WARNING("tevent_req_poll returned %s\n", strerror(errno));
+ exit(1);
+ }
+ exit(0);
+}
+
+static void notifyd_init_trigger(struct tevent_req *req);
+
+struct notifyd_init_state {
+ bool ok;
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct server_id *ppid;
+};
+
+static struct tevent_req *notifyd_init_send(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct server_id *ppid)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct notifyd_init_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct notifyd_init_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct notifyd_init_state) {
+ .msg = msg,
+ .ev = ev,
+ .ppid = ppid
+ };
+
+ subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, notifyd_init_trigger, req);
+ return req;
+}
+
+static void notifyd_init_trigger(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notifyd_init_state *state = tevent_req_data(
+ req, struct notifyd_init_state);
+ bool ok;
+
+ DBG_NOTICE("Triggering notifyd startup\n");
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->ok = smbd_notifyd_init(state->msg, false, state->ppid);
+ if (state->ok) {
+ DBG_WARNING("notifyd restarted\n");
+ tevent_req_done(req);
+ return;
+ }
+
+ DBG_NOTICE("notifyd startup failed, rescheduling\n");
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ DBG_ERR("scheduling notifyd restart failed, giving up\n");
+ return;
+ }
+
+ tevent_req_set_callback(subreq, notifyd_init_trigger, req);
+ return;
+}
+
+static bool notifyd_init_recv(struct tevent_req *req)
+{
+ struct notifyd_init_state *state = tevent_req_data(
+ req, struct notifyd_init_state);
+
+ return state->ok;
+}
+
+static void notifyd_started(struct tevent_req *req)
+{
+ bool ok;
+
+ ok = notifyd_init_recv(req);
+ TALLOC_FREE(req);
+ if (!ok) {
+ DBG_ERR("Failed to restart notifyd, giving up\n");
+ return;
+ }
+}
+
+static void cleanupd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *pvt)
+{
+ DBG_NOTICE("cleanupd: Reloading services after SIGHUP\n");
+ reopen_logs();
+}
+
+static void cleanupd_stopped(struct tevent_req *req);
+
+static bool cleanupd_init(struct messaging_context *msg, bool interactive,
+ struct server_id *ppid)
+{
+ struct tevent_context *ev = messaging_tevent_context(msg);
+ struct server_id parent_id = messaging_server_id(msg);
+ struct tevent_signal *se = NULL;
+ struct tevent_req *req;
+ pid_t pid;
+ NTSTATUS status;
+ ssize_t rwret;
+ int ret;
+ bool ok;
+ char c;
+ int up_pipe[2];
+
+ if (interactive) {
+ req = smbd_cleanupd_send(msg, ev, msg, parent_id.pid);
+ *ppid = messaging_server_id(msg);
+ return (req != NULL);
+ }
+
+ ret = pipe(up_pipe);
+ if (ret == -1) {
+ DBG_WARNING("pipe failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ pid = fork();
+ if (pid == -1) {
+ DBG_WARNING("fork failed: %s\n", strerror(errno));
+ close(up_pipe[0]);
+ close(up_pipe[1]);
+ return false;
+ }
+
+ if (pid != 0) {
+
+ close(up_pipe[1]);
+ rwret = sys_read(up_pipe[0], &c, 1);
+ close(up_pipe[0]);
+
+ if (rwret == -1) {
+ DBG_WARNING("sys_read failed: %s\n", strerror(errno));
+ return false;
+ }
+ if (rwret == 0) {
+ DBG_WARNING("cleanupd could not start\n");
+ return false;
+ }
+ if (c != 0) {
+ DBG_WARNING("cleanupd returned %d\n", (int)c);
+ return false;
+ }
+
+ DBG_DEBUG("Started cleanupd pid=%d\n", (int)pid);
+
+ if (am_parent != NULL) {
+ add_child_pid(am_parent, pid);
+ }
+
+ *ppid = pid_to_procid(pid);
+ return true;
+ }
+
+ close(up_pipe[0]);
+
+ status = smbd_reinit_after_fork(msg, ev, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("reinit_after_fork failed: %s\n",
+ nt_errstr(status));
+ c = 1;
+ sys_write(up_pipe[1], &c, 1);
+
+ exit(1);
+ }
+
+ process_set_title("smbd-cleanupd", "cleanupd");
+
+ se = tevent_add_signal(ev,
+ ev,
+ SIGHUP,
+ 0,
+ cleanupd_sig_hup_handler,
+ NULL);
+ if (se == NULL) {
+ DBG_ERR("Could not add SIGHUP handler\n");
+ exit(1);
+ }
+
+ req = smbd_cleanupd_send(msg, ev, msg, parent_id.pid);
+ if (req == NULL) {
+ DBG_WARNING("smbd_cleanupd_send failed\n");
+ c = 2;
+ sys_write(up_pipe[1], &c, 1);
+
+ exit(1);
+ }
+
+ tevent_req_set_callback(req, cleanupd_stopped, msg);
+
+ c = 0;
+ rwret = sys_write(up_pipe[1], &c, 1);
+ close(up_pipe[1]);
+
+ if (rwret == -1) {
+ DBG_WARNING("sys_write failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ if (rwret != 1) {
+ DBG_WARNING("sys_write could not write result\n");
+ exit(1);
+ }
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ DBG_WARNING("tevent_req_poll returned %s\n", strerror(errno));
+ }
+ exit(0);
+}
+
+static void cleanupd_stopped(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ status = smbd_cleanupd_recv(req);
+ DBG_WARNING("cleanupd stopped: %s\n", nt_errstr(status));
+}
+
+static void cleanupd_init_trigger(struct tevent_req *req);
+
+struct cleanup_init_state {
+ bool ok;
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct server_id *ppid;
+};
+
+static struct tevent_req *cleanupd_init_send(struct tevent_context *ev,
+ TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg,
+ struct server_id *ppid)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct cleanup_init_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct cleanup_init_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct cleanup_init_state) {
+ .msg = msg,
+ .ev = ev,
+ .ppid = ppid
+ };
+
+ subreq = tevent_wakeup_send(state, ev, tevent_timeval_current_ofs(0, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, cleanupd_init_trigger, req);
+ return req;
+}
+
+static void cleanupd_init_trigger(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct cleanup_init_state *state = tevent_req_data(
+ req, struct cleanup_init_state);
+ bool ok;
+
+ DBG_NOTICE("Triggering cleanupd startup\n");
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ state->ok = cleanupd_init(state->msg, false, state->ppid);
+ if (state->ok) {
+ DBG_WARNING("cleanupd restarted\n");
+ tevent_req_done(req);
+ return;
+ }
+
+ DBG_NOTICE("cleanupd startup failed, rescheduling\n");
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ DBG_ERR("scheduling cleanupd restart failed, giving up\n");
+ return;
+ }
+
+ tevent_req_set_callback(subreq, cleanupd_init_trigger, req);
+ return;
+}
+
+static bool cleanupd_init_recv(struct tevent_req *req)
+{
+ struct cleanup_init_state *state = tevent_req_data(
+ req, struct cleanup_init_state);
+
+ return state->ok;
+}
+
+static void cleanupd_started(struct tevent_req *req)
+{
+ bool ok;
+ NTSTATUS status;
+ struct smbd_parent_context *parent = tevent_req_callback_data(
+ req, struct smbd_parent_context);
+
+ ok = cleanupd_init_recv(req);
+ TALLOC_FREE(req);
+ if (!ok) {
+ DBG_ERR("Failed to restart cleanupd, giving up\n");
+ return;
+ }
+
+ status = messaging_send(parent->msg_ctx,
+ parent->cleanupd,
+ MSG_SMB_NOTIFY_CLEANUP,
+ &data_blob_null);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send returned %s\n",
+ nt_errstr(status));
+ }
+}
+
+static void remove_child_pid(struct smbd_parent_context *parent,
+ pid_t pid,
+ bool unclean_shutdown)
+{
+ struct smbd_child_pid *child;
+ NTSTATUS status;
+ bool ok;
+
+ for (child = parent->children; child != NULL; child = child->next) {
+ if (child->pid == pid) {
+ struct smbd_child_pid *tmp = child;
+ DLIST_REMOVE(parent->children, child);
+ TALLOC_FREE(tmp);
+ parent->num_children -= 1;
+ break;
+ }
+ }
+
+ if (child == NULL) {
+ /* not all forked child processes are added to the children list */
+ DEBUG(2, ("Could not find child %d -- ignoring\n", (int)pid));
+ return;
+ }
+
+ if (pid == procid_to_pid(&parent->cleanupd)) {
+ struct tevent_req *req;
+
+ server_id_set_disconnected(&parent->cleanupd);
+
+ DBG_WARNING("Restarting cleanupd\n");
+ req = cleanupd_init_send(messaging_tevent_context(parent->msg_ctx),
+ parent,
+ parent->msg_ctx,
+ &parent->cleanupd);
+ if (req == NULL) {
+ DBG_ERR("Failed to restart cleanupd\n");
+ return;
+ }
+ tevent_req_set_callback(req, cleanupd_started, parent);
+ return;
+ }
+
+ if (pid == procid_to_pid(&parent->notifyd)) {
+ struct tevent_req *req;
+ struct tevent_context *ev = messaging_tevent_context(
+ parent->msg_ctx);
+
+ server_id_set_disconnected(&parent->notifyd);
+
+ DBG_WARNING("Restarting notifyd\n");
+ req = notifyd_init_send(ev,
+ parent,
+ parent->msg_ctx,
+ &parent->notifyd);
+ if (req == NULL) {
+ DBG_ERR("Failed to restart notifyd\n");
+ return;
+ }
+ tevent_req_set_callback(req, notifyd_started, parent);
+ return;
+ }
+
+ ok = cleanupdb_store_child(pid, unclean_shutdown);
+ if (!ok) {
+ DBG_ERR("cleanupdb_store_child failed\n");
+ return;
+ }
+
+ if (!server_id_is_disconnected(&parent->cleanupd)) {
+ status = messaging_send(parent->msg_ctx,
+ parent->cleanupd,
+ MSG_SMB_NOTIFY_CLEANUP,
+ &data_blob_null);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send returned %s\n",
+ nt_errstr(status));
+ }
+ }
+}
+
+/****************************************************************************
+ Have we reached the process limit ?
+****************************************************************************/
+
+static bool allowable_number_of_smbd_processes(struct smbd_parent_context *parent)
+{
+ int max_processes = lp_max_smbd_processes();
+
+ if (!max_processes)
+ return True;
+
+ return parent->num_children < max_processes;
+}
+
+static void smbd_sig_chld_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ pid_t pid;
+ int status;
+ struct smbd_parent_context *parent =
+ talloc_get_type_abort(private_data,
+ struct smbd_parent_context);
+
+ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
+ bool unclean_shutdown = False;
+
+ /* If the child terminated normally, assume
+ it was an unclean shutdown unless the
+ status is 0
+ */
+ if (WIFEXITED(status)) {
+ unclean_shutdown = WEXITSTATUS(status);
+ }
+ /* If the child terminated due to a signal
+ we always assume it was unclean.
+ */
+ if (WIFSIGNALED(status)) {
+ unclean_shutdown = True;
+ }
+ remove_child_pid(parent, pid, unclean_shutdown);
+ }
+}
+
+static void smbd_setup_sig_chld_handler(struct smbd_parent_context *parent)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(parent->ev_ctx,
+ parent, /* mem_ctx */
+ SIGCHLD, 0,
+ smbd_sig_chld_handler,
+ parent);
+ if (!se) {
+ exit_server("failed to setup SIGCHLD handler");
+ }
+}
+
+static void smbd_open_socket_close_fn(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ int fd,
+ void *private_data)
+{
+ /* this might be the socket_wrapper swrap_close() */
+ close(fd);
+}
+
+static void smbd_accept_connection(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbd_open_socket *s = talloc_get_type_abort(private_data,
+ struct smbd_open_socket);
+ struct messaging_context *msg_ctx = s->parent->msg_ctx;
+ struct sockaddr_storage addr;
+ socklen_t in_addrlen = sizeof(addr);
+ int fd;
+ pid_t pid = 0;
+
+ fd = accept(s->fd, (struct sockaddr *)(void *)&addr,&in_addrlen);
+ if (fd == -1 && errno == EINTR)
+ return;
+
+ if (fd == -1) {
+ DEBUG(0,("accept: %s\n",
+ strerror(errno)));
+ return;
+ }
+ smb_set_close_on_exec(fd);
+
+ if (s->parent->interactive) {
+ reinit_after_fork(msg_ctx, ev, true);
+ smbd_process(ev, msg_ctx, fd, true);
+ exit_server_cleanly("end of interactive mode");
+ return;
+ }
+
+ if (!allowable_number_of_smbd_processes(s->parent)) {
+ close(fd);
+ return;
+ }
+
+ pid = fork();
+ if (pid == 0) {
+ char addrstr[INET6_ADDRSTRLEN];
+ NTSTATUS status = NT_STATUS_OK;
+
+ /*
+ * Can't use TALLOC_FREE here. Nulling out the argument to it
+ * would overwrite memory we've just freed.
+ */
+ talloc_free(s->parent);
+ s = NULL;
+
+ /* Stop zombies, the parent explicitly handles
+ * them, counting worker smbds. */
+ CatchChild();
+
+ status = smbd_reinit_after_fork(msg_ctx, ev, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_TOO_MANY_OPENED_FILES)) {
+ DEBUG(0,("child process cannot initialize "
+ "because too many files are open\n"));
+ goto exit;
+ }
+ if (lp_clustering() &&
+ (NT_STATUS_EQUAL(
+ status, NT_STATUS_INTERNAL_DB_ERROR) ||
+ NT_STATUS_EQUAL(
+ status, NT_STATUS_CONNECTION_REFUSED))) {
+ DEBUG(1, ("child process cannot initialize "
+ "because connection to CTDB "
+ "has failed: %s\n",
+ nt_errstr(status)));
+ goto exit;
+ }
+
+ DEBUG(0,("reinit_after_fork() failed\n"));
+ smb_panic("reinit_after_fork() failed");
+ }
+
+ print_sockaddr(addrstr, sizeof(addrstr), &addr);
+ process_set_title("smbd[%s]", "client [%s]", addrstr);
+
+ smbd_process(ev, msg_ctx, fd, false);
+ exit:
+ exit_server_cleanly("end of child");
+ return;
+ }
+
+ if (pid < 0) {
+ DEBUG(0,("smbd_accept_connection: fork() failed: %s\n",
+ strerror(errno)));
+ }
+
+ /* The parent doesn't need this socket */
+ close(fd);
+
+ /* Sun May 6 18:56:14 2001 ackley@cs.unm.edu:
+ Clear the closed fd info out of server_fd --
+ and more importantly, out of client_fd in
+ util_sock.c, to avoid a possible
+ getpeername failure if we reopen the logs
+ and use %I in the filename.
+ */
+
+ if (pid != 0) {
+ add_child_pid(s->parent, pid);
+ }
+
+ /* Force parent to check log size after
+ * spawning child. Fix from
+ * klausr@ITAP.Physik.Uni-Stuttgart.De. The
+ * parent smbd will log to logserver.smb. It
+ * writes only two messages for each child
+ * started/finished. But each child writes,
+ * say, 50 messages also in logserver.smb,
+ * beginning with the debug_count of the
+ * parent, before the child opens its own log
+ * file logserver.client. In a worst case
+ * scenario the size of logserver.smb would be
+ * checked after about 50*50=2500 messages
+ * (ca. 100kb).
+ * */
+ force_check_log_size();
+}
+
+static bool smbd_open_one_socket(struct smbd_parent_context *parent,
+ struct tevent_context *ev_ctx,
+ const struct sockaddr_storage *ifss,
+ uint16_t port)
+{
+ struct smbd_open_socket *s;
+
+ s = talloc(parent, struct smbd_open_socket);
+ if (!s) {
+ return false;
+ }
+
+ s->parent = parent;
+
+ s->fd = open_socket_in(SOCK_STREAM, ifss, port, true);
+ if (s->fd < 0) {
+ int err = -(s->fd);
+ DBG_ERR("open_socket_in failed: %s\n", strerror(err));
+ TALLOC_FREE(s);
+ /*
+ * We ignore an error here, as we've done before
+ */
+ return true;
+ }
+
+ /* ready to listen */
+ set_socket_options(s->fd, "SO_KEEPALIVE");
+ set_socket_options(s->fd, lp_socket_options());
+
+ /* Set server socket to
+ * non-blocking for the accept. */
+ set_blocking(s->fd, False);
+
+ if (listen(s->fd, SMBD_LISTEN_BACKLOG) == -1) {
+ DEBUG(0,("smbd_open_one_socket: listen: "
+ "%s\n", strerror(errno)));
+ close(s->fd);
+ TALLOC_FREE(s);
+ return false;
+ }
+
+ s->fde = tevent_add_fd(ev_ctx,
+ s,
+ s->fd, TEVENT_FD_READ,
+ smbd_accept_connection,
+ s);
+ if (!s->fde) {
+ DEBUG(0,("smbd_open_one_socket: "
+ "tevent_add_fd: %s\n",
+ strerror(errno)));
+ close(s->fd);
+ TALLOC_FREE(s);
+ return false;
+ }
+ tevent_fd_set_close_fn(s->fde, smbd_open_socket_close_fn);
+
+ DLIST_ADD_END(parent->sockets, s);
+
+ return true;
+}
+
+/****************************************************************************
+ Open the socket communication.
+****************************************************************************/
+
+static bool open_sockets_smbd(struct smbd_parent_context *parent,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const char *smb_ports)
+{
+ int num_interfaces = iface_count();
+ int i,j;
+ const char **ports;
+ unsigned dns_port = 0;
+
+#ifdef HAVE_ATEXIT
+ atexit(killkids);
+#endif
+
+ /* Stop zombies */
+ smbd_setup_sig_chld_handler(parent);
+
+ ports = lp_smb_ports();
+
+ /* use a reasonable default set of ports - listing on 445 and 139 */
+ if (smb_ports) {
+ char **l;
+ l = str_list_make_v3(talloc_tos(), smb_ports, NULL);
+ ports = discard_const_p(const char *, l);
+ }
+
+ for (j = 0; ports && ports[j]; j++) {
+ unsigned port = atoi(ports[j]);
+
+ if (port == 0 || port > 0xffff) {
+ exit_server_cleanly("Invalid port in the config or on "
+ "the commandline specified!");
+ }
+ }
+
+ if (lp_interfaces() && lp_bind_interfaces_only()) {
+ /* We have been given an interfaces line, and been
+ told to only bind to those interfaces. Create a
+ socket per interface and bind to only these.
+ */
+
+ /* Now open a listen socket for each of the
+ interfaces. */
+ for(i = 0; i < num_interfaces; i++) {
+ const struct sockaddr_storage *ifss =
+ iface_n_sockaddr_storage(i);
+ if (ifss == NULL) {
+ DEBUG(0,("open_sockets_smbd: "
+ "interface %d has NULL IP address !\n",
+ i));
+ continue;
+ }
+
+ for (j = 0; ports && ports[j]; j++) {
+ unsigned port = atoi(ports[j]);
+
+ /* Keep the first port for mDNS service
+ * registration.
+ */
+ if (dns_port == 0) {
+ dns_port = port;
+ }
+
+ if (!smbd_open_one_socket(parent,
+ ev_ctx,
+ ifss,
+ port)) {
+ return false;
+ }
+ }
+ }
+ } else {
+ /* Just bind to 0.0.0.0 - accept connections
+ from anywhere. */
+
+ const char *sock_addr;
+ char *sock_tok;
+ const char *sock_ptr;
+
+#ifdef HAVE_IPV6
+ sock_addr = "::,0.0.0.0";
+#else
+ sock_addr = "0.0.0.0";
+#endif
+
+ for (sock_ptr=sock_addr;
+ next_token_talloc(talloc_tos(), &sock_ptr, &sock_tok, " \t,"); ) {
+ for (j = 0; ports && ports[j]; j++) {
+ struct sockaddr_storage ss;
+ unsigned port = atoi(ports[j]);
+
+ /* Keep the first port for mDNS service
+ * registration.
+ */
+ if (dns_port == 0) {
+ dns_port = port;
+ }
+
+ /* open an incoming socket */
+ if (!interpret_string_addr(&ss, sock_tok,
+ AI_NUMERICHOST|AI_PASSIVE)) {
+ continue;
+ }
+
+ /*
+ * If we fail to open any sockets
+ * in this loop the parent-sockets == NULL
+ * case below will prevent us from starting.
+ */
+
+ (void)smbd_open_one_socket(parent,
+ ev_ctx,
+ &ss,
+ port);
+ }
+ }
+ }
+
+ if (parent->sockets == NULL) {
+ DEBUG(0,("open_sockets_smbd: No "
+ "sockets available to bind to.\n"));
+ return false;
+ }
+
+ /* Listen to messages */
+
+ messaging_register(msg_ctx, NULL, MSG_SHUTDOWN, msg_exit_server);
+ messaging_register(msg_ctx, ev_ctx, MSG_SMB_CONF_UPDATED,
+ smbd_parent_conf_updated);
+ messaging_register(msg_ctx, NULL, MSG_DEBUG, smbd_msg_debug);
+ messaging_register(msg_ctx, NULL, MSG_SMB_FORCE_TDIS,
+ smb_parent_send_to_children);
+ messaging_register(msg_ctx, NULL, MSG_SMB_FORCE_TDIS_DENIED,
+ smb_parent_send_to_children);
+ messaging_register(msg_ctx, NULL, MSG_SMB_KILL_CLIENT_IP,
+ smb_parent_send_to_children);
+ messaging_register(msg_ctx, NULL, MSG_SMB_TELL_NUM_CHILDREN,
+ smb_tell_num_children);
+
+ messaging_register(msg_ctx, NULL,
+ ID_CACHE_DELETE, smbd_parent_id_cache_delete);
+ messaging_register(msg_ctx, NULL,
+ ID_CACHE_KILL, smbd_parent_id_cache_kill);
+ messaging_register(msg_ctx, NULL, MSG_SMB_NOTIFY_STARTED,
+ smb_parent_send_to_children);
+
+#ifdef DEVELOPER
+ messaging_register(msg_ctx, NULL, MSG_SMB_INJECT_FAULT,
+ msg_inject_fault);
+#endif
+
+#if defined(DEVELOPER) || defined(ENABLE_SELFTEST)
+ messaging_register(msg_ctx, NULL, MSG_SMB_SLEEP, msg_sleep);
+#endif
+
+ if (lp_multicast_dns_register() && (dns_port != 0)) {
+#ifdef WITH_DNSSD_SUPPORT
+ smbd_setup_mdns_registration(ev_ctx,
+ parent, dns_port);
+#endif
+#ifdef WITH_AVAHI_SUPPORT
+ void *avahi_conn;
+
+ avahi_conn = avahi_start_register(ev_ctx,
+ ev_ctx,
+ dns_port);
+ if (avahi_conn == NULL) {
+ DEBUG(10, ("avahi_start_register failed\n"));
+ }
+#endif
+ }
+
+ return true;
+}
+
+
+/*
+ handle stdin becoming readable when we are in --foreground mode
+ */
+static void smbd_stdin_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ char c;
+ if (read(0, &c, 1) != 1) {
+ /* we have reached EOF on stdin, which means the
+ parent has exited. Shutdown the server */
+ exit_server_cleanly("EOF on stdin");
+ }
+}
+
+struct smbd_parent_tevent_trace_state {
+ TALLOC_CTX *frame;
+};
+
+static void smbd_parent_tevent_trace_callback(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct smbd_parent_tevent_trace_state *state =
+ (struct smbd_parent_tevent_trace_state *)private_data;
+
+ switch (point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ break;
+ case TEVENT_TRACE_BEFORE_LOOP_ONCE:
+ TALLOC_FREE(state->frame);
+ state->frame = talloc_stackframe();
+ break;
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ TALLOC_FREE(state->frame);
+ break;
+ }
+
+ errno = 0;
+}
+
+static void smbd_parent_loop(struct tevent_context *ev_ctx,
+ struct smbd_parent_context *parent)
+{
+ struct smbd_parent_tevent_trace_state trace_state = {
+ .frame = NULL,
+ };
+ int ret = 0;
+
+ tevent_set_trace_callback(ev_ctx, smbd_parent_tevent_trace_callback,
+ &trace_state);
+
+ /* now accept incoming connections - forking a new process
+ for each incoming connection */
+ DEBUG(2,("waiting for connections\n"));
+
+ ret = tevent_loop_wait(ev_ctx);
+ if (ret != 0) {
+ DEBUG(0, ("tevent_loop_wait failed: %d, %s, exiting\n",
+ ret, strerror(errno)));
+ }
+
+ TALLOC_FREE(trace_state.frame);
+
+/* NOTREACHED return True; */
+}
+
+
+/****************************************************************************
+ Initialise connect, service and file structs.
+****************************************************************************/
+
+static bool init_structs(void )
+{
+ /*
+ * Set the machine NETBIOS name if not already
+ * set from the config file.
+ */
+
+ if (!secrets_init())
+ return False;
+
+ return True;
+}
+
+static void smbd_parent_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ exit_server_cleanly("termination signal");
+}
+
+static void smbd_parent_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ change_to_root_user();
+ DEBUG(1,("parent: Reloading services after SIGHUP\n"));
+ reload_services(NULL, NULL, false);
+}
+
+struct smbd_claim_version_state {
+ TALLOC_CTX *mem_ctx;
+ char *version;
+};
+
+static void smbd_claim_version_parser(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct smbd_claim_version_state *state = private_data;
+
+ if (datalen == 0) {
+ state->version = NULL;
+ return;
+ }
+ if (data[datalen-1] != '\0') {
+ DBG_WARNING("Invalid samba version\n");
+ dump_data(DBGLVL_WARNING, data, datalen);
+ state->version = NULL;
+ return;
+ }
+ state->version = talloc_strdup(state->mem_ctx, (const char *)data);
+}
+
+static NTSTATUS smbd_claim_version(struct messaging_context *msg,
+ const char *version)
+{
+ const char *name = "samba_version_string";
+ const TDB_DATA key = string_term_tdb_data(name);
+ struct smbd_claim_version_state state;
+ struct g_lock_ctx *ctx;
+ NTSTATUS status;
+
+ ctx = g_lock_ctx_init(msg, msg);
+ if (ctx == NULL) {
+ DBG_WARNING("g_lock_ctx_init failed\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = g_lock_lock(ctx,
+ key,
+ G_LOCK_READ,
+ (struct timeval) { .tv_sec = 60 },
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_lock(G_LOCK_READ) failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ state = (struct smbd_claim_version_state) { .mem_ctx = ctx };
+
+ status = g_lock_dump(ctx, key, smbd_claim_version_parser, &state);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DBG_ERR("Could not read samba_version_string\n");
+ g_lock_unlock(ctx, key);
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ if ((state.version != NULL) && (strcmp(version, state.version) == 0)) {
+ /*
+ * Leave the read lock for us around. Someone else already
+ * set the version correctly
+ */
+ TALLOC_FREE(ctx);
+ return NT_STATUS_OK;
+ }
+
+ status = g_lock_lock(ctx,
+ key,
+ G_LOCK_UPGRADE,
+ (struct timeval) { .tv_sec = 60 },
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_lock(G_LOCK_WRITE) failed: %s\n",
+ nt_errstr(status));
+ DBG_ERR("smbd %s already running, refusing to start "
+ "version %s\n", state.version, version);
+ TALLOC_FREE(ctx);
+ return NT_STATUS_SXS_VERSION_CONFLICT;
+ }
+
+ status = g_lock_write_data(
+ ctx, key, (const uint8_t *)version, strlen(version)+1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_write_data failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ status = g_lock_lock(ctx,
+ key,
+ G_LOCK_DOWNGRADE,
+ (struct timeval) { .tv_sec = 60 },
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("g_lock_lock(G_LOCK_READ) failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ /*
+ * Leave "ctx" dangling so that g_lock.tdb keeps opened.
+ */
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ main program.
+****************************************************************************/
+
+/* Declare prototype for build_options() to avoid having to run it through
+ mkproto.h. Mixing $(builddir) and $(srcdir) source files in the current
+ prototype generation system is too complicated. */
+
+extern void build_options(bool screen);
+
+ int main(int argc,const char *argv[])
+{
+ /* shall I run as a daemon */
+ struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
+ bool log_stdout = false;
+ char *ports = NULL;
+ char *profile_level = NULL;
+ int opt;
+ poptContext pc;
+ struct server_id main_server_id = {0};
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "build-options",
+ .shortName = 'b',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'b',
+ .descrip = "Print build options" ,
+ },
+ {
+ .longName = "port",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &ports,
+ .val = 0,
+ .descrip = "Listen on the specified ports",
+ },
+ {
+ .longName = "profiling-level",
+ .shortName = 'P',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &profile_level,
+ .val = 0,
+ .descrip = "Set profiling level","PROFILE_LEVEL",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DAEMON
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ struct smbd_parent_context *parent = NULL;
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+ struct server_id server_id;
+ struct tevent_signal *se;
+ int profiling_level;
+ char *np_dir = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ static const struct smbd_shim smbd_shim_fns =
+ {
+ .change_to_root_user = smbd_change_to_root_user,
+ .become_authenticated_pipe_user = smbd_become_authenticated_pipe_user,
+ .unbecome_authenticated_pipe_user = smbd_unbecome_authenticated_pipe_user,
+
+ .contend_level2_oplocks_begin = smbd_contend_level2_oplocks_begin,
+ .contend_level2_oplocks_end = smbd_contend_level2_oplocks_end,
+
+ .become_root = smbd_become_root,
+ .unbecome_root = smbd_unbecome_root,
+
+ .exit_server = smbd_exit_server,
+ .exit_server_cleanly = smbd_exit_server_cleanly,
+ };
+ bool ok;
+
+ setproctitle_init(argc, discard_const(argv), environ);
+
+ /*
+ * Do this before any other talloc operation
+ */
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+
+ smb_init_locale();
+
+ set_smbd_shim(&smbd_shim_fns);
+
+ smbd_init_globals();
+
+ TimeInit();
+
+#ifdef HAVE_SET_AUTH_PARAMETERS
+ set_auth_parameters(argc,argv);
+#endif
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to setup cmdline parser!\n");
+ exit(ENOMEM);
+ }
+
+ cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to get popt context!\n");
+ exit(ENOMEM);
+ }
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'b':
+ build_options(true); /* Display output to screen as well as debug */
+ exit(0);
+ break;
+ default:
+ d_fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+ poptFreeContext(pc);
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+
+ if (cmdline_daemon_cfg->interactive) {
+ log_stdout = True;
+ }
+
+#ifdef HAVE_SETLUID
+ /* needed for SecureWare on SCO */
+ setluid(0);
+#endif
+
+ set_remote_machine_name("smbd", False);
+
+ if (cmdline_daemon_cfg->interactive && (DEBUGLEVEL >= 9)) {
+ talloc_enable_leak_report();
+ }
+
+ if (log_stdout && cmdline_daemon_cfg->fork) {
+ DEBUG(0,("ERROR: Can't log to stdout (-S) unless daemon is in foreground (-F) or interactive (-i)\n"));
+ exit(1);
+ }
+
+ /*
+ * We want to die early if we can't open /dev/urandom
+ */
+ generate_random_buffer(NULL, 0);
+
+ /* get initial effective uid and gid */
+ sec_init();
+
+ /* make absolutely sure we run as root - to handle cases where people
+ are crazy enough to have it setuid */
+ gain_root_privilege();
+ gain_root_group_privilege();
+
+ dump_core_setup("smbd", lp_logfile(talloc_tos(), lp_sub));
+
+ /* we are never interested in SIGPIPE */
+ BlockSignals(True,SIGPIPE);
+
+#if defined(SIGFPE)
+ /* we are never interested in SIGFPE */
+ BlockSignals(True,SIGFPE);
+#endif
+
+#if defined(SIGUSR2)
+ /* We are no longer interested in USR2 */
+ BlockSignals(True,SIGUSR2);
+#endif
+
+ /*
+ * POSIX demands that signals are inherited. If the invoking
+ * process has these signals masked, we will have problems, as
+ * we won't receive them.
+ */
+ BlockSignals(False, SIGHUP);
+ BlockSignals(False, SIGUSR1);
+ BlockSignals(False, SIGTERM);
+
+ /* Ensure we leave no zombies until we
+ * correctly set up child handling below. */
+
+ CatchChild();
+
+ /* we want total control over the permissions on created files,
+ so set our umask to 0 */
+ umask(0);
+
+ reopen_logs();
+
+ DBG_STARTUP_NOTICE("smbd version %s started.\n%s\n",
+ samba_version_string(),
+ samba_copyright_string());
+
+ DEBUG(2,("uid=%d gid=%d euid=%d egid=%d\n",
+ (int)getuid(),(int)getgid(),(int)geteuid(),(int)getegid()));
+
+ /* Output the build options to the debug log */
+ build_options(False);
+
+ if (sizeof(uint16_t) < 2 || sizeof(uint32_t) < 4) {
+ DEBUG(0,("ERROR: Samba is not configured correctly for the word size on your machine\n"));
+ exit(1);
+ }
+
+ /*
+ * This calls unshare(CLONE_FS); on linux
+ * in order to check if the running kernel/container
+ * environment supports it.
+ */
+ per_thread_cwd_check();
+
+ if (!cluster_probe_ok()) {
+ exit(1);
+ }
+
+ /* Init the security context and global current_user */
+ init_sec_ctx();
+
+ /*
+ * Initialize the event context. The event context needs to be
+ * initialized before the messaging context, cause the messaging
+ * context holds an event context.
+ */
+ ev_ctx = global_event_context();
+ if (ev_ctx == NULL) {
+ exit(1);
+ }
+
+ /*
+ * Init the messaging context
+ * FIXME: This should only call messaging_init()
+ */
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ exit(1);
+ }
+
+ /*
+ * Reloading of the printers will not work here as we don't have a
+ * server info and rpc services set up. It will be called later.
+ */
+ if (!reload_services(NULL, NULL, false)) {
+ exit(1);
+ }
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ if (!lp_parm_bool(-1, "server role check", "inhibit", false)) {
+ DBG_ERR("server role = 'active directory domain controller' not compatible with running smbd standalone. \n");
+ DEBUGADD(0, ("You should start 'samba' instead, and it will control starting smbd if required\n"));
+ exit(1);
+ }
+ /* Main 'samba' daemon will notify */
+ daemon_sd_notifications(false);
+ }
+
+ /* ...NOTE... Log files are working from this point! */
+
+ DEBUG(3,("loaded services\n"));
+
+ init_structs();
+
+ if (!profile_setup(msg_ctx, False)) {
+ DEBUG(0,("ERROR: failed to setup profiling\n"));
+ return -1;
+ }
+
+ if (profile_level != NULL) {
+ profiling_level = atoi(profile_level);
+ } else {
+ profiling_level = lp_smbd_profiling_level();
+ }
+ main_server_id = messaging_server_id(msg_ctx);
+ set_profile_level(profiling_level, &main_server_id);
+
+ if (!cmdline_daemon_cfg->daemon && !is_a_socket(0)) {
+ if (!cmdline_daemon_cfg->interactive) {
+ DEBUG(3, ("Standard input is not a socket, "
+ "assuming -D option\n"));
+ }
+
+ /*
+ * Setting "daemon" here prevents us from eventually calling
+ * the open_sockets_inetd()
+ */
+
+ cmdline_daemon_cfg->daemon = true;
+ }
+
+ if (cmdline_daemon_cfg->daemon && !cmdline_daemon_cfg->interactive) {
+ DEBUG(3, ("Becoming a daemon.\n"));
+ become_daemon(cmdline_daemon_cfg->fork,
+ cmdline_daemon_cfg->no_process_group,
+ log_stdout);
+ } else {
+ daemon_status("smbd", "Starting process ...");
+ }
+
+#ifdef HAVE_SETPGID
+ /*
+ * If we're interactive we want to set our own process group for
+ * signal management.
+ */
+ if (cmdline_daemon_cfg->interactive &&
+ !cmdline_daemon_cfg->no_process_group)
+ {
+ setpgid( (pid_t)0, (pid_t)0);
+ }
+#endif
+
+ if (!directory_exist(lp_lock_directory()))
+ mkdir(lp_lock_directory(), 0755);
+
+ if (!directory_exist(lp_pid_directory()))
+ mkdir(lp_pid_directory(), 0755);
+
+ if (cmdline_daemon_cfg->daemon)
+ pidfile_create(lp_pid_directory(), "smbd");
+
+ status = reinit_after_fork(msg_ctx, ev_ctx, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("reinit_after_fork() failed", map_errno_from_nt_status(status));
+ }
+
+ if (!cmdline_daemon_cfg->interactive) {
+ /*
+ * Do not initialize the parent-child-pipe before becoming a
+ * daemon: this is used to detect a died parent in the child
+ * process.
+ */
+ status = init_before_fork();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon(nt_errstr(status), map_errno_from_nt_status(status));
+ }
+ }
+
+ parent = talloc_zero(ev_ctx, struct smbd_parent_context);
+ if (!parent) {
+ exit_server("talloc(struct smbd_parent_context) failed");
+ }
+ parent->interactive = cmdline_daemon_cfg->interactive;
+ parent->ev_ctx = ev_ctx;
+ parent->msg_ctx = msg_ctx;
+ am_parent = parent;
+
+ se = tevent_add_signal(parent->ev_ctx,
+ parent,
+ SIGTERM, 0,
+ smbd_parent_sig_term_handler,
+ parent);
+ if (!se) {
+ exit_server("failed to setup SIGTERM handler");
+ }
+ se = tevent_add_signal(parent->ev_ctx,
+ parent,
+ SIGHUP, 0,
+ smbd_parent_sig_hup_handler,
+ parent);
+ if (!se) {
+ exit_server("failed to setup SIGHUP handler");
+ }
+
+ /* Setup all the TDB's - including CLEAR_IF_FIRST tdb's. */
+
+ if (smbd_memcache() == NULL) {
+ exit_daemon("no memcache available", EACCES);
+ }
+
+ memcache_set_global(smbd_memcache());
+
+ /* Initialise the password backed before the global_sam_sid
+ to ensure that we fetch from ldap before we make a domain sid up */
+
+ if(!initialize_password_db(false, ev_ctx))
+ exit(1);
+
+ if (!secrets_init()) {
+ exit_daemon("smbd can not open secrets.tdb", EACCES);
+ }
+
+ if (lp_server_role() == ROLE_DOMAIN_BDC || lp_server_role() == ROLE_DOMAIN_PDC || lp_server_role() == ROLE_IPA_DC) {
+ struct loadparm_context *lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+ if (!open_schannel_session_store(NULL, lp_ctx)) {
+ exit_daemon("ERROR: Samba cannot open schannel store for secured NETLOGON operations.", EACCES);
+ }
+ TALLOC_FREE(lp_ctx);
+ }
+
+ if(!get_global_sam_sid()) {
+ exit_daemon("Samba cannot create a SAM SID", EACCES);
+ }
+
+ server_id = messaging_server_id(msg_ctx);
+ status = smbXsrv_version_global_init(&server_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init server context", EACCES);
+ }
+
+ status = smbXsrv_client_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init clients context", EACCES);
+ }
+
+ status = smbXsrv_session_global_init(msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init session context", EACCES);
+ }
+
+ status = smbXsrv_tcon_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init tcon context", EACCES);
+ }
+
+ if (!locking_init())
+ exit_daemon("Samba cannot init locking", EACCES);
+
+ if (!leases_db_init(false)) {
+ exit_daemon("Samba cannot init leases", EACCES);
+ }
+
+ if (!smbd_notifyd_init(
+ msg_ctx,
+ cmdline_daemon_cfg->interactive,
+ &parent->notifyd)) {
+ exit_daemon("Samba cannot init notification", EACCES);
+ }
+
+ if (!cleanupd_init(
+ msg_ctx,
+ cmdline_daemon_cfg->interactive,
+ &parent->cleanupd)) {
+ exit_daemon("Samba cannot init the cleanupd", EACCES);
+ }
+
+ if (!messaging_parent_dgm_cleanup_init(msg_ctx)) {
+ exit(1);
+ }
+
+ if (!smbd_scavenger_init(NULL, msg_ctx, ev_ctx)) {
+ exit_daemon("Samba cannot init scavenging", EACCES);
+ }
+
+ if (!W_ERROR_IS_OK(registry_init_full()))
+ exit_daemon("Samba cannot init registry", EACCES);
+
+ /* Open the share_info.tdb here, so we don't have to open
+ after the fork on every single connection. This is a small
+ performance improvement and reduces the total number of system
+ fds used. */
+ status = share_info_db_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("ERROR: failed to load share info db.", EACCES);
+ }
+
+ status = init_system_session_info(NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("ERROR: failed to setup system user info: %s.\n",
+ nt_errstr(status)));
+ return -1;
+ }
+
+ if (!init_guest_session_info(NULL)) {
+ DEBUG(0,("ERROR: failed to setup guest info.\n"));
+ return -1;
+ }
+
+ if (!file_init_global()) {
+ DEBUG(0, ("ERROR: file_init_global() failed\n"));
+ return -1;
+ }
+ status = smbXsrv_open_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Samba cannot init global open", map_errno_from_nt_status(status));
+ }
+
+ if (lp_clustering() && !lp_allow_unsafe_cluster_upgrade()) {
+ status = smbd_claim_version(msg_ctx, samba_version_string());
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Could not claim version: %s\n",
+ nt_errstr(status));
+ return -1;
+ }
+ }
+
+ /* This MUST be done before start_epmd() because otherwise
+ * start_epmd() forks and races against dcesrv_ep_setup() to
+ * call directory_create_or_exist() */
+ if (!directory_create_or_exist(lp_ncalrpc_dir(), 0755)) {
+ DEBUG(0, ("Failed to create pipe directory %s - %s\n",
+ lp_ncalrpc_dir(), strerror(errno)));
+ return -1;
+ }
+
+ np_dir = talloc_asprintf(talloc_tos(), "%s/np", lp_ncalrpc_dir());
+ if (!np_dir) {
+ DEBUG(0, ("%s: Out of memory\n", __location__));
+ return -1;
+ }
+
+ if (!directory_create_or_exist_strict(np_dir, geteuid(), 0700)) {
+ DEBUG(0, ("Failed to create pipe directory %s - %s\n",
+ np_dir, strerror(errno)));
+ return -1;
+ }
+
+ if (!cmdline_daemon_cfg->interactive) {
+ daemon_ready("smbd");
+ }
+
+ if (!cmdline_daemon_cfg->daemon) {
+ int ret, sock;
+
+ /* inetd mode */
+ TALLOC_FREE(frame);
+
+ /* Started from inetd. fd 0 is the socket. */
+ /* We will abort gracefully when the client or remote system
+ goes away */
+ sock = dup(0);
+
+ /* close stdin, stdout (if not logging to it), but not stderr */
+ ret = close_low_fd(0);
+ if (ret != 0) {
+ DBG_ERR("close_low_fd(0) failed: %s\n", strerror(ret));
+ return 1;
+ }
+ if (!debug_get_output_is_stdout()) {
+ ret = close_low_fd(1);
+ if (ret != 0) {
+ DBG_ERR("close_low_fd(1) failed: %s\n",
+ strerror(ret));
+ return 1;
+ }
+ }
+
+#ifdef HAVE_ATEXIT
+ atexit(killkids);
+#endif
+
+ /* Stop zombies */
+ smbd_setup_sig_chld_handler(parent);
+
+ smbd_process(ev_ctx, msg_ctx, sock, true);
+
+ exit_server_cleanly(NULL);
+ return(0);
+ }
+
+ if (!open_sockets_smbd(parent, ev_ctx, msg_ctx, ports))
+ exit_server("open_sockets_smbd() failed");
+
+ TALLOC_FREE(frame);
+ /* make sure we always have a valid stackframe */
+ frame = talloc_stackframe();
+
+ if (!cmdline_daemon_cfg->fork) {
+ /* if we are running in the foreground then look for
+ EOF on stdin, and exit if it happens. This allows
+ us to die if the parent process dies
+ Only do this on a pipe or socket, no other device.
+ */
+ struct stat st;
+ if (fstat(0, &st) != 0) {
+ return 1;
+ }
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) {
+ tevent_add_fd(ev_ctx,
+ parent,
+ 0,
+ TEVENT_FD_READ,
+ smbd_stdin_handler,
+ NULL);
+ }
+ }
+
+ smbd_parent_loop(ev_ctx, parent);
+
+ exit_server_cleanly(NULL);
+ TALLOC_FREE(frame);
+ return(0);
+}
diff --git a/source3/smbd/server_exit.c b/source3/smbd/server_exit.c
new file mode 100644
index 0000000..5e0c386
--- /dev/null
+++ b/source3/smbd/server_exit.c
@@ -0,0 +1,262 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002-2003
+ Copyright (C) Volker Lendecke 1993-2007
+ Copyright (C) Jeremy Allison 1993-2007
+ Copyright (C) Andrew Bartlett 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "printing/notify.h"
+#include "printing.h"
+#include "serverid.h"
+#include "messages.h"
+#include "passdb.h"
+#include "../lib/util/pidfile.h"
+#include "smbprofile.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "lib/gencache.h"
+#include "rpc_server/rpc_config.h"
+#include "lib/global_contexts.h"
+
+static struct files_struct *log_writeable_file_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ bool *found = (bool *)private_data;
+ char *path;
+
+ if (!fsp->fsp_flags.can_write) {
+ return NULL;
+ }
+ if (!(*found)) {
+ DEBUG(0, ("Writable files open at exit:\n"));
+ *found = true;
+ }
+
+ path = talloc_asprintf(talloc_tos(), "%s/%s", fsp->conn->connectpath,
+ smb_fname_str_dbg(fsp->fsp_name));
+ if (path == NULL) {
+ DEBUGADD(0, ("<NOMEM>\n"));
+ }
+
+ DEBUGADD(0, ("%s\n", path));
+
+ TALLOC_FREE(path);
+ return NULL;
+}
+
+/****************************************************************************
+ Exit the server.
+****************************************************************************/
+
+/* Reasons for shutting down a server process. */
+enum server_exit_reason { SERVER_EXIT_NORMAL, SERVER_EXIT_ABNORMAL };
+
+static void exit_server_common(enum server_exit_reason how,
+ const char *reason) _NORETURN_;
+
+static void exit_server_common(enum server_exit_reason how,
+ const char *reason)
+{
+ struct smbXsrv_client *client = global_smbXsrv_client;
+ struct smbXsrv_connection *xconn = NULL;
+ struct smbd_server_connection *sconn = NULL;
+ NTSTATUS disconnect_status;
+
+ if (!exit_firsttime) {
+ exit(0);
+ }
+ exit_firsttime = false;
+
+ switch (how) {
+ case SERVER_EXIT_NORMAL:
+ disconnect_status = NT_STATUS_LOCAL_DISCONNECT;
+ break;
+ case SERVER_EXIT_ABNORMAL:
+ default:
+ disconnect_status = NT_STATUS_INTERNAL_ERROR;
+ break;
+ }
+
+ if (client != NULL) {
+ NTSTATUS status;
+
+ sconn = client->sconn;
+ xconn = client->connections;
+
+ /*
+ * Make sure we stop handling new multichannel
+ * connections early!
+ *
+ * From here, we're not able to handle them.
+ */
+ status = smbXsrv_client_remove(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ D_ERR("Server exit (%s)\n",
+ (reason ? reason : "normal exit"));
+ DBG_ERR("smbXsrv_client_remove() failed (%s)\n",
+ nt_errstr(status));
+ }
+ }
+
+ change_to_root_user();
+
+
+ /*
+ * Here we typically have just one connection
+ */
+ for (; xconn != NULL; xconn = xconn->next) {
+ /*
+ * This is typically the disconnect for the only
+ * (or with multi-channel last) connection of the client.
+ *
+ * smbXsrv_connection_disconnect_transport() might be called already,
+ * but calling it again is a no-op.
+ */
+ smbXsrv_connection_disconnect_transport(xconn, disconnect_status);
+ }
+
+ change_to_root_user();
+
+ if (sconn != NULL) {
+ if (lp_log_writeable_files_on_exit()) {
+ bool found = false;
+ files_forall(sconn, log_writeable_file_fn, &found);
+ }
+ }
+
+ change_to_root_user();
+
+ if (client != NULL) {
+ NTSTATUS status;
+
+ /*
+ * Note: this is a no-op for smb2 as
+ * conn->tcon_table is empty
+ */
+ status = smb1srv_tcon_disconnect_all(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Server exit (%s)\n",
+ (reason ? reason : "normal exit")));
+ DEBUG(0, ("exit_server_common: "
+ "smb1srv_tcon_disconnect_all() failed (%s) - "
+ "triggering cleanup\n", nt_errstr(status)));
+ }
+
+ status = smbXsrv_session_logoff_all(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Server exit (%s)\n",
+ (reason ? reason : "normal exit")));
+ DEBUG(0, ("exit_server_common: "
+ "smbXsrv_session_logoff_all() failed (%s) - "
+ "triggering cleanup\n", nt_errstr(status)));
+ }
+ }
+
+ change_to_root_user();
+
+ if (client != NULL) {
+ struct smbXsrv_connection *xconn_next = NULL;
+
+ for (xconn = client->connections;
+ xconn != NULL;
+ xconn = xconn_next) {
+ xconn_next = xconn->next;
+ DLIST_REMOVE(client->connections, xconn);
+ TALLOC_FREE(xconn);
+ }
+ }
+
+ change_to_root_user();
+
+#ifdef USE_DMAPI
+ /* Destroy Samba DMAPI session only if we are master smbd process */
+ if (am_parent) {
+ if (!dmapi_destroy_session()) {
+ DEBUG(0,("Unable to close Samba DMAPI session\n"));
+ }
+ }
+#endif
+
+
+ /*
+ * we need to force the order of freeing the following,
+ * because smbd_msg_ctx is not a talloc child of smbd_server_conn.
+ */
+ if (client != NULL) {
+ TALLOC_FREE(client->sconn);
+ }
+ sconn = NULL;
+ xconn = NULL;
+ client = NULL;
+ netlogon_creds_cli_close_global_db();
+ TALLOC_FREE(global_smbXsrv_client);
+ smbprofile_dump();
+ global_messaging_context_free();
+ global_event_context_free();
+ TALLOC_FREE(smbd_memcache_ctx);
+
+ locking_end();
+
+ if (how != SERVER_EXIT_NORMAL) {
+
+ smb_panic(reason);
+
+ /* Notreached. */
+ exit(1);
+ } else {
+ DEBUG(3,("Server exit (%s)\n",
+ (reason ? reason : "normal exit")));
+ if (am_parent) {
+ pidfile_unlink(lp_pid_directory(), "smbd");
+ }
+ }
+
+ exit(0);
+}
+
+void smbd_exit_server(const char *const explanation)
+{
+ exit_server_common(SERVER_EXIT_ABNORMAL, explanation);
+}
+
+void smbd_exit_server_cleanly(const char *const explanation)
+{
+ exit_server_common(SERVER_EXIT_NORMAL, explanation);
+}
+
+/*
+ * reinit_after_fork() wrapper that should be called when forking from
+ * smbd.
+ */
+NTSTATUS smbd_reinit_after_fork(struct messaging_context *msg_ctx,
+ struct tevent_context *ev_ctx,
+ bool parent_longlived)
+{
+ NTSTATUS ret;
+ am_parent = NULL;
+ ret = reinit_after_fork(msg_ctx, ev_ctx, parent_longlived);
+ initialize_password_db(true, ev_ctx);
+ return ret;
+}
diff --git a/source3/smbd/server_reload.c b/source3/smbd/server_reload.c
new file mode 100644
index 0000000..d3322d1
--- /dev/null
+++ b/source3/smbd/server_reload.c
@@ -0,0 +1,176 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Martin Pool 2002
+ Copyright (C) Jelmer Vernooij 2002-2003
+ Copyright (C) Volker Lendecke 1993-2007
+ Copyright (C) Jeremy Allison 1993-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "nt_printing.h"
+#include "printing/pcap.h"
+#include "printing/printer_list.h"
+#include "printing/load.h"
+#include "auth.h"
+#include "messages.h"
+#include "lib/param/loadparm.h"
+
+/*
+ * The persistent pcap cache is populated by the background print process. Per
+ * client smbds should only reload their printer share inventories if this
+ * information has changed. Use reload_last_pcap_time to detect this.
+ */
+static time_t reload_last_pcap_time = 0;
+
+bool snum_is_shared_printer(int snum)
+{
+ return (lp_browseable(snum) && lp_snum_ok(snum) && lp_printable(snum));
+}
+
+/**
+ * @brief Purge stale printer shares and reload from pre-populated pcap cache.
+ *
+ * This function should normally only be called as a callback on a successful
+ * pcap_cache_reload(), or on client enumeration.
+ */
+void delete_and_reload_printers(void)
+{
+ int n_services;
+ int pnum;
+ int snum;
+ const char *pname;
+ bool ok;
+ time_t pcap_last_update;
+ TALLOC_CTX *frame = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (!lp_load_printers()) {
+ DBG_DEBUG("skipping printer reload: disabled\n");
+ return;
+ }
+
+ frame = talloc_stackframe();
+ ok = pcap_cache_loaded(&pcap_last_update);
+ if (!ok) {
+ DEBUG(1, ("pcap cache not loaded\n"));
+ talloc_free(frame);
+ return;
+ }
+
+ if (reload_last_pcap_time == pcap_last_update) {
+ DEBUG(5, ("skipping printer reload, already up to date.\n"));
+ talloc_free(frame);
+ return;
+ }
+ reload_last_pcap_time = pcap_last_update;
+
+ /* Get pcap printers updated */
+ load_printers();
+
+ n_services = lp_numservices();
+ pnum = lp_servicenumber(PRINTERS_NAME);
+
+ DEBUG(10, ("reloading printer services from pcap cache\n"));
+
+ /*
+ * Add default config for printers added to smb.conf file and remove
+ * stale printers
+ */
+ for (snum = 0; snum < n_services; snum++) {
+ /* avoid removing PRINTERS_NAME */
+ if (snum == pnum) {
+ continue;
+ }
+
+ /* skip no-printer services */
+ if (!snum_is_shared_printer(snum)) {
+ continue;
+ }
+
+ pname = lp_printername(frame, lp_sub, snum);
+
+ /* check printer, but avoid removing non-autoloaded printers */
+ if (lp_autoloaded(snum) &&
+ !printer_list_printername_exists(pname)) {
+ lp_killservice(snum);
+ }
+ }
+
+ /* Make sure deleted printers are gone */
+ load_printers();
+
+ talloc_free(frame);
+}
+
+/****************************************************************************
+ Reload the services file.
+**************************************************************************/
+
+bool reload_services(struct smbd_server_connection *sconn,
+ bool (*snumused) (struct smbd_server_connection *, int),
+ bool test)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = NULL;
+ bool ret;
+
+ if (lp_loaded()) {
+ char *fname = lp_next_configfile(talloc_tos(), lp_sub);
+ if (file_exist(fname) &&
+ !strcsequal(fname, get_dyn_CONFIGFILE())) {
+ set_dyn_CONFIGFILE(fname);
+ test = False;
+ }
+ TALLOC_FREE(fname);
+ }
+
+ reopen_logs();
+
+ if (test && !lp_file_list_changed())
+ return(True);
+
+ lp_killunused(sconn, snumused);
+
+ ret = lp_load_with_shares(get_dyn_CONFIGFILE());
+
+ /* perhaps the config filename is now set */
+ if (!test) {
+ reload_services(sconn, snumused, true);
+ }
+
+ reopen_logs();
+
+ load_interfaces();
+
+ if (sconn != NULL && sconn->client != NULL) {
+ xconn = sconn->client->connections;
+ }
+ for (;xconn != NULL; xconn = xconn->next) {
+ set_socket_options(xconn->transport.sock, "SO_KEEPALIVE");
+ set_socket_options(xconn->transport.sock, lp_socket_options());
+ }
+
+ mangle_reset_cache();
+ flush_dfree_cache();
+
+ return(ret);
+}
diff --git a/source3/smbd/session.c b/source3/smbd/session.c
new file mode 100644
index 0000000..abc7991
--- /dev/null
+++ b/source3/smbd/session.c
@@ -0,0 +1,218 @@
+/*
+ Unix SMB/CIFS implementation.
+ session handling for utmp and PAM
+
+ Copyright (C) tridge@samba.org 2001
+ Copyright (C) abartlet@samba.org 2001
+ Copyright (C) Gerald (Jerry) Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* a "session" is claimed when we do a SessionSetupX operation
+ and is yielded when the corresponding vuid is destroyed.
+
+ sessions are used to populate utmp and PAM session structures
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "session.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+
+/********************************************************************
+ called when a session is created
+********************************************************************/
+
+bool session_claim(struct smbXsrv_session *session)
+{
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ const char *username;
+ const char *hostname;
+ unsigned int id_num;
+ fstring id_str;
+
+ /* don't register sessions for the guest user - its just too
+ expensive to go through pam session code for browsing etc */
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ return true;
+ }
+
+ id_num = session->global->session_global_id;
+
+ snprintf(id_str, sizeof(id_str), "smb/%u", id_num);
+
+ /* Make clear that we require the optional unix_token in the source3 code */
+ SMB_ASSERT(session_info->unix_token);
+
+ username = session_info->unix_info->unix_name;
+ hostname = session->global->channels[0].remote_name;
+
+ if (!smb_pam_claim_session(username, id_str, hostname)) {
+ DEBUG(1,("pam_session rejected the session for %s [%s]\n",
+ username, id_str));
+ return false;
+ }
+
+ if (lp_utmp()) {
+ sys_utmp_claim(username, hostname, id_str, id_num);
+ }
+
+ return true;
+}
+
+/********************************************************************
+ called when a session is destroyed
+********************************************************************/
+
+void session_yield(struct smbXsrv_session *session)
+{
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ const char *username;
+ const char *hostname;
+ unsigned int id_num;
+ fstring id_str = "";
+
+ id_num = session->global->session_global_id;
+
+ snprintf(id_str, sizeof(id_str), "smb/%u", id_num);
+
+ /* Make clear that we require the optional unix_token in the source3 code */
+ SMB_ASSERT(session_info->unix_token);
+
+ username = session_info->unix_info->unix_name;
+ hostname = session->global->channels[0].remote_name;
+
+ if (lp_utmp()) {
+ sys_utmp_yield(username, hostname, id_str, id_num);
+ }
+
+ smb_pam_close_session(username, id_str, hostname);
+}
+
+/********************************************************************
+********************************************************************/
+
+struct session_list {
+ TALLOC_CTX *mem_ctx;
+ int count;
+ const char *filter_user;
+ const char *filter_machine;
+ struct sessionid *sessions;
+};
+
+static int gather_sessioninfo(const char *key, struct sessionid *session,
+ void *private_data)
+{
+ struct session_list *sesslist = (struct session_list *)private_data;
+
+ /* filter the session if required */
+
+ if (sesslist->filter_user &&
+ (sesslist->filter_user[0] != '\0') &&
+ !strequal(session->username, sesslist->filter_user)) {
+ return 0;
+ }
+
+ if (sesslist->filter_machine &&
+ (sesslist->filter_machine[0] != '\0') &&
+ !strequal(session->remote_machine,
+ sesslist->filter_machine)) {
+ return 0;
+ }
+
+ if (!process_exists(session->pid)) {
+ return 0;
+ }
+
+ sesslist->sessions = talloc_realloc(
+ sesslist->mem_ctx, sesslist->sessions, struct sessionid,
+ sesslist->count+1);
+
+ if (!sesslist->sessions) {
+ sesslist->count = 0;
+ return -1;
+ }
+
+ memcpy(&sesslist->sessions[sesslist->count], session,
+ sizeof(struct sessionid));
+
+ sesslist->count++;
+
+ DEBUG(7, ("gather_sessioninfo session from %s@%s\n",
+ session->username, session->remote_machine));
+
+ return 0;
+}
+
+/********************************************************************
+********************************************************************/
+
+int list_sessions(TALLOC_CTX *mem_ctx, struct sessionid **session_list)
+{
+ struct session_list sesslist;
+ NTSTATUS status;
+
+ sesslist.mem_ctx = mem_ctx;
+ sesslist.count = 0;
+ sesslist.filter_user = NULL;
+ sesslist.filter_machine = NULL;
+ sesslist.sessions = NULL;
+
+ status = sessionid_traverse_read(gather_sessioninfo, (void *) &sesslist);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Session traverse failed: %s\n", nt_errstr(status));
+ TALLOC_FREE(sesslist.sessions);
+ *session_list = NULL;
+ return 0;
+ }
+
+ *session_list = sesslist.sessions;
+ return sesslist.count;
+}
+
+/********************************************************************
+find the sessions that match the given username and machine
+********************************************************************/
+
+int find_sessions(TALLOC_CTX *mem_ctx, const char *username,
+ const char *machine, struct sessionid **session_list)
+{
+ struct session_list sesslist;
+ NTSTATUS status;
+
+ sesslist.mem_ctx = mem_ctx;
+ sesslist.count = 0;
+ sesslist.filter_user = username;
+ sesslist.filter_machine = machine;
+ sesslist.sessions = NULL;
+
+ status = sessionid_traverse_read(gather_sessioninfo, (void *)&sesslist);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Session traverse failed: %s\n", nt_errstr(status)));
+ TALLOC_FREE(sesslist.sessions);
+ *session_list = NULL;
+ return 0;
+ }
+
+ *session_list = sesslist.sessions;
+ return sesslist.count;
+}
diff --git a/source3/smbd/share_access.c b/source3/smbd/share_access.c
new file mode 100644
index 0000000..4592814
--- /dev/null
+++ b/source3/smbd/share_access.c
@@ -0,0 +1,291 @@
+/*
+ Unix SMB/CIFS implementation.
+ Check access based on valid users, read list and friends
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "source3/lib/substitute.h"
+
+/*
+ * No prefix means direct username
+ * @name means netgroup first, then unix group
+ * &name means netgroup
+ * +name means unix group
+ * + and & may be combined
+ */
+
+static bool do_group_checks(const char **name, const char **pattern)
+{
+ if ((*name)[0] == '@') {
+ *pattern = "&+";
+ *name += 1;
+ return True;
+ }
+
+ if (((*name)[0] == '+') && ((*name)[1] == '&')) {
+ *pattern = "+&";
+ *name += 2;
+ return True;
+ }
+
+ if ((*name)[0] == '+') {
+ *pattern = "+";
+ *name += 1;
+ return True;
+ }
+
+ if (((*name)[0] == '&') && ((*name)[1] == '+')) {
+ *pattern = "&+";
+ *name += 2;
+ return True;
+ }
+
+ if ((*name)[0] == '&') {
+ *pattern = "&";
+ *name += 1;
+ return True;
+ }
+
+ return False;
+}
+
+static bool token_contains_name(TALLOC_CTX *mem_ctx,
+ const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct security_token *token,
+ const char *name)
+{
+ const char *prefix;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+
+ if (username != NULL) {
+ size_t domain_len = domain != NULL ? strlen(domain) : 0;
+
+ /* Check if username starts with domain name */
+ if (domain_len > 0) {
+ const char *sep = lp_winbind_separator();
+ int cmp = strncasecmp_m(username, domain, domain_len);
+ if (cmp == 0 && sep[0] == username[domain_len]) {
+ /* Move after the winbind separator */
+ domain_len += 1;
+ } else {
+ domain_len = 0;
+ }
+ }
+ name = talloc_sub_basic(mem_ctx,
+ username + domain_len,
+ domain,
+ name);
+ }
+ if (sharename != NULL) {
+ name = talloc_string_sub(mem_ctx, name, "%S", sharename);
+ }
+
+ if (name == NULL) {
+ /* This is too security sensitive, better panic than return a
+ * result that might be interpreted in a wrong way. */
+ smb_panic("substitutions failed");
+ }
+
+ if ( string_to_sid( &sid, name ) ) {
+ DEBUG(5,("token_contains_name: Checking for SID [%s] in token\n", name));
+ return nt_token_check_sid( &sid, token );
+ }
+
+ if (!do_group_checks(&name, &prefix)) {
+ if (!lookup_name_smbconf(mem_ctx, name, LOOKUP_NAME_ALL,
+ NULL, NULL, &sid, &type)) {
+ DEBUG(5, ("lookup_name %s failed\n", name));
+ return False;
+ }
+ if (type != SID_NAME_USER) {
+ DEBUG(5, ("%s is a %s, expected a user\n",
+ name, sid_type_lookup(type)));
+ return False;
+ }
+ return nt_token_check_sid(&sid, token);
+ }
+
+ for (/* initialized above */ ; *prefix != '\0'; prefix++) {
+ if (*prefix == '+') {
+ if (!lookup_name_smbconf(mem_ctx, name,
+ LOOKUP_NAME_ALL|LOOKUP_NAME_GROUP,
+ NULL, NULL, &sid, &type)) {
+ DEBUG(5, ("lookup_name %s failed\n", name));
+ return False;
+ }
+ if ((type != SID_NAME_DOM_GRP) &&
+ (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ DEBUG(5, ("%s is a %s, expected a group\n",
+ name, sid_type_lookup(type)));
+ return False;
+ }
+ if (nt_token_check_sid(&sid, token)) {
+ return True;
+ }
+ continue;
+ }
+ if (*prefix == '&') {
+ if (username) {
+ if (user_in_netgroup(mem_ctx, username, name)) {
+ return True;
+ }
+ }
+ continue;
+ }
+ smb_panic("got invalid prefix from do_groups_check");
+ }
+ return False;
+}
+
+/*
+ * Check whether a user is contained in the list provided.
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool token_contains_name_in_list(const char *username,
+ const char *domain,
+ const char *sharename,
+ const struct security_token *token,
+ const char **list)
+{
+ if (list == NULL) {
+ return False;
+ }
+ while (*list != NULL) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ret;
+
+ ret = token_contains_name(frame, username, domain, sharename,
+ token, *list);
+ TALLOC_FREE(frame);
+ if (ret) {
+ return true;
+ }
+ list += 1;
+ }
+ return False;
+}
+
+/*
+ * Check whether the user described by "token" has access to share snum.
+ *
+ * This looks at "invalid users" and "valid users".
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool user_ok_token(const char *username, const char *domain,
+ const struct security_token *token, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (lp_invalid_users(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_invalid_users(snum))) {
+ DEBUG(10, ("User %s in 'invalid users'\n", username));
+ return False;
+ }
+ }
+
+ if (lp_valid_users(snum) != NULL) {
+ if (!token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_valid_users(snum))) {
+ DEBUG(10, ("User %s not in 'valid users'\n",
+ username));
+ return False;
+ }
+ }
+
+ DEBUG(10, ("user_ok_token: share %s is ok for unix user %s\n",
+ lp_servicename(talloc_tos(), lp_sub, snum), username));
+
+ return True;
+}
+
+/*
+ * Check whether the user described by "token" is restricted to read-only
+ * access on share snum.
+ *
+ * This looks at "read list", "write list" and "read only".
+ *
+ * Please note that the user name and share names passed in here mainly for
+ * the substitution routines that expand the parameter values, the decision
+ * whether a user is in the list is done after a lookup_name on the expanded
+ * parameter value, solely based on comparing the SIDs in token.
+ *
+ * The other use is the netgroup check when using @group or &group.
+ */
+
+bool is_share_read_only_for_token(const char *username,
+ const char *domain,
+ const struct security_token *token,
+ connection_struct *conn)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum = SNUM(conn);
+ bool result = conn->read_only;
+
+ if (lp_read_list(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_read_list(snum))) {
+ result = True;
+ }
+ }
+
+ if (lp_write_list(snum) != NULL) {
+ if (token_contains_name_in_list(username, domain,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ token,
+ lp_write_list(snum))) {
+ result = False;
+ }
+ }
+
+ DEBUG(10,("is_share_read_only_for_user: share %s is %s for unix user "
+ "%s\n", lp_servicename(talloc_tos(), lp_sub, snum),
+ result ? "read-only" : "read-write", username));
+
+ return result;
+}
diff --git a/source3/smbd/smb1_aio.c b/source3/smbd/smb1_aio.c
new file mode 100644
index 0000000..d54a372
--- /dev/null
+++ b/source3/smbd/smb1_aio.c
@@ -0,0 +1,409 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ async_io read handling using POSIX async io.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../lib/util/tevent_unix.h"
+
+static void aio_pread_smb1_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMBreadX call.
+*****************************************************************************/
+
+NTSTATUS schedule_aio_read_and_X(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp, off_t startpos,
+ size_t smb_maxcnt)
+{
+ struct aio_extra *aio_ex;
+ size_t bufsize;
+ size_t min_aio_read_size = lp_aio_read_size(SNUM(conn));
+ struct tevent_req *req;
+ bool ok;
+
+ ok = vfs_valid_pread_range(startpos, smb_maxcnt);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_read_size || (smb_maxcnt < min_aio_read_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a read for aio request. */
+ DEBUG(10,("schedule_aio_read_and_X: read size (%u) too small "
+ "for minimum aio_read of %u\n",
+ (unsigned int)smb_maxcnt,
+ (unsigned int)min_aio_read_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ /* Only do this on non-chained and non-chaining reads */
+ if (req_is_in_chain(smbreq)) {
+ return NT_STATUS_RETRY;
+ }
+
+ /* The following is safe from integer wrap as we've already checked
+ smb_maxcnt is 128k or less. Wct is 12 for read replies */
+
+ bufsize = smb_size + 12 * 2 + smb_maxcnt + 1 /* padding byte */;
+
+ if ((aio_ex = create_aio_extra(NULL, fsp, bufsize)) == NULL) {
+ DEBUG(10,("schedule_aio_read_and_X: malloc fail.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ construct_smb1_reply_common_req(smbreq, (char *)aio_ex->outbuf.data);
+ srv_smb1_set_message((char *)aio_ex->outbuf.data, 12, 0, True);
+ SCVAL(aio_ex->outbuf.data,smb_vwv0,0xFF); /* Never a chained reply. */
+ SCVAL(smb_buf(aio_ex->outbuf.data), 0, 0); /* padding byte */
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)smbreq->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)smb_maxcnt,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = smb_maxcnt;
+ aio_ex->offset = startpos;
+
+ req = SMB_VFS_PREAD_SEND(aio_ex, fsp->conn->sconn->ev_ctx,
+ fsp,
+ smb_buf(aio_ex->outbuf.data) + 1 /* pad */,
+ smb_maxcnt, startpos);
+ if (req == NULL) {
+ DEBUG(0,("schedule_aio_read_and_X: aio_read failed. "
+ "Error %s\n", strerror(errno) ));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pread_smb1_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ aio_ex->smbreq = talloc_move(aio_ex, &smbreq);
+
+ DEBUG(10,("schedule_aio_read_and_X: scheduled aio_read for file %s, "
+ "offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp), (double)startpos, (unsigned int)smb_maxcnt,
+ (unsigned int)aio_ex->smbreq->mid ));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pread_smb1_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ files_struct *fsp = aio_ex->fsp;
+ size_t outsize;
+ char *outbuf = (char *)aio_ex->outbuf.data;
+ ssize_t nread;
+ struct vfs_aio_state vfs_aio_state;
+
+ nread = SMB_VFS_PREAD_RECV(req, &vfs_aio_state);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pread_recv returned %d, err = %s\n", (int)nread,
+ (nread == -1) ? strerror(vfs_aio_state.error) : "no error"));
+
+ if (fsp == NULL) {
+ DEBUG( 3, ("aio_pread_smb1_done: file closed whilst "
+ "aio outstanding (mid[%llu]).\n",
+ (unsigned long long)aio_ex->smbreq->mid));
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+
+ if (nread < 0) {
+ DEBUG( 3, ("handle_aio_read_complete: file %s nread == %d. "
+ "Error = %s\n", fsp_str_dbg(fsp), (int)nread,
+ strerror(vfs_aio_state.error)));
+
+ ERROR_NT(map_nt_error_from_unix(vfs_aio_state.error));
+ outsize = srv_smb1_set_message(outbuf,0,0,true);
+ } else {
+ outsize = setup_readX_header(outbuf, nread);
+
+ fh_set_pos(aio_ex->fsp->fh, aio_ex->offset + nread);
+ fh_set_position_information(aio_ex->fsp->fh,
+ fh_get_pos(aio_ex->fsp->fh));
+
+ DEBUG( 3, ("handle_aio_read_complete file %s max=%d "
+ "nread=%d\n", fsp_str_dbg(fsp),
+ (int)aio_ex->nbyte, (int)nread ) );
+
+ }
+
+ if (outsize <= 4) {
+ DBG_INFO("Invalid outsize (%zu)\n", outsize);
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+ outsize -= 4;
+ _smb_setlen_large(outbuf, outsize);
+
+ show_msg(outbuf);
+ if (!smb1_srv_send(aio_ex->smbreq->xconn,
+ outbuf,
+ true,
+ aio_ex->smbreq->seqnum + 1,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("handle_aio_read_complete: smb1_srv_send "
+ "failed.");
+ }
+
+ DEBUG(10, ("handle_aio_read_complete: scheduled aio_read completed "
+ "for file %s, offset %.0f, len = %u\n",
+ fsp_str_dbg(fsp), (double)aio_ex->offset,
+ (unsigned int)nread));
+
+ TALLOC_FREE(aio_ex);
+}
+
+static void aio_pwrite_smb1_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMBwriteX call.
+*****************************************************************************/
+
+NTSTATUS schedule_aio_write_and_X(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp, const char *data,
+ off_t startpos,
+ size_t numtowrite)
+{
+ struct aio_extra *aio_ex;
+ size_t bufsize;
+ size_t min_aio_write_size = lp_aio_write_size(SNUM(conn));
+ struct tevent_req *req;
+
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_write_size || (numtowrite < min_aio_write_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a write for aio request. */
+ DEBUG(10,("schedule_aio_write_and_X: write size (%u) too "
+ "small for minimum aio_write of %u\n",
+ (unsigned int)numtowrite,
+ (unsigned int)min_aio_write_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ /* Only do this on non-chained and non-chaining writes */
+ if (req_is_in_chain(smbreq)) {
+ return NT_STATUS_RETRY;
+ }
+
+ bufsize = smb_size + 6*2;
+
+ if (!(aio_ex = create_aio_extra(NULL, fsp, bufsize))) {
+ DEBUG(0,("schedule_aio_write_and_X: malloc fail.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ aio_ex->write_through = BITSETW(smbreq->vwv+7,0);
+
+ construct_smb1_reply_common_req(smbreq, (char *)aio_ex->outbuf.data);
+ srv_smb1_set_message((char *)aio_ex->outbuf.data, 6, 0, True);
+ SCVAL(aio_ex->outbuf.data,smb_vwv0,0xFF); /* Never a chained reply. */
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)smbreq->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = numtowrite;
+ aio_ex->offset = startpos;
+
+ req = pwrite_fsync_send(aio_ex, fsp->conn->sconn->ev_ctx, fsp,
+ data, numtowrite, startpos,
+ aio_ex->write_through);
+ if (req == NULL) {
+ DEBUG(3,("schedule_aio_wrote_and_X: aio_write failed. "
+ "Error %s\n", strerror(errno) ));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pwrite_smb1_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ aio_ex->smbreq = talloc_move(aio_ex, &smbreq);
+
+ /* This should actually be improved to span the write. */
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WRITE);
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WRITE);
+
+ if (!aio_ex->write_through && !lp_sync_always(SNUM(fsp->conn))
+ && fsp->fsp_flags.aio_write_behind)
+ {
+ /* Lie to the client and immediately claim we finished the
+ * write. */
+ SSVAL(aio_ex->outbuf.data,smb_vwv2,numtowrite);
+ SSVAL(aio_ex->outbuf.data,smb_vwv4,(numtowrite>>16)&1);
+ show_msg((char *)aio_ex->outbuf.data);
+ if (!smb1_srv_send(aio_ex->smbreq->xconn,
+ (char *)aio_ex->outbuf.data,
+ true,
+ aio_ex->smbreq->seqnum + 1,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("schedule_aio_write_and_X: "
+ "smb1_srv_send failed.");
+ }
+ DEBUG(10,("schedule_aio_write_and_X: scheduled aio_write "
+ "behind for file %s\n", fsp_str_dbg(fsp)));
+ }
+
+ DEBUG(10,("schedule_aio_write_and_X: scheduled aio_write for file "
+ "%s, offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp), (double)startpos, (unsigned int)numtowrite,
+ (unsigned int)aio_ex->smbreq->mid));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pwrite_smb1_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ files_struct *fsp = aio_ex->fsp;
+ char *outbuf = (char *)aio_ex->outbuf.data;
+ ssize_t numtowrite = aio_ex->nbyte;
+ ssize_t nwritten;
+ int err;
+
+ nwritten = pwrite_fsync_recv(req, &err);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pwrite_recv returned %d, err = %s\n", (int)nwritten,
+ (nwritten == -1) ? strerror(err) : "no error"));
+
+ if (fsp == NULL) {
+ DEBUG( 3, ("aio_pwrite_smb1_done: file closed whilst "
+ "aio outstanding (mid[%llu]).\n",
+ (unsigned long long)aio_ex->smbreq->mid));
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+
+ mark_file_modified(fsp);
+
+ if (fsp->fsp_flags.aio_write_behind) {
+
+ if (nwritten != numtowrite) {
+ if (nwritten == -1) {
+ DEBUG(5,("handle_aio_write_complete: "
+ "aio_write_behind failed ! File %s "
+ "is corrupt ! Error %s\n",
+ fsp_str_dbg(fsp), strerror(err)));
+ } else {
+ DEBUG(0,("handle_aio_write_complete: "
+ "aio_write_behind failed ! File %s "
+ "is corrupt ! Wanted %u bytes but "
+ "only wrote %d\n", fsp_str_dbg(fsp),
+ (unsigned int)numtowrite,
+ (int)nwritten ));
+ }
+ } else {
+ DEBUG(10,("handle_aio_write_complete: "
+ "aio_write_behind completed for file %s\n",
+ fsp_str_dbg(fsp)));
+ }
+ /* TODO: should no return success in case of an error !!! */
+ TALLOC_FREE(aio_ex);
+ return;
+ }
+
+ /* We don't need outsize or set_message here as we've already set the
+ fixed size length when we set up the aio call. */
+
+ if (nwritten == -1) {
+ DEBUG(3, ("handle_aio_write: file %s wanted %u bytes. "
+ "nwritten == %d. Error = %s\n",
+ fsp_str_dbg(fsp), (unsigned int)numtowrite,
+ (int)nwritten, strerror(err)));
+
+ ERROR_NT(map_nt_error_from_unix(err));
+ srv_smb1_set_message(outbuf,0,0,true);
+ } else {
+ SSVAL(outbuf,smb_vwv2,nwritten);
+ SSVAL(outbuf,smb_vwv4,(nwritten>>16)&1);
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(outbuf,smb_rcls,ERRHRD);
+ SSVAL(outbuf,smb_err,ERRdiskfull);
+ }
+
+ DEBUG(3,("handle_aio_write: %s, num=%d wrote=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+ fh_set_pos(aio_ex->fsp->fh, aio_ex->offset + nwritten);
+ }
+
+ show_msg(outbuf);
+ if (!smb1_srv_send(aio_ex->smbreq->xconn,
+ outbuf,
+ true,
+ aio_ex->smbreq->seqnum + 1,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("handle_aio_write_complete: "
+ "smb1_srv_send failed.");
+ }
+
+ DEBUG(10, ("handle_aio_write_complete: scheduled aio_write completed "
+ "for file %s, offset %.0f, requested %u, written = %u\n",
+ fsp_str_dbg(fsp), (double)aio_ex->offset,
+ (unsigned int)numtowrite, (unsigned int)nwritten));
+
+ TALLOC_FREE(aio_ex);
+}
diff --git a/source3/smbd/smb1_aio.h b/source3/smbd/smb1_aio.h
new file mode 100644
index 0000000..d13bbb9
--- /dev/null
+++ b/source3/smbd/smb1_aio.h
@@ -0,0 +1,29 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ async_io read handling using POSIX async io.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+NTSTATUS schedule_aio_read_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, off_t startpos,
+ size_t smb_maxcnt);
+NTSTATUS schedule_aio_write_and_X(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp, const char *data,
+ off_t startpos,
+ size_t numtowrite);
diff --git a/source3/smbd/smb1_ipc.c b/source3/smbd/smb1_ipc.c
new file mode 100644
index 0000000..716b67b
--- /dev/null
+++ b/source3/smbd/smb1_ipc.c
@@ -0,0 +1,949 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbprofile.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "source3/lib/substitute.h"
+
+#define NERR_notsupported 50
+
+static void api_no_reply(connection_struct *conn, struct smb_request *req);
+
+/*******************************************************************
+ copies parameters and data, as needed, into the smb buffer
+
+ *both* the data and params sections should be aligned. this
+ is fudged in the rpc pipes by
+ at present, only the data section is. this may be a possible
+ cause of some of the ipc problems being experienced. lkcl26dec97
+
+ ******************************************************************/
+
+static void copy_trans_params_and_data(char *outbuf, int align,
+ char *rparam, int param_offset, int param_len,
+ char *rdata, int data_offset, int data_len)
+{
+ char *copy_into = smb_buf(outbuf);
+
+ if(param_len < 0)
+ param_len = 0;
+
+ if(data_len < 0)
+ data_len = 0;
+
+ DEBUG(5,("copy_trans_params_and_data: params[%d..%d] data[%d..%d] (align %d)\n",
+ param_offset, param_offset + param_len,
+ data_offset , data_offset + data_len,
+ align));
+
+ *copy_into = '\0';
+
+ copy_into += 1;
+
+ if (param_len)
+ memcpy(copy_into, &rparam[param_offset], param_len);
+
+ copy_into += param_len;
+ if (align) {
+ memset(copy_into, '\0', align);
+ }
+
+ copy_into += align;
+
+ if (data_len )
+ memcpy(copy_into, &rdata[data_offset], data_len);
+}
+
+/****************************************************************************
+ Send a trans reply.
+ ****************************************************************************/
+
+void send_trans_reply(connection_struct *conn,
+ struct smb_request *req,
+ char *rparam, int rparam_len,
+ char *rdata, int rdata_len,
+ bool buffer_too_large)
+{
+ int this_ldata,this_lparam;
+ int tot_data_sent = 0;
+ int tot_param_sent = 0;
+ int align;
+
+ int ldata = rdata ? rdata_len : 0;
+ int lparam = rparam ? rparam_len : 0;
+ struct smbXsrv_connection *xconn = req->xconn;
+ int max_send = xconn->smb1.sessions.max_send;
+ /* HACK: make sure we send at least 128 byte in one go */
+ int hdr_overhead = SMB_BUFFER_SIZE_MIN - 128;
+
+ if (buffer_too_large)
+ DEBUG(5,("send_trans_reply: buffer %d too large\n", ldata ));
+
+ this_lparam = MIN(lparam,max_send - hdr_overhead);
+ this_ldata = MIN(ldata,max_send - (hdr_overhead+this_lparam));
+
+ align = ((this_lparam)%4);
+
+ reply_smb1_outbuf(req, 10, 1+align+this_ldata+this_lparam);
+
+ /*
+ * We might have SMBtranss in req which was transferred to the outbuf,
+ * fix that.
+ */
+ SCVAL(req->outbuf, smb_com, SMBtrans);
+
+ copy_trans_params_and_data((char *)req->outbuf, align,
+ rparam, tot_param_sent, this_lparam,
+ rdata, tot_data_sent, this_ldata);
+
+ SSVAL(req->outbuf,smb_vwv0,lparam);
+ SSVAL(req->outbuf,smb_vwv1,ldata);
+ SSVAL(req->outbuf,smb_vwv3,this_lparam);
+ SSVAL(req->outbuf,smb_vwv4,
+ smb_offset(smb_buf(req->outbuf)+1, req->outbuf));
+ SSVAL(req->outbuf,smb_vwv5,0);
+ SSVAL(req->outbuf,smb_vwv6,this_ldata);
+ SSVAL(req->outbuf,smb_vwv7,
+ smb_offset(smb_buf(req->outbuf)+1+this_lparam+align,
+ req->outbuf));
+ SSVAL(req->outbuf,smb_vwv8,0);
+ SSVAL(req->outbuf,smb_vwv9,0);
+
+ if (buffer_too_large) {
+ error_packet_set((char *)req->outbuf, ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW, __LINE__, __FILE__);
+ }
+
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans_reply: smb1_srv_send failed.");
+ }
+
+ TALLOC_FREE(req->outbuf);
+
+ tot_data_sent = this_ldata;
+ tot_param_sent = this_lparam;
+
+ while (tot_data_sent < ldata || tot_param_sent < lparam)
+ {
+ this_lparam = MIN(lparam-tot_param_sent,
+ max_send - hdr_overhead);
+ this_ldata = MIN(ldata -tot_data_sent,
+ max_send - (hdr_overhead+this_lparam));
+
+ if(this_lparam < 0)
+ this_lparam = 0;
+
+ if(this_ldata < 0)
+ this_ldata = 0;
+
+ align = (this_lparam%4);
+
+ reply_smb1_outbuf(req, 10, 1+align+this_ldata+this_lparam);
+
+ /*
+ * We might have SMBtranss in req which was transferred to the
+ * outbuf, fix that.
+ */
+ SCVAL(req->outbuf, smb_com, SMBtrans);
+
+ copy_trans_params_and_data((char *)req->outbuf, align,
+ rparam, tot_param_sent, this_lparam,
+ rdata, tot_data_sent, this_ldata);
+
+ SSVAL(req->outbuf,smb_vwv0,lparam);
+ SSVAL(req->outbuf,smb_vwv1,ldata);
+
+ SSVAL(req->outbuf,smb_vwv3,this_lparam);
+ SSVAL(req->outbuf,smb_vwv4,
+ smb_offset(smb_buf(req->outbuf)+1,req->outbuf));
+ SSVAL(req->outbuf,smb_vwv5,tot_param_sent);
+ SSVAL(req->outbuf,smb_vwv6,this_ldata);
+ SSVAL(req->outbuf,smb_vwv7,
+ smb_offset(smb_buf(req->outbuf)+1+this_lparam+align,
+ req->outbuf));
+ SSVAL(req->outbuf,smb_vwv8,tot_data_sent);
+ SSVAL(req->outbuf,smb_vwv9,0);
+
+ if (buffer_too_large) {
+ error_packet_set((char *)req->outbuf,
+ ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW,
+ __LINE__, __FILE__);
+ }
+
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans_reply: smb1_srv_send "
+ "failed.");
+ }
+
+ tot_data_sent += this_ldata;
+ tot_param_sent += this_lparam;
+ TALLOC_FREE(req->outbuf);
+ }
+}
+
+/****************************************************************************
+ Start the first part of an RPC reply which began with an SMBtrans request.
+****************************************************************************/
+
+struct dcerpc_cmd_state {
+ struct fake_file_handle *handle;
+ uint8_t *data;
+ size_t num_data;
+ size_t max_read;
+};
+
+static void api_dcerpc_cmd_write_done(struct tevent_req *subreq);
+static void api_dcerpc_cmd_read_done(struct tevent_req *subreq);
+
+static void api_dcerpc_cmd(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp, uint8_t *data, size_t length,
+ size_t max_read)
+{
+ struct tevent_req *subreq;
+ struct dcerpc_cmd_state *state;
+ bool busy;
+
+ if (!fsp_is_np(fsp)) {
+ api_no_reply(conn, req);
+ return;
+ }
+
+ /*
+ * Trans requests are only allowed
+ * if no other Trans or Read is active
+ */
+ busy = np_read_in_progress(fsp->fake_file_handle);
+ if (busy) {
+ reply_nterror(req, NT_STATUS_PIPE_BUSY);
+ return;
+ }
+
+ state = talloc(req, struct dcerpc_cmd_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->handle = fsp->fake_file_handle;
+
+ /*
+ * This memdup severely sucks. But doing it properly essentially means
+ * to rewrite lanman.c, something which I don't really want to do now.
+ */
+ state->data = (uint8_t *)talloc_memdup(state, data, length);
+ if (state->data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ state->num_data = length;
+ state->max_read = max_read;
+
+ subreq = np_write_send(state, req->sconn->ev_ctx, state->handle,
+ state->data, length);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, api_dcerpc_cmd_write_done,
+ talloc_move(conn, &req));
+}
+
+static void api_dcerpc_cmd_write_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct dcerpc_cmd_state *state = talloc_get_type_abort(
+ req->async_priv, struct dcerpc_cmd_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+
+ DEBUG(10, ("Could not write to pipe: %s%s%s\n",
+ nt_errstr(old),
+ NT_STATUS_EQUAL(old, status)?"":" => ",
+ NT_STATUS_EQUAL(old, status)?"":nt_errstr(status)));
+ reply_nterror(req, status);
+ goto send;
+ }
+ if (nwritten != state->num_data) {
+ status = NT_STATUS_PIPE_NOT_AVAILABLE;
+ DEBUG(10, ("Could not write to pipe: (%d/%d) => %s\n",
+ (int)state->num_data,
+ (int)nwritten, nt_errstr(status)));
+ reply_nterror(req, status);
+ goto send;
+ }
+
+ state->data = talloc_realloc(state, state->data, uint8_t,
+ state->max_read);
+ if (state->data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto send;
+ }
+
+ subreq = np_read_send(state, req->sconn->ev_ctx,
+ state->handle, state->data, state->max_read);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto send;
+ }
+ tevent_req_set_callback(subreq, api_dcerpc_cmd_read_done, req);
+ return;
+
+ send:
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) || req->encrypted)) {
+ exit_server_cleanly("api_dcerpc_cmd_write_done: "
+ "smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+}
+
+static void api_dcerpc_cmd_read_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct dcerpc_cmd_state *state = talloc_get_type_abort(
+ req->async_priv, struct dcerpc_cmd_state);
+ NTSTATUS status;
+ ssize_t nread;
+ bool is_data_outstanding;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+
+ DEBUG(10, ("Could not read from to pipe: %s%s%s\n",
+ nt_errstr(old),
+ NT_STATUS_EQUAL(old, status)?"":" => ",
+ NT_STATUS_EQUAL(old, status)?"":nt_errstr(status)));
+ reply_nterror(req, status);
+
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) ||
+ req->encrypted)) {
+ exit_server_cleanly("api_dcerpc_cmd_read_done: "
+ "smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ return;
+ }
+
+ send_trans_reply(req->conn, req, NULL, 0, (char *)state->data, nread,
+ is_data_outstanding);
+ TALLOC_FREE(req);
+}
+
+/****************************************************************************
+ WaitNamedPipeHandleState
+****************************************************************************/
+
+static void api_WNPHS(connection_struct *conn, struct smb_request *req,
+ struct files_struct *fsp, char *param, int param_len)
+{
+ if (!param || param_len < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ DEBUG(4,("WaitNamedPipeHandleState priority %x\n",
+ (int)SVAL(param,0)));
+
+ send_trans_reply(conn, req, NULL, 0, NULL, 0, False);
+}
+
+
+/****************************************************************************
+ SetNamedPipeHandleState
+****************************************************************************/
+
+static void api_SNPHS(connection_struct *conn, struct smb_request *req,
+ struct files_struct *fsp, char *param, int param_len)
+{
+ if (!param || param_len < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ DEBUG(4,("SetNamedPipeHandleState to code %x\n", (int)SVAL(param,0)));
+
+ send_trans_reply(conn, req, NULL, 0, NULL, 0, False);
+}
+
+
+/****************************************************************************
+ When no reply is generated, indicate unsupported.
+ ****************************************************************************/
+
+static void api_no_reply(connection_struct *conn, struct smb_request *req)
+{
+ char rparam[4];
+
+ /* unsupported */
+ SSVAL(rparam,0,NERR_notsupported);
+ SSVAL(rparam,2,0); /* converter word */
+
+ DEBUG(3,("Unsupported API fd command\n"));
+
+ /* now send the reply */
+ send_trans_reply(conn, req, rparam, 4, NULL, 0, False);
+
+ return;
+}
+
+/****************************************************************************
+ Handle remote api calls delivered to a named pipe already opened.
+ ****************************************************************************/
+
+static void api_fd_reply(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ uint16_t *setup, uint8_t *data, char *params,
+ int suwcnt, int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt)
+{
+ struct files_struct *fsp;
+ int pnum;
+ int subcommand;
+
+ DEBUG(5,("api_fd_reply\n"));
+
+ /* First find out the name of this file. */
+ if (suwcnt != 2) {
+ DEBUG(0,("Unexpected named pipe transaction.\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* Get the file handle and hence the file name. */
+ /*
+ * NB. The setup array has already been transformed
+ * via SVAL and so is in host byte order.
+ */
+ pnum = ((int)setup[1]) & 0xFFFF;
+ subcommand = ((int)setup[0]) & 0xFFFF;
+
+ fsp = file_fsp(req, pnum);
+
+ if (!fsp_is_np(fsp)) {
+ if (subcommand == TRANSACT_WAITNAMEDPIPEHANDLESTATE) {
+ /* Win9x does this call with a unicode pipe name, not a pnum. */
+ /* Just return success for now... */
+ DEBUG(3,("Got TRANSACT_WAITNAMEDPIPEHANDLESTATE on text pipe name\n"));
+ send_trans_reply(conn, req, NULL, 0, NULL, 0, False);
+ return;
+ }
+
+ DEBUG(1,("api_fd_reply: INVALID PIPE HANDLE: %x\n", pnum));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (vuid != fsp->vuid) {
+ DEBUG(1, ("Got pipe request (pnum %x) using invalid VUID %llu, "
+ "expected %llu\n", pnum, (unsigned long long)vuid,
+ (unsigned long long)fsp->vuid));
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ DEBUG(3,("Got API command 0x%x on pipe \"%s\" (pnum %x)\n",
+ subcommand, fsp_str_dbg(fsp), pnum));
+
+ DEBUG(10, ("api_fd_reply: p:%p max_trans_reply: %d\n", fsp, mdrcnt));
+
+ switch (subcommand) {
+ case TRANSACT_DCERPCCMD: {
+ /* dce/rpc command */
+ api_dcerpc_cmd(conn, req, fsp, (uint8_t *)data, tdscnt,
+ mdrcnt);
+ break;
+ }
+ case TRANSACT_WAITNAMEDPIPEHANDLESTATE:
+ /* Wait Named Pipe Handle state */
+ api_WNPHS(conn, req, fsp, params, tpscnt);
+ break;
+ case TRANSACT_SETNAMEDPIPEHANDLESTATE:
+ /* Set Named Pipe Handle state */
+ api_SNPHS(conn, req, fsp, params, tpscnt);
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+}
+
+/****************************************************************************
+ Handle named pipe commands.
+****************************************************************************/
+
+static void named_pipe(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ const char *name, uint16_t *setup,
+ char *data, char *params,
+ int suwcnt, int tdscnt,int tpscnt,
+ int msrcnt, int mdrcnt, int mprcnt)
+{
+ DEBUG(3,("named pipe command on <%s> name\n", name));
+
+ if (strequal(name,"LANMAN")) {
+ api_reply(conn, vuid, req,
+ data, params,
+ tdscnt, tpscnt,
+ mdrcnt, mprcnt);
+ return;
+ }
+
+ if (strequal(name,"WKSSVC") ||
+ strequal(name,"SRVSVC") ||
+ strequal(name,"WINREG") ||
+ strequal(name,"SAMR") ||
+ strequal(name,"LSARPC")) {
+
+ DEBUG(4,("named pipe command from Win95 (wow!)\n"));
+
+ api_fd_reply(conn, vuid, req,
+ setup, (uint8_t *)data, params,
+ suwcnt, tdscnt, tpscnt,
+ mdrcnt, mprcnt);
+ return;
+ }
+
+ if (strlen(name) < 1) {
+ api_fd_reply(conn, vuid, req,
+ setup, (uint8_t *)data,
+ params, suwcnt, tdscnt,
+ tpscnt, mdrcnt, mprcnt);
+ return;
+ }
+
+ if (setup)
+ DEBUG(3,("unknown named pipe: setup 0x%X setup1=%d\n",
+ (int)setup[0],(int)setup[1]));
+
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+}
+
+static void handle_trans(connection_struct *conn, struct smb_request *req,
+ struct trans_state *state)
+{
+ char *local_machine_name;
+ int name_offset = 0;
+
+ DEBUG(3,("trans <%s> data=%u params=%u setup=%u\n",
+ state->name,(unsigned int)state->total_data,(unsigned int)state->total_param,
+ (unsigned int)state->setup_count));
+
+ /*
+ * WinCE weirdness....
+ */
+
+ local_machine_name = talloc_asprintf(state, "\\%s\\",
+ get_local_machine_name());
+
+ if (local_machine_name == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ if (strnequal(state->name, local_machine_name,
+ strlen(local_machine_name))) {
+ name_offset = strlen(local_machine_name)-1;
+ }
+
+ if (!strnequal(&state->name[name_offset], "\\PIPE",
+ strlen("\\PIPE"))) {
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ name_offset += strlen("\\PIPE");
+
+ /* Win9x weirdness. When talking to a unicode server Win9x
+ only sends \PIPE instead of \PIPE\ */
+
+ if (state->name[name_offset] == '\\')
+ name_offset++;
+
+ DEBUG(5,("calling named_pipe\n"));
+ named_pipe(conn, state->vuid, req,
+ state->name+name_offset,
+ state->setup,state->data,
+ state->param,
+ state->setup_count,state->total_data,
+ state->total_param,
+ state->max_setup_return,
+ state->max_data_return,
+ state->max_param_return);
+
+ if (state->close_on_completion) {
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+
+ tcon = conn->tcon;
+ req->conn = NULL;
+ conn = NULL;
+
+ /*
+ * TODO: cancel all outstanding requests on the tcon
+ */
+ status = smbXsrv_tcon_disconnect(tcon, state->vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("handle_trans: "
+ "smbXsrv_tcon_disconnect() failed: %s\n",
+ nt_errstr(status)));
+ /*
+ * If we hit this case, there is something completely
+ * wrong, so we better disconnect the transport connection.
+ */
+ exit_server(__location__ ": smbXsrv_tcon_disconnect failed");
+ return;
+ }
+
+ TALLOC_FREE(tcon);
+ }
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBtrans.
+ ****************************************************************************/
+
+void reply_trans(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int dsoff;
+ unsigned int dscnt;
+ unsigned int psoff;
+ unsigned int pscnt;
+ struct trans_state *state;
+ NTSTATUS result;
+
+ START_PROFILE(SMBtrans);
+
+ if (req->wct < 14) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ dsoff = SVAL(req->vwv+12, 0);
+ dscnt = SVAL(req->vwv+11, 0);
+ psoff = SVAL(req->vwv+10, 0);
+ pscnt = SVAL(req->vwv+9, 0);
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid trans request: %s\n",
+ nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ if ((state = talloc_zero(conn, struct trans_state)) == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ state->cmd = SMBtrans;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->setup_count = CVAL(req->vwv+13, 0);
+ state->setup = NULL;
+ state->total_param = SVAL(req->vwv+0, 0);
+ state->param = NULL;
+ state->total_data = SVAL(req->vwv+1, 0);
+ state->data = NULL;
+ state->max_param_return = SVAL(req->vwv+2, 0);
+ state->max_data_return = SVAL(req->vwv+3, 0);
+ state->max_setup_return = CVAL(req->vwv+4, 0);
+ state->close_on_completion = BITSETW(req->vwv+5, 0);
+ state->one_way = BITSETW(req->vwv+5, 1);
+
+ srvstr_pull_req_talloc(state, req, &state->name, req->buf,
+ STR_TERMINATE);
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param) ||
+ !state->name)
+ goto bad_param;
+
+ if (state->total_data) {
+
+ if (smb_buffer_oob(state->total_data, 0, dscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), dsoff, dscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. Out of paranoia, 100 bytes too many. */
+ state->data = (char *)SMB_MALLOC(state->total_data+100);
+ if (state->data == NULL) {
+ DEBUG(0,("reply_trans: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+ /* null-terminate the slack space */
+ memset(&state->data[state->total_data], 0, 100);
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+
+ if (smb_buffer_oob(state->total_param, 0, pscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), psoff, pscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. Out of paranoia, 100 bytes too many */
+ state->param = (char *)SMB_MALLOC(state->total_param+100);
+ if (state->param == NULL) {
+ DEBUG(0,("reply_trans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+ /* null-terminate the slack space */
+ memset(&state->param[state->total_param], 0, 100);
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if (state->setup_count) {
+ unsigned int i;
+
+ /*
+ * No overflow possible here, state->setup_count is an
+ * unsigned int, being filled by a single byte from
+ * CVAL(req->vwv+13, 0) above. The cast in the comparison
+ * below is not necessary, it's here to clarify things. The
+ * validity of req->vwv and req->wct has been checked in
+ * init_smb1_request already.
+ */
+ if (state->setup_count + 14 > (unsigned int)req->wct) {
+ goto bad_param;
+ }
+
+ if((state->setup = talloc_array(
+ state, uint16_t, state->setup_count)) == NULL) {
+ DEBUG(0,("reply_trans: setup malloc fail for %u "
+ "bytes !\n", (unsigned int)
+ (state->setup_count * sizeof(uint16_t))));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ for (i=0;i<state->setup_count;i++) {
+ state->setup[i] = SVAL(req->vwv + 14 + i, 0);
+ }
+ }
+
+ state->received_param = pscnt;
+
+ if ((state->received_param != state->total_param) ||
+ (state->received_data != state->total_data)) {
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_smb1_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBtrans);
+ return;
+ }
+
+ talloc_steal(talloc_tos(), state);
+
+ handle_trans(conn, req, state);
+
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtrans);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_trans: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+}
+
+/****************************************************************************
+ Reply to a secondary SMBtrans.
+ ****************************************************************************/
+
+void reply_transs(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+
+ START_PROFILE(SMBtranss);
+
+ show_msg((const char *)req->inbuf);
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBtrans)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ /* Revise total_params and total_data in case they have changed
+ * downwards */
+
+ if (SVAL(req->vwv+0, 0) < state->total_param)
+ state->total_param = SVAL(req->vwv+0, 0);
+ if (SVAL(req->vwv+1, 0) < state->total_data)
+ state->total_data = SVAL(req->vwv+1, 0);
+
+ pcnt = SVAL(req->vwv+2, 0);
+ poff = SVAL(req->vwv+3, 0);
+ pdisp = SVAL(req->vwv+4, 0);
+
+ dcnt = SVAL(req->vwv+5, 0);
+ doff = SVAL(req->vwv+6, 0);
+ ddisp = SVAL(req->vwv+7, 0);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (smb_buffer_oob(state->total_param, pdisp, pcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), poff, pcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->param+pdisp,smb_base(req->inbuf)+poff,pcnt);
+ }
+
+ if (dcnt) {
+ if (smb_buffer_oob(state->total_data, ddisp, dcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), doff, dcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBtranss);
+ return;
+ }
+
+ talloc_steal(talloc_tos(), state);
+
+ handle_trans(conn, req, state);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtranss);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_transs: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss);
+ return;
+}
diff --git a/source3/smbd/smb1_ipc.h b/source3/smbd/smb1_ipc.h
new file mode 100644
index 0000000..5866f21
--- /dev/null
+++ b/source3/smbd/smb1_ipc.h
@@ -0,0 +1,33 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+void send_trans_reply(connection_struct *conn,
+ struct smb_request *req,
+ char *rparam, int rparam_len,
+ char *rdata, int rdata_len,
+ bool buffer_too_large);
+void reply_trans(struct smb_request *req);
+void reply_transs(struct smb_request *req);
diff --git a/source3/smbd/smb1_lanman.c b/source3/smbd/smb1_lanman.c
new file mode 100644
index 0000000..9f703eb
--- /dev/null
+++ b/source3/smbd/smb1_lanman.c
@@ -0,0 +1,5920 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "../librpc/gen_ndr/rap.h"
+#include "../lib/util/binsearch.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "printing.h"
+#include "passdb/machine_sid.h"
+#include "auth.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+
+#ifdef CHECK_TYPES
+#undef CHECK_TYPES
+#endif
+#define CHECK_TYPES 0
+
+#define NERR_Success 0
+#define NERR_badpass 86
+#define NERR_notsupported 50
+
+#define NERR_BASE (2100)
+#define NERR_BufTooSmall (NERR_BASE+23)
+#define NERR_JobNotFound (NERR_BASE+51)
+#define NERR_DestNotFound (NERR_BASE+52)
+
+#define ACCESS_READ 0x01
+#define ACCESS_WRITE 0x02
+#define ACCESS_CREATE 0x04
+
+#define SHPWLEN 8 /* share password length */
+
+/* Limit size of ipc replies */
+
+static char *smb_realloc_limit(void *ptr, size_t size)
+{
+ char *val;
+
+ size = MAX((size),4*1024);
+ val = (char *)SMB_REALLOC(ptr,size);
+ if (val) {
+ memset(val,'\0',size);
+ }
+ return val;
+}
+
+static bool api_Unsupported(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len);
+
+static bool api_TooSmall(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid, char *param, char *data,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len);
+
+
+static int CopyExpanded(connection_struct *conn,
+ int snum, char **dst, char *src, int *p_space_remaining)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *buf = NULL;
+ int l;
+
+ if (!src || !dst || !p_space_remaining || !(*dst) ||
+ *p_space_remaining <= 0) {
+ return 0;
+ }
+
+ buf = talloc_strdup(ctx, src);
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ buf = talloc_string_sub(ctx, buf,"%S", lp_servicename(ctx, lp_sub, snum));
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ buf = talloc_sub_full(ctx,
+ lp_servicename(ctx, lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ buf);
+ if (!buf) {
+ *p_space_remaining = 0;
+ return 0;
+ }
+ l = push_ascii(*dst,buf,*p_space_remaining, STR_TERMINATE);
+ if (l == 0) {
+ return 0;
+ }
+ (*dst) += l;
+ (*p_space_remaining) -= l;
+ return l;
+}
+
+static int CopyAndAdvance(char **dst, char *src, int *n)
+{
+ int l;
+ if (!src || !dst || !n || !(*dst)) {
+ return 0;
+ }
+ l = push_ascii(*dst,src,*n, STR_TERMINATE);
+ if (l == 0) {
+ return 0;
+ }
+ (*dst) += l;
+ (*n) -= l;
+ return l;
+}
+
+static int StrlenExpanded(connection_struct *conn, int snum, char *s)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *buf = NULL;
+ if (!s) {
+ return 0;
+ }
+ buf = talloc_strdup(ctx,s);
+ if (!buf) {
+ return 0;
+ }
+ buf = talloc_string_sub(ctx,buf,"%S",lp_servicename(ctx, lp_sub, snum));
+ if (!buf) {
+ return 0;
+ }
+ buf = talloc_sub_full(ctx,
+ lp_servicename(ctx, lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ buf);
+ if (!buf) {
+ return 0;
+ }
+ return strlen(buf) + 1;
+}
+
+/****************************************************************
+ Return an SVAL at a pointer, or failval if beyond the end.
+****************************************************************/
+
+static int get_safe_SVAL(
+ const char *buf_base,
+ size_t buf_len,
+ char *ptr,
+ size_t off,
+ int failval)
+{
+ /*
+ * Note we use off+1 here, not off+2 as SVAL accesses ptr[0]
+ * and ptr[1], NOT ptr[2].
+ */
+ if (!is_offset_safe(buf_base, buf_len, ptr, off+1)) {
+ return failval;
+ }
+ return SVAL(ptr,off);
+}
+
+/****************************************************************
+ Return an IVAL at a pointer, or failval if beyond the end.
+****************************************************************/
+
+static int get_safe_IVAL(
+ const char *buf_base,
+ size_t buf_len,
+ char *ptr,
+ size_t off,
+ int failval)
+{
+ /*
+ * Note we use off+3 here, not off+4 as IVAL accesses
+ * ptr[0] ptr[1] ptr[2] ptr[3] NOT ptr[4].
+ */
+ if (!is_offset_safe(buf_base, buf_len, ptr, off+3)) {
+ return failval;
+ }
+ return IVAL(ptr,off);
+}
+
+/****************************************************************
+ Return a safe pointer into a buffer, or NULL.
+****************************************************************/
+
+static char *get_safe_ptr(
+ const char *buf_base,
+ size_t buf_len,
+ char *ptr,
+ size_t off)
+{
+ return is_offset_safe(buf_base, buf_len, ptr, off) ?
+ ptr + off : NULL;
+}
+
+/*******************************************************************
+ Check a API string for validity when we only need to check the prefix.
+******************************************************************/
+
+static bool prefix_ok(const char *str, const char *prefix)
+{
+ return(strncmp(str,prefix,strlen(prefix)) == 0);
+}
+
+struct pack_desc {
+ const char *format; /* formatstring for structure */
+ const char *subformat; /* subformat for structure */
+ char *base; /* baseaddress of buffer */
+ int buflen; /* remaining size for fixed part; on init: length of base */
+ int subcount; /* count of substructures */
+ char *structbuf; /* pointer into buffer for remaining fixed part */
+ int stringlen; /* remaining size for variable part */
+ char *stringbuf; /* pointer into buffer for remaining variable part */
+ int neededlen; /* total needed size */
+ int usedlen; /* total used size (usedlen <= neededlen and usedlen <= buflen) */
+ const char *curpos; /* current position; pointer into format or subformat */
+ int errcode;
+};
+
+static int get_counter(const char **p)
+{
+ int i, n;
+ if (!p || !(*p)) {
+ return 1;
+ }
+ if (!isdigit((int)**p)) {
+ return 1;
+ }
+ for (n = 0;;) {
+ i = **p;
+ if (isdigit(i)) {
+ n = 10 * n + (i - '0');
+ } else {
+ return n;
+ }
+ (*p)++;
+ }
+}
+
+static int getlen(const char *p)
+{
+ int n = 0;
+ if (!p) {
+ return 0;
+ }
+
+ while (*p) {
+ switch( *p++ ) {
+ case 'W': /* word (2 byte) */
+ n += 2;
+ break;
+ case 'K': /* status word? (2 byte) */
+ n += 2;
+ break;
+ case 'N': /* count of substructures (word) at end */
+ n += 2;
+ break;
+ case 'D': /* double word (4 byte) */
+ case 'z': /* offset to zero terminated string (4 byte) */
+ case 'l': /* offset to user data (4 byte) */
+ n += 4;
+ break;
+ case 'b': /* offset to data (with counter) (4 byte) */
+ n += 4;
+ get_counter(&p);
+ break;
+ case 'B': /* byte (with optional counter) */
+ n += get_counter(&p);
+ break;
+ }
+ }
+ return n;
+}
+
+static bool init_package(struct pack_desc *p, int count, int subcount)
+{
+ int n = p->buflen;
+ int i;
+
+ if (!p->format || !p->base) {
+ return False;
+ }
+
+ i = count * getlen(p->format);
+ if (p->subformat) {
+ i += subcount * getlen(p->subformat);
+ }
+ p->structbuf = p->base;
+ p->neededlen = 0;
+ p->usedlen = 0;
+ p->subcount = 0;
+ p->curpos = p->format;
+ if (i > n) {
+ p->neededlen = i;
+ i = n = 0;
+#if 0
+ /*
+ * This is the old error code we used. Apparently
+ * WinNT/2k systems return ERRbuftoosmall (2123) and
+ * OS/2 needs this. I'm leaving this here so we can revert
+ * if needed. JRA.
+ */
+ p->errcode = ERRmoredata;
+#else
+ p->errcode = ERRbuftoosmall;
+#endif
+ } else {
+ p->errcode = NERR_Success;
+ }
+ p->buflen = i;
+ n -= i;
+ p->stringbuf = p->base + i;
+ p->stringlen = n;
+ return (p->errcode == NERR_Success);
+}
+
+static int package(struct pack_desc *p, ...)
+{
+ va_list args;
+ int needed=0, stringneeded;
+ const char *str=NULL;
+ int is_string=0, stringused;
+ int32_t temp;
+
+ va_start(args,p);
+
+ if (!*p->curpos) {
+ if (!p->subcount) {
+ p->curpos = p->format;
+ } else {
+ p->curpos = p->subformat;
+ p->subcount--;
+ }
+ }
+#if CHECK_TYPES
+ str = va_arg(args,char*);
+ SMB_ASSERT(strncmp(str,p->curpos,strlen(str)) == 0);
+#endif
+ stringneeded = -1;
+
+ if (!p->curpos) {
+ va_end(args);
+ return 0;
+ }
+
+ switch( *p->curpos++ ) {
+ case 'W': /* word (2 byte) */
+ needed = 2;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'K': /* status word? (2 byte) */
+ needed = 2;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'N': /* count of substructures (word) at end */
+ needed = 2;
+ p->subcount = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SSVAL(p->structbuf,0,p->subcount);
+ }
+ break;
+ case 'D': /* double word (4 byte) */
+ needed = 4;
+ temp = va_arg(args,int);
+ if (p->buflen >= needed) {
+ SIVAL(p->structbuf,0,temp);
+ }
+ break;
+ case 'B': /* byte (with optional counter) */
+ needed = get_counter(&p->curpos);
+ {
+ char *s = va_arg(args,char*);
+ if (p->buflen >= needed) {
+ strlcpy(p->structbuf,s?s:"",needed);
+ }
+ }
+ break;
+ case 'z': /* offset to zero terminated string (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = (str ? strlen(str)+1 : 0);
+ is_string = 1;
+ break;
+ case 'l': /* offset to user data (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = va_arg(args,int);
+ is_string = 0;
+ break;
+ case 'b': /* offset to data (with counter) (4 byte) */
+ str = va_arg(args,char*);
+ stringneeded = get_counter(&p->curpos);
+ is_string = 0;
+ break;
+ }
+
+ va_end(args);
+ if (stringneeded >= 0) {
+ needed = 4;
+ if (p->buflen >= needed) {
+ stringused = stringneeded;
+ if (stringused > p->stringlen) {
+ stringused = (is_string ? p->stringlen : 0);
+ if (p->errcode == NERR_Success) {
+ p->errcode = ERRmoredata;
+ }
+ }
+ if (!stringused) {
+ SIVAL(p->structbuf,0,0);
+ } else {
+ SIVAL(p->structbuf,0,PTR_DIFF(p->stringbuf,p->base));
+ memcpy(p->stringbuf,str?str:"",stringused);
+ if (is_string) {
+ p->stringbuf[stringused-1] = '\0';
+ }
+ p->stringbuf += stringused;
+ p->stringlen -= stringused;
+ p->usedlen += stringused;
+ }
+ }
+ p->neededlen += stringneeded;
+ }
+
+ p->neededlen += needed;
+ if (p->buflen >= needed) {
+ p->structbuf += needed;
+ p->buflen -= needed;
+ p->usedlen += needed;
+ } else {
+ if (p->errcode == NERR_Success) {
+ p->errcode = ERRmoredata;
+ }
+ }
+ return 1;
+}
+
+#if CHECK_TYPES
+#define PACK(desc,t,v) package(desc,t,v,0,0,0,0)
+#define PACKl(desc,t,v,l) package(desc,t,v,l,0,0,0,0)
+#else
+#define PACK(desc,t,v) package(desc,v)
+#define PACKl(desc,t,v,l) package(desc,v,l)
+#endif
+
+static void PACKI(struct pack_desc* desc, const char *t,int v)
+{
+ PACK(desc,t,v);
+}
+
+static void PACKS(struct pack_desc* desc,const char *t,const char *v)
+{
+ PACK(desc,t,v);
+}
+
+/****************************************************************************
+ Get a print queue.
+****************************************************************************/
+
+static void PackDriverData(struct pack_desc* desc)
+{
+ char drivdata[4+4+32];
+ SIVAL(drivdata,0,sizeof drivdata); /* cb */
+ SIVAL(drivdata,4,1000); /* lVersion */
+ memset(drivdata+8,0,32); /* szDeviceName */
+ push_ascii(drivdata+8,"NULL",32, STR_TERMINATE);
+ PACKl(desc,"l",drivdata,sizeof drivdata); /* pDriverData */
+}
+
+static int check_printq_info(struct pack_desc* desc,
+ unsigned int uLevel, char *id1, char *id2)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0:
+ desc->format = "B13";
+ break;
+ case 1:
+ desc->format = "B13BWWWzzzzzWW";
+ break;
+ case 2:
+ desc->format = "B13BWWWzzzzzWN";
+ desc->subformat = "WB21BB16B10zWWzDDz";
+ break;
+ case 3:
+ desc->format = "zWWWWzzzzWWzzl";
+ break;
+ case 4:
+ desc->format = "zWWWWzzzzWNzzl";
+ desc->subformat = "WWzWWDDzz";
+ break;
+ case 5:
+ desc->format = "z";
+ break;
+ case 51:
+ desc->format = "K";
+ break;
+ case 52:
+ desc->format = "WzzzzzzzzN";
+ desc->subformat = "z";
+ break;
+ default:
+ DEBUG(0,("check_printq_info: invalid level %d\n",
+ uLevel ));
+ return False;
+ }
+ if (id1 == NULL || strcmp(desc->format,id1) != 0) {
+ DEBUG(0,("check_printq_info: invalid format %s\n",
+ id1 ? id1 : "<NULL>" ));
+ return False;
+ }
+ if (desc->subformat && (id2 == NULL || strcmp(desc->subformat,id2) != 0)) {
+ DEBUG(0,("check_printq_info: invalid subformat %s\n",
+ id2 ? id2 : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+
+#define RAP_JOB_STATUS_QUEUED 0
+#define RAP_JOB_STATUS_PAUSED 1
+#define RAP_JOB_STATUS_SPOOLING 2
+#define RAP_JOB_STATUS_PRINTING 3
+#define RAP_JOB_STATUS_PRINTED 4
+
+#define RAP_QUEUE_STATUS_PAUSED 1
+#define RAP_QUEUE_STATUS_ERROR 2
+
+/* turn a print job status into a on the wire status
+*/
+static int printj_spoolss_status(int v)
+{
+ if (v == JOB_STATUS_QUEUED)
+ return RAP_JOB_STATUS_QUEUED;
+ if (v & JOB_STATUS_PAUSED)
+ return RAP_JOB_STATUS_PAUSED;
+ if (v & JOB_STATUS_SPOOLING)
+ return RAP_JOB_STATUS_SPOOLING;
+ if (v & JOB_STATUS_PRINTING)
+ return RAP_JOB_STATUS_PRINTING;
+ return 0;
+}
+
+/* turn a print queue status into a on the wire status
+*/
+static int printq_spoolss_status(int v)
+{
+ if (v == PRINTER_STATUS_OK)
+ return 0;
+ if (v & PRINTER_STATUS_PAUSED)
+ return RAP_QUEUE_STATUS_PAUSED;
+ return RAP_QUEUE_STATUS_ERROR;
+}
+
+static void fill_spoolss_printjob_info(int uLevel,
+ struct pack_desc *desc,
+ struct spoolss_JobInfo2 *info2,
+ int n)
+{
+ time_t t = spoolss_Time_to_time_t(&info2->submitted);
+
+ /* the client expects localtime */
+ t -= get_time_zone(t);
+
+ PACKI(desc,"W",pjobid_to_rap(info2->printer_name, info2->job_id)); /* uJobId */
+ if (uLevel == 1) {
+ PACKS(desc,"B21", info2->user_name); /* szUserName */
+ PACKS(desc,"B",""); /* pad */
+ PACKS(desc,"B16",""); /* szNotifyName */
+ PACKS(desc,"B10","PM_Q_RAW"); /* szDataType */
+ PACKS(desc,"z",""); /* pszParms */
+ PACKI(desc,"W",n+1); /* uPosition */
+ PACKI(desc,"W", printj_spoolss_status(info2->status)); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"D", t); /* ulSubmitted */
+ PACKI(desc,"D", info2->size); /* ulSize */
+ PACKS(desc,"z", info2->document_name); /* pszComment */
+ }
+ if (uLevel == 2 || uLevel == 3 || uLevel == 4) {
+ PACKI(desc,"W", info2->priority); /* uPriority */
+ PACKS(desc,"z", info2->user_name); /* pszUserName */
+ PACKI(desc,"W",n+1); /* uPosition */
+ PACKI(desc,"W", printj_spoolss_status(info2->status)); /* fsStatus */
+ PACKI(desc,"D",t); /* ulSubmitted */
+ PACKI(desc,"D", info2->size); /* ulSize */
+ PACKS(desc,"z","Samba"); /* pszComment */
+ PACKS(desc,"z", info2->document_name); /* pszDocument */
+ if (uLevel == 3) {
+ PACKS(desc,"z",""); /* pszNotifyName */
+ PACKS(desc,"z","PM_Q_RAW"); /* pszDataType */
+ PACKS(desc,"z",""); /* pszParms */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKS(desc,"z", info2->printer_name); /* pszQueue */
+ PACKS(desc,"z","lpd"); /* pszQProcName */
+ PACKS(desc,"z",""); /* pszQProcParms */
+ PACKS(desc,"z","NULL"); /* pszDriverName */
+ PackDriverData(desc); /* pDriverData */
+ PACKS(desc,"z",""); /* pszPrinterName */
+ } else if (uLevel == 4) { /* OS2 */
+ PACKS(desc,"z",""); /* pszSpoolFileName */
+ PACKS(desc,"z",""); /* pszPortName */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"D",0); /* ulPagesSpooled */
+ PACKI(desc,"D",0); /* ulPagesSent */
+ PACKI(desc,"D",0); /* ulPagesPrinted */
+ PACKI(desc,"D",0); /* ulTimePrinted */
+ PACKI(desc,"D",0); /* ulExtendJobStatus */
+ PACKI(desc,"D",0); /* ulStartPage */
+ PACKI(desc,"D",0); /* ulEndPage */
+ }
+ }
+}
+
+/********************************************************************
+ Respond to the DosPrintQInfo command with a level of 52
+ This is used to get printer driver information for Win9x clients
+ ********************************************************************/
+static void fill_printq_info_52(struct spoolss_DriverInfo3 *driver,
+ struct pack_desc* desc, int count,
+ const char *printer_name)
+{
+ int i;
+ fstring location;
+ trim_string(discard_const_p(char, driver->driver_path), "\\print$\\WIN40\\0\\", 0);
+ trim_string(discard_const_p(char, driver->data_file), "\\print$\\WIN40\\0\\", 0);
+ trim_string(discard_const_p(char, driver->help_file), "\\print$\\WIN40\\0\\", 0);
+
+ PACKI(desc, "W", 0x0400); /* don't know */
+ PACKS(desc, "z", driver->driver_name); /* long printer name */
+ PACKS(desc, "z", driver->driver_path); /* Driverfile Name */
+ PACKS(desc, "z", driver->data_file); /* Datafile name */
+ PACKS(desc, "z", driver->monitor_name); /* language monitor */
+
+ fstrcpy(location, "\\\\%L\\print$\\WIN40\\0");
+ standard_sub_basic( "", "", location, sizeof(location)-1 );
+ PACKS(desc,"z", location); /* share to retrieve files */
+
+ PACKS(desc,"z", driver->default_datatype); /* default data type */
+ PACKS(desc,"z", driver->help_file); /* helpfile name */
+ PACKS(desc,"z", driver->driver_path); /* driver name */
+
+ DEBUG(3,("Printer Driver Name: %s:\n",driver->driver_name));
+ DEBUG(3,("Driver: %s:\n",driver->driver_path));
+ DEBUG(3,("Data File: %s:\n",driver->data_file));
+ DEBUG(3,("Language Monitor: %s:\n",driver->monitor_name));
+ DEBUG(3,("Driver Location: %s:\n",location));
+ DEBUG(3,("Data Type: %s:\n",driver->default_datatype));
+ DEBUG(3,("Help File: %s:\n",driver->help_file));
+ PACKI(desc,"N",count); /* number of files to copy */
+
+ for ( i=0; i<count && driver->dependent_files && *driver->dependent_files[i]; i++)
+ {
+ trim_string(discard_const_p(char, driver->dependent_files[i]), "\\print$\\WIN40\\0\\", 0);
+ PACKS(desc,"z",driver->dependent_files[i]); /* driver files to copy */
+ DEBUG(3,("Dependent File: %s:\n", driver->dependent_files[i]));
+ }
+
+ /* sanity check */
+ if ( i != count )
+ DEBUG(3,("fill_printq_info_52: file count specified by client [%d] != number of dependent files [%i]\n",
+ count, i));
+
+ DEBUG(3,("fill_printq_info on <%s> gave %d entries\n", printer_name, i));
+
+ desc->errcode=NERR_Success;
+
+}
+
+static const char *strip_unc(const char *unc)
+{
+ char *p;
+
+ if (unc == NULL) {
+ return NULL;
+ }
+
+ if ((p = strrchr(unc, '\\')) != NULL) {
+ return p+1;
+ }
+
+ return unc;
+}
+
+static void fill_printq_info(int uLevel,
+ struct pack_desc* desc,
+ int count,
+ union spoolss_JobInfo *job_info,
+ struct spoolss_DriverInfo3 *driver_info,
+ struct spoolss_PrinterInfo2 *printer_info)
+{
+ switch (uLevel) {
+ case 0:
+ case 1:
+ case 2:
+ PACKS(desc,"B13", strip_unc(printer_info->printername));
+ break;
+ case 3:
+ case 4:
+ case 5:
+ PACKS(desc,"z", strip_unc(printer_info->printername));
+ break;
+ case 51:
+ PACKI(desc,"K", printq_spoolss_status(printer_info->status));
+ break;
+ }
+
+ if (uLevel == 1 || uLevel == 2) {
+ PACKS(desc,"B",""); /* alignment */
+ PACKI(desc,"W",5); /* priority */
+ PACKI(desc,"W",0); /* start time */
+ PACKI(desc,"W",0); /* until time */
+ PACKS(desc,"z",""); /* pSepFile */
+ PACKS(desc,"z","lpd"); /* pPrProc */
+ PACKS(desc,"z", strip_unc(printer_info->printername)); /* pDestinations */
+ PACKS(desc,"z",""); /* pParms */
+ if (printer_info->printername == NULL) {
+ PACKS(desc,"z","UNKNOWN PRINTER");
+ PACKI(desc,"W",LPSTAT_ERROR);
+ } else {
+ PACKS(desc,"z", printer_info->comment);
+ PACKI(desc,"W", printq_spoolss_status(printer_info->status)); /* status */
+ }
+ PACKI(desc,(uLevel == 1 ? "W" : "N"),count);
+ }
+
+ if (uLevel == 3 || uLevel == 4) {
+ PACKI(desc,"W",5); /* uPriority */
+ PACKI(desc,"W",0); /* uStarttime */
+ PACKI(desc,"W",0); /* uUntiltime */
+ PACKI(desc,"W",5); /* pad1 */
+ PACKS(desc,"z",""); /* pszSepFile */
+ PACKS(desc,"z","WinPrint"); /* pszPrProc */
+ PACKS(desc,"z",NULL); /* pszParms */
+ PACKS(desc,"z",NULL); /* pszComment - don't ask.... JRA */
+ /* "don't ask" that it's done this way to fix corrupted
+ Win9X/ME printer comments. */
+ PACKI(desc,"W", printq_spoolss_status(printer_info->status)); /* fsStatus */
+ PACKI(desc,(uLevel == 3 ? "W" : "N"),count); /* cJobs */
+ PACKS(desc,"z", strip_unc(printer_info->printername)); /* pszPrinters */
+ PACKS(desc,"z", printer_info->drivername); /* pszDriverName */
+ PackDriverData(desc); /* pDriverData */
+ }
+
+ if (uLevel == 2 || uLevel == 4) {
+ int i;
+ for (i = 0; i < count; i++) {
+ fill_spoolss_printjob_info(uLevel == 2 ? 1 : 2, desc, &job_info[i].info2, i);
+ }
+ }
+
+ if (uLevel==52)
+ fill_printq_info_52(driver_info, desc, count, printer_info->printername);
+}
+
+/* This function returns the number of files for a given driver */
+static int get_printerdrivernumber(const struct spoolss_DriverInfo3 *driver)
+{
+ int result = 0;
+
+ /* count the number of files */
+ while (driver->dependent_files && *driver->dependent_files[result])
+ result++;
+
+ return result;
+}
+
+static bool api_DosPrintQGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *QueueName = p;
+ unsigned int uLevel;
+ uint32_t count = 0;
+ char *str3;
+ struct pack_desc desc;
+ char* tmpdata=NULL;
+
+ WERROR werr = WERR_OK;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_DriverInfo driver_info;
+ union spoolss_JobInfo *job_info = NULL;
+ union spoolss_PrinterInfo printer_info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ str3 = get_safe_str_ptr(param,tpscnt,p,4);
+ /* str3 may be null here and is checked in check_printq_info(). */
+
+ /* remove any trailing username */
+ if ((p = strchr_m(QueueName,'%')))
+ *p = 0;
+
+ DEBUG(3,("api_DosPrintQGetInfo uLevel=%d name=%s\n",uLevel,QueueName));
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,"zWrLh"))
+ return False;
+ if (!check_printq_info(&desc,uLevel,str2,str3)) {
+ /*
+ * Patch from Scott Moomaw <scott@bridgewater.edu>
+ * to return the 'invalid info level' error if an
+ * unknown level was requested.
+ */
+ *rdata_len = 0;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,ERRunknownlevel);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,0);
+ return(True);
+ }
+
+ ZERO_STRUCT(handle);
+
+ if (QueueName == NULL || (strlen(QueueName) < 1)) {
+ desc.errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_DosPrintQGetInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ QueueName,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &handle,
+ 2,
+ 0,
+ &printer_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (uLevel==52) {
+ uint32_t server_major_version;
+ uint32_t server_minor_version;
+
+ werr = rpccli_spoolss_getprinterdriver2(cli, mem_ctx,
+ &handle,
+ "Windows 4.0",
+ 3, /* level */
+ 0,
+ 0, /* version */
+ 0,
+ &driver_info,
+ &server_major_version,
+ &server_minor_version);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ count = get_printerdrivernumber(&driver_info.info3);
+ DEBUG(3,("api_DosPrintQGetInfo: Driver files count: %d\n",count));
+ } else {
+ uint32_t num_jobs;
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &num_jobs,
+ &job_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ count = num_jobs;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *) SMB_MALLOC (desc.buflen);
+ }
+
+ if (init_package(&desc,1,count)) {
+ desc.subcount = count;
+ fill_printq_info(uLevel,&desc,count, job_info, &driver_info.info3, &printer_info.info2);
+ }
+
+ *rdata_len = desc.usedlen;
+
+ /*
+ * We must set the return code to ERRbuftoosmall
+ * in order to support lanman style printing with Win NT/2k
+ * clients --jerry
+ */
+ if (!mdrcnt && lp_disable_spoolss())
+ desc.errcode = ERRbuftoosmall;
+
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rdata_len = desc.usedlen;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ SAFE_FREE(tmpdata);
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("printqgetinfo: errorcode %d\n",desc.errcode));
+
+ SAFE_FREE(tmpdata);
+
+ return(True);
+}
+
+/****************************************************************************
+ View list of all print jobs on all queues.
+****************************************************************************/
+
+static bool api_DosPrintQEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char** rparam,
+ int *rdata_len, int *rparam_len)
+{
+ char *param_format = get_safe_str_ptr(param,tpscnt,param,2);
+ char *output_format1 = skip_string(param,tpscnt,param_format);
+ char *p = skip_string(param,tpscnt,output_format1);
+ unsigned int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *output_format2 = get_safe_str_ptr(param,tpscnt,p,4);
+ int i;
+ struct pack_desc desc;
+ int *subcntarr = NULL;
+ int queuecnt = 0, subcnt = 0, succnt = 0;
+
+ WERROR werr = WERR_OK;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ uint32_t num_printers;
+ union spoolss_PrinterInfo *printer_info;
+ union spoolss_DriverInfo *driver_info;
+ union spoolss_JobInfo **job_info;
+
+ if (!param_format || !output_format1 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ DEBUG(3,("DosPrintQEnum uLevel=%d\n",uLevel));
+
+ if (!prefix_ok(param_format,"WrLeh")) {
+ return False;
+ }
+ if (!check_printq_info(&desc,uLevel,output_format1,output_format2)) {
+ /*
+ * Patch from Scott Moomaw <scott@bridgewater.edu>
+ * to return the 'invalid info level' error if an
+ * unknown level was requested.
+ */
+ *rdata_len = 0;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,ERRunknownlevel);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,0);
+ return(True);
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_DosPrintQEnum: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ werr = rpccli_spoolss_enumprinters(cli, mem_ctx,
+ PRINTER_ENUM_LOCAL,
+ cli->srv_name_slash,
+ 2,
+ 0,
+ &num_printers,
+ &printer_info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ queuecnt = num_printers;
+
+ job_info = talloc_array(mem_ctx, union spoolss_JobInfo *, num_printers);
+ if (job_info == NULL) {
+ goto err;
+ }
+
+ driver_info = talloc_array(mem_ctx, union spoolss_DriverInfo, num_printers);
+ if (driver_info == NULL) {
+ goto err;
+ }
+
+ if((subcntarr = SMB_MALLOC_ARRAY(int,queuecnt)) == NULL) {
+ DEBUG(0,("api_DosPrintQEnum: malloc fail !\n"));
+ goto err;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ goto err;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+
+ subcnt = 0;
+ for (i = 0; i < num_printers; i++) {
+
+ uint32_t num_jobs;
+ struct policy_handle handle;
+ const char *printername;
+
+ printername = talloc_strdup(mem_ctx, printer_info[i].info2.printername);
+ if (printername == NULL) {
+ goto err;
+ }
+
+ ZERO_STRUCT(handle);
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ printername,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &num_jobs,
+ &job_info[i]);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (uLevel==52) {
+ uint32_t server_major_version;
+ uint32_t server_minor_version;
+
+ werr = rpccli_spoolss_getprinterdriver2(cli, mem_ctx,
+ &handle,
+ "Windows 4.0",
+ 3, /* level */
+ 0,
+ 0, /* version */
+ 0,
+ &driver_info[i],
+ &server_major_version,
+ &server_minor_version);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+ }
+
+ subcntarr[i] = num_jobs;
+ subcnt += subcntarr[i];
+
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ if (init_package(&desc,queuecnt,subcnt)) {
+ for (i = 0; i < num_printers; i++) {
+ fill_printq_info(uLevel,&desc,subcntarr[i], job_info[i], &driver_info[i].info3, &printer_info[i].info2);
+ if (desc.errcode == NERR_Success) {
+ succnt = i;
+ }
+ }
+ }
+
+ out:
+ SAFE_FREE(subcntarr);
+ *rdata_len = desc.usedlen;
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ goto err;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,queuecnt);
+
+ return True;
+
+ err:
+
+ SAFE_FREE(subcntarr);
+
+ return False;
+}
+
+/****************************************************************************
+ Get info level for a server list query.
+****************************************************************************/
+
+static bool check_session_info(int uLevel, char* id)
+{
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(id,"B16") != 0) {
+ return False;
+ }
+ break;
+ case 1:
+ if (strcmp(id,"B16BBDz") != 0) {
+ return False;
+ }
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+struct srv_info_struct {
+ fstring name;
+ uint32_t type;
+ fstring comment;
+ fstring domain;
+ bool server_added;
+};
+
+/*******************************************************************
+ Get server info lists from the files saved by nmbd. Return the
+ number of entries.
+******************************************************************/
+
+static int get_session_info(uint32_t servertype,
+ struct srv_info_struct **servers,
+ const char *domain)
+{
+ int count=0;
+ int alloced=0;
+ char **lines;
+ bool local_list_only;
+ int i;
+ char *slist_cache_path = cache_path(talloc_tos(), SERVER_LIST);
+ if (slist_cache_path == NULL) {
+ return 0;
+ }
+
+ lines = file_lines_load(slist_cache_path, NULL, 0, NULL);
+ if (!lines) {
+ DEBUG(4, ("Can't open %s - %s\n",
+ slist_cache_path, strerror(errno)));
+ TALLOC_FREE(slist_cache_path);
+ return 0;
+ }
+ TALLOC_FREE(slist_cache_path);
+
+ /* request for everything is code for request all servers */
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ local_list_only = (servertype & SV_TYPE_LOCAL_LIST_ONLY);
+
+ DEBUG(4,("Servertype search: %8x\n",servertype));
+
+ for (i=0;lines[i];i++) {
+ fstring stype;
+ struct srv_info_struct *s;
+ const char *ptr = lines[i];
+ bool ok = True;
+ TALLOC_CTX *frame = NULL;
+ char *p;
+
+ if (!*ptr) {
+ continue;
+ }
+
+ if (count == alloced) {
+ alloced += 10;
+ *servers = SMB_REALLOC_ARRAY(*servers,struct srv_info_struct, alloced);
+ if (!*servers) {
+ DEBUG(0,("get_session_info: failed to enlarge servers info struct!\n"));
+ TALLOC_FREE(lines);
+ return 0;
+ }
+ memset((char *)((*servers)+count),'\0',sizeof(**servers)*(alloced-count));
+ }
+ s = &(*servers)[count];
+
+ frame = talloc_stackframe();
+ s->name[0] = '\0';
+ if (!next_token_talloc(frame,&ptr,&p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(s->name, p);
+
+ stype[0] = '\0';
+ if (!next_token_talloc(frame,&ptr, &p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(stype, p);
+
+ s->comment[0] = '\0';
+ if (!next_token_talloc(frame,&ptr, &p, NULL)) {
+ TALLOC_FREE(frame);
+ continue;
+ }
+ fstrcpy(s->comment, p);
+ string_truncate(s->comment, MAX_SERVER_STRING_LENGTH);
+
+ s->domain[0] = '\0';
+ if (!next_token_talloc(frame,&ptr,&p, NULL)) {
+ /* this allows us to cope with an old nmbd */
+ fstrcpy(s->domain,lp_workgroup());
+ } else {
+ fstrcpy(s->domain, p);
+ }
+ TALLOC_FREE(frame);
+
+ if (sscanf(stype,"%X",&s->type) != 1) {
+ DEBUG(4,("r:host file "));
+ ok = False;
+ }
+
+ /* Filter the servers/domains we return based on what was asked for. */
+
+ /* Check to see if we are being asked for a local list only. */
+ if(local_list_only && ((s->type & SV_TYPE_LOCAL_LIST_ONLY) == 0)) {
+ DEBUG(4,("r: local list only"));
+ ok = False;
+ }
+
+ /* doesn't match up: don't want it */
+ if (!(servertype & s->type)) {
+ DEBUG(4,("r:serv type "));
+ ok = False;
+ }
+
+ if ((servertype & SV_TYPE_DOMAIN_ENUM) !=
+ (s->type & SV_TYPE_DOMAIN_ENUM)) {
+ DEBUG(4,("s: dom mismatch "));
+ ok = False;
+ }
+
+ if (!strequal(domain, s->domain) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ ok = False;
+ }
+
+ /* We should never return a server type with a SV_TYPE_LOCAL_LIST_ONLY set. */
+ s->type &= ~SV_TYPE_LOCAL_LIST_ONLY;
+
+ if (ok) {
+ DEBUG(4,("**SV** %20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+ s->server_added = True;
+ count++;
+ } else {
+ DEBUG(4,("%20s %8x %25s %15s\n",
+ s->name, s->type, s->comment, s->domain));
+ }
+ }
+
+ TALLOC_FREE(lines);
+ return count;
+}
+
+/*******************************************************************
+ Fill in a server info structure.
+******************************************************************/
+
+static int fill_srv_info(struct srv_info_struct *service,
+ int uLevel, char **buf, int *buflen,
+ char **stringbuf, int *stringspace, char *baseaddr)
+{
+ int struct_len;
+ char* p;
+ char* p2;
+ int l2;
+ int len;
+
+ switch (uLevel) {
+ case 0:
+ struct_len = 16;
+ break;
+ case 1:
+ struct_len = 26;
+ break;
+ default:
+ return -1;
+ }
+
+ if (!buf) {
+ len = 0;
+ switch (uLevel) {
+ case 1:
+ len = strlen(service->comment)+1;
+ break;
+ }
+
+ *buflen = struct_len;
+ *stringspace = len;
+ return struct_len + len;
+ }
+
+ len = struct_len;
+ p = *buf;
+ if (*buflen < struct_len) {
+ return -1;
+ }
+ if (stringbuf) {
+ p2 = *stringbuf;
+ l2 = *stringspace;
+ } else {
+ p2 = p + struct_len;
+ l2 = *buflen - struct_len;
+ }
+ if (!baseaddr) {
+ baseaddr = p;
+ }
+
+ switch (uLevel) {
+ case 0:
+ push_ascii(p,service->name, MAX_NETBIOSNAME_LEN, STR_TERMINATE);
+ break;
+
+ case 1:
+ push_ascii(p,service->name,MAX_NETBIOSNAME_LEN, STR_TERMINATE);
+ SIVAL(p,18,service->type);
+ SIVAL(p,22,PTR_DIFF(p2,baseaddr));
+ len += CopyAndAdvance(&p2,service->comment,&l2);
+ break;
+ }
+
+ if (stringbuf) {
+ *buf = p + struct_len;
+ *buflen -= struct_len;
+ *stringbuf = p2;
+ *stringspace = l2;
+ } else {
+ *buf = p2;
+ *buflen -= len;
+ }
+ return len;
+}
+
+
+static int srv_comp(struct srv_info_struct *s1,struct srv_info_struct *s2)
+{
+ return strcasecmp_m(s1->name,s2->name);
+}
+
+/****************************************************************************
+ View list of servers available (or possibly domains). The info is
+ extracted from lists saved by nmbd on the local host.
+****************************************************************************/
+
+static bool api_RNetServerEnum2(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param, tpscnt, param, 2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param, tpscnt, p, 0, -1);
+ int buf_len = get_safe_SVAL(param,tpscnt, p, 2, 0);
+ uint32_t servertype = get_safe_IVAL(param,tpscnt,p,4, 0);
+ char *p2;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+ struct srv_info_struct *servers=NULL;
+ int counted=0,total=0;
+ int i,missed;
+ fstring domain;
+ bool domain_request;
+ bool local_request;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* If someone sets all the bits they don't really mean to set
+ DOMAIN_ENUM and LOCAL_LIST_ONLY, they just want all the
+ known servers. */
+
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ /* If someone sets SV_TYPE_LOCAL_LIST_ONLY but hasn't set
+ any other bit (they may just set this bit on its own) they
+ want all the locally seen servers. However this bit can be
+ set on its own so set the requested servers to be
+ ALL - DOMAIN_ENUM. */
+
+ if ((servertype & SV_TYPE_LOCAL_LIST_ONLY) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ servertype = SV_TYPE_ALL & ~(SV_TYPE_DOMAIN_ENUM);
+ }
+
+ domain_request = ((servertype & SV_TYPE_DOMAIN_ENUM) != 0);
+ local_request = ((servertype & SV_TYPE_LOCAL_LIST_ONLY) != 0);
+
+ p += 8;
+
+ if (!prefix_ok(str1,"WrLehD")) {
+ return False;
+ }
+ if (!check_session_info(uLevel,str2)) {
+ return False;
+ }
+
+ DEBUG(4, ("server request level: %s %8x ", str2, servertype));
+ DEBUG(4, ("domains_req:%s ", BOOLSTR(domain_request)));
+ DEBUG(4, ("local_only:%s\n", BOOLSTR(local_request)));
+
+ if (strcmp(str1, "WrLehDz") == 0) {
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(domain, p);
+ } else {
+ fstrcpy(domain, lp_workgroup());
+ }
+
+ DEBUG(4, ("domain [%s]\n", domain));
+
+ if (lp_browse_list()) {
+ total = get_session_info(servertype,&servers,domain);
+ }
+
+ data_len = fixed_len = string_len = 0;
+ missed = 0;
+
+ TYPESAFE_QSORT(servers, total, srv_comp);
+
+ {
+ char *lastname=NULL;
+
+ for (i=0;i<total;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ data_len += fill_srv_info(s,uLevel,0,&f_len,0,&s_len,0);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+
+ if (data_len < buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed++;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxiliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ {
+ char *lastname=NULL;
+ int count2 = counted;
+
+ for (i = 0; i < total && count2;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ fill_srv_info(s,uLevel,&p,&f_len,&p2,&s_len,*rdata);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+ count2--;
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,(missed == 0 ? NERR_Success : ERRmoredata));
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ SAFE_FREE(servers);
+
+ DEBUG(3,("NetServerEnum2 domain = %s uLevel=%d counted=%d total=%d\n",
+ domain,uLevel,counted,counted+missed));
+
+ return True;
+}
+
+static int srv_name_match(const char *n1, const char *n2)
+{
+ /*
+ * [MS-RAP] footnote <88> for Section 3.2.5.15 says:
+ *
+ * In Windows, FirstNameToReturn need not be an exact match:
+ * the server will return a list of servers that exist on
+ * the network greater than or equal to the FirstNameToReturn.
+ */
+ int ret = strcasecmp_m(n1, n2);
+
+ if (ret <= 0) {
+ return 0;
+ }
+
+ return ret;
+}
+
+static bool api_RNetServerEnum3(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param, tpscnt, param, 2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param, tpscnt, p, 0, -1);
+ int buf_len = get_safe_SVAL(param,tpscnt, p, 2, 0);
+ uint32_t servertype = get_safe_IVAL(param,tpscnt,p,4, 0);
+ char *p2;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+ struct srv_info_struct *servers=NULL;
+ int counted=0,first=0,total=0;
+ int i,missed;
+ fstring domain;
+ fstring first_name;
+ bool domain_request;
+ bool local_request;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* If someone sets all the bits they don't really mean to set
+ DOMAIN_ENUM and LOCAL_LIST_ONLY, they just want all the
+ known servers. */
+
+ if (servertype == SV_TYPE_ALL) {
+ servertype &= ~(SV_TYPE_DOMAIN_ENUM|SV_TYPE_LOCAL_LIST_ONLY);
+ }
+
+ /* If someone sets SV_TYPE_LOCAL_LIST_ONLY but hasn't set
+ any other bit (they may just set this bit on its own) they
+ want all the locally seen servers. However this bit can be
+ set on its own so set the requested servers to be
+ ALL - DOMAIN_ENUM. */
+
+ if ((servertype & SV_TYPE_LOCAL_LIST_ONLY) && !(servertype & SV_TYPE_DOMAIN_ENUM)) {
+ servertype = SV_TYPE_ALL & ~(SV_TYPE_DOMAIN_ENUM);
+ }
+
+ domain_request = ((servertype & SV_TYPE_DOMAIN_ENUM) != 0);
+ local_request = ((servertype & SV_TYPE_LOCAL_LIST_ONLY) != 0);
+
+ p += 8;
+
+ if (strcmp(str1, "WrLehDzz") != 0) {
+ return false;
+ }
+ if (!check_session_info(uLevel,str2)) {
+ return False;
+ }
+
+ DEBUG(4, ("server request level: %s %8x ", str2, servertype));
+ DEBUG(4, ("domains_req:%s ", BOOLSTR(domain_request)));
+ DEBUG(4, ("local_only:%s\n", BOOLSTR(local_request)));
+
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(domain, p);
+ if (domain[0] == '\0') {
+ fstrcpy(domain, lp_workgroup());
+ }
+ p = skip_string(param,tpscnt,p);
+ if (skip_string(param,tpscnt,p) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(first_name, p);
+
+ DEBUG(4, ("domain: '%s' first_name: '%s'\n",
+ domain, first_name));
+
+ if (lp_browse_list()) {
+ total = get_session_info(servertype,&servers,domain);
+ }
+
+ data_len = fixed_len = string_len = 0;
+ missed = 0;
+
+ TYPESAFE_QSORT(servers, total, srv_comp);
+
+ if (first_name[0] != '\0') {
+ struct srv_info_struct *first_server = NULL;
+
+ BINARY_ARRAY_SEARCH(servers, total, name, first_name,
+ srv_name_match, first_server);
+ if (first_server) {
+ first = PTR_DIFF(first_server, servers) / sizeof(*servers);
+ /*
+ * The binary search may not find the exact match
+ * so we need to search backward to find the first match
+ *
+ * This implements the strange matching windows
+ * implements. (see the comment in srv_name_match().
+ */
+ for (;first > 0;) {
+ int ret;
+ ret = strcasecmp_m(first_name,
+ servers[first-1].name);
+ if (ret > 0) {
+ break;
+ }
+ first--;
+ }
+ } else {
+ /* we should return no entries */
+ first = total;
+ }
+ }
+
+ {
+ char *lastname=NULL;
+
+ for (i=first;i<total;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ data_len += fill_srv_info(s,uLevel,0,&f_len,0,&s_len,0);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+
+ if (data_len < buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed++;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxiliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ {
+ char *lastname=NULL;
+ int count2 = counted;
+
+ for (i = first; i < total && count2;i++) {
+ struct srv_info_struct *s = &servers[i];
+
+ if (lastname && strequal(lastname,s->name)) {
+ continue;
+ }
+ lastname = s->name;
+ fill_srv_info(s,uLevel,&p,&f_len,&p2,&s_len,*rdata);
+ DEBUG(4,("fill_srv_info[%d] %20s %8x %25s %15s\n",
+ i, s->name, s->type, s->comment, s->domain));
+ count2--;
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,(missed == 0 ? NERR_Success : ERRmoredata));
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ DEBUG(3,("NetServerEnum3 domain = %s uLevel=%d first=%d[%s => %s] counted=%d total=%d\n",
+ domain,uLevel,first,first_name,
+ first < total ? servers[first].name : "",
+ counted,counted+missed));
+
+ SAFE_FREE(servers);
+
+ return True;
+}
+
+/****************************************************************************
+ command 0x34 - suspected of being a "Lookup Names" stub api
+ ****************************************************************************/
+
+static bool api_RNetGroupGetUsers(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt, char **rdata,
+ char **rparam, int *rdata_len, int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int buf_len = get_safe_SVAL(param,tpscnt,p,2,0);
+ int counted=0;
+ int missed=0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(5,("RNetGroupGetUsers: %s %s %s %d %d\n",
+ str1, str2, p, uLevel, buf_len));
+
+ if (!prefix_ok(str1,"zWrLeh")) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,0x08AC); /* informational warning message */
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,counted+missed);
+
+ return True;
+}
+
+/****************************************************************************
+ get info about a share
+ ****************************************************************************/
+
+static bool check_share_info(int uLevel, char* id)
+{
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(id,"B13") != 0) {
+ return False;
+ }
+ break;
+ case 1:
+ /* Level-2 descriptor is allowed (and ignored) */
+ if (strcmp(id,"B13BWz") != 0 &&
+ strcmp(id,"B13BWzWWWzB9B") != 0) {
+ return False;
+ }
+ break;
+ case 2:
+ if (strcmp(id,"B13BWzWWWzB9B") != 0) {
+ return False;
+ }
+ break;
+ case 91:
+ if (strcmp(id,"B13BWzWWWzB9BB9BWzWWzWW") != 0) {
+ return False;
+ }
+ break;
+ default:
+ return False;
+ }
+ return True;
+}
+
+static int fill_share_info(connection_struct *conn, int snum, int uLevel,
+ char** buf, int* buflen,
+ char** stringbuf, int* stringspace, char* baseaddr)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int struct_len;
+ char* p;
+ char* p2;
+ int l2;
+ int len;
+
+ switch( uLevel ) {
+ case 0:
+ struct_len = 13;
+ break;
+ case 1:
+ struct_len = 20;
+ break;
+ case 2:
+ struct_len = 40;
+ break;
+ case 91:
+ struct_len = 68;
+ break;
+ default:
+ return -1;
+ }
+
+ if (!buf) {
+ len = 0;
+
+ if (uLevel > 0) {
+ len += StrlenExpanded(conn,snum,lp_comment(talloc_tos(), lp_sub, snum));
+ }
+ if (uLevel > 1) {
+ len += strlen(lp_path(talloc_tos(), lp_sub, snum)) + 1;
+ }
+ if (buflen) {
+ *buflen = struct_len;
+ }
+ if (stringspace) {
+ *stringspace = len;
+ }
+ return struct_len + len;
+ }
+
+ len = struct_len;
+ p = *buf;
+ if ((*buflen) < struct_len) {
+ return -1;
+ }
+
+ if (stringbuf) {
+ p2 = *stringbuf;
+ l2 = *stringspace;
+ } else {
+ p2 = p + struct_len;
+ l2 = (*buflen) - struct_len;
+ }
+
+ if (!baseaddr) {
+ baseaddr = p;
+ }
+
+ push_ascii(p,lp_servicename(talloc_tos(), lp_sub, snum),13, STR_TERMINATE);
+
+ if (uLevel > 0) {
+ int type;
+
+ SCVAL(p,13,0);
+ type = STYPE_DISKTREE;
+ if (lp_printable(snum)) {
+ type = STYPE_PRINTQ;
+ }
+ if (strequal("IPC",lp_fstype(snum))) {
+ type = STYPE_IPC;
+ }
+ SSVAL(p,14,type); /* device type */
+ SIVAL(p,16,PTR_DIFF(p2,baseaddr));
+ len += CopyExpanded(conn,snum,&p2,lp_comment(talloc_tos(), lp_sub, snum),&l2);
+ }
+
+ if (uLevel > 1) {
+ SSVAL(p,20,ACCESS_READ|ACCESS_WRITE|ACCESS_CREATE); /* permissions */
+ SSVALS(p,22,-1); /* max uses */
+ SSVAL(p,24,1); /* current uses */
+ SIVAL(p,26,PTR_DIFF(p2,baseaddr)); /* local pathname */
+ len += CopyAndAdvance(&p2,lp_path(talloc_tos(),lp_sub, snum),&l2);
+ memset(p+30,0,SHPWLEN+2); /* passwd (reserved), pad field */
+ }
+
+ if (uLevel > 2) {
+ memset(p+40,0,SHPWLEN+2);
+ SSVAL(p,50,0);
+ SIVAL(p,52,0);
+ SSVAL(p,56,0);
+ SSVAL(p,58,0);
+ SIVAL(p,60,0);
+ SSVAL(p,64,0);
+ SSVAL(p,66,0);
+ }
+
+ if (stringbuf) {
+ (*buf) = p + struct_len;
+ (*buflen) -= struct_len;
+ (*stringbuf) = p2;
+ (*stringspace) = l2;
+ } else {
+ (*buf) = p2;
+ (*buflen) -= len;
+ }
+
+ return len;
+}
+
+static bool api_RNetShareGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *netname_in = skip_string(param,tpscnt,str2);
+ char *netname = NULL;
+ char *p = skip_string(param,tpscnt,netname);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int snum;
+
+ if (!str1 || !str2 || !netname_in || !p) {
+ return False;
+ }
+
+ snum = find_service(talloc_tos(), netname_in, &netname);
+ if (snum < 0 || !netname) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,"zWrLh")) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ p = *rdata;
+ *rdata_len = fill_share_info(conn,snum,uLevel,&p,&mdrcnt,0,0,0);
+ if (*rdata_len < 0) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ View the list of available shares.
+
+ This function is the server side of the NetShareEnum() RAP call.
+ It fills the return buffer with share names and share comments.
+ Note that the return buffer normally (in all known cases) allows only
+ twelve byte strings for share names (plus one for a nul terminator).
+ Share names longer than 12 bytes must be skipped.
+ ****************************************************************************/
+
+static bool api_RNetShareEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,
+ int mprcnt,
+ char **rdata,
+ char **rparam,
+ int *rdata_len,
+ int *rparam_len )
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ int buf_len = get_safe_SVAL(param,tpscnt,p,2,0);
+ char *p2;
+ int count = 0;
+ int total=0,counted=0;
+ bool missed = False;
+ int i;
+ int data_len, fixed_len, string_len;
+ int f_len = 0, s_len = 0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (!prefix_ok(str1,"WrLeh")) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+
+ /* Ensure all the usershares are loaded. */
+ become_root();
+ delete_and_reload_printers();
+ load_registry_shares();
+ count = load_usershare_shares(NULL, connections_snum_used);
+ unbecome_root();
+
+ data_len = fixed_len = string_len = 0;
+ for (i=0;i<count;i++) {
+ fstring servicename_dos;
+ if (!(lp_browseable(i) && lp_snum_ok(i))) {
+ continue;
+ }
+ push_ascii_fstring(servicename_dos, lp_servicename(talloc_tos(), lp_sub, i));
+ /* Maximum name length = 13. */
+ if( lp_browseable( i ) && lp_snum_ok( i ) && (strlen(servicename_dos) < 13)) {
+ total++;
+ data_len += fill_share_info(conn,i,uLevel,0,&f_len,0,&s_len,0);
+ if (data_len < buf_len) {
+ counted++;
+ fixed_len += f_len;
+ string_len += s_len;
+ } else {
+ missed = True;
+ }
+ }
+ }
+
+ *rdata_len = fixed_len + string_len;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p2 = (*rdata) + fixed_len; /* auxiliary data (strings) will go here */
+ p = *rdata;
+ f_len = fixed_len;
+ s_len = string_len;
+
+ for( i = 0; i < count; i++ ) {
+ fstring servicename_dos;
+ if (!(lp_browseable(i) && lp_snum_ok(i))) {
+ continue;
+ }
+
+ push_ascii_fstring(servicename_dos,
+ lp_servicename(talloc_tos(), lp_sub, i));
+ if (lp_browseable(i) && lp_snum_ok(i) && (strlen(servicename_dos) < 13)) {
+ if (fill_share_info( conn,i,uLevel,&p,&f_len,&p2,&s_len,*rdata ) < 0) {
+ break;
+ }
+ }
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,missed ? ERRmoredata : NERR_Success);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,counted);
+ SSVAL(*rparam,6,total);
+
+ DEBUG(3,("RNetShareEnum gave %d entries of %d (%d %d %d %d)\n",
+ counted,total,uLevel,
+ buf_len,*rdata_len,mdrcnt));
+
+ return True;
+}
+
+/****************************************************************************
+ Add a share
+ ****************************************************************************/
+
+static bool api_RNetShareAdd(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ fstring sharename;
+ fstring comment;
+ char *pathname = NULL;
+ unsigned int offset;
+ int res = ERRunsup;
+ size_t converted_size;
+
+ WERROR werr = WERR_OK;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ union srvsvc_NetShareInfo info;
+ struct srvsvc_NetShareInfo2 info2;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,RAP_WShareAdd_REQ)) {
+ return False;
+ }
+ if (!check_share_info(uLevel,str2)) {
+ return False;
+ }
+ if (uLevel != 2) {
+ return False;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(sharename,data);
+
+ if (mdrcnt < 28) {
+ return False;
+ }
+
+ /* only support disk share adds */
+ if (SVAL(data,14)!=STYPE_DISKTREE) {
+ return False;
+ }
+
+ offset = IVAL(data, 16);
+ if (offset >= mdrcnt) {
+ res = ERRinvalidparam;
+ goto out;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data+offset) == NULL) {
+ return False;
+ }
+ pull_ascii_fstring(comment, offset? (data+offset) : "");
+
+ offset = IVAL(data, 26);
+
+ if (offset >= mdrcnt) {
+ res = ERRinvalidparam;
+ goto out;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(data,mdrcnt,data+offset) == NULL) {
+ return False;
+ }
+
+ if (!pull_ascii_talloc(talloc_tos(), &pathname,
+ offset ? (data+offset) : "", &converted_size))
+ {
+ DEBUG(0,("api_RNetShareAdd: pull_ascii_talloc failed: %s\n",
+ strerror(errno)));
+ }
+
+ if (!pathname) {
+ return false;
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_srvsvc,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RNetShareAdd: could not connect to srvsvc: %s\n",
+ nt_errstr(status)));
+ res = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ info2.name = sharename;
+ info2.type = STYPE_DISKTREE;
+ info2.comment = comment;
+ info2.permissions = 0;
+ info2.max_users = 0;
+ info2.current_users = 0;
+ info2.path = pathname;
+ info2.password = NULL;
+
+ info.info2 = &info2;
+
+ status = dcerpc_srvsvc_NetShareAdd(b, mem_ctx,
+ cli->srv_name_slash,
+ 2,
+ &info,
+ NULL,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ res = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ res = W_ERROR_V(werr);
+ goto out;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+ *rdata_len = 0;
+
+ return True;
+
+ out:
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+ SSVAL(*rparam,0,res);
+ SSVAL(*rparam,2,0);
+ return True;
+}
+
+/****************************************************************************
+ view list of groups available
+ ****************************************************************************/
+
+static bool api_RNetGroupEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int i;
+ int errflags=0;
+ int resume_context, cli_buf_size;
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+
+ uint32_t num_groups;
+ uint32_t resume_handle;
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle samr_handle, domain_handle;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+
+ /* parameters
+ * W-> resume context (number of users to skip)
+ * r -> return parameter pointer to receive buffer
+ * L -> length of receive buffer
+ * e -> return parameter number of entries
+ * h -> return parameter total number of users
+ */
+
+ if (strcmp("B21",str2) != 0) {
+ return False;
+ }
+
+ status = rpc_pipe_open_interface(
+ talloc_tos(), &ndr_table_samr,
+ conn->session_info, conn->sconn->remote_address,
+ conn->sconn->local_address, conn->sconn->msg_ctx, &samr_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: Could not connect to samr: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, talloc_tos(), lp_netbios_name(),
+ SAMR_ACCESS_LOOKUP_DOMAIN, &samr_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(result)));
+ return false;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, talloc_tos(), &samr_handle,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ get_global_sam_sid(), &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(status)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(result)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+
+ resume_context = get_safe_SVAL(param,tpscnt,p,0,-1);
+ cli_buf_size= get_safe_SVAL(param,tpscnt,p,2,0);
+ DEBUG(10,("api_RNetGroupEnum:resume context: %d, client buffer size: "
+ "%d\n", resume_context, cli_buf_size));
+
+ *rdata_len = cli_buf_size;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+
+ errflags = NERR_Success;
+ num_groups = 0;
+ resume_handle = 0;
+
+ while (true) {
+ struct samr_SamArray *sam_entries;
+ uint32_t num_entries;
+
+ status = dcerpc_samr_EnumDomainGroups(b, talloc_tos(),
+ &domain_handle,
+ &resume_handle,
+ &sam_entries, 1,
+ &num_entries,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainGroups returned "
+ "%s\n", nt_errstr(status)));
+ break;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ DEBUG(10, ("dcerpc_samr_EnumDomainGroups returned "
+ "%s\n", nt_errstr(result)));
+ break;
+ }
+
+ if (num_entries == 0) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainGroups returned "
+ "no entries -- done\n"));
+ break;
+ }
+
+ for(i=0; i<num_entries; i++) {
+ const char *name;
+
+ name = sam_entries->entries[i].name.string;
+
+ if( ((PTR_DIFF(p,*rdata)+21) > *rdata_len) ) {
+ /* set overflow error */
+ DEBUG(3,("overflow on entry %d group %s\n", i,
+ name));
+ errflags=234;
+ break;
+ }
+
+ /* truncate the name at 21 chars. */
+ memset(p, 0, 21);
+ strlcpy(p, name, 21);
+ DEBUG(10,("adding entry %d group %s\n", i, p));
+ p += 21;
+ p += 5; /* Both NT4 and W2k3SP1 do padding here. No
+ * idea why... */
+ num_groups += 1;
+ }
+
+ if (errflags != NERR_Success) {
+ break;
+ }
+
+ TALLOC_FREE(sam_entries);
+ }
+
+ dcerpc_samr_Close(b, talloc_tos(), &domain_handle, &result);
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam, 0, errflags);
+ SSVAL(*rparam, 2, 0); /* converter word */
+ SSVAL(*rparam, 4, num_groups); /* is this right?? */
+ SSVAL(*rparam, 6, resume_context+num_groups); /* is this right?? */
+
+ return(True);
+}
+
+/*******************************************************************
+ Get groups that a user is a member of.
+******************************************************************/
+
+static bool api_NetUserGetGroups(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *UserName = skip_string(param,tpscnt,str2);
+ char *p = skip_string(param,tpscnt,UserName);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ const char *level_string;
+ int count=0;
+ bool ret = False;
+ uint32_t i;
+ char *endp = NULL;
+
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle samr_handle, domain_handle, user_handle;
+ struct lsa_String name;
+ struct lsa_Strings names;
+ struct samr_Ids type, rid;
+ struct samr_RidWithAttributeArray *rids;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !UserName || !p) {
+ return False;
+ }
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+
+ if ( strcmp(str1,"zWrLeh") != 0 )
+ return False;
+
+ switch( uLevel ) {
+ case 0:
+ level_string = "B21";
+ break;
+ default:
+ return False;
+ }
+
+ if (strcmp(level_string,str2) != 0)
+ return False;
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ status = rpc_pipe_open_interface(
+ talloc_tos(), &ndr_table_samr,
+ conn->session_info, conn->sconn->remote_address,
+ conn->sconn->local_address, conn->sconn->msg_ctx, &samr_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: Could not connect to samr: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, talloc_tos(), lp_netbios_name(),
+ SAMR_ACCESS_LOOKUP_DOMAIN, &samr_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(result)));
+ return false;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, talloc_tos(), &samr_handle,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ get_global_sam_sid(), &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(status)));
+ goto close_sam;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(result)));
+ goto close_sam;
+ }
+
+ name.string = UserName;
+
+ status = dcerpc_samr_LookupNames(b, talloc_tos(),
+ &domain_handle, 1, &name,
+ &rid, &type,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(status)));
+ goto close_domain;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(result)));
+ goto close_domain;
+ }
+ if (rid.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto close_domain;
+ }
+ if (type.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto close_domain;
+ }
+
+ if (type.ids[0] != SID_NAME_USER) {
+ DEBUG(10, ("%s is a %s, not a user\n", UserName,
+ sid_type_lookup(type.ids[0])));
+ goto close_domain;
+ }
+
+ status = dcerpc_samr_OpenUser(b, talloc_tos(),
+ &domain_handle,
+ SAMR_USER_ACCESS_GET_GROUPS,
+ rid.ids[0], &user_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(status)));
+ goto close_domain;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(result)));
+ goto close_domain;
+ }
+
+ status = dcerpc_samr_GetGroupsForUser(b, talloc_tos(),
+ &user_handle, &rids,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(status)));
+ goto close_user;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_LookupNames failed: %s\n",
+ nt_errstr(result)));
+ goto close_user;
+ }
+
+ for (i=0; i<rids->count; i++) {
+
+ status = dcerpc_samr_LookupRids(b, talloc_tos(),
+ &domain_handle,
+ 1, &rids->rids[i].rid,
+ &names, &type,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result) && (names.count == 1)) {
+ strlcpy(p, names.names[0].string, PTR_DIFF(endp,p));
+ p += 21;
+ count++;
+ }
+ }
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ SSVAL(*rparam,4,count); /* is this right?? */
+ SSVAL(*rparam,6,count); /* is this right?? */
+
+ ret = True;
+
+ close_user:
+ dcerpc_samr_Close(b, talloc_tos(), &user_handle, &result);
+ close_domain:
+ dcerpc_samr_Close(b, talloc_tos(), &domain_handle, &result);
+ close_sam:
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+
+ return ret;
+}
+
+/*******************************************************************
+ Get all users.
+******************************************************************/
+
+static bool api_RNetUserEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int count_sent=0;
+ int num_users=0;
+ int errflags=0;
+ int i, resume_context, cli_buf_size;
+ uint32_t resume_handle;
+
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle samr_handle, domain_handle;
+ NTSTATUS status, result;
+
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *endp = NULL;
+
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ if (strcmp(str1,"WrLeh") != 0)
+ return False;
+ /* parameters
+ * W-> resume context (number of users to skip)
+ * r -> return parameter pointer to receive buffer
+ * L -> length of receive buffer
+ * e -> return parameter number of entries
+ * h -> return parameter total number of users
+ */
+
+ resume_context = get_safe_SVAL(param,tpscnt,p,0,-1);
+ cli_buf_size= get_safe_SVAL(param,tpscnt,p,2,0);
+ DEBUG(10,("api_RNetUserEnum:resume context: %d, client buffer size: %d\n",
+ resume_context, cli_buf_size));
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (strcmp("B21",str2) != 0)
+ return False;
+
+ *rdata_len = cli_buf_size;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ status = rpc_pipe_open_interface(
+ talloc_tos(), &ndr_table_samr,
+ conn->session_info, conn->sconn->remote_address,
+ conn->sconn->local_address, conn->sconn->msg_ctx, &samr_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: Could not connect to samr: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, talloc_tos(), lp_netbios_name(),
+ SAMR_ACCESS_LOOKUP_DOMAIN, &samr_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_Connect2 failed: %s\n",
+ nt_errstr(result)));
+ return false;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, talloc_tos(), &samr_handle,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ get_global_sam_sid(), &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(status)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("api_RNetUserEnum: samr_OpenDomain failed: %s\n",
+ nt_errstr(result)));
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+ return false;
+ }
+
+ errflags=NERR_Success;
+
+ resume_handle = 0;
+
+ while (true) {
+ struct samr_SamArray *sam_entries;
+ uint32_t num_entries;
+
+ status = dcerpc_samr_EnumDomainUsers(b, talloc_tos(),
+ &domain_handle,
+ &resume_handle,
+ 0, &sam_entries, 1,
+ &num_entries,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainUsers returned "
+ "%s\n", nt_errstr(status)));
+ break;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainUsers returned "
+ "%s\n", nt_errstr(result)));
+ break;
+ }
+
+ if (num_entries == 0) {
+ DEBUG(10, ("dcerpc_samr_EnumDomainUsers returned "
+ "no entries -- done\n"));
+ break;
+ }
+
+ for (i=0; i<num_entries; i++) {
+ const char *name;
+
+ name = sam_entries->entries[i].name.string;
+
+ if(((PTR_DIFF(p,*rdata)+21)<=*rdata_len)
+ &&(strlen(name)<=21)) {
+ strlcpy(p,name,PTR_DIFF(endp,p));
+ DEBUG(10,("api_RNetUserEnum:adding entry %d "
+ "username %s\n",count_sent,p));
+ p += 21;
+ count_sent++;
+ } else {
+ /* set overflow error */
+ DEBUG(10,("api_RNetUserEnum:overflow on entry %d "
+ "username %s\n",count_sent,name));
+ errflags=234;
+ break;
+ }
+ }
+
+ if (errflags != NERR_Success) {
+ break;
+ }
+
+ TALLOC_FREE(sam_entries);
+ }
+
+ dcerpc_samr_Close(b, talloc_tos(), &domain_handle, &result);
+ dcerpc_samr_Close(b, talloc_tos(), &samr_handle, &result);
+
+ *rdata_len = PTR_DIFF(p,*rdata);
+
+ SSVAL(*rparam,0,errflags);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,count_sent); /* is this right?? */
+ SSVAL(*rparam,6,num_users); /* is this right?? */
+
+ return True;
+}
+
+/****************************************************************************
+ Get the time of day info.
+****************************************************************************/
+
+static bool api_NetRemoteTOD(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ struct tm *t;
+ time_t unixdate = time(NULL);
+ char *p;
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 21;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+
+ srv_put_dos_date3(p,0,unixdate); /* this is the time that is looked at
+ by NT in a "net time" operation,
+ it seems to ignore the one below */
+
+ /* the client expects to get localtime, not GMT, in this bit
+ (I think, this needs testing) */
+ t = localtime(&unixdate);
+ if (!t) {
+ return False;
+ }
+
+ SIVAL(p,4,0); /* msecs ? */
+ SCVAL(p,8,t->tm_hour);
+ SCVAL(p,9,t->tm_min);
+ SCVAL(p,10,t->tm_sec);
+ SCVAL(p,11,0); /* hundredths of seconds */
+ SSVALS(p,12,get_time_zone(unixdate)/60); /* timezone in minutes from GMT */
+ SSVAL(p,14,10000); /* timer interval in 0.0001 of sec */
+ SCVAL(p,16,t->tm_mday);
+ SCVAL(p,17,t->tm_mon + 1);
+ SSVAL(p,18,1900+t->tm_year);
+ SCVAL(p,20,t->tm_wday);
+
+ return True;
+}
+
+/****************************************************************************
+ Set the user password (SamOEM version - gets plaintext).
+****************************************************************************/
+
+static bool api_SamOEMChangePassword(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ fstring user;
+ char *p = get_safe_str_ptr(param,tpscnt,param,2);
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status, result;
+ struct rpc_pipe_client *cli = NULL;
+ struct lsa_AsciiString server, account;
+ struct samr_CryptPassword password;
+ struct samr_Password hash;
+ int errcode = NERR_badpass;
+ int bufsize;
+ struct dcerpc_binding_handle *b;
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ if (!p) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_badpass);
+
+ /*
+ * Check the parameter definition is correct.
+ */
+
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ if(!strequal(p, "zsT")) {
+ DEBUG(0,("api_SamOEMChangePassword: Invalid parameter string %s\n", p));
+ return False;
+ }
+ p = skip_string(param, tpscnt, p);
+ if (!p) {
+ return False;
+ }
+
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ if(!strequal(p, "B516B16")) {
+ DEBUG(0,("api_SamOEMChangePassword: Invalid data parameter string %s\n", p));
+ return False;
+ }
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ /* Do we have a string ? */
+ if (skip_string(param,tpscnt,p) == 0) {
+ return False;
+ }
+ p += pull_ascii_fstring(user,p);
+
+ DEBUG(3,("api_SamOEMChangePassword: Change password for <%s>\n",user));
+
+ if (tdscnt != 532) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ bufsize = get_safe_SVAL(param,tpscnt,p,0,-1);
+ if (bufsize != 532) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ memcpy(password.data, data, 516);
+ memcpy(hash.hash, data+516, 16);
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_samr,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_SamOEMChangePassword: could not connect to samr: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ init_lsa_AsciiString(&server, lp_netbios_name());
+ init_lsa_AsciiString(&account, user);
+
+ status = dcerpc_samr_OemChangePasswordUser2(b, mem_ctx,
+ &server,
+ &account,
+ &password,
+ &hash,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ errcode = NERR_Success;
+ out:
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ delete a print job
+ Form: <W> <>
+ ****************************************************************************/
+
+static bool api_RDosPrintJobDel(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int function = get_safe_SVAL(param,tpscnt,param,0,0);
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ uint32_t jobid;
+ fstring sharename;
+ int errcode;
+ WERROR werr = WERR_OK;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ enum spoolss_JobControl command;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ /*
+ * We use 1 here not 2 as we're checking
+ * the last byte we want to access is safe.
+ */
+ if (!is_offset_safe(param,tpscnt,p,1)) {
+ return False;
+ }
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid))
+ return False;
+
+ /* check it's a supported variant */
+ if (!(strcsequal(str1,"W") && strcsequal(str2,"")))
+ return(False);
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RDosPrintJobDel: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ "RAW",
+ devmode_ctr,
+ JOB_ACCESS_ADMINISTER,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ /* FIXME: formerly NERR_JobNotFound was returned if job did not exist
+ * and NERR_DestNotFound if share did not exist */
+
+ errcode = NERR_Success;
+
+ switch (function) {
+ case 81: /* delete */
+ command = SPOOLSS_JOB_CONTROL_DELETE;
+ break;
+ case 82: /* pause */
+ command = SPOOLSS_JOB_CONTROL_PAUSE;
+ break;
+ case 83: /* resume */
+ command = SPOOLSS_JOB_CONTROL_RESUME;
+ break;
+ default:
+ errcode = NERR_notsupported;
+ goto out;
+ }
+
+ status = dcerpc_spoolss_SetJob(b, mem_ctx,
+ &handle,
+ jobid,
+ NULL, /* unique ptr ctr */
+ command,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ Purge a print queue - or pause or resume it.
+ ****************************************************************************/
+
+static bool api_WPrintQueueCtrl(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ int function = get_safe_SVAL(param,tpscnt,param,0,0);
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *QueueName = skip_string(param,tpscnt,str2);
+ int errcode = NERR_notsupported;
+ WERROR werr = WERR_OK;
+ NTSTATUS status;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+ enum spoolss_PrinterControl command = SPOOLSS_PRINTER_CONTROL_UNPAUSE;
+
+ if (!str1 || !str2 || !QueueName) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!(strcsequal(str1,"z") && strcsequal(str2,"")))
+ return(False);
+
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ *rdata_len = 0;
+
+ if (skip_string(param,tpscnt,QueueName) == NULL) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintQueueCtrl: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ QueueName,
+ NULL,
+ devmode_ctr,
+ PRINTER_ACCESS_ADMINISTER,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ switch (function) {
+ case 74: /* Pause queue */
+ command = SPOOLSS_PRINTER_CONTROL_PAUSE;
+ break;
+ case 75: /* Resume queue */
+ command = SPOOLSS_PRINTER_CONTROL_RESUME;
+ break;
+ case 103: /* Purge */
+ command = SPOOLSS_PRINTER_CONTROL_PURGE;
+ break;
+ default:
+ werr = WERR_NOT_SUPPORTED;
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ ZERO_STRUCT(info_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ status = dcerpc_spoolss_SetPrinter(b, mem_ctx,
+ &handle,
+ &info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ command,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ errcode = W_ERROR_V(werr);
+
+ out:
+
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+/****************************************************************************
+ set the property of a print job (undocumented?)
+ ? function = 0xb -> set name of print job
+ ? function = 0x6 -> move print job up/down
+ Form: <WWsTP> <WWzWWDDzzzzzzzzzzlz>
+ or <WWsTP> <WB21BB16B10zWWzDDz>
+****************************************************************************/
+
+static int check_printjob_info(struct pack_desc* desc,
+ int uLevel, char* id)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0: desc->format = "W"; break;
+ case 1: desc->format = "WB21BB16B10zWWzDDz"; break;
+ case 2: desc->format = "WWzWWDDzz"; break;
+ case 3: desc->format = "WWzWWDDzzzzzzzzzzlz"; break;
+ case 4: desc->format = "WWzWWDDzzzzzDDDDDDD"; break;
+ default:
+ DEBUG(0,("check_printjob_info: invalid level %d\n",
+ uLevel ));
+ return False;
+ }
+ if (id == NULL || strcmp(desc->format,id) != 0) {
+ DEBUG(0,("check_printjob_info: invalid format %s\n",
+ id ? id : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+static bool api_PrintJobInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ struct pack_desc desc;
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ uint32_t jobid;
+ fstring sharename;
+ int uLevel = get_safe_SVAL(param,tpscnt,p,2,-1);
+ int function = get_safe_SVAL(param,tpscnt,p,4,-1);
+ int errcode;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct spoolss_JobInfoContainer ctr;
+ union spoolss_JobInfo info;
+ struct spoolss_SetJobInfo1 info1;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ /*
+ * We use 1 here not 2 as we're checking
+ * the last byte we want to access is safe.
+ */
+ if (!is_offset_safe(param,tpscnt,p,1)) {
+ return False;
+ }
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid))
+ return False;
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ /* check it's a supported variant */
+ if ((strcmp(str1,"WWsTP")) ||
+ (!check_printjob_info(&desc,uLevel,str2)))
+ return(False);
+
+ errcode = NERR_notsupported;
+
+ switch (function) {
+ case 0xb:
+ /* change print job name, data gives the name */
+ break;
+ default:
+ goto out;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_PrintJobInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getjob(cli, mem_ctx,
+ &handle,
+ jobid,
+ 1, /* level */
+ 0, /* offered */
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ ZERO_STRUCT(ctr);
+
+ info1.job_id = info.info1.job_id;
+ info1.printer_name = info.info1.printer_name;
+ info1.user_name = info.info1.user_name;
+ info1.document_name = data;
+ info1.data_type = info.info1.data_type;
+ info1.text_status = info.info1.text_status;
+ info1.status = info.info1.status;
+ info1.priority = info.info1.priority;
+ info1.position = info.info1.position;
+ info1.total_pages = info.info1.total_pages;
+ info1.pages_printed = info.info1.pages_printed;
+ info1.submitted = info.info1.submitted;
+
+ ctr.level = 1;
+ ctr.info.info1 = &info1;
+
+ status = dcerpc_spoolss_SetJob(b, mem_ctx,
+ &handle,
+ jobid,
+ &ctr,
+ 0,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ errcode = NERR_Success;
+ out:
+
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ SSVALS(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ return(True);
+}
+
+
+/****************************************************************************
+ Get info about the server.
+****************************************************************************/
+
+static bool api_RNetServerGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *p2;
+ int struct_len;
+
+ NTSTATUS status;
+ WERROR werr;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct rpc_pipe_client *cli = NULL;
+ union srvsvc_NetSrvInfo info;
+ int errcode;
+ struct dcerpc_binding_handle *b;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(4,("NetServerGetInfo level %d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (!prefix_ok(str1,"WrLh")) {
+ return False;
+ }
+
+ switch( uLevel ) {
+ case 0:
+ if (strcmp(str2,"B16") != 0) {
+ return False;
+ }
+ struct_len = 16;
+ break;
+ case 1:
+ if (strcmp(str2,"B16BBDz") != 0) {
+ return False;
+ }
+ struct_len = 26;
+ break;
+ case 2:
+ if (strcmp(str2,"B16BBDzDDDWWzWWWWWWWBB21zWWWWWWWWWWWWWWWWWWWWWWz")!= 0) {
+ return False;
+ }
+ struct_len = 134;
+ break;
+ case 3:
+ if (strcmp(str2,"B16BBDzDDDWWzWWWWWWWBB21zWWWWWWWWWWWWWWWWWWWWWWzDWz") != 0) {
+ return False;
+ }
+ struct_len = 144;
+ break;
+ case 20:
+ if (strcmp(str2,"DN") != 0) {
+ return False;
+ }
+ struct_len = 6;
+ break;
+ case 50:
+ if (strcmp(str2,"B16BBDzWWzzz") != 0) {
+ return False;
+ }
+ struct_len = 42;
+ break;
+ default:
+ return False;
+ }
+
+ *rdata_len = mdrcnt;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ p2 = p + struct_len;
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_srvsvc,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RNetServerGetInfo: could not connect to srvsvc: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ status = dcerpc_srvsvc_NetSrvGetInfo(b, mem_ctx,
+ NULL,
+ 101,
+ &info,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (info.info101 == NULL) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (uLevel != 20) {
+ size_t len = 0;
+ status = srvstr_push(NULL, 0, p, info.info101->server_name, 16,
+ STR_ASCII|STR_UPPER|STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ }
+ p += 16;
+ if (uLevel > 0) {
+ SCVAL(p,0,info.info101->version_major);
+ SCVAL(p,1,info.info101->version_minor);
+ SIVAL(p,2,info.info101->server_type);
+
+ if (mdrcnt == struct_len) {
+ SIVAL(p,6,0);
+ } else {
+ SIVAL(p,6,PTR_DIFF(p2,*rdata));
+ if (mdrcnt - struct_len <= 0) {
+ return false;
+ }
+ push_ascii(p2,
+ info.info101->comment,
+ MIN(mdrcnt - struct_len,
+ MAX_SERVER_STRING_LENGTH),
+ STR_TERMINATE);
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ }
+ }
+
+ if (uLevel > 1) {
+ return False; /* not yet implemented */
+ }
+
+ errcode = NERR_Success;
+
+ out:
+
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ Get info about the server.
+****************************************************************************/
+
+static bool api_NetWkstaGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *p2;
+ char *endp;
+ int level = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DEBUG(4,("NetWkstaGetInfo level %d\n",level));
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ /* check it's a supported variant */
+ if (!(level==10 && strcsequal(str1,"WrLh") && strcsequal(str2,"zzzBBzz"))) {
+ return False;
+ }
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ SSVAL(*rparam,0,NERR_Success);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+
+ p2 = get_safe_ptr(*rdata,*rdata_len,p,22);
+ if (!p2) {
+ return False;
+ }
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* host name */
+ strlcpy(p2,get_local_machine_name(),PTR_DIFF(endp,p2));
+ if (!strupper_m(p2)) {
+ return false;
+ }
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata));
+ strlcpy(p2,conn->session_info->unix_info->sanitized_username,PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* login domain */
+ strlcpy(p2,lp_workgroup(),PTR_DIFF(endp,p2));
+ if (!strupper_m(p2)) {
+ return false;
+ }
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SCVAL(p,0,SAMBA_MAJOR_NBT_ANNOUNCE_VERSION); /* system version - e.g 4 in 4.1 */
+ SCVAL(p,1,SAMBA_MINOR_NBT_ANNOUNCE_VERSION); /* system version - e.g .1 in 4.1 */
+ p += 2;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata));
+ strlcpy(p2,lp_workgroup(),PTR_DIFF(endp,p2)); /* don't know. login domain?? */
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ SIVAL(p,0,PTR_DIFF(p2,*rdata)); /* don't know */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ p += 4;
+
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ SSVAL(*rparam,4,*rdata_len);
+
+ return True;
+}
+
+/****************************************************************************
+ get info about a user
+
+ struct user_info_11 {
+ char usri11_name[21]; 0-20
+ char usri11_pad; 21
+ char *usri11_comment; 22-25
+ char *usri11_usr_comment; 26-29
+ unsigned short usri11_priv; 30-31
+ unsigned long usri11_auth_flags; 32-35
+ long usri11_password_age; 36-39
+ char *usri11_homedir; 40-43
+ char *usri11_parms; 44-47
+ long usri11_last_logon; 48-51
+ long usri11_last_logoff; 52-55
+ unsigned short usri11_bad_pw_count; 56-57
+ unsigned short usri11_num_logons; 58-59
+ char *usri11_logon_server; 60-63
+ unsigned short usri11_country_code; 64-65
+ char *usri11_workstations; 66-69
+ unsigned long usri11_max_storage; 70-73
+ unsigned short usri11_units_per_week; 74-75
+ unsigned char *usri11_logon_hours; 76-79
+ unsigned short usri11_code_page; 80-81
+ };
+
+where:
+
+ usri11_name specifies the user name for which information is retrieved
+
+ usri11_pad aligns the next data structure element to a word boundary
+
+ usri11_comment is a null terminated ASCII comment
+
+ usri11_user_comment is a null terminated ASCII comment about the user
+
+ usri11_priv specifies the level of the privilege assigned to the user.
+ The possible values are:
+
+Name Value Description
+USER_PRIV_GUEST 0 Guest privilege
+USER_PRIV_USER 1 User privilege
+USER_PRV_ADMIN 2 Administrator privilege
+
+ usri11_auth_flags specifies the account operator privileges. The
+ possible values are:
+
+Name Value Description
+AF_OP_PRINT 0 Print operator
+
+
+Leach, Naik [Page 28]
+
+
+
+INTERNET-DRAFT CIFS Remote Admin Protocol January 10, 1997
+
+
+AF_OP_COMM 1 Communications operator
+AF_OP_SERVER 2 Server operator
+AF_OP_ACCOUNTS 3 Accounts operator
+
+
+ usri11_password_age specifies how many seconds have elapsed since the
+ password was last changed.
+
+ usri11_home_dir points to a null terminated ASCII string that contains
+ the path name of the user's home directory.
+
+ usri11_parms points to a null terminated ASCII string that is set
+ aside for use by applications.
+
+ usri11_last_logon specifies the time when the user last logged on.
+ This value is stored as the number of seconds elapsed since
+ 00:00:00, January 1, 1970.
+
+ usri11_last_logoff specifies the time when the user last logged off.
+ This value is stored as the number of seconds elapsed since
+ 00:00:00, January 1, 1970. A value of 0 means the last logoff
+ time is unknown.
+
+ usri11_bad_pw_count specifies the number of incorrect passwords
+ entered since the last successful logon.
+
+ usri11_log1_num_logons specifies the number of times this user has
+ logged on. A value of -1 means the number of logons is unknown.
+
+ usri11_logon_server points to a null terminated ASCII string that
+ contains the name of the server to which logon requests are sent.
+ A null string indicates logon requests should be sent to the
+ domain controller.
+
+ usri11_country_code specifies the country code for the user's language
+ of choice.
+
+ usri11_workstations points to a null terminated ASCII string that
+ contains the names of workstations the user may log on from.
+ There may be up to 8 workstations, with the names separated by
+ commas. A null strings indicates there are no restrictions.
+
+ usri11_max_storage specifies the maximum amount of disk space the user
+ can occupy. A value of 0xffffffff indicates there are no
+ restrictions.
+
+ usri11_units_per_week specifies the equal number of time units into
+ which a week is divided. This value must be equal to 168.
+
+ usri11_logon_hours points to a 21 byte (168 bits) string that
+ specifies the time during which the user can log on. Each bit
+ represents one unique hour in a week. The first bit (bit 0, word
+ 0) is Sunday, 0:00 to 0:59, the second bit (bit 1, word 0) is
+
+
+
+Leach, Naik [Page 29]
+
+
+
+INTERNET-DRAFT CIFS Remote Admin Protocol January 10, 1997
+
+
+ Sunday, 1:00 to 1:59 and so on. A null pointer indicates there
+ are no restrictions.
+
+ usri11_code_page specifies the code page for the user's language of
+ choice
+
+All of the pointers in this data structure need to be treated
+specially. The pointer is a 32 bit pointer. The higher 16 bits need
+to be ignored. The converter word returned in the parameters section
+needs to be subtracted from the lower 16 bits to calculate an offset
+into the return buffer where this ASCII string resides.
+
+There is no auxiliary data in the response.
+
+ ****************************************************************************/
+
+#define usri11_name 0
+#define usri11_pad 21
+#define usri11_comment 22
+#define usri11_usr_comment 26
+#define usri11_full_name 30
+#define usri11_priv 34
+#define usri11_auth_flags 36
+#define usri11_password_age 40
+#define usri11_homedir 44
+#define usri11_parms 48
+#define usri11_last_logon 52
+#define usri11_last_logoff 56
+#define usri11_bad_pw_count 60
+#define usri11_num_logons 62
+#define usri11_logon_server 64
+#define usri11_country_code 68
+#define usri11_workstations 70
+#define usri11_max_storage 74
+#define usri11_units_per_week 78
+#define usri11_logon_hours 80
+#define usri11_code_page 84
+#define usri11_end 86
+
+static bool api_RNetUserGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *UserName = skip_string(param,tpscnt,str2);
+ char *p = skip_string(param,tpscnt,UserName);
+ int uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ char *p2;
+ char *endp;
+ const char *level_string;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status, result;
+ struct rpc_pipe_client *cli = NULL;
+ struct policy_handle connect_handle, domain_handle, user_handle;
+ struct lsa_String domain_name;
+ struct dom_sid2 *domain_sid;
+ struct lsa_String names;
+ struct samr_Ids rids;
+ struct samr_Ids types;
+ int errcode = W_ERROR_V(WERR_NERR_USERNOTFOUND);
+ uint32_t rid;
+ union samr_UserInfo *info;
+ struct dcerpc_binding_handle *b = NULL;
+
+ if (!str1 || !str2 || !UserName || !p) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ DEBUG(4,("RNetUserGetInfo level=%d\n", uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLh") != 0) {
+ return False;
+ }
+ switch( uLevel ) {
+ case 0: level_string = "B21"; break;
+ case 1: level_string = "B21BB16DWzzWz"; break;
+ case 2: level_string = "B21BB16DWzzWzDzzzzDDDDWb21WWzWW"; break;
+ case 10: level_string = "B21Bzzz"; break;
+ case 11: level_string = "B21BzzzWDDzzDDWWzWzDWb21W"; break;
+ default: return False;
+ }
+
+ if (strcmp(level_string,str2) != 0) {
+ return False;
+ }
+
+ *rdata_len = mdrcnt + 1024;
+ *rdata = smb_realloc_limit(*rdata,*rdata_len);
+ if (!*rdata) {
+ return False;
+ }
+
+ p = *rdata;
+ endp = *rdata + *rdata_len;
+ p2 = get_safe_ptr(*rdata,*rdata_len,p,usri11_end);
+ if (!p2) {
+ return False;
+ }
+
+ ZERO_STRUCT(connect_handle);
+ ZERO_STRUCT(domain_handle);
+ ZERO_STRUCT(user_handle);
+
+ status = rpc_pipe_open_interface(mem_ctx, &ndr_table_samr,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_RNetUserGetInfo: could not connect to samr: %s\n",
+ nt_errstr(status)));
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ b = cli->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ lp_netbios_name(),
+ SAMR_ACCESS_CONNECT_TO_SERVER |
+ SAMR_ACCESS_ENUM_DOMAINS |
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ &connect_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ init_lsa_String(&domain_name, get_global_sam_name());
+
+ status = dcerpc_samr_LookupDomain(b, mem_ctx,
+ &connect_handle,
+ &domain_name,
+ &domain_sid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_handle,
+ SAMR_DOMAIN_ACCESS_OPEN_ACCOUNT,
+ domain_sid,
+ &domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ init_lsa_String(&names, UserName);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_handle,
+ 1,
+ &names,
+ &rids,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ if (rids.count != 1) {
+ errcode = W_ERROR_V(WERR_NO_SUCH_USER);
+ goto out;
+ }
+ if (rids.count != types.count) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+ if (types.ids[0] != SID_NAME_USER) {
+ errcode = W_ERROR_V(WERR_INVALID_PARAMETER);
+ goto out;
+ }
+
+ rid = rids.ids[0];
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_handle,
+ SAMR_USER_ACCESS_GET_LOCALE |
+ SAMR_USER_ACCESS_GET_LOGONINFO |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_GET_GROUPS |
+ SAMR_USER_ACCESS_GET_GROUP_MEMBERSHIP |
+ SEC_STD_READ_CONTROL,
+ rid,
+ &user_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ status = dcerpc_samr_QueryUserInfo2(b, mem_ctx,
+ &user_handle,
+ UserAllInformation,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ errcode = W_ERROR_V(ntstatus_to_werror(result));
+ goto out;
+ }
+
+ memset(p,0,21);
+ fstrcpy(p+usri11_name,UserName); /* 21 bytes - user name */
+
+ if (uLevel > 0) {
+ SCVAL(p,usri11_pad,0); /* padding - 1 byte */
+ *p2 = 0;
+ }
+
+ if (uLevel >= 10) {
+ SIVAL(p,usri11_comment,PTR_DIFF(p2,p)); /* comment */
+ strlcpy(p2,"Comment",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SIVAL(p,usri11_usr_comment,PTR_DIFF(p2,p)); /* user_comment */
+ strlcpy(p2,"UserComment",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ /* EEK! the cifsrap.txt doesn't have this in!!!! */
+ SIVAL(p,usri11_full_name,PTR_DIFF(p2,p)); /* full name */
+ strlcpy(p2,info->info21.full_name.string,PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ }
+
+ if (uLevel == 11) {
+ const char *homedir = info->info21.home_directory.string;
+ /* modelled after NTAS 3.51 reply */
+ SSVAL(p,usri11_priv,
+ (get_current_uid(conn) == sec_initial_uid())?
+ USER_PRIV_ADMIN:USER_PRIV_USER);
+ SIVAL(p,usri11_auth_flags,AF_OP_PRINT); /* auth flags */
+ SIVALS(p,usri11_password_age,-1); /* password age */
+ SIVAL(p,usri11_homedir,PTR_DIFF(p2,p)); /* home dir */
+ strlcpy(p2, homedir, PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,usri11_parms,PTR_DIFF(p2,p)); /* parms */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,usri11_last_logon,0); /* last logon */
+ SIVAL(p,usri11_last_logoff,0); /* last logoff */
+ SSVALS(p,usri11_bad_pw_count,-1); /* bad pw counts */
+ SSVALS(p,usri11_num_logons,-1); /* num logons */
+ SIVAL(p,usri11_logon_server,PTR_DIFF(p2,p)); /* logon server */
+ strlcpy(p2,"\\\\*",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SSVAL(p,usri11_country_code,0); /* country code */
+
+ SIVAL(p,usri11_workstations,PTR_DIFF(p2,p)); /* workstations */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SIVALS(p,usri11_max_storage,-1); /* max storage */
+ SSVAL(p,usri11_units_per_week,168); /* units per week */
+ SIVAL(p,usri11_logon_hours,PTR_DIFF(p2,p)); /* logon hours */
+
+ /* a simple way to get logon hours at all times. */
+ memset(p2,0xff,21);
+ SCVAL(p2,21,0); /* fix zero termination */
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+
+ SSVAL(p,usri11_code_page,0); /* code page */
+ }
+
+ if (uLevel == 1 || uLevel == 2) {
+ memset(p+22,' ',16); /* password */
+ SIVALS(p,38,-1); /* password age */
+ SSVAL(p,42,
+ (get_current_uid(conn) == sec_initial_uid())?
+ USER_PRIV_ADMIN:USER_PRIV_USER);
+ SIVAL(p,44,PTR_DIFF(p2,*rdata)); /* home dir */
+ strlcpy(p2, info->info21.home_directory.string,
+ PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,48,PTR_DIFF(p2,*rdata)); /* comment */
+ *p2++ = 0;
+ SSVAL(p,52,0); /* flags */
+ SIVAL(p,54,PTR_DIFF(p2,*rdata)); /* script_path */
+ strlcpy(p2, info->info21.logon_script.string,
+ PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ if (uLevel == 2) {
+ SIVAL(p,58,0); /* auth_flags */
+ SIVAL(p,62,PTR_DIFF(p2,*rdata)); /* full_name */
+ strlcpy(p2,info->info21.full_name.string,PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,66,0); /* urs_comment */
+ SIVAL(p,70,PTR_DIFF(p2,*rdata)); /* parms */
+ strlcpy(p2,"",PTR_DIFF(endp,p2));
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SIVAL(p,74,0); /* workstations */
+ SIVAL(p,78,0); /* last_logon */
+ SIVAL(p,82,0); /* last_logoff */
+ SIVALS(p,86,-1); /* acct_expires */
+ SIVALS(p,90,-1); /* max_storage */
+ SSVAL(p,94,168); /* units_per_week */
+ SIVAL(p,96,PTR_DIFF(p2,*rdata)); /* logon_hours */
+ memset(p2,-1,21);
+ p2 += 21;
+ SSVALS(p,100,-1); /* bad_pw_count */
+ SSVALS(p,102,-1); /* num_logons */
+ SIVAL(p,104,PTR_DIFF(p2,*rdata)); /* logon_server */
+ {
+ TALLOC_CTX *ctx = talloc_tos();
+ int space_rem = *rdata_len - (p2 - *rdata);
+ char *tmp;
+
+ if (space_rem <= 0) {
+ return false;
+ }
+ tmp = talloc_strdup(ctx, "\\\\%L");
+ if (!tmp) {
+ return false;
+ }
+ tmp = talloc_sub_basic(ctx,
+ "",
+ "",
+ tmp);
+ if (!tmp) {
+ return false;
+ }
+
+ push_ascii(p2,
+ tmp,
+ space_rem,
+ STR_TERMINATE);
+ }
+ p2 = skip_string(*rdata,*rdata_len,p2);
+ if (!p2) {
+ return False;
+ }
+ SSVAL(p,108,49); /* country_code */
+ SSVAL(p,110,860); /* code page */
+ }
+ }
+
+ errcode = NERR_Success;
+
+ out:
+ *rdata_len = PTR_DIFF(p2,*rdata);
+
+ if (b && is_valid_policy_hnd(&user_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_handle, &result);
+ }
+ if (b && is_valid_policy_hnd(&domain_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_handle, &result);
+ }
+ if (b && is_valid_policy_hnd(&connect_handle)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_handle, &result);
+ }
+
+ SSVAL(*rparam,0,errcode);
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,*rdata_len); /* is this right?? */
+
+ return(True);
+}
+
+static bool api_WWkstaUserLogon(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ struct pack_desc desc;
+ char* name;
+ struct auth_session_info *si = NULL;
+ NTSTATUS status;
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &si);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ DBG_INFO("Username of UID %ju is %s\n",
+ (uintmax_t)si->unix_token->uid,
+ si->unix_info->unix_name);
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+ name = get_safe_str_ptr(param,tpscnt,p,2);
+ if (!name) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ DEBUG(3,("WWkstaUserLogon uLevel=%d name=%s\n",uLevel,name));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"OOWb54WrLh") != 0) {
+ return False;
+ }
+ if (uLevel != 1 || strcmp(str2,"WB21BWDWWDDDDDDDzzzD") != 0) {
+ return False;
+ }
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.subformat = NULL;
+ desc.format = str2;
+
+ if (init_package(&desc,1,0)) {
+ PACKI(&desc,"W",0); /* code */
+ PACKS(&desc,"B21",name); /* eff. name */
+ PACKS(&desc,"B",""); /* pad */
+ PACKI(&desc,"W",
+ (get_current_uid(conn) == sec_initial_uid())?
+ USER_PRIV_ADMIN:USER_PRIV_USER);
+ PACKI(&desc,"D",0); /* auth flags XXX */
+ PACKI(&desc,"W",0); /* num logons */
+ PACKI(&desc,"W",0); /* bad pw count */
+ PACKI(&desc,"D",0); /* last logon */
+ PACKI(&desc,"D",-1); /* last logoff */
+ PACKI(&desc,"D",-1); /* logoff time */
+ PACKI(&desc,"D",-1); /* kickoff time */
+ PACKI(&desc,"D",0); /* password age */
+ PACKI(&desc,"D",0); /* password can change */
+ PACKI(&desc,"D",-1); /* password must change */
+
+ {
+ fstring mypath;
+ fstrcpy(mypath,"\\\\");
+ fstrcat(mypath,get_local_machine_name());
+ if (!strupper_m(mypath)) {
+ return false;
+ }
+ PACKS(&desc,"z",mypath); /* computer */
+ }
+
+ PACKS(&desc,"z",lp_workgroup());/* domain */
+ PACKS(&desc,"z", si->info->logon_script); /* script path */
+ PACKI(&desc,"D",0x00000000); /* reserved */
+ }
+
+ *rdata_len = desc.usedlen;
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("WWkstaUserLogon: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+/****************************************************************************
+ api_WAccessGetUserPerms
+****************************************************************************/
+
+static bool api_WAccessGetUserPerms(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *user = skip_string(param,tpscnt,str2);
+ char *resource = skip_string(param,tpscnt,user);
+
+ if (!str1 || !str2 || !user || !resource) {
+ return False;
+ }
+
+ if (skip_string(param,tpscnt,resource) == NULL) {
+ return False;
+ }
+ DEBUG(3,("WAccessGetUserPerms user=%s resource=%s\n",user,resource));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zzh") != 0) {
+ return False;
+ }
+ if (strcmp(str2,"") != 0) {
+ return False;
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,0); /* errorcode */
+ SSVAL(*rparam,2,0); /* converter word */
+ SSVAL(*rparam,4,0x7f); /* permission flags */
+
+ return True;
+}
+
+/****************************************************************************
+ api_WPrintJobEnumerate
+ ****************************************************************************/
+
+static bool api_WPrintJobGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ fstring sharename;
+ uint32_t jobid;
+ struct pack_desc desc;
+ char *tmpdata=NULL;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_JobInfo info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,2,-1);
+
+ memset((char *)&desc,'\0',sizeof(desc));
+ memset((char *)&status,'\0',sizeof(status));
+
+ DEBUG(3,("WPrintJobGetInfo uLevel=%d uJobId=0x%X\n",uLevel,SVAL(p,0)));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WWrLh") != 0) {
+ return False;
+ }
+ if (!check_printjob_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ if(!rap_to_pjobid(SVAL(p,0), sharename, &jobid)) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintJobGetInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ "RAW",
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getjob(cli, mem_ctx,
+ &handle,
+ jobid,
+ 2, /* level */
+ 0, /* offered */
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *)SMB_MALLOC( desc.buflen );
+ }
+
+ if (init_package(&desc,1,0)) {
+ fill_spoolss_printjob_info(uLevel, &desc, &info.info2, info.info2.position);
+ *rdata_len = desc.usedlen;
+ } else {
+ desc.errcode = NERR_JobNotFound;
+ *rdata_len = 0;
+ }
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ SAFE_FREE(tmpdata);
+
+ DEBUG(4,("WPrintJobGetInfo: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintJobEnumerate(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char *name = p;
+ int uLevel;
+ int i, succnt=0;
+ struct pack_desc desc;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ uint32_t count = 0;
+ union spoolss_JobInfo *info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintJobEnumerate uLevel=%d name=%s\n",uLevel,name));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLeh") != 0) {
+ return False;
+ }
+
+ if (uLevel > 2) {
+ return False; /* defined only for uLevel 0,1,2 */
+ }
+
+ if (!check_printjob_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintJobEnumerate: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ name,
+ NULL,
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+
+ if (init_package(&desc,count,0)) {
+ succnt = 0;
+ for (i = 0; i < count; i++) {
+ fill_spoolss_printjob_info(uLevel, &desc, &info[i].info2, i);
+ if (desc.errcode == NERR_Success) {
+ succnt = i+1;
+ }
+ }
+ }
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,count);
+
+ DEBUG(4,("WPrintJobEnumerate: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static int check_printdest_info(struct pack_desc* desc,
+ int uLevel, char* id)
+{
+ desc->subformat = NULL;
+ switch( uLevel ) {
+ case 0:
+ desc->format = "B9";
+ break;
+ case 1:
+ desc->format = "B9B21WWzW";
+ break;
+ case 2:
+ desc->format = "z";
+ break;
+ case 3:
+ desc->format = "zzzWWzzzWW";
+ break;
+ default:
+ DEBUG(0,("check_printdest_info: invalid level %d\n",
+ uLevel));
+ return False;
+ }
+ if (id == NULL || strcmp(desc->format,id) != 0) {
+ DEBUG(0,("check_printdest_info: invalid string %s\n",
+ id ? id : "<NULL>" ));
+ return False;
+ }
+ return True;
+}
+
+static void fill_printdest_info(struct spoolss_PrinterInfo2 *info2, int uLevel,
+ struct pack_desc* desc)
+{
+ char buf[100];
+
+ strncpy(buf, info2->printername, sizeof(buf)-1);
+ buf[sizeof(buf)-1] = 0;
+ (void)strupper_m(buf);
+
+ if (uLevel <= 1) {
+ PACKS(desc,"B9",buf); /* szName */
+ if (uLevel == 1) {
+ PACKS(desc,"B21",""); /* szUserName */
+ PACKI(desc,"W",0); /* uJobId */
+ PACKI(desc,"W",0); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKI(desc,"W",0); /* time */
+ }
+ }
+
+ if (uLevel == 2 || uLevel == 3) {
+ PACKS(desc,"z",buf); /* pszPrinterName */
+ if (uLevel == 3) {
+ PACKS(desc,"z",""); /* pszUserName */
+ PACKS(desc,"z",""); /* pszLogAddr */
+ PACKI(desc,"W",0); /* uJobId */
+ PACKI(desc,"W",0); /* fsStatus */
+ PACKS(desc,"z",""); /* pszStatus */
+ PACKS(desc,"z",""); /* pszComment */
+ PACKS(desc,"z","NULL"); /* pszDrivers */
+ PACKI(desc,"W",0); /* time */
+ PACKI(desc,"W",0); /* pad1 */
+ }
+ }
+}
+
+static bool api_WPrintDestGetInfo(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ char* PrinterName = p;
+ int uLevel;
+ struct pack_desc desc;
+ char *tmpdata=NULL;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_PrinterInfo info;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ p = skip_string(param,tpscnt,p);
+ if (!p) {
+ return False;
+ }
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDestGetInfo uLevel=%d PrinterName=%s\n",uLevel,PrinterName));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"zWrLh") != 0) {
+ return False;
+ }
+ if (!check_printdest_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintDestGetInfo: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ PrinterName,
+ NULL,
+ devmode_ctr,
+ PRINTER_ACCESS_USE,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+
+ werr = rpccli_spoolss_getprinter(cli, mem_ctx,
+ &handle,
+ 2,
+ 0,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ } else {
+ /*
+ * Don't return data but need to get correct length
+ * init_package will return wrong size if buflen=0
+ */
+ desc.buflen = getlen(desc.format);
+ desc.base = tmpdata = (char *)SMB_MALLOC( desc.buflen );
+ }
+ if (init_package(&desc,1,0)) {
+ fill_printdest_info(&info.info2, uLevel,&desc);
+ }
+
+ out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 6;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,desc.neededlen);
+
+ DEBUG(4,("WPrintDestGetInfo: errorcode %d\n",desc.errcode));
+ SAFE_FREE(tmpdata);
+
+ return True;
+}
+
+static bool api_WPrintDestEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int queuecnt;
+ int i, n, succnt=0;
+ struct pack_desc desc;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ union spoolss_PrinterInfo *info;
+ uint32_t count;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDestEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (!check_printdest_info(&desc,uLevel,str2)) {
+ return False;
+ }
+
+ queuecnt = 0;
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("api_WPrintDestEnum: could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumprinters(cli, mem_ctx,
+ PRINTER_ENUM_LOCAL,
+ cli->srv_name_slash,
+ 2,
+ 0,
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ desc.errcode = W_ERROR_V(werr);
+ *rdata_len = 0;
+ desc.errcode = NERR_DestNotFound;
+ desc.neededlen = 0;
+ goto out;
+ }
+
+ queuecnt = count;
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ if (init_package(&desc,queuecnt,0)) {
+ succnt = 0;
+ n = 0;
+ for (i = 0; i < count; i++) {
+ fill_printdest_info(&info[i].info2, uLevel,&desc);
+ n++;
+ if (desc.errcode == NERR_Success) {
+ succnt = n;
+ }
+ }
+ }
+ out:
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,queuecnt);
+
+ DEBUG(4,("WPrintDestEnumerate: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintDriverEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintDriverEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B41") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B41","NULL");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintDriverEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintQProcEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintQProcEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B13") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B13","lpd");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintQProcEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+static bool api_WPrintPortEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ int succnt;
+ struct pack_desc desc;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ memset((char *)&desc,'\0',sizeof(desc));
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("WPrintPortEnum uLevel=%d\n",uLevel));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,"WrLeh") != 0) {
+ return False;
+ }
+ if (uLevel != 0 || strcmp(str2,"B9") != 0) {
+ return False;
+ }
+
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+ memset((char *)&desc,'\0',sizeof(desc));
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (init_package(&desc,1,0)) {
+ PACKS(&desc,"B13","lp0");
+ }
+
+ succnt = (desc.errcode == NERR_Success ? 1 : 0);
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0);
+ SSVAL(*rparam,4,succnt);
+ SSVAL(*rparam,6,1);
+
+ DEBUG(4,("WPrintPortEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+/****************************************************************************
+ List open sessions
+ ****************************************************************************/
+
+static bool api_RNetSessionEnum(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt,int mprcnt,
+ char **rdata,char **rparam,
+ int *rdata_len,int *rparam_len)
+
+{
+ char *str1 = get_safe_str_ptr(param,tpscnt,param,2);
+ char *str2 = skip_string(param,tpscnt,str1);
+ char *p = skip_string(param,tpscnt,str2);
+ int uLevel;
+ struct pack_desc desc;
+ int i;
+
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ WERROR werr;
+ NTSTATUS status;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct srvsvc_NetSessInfoCtr info_ctr;
+ uint32_t totalentries, resume_handle = 0;
+ uint32_t count = 0;
+
+ if (!str1 || !str2 || !p) {
+ return False;
+ }
+
+ ZERO_STRUCT(desc);
+
+ uLevel = get_safe_SVAL(param,tpscnt,p,0,-1);
+
+ DEBUG(3,("RNetSessionEnum uLevel=%d\n",uLevel));
+ DEBUG(7,("RNetSessionEnum req string=%s\n",str1));
+ DEBUG(7,("RNetSessionEnum ret string=%s\n",str2));
+
+ /* check it's a supported variant */
+ if (strcmp(str1,RAP_NetSessionEnum_REQ) != 0) {
+ return False;
+ }
+ if (uLevel != 2 || strcmp(str2,RAP_SESSION_INFO_L2) != 0) {
+ return False;
+ }
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_srvsvc,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RNetSessionEnum: could not connect to srvsvc: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ info_ctr.level = 1;
+ info_ctr.ctr.ctr1 = talloc_zero(talloc_tos(), struct srvsvc_NetSessCtr1);
+ if (info_ctr.ctr.ctr1 == NULL) {
+ desc.errcode = W_ERROR_V(WERR_NOT_ENOUGH_MEMORY);
+ goto out;
+ }
+
+ status = dcerpc_srvsvc_NetSessEnum(b, mem_ctx,
+ cli->srv_name_slash,
+ NULL, /* client */
+ NULL, /* user */
+ &info_ctr,
+ (uint32_t)-1, /* max_buffer */
+ &totalentries,
+ &resume_handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RNetSessionEnum: dcerpc_srvsvc_NetSessEnum failed: %s\n",
+ nt_errstr(status)));
+ desc.errcode = W_ERROR_V(ntstatus_to_werror(status));
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("RNetSessionEnum: dcerpc_srvsvc_NetSessEnum failed: %s\n",
+ win_errstr(werr)));
+ desc.errcode = W_ERROR_V(werr);
+ goto out;
+ }
+
+ count = info_ctr.ctr.ctr1->count;
+
+ out:
+ if (mdrcnt > 0) {
+ *rdata = smb_realloc_limit(*rdata,mdrcnt);
+ if (!*rdata) {
+ return False;
+ }
+ }
+
+ desc.base = *rdata;
+ desc.buflen = mdrcnt;
+ desc.format = str2;
+ if (!init_package(&desc, count,0)) {
+ return False;
+ }
+
+ for(i=0; i < count; i++) {
+ PACKS(&desc, "z", info_ctr.ctr.ctr1->array[i].client);
+ PACKS(&desc, "z", info_ctr.ctr.ctr1->array[i].user);
+ PACKI(&desc, "W", 1); /* num conns */
+ PACKI(&desc, "W", info_ctr.ctr.ctr1->array[i].num_open);
+ PACKI(&desc, "W", 1); /* num users */
+ PACKI(&desc, "D", 0); /* session time */
+ PACKI(&desc, "D", 0); /* idle time */
+ PACKI(&desc, "D", 0); /* flags */
+ PACKS(&desc, "z", "Unknown Client"); /* client type string */
+ }
+
+ *rdata_len = desc.usedlen;
+
+ *rparam_len = 8;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+ SSVALS(*rparam,0,desc.errcode);
+ SSVAL(*rparam,2,0); /* converter */
+ SSVAL(*rparam,4, count); /* count */
+
+ DEBUG(4,("RNetSessionEnum: errorcode %d\n",desc.errcode));
+
+ return True;
+}
+
+
+/****************************************************************************
+ The buffer was too small.
+ ****************************************************************************/
+
+static bool api_TooSmall(struct smbd_server_connection *sconn,
+ connection_struct *conn,uint64_t vuid, char *param, char *data,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len)
+{
+ *rparam_len = MIN(*rparam_len,mprcnt);
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_BufTooSmall);
+
+ DEBUG(3,("Supplied buffer too small in API command\n"));
+
+ return True;
+}
+
+/****************************************************************************
+ The request is not supported.
+ ****************************************************************************/
+
+static bool api_Unsupported(struct smbd_server_connection *sconn,
+ connection_struct *conn, uint64_t vuid,
+ char *param, int tpscnt,
+ char *data, int tdscnt,
+ int mdrcnt, int mprcnt,
+ char **rdata, char **rparam,
+ int *rdata_len, int *rparam_len)
+{
+ *rparam_len = 4;
+ *rparam = smb_realloc_limit(*rparam,*rparam_len);
+ if (!*rparam) {
+ return False;
+ }
+
+ *rdata_len = 0;
+
+ SSVAL(*rparam,0,NERR_notsupported);
+ SSVAL(*rparam,2,0); /* converter word */
+
+ DEBUG(3,("Unsupported API command\n"));
+
+ return True;
+}
+
+static const struct {
+ const char *name;
+ int id;
+ bool (*fn)(struct smbd_server_connection *sconn,
+ connection_struct *, uint64_t,
+ char *, int,
+ char *, int,
+ int,int,char **,char **,int *,int *);
+ bool auth_user; /* Deny anonymous access? */
+} api_commands[] = {
+ {
+ .name = "RNetShareEnum",
+ .id = RAP_WshareEnum,
+ .fn = api_RNetShareEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "RNetShareGetInfo",
+ .id = RAP_WshareGetInfo,
+ .fn = api_RNetShareGetInfo
+ },
+ {
+ .name = "RNetShareAdd",
+ .id = RAP_WshareAdd,
+ .fn = api_RNetShareAdd
+ },
+ {
+ .name = "RNetSessionEnum",
+ .id = RAP_WsessionEnum,
+ .fn = api_RNetSessionEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "RNetServerGetInfo",
+ .id = RAP_WserverGetInfo,
+ .fn = api_RNetServerGetInfo
+ },
+ {
+ .name = "RNetGroupEnum",
+ .id = RAP_WGroupEnum,
+ .fn = api_RNetGroupEnum, True
+ },
+ {
+ .name = "RNetGroupGetUsers",
+ .id = RAP_WGroupGetUsers,
+ .fn = api_RNetGroupGetUsers,
+ .auth_user = true},
+ {
+ .name = "RNetUserEnum",
+ .id = RAP_WUserEnum,
+ .fn = api_RNetUserEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "RNetUserGetInfo",
+ .id = RAP_WUserGetInfo,
+ .fn = api_RNetUserGetInfo
+ },
+ {
+ .name = "NetUserGetGroups",
+ .id = RAP_WUserGetGroups,
+ .fn = api_NetUserGetGroups
+ },
+ {
+ .name = "NetWkstaGetInfo",
+ .id = RAP_WWkstaGetInfo,
+ .fn = api_NetWkstaGetInfo
+ },
+ {
+ .name = "DosPrintQEnum",
+ .id = RAP_WPrintQEnum,
+ .fn = api_DosPrintQEnum,
+ .auth_user = true,
+ },
+ {
+ .name = "DosPrintQGetInfo",
+ .id = RAP_WPrintQGetInfo,
+ .fn = api_DosPrintQGetInfo
+ },
+ {
+ .name = "WPrintQueuePause",
+ .id = RAP_WPrintQPause,
+ .fn = api_WPrintQueueCtrl
+ },
+ {
+ .name = "WPrintQueueResume",
+ .id = RAP_WPrintQContinue,
+ .fn = api_WPrintQueueCtrl
+ },
+ {
+ .name = "WPrintJobEnumerate",
+ .id = RAP_WPrintJobEnum,
+ .fn = api_WPrintJobEnumerate
+ },
+ {
+ .name = "WPrintJobGetInfo",
+ .id = RAP_WPrintJobGetInfo,
+ .fn = api_WPrintJobGetInfo
+ },
+ {
+ .name = "RDosPrintJobDel",
+ .id = RAP_WPrintJobDel,
+ .fn = api_RDosPrintJobDel
+ },
+ {
+ .name = "RDosPrintJobPause",
+ .id = RAP_WPrintJobPause,
+ .fn = api_RDosPrintJobDel
+ },
+ {
+ .name = "RDosPrintJobResume",
+ .id = RAP_WPrintJobContinue,
+ .fn = api_RDosPrintJobDel
+ },
+ {
+ .name = "WPrintDestEnum",
+ .id = RAP_WPrintDestEnum,
+ .fn = api_WPrintDestEnum
+ },
+ {
+ .name = "WPrintDestGetInfo",
+ .id = RAP_WPrintDestGetInfo,
+ .fn = api_WPrintDestGetInfo
+ },
+ {
+ .name = "NetRemoteTOD",
+ .id = RAP_NetRemoteTOD,
+ .fn = api_NetRemoteTOD
+ },
+ {
+ .name = "WPrintQueuePurge",
+ .id = RAP_WPrintQPurge,
+ .fn = api_WPrintQueueCtrl
+ },
+ {
+ .name = "NetServerEnum2",
+ .id = RAP_NetServerEnum2,
+ .fn = api_RNetServerEnum2
+ }, /* anon OK */
+ {
+ .name = "NetServerEnum3",
+ .id = RAP_NetServerEnum3,
+ .fn = api_RNetServerEnum3
+ }, /* anon OK */
+ {
+ .name = "WAccessGetUserPerms",
+ .id = RAP_WAccessGetUserPerms,
+ .fn = api_WAccessGetUserPerms
+ },
+ {
+ .name = "WWkstaUserLogon",
+ .id = RAP_WWkstaUserLogon,
+ .fn = api_WWkstaUserLogon
+ },
+ {
+ .name = "PrintJobInfo",
+ .id = RAP_WPrintJobSetInfo,
+ .fn = api_PrintJobInfo
+ },
+ {
+ .name = "WPrintDriverEnum",
+ .id = RAP_WPrintDriverEnum,
+ .fn = api_WPrintDriverEnum
+ },
+ {
+ .name = "WPrintQProcEnum",
+ .id = RAP_WPrintQProcessorEnum,
+ .fn = api_WPrintQProcEnum
+ },
+ {
+ .name = "WPrintPortEnum",
+ .id = RAP_WPrintPortEnum,
+ .fn = api_WPrintPortEnum
+ },
+ {
+ .name = "SamOEMChangePassword",
+ .id = RAP_SamOEMChgPasswordUser2_P,
+ .fn = api_SamOEMChangePassword
+ }, /* anon OK */
+ {
+ .name = NULL,
+ .id = -1,
+ .fn = api_Unsupported}
+ /*
+ * The following RAP calls are not implemented by Samba:
+ * RAP_WFileEnum2 - anon not OK
+ */
+};
+
+
+/****************************************************************************
+ Handle remote api calls.
+****************************************************************************/
+
+void api_reply(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ char *data, char *params,
+ int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt)
+{
+ int api_command;
+ char *rdata = NULL;
+ char *rparam = NULL;
+ const char *name1 = NULL;
+ const char *name2 = NULL;
+ int rdata_len = 0;
+ int rparam_len = 0;
+ bool reply=False;
+ int i;
+
+ if (!params) {
+ DEBUG(0,("ERROR: NULL params in api_reply()\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (tpscnt < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ api_command = SVAL(params,0);
+ /* Is there a string at position params+2 ? */
+ if (skip_string(params,tpscnt,params+2)) {
+ name1 = params + 2;
+ } else {
+ name1 = "";
+ }
+ name2 = skip_string(params,tpscnt,params+2);
+ if (!name2) {
+ name2 = "";
+ }
+
+ DEBUG(3,("Got API command %d of form <%s> <%s> (tdscnt=%d,tpscnt=%d,mdrcnt=%d,mprcnt=%d)\n",
+ api_command,
+ name1,
+ name2,
+ tdscnt,tpscnt,mdrcnt,mprcnt));
+
+ for (i=0;api_commands[i].name;i++) {
+ if (api_commands[i].id == api_command && api_commands[i].fn) {
+ DEBUG(3,("Doing %s\n",api_commands[i].name));
+ break;
+ }
+ }
+
+ /* Check whether this api call can be done anonymously */
+
+ if (api_commands[i].auth_user && lp_restrict_anonymous()) {
+ struct auth_session_info *si = NULL;
+ NTSTATUS status;
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &si);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (security_session_user_level(si, NULL) < SECURITY_USER) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ rdata = (char *)SMB_MALLOC(1024);
+ if (rdata) {
+ memset(rdata,'\0',1024);
+ }
+
+ rparam = (char *)SMB_MALLOC(1024);
+ if (rparam) {
+ memset(rparam,'\0',1024);
+ }
+
+ if(!rdata || !rparam) {
+ DEBUG(0,("api_reply: malloc fail !\n"));
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ reply = api_commands[i].fn(req->sconn, conn,
+ vuid,
+ params,tpscnt, /* params + length */
+ data,tdscnt, /* data + length */
+ mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+
+
+ if (rdata_len > mdrcnt || rparam_len > mprcnt) {
+ reply = api_TooSmall(req->sconn,conn,vuid,params,data,
+ mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+ }
+
+ /* if we get False back then it's actually unsupported */
+ if (!reply) {
+ reply = api_Unsupported(req->sconn,conn,vuid,params,tpscnt,
+ data,
+ tdscnt,mdrcnt,mprcnt,
+ &rdata,&rparam,&rdata_len,&rparam_len);
+ }
+
+ /* If api_Unsupported returns false we can't return anything. */
+ if (reply) {
+ send_trans_reply(conn, req, rparam, rparam_len,
+ rdata, rdata_len, False);
+ }
+
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ return;
+}
diff --git a/source3/smbd/smb1_lanman.h b/source3/smbd/smb1_lanman.h
new file mode 100644
index 0000000..9e7ecb4
--- /dev/null
+++ b/source3/smbd/smb1_lanman.h
@@ -0,0 +1,28 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 2007.
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+void api_reply(connection_struct *conn, uint64_t vuid,
+ struct smb_request *req,
+ char *data, char *params,
+ int tdscnt, int tpscnt,
+ int mdrcnt, int mprcnt);
diff --git a/source3/smbd/smb1_message.c b/source3/smbd/smb1_message.c
new file mode 100644
index 0000000..ca7201e
--- /dev/null
+++ b/source3/smbd/smb1_message.c
@@ -0,0 +1,334 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB messaging
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ This file handles the messaging system calls for winpopup style
+ messages
+*/
+
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbprofile.h"
+#include "source3/lib/substitute.h"
+
+struct msg_state {
+ char *from;
+ char *to;
+ char *msg;
+};
+
+/****************************************************************************
+ Deliver the message.
+****************************************************************************/
+
+static void msg_deliver(struct msg_state *state)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *name = NULL;
+ int i;
+ int fd;
+ char *msg;
+ size_t len;
+ ssize_t sz;
+ fstring alpha_buf;
+ char *s;
+ mode_t mask;
+
+ if (! (*lp_message_command(frame, lp_sub))) {
+ DEBUG(1,("no messaging command specified\n"));
+ goto done;
+ }
+
+ /* put it in a temporary file */
+ name = talloc_asprintf(talloc_tos(), "%s/msg.XXXXXX", tmpdir());
+ if (!name) {
+ goto done;
+ }
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(name);
+ umask(mask);
+
+ if (fd == -1) {
+ DEBUG(1, ("can't open message file %s: %s\n", name,
+ strerror(errno)));
+ goto done;
+ }
+
+ /*
+ * Incoming message is in DOS codepage format. Convert to UNIX.
+ */
+
+ if (!convert_string_talloc(talloc_tos(), CH_DOS, CH_UNIX, state->msg,
+ talloc_get_size(state->msg), (void *)&msg,
+ &len)) {
+ DEBUG(3, ("Conversion failed, delivering message in DOS "
+ "codepage format\n"));
+ msg = state->msg;
+ }
+
+ for (i = 0; i < len; i++) {
+ if ((msg[i] == '\r') &&
+ (i < (len-1)) && (msg[i+1] == '\n')) {
+ continue;
+ }
+ sz = write(fd, &msg[i], 1);
+ if ( sz != 1 ) {
+ DEBUG(0, ("Write error to fd %d: %ld(%s)\n", fd,
+ (long)sz, strerror(errno)));
+ }
+ }
+
+ close(fd);
+
+ /* run the command */
+ s = lp_message_command(frame, lp_sub);
+ if (s == NULL) {
+ goto done;
+ }
+
+ alpha_strcpy(alpha_buf, state->from, NULL, sizeof(alpha_buf));
+
+ s = talloc_string_sub(talloc_tos(), s, "%f", alpha_buf);
+ if (s == NULL) {
+ goto done;
+ }
+
+ alpha_strcpy(alpha_buf, state->to, NULL, sizeof(alpha_buf));
+
+ s = talloc_string_sub(talloc_tos(), s, "%t", alpha_buf);
+ if (s == NULL) {
+ goto done;
+ }
+
+ s = talloc_sub_basic(talloc_tos(), get_current_username(),
+ get_current_user_info_domain(), s);
+ if (s == NULL) {
+ goto done;
+ }
+
+ s = talloc_string_sub(talloc_tos(), s, "%s", name);
+ if (s == NULL) {
+ goto done;
+ }
+ smbrun(s, NULL, NULL);
+
+ done:
+ TALLOC_FREE(frame);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sends.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sends(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct msg_state *state;
+ int len;
+ const uint8_t *msg;
+ const uint8_t *p;
+
+ START_PROFILE(SMBsends);
+
+ if (!(*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsends);
+ return;
+ }
+
+ state = talloc_zero(talloc_tos(), struct msg_state);
+
+ p = req->buf + 1;
+ p += srvstr_pull_req_talloc(
+ state, req, &state->from, p, STR_ASCII|STR_TERMINATE) + 1;
+ p += srvstr_pull_req_talloc(
+ state, req, &state->to, p, STR_ASCII|STR_TERMINATE) + 1;
+
+ msg = p;
+
+ len = SVAL(msg,0);
+ len = MIN(len, smbreq_bufrem(req, msg+2));
+
+ state->msg = talloc_array(state, char, len);
+
+ if (state->msg == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsends);
+ return;
+ }
+
+ memcpy(state->msg, msg+2, len);
+
+ msg_deliver(state);
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsends);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendstrt.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendstrt(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *p;
+
+ START_PROFILE(SMBsendstrt);
+
+ if (!(*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsendstrt);
+ return;
+ }
+
+ TALLOC_FREE(xconn->smb1.msg_state);
+
+ xconn->smb1.msg_state = talloc_zero(xconn, struct msg_state);
+
+ if (xconn->smb1.msg_state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsendstrt);
+ return;
+ }
+
+ p = req->buf+1;
+ p += srvstr_pull_req_talloc(
+ xconn->smb1.msg_state, req,
+ &xconn->smb1.msg_state->from, p,
+ STR_ASCII|STR_TERMINATE) + 1;
+ p += srvstr_pull_req_talloc(
+ xconn->smb1.msg_state, req,
+ &xconn->smb1.msg_state->to, p,
+ STR_ASCII|STR_TERMINATE) + 1;
+
+ DEBUG(3, ("SMBsendstrt (from %s to %s)\n",
+ xconn->smb1.msg_state->from,
+ xconn->smb1.msg_state->to));
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendstrt);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendtxt.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendtxt(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ int len;
+ const char *msg;
+ char *tmp;
+ size_t old_len;
+
+ START_PROFILE(SMBsendtxt);
+
+ if (! (*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ if ((xconn->smb1.msg_state == NULL) || (req->buflen < 3)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ msg = (const char *)req->buf + 1;
+
+ old_len = talloc_get_size(xconn->smb1.msg_state->msg);
+
+ len = MIN(SVAL(msg, 0), smbreq_bufrem(req, msg+2));
+
+ tmp = talloc_realloc(xconn->smb1.msg_state,
+ xconn->smb1.msg_state->msg,
+ char, old_len + len);
+
+ if (tmp == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsendtxt);
+ return;
+ }
+
+ xconn->smb1.msg_state->msg = tmp;
+
+ memcpy(&xconn->smb1.msg_state->msg[old_len], msg+2, len);
+
+ DEBUG( 3, ( "SMBsendtxt\n" ) );
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendtxt);
+ return;
+}
+
+/****************************************************************************
+ Reply to a sendend.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_sendend(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ START_PROFILE(SMBsendend);
+
+ if (! (*lp_message_command(talloc_tos(), lp_sub))) {
+ reply_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ END_PROFILE(SMBsendend);
+ return;
+ }
+
+ if (xconn->smb1.msg_state == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsendend);
+ return;
+ }
+
+ DEBUG(3,("SMBsendend\n"));
+
+ msg_deliver(xconn->smb1.msg_state);
+
+ TALLOC_FREE(xconn->smb1.msg_state);
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsendend);
+ return;
+}
diff --git a/source3/smbd/smb1_message.h b/source3/smbd/smb1_message.h
new file mode 100644
index 0000000..f54d7ae
--- /dev/null
+++ b/source3/smbd/smb1_message.h
@@ -0,0 +1,23 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB messaging
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+void reply_sends(struct smb_request *req);
+void reply_sendstrt(struct smb_request *req);
+void reply_sendtxt(struct smb_request *req);
+void reply_sendend(struct smb_request *req);
diff --git a/source3/smbd/smb1_negprot.c b/source3/smbd/smb1_negprot.c
new file mode 100644
index 0000000..7171c66
--- /dev/null
+++ b/source3/smbd/smb1_negprot.c
@@ -0,0 +1,706 @@
+/*
+ Unix SMB/CIFS implementation.
+ negprot reply code
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "serverid.h"
+#include "auth.h"
+#include "messages.h"
+#include "smbprofile.h"
+#include "auth/gensec/gensec.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+/*
+ * MS-CIFS, 2.2.4.52.2 SMB_COM_NEGOTIATE Response:
+ * If the server does not support any of the listed dialects, it MUST return a
+ * DialectIndex of 0XFFFF
+ */
+#define NO_PROTOCOL_CHOSEN 0xffff
+
+static void get_challenge(struct smbXsrv_connection *xconn, uint8_t buff[8])
+{
+ NTSTATUS nt_status;
+
+ /* We might be called more than once, multiple negprots are
+ * permitted */
+ if (xconn->smb1.negprot.auth_context) {
+ DEBUG(3, ("get challenge: is this a secondary negprot? "
+ "sconn->negprot.auth_context is non-NULL!\n"));
+ TALLOC_FREE(xconn->smb1.negprot.auth_context);
+ }
+
+ DEBUG(10, ("get challenge: creating negprot_global_auth_context\n"));
+ nt_status = make_auth4_context(
+ xconn, &xconn->smb1.negprot.auth_context);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("make_auth_context_subsystem returned %s\n",
+ nt_errstr(nt_status)));
+ smb_panic("cannot make_negprot_global_auth_context!");
+ }
+ DEBUG(10, ("get challenge: getting challenge\n"));
+ xconn->smb1.negprot.auth_context->get_ntlm_challenge(
+ xconn->smb1.negprot.auth_context, buff);
+}
+
+/****************************************************************************
+ Reply for the lanman 1.0 protocol.
+****************************************************************************/
+
+static NTSTATUS reply_lanman1(struct smb_request *req, uint16_t choice)
+{
+ int secword=0;
+ time_t t = time(NULL);
+ struct smbXsrv_connection *xconn = req->xconn;
+ uint16_t raw;
+ NTSTATUS status;
+
+ if (lp_async_smb_echo_handler()) {
+ raw = 0;
+ } else {
+ raw = (lp_read_raw()?1:0) | (lp_write_raw()?2:0);
+ }
+
+ xconn->smb1.negprot.encrypted_passwords = lp_encrypt_passwords();
+
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+ }
+
+ reply_smb1_outbuf(req, 13, xconn->smb1.negprot.encrypted_passwords?8:0);
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SSVAL(req->outbuf,smb_vwv1,secword);
+ /* Create a token value and add it to the outgoing packet. */
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ get_challenge(xconn, (uint8_t *)smb_buf(req->outbuf));
+ SSVAL(req->outbuf,smb_vwv11, 8);
+ }
+
+ status = smbXsrv_connection_init_tables(xconn, PROTOCOL_LANMAN1);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return status;
+ }
+
+ /* Reply, SMBlockread, SMBwritelock supported. */
+ SCVAL(req->outbuf,smb_flg, FLAG_REPLY|FLAG_SUPPORT_LOCKREAD);
+ SSVAL(req->outbuf,smb_vwv2, xconn->smb1.negprot.max_recv);
+ SSVAL(req->outbuf,smb_vwv3, lp_max_mux()); /* maxmux */
+ SSVAL(req->outbuf,smb_vwv4, 1);
+ SSVAL(req->outbuf,smb_vwv5, raw); /* tell redirector we support
+ readbraw writebraw (possibly) */
+ SIVAL(req->outbuf,smb_vwv6, getpid());
+ SSVAL(req->outbuf,smb_vwv10, set_server_zone_offset(t)/60);
+
+ srv_put_dos_date((char *)req->outbuf,smb_vwv8,t);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply for the lanman 2.0 protocol.
+****************************************************************************/
+
+static NTSTATUS reply_lanman2(struct smb_request *req, uint16_t choice)
+{
+ int secword=0;
+ time_t t = time(NULL);
+ struct smbXsrv_connection *xconn = req->xconn;
+ uint16_t raw;
+ NTSTATUS status;
+
+ if (lp_async_smb_echo_handler()) {
+ raw = 0;
+ } else {
+ raw = (lp_read_raw()?1:0) | (lp_write_raw()?2:0);
+ }
+
+ xconn->smb1.negprot.encrypted_passwords = lp_encrypt_passwords();
+
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+ }
+
+ reply_smb1_outbuf(req, 13, xconn->smb1.negprot.encrypted_passwords?8:0);
+
+ SSVAL(req->outbuf,smb_vwv0, choice);
+ SSVAL(req->outbuf,smb_vwv1, secword);
+ SIVAL(req->outbuf,smb_vwv6, getpid());
+
+ /* Create a token value and add it to the outgoing packet. */
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ get_challenge(xconn, (uint8_t *)smb_buf(req->outbuf));
+ SSVAL(req->outbuf,smb_vwv11, 8);
+ }
+
+ status = smbXsrv_connection_init_tables(xconn, PROTOCOL_LANMAN2);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return status;
+ }
+
+ /* Reply, SMBlockread, SMBwritelock supported. */
+ SCVAL(req->outbuf,smb_flg,FLAG_REPLY|FLAG_SUPPORT_LOCKREAD);
+ SSVAL(req->outbuf,smb_vwv2,xconn->smb1.negprot.max_recv);
+ SSVAL(req->outbuf,smb_vwv3,lp_max_mux());
+ SSVAL(req->outbuf,smb_vwv4,1);
+ SSVAL(req->outbuf,smb_vwv5,raw); /* readbraw and/or writebraw */
+ SSVAL(req->outbuf,smb_vwv10, set_server_zone_offset(t)/60);
+ srv_put_dos_date((char *)req->outbuf,smb_vwv8,t);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply for the nt protocol.
+****************************************************************************/
+
+static NTSTATUS reply_nt1(struct smb_request *req, uint16_t choice)
+{
+ /* dual names + lock_and_read + nt SMBs + remote API calls */
+ int capabilities = CAP_NT_FIND|CAP_LOCK_AND_READ|
+ CAP_LEVEL_II_OPLOCKS;
+
+ int secword=0;
+ bool negotiate_spnego = False;
+ struct timespec ts;
+ ssize_t ret;
+ struct smbXsrv_connection *xconn = req->xconn;
+ bool signing_desired = false;
+ bool signing_required = false;
+ NTSTATUS status;
+
+ xconn->smb1.negprot.encrypted_passwords = lp_encrypt_passwords();
+
+ /* Check the flags field to see if this is Vista.
+ WinXP sets it and Vista does not. But we have to
+ distinguish from NT which doesn't set it either. */
+
+ if ( (req->flags2 & FLAGS2_EXTENDED_SECURITY) &&
+ ((req->flags2 & FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED) == 0) )
+ {
+ if ((get_remote_arch() != RA_SAMBA) &&
+ (get_remote_arch() != RA_CIFSFS)) {
+ set_remote_arch( RA_VISTA );
+ }
+ }
+
+ reply_smb1_outbuf(req,17,0);
+
+ /* do spnego in user level security if the client
+ supports it and we can do encrypted passwords */
+
+ if (xconn->smb1.negprot.encrypted_passwords &&
+ (req->flags2 & FLAGS2_EXTENDED_SECURITY)) {
+ negotiate_spnego = True;
+ capabilities |= CAP_EXTENDED_SECURITY;
+ add_to_common_flags2(FLAGS2_EXTENDED_SECURITY);
+ /* Ensure FLAGS2_EXTENDED_SECURITY gets set in this reply
+ (already partially constructed. */
+ SSVAL(req->outbuf, smb_flg2,
+ req->flags2 | FLAGS2_EXTENDED_SECURITY);
+ }
+
+ capabilities |= CAP_NT_SMBS|CAP_RPC_REMOTE_APIS;
+
+ if (lp_unicode()) {
+ capabilities |= CAP_UNICODE;
+ }
+
+ if (lp_smb1_unix_extensions()) {
+ capabilities |= CAP_UNIX;
+ }
+
+ if (lp_large_readwrite())
+ capabilities |= CAP_LARGE_READX|CAP_LARGE_WRITEX|CAP_W2K_SMBS;
+
+ capabilities |= CAP_LARGE_FILES;
+
+ if (!lp_async_smb_echo_handler() && lp_read_raw() && lp_write_raw())
+ capabilities |= CAP_RAW_MODE;
+
+ if (lp_nt_status_support())
+ capabilities |= CAP_STATUS32;
+
+ if (lp_host_msdfs())
+ capabilities |= CAP_DFS;
+
+ secword |= NEGOTIATE_SECURITY_USER_LEVEL;
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ secword |= NEGOTIATE_SECURITY_CHALLENGE_RESPONSE;
+ }
+
+ signing_desired = smb1_signing_is_desired(xconn->smb1.signing_state);
+ signing_required = smb1_signing_is_mandatory(xconn->smb1.signing_state);
+
+ if (signing_desired) {
+ secword |= NEGOTIATE_SECURITY_SIGNATURES_ENABLED;
+ /* No raw mode with smb signing. */
+ capabilities &= ~CAP_RAW_MODE;
+ if (signing_required) {
+ secword |=NEGOTIATE_SECURITY_SIGNATURES_REQUIRED;
+ }
+ }
+
+ SSVAL(req->outbuf,smb_vwv0,choice);
+ SCVAL(req->outbuf,smb_vwv1,secword);
+
+ status = smbXsrv_connection_init_tables(xconn, PROTOCOL_NT1);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return status;
+ }
+
+ SSVAL(req->outbuf,smb_vwv1+1, lp_max_mux()); /* maxmpx */
+ SSVAL(req->outbuf,smb_vwv2+1, 1); /* num vcs */
+ SIVAL(req->outbuf,smb_vwv3+1,
+ xconn->smb1.negprot.max_recv); /* max buffer. LOTS! */
+ SIVAL(req->outbuf,smb_vwv5+1, 0x10000); /* raw size. full 64k */
+ SIVAL(req->outbuf,smb_vwv7+1, getpid()); /* session key */
+ SIVAL(req->outbuf,smb_vwv9+1, capabilities); /* capabilities */
+ clock_gettime(CLOCK_REALTIME,&ts);
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER,(char *)req->outbuf+smb_vwv11+1,&ts);
+ SSVALS(req->outbuf,smb_vwv15+1,set_server_zone_offset(ts.tv_sec)/60);
+
+ if (!negotiate_spnego) {
+ /* Create a token value and add it to the outgoing packet. */
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ uint8_t chal[8];
+ /* note that we do not send a challenge at all if
+ we are using plaintext */
+ get_challenge(xconn, chal);
+ ret = message_push_blob(
+ &req->outbuf, data_blob_const(chal, sizeof(chal)));
+ if (ret == -1) {
+ DEBUG(0, ("Could not push challenge\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ SCVAL(req->outbuf, smb_vwv16+1, ret);
+ }
+ ret = message_push_string(&req->outbuf, lp_workgroup(),
+ STR_UNICODE|STR_TERMINATE
+ |STR_NOALIGN);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push workgroup string\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = message_push_string(&req->outbuf, lp_netbios_name(),
+ STR_UNICODE|STR_TERMINATE
+ |STR_NOALIGN);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push netbios name string\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ DEBUG(3,("not using SPNEGO\n"));
+ } else {
+ DATA_BLOB spnego_blob = negprot_spnego(req, xconn);
+
+ if (spnego_blob.data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = message_push_blob(&req->outbuf, spnego_blob);
+ if (ret == -1) {
+ DEBUG(0, ("Could not push spnego blob\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ data_blob_free(&spnego_blob);
+
+ SCVAL(req->outbuf,smb_vwv16+1, 0);
+ DEBUG(3,("using SPNEGO\n"));
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* these are the protocol lists used for auto architecture detection:
+
+WinNT 3.51:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [MICROSOFT NETWORKS 1.03]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Win95:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [MICROSOFT NETWORKS 1.03]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Win2K:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+
+Vista:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [LANMAN1.0]
+protocol [Windows for Workgroups 3.1a]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+protocol [NT LM 0.12]
+protocol [SMB 2.001]
+
+OS/2:
+protocol [PC NETWORK PROGRAM 1.0]
+protocol [XENIX CORE]
+protocol [LANMAN1.0]
+protocol [LM1.2X002]
+protocol [LANMAN2.1]
+
+OSX:
+protocol [NT LM 0.12]
+protocol [SMB 2.002]
+protocol [SMB 2.???]
+*/
+
+/*
+ * Modified to recognize the architecture of the remote machine better.
+ *
+ * This appears to be the matrix of which protocol is used by which
+ * product.
+ Protocol WfWg Win95 WinNT Win2K OS/2 Vista OSX
+ PC NETWORK PROGRAM 1.0 1 1 1 1 1 1
+ XENIX CORE 2 2
+ MICROSOFT NETWORKS 3.0 2 2
+ DOS LM1.2X002 3 3
+ MICROSOFT NETWORKS 1.03 3
+ DOS LANMAN2.1 4 4
+ LANMAN1.0 4 2 3 2
+ Windows for Workgroups 3.1a 5 5 5 3 3
+ LM1.2X002 6 4 4 4
+ LANMAN2.1 7 5 5 5
+ NT LM 0.12 6 8 6 6 6 1
+ SMB 2.001 7
+ SMB 2.002 2
+ SMB 2.??? 3
+ *
+ * tim@fsg.com 09/29/95
+ * Win2K added by matty 17/7/99
+ */
+
+#define PROT_PC_NETWORK_PROGRAM_1_0 0x0001
+#define PROT_XENIX_CORE 0x0002
+#define PROT_MICROSOFT_NETWORKS_3_0 0x0004
+#define PROT_DOS_LM1_2X002 0x0008
+#define PROT_MICROSOFT_NETWORKS_1_03 0x0010
+#define PROT_DOS_LANMAN2_1 0x0020
+#define PROT_LANMAN1_0 0x0040
+#define PROT_WFWG 0x0080
+#define PROT_LM1_2X002 0x0100
+#define PROT_LANMAN2_1 0x0200
+#define PROT_NT_LM_0_12 0x0400
+#define PROT_SMB_2_001 0x0800
+#define PROT_SMB_2_002 0x1000
+#define PROT_SMB_2_FF 0x2000
+#define PROT_SAMBA 0x4000
+#define PROT_POSIX_2 0x8000
+
+#define ARCH_WFWG ( PROT_PC_NETWORK_PROGRAM_1_0 | PROT_MICROSOFT_NETWORKS_3_0 | \
+ PROT_DOS_LM1_2X002 | PROT_DOS_LANMAN2_1 | PROT_WFWG )
+#define ARCH_WIN95 ( ARCH_WFWG | PROT_NT_LM_0_12 )
+#define ARCH_WINNT ( PROT_PC_NETWORK_PROGRAM_1_0 | PROT_XENIX_CORE | \
+ PROT_MICROSOFT_NETWORKS_1_03 | PROT_LANMAN1_0 | PROT_WFWG | \
+ PROT_LM1_2X002 | PROT_LANMAN2_1 | PROT_NT_LM_0_12 )
+#define ARCH_WIN2K ( ARCH_WINNT & ~(PROT_XENIX_CORE | PROT_MICROSOFT_NETWORKS_1_03) )
+#define ARCH_OS2 ( ARCH_WINNT & ~(PROT_MICROSOFT_NETWORKS_1_03 | PROT_WFWG) )
+#define ARCH_VISTA ( ARCH_WIN2K | PROT_SMB_2_001 )
+#define ARCH_SAMBA ( PROT_SAMBA )
+#define ARCH_CIFSFS ( PROT_POSIX_2 )
+#define ARCH_OSX ( PROT_NT_LM_0_12 | PROT_SMB_2_002 | PROT_SMB_2_FF )
+
+/* List of supported protocols, most desired first */
+static const struct {
+ const char *proto_name;
+ const char *short_name;
+ NTSTATUS (*proto_reply_fn)(struct smb_request *req, uint16_t choice);
+ int protocol_level;
+} supported_protocols[] = {
+ {"SMB 2.???", "SMB2_FF", reply_smb20ff, PROTOCOL_SMB2_10},
+ {"SMB 2.002", "SMB2_02", reply_smb2002, PROTOCOL_SMB2_02},
+ {"NT LANMAN 1.0", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"NT LM 0.12", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"POSIX 2", "NT1", reply_nt1, PROTOCOL_NT1},
+ {"LANMAN2.1", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"Samba", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"DOS LM1.2X002", "LANMAN2", reply_lanman2, PROTOCOL_LANMAN2},
+ {"LANMAN1.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
+ {"MICROSOFT NETWORKS 3.0", "LANMAN1", reply_lanman1, PROTOCOL_LANMAN1},
+ {NULL,NULL,NULL,0},
+};
+
+/****************************************************************************
+ Reply to a negprot.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_negprot(struct smb_request *req)
+{
+ size_t choice = 0;
+ int chosen_level = -1;
+ bool choice_set = false;
+ int protocol;
+ const char *p;
+ int protocols = 0;
+ int num_cliprotos;
+ char **cliprotos;
+ size_t i;
+ size_t converted_size;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ bool signing_required = true;
+ int max_proto;
+ int min_proto;
+ NTSTATUS status;
+
+ START_PROFILE(SMBnegprot);
+
+ if (xconn->smb1.negprot.done) {
+ END_PROFILE(SMBnegprot);
+ exit_server_cleanly("multiple negprot's are not permitted");
+ }
+
+ if (req->buflen == 0) {
+ DEBUG(0, ("negprot got no protocols\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ if (req->buf[req->buflen-1] != '\0') {
+ DEBUG(0, ("negprot protocols not 0-terminated\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ p = (const char *)req->buf + 1;
+
+ num_cliprotos = 0;
+ cliprotos = NULL;
+
+ while (smbreq_bufrem(req, p) > 0) {
+
+ char **tmp;
+
+ tmp = talloc_realloc(talloc_tos(), cliprotos, char *,
+ num_cliprotos+1);
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ cliprotos = tmp;
+
+ if (!pull_ascii_talloc(cliprotos, &cliprotos[num_cliprotos], p,
+ &converted_size)) {
+ DEBUG(0, ("pull_ascii_talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return;
+ }
+
+ DEBUG(3, ("Requested protocol [%s]\n",
+ cliprotos[num_cliprotos]));
+
+ num_cliprotos += 1;
+ p += strlen(p) + 2;
+ }
+
+ for (i=0; i<num_cliprotos; i++) {
+ if (strcsequal(cliprotos[i], "Windows for Workgroups 3.1a")) {
+ protocols |= PROT_WFWG;
+ } else if (strcsequal(cliprotos[i], "DOS LM1.2X002")) {
+ protocols |= PROT_DOS_LM1_2X002;
+ } else if (strcsequal(cliprotos[i], "DOS LANMAN2.1")) {
+ protocols |= PROT_DOS_LANMAN2_1;
+ } else if (strcsequal(cliprotos[i], "LANMAN1.0")) {
+ protocols |= PROT_LANMAN1_0;
+ } else if (strcsequal(cliprotos[i], "NT LM 0.12")) {
+ protocols |= PROT_NT_LM_0_12;
+ } else if (strcsequal(cliprotos[i], "SMB 2.001")) {
+ protocols |= PROT_SMB_2_001;
+ } else if (strcsequal(cliprotos[i], "SMB 2.002")) {
+ protocols |= PROT_SMB_2_002;
+ } else if (strcsequal(cliprotos[i], "SMB 2.???")) {
+ protocols |= PROT_SMB_2_FF;
+ } else if (strcsequal(cliprotos[i], "LANMAN2.1")) {
+ protocols |= PROT_LANMAN2_1;
+ } else if (strcsequal(cliprotos[i], "LM1.2X002")) {
+ protocols |= PROT_LM1_2X002;
+ } else if (strcsequal(cliprotos[i], "MICROSOFT NETWORKS 1.03")) {
+ protocols |= PROT_MICROSOFT_NETWORKS_1_03;
+ } else if (strcsequal(cliprotos[i], "MICROSOFT NETWORKS 3.0")) {
+ protocols |= PROT_MICROSOFT_NETWORKS_3_0;
+ } else if (strcsequal(cliprotos[i], "PC NETWORK PROGRAM 1.0")) {
+ protocols |= PROT_PC_NETWORK_PROGRAM_1_0;
+ } else if (strcsequal(cliprotos[i], "XENIX CORE")) {
+ protocols |= PROT_XENIX_CORE;
+ } else if (strcsequal(cliprotos[i], "Samba")) {
+ protocols = PROT_SAMBA;
+ break;
+ } else if (strcsequal(cliprotos[i], "POSIX 2")) {
+ protocols = PROT_POSIX_2;
+ break;
+ }
+ }
+
+ switch ( protocols ) {
+ /* Old CIFSFS can send one arch only, NT LM 0.12. */
+ case PROT_NT_LM_0_12:
+ case ARCH_CIFSFS:
+ set_remote_arch(RA_CIFSFS);
+ break;
+ case ARCH_SAMBA:
+ set_remote_arch(RA_SAMBA);
+ break;
+ case ARCH_WFWG:
+ set_remote_arch(RA_WFWG);
+ break;
+ case ARCH_WIN95:
+ set_remote_arch(RA_WIN95);
+ break;
+ case ARCH_WINNT:
+ set_remote_arch(RA_WINNT);
+ break;
+ case ARCH_WIN2K:
+ set_remote_arch(RA_WIN2K);
+ break;
+ case ARCH_VISTA:
+ set_remote_arch(RA_VISTA);
+ break;
+ case ARCH_OS2:
+ set_remote_arch(RA_OS2);
+ break;
+ case ARCH_OSX:
+ set_remote_arch(RA_OSX);
+ break;
+ default:
+ set_remote_arch(RA_UNKNOWN);
+ break;
+ }
+
+ /* possibly reload - change of architecture */
+ reload_services(sconn, conn_snum_used, true);
+
+ /*
+ * Anything higher than PROTOCOL_SMB2_10 still
+ * needs to go via "SMB 2.???", which is marked
+ * as PROTOCOL_SMB2_10.
+ *
+ * The real negotiation happens via reply_smb20ff()
+ * using SMB2 Negotiation.
+ */
+ max_proto = lp_server_max_protocol();
+ if (max_proto > PROTOCOL_SMB2_10) {
+ max_proto = PROTOCOL_SMB2_10;
+ }
+ min_proto = lp_server_min_protocol();
+ if (min_proto > PROTOCOL_SMB2_10) {
+ min_proto = PROTOCOL_SMB2_10;
+ }
+
+ /* Check for protocols, most desirable first */
+ for (protocol = 0; supported_protocols[protocol].proto_name; protocol++) {
+ i = 0;
+ if ((supported_protocols[protocol].protocol_level <= max_proto) &&
+ (supported_protocols[protocol].protocol_level >= min_proto))
+ while (i < num_cliprotos) {
+ if (strequal(cliprotos[i],supported_protocols[protocol].proto_name)) {
+ choice = i;
+ chosen_level = supported_protocols[protocol].protocol_level;
+ choice_set = true;
+ }
+ i++;
+ }
+ if (choice_set) {
+ break;
+ }
+ }
+
+ if (!choice_set) {
+ bool ok;
+
+ DBG_NOTICE("No protocol supported !\n");
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf, smb_vwv0, NO_PROTOCOL_CHOSEN);
+
+ ok = smb1_srv_send(xconn, (char *)req->outbuf, false, 0, false);
+ if (!ok) {
+ DBG_NOTICE("smb1_srv_send failed\n");
+ }
+ exit_server_cleanly("no protocol supported\n");
+ }
+
+ set_remote_proto(supported_protocols[protocol].short_name);
+ reload_services(sconn, conn_snum_used, true);
+ status = supported_protocols[protocol].proto_reply_fn(req, choice);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("negprot function failed\n");
+ }
+
+ DEBUG(3,("Selected protocol %s\n",supported_protocols[protocol].proto_name));
+
+ DBG_INFO("negprot index=%zu\n", choice);
+
+ xconn->smb1.negprot.done = true;
+
+ /* We always have xconn->smb1.signing_state also for >= SMB2_02 */
+ signing_required = smb1_signing_is_mandatory(xconn->smb1.signing_state);
+ if (signing_required && (chosen_level < PROTOCOL_NT1)) {
+ exit_server_cleanly("SMB signing is required and "
+ "client negotiated a downlevel protocol");
+ }
+
+ TALLOC_FREE(cliprotos);
+
+ if (lp_async_smb_echo_handler() && (chosen_level < PROTOCOL_SMB2_02) &&
+ !fork_echo_handler(xconn)) {
+ exit_server("Failed to fork echo handler");
+ }
+
+ END_PROFILE(SMBnegprot);
+ return;
+}
diff --git a/source3/smbd/smb1_negprot.h b/source3/smbd/smb1_negprot.h
new file mode 100644
index 0000000..2b23a04
--- /dev/null
+++ b/source3/smbd/smb1_negprot.h
@@ -0,0 +1,21 @@
+/*
+ Unix SMB/CIFS implementation.
+ negprot reply code
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+void reply_negprot(struct smb_request *req);
diff --git a/source3/smbd/smb1_nttrans.c b/source3/smbd/smb1_nttrans.c
new file mode 100644
index 0000000..104d3c0
--- /dev/null
+++ b/source3/smbd/smb1_nttrans.c
@@ -0,0 +1,2718 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT transaction handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "libsmb/libsmb.h"
+#include "lib/util_ea.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+static char *nttrans_realloc(char **ptr, size_t size)
+{
+ if (ptr==NULL) {
+ smb_panic("nttrans_realloc() called with NULL ptr");
+ }
+
+ *ptr = (char *)SMB_REALLOC(*ptr, size);
+ if(*ptr == NULL) {
+ return NULL;
+ }
+ memset(*ptr,'\0',size);
+ return *ptr;
+}
+
+/****************************************************************************
+ Send the required number of replies back.
+ We assume all fields other than the data fields are
+ set correctly for the type of call.
+ HACK ! Always assumes smb_setup field is zero.
+****************************************************************************/
+
+static void send_nt_replies(connection_struct *conn,
+ struct smb_request *req, NTSTATUS nt_error,
+ char *params, int paramsize,
+ char *pdata, int datasize)
+{
+ int data_to_send = datasize;
+ int params_to_send = paramsize;
+ int useable_space;
+ char *pp = params;
+ char *pd = pdata;
+ int params_sent_thistime, data_sent_thistime, total_sent_thistime;
+ int alignment_offset = 1;
+ int data_alignment_offset = 0;
+ struct smbXsrv_connection *xconn = req->xconn;
+ int max_send = xconn->smb1.sessions.max_send;
+
+ /*
+ * If there genuinely are no parameters or data to send just send
+ * the empty packet.
+ */
+
+ if(params_to_send == 0 && data_to_send == 0) {
+ reply_smb1_outbuf(req, 18, 0);
+ if (NT_STATUS_V(nt_error)) {
+ error_packet_set((char *)req->outbuf,
+ 0, 0, nt_error,
+ __LINE__,__FILE__);
+ }
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_nt_replies: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req->outbuf);
+ return;
+ }
+
+ /*
+ * When sending params and data ensure that both are nicely aligned.
+ * Only do this alignment when there is also data to send - else
+ * can cause NT redirector problems.
+ */
+
+ if (((params_to_send % 4) != 0) && (data_to_send != 0)) {
+ data_alignment_offset = 4 - (params_to_send % 4);
+ }
+
+ /*
+ * Space is bufsize minus Netbios over TCP header minus SMB header.
+ * The alignment_offset is to align the param bytes on a four byte
+ * boundary (2 bytes for data len, one byte pad).
+ * NT needs this to work correctly.
+ */
+
+ useable_space = max_send - (smb_size
+ + 2 * 18 /* wct */
+ + alignment_offset
+ + data_alignment_offset);
+
+ if (useable_space < 0) {
+ char *msg = talloc_asprintf(
+ talloc_tos(),
+ "send_nt_replies failed sanity useable_space = %d!!!",
+ useable_space);
+ DEBUG(0, ("%s\n", msg));
+ exit_server_cleanly(msg);
+ }
+
+ while (params_to_send || data_to_send) {
+
+ /*
+ * Calculate whether we will totally or partially fill this packet.
+ */
+
+ total_sent_thistime = params_to_send + data_to_send;
+
+ /*
+ * We can never send more than useable_space.
+ */
+
+ total_sent_thistime = MIN(total_sent_thistime, useable_space);
+
+ reply_smb1_outbuf(req, 18,
+ total_sent_thistime + alignment_offset
+ + data_alignment_offset);
+
+ /*
+ * Set total params and data to be sent.
+ */
+
+ SIVAL(req->outbuf,smb_ntr_TotalParameterCount,paramsize);
+ SIVAL(req->outbuf,smb_ntr_TotalDataCount,datasize);
+
+ /*
+ * Calculate how many parameters and data we can fit into
+ * this packet. Parameters get precedence.
+ */
+
+ params_sent_thistime = MIN(params_to_send,useable_space);
+ data_sent_thistime = useable_space - params_sent_thistime;
+ data_sent_thistime = MIN(data_sent_thistime,data_to_send);
+
+ SIVAL(req->outbuf, smb_ntr_ParameterCount,
+ params_sent_thistime);
+
+ if(params_sent_thistime == 0) {
+ SIVAL(req->outbuf,smb_ntr_ParameterOffset,0);
+ SIVAL(req->outbuf,smb_ntr_ParameterDisplacement,0);
+ } else {
+ /*
+ * smb_ntr_ParameterOffset is the offset from the start of the SMB header to the
+ * parameter bytes, however the first 4 bytes of outbuf are
+ * the Netbios over TCP header. Thus use smb_base() to subtract
+ * them from the calculation.
+ */
+
+ SIVAL(req->outbuf,smb_ntr_ParameterOffset,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf)));
+ /*
+ * Absolute displacement of param bytes sent in this packet.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_ParameterDisplacement,
+ pp - params);
+ }
+
+ /*
+ * Deal with the data portion.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_DataCount, data_sent_thistime);
+
+ if(data_sent_thistime == 0) {
+ SIVAL(req->outbuf,smb_ntr_DataOffset,0);
+ SIVAL(req->outbuf,smb_ntr_DataDisplacement, 0);
+ } else {
+ /*
+ * The offset of the data bytes is the offset of the
+ * parameter bytes plus the number of parameters being sent this time.
+ */
+
+ SIVAL(req->outbuf, smb_ntr_DataOffset,
+ ((smb_buf(req->outbuf)+alignment_offset) -
+ smb_base(req->outbuf))
+ + params_sent_thistime + data_alignment_offset);
+ SIVAL(req->outbuf,smb_ntr_DataDisplacement, pd - pdata);
+ }
+
+ /*
+ * Copy the param bytes into the packet.
+ */
+
+ if(params_sent_thistime) {
+ if (alignment_offset != 0) {
+ memset(smb_buf(req->outbuf), 0,
+ alignment_offset);
+ }
+ memcpy((smb_buf(req->outbuf)+alignment_offset), pp,
+ params_sent_thistime);
+ }
+
+ /*
+ * Copy in the data bytes
+ */
+
+ if(data_sent_thistime) {
+ if (data_alignment_offset != 0) {
+ memset((smb_buf(req->outbuf)+alignment_offset+
+ params_sent_thistime), 0,
+ data_alignment_offset);
+ }
+ memcpy(smb_buf(req->outbuf)+alignment_offset
+ +params_sent_thistime+data_alignment_offset,
+ pd,data_sent_thistime);
+ }
+
+ DEBUG(9,("nt_rep: params_sent_thistime = %d, data_sent_thistime = %d, useable_space = %d\n",
+ params_sent_thistime, data_sent_thistime, useable_space));
+ DEBUG(9,("nt_rep: params_to_send = %d, data_to_send = %d, paramsize = %d, datasize = %d\n",
+ params_to_send, data_to_send, paramsize, datasize));
+
+ if (NT_STATUS_V(nt_error)) {
+ error_packet_set((char *)req->outbuf,
+ 0, 0, nt_error,
+ __LINE__,__FILE__);
+ }
+
+ /* Send the packet */
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_nt_replies: smb1_srv_send failed.");
+ }
+
+ TALLOC_FREE(req->outbuf);
+
+ pp += params_sent_thistime;
+ pd += data_sent_thistime;
+
+ params_to_send -= params_sent_thistime;
+ data_to_send -= data_sent_thistime;
+
+ /*
+ * Sanity check
+ */
+
+ if(params_to_send < 0 || data_to_send < 0) {
+ DEBUG(0,("send_nt_replies failed sanity check pts = %d, dts = %d\n!!!",
+ params_to_send, data_to_send));
+ exit_server_cleanly("send_nt_replies: internal error");
+ }
+ }
+}
+
+/****************************************************************************
+ Reply to an NT create and X call on a pipe
+****************************************************************************/
+
+static void nt_open_pipe(char *fname, connection_struct *conn,
+ struct smb_request *req, uint16_t *ppnum)
+{
+ files_struct *fsp;
+ NTSTATUS status;
+
+ DEBUG(4,("nt_open_pipe: Opening pipe %s.\n", fname));
+
+ /* Strip \\ off the name if present. */
+ while (fname[0] == '\\') {
+ fname++;
+ }
+
+ status = open_np_file(req, fname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ *ppnum = fsp->fnum;
+ return;
+}
+
+/****************************************************************************
+ Reply to an NT create and X call for pipes.
+****************************************************************************/
+
+static void do_ntcreate_pipe_open(connection_struct *conn,
+ struct smb_request *req)
+{
+ char *fname = NULL;
+ uint16_t pnum = FNUM_FIELD_INVALID;
+ char *p = NULL;
+ uint32_t flags = IVAL(req->vwv+3, 1);
+ TALLOC_CTX *ctx = talloc_tos();
+
+ srvstr_pull_req_talloc(ctx, req, &fname, req->buf, STR_TERMINATE);
+
+ if (!fname) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+ nt_open_pipe(fname, conn, req, &pnum);
+
+ if (req->outbuf) {
+ /* error reply */
+ return;
+ }
+
+ /*
+ * Deal with pipe return.
+ */
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* This is very strange. We
+ * return 50 words, but only set
+ * the wcnt to 42 ? It's definitely
+ * what happens on the wire....
+ */
+ reply_smb1_outbuf(req, 50, 0);
+ SCVAL(req->outbuf,smb_wct,42);
+ } else {
+ reply_smb1_outbuf(req, 34, 0);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ p = (char *)req->outbuf + smb_vwv2;
+ p++;
+ SSVAL(p,0,pnum);
+ p += 2;
+ SIVAL(p,0,FILE_WAS_OPENED);
+ p += 4;
+ p += 32;
+ SIVAL(p,0,FILE_ATTRIBUTE_NORMAL); /* File Attributes. */
+ p += 20;
+ /* File type. */
+ SSVAL(p,0,FILE_TYPE_MESSAGE_MODE_PIPE);
+ /* Device state. */
+ SSVAL(p,2, 0x5FF); /* ? */
+ p += 4;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ p += 25;
+ SIVAL(p,0,FILE_GENERIC_ALL);
+ /*
+ * For pipes W2K3 seems to return
+ * 0x12019B next.
+ * This is ((FILE_GENERIC_READ|FILE_GENERIC_WRITE) & ~FILE_APPEND_DATA)
+ */
+ SIVAL(p,4,(FILE_GENERIC_READ|FILE_GENERIC_WRITE)&~FILE_APPEND_DATA);
+ }
+
+ DEBUG(5,("do_ntcreate_pipe_open: open pipe = %s\n", fname));
+}
+
+struct case_semantics_state {
+ connection_struct *conn;
+ bool case_sensitive;
+ bool case_preserve;
+ bool short_case_preserve;
+};
+
+/****************************************************************************
+ Restore case semantics.
+****************************************************************************/
+
+static int restore_case_semantics(struct case_semantics_state *state)
+{
+ state->conn->case_sensitive = state->case_sensitive;
+ state->conn->case_preserve = state->case_preserve;
+ state->conn->short_case_preserve = state->short_case_preserve;
+ return 0;
+}
+
+/****************************************************************************
+ Save case semantics.
+****************************************************************************/
+
+static struct case_semantics_state *set_posix_case_semantics(TALLOC_CTX *mem_ctx,
+ connection_struct *conn)
+{
+ struct case_semantics_state *result;
+
+ if (!(result = talloc(mem_ctx, struct case_semantics_state))) {
+ return NULL;
+ }
+
+ result->conn = conn;
+ result->case_sensitive = conn->case_sensitive;
+ result->case_preserve = conn->case_preserve;
+ result->short_case_preserve = conn->short_case_preserve;
+
+ /* Set to POSIX. */
+ conn->case_sensitive = True;
+ conn->case_preserve = True;
+ conn->short_case_preserve = True;
+
+ talloc_set_destructor(result, restore_case_semantics);
+
+ return result;
+}
+
+/*
+ * Calculate the full path name given a relative fid.
+ */
+static NTSTATUS get_relative_fid_filename(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t root_dir_fid,
+ char *path,
+ char **path_out)
+{
+ struct files_struct *dir_fsp = NULL;
+ char *new_path = NULL;
+
+ if (root_dir_fid == 0 || path == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ dir_fsp = file_fsp(req, root_dir_fid);
+ if (dir_fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (fsp_is_alternate_stream(dir_fsp)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!dir_fsp->fsp_flags.is_directory) {
+ /*
+ * Check to see if this is a mac fork of some kind.
+ */
+ if (conn->fs_capabilities & FILE_NAMED_STREAMS) {
+ char *stream = NULL;
+
+ stream = strchr_m(path, ':');
+ if (stream != NULL) {
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ }
+
+ /*
+ * We need to handle the case when we get a relative open
+ * relative to a file and the pathname is blank - this is a
+ * reopen! (hint from demyn plantenberg)
+ */
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (ISDOT(dir_fsp->fsp_name->base_name)) {
+ /*
+ * We're at the toplevel dir, the final file name
+ * must not contain ./, as this is filtered out
+ * normally by srvstr_get_path and unix_convert
+ * explicitly rejects paths containing ./.
+ */
+ new_path = talloc_strdup(talloc_tos(), path);
+ } else {
+ /*
+ * Copy in the base directory name.
+ */
+
+ new_path = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ dir_fsp->fsp_name->base_name,
+ path);
+ }
+ if (new_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *path_out = new_path;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to an NT create and X call.
+****************************************************************************/
+
+void reply_ntcreate_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint32_t flags;
+ uint32_t access_mask;
+ uint32_t file_attributes;
+ uint32_t share_access;
+ uint32_t create_disposition;
+ uint32_t create_options;
+ uint16_t root_dir_fid;
+ uint64_t allocation_size;
+ /* Breakout the oplock request bits so we can set the
+ reply bits separately. */
+ uint32_t fattr=0;
+ off_t file_len = 0;
+ int info = 0;
+ files_struct *fsp = NULL;
+ char *p = NULL;
+ struct timespec create_timespec;
+ struct timespec c_timespec;
+ struct timespec a_timespec;
+ struct timespec m_timespec;
+ NTSTATUS status;
+ int oplock_request;
+ uint8_t oplock_granted = NO_OPLOCK_RETURN;
+ struct case_semantics_state *case_state = NULL;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBntcreateX);
+
+ if (req->wct < 24) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ flags = IVAL(req->vwv+3, 1);
+ access_mask = IVAL(req->vwv+7, 1);
+ file_attributes = IVAL(req->vwv+13, 1);
+ share_access = IVAL(req->vwv+15, 1);
+ create_disposition = IVAL(req->vwv+17, 1);
+ create_options = IVAL(req->vwv+19, 1);
+ root_dir_fid = (uint16_t)IVAL(req->vwv+5, 1);
+
+ allocation_size = BVAL(req->vwv+9, 1);
+
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf,
+ STR_TERMINATE, &status);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ DEBUG(10,("reply_ntcreate_and_X: flags = 0x%x, access_mask = 0x%x "
+ "file_attributes = 0x%x, share_access = 0x%x, "
+ "create_disposition = 0x%x create_options = 0x%x "
+ "root_dir_fid = 0x%x, fname = %s\n",
+ (unsigned int)flags,
+ (unsigned int)access_mask,
+ (unsigned int)file_attributes,
+ (unsigned int)share_access,
+ (unsigned int)create_disposition,
+ (unsigned int)create_options,
+ (unsigned int)root_dir_fid,
+ fname));
+
+ /*
+ * we need to remove ignored bits when they come directly from the client
+ * because we reuse some of them for internal stuff
+ */
+ create_options &= ~NTCREATEX_OPTIONS_MUST_IGNORE_MASK;
+
+ /*
+ * If it's an IPC, use the pipe handler.
+ */
+
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ do_ntcreate_pipe_open(conn, req);
+ goto out;
+ }
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK)
+ ? BATCH_OPLOCK : 0;
+ }
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ case_state = set_posix_case_semantics(ctx, conn);
+ if (!case_state) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (root_dir_fid != 0) {
+ char *new_fname = NULL;
+
+ status = get_relative_fid_filename(conn,
+ req,
+ root_dir_fid,
+ fname,
+ &new_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ fname = new_fname;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(
+ ctx, conn, fname, ucf_flags, twrp, &dirfsp, &smb_fname);
+
+ TALLOC_FREE(case_state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /*
+ * Bug #6898 - clients using Windows opens should
+ * never be able to set this attribute into the
+ * VFS.
+ */
+ file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ file_attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ allocation_size, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call, no error. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ /* Ensure we're pointing at the correct stat struct. */
+ smb_fname = fsp->fsp_name;
+
+ /*
+ * If the caller set the extended oplock request bit
+ * and we granted one (by whatever means) - set the
+ * correct bit for extended oplock reply.
+ */
+
+ if (oplock_request &&
+ (lp_fake_oplocks(SNUM(conn))
+ || EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type))) {
+
+ /*
+ * Exclusive oplock granted
+ */
+
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ oplock_granted = BATCH_OPLOCK_RETURN;
+ } else {
+ oplock_granted = EXCLUSIVE_OPLOCK_RETURN;
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ oplock_granted = LEVEL_II_OPLOCK_RETURN;
+ } else {
+ oplock_granted = NO_OPLOCK_RETURN;
+ }
+
+ file_len = smb_fname->st.st_ex_size;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* This is very strange. We
+ * return 50 words, but only set
+ * the wcnt to 42 ? It's definitely
+ * what happens on the wire....
+ */
+ reply_smb1_outbuf(req, 50, 0);
+ SCVAL(req->outbuf,smb_wct,42);
+ } else {
+ reply_smb1_outbuf(req, 34, 0);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ p = (char *)req->outbuf + smb_vwv2;
+
+ SCVAL(p, 0, oplock_granted);
+
+ p++;
+ SSVAL(p,0,fsp->fnum);
+ p += 2;
+ if ((create_disposition == FILE_SUPERSEDE)
+ && (info == FILE_WAS_OVERWRITTEN)) {
+ SIVAL(p,0,FILE_WAS_SUPERSEDED);
+ } else {
+ SIVAL(p,0,info);
+ }
+ p += 4;
+
+ fattr = fdos_mode(fsp);
+ if (fattr == 0) {
+ fattr = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* Create time. */
+ create_timespec = get_create_timespec(conn, fsp, smb_fname);
+ a_timespec = smb_fname->st.st_ex_atime;
+ m_timespec = smb_fname->st.st_ex_mtime;
+ c_timespec = get_change_timespec(conn, fsp, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_timespec);
+ dos_filetime_timespec(&a_timespec);
+ dos_filetime_timespec(&m_timespec);
+ dos_filetime_timespec(&c_timespec);
+ }
+
+ put_long_date_full_timespec(conn->ts_res, p, &create_timespec); /* create time. */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &a_timespec); /* access time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &m_timespec); /* write time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &c_timespec); /* change time */
+ p += 8;
+ SIVAL(p,0,fattr); /* File Attributes. */
+ p += 4;
+ SOFF_T(p, 0, SMB_VFS_GET_ALLOC_SIZE(conn,fsp,&smb_fname->st));
+ p += 8;
+ SOFF_T(p,0,file_len);
+ p += 8;
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint16_t file_status = (NO_EAS|NO_SUBSTREAMS|NO_REPARSETAG);
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+
+ if (lp_ea_support(SNUM(conn))) {
+ size_t num_names = 0;
+ /* Do we have any EA's ? */
+ status = get_ea_names_from_fsp(
+ ctx, smb_fname->fsp, NULL, &num_names);
+ if (NT_STATUS_IS_OK(status) && num_names) {
+ file_status &= ~NO_EAS;
+ }
+ }
+
+ status = vfs_fstreaminfo(smb_fname->fsp, ctx,
+ &num_streams, &streams);
+ /* There is always one stream, ::$DATA. */
+ if (NT_STATUS_IS_OK(status) && num_streams > 1) {
+ file_status &= ~NO_SUBSTREAMS;
+ }
+ TALLOC_FREE(streams);
+ SSVAL(p,2,file_status);
+ }
+ p += 4;
+ SCVAL(p,0,fsp->fsp_flags.is_directory ? 1 : 0);
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint32_t perms = 0;
+ p += 25;
+ if (fsp->fsp_flags.is_directory ||
+ fsp->fsp_flags.can_write ||
+ can_write_to_fsp(fsp))
+ {
+ perms = FILE_GENERIC_ALL;
+ } else {
+ perms = FILE_GENERIC_READ|FILE_EXECUTE;
+ }
+ SIVAL(p,0,perms);
+ }
+
+ DEBUG(5,("reply_ntcreate_and_X: %s, open name = %s\n",
+ fsp_fnum_dbg(fsp), smb_fname_str_dbg(smb_fname)));
+
+ out:
+ END_PROFILE(SMBntcreateX);
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT_TRANSACT_CREATE call to open a pipe.
+****************************************************************************/
+
+static void do_nt_transact_create_pipe(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count)
+{
+ char *fname = NULL;
+ char *params = *ppparams;
+ uint16_t pnum = FNUM_FIELD_INVALID;
+ char *p = NULL;
+ NTSTATUS status;
+ size_t param_len;
+ uint32_t flags;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if(parameter_count < 54) {
+ DEBUG(0,("do_nt_transact_create_pipe - insufficient parameters (%u)\n", (unsigned int)parameter_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ flags = IVAL(params,0);
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ nt_open_pipe(fname, conn, req, &pnum);
+
+ if (req->outbuf) {
+ /* Error return */
+ return;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more byyes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ p = params;
+ SCVAL(p,0,NO_OPLOCK_RETURN);
+
+ p += 2;
+ SSVAL(p,0,pnum);
+ p += 2;
+ SIVAL(p,0,FILE_WAS_OPENED);
+ p += 8;
+
+ p += 32;
+ SIVAL(p,0,FILE_ATTRIBUTE_NORMAL); /* File Attributes. */
+ p += 20;
+ /* File type. */
+ SSVAL(p,0,FILE_TYPE_MESSAGE_MODE_PIPE);
+ /* Device state. */
+ SSVAL(p,2, 0x5FF); /* ? */
+ p += 4;
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ p += 25;
+ SIVAL(p,0,FILE_GENERIC_ALL);
+ /*
+ * For pipes W2K3 seems to return
+ * 0x12019B next.
+ * This is ((FILE_GENERIC_READ|FILE_GENERIC_WRITE) & ~FILE_APPEND_DATA)
+ */
+ SIVAL(p,4,(FILE_GENERIC_READ|FILE_GENERIC_WRITE)&~FILE_APPEND_DATA);
+ }
+
+ DEBUG(5,("do_nt_transact_create_pipe: open name = %s\n", fname));
+
+ /* Send the required number of replies */
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len, *ppdata, 0);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT_TRANSACT_CREATE call (needs to process SD's).
+****************************************************************************/
+
+static void call_nt_transact_create(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count)
+{
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ char *params = *ppparams;
+ char *data = *ppdata;
+ /* Breakout the oplock request bits so we can set the reply bits separately. */
+ uint32_t fattr=0;
+ off_t file_len = 0;
+ int info = 0;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ char *p = NULL;
+ uint32_t flags;
+ uint32_t access_mask;
+ uint32_t file_attributes;
+ uint32_t share_access;
+ uint32_t create_disposition;
+ uint32_t create_options;
+ uint32_t sd_len;
+ struct security_descriptor *sd = NULL;
+ uint32_t ea_len;
+ uint16_t root_dir_fid;
+ struct timespec create_timespec;
+ struct timespec c_timespec;
+ struct timespec a_timespec;
+ struct timespec m_timespec;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS status;
+ size_t param_len;
+ uint64_t allocation_size;
+ int oplock_request;
+ uint8_t oplock_granted;
+ struct case_semantics_state *case_state = NULL;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ DEBUG(5,("call_nt_transact_create\n"));
+
+ /*
+ * If it's an IPC, use the pipe handler.
+ */
+
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ do_nt_transact_create_pipe(
+ conn, req,
+ ppsetup, setup_count,
+ ppparams, parameter_count,
+ ppdata, data_count);
+ goto out;
+ }
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if(parameter_count < 54) {
+ DEBUG(0,("call_nt_transact_create - insufficient parameters (%u)\n", (unsigned int)parameter_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ flags = IVAL(params,0);
+ access_mask = IVAL(params,8);
+ file_attributes = IVAL(params,20);
+ share_access = IVAL(params,24);
+ create_disposition = IVAL(params,28);
+ create_options = IVAL(params,32);
+ sd_len = IVAL(params,36);
+ ea_len = IVAL(params,40);
+ root_dir_fid = (uint16_t)IVAL(params,4);
+ allocation_size = BVAL(params,12);
+
+ /*
+ * we need to remove ignored bits when they come directly from the client
+ * because we reuse some of them for internal stuff
+ */
+ create_options &= ~NTCREATEX_OPTIONS_MUST_IGNORE_MASK;
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &fname,
+ params+53,
+ parameter_count-53,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (file_attributes & FILE_FLAG_POSIX_SEMANTICS) {
+ case_state = set_posix_case_semantics(ctx, conn);
+ if (!case_state) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (root_dir_fid != 0) {
+ char *new_fname = NULL;
+
+ status = get_relative_fid_filename(conn,
+ req,
+ root_dir_fid,
+ fname,
+ &new_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ fname = new_fname;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+
+ TALLOC_FREE(case_state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Ensure the data_len is correct for the sd and ea values given. */
+ if ((ea_len + sd_len > data_count)
+ || (ea_len > data_count) || (sd_len > data_count)
+ || (ea_len + sd_len < ea_len) || (ea_len + sd_len < sd_len)) {
+ DEBUG(10, ("call_nt_transact_create - ea_len = %u, sd_len = "
+ "%u, data_count = %u\n", (unsigned int)ea_len,
+ (unsigned int)sd_len, (unsigned int)data_count));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (sd_len) {
+ DEBUG(10, ("call_nt_transact_create - sd_len = %d\n",
+ sd_len));
+
+ status = unmarshall_sec_desc(ctx, (uint8_t *)data, sd_len,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("call_nt_transact_create: "
+ "unmarshall_sec_desc failed: %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ if (ea_len) {
+ if (!lp_ea_support(SNUM(conn))) {
+ DEBUG(10, ("call_nt_transact_create - ea_len = %u but "
+ "EA's not supported.\n",
+ (unsigned int)ea_len));
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+
+ if (ea_len < 10) {
+ DEBUG(10,("call_nt_transact_create - ea_len = %u - "
+ "too small (should be more than 10)\n",
+ (unsigned int)ea_len ));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ /* We have already checked that ea_len <= data_count here. */
+ ea_list = read_nttrans_ea_list(talloc_tos(), data + sd_len,
+ ea_len);
+ if (ea_list == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!req->posix_pathnames &&
+ ea_list_has_invalid_name(ea_list)) {
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more bytes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ memset(params, '\0', param_len);
+ send_nt_replies(conn, req, STATUS_INVALID_EA_NAME,
+ params, param_len, NULL, 0);
+ goto out;
+ }
+ }
+
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK)
+ ? BATCH_OPLOCK : 0;
+ }
+
+ /*
+ * Bug #6898 - clients using Windows opens should
+ * never be able to set this attribute into the
+ * VFS.
+ */
+ file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ file_attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ allocation_size, /* allocation_size */
+ 0, /* private_flags */
+ sd, /* sd */
+ ea_list, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if(!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call, no error. */
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ return;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ /* Ensure we're pointing at the correct stat struct. */
+ TALLOC_FREE(smb_fname);
+ smb_fname = fsp->fsp_name;
+
+ /*
+ * If the caller set the extended oplock request bit
+ * and we granted one (by whatever means) - set the
+ * correct bit for extended oplock reply.
+ */
+
+ if (oplock_request &&
+ (lp_fake_oplocks(SNUM(conn))
+ || EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type))) {
+
+ /*
+ * Exclusive oplock granted
+ */
+
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ oplock_granted = BATCH_OPLOCK_RETURN;
+ } else {
+ oplock_granted = EXCLUSIVE_OPLOCK_RETURN;
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ oplock_granted = LEVEL_II_OPLOCK_RETURN;
+ } else {
+ oplock_granted = NO_OPLOCK_RETURN;
+ }
+
+ file_len = smb_fname->st.st_ex_size;
+
+ /* Realloc the size of parameters and data we will return */
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ /* Extended response is 32 more byyes. */
+ param_len = 101;
+ } else {
+ param_len = 69;
+ }
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ p = params;
+ SCVAL(p, 0, oplock_granted);
+
+ p += 2;
+ SSVAL(p,0,fsp->fnum);
+ p += 2;
+ if ((create_disposition == FILE_SUPERSEDE)
+ && (info == FILE_WAS_OVERWRITTEN)) {
+ SIVAL(p,0,FILE_WAS_SUPERSEDED);
+ } else {
+ SIVAL(p,0,info);
+ }
+ p += 8;
+
+ fattr = fdos_mode(fsp);
+ if (fattr == 0) {
+ fattr = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ /* Create time. */
+ create_timespec = get_create_timespec(conn, fsp, smb_fname);
+ a_timespec = smb_fname->st.st_ex_atime;
+ m_timespec = smb_fname->st.st_ex_mtime;
+ c_timespec = get_change_timespec(conn, fsp, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_timespec);
+ dos_filetime_timespec(&a_timespec);
+ dos_filetime_timespec(&m_timespec);
+ dos_filetime_timespec(&c_timespec);
+ }
+
+ put_long_date_full_timespec(conn->ts_res, p, &create_timespec); /* create time. */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &a_timespec); /* access time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &m_timespec); /* write time */
+ p += 8;
+ put_long_date_full_timespec(conn->ts_res, p, &c_timespec); /* change time */
+ p += 8;
+ SIVAL(p,0,fattr); /* File Attributes. */
+ p += 4;
+ SOFF_T(p, 0, SMB_VFS_GET_ALLOC_SIZE(conn, fsp, &smb_fname->st));
+ p += 8;
+ SOFF_T(p,0,file_len);
+ p += 8;
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint16_t file_status = (NO_EAS|NO_SUBSTREAMS|NO_REPARSETAG);
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+
+ if (lp_ea_support(SNUM(conn))) {
+ size_t num_names = 0;
+ /* Do we have any EA's ? */
+ status = get_ea_names_from_fsp(
+ ctx, smb_fname->fsp, NULL, &num_names);
+ if (NT_STATUS_IS_OK(status) && num_names) {
+ file_status &= ~NO_EAS;
+ }
+ }
+
+ status = vfs_fstreaminfo(smb_fname->fsp, ctx,
+ &num_streams, &streams);
+ /* There is always one stream, ::$DATA. */
+ if (NT_STATUS_IS_OK(status) && num_streams > 1) {
+ file_status &= ~NO_SUBSTREAMS;
+ }
+ TALLOC_FREE(streams);
+ SSVAL(p,2,file_status);
+ }
+ p += 4;
+ SCVAL(p,0,fsp->fsp_flags.is_directory ? 1 : 0);
+
+ if (flags & EXTENDED_RESPONSE_REQUIRED) {
+ uint32_t perms = 0;
+ p += 25;
+ if (fsp->fsp_flags.is_directory ||
+ fsp->fsp_flags.can_write ||
+ can_write_to_fsp(fsp))
+ {
+ perms = FILE_GENERIC_ALL;
+ } else {
+ perms = FILE_GENERIC_READ|FILE_EXECUTE;
+ }
+ SIVAL(p,0,perms);
+ }
+
+ DEBUG(5,("call_nt_transact_create: open name = %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ /* Send the required number of replies */
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len, *ppdata, 0);
+ out:
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT CANCEL request.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_ntcancel(struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ bool found;
+
+ /*
+ * Go through and cancel any pending change notifies.
+ */
+
+ START_PROFILE(SMBntcancel);
+ smb1_srv_cancel_sign_response(xconn);
+ found = remove_pending_change_notify_requests_by_mid(sconn, req->mid);
+ if (!found) {
+ smbd_smb1_brl_finish_by_mid(sconn, req->mid);
+ }
+
+ DEBUG(3,("reply_ntcancel: cancel called on mid = %llu.\n",
+ (unsigned long long)req->mid));
+
+ END_PROFILE(SMBntcancel);
+ return;
+}
+
+/****************************************************************************
+ Reply to a NT rename request.
+****************************************************************************/
+
+void reply_ntrename(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct files_struct *src_dirfsp = NULL;
+ struct smb_filename *smb_fname_old = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_new = NULL;
+ char *oldname = NULL;
+ char *newname = NULL;
+ const char *dst_original_lcomp = NULL;
+ const char *p;
+ NTSTATUS status;
+ uint32_t attrs;
+ uint32_t ucf_flags_src = ucf_flags_from_smb_request(req);
+ NTTIME src_twrp = 0;
+ uint32_t ucf_flags_dst = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+ uint16_t rename_type;
+ TALLOC_CTX *ctx = talloc_tos();
+ bool stream_rename = false;
+
+ START_PROFILE(SMBntrename);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ attrs = SVAL(req->vwv+0, 0);
+ rename_type = SVAL(req->vwv+1, 0);
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &oldname, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!req->posix_pathnames && ms_has_wild(oldname)) {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_SYNTAX_BAD);
+ goto out;
+ }
+
+ p++;
+ p += srvstr_get_path_req(ctx, req, &newname, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!req->posix_pathnames && ms_has_wild(newname)) {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_SYNTAX_BAD);
+ goto out;
+ }
+
+ if (!req->posix_pathnames) {
+ /* The newname must begin with a ':' if the
+ oldname contains a ':'. */
+ if (strchr_m(oldname, ':')) {
+ if (newname[0] != ':') {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ stream_rename = true;
+ }
+ }
+
+ if (ucf_flags_src & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(oldname, &src_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags_src, &oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ oldname,
+ ucf_flags_src,
+ src_twrp,
+ &src_dirfsp,
+ &smb_fname_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (stream_rename) {
+ /*
+ * No point in calling filename_convert()
+ * on a raw stream name. It can never find
+ * the file anyway. Use the same logic as
+ * SMB2_FILE_RENAME_INFORMATION_INTERNAL
+ * and generate smb_fname_new directly.
+ */
+ smb_fname_new = synthetic_smb_fname(talloc_tos(),
+ smb_fname_old->base_name,
+ newname,
+ NULL,
+ smb_fname_old->twrp,
+ smb_fname_old->flags);
+ if (smb_fname_new == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ } else {
+ if (ucf_flags_dst & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(newname,
+ &dst_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags_dst, &newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags_dst,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ /* Get the last component of the destination for rename_internals(). */
+ dst_original_lcomp = get_original_lcomp(ctx,
+ conn,
+ newname,
+ ucf_flags_dst);
+ if (dst_original_lcomp == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+
+ DEBUG(3,("reply_ntrename: %s -> %s\n",
+ smb_fname_str_dbg(smb_fname_old),
+ smb_fname_str_dbg(smb_fname_new)));
+
+ switch(rename_type) {
+ case RENAME_FLAG_RENAME:
+ status = rename_internals(ctx,
+ conn,
+ req,
+ src_dirfsp,
+ smb_fname_old,
+ smb_fname_new,
+ dst_original_lcomp,
+ attrs,
+ false,
+ DELETE_ACCESS);
+ break;
+ case RENAME_FLAG_HARD_LINK:
+ status = hardlink_internals(ctx,
+ conn,
+ req,
+ false,
+ smb_fname_old,
+ smb_fname_new);
+ break;
+ case RENAME_FLAG_COPY:
+ status = copy_internals(ctx,
+ conn,
+ req,
+ src_dirfsp,
+ smb_fname_old,
+ dst_dirfsp,
+ smb_fname_new,
+ attrs);
+ break;
+ case RENAME_FLAG_MOVE_CLUSTER_INFORMATION:
+ status = NT_STATUS_INVALID_PARAMETER;
+ break;
+ default:
+ status = NT_STATUS_ACCESS_DENIED; /* Default error. */
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+ out:
+ END_PROFILE(SMBntrename);
+ return;
+}
+
+/****************************************************************************
+ Reply to a notify change - queue the request and
+ don't allow a directory to be opened.
+****************************************************************************/
+
+static void smbd_smb1_notify_reply(struct smb_request *req,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len)
+{
+ send_nt_replies(req->conn, req, error_code, (char *)buf, len, NULL, 0);
+}
+
+static void call_nt_transact_notify_change(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count,
+ uint32_t max_param_count)
+{
+ uint16_t *setup = *ppsetup;
+ files_struct *fsp;
+ uint32_t filter;
+ NTSTATUS status;
+ bool recursive;
+
+ if(setup_count < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(setup,4));
+ filter = IVAL(setup, 0);
+ recursive = (SVAL(setup, 6) != 0) ? True : False;
+
+ DEBUG(3,("call_nt_transact_notify_change\n"));
+
+ if(!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ {
+ char *filter_string;
+
+ if (!(filter_string = notify_filter_string(NULL, filter))) {
+ reply_nterror(req,NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ DEBUG(3,("call_nt_transact_notify_change: notify change "
+ "called on %s, filter = %s, recursive = %d\n",
+ fsp_str_dbg(fsp), filter_string, recursive));
+
+ TALLOC_FREE(filter_string);
+ }
+
+ if((!fsp->fsp_flags.is_directory) || (conn != fsp->conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (fsp->notify == NULL) {
+
+ status = change_notify_create(fsp,
+ max_param_count,
+ filter,
+ recursive);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("change_notify_create returned %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ if (change_notify_fsp_has_changes(fsp)) {
+
+ /*
+ * We've got changes pending, respond immediately
+ */
+
+ /*
+ * TODO: write a torture test to check the filtering behaviour
+ * here.
+ */
+
+ change_notify_reply(req,
+ NT_STATUS_OK,
+ max_param_count,
+ fsp->notify,
+ smbd_smb1_notify_reply);
+
+ /*
+ * change_notify_reply() above has independently sent its
+ * results
+ */
+ return;
+ }
+
+ /*
+ * No changes pending, queue the request
+ */
+
+ status = change_notify_add_request(req,
+ max_param_count,
+ filter,
+ recursive, fsp,
+ smbd_smb1_notify_reply);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ }
+ return;
+}
+
+/****************************************************************************
+ Reply to an NT transact rename command.
+****************************************************************************/
+
+static void call_nt_transact_rename(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count)
+{
+ char *params = *ppparams;
+ char *new_name = NULL;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if(parameter_count < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params, 0));
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &new_name,
+ params+4,
+ parameter_count - 4,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &new_name,
+ params+4,
+ parameter_count - 4,
+ STR_TERMINATE,
+ &status);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ /*
+ * W2K3 ignores this request as the RAW-RENAME test
+ * demonstrates, so we do.
+ */
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+
+ DEBUG(3,("nt transact rename from = %s, to = %s ignored!\n",
+ fsp_str_dbg(fsp), new_name));
+
+ return;
+}
+
+/****************************************************************************
+ SMB1 reply to query a security descriptor.
+****************************************************************************/
+
+static void call_nt_transact_query_security_desc(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ char *params = *ppparams;
+ char *data = *ppdata;
+ size_t sd_size = 0;
+ uint32_t security_info_wanted;
+ files_struct *fsp = NULL;
+ NTSTATUS status;
+ uint8_t *marshalled_sd = NULL;
+
+ if(parameter_count < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params,0));
+ if(!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ security_info_wanted = IVAL(params,4);
+
+ DEBUG(3,("call_nt_transact_query_security_desc: file = %s, "
+ "info_wanted = 0x%x\n", fsp_str_dbg(fsp),
+ (unsigned int)security_info_wanted));
+
+ params = nttrans_realloc(ppparams, 4);
+ if(params == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ /*
+ * Get the permissions to return.
+ */
+
+ status = smbd_do_query_security_desc(conn,
+ talloc_tos(),
+ fsp,
+ security_info_wanted &
+ SMB_SUPPORTED_SECINFO_FLAGS,
+ max_data_count,
+ &marshalled_sd,
+ &sd_size);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BUFFER_TOO_SMALL)) {
+ SIVAL(params,0,(uint32_t)sd_size);
+ send_nt_replies(conn, req, NT_STATUS_BUFFER_TOO_SMALL,
+ params, 4, NULL, 0);
+ return;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ SMB_ASSERT(sd_size > 0);
+
+ SIVAL(params,0,(uint32_t)sd_size);
+
+ if (max_data_count < sd_size) {
+ send_nt_replies(conn, req, NT_STATUS_BUFFER_TOO_SMALL,
+ params, 4, NULL, 0);
+ return;
+ }
+
+ /*
+ * Allocate the data we will return.
+ */
+
+ data = nttrans_realloc(ppdata, sd_size);
+ if(data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ memcpy(data, marshalled_sd, sd_size);
+
+ send_nt_replies(conn, req, NT_STATUS_OK, params, 4, data, (int)sd_size);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to set a security descriptor. Map to UNIX perms or POSIX ACLs.
+****************************************************************************/
+
+static void call_nt_transact_set_security_desc(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ char *params= *ppparams;
+ char *data = *ppdata;
+ files_struct *fsp = NULL;
+ uint32_t security_info_sent = 0;
+ NTSTATUS status;
+
+ if(parameter_count < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if((fsp = file_fsp(req, SVAL(params,0))) == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (!CAN_WRITE(fsp->conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if(!lp_nt_acl_support(SNUM(conn))) {
+ goto done;
+ }
+
+ security_info_sent = IVAL(params,4);
+
+ DEBUG(3,("call_nt_transact_set_security_desc: file = %s, sent 0x%x\n",
+ fsp_str_dbg(fsp), (unsigned int)security_info_sent));
+
+ if (data_count == 0) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ status = set_sd_blob(fsp, (uint8_t *)data, data_count,
+ security_info_sent & SMB_SUPPORTED_SECINFO_FLAGS);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ done:
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, NULL, 0);
+ return;
+}
+
+/****************************************************************************
+ Reply to NT IOCTL
+****************************************************************************/
+
+static void call_nt_transact_ioctl(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup, uint32_t setup_count,
+ char **ppparams, uint32_t parameter_count,
+ char **ppdata, uint32_t data_count,
+ uint32_t max_data_count)
+{
+ NTSTATUS status;
+ uint32_t function;
+ uint16_t fidnum;
+ files_struct *fsp;
+ uint8_t isFSctl;
+ uint8_t compfilter;
+ char *out_data = NULL;
+ uint32_t out_data_len = 0;
+ char *pdata = *ppdata;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (setup_count != 8) {
+ DEBUG(3,("call_nt_transact_ioctl: invalid setup count %d\n", setup_count));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ function = IVAL(*ppsetup, 0);
+ fidnum = SVAL(*ppsetup, 4);
+ isFSctl = CVAL(*ppsetup, 6);
+ compfilter = CVAL(*ppsetup, 7);
+
+ DEBUG(10, ("call_nt_transact_ioctl: function[0x%08X] FID[0x%04X] isFSctl[0x%02X] compfilter[0x%02X]\n",
+ function, fidnum, isFSctl, compfilter));
+
+ fsp=file_fsp(req, fidnum);
+
+ /*
+ * We don't really implement IOCTLs, especially on files.
+ */
+ if (!isFSctl) {
+ DEBUG(10, ("isFSctl: 0x%02X indicates IOCTL, not FSCTL!\n",
+ isFSctl));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ /* Has to be for an open file! */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+
+ /*
+ * out_data might be allocated by the VFS module, but talloc should be
+ * used, and should be cleaned up when the request ends.
+ */
+ status = SMB_VFS_FSCTL(fsp,
+ ctx,
+ function,
+ req->flags2,
+ (uint8_t *)pdata,
+ data_count,
+ (uint8_t **)&out_data,
+ max_data_count,
+ &out_data_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ } else {
+ send_nt_replies(conn, req, NT_STATUS_OK, NULL, 0, out_data, out_data_len);
+ }
+}
+
+
+#ifdef HAVE_SYS_QUOTAS
+/****************************************************************************
+ Reply to get user quota
+****************************************************************************/
+
+static void call_nt_transact_get_user_quota(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS nt_status = NT_STATUS_OK;
+ char *params = *ppparams;
+ char *pdata = *ppdata;
+ int data_len = 0;
+ int param_len = 0;
+ files_struct *fsp = NULL;
+ DATA_BLOB blob = data_blob_null;
+ struct nttrans_query_quota_params info = {0};
+ enum ndr_err_code err;
+ TALLOC_CTX *tmp_ctx = NULL;
+ uint32_t resp_len = 0;
+ uint8_t *resp_data = 0;
+
+ tmp_ctx = talloc_init("ntquota_list");
+ if (!tmp_ctx) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ /* access check */
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ DEBUG(1,("get_user_quota: access_denied service [%s] user "
+ "[%s]\n", lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name));
+ nt_status = NT_STATUS_ACCESS_DENIED;
+ goto error;
+ }
+
+ blob.data = (uint8_t*)params;
+ blob.length = parameter_count;
+
+ err = ndr_pull_struct_blob(&blob, tmp_ctx, &info,
+ (ndr_pull_flags_fn_t)ndr_pull_nttrans_query_quota_params);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DEBUG(0,("TRANSACT_GET_USER_QUOTA: failed to pull "
+ "query_quota_params.\n"));
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+ DBG_DEBUG("info.return_single_entry = %u, info.restart_scan = %u, "
+ "info.sid_list_length = %u, info.start_sid_length = %u, "
+ "info.start_sid_offset = %u\n",
+ (unsigned int)info.return_single_entry,
+ (unsigned int)info.restart_scan,
+ (unsigned int)info.sid_list_length,
+ (unsigned int)info.start_sid_length,
+ (unsigned int)info.start_sid_offset);
+
+ /* set blob to point at data for further parsing */
+ blob.data = (uint8_t*)pdata;
+ blob.length = data_count;
+ /*
+ * Although MS-SMB ref is ambiguous here, a microsoft client will
+ * only ever send a start sid (as part of a list) with
+ * sid_list_length & start_sid_offset both set to the actual list
+ * length. Note: Only a single result is returned in this case
+ * In the case where either start_sid_offset or start_sid_length
+ * are set alone or if both set (but have different values) then
+ * it seems windows will return a number of entries from the start
+ * of the list of users with quotas set. This behaviour is undocumented
+ * and windows clients do not send messages of that type. As such we
+ * currently will reject these requests.
+ */
+ if (info.start_sid_length
+ || (info.sid_list_length != info.start_sid_offset)) {
+ DBG_ERR("TRANSACT_GET_USER_QUOTA: unsupported single or "
+ "compound sid format\n");
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ /* maybe we can check the quota_fnum */
+ fsp = file_fsp(req, info.fid);
+ if (!check_fsp_ntquota_handle(conn, req, fsp)) {
+ DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+ nt_status = NT_STATUS_INVALID_HANDLE;
+ goto error;
+ }
+ nt_status = smbd_do_query_getinfo_quota(tmp_ctx,
+ fsp,
+ info.restart_scan,
+ info.return_single_entry,
+ info.sid_list_length,
+ &blob,
+ max_data_count,
+ &resp_data,
+ &resp_len);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_MORE_ENTRIES)) {
+ goto error;
+ }
+ nt_status = NT_STATUS_OK;
+ }
+
+ param_len = 4;
+ params = nttrans_realloc(ppparams, param_len);
+ if(params == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ data_len = resp_len;
+ SIVAL(params, 0, data_len);
+ pdata = nttrans_realloc(ppdata, data_len);
+ memcpy(pdata, resp_data, data_len);
+
+ TALLOC_FREE(tmp_ctx);
+ send_nt_replies(conn, req, nt_status, params, param_len,
+ pdata, data_len);
+ return;
+error:
+ TALLOC_FREE(tmp_ctx);
+ reply_nterror(req, nt_status);
+}
+
+/****************************************************************************
+ Reply to set user quota
+****************************************************************************/
+
+static void call_nt_transact_set_user_quota(connection_struct *conn,
+ struct smb_request *req,
+ uint16_t **ppsetup,
+ uint32_t setup_count,
+ char **ppparams,
+ uint32_t parameter_count,
+ char **ppdata,
+ uint32_t data_count,
+ uint32_t max_data_count)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *params = *ppparams;
+ char *pdata = *ppdata;
+ int data_len=0,param_len=0;
+ SMB_NTQUOTA_STRUCT qt;
+ struct file_quota_information info = {0};
+ enum ndr_err_code err;
+ struct dom_sid sid;
+ DATA_BLOB inblob;
+ files_struct *fsp = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ ZERO_STRUCT(qt);
+
+ /* access check */
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ DEBUG(1,("set_user_quota: access_denied service [%s] user "
+ "[%s]\n", lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto error;
+ }
+
+ /*
+ * Ensure minimum number of parameters sent.
+ */
+
+ if (parameter_count < 2) {
+ DEBUG(0,("TRANSACT_SET_USER_QUOTA: requires %d >= 2 bytes parameters\n",parameter_count));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ /* maybe we can check the quota_fnum */
+ fsp = file_fsp(req, SVAL(params,0));
+ if (!check_fsp_ntquota_handle(conn, req, fsp)) {
+ DEBUG(3,("TRANSACT_GET_USER_QUOTA: no valid QUOTA HANDLE\n"));
+ status = NT_STATUS_INVALID_HANDLE;
+ goto error;
+ }
+
+ ctx = talloc_init("set_user_quota");
+ if (!ctx) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ inblob.data = (uint8_t*)pdata;
+ inblob.length = data_count;
+
+ err = ndr_pull_struct_blob(
+ &inblob,
+ ctx,
+ &info,
+ (ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DEBUG(0,("TRANSACT_SET_USER_QUOTA: failed to pull "
+ "file_quota_information\n"));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+ qt.usedspace = info.quota_used;
+
+ qt.softlim = info.quota_threshold;
+
+ qt.hardlim = info.quota_limit;
+
+ sid = info.sid;
+
+ if (vfs_set_ntquota(fsp, SMB_USER_QUOTA_TYPE, &sid, &qt)!=0) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ send_nt_replies(conn, req, NT_STATUS_OK, params, param_len,
+ pdata, data_len);
+ TALLOC_FREE(ctx);
+ return;
+error:
+ TALLOC_FREE(ctx);
+ reply_nterror(req, status);
+}
+#endif /* HAVE_SYS_QUOTAS */
+
+static void handle_nttrans(connection_struct *conn,
+ struct trans_state *state,
+ struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ req->flags2 |= 0x40; /* IS_LONG_NAME */
+ SSVAL(discard_const_p(uint8_t, req->inbuf),smb_flg2,req->flags2);
+ }
+
+
+ /* Now we must call the relevant NT_TRANS function */
+ switch(state->call) {
+ case NT_TRANSACT_CREATE:
+ {
+ START_PROFILE(NT_transact_create);
+ call_nt_transact_create(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_create);
+ break;
+ }
+
+ case NT_TRANSACT_IOCTL:
+ {
+ START_PROFILE(NT_transact_ioctl);
+ call_nt_transact_ioctl(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_ioctl);
+ break;
+ }
+
+ case NT_TRANSACT_SET_SECURITY_DESC:
+ {
+ START_PROFILE(NT_transact_set_security_desc);
+ call_nt_transact_set_security_desc(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_set_security_desc);
+ break;
+ }
+
+ case NT_TRANSACT_NOTIFY_CHANGE:
+ {
+ START_PROFILE(NT_transact_notify_change);
+ call_nt_transact_notify_change(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return,
+ state->max_param_return);
+ END_PROFILE(NT_transact_notify_change);
+ break;
+ }
+
+ case NT_TRANSACT_RENAME:
+ {
+ START_PROFILE(NT_transact_rename);
+ call_nt_transact_rename(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_rename);
+ break;
+ }
+
+ case NT_TRANSACT_QUERY_SECURITY_DESC:
+ {
+ START_PROFILE(NT_transact_query_security_desc);
+ call_nt_transact_query_security_desc(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_query_security_desc);
+ break;
+ }
+
+#ifdef HAVE_SYS_QUOTAS
+ case NT_TRANSACT_GET_USER_QUOTA:
+ {
+ START_PROFILE(NT_transact_get_user_quota);
+ call_nt_transact_get_user_quota(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_get_user_quota);
+ break;
+ }
+
+ case NT_TRANSACT_SET_USER_QUOTA:
+ {
+ START_PROFILE(NT_transact_set_user_quota);
+ call_nt_transact_set_user_quota(
+ conn, req,
+ &state->setup, state->setup_count,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(NT_transact_set_user_quota);
+ break;
+ }
+#endif /* HAVE_SYS_QUOTAS */
+
+ default:
+ /* Error in request */
+ DEBUG(0,("handle_nttrans: Unknown request %d in "
+ "nttrans call\n", state->call));
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBNTtrans.
+****************************************************************************/
+
+void reply_nttrans(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint32_t pscnt;
+ uint32_t psoff;
+ uint32_t dscnt;
+ uint32_t dsoff;
+ uint16_t function_code;
+ NTSTATUS result;
+ struct trans_state *state;
+
+ START_PROFILE(SMBnttrans);
+
+ if (req->wct < 19) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ pscnt = IVAL(req->vwv+9, 1);
+ psoff = IVAL(req->vwv+11, 1);
+ dscnt = IVAL(req->vwv+13, 1);
+ dsoff = IVAL(req->vwv+15, 1);
+ function_code = SVAL(req->vwv+18, 0);
+
+ if (IS_IPC(conn) && (function_code != NT_TRANSACT_CREATE)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid nttrans request: %s\n", nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if ((state = talloc(conn, struct trans_state)) == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ state->cmd = SMBnttrans;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->total_data = IVAL(req->vwv+3, 1);
+ state->data = NULL;
+ state->total_param = IVAL(req->vwv+1, 1);
+ state->param = NULL;
+ state->max_data_return = IVAL(req->vwv+7, 1);
+ state->max_param_return = IVAL(req->vwv+5, 1);
+
+ /* setup count is in *words* */
+ state->setup_count = 2*CVAL(req->vwv+17, 1);
+ state->setup = NULL;
+ state->call = function_code;
+
+ DEBUG(10, ("num_setup=%u, "
+ "param_total=%u, this_param=%u, max_param=%u, "
+ "data_total=%u, this_data=%u, max_data=%u, "
+ "param_offset=%u, data_offset=%u\n",
+ (unsigned)state->setup_count,
+ (unsigned)state->total_param, (unsigned)pscnt,
+ (unsigned)state->max_param_return,
+ (unsigned)state->total_data, (unsigned)dscnt,
+ (unsigned)state->max_data_return,
+ (unsigned)psoff, (unsigned)dsoff));
+
+ /*
+ * All nttrans messages we handle have smb_wct == 19 +
+ * state->setup_count. Ensure this is so as a sanity check.
+ */
+
+ if(req->wct != 19 + (state->setup_count/2)) {
+ DEBUG(2,("Invalid smb_wct %d in nttrans call (should be %d)\n",
+ req->wct, 19 + (state->setup_count/2)));
+ goto bad_param;
+ }
+
+ /* Don't allow more than 128mb for each value. */
+ if ((state->total_data > (1024*1024*128)) ||
+ (state->total_param > (1024*1024*128))) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param))
+ goto bad_param;
+
+ if (state->total_data) {
+
+ if (smb_buffer_oob(state->total_data, 0, dscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), dsoff, dscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ if ((state->data = (char *)SMB_MALLOC(state->total_data)) == NULL) {
+ DEBUG(0,("reply_nttrans: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+
+ if (smb_buffer_oob(state->total_param, 0, pscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), psoff, pscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ if ((state->param = (char *)SMB_MALLOC(state->total_param)) == NULL) {
+ DEBUG(0,("reply_nttrans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if(state->setup_count > 0) {
+ DEBUG(10,("reply_nttrans: state->setup_count = %d\n",
+ state->setup_count));
+
+ /*
+ * No overflow possible here, state->setup_count is an
+ * unsigned int, being filled by a single byte from
+ * CVAL(req->vwv+13, 0) above. The cast in the comparison
+ * below is not necessary, it's here to clarify things. The
+ * validity of req->vwv and req->wct has been checked in
+ * init_smb1_request already.
+ */
+ if ((state->setup_count/2) + 19 > (unsigned int)req->wct) {
+ goto bad_param;
+ }
+
+ state->setup = (uint16_t *)TALLOC(state, state->setup_count);
+ if (state->setup == NULL) {
+ DEBUG(0,("reply_nttrans : Out of memory\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ memcpy(state->setup, req->vwv+19, state->setup_count);
+ dump_data(10, (uint8_t *)state->setup, state->setup_count);
+ }
+
+ if ((state->received_data == state->total_data) &&
+ (state->received_param == state->total_param)) {
+ handle_nttrans(conn, state, req);
+ SAFE_FREE(state->param);
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBnttrans);
+ return;
+ }
+
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_smb1_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBnttrans);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_nttrans: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttrans);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBnttranss
+ ****************************************************************************/
+
+void reply_nttranss(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint32_t pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+
+ START_PROFILE(SMBnttranss);
+
+ show_msg((const char *)req->inbuf);
+
+ /* Windows clients expect all replies to
+ an NT transact secondary (SMBnttranss 0xA1)
+ to have a command code of NT transact
+ (SMBnttrans 0xA0). See bug #8989 for details. */
+ req->cmd = SMBnttrans;
+
+ if (req->wct < 18) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBnttrans)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ /* Revise state->total_param and state->total_data in case they have
+ changed downwards */
+ if (IVAL(req->vwv+1, 1) < state->total_param) {
+ state->total_param = IVAL(req->vwv+1, 1);
+ }
+ if (IVAL(req->vwv+3, 1) < state->total_data) {
+ state->total_data = IVAL(req->vwv+3, 1);
+ }
+
+ pcnt = IVAL(req->vwv+5, 1);
+ poff = IVAL(req->vwv+7, 1);
+ pdisp = IVAL(req->vwv+9, 1);
+
+ dcnt = IVAL(req->vwv+11, 1);
+ doff = IVAL(req->vwv+13, 1);
+ ddisp = IVAL(req->vwv+15, 1);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (smb_buffer_oob(state->total_param, pdisp, pcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), poff, pcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->param+pdisp, smb_base(req->inbuf)+poff,pcnt);
+ }
+
+ if (dcnt) {
+ if (smb_buffer_oob(state->total_data, ddisp, dcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), doff, dcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBnttranss);
+ return;
+ }
+
+ handle_nttrans(conn, state, req);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBnttranss);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_nttranss: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnttranss);
+ return;
+}
diff --git a/source3/smbd/smb1_nttrans.h b/source3/smbd/smb1_nttrans.h
new file mode 100644
index 0000000..cbd6d1f
--- /dev/null
+++ b/source3/smbd/smb1_nttrans.h
@@ -0,0 +1,25 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT transaction handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+void reply_ntcreate_and_X(struct smb_request *req);
+void reply_ntcancel(struct smb_request *req);
+void reply_ntrename(struct smb_request *req);
+void reply_nttrans(struct smb_request *req);
+void reply_nttranss(struct smb_request *req);
diff --git a/source3/smbd/smb1_oplock.c b/source3/smbd/smb1_oplock.c
new file mode 100644
index 0000000..5b8a09b
--- /dev/null
+++ b/source3/smbd/smb1_oplock.c
@@ -0,0 +1,72 @@
+/*
+ Unix SMB/CIFS implementation.
+ oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998 - 2001
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "locking/leases_db.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+
+/****************************************************************************
+ Set up an oplock break message.
+****************************************************************************/
+
+void new_break_message_smb1(files_struct *fsp, int cmd,
+ char result[SMB1_BREAK_MESSAGE_LENGTH])
+{
+ memset(result,'\0',smb_size);
+ srv_smb1_set_message(result,8,0,true);
+ SCVAL(result,smb_com,SMBlockingX);
+ SSVAL(result,smb_tid,fsp->conn->cnum);
+ SSVAL(result,smb_pid,0xFFFF);
+ SSVAL(result,smb_uid,0);
+ SSVAL(result,smb_mid,0xFFFF);
+ SCVAL(result,smb_vwv0,0xFF);
+ SSVAL(result,smb_vwv2,fsp->fnum);
+ SCVAL(result,smb_vwv3,LOCKING_ANDX_OPLOCK_RELEASE);
+ SCVAL(result,smb_vwv3+1,cmd);
+}
+
+void send_break_message_smb1(files_struct *fsp, int level)
+{
+ struct smbXsrv_connection *xconn = NULL;
+ char break_msg[SMB1_BREAK_MESSAGE_LENGTH];
+
+ /*
+ * For SMB1 we only have one connection
+ */
+ xconn = fsp->conn->sconn->client->connections;
+
+ new_break_message_smb1(fsp, level, break_msg);
+
+ show_msg(break_msg);
+ if (!smb1_srv_send(xconn,
+ break_msg,
+ false,
+ 0,
+ IS_CONN_ENCRYPTED(fsp->conn))) {
+ exit_server_cleanly("send_break_message_smb1: "
+ "smb1_srv_send failed.");
+ }
+}
diff --git a/source3/smbd/smb1_oplock.h b/source3/smbd/smb1_oplock.h
new file mode 100644
index 0000000..a9b3c47
--- /dev/null
+++ b/source3/smbd/smb1_oplock.h
@@ -0,0 +1,26 @@
+/*
+ Unix SMB/CIFS implementation.
+ oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998 - 2001
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define SMB1_BREAK_MESSAGE_LENGTH (smb_size + 8*2)
+
+void new_break_message_smb1(files_struct *fsp, int cmd,
+ char result[SMB1_BREAK_MESSAGE_LENGTH]);
+void send_break_message_smb1(files_struct *fsp, int level);
diff --git a/source3/smbd/smb1_pipes.c b/source3/smbd/smb1_pipes.c
new file mode 100644
index 0000000..ea2fae5
--- /dev/null
+++ b/source3/smbd/smb1_pipes.c
@@ -0,0 +1,447 @@
+/*
+ Unix SMB/CIFS implementation.
+ Pipe SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1997-1998.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ This file handles reply_ calls on named pipes that the server
+ makes to handle specific protocols
+*/
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "auth/auth_util.h"
+#include "librpc/rpc/dcerpc_helper.h"
+
+/****************************************************************************
+ Reply to an open and X on a named pipe.
+ This code is basically stolen from reply_open_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+void reply_open_pipe_and_X(connection_struct *conn, struct smb_request *req)
+{
+ const char *fname = NULL;
+ char *pipe_name = NULL;
+ files_struct *fsp;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status;
+
+ /* XXXX we need to handle passed times, sattr and flags */
+ srvstr_pull_req_talloc(ctx, req, &pipe_name, req->buf, STR_TERMINATE);
+ if (!pipe_name) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+
+ /* If the name doesn't start \PIPE\ then this is directed */
+ /* at a mailslot or something we really, really don't understand, */
+ /* not just something we really don't understand. */
+
+#define PIPE "PIPE\\"
+#define PIPELEN strlen(PIPE)
+
+ fname = pipe_name;
+ while (fname[0] == '\\') {
+ fname++;
+ }
+ if (!strnequal(fname, PIPE, PIPELEN)) {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_SYNTAX_BAD);
+ return;
+ }
+ fname += PIPELEN;
+ while (fname[0] == '\\') {
+ fname++;
+ }
+
+ DEBUG(4,("Opening pipe %s => %s.\n", pipe_name, fname));
+
+ status = open_np_file(req, fname, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpipe);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ /* Prepare the reply */
+ reply_smb1_outbuf(req, 15, 0);
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ /* Mark the opened file as an existing named pipe in message mode. */
+ SSVAL(req->outbuf,smb_vwv9,2);
+ SSVAL(req->outbuf,smb_vwv10,0xc700);
+
+ SSVAL(req->outbuf, smb_vwv2, fsp->fnum);
+ SSVAL(req->outbuf, smb_vwv3, 0); /* fmode */
+ srv_put_dos_date3((char *)req->outbuf, smb_vwv4, 0); /* mtime */
+ SIVAL(req->outbuf, smb_vwv6, 0); /* size */
+ SSVAL(req->outbuf, smb_vwv8, 0); /* rmode */
+ SSVAL(req->outbuf, smb_vwv11, 0x0001);
+}
+
+/****************************************************************************
+ Reply to a write and X.
+
+ This code is basically stolen from reply_write_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+struct pipe_write_andx_state {
+ bool pipe_start_message_raw;
+ size_t numtowrite;
+};
+
+static void pipe_write_andx_done(struct tevent_req *subreq);
+
+void reply_pipe_write_and_X(struct smb_request *req)
+{
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ int smb_doff = SVAL(req->vwv+11, 0);
+ const uint8_t *data;
+ struct pipe_write_andx_state *state;
+ struct tevent_req *subreq;
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (fsp->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ state = talloc(req, struct pipe_write_andx_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->numtowrite = SVAL(req->vwv+10, 0);
+ state->pipe_start_message_raw =
+ ((SVAL(req->vwv+7, 0) & (PIPE_START_MESSAGE|PIPE_RAW_MODE))
+ == (PIPE_START_MESSAGE|PIPE_RAW_MODE));
+
+ DEBUG(6, ("reply_pipe_write_and_X: %s, name: %s len: %d\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp), (int)state->numtowrite));
+
+ data = (const uint8_t *)smb_base(req->inbuf) + smb_doff;
+
+ if (state->pipe_start_message_raw) {
+ /*
+ * For the start of a message in named pipe byte mode,
+ * the first two bytes are a length-of-pdu field. Ignore
+ * them (we don't trust the client). JRA.
+ */
+ if (state->numtowrite < 2) {
+ DEBUG(0,("reply_pipe_write_and_X: start of message "
+ "set and not enough data sent.(%u)\n",
+ (unsigned int)state->numtowrite ));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ data += 2;
+ state->numtowrite -= 2;
+ }
+
+ subreq = np_write_send(state, req->sconn->ev_ctx,
+ fsp->fake_file_handle, data, state->numtowrite);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, pipe_write_andx_done,
+ talloc_move(req->conn, &req));
+}
+
+static void pipe_write_andx_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct pipe_write_andx_state *state = talloc_get_type_abort(
+ req->async_priv, struct pipe_write_andx_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto done;
+ }
+
+ /* Looks bogus to me now. Is this error message correct ? JRA. */
+ if (nwritten != state->numtowrite) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto done;
+ }
+
+ reply_smb1_outbuf(req, 6, 0);
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ nwritten = (state->pipe_start_message_raw ? nwritten + 2 : nwritten);
+ SSVAL(req->outbuf,smb_vwv2,nwritten);
+
+ DEBUG(3,("writeX-IPC nwritten=%d\n", (int)nwritten));
+
+ done:
+ /*
+ * We must free here as the ownership of req was
+ * moved to the connection struct in reply_pipe_write_and_X().
+ */
+ smb_request_done(req);
+}
+
+/****************************************************************************
+ Reply to a read and X.
+ This code is basically stolen from reply_read_and_X with some
+ wrinkles to handle pipes.
+****************************************************************************/
+
+struct pipe_read_andx_state {
+ uint8_t *outbuf;
+ int smb_mincnt;
+ int smb_maxcnt;
+};
+
+static void pipe_read_andx_done(struct tevent_req *subreq);
+
+void reply_pipe_read_and_X(struct smb_request *req)
+{
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ uint8_t *data;
+ struct pipe_read_andx_state *state;
+ struct tevent_req *subreq;
+
+ /* we don't use the offset given to use for pipe reads. This
+ is deliberate, instead we always return the next lump of
+ data on the pipe */
+#if 0
+ uint32_t smb_offs = IVAL(req->vwv+3, 0);
+#endif
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (fsp->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ state = talloc(req, struct pipe_read_andx_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->smb_maxcnt = SVAL(req->vwv+5, 0);
+ state->smb_mincnt = SVAL(req->vwv+6, 0);
+
+ reply_smb1_outbuf(req, 12, state->smb_maxcnt + 1 /* padding byte */);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+ SCVAL(smb_buf(req->outbuf), 0, 0); /* padding byte */
+
+ data = (uint8_t *)smb_buf(req->outbuf) + 1 /* padding byte */;
+
+ /*
+ * We have to tell the upper layers that we're async.
+ */
+ state->outbuf = req->outbuf;
+ req->outbuf = NULL;
+
+ subreq = np_read_send(state, req->sconn->ev_ctx,
+ fsp->fake_file_handle, data,
+ state->smb_maxcnt);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, pipe_read_andx_done,
+ talloc_move(req->conn, &req));
+}
+
+static void pipe_read_andx_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct pipe_read_andx_state *state = talloc_get_type_abort(
+ req->async_priv, struct pipe_read_andx_state);
+ NTSTATUS status;
+ ssize_t nread;
+ bool is_data_outstanding;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+ reply_nterror(req, status);
+ goto done;
+ }
+
+ req->outbuf = state->outbuf;
+ state->outbuf = NULL;
+
+ srv_smb1_set_message((char *)req->outbuf, 12, nread + 1 /* padding byte */,
+ false);
+
+#if 0
+ /*
+ * we should return STATUS_BUFFER_OVERFLOW if there's
+ * out standing data.
+ *
+ * But we can't enable it yet, as it has bad interactions
+ * with fixup_chain_error_packet() in chain_reply().
+ */
+ if (is_data_outstanding) {
+ error_packet_set((char *)req->outbuf, ERRDOS, ERRmoredata,
+ STATUS_BUFFER_OVERFLOW, __LINE__, __FILE__);
+ }
+#endif
+
+ SSVAL(req->outbuf,smb_vwv5,nread);
+ SSVAL(req->outbuf,smb_vwv6,
+ (smb_wct - 4) /* offset from smb header to wct */
+ + 1 /* the wct field */
+ + 12 * sizeof(uint16_t) /* vwv */
+ + 2 /* the buflen field */
+ + 1); /* padding byte */
+
+ DEBUG(3,("readX-IPC min=%d max=%d nread=%d\n",
+ state->smb_mincnt, state->smb_maxcnt, (int)nread));
+
+ done:
+ /*
+ * We must free here as the ownership of req was
+ * moved to the connection struct in reply_pipe_read_and_X().
+ */
+ smb_request_done(req);
+}
+
+/****************************************************************************
+ Reply to a write on a pipe.
+****************************************************************************/
+
+struct pipe_write_state {
+ size_t numtowrite;
+};
+
+static void pipe_write_done(struct tevent_req *subreq);
+
+void reply_pipe_write(struct smb_request *req)
+{
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ const uint8_t *data;
+ struct pipe_write_state *state;
+ struct tevent_req *subreq;
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if (fsp->vuid != req->vuid) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ state = talloc(req, struct pipe_write_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ req->async_priv = state;
+
+ state->numtowrite = SVAL(req->vwv+1, 0);
+
+ data = req->buf + 3;
+
+ DEBUG(6, ("reply_pipe_write: %s, name: %s len: %d\n", fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp), (int)state->numtowrite));
+
+ subreq = np_write_send(state, req->sconn->ev_ctx,
+ fsp->fake_file_handle, data, state->numtowrite);
+ if (subreq == NULL) {
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ tevent_req_set_callback(subreq, pipe_write_done,
+ talloc_move(req->conn, &req));
+}
+
+static void pipe_write_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = tevent_req_callback_data(
+ subreq, struct smb_request);
+ struct pipe_write_state *state = talloc_get_type_abort(
+ req->async_priv, struct pipe_write_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+ if (nwritten < 0) {
+ reply_nterror(req, status);
+ goto send;
+ }
+
+ /* Looks bogus to me now. Needs to be removed ? JRA. */
+ if ((nwritten == 0 && state->numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto send;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ DEBUG(3,("write-IPC nwritten=%d\n", (int)nwritten));
+
+ send:
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) || req->encrypted)) {
+ exit_server_cleanly("construct_reply: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+}
diff --git a/source3/smbd/smb1_pipes.h b/source3/smbd/smb1_pipes.h
new file mode 100644
index 0000000..20e8a99
--- /dev/null
+++ b/source3/smbd/smb1_pipes.h
@@ -0,0 +1,26 @@
+/*
+ Unix SMB/CIFS implementation.
+ Pipe SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1997-1998.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+void reply_open_pipe_and_X(connection_struct *conn, struct smb_request *req);
+void reply_pipe_write_and_X(struct smb_request *req);
+void reply_pipe_read_and_X(struct smb_request *req);
+void reply_pipe_write(struct smb_request *req);
diff --git a/source3/smbd/smb1_process.c b/source3/smbd/smb1_process.c
new file mode 100644
index 0000000..e4f6cdf
--- /dev/null
+++ b/source3/smbd/smb1_process.c
@@ -0,0 +1,2718 @@
+/*
+ Unix SMB/CIFS implementation.
+ process incoming packets - main loop
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../lib/tsocket/tsocket.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "../lib/async_req/async_sock.h"
+#include "ctdbd_conn.h"
+#include "../lib/util/select.h"
+#include "printing/queue_process.h"
+#include "system/select.h"
+#include "passdb.h"
+#include "auth.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "smbprofile.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../libcli/security/dom_sid.h"
+#include "../libcli/security/security_token.h"
+#include "lib/id_cache.h"
+#include "lib/util/sys_rw_data.h"
+#include "system/threads.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+#include "util_event.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "lib/util/time_basic.h"
+#include "source3/lib/substitute.h"
+#include "lib/util/util_process.h"
+
+/* Internal message queue for deferred opens. */
+struct pending_message_list {
+ struct pending_message_list *next, *prev;
+ struct timeval request_time; /* When was this first issued? */
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ struct tevent_timer *te;
+ uint32_t seqnum;
+ bool encrypted;
+ bool processed;
+ DATA_BLOB buf;
+ struct deferred_open_record *open_rec;
+};
+
+static bool smb_splice_chain(uint8_t **poutbuf, const uint8_t *andx_buf);
+
+void smbd_echo_init(struct smbXsrv_connection *xconn)
+{
+ xconn->smb1.echo_handler.trusted_fd = -1;
+ xconn->smb1.echo_handler.socket_lock_fd = -1;
+#ifdef HAVE_ROBUST_MUTEXES
+ xconn->smb1.echo_handler.socket_mutex = NULL;
+#endif
+}
+
+static bool smbd_echo_active(struct smbXsrv_connection *xconn)
+{
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ return true;
+ }
+
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+static bool smbd_lock_socket_internal(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_echo_active(xconn)) {
+ return true;
+ }
+
+ xconn->smb1.echo_handler.ref_count++;
+
+ if (xconn->smb1.echo_handler.ref_count > 1) {
+ return true;
+ }
+
+ DEBUG(10,("pid[%d] wait for socket lock\n", (int)getpid()));
+
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ int ret = EINTR;
+
+ while (ret == EINTR) {
+ ret = pthread_mutex_lock(
+ xconn->smb1.echo_handler.socket_mutex);
+ if (ret == 0) {
+ break;
+ }
+ }
+ if (ret != 0) {
+ DEBUG(1, ("pthread_mutex_lock failed: %s\n",
+ strerror(ret)));
+ return false;
+ }
+ }
+#endif
+
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ bool ok;
+
+ do {
+ ok = fcntl_lock(
+ xconn->smb1.echo_handler.socket_lock_fd,
+ F_SETLKW, 0, 0, F_WRLCK);
+ } while (!ok && (errno == EINTR));
+
+ if (!ok) {
+ DEBUG(1, ("fcntl_lock failed: %s\n", strerror(errno)));
+ return false;
+ }
+ }
+
+ DEBUG(10,("pid[%d] got socket lock\n", (int)getpid()));
+
+ return true;
+}
+
+void smbd_lock_socket(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_lock_socket_internal(xconn)) {
+ exit_server_cleanly("failed to lock socket");
+ }
+}
+
+static bool smbd_unlock_socket_internal(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_echo_active(xconn)) {
+ return true;
+ }
+
+ xconn->smb1.echo_handler.ref_count--;
+
+ if (xconn->smb1.echo_handler.ref_count > 0) {
+ return true;
+ }
+
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ int ret;
+ ret = pthread_mutex_unlock(
+ xconn->smb1.echo_handler.socket_mutex);
+ if (ret != 0) {
+ DEBUG(1, ("pthread_mutex_unlock failed: %s\n",
+ strerror(ret)));
+ return false;
+ }
+ }
+#endif
+
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ bool ok;
+
+ do {
+ ok = fcntl_lock(
+ xconn->smb1.echo_handler.socket_lock_fd,
+ F_SETLKW, 0, 0, F_UNLCK);
+ } while (!ok && (errno == EINTR));
+
+ if (!ok) {
+ DEBUG(1, ("fcntl_lock failed: %s\n", strerror(errno)));
+ return false;
+ }
+ }
+
+ DEBUG(10,("pid[%d] unlocked socket\n", (int)getpid()));
+
+ return true;
+}
+
+void smbd_unlock_socket(struct smbXsrv_connection *xconn)
+{
+ if (!smbd_unlock_socket_internal(xconn)) {
+ exit_server_cleanly("failed to unlock socket");
+ }
+}
+
+/* Accessor function for smb_read_error for smbd functions. */
+
+/****************************************************************************
+ Send an smb to a fd.
+****************************************************************************/
+
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt)
+{
+ size_t len = 0;
+ ssize_t ret;
+ char *buf_out = buffer;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ return true;
+ }
+
+ smbd_lock_socket(xconn);
+
+ if (do_signing) {
+ NTSTATUS status;
+
+ /* Sign the outgoing packet if required. */
+ status = smb1_srv_calculate_sign_mac(xconn, buf_out, seqnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to calculate signing mac: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ }
+
+ if (do_encrypt) {
+ NTSTATUS status = srv_encrypt_buffer(xconn, buffer, &buf_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("send_smb: SMB encryption failed "
+ "on outgoing packet! Error %s\n",
+ nt_errstr(status) ));
+ ret = -1;
+ goto out;
+ }
+ }
+
+ len = smb_len_large(buf_out) + 4;
+
+ ret = write_data(xconn->transport.sock, buf_out, len);
+ if (ret <= 0) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(1,("pid[%d] Error writing %d bytes to client %s. %d. (%s)\n",
+ (int)getpid(), (int)len,
+ smbXsrv_connection_dbg(xconn),
+ (int)ret, strerror(saved_errno)));
+ errno = saved_errno;
+
+ srv_free_enc_buffer(xconn, buf_out);
+ goto out;
+ }
+
+ srv_free_enc_buffer(xconn, buf_out);
+out:
+ smbd_unlock_socket(xconn);
+ return (ret > 0);
+}
+
+/* Socket functions for smbd packet processing. */
+
+static bool valid_packet_size(size_t len)
+{
+ /*
+ * A WRITEX with CAP_LARGE_WRITEX can be 64k worth of data plus 65 bytes
+ * of header. Don't print the error if this fits.... JRA.
+ */
+
+ if (len > (LARGE_WRITEX_BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
+ DEBUG(0,("Invalid packet length! (%lu bytes).\n",
+ (unsigned long)len));
+ return false;
+ }
+ return true;
+}
+
+/****************************************************************************
+ Attempt a zerocopy writeX read. We know here that len > smb_size-4
+****************************************************************************/
+
+/*
+ * Unfortunately, earlier versions of smbclient/libsmbclient
+ * don't send this "standard" writeX header. I've fixed this
+ * for 3.2 but we'll use the old method with earlier versions.
+ * Windows and CIFSFS at least use this standard size. Not
+ * sure about MacOSX.
+ */
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+ (2*14) + /* word count (including bcc) */ \
+ 1 /* pad byte */)
+
+static NTSTATUS receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
+ const char lenbuf[4],
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer,
+ unsigned int timeout,
+ size_t *p_unread,
+ size_t *len_ret)
+{
+ /* Size of a WRITEX call (+4 byte len). */
+ char writeX_header[4 + STANDARD_WRITE_AND_X_HEADER_SIZE];
+ ssize_t len = smb_len_large(lenbuf); /* Could be a UNIX large writeX. */
+ ssize_t toread;
+ NTSTATUS status;
+
+ memcpy(writeX_header, lenbuf, 4);
+
+ status = read_fd_with_timeout(
+ sock, writeX_header + 4,
+ STANDARD_WRITE_AND_X_HEADER_SIZE,
+ STANDARD_WRITE_AND_X_HEADER_SIZE,
+ timeout, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("read_fd_with_timeout failed for client %s read "
+ "error = %s.\n",
+ smbXsrv_connection_dbg(xconn),
+ nt_errstr(status)));
+ return status;
+ }
+
+ /*
+ * Ok - now try and see if this is a possible
+ * valid writeX call.
+ */
+
+ if (is_valid_writeX_buffer(xconn, (uint8_t *)writeX_header)) {
+ /*
+ * If the data offset is beyond what
+ * we've read, drain the extra bytes.
+ */
+ uint16_t doff = SVAL(writeX_header,smb_vwv11);
+ ssize_t newlen;
+
+ if (doff > STANDARD_WRITE_AND_X_HEADER_SIZE) {
+ size_t drain = doff - STANDARD_WRITE_AND_X_HEADER_SIZE;
+ if (drain_socket(sock, drain) != drain) {
+ smb_panic("receive_smb_raw_talloc_partial_read:"
+ " failed to drain pending bytes");
+ }
+ } else {
+ doff = STANDARD_WRITE_AND_X_HEADER_SIZE;
+ }
+
+ /* Spoof down the length and null out the bcc. */
+ set_message_bcc(writeX_header, 0);
+ newlen = smb_len(writeX_header);
+
+ /* Copy the header we've written. */
+
+ *buffer = (char *)talloc_memdup(mem_ctx,
+ writeX_header,
+ sizeof(writeX_header));
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)sizeof(writeX_header)));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Work out the remaining bytes. */
+ *p_unread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+ *len_ret = newlen + 4;
+ return NT_STATUS_OK;
+ }
+
+ if (!valid_packet_size(len)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Not a valid writeX call. Just do the standard
+ * talloc and return.
+ */
+
+ *buffer = talloc_array(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Copy in what we already read. */
+ memcpy(*buffer,
+ writeX_header,
+ 4 + STANDARD_WRITE_AND_X_HEADER_SIZE);
+ toread = len - STANDARD_WRITE_AND_X_HEADER_SIZE;
+
+ if(toread > 0) {
+ status = read_packet_remainder(
+ sock,
+ (*buffer) + 4 + STANDARD_WRITE_AND_X_HEADER_SIZE,
+ timeout, toread);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("receive_smb_raw_talloc_partial_read: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ *len_ret = len + 4;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, size_t *plen)
+{
+ char lenbuf[4];
+ size_t len;
+ int min_recv_size = lp_min_receive_file_size();
+ NTSTATUS status;
+
+ *p_unread = 0;
+
+ status = read_smb_length_return_keepalive(sock, lenbuf, timeout,
+ &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (CVAL(lenbuf,0) == 0 && min_recv_size &&
+ (smb_len_large(lenbuf) > /* Could be a UNIX large writeX. */
+ (min_recv_size + STANDARD_WRITE_AND_X_HEADER_SIZE)) &&
+ !smb1_srv_is_signing_active(xconn) &&
+ xconn->smb1.echo_handler.trusted_fde == NULL) {
+
+ return receive_smb_raw_talloc_partial_read(
+ mem_ctx, lenbuf, xconn, sock, buffer, timeout,
+ p_unread, plen);
+ }
+
+ if (!valid_packet_size(len)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * The +4 here can't wrap, we've checked the length above already.
+ */
+
+ *buffer = talloc_array(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(*buffer, lenbuf, sizeof(lenbuf));
+
+ status = read_packet_remainder(sock, (*buffer)+4, timeout, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *plen = len + 4;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb1_receive_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel)
+{
+ size_t len = 0;
+ NTSTATUS status;
+
+ *p_encrypted = false;
+
+ status = receive_smb_raw_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)?5:1,
+ ("receive_smb_raw_talloc failed for client %s "
+ "read error = %s.\n",
+ smbXsrv_connection_dbg(xconn),
+ nt_errstr(status)) );
+ return status;
+ }
+
+ if (is_encrypted_packet((uint8_t *)*buffer)) {
+ status = srv_decrypt_buffer(xconn, *buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("receive_smb_talloc: SMB decryption failed on "
+ "incoming packet! Error %s\n",
+ nt_errstr(status) ));
+ return status;
+ }
+ *p_encrypted = true;
+ }
+
+ /* Check the incoming SMB signature. */
+ if (!smb1_srv_check_sign_mac(xconn, *buffer, seqnum, trusted_channel)) {
+ DEBUG(0, ("receive_smb: SMB Signature verification failed on "
+ "incoming packet!\n"));
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ *p_len = len;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Function to push a message onto the tail of a linked list of smb messages ready
+ for processing.
+****************************************************************************/
+
+static bool push_queued_message(struct smb_request *req,
+ struct timeval request_time,
+ struct timeval end_time,
+ struct deferred_open_record *open_rec)
+{
+ int msg_len = smb_len(req->inbuf) + 4;
+ struct pending_message_list *msg;
+
+ msg = talloc_zero(NULL, struct pending_message_list);
+
+ if(msg == NULL) {
+ DEBUG(0,("push_message: malloc fail (1)\n"));
+ return False;
+ }
+ msg->sconn = req->sconn;
+ msg->xconn = req->xconn;
+
+ msg->buf = data_blob_talloc(msg, req->inbuf, msg_len);
+ if(msg->buf.data == NULL) {
+ DEBUG(0,("push_message: malloc fail (2)\n"));
+ TALLOC_FREE(msg);
+ return False;
+ }
+
+ msg->request_time = request_time;
+ msg->seqnum = req->seqnum;
+ msg->encrypted = req->encrypted;
+ msg->processed = false;
+
+ if (open_rec) {
+ msg->open_rec = talloc_move(msg, &open_rec);
+ }
+
+#if 0
+ msg->te = tevent_add_timer(msg->sconn->ev_ctx,
+ msg,
+ end_time,
+ smbd_deferred_open_timer,
+ msg);
+ if (!msg->te) {
+ DEBUG(0,("push_message: event_add_timed failed\n"));
+ TALLOC_FREE(msg);
+ return false;
+ }
+#endif
+
+ DLIST_ADD_END(req->sconn->deferred_open_queue, msg);
+
+ DEBUG(10,("push_message: pushed message length %u on "
+ "deferred_open_queue\n", (unsigned int)msg_len));
+
+ return True;
+}
+
+/****************************************************************************
+ Function to push a deferred open smb message onto a linked list of local smb
+ messages ready for processing.
+****************************************************************************/
+
+bool push_deferred_open_message_smb1(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec)
+{
+ struct timeval_buf tvbuf;
+ struct timeval end_time;
+
+ if (req->unread_bytes) {
+ DEBUG(0,("push_deferred_open_message_smb: logic error ! "
+ "unread_bytes = %u\n",
+ (unsigned int)req->unread_bytes ));
+ smb_panic("push_deferred_open_message_smb: "
+ "logic error unread_bytes != 0" );
+ }
+
+ end_time = timeval_sum(&req->request_time, &timeout);
+
+ DBG_DEBUG("pushing message len %u mid %"PRIu64" timeout time [%s]\n",
+ (unsigned int) smb_len(req->inbuf)+4,
+ req->mid,
+ timeval_str_buf(&end_time, false, true, &tvbuf));
+
+ return push_queued_message(req, req->request_time, end_time, open_rec);
+}
+
+/*
+ * Only allow 5 outstanding trans requests. We're allocating memory, so
+ * prevent a DoS.
+ */
+
+NTSTATUS allow_new_trans(struct trans_state *list, uint64_t mid)
+{
+ int count = 0;
+ for (; list != NULL; list = list->next) {
+
+ if (list->mid == mid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ count += 1;
+ }
+ if (count > 5) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+These flags determine some of the permissions required to do an operation
+
+Note that I don't set NEED_WRITE on some write operations because they
+are used by some brain-dead clients when printing, and I don't want to
+force write permissions on print services.
+*/
+#define AS_USER (1<<0)
+#define NEED_WRITE (1<<1) /* Must be paired with AS_USER */
+#define TIME_INIT (1<<2)
+#define CAN_IPC (1<<3) /* Must be paired with AS_USER */
+#define AS_GUEST (1<<5) /* Must *NOT* be paired with AS_USER */
+#define DO_CHDIR (1<<6)
+
+/*
+ define a list of possible SMB messages and their corresponding
+ functions. Any message that has a NULL function is unimplemented -
+ please feel free to contribute implementations!
+*/
+static const struct smb_message_struct {
+ const char *name;
+ void (*fn)(struct smb_request *req);
+ int flags;
+} smb_messages[256] = {
+
+/* 0x00 */ { "SMBmkdir",reply_mkdir,AS_USER | NEED_WRITE},
+/* 0x01 */ { "SMBrmdir",reply_rmdir,AS_USER | NEED_WRITE},
+/* 0x02 */ { "SMBopen",reply_open,AS_USER },
+/* 0x03 */ { "SMBcreate",reply_mknew,AS_USER},
+/* 0x04 */ { "SMBclose",reply_close,AS_USER | CAN_IPC },
+/* 0x05 */ { "SMBflush",reply_flush,AS_USER},
+/* 0x06 */ { "SMBunlink",reply_unlink,AS_USER | NEED_WRITE },
+/* 0x07 */ { "SMBmv",reply_mv,AS_USER | NEED_WRITE },
+/* 0x08 */ { "SMBgetatr",reply_getatr,AS_USER},
+/* 0x09 */ { "SMBsetatr",reply_setatr,AS_USER | NEED_WRITE},
+/* 0x0a */ { "SMBread",reply_read,AS_USER},
+/* 0x0b */ { "SMBwrite",reply_write,AS_USER | CAN_IPC },
+/* 0x0c */ { "SMBlock",reply_lock,AS_USER},
+/* 0x0d */ { "SMBunlock",reply_unlock,AS_USER},
+/* 0x0e */ { "SMBctemp",reply_ctemp,AS_USER },
+/* 0x0f */ { "SMBmknew",reply_mknew,AS_USER},
+/* 0x10 */ { "SMBcheckpath",reply_checkpath,AS_USER},
+/* 0x11 */ { "SMBexit",reply_exit,DO_CHDIR},
+/* 0x12 */ { "SMBlseek",reply_lseek,AS_USER},
+/* 0x13 */ { "SMBlockread",reply_lockread,AS_USER},
+/* 0x14 */ { "SMBwriteunlock",reply_writeunlock,AS_USER},
+/* 0x15 */ { NULL, NULL, 0 },
+/* 0x16 */ { NULL, NULL, 0 },
+/* 0x17 */ { NULL, NULL, 0 },
+/* 0x18 */ { NULL, NULL, 0 },
+/* 0x19 */ { NULL, NULL, 0 },
+/* 0x1a */ { "SMBreadbraw",reply_readbraw,AS_USER},
+/* 0x1b */ { "SMBreadBmpx",reply_readbmpx,AS_USER},
+/* 0x1c */ { "SMBreadBs",reply_readbs,AS_USER },
+/* 0x1d */ { "SMBwritebraw",reply_writebraw,AS_USER},
+/* 0x1e */ { "SMBwriteBmpx",reply_writebmpx,AS_USER},
+/* 0x1f */ { "SMBwriteBs",reply_writebs,AS_USER},
+/* 0x20 */ { "SMBwritec", NULL,0},
+/* 0x21 */ { NULL, NULL, 0 },
+/* 0x22 */ { "SMBsetattrE",reply_setattrE,AS_USER | NEED_WRITE },
+/* 0x23 */ { "SMBgetattrE",reply_getattrE,AS_USER },
+/* 0x24 */ { "SMBlockingX",reply_lockingX,AS_USER },
+/* 0x25 */ { "SMBtrans",reply_trans,AS_USER | CAN_IPC },
+/* 0x26 */ { "SMBtranss",reply_transs,AS_USER | CAN_IPC},
+/* 0x27 */ { "SMBioctl",reply_ioctl,0},
+/* 0x28 */ { "SMBioctls", NULL,AS_USER},
+/* 0x29 */ { "SMBcopy",reply_copy,AS_USER | NEED_WRITE },
+/* 0x2a */ { "SMBmove", NULL,AS_USER | NEED_WRITE },
+/* 0x2b */ { "SMBecho",reply_echo,0},
+/* 0x2c */ { "SMBwriteclose",reply_writeclose,AS_USER},
+/* 0x2d */ { "SMBopenX",reply_open_and_X,AS_USER | CAN_IPC },
+/* 0x2e */ { "SMBreadX",reply_read_and_X,AS_USER | CAN_IPC },
+/* 0x2f */ { "SMBwriteX",reply_write_and_X,AS_USER | CAN_IPC },
+/* 0x30 */ { NULL, NULL, 0 },
+/* 0x31 */ { NULL, NULL, 0 },
+/* 0x32 */ { "SMBtrans2",reply_trans2, AS_USER | CAN_IPC },
+/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER | CAN_IPC },
+/* 0x34 */ { "SMBfindclose",reply_findclose,AS_USER},
+/* 0x35 */ { "SMBfindnclose",reply_findnclose,AS_USER},
+/* 0x36 */ { NULL, NULL, 0 },
+/* 0x37 */ { NULL, NULL, 0 },
+/* 0x38 */ { NULL, NULL, 0 },
+/* 0x39 */ { NULL, NULL, 0 },
+/* 0x3a */ { NULL, NULL, 0 },
+/* 0x3b */ { NULL, NULL, 0 },
+/* 0x3c */ { NULL, NULL, 0 },
+/* 0x3d */ { NULL, NULL, 0 },
+/* 0x3e */ { NULL, NULL, 0 },
+/* 0x3f */ { NULL, NULL, 0 },
+/* 0x40 */ { NULL, NULL, 0 },
+/* 0x41 */ { NULL, NULL, 0 },
+/* 0x42 */ { NULL, NULL, 0 },
+/* 0x43 */ { NULL, NULL, 0 },
+/* 0x44 */ { NULL, NULL, 0 },
+/* 0x45 */ { NULL, NULL, 0 },
+/* 0x46 */ { NULL, NULL, 0 },
+/* 0x47 */ { NULL, NULL, 0 },
+/* 0x48 */ { NULL, NULL, 0 },
+/* 0x49 */ { NULL, NULL, 0 },
+/* 0x4a */ { NULL, NULL, 0 },
+/* 0x4b */ { NULL, NULL, 0 },
+/* 0x4c */ { NULL, NULL, 0 },
+/* 0x4d */ { NULL, NULL, 0 },
+/* 0x4e */ { NULL, NULL, 0 },
+/* 0x4f */ { NULL, NULL, 0 },
+/* 0x50 */ { NULL, NULL, 0 },
+/* 0x51 */ { NULL, NULL, 0 },
+/* 0x52 */ { NULL, NULL, 0 },
+/* 0x53 */ { NULL, NULL, 0 },
+/* 0x54 */ { NULL, NULL, 0 },
+/* 0x55 */ { NULL, NULL, 0 },
+/* 0x56 */ { NULL, NULL, 0 },
+/* 0x57 */ { NULL, NULL, 0 },
+/* 0x58 */ { NULL, NULL, 0 },
+/* 0x59 */ { NULL, NULL, 0 },
+/* 0x5a */ { NULL, NULL, 0 },
+/* 0x5b */ { NULL, NULL, 0 },
+/* 0x5c */ { NULL, NULL, 0 },
+/* 0x5d */ { NULL, NULL, 0 },
+/* 0x5e */ { NULL, NULL, 0 },
+/* 0x5f */ { NULL, NULL, 0 },
+/* 0x60 */ { NULL, NULL, 0 },
+/* 0x61 */ { NULL, NULL, 0 },
+/* 0x62 */ { NULL, NULL, 0 },
+/* 0x63 */ { NULL, NULL, 0 },
+/* 0x64 */ { NULL, NULL, 0 },
+/* 0x65 */ { NULL, NULL, 0 },
+/* 0x66 */ { NULL, NULL, 0 },
+/* 0x67 */ { NULL, NULL, 0 },
+/* 0x68 */ { NULL, NULL, 0 },
+/* 0x69 */ { NULL, NULL, 0 },
+/* 0x6a */ { NULL, NULL, 0 },
+/* 0x6b */ { NULL, NULL, 0 },
+/* 0x6c */ { NULL, NULL, 0 },
+/* 0x6d */ { NULL, NULL, 0 },
+/* 0x6e */ { NULL, NULL, 0 },
+/* 0x6f */ { NULL, NULL, 0 },
+/* 0x70 */ { "SMBtcon",reply_tcon,0},
+/* 0x71 */ { "SMBtdis",reply_tdis,DO_CHDIR},
+/* 0x72 */ { "SMBnegprot",reply_negprot,0},
+/* 0x73 */ { "SMBsesssetupX",reply_sesssetup_and_X,0},
+/* 0x74 */ { "SMBulogoffX",reply_ulogoffX, 0}, /* ulogoff doesn't give a valid TID */
+/* 0x75 */ { "SMBtconX",reply_tcon_and_X,0},
+/* 0x76 */ { NULL, NULL, 0 },
+/* 0x77 */ { NULL, NULL, 0 },
+/* 0x78 */ { NULL, NULL, 0 },
+/* 0x79 */ { NULL, NULL, 0 },
+/* 0x7a */ { NULL, NULL, 0 },
+/* 0x7b */ { NULL, NULL, 0 },
+/* 0x7c */ { NULL, NULL, 0 },
+/* 0x7d */ { NULL, NULL, 0 },
+/* 0x7e */ { NULL, NULL, 0 },
+/* 0x7f */ { NULL, NULL, 0 },
+/* 0x80 */ { "SMBdskattr",reply_dskattr,AS_USER},
+/* 0x81 */ { "SMBsearch",reply_search,AS_USER},
+/* 0x82 */ { "SMBffirst",reply_search,AS_USER},
+/* 0x83 */ { "SMBfunique",reply_search,AS_USER},
+/* 0x84 */ { "SMBfclose",reply_fclose,AS_USER},
+/* 0x85 */ { NULL, NULL, 0 },
+/* 0x86 */ { NULL, NULL, 0 },
+/* 0x87 */ { NULL, NULL, 0 },
+/* 0x88 */ { NULL, NULL, 0 },
+/* 0x89 */ { NULL, NULL, 0 },
+/* 0x8a */ { NULL, NULL, 0 },
+/* 0x8b */ { NULL, NULL, 0 },
+/* 0x8c */ { NULL, NULL, 0 },
+/* 0x8d */ { NULL, NULL, 0 },
+/* 0x8e */ { NULL, NULL, 0 },
+/* 0x8f */ { NULL, NULL, 0 },
+/* 0x90 */ { NULL, NULL, 0 },
+/* 0x91 */ { NULL, NULL, 0 },
+/* 0x92 */ { NULL, NULL, 0 },
+/* 0x93 */ { NULL, NULL, 0 },
+/* 0x94 */ { NULL, NULL, 0 },
+/* 0x95 */ { NULL, NULL, 0 },
+/* 0x96 */ { NULL, NULL, 0 },
+/* 0x97 */ { NULL, NULL, 0 },
+/* 0x98 */ { NULL, NULL, 0 },
+/* 0x99 */ { NULL, NULL, 0 },
+/* 0x9a */ { NULL, NULL, 0 },
+/* 0x9b */ { NULL, NULL, 0 },
+/* 0x9c */ { NULL, NULL, 0 },
+/* 0x9d */ { NULL, NULL, 0 },
+/* 0x9e */ { NULL, NULL, 0 },
+/* 0x9f */ { NULL, NULL, 0 },
+/* 0xa0 */ { "SMBnttrans",reply_nttrans, AS_USER | CAN_IPC },
+/* 0xa1 */ { "SMBnttranss",reply_nttranss, AS_USER | CAN_IPC },
+/* 0xa2 */ { "SMBntcreateX",reply_ntcreate_and_X, AS_USER | CAN_IPC },
+/* 0xa3 */ { NULL, NULL, 0 },
+/* 0xa4 */ { "SMBntcancel",reply_ntcancel, 0 },
+/* 0xa5 */ { "SMBntrename",reply_ntrename, AS_USER | NEED_WRITE },
+/* 0xa6 */ { NULL, NULL, 0 },
+/* 0xa7 */ { NULL, NULL, 0 },
+/* 0xa8 */ { NULL, NULL, 0 },
+/* 0xa9 */ { NULL, NULL, 0 },
+/* 0xaa */ { NULL, NULL, 0 },
+/* 0xab */ { NULL, NULL, 0 },
+/* 0xac */ { NULL, NULL, 0 },
+/* 0xad */ { NULL, NULL, 0 },
+/* 0xae */ { NULL, NULL, 0 },
+/* 0xaf */ { NULL, NULL, 0 },
+/* 0xb0 */ { NULL, NULL, 0 },
+/* 0xb1 */ { NULL, NULL, 0 },
+/* 0xb2 */ { NULL, NULL, 0 },
+/* 0xb3 */ { NULL, NULL, 0 },
+/* 0xb4 */ { NULL, NULL, 0 },
+/* 0xb5 */ { NULL, NULL, 0 },
+/* 0xb6 */ { NULL, NULL, 0 },
+/* 0xb7 */ { NULL, NULL, 0 },
+/* 0xb8 */ { NULL, NULL, 0 },
+/* 0xb9 */ { NULL, NULL, 0 },
+/* 0xba */ { NULL, NULL, 0 },
+/* 0xbb */ { NULL, NULL, 0 },
+/* 0xbc */ { NULL, NULL, 0 },
+/* 0xbd */ { NULL, NULL, 0 },
+/* 0xbe */ { NULL, NULL, 0 },
+/* 0xbf */ { NULL, NULL, 0 },
+/* 0xc0 */ { "SMBsplopen",reply_printopen,AS_USER},
+/* 0xc1 */ { "SMBsplwr",reply_printwrite,AS_USER},
+/* 0xc2 */ { "SMBsplclose",reply_printclose,AS_USER},
+/* 0xc3 */ { "SMBsplretq",reply_printqueue,AS_USER},
+/* 0xc4 */ { NULL, NULL, 0 },
+/* 0xc5 */ { NULL, NULL, 0 },
+/* 0xc6 */ { NULL, NULL, 0 },
+/* 0xc7 */ { NULL, NULL, 0 },
+/* 0xc8 */ { NULL, NULL, 0 },
+/* 0xc9 */ { NULL, NULL, 0 },
+/* 0xca */ { NULL, NULL, 0 },
+/* 0xcb */ { NULL, NULL, 0 },
+/* 0xcc */ { NULL, NULL, 0 },
+/* 0xcd */ { NULL, NULL, 0 },
+/* 0xce */ { NULL, NULL, 0 },
+/* 0xcf */ { NULL, NULL, 0 },
+/* 0xd0 */ { "SMBsends",reply_sends,AS_GUEST},
+/* 0xd1 */ { "SMBsendb", NULL,AS_GUEST},
+/* 0xd2 */ { "SMBfwdname", NULL,AS_GUEST},
+/* 0xd3 */ { "SMBcancelf", NULL,AS_GUEST},
+/* 0xd4 */ { "SMBgetmac", NULL,AS_GUEST},
+/* 0xd5 */ { "SMBsendstrt",reply_sendstrt,AS_GUEST},
+/* 0xd6 */ { "SMBsendend",reply_sendend,AS_GUEST},
+/* 0xd7 */ { "SMBsendtxt",reply_sendtxt,AS_GUEST},
+/* 0xd8 */ { NULL, NULL, 0 },
+/* 0xd9 */ { NULL, NULL, 0 },
+/* 0xda */ { NULL, NULL, 0 },
+/* 0xdb */ { NULL, NULL, 0 },
+/* 0xdc */ { NULL, NULL, 0 },
+/* 0xdd */ { NULL, NULL, 0 },
+/* 0xde */ { NULL, NULL, 0 },
+/* 0xdf */ { NULL, NULL, 0 },
+/* 0xe0 */ { NULL, NULL, 0 },
+/* 0xe1 */ { NULL, NULL, 0 },
+/* 0xe2 */ { NULL, NULL, 0 },
+/* 0xe3 */ { NULL, NULL, 0 },
+/* 0xe4 */ { NULL, NULL, 0 },
+/* 0xe5 */ { NULL, NULL, 0 },
+/* 0xe6 */ { NULL, NULL, 0 },
+/* 0xe7 */ { NULL, NULL, 0 },
+/* 0xe8 */ { NULL, NULL, 0 },
+/* 0xe9 */ { NULL, NULL, 0 },
+/* 0xea */ { NULL, NULL, 0 },
+/* 0xeb */ { NULL, NULL, 0 },
+/* 0xec */ { NULL, NULL, 0 },
+/* 0xed */ { NULL, NULL, 0 },
+/* 0xee */ { NULL, NULL, 0 },
+/* 0xef */ { NULL, NULL, 0 },
+/* 0xf0 */ { NULL, NULL, 0 },
+/* 0xf1 */ { NULL, NULL, 0 },
+/* 0xf2 */ { NULL, NULL, 0 },
+/* 0xf3 */ { NULL, NULL, 0 },
+/* 0xf4 */ { NULL, NULL, 0 },
+/* 0xf5 */ { NULL, NULL, 0 },
+/* 0xf6 */ { NULL, NULL, 0 },
+/* 0xf7 */ { NULL, NULL, 0 },
+/* 0xf8 */ { NULL, NULL, 0 },
+/* 0xf9 */ { NULL, NULL, 0 },
+/* 0xfa */ { NULL, NULL, 0 },
+/* 0xfb */ { NULL, NULL, 0 },
+/* 0xfc */ { NULL, NULL, 0 },
+/* 0xfd */ { NULL, NULL, 0 },
+/* 0xfe */ { NULL, NULL, 0 },
+/* 0xff */ { NULL, NULL, 0 }
+
+};
+
+
+/*******************************************************************
+ Dump a packet to a file.
+********************************************************************/
+
+static void smb_dump(const char *name, int type, const char *data)
+{
+ size_t len;
+ int fd, i;
+ char *fname = NULL;
+ if (DEBUGLEVEL < 50) {
+ return;
+ }
+
+ len = smb_len_tcp(data)+4;
+ for (i=1;i<100;i++) {
+ fname = talloc_asprintf(talloc_tos(),
+ "/tmp/%s.%d.%s",
+ name,
+ i,
+ type ? "req" : "resp");
+ if (fname == NULL) {
+ return;
+ }
+ fd = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0644);
+ if (fd != -1 || errno != EEXIST) break;
+ TALLOC_FREE(fname);
+ }
+ if (fd != -1) {
+ ssize_t ret = write(fd, data, len);
+ if (ret != len)
+ DEBUG(0,("smb_dump: problem: write returned %d\n", (int)ret ));
+ close(fd);
+ DEBUG(0,("created %s len %lu\n", fname, (unsigned long)len));
+ }
+ TALLOC_FREE(fname);
+}
+
+static void smb1srv_update_crypto_flags(struct smbXsrv_session *session,
+ struct smb_request *req,
+ uint8_t type,
+ bool *update_session_globalp,
+ bool *update_tcon_globalp)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_tcon *tcon = conn ? conn->tcon : NULL;
+ uint8_t encrypt_flag = SMBXSRV_PROCESSED_UNENCRYPTED_PACKET;
+ uint8_t sign_flag = SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ bool update_session = false;
+ bool update_tcon = false;
+
+ if (req->encrypted) {
+ encrypt_flag = SMBXSRV_PROCESSED_ENCRYPTED_PACKET;
+ }
+
+ if (smb1_srv_is_signing_active(req->xconn)) {
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ } else if ((type == SMBecho) || (type == SMBsesssetupX)) {
+ /*
+ * echo can be unsigned. Session setup except final
+ * session setup response too
+ */
+ sign_flag &= ~SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ }
+
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->encryption_flags, encrypt_flag);
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->signing_flags, sign_flag);
+
+ if (tcon) {
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->encryption_flags, encrypt_flag);
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->signing_flags, sign_flag);
+ }
+
+ if (update_session) {
+ session->global->channels[0].encryption_cipher = SMB_ENCRYPTION_GSSAPI;
+ }
+
+ *update_session_globalp = update_session;
+ *update_tcon_globalp = update_tcon;
+ return;
+}
+
+static void set_current_case_sensitive(connection_struct *conn, uint16_t flags)
+{
+ int snum;
+ enum remote_arch_types ra_type;
+
+ SMB_ASSERT(conn != NULL);
+ SMB_ASSERT(!conn->sconn->using_smb2);
+
+ snum = SNUM(conn);
+
+ /*
+ * Obey the client case sensitivity requests - only for clients that
+ * support it. */
+ switch (lp_case_sensitive(snum)) {
+ case Auto:
+ /*
+ * We need this ugliness due to DOS/Win9x clients that lie
+ * about case insensitivity. */
+ ra_type = get_remote_arch();
+ if ((ra_type != RA_SAMBA) && (ra_type != RA_CIFSFS)) {
+ /*
+ * Client can't support per-packet case sensitive
+ * pathnames. */
+ conn->case_sensitive = false;
+ } else {
+ conn->case_sensitive =
+ !(flags & FLAG_CASELESS_PATHNAMES);
+ }
+ break;
+ case True:
+ conn->case_sensitive = true;
+ break;
+ default:
+ conn->case_sensitive = false;
+ break;
+ }
+}
+
+/****************************************************************************
+ Prepare everything for calling the actual request function, and potentially
+ call the request function via the "new" interface.
+
+ Return False if the "legacy" function needs to be called, everything is
+ prepared.
+
+ Return True if we're done.
+
+ I know this API sucks, but it is the one with the least code change I could
+ find.
+****************************************************************************/
+
+static connection_struct *switch_message(uint8_t type, struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int flags;
+ uint64_t session_tag;
+ connection_struct *conn = NULL;
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ errno = 0;
+
+ if (!xconn->smb1.negprot.done) {
+ switch (type) {
+ /*
+ * Without a negprot the request must
+ * either be a negprot, or one of the
+ * evil old SMB mailslot messaging types.
+ */
+ case SMBnegprot:
+ case SMBsendstrt:
+ case SMBsendend:
+ case SMBsendtxt:
+ break;
+ default:
+ exit_server_cleanly("The first request "
+ "should be a negprot");
+ }
+ }
+
+ if (smb_messages[type].fn == NULL) {
+ DEBUG(0,("Unknown message type %d!\n",type));
+ smb_dump("Unknown", 1, (const char *)req->inbuf);
+ reply_unknown_new(req, type);
+ return NULL;
+ }
+
+ flags = smb_messages[type].flags;
+
+ /* In share mode security we must ignore the vuid. */
+ session_tag = req->vuid;
+ conn = req->conn;
+
+ DEBUG(3,("switch message %s (pid %d) conn 0x%lx\n", smb_fn_name(type),
+ (int)getpid(), (unsigned long)conn));
+
+ smb_dump(smb_fn_name(type), 1, (const char *)req->inbuf);
+
+ /* Ensure this value is replaced in the incoming packet. */
+ SSVAL(discard_const_p(uint8_t, req->inbuf),smb_uid,session_tag);
+
+ /*
+ * Ensure the correct username is in current_user_info. This is a
+ * really ugly bugfix for problems with multiple session_setup_and_X's
+ * being done and allowing %U and %G substitutions to work correctly.
+ * There is a reason this code is done here, don't move it unless you
+ * know what you're doing... :-).
+ * JRA.
+ */
+
+ /*
+ * lookup an existing session
+ *
+ * Note: for now we only check for NT_STATUS_NETWORK_SESSION_EXPIRED
+ * here, the main check is still in change_to_user()
+ */
+ status = smb1srv_session_lookup(xconn,
+ session_tag,
+ now,
+ &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ switch (type) {
+ case SMBsesssetupX:
+ status = NT_STATUS_OK;
+ break;
+ default:
+ DEBUG(1,("Error: session %llu is expired, mid=%llu.\n",
+ (unsigned long long)session_tag,
+ (unsigned long long)req->mid));
+ reply_nterror(req, NT_STATUS_NETWORK_SESSION_EXPIRED);
+ return conn;
+ }
+ }
+
+ if (session != NULL &&
+ session->global->auth_session_info != NULL &&
+ !(flags & AS_USER))
+ {
+ /*
+ * change_to_user() implies set_current_user_info()
+ * and chdir_connect_service().
+ *
+ * So we only call set_current_user_info if
+ * we don't have AS_USER specified.
+ */
+ set_current_user_info(
+ session->global->auth_session_info->unix_info->sanitized_username,
+ session->global->auth_session_info->unix_info->unix_name,
+ session->global->auth_session_info->info->domain_name);
+ }
+
+ /* Does this call need to be run as the connected user? */
+ if (flags & AS_USER) {
+
+ /* Does this call need a valid tree connection? */
+ if (!conn) {
+ /*
+ * Amazingly, the error code depends on the command
+ * (from Samba4).
+ */
+ if (type == SMBntcreateX) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ } else {
+ reply_nterror(req, NT_STATUS_NETWORK_NAME_DELETED);
+ }
+ return NULL;
+ }
+
+ set_current_case_sensitive(conn, SVAL(req->inbuf,smb_flg));
+
+ /*
+ * change_to_user() implies set_current_user_info()
+ * and chdir_connect_service().
+ */
+ if (!change_to_user_and_service(conn,session_tag)) {
+ DEBUG(0, ("Error: Could not change to user. Removing "
+ "deferred open, mid=%llu.\n",
+ (unsigned long long)req->mid));
+ reply_force_doserror(req, ERRSRV, ERRbaduid);
+ return conn;
+ }
+
+ /* All NEED_WRITE and CAN_IPC flags must also have AS_USER. */
+
+ /* Does it need write permission? */
+ if ((flags & NEED_WRITE) && !CAN_WRITE(conn)) {
+ reply_nterror(req, NT_STATUS_MEDIA_WRITE_PROTECTED);
+ return conn;
+ }
+
+ /* IPC services are limited */
+ if (IS_IPC(conn) && !(flags & CAN_IPC)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ } else if (flags & AS_GUEST) {
+ /*
+ * Does this protocol need to be run as guest? (Only archane
+ * messenger service requests have this...)
+ */
+ if (!change_to_guest()) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ } else {
+ /* This call needs to be run as root */
+ change_to_root_user();
+ }
+
+ /* load service specific parameters */
+ if (conn) {
+ if (req->encrypted) {
+ conn->encrypted_tid = true;
+ /* encrypted required from now on. */
+ conn->encrypt_level = SMB_SIGNING_REQUIRED;
+ } else if (ENCRYPTION_REQUIRED(conn)) {
+ if (req->cmd != SMBtrans2 && req->cmd != SMBtranss2) {
+ DEBUG(1,("service[%s] requires encryption"
+ "%s ACCESS_DENIED. mid=%llu\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ smb_fn_name(type),
+ (unsigned long long)req->mid));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ }
+
+ if (flags & DO_CHDIR) {
+ bool ok;
+
+ ok = chdir_current_service(conn);
+ if (!ok) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return conn;
+ }
+ }
+ conn->num_smb_operations++;
+ }
+
+ /*
+ * Update encryption and signing state tracking flags that are
+ * used by smbstatus to display signing and encryption status.
+ */
+ if (session != NULL) {
+ bool update_session_global = false;
+ bool update_tcon_global = false;
+
+ req->session = session;
+
+ smb1srv_update_crypto_flags(session, req, type,
+ &update_session_global,
+ &update_tcon_global);
+
+ if (update_session_global) {
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return conn;
+ }
+ }
+
+ if (update_tcon_global) {
+ status = smbXsrv_tcon_update(req->conn->tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return conn;
+ }
+ }
+ }
+
+ smb_messages[type].fn(req);
+ return req->conn;
+}
+
+/****************************************************************************
+ Construct a reply to the incoming packet.
+****************************************************************************/
+
+void construct_reply(struct smbXsrv_connection *xconn,
+ char *inbuf,
+ int size,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct smb_request *req;
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ smb_panic("could not allocate smb_request");
+ }
+
+ if (!init_smb1_request(req, sconn, xconn, (uint8_t *)inbuf, unread_bytes,
+ encrypted, seqnum)) {
+ exit_server_cleanly("Invalid SMB request");
+ }
+
+ req->inbuf = (uint8_t *)talloc_move(req, &inbuf);
+
+ req->conn = switch_message(req->cmd, req);
+
+ if (req->outbuf == NULL) {
+ /*
+ * Request has suspended itself, will come
+ * back here.
+ */
+ return;
+ }
+ if (CVAL(req->outbuf,0) == 0) {
+ show_msg((char *)req->outbuf);
+ }
+ smb_request_done(req);
+}
+
+static void construct_reply_chain(struct smbXsrv_connection *xconn,
+ char *inbuf,
+ int size,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smb_request **reqs = NULL;
+ struct smb_request *req;
+ unsigned num_reqs;
+ bool ok;
+
+ ok = smb1_parse_chain(xconn, (uint8_t *)inbuf, xconn, encrypted,
+ seqnum, &reqs, &num_reqs);
+ if (!ok) {
+ char errbuf[smb_size];
+ error_packet(errbuf, 0, 0, NT_STATUS_INVALID_PARAMETER,
+ __LINE__, __FILE__);
+ if (!smb1_srv_send(xconn, errbuf, true, seqnum, encrypted)) {
+ exit_server_cleanly("construct_reply_chain: "
+ "smb1_srv_send failed.");
+ }
+ return;
+ }
+
+ req = reqs[0];
+ req->inbuf = (uint8_t *)talloc_move(reqs, &inbuf);
+
+ req->conn = switch_message(req->cmd, req);
+
+ if (req->outbuf == NULL) {
+ /*
+ * Request has suspended itself, will come
+ * back here.
+ */
+ return;
+ }
+ smb_request_done(req);
+}
+
+/*
+ * To be called from an async SMB handler that is potentially chained
+ * when it is finished for shipping.
+ */
+
+void smb_request_done(struct smb_request *req)
+{
+ struct smb_request **reqs = NULL;
+ struct smb_request *first_req;
+ size_t i, num_reqs, next_index;
+ NTSTATUS status;
+
+ if (req->chain == NULL) {
+ first_req = req;
+ goto shipit;
+ }
+
+ reqs = req->chain;
+ num_reqs = talloc_array_length(reqs);
+
+ for (i=0; i<num_reqs; i++) {
+ if (reqs[i] == req) {
+ break;
+ }
+ }
+ if (i == num_reqs) {
+ /*
+ * Invalid chain, should not happen
+ */
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ next_index = i+1;
+
+ while ((next_index < num_reqs) && (IVAL(req->outbuf, smb_rcls) == 0)) {
+ struct smb_request *next = reqs[next_index];
+ struct smbXsrv_tcon *tcon;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ next->vuid = SVAL(req->outbuf, smb_uid);
+ next->tid = SVAL(req->outbuf, smb_tid);
+ status = smb1srv_tcon_lookup(req->xconn, next->tid,
+ now, &tcon);
+
+ if (NT_STATUS_IS_OK(status)) {
+ next->conn = tcon->compat;
+ } else {
+ next->conn = NULL;
+ }
+ next->chain_fsp = req->chain_fsp;
+ next->inbuf = req->inbuf;
+
+ req = next;
+ req->conn = switch_message(req->cmd, req);
+
+ if (req->outbuf == NULL) {
+ /*
+ * Request has suspended itself, will come
+ * back here.
+ */
+ return;
+ }
+ next_index += 1;
+ }
+
+ first_req = reqs[0];
+
+ for (i=1; i<next_index; i++) {
+ bool ok;
+
+ ok = smb_splice_chain(&first_req->outbuf, reqs[i]->outbuf);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+ }
+
+ SSVAL(first_req->outbuf, smb_uid, SVAL(req->outbuf, smb_uid));
+ SSVAL(first_req->outbuf, smb_tid, SVAL(req->outbuf, smb_tid));
+
+ /*
+ * This scary statement intends to set the
+ * FLAGS2_32_BIT_ERROR_CODES flg2 field in first_req->outbuf
+ * to the value last_req->outbuf carries
+ */
+ SSVAL(first_req->outbuf, smb_flg2,
+ (SVAL(first_req->outbuf, smb_flg2) & ~FLAGS2_32_BIT_ERROR_CODES)
+ |(SVAL(req->outbuf, smb_flg2) & FLAGS2_32_BIT_ERROR_CODES));
+
+ /*
+ * Transfer the error codes from the subrequest to the main one
+ */
+ SSVAL(first_req->outbuf, smb_rcls, SVAL(req->outbuf, smb_rcls));
+ SSVAL(first_req->outbuf, smb_err, SVAL(req->outbuf, smb_err));
+
+ _smb_setlen_large(
+ first_req->outbuf, talloc_get_size(first_req->outbuf) - 4);
+
+shipit:
+ if (!smb1_srv_send(first_req->xconn,
+ (char *)first_req->outbuf,
+ true,
+ first_req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) ||
+ first_req->encrypted)) {
+ exit_server_cleanly("construct_reply_chain: smb1_srv_send "
+ "failed.");
+ }
+ TALLOC_FREE(req); /* non-chained case */
+ TALLOC_FREE(reqs); /* chained case */
+ return;
+
+error:
+ {
+ char errbuf[smb_size];
+ error_packet(errbuf, 0, 0, status, __LINE__, __FILE__);
+ if (!smb1_srv_send(req->xconn,
+ errbuf,
+ true,
+ req->seqnum + 1,
+ req->encrypted)) {
+ exit_server_cleanly("construct_reply_chain: "
+ "smb1_srv_send failed.");
+ }
+ }
+ TALLOC_FREE(req); /* non-chained case */
+ TALLOC_FREE(reqs); /* chained case */
+}
+
+/****************************************************************************
+ Process an smb from the client
+****************************************************************************/
+
+void process_smb1(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+
+ /* Make sure this is an SMB packet. smb_size contains NetBIOS header
+ * so subtract 4 from it. */
+ if ((nread < (smb_size - 4)) || !valid_smb1_header(inbuf)) {
+ DEBUG(2,("Non-SMB packet of length %d. Terminating server\n",
+ smb_len(inbuf)));
+
+ /* special magic for immediate exit */
+ if ((nread == 9) &&
+ (IVAL(inbuf, 4) == SMB_SUICIDE_PACKET) &&
+ lp_parm_bool(-1, "smbd", "suicide mode", false)) {
+ uint8_t exitcode = CVAL(inbuf, 8);
+ DBG_WARNING("SUICIDE: Exiting immediately with code %d\n",
+ (int)exitcode);
+ exit(exitcode);
+ }
+
+ exit_server_cleanly("Non-SMB packet");
+ }
+
+ show_msg((char *)inbuf);
+
+ if ((unread_bytes == 0) && smb1_is_chain(inbuf)) {
+ construct_reply_chain(xconn,
+ (char *)inbuf,
+ nread,
+ seqnum,
+ encrypted);
+ } else {
+ construct_reply(xconn,
+ (char *)inbuf,
+ nread,
+ unread_bytes,
+ seqnum,
+ encrypted);
+ }
+
+ sconn->trans_num++;
+}
+
+/****************************************************************************
+ Return a string containing the function name of a SMB command.
+****************************************************************************/
+
+const char *smb_fn_name(int type)
+{
+ const char *unknown_name = "SMBunknown";
+
+ if (smb_messages[type].name == NULL)
+ return(unknown_name);
+
+ return(smb_messages[type].name);
+}
+
+/****************************************************************************
+ Helper functions for contruct_reply.
+****************************************************************************/
+
+void add_to_common_flags2(uint32_t v)
+{
+ common_flags2 |= v;
+}
+
+void remove_from_common_flags2(uint32_t v)
+{
+ common_flags2 &= ~v;
+}
+
+/**
+ * @brief Find the smb_cmd offset of the last command pushed
+ * @param[in] buf The buffer we're building up
+ * @retval Where can we put our next andx cmd?
+ *
+ * While chaining requests, the "next" request we're looking at needs to put
+ * its SMB_Command before the data the previous request already built up added
+ * to the chain. Find the offset to the place where we have to put our cmd.
+ */
+
+static bool find_andx_cmd_ofs(uint8_t *buf, size_t *pofs)
+{
+ uint8_t cmd;
+ size_t ofs;
+
+ cmd = CVAL(buf, smb_com);
+
+ if (!smb1cli_is_andx_req(cmd)) {
+ return false;
+ }
+
+ ofs = smb_vwv0;
+
+ while (CVAL(buf, ofs) != 0xff) {
+
+ if (!smb1cli_is_andx_req(CVAL(buf, ofs))) {
+ return false;
+ }
+
+ /*
+ * ofs is from start of smb header, so add the 4 length
+ * bytes. The next cmd is right after the wct field.
+ */
+ ofs = SVAL(buf, ofs+2) + 4 + 1;
+
+ if (ofs+4 >= talloc_get_size(buf)) {
+ return false;
+ }
+ }
+
+ *pofs = ofs;
+ return true;
+}
+
+/**
+ * @brief Do the smb chaining at a buffer level
+ * @param[in] poutbuf Pointer to the talloc'ed buffer to be modified
+ * @param[in] andx_buf Buffer to be appended
+ */
+
+static bool smb_splice_chain(uint8_t **poutbuf, const uint8_t *andx_buf)
+{
+ uint8_t smb_command = CVAL(andx_buf, smb_com);
+ uint8_t wct = CVAL(andx_buf, smb_wct);
+ const uint16_t *vwv = (const uint16_t *)(andx_buf + smb_vwv);
+ uint32_t num_bytes = smb_buflen(andx_buf);
+ const uint8_t *bytes = (const uint8_t *)smb_buf_const(andx_buf);
+
+ uint8_t *outbuf;
+ size_t old_size, new_size;
+ size_t ofs;
+ size_t chain_padding = 0;
+ size_t andx_cmd_ofs;
+
+
+ old_size = talloc_get_size(*poutbuf);
+
+ if ((old_size % 4) != 0) {
+ /*
+ * Align the wct field of subsequent requests to a 4-byte
+ * boundary
+ */
+ chain_padding = 4 - (old_size % 4);
+ }
+
+ /*
+ * After the old request comes the new wct field (1 byte), the vwv's
+ * and the num_bytes field.
+ */
+
+ new_size = old_size + chain_padding + 1 + wct * sizeof(uint16_t) + 2;
+ new_size += num_bytes;
+
+ if ((smb_command != SMBwriteX) && (new_size > 0xffff)) {
+ DEBUG(1, ("smb_splice_chain: %u bytes won't fit\n",
+ (unsigned)new_size));
+ return false;
+ }
+
+ outbuf = talloc_realloc(NULL, *poutbuf, uint8_t, new_size);
+ if (outbuf == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return false;
+ }
+ *poutbuf = outbuf;
+
+ if (!find_andx_cmd_ofs(outbuf, &andx_cmd_ofs)) {
+ DEBUG(1, ("invalid command chain\n"));
+ *poutbuf = talloc_realloc(NULL, *poutbuf, uint8_t, old_size);
+ return false;
+ }
+
+ if (chain_padding != 0) {
+ memset(outbuf + old_size, 0, chain_padding);
+ old_size += chain_padding;
+ }
+
+ SCVAL(outbuf, andx_cmd_ofs, smb_command);
+ SSVAL(outbuf, andx_cmd_ofs + 2, old_size - 4);
+
+ ofs = old_size;
+
+ /*
+ * Push the chained request:
+ *
+ * wct field
+ */
+
+ SCVAL(outbuf, ofs, wct);
+ ofs += 1;
+
+ /*
+ * vwv array
+ */
+
+ memcpy(outbuf + ofs, vwv, sizeof(uint16_t) * wct);
+
+ /*
+ * HACK ALERT
+ *
+ * Read&X has an offset into its data buffer at
+ * vwv[6]. reply_read_andx has no idea anymore that it's
+ * running from within a chain, so we have to fix up the
+ * offset here.
+ *
+ * Although it looks disgusting at this place, I want to keep
+ * it here. The alternative would be to push knowledge about
+ * the andx chain down into read&x again.
+ */
+
+ if (smb_command == SMBreadX) {
+ uint8_t *bytes_addr;
+
+ if (wct < 7) {
+ /*
+ * Invalid read&x response
+ */
+ return false;
+ }
+
+ bytes_addr = outbuf + ofs /* vwv start */
+ + sizeof(uint16_t) * wct /* vwv array */
+ + sizeof(uint16_t) /* bcc */
+ + 1; /* padding byte */
+
+ SSVAL(outbuf + ofs, 6 * sizeof(uint16_t),
+ bytes_addr - outbuf - 4);
+ }
+
+ ofs += sizeof(uint16_t) * wct;
+
+ /*
+ * bcc (byte count)
+ */
+
+ SSVAL(outbuf, ofs, num_bytes);
+ ofs += sizeof(uint16_t);
+
+ /*
+ * The bytes field
+ */
+
+ memcpy(outbuf + ofs, bytes, num_bytes);
+
+ return true;
+}
+
+bool smb1_is_chain(const uint8_t *buf)
+{
+ uint8_t cmd, wct, andx_cmd;
+
+ cmd = CVAL(buf, smb_com);
+ if (!smb1cli_is_andx_req(cmd)) {
+ return false;
+ }
+ wct = CVAL(buf, smb_wct);
+ if (wct < 2) {
+ return false;
+ }
+ andx_cmd = CVAL(buf, smb_vwv);
+ return (andx_cmd != 0xFF);
+}
+
+bool smb1_walk_chain(const uint8_t *buf,
+ bool (*fn)(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data),
+ void *private_data)
+{
+ size_t smblen = smb_len(buf);
+ const char *smb_buf = smb_base(buf);
+ uint8_t cmd, chain_cmd;
+ uint8_t wct;
+ const uint16_t *vwv;
+ uint16_t num_bytes;
+ const uint8_t *bytes;
+
+ cmd = CVAL(buf, smb_com);
+ wct = CVAL(buf, smb_wct);
+ vwv = (const uint16_t *)(buf + smb_vwv);
+ num_bytes = smb_buflen(buf);
+ bytes = (const uint8_t *)smb_buf_const(buf);
+
+ if (!fn(cmd, wct, vwv, num_bytes, bytes, private_data)) {
+ return false;
+ }
+
+ if (!smb1cli_is_andx_req(cmd)) {
+ return true;
+ }
+ if (wct < 2) {
+ return false;
+ }
+
+ chain_cmd = CVAL(vwv, 0);
+
+ while (chain_cmd != 0xff) {
+ uint32_t chain_offset; /* uint32_t to avoid overflow */
+ size_t length_needed;
+ ptrdiff_t vwv_offset;
+
+ chain_offset = SVAL(vwv+1, 0);
+
+ /*
+ * Check if the client tries to fool us. The chain
+ * offset needs to point beyond the current request in
+ * the chain, it needs to strictly grow. Otherwise we
+ * might be tricked into an endless loop always
+ * processing the same request over and over again. We
+ * used to assume that vwv and the byte buffer array
+ * in a chain are always attached, but OS/2 the
+ * Write&X/Read&X chain puts the Read&X vwv array
+ * right behind the Write&X vwv chain. The Write&X bcc
+ * array is put behind the Read&X vwv array. So now we
+ * check whether the chain offset points strictly
+ * behind the previous vwv array. req->buf points
+ * right after the vwv array of the previous
+ * request. See
+ * https://bugzilla.samba.org/show_bug.cgi?id=8360 for
+ * more information.
+ */
+
+ vwv_offset = ((const char *)vwv - smb_buf);
+ if (chain_offset <= vwv_offset) {
+ return false;
+ }
+
+ /*
+ * Next check: Make sure the chain offset does not
+ * point beyond the overall smb request length.
+ */
+
+ length_needed = chain_offset+1; /* wct */
+ if (length_needed > smblen) {
+ return false;
+ }
+
+ /*
+ * Now comes the pointer magic. Goal here is to set up
+ * vwv and buf correctly again. The chain offset (the
+ * former vwv[1]) points at the new wct field.
+ */
+
+ wct = CVAL(smb_buf, chain_offset);
+
+ if (smb1cli_is_andx_req(chain_cmd) && (wct < 2)) {
+ return false;
+ }
+
+ /*
+ * Next consistency check: Make the new vwv array fits
+ * in the overall smb request.
+ */
+
+ length_needed += (wct+1)*sizeof(uint16_t); /* vwv+buflen */
+ if (length_needed > smblen) {
+ return false;
+ }
+ vwv = (const uint16_t *)(smb_buf + chain_offset + 1);
+
+ /*
+ * Now grab the new byte buffer....
+ */
+
+ num_bytes = SVAL(vwv+wct, 0);
+
+ /*
+ * .. and check that it fits.
+ */
+
+ length_needed += num_bytes;
+ if (length_needed > smblen) {
+ return false;
+ }
+ bytes = (const uint8_t *)(vwv+wct+1);
+
+ if (!fn(chain_cmd, wct, vwv, num_bytes, bytes, private_data)) {
+ return false;
+ }
+
+ if (!smb1cli_is_andx_req(chain_cmd)) {
+ return true;
+ }
+ chain_cmd = CVAL(vwv, 0);
+ }
+ return true;
+}
+
+static bool smb1_chain_length_cb(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data)
+{
+ unsigned *count = (unsigned *)private_data;
+ *count += 1;
+ return true;
+}
+
+unsigned smb1_chain_length(const uint8_t *buf)
+{
+ unsigned count = 0;
+
+ if (!smb1_walk_chain(buf, smb1_chain_length_cb, &count)) {
+ return 0;
+ }
+ return count;
+}
+
+struct smb1_parse_chain_state {
+ TALLOC_CTX *mem_ctx;
+ const uint8_t *buf;
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ bool encrypted;
+ uint32_t seqnum;
+
+ struct smb_request **reqs;
+ unsigned num_reqs;
+};
+
+static bool smb1_parse_chain_cb(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data)
+{
+ struct smb1_parse_chain_state *state =
+ (struct smb1_parse_chain_state *)private_data;
+ struct smb_request **reqs;
+ struct smb_request *req;
+ bool ok;
+
+ reqs = talloc_realloc(state->mem_ctx, state->reqs,
+ struct smb_request *, state->num_reqs+1);
+ if (reqs == NULL) {
+ return false;
+ }
+ state->reqs = reqs;
+
+ req = talloc(reqs, struct smb_request);
+ if (req == NULL) {
+ return false;
+ }
+
+ ok = init_smb1_request(req, state->sconn, state->xconn, state->buf, 0,
+ state->encrypted, state->seqnum);
+ if (!ok) {
+ return false;
+ }
+ req->cmd = cmd;
+ req->wct = wct;
+ req->vwv = vwv;
+ req->buflen = num_bytes;
+ req->buf = bytes;
+
+ reqs[state->num_reqs] = req;
+ state->num_reqs += 1;
+ return true;
+}
+
+bool smb1_parse_chain(TALLOC_CTX *mem_ctx, const uint8_t *buf,
+ struct smbXsrv_connection *xconn,
+ bool encrypted, uint32_t seqnum,
+ struct smb_request ***reqs, unsigned *num_reqs)
+{
+ struct smbd_server_connection *sconn = NULL;
+ struct smb1_parse_chain_state state;
+ unsigned i;
+
+ if (xconn != NULL) {
+ sconn = xconn->client->sconn;
+ }
+
+ state.mem_ctx = mem_ctx;
+ state.buf = buf;
+ state.sconn = sconn;
+ state.xconn = xconn;
+ state.encrypted = encrypted;
+ state.seqnum = seqnum;
+ state.reqs = NULL;
+ state.num_reqs = 0;
+
+ if (!smb1_walk_chain(buf, smb1_parse_chain_cb, &state)) {
+ TALLOC_FREE(state.reqs);
+ return false;
+ }
+ for (i=0; i<state.num_reqs; i++) {
+ state.reqs[i]->chain = state.reqs;
+ }
+ *reqs = state.reqs;
+ *num_reqs = state.num_reqs;
+ return true;
+}
+
+static bool fd_is_readable(int fd)
+{
+ int ret, revents;
+
+ ret = poll_one_fd(fd, POLLIN|POLLHUP, 0, &revents);
+
+ return ((ret > 0) && ((revents & (POLLIN|POLLHUP|POLLERR)) != 0));
+
+}
+
+static void smbd_server_connection_write_handler(
+ struct smbXsrv_connection *xconn)
+{
+ /* TODO: make write nonblocking */
+}
+
+void smbd_smb1_server_connection_read_handler(struct smbXsrv_connection *xconn,
+ int fd)
+{
+ uint8_t *inbuf = NULL;
+ size_t inbuf_len = 0;
+ size_t unread_bytes = 0;
+ bool encrypted = false;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ uint32_t seqnum;
+
+ bool async_echo = lp_async_smb_echo_handler();
+ bool from_client = false;
+
+ if (async_echo) {
+ if (fd_is_readable(xconn->smb1.echo_handler.trusted_fd)) {
+ /*
+ * This is the super-ugly hack to prefer the packets
+ * forwarded by the echo handler over the ones by the
+ * client directly
+ */
+ fd = xconn->smb1.echo_handler.trusted_fd;
+ }
+ }
+
+ from_client = (xconn->transport.sock == fd);
+
+ if (async_echo && from_client) {
+ smbd_lock_socket(xconn);
+
+ if (!fd_is_readable(fd)) {
+ DEBUG(10,("the echo listener was faster\n"));
+ smbd_unlock_socket(xconn);
+ return;
+ }
+ }
+
+ /* TODO: make this completely nonblocking */
+ status = receive_smb_talloc(mem_ctx, xconn, fd,
+ (char **)(void *)&inbuf,
+ 0, /* timeout */
+ &unread_bytes,
+ &encrypted,
+ &inbuf_len, &seqnum,
+ !from_client /* trusted channel */);
+
+ if (async_echo && from_client) {
+ smbd_unlock_socket(xconn);
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ goto process;
+ }
+ if (NT_STATUS_IS_ERR(status)) {
+ exit_server_cleanly("failed to receive smb request");
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+process:
+ process_smb(xconn, inbuf, inbuf_len, unread_bytes, seqnum, encrypted);
+}
+
+static void smbd_server_echo_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbXsrv_connection *xconn =
+ talloc_get_type_abort(private_data,
+ struct smbXsrv_connection);
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ TEVENT_FD_NOT_READABLE(xconn->smb1.echo_handler.trusted_fde);
+ TEVENT_FD_NOT_WRITEABLE(xconn->smb1.echo_handler.trusted_fde);
+ return;
+ }
+
+ if (flags & TEVENT_FD_WRITE) {
+ smbd_server_connection_write_handler(xconn);
+ return;
+ }
+ if (flags & TEVENT_FD_READ) {
+ smbd_smb1_server_connection_read_handler(
+ xconn, xconn->smb1.echo_handler.trusted_fd);
+ return;
+ }
+}
+
+/*
+ * Send keepalive packets to our client
+ */
+bool keepalive_fn(const struct timeval *now, void *private_data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+ struct smbXsrv_connection *xconn = NULL;
+ bool ret;
+
+ if (sconn->using_smb2) {
+ /* Don't do keepalives on an SMB2 connection. */
+ return false;
+ }
+
+ /*
+ * With SMB1 we only have 1 connection
+ */
+ xconn = sconn->client->connections;
+ smbd_lock_socket(xconn);
+ ret = send_keepalive(xconn->transport.sock);
+ smbd_unlock_socket(xconn);
+
+ if (!ret) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("send_keepalive failed for client %s. "
+ "Error %s - exiting\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ return False;
+ }
+ return True;
+}
+
+/*
+ * Read an smb packet in the echo handler child, giving the parent
+ * smbd one second to react once the socket becomes readable.
+ */
+
+struct smbd_echo_read_state {
+ struct tevent_context *ev;
+ struct smbXsrv_connection *xconn;
+
+ char *buf;
+ size_t buflen;
+ uint32_t seqnum;
+};
+
+static void smbd_echo_read_readable(struct tevent_req *subreq);
+static void smbd_echo_read_waited(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_echo_read_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct smbXsrv_connection *xconn)
+{
+ struct tevent_req *req, *subreq;
+ struct smbd_echo_read_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_echo_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->xconn = xconn;
+
+ subreq = wait_for_read_send(state, ev, xconn->transport.sock, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_echo_read_readable, req);
+ return req;
+}
+
+static void smbd_echo_read_readable(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_echo_read_state *state = tevent_req_data(
+ req, struct smbd_echo_read_state);
+ bool ok;
+ int err;
+
+ ok = wait_for_read_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ /*
+ * Give the parent smbd one second to step in
+ */
+
+ subreq = tevent_wakeup_send(
+ state, state->ev, timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_echo_read_waited, req);
+}
+
+static void smbd_echo_read_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_echo_read_state *state = tevent_req_data(
+ req, struct smbd_echo_read_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+ bool ok;
+ NTSTATUS status;
+ size_t unread = 0;
+ bool encrypted;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ ok = smbd_lock_socket_internal(xconn);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ DEBUG(0, ("%s: failed to lock socket\n", __location__));
+ return;
+ }
+
+ if (!fd_is_readable(xconn->transport.sock)) {
+ DEBUG(10,("echo_handler[%d] the parent smbd was faster\n",
+ (int)getpid()));
+
+ ok = smbd_unlock_socket_internal(xconn);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ DEBUG(1, ("%s: failed to unlock socket\n",
+ __location__));
+ return;
+ }
+
+ subreq = wait_for_read_send(state, state->ev,
+ xconn->transport.sock, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_echo_read_readable, req);
+ return;
+ }
+
+ status = receive_smb_talloc(state, xconn,
+ xconn->transport.sock,
+ &state->buf,
+ 0 /* timeout */,
+ &unread,
+ &encrypted,
+ &state->buflen,
+ &state->seqnum,
+ false /* trusted_channel*/);
+
+ if (tevent_req_nterror(req, status)) {
+ tevent_req_nterror(req, status);
+ DEBUG(1, ("echo_handler[%d]: receive_smb_raw_talloc failed: %s\n",
+ (int)getpid(), nt_errstr(status)));
+ return;
+ }
+
+ ok = smbd_unlock_socket_internal(xconn);
+ if (!ok) {
+ tevent_req_nterror(req, map_nt_error_from_unix(errno));
+ DEBUG(1, ("%s: failed to unlock socket\n", __location__));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_echo_read_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **pbuf, size_t *pbuflen, uint32_t *pseqnum)
+{
+ struct smbd_echo_read_state *state = tevent_req_data(
+ req, struct smbd_echo_read_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pbuf = talloc_move(mem_ctx, &state->buf);
+ *pbuflen = state->buflen;
+ *pseqnum = state->seqnum;
+ return NT_STATUS_OK;
+}
+
+struct smbd_echo_state {
+ struct tevent_context *ev;
+ struct iovec *pending;
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ int parent_pipe;
+
+ struct tevent_fd *parent_fde;
+
+ struct tevent_req *write_req;
+};
+
+static void smbd_echo_writer_done(struct tevent_req *req);
+
+static void smbd_echo_activate_writer(struct smbd_echo_state *state)
+{
+ int num_pending;
+
+ if (state->write_req != NULL) {
+ return;
+ }
+
+ num_pending = talloc_array_length(state->pending);
+ if (num_pending == 0) {
+ return;
+ }
+
+ state->write_req = writev_send(state, state->ev, NULL,
+ state->parent_pipe, false,
+ state->pending, num_pending);
+ if (state->write_req == NULL) {
+ DEBUG(1, ("writev_send failed\n"));
+ exit(1);
+ }
+
+ talloc_steal(state->write_req, state->pending);
+ state->pending = NULL;
+
+ tevent_req_set_callback(state->write_req, smbd_echo_writer_done,
+ state);
+}
+
+static void smbd_echo_writer_done(struct tevent_req *req)
+{
+ struct smbd_echo_state *state = tevent_req_callback_data(
+ req, struct smbd_echo_state);
+ ssize_t written;
+ int err;
+
+ written = writev_recv(req, &err);
+ TALLOC_FREE(req);
+ state->write_req = NULL;
+ if (written == -1) {
+ DEBUG(1, ("writev to parent failed: %s\n", strerror(err)));
+ exit(1);
+ }
+ DEBUG(10,("echo_handler[%d]: forwarded pdu to main\n", (int)getpid()));
+ smbd_echo_activate_writer(state);
+}
+
+static bool smbd_echo_reply(struct smbd_echo_state *state,
+ uint8_t *inbuf, size_t inbuf_len,
+ uint32_t seqnum)
+{
+ struct smb_request req;
+ uint16_t num_replies;
+ char *outbuf;
+ bool ok;
+
+ if ((inbuf_len == 4) && (CVAL(inbuf, 0) == NBSSkeepalive)) {
+ DEBUG(10, ("Got netbios keepalive\n"));
+ /*
+ * Just swallow it
+ */
+ return true;
+ }
+
+ if (inbuf_len < smb_size) {
+ DEBUG(10, ("Got short packet: %d bytes\n", (int)inbuf_len));
+ return false;
+ }
+ if (!valid_smb1_header(inbuf)) {
+ DEBUG(10, ("Got invalid SMB header\n"));
+ return false;
+ }
+
+ if (!init_smb1_request(&req, state->sconn, state->xconn, inbuf, 0, false,
+ seqnum)) {
+ return false;
+ }
+ req.inbuf = inbuf;
+
+ DEBUG(10, ("smbecho handler got cmd %d (%s)\n", (int)req.cmd,
+ smb_fn_name(req.cmd)));
+
+ if (req.cmd != SMBecho) {
+ return false;
+ }
+ if (req.wct < 1) {
+ return false;
+ }
+
+ num_replies = SVAL(req.vwv+0, 0);
+ if (num_replies != 1) {
+ /* Not a Windows "Hey, you're still there?" request */
+ return false;
+ }
+
+ if (!create_smb1_outbuf(talloc_tos(), &req, req.inbuf, &outbuf,
+ 1, req.buflen)) {
+ DEBUG(10, ("create_smb1_outbuf failed\n"));
+ return false;
+ }
+ req.outbuf = (uint8_t *)outbuf;
+
+ SSVAL(req.outbuf, smb_vwv0, num_replies);
+
+ if (req.buflen > 0) {
+ memcpy(smb_buf(req.outbuf), req.buf, req.buflen);
+ }
+
+ ok = smb1_srv_send(req.xconn, (char *)outbuf, true, seqnum + 1, false);
+ TALLOC_FREE(outbuf);
+ if (!ok) {
+ exit(1);
+ }
+
+ return true;
+}
+
+static void smbd_echo_exit(struct tevent_context *ev,
+ struct tevent_fd *fde, uint16_t flags,
+ void *private_data)
+{
+ DEBUG(2, ("smbd_echo_exit: lost connection to parent\n"));
+ exit(0);
+}
+
+static void smbd_echo_got_packet(struct tevent_req *req);
+
+static void smbd_echo_loop(struct smbXsrv_connection *xconn,
+ int parent_pipe)
+{
+ struct smbd_echo_state *state;
+ struct tevent_req *read_req;
+
+ state = talloc_zero(xconn, struct smbd_echo_state);
+ if (state == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return;
+ }
+ state->xconn = xconn;
+ state->parent_pipe = parent_pipe;
+ state->ev = samba_tevent_context_init(state);
+ if (state->ev == NULL) {
+ DEBUG(1, ("samba_tevent_context_init failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ state->parent_fde = tevent_add_fd(state->ev, state, parent_pipe,
+ TEVENT_FD_READ, smbd_echo_exit,
+ state);
+ if (state->parent_fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+
+ read_req = smbd_echo_read_send(state, state->ev, xconn);
+ if (read_req == NULL) {
+ DEBUG(1, ("smbd_echo_read_send failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ tevent_req_set_callback(read_req, smbd_echo_got_packet, state);
+
+ while (true) {
+ if (tevent_loop_once(state->ev) == -1) {
+ DEBUG(1, ("tevent_loop_once failed: %s\n",
+ strerror(errno)));
+ break;
+ }
+ }
+ TALLOC_FREE(state);
+}
+
+static void smbd_echo_got_packet(struct tevent_req *req)
+{
+ struct smbd_echo_state *state = tevent_req_callback_data(
+ req, struct smbd_echo_state);
+ NTSTATUS status;
+ char *buf = NULL;
+ size_t buflen = 0;
+ uint32_t seqnum = 0;
+ bool reply;
+
+ status = smbd_echo_read_recv(req, state, &buf, &buflen, &seqnum);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("smbd_echo_read_recv returned %s\n",
+ nt_errstr(status)));
+ exit(1);
+ }
+
+ reply = smbd_echo_reply(state, (uint8_t *)buf, buflen, seqnum);
+ if (!reply) {
+ size_t num_pending;
+ struct iovec *tmp;
+ struct iovec *iov;
+
+ num_pending = talloc_array_length(state->pending);
+ tmp = talloc_realloc(state, state->pending, struct iovec,
+ num_pending+1);
+ if (tmp == NULL) {
+ DEBUG(1, ("talloc_realloc failed\n"));
+ exit(1);
+ }
+ state->pending = tmp;
+
+ if (buflen >= smb_size) {
+ /*
+ * place the seqnum in the packet so that the main process
+ * can reply with signing
+ */
+ SIVAL(buf, smb_ss_field, seqnum);
+ SIVAL(buf, smb_ss_field+4, NT_STATUS_V(NT_STATUS_OK));
+ }
+
+ iov = &state->pending[num_pending];
+ iov->iov_base = talloc_move(state->pending, &buf);
+ iov->iov_len = buflen;
+
+ DEBUG(10,("echo_handler[%d]: forward to main\n",
+ (int)getpid()));
+ smbd_echo_activate_writer(state);
+ }
+
+ req = smbd_echo_read_send(state, state->ev, state->xconn);
+ if (req == NULL) {
+ DEBUG(1, ("smbd_echo_read_send failed\n"));
+ exit(1);
+ }
+ tevent_req_set_callback(req, smbd_echo_got_packet, state);
+}
+
+
+/*
+ * Handle SMBecho requests in a forked child process
+ */
+bool fork_echo_handler(struct smbXsrv_connection *xconn)
+{
+ int listener_pipe[2];
+ int res;
+ pid_t child;
+ bool use_mutex = false;
+
+ res = pipe(listener_pipe);
+ if (res == -1) {
+ DEBUG(1, ("pipe() failed: %s\n", strerror(errno)));
+ return false;
+ }
+
+#ifdef HAVE_ROBUST_MUTEXES
+ use_mutex = tdb_runtime_check_for_robust_mutexes();
+
+ if (use_mutex) {
+ pthread_mutexattr_t a;
+
+ xconn->smb1.echo_handler.socket_mutex =
+ anonymous_shared_allocate(sizeof(pthread_mutex_t));
+ if (xconn->smb1.echo_handler.socket_mutex == NULL) {
+ DEBUG(1, ("Could not create mutex shared memory: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+
+ res = pthread_mutexattr_init(&a);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_init failed: %s\n",
+ strerror(res)));
+ goto fail;
+ }
+ res = pthread_mutexattr_settype(&a, PTHREAD_MUTEX_ERRORCHECK);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_settype failed: %s\n",
+ strerror(res)));
+ pthread_mutexattr_destroy(&a);
+ goto fail;
+ }
+ res = pthread_mutexattr_setpshared(&a, PTHREAD_PROCESS_SHARED);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_setpshared failed: %s\n",
+ strerror(res)));
+ pthread_mutexattr_destroy(&a);
+ goto fail;
+ }
+ res = pthread_mutexattr_setrobust(&a, PTHREAD_MUTEX_ROBUST);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutexattr_setrobust failed: "
+ "%s\n", strerror(res)));
+ pthread_mutexattr_destroy(&a);
+ goto fail;
+ }
+ res = pthread_mutex_init(xconn->smb1.echo_handler.socket_mutex,
+ &a);
+ pthread_mutexattr_destroy(&a);
+ if (res != 0) {
+ DEBUG(1, ("pthread_mutex_init failed: %s\n",
+ strerror(res)));
+ goto fail;
+ }
+ }
+#endif
+
+ if (!use_mutex) {
+ xconn->smb1.echo_handler.socket_lock_fd =
+ create_unlink_tmp(lp_lock_directory());
+ if (xconn->smb1.echo_handler.socket_lock_fd == -1) {
+ DEBUG(1, ("Could not create lock fd: %s\n",
+ strerror(errno)));
+ goto fail;
+ }
+ }
+
+ child = fork();
+ if (child == 0) {
+ NTSTATUS status;
+
+ close(listener_pipe[0]);
+ set_blocking(listener_pipe[1], false);
+
+ status = smbd_reinit_after_fork(xconn->client->msg_ctx,
+ xconn->client->raw_ev_ctx,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("reinit_after_fork failed: %s\n",
+ nt_errstr(status)));
+ exit(1);
+ }
+ process_set_title("smbd-echo", "echo handler");
+ initialize_password_db(true, xconn->client->raw_ev_ctx);
+ smbd_echo_loop(xconn, listener_pipe[1]);
+ exit(0);
+ }
+ close(listener_pipe[1]);
+ listener_pipe[1] = -1;
+ xconn->smb1.echo_handler.trusted_fd = listener_pipe[0];
+
+ DEBUG(10,("fork_echo_handler: main[%d] echo_child[%d]\n", (int)getpid(), (int)child));
+
+ /*
+ * Without smb signing this is the same as the normal smbd
+ * listener. This needs to change once signing comes in.
+ */
+ xconn->smb1.echo_handler.trusted_fde = tevent_add_fd(
+ xconn->client->raw_ev_ctx,
+ xconn,
+ xconn->smb1.echo_handler.trusted_fd,
+ TEVENT_FD_READ,
+ smbd_server_echo_handler,
+ xconn);
+ if (xconn->smb1.echo_handler.trusted_fde == NULL) {
+ DEBUG(1, ("event_add_fd failed\n"));
+ goto fail;
+ }
+
+ return true;
+
+fail:
+ if (listener_pipe[0] != -1) {
+ close(listener_pipe[0]);
+ }
+ if (listener_pipe[1] != -1) {
+ close(listener_pipe[1]);
+ }
+ if (xconn->smb1.echo_handler.socket_lock_fd != -1) {
+ close(xconn->smb1.echo_handler.socket_lock_fd);
+ }
+#ifdef HAVE_ROBUST_MUTEXES
+ if (xconn->smb1.echo_handler.socket_mutex != NULL) {
+ pthread_mutex_destroy(xconn->smb1.echo_handler.socket_mutex);
+ anonymous_shared_free(xconn->smb1.echo_handler.socket_mutex);
+ }
+#endif
+ smbd_echo_init(xconn);
+
+ return false;
+}
+
+bool req_is_in_chain(const struct smb_request *req)
+{
+ if (req->vwv != (const uint16_t *)(req->inbuf+smb_vwv)) {
+ /*
+ * We're right now handling a subsequent request, so we must
+ * be in a chain
+ */
+ return true;
+ }
+
+ if (!smb1cli_is_andx_req(req->cmd)) {
+ return false;
+ }
+
+ if (req->wct < 2) {
+ /*
+ * Okay, an illegal request, but definitely not chained :-)
+ */
+ return false;
+ }
+
+ return (CVAL(req->vwv+0, 0) != 0xFF);
+}
diff --git a/source3/smbd/smb1_process.h b/source3/smbd/smb1_process.h
new file mode 100644
index 0000000..1e36248
--- /dev/null
+++ b/source3/smbd/smb1_process.h
@@ -0,0 +1,72 @@
+/*
+ Unix SMB/CIFS implementation.
+ process incoming packets - main loop
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt);
+NTSTATUS allow_new_trans(struct trans_state *list, uint64_t mid);
+void smb_request_done(struct smb_request *req);
+const char *smb_fn_name(int type);
+void add_to_common_flags2(uint32_t v);
+void remove_from_common_flags2(uint32_t v);
+bool smb1_is_chain(const uint8_t *buf);
+bool smb1_walk_chain(const uint8_t *buf,
+ bool (*fn)(uint8_t cmd,
+ uint8_t wct, const uint16_t *vwv,
+ uint16_t num_bytes, const uint8_t *bytes,
+ void *private_data),
+ void *private_data);
+unsigned smb1_chain_length(const uint8_t *buf);
+bool smb1_parse_chain(TALLOC_CTX *mem_ctx, const uint8_t *buf,
+ struct smbXsrv_connection *xconn,
+ bool encrypted, uint32_t seqnum,
+ struct smb_request ***reqs, unsigned *num_reqs);
+bool req_is_in_chain(const struct smb_request *req);
+bool fork_echo_handler(struct smbXsrv_connection *xconn);
+NTSTATUS smb1_receive_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel);
+bool push_deferred_open_message_smb1(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec);
+void process_smb1(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted);
+void smbd_echo_init(struct smbXsrv_connection *xconn);
+void construct_reply(struct smbXsrv_connection *xconn,
+ char *inbuf,
+ int size,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted);
+void smbd_smb1_server_connection_read_handler(struct smbXsrv_connection *xconn,
+ int fd);
+bool keepalive_fn(const struct timeval *now, void *private_data);
diff --git a/source3/smbd/smb1_reply.c b/source3/smbd/smb1_reply.c
new file mode 100644
index 0000000..b2f317a
--- /dev/null
+++ b/source3/smbd/smb1_reply.c
@@ -0,0 +1,7178 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ This file handles most of the reply_ calls that the server
+ makes to handle specific protocols
+*/
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "fake_file.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "libcli/security/security.h"
+#include "libsmb/nmblib.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/smb/smb_signing.h"
+#include "lib/util/sys_rw_data.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a file. Basic check for open fsp.
+****************************************************************************/
+
+bool check_fsp_open(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if ((fsp == NULL) || (conn == NULL)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return false;
+ }
+ if ((conn != fsp->conn) || (req->vuid != fsp->vuid)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return false;
+ }
+ return true;
+}
+
+/****************************************************************************
+ SMB1 version of smb2_strip_dfs_path()
+ Differs from SMB2 in that all Windows path separator '\' characters
+ have already been converted to '/' by check_path_syntax().
+****************************************************************************/
+
+NTSTATUS smb1_strip_dfs_path(TALLOC_CTX *mem_ctx,
+ uint32_t *_ucf_flags,
+ char **in_path)
+{
+ uint32_t ucf_flags = *_ucf_flags;
+ char *path = *in_path;
+ char *return_path = NULL;
+
+ if (!(ucf_flags & UCF_DFS_PATHNAME)) {
+ return NT_STATUS_OK;
+ }
+
+ /* Strip any leading '/' characters - MacOSX client behavior. */
+ while (*path == '/') {
+ path++;
+ }
+
+ /* We should now be pointing at the server name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto done;
+ }
+ if (*path == '/') {
+ /* End of server name. Go past and break. */
+ path++;
+ break;
+ }
+ path++; /* Continue looking for end of server name or string. */
+ }
+
+ /* We should now be pointing at the share name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto done;
+ }
+ if (*path == '/') {
+ /* End of share name. Go past and break. */
+ path++;
+ break;
+ }
+ if (*path == ':') {
+ /* Only invalid character in sharename. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ path++; /* Continue looking for end of share name or string. */
+ }
+
+ done:
+ /* path now points at the start of the real filename (if any). */
+ /* Duplicate it first. */
+ return_path = talloc_strdup(mem_ctx, path);
+ if (return_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Now we can free the original (path points to part of this). */
+ TALLOC_FREE(*in_path);
+
+ *in_path = return_path;
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+ *_ucf_flags = ucf_flags;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a file.
+****************************************************************************/
+
+bool check_fsp(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if (!check_fsp_open(conn, req, fsp)) {
+ return false;
+ }
+ if (fsp->fsp_flags.is_directory) {
+ reply_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return false;
+ }
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return false;
+ }
+ fsp->num_smb_operations++;
+ return true;
+}
+
+/****************************************************************************
+ Reply to a tcon.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_tcon(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ const char *service;
+ char *service_buf = NULL;
+ char *password = NULL;
+ char *dev = NULL;
+ int pwlen=0;
+ NTSTATUS nt_status;
+ const uint8_t *p;
+ const char *p2;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ START_PROFILE(SMBtcon);
+
+ if (req->buflen < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+
+ p = req->buf + 1;
+ p += srvstr_pull_req_talloc(ctx, req, &service_buf, p, STR_TERMINATE);
+ p += 1;
+ pwlen = srvstr_pull_req_talloc(ctx, req, &password, p, STR_TERMINATE);
+ p += pwlen+1;
+ p += srvstr_pull_req_talloc(ctx, req, &dev, p, STR_TERMINATE);
+ p += 1;
+
+ if (service_buf == NULL || password == NULL || dev == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+ p2 = strrchr_m(service_buf,'\\');
+ if (p2) {
+ service = p2+1;
+ } else {
+ service = service_buf;
+ }
+
+ conn = make_connection(req, now, service, dev,
+ req->vuid,&nt_status);
+ req->conn = conn;
+
+ if (!conn) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtcon);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf,smb_vwv0,xconn->smb1.negprot.max_recv);
+ SSVAL(req->outbuf,smb_vwv1,conn->cnum);
+ SSVAL(req->outbuf,smb_tid,conn->cnum);
+
+ DEBUG(3,("tcon service=%s cnum=%d\n",
+ service, conn->cnum));
+
+ END_PROFILE(SMBtcon);
+ return;
+}
+
+/****************************************************************************
+ Reply to a tcon and X.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_tcon_and_X(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = req->conn;
+ const char *service = NULL;
+ TALLOC_CTX *ctx = talloc_tos();
+ /* what the client thinks the device is */
+ char *client_devicetype = NULL;
+ /* what the server tells the client the share represents */
+ const char *server_devicetype;
+ NTSTATUS nt_status;
+ int passlen;
+ char *path = NULL;
+ const uint8_t *p;
+ const char *q;
+ uint16_t tcon_flags;
+ struct smbXsrv_session *session = NULL;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ bool session_key_updated = false;
+ uint16_t optional_support = 0;
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ START_PROFILE(SMBtconX);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ passlen = SVAL(req->vwv+3, 0);
+ tcon_flags = SVAL(req->vwv+2, 0);
+
+ /* we might have to close an old one */
+ if ((tcon_flags & TCONX_FLAG_DISCONNECT_TID) && conn) {
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+
+ tcon = conn->tcon;
+ req->conn = NULL;
+ conn = NULL;
+
+ /*
+ * TODO: cancel all outstanding requests on the tcon
+ */
+ status = smbXsrv_tcon_disconnect(tcon, req->vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("reply_tcon_and_X: "
+ "smbXsrv_tcon_disconnect() failed: %s\n",
+ nt_errstr(status)));
+ /*
+ * If we hit this case, there is something completely
+ * wrong, so we better disconnect the transport connection.
+ */
+ END_PROFILE(SMBtconX);
+ exit_server(__location__ ": smbXsrv_tcon_disconnect failed");
+ return;
+ }
+
+ TALLOC_FREE(tcon);
+ /*
+ * This tree id is gone. Make sure we can't re-use it
+ * by accident.
+ */
+ req->tid = 0;
+ }
+
+ if ((passlen > MAX_PASS_LEN) || (passlen >= req->buflen)) {
+ reply_force_doserror(req, ERRDOS, ERRbuftoosmall);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if (xconn->smb1.negprot.encrypted_passwords) {
+ p = req->buf + passlen;
+ } else {
+ p = req->buf + passlen + 1;
+ }
+
+ p += srvstr_pull_req_talloc(ctx, req, &path, p, STR_TERMINATE);
+
+ if (path == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /*
+ * the service name can be either: \\server\share
+ * or share directly like on the DELL PowerVault 705
+ */
+ if (*path=='\\') {
+ q = strchr_m(path+2,'\\');
+ if (!q) {
+ reply_nterror(req, NT_STATUS_BAD_NETWORK_NAME);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ service = q+1;
+ } else {
+ service = path;
+ }
+
+ p += srvstr_pull_talloc(ctx, req->inbuf, req->flags2,
+ &client_devicetype, p,
+ MIN(6, smbreq_bufrem(req, p)), STR_ASCII);
+
+ if (client_devicetype == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ DEBUG(4,("Client requested device type [%s] for share [%s]\n", client_devicetype, service));
+
+ nt_status = smb1srv_session_lookup(xconn,
+ req->vuid, now, &session);
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_USER_SESSION_DELETED)) {
+ reply_force_doserror(req, ERRSRV, ERRbaduid);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if (session->global->auth_session_info == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /*
+ * If there is no application key defined yet
+ * we create one.
+ *
+ * This means we setup the application key on the
+ * first tcon that happens via the given session.
+ *
+ * Once the application key is defined, it does not
+ * change any more.
+ */
+ if (session->global->application_key_blob.length == 0 &&
+ smb2_signing_key_valid(session->global->signing_key))
+ {
+ struct smbXsrv_session *x = session;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ uint8_t session_key[16];
+
+ ZERO_STRUCT(session_key);
+ memcpy(session_key, x->global->signing_key->blob.data,
+ MIN(x->global->signing_key->blob.length, sizeof(session_key)));
+
+ /*
+ * The application key is truncated/padded to 16 bytes
+ */
+ x->global->application_key_blob = data_blob_talloc(x->global,
+ session_key,
+ sizeof(session_key));
+ ZERO_STRUCT(session_key);
+ if (x->global->application_key_blob.data == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ talloc_keep_secret(x->global->application_key_blob.data);
+
+ if (tcon_flags & TCONX_FLAG_EXTENDED_SIGNATURES) {
+ NTSTATUS status;
+
+ status = smb1_key_derivation(x->global->application_key_blob.data,
+ x->global->application_key_blob.length,
+ x->global->application_key_blob.data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smb1_key_derivation failed: %s\n",
+ nt_errstr(status));
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ optional_support |= SMB_EXTENDED_SIGNATURES;
+ }
+
+ /*
+ * Place the application key into the session_info
+ */
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ x->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ data_blob_clear_free(&x->global->application_key_blob);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+ session_key_updated = true;
+ }
+
+ conn = make_connection(req, now, service, client_devicetype,
+ req->vuid, &nt_status);
+ req->conn =conn;
+
+ if (!conn) {
+ if (session_key_updated) {
+ struct smbXsrv_session *x = session;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ data_blob_clear_free(&x->global->application_key_blob);
+ data_blob_clear_free(&session_info->session_key);
+ }
+ reply_nterror(req, nt_status);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ if ( IS_IPC(conn) )
+ server_devicetype = "IPC";
+ else if ( IS_PRINT(conn) )
+ server_devicetype = "LPT1:";
+ else
+ server_devicetype = "A:";
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ reply_smb1_outbuf(req, 2, 0);
+ if (message_push_string(&req->outbuf, server_devicetype,
+ STR_TERMINATE|STR_ASCII) == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+ } else {
+ /* NT sets the fstype of IPC$ to the null string */
+ const char *fstype = IS_IPC(conn) ? "" : lp_fstype(SNUM(conn));
+
+ if (tcon_flags & TCONX_FLAG_EXTENDED_RESPONSE) {
+ /* Return permissions. */
+ uint32_t perm1 = 0;
+ uint32_t perm2 = 0;
+
+ reply_smb1_outbuf(req, 7, 0);
+
+ if (IS_IPC(conn)) {
+ perm1 = FILE_ALL_ACCESS;
+ perm2 = FILE_ALL_ACCESS;
+ } else {
+ perm1 = conn->share_access;
+ }
+
+ SIVAL(req->outbuf, smb_vwv3, perm1);
+ SIVAL(req->outbuf, smb_vwv5, perm2);
+ } else {
+ reply_smb1_outbuf(req, 3, 0);
+ }
+
+ if ((message_push_string(&req->outbuf, server_devicetype,
+ STR_TERMINATE|STR_ASCII) == -1)
+ || (message_push_string(&req->outbuf, fstype,
+ STR_TERMINATE) == -1)) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtconX);
+ return;
+ }
+
+ /* what does setting this bit do? It is set by NT4 and
+ may affect the ability to autorun mounted cdroms */
+ optional_support |= SMB_SUPPORT_SEARCH_BITS;
+ optional_support |=
+ (lp_csc_policy(SNUM(conn)) << SMB_CSC_POLICY_SHIFT);
+
+ if (lp_msdfs_root(SNUM(conn)) && lp_host_msdfs()) {
+ DEBUG(2,("Serving %s as a Dfs root\n",
+ lp_servicename(ctx, lp_sub, SNUM(conn)) ));
+ optional_support |= SMB_SHARE_IN_DFS;
+ }
+
+ SSVAL(req->outbuf, smb_vwv2, optional_support);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ DEBUG(3,("tconX service=%s \n",
+ service));
+
+ /* set the incoming and outgoing tid to the just created one */
+ SSVAL(discard_const_p(uint8_t, req->inbuf),smb_tid,conn->cnum);
+ SSVAL(req->outbuf,smb_tid,conn->cnum);
+
+ END_PROFILE(SMBtconX);
+
+ req->tid = conn->cnum;
+}
+
+/****************************************************************************
+ Reply to an unknown type.
+****************************************************************************/
+
+void reply_unknown_new(struct smb_request *req, uint8_t type)
+{
+ DEBUG(0, ("unknown command type (%s): type=%d (0x%X)\n",
+ smb_fn_name(type), type, type));
+ reply_force_doserror(req, ERRSRV, ERRunknownsmb);
+ return;
+}
+
+/****************************************************************************
+ Reply to an ioctl.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_ioctl(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = req->conn;
+ uint16_t device;
+ uint16_t function;
+ uint32_t ioctl_code;
+ int replysize;
+ char *p;
+
+ START_PROFILE(SMBioctl);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+
+ device = SVAL(req->vwv+1, 0);
+ function = SVAL(req->vwv+2, 0);
+ ioctl_code = (device << 16) + function;
+
+ DEBUG(4, ("Received IOCTL (code 0x%x)\n", ioctl_code));
+
+ switch (ioctl_code) {
+ case IOCTL_QUERY_JOB_INFO:
+ replysize = 32;
+ break;
+ default:
+ reply_force_doserror(req, ERRSRV, ERRnosupport);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 8, replysize+1);
+ SSVAL(req->outbuf,smb_vwv1,replysize); /* Total data bytes returned */
+ SSVAL(req->outbuf,smb_vwv5,replysize); /* Data bytes this buffer */
+ SSVAL(req->outbuf,smb_vwv6,52); /* Offset to data */
+ p = smb_buf(req->outbuf);
+ memset(p, '\0', replysize+1); /* valgrind-safe. */
+ p += 1; /* Allow for alignment */
+
+ switch (ioctl_code) {
+ case IOCTL_QUERY_JOB_INFO:
+ {
+ NTSTATUS status;
+ size_t len = 0;
+ files_struct *fsp = file_fsp(
+ req, SVAL(req->vwv+0, 0));
+ if (!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+ /* Job number */
+ SSVAL(p, 0, print_spool_rap_jobid(fsp->print_file));
+
+ status = srvstr_push((char *)req->outbuf, req->flags2, p+2,
+ lp_netbios_name(), 15,
+ STR_TERMINATE|STR_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+ if (conn) {
+ status = srvstr_push((char *)req->outbuf, req->flags2,
+ p+18,
+ lp_servicename(talloc_tos(),
+ lp_sub,
+ SNUM(conn)),
+ 13, STR_TERMINATE|STR_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBioctl);
+ return;
+ }
+ } else {
+ memset(p+18, 0, 13);
+ }
+ break;
+ }
+ }
+
+ END_PROFILE(SMBioctl);
+ return;
+}
+
+/****************************************************************************
+ Strange checkpath NTSTATUS mapping.
+****************************************************************************/
+
+static NTSTATUS map_checkpath_error(uint16_t flags2, NTSTATUS status)
+{
+ /* Strange DOS error code semantics only for checkpath... */
+ if (!(flags2 & FLAGS2_32_BIT_ERROR_CODES)) {
+ if (NT_STATUS_EQUAL(NT_STATUS_OBJECT_NAME_INVALID,status)) {
+ /* We need to map to ERRbadpath */
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ }
+ }
+ return status;
+}
+
+/****************************************************************************
+ Reply to a checkpath.
+****************************************************************************/
+
+void reply_checkpath(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *name = NULL;
+ NTSTATUS status;
+ struct files_struct *dirfsp = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBcheckpath);
+
+ srvstr_get_path_req(ctx, req, &name, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ status = map_checkpath_error(req->flags2, status);
+ reply_nterror(req, status);
+ END_PROFILE(SMBcheckpath);
+ return;
+ }
+
+ DEBUG(3,("reply_checkpath %s mode=%d\n", name, (int)SVAL(req->vwv+0, 0)));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ END_PROFILE(SMBcheckpath);
+ return;
+ }
+ goto path_err;
+ }
+
+ if (!VALID_STAT(smb_fname->st) &&
+ (SMB_VFS_STAT(conn, smb_fname) != 0)) {
+ DEBUG(3,("reply_checkpath: stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname), strerror(errno)));
+ status = map_nt_error_from_unix(errno);
+ goto path_err;
+ }
+
+ if (!S_ISDIR(smb_fname->st.st_ex_mode)) {
+ reply_botherror(req, NT_STATUS_NOT_A_DIRECTORY,
+ ERRDOS, ERRbadpath);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ path_err:
+ /* We special case this - as when a Windows machine
+ is parsing a path is steps through the components
+ one at a time - if a component fails it expects
+ ERRbadpath, not ERRbadfile.
+ */
+ status = map_checkpath_error(req->flags2, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * Windows returns different error codes if
+ * the parent directory is valid but not the
+ * last component - it returns NT_STATUS_OBJECT_NAME_NOT_FOUND
+ * for that case and NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * if the path is invalid.
+ */
+ reply_botherror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND,
+ ERRDOS, ERRbadpath);
+ goto out;
+ }
+
+ reply_nterror(req, status);
+
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBcheckpath);
+ return;
+}
+
+/****************************************************************************
+ Reply to a getatr.
+****************************************************************************/
+
+void reply_getatr(struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ int mode=0;
+ off_t size=0;
+ time_t mtime=0;
+ const char *p;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBgetatr);
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &fname, p, STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /*
+ * dos sometimes asks for a stat of "" - it returns a "hidden
+ * directory" under WfWg - weird!
+ */
+ if (*fname == '\0') {
+ mode = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY;
+ if (!CAN_WRITE(conn)) {
+ mode |= FILE_ATTRIBUTE_READONLY;
+ }
+ size = 0;
+ mtime = 0;
+ } else {
+ struct files_struct *dirfsp = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ bool ask_sharemode;
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+ if (!VALID_STAT(smb_fname->st) &&
+ (SMB_VFS_STAT(conn, smb_fname) != 0)) {
+ DEBUG(3,("reply_getatr: stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno)));
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto out;
+ }
+
+ mode = fdos_mode(smb_fname->fsp);
+ size = smb_fname->st.st_ex_size;
+
+ ask_sharemode = fsp_search_ask_sharemode(smb_fname->fsp);
+ if (ask_sharemode) {
+ struct timespec write_time_ts;
+ struct file_id fileid;
+
+ ZERO_STRUCT(write_time_ts);
+ fileid = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ get_file_infos(fileid, 0, NULL, &write_time_ts);
+ if (!is_omit_timespec(&write_time_ts)) {
+ update_stat_ex_mtime(&smb_fname->st, write_time_ts);
+ }
+ }
+
+ mtime = convert_timespec_to_time_t(smb_fname->st.st_ex_mtime);
+ if (mode & FILE_ATTRIBUTE_DIRECTORY) {
+ size = 0;
+ }
+ }
+
+ reply_smb1_outbuf(req, 10, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,mode);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv1,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv1,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv3,(uint32_t)size);
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2) | FLAGS2_IS_LONG_NAME);
+ }
+
+ DEBUG(3,("reply_getatr: name=%s mode=%d size=%u\n",
+ smb_fname_str_dbg(smb_fname), mode, (unsigned int)size));
+
+ out:
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ END_PROFILE(SMBgetatr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a setatr.
+****************************************************************************/
+
+void reply_setatr(struct smb_request *req)
+{
+ struct smb_file_time ft;
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ struct files_struct *dirfsp = NULL;
+ char *fname = NULL;
+ int mode;
+ time_t mtime;
+ const char *p;
+ NTSTATUS status;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBsetatr);
+ init_smb_file_time(&ft);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &fname, p, STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ISDOT(smb_fname->base_name)) {
+ /*
+ * Not sure here is the right place to catch this
+ * condition. Might be moved to somewhere else later -- vl
+ */
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ if (smb_fname->fsp == NULL) {
+ /* Can't set access rights on a symlink. */
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ mode = SVAL(req->vwv+0, 0);
+ mtime = srv_make_unix_date3(req->vwv+1);
+
+ if (mode != FILE_ATTRIBUTE_NORMAL) {
+ if (VALID_STAT_OF_DIR(smb_fname->st))
+ mode |= FILE_ATTRIBUTE_DIRECTORY;
+ else
+ mode &= ~FILE_ATTRIBUTE_DIRECTORY;
+
+ status = smbd_check_access_rights_fsp(conn->cwd_fsp,
+ smb_fname->fsp,
+ false,
+ FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (file_set_dosmode(conn, smb_fname, mode, NULL,
+ false) != 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto out;
+ }
+ }
+
+ ft.mtime = time_t_to_full_timespec(mtime);
+
+ status = smb_set_file_time(conn, smb_fname->fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3, ("setatr name=%s mode=%d\n", smb_fname_str_dbg(smb_fname),
+ mode));
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBsetatr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a dskattr.
+****************************************************************************/
+
+void reply_dskattr(struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ connection_struct *conn = req->conn;
+ uint64_t ret;
+ uint64_t dfree,dsize,bsize;
+ struct smb_filename smb_fname;
+ START_PROFILE(SMBdskattr);
+
+ ZERO_STRUCT(smb_fname);
+ smb_fname.base_name = discard_const_p(char, ".");
+
+ if (SMB_VFS_STAT(conn, &smb_fname) != 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ DBG_WARNING("stat of . failed (%s)\n", strerror(errno));
+ END_PROFILE(SMBdskattr);
+ return;
+ }
+
+ ret = get_dfree_info(conn, &smb_fname, &bsize, &dfree, &dsize);
+ if (ret == (uint64_t)-1) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ END_PROFILE(SMBdskattr);
+ return;
+ }
+
+ /*
+ * Force max to fit in 16 bit fields.
+ */
+ while (dfree > WORDMAX || dsize > WORDMAX || bsize < 512) {
+ dfree /= 2;
+ dsize /= 2;
+ bsize *= 2;
+ if (bsize > (WORDMAX*512)) {
+ bsize = (WORDMAX*512);
+ if (dsize > WORDMAX)
+ dsize = WORDMAX;
+ if (dfree > WORDMAX)
+ dfree = WORDMAX;
+ break;
+ }
+ }
+
+ reply_smb1_outbuf(req, 5, 0);
+
+ if (xconn->protocol <= PROTOCOL_LANMAN2) {
+ double total_space, free_space;
+ /* we need to scale this to a number that DOS6 can handle. We
+ use floating point so we can handle large drives on systems
+ that don't have 64 bit integers
+
+ we end up displaying a maximum of 2G to DOS systems
+ */
+ total_space = dsize * (double)bsize;
+ free_space = dfree * (double)bsize;
+
+ dsize = (uint64_t)((total_space+63*512) / (64*512));
+ dfree = (uint64_t)((free_space+63*512) / (64*512));
+
+ if (dsize > 0xFFFF) dsize = 0xFFFF;
+ if (dfree > 0xFFFF) dfree = 0xFFFF;
+
+ SSVAL(req->outbuf,smb_vwv0,dsize);
+ SSVAL(req->outbuf,smb_vwv1,64); /* this must be 64 for dos systems */
+ SSVAL(req->outbuf,smb_vwv2,512); /* and this must be 512 */
+ SSVAL(req->outbuf,smb_vwv3,dfree);
+ } else {
+ SSVAL(req->outbuf,smb_vwv0,dsize);
+ SSVAL(req->outbuf,smb_vwv1,bsize/512);
+ SSVAL(req->outbuf,smb_vwv2,512);
+ SSVAL(req->outbuf,smb_vwv3,dfree);
+ }
+
+ DEBUG(3,("dskattr dfree=%d\n", (unsigned int)dfree));
+
+ END_PROFILE(SMBdskattr);
+ return;
+}
+
+/****************************************************************************
+ Make a dir struct.
+****************************************************************************/
+
+static void make_dir_struct(TALLOC_CTX *ctx,
+ char *buf,
+ const char *mask,
+ const char *fname,
+ off_t size,
+ uint32_t mode,
+ time_t date,
+ bool uc)
+{
+ char *p;
+
+ if ((mode & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+ size = 0;
+ }
+
+ memset(buf+1,' ',11);
+ if ((p = strchr_m(mask, '.')) != NULL) {
+ char name[p - mask + 1];
+ strlcpy(name, mask, sizeof(name));
+ push_ascii(buf + 1, name, 8, 0);
+ push_ascii(buf+9,p+1,3, 0);
+ } else {
+ push_ascii(buf + 1, mask, 11, 0);
+ }
+
+ memset(buf+21,'\0',DIR_STRUCT_SIZE-21);
+ SCVAL(buf,21,mode);
+ srv_put_dos_date(buf,22,date);
+ SSVAL(buf,26,size & 0xFFFF);
+ SSVAL(buf,28,(size >> 16)&0xFFFF);
+ /* We only uppercase if FLAGS2_LONG_PATH_COMPONENTS is zero in the input buf.
+ Strange, but verified on W2K3. Needed for OS/2. JRA. */
+ push_ascii(buf+30,fname,12, uc ? STR_UPPER : 0);
+ DEBUG(8,("put name [%s] from [%s] into dir struct\n",buf+30, fname));
+}
+
+/*******************************************************************
+ A wrapper that handles case sensitivity and the special handling
+ of the ".." name.
+*******************************************************************/
+
+static bool mask_match_search(const char *string,
+ const char *pattern,
+ bool is_case_sensitive)
+{
+ if (ISDOTDOT(string)) {
+ string = ".";
+ }
+ if (ISDOT(pattern)) {
+ return False;
+ }
+
+ return ms_fnmatch(pattern, string, True, is_case_sensitive) == 0;
+}
+
+static bool mangle_mask_match(connection_struct *conn,
+ const char *filename,
+ const char *mask)
+{
+ char mname[13];
+
+ if (!name_to_8_3(filename, mname, False, conn->params)) {
+ return False;
+ }
+ return mask_match_search(mname, mask, False);
+}
+
+/****************************************************************************
+ Get an 8.3 directory entry.
+****************************************************************************/
+
+static bool smbd_dirptr_8_3_match_fn(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname)
+{
+ connection_struct *conn = (connection_struct *)private_data;
+
+ if ((strcmp(mask, "*.*") == 0) ||
+ mask_match_search(dname, mask, false) ||
+ mangle_mask_match(conn, dname, mask)) {
+ char mname[13];
+ const char *fname;
+ /*
+ * Ensure we can push the original name as UCS2. If
+ * not, then just don't return this name.
+ */
+ NTSTATUS status;
+ size_t ret_len = 0;
+ size_t len = (strlen(dname) + 2) * 4; /* Allow enough space. */
+ uint8_t *tmp = talloc_array(talloc_tos(), uint8_t, len);
+
+ status = srvstr_push(NULL,
+ FLAGS2_UNICODE_STRINGS,
+ tmp,
+ dname,
+ len,
+ STR_TERMINATE,
+ &ret_len);
+
+ TALLOC_FREE(tmp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (!mangle_is_8_3(dname, false, conn->params)) {
+ bool ok =
+ name_to_8_3(dname, mname, false, conn->params);
+ if (!ok) {
+ return false;
+ }
+ fname = mname;
+ } else {
+ fname = dname;
+ }
+
+ *_fname = talloc_strdup(ctx, fname);
+ if (*_fname == NULL) {
+ return false;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+static bool get_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ const char *mask,
+ uint32_t dirtype,
+ char **_fname,
+ off_t *_size,
+ uint32_t *_mode,
+ struct timespec *_date,
+ bool check_descend,
+ bool ask_sharemode)
+{
+ char *fname = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t mode = 0;
+ bool ok;
+
+again:
+ ok = smbd_dirptr_get_entry(ctx,
+ dirptr,
+ mask,
+ dirtype,
+ check_descend,
+ ask_sharemode,
+ true,
+ smbd_dirptr_8_3_match_fn,
+ conn,
+ &fname,
+ &smb_fname,
+ &mode);
+ if (!ok) {
+ return false;
+ }
+ if (mode & FILE_ATTRIBUTE_REPARSE_POINT) {
+ /* hide reparse points from ancient clients */
+ TALLOC_FREE(fname);
+ TALLOC_FREE(smb_fname);
+ goto again;
+ }
+
+ *_fname = talloc_move(ctx, &fname);
+ *_size = smb_fname->st.st_ex_size;
+ *_mode = mode;
+ *_date = smb_fname->st.st_ex_mtime;
+ TALLOC_FREE(smb_fname);
+ return true;
+}
+
+/****************************************************************************
+ Reply to a search.
+ Can be called from SMBsearch, SMBffirst or SMBfunique.
+****************************************************************************/
+
+void reply_search(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *path = NULL;
+ char *mask = NULL;
+ char *directory = NULL;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ off_t size;
+ uint32_t mode;
+ struct timespec date;
+ uint32_t dirtype;
+ unsigned int numentries = 0;
+ unsigned int maxentries = 0;
+ bool finished = False;
+ const char *p;
+ int status_len;
+ char status[21];
+ int dptr_num= -1;
+ bool check_descend = False;
+ bool expect_close = False;
+ NTSTATUS nt_status;
+ bool mask_contains_wcard = False;
+ bool allow_long_path_components = (req->flags2 & FLAGS2_LONG_PATH_COMPONENTS) ? True : False;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ files_struct *fsp = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ START_PROFILE(SMBsearch);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (req->posix_pathnames) {
+ reply_unknown_new(req, req->cmd);
+ goto out;
+ }
+
+ /* If we were called as SMBffirst then we must expect close. */
+ if(req->cmd == SMBffirst) {
+ expect_close = True;
+ }
+
+ reply_smb1_outbuf(req, 1, 3);
+ maxentries = SVAL(req->vwv+0, 0);
+ dirtype = SVAL(req->vwv+1, 0);
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &path, p, STR_TERMINATE,
+ &nt_status);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ if (smbreq_bufrem(req, p) < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ p++;
+ status_len = SVAL(p, 0);
+ p += 2;
+
+ /* dirtype &= ~FILE_ATTRIBUTE_DIRECTORY; */
+
+ if (status_len == 0) {
+ const char *dirpath;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+
+ nt_status = smb1_strip_dfs_path(ctx, &ucf_flags, &path);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ nt_status = filename_convert_smb1_search_path(ctx,
+ conn,
+ path,
+ ucf_flags,
+ &dirfsp,
+ &smb_dname,
+ &mask);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ memset((char *)status,'\0',21);
+ SCVAL(status,0,(dirtype & 0x1F));
+
+ /*
+ * Open an fsp on this directory for the dptr.
+ */
+ nt_status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* dname */
+ FILE_LIST_DIRECTORY, /* access_mask */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY,/* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, /* in_context */
+ NULL);/* out_context */
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("failed to open directory %s\n",
+ smb_fname_str_dbg(smb_dname));
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ nt_status = dptr_create(conn,
+ NULL, /* req */
+ fsp, /* fsp */
+ True,
+ mask,
+ dirtype,
+ &fsp->dptr);
+
+ TALLOC_FREE(smb_dname);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /*
+ * Use NULL here for the first parameter (req)
+ * as this is not a client visible handle so
+ * can't be part of an SMB1 chain.
+ */
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ reply_nterror(req, nt_status);
+ goto out;
+ }
+
+ dptr_num = dptr_dnum(fsp->dptr);
+ dirpath = dptr_path(sconn, dptr_num);
+ directory = talloc_strdup(ctx, dirpath);
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ } else {
+ int status_dirtype;
+ const char *dirpath;
+ unsigned int dptr_filenum;
+ uint32_t resume_key_index;
+
+ if (smbreq_bufrem(req, p) < 21) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ memcpy(status,p,21);
+ status_dirtype = CVAL(status,0) & 0x1F;
+ if (status_dirtype != (dirtype & 0x1F)) {
+ dirtype = status_dirtype;
+ }
+
+ dptr_num = CVAL(status, 12);
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ if (fsp == NULL) {
+ goto SearchEmpty;
+ }
+
+ resume_key_index = PULL_LE_U32(status, 13);
+ dptr_filenum = dptr_FileNumber(fsp->dptr);
+
+ if (resume_key_index > dptr_filenum) {
+ /*
+ * Haven't seen this resume key yet. Just stop
+ * the search.
+ */
+ goto SearchEmpty;
+ }
+
+ if (resume_key_index < dptr_filenum) {
+ /*
+ * The resume key was not the last one we
+ * sent, rewind and skip to what the client
+ * sent.
+ */
+ dptr_RewindDir(fsp->dptr);
+
+ dptr_filenum = dptr_FileNumber(fsp->dptr);
+ SMB_ASSERT(dptr_filenum == 0);
+
+ while (dptr_filenum < resume_key_index) {
+ bool ok = get_dir_entry(
+ ctx,
+ conn,
+ fsp->dptr,
+ dptr_wcard(sconn, dptr_num),
+ dirtype,
+ &fname,
+ &size,
+ &mode,
+ &date,
+ check_descend,
+ false);
+ TALLOC_FREE(fname);
+ if (!ok) {
+ goto SearchEmpty;
+ }
+
+ dptr_filenum = dptr_FileNumber(fsp->dptr);
+ }
+ }
+
+ dirpath = dptr_path(sconn, dptr_num);
+ directory = talloc_strdup(ctx, dirpath);
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ mask = talloc_strdup(ctx, dptr_wcard(sconn, dptr_num));
+ if (!mask) {
+ goto SearchEmpty;
+ }
+ dirtype = dptr_attr(sconn, dptr_num);
+ }
+
+ mask_contains_wcard = dptr_has_wild(fsp->dptr);
+
+ DEBUG(4,("dptr_num is %d\n",dptr_num));
+
+ if ((dirtype&0x1F) == FILE_ATTRIBUTE_VOLUME) {
+ char buf[DIR_STRUCT_SIZE];
+ memcpy(buf,status,21);
+ make_dir_struct(ctx,
+ buf,
+ "???????????",
+ volume_label(ctx, SNUM(conn)),
+ 0,
+ FILE_ATTRIBUTE_VOLUME,
+ 0,
+ !allow_long_path_components);
+ SCVAL(buf, 12, dptr_num);
+ numentries = 1;
+ if (message_push_blob(&req->outbuf,
+ data_blob_const(buf, sizeof(buf)))
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ } else {
+ unsigned int i;
+ size_t hdr_size = ((uint8_t *)smb_buf(req->outbuf) + 3 - req->outbuf);
+ size_t available_space = xconn->smb1.sessions.max_send - hdr_size;
+ bool ask_sharemode;
+
+ maxentries = MIN(maxentries, available_space/DIR_STRUCT_SIZE);
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",
+ directory,lp_dont_descend(ctx, lp_sub, SNUM(conn))));
+ if (in_list(directory, lp_dont_descend(ctx, lp_sub, SNUM(conn)),True)) {
+ check_descend = True;
+ }
+
+ ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ for (i=numentries;(i<maxentries) && !finished;i++) {
+ finished = !get_dir_entry(ctx,
+ conn,
+ fsp->dptr,
+ mask,
+ dirtype,
+ &fname,
+ &size,
+ &mode,
+ &date,
+ check_descend,
+ ask_sharemode);
+ if (!finished) {
+ char buf[DIR_STRUCT_SIZE];
+ memcpy(buf,status,21);
+ make_dir_struct(
+ ctx,
+ buf,
+ mask,
+ fname,
+ size,
+ mode,
+ convert_timespec_to_time_t(date),
+ !allow_long_path_components);
+ SCVAL(buf, 12, dptr_num);
+ PUSH_LE_U32(buf,
+ 13,
+ dptr_FileNumber(fsp->dptr));
+ if (message_push_blob(&req->outbuf,
+ data_blob_const(buf, sizeof(buf)))
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ numentries++;
+ }
+ TALLOC_FREE(fname);
+ }
+ }
+
+ SearchEmpty:
+
+ /* If we were called as SMBffirst with smb_search_id == NULL
+ and no entries were found then return error and close fsp->dptr
+ (X/Open spec) */
+
+ if (numentries == 0) {
+ dptr_num = -1;
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ } else if(expect_close && status_len == 0) {
+ /* Close the dptr - we know it's gone */
+ dptr_num = -1;
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+
+ /* If we were called as SMBfunique, then we can close the fsp->dptr now ! */
+ if(dptr_num >= 0 && req->cmd == SMBfunique) {
+ dptr_num = -1;
+ /* fsp may have been closed above. */
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+
+ if ((numentries == 0) && !mask_contains_wcard) {
+ reply_botherror(req, STATUS_NO_MORE_FILES, ERRDOS, ERRnofiles);
+ goto out;
+ }
+
+ SSVAL(req->outbuf,smb_vwv0,numentries);
+ SSVAL(req->outbuf,smb_vwv1,3 + numentries * DIR_STRUCT_SIZE);
+ SCVAL(smb_buf(req->outbuf),0,5);
+ SSVAL(smb_buf(req->outbuf),1,numentries*DIR_STRUCT_SIZE);
+
+ /* The replies here are never long name. */
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2) & (~FLAGS2_IS_LONG_NAME));
+ if (!allow_long_path_components) {
+ SSVAL(req->outbuf, smb_flg2,
+ SVAL(req->outbuf, smb_flg2)
+ & (~FLAGS2_LONG_PATH_COMPONENTS));
+ }
+
+ /* This SMB *always* returns ASCII names. Remove the unicode bit in flags2. */
+ SSVAL(req->outbuf, smb_flg2,
+ (SVAL(req->outbuf, smb_flg2) & (~FLAGS2_UNICODE_STRINGS)));
+
+ DEBUG(4,("%s mask=%s path=%s dtype=%d nument=%u of %u\n",
+ smb_fn_name(req->cmd),
+ mask,
+ directory,
+ dirtype,
+ numentries,
+ maxentries ));
+ out:
+ TALLOC_FREE(directory);
+ TALLOC_FREE(mask);
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBsearch);
+ return;
+}
+
+/****************************************************************************
+ Reply to a fclose (stop directory search).
+****************************************************************************/
+
+void reply_fclose(struct smb_request *req)
+{
+ int status_len;
+ int dptr_num= -2;
+ const char *p;
+ char *path = NULL;
+ NTSTATUS err;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbd_server_connection *sconn = req->sconn;
+ files_struct *fsp = NULL;
+
+ START_PROFILE(SMBfclose);
+
+ if (req->posix_pathnames) {
+ reply_unknown_new(req, req->cmd);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &path, p, STR_TERMINATE,
+ &err);
+ if (!NT_STATUS_IS_OK(err)) {
+ reply_nterror(req, err);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ if (smbreq_bufrem(req, p) < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ p++;
+ status_len = SVAL(p,0);
+ p += 2;
+
+ if (status_len == 0) {
+ reply_force_doserror(req, ERRSRV, ERRsrverror);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ if (smbreq_bufrem(req, p) < 21) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfclose);
+ return;
+ }
+
+ dptr_num = CVAL(p, 12);
+
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ if(fsp != NULL) {
+ /* Close the file - we know it's gone */
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ dptr_num = -1;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,0);
+
+ DEBUG(3,("search close\n"));
+
+ END_PROFILE(SMBfclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to an open.
+****************************************************************************/
+
+void reply_open(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint32_t fattr=0;
+ off_t size = 0;
+ time_t mtime=0;
+ int info;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ int oplock_request;
+ int deny_mode;
+ uint32_t dos_attr;
+ uint32_t access_mask;
+ uint32_t share_mode;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t private_flags = 0;
+ NTSTATUS status;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBopen);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+ deny_mode = SVAL(req->vwv+0, 0);
+ dos_attr = SVAL(req->vwv+1, 0);
+
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf+1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!map_open_params_to_ntcreate(fname, deny_mode,
+ OPENX_FILE_EXISTS_OPEN, &access_mask,
+ &share_mode, &create_disposition,
+ &create_options, &private_flags)) {
+ reply_force_doserror(req, ERRDOS, ERRbadaccess);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ dos_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ private_flags,
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ fsp = fcb_or_dos_open(
+ req,
+ smb_fname,
+ access_mask,
+ create_options,
+ private_flags);
+ if (fsp == NULL) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+ }
+
+ /* Ensure we're pointing at the correct stat struct. */
+ TALLOC_FREE(smb_fname);
+ smb_fname = fsp->fsp_name;
+
+ size = smb_fname->st.st_ex_size;
+ fattr = fdos_mode(fsp);
+
+ mtime = convert_timespec_to_time_t(smb_fname->st.st_ex_mtime);
+
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ DEBUG(3,("attempt to open a directory %s\n",
+ fsp_str_dbg(fsp)));
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_botherror(req, NT_STATUS_ACCESS_DENIED,
+ ERRDOS, ERRnoaccess);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 7, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+ SSVAL(req->outbuf,smb_vwv1,fattr);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv2,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv2,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv4,(uint32_t)size);
+ SSVAL(req->outbuf,smb_vwv6,deny_mode);
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+ out:
+ END_PROFILE(SMBopen);
+ return;
+}
+
+/****************************************************************************
+ Reply to an open and X.
+****************************************************************************/
+
+void reply_open_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint16_t open_flags;
+ int deny_mode;
+ uint32_t smb_attr;
+ /* Breakout the oplock request bits so we can set the
+ reply bits separately. */
+ int ex_oplock_request;
+ int core_oplock_request;
+ int oplock_request;
+#if 0
+ int smb_sattr = SVAL(req->vwv+4, 0);
+ uint32_t smb_time = make_unix_date3(req->vwv+6);
+#endif
+ int smb_ofun;
+ uint32_t fattr=0;
+ int mtime=0;
+ int smb_action = 0;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ NTSTATUS status;
+ uint64_t allocation_size;
+ ssize_t retval = -1;
+ uint32_t access_mask;
+ uint32_t share_mode;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t private_flags = 0;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBopenX);
+
+ if (req->wct < 15) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ open_flags = SVAL(req->vwv+2, 0);
+ deny_mode = SVAL(req->vwv+3, 0);
+ smb_attr = SVAL(req->vwv+5, 0);
+ ex_oplock_request = EXTENDED_OPLOCK_REQUEST(req->inbuf);
+ core_oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+ oplock_request = ex_oplock_request | core_oplock_request;
+ smb_ofun = SVAL(req->vwv+8, 0);
+ allocation_size = (uint64_t)IVAL(req->vwv+9, 0);
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ if (lp_nt_pipe_support()) {
+ reply_open_pipe_and_X(conn, req);
+ } else {
+ reply_nterror(req, NT_STATUS_NETWORK_ACCESS_DENIED);
+ }
+ goto out;
+ }
+
+ /* XXXX we need to handle passed times, sattr and flags */
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!map_open_params_to_ntcreate(fname, deny_mode,
+ smb_ofun,
+ &access_mask, &share_mode,
+ &create_disposition,
+ &create_options,
+ &private_flags)) {
+ reply_force_doserror(req, ERRDOS, ERRbadaccess);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ smb_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ private_flags,
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &smb_action, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ fsp = fcb_or_dos_open(
+ req,
+ smb_fname,
+ access_mask,
+ create_options,
+ private_flags);
+ if (fsp == NULL) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+
+ smb_action = FILE_WAS_OPENED;
+ }
+
+ /* Setting the "size" field in vwv9 and vwv10 causes the file to be set to this size,
+ if the file is truncated or created. */
+ if (((smb_action == FILE_WAS_CREATED) || (smb_action == FILE_WAS_OVERWRITTEN)) && allocation_size) {
+ fsp->initial_allocation_size = smb_roundup(fsp->conn, allocation_size);
+ if (vfs_allocate_file_space(fsp, fsp->initial_allocation_size) == -1) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ retval = vfs_set_filelen(fsp, (off_t)allocation_size);
+ if (retval < 0) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ fattr = fdos_mode(fsp);
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+ mtime = convert_timespec_to_time_t(fsp->fsp_name->st.st_ex_mtime);
+
+ /* If the caller set the extended oplock request bit
+ and we granted one (by whatever means) - set the
+ correct bit for extended oplock reply.
+ */
+
+ if (ex_oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ if(ex_oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ /* If the caller set the core oplock request bit
+ and we granted one (by whatever means) - set the
+ correct bit for core oplock reply.
+ */
+
+ if (open_flags & EXTENDED_RESPONSE_REQUIRED) {
+ reply_smb1_outbuf(req, 19, 0);
+ } else {
+ reply_smb1_outbuf(req, 15, 0);
+ }
+
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ if (core_oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(core_oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ SSVAL(req->outbuf,smb_vwv2,fsp->fnum);
+ SSVAL(req->outbuf,smb_vwv3,fattr);
+ if(lp_dos_filetime_resolution(SNUM(conn)) ) {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv4,mtime & ~1);
+ } else {
+ srv_put_dos_date3((char *)req->outbuf,smb_vwv4,mtime);
+ }
+ SIVAL(req->outbuf,smb_vwv6,(uint32_t)fsp->fsp_name->st.st_ex_size);
+ SSVAL(req->outbuf,smb_vwv8,GET_OPENX_MODE(deny_mode));
+ SSVAL(req->outbuf,smb_vwv11,smb_action);
+
+ if (open_flags & EXTENDED_RESPONSE_REQUIRED) {
+ SIVAL(req->outbuf, smb_vwv15, SEC_STD_ALL);
+ }
+
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBopenX);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBulogoffX.
+****************************************************************************/
+
+static struct tevent_req *reply_ulogoffX_send(struct smb_request *smb1req,
+ struct smbXsrv_session *session);
+static void reply_ulogoffX_done(struct tevent_req *req);
+
+void reply_ulogoffX(struct smb_request *smb1req)
+{
+ struct timeval now = timeval_current();
+ struct smbXsrv_session *session = NULL;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ /*
+ * Don't setup the profile charge here, take
+ * it in reply_ulogoffX_done(). Not strictly correct
+ * but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+
+ status = smb1srv_session_lookup(smb1req->xconn,
+ smb1req->vuid,
+ timeval_to_nttime(&now),
+ &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBulogoffX);
+ DBG_WARNING("ulogoff, vuser id %llu does not map to user.\n",
+ (unsigned long long)smb1req->vuid);
+
+ smb1req->vuid = UID_FIELD_INVALID;
+ reply_force_doserror(smb1req, ERRSRV, ERRbaduid);
+ END_PROFILE(SMBulogoffX);
+ return;
+ }
+
+ req = reply_ulogoffX_send(smb1req, session);
+ if (req == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBulogoffX);
+ reply_force_doserror(smb1req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBulogoffX);
+ return;
+ }
+
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_ulogoffX_done, smb1req);
+ return;
+}
+
+struct reply_ulogoffX_state {
+ struct tevent_queue *wait_queue;
+ struct smbXsrv_session *session;
+};
+
+static void reply_ulogoffX_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 ulogoffX.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_ulogoffX_send(struct smb_request *smb1req,
+ struct smbXsrv_session *session)
+{
+ struct tevent_req *req;
+ struct reply_ulogoffX_state *state;
+ struct tevent_req *subreq;
+ files_struct *fsp;
+ struct smbd_server_connection *sconn = session->client->sconn;
+ uint64_t vuid = session->global->session_wire_id;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_ulogoffX_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "reply_ulogoffX_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ state->session = session;
+
+ /*
+ * Make sure that no new request will be able to use this session.
+ * This ensures that once all outstanding fsp->aio_requests
+ * on this session are done, we are safe to close it.
+ */
+ session->status = NT_STATUS_USER_SESSION_DELETED;
+
+ for (fsp = sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->vuid != vuid) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the sconn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_ulogoffX_wait_done, req);
+
+ return req;
+}
+
+static void reply_ulogoffX_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_ulogoffX_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_ulogoffX_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ struct reply_ulogoffX_state *state = tevent_req_data(req,
+ struct reply_ulogoffX_state);
+ struct smbXsrv_session *session = state->session;
+ NTSTATUS status;
+
+ /*
+ * Take the profile charge here. Not strictly
+ * correct but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ START_PROFILE(SMBulogoffX);
+
+ status = reply_ulogoffX_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBulogoffX);
+ exit_server(__location__ ": reply_ulogoffX_recv failed");
+ return;
+ }
+
+ status = smbXsrv_session_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBulogoffX);
+ exit_server(__location__ ": smbXsrv_session_logoff failed");
+ return;
+ }
+
+ TALLOC_FREE(session);
+
+ reply_smb1_outbuf(smb1req, 2, 0);
+ SSVAL(smb1req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(smb1req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ DBG_NOTICE("ulogoffX vuid=%llu\n",
+ (unsigned long long)smb1req->vuid);
+
+ smb1req->vuid = UID_FIELD_INVALID;
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+ END_PROFILE(SMBulogoffX);
+}
+
+/****************************************************************************
+ Reply to a mknew or a create.
+****************************************************************************/
+
+void reply_mknew(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *fname = NULL;
+ uint32_t fattr = 0;
+ struct smb_file_time ft;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ int oplock_request = 0;
+ NTSTATUS status;
+ uint32_t access_mask = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
+ uint32_t share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBcreate);
+ init_smb_file_time(&ft);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ fattr = SVAL(req->vwv+0, 0);
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+
+ if (req->cmd == SMBmknew) {
+ /* We should fail if file exists. */
+ create_disposition = FILE_CREATE;
+ } else {
+ /* Create if file doesn't exist, truncate if it does. */
+ create_disposition = FILE_OVERWRITE_IF;
+ }
+
+ /* mtime. */
+ ft.mtime = time_t_to_full_timespec(srv_make_unix_date3(req->vwv+1));
+
+ srvstr_get_path_req(ctx, req, &fname, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, create_disposition);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (fattr & FILE_ATTRIBUTE_VOLUME) {
+ DEBUG(0,("Attempt to create file (%s) with volid set - "
+ "please report this\n",
+ smb_fname_str_dbg(smb_fname)));
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ fattr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ ft.atime = smb_fname->st.st_ex_atime; /* atime. */
+ status = smb_set_file_time(conn, fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ END_PROFILE(SMBcreate);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if(EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf,smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ DEBUG(2, ("reply_mknew: file %s\n", smb_fname_str_dbg(smb_fname)));
+ DEBUG(3, ("reply_mknew %s fd=%d dmode=0x%x\n",
+ smb_fname_str_dbg(smb_fname), fsp_get_io_fd(fsp),
+ (unsigned int)fattr));
+
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBcreate);
+ return;
+}
+
+/****************************************************************************
+ Reply to a create temporary file.
+****************************************************************************/
+
+void reply_ctemp(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_fname = NULL;
+ char *wire_name = NULL;
+ char *fname = NULL;
+ uint32_t fattr;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ int oplock_request;
+ char *s;
+ NTSTATUS status;
+ int i;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBctemp);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ fattr = SVAL(req->vwv+0, 0);
+ oplock_request = CORE_OPLOCK_REQUEST(req->inbuf);
+
+ srvstr_get_path_req(ctx, req, &wire_name, (const char *)req->buf+1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ for (i = 0; i < 10; i++) {
+ if (*wire_name) {
+ fname = talloc_asprintf(ctx,
+ "%s/TMP%s",
+ wire_name,
+ generate_random_str_list(ctx, 5, "0123456789"));
+ } else {
+ fname = talloc_asprintf(ctx,
+ "TMP%s",
+ generate_random_str_list(ctx, 5, "0123456789"));
+ }
+
+ if (!fname) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, FILE_CREATE);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Create the file. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ 0, /* create_options */
+ fattr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ TALLOC_FREE(fname);
+ TALLOC_FREE(dirfsp);
+ TALLOC_FREE(smb_fname);
+ continue;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(
+ status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ break;
+ }
+
+ if (i == 10) {
+ /* Collision after 10 times... */
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ /* the returned filename is relative to the directory */
+ s = strrchr_m(fsp->fsp_name->base_name, '/');
+ if (!s) {
+ s = fsp->fsp_name->base_name;
+ } else {
+ s++;
+ }
+
+#if 0
+ /* Tested vs W2K3 - this doesn't seem to be here - null terminated filename is the only
+ thing in the byte section. JRA */
+ SSVALS(p, 0, -1); /* what is this? not in spec */
+#endif
+ if (message_push_string(&req->outbuf, s, STR_ASCII|STR_TERMINATE)
+ == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ SCVAL(req->outbuf, smb_flg,
+ CVAL(req->outbuf,smb_flg)|CORE_OPLOCK_GRANTED);
+ }
+
+ DEBUG(2, ("reply_ctemp: created temp file %s\n", fsp_str_dbg(fsp)));
+ DEBUG(3, ("reply_ctemp %s fd=%d umode=0%o\n", fsp_str_dbg(fsp),
+ fsp_get_io_fd(fsp), (unsigned int)smb_fname->st.st_ex_mode));
+ out:
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(wire_name);
+ END_PROFILE(SMBctemp);
+ return;
+}
+
+/****************************************************************************
+ Reply to a unlink
+****************************************************************************/
+
+void reply_unlink(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t dirtype;
+ NTSTATUS status;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBunlink);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ dirtype = SVAL(req->vwv+0, 0);
+
+ srvstr_get_path_req(ctx, req, &name, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ ucf_flags | UCF_LCOMP_LNK_OK,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ DEBUG(3,("reply_unlink : %s\n", smb_fname_str_dbg(smb_fname)));
+
+ status = unlink_internals(conn, req, dirtype, dirfsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+ out:
+ TALLOC_FREE(smb_fname);
+ END_PROFILE(SMBunlink);
+ return;
+}
+
+/****************************************************************************
+ Fail for readbraw.
+****************************************************************************/
+
+static void fail_readraw(void)
+{
+ const char *errstr = talloc_asprintf(talloc_tos(),
+ "FAIL ! reply_readbraw: socket write fail (%s)",
+ strerror(errno));
+ if (!errstr) {
+ errstr = "";
+ }
+ exit_server_cleanly(errstr);
+}
+
+/****************************************************************************
+ Return a readbraw error (4 bytes of zero).
+****************************************************************************/
+
+static void reply_readbraw_error(struct smbXsrv_connection *xconn)
+{
+ char header[4];
+
+ SIVAL(header,0,0);
+
+ smbd_lock_socket(xconn);
+ if (write_data(xconn->transport.sock,header,4) != 4) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. "
+ "Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+
+ fail_readraw();
+ }
+ smbd_unlock_socket(xconn);
+}
+
+/*******************************************************************
+ Ensure we don't use sendfile if server smb signing is active.
+********************************************************************/
+
+static bool lp_use_sendfile(struct smbXsrv_connection *xconn,
+ int snum,
+ struct smb1_signing_state *signing_state)
+{
+ bool sign_active = false;
+
+ /* Using sendfile blows the brains out of any DOS or Win9x TCP stack... JRA. */
+ if (xconn->protocol < PROTOCOL_NT1) {
+ return false;
+ }
+ if (signing_state) {
+ sign_active = smb1_signing_is_active(signing_state);
+ }
+ return (lp__use_sendfile(snum) &&
+ (get_remote_arch() != RA_WIN95) &&
+ !sign_active);
+}
+/****************************************************************************
+ Use sendfile in readbraw.
+****************************************************************************/
+
+static void send_file_readbraw(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ off_t startpos,
+ size_t nread,
+ ssize_t mincount)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ char *outbuf = NULL;
+ ssize_t ret=0;
+
+ /*
+ * We can only use sendfile on a non-chained packet
+ * but we can use on a non-oplocked file. tridge proved this
+ * on a train in Germany :-). JRA.
+ * reply_readbraw has already checked the length.
+ */
+
+ if ( !req_is_in_chain(req) &&
+ (nread > 0) &&
+ !fsp_is_alternate_stream(fsp) &&
+ lp_use_sendfile(xconn, SNUM(conn), xconn->smb1.signing_state) ) {
+ ssize_t sendfile_read = -1;
+ char header[4];
+ DATA_BLOB header_blob;
+
+ _smb_setlen(header,nread);
+ header_blob = data_blob_const(header, 4);
+
+ sendfile_read = SMB_VFS_SENDFILE(xconn->transport.sock, fsp,
+ &header_blob, startpos,
+ nread);
+ if (sendfile_read == -1) {
+ /* Returning ENOSYS means no data at all was sent.
+ * Do this as a normal read. */
+ if (errno == ENOSYS) {
+ goto normal_readbraw;
+ }
+
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+ if (errno == EINTR) {
+ /* Ensure we don't do this again. */
+ set_use_sendfile(SNUM(conn), False);
+ DEBUG(0,("send_file_readbraw: sendfile not available. Faking..\n"));
+
+ if (fake_sendfile(xconn, fsp, startpos, nread) == -1) {
+ DEBUG(0,("send_file_readbraw: "
+ "fake_sendfile failed for "
+ "file %s (%s).\n",
+ fsp_str_dbg(fsp),
+ strerror(errno)));
+ exit_server_cleanly("send_file_readbraw fake_sendfile failed");
+ }
+ return;
+ }
+
+ DEBUG(0,("send_file_readbraw: sendfile failed for "
+ "file %s (%s). Terminating\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+ exit_server_cleanly("send_file_readbraw sendfile failed");
+ } else if (sendfile_read == 0) {
+ /*
+ * Some sendfile implementations return 0 to indicate
+ * that there was a short read, but nothing was
+ * actually written to the socket. In this case,
+ * fallback to the normal read path so the header gets
+ * the correct byte count.
+ */
+ DEBUG(3, ("send_file_readbraw: sendfile sent zero "
+ "bytes falling back to the normal read: "
+ "%s\n", fsp_str_dbg(fsp)));
+ goto normal_readbraw;
+ }
+
+ /* Deal with possible short send. */
+ if (sendfile_read != 4+nread) {
+ ret = sendfile_short_send(xconn, fsp,
+ sendfile_read, 4, nread);
+ if (ret == -1) {
+ fail_readraw();
+ }
+ }
+ return;
+ }
+
+normal_readbraw:
+
+ outbuf = talloc_array(NULL, char, nread+4);
+ if (!outbuf) {
+ DEBUG(0,("send_file_readbraw: talloc_array failed for size %u.\n",
+ (unsigned)(nread+4)));
+ reply_readbraw_error(xconn);
+ return;
+ }
+
+ if (nread > 0) {
+ ret = read_file(fsp,outbuf+4,startpos,nread);
+#if 0 /* mincount appears to be ignored in a W2K server. JRA. */
+ if (ret < mincount)
+ ret = 0;
+#else
+ if (ret < nread)
+ ret = 0;
+#endif
+ }
+
+ _smb_setlen(outbuf,ret);
+ if (write_data(xconn->transport.sock, outbuf, 4+ret) != 4+ret) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+
+ fail_readraw();
+ }
+
+ TALLOC_FREE(outbuf);
+}
+
+/****************************************************************************
+ Reply to a readbraw (core+ protocol).
+****************************************************************************/
+
+void reply_readbraw(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
+ ssize_t maxcount,mincount;
+ size_t nread = 0;
+ off_t startpos;
+ files_struct *fsp;
+ struct lock_struct lock;
+ off_t size = 0;
+ NTSTATUS status;
+
+ START_PROFILE(SMBreadbraw);
+
+ if (smb1_srv_is_signing_active(xconn) || req->encrypted) {
+ exit_server_cleanly("reply_readbraw: SMB signing/sealing is active - "
+ "raw reads/writes are disallowed.");
+ }
+
+ if (req->wct < 8) {
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ if (xconn->smb1.echo_handler.trusted_fde) {
+ DEBUG(2,("SMBreadbraw rejected with NOT_SUPPORTED because of "
+ "'async smb echo handler = yes'\n"));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ /*
+ * Special check if an oplock break has been issued
+ * and the readraw request croses on the wire, we must
+ * return a zero length response here.
+ */
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ /*
+ * We have to do a check_fsp by hand here, as
+ * we must always return 4 zero bytes on error,
+ * not a NTSTATUS.
+ */
+
+ if (fsp == NULL ||
+ conn == NULL ||
+ conn != fsp->conn ||
+ req->vuid != fsp->vuid ||
+ fsp->fsp_flags.is_directory ||
+ fsp_get_io_fd(fsp) == -1)
+ {
+ /*
+ * fsp could be NULL here so use the value from the packet. JRA.
+ */
+ DEBUG(3,("reply_readbraw: fnum %d not valid "
+ "- cache prime?\n",
+ (int)SVAL(req->vwv+0, 0)));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ /* Do a "by hand" version of CHECK_READ. */
+ if (!(fsp->fsp_flags.can_read ||
+ ((req->flags2 & FLAGS2_READ_PERMIT_EXECUTE) &&
+ (fsp->access_mask & FILE_EXECUTE)))) {
+ DEBUG(3,("reply_readbraw: fnum %d not readable.\n",
+ (int)SVAL(req->vwv+0, 0)));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+1, 0);
+ if(req->wct == 10) {
+ /*
+ * This is a large offset (64 bit) read.
+ */
+
+ startpos |= (((off_t)IVAL(req->vwv+8, 0)) << 32);
+
+ if(startpos < 0) {
+ DEBUG(0,("reply_readbraw: negative 64 bit "
+ "readraw offset (%.0f) !\n",
+ (double)startpos ));
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+ }
+
+ maxcount = (SVAL(req->vwv+3, 0) & 0xFFFF);
+ mincount = (SVAL(req->vwv+4, 0) & 0xFFFF);
+
+ /* ensure we don't overrun the packet size */
+ maxcount = MIN(65535,maxcount);
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)maxcount,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_readbraw_error(xconn);
+ END_PROFILE(SMBreadbraw);
+ return;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (NT_STATUS_IS_OK(status)) {
+ size = fsp->fsp_name->st.st_ex_size;
+ }
+
+ if (startpos >= size) {
+ nread = 0;
+ } else {
+ nread = MIN(maxcount,(size - startpos));
+ }
+
+#if 0 /* mincount appears to be ignored in a W2K server. JRA. */
+ if (nread < mincount)
+ nread = 0;
+#endif
+
+ DEBUG( 3, ( "reply_readbraw: %s start=%.0f max=%lu "
+ "min=%lu nread=%lu\n",
+ fsp_fnum_dbg(fsp), (double)startpos,
+ (unsigned long)maxcount,
+ (unsigned long)mincount,
+ (unsigned long)nread ) );
+
+ send_file_readbraw(conn, req, fsp, startpos, nread, mincount);
+
+ DEBUG(5,("reply_readbraw finished\n"));
+
+ END_PROFILE(SMBreadbraw);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a lockread (core+ protocol).
+****************************************************************************/
+
+static void reply_lockread_locked(struct tevent_req *subreq);
+
+void reply_lockread(struct smb_request *req)
+{
+ struct tevent_req *subreq = NULL;
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ struct smbd_lock_element *lck = NULL;
+
+ START_PROFILE(SMBlockread);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ lck = talloc(req, struct smbd_lock_element);
+ if (lck == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+
+ /*
+ * NB. Discovered by Menny Hamburger at Mainsoft. This is a core+
+ * protocol request that predates the read/write lock concept.
+ * Thus instead of asking for a read lock here we need to ask
+ * for a write lock. JRA.
+ * Note that the requested lock size is unaffected by max_send.
+ */
+
+ *lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = WRITE_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .count = SVAL(req->vwv+1, 0),
+ .offset = IVAL_TO_SMB_OFF_T(req->vwv+2, 0),
+ };
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ 0,
+ false, /* large_offset */
+ 1,
+ lck);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockread);
+ return;
+ }
+ tevent_req_set_callback(subreq, reply_lockread_locked, NULL);
+ END_PROFILE(SMBlockread);
+}
+
+static void reply_lockread_locked(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ ssize_t nread = -1;
+ char *data = NULL;
+ NTSTATUS status;
+ bool ok;
+ off_t startpos;
+ size_t numtoread, maxtoread;
+ struct files_struct *fsp = NULL;
+ char *p = NULL;
+
+ START_PROFILE(SMBlockread);
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto send;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ if (fsp == NULL) {
+ reply_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ goto send;
+ }
+
+ numtoread = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+
+ /*
+ * However the requested READ size IS affected by max_send. Insanity.... JRA.
+ */
+ maxtoread = req->xconn->smb1.sessions.max_send - (MIN_SMB_SIZE + 5*2 + 3);
+
+ if (numtoread > maxtoread) {
+ DBG_WARNING("requested read size (%zu) is greater than "
+ "maximum allowed (%zu/%d). "
+ "Returning short read of maximum allowed for "
+ "compatibility with Windows 2000.\n",
+ numtoread,
+ maxtoread,
+ req->xconn->smb1.sessions.max_send);
+ numtoread = maxtoread;
+ }
+
+ reply_smb1_outbuf(req, 5, numtoread + 3);
+
+ data = smb_buf(req->outbuf) + 3;
+
+ nread = read_file(fsp,data,startpos,numtoread);
+
+ if (nread < 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto send;
+ }
+
+ srv_smb1_set_message((char *)req->outbuf, 5, nread+3, False);
+
+ SSVAL(req->outbuf,smb_vwv0,nread);
+ SSVAL(req->outbuf,smb_vwv5,nread+3);
+ p = smb_buf(req->outbuf);
+ SCVAL(p,0,0); /* pad byte. */
+ SSVAL(p,1,nread);
+
+ DEBUG(3,("lockread %s num=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtoread, (int)nread));
+
+send:
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("reply_lock_done: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ END_PROFILE(SMBlockread);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a read.
+****************************************************************************/
+
+void reply_read(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtoread;
+ size_t maxtoread;
+ ssize_t nread = 0;
+ char *data;
+ off_t startpos;
+ files_struct *fsp;
+ struct lock_struct lock;
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ START_PROFILE(SMBread);
+
+ if (req->wct < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ numtoread = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+
+ /*
+ * The requested read size cannot be greater than max_send. JRA.
+ */
+ maxtoread = xconn->smb1.sessions.max_send - (MIN_SMB_SIZE + 5*2 + 3);
+
+ if (numtoread > maxtoread) {
+ DEBUG(0,("reply_read: requested read size (%u) is greater than maximum allowed (%u/%u). \
+Returning short read of maximum allowed for compatibility with Windows 2000.\n",
+ (unsigned int)numtoread, (unsigned int)maxtoread,
+ (unsigned int)xconn->smb1.sessions.max_send));
+ numtoread = maxtoread;
+ }
+
+ reply_smb1_outbuf(req, 5, numtoread+3);
+
+ data = smb_buf(req->outbuf) + 3;
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtoread,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBread);
+ return;
+ }
+
+ if (numtoread > 0)
+ nread = read_file(fsp,data,startpos,numtoread);
+
+ if (nread < 0) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ goto out;
+ }
+
+ srv_smb1_set_message((char *)req->outbuf, 5, nread+3, False);
+
+ SSVAL(req->outbuf,smb_vwv0,nread);
+ SSVAL(req->outbuf,smb_vwv5,nread+3);
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,nread);
+
+ DEBUG(3, ("read %s num=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtoread, (int)nread));
+
+out:
+ END_PROFILE(SMBread);
+ return;
+}
+
+/****************************************************************************
+ Setup readX header.
+****************************************************************************/
+
+size_t setup_readX_header(char *outbuf, size_t smb_maxcnt)
+{
+ size_t outsize;
+
+ outsize = srv_smb1_set_message(outbuf,12,smb_maxcnt + 1 /* padding byte */,
+ False);
+
+ memset(outbuf+smb_vwv0,'\0',24); /* valgrind init. */
+
+ SCVAL(outbuf,smb_vwv0,0xFF);
+ SSVAL(outbuf,smb_vwv2,0xFFFF); /* Remaining - must be -1. */
+ SSVAL(outbuf,smb_vwv5,smb_maxcnt);
+ SSVAL(outbuf,smb_vwv6,
+ (smb_wct - 4) /* offset from smb header to wct */
+ + 1 /* the wct field */
+ + 12 * sizeof(uint16_t) /* vwv */
+ + 2 /* the buflen field */
+ + 1); /* padding byte */
+ SSVAL(outbuf,smb_vwv7,(smb_maxcnt >> 16));
+ SCVAL(smb_buf(outbuf), 0, 0); /* padding byte */
+ /* Reset the outgoing length, set_message truncates at 0x1FFFF. */
+ _smb_setlen_large(outbuf,
+ smb_size + 12*2 + smb_maxcnt - 4 + 1 /* pad */);
+ return outsize;
+}
+
+/****************************************************************************
+ Reply to a read and X - possibly using sendfile.
+****************************************************************************/
+
+static void send_file_readX(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp, off_t startpos,
+ size_t smb_maxcnt)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ ssize_t nread = -1;
+ struct lock_struct lock;
+ int saved_errno = 0;
+ NTSTATUS status;
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)smb_maxcnt,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return;
+ }
+
+ /*
+ * We can only use sendfile on a non-chained packet
+ * but we can use on a non-oplocked file. tridge proved this
+ * on a train in Germany :-). JRA.
+ */
+
+ if (!req_is_in_chain(req) &&
+ !req->encrypted &&
+ !fsp_is_alternate_stream(fsp) &&
+ lp_use_sendfile(xconn, SNUM(conn), xconn->smb1.signing_state) ) {
+ uint8_t headerbuf[smb_size + 12 * 2 + 1 /* padding byte */];
+ DATA_BLOB header;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode) ||
+ (startpos > fsp->fsp_name->st.st_ex_size) ||
+ (smb_maxcnt > (fsp->fsp_name->st.st_ex_size - startpos))) {
+ /*
+ * We already know that we would do a short read, so don't
+ * try the sendfile() path.
+ */
+ goto nosendfile_read;
+ }
+
+ /*
+ * Set up the packet header before send. We
+ * assume here the sendfile will work (get the
+ * correct amount of data).
+ */
+
+ header = data_blob_const(headerbuf, sizeof(headerbuf));
+
+ construct_smb1_reply_common_req(req, (char *)headerbuf);
+ setup_readX_header((char *)headerbuf, smb_maxcnt);
+
+ nread = SMB_VFS_SENDFILE(xconn->transport.sock, fsp, &header,
+ startpos, smb_maxcnt);
+ if (nread == -1) {
+ saved_errno = errno;
+
+ /* Returning ENOSYS means no data at all was sent.
+ Do this as a normal read. */
+ if (errno == ENOSYS) {
+ goto normal_read;
+ }
+
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+
+ if (errno == EINTR) {
+ /* Ensure we don't do this again. */
+ set_use_sendfile(SNUM(conn), False);
+ DEBUG(0,("send_file_readX: sendfile not available. Faking..\n"));
+ nread = fake_sendfile(xconn, fsp, startpos,
+ smb_maxcnt);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("send_file_readX: "
+ "fake_sendfile failed for "
+ "file %s (%s) for client %s. "
+ "Terminating\n",
+ fsp_str_dbg(fsp),
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ exit_server_cleanly("send_file_readX: fake_sendfile failed");
+ }
+ DEBUG(3, ("send_file_readX: fake_sendfile %s max=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)smb_maxcnt, (int)nread));
+ /* No outbuf here means successful sendfile. */
+ goto out;
+ }
+
+ DEBUG(0,("send_file_readX: sendfile failed for file "
+ "%s (%s). Terminating\n", fsp_str_dbg(fsp),
+ strerror(errno)));
+ exit_server_cleanly("send_file_readX sendfile failed");
+ } else if (nread == 0) {
+ /*
+ * Some sendfile implementations return 0 to indicate
+ * that there was a short read, but nothing was
+ * actually written to the socket. In this case,
+ * fallback to the normal read path so the header gets
+ * the correct byte count.
+ */
+ DEBUG(3, ("send_file_readX: sendfile sent zero bytes "
+ "falling back to the normal read: %s\n",
+ fsp_str_dbg(fsp)));
+ goto normal_read;
+ }
+
+ DEBUG(3, ("send_file_readX: sendfile %s max=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)smb_maxcnt, (int)nread));
+
+ /* Deal with possible short send. */
+ if (nread != smb_maxcnt + sizeof(headerbuf)) {
+ ssize_t ret;
+
+ ret = sendfile_short_send(xconn, fsp, nread,
+ sizeof(headerbuf), smb_maxcnt);
+ if (ret == -1) {
+ const char *r;
+ r = "send_file_readX: sendfile_short_send failed";
+ DEBUG(0,("%s for file %s (%s).\n",
+ r, fsp_str_dbg(fsp), strerror(errno)));
+ exit_server_cleanly(r);
+ }
+ }
+ /* No outbuf here means successful sendfile. */
+ goto out;
+ }
+
+normal_read:
+
+ if ((smb_maxcnt & 0xFF0000) > 0x10000) {
+ uint8_t headerbuf[smb_size + 2*12 + 1 /* padding byte */];
+ ssize_t ret;
+
+ if (!S_ISREG(fsp->fsp_name->st.st_ex_mode) ||
+ (startpos > fsp->fsp_name->st.st_ex_size) ||
+ (smb_maxcnt > (fsp->fsp_name->st.st_ex_size - startpos))) {
+ /*
+ * We already know that we would do a short
+ * read, so don't try the sendfile() path.
+ */
+ goto nosendfile_read;
+ }
+
+ construct_smb1_reply_common_req(req, (char *)headerbuf);
+ setup_readX_header((char *)headerbuf, smb_maxcnt);
+
+ /* Send out the header. */
+ ret = write_data(xconn->transport.sock, (char *)headerbuf,
+ sizeof(headerbuf));
+ if (ret != sizeof(headerbuf)) {
+ saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0,("send_file_readX: write_data failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp),
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ exit_server_cleanly("send_file_readX sendfile failed");
+ }
+ nread = fake_sendfile(xconn, fsp, startpos, smb_maxcnt);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("send_file_readX: fake_sendfile failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp),
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ exit_server_cleanly("send_file_readX: fake_sendfile failed");
+ }
+ goto out;
+ }
+
+nosendfile_read:
+
+ reply_smb1_outbuf(req, 12, smb_maxcnt + 1 /* padding byte */);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ nread = read_file(fsp, smb_buf(req->outbuf) + 1 /* padding byte */,
+ startpos, smb_maxcnt);
+ saved_errno = errno;
+
+ if (nread < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ return;
+ }
+
+ setup_readX_header((char *)req->outbuf, nread);
+
+ DEBUG(3, ("send_file_readX %s max=%d nread=%d\n",
+ fsp_fnum_dbg(fsp), (int)smb_maxcnt, (int)nread));
+ return;
+
+out:
+ TALLOC_FREE(req->outbuf);
+ return;
+}
+
+/****************************************************************************
+ Work out how much space we have for a read return.
+****************************************************************************/
+
+static size_t calc_max_read_pdu(const struct smb_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (!lp_large_readwrite()) {
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (req_is_in_chain(req)) {
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (req->encrypted) {
+ /*
+ * Don't take encrypted traffic up to the
+ * limit. There are padding considerations
+ * that make that tricky.
+ */
+ return xconn->smb1.sessions.max_send;
+ }
+
+ if (smb1_srv_is_signing_active(xconn)) {
+ return 0x1FFFF;
+ }
+
+ if (!lp_smb1_unix_extensions()) {
+ return 0x1FFFF;
+ }
+
+ /*
+ * We can do ultra-large POSIX reads.
+ */
+ return 0xFFFFFF;
+}
+
+/****************************************************************************
+ Calculate how big a read can be. Copes with all clients. It's always
+ safe to return a short read - Windows does this.
+****************************************************************************/
+
+static size_t calc_read_size(const struct smb_request *req,
+ size_t upper_size,
+ size_t lower_size)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ size_t max_pdu = calc_max_read_pdu(req);
+ size_t total_size = 0;
+ size_t hdr_len = MIN_SMB_SIZE + VWV(12);
+ size_t max_len = max_pdu - hdr_len - 1 /* padding byte */;
+
+ /*
+ * Windows explicitly ignores upper size of 0xFFFF.
+ * See [MS-SMB].pdf <26> Section 2.2.4.2.1:
+ * We must do the same as these will never fit even in
+ * an extended size NetBIOS packet.
+ */
+ if (upper_size == 0xFFFF) {
+ upper_size = 0;
+ }
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ upper_size = 0;
+ }
+
+ total_size = ((upper_size<<16) | lower_size);
+
+ /*
+ * LARGE_READX test shows it's always safe to return
+ * a short read. Windows does so.
+ */
+ return MIN(total_size, max_len);
+}
+
+/****************************************************************************
+ Reply to a read and X.
+****************************************************************************/
+
+void reply_read_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ off_t startpos;
+ size_t smb_maxcnt;
+ size_t upper_size;
+ bool big_readX = False;
+#if 0
+ size_t smb_mincnt = SVAL(req->vwv+6, 0);
+#endif
+
+ START_PROFILE(SMBreadX);
+
+ if ((req->wct != 10) && (req->wct != 12)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+3, 0);
+ smb_maxcnt = SVAL(req->vwv+5, 0);
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ reply_pipe_read_and_X(req);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ if (!CHECK_READ(fsp,req)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBreadX);
+ return;
+ }
+
+ upper_size = SVAL(req->vwv+7, 0);
+ smb_maxcnt = calc_read_size(req, upper_size, smb_maxcnt);
+ if (smb_maxcnt > (0x1FFFF - (MIN_SMB_SIZE + VWV(12)))) {
+ /*
+ * This is a heuristic to avoid keeping large
+ * outgoing buffers around over long-lived aio
+ * requests.
+ */
+ big_readX = True;
+ }
+
+ if (req->wct == 12) {
+ /*
+ * This is a large offset (64 bit) read.
+ */
+ startpos |= (((off_t)IVAL(req->vwv+10, 0)) << 32);
+
+ }
+
+ if (!big_readX) {
+ NTSTATUS status = schedule_aio_read_and_X(conn,
+ req,
+ fsp,
+ startpos,
+ smb_maxcnt);
+ if (NT_STATUS_IS_OK(status)) {
+ /* Read scheduled - we're done. */
+ goto out;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error - report to client. */
+ END_PROFILE(SMBreadX);
+ reply_nterror(req, status);
+ return;
+ }
+ /* NT_STATUS_RETRY - fall back to sync read. */
+ }
+
+ smbd_lock_socket(req->xconn);
+ send_file_readX(conn, req, fsp, startpos, smb_maxcnt);
+ smbd_unlock_socket(req->xconn);
+
+ out:
+ END_PROFILE(SMBreadX);
+ return;
+}
+
+/****************************************************************************
+ Error replies to writebraw must have smb_wct == 1. Fix this up.
+****************************************************************************/
+
+void error_to_writebrawerr(struct smb_request *req)
+{
+ uint8_t *old_outbuf = req->outbuf;
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ memcpy(req->outbuf, old_outbuf, smb_size);
+ TALLOC_FREE(old_outbuf);
+}
+
+/****************************************************************************
+ Read 4 bytes of a smb packet and return the smb length of the packet.
+ Store the result in the buffer. This version of the function will
+ never return a session keepalive (length of zero).
+ Timeout is in milliseconds.
+****************************************************************************/
+
+static NTSTATUS read_smb_length(int fd, char *inbuf, unsigned int timeout,
+ size_t *len)
+{
+ uint8_t msgtype = NBSSkeepalive;
+
+ while (msgtype == NBSSkeepalive) {
+ NTSTATUS status;
+
+ status = read_smb_length_return_keepalive(fd, inbuf, timeout,
+ len);
+ if (!NT_STATUS_IS_OK(status)) {
+ char addr[INET6_ADDRSTRLEN];
+ /* Try and give an error message
+ * saying what client failed. */
+ DEBUG(0, ("read_smb_length_return_keepalive failed for "
+ "client %s read error = %s.\n",
+ get_peer_addr(fd,addr,sizeof(addr)),
+ nt_errstr(status)));
+ return status;
+ }
+
+ msgtype = CVAL(inbuf, 0);
+ }
+
+ DEBUG(10,("read_smb_length: got smb length of %lu\n",
+ (unsigned long)len));
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Reply to a writebraw (core+ or LANMAN1.0 protocol).
+****************************************************************************/
+
+void reply_writebraw(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
+ char *buf = NULL;
+ ssize_t nwritten=0;
+ ssize_t total_written=0;
+ size_t numtowrite=0;
+ size_t tcount;
+ off_t startpos;
+ const char *data=NULL;
+ bool write_through;
+ files_struct *fsp;
+ struct lock_struct lock;
+ NTSTATUS status;
+
+ START_PROFILE(SMBwritebraw);
+
+ /*
+ * If we ever reply with an error, it must have the SMB command
+ * type of SMBwritec, not SMBwriteBraw, as this tells the client
+ * we're finished.
+ */
+ SCVAL(discard_const_p(uint8_t, req->inbuf),smb_com,SMBwritec);
+
+ if (smb1_srv_is_signing_active(xconn)) {
+ END_PROFILE(SMBwritebraw);
+ exit_server_cleanly("reply_writebraw: SMB signing is active - "
+ "raw reads/writes are disallowed.");
+ }
+
+ if (req->wct < 12) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (xconn->smb1.echo_handler.trusted_fde) {
+ DEBUG(2,("SMBwritebraw rejected with NOT_SUPPORTED because of "
+ "'async smb echo handler = yes'\n"));
+ reply_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+ if (!check_fsp(conn, req, fsp)) {
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ tcount = IVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+3, 0);
+ write_through = BITSETW(req->vwv+7,0);
+
+ /* We have to deal with slightly different formats depending
+ on whether we are using the core+ or lanman1.0 protocol */
+
+ if(xconn->protocol <= PROTOCOL_COREPLUS) {
+ numtowrite = SVAL(smb_buf_const(req->inbuf),-2);
+ data = smb_buf_const(req->inbuf);
+ } else {
+ numtowrite = SVAL(req->vwv+10, 0);
+ data = smb_base(req->inbuf) + SVAL(req->vwv+11, 0);
+ }
+
+ /* Ensure we don't write bytes past the end of this packet. */
+ /*
+ * This already protects us against CVE-2017-12163.
+ */
+ if (data + numtowrite > smb_base(req->inbuf) + smb_len(req->inbuf)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+
+ if (!fsp->print_file) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)tcount,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ error_to_writebrawerr(req);
+ END_PROFILE(SMBwritebraw);
+ return;
+ }
+ }
+
+ if (numtowrite>0) {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ DEBUG(3, ("reply_writebraw: initial write %s start=%.0f num=%d "
+ "wrote=%d sync=%d\n",
+ fsp_fnum_dbg(fsp), (double)startpos, (int)numtowrite,
+ (int)nwritten, (int)write_through));
+
+ if (nwritten < (ssize_t)numtowrite) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ total_written = nwritten;
+
+ /* Allocate a buffer of 64k + length. */
+ buf = talloc_array(NULL, char, 65540);
+ if (!buf) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ /* Return a SMBwritebraw message to the redirector to tell
+ * it to send more bytes */
+
+ memcpy(buf, req->inbuf, smb_size);
+ srv_smb1_set_message(buf,xconn->protocol>PROTOCOL_COREPLUS?1:0,0,True);
+ SCVAL(buf,smb_com,SMBwritebraw);
+ SSVALS(buf,smb_vwv0,0xFFFF);
+ show_msg(buf);
+ if (!smb1_srv_send(req->xconn,
+ buf,
+ false,
+ 0, /* no signing */
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("reply_writebraw: smb1_srv_send "
+ "failed.");
+ }
+
+ /* Now read the raw data into the buffer and write it */
+ status = read_smb_length(xconn->transport.sock, buf, SMB_SECONDARY_WAIT,
+ &numtowrite);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ /* Set up outbuf to return the correct size */
+ reply_smb1_outbuf(req, 1, 0);
+
+ if (numtowrite != 0) {
+
+ if (numtowrite > 0xFFFF) {
+ DEBUG(0,("reply_writebraw: Oversize secondary write "
+ "raw requested (%u). Terminating\n",
+ (unsigned int)numtowrite ));
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ if (tcount > nwritten+numtowrite) {
+ DEBUG(3,("reply_writebraw: Client overestimated the "
+ "write %d %d %d\n",
+ (int)tcount,(int)nwritten,(int)numtowrite));
+ }
+
+ status = read_data_ntstatus(xconn->transport.sock, buf+4,
+ numtowrite);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Try and give an error message
+ * saying what client failed. */
+ DEBUG(0, ("reply_writebraw: Oversize secondary write "
+ "raw read failed (%s) for client %s. "
+ "Terminating\n", nt_errstr(status),
+ smbXsrv_connection_dbg(xconn)));
+ exit_server_cleanly("secondary writebraw failed");
+ }
+
+ /*
+ * We are not vulnerable to CVE-2017-12163
+ * here as we are guaranteed to have numtowrite
+ * bytes available - we just read from the client.
+ */
+ nwritten = write_file(req,fsp,buf+4,startpos+nwritten,numtowrite);
+ if (nwritten == -1) {
+ TALLOC_FREE(buf);
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(req->outbuf,smb_rcls,ERRHRD);
+ SSVAL(req->outbuf,smb_err,ERRdiskfull);
+ }
+
+ if (nwritten > 0) {
+ total_written += nwritten;
+ }
+ }
+
+ TALLOC_FREE(buf);
+ SSVAL(req->outbuf,smb_vwv0,total_written);
+
+ status = sync_file(conn, fsp, write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_writebraw: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ error_to_writebrawerr(req);
+ goto out;
+ }
+
+ DEBUG(3,("reply_writebraw: secondary write %s start=%.0f num=%d "
+ "wrote=%d\n",
+ fsp_fnum_dbg(fsp), (double)startpos, (int)numtowrite,
+ (int)total_written));
+
+ /* We won't return a status if write through is not selected - this
+ * follows what WfWg does */
+ END_PROFILE(SMBwritebraw);
+
+ if (!write_through && total_written==tcount) {
+
+#if RABBIT_PELLET_FIX
+ /*
+ * Fix for "rabbit pellet" mode, trigger an early TCP ack by
+ * sending a NBSSkeepalive. Thanks to DaveCB at Sun for this.
+ * JRA.
+ */
+ if (!send_keepalive(xconn->transport.sock)) {
+ exit_server_cleanly("reply_writebraw: send of "
+ "keepalive failed");
+ }
+#endif
+ TALLOC_FREE(req->outbuf);
+ }
+ return;
+
+out:
+ END_PROFILE(SMBwritebraw);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a writeunlock (core+).
+****************************************************************************/
+
+void reply_writeunlock(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ ssize_t nwritten = -1;
+ size_t numtowrite;
+ size_t remaining;
+ off_t startpos;
+ const char *data;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp;
+ struct lock_struct lock;
+ int saved_errno = 0;
+
+ START_PROFILE(SMBwriteunlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ numtowrite = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+ data = (const char *)req->buf + 3;
+
+ /*
+ * Ensure client isn't asking us to write more than
+ * they sent. CVE-2017-12163.
+ */
+ remaining = smbreq_bufrem(req, data);
+ if (numtowrite > remaining) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+
+ if (!fsp->print_file && numtowrite > 0) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBwriteunlock);
+ return;
+ }
+ }
+
+ /* The special X/Open SMB protocol handling of
+ zero length writes is *NOT* done for
+ this call */
+ if(numtowrite == 0) {
+ nwritten = 0;
+ } else {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ saved_errno = errno;
+ }
+
+ status = sync_file(conn, fsp, False /* write through */);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_writeunlock: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if(nwritten < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ goto out;
+ }
+
+ if((nwritten < numtowrite) && (numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ if (numtowrite && !fsp->print_file) {
+ struct smbd_lock_element l = {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = UNLOCK_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .offset = startpos,
+ .count = numtowrite,
+ };
+ status = smbd_do_unlocking(req, fsp, 1, &l);
+ if (NT_STATUS_V(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ DEBUG(3, ("writeunlock %s num=%d wrote=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+out:
+ END_PROFILE(SMBwriteunlock);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a write.
+****************************************************************************/
+
+void reply_write(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtowrite;
+ size_t remaining;
+ ssize_t nwritten = -1;
+ off_t startpos;
+ const char *data;
+ files_struct *fsp;
+ struct lock_struct lock;
+ NTSTATUS status;
+ int saved_errno = 0;
+
+ START_PROFILE(SMBwrite);
+
+ if (req->wct < 5) {
+ END_PROFILE(SMBwrite);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ reply_pipe_write(req);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ numtowrite = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+ data = (const char *)req->buf + 3;
+
+ /*
+ * Ensure client isn't asking us to write more than
+ * they sent. CVE-2017-12163.
+ */
+ remaining = smbreq_bufrem(req, data);
+ if (numtowrite > remaining) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+
+ if (!fsp->print_file) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBwrite);
+ return;
+ }
+ }
+
+ /*
+ * X/Open SMB protocol says that if smb_vwv1 is
+ * zero then the file size should be extended or
+ * truncated to the size given in smb_vwv[2-3].
+ */
+
+ if(numtowrite == 0) {
+ /*
+ * This is actually an allocate call, and set EOF. JRA.
+ */
+ nwritten = vfs_allocate_file_space(fsp, (off_t)startpos);
+ if (nwritten < 0) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ nwritten = vfs_set_filelen(fsp, (off_t)startpos);
+ if (nwritten < 0) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+ trigger_write_time_update_immediate(fsp);
+ } else {
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ }
+
+ status = sync_file(conn, fsp, False);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_write: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if(nwritten < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ goto out;
+ }
+
+ if((nwritten == 0) && (numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+ if (nwritten < (ssize_t)numtowrite) {
+ SCVAL(req->outbuf,smb_rcls,ERRHRD);
+ SSVAL(req->outbuf,smb_err,ERRdiskfull);
+ }
+
+ DEBUG(3, ("write %s num=%d wrote=%d\n", fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+out:
+ END_PROFILE(SMBwrite);
+ return;
+}
+
+/****************************************************************************
+ Ensure a buffer is a valid writeX for recvfile purposes.
+****************************************************************************/
+
+#define STANDARD_WRITE_AND_X_HEADER_SIZE (smb_size - 4 + /* basic header */ \
+ (2*14) + /* word count (including bcc) */ \
+ 1 /* pad byte */)
+
+bool is_valid_writeX_buffer(struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf)
+{
+ size_t numtowrite;
+ unsigned int doff = 0;
+ size_t len = smb_len_large(inbuf);
+ uint16_t fnum;
+ struct smbXsrv_open *op = NULL;
+ struct files_struct *fsp = NULL;
+ NTSTATUS status;
+
+ if (is_encrypted_packet(inbuf)) {
+ /* Can't do this on encrypted
+ * connections. */
+ return false;
+ }
+
+ if (CVAL(inbuf,smb_com) != SMBwriteX) {
+ return false;
+ }
+
+ if (CVAL(inbuf,smb_vwv0) != 0xFF ||
+ CVAL(inbuf,smb_wct) != 14) {
+ DEBUG(10,("is_valid_writeX_buffer: chained or "
+ "invalid word length.\n"));
+ return false;
+ }
+
+ fnum = SVAL(inbuf, smb_vwv2);
+ status = smb1srv_open_lookup(xconn,
+ fnum,
+ 0, /* now */
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("is_valid_writeX_buffer: bad fnum\n"));
+ return false;
+ }
+ fsp = op->compat;
+ if (fsp == NULL) {
+ DEBUG(10,("is_valid_writeX_buffer: bad fsp\n"));
+ return false;
+ }
+ if (fsp->conn == NULL) {
+ DEBUG(10,("is_valid_writeX_buffer: bad fsp->conn\n"));
+ return false;
+ }
+
+ if (IS_IPC(fsp->conn)) {
+ DEBUG(10,("is_valid_writeX_buffer: IPC$ tid\n"));
+ return false;
+ }
+ if (IS_PRINT(fsp->conn)) {
+ DEBUG(10,("is_valid_writeX_buffer: printing tid\n"));
+ return false;
+ }
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10,("is_valid_writeX_buffer: stream fsp\n"));
+ return false;
+ }
+ doff = SVAL(inbuf,smb_vwv11);
+
+ numtowrite = SVAL(inbuf,smb_vwv10);
+
+ if (len > doff && len - doff > 0xFFFF) {
+ numtowrite |= (((size_t)SVAL(inbuf,smb_vwv9))<<16);
+ }
+
+ if (numtowrite == 0) {
+ DEBUG(10,("is_valid_writeX_buffer: zero write\n"));
+ return false;
+ }
+
+ /* Ensure the sizes match up. */
+ if (doff < STANDARD_WRITE_AND_X_HEADER_SIZE) {
+ /* no pad byte...old smbclient :-( */
+ DEBUG(10,("is_valid_writeX_buffer: small doff %u (min %u)\n",
+ (unsigned int)doff,
+ (unsigned int)STANDARD_WRITE_AND_X_HEADER_SIZE));
+ return false;
+ }
+
+ if (len - doff != numtowrite) {
+ DEBUG(10,("is_valid_writeX_buffer: doff mismatch "
+ "len = %u, doff = %u, numtowrite = %u\n",
+ (unsigned int)len,
+ (unsigned int)doff,
+ (unsigned int)numtowrite ));
+ return false;
+ }
+
+ DEBUG(10,("is_valid_writeX_buffer: true "
+ "len = %u, doff = %u, numtowrite = %u\n",
+ (unsigned int)len,
+ (unsigned int)doff,
+ (unsigned int)numtowrite ));
+
+ return true;
+}
+
+/****************************************************************************
+ Reply to a write and X.
+****************************************************************************/
+
+void reply_write_and_X(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smbXsrv_connection *xconn = req->xconn;
+ files_struct *fsp;
+ struct lock_struct lock;
+ off_t startpos;
+ size_t numtowrite;
+ bool write_through;
+ ssize_t nwritten;
+ unsigned int smb_doff;
+ unsigned int smblen;
+ const char *data;
+ NTSTATUS status;
+ int saved_errno = 0;
+
+ START_PROFILE(SMBwriteX);
+
+ if ((req->wct != 12) && (req->wct != 14)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ numtowrite = SVAL(req->vwv+10, 0);
+ smb_doff = SVAL(req->vwv+11, 0);
+ smblen = smb_len(req->inbuf);
+
+ if (req->unread_bytes > 0xFFFF ||
+ (smblen > smb_doff &&
+ smblen - smb_doff > 0xFFFF)) {
+ numtowrite |= (((size_t)SVAL(req->vwv+9, 0))<<16);
+ }
+
+ if (req->unread_bytes) {
+ /* Can't do a recvfile write on IPC$ */
+ if (IS_IPC(conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ if (numtowrite != req->unread_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ } else {
+ /*
+ * This already protects us against CVE-2017-12163.
+ */
+ if (smb_doff > smblen || smb_doff + numtowrite < numtowrite ||
+ smb_doff + numtowrite > smblen) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ }
+
+ /* If it's an IPC, pass off the pipe handler. */
+ if (IS_IPC(conn)) {
+ if (req->unread_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ reply_pipe_write_and_X(req);
+ goto out;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+3, 0);
+ write_through = BITSETW(req->vwv+7,0);
+
+ if (!check_fsp(conn, req, fsp)) {
+ goto out;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ data = smb_base(req->inbuf) + smb_doff;
+
+ if(req->wct == 14) {
+ /*
+ * This is a large offset (64 bit) write.
+ */
+ startpos |= (((off_t)IVAL(req->vwv+12, 0)) << 32);
+
+ }
+
+ /* X/Open SMB protocol says that, unlike SMBwrite
+ if the length is zero then NO truncation is
+ done, just a write of zero. To truncate a file,
+ use SMBwrite. */
+
+ if(numtowrite == 0) {
+ nwritten = 0;
+ } else {
+ if (req->unread_bytes == 0) {
+ status = schedule_aio_write_and_X(conn,
+ req,
+ fsp,
+ data,
+ startpos,
+ numtowrite);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* write scheduled - we're done. */
+ goto out;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error - report to client. */
+ reply_nterror(req, status);
+ goto out;
+ }
+ /* NT_STATUS_RETRY - fall through to sync write. */
+ }
+
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ goto out;
+ }
+
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+ saved_errno = errno;
+ }
+
+ if(nwritten < 0) {
+ reply_nterror(req, map_nt_error_from_unix(saved_errno));
+ goto out;
+ }
+
+ if((nwritten == 0) && (numtowrite != 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 6, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+ SSVAL(req->outbuf,smb_vwv2,nwritten);
+ SSVAL(req->outbuf,smb_vwv4,nwritten>>16);
+
+ DEBUG(3,("writeX %s num=%d wrote=%d\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten));
+
+ status = sync_file(conn, fsp, write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_write_and_X: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ END_PROFILE(SMBwriteX);
+ return;
+
+out:
+ if (req->unread_bytes) {
+ /* writeX failed. drain socket. */
+ if (drain_socket(xconn->transport.sock, req->unread_bytes) !=
+ req->unread_bytes) {
+ smb_panic("failed to drain pending bytes");
+ }
+ req->unread_bytes = 0;
+ }
+
+ END_PROFILE(SMBwriteX);
+ return;
+}
+
+/****************************************************************************
+ Reply to a lseek.
+****************************************************************************/
+
+void reply_lseek(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ off_t startpos;
+ off_t res= -1;
+ int mode,umode;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBlseek);
+
+ if (req->wct < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlseek);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ mode = SVAL(req->vwv+1, 0) & 3;
+ /* NB. This doesn't use IVAL_TO_SMB_OFF_T as startpos can be signed in this case. */
+ startpos = (off_t)IVALS(req->vwv+2, 0);
+
+ switch (mode) {
+ case 0:
+ umode = SEEK_SET;
+ res = startpos;
+ break;
+ case 1:
+ umode = SEEK_CUR;
+ res = fh_get_pos(fsp->fh) + startpos;
+ break;
+ case 2:
+ umode = SEEK_END;
+ break;
+ default:
+ umode = SEEK_SET;
+ res = startpos;
+ break;
+ }
+
+ if (umode == SEEK_END) {
+ if((res = SMB_VFS_LSEEK(fsp,startpos,umode)) == -1) {
+ if(errno == EINVAL) {
+ off_t current_pos = startpos;
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBlseek);
+ return;
+ }
+
+ current_pos += fsp->fsp_name->st.st_ex_size;
+ if(current_pos < 0)
+ res = SMB_VFS_LSEEK(fsp,0,SEEK_SET);
+ }
+ }
+
+ if(res == -1) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ END_PROFILE(SMBlseek);
+ return;
+ }
+ }
+
+ fh_set_pos(fsp->fh, res);
+
+ reply_smb1_outbuf(req, 2, 0);
+ SIVAL(req->outbuf,smb_vwv0,res);
+
+ DEBUG(3,("lseek %s ofs=%.0f newpos = %.0f mode=%d\n",
+ fsp_fnum_dbg(fsp), (double)startpos, (double)res, mode));
+
+ END_PROFILE(SMBlseek);
+ return;
+}
+
+static struct files_struct *file_sync_one_fn(struct files_struct *fsp,
+ void *private_data)
+{
+ connection_struct *conn = talloc_get_type_abort(
+ private_data, connection_struct);
+
+ if (conn != fsp->conn) {
+ return NULL;
+ }
+ if (fsp_get_io_fd(fsp) == -1) {
+ return NULL;
+ }
+ sync_file(conn, fsp, True /* write through */);
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+
+ return NULL;
+}
+
+/****************************************************************************
+ Reply to a flush.
+****************************************************************************/
+
+void reply_flush(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ uint16_t fnum;
+ files_struct *fsp;
+
+ START_PROFILE(SMBflush);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fnum = SVAL(req->vwv+0, 0);
+ fsp = file_fsp(req, fnum);
+
+ if ((fnum != 0xFFFF) && !check_fsp(conn, req, fsp)) {
+ return;
+ }
+
+ if (!fsp) {
+ files_forall(req->sconn, file_sync_one_fn, conn);
+ } else {
+ NTSTATUS status = sync_file(conn, fsp, True);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("reply_flush: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ END_PROFILE(SMBflush);
+ return;
+ }
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3,("flush\n"));
+ END_PROFILE(SMBflush);
+ return;
+}
+
+/****************************************************************************
+ Reply to a exit.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+static struct tevent_req *reply_exit_send(struct smb_request *smb1req);
+static void reply_exit_done(struct tevent_req *req);
+
+void reply_exit(struct smb_request *smb1req)
+{
+ struct tevent_req *req;
+
+ /*
+ * Don't setup the profile charge here, take
+ * it in reply_exit_done(). Not strictly correct
+ * but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ req = reply_exit_send(smb1req);
+ if (req == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBexit);
+ reply_force_doserror(smb1req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBexit);
+ return;
+ }
+
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_exit_done, smb1req);
+ return;
+}
+
+struct reply_exit_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void reply_exit_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 exit.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_exit_send(struct smb_request *smb1req)
+{
+ struct tevent_req *req;
+ struct reply_exit_state *state;
+ struct tevent_req *subreq;
+ files_struct *fsp;
+ struct smbd_server_connection *sconn = smb1req->sconn;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_exit_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "reply_exit_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ for (fsp = sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->file_pid != smb1req->smbpid) {
+ continue;
+ }
+ if (fsp->vuid != smb1req->vuid) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the conn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_exit_wait_done, req);
+
+ return req;
+}
+
+static void reply_exit_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_exit_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_exit_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ struct smbd_server_connection *sconn = smb1req->sconn;
+ struct smbXsrv_connection *xconn = smb1req->xconn;
+ NTTIME now = timeval_to_nttime(&smb1req->request_time);
+ struct smbXsrv_session *session = NULL;
+ files_struct *fsp, *next;
+ NTSTATUS status;
+
+ /*
+ * Take the profile charge here. Not strictly
+ * correct but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ START_PROFILE(SMBexit);
+
+ status = reply_exit_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBexit);
+ exit_server(__location__ ": reply_exit_recv failed");
+ return;
+ }
+
+ /*
+ * Ensure the session is still valid.
+ */
+ status = smb1srv_session_lookup(xconn,
+ smb1req->vuid,
+ now,
+ &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ smb_request_done(smb1req);
+ END_PROFILE(SMBexit);
+ return;
+ }
+
+ /*
+ * Ensure the vuid is still valid - no one
+ * called reply_ulogoffX() in the meantime.
+ * reply_exit() doesn't have AS_USER set, so
+ * use set_current_user_info() directly.
+ * This is the same logic as in switch_message().
+ */
+ if (session->global->auth_session_info != NULL) {
+ set_current_user_info(
+ session->global->auth_session_info->unix_info->sanitized_username,
+ session->global->auth_session_info->unix_info->unix_name,
+ session->global->auth_session_info->info->domain_name);
+ }
+
+ /* No more aio - do the actual closes. */
+ for (fsp = sconn->files; fsp; fsp = next) {
+ bool ok;
+ next = fsp->next;
+
+ if (fsp->file_pid != smb1req->smbpid) {
+ continue;
+ }
+ if (fsp->vuid != smb1req->vuid) {
+ continue;
+ }
+ if (!fsp->fsp_flags.closing) {
+ continue;
+ }
+
+ /*
+ * reply_exit() has the DO_CHDIR flag set.
+ */
+ ok = chdir_current_service(fsp->conn);
+ if (!ok) {
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ smb_request_done(smb1req);
+ END_PROFILE(SMBexit);
+ return;
+ }
+ close_file_free(NULL, &fsp, SHUTDOWN_CLOSE);
+ }
+
+ reply_smb1_outbuf(smb1req, 0, 0);
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+ DBG_INFO("reply_exit complete\n");
+ END_PROFILE(SMBexit);
+ return;
+}
+
+static struct tevent_req *reply_close_send(struct smb_request *smb1req,
+ files_struct *fsp);
+static void reply_close_done(struct tevent_req *req);
+
+void reply_close(struct smb_request *smb1req)
+{
+ connection_struct *conn = smb1req->conn;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp = NULL;
+ START_PROFILE(SMBclose);
+
+ if (smb1req->wct < 3) {
+ reply_nterror(smb1req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ fsp = file_fsp(smb1req, SVAL(smb1req->vwv+0, 0));
+
+ /*
+ * We can only use check_fsp if we know it's not a directory.
+ */
+
+ if (!check_fsp_open(conn, smb1req, fsp)) {
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ DBG_NOTICE("Close %s fd=%d %s (numopen=%d)\n",
+ fsp->fsp_flags.is_directory ?
+ "directory" : "file",
+ fsp_get_pathref_fd(fsp), fsp_fnum_dbg(fsp),
+ conn->num_files_open);
+
+ if (!fsp->fsp_flags.is_directory) {
+ time_t t;
+
+ /*
+ * Take care of any time sent in the close.
+ */
+
+ t = srv_make_unix_date3(smb1req->vwv+1);
+ set_close_write_time(fsp, time_t_to_full_timespec(t));
+ }
+
+ if (fsp->num_aio_requests != 0) {
+ struct tevent_req *req;
+
+ req = reply_close_send(smb1req, fsp);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_close_done, smb1req);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ /*
+ * close_file_free() returns the unix errno if an error was detected on
+ * close - normally this is due to a disk full error. If not then it
+ * was probably an I/O error.
+ */
+
+ status = close_file_free(smb1req, &fsp, NORMAL_CLOSE);
+done:
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(smb1req, status);
+ END_PROFILE(SMBclose);
+ return;
+ }
+
+ reply_smb1_outbuf(smb1req, 0, 0);
+ END_PROFILE(SMBclose);
+ return;
+}
+
+struct reply_close_state {
+ files_struct *fsp;
+ struct tevent_queue *wait_queue;
+};
+
+static void reply_close_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 close.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_close_send(struct smb_request *smb1req,
+ files_struct *fsp)
+{
+ struct tevent_req *req;
+ struct reply_close_state *state;
+ struct tevent_req *subreq;
+ struct smbd_server_connection *sconn = smb1req->sconn;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state,
+ "reply_close_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it.
+ */
+ fsp->fsp_flags.closing = true;
+
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the conn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_close_wait_done, req);
+
+ return req;
+}
+
+static void reply_close_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_close_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_close_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ struct reply_close_state *state = tevent_req_data(req,
+ struct reply_close_state);
+ NTSTATUS status;
+
+ status = reply_close_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ exit_server(__location__ ": reply_close_recv failed");
+ return;
+ }
+
+ status = close_file_free(smb1req, &state->fsp, NORMAL_CLOSE);
+ if (NT_STATUS_IS_OK(status)) {
+ reply_smb1_outbuf(smb1req, 0, 0);
+ } else {
+ reply_nterror(smb1req, status);
+ }
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+}
+
+/****************************************************************************
+ Reply to a writeclose (Core+ protocol).
+****************************************************************************/
+
+void reply_writeclose(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ size_t numtowrite;
+ size_t remaining;
+ ssize_t nwritten = -1;
+ NTSTATUS close_status = NT_STATUS_OK;
+ off_t startpos;
+ const char *data;
+ struct timespec mtime;
+ files_struct *fsp;
+ struct lock_struct lock;
+ NTSTATUS status;
+
+ START_PROFILE(SMBwriteclose);
+
+ if (req->wct < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ numtowrite = SVAL(req->vwv+1, 0);
+ startpos = IVAL_TO_SMB_OFF_T(req->vwv+2, 0);
+ mtime = time_t_to_full_timespec(srv_make_unix_date3(req->vwv+4));
+ data = (const char *)req->buf + 1;
+
+ /*
+ * Ensure client isn't asking us to write more than
+ * they sent. CVE-2017-12163.
+ */
+ remaining = smbreq_bufrem(req, data);
+ if (numtowrite > remaining) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+
+ if (fsp->print_file == NULL) {
+ init_strict_lock_struct(fsp,
+ (uint64_t)req->smbpid,
+ (uint64_t)startpos,
+ (uint64_t)numtowrite,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ END_PROFILE(SMBwriteclose);
+ return;
+ }
+ }
+
+ nwritten = write_file(req,fsp,data,startpos,numtowrite);
+
+ set_close_write_time(fsp, mtime);
+
+ /*
+ * More insanity. W2K only closes the file if writelen > 0.
+ * JRA.
+ */
+
+ DEBUG(3,("writeclose %s num=%d wrote=%d (numopen=%d)\n",
+ fsp_fnum_dbg(fsp), (int)numtowrite, (int)nwritten,
+ (numtowrite) ? conn->num_files_open - 1 : conn->num_files_open));
+
+ if (numtowrite) {
+ DEBUG(3,("reply_writeclose: zero length write doesn't close "
+ "file %s\n", fsp_str_dbg(fsp)));
+ close_status = close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ if(((nwritten == 0) && (numtowrite != 0))||(nwritten < 0)) {
+ reply_nterror(req, NT_STATUS_DISK_FULL);
+ goto out;
+ }
+
+ if(!NT_STATUS_IS_OK(close_status)) {
+ reply_nterror(req, close_status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+
+ SSVAL(req->outbuf,smb_vwv0,nwritten);
+
+out:
+
+ END_PROFILE(SMBwriteclose);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Reply to a lock.
+****************************************************************************/
+
+static void reply_lock_done(struct tevent_req *subreq);
+
+void reply_lock(struct smb_request *req)
+{
+ struct tevent_req *subreq = NULL;
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ struct smbd_lock_element *lck = NULL;
+
+ START_PROFILE(SMBlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ lck = talloc(req, struct smbd_lock_element);
+ if (lck == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlock);
+ return;
+ }
+
+ *lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = WRITE_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .count = IVAL(req->vwv+1, 0),
+ .offset = IVAL(req->vwv+3, 0),
+ };
+
+ DBG_NOTICE("lock fd=%d %s offset=%"PRIu64" count=%"PRIu64"\n",
+ fsp_get_io_fd(fsp),
+ fsp_fnum_dbg(fsp),
+ lck->offset,
+ lck->count);
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ 0,
+ false, /* large_offset */
+ 1,
+ lck);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlock);
+ return;
+ }
+ tevent_req_set_callback(subreq, reply_lock_done, NULL);
+ END_PROFILE(SMBlock);
+}
+
+static void reply_lock_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ START_PROFILE(SMBlock);
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(status)) {
+ reply_smb1_outbuf(req, 0, 0);
+ } else {
+ reply_nterror(req, status);
+ }
+
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("reply_lock_done: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ END_PROFILE(SMBlock);
+}
+
+/****************************************************************************
+ Reply to a unlock.
+****************************************************************************/
+
+void reply_unlock(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ NTSTATUS status;
+ files_struct *fsp;
+ struct smbd_lock_element lck;
+
+ START_PROFILE(SMBunlock);
+
+ if (req->wct < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = req->smbpid,
+ .brltype = UNLOCK_LOCK,
+ .lock_flav = WINDOWS_LOCK,
+ .offset = IVAL(req->vwv+3, 0),
+ .count = IVAL(req->vwv+1, 0),
+ };
+
+ status = smbd_do_unlocking(req, fsp, 1, &lck);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBunlock);
+ return;
+ }
+
+ DBG_NOTICE("unlock fd=%d %s offset=%"PRIu64" count=%"PRIu64"\n",
+ fsp_get_io_fd(fsp),
+ fsp_fnum_dbg(fsp),
+ lck.offset,
+ lck.count);
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBunlock);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a tdis.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+static struct tevent_req *reply_tdis_send(struct smb_request *smb1req);
+static void reply_tdis_done(struct tevent_req *req);
+
+void reply_tdis(struct smb_request *smb1req)
+{
+ connection_struct *conn = smb1req->conn;
+ struct tevent_req *req;
+
+ /*
+ * Don't setup the profile charge here, take
+ * it in reply_tdis_done(). Not strictly correct
+ * but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+
+ if (conn == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBtdis);
+ DBG_INFO("Invalid connection in tdis\n");
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ END_PROFILE(SMBtdis);
+ return;
+ }
+
+ req = reply_tdis_send(smb1req);
+ if (req == NULL) {
+ /* Not going async, profile here. */
+ START_PROFILE(SMBtdis);
+ reply_force_doserror(smb1req, ERRDOS, ERRnomem);
+ END_PROFILE(SMBtdis);
+ return;
+ }
+ /* We're async. This will complete later. */
+ tevent_req_set_callback(req, reply_tdis_done, smb1req);
+ return;
+}
+
+struct reply_tdis_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void reply_tdis_wait_done(struct tevent_req *subreq);
+
+/****************************************************************************
+ Async SMB1 tdis.
+ Note, on failure here we deallocate and return NULL to allow the caller to
+ SMB1 return an error of ERRnomem immediately.
+****************************************************************************/
+
+static struct tevent_req *reply_tdis_send(struct smb_request *smb1req)
+{
+ struct tevent_req *req;
+ struct reply_tdis_state *state;
+ struct tevent_req *subreq;
+ connection_struct *conn = smb1req->conn;
+ files_struct *fsp;
+
+ req = tevent_req_create(smb1req, &state,
+ struct reply_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wait_queue = tevent_queue_create(state, "reply_tdis_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * Make sure that no new request will be able to use this tcon.
+ * This ensures that once all outstanding fsp->aio_requests
+ * on this tcon are done, we are safe to close it.
+ */
+ conn->tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ for (fsp = conn->sconn->files; fsp; fsp = fsp->next) {
+ if (fsp->conn != conn) {
+ continue;
+ }
+ /*
+ * Flag the file as close in progress.
+ * This will prevent any more IO being
+ * done on it. Not strictly needed, but
+ * doesn't hurt to flag it as closing.
+ */
+ fsp->fsp_flags.closing = true;
+
+ if (fsp->num_aio_requests > 0) {
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(fsp->aio_requests,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and reply to the outstanding SMB1 request.
+ */
+ subreq = tevent_queue_wait_send(state,
+ conn->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ TALLOC_FREE(req);
+ return NULL;
+ }
+
+ /*
+ * We're really going async - move the SMB1 request from
+ * a talloc stackframe above us to the sconn talloc-context.
+ * We need this to stick around until the wait_done
+ * callback is invoked.
+ */
+ smb1req = talloc_move(smb1req->sconn, &smb1req);
+
+ tevent_req_set_callback(subreq, reply_tdis_wait_done, req);
+
+ return req;
+}
+
+static void reply_tdis_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS reply_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void reply_tdis_done(struct tevent_req *req)
+{
+ struct smb_request *smb1req = tevent_req_callback_data(
+ req, struct smb_request);
+ NTSTATUS status;
+ struct smbXsrv_tcon *tcon = smb1req->conn->tcon;
+ bool ok;
+
+ /*
+ * Take the profile charge here. Not strictly
+ * correct but better than the other SMB1 async
+ * code that double-charges at the moment.
+ */
+ START_PROFILE(SMBtdis);
+
+ status = reply_tdis_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBtdis);
+ exit_server(__location__ ": reply_tdis_recv failed");
+ return;
+ }
+
+ /*
+ * As we've been awoken, we may have changed
+ * directory in the meantime.
+ * reply_tdis() has the DO_CHDIR flag set.
+ */
+ ok = chdir_current_service(smb1req->conn);
+ if (!ok) {
+ reply_force_doserror(smb1req, ERRSRV, ERRinvnid);
+ smb_request_done(smb1req);
+ END_PROFILE(SMBtdis);
+ }
+
+ status = smbXsrv_tcon_disconnect(tcon,
+ smb1req->vuid);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb1req);
+ END_PROFILE(SMBtdis);
+ exit_server(__location__ ": smbXsrv_tcon_disconnect failed");
+ return;
+ }
+
+ /* smbXsrv_tcon_disconnect frees smb1req->conn. */
+ smb1req->conn = NULL;
+
+ TALLOC_FREE(tcon);
+
+ reply_smb1_outbuf(smb1req, 0, 0);
+ /*
+ * The following call is needed to push the
+ * reply data back out the socket after async
+ * return. Plus it frees smb1req.
+ */
+ smb_request_done(smb1req);
+ END_PROFILE(SMBtdis);
+}
+
+/****************************************************************************
+ Reply to a echo.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+void reply_echo(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int smb_reverb;
+ int seq_num;
+
+ START_PROFILE(SMBecho);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBecho);
+ return;
+ }
+
+ smb_reverb = SVAL(req->vwv+0, 0);
+
+ reply_smb1_outbuf(req, 1, req->buflen);
+
+ /* copy any incoming data back out */
+ if (req->buflen > 0) {
+ memcpy(smb_buf(req->outbuf), req->buf, req->buflen);
+ }
+
+ if (smb_reverb > 100) {
+ DEBUG(0,("large reverb (%d)?? Setting to 100\n",smb_reverb));
+ smb_reverb = 100;
+ }
+
+ for (seq_num = 1 ; seq_num <= smb_reverb ; seq_num++) {
+
+ SSVAL(req->outbuf,smb_vwv0,seq_num);
+
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn) || req->encrypted))
+ exit_server_cleanly("reply_echo: smb1_srv_send failed.");
+ }
+
+ DEBUG(3,("echo %d times\n", smb_reverb));
+
+ TALLOC_FREE(req->outbuf);
+
+ END_PROFILE(SMBecho);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printopen.
+****************************************************************************/
+
+void reply_printopen(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplopen);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ if (!CAN_PRINT(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ status = file_new(req, conn, &fsp);
+ if(!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ /* Open for exclusive use, write only. */
+ status = print_spool_open(fsp, NULL, req->vuid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(req, fsp);
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplopen);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf,smb_vwv0,fsp->fnum);
+
+ DEBUG(3,("openprint fd=%d %s\n",
+ fsp_get_io_fd(fsp), fsp_fnum_dbg(fsp)));
+
+ END_PROFILE(SMBsplopen);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printclose.
+****************************************************************************/
+
+void reply_printclose(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ if (!CAN_PRINT(conn)) {
+ reply_force_doserror(req, ERRSRV, ERRerror);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ DEBUG(3,("printclose fd=%d %s\n",
+ fsp_get_io_fd(fsp), fsp_fnum_dbg(fsp)));
+
+ status = close_file_free(req, &fsp, NORMAL_CLOSE);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplclose);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsplclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printqueue.
+****************************************************************************/
+
+void reply_printqueue(struct smb_request *req)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ connection_struct *conn = req->conn;
+ int max_count;
+ int start_index;
+
+ START_PROFILE(SMBsplretq);
+
+ if (req->wct < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplretq);
+ return;
+ }
+
+ max_count = SVAL(req->vwv+0, 0);
+ start_index = SVAL(req->vwv+1, 0);
+
+ /* we used to allow the client to get the cnum wrong, but that
+ is really quite gross and only worked when there was only
+ one printer - I think we should now only accept it if they
+ get it right (tridge) */
+ if (!CAN_PRINT(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBsplretq);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 2, 3);
+ SSVAL(req->outbuf,smb_vwv0,0);
+ SSVAL(req->outbuf,smb_vwv1,0);
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,0);
+
+ DEBUG(3,("printqueue start_index=%d max_count=%d\n",
+ start_index, max_count));
+
+ {
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ NTSTATUS status;
+ WERROR werr;
+ const char *sharename = lp_servicename(mem_ctx, lp_sub, SNUM(conn));
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle handle;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ union spoolss_JobInfo *info;
+ uint32_t count;
+ uint32_t num_to_get;
+ uint32_t first;
+ uint32_t i;
+
+ ZERO_STRUCT(handle);
+
+ status = rpc_pipe_open_interface(mem_ctx,
+ &ndr_table_spoolss,
+ conn->session_info,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ conn->sconn->msg_ctx,
+ &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("reply_printqueue: "
+ "could not connect to spoolss: %s\n",
+ nt_errstr(status)));
+ reply_nterror(req, status);
+ goto out;
+ }
+ b = cli->binding_handle;
+
+ ZERO_STRUCT(devmode_ctr);
+
+ status = dcerpc_spoolss_OpenPrinter(b, mem_ctx,
+ sharename,
+ NULL, devmode_ctr,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &handle,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ reply_nterror(req, werror_to_ntstatus(werr));
+ goto out;
+ }
+
+ werr = rpccli_spoolss_enumjobs(cli, mem_ctx,
+ &handle,
+ 0, /* firstjob */
+ 0xff, /* numjobs */
+ 2, /* level */
+ 0, /* offered */
+ &count,
+ &info);
+ if (!W_ERROR_IS_OK(werr)) {
+ reply_nterror(req, werror_to_ntstatus(werr));
+ goto out;
+ }
+
+ if (max_count > 0) {
+ first = start_index;
+ } else {
+ first = start_index + max_count + 1;
+ }
+
+ if (first >= count) {
+ num_to_get = first;
+ } else {
+ num_to_get = first + MIN(ABS(max_count), count - first);
+ }
+
+ for (i = first; i < num_to_get; i++) {
+ char blob[28];
+ char *p = blob;
+ struct timespec qtime = {
+ .tv_sec = spoolss_Time_to_time_t(
+ &info[i].info2.submitted),
+ };
+ int qstatus;
+ size_t len = 0;
+ uint16_t qrapjobid = pjobid_to_rap(sharename,
+ info[i].info2.job_id);
+
+ if (info[i].info2.status == JOB_STATUS_PRINTING) {
+ qstatus = 2;
+ } else {
+ qstatus = 3;
+ }
+
+ srv_put_dos_date2_ts(p, 0, qtime);
+ SCVAL(p, 4, qstatus);
+ SSVAL(p, 5, qrapjobid);
+ SIVAL(p, 7, info[i].info2.size);
+ SCVAL(p, 11, 0);
+ status = srvstr_push(blob, req->flags2, p+12,
+ info[i].info2.notify_name, 16, STR_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ if (message_push_blob(
+ &req->outbuf,
+ data_blob_const(
+ blob, sizeof(blob))) == -1) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ if (count > 0) {
+ SSVAL(req->outbuf,smb_vwv0,count);
+ SSVAL(req->outbuf,smb_vwv1,
+ (max_count>0?first+count:first-1));
+ SCVAL(smb_buf(req->outbuf),0,1);
+ SSVAL(smb_buf(req->outbuf),1,28*count);
+ }
+
+
+ DEBUG(3, ("%u entries returned in queue\n",
+ (unsigned)count));
+
+out:
+ if (b && is_valid_policy_hnd(&handle)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &handle, &werr);
+ }
+
+ }
+
+ END_PROFILE(SMBsplretq);
+ return;
+}
+
+/****************************************************************************
+ Reply to a printwrite.
+****************************************************************************/
+
+void reply_printwrite(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int numtowrite;
+ const char *data;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsplwr);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ if (!fsp->print_file) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ numtowrite = SVAL(req->buf, 1);
+
+ /*
+ * This already protects us against CVE-2017-12163.
+ */
+ if (req->buflen < numtowrite + 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ data = (const char *)req->buf + 3;
+
+ if (write_file(req,fsp,data,(off_t)-1,numtowrite) != numtowrite) {
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ END_PROFILE(SMBsplwr);
+ return;
+ }
+
+ DEBUG(3, ("printwrite %s num=%d\n", fsp_fnum_dbg(fsp), numtowrite));
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ END_PROFILE(SMBsplwr);
+ return;
+}
+
+/****************************************************************************
+ Reply to a mkdir.
+****************************************************************************/
+
+void reply_mkdir(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ char *directory = NULL;
+ NTSTATUS status;
+ uint32_t ucf_flags;
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ START_PROFILE(SMBmkdir);
+
+ srvstr_get_path_req(ctx, req, &directory, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ ucf_flags = filename_create_ucf_flags(req, FILE_CREATE);
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(directory, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ directory,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = create_directory(conn, req, dirfsp, smb_dname);
+
+ DEBUG(5, ("create_directory returned %s\n", nt_errstr(status)));
+
+ if (!NT_STATUS_IS_OK(status)) {
+
+ if (!use_nt_status()
+ && NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_COLLISION)) {
+ /*
+ * Yes, in the DOS error code case we get a
+ * ERRDOS:ERRnoaccess here. See BASE-SAMBA3ERROR
+ * samba4 torture test.
+ */
+ status = NT_STATUS_DOS(ERRDOS, ERRnoaccess);
+ }
+
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3, ("mkdir %s\n", smb_dname->base_name));
+ out:
+ TALLOC_FREE(smb_dname);
+ END_PROFILE(SMBmkdir);
+ return;
+}
+
+/****************************************************************************
+ Reply to a rmdir.
+****************************************************************************/
+
+void reply_rmdir(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_filename *smb_dname = NULL;
+ char *directory = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ int info = 0;
+ NTTIME twrp = 0;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+
+ START_PROFILE(SMBrmdir);
+
+ srvstr_get_path_req(ctx, req, &directory, (const char *)req->buf + 1,
+ STR_TERMINATE, &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(directory, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ directory,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ FILE_DIRECTORY_FILE |
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = can_set_delete_on_close(fsp, FILE_ATTRIBUTE_DIRECTORY);
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!set_delete_on_close(fsp, true,
+ conn->session_info->security_token,
+ conn->session_info->unix_token)) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ status = close_file_free(req, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ } else {
+ reply_smb1_outbuf(req, 0, 0);
+ }
+
+ DEBUG(3, ("rmdir %s\n", smb_fname_str_dbg(smb_dname)));
+ out:
+ TALLOC_FREE(smb_dname);
+ END_PROFILE(SMBrmdir);
+ return;
+}
+
+/****************************************************************************
+ Reply to a mv.
+****************************************************************************/
+
+void reply_mv(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ char *name = NULL;
+ char *newname = NULL;
+ const char *p;
+ uint32_t attrs;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct files_struct *src_dirfsp = NULL;
+ struct smb_filename *smb_fname_src = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ const char *dst_original_lcomp = NULL;
+ uint32_t src_ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME src_twrp = 0;
+ uint32_t dst_ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+ bool stream_rename = false;
+
+ START_PROFILE(SMBmv);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ attrs = SVAL(req->vwv+0, 0);
+
+ p = (const char *)req->buf + 1;
+ p += srvstr_get_path_req(ctx, req, &name, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ p++;
+ p += srvstr_get_path_req(ctx, req, &newname, p, STR_TERMINATE,
+ &status);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (!req->posix_pathnames) {
+ /* The newname must begin with a ':' if the
+ name contains a ':'. */
+ if (strchr_m(name, ':')) {
+ if (newname[0] != ':') {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ stream_rename = true;
+ }
+ }
+
+ if (src_ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name, &src_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &src_ucf_flags, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name,
+ src_ucf_flags,
+ src_twrp,
+ &src_dirfsp,
+ &smb_fname_src);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (dst_ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(newname, &dst_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &dst_ucf_flags, &newname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ dst_ucf_flags,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_dst);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Get the last component of the destination for rename_internals(). */
+ dst_original_lcomp = get_original_lcomp(ctx,
+ conn,
+ newname,
+ dst_ucf_flags);
+ if (dst_original_lcomp == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ if (stream_rename) {
+ /* smb_fname_dst->base_name must be the same as
+ smb_fname_src->base_name. */
+ TALLOC_FREE(smb_fname_dst->base_name);
+ smb_fname_dst->base_name = talloc_strdup(smb_fname_dst,
+ smb_fname_src->base_name);
+ if (!smb_fname_dst->base_name) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ }
+
+ DEBUG(3,("reply_mv : %s -> %s\n", smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ status = rename_internals(ctx,
+ conn,
+ req,
+ src_dirfsp, /* src_dirfsp */
+ smb_fname_src,
+ smb_fname_dst,
+ dst_original_lcomp,
+ attrs,
+ false,
+ DELETE_ACCESS);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+ out:
+ TALLOC_FREE(smb_fname_src);
+ TALLOC_FREE(smb_fname_dst);
+ END_PROFILE(SMBmv);
+ return;
+}
+
+/****************************************************************************
+ Reply to a file copy.
+
+ From MS-CIFS.
+
+ This command was introduced in the LAN Manager 1.0 dialect
+ It was rendered obsolete in the NT LAN Manager dialect.
+ This command was used to perform server-side file copies, but
+ is no longer used. Clients SHOULD
+ NOT send requests using this command code.
+ Servers receiving requests with this command code
+ SHOULD return STATUS_NOT_IMPLEMENTED (ERRDOS/ERRbadfunc).
+****************************************************************************/
+
+void reply_copy(struct smb_request *req)
+{
+ START_PROFILE(SMBcopy);
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ END_PROFILE(SMBcopy);
+ return;
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_LOCKING
+
+/****************************************************************************
+ Get a lock pid, dealing with large count requests.
+****************************************************************************/
+
+uint64_t get_lock_pid(const uint8_t *data, int data_offset,
+ bool large_file_format)
+{
+ if(!large_file_format)
+ return (uint64_t)SVAL(data,SMB_LPID_OFFSET(data_offset));
+ else
+ return (uint64_t)SVAL(data,SMB_LARGE_LPID_OFFSET(data_offset));
+}
+
+/****************************************************************************
+ Get a lock count, dealing with large count requests.
+****************************************************************************/
+
+uint64_t get_lock_count(const uint8_t *data, int data_offset,
+ bool large_file_format)
+{
+ uint64_t count = 0;
+
+ if(!large_file_format) {
+ count = (uint64_t)IVAL(data,SMB_LKLEN_OFFSET(data_offset));
+ } else {
+ /*
+ * No BVAL, this is reversed!
+ */
+ count = (((uint64_t) IVAL(data,SMB_LARGE_LKLEN_OFFSET_HIGH(data_offset))) << 32) |
+ ((uint64_t) IVAL(data,SMB_LARGE_LKLEN_OFFSET_LOW(data_offset)));
+ }
+
+ return count;
+}
+
+/****************************************************************************
+ Reply to a lockingX request.
+****************************************************************************/
+
+static void reply_lockingx_done(struct tevent_req *subreq);
+
+void reply_lockingX(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ files_struct *fsp;
+ unsigned char locktype;
+ enum brl_type brltype;
+ unsigned char oplocklevel;
+ uint16_t num_ulocks;
+ uint16_t num_locks;
+ int32_t lock_timeout;
+ uint16_t i;
+ const uint8_t *data;
+ bool large_file_format;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ struct smbd_lock_element *locks = NULL;
+ struct tevent_req *subreq = NULL;
+
+ START_PROFILE(SMBlockingX);
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+2, 0));
+ locktype = CVAL(req->vwv+3, 0);
+ oplocklevel = CVAL(req->vwv+3, 1);
+ num_ulocks = SVAL(req->vwv+6, 0);
+ num_locks = SVAL(req->vwv+7, 0);
+ lock_timeout = IVAL(req->vwv+4, 0);
+ large_file_format = ((locktype & LOCKING_ANDX_LARGE_FILES) != 0);
+
+ if (!check_fsp(conn, req, fsp)) {
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ data = req->buf;
+
+ if (locktype & LOCKING_ANDX_CHANGE_LOCKTYPE) {
+ /* we don't support these - and CANCEL_LOCK makes w2k
+ and XP reboot so I don't really want to be
+ compatible! (tridge) */
+ reply_force_doserror(req, ERRDOS, ERRnoatomiclocks);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ /* Check if this is an oplock break on a file
+ we have granted an oplock on.
+ */
+ if (locktype & LOCKING_ANDX_OPLOCK_RELEASE) {
+ /* Client can insist on breaking to none. */
+ bool break_to_none = (oplocklevel == 0);
+ bool result;
+
+ DEBUG(5,("reply_lockingX: oplock break reply (%u) from client "
+ "for %s\n", (unsigned int)oplocklevel,
+ fsp_fnum_dbg(fsp)));
+
+ /*
+ * Make sure we have granted an exclusive or batch oplock on
+ * this file.
+ */
+
+ if (fsp->oplock_type == 0) {
+
+ /* The Samba4 nbench simulator doesn't understand
+ the difference between break to level2 and break
+ to none from level2 - it sends oplock break
+ replies in both cases. Don't keep logging an error
+ message here - just ignore it. JRA. */
+
+ DEBUG(5,("reply_lockingX: Error : oplock break from "
+ "client for %s (oplock=%d) and no "
+ "oplock granted on this file (%s).\n",
+ fsp_fnum_dbg(fsp), fsp->oplock_type,
+ fsp_str_dbg(fsp)));
+
+ /* if this is a pure oplock break request then don't
+ * send a reply */
+ if (num_locks == 0 && num_ulocks == 0) {
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ END_PROFILE(SMBlockingX);
+ reply_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return;
+ }
+
+ if ((fsp->sent_oplock_break == BREAK_TO_NONE_SENT) ||
+ (break_to_none)) {
+ result = remove_oplock(fsp);
+ } else {
+ result = downgrade_oplock(fsp);
+ }
+
+ if (!result) {
+ DEBUG(0, ("reply_lockingX: error in removing "
+ "oplock on file %s\n", fsp_str_dbg(fsp)));
+ /* Hmmm. Is this panic justified? */
+ smb_panic("internal tdb error");
+ }
+
+ /* if this is a pure oplock break request then don't send a
+ * reply */
+ if (num_locks == 0 && num_ulocks == 0) {
+ /* Sanity check - ensure a pure oplock break is not a
+ chained request. */
+ if (CVAL(req->vwv+0, 0) != 0xff) {
+ DEBUG(0,("reply_lockingX: Error : pure oplock "
+ "break is a chained %d request !\n",
+ (unsigned int)CVAL(req->vwv+0, 0)));
+ }
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+ }
+
+ if (req->buflen <
+ (num_ulocks + num_locks) * (large_file_format ? 20 : 10)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ if (num_ulocks != 0) {
+ struct smbd_lock_element *ulocks = NULL;
+ bool ok;
+
+ ulocks = talloc_array(
+ req, struct smbd_lock_element, num_ulocks);
+ if (ulocks == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ /*
+ * Data now points at the beginning of the list of
+ * smb_unlkrng structs
+ */
+ for (i = 0; i < num_ulocks; i++) {
+ ulocks[i].req_guid = smbd_request_guid(req,
+ UINT16_MAX - i),
+ ulocks[i].smblctx = get_lock_pid(
+ data, i, large_file_format);
+ ulocks[i].count = get_lock_count(
+ data, i, large_file_format);
+ ulocks[i].offset = get_lock_offset(
+ data, i, large_file_format);
+ ulocks[i].brltype = UNLOCK_LOCK;
+ ulocks[i].lock_flav = WINDOWS_LOCK;
+ }
+
+ /*
+ * Unlock cancels pending locks
+ */
+
+ ok = smbd_smb1_brl_finish_by_lock(
+ fsp,
+ large_file_format,
+ ulocks[0],
+ NT_STATUS_OK);
+ if (ok) {
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff);
+ SSVAL(req->outbuf, smb_vwv1, 0);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ status = smbd_do_unlocking(
+ req, fsp, num_ulocks, ulocks);
+ TALLOC_FREE(ulocks);
+ if (!NT_STATUS_IS_OK(status)) {
+ END_PROFILE(SMBlockingX);
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ /* Now do any requested locks */
+ data += ((large_file_format ? 20 : 10)*num_ulocks);
+
+ /* Data now points at the beginning of the list
+ of smb_lkrng structs */
+
+ if (locktype & LOCKING_ANDX_SHARED_LOCK) {
+ brltype = READ_LOCK;
+ } else {
+ brltype = WRITE_LOCK;
+ }
+
+ locks = talloc_array(req, struct smbd_lock_element, num_locks);
+ if (locks == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ for (i = 0; i < num_locks; i++) {
+ locks[i].req_guid = smbd_request_guid(req, i),
+ locks[i].smblctx = get_lock_pid(data, i, large_file_format);
+ locks[i].count = get_lock_count(data, i, large_file_format);
+ locks[i].offset = get_lock_offset(data, i, large_file_format);
+ locks[i].brltype = brltype;
+ locks[i].lock_flav = WINDOWS_LOCK;
+ }
+
+ if (locktype & LOCKING_ANDX_CANCEL_LOCK) {
+
+ bool ok;
+
+ if (num_locks == 0) {
+ /* See smbtorture3 lock11 test */
+ reply_smb1_outbuf(req, 2, 0);
+ /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv0, 0xff);
+ SSVAL(req->outbuf, smb_vwv1, 0);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ ok = smbd_smb1_brl_finish_by_lock(
+ fsp,
+ large_file_format,
+ locks[0], /* Windows only cancels the first lock */
+ NT_STATUS_FILE_LOCK_CONFLICT);
+
+ if (!ok) {
+ reply_force_doserror(req, ERRDOS, ERRcancelviolation);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff);
+ SSVAL(req->outbuf, smb_vwv1, 0);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ lock_timeout,
+ large_file_format,
+ num_locks,
+ locks);
+ if (subreq == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBlockingX);
+ return;
+ }
+ tevent_req_set_callback(subreq, reply_lockingx_done, NULL);
+ END_PROFILE(SMBlockingX);
+}
+
+static void reply_lockingx_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ START_PROFILE(SMBlockingX);
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ DBG_DEBUG("smbd_smb1_do_locks_recv returned %s\n", nt_errstr(status));
+
+ if (NT_STATUS_IS_OK(status)) {
+ reply_smb1_outbuf(req, 2, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+ } else {
+ reply_nterror(req, status);
+ }
+
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("reply_lock_done: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ END_PROFILE(SMBlockingX);
+}
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_ALL
+
+/****************************************************************************
+ Reply to a SMBreadbmpx (read block multiplex) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_readbmpx(struct smb_request *req)
+{
+ START_PROFILE(SMBreadBmpx);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBreadBmpx);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBreadbs (read block multiplex secondary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_readbs(struct smb_request *req)
+{
+ START_PROFILE(SMBreadBs);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBreadBs);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBsetattrE.
+****************************************************************************/
+
+void reply_setattrE(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ struct smb_file_time ft;
+ files_struct *fsp;
+ NTSTATUS status;
+
+ START_PROFILE(SMBsetattrE);
+ init_smb_file_time(&ft);
+
+ if (req->wct < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if(!fsp || (fsp->conn != conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ goto out;
+ }
+
+ /*
+ * Convert the DOS times into unix times.
+ */
+
+ ft.atime = time_t_to_full_timespec(
+ srv_make_unix_date2(req->vwv+3));
+ ft.mtime = time_t_to_full_timespec(
+ srv_make_unix_date2(req->vwv+5));
+ ft.create_time = time_t_to_full_timespec(
+ srv_make_unix_date2(req->vwv+1));
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ /*
+ * Patch from Ray Frush <frush@engr.colostate.edu>
+ * Sometimes times are sent as zero - ignore them.
+ */
+
+ /* Ensure we have a valid stat struct for the source. */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ status = smb_set_file_time(conn, fsp, fsp->fsp_name, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+
+ DEBUG( 3, ( "reply_setattrE %s actime=%u modtime=%u "
+ " createtime=%u\n",
+ fsp_fnum_dbg(fsp),
+ (unsigned int)ft.atime.tv_sec,
+ (unsigned int)ft.mtime.tv_sec,
+ (unsigned int)ft.create_time.tv_sec
+ ));
+ out:
+ END_PROFILE(SMBsetattrE);
+ return;
+}
+
+
+/* Back from the dead for OS/2..... JRA. */
+
+/****************************************************************************
+ Reply to a SMBwritebmpx (write block multiplex primary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_writebmpx(struct smb_request *req)
+{
+ START_PROFILE(SMBwriteBmpx);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBwriteBmpx);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBwritebs (write block multiplex secondary) request.
+ Always reply with an error, if someone has a platform really needs this,
+ please contact vl@samba.org
+****************************************************************************/
+
+void reply_writebs(struct smb_request *req)
+{
+ START_PROFILE(SMBwriteBs);
+ reply_force_doserror(req, ERRSRV, ERRuseSTD);
+ END_PROFILE(SMBwriteBs);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBgetattrE.
+****************************************************************************/
+
+void reply_getattrE(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ int mode;
+ files_struct *fsp;
+ struct timespec create_ts;
+ NTSTATUS status;
+
+ START_PROFILE(SMBgetattrE);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(req->vwv+0, 0));
+
+ if(!fsp || (fsp->conn != conn)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ /* Do an fstat on this file */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ END_PROFILE(SMBgetattrE);
+ return;
+ }
+
+ mode = fdos_mode(fsp);
+
+ /*
+ * Convert the times into dos times. Set create
+ * date to be last modify date as UNIX doesn't save
+ * this.
+ */
+
+ reply_smb1_outbuf(req, 11, 0);
+
+ create_ts = get_create_timespec(conn, fsp, fsp->fsp_name);
+ srv_put_dos_date2_ts((char *)req->outbuf, smb_vwv0, create_ts);
+ srv_put_dos_date2_ts((char *)req->outbuf,
+ smb_vwv2,
+ fsp->fsp_name->st.st_ex_atime);
+ /* Should we check pending modtime here ? JRA */
+ srv_put_dos_date2_ts((char *)req->outbuf,
+ smb_vwv4,
+ fsp->fsp_name->st.st_ex_mtime);
+
+ if (mode & FILE_ATTRIBUTE_DIRECTORY) {
+ SIVAL(req->outbuf, smb_vwv6, 0);
+ SIVAL(req->outbuf, smb_vwv8, 0);
+ } else {
+ uint32_t allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn,fsp, &fsp->fsp_name->st);
+ SIVAL(req->outbuf, smb_vwv6, (uint32_t)fsp->fsp_name->st.st_ex_size);
+ SIVAL(req->outbuf, smb_vwv8, allocation_size);
+ }
+ SSVAL(req->outbuf,smb_vwv10, mode);
+
+ DEBUG( 3, ( "reply_getattrE %s\n", fsp_fnum_dbg(fsp)));
+
+ END_PROFILE(SMBgetattrE);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBfindclose (stop trans2 directory search).
+****************************************************************************/
+
+void reply_findclose(struct smb_request *req)
+{
+ int dptr_num;
+ struct smbd_server_connection *sconn = req->sconn;
+ files_struct *fsp = NULL;
+
+ START_PROFILE(SMBfindclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfindclose);
+ return;
+ }
+
+ dptr_num = SVALS(req->vwv+0, 0);
+
+ DEBUG(3,("reply_findclose, dptr_num = %d\n", dptr_num));
+
+ /*
+ * OS/2 seems to use -1 to indicate "close all directories"
+ * This has to mean on this specific connection struct.
+ */
+ if (dptr_num == -1) {
+ dptr_closecnum(req->conn);
+ } else {
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ dptr_num = -1;
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ }
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3,("SMBfindclose dptr_num = %d\n", dptr_num));
+
+ END_PROFILE(SMBfindclose);
+ return;
+}
+
+/****************************************************************************
+ Reply to a SMBfindnclose (stop FINDNOTIFYFIRST directory search).
+****************************************************************************/
+
+void reply_findnclose(struct smb_request *req)
+{
+ int dptr_num;
+
+ START_PROFILE(SMBfindnclose);
+
+ if (req->wct < 1) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBfindnclose);
+ return;
+ }
+
+ dptr_num = SVAL(req->vwv+0, 0);
+
+ DEBUG(3,("reply_findnclose, dptr_num = %d\n", dptr_num));
+
+ /* We never give out valid handles for a
+ findnotifyfirst - so any dptr_num is ok here.
+ Just ignore it. */
+
+ reply_smb1_outbuf(req, 0, 0);
+
+ DEBUG(3,("SMB_findnclose dptr_num = %d\n", dptr_num));
+
+ END_PROFILE(SMBfindnclose);
+ return;
+}
diff --git a/source3/smbd/smb1_reply.h b/source3/smbd/smb1_reply.h
new file mode 100644
index 0000000..5a12f0b
--- /dev/null
+++ b/source3/smbd/smb1_reply.h
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+void reply_tcon(struct smb_request *req);
+void reply_tcon_and_X(struct smb_request *req);
+void reply_unknown_new(struct smb_request *req, uint8_t type);
+void reply_ioctl(struct smb_request *req);
+void reply_checkpath(struct smb_request *req);
+void reply_getatr(struct smb_request *req);
+void reply_setatr(struct smb_request *req);
+void reply_dskattr(struct smb_request *req);
+void reply_search(struct smb_request *req);
+void reply_fclose(struct smb_request *req);
+void reply_open(struct smb_request *req);
+void reply_open_and_X(struct smb_request *req);
+void reply_ulogoffX(struct smb_request *req);
+void reply_mknew(struct smb_request *req);
+void reply_ctemp(struct smb_request *req);
+void reply_unlink(struct smb_request *req);
+void reply_readbraw(struct smb_request *req);
+void reply_lockread(struct smb_request *req);
+size_t setup_readX_header(char *outbuf, size_t smb_maxcnt);
+void reply_read(struct smb_request *req);
+void reply_read_and_X(struct smb_request *req);
+void error_to_writebrawerr(struct smb_request *req);
+void reply_writebraw(struct smb_request *req);
+void reply_writeunlock(struct smb_request *req);
+void reply_write(struct smb_request *req);
+bool is_valid_writeX_buffer(struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf);
+void reply_write_and_X(struct smb_request *req);
+void reply_lseek(struct smb_request *req);
+void reply_flush(struct smb_request *req);
+void reply_exit(struct smb_request *req);
+void reply_close(struct smb_request *req);
+void reply_writeclose(struct smb_request *req);
+void reply_lock(struct smb_request *req);
+void reply_unlock(struct smb_request *req);
+void reply_tdis(struct smb_request *req);
+void reply_echo(struct smb_request *req);
+void reply_printopen(struct smb_request *req);
+void reply_printclose(struct smb_request *req);
+void reply_printqueue(struct smb_request *req);
+void reply_printwrite(struct smb_request *req);
+void reply_mkdir(struct smb_request *req);
+void reply_rmdir(struct smb_request *req);
+void reply_mv(struct smb_request *req);
+void reply_copy(struct smb_request *req);
+uint64_t get_lock_pid(const uint8_t *data, int data_offset,
+ bool large_file_format);
+uint64_t get_lock_count(const uint8_t *data, int data_offset,
+ bool large_file_format);
+void reply_lockingX(struct smb_request *req);
+void reply_readbmpx(struct smb_request *req);
+void reply_readbs(struct smb_request *req);
+void reply_setattrE(struct smb_request *req);
+void reply_writebmpx(struct smb_request *req);
+void reply_writebs(struct smb_request *req);
+void reply_getattrE(struct smb_request *req);
+void reply_findclose(struct smb_request *req);
+void reply_findnclose(struct smb_request *req);
diff --git a/source3/smbd/smb1_service.c b/source3/smbd/smb1_service.c
new file mode 100644
index 0000000..9954101
--- /dev/null
+++ b/source3/smbd/smb1_service.c
@@ -0,0 +1,241 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "../lib/tsocket/tsocket.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "printing/pcap.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "lib/param/loadparm.h"
+#include "messages.h"
+#include "lib/afs/afs_funcs.h"
+#include "lib/util_path.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+/****************************************************************************
+ Make a connection to a service from SMB1. Internal interface.
+****************************************************************************/
+
+static connection_struct *make_connection_smb1(struct smb_request *req,
+ NTTIME now,
+ int snum,
+ const char *pdev,
+ NTSTATUS *pstatus)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uint32_t session_global_id;
+ char *share_name = NULL;
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+ struct connection_struct *conn;
+
+ session_global_id = req->session->global->session_global_id;
+ share_name = lp_servicename(talloc_tos(), lp_sub, snum);
+ if (share_name == NULL) {
+ *pstatus = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ if ((lp_max_connections(snum) > 0)
+ && (count_current_connections(lp_const_servicename(snum), true) >=
+ lp_max_connections(snum))) {
+
+ DBG_WARNING("Max connections (%d) exceeded for [%s][%s]\n",
+ lp_max_connections(snum),
+ lp_const_servicename(snum), share_name);
+ TALLOC_FREE(share_name);
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ status = smb1srv_tcon_create(req->xconn,
+ session_global_id,
+ share_name,
+ now, &tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("make_connection_smb1: Couldn't find free tcon for [%s] - %s\n",
+ share_name, nt_errstr(status)));
+ TALLOC_FREE(share_name);
+ *pstatus = status;
+ return NULL;
+ }
+ TALLOC_FREE(share_name);
+
+ conn = conn_new(req->sconn);
+ if (!conn) {
+ TALLOC_FREE(tcon);
+
+ DEBUG(0,("make_connection_smb1: Couldn't find free connection.\n"));
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ conn->cnum = tcon->global->tcon_wire_id;
+ conn->tcon = tcon;
+
+ *pstatus = make_connection_snum(req->xconn,
+ conn,
+ snum,
+ req->session,
+ pdev);
+ if (!NT_STATUS_IS_OK(*pstatus)) {
+ conn_free(conn);
+ TALLOC_FREE(tcon);
+ return NULL;
+ }
+
+ tcon->compat = talloc_move(tcon, &conn);
+ tcon->status = NT_STATUS_OK;
+
+ *pstatus = NT_STATUS_OK;
+
+ return tcon->compat;
+}
+
+/****************************************************************************
+ Make a connection to a service. External SMB1 interface.
+ *
+ * @param service
+****************************************************************************/
+
+connection_struct *make_connection(struct smb_request *req,
+ NTTIME now,
+ const char *service_in,
+ const char *pdev, uint64_t vuid,
+ NTSTATUS *status)
+{
+ struct smbd_server_connection *sconn = req->sconn;
+ struct smbXsrv_session *session = req->session;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ uid_t euid;
+ char *service = NULL;
+ fstring dev;
+ int snum = -1;
+
+ fstrcpy(dev, pdev);
+
+ /* This must ONLY BE CALLED AS ROOT. As it exits this function as
+ * root. */
+ if (!non_root_mode() && (euid = geteuid()) != 0) {
+ DEBUG(0,("make_connection: PANIC ERROR. Called as nonroot "
+ "(%u)\n", (unsigned int)euid ));
+ smb_panic("make_connection: PANIC ERROR. Called as nonroot\n");
+ }
+
+ if (conn_num_open(sconn) > 2047) {
+ *status = NT_STATUS_INSUFF_SERVER_RESOURCES;
+ return NULL;
+ }
+
+ if (session == NULL) {
+ DEBUG(1,("make_connection: refusing to connect with "
+ "no session setup\n"));
+ *status = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ }
+
+ /* Logic to try and connect to the correct [homes] share, preferably
+ without too many getpwnam() lookups. This is particularly nasty for
+ winbind usernames, where the share name isn't the same as unix
+ username.
+ */
+
+ if (strequal(service_in,HOMES_NAME)) {
+ if (session->homes_snum == -1) {
+ DEBUG(2, ("[homes] share not available for "
+ "this user because it was not found "
+ "or created at session setup "
+ "time\n"));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+ DEBUG(5, ("making a connection to [homes] service "
+ "created at session setup time\n"));
+ return make_connection_smb1(req, now,
+ session->homes_snum,
+ dev, status);
+ } else if ((session->homes_snum != -1)
+ && strequal(service_in,
+ lp_const_servicename(session->homes_snum))) {
+ DEBUG(5, ("making a connection to 'homes' service [%s] "
+ "created at session setup time\n", service_in));
+ return make_connection_smb1(req, now,
+ session->homes_snum,
+ dev, status);
+ }
+
+ service = talloc_strdup(talloc_tos(), service_in);
+ if (!service) {
+ *status = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ if (!strlower_m(service)) {
+ DEBUG(2, ("strlower_m %s failed\n", service));
+ *status = NT_STATUS_INVALID_PARAMETER;
+ return NULL;
+ }
+
+ snum = find_service(talloc_tos(), service, &service);
+ if (!service) {
+ *status = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ if (snum < 0) {
+ if (strequal(service,"IPC$") ||
+ (lp_enable_asu_support() && strequal(service,"ADMIN$"))) {
+ DEBUG(3,("refusing IPC connection to %s\n", service));
+ *status = NT_STATUS_ACCESS_DENIED;
+ return NULL;
+ }
+
+ DEBUG(3,("%s (%s) couldn't find service %s\n",
+ get_remote_machine_name(),
+ tsocket_address_string(
+ sconn->remote_address, talloc_tos()),
+ service));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+
+ /* Handle non-Dfs clients attempting connections to msdfs proxy */
+ if (lp_host_msdfs() && (*lp_msdfs_proxy(talloc_tos(), lp_sub, snum) != '\0')) {
+ DEBUG(3, ("refusing connection to dfs proxy share '%s' "
+ "(pointing to %s)\n",
+ service, lp_msdfs_proxy(talloc_tos(), lp_sub, snum)));
+ *status = NT_STATUS_BAD_NETWORK_NAME;
+ return NULL;
+ }
+
+ DEBUG(5, ("making a connection to 'normal' service %s\n", service));
+
+ return make_connection_smb1(req, now, snum,
+ dev, status);
+}
diff --git a/source3/smbd/smb1_service.h b/source3/smbd/smb1_service.h
new file mode 100644
index 0000000..cf6b967
--- /dev/null
+++ b/source3/smbd/smb1_service.h
@@ -0,0 +1,24 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+connection_struct *make_connection(struct smb_request *req,
+ NTTIME now,
+ const char *service_in,
+ const char *pdev, uint64_t vuid,
+ NTSTATUS *status);
diff --git a/source3/smbd/smb1_sesssetup.c b/source3/smbd/smb1_sesssetup.c
new file mode 100644
index 0000000..6c668ff
--- /dev/null
+++ b/source3/smbd/smb1_sesssetup.c
@@ -0,0 +1,1118 @@
+/*
+ Unix SMB/CIFS implementation.
+ handle SMBsessionsetup
+ Copyright (C) Andrew Tridgell 1998-2001
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Luke Howard 2003
+ Copyright (C) Volker Lendecke 2007
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "auth.h"
+#include "messages.h"
+#include "smbprofile.h"
+#include "../libcli/security/security.h"
+#include "auth/gensec/gensec.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+/****************************************************************************
+ Add the standard 'Samba' signature to the end of the session setup.
+****************************************************************************/
+
+static int push_signature(uint8_t **outbuf)
+{
+ char *lanman;
+ int result, tmp;
+ fstring native_os;
+
+ result = 0;
+
+ fstr_sprintf(native_os, "Windows %d.%d", SAMBA_MAJOR_NBT_ANNOUNCE_VERSION,
+ SAMBA_MINOR_NBT_ANNOUNCE_VERSION);
+
+ tmp = message_push_string(outbuf, native_os, STR_TERMINATE);
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ if (asprintf(&lanman, "Samba %s", samba_version_string()) != -1) {
+ tmp = message_push_string(outbuf, lanman, STR_TERMINATE);
+ SAFE_FREE(lanman);
+ }
+ else {
+ tmp = message_push_string(outbuf, "Samba", STR_TERMINATE);
+ }
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ tmp = message_push_string(outbuf, lp_workgroup(), STR_TERMINATE);
+
+ if (tmp == -1) return -1;
+ result += tmp;
+
+ return result;
+}
+
+/****************************************************************************
+ Reply to a session setup command.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+static void reply_sesssetup_and_X_spnego(struct smb_request *req)
+{
+ const uint8_t *p;
+ DATA_BLOB in_blob;
+ DATA_BLOB out_blob = data_blob_null;
+ size_t bufrem;
+ char *tmp = NULL;
+ const char *native_os;
+ const char *native_lanman;
+ const char *primary_domain;
+ uint16_t data_blob_len = SVAL(req->vwv+7, 0);
+ enum remote_arch_types ra_type = get_remote_arch();
+ uint64_t vuid = req->vuid;
+ NTSTATUS status = NT_STATUS_OK;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ uint16_t action = 0;
+ bool is_authenticated = false;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ struct smbXsrv_session *session = NULL;
+ uint16_t smb_bufsize = SVAL(req->vwv+2, 0);
+ uint32_t client_caps = IVAL(req->vwv+10, 0);
+ struct smbXsrv_session_auth0 *auth;
+
+ DEBUG(3,("Doing spnego session setup\n"));
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ global_client_caps = client_caps;
+
+ if (!(global_client_caps & CAP_STATUS32)) {
+ remove_from_common_flags2(FLAGS2_32_BIT_ERROR_CODES);
+ }
+ }
+
+ p = req->buf;
+
+ if (data_blob_len == 0) {
+ /* an invalid request */
+ reply_nterror(req, nt_status_squash(NT_STATUS_LOGON_FAILURE));
+ return;
+ }
+
+ bufrem = smbreq_bufrem(req, p);
+ /* pull the spnego blob */
+ in_blob = data_blob_const(p, MIN(bufrem, data_blob_len));
+
+#if 0
+ file_save("negotiate.dat", in_blob.data, in_blob.length);
+#endif
+
+ p = req->buf + in_blob.length;
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_os = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_lanman = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ primary_domain = tmp ? tmp : "";
+
+ DEBUG(3,("NativeOS=[%s] NativeLanMan=[%s] PrimaryDomain=[%s]\n",
+ native_os, native_lanman, primary_domain));
+
+ if ( ra_type == RA_WIN2K ) {
+ /* Vista sets neither the OS or lanman strings */
+
+ if ( !strlen(native_os) && !strlen(native_lanman) )
+ set_remote_arch(RA_VISTA);
+
+ /* Windows 2003 doesn't set the native lanman string,
+ but does set primary domain which is a bug I think */
+
+ if ( !strlen(native_lanman) ) {
+ ra_lanman_string( primary_domain );
+ } else {
+ ra_lanman_string( native_lanman );
+ }
+ } else if ( ra_type == RA_VISTA ) {
+ if ( strncmp(native_os, "Mac OS X", 8) == 0 ) {
+ set_remote_arch(RA_OSX);
+ }
+ }
+
+ if (vuid != 0) {
+ status = smb1srv_session_lookup(xconn,
+ vuid, now,
+ &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ reply_force_doserror(req, ERRSRV, ERRbaduid);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ session->status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ TALLOC_FREE(session->pending_auth);
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ if (session == NULL) {
+ /* create a new session */
+ status = smbXsrv_session_create(xconn,
+ now, &session);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ status = smbXsrv_session_find_auth(session, xconn, now, &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbXsrv_session_create_auth(session, xconn, now,
+ 0, /* flags */
+ 0, /* security */
+ &auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ if (auth->gensec == NULL) {
+ status = auth_generic_prepare(session,
+ xconn->remote_address,
+ xconn->local_address,
+ "SMB",
+ &auth->gensec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ gensec_want_feature(auth->gensec, GENSEC_FEATURE_SESSION_KEY);
+ gensec_want_feature(auth->gensec, GENSEC_FEATURE_UNIX_TOKEN);
+ gensec_want_feature(auth->gensec, GENSEC_FEATURE_SMB_TRANSPORT);
+
+ status = gensec_start_mech_by_oid(auth->gensec,
+ GENSEC_OID_SPNEGO);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to start SPNEGO handler!\n"));
+ TALLOC_FREE(session);;
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+ }
+
+ become_root();
+ status = gensec_update(auth->gensec,
+ talloc_tos(),
+ in_blob, &out_blob);
+ unbecome_root();
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ if (NT_STATUS_IS_OK(status) && session->global->auth_session_info == NULL) {
+ struct auth_session_info *session_info = NULL;
+
+ status = gensec_session_info(auth->gensec,
+ session,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("Failed to generate session_info "
+ "(user and group token) for session setup: %s\n",
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ action |= SMB_SETUP_GUEST;
+ }
+
+ session->global->signing_algo = SMB2_SIGNING_MD5_SMB1;
+ session->global->encryption_cipher = 0;
+ session->global->channels[0].signing_algo =
+ session->global->signing_algo;
+ session->global->channels[0].encryption_cipher =
+ session->global->encryption_cipher;
+
+ if (session_info->session_key.length > 0) {
+ struct smbXsrv_session *x = session;
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ NULL, /* no derivation */
+ &x->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, status);
+ return;
+ }
+ x->global->signing_key_blob = x->global->signing_key->blob;
+
+ /*
+ * clear the session key
+ * the first tcon will add setup the application key
+ */
+ data_blob_clear_free(&session_info->session_key);
+ }
+
+ sconn->num_users++;
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ is_authenticated = true;
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ if (smb1_srv_is_signing_negotiated(xconn) &&
+ is_authenticated &&
+ smb2_signing_key_valid(session->global->signing_key))
+ {
+ /*
+ * Try and turn on server signing on the first non-guest
+ * sessionsetup.
+ */
+ smb1_srv_set_signing(xconn,
+ session->global->signing_key->blob,
+ data_blob_null);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ session->global->channels[0].auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ session->global->auth_time = now;
+ if (client_caps & CAP_DYNAMIC_REAUTH) {
+ session->global->expiration_time =
+ gensec_expire_time(auth->gensec);
+ } else {
+ session->global->expiration_time =
+ GENSEC_EXPIRE_TIME_INFINITY;
+ }
+
+ if (!session_claim(session)) {
+ DEBUG(1, ("smb1: Failed to claim session for vuid=%llu\n",
+ (unsigned long long)session->global->session_wire_id));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ return;
+ }
+
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb1: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ return;
+ }
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ if (smb_bufsize < SMB_BUFFER_SIZE_MIN) {
+ reply_force_doserror(req, ERRSRV, ERRerror);
+ return;
+ }
+ xconn->smb1.sessions.max_send = smb_bufsize;
+ xconn->smb1.sessions.done_sesssetup = true;
+ }
+
+ /* current_user_info is changed on new vuid */
+ reload_services(sconn, conn_snum_used, true);
+ } else if (NT_STATUS_IS_OK(status)) {
+ struct auth_session_info *session_info = NULL;
+
+ status = gensec_session_info(auth->gensec,
+ session,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1,("Failed to generate session_info "
+ "(user and group token) for session setup: %s\n",
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(status));
+ return;
+ }
+
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ action |= SMB_SETUP_GUEST;
+ }
+
+ /*
+ * Keep the application key
+ */
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key =
+ session->global->auth_session_info->session_key;
+ talloc_steal(session_info, session_info->session_key.data);
+ TALLOC_FREE(session->global->auth_session_info);
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ session->global->channels[0].auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ session->global->auth_time = now;
+ if (client_caps & CAP_DYNAMIC_REAUTH) {
+ session->global->expiration_time =
+ gensec_expire_time(auth->gensec);
+ } else {
+ session->global->expiration_time =
+ GENSEC_EXPIRE_TIME_INFINITY;
+ }
+
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb1: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ return;
+ }
+
+ conn_clear_vuid_caches(sconn, session->global->session_wire_id);
+
+ /* current_user_info is changed on new vuid */
+ reload_services(sconn, conn_snum_used, true);
+ }
+
+ vuid = session->global->session_wire_id;
+
+ reply_smb1_outbuf(req, 4, 0);
+
+ SSVAL(req->outbuf, smb_uid, vuid);
+ SIVAL(req->outbuf, smb_rcls, NT_STATUS_V(status));
+ SSVAL(req->outbuf, smb_vwv0, 0xFF); /* no chaining possible */
+ SSVAL(req->outbuf, smb_vwv2, action);
+ SSVAL(req->outbuf, smb_vwv3, out_blob.length);
+
+ if (message_push_blob(&req->outbuf, out_blob) == -1) {
+ data_blob_free(&out_blob);
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ data_blob_free(&out_blob);
+
+ if (push_signature(&req->outbuf) == -1) {
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+}
+
+/****************************************************************************
+ On new VC == 0, shutdown *all* old connections and users.
+ It seems that only NT4.x does this. At W2K and above (XP etc.).
+ a new session setup with VC==0 is ignored.
+****************************************************************************/
+
+struct shutdown_state {
+ const char *ip;
+ size_t ip_length;
+ struct messaging_context *msg_ctx;
+};
+
+static int shutdown_other_smbds(struct smbXsrv_session_global0 *session,
+ void *private_data)
+{
+ struct shutdown_state *state = (struct shutdown_state *)private_data;
+ struct server_id self_pid = messaging_server_id(state->msg_ctx);
+ struct server_id pid = session->channels[0].server_id;
+ const char *addr = session->channels[0].remote_address;
+ const char *port_colon;
+ size_t addr_len;
+ struct server_id_buf tmp;
+
+ DEBUG(10, ("shutdown_other_smbds: %s, %s\n",
+ server_id_str_buf(pid, &tmp), addr));
+
+ if (!process_exists(pid)) {
+ DEBUG(10, ("process does not exist\n"));
+ return 0;
+ }
+
+ if (server_id_equal(&pid, &self_pid)) {
+ DEBUG(10, ("It's me\n"));
+ return 0;
+ }
+
+ port_colon = strrchr(addr, ':');
+ if (port_colon == NULL) {
+ DBG_DEBUG("addr %s in contains no port\n", addr);
+ return 0;
+ }
+ addr_len = port_colon - addr;
+
+ if ((addr_len != state->ip_length) ||
+ (strncmp(addr, state->ip, state->ip_length) != 0)) {
+ DEBUG(10, ("%s (%zu) does not match %s (%zu)\n",
+ state->ip, state->ip_length, addr, addr_len));
+ return 0;
+ }
+
+ DEBUG(1, ("shutdown_other_smbds: shutting down pid %u "
+ "(IP %s)\n", (unsigned int)procid_to_pid(&pid),
+ state->ip));
+
+ messaging_send(state->msg_ctx, pid, MSG_SHUTDOWN,
+ &data_blob_null);
+ return 0;
+}
+
+static void setup_new_vc_session(struct smbd_server_connection *sconn)
+{
+ DEBUG(2,("setup_new_vc_session: New VC == 0, if NT4.x "
+ "compatible we would close all old resources.\n"));
+
+ if (lp_reset_on_zero_vc()) {
+ char *addr;
+ const char *port_colon;
+ struct shutdown_state state;
+
+ addr = tsocket_address_string(
+ sconn->remote_address, talloc_tos());
+ if (addr == NULL) {
+ return;
+ }
+ state.ip = addr;
+
+ port_colon = strrchr(addr, ':');
+ if (port_colon == NULL) {
+ return;
+ }
+ state.ip_length = port_colon - addr;
+ state.msg_ctx = sconn->msg_ctx;
+ smbXsrv_session_global_traverse(shutdown_other_smbds, &state);
+ TALLOC_FREE(addr);
+ }
+}
+
+/****************************************************************************
+ Reply to a session setup command.
+****************************************************************************/
+
+struct reply_sesssetup_and_X_state {
+ struct smb_request *req;
+ struct auth4_context *auth_context;
+ struct auth_usersupplied_info *user_info;
+ const char *user;
+ const char *domain;
+ DATA_BLOB lm_resp;
+ DATA_BLOB nt_resp;
+ DATA_BLOB plaintext_password;
+};
+
+static int reply_sesssetup_and_X_state_destructor(
+ struct reply_sesssetup_and_X_state *state)
+{
+ data_blob_clear_free(&state->nt_resp);
+ data_blob_clear_free(&state->lm_resp);
+ data_blob_clear_free(&state->plaintext_password);
+ return 0;
+}
+
+void reply_sesssetup_and_X(struct smb_request *req)
+{
+ struct reply_sesssetup_and_X_state *state = NULL;
+ uint64_t sess_vuid;
+ uint16_t smb_bufsize;
+ char *tmp = NULL;
+ fstring sub_user; /* Sanitised username for substitution */
+ const char *native_os;
+ const char *native_lanman;
+ const char *primary_domain;
+ struct auth_session_info *session_info = NULL;
+ uint16_t smb_flag2 = req->flags2;
+ uint16_t action = 0;
+ bool is_authenticated = false;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS nt_status;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ bool doencrypt = xconn->smb1.negprot.encrypted_passwords;
+ bool signing_allowed = false;
+ bool signing_mandatory = smb1_signing_is_mandatory(
+ xconn->smb1.signing_state);
+
+ START_PROFILE(SMBsesssetupX);
+
+ DEBUG(3,("wct=%d flg2=0x%x\n", req->wct, req->flags2));
+
+ state = talloc_zero(req, struct reply_sesssetup_and_X_state);
+ if (state == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ state->req = req;
+ talloc_set_destructor(state, reply_sesssetup_and_X_state_destructor);
+
+ if (req->flags2 & FLAGS2_SMB_SECURITY_SIGNATURES) {
+ signing_allowed = true;
+ }
+ if (req->flags2 & FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED) {
+ signing_mandatory = true;
+ }
+
+ /*
+ * We can call smb1_srv_set_signing_negotiated() each time.
+ * It finds out when it needs to turn into a noop
+ * itself.
+ */
+ smb1_srv_set_signing_negotiated(xconn,
+ signing_allowed,
+ signing_mandatory);
+
+ /* a SPNEGO session setup has 12 command words, whereas a normal
+ NT1 session setup has 13. See the cifs spec. */
+ if (req->wct == 12 &&
+ (req->flags2 & FLAGS2_EXTENDED_SECURITY)) {
+
+ if (!xconn->smb1.negprot.spnego) {
+ DEBUG(0,("reply_sesssetup_and_X: Rejecting attempt "
+ "at SPNEGO session setup when it was not "
+ "negotiated.\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (SVAL(req->vwv+4, 0) == 0) {
+ setup_new_vc_session(req->sconn);
+ }
+
+ reply_sesssetup_and_X_spnego(req);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ smb_bufsize = SVAL(req->vwv+2, 0);
+
+ if (xconn->protocol < PROTOCOL_NT1) {
+ uint16_t passlen1 = SVAL(req->vwv+7, 0);
+
+ /* Never do NT status codes with protocols before NT1 as we
+ * don't get client caps. */
+ remove_from_common_flags2(FLAGS2_32_BIT_ERROR_CODES);
+
+ if ((passlen1 > MAX_PASS_LEN) || (passlen1 > req->buflen)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (doencrypt) {
+ state->lm_resp = data_blob_talloc(state,
+ req->buf,
+ passlen1);
+ } else {
+ state->plaintext_password = data_blob_talloc(state,
+ req->buf,
+ passlen1+1);
+ /* Ensure null termination */
+ state->plaintext_password.data[passlen1] = 0;
+ }
+
+ srvstr_pull_req_talloc(state, req, &tmp,
+ req->buf + passlen1, STR_TERMINATE);
+ state->user = tmp ? tmp : "";
+
+ state->domain = "";
+
+ } else {
+ uint16_t passlen1 = SVAL(req->vwv+7, 0);
+ uint16_t passlen2 = SVAL(req->vwv+8, 0);
+ enum remote_arch_types ra_type = get_remote_arch();
+ const uint8_t *p = req->buf;
+ const uint8_t *save_p = req->buf;
+ uint16_t byte_count;
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ global_client_caps = IVAL(req->vwv+11, 0);
+
+ if (!(global_client_caps & CAP_STATUS32)) {
+ remove_from_common_flags2(
+ FLAGS2_32_BIT_ERROR_CODES);
+ }
+
+ /* client_caps is used as final determination if
+ * client is NT or Win95. This is needed to return
+ * the correct error codes in some circumstances.
+ */
+
+ if(ra_type == RA_WINNT || ra_type == RA_WIN2K ||
+ ra_type == RA_WIN95) {
+ if(!(global_client_caps & (CAP_NT_SMBS|
+ CAP_STATUS32))) {
+ set_remote_arch( RA_WIN95);
+ }
+ }
+ }
+
+ if (!doencrypt) {
+ /* both Win95 and WinNT stuff up the password
+ * lengths for non-encrypting systems. Uggh.
+
+ if passlen1==24 its a win95 system, and its setting
+ the password length incorrectly. Luckily it still
+ works with the default code because Win95 will null
+ terminate the password anyway
+
+ if passlen1>0 and passlen2>0 then maybe its a NT box
+ and its setting passlen2 to some random value which
+ really stuffs things up. we need to fix that one. */
+
+ if (passlen1 > 0 && passlen2 > 0 && passlen2 != 24 &&
+ passlen2 != 1) {
+ passlen2 = 0;
+ }
+ }
+
+ /* check for nasty tricks */
+ if (passlen1 > MAX_PASS_LEN
+ || passlen1 > smbreq_bufrem(req, p)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (passlen2 > MAX_PASS_LEN
+ || passlen2 > smbreq_bufrem(req, p+passlen1)) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* Save the lanman2 password and the NT md4 password. */
+
+ if ((doencrypt) && (passlen1 != 0) && (passlen1 != 24)) {
+ doencrypt = False;
+ }
+
+ if (doencrypt) {
+ state->lm_resp = data_blob_talloc(state, p, passlen1);
+ state->nt_resp = data_blob_talloc(state, p+passlen1, passlen2);
+ } else {
+ char *pass = NULL;
+ bool unic= smb_flag2 & FLAGS2_UNICODE_STRINGS;
+
+ if (unic && (passlen2 == 0) && passlen1) {
+ /* Only a ascii plaintext password was sent. */
+ (void)srvstr_pull_talloc(state,
+ req->inbuf,
+ req->flags2,
+ &pass,
+ req->buf,
+ passlen1,
+ STR_TERMINATE|STR_ASCII);
+ } else {
+ (void)srvstr_pull_talloc(state,
+ req->inbuf,
+ req->flags2,
+ &pass,
+ req->buf,
+ unic ? passlen2 : passlen1,
+ STR_TERMINATE);
+ }
+ if (!pass) {
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_INVALID_PARAMETER));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ state->plaintext_password = data_blob_talloc(state,
+ pass,
+ strlen(pass)+1);
+ }
+
+ p += passlen1 + passlen2;
+
+ p += srvstr_pull_req_talloc(state, req, &tmp, p,
+ STR_TERMINATE);
+ state->user = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(state, req, &tmp, p,
+ STR_TERMINATE);
+ state->domain = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_os = tmp ? tmp : "";
+
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ native_lanman = tmp ? tmp : "";
+
+ /* not documented or decoded by Ethereal but there is one more
+ * string in the extra bytes which is the same as the
+ * PrimaryDomain when using extended security. Windows NT 4
+ * and 2003 use this string to store the native lanman string.
+ * Windows 9x does not include a string here at all so we have
+ * to check if we have any extra bytes left */
+
+ byte_count = SVAL(req->vwv+13, 0);
+ if ( PTR_DIFF(p, save_p) < byte_count) {
+ p += srvstr_pull_req_talloc(talloc_tos(), req, &tmp, p,
+ STR_TERMINATE);
+ primary_domain = tmp ? tmp : "";
+ } else {
+ primary_domain = talloc_strdup(talloc_tos(), "null");
+ }
+
+ DEBUG(3,("Domain=[%s] NativeOS=[%s] NativeLanMan=[%s] "
+ "PrimaryDomain=[%s]\n",
+ state->domain, native_os, native_lanman, primary_domain));
+
+ if ( ra_type == RA_WIN2K ) {
+ if ( strlen(native_lanman) == 0 )
+ ra_lanman_string( primary_domain );
+ else
+ ra_lanman_string( native_lanman );
+ }
+
+ }
+
+ if (SVAL(req->vwv+4, 0) == 0) {
+ setup_new_vc_session(req->sconn);
+ }
+
+ DEBUG(3,("sesssetupX:name=[%s]\\[%s]@[%s]\n",
+ state->domain, state->user, get_remote_machine_name()));
+
+ if (*state->user) {
+ if (xconn->smb1.negprot.spnego) {
+
+ /* This has to be here, because this is a perfectly
+ * valid behaviour for guest logons :-( */
+
+ DEBUG(0,("reply_sesssetup_and_X: Rejecting attempt "
+ "at 'normal' session setup after "
+ "negotiating spnego.\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ fstrcpy(sub_user, state->user);
+ } else {
+ fstrcpy(sub_user, "");
+ }
+
+ if (!*state->user) {
+ DEBUG(3,("Got anonymous request\n"));
+
+ nt_status = make_auth4_context(state, &state->auth_context);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ uint8_t chal[8];
+
+ state->auth_context->get_ntlm_challenge(
+ state->auth_context, chal);
+
+ if (!make_user_info_guest(state,
+ sconn->remote_address,
+ sconn->local_address,
+ "SMB", &state->user_info)) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ }
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ state->user_info->auth_description = "guest";
+ }
+ }
+ } else if (doencrypt) {
+ state->auth_context = xconn->smb1.negprot.auth_context;
+ if (state->auth_context == NULL) {
+ DEBUG(0, ("reply_sesssetup_and_X: Attempted encrypted "
+ "session setup without negprot denied!\n"));
+ reply_nterror(req, nt_status_squash(
+ NT_STATUS_LOGON_FAILURE));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ nt_status = make_user_info_for_reply_enc(state,
+ &state->user_info,
+ state->user,
+ state->domain,
+ sconn->remote_address,
+ sconn->local_address,
+ "SMB",
+ state->lm_resp,
+ state->nt_resp);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ state->user_info->auth_description = "bare-NTLM";
+ }
+ } else {
+ nt_status = make_auth4_context(state, &state->auth_context);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ uint8_t chal[8];
+
+ state->auth_context->get_ntlm_challenge(
+ state->auth_context, chal);
+
+ if (!make_user_info_for_reply(state,
+ &state->user_info,
+ state->user,
+ state->domain,
+ sconn->remote_address,
+ sconn->local_address,
+ "SMB",
+ chal,
+ state->plaintext_password)) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ }
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ state->user_info->auth_description = "plaintext";
+ }
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ nt_status = auth_check_password_session_info(state->auth_context,
+ req, state->user_info,
+ &session_info);
+ TALLOC_FREE(state->user_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* it's ok - setup a reply */
+ reply_smb1_outbuf(req, 3, 0);
+ SSVAL(req->outbuf, smb_vwv0, 0xff); /* andx chain ends */
+ SSVAL(req->outbuf, smb_vwv1, 0); /* no andx offset */
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ push_signature(&req->outbuf);
+ /* perhaps grab OS version here?? */
+ }
+
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ action |= SMB_SETUP_GUEST;
+ }
+
+ /* register the name and uid as being validated, so further connections
+ to a uid can get through without a password, on the same VC */
+
+ nt_status = smbXsrv_session_create(xconn,
+ now, &session);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ session->global->signing_algo = SMB2_SIGNING_MD5_SMB1;
+ session->global->encryption_cipher = 0;
+ session->global->channels[0].signing_algo =
+ session->global->signing_algo;
+ session->global->channels[0].encryption_cipher =
+ session->global->encryption_cipher;
+
+ if (session_info->session_key.length > 0) {
+ struct smbXsrv_session *x = session;
+ uint8_t session_key[16];
+ NTSTATUS status;
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ NULL, /* no derivation */
+ &x->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ reply_nterror(req, status);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ x->global->signing_key_blob = x->global->signing_key->blob;
+
+ /*
+ * The application key is truncated/padded to 16 bytes
+ */
+ ZERO_STRUCT(session_key);
+ memcpy(session_key, session->global->signing_key_blob.data,
+ MIN(session->global->signing_key_blob.length,
+ sizeof(session_key)));
+ session->global->application_key_blob =
+ data_blob_talloc(session->global,
+ session_key,
+ sizeof(session_key));
+ ZERO_STRUCT(session_key);
+ if (session->global->application_key_blob.data == NULL) {
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ talloc_keep_secret(session->global->application_key_blob.data);
+
+ /*
+ * Place the application key into the session_info
+ */
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ session->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+ }
+
+ sconn->num_users++;
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ is_authenticated = true;
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ if (smb1_srv_is_signing_negotiated(xconn) &&
+ is_authenticated &&
+ smb2_signing_key_valid(session->global->signing_key))
+ {
+ /*
+ * Try and turn on server signing on the first non-guest
+ * sessionsetup.
+ */
+ smb1_srv_set_signing(xconn,
+ session->global->signing_key->blob,
+ state->nt_resp.data ? state->nt_resp : state->lm_resp);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ session->global->channels[0].auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ session->global->auth_time = now;
+ session->global->expiration_time = GENSEC_EXPIRE_TIME_INFINITY;
+
+ nt_status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("smb1: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(nt_status)));
+ TALLOC_FREE(session);
+ reply_nterror(req, nt_status_squash(nt_status));
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ if (!session_claim(session)) {
+ DEBUG(1, ("smb1: Failed to claim session for vuid=%llu\n",
+ (unsigned long long)session->global->session_wire_id));
+ TALLOC_FREE(session);
+ reply_nterror(req, NT_STATUS_LOGON_FAILURE);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+
+ /* current_user_info is changed on new vuid */
+ reload_services(sconn, conn_snum_used, true);
+
+ sess_vuid = session->global->session_wire_id;
+
+ SSVAL(req->outbuf,smb_vwv2,action);
+ SSVAL(req->outbuf,smb_uid,sess_vuid);
+ SSVAL(discard_const_p(char, req->inbuf),smb_uid,sess_vuid);
+ req->vuid = sess_vuid;
+
+ if (!xconn->smb1.sessions.done_sesssetup) {
+ if (smb_bufsize < SMB_BUFFER_SIZE_MIN) {
+ reply_force_doserror(req, ERRSRV, ERRerror);
+ END_PROFILE(SMBsesssetupX);
+ return;
+ }
+ xconn->smb1.sessions.max_send = smb_bufsize;
+ xconn->smb1.sessions.done_sesssetup = true;
+ }
+
+ TALLOC_FREE(state);
+ END_PROFILE(SMBsesssetupX);
+}
diff --git a/source3/smbd/smb1_sesssetup.h b/source3/smbd/smb1_sesssetup.h
new file mode 100644
index 0000000..6f1324a
--- /dev/null
+++ b/source3/smbd/smb1_sesssetup.h
@@ -0,0 +1,25 @@
+/*
+ Unix SMB/CIFS implementation.
+ handle SMBsessionsetup
+ Copyright (C) Andrew Tridgell 1998-2001
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002
+ Copyright (C) Luke Howard 2003
+ Copyright (C) Volker Lendecke 2007
+ Copyright (C) Jeremy Allison 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+void reply_sesssetup_and_X(struct smb_request *req);
diff --git a/source3/smbd/smb1_signing.c b/source3/smbd/smb1_signing.c
new file mode 100644
index 0000000..aa3027d
--- /dev/null
+++ b/source3/smbd/smb1_signing.c
@@ -0,0 +1,290 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Signing Code
+ Copyright (C) Jeremy Allison 2003.
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/param/param.h"
+
+/***********************************************************
+ Called to validate an incoming packet from the client.
+************************************************************/
+
+bool smb1_srv_check_sign_mac(struct smbXsrv_connection *conn,
+ const char *inbuf, uint32_t *seqnum,
+ bool trusted_channel)
+{
+ const uint8_t *inhdr;
+ size_t len;
+
+ /* Check if it's a non-session message. */
+ if(CVAL(inbuf,0)) {
+ return true;
+ }
+
+ len = smb_len(inbuf);
+ inhdr = (const uint8_t *)inbuf + NBT_HDR_SIZE;
+
+ if (trusted_channel) {
+ NTSTATUS status;
+
+ if (len < (HDR_SS_FIELD + 8)) {
+ DBG_WARNING("Can't check signature "
+ "on short packet! smb_len = %u\n",
+ (unsigned)len);
+ return false;
+ }
+
+ status = NT_STATUS(IVAL(inhdr, HDR_SS_FIELD + 4));
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("trusted channel passed %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ *seqnum = IVAL(inhdr, HDR_SS_FIELD);
+ return true;
+ }
+
+ *seqnum = smb1_signing_next_seqnum(conn->smb1.signing_state, false);
+ return smb1_signing_check_pdu(conn->smb1.signing_state,
+ inhdr, len,
+ *seqnum);
+}
+
+/***********************************************************
+ Called to sign an outgoing packet to the client.
+************************************************************/
+
+NTSTATUS smb1_srv_calculate_sign_mac(struct smbXsrv_connection *conn,
+ char *outbuf, uint32_t seqnum)
+{
+ uint8_t *outhdr;
+ size_t len;
+
+ /* Check if it's a non-session message. */
+ if(CVAL(outbuf,0)) {
+ return NT_STATUS_OK;;
+ }
+
+ len = smb_len(outbuf);
+ outhdr = (uint8_t *)outbuf + NBT_HDR_SIZE;
+
+ return smb1_signing_sign_pdu(conn->smb1.signing_state,
+ outhdr,
+ len,
+ seqnum);
+}
+
+
+/***********************************************************
+ Called to indicate a oneway request
+************************************************************/
+void smb1_srv_cancel_sign_response(struct smbXsrv_connection *conn)
+{
+ smb1_signing_cancel_reply(conn->smb1.signing_state, true);
+}
+
+struct smbd_shm_signing {
+ size_t shm_size;
+ uint8_t *shm_pointer;
+
+ /* we know the signing engine will only allocate 2 chunks */
+ uint8_t *ptr1;
+ size_t len1;
+ uint8_t *ptr2;
+ size_t len2;
+};
+
+static int smbd_shm_signing_destructor(struct smbd_shm_signing *s)
+{
+ anonymous_shared_free(s->shm_pointer);
+ return 0;
+}
+
+static void *smbd_shm_signing_alloc(TALLOC_CTX *mem_ctx, size_t len)
+{
+ struct smbd_shm_signing *s = talloc_get_type_abort(mem_ctx,
+ struct smbd_shm_signing);
+
+ if (s->ptr1 == NULL) {
+ s->len1 = len;
+ if (len % 8) {
+ s->len1 += (8 - (len % 8));
+ }
+ if (s->len1 > s->shm_size) {
+ s->len1 = 0;
+ errno = ENOMEM;
+ return NULL;
+ }
+ s->ptr1 = s->shm_pointer;
+ return s->ptr1;
+ }
+
+ if (s->ptr2 == NULL) {
+ s->len2 = len;
+ if (s->len2 > (s->shm_size - s->len1)) {
+ s->len2 = 0;
+ errno = ENOMEM;
+ return NULL;
+ }
+ s->ptr2 = s->shm_pointer + s->len1;
+ return s->ptr2;
+ }
+
+ errno = ENOMEM;
+ return NULL;
+}
+
+static void smbd_shm_signing_free(TALLOC_CTX *mem_ctx, void *ptr)
+{
+ struct smbd_shm_signing *s = talloc_get_type_abort(mem_ctx,
+ struct smbd_shm_signing);
+
+ if (s->ptr2 == ptr) {
+ s->ptr2 = NULL;
+ s->len2 = 0;
+ }
+}
+
+/***********************************************************
+ Called by server negprot when signing has been negotiated.
+************************************************************/
+
+bool smb1_srv_init_signing(struct loadparm_context *lp_ctx,
+ struct smbXsrv_connection *conn)
+{
+ bool allowed = true;
+ bool desired;
+ bool mandatory = false;
+
+ /*
+ * if the client and server allow signing,
+ * we desire to use it.
+ *
+ * This matches Windows behavior and is needed
+ * because not every client that requires signing
+ * sends FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED.
+ *
+ * Note that we'll always allow signing if the client
+ * does send FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED.
+ */
+
+ desired = lpcfg_server_signing_allowed(lp_ctx, &mandatory);
+
+ if (lp_async_smb_echo_handler()) {
+ struct smbd_shm_signing *s;
+
+ /* setup the signing state in shared memory */
+ s = talloc_zero(conn, struct smbd_shm_signing);
+ if (s == NULL) {
+ return false;
+ }
+ s->shm_size = 4096;
+ s->shm_pointer =
+ (uint8_t *)anonymous_shared_allocate(s->shm_size);
+ if (s->shm_pointer == NULL) {
+ talloc_free(s);
+ return false;
+ }
+ talloc_set_destructor(s, smbd_shm_signing_destructor);
+ conn->smb1.signing_state = smb1_signing_init_ex(s,
+ allowed, desired, mandatory,
+ smbd_shm_signing_alloc,
+ smbd_shm_signing_free);
+ if (!conn->smb1.signing_state) {
+ return false;
+ }
+ return true;
+ }
+
+ conn->smb1.signing_state = smb1_signing_init(conn,
+ allowed, desired, mandatory);
+ if (!conn->smb1.signing_state) {
+ return false;
+ }
+
+ return true;
+}
+
+void smb1_srv_set_signing_negotiated(struct smbXsrv_connection *conn,
+ bool allowed, bool mandatory)
+{
+ smb1_signing_set_negotiated(conn->smb1.signing_state,
+ allowed, mandatory);
+}
+
+/***********************************************************
+ Returns whether signing is active. We can't use sendfile or raw
+ reads/writes if it is.
+************************************************************/
+
+bool smb1_srv_is_signing_active(struct smbXsrv_connection *conn)
+{
+ return smb1_signing_is_active(conn->smb1.signing_state);
+}
+
+
+/***********************************************************
+ Returns whether signing is negotiated. We can't use it unless it was
+ in the negprot.
+************************************************************/
+
+bool smb1_srv_is_signing_negotiated(struct smbXsrv_connection *conn)
+{
+ return smb1_signing_is_negotiated(conn->smb1.signing_state);
+}
+
+/***********************************************************
+ Turn on signing from this packet onwards.
+************************************************************/
+
+void smb1_srv_set_signing(struct smbXsrv_connection *conn,
+ const DATA_BLOB user_session_key,
+ const DATA_BLOB response)
+{
+ bool negotiated;
+ bool mandatory;
+
+ if (!user_session_key.length)
+ return;
+
+ negotiated = smb1_signing_is_negotiated(conn->smb1.signing_state);
+ mandatory = smb1_signing_is_mandatory(conn->smb1.signing_state);
+
+ if (!negotiated && !mandatory) {
+ DBG_INFO("signing negotiated = %u, "
+ "mandatory_signing = %u. Not allowing smb signing.\n",
+ negotiated, mandatory);
+ return;
+ }
+
+ if (!smb1_signing_activate(conn->smb1.signing_state,
+ user_session_key, response)) {
+ return;
+ }
+
+ DBG_NOTICE("turning on SMB signing: "
+ "signing negotiated = %u, mandatory_signing = %u.\n",
+ negotiated, mandatory);
+}
+
diff --git a/source3/smbd/smb1_signing.h b/source3/smbd/smb1_signing.h
new file mode 100644
index 0000000..26f6042
--- /dev/null
+++ b/source3/smbd/smb1_signing.h
@@ -0,0 +1,37 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Signing Code
+ Copyright (C) Jeremy Allison 2003.
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+struct smbXsrv_connection;
+
+bool smb1_srv_check_sign_mac(struct smbXsrv_connection *conn,
+ const char *inbuf, uint32_t *seqnum, bool trusted_channel);
+NTSTATUS smb1_srv_calculate_sign_mac(struct smbXsrv_connection *conn,
+ char *outbuf, uint32_t seqnum);
+void smb1_srv_cancel_sign_response(struct smbXsrv_connection *conn);
+void smb1_srv_set_signing_negotiated(struct smbXsrv_connection *conn,
+ bool allowed, bool mandatory);
+bool smb1_srv_is_signing_active(struct smbXsrv_connection *conn);
+bool smb1_srv_is_signing_negotiated(struct smbXsrv_connection *conn);
+void smb1_srv_set_signing(struct smbXsrv_connection *conn,
+ const DATA_BLOB user_session_key,
+ const DATA_BLOB response);
+bool smb1_srv_init_signing(struct loadparm_context *lp_ctx,
+ struct smbXsrv_connection *conn);
diff --git a/source3/smbd/smb1_trans2.c b/source3/smbd/smb1_trans2.c
new file mode 100644
index 0000000..16dff34
--- /dev/null
+++ b/source3/smbd/smb1_trans2.c
@@ -0,0 +1,5703 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2005-2007
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006-2007
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ntioctl.h"
+#include "system/filesys.h"
+#include "lib/util/time_basic.h"
+#include "version.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/xattr.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "libcli/security/security.h"
+#include "trans2.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "printing.h"
+#include "lib/util_ea.h"
+#include "lib/readdir_attr.h"
+#include "messages.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+#include "source3/lib/adouble.h"
+#include "source3/smbd/dir.h"
+
+#define DIR_ENTRY_SAFETY_MARGIN 4096
+
+/****************************************************************************
+ Send the required number of replies back.
+ We assume all fields other than the data fields are
+ set correctly for the type of call.
+ HACK ! Always assumes smb_setup field is zero.
+****************************************************************************/
+
+static void send_trans2_replies(connection_struct *conn,
+ struct smb_request *req,
+ NTSTATUS status,
+ const char *params,
+ int paramsize,
+ const char *pdata,
+ int datasize,
+ int max_data_bytes)
+{
+ /* As we are using a protocol > LANMAN1 then the max_send
+ variable must have been set in the sessetupX call.
+ This takes precedence over the max_xmit field in the
+ global struct. These different max_xmit variables should
+ be merged as this is now too confusing */
+
+ int data_to_send = datasize;
+ int params_to_send = paramsize;
+ int useable_space;
+ const char *pp = params;
+ const char *pd = pdata;
+ int params_sent_thistime, data_sent_thistime, total_sent_thistime;
+ int alignment_offset = 1; /* JRA. This used to be 3. Set to 1 to make netmon parse ok. */
+ int data_alignment_offset = 0;
+ bool overflow = False;
+ struct smbXsrv_connection *xconn = req->xconn;
+ int max_send = xconn->smb1.sessions.max_send;
+
+ /* Modify the data_to_send and datasize and set the error if
+ we're trying to send more than max_data_bytes. We still send
+ the part of the packet(s) that fit. Strange, but needed
+ for OS/2. */
+
+ if (max_data_bytes > 0 && datasize > max_data_bytes) {
+ DEBUG(5,("send_trans2_replies: max_data_bytes %d exceeded by data %d\n",
+ max_data_bytes, datasize ));
+ datasize = data_to_send = max_data_bytes;
+ overflow = True;
+ }
+
+ /* If there genuinely are no parameters or data to send just send the empty packet */
+
+ if(params_to_send == 0 && data_to_send == 0) {
+ reply_smb1_outbuf(req, 10, 0);
+ if (NT_STATUS_V(status)) {
+ uint8_t eclass;
+ uint32_t ecode;
+ ntstatus_to_dos(status, &eclass, &ecode);
+ error_packet_set((char *)req->outbuf,
+ eclass, ecode, status,
+ __LINE__,__FILE__);
+ }
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans2_replies: smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req->outbuf);
+ return;
+ }
+
+ /* When sending params and data ensure that both are nicely aligned */
+ /* Only do this alignment when there is also data to send - else
+ can cause NT redirector problems. */
+
+ if (((params_to_send % 4) != 0) && (data_to_send != 0))
+ data_alignment_offset = 4 - (params_to_send % 4);
+
+ /* Space is bufsize minus Netbios over TCP header minus SMB header */
+ /* The alignment_offset is to align the param bytes on an even byte
+ boundary. NT 4.0 Beta needs this to work correctly. */
+
+ useable_space = max_send - (smb_size
+ + 2 * 10 /* wct */
+ + alignment_offset
+ + data_alignment_offset);
+
+ if (useable_space < 0) {
+ DEBUG(0, ("send_trans2_replies failed sanity useable_space "
+ "= %d!!!\n", useable_space));
+ exit_server_cleanly("send_trans2_replies: Not enough space");
+ }
+
+ while (params_to_send || data_to_send) {
+ /* Calculate whether we will totally or partially fill this packet */
+
+ total_sent_thistime = params_to_send + data_to_send;
+
+ /* We can never send more than useable_space */
+ /*
+ * Note that 'useable_space' does not include the alignment offsets,
+ * but we must include the alignment offsets in the calculation of
+ * the length of the data we send over the wire, as the alignment offsets
+ * are sent here. Fix from Marc_Jacobsen@hp.com.
+ */
+
+ total_sent_thistime = MIN(total_sent_thistime, useable_space);
+
+ reply_smb1_outbuf(req, 10, total_sent_thistime + alignment_offset
+ + data_alignment_offset);
+
+ /* Set total params and data to be sent */
+ SSVAL(req->outbuf,smb_tprcnt,paramsize);
+ SSVAL(req->outbuf,smb_tdrcnt,datasize);
+
+ /* Calculate how many parameters and data we can fit into
+ * this packet. Parameters get precedence
+ */
+
+ params_sent_thistime = MIN(params_to_send,useable_space);
+ data_sent_thistime = useable_space - params_sent_thistime;
+ data_sent_thistime = MIN(data_sent_thistime,data_to_send);
+
+ SSVAL(req->outbuf,smb_prcnt, params_sent_thistime);
+
+ /* smb_proff is the offset from the start of the SMB header to the
+ parameter bytes, however the first 4 bytes of outbuf are
+ the Netbios over TCP header. Thus use smb_base() to subtract
+ them from the calculation */
+
+ SSVAL(req->outbuf,smb_proff,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf)));
+
+ if(params_sent_thistime == 0)
+ SSVAL(req->outbuf,smb_prdisp,0);
+ else
+ /* Absolute displacement of param bytes sent in this packet */
+ SSVAL(req->outbuf,smb_prdisp,pp - params);
+
+ SSVAL(req->outbuf,smb_drcnt, data_sent_thistime);
+ if(data_sent_thistime == 0) {
+ SSVAL(req->outbuf,smb_droff,0);
+ SSVAL(req->outbuf,smb_drdisp, 0);
+ } else {
+ /* The offset of the data bytes is the offset of the
+ parameter bytes plus the number of parameters being sent this time */
+ SSVAL(req->outbuf, smb_droff,
+ ((smb_buf(req->outbuf)+alignment_offset)
+ - smb_base(req->outbuf))
+ + params_sent_thistime + data_alignment_offset);
+ SSVAL(req->outbuf,smb_drdisp, pd - pdata);
+ }
+
+ /* Initialize the padding for alignment */
+
+ if (alignment_offset != 0) {
+ memset(smb_buf(req->outbuf), 0, alignment_offset);
+ }
+
+ /* Copy the param bytes into the packet */
+
+ if(params_sent_thistime) {
+ memcpy((smb_buf(req->outbuf)+alignment_offset), pp,
+ params_sent_thistime);
+ }
+
+ /* Copy in the data bytes */
+ if(data_sent_thistime) {
+ if (data_alignment_offset != 0) {
+ memset((smb_buf(req->outbuf)+alignment_offset+
+ params_sent_thistime), 0,
+ data_alignment_offset);
+ }
+ memcpy(smb_buf(req->outbuf)+alignment_offset
+ +params_sent_thistime+data_alignment_offset,
+ pd,data_sent_thistime);
+ }
+
+ DEBUG(9,("t2_rep: params_sent_thistime = %d, data_sent_thistime = %d, useable_space = %d\n",
+ params_sent_thistime, data_sent_thistime, useable_space));
+ DEBUG(9,("t2_rep: params_to_send = %d, data_to_send = %d, paramsize = %d, datasize = %d\n",
+ params_to_send, data_to_send, paramsize, datasize));
+
+ if (overflow) {
+ error_packet_set((char *)req->outbuf,
+ ERRDOS,ERRbufferoverflow,
+ STATUS_BUFFER_OVERFLOW,
+ __LINE__,__FILE__);
+ } else if (NT_STATUS_V(status)) {
+ uint8_t eclass;
+ uint32_t ecode;
+ ntstatus_to_dos(status, &eclass, &ecode);
+ error_packet_set((char *)req->outbuf,
+ eclass, ecode, status,
+ __LINE__,__FILE__);
+ }
+
+ /* Send the packet */
+ show_msg((char *)req->outbuf);
+ if (!smb1_srv_send(xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(conn))) {
+ exit_server_cleanly("send_trans2_replies: smb1_srv_send failed.");
+ }
+
+ TALLOC_FREE(req->outbuf);
+
+ pp += params_sent_thistime;
+ pd += data_sent_thistime;
+
+ params_to_send -= params_sent_thistime;
+ data_to_send -= data_sent_thistime;
+
+ /* Sanity check */
+ if(params_to_send < 0 || data_to_send < 0) {
+ DEBUG(0,("send_trans2_replies failed sanity check pts = %d, dts = %d\n!!!",
+ params_to_send, data_to_send));
+ return;
+ }
+ }
+
+ return;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_POSIX_LOCK.
+****************************************************************************/
+
+static void smb_set_posix_lock_done(struct tevent_req *subreq);
+
+static NTSTATUS smb_set_posix_lock(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ struct tevent_req *subreq = NULL;
+ struct smbd_lock_element *lck = NULL;
+ uint64_t count;
+ uint64_t offset;
+ uint64_t smblctx;
+ bool blocking_lock = False;
+ enum brl_type lock_type;
+
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ if (fsp == NULL ||
+ fsp->fsp_flags.is_pathref ||
+ fsp_get_io_fd(fsp) == -1)
+ {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data != POSIX_LOCK_DATA_SIZE) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_TYPE_OFFSET)) {
+ case POSIX_LOCK_TYPE_READ:
+ lock_type = READ_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_WRITE:
+ /* Return the right POSIX-mappable error code for files opened read-only. */
+ if (!fsp->fsp_flags.can_write) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ lock_type = WRITE_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_UNLOCK:
+ lock_type = UNLOCK_LOCK;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_FLAGS_OFFSET)) {
+ case POSIX_LOCK_FLAG_NOWAIT:
+ blocking_lock = false;
+ break;
+ case POSIX_LOCK_FLAG_WAIT:
+ blocking_lock = true;
+ break;
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_blocking_locks(SNUM(conn))) {
+ blocking_lock = False;
+ }
+
+ smblctx = (uint64_t)IVAL(pdata, POSIX_LOCK_PID_OFFSET);
+ offset = (((uint64_t) IVAL(pdata,(POSIX_LOCK_START_OFFSET+4))) << 32) |
+ ((uint64_t) IVAL(pdata,POSIX_LOCK_START_OFFSET));
+ count = (((uint64_t) IVAL(pdata,(POSIX_LOCK_LEN_OFFSET+4))) << 32) |
+ ((uint64_t) IVAL(pdata,POSIX_LOCK_LEN_OFFSET));
+
+ DBG_DEBUG("file %s, lock_type = %u, smblctx = %"PRIu64", "
+ "count = %"PRIu64", offset = %"PRIu64"\n",
+ fsp_str_dbg(fsp),
+ (unsigned int)lock_type,
+ smblctx,
+ count,
+ offset);
+
+ if (lock_type == UNLOCK_LOCK) {
+ struct smbd_lock_element l = {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = smblctx,
+ .brltype = UNLOCK_LOCK,
+ .lock_flav = POSIX_LOCK,
+ .offset = offset,
+ .count = count,
+ };
+ status = smbd_do_unlocking(req, fsp, 1, &l);
+ return status;
+ }
+
+ lck = talloc(req, struct smbd_lock_element);
+ if (lck == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *lck = (struct smbd_lock_element) {
+ .req_guid = smbd_request_guid(req, 0),
+ .smblctx = smblctx,
+ .brltype = lock_type,
+ .lock_flav = POSIX_LOCK,
+ .count = count,
+ .offset = offset,
+ };
+
+ subreq = smbd_smb1_do_locks_send(
+ fsp,
+ req->sconn->ev_ctx,
+ &req,
+ fsp,
+ blocking_lock ? UINT32_MAX : 0,
+ true, /* large_offset */
+ 1,
+ lck);
+ if (subreq == NULL) {
+ TALLOC_FREE(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smb_set_posix_lock_done, req);
+ return NT_STATUS_EVENT_PENDING;
+}
+
+static void smb_set_posix_lock_done(struct tevent_req *subreq)
+{
+ struct smb_request *req = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ ok = smbd_smb1_do_locks_extract_smbreq(subreq, talloc_tos(), &req);
+ SMB_ASSERT(ok);
+
+ status = smbd_smb1_do_locks_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_IS_OK(status)) {
+ char params[2] = {0};
+ /* Fake up max_data_bytes here - we know it fits. */
+ send_trans2_replies(
+ req->conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ 2,
+ NULL,
+ 0,
+ 0xffff);
+ } else {
+ reply_nterror(req, status);
+ ok = smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn));
+ if (!ok) {
+ exit_server_cleanly("smb_set_posix_lock_done: "
+ "smb1_srv_send failed.");
+ }
+ }
+
+ TALLOC_FREE(req);
+ return;
+}
+
+/****************************************************************************
+ Read a list of EA names from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+static struct ea_list *read_ea_name_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t converted_size, offset = 0;
+
+ while (offset + 2 < data_size) {
+ struct ea_list *eal = talloc_zero(ctx, struct ea_list);
+ unsigned int namelen = CVAL(pdata,offset);
+
+ offset++; /* Go past the namelen byte. */
+
+ /* integer wrap paranioa. */
+ if ((offset + namelen < offset) || (offset + namelen < namelen) ||
+ (offset > data_size) || (namelen > data_size) ||
+ (offset + namelen >= data_size)) {
+ break;
+ }
+ /* Ensure the name is null terminated. */
+ if (pdata[offset + namelen] != '\0') {
+ return NULL;
+ }
+ if (!pull_ascii_talloc(ctx, &eal->ea.name, &pdata[offset],
+ &converted_size)) {
+ DEBUG(0,("read_ea_name_list: pull_ascii_talloc "
+ "failed: %s\n", strerror(errno)));
+ }
+ if (!eal->ea.name) {
+ return NULL;
+ }
+
+ offset += (namelen + 1); /* Go past the name + terminating zero. */
+ DLIST_ADD_END(ea_list_head, eal);
+ DEBUG(10,("read_ea_name_list: read ea name %s\n", eal->ea.name));
+ }
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Reply to a TRANSACT2_OPEN.
+****************************************************************************/
+
+static void call_trans2open(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ struct smb_filename *smb_fname = NULL;
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ int deny_mode;
+ int32_t open_attr;
+ bool oplock_request;
+#if 0
+ bool return_additional_info;
+ int16 open_sattr;
+ time_t open_time;
+#endif
+ int open_ofun;
+ uint32_t open_size;
+ char *pname;
+ char *fname = NULL;
+ off_t size=0;
+ int fattr = 0;
+ SMB_INO_T inode = 0;
+ int smb_action = 0;
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp;
+ struct ea_list *ea_list = NULL;
+ uint16_t flags = 0;
+ NTSTATUS status;
+ uint32_t access_mask;
+ uint32_t share_mode;
+ uint32_t create_disposition;
+ uint32_t create_options = 0;
+ uint32_t private_flags = 0;
+ NTTIME twrp = 0;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ TALLOC_CTX *ctx = talloc_tos();
+
+ /*
+ * Ensure we have enough parameters to perform the operation.
+ */
+
+ if (total_params < 29) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ flags = SVAL(params, 0);
+ deny_mode = SVAL(params, 2);
+ open_attr = SVAL(params,6);
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0;
+ }
+
+#if 0
+ return_additional_info = BITSETW(params,0);
+ open_sattr = SVAL(params, 4);
+ open_time = make_unix_date3(params+8);
+#endif
+ open_ofun = SVAL(params,12);
+ open_size = IVAL(params,14);
+ pname = &params[28];
+
+ if (IS_IPC(conn)) {
+ reply_nterror(req, NT_STATUS_NETWORK_ACCESS_DENIED);
+ goto out;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &fname,
+ pname,
+ total_params - 28,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &fname,
+ pname,
+ total_params - 28,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ DEBUG(3,("call_trans2open %s deny_mode=0x%x attr=%d ofun=0x%x size=%d\n",
+ fname, (unsigned int)deny_mode, (unsigned int)open_attr,
+ (unsigned int)open_ofun, open_size));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ if (open_ofun == 0) {
+ reply_nterror(req, NT_STATUS_OBJECT_NAME_COLLISION);
+ goto out;
+ }
+
+ if (!map_open_params_to_ntcreate(smb_fname->base_name, deny_mode,
+ open_ofun,
+ &access_mask, &share_mode,
+ &create_disposition,
+ &create_options,
+ &private_flags)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ /* Any data in this call is an EA list. */
+ if (total_data && (total_data != 4)) {
+ if (total_data < 10) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("call_trans2open: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ ea_list = read_ea_list(talloc_tos(), pdata + 4,
+ total_data - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+
+ if (!req->posix_pathnames &&
+ ea_list_has_invalid_name(ea_list)) {
+ int param_len = 30;
+ *pparams = (char *)SMB_REALLOC(*pparams, param_len);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+ memset(params, '\0', param_len);
+ send_trans2_replies(conn, req, STATUS_INVALID_EA_NAME,
+ params, param_len, NULL, 0, max_data_bytes);
+ goto out;
+ }
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_mode, /* share_access */
+ create_disposition, /* create_disposition*/
+ create_options, /* create_options */
+ open_attr, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ open_size, /* allocation_size */
+ private_flags,
+ NULL, /* sd */
+ ea_list, /* ea_list */
+ &fsp, /* result */
+ &smb_action, /* psbuf */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ goto out;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ fsp = fcb_or_dos_open(
+ req,
+ smb_fname,
+ access_mask,
+ create_options,
+ private_flags);
+ if (fsp == NULL) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ goto out;
+ }
+ reply_openerror(req, status);
+ goto out;
+ }
+
+ smb_action = FILE_WAS_OPENED;
+ }
+
+ size = get_file_size_stat(&smb_fname->st);
+ fattr = fdos_mode(fsp);
+ inode = smb_fname->st.st_ex_ino;
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ goto out;
+ }
+
+ /* Realloc the size of parameters and data we will return */
+ *pparams = (char *)SMB_REALLOC(*pparams, 30);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,fsp->fnum);
+ SSVAL(params,2,fattr);
+ srv_put_dos_date2_ts(params, 4, smb_fname->st.st_ex_mtime);
+ SIVAL(params,8, (uint32_t)size);
+ SSVAL(params,12,deny_mode);
+ SSVAL(params,14,0); /* open_type - file or directory. */
+ SSVAL(params,16,0); /* open_state - only valid for IPC device. */
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ smb_action |= EXTENDED_OPLOCK_GRANTED;
+ }
+
+ SSVAL(params,18,smb_action);
+
+ /*
+ * WARNING - this may need to be changed if SMB_INO_T <> 4 bytes.
+ */
+ SIVAL(params,20,inode);
+ SSVAL(params,24,0); /* Padding. */
+ if (flags & 8) {
+ uint32_t ea_size = estimate_ea_size(smb_fname->fsp);
+ SIVAL(params, 26, ea_size);
+ } else {
+ SIVAL(params, 26, 0);
+ }
+
+ /* Send the required number of replies */
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 30, *ppdata, 0, max_data_bytes);
+ out:
+ TALLOC_FREE(smb_fname);
+}
+
+static NTSTATUS get_lanman2_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ uint16_t flags2,
+ const char *path_mask,
+ uint32_t dirtype,
+ int info_level,
+ bool requires_resume_key,
+ bool dont_descend,
+ bool ask_sharemode,
+ char **ppdata,
+ char *base_data,
+ char *end_data,
+ int space_remaining,
+ int *last_entry_off,
+ struct ea_list *name_list)
+{
+ uint8_t align = 4;
+ const bool do_pad = true;
+
+ if (info_level >= 1 && info_level <= 3) {
+ /* No alignment on earlier info levels. */
+ align = 1;
+ }
+
+ return smbd_dirptr_lanman2_entry(ctx, conn, dirptr, flags2,
+ path_mask, dirtype, info_level,
+ requires_resume_key, dont_descend, ask_sharemode,
+ true, align, do_pad,
+ ppdata, base_data, end_data,
+ space_remaining,
+ NULL,
+ last_entry_off, name_list, NULL);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDFIRST.
+****************************************************************************/
+
+static void call_trans2findfirst(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ /* We must be careful here that we don't return more than the
+ allowed number of data bytes. If this means returning fewer than
+ maxentries then so be it. We assume that the redirector has
+ enough room for the fixed number of parameter bytes it has
+ requested. */
+ struct smb_filename *smb_dname = NULL;
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *data_end;
+ uint32_t dirtype;
+ int maxentries;
+ uint16_t findfirst_flags;
+ bool close_after_first;
+ bool close_if_end;
+ bool requires_resume_key;
+ int info_level;
+ char *directory = NULL;
+ char *mask = NULL;
+ char *p;
+ int last_entry_off=0;
+ int dptr_num = -1;
+ int numentries = 0;
+ int i;
+ bool finished = False;
+ bool dont_descend = False;
+ bool out_of_space = False;
+ int space_remaining;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS ntstatus = NT_STATUS_OK;
+ bool ask_sharemode;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ bool backup_priv = false;
+ bool as_root = false;
+ files_struct *fsp = NULL;
+ struct files_struct *dirfsp = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (total_params < 13) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ dirtype = SVAL(params,0);
+ maxentries = SVAL(params,2);
+ findfirst_flags = SVAL(params,4);
+ close_after_first = (findfirst_flags & FLAG_TRANS2_FIND_CLOSE);
+ close_if_end = (findfirst_flags & FLAG_TRANS2_FIND_CLOSE_IF_END);
+ requires_resume_key = (findfirst_flags & FLAG_TRANS2_FIND_REQUIRE_RESUME);
+ backup_priv = ((findfirst_flags & FLAG_TRANS2_FIND_BACKUP_INTENT) &&
+ security_token_has_privilege(get_current_nttok(conn),
+ SEC_PRIV_BACKUP));
+
+ info_level = SVAL(params,6);
+
+ DBG_NOTICE("dirtype = %"PRIx32", maxentries = %d, "
+ "close_after_first=%d, close_if_end = %d "
+ "requires_resume_key = %d backup_priv = %d level = 0x%x, "
+ "max_data_bytes = %d\n",
+ dirtype,
+ maxentries,
+ close_after_first,
+ close_if_end,
+ requires_resume_key,
+ backup_priv,
+ info_level,
+ max_data_bytes);
+
+ if (!maxentries) {
+ /* W2K3 seems to treat zero as 1. */
+ maxentries = 1;
+ }
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ case SMB_FIND_EA_SIZE:
+ case SMB_FIND_EA_LIST:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_NAMES_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ break;
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ goto out;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ goto out;
+ }
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ goto out;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(talloc_tos(),
+ params,
+ req->flags2,
+ &directory,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ } else {
+ srvstr_get_path(talloc_tos(),
+ params,
+ req->flags2,
+ &directory,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ }
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ if (backup_priv) {
+ become_root();
+ as_root = true;
+ }
+ ntstatus = smb1_strip_dfs_path(talloc_tos(), &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ ntstatus = filename_convert_smb1_search_path(talloc_tos(),
+ conn,
+ directory,
+ ucf_flags,
+ &dirfsp,
+ &smb_dname,
+ &mask);
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ if (NT_STATUS_EQUAL(ntstatus,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req, NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ goto out;
+ }
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ TALLOC_FREE(directory);
+ directory = smb_dname->base_name;
+
+ DEBUG(5,("dir=%s, mask = %s\n",directory, mask));
+
+ if (info_level == SMB_FIND_EA_LIST) {
+ uint32_t ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ ea_size = IVAL(pdata,0);
+ if (ea_size != total_data) {
+ DBG_NOTICE("Rejecting EA request with incorrect "
+ "total_data=%d (should be %" PRIu32 ")\n",
+ total_data,
+ ea_size);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(talloc_tos(), pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+ }
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if(*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ pdata = *ppdata;
+ data_end = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+ /*
+ * squash valgrind "writev(vector[...]) points to uninitialised byte(s)"
+ * error.
+ */
+ memset(pdata + total_data, 0, ((max_data_bytes + DIR_ENTRY_SAFETY_MARGIN) - total_data));
+ /* Realloc the params space */
+ *pparams = (char *)SMB_REALLOC(*pparams, 10);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+
+ /*
+ * Open an fsp on this directory for the dptr.
+ */
+ ntstatus = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* dname */
+ FILE_LIST_DIRECTORY, /* access_mask */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY,/* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, /* in_context */
+ NULL);/* out_context */
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ DBG_ERR("failed to open directory %s\n",
+ smb_fname_str_dbg(smb_dname));
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ /* Save the wildcard match and attribs we are using on this directory -
+ needed as lanman2 assumes these are being saved between calls */
+
+ ntstatus = dptr_create(conn,
+ req,
+ fsp, /* fsp */
+ False,
+ mask,
+ dirtype,
+ &fsp->dptr);
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ /*
+ * Use NULL here for the first parameter (req)
+ * as this is not a client visible handle so
+ * can't be part of an SMB1 chain.
+ */
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ reply_nterror(req, ntstatus);
+ goto out;
+ }
+
+ if (backup_priv) {
+ /* Remember this in case we have
+ to do a findnext. */
+ dptr_set_priv(fsp->dptr);
+ }
+
+ dptr_num = dptr_dnum(fsp->dptr);
+ DEBUG(4,("dptr_num is %d, wcard = %s, attr = %d\n", dptr_num, mask, dirtype));
+
+ /* We don't need to check for VOL here as this is returned by
+ a different TRANS2 call. */
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",
+ directory,lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn))));
+ if (in_list(directory,
+ lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn)),
+ dptr_case_sensitive(fsp->dptr))) {
+ dont_descend = True;
+ }
+
+ p = pdata;
+ space_remaining = max_data_bytes;
+ out_of_space = False;
+
+ ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ for (i=0;(i<maxentries) && !finished && !out_of_space;i++) {
+
+ ntstatus = get_lanman2_dir_entry(talloc_tos(),
+ conn,
+ fsp->dptr,
+ req->flags2,
+ mask,
+ dirtype,
+ info_level,
+ requires_resume_key,
+ dont_descend,
+ ask_sharemode,
+ &p,
+ pdata,
+ data_end,
+ space_remaining,
+ &last_entry_off,
+ ea_list);
+ if (NT_STATUS_EQUAL(ntstatus, NT_STATUS_ILLEGAL_CHARACTER)) {
+ /*
+ * Bad character conversion on name. Ignore
+ * this entry.
+ */
+ continue;
+ }
+ if (NT_STATUS_EQUAL(ntstatus, STATUS_MORE_ENTRIES)) {
+ out_of_space = true;
+ } else {
+ finished = !NT_STATUS_IS_OK(ntstatus);
+ }
+
+ if (!finished && !out_of_space) {
+ numentries++;
+ }
+
+ /* Ensure space_remaining never goes -ve. */
+ if (PTR_DIFF(p,pdata) > max_data_bytes) {
+ space_remaining = 0;
+ out_of_space = true;
+ } else {
+ space_remaining = max_data_bytes - PTR_DIFF(p,pdata);
+ }
+ }
+
+ /* Check if we can close the dirptr */
+ if(close_after_first || (finished && close_if_end)) {
+ DEBUG(5,("call_trans2findfirst - (2) closing dptr_num %d\n", dptr_num));
+ dptr_num = -1;
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+
+ /*
+ * If there are no matching entries we must return ERRDOS/ERRbadfile -
+ * from observation of NT. NB. This changes to ERRDOS,ERRnofiles if
+ * the protocol level is less than NT1. Tested with smbclient. JRA.
+ * This should fix the OS/2 client bug #2335.
+ */
+
+ if(numentries == 0) {
+ dptr_num = -1;
+ /*
+ * We may have already closed the file in the
+ * close_after_first or finished case above.
+ */
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ if (xconn->protocol < PROTOCOL_NT1) {
+ reply_force_doserror(req, ERRDOS, ERRnofiles);
+ goto out;
+ } else {
+ reply_botherror(req, NT_STATUS_NO_SUCH_FILE,
+ ERRDOS, ERRbadfile);
+ goto out;
+ }
+ }
+
+ /* At this point pdata points to numentries directory entries. */
+
+ /* Set up the return parameter block */
+ SSVAL(params,0,dptr_num);
+ SSVAL(params,2,numentries);
+ SSVAL(params,4,finished);
+ SSVAL(params,6,0); /* Never an EA error */
+ SSVAL(params,8,last_entry_off);
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 10, pdata, PTR_DIFF(p,pdata),
+ max_data_bytes);
+
+ if ((! *directory) && dptr_path(sconn, dptr_num)) {
+ directory = talloc_strdup(talloc_tos(),dptr_path(sconn, dptr_num));
+ if (!directory) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ DEBUG( 4, ( "%s mask=%s directory=%s dirtype=%d numentries=%d\n",
+ smb_fn_name(req->cmd),
+ mask, directory, dirtype, numentries ) );
+
+ /*
+ * Force a name mangle here to ensure that the
+ * mask as an 8.3 name is top of the mangled cache.
+ * The reasons for this are subtle. Don't remove
+ * this code unless you know what you are doing
+ * (see PR#13758). JRA.
+ */
+
+ if(!mangle_is_8_3_wildcards( mask, False, conn->params)) {
+ char mangled_name[13];
+ name_to_8_3(mask, mangled_name, True, conn->params);
+ }
+ out:
+
+ if (as_root) {
+ unbecome_root();
+ }
+
+ TALLOC_FREE(smb_dname);
+ return;
+}
+
+static bool smbd_dptr_name_equal(struct dptr_struct *dptr,
+ const char *name1,
+ const char *name2)
+{
+ bool equal;
+
+ if (dptr_case_sensitive(dptr)) {
+ equal = (strcmp(name1, name2) == 0);
+ } else {
+ equal = strequal(name1, name2);
+ }
+
+ return equal;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNEXT.
+****************************************************************************/
+
+static void call_trans2findnext(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ /* We must be careful here that we don't return more than the
+ allowed number of data bytes. If this means returning fewer than
+ maxentries then so be it. We assume that the redirector has
+ enough room for the fixed number of parameter bytes it has
+ requested. */
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *data_end;
+ int dptr_num;
+ int maxentries;
+ uint16_t info_level;
+ uint32_t resume_key;
+ uint16_t findnext_flags;
+ bool close_after_request;
+ bool close_if_end;
+ bool requires_resume_key;
+ bool continue_bit;
+ char *resume_name = NULL;
+ const char *mask = NULL;
+ const char *directory = NULL;
+ char *p = NULL;
+ uint16_t dirtype;
+ int numentries = 0;
+ int i, last_entry_off=0;
+ bool finished = False;
+ bool dont_descend = False;
+ bool out_of_space = False;
+ int space_remaining;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS ntstatus = NT_STATUS_OK;
+ bool ask_sharemode;
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smbd_server_connection *sconn = req->sconn;
+ bool backup_priv = false;
+ bool as_root = false;
+ files_struct *fsp = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (total_params < 13) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ dptr_num = SVAL(params,0);
+ maxentries = SVAL(params,2);
+ info_level = SVAL(params,4);
+ resume_key = IVAL(params,6);
+ findnext_flags = SVAL(params,10);
+ close_after_request = (findnext_flags & FLAG_TRANS2_FIND_CLOSE);
+ close_if_end = (findnext_flags & FLAG_TRANS2_FIND_CLOSE_IF_END);
+ requires_resume_key = (findnext_flags & FLAG_TRANS2_FIND_REQUIRE_RESUME);
+ continue_bit = (findnext_flags & FLAG_TRANS2_FIND_CONTINUE);
+
+ if (!continue_bit) {
+ /* We only need resume_name if continue_bit is zero. */
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &resume_name,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &resume_name,
+ params+12,
+ total_params - 12,
+ STR_TERMINATE,
+ &ntstatus);
+ }
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ /* Win9x or OS/2 can send a resume name of ".." or ".". This will cause the parser to
+ complain (it thinks we're asking for the directory above the shared
+ path or an invalid name). Catch this as the resume name is only compared, never used in
+ a file access. JRA. */
+ srvstr_pull_talloc(ctx, params, req->flags2,
+ &resume_name, params+12,
+ total_params - 12,
+ STR_TERMINATE);
+
+ if (!resume_name || !(ISDOT(resume_name) || ISDOTDOT(resume_name))) {
+ reply_nterror(req, ntstatus);
+ return;
+ }
+ }
+ }
+
+ DBG_NOTICE("dirhandle = %d, max_data_bytes = %u, maxentries = %d, "
+ "close_after_request=%d, close_if_end = %d "
+ "requires_resume_key = %d resume_key = %d "
+ "resume name = %s continue=%d level = %d\n",
+ dptr_num,
+ max_data_bytes,
+ maxentries,
+ close_after_request,
+ close_if_end,
+ requires_resume_key,
+ resume_key,
+ resume_name ? resume_name : "(NULL)",
+ continue_bit,
+ info_level);
+
+ if (!maxentries) {
+ /* W2K3 seems to treat zero as 1. */
+ maxentries = 1;
+ }
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ case SMB_FIND_EA_SIZE:
+ case SMB_FIND_EA_LIST:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_NAMES_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ break;
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ if (info_level == SMB_FIND_EA_LIST) {
+ uint32_t ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ ea_size = IVAL(pdata,0);
+ if (ea_size != total_data) {
+ DBG_NOTICE("Rejecting EA request with incorrect "
+ "total_data=%d (should be %" PRIu32 ")\n",
+ total_data,
+ ea_size);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(ctx, pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if(*ppdata == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ pdata = *ppdata;
+ data_end = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ /*
+ * squash valgrind "writev(vector[...]) points to uninitialised byte(s)"
+ * error.
+ */
+ memset(pdata + total_data, 0, (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN) - total_data);
+ /* Realloc the params space */
+ *pparams = (char *)SMB_REALLOC(*pparams, 6*SIZEOFWORD);
+ if(*pparams == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ params = *pparams;
+
+ /* Check that the dptr is valid */
+ fsp = dptr_fetch_lanman2_fsp(sconn, dptr_num);
+ if (fsp == NULL) {
+ reply_nterror(req, STATUS_NO_MORE_FILES);
+ return;
+ }
+
+ directory = dptr_path(sconn, dptr_num);
+
+ /* Get the wildcard mask from the dptr */
+ if((mask = dptr_wcard(sconn, dptr_num))== NULL) {
+ DEBUG(2,("dptr_num %d has no wildcard\n", dptr_num));
+ reply_nterror(req, STATUS_NO_MORE_FILES);
+ return;
+ }
+
+ /* Get the attr mask from the dptr */
+ dirtype = dptr_attr(sconn, dptr_num);
+
+ backup_priv = dptr_get_priv(fsp->dptr);
+
+ DEBUG(3,("dptr_num is %d, mask = %s, attr = %x, dirptr=(0x%lX) "
+ "backup_priv = %d\n",
+ dptr_num, mask, dirtype,
+ (long)fsp->dptr,
+ (int)backup_priv));
+
+ /* We don't need to check for VOL here as this is returned by
+ a different TRANS2 call. */
+
+ DEBUG(8,("dirpath=<%s> dontdescend=<%s>\n",
+ directory,lp_dont_descend(ctx, lp_sub, SNUM(conn))));
+ if (in_list(directory,lp_dont_descend(ctx, lp_sub, SNUM(conn)),
+ dptr_case_sensitive(fsp->dptr)))
+ dont_descend = True;
+
+ p = pdata;
+ space_remaining = max_data_bytes;
+ out_of_space = False;
+
+ if (backup_priv) {
+ become_root();
+ as_root = true;
+ }
+
+ /*
+ * Seek to the correct position. We no longer use the resume key but
+ * depend on the last file name instead.
+ */
+
+ if(!continue_bit && resume_name && *resume_name) {
+ bool posix_open = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+ char *last_name_sent = NULL;
+ bool sequential;
+
+ /*
+ * Remember, name_to_8_3 is called by
+ * get_lanman2_dir_entry(), so the resume name
+ * could be mangled. Ensure we check the unmangled name.
+ */
+
+ if (!posix_open &&
+ mangle_is_mangled(resume_name, conn->params)) {
+ char *new_resume_name = NULL;
+ mangle_lookup_name_from_8_3(ctx,
+ resume_name,
+ &new_resume_name,
+ conn->params);
+ if (new_resume_name) {
+ resume_name = new_resume_name;
+ }
+ }
+
+ /*
+ * Fix for NT redirector problem triggered by resume key indexes
+ * changing between directory scans. We now return a resume key of 0
+ * and instead look for the filename to continue from (also given
+ * to us by NT/95/smbfs/smbclient). If no other scans have been done between the
+ * findfirst/findnext (as is usual) then the directory pointer
+ * should already be at the correct place.
+ */
+
+ last_name_sent = smbd_dirptr_get_last_name_sent(fsp->dptr);
+ sequential = smbd_dptr_name_equal(fsp->dptr,
+ resume_name,
+ last_name_sent);
+ if (!sequential) {
+ char *name = NULL;
+ bool found = false;
+
+ dptr_RewindDir(fsp->dptr);
+
+ while ((name = dptr_ReadDirName(talloc_tos(),
+ fsp->dptr)) != NULL) {
+ found = smbd_dptr_name_equal(fsp->dptr,
+ resume_name,
+ name);
+ TALLOC_FREE(name);
+ if (found) {
+ break;
+ }
+ }
+
+ if (!found) {
+ /*
+ * We got a name that used to exist
+ * but does not anymore. Just start
+ * from the beginning. Shown by the
+ * "raw.search.os2 delete" smbtorture
+ * test.
+ */
+ dptr_RewindDir(fsp->dptr);
+ }
+ }
+ } /* end if resume_name && !continue_bit */
+
+ ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ for (i=0;(i<(int)maxentries) && !finished && !out_of_space ;i++) {
+
+ ntstatus = get_lanman2_dir_entry(ctx,
+ conn,
+ fsp->dptr,
+ req->flags2,
+ mask,
+ dirtype,
+ info_level,
+ requires_resume_key,
+ dont_descend,
+ ask_sharemode,
+ &p,
+ pdata,
+ data_end,
+ space_remaining,
+ &last_entry_off,
+ ea_list);
+ if (NT_STATUS_EQUAL(ntstatus, NT_STATUS_ILLEGAL_CHARACTER)) {
+ /*
+ * Bad character conversion on name. Ignore
+ * this entry.
+ */
+ continue;
+ }
+ if (NT_STATUS_EQUAL(ntstatus, STATUS_MORE_ENTRIES)) {
+ out_of_space = true;
+ } else {
+ finished = !NT_STATUS_IS_OK(ntstatus);
+ }
+
+ if (!finished && !out_of_space) {
+ numentries++;
+ }
+
+ space_remaining = max_data_bytes - PTR_DIFF(p,pdata);
+ }
+
+ DEBUG( 3, ( "%s mask=%s directory=%s dirtype=%d numentries=%d\n",
+ smb_fn_name(req->cmd),
+ mask, directory, dirtype, numentries ) );
+
+ /* Check if we can close the fsp->dptr */
+ if(close_after_request || (finished && close_if_end)) {
+ DBG_INFO("closing dptr_num = %d\n", dptr_num);
+ dptr_num = -1;
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+
+ if (as_root) {
+ unbecome_root();
+ }
+
+ /* Set up the return parameter block */
+ SSVAL(params,0,numentries);
+ SSVAL(params,2,finished);
+ SSVAL(params,4,0); /* Never an EA error */
+ SSVAL(params,6,last_entry_off);
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 8, pdata, PTR_DIFF(p,pdata),
+ max_data_bytes);
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_QFSINFO (query filesystem info).
+****************************************************************************/
+
+static void call_trans2qfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+ int data_len = 0;
+ size_t fixed_portion;
+ NTSTATUS status;
+
+ if (total_params < 2) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (info_level != SMB_QUERY_CIFS_UNIX_INFO) {
+ DEBUG(0,("call_trans2qfsinfo: encryption required "
+ "and info level 0x%x sent.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ DEBUG(3,("call_trans2qfsinfo: level = %d\n", info_level));
+
+ status = smbd_do_qfsinfo(req->xconn, conn, req,
+ info_level,
+ req->flags2,
+ max_data_bytes,
+ &fixed_portion,
+ NULL,
+ NULL,
+ ppdata, &data_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 0, *ppdata, data_len,
+ max_data_bytes);
+
+ DEBUG( 4, ( "%s info_level = %d\n",
+ smb_fn_name(req->cmd), info_level) );
+
+ return;
+}
+
+/****************************************************************************
+ Reply to a TRANS2_SETFSINFO (set filesystem info).
+****************************************************************************/
+
+static void call_trans2setfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *xconn = req->xconn;
+ char *pdata = *ppdata;
+ char *params = *pparams;
+ uint16_t info_level;
+
+ DEBUG(10,("call_trans2setfsinfo: for service [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+
+ /* */
+ if (total_params < 4) {
+ DEBUG(0,("call_trans2setfsinfo: requires total_params(%d) >= 4 bytes!\n",
+ total_params));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,2);
+
+ if (IS_IPC(conn)) {
+ if (info_level != SMB_REQUEST_TRANSPORT_ENCRYPTION &&
+ info_level != SMB_SET_CIFS_UNIX_INFO) {
+ DEBUG(0,("call_trans2setfsinfo: not an allowed "
+ "info level (0x%x) on IPC$.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (info_level != SMB_REQUEST_TRANSPORT_ENCRYPTION) {
+ DEBUG(0,("call_trans2setfsinfo: encryption required "
+ "and info level 0x%x sent.\n",
+ (unsigned int)info_level));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ switch(info_level) {
+ case SMB_SET_CIFS_UNIX_INFO:
+ if (!lp_smb1_unix_extensions()) {
+ DEBUG(2,("call_trans2setfsinfo: "
+ "SMB_SET_CIFS_UNIX_INFO is invalid with "
+ "unix extensions off\n"));
+ reply_nterror(req,
+ NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ /* There should be 12 bytes of capabilities set. */
+ if (total_data < 12) {
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ xconn->smb1.unix_info.client_major = SVAL(pdata,0);
+ xconn->smb1.unix_info.client_minor = SVAL(pdata,2);
+ xconn->smb1.unix_info.client_cap_low = IVAL(pdata,4);
+ xconn->smb1.unix_info.client_cap_high = IVAL(pdata,8);
+
+ /* Just print these values for now. */
+ DBG_DEBUG("set unix_info info. "
+ "major = %"PRIu16", minor = %"PRIu16
+ "cap_low = 0x%"PRIx32", "
+ "cap_high = 0x%"PRIx32"\n",
+ xconn->smb1.unix_info.client_major,
+ xconn->smb1.unix_info.client_minor,
+ xconn->smb1.unix_info.client_cap_low,
+ xconn->smb1.unix_info.client_cap_high);
+
+ /*
+ * Here is where we must switch to posix
+ * pathname processing...
+ */
+ if (xconn->smb1.unix_info.client_cap_low &
+ CIFS_UNIX_POSIX_PATHNAMES_CAP)
+ {
+ lp_set_posix_pathnames();
+ mangle_change_to_posix();
+ }
+
+ if ((xconn->smb1.unix_info.client_cap_low &
+ CIFS_UNIX_FCNTL_LOCKS_CAP) &&
+ !(xconn->smb1.unix_info.client_cap_low &
+ CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP))
+ {
+ /* Client that knows how to do posix locks,
+ * but not posix open/mkdir operations. Set a
+ * default type for read/write checks. */
+
+ lp_set_posix_default_cifsx_readwrite_locktype(
+ POSIX_LOCK);
+
+ }
+ break;
+
+ case SMB_REQUEST_TRANSPORT_ENCRYPTION:
+ {
+ NTSTATUS status;
+ size_t param_len = 0;
+ size_t data_len = total_data;
+
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(
+ req,
+ NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ if (lp_server_smb_encrypt(SNUM(conn)) ==
+ SMB_ENCRYPTION_OFF) {
+ reply_nterror(
+ req,
+ NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ if (xconn->smb1.echo_handler.trusted_fde) {
+ DEBUG( 2,("call_trans2setfsinfo: "
+ "request transport encryption disabled"
+ "with 'fork echo handler = yes'\n"));
+ reply_nterror(
+ req,
+ NT_STATUS_NOT_SUPPORTED);
+ return;
+ }
+
+ DEBUG( 4,("call_trans2setfsinfo: "
+ "request transport encryption.\n"));
+
+ status = srv_request_encryption_setup(conn,
+ (unsigned char **)ppdata,
+ &data_len,
+ (unsigned char **)pparams,
+ &param_len);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ send_trans2_replies(conn, req,
+ NT_STATUS_OK,
+ *pparams,
+ param_len,
+ *ppdata,
+ data_len,
+ max_data_bytes);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Server-side transport
+ * encryption is now *on*. */
+ status = srv_encryption_start(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ char *reason = talloc_asprintf(talloc_tos(),
+ "Failure in setting "
+ "up encrypted transport: %s",
+ nt_errstr(status));
+ exit_server_cleanly(reason);
+ }
+ }
+ return;
+ }
+
+ case SMB_FS_QUOTA_INFORMATION:
+ {
+ NTSTATUS status;
+ DATA_BLOB qdata = {
+ .data = (uint8_t *)pdata,
+ .length = total_data
+ };
+ files_struct *fsp = NULL;
+ fsp = file_fsp(req, SVAL(params,0));
+
+ status = smb_set_fsquota(conn,
+ req,
+ fsp,
+ &qdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ break;
+ }
+ default:
+ DEBUG(3,("call_trans2setfsinfo: unknown level (0x%X) not implemented yet.\n",
+ info_level));
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ break;
+ }
+
+ /*
+ * sending this reply works fine,
+ * but I'm not sure it's the same
+ * like windows do...
+ * --metze
+ */
+ reply_smb1_outbuf(req, 10, 0);
+}
+
+/****************************************************************************
+ Reply to a TRANSACT2_QFILEINFO on a PIPE !
+****************************************************************************/
+
+static void call_trans2qpipeinfo(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ uint16_t info_level,
+ unsigned int tran_call,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ unsigned int data_size = 0;
+ unsigned int param_size = 2;
+
+ if (!fsp_is_np(fsp)) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+ SSVAL(params,0,0);
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ data_size = max_data_bytes + DIR_ENTRY_SAFETY_MARGIN;
+ *ppdata = (char *)SMB_REALLOC(*ppdata, data_size);
+ if (*ppdata == NULL ) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+
+ switch (info_level) {
+ case SMB_FILE_STANDARD_INFORMATION:
+ memset(pdata,0,24);
+ SOFF_T(pdata,0,4096LL);
+ SIVAL(pdata,16,1);
+ SIVAL(pdata,20,1);
+ data_size = 24;
+ break;
+
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, param_size, *ppdata, data_size,
+ max_data_bytes);
+}
+
+static void handle_trans2qfilepathinfo_result(
+ connection_struct *conn,
+ struct smb_request *req,
+ uint16_t info_level,
+ NTSTATUS status,
+ char *pdata,
+ int data_return_size,
+ size_t fixed_portion,
+ unsigned int max_data_bytes)
+{
+ char params[2] = { 0, 0, };
+ int param_size = 2;
+
+ /*
+ * draft-leach-cifs-v1-spec-02.txt
+ * 4.2.14 TRANS2_QUERY_PATH_INFORMATION: Get File Attributes given Path
+ * says:
+ *
+ * The requested information is placed in the Data portion of the
+ * transaction response. For the information levels greater than 0x100,
+ * the transaction response has 1 parameter word which should be
+ * ignored by the client.
+ *
+ * However Windows only follows this rule for the IS_NAME_VALID call.
+ */
+ switch (info_level) {
+ case SMB_INFO_IS_NAME_VALID:
+ param_size = 0;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ return;
+ }
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (fixed_portion > max_data_bytes) {
+ reply_nterror(req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return;
+ }
+
+ send_trans2_replies(
+ conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ param_size,
+ pdata,
+ data_return_size,
+ max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_QFILEPATHINFO or TRANSACT2_QFILEINFO (query file info by
+ file name or file id).
+****************************************************************************/
+
+static void call_trans2qfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ unsigned int tran_call,
+ uint16_t info_level,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ bool delete_pending,
+ struct timespec write_time_ts,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ unsigned int data_size = 0;
+ struct ea_list *ea_list = NULL;
+ size_t fixed_portion;
+ NTSTATUS status = NT_STATUS_OK;
+
+ DEBUG(3,("call_trans2qfilepathinfo %s (%s) level=%d call=%d "
+ "total_data=%d\n", smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ info_level,tran_call,total_data));
+
+ /* Pull out any data sent here before we realloc. */
+ switch (info_level) {
+ case SMB_INFO_QUERY_EAS_FROM_LIST:
+ {
+ /* Pull any EA list from the data portion. */
+ uint32_t ea_size;
+
+ if (total_data < 4) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ ea_size = IVAL(pdata,0);
+
+ if (total_data > 0 && ea_size != total_data) {
+ DEBUG(4,("call_trans2qfilepathinfo: Rejecting EA request with incorrect \
+total_data=%u (should be %u)\n", (unsigned int)total_data, (unsigned int)IVAL(pdata,0) ));
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ /* Pull out the list of names. */
+ ea_list = read_ea_name_list(req, pdata + 4, ea_size - 4);
+ if (!ea_list) {
+ reply_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+ SSVAL(params,0,0);
+
+ if ((info_level & SMB2_INFO_SPECIAL) == SMB2_INFO_SPECIAL) {
+ /*
+ * We use levels that start with 0xFF00
+ * internally to represent SMB2 specific levels
+ */
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ status = smbd_do_qfilepathinfo(conn, req, req, info_level,
+ fsp, smb_fname,
+ delete_pending, write_time_ts,
+ ea_list,
+ req->flags2, max_data_bytes,
+ &fixed_portion,
+ ppdata, &data_size);
+
+ handle_trans2qfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_size,
+ fixed_portion,
+ max_data_bytes);
+}
+
+static NTSTATUS smb_q_unix_basic(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+ const int total_data = 100;
+
+ *ppdata = SMB_REALLOC(*ppdata, total_data);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ store_file_unix_basic(conn, *ppdata, fsp, &smb_fname->st);
+
+ *ptotal_data = total_data;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb_q_unix_info2(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+ const int total_data = 116;
+
+ *ppdata = SMB_REALLOC(*ppdata, total_data);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ store_file_unix_basic_info2(conn, *ppdata, fsp, &smb_fname->st);
+
+ *ptotal_data = total_data;
+
+ return NT_STATUS_OK;
+}
+
+#if defined(HAVE_POSIX_ACLS)
+/****************************************************************************
+ Utility function to open a fsp for a POSIX handle operation.
+****************************************************************************/
+
+static NTSTATUS get_posix_fsp(connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ files_struct **ret_fsp)
+{
+ NTSTATUS status;
+ uint32_t create_disposition = FILE_OPEN;
+ uint32_t share_access = FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE;
+ struct smb2_create_blobs *posx = NULL;
+
+ /*
+ * Only FILE_FLAG_POSIX_SEMANTICS matters on existing files,
+ * but set reasonable defaults.
+ */
+ uint32_t file_attributes = 0664;
+ uint32_t oplock = NO_OPLOCK;
+ uint32_t create_options = FILE_NON_DIRECTORY_FILE;
+
+ /* File or directory must exist. */
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ /* Cannot be a symlink. */
+ if (S_ISLNK(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ /* Set options correctly for directory open. */
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ /*
+ * Only FILE_FLAG_POSIX_SEMANTICS matters on existing
+ * directories, but set reasonable defaults.
+ */
+ file_attributes = 0775;
+ create_options = FILE_DIRECTORY_FILE;
+ }
+
+ status = make_smb2_posix_create_ctx(
+ talloc_tos(), &posx, file_attributes);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ share_access, /* share_access */
+ create_disposition,/* create_disposition*/
+ create_options, /* create_options */
+ file_attributes,/* file_attributes */
+ oplock, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ ret_fsp, /* result */
+ NULL, /* pinfo */
+ posx, /* in_context */
+ NULL); /* out_context */
+
+done:
+ TALLOC_FREE(posx);
+ return status;
+}
+
+/****************************************************************************
+ Utility function to count the number of entries in a POSIX acl.
+****************************************************************************/
+
+static unsigned int count_acl_entries(connection_struct *conn, SMB_ACL_T posix_acl)
+{
+ unsigned int ace_count = 0;
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+
+ while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) {
+ entry_id = SMB_ACL_NEXT_ENTRY;
+ ace_count++;
+ }
+ return ace_count;
+}
+
+/****************************************************************************
+ Utility function to marshall a POSIX acl into wire format.
+****************************************************************************/
+
+static bool marshall_posix_acl(connection_struct *conn, char *pdata, SMB_STRUCT_STAT *pst, SMB_ACL_T posix_acl)
+{
+ int entry_id = SMB_ACL_FIRST_ENTRY;
+ SMB_ACL_ENTRY_T entry;
+
+ while ( posix_acl && (sys_acl_get_entry(posix_acl, entry_id, &entry) == 1)) {
+ SMB_ACL_TAG_T tagtype;
+ SMB_ACL_PERMSET_T permset;
+ unsigned char perms = 0;
+ unsigned int own_grp;
+
+ entry_id = SMB_ACL_NEXT_ENTRY;
+
+ if (sys_acl_get_tag_type(entry, &tagtype) == -1) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_TAG_TYPE failed.\n"));
+ return False;
+ }
+
+ if (sys_acl_get_permset(entry, &permset) == -1) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_PERMSET failed.\n"));
+ return False;
+ }
+
+ perms |= (sys_acl_get_perm(permset, SMB_ACL_READ) ? SMB_POSIX_ACL_READ : 0);
+ perms |= (sys_acl_get_perm(permset, SMB_ACL_WRITE) ? SMB_POSIX_ACL_WRITE : 0);
+ perms |= (sys_acl_get_perm(permset, SMB_ACL_EXECUTE) ? SMB_POSIX_ACL_EXECUTE : 0);
+
+ SCVAL(pdata,1,perms);
+
+ switch (tagtype) {
+ case SMB_ACL_USER_OBJ:
+ SCVAL(pdata,0,SMB_POSIX_ACL_USER_OBJ);
+ own_grp = (unsigned int)pst->st_ex_uid;
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ case SMB_ACL_USER:
+ {
+ uid_t *puid = (uid_t *)sys_acl_get_qualifier(entry);
+ if (!puid) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n"));
+ return False;
+ }
+ own_grp = (unsigned int)*puid;
+ SCVAL(pdata,0,SMB_POSIX_ACL_USER);
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ }
+ case SMB_ACL_GROUP_OBJ:
+ SCVAL(pdata,0,SMB_POSIX_ACL_GROUP_OBJ);
+ own_grp = (unsigned int)pst->st_ex_gid;
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ case SMB_ACL_GROUP:
+ {
+ gid_t *pgid= (gid_t *)sys_acl_get_qualifier(entry);
+ if (!pgid) {
+ DEBUG(0,("marshall_posix_acl: SMB_VFS_SYS_ACL_GET_QUALIFIER failed.\n"));
+ return False;
+ }
+ own_grp = (unsigned int)*pgid;
+ SCVAL(pdata,0,SMB_POSIX_ACL_GROUP);
+ SIVAL(pdata,2,own_grp);
+ SIVAL(pdata,6,0);
+ break;
+ }
+ case SMB_ACL_MASK:
+ SCVAL(pdata,0,SMB_POSIX_ACL_MASK);
+ SIVAL(pdata,2,0xFFFFFFFF);
+ SIVAL(pdata,6,0xFFFFFFFF);
+ break;
+ case SMB_ACL_OTHER:
+ SCVAL(pdata,0,SMB_POSIX_ACL_OTHER);
+ SIVAL(pdata,2,0xFFFFFFFF);
+ SIVAL(pdata,6,0xFFFFFFFF);
+ break;
+ default:
+ DEBUG(0,("marshall_posix_acl: unknown tagtype.\n"));
+ return False;
+ }
+ pdata += SMB_POSIX_ACL_ENTRY_SIZE;
+ }
+
+ return True;
+}
+#endif
+
+static NTSTATUS smb_q_posix_acl(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+#if !defined(HAVE_POSIX_ACLS)
+ return NT_STATUS_INVALID_LEVEL;
+#else
+ char *pdata = NULL;
+ SMB_ACL_T file_acl = NULL;
+ SMB_ACL_T def_acl = NULL;
+ uint16_t num_file_acls = 0;
+ uint16_t num_def_acls = 0;
+ unsigned int size_needed = 0;
+ NTSTATUS status;
+ bool ok;
+ bool close_fsp = false;
+
+ /*
+ * Ensure we always operate on a file descriptor, not just
+ * the filename.
+ */
+ if (fsp == NULL || !fsp->fsp_flags.is_fsa) {
+ uint32_t access_mask = SEC_STD_READ_CONTROL|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES;
+
+ status = get_posix_fsp(conn,
+ req,
+ smb_fname,
+ access_mask,
+ &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ close_fsp = true;
+ }
+
+ SMB_ASSERT(fsp != NULL);
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ file_acl = SMB_VFS_SYS_ACL_GET_FD(fsp, SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+
+ if (file_acl == NULL && no_acl_syscall_error(errno)) {
+ DBG_INFO("ACLs not implemented on "
+ "filesystem containing %s\n",
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_NOT_IMPLEMENTED;
+ goto out;
+ }
+
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ /*
+ * We can only have default POSIX ACLs on
+ * directories.
+ */
+ if (!fsp->fsp_flags.is_directory) {
+ DBG_INFO("Non-directory open %s\n",
+ fsp_str_dbg(fsp));
+ status = NT_STATUS_INVALID_HANDLE;
+ goto out;
+ }
+ def_acl = SMB_VFS_SYS_ACL_GET_FD(fsp,
+ SMB_ACL_TYPE_DEFAULT,
+ talloc_tos());
+ def_acl = free_empty_sys_acl(conn, def_acl);
+ }
+
+ num_file_acls = count_acl_entries(conn, file_acl);
+ num_def_acls = count_acl_entries(conn, def_acl);
+
+ /* Wrap checks. */
+ if (num_file_acls + num_def_acls < num_file_acls) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = num_file_acls + num_def_acls;
+
+ /*
+ * (size_needed * SMB_POSIX_ACL_ENTRY_SIZE) must be less
+ * than UINT_MAX, so check by division.
+ */
+ if (size_needed > (UINT_MAX/SMB_POSIX_ACL_ENTRY_SIZE)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = size_needed*SMB_POSIX_ACL_ENTRY_SIZE;
+ if (size_needed + SMB_POSIX_ACL_HEADER_SIZE < size_needed) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ size_needed += SMB_POSIX_ACL_HEADER_SIZE;
+
+ *ppdata = SMB_REALLOC(*ppdata, size_needed);
+ if (*ppdata == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ pdata = *ppdata;
+
+ SSVAL(pdata,0,SMB_POSIX_ACL_VERSION);
+ SSVAL(pdata,2,num_file_acls);
+ SSVAL(pdata,4,num_def_acls);
+ pdata += SMB_POSIX_ACL_HEADER_SIZE;
+
+ ok = marshall_posix_acl(conn,
+ pdata,
+ &fsp->fsp_name->st,
+ file_acl);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+ pdata += (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE);
+
+ ok = marshall_posix_acl(conn,
+ pdata,
+ &fsp->fsp_name->st,
+ def_acl);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ *ptotal_data = size_needed;
+ status = NT_STATUS_OK;
+
+ out:
+
+ if (close_fsp) {
+ /*
+ * Ensure the stat struct in smb_fname is up to
+ * date. Structure copy.
+ */
+ smb_fname->st = fsp->fsp_name->st;
+ (void)close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ TALLOC_FREE(file_acl);
+ TALLOC_FREE(def_acl);
+ return status;
+#endif
+}
+
+static NTSTATUS smb_q_posix_symlink(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct smb_filename *smb_fname,
+ char **ppdata,
+ int *ptotal_data)
+{
+ char buffer[PATH_MAX+1];
+ size_t needed, len;
+ int link_len;
+ char *pdata = NULL;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_name = NULL;
+ NTSTATUS status;
+
+ DBG_DEBUG("SMB_QUERY_FILE_UNIX_LINK for file %s\n",
+ smb_fname_str_dbg(smb_fname));
+
+ if (!S_ISLNK(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_DOS(ERRSRV, ERRbadlink);
+ }
+
+ status = parent_pathref(
+ talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname,
+ &parent_fname,
+ &base_name);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("parent_pathref failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ link_len = SMB_VFS_READLINKAT(
+ conn,
+ parent_fname->fsp,
+ base_name,
+ buffer,
+ sizeof(buffer)-1);
+ TALLOC_FREE(parent_fname);
+
+ if (link_len == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("READLINKAT failed: %s\n", nt_errstr(status));
+ return status;
+ }
+ if (link_len >= sizeof(buffer)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ buffer[link_len] = 0;
+
+ needed = (link_len+1)*2;
+
+ *ppdata = SMB_REALLOC(*ppdata, needed);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ status = srvstr_push(
+ pdata,
+ req->flags2,
+ pdata,
+ buffer,
+ needed,
+ STR_TERMINATE,
+ &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *ptotal_data = len;
+
+ return NT_STATUS_OK;
+}
+
+static void call_trans2qpathinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ bool delete_pending = False;
+ struct timespec write_time_ts = { .tv_sec = 0, };
+ struct files_struct *dirfsp = NULL;
+ files_struct *fsp = NULL;
+ struct file_id fileid;
+ uint32_t name_hash;
+ char *fname = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ bool info_level_handled;
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+
+ if (!params) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+
+ /* qpathinfo */
+ if (total_params < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ DBG_NOTICE("TRANSACT2_QPATHINFO: level = %d\n", info_level);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(req, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ status = filename_convert_dirfsp(req,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ /*
+ * qpathinfo must operate on an existing file, so we
+ * can exit early if filename_convert_dirfsp() returned the
+ * "new file" NT_STATUS_OK, !VALID_STAT case.
+ */
+
+ if (!VALID_STAT(smb_fname->st)) {
+ reply_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return;
+ }
+
+ /*
+ * smb_fname->fsp may be NULL if smb_fname points at a symlink
+ * and we're in POSIX context, so be careful when using fsp
+ * below, it can still be NULL.
+ */
+ fsp = smb_fname->fsp;
+
+ /* If this is a stream, check if there is a delete_pending. */
+ if ((conn->fs_capabilities & FILE_NAMED_STREAMS)
+ && is_ntfs_stream_smb_fname(smb_fname)) {
+ struct smb_filename *smb_fname_base;
+
+ /* Create an smb_filename with stream_name == NULL. */
+ smb_fname_base = synthetic_smb_fname(
+ talloc_tos(),
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags);
+ if (smb_fname_base == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ ret = vfs_stat(conn, smb_fname_base);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed "
+ "(%s)\n",
+ smb_fname_str_dbg(smb_fname_base),
+ strerror(errno));
+ TALLOC_FREE(smb_fname_base);
+ reply_nterror(req,
+ map_nt_error_from_unix(errno));
+ return;
+ }
+
+ status = file_name_hash(conn,
+ smb_fname_str_dbg(smb_fname_base),
+ &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname_base);
+ reply_nterror(req, status);
+ return;
+ }
+
+ fileid = vfs_file_id_from_sbuf(conn,
+ &smb_fname_base->st);
+ TALLOC_FREE(smb_fname_base);
+ get_file_infos(fileid, name_hash, &delete_pending, NULL);
+ if (delete_pending) {
+ reply_nterror(req, NT_STATUS_DELETE_PENDING);
+ return;
+ }
+ }
+
+ status = file_name_hash(conn,
+ smb_fname_str_dbg(smb_fname),
+ &name_hash);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ get_file_infos(fileid, name_hash, &delete_pending,
+ &write_time_ts);
+ }
+
+ if (delete_pending) {
+ reply_nterror(req, NT_STATUS_DELETE_PENDING);
+ return;
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ status = smb_q_unix_basic(
+ conn,
+ req,
+ smb_fname,
+ smb_fname->fsp,
+ ppdata,
+ &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ status = smb_q_unix_info2(
+ conn,
+ req,
+ smb_fname,
+ smb_fname->fsp,
+ ppdata,
+ &total_data);
+ break;
+
+ case SMB_QUERY_POSIX_ACL:
+ status = smb_q_posix_acl(
+ conn,
+ req,
+ smb_fname,
+ smb_fname->fsp,
+ ppdata,
+ &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_LINK:
+ status = smb_q_posix_symlink(
+ conn,
+ req,
+ smb_fname,
+ ppdata,
+ &total_data);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2qfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ total_data,
+ total_data,
+ max_data_bytes);
+ return;
+ }
+
+ call_trans2qfilepathinfo(
+ conn,
+ req,
+ TRANSACT2_QPATHINFO,
+ info_level,
+ smb_fname,
+ fsp,
+ false,
+ write_time_ts,
+ pparams,
+ total_params,
+ ppdata,
+ total_data,
+ max_data_bytes);
+}
+
+static NTSTATUS smb_q_posix_lock(
+ struct connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *fsp,
+ char **ppdata,
+ int *ptotal_data)
+{
+ char *pdata = *ppdata;
+ int total_data = *ptotal_data;
+ uint64_t count;
+ uint64_t offset;
+ uint64_t smblctx;
+ enum brl_type lock_type;
+ NTSTATUS status;
+
+ if (fsp->fsp_flags.is_pathref || (fsp_get_io_fd(fsp) == -1)) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data != POSIX_LOCK_DATA_SIZE) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (SVAL(pdata, POSIX_LOCK_TYPE_OFFSET)) {
+ case POSIX_LOCK_TYPE_READ:
+ lock_type = READ_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_WRITE:
+ lock_type = WRITE_LOCK;
+ break;
+ case POSIX_LOCK_TYPE_UNLOCK:
+ default:
+ /* There's no point in asking for an unlock... */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ smblctx = (uint64_t)IVAL(pdata, POSIX_LOCK_PID_OFFSET);
+ offset = BVAL(pdata,POSIX_LOCK_START_OFFSET);
+ count = BVAL(pdata,POSIX_LOCK_LEN_OFFSET);
+
+ status = query_lock(
+ fsp,
+ &smblctx,
+ &count,
+ &offset,
+ &lock_type,
+ POSIX_LOCK);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * For success we just return a copy of what we sent
+ * with the lock type set to POSIX_LOCK_TYPE_UNLOCK.
+ */
+ SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, POSIX_LOCK_TYPE_UNLOCK);
+ return NT_STATUS_OK;
+ }
+
+ if (!ERROR_WAS_LOCK_DENIED(status)) {
+ DBG_DEBUG("query_lock() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * Here we need to report who has it locked.
+ */
+
+ SSVAL(pdata, POSIX_LOCK_TYPE_OFFSET, lock_type);
+ SSVAL(pdata, POSIX_LOCK_FLAGS_OFFSET, 0);
+ SIVAL(pdata, POSIX_LOCK_PID_OFFSET, (uint32_t)smblctx);
+ SBVAL(pdata, POSIX_LOCK_START_OFFSET, offset);
+ SBVAL(pdata, POSIX_LOCK_LEN_OFFSET, count);
+
+ return NT_STATUS_OK;
+}
+
+static void call_trans2qfileinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ bool delete_pending = False;
+ struct timespec write_time_ts = { .tv_sec = 0, };
+ files_struct *fsp = NULL;
+ struct file_id fileid;
+ bool info_level_handled;
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+
+ if (params == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (total_params < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params,0));
+ info_level = SVAL(params,2);
+
+ if (IS_IPC(conn)) {
+ call_trans2qpipeinfo(
+ conn,
+ req,
+ fsp,
+ info_level,
+ TRANSACT2_QFILEINFO,
+ pparams,
+ total_params,
+ ppdata,
+ total_data,
+ max_data_bytes);
+ return;
+ }
+
+ DBG_NOTICE("TRANSACT2_QFILEINFO: level = %d\n", info_level);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ /* Initial check for valid fsp ptr. */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+
+ smb_fname = fsp->fsp_name;
+
+ if(fsp->fake_file_handle) {
+ /*
+ * This is actually for the QUOTA_FAKE_FILE --metze
+ */
+
+ /* We know this name is ok, it's already passed the checks. */
+
+ } else if(fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a QFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ ret = vfs_stat(conn, smb_fname);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno));
+ reply_nterror(req,
+ map_nt_error_from_unix(errno));
+ return;
+ }
+
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &smb_fname->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("fstat of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp), nt_errstr(status)));
+ reply_nterror(req, status);
+ return;
+ }
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &smb_fname->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_QUERY_POSIX_LOCK:
+ status = smb_q_posix_lock(conn, req, fsp, ppdata, &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ status = smb_q_unix_basic(
+ conn, req, fsp->fsp_name, fsp, ppdata, &total_data);
+ break;
+
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ status = smb_q_unix_info2(
+ conn, req, fsp->fsp_name, fsp, ppdata, &total_data);
+ break;
+
+ case SMB_QUERY_POSIX_ACL:
+ status = smb_q_posix_acl(
+ conn, req, fsp->fsp_name, fsp, ppdata, &total_data);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2qfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ total_data,
+ total_data,
+ max_data_bytes);
+ return;
+ }
+
+ call_trans2qfilepathinfo(
+ conn,
+ req,
+ TRANSACT2_QFILEINFO,
+ info_level,
+ smb_fname,
+ fsp,
+ delete_pending,
+ write_time_ts,
+ pparams,
+ total_params,
+ ppdata,
+ total_data,
+ max_data_bytes);
+}
+
+static void handle_trans2setfilepathinfo_result(
+ connection_struct *conn,
+ struct smb_request *req,
+ uint16_t info_level,
+ NTSTATUS status,
+ char *pdata,
+ int data_return_size,
+ unsigned int max_data_bytes)
+{
+ char params[2] = { 0, 0, };
+
+ if (NT_STATUS_IS_OK(status)) {
+ send_trans2_replies(
+ conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ 2,
+ pdata,
+ data_return_size,
+ max_data_bytes);
+ return;
+ }
+
+ if (open_was_deferred(req->xconn, req->mid)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ bool ok = defer_smb1_sharing_violation(req);
+ if (ok) {
+ return;
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_EVENT_PENDING)) {
+ /* We have re-scheduled this call. */
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(
+ req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV,
+ ERRbadpath);
+ return;
+ }
+
+ if (info_level == SMB_POSIX_PATH_OPEN) {
+ reply_openerror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, STATUS_INVALID_EA_NAME)) {
+ /*
+ * Invalid EA name needs to return 2 param bytes,
+ * not a zero-length error packet.
+ */
+
+ send_trans2_replies(
+ conn,
+ req,
+ status,
+ params,
+ 2,
+ NULL,
+ 0,
+ max_data_bytes);
+ return;
+ }
+
+ reply_nterror(req, status);
+}
+
+/****************************************************************************
+ Create a directory with POSIX semantics.
+****************************************************************************/
+
+static NTSTATUS smb_posix_mkdir(connection_struct *conn,
+ struct smb_request *req,
+ char **ppdata,
+ int total_data,
+ struct smb_filename *smb_fname,
+ int *pdata_return_size)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ uint32_t raw_unixmode = 0;
+ mode_t unixmode = (mode_t)0;
+ files_struct *fsp = NULL;
+ uint16_t info_level_return = 0;
+ int info;
+ char *pdata = *ppdata;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (total_data < 18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ raw_unixmode = IVAL(pdata,8);
+ /* Next 4 bytes are not yet defined. */
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ PERM_NEW_DIR, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ DEBUG(10,("smb_posix_mkdir: file %s, mode 0%o\n",
+ smb_fname_str_dbg(smb_fname), (unsigned int)unixmode));
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_READ_ATTRIBUTES, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ 0, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+
+ info_level_return = SVAL(pdata,16);
+
+ if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE;
+ } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE;
+ } else {
+ *pdata_return_size = 12;
+ }
+
+ /* Realloc the data size */
+ *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size);
+ if (*ppdata == NULL) {
+ *pdata_return_size = 0;
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ SSVAL(pdata,0,NO_OPLOCK_RETURN);
+ SSVAL(pdata,2,0); /* No fnum. */
+ SIVAL(pdata,4,info); /* Was directory created. */
+
+ switch (info_level_return) {
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC);
+ SSVAL(pdata,10,0); /* Padding. */
+ store_file_unix_basic(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2);
+ SSVAL(pdata,10,0); /* Padding. */
+ store_file_unix_basic_info2(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ default:
+ SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED);
+ SSVAL(pdata,10,0); /* Padding. */
+ break;
+ }
+
+ return status;
+}
+
+/****************************************************************************
+ Open/Create a file with POSIX semantics.
+****************************************************************************/
+
+#define SMB_O_RDONLY_MAPPING (FILE_READ_DATA|FILE_READ_ATTRIBUTES|FILE_READ_EA)
+#define SMB_O_WRONLY_MAPPING (FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA)
+
+static NTSTATUS smb_posix_open(connection_struct *conn,
+ struct smb_request *req,
+ char **ppdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ int *pdata_return_size)
+{
+ bool extended_oplock_granted = False;
+ char *pdata = *ppdata;
+ uint32_t flags = 0;
+ uint32_t wire_open_mode = 0;
+ uint32_t raw_unixmode = 0;
+ uint32_t attributes = 0;
+ uint32_t create_disp = 0;
+ uint32_t access_mask = 0;
+ uint32_t create_options = FILE_NON_DIRECTORY_FILE;
+ NTSTATUS status = NT_STATUS_OK;
+ mode_t unixmode = (mode_t)0;
+ files_struct *fsp = NULL;
+ int oplock_request = 0;
+ int info = 0;
+ uint16_t info_level_return = 0;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (total_data < 18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = IVAL(pdata,0);
+ oplock_request = (flags & REQUEST_OPLOCK) ? EXCLUSIVE_OPLOCK : 0;
+ if (oplock_request) {
+ oplock_request |= (flags & REQUEST_BATCH_OPLOCK) ? BATCH_OPLOCK : 0;
+ }
+
+ wire_open_mode = IVAL(pdata,4);
+
+ if (wire_open_mode == (SMB_O_CREAT|SMB_O_DIRECTORY)) {
+ return smb_posix_mkdir(conn, req,
+ ppdata,
+ total_data,
+ smb_fname,
+ pdata_return_size);
+ }
+
+ switch (wire_open_mode & SMB_ACCMODE) {
+ case SMB_O_RDONLY:
+ access_mask = SMB_O_RDONLY_MAPPING;
+ break;
+ case SMB_O_WRONLY:
+ access_mask = SMB_O_WRONLY_MAPPING;
+ break;
+ case SMB_O_RDWR:
+ access_mask = (SMB_O_RDONLY_MAPPING|
+ SMB_O_WRONLY_MAPPING);
+ break;
+ default:
+ DEBUG(5,("smb_posix_open: invalid open mode 0x%x\n",
+ (unsigned int)wire_open_mode ));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ wire_open_mode &= ~SMB_ACCMODE;
+
+ /* First take care of O_CREAT|O_EXCL interactions. */
+ switch (wire_open_mode & (SMB_O_CREAT | SMB_O_EXCL)) {
+ case (SMB_O_CREAT | SMB_O_EXCL):
+ /* File exists fail. File not exist create. */
+ create_disp = FILE_CREATE;
+ break;
+ case SMB_O_CREAT:
+ /* File exists open. File not exist create. */
+ create_disp = FILE_OPEN_IF;
+ break;
+ case SMB_O_EXCL:
+ /* O_EXCL on its own without O_CREAT is undefined.
+ We deliberately ignore it as some versions of
+ Linux CIFSFS can send a bare O_EXCL on the
+ wire which other filesystems in the kernel
+ ignore. See bug 9519 for details. */
+
+ /* Fallthrough. */
+
+ case 0:
+ /* File exists open. File not exist fail. */
+ create_disp = FILE_OPEN;
+ break;
+ default:
+ DEBUG(5,("smb_posix_open: invalid create mode 0x%x\n",
+ (unsigned int)wire_open_mode ));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Next factor in the effects of O_TRUNC. */
+ wire_open_mode &= ~(SMB_O_CREAT | SMB_O_EXCL);
+
+ if (wire_open_mode & SMB_O_TRUNC) {
+ switch (create_disp) {
+ case FILE_CREATE:
+ /* (SMB_O_CREAT | SMB_O_EXCL | O_TRUNC) */
+ /* Leave create_disp alone as
+ (O_CREAT|O_EXCL|O_TRUNC) == (O_CREAT|O_EXCL)
+ */
+ /* File exists fail. File not exist create. */
+ break;
+ case FILE_OPEN_IF:
+ /* SMB_O_CREAT | SMB_O_TRUNC */
+ /* File exists overwrite. File not exist create. */
+ create_disp = FILE_OVERWRITE_IF;
+ break;
+ case FILE_OPEN:
+ /* SMB_O_TRUNC */
+ /* File exists overwrite. File not exist fail. */
+ create_disp = FILE_OVERWRITE;
+ break;
+ default:
+ /* Cannot get here. */
+ smb_panic("smb_posix_open: logic error");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ raw_unixmode = IVAL(pdata,8);
+ /* Next 4 bytes are not yet defined. */
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ (VALID_STAT(smb_fname->st) ?
+ PERM_EXISTING_FILE : PERM_NEW_FILE),
+ &unixmode);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (wire_open_mode & SMB_O_SYNC) {
+ create_options |= FILE_WRITE_THROUGH;
+ }
+ if (wire_open_mode & SMB_O_APPEND) {
+ access_mask |= FILE_APPEND_DATA;
+ }
+ if (wire_open_mode & SMB_O_DIRECT) {
+ /*
+ * BUG: this doesn't work anymore since
+ * e0814dc5082dd4ecca8a155e0ce24b073158fd92. But since
+ * FILE_FLAG_NO_BUFFERING isn't used at all in the IO codepath,
+ * it doesn't really matter.
+ */
+ attributes |= FILE_FLAG_NO_BUFFERING;
+ }
+
+ if ((wire_open_mode & SMB_O_DIRECTORY) ||
+ VALID_STAT_OF_DIR(smb_fname->st)) {
+ if (access_mask != SMB_O_RDONLY_MAPPING) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ create_options &= ~FILE_NON_DIRECTORY_FILE;
+ create_options |= FILE_DIRECTORY_FILE;
+ }
+
+ DEBUG(10,("smb_posix_open: file %s, smb_posix_flags = %u, mode 0%o\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)wire_open_mode,
+ (unsigned int)unixmode ));
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ access_mask, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ create_disp, /* create_disposition*/
+ create_options, /* create_options */
+ attributes, /* file_attributes */
+ oplock_request, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (oplock_request && lp_fake_oplocks(SNUM(conn))) {
+ extended_oplock_granted = True;
+ }
+
+ if(oplock_request && EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ extended_oplock_granted = True;
+ }
+
+ info_level_return = SVAL(pdata,16);
+
+ /* Allocate the correct return size. */
+
+ if (info_level_return == SMB_QUERY_FILE_UNIX_BASIC) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_BASIC_SIZE;
+ } else if (info_level_return == SMB_QUERY_FILE_UNIX_INFO2) {
+ *pdata_return_size = 12 + SMB_FILE_UNIX_INFO2_SIZE;
+ } else {
+ *pdata_return_size = 12;
+ }
+
+ /* Realloc the data size */
+ *ppdata = (char *)SMB_REALLOC(*ppdata,*pdata_return_size);
+ if (*ppdata == NULL) {
+ close_file_free(req, &fsp, ERROR_CLOSE);
+ *pdata_return_size = 0;
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+
+ if (extended_oplock_granted) {
+ if (flags & REQUEST_BATCH_OPLOCK) {
+ SSVAL(pdata,0, BATCH_OPLOCK_RETURN);
+ } else {
+ SSVAL(pdata,0, EXCLUSIVE_OPLOCK_RETURN);
+ }
+ } else if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ SSVAL(pdata,0, LEVEL_II_OPLOCK_RETURN);
+ } else {
+ SSVAL(pdata,0,NO_OPLOCK_RETURN);
+ }
+
+ SSVAL(pdata,2,fsp->fnum);
+ SIVAL(pdata,4,info); /* Was file created etc. */
+
+ switch (info_level_return) {
+ case SMB_QUERY_FILE_UNIX_BASIC:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_BASIC);
+ SSVAL(pdata,10,0); /* padding. */
+ store_file_unix_basic(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ case SMB_QUERY_FILE_UNIX_INFO2:
+ SSVAL(pdata,8,SMB_QUERY_FILE_UNIX_INFO2);
+ SSVAL(pdata,10,0); /* padding. */
+ store_file_unix_basic_info2(conn, pdata + 12, fsp,
+ &smb_fname->st);
+ break;
+ default:
+ SSVAL(pdata,8,SMB_NO_INFO_LEVEL_RETURNED);
+ SSVAL(pdata,10,0); /* padding. */
+ break;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Delete a file with POSIX semantics.
+****************************************************************************/
+
+struct smb_posix_unlink_state {
+ struct smb_filename *smb_fname;
+ struct files_struct *fsp;
+ NTSTATUS status;
+};
+
+static void smb_posix_unlink_locked(struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct smb_posix_unlink_state *state = private_data;
+ char del = 1;
+ bool other_nonposix_opens;
+
+ other_nonposix_opens = has_other_nonposix_opens(lck, state->fsp);
+ if (other_nonposix_opens) {
+ /* Fail with sharing violation. */
+ state->status = NT_STATUS_SHARING_VIOLATION;
+ return;
+ }
+
+ /*
+ * Set the delete on close.
+ */
+ state->status = smb_set_file_disposition_info(state->fsp->conn,
+ &del,
+ 1,
+ state->fsp,
+ state->smb_fname);
+}
+
+static NTSTATUS smb_posix_unlink(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname)
+{
+ struct smb_posix_unlink_state state = {};
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *fsp = NULL;
+ uint16_t flags = 0;
+ int info = 0;
+ int create_options = FILE_OPEN_REPARSE_POINT;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ if (total_data < 2) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ flags = SVAL(pdata,0);
+
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if ((flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) &&
+ !VALID_STAT_OF_DIR(smb_fname->st)) {
+ return NT_STATUS_NOT_A_DIRECTORY;
+ }
+
+ DEBUG(10,("smb_posix_unlink: %s %s\n",
+ (flags == SMB_POSIX_UNLINK_DIRECTORY_TARGET) ? "directory" : "file",
+ smb_fname_str_dbg(smb_fname)));
+
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ create_options |= FILE_DIRECTORY_FILE;
+ }
+
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ create_options, /* create_options */
+ 0, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ &info, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Don't lie to client. If we can't really delete due to
+ * non-POSIX opens return SHARING_VIOLATION.
+ */
+
+ state = (struct smb_posix_unlink_state) {
+ .smb_fname = smb_fname,
+ .fsp = fsp,
+ };
+
+ status = share_mode_do_locked_vfs_allowed(fsp->file_id,
+ smb_posix_unlink_locked,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("share_mode_do_locked_vfs_allowed(%s) failed - %s\n",
+ fsp_str_dbg(fsp), nt_errstr(status));
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = state.status;
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return status;
+ }
+ return close_file_free(req, &fsp, NORMAL_CLOSE);
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_LINK (create a UNIX symlink).
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_link(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct smb_filename *new_smb_fname)
+{
+ char *link_target = NULL;
+ struct smb_filename target_fname;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status;
+ int ret;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *base_name = NULL;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ /* Set a symbolic link. */
+ /* Don't allow this if follow links is false. */
+
+ if (total_data == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_follow_symlinks(SNUM(conn))) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ srvstr_pull_talloc(ctx, pdata, req->flags2, &link_target, pdata,
+ total_data, STR_TERMINATE);
+
+ if (!link_target) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ target_fname = (struct smb_filename) {
+ .base_name = link_target,
+ };
+
+ /* Removes @GMT tokens if any */
+ status = canonicalize_snapshot_path(&target_fname, UCF_GMT_PATHNAME, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_link: SMB_SET_FILE_UNIX_LINK doing symlink %s -> %s\n",
+ new_smb_fname->base_name, link_target ));
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ new_smb_fname,
+ &parent_fname,
+ &base_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = SMB_VFS_SYMLINKAT(conn,
+ &target_fname,
+ parent_fname->fsp,
+ base_name);
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ TALLOC_FREE(parent_fname);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_HLINK (create a UNIX hard link).
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_hlink(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata, int total_data,
+ struct smb_filename *smb_fname_new)
+{
+ char *oldname = NULL;
+ struct files_struct *src_dirfsp = NULL;
+ struct smb_filename *smb_fname_old = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME old_twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ /* Set a hard link. */
+ if (total_data == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ pdata,
+ req->flags2,
+ &oldname,
+ pdata,
+ total_data,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ pdata,
+ req->flags2,
+ &oldname,
+ pdata,
+ total_data,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_hlink: SMB_SET_FILE_UNIX_LINK doing hard link %s -> %s\n",
+ smb_fname_str_dbg(smb_fname_new), oldname));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(oldname, &old_twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &oldname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ oldname,
+ ucf_flags,
+ old_twrp,
+ &src_dirfsp,
+ &smb_fname_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return hardlink_internals(ctx,
+ conn,
+ req,
+ false,
+ smb_fname_old,
+ smb_fname_new);
+}
+
+/****************************************************************************
+ Allow a UNIX info mknod.
+****************************************************************************/
+
+static NTSTATUS smb_unix_mknod(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ uint32_t file_type = IVAL(pdata,56);
+#if defined(HAVE_MAKEDEV)
+ uint32_t dev_major = IVAL(pdata,60);
+ uint32_t dev_minor = IVAL(pdata,68);
+#endif
+ SMB_DEV_T dev = (SMB_DEV_T)0;
+ uint32_t raw_unixmode = IVAL(pdata,84);
+ NTSTATUS status;
+ mode_t unixmode;
+ int ret;
+ struct smb_filename *parent_fname = NULL;
+ struct smb_filename *atname = NULL;
+
+ if (total_data < 100) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ PERM_NEW_FILE, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+#if defined(HAVE_MAKEDEV)
+ dev = makedev(dev_major, dev_minor);
+#endif
+
+ switch (file_type) {
+ /* We can't create other objects here. */
+ case UNIX_TYPE_FILE:
+ case UNIX_TYPE_DIR:
+ case UNIX_TYPE_SYMLINK:
+ return NT_STATUS_ACCESS_DENIED;
+#if defined(S_IFIFO)
+ case UNIX_TYPE_FIFO:
+ unixmode |= S_IFIFO;
+ break;
+#endif
+#if defined(S_IFSOCK)
+ case UNIX_TYPE_SOCKET:
+ unixmode |= S_IFSOCK;
+ break;
+#endif
+#if defined(S_IFCHR)
+ case UNIX_TYPE_CHARDEV:
+ /* This is only allowed for root. */
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ unixmode |= S_IFCHR;
+ break;
+#endif
+#if defined(S_IFBLK)
+ case UNIX_TYPE_BLKDEV:
+ if (get_current_uid(conn) != sec_initial_uid()) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ unixmode |= S_IFBLK;
+ break;
+#endif
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10,("smb_unix_mknod: SMB_SET_FILE_UNIX_BASIC doing mknod dev "
+ "%.0f mode 0%o for file %s\n", (double)dev,
+ (unsigned int)unixmode, smb_fname_str_dbg(smb_fname)));
+
+ status = SMB_VFS_PARENT_PATHNAME(dirfsp->conn,
+ talloc_tos(),
+ smb_fname,
+ &parent_fname,
+ &atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Ok - do the mknod. */
+ ret = SMB_VFS_MKNODAT(conn,
+ dirfsp,
+ atname,
+ unixmode,
+ dev);
+
+ if (ret != 0) {
+ TALLOC_FREE(parent_fname);
+ return map_nt_error_from_unix(errno);
+ }
+
+ /* If any of the other "set" calls fail we
+ * don't want to end up with a half-constructed mknod.
+ */
+
+ if (lp_inherit_permissions(SNUM(conn))) {
+ inherit_access_posix_acl(conn,
+ dirfsp,
+ smb_fname,
+ unixmode);
+ }
+ TALLOC_FREE(parent_fname);
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_BASIC.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_basic(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ struct smb_file_time ft;
+ uint32_t raw_unixmode;
+ mode_t unixmode;
+ off_t size = 0;
+ uid_t set_owner = (uid_t)SMB_UID_NO_CHANGE;
+ gid_t set_grp = (uid_t)SMB_GID_NO_CHANGE;
+ NTSTATUS status = NT_STATUS_OK;
+ enum perm_type ptype;
+ files_struct *all_fsps = NULL;
+ bool modify_mtime = true;
+ struct file_id id;
+ SMB_STRUCT_STAT sbuf;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ init_smb_file_time(&ft);
+
+ if (total_data < 100) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if(IVAL(pdata, 0) != SMB_SIZE_NO_CHANGE_LO &&
+ IVAL(pdata, 4) != SMB_SIZE_NO_CHANGE_HI) {
+ size=IVAL(pdata,0); /* first 8 Bytes are size */
+ size |= (((off_t)IVAL(pdata,4)) << 32);
+ }
+
+ ft.atime = pull_long_date_full_timespec(pdata+24); /* access_time */
+ ft.mtime = pull_long_date_full_timespec(pdata+32); /* modification_time */
+ set_owner = (uid_t)IVAL(pdata,40);
+ set_grp = (gid_t)IVAL(pdata,48);
+ raw_unixmode = IVAL(pdata,84);
+
+ if (VALID_STAT(smb_fname->st)) {
+ if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ ptype = PERM_EXISTING_DIR;
+ } else {
+ ptype = PERM_EXISTING_FILE;
+ }
+ } else {
+ ptype = PERM_NEW_FILE;
+ }
+
+ status = unix_perms_from_wire(conn, &smb_fname->st, raw_unixmode,
+ ptype, &unixmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC: name = "
+ "%s size = %.0f, uid = %u, gid = %u, raw perms = 0%o\n",
+ smb_fname_str_dbg(smb_fname), (double)size,
+ (unsigned int)set_owner, (unsigned int)set_grp,
+ (int)raw_unixmode));
+
+ sbuf = smb_fname->st;
+
+ if (!VALID_STAT(sbuf)) {
+ /*
+ * The only valid use of this is to create character and block
+ * devices, and named pipes. This is deprecated (IMHO) and
+ * a new info level should be used for mknod. JRA.
+ */
+
+ if (dirfsp == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return smb_unix_mknod(conn,
+ pdata,
+ total_data,
+ dirfsp,
+ smb_fname);
+ }
+
+#if 1
+ /* Horrible backwards compatibility hack as an old server bug
+ * allowed a CIFS client bug to remain unnoticed :-(. JRA.
+ * */
+
+ if (!size) {
+ size = get_file_size_stat(&sbuf);
+ }
+#endif
+
+ /*
+ * Deal with the UNIX specific mode set.
+ */
+
+ if (raw_unixmode != SMB_MODE_NO_CHANGE) {
+ int ret;
+
+ if (fsp == NULL || S_ISLNK(smb_fname->st.st_ex_mode)) {
+ DBG_WARNING("Can't set mode on symlink %s\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC "
+ "setting mode 0%o for file %s\n",
+ (unsigned int)unixmode,
+ smb_fname_str_dbg(smb_fname)));
+ ret = SMB_VFS_FCHMOD(fsp, unixmode);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ /*
+ * Deal with the UNIX specific uid set.
+ */
+
+ if ((set_owner != (uid_t)SMB_UID_NO_CHANGE) &&
+ (sbuf.st_ex_uid != set_owner)) {
+ int ret;
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC "
+ "changing owner %u for path %s\n",
+ (unsigned int)set_owner,
+ smb_fname_str_dbg(smb_fname)));
+
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ ret = SMB_VFS_FCHOWN(fsp, set_owner, (gid_t)-1);
+ } else {
+ /*
+ * UNIX extensions calls must always operate
+ * on symlinks.
+ */
+ ret = SMB_VFS_LCHOWN(conn, smb_fname,
+ set_owner, (gid_t)-1);
+ }
+
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ return status;
+ }
+ }
+
+ /*
+ * Deal with the UNIX specific gid set.
+ */
+
+ if ((set_grp != (uid_t)SMB_GID_NO_CHANGE) &&
+ (sbuf.st_ex_gid != set_grp)) {
+ int ret;
+
+ DEBUG(10,("smb_set_file_unix_basic: SMB_SET_FILE_UNIX_BASIC "
+ "changing group %u for file %s\n",
+ (unsigned int)set_grp,
+ smb_fname_str_dbg(smb_fname)));
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ ret = SMB_VFS_FCHOWN(fsp, (uid_t)-1, set_grp);
+ } else {
+ /*
+ * UNIX extensions calls must always operate
+ * on symlinks.
+ */
+ ret = SMB_VFS_LCHOWN(conn, smb_fname, (uid_t)-1,
+ set_grp);
+ }
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ return status;
+ }
+ }
+
+ /* Deal with any size changes. */
+
+ if (S_ISREG(sbuf.st_ex_mode)) {
+ status = smb_set_file_size(conn, req,
+ fsp,
+ smb_fname,
+ &sbuf,
+ size,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* Deal with any time changes. */
+ if (is_omit_timespec(&ft.mtime) && is_omit_timespec(&ft.atime)) {
+ /* No change, don't cancel anything. */
+ return status;
+ }
+
+ id = vfs_file_id_from_sbuf(conn, &sbuf);
+ for(all_fsps = file_find_di_first(conn->sconn, id, true); all_fsps;
+ all_fsps = file_find_di_next(all_fsps, true)) {
+ /*
+ * We're setting the time explicitly for UNIX.
+ * Cancel any pending changes over all handles.
+ */
+ all_fsps->fsp_flags.update_write_time_on_close = false;
+ TALLOC_FREE(all_fsps->update_write_time_event);
+ }
+
+ /*
+ * Override the "setting_write_time"
+ * parameter here as it almost does what
+ * we need. Just remember if we modified
+ * mtime and send the notify ourselves.
+ */
+ if (is_omit_timespec(&ft.mtime)) {
+ modify_mtime = false;
+ }
+
+ status = smb_set_file_time(conn,
+ fsp,
+ smb_fname,
+ &ft,
+ false);
+ if (modify_mtime) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_LAST_WRITE, smb_fname->base_name);
+ }
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_UNIX_INFO2.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_unix_info2(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ struct files_struct *dirfsp,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ NTSTATUS status;
+ uint32_t smb_fflags;
+ uint32_t smb_fmask;
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_DOS(ERRSRV, ERRaccess);
+ }
+
+ if (total_data < 116) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Start by setting all the fields that are common between UNIX_BASIC
+ * and UNIX_INFO2.
+ */
+ status = smb_set_file_unix_basic(conn,
+ req,
+ pdata,
+ total_data,
+ dirfsp,
+ fsp,
+ smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ smb_fflags = IVAL(pdata, 108);
+ smb_fmask = IVAL(pdata, 112);
+
+ /* NB: We should only attempt to alter the file flags if the client
+ * sends a non-zero mask.
+ */
+ if (smb_fmask != 0) {
+ int stat_fflags = 0;
+
+ if (!map_info2_flags_to_sbuf(&smb_fname->st, smb_fflags,
+ smb_fmask, &stat_fflags)) {
+ /* Client asked to alter a flag we don't understand. */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL || S_ISLNK(smb_fname->st.st_ex_mode)) {
+ DBG_WARNING("Can't change flags on symlink %s\n",
+ smb_fname_str_dbg(smb_fname));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ if (SMB_VFS_FCHFLAGS(fsp, stat_fflags) != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+
+ /* XXX: need to add support for changing the create_time here. You
+ * can do this for paths on Darwin with setattrlist(2). The right way
+ * to hook this up is probably by extending the VFS utimes interface.
+ */
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_POSIX_ACL.
+****************************************************************************/
+
+static NTSTATUS smb_set_posix_acl(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data_in,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+#if !defined(HAVE_POSIX_ACLS)
+ return NT_STATUS_INVALID_LEVEL;
+#else
+ uint16_t posix_acl_version;
+ uint16_t num_file_acls;
+ uint16_t num_def_acls;
+ bool valid_file_acls = true;
+ bool valid_def_acls = true;
+ NTSTATUS status;
+ unsigned int size_needed;
+ unsigned int total_data;
+ bool close_fsp = false;
+
+ if (total_data_in < 0) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ total_data = total_data_in;
+
+ if (total_data < SMB_POSIX_ACL_HEADER_SIZE) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ posix_acl_version = SVAL(pdata,0);
+ num_file_acls = SVAL(pdata,2);
+ num_def_acls = SVAL(pdata,4);
+
+ if (num_file_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) {
+ valid_file_acls = false;
+ num_file_acls = 0;
+ }
+
+ if (num_def_acls == SMB_POSIX_IGNORE_ACE_ENTRIES) {
+ valid_def_acls = false;
+ num_def_acls = 0;
+ }
+
+ if (posix_acl_version != SMB_POSIX_ACL_VERSION) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /* Wrap checks. */
+ if (num_file_acls + num_def_acls < num_file_acls) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = num_file_acls + num_def_acls;
+
+ /*
+ * (size_needed * SMB_POSIX_ACL_ENTRY_SIZE) must be less
+ * than UINT_MAX, so check by division.
+ */
+ if (size_needed > (UINT_MAX/SMB_POSIX_ACL_ENTRY_SIZE)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ size_needed = size_needed*SMB_POSIX_ACL_ENTRY_SIZE;
+ if (size_needed + SMB_POSIX_ACL_HEADER_SIZE < size_needed) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ size_needed += SMB_POSIX_ACL_HEADER_SIZE;
+
+ if (total_data < size_needed) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ /*
+ * Ensure we always operate on a file descriptor, not just
+ * the filename.
+ */
+ if (fsp == NULL || !fsp->fsp_flags.is_fsa) {
+ uint32_t access_mask = SEC_STD_WRITE_OWNER|
+ SEC_STD_WRITE_DAC|
+ SEC_STD_READ_CONTROL|
+ FILE_READ_ATTRIBUTES|
+ FILE_WRITE_ATTRIBUTES;
+
+ status = get_posix_fsp(conn,
+ req,
+ smb_fname,
+ access_mask,
+ &fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ close_fsp = true;
+ }
+
+ /* Here we know fsp != NULL */
+ SMB_ASSERT(fsp != NULL);
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /* If we have a default acl, this *must* be a directory. */
+ if (valid_def_acls && !fsp->fsp_flags.is_directory) {
+ DBG_INFO("Can't set default acls on "
+ "non-directory %s\n",
+ fsp_str_dbg(fsp));
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ DBG_DEBUG("file %s num_file_acls = %"PRIu16", "
+ "num_def_acls = %"PRIu16"\n",
+ fsp_str_dbg(fsp),
+ num_file_acls,
+ num_def_acls);
+
+ /* Move pdata to the start of the file ACL entries. */
+ pdata += SMB_POSIX_ACL_HEADER_SIZE;
+
+ if (valid_file_acls) {
+ status = set_unix_posix_acl(conn,
+ fsp,
+ num_file_acls,
+ pdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ /* Move pdata to the start of the default ACL entries. */
+ pdata += (num_file_acls*SMB_POSIX_ACL_ENTRY_SIZE);
+
+ if (valid_def_acls) {
+ status = set_unix_posix_default_acl(conn,
+ fsp,
+ num_def_acls,
+ pdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+ out:
+
+ if (close_fsp) {
+ (void)close_file_free(req, &fsp, NORMAL_CLOSE);
+ }
+ return status;
+#endif
+}
+
+static void call_trans2setpathinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct files_struct *fsp = NULL;
+ char *params = *pparams;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ char *fname = NULL;
+ bool info_level_handled;
+ int data_return_size = 0;
+ NTSTATUS status;
+
+ if (params == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /* set path info */
+ if (total_params < 7) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,0);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(req,
+ params,
+ req->flags2,
+ &fname,
+ &params[6],
+ total_params - 6,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ DBG_NOTICE("fname=%s info_level=%d totdata=%d\n",
+ fname,
+ info_level,
+ total_data);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(fname, &twrp);
+ }
+ status = smb1_strip_dfs_path(req, &ucf_flags, &fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ status = filename_convert_dirfsp(req,
+ conn,
+ fname,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_POSIX_PATH_OPEN:
+ status = smb_posix_open(conn,
+ req,
+ ppdata,
+ total_data,
+ dirfsp,
+ smb_fname,
+ &data_return_size);
+ break;
+
+ case SMB_POSIX_PATH_UNLINK:
+ status = smb_posix_unlink(conn,
+ req,
+ *ppdata,
+ total_data,
+ dirfsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_LINK:
+ status = smb_set_file_unix_link(
+ conn, req, *ppdata, total_data, smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_HLINK:
+ status = smb_set_file_unix_hlink(
+ conn, req, *ppdata, total_data, smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_BASIC:
+ status = smb_set_file_unix_basic(conn,
+ req,
+ *ppdata,
+ total_data,
+ dirfsp,
+ smb_fname->fsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_INFO2:
+ status = smb_set_file_unix_info2(conn,
+ req,
+ *ppdata,
+ total_data,
+ dirfsp,
+ smb_fname->fsp,
+ smb_fname);
+ break;
+ case SMB_SET_POSIX_ACL:
+ status = smb_set_posix_acl(
+ conn, req, *ppdata, total_data, NULL, smb_fname);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+ return;
+ }
+
+ /*
+ * smb_fname->fsp may be NULL if smb_fname points at a symlink
+ * and we're in POSIX context, so be careful when using fsp
+ * below, it can still be NULL.
+ */
+ fsp = smb_fname->fsp;
+
+ status = smbd_do_setfilepathinfo(
+ conn,
+ req,
+ req,
+ info_level,
+ fsp,
+ smb_fname,
+ ppdata,
+ total_data,
+ &data_return_size);
+
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+}
+
+static void call_trans2setfileinfo(
+ connection_struct *conn,
+ struct smb_request *req,
+ char **pparams,
+ int total_params,
+ char **ppdata,
+ int total_data,
+ unsigned int max_data_bytes)
+{
+ char *pdata = *ppdata;
+ uint16_t info_level;
+ struct smb_filename *smb_fname = NULL;
+ struct files_struct *fsp = NULL;
+ char *params = *pparams;
+ int data_return_size = 0;
+ bool info_level_handled;
+ NTSTATUS status;
+ int ret;
+
+ if (params == NULL) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ if (total_params < 4) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ fsp = file_fsp(req, SVAL(params,0));
+ /* Basic check for non-null fsp. */
+ if (!check_fsp_open(conn, req, fsp)) {
+ return;
+ }
+ info_level = SVAL(params,2);
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ if (!lp_smb1_unix_extensions()) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ if (!req->posix_pathnames) {
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+ }
+
+ smb_fname = fsp->fsp_name;
+
+ DBG_NOTICE("fnum=%s fname=%s info_level=%d totdata=%d\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ info_level,
+ total_data);
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a SETFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ ret = vfs_stat(conn, smb_fname);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed (%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ strerror(errno));
+ reply_nterror(req, map_nt_error_from_unix(errno));
+ return;
+ }
+ } else if (fsp->print_file) {
+ /*
+ * Doing a DELETE_ON_CLOSE should cancel a print job.
+ */
+ if ((info_level == SMB_SET_FILE_DISPOSITION_INFO) &&
+ CVAL(pdata,0)) {
+
+ fsp->fsp_flags.delete_on_close = true;
+
+ DBG_NOTICE("Cancelling print job (%s)\n",
+ fsp_str_dbg(fsp));
+
+ SSVAL(params,0,0);
+ send_trans2_replies(
+ conn,
+ req,
+ NT_STATUS_OK,
+ params,
+ 2,
+ *ppdata, 0,
+ max_data_bytes);
+ return;
+ } else {
+ reply_nterror(req, NT_STATUS_OBJECT_PATH_NOT_FOUND);
+ return;
+ }
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("fstat of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp),
+ nt_errstr(status));
+ reply_nterror(req, status);
+ return;
+ }
+ }
+
+ info_level_handled = true; /* Untouched in switch cases below */
+
+ switch (info_level) {
+
+ default:
+ info_level_handled = false;
+ break;
+
+ case SMB_SET_FILE_UNIX_BASIC:
+ status = smb_set_file_unix_basic(conn,
+ req,
+ pdata,
+ total_data,
+ NULL,
+ fsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_FILE_UNIX_INFO2:
+ status = smb_set_file_unix_info2(conn,
+ req,
+ pdata,
+ total_data,
+ NULL,
+ fsp,
+ smb_fname);
+ break;
+
+ case SMB_SET_POSIX_LOCK:
+ status = smb_set_posix_lock(
+ conn, req, *ppdata, total_data, fsp);
+ break;
+ }
+
+ if (info_level_handled) {
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+ return;
+ }
+
+ status = smbd_do_setfilepathinfo(
+ conn,
+ req,
+ req,
+ info_level,
+ fsp,
+ smb_fname,
+ ppdata,
+ total_data,
+ &data_return_size);
+
+ handle_trans2setfilepathinfo_result(
+ conn,
+ req,
+ info_level,
+ status,
+ *ppdata,
+ data_return_size,
+ max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_MKDIR (make directory with extended attributes).
+****************************************************************************/
+
+static void call_trans2mkdir(connection_struct *conn, struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ struct files_struct *dirfsp = NULL;
+ struct files_struct *fsp = NULL;
+ struct smb_filename *smb_dname = NULL;
+ char *params = *pparams;
+ char *pdata = *ppdata;
+ char *directory = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct ea_list *ea_list = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!CAN_WRITE(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (total_params < 5) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ params,
+ req->flags2,
+ &directory,
+ &params[4],
+ total_params - 4,
+ STR_TERMINATE,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ params,
+ req->flags2,
+ &directory,
+ &params[4],
+ total_params - 4,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ DEBUG(3,("call_trans2mkdir : name = %s\n", directory));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(directory, &twrp);
+ }
+ status = smb1_strip_dfs_path(ctx, &ucf_flags, &directory);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ directory,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status,NT_STATUS_PATH_NOT_COVERED)) {
+ reply_botherror(req,
+ NT_STATUS_PATH_NOT_COVERED,
+ ERRSRV, ERRbadpath);
+ return;
+ }
+ reply_nterror(req, status);
+ return;
+ }
+
+ /*
+ * OS/2 workplace shell seems to send SET_EA requests of "null"
+ * length (4 bytes containing IVAL 4).
+ * They seem to have no effect. Bug #3212. JRA.
+ */
+
+ if (total_data && (total_data != 4)) {
+ /* Any data in this call is an EA list. */
+ if (total_data < 10) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("call_trans2mkdir: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ ea_list = read_ea_list(talloc_tos(), pdata + 4,
+ total_data - 4);
+ if (!ea_list) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ goto out;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ reply_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ goto out;
+ }
+ }
+ /* If total_data == 4 Windows doesn't care what values
+ * are placed in that field, it just ignores them.
+ * The System i QNTC IBM SMB client puts bad values here,
+ * so ignore them. */
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_dname, /* fname */
+ MAXIMUM_ALLOWED_ACCESS, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_CREATE, /* create_disposition*/
+ FILE_DIRECTORY_FILE, /* create_options */
+ FILE_ATTRIBUTE_DIRECTORY, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+
+ /* Try and set any given EA. */
+ if (ea_list) {
+ status = set_ea(conn, fsp, ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ goto out;
+ }
+ }
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,2);
+ if(*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,0);
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 2, *ppdata, 0, max_data_bytes);
+
+ out:
+ if (fsp != NULL) {
+ close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ }
+ TALLOC_FREE(smb_dname);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNOTIFYFIRST (start monitoring a directory for changes).
+ We don't actually do this - we just send a null response.
+****************************************************************************/
+
+static void call_trans2findnotifyfirst(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ uint16_t info_level;
+
+ if (total_params < 6) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ info_level = SVAL(params,4);
+ DEBUG(3,("call_trans2findnotifyfirst - info_level %d\n", info_level));
+
+ switch (info_level) {
+ case 1:
+ case 2:
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,6);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,fnf_handle);
+ SSVAL(params,2,0); /* No changes */
+ SSVAL(params,4,0); /* No EA errors */
+
+ fnf_handle++;
+
+ if(fnf_handle == 0)
+ fnf_handle = 257;
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 6, *ppdata, 0, max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_FINDNOTIFYNEXT (continue monitoring a directory for
+ changes). Currently this does nothing.
+****************************************************************************/
+
+static void call_trans2findnotifynext(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+
+ DEBUG(3,("call_trans2findnotifynext\n"));
+
+ /* Realloc the parameter and data sizes */
+ *pparams = (char *)SMB_REALLOC(*pparams,4);
+ if (*pparams == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ params = *pparams;
+
+ SSVAL(params,0,0); /* No changes */
+ SSVAL(params,2,0); /* No EA errors */
+
+ send_trans2_replies(conn, req, NT_STATUS_OK, params, 4, *ppdata, 0, max_data_bytes);
+}
+
+/****************************************************************************
+ Reply to a TRANS2_GET_DFS_REFERRAL - Shirish Kalele <kalele@veritas.com>.
+****************************************************************************/
+
+static void call_trans2getdfsreferral(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ char *params = *pparams;
+ char *pathname = NULL;
+ int reply_size = 0;
+ int max_referral_level;
+ NTSTATUS status = NT_STATUS_OK;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ DEBUG(10,("call_trans2getdfsreferral\n"));
+
+ if (!IS_IPC(conn)) {
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ if (total_params < 3) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ max_referral_level = SVAL(params,0);
+
+ if(!lp_host_msdfs()) {
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return;
+ }
+
+ srvstr_pull_talloc(ctx, params, req->flags2, &pathname, &params[2],
+ total_params - 2, STR_TERMINATE);
+ if (!pathname) {
+ reply_nterror(req, NT_STATUS_NOT_FOUND);
+ return;
+ }
+ reply_size = setup_dfs_referral(
+ conn, pathname, max_referral_level, ppdata, &status);
+ if (reply_size < 0) {
+ reply_nterror(req, status);
+ return;
+ }
+
+ SSVAL((discard_const_p(uint8_t, req->inbuf)), smb_flg2,
+ SVAL(req->inbuf,smb_flg2) | FLAGS2_DFS_PATHNAMES);
+ send_trans2_replies(conn, req, NT_STATUS_OK, 0,0,*ppdata,reply_size, max_data_bytes);
+}
+
+#define LMCAT_SPL 0x53
+#define LMFUNC_GETJOBID 0x60
+
+/****************************************************************************
+ Reply to a TRANS2_IOCTL - used for OS/2 printing.
+****************************************************************************/
+
+static void call_trans2ioctl(connection_struct *conn,
+ struct smb_request *req,
+ char **pparams, int total_params,
+ char **ppdata, int total_data,
+ unsigned int max_data_bytes)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *pdata = *ppdata;
+ files_struct *fsp = file_fsp(req, SVAL(req->vwv+15, 0));
+ NTSTATUS status;
+ size_t len = 0;
+
+ /* check for an invalid fid before proceeding */
+
+ if (!fsp) {
+ reply_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return;
+ }
+
+ if ((SVAL(req->vwv+16, 0) == LMCAT_SPL)
+ && (SVAL(req->vwv+17, 0) == LMFUNC_GETJOBID)) {
+ *ppdata = (char *)SMB_REALLOC(*ppdata, 32);
+ if (*ppdata == NULL) {
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ pdata = *ppdata;
+
+ /* NOTE - THIS IS ASCII ONLY AT THE MOMENT - NOT SURE IF OS/2
+ CAN ACCEPT THIS IN UNICODE. JRA. */
+
+ /* Job number */
+ SSVAL(pdata, 0, print_spool_rap_jobid(fsp->print_file));
+
+ status = srvstr_push(pdata, req->flags2, pdata + 2,
+ lp_netbios_name(), 15,
+ STR_ASCII|STR_TERMINATE, &len); /* Our NetBIOS name */
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ status = srvstr_push(pdata, req->flags2, pdata+18,
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)), 13,
+ STR_ASCII|STR_TERMINATE, &len); /* Service name */
+ if (!NT_STATUS_IS_OK(status)) {
+ reply_nterror(req, status);
+ return;
+ }
+ send_trans2_replies(conn, req, NT_STATUS_OK, *pparams, 0, *ppdata, 32,
+ max_data_bytes);
+ return;
+ }
+
+ DEBUG(2,("Unknown TRANS2_IOCTL\n"));
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+}
+
+static void handle_trans2(connection_struct *conn, struct smb_request *req,
+ struct trans_state *state)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+
+ if (xconn->protocol >= PROTOCOL_NT1) {
+ req->flags2 |= 0x40; /* IS_LONG_NAME */
+ SSVAL((discard_const_p(uint8_t, req->inbuf)),smb_flg2,req->flags2);
+ }
+
+ if (ENCRYPTION_REQUIRED(conn) && !req->encrypted) {
+ if (state->call != TRANSACT2_QFSINFO &&
+ state->call != TRANSACT2_SETFSINFO) {
+ DEBUG(0,("handle_trans2: encryption required "
+ "with call 0x%x\n",
+ (unsigned int)state->call));
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+ }
+
+ /* Now we must call the relevant TRANS2 function */
+ switch(state->call) {
+ case TRANSACT2_OPEN:
+ {
+ START_PROFILE(Trans2_open);
+ call_trans2open(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_open);
+ break;
+ }
+
+ case TRANSACT2_FINDFIRST:
+ {
+ START_PROFILE(Trans2_findfirst);
+ call_trans2findfirst(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findfirst);
+ break;
+ }
+
+ case TRANSACT2_FINDNEXT:
+ {
+ START_PROFILE(Trans2_findnext);
+ call_trans2findnext(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnext);
+ break;
+ }
+
+ case TRANSACT2_QFSINFO:
+ {
+ START_PROFILE(Trans2_qfsinfo);
+ call_trans2qfsinfo(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qfsinfo);
+ break;
+ }
+
+ case TRANSACT2_SETFSINFO:
+ {
+ START_PROFILE(Trans2_setfsinfo);
+ call_trans2setfsinfo(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setfsinfo);
+ break;
+ }
+
+ case TRANSACT2_QPATHINFO:
+ {
+ START_PROFILE(Trans2_qpathinfo);
+ call_trans2qpathinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qpathinfo);
+ break;
+ }
+
+ case TRANSACT2_QFILEINFO:
+ {
+ START_PROFILE(Trans2_qfileinfo);
+ call_trans2qfileinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_qfileinfo);
+ break;
+ }
+
+ case TRANSACT2_SETPATHINFO:
+ {
+ START_PROFILE(Trans2_setpathinfo);
+ call_trans2setpathinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setpathinfo);
+ break;
+ }
+
+ case TRANSACT2_SETFILEINFO:
+ {
+ START_PROFILE(Trans2_setfileinfo);
+ call_trans2setfileinfo(
+ conn,
+ req,
+ &state->param,
+ state->total_param,
+ &state->data,
+ state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_setfileinfo);
+ break;
+ }
+
+ case TRANSACT2_FINDNOTIFYFIRST:
+ {
+ START_PROFILE(Trans2_findnotifyfirst);
+ call_trans2findnotifyfirst(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnotifyfirst);
+ break;
+ }
+
+ case TRANSACT2_FINDNOTIFYNEXT:
+ {
+ START_PROFILE(Trans2_findnotifynext);
+ call_trans2findnotifynext(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_findnotifynext);
+ break;
+ }
+
+ case TRANSACT2_MKDIR:
+ {
+ START_PROFILE(Trans2_mkdir);
+ call_trans2mkdir(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_mkdir);
+ break;
+ }
+
+ case TRANSACT2_GET_DFS_REFERRAL:
+ {
+ START_PROFILE(Trans2_get_dfs_referral);
+ call_trans2getdfsreferral(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_get_dfs_referral);
+ break;
+ }
+
+ case TRANSACT2_IOCTL:
+ {
+ START_PROFILE(Trans2_ioctl);
+ call_trans2ioctl(conn, req,
+ &state->param, state->total_param,
+ &state->data, state->total_data,
+ state->max_data_return);
+ END_PROFILE(Trans2_ioctl);
+ break;
+ }
+
+ default:
+ /* Error in request */
+ DEBUG(2,("Unknown request %d in trans2 call\n", state->call));
+ reply_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ }
+}
+
+/****************************************************************************
+ Reply to a SMBtrans2.
+ ****************************************************************************/
+
+void reply_trans2(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int dsoff;
+ unsigned int dscnt;
+ unsigned int psoff;
+ unsigned int pscnt;
+ unsigned int tran_call;
+ struct trans_state *state;
+ NTSTATUS result;
+
+ START_PROFILE(SMBtrans2);
+
+ if (req->wct < 14) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ dsoff = SVAL(req->vwv+12, 0);
+ dscnt = SVAL(req->vwv+11, 0);
+ psoff = SVAL(req->vwv+10, 0);
+ pscnt = SVAL(req->vwv+9, 0);
+ tran_call = SVAL(req->vwv+14, 0);
+
+ result = allow_new_trans(conn->pending_trans, req->mid);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2, ("Got invalid trans2 request: %s\n",
+ nt_errstr(result)));
+ reply_nterror(req, result);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ if (IS_IPC(conn)) {
+ switch (tran_call) {
+ /* List the allowed trans2 calls on IPC$ */
+ case TRANSACT2_OPEN:
+ case TRANSACT2_GET_DFS_REFERRAL:
+ case TRANSACT2_QFILEINFO:
+ case TRANSACT2_QFSINFO:
+ case TRANSACT2_SETFSINFO:
+ break;
+ default:
+ reply_nterror(req, NT_STATUS_ACCESS_DENIED);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+ }
+
+ if ((state = talloc(conn, struct trans_state)) == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ state->cmd = SMBtrans2;
+
+ state->mid = req->mid;
+ state->vuid = req->vuid;
+ state->setup_count = SVAL(req->vwv+13, 0);
+ state->setup = NULL;
+ state->total_param = SVAL(req->vwv+0, 0);
+ state->param = NULL;
+ state->total_data = SVAL(req->vwv+1, 0);
+ state->data = NULL;
+ state->max_param_return = SVAL(req->vwv+2, 0);
+ state->max_data_return = SVAL(req->vwv+3, 0);
+ state->max_setup_return = SVAL(req->vwv+4, 0);
+ state->close_on_completion = BITSETW(req->vwv+5, 0);
+ state->one_way = BITSETW(req->vwv+5, 1);
+
+ state->call = tran_call;
+
+ /* All trans2 messages we handle have smb_sucnt == 1 - ensure this
+ is so as a sanity check */
+ if (state->setup_count != 1) {
+ /*
+ * Need to have rc=0 for ioctl to get job id for OS/2.
+ * Network printing will fail if function is not successful.
+ * Similar function in reply.c will be used if protocol
+ * is LANMAN1.0 instead of LM1.2X002.
+ * Until DosPrintSetJobInfo with PRJINFO3 is supported,
+ * outbuf doesn't have to be set(only job id is used).
+ */
+ if ( (state->setup_count == 4)
+ && (tran_call == TRANSACT2_IOCTL)
+ && (SVAL(req->vwv+16, 0) == LMCAT_SPL)
+ && (SVAL(req->vwv+17, 0) == LMFUNC_GETJOBID)) {
+ DEBUG(2,("Got Trans2 DevIOctl jobid\n"));
+ } else {
+ DEBUG(2,("Invalid smb_sucnt in trans2 call(%u)\n",state->setup_count));
+ DEBUG(2,("Transaction is %d\n",tran_call));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+ }
+
+ if ((dscnt > state->total_data) || (pscnt > state->total_param))
+ goto bad_param;
+
+ if (state->total_data) {
+
+ if (smb_buffer_oob(state->total_data, 0, dscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), dsoff, dscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ state->data = (char *)SMB_MALLOC(state->total_data);
+ if (state->data == NULL) {
+ DEBUG(0,("reply_trans2: data malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_data));
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ memcpy(state->data,smb_base(req->inbuf)+dsoff,dscnt);
+ }
+
+ if (state->total_param) {
+
+ if (smb_buffer_oob(state->total_param, 0, pscnt)
+ || smb_buffer_oob(smb_len(req->inbuf), psoff, pscnt)) {
+ goto bad_param;
+ }
+
+ /* Can't use talloc here, the core routines do realloc on the
+ * params and data. */
+ state->param = (char *)SMB_MALLOC(state->total_param);
+ if (state->param == NULL) {
+ DEBUG(0,("reply_trans: param malloc fail for %u "
+ "bytes !\n", (unsigned int)state->total_param));
+ SAFE_FREE(state->data);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ memcpy(state->param,smb_base(req->inbuf)+psoff,pscnt);
+ }
+
+ state->received_data = dscnt;
+ state->received_param = pscnt;
+
+ if ((state->received_param == state->total_param) &&
+ (state->received_data == state->total_data)) {
+
+ handle_trans2(conn, req, state);
+
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans2);
+ return;
+ }
+
+ DLIST_ADD(conn->pending_trans, state);
+
+ /* We need to send an interim response then receive the rest
+ of the parameter/data bytes */
+ reply_smb1_outbuf(req, 0, 0);
+ show_msg((char *)req->outbuf);
+ END_PROFILE(SMBtrans2);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_trans2: invalid trans parameters\n"));
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ END_PROFILE(SMBtrans2);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+}
+
+/****************************************************************************
+ Reply to a SMBtranss2
+ ****************************************************************************/
+
+void reply_transs2(struct smb_request *req)
+{
+ connection_struct *conn = req->conn;
+ unsigned int pcnt,poff,dcnt,doff,pdisp,ddisp;
+ struct trans_state *state;
+
+ START_PROFILE(SMBtranss2);
+
+ show_msg((const char *)req->inbuf);
+
+ /* Windows clients expect all replies to
+ a transact secondary (SMBtranss2 0x33)
+ to have a command code of transact
+ (SMBtrans2 0x32). See bug #8989
+ and also [MS-CIFS] section 2.2.4.47.2
+ for details.
+ */
+ req->cmd = SMBtrans2;
+
+ if (req->wct < 8) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ for (state = conn->pending_trans; state != NULL;
+ state = state->next) {
+ if (state->mid == req->mid) {
+ break;
+ }
+ }
+
+ if ((state == NULL) || (state->cmd != SMBtrans2)) {
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ /* Revise state->total_param and state->total_data in case they have
+ changed downwards */
+
+ if (SVAL(req->vwv+0, 0) < state->total_param)
+ state->total_param = SVAL(req->vwv+0, 0);
+ if (SVAL(req->vwv+1, 0) < state->total_data)
+ state->total_data = SVAL(req->vwv+1, 0);
+
+ pcnt = SVAL(req->vwv+2, 0);
+ poff = SVAL(req->vwv+3, 0);
+ pdisp = SVAL(req->vwv+4, 0);
+
+ dcnt = SVAL(req->vwv+5, 0);
+ doff = SVAL(req->vwv+6, 0);
+ ddisp = SVAL(req->vwv+7, 0);
+
+ state->received_param += pcnt;
+ state->received_data += dcnt;
+
+ if ((state->received_data > state->total_data) ||
+ (state->received_param > state->total_param))
+ goto bad_param;
+
+ if (pcnt) {
+ if (smb_buffer_oob(state->total_param, pdisp, pcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), poff, pcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->param+pdisp,smb_base(req->inbuf)+poff,pcnt);
+ }
+
+ if (dcnt) {
+ if (smb_buffer_oob(state->total_data, ddisp, dcnt)
+ || smb_buffer_oob(smb_len(req->inbuf), doff, dcnt)) {
+ goto bad_param;
+ }
+ memcpy(state->data+ddisp, smb_base(req->inbuf)+doff,dcnt);
+ }
+
+ if ((state->received_param < state->total_param) ||
+ (state->received_data < state->total_data)) {
+ END_PROFILE(SMBtranss2);
+ return;
+ }
+
+ handle_trans2(conn, req, state);
+
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+
+ END_PROFILE(SMBtranss2);
+ return;
+
+ bad_param:
+
+ DEBUG(0,("reply_transs2: invalid trans parameters\n"));
+ DLIST_REMOVE(conn->pending_trans, state);
+ SAFE_FREE(state->data);
+ SAFE_FREE(state->param);
+ TALLOC_FREE(state);
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBtranss2);
+}
diff --git a/source3/smbd/smb1_trans2.h b/source3/smbd/smb1_trans2.h
new file mode 100644
index 0000000..4b8b4b1
--- /dev/null
+++ b/source3/smbd/smb1_trans2.h
@@ -0,0 +1,27 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2005-2007
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006-2007
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+void reply_trans2(struct smb_request *req);
+void reply_transs2(struct smb_request *req);
diff --git a/source3/smbd/smb1_utils.c b/source3/smbd/smb1_utils.c
new file mode 100644
index 0000000..bdd842c
--- /dev/null
+++ b/source3/smbd/smb1_utils.c
@@ -0,0 +1,261 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Util functions valid in the SMB1 server
+ *
+ * Copyright (C) Volker Lendecke 2019
+ * Copyright by the authors of the functions moved here eventually
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "lib/util/sys_rw_data.h"
+#include "smbd/fd_handle.h"
+
+/****************************************************************************
+ Special FCB or DOS processing in the case of a sharing violation.
+ Try and find a duplicated file handle.
+****************************************************************************/
+
+struct files_struct *fcb_or_dos_open(
+ struct smb_request *req,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t create_options,
+ uint32_t private_flags)
+{
+ struct connection_struct *conn = req->conn;
+ struct file_id id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ struct files_struct *fsp = NULL, *new_fsp = NULL;
+ NTSTATUS status;
+
+ if ((private_flags &
+ (NTCREATEX_FLAG_DENY_DOS|
+ NTCREATEX_FLAG_DENY_FCB))
+ == 0) {
+ return NULL;
+ }
+
+ for(fsp = file_find_di_first(conn->sconn, id, true);
+ fsp != NULL;
+ fsp = file_find_di_next(fsp, true)) {
+
+ DBG_DEBUG("Checking file %s, fd = %d, vuid = %"PRIu64", "
+ "file_pid = %"PRIu16", "
+ "private_options = 0x%"PRIx32", "
+ "access_mask = 0x%"PRIx32"\n",
+ fsp_str_dbg(fsp),
+ fsp_get_pathref_fd(fsp),
+ fsp->vuid,
+ fsp->file_pid,
+ fh_get_private_options(fsp->fh),
+ fsp->access_mask);
+
+ if (fsp_get_pathref_fd(fsp) != -1 &&
+ fsp->vuid == req->vuid &&
+ fsp->file_pid == req->smbpid &&
+ (fh_get_private_options(fsp->fh) &
+ (NTCREATEX_FLAG_DENY_DOS |
+ NTCREATEX_FLAG_DENY_FCB)) &&
+ (fsp->access_mask & FILE_WRITE_DATA) &&
+ strequal(fsp->fsp_name->base_name, smb_fname->base_name) &&
+ strequal(fsp->fsp_name->stream_name,
+ smb_fname->stream_name)) {
+ DBG_DEBUG("file match\n");
+ break;
+ }
+ }
+
+ if (fsp == NULL) {
+ return NULL;
+ }
+
+ /* quite an insane set of semantics ... */
+ if (is_executable(smb_fname->base_name) &&
+ (fh_get_private_options(fsp->fh) & NTCREATEX_FLAG_DENY_DOS)) {
+ DBG_DEBUG("file fail due to is_executable.\n");
+ return NULL;
+ }
+
+ status = file_new(req, conn, &new_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("file_new failed: %s\n", nt_errstr(status));
+ return NULL;
+ }
+
+ status = dup_file_fsp(fsp, access_mask, new_fsp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dup_file_fsp failed: %s\n", nt_errstr(status));
+ file_free(req, new_fsp);
+ return NULL;
+ }
+
+ return new_fsp;
+}
+
+/****************************************************************************
+ Send a keepalive packet (rfc1002).
+****************************************************************************/
+
+bool send_keepalive(int client)
+{
+ unsigned char buf[4];
+
+ buf[0] = NBSSkeepalive;
+ buf[1] = buf[2] = buf[3] = 0;
+
+ return(write_data(client,(char *)buf,4) == 4);
+}
+
+/*******************************************************************
+ Add a string to the end of a smb_buf, adjusting bcc and smb_len.
+ Return the bytes added
+********************************************************************/
+
+ssize_t message_push_string(uint8_t **outbuf, const char *str, int flags)
+{
+ size_t buf_size = smb_len(*outbuf) + 4;
+ size_t grow_size;
+ size_t result = 0;
+ uint8_t *tmp;
+ NTSTATUS status;
+
+ /*
+ * We need to over-allocate, now knowing what srvstr_push will
+ * actually use. This is very generous by incorporating potential
+ * padding, the terminating 0 and at most 4 chars per UTF-16 code
+ * point.
+ */
+ grow_size = (strlen(str) + 2) * 4;
+
+ if (!(tmp = talloc_realloc(NULL, *outbuf, uint8_t,
+ buf_size + grow_size))) {
+ DEBUG(0, ("talloc failed\n"));
+ return -1;
+ }
+
+ status = srvstr_push((char *)tmp, SVAL(tmp, smb_flg2),
+ tmp + buf_size, str, grow_size, flags, &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("srvstr_push failed\n"));
+ return -1;
+ }
+
+ /*
+ * Ensure we clear out the extra data we have
+ * grown the buffer by, but not written to.
+ */
+ if (buf_size + result < buf_size) {
+ return -1;
+ }
+ if (grow_size < result) {
+ return -1;
+ }
+
+ memset(tmp + buf_size + result, '\0', grow_size - result);
+
+ set_message_bcc((char *)tmp, smb_buflen(tmp) + result);
+
+ *outbuf = tmp;
+
+ return result;
+}
+
+/*
+ * Deal with the SMB1 semantics of sending a pathname with a
+ * wildcard as the terminal component for a SMB1search or
+ * trans2 findfirst.
+ */
+
+NTSTATUS filename_convert_smb1_search_path(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ char *name_in,
+ uint32_t ucf_flags,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname_out,
+ char **_mask_out)
+{
+ NTSTATUS status;
+ char *p = NULL;
+ char *mask = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTTIME twrp = 0;
+
+ *_smb_fname_out = NULL;
+ *_dirfsp = NULL;
+ *_mask_out = NULL;
+
+ DBG_DEBUG("name_in: %s\n", name_in);
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(name_in, &twrp);
+ ucf_flags &= ~UCF_GMT_PATHNAME;
+ }
+
+ /* Get the original lcomp. */
+ mask = get_original_lcomp(ctx, conn, name_in, ucf_flags);
+ if (mask == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (mask[0] == '\0') {
+ /* Windows and OS/2 systems treat search on the root as * */
+ TALLOC_FREE(mask);
+ mask = talloc_strdup(ctx, "*");
+ if (mask == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ DBG_DEBUG("mask = %s\n", mask);
+
+ /*
+ * Remove the terminal component so
+ * filename_convert_dirfsp never sees the mask.
+ */
+ p = strrchr_m(name_in, '/');
+ if (p == NULL) {
+ /* filename_convert_dirfsp handles a '\0' name. */
+ name_in[0] = '\0';
+ } else {
+ *p = '\0';
+ }
+
+ DBG_DEBUG("For filename_convert_dirfsp: name_in = %s\n", name_in);
+
+ /* Convert the parent directory path. */
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ name_in,
+ ucf_flags,
+ twrp,
+ _dirfsp,
+ &smb_fname);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("filename_convert error for %s: %s\n",
+ name_in,
+ nt_errstr(status));
+ }
+
+ *_smb_fname_out = talloc_move(ctx, &smb_fname);
+ *_mask_out = talloc_move(ctx, &mask);
+
+ return status;
+}
diff --git a/source3/smbd/smb1_utils.h b/source3/smbd/smb1_utils.h
new file mode 100644
index 0000000..539648e
--- /dev/null
+++ b/source3/smbd/smb1_utils.h
@@ -0,0 +1,46 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Util functions valid in the SMB1 server
+ *
+ * Copyright (C) Volker Lendecke 2019
+ * Copyright by the authors of the functions moved here eventually
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SMBD_SMB1_UTILS_H__
+#define __SMBD_SMB1_UTILS_H__
+
+#include "includes.h"
+#include "vfs.h"
+#include "proto.h"
+#include "lib/util/string_wrappers.h"
+
+struct files_struct *fcb_or_dos_open(
+ struct smb_request *req,
+ const struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t create_options,
+ uint32_t private_flags);
+bool send_keepalive(int client);
+ssize_t message_push_string(uint8_t **outbuf, const char *str, int flags);
+NTSTATUS filename_convert_smb1_search_path(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ char *name_in,
+ uint32_t ucf_flags,
+ struct files_struct **_dirfsp,
+ struct smb_filename **_smb_fname_out,
+ char **_mask_out);
+
+#endif
diff --git a/source3/smbd/smb2_aio.c b/source3/smbd/smb2_aio.c
new file mode 100644
index 0000000..88aa68d
--- /dev/null
+++ b/source3/smbd/smb2_aio.c
@@ -0,0 +1,608 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 3.0
+ async_io read handling using POSIX async io.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../lib/util/tevent_unix.h"
+
+/****************************************************************************
+ Accessor function to return write_through state.
+*****************************************************************************/
+
+bool aio_write_through_requested(struct aio_extra *aio_ex)
+{
+ return aio_ex->write_through;
+}
+
+/****************************************************************************
+ Create the extended aio struct we must keep around for the lifetime
+ of the aio call.
+*****************************************************************************/
+
+struct aio_extra *create_aio_extra(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ size_t buflen)
+{
+ struct aio_extra *aio_ex = talloc_zero(mem_ctx, struct aio_extra);
+
+ if (!aio_ex) {
+ return NULL;
+ }
+
+ /* The output buffer stored in the aio_ex is the start of
+ the smb return buffer. The buffer used in the acb
+ is the start of the reply data portion of that buffer. */
+
+ if (buflen) {
+ aio_ex->outbuf = data_blob_talloc(aio_ex, NULL, buflen);
+ if (!aio_ex->outbuf.data) {
+ TALLOC_FREE(aio_ex);
+ return NULL;
+ }
+ }
+ aio_ex->fsp = fsp;
+ return aio_ex;
+}
+
+struct aio_req_fsp_link {
+#ifdef DEVELOPER
+ struct smbd_server_connection *sconn;
+#endif
+ files_struct *fsp;
+ struct tevent_req *req;
+};
+
+static int aio_del_req_from_fsp(struct aio_req_fsp_link *lnk)
+{
+ unsigned i;
+ files_struct *fsp = lnk->fsp;
+ struct tevent_req *req = lnk->req;
+
+#ifdef DEVELOPER
+ struct files_struct *ifsp = NULL;
+ bool found = false;
+
+ /*
+ * When this is called, lnk->fsp must still exist
+ * on the files list for this connection. Panic if not.
+ */
+ for (ifsp = lnk->sconn->files; ifsp; ifsp = ifsp->next) {
+ if (ifsp == fsp) {
+ found = true;
+ }
+ }
+ if (!found) {
+ smb_panic("orphaned lnk on fsp aio list.\n");
+ }
+#endif
+
+ for (i=0; i<fsp->num_aio_requests; i++) {
+ if (fsp->aio_requests[i] == req) {
+ break;
+ }
+ }
+ if (i == fsp->num_aio_requests) {
+ DEBUG(1, ("req %p not found in fsp %p\n", req, fsp));
+ return 0;
+ }
+ fsp->num_aio_requests -= 1;
+ fsp->aio_requests[i] = fsp->aio_requests[fsp->num_aio_requests];
+
+ if (fsp->num_aio_requests == 0) {
+ TALLOC_FREE(fsp->aio_requests);
+ }
+ return 0;
+}
+
+bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req)
+{
+ size_t array_len;
+ struct aio_req_fsp_link *lnk;
+
+ lnk = talloc(req, struct aio_req_fsp_link);
+ if (lnk == NULL) {
+ return false;
+ }
+
+ array_len = talloc_array_length(fsp->aio_requests);
+ if (array_len <= fsp->num_aio_requests) {
+ struct tevent_req **tmp;
+
+ if (fsp->num_aio_requests + 10 < 10) {
+ /* Integer wrap. */
+ TALLOC_FREE(lnk);
+ return false;
+ }
+
+ /*
+ * Allocate in blocks of 10 so we don't allocate
+ * on every aio request.
+ */
+ tmp = talloc_realloc(
+ fsp, fsp->aio_requests, struct tevent_req *,
+ fsp->num_aio_requests+10);
+ if (tmp == NULL) {
+ TALLOC_FREE(lnk);
+ return false;
+ }
+ fsp->aio_requests = tmp;
+ }
+ fsp->aio_requests[fsp->num_aio_requests] = req;
+ fsp->num_aio_requests += 1;
+
+ lnk->fsp = fsp;
+ lnk->req = req;
+#ifdef DEVELOPER
+ lnk->sconn = fsp->conn->sconn;
+#endif
+ talloc_set_destructor(lnk, aio_del_req_from_fsp);
+
+ return true;
+}
+
+struct pwrite_fsync_state {
+ struct tevent_context *ev;
+ files_struct *fsp;
+ bool write_through;
+ ssize_t nwritten;
+};
+
+static void pwrite_fsync_write_done(struct tevent_req *subreq);
+static void pwrite_fsync_sync_done(struct tevent_req *subreq);
+
+struct tevent_req *pwrite_fsync_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset,
+ bool write_through)
+{
+ struct tevent_req *req, *subreq;
+ struct pwrite_fsync_state *state;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state, struct pwrite_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->fsp = fsp;
+ state->write_through = write_through;
+
+ ok = vfs_valid_pwrite_range(offset, n);
+ if (!ok) {
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ if (n == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_PWRITE_SEND(state, ev, fsp, data, n, offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, pwrite_fsync_write_done, req);
+ return req;
+}
+
+static void pwrite_fsync_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct pwrite_fsync_state *state = tevent_req_data(
+ req, struct pwrite_fsync_state);
+ connection_struct *conn = state->fsp->conn;
+ bool do_sync;
+ struct vfs_aio_state vfs_aio_state;
+
+ state->nwritten = SMB_VFS_PWRITE_RECV(subreq, &vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->nwritten == -1) {
+ tevent_req_error(req, vfs_aio_state.error);
+ return;
+ }
+
+ do_sync = (lp_strict_sync(SNUM(conn)) &&
+ (lp_sync_always(SNUM(conn)) || state->write_through));
+ if (!do_sync) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = SMB_VFS_FSYNC_SEND(state, state->ev, state->fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, pwrite_fsync_sync_done, req);
+}
+
+static void pwrite_fsync_sync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+
+ ret = SMB_VFS_FSYNC_RECV(subreq, &vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+ssize_t pwrite_fsync_recv(struct tevent_req *req, int *perr)
+{
+ struct pwrite_fsync_state *state = tevent_req_data(
+ req, struct pwrite_fsync_state);
+
+ if (tevent_req_is_unix_error(req, perr)) {
+ return -1;
+ }
+ return state->nwritten;
+}
+
+bool cancel_smb2_aio(struct smb_request *smbreq)
+{
+ struct smbd_smb2_request *smb2req = smbreq->smb2req;
+ struct aio_extra *aio_ex = NULL;
+
+ if (smb2req) {
+ aio_ex = talloc_get_type(smbreq->async_priv,
+ struct aio_extra);
+ }
+
+ if (aio_ex == NULL) {
+ return false;
+ }
+
+ if (aio_ex->fsp == NULL) {
+ return false;
+ }
+
+ /*
+ * We let the aio request run and don't try to cancel it which means
+ * processing of the SMB2 request must continue as normal, cf MS-SMB2
+ * 3.3.5.16:
+ *
+ * If the target request is not successfully canceled, processing of
+ * the target request MUST continue and no response is sent to the
+ * cancel request.
+ */
+
+ return false;
+}
+
+static void aio_pread_smb2_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMB2 read call.
+*****************************************************************************/
+
+NTSTATUS schedule_smb2_aio_read(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ TALLOC_CTX *ctx,
+ DATA_BLOB *preadbuf,
+ off_t startpos,
+ size_t smb_maxcnt)
+{
+ struct aio_extra *aio_ex;
+ size_t min_aio_read_size = lp_aio_read_size(SNUM(conn));
+ struct tevent_req *req;
+ bool is_compound = false;
+ bool is_last_in_compound = false;
+ bool ok;
+
+ ok = vfs_valid_pread_range(startpos, smb_maxcnt);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if (fsp->op == NULL) {
+ /* No AIO on internal opens. */
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_read_size || (smb_maxcnt < min_aio_read_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a read for aio request. */
+ DEBUG(10,("smb2: read size (%u) too small "
+ "for minimum aio_read of %u\n",
+ (unsigned int)smb_maxcnt,
+ (unsigned int)min_aio_read_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ is_compound = smbd_smb2_is_compound(smbreq->smb2req);
+ is_last_in_compound = smbd_smb2_is_last_in_compound(smbreq->smb2req);
+
+ if (is_compound && !is_last_in_compound) {
+ /*
+ * Only allow going async if this is the last
+ * request in a compound.
+ */
+ return NT_STATUS_RETRY;
+ }
+
+ /* Create the out buffer. */
+ *preadbuf = data_blob_talloc(ctx, NULL, smb_maxcnt);
+ if (preadbuf->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!(aio_ex = create_aio_extra(smbreq->smb2req, fsp, 0))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ (uint64_t)startpos,
+ (uint64_t)smb_maxcnt,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = smb_maxcnt;
+ aio_ex->offset = startpos;
+
+ req = SMB_VFS_PREAD_SEND(aio_ex, fsp->conn->sconn->ev_ctx, fsp,
+ preadbuf->data, smb_maxcnt, startpos);
+ if (req == NULL) {
+ DEBUG(0, ("smb2: SMB_VFS_PREAD_SEND failed. "
+ "Error %s\n", strerror(errno)));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pread_smb2_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ /* We don't need talloc_move here as both aio_ex and
+ * smbreq are children of smbreq->smb2req. */
+ aio_ex->smbreq = smbreq;
+ smbreq->async_priv = aio_ex;
+
+ DEBUG(10,("smb2: scheduled aio_read for file %s, "
+ "offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp), (double)startpos, (unsigned int)smb_maxcnt,
+ (unsigned int)aio_ex->smbreq->mid ));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pread_smb2_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ struct tevent_req *subreq = aio_ex->smbreq->smb2req->subreq;
+ files_struct *fsp = aio_ex->fsp;
+ NTSTATUS status;
+ ssize_t nread;
+ struct vfs_aio_state vfs_aio_state = { 0 };
+
+ nread = SMB_VFS_PREAD_RECV(req, &vfs_aio_state);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pread_recv returned %d, err = %s\n", (int)nread,
+ (nread == -1) ? strerror(vfs_aio_state.error) : "no error"));
+
+ /* Common error or success code processing for async or sync
+ read returns. */
+
+ status = smb2_read_complete(subreq, nread, vfs_aio_state.error);
+
+ if (nread > 0) {
+ fh_set_pos(fsp->fh, aio_ex->offset + nread);
+ fh_set_position_information(fsp->fh,
+ fh_get_pos(fsp->fh));
+ }
+
+ DEBUG(10, ("smb2: scheduled aio_read completed "
+ "for file %s, offset %.0f, len = %u "
+ "(errcode = %d, NTSTATUS = %s)\n",
+ fsp_str_dbg(aio_ex->fsp),
+ (double)aio_ex->offset,
+ (unsigned int)nread,
+ vfs_aio_state.error, nt_errstr(status)));
+
+ if (tevent_req_nterror(subreq, status)) {
+ return;
+ }
+ tevent_req_done(subreq);
+}
+
+static void aio_pwrite_smb2_done(struct tevent_req *req);
+
+/****************************************************************************
+ Set up an aio request from a SMB2write call.
+*****************************************************************************/
+
+NTSTATUS schedule_aio_smb2_write(connection_struct *conn,
+ struct smb_request *smbreq,
+ files_struct *fsp,
+ uint64_t in_offset,
+ DATA_BLOB in_data,
+ bool write_through)
+{
+ struct aio_extra *aio_ex = NULL;
+ size_t min_aio_write_size = lp_aio_write_size(SNUM(conn));
+ struct tevent_req *req;
+ bool is_compound = false;
+ bool is_last_in_compound = false;
+
+ if (fsp_is_alternate_stream(fsp)) {
+ /* No AIO on streams yet */
+ DEBUG(10, ("AIO on streams not yet supported\n"));
+ return NT_STATUS_RETRY;
+ }
+
+ if (fsp->op == NULL) {
+ /* No AIO on internal opens. */
+ return NT_STATUS_RETRY;
+ }
+
+ if ((!min_aio_write_size || (in_data.length < min_aio_write_size))
+ && !SMB_VFS_AIO_FORCE(fsp)) {
+ /* Too small a write for aio request. */
+ DEBUG(10,("smb2: write size (%u) too "
+ "small for minimum aio_write of %u\n",
+ (unsigned int)in_data.length,
+ (unsigned int)min_aio_write_size ));
+ return NT_STATUS_RETRY;
+ }
+
+ is_compound = smbd_smb2_is_compound(smbreq->smb2req);
+ is_last_in_compound = smbd_smb2_is_last_in_compound(smbreq->smb2req);
+
+ if (is_compound && !is_last_in_compound) {
+ /*
+ * Only allow going async if this is the last
+ * request in a compound.
+ */
+ return NT_STATUS_RETRY;
+ }
+
+ if (smbreq->unread_bytes) {
+ /* Can't do async with recvfile. */
+ return NT_STATUS_RETRY;
+ }
+
+ if (!(aio_ex = create_aio_extra(smbreq->smb2req, fsp, 0))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ aio_ex->write_through = write_through;
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ (uint64_t)in_data.length,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &aio_ex->lock);
+
+ /* Take the lock until the AIO completes. */
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &aio_ex->lock)) {
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ aio_ex->nbyte = in_data.length;
+ aio_ex->offset = in_offset;
+
+ req = pwrite_fsync_send(aio_ex, fsp->conn->sconn->ev_ctx, fsp,
+ in_data.data, in_data.length, in_offset,
+ write_through);
+ if (req == NULL) {
+ DEBUG(3, ("smb2: SMB_VFS_PWRITE_SEND failed. "
+ "Error %s\n", strerror(errno)));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+ tevent_req_set_callback(req, aio_pwrite_smb2_done, aio_ex);
+
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ DEBUG(1, ("Could not add req to fsp\n"));
+ TALLOC_FREE(aio_ex);
+ return NT_STATUS_RETRY;
+ }
+
+ /* We don't need talloc_move here as both aio_ex and
+ * smbreq are children of smbreq->smb2req. */
+ aio_ex->smbreq = smbreq;
+ smbreq->async_priv = aio_ex;
+
+ /* This should actually be improved to span the write. */
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_WRITE);
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_WRITE);
+
+ /*
+ * We don't want to do write behind due to ownership
+ * issues of the request structs. Maybe add it if I
+ * figure those out. JRA.
+ */
+
+ DEBUG(10,("smb2: scheduled aio_write for file "
+ "%s, offset %.0f, len = %u (mid = %u)\n",
+ fsp_str_dbg(fsp),
+ (double)in_offset,
+ (unsigned int)in_data.length,
+ (unsigned int)aio_ex->smbreq->mid));
+
+ return NT_STATUS_OK;
+}
+
+static void aio_pwrite_smb2_done(struct tevent_req *req)
+{
+ struct aio_extra *aio_ex = tevent_req_callback_data(
+ req, struct aio_extra);
+ ssize_t numtowrite = aio_ex->nbyte;
+ struct tevent_req *subreq = aio_ex->smbreq->smb2req->subreq;
+ files_struct *fsp = aio_ex->fsp;
+ NTSTATUS status;
+ ssize_t nwritten;
+ int err = 0;
+
+ nwritten = pwrite_fsync_recv(req, &err);
+ TALLOC_FREE(req);
+
+ DEBUG(10, ("pwrite_recv returned %d, err = %s\n", (int)nwritten,
+ (nwritten == -1) ? strerror(err) : "no error"));
+
+ mark_file_modified(fsp);
+
+ status = smb2_write_complete_nosync(subreq, nwritten, err);
+
+ DEBUG(10, ("smb2: scheduled aio_write completed "
+ "for file %s, offset %.0f, requested %u, "
+ "written = %u (errcode = %d, NTSTATUS = %s)\n",
+ fsp_str_dbg(fsp),
+ (double)aio_ex->offset,
+ (unsigned int)numtowrite,
+ (unsigned int)nwritten,
+ err, nt_errstr(status)));
+
+ if (tevent_req_nterror(subreq, status)) {
+ return;
+ }
+ tevent_req_done(subreq);
+}
diff --git a/source3/smbd/smb2_break.c b/source3/smbd/smb2_break.c
new file mode 100644
index 0000000..f837b8e
--- /dev/null
+++ b/source3/smbd/smb2_break.c
@@ -0,0 +1,495 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "locking/leases_db.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static NTSTATUS smbd_smb2_request_process_lease_break(
+ struct smbd_smb2_request *req);
+
+static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_oplock_level);
+static NTSTATUS smbd_smb2_oplock_break_recv(struct tevent_req *req,
+ uint8_t *out_oplock_level);
+
+static void smbd_smb2_request_oplock_break_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_break(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_oplock_level;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x18);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ /*
+ * Retry as a lease break
+ */
+ return smbd_smb2_request_process_lease_break(req);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_oplock_level = CVAL(inbody, 0x02);
+
+ /* 0x03 1 bytes reserved */
+ /* 0x04 4 bytes reserved */
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ /* Are we awaiting a break message ? */
+ if (in_fsp->oplock_timeout == NULL) {
+ return smbd_smb2_request_error(
+ req, NT_STATUS_INVALID_OPLOCK_PROTOCOL);
+ }
+
+ if (in_oplock_level != SMB2_OPLOCK_LEVEL_NONE &&
+ in_oplock_level != SMB2_OPLOCK_LEVEL_II) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ subreq = smbd_smb2_oplock_break_send(req, req->sconn->ev_ctx,
+ req, in_fsp, in_oplock_level);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_oplock_break_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_oplock_break_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ const uint8_t *inbody;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ uint8_t out_oplock_level = 0;
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_oplock_break_recv(subreq, &out_oplock_level);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ outbody = smbd_smb2_generate_outbody(req, 0x18);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x18); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_oplock_level); /* SMB2 oplock level */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04, 0); /* reserved */
+ SBVAL(outbody.data, 0x08,
+ in_file_id_persistent); /* file id (persistent) */
+ SBVAL(outbody.data, 0x10,
+ in_file_id_volatile); /* file id (volatile) */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_oplock_break_state {
+ struct smbd_smb2_request *smb2req;
+ uint8_t out_oplock_level; /* SMB2 oplock level. */
+};
+
+static struct tevent_req *smbd_smb2_oplock_break_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_oplock_level)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_oplock_break_state *state;
+ struct smb_request *smbreq;
+ int oplocklevel = map_smb2_oplock_levels_to_samba(in_oplock_level);
+ bool break_to_none = (oplocklevel == NO_OPLOCK);
+ bool result;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_oplock_break_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->out_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+
+ DEBUG(10,("smbd_smb2_oplock_break_send: %s - %s, "
+ "samba level %d\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+ oplocklevel));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(5,("smbd_smb2_oplock_break_send: got SMB2 oplock break (%u) from client "
+ "for file %s, %s\n",
+ (unsigned int)in_oplock_level,
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp)));
+
+ if ((fsp->sent_oplock_break == BREAK_TO_NONE_SENT) ||
+ (break_to_none)) {
+ result = remove_oplock(fsp);
+ state->out_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ } else {
+ result = downgrade_oplock(fsp);
+ state->out_oplock_level = SMB2_OPLOCK_LEVEL_II;
+ }
+
+ if (!result) {
+ DEBUG(0, ("smbd_smb2_oplock_break_send: error in removing "
+ "oplock on file %s\n", fsp_str_dbg(fsp)));
+ /* Hmmm. Is this panic justified? */
+ smb_panic("internal tdb error");
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_oplock_break_recv(struct tevent_req *req,
+ uint8_t *out_oplock_level)
+{
+ NTSTATUS status;
+ struct smbd_smb2_oplock_break_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_oplock_break_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_oplock_level = state->out_oplock_level;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+ uint32_t in_lease_state);
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+ uint32_t *out_lease_state);
+
+
+static NTSTATUS smbd_smb2_request_process_lease_break(
+ struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ struct smb2_lease_key in_lease_key;
+ uint32_t in_lease_state;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x24);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_lease_key.data[0] = BVAL(inbody, 8);
+ in_lease_key.data[1] = BVAL(inbody, 16);
+ in_lease_state = IVAL(inbody, 24);
+
+ subreq = smbd_smb2_lease_break_send(req, req->sconn->ev_ctx, req,
+ in_lease_key, in_lease_state);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_lease_break_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_lease_break_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(
+ subreq, struct smbd_smb2_request);
+ const uint8_t *inbody;
+ struct smb2_lease_key in_lease_key;
+ uint32_t out_lease_state = 0;
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_lease_break_recv(subreq, &out_lease_state);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_lease_key.data[0] = BVAL(inbody, 8);
+ in_lease_key.data[1] = BVAL(inbody, 16);
+
+ outbody = smbd_smb2_generate_outbody(req, 0x24);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x24); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+ SIVAL(outbody.data, 0x04, 0); /* flags, must be 0 */
+ SBVAL(outbody.data, 0x08, in_lease_key.data[0]);
+ SBVAL(outbody.data, 0x10, in_lease_key.data[1]);
+ SIVAL(outbody.data, 0x18, out_lease_state);
+ SBVAL(outbody.data, 0x1c, 0); /* leaseduration, must be 0 */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_lease_break_state {
+ uint32_t lease_state;
+};
+
+struct lease_lookup_state {
+ TALLOC_CTX *mem_ctx;
+ /* Return parameters. */
+ uint32_t num_file_ids;
+ struct file_id *ids;
+ NTSTATUS status;
+};
+
+static void lease_parser(
+ uint32_t num_files,
+ const struct leases_db_file *files,
+ void *private_data)
+{
+ struct lease_lookup_state *lls =
+ (struct lease_lookup_state *)private_data;
+
+ lls->status = NT_STATUS_OK;
+ lls->num_file_ids = num_files;
+ lls->status = leases_db_copy_file_ids(lls->mem_ctx,
+ num_files,
+ files,
+ &lls->ids);
+}
+
+static struct tevent_req *smbd_smb2_lease_break_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct smbd_smb2_request *smb2_req, struct smb2_lease_key in_lease_key,
+ uint32_t in_lease_state)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_lease_break_state *state;
+ struct lease_lookup_state lls = {.mem_ctx = mem_ctx};
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_lease_break_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->lease_state = in_lease_state;
+
+ /* Find any file ids with this lease key. */
+ status = leases_db_parse(&smb2_req->xconn->smb2.client.guid,
+ &in_lease_key,
+ lease_parser,
+ &lls);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ DEBUG(10, ("No record for lease key found\n"));
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ if (tevent_req_nterror(req, lls.status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (lls.num_file_ids == 0) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, ev);
+ }
+
+ status = downgrade_lease(smb2_req->xconn->client,
+ lls.num_file_ids,
+ lls.ids,
+ &in_lease_key,
+ in_lease_state);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OPLOCK_BREAK_IN_PROGRESS)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("downgrade_lease returned %s\n",
+ nt_errstr(status)));
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_lease_break_recv(struct tevent_req *req,
+ uint32_t *out_lease_state)
+{
+ struct smbd_smb2_lease_break_state *state = tevent_req_data(
+ req, struct smbd_smb2_lease_break_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *out_lease_state = state->lease_state;
+ return NT_STATUS_OK;
+}
+
+/*********************************************************
+ Create and send an asynchronous
+ SMB2 OPLOCK_BREAK_NOTIFICATION.
+*********************************************************/
+
+void send_break_message_smb2(files_struct *fsp,
+ uint32_t break_from,
+ uint32_t break_to)
+{
+ struct smbXsrv_client *client =
+ fsp->conn->sconn->client;
+ NTSTATUS status;
+
+ if (!NT_STATUS_IS_OK(fsp->op->status)) {
+ DBG_DEBUG("skip oplock break for file %s, %s, "
+ "smb2 level %u fsp status=%s\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ (unsigned int)break_to,
+ nt_errstr(fsp->op->status));
+ return;
+ }
+
+ DBG_DEBUG("sending oplock break "
+ "for file %s, %s, smb2 level %u\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ (unsigned int)break_to);
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ uint32_t break_flags = 0;
+ uint16_t new_epoch;
+
+ if (fsp->lease->lease.lease_state != SMB2_LEASE_NONE) {
+ break_flags = SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED;
+ }
+
+ if (fsp->lease->lease.lease_version > 1) {
+ new_epoch = fsp->lease->lease.lease_epoch;
+ } else {
+ new_epoch = 0;
+ }
+
+ status = smbd_smb2_send_lease_break(client, new_epoch, break_flags,
+ &fsp->lease->lease.lease_key,
+ break_from, break_to);
+ } else {
+ uint8_t smb2_oplock_level;
+ smb2_oplock_level = (break_to & SMB2_LEASE_READ) ?
+ SMB2_OPLOCK_LEVEL_II : SMB2_OPLOCK_LEVEL_NONE;
+ status = smbd_smb2_send_oplock_break(client,
+ fsp->op,
+ smb2_oplock_level);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(client,
+ nt_errstr(status));
+ return;
+ }
+}
diff --git a/source3/smbd/smb2_close.c b/source3/smbd/smb2_close.c
new file mode 100644
index 0000000..996ca8b
--- /dev/null
+++ b/source3/smbd/smb2_close.c
@@ -0,0 +1,427 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint16_t in_flags);
+static NTSTATUS smbd_smb2_close_recv(struct tevent_req *req,
+ uint16_t *out_flags,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes);
+
+static void smbd_smb2_request_close_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_close(struct smbd_smb2_request *req)
+{
+ const uint8_t *inbody;
+ uint16_t in_flags;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ NTSTATUS status;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x18);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_flags = SVAL(inbody, 0x02);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_close_send(req, req->sconn->ev_ctx,
+ req, in_fsp, in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_close_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_close_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ uint16_t out_flags = 0;
+ connection_struct *conn = req->tcon->compat;
+ struct timespec out_creation_ts = { 0, };
+ struct timespec out_last_access_ts = { 0, };
+ struct timespec out_last_write_ts = { 0, };
+ struct timespec out_change_ts = { 0, };
+ uint64_t out_allocation_size = 0;
+ uint64_t out_end_of_file = 0;
+ uint32_t out_file_attributes = 0;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_close_recv(subreq,
+ &out_flags,
+ &out_creation_ts,
+ &out_last_access_ts,
+ &out_last_write_ts,
+ &out_change_ts,
+ &out_allocation_size,
+ &out_end_of_file,
+ &out_file_attributes);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x3C);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x3C); /* struct size */
+ SSVAL(outbody.data, 0x02, out_flags);
+ SIVAL(outbody.data, 0x04, 0); /* reserved */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x08, &out_creation_ts);
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x10, &out_last_access_ts);
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x18, &out_last_write_ts);
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x20, &out_change_ts);
+ SBVAL(outbody.data, 0x28, out_allocation_size);
+ SBVAL(outbody.data, 0x30, out_end_of_file);
+ SIVAL(outbody.data, 0x38, out_file_attributes);
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static void setup_close_full_information(connection_struct *conn,
+ struct smb_filename *smb_fname,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint16_t *out_flags,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file)
+{
+ *out_flags = SMB2_CLOSE_FLAGS_FULL_INFORMATION;
+ *out_last_write_ts = smb_fname->st.st_ex_mtime;
+ *out_last_access_ts = smb_fname->st.st_ex_atime;
+ *out_creation_ts = get_create_timespec(conn, NULL, smb_fname);
+ *out_change_ts = get_change_timespec(conn, NULL, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(out_creation_ts);
+ dos_filetime_timespec(out_last_write_ts);
+ dos_filetime_timespec(out_last_access_ts);
+ dos_filetime_timespec(out_change_ts);
+ }
+ if (!S_ISDIR(smb_fname->st.st_ex_mode)) {
+ *out_end_of_file = get_file_size_stat(&smb_fname->st);
+ }
+
+ *out_allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, NULL, &smb_fname->st);
+}
+
+static NTSTATUS smbd_smb2_close(struct smbd_smb2_request *req,
+ struct files_struct **_fsp,
+ uint16_t in_flags,
+ uint16_t *out_flags,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes)
+{
+ NTSTATUS status;
+ struct smb_request *smbreq;
+ connection_struct *conn = req->tcon->compat;
+ struct files_struct *fsp = *_fsp;
+ struct smb_filename *smb_fname = NULL;
+
+ *out_creation_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+ *out_last_access_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+ *out_last_write_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+ *out_change_ts = (struct timespec){0, SAMBA_UTIME_OMIT};
+
+ *out_flags = 0;
+ *out_allocation_size = 0;
+ *out_end_of_file = 0;
+ *out_file_attributes = 0;
+
+ DEBUG(10,("smbd_smb2_close: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(req, fsp);
+ if (smbreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (in_flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) {
+ *out_file_attributes = fdos_mode(fsp);
+ fsp->fsp_flags.fstat_before_close = true;
+ }
+
+ status = close_file_smb(smbreq, fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("smbd_smb2_close: close_file[%s]: %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status)));
+ file_free(smbreq, fsp);
+ *_fsp = fsp = NULL;
+ return status;
+ }
+
+ if (in_flags & SMB2_CLOSE_FLAGS_FULL_INFORMATION) {
+ setup_close_full_information(conn,
+ fsp->fsp_name,
+ out_creation_ts,
+ out_last_access_ts,
+ out_last_write_ts,
+ out_change_ts,
+ out_flags,
+ out_allocation_size,
+ out_end_of_file);
+ }
+
+ file_free(smbreq, fsp);
+ *_fsp = fsp = NULL;
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_close_state {
+ struct smbd_smb2_request *smb2req;
+ struct files_struct *in_fsp;
+ uint16_t in_flags;
+ uint16_t out_flags;
+ struct timespec out_creation_ts;
+ struct timespec out_last_access_ts;
+ struct timespec out_last_write_ts;
+ struct timespec out_change_ts;
+ uint64_t out_allocation_size;
+ uint64_t out_end_of_file;
+ uint32_t out_file_attributes;
+ struct tevent_queue *wait_queue;
+};
+
+static void smbd_smb2_close_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_close_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint16_t in_flags)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_close_state *state;
+ const char *fsp_name_str = NULL;
+ const char *fsp_fnum_str = NULL;
+ unsigned i;
+ NTSTATUS status;
+
+ if (CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ fsp_name_str = fsp_str_dbg(in_fsp);
+ fsp_fnum_str = fsp_fnum_dbg(in_fsp);
+ }
+
+ DBG_DEBUG("%s - %s\n", fsp_name_str, fsp_fnum_str);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->in_fsp = in_fsp;
+ state->in_flags = in_flags;
+
+ in_fsp->fsp_flags.closing = true;
+
+ i = 0;
+ while (i < in_fsp->num_aio_requests) {
+ bool ok = tevent_req_cancel(in_fsp->aio_requests[i]);
+ if (ok) {
+ continue;
+ }
+ i += 1;
+ }
+
+ if (in_fsp->num_aio_requests != 0) {
+ struct tevent_req *subreq;
+
+ state->wait_queue = tevent_queue_create(state,
+ "smbd_smb2_close_send_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * Now wait until all aio requests on this fsp are
+ * finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of fsp->aio_request
+ * will remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(in_fsp->aio_requests,
+ smb2req->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are
+ * finished.
+ */
+ subreq = tevent_queue_wait_send(state,
+ smb2req->sconn->ev_ctx,
+ state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, smbd_smb2_close_wait_done, req);
+ return req;
+ }
+
+ status = smbd_smb2_close(smb2req,
+ &state->in_fsp,
+ state->in_flags,
+ &state->out_flags,
+ &state->out_creation_ts,
+ &state->out_last_access_ts,
+ &state->out_last_write_ts,
+ &state->out_change_ts,
+ &state->out_allocation_size,
+ &state->out_end_of_file,
+ &state->out_file_attributes);
+ if (tevent_req_nterror(req, status)) {
+ DBG_INFO("%s - %s: close file failed: %s\n",
+ fsp_name_str, fsp_fnum_str,
+ nt_errstr(status));
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_close_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_close_state *state = tevent_req_data(
+ req, struct smbd_smb2_close_state);
+ NTSTATUS status;
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ status = smbd_smb2_close(state->smb2req,
+ &state->in_fsp,
+ state->in_flags,
+ &state->out_flags,
+ &state->out_creation_ts,
+ &state->out_last_access_ts,
+ &state->out_last_write_ts,
+ &state->out_change_ts,
+ &state->out_allocation_size,
+ &state->out_end_of_file,
+ &state->out_file_attributes);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_close_recv(struct tevent_req *req,
+ uint16_t *out_flags,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes)
+{
+ struct smbd_smb2_close_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_close_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_flags = state->out_flags;
+ *out_creation_ts = state->out_creation_ts;
+ *out_last_access_ts = state->out_last_access_ts;
+ *out_last_write_ts = state->out_last_write_ts;
+ *out_change_ts = state->out_change_ts;
+ *out_allocation_size = state->out_allocation_size;
+ *out_end_of_file = state->out_end_of_file;
+ *out_file_attributes = state->out_file_attributes;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_create.c b/source3/smbd/smb2_create.c
new file mode 100644
index 0000000..8a40717
--- /dev/null
+++ b/source3/smbd/smb2_create.c
@@ -0,0 +1,2113 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "printing.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "../libcli/smb/smb_common.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_smb2_lease_struct.h"
+#include "../librpc/gen_ndr/ndr_smb3posix.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "messages.h"
+#include "lib/util_ea.h"
+#include "source3/passdb/lookup_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+int map_smb2_oplock_levels_to_samba(uint8_t in_oplock_level)
+{
+ switch(in_oplock_level) {
+ case SMB2_OPLOCK_LEVEL_NONE:
+ return NO_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_II:
+ return LEVEL_II_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_EXCLUSIVE:
+ return EXCLUSIVE_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_BATCH:
+ return BATCH_OPLOCK;
+ case SMB2_OPLOCK_LEVEL_LEASE:
+ return LEASE_OPLOCK;
+ default:
+ DEBUG(2,("map_smb2_oplock_levels_to_samba: "
+ "unknown level %u\n",
+ (unsigned int)in_oplock_level));
+ return NO_OPLOCK;
+ }
+}
+
+static uint8_t map_samba_oplock_levels_to_smb2(int oplock_type)
+{
+ if (BATCH_OPLOCK_TYPE(oplock_type)) {
+ return SMB2_OPLOCK_LEVEL_BATCH;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(oplock_type)) {
+ return SMB2_OPLOCK_LEVEL_EXCLUSIVE;
+ } else if (oplock_type == LEVEL_II_OPLOCK) {
+ return SMB2_OPLOCK_LEVEL_II;
+ } else if (oplock_type == LEASE_OPLOCK) {
+ return SMB2_OPLOCK_LEVEL_LEASE;
+ } else {
+ return SMB2_OPLOCK_LEVEL_NONE;
+ }
+}
+
+/*
+ MS-FSA 2.1.5.1 Server Requests an Open of a File
+ Trailing '/' or '\\' checker.
+ Must be done before the filename parser removes any
+ trailing characters. If we decide to add this to SMB1
+ NTCreate processing we can make this public.
+
+ Note this is Windows pathname processing only. When
+ POSIX pathnames are added to SMB2 this will not apply.
+*/
+
+static NTSTATUS windows_name_trailing_check(const char *name,
+ uint32_t create_options)
+{
+ size_t name_len = strlen(name);
+ char trail_c;
+
+ if (name_len <= 1) {
+ return NT_STATUS_OK;
+ }
+
+ trail_c = name[name_len-1];
+
+ /*
+ * Trailing '/' is always invalid.
+ */
+ if (trail_c == '/') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ if (create_options & FILE_NON_DIRECTORY_FILE) {
+ if (trail_c == '\\') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint8_t in_oplock_level,
+ uint32_t in_impersonation_level,
+ uint32_t in_desired_access,
+ uint32_t in_file_attributes,
+ uint32_t in_share_access,
+ uint32_t in_create_disposition,
+ uint32_t _in_create_options,
+ const char *in_name,
+ struct smb2_create_blobs in_context_blobs);
+static NTSTATUS smbd_smb2_create_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *out_oplock_level,
+ uint32_t *out_create_action,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes,
+ uint64_t *out_file_id_persistent,
+ uint64_t *out_file_id_volatile,
+ struct smb2_create_blobs *out_context_blobs);
+
+static void smbd_smb2_request_create_done(struct tevent_req *tsubreq);
+NTSTATUS smbd_smb2_request_process_create(struct smbd_smb2_request *smb2req)
+{
+ const uint8_t *inbody;
+ const struct iovec *indyniov;
+ uint8_t in_oplock_level;
+ uint32_t in_impersonation_level;
+ uint32_t in_desired_access;
+ uint32_t in_file_attributes;
+ uint32_t in_share_access;
+ uint32_t in_create_disposition;
+ uint32_t in_create_options;
+ uint16_t in_name_offset;
+ uint16_t in_name_length;
+ DATA_BLOB in_name_buffer;
+ char *in_name_string;
+ size_t in_name_string_size;
+ uint32_t name_offset = 0;
+ uint32_t name_available_length = 0;
+ uint32_t in_context_offset;
+ uint32_t in_context_length;
+ DATA_BLOB in_context_buffer;
+ struct smb2_create_blobs in_context_blobs;
+ uint32_t context_offset = 0;
+ uint32_t context_available_length = 0;
+ uint32_t dyn_offset;
+ NTSTATUS status;
+ bool ok;
+ struct tevent_req *tsubreq;
+
+ status = smbd_smb2_request_verify_sizes(smb2req, 0x39);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(smb2req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(smb2req);
+
+ in_oplock_level = CVAL(inbody, 0x03);
+ in_impersonation_level = IVAL(inbody, 0x04);
+ in_desired_access = IVAL(inbody, 0x18);
+ in_file_attributes = IVAL(inbody, 0x1C);
+ in_share_access = IVAL(inbody, 0x20);
+ in_create_disposition = IVAL(inbody, 0x24);
+ in_create_options = IVAL(inbody, 0x28);
+ in_name_offset = SVAL(inbody, 0x2C);
+ in_name_length = SVAL(inbody, 0x2E);
+ in_context_offset = IVAL(inbody, 0x30);
+ in_context_length = IVAL(inbody, 0x34);
+
+ /*
+ * First check if the dynamic name and context buffers
+ * are correctly specified.
+ *
+ * Note: That we don't check if the name and context buffers
+ * overlap
+ */
+
+ dyn_offset = SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(smb2req);
+
+ if (in_name_offset == 0 && in_name_length == 0) {
+ /* This is ok */
+ name_offset = 0;
+ } else if (in_name_offset < dyn_offset) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ } else {
+ name_offset = in_name_offset - dyn_offset;
+ }
+
+ indyniov = SMBD_SMB2_IN_DYN_IOV(smb2req);
+
+ if (name_offset > indyniov->iov_len) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ name_available_length = indyniov->iov_len - name_offset;
+
+ if (in_name_length > name_available_length) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_name_buffer.data = (uint8_t *)indyniov->iov_base + name_offset;
+ in_name_buffer.length = in_name_length;
+
+ if (in_context_offset == 0 && in_context_length == 0) {
+ /* This is ok */
+ context_offset = 0;
+ } else if (in_context_offset < dyn_offset) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ } else {
+ context_offset = in_context_offset - dyn_offset;
+ }
+
+ if (context_offset > indyniov->iov_len) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ context_available_length = indyniov->iov_len - context_offset;
+
+ if (in_context_length > context_available_length) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_context_buffer.data = (uint8_t *)indyniov->iov_base +
+ context_offset;
+ in_context_buffer.length = in_context_length;
+
+ /*
+ * Now interpret the name and context buffers
+ */
+
+ ok = convert_string_talloc(smb2req, CH_UTF16, CH_UNIX,
+ in_name_buffer.data,
+ in_name_buffer.length,
+ &in_name_string,
+ &in_name_string_size);
+ if (!ok) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_ILLEGAL_CHARACTER);
+ }
+
+ if (in_name_buffer.length == 0) {
+ in_name_string_size = 0;
+ }
+
+ if (strlen(in_name_string) != in_name_string_size) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_OBJECT_NAME_INVALID);
+ }
+
+ ZERO_STRUCT(in_context_blobs);
+ status = smb2_create_blob_parse(smb2req, in_context_buffer, &in_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(smb2req, status);
+ }
+
+ if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) {
+ char *str = talloc_asprintf(
+ talloc_tos(),
+ "\nGot %"PRIu32" create blobs\n",
+ in_context_blobs.num_blobs);
+ uint32_t i;
+
+ for (i=0; i<in_context_blobs.num_blobs; i++) {
+ struct smb2_create_blob *b =
+ &in_context_blobs.blobs[i];
+ talloc_asprintf_addbuf(&str, "[%"PRIu32"]\n", i);
+ dump_data_addbuf(
+ (uint8_t *)b->tag, strlen(b->tag), &str);
+ dump_data_addbuf(
+ b->data.data, b->data.length, &str);
+ }
+ DBG_DEBUG("%s", str);
+ TALLOC_FREE(str);
+ }
+
+ tsubreq = smbd_smb2_create_send(smb2req,
+ smb2req->sconn->ev_ctx,
+ smb2req,
+ in_oplock_level,
+ in_impersonation_level,
+ in_desired_access,
+ in_file_attributes,
+ in_share_access,
+ in_create_disposition,
+ in_create_options,
+ in_name_string,
+ in_context_blobs);
+ if (tsubreq == NULL) {
+ smb2req->subreq = NULL;
+ return smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(tsubreq, smbd_smb2_request_create_done, smb2req);
+
+ return smbd_smb2_request_pending_queue(smb2req, tsubreq, 500);
+}
+
+static uint64_t get_mid_from_smb2req(struct smbd_smb2_request *smb2req)
+{
+ uint8_t *reqhdr = SMBD_SMB2_OUT_HDR_PTR(smb2req);
+ return BVAL(reqhdr, SMB2_HDR_MESSAGE_ID);
+}
+
+static void smbd_smb2_request_create_done(struct tevent_req *tsubreq)
+{
+ struct smbd_smb2_request *smb2req = tevent_req_callback_data(tsubreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint8_t out_oplock_level = 0;
+ uint32_t out_create_action = 0;
+ connection_struct *conn = smb2req->tcon->compat;
+ struct timespec out_creation_ts = { 0, };
+ struct timespec out_last_access_ts = { 0, };
+ struct timespec out_last_write_ts = { 0, };
+ struct timespec out_change_ts = { 0, };
+ uint64_t out_allocation_size = 0;
+ uint64_t out_end_of_file = 0;
+ uint32_t out_file_attributes = 0;
+ uint64_t out_file_id_persistent = 0;
+ uint64_t out_file_id_volatile = 0;
+ struct smb2_create_blobs out_context_blobs;
+ DATA_BLOB out_context_buffer;
+ uint16_t out_context_buffer_offset = 0;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_create_recv(tsubreq,
+ smb2req,
+ &out_oplock_level,
+ &out_create_action,
+ &out_creation_ts,
+ &out_last_access_ts,
+ &out_last_write_ts,
+ &out_change_ts,
+ &out_allocation_size,
+ &out_end_of_file,
+ &out_file_attributes,
+ &out_file_id_persistent,
+ &out_file_id_volatile,
+ &out_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (smbd_smb2_is_compound(smb2req)) {
+ smb2req->compound_create_err = status;
+ }
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ status = smb2_create_blob_push(smb2req, &out_context_buffer, out_context_blobs);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ if (out_context_buffer.length > 0) {
+ out_context_buffer_offset = SMB2_HDR_BODY + 0x58;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x58);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x58 + 1); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_oplock_level); /* oplock level */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ out_create_action); /* create action */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x08,
+ &out_creation_ts); /* creation time */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x10,
+ &out_last_access_ts); /* last access time */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x18,
+ &out_last_write_ts); /* last write time */
+ put_long_date_full_timespec(conn->ts_res,
+ (char *)outbody.data + 0x20,
+ &out_change_ts); /* change time */
+ SBVAL(outbody.data, 0x28,
+ out_allocation_size); /* allocation size */
+ SBVAL(outbody.data, 0x30,
+ out_end_of_file); /* end of file */
+ SIVAL(outbody.data, 0x38,
+ out_file_attributes); /* file attributes */
+ SIVAL(outbody.data, 0x3C, 0); /* reserved */
+ SBVAL(outbody.data, 0x40,
+ out_file_id_persistent); /* file id (persistent) */
+ SBVAL(outbody.data, 0x48,
+ out_file_id_volatile); /* file id (volatile) */
+ SIVAL(outbody.data, 0x50,
+ out_context_buffer_offset); /* create contexts offset */
+ SIVAL(outbody.data, 0x54,
+ out_context_buffer.length); /* create contexts length */
+
+ outdyn = out_context_buffer;
+
+ error = smbd_smb2_request_done(smb2req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static bool smb2_lease_key_valid(const struct smb2_lease_key *key)
+{
+ return ((key->data[0] != 0) || (key->data[1] != 0));
+}
+
+static NTSTATUS smbd_smb2_create_durable_lease_check(struct smb_request *smb1req,
+ const char *requested_filename, const struct files_struct *fsp,
+ const struct smb2_lease *lease_ptr)
+{
+ struct files_struct *dirfsp = NULL;
+ char *filename = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t ucf_flags;
+ NTTIME twrp = fsp->fsp_name->twrp;
+ NTSTATUS status;
+ bool is_dfs = (smb1req->flags2 & FLAGS2_DFS_PATHNAMES);
+ bool is_posix = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+
+ if (lease_ptr == NULL) {
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ return NT_STATUS_OK;
+ }
+ DEBUG(10, ("Reopened file has lease, but no lease "
+ "requested\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ DEBUG(10, ("Lease requested, but reopened file has no "
+ "lease\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!smb2_lease_key_equal(&lease_ptr->lease_key,
+ &fsp->lease->lease.lease_key)) {
+ DEBUG(10, ("Different lease key requested than found "
+ "in reopened file\n"));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (is_dfs) {
+ const char *non_dfs_requested_filename = NULL;
+ /*
+ * With a DFS flag set, remove any DFS prefix
+ * before further processing.
+ */
+ status = smb2_strip_dfs_path(requested_filename,
+ &non_dfs_requested_filename);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /*
+ * TODO: Note for dealing with reparse point errors.
+ * We will need to remember and store the number of characters
+ * we have removed here, which is
+ * (requested_filename - non_dfs_requested_filename)
+ * in order to correctly report how many characters we
+ * have removed before hitting the reparse point.
+ * This will be a patch needed once we properly
+ * deal with reparse points later.
+ */
+ requested_filename = non_dfs_requested_filename;
+ /*
+ * Now we're no longer dealing with a DFS path, so
+ * remove the flag.
+ */
+ smb1req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ is_dfs = false;
+ }
+
+ filename = talloc_strdup(talloc_tos(), requested_filename);
+ if (filename == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* This also converts '\' to '/' */
+ status = check_path_syntax(filename, is_posix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(filename);
+ return status;
+ }
+
+ ucf_flags = filename_create_ucf_flags(smb1req, FILE_OPEN);
+ status = filename_convert_dirfsp(talloc_tos(),
+ fsp->conn,
+ filename,
+ ucf_flags,
+ twrp,
+ &dirfsp,
+ &smb_fname);
+ TALLOC_FREE(filename);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("filename_convert returned %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!strequal(fsp->fsp_name->base_name, smb_fname->base_name)) {
+ DEBUG(10, ("Lease requested for file %s, reopened file "
+ "is named %s\n", smb_fname->base_name,
+ fsp->fsp_name->base_name));
+ TALLOC_FREE(smb_fname);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ TALLOC_FREE(smb_fname);
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_create_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ struct GUID req_guid;
+ struct smb_request *smb1req;
+ bool open_was_deferred;
+ struct tevent_immediate *im;
+ struct timeval request_time;
+ struct file_id id;
+ struct deferred_open_record *open_rec;
+ files_struct *result;
+ bool replay_operation;
+ uint8_t in_oplock_level;
+ uint32_t in_create_disposition;
+ uint32_t in_create_options;
+ int requested_oplock_level;
+ int info;
+ char *fname;
+ struct ea_list *ea_list;
+ NTTIME max_access_time;
+ struct security_descriptor *sec_desc;
+ uint64_t allocation_size;
+ struct GUID _create_guid;
+ struct GUID *create_guid;
+ struct GUID _purge_create_guid;
+ struct GUID *purge_create_guid;
+ bool update_open;
+ bool durable_requested;
+ uint32_t durable_timeout_msec;
+ bool do_durable_reconnect;
+ uint64_t persistent_id;
+ struct smb2_lease lease;
+ struct smb2_lease *lease_ptr;
+ ssize_t lease_len;
+ bool need_replay_cache;
+ struct smbXsrv_open *op;
+ NTTIME twrp_time;
+
+ struct smb2_create_blob *dhnc;
+ struct smb2_create_blob *dh2c;
+ struct smb2_create_blob *dhnq;
+ struct smb2_create_blob *dh2q;
+ struct smb2_create_blob *rqls;
+ struct smb2_create_blob *exta;
+ struct smb2_create_blob *mxac;
+ struct smb2_create_blob *secd;
+ struct smb2_create_blob *alsi;
+ struct smb2_create_blob *twrp;
+ struct smb2_create_blob *qfid;
+ struct smb2_create_blob *posx;
+ struct smb2_create_blob *svhdx;
+
+ uint8_t out_oplock_level;
+ uint32_t out_create_action;
+ struct timespec out_creation_ts;
+ struct timespec out_last_access_ts;
+ struct timespec out_last_write_ts;
+ struct timespec out_change_ts;
+ uint64_t out_allocation_size;
+ uint64_t out_end_of_file;
+ uint32_t out_file_attributes;
+ uint64_t out_file_id_persistent;
+ uint64_t out_file_id_volatile;
+ struct smb2_create_blobs *out_context_blobs;
+};
+
+static void smbd_smb2_create_purge_replay_cache(struct tevent_req *req,
+ const char *caller_func);
+
+static void smbd_smb2_create_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ smbd_smb2_create_purge_replay_cache(req, __func__);
+}
+
+static NTSTATUS smbd_smb2_create_fetch_create_ctx(
+ struct tevent_req *req,
+ struct smb2_create_blobs *in_context_blobs)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ struct smbd_smb2_request *smb2req = state->smb2req;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+
+ state->dhnq = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DHNQ);
+ state->dhnc = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DHNC);
+ state->dh2q = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DH2Q);
+ state->dh2c = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_DH2C);
+ if (xconn->smb2.server.capabilities & SMB2_CAP_LEASING) {
+ state->rqls = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_RQLS);
+ }
+
+ if (((state->dhnc != NULL) && (state->dh2c != NULL)) ||
+ ((state->dhnc != NULL) && (state->dh2q != NULL)) ||
+ ((state->dh2c != NULL) && (state->dhnq != NULL)) ||
+ ((state->dh2q != NULL) && (state->dh2c != NULL)))
+ {
+ /* not both are allowed at the same time */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (state->dhnc != NULL) {
+ uint32_t num_blobs_allowed;
+
+ if (state->dhnc->data.length != 16) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * According to MS-SMB2: 3.3.5.9.7, "Handling the
+ * SMB2_CREATE_DURABLE_HANDLE_RECONNECT Create Context",
+ * we should ignore an additional dhnq blob, but fail
+ * the request (with status OBJECT_NAME_NOT_FOUND) if
+ * any other extra create blob has been provided.
+ *
+ * (Note that the cases of an additional dh2q or dh2c blob
+ * which require a different error code, have been treated
+ * above.)
+ */
+
+ if (state->dhnq != NULL) {
+ num_blobs_allowed = 2;
+ } else {
+ num_blobs_allowed = 1;
+ }
+
+ if (state->rqls != NULL) {
+ num_blobs_allowed += 1;
+ }
+
+ if (in_context_blobs->num_blobs != num_blobs_allowed) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ }
+
+ if (state->dh2c!= NULL) {
+ uint32_t num_blobs_allowed;
+
+ if (state->dh2c->data.length != 36) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * According to MS-SMB2: 3.3.5.9.12, "Handling the
+ * SMB2_CREATE_DURABLE_HANDLE_RECONNECT_V2 Create Context",
+ * we should fail the request with status
+ * OBJECT_NAME_NOT_FOUND if any other create blob has been
+ * provided.
+ *
+ * (Note that the cases of an additional dhnq, dhnc or dh2q
+ * blob which require a different error code, have been
+ * treated above.)
+ */
+
+ num_blobs_allowed = 1;
+
+ if (state->rqls != NULL) {
+ num_blobs_allowed += 1;
+ }
+
+ if (in_context_blobs->num_blobs != num_blobs_allowed) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+ }
+
+ state->exta = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_EXTA);
+ state->mxac = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_MXAC);
+ state->secd = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_SECD);
+ state->alsi = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_ALSI);
+ state->twrp = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_TWRP);
+ state->qfid = smb2_create_blob_find(in_context_blobs,
+ SMB2_CREATE_TAG_QFID);
+ if (xconn->protocol >= PROTOCOL_SMB3_02) {
+ /*
+ * This was introduced with SMB3_02
+ */
+ state->svhdx = smb2_create_blob_find(
+ in_context_blobs, SVHDX_OPEN_DEVICE_CONTEXT);
+ }
+ if (xconn->smb2.server.posix_extensions_negotiated &&
+ lp_smb3_unix_extensions(SNUM(state->smb1req->conn)))
+ {
+ /*
+ * Negprot only allowed this for proto>=3.11
+ */
+ SMB_ASSERT(xconn->protocol >= PROTOCOL_SMB3_11);
+
+ state->posx = smb2_create_blob_find(
+ in_context_blobs, SMB2_CREATE_TAG_POSIX);
+ /*
+ * Setting the bool below will cause
+ * ucf_flags_from_smb_request() to
+ * return UCF_POSIX_PATHNAMES in ucf_flags.
+ */
+ state->smb1req->posix_pathnames = (state->posx != NULL);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_create_before_exec(struct tevent_req *req);
+static void smbd_smb2_create_after_exec(struct tevent_req *req);
+static void smbd_smb2_create_finish(struct tevent_req *req);
+
+static struct tevent_req *smbd_smb2_create_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint8_t in_oplock_level,
+ uint32_t in_impersonation_level,
+ uint32_t in_desired_access,
+ uint32_t in_file_attributes,
+ uint32_t in_share_access,
+ uint32_t in_create_disposition,
+ uint32_t _in_create_options,
+ const char *in_name,
+ struct smb2_create_blobs in_context_blobs)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_create_state *state = NULL;
+ NTSTATUS status;
+ struct smb_request *smb1req = NULL;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ uint32_t ucf_flags;
+ bool is_dfs = false;
+ bool is_posix = false;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_create_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct smbd_smb2_create_state) {
+ .ev = ev,
+ .smb2req = smb2req,
+ .in_oplock_level = in_oplock_level,
+ .in_create_disposition = in_create_disposition,
+ .in_create_options = _in_create_options,
+ };
+
+ smb1req = smbd_smb2_fake_smb_request(smb2req, NULL);
+ if (tevent_req_nomem(smb1req, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+ state->smb1req = smb1req;
+
+ state->req_guid = smbd_request_guid(smb1req, 0);
+
+ tevent_req_set_cleanup_fn(req, smbd_smb2_create_cleanup);
+
+ if (smb2req->subreq == NULL) {
+ DBG_DEBUG("name [%s]\n", in_name);
+ } else {
+ struct smbd_smb2_create_state *old_state = tevent_req_data(
+ smb2req->subreq, struct smbd_smb2_create_state);
+
+ DBG_DEBUG("reentrant for file %s\n", in_name);
+
+ state->id = old_state->id;
+ state->request_time = old_state->request_time;
+ state->open_rec = talloc_move(state, &old_state->open_rec);
+ state->open_was_deferred = old_state->open_was_deferred;
+ state->_purge_create_guid = old_state->_purge_create_guid;
+ state->purge_create_guid = old_state->purge_create_guid;
+ old_state->purge_create_guid = NULL;
+ }
+
+ TALLOC_FREE(smb2req->subreq);
+ smb2req->subreq = req;
+
+ if (lp_fake_oplocks(SNUM(smb2req->tcon->compat))) {
+ state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ } else {
+ state->requested_oplock_level = state->in_oplock_level;
+ }
+
+ /* these are ignored for SMB2 */
+ state->in_create_options &= ~(0x10); /* NTCREATEX_OPTIONS_SYNC_ALERT */
+ state->in_create_options &= ~(0x20); /* NTCREATEX_OPTIONS_ASYNC_ALERT */
+
+ in_file_attributes &= ~FILE_FLAG_POSIX_SEMANTICS;
+
+ is_dfs = (smb1req->flags2 & FLAGS2_DFS_PATHNAMES);
+ if (is_dfs) {
+ const char *non_dfs_in_name = NULL;
+ /*
+ * With a DFS flag set, remove any DFS prefix
+ * before further processing.
+ */
+ status = smb2_strip_dfs_path(in_name, &non_dfs_in_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, state->ev);
+ }
+ /*
+ * TODO: Note for dealing with reparse point errors.
+ * We will need to remember and store the number of characters
+ * we have removed here, which is (non_dfs_in_name - in_name)
+ * in order to correctly report how many characters we
+ * have removed before hitting the reparse point.
+ * This will be a patch needed once we properly
+ * deal with reparse points later.
+ */
+ in_name = non_dfs_in_name;
+ /*
+ * Now we're no longer dealing with a DFS path, so
+ * remove the flag.
+ */
+ smb1req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ is_dfs = false;
+ }
+
+ state->fname = talloc_strdup(state, in_name);
+ if (tevent_req_nomem(state->fname, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ state->out_context_blobs = talloc_zero(state, struct smb2_create_blobs);
+ if (tevent_req_nomem(state->out_context_blobs, req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = smbd_smb2_create_fetch_create_ctx(req, &in_context_blobs);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ if (IS_IPC(smb1req->conn)) {
+ const char *pipe_name = in_name;
+
+ if (state->dhnc != NULL || state->dh2c != NULL) {
+ /* durable handles are not supported on IPC$ */
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, state->ev);
+ }
+
+ if (!lp_nt_pipe_support()) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = open_np_file(smb1req, pipe_name, &state->result);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+ state->info = FILE_WAS_OPENED;
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ if (CAN_PRINT(smb1req->conn)) {
+ if (state->dhnc != NULL || state->dh2c != NULL) {
+ /* durable handles are not supported on printers */
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = file_new(smb1req, smb1req->conn, &state->result);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = print_spool_open(state->result, in_name,
+ smb1req->vuid);
+ if (tevent_req_nterror(req, status)) {
+ file_free(smb1req, state->result);
+ return tevent_req_post(req, state->ev);
+ }
+ state->info = FILE_WAS_CREATED;
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ /* Check for trailing slash specific directory handling. */
+ status = windows_name_trailing_check(state->fname,
+ state->in_create_options);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_before_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ DBG_DEBUG("open execution phase\n");
+
+ /*
+ * For the backend file open procedure, there are
+ * three possible modes: replay operation (in which case
+ * there is nothing else to do), durable_reconnect or
+ * new open.
+ */
+ if (state->replay_operation) {
+ state->result = state->op->compat;
+ state->result->op = state->op;
+ state->update_open = false;
+ state->info = state->op->create_action;
+
+ smbd_smb2_create_after_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ if (state->do_durable_reconnect) {
+ DATA_BLOB new_cookie = data_blob_null;
+ NTTIME now = timeval_to_nttime(&smb2req->request_time);
+
+ status = smb2srv_open_recreate(smb2req->xconn,
+ smb1req->conn->session_info,
+ state->persistent_id,
+ state->create_guid,
+ now,
+ &state->op);
+ if (tevent_req_nterror(req, status)) {
+ DBG_NOTICE("smb2srv_open_recreate failed: %s\n",
+ nt_errstr(status));
+ return tevent_req_post(req, state->ev);
+ }
+
+ DBG_DEBUG("%s to recreate durable handle\n",
+ state->op->global->durable ? "succeeded" : "failed");
+
+ if (!state->op->global->durable) {
+ talloc_free(state->op);
+ tevent_req_nterror(req,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ return tevent_req_post(req, state->ev);
+ }
+
+ status = SMB_VFS_DURABLE_RECONNECT(smb1req->conn,
+ smb1req,
+ state->op, /* smbXsrv_open input */
+ state->op->global->backend_cookie,
+ state->op, /* TALLOC_CTX */
+ &state->result,
+ &new_cookie);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS return_status;
+
+ return_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+ DBG_NOTICE("durable_reconnect failed: %s => %s\n",
+ nt_errstr(status),
+ nt_errstr(return_status));
+
+ tevent_req_nterror(req, return_status);
+ return tevent_req_post(req, state->ev);
+ }
+
+ DBG_DEBUG("oplock_type=%u, lease_ptr==%p\n",
+ (unsigned)state->result->oplock_type, state->lease_ptr);
+
+ status = smbd_smb2_create_durable_lease_check(
+ smb1req, state->fname, state->result, state->lease_ptr);
+ if (tevent_req_nterror(req, status)) {
+ close_file_free(
+ smb1req, &state->result, SHUTDOWN_CLOSE);
+ return tevent_req_post(req, state->ev);
+ }
+
+ data_blob_free(&state->op->global->backend_cookie);
+ state->op->global->backend_cookie = new_cookie;
+
+ state->op->status = NT_STATUS_OK;
+ state->op->global->disconnect_time = 0;
+
+ /* save the timeout for later update */
+ state->durable_timeout_msec = state->op->global->durable_timeout_msec;
+
+ state->update_open = true;
+
+ state->info = FILE_WAS_OPENED;
+
+ smbd_smb2_create_after_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_finish(req);
+ return req;
+ }
+
+ if (state->requested_oplock_level == SMB2_OPLOCK_LEVEL_LEASE) {
+ if (state->lease_ptr == NULL) {
+ state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ }
+ } else {
+ state->lease_ptr = NULL;
+ }
+
+ is_posix = (state->posx != NULL);
+
+ /* convert '\\' into '/' */
+ status = check_path_syntax(state->fname, is_posix);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ ucf_flags = filename_create_ucf_flags(
+ smb1req, state->in_create_disposition);
+
+ status = filename_convert_dirfsp(
+ req,
+ smb1req->conn,
+ state->fname,
+ ucf_flags,
+ state->twrp_time,
+ &dirfsp,
+ &smb_fname);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ /*
+ * MS-SMB2: 2.2.13 SMB2 CREATE Request
+ * ImpersonationLevel ... MUST contain one of the
+ * following values. The server MUST validate this
+ * field, but otherwise ignore it.
+ *
+ * NB. The source4/torture/smb2/durable_open.c test
+ * shows this check is only done on real opens, not
+ * on durable handle-reopens.
+ */
+
+ if (in_impersonation_level >
+ SMB2_IMPERSONATION_DELEGATE) {
+ tevent_req_nterror(req,
+ NT_STATUS_BAD_IMPERSONATION_LEVEL);
+ return tevent_req_post(req, state->ev);
+ }
+
+ /*
+ * We know we're going to do a local open, so now
+ * we must be protocol strict. JRA.
+ *
+ * MS-SMB2: 3.3.5.9 - Receiving an SMB2 CREATE Request
+ * If the file name length is greater than zero and the
+ * first character is a path separator character, the
+ * server MUST fail the request with
+ * STATUS_INVALID_PARAMETER.
+ */
+ if (in_name[0] == '/') {
+ /* Names starting with '/' are never allowed. */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (!is_posix && (in_name[0] == '\\')) {
+ /*
+ * Windows names starting with '\' are not allowed.
+ */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = SMB_VFS_CREATE_FILE(smb1req->conn,
+ smb1req,
+ dirfsp,
+ smb_fname,
+ in_desired_access,
+ in_share_access,
+ state->in_create_disposition,
+ state->in_create_options,
+ in_file_attributes,
+ map_smb2_oplock_levels_to_samba(
+ state->requested_oplock_level),
+ state->lease_ptr,
+ state->allocation_size,
+ 0, /* private_flags */
+ state->sec_desc,
+ state->ea_list,
+ &state->result,
+ &state->info,
+ &in_context_blobs,
+ state->out_context_blobs);
+ if (NT_STATUS_IS_OK(status) &&
+ !(state->in_create_options & FILE_OPEN_REPARSE_POINT))
+ {
+
+ mode_t mode = state->result->fsp_name->st.st_ex_mode;
+
+ if (!(S_ISREG(mode) || S_ISDIR(mode))) {
+ /*
+ * Only open files and dirs without
+ * FILE_OPEN_REPARSE_POINT
+ */
+ close_file_free(smb1req, &state->result, ERROR_CLOSE);
+ status = NT_STATUS_IO_REPARSE_TAG_NOT_HANDLED;
+ }
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (open_was_deferred(smb1req->xconn, smb1req->mid)) {
+ SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(smb2req->profile);
+ return req;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, state->ev);
+ }
+ state->op = state->result->op;
+
+ smbd_smb2_create_after_exec(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, state->ev);
+ }
+
+ smbd_smb2_create_finish(req);
+ return req;
+}
+
+static void smbd_smb2_create_purge_replay_cache(struct tevent_req *req,
+ const char *caller_func)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ NTSTATUS status;
+
+ if (state->purge_create_guid == NULL) {
+ return;
+ }
+
+ status = smbXsrv_open_purge_replay_cache(state->smb2req->xconn->client,
+ state->purge_create_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf buf;
+
+ D_ERR("%s: smbXsrv_open_purge_replay_cache(%s) %s\n",
+ caller_func,
+ GUID_buf_string(state->purge_create_guid, &buf),
+ nt_errstr(status));
+ }
+
+ state->purge_create_guid = NULL;
+}
+
+static void smbd_smb2_create_before_exec(struct tevent_req *req)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ struct smbd_smb2_request *smb2req = state->smb2req;
+ NTSTATUS status;
+
+ if (state->exta != NULL) {
+ if (!lp_ea_support(SNUM(smb2req->tcon->compat))) {
+ tevent_req_nterror(req, NT_STATUS_EAS_NOT_SUPPORTED);
+ return;
+ }
+
+ state->ea_list = read_nttrans_ea_list(
+ state,
+ (const char *)state->exta->data.data,
+ state->exta->data.length);
+ if (state->ea_list == NULL) {
+ DEBUG(10,("smbd_smb2_create_send: read_ea_name_list failed.\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if ((state->posx == NULL) &&
+ ea_list_has_invalid_name(state->ea_list)) {
+ tevent_req_nterror(req, STATUS_INVALID_EA_NAME);
+ return;
+ }
+ }
+
+ if (state->mxac != NULL) {
+ if (state->mxac->data.length == 0) {
+ state->max_access_time = 0;
+ } else if (state->mxac->data.length == 8) {
+ state->max_access_time = BVAL(state->mxac->data.data, 0);
+ } else {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (state->secd != NULL) {
+ enum ndr_err_code ndr_err;
+
+ state->sec_desc = talloc_zero(state, struct security_descriptor);
+ if (tevent_req_nomem(state->sec_desc, req)) {
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&state->secd->data,
+ state->sec_desc, state->sec_desc,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(2,("ndr_pull_security_descriptor failed: %s\n",
+ ndr_errstr(ndr_err)));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (state->dhnq != NULL) {
+ if (state->dhnq->data.length != 16) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (state->dh2q != NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /*
+ * durable handle request is processed below.
+ */
+ state->durable_requested = true;
+ /*
+ * Set the timeout to 16 mins.
+ *
+ * TODO: test this against Windows 2012
+ * as the default for durable v2 is 1 min.
+ */
+ state->durable_timeout_msec = (16*60*1000);
+ }
+
+ if (state->dh2q != NULL) {
+ const uint8_t *p = state->dh2q->data.data;
+ NTTIME now = timeval_to_nttime(&smb2req->request_time);
+ uint32_t durable_v2_timeout = 0;
+ DATA_BLOB create_guid_blob;
+ const uint8_t *hdr;
+ uint32_t flags;
+
+ if (state->dh2q->data.length != 32) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ if (state->dhnq != NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ durable_v2_timeout = IVAL(p, 0);
+ create_guid_blob = data_blob_const(p + 16, 16);
+
+ status = GUID_from_ndr_blob(&create_guid_blob,
+ &state->_create_guid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->create_guid = &state->_create_guid;
+
+ /*
+ * we need to store the create_guid later
+ */
+ state->update_open = true;
+
+ /*
+ * And we need to create a cache for replaying the
+ * create.
+ */
+ state->need_replay_cache = true;
+
+ /*
+ * durable handle v2 request processed below
+ */
+ state->durable_requested = true;
+ state->durable_timeout_msec = MIN(durable_v2_timeout, 300*1000);
+ if (state->durable_timeout_msec == 0) {
+ /*
+ * Set the timeout to 1 min as default.
+ *
+ * This matches Windows 2012.
+ */
+ state->durable_timeout_msec = (60*1000);
+ }
+
+ /*
+ * Check for replay operation.
+ * Only consider it when we have dh2q.
+ * If we do not have a replay operation, verify that
+ * the create_guid is not cached for replay.
+ */
+ hdr = SMBD_SMB2_IN_HDR_PTR(smb2req);
+ flags = IVAL(hdr, SMB2_HDR_FLAGS);
+ state->replay_operation =
+ flags & SMB2_HDR_FLAG_REPLAY_OPERATION;
+
+ status = smb2srv_open_lookup_replay_cache(smb2req->xconn,
+ state->req_guid,
+ *state->create_guid,
+ state->fname,
+ now,
+ &state->op);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FWP_RESERVED)) {
+ /*
+ * We've reserved the replay_cache record
+ * for ourself, indicating we're still
+ * in progress.
+ *
+ * It means the smbd_smb2_create_cleanup()
+ * may need to call smbXsrv_open_purge_replay_cache()
+ * in order to cleanup.
+ */
+ SMB_ASSERT(state->op == NULL);
+ state->_purge_create_guid = state->_create_guid;
+ state->purge_create_guid = &state->_purge_create_guid;
+ status = NT_STATUS_OK;
+ state->replay_operation = false;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_NOT_AVAILABLE)) {
+ tevent_req_nterror(req, status);
+ return;
+ } else if (tevent_req_nterror(req, status)) {
+ DBG_WARNING("smb2srv_open_lookup_replay_cache "
+ "failed: %s\n", nt_errstr(status));
+ return;
+ } else if (!state->replay_operation) {
+ /*
+ * If a create without replay operation flag
+ * is sent but with a create_guid that is
+ * currently in the replay cache -- fail.
+ */
+ status = NT_STATUS_DUPLICATE_OBJECTID;
+ (void)tevent_req_nterror(req, status);
+ return;
+ }
+ }
+
+ if (state->dhnc != NULL) {
+ state->persistent_id = BVAL(state->dhnc->data.data, 0);
+ state->do_durable_reconnect = true;
+ }
+
+ if (state->dh2c != NULL) {
+ const uint8_t *p = state->dh2c->data.data;
+ DATA_BLOB create_guid_blob;
+
+ state->persistent_id = BVAL(p, 0);
+ create_guid_blob = data_blob_const(p + 16, 16);
+
+ status = GUID_from_ndr_blob(&create_guid_blob,
+ &state->_create_guid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->create_guid = &state->_create_guid;
+ state->do_durable_reconnect = true;
+ }
+
+ if (state->alsi != NULL) {
+ if (state->alsi->data.length != 8) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ state->allocation_size = BVAL(state->alsi->data.data, 0);
+ }
+
+ if (state->twrp != NULL) {
+ if (state->twrp->data.length != 8) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ state->twrp_time = BVAL(state->twrp->data.data, 0);
+ }
+
+ if (state->qfid != NULL) {
+ if (state->qfid->data.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+
+ if (state->rqls != NULL) {
+ ssize_t lease_len = -1;
+
+ lease_len = smb2_lease_pull(state->rqls->data.data,
+ state->rqls->data.length,
+ &state->lease);
+ if (lease_len == -1) {
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ state->lease_ptr = &state->lease;
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10, ("Got lease request size %d\n",
+ (int)lease_len));
+ NDR_PRINT_DEBUG(smb2_lease, state->lease_ptr);
+ }
+
+ if (!smb2_lease_key_valid(&state->lease.lease_key)) {
+ state->lease_ptr = NULL;
+ state->requested_oplock_level = SMB2_OPLOCK_LEVEL_NONE;
+ }
+
+ if ((smb2req->xconn->protocol < PROTOCOL_SMB3_00) &&
+ (state->lease.lease_version != 1))
+ {
+ DEBUG(10, ("v2 lease key only for SMB3\n"));
+ state->lease_ptr = NULL;
+ }
+
+ /*
+ * Replay with a lease is only allowed if the
+ * established open carries a lease with the
+ * same lease key.
+ */
+ if (state->replay_operation) {
+ struct smb2_lease *op_ls =
+ &state->op->compat->lease->lease;
+ int op_oplock = state->op->compat->oplock_type;
+
+ if (map_samba_oplock_levels_to_smb2(op_oplock)
+ != SMB2_OPLOCK_LEVEL_LEASE)
+ {
+ status = NT_STATUS_ACCESS_DENIED;
+ (void)tevent_req_nterror(req, status);
+ return;
+ }
+ if (!smb2_lease_key_equal(&state->lease.lease_key,
+ &op_ls->lease_key))
+ {
+ status = NT_STATUS_ACCESS_DENIED;
+ (void)tevent_req_nterror(req, status);
+ return;
+ }
+ }
+ }
+
+ if (state->posx != NULL) {
+ if (state->posx->data.length != 4) {
+ DBG_DEBUG("Got %zu bytes POSX cctx, expected 4\n",
+ state->posx->data.length);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ }
+}
+
+static void smbd_smb2_create_after_exec(struct tevent_req *req)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ connection_struct *conn = state->result->conn;
+ NTSTATUS status;
+
+ /*
+ * here we have op == result->op
+ */
+
+ DBG_DEBUG("response construction phase\n");
+
+ state->out_file_attributes = fdos_mode(state->result);
+
+ if (state->mxac != NULL) {
+ NTTIME last_write_time;
+
+ last_write_time = full_timespec_to_nt_time(
+ &state->result->fsp_name->st.st_ex_mtime);
+ if (last_write_time != state->max_access_time) {
+ uint8_t p[8];
+ uint32_t max_access_granted;
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+
+ status = smbd_calculate_access_mask_fsp(
+ conn->cwd_fsp,
+ state->result,
+ false,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &max_access_granted);
+
+ SIVAL(p, 0, NT_STATUS_V(status));
+ SIVAL(p, 4, max_access_granted);
+
+ status = smb2_create_blob_add(
+ state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_MXAC,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+ }
+
+ if (!state->replay_operation && state->durable_requested &&
+ (fsp_lease_type(state->result) & SMB2_LEASE_HANDLE))
+ {
+ status = SMB_VFS_DURABLE_COOKIE(
+ state->result,
+ state->op,
+ &state->op->global->backend_cookie);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->op->global->backend_cookie = data_blob_null;
+ }
+ }
+ if (!state->replay_operation && state->op->global->backend_cookie.length > 0)
+ {
+ state->update_open = true;
+
+ state->op->global->durable = true;
+ state->op->global->durable_timeout_msec = state->durable_timeout_msec;
+ }
+
+ if (state->update_open) {
+ state->op->global->create_guid = state->_create_guid;
+ if (state->need_replay_cache) {
+ state->op->flags |= SMBXSRV_OPEN_NEED_REPLAY_CACHE;
+ }
+
+ status = smbXsrv_open_update(state->op);
+ DEBUG(10, ("smb2_create_send: smbXsrv_open_update "
+ "returned %s\n",
+ nt_errstr(status)));
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /*
+ * We should not purge the replay cache anymore
+ * as it's attached to the smbXsrv_open record now.
+ */
+ state->purge_create_guid = NULL;
+ }
+
+ if (state->dhnq != NULL && state->op->global->durable) {
+ uint8_t p[8] = { 0, };
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_DHNQ,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (state->dh2q != NULL && state->op->global->durable &&
+ /*
+ * For replay operations, we return the dh2q blob
+ * in the case of oplocks not based on the state of
+ * the open, but on whether it could have been granted
+ * for the request data. In the case of leases instead,
+ * the state of the open is used...
+ */
+ (!state->replay_operation ||
+ state->in_oplock_level == SMB2_OPLOCK_LEVEL_BATCH ||
+ state->in_oplock_level == SMB2_OPLOCK_LEVEL_LEASE))
+ {
+ uint8_t p[8] = { 0, };
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+ uint32_t durable_v2_response_flags = 0;
+
+ SIVAL(p, 0, state->op->global->durable_timeout_msec);
+ SIVAL(p, 4, durable_v2_response_flags);
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_DH2Q,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (state->qfid != NULL) {
+ uint8_t p[32];
+ SMB_STRUCT_STAT *base_sp = state->result->base_fsp ?
+ &state->result->base_fsp->fsp_name->st :
+ &state->result->fsp_name->st;
+ uint64_t file_id = SMB_VFS_FS_FILE_ID(conn, base_sp);
+ DATA_BLOB blob = data_blob_const(p, sizeof(p));
+
+ ZERO_STRUCT(p);
+
+ /* From conversations with Microsoft engineers at
+ the MS plugfest. The first 8 bytes are the "volume index"
+ == inode, the second 8 bytes are the "volume id",
+ == dev. This will be updated in the SMB2 doc. */
+ SBVAL(p, 0, file_id);
+ SIVAL(p, 8, base_sp->st_ex_dev);/* FileIndexHigh */
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_QFID,
+ blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if ((state->rqls != NULL) && (state->result->oplock_type == LEASE_OPLOCK)) {
+ uint8_t buf[52];
+ struct smb2_lease lease;
+ size_t lease_len;
+
+ lease = state->result->lease->lease;
+
+ lease_len = sizeof(buf);
+ if (lease.lease_version == 1) {
+ lease_len = 32;
+ }
+
+ if (!smb2_lease_push(&lease, buf, lease_len)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ status = smb2_create_blob_add(
+ state, state->out_context_blobs,
+ SMB2_CREATE_TAG_RQLS,
+ data_blob_const(buf, lease_len));
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ if (state->posx != NULL) {
+ struct stat_ex *psbuf = &state->result->fsp_name->st;
+ struct smb3_posix_cc_info cc = {
+ .nlinks = psbuf->st_ex_nlink,
+ .posix_perms = unix_perms_to_wire(psbuf->st_ex_mode &
+ ~S_IFMT),
+ };
+ uint8_t buf[sizeof(struct smb3_posix_cc_info)];
+ struct ndr_push ndr = {
+ .data = buf,
+ .alloc_size = sizeof(buf),
+ .fixed_buf_size = true,
+ };
+ enum ndr_err_code ndr_err;
+
+ uid_to_sid(&cc.owner, psbuf->st_ex_uid);
+ gid_to_sid(&cc.group, psbuf->st_ex_gid);
+
+ ndr_err =
+ ndr_push_smb3_posix_cc_info(&ndr,
+ NDR_SCALARS | NDR_BUFFERS,
+ &cc);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = NT_STATUS_INSUFFICIENT_RESOURCES;
+ goto fail;
+ }
+
+ status = smb2_create_blob_add(state->out_context_blobs,
+ state->out_context_blobs,
+ SMB2_CREATE_TAG_POSIX,
+ (DATA_BLOB){
+ .data = buf,
+ .length = ndr.offset,
+ });
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ return;
+
+fail:
+ close_file_free(state->smb1req, &state->result, ERROR_CLOSE);
+ tevent_req_nterror(req, status);
+}
+
+static void smbd_smb2_create_finish(struct tevent_req *req)
+{
+ struct smbd_smb2_create_state *state = tevent_req_data(
+ req, struct smbd_smb2_create_state);
+ struct smbd_smb2_request *smb2req = state->smb2req;
+ struct smb_request *smb1req = state->smb1req;
+ files_struct *result = state->result;
+
+ smb2req->compat_chain_fsp = smb1req->chain_fsp;
+
+ if (state->replay_operation) {
+ state->out_oplock_level = state->in_oplock_level;
+ } else if (lp_fake_oplocks(SNUM(smb2req->tcon->compat))) {
+ state->out_oplock_level = state->in_oplock_level;
+ } else {
+ state->out_oplock_level = map_samba_oplock_levels_to_smb2(result->oplock_type);
+ }
+
+ if ((state->in_create_disposition == FILE_SUPERSEDE)
+ && (state->info == FILE_WAS_OVERWRITTEN)) {
+ state->out_create_action = FILE_WAS_SUPERSEDED;
+ } else {
+ state->out_create_action = state->info;
+ }
+ result->op->create_action = state->out_create_action;
+
+ state->out_creation_ts = get_create_timespec(smb1req->conn,
+ result, result->fsp_name);
+ state->out_last_access_ts = result->fsp_name->st.st_ex_atime;
+ state->out_last_write_ts = result->fsp_name->st.st_ex_mtime;
+ state->out_change_ts = get_change_timespec(smb1req->conn,
+ result, result->fsp_name);
+
+ if (lp_dos_filetime_resolution(SNUM(smb2req->tcon->compat))) {
+ dos_filetime_timespec(&state->out_creation_ts);
+ dos_filetime_timespec(&state->out_last_access_ts);
+ dos_filetime_timespec(&state->out_last_write_ts);
+ dos_filetime_timespec(&state->out_change_ts);
+ }
+
+ state->out_allocation_size =
+ SMB_VFS_GET_ALLOC_SIZE(smb1req->conn, result,
+ &(result->fsp_name->st));
+ state->out_end_of_file = result->fsp_name->st.st_ex_size;
+ if (state->out_file_attributes == 0) {
+ state->out_file_attributes = FILE_ATTRIBUTE_NORMAL;
+ }
+ state->out_file_id_persistent = result->op->global->open_persistent_id;
+ state->out_file_id_volatile = result->op->global->open_volatile_id;
+
+ DBG_DEBUG("%s - %s\n", fsp_str_dbg(result), fsp_fnum_dbg(result));
+
+ tevent_req_done(req);
+ tevent_req_post(req, state->ev);
+}
+
+static NTSTATUS smbd_smb2_create_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *out_oplock_level,
+ uint32_t *out_create_action,
+ struct timespec *out_creation_ts,
+ struct timespec *out_last_access_ts,
+ struct timespec *out_last_write_ts,
+ struct timespec *out_change_ts,
+ uint64_t *out_allocation_size,
+ uint64_t *out_end_of_file,
+ uint32_t *out_file_attributes,
+ uint64_t *out_file_id_persistent,
+ uint64_t *out_file_id_volatile,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ NTSTATUS status;
+ struct smbd_smb2_create_state *state = tevent_req_data(req,
+ struct smbd_smb2_create_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_oplock_level = state->out_oplock_level;
+ *out_create_action = state->out_create_action;
+ *out_creation_ts = state->out_creation_ts;
+ *out_last_access_ts = state->out_last_access_ts;
+ *out_last_write_ts = state->out_last_write_ts;
+ *out_change_ts = state->out_change_ts;
+ *out_allocation_size = state->out_allocation_size;
+ *out_end_of_file = state->out_end_of_file;
+ *out_file_attributes = state->out_file_attributes;
+ *out_file_id_persistent = state->out_file_id_persistent;
+ *out_file_id_volatile = state->out_file_id_volatile;
+ *out_context_blobs = *(state->out_context_blobs);
+
+ talloc_steal(mem_ctx, state->out_context_blobs->blobs);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/*********************************************************
+ Code for dealing with deferred opens.
+*********************************************************/
+
+bool get_deferred_open_message_state_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec)
+{
+ struct smbd_smb2_create_state *state = NULL;
+ struct tevent_req *req = NULL;
+
+ if (!smb2req) {
+ return false;
+ }
+ req = smb2req->subreq;
+ if (!req) {
+ return false;
+ }
+ state = tevent_req_data(req, struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+ if (!state->open_was_deferred) {
+ return false;
+ }
+ if (p_request_time) {
+ *p_request_time = state->request_time;
+ }
+ if (open_rec != NULL) {
+ *open_rec = state->open_rec;
+ }
+ return true;
+}
+
+/*********************************************************
+ Re-process this call early - requested by message or
+ close.
+*********************************************************/
+
+static struct smbd_smb2_request *find_open_smb2req(
+ struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_request *smb2req;
+
+ for (smb2req = xconn->smb2.requests; smb2req; smb2req = smb2req->next) {
+ uint64_t message_id;
+ if (smb2req->subreq == NULL) {
+ /* This message has been processed. */
+ continue;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ /* This message has been processed. */
+ continue;
+ }
+ message_id = get_mid_from_smb2req(smb2req);
+ if (message_id == mid) {
+ return smb2req;
+ }
+ }
+ return NULL;
+}
+
+bool open_was_deferred_smb2(struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_create_state *state = NULL;
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(xconn, mid);
+
+ if (!smb2req) {
+ DEBUG(10,("open_was_deferred_smb2: mid %llu smb2req == NULL\n",
+ (unsigned long long)mid));
+ return false;
+ }
+ if (!smb2req->subreq) {
+ return false;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ return false;
+ }
+ state = tevent_req_data(smb2req->subreq,
+ struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+ /* It's not in progress if there's no timeout event. */
+ if (!state->open_was_deferred) {
+ return false;
+ }
+
+ DEBUG(10,("open_was_deferred_smb2: mid = %llu\n",
+ (unsigned long long)mid));
+
+ return true;
+}
+
+static void remove_deferred_open_message_smb2_internal(struct smbd_smb2_request *smb2req,
+ uint64_t mid)
+{
+ struct smbd_smb2_create_state *state = NULL;
+
+ if (!smb2req->subreq) {
+ return;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ return;
+ }
+ state = tevent_req_data(smb2req->subreq,
+ struct smbd_smb2_create_state);
+ if (!state) {
+ return;
+ }
+
+ DEBUG(10,("remove_deferred_open_message_smb2_internal: "
+ "mid %llu\n",
+ (unsigned long long)mid ));
+
+ state->open_was_deferred = false;
+ /* Ensure we don't have any outstanding immediate event. */
+ TALLOC_FREE(state->im);
+ TALLOC_FREE(state->open_rec);
+}
+
+void remove_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(xconn, mid);
+
+ if (!smb2req) {
+ DEBUG(10,("remove_deferred_open_message_smb2: "
+ "can't find mid %llu\n",
+ (unsigned long long)mid ));
+ return;
+ }
+ remove_deferred_open_message_smb2_internal(smb2req, mid);
+}
+
+static void smbd_smb2_create_request_dispatch_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct smbd_smb2_request *smb2req = talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ uint64_t mid = get_mid_from_smb2req(smb2req);
+ NTSTATUS status;
+
+ DEBUG(10,("smbd_smb2_create_request_dispatch_immediate: "
+ "re-dispatching mid %llu\n",
+ (unsigned long long)mid ));
+
+ status = smbd_smb2_request_dispatch(smb2req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(status));
+ return;
+ }
+}
+
+bool schedule_deferred_open_message_smb2(
+ struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_smb2_create_state *state = NULL;
+ struct smbd_smb2_request *smb2req;
+
+ smb2req = find_open_smb2req(xconn, mid);
+
+ if (!smb2req) {
+ DEBUG(10,("schedule_deferred_open_message_smb2: "
+ "can't find mid %llu\n",
+ (unsigned long long)mid ));
+ return false;
+ }
+ if (!smb2req->subreq) {
+ return false;
+ }
+ if (!tevent_req_is_in_progress(smb2req->subreq)) {
+ return false;
+ }
+ state = tevent_req_data(smb2req->subreq,
+ struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+
+ /* Ensure we don't have any outstanding immediate event. */
+ TALLOC_FREE(state->im);
+
+ /*
+ * This is subtle. We must null out the callback
+ * before rescheduling, else the first call to
+ * tevent_req_nterror() causes the _receive()
+ * function to be called, this causing tevent_req_post()
+ * to crash.
+ */
+ tevent_req_set_callback(smb2req->subreq, NULL, NULL);
+
+ state->im = tevent_create_immediate(smb2req);
+ if (!state->im) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(NT_STATUS_NO_MEMORY));
+ return false;
+ }
+
+ DEBUG(10,("schedule_deferred_open_message_smb2: "
+ "re-processing mid %llu\n",
+ (unsigned long long)mid ));
+
+ tevent_schedule_immediate(state->im,
+ smb2req->sconn->ev_ctx,
+ smbd_smb2_create_request_dispatch_immediate,
+ smb2req);
+
+ return true;
+}
+
+static bool smbd_smb2_create_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_request *smb2req = NULL;
+ struct smbd_smb2_create_state *state = tevent_req_data(req,
+ struct smbd_smb2_create_state);
+ uint64_t mid;
+
+ if (!state) {
+ return false;
+ }
+
+ if (!state->smb2req) {
+ return false;
+ }
+
+ smb2req = state->smb2req;
+ mid = get_mid_from_smb2req(smb2req);
+
+ if (is_deferred_open_async(state->open_rec)) {
+ /* Can't cancel an async create. */
+ return false;
+ }
+
+ remove_deferred_open_message_smb2_internal(smb2req, mid);
+
+ tevent_req_defer_callback(req, smb2req->sconn->ev_ctx);
+ tevent_req_nterror(req, NT_STATUS_CANCELLED);
+ return true;
+}
+
+bool push_deferred_open_message_smb2(struct smbd_smb2_request *smb2req,
+ struct timeval request_time,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_create_state *state = NULL;
+ struct timeval end_time;
+
+ if (!smb2req) {
+ return false;
+ }
+ req = smb2req->subreq;
+ if (!req) {
+ return false;
+ }
+ state = tevent_req_data(req, struct smbd_smb2_create_state);
+ if (!state) {
+ return false;
+ }
+ state->id = id;
+ state->request_time = request_time;
+ state->open_rec = talloc_move(state, &open_rec);
+
+ /* Re-schedule us to retry on timer expiry. */
+ end_time = timeval_sum(&request_time, &timeout);
+
+ DEBUG(10,("push_deferred_open_message_smb2: "
+ "timeout at %s\n",
+ timeval_string(talloc_tos(),
+ &end_time,
+ true) ));
+
+ state->open_was_deferred = true;
+
+ /* allow this request to be canceled */
+ tevent_req_set_cancel_fn(req, smbd_smb2_create_cancel);
+
+ return true;
+}
diff --git a/source3/smbd/smb2_flush.c b/source3/smbd/smb2_flush.c
new file mode 100644
index 0000000..35f545a
--- /dev/null
+++ b/source3/smbd/smb2_flush.c
@@ -0,0 +1,257 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "libcli/security/security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp);
+static NTSTATUS smbd_smb2_flush_recv(struct tevent_req *req);
+
+static void smbd_smb2_request_flush_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_flush(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x18);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_flush_send(req, req->sconn->ev_ctx,
+ req, in_fsp);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_flush_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_flush_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_flush_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_flush_state {
+ struct smbd_smb2_request *smb2req;
+ struct files_struct *fsp;
+};
+
+static void smbd_smb2_flush_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_flush_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req;
+ struct tevent_req *subreq;
+ struct smbd_smb2_flush_state *state;
+ struct smb_request *smbreq;
+ bool is_compound = false;
+ bool is_last_in_compound = false;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_flush_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->fsp = fsp;
+
+ DEBUG(10,("smbd_smb2_flush: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (IS_IPC(smbreq->conn)) {
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ bool allow_dir_flush = false;
+ uint32_t flush_access = FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY;
+
+ if (!fsp->fsp_flags.is_directory) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Directories are not writable in the conventional
+ * sense, but if opened with *either*
+ * FILE_ADD_FILE or FILE_ADD_SUBDIRECTORY
+ * they can be flushed.
+ */
+
+ status = check_any_access_fsp(fsp, flush_access);
+ if (NT_STATUS_IS_OK(status)) {
+ allow_dir_flush = true;
+ }
+
+ if (allow_dir_flush == false) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (fsp_get_io_fd(fsp) == -1) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!lp_strict_sync(SNUM(smbreq->conn))) {
+ /*
+ * No strict sync. Don't really do
+ * anything here.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_FSYNC_SEND(state, ev, fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, smbd_smb2_flush_done, req);
+
+ is_compound = smbd_smb2_is_compound(smb2req);
+ is_last_in_compound = smbd_smb2_is_last_in_compound(smb2req);
+
+ if (is_compound && !is_last_in_compound) {
+ /*
+ * Can't go async if we're not the
+ * last request in a compound request.
+ * Cause this request to complete synchronously.
+ */
+ smb2_request_set_async_internal(state->smb2req, true);
+ }
+
+ /* Ensure any close request knows about this outstanding IO. */
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+
+}
+
+static void smbd_smb2_flush_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_flush_state *state = tevent_req_data(
+ req, struct smbd_smb2_flush_state);
+ int ret;
+ struct vfs_aio_state vfs_aio_state;
+
+ ret = SMB_VFS_FSYNC_RECV(subreq, &vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(vfs_aio_state.error));
+ return;
+ }
+ if (state->fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(state->fsp);
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_flush_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_getinfo.c b/source3/smbd/smb2_getinfo.c
new file mode 100644
index 0000000..7c43a4e
--- /dev/null
+++ b/source3/smbd/smb2_getinfo.c
@@ -0,0 +1,694 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "trans2.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_getinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ uint32_t in_output_buffer_length,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information,
+ uint32_t in_flags);
+static NTSTATUS smbd_smb2_getinfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer,
+ NTSTATUS *p_call_status);
+
+static void smbd_smb2_request_getinfo_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_getinfo(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_info_type;
+ uint8_t in_file_info_class;
+ uint32_t in_output_buffer_length;
+ uint16_t in_input_buffer_offset;
+ uint32_t in_input_buffer_length;
+ DATA_BLOB in_input_buffer;
+ uint32_t in_additional_information;
+ uint32_t in_flags;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x29);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_info_type = CVAL(inbody, 0x02);
+ in_file_info_class = CVAL(inbody, 0x03);
+ in_output_buffer_length = IVAL(inbody, 0x04);
+ in_input_buffer_offset = SVAL(inbody, 0x08);
+ /* 0x0A 2 bytes reserved */
+ in_input_buffer_length = IVAL(inbody, 0x0C);
+ in_additional_information = IVAL(inbody, 0x10);
+ in_flags = IVAL(inbody, 0x14);
+ in_file_id_persistent = BVAL(inbody, 0x18);
+ in_file_id_volatile = BVAL(inbody, 0x20);
+
+ if (in_input_buffer_offset == 0 && in_input_buffer_length == 0) {
+ /* This is ok */
+ } else if (in_input_buffer_offset !=
+ (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_input_buffer_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_input_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_input_buffer.length = in_input_buffer_length;
+
+ if (in_input_buffer.length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_request_process_getinfo: "
+ "client ignored max trans: %s: 0x%08X: 0x%08X\n",
+ __location__, (unsigned)in_input_buffer.length,
+ (unsigned)xconn->smb2.server.max_trans));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ if (in_output_buffer_length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_request_process_getinfo: "
+ "client ignored max trans: %s: 0x%08X: 0x%08X\n",
+ __location__, in_output_buffer_length,
+ xconn->smb2.server.max_trans));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req,
+ MAX(in_input_buffer.length,in_output_buffer_length));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_getinfo_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_info_type,
+ in_file_info_class,
+ in_output_buffer_length,
+ in_input_buffer,
+ in_additional_information,
+ in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_getinfo_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_getinfo_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_output_buffer_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS call_status = NT_STATUS_OK;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_getinfo_recv(subreq,
+ req,
+ &out_output_buffer,
+ &call_status);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ /* some GetInfo responses set STATUS_BUFFER_OVERFLOW and return partial,
+ but valid data */
+ if (!(NT_STATUS_IS_OK(call_status) ||
+ NT_STATUS_EQUAL(call_status, STATUS_BUFFER_OVERFLOW))) {
+ /* Return a specific error with data. */
+ error = smbd_smb2_request_error_ex(req,
+ call_status,
+ 0,
+ &out_output_buffer,
+ __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
+
+ outbody = smbd_smb2_generate_outbody(req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_output_buffer_offset); /* output buffer offset */
+ SIVAL(outbody.data, 0x04,
+ out_output_buffer.length); /* output buffer length */
+
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done_ex(req, call_status, outbody, &outdyn, __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_getinfo_state {
+ struct smbd_smb2_request *smb2req;
+ NTSTATUS status;
+ DATA_BLOB out_output_buffer;
+};
+
+static void smb2_ipc_getinfo(struct tevent_req *req,
+ struct smbd_smb2_getinfo_state *state,
+ struct tevent_context *ev,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class)
+{
+ /* We want to reply to SMB2_GETINFO_FILE
+ with a class of SMB2_FILE_STANDARD_INFO as
+ otherwise a Win7 client issues this request
+ twice (2xroundtrips) if we return NOT_SUPPORTED.
+ NB. We do the same for SMB1 in call_trans2qpipeinfo() */
+
+ if (in_info_type == 0x01 && /* SMB2_GETINFO_FILE */
+ in_file_info_class == 0x05) { /* SMB2_FILE_STANDARD_INFO */
+ state->out_output_buffer = data_blob_talloc(state,
+ NULL, 24);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return;
+ }
+
+ memset(state->out_output_buffer.data,0,24);
+ SOFF_T(state->out_output_buffer.data,0,4096LL);
+ SIVAL(state->out_output_buffer.data,16,1);
+ SIVAL(state->out_output_buffer.data,20,1);
+ tevent_req_done(req);
+ } else {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ }
+}
+
+static struct tevent_req *smbd_smb2_getinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ uint32_t in_output_buffer_length,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information,
+ uint32_t in_flags)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_getinfo_state *state;
+ struct smb_request *smbreq;
+ connection_struct *conn = smb2req->tcon->compat;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_getinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->status = NT_STATUS_OK;
+ state->out_output_buffer = data_blob_null;
+
+ DEBUG(10,("smbd_smb2_getinfo_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (IS_IPC(conn)) {
+ smb2_ipc_getinfo(req, state, ev,
+ in_info_type, in_file_info_class);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_info_type) {
+ case SMB2_0_INFO_FILE:
+ {
+ uint16_t file_info_level;
+ char *data = NULL;
+ unsigned int data_size = 0;
+ bool delete_pending = false;
+ struct timespec write_time_ts;
+ struct file_id fileid;
+ size_t fixed_portion;
+
+ ZERO_STRUCT(write_time_ts);
+
+ /*
+ * MS-SMB2 3.3.5.20.1 "Handling SMB2_0_INFO_FILE"
+ *
+ * FileBasicInformation, FileAllInformation,
+ * FileNetworkOpenInformation, FileAttributeTagInformation
+ * require FILE_READ_ATTRIBUTES.
+ *
+ * FileFullEaInformation requires FILE_READ_EA.
+ */
+ switch (in_file_info_class) {
+ case FSCC_FILE_BASIC_INFORMATION:
+ case FSCC_FILE_ALL_INFORMATION:
+ case FSCC_FILE_NETWORK_OPEN_INFORMATION:
+ case FSCC_FILE_ATTRIBUTE_TAG_INFORMATION:
+ status = check_any_access_fsp(fsp, SEC_FILE_READ_ATTRIBUTE);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ break;
+
+ case FSCC_FILE_FULL_EA_INFORMATION:
+ status = check_any_access_fsp(fsp, SEC_FILE_READ_EA);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ switch (in_file_info_class) {
+ case FSCC_FILE_FULL_EA_INFORMATION:
+ file_info_level = SMB2_FILE_FULL_EA_INFORMATION;
+ break;
+
+ case FSCC_FILE_ALL_INFORMATION:
+ file_info_level = SMB2_FILE_ALL_INFORMATION;
+ break;
+
+ case SMB2_FILE_POSIX_INFORMATION:
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return tevent_req_post(req, ev);
+ }
+ file_info_level = SMB2_FILE_POSIX_INFORMATION_INTERNAL;
+ break;
+
+ default:
+ /* the levels directly map to the passthru levels */
+ file_info_level = in_file_info_class + 1000;
+ break;
+ }
+
+ switch (file_info_level) {
+ case SMB_FILE_NORMALIZED_NAME_INFORMATION:
+ if (smb2req->xconn->protocol < PROTOCOL_SMB3_11) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ if (fsp->fake_file_handle) {
+ /*
+ * This is actually for the QUOTA_FAKE_FILE --metze
+ */
+
+ /* We know this name is ok, it's already passed the checks. */
+
+ } else if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a QFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ int ret = vfs_stat(conn, fsp->fsp_name);
+ if (ret != 0) {
+ DBG_NOTICE("vfs_stat of %s failed (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &fsp->fsp_name->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("smbd_smb2_getinfo_send: "
+ "fstat of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp), nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ if (fsp_getinfo_ask_sharemode(fsp)) {
+ fileid = vfs_file_id_from_sbuf(
+ conn, &fsp->fsp_name->st);
+ get_file_infos(fileid, fsp->name_hash,
+ &delete_pending,
+ &write_time_ts);
+ }
+ }
+
+ status = smbd_do_qfilepathinfo(conn, state,
+ smbreq,
+ file_info_level,
+ fsp,
+ fsp->fsp_name,
+ delete_pending,
+ write_time_ts,
+ NULL,
+ STR_UNICODE,
+ in_output_buffer_length,
+ &fixed_portion,
+ &data,
+ &data_size);
+ if (!NT_STATUS_IS_OK(status)) {
+ SAFE_FREE(data);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ if (in_output_buffer_length < fixed_portion) {
+ SAFE_FREE(data);
+ tevent_req_nterror(
+ req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > 0) {
+ state->out_output_buffer = data_blob_talloc(state,
+ data,
+ data_size);
+ SAFE_FREE(data);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > in_output_buffer_length) {
+ state->out_output_buffer.length =
+ in_output_buffer_length;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ }
+ SAFE_FREE(data);
+ break;
+ }
+
+ case SMB2_0_INFO_FILESYSTEM:
+ {
+ uint16_t file_info_level;
+ char *data = NULL;
+ int data_size = 0;
+ size_t fixed_portion;
+
+ /* the levels directly map to the passthru levels */
+ file_info_level = in_file_info_class + 1000;
+
+ status = smbd_do_qfsinfo(smb2req->xconn, conn, state,
+ file_info_level,
+ STR_UNICODE,
+ in_output_buffer_length,
+ &fixed_portion,
+ fsp,
+ fsp->fsp_name,
+ &data,
+ &data_size);
+ /* some responses set STATUS_BUFFER_OVERFLOW and return
+ partial, but valid data */
+ if (!(NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW))) {
+ SAFE_FREE(data);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ if (in_output_buffer_length < fixed_portion) {
+ SAFE_FREE(data);
+ tevent_req_nterror(
+ req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > 0) {
+ state->out_output_buffer = data_blob_talloc(state,
+ data,
+ data_size);
+ SAFE_FREE(data);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (data_size > in_output_buffer_length) {
+ state->out_output_buffer.length =
+ in_output_buffer_length;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ }
+ SAFE_FREE(data);
+ break;
+ }
+
+ case SMB2_0_INFO_SECURITY:
+ {
+ uint8_t *p_marshalled_sd = NULL;
+ size_t sd_size = 0;
+
+ status = smbd_do_query_security_desc(conn,
+ state,
+ fsp,
+ /* Security info wanted. */
+ in_additional_information &
+ SMB_SUPPORTED_SECINFO_FLAGS,
+ in_output_buffer_length,
+ &p_marshalled_sd,
+ &sd_size);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BUFFER_TOO_SMALL)) {
+ /* Return needed size. */
+ state->out_output_buffer = data_blob_talloc(state,
+ NULL,
+ 4);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ SIVAL(state->out_output_buffer.data,0,(uint32_t)sd_size);
+ state->status = NT_STATUS_BUFFER_TOO_SMALL;
+ break;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("smbd_smb2_getinfo_send: "
+ "smbd_do_query_security_desc of %s failed "
+ "(%s)\n", fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ if (sd_size > 0) {
+ state->out_output_buffer = data_blob_talloc(state,
+ p_marshalled_sd,
+ sd_size);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_QUOTA: {
+#ifdef HAVE_SYS_QUOTAS
+ struct smb2_query_quota_info info;
+ enum ndr_err_code err;
+ uint8_t *data = NULL;
+ uint32_t data_size = 0;
+ struct ndr_pull *ndr_pull = NULL;
+ DATA_BLOB sid_buf = data_blob_null;
+ TALLOC_CTX *tmp_ctx = talloc_init("geninfo_quota");
+ bool ok;
+
+ if (!tmp_ctx) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ ok = check_fsp_ntquota_handle(conn, smbreq, fsp);
+ if (!ok) {
+ DBG_INFO("no valid QUOTA HANDLE\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ ndr_pull = ndr_pull_init_blob(&in_input_buffer, tmp_ctx);
+ if (!ndr_pull) {
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ err = ndr_pull_smb2_query_quota_info(ndr_pull,
+ NDR_SCALARS | NDR_BUFFERS,
+ &info);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DBG_DEBUG("failed to pull smb2_query_quota_info\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ DBG_DEBUG("quota list returnsingle %u, restartscan %u, "
+ "sid_list_length %u, start_sid_length %u, "
+ "startsidoffset %u\n",
+ (unsigned int)info.return_single,
+ (unsigned int)info.restart_scan,
+ (unsigned int)info.sid_list_length,
+ (unsigned int)info.start_sid_length,
+ (unsigned int)info.start_sid_offset);
+
+ /* Currently we do not support the single start sid format */
+ if (info.start_sid_length != 0 || info.start_sid_offset != 0 ) {
+ DBG_INFO("illegal single sid query\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (in_input_buffer.length < ndr_pull->offset) {
+ DBG_INFO("Invalid buffer length\n");
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ sid_buf.data = in_input_buffer.data + ndr_pull->offset;
+ sid_buf.length = in_input_buffer.length - ndr_pull->offset;
+
+ status = smbd_do_query_getinfo_quota(tmp_ctx,
+ fsp,
+ info.restart_scan,
+ info.return_single,
+ info.sid_list_length,
+ &sid_buf,
+ in_output_buffer_length,
+ &data,
+ &data_size);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->out_output_buffer =
+ data_blob_talloc(state, data, data_size);
+ status = NT_STATUS_OK;
+ TALLOC_FREE(tmp_ctx);
+ break;
+#else
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+#endif
+ }
+
+ default:
+ DEBUG(10,("smbd_smb2_getinfo_send: "
+ "unknown in_info_type of %u "
+ " for file %s\n",
+ (unsigned int)in_info_type,
+ fsp_str_dbg(fsp) ));
+
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->status = status;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_getinfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer,
+ NTSTATUS *pstatus)
+{
+ NTSTATUS status;
+ struct smbd_smb2_getinfo_state *state = tevent_req_data(req,
+ struct smbd_smb2_getinfo_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_output_buffer = state->out_output_buffer;
+ talloc_steal(mem_ctx, out_output_buffer->data);
+ *pstatus = state->status;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_glue.c b/source3/smbd/smb2_glue.c
new file mode 100644
index 0000000..563aaf8
--- /dev/null
+++ b/source3/smbd/smb2_glue.c
@@ -0,0 +1,118 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct smb_request *smbd_smb2_fake_smb_request(struct smbd_smb2_request *req,
+ struct files_struct *fsp)
+{
+ struct smb_request *smbreq;
+ const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ if (req->smb1req) {
+ smbreq = req->smb1req;
+ } else {
+ smbreq = talloc_zero(req, struct smb_request);
+ if (smbreq == NULL) {
+ return NULL;
+ }
+ }
+
+ smbreq->request_time = req->request_time;
+ if (req->session != NULL) {
+ smbreq->vuid = req->session->global->session_wire_id;
+ }
+ if (req->tcon != NULL) {
+ smbreq->tid = req->tcon->compat->cnum;
+ smbreq->conn = req->tcon->compat;
+ }
+ smbreq->sconn = req->sconn;
+ smbreq->xconn = req->xconn;
+ smbreq->session = req->session;
+ smbreq->smbpid = (uint16_t)IVAL(inhdr, SMB2_HDR_PID);
+ smbreq->flags2 = FLAGS2_UNICODE_STRINGS |
+ FLAGS2_32_BIT_ERROR_CODES |
+ FLAGS2_LONG_PATH_COMPONENTS |
+ FLAGS2_IS_LONG_NAME;
+
+ /* Only set FLAGS2_DFS_PATHNAMES if it's really a DFS share */
+ if (smbreq->conn != NULL &&
+ lp_host_msdfs() &&
+ lp_msdfs_root(SNUM(smbreq->conn))) {
+ if (IVAL(inhdr, SMB2_HDR_FLAGS) & SMB2_HDR_FLAG_DFS) {
+ smbreq->flags2 |= FLAGS2_DFS_PATHNAMES;
+ }
+ }
+ smbreq->mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ smbreq->chain_fsp = req->compat_chain_fsp;
+ if (fsp != NULL) {
+ smbreq->posix_pathnames =
+ (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+ }
+ smbreq->smb2req = req;
+ req->smb1req = smbreq;
+
+ return smbreq;
+}
+
+/*********************************************************
+ Are there unread bytes for recvfile ?
+*********************************************************/
+
+size_t smbd_smb2_unread_bytes(struct smbd_smb2_request *req)
+{
+ if (req->smb1req) {
+ return req->smb1req->unread_bytes;
+ }
+ return 0;
+}
+
+/*********************************************************
+ Called from file_free() to remove any chained fsp pointers.
+*********************************************************/
+
+void remove_smb2_chained_fsp(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct smbXsrv_connection *xconn = NULL;
+
+ if (sconn->client != NULL) {
+ xconn = sconn->client->connections;
+ }
+
+ for (; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *smb2req;
+
+ for (smb2req = xconn->smb2.requests; smb2req; smb2req = smb2req->next) {
+ if (smb2req->compat_chain_fsp == fsp) {
+ smb2req->compat_chain_fsp = NULL;
+ }
+ if (smb2req->smb1req && smb2req->smb1req->chain_fsp == fsp) {
+ smb2req->smb1req->chain_fsp = NULL;
+ }
+ }
+ }
+}
diff --git a/source3/smbd/smb2_ioctl.c b/source3/smbd/smb2_ioctl.c
new file mode 100644
index 0000000..7d0f11d
--- /dev/null
+++ b/source3/smbd/smb2_ioctl.c
@@ -0,0 +1,505 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+#include "librpc/gen_ndr/ioctl.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_ioctl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint32_t in_ctl_code,
+ DATA_BLOB in_input,
+ uint32_t in_max_output,
+ uint32_t in_flags);
+static NTSTATUS smbd_smb2_ioctl_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output,
+ uint8_t *body_padding,
+ bool *disconnect);
+
+static void smbd_smb2_request_ioctl_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_ioctl(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint32_t min_buffer_offset;
+ uint32_t max_buffer_offset;
+ uint32_t min_output_offset;
+ uint32_t allowed_length_in;
+ uint32_t allowed_length_out;
+ uint32_t in_ctl_code;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp = NULL;
+ uint32_t in_input_offset;
+ uint32_t in_input_length;
+ DATA_BLOB in_input_buffer = data_blob_null;
+ uint32_t in_max_input_length;
+ uint32_t in_output_offset;
+ uint32_t in_output_length;
+ DATA_BLOB in_output_buffer = data_blob_null;
+ uint32_t in_max_output_length;
+ uint32_t in_flags;
+ uint32_t data_length_in;
+ uint32_t data_length_out;
+ uint32_t data_length_tmp;
+ uint32_t data_length_max;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x39);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_ctl_code = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_input_offset = IVAL(inbody, 0x18);
+ in_input_length = IVAL(inbody, 0x1C);
+ in_max_input_length = IVAL(inbody, 0x20);
+ in_output_offset = IVAL(inbody, 0x24);
+ in_output_length = IVAL(inbody, 0x28);
+ in_max_output_length = IVAL(inbody, 0x2C);
+ in_flags = IVAL(inbody, 0x30);
+
+ min_buffer_offset = SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req);
+ max_buffer_offset = min_buffer_offset + SMBD_SMB2_IN_DYN_LEN(req);
+ min_output_offset = min_buffer_offset;
+
+ /*
+ * InputOffset (4 bytes): The offset, in bytes, from the beginning of
+ * the SMB2 header to the input data buffer. If no input data is
+ * required for the FSCTL/IOCTL command being issued, the client SHOULD
+ * set this value to 0.<49>
+ * <49> If no input data is required for the FSCTL/IOCTL command being
+ * issued, Windows-based clients set this field to any value.
+ */
+ allowed_length_in = 0;
+ if ((in_input_offset > 0) && (in_input_length > 0)) {
+ uint32_t tmp_ofs;
+
+ if (in_input_offset < min_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ if (in_input_offset > max_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ allowed_length_in = max_buffer_offset - in_input_offset;
+
+ tmp_ofs = in_input_offset - min_buffer_offset;
+ in_input_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_input_buffer.data += tmp_ofs;
+ in_input_buffer.length = in_input_length;
+ min_output_offset += tmp_ofs;
+ min_output_offset += in_input_length;
+ }
+
+ if (in_input_length > allowed_length_in) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ allowed_length_out = 0;
+ if (in_output_offset > 0) {
+ if (in_output_offset < min_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ if (in_output_offset > max_buffer_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ allowed_length_out = max_buffer_offset - in_output_offset;
+ }
+
+ if (in_output_length > allowed_length_out) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_output_length > 0) {
+ uint32_t tmp_ofs;
+
+ if (in_output_offset < min_output_offset) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ tmp_ofs = in_output_offset - min_buffer_offset;
+ in_output_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_output_buffer.data += tmp_ofs;
+ in_output_buffer.length = in_output_length;
+ }
+
+ /*
+ * verify the credits and avoid overflows
+ * in_input_buffer.length and in_output_buffer.length
+ * are already verified.
+ */
+ data_length_in = in_input_buffer.length + in_output_buffer.length;
+
+ data_length_out = in_max_input_length;
+ data_length_tmp = UINT32_MAX - data_length_out;
+ if (data_length_tmp < in_max_output_length) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ data_length_out += in_max_output_length;
+
+ data_length_max = MAX(data_length_in, data_length_out);
+
+ status = smbd_smb2_request_verify_creditcharge(req, data_length_max);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ /*
+ * If the Flags field of the request is not SMB2_0_IOCTL_IS_FSCTL the
+ * server MUST fail the request with STATUS_NOT_SUPPORTED.
+ */
+ if (in_flags != SMB2_IOCTL_FLAG_IS_FSCTL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NOT_SUPPORTED);
+ }
+
+ switch (in_ctl_code) {
+ case FSCTL_DFS_GET_REFERRALS:
+ case FSCTL_DFS_GET_REFERRALS_EX:
+ case FSCTL_PIPE_WAIT:
+ case FSCTL_VALIDATE_NEGOTIATE_INFO_224:
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
+ case FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8:
+ case FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8:
+ /*
+ * Some SMB2 specific CtlCodes like FSCTL_DFS_GET_REFERRALS or
+ * FSCTL_PIPE_WAIT does not take a file handle.
+ *
+ * If FileId in the SMB2 Header of the request is not
+ * 0xFFFFFFFFFFFFFFFF, then the server MUST fail the request
+ * with STATUS_INVALID_PARAMETER.
+ */
+ if (in_file_id_persistent != UINT64_MAX ||
+ in_file_id_volatile != UINT64_MAX) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+ break;
+ default:
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent,
+ in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+ break;
+ }
+
+ subreq = smbd_smb2_ioctl_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_ctl_code,
+ in_input_buffer,
+ in_max_output_length,
+ in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ /*
+ * If the FSCTL has gone async on a file handle, remember
+ * to add it to the list of async requests we need to wait
+ * for on file handle close.
+ */
+ if (in_fsp != NULL && tevent_req_is_in_progress(subreq)) {
+ bool ok;
+
+ ok = aio_add_req_to_fsp(in_fsp, subreq);
+ if (!ok) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ tevent_req_set_callback(subreq, smbd_smb2_request_ioctl_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 1000);
+}
+
+/*
+ * 3.3.4.4 Sending an Error Response
+ * An error code other than one of the following indicates a failure:
+ */
+static bool smbd_smb2_ioctl_is_failure(uint32_t ctl_code, NTSTATUS status,
+ size_t data_size)
+{
+ if (NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ /*
+ * STATUS_BUFFER_OVERFLOW in a FSCTL_PIPE_TRANSCEIVE, FSCTL_PIPE_PEEK or
+ * FSCTL_DFS_GET_REFERRALS Response specified in section 2.2.32.<153>
+ */
+ if (NT_STATUS_EQUAL(status, STATUS_BUFFER_OVERFLOW)
+ && ((ctl_code == FSCTL_PIPE_TRANSCEIVE)
+ || (ctl_code == FSCTL_PIPE_PEEK)
+ || (ctl_code == FSCTL_DFS_GET_REFERRALS))) {
+ return false;
+ }
+
+ /*
+ * Any status other than STATUS_SUCCESS in a FSCTL_SRV_COPYCHUNK or
+ * FSCTL_SRV_COPYCHUNK_WRITE Response, when returning an
+ * SRV_COPYCHUNK_RESPONSE as described in section 2.2.32.1.
+ */
+ if (((ctl_code == FSCTL_SRV_COPYCHUNK)
+ || (ctl_code == FSCTL_SRV_COPYCHUNK_WRITE))
+ && (data_size == sizeof(struct srv_copychunk_rsp))) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smbd_smb2_request_ioctl_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ const uint8_t *inbody;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint32_t in_ctl_code;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ uint32_t in_max_output_length;
+ uint32_t out_input_offset;
+ uint32_t out_output_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+ bool disconnect = false;
+ uint16_t body_size;
+ uint8_t body_padding = 0;
+
+ status = smbd_smb2_ioctl_recv(subreq, req,
+ &out_output_buffer,
+ &body_padding,
+ &disconnect);
+
+ DEBUG(10,("smbd_smb2_request_ioctl_done: smbd_smb2_ioctl_recv returned "
+ "%u status %s\n",
+ (unsigned int)out_output_buffer.length,
+ nt_errstr(status) ));
+
+ TALLOC_FREE(subreq);
+ if (disconnect) {
+ error = status;
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_ctl_code = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_max_output_length = IVAL(inbody, 0x2C);
+
+ if (out_output_buffer.length > in_max_output_length) {
+ /*
+ * Return NT_STATUS_BUFFER_TOO_SMALL by
+ * default if the provided buffer doesn't
+ * fit.
+ *
+ * If someone wants truncated data
+ * together with STATUS_BUFFER_OVERFLOW
+ * it needs to be done explicitly in
+ * the backends. We currently do that
+ * in:
+ * - fsctl_dfs_get_refers()
+ * - smbd_smb2_ioctl_pipe_read_done()
+ */
+ status = NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ if (smbd_smb2_ioctl_is_failure(in_ctl_code, status,
+ out_output_buffer.length)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ /*
+ * Only FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8
+ * sets body_padding to a value different from 0.
+ */
+ body_size = 0x30 + body_padding;
+ out_input_offset = SMB2_HDR_BODY + body_size;
+ out_output_offset = SMB2_HDR_BODY + body_size;
+
+ outbody = smbd_smb2_generate_outbody(req, body_size);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x30 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ in_ctl_code); /* ctl code */
+ SBVAL(outbody.data, 0x08,
+ in_file_id_persistent); /* file id (persistent) */
+ SBVAL(outbody.data, 0x10,
+ in_file_id_volatile); /* file id (volatile) */
+ SIVAL(outbody.data, 0x18,
+ out_input_offset); /* input offset */
+ SIVAL(outbody.data, 0x1C, 0); /* input count */
+ SIVAL(outbody.data, 0x20,
+ out_output_offset); /* output offset */
+ SIVAL(outbody.data, 0x24,
+ out_output_buffer.length); /* output count */
+ SIVAL(outbody.data, 0x28, 0); /* flags */
+ SIVAL(outbody.data, 0x2C, 0); /* reserved */
+ if (body_padding != 0) {
+ memset(outbody.data + 0x30, 0, body_padding);
+ }
+
+ /*
+ * Note: Windows Vista and 2008 send back also the
+ * input from the request. But it was fixed in
+ * Windows 7.
+ */
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done_ex(req, status, outbody, &outdyn,
+ __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static struct tevent_req *smbd_smb2_ioctl_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint32_t in_ctl_code,
+ DATA_BLOB in_input,
+ uint32_t in_max_output,
+ uint32_t in_flags)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_ioctl_state *state;
+ struct smb_request *smbreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_ioctl_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->smbreq = NULL;
+ state->fsp = fsp;
+ state->in_input = in_input;
+ state->in_max_output = in_max_output;
+ state->out_output = data_blob_null;
+
+ DEBUG(10, ("smbd_smb2_ioctl: ctl_code[0x%08x] %s, %s\n",
+ (unsigned)in_ctl_code,
+ fsp ? fsp_str_dbg(fsp) : "<no handle>",
+ fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->smbreq = smbreq;
+
+ switch (in_ctl_code & IOCTL_DEV_TYPE_MASK) {
+ case FSCTL_DFS:
+ return smb2_ioctl_dfs(in_ctl_code, ev, req, state);
+ case FSCTL_FILESYSTEM:
+ return smb2_ioctl_filesys(in_ctl_code, ev, req, state);
+ case FSCTL_NAMED_PIPE:
+ return smb2_ioctl_named_pipe(in_ctl_code, ev, req, state);
+ case FSCTL_NETWORK_FILESYSTEM:
+ return smb2_ioctl_network_fs(in_ctl_code, ev, req, state);
+ case FSCTL_SMBTORTURE:
+ return smb2_ioctl_smbtorture(in_ctl_code, ev, req, state);
+ default:
+ if (IS_IPC(smbreq->conn)) {
+ tevent_req_nterror(req, NT_STATUS_FS_DRIVER_REQUIRED);
+ } else {
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ }
+
+ return tevent_req_post(req, ev);
+ }
+}
+
+static NTSTATUS smbd_smb2_ioctl_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output,
+ uint8_t *body_padding,
+ bool *disconnect)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ enum tevent_req_state req_state;
+ uint64_t err;
+
+ *body_padding = state->body_padding;
+ *disconnect = state->disconnect;
+
+ if ((tevent_req_is_error(req, &req_state, &err) == false)
+ || (req_state == TEVENT_REQ_USER_ERROR)) {
+ /*
+ * Return output buffer to caller if the ioctl was successfully
+ * processed, even if a user error occurred. Some ioctls return
+ * data on failure.
+ */
+ *out_output = state->out_output;
+ talloc_steal(mem_ctx, out_output->data);
+ }
+
+ tevent_req_is_nterror(req, &status);
+ tevent_req_received(req);
+ return status;
+}
diff --git a/source3/smbd/smb2_ioctl_dfs.c b/source3/smbd/smb2_ioctl_dfs.c
new file mode 100644
index 0000000..72893ca
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_dfs.c
@@ -0,0 +1,157 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static NTSTATUS fsctl_dfs_get_refers(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct connection_struct *conn,
+ DATA_BLOB *in_input,
+ uint32_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ uint16_t in_max_referral_level;
+ DATA_BLOB in_file_name_buffer;
+ char *in_file_name_string;
+ size_t in_file_name_string_size;
+ bool ok;
+ bool overflow = false;
+ NTSTATUS status;
+ int dfs_size;
+ char *dfs_data = NULL;
+ DATA_BLOB output;
+
+ if (!lp_host_msdfs()) {
+ return NT_STATUS_FS_DRIVER_REQUIRED;
+ }
+
+ if (in_input->length < (2 + 2)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ in_max_referral_level = SVAL(in_input->data, 0);
+ in_file_name_buffer.data = in_input->data + 2;
+ in_file_name_buffer.length = in_input->length - 2;
+
+ ok = convert_string_talloc(mem_ctx, CH_UTF16, CH_UNIX,
+ in_file_name_buffer.data,
+ in_file_name_buffer.length,
+ &in_file_name_string,
+ &in_file_name_string_size);
+ if (!ok) {
+ return NT_STATUS_ILLEGAL_CHARACTER;
+ }
+
+ dfs_size = setup_dfs_referral(conn,
+ in_file_name_string,
+ in_max_referral_level,
+ &dfs_data, &status);
+ if (dfs_size < 0) {
+ return status;
+ }
+
+ if (dfs_size > in_max_output) {
+ /*
+ * TODO: we need a testsuite for this
+ */
+ overflow = true;
+ dfs_size = in_max_output;
+ }
+
+ output = data_blob_talloc(mem_ctx, (uint8_t *)dfs_data, dfs_size);
+ SAFE_FREE(dfs_data);
+ if ((dfs_size > 0) && (output.data == NULL)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *out_output = output;
+
+ if (overflow) {
+ return STATUS_BUFFER_OVERFLOW;
+ }
+ return NT_STATUS_OK;
+}
+
+struct tevent_req *smb2_ioctl_dfs(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+
+ switch (ctl_code) {
+ case FSCTL_DFS_GET_REFERRALS:
+ status = fsctl_dfs_get_refers(state, ev, state->smbreq->conn,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ default: {
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ break;
+ }
+ }
+
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+}
diff --git a/source3/smbd/smb2_ioctl_filesys.c b/source3/smbd/smb2_ioctl_filesys.c
new file mode 100644
index 0000000..6cc53d4
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_filesys.c
@@ -0,0 +1,830 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) David Disseldorp 2013-2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "include/ntioctl.h"
+#include "../librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "smb2_ioctl_private.h"
+#include "lib/util/sys_rw.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+/*
+ * XXX this may reduce dup_extents->byte_count so that it's less than the
+ * target file size.
+ */
+static NTSTATUS fsctl_dup_extents_check_lengths(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp,
+ struct fsctl_dup_extents_to_file *dup_extents)
+{
+ NTSTATUS status;
+
+ if ((dup_extents->source_off + dup_extents->byte_count
+ < dup_extents->source_off)
+ || (dup_extents->target_off + dup_extents->byte_count
+ < dup_extents->target_off)) {
+ return NT_STATUS_INVALID_PARAMETER; /* wrap */
+ }
+
+ status = vfs_stat_fsp(src_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * XXX vfs_btrfs and vfs_default have size checks in the copychunk
+ * handler, as this needs to be rechecked after the src has potentially
+ * been extended by a previous chunk in the compound copychunk req.
+ */
+ if (src_fsp->fsp_name->st.st_ex_size
+ < dup_extents->source_off + dup_extents->byte_count) {
+ DEBUG(2, ("dup_extents req exceeds src size\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = vfs_stat_fsp(dst_fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (dst_fsp->fsp_name->st.st_ex_size
+ < dup_extents->target_off + dup_extents->byte_count) {
+
+ if (dst_fsp->fsp_name->st.st_ex_size - dup_extents->target_off
+ > dst_fsp->fsp_name->st.st_ex_size) {
+ return NT_STATUS_INVALID_PARAMETER; /* wrap */
+ }
+
+ /*
+ * this server behaviour is pretty hairy, but we need to match
+ * Windows, so...
+ */
+ DEBUG(2, ("dup_extents req exceeds target size, capping\n"));
+ dup_extents->byte_count = dst_fsp->fsp_name->st.st_ex_size
+ - dup_extents->target_off;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_dup_extents_check_overlap(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp,
+ struct fsctl_dup_extents_to_file *dup_extents)
+{
+ if (!file_id_equal(&src_fsp->file_id, &dst_fsp->file_id)) {
+ /* src and dest refer to different files */
+ return NT_STATUS_OK;
+ }
+
+ if (sys_io_ranges_overlap(dup_extents->byte_count,
+ dup_extents->source_off,
+ dup_extents->byte_count,
+ dup_extents->target_off))
+ {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_dup_extents_check_sparse(struct files_struct *src_fsp,
+ struct files_struct *dst_fsp)
+{
+ /*
+ * 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply...
+ * STATUS_NOT_SUPPORTED: Target file is sparse, while source
+ * is a non-sparse file.
+ *
+ * WS2016 has the following behaviour (MS are in the process of fixing
+ * the spec):
+ * STATUS_NOT_SUPPORTED is returned if the source is sparse, while the
+ * target is non-sparse. However, if target is sparse while the source
+ * is non-sparse, then FSCTL_DUPLICATE_EXTENTS_TO_FILE completes
+ * successfully.
+ */
+ if (src_fsp->fsp_flags.is_sparse && !dst_fsp->fsp_flags.is_sparse) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct fsctl_dup_extents_state {
+ struct tevent_context *ev;
+ struct connection_struct *conn;
+ struct files_struct *dst_fsp;
+ struct fsctl_dup_extents_to_file dup_extents;
+};
+
+static void fsctl_dup_extents_offload_read_done(struct tevent_req *subreq);
+static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq);
+
+static struct tevent_req *fsctl_dup_extents_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *dst_fsp,
+ DATA_BLOB *in_input,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct fsctl_dup_extents_state *state = NULL;
+ uint64_t src_fid_persistent = 0;
+ uint64_t src_fid_volatile = 0;
+ struct files_struct *src_fsp = NULL;
+ int ndr_ret;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fsctl_dup_extents_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (dst_fsp == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ *state = (struct fsctl_dup_extents_state) {
+ .conn = dst_fsp->conn,
+ .ev = ev,
+ .dst_fsp = dst_fsp,
+ };
+
+ if ((dst_fsp->conn->fs_capabilities
+ & FILE_SUPPORTS_BLOCK_REFCOUNTING) == 0) {
+ DBG_INFO("FS does not advertise block refcounting support\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return tevent_req_post(req, ev);
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, state, &state->dup_extents,
+ (ndr_pull_flags_fn_t)ndr_pull_fsctl_dup_extents_to_file);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DBG_ERR("failed to unmarshall dup extents to file req\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ src_fid_persistent = BVAL(state->dup_extents.source_fid, 0);
+ src_fid_volatile = BVAL(state->dup_extents.source_fid, 8);
+ src_fsp = file_fsp_get(smb2req, src_fid_persistent, src_fid_volatile);
+ if ((src_fsp == NULL)
+ || (src_fsp->file_id.devid != dst_fsp->file_id.devid)) {
+ /*
+ * [MS-FSCC] 2.3.8 FSCTL_DUPLICATE_EXTENTS_TO_FILE Reply
+ * STATUS_INVALID_PARAMETER:
+ * The FileHandle parameter is either invalid or does not
+ * represent a handle to an opened file on the same volume.
+ *
+ * Windows Server responds with NT_STATUS_INVALID_HANDLE instead
+ * of STATUS_INVALID_PARAMETER here, despite the above spec.
+ */
+ DBG_ERR("invalid src_fsp for dup_extents\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_dup_extents_check_lengths(src_fsp, dst_fsp,
+ &state->dup_extents);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->dup_extents.byte_count == 0) {
+ DBG_ERR("skipping zero length dup extents\n");
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_dup_extents_check_overlap(src_fsp, dst_fsp,
+ &state->dup_extents);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_dup_extents_check_sparse(src_fsp, dst_fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = SMB_VFS_OFFLOAD_READ_SEND(state, ev, src_fsp,
+ FSCTL_DUP_EXTENTS_TO_FILE,
+ 0, 0, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fsctl_dup_extents_offload_read_done,
+ req);
+ return req;
+}
+
+static void fsctl_dup_extents_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fsctl_dup_extents_state *state = tevent_req_data(
+ req, struct fsctl_dup_extents_state);
+ uint32_t flags;
+ uint64_t xferlen;
+ DATA_BLOB token;
+ NTSTATUS status;
+
+ /*
+ * Note that both flags and xferlen are not used with copy-chunk.
+ */
+
+ status = SMB_VFS_OFFLOAD_READ_RECV(subreq, state->dst_fsp->conn,
+ state, &flags, &xferlen, &token);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* tell the VFS to ignore locks across the clone, matching ReFS */
+ subreq = SMB_VFS_OFFLOAD_WRITE_SEND(state->dst_fsp->conn,
+ state,
+ state->ev,
+ FSCTL_DUP_EXTENTS_TO_FILE,
+ &token,
+ state->dup_extents.source_off,
+ state->dst_fsp,
+ state->dup_extents.target_off,
+ state->dup_extents.byte_count);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, fsctl_dup_extents_vfs_done, req);
+ return;
+}
+
+static void fsctl_dup_extents_vfs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fsctl_dup_extents_state *state = tevent_req_data(
+ req, struct fsctl_dup_extents_state);
+ off_t nb_chunk;
+ NTSTATUS status;
+
+ status = SMB_VFS_OFFLOAD_WRITE_RECV(state->conn, subreq, &nb_chunk);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (nb_chunk != state->dup_extents.byte_count) {
+ tevent_req_nterror(req, NT_STATUS_IO_DEVICE_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS fsctl_dup_extents_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS fsctl_get_cmprn(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ size_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ struct compression_state cmpr_state;
+ enum ndr_err_code ndr_ret;
+ DATA_BLOB output;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* Windows doesn't check for SEC_FILE_READ_ATTRIBUTE permission here */
+
+ ZERO_STRUCT(cmpr_state);
+ if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
+ status = SMB_VFS_FGET_COMPRESSION(fsp->conn,
+ mem_ctx,
+ fsp,
+ &cmpr_state.format);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ /*
+ * bso#12144: The underlying filesystem doesn't support
+ * compression, so we should respond with "not-compressed"
+ * (like WS2016 ReFS) instead of STATUS_NOT_SUPPORTED or
+ * NT_STATUS_INVALID_DEVICE_REQUEST.
+ */
+ cmpr_state.format = COMPRESSION_FORMAT_NONE;
+ }
+
+ ndr_ret = ndr_push_struct_blob(&output, mem_ctx,
+ &cmpr_state,
+ (ndr_push_flags_fn_t)ndr_push_compression_state);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (in_max_output < output.length) {
+ DEBUG(1, ("max output %u too small for compression state %ld\n",
+ (unsigned int)in_max_output, (long int)output.length));
+ return NT_STATUS_INVALID_USER_BUFFER;
+ }
+ *out_output = output;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_set_cmprn(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ DATA_BLOB *in_input)
+{
+ struct compression_state cmpr_state;
+ enum ndr_err_code ndr_ret;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* WRITE_DATA permission is required, WRITE_ATTRIBUTES is not */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &cmpr_state,
+ (ndr_pull_flags_fn_t)ndr_pull_compression_state);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall set compression req\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = NT_STATUS_NOT_SUPPORTED;
+ if (fsp->conn->fs_capabilities & FILE_FILE_COMPRESSION) {
+ status = SMB_VFS_SET_COMPRESSION(fsp->conn,
+ mem_ctx,
+ fsp,
+ cmpr_state.format);
+ } else if (cmpr_state.format == COMPRESSION_FORMAT_NONE) {
+ /*
+ * bso#12144: The underlying filesystem doesn't support
+ * compression. We should still accept set(FORMAT_NONE) requests
+ * (like WS2016 ReFS).
+ */
+ status = NT_STATUS_OK;
+ }
+
+ return status;
+}
+
+static NTSTATUS fsctl_zero_data(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ DATA_BLOB *in_input)
+{
+ struct file_zero_data_info zdata_info;
+ enum ndr_err_code ndr_ret;
+ struct lock_struct lck;
+ int mode;
+ uint64_t len;
+ int ret;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* WRITE_DATA permission is required */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* allow regardless of whether FS supports sparse or not */
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &zdata_info,
+ (ndr_pull_flags_fn_t)ndr_pull_file_zero_data_info);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall zero data request\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (zdata_info.beyond_final_zero < zdata_info.file_off) {
+ DEBUG(0, ("invalid zero data params: off %lu, bfz, %lu\n",
+ (unsigned long)zdata_info.file_off,
+ (unsigned long)zdata_info.beyond_final_zero));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* convert strange "beyond final zero" param into length */
+ len = zdata_info.beyond_final_zero - zdata_info.file_off;
+
+ if (len == 0) {
+ DEBUG(2, ("zero data called with zero length range\n"));
+ return NT_STATUS_OK;
+ }
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ zdata_info.file_off,
+ len,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lck);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(fsp->conn, fsp, &lck)) {
+ DEBUG(2, ("failed to lock range for zero-data\n"));
+ return NT_STATUS_FILE_LOCK_CONFLICT;
+ }
+
+ /*
+ * MS-FSCC <58> Section 2.3.67
+ * This FSCTL sets the range of bytes to zero (0) without extending the
+ * file size.
+ *
+ * The VFS_FALLOCATE_FL_KEEP_SIZE flag is used to satisfy this
+ * constraint.
+ */
+
+ mode = VFS_FALLOCATE_FL_PUNCH_HOLE | VFS_FALLOCATE_FL_KEEP_SIZE;
+ ret = SMB_VFS_FALLOCATE(fsp, mode, zdata_info.file_off, len);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(2, ("zero-data fallocate(0x%x) failed: %s\n", mode,
+ strerror(errno)));
+ return status;
+ }
+
+ if (!fsp->fsp_flags.is_sparse && lp_strict_allocate(SNUM(fsp->conn))) {
+ /*
+ * File marked non-sparse and "strict allocate" is enabled -
+ * allocate the range that we just punched out.
+ * In future FALLOC_FL_ZERO_RANGE could be used exclusively for
+ * this, but it's currently only supported on XFS and ext4.
+ *
+ * The newly allocated range still won't be found by SEEK_DATA
+ * for QAR, but stat.st_blocks will reflect it.
+ */
+ ret = SMB_VFS_FALLOCATE(fsp, VFS_FALLOCATE_FL_KEEP_SIZE,
+ zdata_info.file_off, len);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0, ("fallocate failed: %s\n", strerror(errno)));
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_qar_buf_push(TALLOC_CTX *mem_ctx,
+ struct file_alloced_range_buf *qar_buf,
+ DATA_BLOB *qar_array_blob)
+{
+ DATA_BLOB new_slot;
+ enum ndr_err_code ndr_ret;
+ bool ok;
+
+ ndr_ret = ndr_push_struct_blob(&new_slot, mem_ctx, qar_buf,
+ (ndr_push_flags_fn_t)ndr_push_file_alloced_range_buf);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to marshall QAR buf\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* TODO should be able to avoid copy by pushing into prealloced buf */
+ ok = data_blob_append(mem_ctx, qar_array_blob, new_slot.data,
+ new_slot.length);
+ data_blob_free(&new_slot);
+ if (!ok) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_qar_seek_fill(TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ off_t curr_off,
+ off_t max_off,
+ DATA_BLOB *qar_array_blob)
+{
+ NTSTATUS status = NT_STATUS_NOT_SUPPORTED;
+
+#ifdef HAVE_LSEEK_HOLE_DATA
+ while (curr_off <= max_off) {
+ off_t data_off;
+ off_t hole_off;
+ struct file_alloced_range_buf qar_buf;
+
+ /* seek next data */
+ data_off = SMB_VFS_LSEEK(fsp, curr_off, SEEK_DATA);
+ if ((data_off == -1) && (errno == ENXIO)) {
+ /* no data from curr_off to EOF */
+ break;
+ } else if (data_off == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("lseek data failed: %s\n", strerror(errno)));
+ return status;
+ }
+
+ if (data_off > max_off) {
+ /* found something, but passed range of interest */
+ break;
+ }
+
+ hole_off = SMB_VFS_LSEEK(fsp, data_off, SEEK_HOLE);
+ if (hole_off == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(1, ("lseek hole failed: %s\n", strerror(errno)));
+ return status;
+ }
+
+ if (hole_off <= data_off) {
+ DEBUG(1, ("lseek inconsistent: hole %lu at or before "
+ "data %lu\n", (unsigned long)hole_off,
+ (unsigned long)data_off));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ qar_buf.file_off = data_off;
+ /* + 1 to convert maximum offset to length */
+ qar_buf.len = MIN(hole_off, max_off + 1) - data_off;
+
+ status = fsctl_qar_buf_push(mem_ctx, &qar_buf, qar_array_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ curr_off = hole_off;
+ }
+ status = NT_STATUS_OK;
+#endif
+
+ return status;
+}
+
+static NTSTATUS fsctl_qar(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ DATA_BLOB *in_input,
+ size_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ struct fsctl_query_alloced_ranges_req qar_req;
+ struct fsctl_query_alloced_ranges_rsp qar_rsp;
+ DATA_BLOB qar_array_blob = data_blob_null;
+ uint64_t max_off;
+ enum ndr_err_code ndr_ret;
+ int ret;
+ NTSTATUS status;
+ SMB_STRUCT_STAT sbuf;
+
+ if (fsp == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ /* READ_DATA permission is required */
+ status = check_any_access_fsp(fsp, FILE_READ_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &qar_req,
+ (ndr_pull_flags_fn_t)ndr_pull_fsctl_query_alloced_ranges_req);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall QAR req\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * XXX Windows Server 2008 & 2012 servers don't return lock-conflict
+ * for QAR requests over an exclusively locked range!
+ */
+
+ ret = SMB_VFS_FSTAT(fsp, &sbuf);
+ if (ret == -1) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(2, ("fstat failed: %s\n", strerror(errno)));
+ return status;
+ }
+
+ if ((qar_req.buf.len == 0)
+ || (sbuf.st_ex_size == 0)
+ || (qar_req.buf.file_off >= sbuf.st_ex_size)) {
+ /* zero length range or after EOF, no ranges to return */
+ return NT_STATUS_OK;
+ }
+
+ /* check for integer overflow */
+ if (qar_req.buf.file_off + qar_req.buf.len < qar_req.buf.file_off) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Maximum offset is either the last valid offset _before_ EOF, or the
+ * last byte offset within the requested range. -1 converts length to
+ * offset, which is easier to work with for SEEK_DATA/SEEK_HOLE, E.g.:
+ *
+ * /off=0 /off=512K /st_ex_size=1M
+ * |-------------------------------------|
+ * | File data |
+ * |-------------------------------------|
+ * QAR end\
+ * |=====================================|
+ * | QAR off=512K, len=1M |
+ * |=================^===================|
+ * max_off=1M - 1
+ * QAR end\
+ * |==================|
+ * |QAR off=0 len=512K|
+ * |==================|
+ * ^
+ * max_off=512K - 1
+ */
+ max_off = MIN(sbuf.st_ex_size,
+ qar_req.buf.file_off + qar_req.buf.len) - 1;
+
+ if (!fsp->fsp_flags.is_sparse) {
+ struct file_alloced_range_buf qar_buf;
+
+ /* file is non-sparse, claim file_off->max_off is allocated */
+ qar_buf.file_off = qar_req.buf.file_off;
+ /* + 1 to convert maximum offset back to length */
+ qar_buf.len = max_off - qar_req.buf.file_off + 1;
+
+ status = fsctl_qar_buf_push(mem_ctx, &qar_buf, &qar_array_blob);
+ } else {
+ status = fsctl_qar_seek_fill(mem_ctx, fsp, qar_req.buf.file_off,
+ max_off, &qar_array_blob);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* marshall response buffer. */
+ qar_rsp.far_buf_array = qar_array_blob;
+
+ ndr_ret = ndr_push_struct_blob(out_output, mem_ctx, &qar_rsp,
+ (ndr_push_flags_fn_t)ndr_push_fsctl_query_alloced_ranges_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to marshall QAR rsp\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (out_output->length > in_max_output) {
+ DEBUG(2, ("QAR output len %lu exceeds max %lu\n",
+ (unsigned long)out_output->length,
+ (unsigned long)in_max_output));
+ data_blob_free(out_output);
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2_ioctl_filesys(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+
+ switch (ctl_code) {
+ case FSCTL_GET_COMPRESSION:
+ status = fsctl_get_cmprn(state, ev, state->fsp,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_SET_COMPRESSION:
+ status = fsctl_set_cmprn(state, ev, state->fsp,
+ &state->in_input);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_SET_ZERO_DATA:
+ status = fsctl_zero_data(state, ev, state->fsp,
+ &state->in_input);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_QUERY_ALLOCATED_RANGES:
+ status = fsctl_qar(state, ev, state->fsp,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_DUP_EXTENTS_TO_FILE: {
+ struct tevent_req *subreq = NULL;
+
+ subreq = fsctl_dup_extents_send(state, ev,
+ state->fsp,
+ &state->in_input,
+ state->smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb2_ioctl_filesys_dup_extents_done,
+ req);
+ return req;
+ break;
+ }
+ default: {
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ break;
+ }
+ }
+
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void smb2_ioctl_filesys_dup_extents_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ NTSTATUS status;
+
+ status = fsctl_dup_extents_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+}
diff --git a/source3/smbd/smb2_ioctl_named_pipe.c b/source3/smbd/smb2_ioctl_named_pipe.c
new file mode 100644
index 0000000..f9e3dec
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_named_pipe.c
@@ -0,0 +1,188 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static void smbd_smb2_ioctl_pipe_write_done(struct tevent_req *subreq);
+static void smbd_smb2_ioctl_pipe_read_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2_ioctl_named_pipe(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (ctl_code == FSCTL_PIPE_TRANSCEIVE) {
+ struct tevent_req *subreq;
+
+ if (!IS_IPC(state->smbreq->conn)) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->fsp == NULL) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!fsp_is_np(state->fsp)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(10,("smbd_smb2_ioctl_send: np_write_send of size %u\n",
+ (unsigned int)state->in_input.length ));
+
+ subreq = np_write_send(state, ev,
+ state->fsp->fake_file_handle,
+ state->in_input.data,
+ state->in_input.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_ioctl_pipe_write_done,
+ req);
+ return req;
+ }
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_ioctl_pipe_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+
+ DEBUG(10,("smbd_smb2_ioctl_pipe_write_done: received %ld\n",
+ (long int)nwritten ));
+
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (nwritten != state->in_input.length) {
+ tevent_req_nterror(req, NT_STATUS_PIPE_NOT_AVAILABLE);
+ return;
+ }
+
+ state->out_output = data_blob_talloc(state, NULL, state->in_max_output);
+ if (state->in_max_output > 0 &&
+ tevent_req_nomem(state->out_output.data, req)) {
+ return;
+ }
+
+ DEBUG(10,("smbd_smb2_ioctl_pipe_write_done: issuing np_read_send "
+ "of size %u\n",
+ (unsigned int)state->out_output.length ));
+
+ subreq = np_read_send(state->smbreq->conn,
+ state->smb2req->sconn->ev_ctx,
+ state->fsp->fake_file_handle,
+ state->out_output.data,
+ state->out_output.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_ioctl_pipe_read_done, req);
+}
+
+static void smbd_smb2_ioctl_pipe_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ NTSTATUS status;
+ ssize_t nread = -1;
+ bool is_data_outstanding = false;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+
+ DEBUG(10,("smbd_smb2_ioctl_pipe_read_done: np_read_recv nread = %d "
+ "is_data_outstanding = %d, status = %s\n",
+ (int)nread,
+ (int)is_data_outstanding,
+ nt_errstr(status) ));
+
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->out_output.length = nread;
+
+ if (is_data_outstanding) {
+ tevent_req_nterror(req, STATUS_BUFFER_OVERFLOW);
+ return;
+ }
+
+ tevent_req_done(req);
+}
diff --git a/source3/smbd/smb2_ioctl_network_fs.c b/source3/smbd/smb2_ioctl_network_fs.c
new file mode 100644
index 0000000..bcfa37f
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_network_fs.c
@@ -0,0 +1,839 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) David Disseldorp 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "../librpc/ndr/libndr.h"
+#include "librpc/gen_ndr/ndr_ioctl.h"
+#include "smb2_ioctl_private.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/messages_ctdb.h"
+#include "ctdbd_conn.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static void copychunk_pack_limits(struct srv_copychunk_rsp *cc_rsp)
+{
+ cc_rsp->chunks_written = COPYCHUNK_MAX_CHUNKS;
+ cc_rsp->chunk_bytes_written = COPYCHUNK_MAX_CHUNK_LEN;
+ cc_rsp->total_bytes_written = COPYCHUNK_MAX_TOTAL_LEN;
+}
+
+static NTSTATUS copychunk_check_limits(struct srv_copychunk_copy *cc_copy)
+{
+ uint32_t i;
+ uint32_t total_len = 0;
+
+ /*
+ * [MS-SMB2] 3.3.5.15.6 Handling a Server-Side Data Copy Request
+ * Send and invalid parameter response if:
+ * - The ChunkCount value is greater than
+ * ServerSideCopyMaxNumberofChunks
+ */
+ if (cc_copy->chunk_count > COPYCHUNK_MAX_CHUNKS) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ for (i = 0; i < cc_copy->chunk_count; i++) {
+ /*
+ * - The Length value in a single chunk is greater than
+ * ServerSideCopyMaxChunkSize or equal to zero.
+ */
+ if ((cc_copy->chunks[i].length == 0)
+ || (cc_copy->chunks[i].length > COPYCHUNK_MAX_CHUNK_LEN)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ total_len += cc_copy->chunks[i].length;
+ }
+ /*
+ * - Sum of Lengths in all chunks is greater than
+ * ServerSideCopyMaxDataSize
+ */
+ if (total_len > COPYCHUNK_MAX_TOTAL_LEN) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct fsctl_srv_copychunk_state {
+ struct tevent_context *ev;
+ struct connection_struct *conn;
+ struct srv_copychunk_copy cc_copy;
+ uint32_t current_chunk;
+ NTSTATUS status;
+ off_t total_written;
+ uint32_t ctl_code;
+ DATA_BLOB token;
+ struct files_struct *src_fsp;
+ struct files_struct *dst_fsp;
+ enum {
+ COPYCHUNK_OUT_EMPTY = 0,
+ COPYCHUNK_OUT_LIMITS,
+ COPYCHUNK_OUT_RSP,
+ } out_data;
+};
+static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq);
+
+static NTSTATUS fsctl_srv_copychunk_loop(struct tevent_req *req);
+
+static struct tevent_req *fsctl_srv_copychunk_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t ctl_code,
+ struct files_struct *dst_fsp,
+ DATA_BLOB *in_input,
+ size_t in_max_output,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req = NULL;
+ struct fsctl_srv_copychunk_state *state = NULL;
+ enum ndr_err_code ndr_ret;
+ NTSTATUS status;
+
+ /* handler for both copy-chunk variants */
+ SMB_ASSERT((ctl_code == FSCTL_SRV_COPYCHUNK)
+ || (ctl_code == FSCTL_SRV_COPYCHUNK_WRITE));
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct fsctl_srv_copychunk_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct fsctl_srv_copychunk_state) {
+ .conn = dst_fsp->conn,
+ .ev = ev,
+ .ctl_code = ctl_code,
+ .dst_fsp = dst_fsp,
+ };
+
+ if (in_max_output < sizeof(struct srv_copychunk_rsp)) {
+ DEBUG(3, ("max output %d not large enough to hold copy chunk "
+ "response %lu\n", (int)in_max_output,
+ (unsigned long)sizeof(struct srv_copychunk_rsp)));
+ state->status = NT_STATUS_INVALID_PARAMETER;
+ tevent_req_nterror(req, state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ ndr_ret = ndr_pull_struct_blob(in_input, mem_ctx, &state->cc_copy,
+ (ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_copy);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ DEBUG(0, ("failed to unmarshall copy chunk req\n"));
+ state->status = NT_STATUS_INVALID_PARAMETER;
+ tevent_req_nterror(req, state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->token = data_blob_const(state->cc_copy.source_key,
+ sizeof(state->cc_copy.source_key));
+
+ state->status = copychunk_check_limits(&state->cc_copy);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DEBUG(3, ("copy chunk req exceeds limits\n"));
+ state->out_data = COPYCHUNK_OUT_LIMITS;
+ tevent_req_nterror(req, state->status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* any errors from here onwards should carry copychunk response data */
+ state->out_data = COPYCHUNK_OUT_RSP;
+
+ status = fsctl_srv_copychunk_loop(req);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static NTSTATUS fsctl_srv_copychunk_loop(struct tevent_req *req)
+{
+ struct fsctl_srv_copychunk_state *state = tevent_req_data(
+ req, struct fsctl_srv_copychunk_state);
+ struct tevent_req *subreq = NULL;
+ uint32_t length = 0;
+ off_t source_off = 0;
+ off_t target_off = 0;
+
+ /*
+ * chunk_count can be 0 which must either just do nothing returning
+ * success saying number of copied chunks is 0 (verified against
+ * Windows).
+ *
+ * Or it can be a special macOS copyfile request, so we send this into
+ * the VFS, vfs_fruit if loaded implements the macOS copyile semantics.
+ */
+ if (state->cc_copy.chunk_count > 0) {
+ struct srv_copychunk *chunk = NULL;
+
+ chunk = &state->cc_copy.chunks[state->current_chunk];
+ length = chunk->length;
+ source_off = chunk->source_off;
+ target_off = chunk->target_off;
+ }
+
+ subreq = SMB_VFS_OFFLOAD_WRITE_SEND(state->dst_fsp->conn,
+ state,
+ state->ev,
+ state->ctl_code,
+ &state->token,
+ source_off,
+ state->dst_fsp,
+ target_off,
+ length);
+ if (tevent_req_nomem(subreq, req)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, fsctl_srv_copychunk_vfs_done, req);
+
+ return NT_STATUS_OK;
+}
+
+static void fsctl_srv_copychunk_vfs_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fsctl_srv_copychunk_state *state = tevent_req_data(
+ req, struct fsctl_srv_copychunk_state);
+ off_t chunk_nwritten;
+ NTSTATUS status;
+
+ status = SMB_VFS_OFFLOAD_WRITE_RECV(state->conn, subreq,
+ &chunk_nwritten);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("copy chunk failed [%s] chunk [%u] of [%u]\n",
+ nt_errstr(status),
+ (unsigned int)state->current_chunk,
+ (unsigned int)state->cc_copy.chunk_count);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ DBG_DEBUG("good copy chunk [%u] of [%u]\n",
+ (unsigned int)state->current_chunk,
+ (unsigned int)state->cc_copy.chunk_count);
+ state->total_written += chunk_nwritten;
+
+ if (state->cc_copy.chunk_count == 0) {
+ /*
+ * This must not produce an error but just return a chunk count
+ * of 0 in the response.
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ state->current_chunk++;
+ if (state->current_chunk == state->cc_copy.chunk_count) {
+ tevent_req_done(req);
+ return;
+ }
+
+ status = fsctl_srv_copychunk_loop(req);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+}
+
+static NTSTATUS fsctl_srv_copychunk_recv(struct tevent_req *req,
+ struct srv_copychunk_rsp *cc_rsp,
+ bool *pack_rsp)
+{
+ struct fsctl_srv_copychunk_state *state = tevent_req_data(req,
+ struct fsctl_srv_copychunk_state);
+ NTSTATUS status;
+
+ switch (state->out_data) {
+ case COPYCHUNK_OUT_EMPTY:
+ *pack_rsp = false;
+ break;
+ case COPYCHUNK_OUT_LIMITS:
+ /* 2.2.32.1 - send back our maximum transfer size limits */
+ copychunk_pack_limits(cc_rsp);
+ *pack_rsp = true;
+ break;
+ case COPYCHUNK_OUT_RSP:
+ cc_rsp->chunks_written = state->current_chunk;
+ cc_rsp->chunk_bytes_written = 0;
+ cc_rsp->total_bytes_written = state->total_written;
+ *pack_rsp = true;
+ break;
+ default: /* not reached */
+ assert(1);
+ break;
+ }
+ status = tevent_req_simple_recv_ntstatus(req);
+ return status;
+}
+
+struct cluster_movable_ips {
+ uint32_t array_len;
+ uint32_t array_index;
+ struct sockaddr_storage *ips;
+};
+
+static int stash_cluster_movable_ips(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ bool is_movable_ip,
+ void *private_data)
+{
+ struct cluster_movable_ips *cluster_movable_ips
+ = talloc_get_type_abort(private_data,
+ struct cluster_movable_ips);
+
+ if (!is_movable_ip) {
+ return 0;
+ }
+
+ if (cluster_movable_ips->array_len == 0) {
+ SMB_ASSERT(total_ip_count < INT_MAX);
+ cluster_movable_ips->ips
+ = talloc_zero_array(cluster_movable_ips,
+ struct sockaddr_storage,
+ total_ip_count);
+ if (cluster_movable_ips->ips == NULL) {
+ return ENOMEM;
+ }
+ cluster_movable_ips->array_len = total_ip_count;
+ }
+
+ SMB_ASSERT(cluster_movable_ips->array_index
+ < cluster_movable_ips->array_len);
+
+ cluster_movable_ips->ips[cluster_movable_ips->array_index] = *ip;
+ cluster_movable_ips->array_index++;
+
+ return 0;
+}
+
+static bool find_in_cluster_movable_ips(
+ struct cluster_movable_ips *cluster_movable_ips,
+ const struct sockaddr_storage *ifss)
+{
+ struct samba_sockaddr srv_ip = {
+ .u = {
+ .ss = *ifss,
+ },
+ };
+ uint32_t i;
+
+ for (i = 0; i < cluster_movable_ips->array_index; i++) {
+ struct samba_sockaddr pub_ip = {
+ .u = {
+ .ss = cluster_movable_ips->ips[i],
+ },
+ };
+ if (sockaddr_equal(&pub_ip.u.sa, &srv_ip.u.sa)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static NTSTATUS fsctl_network_iface_info(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *xconn,
+ DATA_BLOB *in_input,
+ uint32_t in_max_output,
+ DATA_BLOB *out_output)
+{
+ struct samba_sockaddr xconn_srv_addr = { .sa_socklen = 0, };
+ struct fsctl_net_iface_info *array = NULL;
+ struct fsctl_net_iface_info *first = NULL;
+ struct fsctl_net_iface_info *last = NULL;
+ size_t i;
+ size_t num_ifaces;
+ enum ndr_err_code ndr_err;
+ struct cluster_movable_ips *cluster_movable_ips = NULL;
+ ssize_t sret;
+ int ret;
+
+ if (in_input->length != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * The list of probed interfaces might have changed, we might need to
+ * refresh local_interfaces to get up-to-date network information, and
+ * respond to clients which sent FSCTL_QUERY_NETWORK_INTERFACE_INFO.
+ * For example, network speed is changed, interfaces count is changed
+ * (some link down or link up), and etc.
+ */
+ load_interfaces();
+ num_ifaces = iface_count();
+
+ *out_output = data_blob_null;
+
+ array = talloc_zero_array(mem_ctx,
+ struct fsctl_net_iface_info,
+ num_ifaces);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (lp_clustering()) {
+ cluster_movable_ips = talloc_zero(array,
+ struct cluster_movable_ips);
+ if (cluster_movable_ips == NULL) {
+ TALLOC_FREE(array);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = ctdbd_public_ip_foreach(messaging_ctdb_connection(),
+ stash_cluster_movable_ips,
+ cluster_movable_ips);
+ if (ret != 0) {
+ TALLOC_FREE(array);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ sret = tsocket_address_bsd_sockaddr(xconn->local_address,
+ &xconn_srv_addr.u.sa,
+ sizeof(xconn_srv_addr.u.ss));
+ if (sret < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ xconn_srv_addr.sa_socklen = sret;
+
+ for (i=0; i < num_ifaces; i++) {
+ struct fsctl_net_iface_info *cur = &array[i];
+ const struct interface *iface = get_interface(i);
+ const struct sockaddr_storage *ifss = &iface->ip;
+ const void *ifptr = ifss;
+ const struct sockaddr *ifsa = (const struct sockaddr *)ifptr;
+ struct tsocket_address *a = NULL;
+ char *addr;
+ bool ok;
+
+ ret = tsocket_address_bsd_from_sockaddr(array,
+ ifsa, sizeof(struct sockaddr_storage),
+ &a);
+ if (ret != 0) {
+ NTSTATUS status = map_nt_error_from_unix_common(errno);
+ TALLOC_FREE(array);
+ return status;
+ }
+
+ ok = tsocket_address_is_inet(a, "ip");
+ if (!ok) {
+ continue;
+ }
+
+ addr = tsocket_address_inet_addr_string(a, array);
+ if (addr == NULL) {
+ TALLOC_FREE(array);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (sockaddr_equal(ifsa, &xconn_srv_addr.u.sa)) {
+ /*
+ * We can announce the ip of the current connection even
+ * if it is a moveable cluster address... as the client
+ * is already connected to it.
+ *
+ * It means in a typical ctdb cluster, where we
+ * only have public addresses, the client can at least
+ * have more than one multichannel'ed connection to the
+ * public ip.
+ */
+ } else if (cluster_movable_ips != NULL) {
+ bool is_movable_ip = find_in_cluster_movable_ips(
+ cluster_movable_ips,
+ ifss);
+ if (is_movable_ip) {
+ DBG_DEBUG("Interface [%s] - "
+ "has movable public ip - "
+ "skipping address [%s].\n",
+ iface->name, addr);
+ continue;
+ }
+ }
+
+ cur->ifindex = iface->if_index;
+ if (cur->ifindex == 0) {
+ /*
+ * Did not get interface index from kernel,
+ * nor from the config. ==> Apply a common
+ * default value for these cases.
+ */
+ cur->ifindex = UINT32_MAX;
+ }
+ cur->capability = iface->capability;
+ cur->linkspeed = iface->linkspeed;
+ if (cur->linkspeed == 0) {
+ DBG_DEBUG("Link speed 0 on interface [%s] - skipping "
+ "address [%s].\n", iface->name, addr);
+ continue;
+ }
+
+ ok = tsocket_address_is_inet(a, "ipv4");
+ if (ok) {
+ cur->sockaddr.family = FSCTL_NET_IFACE_AF_INET;
+ cur->sockaddr.saddr.saddr_in.ipv4 = addr;
+ }
+ ok = tsocket_address_is_inet(a, "ipv6");
+ if (ok) {
+ cur->sockaddr.family = FSCTL_NET_IFACE_AF_INET6;
+ cur->sockaddr.saddr.saddr_in6.ipv6 = addr;
+ }
+
+ if (first == NULL) {
+ first = cur;
+ }
+ if (last != NULL) {
+ last->next = cur;
+ }
+ last = cur;
+ }
+
+ if (first == NULL) {
+ TALLOC_FREE(array);
+ return NT_STATUS_OK;
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ NDR_PRINT_DEBUG(fsctl_net_iface_info, first);
+ }
+
+ ndr_err = ndr_push_struct_blob(out_output, mem_ctx, first,
+ (ndr_push_flags_fn_t)ndr_push_fsctl_net_iface_info);
+ TALLOC_FREE(array);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS fsctl_validate_neg_info(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *conn,
+ DATA_BLOB *in_input,
+ uint32_t in_max_output,
+ DATA_BLOB *out_output,
+ bool *disconnect)
+{
+ uint32_t in_capabilities;
+ DATA_BLOB in_guid_blob;
+ struct GUID in_guid;
+ uint16_t in_security_mode;
+ uint16_t in_num_dialects;
+ uint16_t dialect;
+ struct GUID_ndr_buf out_guid_buf = { .buf = {0}, };
+ NTSTATUS status;
+ enum protocol_types protocol = PROTOCOL_NONE;
+
+ if (lp_server_max_protocol() <= PROTOCOL_SMB2_02) {
+ /*
+ * With SMB 2.02 we didn't get the
+ * capabitities, client guid, security mode
+ * and dialects the client would have offered.
+ *
+ * So we behave compatible with a true
+ * SMB 2.02 server and return NT_STATUS_FILE_CLOSED.
+ *
+ * As SMB >= 2.10 offers the two phase SMB2 Negotiate
+ * we keep supporting FSCTL_VALIDATE_NEGOTIATE_INFO
+ * starting with SMB 2.10, while Windows only supports
+ * it starting with SMB > 2.10.
+ */
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (in_input->length < 0x18) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ in_capabilities = IVAL(in_input->data, 0x00);
+ in_guid_blob = data_blob_const(in_input->data + 0x04, 16);
+ in_security_mode = SVAL(in_input->data, 0x14);
+ in_num_dialects = SVAL(in_input->data, 0x16);
+
+ if (in_input->length < (0x18 + in_num_dialects*2)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (in_max_output < 0x18) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ status = GUID_from_ndr_blob(&in_guid_blob, &in_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * From: [MS-SMB2]
+ * 3.3.5.15.12 Handling a Validate Negotiate Info Request
+ *
+ * The server MUST determine the greatest common dialect
+ * between the dialects it implements and the Dialects array
+ * of the VALIDATE_NEGOTIATE_INFO request. If no dialect is
+ * matched, or if the value is not equal to Connection.Dialect,
+ * the server MUST terminate the transport connection
+ * and free the Connection object.
+ */
+ protocol = smbd_smb2_protocol_dialect_match(in_input->data + 0x18,
+ in_num_dialects,
+ &dialect);
+ if (conn->protocol != protocol) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!GUID_equal(&in_guid, &conn->smb2.client.guid)) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (in_security_mode != conn->smb2.client.security_mode) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (in_capabilities != conn->smb2.client.capabilities) {
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = GUID_to_ndr_buf(&conn->smb2.server.guid, &out_guid_buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *out_output = data_blob_talloc(mem_ctx, NULL, 0x18);
+ if (out_output->data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SIVAL(out_output->data, 0x00, conn->smb2.server.capabilities);
+ memcpy(out_output->data+0x04, out_guid_buf.buf, 16);
+ SSVAL(out_output->data, 0x14, conn->smb2.server.security_mode);
+ SSVAL(out_output->data, 0x16, conn->smb2.server.dialect);
+
+ return NT_STATUS_OK;
+}
+
+static void smb2_ioctl_network_fs_copychunk_done(struct tevent_req *subreq);
+static void smb2_ioctl_network_fs_offload_read_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2_ioctl_network_fs(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ struct tevent_req *subreq;
+ NTSTATUS status;
+
+ switch (ctl_code) {
+ /*
+ * [MS-SMB2] 2.2.31
+ * FSCTL_SRV_COPYCHUNK is issued when a handle has
+ * FILE_READ_DATA and FILE_WRITE_DATA access to the file;
+ * FSCTL_SRV_COPYCHUNK_WRITE is issued when a handle only has
+ * FILE_WRITE_DATA access.
+ */
+ case FSCTL_SRV_COPYCHUNK_WRITE: /* FALL THROUGH */
+ case FSCTL_SRV_COPYCHUNK:
+ subreq = fsctl_srv_copychunk_send(state, ev,
+ ctl_code,
+ state->fsp,
+ &state->in_input,
+ state->in_max_output,
+ state->smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb2_ioctl_network_fs_copychunk_done,
+ req);
+ return req;
+ break;
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ if (!state->smbreq->xconn->client->server_multi_channel_enabled)
+ {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ status = fsctl_network_iface_info(state, ev,
+ state->smbreq->xconn,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ status = fsctl_validate_neg_info(state, ev,
+ state->smbreq->xconn,
+ &state->in_input,
+ state->in_max_output,
+ &state->out_output,
+ &state->disconnect);
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+ break;
+ case FSCTL_SRV_REQUEST_RESUME_KEY:
+ subreq = SMB_VFS_OFFLOAD_READ_SEND(state,
+ ev,
+ state->fsp,
+ FSCTL_SRV_REQUEST_RESUME_KEY,
+ 0, 0, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, smb2_ioctl_network_fs_offload_read_done, req);
+ return req;
+
+ default: {
+ uint8_t *out_data = NULL;
+ uint32_t out_data_len = 0;
+
+ if (state->fsp == NULL) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ } else {
+ status = SMB_VFS_FSCTL(state->fsp,
+ state,
+ ctl_code,
+ state->smbreq->flags2,
+ state->in_input.data,
+ state->in_input.length,
+ &out_data,
+ state->in_max_output,
+ &out_data_len);
+ state->out_output = data_blob_const(out_data, out_data_len);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ break;
+ }
+ }
+
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+}
+
+static void smb2_ioctl_network_fs_copychunk_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_ioctl_state *ioctl_state = tevent_req_data(req,
+ struct smbd_smb2_ioctl_state);
+ struct srv_copychunk_rsp cc_rsp;
+ NTSTATUS status;
+ bool pack_rsp = false;
+
+ ZERO_STRUCT(cc_rsp);
+ status = fsctl_srv_copychunk_recv(subreq, &cc_rsp, &pack_rsp);
+ TALLOC_FREE(subreq);
+ if (pack_rsp == true) {
+ enum ndr_err_code ndr_ret;
+ ndr_ret = ndr_push_struct_blob(&ioctl_state->out_output,
+ ioctl_state,
+ &cc_rsp,
+ (ndr_push_flags_fn_t)ndr_push_srv_copychunk_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (!tevent_req_nterror(req, status)) {
+ tevent_req_done(req);
+ }
+}
+
+static void smb2_ioctl_network_fs_offload_read_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_ioctl_state *state = tevent_req_data(
+ req, struct smbd_smb2_ioctl_state);
+ struct req_resume_key_rsp rkey_rsp;
+ enum ndr_err_code ndr_ret;
+ uint32_t flags;
+ uint64_t xferlen;
+ DATA_BLOB token;
+ NTSTATUS status;
+
+ /*
+ * Note that both flags and xferlen are not used with copy-chunk.
+ */
+ status = SMB_VFS_OFFLOAD_READ_RECV(subreq,
+ state->fsp->conn,
+ state,
+ &flags,
+ &xferlen,
+ &token);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (token.length != sizeof(rkey_rsp.resume_key)) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ ZERO_STRUCT(rkey_rsp);
+ memcpy(rkey_rsp.resume_key, token.data, token.length);
+
+ ndr_ret = ndr_push_struct_blob(&state->out_output, state, &rkey_rsp,
+ (ndr_push_flags_fn_t)ndr_push_req_resume_key_rsp);
+ if (ndr_ret != NDR_ERR_SUCCESS) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
diff --git a/source3/smbd/smb2_ioctl_private.h b/source3/smbd/smb2_ioctl_private.h
new file mode 100644
index 0000000..d653c10
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_private.h
@@ -0,0 +1,60 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __SMB2_IOCTL_PRIVATE_H__
+#define __SMB2_IOCTL_PRIVATE_H__
+
+struct smbd_smb2_ioctl_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ files_struct *fsp;
+ DATA_BLOB in_input;
+ uint32_t in_max_output;
+ DATA_BLOB out_output;
+ uint8_t body_padding;
+ bool disconnect;
+};
+
+struct tevent_req *smb2_ioctl_dfs(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_filesys(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_named_pipe(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_network_fs(uint32_t,
+ struct tevent_context *,
+ struct tevent_req *,
+ struct smbd_smb2_ioctl_state *);
+
+struct tevent_req *smb2_ioctl_smbtorture(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state);
+
+#endif
diff --git a/source3/smbd/smb2_ioctl_smbtorture.c b/source3/smbd/smb2_ioctl_smbtorture.c
new file mode 100644
index 0000000..9a259fb
--- /dev/null
+++ b/source3/smbd/smb2_ioctl_smbtorture.c
@@ -0,0 +1,230 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2021
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "include/ntioctl.h"
+#include "smb2_ioctl_private.h"
+#include "librpc/gen_ndr/ioctl.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct async_sleep_state {
+ struct smbd_server_connection *sconn;
+ files_struct *fsp;
+};
+
+static void smbd_fsctl_torture_async_sleep_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_fsctl_torture_async_sleep_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ files_struct *fsp,
+ uint8_t msecs)
+{
+ struct async_sleep_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ bool ok;
+
+ subreq = tevent_req_create(mem_ctx,
+ &state,
+ struct async_sleep_state);
+ if (!subreq) {
+ return NULL;
+ }
+
+ /*
+ * Store the conn separately, as the test is to
+ * see if fsp is still a valid pointer, so we can't
+ * do anything other than test it for entry in the
+ * open files on this server connection.
+ */
+ state->sconn = fsp->conn->sconn;
+ state->fsp = fsp;
+
+ /*
+ * Just wait for the specified number of micro seconds,
+ * to allow the client time to close fsp.
+ */
+ ok = tevent_req_set_endtime(subreq,
+ ev,
+ timeval_current_ofs(0, msecs));
+ if (!ok) {
+ tevent_req_nterror(subreq, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(subreq, ev);
+ }
+
+ return subreq;
+}
+
+static files_struct *find_my_fsp(struct files_struct *fsp,
+ void *private_data)
+{
+ struct files_struct *myfsp = (struct files_struct *)private_data;
+
+ if (fsp == myfsp) {
+ return myfsp;
+ }
+ return NULL;
+}
+
+static bool smbd_fsctl_torture_async_sleep_recv(struct tevent_req *subreq)
+{
+ tevent_req_received(subreq);
+ return true;
+}
+
+static void smbd_fsctl_torture_async_sleep_done(struct tevent_req *subreq)
+{
+ struct files_struct *found_fsp;
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq,
+ struct tevent_req);
+ struct async_sleep_state *state = tevent_req_data(
+ subreq,
+ struct async_sleep_state);
+
+ /* Does state->fsp still exist on state->sconn ? */
+ found_fsp = files_forall(state->sconn,
+ find_my_fsp,
+ state->fsp);
+
+ smbd_fsctl_torture_async_sleep_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (found_fsp == NULL) {
+ /*
+ * We didn't find it - return an error to the
+ * smb2 ioctl request. Use NT_STATUS_FILE_CLOSED so
+ * the client can tell the difference between
+ * a bad fsp handle and
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14769
+ *
+ * This request should block file closure until it
+ * has completed.
+ */
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+struct tevent_req *smb2_ioctl_smbtorture(uint32_t ctl_code,
+ struct tevent_context *ev,
+ struct tevent_req *req,
+ struct smbd_smb2_ioctl_state *state)
+{
+ NTSTATUS status;
+ bool ok;
+
+ ok = lp_parm_bool(-1, "smbd", "FSCTL_SMBTORTURE", false);
+ if (!ok) {
+ goto not_supported;
+ }
+
+ switch (ctl_code) {
+ case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
+ if (state->in_input.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->smb2req->xconn->ack.force_unacked_timeout = true;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+
+ case FSCTL_SMBTORTURE_IOCTL_RESPONSE_BODY_PADDING8:
+ if (state->in_input.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->in_max_output > 0) {
+ uint32_t size = state->in_max_output;
+
+ state->out_output = data_blob_talloc(state, NULL, size);
+ if (tevent_req_nomem(state->out_output.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ memset(state->out_output.data, 8, size);
+ }
+
+ state->body_padding = 8;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+
+ case FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8:
+ if (state->in_input.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->smb2req->xconn->smb2.smbtorture.read_body_padding = 8;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+
+ case FSCTL_SMBTORTURE_FSP_ASYNC_SLEEP: {
+ struct tevent_req *subreq = NULL;
+
+ /* Data is 1 byte of CVAL stored seconds to delay for. */
+ if (state->in_input.length != 1) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (state->fsp == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = smbd_fsctl_torture_async_sleep_send(
+ req,
+ ev,
+ state->fsp,
+ CVAL(state->in_input.data,0));
+ if (subreq == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_fsctl_torture_async_sleep_done,
+ req);
+ return req;
+ }
+
+ default:
+ goto not_supported;
+ }
+
+not_supported:
+ if (IS_IPC(state->smbreq->conn)) {
+ status = NT_STATUS_FS_DRIVER_REQUIRED;
+ } else {
+ status = NT_STATUS_INVALID_DEVICE_REQUEST;
+ }
+
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+}
diff --git a/source3/smbd/smb2_ipc.c b/source3/smbd/smb2_ipc.c
new file mode 100644
index 0000000..fbe2233
--- /dev/null
+++ b/source3/smbd/smb2_ipc.c
@@ -0,0 +1,40 @@
+/*
+ Unix SMB/CIFS implementation.
+ Inter-process communication and named pipe handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ SMB Version handling
+ Copyright (C) John H Terpstra 1995-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+/*
+ This file handles the named pipe and mailslot calls
+ in the SMBtrans protocol
+ */
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+
+NTSTATUS nt_status_np_pipe(NTSTATUS status)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_DISCONNECTED)) {
+ status = NT_STATUS_PIPE_DISCONNECTED;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_RESET)) {
+ status = NT_STATUS_PIPE_BROKEN;
+ }
+
+ return status;
+}
diff --git a/source3/smbd/smb2_keepalive.c b/source3/smbd/smb2_keepalive.c
new file mode 100644
index 0000000..fac567c
--- /dev/null
+++ b/source3/smbd/smb2_keepalive.c
@@ -0,0 +1,50 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+NTSTATUS smbd_smb2_request_process_keepalive(struct smbd_smb2_request *req)
+{
+ DATA_BLOB outbody;
+ NTSTATUS status;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x04);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ /* TODO: update some time stamps */
+
+ outbody = smbd_smb2_generate_outbody(req, 0x04);
+ if (outbody.data == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ return smbd_smb2_request_done(req, outbody, NULL);
+}
diff --git a/source3/smbd/smb2_lock.c b/source3/smbd/smb2_lock.c
new file mode 100644
index 0000000..c9d810f
--- /dev/null
+++ b/source3/smbd/smb2_lock.c
@@ -0,0 +1,782 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "lib/dbwrap/dbwrap_watch.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "messages.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct smbd_smb2_lock_element {
+ uint64_t offset;
+ uint64_t length;
+ uint32_t flags;
+};
+
+struct smbd_smb2_lock_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smb1req;
+ struct files_struct *fsp;
+ bool blocking;
+ uint32_t polling_msecs;
+ uint32_t retry_msecs;
+ uint16_t lock_count;
+ struct smbd_lock_element *locks;
+ uint8_t lock_sequence_value;
+ uint8_t *lock_sequence_element;
+};
+
+static struct tevent_req *smbd_smb2_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint32_t in_lock_sequence,
+ uint16_t in_lock_count,
+ struct smbd_smb2_lock_element *in_locks);
+static NTSTATUS smbd_smb2_lock_recv(struct tevent_req *req);
+
+static void smbd_smb2_request_lock_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_lock(struct smbd_smb2_request *req)
+{
+ const uint8_t *inbody;
+ uint16_t in_lock_count;
+ uint32_t in_lock_sequence;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct smbd_smb2_lock_element *in_locks;
+ struct tevent_req *subreq;
+ const uint8_t *lock_buffer;
+ uint16_t l;
+ NTSTATUS status;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x30);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_lock_count = CVAL(inbody, 0x02);
+ if (req->xconn->protocol >= PROTOCOL_SMB2_10) {
+ in_lock_sequence = IVAL(inbody, 0x04);
+ } else {
+ /* 0x04 - 4 bytes reserved */
+ in_lock_sequence = 0;
+ }
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+
+ if (in_lock_count < 1) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (((in_lock_count - 1) * 0x18) > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_locks = talloc_array(req, struct smbd_smb2_lock_element,
+ in_lock_count);
+ if (in_locks == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ l = 0;
+ lock_buffer = inbody + 0x18;
+
+ in_locks[l].offset = BVAL(lock_buffer, 0x00);
+ in_locks[l].length = BVAL(lock_buffer, 0x08);
+ in_locks[l].flags = IVAL(lock_buffer, 0x10);
+ /* 0x14 - 4 reserved bytes */
+
+ status = req->session->status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ /*
+ * We need to catch NT_STATUS_NETWORK_SESSION_EXPIRED
+ * for lock requests only.
+ *
+ * Unlock requests still need to be processed!
+ *
+ * This means smbd_smb2_request_check_session()
+ * can't handle the difference and always
+ * allows SMB2_OP_LOCK.
+ */
+ if (in_locks[0].flags != SMB2_LOCK_FLAG_UNLOCK) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ lock_buffer = SMBD_SMB2_IN_DYN_PTR(req);
+
+ for (l=1; l < in_lock_count; l++) {
+ in_locks[l].offset = BVAL(lock_buffer, 0x00);
+ in_locks[l].length = BVAL(lock_buffer, 0x08);
+ in_locks[l].flags = IVAL(lock_buffer, 0x10);
+ /* 0x14 - 4 reserved bytes */
+
+ lock_buffer += 0x18;
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_lock_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_lock_sequence,
+ in_lock_count,
+ in_locks);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_lock_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_lock_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_lock_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(smb2req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static void smbd_smb2_lock_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
+static void smbd_smb2_lock_try(struct tevent_req *req);
+static void smbd_smb2_lock_retry(struct tevent_req *subreq);
+static bool smbd_smb2_lock_cancel(struct tevent_req *req);
+
+static struct tevent_req *smbd_smb2_lock_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint32_t in_lock_sequence,
+ uint16_t in_lock_count,
+ struct smbd_smb2_lock_element *in_locks)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_lock_state *state;
+ bool isunlock = false;
+ uint16_t i;
+ struct smbd_lock_element *locks;
+ NTSTATUS status;
+ bool check_lock_sequence = false;
+ uint32_t lock_sequence_bucket = 0;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_lock_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->fsp = fsp;
+ state->smb2req = smb2req;
+ smb2req->subreq = req; /* So we can find this when going async. */
+
+ tevent_req_set_cleanup_fn(req, smbd_smb2_lock_cleanup);
+
+ state->smb1req = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(state->smb1req, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(10,("smbd_smb2_lock_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ /*
+ * Windows sets check_lock_sequence = true
+ * only for resilient and persistent handles.
+ *
+ * [MS-SMB2] 3.3.5.14 Receiving an SMB2 LOCK Request
+ *
+ * ... if Open.IsResilient or Open.IsDurable or Open.IsPersistent is
+ * TRUE or if Connection.Dialect belongs to the SMB 3.x dialect family
+ * and Connection.ServerCapabilities includes
+ * SMB2_GLOBAL_CAP_MULTI_CHANNEL bit, the server SHOULD<314>
+ * perform lock sequence * verification ...
+
+ * <314> Section 3.3.5.14: Windows 7 and Windows Server 2008 R2 perform
+ * lock sequence verification only when Open.IsResilient is TRUE.
+ * Windows 8 through Windows 10 v1909 and Windows Server 2012 through
+ * Windows Server v1909 perform lock sequence verification only when
+ * Open.IsResilient or Open.IsPersistent is TRUE.
+ *
+ * Note <314> also applies to all versions (at least) up to
+ * Windows Server v2004.
+ *
+ * Hopefully this will be fixed in future Windows versions and they
+ * will avoid Note <314>.
+ *
+ * We implement what the specification says by default, but
+ * allow "smb2 disable lock sequence checking = yes" to
+ * behave like Windows again.
+ *
+ * Note: that we already check the dialect before setting
+ * SMB2_CAP_MULTI_CHANNEL in smb2_negprot.c
+ */
+ if (smb2req->xconn->smb2.server.capabilities & SMB2_CAP_MULTI_CHANNEL) {
+ check_lock_sequence = true;
+ }
+ if (fsp->op->global->durable) {
+ check_lock_sequence = true;
+ }
+
+ if (check_lock_sequence) {
+ bool disable_lock_sequence_checking =
+ lp_smb2_disable_lock_sequence_checking();
+
+ if (disable_lock_sequence_checking) {
+ check_lock_sequence = false;
+ }
+ }
+
+ if (check_lock_sequence) {
+ state->lock_sequence_value = in_lock_sequence & 0xF;
+ lock_sequence_bucket = in_lock_sequence >> 4;
+ }
+ if ((lock_sequence_bucket > 0) &&
+ (lock_sequence_bucket <= sizeof(fsp->op->global->lock_sequence_array)))
+ {
+ uint32_t idx = lock_sequence_bucket - 1;
+ uint8_t *array = fsp->op->global->lock_sequence_array;
+
+ state->lock_sequence_element = &array[idx];
+ }
+
+ if (state->lock_sequence_element != NULL) {
+ /*
+ * The incoming 'state->lock_sequence_value' is masked with 0xF.
+ *
+ * Note per default '*state->lock_sequence_element'
+ * is invalid, a value of 0xFF that can never match on
+ * incoming value.
+ */
+ if (*state->lock_sequence_element == state->lock_sequence_value)
+ {
+ DBG_INFO("replayed smb2 lock request detected: "
+ "file %s, value %u, bucket %u\n",
+ fsp_str_dbg(fsp),
+ (unsigned)state->lock_sequence_value,
+ (unsigned)lock_sequence_bucket);
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ /*
+ * If it's not a replay, mark the element as
+ * invalid again.
+ */
+ *state->lock_sequence_element = 0xFF;
+ }
+
+ locks = talloc_array(state, struct smbd_lock_element, in_lock_count);
+ if (locks == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_locks[0].flags) {
+ case SMB2_LOCK_FLAG_SHARED:
+ case SMB2_LOCK_FLAG_EXCLUSIVE:
+ if (in_lock_count > 1) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ state->blocking = true;
+ break;
+
+ case SMB2_LOCK_FLAG_SHARED|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ case SMB2_LOCK_FLAG_EXCLUSIVE|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ break;
+
+ case SMB2_LOCK_FLAG_UNLOCK:
+ /* only the first lock gives the UNLOCK bit - see
+ MS-SMB2 3.3.5.14 */
+ isunlock = true;
+ break;
+
+ default:
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!isunlock && (in_lock_count > 1)) {
+
+ /*
+ * 3.3.5.14.2 says we SHOULD fail with INVALID_PARAMETER if we
+ * have more than one lock and one of those is blocking.
+ */
+
+ for (i=0; i<in_lock_count; i++) {
+ uint32_t flags = in_locks[i].flags;
+
+ if ((flags & SMB2_LOCK_FLAG_FAIL_IMMEDIATELY) == 0) {
+ tevent_req_nterror(
+ req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ for (i=0; i<in_lock_count; i++) {
+ bool invalid = false;
+ bool posix_handle =(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+
+ switch (in_locks[i].flags) {
+ case SMB2_LOCK_FLAG_SHARED:
+ case SMB2_LOCK_FLAG_EXCLUSIVE:
+ if (isunlock) {
+ invalid = true;
+ break;
+ }
+ break;
+
+ case SMB2_LOCK_FLAG_SHARED|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ case SMB2_LOCK_FLAG_EXCLUSIVE|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY:
+ if (isunlock) {
+ invalid = true;
+ }
+ break;
+
+ case SMB2_LOCK_FLAG_UNLOCK:
+ if (!isunlock) {
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ break;
+
+ default:
+ if (isunlock) {
+ /*
+ * If the first element was a UNLOCK
+ * we need to defer the error response
+ * to the backend, because we need to process
+ * all unlock elements before
+ */
+ invalid = true;
+ break;
+ }
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ locks[i].req_guid = smbd_request_guid(smb2req->smb1req, i);
+ locks[i].smblctx = fsp->op->global->open_persistent_id;
+ locks[i].offset = in_locks[i].offset;
+ locks[i].count = in_locks[i].length;
+
+ if (posix_handle) {
+ locks[i].lock_flav = POSIX_LOCK;
+ } else {
+ locks[i].lock_flav = WINDOWS_LOCK;
+ }
+
+ if (in_locks[i].flags & SMB2_LOCK_FLAG_EXCLUSIVE) {
+ if (posix_handle && fsp->fsp_flags.can_write == false) {
+ /*
+ * Can't get a write lock on a posix
+ * read-only handle.
+ */
+ DBG_INFO("POSIX write lock requested "
+ "on read-only handle for file %s\n",
+ fsp_str_dbg(fsp));
+ tevent_req_nterror(req,
+ NT_STATUS_INVALID_HANDLE);
+ return tevent_req_post(req, ev);
+ }
+ locks[i].brltype = WRITE_LOCK;
+ } else if (in_locks[i].flags & SMB2_LOCK_FLAG_SHARED) {
+ locks[i].brltype = READ_LOCK;
+ } else if (invalid) {
+ /*
+ * this is an invalid UNLOCK element
+ * and the backend needs to test for
+ * brltype != UNLOCK_LOCK and return
+ * NT_STATUS_INVALID_PARAMETER
+ */
+ locks[i].brltype = READ_LOCK;
+ } else {
+ locks[i].brltype = UNLOCK_LOCK;
+ }
+ locks[i].lock_flav = WINDOWS_LOCK;
+
+ DBG_DEBUG("index %"PRIu16" offset=%"PRIu64", count=%"PRIu64", "
+ "smblctx = %"PRIu64" type %d\n",
+ i,
+ locks[i].offset,
+ locks[i].count,
+ locks[i].smblctx,
+ (int)locks[i].brltype);
+ }
+
+ state->locks = locks;
+ state->lock_count = in_lock_count;
+
+ if (isunlock) {
+ status = smbd_do_unlocking(
+ state->smb1req, fsp, in_lock_count, locks);
+
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ smbd_smb2_lock_try(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_defer_callback(req, smb2req->sconn->ev_ctx);
+ aio_add_req_to_fsp(state->fsp, req);
+ tevent_req_set_cancel_fn(req, smbd_smb2_lock_cancel);
+
+ return req;
+}
+
+static void smbd_smb2_lock_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smbd_smb2_lock_state *state = tevent_req_data(
+ req, struct smbd_smb2_lock_state);
+
+ if (req_state != TEVENT_REQ_DONE) {
+ return;
+ }
+
+ if (state->lock_sequence_element != NULL) {
+ /*
+ * On success we remember the given/incoming
+ * value (which was masked with 0xF.
+ */
+ *state->lock_sequence_element = state->lock_sequence_value;
+ }
+}
+
+static void smbd_smb2_lock_update_retry_msecs(
+ struct smbd_smb2_lock_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms,
+ * we just use half of it to trigger the first retry.
+ *
+ * v_min is in the range of 0.001 to 10 secs
+ * (0.1 secs by default)
+ *
+ * v_max is in the range of 0.01 to 100 secs
+ * (1.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.1, 0.2, 0.3, 0.4, ... 1.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()))/2;
+ uint32_t v_max = 10 * v_min;
+
+ if (state->retry_msecs >= v_max) {
+ state->retry_msecs = v_max;
+ return;
+ }
+
+ state->retry_msecs += v_min;
+}
+
+static void smbd_smb2_lock_update_polling_msecs(
+ struct smbd_smb2_lock_state *state)
+{
+ /*
+ * The default lp_lock_spin_time() is 200ms.
+ *
+ * v_min is in the range of 0.002 to 20 secs
+ * (0.2 secs by default)
+ *
+ * v_max is in the range of 0.02 to 200 secs
+ * (2.0 secs by default)
+ *
+ * The typical steps are:
+ * 0.2, 0.4, 0.6, 0.8, ... 2.0
+ */
+ uint32_t v_min = MAX(2, MIN(20000, lp_lock_spin_time()));
+ uint32_t v_max = 10 * v_min;
+
+ if (state->polling_msecs >= v_max) {
+ state->polling_msecs = v_max;
+ return;
+ }
+
+ state->polling_msecs += v_min;
+}
+
+static void smbd_smb2_lock_try(struct tevent_req *req)
+{
+ struct smbd_smb2_lock_state *state = tevent_req_data(
+ req, struct smbd_smb2_lock_state);
+ struct share_mode_lock *lck = NULL;
+ uint16_t blocker_idx;
+ struct server_id blocking_pid = { 0 };
+ uint64_t blocking_smblctx;
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+ struct timeval endtime = { 0 };
+
+ lck = get_existing_share_mode_lock(
+ talloc_tos(), state->fsp->file_id);
+ if (tevent_req_nomem(lck, req)) {
+ return;
+ }
+
+ status = smbd_do_locks_try(
+ state->fsp,
+ state->lock_count,
+ state->locks,
+ &blocker_idx,
+ &blocking_pid,
+ &blocking_smblctx);
+ if (NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(lck);
+ tevent_req_done(req);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /*
+ * We got NT_STATUS_RETRY,
+ * we reset polling_msecs so that
+ * that the retries based on LOCK_NOT_GRANTED
+ * will later start with small intervals again.
+ */
+ state->polling_msecs = 0;
+
+ /*
+ * The backend wasn't able to decide yet.
+ * We need to wait even for non-blocking
+ * locks.
+ *
+ * The backend uses blocking_smblctx == UINT64_MAX
+ * to indicate that we should use retry timers.
+ *
+ * It uses blocking_smblctx == 0 to indicate
+ * it will use share_mode_wakeup_waiters()
+ * to wake us. Note that unrelated changes in
+ * locking.tdb may cause retries.
+ */
+
+ if (blocking_smblctx != UINT64_MAX) {
+ SMB_ASSERT(blocking_smblctx == 0);
+ goto setup_retry;
+ }
+
+ smbd_smb2_lock_update_retry_msecs(state);
+
+ DBG_DEBUG("Waiting for a backend decision. "
+ "Retry in %"PRIu32" msecs\n",
+ state->retry_msecs);
+
+ /*
+ * We completely ignore state->endtime here
+ * we we'll wait for a backend decision forever.
+ * If the backend is smart enough to implement
+ * some NT_STATUS_RETRY logic, it has to
+ * switch to any other status after in order
+ * to avoid waiting forever.
+ */
+ endtime = timeval_current_ofs_msec(state->retry_msecs);
+ goto setup_retry;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ /*
+ * This is a bug and will be changed into an assert
+ * in future version. We should only
+ * ever get NT_STATUS_LOCK_NOT_GRANTED here!
+ */
+ static uint64_t _bug_count;
+ int _level = (_bug_count++ == 0) ? DBGLVL_ERR: DBGLVL_DEBUG;
+ DBG_PREFIX(_level, ("BUG: Got %s mapping to "
+ "NT_STATUS_LOCK_NOT_GRANTED\n",
+ nt_errstr(status)));
+ status = NT_STATUS_LOCK_NOT_GRANTED;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_LOCK_NOT_GRANTED)) {
+ TALLOC_FREE(lck);
+ tevent_req_nterror(req, status);
+ return;
+ }
+ /*
+ * We got LOCK_NOT_GRANTED, make sure
+ * a following STATUS_RETRY will start
+ * with short intervals again.
+ */
+ state->retry_msecs = 0;
+
+ if (!state->blocking) {
+ TALLOC_FREE(lck);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (blocking_smblctx == UINT64_MAX) {
+ smbd_smb2_lock_update_polling_msecs(state);
+
+ DBG_DEBUG("Blocked on a posix lock. Retry in %"PRIu32" msecs\n",
+ state->polling_msecs);
+
+ endtime = timeval_current_ofs_msec(state->polling_msecs);
+ }
+
+setup_retry:
+ DBG_DEBUG("Watching share mode lock\n");
+
+ subreq = share_mode_watch_send(
+ state, state->ev, lck, blocking_pid);
+ TALLOC_FREE(lck);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_lock_retry, req);
+
+ if (!timeval_is_zero(&endtime)) {
+ bool ok;
+
+ ok = tevent_req_set_endtime(subreq,
+ state->ev,
+ endtime);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ }
+}
+
+static void smbd_smb2_lock_retry(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_lock_state *state = tevent_req_data(
+ req, struct smbd_smb2_lock_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->fsp);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ /*
+ * This is just a trigger for a timed retry.
+ */
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smbd_smb2_lock_try(req);
+}
+
+static NTSTATUS smbd_smb2_lock_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/****************************************************************
+ Cancel an outstanding blocking lock request.
+*****************************************************************/
+
+static bool smbd_smb2_lock_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_request *smb2req = NULL;
+ struct smbd_smb2_lock_state *state = tevent_req_data(req,
+ struct smbd_smb2_lock_state);
+ if (!state) {
+ return false;
+ }
+
+ if (!state->smb2req) {
+ return false;
+ }
+
+ smb2req = state->smb2req;
+
+ /*
+ * If the request is canceled because of close, logoff or tdis
+ * the status is NT_STATUS_RANGE_NOT_LOCKED instead of
+ * NT_STATUS_CANCELLED.
+ */
+ if (state->fsp->fsp_flags.closing ||
+ !NT_STATUS_IS_OK(smb2req->session->status) ||
+ !NT_STATUS_IS_OK(smb2req->tcon->status)) {
+ tevent_req_nterror(req, NT_STATUS_RANGE_NOT_LOCKED);
+ return true;
+ }
+
+ tevent_req_nterror(req, NT_STATUS_CANCELLED);
+ return true;
+}
diff --git a/source3/smbd/smb2_negprot.c b/source3/smbd/smb2_negprot.c
new file mode 100644
index 0000000..8c50fc9
--- /dev/null
+++ b/source3/smbd/smb2_negprot.c
@@ -0,0 +1,1229 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/smb/smb2_negotiate_context.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../librpc/ndr/libndr.h"
+#include "../libcli/smb/smb_signing.h"
+#include "auth.h"
+#include "auth/gensec/gensec.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+#ifdef HAVE_VALGRIND_CALLGRIND_H
+#include <valgrind/callgrind.h>
+#endif /* HAVE_VALGRIND_CALLGRIND_H */
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+/*
+ * this is the entry point if SMB2 is selected via
+ * the SMB negprot and the given dialect.
+ */
+static NTSTATUS reply_smb20xx(struct smb_request *req, uint16_t dialect)
+{
+ uint8_t *smb2_inpdu;
+ uint8_t *smb2_hdr;
+ uint8_t *smb2_body;
+ uint8_t *smb2_dyn;
+ size_t len = SMB2_HDR_BODY + 0x24 + 2;
+
+ smb2_inpdu = talloc_zero_array(talloc_tos(), uint8_t, len);
+ if (smb2_inpdu == NULL) {
+ DEBUG(0, ("Could not push spnego blob\n"));
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ return NT_STATUS_NO_MEMORY;
+ }
+ smb2_hdr = smb2_inpdu;
+ smb2_body = smb2_hdr + SMB2_HDR_BODY;
+ smb2_dyn = smb2_body + 0x24;
+
+ SIVAL(smb2_hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+ SIVAL(smb2_hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+
+ SSVAL(smb2_body, 0x00, 0x0024); /* struct size */
+ SSVAL(smb2_body, 0x02, 0x0001); /* dialect count */
+
+ SSVAL(smb2_dyn, 0x00, dialect);
+
+ req->outbuf = NULL;
+
+ return smbd_smb2_process_negprot(req->xconn, 0, smb2_inpdu, len);
+}
+
+/*
+ * this is the entry point if SMB2 is selected via
+ * the SMB negprot and the "SMB 2.002" dialect.
+ */
+NTSTATUS reply_smb2002(struct smb_request *req, uint16_t choice)
+{
+ return reply_smb20xx(req, SMB2_DIALECT_REVISION_202);
+}
+
+/*
+ * this is the entry point if SMB2 is selected via
+ * the SMB negprot and the "SMB 2.???" dialect.
+ */
+NTSTATUS reply_smb20ff(struct smb_request *req, uint16_t choice)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ xconn->smb2.allow_2ff = true;
+ return reply_smb20xx(req, SMB2_DIALECT_REVISION_2FF);
+}
+
+enum protocol_types smbd_smb2_protocol_dialect_match(const uint8_t *indyn,
+ const int dialect_count,
+ uint16_t *dialect)
+{
+ struct {
+ enum protocol_types proto;
+ uint16_t dialect;
+ } pd[] = {
+ { PROTOCOL_SMB3_11, SMB3_DIALECT_REVISION_311 },
+ { PROTOCOL_SMB3_02, SMB3_DIALECT_REVISION_302 },
+ { PROTOCOL_SMB3_00, SMB3_DIALECT_REVISION_300 },
+ { PROTOCOL_SMB2_10, SMB2_DIALECT_REVISION_210 },
+ { PROTOCOL_SMB2_02, SMB2_DIALECT_REVISION_202 },
+ };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(pd); i ++) {
+ int c = 0;
+
+ if (lp_server_max_protocol() < pd[i].proto) {
+ continue;
+ }
+ if (lp_server_min_protocol() > pd[i].proto) {
+ continue;
+ }
+
+ for (c = 0; c < dialect_count; c++) {
+ *dialect = SVAL(indyn, c*2);
+ if (*dialect == pd[i].dialect) {
+ return pd[i].proto;
+ }
+ }
+ }
+
+ return PROTOCOL_NONE;
+}
+
+static NTSTATUS smb2_negotiate_context_process_posix(
+ const struct smb2_negotiate_contexts *in_c,
+ bool *posix)
+{
+ struct smb2_negotiate_context *in_posix = NULL;
+ const uint8_t *inbuf = NULL;
+ size_t inbuflen;
+ bool posix_found = false;
+ size_t ofs;
+ int cmp;
+
+ *posix = false;
+
+ if (!lp_smb3_unix_extensions(GLOBAL_SECTION_SNUM)) {
+ return NT_STATUS_OK;
+ }
+
+ in_posix = smb2_negotiate_context_find(in_c,
+ SMB2_POSIX_EXTENSIONS_AVAILABLE);
+ if (in_posix == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ inbuf = in_posix->data.data;
+ inbuflen = in_posix->data.length;
+
+ /*
+ * For now the server only supports one variant.
+ * Check it's the right one.
+ */
+ if ((inbuflen % 16) != 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ SMB_ASSERT(strlen(SMB2_CREATE_TAG_POSIX) == 16);
+
+ for (ofs = 0; ofs < inbuflen; ofs += 16) {
+ cmp = memcmp(inbuf+ofs, SMB2_CREATE_TAG_POSIX, 16);
+ if (cmp == 0) {
+ posix_found = true;
+ break;
+ }
+ }
+
+ if (!posix_found) {
+ DBG_DEBUG("Client requested unknown SMB3 Unix extensions:\n");
+ dump_data(10, inbuf, inbuflen);
+ return NT_STATUS_OK;
+ }
+
+ DBG_DEBUG("Client requested SMB3 Unix extensions\n");
+ *posix = true;
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_request_process_negprot_state {
+ struct smbd_smb2_request *req;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+};
+
+static void smbd_smb2_request_process_negprot_mc_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_negprot(struct smbd_smb2_request *req)
+{
+ struct smbd_smb2_request_process_negprot_state *state = NULL;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ const uint8_t *indyn = NULL;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ DATA_BLOB negprot_spnego_blob;
+ uint16_t security_offset;
+ DATA_BLOB security_buffer;
+ size_t expected_dyn_size = 0;
+ size_t c;
+ uint16_t security_mode;
+ uint16_t dialect_count;
+ uint16_t in_security_mode;
+ uint32_t in_capabilities;
+ DATA_BLOB in_guid_blob;
+ struct GUID in_guid;
+ struct smb2_negotiate_contexts in_c = { .num_contexts = 0, };
+ struct smb2_negotiate_context *in_preauth = NULL;
+ struct smb2_negotiate_context *in_cipher = NULL;
+ struct smb2_negotiate_context *in_sign_algo = NULL;
+ struct smb2_negotiate_contexts out_c = { .num_contexts = 0, };
+ const struct smb311_capabilities default_smb3_capabilities =
+ smb311_capabilities_parse("server",
+ lp_server_smb3_signing_algorithms(),
+ lp_server_smb3_encryption_algorithms());
+ DATA_BLOB out_negotiate_context_blob = data_blob_null;
+ uint32_t out_negotiate_context_offset = 0;
+ uint16_t out_negotiate_context_count = 0;
+ uint16_t dialect = 0;
+ uint32_t capabilities;
+ DATA_BLOB out_guid_blob;
+ struct GUID out_guid;
+ enum protocol_types protocol = PROTOCOL_NONE;
+ uint32_t max_limit;
+ uint32_t max_trans = lp_smb2_max_trans();
+ uint32_t max_read = lp_smb2_max_read();
+ uint32_t max_write = lp_smb2_max_write();
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ bool posix = false;
+ bool ok;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x24);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ dialect_count = SVAL(inbody, 0x02);
+
+ in_security_mode = SVAL(inbody, 0x04);
+ in_capabilities = IVAL(inbody, 0x08);
+ in_guid_blob = data_blob_const(inbody + 0x0C, 16);
+
+ if (dialect_count == 0) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = GUID_from_ndr_blob(&in_guid_blob, &in_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ expected_dyn_size = dialect_count * 2;
+ if (SMBD_SMB2_IN_DYN_LEN(req) < expected_dyn_size) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ indyn = SMBD_SMB2_IN_DYN_PTR(req);
+
+ protocol = smbd_smb2_protocol_dialect_match(indyn,
+ dialect_count,
+ &dialect);
+
+ for (c=0; protocol == PROTOCOL_NONE && c < dialect_count; c++) {
+ if (lp_server_max_protocol() < PROTOCOL_SMB2_10) {
+ break;
+ }
+
+ dialect = SVAL(indyn, c*2);
+ if (dialect == SMB2_DIALECT_REVISION_2FF) {
+ if (xconn->smb2.allow_2ff) {
+ xconn->smb2.allow_2ff = false;
+ protocol = PROTOCOL_SMB2_10;
+ break;
+ }
+ }
+ }
+
+ if (protocol == PROTOCOL_NONE) {
+ return smbd_smb2_request_error(req, NT_STATUS_NOT_SUPPORTED);
+ }
+
+ if (protocol >= PROTOCOL_SMB3_11) {
+ uint32_t in_negotiate_context_offset = 0;
+ uint16_t in_negotiate_context_count = 0;
+ DATA_BLOB in_negotiate_context_blob = data_blob_null;
+ size_t ofs;
+
+ in_negotiate_context_offset = IVAL(inbody, 0x1C);
+ in_negotiate_context_count = SVAL(inbody, 0x20);
+
+ ofs = SMB2_HDR_BODY;
+ ofs += SMBD_SMB2_IN_BODY_LEN(req);
+ ofs += expected_dyn_size;
+ if ((ofs % 8) != 0) {
+ ofs += 8 - (ofs % 8);
+ }
+
+ if (in_negotiate_context_offset != ofs) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ ofs -= SMB2_HDR_BODY;
+ ofs -= SMBD_SMB2_IN_BODY_LEN(req);
+
+ if (SMBD_SMB2_IN_DYN_LEN(req) < ofs) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_negotiate_context_blob = data_blob_const(indyn,
+ SMBD_SMB2_IN_DYN_LEN(req));
+
+ in_negotiate_context_blob.data += ofs;
+ in_negotiate_context_blob.length -= ofs;
+
+ status = smb2_negotiate_context_parse(req,
+ in_negotiate_context_blob,
+ in_negotiate_context_count,
+ &in_c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ status = smb2_negotiate_context_process_posix(&in_c, &posix);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ if ((dialect != SMB2_DIALECT_REVISION_2FF) &&
+ (protocol >= PROTOCOL_SMB2_10) &&
+ !GUID_all_zero(&in_guid))
+ {
+ ok = remote_arch_cache_update(&in_guid);
+ if (!ok) {
+ return smbd_smb2_request_error(
+ req, NT_STATUS_UNSUCCESSFUL);
+ }
+ }
+
+ switch (get_remote_arch()) {
+ case RA_VISTA:
+ case RA_SAMBA:
+ case RA_CIFSFS:
+ case RA_OSX:
+ break;
+ default:
+ set_remote_arch(RA_VISTA);
+ break;
+ }
+
+ {
+ fstring proto;
+ fstr_sprintf(proto,
+ "SMB%X_%02X",
+ (dialect >> 8) & 0xFF, dialect & 0xFF);
+ set_remote_proto(proto);
+ DEBUG(3,("Selected protocol %s\n", proto));
+ }
+
+ reload_services(req->sconn, conn_snum_used, true);
+
+ in_preauth = smb2_negotiate_context_find(&in_c,
+ SMB2_PREAUTH_INTEGRITY_CAPABILITIES);
+ if (protocol >= PROTOCOL_SMB3_11 && in_preauth == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ in_cipher = smb2_negotiate_context_find(&in_c,
+ SMB2_ENCRYPTION_CAPABILITIES);
+ in_sign_algo = smb2_negotiate_context_find(&in_c,
+ SMB2_SIGNING_CAPABILITIES);
+
+ /* negprot_spnego() returns the server guid in the first 16 bytes */
+ negprot_spnego_blob = negprot_spnego(req, xconn);
+ if (negprot_spnego_blob.data == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ if (negprot_spnego_blob.length < 16) {
+ return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
+ }
+
+ security_mode = SMB2_NEGOTIATE_SIGNING_ENABLED;
+ if (xconn->smb2.signing_mandatory) {
+ security_mode |= SMB2_NEGOTIATE_SIGNING_REQUIRED;
+ }
+
+ capabilities = 0;
+ if (lp_host_msdfs()) {
+ capabilities |= SMB2_CAP_DFS;
+ }
+
+ if (protocol >= PROTOCOL_SMB2_10 &&
+ lp_smb2_leases() &&
+ lp_oplocks(GLOBAL_SECTION_SNUM) &&
+ !lp_kernel_oplocks(GLOBAL_SECTION_SNUM))
+ {
+ capabilities |= SMB2_CAP_LEASING;
+ }
+
+ if ((protocol >= PROTOCOL_SMB3_00) &&
+ (lp_server_smb_encrypt(-1) != SMB_ENCRYPTION_OFF) &&
+ (in_capabilities & SMB2_CAP_ENCRYPTION)) {
+ capabilities |= SMB2_CAP_ENCRYPTION;
+ }
+
+ /*
+ * 0x10000 (65536) is the maximum allowed message size
+ * for SMB 2.0
+ */
+ max_limit = 0x10000;
+
+ if (protocol >= PROTOCOL_SMB2_10) {
+ int p = 0;
+
+ if (tsocket_address_is_inet(req->sconn->local_address, "ip")) {
+ p = tsocket_address_inet_port(req->sconn->local_address);
+ }
+
+ /* largeMTU is not supported over NBT (tcp port 139) */
+ if (p != NBT_SMB_PORT) {
+ capabilities |= SMB2_CAP_LARGE_MTU;
+ xconn->smb2.credits.multicredit = true;
+
+ /*
+ * We allow up to almost 16MB.
+ *
+ * The maximum PDU size is 0xFFFFFF (16776960)
+ * and we need some space for the header.
+ */
+ max_limit = 0xFFFF00;
+ }
+ }
+
+ /*
+ * the defaults are 8MB, but we'll limit this to max_limit based on
+ * the dialect (64kb for SMB 2.0, 8MB for SMB >= 2.1 with LargeMTU)
+ *
+ * user configured values exceeding the limits will be overwritten,
+ * only smaller values will be accepted
+ */
+
+ max_trans = MIN(max_limit, lp_smb2_max_trans());
+ max_read = MIN(max_limit, lp_smb2_max_read());
+ max_write = MIN(max_limit, lp_smb2_max_write());
+
+ if (in_preauth != NULL) {
+ size_t needed = 4;
+ uint16_t hash_count;
+ uint16_t salt_length;
+ uint16_t selected_preauth = 0;
+ const uint8_t *p;
+ uint8_t buf[38];
+ size_t i;
+
+ if (in_preauth->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ hash_count = SVAL(in_preauth->data.data, 0);
+ salt_length = SVAL(in_preauth->data.data, 2);
+
+ if (hash_count == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ p = in_preauth->data.data + needed;
+ needed += hash_count * 2;
+ needed += salt_length;
+
+ if (in_preauth->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ for (i=0; i < hash_count; i++) {
+ uint16_t v;
+
+ v = SVAL(p, 0);
+ p += 2;
+
+ if (v == SMB2_PREAUTH_INTEGRITY_SHA512) {
+ selected_preauth = v;
+ break;
+ }
+ }
+
+ if (selected_preauth == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_SMB_NO_PREAUTH_INTEGRITY_HASH_OVERLAP);
+ }
+
+ SSVAL(buf, 0, 1); /* HashAlgorithmCount */
+ SSVAL(buf, 2, 32); /* SaltLength */
+ SSVAL(buf, 4, selected_preauth);
+ generate_random_buffer(buf + 6, 32);
+
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_PREAUTH_INTEGRITY_CAPABILITIES,
+ buf,
+ sizeof(buf));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ req->preauth = &req->xconn->smb2.preauth;
+ }
+
+ if (protocol >= PROTOCOL_SMB3_00) {
+ xconn->smb2.server.sign_algo = SMB2_SIGNING_AES128_CMAC;
+ } else {
+ xconn->smb2.server.sign_algo = SMB2_SIGNING_HMAC_SHA256;
+ }
+
+ if ((capabilities & SMB2_CAP_ENCRYPTION) && (in_cipher != NULL)) {
+ const struct smb3_encryption_capabilities *srv_ciphers =
+ &default_smb3_capabilities.encryption;
+ uint16_t srv_preferred_idx = UINT16_MAX;
+ size_t needed = 2;
+ uint16_t cipher_count;
+ const uint8_t *p;
+ uint8_t buf[4];
+ size_t i;
+
+ capabilities &= ~SMB2_CAP_ENCRYPTION;
+
+ if (in_cipher->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ cipher_count = SVAL(in_cipher->data.data, 0);
+ if (cipher_count == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ p = in_cipher->data.data + needed;
+ needed += cipher_count * 2;
+
+ if (in_cipher->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ for (i=0; i < cipher_count; i++) {
+ uint16_t si;
+ uint16_t v;
+
+ v = SVAL(p, 0);
+ p += 2;
+
+ for (si = 0; si < srv_ciphers->num_algos; si++) {
+ if (srv_ciphers->algos[si] != v) {
+ continue;
+ }
+
+ /*
+ * The server ciphers are listed
+ * with the lowest idx being preferred.
+ */
+ if (si < srv_preferred_idx) {
+ srv_preferred_idx = si;
+ }
+ break;
+ }
+ }
+
+ if (srv_preferred_idx != UINT16_MAX) {
+ xconn->smb2.server.cipher =
+ srv_ciphers->algos[srv_preferred_idx];
+ }
+
+ SSVAL(buf, 0, 1); /* ChiperCount */
+ SSVAL(buf, 2, xconn->smb2.server.cipher);
+
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_ENCRYPTION_CAPABILITIES,
+ buf,
+ sizeof(buf));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ if (capabilities & SMB2_CAP_ENCRYPTION) {
+ xconn->smb2.server.cipher = SMB2_ENCRYPTION_AES128_CCM;
+ }
+
+ if (in_sign_algo != NULL) {
+ const struct smb3_signing_capabilities *srv_sign_algos =
+ &default_smb3_capabilities.signing;
+ uint16_t srv_preferred_idx = UINT16_MAX;
+ size_t needed = 2;
+ uint16_t sign_algo_count;
+ const uint8_t *p;
+ size_t i;
+
+ if (in_sign_algo->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ sign_algo_count = SVAL(in_sign_algo->data.data, 0);
+ if (sign_algo_count == 0) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ p = in_sign_algo->data.data + needed;
+ needed += sign_algo_count * 2;
+
+ if (in_sign_algo->data.length < needed) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ for (i=0; i < sign_algo_count; i++) {
+ uint16_t si;
+ uint16_t v;
+
+ v = SVAL(p, 0);
+ p += 2;
+
+ for (si = 0; si < srv_sign_algos->num_algos; si++) {
+ if (srv_sign_algos->algos[si] != v) {
+ continue;
+ }
+
+ /*
+ * The server sign_algos are listed
+ * with the lowest idx being preferred.
+ */
+ if (si < srv_preferred_idx) {
+ srv_preferred_idx = si;
+ }
+ break;
+ }
+ }
+
+ /*
+ * If we found a match announce it
+ * otherwise we'll keep the default
+ * of SMB2_SIGNING_AES128_CMAC
+ */
+ if (srv_preferred_idx != UINT16_MAX) {
+ uint8_t buf[4];
+
+ xconn->smb2.server.sign_algo =
+ srv_sign_algos->algos[srv_preferred_idx];
+
+ SSVAL(buf, 0, 1); /* SigningAlgorithmCount */
+ SSVAL(buf, 2, xconn->smb2.server.sign_algo);
+
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_SIGNING_CAPABILITIES,
+ buf,
+ sizeof(buf));
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ }
+
+ status = smb311_capabilities_check(&default_smb3_capabilities,
+ "smb2srv_negprot",
+ DBGLVL_NOTICE,
+ NT_STATUS_INVALID_PARAMETER,
+ "server",
+ protocol,
+ xconn->smb2.server.sign_algo,
+ xconn->smb2.server.cipher);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ if (protocol >= PROTOCOL_SMB3_00 &&
+ xconn->client->server_multi_channel_enabled)
+ {
+ if (in_capabilities & SMB2_CAP_MULTI_CHANNEL) {
+ capabilities |= SMB2_CAP_MULTI_CHANNEL;
+ }
+ }
+
+ security_offset = SMB2_HDR_BODY + 0x40;
+
+#if 1
+ /* Try SPNEGO auth... */
+ security_buffer = data_blob_const(negprot_spnego_blob.data + 16,
+ negprot_spnego_blob.length - 16);
+#else
+ /* for now we want raw NTLMSSP */
+ security_buffer = data_blob_const(NULL, 0);
+#endif
+
+ if (posix) {
+ /* Client correctly negotiated SMB2 unix extensions. */
+ const uint8_t *buf = (const uint8_t *)SMB2_CREATE_TAG_POSIX;
+ status = smb2_negotiate_context_add(
+ req,
+ &out_c,
+ SMB2_POSIX_EXTENSIONS_AVAILABLE,
+ buf,
+ 16);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ xconn->smb2.server.posix_extensions_negotiated = true;
+ }
+
+ if (out_c.num_contexts != 0) {
+ status = smb2_negotiate_context_push(req,
+ &out_negotiate_context_blob,
+ out_c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+
+ if (out_negotiate_context_blob.length != 0) {
+ static const uint8_t zeros[8];
+ size_t pad = 0;
+ size_t ofs;
+
+ outdyn = data_blob_dup_talloc(req, security_buffer);
+ if (outdyn.length != security_buffer.length) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ ofs = security_offset + security_buffer.length;
+ if ((ofs % 8) != 0) {
+ pad = 8 - (ofs % 8);
+ }
+ ofs += pad;
+
+ ok = data_blob_append(req, &outdyn, zeros, pad);
+ if (!ok) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ ok = data_blob_append(req, &outdyn,
+ out_negotiate_context_blob.data,
+ out_negotiate_context_blob.length);
+ if (!ok) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ out_negotiate_context_offset = ofs;
+ out_negotiate_context_count = out_c.num_contexts;
+ } else {
+ outdyn = security_buffer;
+ }
+
+ out_guid_blob = data_blob_const(negprot_spnego_blob.data, 16);
+ status = GUID_from_ndr_blob(&out_guid_blob, &out_guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x40);
+ if (outbody.data == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+
+ SSVAL(outbody.data, 0x00, 0x40 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ security_mode); /* security mode */
+ SSVAL(outbody.data, 0x04, dialect); /* dialect revision */
+ SSVAL(outbody.data, 0x06,
+ out_negotiate_context_count); /* reserved/NegotiateContextCount */
+ memcpy(outbody.data + 0x08,
+ out_guid_blob.data, 16); /* server guid */
+ SIVAL(outbody.data, 0x18,
+ capabilities); /* capabilities */
+ SIVAL(outbody.data, 0x1C, max_trans); /* max transact size */
+ SIVAL(outbody.data, 0x20, max_read); /* max read size */
+ SIVAL(outbody.data, 0x24, max_write); /* max write size */
+ SBVAL(outbody.data, 0x28, now); /* system time */
+ SBVAL(outbody.data, 0x30, 0); /* server start time */
+ SSVAL(outbody.data, 0x38,
+ security_offset); /* security buffer offset */
+ SSVAL(outbody.data, 0x3A,
+ security_buffer.length); /* security buffer length */
+ SIVAL(outbody.data, 0x3C,
+ out_negotiate_context_offset); /* reserved/NegotiateContextOffset */
+
+ req->sconn->using_smb2 = true;
+
+ if (dialect == SMB2_DIALECT_REVISION_2FF) {
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ status = smbXsrv_connection_init_tables(xconn, protocol);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ xconn->smb2.client.capabilities = in_capabilities;
+ xconn->smb2.client.security_mode = in_security_mode;
+ xconn->smb2.client.guid = in_guid;
+ xconn->smb2.client.num_dialects = dialect_count;
+ xconn->smb2.client.dialects = talloc_array(xconn,
+ uint16_t,
+ dialect_count);
+ if (xconn->smb2.client.dialects == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ for (c=0; c < dialect_count; c++) {
+ xconn->smb2.client.dialects[c] = SVAL(indyn, c*2);
+ }
+
+ xconn->smb2.server.capabilities = capabilities;
+ xconn->smb2.server.security_mode = security_mode;
+ xconn->smb2.server.guid = out_guid;
+ xconn->smb2.server.dialect = dialect;
+ xconn->smb2.server.max_trans = max_trans;
+ xconn->smb2.server.max_read = max_read;
+ xconn->smb2.server.max_write = max_write;
+
+ if (xconn->protocol < PROTOCOL_SMB2_10) {
+ /*
+ * SMB2_02 doesn't support client guids
+ */
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ if (!xconn->client->server_multi_channel_enabled) {
+ /*
+ * Only deal with the client guid database
+ * if multi-channel is enabled.
+ *
+ * But we still need to setup
+ * xconn->client->global->client_guid to
+ * the correct value.
+ */
+ xconn->client->global->client_guid =
+ xconn->smb2.client.guid;
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ if (xconn->smb2.client.guid_verified) {
+ /*
+ * The connection was passed from another
+ * smbd process.
+ */
+ return smbd_smb2_request_done(req, outbody, &outdyn);
+ }
+
+ state = talloc_zero(req, struct smbd_smb2_request_process_negprot_state);
+ if (state == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ *state = (struct smbd_smb2_request_process_negprot_state) {
+ .req = req,
+ .outbody = outbody,
+ .outdyn = outdyn,
+ };
+
+ subreq = smb2srv_client_mc_negprot_send(state,
+ req->xconn->client->raw_ev_ctx,
+ req);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_request_process_negprot_mc_done,
+ state);
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_request_process_negprot_mc_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request_process_negprot_state *state =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request_process_negprot_state);
+ struct smbd_smb2_request *req = state->req;
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+
+ status = smb2srv_client_mc_negprot_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MESSAGE_RETRIEVED)) {
+ /*
+ * The connection was passed to another process
+ *
+ * We mark the error as NT_STATUS_CONNECTION_IN_USE,
+ * in order to indicate to low level code if
+ * ctdbd_unregister_ips() or ctdbd_passed_ips()
+ * is more useful.
+ */
+ smbXsrv_connection_disconnect_transport(xconn,
+ NT_STATUS_CONNECTION_IN_USE);
+ smbd_server_connection_terminate(xconn,
+ "passed connection");
+ /*
+ * smbd_server_connection_terminate() should not return!
+ */
+ smb_panic(__location__);
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbd_smb2_request_error(req, status);
+ if (NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ /*
+ * The connection was passed to another process
+ */
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ /*
+ * smbd_server_connection_terminate() should not return!
+ */
+ smb_panic(__location__);
+ return;
+ }
+
+ /*
+ * We're the first connection...
+ */
+ status = smbd_smb2_request_done(req, state->outbody, &state->outdyn);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * This allows us to support starting smbd under
+ * callgrind and only start the overhead and
+ * instrumentation after the SMB2 negprot,
+ * this allows us to profile only useful
+ * stuff and not all the smbd startup, forking
+ * and multichannel handling.
+ *
+ * valgrind --tool=callgrind --instr-atstart=no smbd
+ */
+#ifdef CALLGRIND_START_INSTRUMENTATION
+ CALLGRIND_START_INSTRUMENTATION;
+#endif
+ return;
+ }
+
+ /*
+ * The connection was passed to another process
+ */
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ /*
+ * smbd_server_connection_terminate() should not return!
+ */
+ smb_panic(__location__);
+ return;
+}
+
+/****************************************************************************
+ Generate the spnego negprot reply blob. Return the number of bytes used.
+****************************************************************************/
+
+DATA_BLOB negprot_spnego(TALLOC_CTX *ctx, struct smbXsrv_connection *xconn)
+{
+ DATA_BLOB blob = data_blob_null;
+ DATA_BLOB blob_out = data_blob_null;
+ nstring dos_name;
+ fstring unix_name;
+ NTSTATUS status;
+#ifdef DEVELOPER
+ size_t slen;
+#endif
+ struct gensec_security *gensec_security;
+
+ /* See if we can get an SPNEGO blob */
+ status = auth_generic_prepare(talloc_tos(),
+ xconn->remote_address,
+ xconn->local_address,
+ "SMB",
+ &gensec_security);
+
+ /*
+ * Despite including it above, there is no need to set a
+ * remote address or similar as we are just interested in the
+ * SPNEGO blob, we never keep this context.
+ */
+
+ if (NT_STATUS_IS_OK(status)) {
+ status = gensec_start_mech_by_oid(gensec_security, GENSEC_OID_SPNEGO);
+ if (NT_STATUS_IS_OK(status)) {
+ status = gensec_update(gensec_security, ctx,
+ data_blob_null, &blob);
+ /* If we get the list of OIDs, the 'OK' answer
+ * is NT_STATUS_MORE_PROCESSING_REQUIRED */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ DEBUG(0, ("Failed to start SPNEGO handler for negprot OID list!\n"));
+ blob = data_blob_null;
+ }
+ }
+ TALLOC_FREE(gensec_security);
+ }
+
+#if defined(WITH_SMB1SERVER)
+ xconn->smb1.negprot.spnego = true;
+#endif
+
+ /* strangely enough, NT does not sent the single OID NTLMSSP when
+ not a ADS member, it sends no OIDs at all
+
+ OLD COMMENT : "we can't do this until we teach our session setup parser to know
+ about raw NTLMSSP (clients send no ASN.1 wrapping if we do this)"
+
+ Our sessionsetup code now handles raw NTLMSSP connects, so we can go
+ back to doing what W2K3 does here. This is needed to make PocketPC 2003
+ CIFS connections work with SPNEGO. See bugzilla bugs #1828 and #3133
+ for details. JRA.
+
+ */
+
+ if (blob.length == 0 || blob.data == NULL) {
+ return data_blob_null;
+ }
+
+ blob_out = data_blob_talloc(ctx, NULL, 16 + blob.length);
+ if (blob_out.data == NULL) {
+ data_blob_free(&blob);
+ return data_blob_null;
+ }
+
+ memset(blob_out.data, '\0', 16);
+
+ checked_strlcpy(unix_name, lp_netbios_name(), sizeof(unix_name));
+ (void)strlower_m(unix_name);
+ push_ascii_nstring(dos_name, unix_name);
+ strlcpy((char *)blob_out.data, dos_name, 17);
+
+#ifdef DEVELOPER
+ /* Fix valgrind 'uninitialized bytes' issue. */
+ slen = strlen(dos_name);
+ if (slen < 16) {
+ memset(blob_out.data+slen, '\0', 16 - slen);
+ }
+#endif
+
+ memcpy(&blob_out.data[16], blob.data, blob.length);
+
+ data_blob_free(&blob);
+
+ return blob_out;
+}
+
+/*
+ * MS-CIFS, 2.2.4.52.2 SMB_COM_NEGOTIATE Response:
+ * If the server does not support any of the listed dialects, it MUST return a
+ * DialectIndex of 0XFFFF
+ */
+#define NO_PROTOCOL_CHOSEN 0xffff
+
+#define PROT_SMB_2_002 0x1000
+#define PROT_SMB_2_FF 0x2000
+
+/* List of supported SMB1 protocols, most desired first.
+ * This is for enabling multi-protocol negotiation in SMB2 when SMB1
+ * is disabled.
+ */
+static const struct {
+ const char *proto_name;
+ const char *short_name;
+ NTSTATUS (*proto_reply_fn)(struct smb_request *req, uint16_t choice);
+ int protocol_level;
+} supported_protocols[] = {
+ {"SMB 2.???", "SMB2_FF", reply_smb20ff, PROTOCOL_SMB2_10},
+ {"SMB 2.002", "SMB2_02", reply_smb2002, PROTOCOL_SMB2_02},
+ {NULL,NULL,NULL,0},
+};
+
+/****************************************************************************
+ Reply to a negprot.
+ conn POINTER CAN BE NULL HERE !
+****************************************************************************/
+
+NTSTATUS smb2_multi_protocol_reply_negprot(struct smb_request *req)
+{
+ size_t choice = 0;
+ bool choice_set = false;
+ int protocol;
+ const char *p;
+ int num_cliprotos;
+ char **cliprotos;
+ size_t i;
+ size_t converted_size;
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_server_connection *sconn = req->sconn;
+ int max_proto;
+ int min_proto;
+ NTSTATUS status;
+
+ START_PROFILE(SMBnegprot);
+
+ if (req->buflen == 0) {
+ DEBUG(0, ("negprot got no protocols\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (req->buf[req->buflen-1] != '\0') {
+ DEBUG(0, ("negprot protocols not 0-terminated\n"));
+ reply_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ p = (const char *)req->buf + 1;
+
+ num_cliprotos = 0;
+ cliprotos = NULL;
+
+ while (smbreq_bufrem(req, p) > 0) {
+
+ char **tmp;
+
+ tmp = talloc_realloc(talloc_tos(), cliprotos, char *,
+ num_cliprotos+1);
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cliprotos = tmp;
+
+ if (!pull_ascii_talloc(cliprotos, &cliprotos[num_cliprotos], p,
+ &converted_size)) {
+ DEBUG(0, ("pull_ascii_talloc failed\n"));
+ TALLOC_FREE(cliprotos);
+ reply_nterror(req, NT_STATUS_NO_MEMORY);
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(3, ("Requested protocol [%s]\n",
+ cliprotos[num_cliprotos]));
+
+ num_cliprotos += 1;
+ p += strlen(p) + 2;
+ }
+
+ /* possibly reload - change of architecture */
+ reload_services(sconn, conn_snum_used, true);
+
+ /*
+ * Anything higher than PROTOCOL_SMB2_10 still
+ * needs to go via "SMB 2.???", which is marked
+ * as PROTOCOL_SMB2_10.
+ *
+ * The real negotiation happens via reply_smb20ff()
+ * using SMB2 Negotiation.
+ */
+ max_proto = lp_server_max_protocol();
+ if (max_proto > PROTOCOL_SMB2_10) {
+ max_proto = PROTOCOL_SMB2_10;
+ }
+ min_proto = lp_server_min_protocol();
+ if (min_proto > PROTOCOL_SMB2_10) {
+ min_proto = PROTOCOL_SMB2_10;
+ }
+
+ /* Check for protocols, most desirable first */
+ for (protocol = 0; supported_protocols[protocol].proto_name; protocol++) {
+ i = 0;
+ if ((supported_protocols[protocol].protocol_level <= max_proto) &&
+ (supported_protocols[protocol].protocol_level >= min_proto))
+ while (i < num_cliprotos) {
+ if (strequal(cliprotos[i],supported_protocols[protocol].proto_name)) {
+ choice = i;
+ choice_set = true;
+ }
+ i++;
+ }
+ if (choice_set) {
+ break;
+ }
+ }
+
+ if (!choice_set) {
+ bool ok;
+
+ DBG_NOTICE("No protocol supported !\n");
+ reply_smb1_outbuf(req, 1, 0);
+ SSVAL(req->outbuf, smb_vwv0, NO_PROTOCOL_CHOSEN);
+
+ ok = smb1_srv_send(xconn, (char *)req->outbuf, false, 0, false);
+ if (!ok) {
+ DBG_NOTICE("smb1_srv_send failed\n");
+ }
+ exit_server_cleanly("no protocol supported\n");
+ }
+
+ set_remote_proto(supported_protocols[protocol].short_name);
+ reload_services(sconn, conn_snum_used, true);
+ status = supported_protocols[protocol].proto_reply_fn(req, choice);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("negprot function failed\n");
+ }
+
+ DEBUG(3,("Selected protocol %s\n",supported_protocols[protocol].proto_name));
+
+ DBG_INFO("negprot index=%zu\n", choice);
+
+ TALLOC_FREE(cliprotos);
+
+ END_PROFILE(SMBnegprot);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_notify.c b/source3/smbd/smb2_notify.c
new file mode 100644
index 0000000..8cccd90
--- /dev/null
+++ b/source3/smbd/smb2_notify.c
@@ -0,0 +1,394 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+struct smbd_smb2_notify_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ bool has_request;
+ bool skip_reply;
+ NTSTATUS status;
+ DATA_BLOB out_output_buffer;
+};
+
+static struct tevent_req *smbd_smb2_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint16_t in_flags,
+ uint32_t in_output_buffer_length,
+ uint64_t in_completion_filter);
+static NTSTATUS smbd_smb2_notify_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer);
+
+static void smbd_smb2_request_notify_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_notify(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint16_t in_flags;
+ uint32_t in_output_buffer_length;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint64_t in_completion_filter;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x20);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_flags = SVAL(inbody, 0x02);
+ in_output_buffer_length = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_completion_filter = IVAL(inbody, 0x18);
+
+ /*
+ * 0x00010000 is what Windows 7 uses,
+ * Windows 2008 uses 0x00080000
+ */
+ if (in_output_buffer_length > xconn->smb2.server.max_trans) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req,
+ in_output_buffer_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_notify_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_flags,
+ in_output_buffer_length,
+ in_completion_filter);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_notify_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_notify_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_output_buffer_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_notify_recv(subreq,
+ req,
+ &out_output_buffer);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
+
+ outbody = smbd_smb2_generate_outbody(req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_output_buffer_offset); /* output buffer offset */
+ SIVAL(outbody.data, 0x04,
+ out_output_buffer.length); /* output buffer length */
+
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static void smbd_smb2_notify_reply(struct smb_request *smbreq,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len);
+static bool smbd_smb2_notify_cancel(struct tevent_req *req);
+
+static int smbd_smb2_notify_state_destructor(struct smbd_smb2_notify_state *state)
+{
+ if (!state->has_request) {
+ return 0;
+ }
+
+ state->skip_reply = true;
+ smbd_notify_cancel_by_smbreq(state->smbreq);
+ return 0;
+}
+
+static int smbd_smb2_notify_smbreq_destructor(struct smb_request *smbreq)
+{
+ struct tevent_req *req = talloc_get_type_abort(smbreq->async_priv,
+ struct tevent_req);
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ /*
+ * Our temporary parent from change_notify_add_request()
+ * goes away.
+ */
+ state->has_request = false;
+
+ /*
+ * move it back to its original parent,
+ * which means we no longer need the destructor
+ * to protect it.
+ */
+ talloc_steal(smbreq->smb2req, smbreq);
+ talloc_set_destructor(smbreq, NULL);
+
+ /*
+ * We want to keep smbreq!
+ */
+ return -1;
+}
+
+static struct tevent_req *smbd_smb2_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint16_t in_flags,
+ uint32_t in_output_buffer_length,
+ uint64_t in_completion_filter)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_notify_state *state;
+ struct smb_request *smbreq;
+ connection_struct *conn = smb2req->tcon->compat;
+ bool recursive = (in_flags & SMB2_WATCH_TREE) ? true : false;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_notify_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ state->out_output_buffer = data_blob_null;
+ talloc_set_destructor(state, smbd_smb2_notify_state_destructor);
+
+ DEBUG(10,("smbd_smb2_notify_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->smbreq = smbreq;
+ smbreq->async_priv = (void *)req;
+
+ if (DEBUGLEVEL >= 3) {
+ char *filter_string;
+
+ filter_string = notify_filter_string(NULL, in_completion_filter);
+ if (tevent_req_nomem(filter_string, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ DEBUG(3,("smbd_smb2_notify_send: notify change "
+ "called on %s, filter = %s, recursive = %d\n",
+ fsp_str_dbg(fsp), filter_string, recursive));
+
+ TALLOC_FREE(filter_string);
+ }
+
+ if ((!fsp->fsp_flags.is_directory) || (conn != fsp->conn)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (fsp->notify == NULL) {
+
+ status = change_notify_create(fsp,
+ in_output_buffer_length,
+ in_completion_filter,
+ recursive);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("change_notify_create returned %s\n",
+ nt_errstr(status)));
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (change_notify_fsp_has_changes(fsp)) {
+
+ /*
+ * We've got changes pending, respond immediately
+ */
+
+ /*
+ * TODO: write a torture test to check the filtering behaviour
+ * here.
+ */
+
+ change_notify_reply(smbreq,
+ NT_STATUS_OK,
+ in_output_buffer_length,
+ fsp->notify,
+ smbd_smb2_notify_reply);
+
+ /*
+ * change_notify_reply() above has independently
+ * called tevent_req_done().
+ */
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * No changes pending, queue the request
+ */
+
+ status = change_notify_add_request(smbreq,
+ in_output_buffer_length,
+ in_completion_filter,
+ recursive, fsp,
+ smbd_smb2_notify_reply);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * This is a HACK!
+ *
+ * change_notify_add_request() talloc_moves()
+ * smbreq away from us, so we need a destructor
+ * which moves it back at the end.
+ */
+ state->has_request = true;
+ talloc_set_destructor(smbreq, smbd_smb2_notify_smbreq_destructor);
+
+ /* allow this request to be canceled */
+ tevent_req_set_cancel_fn(req, smbd_smb2_notify_cancel);
+
+ SMBPROFILE_IOBYTES_ASYNC_SET_IDLE(state->smb2req->profile);
+ return req;
+}
+
+static void smbd_smb2_notify_reply(struct smb_request *smbreq,
+ NTSTATUS error_code,
+ uint8_t *buf, size_t len)
+{
+ struct tevent_req *req = talloc_get_type_abort(smbreq->async_priv,
+ struct tevent_req);
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ if (state->skip_reply) {
+ return;
+ }
+
+ SMBPROFILE_IOBYTES_ASYNC_SET_BUSY(state->smb2req->profile);
+
+ state->status = error_code;
+ if (!NT_STATUS_IS_OK(error_code)) {
+ /* nothing */
+ } else if (len == 0) {
+ state->status = NT_STATUS_NOTIFY_ENUM_DIR;
+ } else {
+ state->out_output_buffer = data_blob_talloc(state, buf, len);
+ if (state->out_output_buffer.data == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ tevent_req_defer_callback(req, state->smb2req->sconn->ev_ctx);
+
+ if (tevent_req_nterror(req, state->status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static bool smbd_smb2_notify_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ smbd_notify_cancel_by_smbreq(state->smbreq);
+
+ return true;
+}
+
+static NTSTATUS smbd_smb2_notify_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer)
+{
+ NTSTATUS status;
+ struct smbd_smb2_notify_state *state = tevent_req_data(req,
+ struct smbd_smb2_notify_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_output_buffer = state->out_output_buffer;
+ talloc_steal(mem_ctx, out_output_buffer->data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_nttrans.c b/source3/smbd/smb2_nttrans.c
new file mode 100644
index 0000000..49bddf5
--- /dev/null
+++ b/source3/smbd/smb2_nttrans.c
@@ -0,0 +1,911 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB NT transaction handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "libsmb/libsmb.h"
+#include "lib/util_ea.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+#include "librpc/gen_ndr/ndr_security.h"
+
+extern const struct generic_mapping file_generic_mapping;
+
+/*********************************************************************
+ Windows seems to do canonicalization of inheritance bits. Do the
+ same.
+*********************************************************************/
+
+static void canonicalize_inheritance_bits(struct files_struct *fsp,
+ struct security_descriptor *psd)
+{
+ bool set_auto_inherited = false;
+
+ /*
+ * We need to filter out the
+ * SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ
+ * bits. If both are set we store SEC_DESC_DACL_AUTO_INHERITED
+ * as this alters whether SEC_ACE_FLAG_INHERITED_ACE is set
+ * when an ACE is inherited. Otherwise we zero these bits out.
+ * See:
+ *
+ * http://social.msdn.microsoft.com/Forums/eu/os_fileservices/thread/11f77b68-731e-407d-b1b3-064750716531
+ *
+ * for details.
+ */
+
+ if (!lp_acl_flag_inherited_canonicalization(SNUM(fsp->conn))) {
+ psd->type &= ~SEC_DESC_DACL_AUTO_INHERIT_REQ;
+ return;
+ }
+
+ if ((psd->type & (SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ))
+ == (SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ)) {
+ set_auto_inherited = true;
+ }
+
+ psd->type &= ~(SEC_DESC_DACL_AUTO_INHERITED|SEC_DESC_DACL_AUTO_INHERIT_REQ);
+ if (set_auto_inherited) {
+ psd->type |= SEC_DESC_DACL_AUTO_INHERITED;
+ }
+}
+
+/****************************************************************************
+ Internal fn to set security descriptors.
+****************************************************************************/
+
+NTSTATUS set_sd(files_struct *fsp, struct security_descriptor *psd,
+ uint32_t security_info_sent)
+{
+ files_struct *sd_fsp = NULL;
+ NTSTATUS status;
+
+ if (!CAN_WRITE(fsp->conn)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!lp_nt_acl_support(SNUM(fsp->conn))) {
+ return NT_STATUS_OK;
+ }
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("ACL set on symlink %s denied.\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ if (psd->owner_sid == NULL) {
+ security_info_sent &= ~SECINFO_OWNER;
+ }
+ if (psd->group_sid == NULL) {
+ security_info_sent &= ~SECINFO_GROUP;
+ }
+
+ /* Ensure we have at least one thing set. */
+ if ((security_info_sent & (SECINFO_OWNER|SECINFO_GROUP|SECINFO_DACL|SECINFO_SACL)) == 0) {
+ /* Just like W2K3 */
+ return NT_STATUS_OK;
+ }
+
+ /* Ensure we have the rights to do this. */
+ if (security_info_sent & SECINFO_OWNER) {
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (security_info_sent & SECINFO_GROUP) {
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_OWNER);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (security_info_sent & SECINFO_DACL) {
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_DAC);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* Convert all the generic bits. */
+ if (psd->dacl) {
+ security_acl_map_generic(psd->dacl, &file_generic_mapping);
+ }
+ }
+
+ if (security_info_sent & SECINFO_SACL) {
+ status = check_any_access_fsp(fsp, SEC_FLAG_SYSTEM_SECURITY);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /*
+ * Setting a SACL also requires WRITE_DAC.
+ * See the smbtorture3 SMB2-SACL test.
+ */
+ status = check_any_access_fsp(fsp, SEC_STD_WRITE_DAC);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ /* Convert all the generic bits. */
+ if (psd->sacl) {
+ security_acl_map_generic(psd->sacl, &file_generic_mapping);
+ }
+ }
+
+ canonicalize_inheritance_bits(fsp, psd);
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("set_sd for file %s\n", fsp_str_dbg(fsp)));
+ NDR_PRINT_DEBUG(security_descriptor, psd);
+ }
+
+ sd_fsp = metadata_fsp(fsp);
+ status = SMB_VFS_FSET_NT_ACL(sd_fsp, security_info_sent, psd);
+
+ TALLOC_FREE(psd);
+
+ return status;
+}
+
+static bool check_smb2_posix_chmod_ace(const struct files_struct *fsp,
+ uint32_t security_info_sent,
+ struct security_descriptor *psd,
+ mode_t *pmode)
+{
+ int cmp;
+
+ /*
+ * This must be an ACL with one ACE containing an
+ * MS NFS style mode entry coming in on a POSIX
+ * handle over SMB2+.
+ */
+ if (!fsp->conn->sconn->using_smb2) {
+ return false;
+ }
+
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ return false;
+ }
+
+ if (!(security_info_sent & SECINFO_DACL)) {
+ return false;
+ }
+
+ if (psd->dacl == NULL) {
+ return false;
+ }
+
+ if (psd->dacl->num_aces != 1) {
+ return false;
+ }
+
+ cmp = dom_sid_compare_domain(&global_sid_Unix_NFS_Mode,
+ &psd->dacl->aces[0].trustee);
+ if (cmp != 0) {
+ return false;
+ }
+
+ *pmode = (mode_t)psd->dacl->aces[0].trustee.sub_auths[2];
+ *pmode &= (S_IRWXU | S_IRWXG | S_IRWXO);
+
+ return true;
+}
+
+/****************************************************************************
+ Internal fn to set security descriptors from a data blob.
+****************************************************************************/
+
+NTSTATUS set_sd_blob(files_struct *fsp, uint8_t *data, uint32_t sd_len,
+ uint32_t security_info_sent)
+{
+ struct security_descriptor *psd = NULL;
+ NTSTATUS status;
+ bool do_chmod = false;
+ mode_t smb2_posix_mode = 0;
+ int ret;
+
+ if (sd_len == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = unmarshall_sec_desc(talloc_tos(), data, sd_len, &psd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ do_chmod = check_smb2_posix_chmod_ace(fsp,
+ security_info_sent,
+ psd,
+ &smb2_posix_mode);
+ if (!do_chmod) {
+ return set_sd(fsp, psd, security_info_sent);
+ }
+
+ TALLOC_FREE(psd);
+
+ ret = SMB_VFS_FCHMOD(fsp, smb2_posix_mode);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_ERR("smb2_posix_chmod [%s] [%04o] failed: %s\n",
+ fsp_str_dbg(fsp),
+ (unsigned)smb2_posix_mode,
+ nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Copy a file.
+****************************************************************************/
+
+NTSTATUS copy_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct files_struct *dst_dirfsp,
+ struct smb_filename *smb_fname_dst,
+ uint32_t attrs)
+{
+ files_struct *fsp1,*fsp2;
+ uint32_t fattr;
+ int info;
+ off_t ret=-1;
+ NTSTATUS status = NT_STATUS_OK;
+ struct smb_filename *parent = NULL;
+ struct smb_filename *pathref = NULL;
+
+ if (!CAN_WRITE(conn)) {
+ status = NT_STATUS_MEDIA_WRITE_PROTECTED;
+ goto out;
+ }
+
+ /* Source must already exist. */
+ if (!VALID_STAT(smb_fname_src->st)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto out;
+ }
+
+ /* Ensure attributes match. */
+ fattr = fdos_mode(smb_fname_src->fsp);
+ if ((fattr & ~attrs) & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) {
+ status = NT_STATUS_NO_SUCH_FILE;
+ goto out;
+ }
+
+ /* Disallow if dst file already exists. */
+ if (VALID_STAT(smb_fname_dst->st)) {
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto out;
+ }
+
+ /* No links from a directory. */
+ if (S_ISDIR(smb_fname_src->st.st_ex_mode)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto out;
+ }
+
+ DEBUG(10,("copy_internals: doing file copy %s to %s\n",
+ smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ src_dirfsp, /* dirfsp */
+ smb_fname_src, /* fname */
+ FILE_READ_DATA|FILE_READ_ATTRIBUTES|
+ FILE_READ_EA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp1, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ dst_dirfsp, /* dirfsp */
+ smb_fname_dst, /* fname */
+ FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|
+ FILE_WRITE_EA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_CREATE, /* create_disposition*/
+ 0, /* create_options */
+ fattr, /* file_attributes */
+ NO_OPLOCK, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp2, /* result */
+ &info, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(NULL, &fsp1, ERROR_CLOSE);
+ goto out;
+ }
+
+ if (smb_fname_src->st.st_ex_size) {
+ ret = vfs_transfer_file(fsp1, fsp2, smb_fname_src->st.st_ex_size);
+ }
+
+ /*
+ * As we are opening fsp1 read-only we only expect
+ * an error on close on fsp2 if we are out of space.
+ * Thus we don't look at the error return from the
+ * close of fsp1.
+ */
+ close_file_free(NULL, &fsp1, NORMAL_CLOSE);
+
+ /* Ensure the modtime is set correctly on the destination file. */
+ set_close_write_time(fsp2, smb_fname_src->st.st_ex_mtime);
+
+ status = close_file_free(NULL, &fsp2, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("close_file_free() failed: %s\n",
+ nt_errstr(status));
+ /*
+ * We can't do much but leak the fsp
+ */
+ goto out;
+ }
+
+ /* Grrr. We have to do this as open_file_ntcreate adds FILE_ATTRIBUTE_ARCHIVE when it
+ creates the file. This isn't the correct thing to do in the copy
+ case. JRA */
+
+ status = SMB_VFS_PARENT_PATHNAME(conn,
+ talloc_tos(),
+ smb_fname_dst,
+ &parent,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ if (smb_fname_dst->fsp == NULL) {
+ status = synthetic_pathref(parent,
+ conn->cwd_fsp,
+ smb_fname_dst->base_name,
+ smb_fname_dst->stream_name,
+ NULL,
+ smb_fname_dst->twrp,
+ smb_fname_dst->flags,
+ &pathref);
+
+ /* should we handle NT_STATUS_OBJECT_NAME_NOT_FOUND specially here ???? */
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(parent);
+ goto out;
+ }
+ file_set_dosmode(conn, pathref, fattr, parent, false);
+ smb_fname_dst->st.st_ex_mode = pathref->st.st_ex_mode;
+ } else {
+ file_set_dosmode(conn, smb_fname_dst, fattr, parent, false);
+ }
+ TALLOC_FREE(parent);
+
+ if (ret < (off_t)smb_fname_src->st.st_ex_size) {
+ status = NT_STATUS_DISK_FULL;
+ goto out;
+ }
+ out:
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("copy_internals: Error %s copy file %s to %s\n",
+ nt_errstr(status), smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+ }
+
+ return status;
+}
+
+/******************************************************************************
+ Fake up a completely empty SD.
+*******************************************************************************/
+
+static NTSTATUS get_null_nt_acl(TALLOC_CTX *mem_ctx, struct security_descriptor **ppsd)
+{
+ size_t sd_size;
+
+ *ppsd = make_standard_sec_desc( mem_ctx, &global_sid_World, &global_sid_World, NULL, &sd_size);
+ if(!*ppsd) {
+ DEBUG(0,("get_null_nt_acl: Unable to malloc space for security descriptor.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Get a security descriptor from the file system, normalize for components
+ requested.
+****************************************************************************/
+
+static NTSTATUS smbd_fetch_security_desc(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ uint32_t security_info_wanted,
+ struct security_descriptor **ppsd)
+{
+ NTSTATUS status;
+ struct security_descriptor *psd = NULL;
+ bool need_to_read_sd = false;
+
+ /*
+ * Get the permissions to return.
+ */
+
+ if (security_info_wanted & SECINFO_SACL) {
+ status = check_any_access_fsp(fsp, SEC_FLAG_SYSTEM_SECURITY);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Access to SACL denied.\n");
+ return status;
+ }
+ }
+
+ if (security_info_wanted & (SECINFO_DACL|SECINFO_OWNER|SECINFO_GROUP)) {
+ status = check_any_access_fsp(fsp, SEC_STD_READ_CONTROL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Access to DACL, OWNER, or GROUP denied.\n");
+ return status;
+ }
+ }
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("ACL get on symlink %s denied.\n",
+ fsp_str_dbg(fsp));
+ return status;
+ }
+
+ if (security_info_wanted & (SECINFO_DACL|SECINFO_OWNER|
+ SECINFO_GROUP|SECINFO_SACL)) {
+ /* Don't return SECINFO_LABEL if anything else was
+ requested. See bug #8458. */
+ security_info_wanted &= ~SECINFO_LABEL;
+
+ /*
+ * Only query the file system SD if the caller asks
+ * for any bits. This allows a caller to open without
+ * READ_CONTROL but still issue a query sd. See
+ * smb2.sdread test.
+ */
+ need_to_read_sd = true;
+ }
+
+ if (lp_nt_acl_support(SNUM(conn)) &&
+ ((security_info_wanted & SECINFO_LABEL) == 0) &&
+ need_to_read_sd)
+ {
+ files_struct *sd_fsp = metadata_fsp(fsp);
+ status = SMB_VFS_FGET_NT_ACL(
+ sd_fsp, security_info_wanted, mem_ctx, &psd);
+ } else {
+ status = get_null_nt_acl(mem_ctx, &psd);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!(security_info_wanted & SECINFO_OWNER)) {
+ psd->owner_sid = NULL;
+ }
+ if (!(security_info_wanted & SECINFO_GROUP)) {
+ psd->group_sid = NULL;
+ }
+ if (!(security_info_wanted & SECINFO_DACL)) {
+ psd->type &= ~SEC_DESC_DACL_PRESENT;
+ psd->dacl = NULL;
+ }
+ if (!(security_info_wanted & SECINFO_SACL)) {
+ psd->type &= ~SEC_DESC_SACL_PRESENT;
+ psd->sacl = NULL;
+ }
+
+ /* If the SACL/DACL is NULL, but was requested, we mark that it is
+ * present in the reply to match Windows behavior */
+ if (psd->sacl == NULL &&
+ security_info_wanted & SECINFO_SACL)
+ psd->type |= SEC_DESC_SACL_PRESENT;
+ if (psd->dacl == NULL &&
+ security_info_wanted & SECINFO_DACL)
+ psd->type |= SEC_DESC_DACL_PRESENT;
+
+ if (security_info_wanted & SECINFO_LABEL) {
+ /* Like W2K3 return a null object. */
+ psd->owner_sid = NULL;
+ psd->group_sid = NULL;
+ psd->dacl = NULL;
+ psd->sacl = NULL;
+ psd->type &= ~(SEC_DESC_DACL_PRESENT|SEC_DESC_SACL_PRESENT);
+ }
+
+ *ppsd = psd;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Write a security descriptor into marshalled format.
+****************************************************************************/
+
+static NTSTATUS smbd_marshall_security_desc(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ struct security_descriptor *psd,
+ uint32_t max_data_count,
+ uint8_t **ppmarshalled_sd,
+ size_t *psd_size)
+{
+ *psd_size = ndr_size_security_descriptor(psd, 0);
+
+ DBG_NOTICE("sd_size = %zu.\n", *psd_size);
+
+ if (DEBUGLEVEL >= 10) {
+ DBG_DEBUG("security desc for file %s\n",
+ fsp_str_dbg(fsp));
+ NDR_PRINT_DEBUG(security_descriptor, psd);
+ }
+
+ if (max_data_count < *psd_size) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ return marshall_sec_desc(mem_ctx,
+ psd,
+ ppmarshalled_sd,
+ psd_size);
+}
+
+/****************************************************************************
+ Reply to query a security descriptor.
+ Callable from SMB1 and SMB2.
+ If it returns NT_STATUS_BUFFER_TOO_SMALL, psd_size is initialized with
+ the required size.
+****************************************************************************/
+
+NTSTATUS smbd_do_query_security_desc(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ uint32_t security_info_wanted,
+ uint32_t max_data_count,
+ uint8_t **ppmarshalled_sd,
+ size_t *psd_size)
+{
+ NTSTATUS status;
+ struct security_descriptor *psd = NULL;
+
+ /*
+ * Get the permissions to return.
+ */
+
+ status = smbd_fetch_security_desc(conn,
+ mem_ctx,
+ fsp,
+ security_info_wanted,
+ &psd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_marshall_security_desc(mem_ctx,
+ fsp,
+ psd,
+ max_data_count,
+ ppmarshalled_sd,
+ psd_size);
+ TALLOC_FREE(psd);
+ return status;
+}
+
+#ifdef HAVE_SYS_QUOTAS
+static enum ndr_err_code fill_qtlist_from_sids(TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ SMB_NTQUOTA_HANDLE *qt_handle,
+ struct dom_sid *sids,
+ uint32_t elems)
+{
+ uint32_t i;
+ TALLOC_CTX *list_ctx = NULL;
+
+ list_ctx = talloc_init("quota_sid_list");
+
+ if (list_ctx == NULL) {
+ DBG_ERR("failed to allocate\n");
+ return NDR_ERR_ALLOC;
+ }
+
+ if (qt_handle->quota_list!=NULL) {
+ free_ntquota_list(&(qt_handle->quota_list));
+ }
+ for (i = 0; i < elems; i++) {
+ SMB_NTQUOTA_STRUCT qt;
+ SMB_NTQUOTA_LIST *list_item;
+ bool ok;
+
+ if (!NT_STATUS_IS_OK(vfs_get_ntquota(fsp,
+ SMB_USER_QUOTA_TYPE,
+ &sids[i], &qt))) {
+ /* non fatal error, return empty item in result */
+ ZERO_STRUCT(qt);
+ continue;
+ }
+
+
+ list_item = talloc_zero(list_ctx, SMB_NTQUOTA_LIST);
+ if (list_item == NULL) {
+ DBG_ERR("failed to allocate\n");
+ return NDR_ERR_ALLOC;
+ }
+
+ ok = sid_to_uid(&sids[i], &list_item->uid);
+ if (!ok) {
+ struct dom_sid_buf buf;
+ DBG_WARNING("Could not convert SID %s to uid\n",
+ dom_sid_str_buf(&sids[i], &buf));
+ /* No idea what to return here... */
+ return NDR_ERR_INVALID_POINTER;
+ }
+
+ list_item->quotas = talloc_zero(list_item, SMB_NTQUOTA_STRUCT);
+ if (list_item->quotas == NULL) {
+ DBG_ERR("failed to allocate\n");
+ return NDR_ERR_ALLOC;
+ }
+
+ *list_item->quotas = qt;
+ list_item->mem_ctx = list_ctx;
+ DLIST_ADD(qt_handle->quota_list, list_item);
+ }
+ qt_handle->tmp_list = qt_handle->quota_list;
+ return NDR_ERR_SUCCESS;
+}
+
+static enum ndr_err_code extract_sids_from_buf(TALLOC_CTX *mem_ctx,
+ uint32_t sidlistlength,
+ DATA_BLOB *sid_buf,
+ struct dom_sid **sids,
+ uint32_t *num)
+{
+ DATA_BLOB blob;
+ uint32_t i = 0;
+ enum ndr_err_code err;
+
+ struct sid_list_elem {
+ struct sid_list_elem *prev, *next;
+ struct dom_sid sid;
+ };
+
+ struct sid_list_elem *sid_list = NULL;
+ struct sid_list_elem *iter = NULL;
+ TALLOC_CTX *list_ctx = talloc_init("sid_list");
+ if (!list_ctx) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+
+ *num = 0;
+ *sids = NULL;
+
+ if (sidlistlength) {
+ uint32_t offset = 0;
+ struct ndr_pull *ndr_pull = NULL;
+
+ if (sidlistlength > sid_buf->length) {
+ DBG_ERR("sid_list_length 0x%x exceeds "
+ "available bytes %zx\n",
+ sidlistlength,
+ sid_buf->length);
+ err = NDR_ERR_OFFSET;
+ goto done;
+ }
+ while (true) {
+ struct file_get_quota_info info;
+ struct sid_list_elem *item = NULL;
+ uint32_t new_offset = 0;
+ blob.data = sid_buf->data + offset;
+ blob.length = sidlistlength - offset;
+ ndr_pull = ndr_pull_init_blob(&blob, list_ctx);
+ if (!ndr_pull) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+ err = ndr_pull_file_get_quota_info(ndr_pull,
+ NDR_SCALARS | NDR_BUFFERS, &info);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ DBG_ERR("Failed to pull file_get_quota_info "
+ "from sidlist buffer\n");
+ goto done;
+ }
+ item = talloc_zero(list_ctx, struct sid_list_elem);
+ if (!item) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+ item->sid = info.sid;
+ DLIST_ADD(sid_list, item);
+ i++;
+ if (i == UINT32_MAX) {
+ DBG_ERR("Integer overflow\n");
+ err = NDR_ERR_ARRAY_SIZE;
+ goto done;
+ }
+ new_offset = info.next_entry_offset;
+
+ /* if new_offset == 0 no more sid(s) to read. */
+ if (new_offset == 0) {
+ break;
+ }
+
+ /* Integer wrap? */
+ if ((offset + new_offset) < offset) {
+ DBG_ERR("Integer wrap while adding "
+ "new_offset 0x%x to current "
+ "buffer offset 0x%x\n",
+ new_offset, offset);
+ err = NDR_ERR_OFFSET;
+ goto done;
+ }
+
+ offset += new_offset;
+
+ /* check if new offset is outside buffer boundary. */
+ if (offset >= sidlistlength) {
+ DBG_ERR("bufsize 0x%x exceeded by "
+ "new offset 0x%x)\n",
+ sidlistlength,
+ offset);
+ err = NDR_ERR_OFFSET;
+ goto done;
+ }
+ }
+ *sids = talloc_zero_array(mem_ctx, struct dom_sid, i);
+ if (*sids == NULL) {
+ DBG_ERR("OOM\n");
+ err = NDR_ERR_ALLOC;
+ goto done;
+ }
+
+ *num = i;
+
+ for (iter = sid_list, i = 0; iter; iter = iter->next, i++) {
+ struct dom_sid_buf buf;
+ (*sids)[i] = iter->sid;
+ DBG_DEBUG("quota SID[%u] %s\n",
+ (unsigned int)i,
+ dom_sid_str_buf(&iter->sid, &buf));
+ }
+ }
+ err = NDR_ERR_SUCCESS;
+done:
+ TALLOC_FREE(list_ctx);
+ return err;
+}
+
+NTSTATUS smbd_do_query_getinfo_quota(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ bool restart_scan,
+ bool return_single,
+ uint32_t sid_list_length,
+ DATA_BLOB *sid_buf,
+ uint32_t max_data_count,
+ uint8_t **p_data,
+ uint32_t *p_data_size)
+{
+ NTSTATUS status;
+ SMB_NTQUOTA_HANDLE *qt_handle = NULL;
+ SMB_NTQUOTA_LIST *qt_list = NULL;
+ DATA_BLOB blob = data_blob_null;
+ enum ndr_err_code err;
+
+ qt_handle =
+ (SMB_NTQUOTA_HANDLE *)fsp->fake_file_handle->private_data;
+
+ if (sid_list_length ) {
+ struct dom_sid *sids;
+ uint32_t elems = 0;
+ /*
+ * error check pulled offsets and lengths for wrap and
+ * exceeding available bytes.
+ */
+ if (sid_list_length > sid_buf->length) {
+ DBG_ERR("sid_list_length 0x%x exceeds "
+ "available bytes %zx\n",
+ sid_list_length,
+ sid_buf->length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ err = extract_sids_from_buf(mem_ctx, sid_list_length,
+ sid_buf, &sids, &elems);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err) || elems == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ err = fill_qtlist_from_sids(mem_ctx,
+ fsp,
+ qt_handle,
+ sids,
+ elems);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else if (restart_scan) {
+ if (vfs_get_user_ntquota_list(fsp,
+ &(qt_handle->quota_list))!=0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ } else {
+ if (qt_handle->quota_list!=NULL &&
+ qt_handle->tmp_list==NULL) {
+ free_ntquota_list(&(qt_handle->quota_list));
+ }
+ }
+
+ if (restart_scan !=0 ) {
+ qt_list = qt_handle->quota_list;
+ } else {
+ qt_list = qt_handle->tmp_list;
+ }
+ status = fill_quota_buffer(mem_ctx, qt_list,
+ return_single != 0,
+ max_data_count,
+ &blob,
+ &qt_handle->tmp_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (blob.length > max_data_count) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ *p_data = blob.data;
+ *p_data_size = blob.length;
+ return NT_STATUS_OK;
+}
+#endif /* HAVE_SYS_QUOTAS */
diff --git a/source3/smbd/smb2_oplock.c b/source3/smbd/smb2_oplock.c
new file mode 100644
index 0000000..75d50b3
--- /dev/null
+++ b/source3/smbd/smb2_oplock.c
@@ -0,0 +1,1430 @@
+/*
+ Unix SMB/CIFS implementation.
+ oplock processing
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Jeremy Allison 1998 - 2001
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#define DBGC_CLASS DBGC_LOCKING
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "messages.h"
+#include "locking/leases_db.h"
+#include "../librpc/gen_ndr/ndr_open_files.h"
+
+/*
+ * helper function used by the kernel oplock backends to post the break message
+ */
+void break_kernel_oplock(struct messaging_context *msg_ctx, files_struct *fsp)
+{
+ uint8_t msg[MSG_SMB_KERNEL_BREAK_SIZE];
+
+ /* Put the kernel break info into the message. */
+ push_file_id_24((char *)msg, &fsp->file_id);
+ SIVAL(msg, 24, fh_get_gen_id(fsp->fh));
+
+ /* Don't need to be root here as we're only ever
+ sending to ourselves. */
+
+ messaging_send_buf(msg_ctx, messaging_server_id(msg_ctx),
+ MSG_SMB_KERNEL_BREAK,
+ msg, MSG_SMB_KERNEL_BREAK_SIZE);
+}
+
+/****************************************************************************
+ Attempt to set an oplock on a file. Succeeds if kernel oplocks are
+ disabled (just sets flags).
+****************************************************************************/
+
+NTSTATUS set_file_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ bool use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) &&
+ (koplocks != NULL);
+ struct file_id_buf buf;
+
+ smb_vfs_assert_allowed();
+
+ if (fsp->oplock_type == LEVEL_II_OPLOCK && use_kernel) {
+ DEBUG(10, ("Refusing level2 oplock, kernel oplocks "
+ "don't support them\n"));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if ((fsp->oplock_type != NO_OPLOCK) &&
+ use_kernel &&
+ !koplocks->ops->set_oplock(koplocks, fsp, fsp->oplock_type))
+ {
+ return map_nt_error_from_unix(errno);
+ }
+
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ sconn->oplocks.level_II_open++;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ sconn->oplocks.exclusive_open++;
+ }
+
+ DBG_INFO("granted oplock on file %s, %s/%"PRIu64", "
+ "tv_sec = %x, tv_usec = %x\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &buf),
+ fh_get_gen_id(fsp->fh),
+ (int)fsp->open_time.tv_sec,
+ (int)fsp->open_time.tv_usec);
+
+ return NT_STATUS_OK;
+}
+
+static void release_fsp_kernel_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ bool use_kernel;
+
+ smb_vfs_assert_allowed();
+
+ if (koplocks == NULL) {
+ return;
+ }
+ use_kernel = lp_kernel_oplocks(SNUM(fsp->conn));
+ if (!use_kernel) {
+ return;
+ }
+ if (fsp->oplock_type == NO_OPLOCK) {
+ return;
+ }
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ /*
+ * For leases we don't touch kernel oplocks at all
+ */
+ return;
+ }
+
+ koplocks->ops->release_oplock(koplocks, fsp, NO_OPLOCK);
+}
+
+/****************************************************************************
+ Attempt to release an oplock on a file. Decrements oplock count.
+****************************************************************************/
+
+void release_file_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+
+ release_fsp_kernel_oplock(fsp);
+
+ if (fsp->oplock_type == LEVEL_II_OPLOCK) {
+ sconn->oplocks.level_II_open--;
+ } else if (EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ sconn->oplocks.exclusive_open--;
+ }
+
+ SMB_ASSERT(sconn->oplocks.exclusive_open>=0);
+ SMB_ASSERT(sconn->oplocks.level_II_open>=0);
+
+ fsp->oplock_type = NO_OPLOCK;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+
+ TALLOC_FREE(fsp->oplock_timeout);
+}
+
+/****************************************************************************
+ Attempt to downgrade an oplock on a file. Doesn't decrement oplock count.
+****************************************************************************/
+
+static void downgrade_file_oplock(files_struct *fsp)
+{
+ struct smbd_server_connection *sconn = fsp->conn->sconn;
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ bool use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) &&
+ (koplocks != NULL);
+
+ smb_vfs_assert_allowed();
+
+ if (!EXCLUSIVE_OPLOCK_TYPE(fsp->oplock_type)) {
+ DEBUG(0, ("trying to downgrade an already-downgraded oplock!\n"));
+ return;
+ }
+
+ if (use_kernel) {
+ koplocks->ops->release_oplock(koplocks, fsp, LEVEL_II_OPLOCK);
+ }
+ fsp->oplock_type = LEVEL_II_OPLOCK;
+ sconn->oplocks.exclusive_open--;
+ sconn->oplocks.level_II_open++;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+
+ TALLOC_FREE(fsp->oplock_timeout);
+}
+
+uint32_t get_lease_type(struct share_mode_entry *e, struct file_id id)
+{
+ struct GUID_txt_buf guid_strbuf;
+ struct file_id_buf file_id_strbuf;
+ NTSTATUS status;
+ uint32_t current_state;
+
+ if (e->op_type != LEASE_OPLOCK) {
+ return map_oplock_to_lease_type(e->op_type);
+ }
+
+ status = leases_db_get(&e->client_guid,
+ &e->lease_key,
+ &id,
+ &current_state,
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+ if (NT_STATUS_IS_OK(status)) {
+ return current_state;
+ }
+
+ if (share_entry_stale_pid(e)) {
+ return 0;
+ }
+ DBG_ERR("leases_db_get for client_guid [%s] "
+ "lease_key [%"PRIu64"/%"PRIu64"] "
+ "file_id [%s] failed: %s\n",
+ GUID_buf_string(&e->client_guid, &guid_strbuf),
+ e->lease_key.data[0],
+ e->lease_key.data[1],
+ file_id_str_buf(id, &file_id_strbuf),
+ nt_errstr(status));
+ smb_panic("leases_db_get() failed");
+}
+
+/****************************************************************************
+ Remove a file oplock. Copes with level II and exclusive.
+ Locks then unlocks the share mode lock. Client can decide to go directly
+ to none even if a "break-to-level II" was sent.
+****************************************************************************/
+
+bool remove_oplock(files_struct *fsp)
+{
+ bool ret;
+ struct share_mode_lock *lck;
+
+ DBG_DEBUG("remove_oplock called for %s\n", fsp_str_dbg(fsp));
+
+ /* Remove the oplock flag from the sharemode. */
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ DBG_ERR("failed to lock share entry for "
+ "file %s\n", fsp_str_dbg(fsp));
+ return false;
+ }
+
+ ret = remove_share_oplock(lck, fsp);
+ if (!ret) {
+ struct file_id_buf buf;
+
+ DBG_ERR("failed to remove share oplock for "
+ "file %s, %s, %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &buf));
+ }
+ release_file_oplock(fsp);
+
+ TALLOC_FREE(lck);
+ return ret;
+}
+
+/*
+ * Deal with a reply when a break-to-level II was sent.
+ */
+bool downgrade_oplock(files_struct *fsp)
+{
+ bool ret;
+ struct share_mode_lock *lck;
+
+ DEBUG(10, ("downgrade_oplock called for %s\n",
+ fsp_str_dbg(fsp)));
+
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ DEBUG(0,("downgrade_oplock: failed to lock share entry for "
+ "file %s\n", fsp_str_dbg(fsp)));
+ return False;
+ }
+ ret = downgrade_share_oplock(lck, fsp);
+ if (!ret) {
+ struct file_id_buf idbuf;
+ DBG_ERR("failed to downgrade share oplock "
+ "for file %s, %s, file_id %s\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf));
+ }
+ downgrade_file_oplock(fsp);
+
+ TALLOC_FREE(lck);
+ return ret;
+}
+
+static void lease_timeout_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct fsp_lease *lease =
+ talloc_get_type_abort(private_data,
+ struct fsp_lease);
+ struct files_struct *fsp;
+ struct share_mode_lock *lck;
+ uint16_t old_epoch = lease->lease.lease_epoch;
+
+ fsp = file_find_one_fsp_from_lease_key(lease->sconn,
+ &lease->lease.lease_key);
+ if (fsp == NULL) {
+ /* race? */
+ TALLOC_FREE(lease->timeout);
+ return;
+ }
+
+ /*
+ * Paranoia check: There can only be one fsp_lease per lease
+ * key
+ */
+ SMB_ASSERT(fsp->lease == lease);
+
+ lck = get_existing_share_mode_lock(
+ talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ /* race? */
+ TALLOC_FREE(lease->timeout);
+ return;
+ }
+
+ fsp_lease_update(fsp);
+
+ if (lease->lease.lease_epoch != old_epoch) {
+ /*
+ * If the epoch changed we need to wait for
+ * the next timeout to happen.
+ */
+ DEBUG(10, ("lease break timeout race (epoch) for file %s - ignoring\n",
+ fsp_str_dbg(fsp)));
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ if (!(lease->lease.lease_flags & SMB2_LEASE_FLAG_BREAK_IN_PROGRESS)) {
+ /*
+ * If the epoch changed we need to wait for
+ * the next timeout to happen.
+ */
+ DEBUG(10, ("lease break timeout race (flags) for file %s - ignoring\n",
+ fsp_str_dbg(fsp)));
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ DEBUG(1, ("lease break timed out for file %s -- replying anyway\n",
+ fsp_str_dbg(fsp)));
+ (void)downgrade_lease(lease->sconn->client,
+ 1,
+ &fsp->file_id,
+ &lease->lease.lease_key,
+ SMB2_LEASE_NONE);
+
+ TALLOC_FREE(lck);
+}
+
+bool fsp_lease_update(struct files_struct *fsp)
+{
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ struct fsp_lease *lease = fsp->lease;
+ uint32_t current_state;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ NTSTATUS status;
+
+ status = leases_db_get(client_guid,
+ &lease->lease.lease_key,
+ &fsp->file_id,
+ &current_state,
+ &breaking,
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Could not find lease entry: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(lease->timeout);
+ lease->lease.lease_state = SMB2_LEASE_NONE;
+ lease->lease.lease_epoch += 1;
+ lease->lease.lease_flags = 0;
+ return false;
+ }
+
+ DEBUG(10,("%s: refresh lease state\n", __func__));
+
+ /* Ensure we're in sync with current lease state. */
+ if (lease->lease.lease_epoch != epoch) {
+ DEBUG(10,("%s: cancel outdated timeout\n", __func__));
+ TALLOC_FREE(lease->timeout);
+ }
+ lease->lease.lease_epoch = epoch;
+ lease->lease.lease_state = current_state;
+
+ if (breaking) {
+ lease->lease.lease_flags |= SMB2_LEASE_FLAG_BREAK_IN_PROGRESS;
+
+ if (lease->timeout == NULL) {
+ struct timeval t = timeval_current_ofs(OPLOCK_BREAK_TIMEOUT, 0);
+
+ DEBUG(10,("%s: setup timeout handler\n", __func__));
+
+ lease->timeout = tevent_add_timer(lease->sconn->ev_ctx,
+ lease, t,
+ lease_timeout_handler,
+ lease);
+ if (lease->timeout == NULL) {
+ DEBUG(0, ("%s: Could not add lease timeout handler\n",
+ __func__));
+ }
+ }
+ } else {
+ lease->lease.lease_flags &= ~SMB2_LEASE_FLAG_BREAK_IN_PROGRESS;
+ TALLOC_FREE(lease->timeout);
+ }
+
+ return true;
+}
+
+struct downgrade_lease_additional_state {
+ struct tevent_immediate *im;
+ struct smbXsrv_client *client;
+ uint32_t break_flags;
+ struct smb2_lease_key lease_key;
+ uint32_t break_from;
+ uint32_t break_to;
+ uint16_t new_epoch;
+};
+
+static void downgrade_lease_additional_trigger(struct tevent_context *ev,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct downgrade_lease_additional_state *state =
+ talloc_get_type_abort(private_data,
+ struct downgrade_lease_additional_state);
+ NTSTATUS status;
+
+ status = smbd_smb2_send_lease_break(state->client,
+ state->new_epoch,
+ state->break_flags,
+ &state->lease_key,
+ state->break_from,
+ state->break_to);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(state->client,
+ nt_errstr(status));
+ }
+ TALLOC_FREE(state);
+}
+
+struct fsps_lease_update_state {
+ const struct file_id *id;
+ const struct smb2_lease_key *key;
+};
+
+static struct files_struct *fsps_lease_update_fn(
+ struct files_struct *fsp, void *private_data)
+{
+ struct fsps_lease_update_state *state =
+ (struct fsps_lease_update_state *)private_data;
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ return NULL;
+ }
+ if (!smb2_lease_key_equal(&fsp->lease->lease.lease_key, state->key)) {
+ return NULL;
+ }
+ if (!file_id_equal(&fsp->file_id, state->id)) {
+ return NULL;
+ }
+
+ fsp_lease_update(fsp);
+
+ return NULL;
+}
+
+static void fsps_lease_update(struct smbd_server_connection *sconn,
+ const struct file_id *id,
+ const struct smb2_lease_key *key)
+{
+ struct fsps_lease_update_state state = { .id = id, .key = key };
+ files_forall(sconn, fsps_lease_update_fn, &state);
+}
+
+NTSTATUS downgrade_lease(struct smbXsrv_client *client,
+ uint32_t num_file_ids,
+ const struct file_id *ids,
+ const struct smb2_lease_key *key,
+ uint32_t lease_state)
+{
+ struct smbd_server_connection *sconn = client->sconn;
+ const struct GUID *client_guid = NULL;
+ struct share_mode_lock *lck;
+ const struct file_id id = ids[0];
+ uint32_t current_state, breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ NTSTATUS status;
+ uint32_t i;
+ struct file_id_buf idbuf;
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32"\n",
+ file_id_str_buf(id, &idbuf),
+ lease_state);
+
+ lck = get_existing_share_mode_lock(talloc_tos(), id);
+ if (lck == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ client_guid = &sconn->client->global->client_guid;
+
+ status = leases_db_get(client_guid,
+ key,
+ &id,
+ &current_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("leases_db_get returned %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(lck);
+ return status;
+ }
+
+ if (!breaking) {
+ DBG_WARNING("Attempt to break from %"PRIu32" to %"PRIu32" - "
+ "but we're not in breaking state\n",
+ current_state, lease_state);
+ TALLOC_FREE(lck);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /*
+ * Can't upgrade anything: breaking_to_requested (and current_state)
+ * must be a strict bitwise superset of new_lease_state
+ */
+ if ((lease_state & breaking_to_requested) != lease_state) {
+ DBG_WARNING("Attempt to upgrade from %"PRIu32" to %"PRIu32" "
+ "- expected %"PRIu32"\n",
+ current_state, lease_state,
+ breaking_to_requested);
+ TALLOC_FREE(lck);
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ if (current_state != lease_state) {
+ current_state = lease_state;
+ }
+
+ status = NT_STATUS_OK;
+
+ if ((lease_state & ~breaking_to_required) != 0) {
+ struct downgrade_lease_additional_state *state;
+
+ DBG_INFO("lease state %"PRIu32" not fully broken from "
+ "%"PRIu32" to %"PRIu32"\n",
+ lease_state,
+ current_state,
+ breaking_to_required);
+
+ breaking_to_requested = breaking_to_required;
+
+ if (current_state & (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+ /*
+ * Here we break in steps, as windows does
+ * see the breaking3 and v2_breaking3 tests.
+ */
+ breaking_to_requested |= SMB2_LEASE_READ;
+ }
+
+ state = talloc_zero(client,
+ struct downgrade_lease_additional_state);
+ if (state == NULL) {
+ TALLOC_FREE(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->im = tevent_create_immediate(state);
+ if (state->im == NULL) {
+ TALLOC_FREE(state);
+ TALLOC_FREE(lck);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->client = client;
+ state->lease_key = *key;
+ state->break_from = current_state;
+ state->break_to = breaking_to_requested;
+ if (lease_version > 1) {
+ state->new_epoch = epoch;
+ }
+
+ if (current_state & (SMB2_LEASE_WRITE|SMB2_LEASE_HANDLE)) {
+ state->break_flags =
+ SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED;
+ } else {
+ /*
+ * This is an async break without
+ * SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED
+ *
+ * we need to store NONE state in the
+ * database.
+ */
+ current_state = 0;
+ breaking_to_requested = 0;
+ breaking_to_required = 0;
+ breaking = false;
+
+ {
+ NTSTATUS set_status;
+
+ set_status = leases_db_set(
+ &sconn->client->global->client_guid,
+ key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ return set_status;
+ }
+ }
+ }
+
+ tevent_schedule_immediate(state->im,
+ client->raw_ev_ctx,
+ downgrade_lease_additional_trigger,
+ state);
+
+ status = NT_STATUS_OPLOCK_BREAK_IN_PROGRESS;
+ } else {
+ DBG_DEBUG("breaking from %"PRIu32" to %"PRIu32" - "
+ "expected %"PRIu32"\n",
+ current_state,
+ lease_state,
+ breaking_to_requested);
+
+ breaking_to_requested = 0;
+ breaking_to_required = 0;
+ breaking = false;
+ }
+
+ {
+ NTSTATUS set_status;
+
+ set_status = leases_db_set(
+ client_guid,
+ key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ TALLOC_FREE(lck);
+ return set_status;
+ }
+ }
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32" => %s\n",
+ file_id_str_buf(id, &idbuf),
+ lease_state,
+ nt_errstr(status));
+
+ share_mode_wakeup_waiters(id);
+
+ fsps_lease_update(sconn, &id, key);
+
+ TALLOC_FREE(lck);
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32" => %s\n",
+ file_id_str_buf(id, &idbuf),
+ lease_state,
+ nt_errstr(status));
+
+ /*
+ * Dynamic share case. Ensure other opens are copies.
+ * This will only be breaking to NONE.
+ */
+
+ for (i = 1; i < num_file_ids; i++) {
+ lck = get_existing_share_mode_lock(talloc_tos(), ids[i]);
+ if (lck == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ fsps_lease_update(sconn, &ids[i], key);
+
+ DBG_DEBUG("Downgrading %s to %"PRIu32" => %s\n",
+ file_id_str_buf(ids[i], &idbuf),
+ lease_state,
+ nt_errstr(status));
+
+ TALLOC_FREE(lck);
+ }
+
+ return status;
+}
+
+#define SMB1_BREAK_MESSAGE_LENGTH (smb_size + 8*2)
+
+/****************************************************************************
+ Function to do the waiting before sending a local break.
+****************************************************************************/
+
+static void wait_before_sending_break(void)
+{
+ long wait_time = (long)lp_oplock_break_wait_time();
+
+ if (wait_time) {
+ smb_msleep(wait_time);
+ }
+}
+
+/****************************************************************************
+ Ensure that we have a valid oplock.
+****************************************************************************/
+
+static files_struct *initial_break_processing(
+ struct smbd_server_connection *sconn, struct file_id id,
+ unsigned long file_id)
+{
+ files_struct *fsp = NULL;
+ struct file_id_buf idbuf;
+
+ DBG_NOTICE("called for %s/%u\n"
+ "Current oplocks_open (exclusive = %d, levelII = %d)\n",
+ file_id_str_buf(id, &idbuf),
+ (int)file_id,
+ sconn->oplocks.exclusive_open,
+ sconn->oplocks.level_II_open);
+
+ /*
+ * We need to search the file open table for the
+ * entry containing this dev and inode, and ensure
+ * we have an oplock on it.
+ */
+
+ fsp = file_find_dif(sconn, id, file_id);
+
+ if(fsp == NULL) {
+ /* The file could have been closed in the meantime - return success. */
+ DBG_NOTICE("cannot find open file "
+ "with file_id %s gen_id = %lu, allowing break to "
+ "succeed.\n",
+ file_id_str_buf(id, &idbuf),
+ file_id);
+ return NULL;
+ }
+
+ /* Ensure we have an oplock on the file */
+
+ /*
+ * There is a potential race condition in that an oplock could
+ * have been broken due to another udp request, and yet there are
+ * still oplock break messages being sent in the udp message
+ * queue for this file. So return true if we don't have an oplock,
+ * as we may have just freed it.
+ */
+
+ if(fsp->oplock_type == NO_OPLOCK) {
+ DBG_NOTICE("file %s (file_id = %s gen_id = %"PRIu64") "
+ "has no oplock. "
+ "Allowing break to succeed regardless.\n",
+ fsp_str_dbg(fsp),
+ file_id_str_buf(id, &idbuf),
+ fh_get_gen_id(fsp->fh));
+ return NULL;
+ }
+
+ return fsp;
+}
+
+static void oplock_timeout_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ files_struct *fsp = (files_struct *)private_data;
+
+ SMB_ASSERT(fsp->sent_oplock_break != NO_BREAK_SENT);
+
+ /* Remove the timed event handler. */
+ TALLOC_FREE(fsp->oplock_timeout);
+ DEBUG(0, ("Oplock break failed for file %s -- replying anyway\n",
+ fsp_str_dbg(fsp)));
+ remove_oplock(fsp);
+}
+
+/*******************************************************************
+ Add a timeout handler waiting for the client reply.
+*******************************************************************/
+
+static void add_oplock_timeout_handler(files_struct *fsp)
+{
+ if (fsp->oplock_timeout != NULL) {
+ DEBUG(0, ("Logic problem -- have an oplock event hanging "
+ "around\n"));
+ }
+
+ fsp->oplock_timeout =
+ tevent_add_timer(fsp->conn->sconn->ev_ctx, fsp,
+ timeval_current_ofs(OPLOCK_BREAK_TIMEOUT, 0),
+ oplock_timeout_handler, fsp);
+
+ if (fsp->oplock_timeout == NULL) {
+ DEBUG(0, ("Could not add oplock timeout handler\n"));
+ }
+}
+
+/*******************************************************************
+ This handles the generic oplock break message from another smbd.
+*******************************************************************/
+
+static void process_oplock_break_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct oplock_break_message *msg = NULL;
+ enum ndr_err_code ndr_err;
+ files_struct *fsp;
+ bool use_kernel;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+ struct server_id self = messaging_server_id(sconn->msg_ctx);
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+ uint16_t break_from;
+ uint16_t break_to;
+ bool break_needed = true;
+
+ smb_vfs_assert_allowed();
+
+ msg = talloc(talloc_tos(), struct oplock_break_message);
+ if (msg == NULL) {
+ DBG_WARNING("talloc failed\n");
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ data,
+ msg,
+ msg,
+ (ndr_pull_flags_fn_t)ndr_pull_oplock_break_message);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_DEBUG("ndr_pull_oplock_break_message failed: %s\n",
+ ndr_errstr(ndr_err));
+ TALLOC_FREE(msg);
+ return;
+ }
+ if (DEBUGLEVEL >= 10) {
+ struct server_id_buf buf;
+ DBG_DEBUG("Got break message from %s\n",
+ server_id_str_buf(src, &buf));
+ NDR_PRINT_DEBUG(oplock_break_message, msg);
+ }
+
+ break_to = msg->break_to;
+ fsp = initial_break_processing(sconn, msg->id, msg->share_file_id);
+
+ TALLOC_FREE(msg);
+
+ if (fsp == NULL) {
+ /* We hit a race here. Break messages are sent, and before we
+ * get to process this message, we have closed the file. */
+ DEBUG(3, ("Did not find fsp\n"));
+ return;
+ }
+
+ break_from = fsp_lease_type(fsp);
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ if (fsp->sent_oplock_break != NO_BREAK_SENT) {
+ /*
+ * Nothing to do anymore
+ */
+ DEBUG(10, ("fsp->sent_oplock_break = %d\n",
+ fsp->sent_oplock_break));
+ return;
+ }
+ }
+
+ if (!(global_client_caps & CAP_LEVEL_II_OPLOCKS)) {
+ DEBUG(10, ("client_caps without level2 oplocks\n"));
+ break_to &= ~SMB2_LEASE_READ;
+ }
+
+ use_kernel = lp_kernel_oplocks(SNUM(fsp->conn)) &&
+ (koplocks != NULL);
+ if (use_kernel) {
+ DEBUG(10, ("Kernel oplocks don't allow level2\n"));
+ break_to &= ~SMB2_LEASE_READ;
+ }
+
+ if (!lp_level2_oplocks(SNUM(fsp->conn))) {
+ DEBUG(10, ("no level2 oplocks by config\n"));
+ break_to &= ~SMB2_LEASE_READ;
+ }
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ const struct GUID *client_guid = fsp_client_guid(fsp);
+ struct share_mode_lock *lck;
+ uint32_t current_state;
+ uint32_t breaking_to_requested, breaking_to_required;
+ bool breaking;
+ uint16_t lease_version, epoch;
+ NTSTATUS status;
+
+ lck = get_existing_share_mode_lock(
+ talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ /*
+ * We hit a race here. Break messages are sent, and
+ * before we get to process this message, we have closed
+ * the file.
+ */
+ DEBUG(3, ("Did not find share_mode\n"));
+ return;
+ }
+
+ status = leases_db_get(client_guid,
+ &fsp->lease->lease.lease_key,
+ &fsp->file_id,
+ &current_state,
+ &breaking,
+ &breaking_to_requested,
+ &breaking_to_required,
+ &lease_version,
+ &epoch);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("leases_db_get returned %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ break_from = current_state;
+ break_to &= current_state;
+
+ if (breaking) {
+ break_to &= breaking_to_required;
+ if (breaking_to_required != break_to) {
+ /*
+ * Note we don't increment the epoch
+ * here, which might be a bug in
+ * Windows too...
+ */
+ breaking_to_required = break_to;
+ }
+ break_needed = false;
+ } else if (current_state == break_to) {
+ break_needed = false;
+ } else if (current_state == SMB2_LEASE_READ) {
+ current_state = SMB2_LEASE_NONE;
+ /* Need to increment the epoch */
+ epoch += 1;
+ } else {
+ breaking = true;
+ breaking_to_required = break_to;
+ breaking_to_requested = break_to;
+ /* Need to increment the epoch */
+ epoch += 1;
+ }
+
+ {
+ NTSTATUS set_status;
+
+ set_status = leases_db_set(
+ client_guid,
+ &fsp->lease->lease.lease_key,
+ current_state,
+ breaking,
+ breaking_to_requested,
+ breaking_to_required,
+ lease_version,
+ epoch);
+
+ if (!NT_STATUS_IS_OK(set_status)) {
+ DBG_DEBUG("leases_db_set failed: %s\n",
+ nt_errstr(set_status));
+ return;
+ }
+ }
+
+ /* Ensure we're in sync with current lease state. */
+ fsp_lease_update(fsp);
+
+ TALLOC_FREE(lck);
+ }
+
+ if (!break_needed) {
+ DEBUG(10,("%s: skip break\n", __func__));
+ return;
+ }
+
+ if (break_from == SMB2_LEASE_NONE) {
+ struct file_id_buf idbuf;
+ DBG_NOTICE("Already downgraded oplock to none on %s: %s\n",
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fsp_str_dbg(fsp));
+ return;
+ }
+
+ DEBUG(10, ("break_from=%u, break_to=%u\n",
+ (unsigned)break_from, (unsigned)break_to));
+
+ if (break_from == break_to) {
+ struct file_id_buf idbuf;
+ DBG_NOTICE("Already downgraded oplock to %u on %s: %s\n",
+ (unsigned)break_to,
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fsp_str_dbg(fsp));
+ return;
+ }
+
+ /* Need to wait before sending a break
+ message if we sent ourselves this message. */
+ if (server_id_equal(&self, &src)) {
+ wait_before_sending_break();
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+#endif
+ send_break_message_smb2(fsp, break_from, break_to);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ send_break_message_smb1(fsp, (break_to & SMB2_LEASE_READ) ?
+ OPLOCKLEVEL_II : OPLOCKLEVEL_NONE);
+ }
+#endif
+
+ if ((break_from == SMB2_LEASE_READ) &&
+ (break_to == SMB2_LEASE_NONE)) {
+ /*
+ * This is an async break without a reply and thus no timeout
+ *
+ * leases are handled above.
+ */
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ remove_oplock(fsp);
+ }
+ return;
+ }
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ return;
+ }
+
+ fsp->sent_oplock_break = (break_to & SMB2_LEASE_READ) ?
+ LEVEL_II_BREAK_SENT:BREAK_TO_NONE_SENT;
+
+ add_oplock_timeout_handler(fsp);
+}
+
+/*******************************************************************
+ This handles the kernel oplock break message.
+*******************************************************************/
+
+static void process_kernel_oplock_break(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id src,
+ DATA_BLOB *data)
+{
+ struct file_id id;
+ struct file_id_buf idbuf;
+ unsigned long file_id;
+ files_struct *fsp;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+ struct server_id_buf tmp;
+
+ if (data->data == NULL) {
+ DEBUG(0, ("Got NULL buffer\n"));
+ return;
+ }
+
+ if (data->length != MSG_SMB_KERNEL_BREAK_SIZE) {
+ DEBUG(0, ("Got invalid msg len %d\n", (int)data->length));
+ return;
+ }
+
+ /* Pull the data from the message. */
+ pull_file_id_24((char *)data->data, &id);
+ file_id = (unsigned long)IVAL(data->data, 24);
+
+ DBG_DEBUG("Got kernel oplock break message from pid %s: %s/%u\n",
+ server_id_str_buf(src, &tmp),
+ file_id_str_buf(id, &idbuf),
+ (unsigned int)file_id);
+
+ fsp = initial_break_processing(sconn, id, file_id);
+
+ if (fsp == NULL) {
+ DEBUG(3, ("Got a kernel oplock break message for a file "
+ "I don't know about\n"));
+ return;
+ }
+
+ if (fsp->sent_oplock_break != NO_BREAK_SENT) {
+ /* This is ok, kernel oplocks come in completely async */
+ DEBUG(3, ("Got a kernel oplock request while waiting for a "
+ "break reply\n"));
+ return;
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+#endif
+ send_break_message_smb2(fsp, 0, OPLOCKLEVEL_NONE);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ send_break_message_smb1(fsp, OPLOCKLEVEL_NONE);
+ }
+#endif
+
+ fsp->sent_oplock_break = BREAK_TO_NONE_SENT;
+
+ add_oplock_timeout_handler(fsp);
+}
+
+static void send_break_to_none(struct messaging_context *msg_ctx,
+ const struct file_id *id,
+ const struct share_mode_entry *e)
+{
+ NTSTATUS status;
+ status = send_break_message(msg_ctx, id, e, OPLOCK_NONE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("send_break_message failed: %s\n",
+ nt_errstr(status));
+ }
+}
+struct break_to_none_state {
+ struct smbd_server_connection *sconn;
+ struct file_id id;
+ struct smb2_lease_key lease_key;
+ struct GUID client_guid;
+ size_t num_read_leases;
+ uint32_t total_lease_types;
+};
+
+static bool do_break_lease_to_none(struct share_mode_entry *e,
+ void *private_data)
+{
+ struct break_to_none_state *state = private_data;
+ uint32_t current_state = 0;
+ bool our_own;
+ NTSTATUS status;
+
+ DBG_DEBUG("lease_key=%"PRIu64"/%"PRIu64"\n",
+ e->lease_key.data[0],
+ e->lease_key.data[1]);
+
+ status = leases_db_get(&e->client_guid,
+ &e->lease_key,
+ &state->id,
+ &current_state,
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("leases_db_get failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ state->total_lease_types |= current_state;
+
+ if ((current_state & SMB2_LEASE_READ) == 0) {
+ return false;
+ }
+
+ state->num_read_leases += 1;
+
+ our_own = smb2_lease_equal(&state->client_guid,
+ &state->lease_key,
+ &e->client_guid,
+ &e->lease_key);
+ if (our_own) {
+ DEBUG(10, ("Don't break our own lease\n"));
+ return false;
+ }
+
+ DBG_DEBUG("Breaking %"PRIu64"/%"PRIu64" to none\n",
+ e->lease_key.data[0],
+ e->lease_key.data[1]);
+
+ send_break_to_none(state->sconn->msg_ctx, &state->id, e);
+
+ return false;
+}
+
+static bool do_break_oplock_to_none(struct share_mode_entry *e,
+ bool *modified,
+ void *private_data)
+{
+ struct break_to_none_state *state = private_data;
+
+ if (e->op_type == LEASE_OPLOCK) {
+ /*
+ * Already being taken care of
+ */
+ return false;
+ }
+
+ /*
+ * As there could have been multiple writes waiting at the
+ * lock_share_entry gate we may not be the first to
+ * enter. Hence the state of the op_types in the share mode
+ * entries may be partly NO_OPLOCK and partly LEVEL_II
+ * oplock. It will do no harm to re-send break messages to
+ * those smbd's that are still waiting their turn to remove
+ * their LEVEL_II state, and also no harm to ignore existing
+ * NO_OPLOCK states. JRA.
+ */
+
+ DBG_DEBUG("e->op_type == %d\n", e->op_type);
+
+ state->total_lease_types |= map_oplock_to_lease_type(e->op_type);
+
+ if (e->op_type == NO_OPLOCK) {
+ return false;
+ }
+
+ state->num_read_leases += 1;
+
+ /* Paranoia .... */
+ SMB_ASSERT(!EXCLUSIVE_OPLOCK_TYPE(e->op_type));
+
+ send_break_to_none(state->sconn->msg_ctx, &state->id, e);
+
+ return false;
+}
+
+/****************************************************************************
+ This function is called on any file modification or lock request. If a file
+ is level 2 oplocked then it must tell all other level 2 holders to break to
+ none.
+****************************************************************************/
+
+static void contend_level2_oplocks_begin_default(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ struct break_to_none_state state = {
+ .sconn = fsp->conn->sconn, .id = fsp->file_id,
+ };
+ struct share_mode_lock *lck = NULL;
+ uint32_t fsp_lease = fsp_lease_type(fsp);
+ bool ok, has_read_lease;
+
+ /*
+ * If this file is level II oplocked then we need
+ * to grab the shared memory lock and inform all
+ * other files with a level II lock that they need
+ * to flush their read caches. We keep the lock over
+ * the shared memory area whilst doing this.
+ */
+
+ if (fsp_lease & SMB2_LEASE_WRITE) {
+ /*
+ * There can't be any level2 oplocks, we're alone.
+ */
+ return;
+ }
+
+ has_read_lease = file_has_read_lease(fsp);
+ if (!has_read_lease) {
+ DEBUG(10, ("No read oplocks around\n"));
+ return;
+ }
+
+ if (fsp->oplock_type == LEASE_OPLOCK) {
+ state.client_guid = *fsp_client_guid(fsp);
+ state.lease_key = fsp->lease->lease.lease_key;
+ DEBUG(10, ("Breaking through lease key %"PRIu64"/%"PRIu64"\n",
+ state.lease_key.data[0],
+ state.lease_key.data[1]));
+ }
+
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+ if (lck == NULL) {
+ struct file_id_buf idbuf;
+ DBG_WARNING("failed to lock share mode entry for file %s.\n",
+ file_id_str_buf(state.id, &idbuf));
+ return;
+ }
+
+ /*
+ * Walk leases and oplocks separately: We have to send one break per
+ * lease. If we have multiple share_mode_entry having a common lease,
+ * we would break the lease twice if we don't walk the leases list
+ * separately.
+ */
+
+ ok = share_mode_forall_leases(lck, do_break_lease_to_none, &state);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_leases failed\n");
+ }
+
+ ok = share_mode_forall_entries(lck, do_break_oplock_to_none, &state);
+ if (!ok) {
+ DBG_WARNING("share_mode_forall_entries failed\n");
+ }
+
+ {
+ /*
+ * Lazy update here. It might be that all leases
+ * have gone in the meantime.
+ */
+ uint32_t acc, sh, ls;
+ share_mode_flags_get(lck, &acc, &sh, &ls);
+ ls = state.total_lease_types;
+ share_mode_flags_set(lck, acc, sh, ls, NULL);
+ }
+
+ TALLOC_FREE(lck);
+}
+
+void smbd_contend_level2_oplocks_begin(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ contend_level2_oplocks_begin_default(fsp, type);
+}
+
+void smbd_contend_level2_oplocks_end(files_struct *fsp,
+ enum level2_contention_type type)
+{
+ return;
+}
+
+/****************************************************************************
+ Linearize a share mode entry struct to an internal oplock break message.
+****************************************************************************/
+
+void share_mode_entry_to_message(char *msg, const struct file_id *id,
+ const struct share_mode_entry *e)
+{
+ SIVAL(msg,OP_BREAK_MSG_PID_OFFSET,(uint32_t)e->pid.pid);
+ SBVAL(msg,OP_BREAK_MSG_MID_OFFSET,e->op_mid);
+ SSVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET,e->op_type);
+ SIVAL(msg,OP_BREAK_MSG_ACCESS_MASK_OFFSET,e->access_mask);
+ SIVAL(msg,OP_BREAK_MSG_SHARE_ACCESS_OFFSET,e->share_access);
+ SIVAL(msg,OP_BREAK_MSG_PRIV_OFFSET,e->private_options);
+ SIVAL(msg,OP_BREAK_MSG_TIME_SEC_OFFSET,(uint32_t)e->time.tv_sec);
+ SIVAL(msg,OP_BREAK_MSG_TIME_USEC_OFFSET,(uint32_t)e->time.tv_usec);
+ /*
+ * "id" used to be part of share_mode_entry, thus the strange
+ * place to put this. Feel free to move somewhere else :-)
+ */
+ push_file_id_24(msg+OP_BREAK_MSG_DEV_OFFSET, id);
+ SIVAL(msg,OP_BREAK_MSG_FILE_ID_OFFSET,e->share_file_id);
+ SIVAL(msg,OP_BREAK_MSG_UID_OFFSET,e->uid);
+ SSVAL(msg,OP_BREAK_MSG_FLAGS_OFFSET,e->flags);
+ SIVAL(msg,OP_BREAK_MSG_NAME_HASH_OFFSET,e->name_hash);
+ SIVAL(msg,OP_BREAK_MSG_VNN_OFFSET,e->pid.vnn);
+}
+
+/****************************************************************************
+ De-linearize an internal oplock break message to a share mode entry struct.
+****************************************************************************/
+
+void message_to_share_mode_entry(struct file_id *id,
+ struct share_mode_entry *e,
+ const char *msg)
+{
+ e->pid = (struct server_id){
+ .pid = (pid_t)IVAL(msg, OP_BREAK_MSG_PID_OFFSET),
+ .vnn = IVAL(msg, OP_BREAK_MSG_VNN_OFFSET),
+ };
+ e->op_mid = BVAL(msg,OP_BREAK_MSG_MID_OFFSET);
+ e->op_type = SVAL(msg,OP_BREAK_MSG_OP_TYPE_OFFSET);
+ e->access_mask = IVAL(msg,OP_BREAK_MSG_ACCESS_MASK_OFFSET);
+ e->share_access = IVAL(msg,OP_BREAK_MSG_SHARE_ACCESS_OFFSET);
+ e->private_options = IVAL(msg,OP_BREAK_MSG_PRIV_OFFSET);
+ e->time.tv_sec = (time_t)IVAL(msg,OP_BREAK_MSG_TIME_SEC_OFFSET);
+ e->time.tv_usec = (int)IVAL(msg,OP_BREAK_MSG_TIME_USEC_OFFSET);
+ /*
+ * "id" used to be part of share_mode_entry, thus the strange
+ * place to put this. Feel free to move somewhere else :-)
+ */
+ pull_file_id_24(msg+OP_BREAK_MSG_DEV_OFFSET, id);
+ e->share_file_id = (unsigned long)IVAL(msg,OP_BREAK_MSG_FILE_ID_OFFSET);
+ e->uid = (uint32_t)IVAL(msg,OP_BREAK_MSG_UID_OFFSET);
+ e->flags = (uint16_t)SVAL(msg,OP_BREAK_MSG_FLAGS_OFFSET);
+ e->name_hash = IVAL(msg, OP_BREAK_MSG_NAME_HASH_OFFSET);
+}
+
+/****************************************************************************
+ Setup oplocks for this process.
+****************************************************************************/
+
+bool init_oplocks(struct smbd_server_connection *sconn)
+{
+ DEBUG(3,("init_oplocks: initializing messages.\n"));
+
+ messaging_register(sconn->msg_ctx, sconn, MSG_SMB_BREAK_REQUEST,
+ process_oplock_break_message);
+ messaging_register(sconn->msg_ctx, sconn, MSG_SMB_KERNEL_BREAK,
+ process_kernel_oplock_break);
+ return true;
+}
+
+void init_kernel_oplocks(struct smbd_server_connection *sconn)
+{
+ struct kernel_oplocks *koplocks = sconn->oplocks.kernel_ops;
+
+ /* only initialize once */
+ if (koplocks == NULL) {
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+ koplocks = linux_init_kernel_oplocks(sconn);
+#endif
+ sconn->oplocks.kernel_ops = koplocks;
+ }
+}
diff --git a/source3/smbd/smb2_pipes.c b/source3/smbd/smb2_pipes.c
new file mode 100644
index 0000000..8f87867
--- /dev/null
+++ b/source3/smbd/smb2_pipes.c
@@ -0,0 +1,150 @@
+/*
+ Unix SMB/CIFS implementation.
+ Pipe SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Luke Kenneth Casson Leighton 1996-1998
+ Copyright (C) Paul Ashton 1997-1998.
+ Copyright (C) Jeremy Allison 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ This file handles reply_ calls on named pipes that the server
+ makes to handle specific protocols
+*/
+
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "libcli/security/security.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "auth/auth_util.h"
+#include "librpc/rpc/dcerpc_helper.h"
+
+NTSTATUS open_np_file(struct smb_request *smb_req, const char *name,
+ struct files_struct **pfsp)
+{
+ struct smbXsrv_connection *xconn = smb_req->xconn;
+ struct connection_struct *conn = smb_req->conn;
+ struct files_struct *fsp;
+ struct smb_filename *smb_fname = NULL;
+ struct auth_session_info *session_info = conn->session_info;
+ NTSTATUS status;
+
+ status = file_new(smb_req, conn, &fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("file_new failed: %s\n", nt_errstr(status)));
+ return status;
+ }
+
+ fsp->conn = conn;
+ fsp_set_fd(fsp, -1);
+ fsp->vuid = smb_req->vuid;
+ fsp->fsp_flags.can_lock = false;
+ fsp->access_mask = FILE_READ_DATA | FILE_WRITE_DATA;
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ name,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ file_free(smb_req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = fsp_set_smb_fname(fsp, smb_fname);
+ TALLOC_FREE(smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_free(smb_req, fsp);
+ return status;
+ }
+
+ if (smb_req->smb2req != NULL && smb_req->smb2req->was_encrypted) {
+ struct security_token *security_token = NULL;
+ uint16_t dialect = xconn->smb2.server.dialect;
+ uint16_t srv_smb_encrypt = DCERPC_SMB_ENCRYPTION_REQUIRED;
+ uint16_t cipher = xconn->smb2.server.cipher;
+ struct dom_sid smb3_sid = global_sid_Samba_SMB3;
+ size_t num_smb3_sids;
+ bool ok;
+
+ session_info = copy_session_info(fsp, conn->session_info);
+ if (session_info == NULL) {
+ DBG_ERR("Failed to copy session info\n");
+ file_free(smb_req, fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ security_token = session_info->security_token;
+
+ /*
+ * Security check:
+ *
+ * Make sure we don't have a SMB3 SID in the security token!
+ */
+ num_smb3_sids = security_token_count_flag_sids(security_token,
+ &smb3_sid,
+ 3,
+ NULL);
+ if (num_smb3_sids != 0) {
+ DBG_ERR("ERROR: %zu SMB3 SIDs have already been "
+ "detected in the security token!\n",
+ num_smb3_sids);
+ file_free(smb_req, fsp);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ ok = sid_append_rid(&smb3_sid, dialect);
+ ok &= sid_append_rid(&smb3_sid, srv_smb_encrypt);
+ ok &= sid_append_rid(&smb3_sid, cipher);
+
+ if (!ok) {
+ DBG_ERR("sid too small\n");
+ file_free(smb_req, fsp);
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ status = add_sid_to_array_unique(security_token,
+ &smb3_sid,
+ &security_token->sids,
+ &security_token->num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to add SMB3 SID to security token\n");
+ file_free(smb_req, fsp);
+ return status;
+ }
+
+ fsp->fsp_flags.encryption_required = true;
+ }
+
+ status = np_open(fsp, name,
+ conn->sconn->remote_address,
+ conn->sconn->local_address,
+ session_info,
+ conn->sconn->ev_ctx,
+ conn->sconn->msg_ctx,
+ conn->sconn->dce_ctx,
+ &fsp->fake_file_handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("np_open(%s) returned %s\n", name,
+ nt_errstr(status)));
+ file_free(smb_req, fsp);
+ return status;
+ }
+
+ *pfsp = fsp;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_posix.c b/source3/smbd/smb2_posix.c
new file mode 100644
index 0000000..9623e59
--- /dev/null
+++ b/source3/smbd/smb2_posix.c
@@ -0,0 +1,73 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB2 POSIX code.
+ Copyright (C) Jeremy Allison 2022
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "passdb/lookup_sid.h"
+#include "librpc/gen_ndr/ndr_security.h"
+#include "librpc/gen_ndr/smb3posix.h"
+#include "libcli/security/security.h"
+
+void smb3_file_posix_information_init(
+ connection_struct *conn,
+ const struct stat_ex *st,
+ uint32_t reparse_tag,
+ uint32_t dos_attributes,
+ struct smb3_file_posix_information *dst)
+{
+ *dst = (struct smb3_file_posix_information) {
+ .end_of_file = get_file_size_stat(st),
+ .allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn,NULL,st),
+ .inode = SMB_VFS_FS_FILE_ID(conn, st),
+ .device = st->st_ex_dev,
+ .creation_time = unix_timespec_to_nt_time(st->st_ex_btime),
+ .last_access_time = unix_timespec_to_nt_time(st->st_ex_atime),
+ .last_write_time = unix_timespec_to_nt_time(st->st_ex_mtime),
+ .change_time = unix_timespec_to_nt_time(st->st_ex_ctime),
+ .cc.nlinks = st->st_ex_nlink,
+ .cc.reparse_tag = reparse_tag,
+ .cc.posix_perms = unix_perms_to_wire(st->st_ex_mode & ~S_IFMT),
+ .cc.owner = global_sid_NULL,
+ .cc.group = global_sid_NULL,
+ };
+
+ if (st->st_ex_uid != (uid_t)-1) {
+ uid_to_sid(&dst->cc.owner, st->st_ex_uid);
+ }
+ if (st->st_ex_gid != (uid_t)-1) {
+ gid_to_sid(&dst->cc.group, st->st_ex_gid);
+ }
+
+ switch (st->st_ex_mode & S_IFMT) {
+ case S_IFREG:
+ dst->file_attributes = dos_attributes;
+ break;
+ case S_IFDIR:
+ dst->file_attributes = dos_attributes | FILE_ATTRIBUTE_DIRECTORY;
+ break;
+ default:
+ /*
+ * All non-directory or regular files are reported
+ * as reparse points. Client may or may not be able
+ * to access these.
+ */
+ dst->file_attributes = FILE_ATTRIBUTE_REPARSE_POINT;
+ break;
+ }
+}
diff --git a/source3/smbd/smb2_process.c b/source3/smbd/smb2_process.c
new file mode 100644
index 0000000..7366427
--- /dev/null
+++ b/source3/smbd/smb2_process.c
@@ -0,0 +1,2073 @@
+/*
+ Unix SMB/CIFS implementation.
+ process incoming packets - main loop
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Volker Lendecke 2005-2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "../lib/tsocket/tsocket.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "librpc/gen_ndr/netlogon.h"
+#include "../lib/async_req/async_sock.h"
+#include "ctdbd_conn.h"
+#include "../lib/util/select.h"
+#include "printing/queue_process.h"
+#include "system/select.h"
+#include "passdb.h"
+#include "auth.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "smbprofile.h"
+#include "rpc_server/spoolss/srv_spoolss_nt.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../libcli/security/dom_sid.h"
+#include "../libcli/security/security_token.h"
+#include "lib/id_cache.h"
+#include "lib/util/sys_rw_data.h"
+#include "system/threads.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+#include "util_event.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "lib/util/time_basic.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/* Internal message queue for deferred opens. */
+struct pending_message_list {
+ struct pending_message_list *next, *prev;
+ struct timeval request_time; /* When was this first issued? */
+ struct smbd_server_connection *sconn;
+ struct smbXsrv_connection *xconn;
+ struct tevent_timer *te;
+ uint32_t seqnum;
+ bool encrypted;
+ bool processed;
+ DATA_BLOB buf;
+ struct deferred_open_record *open_rec;
+};
+
+static struct pending_message_list *get_deferred_open_message_smb(
+ struct smbd_server_connection *sconn, uint64_t mid);
+
+#if !defined(WITH_SMB1SERVER)
+bool smb1_srv_send(struct smbXsrv_connection *xconn,
+ char *buffer,
+ bool do_signing,
+ uint32_t seqnum,
+ bool do_encrypt)
+{
+ size_t len = 0;
+ ssize_t ret;
+ len = smb_len_large(buffer) + 4;
+ ret = write_data(xconn->transport.sock, buffer, len);
+ return (ret > 0);
+}
+#endif
+
+/*******************************************************************
+ Setup the word count and byte count for a smb1 message.
+********************************************************************/
+
+size_t srv_smb1_set_message(char *buf,
+ size_t num_words,
+ size_t num_bytes,
+ bool zero)
+{
+ if (zero && (num_words || num_bytes)) {
+ memset(buf + smb_size,'\0',num_words*2 + num_bytes);
+ }
+ SCVAL(buf,smb_wct,num_words);
+ SSVAL(buf,smb_vwv + num_words*SIZEOFWORD,num_bytes);
+ smb_setlen(buf,(smb_size + num_words*2 + num_bytes - 4));
+ return (smb_size + num_words*2 + num_bytes);
+}
+
+NTSTATUS read_packet_remainder(int fd, char *buffer,
+ unsigned int timeout, ssize_t len)
+{
+ NTSTATUS status;
+
+ if (len <= 0) {
+ return NT_STATUS_OK;
+ }
+
+ status = read_fd_with_timeout(fd, buffer, len, len, timeout, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ char addr[INET6_ADDRSTRLEN];
+ DEBUG(0, ("read_fd_with_timeout failed for client %s read "
+ "error = %s.\n",
+ get_peer_addr(fd, addr, sizeof(addr)),
+ nt_errstr(status)));
+ }
+ return status;
+}
+
+#if !defined(WITH_SMB1SERVER)
+static NTSTATUS smb2_receive_raw_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, size_t *plen)
+{
+ char lenbuf[4];
+ size_t len;
+ NTSTATUS status;
+
+ *p_unread = 0;
+
+ status = read_smb_length_return_keepalive(sock, lenbuf, timeout,
+ &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * The +4 here can't wrap, we've checked the length above already.
+ */
+
+ *buffer = talloc_array(mem_ctx, char, len+4);
+
+ if (*buffer == NULL) {
+ DEBUG(0, ("Could not allocate inbuf of length %d\n",
+ (int)len+4));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ memcpy(*buffer, lenbuf, sizeof(lenbuf));
+
+ status = read_packet_remainder(sock, (*buffer)+4, timeout, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *plen = len + 4;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb2_receive_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel)
+{
+ size_t len = 0;
+ NTSTATUS status;
+
+ *p_encrypted = false;
+
+ status = smb2_receive_raw_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)?5:1,
+ ("smb2_receive_raw_talloc failed for client %s "
+ "read error = %s.\n",
+ smbXsrv_connection_dbg(xconn),
+ nt_errstr(status)) );
+ return status;
+ }
+
+ *p_len = len;
+ return NT_STATUS_OK;
+}
+#endif
+
+NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_connection *xconn,
+ int sock,
+ char **buffer, unsigned int timeout,
+ size_t *p_unread, bool *p_encrypted,
+ size_t *p_len,
+ uint32_t *seqnum,
+ bool trusted_channel)
+{
+#if defined(WITH_SMB1SERVER)
+ return smb1_receive_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, p_encrypted, p_len, seqnum,
+ trusted_channel);
+#else
+ return smb2_receive_talloc(mem_ctx, xconn, sock, buffer, timeout,
+ p_unread, p_encrypted, p_len, seqnum,
+ trusted_channel);
+#endif
+}
+
+/****************************************************************************
+ Function to delete a sharing violation open message by mid.
+****************************************************************************/
+
+void remove_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct pending_message_list *pml;
+
+ if (sconn->using_smb2) {
+ remove_deferred_open_message_smb2(xconn, mid);
+ return;
+ }
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ if (mid == (uint64_t)SVAL(pml->buf.data,smb_mid)) {
+ DEBUG(10,("remove_deferred_open_message_smb: "
+ "deleting mid %llu len %u\n",
+ (unsigned long long)mid,
+ (unsigned int)pml->buf.length ));
+ DLIST_REMOVE(sconn->deferred_open_queue, pml);
+ TALLOC_FREE(pml);
+ return;
+ }
+ }
+}
+
+static void smbd_deferred_open_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval _tval,
+ void *private_data)
+{
+ struct pending_message_list *msg = talloc_get_type(private_data,
+ struct pending_message_list);
+ struct smbd_server_connection *sconn = msg->sconn;
+ struct smbXsrv_connection *xconn = msg->xconn;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ uint64_t mid = (uint64_t)SVAL(msg->buf.data,smb_mid);
+ uint8_t *inbuf;
+
+ inbuf = (uint8_t *)talloc_memdup(mem_ctx, msg->buf.data,
+ msg->buf.length);
+ if (inbuf == NULL) {
+ exit_server("smbd_deferred_open_timer: talloc failed\n");
+ return;
+ }
+
+ /* We leave this message on the queue so the open code can
+ know this is a retry. */
+ DEBUG(5,("smbd_deferred_open_timer: trigger mid %llu.\n",
+ (unsigned long long)mid ));
+
+ /* Mark the message as processed so this is not
+ * re-processed in error. */
+ msg->processed = true;
+
+ process_smb(xconn,
+ inbuf,
+ msg->buf.length,
+ 0,
+ msg->seqnum,
+ msg->encrypted);
+
+ /* If it's still there and was processed, remove it. */
+ msg = get_deferred_open_message_smb(sconn, mid);
+ if (msg && msg->processed) {
+ remove_deferred_open_message_smb(xconn, mid);
+ }
+}
+
+/****************************************************************************
+ Move a sharing violation open retry message to the front of the list and
+ schedule it for immediate processing.
+****************************************************************************/
+
+bool schedule_deferred_open_message_smb(struct smbXsrv_connection *xconn,
+ uint64_t mid)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct pending_message_list *pml;
+ int i = 0;
+
+ if (sconn->using_smb2) {
+ return schedule_deferred_open_message_smb2(xconn, mid);
+ }
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ uint64_t msg_mid = (uint64_t)SVAL(pml->buf.data,smb_mid);
+
+ DEBUG(10,("schedule_deferred_open_message_smb: [%d] "
+ "msg_mid = %llu\n",
+ i++,
+ (unsigned long long)msg_mid ));
+
+ if (mid == msg_mid) {
+ struct tevent_timer *te;
+
+ if (pml->processed) {
+ /* A processed message should not be
+ * rescheduled. */
+ DEBUG(0,("schedule_deferred_open_message_smb: LOGIC ERROR "
+ "message mid %llu was already processed\n",
+ (unsigned long long)msg_mid ));
+ continue;
+ }
+
+ DEBUG(10,("schedule_deferred_open_message_smb: "
+ "scheduling mid %llu\n",
+ (unsigned long long)mid ));
+
+ /*
+ * smbd_deferred_open_timer() calls
+ * process_smb() to redispatch the request
+ * including the required impersonation.
+ *
+ * So we can just use the raw tevent_context.
+ */
+ te = tevent_add_timer(xconn->client->raw_ev_ctx,
+ pml,
+ timeval_zero(),
+ smbd_deferred_open_timer,
+ pml);
+ if (!te) {
+ DEBUG(10,("schedule_deferred_open_message_smb: "
+ "event_add_timed() failed, "
+ "skipping mid %llu\n",
+ (unsigned long long)msg_mid ));
+ }
+
+ TALLOC_FREE(pml->te);
+ pml->te = te;
+ DLIST_PROMOTE(sconn->deferred_open_queue, pml);
+ return true;
+ }
+ }
+
+ DEBUG(10,("schedule_deferred_open_message_smb: failed to "
+ "find message mid %llu\n",
+ (unsigned long long)mid ));
+
+ return false;
+}
+
+/****************************************************************************
+ Return true if this mid is on the deferred queue and was not yet processed.
+****************************************************************************/
+
+bool open_was_deferred(struct smbXsrv_connection *xconn, uint64_t mid)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct pending_message_list *pml;
+
+ if (sconn->using_smb2) {
+ return open_was_deferred_smb2(xconn, mid);
+ }
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ if (((uint64_t)SVAL(pml->buf.data,smb_mid)) == mid && !pml->processed) {
+ return True;
+ }
+ }
+ return False;
+}
+
+/****************************************************************************
+ Return the message queued by this mid.
+****************************************************************************/
+
+static struct pending_message_list *get_deferred_open_message_smb(
+ struct smbd_server_connection *sconn, uint64_t mid)
+{
+ struct pending_message_list *pml;
+
+ for (pml = sconn->deferred_open_queue; pml; pml = pml->next) {
+ if (((uint64_t)SVAL(pml->buf.data,smb_mid)) == mid) {
+ return pml;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************************
+ Get the state data queued by this mid.
+****************************************************************************/
+
+bool get_deferred_open_message_state(struct smb_request *smbreq,
+ struct timeval *p_request_time,
+ struct deferred_open_record **open_rec)
+{
+ struct pending_message_list *pml;
+
+ if (smbreq->sconn->using_smb2) {
+ return get_deferred_open_message_state_smb2(smbreq->smb2req,
+ p_request_time,
+ open_rec);
+ }
+
+ pml = get_deferred_open_message_smb(smbreq->sconn, smbreq->mid);
+ if (!pml) {
+ return false;
+ }
+ if (p_request_time) {
+ *p_request_time = pml->request_time;
+ }
+ if (open_rec != NULL) {
+ *open_rec = pml->open_rec;
+ }
+ return true;
+}
+
+bool push_deferred_open_message_smb(struct smb_request *req,
+ struct timeval timeout,
+ struct file_id id,
+ struct deferred_open_record *open_rec)
+{
+#if defined(WITH_SMB1SERVER)
+ if (req->smb2req) {
+#endif
+ return push_deferred_open_message_smb2(req->smb2req,
+ req->request_time,
+ timeout,
+ id,
+ open_rec);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ return push_deferred_open_message_smb1(req, timeout,
+ id, open_rec);
+ }
+#endif
+}
+
+static void construct_smb1_reply_common(uint8_t cmd, const uint8_t *inbuf,
+ char *outbuf)
+{
+ uint16_t in_flags2 = SVAL(inbuf,smb_flg2);
+ uint16_t out_flags2 = common_flags2;
+
+ out_flags2 |= in_flags2 & FLAGS2_UNICODE_STRINGS;
+ out_flags2 |= in_flags2 & FLAGS2_SMB_SECURITY_SIGNATURES;
+ out_flags2 |= in_flags2 & FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED;
+
+ srv_smb1_set_message(outbuf,0,0,false);
+
+ SCVAL(outbuf, smb_com, cmd);
+ SIVAL(outbuf,smb_rcls,0);
+ SCVAL(outbuf,smb_flg, FLAG_REPLY | (CVAL(inbuf,smb_flg) & FLAG_CASELESS_PATHNAMES));
+ SSVAL(outbuf,smb_flg2, out_flags2);
+ memset(outbuf+smb_pidhigh,'\0',(smb_tid-smb_pidhigh));
+ memcpy(outbuf+smb_ss_field, inbuf+smb_ss_field, 8);
+
+ SSVAL(outbuf,smb_tid,SVAL(inbuf,smb_tid));
+ SSVAL(outbuf,smb_pid,SVAL(inbuf,smb_pid));
+ SSVAL(outbuf,smb_pidhigh,SVAL(inbuf,smb_pidhigh));
+ SSVAL(outbuf,smb_uid,SVAL(inbuf,smb_uid));
+ SSVAL(outbuf,smb_mid,SVAL(inbuf,smb_mid));
+}
+
+void construct_smb1_reply_common_req(struct smb_request *req, char *outbuf)
+{
+ construct_smb1_reply_common(req->cmd, req->inbuf, outbuf);
+}
+
+/*******************************************************************
+ allocate and initialize a reply packet
+********************************************************************/
+
+bool create_smb1_outbuf(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ const uint8_t *inbuf, char **outbuf,
+ uint8_t num_words, uint32_t num_bytes)
+{
+ size_t smb_len = MIN_SMB_SIZE + VWV(num_words) + num_bytes;
+
+ /*
+ * Protect against integer wrap.
+ * The SMB layer reply can be up to 0xFFFFFF bytes.
+ */
+ if ((num_bytes > 0xffffff) || (smb_len > 0xffffff)) {
+ char *msg;
+ if (asprintf(&msg, "num_bytes too large: %u",
+ (unsigned)num_bytes) == -1) {
+ msg = discard_const_p(char, "num_bytes too large");
+ }
+ smb_panic(msg);
+ }
+
+ /*
+ * Here we include the NBT header for now.
+ */
+ *outbuf = talloc_array(mem_ctx, char,
+ NBT_HDR_SIZE + smb_len);
+ if (*outbuf == NULL) {
+ return false;
+ }
+
+ construct_smb1_reply_common(req->cmd, inbuf, *outbuf);
+ srv_smb1_set_message(*outbuf, num_words, num_bytes, false);
+ /*
+ * Zero out the word area, the caller has to take care of the bcc area
+ * himself
+ */
+ if (num_words != 0) {
+ memset(*outbuf + (NBT_HDR_SIZE + HDR_VWV), 0, VWV(num_words));
+ }
+
+ return true;
+}
+
+void reply_smb1_outbuf(struct smb_request *req, uint8_t num_words, uint32_t num_bytes)
+{
+ char *outbuf;
+ if (!create_smb1_outbuf(req, req, req->inbuf, &outbuf, num_words,
+ num_bytes)) {
+ smb_panic("could not allocate output buffer\n");
+ }
+ req->outbuf = (uint8_t *)outbuf;
+}
+
+bool valid_smb1_header(const uint8_t *inbuf)
+{
+ if (is_encrypted_packet(inbuf)) {
+ return true;
+ }
+ /*
+ * This used to be (strncmp(smb_base(inbuf),"\377SMB",4) == 0)
+ * but it just looks weird to call strncmp for this one.
+ */
+ return (IVAL(smb_base(inbuf), 0) == 0x424D53FF);
+}
+
+/****************************************************************************
+ Process an smb from the client
+****************************************************************************/
+
+static void process_smb2(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ const uint8_t *inpdu = inbuf + NBT_HDR_SIZE;
+ size_t pdulen = nread - NBT_HDR_SIZE;
+ NTSTATUS status = smbd_smb2_process_negprot(xconn, 0, inpdu, pdulen);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("SMB2 negprot fail");
+ }
+}
+
+void process_smb(struct smbXsrv_connection *xconn,
+ uint8_t *inbuf,
+ size_t nread,
+ size_t unread_bytes,
+ uint32_t seqnum,
+ bool encrypted)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ int msg_type = CVAL(inbuf,0);
+
+ DO_PROFILE_INC(request);
+
+ DEBUG( 6, ( "got message type 0x%x of len 0x%x\n", msg_type,
+ smb_len(inbuf) ) );
+ DEBUG(3, ("Transaction %d of length %d (%u toread)\n",
+ sconn->trans_num, (int)nread, (unsigned int)unread_bytes));
+
+ if (msg_type != NBSSmessage) {
+ /*
+ * NetBIOS session request, keepalive, etc.
+ */
+ reply_special(xconn, (char *)inbuf, nread);
+ goto done;
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+ /* At this point we're not really using smb2,
+ * we make the decision here.. */
+ if (smbd_is_smb2_header(inbuf, nread)) {
+#endif
+ process_smb2(xconn,
+ inbuf,
+ nread,
+ unread_bytes,
+ seqnum,
+ encrypted);
+ return;
+#if defined(WITH_SMB1SERVER)
+ }
+ if (nread >= smb_size && valid_smb1_header(inbuf)
+ && CVAL(inbuf, smb_com) != 0x72) {
+ /* This is a non-negprot SMB1 packet.
+ Disable SMB2 from now on. */
+ sconn->using_smb2 = false;
+ }
+ }
+ process_smb1(xconn, inbuf, nread, unread_bytes, seqnum, encrypted);
+#endif
+
+done:
+ sconn->num_requests++;
+
+ /* The timeout_processing function isn't run nearly
+ often enough to implement 'max log size' without
+ overrunning the size of the file by many megabytes.
+ This is especially true if we are running at debug
+ level 10. Checking every 50 SMBs is a nice
+ tradeoff of performance vs log file size overrun. */
+
+ if ((sconn->num_requests % 50) == 0 &&
+ need_to_check_log_size()) {
+ change_to_root_user();
+ check_log_size();
+ }
+}
+
+NTSTATUS smbXsrv_connection_init_tables(struct smbXsrv_connection *conn,
+ enum protocol_types protocol)
+{
+ NTSTATUS status;
+
+ conn->protocol = protocol;
+
+ if (conn->client->session_table != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (protocol >= PROTOCOL_SMB2_02) {
+ status = smb2srv_session_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+
+ status = smb2srv_open_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+ } else {
+#if defined(WITH_SMB1SERVER)
+ status = smb1srv_session_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+
+ status = smb1srv_tcon_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+
+ status = smb1srv_open_table_init(conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ conn->protocol = PROTOCOL_NONE;
+ return status;
+ }
+#else
+ conn->protocol = PROTOCOL_NONE;
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+#endif
+ }
+
+ set_Protocol(protocol);
+ return NT_STATUS_OK;
+}
+
+/**
+ * Create a debug string for the connection
+ *
+ * This is allocated to talloc_tos() or a string constant
+ * in certain corner cases. The returned string should
+ * hence not be free'd directly but only via the talloc stack.
+ */
+const char *smbXsrv_connection_dbg(const struct smbXsrv_connection *xconn)
+{
+ const char *ret = NULL;
+ char *raddr = NULL;
+ char *laddr = NULL;
+ struct GUID_txt_buf guid_buf = {};
+
+ /*
+ * TODO: this can be improved further later...
+ */
+
+ raddr = tsocket_address_string(xconn->remote_address, talloc_tos());
+ if (raddr == NULL) {
+ return "<tsocket_address_string() failed>";
+ }
+ laddr = tsocket_address_string(xconn->local_address, talloc_tos());
+ if (laddr == NULL) {
+ return "<tsocket_address_string() failed>";
+ }
+
+ ret = talloc_asprintf(talloc_tos(),
+ "PID=%d,CLIENT=%s,channel=%"PRIu64",remote=%s,local=%s",
+ getpid(),
+ GUID_buf_string(&xconn->smb2.client.guid, &guid_buf),
+ xconn->channel_id,
+ raddr,
+ laddr);
+ TALLOC_FREE(raddr);
+ TALLOC_FREE(laddr);
+ if (ret == NULL) {
+ return "<talloc_asprintf() failed>";
+ }
+
+ return ret;
+}
+
+/*
+ * Initialize a struct smb_request from an inbuf
+ */
+
+bool init_smb1_request(struct smb_request *req,
+ struct smbd_server_connection *sconn,
+ struct smbXsrv_connection *xconn,
+ const uint8_t *inbuf,
+ size_t unread_bytes, bool encrypted,
+ uint32_t seqnum)
+{
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+ NTTIME now;
+ size_t req_size = smb_len(inbuf) + 4;
+
+ /* Ensure we have at least smb_size bytes. */
+ if (req_size < smb_size) {
+ DEBUG(0,("init_smb1_request: invalid request size %u\n",
+ (unsigned int)req_size ));
+ return false;
+ }
+
+ *req = (struct smb_request) { .cmd = 0};
+
+ req->request_time = timeval_current();
+ now = timeval_to_nttime(&req->request_time);
+
+ req->cmd = CVAL(inbuf, smb_com);
+ req->flags2 = SVAL(inbuf, smb_flg2);
+ req->smbpid = SVAL(inbuf, smb_pid);
+ req->mid = (uint64_t)SVAL(inbuf, smb_mid);
+ req->seqnum = seqnum;
+ req->vuid = SVAL(inbuf, smb_uid);
+ req->tid = SVAL(inbuf, smb_tid);
+ req->wct = CVAL(inbuf, smb_wct);
+ req->vwv = (const uint16_t *)(inbuf+smb_vwv);
+ req->buflen = smb_buflen(inbuf);
+ req->buf = (const uint8_t *)smb_buf_const(inbuf);
+ req->unread_bytes = unread_bytes;
+ req->encrypted = encrypted;
+ req->sconn = sconn;
+ req->xconn = xconn;
+ if (xconn != NULL) {
+ status = smb1srv_tcon_lookup(xconn, req->tid, now, &tcon);
+ if (NT_STATUS_IS_OK(status)) {
+ req->conn = tcon->compat;
+ }
+ }
+ req->posix_pathnames = lp_posix_pathnames();
+
+ /* Ensure we have at least wct words and 2 bytes of bcc. */
+ if (smb_size + req->wct*2 > req_size) {
+ DEBUG(0,("init_smb1_request: invalid wct number %u (size %u)\n",
+ (unsigned int)req->wct,
+ (unsigned int)req_size));
+ return false;
+ }
+ /* Ensure bcc is correct. */
+ if (((const uint8_t *)smb_buf_const(inbuf)) + req->buflen > inbuf + req_size) {
+ DEBUG(0,("init_smb1_request: invalid bcc number %u "
+ "(wct = %u, size %u)\n",
+ (unsigned int)req->buflen,
+ (unsigned int)req->wct,
+ (unsigned int)req_size));
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Construct a reply to the incoming packet.
+****************************************************************************/
+
+static void construct_reply_smb1negprot(struct smbXsrv_connection *xconn,
+ char *inbuf, int size,
+ size_t unread_bytes)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct smb_request *req;
+ NTSTATUS status;
+
+ if (!(req = talloc(talloc_tos(), struct smb_request))) {
+ smb_panic("could not allocate smb_request");
+ }
+
+ if (!init_smb1_request(req, sconn, xconn, (uint8_t *)inbuf, unread_bytes,
+ false, 0)) {
+ exit_server_cleanly("Invalid SMB request");
+ }
+
+ req->inbuf = (uint8_t *)talloc_move(req, &inbuf);
+
+ status = smb2_multi_protocol_reply_negprot(req);
+ if (req->outbuf == NULL) {
+ /*
+ * req->outbuf == NULL means we bootstrapped into SMB2.
+ */
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!smb1_srv_send(req->xconn,
+ (char *)req->outbuf,
+ true,
+ req->seqnum + 1,
+ IS_CONN_ENCRYPTED(req->conn) ||
+ req->encrypted)) {
+ exit_server_cleanly("construct_reply_smb1negprot: "
+ "smb1_srv_send failed.");
+ }
+ TALLOC_FREE(req);
+ } else {
+ /* This code path should only *ever* bootstrap into SMB2. */
+ exit_server_cleanly("Internal error SMB1negprot didn't reply "
+ "with an SMB2 packet");
+ }
+}
+
+static void smbd_server_connection_write_handler(
+ struct smbXsrv_connection *xconn)
+{
+ /* TODO: make write nonblocking */
+}
+
+static void smbd_smb2_server_connection_read_handler(
+ struct smbXsrv_connection *xconn, int fd)
+{
+ char lenbuf[NBT_HDR_SIZE];
+ size_t len = 0;
+ uint8_t *buffer = NULL;
+ size_t bufferlen = 0;
+ NTSTATUS status;
+ uint8_t msg_type = 0;
+
+ /* Read the first 4 bytes - contains length of remainder. */
+ status = read_smb_length_return_keepalive(fd, lenbuf, 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("failed to receive request length");
+ return;
+ }
+
+ /* Integer wrap check. */
+ if (len + NBT_HDR_SIZE < len) {
+ exit_server_cleanly("Invalid length on initial request");
+ return;
+ }
+
+ /*
+ * The +4 here can't wrap, we've checked the length above already.
+ */
+ bufferlen = len+NBT_HDR_SIZE;
+
+ buffer = talloc_array(talloc_tos(), uint8_t, bufferlen);
+ if (buffer == NULL) {
+ DBG_ERR("Could not allocate request inbuf of length %zu\n",
+ bufferlen);
+ exit_server_cleanly("talloc fail");
+ return;
+ }
+
+ /* Copy the NBT_HDR_SIZE length. */
+ memcpy(buffer, lenbuf, sizeof(lenbuf));
+
+ status = read_packet_remainder(fd, (char *)buffer+NBT_HDR_SIZE, 0, len);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("Failed to read remainder of initial request");
+ return;
+ }
+
+ /* Check the message type. */
+ msg_type = PULL_LE_U8(buffer,0);
+ if (msg_type == NBSSrequest) {
+ /*
+ * clients can send this request before
+ * bootstrapping into SMB2. Cope with this
+ * message only, don't allow any other strange
+ * NBSS types.
+ */
+ reply_special(xconn, (char *)buffer, bufferlen);
+ xconn->client->sconn->num_requests++;
+ return;
+ }
+
+ /* Only a 'normal' message type allowed now. */
+ if (msg_type != NBSSmessage) {
+ DBG_ERR("Invalid message type %d\n", msg_type);
+ exit_server_cleanly("Invalid message type for initial request");
+ return;
+ }
+
+ /* Could this be an SMB1 negprot bootstrap into SMB2 ? */
+ if (bufferlen < smb_size) {
+ exit_server_cleanly("Invalid initial SMB1 or SMB2 packet");
+ return;
+ }
+ if (valid_smb1_header(buffer)) {
+ /* Can *only* allow an SMB1 negprot here. */
+ uint8_t cmd = PULL_LE_U8(buffer, smb_com);
+ if (cmd != SMBnegprot) {
+ DBG_ERR("Incorrect SMB1 command 0x%hhx, "
+ "should be SMBnegprot (0x72)\n",
+ cmd);
+ exit_server_cleanly("Invalid initial SMB1 packet");
+ }
+ /* Minimal process_smb(). */
+ show_msg((char *)buffer);
+ construct_reply_smb1negprot(xconn, (char *)buffer,
+ bufferlen, 0);
+ xconn->client->sconn->trans_num++;
+ xconn->client->sconn->num_requests++;
+ return;
+
+ } else if (!smbd_is_smb2_header(buffer, bufferlen)) {
+ exit_server_cleanly("Invalid initial SMB2 packet");
+ return;
+ }
+
+ /* Here we know we're a valid SMB2 packet. */
+
+ /*
+ * Point at the start of the SMB2 PDU.
+ * len is the length of the SMB2 PDU.
+ */
+
+ status = smbd_smb2_process_negprot(xconn,
+ 0,
+ (const uint8_t *)buffer+NBT_HDR_SIZE,
+ len);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly("SMB2 negprot fail");
+ }
+ return;
+}
+
+static void smbd_server_connection_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbXsrv_connection *xconn =
+ talloc_get_type_abort(private_data,
+ struct smbXsrv_connection);
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+ TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
+ return;
+ }
+
+ if (flags & TEVENT_FD_WRITE) {
+ smbd_server_connection_write_handler(xconn);
+ return;
+ }
+ if (flags & TEVENT_FD_READ) {
+#if defined(WITH_SMB1SERVER)
+ if (lp_server_min_protocol() > PROTOCOL_NT1) {
+#endif
+ smbd_smb2_server_connection_read_handler(xconn,
+ xconn->transport.sock);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ smbd_smb1_server_connection_read_handler(xconn,
+ xconn->transport.sock);
+ }
+#endif
+ return;
+ }
+}
+
+struct smbd_release_ip_state {
+ struct smbXsrv_connection *xconn;
+ struct tevent_immediate *im;
+ struct sockaddr_storage srv;
+ struct sockaddr_storage clnt;
+ char addr[INET6_ADDRSTRLEN];
+};
+
+static int release_ip(struct tevent_context *ev,
+ uint32_t src_vnn,
+ uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg,
+ size_t msglen,
+ void *private_data);
+
+static int smbd_release_ip_state_destructor(struct smbd_release_ip_state *s)
+{
+ struct ctdbd_connection *cconn = messaging_ctdb_connection();
+ struct smbXsrv_connection *xconn = s->xconn;
+
+ if (cconn == NULL) {
+ return 0;
+ }
+
+ if (NT_STATUS_EQUAL(xconn->transport.status, NT_STATUS_CONNECTION_IN_USE)) {
+ ctdbd_passed_ips(cconn, &s->srv, &s->clnt, release_ip, s);
+ } else {
+ ctdbd_unregister_ips(cconn, &s->srv, &s->clnt, release_ip, s);
+ }
+
+ return 0;
+}
+
+static void smbd_release_ip_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct smbd_release_ip_state *state =
+ talloc_get_type_abort(private_data,
+ struct smbd_release_ip_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+
+ if (!NT_STATUS_EQUAL(xconn->transport.status, NT_STATUS_ADDRESS_CLOSED)) {
+ /*
+ * smbd_server_connection_terminate() already triggered ?
+ */
+ return;
+ }
+
+ smbd_server_connection_terminate(xconn, "CTDB_SRVID_RELEASE_IP");
+}
+
+/****************************************************************************
+received when we should release a specific IP
+****************************************************************************/
+static int release_ip(struct tevent_context *ev,
+ uint32_t src_vnn, uint32_t dst_vnn,
+ uint64_t dst_srvid,
+ const uint8_t *msg, size_t msglen,
+ void *private_data)
+{
+ struct smbd_release_ip_state *state =
+ talloc_get_type_abort(private_data,
+ struct smbd_release_ip_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+ const char *ip;
+ const char *addr = state->addr;
+ const char *p = addr;
+
+ if (msglen == 0) {
+ return 0;
+ }
+ if (msg[msglen-1] != '\0') {
+ return 0;
+ }
+
+ ip = (const char *)msg;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /* avoid recursion */
+ return 0;
+ }
+
+ if (strncmp("::ffff:", addr, 7) == 0) {
+ p = addr + 7;
+ }
+
+ DEBUG(10, ("Got release IP message for %s, "
+ "our address is %s\n", ip, p));
+
+ if ((strcmp(p, ip) == 0) || ((p != addr) && strcmp(addr, ip) == 0)) {
+ DEBUG(0,("Got release IP message for our IP %s - exiting immediately\n",
+ ip));
+ /*
+ * With SMB2 we should do a clean disconnect,
+ * the previous_session_id in the session setup
+ * will cleanup the old session, tcons and opens.
+ *
+ * A clean disconnect is needed in order to support
+ * durable handles.
+ *
+ * Note: typically this is never triggered
+ * as we got a TCP RST (triggered by ctdb event scripts)
+ * before we get CTDB_SRVID_RELEASE_IP.
+ *
+ * We used to call _exit(1) here, but as this was mostly never
+ * triggered and has implication on our process model,
+ * we can just use smbd_server_connection_terminate()
+ * (also for SMB1).
+ *
+ * We don't call smbd_server_connection_terminate() directly
+ * as we might be called from within ctdbd_migrate(),
+ * we need to defer our action to the next event loop
+ */
+ tevent_schedule_immediate(state->im,
+ xconn->client->raw_ev_ctx,
+ smbd_release_ip_immediate,
+ state);
+
+ /*
+ * Make sure we don't get any io on the connection.
+ */
+ xconn->transport.status = NT_STATUS_ADDRESS_CLOSED;
+ return EADDRNOTAVAIL;
+ }
+
+ return 0;
+}
+
+static int match_cluster_movable_ip(uint32_t total_ip_count,
+ const struct sockaddr_storage *ip,
+ bool is_movable_ip,
+ void *private_data)
+{
+ const struct sockaddr_storage *srv = private_data;
+ struct samba_sockaddr pub_ip = {
+ .u = {
+ .ss = *ip,
+ },
+ };
+ struct samba_sockaddr srv_ip = {
+ .u = {
+ .ss = *srv,
+ },
+ };
+
+ if (is_movable_ip && sockaddr_equal(&pub_ip.u.sa, &srv_ip.u.sa)) {
+ return EADDRNOTAVAIL;
+ }
+
+ return 0;
+}
+
+static NTSTATUS smbd_register_ips(struct smbXsrv_connection *xconn,
+ struct sockaddr_storage *srv,
+ struct sockaddr_storage *clnt)
+{
+ struct smbd_release_ip_state *state;
+ struct ctdbd_connection *cconn;
+ int ret;
+
+ cconn = messaging_ctdb_connection();
+ if (cconn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state = talloc_zero(xconn, struct smbd_release_ip_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->xconn = xconn;
+ state->im = tevent_create_immediate(state);
+ if (state->im == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->srv = *srv;
+ state->clnt = *clnt;
+ if (print_sockaddr(state->addr, sizeof(state->addr), srv) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (xconn->client->server_multi_channel_enabled) {
+ ret = ctdbd_public_ip_foreach(cconn,
+ match_cluster_movable_ip,
+ srv);
+ if (ret == EADDRNOTAVAIL) {
+ xconn->has_cluster_movable_ip = true;
+ DBG_DEBUG("cluster movable IP on %s\n",
+ smbXsrv_connection_dbg(xconn));
+ } else if (ret != 0) {
+ DBG_ERR("failed to iterate cluster IPs: %s\n",
+ strerror(ret));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ ret = ctdbd_register_ips(cconn, srv, clnt, release_ip, state);
+ if (ret != 0) {
+ return map_nt_error_from_unix(ret);
+ }
+
+ talloc_set_destructor(state, smbd_release_ip_state_destructor);
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_connection_destructor(struct smbXsrv_connection *xconn)
+{
+ DBG_DEBUG("xconn[%s]\n", smbXsrv_connection_dbg(xconn));
+ return 0;
+}
+
+NTSTATUS smbd_add_connection(struct smbXsrv_client *client, int sock_fd,
+ NTTIME now, struct smbXsrv_connection **_xconn)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smbXsrv_connection *xconn;
+ struct sockaddr_storage ss_srv;
+ void *sp_srv = (void *)&ss_srv;
+ struct sockaddr *sa_srv = (struct sockaddr *)sp_srv;
+ struct sockaddr_storage ss_clnt;
+ void *sp_clnt = (void *)&ss_clnt;
+ struct sockaddr *sa_clnt = (struct sockaddr *)sp_clnt;
+ socklen_t sa_socklen;
+ struct tsocket_address *local_address = NULL;
+ struct tsocket_address *remote_address = NULL;
+ const char *remaddr = NULL;
+ char *p;
+ const char *rhost = NULL;
+ int ret;
+ int tmp;
+
+ *_xconn = NULL;
+
+ DO_PROFILE_INC(connect);
+
+ xconn = talloc_zero(client, struct smbXsrv_connection);
+ if (xconn == NULL) {
+ DEBUG(0,("talloc_zero(struct smbXsrv_connection)\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(xconn, smbXsrv_connection_destructor);
+ talloc_steal(frame, xconn);
+ xconn->client = client;
+ xconn->connect_time = now;
+ if (client->next_channel_id != 0) {
+ xconn->channel_id = client->next_channel_id++;
+ }
+
+ xconn->transport.sock = sock_fd;
+#if defined(WITH_SMB1SERVER)
+ smbd_echo_init(xconn);
+#endif
+ xconn->protocol = PROTOCOL_NONE;
+
+ /* Ensure child is set to blocking mode */
+ set_blocking(sock_fd,True);
+
+ set_socket_options(sock_fd, "SO_KEEPALIVE");
+ set_socket_options(sock_fd, lp_socket_options());
+
+ sa_socklen = sizeof(ss_clnt);
+ ret = getpeername(sock_fd, sa_clnt, &sa_socklen);
+ if (ret != 0) {
+ int saved_errno = errno;
+ int level = (errno == ENOTCONN)?2:0;
+ DEBUG(level,("getpeername() failed - %s\n",
+ strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+ ret = tsocket_address_bsd_from_sockaddr(xconn,
+ sa_clnt, sa_socklen,
+ &remote_address);
+ if (ret != 0) {
+ int saved_errno = errno;
+ DEBUG(0,("%s: tsocket_address_bsd_from_sockaddr remote failed - %s\n",
+ __location__, strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+
+ sa_socklen = sizeof(ss_srv);
+ ret = getsockname(sock_fd, sa_srv, &sa_socklen);
+ if (ret != 0) {
+ int saved_errno = errno;
+ int level = (errno == ENOTCONN)?2:0;
+ DEBUG(level,("getsockname() failed - %s\n",
+ strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+ ret = tsocket_address_bsd_from_sockaddr(xconn,
+ sa_srv, sa_socklen,
+ &local_address);
+ if (ret != 0) {
+ int saved_errno = errno;
+ DEBUG(0,("%s: tsocket_address_bsd_from_sockaddr remote failed - %s\n",
+ __location__, strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+
+ if (tsocket_address_is_inet(remote_address, "ip")) {
+ remaddr = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (remaddr == NULL) {
+ DEBUG(0,("%s: tsocket_address_inet_addr_string remote failed - %s\n",
+ __location__, strerror(errno)));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ remaddr = "0.0.0.0";
+ }
+
+ /*
+ * Before the first packet, check the global hosts allow/ hosts deny
+ * parameters before doing any parsing of packets passed to us by the
+ * client. This prevents attacks on our parsing code from hosts not in
+ * the hosts allow list.
+ */
+
+ ret = get_remote_hostname(remote_address,
+ &p, talloc_tos());
+ if (ret < 0) {
+ int saved_errno = errno;
+ DEBUG(0,("%s: get_remote_hostname failed - %s\n",
+ __location__, strerror(saved_errno)));
+ TALLOC_FREE(frame);
+ return map_nt_error_from_unix_common(saved_errno);
+ }
+ rhost = p;
+ if (strequal(rhost, "UNKNOWN")) {
+ rhost = remaddr;
+ }
+
+ xconn->local_address = local_address;
+ xconn->remote_address = remote_address;
+ xconn->remote_hostname = talloc_strdup(xconn, rhost);
+ if (xconn->remote_hostname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!srv_init_signing(xconn)) {
+ DEBUG(0, ("Failed to init smb_signing\n"));
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!allow_access(lp_hosts_deny(-1), lp_hosts_allow(-1),
+ xconn->remote_hostname,
+ remaddr)) {
+ DEBUG( 1, ("Connection denied from %s to %s\n",
+ tsocket_address_string(remote_address, talloc_tos()),
+ tsocket_address_string(local_address, talloc_tos())));
+
+ /*
+ * We return a valid xconn
+ * so that the caller can return an error message
+ * to the client
+ */
+ DLIST_ADD_END(client->connections, xconn);
+ talloc_steal(client, xconn);
+
+ *_xconn = xconn;
+ TALLOC_FREE(frame);
+ return NT_STATUS_NETWORK_ACCESS_DENIED;
+ }
+
+ DEBUG(10, ("Connection allowed from %s to %s\n",
+ tsocket_address_string(remote_address, talloc_tos()),
+ tsocket_address_string(local_address, talloc_tos())));
+
+ if (lp_clustering()) {
+ /*
+ * We need to tell ctdb about our client's TCP
+ * connection, so that for failover ctdbd can send
+ * tickle acks, triggering a reconnection by the
+ * client.
+ */
+ NTSTATUS status;
+
+ status = smbd_register_ips(xconn, &ss_srv, &ss_clnt);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("ctdbd_register_ips failed: %s\n",
+ nt_errstr(status)));
+ }
+ }
+
+ tmp = lp_max_xmit();
+ tmp = MAX(tmp, SMB_BUFFER_SIZE_MIN);
+ tmp = MIN(tmp, SMB_BUFFER_SIZE_MAX);
+
+#if defined(WITH_SMB1SERVER)
+ xconn->smb1.negprot.max_recv = tmp;
+
+ xconn->smb1.sessions.done_sesssetup = false;
+ xconn->smb1.sessions.max_send = SMB_BUFFER_SIZE_MAX;
+#endif
+
+ xconn->transport.fde = tevent_add_fd(client->raw_ev_ctx,
+ xconn,
+ sock_fd,
+ TEVENT_FD_READ,
+ smbd_server_connection_handler,
+ xconn);
+ if (!xconn->transport.fde) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_fd_set_auto_close(xconn->transport.fde);
+
+ /* for now we only have one connection */
+ DLIST_ADD_END(client->connections, xconn);
+ talloc_steal(client, xconn);
+
+ *_xconn = xconn;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+static bool uid_in_use(struct auth_session_info *session_info,
+ uid_t uid)
+{
+ if (session_info->unix_token->uid == uid) {
+ return true;
+ }
+ return false;
+}
+
+static bool gid_in_use(struct auth_session_info *session_info,
+ gid_t gid)
+{
+ uint32_t i;
+ struct security_unix_token *utok = NULL;
+
+ utok = session_info->unix_token;
+ if (utok->gid == gid) {
+ return true;
+ }
+
+ for(i = 0; i < utok->ngroups; i++) {
+ if (utok->groups[i] == gid) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool sid_in_use(struct auth_session_info *session_info,
+ const struct dom_sid *psid)
+{
+ struct security_token *tok = NULL;
+
+ tok = session_info->security_token;
+ if (tok == NULL) {
+ /*
+ * Not sure session_info->security_token can
+ * ever be NULL. This check might be not
+ * necessary.
+ */
+ return false;
+ }
+ if (security_token_has_sid(tok, psid)) {
+ return true;
+ }
+ return false;
+}
+
+struct id_in_use_state {
+ const struct id_cache_ref *id;
+ bool match;
+};
+
+static int id_in_use_cb(struct smbXsrv_session *session,
+ void *private_data)
+{
+ struct id_in_use_state *state = (struct id_in_use_state *)
+ private_data;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+
+ switch(state->id->type) {
+ case UID:
+ state->match = uid_in_use(session_info, state->id->id.uid);
+ break;
+ case GID:
+ state->match = gid_in_use(session_info, state->id->id.gid);
+ break;
+ case SID:
+ state->match = sid_in_use(session_info, &state->id->id.sid);
+ break;
+ default:
+ state->match = false;
+ break;
+ }
+ if (state->match) {
+ return -1;
+ }
+ return 0;
+}
+
+static bool id_in_use(struct smbd_server_connection *sconn,
+ const struct id_cache_ref *id)
+{
+ struct id_in_use_state state;
+ NTSTATUS status;
+
+ state = (struct id_in_use_state) {
+ .id = id,
+ .match = false,
+ };
+
+ status = smbXsrv_session_local_traverse(sconn->client,
+ id_in_use_cb,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ return state.match;
+}
+
+/****************************************************************************
+ Check if services need reloading.
+****************************************************************************/
+
+static void check_reload(struct smbd_server_connection *sconn, time_t t)
+{
+
+ if (last_smb_conf_reload_time == 0) {
+ last_smb_conf_reload_time = t;
+ }
+
+ if (t >= last_smb_conf_reload_time+SMBD_RELOAD_CHECK) {
+ reload_services(sconn, conn_snum_used, true);
+ last_smb_conf_reload_time = t;
+ }
+}
+
+static void msg_kill_client_ip(struct messaging_context *msg_ctx,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id, DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+ const char *ip = (char *) data->data;
+ char *client_ip;
+
+ DBG_DEBUG("Got kill request for client IP %s\n", ip);
+
+ client_ip = tsocket_address_inet_addr_string(sconn->remote_address,
+ talloc_tos());
+ if (client_ip == NULL) {
+ return;
+ }
+
+ if (strequal(ip, client_ip)) {
+ DBG_WARNING("Got kill client message for %s - "
+ "exiting immediately\n", ip);
+ exit_server_cleanly("Forced disconnect for client");
+ }
+
+ TALLOC_FREE(client_ip);
+}
+
+/*
+ * Do the recurring check if we're idle
+ */
+static bool deadtime_fn(const struct timeval *now, void *private_data)
+{
+ struct smbd_server_connection *sconn =
+ (struct smbd_server_connection *)private_data;
+
+ if ((conn_num_open(sconn) == 0)
+ || (conn_idle_all(sconn, now->tv_sec))) {
+ DEBUG( 2, ( "Closing idle connection\n" ) );
+ messaging_send(sconn->msg_ctx,
+ messaging_server_id(sconn->msg_ctx),
+ MSG_SHUTDOWN, &data_blob_null);
+ return False;
+ }
+
+ return True;
+}
+
+/*
+ * Do the recurring log file and smb.conf reload checks.
+ */
+
+static bool housekeeping_fn(const struct timeval *now, void *private_data)
+{
+ struct smbd_server_connection *sconn = talloc_get_type_abort(
+ private_data, struct smbd_server_connection);
+
+ DEBUG(5, ("housekeeping\n"));
+
+ change_to_root_user();
+
+ /* check if we need to reload services */
+ check_reload(sconn, time_mono(NULL));
+
+ /*
+ * Force a log file check.
+ */
+ force_check_log_size();
+ check_log_size();
+ return true;
+}
+
+static void smbd_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ exit_server_cleanly("termination signal");
+}
+
+static void smbd_setup_sig_term_handler(struct smbd_server_connection *sconn)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(sconn->ev_ctx,
+ sconn,
+ SIGTERM, 0,
+ smbd_sig_term_handler,
+ sconn);
+ if (!se) {
+ exit_server("failed to setup SIGTERM handler");
+ }
+}
+
+static void smbd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ change_to_root_user();
+ DEBUG(1,("Reloading services after SIGHUP\n"));
+ reload_services(sconn, conn_snum_used, false);
+}
+
+static void smbd_setup_sig_hup_handler(struct smbd_server_connection *sconn)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(sconn->ev_ctx,
+ sconn,
+ SIGHUP, 0,
+ smbd_sig_hup_handler,
+ sconn);
+ if (!se) {
+ exit_server("failed to setup SIGHUP handler");
+ }
+}
+
+static void smbd_conf_updated(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ DEBUG(10,("smbd_conf_updated: Got message saying smb.conf was "
+ "updated. Reloading.\n"));
+ change_to_root_user();
+ reload_services(sconn, conn_snum_used, false);
+}
+
+static void smbd_id_cache_kill(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB* data)
+{
+ const char *msg = (data && data->data)
+ ? (const char *)data->data : "<NULL>";
+ struct id_cache_ref id;
+ struct smbd_server_connection *sconn =
+ talloc_get_type_abort(private_data,
+ struct smbd_server_connection);
+
+ if (!id_cache_ref_parse(msg, &id)) {
+ DEBUG(0, ("Invalid ?ID: %s\n", msg));
+ return;
+ }
+
+ if (id_in_use(sconn, &id)) {
+ exit_server_cleanly(msg);
+ }
+ id_cache_delete_from_cache(&id);
+}
+
+struct smbd_tevent_trace_state {
+ struct tevent_context *ev;
+ TALLOC_CTX *frame;
+ SMBPROFILE_BASIC_ASYNC_STATE(profile_idle);
+};
+
+static inline void smbd_tevent_trace_callback_before_loop_once(
+ struct smbd_tevent_trace_state *state)
+{
+ talloc_free(state->frame);
+ state->frame = talloc_stackframe_pool(8192);
+}
+
+static inline void smbd_tevent_trace_callback_after_loop_once(
+ struct smbd_tevent_trace_state *state)
+{
+ TALLOC_FREE(state->frame);
+}
+
+static void smbd_tevent_trace_callback(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct smbd_tevent_trace_state *state =
+ (struct smbd_tevent_trace_state *)private_data;
+
+ switch (point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ break;
+ case TEVENT_TRACE_BEFORE_LOOP_ONCE:
+ smbd_tevent_trace_callback_before_loop_once(state);
+ break;
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ smbd_tevent_trace_callback_after_loop_once(state);
+ break;
+ }
+
+ errno = 0;
+}
+
+static void smbd_tevent_trace_callback_profile(enum tevent_trace_point point,
+ void *private_data)
+{
+ struct smbd_tevent_trace_state *state =
+ (struct smbd_tevent_trace_state *)private_data;
+
+ switch (point) {
+ case TEVENT_TRACE_BEFORE_WAIT:
+ if (!smbprofile_dump_pending()) {
+ /*
+ * If there's no dump pending
+ * we don't want to schedule a new 1 sec timer.
+ *
+ * Instead we want to sleep as long as nothing happens.
+ */
+ smbprofile_dump_setup(NULL);
+ }
+ SMBPROFILE_BASIC_ASYNC_START(idle, profile_p, state->profile_idle);
+ break;
+ case TEVENT_TRACE_AFTER_WAIT:
+ SMBPROFILE_BASIC_ASYNC_END(state->profile_idle);
+ if (!smbprofile_dump_pending()) {
+ /*
+ * We need to flush our state after sleeping
+ * (hopefully a long time).
+ */
+ smbprofile_dump();
+ /*
+ * future profiling events should trigger timers
+ * on our main event context.
+ */
+ smbprofile_dump_setup(state->ev);
+ }
+ break;
+ case TEVENT_TRACE_BEFORE_LOOP_ONCE:
+ smbd_tevent_trace_callback_before_loop_once(state);
+ break;
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ smbd_tevent_trace_callback_after_loop_once(state);
+ break;
+ }
+
+ errno = 0;
+}
+
+/****************************************************************************
+ Process commands from the client
+****************************************************************************/
+
+void smbd_process(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ int sock_fd,
+ bool interactive)
+{
+ struct smbd_tevent_trace_state trace_state = {
+ .ev = ev_ctx,
+ .frame = talloc_stackframe(),
+ };
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_client *client = NULL;
+ struct smbd_server_connection *sconn = NULL;
+ struct smbXsrv_connection *xconn = NULL;
+ const char *locaddr = NULL;
+ const char *remaddr = NULL;
+ int ret;
+ NTSTATUS status;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+ char *chroot_dir = NULL;
+ int rc;
+
+ status = smbXsrv_client_create(ev_ctx, ev_ctx, msg_ctx, now, &client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_client_create(): %s\n", nt_errstr(status));
+ exit_server_cleanly("talloc_zero(struct smbXsrv_client).\n");
+ }
+
+ /*
+ * TODO: remove this...:-)
+ */
+ global_smbXsrv_client = client;
+
+ sconn = talloc_zero(client, struct smbd_server_connection);
+ if (sconn == NULL) {
+ exit_server("failed to create smbd_server_connection");
+ }
+
+ client->sconn = sconn;
+ sconn->client = client;
+
+ sconn->ev_ctx = ev_ctx;
+ sconn->msg_ctx = msg_ctx;
+
+ ret = pthreadpool_tevent_init(sconn, lp_aio_max_threads(),
+ &sconn->pool);
+ if (ret != 0) {
+ exit_server("pthreadpool_tevent_init() failed.");
+ }
+
+#if defined(WITH_SMB1SERVER)
+ if (lp_server_max_protocol() >= PROTOCOL_SMB2_02) {
+#endif
+ /*
+ * We're not making the decision here,
+ * we're just allowing the client
+ * to decide between SMB1 and SMB2
+ * with the first negprot
+ * packet.
+ */
+ sconn->using_smb2 = true;
+#if defined(WITH_SMB1SERVER)
+ }
+#endif
+
+ if (!interactive) {
+ smbd_setup_sig_term_handler(sconn);
+ smbd_setup_sig_hup_handler(sconn);
+ }
+
+ status = smbd_add_connection(client, sock_fd, now, &xconn);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ /*
+ * send a negative session response "not listening on calling
+ * name"
+ */
+ unsigned char buf[5] = {0x83, 0, 0, 1, 0x81};
+ (void)smb1_srv_send(xconn, (char *)buf, false, 0, false);
+ exit_server_cleanly("connection denied");
+ } else if (!NT_STATUS_IS_OK(status)) {
+ exit_server_cleanly(nt_errstr(status));
+ }
+
+ sconn->local_address =
+ tsocket_address_copy(xconn->local_address, sconn);
+ if (sconn->local_address == NULL) {
+ exit_server_cleanly("tsocket_address_copy() failed");
+ }
+ sconn->remote_address =
+ tsocket_address_copy(xconn->remote_address, sconn);
+ if (sconn->remote_address == NULL) {
+ exit_server_cleanly("tsocket_address_copy() failed");
+ }
+ sconn->remote_hostname =
+ talloc_strdup(sconn, xconn->remote_hostname);
+ if (sconn->remote_hostname == NULL) {
+ exit_server_cleanly("tsocket_strdup() failed");
+ }
+
+ client->global->local_address =
+ tsocket_address_string(sconn->local_address,
+ client->global);
+ if (client->global->local_address == NULL) {
+ exit_server_cleanly("tsocket_address_string() failed");
+ }
+ client->global->remote_address =
+ tsocket_address_string(sconn->remote_address,
+ client->global);
+ if (client->global->remote_address == NULL) {
+ exit_server_cleanly("tsocket_address_string() failed");
+ }
+ client->global->remote_name =
+ talloc_strdup(client->global, sconn->remote_hostname);
+ if (client->global->remote_name == NULL) {
+ exit_server_cleanly("tsocket_strdup() failed");
+ }
+
+ if (tsocket_address_is_inet(sconn->local_address, "ip")) {
+ locaddr = tsocket_address_inet_addr_string(
+ sconn->local_address,
+ talloc_tos());
+ if (locaddr == NULL) {
+ DEBUG(0,("%s: tsocket_address_inet_addr_string remote failed - %s\n",
+ __location__, strerror(errno)));
+ exit_server_cleanly("tsocket_address_inet_addr_string remote failed.\n");
+ }
+ } else {
+ locaddr = "0.0.0.0";
+ }
+
+ if (tsocket_address_is_inet(sconn->remote_address, "ip")) {
+ remaddr = tsocket_address_inet_addr_string(
+ sconn->remote_address,
+ talloc_tos());
+ if (remaddr == NULL) {
+ DEBUG(0,("%s: tsocket_address_inet_addr_string remote failed - %s\n",
+ __location__, strerror(errno)));
+ exit_server_cleanly("tsocket_address_inet_addr_string remote failed.\n");
+ }
+ } else {
+ remaddr = "0.0.0.0";
+ }
+
+ /* this is needed so that we get decent entries
+ in smbstatus for port 445 connects */
+ set_remote_machine_name(remaddr, false);
+ reload_services(sconn, conn_snum_used, true);
+ sub_set_socket_ids(remaddr,
+ sconn->remote_hostname,
+ locaddr);
+
+ if (lp_preload_modules()) {
+ smb_load_all_modules_absoute_path(lp_preload_modules());
+ }
+
+ if (!init_account_policy()) {
+ exit_server("Could not open account policy tdb.\n");
+ }
+
+ chroot_dir = lp_root_directory(talloc_tos(), lp_sub);
+ if (chroot_dir[0] != '\0') {
+ rc = chdir(chroot_dir);
+ if (rc != 0) {
+ DBG_ERR("Failed to chdir to %s\n", chroot_dir);
+ exit_server("Failed to chdir()");
+ }
+
+ rc = chroot(chroot_dir);
+ if (rc != 0) {
+ DBG_ERR("Failed to change root to %s\n", chroot_dir);
+ exit_server("Failed to chroot()");
+ }
+ DBG_WARNING("Changed root to %s\n", chroot_dir);
+
+ TALLOC_FREE(chroot_dir);
+ }
+
+ if (!file_init(sconn)) {
+ exit_server("file_init() failed");
+ }
+
+ /* Setup oplocks */
+ if (!init_oplocks(sconn))
+ exit_server("Failed to init oplocks");
+
+ /* register our message handlers */
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_FORCE_TDIS, msg_force_tdis);
+ messaging_register(
+ sconn->msg_ctx,
+ sconn,
+ MSG_SMB_FORCE_TDIS_DENIED,
+ msg_force_tdis_denied);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_CLOSE_FILE, msg_close_file);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_FILE_RENAME, msg_file_was_renamed);
+
+ id_cache_register_msgs(sconn->msg_ctx);
+ messaging_deregister(sconn->msg_ctx, ID_CACHE_KILL, NULL);
+ messaging_register(sconn->msg_ctx, sconn,
+ ID_CACHE_KILL, smbd_id_cache_kill);
+
+ messaging_deregister(sconn->msg_ctx,
+ MSG_SMB_CONF_UPDATED, sconn->ev_ctx);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_CONF_UPDATED, smbd_conf_updated);
+
+ messaging_deregister(sconn->msg_ctx, MSG_SMB_KILL_CLIENT_IP,
+ NULL);
+ messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_KILL_CLIENT_IP,
+ msg_kill_client_ip);
+
+ messaging_deregister(sconn->msg_ctx, MSG_SMB_TELL_NUM_CHILDREN, NULL);
+
+ /*
+ * Use the default MSG_DEBUG handler to avoid rebroadcasting
+ * MSGs to all child processes
+ */
+ messaging_deregister(sconn->msg_ctx,
+ MSG_DEBUG, NULL);
+ messaging_register(sconn->msg_ctx, NULL,
+ MSG_DEBUG, debug_message);
+
+#if defined(WITH_SMB1SERVER)
+ if ((lp_keepalive() != 0)
+ && !(event_add_idle(ev_ctx, NULL,
+ timeval_set(lp_keepalive(), 0),
+ "keepalive", keepalive_fn,
+ sconn))) {
+ DEBUG(0, ("Could not add keepalive event\n"));
+ exit(1);
+ }
+#endif
+
+ if (!(event_add_idle(ev_ctx, NULL,
+ timeval_set(IDLE_CLOSED_TIMEOUT, 0),
+ "deadtime", deadtime_fn, sconn))) {
+ DEBUG(0, ("Could not add deadtime event\n"));
+ exit(1);
+ }
+
+ if (!(event_add_idle(ev_ctx, NULL,
+ timeval_set(SMBD_HOUSEKEEPING_INTERVAL, 0),
+ "housekeeping", housekeeping_fn, sconn))) {
+ DEBUG(0, ("Could not add housekeeping event\n"));
+ exit(1);
+ }
+
+ smbprofile_dump_setup(ev_ctx);
+
+ if (!init_dptrs(sconn)) {
+ exit_server("init_dptrs() failed");
+ }
+
+ TALLOC_FREE(trace_state.frame);
+
+ if (smbprofile_active()) {
+ tevent_set_trace_callback(ev_ctx,
+ smbd_tevent_trace_callback_profile,
+ &trace_state);
+ } else {
+ tevent_set_trace_callback(ev_ctx,
+ smbd_tevent_trace_callback,
+ &trace_state);
+ }
+
+ ret = tevent_loop_wait(ev_ctx);
+ if (ret != 0) {
+ DEBUG(1, ("tevent_loop_wait failed: %d, %s,"
+ " exiting\n", ret, strerror(errno)));
+ }
+
+ TALLOC_FREE(trace_state.frame);
+
+ exit_server_cleanly(NULL);
+}
diff --git a/source3/smbd/smb2_query_directory.c b/source3/smbd/smb2_query_directory.c
new file mode 100644
index 0000000..ebbbbb3
--- /dev/null
+++ b/source3/smbd/smb2_query_directory.c
@@ -0,0 +1,1076 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "trans2.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "system/filesys.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+#include "source3/smbd/dir.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_file_info_class,
+ uint8_t in_flags,
+ uint32_t in_file_index,
+ uint32_t in_output_buffer_length,
+ const char *in_file_name);
+static NTSTATUS smbd_smb2_query_directory_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer);
+
+static void smbd_smb2_request_find_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_query_directory(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_file_info_class;
+ uint8_t in_flags;
+ uint32_t in_file_index;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint16_t in_file_name_offset;
+ uint16_t in_file_name_length;
+ DATA_BLOB in_file_name_buffer;
+ char *in_file_name_string;
+ size_t in_file_name_string_size;
+ uint32_t in_output_buffer_length;
+ struct tevent_req *subreq;
+ bool ok;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x21);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_file_info_class = CVAL(inbody, 0x02);
+ in_flags = CVAL(inbody, 0x03);
+ in_file_index = IVAL(inbody, 0x04);
+ in_file_id_persistent = BVAL(inbody, 0x08);
+ in_file_id_volatile = BVAL(inbody, 0x10);
+ in_file_name_offset = SVAL(inbody, 0x18);
+ in_file_name_length = SVAL(inbody, 0x1A);
+ in_output_buffer_length = IVAL(inbody, 0x1C);
+
+ if (in_file_name_offset == 0 && in_file_name_length == 0) {
+ /* This is ok */
+ } else if (in_file_name_offset !=
+ (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_file_name_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ /* The output header is 8 bytes. */
+ if (in_output_buffer_length <= 8) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ DEBUG(10,("smbd_smb2_request_find_done: in_output_buffer_length = %u\n",
+ (unsigned int)in_output_buffer_length ));
+
+ /* Take into account the output header. */
+ in_output_buffer_length -= 8;
+
+ in_file_name_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_file_name_buffer.length = in_file_name_length;
+
+ ok = convert_string_talloc(req, CH_UTF16, CH_UNIX,
+ in_file_name_buffer.data,
+ in_file_name_buffer.length,
+ &in_file_name_string,
+ &in_file_name_string_size);
+ if (!ok) {
+ return smbd_smb2_request_error(req, NT_STATUS_ILLEGAL_CHARACTER);
+ }
+
+ if (in_file_name_buffer.length == 0) {
+ in_file_name_string_size = 0;
+ }
+
+ if (strlen(in_file_name_string) != in_file_name_string_size) {
+ return smbd_smb2_request_error(req, NT_STATUS_OBJECT_NAME_INVALID);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_query_directory_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_file_info_class,
+ in_flags,
+ in_file_index,
+ in_output_buffer_length,
+ in_file_name_string);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_find_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_find_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_output_buffer_offset;
+ DATA_BLOB out_output_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_query_directory_recv(subreq,
+ req,
+ &out_output_buffer);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_output_buffer_offset = SMB2_HDR_BODY + 0x08;
+
+ outbody = smbd_smb2_generate_outbody(req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_output_buffer_offset); /* output buffer offset */
+ SIVAL(outbody.data, 0x04,
+ out_output_buffer.length); /* output buffer length */
+
+ DEBUG(10,("smbd_smb2_request_find_done: out_output_buffer.length = %u\n",
+ (unsigned int)out_output_buffer.length ));
+
+ outdyn = out_output_buffer;
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static struct tevent_req *fetch_write_time_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ connection_struct *conn,
+ struct file_id id,
+ int info_level,
+ char *entry_marshall_buf,
+ bool *stop);
+static NTSTATUS fetch_write_time_recv(struct tevent_req *req);
+
+static struct tevent_req *fetch_dos_mode_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *dir_fsp,
+ struct smb_filename **smb_fname,
+ uint32_t info_level,
+ uint8_t *entry_marshall_buf);
+
+static NTSTATUS fetch_dos_mode_recv(struct tevent_req *req);
+
+struct smbd_smb2_query_directory_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ uint64_t async_sharemode_count;
+ uint32_t find_async_delay_usec;
+ DATA_BLOB out_output_buffer;
+ struct smb_request *smbreq;
+ int in_output_buffer_length;
+ struct files_struct *dirfsp;
+ const char *in_file_name;
+ NTSTATUS empty_status;
+ uint32_t info_level;
+ uint32_t max_count;
+ char *pdata;
+ char *base_data;
+ char *end_data;
+ uint32_t num;
+ uint32_t dirtype;
+ bool dont_descend;
+ bool ask_sharemode;
+ bool async_dosmode;
+ bool async_ask_sharemode;
+ int last_entry_off;
+ size_t max_async_dosmode_active;
+ uint32_t async_dosmode_active;
+ bool done;
+};
+
+static bool smb2_query_directory_next_entry(struct tevent_req *req);
+static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq);
+static void smb2_query_directory_dos_mode_done(struct tevent_req *subreq);
+static void smb2_query_directory_waited(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_query_directory_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_file_info_class,
+ uint8_t in_flags,
+ uint32_t in_file_index,
+ uint32_t in_output_buffer_length,
+ const char *in_file_name)
+{
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ struct tevent_req *req;
+ struct smbd_smb2_query_directory_state *state;
+ connection_struct *conn = smb2req->tcon->compat;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+ bool wcard_has_wild = false;
+ struct tm tm = {};
+ char *p;
+ bool stop = false;
+ bool ok;
+ bool posix_dir_handle = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_query_directory_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->dirfsp = fsp;
+ state->smb2req = smb2req;
+ state->in_output_buffer_length = in_output_buffer_length;
+ state->in_file_name = in_file_name;
+ state->out_output_buffer = data_blob_null;
+ state->dirtype = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_DIRECTORY;
+
+ DEBUG(10,("smbd_smb2_query_directory_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ state->smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(state->smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (!fsp->fsp_flags.is_directory) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (strcmp(state->in_file_name, "") == 0) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+ return tevent_req_post(req, ev);
+ }
+ if (strchr_m(state->in_file_name, '\\') != NULL) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+ return tevent_req_post(req, ev);
+ }
+ if (strchr_m(state->in_file_name, '/') != NULL) {
+ tevent_req_nterror(req, NT_STATUS_OBJECT_NAME_INVALID);
+ return tevent_req_post(req, ev);
+ }
+
+ p = strptime(state->in_file_name, GMT_FORMAT, &tm);
+ if ((p != NULL) && (*p =='\0')) {
+ /*
+ * Bogus find that asks for a shadow copy timestamp as a
+ * directory. The correct response is that it does not exist as
+ * a directory.
+ */
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_FILE);
+ return tevent_req_post(req, ev);
+ }
+
+ if (in_output_buffer_length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_query_directory_send: "
+ "client ignored max trans:%s: 0x%08X: 0x%08X\n",
+ __location__, in_output_buffer_length,
+ xconn->smb2.server.max_trans));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(smb2req,
+ in_output_buffer_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_file_info_class) {
+ case SMB2_FIND_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_FILE_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_FULL_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_FILE_FULL_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_BOTH_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_FILE_BOTH_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_NAME_INFO:
+ state->info_level = SMB_FIND_FILE_NAMES_INFO;
+ break;
+
+ case SMB2_FIND_ID_BOTH_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_ID_BOTH_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_ID_FULL_DIRECTORY_INFO:
+ state->info_level = SMB_FIND_ID_FULL_DIRECTORY_INFO;
+ break;
+
+ case SMB2_FIND_POSIX_INFORMATION:
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return tevent_req_post(req, ev);
+ }
+ state->info_level = SMB2_FILE_POSIX_INFORMATION;
+ break;
+ default:
+ tevent_req_nterror(req, NT_STATUS_INVALID_INFO_CLASS);
+ return tevent_req_post(req, ev);
+ }
+
+ if (in_flags & SMB2_CONTINUE_FLAG_REOPEN) {
+ struct vfs_open_how how = { .flags = O_RDONLY, };
+
+ status = fd_close(fsp);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * fd_close() will close and invalidate the fsp's file
+ * descriptor. So we have to reopen it.
+ */
+
+#ifdef O_DIRECTORY
+ how.flags |= O_DIRECTORY;
+#endif
+ status = fd_openat(conn->cwd_fsp, fsp->fsp_name, fsp, &how);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (!state->smbreq->posix_pathnames) {
+ wcard_has_wild = ms_has_wild(state->in_file_name);
+ }
+
+ /* Ensure we've canonicalized any search path if not a wildcard. */
+ if (!wcard_has_wild) {
+ /*
+ * We still need to do the case processing
+ * to save off the client-supplied last component.
+ * At least we know there's no @GMT normalization
+ * or MS-DFS paths to do in a directory mask.
+ */
+ state->in_file_name = get_original_lcomp(state,
+ conn,
+ state->in_file_name,
+ 0);
+ if (tevent_req_nomem(state->in_file_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (fsp->dptr == NULL) {
+ status = dptr_create(conn,
+ NULL, /* req */
+ fsp,
+ false, /* old_handle */
+ state->in_file_name, /* wcard */
+ state->dirtype,
+ &fsp->dptr);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ state->empty_status = NT_STATUS_NO_SUCH_FILE;
+ } else {
+ state->empty_status = STATUS_NO_MORE_FILES;
+ }
+
+ if (in_flags & SMB2_CONTINUE_FLAG_RESTART) {
+ dptr_RewindDir(fsp->dptr);
+ }
+
+ if (in_flags & SMB2_CONTINUE_FLAG_SINGLE) {
+ state->max_count = 1;
+ } else {
+ state->max_count = UINT16_MAX;
+ }
+
+#define DIR_ENTRY_SAFETY_MARGIN 4096
+
+ state->out_output_buffer = data_blob_talloc(state, NULL,
+ in_output_buffer_length + DIR_ENTRY_SAFETY_MARGIN);
+ if (tevent_req_nomem(state->out_output_buffer.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->out_output_buffer.length = 0;
+ state->pdata = (char *)state->out_output_buffer.data;
+ state->base_data = state->pdata;
+ /*
+ * end_data must include the safety margin as it's what is
+ * used to determine if pushed strings have been truncated.
+ */
+ state->end_data = state->pdata + in_output_buffer_length + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ DEBUG(8,("smbd_smb2_query_directory_send: dirpath=<%s> dontdescend=<%s>, "
+ "in_output_buffer_length = %u\n",
+ fsp->fsp_name->base_name, lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn)),
+ (unsigned int)in_output_buffer_length ));
+
+ state->dont_descend = in_list(
+ fsp->fsp_name->base_name,
+ lp_dont_descend(talloc_tos(), lp_sub, SNUM(conn)),
+ posix_dir_handle ? true : conn->case_sensitive);
+
+ /*
+ * SMB_FIND_FILE_NAMES_INFO doesn't need stat information
+ *
+ * This may change when we try to improve the delete on close
+ * handling in future.
+ */
+ if (state->info_level != SMB_FIND_FILE_NAMES_INFO) {
+ state->ask_sharemode = fsp_search_ask_sharemode(fsp);
+
+ state->async_dosmode = lp_smbd_async_dosmode(SNUM(conn));
+ }
+
+ if (state->ask_sharemode && lp_clustering()) {
+ state->ask_sharemode = false;
+ state->async_ask_sharemode = true;
+ }
+
+ if (state->async_dosmode) {
+ size_t max_threads;
+
+ max_threads = pthreadpool_tevent_max_threads(conn->sconn->pool);
+ if (max_threads == 0 || !per_thread_cwd_supported()) {
+ state->async_dosmode = false;
+ }
+
+ state->max_async_dosmode_active = lp_smbd_max_async_dosmode(
+ SNUM(conn));
+ if (state->max_async_dosmode_active == 0) {
+ state->max_async_dosmode_active = max_threads * 2;
+ }
+ }
+
+ if (state->async_dosmode || state->async_ask_sharemode) {
+ /*
+ * Should we only set async_internal
+ * if we're not the last request in
+ * a compound chain?
+ */
+ smb2_request_set_async_internal(smb2req, true);
+ }
+
+ /*
+ * This gets set in autobuild for some tests
+ */
+ state->find_async_delay_usec = lp_parm_ulong(SNUM(conn), "smbd",
+ "find async delay usec",
+ 0);
+
+ while (!stop) {
+ stop = smb2_query_directory_next_entry(req);
+ }
+
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ok = aio_add_req_to_fsp(fsp, req);
+ if (!ok) {
+ DBG_ERR("Could not add req to fsp\n");
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static bool smb2_query_directory_next_entry(struct tevent_req *req)
+{
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(
+ req, struct smbd_smb2_query_directory_state);
+ struct smb_filename *smb_fname = NULL; /* relative to fsp !! */
+ int off = state->out_output_buffer.length;
+ int space_remaining = state->in_output_buffer_length - off;
+ struct file_id file_id;
+ NTSTATUS status;
+ bool get_dosmode = !state->async_dosmode;
+ bool stop = false;
+
+ SMB_ASSERT(space_remaining >= 0);
+
+ status = smbd_dirptr_lanman2_entry(state,
+ state->dirfsp->conn,
+ state->dirfsp->dptr,
+ state->smbreq->flags2,
+ state->in_file_name,
+ state->dirtype,
+ state->info_level,
+ false, /* requires_resume_key */
+ state->dont_descend,
+ state->ask_sharemode,
+ get_dosmode,
+ 8, /* align to 8 bytes */
+ false, /* no padding */
+ &state->pdata,
+ state->base_data,
+ state->end_data,
+ space_remaining,
+ &smb_fname,
+ &state->last_entry_off,
+ NULL,
+ &file_id);
+
+ off = (int)PTR_DIFF(state->pdata, state->base_data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ILLEGAL_CHARACTER)) {
+ /*
+ * Bad character conversion on name. Ignore this
+ * entry.
+ */
+ return false;
+ }
+
+ if (state->num > 0) {
+ goto last_entry_done;
+ }
+
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ tevent_req_nterror(req, NT_STATUS_INFO_LENGTH_MISMATCH);
+ return true;
+ }
+
+ tevent_req_nterror(req, state->empty_status);
+ return true;
+ }
+
+ if (state->async_ask_sharemode &&
+ !S_ISDIR(smb_fname->st.st_ex_mode))
+ {
+ struct tevent_req *subreq = NULL;
+ char *buf = state->base_data + state->last_entry_off;
+
+ subreq = fetch_write_time_send(state,
+ state->ev,
+ state->dirfsp->conn,
+ file_id,
+ state->info_level,
+ buf,
+ &stop);
+ if (tevent_req_nomem(subreq, req)) {
+ return true;
+ }
+ tevent_req_set_callback(
+ subreq,
+ smb2_query_directory_fetch_write_time_done,
+ req);
+ state->async_sharemode_count++;
+ }
+
+ if (state->async_dosmode) {
+ struct tevent_req *subreq = NULL;
+ uint8_t *buf = NULL;
+ size_t outstanding_aio;
+
+ buf = (uint8_t *)state->base_data + state->last_entry_off;
+
+ subreq = fetch_dos_mode_send(state,
+ state->ev,
+ state->dirfsp,
+ &smb_fname,
+ state->info_level,
+ buf);
+ if (tevent_req_nomem(subreq, req)) {
+ return true;
+ }
+ tevent_req_set_callback(subreq,
+ smb2_query_directory_dos_mode_done,
+ req);
+
+ state->async_dosmode_active++;
+
+ outstanding_aio = pthreadpool_tevent_queued_jobs(
+ state->dirfsp->conn->sconn->pool);
+
+ if (outstanding_aio > state->max_async_dosmode_active) {
+ stop = true;
+ }
+ }
+
+ TALLOC_FREE(smb_fname);
+
+ state->num++;
+ state->out_output_buffer.length = off;
+
+ if (!state->done && state->num < state->max_count) {
+ return stop;
+ }
+
+last_entry_done:
+ SIVAL(state->out_output_buffer.data, state->last_entry_off, 0);
+
+ state->done = true;
+
+ if (state->async_sharemode_count > 0) {
+ DBG_DEBUG("Stopping after %"PRIu64" async mtime "
+ "updates\n", state->async_sharemode_count);
+ return true;
+ }
+
+ if (state->async_dosmode_active > 0) {
+ return true;
+ }
+
+ if (state->find_async_delay_usec > 0) {
+ struct timeval tv;
+ struct tevent_req *subreq = NULL;
+
+ /*
+ * Should we only set async_internal
+ * if we're not the last request in
+ * a compound chain?
+ */
+ smb2_request_set_async_internal(state->smb2req, true);
+
+ tv = timeval_current_ofs(0, state->find_async_delay_usec);
+
+ subreq = tevent_wakeup_send(state, state->ev, tv);
+ if (tevent_req_nomem(subreq, req)) {
+ return true;
+ }
+ tevent_req_set_callback(subreq,
+ smb2_query_directory_waited,
+ req);
+ return true;
+ }
+
+ tevent_req_done(req);
+ return true;
+}
+
+static void smb2_query_directory_check_next_entry(struct tevent_req *req);
+
+static void smb2_query_directory_fetch_write_time_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(
+ req, struct smbd_smb2_query_directory_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dirfsp);
+ SMB_ASSERT(ok);
+
+ state->async_sharemode_count--;
+
+ status = fetch_write_time_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smb2_query_directory_check_next_entry(req);
+ return;
+}
+
+static void smb2_query_directory_dos_mode_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_query_directory_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_query_directory_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dirfsp);
+ SMB_ASSERT(ok);
+
+ status = fetch_dos_mode_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->async_dosmode_active--;
+
+ smb2_query_directory_check_next_entry(req);
+ return;
+}
+
+static void smb2_query_directory_check_next_entry(struct tevent_req *req)
+{
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(
+ req, struct smbd_smb2_query_directory_state);
+ bool stop = false;
+
+ if (!state->done) {
+ while (!stop) {
+ stop = smb2_query_directory_next_entry(req);
+ }
+ return;
+ }
+
+ if (state->async_sharemode_count > 0 ||
+ state->async_dosmode_active > 0)
+ {
+ return;
+ }
+
+ if (state->find_async_delay_usec > 0) {
+ struct timeval tv;
+ struct tevent_req *subreq = NULL;
+
+ tv = timeval_current_ofs(0, state->find_async_delay_usec);
+
+ subreq = tevent_wakeup_send(state, state->ev, tv);
+ if (tevent_req_nomem(subreq, req)) {
+ tevent_req_post(req, state->ev);
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smb2_query_directory_waited,
+ req);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static void smb2_query_directory_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_query_directory_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_output_buffer)
+{
+ NTSTATUS status;
+ struct smbd_smb2_query_directory_state *state = tevent_req_data(req,
+ struct smbd_smb2_query_directory_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_output_buffer = state->out_output_buffer;
+ talloc_steal(mem_ctx, out_output_buffer->data);
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct fetch_write_time_state {
+ connection_struct *conn;
+ struct file_id id;
+ int info_level;
+ char *entry_marshall_buf;
+};
+
+static void fetch_write_time_done(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_write_time_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ connection_struct *conn,
+ struct file_id id,
+ int info_level,
+ char *entry_marshall_buf,
+ bool *stop)
+{
+ struct tevent_req *req = NULL;
+ struct fetch_write_time_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ bool req_queued;
+
+ *stop = false;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_write_time_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ *state = (struct fetch_write_time_state) {
+ .conn = conn,
+ .id = id,
+ .info_level = info_level,
+ .entry_marshall_buf = entry_marshall_buf,
+ };
+
+ subreq = fetch_share_mode_send(state, ev, id, &req_queued);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_write_time_done, req);
+
+ if (req_queued) {
+ *stop = true;
+ }
+ return req;
+}
+
+static void fetch_write_time_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct fetch_write_time_state *state = tevent_req_data(
+ req, struct fetch_write_time_state);
+ struct timespec write_time;
+ struct share_mode_lock *lck = NULL;
+ NTSTATUS status;
+ size_t off;
+
+ status = fetch_share_mode_recv(subreq, state, &lck);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ write_time = get_share_mode_write_time(lck);
+ TALLOC_FREE(lck);
+
+ if (is_omit_timespec(&write_time)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ switch (state->info_level) {
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ off = 24;
+ break;
+
+ default:
+ DBG_ERR("Unsupported info_level [%d]\n", state->info_level);
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+ put_long_date_full_timespec(state->conn->ts_res,
+ state->entry_marshall_buf + off,
+ &write_time);
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS fetch_write_time_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct fetch_dos_mode_state {
+ struct files_struct *dir_fsp;
+ struct smb_filename *smb_fname;
+ uint32_t info_level;
+ uint8_t *entry_marshall_buf;
+};
+
+static void fetch_dos_mode_done(struct tevent_req *subreq);
+
+static struct tevent_req *fetch_dos_mode_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *dir_fsp,
+ struct smb_filename **smb_fname,
+ uint32_t info_level,
+ uint8_t *entry_marshall_buf)
+{
+ struct tevent_req *req = NULL;
+ struct fetch_dos_mode_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct fetch_dos_mode_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct fetch_dos_mode_state) {
+ .dir_fsp = dir_fsp,
+ .info_level = info_level,
+ .entry_marshall_buf = entry_marshall_buf,
+ };
+
+ state->smb_fname = talloc_move(state, smb_fname);
+
+ subreq = dos_mode_at_send(state, ev, dir_fsp, state->smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, fetch_dos_mode_done, req);
+
+ return req;
+}
+
+static void fetch_dos_mode_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct fetch_dos_mode_state *state =
+ tevent_req_data(req,
+ struct fetch_dos_mode_state);
+ uint32_t dfs_dosmode;
+ uint32_t dosmode;
+ struct timespec btime_ts = {0};
+ off_t dosmode_off;
+ off_t btime_off;
+ NTSTATUS status;
+
+ status = dos_mode_at_recv(subreq, &dosmode);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ switch (state->info_level) {
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ btime_off = 8;
+ dosmode_off = 56;
+ break;
+
+ default:
+ DBG_ERR("Unsupported info_level [%u]\n", state->info_level);
+ tevent_req_nterror(req, NT_STATUS_INVALID_LEVEL);
+ return;
+ }
+
+
+ dfs_dosmode = IVAL(state->entry_marshall_buf, dosmode_off);
+ if (dfs_dosmode == 0) {
+ /*
+ * DOS mode for a DFS link, only overwrite if still set to 0 and
+ * not already populated by the lower layer for a DFS link in
+ * smbd_dirptr_lanman2_mode_fn().
+ */
+ SIVAL(state->entry_marshall_buf, dosmode_off, dosmode);
+ }
+
+ btime_ts = get_create_timespec(state->dir_fsp->conn,
+ NULL,
+ state->smb_fname);
+ if (lp_dos_filetime_resolution(SNUM(state->dir_fsp->conn))) {
+ dos_filetime_timespec(&btime_ts);
+ }
+
+ put_long_date_full_timespec(state->dir_fsp->conn->ts_res,
+ (char *)state->entry_marshall_buf + btime_off,
+ &btime_ts);
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS fetch_dos_mode_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_read.c b/source3/smbd/smb2_read.c
new file mode 100644
index 0000000..bac78e4
--- /dev/null
+++ b/source3/smbd/smb2_read.c
@@ -0,0 +1,685 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "lib/util/sys_rw_data.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_flags,
+ uint32_t in_length,
+ uint64_t in_offset,
+ uint32_t in_minimum,
+ uint32_t in_remaining);
+static NTSTATUS smbd_smb2_read_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_data,
+ uint32_t *out_remaining);
+
+static void smbd_smb2_request_read_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_read(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_flags;
+ uint32_t in_length;
+ uint64_t in_offset;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint32_t in_minimum_count;
+ uint32_t in_remaining_bytes;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x31);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ if (xconn->protocol >= PROTOCOL_SMB3_02) {
+ in_flags = CVAL(inbody, 0x03);
+ } else {
+ in_flags = 0;
+ }
+ in_length = IVAL(inbody, 0x04);
+ in_offset = BVAL(inbody, 0x08);
+ in_file_id_persistent = BVAL(inbody, 0x10);
+ in_file_id_volatile = BVAL(inbody, 0x18);
+ in_minimum_count = IVAL(inbody, 0x20);
+ in_remaining_bytes = IVAL(inbody, 0x28);
+
+ /* check the max read size */
+ if (in_length > xconn->smb2.server.max_read) {
+ DEBUG(2,("smbd_smb2_request_process_read: "
+ "client ignored max read: %s: 0x%08X: 0x%08X\n",
+ __location__, in_length, xconn->smb2.server.max_read));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req, in_length);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_read_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_flags,
+ in_length,
+ in_offset,
+ in_minimum_count,
+ in_remaining_bytes);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_read_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_read_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ uint16_t body_size;
+ uint8_t body_padding = req->xconn->smb2.smbtorture.read_body_padding;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint8_t out_data_offset;
+ DATA_BLOB out_data_buffer = data_blob_null;
+ uint32_t out_data_remaining = 0;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_read_recv(subreq,
+ req,
+ &out_data_buffer,
+ &out_data_remaining);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ /*
+ * Only FSCTL_SMBTORTURE_GLOBAL_READ_RESPONSE_BODY_PADDING8
+ * sets body_padding to a value different from 0.
+ */
+ body_size = 0x10 + body_padding;
+ out_data_offset = SMB2_HDR_BODY + body_size;
+
+ outbody = smbd_smb2_generate_outbody(req, body_size);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x10 + 1); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_data_offset); /* data offset */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ out_data_buffer.length); /* data length */
+ SIVAL(outbody.data, 0x08,
+ out_data_remaining); /* data remaining */
+ SIVAL(outbody.data, 0x0C, 0); /* reserved */
+ if (body_padding != 0) {
+ memset(outbody.data + 0x10, 0, body_padding);
+ }
+
+ outdyn = out_data_buffer;
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_read_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ files_struct *fsp;
+ uint8_t in_flags;
+ uint32_t in_length;
+ uint64_t in_offset;
+ uint32_t in_minimum;
+ DATA_BLOB out_headers;
+ uint8_t _out_hdr_buf[NBT_HDR_SIZE + SMB2_HDR_BODY + 0x10];
+ DATA_BLOB out_data;
+ uint32_t out_remaining;
+};
+
+static int smb2_smb2_read_state_deny_destructor(struct smbd_smb2_read_state *state)
+{
+ return -1;
+}
+
+/* struct smbd_smb2_read_state destructor. Send the SMB2_READ data. */
+static int smb2_sendfile_send_data(struct smbd_smb2_read_state *state)
+{
+ struct lock_struct lock;
+ uint32_t in_length = state->in_length;
+ uint64_t in_offset = state->in_offset;
+ files_struct *fsp = state->fsp;
+ const DATA_BLOB *hdr = state->smb2req->queue_entry.sendfile_header;
+ NTSTATUS *pstatus = state->smb2req->queue_entry.sendfile_status;
+ struct smbXsrv_connection *xconn = state->smb2req->xconn;
+ ssize_t nread;
+ ssize_t ret;
+ int saved_errno;
+
+ nread = SMB_VFS_SENDFILE(xconn->transport.sock,
+ fsp,
+ hdr,
+ in_offset,
+ in_length);
+ DEBUG(10,("smb2_sendfile_send_data: SMB_VFS_SENDFILE returned %d on file %s\n",
+ (int)nread,
+ fsp_str_dbg(fsp) ));
+
+ if (nread == -1) {
+ saved_errno = errno;
+
+ /*
+ * Returning ENOSYS means no data at all was sent.
+ Do this as a normal read. */
+ if (errno == ENOSYS) {
+ goto normal_read;
+ }
+
+ if (errno == ENOTSUP) {
+ set_use_sendfile(SNUM(fsp->conn), false);
+ DBG_WARNING("Disabling sendfile use as sendfile is "
+ "not supported by the system\n");
+ goto normal_read;
+ }
+
+ if (errno == EINTR) {
+ /*
+ * Special hack for broken Linux with no working sendfile. If we
+ * return EINTR we sent the header but not the rest of the data.
+ * Fake this up by doing read/write calls.
+ */
+ set_use_sendfile(SNUM(fsp->conn), false);
+ nread = fake_sendfile(xconn, fsp, in_offset, in_length);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("smb2_sendfile_send_data: fake_sendfile "
+ "failed for file %s (%s) for client %s. "
+ "Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+ goto out;
+ }
+
+ DEBUG(0,("smb2_sendfile_send_data: sendfile failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ } else if (nread == 0) {
+ /*
+ * Some sendfile implementations return 0 to indicate
+ * that there was a short read, but nothing was
+ * actually written to the socket. In this case,
+ * fallback to the normal read path so the header gets
+ * the correct byte count.
+ */
+ DEBUG(3, ("send_file_readX: sendfile sent zero bytes "
+ "falling back to the normal read: %s\n",
+ fsp_str_dbg(fsp)));
+ goto normal_read;
+ }
+
+ /*
+ * We got a short read
+ */
+ goto out;
+
+normal_read:
+ /* Send out the header. */
+ ret = write_data(xconn->transport.sock,
+ (const char *)hdr->data, hdr->length);
+ if (ret != hdr->length) {
+ saved_errno = errno;
+ DEBUG(0,("smb2_sendfile_send_data: write_data failed for file "
+ "%s (%s) for client %s. Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+ nread = fake_sendfile(xconn, fsp, in_offset, in_length);
+ if (nread == -1) {
+ saved_errno = errno;
+ DEBUG(0,("smb2_sendfile_send_data: fake_sendfile "
+ "failed for file %s (%s) for client %s. "
+ "Terminating\n",
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+
+ out:
+
+ if (nread < in_length) {
+ ret = sendfile_short_send(xconn, fsp, nread,
+ hdr->length, in_length);
+ if (ret == -1) {
+ saved_errno = errno;
+ DEBUG(0,("%s: sendfile_short_send "
+ "failed for file %s (%s) for client %s. "
+ "Terminating\n",
+ __func__,
+ fsp_str_dbg(fsp), strerror(saved_errno),
+ smbXsrv_connection_dbg(xconn)));
+ *pstatus = map_nt_error_from_unix_common(saved_errno);
+ return 0;
+ }
+ }
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ in_length,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ *pstatus = NT_STATUS_OK;
+ return 0;
+}
+
+static NTSTATUS schedule_smb2_sendfile_read(struct smbd_smb2_request *smb2req,
+ struct smbd_smb2_read_state *state)
+{
+ files_struct *fsp = state->fsp;
+
+ /*
+ * We cannot use sendfile if...
+ * We were not configured to do so OR
+ * Signing is active OR
+ * This is a compound SMB2 operation OR
+ * fsp is a STREAM file OR
+ * It's not a regular file OR
+ * Requested offset is greater than file size OR
+ * there's not enough data in the file.
+ * Phew :-). Luckily this means most
+ * reads on most normal files. JRA.
+ */
+
+ if (!lp__use_sendfile(SNUM(fsp->conn)) ||
+ smb2req->do_signing ||
+ smb2req->do_encryption ||
+ smbd_smb2_is_compound(smb2req) ||
+ fsp_is_alternate_stream(fsp) ||
+ (!S_ISREG(fsp->fsp_name->st.st_ex_mode)) ||
+ (state->in_offset >= fsp->fsp_name->st.st_ex_size) ||
+ (fsp->fsp_name->st.st_ex_size < state->in_offset + state->in_length))
+ {
+ return NT_STATUS_RETRY;
+ }
+
+ /* We've already checked there's this amount of data
+ to read. */
+ state->out_data.length = state->in_length;
+ state->out_remaining = 0;
+
+ state->out_headers = data_blob_const(state->_out_hdr_buf,
+ sizeof(state->_out_hdr_buf));
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_read_pipe_done(struct tevent_req *subreq);
+
+/*******************************************************************
+ Common read complete processing function for both synchronous and
+ asynchronous reads.
+*******************************************************************/
+
+NTSTATUS smb2_read_complete(struct tevent_req *req, ssize_t nread, int err)
+{
+ struct smbd_smb2_read_state *state = tevent_req_data(req,
+ struct smbd_smb2_read_state);
+ files_struct *fsp = state->fsp;
+
+ if (nread < 0) {
+ NTSTATUS status = map_nt_error_from_unix(err);
+
+ DEBUG( 3,( "smb2_read_complete: file %s nread = %d. "
+ "Error = %s (NTSTATUS %s)\n",
+ fsp_str_dbg(fsp),
+ (int)nread,
+ strerror(err),
+ nt_errstr(status)));
+
+ return status;
+ }
+ if (nread == 0 && state->in_length != 0) {
+ DEBUG(5,("smb2_read_complete: read_file[%s] end of file\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ if (nread < state->in_minimum) {
+ DEBUG(5,("smb2_read_complete: read_file[%s] read less %d than "
+ "minimum requested %u. Returning end of file\n",
+ fsp_str_dbg(fsp),
+ (int)nread,
+ (unsigned int)state->in_minimum));
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ DEBUG(3,("smbd_smb2_read: %s, file %s, length=%lu offset=%lu read=%lu\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ (unsigned long)state->in_length,
+ (unsigned long)state->in_offset,
+ (unsigned long)nread));
+
+ state->out_data.length = nread;
+ state->out_remaining = 0;
+
+ return NT_STATUS_OK;
+}
+
+static bool smbd_smb2_read_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_read_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_read_state);
+
+ return cancel_smb2_aio(state->smbreq);
+}
+
+static struct tevent_req *smbd_smb2_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_flags,
+ uint32_t in_length,
+ uint64_t in_offset,
+ uint32_t in_minimum,
+ uint32_t in_remaining)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_read_state *state = NULL;
+ struct smb_request *smbreq = NULL;
+ connection_struct *conn = smb2req->tcon->compat;
+ ssize_t nread = -1;
+ struct lock_struct lock;
+ int saved_errno;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_read_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ state->in_flags = in_flags;
+ state->in_length = in_length;
+ state->in_offset = in_offset;
+ state->in_minimum = in_minimum;
+ state->out_data = data_blob_null;
+ state->out_remaining = 0;
+
+ DEBUG(10,("smbd_smb2_read: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->smbreq = smbreq;
+
+ if (fsp->fsp_flags.is_directory) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_DEVICE_REQUEST);
+ return tevent_req_post(req, ev);
+ }
+
+ state->fsp = fsp;
+
+ if (IS_IPC(smbreq->conn)) {
+ struct tevent_req *subreq = NULL;
+ bool ok;
+
+ state->out_data = data_blob_talloc(state, NULL, in_length);
+ if (in_length > 0 && tevent_req_nomem(state->out_data.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (!fsp_is_np(fsp)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = np_read_send(state, ev,
+ fsp->fake_file_handle,
+ state->out_data.data,
+ state->out_data.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_read_pipe_done,
+ req);
+
+ /*
+ * Make sure we mark the fsp as having outstanding async
+ * activity so we don't crash on shutdown close.
+ */
+
+ ok = aio_add_req_to_fsp(fsp, req);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+ }
+
+ if (!CHECK_READ_SMB2(fsp)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = schedule_smb2_aio_read(fsp->conn,
+ smbreq,
+ fsp,
+ state,
+ &state->out_data,
+ (off_t)in_offset,
+ (size_t)in_length);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * Doing an async read, allow this
+ * request to be canceled
+ */
+ tevent_req_set_cancel_fn(req, smbd_smb2_read_cancel);
+ return req;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error in setting up aio. Fail. */
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Fallback to synchronous. */
+
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ in_length,
+ READ_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Try sendfile in preference. */
+ status = schedule_smb2_sendfile_read(smb2req, state);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ } else {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ /* Ok, read into memory. Allocate the out buffer. */
+ state->out_data = data_blob_talloc(state, NULL, in_length);
+ if (in_length > 0 && tevent_req_nomem(state->out_data.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ nread = read_file(fsp,
+ (char *)state->out_data.data,
+ in_offset,
+ in_length);
+
+ saved_errno = errno;
+
+ DEBUG(10,("smbd_smb2_read: file %s, %s, offset=%llu "
+ "len=%llu returned %lld\n",
+ fsp_str_dbg(fsp),
+ fsp_fnum_dbg(fsp),
+ (unsigned long long)in_offset,
+ (unsigned long long)in_length,
+ (long long)nread));
+
+ status = smb2_read_complete(req, nread, saved_errno);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ } else {
+ /* Success. */
+ tevent_req_done(req);
+ }
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_read_pipe_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_read_state *state = tevent_req_data(req,
+ struct smbd_smb2_read_state);
+ NTSTATUS status;
+ ssize_t nread = -1;
+ bool is_data_outstanding;
+
+ status = np_read_recv(subreq, &nread, &is_data_outstanding);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (nread == 0 && state->out_data.length != 0) {
+ tevent_req_nterror(req, NT_STATUS_END_OF_FILE);
+ return;
+ }
+
+ state->out_data.length = nread;
+ state->out_remaining = 0;
+
+ /*
+ * TODO: add STATUS_BUFFER_OVERFLOW handling, once we also
+ * handle it in SMB1 pipe_read_andx_done().
+ */
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_read_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_data,
+ uint32_t *out_remaining)
+{
+ NTSTATUS status;
+ struct smbd_smb2_read_state *state = tevent_req_data(req,
+ struct smbd_smb2_read_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_data = state->out_data;
+ talloc_steal(mem_ctx, out_data->data);
+ *out_remaining = state->out_remaining;
+
+ if (state->out_headers.length > 0) {
+ talloc_steal(mem_ctx, state);
+ talloc_set_destructor(state, smb2_smb2_read_state_deny_destructor);
+ tevent_req_received(req);
+ state->smb2req->queue_entry.sendfile_header = &state->out_headers;
+ state->smb2req->queue_entry.sendfile_body_size = state->in_length;
+ talloc_set_destructor(state, smb2_sendfile_send_data);
+ } else {
+ tevent_req_received(req);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_reply.c b/source3/smbd/smb2_reply.c
new file mode 100644
index 0000000..ab12399
--- /dev/null
+++ b/source3/smbd/smb2_reply.c
@@ -0,0 +1,2195 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB reply routines
+ Copyright (C) Andrew Tridgell 1992-1998
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jeremy Allison 1992-2007.
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ This file handles most of the reply_ calls that the server
+ makes to handle specific protocols
+*/
+
+#include "includes.h"
+#include "libsmb/namequery.h"
+#include "system/filesys.h"
+#include "printing.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "fake_file.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "libcli/security/security.h"
+#include "libsmb/nmblib.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "../lib/tsocket/tsocket.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/smb/smb_signing.h"
+#include "lib/util/sys_rw_data.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/printing/rap_jobid.h"
+#include "source3/lib/substitute.h"
+#include "source3/smbd/dir.h"
+
+/****************************************************************************
+ Ensure we check the path in *exactly* the same way as W2K for a findfirst/findnext
+ path or anything including wildcards.
+ We're assuming here that '/' is not the second byte in any multibyte char
+ set (a safe assumption). '\\' *may* be the second byte in a multibyte char
+ set.
+****************************************************************************/
+
+/* Custom version for processing POSIX paths. */
+#define IS_PATH_SEP(c,posix_only) ((c) == '/' || (!(posix_only) && (c) == '\\'))
+
+NTSTATUS check_path_syntax(char *path, bool posix_path)
+{
+ char *d = path;
+ const char *s = path;
+ NTSTATUS ret = NT_STATUS_OK;
+ bool start_of_name_component = True;
+ bool stream_started = false;
+ bool last_component_contains_wcard = false;
+
+ while (*s) {
+ if (stream_started) {
+ switch (*s) {
+ case '/':
+ case '\\':
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ case ':':
+ if (s[1] == '\0') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ if (strchr_m(&s[1], ':')) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ break;
+ }
+ }
+
+ if ((*s == ':') && !posix_path && !stream_started) {
+ if (last_component_contains_wcard) {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ /* Stream names allow more characters than file names.
+ We're overloading posix_path here to allow a wider
+ range of characters. If stream_started is true this
+ is still a Windows path even if posix_path is true.
+ JRA.
+ */
+ stream_started = true;
+ start_of_name_component = false;
+ posix_path = true;
+
+ if (s[1] == '\0') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ }
+
+ if (!stream_started && IS_PATH_SEP(*s,posix_path)) {
+ /*
+ * Safe to assume is not the second part of a mb char
+ * as this is handled below.
+ */
+ /* Eat multiple '/' or '\\' */
+ while (IS_PATH_SEP(*s,posix_path)) {
+ s++;
+ }
+ if ((d != path) && (*s != '\0')) {
+ /* We only care about non-leading or trailing '/' or '\\' */
+ *d++ = '/';
+ }
+
+ start_of_name_component = True;
+ /* New component. */
+ last_component_contains_wcard = false;
+ continue;
+ }
+
+ if (start_of_name_component) {
+ if ((s[0] == '.') && (s[1] == '.') && (IS_PATH_SEP(s[2],posix_path) || s[2] == '\0')) {
+ /* Uh oh - "/../" or "\\..\\" or "/..\0" or "\\..\0" ! */
+
+ /*
+ * No mb char starts with '.' so we're safe checking the directory separator here.
+ */
+
+ /* If we just added a '/' - delete it */
+ if ((d > path) && (*(d-1) == '/')) {
+ *(d-1) = '\0';
+ d--;
+ }
+
+ /* Are we at the start ? Can't go back further if so. */
+ if (d <= path) {
+ ret = NT_STATUS_OBJECT_PATH_SYNTAX_BAD;
+ break;
+ }
+ /* Go back one level... */
+ /* We know this is safe as '/' cannot be part of a mb sequence. */
+ /* NOTE - if this assumption is invalid we are not in good shape... */
+ /* Decrement d first as d points to the *next* char to write into. */
+ for (d--; d > path; d--) {
+ if (*d == '/')
+ break;
+ }
+ s += 2; /* Else go past the .. */
+ /* We're still at the start of a name component, just the previous one. */
+ continue;
+
+ } else if ((s[0] == '.') && ((s[1] == '\0') || IS_PATH_SEP(s[1],posix_path))) {
+ if (posix_path) {
+ /* Eat the '.' */
+ s++;
+ continue;
+ }
+ }
+
+ }
+
+ if (!(*s & 0x80)) {
+ if (!posix_path) {
+ if (*s <= 0x1f || *s == '|') {
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ switch (*s) {
+ case '*':
+ case '?':
+ case '<':
+ case '>':
+ case '"':
+ last_component_contains_wcard = true;
+ break;
+ default:
+ break;
+ }
+ }
+ *d++ = *s++;
+ } else {
+ size_t ch_size;
+ /* Get the size of the next MB character. */
+ next_codepoint(s,&ch_size);
+ switch(ch_size) {
+ case 5:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 4:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 3:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 2:
+ *d++ = *s++;
+ FALL_THROUGH;
+ case 1:
+ *d++ = *s++;
+ break;
+ default:
+ DBG_ERR("character length assumptions invalid !\n");
+ *d = '\0';
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+ start_of_name_component = False;
+ }
+
+ *d = '\0';
+
+ return ret;
+}
+
+/****************************************************************************
+ SMB2-only code to strip an MSDFS prefix from an incoming pathname.
+****************************************************************************/
+
+NTSTATUS smb2_strip_dfs_path(const char *in_path, const char **out_path)
+{
+ const char *path = in_path;
+
+ /* Match the Windows 2022 behavior for an empty DFS pathname. */
+ if (*path == '\0') {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ /* Strip any leading '\\' characters - MacOSX client behavior. */
+ while (*path == '\\') {
+ path++;
+ }
+ /* We should now be pointing at the server name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto out;
+ }
+ if (*path == '\\') {
+ /* End of server name. Go past and break. */
+ path++;
+ break;
+ }
+ path++; /* Continue looking for end of server name or string. */
+ }
+
+ /* We should now be pointing at the share name. Go past it. */
+ for (;;) {
+ if (*path == '\0') {
+ /* End of complete path. Exit OK. */
+ goto out;
+ }
+ if (*path == '\\') {
+ /* End of share name. Go past and break. */
+ path++;
+ break;
+ }
+ if (*path == ':') {
+ /* Only invalid character in sharename. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ path++; /* Continue looking for end of share name or string. */
+ }
+
+ /* path now points at the start of the real filename (if any). */
+
+ out:
+ /* We have stripped the DFS path prefix (if any). */
+ *out_path = path;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Pull a string and check the path allowing a wildcard - provide for error return.
+ Passes in posix flag.
+****************************************************************************/
+
+static size_t srvstr_get_path_internal(TALLOC_CTX *ctx,
+ const char *base_ptr,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ bool posix_pathnames,
+ NTSTATUS *err)
+{
+ size_t ret;
+ char *dst = NULL;
+
+ *pp_dest = NULL;
+
+ ret = srvstr_pull_talloc(ctx, base_ptr, smb_flags2, pp_dest, src,
+ src_len, flags);
+
+ if (!*pp_dest) {
+ *err = NT_STATUS_INVALID_PARAMETER;
+ return ret;
+ }
+
+ dst = *pp_dest;
+
+ if (smb_flags2 & FLAGS2_DFS_PATHNAMES) {
+ /*
+ * A valid DFS path looks either like
+ * /server/share
+ * \server\share
+ * (there may be more components after).
+ * Either way it must have at least two separators.
+ *
+ * Ensure we end up as /server/share
+ * so we don't need to special case
+ * separator characters elsewhere in
+ * the code.
+ */
+ char *server = NULL;
+ char *share = NULL;
+ char *remaining_path = NULL;
+ char path_sep = 0;
+ char *p = NULL;
+
+ if (posix_pathnames && (dst[0] == '/')) {
+ path_sep = dst[0];
+ } else if (dst[0] == '\\') {
+ path_sep = dst[0];
+ }
+
+ if (path_sep == 0) {
+ goto local_path;
+ }
+ /*
+ * May be a DFS path.
+ * We need some heuristics here,
+ * as clients differ on what constitutes
+ * a well-formed DFS path. If the path
+ * appears malformed, just fall back to
+ * processing as a local path.
+ */
+ server = dst;
+
+ /*
+ * Cosmetic fix for Linux-only DFS clients.
+ * The Linux kernel SMB1 client has a bug - it sends
+ * DFS pathnames as:
+ *
+ * \\server\share\path
+ *
+ * Causing us to mis-parse server,share,remaining_path here
+ * and jump into 'goto local_path' at 'share\path' instead
+ * of 'path'.
+ *
+ * This doesn't cause an error as the limits on share names
+ * are similar to those on pathnames.
+ *
+ * parse_dfs_path() which we call before filename parsing
+ * copes with this by calling trim_char on the leading '\'
+ * characters before processing.
+ * Do the same here so logging of pathnames looks better.
+ */
+ if (server[1] == path_sep) {
+ trim_char(&server[1], path_sep, '\0');
+ }
+
+ /*
+ * Look to see if we also have /share following.
+ */
+ share = strchr(server+1, path_sep);
+ if (share == NULL) {
+ goto local_path;
+ }
+ /*
+ * Ensure the server name does not contain
+ * any possible path components by converting
+ * them to _'s.
+ */
+ for (p = server + 1; p < share; p++) {
+ if (*p == '/' || *p == '\\') {
+ *p = '_';
+ }
+ }
+ /*
+ * It's a well formed DFS path with
+ * at least server and share components.
+ * Replace the slashes with '/' and
+ * pass the remainder to local_path.
+ */
+ *server = '/';
+ *share = '/';
+ /*
+ * Skip past share so we don't pass the
+ * sharename into check_path_syntax().
+ */
+ remaining_path = strchr(share+1, path_sep);
+ if (remaining_path == NULL) {
+ /*
+ * Ensure the share name does not contain
+ * any possible path components by converting
+ * them to _'s.
+ */
+ for (p = share + 1; *p; p++) {
+ if (*p == '/' || *p == '\\') {
+ *p = '_';
+ }
+ }
+ /*
+ * If no remaining path this was
+ * a bare /server/share path. Just return.
+ */
+ *err = NT_STATUS_OK;
+ return ret;
+ }
+ /*
+ * Ensure the share name does not contain
+ * any possible path components by converting
+ * them to _'s.
+ */
+ for (p = share + 1; p < remaining_path; p++) {
+ if (*p == '/' || *p == '\\') {
+ *p = '_';
+ }
+ }
+ *remaining_path = '/';
+ dst = remaining_path + 1;
+ /* dst now points at any following components. */
+ }
+
+ local_path:
+
+ *err = check_path_syntax(dst, posix_pathnames);
+
+ return ret;
+}
+
+/****************************************************************************
+ Pull a string and check the path - provide for error return.
+****************************************************************************/
+
+size_t srvstr_get_path(TALLOC_CTX *ctx,
+ const char *base_ptr,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err)
+{
+ return srvstr_get_path_internal(ctx,
+ base_ptr,
+ smb_flags2,
+ pp_dest,
+ src,
+ src_len,
+ flags,
+ false,
+ err);
+}
+
+/****************************************************************************
+ Pull a string and check the path - provide for error return.
+ posix_pathnames version.
+****************************************************************************/
+
+size_t srvstr_get_path_posix(TALLOC_CTX *ctx,
+ const char *base_ptr,
+ uint16_t smb_flags2,
+ char **pp_dest,
+ const char *src,
+ size_t src_len,
+ int flags,
+ NTSTATUS *err)
+{
+ return srvstr_get_path_internal(ctx,
+ base_ptr,
+ smb_flags2,
+ pp_dest,
+ src,
+ src_len,
+ flags,
+ true,
+ err);
+}
+
+
+size_t srvstr_get_path_req(TALLOC_CTX *mem_ctx, struct smb_request *req,
+ char **pp_dest, const char *src, int flags,
+ NTSTATUS *err)
+{
+ ssize_t bufrem = smbreq_bufrem(req, src);
+
+ if (bufrem == 0) {
+ *err = NT_STATUS_INVALID_PARAMETER;
+ return 0;
+ }
+
+ if (req->posix_pathnames) {
+ return srvstr_get_path_internal(mem_ctx,
+ (const char *)req->inbuf,
+ req->flags2,
+ pp_dest,
+ src,
+ bufrem,
+ flags,
+ true,
+ err);
+ } else {
+ return srvstr_get_path_internal(mem_ctx,
+ (const char *)req->inbuf,
+ req->flags2,
+ pp_dest,
+ src,
+ bufrem,
+ flags,
+ false,
+ err);
+ }
+}
+
+/**
+ * pull a string from the smb_buf part of a packet. In this case the
+ * string can either be null terminated or it can be terminated by the
+ * end of the smbbuf area
+ */
+size_t srvstr_pull_req_talloc(TALLOC_CTX *ctx, struct smb_request *req,
+ char **dest, const uint8_t *src, int flags)
+{
+ ssize_t bufrem = smbreq_bufrem(req, src);
+
+ if (bufrem == 0) {
+ *dest = NULL;
+ return 0;
+ }
+
+ return pull_string_talloc(ctx, req->inbuf, req->flags2, dest, src,
+ bufrem, flags);
+}
+
+/****************************************************************************
+ Check if we have a correct fsp pointing to a quota fake file. Replacement for
+ the CHECK_NTQUOTA_HANDLE_OK macro.
+****************************************************************************/
+
+bool check_fsp_ntquota_handle(connection_struct *conn, struct smb_request *req,
+ files_struct *fsp)
+{
+ if ((fsp == NULL) || (conn == NULL)) {
+ return false;
+ }
+
+ if ((conn != fsp->conn) || (req->vuid != fsp->vuid)) {
+ return false;
+ }
+
+ if (fsp->fsp_flags.is_directory) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle == NULL) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle->type != FAKE_FILE_TYPE_QUOTA) {
+ return false;
+ }
+
+ if (fsp->fake_file_handle->private_data == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+/****************************************************************************
+ Return the port number we've bound to on a socket.
+****************************************************************************/
+
+static int get_socket_port(int fd)
+{
+ struct samba_sockaddr saddr = {
+ .sa_socklen = sizeof(struct sockaddr_storage),
+ };
+
+ if (fd == -1) {
+ return -1;
+ }
+
+ if (getsockname(fd, &saddr.u.sa, &saddr.sa_socklen) < 0) {
+ int level = (errno == ENOTCONN) ? 2 : 0;
+ DEBUG(level, ("getsockname failed. Error was %s\n",
+ strerror(errno)));
+ return -1;
+ }
+
+#if defined(HAVE_IPV6)
+ if (saddr.u.sa.sa_family == AF_INET6) {
+ return ntohs(saddr.u.in6.sin6_port);
+ }
+#endif
+ if (saddr.u.sa.sa_family == AF_INET) {
+ return ntohs(saddr.u.in.sin_port);
+ }
+ return -1;
+}
+
+static bool netbios_session_retarget(struct smbXsrv_connection *xconn,
+ const char *name, int name_type)
+{
+ char *trim_name;
+ char *trim_name_type;
+ const char *retarget_parm;
+ char *retarget;
+ char *p;
+ int retarget_type = 0x20;
+ int retarget_port = NBT_SMB_PORT;
+ struct sockaddr_storage retarget_addr;
+ struct sockaddr_in *in_addr;
+ bool ret = false;
+ uint8_t outbuf[10];
+
+ if (get_socket_port(xconn->transport.sock) != NBT_SMB_PORT) {
+ return false;
+ }
+
+ trim_name = talloc_strdup(talloc_tos(), name);
+ if (trim_name == NULL) {
+ goto fail;
+ }
+ trim_char(trim_name, ' ', ' ');
+
+ trim_name_type = talloc_asprintf(trim_name, "%s#%2.2x", trim_name,
+ name_type);
+ if (trim_name_type == NULL) {
+ goto fail;
+ }
+
+ retarget_parm = lp_parm_const_string(-1, "netbios retarget",
+ trim_name_type, NULL);
+ if (retarget_parm == NULL) {
+ retarget_parm = lp_parm_const_string(-1, "netbios retarget",
+ trim_name, NULL);
+ }
+ if (retarget_parm == NULL) {
+ goto fail;
+ }
+
+ retarget = talloc_strdup(trim_name, retarget_parm);
+ if (retarget == NULL) {
+ goto fail;
+ }
+
+ DEBUG(10, ("retargeting %s to %s\n", trim_name_type, retarget));
+
+ p = strchr(retarget, ':');
+ if (p != NULL) {
+ *p++ = '\0';
+ retarget_port = atoi(p);
+ }
+
+ p = strchr_m(retarget, '#');
+ if (p != NULL) {
+ *p++ = '\0';
+ if (sscanf(p, "%x", &retarget_type) != 1) {
+ goto fail;
+ }
+ }
+
+ ret = resolve_name(retarget, &retarget_addr, retarget_type, false);
+ if (!ret) {
+ DEBUG(10, ("could not resolve %s\n", retarget));
+ goto fail;
+ }
+
+ if (retarget_addr.ss_family != AF_INET) {
+ DEBUG(10, ("Retarget target not an IPv4 addr\n"));
+ goto fail;
+ }
+
+ in_addr = (struct sockaddr_in *)(void *)&retarget_addr;
+
+ _smb_setlen(outbuf, 6);
+ SCVAL(outbuf, 0, 0x84);
+ *(uint32_t *)(outbuf+4) = in_addr->sin_addr.s_addr;
+ *(uint16_t *)(outbuf+8) = htons(retarget_port);
+
+ if (!smb1_srv_send(xconn, (char *)outbuf, false, 0, false)) {
+ exit_server_cleanly("netbios_session_retarget: smb1_srv_send "
+ "failed.");
+ }
+
+ ret = true;
+ fail:
+ TALLOC_FREE(trim_name);
+ return ret;
+}
+
+static void reply_called_name_not_present(char *outbuf)
+{
+ smb_setlen(outbuf, 1);
+ SCVAL(outbuf, 0, 0x83);
+ SCVAL(outbuf, 4, 0x82);
+}
+
+/****************************************************************************
+ Reply to a (netbios-level) special message.
+****************************************************************************/
+
+void reply_special(struct smbXsrv_connection *xconn, char *inbuf, size_t inbuf_size)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ int msg_type = CVAL(inbuf,0);
+ int msg_flags = CVAL(inbuf,1);
+ /*
+ * We only really use 4 bytes of the outbuf, but for the smb_setlen
+ * calculation & friends (smb1_srv_send uses that) we need the full smb
+ * header.
+ */
+ char outbuf[smb_size];
+
+ memset(outbuf, '\0', sizeof(outbuf));
+
+ smb_setlen(outbuf,0);
+
+ switch (msg_type) {
+ case NBSSrequest: /* session request */
+ {
+ /* inbuf_size is guaranteed to be at least 4. */
+ fstring name1,name2;
+ int name_type1, name_type2;
+ int name_len1, name_len2;
+
+ *name1 = *name2 = 0;
+
+ if (xconn->transport.nbt.got_session) {
+ exit_server_cleanly("multiple session request not permitted");
+ }
+
+ SCVAL(outbuf,0,NBSSpositive);
+ SCVAL(outbuf,3,0);
+
+ /* inbuf_size is guaranteed to be at least 4. */
+ name_len1 = name_len((unsigned char *)(inbuf+4),inbuf_size - 4);
+ if (name_len1 <= 0 || name_len1 > inbuf_size - 4) {
+ DEBUG(0,("Invalid name length in session request\n"));
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+ name_len2 = name_len((unsigned char *)(inbuf+4+name_len1),inbuf_size - 4 - name_len1);
+ if (name_len2 <= 0 || name_len2 > inbuf_size - 4 - name_len1) {
+ DEBUG(0,("Invalid name length in session request\n"));
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+
+ name_type1 = name_extract((unsigned char *)inbuf,
+ inbuf_size,(unsigned int)4,name1);
+ name_type2 = name_extract((unsigned char *)inbuf,
+ inbuf_size,(unsigned int)(4 + name_len1),name2);
+
+ if (name_type1 == -1 || name_type2 == -1) {
+ DEBUG(0,("Invalid name type in session request\n"));
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+
+ DEBUG(2,("netbios connect: name1=%s0x%x name2=%s0x%x\n",
+ name1, name_type1, name2, name_type2));
+
+ if (netbios_session_retarget(xconn, name1, name_type1)) {
+ exit_server_cleanly("retargeted client");
+ }
+
+ /*
+ * Windows NT/2k uses "*SMBSERVER" and XP uses
+ * "*SMBSERV" arrggg!!!
+ */
+ if (strequal(name1, "*SMBSERVER ")
+ || strequal(name1, "*SMBSERV ")) {
+ char *raddr;
+
+ raddr = tsocket_address_inet_addr_string(sconn->remote_address,
+ talloc_tos());
+ if (raddr == NULL) {
+ exit_server_cleanly("could not allocate raddr");
+ }
+
+ fstrcpy(name1, raddr);
+ }
+
+ set_local_machine_name(name1, True);
+ set_remote_machine_name(name2, True);
+
+ if (is_ipaddress(sconn->remote_hostname)) {
+ char *p = discard_const_p(char, sconn->remote_hostname);
+
+ talloc_free(p);
+
+ sconn->remote_hostname = talloc_strdup(sconn,
+ get_remote_machine_name());
+ if (sconn->remote_hostname == NULL) {
+ exit_server_cleanly("could not copy remote name");
+ }
+ xconn->remote_hostname = sconn->remote_hostname;
+ }
+
+ DEBUG(2,("netbios connect: local=%s remote=%s, name type = %x\n",
+ get_local_machine_name(), get_remote_machine_name(),
+ name_type2));
+
+ if (name_type2 == 'R') {
+ /* We are being asked for a pathworks session ---
+ no thanks! */
+ reply_called_name_not_present(outbuf);
+ break;
+ }
+
+ reload_services(sconn, conn_snum_used, true);
+ reopen_logs();
+
+ xconn->transport.nbt.got_session = true;
+ break;
+ }
+
+ case 0x89: /* session keepalive request
+ (some old clients produce this?) */
+ SCVAL(outbuf,0,NBSSkeepalive);
+ SCVAL(outbuf,3,0);
+ break;
+
+ case NBSSpositive: /* positive session response */
+ case NBSSnegative: /* negative session response */
+ case NBSSretarget: /* retarget session response */
+ DEBUG(0,("Unexpected session response\n"));
+ break;
+
+ case NBSSkeepalive: /* session keepalive */
+ default:
+ return;
+ }
+
+ DEBUG(5,("init msg_type=0x%x msg_flags=0x%x\n",
+ msg_type, msg_flags));
+
+ if (!smb1_srv_send(xconn, outbuf, false, 0, false)) {
+ exit_server_cleanly("reply_special: smb1_srv_send failed.");
+ }
+
+ if (CVAL(outbuf, 0) != 0x82) {
+ exit_server_cleanly("invalid netbios session");
+ }
+ return;
+}
+
+/*******************************************************************
+ * unlink a file with all relevant access checks
+ *******************************************************************/
+
+NTSTATUS unlink_internals(connection_struct *conn,
+ struct smb_request *req,
+ uint32_t dirtype,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname)
+{
+ uint32_t fattr;
+ files_struct *fsp;
+ uint32_t dirtype_orig = dirtype;
+ NTSTATUS status;
+ int ret;
+ struct smb2_create_blobs *posx = NULL;
+
+ if (dirtype == 0) {
+ dirtype = FILE_ATTRIBUTE_NORMAL;
+ }
+
+ DBG_DEBUG("%s, dirtype = %d\n",
+ smb_fname_str_dbg(smb_fname),
+ dirtype);
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ ret = vfs_stat(conn, smb_fname);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ fattr = fdos_mode(smb_fname->fsp);
+
+ if (dirtype & FILE_ATTRIBUTE_NORMAL) {
+ dirtype = FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY;
+ }
+
+ dirtype &= (FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM);
+ if (!dirtype) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (!dir_check_ftype(fattr, dirtype)) {
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (dirtype_orig & 0x8000) {
+ /* These will never be set for POSIX. */
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+#if 0
+ if ((fattr & dirtype) & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ if ((fattr & ~dirtype) & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ if (dirtype & 0xFF00) {
+ /* These will never be set for POSIX. */
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ dirtype &= 0xFF;
+ if (!dirtype) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+
+ /* Can't delete a directory. */
+ if (fattr & FILE_ATTRIBUTE_DIRECTORY) {
+ return NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+#endif
+
+#if 0 /* JRATEST */
+ else if (dirtype & FILE_ATTRIBUTE_DIRECTORY) /* Asked for a directory and it isn't. */
+ return NT_STATUS_OBJECT_NAME_INVALID;
+#endif /* JRATEST */
+
+ if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ status = make_smb2_posix_create_ctx(
+ talloc_tos(), &posx, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ }
+
+ /* On open checks the open itself will check the share mode, so
+ don't do it here as we'll get it wrong. */
+
+ status = SMB_VFS_CREATE_FILE
+ (conn, /* conn */
+ req, /* req */
+ dirfsp, /* dirfsp */
+ smb_fname, /* fname */
+ DELETE_ACCESS, /* access_mask */
+ FILE_SHARE_NONE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ FILE_NON_DIRECTORY_FILE |
+ FILE_OPEN_REPARSE_POINT, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ TALLOC_FREE(posx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("SMB_VFS_CREATEFILE failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = can_set_delete_on_close(fsp, fattr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("can_set_delete_on_close for file %s - "
+ "(%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return status;
+ }
+
+ /* The set is across all open files on this dev/inode pair. */
+ if (!set_delete_on_close(fsp, True,
+ conn->session_info->security_token,
+ conn->session_info->unix_token)) {
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ return close_file_free(req, &fsp, NORMAL_CLOSE);
+}
+
+/****************************************************************************
+ Fake (read/write) sendfile. Returns -1 on read or write fail.
+****************************************************************************/
+
+ssize_t fake_sendfile(struct smbXsrv_connection *xconn, files_struct *fsp,
+ off_t startpos, size_t nread)
+{
+ size_t bufsize;
+ size_t tosend = nread;
+ char *buf;
+
+ if (nread == 0) {
+ return 0;
+ }
+
+ bufsize = MIN(nread, 65536);
+
+ if (!(buf = SMB_MALLOC_ARRAY(char, bufsize))) {
+ return -1;
+ }
+
+ while (tosend > 0) {
+ ssize_t ret;
+ size_t cur_read;
+
+ cur_read = MIN(tosend, bufsize);
+ ret = read_file(fsp,buf,startpos,cur_read);
+ if (ret == -1) {
+ SAFE_FREE(buf);
+ return -1;
+ }
+
+ /* If we had a short read, fill with zeros. */
+ if (ret < cur_read) {
+ memset(buf + ret, '\0', cur_read - ret);
+ }
+
+ ret = write_data(xconn->transport.sock, buf, cur_read);
+ if (ret != cur_read) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. "
+ "Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ SAFE_FREE(buf);
+ errno = saved_errno;
+ return -1;
+ }
+ tosend -= cur_read;
+ startpos += cur_read;
+ }
+
+ SAFE_FREE(buf);
+ return (ssize_t)nread;
+}
+
+/****************************************************************************
+ Deal with the case of sendfile reading less bytes from the file than
+ requested. Fill with zeros (all we can do). Returns 0 on success
+****************************************************************************/
+
+ssize_t sendfile_short_send(struct smbXsrv_connection *xconn,
+ files_struct *fsp,
+ ssize_t nread,
+ size_t headersize,
+ size_t smb_maxcnt)
+{
+#define SHORT_SEND_BUFSIZE 1024
+ if (nread < headersize) {
+ DEBUG(0,("sendfile_short_send: sendfile failed to send "
+ "header for file %s (%s). Terminating\n",
+ fsp_str_dbg(fsp), strerror(errno)));
+ return -1;
+ }
+
+ nread -= headersize;
+
+ if (nread < smb_maxcnt) {
+ char buf[SHORT_SEND_BUFSIZE] = { 0 };
+
+ DEBUG(0,("sendfile_short_send: filling truncated file %s "
+ "with zeros !\n", fsp_str_dbg(fsp)));
+
+ while (nread < smb_maxcnt) {
+ /*
+ * We asked for the real file size and told sendfile
+ * to not go beyond the end of the file. But it can
+ * happen that in between our fstat call and the
+ * sendfile call the file was truncated. This is very
+ * bad because we have already announced the larger
+ * number of bytes to the client.
+ *
+ * The best we can do now is to send 0-bytes, just as
+ * a read from a hole in a sparse file would do.
+ *
+ * This should happen rarely enough that I don't care
+ * about efficiency here :-)
+ */
+ size_t to_write;
+ ssize_t ret;
+
+ to_write = MIN(SHORT_SEND_BUFSIZE, smb_maxcnt - nread);
+ ret = write_data(xconn->transport.sock, buf, to_write);
+ if (ret != to_write) {
+ int saved_errno = errno;
+ /*
+ * Try and give an error message saying what
+ * client failed.
+ */
+ DEBUG(0, ("write_data failed for client %s. "
+ "Error %s\n",
+ smbXsrv_connection_dbg(xconn),
+ strerror(saved_errno)));
+ errno = saved_errno;
+ return -1;
+ }
+ nread += to_write;
+ }
+ }
+
+ return 0;
+}
+
+/*******************************************************************
+ Check if a user is allowed to rename a file.
+********************************************************************/
+
+static NTSTATUS can_rename(connection_struct *conn, files_struct *fsp,
+ uint16_t dirtype)
+{
+ NTSTATUS status;
+
+ if (fsp->fsp_name->twrp != 0) {
+ /* Get the error right, this is what Windows returns. */
+ return NT_STATUS_NOT_SAME_DEVICE;
+ }
+
+ if (!CAN_WRITE(conn)) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ if ((dirtype & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) !=
+ (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) {
+ /* Only bother to read the DOS attribute if we might deny the
+ rename on the grounds of attribute mismatch. */
+ uint32_t fmode = fdos_mode(fsp);
+ if ((fmode & ~dirtype) & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) {
+ return NT_STATUS_NO_SUCH_FILE;
+ }
+ }
+
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_RENAME) {
+ return NT_STATUS_OK;
+ }
+
+ /* If no pathnames are open below this
+ directory, allow the rename. */
+
+ if (lp_strict_rename(SNUM(conn))) {
+ /*
+ * Strict rename, check open file db.
+ */
+ if (have_file_open_below(fsp->conn, fsp->fsp_name)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else if (file_find_subpath(fsp)) {
+ /*
+ * No strict rename, just look in local process.
+ */
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+ }
+
+ status = check_any_access_fsp(fsp, DELETE_ACCESS | FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Ensure open files have their names updated. Updated to notify other smbd's
+ asynchronously.
+****************************************************************************/
+
+static void rename_open_files(connection_struct *conn,
+ struct share_mode_lock *lck,
+ struct file_id id,
+ uint32_t orig_name_hash,
+ const struct smb_filename *smb_fname_dst)
+{
+ files_struct *fsp;
+ bool did_rename = False;
+ NTSTATUS status;
+ uint32_t new_name_hash = 0;
+
+ for(fsp = file_find_di_first(conn->sconn, id, false); fsp;
+ fsp = file_find_di_next(fsp, false)) {
+ SMB_STRUCT_STAT fsp_orig_sbuf;
+ struct file_id_buf idbuf;
+ /* fsp_name is a relative path under the fsp. To change this for other
+ sharepaths we need to manipulate relative paths. */
+ /* TODO - create the absolute path and manipulate the newname
+ relative to the sharepath. */
+ if (!strequal(fsp->conn->connectpath, conn->connectpath)) {
+ continue;
+ }
+ if (fsp->name_hash != orig_name_hash) {
+ continue;
+ }
+ DBG_DEBUG("renaming file %s "
+ "(file_id %s) from %s -> %s\n",
+ fsp_fnum_dbg(fsp),
+ file_id_str_buf(fsp->file_id, &idbuf),
+ fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst));
+
+ /*
+ * The incoming smb_fname_dst here has an
+ * invalid stat struct (it must not have
+ * existed for the rename to succeed).
+ * Preserve the existing stat from the
+ * open fsp after fsp_set_smb_fname()
+ * overwrites with the invalid stat.
+ *
+ * We will do an fstat before returning
+ * any of this metadata to the client anyway.
+ */
+ fsp_orig_sbuf = fsp->fsp_name->st;
+ status = fsp_set_smb_fname(fsp, smb_fname_dst);
+ if (NT_STATUS_IS_OK(status)) {
+ did_rename = True;
+ new_name_hash = fsp->name_hash;
+ /* Restore existing stat. */
+ fsp->fsp_name->st = fsp_orig_sbuf;
+ }
+ }
+
+ if (!did_rename) {
+ struct file_id_buf idbuf;
+ DBG_DEBUG("no open files on file_id %s "
+ "for %s\n",
+ file_id_str_buf(id, &idbuf),
+ smb_fname_str_dbg(smb_fname_dst));
+ }
+
+ /* Send messages to all smbd's (not ourself) that the name has changed. */
+ rename_share_filename(conn->sconn->msg_ctx, lck, id, conn->connectpath,
+ orig_name_hash, new_name_hash,
+ smb_fname_dst);
+
+}
+
+/****************************************************************************
+ We need to check if the source path is a parent directory of the destination
+ (ie. a rename of /foo/bar/baz -> /foo/bar/baz/bibble/bobble. If so we must
+ refuse the rename with a sharing violation. Under UNIX the above call can
+ *succeed* if /foo/bar/baz is a symlink to another area in the share. We
+ probably need to check that the client is a Windows one before disallowing
+ this as a UNIX client (one with UNIX extensions) can know the source is a
+ symlink and make this decision intelligently. Found by an excellent bug
+ report from <AndyLiebman@aol.com>.
+****************************************************************************/
+
+static bool rename_path_prefix_equal(const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ const char *psrc = smb_fname_src->base_name;
+ const char *pdst = smb_fname_dst->base_name;
+ size_t slen;
+
+ if (psrc[0] == '.' && psrc[1] == '/') {
+ psrc += 2;
+ }
+ if (pdst[0] == '.' && pdst[1] == '/') {
+ pdst += 2;
+ }
+ if ((slen = strlen(psrc)) > strlen(pdst)) {
+ return False;
+ }
+ return ((memcmp(psrc, pdst, slen) == 0) && pdst[slen] == '/');
+}
+
+/*
+ * Do the notify calls from a rename
+ */
+
+static void notify_rename(connection_struct *conn, bool is_dir,
+ const struct smb_filename *smb_fname_src,
+ const struct smb_filename *smb_fname_dst)
+{
+ char *parent_dir_src = NULL;
+ char *parent_dir_dst = NULL;
+ uint32_t mask;
+
+ mask = is_dir ? FILE_NOTIFY_CHANGE_DIR_NAME
+ : FILE_NOTIFY_CHANGE_FILE_NAME;
+
+ if (!parent_dirname(talloc_tos(), smb_fname_src->base_name,
+ &parent_dir_src, NULL) ||
+ !parent_dirname(talloc_tos(), smb_fname_dst->base_name,
+ &parent_dir_dst, NULL)) {
+ goto out;
+ }
+
+ if (strcmp(parent_dir_src, parent_dir_dst) == 0) {
+ notify_fname(conn, NOTIFY_ACTION_OLD_NAME, mask,
+ smb_fname_src->base_name);
+ notify_fname(conn, NOTIFY_ACTION_NEW_NAME, mask,
+ smb_fname_dst->base_name);
+ }
+ else {
+ notify_fname(conn, NOTIFY_ACTION_REMOVED, mask,
+ smb_fname_src->base_name);
+ notify_fname(conn, NOTIFY_ACTION_ADDED, mask,
+ smb_fname_dst->base_name);
+ }
+
+ /* this is a strange one. w2k3 gives an additional event for
+ CHANGE_ATTRIBUTES and CHANGE_CREATION on the new file when renaming
+ files, but not directories */
+ if (!is_dir) {
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_ATTRIBUTES
+ |FILE_NOTIFY_CHANGE_CREATION,
+ smb_fname_dst->base_name);
+ }
+ out:
+ TALLOC_FREE(parent_dir_src);
+ TALLOC_FREE(parent_dir_dst);
+}
+
+/****************************************************************************
+ Returns an error if the parent directory for a filename is open in an
+ incompatible way.
+****************************************************************************/
+
+static NTSTATUS parent_dirname_compatible_open(connection_struct *conn,
+ const struct smb_filename *smb_fname_dst_in)
+{
+ struct smb_filename *smb_fname_parent = NULL;
+ struct file_id id;
+ files_struct *fsp = NULL;
+ int ret;
+ NTSTATUS status;
+
+ status = SMB_VFS_PARENT_PATHNAME(conn,
+ talloc_tos(),
+ smb_fname_dst_in,
+ &smb_fname_parent,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = vfs_stat(conn, smb_fname_parent);
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ /*
+ * We're only checking on this smbd here, mostly good
+ * enough.. and will pass tests.
+ */
+
+ id = vfs_file_id_from_sbuf(conn, &smb_fname_parent->st);
+ for (fsp = file_find_di_first(conn->sconn, id, true); fsp;
+ fsp = file_find_di_next(fsp, true)) {
+ if (fsp->access_mask & DELETE_ACCESS) {
+ return NT_STATUS_SHARING_VIOLATION;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Rename an open file - given an fsp.
+****************************************************************************/
+
+NTSTATUS rename_internals_fsp(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_dst_in,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists)
+{
+ TALLOC_CTX *ctx = talloc_tos();
+ struct smb_filename *parent_dir_fname_dst = NULL;
+ struct smb_filename *parent_dir_fname_dst_atname = NULL;
+ struct smb_filename *parent_dir_fname_src = NULL;
+ struct smb_filename *parent_dir_fname_src_atname = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ struct share_mode_lock *lck = NULL;
+ uint32_t access_mask = SEC_DIR_ADD_FILE;
+ bool dst_exists, old_is_stream, new_is_stream;
+ int ret;
+ bool case_sensitive = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
+ true : conn->case_sensitive;
+ bool case_preserve = (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) ?
+ true : conn->case_preserve;
+
+ status = parent_dirname_compatible_open(conn, smb_fname_dst_in);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (file_has_open_streams(fsp)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ /* Make a copy of the dst smb_fname structs */
+
+ smb_fname_dst = cp_smb_filename(ctx, smb_fname_dst_in);
+ if (smb_fname_dst == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /*
+ * Check for special case with case preserving and not
+ * case sensitive. If the new last component differs from the original
+ * last component only by case, then we should allow
+ * the rename (user is trying to change the case of the
+ * filename).
+ */
+ if (!case_sensitive && case_preserve &&
+ strequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) &&
+ strequal(fsp->fsp_name->stream_name, smb_fname_dst->stream_name)) {
+ char *fname_dst_parent = NULL;
+ const char *fname_dst_lcomp = NULL;
+ char *orig_lcomp_path = NULL;
+ char *orig_lcomp_stream = NULL;
+ bool ok = true;
+
+ /*
+ * Split off the last component of the processed
+ * destination name. We will compare this to
+ * the split components of dst_original_lcomp.
+ */
+ if (!parent_dirname(ctx,
+ smb_fname_dst->base_name,
+ &fname_dst_parent,
+ &fname_dst_lcomp)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /*
+ * The dst_original_lcomp component contains
+ * the last_component of the path + stream
+ * name (if a stream exists).
+ *
+ * Split off the stream name so we
+ * can check them separately.
+ */
+
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_PATHNAMES) {
+ /* POSIX - no stream component. */
+ orig_lcomp_path = talloc_strdup(ctx,
+ dst_original_lcomp);
+ if (orig_lcomp_path == NULL) {
+ ok = false;
+ }
+ } else {
+ ok = split_stream_filename(ctx,
+ dst_original_lcomp,
+ &orig_lcomp_path,
+ &orig_lcomp_stream);
+ }
+
+ if (!ok) {
+ TALLOC_FREE(fname_dst_parent);
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* If the base names only differ by case, use original. */
+ if(!strcsequal(fname_dst_lcomp, orig_lcomp_path)) {
+ char *tmp;
+ /*
+ * Replace the modified last component with the
+ * original.
+ */
+ if (!ISDOT(fname_dst_parent)) {
+ tmp = talloc_asprintf(smb_fname_dst,
+ "%s/%s",
+ fname_dst_parent,
+ orig_lcomp_path);
+ } else {
+ tmp = talloc_strdup(smb_fname_dst,
+ orig_lcomp_path);
+ }
+ if (tmp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ TALLOC_FREE(fname_dst_parent);
+ TALLOC_FREE(orig_lcomp_path);
+ TALLOC_FREE(orig_lcomp_stream);
+ goto out;
+ }
+ TALLOC_FREE(smb_fname_dst->base_name);
+ smb_fname_dst->base_name = tmp;
+ }
+
+ /* If the stream_names only differ by case, use original. */
+ if(!strcsequal(smb_fname_dst->stream_name,
+ orig_lcomp_stream)) {
+ /* Use the original stream. */
+ char *tmp = talloc_strdup(smb_fname_dst,
+ orig_lcomp_stream);
+ if (tmp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ TALLOC_FREE(fname_dst_parent);
+ TALLOC_FREE(orig_lcomp_path);
+ TALLOC_FREE(orig_lcomp_stream);
+ goto out;
+ }
+ TALLOC_FREE(smb_fname_dst->stream_name);
+ smb_fname_dst->stream_name = tmp;
+ }
+ TALLOC_FREE(fname_dst_parent);
+ TALLOC_FREE(orig_lcomp_path);
+ TALLOC_FREE(orig_lcomp_stream);
+ }
+
+ /*
+ * If the src and dest names are identical - including case,
+ * don't do the rename, just return success.
+ */
+
+ if (strcsequal(fsp->fsp_name->base_name, smb_fname_dst->base_name) &&
+ strcsequal(fsp->fsp_name->stream_name,
+ smb_fname_dst->stream_name)) {
+ DEBUG(3, ("rename_internals_fsp: identical names in rename %s "
+ "- returning success\n",
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ old_is_stream = is_ntfs_stream_smb_fname(fsp->fsp_name);
+ new_is_stream = is_ntfs_stream_smb_fname(smb_fname_dst);
+
+ /* Return the correct error code if both names aren't streams. */
+ if (!old_is_stream && new_is_stream) {
+ status = NT_STATUS_OBJECT_NAME_INVALID;
+ goto out;
+ }
+
+ if (old_is_stream && !new_is_stream) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ dst_exists = vfs_stat(conn, smb_fname_dst) == 0;
+
+ if(!replace_if_exists && dst_exists) {
+ DEBUG(3, ("rename_internals_fsp: dest exists doing rename "
+ "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto out;
+ }
+
+ /*
+ * Drop the pathref fsp on the destination otherwise we trip upon in in
+ * the below check for open files check.
+ */
+ if (smb_fname_dst_in->fsp != NULL) {
+ fd_close(smb_fname_dst_in->fsp);
+ file_free(NULL, smb_fname_dst_in->fsp);
+ SMB_ASSERT(smb_fname_dst_in->fsp == NULL);
+ }
+
+ if (dst_exists) {
+ struct file_id fileid = vfs_file_id_from_sbuf(conn,
+ &smb_fname_dst->st);
+ files_struct *dst_fsp = file_find_di_first(conn->sconn,
+ fileid, true);
+ /* The file can be open when renaming a stream */
+ if (dst_fsp && !new_is_stream) {
+ DEBUG(3, ("rename_internals_fsp: Target file open\n"));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+ }
+
+ /* Ensure we have a valid stat struct for the source. */
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = can_rename(conn, fsp, attrs);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n",
+ nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+ if (NT_STATUS_EQUAL(status,NT_STATUS_SHARING_VIOLATION))
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ if (rename_path_prefix_equal(fsp->fsp_name, smb_fname_dst)) {
+ status = NT_STATUS_ACCESS_DENIED;
+ goto out;
+ }
+
+ /* Do we have rights to move into the destination ? */
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ /* We're moving a directory. */
+ access_mask = SEC_DIR_ADD_SUBDIR;
+ }
+
+ /*
+ * Get a pathref on the destination parent directory, so
+ * we can call check_parent_access_fsp().
+ */
+ status = parent_pathref(ctx,
+ conn->cwd_fsp,
+ smb_fname_dst,
+ &parent_dir_fname_dst,
+ &parent_dir_fname_dst_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = check_parent_access_fsp(parent_dir_fname_dst->fsp,
+ access_mask);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("check_parent_access_fsp on "
+ "dst %s returned %s\n",
+ smb_fname_str_dbg(smb_fname_dst),
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * If the target existed, make sure the destination
+ * atname has the same stat struct.
+ */
+ parent_dir_fname_dst_atname->st = smb_fname_dst->st;
+
+ /*
+ * It's very common that source and
+ * destination directories are the same.
+ * Optimize by not opening the
+ * second parent_pathref if we know
+ * this is the case.
+ */
+
+ status = SMB_VFS_PARENT_PATHNAME(conn,
+ ctx,
+ fsp->fsp_name,
+ &parent_dir_fname_src,
+ &parent_dir_fname_src_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * We do a case-sensitive string comparison. We want to be *sure*
+ * this is the same path. The worst that can happen if
+ * the case doesn't match is we lose out on the optimization,
+ * the code still works.
+ *
+ * We can ignore twrp fields here. Rename is not allowed on
+ * shadow copy handles.
+ */
+
+ if (strcmp(parent_dir_fname_src->base_name,
+ parent_dir_fname_dst->base_name) == 0) {
+ /*
+ * parent directory is the same for source
+ * and destination.
+ */
+ /* Reparent the src_atname to the parent_dir_dest fname. */
+ parent_dir_fname_src_atname = talloc_move(
+ parent_dir_fname_dst,
+ &parent_dir_fname_src_atname);
+ /* Free the unneeded duplicate parent name. */
+ TALLOC_FREE(parent_dir_fname_src);
+ /*
+ * And make the source parent name a copy of the
+ * destination parent name.
+ */
+ parent_dir_fname_src = parent_dir_fname_dst;
+
+ /*
+ * Ensure we have a pathref fsp on the
+ * parent_dir_fname_src_atname to match the code in the else
+ * branch where we use parent_pathref().
+ */
+ status = reference_smb_fname_fsp_link(
+ parent_dir_fname_src_atname,
+ fsp->fsp_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ } else {
+ /*
+ * source and destination parent directories are
+ * different.
+ *
+ * Get a pathref on the source parent directory, so
+ * we can do a relative rename.
+ */
+ TALLOC_FREE(parent_dir_fname_src);
+ status = parent_pathref(ctx,
+ conn->cwd_fsp,
+ fsp->fsp_name,
+ &parent_dir_fname_src,
+ &parent_dir_fname_src_atname);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ /*
+ * Some modules depend on the source smb_fname having a valid stat.
+ * The parent_dir_fname_src_atname is the relative name of the
+ * currently open file, so just copy the stat from the open fsp.
+ */
+ parent_dir_fname_src_atname->st = fsp->fsp_name->st;
+
+ lck = get_existing_share_mode_lock(talloc_tos(), fsp->file_id);
+
+ /*
+ * We have the file open ourselves, so not being able to get the
+ * corresponding share mode lock is a fatal error.
+ */
+
+ SMB_ASSERT(lck != NULL);
+
+ ret = SMB_VFS_RENAMEAT(conn,
+ parent_dir_fname_src->fsp,
+ parent_dir_fname_src_atname,
+ parent_dir_fname_dst->fsp,
+ parent_dir_fname_dst_atname);
+ if (ret == 0) {
+ uint32_t create_options = fh_get_private_options(fsp->fh);
+
+ DEBUG(3, ("rename_internals_fsp: succeeded doing rename on "
+ "%s -> %s\n", smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ notify_rename(conn,
+ fsp->fsp_flags.is_directory,
+ fsp->fsp_name,
+ smb_fname_dst);
+
+ rename_open_files(conn, lck, fsp->file_id, fsp->name_hash,
+ smb_fname_dst);
+
+ if (!fsp->fsp_flags.is_directory &&
+ (lp_map_archive(SNUM(conn)) ||
+ lp_store_dos_attributes(SNUM(conn))))
+ {
+ /*
+ * We must set the archive bit on the newly renamed
+ * file.
+ */
+ status = vfs_stat_fsp(fsp);
+ if (NT_STATUS_IS_OK(status)) {
+ uint32_t old_dosmode;
+ old_dosmode = fdos_mode(fsp);
+ /*
+ * We can use fsp->fsp_name here as it has
+ * already been changed to the new name.
+ */
+ SMB_ASSERT(fsp->fsp_name->fsp == fsp);
+ file_set_dosmode(conn,
+ fsp->fsp_name,
+ old_dosmode | FILE_ATTRIBUTE_ARCHIVE,
+ NULL,
+ true);
+ }
+ }
+
+ /*
+ * A rename acts as a new file create w.r.t. allowing an initial delete
+ * on close, probably because in Windows there is a new handle to the
+ * new file. If initial delete on close was requested but not
+ * originally set, we need to set it here. This is probably not 100% correct,
+ * but will work for the CIFSFS client which in non-posix mode
+ * depends on these semantics. JRA.
+ */
+
+ if (create_options & FILE_DELETE_ON_CLOSE) {
+ status = can_set_delete_on_close(fsp, 0);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /* Note that here we set the *initial* delete on close flag,
+ * not the regular one. The magic gets handled in close. */
+ fsp->fsp_flags.initial_delete_on_close = true;
+ }
+ }
+ TALLOC_FREE(lck);
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ TALLOC_FREE(lck);
+
+ if (errno == ENOTDIR || errno == EISDIR) {
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ } else {
+ status = map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(3, ("rename_internals_fsp: Error %s rename %s -> %s\n",
+ nt_errstr(status), smb_fname_str_dbg(fsp->fsp_name),
+ smb_fname_str_dbg(smb_fname_dst)));
+
+ out:
+
+ /*
+ * parent_dir_fname_src may be a copy of parent_dir_fname_dst.
+ * See the optimization for same source and destination directory
+ * above. Only free one in that case.
+ */
+ if (parent_dir_fname_src != parent_dir_fname_dst) {
+ TALLOC_FREE(parent_dir_fname_src);
+ }
+ TALLOC_FREE(parent_dir_fname_dst);
+ TALLOC_FREE(smb_fname_dst);
+
+ return status;
+}
+
+/****************************************************************************
+ The guts of the rename command, split out so it may be called by the NT SMB
+ code.
+****************************************************************************/
+
+NTSTATUS rename_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ struct files_struct *src_dirfsp,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ const char *dst_original_lcomp,
+ uint32_t attrs,
+ bool replace_if_exists,
+ uint32_t access_mask)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int create_options = FILE_OPEN_REPARSE_POINT;
+ struct smb2_create_blobs *posx = NULL;
+ struct files_struct *fsp = NULL;
+ bool posix_pathname = (smb_fname_src->flags & SMB_FILENAME_POSIX_PATH);
+ bool case_sensitive = posix_pathname ? true : conn->case_sensitive;
+ bool case_preserve = posix_pathname ? true : conn->case_preserve;
+ bool short_case_preserve = posix_pathname ? true :
+ conn->short_case_preserve;
+
+ if (posix_pathname) {
+ status = make_smb2_posix_create_ctx(talloc_tos(), &posx, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("make_smb2_posix_create_ctx failed: %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+ }
+
+ DBG_NOTICE("case_sensitive = %d, "
+ "case_preserve = %d, short case preserve = %d, "
+ "directory = %s, newname = %s, "
+ "last_component_dest = %s\n",
+ case_sensitive, case_preserve,
+ short_case_preserve,
+ smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst),
+ dst_original_lcomp);
+
+ ZERO_STRUCT(smb_fname_src->st);
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_src);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(status,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ goto out;
+ }
+ /*
+ * Possible symlink src.
+ */
+ if (!(smb_fname_src->flags & SMB_FILENAME_POSIX_PATH)) {
+ goto out;
+ }
+ if (!S_ISLNK(smb_fname_src->st.st_ex_mode)) {
+ goto out;
+ }
+ }
+
+ if (S_ISDIR(smb_fname_src->st.st_ex_mode)) {
+ create_options |= FILE_DIRECTORY_FILE;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ src_dirfsp, /* dirfsp */
+ smb_fname_src, /* fname */
+ access_mask, /* access_mask */
+ (FILE_SHARE_READ | /* share_access */
+ FILE_SHARE_WRITE),
+ FILE_OPEN, /* create_disposition*/
+ create_options, /* create_options */
+ 0, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp, /* result */
+ NULL, /* pinfo */
+ posx, /* in_context_blobs */
+ NULL); /* out_context_blobs */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("Could not open rename source %s: %s\n",
+ smb_fname_str_dbg(smb_fname_src),
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = rename_internals_fsp(conn,
+ fsp,
+ smb_fname_dst,
+ dst_original_lcomp,
+ attrs,
+ replace_if_exists);
+
+ close_file_free(req, &fsp, NORMAL_CLOSE);
+
+ DBG_NOTICE("Error %s rename %s -> %s\n",
+ nt_errstr(status), smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst));
+
+ out:
+ TALLOC_FREE(posx);
+ return status;
+}
+
+/*******************************************************************
+ Copy a file as part of a reply_copy.
+******************************************************************/
+
+/*
+ * TODO: check error codes on all callers
+ */
+
+NTSTATUS copy_file(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_filename *smb_fname_src,
+ struct smb_filename *smb_fname_dst,
+ uint32_t new_create_disposition)
+{
+ struct smb_filename *smb_fname_dst_tmp = NULL;
+ off_t ret=-1;
+ files_struct *fsp1,*fsp2;
+ uint32_t dosattrs;
+ NTSTATUS status;
+
+
+ smb_fname_dst_tmp = cp_smb_filename(ctx, smb_fname_dst);
+ if (smb_fname_dst_tmp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = vfs_file_exist(conn, smb_fname_src);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_src);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /* Open the src file for reading. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname_src, /* fname */
+ FILE_GENERIC_READ, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp1, /* result */
+ NULL, /* psbuf */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ dosattrs = fdos_mode(fsp1);
+
+ if (SMB_VFS_STAT(conn, smb_fname_dst_tmp) == -1) {
+ ZERO_STRUCTP(&smb_fname_dst_tmp->st);
+ }
+
+ status = openat_pathref_fsp(conn->cwd_fsp, smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND))
+ {
+ goto out;
+ }
+
+ /* Open the dst file for writing. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ NULL, /* req */
+ NULL, /* dirfsp */
+ smb_fname_dst, /* fname */
+ FILE_GENERIC_WRITE, /* access_mask */
+ FILE_SHARE_READ | FILE_SHARE_WRITE, /* share_access */
+ new_create_disposition, /* create_disposition*/
+ 0, /* create_options */
+ dosattrs, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp2, /* result */
+ NULL, /* psbuf */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ close_file_free(NULL, &fsp1, ERROR_CLOSE);
+ goto out;
+ }
+
+ /* Do the actual copy. */
+ if (smb_fname_src->st.st_ex_size) {
+ ret = vfs_transfer_file(fsp1, fsp2, smb_fname_src->st.st_ex_size);
+ } else {
+ ret = 0;
+ }
+
+ close_file_free(NULL, &fsp1, NORMAL_CLOSE);
+
+ /* Ensure the modtime is set correctly on the destination file. */
+ set_close_write_time(fsp2, smb_fname_src->st.st_ex_mtime);
+
+ /*
+ * As we are opening fsp1 read-only we only expect
+ * an error on close on fsp2 if we are out of space.
+ * Thus we don't look at the error return from the
+ * close of fsp1.
+ */
+ status = close_file_free(NULL, &fsp2, NORMAL_CLOSE);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (ret != (off_t)smb_fname_src->st.st_ex_size) {
+ status = NT_STATUS_DISK_FULL;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+
+ out:
+ TALLOC_FREE(smb_fname_dst_tmp);
+ return status;
+}
+
+/****************************************************************************
+ Get a lock offset, dealing with large offset requests.
+****************************************************************************/
+
+uint64_t get_lock_offset(const uint8_t *data, int data_offset,
+ bool large_file_format)
+{
+ uint64_t offset = 0;
+
+ if(!large_file_format) {
+ offset = (uint64_t)IVAL(data,SMB_LKOFF_OFFSET(data_offset));
+ } else {
+ /*
+ * No BVAL, this is reversed!
+ */
+ offset = (((uint64_t) IVAL(data,SMB_LARGE_LKOFF_OFFSET_HIGH(data_offset))) << 32) |
+ ((uint64_t) IVAL(data,SMB_LARGE_LKOFF_OFFSET_LOW(data_offset)));
+ }
+
+ return offset;
+}
+
+struct smbd_do_unlocking_state {
+ struct files_struct *fsp;
+ uint16_t num_ulocks;
+ struct smbd_lock_element *ulocks;
+ NTSTATUS status;
+};
+
+static void smbd_do_unlocking_fn(
+ struct share_mode_lock *lck,
+ void *private_data)
+{
+ struct smbd_do_unlocking_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+ uint16_t i;
+
+ for (i = 0; i < state->num_ulocks; i++) {
+ struct smbd_lock_element *e = &state->ulocks[i];
+
+ DBG_DEBUG("unlock start=%"PRIu64", len=%"PRIu64" for "
+ "pid %"PRIu64", file %s\n",
+ e->offset,
+ e->count,
+ e->smblctx,
+ fsp_str_dbg(fsp));
+
+ if (e->brltype != UNLOCK_LOCK) {
+ /* this can only happen with SMB2 */
+ state->status = NT_STATUS_INVALID_PARAMETER;
+ return;
+ }
+
+ state->status = do_unlock(
+ fsp, e->smblctx, e->count, e->offset, e->lock_flav);
+
+ DBG_DEBUG("do_unlock returned %s\n",
+ nt_errstr(state->status));
+
+ if (!NT_STATUS_IS_OK(state->status)) {
+ return;
+ }
+ }
+
+ share_mode_wakeup_waiters(fsp->file_id);
+}
+
+NTSTATUS smbd_do_unlocking(struct smb_request *req,
+ files_struct *fsp,
+ uint16_t num_ulocks,
+ struct smbd_lock_element *ulocks)
+{
+ struct smbd_do_unlocking_state state = {
+ .fsp = fsp,
+ .num_ulocks = num_ulocks,
+ .ulocks = ulocks,
+ };
+ NTSTATUS status;
+
+ DBG_NOTICE("%s num_ulocks=%"PRIu16"\n", fsp_fnum_dbg(fsp), num_ulocks);
+
+ status = share_mode_do_locked_vfs_allowed(
+ fsp->file_id, smbd_do_unlocking_fn, &state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("share_mode_do_locked_vfs_allowed failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_DEBUG("smbd_do_unlocking_fn failed: %s\n",
+ nt_errstr(status));
+ return state.status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_server.c b/source3/smbd/smb2_server.c
new file mode 100644
index 0000000..afb86ab
--- /dev/null
+++ b/source3/smbd/smb2_server.c
@@ -0,0 +1,5235 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/network.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "lib/param/param.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "smbprofile.h"
+#include "../lib/util/bitmap.h"
+#include "../librpc/gen_ndr/krb5pac.h"
+#include "lib/util/iov_buf.h"
+#include "auth.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "source3/lib/substitute.h"
+
+#if defined(LINUX)
+/* SIOCOUTQ TIOCOUTQ are the same */
+#define __IOCTL_SEND_QUEUE_SIZE_OPCODE TIOCOUTQ
+#define __HAVE_TCP_INFO_RTO 1
+#define __ALLOW_MULTI_CHANNEL_SUPPORT 1
+#elif defined(FREEBSD)
+#define __IOCTL_SEND_QUEUE_SIZE_OPCODE FIONWRITE
+#define __HAVE_TCP_INFO_RTO 1
+#define __ALLOW_MULTI_CHANNEL_SUPPORT 1
+#endif
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static void smbd_smb2_connection_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data);
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn);
+
+static const struct smbd_smb2_dispatch_table {
+ uint16_t opcode;
+ uint16_t fileid_ofs;
+ bool need_session : 1;
+ bool need_tcon : 1;
+ bool as_root : 1;
+ bool modify : 1;
+} smbd_smb2_table[] = {
+ {
+ .opcode = SMB2_OP_NEGPROT,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_SESSSETUP,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_LOGOFF,
+ .need_session = true,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_TCON,
+ .need_session = true,
+ /*
+ * This call needs to be run as root.
+ *
+ * smbd_smb2_request_process_tcon()
+ * calls make_connection_snum(), which will call
+ * change_to_user(), when needed.
+ */
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_TDIS,
+ .need_session = true,
+ .need_tcon = true,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_CREATE,
+ .need_session = true,
+ .need_tcon = true,
+ },{
+ .opcode = SMB2_OP_CLOSE,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_FLUSH,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_READ,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x10,
+ },{
+ .opcode = SMB2_OP_WRITE,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x10,
+ .modify = true,
+ },{
+ .opcode = SMB2_OP_LOCK,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_IOCTL,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ .modify = true,
+ },{
+ .opcode = SMB2_OP_CANCEL,
+ .as_root = true,
+ },{
+ .opcode = SMB2_OP_KEEPALIVE,
+ },{
+ .opcode = SMB2_OP_QUERY_DIRECTORY,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_NOTIFY,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x08,
+ },{
+ .opcode = SMB2_OP_GETINFO,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x18,
+ },{
+ .opcode = SMB2_OP_SETINFO,
+ .need_session = true,
+ .need_tcon = true,
+ .fileid_ofs = 0x10,
+ .modify = true,
+ },{
+ .opcode = SMB2_OP_BREAK,
+ .need_session = true,
+ .need_tcon = true,
+ /*
+ * we do not set
+ * .fileid_ofs here
+ * as LEASE breaks does not
+ * have a file id
+ */
+ }
+};
+
+const char *smb2_opcode_name(uint16_t opcode)
+{
+ const char *result = "Bad SMB2 opcode";
+
+ switch (opcode) {
+ case SMB2_OP_NEGPROT:
+ result = "SMB2_OP_NEGPROT";
+ break;
+ case SMB2_OP_SESSSETUP:
+ result = "SMB2_OP_SESSSETUP";
+ break;
+ case SMB2_OP_LOGOFF:
+ result = "SMB2_OP_LOGOFF";
+ break;
+ case SMB2_OP_TCON:
+ result = "SMB2_OP_TCON";
+ break;
+ case SMB2_OP_TDIS:
+ result = "SMB2_OP_TDIS";
+ break;
+ case SMB2_OP_CREATE:
+ result = "SMB2_OP_CREATE";
+ break;
+ case SMB2_OP_CLOSE:
+ result = "SMB2_OP_CLOSE";
+ break;
+ case SMB2_OP_FLUSH:
+ result = "SMB2_OP_FLUSH";
+ break;
+ case SMB2_OP_READ:
+ result = "SMB2_OP_READ";
+ break;
+ case SMB2_OP_WRITE:
+ result = "SMB2_OP_WRITE";
+ break;
+ case SMB2_OP_LOCK:
+ result = "SMB2_OP_LOCK";
+ break;
+ case SMB2_OP_IOCTL:
+ result = "SMB2_OP_IOCTL";
+ break;
+ case SMB2_OP_CANCEL:
+ result = "SMB2_OP_CANCEL";
+ break;
+ case SMB2_OP_KEEPALIVE:
+ result = "SMB2_OP_KEEPALIVE";
+ break;
+ case SMB2_OP_QUERY_DIRECTORY:
+ result = "SMB2_OP_QUERY_DIRECTORY";
+ break;
+ case SMB2_OP_NOTIFY:
+ result = "SMB2_OP_NOTIFY";
+ break;
+ case SMB2_OP_GETINFO:
+ result = "SMB2_OP_GETINFO";
+ break;
+ case SMB2_OP_SETINFO:
+ result = "SMB2_OP_SETINFO";
+ break;
+ case SMB2_OP_BREAK:
+ result = "SMB2_OP_BREAK";
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+static const struct smbd_smb2_dispatch_table *smbd_smb2_call(uint16_t opcode)
+{
+ const struct smbd_smb2_dispatch_table *ret = NULL;
+
+ if (opcode >= ARRAY_SIZE(smbd_smb2_table)) {
+ return NULL;
+ }
+
+ ret = &smbd_smb2_table[opcode];
+
+ SMB_ASSERT(ret->opcode == opcode);
+
+ return ret;
+}
+
+static void print_req_vectors(const struct smbd_smb2_request *req)
+{
+ int i;
+
+ for (i = 0; i < req->in.vector_count; i++) {
+ dbgtext("\treq->in.vector[%u].iov_len = %u\n",
+ (unsigned int)i,
+ (unsigned int)req->in.vector[i].iov_len);
+ }
+ for (i = 0; i < req->out.vector_count; i++) {
+ dbgtext("\treq->out.vector[%u].iov_len = %u\n",
+ (unsigned int)i,
+ (unsigned int)req->out.vector[i].iov_len);
+ }
+}
+
+bool smbd_is_smb2_header(const uint8_t *inbuf, size_t size)
+{
+ if (size < (4 + SMB2_HDR_BODY)) {
+ return false;
+ }
+
+ if (IVAL(inbuf, 4) != SMB2_MAGIC) {
+ return false;
+ }
+
+ return true;
+}
+
+bool smbd_smb2_is_compound(const struct smbd_smb2_request *req)
+{
+ return req->in.vector_count >= (2*SMBD_SMB2_NUM_IOV_PER_REQ);
+}
+
+bool smbd_smb2_is_last_in_compound(const struct smbd_smb2_request *req)
+{
+ return (req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ ==
+ req->in.vector_count);
+}
+
+static NTSTATUS smbd_initialize_smb2(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low)
+{
+ int rc;
+
+ xconn->smb2.credits.seq_low = expected_seq_low;
+ xconn->smb2.credits.seq_range = 1;
+ xconn->smb2.credits.granted = 1;
+ xconn->smb2.credits.max = lp_smb2_max_credits();
+ xconn->smb2.credits.bitmap = bitmap_talloc(xconn,
+ xconn->smb2.credits.max);
+ if (xconn->smb2.credits.bitmap == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ tevent_fd_set_close_fn(xconn->transport.fde, NULL);
+ TALLOC_FREE(xconn->transport.fde);
+
+ xconn->transport.fde = tevent_add_fd(
+ xconn->client->raw_ev_ctx,
+ xconn,
+ xconn->transport.sock,
+ TEVENT_FD_ERROR | TEVENT_FD_READ,
+ smbd_smb2_connection_handler,
+ xconn);
+ if (xconn->transport.fde == NULL) {
+ close(xconn->transport.sock);
+ xconn->transport.sock = -1;
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_fd_set_auto_close(xconn->transport.fde);
+
+ /*
+ * Ensure child is set to non-blocking mode,
+ * unless the system supports MSG_DONTWAIT,
+ * if MSG_DONTWAIT is available we should force
+ * blocking mode.
+ */
+#ifdef MSG_DONTWAIT
+ rc = set_blocking(xconn->transport.sock, true);
+ if (rc < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+#else
+ rc = set_blocking(xconn->transport.sock, false);
+ if (rc < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+#endif
+
+ return NT_STATUS_OK;
+}
+
+#define smb2_len(buf) (PVAL(buf,3)|(PVAL(buf,2)<<8)|(PVAL(buf,1)<<16))
+#define _smb2_setlen(_buf,len) do { \
+ uint8_t *buf = (uint8_t *)_buf; \
+ buf[0] = 0; \
+ buf[1] = ((len)&0xFF0000)>>16; \
+ buf[2] = ((len)&0xFF00)>>8; \
+ buf[3] = (len)&0xFF; \
+} while (0)
+
+static bool smb2_setup_nbt_length(struct iovec *vector, int count)
+{
+ ssize_t len;
+
+ if (count == 0) {
+ return false;
+ }
+
+ len = iov_buflen(vector+1, count-1);
+
+ if ((len == -1) || (len > 0xFFFFFF)) {
+ return false;
+ }
+
+ _smb2_setlen(vector[0].iov_base, len);
+ return true;
+}
+
+static int smbd_smb2_request_destructor(struct smbd_smb2_request *req)
+{
+ TALLOC_FREE(req->first_enc_key);
+ TALLOC_FREE(req->last_sign_key);
+ return 0;
+}
+
+void smb2_request_set_async_internal(struct smbd_smb2_request *req,
+ bool async_internal)
+{
+ req->async_internal = async_internal;
+}
+
+static struct smbd_smb2_request *smbd_smb2_request_allocate(struct smbXsrv_connection *xconn)
+{
+ TALLOC_CTX *mem_pool;
+ struct smbd_smb2_request *req;
+
+#if 0
+ /* Enable this to find subtle valgrind errors. */
+ mem_pool = talloc_init("smbd_smb2_request_allocate");
+#else
+ mem_pool = talloc_tos();
+#endif
+ if (mem_pool == NULL) {
+ return NULL;
+ }
+
+ req = talloc(mem_pool, struct smbd_smb2_request);
+ if (req == NULL) {
+ talloc_free(mem_pool);
+ return NULL;
+ }
+ talloc_reparent(mem_pool, xconn, req);
+#if 0
+ TALLOC_FREE(mem_pool);
+#endif
+ *req = (struct smbd_smb2_request) {
+ .sconn = xconn->client->sconn,
+ .xconn = xconn,
+ .last_session_id = UINT64_MAX,
+ .last_tid = UINT32_MAX,
+ };
+
+ talloc_set_destructor(req, smbd_smb2_request_destructor);
+
+ return req;
+}
+
+static NTSTATUS smbd_smb2_inbuf_parse_compound(struct smbXsrv_connection *xconn,
+ NTTIME now,
+ uint8_t *buf,
+ size_t buflen,
+ struct smbd_smb2_request *req,
+ struct iovec **piov,
+ int *pnum_iov)
+{
+ TALLOC_CTX *mem_ctx = req;
+ struct iovec *iov;
+ int num_iov = 1;
+ size_t taken = 0;
+ uint8_t *first_hdr = buf;
+ size_t verified_buflen = 0;
+ uint8_t *tf = NULL;
+ size_t tf_len = 0;
+
+ /*
+ * Note: index '0' is reserved for the transport protocol
+ */
+ iov = req->in._vector;
+
+ while (taken < buflen) {
+ size_t len = buflen - taken;
+ uint8_t *hdr = first_hdr + taken;
+ struct iovec *cur;
+ size_t full_size;
+ size_t next_command_ofs;
+ uint16_t body_size;
+ uint8_t *body = NULL;
+ uint32_t dyn_size;
+ uint8_t *dyn = NULL;
+ struct iovec *iov_alloc = NULL;
+
+ if (iov != req->in._vector) {
+ iov_alloc = iov;
+ }
+
+ if (verified_buflen > taken) {
+ len = verified_buflen - taken;
+ } else {
+ tf = NULL;
+ tf_len = 0;
+ }
+
+ if (len < 4) {
+ DEBUG(10, ("%d bytes left, expected at least %d\n",
+ (int)len, 4));
+ goto inval;
+ }
+ if (IVAL(hdr, 0) == SMB2_TF_MAGIC) {
+ struct smbXsrv_session *s = NULL;
+ uint64_t uid;
+ struct iovec tf_iov[2];
+ NTSTATUS status;
+ size_t enc_len;
+
+ if (xconn->protocol < PROTOCOL_SMB3_00) {
+ DEBUG(10, ("Got SMB2_TRANSFORM header, "
+ "but dialect[0x%04X] is used\n",
+ xconn->smb2.server.dialect));
+ goto inval;
+ }
+
+ if (xconn->smb2.server.cipher == 0) {
+ DEBUG(10, ("Got SMB2_TRANSFORM header, "
+ "but not negotiated "
+ "client[0x%08X] server[0x%08X]\n",
+ xconn->smb2.client.capabilities,
+ xconn->smb2.server.capabilities));
+ goto inval;
+ }
+
+ if (len < SMB2_TF_HDR_SIZE) {
+ DEBUG(1, ("%d bytes left, expected at least %d\n",
+ (int)len, SMB2_TF_HDR_SIZE));
+ goto inval;
+ }
+ tf = hdr;
+ tf_len = SMB2_TF_HDR_SIZE;
+ taken += tf_len;
+
+ hdr = first_hdr + taken;
+ enc_len = IVAL(tf, SMB2_TF_MSG_SIZE);
+ uid = BVAL(tf, SMB2_TF_SESSION_ID);
+
+ if (len < SMB2_TF_HDR_SIZE + enc_len) {
+ DEBUG(1, ("%d bytes left, expected at least %d\n",
+ (int)len,
+ (int)(SMB2_TF_HDR_SIZE + enc_len)));
+ goto inval;
+ }
+
+ status = smb2srv_session_lookup_conn(xconn, uid, now,
+ &s);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smb2srv_session_lookup_global(xconn->client,
+ uid, req, &s);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("invalid session[%llu] in "
+ "SMB2_TRANSFORM header\n",
+ (unsigned long long)uid));
+ TALLOC_FREE(iov_alloc);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ tf_iov[0].iov_base = (void *)tf;
+ tf_iov[0].iov_len = tf_len;
+ tf_iov[1].iov_base = (void *)hdr;
+ tf_iov[1].iov_len = enc_len;
+
+ status = smb2_signing_decrypt_pdu(s->global->decryption_key,
+ tf_iov, 2);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(iov_alloc);
+ return status;
+ }
+
+ verified_buflen = taken + enc_len;
+ len = enc_len;
+ }
+
+ /*
+ * We need the header plus the body length field
+ */
+
+ if (len < SMB2_HDR_BODY + 2) {
+
+ if ((len == 5) &&
+ (IVAL(hdr, 0) == SMB_SUICIDE_PACKET) &&
+ lp_parm_bool(-1, "smbd", "suicide mode", false)) {
+ uint8_t exitcode = CVAL(hdr, 4);
+ DBG_WARNING("SUICIDE: Exiting immediately "
+ "with code %"PRIu8"\n",
+ exitcode);
+ exit(exitcode);
+ }
+
+ DEBUG(10, ("%d bytes left, expected at least %d\n",
+ (int)len, SMB2_HDR_BODY));
+ goto inval;
+ }
+ if (IVAL(hdr, 0) != SMB2_MAGIC) {
+ DEBUG(10, ("Got non-SMB2 PDU: %x\n",
+ IVAL(hdr, 0)));
+ goto inval;
+ }
+ if (SVAL(hdr, 4) != SMB2_HDR_BODY) {
+ DEBUG(10, ("Got HDR len %d, expected %d\n",
+ SVAL(hdr, 4), SMB2_HDR_BODY));
+ goto inval;
+ }
+
+ full_size = len;
+ next_command_ofs = IVAL(hdr, SMB2_HDR_NEXT_COMMAND);
+ body_size = SVAL(hdr, SMB2_HDR_BODY);
+
+ if (next_command_ofs != 0) {
+ if (next_command_ofs < (SMB2_HDR_BODY + 2)) {
+ goto inval;
+ }
+ if (next_command_ofs > full_size) {
+ goto inval;
+ }
+ full_size = next_command_ofs;
+ }
+ if (body_size < 2) {
+ goto inval;
+ }
+ body_size &= 0xfffe;
+
+ if (body_size > (full_size - SMB2_HDR_BODY)) {
+ /*
+ * let the caller handle the error
+ */
+ body_size = full_size - SMB2_HDR_BODY;
+ }
+ body = hdr + SMB2_HDR_BODY;
+ dyn = body + body_size;
+ dyn_size = full_size - (SMB2_HDR_BODY + body_size);
+
+ if (num_iov >= ARRAY_SIZE(req->in._vector)) {
+ struct iovec *iov_tmp = NULL;
+
+ iov_tmp = talloc_realloc(mem_ctx, iov_alloc,
+ struct iovec,
+ num_iov +
+ SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (iov_tmp == NULL) {
+ TALLOC_FREE(iov_alloc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (iov_alloc == NULL) {
+ memcpy(iov_tmp,
+ req->in._vector,
+ sizeof(req->in._vector));
+ }
+
+ iov = iov_tmp;
+ }
+ cur = &iov[num_iov];
+ num_iov += SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ cur[SMBD_SMB2_TF_IOV_OFS].iov_base = tf;
+ cur[SMBD_SMB2_TF_IOV_OFS].iov_len = tf_len;
+ cur[SMBD_SMB2_HDR_IOV_OFS].iov_base = hdr;
+ cur[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+ cur[SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
+ cur[SMBD_SMB2_BODY_IOV_OFS].iov_len = body_size;
+ cur[SMBD_SMB2_DYN_IOV_OFS].iov_base = dyn;
+ cur[SMBD_SMB2_DYN_IOV_OFS].iov_len = dyn_size;
+
+ taken += full_size;
+ }
+
+ *piov = iov;
+ *pnum_iov = num_iov;
+ return NT_STATUS_OK;
+
+inval:
+ if (iov != req->in._vector) {
+ TALLOC_FREE(iov);
+ }
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+static NTSTATUS smbd_smb2_request_create(struct smbXsrv_connection *xconn,
+ const uint8_t *_inpdu, size_t size,
+ struct smbd_smb2_request **_req)
+{
+ struct smbd_smb2_request *req;
+ uint32_t protocol_version;
+ uint8_t *inpdu = NULL;
+ const uint8_t *inhdr = NULL;
+ uint16_t cmd;
+ uint32_t next_command_ofs;
+ NTSTATUS status;
+ NTTIME now;
+
+ if (size < (SMB2_HDR_BODY + 2)) {
+ DEBUG(0,("Invalid SMB2 packet length count %ld\n", (long)size));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ inhdr = _inpdu;
+
+ protocol_version = IVAL(inhdr, SMB2_HDR_PROTOCOL_ID);
+ if (protocol_version != SMB2_MAGIC) {
+ DEBUG(0,("Invalid SMB packet: protocol prefix: 0x%08X\n",
+ protocol_version));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ cmd = SVAL(inhdr, SMB2_HDR_OPCODE);
+ if (cmd != SMB2_OP_NEGPROT) {
+ DEBUG(0,("Invalid SMB packet: first request: 0x%04X\n",
+ cmd));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ next_command_ofs = IVAL(inhdr, SMB2_HDR_NEXT_COMMAND);
+ if (next_command_ofs != 0) {
+ DEBUG(0,("Invalid SMB packet: next_command: 0x%08X\n",
+ next_command_ofs));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ req = smbd_smb2_request_allocate(xconn);
+ if (req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ inpdu = talloc_memdup(req, _inpdu, size);
+ if (inpdu == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ req->request_time = timeval_current();
+ now = timeval_to_nttime(&req->request_time);
+
+ status = smbd_smb2_inbuf_parse_compound(xconn,
+ now,
+ inpdu,
+ size,
+ req, &req->in.vector,
+ &req->in.vector_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(req);
+ return status;
+ }
+
+ req->current_idx = 1;
+
+ *_req = req;
+ return NT_STATUS_OK;
+}
+
+static bool smb2_validate_sequence_number(struct smbXsrv_connection *xconn,
+ uint64_t message_id, uint64_t seq_id)
+{
+ struct bitmap *credits_bm = xconn->smb2.credits.bitmap;
+ unsigned int offset;
+ uint64_t seq_tmp;
+
+ seq_tmp = xconn->smb2.credits.seq_low;
+ if (seq_id < seq_tmp) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: bad message_id "
+ "%llu (sequence id %llu) "
+ "(granted = %u, low = %llu, range = %u)\n",
+ (unsigned long long)message_id,
+ (unsigned long long)seq_id,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range);
+ return false;
+ }
+
+ seq_tmp += xconn->smb2.credits.seq_range;
+ if (seq_id >= seq_tmp) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: bad message_id "
+ "%llu (sequence id %llu) "
+ "(granted = %u, low = %llu, range = %u)\n",
+ (unsigned long long)message_id,
+ (unsigned long long)seq_id,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range);
+ return false;
+ }
+
+ offset = seq_id % xconn->smb2.credits.max;
+
+ if (bitmap_query(credits_bm, offset)) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: duplicate message_id "
+ "%llu (sequence id %llu) "
+ "(granted = %u, low = %llu, range = %u) "
+ "(bm offset %u)\n",
+ (unsigned long long)message_id,
+ (unsigned long long)seq_id,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range,
+ offset);
+ return false;
+ }
+
+ /* Mark the message_ids as seen in the bitmap. */
+ bitmap_set(credits_bm, offset);
+
+ if (seq_id != xconn->smb2.credits.seq_low) {
+ return true;
+ }
+
+ /*
+ * Move the window forward by all the message_id's
+ * already seen.
+ */
+ while (bitmap_query(credits_bm, offset)) {
+ DBGC_DEBUG(DBGC_SMB2_CREDITS,
+ "smb2_validate_sequence_number: clearing "
+ "id %llu (position %u) from bitmap\n",
+ (unsigned long long)(xconn->smb2.credits.seq_low),
+ offset);
+ bitmap_clear(credits_bm, offset);
+
+ xconn->smb2.credits.seq_low += 1;
+ xconn->smb2.credits.seq_range -= 1;
+ offset = xconn->smb2.credits.seq_low % xconn->smb2.credits.max;
+ }
+
+ return true;
+}
+
+static bool smb2_validate_message_id(struct smbXsrv_connection *xconn,
+ const uint8_t *inhdr)
+{
+ uint64_t message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ uint16_t opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+ uint16_t credit_charge = 1;
+ uint64_t i;
+
+ if (opcode == SMB2_OP_CANCEL) {
+ /* SMB2_CANCEL requests by definition resend messageids. */
+ return true;
+ }
+
+ if (xconn->smb2.credits.multicredit) {
+ credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
+ credit_charge = MAX(credit_charge, 1);
+ }
+
+ DEBUGC(11,
+ DBGC_SMB2_CREDITS,
+ ("smb2_validate_message_id: mid %llu (charge %llu), "
+ "credits_granted %llu, "
+ "seqnum low/range: %llu/%llu\n",
+ (unsigned long long) message_id,
+ (unsigned long long) credit_charge,
+ (unsigned long long) xconn->smb2.credits.granted,
+ (unsigned long long) xconn->smb2.credits.seq_low,
+ (unsigned long long) xconn->smb2.credits.seq_range));
+
+ if (xconn->smb2.credits.granted < credit_charge) {
+ DBGC_ERR(DBGC_SMB2_CREDITS,
+ "smb2_validate_message_id: client used more "
+ "credits than granted, mid %llu, charge %llu, "
+ "credits_granted %llu, "
+ "seqnum low/range: %llu/%llu\n",
+ (unsigned long long) message_id,
+ (unsigned long long) credit_charge,
+ (unsigned long long) xconn->smb2.credits.granted,
+ (unsigned long long) xconn->smb2.credits.seq_low,
+ (unsigned long long) xconn->smb2.credits.seq_range);
+ return false;
+ }
+
+ /*
+ * now check the message ids
+ *
+ * for multi-credit requests we need to check all current mid plus
+ * the implicit mids caused by the credit charge
+ * e.g. current mid = 15, charge 5 => mark 15-19 as used
+ */
+
+ for (i = 0; i <= (credit_charge-1); i++) {
+ uint64_t id = message_id + i;
+ bool ok;
+
+ DEBUGC(11,
+ DBGC_SMB2_CREDITS,
+ ("Iterating mid %llu charge %u (sequence %llu)\n",
+ (unsigned long long)message_id,
+ credit_charge,
+ (unsigned long long)id));
+
+ ok = smb2_validate_sequence_number(xconn, message_id, id);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ /* subtract used credits */
+ xconn->smb2.credits.granted -= credit_charge;
+
+ return true;
+}
+
+static NTSTATUS smbd_smb2_request_validate(struct smbd_smb2_request *req)
+{
+ int count;
+ int idx;
+
+ count = req->in.vector_count;
+
+ if (count < 1 + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ /* It's not a SMB2 request */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ struct iovec *hdr = SMBD_SMB2_IDX_HDR_IOV(req,in,idx);
+ struct iovec *body = SMBD_SMB2_IDX_BODY_IOV(req,in,idx);
+ const uint8_t *inhdr = NULL;
+
+ if (hdr->iov_len != SMB2_HDR_BODY) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (body->iov_len < 2) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ inhdr = (const uint8_t *)hdr->iov_base;
+
+ /* Check the SMB2 header */
+ if (IVAL(inhdr, SMB2_HDR_PROTOCOL_ID) != SMB2_MAGIC) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!smb2_validate_message_id(req->xconn, inhdr)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smb2_set_operation_credit(struct smbXsrv_connection *xconn,
+ const struct iovec *in_vector,
+ struct iovec *out_vector)
+{
+ const uint8_t *inhdr = (const uint8_t *)in_vector->iov_base;
+ uint8_t *outhdr = (uint8_t *)out_vector->iov_base;
+ uint16_t credit_charge = 1;
+ uint16_t credits_requested;
+ uint32_t out_flags;
+ uint16_t cmd;
+ NTSTATUS out_status;
+ uint16_t credits_granted = 0;
+ uint64_t credits_possible;
+ uint16_t current_max_credits;
+
+ /*
+ * first we grant only 1/16th of the max range.
+ *
+ * Windows also starts with the 1/16th and then grants
+ * more later. I was only able to trigger higher
+ * values, when using a very high credit charge.
+ *
+ * TODO: scale up depending on load, free memory
+ * or other stuff.
+ * Maybe also on the relationship between number
+ * of requests and the used sequence number.
+ * Which means we would grant more credits
+ * for client which use multi credit requests.
+ *
+ * The above is what Windows Server < 2016 is doing,
+ * but new servers use all credits (8192 by default).
+ */
+ current_max_credits = xconn->smb2.credits.max;
+ current_max_credits = MAX(current_max_credits, 1);
+
+ if (xconn->smb2.credits.multicredit) {
+ credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
+ credit_charge = MAX(credit_charge, 1);
+ }
+
+ cmd = SVAL(inhdr, SMB2_HDR_OPCODE);
+ credits_requested = SVAL(inhdr, SMB2_HDR_CREDIT);
+ credits_requested = MAX(credits_requested, 1);
+ out_flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ out_status = NT_STATUS(IVAL(outhdr, SMB2_HDR_STATUS));
+
+ SMB_ASSERT(xconn->smb2.credits.max >= xconn->smb2.credits.granted);
+
+ if (xconn->smb2.credits.max < credit_charge) {
+ smbd_server_connection_terminate(xconn,
+ "client error: credit charge > max credits\n");
+ return;
+ }
+
+ if (out_flags & SMB2_HDR_FLAG_ASYNC) {
+ /*
+ * In case we already send an async interim
+ * response, we should not grant
+ * credits on the final response.
+ */
+ credits_granted = 0;
+ } else {
+ uint16_t additional_possible =
+ xconn->smb2.credits.max - credit_charge;
+ uint16_t additional_max = 0;
+ uint16_t additional_credits = credits_requested - 1;
+
+ switch (cmd) {
+ case SMB2_OP_NEGPROT:
+ break;
+ case SMB2_OP_SESSSETUP:
+ /*
+ * Windows 2012 RC1 starts to grant
+ * additional credits
+ * with a successful session setup
+ */
+ if (NT_STATUS_IS_OK(out_status)) {
+ additional_max = xconn->smb2.credits.max;
+ }
+ break;
+ default:
+ /*
+ * Windows Server < 2016 and older Samba versions
+ * used to only grant additional credits in
+ * chunks of 32 credits.
+ *
+ * But we match Windows Server 2016 and grant
+ * all credits as requested.
+ */
+ additional_max = xconn->smb2.credits.max;
+ break;
+ }
+
+ additional_max = MIN(additional_max, additional_possible);
+ additional_credits = MIN(additional_credits, additional_max);
+
+ credits_granted = credit_charge + additional_credits;
+ }
+
+ /*
+ * sequence numbers should not wrap
+ *
+ * 1. calculate the possible credits until
+ * the sequence numbers start to wrap on 64-bit.
+ *
+ * 2. UINT64_MAX is used for Break Notifications.
+ *
+ * 2. truncate the possible credits to the maximum
+ * credits we want to grant to the client in total.
+ *
+ * 3. remove the range we'll already granted to the client
+ * this makes sure the client consumes the lowest sequence
+ * number, before we can grant additional credits.
+ */
+ credits_possible = UINT64_MAX - xconn->smb2.credits.seq_low;
+ if (credits_possible > 0) {
+ /* remove UINT64_MAX */
+ credits_possible -= 1;
+ }
+ credits_possible = MIN(credits_possible, current_max_credits);
+ credits_possible -= xconn->smb2.credits.seq_range;
+
+ credits_granted = MIN(credits_granted, credits_possible);
+
+ SSVAL(outhdr, SMB2_HDR_CREDIT, credits_granted);
+ xconn->smb2.credits.granted += credits_granted;
+ xconn->smb2.credits.seq_range += credits_granted;
+
+ DBGC_DEBUG(DBGC_SMB2_CREDITS,
+ "smb2_set_operation_credit: requested %u, charge %u, "
+ "granted %u, current possible/max %u/%u, "
+ "total granted/max/low/range %u/%u/%llu/%u\n",
+ (unsigned int)credits_requested,
+ (unsigned int)credit_charge,
+ (unsigned int)credits_granted,
+ (unsigned int)credits_possible,
+ (unsigned int)current_max_credits,
+ (unsigned int)xconn->smb2.credits.granted,
+ (unsigned int)xconn->smb2.credits.max,
+ (unsigned long long)xconn->smb2.credits.seq_low,
+ (unsigned int)xconn->smb2.credits.seq_range);
+}
+
+static void smb2_calculate_credits(const struct smbd_smb2_request *inreq,
+ struct smbd_smb2_request *outreq)
+{
+ int count, idx;
+ uint16_t total_credits = 0;
+
+ count = outreq->out.vector_count;
+
+ for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ struct iovec *inhdr_v = SMBD_SMB2_IDX_HDR_IOV(inreq,in,idx);
+ struct iovec *outhdr_v = SMBD_SMB2_IDX_HDR_IOV(outreq,out,idx);
+ uint8_t *outhdr = (uint8_t *)outhdr_v->iov_base;
+
+ smb2_set_operation_credit(outreq->xconn, inhdr_v, outhdr_v);
+
+ /* To match Windows, count up what we
+ just granted. */
+ total_credits += SVAL(outhdr, SMB2_HDR_CREDIT);
+ /* Set to zero in all but the last reply. */
+ if (idx + SMBD_SMB2_NUM_IOV_PER_REQ < count) {
+ SSVAL(outhdr, SMB2_HDR_CREDIT, 0);
+ } else {
+ SSVAL(outhdr, SMB2_HDR_CREDIT, total_credits);
+ }
+ }
+}
+
+DATA_BLOB smbd_smb2_generate_outbody(struct smbd_smb2_request *req, size_t size)
+{
+ if (req->current_idx <= 1) {
+ if (size <= sizeof(req->out._body)) {
+ return data_blob_const(req->out._body, size);
+ }
+ }
+
+ return data_blob_talloc(req, NULL, size);
+}
+
+static NTSTATUS smbd_smb2_request_setup_out(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ TALLOC_CTX *mem_ctx;
+ struct iovec *vector;
+ int count;
+ int idx;
+ bool ok;
+
+ count = req->in.vector_count;
+ if (count <= ARRAY_SIZE(req->out._vector)) {
+ mem_ctx = req;
+ vector = req->out._vector;
+ } else {
+ vector = talloc_zero_array(req, struct iovec, count);
+ if (vector == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ mem_ctx = vector;
+ }
+
+ vector[0].iov_base = req->out.nbt_hdr;
+ vector[0].iov_len = 4;
+ SIVAL(req->out.nbt_hdr, 0, 0);
+
+ for (idx=1; idx < count; idx += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ struct iovec *inhdr_v = SMBD_SMB2_IDX_HDR_IOV(req,in,idx);
+ const uint8_t *inhdr = (const uint8_t *)inhdr_v->iov_base;
+ uint8_t *outhdr = NULL;
+ uint8_t *outbody = NULL;
+ uint32_t next_command_ofs = 0;
+ struct iovec *current = &vector[idx];
+
+ if ((idx + SMBD_SMB2_NUM_IOV_PER_REQ) < count) {
+ /* we have a next command -
+ * setup for the error case. */
+ next_command_ofs = SMB2_HDR_BODY + 9;
+ }
+
+ if (idx == 1) {
+ outhdr = req->out._hdr;
+ } else {
+ outhdr = talloc_zero_array(mem_ctx, uint8_t,
+ OUTVEC_ALLOC_SIZE);
+ if (outhdr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ outbody = outhdr + SMB2_HDR_BODY;
+
+ /*
+ * SMBD_SMB2_TF_IOV_OFS might be used later
+ */
+ current[SMBD_SMB2_TF_IOV_OFS].iov_base = NULL;
+ current[SMBD_SMB2_TF_IOV_OFS].iov_len = 0;
+
+ current[SMBD_SMB2_HDR_IOV_OFS].iov_base = (void *)outhdr;
+ current[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+
+ current[SMBD_SMB2_BODY_IOV_OFS].iov_base = (void *)outbody;
+ current[SMBD_SMB2_BODY_IOV_OFS].iov_len = 8;
+
+ current[SMBD_SMB2_DYN_IOV_OFS].iov_base = NULL;
+ current[SMBD_SMB2_DYN_IOV_OFS].iov_len = 0;
+
+ /* setup the SMB2 header */
+ SIVAL(outhdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+ SSVAL(outhdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+ SSVAL(outhdr, SMB2_HDR_CREDIT_CHARGE,
+ SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE));
+ SIVAL(outhdr, SMB2_HDR_STATUS,
+ NT_STATUS_V(NT_STATUS_INTERNAL_ERROR));
+ SSVAL(outhdr, SMB2_HDR_OPCODE,
+ SVAL(inhdr, SMB2_HDR_OPCODE));
+ SIVAL(outhdr, SMB2_HDR_FLAGS,
+ IVAL(inhdr, SMB2_HDR_FLAGS) | SMB2_HDR_FLAG_REDIRECT);
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, next_command_ofs);
+ SBVAL(outhdr, SMB2_HDR_MESSAGE_ID,
+ BVAL(inhdr, SMB2_HDR_MESSAGE_ID));
+ SIVAL(outhdr, SMB2_HDR_PID,
+ IVAL(inhdr, SMB2_HDR_PID));
+ SIVAL(outhdr, SMB2_HDR_TID,
+ IVAL(inhdr, SMB2_HDR_TID));
+ SBVAL(outhdr, SMB2_HDR_SESSION_ID,
+ BVAL(inhdr, SMB2_HDR_SESSION_ID));
+ memcpy(outhdr + SMB2_HDR_SIGNATURE,
+ inhdr + SMB2_HDR_SIGNATURE, 16);
+
+ /* setup error body header */
+ SSVAL(outbody, 0x00, 0x08 + 1);
+ SSVAL(outbody, 0x02, 0);
+ SIVAL(outbody, 0x04, 0);
+ }
+
+ req->out.vector = vector;
+ req->out.vector_count = count;
+
+ /* setup the length of the NBT packet */
+ ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ DLIST_ADD_END(xconn->smb2.requests, req);
+
+ return NT_STATUS_OK;
+}
+
+bool smbXsrv_server_multi_channel_enabled(void)
+{
+ bool enabled = lp_server_multi_channel_support();
+#ifndef __ALLOW_MULTI_CHANNEL_SUPPORT
+ bool forced = false;
+ struct loadparm_context *lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+ bool unspecified = lpcfg_parm_is_unspecified(lp_ctx, "server multi channel support");
+ if (unspecified) {
+ enabled = false;
+ }
+ /*
+ * If we don't have support from the kernel
+ * to ask for the un-acked number of bytes
+ * in the socket send queue, we better
+ * don't support multi-channel.
+ */
+ forced = lp_parm_bool(-1, "force", "server multi channel support", false);
+ if (enabled && !forced) {
+ D_NOTICE("'server multi channel support' enabled "
+ "but not supported on %s (%s)\n",
+ SYSTEM_UNAME_SYSNAME, SYSTEM_UNAME_RELEASE);
+ DEBUGADD(DBGLVL_NOTICE, ("Please report this on "
+ "https://bugzilla.samba.org/show_bug.cgi?id=11897\n"));
+ enabled = false;
+ }
+ TALLOC_FREE(lp_ctx);
+#endif /* ! __ALLOW_MULTI_CHANNEL_SUPPORT */
+ return enabled;
+}
+
+static NTSTATUS smbXsrv_connection_get_rto_usecs(struct smbXsrv_connection *xconn,
+ uint32_t *_rto_usecs)
+{
+ /*
+ * Define an Retransmission Timeout
+ * of 1 second, if there's no way for the
+ * kernel to tell us the current value.
+ */
+ uint32_t rto_usecs = 1000000;
+
+#ifdef __HAVE_TCP_INFO_RTO
+ {
+ struct tcp_info info;
+ socklen_t ilen = sizeof(info);
+ int ret;
+
+ ZERO_STRUCT(info);
+ ret = getsockopt(xconn->transport.sock,
+ IPPROTO_TCP, TCP_INFO,
+ (void *)&info, &ilen);
+ if (ret != 0) {
+ int saved_errno = errno;
+ NTSTATUS status = map_nt_error_from_unix(errno);
+ DBG_ERR("getsockopt(TCP_INFO) errno[%d/%s] -s %s\n",
+ saved_errno, strerror(saved_errno),
+ nt_errstr(status));
+ return status;
+ }
+
+ DBG_DEBUG("tcpi_rto[%u] tcpi_rtt[%u] tcpi_rttvar[%u]\n",
+ (unsigned)info.tcpi_rto,
+ (unsigned)info.tcpi_rtt,
+ (unsigned)info.tcpi_rttvar);
+ rto_usecs = info.tcpi_rto;
+ }
+#endif /* __HAVE_TCP_INFO_RTO */
+
+ rto_usecs = MAX(rto_usecs, 200000); /* at least 0.2s */
+ rto_usecs = MIN(rto_usecs, 1000000); /* at max 1.0s */
+ *_rto_usecs = rto_usecs;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_connection_get_acked_bytes(struct smbXsrv_connection *xconn,
+ uint64_t *_acked_bytes)
+{
+ /*
+ * Unless the kernel has an interface
+ * to reveal the number of un-acked bytes
+ * in the socket send queue, we'll assume
+ * everything is already acked.
+ *
+ * But that would mean that we better don't
+ * pretent to support multi-channel.
+ */
+ uint64_t unacked_bytes = 0;
+
+ *_acked_bytes = 0;
+
+ if (xconn->ack.force_unacked_timeout) {
+ /*
+ * Smbtorture tries to test channel failures...
+ * Just pretend nothing was acked...
+ */
+ DBG_INFO("Simulating channel failure: "
+ "xconn->ack.unacked_bytes[%llu]\n",
+ (unsigned long long)xconn->ack.unacked_bytes);
+ return NT_STATUS_OK;
+ }
+
+#ifdef __IOCTL_SEND_QUEUE_SIZE_OPCODE
+ {
+ int value = 0;
+ int ret;
+
+ /*
+ * If we have kernel support to get
+ * the number of bytes waiting in
+ * the socket's send queue, we
+ * use that in order to find out
+ * the number of unacked bytes.
+ */
+ ret = ioctl(xconn->transport.sock,
+ __IOCTL_SEND_QUEUE_SIZE_OPCODE,
+ &value);
+ if (ret != 0) {
+ int saved_errno = errno;
+ NTSTATUS status = map_nt_error_from_unix(saved_errno);
+ DBG_ERR("Failed to get the SEND_QUEUE_SIZE - "
+ "errno %d (%s) - %s\n",
+ saved_errno, strerror(saved_errno),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (value < 0) {
+ DBG_ERR("xconn->ack.unacked_bytes[%llu] value[%d]\n",
+ (unsigned long long)xconn->ack.unacked_bytes,
+ value);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ unacked_bytes = value;
+ }
+#endif
+ if (xconn->ack.unacked_bytes == 0) {
+ xconn->ack.unacked_bytes = unacked_bytes;
+ return NT_STATUS_OK;
+ }
+
+ if (xconn->ack.unacked_bytes < unacked_bytes) {
+ DBG_ERR("xconn->ack.unacked_bytes[%llu] unacked_bytes[%llu]\n",
+ (unsigned long long)xconn->ack.unacked_bytes,
+ (unsigned long long)unacked_bytes);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ *_acked_bytes = xconn->ack.unacked_bytes - unacked_bytes;
+ xconn->ack.unacked_bytes = unacked_bytes;
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_send_queue_ack_fail(struct smbd_smb2_send_queue **queue,
+ NTSTATUS status)
+{
+ struct smbd_smb2_send_queue *e = NULL;
+ struct smbd_smb2_send_queue *n = NULL;
+
+ for (e = *queue; e != NULL; e = n) {
+ n = e->next;
+
+ DLIST_REMOVE(*queue, e);
+ if (e->ack.req != NULL) {
+ tevent_req_nterror(e->ack.req, status);
+ }
+ }
+}
+
+static NTSTATUS smbd_smb2_send_queue_ack_bytes(struct smbd_smb2_send_queue **queue,
+ uint64_t acked_bytes)
+{
+ struct smbd_smb2_send_queue *e = NULL;
+ struct smbd_smb2_send_queue *n = NULL;
+
+ for (e = *queue; e != NULL; e = n) {
+ bool expired;
+
+ n = e->next;
+
+ if (e->ack.req == NULL) {
+ continue;
+ }
+
+ if (e->ack.required_acked_bytes <= acked_bytes) {
+ e->ack.required_acked_bytes = 0;
+ DLIST_REMOVE(*queue, e);
+ tevent_req_done(e->ack.req);
+ continue;
+ }
+ e->ack.required_acked_bytes -= acked_bytes;
+
+ expired = timeval_expired(&e->ack.timeout);
+ if (expired) {
+ return NT_STATUS_IO_TIMEOUT;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_check_ack_queue(struct smbXsrv_connection *xconn)
+{
+ uint64_t acked_bytes = 0;
+ NTSTATUS status;
+
+ status = smbXsrv_connection_get_acked_bytes(xconn, &acked_bytes);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_send_queue_ack_bytes(&xconn->ack.queue, acked_bytes);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_send_queue_ack_bytes(&xconn->smb2.send_queue, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_connection_ack_checker(struct tevent_req *subreq)
+{
+ struct smbXsrv_connection *xconn =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_connection);
+ struct smbXsrv_client *client = xconn->client;
+ struct timeval next_check;
+ NTSTATUS status;
+ bool ok;
+
+ xconn->ack.checker_subreq = NULL;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ smbd_server_connection_terminate(xconn,
+ "tevent_wakeup_recv() failed");
+ return;
+ }
+
+ status = smbd_smb2_check_ack_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+
+ next_check = timeval_current_ofs_usec(xconn->ack.rto_usecs);
+ xconn->ack.checker_subreq = tevent_wakeup_send(xconn,
+ client->raw_ev_ctx,
+ next_check);
+ if (xconn->ack.checker_subreq == NULL) {
+ smbd_server_connection_terminate(xconn,
+ "tevent_wakeup_send() failed");
+ return;
+ }
+ tevent_req_set_callback(xconn->ack.checker_subreq,
+ smbXsrv_connection_ack_checker,
+ xconn);
+}
+
+static NTSTATUS smbXsrv_client_pending_breaks_updated(struct smbXsrv_client *client)
+{
+ struct smbXsrv_connection *xconn = NULL;
+
+ for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
+ struct timeval next_check;
+ uint64_t acked_bytes = 0;
+ NTSTATUS status;
+
+ /*
+ * A new 'pending break cycle' starts
+ * with a first pending break and lasts until
+ * all pending breaks are finished.
+ *
+ * This is typically a very short time,
+ * the value of one retransmission timeout.
+ */
+
+ if (client->pending_breaks == NULL) {
+ /*
+ * No more pending breaks, remove a pending
+ * checker timer
+ */
+ TALLOC_FREE(xconn->ack.checker_subreq);
+ continue;
+ }
+
+ if (xconn->ack.checker_subreq != NULL) {
+ /*
+ * The cycle already started =>
+ * nothing todo
+ */
+ continue;
+ }
+
+ /*
+ * Get the current retransmission timeout value.
+ *
+ * It may change over time, but fetching it once
+ * per 'pending break' cycled should be enough.
+ */
+ status = smbXsrv_connection_get_rto_usecs(xconn,
+ &xconn->ack.rto_usecs);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * At the start of the cycle we reset the
+ * unacked_bytes counter (first to 0 and
+ * within smbXsrv_connection_get_acked_bytes()
+ * to the current value in the kernel
+ * send queue.
+ */
+ xconn->ack.unacked_bytes = 0;
+ status = smbXsrv_connection_get_acked_bytes(xconn, &acked_bytes);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We setup a timer in order to check for
+ * acked bytes after one retransmission timeout.
+ *
+ * The code that sets up the send_queue.ack.timeout
+ * uses a multiple of the retransmission timeout.
+ */
+ next_check = timeval_current_ofs_usec(xconn->ack.rto_usecs);
+ xconn->ack.checker_subreq = tevent_wakeup_send(xconn,
+ client->raw_ev_ctx,
+ next_check);
+ if (xconn->ack.checker_subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(xconn->ack.checker_subreq,
+ smbXsrv_connection_ack_checker,
+ xconn);
+ }
+
+ return NT_STATUS_OK;
+}
+
+void smbXsrv_connection_disconnect_transport(struct smbXsrv_connection *xconn,
+ NTSTATUS status)
+{
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ return;
+ }
+
+ xconn->transport.status = status;
+ TALLOC_FREE(xconn->transport.fde);
+ if (xconn->transport.sock != -1) {
+ xconn->transport.sock = -1;
+ }
+ smbd_smb2_send_queue_ack_fail(&xconn->ack.queue, status);
+ smbd_smb2_send_queue_ack_fail(&xconn->smb2.send_queue, status);
+ xconn->smb2.send_queue_len = 0;
+ DO_PROFILE_INC(disconnect);
+}
+
+size_t smbXsrv_client_valid_connections(struct smbXsrv_client *client)
+{
+ struct smbXsrv_connection *xconn = NULL;
+ size_t num_ok = 0;
+
+ for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
+ if (NT_STATUS_IS_OK(xconn->transport.status)) {
+ num_ok++;
+ }
+ }
+
+ return num_ok;
+}
+
+struct smbXsrv_connection_shutdown_state {
+ struct smbXsrv_connection *xconn;
+};
+
+static void smbXsrv_connection_shutdown_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbXsrv_connection_shutdown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *xconn)
+{
+ struct tevent_req *req = NULL;
+ struct smbXsrv_connection_shutdown_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ size_t len = 0;
+ struct smbd_smb2_request *preq = NULL;
+ NTSTATUS status;
+
+ /*
+ * The caller should have called
+ * smbXsrv_connection_disconnect_transport() before.
+ */
+ SMB_ASSERT(!NT_STATUS_IS_OK(xconn->transport.status));
+ SMB_ASSERT(xconn->transport.terminating);
+ SMB_ASSERT(xconn->transport.shutdown_wait_queue == NULL);
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbXsrv_connection_shutdown_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->xconn = xconn;
+ tevent_req_defer_callback(req, ev);
+
+ xconn->transport.shutdown_wait_queue =
+ tevent_queue_create(state, "smbXsrv_connection_shutdown_queue");
+ if (tevent_req_nomem(xconn->transport.shutdown_wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ /*
+ * Now wait until the request is finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the request will
+ * remove the item from the wait queue.
+ *
+ * Note that we don't cancel the requests here
+ * in order to keep the replay detection logic correct.
+ *
+ * However if we teardown the last channel of
+ * a connection, we'll call some logic via
+ * smbXsrv_session_disconnect_xconn()
+ * -> smbXsrv_session_disconnect_xconn_callback()
+ * -> smbXsrv_session_remove_channel()
+ * -> smb2srv_session_shutdown_send()
+ * will indeed cancel the request.
+ */
+ subreq = tevent_queue_wait_send(preq, ev,
+ xconn->transport.shutdown_wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ /*
+ * This may attach sessions with num_channels == 0
+ * to xconn->transport.shutdown_wait_queue.
+ */
+ status = smbXsrv_session_disconnect_xconn(xconn);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ len = tevent_queue_length(xconn->transport.shutdown_wait_queue);
+ if (len == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and send to the socket.
+ */
+ subreq = tevent_queue_wait_send(state, ev, xconn->transport.shutdown_wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbXsrv_connection_shutdown_wait_done, req);
+
+ return req;
+}
+
+static void smbXsrv_connection_shutdown_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbXsrv_connection_shutdown_state *state =
+ tevent_req_data(req,
+ struct smbXsrv_connection_shutdown_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ tevent_req_done(req);
+ /*
+ * make sure the xconn pointer is still valid,
+ * it should as we used tevent_req_defer_callback()
+ */
+ SMB_ASSERT(xconn->transport.terminating);
+}
+
+static NTSTATUS smbXsrv_connection_shutdown_recv(struct tevent_req *req)
+{
+ struct smbXsrv_connection_shutdown_state *state =
+ tevent_req_data(req,
+ struct smbXsrv_connection_shutdown_state);
+ struct smbXsrv_connection *xconn = state->xconn;
+ /*
+ * make sure the xconn pointer is still valid,
+ * it should as we used tevent_req_defer_callback()
+ */
+ SMB_ASSERT(xconn->transport.terminating);
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void smbd_server_connection_terminate_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_connection *xconn =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_connection);
+ struct smbXsrv_client *client = xconn->client;
+ NTSTATUS status;
+
+ status = smbXsrv_connection_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_server("smbXsrv_connection_shutdown_recv failed");
+ }
+
+ DLIST_REMOVE(client->connections, xconn);
+ TALLOC_FREE(xconn);
+}
+
+void smbd_server_connection_terminate_ex(struct smbXsrv_connection *xconn,
+ const char *reason,
+ const char *location)
+{
+ struct smbXsrv_client *client = xconn->client;
+ size_t num_ok = 0;
+
+ /*
+ * Make sure that no new request will be able to use this session.
+ *
+ * smbXsrv_connection_disconnect_transport() might be called already,
+ * but calling it again is a no-op.
+ */
+ smbXsrv_connection_disconnect_transport(xconn,
+ NT_STATUS_CONNECTION_DISCONNECTED);
+
+ num_ok = smbXsrv_client_valid_connections(client);
+
+ if (xconn->transport.terminating) {
+ DBG_DEBUG("skip recursion conn[%s] num_ok[%zu] reason[%s] at %s\n",
+ smbXsrv_connection_dbg(xconn), num_ok,
+ reason, location);
+ return;
+ }
+ xconn->transport.terminating = true;
+
+ DBG_DEBUG("conn[%s] num_ok[%zu] reason[%s] at %s\n",
+ smbXsrv_connection_dbg(xconn), num_ok,
+ reason, location);
+
+ if (xconn->has_cluster_movable_ip) {
+ /*
+ * If the connection has a movable cluster public address
+ * we disconnect all client connections,
+ * as the public address might be moved to
+ * a different node.
+ *
+ * In future we may recheck which node currently
+ * holds this address, but for now we keep it simple.
+ */
+ smbd_server_disconnect_client_ex(xconn->client,
+ reason,
+ location);
+ return;
+ }
+
+ if (num_ok != 0) {
+ struct tevent_req *subreq = NULL;
+
+ subreq = smbXsrv_connection_shutdown_send(client,
+ client->raw_ev_ctx,
+ xconn);
+ if (subreq == NULL) {
+ exit_server("smbXsrv_connection_shutdown_send failed");
+ }
+ tevent_req_set_callback(subreq,
+ smbd_server_connection_terminate_done,
+ xconn);
+ return;
+ }
+
+ /*
+ * The last connection was disconnected
+ */
+ exit_server_cleanly(reason);
+}
+
+void smbd_server_disconnect_client_ex(struct smbXsrv_client *client,
+ const char *reason,
+ const char *location)
+{
+ size_t num_ok = 0;
+
+ num_ok = smbXsrv_client_valid_connections(client);
+
+ DBG_WARNING("client[%s] num_ok[%zu] reason[%s] at %s\n",
+ client->global->remote_address, num_ok,
+ reason, location);
+
+ /*
+ * Something bad happened we need to disconnect all connections.
+ */
+ exit_server_cleanly(reason);
+}
+
+static bool dup_smb2_vec4(TALLOC_CTX *ctx,
+ struct iovec *outvec,
+ const struct iovec *srcvec)
+{
+ const uint8_t *srctf;
+ size_t srctf_len;
+ const uint8_t *srchdr;
+ size_t srchdr_len;
+ const uint8_t *srcbody;
+ size_t srcbody_len;
+ const uint8_t *expected_srcbody;
+ const uint8_t *srcdyn;
+ size_t srcdyn_len;
+ const uint8_t *expected_srcdyn;
+ uint8_t *dsttf;
+ uint8_t *dsthdr;
+ uint8_t *dstbody;
+ uint8_t *dstdyn;
+
+ srctf = (const uint8_t *)srcvec[SMBD_SMB2_TF_IOV_OFS].iov_base;
+ srctf_len = srcvec[SMBD_SMB2_TF_IOV_OFS].iov_len;
+ srchdr = (const uint8_t *)srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base;
+ srchdr_len = srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_len;
+ srcbody = (const uint8_t *)srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_base;
+ srcbody_len = srcvec[SMBD_SMB2_BODY_IOV_OFS].iov_len;
+ expected_srcbody = srchdr + SMB2_HDR_BODY;
+ srcdyn = (const uint8_t *)srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_base;
+ srcdyn_len = srcvec[SMBD_SMB2_DYN_IOV_OFS].iov_len;
+ expected_srcdyn = srcbody + 8;
+
+ if ((srctf_len != SMB2_TF_HDR_SIZE) && (srctf_len != 0)) {
+ return false;
+ }
+
+ if (srchdr_len != SMB2_HDR_BODY) {
+ return false;
+ }
+
+ if (srctf_len == SMB2_TF_HDR_SIZE) {
+ dsttf = talloc_memdup(ctx, srctf, SMB2_TF_HDR_SIZE);
+ if (dsttf == NULL) {
+ return false;
+ }
+ } else {
+ dsttf = NULL;
+ }
+ outvec[SMBD_SMB2_TF_IOV_OFS].iov_base = (void *)dsttf;
+ outvec[SMBD_SMB2_TF_IOV_OFS].iov_len = srctf_len;
+
+ /* vec[SMBD_SMB2_HDR_IOV_OFS] is always boilerplate and must
+ * be allocated with size OUTVEC_ALLOC_SIZE. */
+
+ dsthdr = talloc_memdup(ctx, srchdr, OUTVEC_ALLOC_SIZE);
+ if (dsthdr == NULL) {
+ return false;
+ }
+ outvec[SMBD_SMB2_HDR_IOV_OFS].iov_base = (void *)dsthdr;
+ outvec[SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+
+ /*
+ * If this is a "standard" vec[SMBD_SMB2_BOFY_IOV_OFS] of length 8,
+ * pointing to srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + SMB2_HDR_BODY,
+ * then duplicate this. Else use talloc_memdup().
+ */
+
+ if ((srcbody == expected_srcbody) && (srcbody_len == 8)) {
+ dstbody = dsthdr + SMB2_HDR_BODY;
+ } else {
+ dstbody = talloc_memdup(ctx, srcbody, srcbody_len);
+ if (dstbody == NULL) {
+ return false;
+ }
+ }
+ outvec[SMBD_SMB2_BODY_IOV_OFS].iov_base = (void *)dstbody;
+ outvec[SMBD_SMB2_BODY_IOV_OFS].iov_len = srcbody_len;
+
+ /*
+ * If this is a "standard" vec[SMBD_SMB2_DYN_IOV_OFS] of length 1,
+ * pointing to
+ * srcvec[SMBD_SMB2_HDR_IOV_OFS].iov_base + 8
+ * then duplicate this. Else use talloc_memdup().
+ */
+
+ if ((srcdyn == expected_srcdyn) && (srcdyn_len == 1)) {
+ dstdyn = dsthdr + SMB2_HDR_BODY + 8;
+ } else if (srcdyn == NULL) {
+ dstdyn = NULL;
+ } else {
+ dstdyn = talloc_memdup(ctx, srcdyn, srcdyn_len);
+ if (dstdyn == NULL) {
+ return false;
+ }
+ }
+ outvec[SMBD_SMB2_DYN_IOV_OFS].iov_base = (void *)dstdyn;
+ outvec[SMBD_SMB2_DYN_IOV_OFS].iov_len = srcdyn_len;
+
+ return true;
+}
+
+static struct smbd_smb2_request *dup_smb2_req(const struct smbd_smb2_request *req)
+{
+ struct smbd_smb2_request *newreq = NULL;
+ struct iovec *outvec = NULL;
+ int count = req->out.vector_count;
+ int i;
+ bool ok;
+
+ newreq = smbd_smb2_request_allocate(req->xconn);
+ if (!newreq) {
+ return NULL;
+ }
+
+ newreq->session = req->session;
+ newreq->do_encryption = req->do_encryption;
+ newreq->do_signing = req->do_signing;
+ newreq->current_idx = req->current_idx;
+
+ outvec = talloc_zero_array(newreq, struct iovec, count);
+ if (!outvec) {
+ TALLOC_FREE(newreq);
+ return NULL;
+ }
+ newreq->out.vector = outvec;
+ newreq->out.vector_count = count;
+
+ /* Setup the outvec's identically to req. */
+ outvec[0].iov_base = newreq->out.nbt_hdr;
+ outvec[0].iov_len = 4;
+ memcpy(newreq->out.nbt_hdr, req->out.nbt_hdr, 4);
+
+ /* Setup the vectors identically to the ones in req. */
+ for (i = 1; i < count; i += SMBD_SMB2_NUM_IOV_PER_REQ) {
+ if (!dup_smb2_vec4(outvec, &outvec[i], &req->out.vector[i])) {
+ break;
+ }
+ }
+
+ if (i < count) {
+ /* Alloc failed. */
+ TALLOC_FREE(newreq);
+ return NULL;
+ }
+
+ ok = smb2_setup_nbt_length(newreq->out.vector,
+ newreq->out.vector_count);
+ if (!ok) {
+ TALLOC_FREE(newreq);
+ return NULL;
+ }
+
+ return newreq;
+}
+
+static NTSTATUS smb2_send_async_interim_response(const struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ int first_idx = 1;
+ struct iovec *firsttf = NULL;
+ struct iovec *outhdr_v = NULL;
+ uint8_t *outhdr = NULL;
+ struct smbd_smb2_request *nreq = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ /* Create a new smb2 request we'll use
+ for the interim return. */
+ nreq = dup_smb2_req(req);
+ if (!nreq) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Lose the last X out vectors. They're the
+ ones we'll be using for the async reply. */
+ nreq->out.vector_count -= SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ ok = smb2_setup_nbt_length(nreq->out.vector,
+ nreq->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ /* Step back to the previous reply. */
+ nreq->current_idx -= SMBD_SMB2_NUM_IOV_PER_REQ;
+ firsttf = SMBD_SMB2_IDX_TF_IOV(nreq,out,first_idx);
+ outhdr_v = SMBD_SMB2_OUT_HDR_IOV(nreq);
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(nreq);
+ /* And end the chain. */
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
+
+ /* Calculate outgoing credits */
+ smb2_calculate_credits(req, nreq);
+
+ if (DEBUGLEVEL >= 10) {
+ dbgtext("smb2_send_async_interim_response: nreq->current_idx = %u\n",
+ (unsigned int)nreq->current_idx );
+ dbgtext("smb2_send_async_interim_response: returning %u vectors\n",
+ (unsigned int)nreq->out.vector_count );
+ print_req_vectors(nreq);
+ }
+
+ /*
+ * As we have changed the header (SMB2_HDR_NEXT_COMMAND),
+ * we need to sign/encrypt here with the last/first key we remembered
+ */
+ if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
+ status = smb2_signing_encrypt_pdu(req->first_enc_key,
+ firsttf,
+ nreq->out.vector_count - first_idx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else if (smb2_signing_key_valid(req->last_sign_key)) {
+ status = smb2_signing_sign_pdu(req->last_sign_key,
+ outhdr_v,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ nreq->queue_entry.mem_ctx = nreq;
+ nreq->queue_entry.vector = nreq->out.vector;
+ nreq->queue_entry.count = nreq->out.vector_count;
+ DLIST_ADD_END(xconn->smb2.send_queue, &nreq->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_request_pending_state {
+ struct smbd_smb2_send_queue queue_entry;
+ uint8_t buf[NBT_HDR_SIZE + SMB2_TF_HDR_SIZE + SMB2_HDR_BODY + 0x08 + 1];
+ struct iovec vector[1 + SMBD_SMB2_NUM_IOV_PER_REQ];
+};
+
+static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data);
+
+NTSTATUS smbd_smb2_request_pending_queue(struct smbd_smb2_request *req,
+ struct tevent_req *subreq,
+ uint32_t defer_time)
+{
+ NTSTATUS status;
+ struct timeval defer_endtime;
+ uint8_t *outhdr = NULL;
+ uint32_t flags;
+
+ if (!tevent_req_is_in_progress(subreq)) {
+ /*
+ * This is a performance optimization,
+ * it avoids one tevent_loop iteration,
+ * which means we avoid one
+ * talloc_stackframe_pool/talloc_free pair.
+ */
+ tevent_req_notify_callback(subreq);
+ return NT_STATUS_OK;
+ }
+
+ req->subreq = subreq;
+ subreq = NULL;
+
+ if (req->async_te) {
+ /* We're already async. */
+ return NT_STATUS_OK;
+ }
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ if (flags & SMB2_HDR_FLAG_ASYNC) {
+ /* We're already async. */
+ return NT_STATUS_OK;
+ }
+
+ if (req->async_internal || defer_time == 0) {
+ /*
+ * An SMB2 request implementation wants to handle the request
+ * asynchronously "internally" while keeping synchronous
+ * behaviour for the SMB2 request. This means we don't send an
+ * interim response and we can allow processing of compound SMB2
+ * requests (cf the subsequent check) for all cases.
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (req->in.vector_count > req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ /*
+ * We're trying to go async in a compound request
+ * chain. This is only allowed for opens that cause an
+ * oplock break or for the last operation in the
+ * chain, otherwise it is not allowed. See
+ * [MS-SMB2].pdf note <206> on Section 3.3.5.2.7.
+ */
+ const uint8_t *inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ if (SVAL(inhdr, SMB2_HDR_OPCODE) != SMB2_OP_CREATE) {
+ /*
+ * Cancel the outstanding request.
+ */
+ bool ok = tevent_req_cancel(req->subreq);
+ if (ok) {
+ return NT_STATUS_OK;
+ }
+ TALLOC_FREE(req->subreq);
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INTERNAL_ERROR);
+ }
+ }
+
+ if (DEBUGLEVEL >= 10) {
+ dbgtext("smbd_smb2_request_pending_queue: req->current_idx = %u\n",
+ (unsigned int)req->current_idx );
+ print_req_vectors(req);
+ }
+
+ if (req->current_idx > 1) {
+ /*
+ * We're going async in a compound
+ * chain after the first request has
+ * already been processed. Send an
+ * interim response containing the
+ * set of replies already generated.
+ */
+ int idx = req->current_idx;
+
+ status = smb2_send_async_interim_response(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ TALLOC_FREE(req->first_enc_key);
+
+ req->current_idx = 1;
+
+ /*
+ * Re-arrange the in.vectors to remove what
+ * we just sent.
+ */
+ memmove(&req->in.vector[1],
+ &req->in.vector[idx],
+ sizeof(req->in.vector[0])*(req->in.vector_count - idx));
+ req->in.vector_count = 1 + (req->in.vector_count - idx);
+
+ /* Re-arrange the out.vectors to match. */
+ memmove(&req->out.vector[1],
+ &req->out.vector[idx],
+ sizeof(req->out.vector[0])*(req->out.vector_count - idx));
+ req->out.vector_count = 1 + (req->out.vector_count - idx);
+
+ if (req->in.vector_count == 1 + SMBD_SMB2_NUM_IOV_PER_REQ) {
+ /*
+ * We only have one remaining request as
+ * we've processed everything else.
+ * This is no longer a compound request.
+ */
+ req->compound_related = false;
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ flags = (IVAL(outhdr, SMB2_HDR_FLAGS) & ~SMB2_HDR_FLAG_CHAINED);
+ SIVAL(outhdr, SMB2_HDR_FLAGS, flags);
+ }
+ }
+ TALLOC_FREE(req->last_sign_key);
+
+ /*
+ * smbd_smb2_request_pending_timer() just send a packet
+ * to the client and doesn't need any impersonation.
+ * So we use req->xconn->client->raw_ev_ctx instead
+ * of req->ev_ctx here.
+ */
+ defer_endtime = timeval_current_ofs_usec(defer_time);
+ req->async_te = tevent_add_timer(req->xconn->client->raw_ev_ctx,
+ req, defer_endtime,
+ smbd_smb2_request_pending_timer,
+ req);
+ if (req->async_te == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static
+struct smb2_signing_key *smbd_smb2_signing_key(struct smbXsrv_session *session,
+ struct smbXsrv_connection *xconn,
+ bool *_has_channel)
+{
+ struct smbXsrv_channel_global0 *c = NULL;
+ NTSTATUS status;
+ struct smb2_signing_key *key = NULL;
+ bool has_channel = false;
+
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (NT_STATUS_IS_OK(status)) {
+ key = c->signing_key;
+ has_channel = true;
+ }
+
+ if (!smb2_signing_key_valid(key)) {
+ key = session->global->signing_key;
+ has_channel = false;
+ }
+
+ if (_has_channel != NULL) {
+ *_has_channel = has_channel;
+ }
+
+ return key;
+}
+
+static NTSTATUS smb2_get_new_nonce(struct smbXsrv_session *session,
+ uint64_t *new_nonce_high,
+ uint64_t *new_nonce_low)
+{
+ uint64_t nonce_high;
+ uint64_t nonce_low;
+
+ session->nonce_low += 1;
+ if (session->nonce_low == 0) {
+ session->nonce_low += 1;
+ session->nonce_high += 1;
+ }
+
+ /*
+ * CCM and GCM algorithms must never have their
+ * nonce wrap, or the security of the whole
+ * communication and the keys is destroyed.
+ * We must drop the connection once we have
+ * transferred too much data.
+ *
+ * NOTE: We assume nonces greater than 8 bytes.
+ */
+ if (session->nonce_high >= session->nonce_high_max) {
+ return NT_STATUS_ENCRYPTION_FAILED;
+ }
+
+ nonce_high = session->nonce_high_random;
+ nonce_high += session->nonce_high;
+ nonce_low = session->nonce_low;
+
+ *new_nonce_high = nonce_high;
+ *new_nonce_low = nonce_low;
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_request_pending_timer(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct smbd_smb2_request *req =
+ talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_smb2_request_pending_state *state = NULL;
+ uint8_t *outhdr = NULL;
+ const uint8_t *inhdr = NULL;
+ uint8_t *tf = NULL;
+ uint8_t *hdr = NULL;
+ uint8_t *body = NULL;
+ uint8_t *dyn = NULL;
+ uint32_t flags = 0;
+ uint64_t message_id = 0;
+ uint64_t async_id = 0;
+ NTSTATUS status;
+ bool ok;
+
+ TALLOC_FREE(req->async_te);
+
+ /* Ensure our final reply matches the interim one. */
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ flags = IVAL(outhdr, SMB2_HDR_FLAGS);
+ message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+
+ async_id = message_id; /* keep it simple for now... */
+
+ SIVAL(outhdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+ SBVAL(outhdr, SMB2_HDR_ASYNC_ID, async_id);
+
+ DEBUG(10,("smbd_smb2_request_pending_queue: opcode[%s] mid %llu "
+ "going async\n",
+ smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
+ (unsigned long long)async_id ));
+
+ /*
+ * What we send is identical to a smbd_smb2_request_error
+ * packet with an error status of STATUS_PENDING. Make use
+ * of this fact sometime when refactoring. JRA.
+ */
+
+ state = talloc_zero(req->xconn, struct smbd_smb2_request_pending_state);
+ if (state == NULL) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(NT_STATUS_NO_MEMORY));
+ return;
+ }
+
+ tf = state->buf + NBT_HDR_SIZE;
+
+ hdr = tf + SMB2_TF_HDR_SIZE;
+ body = hdr + SMB2_HDR_BODY;
+ dyn = body + 8;
+
+ if (req->do_encryption) {
+ uint64_t nonce_high = 0;
+ uint64_t nonce_low = 0;
+ uint64_t session_id = req->session->global->session_wire_id;
+
+ status = smb2_get_new_nonce(req->session,
+ &nonce_high,
+ &nonce_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(status));
+ return;
+ }
+
+ SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
+ SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
+ SBVAL(tf, SMB2_TF_SESSION_ID, session_id);
+ }
+
+ SIVAL(hdr, SMB2_HDR_PROTOCOL_ID, SMB2_MAGIC);
+ SSVAL(hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+ SSVAL(hdr, SMB2_HDR_EPOCH, 0);
+ SIVAL(hdr, SMB2_HDR_STATUS, NT_STATUS_V(NT_STATUS_PENDING));
+ SSVAL(hdr, SMB2_HDR_OPCODE, SVAL(outhdr, SMB2_HDR_OPCODE));
+
+ /*
+ * The STATUS_PENDING response has SMB2_HDR_FLAG_SIGNED
+ * clearedm, but echoes the signature field.
+ */
+ flags &= ~SMB2_HDR_FLAG_SIGNED;
+ SIVAL(hdr, SMB2_HDR_FLAGS, flags);
+ SIVAL(hdr, SMB2_HDR_NEXT_COMMAND, 0);
+ SBVAL(hdr, SMB2_HDR_MESSAGE_ID, message_id);
+ SBVAL(hdr, SMB2_HDR_PID, async_id);
+ SBVAL(hdr, SMB2_HDR_SESSION_ID,
+ BVAL(outhdr, SMB2_HDR_SESSION_ID));
+ memcpy(hdr+SMB2_HDR_SIGNATURE,
+ outhdr+SMB2_HDR_SIGNATURE, 16);
+
+ SSVAL(body, 0x00, 0x08 + 1);
+
+ SCVAL(body, 0x02, 0);
+ SCVAL(body, 0x03, 0);
+ SIVAL(body, 0x04, 0);
+ /* Match W2K8R2... */
+ SCVAL(dyn, 0x00, 0x21);
+
+ state->vector[0].iov_base = (void *)state->buf;
+ state->vector[0].iov_len = NBT_HDR_SIZE;
+
+ if (req->do_encryption) {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = tf;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len =
+ SMB2_TF_HDR_SIZE;
+ } else {
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_base = NULL;
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS].iov_len = 0;
+ }
+
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_base = hdr;
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS].iov_len = SMB2_HDR_BODY;
+
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_base = body;
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS].iov_len = 8;
+
+ state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_base = dyn;
+ state->vector[1+SMBD_SMB2_DYN_IOV_OFS].iov_len = 1;
+
+ ok = smb2_setup_nbt_length(state->vector,
+ 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!ok) {
+ smbd_server_connection_terminate(
+ xconn, nt_errstr(NT_STATUS_INTERNAL_ERROR));
+ return;
+ }
+
+ /* Ensure we correctly go through crediting. Grant
+ the credits now, and zero credits on the final
+ response. */
+ smb2_set_operation_credit(req->xconn,
+ SMBD_SMB2_IN_HDR_IOV(req),
+ &state->vector[1+SMBD_SMB2_HDR_IOV_OFS]);
+
+ /*
+ * We add SMB2_HDR_FLAG_ASYNC after smb2_set_operation_credit()
+ * as it reacts on it
+ */
+ SIVAL(hdr, SMB2_HDR_FLAGS, flags | SMB2_HDR_FLAG_ASYNC);
+
+ if (DEBUGLVL(10)) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(state->vector); i++) {
+ dbgtext("\tstate->vector[%u/%u].iov_len = %u\n",
+ (unsigned int)i,
+ (unsigned int)ARRAY_SIZE(state->vector),
+ (unsigned int)state->vector[i].iov_len);
+ }
+ }
+
+ if (req->do_encryption) {
+ struct smbXsrv_session *x = req->session;
+ struct smb2_signing_key *encryption_key = x->global->encryption_key;
+
+ status = smb2_signing_encrypt_pdu(encryption_key,
+ &state->vector[1+SMBD_SMB2_TF_IOV_OFS],
+ SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(status));
+ return;
+ }
+ }
+
+ state->queue_entry.mem_ctx = state;
+ state->queue_entry.vector = state->vector;
+ state->queue_entry.count = ARRAY_SIZE(state->vector);
+ DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn,
+ nt_errstr(status));
+ return;
+ }
+}
+
+static NTSTATUS smbd_smb2_request_process_cancel(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ struct smbd_smb2_request *cur;
+ const uint8_t *inhdr;
+ uint32_t flags;
+ uint64_t search_message_id;
+ uint64_t search_async_id;
+ uint64_t found_id = 0;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ search_message_id = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ search_async_id = BVAL(inhdr, SMB2_HDR_PID);
+
+ /*
+ * We don't need the request anymore cancel requests never
+ * have a response.
+ *
+ * We defer the TALLOC_FREE(req) to the caller.
+ */
+ DLIST_REMOVE(xconn->smb2.requests, req);
+
+ for (cur = xconn->smb2.requests; cur; cur = cur->next) {
+ const uint8_t *outhdr;
+ uint64_t message_id;
+ uint64_t async_id;
+
+ if (cur->session != req->session) {
+ continue;
+ }
+
+ if (cur->compound_related) {
+ /*
+ * Never cancel anything in a compound request.
+ * Way too hard to deal with the result.
+ */
+ continue;
+ }
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(cur);
+
+ message_id = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+ async_id = BVAL(outhdr, SMB2_HDR_PID);
+
+ if (flags & SMB2_HDR_FLAG_ASYNC) {
+ if (search_async_id == async_id) {
+ found_id = async_id;
+ break;
+ }
+ } else {
+ if (search_message_id == message_id) {
+ found_id = message_id;
+ break;
+ }
+ }
+ }
+
+ if (cur && cur->subreq) {
+ inhdr = SMBD_SMB2_IN_HDR_PTR(cur);
+ DEBUG(10,("smbd_smb2_request_process_cancel: attempting to "
+ "cancel opcode[%s] mid %llu\n",
+ smb2_opcode_name(SVAL(inhdr, SMB2_HDR_OPCODE)),
+ (unsigned long long)found_id ));
+ tevent_req_cancel(cur->subreq);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************
+ Ensure an incoming tid is a valid one for us to access.
+ Change to the associated uid credentials and chdir to the
+ valid tid directory.
+*************************************************************/
+
+static NTSTATUS smbd_smb2_request_check_tcon(struct smbd_smb2_request *req)
+{
+ const uint8_t *inhdr;
+ uint32_t in_flags;
+ uint32_t in_tid;
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ req->tcon = NULL;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ in_tid = IVAL(inhdr, SMB2_HDR_TID);
+
+ if (in_flags & SMB2_HDR_FLAG_CHAINED) {
+ in_tid = req->last_tid;
+ }
+
+ req->last_tid = 0;
+
+ status = smb2srv_tcon_lookup(req->session,
+ in_tid, now, &tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!change_to_user_and_service(
+ tcon->compat,
+ req->session->global->session_wire_id))
+ {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ req->tcon = tcon;
+ req->last_tid = in_tid;
+
+ return NT_STATUS_OK;
+}
+
+/*************************************************************
+ Ensure an incoming session_id is a valid one for us to access.
+*************************************************************/
+
+static NTSTATUS smbd_smb2_request_check_session(struct smbd_smb2_request *req)
+{
+ const uint8_t *inhdr;
+ uint32_t in_flags;
+ uint16_t in_opcode;
+ uint64_t in_session_id;
+ struct smbXsrv_session *session = NULL;
+ struct auth_session_info *session_info;
+ NTSTATUS status;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+
+ req->session = NULL;
+ req->tcon = NULL;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ in_flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ in_opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+ in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID);
+
+ if (in_flags & SMB2_HDR_FLAG_CHAINED) {
+ in_session_id = req->last_session_id;
+ }
+
+ req->last_session_id = 0;
+
+ /* look an existing session up */
+ switch (in_opcode) {
+ case SMB2_OP_SESSSETUP:
+ /*
+ * For a session bind request, we don't have the
+ * channel set up at this point yet, so we defer
+ * the verification that the connection belongs
+ * to the session to the session setup code, which
+ * can look at the session binding flags.
+ */
+ status = smb2srv_session_lookup_client(req->xconn->client,
+ in_session_id, now,
+ &session);
+ break;
+ default:
+ status = smb2srv_session_lookup_conn(req->xconn,
+ in_session_id, now,
+ &session);
+ break;
+ }
+ if (session) {
+ req->session = session;
+ req->last_session_id = in_session_id;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ switch (in_opcode) {
+ case SMB2_OP_SESSSETUP:
+ status = smb2srv_session_lookup_global(req->xconn->client,
+ in_session_id,
+ req,
+ &session);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * We fallback to a session of
+ * another process in order to
+ * get the signing correct.
+ *
+ * We don't set req->last_session_id here.
+ */
+ req->session = session;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ switch (in_opcode) {
+ case SMB2_OP_SESSSETUP:
+ status = NT_STATUS_OK;
+ break;
+ case SMB2_OP_LOGOFF:
+ case SMB2_OP_CLOSE:
+ case SMB2_OP_LOCK:
+ case SMB2_OP_CANCEL:
+ case SMB2_OP_KEEPALIVE:
+ /*
+ * [MS-SMB2] 3.3.5.2.9 Verifying the Session
+ * specifies that LOGOFF, CLOSE and (UN)LOCK
+ * should always be processed even on expired sessions.
+ *
+ * Also see the logic in
+ * smbd_smb2_request_process_lock().
+ *
+ * The smb2.session.expire2 test shows that
+ * CANCEL and KEEPALIVE/ECHO should also
+ * be processed.
+ */
+ status = NT_STATUS_OK;
+ break;
+ default:
+ break;
+ }
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ switch (in_opcode) {
+ case SMB2_OP_TCON:
+ case SMB2_OP_CREATE:
+ case SMB2_OP_GETINFO:
+ case SMB2_OP_SETINFO:
+ return NT_STATUS_INVALID_HANDLE;
+ default:
+ /*
+ * Notice the check for
+ * (session_info == NULL)
+ * below.
+ */
+ status = NT_STATUS_OK;
+ break;
+ }
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ session_info = session->global->auth_session_info;
+ if (session_info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_request_verify_creditcharge(struct smbd_smb2_request *req,
+ uint32_t data_length)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ uint16_t needed_charge;
+ uint16_t credit_charge = 1;
+ const uint8_t *inhdr;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ if (xconn->smb2.credits.multicredit) {
+ credit_charge = SVAL(inhdr, SMB2_HDR_CREDIT_CHARGE);
+ credit_charge = MAX(credit_charge, 1);
+ }
+
+ needed_charge = (data_length - 1)/ 65536 + 1;
+
+ DBGC_DEBUG(DBGC_SMB2_CREDITS,
+ "mid %llu, CreditCharge: %d, NeededCharge: %d\n",
+ (unsigned long long) BVAL(inhdr, SMB2_HDR_MESSAGE_ID),
+ credit_charge, needed_charge);
+
+ if (needed_charge > credit_charge) {
+ DBGC_WARNING(DBGC_SMB2_CREDITS,
+ "CreditCharge too low, given %d, needed %d\n",
+ credit_charge, needed_charge);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_request_verify_sizes(struct smbd_smb2_request *req,
+ size_t expected_body_size)
+{
+ struct iovec *inhdr_v;
+ const uint8_t *inhdr;
+ uint16_t opcode;
+ const uint8_t *inbody;
+ size_t body_size;
+ size_t min_dyn_size = expected_body_size & 0x00000001;
+ int max_idx = req->in.vector_count - SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ /*
+ * The following should be checked already.
+ */
+ if (req->in.vector_count < SMBD_SMB2_NUM_IOV_PER_REQ) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (req->current_idx > max_idx) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ inhdr_v = SMBD_SMB2_IN_HDR_IOV(req);
+ if (inhdr_v->iov_len != SMB2_HDR_BODY) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (SMBD_SMB2_IN_BODY_LEN(req) < 2) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+
+ switch (opcode) {
+ case SMB2_OP_IOCTL:
+ case SMB2_OP_GETINFO:
+ case SMB2_OP_WRITE:
+ min_dyn_size = 0;
+ break;
+ }
+
+ /*
+ * Now check the expected body size,
+ * where the last byte might be in the
+ * dynamic section..
+ */
+ if (SMBD_SMB2_IN_BODY_LEN(req) != (expected_body_size & 0xFFFFFFFE)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (SMBD_SMB2_IN_DYN_LEN(req) < min_dyn_size) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ body_size = SVAL(inbody, 0x00);
+ if (body_size != expected_body_size) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return NT_STATUS_OK;
+}
+
+bool smbXsrv_is_encrypted(uint8_t encryption_flags)
+{
+ return (!(encryption_flags & SMBXSRV_PROCESSED_UNENCRYPTED_PACKET)
+ &&
+ (encryption_flags & (SMBXSRV_PROCESSED_ENCRYPTED_PACKET |
+ SMBXSRV_ENCRYPTION_DESIRED |
+ SMBXSRV_ENCRYPTION_REQUIRED)));
+}
+
+bool smbXsrv_is_partially_encrypted(uint8_t encryption_flags)
+{
+ return ((encryption_flags & SMBXSRV_PROCESSED_ENCRYPTED_PACKET) &&
+ (encryption_flags & SMBXSRV_PROCESSED_UNENCRYPTED_PACKET));
+}
+
+/* Set a flag if not already set, return true if set */
+bool smbXsrv_set_crypto_flag(uint8_t *flags, uint8_t flag)
+{
+ if ((flag == 0) || (*flags & flag)) {
+ return false;
+ }
+
+ *flags |= flag;
+ return true;
+}
+
+/*
+ * Update encryption state tracking flags, this can be used to
+ * determine whether whether the session or tcon is "encrypted".
+ */
+static void smb2srv_update_crypto_flags(struct smbd_smb2_request *req,
+ uint16_t opcode,
+ bool *update_session_globalp,
+ bool *update_tcon_globalp)
+{
+ /* Default: assume unecrypted and unsigned */
+ struct smbXsrv_session *session = req->session;
+ struct smbXsrv_tcon *tcon = req->tcon;
+ uint8_t encrypt_flag = SMBXSRV_PROCESSED_UNENCRYPTED_PACKET;
+ uint8_t sign_flag = SMBXSRV_PROCESSED_UNSIGNED_PACKET;
+ bool update_session = false;
+ bool update_tcon = false;
+
+ if (session->table == NULL) {
+ /*
+ * sessions from smb2srv_session_lookup_global()
+ * have NT_STATUS_BAD_LOGON_SESSION_STATE
+ * and session->table == NULL.
+ *
+ * They only used to give the correct error
+ * status, we should not update any state.
+ */
+ goto out;
+ }
+
+ if (req->was_encrypted && req->do_encryption) {
+ encrypt_flag = SMBXSRV_PROCESSED_ENCRYPTED_PACKET;
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ } else {
+ /* Unencrypted packet, can be signed */
+ if (req->do_signing) {
+ sign_flag = SMBXSRV_PROCESSED_SIGNED_PACKET;
+ }
+ }
+
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->encryption_flags, encrypt_flag);
+ update_session |= smbXsrv_set_crypto_flag(
+ &session->global->signing_flags, sign_flag);
+
+ if (tcon) {
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->encryption_flags, encrypt_flag);
+ update_tcon |= smbXsrv_set_crypto_flag(
+ &tcon->global->signing_flags, sign_flag);
+ }
+
+out:
+ *update_session_globalp = update_session;
+ *update_tcon_globalp = update_tcon;
+ return;
+}
+
+bool smbXsrv_is_signed(uint8_t signing_flags)
+{
+ /*
+ * Signing is always enabled, so unless we got an unsigned
+ * packet and at least one signed packet that was not
+ * encrypted, the session or tcon is "signed".
+ */
+ return (!(signing_flags & SMBXSRV_PROCESSED_UNSIGNED_PACKET) &&
+ (signing_flags & SMBXSRV_PROCESSED_SIGNED_PACKET));
+}
+
+bool smbXsrv_is_partially_signed(uint8_t signing_flags)
+{
+ return ((signing_flags & SMBXSRV_PROCESSED_UNSIGNED_PACKET) &&
+ (signing_flags & SMBXSRV_PROCESSED_SIGNED_PACKET));
+}
+
+static NTSTATUS smbd_smb2_request_dispatch_update_counts(
+ struct smbd_smb2_request *req,
+ bool modify_call)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inhdr;
+ uint16_t channel_sequence;
+ uint8_t generation_wrap = 0;
+ uint32_t flags;
+ int cmp;
+ struct smbXsrv_open *op;
+ bool update_open = false;
+ NTSTATUS status = NT_STATUS_OK;
+
+ SMB_ASSERT(!req->request_counters_updated);
+
+ if (xconn->protocol < PROTOCOL_SMB3_00) {
+ return NT_STATUS_OK;
+ }
+
+ if (req->compat_chain_fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ op = req->compat_chain_fsp->op;
+ if (op == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+ cmp = channel_sequence - op->global->channel_sequence;
+ if (cmp < 0) {
+ /*
+ * csn wrap. We need to watch out for long-running
+ * requests that are still sitting on a previously
+ * used csn. SMB2_OP_NOTIFY can take VERY long.
+ */
+ generation_wrap += 1;
+ }
+
+ if (abs(cmp) > INT16_MAX) {
+ /*
+ * [MS-SMB2] 3.3.5.2.10 - Verifying the Channel Sequence Number:
+ *
+ * If the channel sequence number of the request and the one
+ * known to the server are not equal, the channel sequence
+ * number and outstanding request counts are only updated
+ * "... if the unsigned difference using 16-bit arithmetic
+ * between ChannelSequence and Open.ChannelSequence is less than
+ * or equal to 0x7FFF ...".
+ * Otherwise, an error is returned for the modifying
+ * calls write, set_info, and ioctl.
+ *
+ * There are currently two issues with the description:
+ *
+ * * For the other calls, the document seems to imply
+ * that processing continues without adapting the
+ * counters (if the sequence numbers are not equal).
+ *
+ * TODO: This needs clarification!
+ *
+ * * Also, the behaviour if the difference is larger
+ * than 0x7FFF is not clear. The document seems to
+ * imply that if such a difference is reached,
+ * the server starts to ignore the counters or
+ * in the case of the modifying calls, return errors.
+ *
+ * TODO: This needs clarification!
+ *
+ * At this point Samba tries to be a little more
+ * clever than the description in the MS-SMB2 document
+ * by heuristically detecting and properly treating
+ * a 16 bit overflow of the client-submitted sequence
+ * number:
+ *
+ * If the stored channel sequence number is more than
+ * 0x7FFF larger than the one from the request, then
+ * the client-provided sequence number has likely
+ * overflown. We treat this case as valid instead
+ * of as failure.
+ *
+ * The MS-SMB2 behaviour would be setting cmp = -1.
+ */
+ cmp *= -1;
+ }
+
+ if (flags & SMB2_HDR_FLAG_REPLAY_OPERATION) {
+ if (cmp == 0 && op->pre_request_count == 0) {
+ op->request_count += 1;
+ req->request_counters_updated = true;
+ } else if (cmp > 0 && op->pre_request_count == 0) {
+ op->pre_request_count += op->request_count;
+ op->request_count = 1;
+ op->global->channel_sequence = channel_sequence;
+ op->global->channel_generation += generation_wrap;
+ update_open = true;
+ req->request_counters_updated = true;
+ } else if (modify_call) {
+ return NT_STATUS_FILE_NOT_AVAILABLE;
+ }
+ } else {
+ if (cmp == 0) {
+ op->request_count += 1;
+ req->request_counters_updated = true;
+ } else if (cmp > 0) {
+ op->pre_request_count += op->request_count;
+ op->request_count = 1;
+ op->global->channel_sequence = channel_sequence;
+ op->global->channel_generation += generation_wrap;
+ update_open = true;
+ req->request_counters_updated = true;
+ } else if (modify_call) {
+ return NT_STATUS_FILE_NOT_AVAILABLE;
+ }
+ }
+ req->channel_generation = op->global->channel_generation;
+
+ if (update_open) {
+ status = smbXsrv_open_update(op);
+ }
+
+ return status;
+}
+
+NTSTATUS smbd_smb2_request_dispatch(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const struct smbd_smb2_dispatch_table *call = NULL;
+ const struct iovec *intf_v = SMBD_SMB2_IN_TF_IOV(req);
+ const uint8_t *inhdr;
+ uint16_t opcode;
+ uint32_t flags;
+ uint64_t mid;
+ NTSTATUS status;
+ NTSTATUS session_status;
+ uint32_t allowed_flags;
+ NTSTATUS return_value;
+ struct smbXsrv_session *x = NULL;
+ bool signing_required = false;
+ bool encryption_desired = false;
+ bool encryption_required = false;
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+
+ DO_PROFILE_INC(request);
+
+ SMB_ASSERT(!req->request_counters_updated);
+
+ /* TODO: verify more things */
+
+ flags = IVAL(inhdr, SMB2_HDR_FLAGS);
+ opcode = SVAL(inhdr, SMB2_HDR_OPCODE);
+ mid = BVAL(inhdr, SMB2_HDR_MESSAGE_ID);
+ DBG_DEBUG("opcode[%s] mid = %"PRIu64"\n",
+ smb2_opcode_name(opcode),
+ mid);
+
+ if (xconn->protocol >= PROTOCOL_SMB2_02) {
+ /*
+ * once the protocol is negotiated
+ * SMB2_OP_NEGPROT is not allowed anymore
+ */
+ if (opcode == SMB2_OP_NEGPROT) {
+ /* drop the connection */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ /*
+ * if the protocol is not negotiated yet
+ * only SMB2_OP_NEGPROT is allowed.
+ */
+ if (opcode != SMB2_OP_NEGPROT) {
+ /* drop the connection */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ /*
+ * Check if the client provided a valid session id.
+ *
+ * As some command don't require a valid session id
+ * we defer the check of the session_status
+ */
+ session_status = smbd_smb2_request_check_session(req);
+ x = req->session;
+ if (x != NULL) {
+ signing_required = x->global->signing_flags & SMBXSRV_SIGNING_REQUIRED;
+ encryption_desired = x->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
+ encryption_required = x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED;
+ }
+
+ req->async_internal = false;
+ req->do_signing = false;
+ if (opcode != SMB2_OP_SESSSETUP) {
+ req->do_encryption = encryption_desired;
+ } else {
+ req->do_encryption = false;
+ }
+ req->was_encrypted = false;
+ if (intf_v->iov_len == SMB2_TF_HDR_SIZE) {
+ const uint8_t *intf = SMBD_SMB2_IN_TF_PTR(req);
+ uint64_t tf_session_id = BVAL(intf, SMB2_TF_SESSION_ID);
+
+ if (x != NULL && x->global->session_wire_id != tf_session_id) {
+ DEBUG(0,("smbd_smb2_request_dispatch: invalid session_id"
+ "in SMB2_HDR[%llu], SMB2_TF[%llu]\n",
+ (unsigned long long)x->global->session_wire_id,
+ (unsigned long long)tf_session_id));
+ /*
+ * TODO: windows allows this...
+ * should we drop the connection?
+ *
+ * For now we just return ACCESS_DENIED
+ * (Windows clients never trigger this)
+ * and wait for an update of [MS-SMB2].
+ */
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
+
+ req->was_encrypted = true;
+ req->do_encryption = true;
+ }
+
+ if (encryption_required && !req->was_encrypted) {
+ req->do_encryption = true;
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
+
+ call = smbd_smb2_call(opcode);
+ if (call == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ allowed_flags = SMB2_HDR_FLAG_CHAINED |
+ SMB2_HDR_FLAG_SIGNED |
+ SMB2_HDR_FLAG_DFS;
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
+ }
+ if (opcode == SMB2_OP_NEGPROT) {
+ if (lp_server_max_protocol() >= PROTOCOL_SMB3_11) {
+ allowed_flags |= SMB2_HDR_FLAG_PRIORITY_MASK;
+ }
+ }
+ if (opcode == SMB2_OP_CANCEL) {
+ allowed_flags |= SMB2_HDR_FLAG_ASYNC;
+ }
+ if (xconn->protocol >= PROTOCOL_SMB3_00) {
+ allowed_flags |= SMB2_HDR_FLAG_REPLAY_OPERATION;
+ }
+ if ((flags & ~allowed_flags) != 0) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ /*
+ * This check is mostly for giving the correct error code
+ * for compounded requests.
+ */
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+ } else {
+ req->compat_chain_fsp = NULL;
+ }
+
+ if (req->was_encrypted) {
+ signing_required = false;
+ } else if (signing_required || (flags & SMB2_HDR_FLAG_SIGNED)) {
+ struct smb2_signing_key *signing_key = NULL;
+ bool has_channel = false;
+
+ if (x == NULL) {
+ /*
+ * MS-SMB2: 3.3.5.2.4 Verifying the Signature.
+ * If the SMB2 header of the SMB2 NEGOTIATE
+ * request has the SMB2_FLAGS_SIGNED bit set in the
+ * Flags field, the server MUST fail the request
+ * with STATUS_INVALID_PARAMETER.
+ *
+ * Microsoft test tool checks this.
+ */
+
+ if ((opcode == SMB2_OP_NEGPROT) &&
+ (flags & SMB2_HDR_FLAG_SIGNED)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ } else {
+ status = NT_STATUS_USER_SESSION_DELETED;
+ }
+ return smbd_smb2_request_error(req, status);
+ }
+
+ signing_key = smbd_smb2_signing_key(x, xconn, &has_channel);
+
+ /*
+ * If we have a signing key, we should
+ * sign the response
+ */
+ if (smb2_signing_key_valid(signing_key) && opcode != SMB2_OP_CANCEL) {
+ req->do_signing = true;
+ }
+
+ status = smb2_signing_check_pdu(signing_key,
+ SMBD_SMB2_IN_HDR_IOV(req),
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
+ opcode == SMB2_OP_SESSSETUP && !has_channel &&
+ NT_STATUS_IS_OK(session_status))
+ {
+ if (!NT_STATUS_EQUAL(x->status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS error;
+
+ error = smb2srv_session_lookup_global(req->xconn->client,
+ x->global->session_wire_id,
+ req,
+ &session);
+ if (!NT_STATUS_IS_OK(error)) {
+ return smbd_smb2_request_error(req, error);
+ }
+
+ /*
+ * We fallback to a session of
+ * another process in order to
+ * get the signing correct.
+ *
+ * We don't set req->last_session_id here.
+ */
+ req->session = x = session;
+ }
+ goto skipped_signing;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ /*
+ * Now that we know the request was correctly signed
+ * we have to sign the response too.
+ */
+ if (opcode != SMB2_OP_CANCEL) {
+ req->do_signing = true;
+ }
+
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, session_status);
+ }
+ }
+
+ if (opcode == SMB2_OP_IOCTL) {
+ /*
+ * Some special IOCTL calls don't require
+ * file, tcon nor session.
+ *
+ * They typically don't do any real action
+ * on behalf of the client.
+ *
+ * They are mainly used to alter the behavior
+ * of the connection for testing. So we can
+ * run as root and skip all file, tcon and session
+ * checks below.
+ */
+ static const struct smbd_smb2_dispatch_table _root_ioctl_call = {
+ .opcode = SMB2_OP_IOCTL,
+ .as_root = true,
+ };
+ const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
+ size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
+ uint32_t in_ctl_code;
+ size_t needed = 8;
+
+ if (needed > body_size) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_ctl_code = IVAL(body, 0x04);
+ /*
+ * Only add trusted IOCTL codes here!
+ */
+ switch (in_ctl_code) {
+ case FSCTL_SMBTORTURE_FORCE_UNACKED_TIMEOUT:
+ call = &_root_ioctl_call;
+ break;
+ case FSCTL_VALIDATE_NEGOTIATE_INFO:
+ call = &_root_ioctl_call;
+ break;
+ case FSCTL_QUERY_NETWORK_INTERFACE_INFO:
+ call = &_root_ioctl_call;
+ break;
+ }
+ }
+
+skipped_signing:
+
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ req->compound_related = true;
+ }
+
+ if (call->need_session) {
+ if (!NT_STATUS_IS_OK(session_status)) {
+ return smbd_smb2_request_error(req, session_status);
+ }
+ }
+
+ if (call->need_tcon) {
+ SMB_ASSERT(call->need_session);
+
+ /*
+ * This call needs to be run as user.
+ *
+ * smbd_smb2_request_check_tcon()
+ * calls change_to_user() on success.
+ * Which implies set_current_user_info()
+ * and chdir_current_service().
+ */
+ status = smbd_smb2_request_check_tcon(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
+ encryption_desired = true;
+ }
+ if (req->tcon->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED) {
+ encryption_required = true;
+ }
+ if (encryption_required && !req->was_encrypted) {
+ req->do_encryption = true;
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ } else if (encryption_desired) {
+ req->do_encryption = true;
+ }
+ } else if (call->need_session) {
+ struct auth_session_info *session_info = NULL;
+
+ /*
+ * Unless we also have need_tcon (see above),
+ * we still need to call set_current_user_info().
+ */
+
+ session_info = req->session->global->auth_session_info;
+ if (session_info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+ }
+
+ if (req->session) {
+ bool update_session_global = false;
+ bool update_tcon_global = false;
+
+ smb2srv_update_crypto_flags(req, opcode,
+ &update_session_global,
+ &update_tcon_global);
+
+ if (update_session_global) {
+ status = smbXsrv_session_update(x);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ if (update_tcon_global) {
+ status = smbXsrv_tcon_update(req->tcon);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ }
+ }
+
+ if (call->fileid_ofs != 0) {
+ size_t needed = call->fileid_ofs + 16;
+ const uint8_t *body = SMBD_SMB2_IN_BODY_PTR(req);
+ size_t body_size = SMBD_SMB2_IN_BODY_LEN(req);
+ uint64_t file_id_persistent;
+ uint64_t file_id_volatile;
+ struct files_struct *fsp;
+
+ SMB_ASSERT(call->need_tcon);
+
+ if (needed > body_size) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_INVALID_PARAMETER);
+ }
+
+ file_id_persistent = BVAL(body, call->fileid_ofs + 0);
+ file_id_volatile = BVAL(body, call->fileid_ofs + 8);
+
+ fsp = file_fsp_smb2(req, file_id_persistent, file_id_volatile);
+ if (fsp == NULL) {
+ if (req->compound_related &&
+ !NT_STATUS_IS_OK(req->compound_create_err))
+ {
+ return smbd_smb2_request_error(req,
+ req->compound_create_err);
+ }
+ /*
+ * smbd_smb2_request_process_ioctl()
+ * has more checks in order to return more
+ * detailed error codes...
+ */
+ if (opcode != SMB2_OP_IOCTL) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_FILE_CLOSED);
+ }
+ } else {
+ if (fsp->fsp_flags.encryption_required && !req->was_encrypted) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_ACCESS_DENIED);
+ }
+ }
+ }
+
+ status = smbd_smb2_request_dispatch_update_counts(req, call->modify);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ if (call->as_root) {
+ SMB_ASSERT(call->fileid_ofs == 0);
+ /* This call needs to be run as root */
+ change_to_root_user();
+ } else if (opcode != SMB2_OP_KEEPALIVE) {
+ SMB_ASSERT(call->need_tcon);
+ }
+
+#define _INBYTES(_r) \
+ iov_buflen(SMBD_SMB2_IN_HDR_IOV(_r), SMBD_SMB2_NUM_IOV_PER_REQ-1)
+
+ switch (opcode) {
+ case SMB2_OP_NEGPROT:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_negprot, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_negprot(req);
+ break;
+
+ case SMB2_OP_SESSSETUP:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_sesssetup, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_sesssetup(req);
+ break;
+
+ case SMB2_OP_LOGOFF:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_logoff, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_logoff(req);
+ break;
+
+ case SMB2_OP_TCON:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_tcon, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_tcon(req);
+ break;
+
+ case SMB2_OP_TDIS:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_tdis, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_tdis(req);
+ break;
+
+ case SMB2_OP_CREATE:
+ if (req->subreq == NULL) {
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_create, profile_p,
+ req->profile, _INBYTES(req));
+ } else {
+ SMBPROFILE_IOBYTES_ASYNC_SET_BUSY(req->profile);
+ }
+ return_value = smbd_smb2_request_process_create(req);
+ break;
+
+ case SMB2_OP_CLOSE:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_close, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_close(req);
+ break;
+
+ case SMB2_OP_FLUSH:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_flush, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_flush(req);
+ break;
+
+ case SMB2_OP_READ:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_read, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_read(req);
+ break;
+
+ case SMB2_OP_WRITE:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_write, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_write(req);
+ break;
+
+ case SMB2_OP_LOCK:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_lock, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_lock(req);
+ break;
+
+ case SMB2_OP_IOCTL:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_ioctl, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_ioctl(req);
+ break;
+
+ case SMB2_OP_CANCEL:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_cancel, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_cancel(req);
+ SMBPROFILE_IOBYTES_ASYNC_END(req->profile, 0);
+
+ /*
+ * We don't need the request anymore cancel requests never
+ * have a response.
+ *
+ * smbd_smb2_request_process_cancel() already called
+ * DLIST_REMOVE(xconn->smb2.requests, req);
+ */
+ TALLOC_FREE(req);
+
+ break;
+
+ case SMB2_OP_KEEPALIVE:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_keepalive, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_keepalive(req);
+ break;
+
+ case SMB2_OP_QUERY_DIRECTORY:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_find, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_query_directory(req);
+ break;
+
+ case SMB2_OP_NOTIFY:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_notify, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_notify(req);
+ break;
+
+ case SMB2_OP_GETINFO:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_getinfo, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_getinfo(req);
+ break;
+
+ case SMB2_OP_SETINFO:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_setinfo, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_setinfo(req);
+ break;
+
+ case SMB2_OP_BREAK:
+ SMBPROFILE_IOBYTES_ASYNC_START(smb2_break, profile_p,
+ req->profile, _INBYTES(req));
+ return_value = smbd_smb2_request_process_break(req);
+ break;
+
+ default:
+ return_value = smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ break;
+ }
+ return return_value;
+}
+
+static void smbd_smb2_request_reply_update_counts(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inhdr;
+ uint16_t channel_sequence;
+ struct smbXsrv_open *op;
+
+ if (!req->request_counters_updated) {
+ return;
+ }
+
+ req->request_counters_updated = false;
+
+ if (xconn->protocol < PROTOCOL_SMB3_00) {
+ return;
+ }
+
+ if (req->compat_chain_fsp == NULL) {
+ return;
+ }
+
+ op = req->compat_chain_fsp->op;
+ if (op == NULL) {
+ return;
+ }
+
+ inhdr = SMBD_SMB2_IN_HDR_PTR(req);
+ channel_sequence = SVAL(inhdr, SMB2_HDR_CHANNEL_SEQUENCE);
+
+ if ((op->global->channel_sequence == channel_sequence) &&
+ (op->global->channel_generation == req->channel_generation)) {
+ SMB_ASSERT(op->request_count > 0);
+ op->request_count -= 1;
+ } else {
+ SMB_ASSERT(op->pre_request_count > 0);
+ op->pre_request_count -= 1;
+ }
+}
+
+static NTSTATUS smbd_smb2_request_reply(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ int first_idx = 1;
+ struct iovec *firsttf = SMBD_SMB2_IDX_TF_IOV(req,out,first_idx);
+ struct iovec *outhdr = SMBD_SMB2_OUT_HDR_IOV(req);
+ struct iovec *outdyn = SMBD_SMB2_OUT_DYN_IOV(req);
+ NTSTATUS status;
+ bool ok;
+
+ req->subreq = NULL;
+ TALLOC_FREE(req->async_te);
+
+ /* MS-SMB2: 3.3.4.1 Sending Any Outgoing Message */
+ smbd_smb2_request_reply_update_counts(req);
+
+ if (req->do_encryption &&
+ (firsttf->iov_len == 0) &&
+ (!smb2_signing_key_valid(req->first_enc_key)) &&
+ (req->session != NULL) &&
+ smb2_signing_key_valid(req->session->global->encryption_key))
+ {
+ struct smb2_signing_key *encryption_key =
+ req->session->global->encryption_key;
+ uint8_t *tf;
+ uint64_t session_id = req->session->global->session_wire_id;
+ uint64_t nonce_high;
+ uint64_t nonce_low;
+
+ status = smb2_get_new_nonce(req->session,
+ &nonce_high,
+ &nonce_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We need to place the SMB2_TRANSFORM header before the
+ * first SMB2 header
+ */
+
+ /*
+ * we need to remember the encryption key
+ * and defer the signing/encryption until
+ * we are sure that we do not change
+ * the header again.
+ */
+ status = smb2_signing_key_copy(req,
+ encryption_key,
+ &req->first_enc_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ tf = talloc_zero_array(req, uint8_t,
+ SMB2_TF_HDR_SIZE);
+ if (tf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ SIVAL(tf, SMB2_TF_PROTOCOL_ID, SMB2_TF_MAGIC);
+ SBVAL(tf, SMB2_TF_NONCE+0, nonce_low);
+ SBVAL(tf, SMB2_TF_NONCE+8, nonce_high);
+ SBVAL(tf, SMB2_TF_SESSION_ID, session_id);
+
+ firsttf->iov_base = (void *)tf;
+ firsttf->iov_len = SMB2_TF_HDR_SIZE;
+ }
+
+ if ((req->current_idx > SMBD_SMB2_NUM_IOV_PER_REQ) &&
+ (smb2_signing_key_valid(req->last_sign_key)) &&
+ (firsttf->iov_len == 0))
+ {
+ int last_idx = req->current_idx - SMBD_SMB2_NUM_IOV_PER_REQ;
+ struct iovec *lasthdr = SMBD_SMB2_IDX_HDR_IOV(req,out,last_idx);
+
+ /*
+ * As we are sure the header of the last request in the
+ * compound chain will not change, we can to sign here
+ * with the last signing key we remembered.
+ */
+ status = smb2_signing_sign_pdu(req->last_sign_key,
+ lasthdr,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ TALLOC_FREE(req->last_sign_key);
+
+ SMBPROFILE_IOBYTES_ASYNC_END(req->profile,
+ iov_buflen(outhdr, SMBD_SMB2_NUM_IOV_PER_REQ-1));
+
+ req->current_idx += SMBD_SMB2_NUM_IOV_PER_REQ;
+
+ if (req->current_idx < req->out.vector_count) {
+ /*
+ * We must process the remaining compound
+ * SMB2 requests before any new incoming SMB2
+ * requests. This is because incoming SMB2
+ * requests may include a cancel for a
+ * compound request we haven't processed
+ * yet.
+ */
+ struct tevent_immediate *im = tevent_create_immediate(req);
+ if (!im) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (req->do_signing && firsttf->iov_len == 0) {
+ struct smbXsrv_session *x = req->session;
+ struct smb2_signing_key *signing_key =
+ smbd_smb2_signing_key(x, xconn, NULL);
+
+ /*
+ * we need to remember the signing key
+ * and defer the signing until
+ * we are sure that we do not change
+ * the header again.
+ */
+ status = smb2_signing_key_copy(req,
+ signing_key,
+ &req->last_sign_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /*
+ * smbd_smb2_request_dispatch() will redo the impersonation.
+ * So we use req->xconn->client->raw_ev_ctx instead
+ * of req->ev_ctx here.
+ */
+ tevent_schedule_immediate(im,
+ req->xconn->client->raw_ev_ctx,
+ smbd_smb2_request_dispatch_immediate,
+ req);
+ return NT_STATUS_OK;
+ }
+
+ if (req->compound_related) {
+ req->compound_related = false;
+ }
+
+ ok = smb2_setup_nbt_length(req->out.vector, req->out.vector_count);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER_MIX;
+ }
+
+ /* Set credit for these operations (zero credits if this
+ is a final reply for an async operation). */
+ smb2_calculate_credits(req, req);
+
+ /*
+ * now check if we need to sign the current response
+ */
+ if (firsttf->iov_len == SMB2_TF_HDR_SIZE) {
+ status = smb2_signing_encrypt_pdu(req->first_enc_key,
+ firsttf,
+ req->out.vector_count - first_idx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else if (req->do_signing) {
+ struct smbXsrv_session *x = req->session;
+ struct smb2_signing_key *signing_key =
+ smbd_smb2_signing_key(x, xconn, NULL);
+
+ status = smb2_signing_sign_pdu(signing_key,
+ outhdr,
+ SMBD_SMB2_NUM_IOV_PER_REQ - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ TALLOC_FREE(req->first_enc_key);
+
+ if (req->preauth != NULL) {
+ gnutls_hash_hd_t hash_hnd = NULL;
+ size_t i;
+ int rc;
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ rc = gnutls_hash(hash_hnd,
+ req->preauth->sha512_value,
+ sizeof(req->preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ for (i = 1; i < req->in.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ req->in.vector[i].iov_base,
+ req->in.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ }
+ gnutls_hash_output(hash_hnd, req->preauth->sha512_value);
+
+ rc = gnutls_hash(hash_hnd,
+ req->preauth->sha512_value,
+ sizeof(req->preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ for (i = 1; i < req->out.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ req->out.vector[i].iov_base,
+ req->out.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ }
+
+ gnutls_hash_deinit(hash_hnd, req->preauth->sha512_value);
+
+ req->preauth = NULL;
+ }
+
+ /* I am a sick, sick man... :-). Sendfile hack ... JRA. */
+ if (req->out.vector_count < (2*SMBD_SMB2_NUM_IOV_PER_REQ) &&
+ outdyn->iov_base == NULL && outdyn->iov_len != 0) {
+ /* Dynamic part is NULL. Chop it off,
+ We're going to send it via sendfile. */
+ req->out.vector_count -= 1;
+ }
+
+ /*
+ * We're done with this request -
+ * move it off the "being processed" queue.
+ */
+ DLIST_REMOVE(xconn->smb2.requests, req);
+
+ req->queue_entry.mem_ctx = req;
+ req->queue_entry.vector = req->out.vector;
+ req->queue_entry.count = req->out.vector_count;
+ DLIST_ADD_END(xconn->smb2.send_queue, &req->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn);
+
+void smbd_smb2_request_dispatch_immediate(struct tevent_context *ctx,
+ struct tevent_immediate *im,
+ void *private_data)
+{
+ struct smbd_smb2_request *req = talloc_get_type_abort(private_data,
+ struct smbd_smb2_request);
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+
+ TALLOC_FREE(im);
+
+ if (DEBUGLEVEL >= 10) {
+ DEBUG(10,("smbd_smb2_request_dispatch_immediate: idx[%d] of %d vectors\n",
+ req->current_idx, req->in.vector_count));
+ print_req_vectors(req);
+ }
+
+ status = smbd_smb2_request_dispatch(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+}
+
+NTSTATUS smbd_smb2_request_done_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ DATA_BLOB body, DATA_BLOB *dyn,
+ const char *location)
+{
+ uint8_t *outhdr;
+ struct iovec *outbody_v;
+ struct iovec *outdyn_v;
+ uint32_t next_command_ofs;
+ uint64_t mid;
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ mid = BVAL(outhdr, SMB2_HDR_MESSAGE_ID);
+
+ DBG_DEBUG("mid [%"PRIu64"] idx[%d] status[%s] "
+ "body[%u] dyn[%s:%u] at %s\n",
+ mid,
+ req->current_idx,
+ nt_errstr(status),
+ (unsigned int)body.length,
+ dyn ? "yes" : "no",
+ (unsigned int)(dyn ? dyn->length : 0),
+ location);
+
+ if (body.length < 2) {
+ return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
+ }
+
+ if ((body.length % 2) != 0) {
+ return smbd_smb2_request_error(req, NT_STATUS_INTERNAL_ERROR);
+ }
+
+ outbody_v = SMBD_SMB2_OUT_BODY_IOV(req);
+ outdyn_v = SMBD_SMB2_OUT_DYN_IOV(req);
+
+ next_command_ofs = IVAL(outhdr, SMB2_HDR_NEXT_COMMAND);
+ SIVAL(outhdr, SMB2_HDR_STATUS, NT_STATUS_V(status));
+
+ outbody_v->iov_base = (void *)body.data;
+ outbody_v->iov_len = body.length;
+
+ if (dyn) {
+ outdyn_v->iov_base = (void *)dyn->data;
+ outdyn_v->iov_len = dyn->length;
+ } else {
+ outdyn_v->iov_base = NULL;
+ outdyn_v->iov_len = 0;
+ }
+
+ /*
+ * See if we need to recalculate the offset to the next response
+ *
+ * Note that all responses may require padding (including the very last
+ * one).
+ */
+ if (req->out.vector_count >= (2 * SMBD_SMB2_NUM_IOV_PER_REQ)) {
+ next_command_ofs = SMB2_HDR_BODY;
+ next_command_ofs += SMBD_SMB2_OUT_BODY_LEN(req);
+ next_command_ofs += SMBD_SMB2_OUT_DYN_LEN(req);
+ }
+
+ if ((next_command_ofs % 8) != 0) {
+ size_t pad_size = 8 - (next_command_ofs % 8);
+ if (SMBD_SMB2_OUT_DYN_LEN(req) == 0) {
+ /*
+ * if the dyn buffer is empty
+ * we can use it to add padding
+ */
+ uint8_t *pad;
+
+ pad = talloc_zero_array(req,
+ uint8_t, pad_size);
+ if (pad == NULL) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ outdyn_v->iov_base = (void *)pad;
+ outdyn_v->iov_len = pad_size;
+ } else {
+ /*
+ * For now we copy the dynamic buffer
+ * and add the padding to the new buffer
+ */
+ size_t old_size;
+ uint8_t *old_dyn;
+ size_t new_size;
+ uint8_t *new_dyn;
+
+ old_size = SMBD_SMB2_OUT_DYN_LEN(req);
+ old_dyn = SMBD_SMB2_OUT_DYN_PTR(req);
+
+ new_size = old_size + pad_size;
+ new_dyn = talloc_zero_array(req,
+ uint8_t, new_size);
+ if (new_dyn == NULL) {
+ return smbd_smb2_request_error(req,
+ NT_STATUS_NO_MEMORY);
+ }
+
+ memcpy(new_dyn, old_dyn, old_size);
+ memset(new_dyn + old_size, 0, pad_size);
+
+ outdyn_v->iov_base = (void *)new_dyn;
+ outdyn_v->iov_len = new_size;
+ }
+ next_command_ofs += pad_size;
+ }
+
+ if ((req->current_idx + SMBD_SMB2_NUM_IOV_PER_REQ) >= req->out.vector_count) {
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, 0);
+ } else {
+ SIVAL(outhdr, SMB2_HDR_NEXT_COMMAND, next_command_ofs);
+ }
+ return smbd_smb2_request_reply(req);
+}
+
+NTSTATUS smbd_smb2_request_error_ex(struct smbd_smb2_request *req,
+ NTSTATUS status,
+ uint8_t error_context_count,
+ DATA_BLOB *info,
+ const char *location)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ DATA_BLOB body;
+ DATA_BLOB _dyn;
+ uint8_t *outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+ size_t unread_bytes = smbd_smb2_unread_bytes(req);
+
+ DBG_NOTICE("smbd_smb2_request_error_ex: idx[%d] status[%s] |%s| "
+ "at %s\n", req->current_idx, nt_errstr(status),
+ info ? " +info" : "", location);
+
+ if (unread_bytes) {
+ /* Recvfile error. Drain incoming socket. */
+ size_t ret;
+
+ errno = 0;
+ ret = drain_socket(xconn->transport.sock, unread_bytes);
+ if (ret != unread_bytes) {
+ NTSTATUS error;
+
+ if (errno == 0) {
+ error = NT_STATUS_IO_DEVICE_ERROR;
+ } else {
+ error = map_nt_error_from_unix_common(errno);
+ }
+
+ DEBUG(2, ("Failed to drain %u bytes from SMB2 socket: "
+ "ret[%u] errno[%d] => %s\n",
+ (unsigned)unread_bytes,
+ (unsigned)ret, errno, nt_errstr(error)));
+ return error;
+ }
+ }
+
+ body.data = outhdr + SMB2_HDR_BODY;
+ body.length = 8;
+ SSVAL(body.data, 0, 9);
+ SCVAL(body.data, 2, error_context_count);
+
+ if (info) {
+ SIVAL(body.data, 0x04, info->length);
+ } else {
+ /* Allocated size of req->out.vector[i].iov_base
+ * *MUST BE* OUTVEC_ALLOC_SIZE. So we have room for
+ * 1 byte without having to do an alloc.
+ */
+ info = &_dyn;
+ info->data = ((uint8_t *)outhdr) +
+ OUTVEC_ALLOC_SIZE - 1;
+ info->length = 1;
+ SCVAL(info->data, 0, 0);
+ }
+
+ /*
+ * Note: Even if there is an error, continue to process the request.
+ * per MS-SMB2.
+ */
+
+ return smbd_smb2_request_done_ex(req, status, body, info, __location__);
+}
+
+struct smbd_smb2_break_state {
+ struct tevent_req *req;
+ struct smbd_smb2_send_queue queue_entry;
+ uint8_t nbt_hdr[NBT_HDR_SIZE];
+ uint8_t hdr[SMB2_HDR_BODY];
+ struct iovec vector[1+SMBD_SMB2_NUM_IOV_PER_REQ];
+};
+
+static struct tevent_req *smbd_smb2_break_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *xconn,
+ uint64_t session_id,
+ const uint8_t *body,
+ size_t body_len)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_break_state *state = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_break_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->req = req;
+ tevent_req_defer_callback(req, ev);
+
+ SIVAL(state->hdr, 0, SMB2_MAGIC);
+ SSVAL(state->hdr, SMB2_HDR_LENGTH, SMB2_HDR_BODY);
+ SSVAL(state->hdr, SMB2_HDR_EPOCH, 0);
+ SIVAL(state->hdr, SMB2_HDR_STATUS, 0);
+ SSVAL(state->hdr, SMB2_HDR_OPCODE, SMB2_OP_BREAK);
+ SSVAL(state->hdr, SMB2_HDR_CREDIT, 0);
+ SIVAL(state->hdr, SMB2_HDR_FLAGS, SMB2_HDR_FLAG_REDIRECT);
+ SIVAL(state->hdr, SMB2_HDR_NEXT_COMMAND, 0);
+ SBVAL(state->hdr, SMB2_HDR_MESSAGE_ID, UINT64_MAX);
+ SIVAL(state->hdr, SMB2_HDR_PID, 0);
+ SIVAL(state->hdr, SMB2_HDR_TID, 0);
+ SBVAL(state->hdr, SMB2_HDR_SESSION_ID, session_id);
+ memset(state->hdr+SMB2_HDR_SIGNATURE, 0, 16);
+
+ state->vector[0] = (struct iovec) {
+ .iov_base = state->nbt_hdr,
+ .iov_len = sizeof(state->nbt_hdr)
+ };
+
+ state->vector[1+SMBD_SMB2_TF_IOV_OFS] = (struct iovec) {
+ .iov_base = NULL,
+ .iov_len = 0
+ };
+
+ state->vector[1+SMBD_SMB2_HDR_IOV_OFS] = (struct iovec) {
+ .iov_base = state->hdr,
+ .iov_len = sizeof(state->hdr)
+ };
+
+ state->vector[1+SMBD_SMB2_BODY_IOV_OFS] = (struct iovec) {
+ .iov_base = discard_const_p(uint8_t, body),
+ .iov_len = body_len,
+ };
+
+ /*
+ * state->vector[1+SMBD_SMB2_DYN_IOV_OFS] is NULL by talloc_zero above
+ */
+
+ ok = smb2_setup_nbt_length(state->vector,
+ 1 + SMBD_SMB2_NUM_IOV_PER_REQ);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * We require TCP acks for this PDU to the client!
+ * We want 5 retransmissions and timeout when the
+ * retransmission timeout (rto) passed 6 times.
+ *
+ * required_acked_bytes gets a dummy value of
+ * UINT64_MAX, as long it's in xconn->smb2.send_queue,
+ * it'll get the real value when it's moved to
+ * xconn->ack.queue.
+ *
+ * state->queue_entry.ack.req gets completed with
+ * 1. tevent_req_done(), when all bytes are acked.
+ * 2a. tevent_req_nterror(NT_STATUS_IO_TIMEOUT), when
+ * the timeout expired before all bytes were acked.
+ * 2b. tevent_req_nterror(transport_error), when the
+ * connection got a disconnect from the kernel.
+ */
+ state->queue_entry.ack.timeout =
+ timeval_current_ofs_usec(xconn->ack.rto_usecs * 6);
+ state->queue_entry.ack.required_acked_bytes = UINT64_MAX;
+ state->queue_entry.ack.req = req;
+ state->queue_entry.mem_ctx = state;
+ state->queue_entry.vector = state->vector;
+ state->queue_entry.count = ARRAY_SIZE(state->vector);
+ DLIST_ADD_END(xconn->smb2.send_queue, &state->queue_entry);
+ xconn->smb2.send_queue_len++;
+
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static NTSTATUS smbd_smb2_break_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct smbXsrv_pending_break {
+ struct smbXsrv_pending_break *prev, *next;
+ struct smbXsrv_client *client;
+ bool disable_oplock_break_retries;
+ uint64_t session_id;
+ uint64_t last_channel_id;
+ union {
+ uint8_t generic[1];
+ uint8_t oplock[0x18];
+ uint8_t lease[0x2c];
+ } body;
+ size_t body_len;
+};
+
+static void smbXsrv_pending_break_done(struct tevent_req *subreq);
+
+static struct smbXsrv_pending_break *smbXsrv_pending_break_create(
+ struct smbXsrv_client *client,
+ uint64_t session_id)
+{
+ struct smbXsrv_pending_break *pb = NULL;
+
+ pb = talloc_zero(client, struct smbXsrv_pending_break);
+ if (pb == NULL) {
+ return NULL;
+ }
+ pb->client = client;
+ pb->session_id = session_id;
+ pb->disable_oplock_break_retries = lp_smb2_disable_oplock_break_retry();
+
+ return pb;
+}
+
+static NTSTATUS smbXsrv_pending_break_submit(struct smbXsrv_pending_break *pb);
+
+static NTSTATUS smbXsrv_pending_break_schedule(struct smbXsrv_pending_break *pb)
+{
+ struct smbXsrv_client *client = pb->client;
+ NTSTATUS status;
+
+ DLIST_ADD_END(client->pending_breaks, pb);
+ status = smbXsrv_client_pending_breaks_updated(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbXsrv_pending_break_submit(pb);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_pending_break_submit(struct smbXsrv_pending_break *pb)
+{
+ struct smbXsrv_client *client = pb->client;
+ struct smbXsrv_session *session = NULL;
+ struct smbXsrv_connection *xconn = NULL;
+ struct smbXsrv_connection *oplock_xconn = NULL;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+
+ if (pb->session_id != 0) {
+ status = get_valid_smbXsrv_session(client,
+ pb->session_id,
+ &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ return NT_STATUS_ABANDONED;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (pb->last_channel_id != 0) {
+ /*
+ * This is what current Windows servers
+ * do, they don't retry on all available
+ * channels. They only use the last channel.
+ *
+ * But it doesn't match the specification in
+ * [MS-SMB2] "3.3.4.6 Object Store Indicates an
+ * Oplock Break"
+ *
+ * Per default disable_oplock_break_retries is false
+ * and we behave like the specification.
+ */
+ if (pb->disable_oplock_break_retries) {
+ return NT_STATUS_ABANDONED;
+ }
+ }
+ }
+
+ for (xconn = client->connections; xconn != NULL; xconn = xconn->next) {
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ continue;
+ }
+
+ if (xconn->channel_id == 0) {
+ /*
+ * non-multichannel case
+ */
+ break;
+ }
+
+ if (session != NULL) {
+ struct smbXsrv_channel_global0 *c = NULL;
+
+ /*
+ * Having a session means we're handling
+ * an oplock break and we only need to
+ * use channels available on the
+ * session.
+ */
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+
+ /*
+ * This is what current Windows servers
+ * do, they don't retry on all available
+ * channels. They only use the last channel.
+ *
+ * But it doesn't match the specification
+ * in [MS-SMB2] "3.3.4.6 Object Store Indicates an
+ * Oplock Break"
+ *
+ * Per default disable_oplock_break_retries is false
+ * and we behave like the specification.
+ */
+ if (pb->disable_oplock_break_retries) {
+ oplock_xconn = xconn;
+ continue;
+ }
+ }
+
+ if (xconn->channel_id > pb->last_channel_id) {
+ /*
+ * multichannel case
+ */
+ break;
+ }
+ }
+
+ if (xconn == NULL) {
+ xconn = oplock_xconn;
+ }
+
+ if (xconn == NULL) {
+ /*
+ * If there's no remaining connection available
+ * tell the caller to stop...
+ */
+ return NT_STATUS_ABANDONED;
+ }
+
+ pb->last_channel_id = xconn->channel_id;
+
+ subreq = smbd_smb2_break_send(pb,
+ client->raw_ev_ctx,
+ xconn,
+ pb->session_id,
+ pb->body.generic,
+ pb->body_len);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq,
+ smbXsrv_pending_break_done,
+ pb);
+
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_pending_break_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_pending_break *pb =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_pending_break);
+ struct smbXsrv_client *client = pb->client;
+ NTSTATUS status;
+
+ status = smbd_smb2_break_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbXsrv_pending_break_submit(pb);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ABANDONED)) {
+ /*
+ * If there's no remaining connection
+ * there's no need to send a break again.
+ */
+ goto remove;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(client, nt_errstr(status));
+ return;
+ }
+ return;
+ }
+
+remove:
+ DLIST_REMOVE(client->pending_breaks, pb);
+ TALLOC_FREE(pb);
+
+ status = smbXsrv_client_pending_breaks_updated(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_disconnect_client(client, nt_errstr(status));
+ return;
+ }
+}
+
+NTSTATUS smbd_smb2_send_oplock_break(struct smbXsrv_client *client,
+ struct smbXsrv_open *op,
+ uint8_t oplock_level)
+{
+ struct smbXsrv_pending_break *pb = NULL;
+ uint8_t *body = NULL;
+
+ pb = smbXsrv_pending_break_create(client,
+ op->compat->vuid);
+ if (pb == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pb->body_len = sizeof(pb->body.oplock);
+ body = pb->body.oplock;
+
+ SSVAL(body, 0x00, pb->body_len);
+ SCVAL(body, 0x02, oplock_level);
+ SCVAL(body, 0x03, 0); /* reserved */
+ SIVAL(body, 0x04, 0); /* reserved */
+ SBVAL(body, 0x08, op->global->open_persistent_id);
+ SBVAL(body, 0x10, op->global->open_volatile_id);
+
+ return smbXsrv_pending_break_schedule(pb);
+}
+
+NTSTATUS smbd_smb2_send_lease_break(struct smbXsrv_client *client,
+ uint16_t new_epoch,
+ uint32_t lease_flags,
+ struct smb2_lease_key *lease_key,
+ uint32_t current_lease_state,
+ uint32_t new_lease_state)
+{
+ struct smbXsrv_pending_break *pb = NULL;
+ uint8_t *body = NULL;
+
+ pb = smbXsrv_pending_break_create(client,
+ 0); /* no session_id */
+ if (pb == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pb->body_len = sizeof(pb->body.lease);
+ body = pb->body.lease;
+
+ SSVAL(body, 0x00, pb->body_len);
+ SSVAL(body, 0x02, new_epoch);
+ SIVAL(body, 0x04, lease_flags);
+ SBVAL(body, 0x08, lease_key->data[0]);
+ SBVAL(body, 0x10, lease_key->data[1]);
+ SIVAL(body, 0x18, current_lease_state);
+ SIVAL(body, 0x1c, new_lease_state);
+ SIVAL(body, 0x20, 0); /* BreakReason, MUST be 0 */
+ SIVAL(body, 0x24, 0); /* AccessMaskHint, MUST be 0 */
+ SIVAL(body, 0x28, 0); /* ShareMaskHint, MUST be 0 */
+
+ return smbXsrv_pending_break_schedule(pb);
+}
+
+static bool is_smb2_recvfile_write(struct smbd_smb2_request_read_state *state)
+{
+ NTSTATUS status;
+ uint32_t flags;
+ uint64_t file_id_persistent;
+ uint64_t file_id_volatile;
+ struct smbXsrv_open *op = NULL;
+ struct files_struct *fsp = NULL;
+ const uint8_t *body = NULL;
+
+ /*
+ * This is only called with a pktbuf
+ * of at least SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN
+ * bytes
+ */
+
+ if (IVAL(state->pktbuf, 0) == SMB2_TF_MAGIC) {
+ /* Transform header. Cannot recvfile. */
+ return false;
+ }
+ if (IVAL(state->pktbuf, 0) != SMB2_MAGIC) {
+ /* Not SMB2. Normal error path will cope. */
+ return false;
+ }
+ if (SVAL(state->pktbuf, 4) != SMB2_HDR_BODY) {
+ /* Not SMB2. Normal error path will cope. */
+ return false;
+ }
+ if (SVAL(state->pktbuf, SMB2_HDR_OPCODE) != SMB2_OP_WRITE) {
+ /* Needs to be a WRITE. */
+ return false;
+ }
+ if (IVAL(state->pktbuf, SMB2_HDR_NEXT_COMMAND) != 0) {
+ /* Chained. Cannot recvfile. */
+ return false;
+ }
+ flags = IVAL(state->pktbuf, SMB2_HDR_FLAGS);
+ if (flags & SMB2_HDR_FLAG_CHAINED) {
+ /* Chained. Cannot recvfile. */
+ return false;
+ }
+ if (flags & SMB2_HDR_FLAG_SIGNED) {
+ /* Signed. Cannot recvfile. */
+ return false;
+ }
+
+ body = &state->pktbuf[SMB2_HDR_BODY];
+
+ file_id_persistent = BVAL(body, 0x10);
+ file_id_volatile = BVAL(body, 0x18);
+
+ status = smb2srv_open_lookup(state->req->xconn,
+ file_id_persistent,
+ file_id_volatile,
+ 0, /* now */
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ fsp = op->compat;
+ if (fsp == NULL) {
+ return false;
+ }
+ if (fsp->conn == NULL) {
+ return false;
+ }
+
+ if (IS_IPC(fsp->conn)) {
+ return false;
+ }
+ if (IS_PRINT(fsp->conn)) {
+ return false;
+ }
+ if (fsp_is_alternate_stream(fsp)) {
+ return false;
+ }
+
+ DEBUG(10,("Doing recvfile write len = %u\n",
+ (unsigned int)(state->pktfull - state->pktlen)));
+
+ return true;
+}
+
+static NTSTATUS smbd_smb2_request_next_incoming(struct smbXsrv_connection *xconn)
+{
+ struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+ struct smbd_smb2_request *req = NULL;
+ size_t max_send_queue_len;
+ size_t cur_send_queue_len;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (state->req != NULL) {
+ /*
+ * if there is already a tstream_readv_pdu
+ * pending, we are done.
+ */
+ return NT_STATUS_OK;
+ }
+
+ max_send_queue_len = MAX(1, xconn->smb2.credits.max/16);
+ cur_send_queue_len = xconn->smb2.send_queue_len;
+
+ if (cur_send_queue_len > max_send_queue_len) {
+ /*
+ * if we have a lot of requests to send,
+ * we wait until they are on the wire until we
+ * ask for the next request.
+ */
+ return NT_STATUS_OK;
+ }
+
+ /* ask for the next request */
+ req = smbd_smb2_request_allocate(xconn);
+ if (req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *state = (struct smbd_smb2_request_read_state) {
+ .req = req,
+ .min_recv_size = lp_min_receive_file_size(),
+ ._vector = {
+ [0] = (struct iovec) {
+ .iov_base = (void *)state->hdr.nbt,
+ .iov_len = NBT_HDR_SIZE,
+ },
+ },
+ .vector = state->_vector,
+ .count = 1,
+ };
+
+ TEVENT_FD_READABLE(xconn->transport.fde);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_smb2_process_negprot(struct smbXsrv_connection *xconn,
+ uint64_t expected_seq_low,
+ const uint8_t *inpdu, size_t size)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ NTSTATUS status;
+ struct smbd_smb2_request *req = NULL;
+
+ DEBUG(10,("smbd_smb2_first_negprot: packet length %u\n",
+ (unsigned int)size));
+
+ status = smbd_initialize_smb2(xconn, expected_seq_low);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * If a new connection joins the process, when we're
+ * already in a "pending break cycle", we need to
+ * turn on the ack checker on the new connection.
+ */
+ status = smbXsrv_client_pending_breaks_updated(xconn->client);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * If there's a problem, we disconnect the whole
+ * client with all connections here!
+ *
+ * Instead of just the new connection.
+ */
+ smbd_server_disconnect_client(xconn->client, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_create(xconn, inpdu, size, &req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_validate(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_setup_out(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+#ifdef WITH_PROFILE
+ /*
+ * this was already counted at the SMB1 layer =>
+ * smbd_smb2_request_dispatch() should not count it twice.
+ */
+ if (profile_p->values.request_stats.count > 0) {
+ profile_p->values.request_stats.count--;
+ }
+#endif
+ status = smbd_smb2_request_dispatch(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return status;
+ }
+
+ sconn->num_requests++;
+ return NT_STATUS_OK;
+}
+
+static int socket_error_from_errno(int ret,
+ int sys_errno,
+ bool *retry)
+{
+ *retry = false;
+
+ if (ret >= 0) {
+ return 0;
+ }
+
+ if (ret != -1) {
+ return EIO;
+ }
+
+ if (sys_errno == 0) {
+ return EIO;
+ }
+
+ if (sys_errno == EINTR) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ if (sys_errno == EINPROGRESS) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ if (sys_errno == EAGAIN) {
+ *retry = true;
+ return sys_errno;
+ }
+
+ /* ENOMEM is retryable on Solaris/illumos, and possibly other systems. */
+ if (sys_errno == ENOMEM) {
+ *retry = true;
+ return sys_errno;
+ }
+
+#ifdef EWOULDBLOCK
+#if EWOULDBLOCK != EAGAIN
+ if (sys_errno == EWOULDBLOCK) {
+ *retry = true;
+ return sys_errno;
+ }
+#endif
+#endif
+
+ return sys_errno;
+}
+
+static NTSTATUS smbd_smb2_advance_send_queue(struct smbXsrv_connection *xconn,
+ struct smbd_smb2_send_queue **_e,
+ size_t n)
+{
+ struct smbd_smb2_send_queue *e = *_e;
+ bool ok;
+
+ xconn->ack.unacked_bytes += n;
+
+ ok = iov_advance(&e->vector, &e->count, n);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (e->count > 0) {
+ return NT_STATUS_RETRY;
+ }
+
+ xconn->smb2.send_queue_len--;
+ DLIST_REMOVE(xconn->smb2.send_queue, e);
+
+ if (e->ack.req == NULL) {
+ *_e = NULL;
+ talloc_free(e->mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ e->ack.required_acked_bytes = xconn->ack.unacked_bytes;
+ DLIST_ADD_END(xconn->ack.queue, e);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_flush_with_sendmsg(struct smbXsrv_connection *xconn)
+{
+ int ret;
+ int err;
+ bool retry;
+ NTSTATUS status;
+
+ if (xconn->smb2.send_queue == NULL) {
+ TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+
+ while (xconn->smb2.send_queue != NULL) {
+ struct smbd_smb2_send_queue *e = xconn->smb2.send_queue;
+ unsigned sendmsg_flags = 0;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ * just flush all pending stuff.
+ */
+ xconn->smb2.send_queue_len--;
+ DLIST_REMOVE(xconn->smb2.send_queue, e);
+
+ talloc_free(e->mem_ctx);
+ continue;
+ }
+
+ if (e->sendfile_header != NULL) {
+ size_t size = 0;
+ size_t i = 0;
+ uint8_t *buf;
+
+ status = NT_STATUS_INTERNAL_ERROR;
+
+ for (i=0; i < e->count; i++) {
+ size += e->vector[i].iov_len;
+ }
+
+ if (size <= e->sendfile_header->length) {
+ buf = e->sendfile_header->data;
+ } else {
+ buf = talloc_array(e->mem_ctx, uint8_t, size);
+ if (buf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ size = 0;
+ for (i=0; i < e->count; i++) {
+ memcpy(buf+size,
+ e->vector[i].iov_base,
+ e->vector[i].iov_len);
+ size += e->vector[i].iov_len;
+ }
+
+ e->sendfile_header->data = buf;
+ e->sendfile_header->length = size;
+ e->sendfile_status = &status;
+ e->count = 0;
+
+ xconn->smb2.send_queue_len--;
+ DLIST_REMOVE(xconn->smb2.send_queue, e);
+
+ size += e->sendfile_body_size;
+
+ /*
+ * This triggers the sendfile path via
+ * the destructor.
+ */
+ talloc_free(e->mem_ctx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ xconn->ack.unacked_bytes += size;
+ continue;
+ }
+
+ e->msg = (struct msghdr) {
+ .msg_iov = e->vector,
+ .msg_iovlen = e->count,
+ };
+
+#ifdef MSG_NOSIGNAL
+ sendmsg_flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+ sendmsg_flags |= MSG_DONTWAIT;
+#endif
+
+ ret = sendmsg(xconn->transport.sock, &e->msg, sendmsg_flags);
+ if (ret == 0) {
+ /* propagate end of file */
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ err = socket_error_from_errno(ret, errno, &retry);
+ if (retry) {
+ /* retry later */
+ TEVENT_FD_WRITEABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (err != 0) {
+ status = map_nt_error_from_unix_common(err);
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+
+ status = smbd_smb2_advance_send_queue(xconn, &e, ret);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* retry later */
+ TEVENT_FD_WRITEABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ }
+
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS smbd_smb2_flush_send_queue(struct smbXsrv_connection *xconn)
+{
+ NTSTATUS status;
+
+ status = smbd_smb2_flush_with_sendmsg(xconn);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return status;
+ }
+
+ /*
+ * Restart reads if we were blocked on
+ * draining the send queue.
+ */
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_advance_incoming(struct smbXsrv_connection *xconn, size_t n)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+ struct smbd_smb2_request *req = NULL;
+ size_t min_recvfile_size = UINT32_MAX;
+ NTSTATUS status;
+ NTTIME now;
+ bool ok;
+
+ ok = iov_advance(&state->vector, &state->count, n);
+ if (!ok) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (state->count > 0) {
+ return NT_STATUS_PENDING;
+ }
+
+ if (state->pktlen > 0) {
+ if (!state->doing_receivefile) {
+ /*
+ * we have all the data.
+ */
+ goto got_full;
+ }
+
+ if (!is_smb2_recvfile_write(state)) {
+ size_t ofs = state->pktlen;
+
+ /*
+ * Not a possible receivefile write.
+ * Read the rest of the data.
+ */
+ state->doing_receivefile = false;
+
+ state->pktbuf = talloc_realloc(state->req,
+ state->pktbuf,
+ uint8_t,
+ state->pktfull);
+ if (state->pktbuf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->_vector[0] = (struct iovec) {
+ .iov_base = (void *)(state->pktbuf + ofs),
+ .iov_len = (state->pktfull - ofs),
+ };
+ state->vector = state->_vector;
+ state->count = 1;
+
+ state->pktlen = state->pktfull;
+ return NT_STATUS_RETRY;
+ }
+
+ /*
+ * This is a receivefile write so we've
+ * done a short read.
+ */
+ goto got_full;
+ }
+
+ /*
+ * Now we analyze the NBT header
+ */
+ if (state->hdr.nbt[0] != 0x00) {
+ state->min_recv_size = 0;
+ }
+ state->pktfull = smb2_len(state->hdr.nbt);
+ if (state->pktfull == 0) {
+ goto got_full;
+ }
+
+ if (state->min_recv_size != 0) {
+ min_recvfile_size = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
+ min_recvfile_size += state->min_recv_size;
+ }
+
+ if (state->pktfull > min_recvfile_size) {
+ /*
+ * Might be a receivefile write. Read the SMB2 HEADER +
+ * SMB2_WRITE header first. Set 'doing_receivefile'
+ * as we're *attempting* receivefile write. If this
+ * turns out not to be a SMB2_WRITE request or otherwise
+ * not suitable then we'll just read the rest of the data
+ * the next time this function is called.
+ */
+ state->pktlen = SMBD_SMB2_SHORT_RECEIVEFILE_WRITE_LEN;
+ state->doing_receivefile = true;
+ } else {
+ state->pktlen = state->pktfull;
+ }
+
+ state->pktbuf = talloc_array(state->req, uint8_t, state->pktlen);
+ if (state->pktbuf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->_vector[0] = (struct iovec) {
+ .iov_base = (void *)state->pktbuf,
+ .iov_len = state->pktlen,
+ };
+ state->vector = state->_vector;
+ state->count = 1;
+
+ return NT_STATUS_RETRY;
+
+got_full:
+
+ if (state->hdr.nbt[0] != 0x00) {
+ DEBUG(1,("ignore NBT[0x%02X] msg\n",
+ state->hdr.nbt[0]));
+
+ req = state->req;
+ *state = (struct smbd_smb2_request_read_state) {
+ .req = req,
+ .min_recv_size = lp_min_receive_file_size(),
+ ._vector = {
+ [0] = (struct iovec) {
+ .iov_base = (void *)state->hdr.nbt,
+ .iov_len = NBT_HDR_SIZE,
+ },
+ },
+ .vector = state->_vector,
+ .count = 1,
+ };
+ return NT_STATUS_RETRY;
+ }
+
+ req = state->req;
+
+ req->request_time = timeval_current();
+ now = timeval_to_nttime(&req->request_time);
+
+ status = smbd_smb2_inbuf_parse_compound(xconn,
+ now,
+ state->pktbuf,
+ state->pktlen,
+ req,
+ &req->in.vector,
+ &req->in.vector_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (state->doing_receivefile) {
+ req->smb1req = talloc_zero(req, struct smb_request);
+ if (req->smb1req == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ req->smb1req->unread_bytes = state->pktfull - state->pktlen;
+ }
+
+ *state = (struct smbd_smb2_request_read_state) {
+ .req = NULL,
+ };
+
+ req->current_idx = 1;
+
+ DEBUG(10,("smbd_smb2_request idx[%d] of %d vectors\n",
+ req->current_idx, req->in.vector_count));
+
+ status = smbd_smb2_request_validate(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_request_setup_out(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smbd_smb2_request_dispatch(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ sconn->num_requests++;
+
+ /* The timeout_processing function isn't run nearly
+ often enough to implement 'max log size' without
+ overrunning the size of the file by many megabytes.
+ This is especially true if we are running at debug
+ level 10. Checking every 50 SMB2s is a nice
+ tradeoff of performance vs log file size overrun. */
+
+ if ((sconn->num_requests % 50) == 0 &&
+ need_to_check_log_size()) {
+ change_to_root_user();
+ check_log_size();
+ }
+
+ status = smbd_smb2_request_next_incoming(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_io_handler(struct smbXsrv_connection *xconn,
+ uint16_t fde_flags)
+{
+ struct smbd_smb2_request_read_state *state = &xconn->smb2.request_read_state;
+ unsigned recvmsg_flags = 0;
+ int ret;
+ int err;
+ bool retry;
+ NTSTATUS status;
+
+ if (!NT_STATUS_IS_OK(xconn->transport.status)) {
+ /*
+ * we're not supposed to do any io
+ */
+ TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+ TEVENT_FD_NOT_WRITEABLE(xconn->transport.fde);
+ TEVENT_FD_NOT_WANTERROR(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+
+ if (fde_flags & TEVENT_FD_ERROR) {
+ ret = samba_socket_poll_or_sock_error(xconn->transport.sock);
+ if (ret == -1) {
+ err = errno;
+ status = map_nt_error_from_unix_common(err);
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ /* This should not happen */
+ status = NT_STATUS_REMOTE_DISCONNECT;
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+
+ if (fde_flags & TEVENT_FD_WRITE) {
+ status = smbd_smb2_flush_send_queue(xconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (!(fde_flags & TEVENT_FD_READ)) {
+ return NT_STATUS_OK;
+ }
+
+ if (state->req == NULL) {
+ TEVENT_FD_NOT_READABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+
+again:
+
+ state->msg = (struct msghdr) {
+ .msg_iov = state->vector,
+ .msg_iovlen = state->count,
+ };
+
+#ifdef MSG_NOSIGNAL
+ recvmsg_flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+ recvmsg_flags |= MSG_DONTWAIT;
+#endif
+
+ ret = recvmsg(xconn->transport.sock, &state->msg, recvmsg_flags);
+ if (ret == 0) {
+ /* propagate end of file */
+ status = NT_STATUS_END_OF_FILE;
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+ err = socket_error_from_errno(ret, errno, &retry);
+ if (retry) {
+ /* retry later */
+ TEVENT_FD_READABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (err != 0) {
+ status = map_nt_error_from_unix_common(err);
+ smbXsrv_connection_disconnect_transport(xconn,
+ status);
+ return status;
+ }
+
+ status = smbd_smb2_advance_incoming(xconn, ret);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PENDING)) {
+ /* we have more to read */
+ TEVENT_FD_READABLE(xconn->transport.fde);
+ return NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /*
+ * smbd_smb2_advance_incoming setup a new vector
+ * that we should try to read immediately.
+ */
+ goto again;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void smbd_smb2_connection_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct smbXsrv_connection *xconn =
+ talloc_get_type_abort(private_data,
+ struct smbXsrv_connection);
+ NTSTATUS status;
+
+ status = smbd_smb2_io_handler(xconn, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ return;
+ }
+}
diff --git a/source3/smbd/smb2_service.c b/source3/smbd/smb2_service.c
new file mode 100644
index 0000000..856974c
--- /dev/null
+++ b/source3/smbd/smb2_service.c
@@ -0,0 +1,951 @@
+/*
+ Unix SMB/CIFS implementation.
+ service (connection) opening and closing
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/passwd.h" /* uid_wrapper */
+#include "../lib/tsocket/tsocket.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../libcli/security/security.h"
+#include "printing/pcap.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "lib/param/loadparm.h"
+#include "messages.h"
+#include "lib/afs/afs_funcs.h"
+#include "lib/util_path.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+bool canonicalize_connect_path(connection_struct *conn)
+{
+ bool ret;
+ struct smb_filename con_fname = { .base_name = conn->connectpath };
+ struct smb_filename *resolved_fname = SMB_VFS_REALPATH(conn, talloc_tos(),
+ &con_fname);
+ if (resolved_fname == NULL) {
+ return false;
+ }
+ ret = set_conn_connectpath(conn,resolved_fname->base_name);
+ TALLOC_FREE(resolved_fname);
+ return ret;
+}
+
+/****************************************************************************
+ Ensure when setting connectpath it is a canonicalized (no ./ // or ../)
+ absolute path stating in / and not ending in /.
+****************************************************************************/
+
+bool set_conn_connectpath(connection_struct *conn, const char *connectpath)
+{
+ char *destname;
+
+ if (connectpath == NULL || connectpath[0] == '\0') {
+ return false;
+ }
+
+ destname = canonicalize_absolute_path(conn, connectpath);
+ if (destname == NULL) {
+ return false;
+ }
+
+ DBG_DEBUG("service %s, connectpath = %s\n",
+ lp_const_servicename(SNUM(conn)), destname);
+
+ talloc_free(conn->connectpath);
+ conn->connectpath = destname;
+ /*
+ * Ensure conn->cwd_fsp->fsp_name is initialized.
+ * start as conn->connectpath.
+ */
+ TALLOC_FREE(conn->cwd_fsp->fsp_name);
+ conn->cwd_fsp->fsp_name = synthetic_smb_fname(conn,
+ conn->connectpath,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (conn->cwd_fsp->fsp_name == NULL) {
+ return false;
+ }
+ return true;
+}
+
+bool chdir_current_service(connection_struct *conn)
+{
+ const struct smb_filename connectpath_fname = {
+ .base_name = conn->connectpath,
+ };
+ int saved_errno = 0;
+ char *utok_str = NULL;
+ int ret;
+
+ conn->lastused_count++;
+
+ ret = vfs_ChDir(conn, &connectpath_fname);
+ if (ret == 0) {
+ return true;
+ }
+ saved_errno = errno;
+
+ utok_str = utok_string(talloc_tos(),
+ conn->session_info->unix_token);
+ if (utok_str == NULL) {
+ errno = saved_errno;
+ return false;
+ }
+
+ DBG_ERR("vfs_ChDir(%s) failed: %s. Current token: %s\n",
+ conn->connectpath,
+ strerror(saved_errno),
+ utok_str);
+
+ if (saved_errno != 0) {
+ errno = saved_errno;
+ }
+ return false;
+}
+
+/****************************************************************************
+ do some basic sainity checks on the share.
+ This function modifies dev, ecode.
+****************************************************************************/
+
+static NTSTATUS share_sanity_checks(const struct tsocket_address *local_address,
+ const struct tsocket_address *remote_address,
+ const char *rhost,
+ int snum,
+ fstring dev)
+{
+ char *raddr;
+
+ if (!lp_allow_local_address(snum, local_address)) {
+ char *laddr = NULL;
+
+ laddr = tsocket_address_inet_addr_string(
+ local_address, talloc_tos());
+ if (laddr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ raddr = tsocket_address_inet_addr_string(
+ remote_address, laddr);
+ if (raddr == NULL) {
+ TALLOC_FREE(laddr);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_ERR("Denied connection from %s (%s) to \\\\%s\\%s\n",
+ rhost, raddr, laddr, lp_const_servicename(snum));
+ TALLOC_FREE(laddr);
+
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+
+ raddr = tsocket_address_inet_addr_string(remote_address,
+ talloc_tos());
+ if (raddr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!lp_snum_ok(snum) ||
+ !allow_access(lp_hosts_deny(snum), lp_hosts_allow(snum),
+ rhost, raddr)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (dev[0] == '?' || !dev[0]) {
+ if (lp_printable(snum)) {
+ fstrcpy(dev,"LPT1:");
+ } else if (strequal(lp_fstype(snum), "IPC")) {
+ fstrcpy(dev, "IPC");
+ } else {
+ fstrcpy(dev,"A:");
+ }
+ }
+
+ if (!strupper_m(dev)) {
+ DBG_WARNING("strupper_m %s failed\n", dev);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (lp_printable(snum)) {
+ if (!strequal(dev, "LPT1:")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+ } else if (strequal(lp_fstype(snum), "IPC")) {
+ if (!strequal(dev, "IPC")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+ } else if (!strequal(dev, "A:")) {
+ return NT_STATUS_BAD_DEVICE_TYPE;
+ }
+
+ /* Behave as a printer if we are supposed to */
+ if (lp_printable(snum) && (strcmp(dev, "A:") == 0)) {
+ fstrcpy(dev, "LPT1:");
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Go through lookup_name etc to find the force'd group.
+ *
+ * Create a new token from src_token, replacing the primary group sid with the
+ * one found.
+ */
+
+static NTSTATUS find_forced_group(bool force_user,
+ int snum, const char *username,
+ struct dom_sid *pgroup_sid,
+ gid_t *pgid)
+{
+ NTSTATUS result = NT_STATUS_NO_SUCH_GROUP;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct dom_sid group_sid;
+ enum lsa_SidType type;
+ char *groupname;
+ bool user_must_be_member = False;
+ gid_t gid;
+
+ groupname = lp_force_group(talloc_tos(), lp_sub, snum);
+ if (groupname == NULL) {
+ DBG_WARNING("talloc_strdup failed\n");
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (groupname[0] == '+') {
+ user_must_be_member = True;
+ groupname += 1;
+ }
+
+ groupname = talloc_string_sub(talloc_tos(), groupname,
+ "%S", lp_const_servicename(snum));
+ if (groupname == NULL) {
+ DBG_WARNING("talloc_string_sub failed\n");
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (!lookup_name_smbconf(talloc_tos(), groupname,
+ LOOKUP_NAME_ALL|LOOKUP_NAME_GROUP,
+ NULL, NULL, &group_sid, &type)) {
+ DBG_DEBUG("lookup_name_smbconf(%s) failed\n",
+ groupname);
+ goto done;
+ }
+
+ if ((type != SID_NAME_DOM_GRP) && (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ DBG_DEBUG("%s is a %s, not a group\n", groupname,
+ sid_type_lookup(type));
+ goto done;
+ }
+
+ if (!sid_to_gid(&group_sid, &gid)) {
+ struct dom_sid_buf buf;
+ DBG_DEBUG("sid_to_gid(%s) for %s failed\n",
+ dom_sid_str_buf(&group_sid, &buf), groupname);
+ goto done;
+ }
+
+ /*
+ * If the user has been forced and the forced group starts with a '+',
+ * then we only set the group to be the forced group if the forced
+ * user is a member of that group. Otherwise, the meaning of the '+'
+ * would be ignored.
+ */
+
+ if (force_user && user_must_be_member) {
+ if (user_in_group_sid(username, &group_sid)) {
+ sid_copy(pgroup_sid, &group_sid);
+ *pgid = gid;
+ DBG_INFO("Forced group %s for member %s\n",
+ groupname, username);
+ } else {
+ DBG_ERR("find_forced_group: forced user %s is not a member "
+ "of forced group %s. Disallowing access.\n",
+ username, groupname );
+ result = NT_STATUS_MEMBER_NOT_IN_GROUP;
+ goto done;
+ }
+ } else {
+ sid_copy(pgroup_sid, &group_sid);
+ *pgid = gid;
+ DBG_INFO("Forced group %s\n", groupname);
+ }
+
+ result = NT_STATUS_OK;
+ done:
+ TALLOC_FREE(frame);
+ return result;
+}
+
+/****************************************************************************
+ Create an auth_session_info structure for a connection_struct
+****************************************************************************/
+
+static NTSTATUS create_connection_session_info(struct smbd_server_connection *sconn,
+ TALLOC_CTX *mem_ctx, int snum,
+ struct auth_session_info *session_info,
+ struct auth_session_info **presult)
+{
+ struct auth_session_info *result;
+
+ if (lp_guest_only(snum)) {
+ return make_session_info_guest(mem_ctx, presult);
+ }
+
+ /*
+ * This is the normal security != share case where we have a
+ * valid vuid from the session setup. */
+
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ if (!lp_guest_ok(snum)) {
+ DBG_WARNING("guest user (from session setup) "
+ "not permitted to access this share "
+ "(%s)\n", lp_const_servicename(snum));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ if (!user_ok_token(session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token, snum)) {
+ DBG_WARNING("user '%s' (from session setup) not "
+ "permitted to access this share "
+ "(%s)\n",
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ result = copy_session_info(mem_ctx, session_info);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *presult = result;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Set relevant user and group settings corresponding to force user/group
+ configuration for the given snum.
+****************************************************************************/
+
+NTSTATUS set_conn_force_user_group(connection_struct *conn, int snum)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+
+ if (*lp_force_user(talloc_tos(), lp_sub, snum)) {
+
+ /*
+ * Replace conn->session_info with a completely faked up one
+ * from the username we are forced into :-)
+ */
+
+ char *fuser;
+ char *sanitized_username;
+ struct auth_session_info *forced_serverinfo;
+ bool guest;
+
+ fuser = talloc_string_sub(conn, lp_force_user(talloc_tos(), lp_sub, snum), "%S",
+ lp_const_servicename(snum));
+ if (fuser == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ guest = security_session_user_level(conn->session_info, NULL) < SECURITY_USER;
+
+ status = make_session_info_from_username(
+ conn, fuser,
+ guest,
+ &forced_serverinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* We don't want to replace the original sanitized_username
+ as it is the original user given in the connect attempt.
+ This is used in '%U' substitutions. */
+ sanitized_username = discard_const_p(char,
+ forced_serverinfo->unix_info->sanitized_username);
+ TALLOC_FREE(sanitized_username);
+ forced_serverinfo->unix_info->sanitized_username =
+ talloc_move(forced_serverinfo->unix_info,
+ &conn->session_info->unix_info->sanitized_username);
+
+ TALLOC_FREE(conn->session_info);
+ conn->session_info = forced_serverinfo;
+
+ conn->force_user = true;
+ DBG_INFO("Forced user %s\n", fuser);
+ }
+
+ /*
+ * If force group is true, then override
+ * any groupid stored for the connecting user.
+ */
+
+ if (*lp_force_group(talloc_tos(), lp_sub, snum)) {
+
+ status = find_forced_group(
+ conn->force_user, snum, conn->session_info->unix_info->unix_name,
+ &conn->session_info->security_token->sids[1],
+ &conn->session_info->unix_token->gid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * We need to cache this gid, to use within
+ * change_to_user() separately from the conn->session_info
+ * struct. We only use conn->session_info directly if
+ * "force_user" was set.
+ */
+ conn->force_group_gid = conn->session_info->unix_token->gid;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS notify_init_sconn(struct smbd_server_connection *sconn)
+{
+ NTSTATUS status;
+
+ if (sconn->notify_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ sconn->notify_ctx = notify_init(sconn, sconn->msg_ctx,
+ sconn, notify_callback);
+ if (sconn->notify_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_NOTIFY_CANCEL_DELETED,
+ smbd_notify_cancel_deleted);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_register failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(sconn->notify_ctx);
+ return status;
+ }
+
+ status = messaging_register(sconn->msg_ctx, sconn,
+ MSG_SMB_NOTIFY_STARTED,
+ smbd_notifyd_restarted);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_register failed: %s\n",
+ nt_errstr(status));
+ messaging_deregister(sconn->msg_ctx,
+ MSG_SMB_NOTIFY_CANCEL_DELETED, sconn);
+ TALLOC_FREE(sconn->notify_ctx);
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Make a connection, given the snum to connect to, and the vuser of the
+ connecting user if appropriate.
+****************************************************************************/
+
+NTSTATUS make_connection_snum(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ int snum,
+ struct smbXsrv_session *session,
+ const char *pdev)
+{
+ struct smbd_server_connection *sconn = xconn->client->sconn;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smb_filename *smb_fname_cpath = NULL;
+ fstring dev;
+ int ret;
+ bool on_err_call_dis_hook = false;
+ uid_t effuid;
+ gid_t effgid;
+ NTSTATUS status;
+ bool ok;
+
+ fstrcpy(dev, pdev);
+
+ status = share_sanity_checks(sconn->local_address,
+ sconn->remote_address,
+ sconn->remote_hostname,
+ snum,
+ dev);
+ if (NT_STATUS_IS_ERR(status)) {
+ goto err_root_exit;
+ }
+
+ conn->params->service = snum;
+
+ status = create_connection_session_info(sconn,
+ conn, snum, session->global->auth_session_info,
+ &conn->session_info);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("create_connection_session_info failed: %s\n",
+ nt_errstr(status));
+ goto err_root_exit;
+ }
+
+ if (lp_guest_only(snum)) {
+ conn->force_user = true;
+ }
+
+ conn->num_files_open = 0;
+ conn->lastused = conn->lastused_count = time(NULL);
+ conn->printer = (strncmp(dev,"LPT",3) == 0);
+ conn->ipc = ( (strncmp(dev,"IPC",3) == 0) ||
+ ( lp_enable_asu_support() && strequal(dev,"ADMIN$")) );
+
+ /* Case options for the share. */
+ conn_setup_case_options(conn);
+
+ conn->encrypt_level = lp_server_smb_encrypt(snum);
+ if (conn->encrypt_level > SMB_ENCRYPTION_OFF) {
+ if (lp_server_smb_encrypt(-1) == SMB_ENCRYPTION_OFF) {
+ if (conn->encrypt_level == SMB_ENCRYPTION_REQUIRED) {
+ DBG_ERR("Service [%s] requires encryption, but "
+ "it is disabled globally!\n",
+ lp_const_servicename(snum));
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_root_exit;
+ }
+ conn->encrypt_level = SMB_ENCRYPTION_OFF;
+ }
+ }
+
+ conn->veto_list = NULL;
+ conn->hide_list = NULL;
+ conn->veto_oplock_list = NULL;
+ conn->aio_write_behind_list = NULL;
+
+ conn->read_only = lp_read_only(SNUM(conn));
+
+ status = set_conn_force_user_group(conn, snum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_root_exit;
+ }
+
+ conn->vuid = session->global->session_wire_id;
+
+ {
+ char *s = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_path(talloc_tos(), lp_sub, snum));
+ if (!s) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_root_exit;
+ }
+
+ if (!set_conn_connectpath(conn,s)) {
+ TALLOC_FREE(s);
+ status = NT_STATUS_NO_MEMORY;
+ goto err_root_exit;
+ }
+ DBG_NOTICE("Connect path is '%s' for service [%s]\n", s,
+ lp_const_servicename(snum));
+ TALLOC_FREE(s);
+ }
+
+ /*
+ * Set up the share security descriptor.
+ * NOTE - we use the *INCOMING USER* session_info
+ * here, as does (indirectly) change_to_user(),
+ * which can be called on any incoming packet.
+ * This way we set up the share access based
+ * on the authenticated user, not the forced
+ * user. See bug:
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=9878
+ */
+
+ status = check_user_share_access(conn,
+ session->global->auth_session_info,
+ &conn->share_access,
+ &conn->read_only);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_root_exit;
+ }
+
+ /* Initialise VFS function pointers */
+
+ if (!smbd_vfs_init(conn)) {
+ DBG_ERR("vfs_init failed for service %s\n",
+ lp_const_servicename(snum));
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto err_root_exit;
+ }
+
+/* ROOT Activities: */
+ /* explicitly check widelinks here so that we can correctly warn
+ * in the logs. */
+ widelinks_warning(snum);
+
+ /* Invoke VFS make connection hook - this must be the first
+ filesystem operation that we do. */
+
+ if (SMB_VFS_CONNECT(conn, lp_const_servicename(snum),
+ conn->session_info->unix_info->unix_name) < 0) {
+ DBG_WARNING("SMB_VFS_CONNECT for service '%s' at '%s' failed: %s\n",
+ lp_const_servicename(snum), conn->connectpath,
+ strerror(errno));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto err_root_exit;
+ }
+
+ /* Any error exit after here needs to call the disconnect hook. */
+ on_err_call_dis_hook = true;
+
+ if ((!conn->printer) && (!conn->ipc) &&
+ lp_change_notify()) {
+
+ status = notify_init_sconn(sconn);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err_root_exit;
+ }
+ }
+
+ if (lp_kernel_oplocks(snum)) {
+ init_kernel_oplocks(conn->sconn);
+ }
+
+ /*
+ * Fix compatibility issue pointed out by Volker.
+ * We pass the conn->connectpath to the preexec
+ * scripts as a parameter, so attempt to canonicalize
+ * it here before calling the preexec scripts.
+ * We ignore errors here, as it is possible that
+ * the conn->connectpath doesn't exist yet and
+ * the preexec scripts will create them.
+ */
+
+ (void)canonicalize_connect_path(conn);
+
+ /* Preexecs are done here as they might make the dir we are to ChDir
+ * to below */
+ /* execute any "root preexec = " line */
+ if (*lp_root_preexec(talloc_tos(), lp_sub, snum)) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_root_preexec(talloc_tos(), lp_sub, snum));
+ DBG_INFO("cmd=%s\n",cmd);
+ ret = smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ if (ret != 0 && lp_root_preexec_close(snum)) {
+ DBG_WARNING("root preexec gave %d - failing "
+ "connection\n", ret);
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_root_exit;
+ }
+ }
+
+/* USER Activities: */
+ if (!change_to_user_and_service(conn, conn->vuid)) {
+ /* No point continuing if they fail the basic checks */
+ DBG_ERR("Can't become connected user!\n");
+ status = NT_STATUS_LOGON_FAILURE;
+ goto err_root_exit;
+ }
+
+ effuid = geteuid();
+ effgid = getegid();
+
+ /* Remember that a different vuid can connect later without these
+ * checks... */
+
+ /* Preexecs are done here as they might make the dir we are to ChDir
+ * to below */
+
+ /* execute any "preexec = " line */
+ if (*lp_preexec(talloc_tos(), lp_sub, snum)) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_preexec(talloc_tos(), lp_sub, snum));
+ ret = smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ if (ret != 0 && lp_preexec_close(snum)) {
+ DBG_WARNING("preexec gave %d - failing connection\n",
+ ret);
+ status = NT_STATUS_ACCESS_DENIED;
+ goto err_root_exit;
+ }
+ }
+
+#ifdef WITH_FAKE_KASERVER
+ if (lp_afs_share(snum)) {
+ afs_login(conn);
+ }
+#endif
+
+ /*
+ * we've finished with the user stuff - go back to root
+ * so the SMB_VFS_STAT call will only fail on path errors,
+ * not permission problems.
+ */
+ change_to_root_user();
+/* ROOT Activities: */
+
+ /*
+ * Canonicalise the connect
+ * path here to ensure we don't have any symlinks in the
+ * connectpath. We will be checking all paths on this connection are
+ * below this directory. We must do this after the VFS init as we
+ * depend on the realpath() pointer in the vfs table. JRA.
+ */
+ ok = canonicalize_connect_path(conn);
+ if (!ok) {
+ DBG_ERR("canonicalize_connect_path failed "
+ "for service %s, path %s\n",
+ lp_const_servicename(snum),
+ conn->connectpath);
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto err_root_exit;
+ }
+
+ /* Add veto/hide lists */
+ if (!IS_IPC(conn) && !IS_PRINT(conn)) {
+ set_namearray( &conn->veto_list,
+ lp_veto_files(talloc_tos(), lp_sub, snum));
+ set_namearray( &conn->hide_list,
+ lp_hide_files(talloc_tos(), lp_sub, snum));
+ set_namearray( &conn->veto_oplock_list,
+ lp_veto_oplock_files(talloc_tos(), lp_sub, snum));
+ set_namearray( &conn->aio_write_behind_list,
+ lp_aio_write_behind(talloc_tos(), lp_sub, snum));
+ }
+ smb_fname_cpath = synthetic_smb_fname(talloc_tos(),
+ conn->connectpath,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname_cpath == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err_root_exit;
+ }
+
+ /* win2000 does not check the permissions on the directory
+ during the tree connect, instead relying on permission
+ check during individual operations. To match this behaviour
+ I have disabled this chdir check (tridge) */
+ /* the alternative is just to check the directory exists */
+
+ if ((ret = SMB_VFS_STAT(conn, smb_fname_cpath)) != 0 ||
+ !S_ISDIR(smb_fname_cpath->st.st_ex_mode)) {
+ if (ret == 0 && !S_ISDIR(smb_fname_cpath->st.st_ex_mode)) {
+ DBG_ERR("'%s' is not a directory, when connecting to "
+ "[%s]\n", conn->connectpath,
+ lp_const_servicename(snum));
+ } else {
+ DBG_ERR("'%s' does not exist or permission denied "
+ "when connecting to [%s] Error was %s\n",
+ conn->connectpath,
+ lp_const_servicename(snum),
+ strerror(errno));
+ }
+ status = NT_STATUS_BAD_NETWORK_NAME;
+ goto err_root_exit;
+ }
+ conn->base_share_dev = smb_fname_cpath->st.st_ex_dev;
+
+ /* Figure out the characteristics of the underlying filesystem. This
+ * assumes that all the filesystem mounted within a share path have
+ * the same characteristics, which is likely but not guaranteed.
+ */
+
+ if(!IS_IPC(conn) ){
+ conn->fs_capabilities = SMB_VFS_FS_CAPABILITIES(conn, &conn->ts_res);
+ }
+ /*
+ * Print out the 'connected as' stuff here as we need
+ * to know the effective uid and gid we will be using
+ * (at least initially).
+ */
+
+ if( DEBUGLVL( IS_IPC(conn) ? DBGLVL_INFO : DBGLVL_NOTICE ) ) {
+ bool signing_active;
+
+ dbgtext( "%s (%s) ", get_remote_machine_name(),
+ tsocket_address_string(conn->sconn->remote_address,
+ talloc_tos()) );
+#if defined(WITH_SMB1SERVER)
+ if (sconn->using_smb2) {
+#endif
+ signing_active = smb2_signing_key_valid(
+ session->global->encryption_key);
+#if defined(WITH_SMB1SERVER)
+ } else {
+ signing_active = smb1_srv_is_signing_active(xconn);
+ }
+#endif
+ dbgtext( "%s", signing_active ? "signed " : "");
+ dbgtext( "connect to service %s ",
+ lp_const_servicename(snum) );
+ dbgtext( "initially as user %s ",
+ conn->session_info->unix_info->unix_name );
+ dbgtext( "(uid=%d, gid=%d) ", (int)effuid, (int)effgid );
+ dbgtext( "(pid %d)\n", (int)getpid() );
+ }
+
+ conn->tcon_done = true;
+ return NT_STATUS_OK;
+
+ err_root_exit:
+
+ TALLOC_FREE(smb_fname_cpath);
+ /* We must exit this function as root. */
+ if (geteuid() != 0) {
+ change_to_root_user();
+ }
+ if (on_err_call_dis_hook) {
+ /* Call VFS disconnect hook */
+ SMB_VFS_DISCONNECT(conn);
+ }
+ return status;
+}
+
+/****************************************************************************
+ Make a connection to a service from SMB2. External SMB2 interface.
+ We must set cnum before claiming connection.
+****************************************************************************/
+
+connection_struct *make_connection_smb2(struct smbd_smb2_request *req,
+ struct smbXsrv_tcon *tcon,
+ int snum,
+ const char *pdev,
+ NTSTATUS *pstatus)
+{
+ struct smbd_server_connection *sconn = req->sconn;
+ connection_struct *conn = conn_new(sconn);
+ if (!conn) {
+ DBG_ERR("make_connection_smb2: Couldn't find free connection.\n");
+ *pstatus = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return NULL;
+ }
+
+ conn->cnum = tcon->global->tcon_wire_id;
+ conn->tcon = tcon;
+
+ *pstatus = make_connection_snum(req->xconn,
+ conn,
+ snum,
+ req->session,
+ pdev);
+ if (!NT_STATUS_IS_OK(*pstatus)) {
+ conn_free(conn);
+ return NULL;
+ }
+ return conn;
+}
+
+/****************************************************************************
+ Close a cnum.
+****************************************************************************/
+
+void close_cnum(connection_struct *conn,
+ uint64_t vuid,
+ enum file_close_type close_type)
+{
+ char rootpath[2] = { '/', '\0'};
+ struct smb_filename root_fname = { .base_name = rootpath };
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ file_close_conn(conn, close_type);
+
+ change_to_root_user();
+
+ DEBUG(IS_IPC(conn)?DBGLVL_INFO:DBGLVL_NOTICE, ("%s (%s) closed connection to service %s\n",
+ get_remote_machine_name(),
+ tsocket_address_string(conn->sconn->remote_address,
+ talloc_tos()),
+ lp_const_servicename(SNUM(conn))));
+
+ /* make sure we leave the directory available for unmount */
+ vfs_ChDir(conn, &root_fname);
+
+ /* Call VFS disconnect hook */
+ SMB_VFS_DISCONNECT(conn);
+
+ /* execute any "postexec = " line */
+ if (*lp_postexec(talloc_tos(), lp_sub, SNUM(conn)) &&
+ change_to_user_and_service(conn, vuid)) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_postexec(talloc_tos(), lp_sub, SNUM(conn)));
+ smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ change_to_root_user();
+ }
+
+ change_to_root_user();
+ /* execute any "root postexec = " line */
+ if (*lp_root_postexec(talloc_tos(), lp_sub, SNUM(conn))) {
+ char *cmd = talloc_sub_full(talloc_tos(),
+ lp_const_servicename(SNUM(conn)),
+ conn->session_info->unix_info->unix_name,
+ conn->connectpath,
+ conn->session_info->unix_token->gid,
+ conn->session_info->unix_info->sanitized_username,
+ conn->session_info->info->domain_name,
+ lp_root_postexec(talloc_tos(), lp_sub, SNUM(conn)));
+ smbrun(cmd, NULL, NULL);
+ TALLOC_FREE(cmd);
+ }
+
+ conn_free(conn);
+}
diff --git a/source3/smbd/smb2_sesssetup.c b/source3/smbd/smb2_sesssetup.c
new file mode 100644
index 0000000..ac71e55
--- /dev/null
+++ b/source3/smbd/smb2_sesssetup.c
@@ -0,0 +1,1373 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../auth/gensec/gensec.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "source3/lib/substitute.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_session_setup_wrap_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint64_t in_session_id,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ uint64_t in_previous_session_id,
+ DATA_BLOB in_security_buffer);
+static NTSTATUS smbd_smb2_session_setup_wrap_recv(struct tevent_req *req,
+ uint16_t *out_session_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_security_buffer,
+ uint64_t *out_session_id);
+
+static void smbd_smb2_request_sesssetup_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_sesssetup(struct smbd_smb2_request *smb2req)
+{
+ const uint8_t *inhdr;
+ const uint8_t *inbody;
+ uint64_t in_session_id;
+ uint8_t in_flags;
+ uint8_t in_security_mode;
+ uint64_t in_previous_session_id;
+ uint16_t in_security_offset;
+ uint16_t in_security_length;
+ DATA_BLOB in_security_buffer;
+ NTSTATUS status;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(smb2req, 0x19);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(smb2req, status);
+ }
+ inhdr = SMBD_SMB2_IN_HDR_PTR(smb2req);
+ inbody = SMBD_SMB2_IN_BODY_PTR(smb2req);
+
+ in_session_id = BVAL(inhdr, SMB2_HDR_SESSION_ID);
+
+ in_flags = CVAL(inbody, 0x02);
+ in_security_mode = CVAL(inbody, 0x03);
+ /* Capabilities = IVAL(inbody, 0x04) */
+ /* Channel = IVAL(inbody, 0x08) */
+ in_security_offset = SVAL(inbody, 0x0C);
+ in_security_length = SVAL(inbody, 0x0E);
+ in_previous_session_id = BVAL(inbody, 0x10);
+
+ if (in_security_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(smb2req))) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_security_length > SMBD_SMB2_IN_DYN_LEN(smb2req)) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_security_buffer.data = SMBD_SMB2_IN_DYN_PTR(smb2req);
+ in_security_buffer.length = in_security_length;
+
+ subreq = smbd_smb2_session_setup_wrap_send(smb2req,
+ smb2req->sconn->ev_ctx,
+ smb2req,
+ in_session_id,
+ in_flags,
+ in_security_mode,
+ in_previous_session_id,
+ in_security_buffer);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_sesssetup_done, smb2req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, which
+ * matches a Windows Server and avoids problems with
+ * MacOS clients.
+ *
+ * Even after 90 seconds a Windows Server doesn't return
+ * STATUS_PENDING if using NTLMSSP against a non reachable
+ * trusted domain.
+ */
+ return smbd_smb2_request_pending_queue(smb2req, subreq, 0);
+}
+
+static void smbd_smb2_request_sesssetup_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ uint8_t *outhdr;
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint16_t out_session_flags = 0;
+ uint64_t out_session_id = 0;
+ uint16_t out_security_offset;
+ DATA_BLOB out_security_buffer = data_blob_null;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_session_setup_wrap_recv(subreq,
+ &out_session_flags,
+ smb2req,
+ &out_security_buffer,
+ &out_session_id);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ status = nt_status_squash(status);
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ out_security_offset = SMB2_HDR_BODY + 0x08;
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(smb2req);
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x08);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SBVAL(outhdr, SMB2_HDR_SESSION_ID, out_session_id);
+
+ SSVAL(outbody.data, 0x00, 0x08 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02,
+ out_session_flags); /* session flags */
+ SSVAL(outbody.data, 0x04,
+ out_security_offset); /* security buffer offset */
+ SSVAL(outbody.data, 0x06,
+ out_security_buffer.length); /* security buffer length */
+
+ outdyn = out_security_buffer;
+
+ error = smbd_smb2_request_done_ex(smb2req, status, outbody, &outdyn,
+ __location__);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static NTSTATUS smbd_smb2_auth_generic_return(struct smbXsrv_session *session,
+ struct smbXsrv_session_auth0 **_auth,
+ struct smbd_smb2_request *smb2req,
+ uint8_t in_security_mode,
+ struct auth_session_info *session_info,
+ uint16_t *out_session_flags,
+ uint64_t *out_session_id)
+{
+ NTSTATUS status;
+ bool guest = false;
+ struct smbXsrv_session *x = session;
+ struct smbXsrv_session_auth0 *auth = *_auth;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ size_t i;
+ struct smb2_signing_derivations derivations = {
+ .signing = NULL,
+ };
+ DATA_BLOB preauth_hash = data_blob_null;
+
+ *_auth = NULL;
+
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ struct smbXsrv_preauth *preauth;
+ gnutls_hash_hd_t hash_hnd;
+ int rc;
+
+ preauth = talloc_move(smb2req, &auth->preauth);
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ rc = gnutls_hash(hash_hnd,
+ preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ for (i = 1; i < smb2req->in.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ smb2req->in.vector[i].iov_base,
+ smb2req->in.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+ gnutls_hash_deinit(hash_hnd, preauth->sha512_value);
+
+ preauth_hash = data_blob_const(preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ }
+
+ smb2_signing_derivations_fill_const_stack(&derivations,
+ xconn->protocol,
+ preauth_hash);
+
+ if ((in_security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED) ||
+ (xconn->smb2.server.security_mode & SMB2_NEGOTIATE_SIGNING_REQUIRED))
+ {
+ x->global->signing_flags = SMBXSRV_SIGNING_REQUIRED;
+ }
+
+ if ((lp_server_smb_encrypt(-1) >= SMB_ENCRYPTION_DESIRED) &&
+ (xconn->smb2.client.capabilities & SMB2_CAP_ENCRYPTION)) {
+ x->global->encryption_flags = SMBXSRV_ENCRYPTION_DESIRED;
+ }
+
+ if (lp_server_smb_encrypt(-1) == SMB_ENCRYPTION_REQUIRED) {
+ x->global->encryption_flags = SMBXSRV_ENCRYPTION_REQUIRED |
+ SMBXSRV_ENCRYPTION_DESIRED;
+ }
+
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ if (security_session_user_level(session_info, NULL) == SECURITY_GUEST) {
+ *out_session_flags |= SMB2_SESSION_FLAG_IS_GUEST;
+ }
+ /* force no signing */
+ x->global->signing_flags &= ~SMBXSRV_SIGNING_REQUIRED;
+ /* we map anonymous to guest internally */
+ guest = true;
+ }
+
+ if (guest && (x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED)) {
+ DEBUG(1,("reject guest session as encryption is required\n"));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (xconn->smb2.server.cipher == 0) {
+ if (x->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED) {
+ DEBUG(1,("reject session with dialect[0x%04X] "
+ "as encryption is required\n",
+ xconn->smb2.server.dialect));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+ x->global->signing_algo = xconn->smb2.server.sign_algo;
+ x->global->encryption_cipher = xconn->smb2.server.cipher;
+ if (guest) {
+ x->global->encryption_cipher = SMB2_ENCRYPTION_NONE;
+ }
+
+ if (x->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED) {
+ *out_session_flags |= SMB2_SESSION_FLAG_ENCRYPT_DATA;
+ }
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ derivations.signing,
+ &x->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->signing_key_blob = x->global->signing_key->blob;
+
+ if (x->global->encryption_cipher != SMB2_ENCRYPTION_NONE) {
+ size_t nonce_size;
+
+ status = smb2_signing_key_cipher_create(x->global,
+ x->global->encryption_cipher,
+ &session_info->session_key,
+ derivations.cipher_s2c,
+ &x->global->encryption_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->encryption_key_blob = x->global->encryption_key->blob;
+
+ status = smb2_signing_key_cipher_create(x->global,
+ x->global->encryption_cipher,
+ &session_info->session_key,
+ derivations.cipher_c2s,
+ &x->global->decryption_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->decryption_key_blob = x->global->decryption_key->blob;
+
+ /*
+ * CCM and GCM algorithms must never have their
+ * nonce wrap, or the security of the whole
+ * communication and the keys is destroyed.
+ * We must drop the connection once we have
+ * transferred too much data.
+ *
+ * NOTE: We assume nonces greater than 8 bytes.
+ */
+ generate_nonce_buffer((uint8_t *)&x->nonce_high_random,
+ sizeof(x->nonce_high_random));
+ switch (xconn->smb2.server.cipher) {
+ case SMB2_ENCRYPTION_AES128_CCM:
+ nonce_size = SMB2_AES_128_CCM_NONCE_SIZE;
+ break;
+ case SMB2_ENCRYPTION_AES128_GCM:
+ nonce_size = gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_GCM);
+ break;
+ case SMB2_ENCRYPTION_AES256_CCM:
+ nonce_size = SMB2_AES_128_CCM_NONCE_SIZE;
+ break;
+ case SMB2_ENCRYPTION_AES256_GCM:
+ nonce_size = gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_256_GCM);
+ break;
+ default:
+ nonce_size = 0;
+ break;
+ }
+ x->nonce_high_max = SMB2_NONCE_HIGH_MAX(nonce_size);
+ x->nonce_high = 0;
+ x->nonce_low = 0;
+ }
+
+ status = smb2_signing_key_sign_create(x->global,
+ x->global->signing_algo,
+ &session_info->session_key,
+ derivations.application,
+ &x->global->application_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->application_key_blob = x->global->application_key->blob;
+
+ if (xconn->protocol >= PROTOCOL_SMB3_00 && lp_debug_encryption()) {
+ DEBUG(0, ("debug encryption: dumping generated session keys\n"));
+ DEBUGADD(0, ("Session Id "));
+ dump_data(0, (uint8_t*)&session->global->session_wire_id,
+ sizeof(session->global->session_wire_id));
+ DEBUGADD(0, ("Session Key "));
+ dump_data(0, session_info->session_key.data,
+ session_info->session_key.length);
+ DEBUGADD(0, ("Signing Algo: %u\n", x->global->signing_algo));
+ DEBUGADD(0, ("Signing Key "));
+ dump_data(0, x->global->signing_key_blob.data,
+ x->global->signing_key_blob.length);
+ DEBUGADD(0, ("App Key "));
+ dump_data(0, x->global->application_key_blob.data,
+ x->global->application_key_blob.length);
+
+ /* In server code, ServerIn is the decryption key */
+
+ DEBUGADD(0, ("Cipher Algo: %u\n", x->global->encryption_cipher));
+ DEBUGADD(0, ("ServerIn Key "));
+ dump_data(0, x->global->decryption_key_blob.data,
+ x->global->decryption_key_blob.length);
+ DEBUGADD(0, ("ServerOut Key "));
+ dump_data(0, x->global->encryption_key_blob.data,
+ x->global->encryption_key_blob.length);
+ }
+
+ status = smb2_signing_key_copy(x->global->channels,
+ x->global->signing_key,
+ &x->global->channels[0].signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ x->global->channels[0].signing_key_blob =
+ x->global->channels[0].signing_key->blob;
+ x->global->channels[0].signing_algo = x->global->signing_algo;
+ x->global->channels[0].encryption_cipher = x->global->encryption_cipher;
+
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ x->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+
+ smb2req->sconn->num_users++;
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ reload_services(smb2req->sconn, conn_snum_used, true);
+
+ session->status = NT_STATUS_OK;
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ for (i=0; i < session->global->num_channels; i++) {
+ struct smbXsrv_channel_global0 *_c =
+ &session->global->channels[i];
+
+ _c->auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ }
+ session->global->auth_time = timeval_to_nttime(&smb2req->request_time);
+ session->global->expiration_time = gensec_expire_time(auth->gensec);
+
+ if (!session_claim(session)) {
+ DEBUG(1, ("smb2: Failed to claim session "
+ "for vuid=%llu\n",
+ (unsigned long long)session->global->session_wire_id));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ TALLOC_FREE(auth);
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb2: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ /*
+ * we attach the session to the request
+ * so that the response can be signed
+ */
+ if (!guest) {
+ smb2req->do_signing = true;
+ }
+
+ global_client_caps |= (CAP_LEVEL_II_OPLOCKS|CAP_STATUS32);
+
+ *out_session_id = session->global->session_wire_id;
+ smb2req->last_session_id = session->global->session_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_reauth_generic_return(struct smbXsrv_session *session,
+ struct smbXsrv_session_auth0 **_auth,
+ struct smbd_smb2_request *smb2req,
+ struct auth_session_info *session_info,
+ uint16_t *out_session_flags,
+ uint64_t *out_session_id)
+{
+ NTSTATUS status;
+ struct smbXsrv_session *x = session;
+ struct smbXsrv_session_auth0 *auth = *_auth;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ size_t i;
+
+ *_auth = NULL;
+
+ data_blob_clear_free(&session_info->session_key);
+ session_info->session_key = data_blob_dup_talloc(session_info,
+ x->global->application_key_blob);
+ if (session_info->session_key.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_keep_secret(session_info->session_key.data);
+
+ session->homes_snum =
+ register_homes_share(session_info->unix_info->unix_name);
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ reload_services(smb2req->sconn, conn_snum_used, true);
+
+ if (security_session_user_level(session_info, NULL) >= SECURITY_USER) {
+ smb2req->do_signing = true;
+ }
+
+ session->status = NT_STATUS_OK;
+ TALLOC_FREE(session->global->auth_session_info);
+ session->global->auth_session_info = talloc_move(session->global,
+ &session_info);
+ session->global->auth_session_info_seqnum += 1;
+ for (i=0; i < session->global->num_channels; i++) {
+ struct smbXsrv_channel_global0 *_c =
+ &session->global->channels[i];
+
+ _c->auth_session_info_seqnum =
+ session->global->auth_session_info_seqnum;
+ }
+ session->global->auth_time = timeval_to_nttime(&smb2req->request_time);
+ session->global->expiration_time = gensec_expire_time(auth->gensec);
+
+ TALLOC_FREE(auth);
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb2: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ conn_clear_vuid_caches(xconn->client->sconn,
+ session->global->session_wire_id);
+
+ *out_session_id = session->global->session_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbd_smb2_bind_auth_return(struct smbXsrv_session *session,
+ struct smbXsrv_session_auth0 **_auth,
+ struct smbd_smb2_request *smb2req,
+ struct auth_session_info *session_info,
+ uint16_t *out_session_flags,
+ uint64_t *out_session_id)
+{
+ NTSTATUS status;
+ struct smbXsrv_session *x = session;
+ struct smbXsrv_session_auth0 *auth = *_auth;
+ struct smbXsrv_connection *xconn = smb2req->xconn;
+ struct smbXsrv_channel_global0 *c = NULL;
+ size_t i;
+ struct smb2_signing_derivations derivations = {
+ .signing = NULL,
+ };
+ DATA_BLOB preauth_hash = data_blob_null;
+ bool ok;
+
+ *_auth = NULL;
+
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ struct smbXsrv_preauth *preauth;
+ gnutls_hash_hd_t hash_hnd = NULL;
+ int rc;
+
+ preauth = talloc_move(smb2req, &auth->preauth);
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_SHA512);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+
+ rc = gnutls_hash(hash_hnd,
+ preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ for (i = 1; i < smb2req->in.vector_count; i++) {
+ rc = gnutls_hash(hash_hnd,
+ smb2req->in.vector[i].iov_base,
+ smb2req->in.vector[i].iov_len);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ }
+ gnutls_hash_deinit(hash_hnd, preauth->sha512_value);
+
+ preauth_hash = data_blob_const(preauth->sha512_value,
+ sizeof(preauth->sha512_value));
+ }
+
+ smb2_signing_derivations_fill_const_stack(&derivations,
+ xconn->protocol,
+ preauth_hash);
+
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ok = security_token_is_sid(session_info->security_token,
+ &x->global->auth_session_info->security_token->sids[0]);
+ if (!ok) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (session_info->session_key.length == 0) {
+ /* See [MS-SMB2] 3.3.5.2.4 for the return code. */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ c->signing_algo = xconn->smb2.server.sign_algo;
+ c->encryption_cipher = xconn->smb2.server.cipher;
+
+ status = smb2_signing_key_sign_create(x->global->channels,
+ c->signing_algo,
+ &session_info->session_key,
+ derivations.signing,
+ &c->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ c->signing_key_blob = c->signing_key->blob;
+
+ TALLOC_FREE(auth);
+ status = smbXsrv_session_update(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smb2: Failed to update session for vuid=%llu - %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status)));
+ return NT_STATUS_LOGON_FAILURE;
+ }
+
+ *out_session_id = session->global->session_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_session_setup_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ uint64_t in_session_id;
+ uint8_t in_flags;
+ uint8_t in_security_mode;
+ uint64_t in_previous_session_id;
+ DATA_BLOB in_security_buffer;
+ struct smbXsrv_session *session;
+ struct smbXsrv_session_auth0 *auth;
+ struct auth_session_info *session_info;
+ uint16_t out_session_flags;
+ DATA_BLOB out_security_buffer;
+ uint64_t out_session_id;
+};
+
+static void smbd_smb2_session_setup_gensec_done(struct tevent_req *subreq);
+static void smbd_smb2_session_setup_previous_done(struct tevent_req *subreq);
+static void smbd_smb2_session_setup_auth_return(struct tevent_req *req);
+
+static struct tevent_req *smbd_smb2_session_setup_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint64_t in_session_id,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ uint64_t in_previous_session_id,
+ DATA_BLOB in_security_buffer)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_session_setup_state *state;
+ NTSTATUS status;
+ NTTIME now = timeval_to_nttime(&smb2req->request_time);
+ struct tevent_req *subreq;
+ struct smbXsrv_channel_global0 *c = NULL;
+ enum security_user_level seclvl;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_session_setup_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smb2req = smb2req;
+ state->in_session_id = in_session_id;
+ state->in_flags = in_flags;
+ state->in_security_mode = in_security_mode;
+ state->in_previous_session_id = in_previous_session_id;
+ state->in_security_buffer = in_security_buffer;
+
+ if (in_flags & SMB2_SESSION_FLAG_BINDING) {
+ if (in_session_id == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->session == NULL) {
+ tevent_req_nterror(req, NT_STATUS_USER_SESSION_DELETED);
+ return tevent_req_post(req, ev);
+ }
+
+ if ((smb2req->session->global->signing_algo >= SMB2_SIGNING_AES128_GMAC) &&
+ (smb2req->xconn->smb2.server.sign_algo != smb2req->session->global->signing_algo))
+ {
+ tevent_req_nterror(req, NT_STATUS_REQUEST_OUT_OF_SEQUENCE);
+ return tevent_req_post(req, ev);
+ }
+ if ((smb2req->xconn->smb2.server.sign_algo >= SMB2_SIGNING_AES128_GMAC) &&
+ (smb2req->session->global->signing_algo != smb2req->xconn->smb2.server.sign_algo))
+ {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->xconn->protocol < PROTOCOL_SMB3_00) {
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!smb2req->xconn->client->server_multi_channel_enabled) {
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!smb2req->do_signing) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->session->global->connection_dialect
+ != smb2req->xconn->smb2.server.dialect)
+ {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (smb2req->session->global->encryption_cipher
+ != smb2req->xconn->smb2.server.cipher)
+ {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smb2req->session->status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
+ /*
+ * This comes from smb2srv_session_lookup_global().
+ * And it's a cross node/cross smbd session bind,
+ * which can't work in our architecture.
+ *
+ * Returning NT_STATUS_REQUEST_NOT_ACCEPTED is better
+ * than NT_STATUS_USER_SESSION_DELETED in order to
+ * avoid a completely new session.
+ */
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbXsrv_session_find_channel(smb2req->session,
+ smb2req->xconn,
+ &c);
+ if (NT_STATUS_IS_OK(status)) {
+ if (!smb2_signing_key_valid(c->signing_key)) {
+ goto auth;
+ }
+ tevent_req_nterror(req, NT_STATUS_REQUEST_NOT_ACCEPTED);
+ return tevent_req_post(req, ev);
+ }
+
+ seclvl = security_session_user_level(
+ smb2req->session->global->auth_session_info,
+ NULL);
+ if (seclvl < SECURITY_USER) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbXsrv_session_add_channel(smb2req->session,
+ smb2req->xconn,
+ now,
+ &c);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = smbXsrv_session_update(smb2req->session);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+auth:
+
+ if (state->in_session_id == 0) {
+ /* create a new session */
+ status = smbXsrv_session_create(state->smb2req->xconn,
+ now, &state->session);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ smb2req->session = state->session;
+ } else {
+ if (smb2req->session == NULL) {
+ tevent_req_nterror(req, NT_STATUS_USER_SESSION_DELETED);
+ return tevent_req_post(req, ev);
+ }
+
+ state->session = smb2req->session;
+ status = state->session->status;
+ if (NT_STATUS_EQUAL(status, NT_STATUS_BAD_LOGON_SESSION_STATE)) {
+ /*
+ * This comes from smb2srv_session_lookup_global().
+ */
+ tevent_req_nterror(req, NT_STATUS_USER_SESSION_DELETED);
+ return tevent_req_post(req, ev);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ status = smbXsrv_session_find_channel(smb2req->session,
+ smb2req->xconn, &c);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (!(in_flags & SMB2_SESSION_FLAG_BINDING)) {
+ state->session->status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ }
+
+ status = smbXsrv_session_find_auth(state->session, smb2req->xconn,
+ now, &state->auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = smbXsrv_session_create_auth(state->session,
+ smb2req->xconn, now,
+ in_flags, in_security_mode,
+ &state->auth);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (state->auth->gensec == NULL) {
+ status = auth_generic_prepare(state->auth,
+ state->smb2req->xconn->remote_address,
+ state->smb2req->xconn->local_address,
+ "SMB2",
+ &state->auth->gensec);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ gensec_want_feature(state->auth->gensec, GENSEC_FEATURE_SESSION_KEY);
+ gensec_want_feature(state->auth->gensec, GENSEC_FEATURE_UNIX_TOKEN);
+ gensec_want_feature(state->auth->gensec, GENSEC_FEATURE_SMB_TRANSPORT);
+
+ status = gensec_start_mech_by_oid(state->auth->gensec,
+ GENSEC_OID_SPNEGO);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ status = smbXsrv_session_update(state->session);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ become_root();
+ subreq = gensec_update_send(state, state->ev,
+ state->auth->gensec,
+ state->in_security_buffer);
+ unbecome_root();
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_session_setup_gensec_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_session_setup_gensec_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_session_setup_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_state);
+ NTSTATUS status;
+
+ become_root();
+ status = gensec_update_recv(subreq, state,
+ &state->out_security_buffer);
+ unbecome_root();
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ state->out_session_id = state->session->global->session_wire_id;
+ state->smb2req->preauth = state->auth->preauth;
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = gensec_session_info(state->auth->gensec,
+ state,
+ &state->session_info);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if ((state->in_previous_session_id != 0) &&
+ (state->session->global->session_wire_id !=
+ state->in_previous_session_id))
+ {
+ subreq = smb2srv_session_close_previous_send(state, state->ev,
+ state->smb2req->xconn,
+ state->session_info,
+ state->in_previous_session_id,
+ state->session->global->session_wire_id);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_session_setup_previous_done,
+ req);
+ return;
+ }
+
+ smbd_smb2_session_setup_auth_return(req);
+}
+
+static void smbd_smb2_session_setup_previous_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ NTSTATUS status;
+
+ status = smb2srv_session_close_previous_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ smbd_smb2_session_setup_auth_return(req);
+}
+
+static void smbd_smb2_session_setup_auth_return(struct tevent_req *req)
+{
+ struct smbd_smb2_session_setup_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_state);
+ NTSTATUS status;
+
+ if (state->in_flags & SMB2_SESSION_FLAG_BINDING) {
+ status = smbd_smb2_bind_auth_return(state->session,
+ &state->auth,
+ state->smb2req,
+ state->session_info,
+ &state->out_session_flags,
+ &state->out_session_id);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+ return;
+ }
+
+ if (state->session->global->auth_session_info != NULL) {
+ status = smbd_smb2_reauth_generic_return(state->session,
+ &state->auth,
+ state->smb2req,
+ state->session_info,
+ &state->out_session_flags,
+ &state->out_session_id);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+ return;
+ }
+
+ status = smbd_smb2_auth_generic_return(state->session,
+ &state->auth,
+ state->smb2req,
+ state->in_security_mode,
+ state->session_info,
+ &state->out_session_flags,
+ &state->out_session_id);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+static NTSTATUS smbd_smb2_session_setup_recv(struct tevent_req *req,
+ uint16_t *out_session_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_security_buffer,
+ uint64_t *out_session_id)
+{
+ struct smbd_smb2_session_setup_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_received(req);
+ return nt_status_squash(status);
+ }
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ *out_session_flags = state->out_session_flags;
+ *out_security_buffer = state->out_security_buffer;
+ *out_session_id = state->out_session_id;
+
+ talloc_steal(mem_ctx, out_security_buffer->data);
+ tevent_req_received(req);
+ return status;
+}
+
+struct smbd_smb2_session_setup_wrap_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ uint64_t in_session_id;
+ uint8_t in_flags;
+ uint8_t in_security_mode;
+ uint64_t in_previous_session_id;
+ DATA_BLOB in_security_buffer;
+ uint16_t out_session_flags;
+ DATA_BLOB out_security_buffer;
+ uint64_t out_session_id;
+ NTSTATUS error;
+};
+
+static void smbd_smb2_session_setup_wrap_setup_done(struct tevent_req *subreq);
+static void smbd_smb2_session_setup_wrap_shutdown_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_session_setup_wrap_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint64_t in_session_id,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ uint64_t in_previous_session_id,
+ DATA_BLOB in_security_buffer)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_session_setup_wrap_state *state;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_session_setup_wrap_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smb2req = smb2req;
+ state->in_session_id = in_session_id;
+ state->in_flags = in_flags;
+ state->in_security_mode = in_security_mode;
+ state->in_previous_session_id = in_previous_session_id;
+ state->in_security_buffer = in_security_buffer;
+
+ subreq = smbd_smb2_session_setup_send(state, state->ev,
+ state->smb2req,
+ state->in_session_id,
+ state->in_flags,
+ state->in_security_mode,
+ state->in_previous_session_id,
+ state->in_security_buffer);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_session_setup_wrap_setup_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_session_setup_wrap_setup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_session_setup_wrap_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_wrap_state);
+ NTSTATUS status;
+
+ status = smbd_smb2_session_setup_recv(subreq,
+ &state->out_session_flags,
+ state,
+ &state->out_security_buffer,
+ &state->out_session_id);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (state->smb2req->session == NULL) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->error = status;
+
+ if (state->in_flags & SMB2_SESSION_FLAG_BINDING) {
+ status = smbXsrv_session_remove_channel(state->smb2req->session,
+ state->smb2req->xconn);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_nterror(req, state->error);
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->error, NT_STATUS_USER_SESSION_DELETED)) {
+ tevent_req_nterror(req, state->error);
+ return;
+ }
+
+ subreq = smb2srv_session_shutdown_send(state, state->ev,
+ state->smb2req->session,
+ state->smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_session_setup_wrap_shutdown_done,
+ req);
+}
+
+static void smbd_smb2_session_setup_wrap_shutdown_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_session_setup_wrap_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_wrap_state);
+ NTSTATUS status;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * we may need to sign the response, so we need to keep
+ * the session until the response is sent to the wire.
+ */
+ talloc_steal(state->smb2req, state->smb2req->session);
+
+ tevent_req_nterror(req, state->error);
+}
+
+static NTSTATUS smbd_smb2_session_setup_wrap_recv(struct tevent_req *req,
+ uint16_t *out_session_flags,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *out_security_buffer,
+ uint64_t *out_session_id)
+{
+ struct smbd_smb2_session_setup_wrap_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_session_setup_wrap_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ tevent_req_received(req);
+ return nt_status_squash(status);
+ }
+ } else {
+ status = NT_STATUS_OK;
+ }
+
+ *out_session_flags = state->out_session_flags;
+ *out_security_buffer = state->out_security_buffer;
+ *out_session_id = state->out_session_id;
+
+ talloc_steal(mem_ctx, out_security_buffer->data);
+ tevent_req_received(req);
+ return status;
+}
+
+static struct tevent_req *smbd_smb2_logoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req);
+static NTSTATUS smbd_smb2_logoff_recv(struct tevent_req *req);
+static void smbd_smb2_request_logoff_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_logoff(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x04);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ subreq = smbd_smb2_logoff_send(req, req->sconn->ev_ctx, req);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_logoff_done, req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, it's very likely
+ * the client won't expect that.
+ */
+ return smbd_smb2_request_pending_queue(req, subreq, 0);
+}
+
+static void smbd_smb2_request_logoff_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_logoff_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(smb2req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_logoff_state {
+ struct smbd_smb2_request *smb2req;
+};
+
+static void smbd_smb2_logoff_shutdown_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_logoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_logoff_state *state;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_logoff_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+
+ subreq = smb2srv_session_shutdown_send(state, ev,
+ smb2req->session,
+ smb2req);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_logoff_shutdown_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_logoff_shutdown_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_logoff_state *state = tevent_req_data(
+ req, struct smbd_smb2_logoff_state);
+ NTSTATUS status;
+ bool ok;
+ const struct GUID *client_guid =
+ &state->smb2req->session->client->global->client_guid;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ TALLOC_FREE(subreq);
+
+ if (!GUID_all_zero(client_guid)) {
+ ok = remote_arch_cache_delete(client_guid);
+ if (!ok) {
+ /* Most likely not an error, but not in cache */
+ DBG_DEBUG("Deletion from remote arch cache failed\n");
+ }
+ }
+
+ /*
+ * As we've been awoken, we may have changed
+ * uid in the meantime. Ensure we're still
+ * root (SMB2_OP_LOGOFF has .as_root = true).
+ */
+ change_to_root_user();
+
+ status = smbXsrv_session_logoff(state->smb2req->session);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * we may need to sign the response, so we need to keep
+ * the session until the response is sent to the wire.
+ */
+ talloc_steal(state->smb2req, state->smb2req->session);
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_logoff_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/smbd/smb2_setinfo.c b/source3/smbd/smb2_setinfo.c
new file mode 100644
index 0000000..f26fce7
--- /dev/null
+++ b/source3/smbd/smb2_setinfo.c
@@ -0,0 +1,628 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+ Copyright (C) Jeremy Allison 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "trans2.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "../librpc/gen_ndr/open_files.h"
+#include "source3/lib/dbwrap/dbwrap_watch.h"
+#include "messages.h"
+#include "librpc/gen_ndr/ndr_quota.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information);
+static NTSTATUS smbd_smb2_setinfo_recv(struct tevent_req *req);
+
+static void smbd_smb2_request_setinfo_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_setinfo(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint8_t in_info_type;
+ uint8_t in_file_info_class;
+ uint16_t in_input_buffer_offset;
+ uint32_t in_input_buffer_length;
+ DATA_BLOB in_input_buffer;
+ uint32_t in_additional_information;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x21);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_info_type = CVAL(inbody, 0x02);
+ in_file_info_class = CVAL(inbody, 0x03);
+ in_input_buffer_length = IVAL(inbody, 0x04);
+ in_input_buffer_offset = SVAL(inbody, 0x08);
+ /* 0x0A 2 bytes reserved */
+ in_additional_information = IVAL(inbody, 0x0C);
+ in_file_id_persistent = BVAL(inbody, 0x10);
+ in_file_id_volatile = BVAL(inbody, 0x18);
+
+ if (in_input_buffer_offset == 0 && in_input_buffer_length == 0) {
+ /* This is ok */
+ } else if (in_input_buffer_offset !=
+ (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_input_buffer_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_input_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_input_buffer.length = in_input_buffer_length;
+
+ if (in_input_buffer.length > xconn->smb2.server.max_trans) {
+ DEBUG(2,("smbd_smb2_request_process_setinfo: "
+ "client ignored max trans: %s: 0x%08X: 0x%08X\n",
+ __location__, (unsigned)in_input_buffer.length,
+ (unsigned)xconn->smb2.server.max_trans));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ status = smbd_smb2_request_verify_creditcharge(req,
+ in_input_buffer.length);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_setinfo_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_info_type,
+ in_file_info_class,
+ in_input_buffer,
+ in_additional_information);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_setinfo_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_setinfo_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_setinfo_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x02);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x02); /* struct size */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct defer_rename_state {
+ struct tevent_req *req;
+ struct smbd_smb2_request *smb2req;
+ struct tevent_context *ev;
+ struct files_struct *fsp;
+ char *data;
+ int data_size;
+};
+
+static int defer_rename_state_destructor(struct defer_rename_state *rename_state)
+{
+ SAFE_FREE(rename_state->data);
+ return 0;
+}
+
+static void defer_rename_done(struct tevent_req *subreq);
+
+struct delay_rename_lease_break_state {
+ struct files_struct *fsp;
+ bool delay;
+};
+
+static bool delay_rename_lease_break_fn(
+ struct share_mode_entry *e,
+ void *private_data)
+{
+ struct delay_rename_lease_break_state *state = private_data;
+ struct files_struct *fsp = state->fsp;
+ uint32_t e_lease_type, break_to;
+ bool ours, stale;
+
+ ours = smb2_lease_equal(fsp_client_guid(fsp),
+ &fsp->lease->lease.lease_key,
+ &e->client_guid,
+ &e->lease_key);
+ if (ours) {
+ return false;
+ }
+
+ e_lease_type = get_lease_type(e, fsp->file_id);
+
+ if ((e_lease_type & SMB2_LEASE_HANDLE) == 0) {
+ return false;
+ }
+
+ stale = share_entry_stale_pid(e);
+ if (stale) {
+ return false;
+ }
+
+ state->delay = true;
+ break_to = (e_lease_type & ~SMB2_LEASE_HANDLE);
+
+ send_break_message(
+ fsp->conn->sconn->msg_ctx, &fsp->file_id, e, break_to);
+
+ return false;
+}
+
+static struct tevent_req *delay_rename_for_lease_break(struct tevent_req *req,
+ struct smbd_smb2_request *smb2req,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ struct share_mode_lock *lck,
+ char *data,
+ int data_size)
+
+{
+ struct tevent_req *subreq;
+ struct defer_rename_state *rename_state;
+ struct delay_rename_lease_break_state state = { .fsp = fsp };
+ struct timeval timeout;
+ bool ok;
+
+ if (fsp->oplock_type != LEASE_OPLOCK) {
+ return NULL;
+ }
+
+ ok = share_mode_forall_leases(
+ lck, delay_rename_lease_break_fn, &state);
+ if (!ok) {
+ return NULL;
+ }
+
+ if (!state.delay) {
+ return NULL;
+ }
+
+ /* Setup a watch on this record. */
+ rename_state = talloc_zero(req, struct defer_rename_state);
+ if (rename_state == NULL) {
+ return NULL;
+ }
+
+ rename_state->req = req;
+ rename_state->smb2req = smb2req;
+ rename_state->ev = ev;
+ rename_state->fsp = fsp;
+ rename_state->data = data;
+ rename_state->data_size = data_size;
+
+ talloc_set_destructor(rename_state, defer_rename_state_destructor);
+
+ subreq = share_mode_watch_send(
+ rename_state,
+ ev,
+ lck,
+ (struct server_id){0});
+
+ if (subreq == NULL) {
+ exit_server("Could not watch share mode record for rename\n");
+ }
+
+ tevent_req_set_callback(subreq, defer_rename_done, rename_state);
+
+ timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0);
+ if (!tevent_req_set_endtime(subreq,
+ ev,
+ timeval_sum(&smb2req->request_time, &timeout))) {
+ exit_server("Could not set rename timeout\n");
+ }
+
+ return subreq;
+}
+
+static void defer_rename_done(struct tevent_req *subreq)
+{
+ struct defer_rename_state *state = tevent_req_callback_data(
+ subreq, struct defer_rename_state);
+ NTSTATUS status;
+ struct share_mode_lock *lck;
+ int ret_size = 0;
+ bool ok;
+
+ status = share_mode_watch_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("dbwrap_record_watch_recv returned %s\n",
+ nt_errstr(status)));
+ tevent_req_nterror(state->req, status);
+ return;
+ }
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service(
+ state->smb2req->tcon->compat,
+ state->smb2req->session->global->session_wire_id);
+ if (!ok) {
+ tevent_req_nterror(state->req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ /* Do we still need to wait ? */
+ lck = get_existing_share_mode_lock(state->req, state->fsp->file_id);
+ if (lck == NULL) {
+ tevent_req_nterror(state->req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+ subreq = delay_rename_for_lease_break(state->req,
+ state->smb2req,
+ state->ev,
+ state->fsp,
+ lck,
+ state->data,
+ state->data_size);
+ if (subreq) {
+ /* Yep - keep waiting. */
+ state->data = NULL;
+ TALLOC_FREE(state);
+ TALLOC_FREE(lck);
+ return;
+ }
+
+ /* Do the rename under the lock. */
+ status = smbd_do_setfilepathinfo(state->fsp->conn,
+ state->smb2req->smb1req,
+ state,
+ SMB2_FILE_RENAME_INFORMATION_INTERNAL,
+ state->fsp,
+ state->fsp->fsp_name,
+ &state->data,
+ state->data_size,
+ &ret_size);
+
+ TALLOC_FREE(lck);
+ SAFE_FREE(state->data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(state->req, status);
+ return;
+ }
+
+ tevent_req_done(state->req);
+}
+
+struct smbd_smb2_setinfo_state {
+ struct smbd_smb2_request *smb2req;
+};
+
+static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ uint8_t in_info_type,
+ uint8_t in_file_info_class,
+ DATA_BLOB in_input_buffer,
+ uint32_t in_additional_information)
+{
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_setinfo_state *state = NULL;
+ struct smb_request *smbreq = NULL;
+ connection_struct *conn = smb2req->tcon->compat;
+ struct share_mode_lock *lck = NULL;
+ NTSTATUS status;
+ int ret;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_setinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+
+ DEBUG(10,("smbd_smb2_setinfo_send: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (IS_IPC(conn)) {
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (in_info_type) {
+ case SMB2_0_INFO_FILE:
+ {
+ uint16_t file_info_level;
+ char *data;
+ int data_size;
+ int ret_size = 0;
+
+
+ file_info_level = in_file_info_class + 1000;
+ if (file_info_level == SMB_FILE_RENAME_INFORMATION) {
+ /* SMB2_FILE_RENAME_INFORMATION_INTERNAL == 0xFF00 + in_file_info_class */
+ file_info_level = SMB2_FILE_RENAME_INFORMATION_INTERNAL;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * This is actually a SETFILEINFO on a directory
+ * handle (returned from an NT SMB). NT5.0 seems
+ * to do this call. JRA.
+ */
+ ret = vfs_stat(fsp->conn, fsp->fsp_name);
+ if (ret != 0) {
+ DBG_WARNING("vfs_stat() of %s failed (%s)\n",
+ fsp_str_dbg(fsp),
+ strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ } else if (fsp->print_file) {
+ /*
+ * Doing a DELETE_ON_CLOSE should cancel a print job.
+ */
+ if ((file_info_level == SMB_SET_FILE_DISPOSITION_INFO)
+ && in_input_buffer.length >= 1
+ && CVAL(in_input_buffer.data,0)) {
+ fsp->fsp_flags.delete_on_close = true;
+
+ DEBUG(3,("smbd_smb2_setinfo_send: "
+ "Cancelling print job (%s)\n",
+ fsp_str_dbg(fsp)));
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_nterror(req, NT_STATUS_OBJECT_PATH_INVALID);
+ return tevent_req_post(req, ev);
+ } else {
+ /*
+ * Original code - this is an open file.
+ */
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3,("smbd_smb2_setinfo_send: fstat "
+ "of %s failed (%s)\n",
+ fsp_fnum_dbg(fsp),
+ nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ data = NULL;
+ data_size = in_input_buffer.length;
+ if (data_size > 0) {
+ data = (char *)SMB_MALLOC_ARRAY(char, data_size);
+ if (tevent_req_nomem(data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ memcpy(data, in_input_buffer.data, data_size);
+ }
+
+ if (file_info_level == SMB2_FILE_RENAME_INFORMATION_INTERNAL) {
+ struct tevent_req *subreq;
+
+ lck = get_existing_share_mode_lock(mem_ctx,
+ fsp->file_id);
+ if (lck == NULL) {
+ SAFE_FREE(data);
+ tevent_req_nterror(req,
+ NT_STATUS_UNSUCCESSFUL);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = delay_rename_for_lease_break(req,
+ smb2req,
+ ev,
+ fsp,
+ lck,
+ data,
+ data_size);
+ if (subreq) {
+ /* Wait for lease break response. */
+
+ /* Ensure we can't be closed in flight. */
+ if (!aio_add_req_to_fsp(fsp, req)) {
+ TALLOC_FREE(lck);
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ TALLOC_FREE(lck);
+ return req;
+ }
+ }
+
+ status = smbd_do_setfilepathinfo(conn, smbreq, state,
+ file_info_level,
+ fsp,
+ fsp->fsp_name,
+ &data,
+ data_size,
+ &ret_size);
+ TALLOC_FREE(lck);
+ SAFE_FREE(data);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_FILESYSTEM:
+ {
+ uint16_t file_info_level = in_file_info_class + 1000;
+
+ status = smbd_do_setfsinfo(conn, smbreq, state,
+ file_info_level,
+ fsp,
+ &in_input_buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) {
+ status = NT_STATUS_INVALID_INFO_CLASS;
+ }
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_SECURITY:
+ {
+ if (!CAN_WRITE(conn)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return tevent_req_post(req, ev);
+ }
+
+ status = set_sd_blob(fsp,
+ in_input_buffer.data,
+ in_input_buffer.length,
+ in_additional_information &
+ SMB_SUPPORTED_SECINFO_FLAGS);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ break;
+ }
+
+ case SMB2_0_INFO_QUOTA:
+ {
+#ifdef HAVE_SYS_QUOTAS
+ struct file_quota_information info = {0};
+ SMB_NTQUOTA_STRUCT qt = {0};
+ enum ndr_err_code err;
+
+ if (!fsp->fake_file_handle) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ err = ndr_pull_struct_blob(
+ &in_input_buffer, state, &info,
+ (ndr_pull_flags_fn_t)ndr_pull_file_quota_information);
+ if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return tevent_req_post(req, ev);
+ }
+
+ qt.usedspace = info.quota_used;
+
+ qt.softlim = info.quota_threshold;
+
+ qt.hardlim = info.quota_limit;
+
+ qt.sid = info.sid;
+ ret = vfs_set_ntquota(fsp, SMB_USER_QUOTA_TYPE, &qt.sid, &qt);
+ if (ret !=0 ) {
+ status = map_nt_error_from_unix(errno);
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+ status = NT_STATUS_OK;
+ break;
+#else
+ tevent_req_nterror(req, NT_STATUS_NOT_SUPPORTED);
+ return tevent_req_post(req, ev);
+#endif
+ }
+ default:
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_setinfo_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smb2_signing.c b/source3/smbd/smb2_signing.c
new file mode 100644
index 0000000..73d0738
--- /dev/null
+++ b/source3/smbd/smb2_signing.c
@@ -0,0 +1,52 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB Signing Code
+ Copyright (C) Jeremy Allison 2003.
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_signing.h"
+#include "lib/param/param.h"
+#include "smb2_signing.h"
+
+bool srv_init_signing(struct smbXsrv_connection *conn)
+{
+ struct loadparm_context *lp_ctx = NULL;
+ bool ok = true;
+
+ lp_ctx = loadparm_init_s3(conn, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DBG_DEBUG("loadparm_init_s3 failed\n");
+ return false;
+ }
+
+ /*
+ * For SMB2 all we need to know is if signing is mandatory.
+ * It is always allowed and desired, whatever the smb.conf says.
+ */
+ (void)lpcfg_server_signing_allowed(lp_ctx, &conn->smb2.signing_mandatory);
+
+#if defined(WITH_SMB1SERVER)
+ ok = smb1_srv_init_signing(lp_ctx, conn);
+#endif
+
+ talloc_unlink(conn, lp_ctx);
+ return ok;
+}
diff --git a/source3/smbd/smb2_tcon.c b/source3/smbd/smb2_tcon.c
new file mode 100644
index 0000000..b228036
--- /dev/null
+++ b/source3/smbd/smb2_tcon.c
@@ -0,0 +1,765 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../libcli/security/security.h"
+#include "auth.h"
+#include "lib/param/loadparm.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_tree_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint16_t in_flags,
+ const char *in_path);
+static NTSTATUS smbd_smb2_tree_connect_recv(struct tevent_req *req,
+ uint8_t *out_share_type,
+ uint32_t *out_share_flags,
+ uint32_t *out_capabilities,
+ uint32_t *out_maximal_access,
+ uint32_t *out_tree_id,
+ bool *disconnect);
+
+static void smbd_smb2_request_tcon_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_tcon(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ const uint8_t *inbody;
+ uint16_t in_flags;
+ uint16_t in_path_offset;
+ uint16_t in_path_length;
+ DATA_BLOB in_path_buffer;
+ char *in_path_string;
+ size_t in_path_string_size;
+ NTSTATUS status;
+ bool ok;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x09);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ if (xconn->protocol >= PROTOCOL_SMB3_11) {
+ in_flags = SVAL(inbody, 0x02);
+ } else {
+ in_flags = 0;
+ }
+ in_path_offset = SVAL(inbody, 0x04);
+ in_path_length = SVAL(inbody, 0x06);
+
+ if (in_path_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (in_path_length > SMBD_SMB2_IN_DYN_LEN(req)) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ in_path_buffer.data = SMBD_SMB2_IN_DYN_PTR(req);
+ in_path_buffer.length = in_path_length;
+
+ ok = convert_string_talloc(req, CH_UTF16, CH_UNIX,
+ in_path_buffer.data,
+ in_path_buffer.length,
+ &in_path_string,
+ &in_path_string_size);
+ if (!ok) {
+ return smbd_smb2_request_error(req, NT_STATUS_ILLEGAL_CHARACTER);
+ }
+
+ if (in_path_buffer.length == 0) {
+ in_path_string_size = 0;
+ }
+
+ if (strlen(in_path_string) != in_path_string_size) {
+ return smbd_smb2_request_error(req, NT_STATUS_BAD_NETWORK_NAME);
+ }
+
+ subreq = smbd_smb2_tree_connect_send(req,
+ req->sconn->ev_ctx,
+ req,
+ in_flags,
+ in_path_string);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_tcon_done, req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, it's very likely
+ * the client won't expect that.
+ */
+ return smbd_smb2_request_pending_queue(req, subreq, 0);
+}
+
+static void smbd_smb2_request_tcon_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ uint8_t *outhdr;
+ DATA_BLOB outbody;
+ uint8_t out_share_type = 0;
+ uint32_t out_share_flags = 0;
+ uint32_t out_capabilities = 0;
+ uint32_t out_maximal_access = 0;
+ uint32_t out_tree_id = 0;
+ bool disconnect = false;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_tree_connect_recv(subreq,
+ &out_share_type,
+ &out_share_flags,
+ &out_capabilities,
+ &out_maximal_access,
+ &out_tree_id,
+ &disconnect);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (disconnect) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(status));
+ return;
+ }
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outhdr = SMBD_SMB2_OUT_HDR_PTR(req);
+
+ outbody = smbd_smb2_generate_outbody(req, 0x10);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SIVAL(outhdr, SMB2_HDR_TID, out_tree_id);
+
+ SSVAL(outbody.data, 0x00, 0x10); /* struct size */
+ SCVAL(outbody.data, 0x02,
+ out_share_type); /* share type */
+ SCVAL(outbody.data, 0x03, 0); /* reserved */
+ SIVAL(outbody.data, 0x04,
+ out_share_flags); /* share flags */
+ SIVAL(outbody.data, 0x08,
+ out_capabilities); /* capabilities */
+ SIVAL(outbody.data, 0x0C,
+ out_maximal_access); /* maximal access */
+
+ error = smbd_smb2_request_done(req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+static NTSTATUS smbd_smb2_tree_connect(struct smbd_smb2_request *req,
+ const char *in_path,
+ uint8_t *out_share_type,
+ uint32_t *out_share_flags,
+ uint32_t *out_capabilities,
+ uint32_t *out_maximal_access,
+ uint32_t *out_tree_id,
+ bool *disconnect)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ struct smbXsrv_connection *conn = req->xconn;
+ struct smbXsrv_session *session = req->session;
+ struct auth_session_info *session_info =
+ session->global->auth_session_info;
+ const char *share = in_path;
+ char *service = NULL;
+ int snum = -1;
+ struct smbXsrv_tcon *tcon;
+ NTTIME now = timeval_to_nttime(&req->request_time);
+ connection_struct *compat_conn = NULL;
+ NTSTATUS status;
+ bool encryption_desired = req->session->global->encryption_flags & SMBXSRV_ENCRYPTION_DESIRED;
+ bool encryption_required = req->session->global->encryption_flags & SMBXSRV_ENCRYPTION_REQUIRED;
+ bool guest_session = false;
+ bool require_signed_tcon = false;
+ uint32_t session_global_id;
+ char *share_name = NULL;
+ uint8_t encryption_flags = 0;
+
+ *disconnect = false;
+
+ if (strncmp(share, "\\\\", 2) == 0) {
+ const char *p = strchr(share+2, '\\');
+ if (p) {
+ share = p + 1;
+ }
+ }
+
+ DEBUG(10,("smbd_smb2_tree_connect: path[%s] share[%s]\n",
+ in_path, share));
+
+ if (security_session_user_level(session_info, NULL) < SECURITY_USER) {
+ guest_session = true;
+ }
+
+ if (conn->protocol >= PROTOCOL_SMB3_11 && !guest_session) {
+ require_signed_tcon = true;
+ }
+
+ if (require_signed_tcon && !req->do_encryption && !req->do_signing) {
+ DEBUG(1, ("smbd_smb2_tree_connect: reject request to share "
+ "[%s] as '%s\\%s' without encryption or signing. "
+ "Disconnecting.\n",
+ share,
+ req->session->global->auth_session_info->info->domain_name,
+ req->session->global->auth_session_info->info->account_name));
+ *disconnect = true;
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ service = talloc_strdup(talloc_tos(), share);
+ if(!service) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!strlower_m(service)) {
+ DEBUG(2, ("strlower_m %s failed\n", service));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* TODO: do more things... */
+ if (strequal(service,HOMES_NAME)) {
+ if (session->homes_snum == -1) {
+ DEBUG(2, ("[homes] share not available for "
+ "user %s because it was not found "
+ "or created at session setup "
+ "time\n",
+ session_info->unix_info->unix_name));
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+ snum = session->homes_snum;
+ } else if ((session->homes_snum != -1)
+ && strequal(service,
+ lp_servicename(talloc_tos(), lp_sub, session->homes_snum))) {
+ snum = session->homes_snum;
+ } else {
+ snum = find_service(talloc_tos(), service, &service);
+ if (!service) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (snum < 0) {
+ DEBUG(3,("smbd_smb2_tree_connect: couldn't find service %s\n",
+ service));
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+
+ /* Handle non-DFS clients attempting connections to msdfs proxy */
+ if (lp_host_msdfs()) {
+ char *proxy = lp_msdfs_proxy(talloc_tos(), lp_sub, snum);
+
+ if ((proxy != NULL) && (*proxy != '\0')) {
+ DBG_NOTICE("refusing connection to dfs proxy share "
+ "'%s' (pointing to %s)\n",
+ service,
+ proxy);
+ TALLOC_FREE(proxy);
+ return NT_STATUS_BAD_NETWORK_NAME;
+ }
+ TALLOC_FREE(proxy);
+ }
+
+ if ((lp_server_smb_encrypt(snum) >= SMB_ENCRYPTION_DESIRED) &&
+ (conn->smb2.server.cipher != 0))
+ {
+ encryption_desired = true;
+ }
+
+ if (lp_server_smb_encrypt(snum) == SMB_ENCRYPTION_REQUIRED) {
+ encryption_desired = true;
+ encryption_required = true;
+ }
+
+ if (guest_session && encryption_required) {
+ DEBUG(1,("reject guest as encryption is required for service %s\n",
+ service));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (conn->smb2.server.cipher == 0) {
+ if (encryption_required) {
+ DEBUG(1,("reject tcon with dialect[0x%04X] "
+ "as encryption is required for service %s\n",
+ conn->smb2.server.dialect, service));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ if (encryption_desired) {
+ encryption_flags |= SMBXSRV_ENCRYPTION_DESIRED;
+ }
+ if (encryption_required) {
+ encryption_flags |= SMBXSRV_ENCRYPTION_REQUIRED;
+ }
+
+ session_global_id = req->session->global->session_global_id;
+ share_name = lp_servicename(talloc_tos(), lp_sub, snum);
+ if (share_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ((lp_max_connections(snum) > 0)
+ && (count_current_connections(lp_const_servicename(snum), true) >=
+ lp_max_connections(snum))) {
+
+ DBG_WARNING("Max connections (%d) exceeded for [%s][%s]\n",
+ lp_max_connections(snum),
+ lp_const_servicename(snum), share_name);
+ TALLOC_FREE(share_name);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /* create a new tcon as child of the session */
+ status = smb2srv_tcon_create(req->session,
+ session_global_id,
+ encryption_flags,
+ share_name,
+ now, &tcon);
+ TALLOC_FREE(share_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ compat_conn = make_connection_smb2(req,
+ tcon, snum,
+ "???",
+ &status);
+ if (compat_conn == NULL) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+
+ tcon->compat = talloc_move(tcon, &compat_conn);
+
+ tcon->status = NT_STATUS_OK;
+
+ if (IS_PRINT(tcon->compat)) {
+ *out_share_type = SMB2_SHARE_TYPE_PRINT;
+ } else if (IS_IPC(tcon->compat)) {
+ *out_share_type = SMB2_SHARE_TYPE_PIPE;
+ } else {
+ *out_share_type = SMB2_SHARE_TYPE_DISK;
+ }
+
+ *out_share_flags = 0;
+
+ if (lp_msdfs_root(SNUM(tcon->compat)) && lp_host_msdfs()) {
+ *out_share_flags |= (SMB2_SHAREFLAG_DFS|SMB2_SHAREFLAG_DFS_ROOT);
+ *out_capabilities = SMB2_SHARE_CAP_DFS;
+ } else {
+ *out_capabilities = 0;
+ }
+
+ switch(lp_csc_policy(SNUM(tcon->compat))) {
+ case CSC_POLICY_MANUAL:
+ break;
+ case CSC_POLICY_DOCUMENTS:
+ *out_share_flags |= SMB2_SHAREFLAG_AUTO_CACHING;
+ break;
+ case CSC_POLICY_PROGRAMS:
+ *out_share_flags |= SMB2_SHAREFLAG_VDO_CACHING;
+ break;
+ case CSC_POLICY_DISABLE:
+ *out_share_flags |= SMB2_SHAREFLAG_NO_CACHING;
+ break;
+ default:
+ break;
+ }
+
+ if (lp_hide_unreadable(SNUM(tcon->compat)) ||
+ lp_hide_unwriteable_files(SNUM(tcon->compat))) {
+ *out_share_flags |= SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM;
+ }
+
+ if (encryption_desired) {
+ *out_share_flags |= SMB2_SHAREFLAG_ENCRYPT_DATA;
+ }
+
+ /*
+ * For disk shares we can change the client
+ * behavior on a cluster...
+ */
+ if (conn->protocol >= PROTOCOL_SMB3_00 &&
+ *out_share_type == SMB2_SHARE_TYPE_DISK)
+ {
+ bool persistent = false; /* persistent handles not implemented yet */
+ bool cluster = lp_clustering();
+ bool scaleout = cluster;
+ bool witness = cluster && !lp_rpc_start_on_demand_helpers();
+ bool asymmetric = false; /* shares are symmetric by default */
+ bool announce;
+
+ /*
+ * In a ctdb cluster shares are continuously available,
+ * but windows clients mix this with the global persistent
+ * handles support.
+ *
+ * Persistent handles are requested if
+ * SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY is present
+ * even without SMB2_CAP_PERSISTENT_HANDLES.
+ *
+ * And SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY is
+ * required for SMB2_SHARE_CAP_CLUSTER to have
+ * an effect.
+ *
+ * So we better don't announce this by default
+ * until we support persistent handles.
+ */
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "CONTINUOUS AVAILABILITY",
+ persistent);
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY;
+ }
+
+ /*
+ * ctdb clusters are always scale out...
+ */
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "SCALE OUT",
+ scaleout);
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_SCALEOUT;
+ }
+
+ /*
+ * We support the witness service when ctdb is active
+ */
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "CLUSTER",
+ witness);
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_CLUSTER;
+ }
+
+ /*
+ * Shares in a ctdb cluster are symmetric by design.
+ *
+ * But it might be useful to let the client use
+ * an isolated transport and witness registration for the
+ * specific share.
+ */
+ if (conn->protocol >= PROTOCOL_SMB3_02) {
+ announce = lp_parm_bool(SNUM(tcon->compat),
+ "smb3 share cap",
+ "ASYMMETRIC",
+ asymmetric);
+ }
+ if (announce) {
+ *out_capabilities |= SMB2_SHARE_CAP_ASYMMETRIC;
+ }
+ }
+
+ *out_maximal_access = tcon->compat->share_access;
+
+ *out_tree_id = tcon->global->tcon_wire_id;
+ req->last_tid = tcon->global->tcon_wire_id;
+
+ return NT_STATUS_OK;
+}
+
+struct smbd_smb2_tree_connect_state {
+ const char *in_path;
+ uint8_t out_share_type;
+ uint32_t out_share_flags;
+ uint32_t out_capabilities;
+ uint32_t out_maximal_access;
+ uint32_t out_tree_id;
+ bool disconnect;
+};
+
+static struct tevent_req *smbd_smb2_tree_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ uint16_t in_flags,
+ const char *in_path)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_tree_connect_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_tree_connect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->in_path = in_path;
+
+ status = smbd_smb2_tree_connect(smb2req,
+ state->in_path,
+ &state->out_share_type,
+ &state->out_share_flags,
+ &state->out_capabilities,
+ &state->out_maximal_access,
+ &state->out_tree_id,
+ &state->disconnect);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS smbd_smb2_tree_connect_recv(struct tevent_req *req,
+ uint8_t *out_share_type,
+ uint32_t *out_share_flags,
+ uint32_t *out_capabilities,
+ uint32_t *out_maximal_access,
+ uint32_t *out_tree_id,
+ bool *disconnect)
+{
+ struct smbd_smb2_tree_connect_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_tree_connect_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_share_type = state->out_share_type;
+ *out_share_flags = state->out_share_flags;
+ *out_capabilities = state->out_capabilities;
+ *out_maximal_access = state->out_maximal_access;
+ *out_tree_id = state->out_tree_id;
+ *disconnect = state->disconnect;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static struct tevent_req *smbd_smb2_tdis_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req);
+static NTSTATUS smbd_smb2_tdis_recv(struct tevent_req *req);
+static void smbd_smb2_request_tdis_done(struct tevent_req *subreq);
+
+NTSTATUS smbd_smb2_request_process_tdis(struct smbd_smb2_request *req)
+{
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x04);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ subreq = smbd_smb2_tdis_send(req, req->sconn->ev_ctx, req);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_tdis_done, req);
+
+ /*
+ * Avoid sending a STATUS_PENDING message, it's very likely
+ * the client won't expect that.
+ */
+ return smbd_smb2_request_pending_queue(req, subreq, 0);
+}
+
+static void smbd_smb2_request_tdis_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *smb2req =
+ tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ NTSTATUS status;
+ NTSTATUS error;
+
+ status = smbd_smb2_tdis_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(smb2req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(smb2req, 0x04);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(smb2req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x04); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+
+ error = smbd_smb2_request_done(smb2req, outbody, NULL);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(smb2req->xconn,
+ nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_tdis_state {
+ struct smbd_smb2_request *smb2req;
+ struct tevent_queue *wait_queue;
+};
+
+static void smbd_smb2_tdis_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *smbd_smb2_tdis_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req;
+ struct smbd_smb2_tdis_state *state;
+ struct tevent_req *subreq;
+ struct smbXsrv_connection *xconn = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_tdis_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+
+ state->wait_queue = tevent_queue_create(state, "tdis_wait_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Make sure that no new request will be able to use this tcon.
+ */
+ smb2req->tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ xconn = smb2req->xconn->client->connections;
+ for (; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *preq;
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ if (preq == smb2req) {
+ /* Can't cancel current request. */
+ continue;
+ }
+ if (preq->tcon != smb2req->tcon) {
+ /* Request on different tcon. */
+ continue;
+ }
+
+ if (preq->subreq != NULL) {
+ tevent_req_cancel(preq->subreq);
+ }
+
+ /*
+ * Now wait until the request is finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the request will
+ * remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(preq, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and send to the socket.
+ */
+ subreq = tevent_queue_wait_send(state, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_tdis_wait_done, req);
+
+ return req;
+}
+
+static void smbd_smb2_tdis_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smbd_smb2_tdis_state *state = tevent_req_data(
+ req, struct smbd_smb2_tdis_state);
+ NTSTATUS status;
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ /*
+ * As we've been awoken, we may have changed
+ * uid in the meantime. Ensure we're still
+ * root (SMB2_OP_TDIS has .as_root = true).
+ */
+ change_to_root_user();
+
+ status = smbXsrv_tcon_disconnect(state->smb2req->tcon,
+ state->smb2req->tcon->compat->vuid);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /* We did tear down the tcon. */
+ TALLOC_FREE(state->smb2req->tcon);
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_tdis_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/smbd/smb2_trans2.c b/source3/smbd/smb2_trans2.c
new file mode 100644
index 0000000..8997c40
--- /dev/null
+++ b/source3/smbd/smb2_trans2.c
@@ -0,0 +1,5243 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB transaction2 handling
+ Copyright (C) Jeremy Allison 1994-2007
+ Copyright (C) Stefan (metze) Metzmacher 2003
+ Copyright (C) Volker Lendecke 2005-2007
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006-2007
+
+ Extensively modified by Andrew Tridgell, 1995
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ntioctl.h"
+#include "system/filesys.h"
+#include "lib/util/time_basic.h"
+#include "version.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/xattr.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_smb3posix.h"
+#include "libcli/security/security.h"
+#include "trans2.h"
+#include "auth.h"
+#include "smbprofile.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "printing.h"
+#include "lib/util_ea.h"
+#include "lib/readdir_attr.h"
+#include "messages.h"
+#include "libcli/smb/smb2_posix.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+#include "source3/lib/adouble.h"
+#include "source3/smbd/dir.h"
+
+#define DIR_ENTRY_SAFETY_MARGIN 4096
+
+static uint32_t generate_volume_serial_number(
+ const struct loadparm_substitution *lp_sub,
+ int snum);
+
+/****************************************************************************
+ Check if an open file handle is a symlink.
+****************************************************************************/
+
+NTSTATUS refuse_symlink_fsp(const files_struct *fsp)
+{
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (S_ISLNK(fsp->fsp_name->st.st_ex_mode)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+/**
+ * Check that one or more of the rights in access mask are
+ * allowed. Iow, access_requested can contain more then one right and
+ * it is sufficient having only one of those granted to pass.
+ **/
+NTSTATUS check_any_access_fsp(struct files_struct *fsp,
+ uint32_t access_requested)
+{
+ const uint32_t ro_access = SEC_RIGHTS_FILE_READ | SEC_FILE_EXECUTE;
+ uint32_t ro_access_granted = 0;
+ uint32_t access_granted = 0;
+ NTSTATUS status;
+
+ if (fsp->fsp_flags.is_fsa) {
+ access_granted = fsp->access_mask;
+ } else {
+ uint32_t mask = 1;
+
+ while (mask != 0) {
+ if (!(mask & access_requested)) {
+ mask <<= 1;
+ continue;
+ }
+
+ status = smbd_check_access_rights_fsp(
+ fsp->conn->cwd_fsp,
+ fsp,
+ false,
+ mask);
+ if (NT_STATUS_IS_OK(status)) {
+ access_granted |= mask;
+ if (fsp->fsp_name->twrp == 0) {
+ /*
+ * We can only optimize
+ * the non-snapshot case
+ */
+ break;
+ }
+ }
+ mask <<= 1;
+ }
+ }
+ if ((access_granted & access_requested) == 0) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (fsp->fsp_name->twrp == 0) {
+ return NT_STATUS_OK;
+ }
+
+ ro_access_granted = access_granted & ro_access;
+ if ((ro_access_granted & access_requested) == 0) {
+ return NT_STATUS_MEDIA_WRITE_PROTECTED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Roundup a value to the nearest allocation roundup size boundary.
+ Only do this for Windows clients.
+********************************************************************/
+
+uint64_t smb_roundup(connection_struct *conn, uint64_t val)
+{
+ uint64_t rval = lp_allocation_roundup_size(SNUM(conn));
+
+ /* Only roundup for Windows clients. */
+ enum remote_arch_types ra_type = get_remote_arch();
+ if (rval && (ra_type != RA_SAMBA) && (ra_type != RA_CIFSFS)) {
+ val = SMB_ROUNDUP(val,rval);
+ }
+ return val;
+}
+
+/****************************************************************************
+ Utility functions for dealing with extended attributes.
+****************************************************************************/
+
+/****************************************************************************
+ Refuse to allow clients to overwrite our private xattrs.
+****************************************************************************/
+
+bool samba_private_attr_name(const char *unix_ea_name)
+{
+ bool prohibited = false;
+
+ prohibited |= strequal(unix_ea_name, SAMBA_POSIX_INHERITANCE_EA_NAME);
+ prohibited |= strequal(unix_ea_name, SAMBA_XATTR_DOS_ATTRIB);
+ prohibited |= strequal(unix_ea_name, SAMBA_XATTR_MARKER);
+ prohibited |= strequal(unix_ea_name, XATTR_NTACL_NAME);
+ prohibited |= strequal(unix_ea_name, AFPINFO_EA_NETATALK);
+
+ if (prohibited) {
+ return true;
+ }
+
+ if (strncasecmp_m(unix_ea_name, SAMBA_XATTR_DOSSTREAM_PREFIX,
+ strlen(SAMBA_XATTR_DOSSTREAM_PREFIX)) == 0) {
+ return true;
+ }
+ return false;
+}
+
+/****************************************************************************
+ Get one EA value. Fill in a struct ea_struct.
+****************************************************************************/
+
+NTSTATUS get_ea_value_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ const char *ea_name,
+ struct ea_struct *pea)
+{
+ /* Get the value of this xattr. Max size is 64k. */
+ size_t attr_size = 256;
+ char *val = NULL;
+ ssize_t sizeret;
+ size_t max_xattr_size = 0;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ max_xattr_size = lp_smbd_max_xattr_size(SNUM(fsp->conn));
+
+ again:
+
+ val = talloc_realloc(mem_ctx, val, char, attr_size);
+ if (!val) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sizeret = SMB_VFS_FGETXATTR(fsp, ea_name, val, attr_size);
+ if (sizeret == -1 && errno == ERANGE && attr_size < max_xattr_size) {
+ attr_size = max_xattr_size;
+ goto again;
+ }
+
+ if (sizeret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ DEBUG(10,("get_ea_value: EA %s is of length %u\n", ea_name, (unsigned int)sizeret));
+ dump_data(10, (uint8_t *)val, sizeret);
+
+ pea->flags = 0;
+ if (strnequal(ea_name, "user.", 5)) {
+ pea->name = talloc_strdup(mem_ctx, &ea_name[5]);
+ } else {
+ pea->name = talloc_strdup(mem_ctx, ea_name);
+ }
+ if (pea->name == NULL) {
+ TALLOC_FREE(val);
+ return NT_STATUS_NO_MEMORY;
+ }
+ pea->value.data = (unsigned char *)val;
+ pea->value.length = (size_t)sizeret;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS get_ea_names_from_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ char ***pnames,
+ size_t *pnum_names)
+{
+ char smallbuf[1024];
+ /* Get a list of all xattrs. Max namesize is 64k. */
+ size_t ea_namelist_size = 1024;
+ char *ea_namelist = smallbuf;
+ char *to_free = NULL;
+
+ char *p;
+ char **names;
+ size_t num_names;
+ ssize_t sizeret = -1;
+ NTSTATUS status;
+
+ if (pnames) {
+ *pnames = NULL;
+ }
+ *pnum_names = 0;
+
+ if ((fsp == NULL) || !NT_STATUS_IS_OK(refuse_symlink_fsp(fsp))) {
+ /*
+ * Callers may pass fsp == NULL when passing smb_fname->fsp of a
+ * symlink. This is ok, handle it here, by just return no EA's
+ * on a symlink.
+ */
+ return NT_STATUS_OK;
+ }
+
+ sizeret = SMB_VFS_FLISTXATTR(fsp, ea_namelist,
+ ea_namelist_size);
+
+ if ((sizeret == -1) && (errno == ERANGE)) {
+ ea_namelist_size = 65536;
+ ea_namelist = talloc_array(mem_ctx, char, ea_namelist_size);
+ if (ea_namelist == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ to_free = ea_namelist;
+
+ sizeret = SMB_VFS_FLISTXATTR(fsp, ea_namelist,
+ ea_namelist_size);
+ }
+
+ if (sizeret == -1) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(to_free);
+ return status;
+ }
+
+ DBG_DEBUG("ea_namelist size = %zd\n", sizeret);
+
+ if (sizeret == 0) {
+ TALLOC_FREE(to_free);
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Ensure the result is 0-terminated
+ */
+
+ if (ea_namelist[sizeret-1] != '\0') {
+ TALLOC_FREE(to_free);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * count the names
+ */
+ num_names = 0;
+
+ for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) {
+ num_names += 1;
+ }
+
+ *pnum_names = num_names;
+
+ if (pnames == NULL) {
+ TALLOC_FREE(to_free);
+ return NT_STATUS_OK;
+ }
+
+ names = talloc_array(mem_ctx, char *, num_names);
+ if (names == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ TALLOC_FREE(to_free);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ea_namelist == smallbuf) {
+ ea_namelist = talloc_memdup(names, smallbuf, sizeret);
+ if (ea_namelist == NULL) {
+ TALLOC_FREE(names);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ talloc_steal(names, ea_namelist);
+
+ ea_namelist = talloc_realloc(names, ea_namelist, char,
+ sizeret);
+ if (ea_namelist == NULL) {
+ TALLOC_FREE(names);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ num_names = 0;
+
+ for (p = ea_namelist; p - ea_namelist < sizeret; p += strlen(p)+1) {
+ names[num_names++] = p;
+ }
+
+ *pnames = names;
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Return a linked list of the total EA's. Plus the total size
+****************************************************************************/
+
+static NTSTATUS get_ea_list_from_fsp(TALLOC_CTX *mem_ctx,
+ files_struct *fsp,
+ size_t *pea_total_len,
+ struct ea_list **ea_list)
+{
+ /* Get a list of all xattrs. Max namesize is 64k. */
+ size_t i, num_names;
+ char **names;
+ struct ea_list *ea_list_head = NULL;
+ bool posix_pathnames = false;
+ NTSTATUS status;
+
+ *pea_total_len = 0;
+ *ea_list = NULL;
+
+ /* symlink */
+ if (fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (!lp_ea_support(SNUM(fsp->conn))) {
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_is_alternate_stream(fsp)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ posix_pathnames = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+
+ status = get_ea_names_from_fsp(talloc_tos(),
+ fsp,
+ &names,
+ &num_names);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (num_names == 0) {
+ return NT_STATUS_OK;
+ }
+
+ for (i=0; i<num_names; i++) {
+ struct ea_list *listp;
+ fstring dos_ea_name;
+
+ /*
+ * POSIX EA names are divided into several namespaces by
+ * means of string prefixes. Usually, the system controls
+ * semantics for each namespace, but the 'user' namespace is
+ * available for arbitrary use, which comes closest to
+ * Windows EA semantics. Hence, we map POSIX EAs from the
+ * 'user' namespace to Windows EAs, and just ignore all the
+ * other namespaces. Also, a few specific names in the 'user'
+ * namespace are used by Samba internally. Filter them out as
+ * well, and only present the EAs that are available for
+ * arbitrary use.
+ */
+ if (!strnequal(names[i], "user.", 5)
+ || samba_private_attr_name(names[i]))
+ continue;
+
+ /*
+ * Filter out any underlying POSIX EA names
+ * that a Windows client can't handle.
+ */
+ if (!posix_pathnames &&
+ is_invalid_windows_ea_name(names[i])) {
+ continue;
+ }
+
+ listp = talloc(mem_ctx, struct ea_list);
+ if (listp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = get_ea_value_fsp(listp,
+ fsp,
+ names[i],
+ &listp->ea);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(listp);
+ return status;
+ }
+
+ if (listp->ea.value.length == 0) {
+ /*
+ * We can never return a zero length EA.
+ * Windows reports the EA's as corrupted.
+ */
+ TALLOC_FREE(listp);
+ continue;
+ } else if (listp->ea.value.length > 65536) {
+ /*
+ * SMB clients may report error with file
+ * if large EA is presented to them.
+ */
+ DBG_ERR("EA [%s] on file [%s] exceeds "
+ "maximum permitted EA size of 64KiB: %zu\n.",
+ listp->ea.name, fsp_str_dbg(fsp),
+ listp->ea.value.length);
+ TALLOC_FREE(listp);
+ continue;
+ }
+
+ push_ascii_fstring(dos_ea_name, listp->ea.name);
+
+ *pea_total_len +=
+ 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length;
+
+ DEBUG(10,("get_ea_list_from_file: total_len = %u, %s, val len "
+ "= %u\n", (unsigned int)*pea_total_len, dos_ea_name,
+ (unsigned int)listp->ea.value.length));
+
+ DLIST_ADD_END(ea_list_head, listp);
+
+ }
+
+ /* Add on 4 for total length. */
+ if (*pea_total_len) {
+ *pea_total_len += 4;
+ }
+
+ DEBUG(10, ("get_ea_list_from_file: total_len = %u\n",
+ (unsigned int)*pea_total_len));
+
+ *ea_list = ea_list_head;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Fill a qfilepathinfo buffer with EA's. Returns the length of the buffer
+ that was filled.
+****************************************************************************/
+
+static unsigned int fill_ea_buffer(TALLOC_CTX *mem_ctx, char *pdata, unsigned int total_data_size,
+ connection_struct *conn, struct ea_list *ea_list)
+{
+ unsigned int ret_data_size = 4;
+ char *p = pdata;
+
+ SMB_ASSERT(total_data_size >= 4);
+
+ if (!lp_ea_support(SNUM(conn))) {
+ SIVAL(pdata,4,0);
+ return 4;
+ }
+
+ for (p = pdata + 4; ea_list; ea_list = ea_list->next) {
+ size_t dos_namelen;
+ fstring dos_ea_name;
+ push_ascii_fstring(dos_ea_name, ea_list->ea.name);
+ dos_namelen = strlen(dos_ea_name);
+ if (dos_namelen > 255 || dos_namelen == 0) {
+ break;
+ }
+ if (ea_list->ea.value.length > 65535) {
+ break;
+ }
+ if (4 + dos_namelen + 1 + ea_list->ea.value.length > total_data_size) {
+ break;
+ }
+
+ /* We know we have room. */
+ SCVAL(p,0,ea_list->ea.flags);
+ SCVAL(p,1,dos_namelen);
+ SSVAL(p,2,ea_list->ea.value.length);
+ strlcpy(p+4, dos_ea_name, dos_namelen+1);
+ if (ea_list->ea.value.length > 0) {
+ memcpy(p + 4 + dos_namelen + 1,
+ ea_list->ea.value.data,
+ ea_list->ea.value.length);
+ }
+
+ total_data_size -= 4 + dos_namelen + 1 + ea_list->ea.value.length;
+ p += 4 + dos_namelen + 1 + ea_list->ea.value.length;
+ }
+
+ ret_data_size = PTR_DIFF(p, pdata);
+ DEBUG(10,("fill_ea_buffer: data_size = %u\n", ret_data_size ));
+ SIVAL(pdata,0,ret_data_size);
+ return ret_data_size;
+}
+
+static NTSTATUS fill_ea_chained_buffer(TALLOC_CTX *mem_ctx,
+ char *pdata,
+ unsigned int total_data_size,
+ unsigned int *ret_data_size,
+ connection_struct *conn,
+ struct ea_list *ea_list)
+{
+ uint8_t *p = (uint8_t *)pdata;
+ uint8_t *last_start = NULL;
+ bool do_store_data = (pdata != NULL);
+
+ *ret_data_size = 0;
+
+ if (!lp_ea_support(SNUM(conn))) {
+ return NT_STATUS_NO_EAS_ON_FILE;
+ }
+
+ for (; ea_list; ea_list = ea_list->next) {
+ size_t dos_namelen;
+ fstring dos_ea_name;
+ size_t this_size;
+ size_t pad = 0;
+
+ if (last_start != NULL && do_store_data) {
+ SIVAL(last_start, 0, PTR_DIFF(p, last_start));
+ }
+ last_start = p;
+
+ push_ascii_fstring(dos_ea_name, ea_list->ea.name);
+ dos_namelen = strlen(dos_ea_name);
+ if (dos_namelen > 255 || dos_namelen == 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ if (ea_list->ea.value.length > 65535) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ this_size = 0x08 + dos_namelen + 1 + ea_list->ea.value.length;
+
+ if (ea_list->next) {
+ pad = (4 - (this_size % 4)) % 4;
+ this_size += pad;
+ }
+
+ if (do_store_data) {
+ if (this_size > total_data_size) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ /* We know we have room. */
+ SIVAL(p, 0x00, 0); /* next offset */
+ SCVAL(p, 0x04, ea_list->ea.flags);
+ SCVAL(p, 0x05, dos_namelen);
+ SSVAL(p, 0x06, ea_list->ea.value.length);
+ strlcpy((char *)(p+0x08), dos_ea_name, dos_namelen+1);
+ memcpy(p + 0x08 + dos_namelen + 1, ea_list->ea.value.data, ea_list->ea.value.length);
+ if (pad) {
+ memset(p + 0x08 + dos_namelen + 1 + ea_list->ea.value.length,
+ '\0',
+ pad);
+ }
+ total_data_size -= this_size;
+ }
+
+ p += this_size;
+ }
+
+ *ret_data_size = PTR_DIFF(p, pdata);
+ DEBUG(10,("fill_ea_chained_buffer: data_size = %u\n", *ret_data_size));
+ return NT_STATUS_OK;
+}
+
+unsigned int estimate_ea_size(files_struct *fsp)
+{
+ size_t total_ea_len = 0;
+ TALLOC_CTX *mem_ctx;
+ struct ea_list *ea_list = NULL;
+ NTSTATUS status;
+
+ /* symlink */
+ if (fsp == NULL) {
+ return 0;
+ }
+
+ if (!lp_ea_support(SNUM(fsp->conn))) {
+ return 0;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ /* If this is a stream fsp, then we need to instead find the
+ * estimated ea len from the main file, not the stream
+ * (streams cannot have EAs), but the estimate isn't just 0 in
+ * this case! */
+ fsp = metadata_fsp(fsp);
+ (void)get_ea_list_from_fsp(mem_ctx,
+ fsp,
+ &total_ea_len,
+ &ea_list);
+
+ if(fsp->conn->sconn->using_smb2) {
+ unsigned int ret_data_size;
+ /*
+ * We're going to be using fill_ea_chained_buffer() to
+ * marshall EA's - this size is significantly larger
+ * than the SMB1 buffer. Re-calculate the size without
+ * marshalling.
+ */
+ status = fill_ea_chained_buffer(mem_ctx,
+ NULL,
+ 0,
+ &ret_data_size,
+ fsp->conn,
+ ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret_data_size = 0;
+ }
+ total_ea_len = ret_data_size;
+ }
+ TALLOC_FREE(mem_ctx);
+ return total_ea_len;
+}
+
+/****************************************************************************
+ Ensure the EA name is case insensitive by matching any existing EA name.
+****************************************************************************/
+
+static void canonicalize_ea_name(files_struct *fsp,
+ fstring unix_ea_name)
+{
+ size_t total_ea_len;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct ea_list *ea_list;
+ NTSTATUS status = get_ea_list_from_fsp(mem_ctx,
+ fsp,
+ &total_ea_len,
+ &ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return;
+ }
+
+ for (; ea_list; ea_list = ea_list->next) {
+ if (strequal(&unix_ea_name[5], ea_list->ea.name)) {
+ DEBUG(10,("canonicalize_ea_name: %s -> %s\n",
+ &unix_ea_name[5], ea_list->ea.name));
+ strlcpy(&unix_ea_name[5], ea_list->ea.name, sizeof(fstring)-5);
+ break;
+ }
+ }
+}
+
+/****************************************************************************
+ Set or delete an extended attribute.
+****************************************************************************/
+
+NTSTATUS set_ea(connection_struct *conn, files_struct *fsp,
+ struct ea_list *ea_list)
+{
+ NTSTATUS status;
+ bool posix_pathnames = false;
+
+ if (!lp_ea_support(SNUM(conn))) {
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ posix_pathnames = (fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+
+ status = refuse_symlink_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_EA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Setting EAs on streams isn't supported. */
+ if (fsp_is_alternate_stream(fsp)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Filter out invalid Windows EA names - before
+ * we set *any* of them.
+ */
+
+ if (!posix_pathnames && ea_list_has_invalid_name(ea_list)) {
+ return STATUS_INVALID_EA_NAME;
+ }
+
+ for (;ea_list; ea_list = ea_list->next) {
+ int ret;
+ fstring unix_ea_name;
+
+ /*
+ * Complementing the forward mapping from POSIX EAs to
+ * Windows EAs in get_ea_list_from_fsp(), here we map in the
+ * opposite direction from Windows EAs to the 'user' namespace
+ * of POSIX EAs. Hence, all POSIX EA names the we set here must
+ * start with a 'user.' prefix.
+ */
+ fstrcpy(unix_ea_name, "user.");
+ fstrcat(unix_ea_name, ea_list->ea.name);
+
+ canonicalize_ea_name(fsp, unix_ea_name);
+
+ DEBUG(10,("set_ea: ea_name %s ealen = %u\n", unix_ea_name, (unsigned int)ea_list->ea.value.length));
+
+ if (samba_private_attr_name(unix_ea_name)) {
+ DEBUG(10,("set_ea: ea name %s is a private Samba name.\n", unix_ea_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (ea_list->ea.value.length == 0) {
+ /* Remove the attribute. */
+ DBG_DEBUG("deleting ea name %s on "
+ "file %s by file descriptor.\n",
+ unix_ea_name, fsp_str_dbg(fsp));
+ ret = SMB_VFS_FREMOVEXATTR(fsp, unix_ea_name);
+#ifdef ENOATTR
+ /* Removing a non existent attribute always succeeds. */
+ if (ret == -1 && errno == ENOATTR) {
+ DEBUG(10,("set_ea: deleting ea name %s didn't exist - succeeding by default.\n",
+ unix_ea_name));
+ ret = 0;
+ }
+#endif
+ } else {
+ DEBUG(10,("set_ea: setting ea name %s on file "
+ "%s by file descriptor.\n",
+ unix_ea_name, fsp_str_dbg(fsp)));
+ ret = SMB_VFS_FSETXATTR(fsp, unix_ea_name,
+ ea_list->ea.value.data, ea_list->ea.value.length, 0);
+ }
+
+ if (ret == -1) {
+#ifdef ENOTSUP
+ if (errno == ENOTSUP) {
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+#endif
+ return map_nt_error_from_unix(errno);
+ }
+
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Read a list of EA names and data from an incoming data buffer. Create an ea_list with them.
+****************************************************************************/
+
+struct ea_list *read_ea_list(TALLOC_CTX *ctx, const char *pdata, size_t data_size)
+{
+ struct ea_list *ea_list_head = NULL;
+ size_t offset = 0;
+ size_t bytes_used = 0;
+
+ while (offset < data_size) {
+ struct ea_list *eal = read_ea_list_entry(ctx, pdata + offset, data_size - offset, &bytes_used);
+
+ if (!eal) {
+ return NULL;
+ }
+
+ DLIST_ADD_END(ea_list_head, eal);
+ offset += bytes_used;
+ }
+
+ return ea_list_head;
+}
+
+/****************************************************************************
+ Count the total EA size needed.
+****************************************************************************/
+
+static size_t ea_list_size(struct ea_list *ealist)
+{
+ fstring dos_ea_name;
+ struct ea_list *listp;
+ size_t ret = 0;
+
+ for (listp = ealist; listp; listp = listp->next) {
+ push_ascii_fstring(dos_ea_name, listp->ea.name);
+ ret += 4 + strlen(dos_ea_name) + 1 + listp->ea.value.length;
+ }
+ /* Add on 4 for total length. */
+ if (ret) {
+ ret += 4;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ Return a union of EA's from a file list and a list of names.
+ The TALLOC context for the two lists *MUST* be identical as we steal
+ memory from one list to add to another. JRA.
+****************************************************************************/
+
+static struct ea_list *ea_list_union(struct ea_list *name_list, struct ea_list *file_list, size_t *total_ea_len)
+{
+ struct ea_list *nlistp, *flistp;
+
+ for (nlistp = name_list; nlistp; nlistp = nlistp->next) {
+ for (flistp = file_list; flistp; flistp = flistp->next) {
+ if (strequal(nlistp->ea.name, flistp->ea.name)) {
+ break;
+ }
+ }
+
+ if (flistp) {
+ /* Copy the data from this entry. */
+ nlistp->ea.flags = flistp->ea.flags;
+ nlistp->ea.value = flistp->ea.value;
+ } else {
+ /* Null entry. */
+ nlistp->ea.flags = 0;
+ ZERO_STRUCT(nlistp->ea.value);
+ }
+ }
+
+ *total_ea_len = ea_list_size(name_list);
+ return name_list;
+}
+
+/****************************************************************************
+ Return the filetype for UNIX extensions.
+****************************************************************************/
+
+static uint32_t unix_filetype(mode_t mode)
+{
+ if(S_ISREG(mode))
+ return UNIX_TYPE_FILE;
+ else if(S_ISDIR(mode))
+ return UNIX_TYPE_DIR;
+#ifdef S_ISLNK
+ else if(S_ISLNK(mode))
+ return UNIX_TYPE_SYMLINK;
+#endif
+#ifdef S_ISCHR
+ else if(S_ISCHR(mode))
+ return UNIX_TYPE_CHARDEV;
+#endif
+#ifdef S_ISBLK
+ else if(S_ISBLK(mode))
+ return UNIX_TYPE_BLKDEV;
+#endif
+#ifdef S_ISFIFO
+ else if(S_ISFIFO(mode))
+ return UNIX_TYPE_FIFO;
+#endif
+#ifdef S_ISSOCK
+ else if(S_ISSOCK(mode))
+ return UNIX_TYPE_SOCKET;
+#endif
+
+ DEBUG(0,("unix_filetype: unknown filetype %u\n", (unsigned)mode));
+ return UNIX_TYPE_UNKNOWN;
+}
+
+/****************************************************************************
+ Map wire perms onto standard UNIX permissions. Obey share restrictions.
+****************************************************************************/
+
+NTSTATUS unix_perms_from_wire(connection_struct *conn,
+ const SMB_STRUCT_STAT *psbuf,
+ uint32_t perms,
+ enum perm_type ptype,
+ mode_t *ret_perms)
+{
+ mode_t ret = 0;
+
+ if (perms == SMB_MODE_NO_CHANGE) {
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ } else {
+ *ret_perms = psbuf->st_ex_mode;
+ return NT_STATUS_OK;
+ }
+ }
+
+ ret = wire_perms_to_unix(perms);
+
+ if (ptype == PERM_NEW_FILE) {
+ /*
+ * "create mask"/"force create mode" are
+ * only applied to new files, not existing ones.
+ */
+ ret &= lp_create_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_create_mode(SNUM(conn));
+ } else if (ptype == PERM_NEW_DIR) {
+ /*
+ * "directory mask"/"force directory mode" are
+ * only applied to new directories, not existing ones.
+ */
+ ret &= lp_directory_mask(SNUM(conn));
+ /* Add in force bits */
+ ret |= lp_force_directory_mode(SNUM(conn));
+ }
+
+ *ret_perms = ret;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Get a level dependent lanman2 dir entry.
+****************************************************************************/
+
+struct smbd_dirptr_lanman2_state {
+ connection_struct *conn;
+ uint32_t info_level;
+ bool check_mangled_names;
+ bool case_sensitive;
+};
+
+static bool smbd_dirptr_lanman2_match_fn(TALLOC_CTX *ctx,
+ void *private_data,
+ const char *dname,
+ const char *mask,
+ char **_fname)
+{
+ struct smbd_dirptr_lanman2_state *state =
+ (struct smbd_dirptr_lanman2_state *)private_data;
+ bool ok;
+ char mangled_name[13]; /* mangled 8.3 name. */
+ bool got_match;
+ const char *fname;
+
+ /* Mangle fname if it's an illegal name. */
+ if (mangle_must_mangle(dname, state->conn->params)) {
+ /*
+ * Slow path - ensure we can push the original name as UCS2. If
+ * not, then just don't return this name.
+ */
+ NTSTATUS status;
+ size_t ret_len = 0;
+ size_t len = (strlen(dname) + 2) * 4; /* Allow enough space. */
+ uint8_t *tmp = talloc_array(talloc_tos(),
+ uint8_t,
+ len);
+
+ status = srvstr_push(NULL,
+ FLAGS2_UNICODE_STRINGS,
+ tmp,
+ dname,
+ len,
+ STR_TERMINATE,
+ &ret_len);
+
+ TALLOC_FREE(tmp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ ok = name_to_8_3(dname, mangled_name,
+ true, state->conn->params);
+ if (!ok) {
+ return false;
+ }
+ fname = mangled_name;
+ } else {
+ fname = dname;
+ }
+
+ got_match = mask_match(fname, mask,
+ state->case_sensitive);
+
+ if(!got_match && state->check_mangled_names &&
+ !mangle_is_8_3(fname, false, state->conn->params)) {
+ /*
+ * It turns out that NT matches wildcards against
+ * both long *and* short names. This may explain some
+ * of the wildcard weirdness from old DOS clients
+ * that some people have been seeing.... JRA.
+ */
+ /* Force the mangling into 8.3. */
+ ok = name_to_8_3(fname, mangled_name,
+ false, state->conn->params);
+ if (!ok) {
+ return false;
+ }
+
+ got_match = mask_match(mangled_name, mask,
+ state->case_sensitive);
+ }
+
+ if (!got_match) {
+ return false;
+ }
+
+ *_fname = talloc_strdup(ctx, fname);
+ if (*_fname == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+static uint32_t get_dirent_ea_size(uint32_t mode, files_struct *fsp)
+{
+ if (!(mode & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ unsigned ea_size = estimate_ea_size(fsp);
+ return ea_size;
+ }
+ return IO_REPARSE_TAG_DFS;
+}
+
+static NTSTATUS smbd_marshall_dir_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ uint16_t flags2,
+ uint32_t info_level,
+ struct ea_list *name_list,
+ bool check_mangled_names,
+ bool requires_resume_key,
+ uint32_t mode,
+ const char *fname,
+ const struct smb_filename *smb_fname,
+ int space_remaining,
+ uint8_t align,
+ bool do_pad,
+ char *base_data,
+ char **ppdata,
+ char *end_data,
+ uint64_t *last_entry_off)
+{
+ char *p, *q, *pdata = *ppdata;
+ uint32_t reskey=0;
+ uint64_t file_size = 0;
+ uint64_t allocation_size = 0;
+ uint64_t file_id = 0;
+ size_t len = 0;
+ struct timespec mdate_ts = {0};
+ struct timespec adate_ts = {0};
+ struct timespec cdate_ts = {0};
+ struct timespec create_date_ts = {0};
+ char *nameptr;
+ char *last_entry_ptr;
+ bool was_8_3;
+ int off;
+ int pad = 0;
+ NTSTATUS status;
+ struct readdir_attr_data *readdir_attr_data = NULL;
+ uint32_t ea_size;
+
+ if (!(mode & FILE_ATTRIBUTE_DIRECTORY)) {
+ file_size = get_file_size_stat(&smb_fname->st);
+ }
+ allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, NULL, &smb_fname->st);
+
+ /*
+ * Skip SMB_VFS_FREADDIR_ATTR if the directory entry is a symlink or
+ * a DFS symlink.
+ */
+ if (smb_fname->fsp != NULL &&
+ !(mode & FILE_ATTRIBUTE_REPARSE_POINT)) {
+ status = SMB_VFS_FREADDIR_ATTR(smb_fname->fsp,
+ ctx,
+ &readdir_attr_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(NT_STATUS_NOT_SUPPORTED,
+ status)) {
+ return status;
+ }
+ }
+ }
+
+ file_id = SMB_VFS_FS_FILE_ID(conn, &smb_fname->st);
+
+ mdate_ts = smb_fname->st.st_ex_mtime;
+ adate_ts = smb_fname->st.st_ex_atime;
+ create_date_ts = get_create_timespec(conn, NULL, smb_fname);
+ cdate_ts = get_change_timespec(conn, NULL, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_date_ts);
+ dos_filetime_timespec(&mdate_ts);
+ dos_filetime_timespec(&adate_ts);
+ dos_filetime_timespec(&cdate_ts);
+ }
+
+ /* align the record */
+ SMB_ASSERT(align >= 1);
+
+ off = (int)PTR_DIFF(pdata, base_data);
+ pad = (off + (align-1)) & ~(align-1);
+ pad -= off;
+
+ if (pad && pad > space_remaining) {
+ DEBUG(9,("smbd_marshall_dir_entry: out of space "
+ "for padding (wanted %u, had %d)\n",
+ (unsigned int)pad,
+ space_remaining ));
+ return STATUS_MORE_ENTRIES; /* Not finished - just out of space */
+ }
+
+ off += pad;
+ /* initialize padding to 0 */
+ if (pad) {
+ memset(pdata, 0, pad);
+ }
+ space_remaining -= pad;
+
+ DEBUG(10,("smbd_marshall_dir_entry: space_remaining = %d\n",
+ space_remaining ));
+
+ pdata += pad;
+ p = pdata;
+ last_entry_ptr = p;
+
+ pad = 0;
+ off = 0;
+
+ switch (info_level) {
+ case SMB_FIND_INFO_STANDARD:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_INFO_STANDARD\n"));
+ if(requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2_ts(p, 0, create_date_ts);
+ srv_put_dos_date2_ts(p, 4, adate_ts);
+ srv_put_dos_date2_ts(p, 8, mdate_ts);
+ SIVAL(p,12,(uint32_t)file_size);
+ SIVAL(p,16,(uint32_t)allocation_size);
+ SSVAL(p,20,mode);
+ p += 23;
+ nameptr = p;
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ p += ucs2_align(base_data, p, 0);
+ }
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ SCVAL(nameptr, -1, len - 2);
+ } else {
+ SCVAL(nameptr, -1, 0);
+ }
+ } else {
+ if (len > 1) {
+ SCVAL(nameptr, -1, len - 1);
+ } else {
+ SCVAL(nameptr, -1, 0);
+ }
+ }
+ p += len;
+ break;
+
+ case SMB_FIND_EA_SIZE:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_EA_SIZE\n"));
+ if (requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2_ts(p, 0, create_date_ts);
+ srv_put_dos_date2_ts(p, 4, adate_ts);
+ srv_put_dos_date2_ts(p, 8, mdate_ts);
+ SIVAL(p,12,(uint32_t)file_size);
+ SIVAL(p,16,(uint32_t)allocation_size);
+ SSVAL(p,20,mode);
+ {
+ ea_size = estimate_ea_size(smb_fname->fsp);
+ SIVAL(p,22,ea_size); /* Extended attributes */
+ }
+ p += 27;
+ nameptr = p - 1;
+ status = srvstr_push(base_data, flags2,
+ p, fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE | STR_NOALIGN, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ len -= 2;
+ } else {
+ len = 0;
+ }
+ } else {
+ if (len > 1) {
+ len -= 1;
+ } else {
+ len = 0;
+ }
+ }
+ SCVAL(nameptr,0,len);
+ p += len;
+ SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */
+ break;
+
+ case SMB_FIND_EA_LIST:
+ {
+ struct ea_list *file_list = NULL;
+ size_t ea_len = 0;
+
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_EA_LIST\n"));
+ if (!name_list) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (requires_resume_key) {
+ SIVAL(p,0,reskey);
+ p += 4;
+ }
+ srv_put_dos_date2_ts(p, 0, create_date_ts);
+ srv_put_dos_date2_ts(p, 4, adate_ts);
+ srv_put_dos_date2_ts(p, 8, mdate_ts);
+ SIVAL(p,12,(uint32_t)file_size);
+ SIVAL(p,16,(uint32_t)allocation_size);
+ SSVAL(p,20,mode);
+ p += 22; /* p now points to the EA area. */
+
+ status = get_ea_list_from_fsp(ctx,
+ smb_fname->fsp,
+ &ea_len, &file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ file_list = NULL;
+ }
+ name_list = ea_list_union(name_list, file_list, &ea_len);
+
+ /* We need to determine if this entry will fit in the space available. */
+ /* Max string size is 255 bytes. */
+ if (PTR_DIFF(p + 255 + ea_len,pdata) > space_remaining) {
+ DEBUG(9,("smbd_marshall_dir_entry: out of space "
+ "(wanted %u, had %d)\n",
+ (unsigned int)PTR_DIFF(p + 255 + ea_len,pdata),
+ space_remaining ));
+ return STATUS_MORE_ENTRIES; /* Not finished - just out of space */
+ }
+
+ /* Push the ea_data followed by the name. */
+ p += fill_ea_buffer(ctx, p, space_remaining, conn, name_list);
+ nameptr = p;
+ status = srvstr_push(base_data, flags2,
+ p + 1, fname, PTR_DIFF(end_data, p+1),
+ STR_TERMINATE | STR_NOALIGN, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (flags2 & FLAGS2_UNICODE_STRINGS) {
+ if (len > 2) {
+ len -= 2;
+ } else {
+ len = 0;
+ }
+ } else {
+ if (len > 1) {
+ len -= 1;
+ } else {
+ len = 0;
+ }
+ }
+ SCVAL(nameptr,0,len);
+ p += len + 1;
+ SCVAL(p,0,0); p += 1; /* Extra zero byte ? - why.. */
+ break;
+ }
+
+ case SMB_FIND_FILE_BOTH_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_BOTH_DIRECTORY_INFO\n"));
+ was_8_3 = mangle_is_8_3(fname, True, conn->params);
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ SIVAL(p, 0, ea_size);
+ p += 4;
+ /* Clear the short name buffer. This is
+ * IMPORTANT as not doing so will trigger
+ * a Win2k client bug. JRA.
+ */
+ if (!was_8_3 && check_mangled_names) {
+ char mangled_name[13]; /* mangled 8.3 name. */
+ if (!name_to_8_3(fname,mangled_name,True,
+ conn->params)) {
+ /* Error - mangle failed ! */
+ memset(mangled_name,'\0',12);
+ }
+ mangled_name[12] = 0;
+ status = srvstr_push(base_data, flags2,
+ p+2, mangled_name, 24,
+ STR_UPPER|STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (len < 24) {
+ memset(p + 2 + len,'\0',24 - len);
+ }
+ SSVAL(p, 0, len);
+ } else {
+ memset(p,'\0',26);
+ }
+ p += 2 + 24;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q,0,len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_FILE_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ status = srvstr_push(base_data, flags2,
+ p + 4, fname, PTR_DIFF(end_data, p+4),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(p,0,len);
+ p += 4 + len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_FILE_FULL_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_FULL_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ SIVAL(p, 0, ea_size);
+ p +=4;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q, 0, len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_FILE_NAMES_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_NAMES_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ p += 4;
+ /* this must *not* be null terminated or w2k gets in a loop trying to set an
+ acl on a dir (tridge) */
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(p, -4, len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_ID_FULL_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_ID_FULL_DIRECTORY_INFO\n"));
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length. */
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ SIVAL(p, 0, ea_size);
+ p += 4;
+ SIVAL(p,0,0); p += 4; /* Unknown - reserved ? */
+ SBVAL(p,0,file_id); p += 8;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q, 0, len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ case SMB_FIND_ID_BOTH_DIRECTORY_INFO:
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_ID_BOTH_DIRECTORY_INFO\n"));
+ was_8_3 = mangle_is_8_3(fname, True, conn->params);
+ p += 4;
+ SIVAL(p,0,reskey); p += 4;
+ put_long_date_full_timespec(conn->ts_res,p,&create_date_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&adate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&mdate_ts); p += 8;
+ put_long_date_full_timespec(conn->ts_res,p,&cdate_ts); p += 8;
+ SOFF_T(p,0,file_size); p += 8;
+ SOFF_T(p,0,allocation_size); p += 8;
+ SIVAL(p,0,mode); p += 4;
+ q = p; p += 4; /* q is placeholder for name length */
+ if (readdir_attr_data &&
+ readdir_attr_data->type == RDATTR_AAPL) {
+ /*
+ * OS X specific SMB2 extension negotiated via
+ * AAPL create context: return max_access in
+ * ea_size field.
+ */
+ ea_size = readdir_attr_data->attr_data.aapl.max_access;
+ } else {
+ ea_size = get_dirent_ea_size(mode, smb_fname->fsp);
+ }
+ SIVAL(p,0,ea_size); /* Extended attributes */
+ p += 4;
+
+ if (readdir_attr_data &&
+ readdir_attr_data->type == RDATTR_AAPL) {
+ /*
+ * OS X specific SMB2 extension negotiated via
+ * AAPL create context: return resource fork
+ * length and compressed FinderInfo in
+ * shortname field.
+ *
+ * According to documentation short_name_len
+ * should be 0, but on the wire behaviour
+ * shows its set to 24 by clients.
+ */
+ SSVAL(p, 0, 24);
+
+ /* Resourefork length */
+ SBVAL(p, 2, readdir_attr_data->attr_data.aapl.rfork_size);
+
+ /* Compressed FinderInfo */
+ memcpy(p + 10, &readdir_attr_data->attr_data.aapl.finder_info, 16);
+ } else if (!was_8_3 && check_mangled_names) {
+ char mangled_name[13]; /* mangled 8.3 name. */
+ if (!name_to_8_3(fname,mangled_name,True,
+ conn->params)) {
+ /* Error - mangle failed ! */
+ memset(mangled_name,'\0',12);
+ }
+ mangled_name[12] = 0;
+ status = srvstr_push(base_data, flags2,
+ p+2, mangled_name, 24,
+ STR_UPPER|STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SSVAL(p, 0, len);
+ if (len < 24) {
+ memset(p + 2 + len,'\0',24 - len);
+ }
+ SSVAL(p, 0, len);
+ } else {
+ /* Clear the short name buffer. This is
+ * IMPORTANT as not doing so will trigger
+ * a Win2k client bug. JRA.
+ */
+ memset(p,'\0',26);
+ }
+ p += 26;
+
+ /* Reserved ? */
+ if (readdir_attr_data &&
+ readdir_attr_data->type == RDATTR_AAPL) {
+ /*
+ * OS X specific SMB2 extension negotiated via
+ * AAPL create context: return UNIX mode in
+ * reserved field.
+ */
+ uint16_t aapl_mode = (uint16_t)readdir_attr_data->attr_data.aapl.unix_mode;
+ SSVAL(p, 0, aapl_mode);
+ } else {
+ SSVAL(p, 0, 0);
+ }
+ p += 2;
+
+ SBVAL(p,0,file_id); p += 8;
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE_ASCII, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(q,0,len);
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ break;
+
+ /* CIFS UNIX Extension. */
+
+ case SMB_FIND_FILE_UNIX:
+ case SMB_FIND_FILE_UNIX_INFO2:
+ p+= 4;
+ SIVAL(p,0,reskey); p+= 4; /* Used for continuing search. */
+
+ /* Begin of SMB_QUERY_FILE_UNIX_BASIC */
+
+ if (info_level == SMB_FIND_FILE_UNIX) {
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_UNIX\n"));
+ p = store_file_unix_basic(conn, p,
+ NULL, &smb_fname->st);
+ status = srvstr_push(base_data, flags2, p,
+ fname, PTR_DIFF(end_data, p),
+ STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ } else {
+ DEBUG(10,("smbd_marshall_dir_entry: SMB_FIND_FILE_UNIX_INFO2\n"));
+ p = store_file_unix_basic_info2(conn, p,
+ NULL, &smb_fname->st);
+ nameptr = p;
+ p += 4;
+ status = srvstr_push(base_data, flags2, p, fname,
+ PTR_DIFF(end_data, p), 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(nameptr, 0, len);
+ }
+
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ /*
+ * set padding to zero
+ */
+ if (do_pad) {
+ memset(p, 0, pad - len);
+ p = pdata + pad;
+ } else {
+ p = pdata + len;
+ }
+ /* End of SMB_QUERY_FILE_UNIX_BASIC */
+
+ break;
+
+ /* SMB2 UNIX Extension. */
+
+ case SMB2_FILE_POSIX_INFORMATION:
+ {
+ struct smb3_file_posix_information info = {};
+ uint8_t buf[sizeof(info)];
+ struct ndr_push ndr = {
+ .data = buf,
+ .alloc_size = sizeof(buf),
+ .fixed_buf_size = true,
+ };
+ enum ndr_err_code ndr_err;
+
+ p+= 4;
+ SIVAL(p,0,reskey); p+= 4;
+
+ DBG_DEBUG("SMB2_FILE_POSIX_INFORMATION\n");
+
+ if (!(conn->sconn->using_smb2)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ smb3_file_posix_information_init(
+ conn, &smb_fname->st, 0, mode, &info);
+
+ ndr_err = ndr_push_smb3_file_posix_information(
+ &ndr, NDR_SCALARS|NDR_BUFFERS, &info);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ memcpy(p, buf, ndr.offset);
+ p += ndr.offset;
+
+ nameptr = p;
+ p += 4;
+ status = srvstr_push(base_data, flags2, p, fname,
+ PTR_DIFF(end_data, p), 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(nameptr, 0, len);
+
+ p += len;
+
+ len = PTR_DIFF(p, pdata);
+ pad = (len + (align-1)) & ~(align-1);
+ /*
+ * offset to the next entry, the caller
+ * will overwrite it for the last entry
+ * that's why we always include the padding
+ */
+ SIVAL(pdata,0,pad);
+ break;
+ }
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (PTR_DIFF(p,pdata) > space_remaining) {
+ DEBUG(9,("smbd_marshall_dir_entry: out of space "
+ "(wanted %u, had %d)\n",
+ (unsigned int)PTR_DIFF(p,pdata),
+ space_remaining ));
+ return STATUS_MORE_ENTRIES; /* Not finished - just out of space */
+ }
+
+ /* Setup the last entry pointer, as an offset from base_data */
+ *last_entry_off = PTR_DIFF(last_entry_ptr,base_data);
+ /* Advance the data pointer to the next slot */
+ *ppdata = p;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_dirptr_lanman2_entry(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct dptr_struct *dirptr,
+ uint16_t flags2,
+ const char *path_mask,
+ uint32_t dirtype,
+ int info_level,
+ int requires_resume_key,
+ bool dont_descend,
+ bool ask_sharemode,
+ bool get_dosmode,
+ uint8_t align,
+ bool do_pad,
+ char **ppdata,
+ char *base_data,
+ char *end_data,
+ int space_remaining,
+ struct smb_filename **_smb_fname,
+ int *_last_entry_off,
+ struct ea_list *name_list,
+ struct file_id *file_id)
+{
+ const char *p;
+ const char *mask = NULL;
+ uint32_t mode = 0;
+ char *fname = NULL;
+ struct smb_filename *smb_fname = NULL;
+ struct smbd_dirptr_lanman2_state state;
+ bool ok;
+ uint64_t last_entry_off = 0;
+ NTSTATUS status;
+ enum mangled_names_options mangled_names;
+ bool marshall_with_83_names;
+
+ mangled_names = lp_mangled_names(conn->params);
+
+ ZERO_STRUCT(state);
+ state.conn = conn;
+ state.info_level = info_level;
+ if (mangled_names != MANGLED_NAMES_NO) {
+ state.check_mangled_names = true;
+ }
+ state.case_sensitive = dptr_case_sensitive(dirptr);
+
+ p = strrchr_m(path_mask,'/');
+ if(p != NULL) {
+ if(p[1] == '\0') {
+ mask = "*.*";
+ } else {
+ mask = p+1;
+ }
+ } else {
+ mask = path_mask;
+ }
+
+ ok = smbd_dirptr_get_entry(ctx,
+ dirptr,
+ mask,
+ dirtype,
+ dont_descend,
+ ask_sharemode,
+ get_dosmode,
+ smbd_dirptr_lanman2_match_fn,
+ &state,
+ &fname,
+ &smb_fname,
+ &mode);
+ if (!ok) {
+ return NT_STATUS_END_OF_FILE;
+ }
+
+ marshall_with_83_names = (mangled_names == MANGLED_NAMES_YES);
+
+ status = smbd_marshall_dir_entry(ctx,
+ conn,
+ flags2,
+ info_level,
+ name_list,
+ marshall_with_83_names,
+ requires_resume_key,
+ mode,
+ fname,
+ smb_fname,
+ space_remaining,
+ align,
+ do_pad,
+ base_data,
+ ppdata,
+ end_data,
+ &last_entry_off);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ILLEGAL_CHARACTER)) {
+ DEBUG(1,("Conversion error: illegal character: %s\n",
+ smb_fname_str_dbg(smb_fname)));
+ }
+
+ if (file_id != NULL) {
+ *file_id = vfs_file_id_from_sbuf(conn, &smb_fname->st);
+ }
+
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ smbd_dirptr_push_overflow(dirptr, &fname, &smb_fname, mode);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ return status;
+ }
+
+ smbd_dirptr_set_last_name_sent(dirptr, &smb_fname->base_name);
+
+ if (_smb_fname != NULL) {
+ /*
+ * smb_fname is already talloc'ed off ctx.
+ * We just need to make sure we don't return
+ * any stream_name, and replace base_name
+ * with fname in case base_name got mangled.
+ * This allows us to preserve any smb_fname->fsp
+ * for asynchronous handle lookups.
+ */
+ TALLOC_FREE(smb_fname->stream_name);
+
+ /*
+ * smbd_dirptr_set_last_name_sent() above consumed
+ * base_name
+ */
+ smb_fname->base_name = talloc_strdup(smb_fname, fname);
+
+ if (smb_fname->base_name == NULL) {
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fname);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *_smb_fname = smb_fname;
+ } else {
+ TALLOC_FREE(smb_fname);
+ }
+ TALLOC_FREE(fname);
+
+ *_last_entry_off = last_entry_off;
+ return NT_STATUS_OK;
+}
+
+unsigned char *create_volume_objectid(connection_struct *conn, unsigned char objid[16])
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ E_md4hash(lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),objid);
+ return objid;
+}
+
+static void samba_extended_info_version(struct smb_extended_info *extended_info)
+{
+ SMB_ASSERT(extended_info != NULL);
+
+ extended_info->samba_magic = SAMBA_EXTENDED_INFO_MAGIC;
+ extended_info->samba_version = ((SAMBA_VERSION_MAJOR & 0xff) << 24)
+ | ((SAMBA_VERSION_MINOR & 0xff) << 16)
+ | ((SAMBA_VERSION_RELEASE & 0xff) << 8);
+#ifdef SAMBA_VERSION_REVISION
+ extended_info->samba_version |= (tolower(*SAMBA_VERSION_REVISION) - 'a' + 1) & 0xff;
+#endif
+ extended_info->samba_subversion = 0;
+#ifdef SAMBA_VERSION_RC_RELEASE
+ extended_info->samba_subversion |= (SAMBA_VERSION_RC_RELEASE & 0xff) << 24;
+#else
+#ifdef SAMBA_VERSION_PRE_RELEASE
+ extended_info->samba_subversion |= (SAMBA_VERSION_PRE_RELEASE & 0xff) << 16;
+#endif
+#endif
+#ifdef SAMBA_VERSION_VENDOR_PATCH
+ extended_info->samba_subversion |= (SAMBA_VERSION_VENDOR_PATCH & 0xffff);
+#endif
+ extended_info->samba_gitcommitdate = 0;
+#ifdef SAMBA_VERSION_COMMIT_TIME
+ unix_to_nt_time(&extended_info->samba_gitcommitdate, SAMBA_VERSION_COMMIT_TIME);
+#endif
+
+ memset(extended_info->samba_version_string, 0,
+ sizeof(extended_info->samba_version_string));
+
+ snprintf (extended_info->samba_version_string,
+ sizeof(extended_info->samba_version_string),
+ "%s", samba_version_string());
+}
+
+static bool fsinfo_unix_valid_level(connection_struct *conn,
+ struct files_struct *fsp,
+ uint16_t info_level)
+{
+ if (conn->sconn->using_smb2 &&
+ fsp->posix_flags == FSP_POSIX_FLAGS_OPEN &&
+ info_level == SMB2_FS_POSIX_INFORMATION_INTERNAL)
+ {
+ return true;
+ }
+#if defined(SMB1SERVER)
+ if (lp_smb1_unix_extensions() &&
+ info_level == SMB_QUERY_POSIX_FS_INFO) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+/*
+ * fsp is only valid for SMB2.
+ */
+NTSTATUS smbd_do_qfsinfo(struct smbXsrv_connection *xconn,
+ connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ struct files_struct *fsp,
+ struct smb_filename *fname,
+ char **ppdata,
+ int *ret_data_len)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *pdata, *end_data;
+ int data_len = 0;
+ size_t len = 0;
+ const char *vname = volume_label(talloc_tos(), SNUM(conn));
+ int snum = SNUM(conn);
+ const char *fstype = lp_fstype(SNUM(conn));
+ const char *filename = NULL;
+ const uint64_t bytes_per_sector = 512;
+ uint32_t additional_flags = 0;
+ struct smb_filename smb_fname;
+ SMB_STRUCT_STAT st;
+ NTSTATUS status = NT_STATUS_OK;
+ uint64_t df_ret;
+ uint32_t serial;
+
+ if (fname == NULL || fname->base_name == NULL) {
+ filename = ".";
+ } else {
+ filename = fname->base_name;
+ }
+
+ if (IS_IPC(conn)) {
+ if (info_level != SMB_QUERY_CIFS_UNIX_INFO) {
+ DEBUG(0,("smbd_do_qfsinfo: not an allowed "
+ "info level (0x%x) on IPC$.\n",
+ (unsigned int)info_level));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ DEBUG(3,("smbd_do_qfsinfo: level = %d\n", info_level));
+
+ smb_fname = (struct smb_filename) {
+ .base_name = discard_const_p(char, filename),
+ .flags = fname ? fname->flags : 0,
+ .twrp = fname ? fname->twrp : 0,
+ };
+
+ if(info_level != SMB_FS_QUOTA_INFORMATION
+ && SMB_VFS_STAT(conn, &smb_fname) != 0) {
+ DEBUG(2,("stat of . failed (%s)\n", strerror(errno)));
+ return map_nt_error_from_unix(errno);
+ }
+
+ st = smb_fname.st;
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *ppdata = (char *)SMB_REALLOC(
+ *ppdata, max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ pdata = *ppdata;
+ memset((char *)pdata,'\0',max_data_bytes + DIR_ENTRY_SAFETY_MARGIN);
+ end_data = pdata + max_data_bytes + DIR_ENTRY_SAFETY_MARGIN - 1;
+
+ *fixed_portion = 0;
+
+ switch (info_level) {
+ case SMB_INFO_ALLOCATION:
+ {
+ uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
+ data_len = 18;
+ df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+ &dfree, &dsize);
+ if (df_ret == (uint64_t)-1) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ uint64_t factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ uint64_t factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ sectors_per_unit = bsize/bytes_per_sector;
+
+ DEBUG(5,("smbd_do_qfsinfo : SMB_INFO_ALLOCATION id=%x, bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)st.st_ex_dev, (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+
+ /*
+ * For large drives, return max values and not modulo.
+ */
+ dsize = MIN(dsize, UINT32_MAX);
+ dfree = MIN(dfree, UINT32_MAX);
+
+ SIVAL(pdata,l1_idFileSystem,st.st_ex_dev);
+ SIVAL(pdata,l1_cSectorUnit,sectors_per_unit);
+ SIVAL(pdata,l1_cUnit,dsize);
+ SIVAL(pdata,l1_cUnitAvail,dfree);
+ SSVAL(pdata,l1_cbSector,bytes_per_sector);
+ break;
+ }
+
+ case SMB_INFO_VOLUME:
+ /* Return volume name */
+ /*
+ * Add volume serial number - hash of a combination of
+ * the called hostname and the service name.
+ */
+ serial = generate_volume_serial_number(lp_sub, snum);
+ SIVAL(pdata,0,serial);
+ /*
+ * Win2k3 and previous mess this up by sending a name length
+ * one byte short. I believe only older clients (OS/2 Win9x) use
+ * this call so try fixing this by adding a terminating null to
+ * the pushed string. The change here was adding the STR_TERMINATE. JRA.
+ */
+ status = srvstr_push(
+ pdata, flags2,
+ pdata+l2_vol_szVolLabel, vname,
+ PTR_DIFF(end_data, pdata+l2_vol_szVolLabel),
+ STR_NOALIGN|STR_TERMINATE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SCVAL(pdata,l2_vol_cch,len);
+ data_len = l2_vol_szVolLabel + len;
+ DEBUG(5,("smbd_do_qfsinfo : time = %x, namelen = %u, "
+ "name = %s serial = 0x%04"PRIx32"\n",
+ (unsigned)convert_timespec_to_time_t(st.st_ex_ctime),
+ (unsigned)len, vname, serial));
+ break;
+
+ case SMB_QUERY_FS_ATTRIBUTE_INFO:
+ case SMB_FS_ATTRIBUTE_INFORMATION:
+
+ additional_flags = 0;
+#if defined(HAVE_SYS_QUOTAS)
+ additional_flags |= FILE_VOLUME_QUOTAS;
+#endif
+
+ if(lp_nt_acl_support(SNUM(conn))) {
+ additional_flags |= FILE_PERSISTENT_ACLS;
+ }
+
+ /* Capabilities are filled in at connection time through STATVFS call */
+ additional_flags |= conn->fs_capabilities;
+ additional_flags |= lp_parm_int(conn->params->service,
+ "share", "fake_fscaps",
+ 0);
+
+ SIVAL(pdata,0,FILE_CASE_PRESERVED_NAMES|FILE_CASE_SENSITIVE_SEARCH|
+ FILE_SUPPORTS_OBJECT_IDS|FILE_UNICODE_ON_DISK|
+ additional_flags); /* FS ATTRIBUTES */
+
+ SIVAL(pdata,4,255); /* Max filename component length */
+ /* NOTE! the fstype must *not* be null terminated or win98 won't recognise it
+ and will think we can't do long filenames */
+ status = srvstr_push(pdata, flags2, pdata+12, fstype,
+ PTR_DIFF(end_data, pdata+12),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,8,len);
+ data_len = 12 + len;
+ if (max_data_bytes >= 16 && data_len > max_data_bytes) {
+ /* the client only requested a portion of the
+ file system name */
+ data_len = max_data_bytes;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ *fixed_portion = 16;
+ break;
+
+ case SMB_QUERY_FS_LABEL_INFO:
+ case SMB_FS_LABEL_INFORMATION:
+ status = srvstr_push(pdata, flags2, pdata+4, vname,
+ PTR_DIFF(end_data, pdata+4), 0, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ data_len = 4 + len;
+ SIVAL(pdata,0,len);
+ break;
+
+ case SMB_QUERY_FS_VOLUME_INFO:
+ case SMB_FS_VOLUME_INFORMATION:
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER,
+ pdata, &st.st_ex_btime);
+ /*
+ * Add volume serial number - hash of a combination of
+ * the called hostname and the service name.
+ */
+ serial = generate_volume_serial_number(lp_sub, snum);
+ SIVAL(pdata,8,serial);
+
+ /* Max label len is 32 characters. */
+ status = srvstr_push(pdata, flags2, pdata+18, vname,
+ PTR_DIFF(end_data, pdata+18),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,12,len);
+ data_len = 18+len;
+
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_VOLUME_INFO "
+ "namelen = %d, vol=%s serv=%s "
+ "serial=0x%04"PRIx32"\n",
+ (int)strlen(vname),vname,
+ lp_servicename(talloc_tos(), lp_sub, snum),
+ serial));
+ if (max_data_bytes >= 24 && data_len > max_data_bytes) {
+ /* the client only requested a portion of the
+ volume label */
+ data_len = max_data_bytes;
+ status = STATUS_BUFFER_OVERFLOW;
+ }
+ *fixed_portion = 24;
+ break;
+
+ case SMB_QUERY_FS_SIZE_INFO:
+ case SMB_FS_SIZE_INFORMATION:
+ {
+ uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
+ data_len = 24;
+ df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+ &dfree, &dsize);
+ if (df_ret == (uint64_t)-1) {
+ return map_nt_error_from_unix(errno);
+ }
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ uint64_t factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ uint64_t factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ sectors_per_unit = bsize/bytes_per_sector;
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_SIZE_INFO bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+ SBIG_UINT(pdata,0,dsize);
+ SBIG_UINT(pdata,8,dfree);
+ SIVAL(pdata,16,sectors_per_unit);
+ SIVAL(pdata,20,bytes_per_sector);
+ *fixed_portion = 24;
+ break;
+ }
+
+ case SMB_FS_FULL_SIZE_INFORMATION:
+ {
+ uint64_t dfree,dsize,bsize,block_size,sectors_per_unit;
+ data_len = 32;
+ df_ret = get_dfree_info(conn, &smb_fname, &bsize,
+ &dfree, &dsize);
+ if (df_ret == (uint64_t)-1) {
+ return map_nt_error_from_unix(errno);
+ }
+ block_size = lp_block_size(snum);
+ if (bsize < block_size) {
+ uint64_t factor = block_size/bsize;
+ bsize = block_size;
+ dsize /= factor;
+ dfree /= factor;
+ }
+ if (bsize > block_size) {
+ uint64_t factor = bsize/block_size;
+ bsize = block_size;
+ dsize *= factor;
+ dfree *= factor;
+ }
+ sectors_per_unit = bsize/bytes_per_sector;
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_FS_FULL_SIZE_INFO bsize=%u, cSectorUnit=%u, \
+cBytesSector=%u, cUnitTotal=%u, cUnitAvail=%d\n", (unsigned int)bsize, (unsigned int)sectors_per_unit,
+ (unsigned int)bytes_per_sector, (unsigned int)dsize, (unsigned int)dfree));
+ SBIG_UINT(pdata,0,dsize); /* Total Allocation units. */
+ SBIG_UINT(pdata,8,dfree); /* Caller available allocation units. */
+ SBIG_UINT(pdata,16,dfree); /* Actual available allocation units. */
+ SIVAL(pdata,24,sectors_per_unit); /* Sectors per allocation unit. */
+ SIVAL(pdata,28,bytes_per_sector); /* Bytes per sector. */
+ *fixed_portion = 32;
+ break;
+ }
+
+ case SMB_QUERY_FS_DEVICE_INFO:
+ case SMB_FS_DEVICE_INFORMATION:
+ {
+ uint32_t characteristics = FILE_DEVICE_IS_MOUNTED;
+
+ if (!CAN_WRITE(conn)) {
+ characteristics |= FILE_READ_ONLY_DEVICE;
+ }
+ data_len = 8;
+ SIVAL(pdata,0,FILE_DEVICE_DISK); /* dev type */
+ SIVAL(pdata,4,characteristics);
+ *fixed_portion = 8;
+ break;
+ }
+
+#ifdef HAVE_SYS_QUOTAS
+ case SMB_FS_QUOTA_INFORMATION:
+ /*
+ * what we have to send --metze:
+ *
+ * Unknown1: 24 NULL bytes
+ * Soft Quota Threshold: 8 bytes seems like uint64_t or so
+ * Hard Quota Limit: 8 bytes seems like uint64_t or so
+ * Quota Flags: 2 byte :
+ * Unknown3: 6 NULL bytes
+ *
+ * 48 bytes total
+ *
+ * details for Quota Flags:
+ *
+ * 0x0020 Log Limit: log if the user exceeds his Hard Quota
+ * 0x0010 Log Warn: log if the user exceeds his Soft Quota
+ * 0x0002 Deny Disk: deny disk access when the user exceeds his Hard Quota
+ * 0x0001 Enable Quotas: enable quota for this fs
+ *
+ */
+ {
+ /* we need to fake up a fsp here,
+ * because its not send in this call
+ */
+ files_struct tmpfsp;
+ SMB_NTQUOTA_STRUCT quotas;
+
+ ZERO_STRUCT(tmpfsp);
+ ZERO_STRUCT(quotas);
+
+ tmpfsp.conn = conn;
+ tmpfsp.fnum = FNUM_FIELD_INVALID;
+
+ /* access check */
+ if (get_current_uid(conn) != 0) {
+ DEBUG(0,("get_user_quota: access_denied "
+ "service [%s] user [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = vfs_get_ntquota(&tmpfsp, SMB_USER_FS_QUOTA_TYPE,
+ NULL, &quotas);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("vfs_get_ntquota() failed for service [%s]\n",lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return status;
+ }
+
+ data_len = 48;
+
+ DEBUG(10,("SMB_FS_QUOTA_INFORMATION: for service [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+
+ /* Unknown1 24 NULL bytes*/
+ SBIG_UINT(pdata,0,(uint64_t)0);
+ SBIG_UINT(pdata,8,(uint64_t)0);
+ SBIG_UINT(pdata,16,(uint64_t)0);
+
+ /* Default Soft Quota 8 bytes */
+ SBIG_UINT(pdata,24,quotas.softlim);
+
+ /* Default Hard Quota 8 bytes */
+ SBIG_UINT(pdata,32,quotas.hardlim);
+
+ /* Quota flag 2 bytes */
+ SSVAL(pdata,40,quotas.qflags);
+
+ /* Unknown3 6 NULL bytes */
+ SSVAL(pdata,42,0);
+ SIVAL(pdata,44,0);
+
+ break;
+ }
+#endif /* HAVE_SYS_QUOTAS */
+ case SMB_FS_OBJECTID_INFORMATION:
+ {
+ unsigned char objid[16];
+ struct smb_extended_info extended_info;
+ memcpy(pdata,create_volume_objectid(conn, objid),16);
+ samba_extended_info_version (&extended_info);
+ SIVAL(pdata,16,extended_info.samba_magic);
+ SIVAL(pdata,20,extended_info.samba_version);
+ SIVAL(pdata,24,extended_info.samba_subversion);
+ SBIG_UINT(pdata,28,extended_info.samba_gitcommitdate);
+ memcpy(pdata+36,extended_info.samba_version_string,28);
+ data_len = 64;
+ break;
+ }
+
+ case SMB_FS_SECTOR_SIZE_INFORMATION:
+ {
+ data_len = 28;
+ /*
+ * These values match a physical Windows Server 2012
+ * share backed by NTFS atop spinning rust.
+ */
+ DEBUG(5, ("SMB_FS_SECTOR_SIZE_INFORMATION:"));
+ /* logical_bytes_per_sector */
+ SIVAL(pdata, 0, bytes_per_sector);
+ /* phys_bytes_per_sector_atomic */
+ SIVAL(pdata, 4, bytes_per_sector);
+ /* phys_bytes_per_sector_perf */
+ SIVAL(pdata, 8, bytes_per_sector);
+ /* fs_effective_phys_bytes_per_sector_atomic */
+ SIVAL(pdata, 12, bytes_per_sector);
+ /* flags */
+ SIVAL(pdata, 16, SSINFO_FLAGS_ALIGNED_DEVICE
+ | SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE);
+ /* byte_off_sector_align */
+ SIVAL(pdata, 20, 0);
+ /* byte_off_partition_align */
+ SIVAL(pdata, 24, 0);
+ *fixed_portion = 28;
+ break;
+ }
+
+
+#if defined(WITH_SMB1SERVER)
+ /*
+ * Query the version and capabilities of the CIFS UNIX extensions
+ * in use.
+ */
+
+ case SMB_QUERY_CIFS_UNIX_INFO:
+ {
+ bool large_write = lp_min_receive_file_size() &&
+ !smb1_srv_is_signing_active(xconn);
+ bool large_read = !smb1_srv_is_signing_active(xconn);
+ int encrypt_caps = 0;
+
+ if (!lp_smb1_unix_extensions()) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ switch (conn->encrypt_level) {
+ case SMB_SIGNING_OFF:
+ encrypt_caps = 0;
+ break;
+ case SMB_SIGNING_DESIRED:
+ case SMB_SIGNING_IF_REQUIRED:
+ case SMB_SIGNING_DEFAULT:
+ encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP;
+ break;
+ case SMB_SIGNING_REQUIRED:
+ encrypt_caps = CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP|
+ CIFS_UNIX_TRANSPORT_ENCRYPTION_MANDATORY_CAP;
+ large_write = false;
+ large_read = false;
+ break;
+ }
+
+ data_len = 12;
+ SSVAL(pdata,0,CIFS_UNIX_MAJOR_VERSION);
+ SSVAL(pdata,2,CIFS_UNIX_MINOR_VERSION);
+
+ /* We have POSIX ACLs, pathname, encryption,
+ * large read/write, and locking capability. */
+
+ SBIG_UINT(pdata,4,((uint64_t)(
+ CIFS_UNIX_POSIX_ACLS_CAP|
+ CIFS_UNIX_POSIX_PATHNAMES_CAP|
+ CIFS_UNIX_FCNTL_LOCKS_CAP|
+ CIFS_UNIX_EXTATTR_CAP|
+ CIFS_UNIX_POSIX_PATH_OPERATIONS_CAP|
+ encrypt_caps|
+ (large_read ? CIFS_UNIX_LARGE_READ_CAP : 0) |
+ (large_write ?
+ CIFS_UNIX_LARGE_WRITE_CAP : 0))));
+ break;
+ }
+#endif
+
+ case SMB_QUERY_POSIX_FS_INFO:
+ case SMB2_FS_POSIX_INFORMATION_INTERNAL:
+ {
+ int rc;
+ struct vfs_statvfs_struct svfs;
+
+ if (!fsinfo_unix_valid_level(conn, fsp, info_level)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ rc = SMB_VFS_STATVFS(conn, &smb_fname, &svfs);
+
+ if (!rc) {
+ data_len = 56;
+ SIVAL(pdata,0,svfs.OptimalTransferSize);
+ SIVAL(pdata,4,svfs.BlockSize);
+ SBIG_UINT(pdata,8,svfs.TotalBlocks);
+ SBIG_UINT(pdata,16,svfs.BlocksAvail);
+ SBIG_UINT(pdata,24,svfs.UserBlocksAvail);
+ SBIG_UINT(pdata,32,svfs.TotalFileNodes);
+ SBIG_UINT(pdata,40,svfs.FreeFileNodes);
+ SBIG_UINT(pdata,48,svfs.FsIdentifier);
+ DEBUG(5,("smbd_do_qfsinfo : SMB_QUERY_POSIX_FS_INFO successful\n"));
+#ifdef EOPNOTSUPP
+ } else if (rc == EOPNOTSUPP) {
+ return NT_STATUS_INVALID_LEVEL;
+#endif /* EOPNOTSUPP */
+ } else {
+ DEBUG(0,("vfs_statvfs() failed for service [%s]\n",lp_servicename(talloc_tos(), lp_sub, SNUM(conn))));
+ return NT_STATUS_DOS(ERRSRV, ERRerror);
+ }
+ break;
+ }
+
+ case SMB_QUERY_POSIX_WHOAMI:
+ {
+ uint32_t flags = 0;
+ uint32_t sid_bytes;
+ uint32_t i;
+
+ if (!lp_smb1_unix_extensions()) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (max_data_bytes < 40) {
+ return NT_STATUS_BUFFER_TOO_SMALL;
+ }
+
+ if (security_session_user_level(conn->session_info, NULL) < SECURITY_USER) {
+ flags |= SMB_WHOAMI_GUEST;
+ }
+
+ /* NOTE: 8 bytes for UID/GID, irrespective of native
+ * platform size. This matches
+ * SMB_QUERY_FILE_UNIX_BASIC and friends.
+ */
+ data_len = 4 /* flags */
+ + 4 /* flag mask */
+ + 8 /* uid */
+ + 8 /* gid */
+ + 4 /* ngroups */
+ + 4 /* num_sids */
+ + 4 /* SID bytes */
+ + 4 /* pad/reserved */
+ + (conn->session_info->unix_token->ngroups * 8)
+ /* groups list */
+ + (conn->session_info->security_token->num_sids *
+ SID_MAX_SIZE)
+ /* SID list */;
+
+ SIVAL(pdata, 0, flags);
+ SIVAL(pdata, 4, SMB_WHOAMI_MASK);
+ SBIG_UINT(pdata, 8,
+ (uint64_t)conn->session_info->unix_token->uid);
+ SBIG_UINT(pdata, 16,
+ (uint64_t)conn->session_info->unix_token->gid);
+
+
+ if (data_len >= max_data_bytes) {
+ /* Potential overflow, skip the GIDs and SIDs. */
+
+ SIVAL(pdata, 24, 0); /* num_groups */
+ SIVAL(pdata, 28, 0); /* num_sids */
+ SIVAL(pdata, 32, 0); /* num_sid_bytes */
+ SIVAL(pdata, 36, 0); /* reserved */
+
+ data_len = 40;
+ break;
+ }
+
+ SIVAL(pdata, 24, conn->session_info->unix_token->ngroups);
+ SIVAL(pdata, 28, conn->session_info->security_token->num_sids);
+
+ /* We walk the SID list twice, but this call is fairly
+ * infrequent, and I don't expect that it's performance
+ * sensitive -- jpeach
+ */
+ for (i = 0, sid_bytes = 0;
+ i < conn->session_info->security_token->num_sids; ++i) {
+ sid_bytes += ndr_size_dom_sid(
+ &conn->session_info->security_token->sids[i],
+ 0);
+ }
+
+ /* SID list byte count */
+ SIVAL(pdata, 32, sid_bytes);
+
+ /* 4 bytes pad/reserved - must be zero */
+ SIVAL(pdata, 36, 0);
+ data_len = 40;
+
+ /* GID list */
+ for (i = 0; i < conn->session_info->unix_token->ngroups; ++i) {
+ SBIG_UINT(pdata, data_len,
+ (uint64_t)conn->session_info->unix_token->groups[i]);
+ data_len += 8;
+ }
+
+ /* SID list */
+ for (i = 0;
+ i < conn->session_info->security_token->num_sids; ++i) {
+ int sid_len = ndr_size_dom_sid(
+ &conn->session_info->security_token->sids[i],
+ 0);
+
+ sid_linearize((uint8_t *)(pdata + data_len),
+ sid_len,
+ &conn->session_info->security_token->sids[i]);
+ data_len += sid_len;
+ }
+
+ break;
+ }
+
+ case SMB_MAC_QUERY_FS_INFO:
+ /*
+ * Thursby MAC extension... ONLY on NTFS filesystems
+ * once we do streams then we don't need this
+ */
+ if (strequal(lp_fstype(SNUM(conn)),"NTFS")) {
+ data_len = 88;
+ SIVAL(pdata,84,0x100); /* Don't support mac... */
+ break;
+ }
+
+ FALL_THROUGH;
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ *ret_data_len = data_len;
+ return status;
+}
+
+NTSTATUS smb_set_fsquota(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ const DATA_BLOB *qdata)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ NTSTATUS status;
+ SMB_NTQUOTA_STRUCT quotas;
+
+ ZERO_STRUCT(quotas);
+
+ /* access check */
+ if ((get_current_uid(conn) != 0) || !CAN_WRITE(conn)) {
+ DBG_NOTICE("access_denied service [%s] user [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)),
+ conn->session_info->unix_info->unix_name);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!check_fsp_ntquota_handle(conn, req,
+ fsp)) {
+ DBG_WARNING("no valid QUOTA HANDLE\n");
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* note: normally there're 48 bytes,
+ * but we didn't use the last 6 bytes for now
+ * --metze
+ */
+ if (qdata->length < 42) {
+ DBG_ERR("requires total_data(%zu) >= 42 bytes!\n",
+ qdata->length);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* unknown_1 24 NULL bytes in pdata*/
+
+ /* the soft quotas 8 bytes (uint64_t)*/
+ quotas.softlim = BVAL(qdata->data,24);
+
+ /* the hard quotas 8 bytes (uint64_t)*/
+ quotas.hardlim = BVAL(qdata->data,32);
+
+ /* quota_flags 2 bytes **/
+ quotas.qflags = SVAL(qdata->data,40);
+
+ /* unknown_2 6 NULL bytes follow*/
+
+ /* now set the quotas */
+ if (vfs_set_ntquota(fsp, SMB_USER_FS_QUOTA_TYPE, NULL, &quotas)!=0) {
+ DBG_WARNING("vfs_set_ntquota() failed for service [%s]\n",
+ lp_servicename(talloc_tos(), lp_sub, SNUM(conn)));
+ status = map_nt_error_from_unix(errno);
+ } else {
+ status = NT_STATUS_OK;
+ }
+ return status;
+}
+
+NTSTATUS smbd_do_setfsinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ const DATA_BLOB *pdata)
+{
+ switch (info_level) {
+ case SMB_FS_QUOTA_INFORMATION:
+ {
+ return smb_set_fsquota(conn,
+ req,
+ fsp,
+ pdata);
+ }
+
+ default:
+ break;
+ }
+ return NT_STATUS_INVALID_LEVEL;
+}
+
+/****************************************************************************
+ Store the FILE_UNIX_BASIC info.
+****************************************************************************/
+
+char *store_file_unix_basic(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ dev_t devno;
+
+ DBG_DEBUG("SMB_QUERY_FILE_UNIX_BASIC\n");
+ DBG_NOTICE("st_mode=%o\n", (int)psbuf->st_ex_mode);
+
+ SOFF_T(pdata,0,get_file_size_stat(psbuf)); /* File size 64 Bit */
+ pdata += 8;
+
+ SOFF_T(pdata,0,SMB_VFS_GET_ALLOC_SIZE(conn,fsp,psbuf)); /* Number of bytes used on disk - 64 Bit */
+ pdata += 8;
+
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER, pdata, &psbuf->st_ex_ctime); /* Change Time 64 Bit */
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER ,pdata+8, &psbuf->st_ex_atime); /* Last access time 64 Bit */
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER, pdata+16, &psbuf->st_ex_mtime); /* Last modification time 64 Bit */
+ pdata += 24;
+
+ SIVAL(pdata,0,psbuf->st_ex_uid); /* user id for the owner */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,psbuf->st_ex_gid); /* group id of owner */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,unix_filetype(psbuf->st_ex_mode));
+ pdata += 4;
+
+ if (S_ISBLK(psbuf->st_ex_mode) || S_ISCHR(psbuf->st_ex_mode)) {
+ devno = psbuf->st_ex_rdev;
+ } else {
+ devno = psbuf->st_ex_dev;
+ }
+
+ SIVAL(pdata,0,unix_dev_major(devno)); /* Major device number if type is device */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,unix_dev_minor(devno)); /* Minor device number if type is device */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SINO_T_VAL(pdata, 0, psbuf->st_ex_ino); /* inode number */
+ pdata += 8;
+
+ SIVAL(pdata,0, unix_perms_to_wire(psbuf->st_ex_mode)); /* Standard UNIX file permissions */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ SIVAL(pdata,0,psbuf->st_ex_nlink); /* number of hard links */
+ SIVAL(pdata,4,0);
+ pdata += 8;
+
+ return pdata;
+}
+
+/* Forward and reverse mappings from the UNIX_INFO2 file flags field and
+ * the chflags(2) (or equivalent) flags.
+ *
+ * XXX: this really should be behind the VFS interface. To do this, we would
+ * need to alter SMB_STRUCT_STAT so that it included a flags and a mask field.
+ * Each VFS module could then implement its own mapping as appropriate for the
+ * platform. We would then pass the SMB flags into SMB_VFS_CHFLAGS.
+ */
+static const struct {unsigned stat_fflag; unsigned smb_fflag;}
+ info2_flags_map[] =
+{
+#ifdef UF_NODUMP
+ { UF_NODUMP, EXT_DO_NOT_BACKUP },
+#endif
+
+#ifdef UF_IMMUTABLE
+ { UF_IMMUTABLE, EXT_IMMUTABLE },
+#endif
+
+#ifdef UF_APPEND
+ { UF_APPEND, EXT_OPEN_APPEND_ONLY },
+#endif
+
+#ifdef UF_HIDDEN
+ { UF_HIDDEN, EXT_HIDDEN },
+#endif
+
+ /* Do not remove. We need to guarantee that this array has at least one
+ * entry to build on HP-UX.
+ */
+ { 0, 0 }
+
+};
+
+static void map_info2_flags_from_sbuf(const SMB_STRUCT_STAT *psbuf,
+ uint32_t *smb_fflags, uint32_t *smb_fmask)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) {
+ *smb_fmask |= info2_flags_map[i].smb_fflag;
+ if (psbuf->st_ex_flags & info2_flags_map[i].stat_fflag) {
+ *smb_fflags |= info2_flags_map[i].smb_fflag;
+ }
+ }
+}
+
+bool map_info2_flags_to_sbuf(const SMB_STRUCT_STAT *psbuf,
+ const uint32_t smb_fflags,
+ const uint32_t smb_fmask,
+ int *stat_fflags)
+{
+ uint32_t max_fmask = 0;
+ size_t i;
+
+ *stat_fflags = psbuf->st_ex_flags;
+
+ /* For each flags requested in smb_fmask, check the state of the
+ * corresponding flag in smb_fflags and set or clear the matching
+ * stat flag.
+ */
+
+ for (i = 0; i < ARRAY_SIZE(info2_flags_map); ++i) {
+ max_fmask |= info2_flags_map[i].smb_fflag;
+ if (smb_fmask & info2_flags_map[i].smb_fflag) {
+ if (smb_fflags & info2_flags_map[i].smb_fflag) {
+ *stat_fflags |= info2_flags_map[i].stat_fflag;
+ } else {
+ *stat_fflags &= ~info2_flags_map[i].stat_fflag;
+ }
+ }
+ }
+
+ /* If smb_fmask is asking to set any bits that are not supported by
+ * our flag mappings, we should fail.
+ */
+ if ((smb_fmask & max_fmask) != smb_fmask) {
+ return False;
+ }
+
+ return True;
+}
+
+
+/* Just like SMB_QUERY_FILE_UNIX_BASIC, but with the addition
+ * of file flags and birth (create) time.
+ */
+char *store_file_unix_basic_info2(connection_struct *conn,
+ char *pdata,
+ files_struct *fsp,
+ const SMB_STRUCT_STAT *psbuf)
+{
+ uint32_t file_flags = 0;
+ uint32_t flags_mask = 0;
+
+ pdata = store_file_unix_basic(conn, pdata, fsp, psbuf);
+
+ /* Create (birth) time 64 bit */
+ put_long_date_full_timespec(TIMESTAMP_SET_NT_OR_BETTER,pdata, &psbuf->st_ex_btime);
+ pdata += 8;
+
+ map_info2_flags_from_sbuf(psbuf, &file_flags, &flags_mask);
+ SIVAL(pdata, 0, file_flags); /* flags */
+ SIVAL(pdata, 4, flags_mask); /* mask */
+ pdata += 8;
+
+ return pdata;
+}
+
+static NTSTATUS marshall_stream_info(unsigned int num_streams,
+ const struct stream_struct *streams,
+ char *data,
+ unsigned int max_data_bytes,
+ unsigned int *data_size)
+{
+ unsigned int i;
+ unsigned int ofs = 0;
+
+ if (max_data_bytes < 32) {
+ return NT_STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ for (i = 0; i < num_streams; i++) {
+ unsigned int next_offset;
+ size_t namelen;
+ smb_ucs2_t *namebuf;
+
+ if (!push_ucs2_talloc(talloc_tos(), &namebuf,
+ streams[i].name, &namelen) ||
+ namelen <= 2)
+ {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * name_buf is now null-terminated, we need to marshall as not
+ * terminated
+ */
+
+ namelen -= 2;
+
+ /*
+ * We cannot overflow ...
+ */
+ if ((ofs + 24 + namelen) > max_data_bytes) {
+ DEBUG(10, ("refusing to overflow reply at stream %u\n",
+ i));
+ TALLOC_FREE(namebuf);
+ return STATUS_BUFFER_OVERFLOW;
+ }
+
+ SIVAL(data, ofs+4, namelen);
+ SOFF_T(data, ofs+8, streams[i].size);
+ SOFF_T(data, ofs+16, streams[i].alloc_size);
+ memcpy(data+ofs+24, namebuf, namelen);
+ TALLOC_FREE(namebuf);
+
+ next_offset = ofs + 24 + namelen;
+
+ if (i == num_streams-1) {
+ SIVAL(data, ofs, 0);
+ }
+ else {
+ unsigned int align = ndr_align_size(next_offset, 8);
+
+ if ((next_offset + align) > max_data_bytes) {
+ DEBUG(10, ("refusing to overflow align "
+ "reply at stream %u\n",
+ i));
+ TALLOC_FREE(namebuf);
+ return STATUS_BUFFER_OVERFLOW;
+ }
+
+ memset(data+next_offset, 0, align);
+ next_offset += align;
+
+ SIVAL(data, ofs, next_offset - ofs);
+ ofs = next_offset;
+ }
+
+ ofs = next_offset;
+ }
+
+ DEBUG(10, ("max_data: %u, data_size: %u\n", max_data_bytes, ofs));
+
+ *data_size = ofs;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbd_do_qfilepathinfo(connection_struct *conn,
+ TALLOC_CTX *mem_ctx,
+ struct smb_request *req,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ bool delete_pending,
+ struct timespec write_time_ts,
+ struct ea_list *ea_list,
+ uint16_t flags2,
+ unsigned int max_data_bytes,
+ size_t *fixed_portion,
+ char **ppdata,
+ unsigned int *pdata_size)
+{
+ char *pdata = *ppdata;
+ char *dstart, *dend;
+ unsigned int data_size;
+ struct timespec create_time_ts, mtime_ts, atime_ts, ctime_ts;
+ SMB_STRUCT_STAT *psbuf = NULL;
+ SMB_STRUCT_STAT *base_sp = NULL;
+ char *p;
+ char *base_name;
+ char *dos_fname;
+ int mode;
+ int nlink;
+ NTSTATUS status;
+ uint64_t file_size = 0;
+ uint64_t pos = 0;
+ uint64_t allocation_size = 0;
+ uint64_t file_id = 0;
+ uint32_t access_mask = 0;
+ size_t len = 0;
+
+ if (INFO_LEVEL_IS_UNIX(info_level)) {
+ bool ok = false;
+
+ if (lp_smb1_unix_extensions() && req->posix_pathnames) {
+ DBG_DEBUG("SMB1 unix extensions activated\n");
+ ok = true;
+ }
+
+ if (conn->sconn->using_smb2 &&
+ (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN))
+ {
+ DBG_DEBUG("SMB2 posix open\n");
+ ok = true;
+ }
+
+ if (!ok) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+ }
+
+ DBG_INFO("%s (%s) level=%d max_data=%u\n",
+ smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ info_level, max_data_bytes);
+
+ /*
+ * In case of querying a symlink in POSIX context,
+ * fsp will be NULL. fdos_mode() deals with it.
+ */
+ if (fsp != NULL) {
+ smb_fname = fsp->fsp_name;
+ }
+ mode = fdos_mode(fsp);
+ psbuf = &smb_fname->st;
+
+ if (fsp != NULL) {
+ base_sp = fsp->base_fsp ?
+ &fsp->base_fsp->fsp_name->st :
+ &fsp->fsp_name->st;
+ } else {
+ base_sp = &smb_fname->st;
+ }
+
+ nlink = psbuf->st_ex_nlink;
+
+ if (nlink && (mode&FILE_ATTRIBUTE_DIRECTORY)) {
+ nlink = 1;
+ }
+
+ if ((nlink > 0) && delete_pending) {
+ nlink -= 1;
+ }
+
+ if (max_data_bytes + DIR_ENTRY_SAFETY_MARGIN < max_data_bytes) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ data_size = max_data_bytes + DIR_ENTRY_SAFETY_MARGIN;
+ *ppdata = (char *)SMB_REALLOC(*ppdata, data_size);
+ if (*ppdata == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ pdata = *ppdata;
+ dstart = pdata;
+ dend = dstart + data_size - 1;
+
+ if (!is_omit_timespec(&write_time_ts) &&
+ !INFO_LEVEL_IS_UNIX(info_level))
+ {
+ update_stat_ex_mtime(psbuf, write_time_ts);
+ }
+
+ create_time_ts = get_create_timespec(conn, fsp, smb_fname);
+ mtime_ts = psbuf->st_ex_mtime;
+ atime_ts = psbuf->st_ex_atime;
+ ctime_ts = get_change_timespec(conn, fsp, smb_fname);
+
+ if (lp_dos_filetime_resolution(SNUM(conn))) {
+ dos_filetime_timespec(&create_time_ts);
+ dos_filetime_timespec(&mtime_ts);
+ dos_filetime_timespec(&atime_ts);
+ dos_filetime_timespec(&ctime_ts);
+ }
+
+ p = strrchr_m(smb_fname->base_name,'/');
+ if (p == NULL) {
+ base_name = smb_fname->base_name;
+ } else {
+ base_name = p+1;
+ }
+
+ /* NT expects the name to be in an exact form of the *full*
+ filename. See the trans2 torture test */
+ if (ISDOT(base_name)) {
+ dos_fname = talloc_strdup(mem_ctx, "\\");
+ if (!dos_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ dos_fname = talloc_asprintf(mem_ctx,
+ "\\%s",
+ smb_fname->base_name);
+ if (!dos_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (is_named_stream(smb_fname)) {
+ dos_fname = talloc_asprintf(dos_fname, "%s",
+ smb_fname->stream_name);
+ if (!dos_fname) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ string_replace(dos_fname, '/', '\\');
+ }
+
+ allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, fsp, psbuf);
+
+ if (fsp == NULL || !fsp->fsp_flags.is_fsa) {
+ /* Do we have this path open ? */
+ struct file_id fileid = vfs_file_id_from_sbuf(conn, psbuf);
+ files_struct *fsp1 = file_find_di_first(
+ conn->sconn, fileid, true);
+ if (fsp1 && fsp1->initial_allocation_size) {
+ allocation_size = SMB_VFS_GET_ALLOC_SIZE(conn, fsp1, psbuf);
+ }
+ }
+
+ if (!(mode & FILE_ATTRIBUTE_DIRECTORY)) {
+ file_size = get_file_size_stat(psbuf);
+ }
+
+ if (fsp) {
+ pos = fh_get_position_information(fsp->fh);
+ }
+
+ if (fsp) {
+ access_mask = fsp->access_mask;
+ } else {
+ /* GENERIC_EXECUTE mapping from Windows */
+ access_mask = 0x12019F;
+ }
+
+ /* This should be an index number - looks like
+ dev/ino to me :-)
+
+ I think this causes us to fail the IFSKIT
+ BasicFileInformationTest. -tpot */
+ file_id = SMB_VFS_FS_FILE_ID(conn, base_sp);
+
+ *fixed_portion = 0;
+
+ switch (info_level) {
+ case SMB_INFO_STANDARD:
+ DBG_DEBUG("SMB_INFO_STANDARD\n");
+ data_size = 22;
+ srv_put_dos_date2_ts(pdata,
+ l1_fdateCreation,
+ create_time_ts);
+ srv_put_dos_date2_ts(pdata,
+ l1_fdateLastAccess,
+ atime_ts);
+ srv_put_dos_date2_ts(pdata,
+ l1_fdateLastWrite,
+ mtime_ts); /* write time */
+ SIVAL(pdata,l1_cbFile,(uint32_t)file_size);
+ SIVAL(pdata,l1_cbFileAlloc,(uint32_t)allocation_size);
+ SSVAL(pdata,l1_attrFile,mode);
+ break;
+
+ case SMB_INFO_QUERY_EA_SIZE:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB_INFO_QUERY_EA_SIZE\n");
+ data_size = 26;
+ srv_put_dos_date2_ts(pdata, 0, create_time_ts);
+ srv_put_dos_date2_ts(pdata, 4, atime_ts);
+ srv_put_dos_date2_ts(pdata,
+ 8,
+ mtime_ts); /* write time */
+ SIVAL(pdata,12,(uint32_t)file_size);
+ SIVAL(pdata,16,(uint32_t)allocation_size);
+ SSVAL(pdata,20,mode);
+ SIVAL(pdata,22,ea_size);
+ break;
+ }
+
+ case SMB_INFO_IS_NAME_VALID:
+ DBG_DEBUG("SMB_INFO_IS_NAME_VALID\n");
+ if (fsp) {
+ /* os/2 needs this ? really ?*/
+ return NT_STATUS_DOS(ERRDOS, ERRbadfunc);
+ }
+ /* This is only reached for qpathinfo */
+ data_size = 0;
+ break;
+
+ case SMB_INFO_QUERY_EAS_FROM_LIST:
+ {
+ size_t total_ea_len = 0;
+ struct ea_list *ea_file_list = NULL;
+ DBG_DEBUG("SMB_INFO_QUERY_EAS_FROM_LIST\n");
+
+ status =
+ get_ea_list_from_fsp(mem_ctx,
+ smb_fname->fsp,
+ &total_ea_len, &ea_file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ea_list = ea_list_union(ea_list, ea_file_list, &total_ea_len);
+
+ if (!ea_list || (total_ea_len > data_size)) {
+ data_size = 4;
+ SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */
+ break;
+ }
+
+ data_size = fill_ea_buffer(mem_ctx, pdata, data_size, conn, ea_list);
+ break;
+ }
+
+ case SMB_INFO_QUERY_ALL_EAS:
+ {
+ /* We have data_size bytes to put EA's into. */
+ size_t total_ea_len = 0;
+ DBG_DEBUG(" SMB_INFO_QUERY_ALL_EAS\n");
+
+ status = get_ea_list_from_fsp(mem_ctx,
+ smb_fname->fsp,
+ &total_ea_len, &ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!ea_list || (total_ea_len > data_size)) {
+ data_size = 4;
+ SIVAL(pdata,0,4); /* EA List Length must be set to 4 if no EA's. */
+ break;
+ }
+
+ data_size = fill_ea_buffer(mem_ctx, pdata, data_size, conn, ea_list);
+ break;
+ }
+
+ case SMB2_FILE_FULL_EA_INFORMATION:
+ {
+ /* We have data_size bytes to put EA's into. */
+ size_t total_ea_len = 0;
+ struct ea_list *ea_file_list = NULL;
+
+ DBG_DEBUG("SMB2_INFO_QUERY_ALL_EAS\n");
+
+ /*TODO: add filtering and index handling */
+
+ status =
+ get_ea_list_from_fsp(mem_ctx,
+ smb_fname->fsp,
+ &total_ea_len, &ea_file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!ea_file_list) {
+ return NT_STATUS_NO_EAS_ON_FILE;
+ }
+
+ status = fill_ea_chained_buffer(mem_ctx,
+ pdata,
+ data_size,
+ &data_size,
+ conn, ea_file_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ break;
+ }
+
+ case SMB_FILE_BASIC_INFORMATION:
+ case SMB_QUERY_FILE_BASIC_INFO:
+
+ if (info_level == SMB_QUERY_FILE_BASIC_INFO) {
+ DBG_DEBUG("SMB_QUERY_FILE_BASIC_INFO\n");
+ data_size = 36; /* w95 returns 40 bytes not 36 - why ?. */
+ } else {
+ DBG_DEBUG("SMB_FILE_BASIC_INFORMATION\n");
+ data_size = 40;
+ SIVAL(pdata,36,0);
+ }
+ put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */
+ SIVAL(pdata,32,mode);
+
+ DBG_INFO("SMB_QFBI - create: %s access: %s "
+ "write: %s change: %s mode: %x\n",
+ ctime(&create_time_ts.tv_sec),
+ ctime(&atime_ts.tv_sec),
+ ctime(&mtime_ts.tv_sec),
+ ctime(&ctime_ts.tv_sec),
+ mode);
+ *fixed_portion = data_size;
+ break;
+
+ case SMB_FILE_STANDARD_INFORMATION:
+ case SMB_QUERY_FILE_STANDARD_INFO:
+
+ DBG_DEBUG("SMB_FILE_STANDARD_INFORMATION\n");
+ data_size = 24;
+ SOFF_T(pdata,0,allocation_size);
+ SOFF_T(pdata,8,file_size);
+ SIVAL(pdata,16,nlink);
+ SCVAL(pdata,20,delete_pending?1:0);
+ SCVAL(pdata,21,(mode&FILE_ATTRIBUTE_DIRECTORY)?1:0);
+ SSVAL(pdata,22,0); /* Padding. */
+ *fixed_portion = 24;
+ break;
+
+ case SMB_FILE_EA_INFORMATION:
+ case SMB_QUERY_FILE_EA_INFO:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB_FILE_EA_INFORMATION\n");
+ data_size = 4;
+ *fixed_portion = 4;
+ SIVAL(pdata,0,ea_size);
+ break;
+ }
+
+ /* Get the 8.3 name - used if NT SMB was negotiated. */
+ case SMB_QUERY_FILE_ALT_NAME_INFO:
+ case SMB_FILE_ALTERNATE_NAME_INFORMATION:
+ {
+ char mangled_name[13];
+ DBG_DEBUG("SMB_FILE_ALTERNATE_NAME_INFORMATION\n");
+ if (!name_to_8_3(base_name,mangled_name,
+ True,conn->params)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = srvstr_push(dstart, flags2,
+ pdata+4, mangled_name,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ *fixed_portion = 8;
+ break;
+ }
+
+ case SMB_QUERY_FILE_NAME_INFO:
+ {
+ /*
+ this must be *exactly* right for ACLs on mapped drives to work
+ */
+ status = srvstr_push(dstart, flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ DBG_DEBUG("SMB_QUERY_FILE_NAME_INFO\n");
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ break;
+ }
+
+ case SMB_FILE_NORMALIZED_NAME_INFORMATION:
+ {
+ char *nfname = NULL;
+
+ if (fsp == NULL || !fsp->conn->sconn->using_smb2) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ nfname = talloc_strdup(mem_ctx, smb_fname->base_name);
+ if (nfname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ISDOT(nfname)) {
+ nfname[0] = '\0';
+ }
+ string_replace(nfname, '/', '\\');
+
+ if (fsp_is_alternate_stream(fsp)) {
+ const char *s = smb_fname->stream_name;
+ const char *e = NULL;
+ size_t n;
+
+ SMB_ASSERT(s[0] != '\0');
+
+ /*
+ * smb_fname->stream_name is in form
+ * of ':StrEam:$DATA', but we should only
+ * append ':StrEam' here.
+ */
+
+ e = strchr(&s[1], ':');
+ if (e == NULL) {
+ n = strlen(s);
+ } else {
+ n = PTR_DIFF(e, s);
+ }
+ nfname = talloc_strndup_append(nfname, s, n);
+ if (nfname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ status = srvstr_push(dstart, flags2,
+ pdata+4, nfname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ DBG_DEBUG("SMB_FILE_NORMALIZED_NAME_INFORMATION\n");
+ data_size = 4 + len;
+ SIVAL(pdata,0,len);
+ *fixed_portion = 8;
+ break;
+ }
+
+ case SMB_FILE_ALLOCATION_INFORMATION:
+ case SMB_QUERY_FILE_ALLOCATION_INFO:
+ DBG_DEBUG("SMB_FILE_ALLOCATION_INFORMATION\n");
+ data_size = 8;
+ SOFF_T(pdata,0,allocation_size);
+ break;
+
+ case SMB_FILE_END_OF_FILE_INFORMATION:
+ case SMB_QUERY_FILE_END_OF_FILEINFO:
+ DBG_DEBUG("SMB_FILE_END_OF_FILE_INFORMATION\n");
+ data_size = 8;
+ SOFF_T(pdata,0,file_size);
+ break;
+
+ case SMB_QUERY_FILE_ALL_INFO:
+ case SMB_FILE_ALL_INFORMATION:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB_FILE_ALL_INFORMATION\n");
+ put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */
+ SIVAL(pdata,32,mode);
+ SIVAL(pdata,36,0); /* padding. */
+ pdata += 40;
+ SOFF_T(pdata,0,allocation_size);
+ SOFF_T(pdata,8,file_size);
+ SIVAL(pdata,16,nlink);
+ SCVAL(pdata,20,delete_pending);
+ SCVAL(pdata,21,(mode&FILE_ATTRIBUTE_DIRECTORY)?1:0);
+ SSVAL(pdata,22,0);
+ pdata += 24;
+ SIVAL(pdata,0,ea_size);
+ pdata += 4; /* EA info */
+ status = srvstr_push(dstart, flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,0,len);
+ pdata += 4 + len;
+ data_size = PTR_DIFF(pdata,(*ppdata));
+ *fixed_portion = 10;
+ break;
+ }
+
+ case SMB2_FILE_ALL_INFORMATION:
+ {
+ unsigned int ea_size =
+ estimate_ea_size(smb_fname->fsp);
+ DBG_DEBUG("SMB2_FILE_ALL_INFORMATION\n");
+ put_long_date_full_timespec(conn->ts_res,pdata+0x00,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+0x08,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+0x10,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+0x18,&ctime_ts); /* change time */
+ SIVAL(pdata, 0x20, mode);
+ SIVAL(pdata, 0x24, 0); /* padding. */
+ SBVAL(pdata, 0x28, allocation_size);
+ SBVAL(pdata, 0x30, file_size);
+ SIVAL(pdata, 0x38, nlink);
+ SCVAL(pdata, 0x3C, delete_pending);
+ SCVAL(pdata, 0x3D, (mode&FILE_ATTRIBUTE_DIRECTORY)?1:0);
+ SSVAL(pdata, 0x3E, 0); /* padding */
+ SBVAL(pdata, 0x40, file_id);
+ SIVAL(pdata, 0x48, ea_size);
+ SIVAL(pdata, 0x4C, access_mask);
+ SBVAL(pdata, 0x50, pos);
+ SIVAL(pdata, 0x58, mode); /*TODO: mode != mode fix this!!! */
+ SIVAL(pdata, 0x5C, 0); /* No alignment needed. */
+
+ pdata += 0x60;
+
+ status = srvstr_push(dstart, flags2,
+ pdata+4, dos_fname,
+ PTR_DIFF(dend, pdata+4),
+ STR_UNICODE, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ SIVAL(pdata,0,len);
+ pdata += 4 + len;
+ data_size = PTR_DIFF(pdata,(*ppdata));
+ *fixed_portion = 104;
+ break;
+ }
+ case SMB_FILE_INTERNAL_INFORMATION:
+
+ DBG_DEBUG("SMB_FILE_INTERNAL_INFORMATION\n");
+ SBVAL(pdata, 0, file_id);
+ data_size = 8;
+ *fixed_portion = 8;
+ break;
+
+ case SMB_FILE_ACCESS_INFORMATION:
+ DBG_DEBUG("SMB_FILE_ACCESS_INFORMATION\n");
+ SIVAL(pdata, 0, access_mask);
+ data_size = 4;
+ *fixed_portion = 4;
+ break;
+
+ case SMB_FILE_NAME_INFORMATION:
+ /* Pathname with leading '\'. */
+ {
+ size_t byte_len;
+ byte_len = dos_PutUniCode(pdata+4,dos_fname,(size_t)max_data_bytes,False);
+ DBG_DEBUG("SMB_FILE_NAME_INFORMATION\n");
+ SIVAL(pdata,0,byte_len);
+ data_size = 4 + byte_len;
+ break;
+ }
+
+ case SMB_FILE_DISPOSITION_INFORMATION:
+ DBG_DEBUG("SMB_FILE_DISPOSITION_INFORMATION\n");
+ data_size = 1;
+ SCVAL(pdata,0,delete_pending);
+ *fixed_portion = 1;
+ break;
+
+ case SMB_FILE_POSITION_INFORMATION:
+ DBG_DEBUG("SMB_FILE_POSITION_INFORMATION\n");
+ data_size = 8;
+ SOFF_T(pdata,0,pos);
+ *fixed_portion = 8;
+ break;
+
+ case SMB_FILE_MODE_INFORMATION:
+ DBG_DEBUG("SMB_FILE_MODE_INFORMATION\n");
+ SIVAL(pdata,0,mode);
+ data_size = 4;
+ *fixed_portion = 4;
+ break;
+
+ case SMB_FILE_ALIGNMENT_INFORMATION:
+ DBG_DEBUG("SMB_FILE_ALIGNMENT_INFORMATION\n");
+ SIVAL(pdata,0,0); /* No alignment needed. */
+ data_size = 4;
+ *fixed_portion = 4;
+ break;
+
+ /*
+ * NT4 server just returns "invalid query" to this - if we try
+ * to answer it then NTws gets a BSOD! (tridge). W2K seems to
+ * want this. JRA.
+ */
+ /* The first statement above is false - verified using Thursby
+ * client against NT4 -- gcolley.
+ */
+ case SMB_QUERY_FILE_STREAM_INFO:
+ case SMB_FILE_STREAM_INFORMATION: {
+ unsigned int num_streams = 0;
+ struct stream_struct *streams = NULL;
+
+ DBG_DEBUG("SMB_FILE_STREAM_INFORMATION\n");
+
+ if (is_ntfs_stream_smb_fname(smb_fname)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = vfs_fstreaminfo(fsp,
+ mem_ctx,
+ &num_streams,
+ &streams);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("could not get stream info: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = marshall_stream_info(num_streams, streams,
+ pdata, max_data_bytes,
+ &data_size);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("marshall_stream_info failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(streams);
+ return status;
+ }
+
+ TALLOC_FREE(streams);
+
+ *fixed_portion = 32;
+
+ break;
+ }
+ case SMB_QUERY_COMPRESSION_INFO:
+ case SMB_FILE_COMPRESSION_INFORMATION:
+ DBG_DEBUG("SMB_FILE_COMPRESSION_INFORMATION\n");
+ SOFF_T(pdata,0,file_size);
+ SIVAL(pdata,8,0); /* ??? */
+ SIVAL(pdata,12,0); /* ??? */
+ data_size = 16;
+ *fixed_portion = 16;
+ break;
+
+ case SMB_FILE_NETWORK_OPEN_INFORMATION:
+ DBG_DEBUG("SMB_FILE_NETWORK_OPEN_INFORMATION\n");
+ put_long_date_full_timespec(conn->ts_res,pdata,&create_time_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+8,&atime_ts);
+ put_long_date_full_timespec(conn->ts_res,pdata+16,&mtime_ts); /* write time */
+ put_long_date_full_timespec(conn->ts_res,pdata+24,&ctime_ts); /* change time */
+ SOFF_T(pdata,32,allocation_size);
+ SOFF_T(pdata,40,file_size);
+ SIVAL(pdata,48,mode);
+ SIVAL(pdata,52,0); /* ??? */
+ data_size = 56;
+ *fixed_portion = 56;
+ break;
+
+ case SMB_FILE_ATTRIBUTE_TAG_INFORMATION:
+ DBG_DEBUG(" SMB_FILE_ATTRIBUTE_TAG_INFORMATION\n");
+ SIVAL(pdata,0,mode);
+ SIVAL(pdata,4,0);
+ data_size = 8;
+ *fixed_portion = 8;
+ break;
+
+ /*
+ * SMB2 UNIX Extensions.
+ */
+ case SMB2_FILE_POSIX_INFORMATION_INTERNAL:
+ {
+ struct smb3_file_posix_information info = {};
+ uint8_t buf[sizeof(info)];
+ struct ndr_push ndr = {
+ .data = buf,
+ .alloc_size = sizeof(buf),
+ .fixed_buf_size = true,
+ };
+ enum ndr_err_code ndr_err;
+
+ if (!(conn->sconn->using_smb2)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ if (!(fsp->posix_flags & FSP_POSIX_FLAGS_OPEN)) {
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ smb3_file_posix_information_init(
+ conn, &smb_fname->st, 0, mode, &info);
+
+ ndr_err = ndr_push_smb3_file_posix_information(
+ &ndr, NDR_SCALARS|NDR_BUFFERS, &info);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ memcpy(pdata, buf, ndr.offset);
+ data_size = ndr.offset;
+ break;
+ }
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ *pdata_size = data_size;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Set a hard link (called by UNIX extensions and by NT rename with HARD link
+ code.
+****************************************************************************/
+
+NTSTATUS hardlink_internals(TALLOC_CTX *ctx,
+ connection_struct *conn,
+ struct smb_request *req,
+ bool overwrite_if_exists,
+ const struct smb_filename *smb_fname_old,
+ struct smb_filename *smb_fname_new)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ int ret;
+ bool ok;
+ struct smb_filename *parent_fname_old = NULL;
+ struct smb_filename *base_name_old = NULL;
+ struct smb_filename *parent_fname_new = NULL;
+ struct smb_filename *base_name_new = NULL;
+
+ /* source must already exist. */
+ if (!VALID_STAT(smb_fname_old->st)) {
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto out;
+ }
+
+ /* No links from a directory. */
+ if (S_ISDIR(smb_fname_old->st.st_ex_mode)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto out;
+ }
+
+ /* Setting a hardlink to/from a stream isn't currently supported. */
+ ok = is_ntfs_stream_smb_fname(smb_fname_old);
+ if (ok) {
+ DBG_DEBUG("Old name has streams\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+ ok = is_ntfs_stream_smb_fname(smb_fname_new);
+ if (ok) {
+ DBG_DEBUG("New name has streams\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (smb_fname_old->twrp != 0) {
+ status = NT_STATUS_NOT_SAME_DEVICE;
+ goto out;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname_old,
+ &parent_fname_old,
+ &base_name_old);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = parent_pathref(talloc_tos(),
+ conn->cwd_fsp,
+ smb_fname_new,
+ &parent_fname_new,
+ &base_name_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ if (VALID_STAT(smb_fname_new->st)) {
+ if (overwrite_if_exists) {
+ if (S_ISDIR(smb_fname_new->st.st_ex_mode)) {
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ goto out;
+ }
+ status = unlink_internals(conn,
+ req,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL, /* new_dirfsp */
+ smb_fname_new);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ } else {
+ /* Disallow if newname already exists. */
+ status = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto out;
+ }
+ }
+
+ DEBUG(10,("hardlink_internals: doing hard link %s -> %s\n",
+ smb_fname_old->base_name, smb_fname_new->base_name));
+
+ ret = SMB_VFS_LINKAT(conn,
+ parent_fname_old->fsp,
+ base_name_old,
+ parent_fname_new->fsp,
+ base_name_new,
+ 0);
+
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DEBUG(3,("hardlink_internals: Error %s hard link %s -> %s\n",
+ nt_errstr(status), smb_fname_old->base_name,
+ smb_fname_new->base_name));
+ }
+
+ out:
+
+ TALLOC_FREE(parent_fname_old);
+ TALLOC_FREE(parent_fname_new);
+ return status;
+}
+
+/****************************************************************************
+ Deal with setting the time from any of the setfilepathinfo functions.
+ NOTE !!!! The check for FILE_WRITE_ATTRIBUTES access must be done *before*
+ calling this function.
+****************************************************************************/
+
+NTSTATUS smb_set_file_time(connection_struct *conn,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ struct smb_file_time *ft,
+ bool setting_write_time)
+{
+ struct files_struct *set_fsp = NULL;
+ struct timeval_buf tbuf[4];
+ uint32_t action =
+ FILE_NOTIFY_CHANGE_LAST_ACCESS
+ |FILE_NOTIFY_CHANGE_LAST_WRITE
+ |FILE_NOTIFY_CHANGE_CREATION;
+ int ret;
+
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (fsp == NULL) {
+ /* A symlink */
+ return NT_STATUS_OK;
+ }
+
+ set_fsp = metadata_fsp(fsp);
+
+ /* get some defaults (no modifications) if any info is zero or -1. */
+ if (is_omit_timespec(&ft->create_time)) {
+ action &= ~FILE_NOTIFY_CHANGE_CREATION;
+ }
+
+ if (is_omit_timespec(&ft->atime)) {
+ action &= ~FILE_NOTIFY_CHANGE_LAST_ACCESS;
+ }
+
+ if (is_omit_timespec(&ft->mtime)) {
+ action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+
+ if (!setting_write_time) {
+ /* ft->mtime comes from change time, not write time. */
+ action &= ~FILE_NOTIFY_CHANGE_LAST_WRITE;
+ }
+
+ /* Ensure the resolution is the correct for
+ * what we can store on this filesystem. */
+
+ round_timespec(conn->ts_res, &ft->create_time);
+ round_timespec(conn->ts_res, &ft->ctime);
+ round_timespec(conn->ts_res, &ft->atime);
+ round_timespec(conn->ts_res, &ft->mtime);
+
+ DBG_DEBUG("smb_set_filetime: actime: %s\n ",
+ timespec_string_buf(&ft->atime, true, &tbuf[0]));
+ DBG_DEBUG("smb_set_filetime: modtime: %s\n ",
+ timespec_string_buf(&ft->mtime, true, &tbuf[1]));
+ DBG_DEBUG("smb_set_filetime: ctime: %s\n ",
+ timespec_string_buf(&ft->ctime, true, &tbuf[2]));
+ DBG_DEBUG("smb_set_file_time: createtime: %s\n ",
+ timespec_string_buf(&ft->create_time, true, &tbuf[3]));
+
+ if (setting_write_time) {
+ /*
+ * This was a Windows setfileinfo on an open file.
+ * NT does this a lot. We also need to
+ * set the time here, as it can be read by
+ * FindFirst/FindNext and with the patch for bug #2045
+ * in smbd/fileio.c it ensures that this timestamp is
+ * kept sticky even after a write. We save the request
+ * away and will set it on file close and after a write. JRA.
+ */
+
+ DBG_DEBUG("setting pending modtime to %s\n",
+ timespec_string_buf(&ft->mtime, true, &tbuf[0]));
+
+ if (set_fsp != NULL) {
+ set_sticky_write_time_fsp(set_fsp, ft->mtime);
+ } else {
+ set_sticky_write_time_path(
+ vfs_file_id_from_sbuf(conn, &smb_fname->st),
+ ft->mtime);
+ }
+ }
+
+ DEBUG(10,("smb_set_file_time: setting utimes to modified values.\n"));
+
+ ret = file_ntimes(conn, set_fsp, ft);
+ if (ret != 0) {
+ return map_nt_error_from_unix(errno);
+ }
+
+ notify_fname(conn, NOTIFY_ACTION_MODIFIED, action,
+ smb_fname->base_name);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with setting the dosmode from any of the setfilepathinfo functions.
+ NB. The check for FILE_WRITE_ATTRIBUTES access on this path must have been
+ done before calling this function.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_dosmode(connection_struct *conn,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ struct files_struct *dos_fsp = NULL;
+ uint32_t current_dosmode;
+ int ret;
+
+ if (!VALID_STAT(fsp->fsp_name->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ dos_fsp = metadata_fsp(fsp);
+
+ if (dosmode != 0) {
+ if (S_ISDIR(fsp->fsp_name->st.st_ex_mode)) {
+ dosmode |= FILE_ATTRIBUTE_DIRECTORY;
+ } else {
+ dosmode &= ~FILE_ATTRIBUTE_DIRECTORY;
+ }
+ }
+
+ DBG_DEBUG("dosmode: 0x%" PRIx32 "\n", dosmode);
+
+ /* check the mode isn't different, before changing it */
+ if (dosmode == 0) {
+ return NT_STATUS_OK;
+ }
+ current_dosmode = fdos_mode(dos_fsp);
+ if (dosmode == current_dosmode) {
+ return NT_STATUS_OK;
+ }
+
+ DBG_DEBUG("file %s : setting dos mode 0x%" PRIx32 "\n",
+ fsp_str_dbg(dos_fsp), dosmode);
+
+ ret = file_set_dosmode(conn, dos_fsp->fsp_name, dosmode, NULL, false);
+ if (ret != 0) {
+ DBG_WARNING("file_set_dosmode of %s failed: %s\n",
+ fsp_str_dbg(dos_fsp), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with setting the size from any of the setfilepathinfo functions.
+****************************************************************************/
+
+NTSTATUS smb_set_file_size(connection_struct *conn,
+ struct smb_request *req,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ const SMB_STRUCT_STAT *psbuf,
+ off_t size,
+ bool fail_after_createfile)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *new_fsp = NULL;
+
+ if (!VALID_STAT(*psbuf)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DBG_INFO("size: %"PRIu64", file_size_stat=%"PRIu64"\n",
+ (uint64_t)size,
+ get_file_size_stat(psbuf));
+
+ if (size == get_file_size_stat(psbuf)) {
+ if (fsp == NULL) {
+ return NT_STATUS_OK;
+ }
+ if (!fsp->fsp_flags.modified) {
+ return NT_STATUS_OK;
+ }
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(10,("smb_set_file_size: file %s : setting new size to %.0f\n",
+ smb_fname_str_dbg(smb_fname), (double)size));
+
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ /* Handle based call. */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (vfs_set_filelen(fsp, size) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_WRITE_DATA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &new_fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* NB. We check for open_was_deferred in the caller. */
+ return status;
+ }
+
+ /* See RAW-SFILEINFO-END-OF-FILE */
+ if (fail_after_createfile) {
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (vfs_set_filelen(new_fsp, size) == -1) {
+ status = map_nt_error_from_unix(errno);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return status;
+ }
+
+ trigger_write_time_update_immediate(new_fsp);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_INFO_SET_EA.
+****************************************************************************/
+
+static NTSTATUS smb_info_set_ea(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ struct ea_list *ea_list = NULL;
+ TALLOC_CTX *ctx = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (total_data < 10) {
+
+ /* OS/2 workplace shell seems to send SET_EA requests of "null"
+ length. They seem to have no effect. Bug #3212. JRA */
+
+ if ((total_data == 4) && (IVAL(pdata,0) == 4)) {
+ /* We're done. We only get EA info in this call. */
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (IVAL(pdata,0) > total_data) {
+ DEBUG(10,("smb_info_set_ea: bad total data size (%u) > %u\n",
+ IVAL(pdata,0), (unsigned int)total_data));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx = talloc_tos();
+ ea_list = read_ea_list(ctx, pdata + 4, total_data - 4);
+ if (!ea_list) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ /*
+ * The only way fsp can be NULL here is if
+ * smb_fname points at a symlink and
+ * and we're in POSIX context.
+ * Ensure this is the case.
+ *
+ * In this case we cannot set the EA.
+ */
+ SMB_ASSERT(smb_fname->flags & SMB_FILENAME_POSIX_PATH);
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ status = set_ea(conn, fsp, ea_list);
+
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_FULL_EA_INFORMATION set.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_full_ea_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ struct ea_list *ea_list = NULL;
+ NTSTATUS status;
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (!lp_ea_support(SNUM(conn))) {
+ DEBUG(10, ("smb_set_file_full_ea_info - ea_len = %u but "
+ "EA's not supported.\n",
+ (unsigned int)total_data));
+ return NT_STATUS_EAS_NOT_SUPPORTED;
+ }
+
+ if (total_data < 10) {
+ DEBUG(10, ("smb_set_file_full_ea_info - ea_len = %u "
+ "too small.\n",
+ (unsigned int)total_data));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ea_list = read_nttrans_ea_list(talloc_tos(),
+ pdata,
+ total_data);
+
+ if (!ea_list) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = set_ea(conn, fsp, ea_list);
+
+ DEBUG(10, ("smb_set_file_full_ea_info on file %s returned %s\n",
+ smb_fname_str_dbg(fsp->fsp_name),
+ nt_errstr(status) ));
+
+ return status;
+}
+
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_DISPOSITION_INFO.
+****************************************************************************/
+
+NTSTATUS smb_set_file_disposition_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ NTSTATUS status = NT_STATUS_OK;
+ bool delete_on_close;
+ uint32_t dosmode = 0;
+
+ if (total_data < 1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ delete_on_close = (CVAL(pdata,0) ? True : False);
+ dosmode = fdos_mode(fsp);
+
+ DEBUG(10,("smb_set_file_disposition_info: file %s, dosmode = %u, "
+ "delete_on_close = %u\n",
+ smb_fname_str_dbg(smb_fname),
+ (unsigned int)dosmode,
+ (unsigned int)delete_on_close ));
+
+ if (delete_on_close) {
+ status = can_set_delete_on_close(fsp, dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ /* The set is across all open files on this dev/inode pair. */
+ if (!set_delete_on_close(fsp, delete_on_close,
+ conn->session_info->security_token,
+ conn->session_info->unix_token)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_POSITION_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_position_information(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp)
+{
+ uint64_t position_information;
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ /* Ignore on pathname based set. */
+ return NT_STATUS_OK;
+ }
+
+ position_information = (uint64_t)IVAL(pdata,0);
+ position_information |= (((uint64_t)IVAL(pdata,4)) << 32);
+
+ DEBUG(10,("smb_file_position_information: Set file position "
+ "information for file %s to %.0f\n", fsp_str_dbg(fsp),
+ (double)position_information));
+ fh_set_position_information(fsp->fh, position_information);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_FILE_MODE_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_mode_information(connection_struct *conn,
+ const char *pdata,
+ int total_data)
+{
+ uint32_t mode;
+
+ if (total_data < 4) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ mode = IVAL(pdata,0);
+ if (mode != 0 && mode != 2 && mode != 4 && mode != 6) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB2_FILE_RENAME_INFORMATION_INTERNAL
+****************************************************************************/
+
+static NTSTATUS smb2_file_rename_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ const char *dst_original_lcomp = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTSTATUS status = NT_STATUS_OK;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!fsp) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data < 20) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) ? True : False);
+ len = IVAL(pdata,16);
+
+ if (len > (total_data - 20) || (len == 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ (void)srvstr_pull_talloc(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE);
+
+ if (newname == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* SMB2 rename paths are never DFS. */
+ req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = check_path_syntax(newname,
+ fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb2_file_rename_information: got name |%s|\n",
+ newname));
+
+ if (newname[0] == ':') {
+ /* Create an smb_fname to call rename_internals_fsp() with. */
+ smb_fname_dst = synthetic_smb_fname(talloc_tos(),
+ fsp->base_fsp->fsp_name->base_name,
+ newname,
+ NULL,
+ fsp->base_fsp->fsp_name->twrp,
+ fsp->base_fsp->fsp_name->flags);
+ if (smb_fname_dst == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ } else {
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags,
+ 0, /* Never a TWRP. */
+ &dst_dirfsp,
+ &smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ /*
+ * Set the original last component, since
+ * rename_internals_fsp() requires it.
+ */
+ dst_original_lcomp = get_original_lcomp(smb_fname_dst,
+ conn,
+ newname,
+ ucf_flags);
+ if (dst_original_lcomp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ DEBUG(10,("smb2_file_rename_information: "
+ "SMB_FILE_RENAME_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = rename_internals_fsp(conn,
+ fsp,
+ smb_fname_dst,
+ dst_original_lcomp,
+ (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM),
+ overwrite);
+
+ out:
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+static NTSTATUS smb2_file_link_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ size_t ret;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!fsp) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data < 20) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) ? true : false);
+ len = IVAL(pdata,16);
+
+ if (len > (total_data - 20) || (len == 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ret = srvstr_pull_talloc(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE);
+
+ if (ret == (size_t)-1 || newname == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* SMB2 hardlink paths are never DFS. */
+ req->flags2 &= ~FLAGS2_DFS_PATHNAMES;
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = check_path_syntax(newname,
+ fsp->fsp_name->flags & SMB_FILENAME_POSIX_PATH);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DBG_DEBUG("got name |%s|\n", newname);
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags,
+ 0, /* No TWRP. */
+ &dst_dirfsp,
+ &smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->base_fsp) {
+ /* No stream names. */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ DBG_DEBUG("SMB_FILE_LINK_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst));
+ status = hardlink_internals(ctx,
+ conn,
+ req,
+ overwrite,
+ fsp->fsp_name,
+ smb_fname_dst);
+
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+static NTSTATUS smb_file_link_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (!fsp) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (total_data < 20) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) ? true : false);
+ len = IVAL(pdata,16);
+
+ if (len > (total_data - 20) || (len == 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (smb_fname_src->flags & SMB_FILENAME_POSIX_PATH) {
+ srvstr_get_path_posix(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE,
+ &status);
+ ucf_flags |= UCF_POSIX_PATHNAMES;
+ } else {
+ srvstr_get_path(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[20],
+ len,
+ STR_TERMINATE,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_file_link_information: got name |%s|\n",
+ newname));
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(newname, &dst_twrp);
+ }
+ /* hardlink paths are never DFS. */
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ newname,
+ ucf_flags,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_dst);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->base_fsp) {
+ /* No stream names. */
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ DEBUG(10,("smb_file_link_information: "
+ "SMB_FILE_LINK_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = hardlink_internals(ctx,
+ conn,
+ req,
+ overwrite,
+ fsp->fsp_name,
+ smb_fname_dst);
+
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+
+/****************************************************************************
+ Deal with SMB_FILE_RENAME_INFORMATION.
+****************************************************************************/
+
+static NTSTATUS smb_file_rename_information(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname_src)
+{
+ bool overwrite;
+ uint32_t root_fid;
+ uint32_t len;
+ char *newname = NULL;
+ struct files_struct *dst_dirfsp = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+ const char *dst_original_lcomp = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ char *p;
+ TALLOC_CTX *ctx = talloc_tos();
+
+ if (total_data < 13) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ overwrite = (CVAL(pdata,0) != 0);
+ root_fid = IVAL(pdata,4);
+ len = IVAL(pdata,8);
+
+ if (len > (total_data - 12) || (len == 0) || (root_fid != 0)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (req->posix_pathnames) {
+ srvstr_get_path_posix(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[12],
+ len,
+ 0,
+ &status);
+ } else {
+ srvstr_get_path(ctx,
+ pdata,
+ req->flags2,
+ &newname,
+ &pdata[12],
+ len,
+ 0,
+ &status);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(10,("smb_file_rename_information: got name |%s|\n",
+ newname));
+
+ /* Check the new name has no '/' characters. */
+ if (strchr_m(newname, '/')) {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ if (fsp && fsp->base_fsp) {
+ /* newname must be a stream name. */
+ if (newname[0] != ':') {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /* Create an smb_fname to call rename_internals_fsp() with. */
+ smb_fname_dst = synthetic_smb_fname(talloc_tos(),
+ fsp->base_fsp->fsp_name->base_name,
+ newname,
+ NULL,
+ fsp->base_fsp->fsp_name->twrp,
+ fsp->base_fsp->fsp_name->flags);
+ if (smb_fname_dst == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /*
+ * Get the original last component, since
+ * rename_internals_fsp() requires it.
+ */
+ dst_original_lcomp = get_original_lcomp(smb_fname_dst,
+ conn,
+ newname,
+ 0);
+ if (dst_original_lcomp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ } else {
+ /*
+ * Build up an smb_fname_dst based on the filename passed in.
+ * We basically just strip off the last component, and put on
+ * the newname instead.
+ */
+ char *base_name = NULL;
+ uint32_t ucf_flags = ucf_flags_from_smb_request(req);
+ NTTIME dst_twrp = 0;
+
+ /* newname must *not* be a stream name. */
+ if (newname[0] == ':') {
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ /*
+ * Strip off the last component (filename) of the path passed
+ * in.
+ */
+ base_name = talloc_strdup(ctx, smb_fname_src->base_name);
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = strrchr_m(base_name, '/');
+ if (p) {
+ p[1] = '\0';
+ } else {
+ base_name = talloc_strdup(ctx, "");
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ /* Append the new name. */
+ base_name = talloc_asprintf_append(base_name,
+ "%s",
+ newname);
+ if (!base_name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ucf_flags & UCF_GMT_PATHNAME) {
+ extract_snapshot_token(base_name, &dst_twrp);
+ }
+
+ /* The newname is *not* a DFS path. */
+ ucf_flags &= ~UCF_DFS_PATHNAME;
+
+ status = filename_convert_dirfsp(ctx,
+ conn,
+ base_name,
+ ucf_flags,
+ dst_twrp,
+ &dst_dirfsp,
+ &smb_fname_dst);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ dst_original_lcomp = get_original_lcomp(smb_fname_dst,
+ conn,
+ newname,
+ ucf_flags);
+ if (dst_original_lcomp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ if (fsp != NULL && fsp->fsp_flags.is_fsa) {
+ DEBUG(10,("smb_file_rename_information: "
+ "SMB_FILE_RENAME_INFORMATION (%s) %s -> %s\n",
+ fsp_fnum_dbg(fsp), fsp_str_dbg(fsp),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = rename_internals_fsp(conn,
+ fsp,
+ smb_fname_dst,
+ dst_original_lcomp,
+ 0,
+ overwrite);
+ } else {
+ DEBUG(10,("smb_file_rename_information: "
+ "SMB_FILE_RENAME_INFORMATION %s -> %s\n",
+ smb_fname_str_dbg(smb_fname_src),
+ smb_fname_str_dbg(smb_fname_dst)));
+ status = rename_internals(ctx,
+ conn,
+ req,
+ NULL, /* src_dirfsp */
+ smb_fname_src,
+ smb_fname_dst,
+ dst_original_lcomp,
+ 0,
+ overwrite,
+ FILE_WRITE_ATTRIBUTES);
+ }
+ out:
+ TALLOC_FREE(smb_fname_dst);
+ return status;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_BASIC_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_basic_info(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ /* Patch to do this correctly from Paul Eggert <eggert@twinsun.com>. */
+ struct smb_file_time ft;
+ uint32_t dosmode = 0;
+ NTSTATUS status = NT_STATUS_OK;
+
+ init_smb_file_time(&ft);
+
+ if (total_data < 36) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Set the attributes */
+ dosmode = IVAL(pdata,32);
+ status = smb_set_file_dosmode(conn, fsp, dosmode);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* create time */
+ ft.create_time = pull_long_date_full_timespec(pdata);
+
+ /* access time */
+ ft.atime = pull_long_date_full_timespec(pdata+8);
+
+ /* write time. */
+ ft.mtime = pull_long_date_full_timespec(pdata+16);
+
+ /* change time. */
+ ft.ctime = pull_long_date_full_timespec(pdata+24);
+
+ DEBUG(10, ("smb_set_file_basic_info: file %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ status = smb_set_file_time(conn, fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_INFO_STANDARD.
+****************************************************************************/
+
+static NTSTATUS smb_set_info_standard(connection_struct *conn,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ NTSTATUS status;
+ struct smb_file_time ft;
+
+ init_smb_file_time(&ft);
+
+ if (total_data < 12) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (fsp == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ /* create time */
+ ft.create_time = time_t_to_full_timespec(srv_make_unix_date2(pdata));
+ /* access time */
+ ft.atime = time_t_to_full_timespec(srv_make_unix_date2(pdata+4));
+ /* write time */
+ ft.mtime = time_t_to_full_timespec(srv_make_unix_date2(pdata+8));
+
+ DEBUG(10,("smb_set_info_standard: file %s\n",
+ smb_fname_str_dbg(smb_fname)));
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_ATTRIBUTES);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smb_set_file_time(conn, fsp, smb_fname, &ft, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (fsp->fsp_flags.modified) {
+ trigger_write_time_update_immediate(fsp);
+ }
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_ALLOCATION_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_allocation_info(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname)
+{
+ uint64_t allocation_size = 0;
+ NTSTATUS status = NT_STATUS_OK;
+ files_struct *new_fsp = NULL;
+
+ if (!VALID_STAT(smb_fname->st)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ allocation_size = (uint64_t)IVAL(pdata,0);
+ allocation_size |= (((uint64_t)IVAL(pdata,4)) << 32);
+ DEBUG(10,("smb_set_file_allocation_info: Set file allocation info for "
+ "file %s to %.0f\n", smb_fname_str_dbg(smb_fname),
+ (double)allocation_size));
+
+ if (allocation_size) {
+ allocation_size = smb_roundup(conn, allocation_size);
+ }
+
+ DEBUG(10,("smb_set_file_allocation_info: file %s : setting new "
+ "allocation size to %.0f\n", smb_fname_str_dbg(smb_fname),
+ (double)allocation_size));
+
+ if (fsp &&
+ !fsp->fsp_flags.is_pathref &&
+ fsp_get_io_fd(fsp) != -1)
+ {
+ /* Open file handle. */
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Only change if needed. */
+ if (allocation_size != get_file_size_stat(&smb_fname->st)) {
+ if (vfs_allocate_file_space(fsp, allocation_size) == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ }
+ /* But always update the time. */
+ /*
+ * This is equivalent to a write. Ensure it's seen immediately
+ * if there are no pending writes.
+ */
+ trigger_write_time_update_immediate(fsp);
+ return NT_STATUS_OK;
+ }
+
+ /* Pathname or stat or directory file. */
+ status = SMB_VFS_CREATE_FILE(
+ conn, /* conn */
+ req, /* req */
+ NULL, /* dirfsp */
+ smb_fname, /* fname */
+ FILE_WRITE_DATA, /* access_mask */
+ (FILE_SHARE_READ | FILE_SHARE_WRITE | /* share_access */
+ FILE_SHARE_DELETE),
+ FILE_OPEN, /* create_disposition*/
+ 0, /* create_options */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes */
+ 0, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &new_fsp, /* result */
+ NULL, /* pinfo */
+ NULL, NULL); /* create context */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* NB. We check for open_was_deferred in the caller. */
+ return status;
+ }
+
+ /* Only change if needed. */
+ if (allocation_size != get_file_size_stat(&smb_fname->st)) {
+ if (vfs_allocate_file_space(new_fsp, allocation_size) == -1) {
+ status = map_nt_error_from_unix(errno);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return status;
+ }
+ }
+
+ /* Changing the allocation size should set the last mod time. */
+ /*
+ * This is equivalent to a write. Ensure it's seen immediately
+ * if there are no pending writes.
+ */
+ trigger_write_time_update_immediate(new_fsp);
+ close_file_free(req, &new_fsp, NORMAL_CLOSE);
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ Deal with SMB_SET_FILE_END_OF_FILE_INFO.
+****************************************************************************/
+
+static NTSTATUS smb_set_file_end_of_file_info(connection_struct *conn,
+ struct smb_request *req,
+ const char *pdata,
+ int total_data,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ bool fail_after_createfile)
+{
+ off_t size;
+
+ if (total_data < 8) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ size = IVAL(pdata,0);
+ size |= (((off_t)IVAL(pdata,4)) << 32);
+ DEBUG(10,("smb_set_file_end_of_file_info: Set end of file info for "
+ "file %s to %.0f\n", smb_fname_str_dbg(smb_fname),
+ (double)size));
+
+ return smb_set_file_size(conn, req,
+ fsp,
+ smb_fname,
+ &smb_fname->st,
+ size,
+ fail_after_createfile);
+}
+
+NTSTATUS smbd_do_setfilepathinfo(connection_struct *conn,
+ struct smb_request *req,
+ TALLOC_CTX *mem_ctx,
+ uint16_t info_level,
+ files_struct *fsp,
+ struct smb_filename *smb_fname,
+ char **ppdata, int total_data,
+ int *ret_data_size)
+{
+ char *pdata = *ppdata;
+ NTSTATUS status = NT_STATUS_OK;
+ int data_return_size = 0;
+
+ *ret_data_size = 0;
+
+ DEBUG(3,("smbd_do_setfilepathinfo: %s (%s) info_level=%d "
+ "totdata=%d\n", smb_fname_str_dbg(smb_fname),
+ fsp_fnum_dbg(fsp),
+ info_level, total_data));
+
+ switch (info_level) {
+
+ case SMB_INFO_STANDARD:
+ {
+ status = smb_set_info_standard(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_INFO_SET_EA:
+ {
+ status = smb_info_set_ea(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_SET_FILE_BASIC_INFO:
+ case SMB_FILE_BASIC_INFORMATION:
+ {
+ status = smb_set_file_basic_info(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_FILE_ALLOCATION_INFORMATION:
+ case SMB_SET_FILE_ALLOCATION_INFO:
+ {
+ status = smb_set_file_allocation_info(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_FILE_END_OF_FILE_INFORMATION:
+ case SMB_SET_FILE_END_OF_FILE_INFO:
+ {
+ /*
+ * XP/Win7 both fail after the createfile with
+ * SMB_SET_FILE_END_OF_FILE_INFO but not
+ * SMB_FILE_END_OF_FILE_INFORMATION (pass-through).
+ * The level is known here, so pass it down
+ * appropriately.
+ */
+ bool should_fail =
+ (info_level == SMB_SET_FILE_END_OF_FILE_INFO);
+
+ status = smb_set_file_end_of_file_info(conn, req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname,
+ should_fail);
+ break;
+ }
+
+ case SMB_FILE_DISPOSITION_INFORMATION:
+ case SMB_SET_FILE_DISPOSITION_INFO: /* Set delete on close for open file. */
+ {
+#if 0
+ /* JRA - We used to just ignore this on a path ?
+ * Shouldn't this be invalid level on a pathname
+ * based call ?
+ */
+ if (tran_call != TRANSACT2_SETFILEINFO) {
+ return ERROR_NT(NT_STATUS_INVALID_LEVEL);
+ }
+#endif
+ status = smb_set_file_disposition_info(conn,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ break;
+ }
+
+ case SMB_FILE_POSITION_INFORMATION:
+ {
+ status = smb_file_position_information(conn,
+ pdata,
+ total_data,
+ fsp);
+ break;
+ }
+
+ case SMB_FILE_FULL_EA_INFORMATION:
+ {
+ status = smb_set_file_full_ea_info(conn,
+ pdata,
+ total_data,
+ fsp);
+ break;
+ }
+
+ /* From tridge Samba4 :
+ * MODE_INFORMATION in setfileinfo (I have no
+ * idea what "mode information" on a file is - it takes a value of 0,
+ * 2, 4 or 6. What could it be?).
+ */
+
+ case SMB_FILE_MODE_INFORMATION:
+ {
+ status = smb_file_mode_information(conn,
+ pdata,
+ total_data);
+ break;
+ }
+
+ /* [MS-SMB2] 3.3.5.21.1 states we MUST fail with STATUS_NOT_SUPPORTED. */
+ case SMB_FILE_VALID_DATA_LENGTH_INFORMATION:
+ case SMB_FILE_SHORT_NAME_INFORMATION:
+ return NT_STATUS_NOT_SUPPORTED;
+
+ case SMB_FILE_RENAME_INFORMATION:
+ {
+ status = smb_file_rename_information(conn, req,
+ pdata, total_data,
+ fsp, smb_fname);
+ break;
+ }
+
+ case SMB2_FILE_RENAME_INFORMATION_INTERNAL:
+ {
+ /* SMB2 rename information. */
+ status = smb2_file_rename_information(conn, req,
+ pdata, total_data,
+ fsp, smb_fname);
+ break;
+ }
+
+ case SMB_FILE_LINK_INFORMATION:
+ {
+ if (conn->sconn->using_smb2) {
+ status = smb2_file_link_information(conn,
+ req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ } else {
+ status = smb_file_link_information(conn,
+ req,
+ pdata,
+ total_data,
+ fsp,
+ smb_fname);
+ }
+ break;
+ }
+
+ default:
+ return NT_STATUS_INVALID_LEVEL;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *ret_data_size = data_return_size;
+ return NT_STATUS_OK;
+}
+
+static uint32_t generate_volume_serial_number(
+ const struct loadparm_substitution *lp_sub,
+ int snum)
+{
+ int serial = lp_volume_serial_number(snum);
+ return serial != -1 ? serial:
+ str_checksum(lp_servicename(talloc_tos(), lp_sub, snum)) ^
+ (str_checksum(get_local_machine_name())<<16);
+}
diff --git a/source3/smbd/smb2_write.c b/source3/smbd/smb2_write.c
new file mode 100644
index 0000000..2675997
--- /dev/null
+++ b/source3/smbd/smb2_write.c
@@ -0,0 +1,457 @@
+/*
+ Unix SMB/CIFS implementation.
+ Core SMB2 server
+
+ Copyright (C) Stefan Metzmacher 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../libcli/smb/smb_common.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "rpc_server/srv_pipe_hnd.h"
+#include "libcli/security/security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_SMB2
+
+static struct tevent_req *smbd_smb2_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *in_fsp,
+ DATA_BLOB in_data,
+ uint64_t in_offset,
+ uint32_t in_flags);
+static NTSTATUS smbd_smb2_write_recv(struct tevent_req *req,
+ uint32_t *out_count);
+
+static void smbd_smb2_request_write_done(struct tevent_req *subreq);
+NTSTATUS smbd_smb2_request_process_write(struct smbd_smb2_request *req)
+{
+ struct smbXsrv_connection *xconn = req->xconn;
+ NTSTATUS status;
+ const uint8_t *inbody;
+ uint16_t in_data_offset;
+ uint32_t in_data_length;
+ DATA_BLOB in_data_buffer;
+ uint64_t in_offset;
+ uint64_t in_file_id_persistent;
+ uint64_t in_file_id_volatile;
+ struct files_struct *in_fsp;
+ uint32_t in_flags;
+ size_t in_dyn_len = 0;
+ uint8_t *in_dyn_ptr = NULL;
+ struct tevent_req *subreq;
+
+ status = smbd_smb2_request_verify_sizes(req, 0x31);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+ inbody = SMBD_SMB2_IN_BODY_PTR(req);
+
+ in_data_offset = SVAL(inbody, 0x02);
+ in_data_length = IVAL(inbody, 0x04);
+ in_offset = BVAL(inbody, 0x08);
+ in_file_id_persistent = BVAL(inbody, 0x10);
+ in_file_id_volatile = BVAL(inbody, 0x18);
+ in_flags = IVAL(inbody, 0x2C);
+
+ if (in_data_offset != (SMB2_HDR_BODY + SMBD_SMB2_IN_BODY_LEN(req))) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ if (req->smb1req != NULL && req->smb1req->unread_bytes > 0) {
+ in_dyn_ptr = NULL;
+ in_dyn_len = req->smb1req->unread_bytes;
+ } else {
+ in_dyn_ptr = SMBD_SMB2_IN_DYN_PTR(req);
+ in_dyn_len = SMBD_SMB2_IN_DYN_LEN(req);
+ }
+
+ if (in_data_length > in_dyn_len) {
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ /* check the max write size */
+ if (in_data_length > xconn->smb2.server.max_write) {
+ DEBUG(2,("smbd_smb2_request_process_write : "
+ "client ignored max write :%s: 0x%08X: 0x%08X\n",
+ __location__, in_data_length, xconn->smb2.server.max_write));
+ return smbd_smb2_request_error(req, NT_STATUS_INVALID_PARAMETER);
+ }
+
+ /*
+ * Note: that in_dyn_ptr is NULL for the recvfile case.
+ */
+ in_data_buffer.data = in_dyn_ptr;
+ in_data_buffer.length = in_data_length;
+
+ status = smbd_smb2_request_verify_creditcharge(req, in_data_length);
+ if (!NT_STATUS_IS_OK(status)) {
+ return smbd_smb2_request_error(req, status);
+ }
+
+ in_fsp = file_fsp_smb2(req, in_file_id_persistent, in_file_id_volatile);
+ if (in_fsp == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_FILE_CLOSED);
+ }
+
+ subreq = smbd_smb2_write_send(req, req->sconn->ev_ctx,
+ req, in_fsp,
+ in_data_buffer,
+ in_offset,
+ in_flags);
+ if (subreq == NULL) {
+ return smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ }
+ tevent_req_set_callback(subreq, smbd_smb2_request_write_done, req);
+
+ return smbd_smb2_request_pending_queue(req, subreq, 500);
+}
+
+static void smbd_smb2_request_write_done(struct tevent_req *subreq)
+{
+ struct smbd_smb2_request *req = tevent_req_callback_data(subreq,
+ struct smbd_smb2_request);
+ DATA_BLOB outbody;
+ DATA_BLOB outdyn;
+ uint32_t out_count = 0;
+ NTSTATUS status;
+ NTSTATUS error; /* transport error */
+
+ status = smbd_smb2_write_recv(subreq, &out_count);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ error = smbd_smb2_request_error(req, status);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ outbody = smbd_smb2_generate_outbody(req, 0x10);
+ if (outbody.data == NULL) {
+ error = smbd_smb2_request_error(req, NT_STATUS_NO_MEMORY);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn,
+ nt_errstr(error));
+ return;
+ }
+ return;
+ }
+
+ SSVAL(outbody.data, 0x00, 0x10 + 1); /* struct size */
+ SSVAL(outbody.data, 0x02, 0); /* reserved */
+ SIVAL(outbody.data, 0x04, out_count); /* count */
+ SIVAL(outbody.data, 0x08, 0); /* remaining */
+ SSVAL(outbody.data, 0x0C, 0); /* write channel info offset */
+ SSVAL(outbody.data, 0x0E, 0); /* write channel info length */
+
+ outdyn = data_blob_const(NULL, 0);
+
+ error = smbd_smb2_request_done(req, outbody, &outdyn);
+ if (!NT_STATUS_IS_OK(error)) {
+ smbd_server_connection_terminate(req->xconn, nt_errstr(error));
+ return;
+ }
+}
+
+struct smbd_smb2_write_state {
+ struct smbd_smb2_request *smb2req;
+ struct smb_request *smbreq;
+ files_struct *fsp;
+ bool write_through;
+ uint32_t in_length;
+ uint64_t in_offset;
+ uint32_t out_count;
+};
+
+static void smbd_smb2_write_pipe_done(struct tevent_req *subreq);
+
+static NTSTATUS smb2_write_complete_internal(struct tevent_req *req,
+ ssize_t nwritten, int err,
+ bool do_sync)
+{
+ NTSTATUS status;
+ struct smbd_smb2_write_state *state = tevent_req_data(req,
+ struct smbd_smb2_write_state);
+ files_struct *fsp = state->fsp;
+
+ if (nwritten == -1) {
+ if (err == EOVERFLOW && fsp_is_alternate_stream(fsp)) {
+ status = NT_STATUS_FILE_SYSTEM_LIMITATION;
+ } else {
+ status = map_nt_error_from_unix(err);
+ }
+
+ DEBUG(2, ("smb2_write failed: %s, file %s, "
+ "length=%lu offset=%lu nwritten=-1: %s\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ (unsigned long)state->in_length,
+ (unsigned long)state->in_offset,
+ nt_errstr(status)));
+
+ return status;
+ }
+
+ DEBUG(3,("smb2: %s, file %s, "
+ "length=%lu offset=%lu wrote=%lu\n",
+ fsp_fnum_dbg(fsp),
+ fsp_str_dbg(fsp),
+ (unsigned long)state->in_length,
+ (unsigned long)state->in_offset,
+ (unsigned long)nwritten));
+
+ if ((nwritten == 0) && (state->in_length != 0)) {
+ DEBUG(5,("smb2: write [%s] disk full\n",
+ fsp_str_dbg(fsp)));
+ return NT_STATUS_DISK_FULL;
+ }
+
+ if (do_sync) {
+ status = sync_file(fsp->conn, fsp, state->write_through);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5,("smb2: sync_file for %s returned %s\n",
+ fsp_str_dbg(fsp),
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ state->out_count = nwritten;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb2_write_complete(struct tevent_req *req, ssize_t nwritten, int err)
+{
+ return smb2_write_complete_internal(req, nwritten, err, true);
+}
+
+NTSTATUS smb2_write_complete_nosync(struct tevent_req *req, ssize_t nwritten,
+ int err)
+{
+ return smb2_write_complete_internal(req, nwritten, err, false);
+}
+
+
+static bool smbd_smb2_write_cancel(struct tevent_req *req)
+{
+ struct smbd_smb2_write_state *state =
+ tevent_req_data(req,
+ struct smbd_smb2_write_state);
+
+ return cancel_smb2_aio(state->smbreq);
+}
+
+static struct tevent_req *smbd_smb2_write_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req,
+ struct files_struct *fsp,
+ DATA_BLOB in_data,
+ uint64_t in_offset,
+ uint32_t in_flags)
+{
+ NTSTATUS status;
+ struct tevent_req *req = NULL;
+ struct smbd_smb2_write_state *state = NULL;
+ struct smb_request *smbreq = NULL;
+ connection_struct *conn = smb2req->tcon->compat;
+ ssize_t nwritten;
+ struct lock_struct lock;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smbd_smb2_write_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->smb2req = smb2req;
+ if (smb2req->xconn->protocol >= PROTOCOL_SMB3_02) {
+ if (in_flags & SMB2_WRITEFLAG_WRITE_UNBUFFERED) {
+ state->write_through = true;
+ }
+ }
+ if (in_flags & SMB2_WRITEFLAG_WRITE_THROUGH) {
+ state->write_through = true;
+ }
+ state->in_length = in_data.length;
+ state->in_offset = in_offset;
+ state->out_count = 0;
+
+ DEBUG(10,("smbd_smb2_write: %s - %s\n",
+ fsp_str_dbg(fsp), fsp_fnum_dbg(fsp)));
+
+ smbreq = smbd_smb2_fake_smb_request(smb2req, fsp);
+ if (tevent_req_nomem(smbreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->smbreq = smbreq;
+
+ state->fsp = fsp;
+
+ if (IS_IPC(smbreq->conn)) {
+ struct tevent_req *subreq = NULL;
+ bool ok;
+
+ if (!fsp_is_np(fsp)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_CLOSED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = np_write_send(state, ev,
+ fsp->fake_file_handle,
+ in_data.data,
+ in_data.length);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smbd_smb2_write_pipe_done,
+ req);
+
+ /*
+ * Make sure we mark the fsp as having outstanding async
+ * activity so we don't crash on shutdown close.
+ */
+
+ ok = aio_add_req_to_fsp(fsp, req);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+ }
+
+ status = check_any_access_fsp(fsp, FILE_WRITE_DATA|FILE_APPEND_DATA);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Try and do an asynchronous write. */
+ status = schedule_aio_smb2_write(conn,
+ smbreq,
+ fsp,
+ in_offset,
+ in_data,
+ state->write_through);
+
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * Doing an async write, allow this
+ * request to be canceled
+ */
+ tevent_req_set_cancel_fn(req, smbd_smb2_write_cancel);
+ return req;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_RETRY)) {
+ /* Real error in setting up aio. Fail. */
+ tevent_req_nterror(req, status);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Fallback to synchronous. */
+ init_strict_lock_struct(fsp,
+ fsp->op->global->open_persistent_id,
+ in_offset,
+ in_data.length,
+ WRITE_LOCK,
+ lp_posix_cifsu_locktype(fsp),
+ &lock);
+
+ if (!SMB_VFS_STRICT_LOCK_CHECK(conn, fsp, &lock)) {
+ tevent_req_nterror(req, NT_STATUS_FILE_LOCK_CONFLICT);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Note: in_data.data is NULL for the recvfile case.
+ */
+ nwritten = write_file(smbreq, fsp,
+ (const char *)in_data.data,
+ in_offset,
+ in_data.length);
+
+ status = smb2_write_complete(req, nwritten, errno);
+
+ DEBUG(10,("smb2: write on "
+ "file %s, offset %.0f, requested %u, written = %u\n",
+ fsp_str_dbg(fsp),
+ (double)in_offset,
+ (unsigned int)in_data.length,
+ (unsigned int)nwritten ));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ } else {
+ /* Success. */
+ tevent_req_done(req);
+ }
+
+ return tevent_req_post(req, ev);
+}
+
+static void smbd_smb2_write_pipe_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smbd_smb2_write_state *state = tevent_req_data(req,
+ struct smbd_smb2_write_state);
+ NTSTATUS status;
+ ssize_t nwritten = -1;
+
+ status = np_write_recv(subreq, &nwritten);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ NTSTATUS old = status;
+ status = nt_status_np_pipe(old);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if ((nwritten == 0 && state->in_length != 0) || (nwritten < 0)) {
+ tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED);
+ return;
+ }
+
+ state->out_count = nwritten;
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS smbd_smb2_write_recv(struct tevent_req *req,
+ uint32_t *out_count)
+{
+ NTSTATUS status;
+ struct smbd_smb2_write_state *state = tevent_req_data(req,
+ struct smbd_smb2_write_state);
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_count = state->out_count;
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smbXsrv_client.c b/source3/smbd/smbXsrv_client.c
new file mode 100644
index 0000000..27df107
--- /dev/null
+++ b/source3/smbd/smbXsrv_client.c
@@ -0,0 +1,1458 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include <tevent.h>
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_watch.h"
+#include "session.h"
+#include "auth.h"
+#include "auth/gensec/gensec.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/iov_buf.h"
+#include "lib/global_contexts.h"
+#include "source3/include/util_tdb.h"
+
+struct smbXsrv_client_table {
+ struct {
+ uint32_t max_clients;
+ uint32_t num_clients;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_client_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_client_global_init(void)
+{
+ const char *global_path = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_client_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * This contains secret information like client keys!
+ */
+ global_path = lock_path(talloc_tos(), "smbXsrv_client_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ backend = db_open(NULL, global_path,
+ 0, /* hash_size */
+ TDB_DEFAULT |
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (backend == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ db_ctx = db_open_watched(NULL, &backend, global_messaging_context());
+ if (db_ctx == NULL) {
+ TALLOC_FREE(backend);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbXsrv_client_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+#define SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE 16
+
+static TDB_DATA smbXsrv_client_global_id_to_key(const struct GUID *client_guid,
+ uint8_t *key_buf)
+{
+ TDB_DATA key = { .dsize = 0, };
+ NTSTATUS status;
+ struct GUID_ndr_buf buf = { .buf = {0}, };
+
+ status = GUID_to_ndr_buf(client_guid, &buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return key;
+ }
+ memcpy(key_buf, buf.buf, SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE);
+
+ key = make_tdb_data(key_buf, SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+static struct db_record *smbXsrv_client_global_fetch_locked(
+ struct db_context *db,
+ const struct GUID *client_guid,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_CLIENT_GLOBAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_client_global_id_to_key(client_guid, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ struct GUID_txt_buf buf;
+ DBG_DEBUG("Failed to lock guid [%s], key '%s'\n",
+ GUID_buf_string(client_guid, &buf),
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static NTSTATUS smbXsrv_client_table_create(TALLOC_CTX *mem_ctx,
+ struct messaging_context *msg_ctx,
+ uint32_t max_clients,
+ struct smbXsrv_client_table **_table)
+{
+ struct smbXsrv_client_table *table;
+ NTSTATUS status;
+
+ if (max_clients > 1) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ table = talloc_zero(mem_ctx, struct smbXsrv_client_table);
+ if (table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ table->local.max_clients = max_clients;
+
+ status = smbXsrv_client_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(table);
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_client_global_db_ctx;
+
+ *_table = table;
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_client_global_destructor(struct smbXsrv_client_global0 *global)
+{
+ return 0;
+}
+
+static void smbXsrv_client_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ const struct server_id *dead_server_id,
+ struct smbXsrv_client_global0 **_g,
+ uint32_t *pseqnum)
+{
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_client_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_client_global0 *global = NULL;
+ bool dead = false;
+ bool exists;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *is_free = false;
+
+ if (was_free) {
+ *was_free = false;
+ }
+ if (_g) {
+ *_g = NULL;
+ }
+ if (pseqnum) {
+ *pseqnum = 0;
+ }
+
+ key = dbwrap_record_get_key(db_rec);
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_client_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ DBG_DEBUG("client_global:\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("key '%s' uses unsupported version %u\n",
+ tdb_data_dbg(key),
+ global_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ global = global_blob.info.info0;
+
+ dead = server_id_equal(dead_server_id, &global->server_id);
+ if (dead) {
+ struct server_id_buf tmp;
+
+ DBG_NOTICE("key '%s' server_id %s is already dead.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->server_id, &tmp));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ exists = serverid_exists(&global->server_id);
+ if (!exists) {
+ struct server_id_buf tmp;
+
+ DBG_NOTICE("key '%s' server_id %s does not exist.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->server_id, &tmp));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ if (_g) {
+ *_g = talloc_move(mem_ctx, &global);
+ }
+ if (pseqnum) {
+ *pseqnum = global_blob.seqnum;
+ }
+ TALLOC_FREE(frame);
+}
+
+static NTSTATUS smb2srv_client_connection_pass(struct smbd_smb2_request *smb2req,
+ struct smbXsrv_client_global0 *global)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ struct smbXsrv_connection_pass0 pass_info0;
+ struct smbXsrv_connection_passB pass_blob;
+ ssize_t reqlen;
+ struct iovec iov;
+
+ pass_info0 = (struct smbXsrv_connection_pass0) {
+ .client_guid = global->client_guid,
+ .src_server_id = smb2req->xconn->client->global->server_id,
+ .xconn_connect_time = smb2req->xconn->client->global->initial_connect_time,
+ .dst_server_id = global->server_id,
+ .client_connect_time = global->initial_connect_time,
+ };
+
+ reqlen = iov_buflen(smb2req->in.vector, smb2req->in.vector_count);
+ if (reqlen == -1) {
+ return NT_STATUS_INVALID_BUFFER_SIZE;
+ }
+
+ pass_info0.negotiate_request.length = reqlen;
+ pass_info0.negotiate_request.data = talloc_array(talloc_tos(), uint8_t,
+ reqlen);
+ if (pass_info0.negotiate_request.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ iov_buf(smb2req->in.vector, smb2req->in.vector_count,
+ pass_info0.negotiate_request.data,
+ pass_info0.negotiate_request.length);
+
+ ZERO_STRUCT(pass_blob);
+ pass_blob.version = smbXsrv_version_global_current();
+ pass_blob.info.info0 = &pass_info0;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &pass_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_connection_passB);
+ data_blob_free(&pass_info0.negotiate_request);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ status = messaging_send_iov(smb2req->xconn->client->msg_ctx,
+ global->server_id,
+ MSG_SMBXSRV_CONNECTION_PASS,
+ &iov, 1,
+ &smb2req->xconn->transport.sock, 1);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb2srv_client_connection_drop(struct smbd_smb2_request *smb2req,
+ struct smbXsrv_client_global0 *global)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ struct smbXsrv_connection_drop0 drop_info0;
+ struct smbXsrv_connection_dropB drop_blob;
+ struct iovec iov;
+
+ drop_info0 = (struct smbXsrv_connection_drop0) {
+ .client_guid = global->client_guid,
+ .src_server_id = smb2req->xconn->client->global->server_id,
+ .xconn_connect_time = smb2req->xconn->client->global->initial_connect_time,
+ .dst_server_id = global->server_id,
+ .client_connect_time = global->initial_connect_time,
+ };
+
+ ZERO_STRUCT(drop_blob);
+ drop_blob.version = smbXsrv_version_global_current();
+ drop_blob.info.info0 = &drop_info0;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &drop_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_connection_dropB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ status = messaging_send_iov(smb2req->xconn->client->msg_ctx,
+ global->server_id,
+ MSG_SMBXSRV_CONNECTION_DROP,
+ &iov, 1,
+ NULL, 0);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_client_global_store(struct smbXsrv_client_global0 *global)
+{
+ struct smbXsrv_client_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA key;
+ TDB_DATA val;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ bool saved_stored = global->stored;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ SMB_ASSERT(global->local_address != NULL);
+ SMB_ASSERT(global->remote_address != NULL);
+ SMB_ASSERT(global->remote_name != NULL);
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+ val = dbwrap_record_get_value(global->db_rec);
+
+ ZERO_STRUCT(global_blob);
+ global_blob.version = smbXsrv_version_global_current();
+ if (val.dsize >= 8) {
+ global_blob.seqnum = IVAL(val.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ global->stored = true;
+ ndr_err = ndr_push_struct_blob(&blob, global->db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_client_globalB);
+ global->stored = saved_stored;
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(global->db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ global->stored = true;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("key '%s' stored\n",
+ tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_client_globalB, &global_blob);
+ }
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+struct smb2srv_client_mc_negprot_state {
+ struct tevent_context *ev;
+ struct smbd_smb2_request *smb2req;
+ struct db_record *db_rec;
+ struct server_id sent_server_id;
+ uint64_t watch_instance;
+ uint32_t last_seqnum;
+ struct tevent_req *filter_subreq;
+};
+
+static void smb2srv_client_mc_negprot_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+
+ if (state->db_rec != NULL) {
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance = 0;
+ TALLOC_FREE(state->db_rec);
+ }
+}
+
+static void smb2srv_client_mc_negprot_next(struct tevent_req *req);
+static bool smb2srv_client_mc_negprot_filter(struct messaging_rec *rec, void *private_data);
+static void smb2srv_client_mc_negprot_done(struct tevent_req *subreq);
+static void smb2srv_client_mc_negprot_watched(struct tevent_req *subreq);
+
+struct tevent_req *smb2srv_client_mc_negprot_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbd_smb2_request *smb2req)
+{
+ struct tevent_req *req = NULL;
+ struct smb2srv_client_mc_negprot_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb2srv_client_mc_negprot_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->smb2req = smb2req;
+
+ tevent_req_set_cleanup_fn(req, smb2srv_client_mc_negprot_cleanup);
+
+ server_id_set_disconnected(&state->sent_server_id);
+
+ smb2srv_client_mc_negprot_next(req);
+
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void smb2srv_client_mc_negprot_next(struct tevent_req *req)
+{
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+ struct smbXsrv_connection *xconn = state->smb2req->xconn;
+ struct smbXsrv_client *client = xconn->client;
+ struct smbXsrv_client_table *table = client->table;
+ struct GUID client_guid = xconn->smb2.client.guid;
+ struct smbXsrv_client_global0 *global = NULL;
+ bool is_free = false;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ uint32_t seqnum = 0;
+ struct server_id last_server_id = { .pid = 0, };
+
+ SMB_ASSERT(state->db_rec == NULL);
+ state->db_rec = smbXsrv_client_global_fetch_locked(table->global.db_ctx,
+ &client_guid,
+ state);
+ if (state->db_rec == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_DB_ERROR);
+ return;
+ }
+
+verify_again:
+ TALLOC_FREE(global);
+
+ smbXsrv_client_global_verify_record(state->db_rec,
+ &is_free,
+ NULL,
+ state,
+ &last_server_id,
+ &global,
+ &seqnum);
+ if (is_free) {
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance = 0;
+
+ /*
+ * This stores the new client information in
+ * smbXsrv_client_global.tdb
+ */
+ client->global->client_guid = xconn->smb2.client.guid;
+
+ client->global->db_rec = state->db_rec;
+ state->db_rec = NULL;
+ status = smbXsrv_client_global_store(client->global);
+ SMB_ASSERT(client->global->db_rec == NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf buf;
+ DBG_ERR("client_guid[%s] store failed - %s\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf),
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_clientB client_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = client,
+ };
+ struct GUID_txt_buf buf;
+
+ DBG_DEBUG("client_guid[%s] stored\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf));
+ NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
+ }
+
+ xconn->smb2.client.guid_verified = true;
+ tevent_req_done(req);
+ return;
+ }
+
+ if (global == NULL) {
+ /*
+ * most likely ndr_pull_struct_blob() failed
+ */
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_DB_CORRUPTION);
+ return;
+ }
+
+ if (server_id_equal(&state->sent_server_id, &global->server_id)) {
+ /*
+ * We hit a race with other concurrent connections,
+ * which have woken us.
+ *
+ * We already sent the pass or drop message to
+ * the process, so we need to wait for a
+ * response and not pass the connection
+ * again! Otherwise the process would
+ * receive the same tcp connection via
+ * more than one file descriptor and
+ * create more than one smbXsrv_connection
+ * structure for the same tcp connection,
+ * which means the client would see more
+ * than one SMB2 negprot response to its
+ * single SMB2 netprot request and we
+ * as server get the session keys and
+ * message id validation wrong
+ */
+ goto watch_again;
+ }
+
+ server_id_set_disconnected(&state->sent_server_id);
+
+ /*
+ * If last_server_id is set, we expect
+ * smbXsrv_client_global_verify_record()
+ * to detect the already dead global->server_id
+ * as state->db_rec is still locked and its
+ * value didn't change.
+ */
+ SMB_ASSERT(last_server_id.pid == 0);
+ last_server_id = global->server_id;
+
+ TALLOC_FREE(state->filter_subreq);
+ if (procid_is_local(&global->server_id)) {
+ subreq = messaging_filtered_read_send(state,
+ state->ev,
+ client->msg_ctx,
+ smb2srv_client_mc_negprot_filter,
+ NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smb2srv_client_mc_negprot_done, req);
+ state->filter_subreq = subreq;
+ }
+
+ if (procid_is_local(&global->server_id)) {
+ status = smb2srv_client_connection_pass(state->smb2req,
+ global);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We remembered last_server_id = global->server_id
+ * above, so we'll treat it as dead in the
+ * next round to smbXsrv_client_global_verify_record().
+ */
+ goto verify_again;
+ }
+ state->sent_server_id = global->server_id;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ } else {
+ status = smb2srv_client_connection_drop(state->smb2req,
+ global);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We remembered last_server_id = global->server_id
+ * above, so we'll treat it as dead in the
+ * next round to smbXsrv_client_global_verify_record().
+ */
+ goto verify_again;
+ }
+ state->sent_server_id = global->server_id;
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+watch_again:
+
+ /*
+ * If the record changed, but we are not happy with the change yet,
+ * we better remove ourself from the waiter list
+ * (most likely the first position)
+ * and re-add us at the end of the list.
+ *
+ * This gives other waiters a change
+ * to make progress.
+ *
+ * Otherwise we'll keep our waiter instance alive,
+ * keep waiting (most likely at first position).
+ * It means the order of watchers stays fair.
+ */
+ if (state->last_seqnum != seqnum) {
+ state->last_seqnum = seqnum;
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance =
+ dbwrap_watched_watch_add_instance(state->db_rec);
+ }
+
+ subreq = dbwrap_watched_watch_send(state,
+ state->ev,
+ state->db_rec,
+ state->watch_instance,
+ global->server_id);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, smb2srv_client_mc_negprot_watched, req);
+
+ TALLOC_FREE(global);
+ TALLOC_FREE(state->db_rec);
+ return;
+}
+
+static bool smb2srv_client_mc_negprot_filter(struct messaging_rec *rec, void *private_data)
+{
+ if (rec->msg_type != MSG_SMBXSRV_CONNECTION_PASSED) {
+ return false;
+ }
+
+ if (rec->num_fds != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smb2srv_client_mc_negprot_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+ struct smbXsrv_connection *xconn = state->smb2req->xconn;
+ struct smbXsrv_client *client = xconn->client;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_connection_passB passed_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_connection_pass0 *passed_info0 = NULL;
+ NTSTATUS status;
+ int ret;
+
+ SMB_ASSERT(state->filter_subreq == subreq);
+ state->filter_subreq = NULL;
+
+ ret = messaging_filtered_read_recv(subreq, state, &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ status = map_nt_error_from_unix_common(ret);
+ DBG_ERR("messaging_filtered_read_recv() - %s\n",
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ DBG_DEBUG("MSG_SMBXSRV_CONNECTION_PASSED: received...\n");
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &passed_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_connection_passB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("ndr_pull_struct_blob - %s\n", nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ }
+
+ if (passed_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("ignore invalid version %u\n", passed_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ passed_info0 = passed_blob.info.info0;
+ if (passed_info0 == NULL) {
+ DBG_ERR("ignore NULL info %u\n", passed_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (!GUID_equal(&xconn->smb2.client.guid, &passed_info0->client_guid)) {
+ struct GUID_txt_buf buf1, buf2;
+
+ DBG_ERR("client's client_guid [%s] != passed guid [%s]\n",
+ GUID_buf_string(&xconn->smb2.client.guid,
+ &buf1),
+ GUID_buf_string(&passed_info0->client_guid,
+ &buf2));
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (client->global->initial_connect_time !=
+ passed_info0->xconn_connect_time)
+ {
+ DBG_ERR("client's initial connect time [%s] (%llu) != "
+ "passed xconn connect time [%s] (%llu)\n",
+ nt_time_string(talloc_tos(),
+ client->global->initial_connect_time),
+ (unsigned long long)client->global->initial_connect_time,
+ nt_time_string(talloc_tos(),
+ passed_info0->xconn_connect_time),
+ (unsigned long long)passed_info0->xconn_connect_time);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (passed_info0->negotiate_request.length != 0) {
+ DBG_ERR("negotiate_request.length[%zu]\n",
+ passed_info0->negotiate_request.length);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ tevent_req_nterror(req, NT_STATUS_MESSAGE_RETRIEVED);
+}
+
+static void smb2srv_client_mc_negprot_watched(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2srv_client_mc_negprot_state *state =
+ tevent_req_data(req,
+ struct smb2srv_client_mc_negprot_state);
+ NTSTATUS status;
+ uint64_t instance = 0;
+
+ status = dbwrap_watched_watch_recv(subreq, &instance, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->watch_instance = instance;
+
+ smb2srv_client_mc_negprot_next(req);
+}
+
+NTSTATUS smb2srv_client_mc_negprot_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS smbXsrv_client_global_remove(struct smbXsrv_client_global0 *global)
+{
+ TDB_DATA key;
+ NTSTATUS status;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+
+ status = dbwrap_record_delete(global->db_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' delete - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+ global->stored = false;
+ DBG_DEBUG("key '%s' delete\n", tdb_data_dbg(key));
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_client_destructor(struct smbXsrv_client *client)
+{
+ NTSTATUS status;
+
+ status = smbXsrv_client_remove(client);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_client_remove() failed: %s\n",
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(client->global);
+
+ return 0;
+}
+
+static bool smbXsrv_client_connection_pass_filter(struct messaging_rec *rec, void *private_data);
+static void smbXsrv_client_connection_pass_loop(struct tevent_req *subreq);
+static bool smbXsrv_client_connection_drop_filter(struct messaging_rec *rec, void *private_data);
+static void smbXsrv_client_connection_drop_loop(struct tevent_req *subreq);
+
+NTSTATUS smbXsrv_client_create(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ NTTIME now,
+ struct smbXsrv_client **_client)
+{
+ struct smbXsrv_client_table *table;
+ struct smbXsrv_client *client = NULL;
+ struct smbXsrv_client_global0 *global = NULL;
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+
+ status = smbXsrv_client_table_create(mem_ctx,
+ msg_ctx,
+ 1, /* max_clients */
+ &table);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (table->local.num_clients >= table->local.max_clients) {
+ TALLOC_FREE(table);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ client = talloc_zero(mem_ctx, struct smbXsrv_client);
+ if (client == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ client->raw_ev_ctx = ev_ctx;
+ client->msg_ctx = msg_ctx;
+
+ client->server_multi_channel_enabled =
+ smbXsrv_server_multi_channel_enabled();
+ if (client->server_multi_channel_enabled) {
+ client->next_channel_id = 1;
+ }
+ client->table = talloc_move(client, &table);
+ table = client->table;
+
+ global = talloc_zero(client, struct smbXsrv_client_global0);
+ if (global == NULL) {
+ TALLOC_FREE(client);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(global, smbXsrv_client_global_destructor);
+ client->global = global;
+
+ global->initial_connect_time = now;
+
+ global->server_id = messaging_server_id(client->msg_ctx);
+
+ table->local.num_clients += 1;
+
+ talloc_set_destructor(client, smbXsrv_client_destructor);
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_clientB client_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = client,
+ };
+ struct GUID_txt_buf buf;
+
+ DBG_DEBUG("client_guid[%s] created\n",
+ GUID_buf_string(&global->client_guid, &buf));
+ NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
+ }
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_pass_filter,
+ client);
+ if (subreq == NULL) {
+ TALLOC_FREE(client);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_pass_loop, client);
+ client->connection_pass_subreq = subreq;
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_drop_filter,
+ client);
+ if (subreq == NULL) {
+ TALLOC_FREE(client);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_drop_loop, client);
+ client->connection_drop_subreq = subreq;
+
+ *_client = client;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb2srv_client_connection_passed(struct smbXsrv_client *client,
+ const struct smbXsrv_connection_pass0 *recv_info0)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ struct smbXsrv_connection_pass0 passed_info0;
+ struct smbXsrv_connection_passB passed_blob;
+ struct iovec iov;
+
+ /*
+ * We echo back the message with a cleared negotiate_request
+ */
+ passed_info0 = *recv_info0;
+ passed_info0.negotiate_request = data_blob_null;
+
+ ZERO_STRUCT(passed_blob);
+ passed_blob.version = smbXsrv_version_global_current();
+ passed_blob.info.info0 = &passed_info0;
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &passed_blob);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &passed_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_connection_passB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ status = messaging_send_iov(client->msg_ctx,
+ recv_info0->src_server_id,
+ MSG_SMBXSRV_CONNECTION_PASSED,
+ &iov, 1,
+ NULL, 0);
+ data_blob_free(&blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool smbXsrv_client_connection_pass_filter(struct messaging_rec *rec, void *private_data)
+{
+ if (rec->msg_type != MSG_SMBXSRV_CONNECTION_PASS) {
+ return false;
+ }
+
+ if (rec->num_fds != 1) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smbXsrv_client_connection_pass_loop(struct tevent_req *subreq)
+{
+ struct smbXsrv_client *client =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_client);
+ struct smbXsrv_connection *xconn = NULL;
+ int ret;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_connection_passB pass_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_connection_pass0 *pass_info0 = NULL;
+ NTSTATUS status;
+ int sock_fd = -1;
+ uint64_t seq_low;
+
+ client->connection_pass_subreq = NULL;
+
+ ret = messaging_filtered_read_recv(subreq, talloc_tos(), &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ goto next;
+ }
+
+ if (rec->num_fds != 1) {
+ DBG_ERR("MSG_SMBXSRV_CONNECTION_PASS: num_fds[%u]\n",
+ rec->num_fds);
+ goto next;
+ }
+
+ sock_fd = rec->fds[0];
+ DBG_DEBUG("MSG_SMBXSRV_CONNECTION_PASS: got sock_fd[%d]\n", sock_fd);
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &pass_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_connection_passB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("ndr_pull_struct_blob - %s\n", nt_errstr(status));
+ goto next;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+
+ if (pass_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("ignore invalid version %u\n", pass_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ goto next;
+ }
+
+ pass_info0 = pass_blob.info.info0;
+ if (pass_info0 == NULL) {
+ DBG_ERR("ignore NULL info %u\n", pass_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ goto next;
+ }
+
+ if (!GUID_equal(&client->global->client_guid, &pass_info0->client_guid))
+ {
+ struct GUID_txt_buf buf1, buf2;
+
+ DBG_WARNING("client's client_guid [%s] != passed guid [%s]\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf1),
+ GUID_buf_string(&pass_info0->client_guid,
+ &buf2));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+ goto next;
+ }
+
+ if (client->global->initial_connect_time !=
+ pass_info0->client_connect_time)
+ {
+ DBG_WARNING("client's initial connect time [%s] (%llu) != "
+ "passed initial connect time [%s] (%llu)\n",
+ nt_time_string(talloc_tos(),
+ client->global->initial_connect_time),
+ (unsigned long long)client->global->initial_connect_time,
+ nt_time_string(talloc_tos(),
+ pass_info0->client_connect_time),
+ (unsigned long long)pass_info0->client_connect_time);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+ goto next;
+ }
+
+ if (pass_info0->negotiate_request.length < SMB2_HDR_BODY) {
+ DBG_WARNING("negotiate_request.length[%zu]\n",
+ pass_info0->negotiate_request.length);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ }
+ goto next;
+ }
+
+ status = smb2srv_client_connection_passed(client, pass_info0);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ /*
+ * We hit a race where, the client dropped the connection
+ * while the socket was passed to us and the origin
+ * process already existed.
+ */
+ DBG_DEBUG("smb2srv_client_connection_passed() ignore %s\n",
+ nt_errstr(status));
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *r = "smb2srv_client_connection_passed() failed";
+ DBG_ERR("%s => %s\n", r, nt_errstr(status));
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ exit_server_cleanly(r);
+ return;
+ }
+
+ status = smbd_add_connection(client,
+ sock_fd,
+ pass_info0->xconn_connect_time,
+ &xconn);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ rec->num_fds = 0;
+ smbd_server_connection_terminate(xconn, nt_errstr(status));
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbd_add_connection => %s\n", nt_errstr(status));
+ NDR_PRINT_DEBUG(smbXsrv_connection_passB, &pass_blob);
+ goto next;
+ }
+ rec->num_fds = 0;
+
+ /*
+ * Set seq_low to mid received in negprot
+ */
+ seq_low = BVAL(pass_info0->negotiate_request.data,
+ SMB2_HDR_MESSAGE_ID);
+
+ xconn->smb2.client.guid_verified = true;
+ smbd_smb2_process_negprot(xconn, seq_low,
+ pass_info0->negotiate_request.data,
+ pass_info0->negotiate_request.length);
+
+next:
+ if (rec != NULL) {
+ uint8_t fd_idx;
+
+ for (fd_idx = 0; fd_idx < rec->num_fds; fd_idx++) {
+ sock_fd = rec->fds[fd_idx];
+ close(sock_fd);
+ }
+ rec->num_fds = 0;
+
+ TALLOC_FREE(rec);
+ }
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_pass_filter,
+ client);
+ if (subreq == NULL) {
+ const char *r;
+ r = "messaging_read_send(MSG_SMBXSRV_CONNECTION_PASS failed";
+ exit_server_cleanly(r);
+ return;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_pass_loop, client);
+ client->connection_pass_subreq = subreq;
+}
+
+static bool smbXsrv_client_connection_drop_filter(struct messaging_rec *rec, void *private_data)
+{
+ if (rec->msg_type != MSG_SMBXSRV_CONNECTION_DROP) {
+ return false;
+ }
+
+ if (rec->num_fds != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static void smbXsrv_client_connection_drop_loop(struct tevent_req *subreq)
+{
+ struct smbXsrv_client *client =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_client);
+ int ret;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_connection_dropB drop_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_connection_drop0 *drop_info0 = NULL;
+ struct server_id_buf src_server_id_buf = {};
+ NTSTATUS status;
+
+ client->connection_drop_subreq = NULL;
+
+ ret = messaging_filtered_read_recv(subreq, talloc_tos(), &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ goto next;
+ }
+
+ if (rec->num_fds != 0) {
+ DBG_ERR("MSG_SMBXSRV_CONNECTION_DROP: num_fds[%u]\n",
+ rec->num_fds);
+ goto next;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &drop_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_connection_dropB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("ndr_pull_struct_blob - %s\n", nt_errstr(status));
+ goto next;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+
+ if (drop_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("ignore invalid version %u\n", drop_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ goto next;
+ }
+
+ drop_info0 = drop_blob.info.info0;
+ if (drop_info0 == NULL) {
+ DBG_ERR("ignore NULL info %u\n", drop_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ goto next;
+ }
+
+ if (!GUID_equal(&client->global->client_guid, &drop_info0->client_guid))
+ {
+ struct GUID_txt_buf buf1, buf2;
+
+ DBG_WARNING("client's client_guid [%s] != dropped guid [%s]\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf1),
+ GUID_buf_string(&drop_info0->client_guid,
+ &buf2));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+ goto next;
+ }
+
+ if (client->global->initial_connect_time !=
+ drop_info0->client_connect_time)
+ {
+ DBG_WARNING("client's initial connect time [%s] (%llu) != "
+ "dropped initial connect time [%s] (%llu)\n",
+ nt_time_string(talloc_tos(),
+ client->global->initial_connect_time),
+ (unsigned long long)client->global->initial_connect_time,
+ nt_time_string(talloc_tos(),
+ drop_info0->client_connect_time),
+ (unsigned long long)drop_info0->client_connect_time);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_connection_dropB, &drop_blob);
+ }
+ goto next;
+ }
+
+ /*
+ * Disconnect all client connections, which means we will tear down all
+ * sessions, tcons and non-durable opens. At the end we will remove our
+ * smbXsrv_client_global.tdb record, which will wake up the watcher on
+ * the other node in order to let it take over the client.
+ *
+ * The client will have to reopen all sessions, tcons and durable opens.
+ */
+ smbd_server_disconnect_client(client,
+ server_id_str_buf(drop_info0->src_server_id, &src_server_id_buf));
+ return;
+
+next:
+ if (rec != NULL) {
+ int sock_fd;
+ uint8_t fd_idx;
+
+ for (fd_idx = 0; fd_idx < rec->num_fds; fd_idx++) {
+ sock_fd = rec->fds[fd_idx];
+ close(sock_fd);
+ }
+ rec->num_fds = 0;
+
+ TALLOC_FREE(rec);
+ }
+
+ subreq = messaging_filtered_read_send(client,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ smbXsrv_client_connection_drop_filter,
+ client);
+ if (subreq == NULL) {
+ const char *r;
+ r = "messaging_read_send(MSG_SMBXSRV_CONNECTION_DROP failed";
+ exit_server_cleanly(r);
+ return;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_client_connection_drop_loop, client);
+ client->connection_drop_subreq = subreq;
+}
+
+NTSTATUS smbXsrv_client_remove(struct smbXsrv_client *client)
+{
+ struct smbXsrv_client_table *table = client->table;
+ NTSTATUS status;
+
+ if (client->global->db_rec != NULL) {
+ struct GUID_txt_buf buf;
+ DBG_ERR("client_guid[%s]: Called with db_rec != NULL'\n",
+ GUID_buf_string(&client->global->client_guid,
+ &buf));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!client->global->stored) {
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(client->connection_pass_subreq);
+ TALLOC_FREE(client->connection_drop_subreq);
+
+ client->global->db_rec = smbXsrv_client_global_fetch_locked(
+ table->global.db_ctx,
+ &client->global->client_guid,
+ client->global /* TALLOC_CTX */);
+ if (client->global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = smbXsrv_client_global_remove(client->global);
+ if (!NT_STATUS_IS_OK(status)) {
+ struct GUID_txt_buf buf;
+ DBG_ERR("client_guid[%s] store failed - %s\n",
+ GUID_buf_string(&client->global->client_guid, &buf),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_clientB client_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = client,
+ };
+ struct GUID_txt_buf buf;
+
+ DBG_DEBUG("client_guid[%s] stored\n",
+ GUID_buf_string(&client->global->client_guid, &buf));
+ NDR_PRINT_DEBUG(smbXsrv_clientB, &client_blob);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/smbd/smbXsrv_open.c b/source3/smbd/smbXsrv_open.c
new file mode 100644
index 0000000..efabb73
--- /dev/null
+++ b/source3/smbd/smbXsrv_open.c
@@ -0,0 +1,1526 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2012
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "smbXsrv_open.h"
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "source3/include/util_tdb.h"
+#include "lib/util/idtree_random.h"
+#include "lib/util/time_basic.h"
+
+struct smbXsrv_open_table {
+ struct {
+ struct idr_context *idr;
+ struct db_context *replay_cache_db_ctx;
+ uint32_t lowest_id;
+ uint32_t highest_id;
+ uint32_t max_opens;
+ uint32_t num_opens;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_open_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_open_global_init(void)
+{
+ char *global_path = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_open_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ global_path = lock_path(talloc_tos(), "smbXsrv_open_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db_ctx = db_open(NULL, global_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(global_path);
+ if (db_ctx == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ smbXsrv_open_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+struct smbXsrv_open_global_key_buf { uint8_t buf[sizeof(uint32_t)]; };
+
+static TDB_DATA smbXsrv_open_global_id_to_key(
+ uint32_t id, struct smbXsrv_open_global_key_buf *key_buf)
+{
+ RSIVAL(key_buf->buf, 0, id);
+
+ return (TDB_DATA) {
+ .dptr = key_buf->buf,
+ .dsize = sizeof(key_buf->buf),
+ };
+}
+
+static NTSTATUS smbXsrv_open_table_init(struct smbXsrv_connection *conn,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ uint32_t max_opens)
+{
+ struct smbXsrv_client *client = conn->client;
+ struct smbXsrv_open_table *table;
+ NTSTATUS status;
+ uint64_t max_range;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ max_range = highest_id;
+ max_range -= lowest_id;
+ max_range += 1;
+
+ if (max_opens > max_range) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ table = talloc_zero(client, struct smbXsrv_open_table);
+ if (table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ table->local.idr = idr_init(table);
+ if (table->local.idr == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.replay_cache_db_ctx = db_open_rbt(table);
+ if (table->local.replay_cache_db_ctx == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.lowest_id = lowest_id;
+ table->local.highest_id = highest_id;
+ table->local.max_opens = max_opens;
+
+ status = smbXsrv_open_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(table);
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_open_global_db_ctx;
+
+ client->open_table = table;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_open_local_lookup(struct smbXsrv_open_table *table,
+ uint32_t open_local_id,
+ uint32_t open_global_id,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open *op = NULL;
+
+ *_open = NULL;
+
+ if (open_local_id == 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (table->local.idr == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ op = idr_find(table->local.idr, open_local_id);
+ if (op == NULL) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (open_global_id == 0) {
+ /* make the global check a no-op for SMB1 */
+ open_global_id = op->global->open_global_id;
+ }
+
+ if (op->global->open_global_id != open_global_id) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (now != 0) {
+ op->idle_time = now;
+ }
+
+ *_open = op;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_open_global_parse_record(
+ TALLOC_CTX *mem_ctx,
+ TDB_DATA key,
+ TDB_DATA val,
+ struct smbXsrv_open_global0 **global)
+{
+ DATA_BLOB blob = data_blob_const(val.dptr, val.dsize);
+ struct smbXsrv_open_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_open_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(1,("Invalid record in smbXsrv_open_global.tdb:"
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ ndr_errstr(ndr_err)));
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto done;
+ }
+
+ DBG_DEBUG("\n");
+ if (CHECK_DEBUGLVL(10)) {
+ NDR_PRINT_DEBUG(smbXsrv_open_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(1,("Invalid record in smbXsrv_open_global.tdb:"
+ "key '%s' unsupported version - %d - %s\n",
+ tdb_data_dbg(key),
+ (int)global_blob.version,
+ nt_errstr(status)));
+ goto done;
+ }
+
+ if (global_blob.info.info0 == NULL) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(1,("Invalid record in smbXsrv_open_global.tdb:"
+ "key '%s' info0 NULL pointer - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status)));
+ goto done;
+ }
+
+ *global = talloc_move(mem_ctx, &global_blob.info.info0);
+ status = NT_STATUS_OK;
+done:
+ talloc_free(frame);
+ return status;
+}
+
+static NTSTATUS smbXsrv_open_global_verify_record(
+ TDB_DATA key,
+ TDB_DATA val,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_open_global0 **_global0)
+{
+ struct smbXsrv_open_global0 *global0 = NULL;
+ struct server_id_buf buf;
+ NTSTATUS status;
+
+ if (val.dsize == 0) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ status = smbXsrv_open_global_parse_record(mem_ctx, key, val, &global0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("smbXsrv_open_global_parse_record for %s failed: "
+ "%s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ return status;
+ }
+ *_global0 = global0;
+
+ if (server_id_is_disconnected(&global0->server_id)) {
+ return NT_STATUS_OK;
+ }
+ if (serverid_exists(&global0->server_id)) {
+ return NT_STATUS_OK;
+ }
+
+ DBG_WARNING("smbd %s did not clean up record %s\n",
+ server_id_str_buf(global0->server_id, &buf),
+ tdb_data_dbg(key));
+
+ return NT_STATUS_FATAL_APP_EXIT;
+}
+
+static NTSTATUS smbXsrv_open_global_store(
+ struct db_record *rec,
+ TDB_DATA key,
+ TDB_DATA oldval,
+ struct smbXsrv_open_global0 *global)
+{
+ struct smbXsrv_open_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA val = { .dptr = NULL, };
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ global_blob = (struct smbXsrv_open_globalB) {
+ .version = smbXsrv_version_global_current(),
+ };
+
+ if (oldval.dsize >= 8) {
+ global_blob.seqnum = IVAL(oldval.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_open_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ ndr_map_error2string(ndr_err));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(rec, val, TDB_REPLACE);
+ TALLOC_FREE(blob.data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (CHECK_DEBUGLVL(10)) {
+ DBG_DEBUG("key '%s' stored\n", tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_open_globalB, &global_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct smbXsrv_open_global_allocate_state {
+ uint32_t id;
+ struct smbXsrv_open_global0 *global;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_global_allocate_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_global_allocate_state *state = private_data;
+ struct smbXsrv_open_global0 *global = state->global;
+ struct smbXsrv_open_global0 *tmp_global0 = NULL;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+
+ state->status = smbXsrv_open_global_verify_record(
+ key, oldval, talloc_tos(), &tmp_global0);
+
+ if (NT_STATUS_IS_OK(state->status)) {
+ /*
+ * Found an existing record
+ */
+ TALLOC_FREE(tmp_global0);
+ state->status = NT_STATUS_RETRY;
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_NOT_FOUND)) {
+ /*
+ * Found an empty slot
+ */
+ global->open_global_id = state->id;
+ global->open_persistent_id = state->id;
+
+ state->status = smbXsrv_open_global_store(
+ rec, key, (TDB_DATA) { .dsize = 0, }, state->global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("smbXsrv_open_global_store() for "
+ "id %"PRIu32" failed: %s\n",
+ state->id,
+ nt_errstr(state->status));
+ }
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(state->status, NT_STATUS_FATAL_APP_EXIT)) {
+ NTSTATUS status;
+
+ TALLOC_FREE(tmp_global0);
+
+ /*
+ * smbd crashed
+ */
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_record_delete() failed "
+ "for record %"PRIu32": %s\n",
+ state->id,
+ nt_errstr(status));
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return;
+ }
+ return;
+ }
+}
+
+static NTSTATUS smbXsrv_open_global_allocate(
+ struct db_context *db, struct smbXsrv_open_global0 *global)
+{
+ struct smbXsrv_open_global_allocate_state state = {
+ .global = global,
+ };
+ uint32_t i;
+ uint32_t last_free = 0;
+ const uint32_t min_tries = 3;
+
+ /*
+ * Here we just randomly try the whole 32-bit space
+ *
+ * We use just 32-bit, because we want to reuse the
+ * ID for SRVSVC.
+ */
+ for (i = 0; i < UINT32_MAX; i++) {
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key;
+ NTSTATUS status;
+
+ if (i >= min_tries && last_free != 0) {
+ state.id = last_free;
+ } else {
+ generate_nonce_buffer(
+ (uint8_t *)&state.id, sizeof(state.id));
+ state.id = MAX(state.id, 1);
+ state.id = MIN(state.id, UINT32_MAX-1);
+ }
+
+ key = smbXsrv_open_global_id_to_key(state.id, &key_buf);
+
+ status = dbwrap_do_locked(
+ db, key, smbXsrv_open_global_allocate_fn, &state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked() failed: %s\n",
+ nt_errstr(status));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (NT_STATUS_IS_OK(state.status)) {
+ /*
+ * Found an empty slot, done.
+ */
+ DBG_DEBUG("Found slot %"PRIu32"\n", state.id);
+ return NT_STATUS_OK;
+ }
+
+ if (NT_STATUS_EQUAL(state.status, NT_STATUS_FATAL_APP_EXIT)) {
+
+ if ((i < min_tries) && (last_free == 0)) {
+ /*
+ * Remember "id" as free but also try
+ * others to not recycle ids too
+ * quickly.
+ */
+ last_free = state.id;
+ }
+ continue;
+ }
+
+ if (NT_STATUS_EQUAL(state.status, NT_STATUS_RETRY)) {
+ /*
+ * Normal collision, try next
+ */
+ DBG_DEBUG("Found record for id %"PRIu32"\n",
+ state.id);
+ continue;
+ }
+
+ DBG_WARNING("smbXsrv_open_global_allocate_fn() failed: %s\n",
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ /* should not be reached */
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+static int smbXsrv_open_destructor(struct smbXsrv_open *op)
+{
+ NTSTATUS status;
+
+ status = smbXsrv_open_close(op, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("smbXsrv_open_destructor: "
+ "smbXsrv_open_close() failed - %s\n",
+ nt_errstr(status)));
+ }
+
+ TALLOC_FREE(op->global);
+
+ return 0;
+}
+
+NTSTATUS smbXsrv_open_create(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ struct smbXsrv_open *op = NULL;
+ struct smbXsrv_open_global0 *global = NULL;
+ NTSTATUS status;
+ struct dom_sid *current_sid = NULL;
+ struct security_token *current_token = NULL;
+ int local_id;
+
+ if (session_info == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ current_token = session_info->security_token;
+
+ if (current_token == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (current_token->num_sids > PRIMARY_USER_SID_INDEX) {
+ current_sid = &current_token->sids[PRIMARY_USER_SID_INDEX];
+ }
+
+ if (current_sid == NULL) {
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if (table->local.num_opens >= table->local.max_opens) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ op = talloc_zero(table, struct smbXsrv_open);
+ if (op == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ op->table = table;
+ op->status = NT_STATUS_OK; /* TODO: start with INTERNAL_ERROR */
+ op->idle_time = now;
+
+ global = talloc_zero(op, struct smbXsrv_open_global0);
+ if (global == NULL) {
+ TALLOC_FREE(op);
+ return NT_STATUS_NO_MEMORY;
+ }
+ op->global = global;
+
+ /*
+ * We mark every slot as invalid using 0xFF.
+ * Valid values are masked with 0xF.
+ */
+ memset(global->lock_sequence_array, 0xFF,
+ sizeof(global->lock_sequence_array));
+
+ local_id = idr_get_new_random(
+ table->local.idr,
+ op,
+ table->local.lowest_id,
+ table->local.highest_id);
+ if (local_id == -1) {
+ TALLOC_FREE(op);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ op->local_id = local_id;
+
+ global->open_volatile_id = op->local_id;
+
+ global->server_id = messaging_server_id(conn->client->msg_ctx);
+ global->open_time = now;
+ global->open_owner = *current_sid;
+ if (conn->protocol >= PROTOCOL_SMB2_10) {
+ global->client_guid = conn->smb2.client.guid;
+ }
+
+ status = smbXsrv_open_global_allocate(table->global.db_ctx,
+ global);
+ if (!NT_STATUS_IS_OK(status)) {
+ int ret = idr_remove(table->local.idr, local_id);
+ SMB_ASSERT(ret == 0);
+
+ DBG_WARNING("smbXsrv_open_global_allocate() failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(op);
+ return status;
+ }
+
+ table->local.num_opens += 1;
+ talloc_set_destructor(op, smbXsrv_open_destructor);
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = op,
+ };
+
+ DEBUG(10,("smbXsrv_open_create: global_id (0x%08x) stored\n",
+ op->global->open_global_id));
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+
+ *_open = op;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_open_set_replay_cache(struct smbXsrv_open *op)
+{
+ struct GUID *create_guid;
+ struct GUID_txt_buf buf;
+ char *guid_string;
+ struct db_context *db = op->table->local.replay_cache_db_ctx;
+ struct smbXsrv_open_replay_cache rc = {
+ .idle_time = op->idle_time,
+ .local_id = op->local_id,
+ };
+ uint8_t data[SMBXSRV_OPEN_REPLAY_CACHE_FIXED_SIZE] = { 0 };
+ DATA_BLOB blob = { .data = data, .length = sizeof(data), };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+ TDB_DATA val;
+
+ if (!(op->flags & SMBXSRV_OPEN_NEED_REPLAY_CACHE)) {
+ return NT_STATUS_OK;
+ }
+
+ if (op->flags & SMBXSRV_OPEN_HAVE_REPLAY_CACHE) {
+ return NT_STATUS_OK;
+ }
+
+ create_guid = &op->global->create_guid;
+ guid_string = GUID_buf_string(create_guid, &buf);
+
+ ndr_err = ndr_push_struct_into_fixed_blob(&blob, &rc,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_open_replay_cache);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ return status;
+ }
+ val = make_tdb_data(blob.data, blob.length);
+
+ status = dbwrap_store_bystring(db, guid_string, val, TDB_REPLACE);
+
+ if (NT_STATUS_IS_OK(status)) {
+ op->flags |= SMBXSRV_OPEN_HAVE_REPLAY_CACHE;
+ op->flags &= ~SMBXSRV_OPEN_NEED_REPLAY_CACHE;
+ }
+
+ return status;
+}
+
+NTSTATUS smbXsrv_open_purge_replay_cache(struct smbXsrv_client *client,
+ const struct GUID *create_guid)
+{
+ struct GUID_txt_buf buf;
+ char *guid_string;
+ struct db_context *db;
+
+ if (client->open_table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ db = client->open_table->local.replay_cache_db_ctx;
+
+ guid_string = GUID_buf_string(create_guid, &buf);
+ if (guid_string == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return dbwrap_purge_bystring(db, guid_string);
+}
+
+static NTSTATUS smbXsrv_open_clear_replay_cache(struct smbXsrv_open *op)
+{
+ struct GUID *create_guid;
+ struct GUID_txt_buf buf;
+ char *guid_string;
+ struct db_context *db;
+ NTSTATUS status;
+
+ if (op->table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ db = op->table->local.replay_cache_db_ctx;
+
+ if (!(op->flags & SMBXSRV_OPEN_HAVE_REPLAY_CACHE)) {
+ return NT_STATUS_OK;
+ }
+
+ create_guid = &op->global->create_guid;
+ if (GUID_all_zero(create_guid)) {
+ return NT_STATUS_OK;
+ }
+
+ guid_string = GUID_buf_string(create_guid, &buf);
+ if (guid_string == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dbwrap_purge_bystring(db, guid_string);
+
+ if (NT_STATUS_IS_OK(status)) {
+ op->flags &= ~SMBXSRV_OPEN_HAVE_REPLAY_CACHE;
+ }
+
+ return status;
+}
+
+struct smbXsrv_open_update_state {
+ struct smbXsrv_open_global0 *global;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_update_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_update_state *state = private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+
+ state->status = smbXsrv_open_global_store(
+ rec, key, oldval, state->global);
+}
+
+NTSTATUS smbXsrv_open_update(struct smbXsrv_open *op)
+{
+ struct smbXsrv_open_update_state state = { .global = op->global, };
+ struct smbXsrv_open_table *table = op->table;
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ op->global->open_global_id, &key_buf);
+ NTSTATUS status;
+
+ status = dbwrap_do_locked(
+ table->global.db_ctx, key, smbXsrv_open_update_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("global_id (0x%08x) dbwrap_do_locked failed: %s\n",
+ op->global->open_global_id,
+ nt_errstr(status));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_WARNING("global_id (0x%08x) smbXsrv_open_global_store "
+ "failed: %s\n",
+ op->global->open_global_id,
+ nt_errstr(state.status));
+ return state.status;
+ }
+
+ status = smbXsrv_open_set_replay_cache(op);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_open_set_replay_cache failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = op,
+ };
+
+ DEBUG(10,("smbXsrv_open_update: global_id (0x%08x) stored\n",
+ op->global->open_global_id));
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct smbXsrv_open_close_state {
+ struct smbXsrv_open *op;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_close_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_close_state *state = private_data;
+ struct smbXsrv_open_global0 *global = state->op->global;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+
+ if (global->durable) {
+ /*
+ * Durable open -- we need to update the global part
+ * instead of deleting it
+ */
+ state->status = smbXsrv_open_global_store(
+ rec, key, oldval, global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("failed to store global key '%s': %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ return;
+ }
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = state->op,
+ };
+
+ DBG_DEBUG("(0x%08x) stored disconnect\n",
+ global->open_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+ return;
+ }
+
+ state->status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("failed to delete global key '%s': %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ }
+}
+
+NTSTATUS smbXsrv_open_close(struct smbXsrv_open *op, NTTIME now)
+{
+ struct smbXsrv_open_close_state state = { .op = op, };
+ struct smbXsrv_open_global0 *global = op->global;
+ struct smbXsrv_open_table *table;
+ NTSTATUS status;
+ NTSTATUS error = NT_STATUS_OK;
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ global->open_global_id, &key_buf);
+ int ret;
+
+ error = smbXsrv_open_clear_replay_cache(op);
+ if (!NT_STATUS_IS_OK(error)) {
+ DBG_ERR("smbXsrv_open_clear_replay_cache failed: %s\n",
+ nt_errstr(error));
+ }
+
+ if (op->table == NULL) {
+ return error;
+ }
+
+ table = op->table;
+ op->table = NULL;
+
+ op->status = NT_STATUS_FILE_CLOSED;
+ global->disconnect_time = now;
+ server_id_set_disconnected(&global->server_id);
+
+ status = dbwrap_do_locked(
+ table->global.db_ctx, key, smbXsrv_open_close_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("dbwrap_do_locked() for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ } else if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_WARNING("smbXsrv_open_close_fn() for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state.status));
+ error = state.status;
+ }
+
+ ret = idr_remove(table->local.idr, op->local_id);
+ SMB_ASSERT(ret == 0);
+
+ table->local.num_opens -= 1;
+
+ if (op->compat) {
+ op->compat->op = NULL;
+ file_free(NULL, op->compat);
+ op->compat = NULL;
+ }
+
+ return error;
+}
+
+NTSTATUS smb1srv_open_table_init(struct smbXsrv_connection *conn)
+{
+ uint32_t max_opens;
+
+ /*
+ * Allow a range from 1..65534.
+ *
+ * With real_max_open_files possible ids,
+ * truncated to the SMB1 limit of 16-bit.
+ *
+ * 0 and 0xFFFF are no valid ids.
+ */
+ max_opens = conn->client->sconn->real_max_open_files;
+ max_opens = MIN(max_opens, UINT16_MAX - 1);
+
+ return smbXsrv_open_table_init(conn, 1, UINT16_MAX - 1, max_opens);
+}
+
+NTSTATUS smb1srv_open_lookup(struct smbXsrv_connection *conn,
+ uint16_t fnum, NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ uint32_t local_id = fnum;
+ uint32_t global_id = 0;
+
+ return smbXsrv_open_local_lookup(table, local_id, global_id, now, _open);
+}
+
+NTSTATUS smb2srv_open_table_init(struct smbXsrv_connection *conn)
+{
+ uint32_t max_opens;
+ uint32_t highest_id;
+
+ /*
+ * Allow a range from 1..4294967294.
+ *
+ * With real_max_open_files possible ids,
+ * truncated to 16-bit (the same as SMB1 for now).
+ *
+ * 0 and 0xFFFFFFFF are no valid ids.
+ *
+ * The usage of conn->sconn->real_max_open_files
+ * is the reason that we use one open table per
+ * transport connection (as we still have a 1:1 mapping
+ * between process and transport connection).
+ */
+ max_opens = conn->client->sconn->real_max_open_files;
+ max_opens = MIN(max_opens, UINT16_MAX - 1);
+
+ /*
+ * idtree uses "int" for local IDs. Limit the maximum ID to
+ * what "int" can hold.
+ */
+ highest_id = UINT32_MAX-1;
+ highest_id = MIN(highest_id, INT_MAX);
+
+ return smbXsrv_open_table_init(conn, 1, highest_id, max_opens);
+}
+
+NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
+ uint64_t persistent_id,
+ uint64_t volatile_id,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ uint32_t local_id = volatile_id & UINT32_MAX;
+ uint64_t local_zeros = volatile_id & 0xFFFFFFFF00000000LLU;
+ uint32_t global_id = persistent_id & UINT32_MAX;
+ uint64_t global_zeros = persistent_id & 0xFFFFFFFF00000000LLU;
+ NTSTATUS status;
+
+ if (local_zeros != 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (global_zeros != 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ if (global_id == 0) {
+ return NT_STATUS_FILE_CLOSED;
+ }
+
+ status = smbXsrv_open_local_lookup(table, local_id, global_id, now,
+ _open);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Clear the replay cache for this create_guid if it exists:
+ * This is based on the assumption that this lookup will be
+ * triggered by a client request using the file-id for lookup.
+ * Hence the client has proven that it has in fact seen the
+ * reply to its initial create call. So subsequent create replays
+ * should be treated as invalid. Hence the index for create_guid
+ * lookup needs to be removed.
+ */
+ status = smbXsrv_open_clear_replay_cache(*_open);
+
+ return status;
+}
+
+/*
+ * This checks or marks the replay cache, we have the following
+ * cases:
+ *
+ * 1. There is no record in the cache
+ * => we add the passes caller_req_guid as holder_req_guid
+ * together with local_id as 0.
+ * => We return STATUS_FWP_RESERVED in order to indicate
+ * that the caller holds the current reservation
+ *
+ * 2. There is a record in the cache and holder_req_guid
+ * is already the same as caller_req_guid and local_id is 0
+ * => We return STATUS_FWP_RESERVED in order to indicate
+ * that the caller holds the current reservation
+ *
+ * 3. There is a record in the cache with a holder_req_guid
+ * other than caller_req_guid (and local_id is 0):
+ * => We return NT_STATUS_FILE_NOT_AVAILABLE to indicate
+ * the original request is still pending
+ *
+ * 4. There is a record in the cache with a zero holder_req_guid
+ * and a valid local_id:
+ * => We lookup the existing open by local_id
+ * => We return NT_STATUS_OK together with the smbXsrv_open
+ *
+ *
+ * With NT_STATUS_OK the caller can continue the replay processing.
+ *
+ * With STATUS_FWP_RESERVED the caller should continue the normal
+ * open processing:
+ * - On success:
+ * - smbXsrv_open_update()/smbXsrv_open_set_replay_cache()
+ * will convert the record to a zero holder_req_guid
+ * with a valid local_id.
+ * - On failure:
+ * - smbXsrv_open_purge_replay_cache() should cleanup
+ * the reservation.
+ *
+ * All other values should be returned to the client,
+ * while NT_STATUS_FILE_NOT_AVAILABLE will trigger the
+ * retry loop on the client.
+ */
+NTSTATUS smb2srv_open_lookup_replay_cache(struct smbXsrv_connection *conn,
+ struct GUID caller_req_guid,
+ struct GUID create_guid,
+ const char *name,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ struct db_context *db = table->local.replay_cache_db_ctx;
+ struct GUID_txt_buf tmp_guid_buf;
+ struct GUID_txt_buf _create_guid_buf;
+ const char *create_guid_str = GUID_buf_string(&create_guid, &_create_guid_buf);
+ TDB_DATA create_guid_key = string_term_tdb_data(create_guid_str);
+ struct db_record *db_rec = NULL;
+ struct smbXsrv_open *op = NULL;
+ struct smbXsrv_open_replay_cache rc = {
+ .holder_req_guid = caller_req_guid,
+ .idle_time = now,
+ .local_id = 0,
+ };
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA val;
+
+ *_open = NULL;
+
+ db_rec = dbwrap_fetch_locked(db, frame, create_guid_key);
+ if (db_rec == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ uint8_t data[SMBXSRV_OPEN_REPLAY_CACHE_FIXED_SIZE];
+
+ blob = data_blob_const(data, ARRAY_SIZE(data));
+ ndr_err = ndr_push_struct_into_fixed_blob(&blob, &rc,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_open_replay_cache);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We're the new holder
+ */
+ *_open = NULL;
+ TALLOC_FREE(frame);
+ return NT_STATUS_FWP_RESERVED;
+ }
+
+ if (val.dsize != SMBXSRV_OPEN_REPLAY_CACHE_FIXED_SIZE) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+ ndr_err = ndr_pull_struct_blob_all_noalloc(&blob, &rc,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_open_replay_cache);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ if (rc.local_id != 0) {
+ if (GUID_equal(&rc.holder_req_guid, &caller_req_guid)) {
+ /*
+ * This should not happen
+ */
+ status = NT_STATUS_INTERNAL_ERROR;
+ DBG_ERR("caller %s already holds local_id %u for create %s [%s] - %s\n",
+ GUID_buf_string(&caller_req_guid, &tmp_guid_buf),
+ (unsigned)rc.local_id,
+ create_guid_str,
+ name,
+ nt_errstr(status));
+
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = smbXsrv_open_local_lookup(table,
+ rc.local_id,
+ 0, /* global_id */
+ now,
+ &op);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("holder %s stale for local_id %u for create %s [%s] - %s\n",
+ GUID_buf_string(&rc.holder_req_guid, &tmp_guid_buf),
+ (unsigned)rc.local_id,
+ create_guid_str,
+ name,
+ nt_errstr(status));
+
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ /*
+ * We found an open the caller can reuse.
+ */
+ SMB_ASSERT(op != NULL);
+ *_open = op;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ if (GUID_equal(&rc.holder_req_guid, &caller_req_guid)) {
+ /*
+ * We're still the holder
+ */
+ *_open = NULL;
+ TALLOC_FREE(frame);
+ return NT_STATUS_FWP_RESERVED;
+ }
+
+ /*
+ * The original request (or a former replay) is still
+ * pending, ask the client to retry by sending FILE_NOT_AVAILABLE.
+ */
+ status = NT_STATUS_FILE_NOT_AVAILABLE;
+ DBG_DEBUG("holder %s still pending for create %s [%s] - %s\n",
+ GUID_buf_string(&rc.holder_req_guid, &tmp_guid_buf),
+ create_guid_str,
+ name,
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+}
+
+struct smb2srv_open_recreate_state {
+ struct smbXsrv_open *op;
+ const struct GUID *create_guid;
+ struct security_token *current_token;
+ struct server_id me;
+
+ NTSTATUS status;
+};
+
+static void smb2srv_open_recreate_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smb2srv_open_recreate_state *state = private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ struct smbXsrv_open_global0 *global = NULL;
+
+ state->status = smbXsrv_open_global_verify_record(
+ key, oldval, state->op, &state->op->global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("smbXsrv_open_global_verify_record for %s "
+ "failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ goto not_found;
+ }
+ global = state->op->global;
+
+ /*
+ * If the provided create_guid is NULL, this means that
+ * the reconnect request was a v1 request. In that case
+ * we should skip the create GUID verification, since
+ * it is valid to v1-reconnect a v2-opened handle.
+ */
+ if ((state->create_guid != NULL) &&
+ !GUID_equal(&global->create_guid, state->create_guid)) {
+ struct GUID_txt_buf buf1, buf2;
+ DBG_NOTICE("%s != %s in %s\n",
+ GUID_buf_string(&global->create_guid, &buf1),
+ GUID_buf_string(state->create_guid, &buf2),
+ tdb_data_dbg(key));
+ goto not_found;
+ }
+
+ if (!security_token_is_sid(
+ state->current_token, &global->open_owner)) {
+ struct dom_sid_buf buf;
+ DBG_NOTICE("global owner %s not in our token in %s\n",
+ dom_sid_str_buf(&global->open_owner, &buf),
+ tdb_data_dbg(key));
+ goto not_found;
+ }
+
+ if (!global->durable) {
+ DBG_NOTICE("%"PRIu64"/%"PRIu64" not durable in %s\n",
+ global->open_persistent_id,
+ global->open_volatile_id,
+ tdb_data_dbg(key));
+ goto not_found;
+ }
+
+ global->open_volatile_id = state->op->local_id;
+ global->server_id = state->me;
+
+ state->status = smbXsrv_open_global_store(rec, key, oldval, global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("smbXsrv_open_global_store for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(state->status));
+ return;
+ }
+ return;
+
+not_found:
+ state->status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+NTSTATUS smb2srv_open_recreate(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t persistent_id,
+ const struct GUID *create_guid,
+ NTTIME now,
+ struct smbXsrv_open **_open)
+{
+ struct smbXsrv_open_table *table = conn->client->open_table;
+ struct smb2srv_open_recreate_state state = {
+ .create_guid = create_guid,
+ .me = messaging_server_id(conn->client->msg_ctx),
+ };
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ persistent_id & UINT32_MAX, &key_buf);
+ int ret, local_id;
+ NTSTATUS status;
+
+ if (session_info == NULL) {
+ DEBUG(10, ("session_info=NULL\n"));
+ return NT_STATUS_INVALID_HANDLE;
+ }
+ state.current_token = session_info->security_token;
+
+ if (state.current_token == NULL) {
+ DEBUG(10, ("current_token=NULL\n"));
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ if ((persistent_id & 0xFFFFFFFF00000000LLU) != 0) {
+ /*
+ * We only use 32 bit for the persistent ID
+ */
+ DBG_DEBUG("persistent_id=%"PRIx64"\n", persistent_id);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (table->local.num_opens >= table->local.max_opens) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ state.op = talloc_zero(table, struct smbXsrv_open);
+ if (state.op == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state.op->table = table;
+
+ local_id = idr_get_new_random(
+ table->local.idr,
+ state.op,
+ table->local.lowest_id,
+ table->local.highest_id);
+ if (local_id == -1) {
+ TALLOC_FREE(state.op);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ state.op->local_id = local_id;
+ SMB_ASSERT(state.op->local_id == local_id); /* No coercion loss */
+
+ table->local.num_opens += 1;
+
+ state.op->idle_time = now;
+ state.op->status = NT_STATUS_FILE_CLOSED;
+
+ status = dbwrap_do_locked(
+ table->global.db_ctx, key, smb2srv_open_recreate_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dbwrap_do_locked() for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ status = state.status;
+ DBG_DEBUG("smb2srv_open_recreate_fn for %s failed: %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ goto fail;
+ }
+
+ talloc_set_destructor(state.op, smbXsrv_open_destructor);
+
+ if (CHECK_DEBUGLVL(10)) {
+ struct smbXsrv_openB open_blob = {
+ .info.info0 = state.op,
+ };
+ DBG_DEBUG("global_id (0x%08x) stored\n",
+ state.op->global->open_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_openB, &open_blob);
+ }
+
+ *_open = state.op;
+
+ return NT_STATUS_OK;
+fail:
+ table->local.num_opens -= 1;
+
+ ret = idr_remove(table->local.idr, state.op->local_id);
+ SMB_ASSERT(ret == 0);
+ TALLOC_FREE(state.op);
+ return status;
+}
+
+struct smbXsrv_open_global_traverse_state {
+ int (*fn)(struct db_record *rec, struct smbXsrv_open_global0 *, void *);
+ void *private_data;
+};
+
+static int smbXsrv_open_global_traverse_fn(struct db_record *rec, void *data)
+{
+ struct smbXsrv_open_global_traverse_state *state =
+ (struct smbXsrv_open_global_traverse_state*)data;
+ struct smbXsrv_open_global0 *global = NULL;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ NTSTATUS status;
+ int ret = -1;
+
+ status = smbXsrv_open_global_parse_record(
+ talloc_tos(), key, val, &global);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ ret = state->fn(rec, global, state->private_data);
+ talloc_free(global);
+ return ret;
+}
+
+NTSTATUS smbXsrv_open_global_traverse(
+ int (*fn)(struct db_record *rec, struct smbXsrv_open_global0 *, void *),
+ void *private_data)
+{
+
+ NTSTATUS status;
+ int count = 0;
+ struct smbXsrv_open_global_traverse_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ become_root();
+ status = smbXsrv_open_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ DEBUG(0, ("Failed to initialize open_global: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = dbwrap_traverse_read(smbXsrv_open_global_db_ctx,
+ smbXsrv_open_global_traverse_fn,
+ &state,
+ &count);
+ unbecome_root();
+
+ return status;
+}
+
+struct smbXsrv_open_cleanup_state {
+ uint32_t global_id;
+ NTSTATUS status;
+};
+
+static void smbXsrv_open_cleanup_fn(
+ struct db_record *rec, TDB_DATA oldval, void *private_data)
+{
+ struct smbXsrv_open_cleanup_state *state = private_data;
+ struct smbXsrv_open_global0 *global = NULL;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ bool delete_open = false;
+
+ if (oldval.dsize == 0) {
+ DBG_DEBUG("[global: 0x%08x] "
+ "empty record in %s, skipping...\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)));
+ state->status = NT_STATUS_OK;
+ return;
+ }
+
+ state->status = smbXsrv_open_global_parse_record(
+ talloc_tos(), key, oldval, &global);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("[global: %x08x] "
+ "smbXsrv_open_global_parse_record() in %s "
+ "failed: %s, deleting record\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)),
+ nt_errstr(state->status));
+ delete_open = true;
+ goto do_delete;
+ }
+
+ if (server_id_is_disconnected(&global->server_id)) {
+ struct timeval now = timeval_current();
+ struct timeval disconnect_time;
+ struct timeval_buf buf;
+ int64_t tdiff;
+
+ nttime_to_timeval(&disconnect_time, global->disconnect_time);
+ tdiff = usec_time_diff(&now, &disconnect_time);
+ delete_open = (tdiff >= 1000*global->durable_timeout_msec);
+
+ DBG_DEBUG("[global: 0x%08x] "
+ "disconnected at [%s] %"PRIi64"s ago with "
+ "timeout of %"PRIu32"s -%s reached\n",
+ state->global_id,
+ timeval_str_buf(&disconnect_time,
+ false,
+ false,
+ &buf),
+ tdiff/1000000,
+ global->durable_timeout_msec / 1000,
+ delete_open ? "" : " not");
+ } else if (!serverid_exists(&global->server_id)) {
+ struct server_id_buf idbuf;
+ DBG_DEBUG("[global: 0x%08x] "
+ "server[%s] does not exist\n",
+ state->global_id,
+ server_id_str_buf(global->server_id, &idbuf));
+ delete_open = true;
+ }
+
+ if (!delete_open) {
+ state->status = NT_STATUS_OK;
+ return;
+ }
+do_delete:
+ state->status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(state->status)) {
+ DBG_WARNING("[global: 0x%08x] "
+ "failed to delete record "
+ "from %s: %s\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)),
+ nt_errstr(state->status));
+ return;
+ }
+
+ DBG_DEBUG("[global: 0x%08x] "
+ "deleted record from %s\n",
+ state->global_id,
+ dbwrap_name(dbwrap_record_get_db(rec)));
+}
+
+NTSTATUS smbXsrv_open_cleanup(uint64_t persistent_id)
+{
+ struct smbXsrv_open_cleanup_state state = {
+ .global_id = persistent_id & UINT32_MAX,
+ };
+ struct smbXsrv_open_global_key_buf key_buf;
+ TDB_DATA key = smbXsrv_open_global_id_to_key(
+ state.global_id, &key_buf);
+ NTSTATUS status;
+
+ status = dbwrap_do_locked(
+ smbXsrv_open_global_db_ctx,
+ key,
+ smbXsrv_open_cleanup_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("[global: 0x%08x] dbwrap_do_locked failed: %s\n",
+ state.global_id,
+ nt_errstr(status));
+ return status;
+ }
+
+ return state.status;
+}
diff --git a/source3/smbd/smbXsrv_open.h b/source3/smbd/smbXsrv_open.h
new file mode 100644
index 0000000..0257650
--- /dev/null
+++ b/source3/smbd/smbXsrv_open.h
@@ -0,0 +1,75 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Stefan Metzmacher 2012
+ * Copyright (C) Michael Adam 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SMBXSRV_OPEN_H__
+#define __SMBXSRV_OPEN_H__
+
+#include "replace.h"
+#include "libcli/util/ntstatus.h"
+#include "lib/util/time.h"
+#include "lib/util/data_blob.h"
+#include "librpc/gen_ndr/misc.h"
+
+struct smbXsrv_connection;
+struct auth_session_info;
+struct smbXsrv_open;
+struct smbXsrv_open_global0;
+struct smbXsrv_client;
+
+NTSTATUS smbXsrv_open_global_init(void);
+NTSTATUS smbXsrv_open_create(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smbXsrv_open_update(struct smbXsrv_open *_open);
+NTSTATUS smbXsrv_open_close(struct smbXsrv_open *op, NTTIME now);
+NTSTATUS smb1srv_open_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb1srv_open_lookup(struct smbXsrv_connection *conn,
+ uint16_t fnum, NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smb2srv_open_table_init(struct smbXsrv_connection *conn);
+NTSTATUS smb2srv_open_lookup(struct smbXsrv_connection *conn,
+ uint64_t persistent_id,
+ uint64_t volatile_id,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smbXsrv_open_purge_replay_cache(struct smbXsrv_client *client,
+ const struct GUID *create_guid);
+NTSTATUS smb2srv_open_lookup_replay_cache(struct smbXsrv_connection *conn,
+ struct GUID caller_req_guid,
+ struct GUID create_guid,
+ const char *name,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+NTSTATUS smb2srv_open_recreate(struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t persistent_id,
+ const struct GUID *create_guid,
+ NTTIME now,
+ struct smbXsrv_open **_open);
+
+struct db_record;
+NTSTATUS smbXsrv_open_global_traverse(
+ int (*fn)(struct db_record *rec, struct smbXsrv_open_global0 *, void *),
+ void *private_data);
+
+NTSTATUS smbXsrv_open_cleanup(uint64_t persistent_id);
+
+#endif
diff --git a/source3/smbd/smbXsrv_session.c b/source3/smbd/smbXsrv_session.c
new file mode 100644
index 0000000..a88b7c1
--- /dev/null
+++ b/source3/smbd/smbXsrv_session.c
@@ -0,0 +1,2527 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2011-2012
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include <tevent.h>
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_watch.h"
+#include "session.h"
+#include "auth.h"
+#include "auth/gensec/gensec.h"
+#include "../lib/tsocket/tsocket.h"
+#include "../libcli/security/security.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/global_contexts.h"
+#include "source3/include/util_tdb.h"
+
+struct smbXsrv_session_table {
+ struct {
+ struct db_context *db_ctx;
+ uint32_t lowest_id;
+ uint32_t highest_id;
+ uint32_t max_sessions;
+ uint32_t num_sessions;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_session_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_session_global_init(struct messaging_context *msg_ctx)
+{
+ char *global_path = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_session_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * This contains secret information like session keys!
+ */
+ global_path = lock_path(talloc_tos(), "smbXsrv_session_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ backend = db_open(NULL, global_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(global_path);
+ if (backend == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ db_ctx = db_open_watched(NULL, &backend, global_messaging_context());
+ if (db_ctx == NULL) {
+ TALLOC_FREE(backend);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smbXsrv_session_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+#define SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_session_global_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+#if 0
+static NTSTATUS smbXsrv_session_global_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+#endif
+
+#define SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_session_local_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+static NTSTATUS smbXsrv_session_local_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+
+static struct db_record *smbXsrv_session_global_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_SESSION_GLOBAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_session_global_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock global id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static struct db_record *smbXsrv_session_local_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_session_local_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock local id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static void smbXsrv_session_close_loop(struct tevent_req *subreq);
+
+static NTSTATUS smbXsrv_session_table_init(struct smbXsrv_connection *conn,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ uint32_t max_sessions)
+{
+ struct smbXsrv_client *client = conn->client;
+ struct smbXsrv_session_table *table;
+ NTSTATUS status;
+ struct tevent_req *subreq;
+ uint64_t max_range;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ max_range = highest_id;
+ max_range -= lowest_id;
+ max_range += 1;
+
+ if (max_sessions > max_range) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ table = talloc_zero(client, struct smbXsrv_session_table);
+ if (table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ table->local.db_ctx = db_open_rbt(table);
+ if (table->local.db_ctx == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.lowest_id = lowest_id;
+ table->local.highest_id = highest_id;
+ table->local.max_sessions = max_sessions;
+
+ status = smbXsrv_session_global_init(client->msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(table);
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_session_global_db_ctx;
+
+ subreq = messaging_read_send(table,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ MSG_SMBXSRV_SESSION_CLOSE);
+ if (subreq == NULL) {
+ TALLOC_FREE(table);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_session_close_loop, client);
+
+ client->session_table = table;
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_session_close_shutdown_done(struct tevent_req *subreq);
+
+static void smbXsrv_session_close_loop(struct tevent_req *subreq)
+{
+ struct smbXsrv_client *client =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_client);
+ struct smbXsrv_session_table *table = client->session_table;
+ int ret;
+ struct messaging_rec *rec = NULL;
+ struct smbXsrv_session_closeB close_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_session_close0 *close_info0 = NULL;
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+
+ ret = messaging_read_recv(subreq, talloc_tos(), &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ goto next;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&rec->buf, rec, &close_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_session_closeB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "ndr_pull_struct_blob - %s\n",
+ nt_errstr(status));
+ goto next;
+ }
+
+ DBG_DEBUG("smbXsrv_session_close_loop: MSG_SMBXSRV_SESSION_CLOSE\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+
+ if (close_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "ignore invalid version %u\n", close_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ goto next;
+ }
+
+ close_info0 = close_blob.info.info0;
+ if (close_info0 == NULL) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "ignore NULL info %u\n", close_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ goto next;
+ }
+
+ status = smb2srv_session_lookup_client(client,
+ close_info0->old_session_wire_id,
+ now, &session);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ DBG_INFO("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu not found\n",
+ (unsigned long long)close_info0->old_session_wire_id);
+ if (DEBUGLVL(DBGLVL_INFO)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu - %s\n",
+ (unsigned long long)close_info0->old_session_wire_id,
+ nt_errstr(status));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+
+ if (session->global->session_global_id != close_info0->old_session_global_id) {
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu - global %u != %u\n",
+ (unsigned long long)close_info0->old_session_wire_id,
+ session->global->session_global_id,
+ close_info0->old_session_global_id);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+
+ if (session->global->creation_time != close_info0->old_creation_time) {
+ DBG_WARNING("smbXsrv_session_close_loop: "
+ "old_session_wire_id %llu - "
+ "creation %s (%llu) != %s (%llu)\n",
+ (unsigned long long)close_info0->old_session_wire_id,
+ nt_time_string(rec, session->global->creation_time),
+ (unsigned long long)session->global->creation_time,
+ nt_time_string(rec, close_info0->old_creation_time),
+ (unsigned long long)close_info0->old_creation_time);
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+
+ subreq = smb2srv_session_shutdown_send(session, client->raw_ev_ctx,
+ session, NULL);
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "smb2srv_session_shutdown_send(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ if (DEBUGLVL(DBGLVL_WARNING)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_closeB, &close_blob);
+ }
+ goto next;
+ }
+ tevent_req_set_callback(subreq,
+ smbXsrv_session_close_shutdown_done,
+ session);
+
+next:
+ TALLOC_FREE(rec);
+
+ subreq = messaging_read_send(table,
+ client->raw_ev_ctx,
+ client->msg_ctx,
+ MSG_SMBXSRV_SESSION_CLOSE);
+ if (subreq == NULL) {
+ const char *r;
+ r = "messaging_read_send(MSG_SMBXSRV_SESSION_CLOSE) failed";
+ exit_server_cleanly(r);
+ return;
+ }
+ tevent_req_set_callback(subreq, smbXsrv_session_close_loop, client);
+}
+
+static void smbXsrv_session_close_shutdown_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_session *session =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_session);
+ NTSTATUS status;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "smb2srv_session_shutdown_recv(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ status = smbXsrv_session_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_close_loop: "
+ "smbXsrv_session_logoff(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(session);
+}
+
+struct smb1srv_session_local_allocate_state {
+ const uint32_t lowest_id;
+ const uint32_t highest_id;
+ uint32_t last_id;
+ uint32_t useable_id;
+ NTSTATUS status;
+};
+
+static int smb1srv_session_local_allocate_traverse(struct db_record *rec,
+ void *private_data)
+{
+ struct smb1srv_session_local_allocate_state *state =
+ (struct smb1srv_session_local_allocate_state *)private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ uint32_t id = 0;
+ NTSTATUS status;
+
+ status = smbXsrv_session_local_key_to_id(key, &id);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->status = status;
+ return -1;
+ }
+
+ if (id <= state->last_id) {
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+ state->last_id = id;
+
+ if (id > state->useable_id) {
+ state->status = NT_STATUS_OK;
+ return -1;
+ }
+
+ if (state->useable_id == state->highest_id) {
+ state->status = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return -1;
+ }
+
+ state->useable_id +=1;
+ return 0;
+}
+
+static NTSTATUS smb1srv_session_local_allocate_id(struct db_context *db,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ TALLOC_CTX *mem_ctx,
+ struct db_record **_rec,
+ uint32_t *_id)
+{
+ struct smb1srv_session_local_allocate_state state = {
+ .lowest_id = lowest_id,
+ .highest_id = highest_id,
+ .last_id = 0,
+ .useable_id = lowest_id,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint32_t i;
+ uint32_t range;
+ NTSTATUS status;
+ int count = 0;
+
+ *_rec = NULL;
+ *_id = 0;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /*
+ * first we try randomly
+ */
+ range = (highest_id - lowest_id) + 1;
+
+ for (i = 0; i < (range / 2); i++) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = generate_random() % range;
+ id += lowest_id;
+
+ if (id < lowest_id) {
+ id = lowest_id;
+ }
+ if (id > highest_id) {
+ id = highest_id;
+ }
+
+ rec = smbXsrv_session_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ continue;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * if the range is almost full,
+ * we traverse the whole table
+ * (this relies on sorted behavior of dbwrap_rbt)
+ */
+ status = dbwrap_traverse_read(db, smb1srv_session_local_allocate_traverse,
+ &state, &count);
+ if (NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state.status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!NT_STATUS_EQUAL(state.status, NT_STATUS_INTERNAL_ERROR)) {
+ return state.status;
+ }
+
+ if (state.useable_id <= state.highest_id) {
+ state.status = NT_STATUS_OK;
+ } else {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ } else if (!NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_DB_CORRUPTION)) {
+ /*
+ * Here we really expect NT_STATUS_INTERNAL_DB_CORRUPTION!
+ *
+ * If we get anything else it is an error, because it
+ * means we did not manage to find a free slot in
+ * the db.
+ */
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (NT_STATUS_IS_OK(state.status)) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = state.useable_id;
+
+ rec = smbXsrv_session_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ return state.status;
+}
+
+struct smbXsrv_session_local_fetch_state {
+ struct smbXsrv_session *session;
+ NTSTATUS status;
+};
+
+static void smbXsrv_session_local_fetch_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct smbXsrv_session_local_fetch_state *state =
+ (struct smbXsrv_session_local_fetch_state *)private_data;
+ void *ptr;
+
+ if (data.dsize != sizeof(ptr)) {
+ state->status = NT_STATUS_INTERNAL_DB_ERROR;
+ return;
+ }
+
+ memcpy(&ptr, data.dptr, data.dsize);
+ state->session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+ state->status = NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_session_local_lookup(struct smbXsrv_session_table *table,
+ /* conn: optional */
+ struct smbXsrv_connection *conn,
+ uint32_t session_local_id,
+ NTTIME now,
+ struct smbXsrv_session **_session)
+{
+ struct smbXsrv_session_local_fetch_state state = {
+ .session = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ TDB_DATA key;
+ NTSTATUS status;
+
+ *_session = NULL;
+
+ if (session_local_id == 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_session_local_id_to_key(session_local_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_session_local_fetch_parser,
+ &state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+
+ if (NT_STATUS_EQUAL(state.session->status, NT_STATUS_USER_SESSION_DELETED)) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ /*
+ * If a connection is specified check if the session is
+ * valid on the channel.
+ */
+ if (conn != NULL) {
+ struct smbXsrv_channel_global0 *c = NULL;
+
+ status = smbXsrv_session_find_channel(state.session, conn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ state.session->idle_time = now;
+
+ if (!NT_STATUS_IS_OK(state.session->status)) {
+ *_session = state.session;
+ return state.session->status;
+ }
+
+ if (now > state.session->global->expiration_time) {
+ state.session->status = NT_STATUS_NETWORK_SESSION_EXPIRED;
+ }
+
+ *_session = state.session;
+ return state.session->status;
+}
+
+static int smbXsrv_session_global_destructor(struct smbXsrv_session_global0 *global)
+{
+ return 0;
+}
+
+static void smbXsrv_session_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session_global0 **_g,
+ uint32_t *pseqnum);
+
+static NTSTATUS smbXsrv_session_global_allocate(struct db_context *db,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session_global0 **_global)
+{
+ uint32_t i;
+ struct smbXsrv_session_global0 *global = NULL;
+ uint32_t last_free = 0;
+ const uint32_t min_tries = 3;
+
+ *_global = NULL;
+
+ global = talloc_zero(mem_ctx, struct smbXsrv_session_global0);
+ if (global == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(global, smbXsrv_session_global_destructor);
+
+ /*
+ * Here we just randomly try the whole 32-bit space
+ *
+ * We use just 32-bit, because we want to reuse the
+ * ID for SRVSVC.
+ */
+ for (i = 0; i < UINT32_MAX; i++) {
+ bool is_free = false;
+ bool was_free = false;
+ uint32_t id;
+
+ if (i >= min_tries && last_free != 0) {
+ id = last_free;
+ } else {
+ id = generate_random();
+ }
+ if (id == 0) {
+ id++;
+ }
+ if (id == UINT32_MAX) {
+ id--;
+ }
+
+ global->db_rec = smbXsrv_session_global_fetch_locked(db, id,
+ mem_ctx);
+ if (global->db_rec == NULL) {
+ talloc_free(global);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ smbXsrv_session_global_verify_record(global->db_rec,
+ &is_free,
+ &was_free,
+ NULL, NULL, NULL);
+
+ if (!is_free) {
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ if (!was_free && i < min_tries) {
+ /*
+ * The session_id is free now,
+ * but was not free before.
+ *
+ * This happens if a smbd crashed
+ * and did not cleanup the record.
+ *
+ * If this is one of our first tries,
+ * then we try to find a real free one.
+ */
+ if (last_free == 0) {
+ last_free = id;
+ }
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ global->session_global_id = id;
+
+ *_global = global;
+ return NT_STATUS_OK;
+ }
+
+ /* should not be reached */
+ talloc_free(global);
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+static void smbXsrv_session_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session_global0 **_g,
+ uint32_t *pseqnum)
+{
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_session_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_session_global0 *global = NULL;
+ bool exists;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *is_free = false;
+
+ if (was_free) {
+ *was_free = false;
+ }
+ if (_g) {
+ *_g = NULL;
+ }
+ if (pseqnum) {
+ *pseqnum = 0;
+ }
+
+ key = dbwrap_record_get_key(db_rec);
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_session_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smbXsrv_session_global_verify_record: "
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ DBG_DEBUG("smbXsrv_session_global_verify_record\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("smbXsrv_session_global_verify_record: "
+ "key '%s' use unsupported version %u\n",
+ tdb_data_dbg(key),
+ global_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ global = global_blob.info.info0;
+
+#define __BLOB_KEEP_SECRET(__blob) do { \
+ if ((__blob).length != 0) { \
+ talloc_keep_secret((__blob).data); \
+ } \
+} while(0)
+ {
+ uint32_t i;
+ __BLOB_KEEP_SECRET(global->application_key_blob);
+ __BLOB_KEEP_SECRET(global->signing_key_blob);
+ __BLOB_KEEP_SECRET(global->encryption_key_blob);
+ __BLOB_KEEP_SECRET(global->decryption_key_blob);
+ for (i = 0; i < global->num_channels; i++) {
+ __BLOB_KEEP_SECRET(global->channels[i].signing_key_blob);
+ }
+ }
+#undef __BLOB_KEEP_SECRET
+
+ exists = serverid_exists(&global->channels[0].server_id);
+ if (!exists) {
+ struct server_id_buf idbuf;
+ DBG_NOTICE("smbXsrv_session_global_verify_record: "
+ "key '%s' server_id %s does not exist.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->channels[0].server_id,
+ &idbuf));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ if (_g) {
+ *_g = talloc_move(mem_ctx, &global);
+ }
+ if (pseqnum) {
+ *pseqnum = global_blob.seqnum;
+ }
+ TALLOC_FREE(frame);
+}
+
+static NTSTATUS smbXsrv_session_global_store(struct smbXsrv_session_global0 *global)
+{
+ struct smbXsrv_session_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA key;
+ TDB_DATA val;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+ val = dbwrap_record_get_value(global->db_rec);
+
+ ZERO_STRUCT(global_blob);
+ global_blob.version = smbXsrv_version_global_current();
+ if (val.dsize >= 8) {
+ global_blob.seqnum = IVAL(val.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, global->db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_session_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smbXsrv_session_global_store: key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(global->db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("smbXsrv_session_global_store: key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("smbXsrv_session_global_store: key '%s' stored\n",
+ tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_session_globalB, &global_blob);
+ }
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+struct smb2srv_session_close_previous_state {
+ struct tevent_context *ev;
+ struct smbXsrv_connection *connection;
+ struct dom_sid *current_sid;
+ uint64_t previous_session_id;
+ uint64_t current_session_id;
+ struct db_record *db_rec;
+ uint64_t watch_instance;
+ uint32_t last_seqnum;
+};
+
+static void smb2srv_session_close_previous_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct smb2srv_session_close_previous_state *state =
+ tevent_req_data(req,
+ struct smb2srv_session_close_previous_state);
+
+ if (state->db_rec != NULL) {
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance = 0;
+ TALLOC_FREE(state->db_rec);
+ }
+}
+
+static void smb2srv_session_close_previous_check(struct tevent_req *req);
+static void smb2srv_session_close_previous_modified(struct tevent_req *subreq);
+
+struct tevent_req *smb2srv_session_close_previous_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_connection *conn,
+ struct auth_session_info *session_info,
+ uint64_t previous_session_id,
+ uint64_t current_session_id)
+{
+ struct tevent_req *req;
+ struct smb2srv_session_close_previous_state *state;
+ uint32_t global_id = previous_session_id & UINT32_MAX;
+ uint64_t global_zeros = previous_session_id & 0xFFFFFFFF00000000LLU;
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ struct security_token *current_token = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb2srv_session_close_previous_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->connection = conn;
+ state->previous_session_id = previous_session_id;
+ state->current_session_id = current_session_id;
+
+ tevent_req_set_cleanup_fn(req, smb2srv_session_close_previous_cleanup);
+
+ if (global_zeros != 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (session_info == NULL) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ current_token = session_info->security_token;
+
+ if (current_token->num_sids > PRIMARY_USER_SID_INDEX) {
+ state->current_sid = &current_token->sids[PRIMARY_USER_SID_INDEX];
+ }
+
+ if (state->current_sid == NULL) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!security_token_has_nt_authenticated_users(current_token)) {
+ /* TODO */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ state->db_rec = smbXsrv_session_global_fetch_locked(
+ table->global.db_ctx,
+ global_id,
+ state /* TALLOC_CTX */);
+ if (state->db_rec == NULL) {
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return tevent_req_post(req, ev);
+ }
+
+ smb2srv_session_close_previous_check(req);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void smb2srv_session_close_previous_check(struct tevent_req *req)
+{
+ struct smb2srv_session_close_previous_state *state =
+ tevent_req_data(req,
+ struct smb2srv_session_close_previous_state);
+ struct smbXsrv_connection *conn = state->connection;
+ DATA_BLOB blob;
+ struct security_token *previous_token = NULL;
+ struct smbXsrv_session_global0 *global = NULL;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_session_close0 close_info0;
+ struct smbXsrv_session_closeB close_blob;
+ struct tevent_req *subreq = NULL;
+ NTSTATUS status;
+ bool is_free = false;
+ uint32_t seqnum = 0;
+
+ smbXsrv_session_global_verify_record(state->db_rec,
+ &is_free,
+ NULL,
+ state,
+ &global,
+ &seqnum);
+
+ if (is_free) {
+ tevent_req_done(req);
+ return;
+ }
+
+ if (global->auth_session_info == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+
+ previous_token = global->auth_session_info->security_token;
+
+ if (!security_token_is_sid(previous_token, state->current_sid)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * If the record changed, but we are not happy with the change yet,
+ * we better remove ourself from the waiter list
+ * (most likely the first position)
+ * and re-add us at the end of the list.
+ *
+ * This gives other waiters a change
+ * to make progress.
+ *
+ * Otherwise we'll keep our waiter instance alive,
+ * keep waiting (most likely at first position).
+ * It means the order of watchers stays fair.
+ */
+ if (state->last_seqnum != seqnum) {
+ state->last_seqnum = seqnum;
+ dbwrap_watched_watch_remove_instance(state->db_rec,
+ state->watch_instance);
+ state->watch_instance =
+ dbwrap_watched_watch_add_instance(state->db_rec);
+ }
+
+ subreq = dbwrap_watched_watch_send(state, state->ev, state->db_rec,
+ state->watch_instance,
+ (struct server_id){0});
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ smb2srv_session_close_previous_modified,
+ req);
+
+ close_info0.old_session_global_id = global->session_global_id;
+ close_info0.old_session_wire_id = global->session_wire_id;
+ close_info0.old_creation_time = global->creation_time;
+ close_info0.new_session_wire_id = state->current_session_id;
+
+ ZERO_STRUCT(close_blob);
+ close_blob.version = smbXsrv_version_global_current();
+ close_blob.info.info0 = &close_info0;
+
+ ndr_err = ndr_push_struct_blob(&blob, state, &close_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_session_closeB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("smb2srv_session_close_previous_check: "
+ "old_session[%llu] new_session[%llu] ndr_push - %s\n",
+ (unsigned long long)close_info0.old_session_wire_id,
+ (unsigned long long)close_info0.new_session_wire_id,
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ status = messaging_send(conn->client->msg_ctx,
+ global->channels[0].server_id,
+ MSG_SMBXSRV_SESSION_CLOSE, &blob);
+ TALLOC_FREE(global);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ TALLOC_FREE(state->db_rec);
+ return;
+}
+
+static void smb2srv_session_close_previous_modified(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb2srv_session_close_previous_state *state =
+ tevent_req_data(req,
+ struct smb2srv_session_close_previous_state);
+ uint32_t global_id;
+ NTSTATUS status;
+ uint64_t instance = 0;
+
+ status = dbwrap_watched_watch_recv(subreq, &instance, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->watch_instance = instance;
+
+ global_id = state->previous_session_id & UINT32_MAX;
+
+ state->db_rec = smbXsrv_session_global_fetch_locked(
+ state->connection->client->session_table->global.db_ctx,
+ global_id, state /* TALLOC_CTX */);
+ if (state->db_rec == NULL) {
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+
+ smb2srv_session_close_previous_check(req);
+}
+
+NTSTATUS smb2srv_session_close_previous_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_session_clear_and_logoff(struct smbXsrv_session *session)
+{
+ NTSTATUS status;
+ struct smbXsrv_connection *xconn = NULL;
+
+ if (session->client != NULL) {
+ xconn = session->client->connections;
+ }
+
+ for (; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *preq;
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ if (preq->session != session) {
+ continue;
+ }
+
+ preq->session = NULL;
+ /*
+ * If we no longer have a session we can't
+ * sign or encrypt replies.
+ */
+ preq->do_signing = false;
+ preq->do_encryption = false;
+ preq->preauth = NULL;
+ }
+ }
+
+ status = smbXsrv_session_logoff(session);
+ return status;
+}
+
+static int smbXsrv_session_destructor(struct smbXsrv_session *session)
+{
+ NTSTATUS status;
+
+ DBG_DEBUG("destructing session(%llu)\n",
+ (unsigned long long)session->global->session_wire_id);
+
+ status = smbXsrv_session_clear_and_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_destructor: "
+ "smbXsrv_session_logoff() failed: %s\n",
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(session->global);
+
+ return 0;
+}
+
+NTSTATUS smbXsrv_session_create(struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session **_session)
+{
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ struct db_record *local_rec = NULL;
+ struct smbXsrv_session *session = NULL;
+ void *ptr = NULL;
+ TDB_DATA val;
+ struct smbXsrv_session_global0 *global = NULL;
+ struct smbXsrv_channel_global0 *channel = NULL;
+ NTSTATUS status;
+
+ if (table->local.num_sessions >= table->local.max_sessions) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ session = talloc_zero(table, struct smbXsrv_session);
+ if (session == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ session->table = table;
+ session->idle_time = now;
+ session->status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ session->client = conn->client;
+ session->homes_snum = -1;
+
+ status = smbXsrv_session_global_allocate(table->global.db_ctx,
+ session,
+ &global);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+ session->global = global;
+
+ if (conn->protocol >= PROTOCOL_SMB2_02) {
+ uint64_t id = global->session_global_id;
+
+ global->connection_dialect = conn->smb2.server.dialect;
+ global->client_guid = conn->smb2.client.guid;
+
+ global->session_wire_id = id;
+
+ status = smb2srv_tcon_table_init(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ session->local_id = global->session_global_id;
+
+ local_rec = smbXsrv_session_local_fetch_locked(
+ table->local.db_ctx,
+ session->local_id,
+ session /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ TALLOC_FREE(session);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(session);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ } else {
+
+ status = smb1srv_session_local_allocate_id(table->local.db_ctx,
+ table->local.lowest_id,
+ table->local.highest_id,
+ session,
+ &local_rec,
+ &session->local_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ global->session_wire_id = session->local_id;
+ }
+
+ global->creation_time = now;
+ global->expiration_time = GENSEC_EXPIRE_TIME_INFINITY;
+
+ status = smbXsrv_session_add_channel(session, conn, now, &channel);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ ptr = session;
+ val = make_tdb_data((uint8_t const *)&ptr, sizeof(ptr));
+ status = dbwrap_record_store(local_rec, val, TDB_REPLACE);
+ TALLOC_FREE(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(session);
+ return status;
+ }
+ table->local.num_sessions += 1;
+
+ talloc_set_destructor(session, smbXsrv_session_destructor);
+
+ status = smbXsrv_session_global_store(global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_create: "
+ "global_id (0x%08x) store failed - %s\n",
+ session->global->session_global_id,
+ nt_errstr(status));
+ TALLOC_FREE(session);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_sessionB session_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = session,
+ };
+
+ DBG_DEBUG("smbXsrv_session_create: global_id (0x%08x) stored\n",
+ session->global->session_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_sessionB, &session_blob);
+ }
+
+ *_session = session;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_session_add_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_channel_global0 **_c)
+{
+ struct smbXsrv_session_global0 *global = session->global;
+ struct smbXsrv_channel_global0 *c = NULL;
+
+ if (global->num_channels > 31) {
+ /*
+ * Windows allow up to 32 channels
+ */
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ c = talloc_realloc(global,
+ global->channels,
+ struct smbXsrv_channel_global0,
+ global->num_channels + 1);
+ if (c == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ global->channels = c;
+
+ c = &global->channels[global->num_channels];
+ ZERO_STRUCTP(c);
+
+ c->server_id = messaging_server_id(conn->client->msg_ctx);
+ c->channel_id = conn->channel_id;
+ c->creation_time = now;
+ c->local_address = tsocket_address_string(conn->local_address,
+ global->channels);
+ if (c->local_address == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c->remote_address = tsocket_address_string(conn->remote_address,
+ global->channels);
+ if (c->remote_address == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c->remote_name = talloc_strdup(global->channels,
+ conn->remote_hostname);
+ if (c->remote_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ c->connection = conn;
+
+ global->num_channels += 1;
+
+ *_c = c;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_session_update(struct smbXsrv_session *session)
+{
+ struct smbXsrv_session_table *table = session->table;
+ NTSTATUS status;
+
+ if (session->global->db_rec != NULL) {
+ DBG_ERR("smbXsrv_session_update(0x%08x): "
+ "Called with db_rec != NULL'\n",
+ session->global->session_global_id);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (table == NULL) {
+ DBG_ERR("smbXsrv_session_update(0x%08x): "
+ "Called with table == NULL'\n",
+ session->global->session_global_id);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ session->global->db_rec = smbXsrv_session_global_fetch_locked(
+ table->global.db_ctx,
+ session->global->session_global_id,
+ session->global /* TALLOC_CTX */);
+ if (session->global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = smbXsrv_session_global_store(session->global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_update: "
+ "global_id (0x%08x) store failed - %s\n",
+ session->global->session_global_id,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_sessionB session_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = session,
+ };
+
+ DBG_DEBUG("smbXsrv_session_update: global_id (0x%08x) stored\n",
+ session->global->session_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_sessionB, &session_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_session_find_channel(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ struct smbXsrv_channel_global0 **_c)
+{
+ uint32_t i;
+
+ for (i=0; i < session->global->num_channels; i++) {
+ struct smbXsrv_channel_global0 *c = &session->global->channels[i];
+
+ if (c->channel_id != conn->channel_id) {
+ continue;
+ }
+
+ if (c->connection != conn) {
+ continue;
+ }
+
+ *_c = c;
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_USER_SESSION_DELETED;
+}
+
+NTSTATUS smbXsrv_session_find_auth(const struct smbXsrv_session *session,
+ const struct smbXsrv_connection *conn,
+ NTTIME now,
+ struct smbXsrv_session_auth0 **_a)
+{
+ struct smbXsrv_session_auth0 *a;
+
+ for (a = session->pending_auth; a != NULL; a = a->next) {
+ if (a->channel_id != conn->channel_id) {
+ continue;
+ }
+
+ if (a->connection == conn) {
+ if (now != 0) {
+ a->idle_time = now;
+ }
+ *_a = a;
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_USER_SESSION_DELETED;
+}
+
+static int smbXsrv_session_auth0_destructor(struct smbXsrv_session_auth0 *a)
+{
+ if (a->session == NULL) {
+ return 0;
+ }
+
+ DLIST_REMOVE(a->session->pending_auth, a);
+ a->session = NULL;
+ return 0;
+}
+
+NTSTATUS smbXsrv_session_create_auth(struct smbXsrv_session *session,
+ struct smbXsrv_connection *conn,
+ NTTIME now,
+ uint8_t in_flags,
+ uint8_t in_security_mode,
+ struct smbXsrv_session_auth0 **_a)
+{
+ struct smbXsrv_session_auth0 *a;
+ NTSTATUS status;
+
+ status = smbXsrv_session_find_auth(session, conn, 0, &a);
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ a = talloc_zero(session, struct smbXsrv_session_auth0);
+ if (a == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ a->session = session;
+ a->connection = conn;
+ a->in_flags = in_flags;
+ a->in_security_mode = in_security_mode;
+ a->creation_time = now;
+ a->idle_time = now;
+ a->channel_id = conn->channel_id;
+
+ if (conn->protocol >= PROTOCOL_SMB3_11) {
+ a->preauth = talloc(a, struct smbXsrv_preauth);
+ if (a->preauth == NULL) {
+ TALLOC_FREE(session);
+ return NT_STATUS_NO_MEMORY;
+ }
+ *a->preauth = conn->smb2.preauth;
+ }
+
+ talloc_set_destructor(a, smbXsrv_session_auth0_destructor);
+ DLIST_ADD_END(session->pending_auth, a);
+
+ *_a = a;
+ return NT_STATUS_OK;
+}
+
+static void smbXsrv_session_remove_channel_done(struct tevent_req *subreq);
+
+NTSTATUS smbXsrv_session_remove_channel(struct smbXsrv_session *session,
+ struct smbXsrv_connection *xconn)
+{
+ struct smbXsrv_session_auth0 *a = NULL;
+ struct smbXsrv_channel_global0 *c = NULL;
+ NTSTATUS status;
+ bool need_update = false;
+
+ status = smbXsrv_session_find_auth(session, xconn, 0, &a);
+ if (!NT_STATUS_IS_OK(status)) {
+ a = NULL;
+ }
+ status = smbXsrv_session_find_channel(session, xconn, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ c = NULL;
+ }
+
+ if (a != NULL) {
+ smbXsrv_session_auth0_destructor(a);
+ a->connection = NULL;
+ need_update = true;
+ }
+
+ if (c != NULL) {
+ struct smbXsrv_session_global0 *global = session->global;
+ ptrdiff_t n;
+
+ n = (c - global->channels);
+ if (n >= global->num_channels || n < 0) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ ARRAY_DEL_ELEMENT(global->channels, n, global->num_channels);
+ global->num_channels--;
+ if (global->num_channels == 0) {
+ struct smbXsrv_client *client = session->client;
+ struct tevent_queue *xconn_wait_queue =
+ xconn->transport.shutdown_wait_queue;
+ struct tevent_req *subreq = NULL;
+
+ /*
+ * Let the connection wait until the session is
+ * destroyed.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the session will
+ * remove the item from the wait queue in order
+ * to remove allow the connection to disappear.
+ */
+ if (xconn_wait_queue != NULL) {
+ subreq = tevent_queue_wait_send(session,
+ client->raw_ev_ctx,
+ xconn_wait_queue);
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("tevent_queue_wait_send() session(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ return status;
+ }
+ }
+
+ /*
+ * This is guaranteed to set
+ * session->status = NT_STATUS_USER_SESSION_DELETED
+ * even if NULL is returned.
+ */
+ subreq = smb2srv_session_shutdown_send(session,
+ client->raw_ev_ctx,
+ session,
+ NULL);
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("smb2srv_session_shutdown_send(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ return status;
+ }
+ tevent_req_set_callback(subreq,
+ smbXsrv_session_remove_channel_done,
+ session);
+ }
+ need_update = true;
+ }
+
+ if (!need_update) {
+ return NT_STATUS_OK;
+ }
+
+ return smbXsrv_session_update(session);
+}
+
+static void smbXsrv_session_remove_channel_done(struct tevent_req *subreq)
+{
+ struct smbXsrv_session *session =
+ tevent_req_callback_data(subreq,
+ struct smbXsrv_session);
+ NTSTATUS status;
+
+ status = smb2srv_session_shutdown_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smb2srv_session_shutdown_recv(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ status = smbXsrv_session_logoff(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_logoff(%llu) failed: %s\n",
+ (unsigned long long)session->global->session_wire_id,
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(session);
+}
+
+struct smb2srv_session_shutdown_state {
+ struct tevent_queue *wait_queue;
+};
+
+static void smb2srv_session_shutdown_wait_done(struct tevent_req *subreq);
+
+struct tevent_req *smb2srv_session_shutdown_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXsrv_session *session,
+ struct smbd_smb2_request *current_req)
+{
+ struct tevent_req *req;
+ struct smb2srv_session_shutdown_state *state;
+ struct tevent_req *subreq;
+ struct smbXsrv_connection *xconn = NULL;
+ size_t len = 0;
+
+ /*
+ * Make sure that no new request will be able to use this session.
+ */
+ session->status = NT_STATUS_USER_SESSION_DELETED;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb2srv_session_shutdown_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->wait_queue = tevent_queue_create(state, "smb2srv_session_shutdown_queue");
+ if (tevent_req_nomem(state->wait_queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (xconn = session->client->connections; xconn != NULL; xconn = xconn->next) {
+ struct smbd_smb2_request *preq;
+
+ for (preq = xconn->smb2.requests; preq != NULL; preq = preq->next) {
+ if (preq == current_req) {
+ /* Can't cancel current request. */
+ continue;
+ }
+ if (preq->session != session) {
+ /* Request on different session. */
+ continue;
+ }
+
+ if (preq->subreq != NULL) {
+ tevent_req_cancel(preq->subreq);
+ }
+
+ /*
+ * Now wait until the request is finished.
+ *
+ * We don't set a callback, as we just want to block the
+ * wait queue and the talloc_free() of the request will
+ * remove the item from the wait queue.
+ */
+ subreq = tevent_queue_wait_send(preq, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+ }
+
+ len = tevent_queue_length(state->wait_queue);
+ if (len == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Now we add our own waiter to the end of the queue,
+ * this way we get notified when all pending requests are finished
+ * and send to the socket.
+ */
+ subreq = tevent_queue_wait_send(state, ev, state->wait_queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb2srv_session_shutdown_wait_done, req);
+
+ return req;
+}
+
+static void smb2srv_session_shutdown_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+
+ tevent_queue_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ tevent_req_done(req);
+}
+
+NTSTATUS smb2srv_session_shutdown_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+NTSTATUS smbXsrv_session_logoff(struct smbXsrv_session *session)
+{
+ struct smbXsrv_session_table *table;
+ struct db_record *local_rec = NULL;
+ struct db_record *global_rec = NULL;
+ struct smbd_server_connection *sconn = NULL;
+ NTSTATUS status;
+ NTSTATUS error = NT_STATUS_OK;
+
+ if (session->table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ table = session->table;
+ session->table = NULL;
+
+ sconn = session->client->sconn;
+ session->client = NULL;
+ session->status = NT_STATUS_USER_SESSION_DELETED;
+
+ /*
+ * For SMB2 this is a bit redundant as files are also close
+ * below via smb2srv_tcon_disconnect_all() -> ... ->
+ * smbXsrv_tcon_disconnect() -> close_cnum() ->
+ * file_close_conn().
+ */
+ file_close_user(sconn, session->global->session_wire_id);
+
+ if (session->tcon_table != NULL) {
+ /*
+ * Note: We only have a tcon_table for SMB2.
+ */
+ status = smb2srv_tcon_disconnect_all(session);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_logoff(0x%08x): "
+ "smb2srv_tcon_disconnect_all() failed: %s\n",
+ session->global->session_global_id,
+ nt_errstr(status));
+ error = status;
+ }
+ }
+
+ invalidate_vuid(sconn, session->global->session_wire_id);
+
+ global_rec = session->global->db_rec;
+ session->global->db_rec = NULL;
+ if (global_rec == NULL) {
+ global_rec = smbXsrv_session_global_fetch_locked(
+ table->global.db_ctx,
+ session->global->session_global_id,
+ session->global /* TALLOC_CTX */);
+ if (global_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (global_rec != NULL) {
+ status = dbwrap_record_delete(global_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(global_rec);
+
+ DBG_ERR("smbXsrv_session_logoff(0x%08x): "
+ "failed to delete global key '%s': %s\n",
+ session->global->session_global_id,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ }
+ TALLOC_FREE(global_rec);
+
+ local_rec = session->db_rec;
+ if (local_rec == NULL) {
+ local_rec = smbXsrv_session_local_fetch_locked(
+ table->local.db_ctx,
+ session->local_id,
+ session /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (local_rec != NULL) {
+ status = dbwrap_record_delete(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(local_rec);
+
+ DBG_ERR("smbXsrv_session_logoff(0x%08x): "
+ "failed to delete local key '%s': %s\n",
+ session->global->session_global_id,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ table->local.num_sessions -= 1;
+ }
+ if (session->db_rec == NULL) {
+ TALLOC_FREE(local_rec);
+ }
+ session->db_rec = NULL;
+
+ return error;
+}
+
+struct smbXsrv_session_logoff_all_state {
+ NTSTATUS first_status;
+ int errors;
+};
+
+static int smbXsrv_session_logoff_all_callback(struct db_record *local_rec,
+ void *private_data);
+
+NTSTATUS smbXsrv_session_logoff_all(struct smbXsrv_client *client)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ struct smbXsrv_session_logoff_all_state state;
+ NTSTATUS status;
+ int count = 0;
+
+ if (table == NULL) {
+ DBG_DEBUG("smbXsrv_session_logoff_all: "
+ "empty session_table, nothing to do.\n");
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(state);
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_session_logoff_all_callback,
+ &state, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_session_logoff_all: "
+ "dbwrap_traverse() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state.first_status)) {
+ DBG_ERR("smbXsrv_session_logoff_all: "
+ "count[%d] errors[%d] first[%s]\n",
+ count, state.errors,
+ nt_errstr(state.first_status));
+ return state.first_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_session_logoff_all_callback(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_session_logoff_all_state *state =
+ (struct smbXsrv_session_logoff_all_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+
+ session->db_rec = local_rec;
+ status = smbXsrv_session_clear_and_logoff(session);
+ session->db_rec = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ return 0;
+}
+
+struct smbXsrv_session_local_trav_state {
+ NTSTATUS status;
+ int (*caller_cb)(struct smbXsrv_session *session,
+ void *caller_data);
+ void *caller_data;
+};
+
+static int smbXsrv_session_local_traverse_cb(struct db_record *local_rec,
+ void *private_data);
+
+NTSTATUS smbXsrv_session_local_traverse(
+ struct smbXsrv_client *client,
+ int (*caller_cb)(struct smbXsrv_session *session,
+ void *caller_data),
+ void *caller_data)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ struct smbXsrv_session_local_trav_state state;
+ NTSTATUS status;
+ int count = 0;
+
+ state = (struct smbXsrv_session_local_trav_state) {
+ .status = NT_STATUS_OK,
+ .caller_cb = caller_cb,
+ .caller_data = caller_data,
+ };
+
+ if (table == NULL) {
+ DBG_DEBUG("empty session_table, nothing to do.\n");
+ return NT_STATUS_OK;
+ }
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_session_local_traverse_cb,
+ &state,
+ &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_traverse() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ DBG_ERR("count[%d] status[%s]\n",
+ count, nt_errstr(state.status));
+ return state.status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_session_local_traverse_cb(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_session_local_trav_state *state =
+ (struct smbXsrv_session_local_trav_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_session *session = NULL;
+ int ret;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ state->status = NT_STATUS_INTERNAL_ERROR;
+ return -1;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+
+ session->db_rec = local_rec;
+ ret = state->caller_cb(session, state->caller_data);
+ session->db_rec = NULL;
+
+ return ret;
+}
+
+struct smbXsrv_session_disconnect_xconn_state {
+ struct smbXsrv_connection *xconn;
+ NTSTATUS first_status;
+ int errors;
+};
+
+static int smbXsrv_session_disconnect_xconn_callback(struct db_record *local_rec,
+ void *private_data);
+
+NTSTATUS smbXsrv_session_disconnect_xconn(struct smbXsrv_connection *xconn)
+{
+ struct smbXsrv_client *client = xconn->client;
+ struct smbXsrv_session_table *table = client->session_table;
+ struct smbXsrv_session_disconnect_xconn_state state;
+ NTSTATUS status;
+ int count = 0;
+
+ if (table == NULL) {
+ DBG_ERR("empty session_table, nothing to do.\n");
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(state);
+ state.xconn = xconn;
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_session_disconnect_xconn_callback,
+ &state, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_traverse() failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state.first_status)) {
+ DBG_ERR("count[%d] errors[%d] first[%s]\n",
+ count, state.errors,
+ nt_errstr(state.first_status));
+ return state.first_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_session_disconnect_xconn_callback(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_session_disconnect_xconn_state *state =
+ (struct smbXsrv_session_disconnect_xconn_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_session *session = NULL;
+ NTSTATUS status;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ session = talloc_get_type_abort(ptr, struct smbXsrv_session);
+
+ session->db_rec = local_rec;
+ status = smbXsrv_session_remove_channel(session, state->xconn);
+ session->db_rec = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ }
+
+ return 0;
+}
+
+NTSTATUS smb1srv_session_table_init(struct smbXsrv_connection *conn)
+{
+ /*
+ * Allow a range from 1..65534 with 65534 values.
+ */
+ return smbXsrv_session_table_init(conn, 1, UINT16_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+NTSTATUS smb1srv_session_lookup(struct smbXsrv_connection *conn,
+ uint16_t vuid, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ uint32_t local_id = vuid;
+
+ return smbXsrv_session_local_lookup(table, conn, local_id, now,
+ session);
+}
+
+NTSTATUS smbXsrv_session_info_lookup(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct auth_session_info **si)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ struct smbXsrv_session_local_fetch_state state = {
+ .session = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ TDB_DATA key;
+ NTSTATUS status;
+
+ if (session_wire_id == 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_session_local_id_to_key(session_wire_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_session_local_fetch_parser,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+ if (state.session->global->auth_session_info == NULL) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ *si = state.session->global->auth_session_info;
+ return NT_STATUS_OK;
+}
+
+/*
+ * In memory of get_valid_user_struct()
+ *
+ * This function is similar to smbXsrv_session_local_lookup() and it's wrappers,
+ * but it doesn't implement the state checks of
+ * those. get_valid_smbXsrv_session() is NOT meant to be called to validate the
+ * session wire-id of incoming SMB requests, it MUST only be used in later
+ * internal processing where the session wire-id has already been validated.
+ */
+NTSTATUS get_valid_smbXsrv_session(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ uint8_t key_buf[SMBXSRV_SESSION_LOCAL_TDB_KEY_SIZE];
+ struct smbXsrv_session_local_fetch_state state = {
+ .session = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ TDB_DATA key;
+ NTSTATUS status;
+
+ if (session_wire_id == 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_session_local_id_to_key(session_wire_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_session_local_fetch_parser,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+ if (state.session->global->auth_session_info == NULL) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ *session = state.session;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb2srv_session_lookup_global(struct smbXsrv_client *client,
+ uint64_t session_wire_id,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_session **_session)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct smbXsrv_session_table *table = client->session_table;
+ uint32_t global_id = session_wire_id & UINT32_MAX;
+ uint64_t global_zeros = session_wire_id & 0xFFFFFFFF00000000LLU;
+ struct smbXsrv_session *session = NULL;
+ struct db_record *global_rec = NULL;
+ bool is_free = false;
+ NTSTATUS status;
+
+ if (global_id == 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+ if (global_zeros != 0) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ if (table->global.db_ctx == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ session = talloc_zero(mem_ctx, struct smbXsrv_session);
+ if (session == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_steal(frame, session);
+
+ session->client = client;
+ session->status = NT_STATUS_BAD_LOGON_SESSION_STATE;
+ session->local_id = global_id;
+
+ /*
+ * This means smb2_get_new_nonce() will return
+ * NT_STATUS_ENCRYPTION_FAILED.
+ *
+ * But we initialize some random parts just in case...
+ */
+ session->nonce_high_max = session->nonce_high = 0;
+ generate_nonce_buffer((uint8_t *)&session->nonce_high_random,
+ sizeof(session->nonce_high_random));
+ generate_nonce_buffer((uint8_t *)&session->nonce_low,
+ sizeof(session->nonce_low));
+
+ global_rec = smbXsrv_session_global_fetch_locked(table->global.db_ctx,
+ global_id,
+ frame);
+ if (global_rec == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ smbXsrv_session_global_verify_record(global_rec,
+ &is_free,
+ NULL,
+ session,
+ &session->global,
+ NULL);
+ if (is_free) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ /*
+ * We don't have channels on this session
+ * and only the main signing key
+ */
+ session->global->num_channels = 0;
+ status = smb2_signing_key_sign_create(session->global,
+ session->global->signing_algo,
+ NULL, /* no master key */
+ NULL, /* derivations */
+ &session->global->signing_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ session->global->signing_key->blob = session->global->signing_key_blob;
+ session->global->signing_flags = 0;
+
+ status = smb2_signing_key_cipher_create(session->global,
+ session->global->encryption_cipher,
+ NULL, /* no master key */
+ NULL, /* derivations */
+ &session->global->decryption_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ session->global->decryption_key->blob = session->global->decryption_key_blob;
+ session->global->encryption_flags = 0;
+
+ *_session = talloc_move(mem_ctx, &session);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb2srv_session_table_init(struct smbXsrv_connection *conn)
+{
+ /*
+ * Allow a range from 1..4294967294 with 65534 (same as SMB1) values.
+ */
+ return smbXsrv_session_table_init(conn, 1, UINT32_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+static NTSTATUS smb2srv_session_lookup_raw(struct smbXsrv_session_table *table,
+ /* conn: optional */
+ struct smbXsrv_connection *conn,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ uint32_t local_id = session_id & UINT32_MAX;
+ uint64_t local_zeros = session_id & 0xFFFFFFFF00000000LLU;
+
+ if (local_zeros != 0) {
+ return NT_STATUS_USER_SESSION_DELETED;
+ }
+
+ return smbXsrv_session_local_lookup(table, conn, local_id, now,
+ session);
+}
+
+NTSTATUS smb2srv_session_lookup_conn(struct smbXsrv_connection *conn,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = conn->client->session_table;
+ return smb2srv_session_lookup_raw(table, conn, session_id, now,
+ session);
+}
+
+NTSTATUS smb2srv_session_lookup_client(struct smbXsrv_client *client,
+ uint64_t session_id, NTTIME now,
+ struct smbXsrv_session **session)
+{
+ struct smbXsrv_session_table *table = client->session_table;
+ return smb2srv_session_lookup_raw(table, NULL, session_id, now,
+ session);
+}
+
+struct smbXsrv_session_global_traverse_state {
+ int (*fn)(struct smbXsrv_session_global0 *, void *);
+ void *private_data;
+};
+
+static int smbXsrv_session_global_traverse_fn(struct db_record *rec, void *data)
+{
+ int ret = -1;
+ struct smbXsrv_session_global_traverse_state *state =
+ (struct smbXsrv_session_global_traverse_state*)data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ DATA_BLOB blob = data_blob_const(val.dptr, val.dsize);
+ struct smbXsrv_session_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_session_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("Invalid record in smbXsrv_session_global.tdb:"
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_WARNING("Invalid record in smbXsrv_session_global.tdb:"
+ "key '%s' unsupported version - %d\n",
+ tdb_data_dbg(key),
+ (int)global_blob.version);
+ goto done;
+ }
+
+ if (global_blob.info.info0 == NULL) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' info0 NULL pointer\n",
+ tdb_data_dbg(key));
+ goto done;
+ }
+
+ global_blob.info.info0->db_rec = rec;
+ ret = state->fn(global_blob.info.info0, state->private_data);
+done:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+NTSTATUS smbXsrv_session_global_traverse(
+ int (*fn)(struct smbXsrv_session_global0 *, void *),
+ void *private_data)
+{
+
+ NTSTATUS status;
+ int count = 0;
+ struct smbXsrv_session_global_traverse_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ become_root();
+ status = smbXsrv_session_global_init(NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ DBG_ERR("Failed to initialize session_global: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = dbwrap_traverse_read(smbXsrv_session_global_db_ctx,
+ smbXsrv_session_global_traverse_fn,
+ &state,
+ &count);
+ unbecome_root();
+
+ return status;
+}
diff --git a/source3/smbd/smbXsrv_tcon.c b/source3/smbd/smbXsrv_tcon.c
new file mode 100644
index 0000000..2f25246
--- /dev/null
+++ b/source3/smbd/smbXsrv_tcon.c
@@ -0,0 +1,1272 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2011-2012
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "dbwrap/dbwrap_open.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+#include "source3/include/util_tdb.h"
+
+struct smbXsrv_tcon_table {
+ struct {
+ struct db_context *db_ctx;
+ uint32_t lowest_id;
+ uint32_t highest_id;
+ uint32_t max_tcons;
+ uint32_t num_tcons;
+ } local;
+ struct {
+ struct db_context *db_ctx;
+ } global;
+};
+
+static struct db_context *smbXsrv_tcon_global_db_ctx = NULL;
+
+NTSTATUS smbXsrv_tcon_global_init(void)
+{
+ char *global_path = NULL;
+ struct db_context *db_ctx = NULL;
+
+ if (smbXsrv_tcon_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ global_path = lock_path(talloc_tos(), "smbXsrv_tcon_global.tdb");
+ if (global_path == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db_ctx = db_open(NULL, global_path,
+ SMBD_VOLATILE_TDB_HASH_SIZE,
+ SMBD_VOLATILE_TDB_FLAGS,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(global_path);
+ if (db_ctx == NULL) {
+ NTSTATUS status;
+
+ status = map_nt_error_from_unix_common(errno);
+
+ return status;
+ }
+
+ smbXsrv_tcon_global_db_ctx = db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * NOTE:
+ * We need to store the keys in big endian so that dbwrap_rbt's memcmp
+ * has the same result as integer comparison between the uint32_t
+ * values.
+ *
+ * TODO: implement string based key
+ */
+
+#define SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_tcon_global_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+#if 0
+static NTSTATUS smbXsrv_tcon_global_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+#endif
+
+#define SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE sizeof(uint32_t)
+
+static TDB_DATA smbXsrv_tcon_local_id_to_key(uint32_t id,
+ uint8_t *key_buf)
+{
+ TDB_DATA key;
+
+ RSIVAL(key_buf, 0, id);
+
+ key = make_tdb_data(key_buf, SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE);
+
+ return key;
+}
+
+static NTSTATUS smbXsrv_tcon_local_key_to_id(TDB_DATA key, uint32_t *id)
+{
+ if (id == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (key.dsize != SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE) {
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *id = RIVAL(key.dptr, 0);
+
+ return NT_STATUS_OK;
+}
+
+static struct db_record *smbXsrv_tcon_global_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_TCON_GLOBAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_tcon_global_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock global id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static struct db_record *smbXsrv_tcon_local_fetch_locked(
+ struct db_context *db,
+ uint32_t id,
+ TALLOC_CTX *mem_ctx)
+{
+ TDB_DATA key;
+ uint8_t key_buf[SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE];
+ struct db_record *rec = NULL;
+
+ key = smbXsrv_tcon_local_id_to_key(id, key_buf);
+
+ rec = dbwrap_fetch_locked(db, mem_ctx, key);
+
+ if (rec == NULL) {
+ DBG_DEBUG("Failed to lock local id 0x%08x, key '%s'\n", id,
+ tdb_data_dbg(key));
+ }
+
+ return rec;
+}
+
+static NTSTATUS smbXsrv_tcon_table_init(TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_table *table,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ uint32_t max_tcons)
+{
+ NTSTATUS status;
+ uint64_t max_range;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ max_range = highest_id;
+ max_range -= lowest_id;
+ max_range += 1;
+
+ if (max_tcons > max_range) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ ZERO_STRUCTP(table);
+ table->local.db_ctx = db_open_rbt(table);
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ table->local.lowest_id = lowest_id;
+ table->local.highest_id = highest_id;
+ table->local.max_tcons = max_tcons;
+
+ status = smbXsrv_tcon_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ table->global.db_ctx = smbXsrv_tcon_global_db_ctx;
+
+ return NT_STATUS_OK;
+}
+
+struct smb1srv_tcon_local_allocate_state {
+ const uint32_t lowest_id;
+ const uint32_t highest_id;
+ uint32_t last_id;
+ uint32_t useable_id;
+ NTSTATUS status;
+};
+
+static int smb1srv_tcon_local_allocate_traverse(struct db_record *rec,
+ void *private_data)
+{
+ struct smb1srv_tcon_local_allocate_state *state =
+ (struct smb1srv_tcon_local_allocate_state *)private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ uint32_t id = 0;
+ NTSTATUS status;
+
+ status = smbXsrv_tcon_local_key_to_id(key, &id);
+ if (!NT_STATUS_IS_OK(status)) {
+ state->status = status;
+ return -1;
+ }
+
+ if (id <= state->last_id) {
+ state->status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ return -1;
+ }
+ state->last_id = id;
+
+ if (id > state->useable_id) {
+ state->status = NT_STATUS_OK;
+ return -1;
+ }
+
+ if (state->useable_id == state->highest_id) {
+ state->status = NT_STATUS_INSUFFICIENT_RESOURCES;
+ return -1;
+ }
+
+ state->useable_id +=1;
+ return 0;
+}
+
+static NTSTATUS smb1srv_tcon_local_allocate_id(struct db_context *db,
+ uint32_t lowest_id,
+ uint32_t highest_id,
+ TALLOC_CTX *mem_ctx,
+ struct db_record **_rec,
+ uint32_t *_id)
+{
+ struct smb1srv_tcon_local_allocate_state state = {
+ .lowest_id = lowest_id,
+ .highest_id = highest_id,
+ .last_id = 0,
+ .useable_id = lowest_id,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint32_t i;
+ uint32_t range;
+ NTSTATUS status;
+ int count = 0;
+
+ *_rec = NULL;
+ *_id = 0;
+
+ if (lowest_id > highest_id) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ /*
+ * first we try randomly
+ */
+ range = (highest_id - lowest_id) + 1;
+
+ for (i = 0; i < (range / 2); i++) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = generate_random() % range;
+ id += lowest_id;
+
+ if (id < lowest_id) {
+ id = lowest_id;
+ }
+ if (id > highest_id) {
+ id = highest_id;
+ }
+
+ rec = smbXsrv_tcon_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ continue;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * if the range is almost full,
+ * we traverse the whole table
+ * (this relies on sorted behavior of dbwrap_rbt)
+ */
+ status = dbwrap_traverse_read(db, smb1srv_tcon_local_allocate_traverse,
+ &state, &count);
+ if (NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state.status)) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!NT_STATUS_EQUAL(state.status, NT_STATUS_INTERNAL_ERROR)) {
+ return state.status;
+ }
+
+ if (state.useable_id <= state.highest_id) {
+ state.status = NT_STATUS_OK;
+ } else {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+ } else if (!NT_STATUS_EQUAL(status, NT_STATUS_INTERNAL_DB_CORRUPTION)) {
+ /*
+ * Here we really expect NT_STATUS_INTERNAL_DB_CORRUPTION!
+ *
+ * If we get anything else it is an error, because it
+ * means we did not manage to find a free slot in
+ * the db.
+ */
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ if (NT_STATUS_IS_OK(state.status)) {
+ uint32_t id;
+ TDB_DATA val;
+ struct db_record *rec = NULL;
+
+ id = state.useable_id;
+
+ rec = smbXsrv_tcon_local_fetch_locked(db, id, mem_ctx);
+ if (rec == NULL) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ val = dbwrap_record_get_value(rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(rec);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ *_rec = rec;
+ *_id = id;
+ return NT_STATUS_OK;
+ }
+
+ return state.status;
+}
+
+struct smbXsrv_tcon_local_fetch_state {
+ struct smbXsrv_tcon *tcon;
+ NTSTATUS status;
+};
+
+static void smbXsrv_tcon_local_fetch_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct smbXsrv_tcon_local_fetch_state *state =
+ (struct smbXsrv_tcon_local_fetch_state *)private_data;
+ void *ptr;
+
+ if (data.dsize != sizeof(ptr)) {
+ state->status = NT_STATUS_INTERNAL_DB_ERROR;
+ return;
+ }
+
+ memcpy(&ptr, data.dptr, data.dsize);
+ state->tcon = talloc_get_type_abort(ptr, struct smbXsrv_tcon);
+ state->status = NT_STATUS_OK;
+}
+
+static NTSTATUS smbXsrv_tcon_local_lookup(struct smbXsrv_tcon_table *table,
+ uint32_t tcon_local_id,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct smbXsrv_tcon_local_fetch_state state = {
+ .tcon = NULL,
+ .status = NT_STATUS_INTERNAL_ERROR,
+ };
+ uint8_t key_buf[SMBXSRV_TCON_LOCAL_TDB_KEY_SIZE];
+ TDB_DATA key;
+ NTSTATUS status;
+
+ *_tcon = NULL;
+
+ if (tcon_local_id == 0) {
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ }
+
+ if (table == NULL) {
+ /* this might happen before the end of negprot */
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ }
+
+ if (table->local.db_ctx == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = smbXsrv_tcon_local_id_to_key(tcon_local_id, key_buf);
+
+ status = dbwrap_parse_record(table->local.db_ctx, key,
+ smbXsrv_tcon_local_fetch_parser,
+ &state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+
+ if (NT_STATUS_EQUAL(state.tcon->status, NT_STATUS_NETWORK_NAME_DELETED)) {
+ return NT_STATUS_NETWORK_NAME_DELETED;
+ }
+
+ state.tcon->idle_time = now;
+
+ *_tcon = state.tcon;
+ return state.tcon->status;
+}
+
+static int smbXsrv_tcon_global_destructor(struct smbXsrv_tcon_global0 *global)
+{
+ return 0;
+}
+
+static void smbXsrv_tcon_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_global0 **_g);
+
+static NTSTATUS smbXsrv_tcon_global_allocate(struct db_context *db,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_global0 **_global)
+{
+ uint32_t i;
+ struct smbXsrv_tcon_global0 *global = NULL;
+ uint32_t last_free = 0;
+ const uint32_t min_tries = 3;
+
+ *_global = NULL;
+
+ global = talloc_zero(mem_ctx, struct smbXsrv_tcon_global0);
+ if (global == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(global, smbXsrv_tcon_global_destructor);
+
+ /*
+ * Here we just randomly try the whole 32-bit space
+ *
+ * We use just 32-bit, because we want to reuse the
+ * ID for SRVSVC.
+ */
+ for (i = 0; i < UINT32_MAX; i++) {
+ bool is_free = false;
+ bool was_free = false;
+ uint32_t id;
+
+ if (i >= min_tries && last_free != 0) {
+ id = last_free;
+ } else {
+ id = generate_random();
+ }
+ if (id == 0) {
+ id++;
+ }
+ if (id == UINT32_MAX) {
+ id--;
+ }
+
+ global->db_rec = smbXsrv_tcon_global_fetch_locked(db, id,
+ mem_ctx);
+ if (global->db_rec == NULL) {
+ talloc_free(global);
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ smbXsrv_tcon_global_verify_record(global->db_rec,
+ &is_free,
+ &was_free,
+ NULL, NULL);
+
+ if (!is_free) {
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ if (!was_free && i < min_tries) {
+ /*
+ * The session_id is free now,
+ * but was not free before.
+ *
+ * This happens if a smbd crashed
+ * and did not cleanup the record.
+ *
+ * If this is one of our first tries,
+ * then we try to find a real free one.
+ */
+ if (last_free == 0) {
+ last_free = id;
+ }
+ TALLOC_FREE(global->db_rec);
+ continue;
+ }
+
+ global->tcon_global_id = id;
+
+ *_global = global;
+ return NT_STATUS_OK;
+ }
+
+ /* should not be reached */
+ talloc_free(global);
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+static void smbXsrv_tcon_global_verify_record(struct db_record *db_rec,
+ bool *is_free,
+ bool *was_free,
+ TALLOC_CTX *mem_ctx,
+ struct smbXsrv_tcon_global0 **_g)
+{
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_tcon_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_tcon_global0 *global = NULL;
+ bool exists;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ *is_free = false;
+
+ if (was_free) {
+ *was_free = false;
+ }
+ if (_g) {
+ *_g = NULL;
+ }
+
+ key = dbwrap_record_get_key(db_rec);
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ TALLOC_FREE(frame);
+ *is_free = true;
+ if (was_free) {
+ *was_free = true;
+ }
+ return;
+ }
+
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_tcon_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ DBG_DEBUG("smbXsrv_tcon_global_verify_record\n");
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_ERR("key '%s' uses unsupported version %u\n",
+ tdb_data_dbg(key),
+ global_blob.version);
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ global = global_blob.info.info0;
+
+ exists = serverid_exists(&global->server_id);
+ if (!exists) {
+ struct server_id_buf idbuf;
+ DBG_NOTICE("key '%s' server_id %s does not exist.\n",
+ tdb_data_dbg(key),
+ server_id_str_buf(global->server_id, &idbuf));
+ if (DEBUGLVL(DBGLVL_NOTICE)) {
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ }
+ TALLOC_FREE(frame);
+ dbwrap_record_delete(db_rec);
+ *is_free = true;
+ return;
+ }
+
+ if (_g) {
+ *_g = talloc_move(mem_ctx, &global);
+ }
+ TALLOC_FREE(frame);
+}
+
+static NTSTATUS smbXsrv_tcon_global_store(struct smbXsrv_tcon_global0 *global)
+{
+ struct smbXsrv_tcon_globalB global_blob;
+ DATA_BLOB blob = data_blob_null;
+ TDB_DATA key;
+ TDB_DATA val;
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+
+ /*
+ * TODO: if we use other versions than '0'
+ * we would add glue code here, that would be able to
+ * store the information in the old format.
+ */
+
+ if (global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ key = dbwrap_record_get_key(global->db_rec);
+ val = dbwrap_record_get_value(global->db_rec);
+
+ ZERO_STRUCT(global_blob);
+ global_blob.version = smbXsrv_version_global_current();
+ if (val.dsize >= 8) {
+ global_blob.seqnum = IVAL(val.dptr, 4);
+ }
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, global->db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_tcon_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("key '%s' ndr_push - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(global->db_rec, val, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("key '%s' store - %s\n",
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ TALLOC_FREE(global->db_rec);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ DBG_DEBUG("key '%s' stored\n", tdb_data_dbg(key));
+ NDR_PRINT_DEBUG(smbXsrv_tcon_globalB, &global_blob);
+ }
+
+ TALLOC_FREE(global->db_rec);
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_tcon_destructor(struct smbXsrv_tcon *tcon)
+{
+ NTSTATUS status;
+
+ status = smbXsrv_tcon_disconnect(tcon, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("smbXsrv_tcon_disconnect() failed - %s\n",
+ nt_errstr(status));
+ }
+
+ TALLOC_FREE(tcon->global);
+
+ return 0;
+}
+
+static NTSTATUS smbXsrv_tcon_create(struct smbXsrv_tcon_table *table,
+ enum protocol_types protocol,
+ struct server_id server_id,
+ NTTIME now,
+ uint32_t session_global_id,
+ uint8_t encryption_flags,
+ const char *share_name,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct db_record *local_rec = NULL;
+ struct smbXsrv_tcon *tcon = NULL;
+ void *ptr = NULL;
+ TDB_DATA val;
+ struct smbXsrv_tcon_global0 *global = NULL;
+ NTSTATUS status;
+
+ if (table->local.num_tcons >= table->local.max_tcons) {
+ return NT_STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ tcon = talloc_zero(table, struct smbXsrv_tcon);
+ if (tcon == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tcon->table = table;
+ tcon->status = NT_STATUS_INTERNAL_ERROR;
+ tcon->idle_time = now;
+
+ status = smbXsrv_tcon_global_allocate(table->global.db_ctx,
+ tcon, &global);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+ tcon->global = global;
+
+ global->session_global_id = session_global_id;
+ global->encryption_flags = encryption_flags;
+ global->share_name = talloc_strdup(global, share_name);
+ if (global->share_name == NULL) {
+ TALLOC_FREE(tcon);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (protocol >= PROTOCOL_SMB2_02) {
+ uint64_t id = global->tcon_global_id;
+
+ global->tcon_wire_id = id;
+
+ tcon->local_id = global->tcon_global_id;
+
+ local_rec = smbXsrv_tcon_local_fetch_locked(table->local.db_ctx,
+ tcon->local_id,
+ tcon /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ TALLOC_FREE(tcon);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != 0) {
+ TALLOC_FREE(tcon);
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+ } else {
+
+ status = smb1srv_tcon_local_allocate_id(table->local.db_ctx,
+ table->local.lowest_id,
+ table->local.highest_id,
+ tcon,
+ &local_rec,
+ &tcon->local_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+
+ global->tcon_wire_id = tcon->local_id;
+ }
+
+ global->creation_time = now;
+
+ global->server_id = server_id;
+
+ ptr = tcon;
+ val = make_tdb_data((uint8_t const *)&ptr, sizeof(ptr));
+ status = dbwrap_record_store(local_rec, val, TDB_REPLACE);
+ TALLOC_FREE(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tcon);
+ return status;
+ }
+ table->local.num_tcons += 1;
+
+ talloc_set_destructor(tcon, smbXsrv_tcon_destructor);
+
+ status = smbXsrv_tcon_global_store(global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("global_id (0x%08x) store failed - %s\n",
+ tcon->global->tcon_global_id,
+ nt_errstr(status));
+ TALLOC_FREE(tcon);
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_tconB tcon_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = tcon,
+ };
+
+ DBG_DEBUG("global_id (0x%08x) stored\n",
+ tcon->global->tcon_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_tconB, &tcon_blob);
+ }
+
+ *_tcon = tcon;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_tcon_update(struct smbXsrv_tcon *tcon)
+{
+ struct smbXsrv_tcon_table *table = tcon->table;
+ NTSTATUS status;
+
+ if (tcon->global->db_rec != NULL) {
+ DBG_ERR("update(0x%08x): "
+ "Called with db_rec != NULL'\n",
+ tcon->global->tcon_global_id);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ tcon->global->db_rec = smbXsrv_tcon_global_fetch_locked(
+ table->global.db_ctx,
+ tcon->global->tcon_global_id,
+ tcon->global /* TALLOC_CTX */);
+ if (tcon->global->db_rec == NULL) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ status = smbXsrv_tcon_global_store(tcon->global);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("global_id (0x%08x) store failed - %s\n",
+ tcon->global->tcon_global_id,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ struct smbXsrv_tconB tcon_blob = {
+ .version = SMBXSRV_VERSION_0,
+ .info.info0 = tcon,
+ };
+
+ DBG_DEBUG("global_id (0x%08x) stored\n",
+ tcon->global->tcon_global_id);
+ NDR_PRINT_DEBUG(smbXsrv_tconB, &tcon_blob);
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smbXsrv_tcon_disconnect(struct smbXsrv_tcon *tcon, uint64_t vuid)
+{
+ struct smbXsrv_tcon_table *table;
+ struct db_record *local_rec = NULL;
+ struct db_record *global_rec = NULL;
+ NTSTATUS status;
+ NTSTATUS error = NT_STATUS_OK;
+
+ if (tcon->table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ table = tcon->table;
+ tcon->table = NULL;
+
+ if (tcon->compat) {
+ bool ok;
+
+ ok = chdir_current_service(tcon->compat);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ DBG_ERR("disconnect(0x%08x, '%s'): "
+ "chdir_current_service() failed: %s\n",
+ tcon->global->tcon_global_id,
+ tcon->global->share_name,
+ nt_errstr(status));
+ /*
+ * We must call close_cnum() on
+ * error, as the caller is going
+ * to free tcon and tcon->compat
+ * so we must ensure tcon->compat is
+ * removed from the linked list
+ * conn->sconn->connections.
+ */
+ close_cnum(tcon->compat, vuid, ERROR_CLOSE);
+ tcon->compat = NULL;
+ return status;
+ }
+
+ close_cnum(tcon->compat, vuid, SHUTDOWN_CLOSE);
+ tcon->compat = NULL;
+ }
+
+ tcon->status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ global_rec = tcon->global->db_rec;
+ tcon->global->db_rec = NULL;
+ if (global_rec == NULL) {
+ global_rec = smbXsrv_tcon_global_fetch_locked(
+ table->global.db_ctx,
+ tcon->global->tcon_global_id,
+ tcon->global /* TALLOC_CTX */);
+ if (global_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (global_rec != NULL) {
+ status = dbwrap_record_delete(global_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(global_rec);
+
+ DBG_ERR("disconnect(0x%08x, '%s'): "
+ "failed to delete global key '%s': %s\n",
+ tcon->global->tcon_global_id,
+ tcon->global->share_name,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ }
+ TALLOC_FREE(global_rec);
+
+ local_rec = tcon->db_rec;
+ if (local_rec == NULL) {
+ local_rec = smbXsrv_tcon_local_fetch_locked(table->local.db_ctx,
+ tcon->local_id,
+ tcon /* TALLOC_CTX */);
+ if (local_rec == NULL) {
+ error = NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ if (local_rec != NULL) {
+ status = dbwrap_record_delete(local_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ TDB_DATA key = dbwrap_record_get_key(local_rec);
+
+ DBG_ERR("disconnect(0x%08x, '%s'): "
+ "failed to delete local key '%s': %s\n",
+ tcon->global->tcon_global_id,
+ tcon->global->share_name,
+ tdb_data_dbg(key),
+ nt_errstr(status));
+ error = status;
+ }
+ table->local.num_tcons -= 1;
+ }
+ if (tcon->db_rec == NULL) {
+ TALLOC_FREE(local_rec);
+ }
+ tcon->db_rec = NULL;
+
+ return error;
+}
+
+struct smbXsrv_tcon_disconnect_all_state {
+ uint64_t vuid;
+ NTSTATUS first_status;
+ int errors;
+};
+
+static int smbXsrv_tcon_disconnect_all_callback(struct db_record *local_rec,
+ void *private_data);
+
+static NTSTATUS smbXsrv_tcon_disconnect_all(struct smbXsrv_tcon_table *table,
+ uint64_t vuid)
+{
+ struct smbXsrv_tcon_disconnect_all_state state = { .vuid = vuid };
+ NTSTATUS status;
+ int count = 0;
+
+ if (table == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ status = dbwrap_traverse(table->local.db_ctx,
+ smbXsrv_tcon_disconnect_all_callback,
+ &state, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_traverse() failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state.first_status)) {
+ DBG_ERR("count[%d] errors[%d] first[%s]\n",
+ count,
+ state.errors,
+ nt_errstr(state.first_status));
+ return state.first_status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static int smbXsrv_tcon_disconnect_all_callback(struct db_record *local_rec,
+ void *private_data)
+{
+ struct smbXsrv_tcon_disconnect_all_state *state =
+ (struct smbXsrv_tcon_disconnect_all_state *)private_data;
+ TDB_DATA val;
+ void *ptr = NULL;
+ struct smbXsrv_tcon *tcon = NULL;
+ uint64_t vuid;
+ NTSTATUS status;
+
+ val = dbwrap_record_get_value(local_rec);
+ if (val.dsize != sizeof(ptr)) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ memcpy(&ptr, val.dptr, val.dsize);
+ tcon = talloc_get_type_abort(ptr, struct smbXsrv_tcon);
+
+ vuid = state->vuid;
+ if (vuid == 0 && tcon->compat) {
+ vuid = tcon->compat->vuid;
+ }
+
+ tcon->db_rec = local_rec;
+ status = smbXsrv_tcon_disconnect(tcon, vuid);
+ tcon->db_rec = NULL;
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_IS_OK(state->first_status)) {
+ state->first_status = status;
+ }
+ state->errors++;
+ return 0;
+ }
+
+ return 0;
+}
+
+NTSTATUS smb1srv_tcon_table_init(struct smbXsrv_connection *conn)
+{
+ struct smbXsrv_client *client = conn->client;
+
+ /*
+ * Allow a range from 1..65534 with 65534 values.
+ */
+ client->tcon_table = talloc_zero(client, struct smbXsrv_tcon_table);
+ if (client->tcon_table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return smbXsrv_tcon_table_init(client, client->tcon_table,
+ 1, UINT16_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+NTSTATUS smb1srv_tcon_create(struct smbXsrv_connection *conn,
+ uint32_t session_global_id,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct server_id id = messaging_server_id(conn->client->msg_ctx);
+ const uint8_t encryption_flags = 0;
+
+ return smbXsrv_tcon_create(conn->client->tcon_table,
+ conn->protocol,
+ id, now,
+ session_global_id,
+ encryption_flags,
+ share_name,
+ _tcon);
+}
+
+NTSTATUS smb1srv_tcon_lookup(struct smbXsrv_connection *conn,
+ uint16_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon)
+{
+ uint32_t local_id = tree_id;
+
+ return smbXsrv_tcon_local_lookup(conn->client->tcon_table,
+ local_id, now, tcon);
+}
+
+NTSTATUS smb1srv_tcon_disconnect_all(struct smbXsrv_client *client)
+{
+
+ /*
+ * We do not pass a vuid here,
+ * which means the vuid is taken from
+ * the tcon->compat->vuid.
+ *
+ * NOTE: that tcon->compat->vuid may point to
+ * a none existing vuid (or the wrong one)
+ * as the tcon can exist without a session
+ * in SMB1.
+ *
+ * This matches the old behavior of
+ * conn_close_all(), but we should think
+ * about how to fix this in future.
+ */
+ return smbXsrv_tcon_disconnect_all(client->tcon_table, 0);
+}
+
+NTSTATUS smb2srv_tcon_table_init(struct smbXsrv_session *session)
+{
+ /*
+ * Allow a range from 1..4294967294 with 65534 (same as SMB1) values.
+ */
+ session->tcon_table = talloc_zero(session, struct smbXsrv_tcon_table);
+ if (session->tcon_table == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return smbXsrv_tcon_table_init(session, session->tcon_table,
+ 1, UINT32_MAX - 1,
+ UINT16_MAX - 1);
+}
+
+NTSTATUS smb2srv_tcon_create(struct smbXsrv_session *session,
+ uint32_t session_global_id,
+ uint8_t encryption_flags,
+ const char *share_name,
+ NTTIME now,
+ struct smbXsrv_tcon **_tcon)
+{
+ struct server_id id = messaging_server_id(session->client->msg_ctx);
+
+ return smbXsrv_tcon_create(session->tcon_table,
+ PROTOCOL_SMB2_02,
+ id, now,
+ session_global_id,
+ encryption_flags,
+ share_name,
+ _tcon);
+}
+
+NTSTATUS smb2srv_tcon_lookup(struct smbXsrv_session *session,
+ uint32_t tree_id, NTTIME now,
+ struct smbXsrv_tcon **tcon)
+{
+ uint32_t local_id = tree_id;
+
+ return smbXsrv_tcon_local_lookup(session->tcon_table,
+ local_id, now, tcon);
+}
+
+NTSTATUS smb2srv_tcon_disconnect_all(struct smbXsrv_session *session)
+{
+ uint64_t vuid = session->global->session_wire_id;
+
+ return smbXsrv_tcon_disconnect_all(session->tcon_table, vuid);
+}
+
+struct smbXsrv_tcon_global_traverse_state {
+ int (*fn)(struct smbXsrv_tcon_global0 *, void *);
+ void *private_data;
+};
+
+static int smbXsrv_tcon_global_traverse_fn(struct db_record *rec, void *data)
+{
+ int ret = -1;
+ struct smbXsrv_tcon_global_traverse_state *state =
+ (struct smbXsrv_tcon_global_traverse_state*)data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ DATA_BLOB blob = data_blob_const(val.dptr, val.dsize);
+ struct smbXsrv_tcon_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_tcon_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ if (global_blob.version != SMBXSRV_VERSION_0) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' unsupported version - %d\n",
+ tdb_data_dbg(key),
+ (int)global_blob.version);
+ goto done;
+ }
+
+ if (global_blob.info.info0 == NULL) {
+ DBG_WARNING("Invalid record in smbXsrv_tcon_global.tdb:"
+ "key '%s' info0 NULL pointer\n",
+ tdb_data_dbg(key));
+ goto done;
+ }
+
+ global_blob.info.info0->db_rec = rec;
+ ret = state->fn(global_blob.info.info0, state->private_data);
+done:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+NTSTATUS smbXsrv_tcon_global_traverse(
+ int (*fn)(struct smbXsrv_tcon_global0 *, void *),
+ void *private_data)
+{
+ NTSTATUS status;
+ int count = 0;
+ struct smbXsrv_tcon_global_traverse_state state = {
+ .fn = fn,
+ .private_data = private_data,
+ };
+
+ become_root();
+ status = smbXsrv_tcon_global_init();
+ if (!NT_STATUS_IS_OK(status)) {
+ unbecome_root();
+ DBG_ERR("Failed to initialize tcon_global: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = dbwrap_traverse_read(smbXsrv_tcon_global_db_ctx,
+ smbXsrv_tcon_global_traverse_fn,
+ &state,
+ &count);
+ unbecome_root();
+
+ return status;
+}
diff --git a/source3/smbd/smbXsrv_version.c b/source3/smbd/smbXsrv_version.c
new file mode 100644
index 0000000..f2d138d
--- /dev/null
+++ b/source3/smbd/smbXsrv_version.c
@@ -0,0 +1,265 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Copyright (C) Stefan Metzmacher 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "lib/util/util_tdb.h"
+#include "librpc/gen_ndr/ndr_smbXsrv.h"
+#include "serverid.h"
+
+/*
+ * This implements a version scheme for file server internal
+ * states. smbXsrv_version_global.tdb stores the possible
+ * and current versions of structure formats (struct smbXsrv_*_global)
+ * per cluster node.
+ *
+ * If the supported versions doesn't match a version of any
+ * of the other nodes, it refused to start.
+ *
+ * This should prevent silent corruption of the internal
+ * databases and structures, if two incompatible implementations
+ * read and write.
+ *
+ * In future this can be used to implement rolling code upgrades
+ * in a cluster, but for now it is simple.
+ */
+
+static struct db_context *smbXsrv_version_global_db_ctx = NULL;
+static uint32_t smbXsrv_version_global_current_version = UINT32_MAX;
+
+NTSTATUS smbXsrv_version_global_init(const struct server_id *server_id)
+{
+ const char *global_path = NULL;
+ struct db_context *db_ctx = NULL;
+ struct db_record *db_rec = NULL;
+ TDB_DATA key;
+ TDB_DATA val;
+ DATA_BLOB blob;
+ struct smbXsrv_version_globalB global_blob;
+ enum ndr_err_code ndr_err;
+ struct smbXsrv_version_global0 *global = NULL;
+ uint32_t i;
+ uint32_t num_valid = 0;
+ struct smbXsrv_version_node0 *valid = NULL;
+ struct smbXsrv_version_node0 *local_node = NULL;
+ bool exists;
+ NTSTATUS status;
+ const char *key_string = "smbXsrv_version_global";
+ TALLOC_CTX *frame;
+
+ if (smbXsrv_version_global_db_ctx != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ frame = talloc_stackframe();
+
+ global_path = lock_path(talloc_tos(), "smbXsrv_version_global.tdb");
+ if (global_path == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ db_ctx = db_open(NULL, global_path,
+ 0, /* hash_size */
+ TDB_DEFAULT |
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (db_ctx == NULL) {
+ status = map_nt_error_from_unix_common(errno);
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "failed to open[%s] - %s\n",
+ global_path, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ key = string_term_tdb_data(key_string);
+
+ db_rec = dbwrap_fetch_locked(db_ctx, db_ctx, key);
+ if (db_rec == NULL) {
+ status = NT_STATUS_INTERNAL_DB_ERROR;
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "dbwrap_fetch_locked(%s) - %s\n",
+ key_string, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ val = dbwrap_record_get_value(db_rec);
+ if (val.dsize == 0) {
+ global = talloc_zero(frame, struct smbXsrv_version_global0);
+ if (global == NULL) {
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "talloc_zero failed - %s\n", __location__));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ ZERO_STRUCT(global_blob);
+ global_blob.version = SMBXSRV_VERSION_CURRENT;
+ global_blob.info.info0 = global;
+ } else {
+ blob = data_blob_const(val.dptr, val.dsize);
+
+ ndr_err = ndr_pull_struct_blob(&blob, frame, &global_blob,
+ (ndr_pull_flags_fn_t)ndr_pull_smbXsrv_version_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "ndr_pull_smbXsrv_version_globalB - %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ switch (global_blob.version) {
+ case SMBXSRV_VERSION_0:
+ global = global_blob.info.info0;
+ if (global == NULL) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ break;
+ }
+ status = NT_STATUS_OK;
+ break;
+ default:
+ status = NT_STATUS_REVISION_MISMATCH;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+ }
+
+ valid = talloc_zero_array(global,
+ struct smbXsrv_version_node0,
+ global->num_nodes + 1);
+ if (valid == NULL) {
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "talloc_zero_array failed - %s\n", __location__));
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ num_valid = 0;
+ for (i=0; i < global->num_nodes; i++) {
+ struct smbXsrv_version_node0 *n = &global->nodes[i];
+
+ exists = serverid_exists(&n->server_id);
+ if (!exists) {
+ continue;
+ }
+
+ if (n->min_version > n->max_version) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (n->min_version > global_blob.version) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ if (n->max_version < global_blob.version) {
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ DEBUG(0,("smbXsrv_version_global_init - %s\n",
+ nt_errstr(status)));
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ valid[num_valid] = *n;
+ if (server_id->vnn == n->server_id.vnn) {
+ local_node = &valid[num_valid];
+ }
+ num_valid++;
+ }
+
+ if (local_node == NULL) {
+ local_node = &valid[num_valid];
+ num_valid++;
+ }
+
+ local_node->server_id = *server_id;
+ local_node->min_version = SMBXSRV_VERSION_0;
+ local_node->max_version = SMBXSRV_VERSION_CURRENT;
+ local_node->current_version = global_blob.version;
+
+ global->num_nodes = num_valid;
+ global->nodes = valid;
+
+ global_blob.seqnum += 1;
+ global_blob.info.info0 = global;
+
+ ndr_err = ndr_push_struct_blob(&blob, db_rec, &global_blob,
+ (ndr_push_flags_fn_t)ndr_push_smbXsrv_version_globalB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "ndr_push_smbXsrv_version_globalB - %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ val = make_tdb_data(blob.data, blob.length);
+ status = dbwrap_record_store(db_rec, val, TDB_REPLACE);
+ TALLOC_FREE(db_rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("smbXsrv_version_global_init: "
+ "dbwrap_record_store - %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ DEBUG(10,("smbXsrv_version_global_init\n"));
+ if (DEBUGLVL(10)) {
+ NDR_PRINT_DEBUG(smbXsrv_version_globalB, &global_blob);
+ }
+
+ smbXsrv_version_global_db_ctx = db_ctx;
+ smbXsrv_version_global_current_version = global_blob.version;
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+uint32_t smbXsrv_version_global_current(void)
+{
+ return smbXsrv_version_global_current_version;
+}
diff --git a/source3/smbd/smbd.h b/source3/smbd/smbd.h
new file mode 100644
index 0000000..1c98bea
--- /dev/null
+++ b/source3/smbd/smbd.h
@@ -0,0 +1,102 @@
+/*
+ Unix SMB/CIFS implementation.
+ Main SMB server routines
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _SMBD_SMBD_H
+#define _SMBD_SMBD_H
+
+struct dptr_struct;
+
+#include "smb_acls.h"
+#include "vfs.h"
+#include "smbd/proto.h"
+#include "locking/proto.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/fd_handle.h"
+#if defined(WITH_SMB1SERVER)
+#include "smbd/smb1_message.h"
+#include "smbd/smb1_sesssetup.h"
+#include "smbd/smb1_lanman.h"
+#include "smbd/smb1_aio.h"
+#include "smbd/smb1_ipc.h"
+#include "smbd/smb1_negprot.h"
+#include "smbd/smb1_nttrans.h"
+#include "smbd/smb1_oplock.h"
+#include "smbd/smb1_pipes.h"
+#include "smbd/smb1_reply.h"
+#include "smbd/smb1_service.h"
+#include "smbd/smb1_signing.h"
+#include "smbd/smb1_process.h"
+#include "smbd/smb1_utils.h"
+#include "smbd/smb1_trans2.h"
+#endif
+
+struct trans_state {
+ struct trans_state *next, *prev;
+ uint64_t vuid; /* SMB2 compat */
+ uint64_t mid;
+
+ uint32_t max_param_return;
+ uint32_t max_data_return;
+ uint32_t max_setup_return;
+
+ uint8_t cmd; /* SMBtrans or SMBtrans2 */
+
+ char *name; /* for trans requests */
+ uint16_t call; /* for trans2 and nttrans requests */
+
+ bool close_on_completion;
+ bool one_way;
+
+ unsigned int setup_count;
+ uint16_t *setup;
+
+ size_t received_data;
+ size_t received_param;
+
+ size_t total_param;
+ char *param;
+
+ size_t total_data;
+ char *data;
+};
+
+/*
+ * unix_convert_flags
+ */
+/* UCF_SAVE_LCOMP 0x00000001 is no longer used. */
+/* UCF_ALWAYS_ALLOW_WCARD_LCOMP 0x00000002 is no longer used. */
+/* UCF_COND_ALLOW_WCARD_LCOMP 0x00000004 is no longer used. */
+#define UCF_POSIX_PATHNAMES 0x00000008
+/* #define UCF_UNIX_NAME_LOOKUP 0x00000010 is no longer used. */
+#define UCF_PREP_CREATEFILE 0x00000020
+/*
+ * Return a non-fsp smb_fname for a symlink
+ */
+#define UCF_LCOMP_LNK_OK 0x00000040
+/*
+ * Use the same bit as FLAGS2_REPARSE_PATH
+ * which means the same thing.
+ */
+#define UCF_GMT_PATHNAME 0x00000400
+/*
+ * Use the same bit as FLAGS2_DFS_PATHNAMES
+ * which means the same thing.
+ */
+#define UCF_DFS_PATHNAME 0x00001000
+
+#endif /* _SMBD_SMBD_H */
diff --git a/source3/smbd/smbd_cleanupd.c b/source3/smbd/smbd_cleanupd.c
new file mode 100644
index 0000000..0a50b18
--- /dev/null
+++ b/source3/smbd/smbd_cleanupd.c
@@ -0,0 +1,182 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbd_cleanupd.h"
+#include "lib/util_procid.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/debug.h"
+#include "smbprofile.h"
+#include "serverid.h"
+#include "locking/proto.h"
+#include "cleanupdb.h"
+
+struct smbd_cleanupd_state {
+ pid_t parent_pid;
+};
+
+static void smbd_cleanupd_shutdown(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+static void smbd_cleanupd_process_exited(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+struct tevent_req *smbd_cleanupd_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ pid_t parent_pid)
+{
+ struct tevent_req *req;
+ struct smbd_cleanupd_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct smbd_cleanupd_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->parent_pid = parent_pid;
+
+ status = messaging_register(msg, req, MSG_SHUTDOWN,
+ smbd_cleanupd_shutdown);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_register(msg, req, MSG_SMB_NOTIFY_CLEANUP,
+ smbd_cleanupd_process_exited);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void smbd_cleanupd_shutdown(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ tevent_req_done(req);
+}
+
+struct cleanup_child {
+ struct cleanup_child *prev, *next;
+ pid_t pid;
+ bool unclean;
+};
+
+struct cleanupdb_traverse_state {
+ TALLOC_CTX *mem_ctx;
+ bool ok;
+ struct cleanup_child *children;
+};
+
+static int cleanupdb_traverse_fn(const pid_t pid,
+ const bool unclean,
+ void *private_data)
+{
+ struct cleanupdb_traverse_state *cleanup_state =
+ (struct cleanupdb_traverse_state *)private_data;
+ struct cleanup_child *child = NULL;
+
+ child = talloc_zero(cleanup_state->mem_ctx, struct cleanup_child);
+ if (child == NULL) {
+ DBG_ERR("talloc_zero failed\n");
+ return -1;
+ }
+
+ child->pid = pid;
+ child->unclean = unclean;
+ DLIST_ADD(cleanup_state->children, child);
+
+ return 0;
+}
+
+static void smbd_cleanupd_process_exited(struct messaging_context *msg,
+ void *private_data, uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct tevent_req *req = talloc_get_type_abort(
+ private_data, struct tevent_req);
+ struct smbd_cleanupd_state *state = tevent_req_data(
+ req, struct smbd_cleanupd_state);
+ int ret;
+ struct cleanupdb_traverse_state cleanup_state;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cleanup_child *child = NULL;
+
+ cleanup_state = (struct cleanupdb_traverse_state) {
+ .mem_ctx = frame
+ };
+
+ /*
+ * This merely collect children in a list, whatever we're
+ * supposed to cleanup for every child, it has to take place
+ * *after* the db traverse in a list loop. This is to minimize
+ * locking interaction between the traverse and writers (i.e.
+ * the parent smbd).
+ */
+ ret = cleanupdb_traverse_read(cleanupdb_traverse_fn, &cleanup_state);
+ if (ret < 0) {
+ DBG_ERR("cleanupdb_traverse_read failed\n");
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ if (ret == 0) {
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ for (child = cleanup_state.children;
+ child != NULL;
+ child = child->next)
+ {
+ bool ok;
+
+ ok = cleanupdb_delete_child(child->pid);
+ if (!ok) {
+ DBG_ERR("failed to delete pid %d\n", (int)child->pid);
+ }
+
+ smbprofile_cleanup(child->pid, state->parent_pid);
+
+ ret = messaging_cleanup(msg, child->pid);
+
+ if ((ret != 0) && (ret != ENOENT)) {
+ DBG_DEBUG("messaging_cleanup returned %s\n",
+ strerror(ret));
+ }
+
+ DBG_DEBUG("cleaned up pid %d\n", (int)child->pid);
+ }
+
+ TALLOC_FREE(frame);
+}
+
+NTSTATUS smbd_cleanupd_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/smbd/smbd_cleanupd.h b/source3/smbd/smbd_cleanupd.h
new file mode 100644
index 0000000..6e5d87f
--- /dev/null
+++ b/source3/smbd/smbd_cleanupd.h
@@ -0,0 +1,33 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SMBD_CLEANUPD_H__
+#define __SMBD_CLEANUPD_H__
+
+#include "replace.h"
+#include <tevent.h>
+#include "messages.h"
+
+struct tevent_req *smbd_cleanupd_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ pid_t parent_pid);
+NTSTATUS smbd_cleanupd_recv(struct tevent_req *req);
+
+#endif
diff --git a/source3/smbd/srvstr.c b/source3/smbd/srvstr.c
new file mode 100644
index 0000000..5298ea7
--- /dev/null
+++ b/source3/smbd/srvstr.c
@@ -0,0 +1,78 @@
+/*
+ Unix SMB/CIFS implementation.
+ server specific string routines
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/util/string_wrappers.h"
+
+/* Make sure we can't write a string past the end of the buffer */
+
+NTSTATUS srvstr_push_fn(const char *base_ptr, uint16_t smb_flags2, void *dest,
+ const char *src, int dest_len, int flags, size_t *ret_len)
+{
+ size_t len;
+ int saved_errno;
+ NTSTATUS status;
+
+ if (dest_len < 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ saved_errno = errno;
+ errno = 0;
+
+ /* 'normal' push into size-specified buffer */
+ len = push_string_base(base_ptr, smb_flags2, dest, src,
+ dest_len, flags);
+
+ if (errno != 0) {
+ /*
+ * Special case E2BIG, EILSEQ, EINVAL
+ * as they mean conversion errors here,
+ * but we don't generically map them as
+ * they can mean different things in
+ * generic filesystem calls (such as
+ * read xattrs).
+ */
+ if (errno == E2BIG || errno == EILSEQ || errno == EINVAL) {
+ status = NT_STATUS_ILLEGAL_CHARACTER;
+ } else {
+ status = map_nt_error_from_unix_common(errno);
+ /*
+ * Paranoia - Filter out STATUS_MORE_ENTRIES.
+ * I don't think we can get this but it has a
+ * specific meaning to the client.
+ */
+ if (NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ DEBUG(10,("character conversion failure "
+ "on string (%s) (%s)\n",
+ src, strerror(errno)));
+ } else {
+ /* Success - restore untouched errno. */
+ errno = saved_errno;
+ *ret_len = len;
+ status = NT_STATUS_OK;
+ }
+ return status;
+}
diff --git a/source3/smbd/statvfs.c b/source3/smbd/statvfs.c
new file mode 100644
index 0000000..03dacc4
--- /dev/null
+++ b/source3/smbd/statvfs.c
@@ -0,0 +1,182 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS API's statvfs abstraction
+ Copyright (C) Alexander Bokovoy 2005
+ Copyright (C) Steve French 2005
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+
+#if defined(DARWINOS)
+#include <sys/attr.h>
+
+static int darwin_fs_capabilities(const char * path)
+{
+ int caps = 0;
+ vol_capabilities_attr_t *vcaps;
+ struct attrlist attrlist;
+ char attrbuf[sizeof(u_int32_t) + sizeof(vol_capabilities_attr_t)];
+
+#define FORMAT_CAP(vinfo, cap) \
+ ( ((vinfo)->valid[VOL_CAPABILITIES_FORMAT] & (cap)) && \
+ ((vinfo)->capabilities[VOL_CAPABILITIES_FORMAT] & (cap)) )
+
+#define INTERFACE_CAP(vinfo, cap) \
+ ( ((vinfo)->valid[VOL_CAPABILITIES_INTERFACES] & (cap)) && \
+ ((vinfo)->capabilities[VOL_CAPABILITIES_INTERFACES] & (cap)) )
+
+ ZERO_STRUCT(attrlist);
+ attrlist.bitmapcount = ATTR_BIT_MAP_COUNT;
+ attrlist.volattr = ATTR_VOL_CAPABILITIES;
+
+ if (getattrlist(path, &attrlist, attrbuf, sizeof(attrbuf), 0) != 0) {
+ DEBUG(0, ("getattrlist for %s capabilities failed: %s\n",
+ path, strerror(errno)));
+ /* Return no capabilities on failure. */
+ return 0;
+ }
+
+ vcaps =
+ (vol_capabilities_attr_t *)(attrbuf + sizeof(u_int32_t));
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_SPARSE_FILES)) {
+ caps |= FILE_SUPPORTS_SPARSE_FILES;
+ }
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_CASE_SENSITIVE)) {
+ caps |= FILE_CASE_SENSITIVE_SEARCH;
+ }
+
+ if (FORMAT_CAP(vcaps, VOL_CAP_FMT_CASE_PRESERVING)) {
+ caps |= FILE_CASE_PRESERVED_NAMES;
+ }
+
+ if (INTERFACE_CAP(vcaps, VOL_CAP_INT_EXTENDED_SECURITY)) {
+ caps |= FILE_PERSISTENT_ACLS;
+ }
+
+ return caps;
+}
+#endif /* DARWINOS */
+
+#if defined(BSD_STYLE_STATVFS)
+static int bsd_statvfs(const char *path, struct vfs_statvfs_struct *statbuf)
+{
+ struct statfs sbuf;
+ int ret;
+
+ ret = statfs(path, &sbuf);
+ if (ret == 0) {
+ statbuf->OptimalTransferSize = sbuf.f_iosize;
+ statbuf->BlockSize = sbuf.f_bsize;
+ statbuf->TotalBlocks = sbuf.f_blocks;
+ statbuf->BlocksAvail = sbuf.f_bfree;
+ statbuf->UserBlocksAvail = sbuf.f_bavail;
+ statbuf->TotalFileNodes = sbuf.f_files;
+ statbuf->FreeFileNodes = sbuf.f_ffree;
+ statbuf->FsIdentifier =
+ (((uint64_t) sbuf.f_fsid.val[0] << 32) & 0xffffffff00000000LL) |
+ (uint64_t) sbuf.f_fsid.val[1];
+#ifdef DARWINOS
+ statbuf->FsCapabilities = darwin_fs_capabilities(sbuf.f_mntonname);
+#else
+ /* Try to extrapolate some of the fs flags into the
+ * capabilities
+ */
+ statbuf->FsCapabilities =
+ FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+#ifdef MNT_ACLS
+ if (sbuf.f_flags & MNT_ACLS)
+ statbuf->FsCapabilities |= FILE_PERSISTENT_ACLS;
+#endif
+#endif
+ if (sbuf.f_flags & MNT_QUOTA)
+ statbuf->FsCapabilities |= FILE_VOLUME_QUOTAS;
+ if (sbuf.f_flags & MNT_RDONLY)
+ statbuf->FsCapabilities |= FILE_READ_ONLY_VOLUME;
+ }
+
+ return ret;
+}
+#elif defined(STAT_STATVFS) && defined(HAVE_FSID_INT)
+static int posix_statvfs(const char *path, struct vfs_statvfs_struct *statbuf)
+{
+ struct statvfs statvfs_buf;
+ int result;
+
+ result = statvfs(path, &statvfs_buf);
+
+ if (!result) {
+ /* statvfs bsize is not the statfs bsize, the naming is terrible,
+ * see bug 11810 */
+ statbuf->OptimalTransferSize = statvfs_buf.f_bsize;
+ statbuf->BlockSize = statvfs_buf.f_frsize;
+ statbuf->TotalBlocks = statvfs_buf.f_blocks;
+ statbuf->BlocksAvail = statvfs_buf.f_bfree;
+ statbuf->UserBlocksAvail = statvfs_buf.f_bavail;
+ statbuf->TotalFileNodes = statvfs_buf.f_files;
+ statbuf->FreeFileNodes = statvfs_buf.f_ffree;
+ statbuf->FsIdentifier = statvfs_buf.f_fsid;
+ /* Try to extrapolate some of the fs flags into the
+ * capabilities
+ */
+ statbuf->FsCapabilities =
+ FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES;
+#ifdef ST_QUOTA
+ if (statvfs_buf.f_flag & ST_QUOTA)
+ statbuf->FsCapabilities |= FILE_VOLUME_QUOTAS;
+#endif
+ if (statvfs_buf.f_flag & ST_RDONLY)
+ statbuf->FsCapabilities |= FILE_READ_ONLY_VOLUME;
+
+#if defined(HAVE_FALLOC_FL_PUNCH_HOLE) && defined(HAVE_LSEEK_HOLE_DATA)
+ /*
+ * Only flag sparse file support if ZERO_DATA can be used to
+ * deallocate blocks, and SEEK_HOLE / SEEK_DATA can be used
+ * to provide QUERY_ALLOCATED_RANGES information.
+ */
+ statbuf->FsCapabilities |= FILE_SUPPORTS_SPARSE_FILES;
+#endif
+ }
+ return result;
+}
+#endif
+
+/*
+ sys_statvfs() is an abstraction layer over system-dependent statvfs()/statfs()
+ for particular POSIX systems. Due to controversy of what is considered more important
+ between LSB and FreeBSD/POSIX.1 (IEEE Std 1003.1-2001) we need to abstract the interface
+ so that particular OS would use its preferred interface.
+*/
+int sys_statvfs(const char *path, struct vfs_statvfs_struct *statbuf)
+{
+#if defined(BSD_STYLE_STATVFS)
+ return bsd_statvfs(path, statbuf);
+#elif defined(STAT_STATVFS) && defined(HAVE_FSID_INT)
+ return posix_statvfs(path, statbuf);
+#else
+ /* BB change this to return invalid level */
+#ifdef EOPNOTSUPP
+ return EOPNOTSUPP;
+#else
+ return -1;
+#endif /* EOPNOTSUPP */
+#endif /* LINUX */
+
+}
diff --git a/source3/smbd/uid.c b/source3/smbd/uid.c
new file mode 100644
index 0000000..52918c4
--- /dev/null
+++ b/source3/smbd/uid.c
@@ -0,0 +1,752 @@
+/*
+ Unix SMB/CIFS implementation.
+ uid/user handling
+ Copyright (C) Andrew Tridgell 1992-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "auth.h"
+#include "../auth/auth_util.h"
+#include "source3/lib/substitute.h"
+
+/* what user is current? */
+extern struct current_user current_user;
+
+/****************************************************************************
+ Become the guest user without changing the security context stack.
+****************************************************************************/
+
+bool change_to_guest(void)
+{
+ struct passwd *pass;
+
+ pass = Get_Pwnam_alloc(talloc_tos(), lp_guest_account());
+ if (!pass) {
+ return false;
+ }
+
+#ifdef AIX
+ /* MWW: From AIX FAQ patch to WU-ftpd: call initgroups before
+ setting IDs */
+ initgroups(pass->pw_name, pass->pw_gid);
+#endif
+
+ set_sec_ctx(pass->pw_uid, pass->pw_gid, 0, NULL, NULL);
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ TALLOC_FREE(pass);
+
+ return true;
+}
+
+/****************************************************************************
+ talloc free the conn->session_info if not used in the vuid cache.
+****************************************************************************/
+
+static void free_conn_session_info_if_unused(connection_struct *conn)
+{
+ unsigned int i;
+
+ for (i = 0; i < VUID_CACHE_SIZE; i++) {
+ struct vuid_cache_entry *ent;
+ ent = &conn->vuid_cache->array[i];
+ if (ent->vuid != UID_FIELD_INVALID &&
+ conn->session_info == ent->session_info) {
+ return;
+ }
+ }
+ /* Not used, safe to free. */
+ TALLOC_FREE(conn->session_info);
+}
+
+/****************************************************************************
+ Setup the share access mask for a connection.
+****************************************************************************/
+
+static uint32_t create_share_access_mask(int snum,
+ bool readonly_share,
+ const struct security_token *token)
+{
+ uint32_t share_access = 0;
+
+ share_access_check(token,
+ lp_const_servicename(snum),
+ MAXIMUM_ALLOWED_ACCESS,
+ &share_access);
+
+ if (readonly_share) {
+ share_access &=
+ ~(SEC_FILE_WRITE_DATA | SEC_FILE_APPEND_DATA |
+ SEC_FILE_WRITE_EA | SEC_FILE_WRITE_ATTRIBUTE |
+ SEC_DIR_DELETE_CHILD );
+ }
+
+ if (security_token_has_privilege(token, SEC_PRIV_SECURITY)) {
+ share_access |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_RESTORE)) {
+ share_access |= SEC_RIGHTS_PRIV_RESTORE;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_BACKUP)) {
+ share_access |= SEC_RIGHTS_PRIV_BACKUP;
+ }
+ if (security_token_has_privilege(token, SEC_PRIV_TAKE_OWNERSHIP)) {
+ share_access |= SEC_STD_WRITE_OWNER;
+ }
+
+ return share_access;
+}
+
+/*******************************************************************
+ Calculate access mask and if this user can access this share.
+********************************************************************/
+
+NTSTATUS check_user_share_access(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint32_t *p_share_access,
+ bool *p_readonly_share)
+{
+ int snum = SNUM(conn);
+ uint32_t share_access = 0;
+ bool readonly_share = false;
+
+ if (!user_ok_token(session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token, snum)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ readonly_share = is_share_read_only_for_token(
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ session_info->security_token,
+ conn);
+
+ share_access = create_share_access_mask(snum,
+ readonly_share,
+ session_info->security_token);
+
+ if ((share_access & (FILE_READ_DATA|FILE_WRITE_DATA)) == 0) {
+ /* No access, read or write. */
+ DBG_NOTICE("user %s connection to %s denied due to share "
+ "security descriptor.\n",
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (!readonly_share &&
+ !(share_access & FILE_WRITE_DATA)) {
+ /* smb.conf allows r/w, but the security descriptor denies
+ * write. Fall back to looking at readonly. */
+ readonly_share = true;
+ DBG_INFO("falling back to read-only access-evaluation due to "
+ "security descriptor\n");
+ }
+
+ *p_share_access = share_access;
+ *p_readonly_share = readonly_share;
+
+ return NT_STATUS_OK;
+}
+
+/*******************************************************************
+ Check if a username is OK.
+
+ This sets up conn->session_info with a copy related to this vuser that
+ later code can then mess with.
+********************************************************************/
+
+static bool check_user_ok(connection_struct *conn,
+ uint64_t vuid,
+ const struct auth_session_info *session_info,
+ int snum)
+{
+ unsigned int i;
+ bool readonly_share = false;
+ bool admin_user = false;
+ struct vuid_cache_entry *ent = NULL;
+ uint32_t share_access = 0;
+ NTSTATUS status;
+
+ for (i=0; i<VUID_CACHE_SIZE; i++) {
+ ent = &conn->vuid_cache->array[i];
+ if (ent->vuid == vuid) {
+ if (vuid == UID_FIELD_INVALID) {
+ /*
+ * Slow path, we don't care
+ * about the array traversal.
+ */
+ continue;
+ }
+ free_conn_session_info_if_unused(conn);
+ conn->session_info = ent->session_info;
+ conn->read_only = ent->read_only;
+ conn->share_access = ent->share_access;
+ conn->vuid = ent->vuid;
+ return(True);
+ }
+ }
+
+ status = check_user_share_access(conn,
+ session_info,
+ &share_access,
+ &readonly_share);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ admin_user = token_contains_name_in_list(
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name,
+ NULL, session_info->security_token, lp_admin_users(snum));
+
+ ent = &conn->vuid_cache->array[conn->vuid_cache->next_entry];
+
+ conn->vuid_cache->next_entry =
+ (conn->vuid_cache->next_entry + 1) % VUID_CACHE_SIZE;
+
+ TALLOC_FREE(ent->session_info);
+
+ /*
+ * If force_user was set, all session_info's are based on the same
+ * username-based faked one.
+ */
+
+ ent->session_info = copy_session_info(
+ conn, conn->force_user ? conn->session_info : session_info);
+
+ if (ent->session_info == NULL) {
+ ent->vuid = UID_FIELD_INVALID;
+ return false;
+ }
+
+ if (admin_user) {
+ DEBUG(2,("check_user_ok: user %s is an admin user. "
+ "Setting uid as %d\n",
+ ent->session_info->unix_info->unix_name,
+ sec_initial_uid() ));
+ ent->session_info->unix_token->uid = sec_initial_uid();
+ }
+
+ /*
+ * It's actually OK to call check_user_ok() with
+ * vuid == UID_FIELD_INVALID as called from become_user_by_session().
+ * All this will do is throw away one entry in the cache.
+ */
+
+ ent->vuid = vuid;
+ ent->read_only = readonly_share;
+ ent->share_access = share_access;
+ free_conn_session_info_if_unused(conn);
+ conn->session_info = ent->session_info;
+ conn->vuid = ent->vuid;
+ if (vuid == UID_FIELD_INVALID) {
+ /*
+ * Not strictly needed, just make it really
+ * clear this entry is actually an unused one.
+ */
+ ent->read_only = false;
+ ent->share_access = 0;
+ ent->session_info = NULL;
+ }
+
+ conn->read_only = readonly_share;
+ conn->share_access = share_access;
+
+ return(True);
+}
+
+static void print_impersonation_info(connection_struct *conn)
+{
+ struct smb_filename *cwdfname = NULL;
+
+ if (!CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ return;
+ }
+
+ cwdfname = vfs_GetWd(talloc_tos(), conn);
+ if (cwdfname == NULL) {
+ return;
+ }
+
+ DBG_INFO("Impersonated user: uid=(%d,%d), gid=(%d,%d), cwd=[%s]\n",
+ (int)getuid(),
+ (int)geteuid(),
+ (int)getgid(),
+ (int)getegid(),
+ cwdfname->base_name);
+ TALLOC_FREE(cwdfname);
+}
+
+/****************************************************************************
+ Become the user of a connection number without changing the security context
+ stack, but modify the current_user entries.
+****************************************************************************/
+
+static bool change_to_user_impersonate(connection_struct *conn,
+ const struct auth_session_info *session_info,
+ uint64_t vuid)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int snum;
+ gid_t gid;
+ uid_t uid;
+ const char *force_group_name;
+ char group_c;
+ int num_groups = 0;
+ gid_t *group_list = NULL;
+ bool ok;
+
+ if ((current_user.conn == conn) &&
+ (current_user.vuid == vuid) &&
+ (current_user.ut.uid == session_info->unix_token->uid))
+ {
+ DBG_INFO("Skipping user change - already user\n");
+ return true;
+ }
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ snum = SNUM(conn);
+
+ ok = check_user_ok(conn, vuid, session_info, snum);
+ if (!ok) {
+ DBG_WARNING("SMB user %s (unix user %s) "
+ "not permitted access to share %s.\n",
+ session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ uid = conn->session_info->unix_token->uid;
+ gid = conn->session_info->unix_token->gid;
+ num_groups = conn->session_info->unix_token->ngroups;
+ group_list = conn->session_info->unix_token->groups;
+
+ /*
+ * See if we should force group for this service. If so this overrides
+ * any group set in the force user code.
+ */
+ force_group_name = lp_force_group(talloc_tos(), lp_sub, snum);
+ group_c = *force_group_name;
+
+ if ((group_c != '\0') && (conn->force_group_gid == (gid_t)-1)) {
+ /*
+ * This can happen if "force group" is added to a
+ * share definition whilst an existing connection
+ * to that share exists. In that case, don't change
+ * the existing credentials for force group, only
+ * do so for new connections.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13690
+ */
+ DBG_INFO("Not forcing group %s on existing connection to "
+ "share %s for SMB user %s (unix user %s)\n",
+ force_group_name,
+ lp_const_servicename(snum),
+ session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name);
+ }
+
+ if((group_c != '\0') && (conn->force_group_gid != (gid_t)-1)) {
+ /*
+ * Only force group for connections where
+ * conn->force_group_gid has already been set
+ * to the correct value (i.e. the connection
+ * happened after the 'force group' definition
+ * was added to the share definition. Connections
+ * that were made before force group was added
+ * should stay with their existing credentials.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=13690
+ */
+
+ if (group_c == '+') {
+ int i;
+
+ /*
+ * Only force group if the user is a member of the
+ * service group. Check the group memberships for this
+ * user (we already have this) to see if we should force
+ * the group.
+ */
+ for (i = 0; i < num_groups; i++) {
+ if (group_list[i] == conn->force_group_gid) {
+ conn->session_info->unix_token->gid =
+ conn->force_group_gid;
+ gid = conn->force_group_gid;
+ gid_to_sid(&conn->session_info->security_token
+ ->sids[1], gid);
+ break;
+ }
+ }
+ } else {
+ conn->session_info->unix_token->gid = conn->force_group_gid;
+ gid = conn->force_group_gid;
+ gid_to_sid(&conn->session_info->security_token->sids[1],
+ gid);
+ }
+ }
+
+ set_sec_ctx(uid,
+ gid,
+ num_groups,
+ group_list,
+ conn->session_info->security_token);
+
+ current_user.conn = conn;
+ current_user.vuid = vuid;
+ return true;
+}
+
+/**
+ * Impersonate user and change directory to service
+ *
+ * change_to_user_and_service() is used to impersonate the user associated with
+ * the given vuid and to change the working directory of the process to the
+ * service base directory.
+ **/
+bool change_to_user_and_service(connection_struct *conn, uint64_t vuid)
+{
+ int snum = SNUM(conn);
+ struct auth_session_info *si = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ if (conn == NULL) {
+ DBG_WARNING("Connection not open\n");
+ return false;
+ }
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &si);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("Invalid vuid %llu used on share %s.\n",
+ (unsigned long long)vuid,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ ok = change_to_user_impersonate(conn, si, vuid);
+ if (!ok) {
+ return false;
+ }
+
+ if (conn->tcon_done) {
+ ok = chdir_current_service(conn);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ print_impersonation_info(conn);
+ return true;
+}
+
+/**
+ * Impersonate user and change directory to service
+ *
+ * change_to_user_and_service_by_fsp() is used to impersonate the user
+ * associated with the given vuid and to change the working directory of the
+ * process to the service base directory.
+ **/
+bool change_to_user_and_service_by_fsp(struct files_struct *fsp)
+{
+ return change_to_user_and_service(fsp->conn, fsp->vuid);
+}
+
+/****************************************************************************
+ Go back to being root without changing the security context stack,
+ but modify the current_user entries.
+****************************************************************************/
+
+bool smbd_change_to_root_user(void)
+{
+ set_root_sec_ctx();
+
+ DEBUG(5,("change_to_root_user: now uid=(%d,%d) gid=(%d,%d)\n",
+ (int)getuid(),(int)geteuid(),(int)getgid(),(int)getegid()));
+
+ current_user.conn = NULL;
+ current_user.vuid = UID_FIELD_INVALID;
+
+ return(True);
+}
+
+/****************************************************************************
+ Become the user of an authenticated connected named pipe.
+ When this is called we are currently running as the connection
+ user. Doesn't modify current_user.
+****************************************************************************/
+
+bool smbd_become_authenticated_pipe_user(struct auth_session_info *session_info)
+{
+ if (!push_sec_ctx())
+ return False;
+
+ set_current_user_info(session_info->unix_info->sanitized_username,
+ session_info->unix_info->unix_name,
+ session_info->info->domain_name);
+
+ set_sec_ctx(session_info->unix_token->uid, session_info->unix_token->gid,
+ session_info->unix_token->ngroups, session_info->unix_token->groups,
+ session_info->security_token);
+
+ DEBUG(5, ("Impersonated user: uid=(%d,%d), gid=(%d,%d)\n",
+ (int)getuid(),
+ (int)geteuid(),
+ (int)getgid(),
+ (int)getegid()));
+
+ return True;
+}
+
+/****************************************************************************
+ Unbecome the user of an authenticated connected named pipe.
+ When this is called we are running as the authenticated pipe
+ user and need to go back to being the connection user. Doesn't modify
+ current_user.
+****************************************************************************/
+
+bool smbd_unbecome_authenticated_pipe_user(void)
+{
+ return pop_sec_ctx();
+}
+
+/****************************************************************************
+ Utility functions used by become_xxx/unbecome_xxx.
+****************************************************************************/
+
+static void push_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+ extern userdom_struct current_user_info;
+
+ /* Check we don't overflow our stack */
+
+ if (conn_ctx_stack_ndx == MAX_SEC_CTX_DEPTH) {
+ DEBUG(0, ("Connection context stack overflow!\n"));
+ smb_panic("Connection context stack overflow!\n");
+ }
+
+ /* Store previous user context */
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ ctx_p->conn = current_user.conn;
+ ctx_p->vuid = current_user.vuid;
+ ctx_p->user_info = current_user_info;
+
+ DEBUG(4, ("push_conn_ctx(%llu) : conn_ctx_stack_ndx = %d\n",
+ (unsigned long long)ctx_p->vuid, conn_ctx_stack_ndx));
+
+ conn_ctx_stack_ndx++;
+}
+
+static void pop_conn_ctx(void)
+{
+ struct conn_ctx *ctx_p;
+
+ /* Check for stack underflow. */
+
+ if (conn_ctx_stack_ndx == 0) {
+ DEBUG(0, ("Connection context stack underflow!\n"));
+ smb_panic("Connection context stack underflow!\n");
+ }
+
+ conn_ctx_stack_ndx--;
+ ctx_p = &conn_ctx_stack[conn_ctx_stack_ndx];
+
+ set_current_user_info(ctx_p->user_info.smb_name,
+ ctx_p->user_info.unix_name,
+ ctx_p->user_info.domain);
+
+ current_user.conn = ctx_p->conn;
+ current_user.vuid = ctx_p->vuid;
+
+ *ctx_p = (struct conn_ctx) {
+ .vuid = UID_FIELD_INVALID,
+ };
+}
+
+/****************************************************************************
+ Temporarily become a root user. Must match with unbecome_root(). Saves and
+ restores the connection context.
+****************************************************************************/
+
+void smbd_become_root(void)
+{
+ /*
+ * no good way to handle push_sec_ctx() failing without changing
+ * the prototype of become_root()
+ */
+ if (!push_sec_ctx()) {
+ smb_panic("become_root: push_sec_ctx failed");
+ }
+ push_conn_ctx();
+ set_root_sec_ctx();
+}
+
+/* Unbecome the root user */
+
+void smbd_unbecome_root(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+}
+
+/****************************************************************************
+ Push the current security context then force a change via change_to_user().
+ Saves and restores the connection context.
+****************************************************************************/
+
+bool become_user_without_service(connection_struct *conn, uint64_t vuid)
+{
+ struct auth_session_info *session_info = NULL;
+ int snum = SNUM(conn);
+ NTSTATUS status;
+ bool ok;
+
+ if (conn == NULL) {
+ DBG_WARNING("Connection not open\n");
+ return false;
+ }
+
+ status = smbXsrv_session_info_lookup(conn->sconn->client,
+ vuid,
+ &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Invalid vuid sent */
+ DBG_WARNING("Invalid vuid %llu used on share %s.\n",
+ (unsigned long long)vuid,
+ lp_const_servicename(snum));
+ return false;
+ }
+
+ ok = push_sec_ctx();
+ if (!ok) {
+ return false;
+ }
+
+ push_conn_ctx();
+
+ ok = change_to_user_impersonate(conn, session_info, vuid);
+ if (!ok) {
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return false;
+ }
+
+ return true;
+}
+
+bool become_user_without_service_by_fsp(struct files_struct *fsp)
+{
+ return become_user_without_service(fsp->conn, fsp->vuid);
+}
+
+bool become_user_without_service_by_session(connection_struct *conn,
+ const struct auth_session_info *session_info)
+{
+ bool ok;
+
+ SMB_ASSERT(conn != NULL);
+ SMB_ASSERT(session_info != NULL);
+
+ ok = push_sec_ctx();
+ if (!ok) {
+ return false;
+ }
+
+ push_conn_ctx();
+
+ ok = change_to_user_impersonate(conn, session_info, UID_FIELD_INVALID);
+ if (!ok) {
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return false;
+ }
+
+ return true;
+}
+
+bool unbecome_user_without_service(void)
+{
+ pop_sec_ctx();
+ pop_conn_ctx();
+ return True;
+}
+
+/****************************************************************************
+ Return the current user we are running effectively as on this connection.
+ I'd like to make this return conn->session_info->unix_token->uid, but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+uid_t get_current_uid(connection_struct *conn)
+{
+ return current_user.ut.uid;
+}
+
+/****************************************************************************
+ Return the current group we are running effectively as on this connection.
+ I'd like to make this return conn->session_info->unix_token->gid, but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+gid_t get_current_gid(connection_struct *conn)
+{
+ return current_user.ut.gid;
+}
+
+/****************************************************************************
+ Return the UNIX token we are running effectively as on this connection.
+ I'd like to make this return &conn->session_info->unix_token-> but become_root()
+ doesn't alter this value.
+****************************************************************************/
+
+const struct security_unix_token *get_current_utok(connection_struct *conn)
+{
+ return &current_user.ut;
+}
+
+/****************************************************************************
+ Return the Windows token we are running effectively as on this connection.
+ If this is currently a NULL token as we're inside become_root() - a temporary
+ UNIX security override, then we search up the stack for the previous active
+ token.
+****************************************************************************/
+
+const struct security_token *get_current_nttok(connection_struct *conn)
+{
+ if (current_user.nt_user_token) {
+ return current_user.nt_user_token;
+ }
+ return sec_ctx_active_token();
+}
diff --git a/source3/smbd/utmp.c b/source3/smbd/utmp.c
new file mode 100644
index 0000000..4327301
--- /dev/null
+++ b/source3/smbd/utmp.c
@@ -0,0 +1,604 @@
+/*
+ Unix SMB/CIFS implementation.
+ utmp routines
+ Copyright (C) T.D.Lee@durham.ac.uk 1999
+ Heavily modified by Andrew Bartlett and Tridge, April 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+
+/****************************************************************************
+Reflect connection status in utmp/wtmp files.
+ T.D.Lee@durham.ac.uk September 1999
+
+ With grateful thanks since then to many who have helped port it to
+ different operating systems. The variety of OS quirks thereby
+ uncovered is amazing...
+
+Hints for porting:
+ o Always attempt to use programmatic interface (pututline() etc.)
+ Indeed, at present only programmatic use is supported.
+ o The only currently supported programmatic interface to "wtmp{,x}"
+ is through "updwtmp*()" routines.
+ o The "x" (utmpx/wtmpx; HAVE_UTMPX_H) seems preferable.
+ o The HAVE_* items should identify supported features.
+ o If at all possible, avoid "if defined(MY-OS)" constructions.
+
+OS observations and status:
+ Almost every OS seems to have its own quirks.
+
+ Solaris 2.x:
+ Tested on 2.6 and 2.7; should be OK on other flavours.
+ AIX:
+ Apparently has utmpx.h but doesn't implement.
+ OSF:
+ Has utmpx.h, but (e.g.) no "getutmpx()". (Is this like AIX ?)
+ Redhat 6:
+ utmpx.h seems not to set default filenames. non-x better.
+ IRIX 6.5:
+ Not tested. Appears to have "x".
+ HP-UX 9.x:
+ Not tested. Appears to lack "x".
+ HP-UX 10.x:
+ Not tested.
+ "updwtmp*()" routines seem absent, so no current wtmp* support.
+ Has "ut_addr": probably trivial to implement (although remember
+ that IPv6 is coming...).
+
+ FreeBSD:
+ No "putut*()" type of interface.
+ No "ut_type" and associated defines.
+ Write files directly. Alternatively use its login(3)/logout(3).
+ SunOS 4:
+ Not tested. Resembles FreeBSD, but no login()/logout().
+
+lastlog:
+ Should "lastlog" files, if any, be updated?
+ BSD systems (SunOS 4, FreeBSD):
+ o Prominent mention on man pages.
+ System-V (e.g. Solaris 2):
+ o No mention on man pages, even under "man -k".
+ o Has a "/var/adm/lastlog" file, but pututxline() etc. seem
+ not to touch it.
+ o Despite downplaying (above), nevertheless has <lastlog.h>.
+ So perhaps UN*X "lastlog" facility is intended for tty/terminal only?
+
+Notes:
+ Each connection requires a small number (starting at 0, working up)
+ to represent the line. This must be unique within and across all
+ smbd processes. It is the 'id_num' from Samba's session.c code.
+
+ The 4 byte 'ut_id' component is vital to distinguish connections,
+ of which there could be several hundred or even thousand.
+ Entries seem to be printable characters, with optional NULL pads.
+
+ We need to be distinct from other entries in utmp/wtmp.
+
+ Observed things: therefore avoid them. Add to this list please.
+ From Solaris 2.x (because that's what I have):
+ 'sN' : run-levels; N: [0-9]
+ 'co' : console
+ 'CC' : arbitrary things; C: [a-z]
+ 'rXNN' : rlogin; N: [0-9]; X: [0-9a-z]
+ 'tXNN' : rlogin; N: [0-9]; X: [0-9a-z]
+ '/NNN' : Solaris CDE
+ 'ftpZ' : ftp (Z is the number 255, aka 0377, aka 0xff)
+ Mostly a record uses the same 'ut_id' in both "utmp" and "wtmp",
+ but differences have been seen.
+
+ Arbitrarily I have chosen to use a distinctive 'SM' for the
+ first two bytes.
+
+ The remaining two bytes encode the session 'id_num' (see above).
+ Our caller (session.c) should note our 16-bit limitation.
+
+****************************************************************************/
+
+#ifndef WITH_UTMP
+/*
+ * Not WITH_UTMP? Simply supply dummy routines.
+ */
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{}
+
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{}
+
+#else /* WITH_UTMP */
+
+#ifdef HAVE_UTMP_H
+#include <utmp.h>
+#endif
+
+#ifdef HAVE_UTMPX_H
+#include <utmpx.h>
+#endif
+
+/* BSD systems: some may need lastlog.h (SunOS 4), some may not (FreeBSD) */
+/* Some System-V systems (e.g. Solaris 2) declare this too. */
+#ifdef HAVE_LASTLOG_H
+#include <lastlog.h>
+#endif
+
+/****************************************************************************
+ Default paths to various {u,w}tmp{,x} files.
+****************************************************************************/
+
+#ifdef HAVE_UTMPX_H
+
+static const char * const ux_pathname =
+# if defined (UTMPX_FILE)
+ UTMPX_FILE ;
+# elif defined (_UTMPX_FILE)
+ _UTMPX_FILE ;
+# elif defined (_PATH_UTMPX)
+ _PATH_UTMPX ;
+# else
+ "" ;
+# endif
+
+static const char * const wx_pathname =
+# if defined (WTMPX_FILE)
+ WTMPX_FILE ;
+# elif defined (_WTMPX_FILE)
+ _WTMPX_FILE ;
+# elif defined (_PATH_WTMPX)
+ _PATH_WTMPX ;
+# else
+ "" ;
+# endif
+
+#endif /* HAVE_UTMPX_H */
+
+static const char * const ut_pathname =
+# if defined (UTMP_FILE)
+ UTMP_FILE ;
+# elif defined (_UTMP_FILE)
+ _UTMP_FILE ;
+# elif defined (_PATH_UTMP)
+ _PATH_UTMP ;
+# else
+ "" ;
+# endif
+
+static const char * const wt_pathname =
+# if defined (WTMP_FILE)
+ WTMP_FILE ;
+# elif defined (_WTMP_FILE)
+ _WTMP_FILE ;
+# elif defined (_PATH_WTMP)
+ _PATH_WTMP ;
+# else
+ "" ;
+# endif
+
+/* BSD-like systems might want "lastlog" support. */
+#if 0 /* *** Not yet implemented */
+#ifndef HAVE_PUTUTLINE /* see "pututline_my()" */
+static const char *ll_pathname =
+# if defined (_PATH_LASTLOG) /* what other names (if any?) */
+ _PATH_LASTLOG ;
+# else
+ "" ;
+# endif /* _PATH_LASTLOG */
+#endif /* HAVE_PUTUTLINE */
+#endif
+
+/*
+ * Get name of {u,w}tmp{,x} file.
+ * return: fname contains filename
+ * Possibly empty if this code not yet ported to this system.
+ *
+ * utmp{,x}: try "utmp dir", then default (a define)
+ * wtmp{,x}: try "wtmp dir", then "utmp dir", then default (a define)
+ */
+static char *uw_pathname(TALLOC_CTX *ctx,
+ const char *uw_name,
+ const char *uw_default)
+{
+ char *dirname = NULL;
+
+ /* For w-files, first look for explicit "wtmp dir" */
+ if (uw_name[0] == 'w') {
+ dirname = talloc_strdup(ctx, lp_wtmp_directory());
+ if (!dirname) {
+ return NULL;
+ }
+ trim_char(dirname,'\0','/');
+ }
+
+ /* For u-files and non-explicit w-dir, look for "utmp dir" */
+ if ((dirname == NULL) || (strlen(dirname) == 0)) {
+ dirname = talloc_strdup(ctx, lp_utmp_directory());
+ if (!dirname) {
+ return NULL;
+ }
+ trim_char(dirname,'\0','/');
+ }
+
+ /* If explicit directory above, use it */
+ if (dirname && strlen(dirname) != 0) {
+ return talloc_asprintf(ctx,
+ "%s/%s",
+ dirname,
+ uw_name);
+ }
+
+ /* No explicit directory: attempt to use default paths */
+ if (strlen(uw_default) == 0) {
+ /* No explicit setting, no known default.
+ * Has it yet been ported to this OS?
+ */
+ DEBUG(2,("uw_pathname: unable to determine pathname\n"));
+ }
+ return talloc_strdup(ctx, uw_default);
+}
+
+#ifndef HAVE_PUTUTLINE
+/****************************************************************************
+ Update utmp file directly. No subroutine interface: probably a BSD system.
+****************************************************************************/
+
+static void pututline_my(const char *uname, struct utmp *u, bool claim)
+{
+ DEBUG(1,("pututline_my: not yet implemented\n"));
+ /* BSD implementor: may want to consider (or not) adjusting "lastlog" */
+}
+#endif /* HAVE_PUTUTLINE */
+
+#ifndef HAVE_UPDWTMP
+
+/****************************************************************************
+ Update wtmp file directly. No subroutine interface: probably a BSD system.
+ Credit: Michail Vidiassov <master@iaas.msu.ru>
+****************************************************************************/
+
+static void updwtmp_my(const char *wname, struct utmp *u, bool claim)
+{
+ int fd;
+ struct stat buf;
+
+ if (! claim) {
+ /*
+ * BSD-like systems:
+ * may use empty ut_name to distinguish a logout record.
+ *
+ * May need "if defined(SUNOS4)" etc. around some of these,
+ * but try to avoid if possible.
+ *
+ * SunOS 4:
+ * man page indicates ut_name and ut_host both NULL
+ * FreeBSD 4.0:
+ * man page appears not to specify (hints non-NULL)
+ * A correspondent suggest at least ut_name should be NULL
+ */
+#if defined(HAVE_UT_UT_NAME)
+ memset((char *)&u->ut_name, '\0', sizeof(u->ut_name));
+#endif
+#if defined(HAVE_UT_UT_HOST)
+ memset((char *)&u->ut_host, '\0', sizeof(u->ut_host));
+#endif
+ }
+ /* Stolen from logwtmp function in libutil.
+ * May be more locking/blocking is needed?
+ */
+ if ((fd = open(wname, O_WRONLY|O_APPEND, 0)) < 0)
+ return;
+ if (fstat(fd, &buf) == 0) {
+ if (write(fd, (char *)u, sizeof(struct utmp)) != sizeof(struct utmp))
+ (void) ftruncate(fd, buf.st_size);
+ }
+ (void) close(fd);
+}
+#endif /* HAVE_UPDWTMP */
+
+/****************************************************************************
+ Update via utmp/wtmp (not utmpx/wtmpx).
+****************************************************************************/
+
+static void utmp_nox_update(struct utmp *u, bool claim)
+{
+ char *uname = NULL;
+ char *wname = NULL;
+#if defined(PUTUTLINE_RETURNS_UTMP)
+ struct utmp *urc;
+#endif /* PUTUTLINE_RETURNS_UTMP */
+
+ uname = uw_pathname(talloc_tos(), "utmp", ut_pathname);
+ if (!uname) {
+ return;
+ }
+ DEBUG(2,("utmp_nox_update: uname:%s\n", uname));
+
+#ifdef HAVE_PUTUTLINE
+ if (strlen(uname) != 0) {
+ utmpname(uname);
+ }
+
+# if defined(PUTUTLINE_RETURNS_UTMP)
+ setutent();
+ urc = pututline(u);
+ endutent();
+ if (urc == NULL) {
+ DEBUG(2,("utmp_nox_update: pututline() failed\n"));
+ return;
+ }
+# else /* PUTUTLINE_RETURNS_UTMP */
+ setutent();
+ pututline(u);
+ endutent();
+# endif /* PUTUTLINE_RETURNS_UTMP */
+
+#else /* HAVE_PUTUTLINE */
+ if (strlen(uname) != 0) {
+ pututline_my(uname, u, claim);
+ }
+#endif /* HAVE_PUTUTLINE */
+
+ wname = uw_pathname(talloc_tos(), "wtmp", wt_pathname);
+ if (!wname) {
+ return;
+ }
+ DEBUG(2,("utmp_nox_update: wname:%s\n", wname));
+ if (strlen(wname) != 0) {
+#ifdef HAVE_UPDWTMP
+ updwtmp(wname, u);
+ /*
+ * updwtmp() and the newer updwtmpx() may be unsymmetrical.
+ * At least one OS, Solaris 2.x declares the former in the
+ * "utmpx" (latter) file and context.
+ * In the Solaris case this is irrelevant: it has both and
+ * we always prefer the "x" case, so doesn't come here.
+ * But are there other systems, with no "x", which lack
+ * updwtmp() perhaps?
+ */
+#else
+ updwtmp_my(wname, u, claim);
+#endif /* HAVE_UPDWTMP */
+ }
+}
+
+/****************************************************************************
+ Copy a string in the utmp structure.
+****************************************************************************/
+
+static void utmp_strcpy(char *dest, const char *src, size_t n)
+{
+ size_t len = 0;
+
+ memset(dest, '\0', n);
+ if (src)
+ len = strlen(src);
+ if (len >= n) {
+ memcpy(dest, src, n);
+ } else {
+ if (len)
+ memcpy(dest, src, len);
+ }
+}
+
+/****************************************************************************
+ Update via utmpx/wtmpx (preferred) or via utmp/wtmp.
+****************************************************************************/
+
+static void sys_utmp_update(struct utmp *u, const char *hostname, bool claim)
+{
+#if !defined(HAVE_UTMPX_H)
+ /* No utmpx stuff. Drop to non-x stuff */
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_PUTUTXLINE)
+ /* Odd. Have utmpx.h but no "pututxline()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no pututxline() function\n"));
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_GETUTMPX)
+ /* Odd. Have utmpx.h but no "getutmpx()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no getutmpx() function\n"));
+ utmp_nox_update(u, claim);
+#elif !defined(HAVE_UPDWTMPX)
+ /* Have utmpx.h but no "updwtmpx()". Drop to non-x stuff */
+ DEBUG(1,("utmp_update: have utmpx.h but no updwtmpx() function\n"));
+ utmp_nox_update(u, claim);
+#else
+ char *uname = NULL;
+ char *wname = NULL;
+ struct utmpx ux, *uxrc;
+
+ getutmpx(u, &ux);
+
+#if defined(HAVE_UX_UT_SYSLEN)
+ if (hostname)
+ ux.ut_syslen = strlen(hostname) + 1; /* include end NULL */
+ else
+ ux.ut_syslen = 0;
+#endif
+#if defined(HAVE_UX_UT_HOST)
+ utmp_strcpy(ux.ut_host, hostname, sizeof(ux.ut_host));
+#endif
+
+ uname = uw_pathname(talloc_tos(), "utmpx", ux_pathname);
+ wname = uw_pathname(talloc_tos(), "wtmpx", wx_pathname);
+ if (uname && wname) {
+ DEBUG(2,("utmp_update: uname:%s wname:%s\n", uname, wname));
+ }
+
+ /*
+ * Check for either uname or wname being empty.
+ * Some systems, such as Redhat 6, have a "utmpx.h" which doesn't
+ * define default filenames.
+ * Also, our local installation has not provided an override.
+ * Drop to non-x method. (E.g. RH6 has good defaults in "utmp.h".)
+ */
+ if (!uname || !wname || (strlen(uname) == 0) || (strlen(wname) == 0)) {
+ utmp_nox_update(u, claim);
+ } else {
+ utmpxname(uname);
+ setutxent();
+ uxrc = pututxline(&ux);
+ endutxent();
+ if (uxrc == NULL) {
+ DEBUG(2,("utmp_update: pututxline() failed\n"));
+ return;
+ }
+ updwtmpx(wname, &ux);
+ }
+#endif /* HAVE_UTMPX_H */
+}
+
+#if defined(HAVE_UT_UT_ID)
+/****************************************************************************
+ Encode the unique connection number into "ut_id".
+****************************************************************************/
+
+static int ut_id_encode(int i, char *fourbyte)
+{
+ int nbase;
+ const char *ut_id_encstr = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+/*
+ * 'ut_id_encstr' is the character set on which modulo arithmetic is done.
+ * Example: digits would produce the base-10 numbers from '001'.
+ */
+ nbase = strlen(ut_id_encstr);
+
+ fourbyte[0] = ut_id_encstr[i % nbase];
+ i /= nbase;
+ fourbyte[1] = ut_id_encstr[i % nbase];
+ i /= nbase;
+ fourbyte[3] = ut_id_encstr[i % nbase];
+ i /= nbase;
+ fourbyte[2] = ut_id_encstr[i % nbase];
+ i /= nbase;
+
+ /* we do not care about overflows as i is a random number */
+ return 0;
+}
+#endif /* defined(HAVE_UT_UT_ID) */
+
+
+/*
+ fill a system utmp structure given all the info we can gather
+*/
+static bool sys_utmp_fill(struct utmp *u,
+ const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{
+ struct timeval timeval;
+
+ /*
+ * ut_name, ut_user:
+ * Several (all?) systems seems to define one as the other.
+ * It is easier and clearer simply to let the following take its course,
+ * rather than to try to detect and optimise.
+ */
+#if defined(HAVE_UT_UT_USER)
+ utmp_strcpy(u->ut_user, username, sizeof(u->ut_user));
+#elif defined(HAVE_UT_UT_NAME)
+ utmp_strcpy(u->ut_name, username, sizeof(u->ut_name));
+#endif
+
+ /*
+ * ut_line:
+ * If size limit proves troublesome, then perhaps use "ut_id_encode()".
+ */
+ utmp_strcpy(u->ut_line, id_str, sizeof(u->ut_line));
+
+#if defined(HAVE_UT_UT_PID)
+ u->ut_pid = getpid();
+#endif
+
+/*
+ * ut_time, ut_tv:
+ * Some have one, some the other. Many have both, but defined (aliased).
+ * It is easier and clearer simply to let the following take its course.
+ * But note that we do the more precise ut_tv as the final assignment.
+ */
+#if defined(HAVE_UT_UT_TIME)
+ GetTimeOfDay(&timeval);
+ u->ut_time = timeval.tv_sec;
+#elif defined(HAVE_UT_UT_TV)
+ GetTimeOfDay(&timeval);
+ u->ut_tv = timeval;
+#else
+#error "with-utmp must have UT_TIME or UT_TV"
+#endif
+
+#if defined(HAVE_UT_UT_HOST)
+ utmp_strcpy(u->ut_host, hostname, sizeof(u->ut_host));
+#endif
+
+#if defined(HAVE_UT_UT_ID)
+ if (ut_id_encode(id_num, u->ut_id) != 0) {
+ DEBUG(1,("utmp_fill: cannot encode id %d\n", id_num));
+ return False;
+ }
+#endif
+
+ return True;
+}
+
+/****************************************************************************
+ Close a connection.
+****************************************************************************/
+
+void sys_utmp_yield(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{
+ struct utmp u;
+
+ ZERO_STRUCT(u);
+
+#if defined(HAVE_UT_UT_EXIT)
+ u.ut_exit.e_termination = 0;
+ u.ut_exit.e_exit = 0;
+#endif
+
+#if defined(HAVE_UT_UT_TYPE)
+ u.ut_type = DEAD_PROCESS;
+#endif
+
+ if (!sys_utmp_fill(&u, username, hostname, id_str, id_num))
+ return;
+
+ sys_utmp_update(&u, NULL, False);
+}
+
+/****************************************************************************
+ Claim a entry in whatever utmp system the OS uses.
+****************************************************************************/
+
+void sys_utmp_claim(const char *username, const char *hostname,
+ const char *id_str, int id_num)
+{
+ struct utmp u;
+
+ ZERO_STRUCT(u);
+
+#if defined(HAVE_UT_UT_TYPE)
+ u.ut_type = USER_PROCESS;
+#endif
+
+ if (!sys_utmp_fill(&u, username, hostname, id_str, id_num))
+ return;
+
+ sys_utmp_update(&u, hostname, True);
+}
+
+#endif /* WITH_UTMP */
diff --git a/source3/smbd/vfs.c b/source3/smbd/vfs.c
new file mode 100644
index 0000000..0b061f1
--- /dev/null
+++ b/source3/smbd/vfs.c
@@ -0,0 +1,2661 @@
+/*
+ Unix SMB/Netbios implementation.
+ Version 1.9.
+ VFS initialisation and support functions
+ Copyright (C) Tim Potter 1999
+ Copyright (C) Alexander Bokovoy 2002
+ Copyright (C) James Peach 2006
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ This work was sponsored by Optifacio Software Services, Inc.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "../lib/util/memcache.h"
+#include "transfer_file.h"
+#include "ntioctl.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/sys_rw.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_VFS
+
+static_decl_vfs;
+
+struct vfs_fsp_data {
+ struct vfs_fsp_data *next;
+ struct vfs_handle_struct *owner;
+ void (*destroy)(void *p_data);
+ void *_dummy_;
+ /* NOTE: This structure contains four pointers so that we can guarantee
+ * that the end of the structure is always both 4-byte and 8-byte aligned.
+ */
+};
+
+struct vfs_init_function_entry {
+ char *name;
+ struct vfs_init_function_entry *prev, *next;
+ const struct vfs_fn_pointers *fns;
+};
+
+/****************************************************************************
+ maintain the list of available backends
+****************************************************************************/
+
+static struct vfs_init_function_entry *vfs_find_backend_entry(const char *name)
+{
+ struct vfs_init_function_entry *entry = backends;
+
+ DEBUG(10, ("vfs_find_backend_entry called for %s\n", name));
+
+ while(entry) {
+ if (strcmp(entry->name, name)==0) return entry;
+ entry = entry->next;
+ }
+
+ return NULL;
+}
+
+NTSTATUS smb_register_vfs(int version, const char *name,
+ const struct vfs_fn_pointers *fns)
+{
+ struct vfs_init_function_entry *entry = backends;
+
+ if ((version != SMB_VFS_INTERFACE_VERSION)) {
+ DEBUG(0, ("Failed to register vfs module.\n"
+ "The module was compiled against SMB_VFS_INTERFACE_VERSION %d,\n"
+ "current SMB_VFS_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current Samba Version!\n",
+ version, SMB_VFS_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0]) {
+ DEBUG(0,("smb_register_vfs() called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (vfs_find_backend_entry(name)) {
+ DEBUG(0,("VFS module %s already loaded!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = SMB_XMALLOC_P(struct vfs_init_function_entry);
+ entry->name = smb_xstrdup(name);
+ entry->fns = fns;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5, ("Successfully added vfs backend '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+ initialise default vfs hooks
+****************************************************************************/
+
+static void vfs_init_default(connection_struct *conn)
+{
+ DEBUG(3, ("Initialising default vfs hooks\n"));
+ vfs_init_custom(conn, DEFAULT_VFS_MODULE_NAME);
+}
+
+/****************************************************************************
+ initialise custom vfs hooks
+ ****************************************************************************/
+
+bool vfs_init_custom(connection_struct *conn, const char *vfs_object)
+{
+ char *module_path = NULL;
+ char *module_name = NULL;
+ char *module_param = NULL, *p;
+ vfs_handle_struct *handle;
+ const struct vfs_init_function_entry *entry;
+
+ if (!conn||!vfs_object||!vfs_object[0]) {
+ DEBUG(0, ("vfs_init_custom() called with NULL pointer or "
+ "empty vfs_object!\n"));
+ return False;
+ }
+
+ if(!backends) {
+ static_init_vfs(NULL);
+ }
+
+ DEBUG(3, ("Initialising custom vfs hooks from [%s]\n", vfs_object));
+
+ module_path = smb_xstrdup(vfs_object);
+
+ p = strchr_m(module_path, ':');
+
+ if (p) {
+ *p = 0;
+ module_param = p+1;
+ trim_char(module_param, ' ', ' ');
+ }
+
+ trim_char(module_path, ' ', ' ');
+
+ module_name = smb_xstrdup(module_path);
+
+ if ((module_name[0] == '/') &&
+ (strcmp(module_path, DEFAULT_VFS_MODULE_NAME) != 0)) {
+
+ /*
+ * Extract the module name from the path. Just use the base
+ * name of the last path component.
+ */
+
+ SAFE_FREE(module_name);
+ module_name = smb_xstrdup(strrchr_m(module_path, '/')+1);
+
+ p = strchr_m(module_name, '.');
+
+ if (p != NULL) {
+ *p = '\0';
+ }
+ }
+
+ /* First, try to load the module with the new module system */
+ entry = vfs_find_backend_entry(module_name);
+ if (!entry) {
+ NTSTATUS status;
+
+ DEBUG(5, ("vfs module [%s] not loaded - trying to load...\n",
+ vfs_object));
+
+ status = smb_load_module("vfs", module_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("error probing vfs module '%s': %s\n",
+ module_path, nt_errstr(status)));
+ goto fail;
+ }
+
+ entry = vfs_find_backend_entry(module_name);
+ if (!entry) {
+ DEBUG(0,("Can't find a vfs module [%s]\n",vfs_object));
+ goto fail;
+ }
+ }
+
+ DEBUGADD(5,("Successfully loaded vfs module [%s] with the new modules system\n", vfs_object));
+
+ handle = talloc_zero(conn, vfs_handle_struct);
+ if (!handle) {
+ DEBUG(0,("TALLOC_ZERO() failed!\n"));
+ goto fail;
+ }
+ handle->conn = conn;
+ handle->fns = entry->fns;
+ if (module_param) {
+ handle->param = talloc_strdup(conn, module_param);
+ }
+ DLIST_ADD(conn->vfs_handles, handle);
+
+ SAFE_FREE(module_path);
+ SAFE_FREE(module_name);
+ return True;
+
+ fail:
+ SAFE_FREE(module_path);
+ SAFE_FREE(module_name);
+ return False;
+}
+
+/*****************************************************************
+ Allow VFS modules to extend files_struct with VFS-specific state.
+ This will be ok for small numbers of extensions, but might need to
+ be refactored if it becomes more widely used.
+******************************************************************/
+
+#define EXT_DATA_AREA(e) ((uint8_t *)(e) + sizeof(struct vfs_fsp_data))
+
+void *vfs_add_fsp_extension_notype(vfs_handle_struct *handle,
+ files_struct *fsp, size_t ext_size,
+ void (*destroy_fn)(void *p_data))
+{
+ struct vfs_fsp_data *ext;
+ void * ext_data;
+
+ /* Prevent VFS modules adding multiple extensions. */
+ if ((ext_data = vfs_fetch_fsp_extension(handle, fsp))) {
+ return ext_data;
+ }
+
+ ext = talloc_zero_size(
+ handle->conn, sizeof(struct vfs_fsp_data) + ext_size);
+ if (ext == NULL) {
+ return NULL;
+ }
+
+ ext->owner = handle;
+ ext->next = fsp->vfs_extension;
+ ext->destroy = destroy_fn;
+ fsp->vfs_extension = ext;
+ return EXT_DATA_AREA(ext);
+}
+
+void vfs_remove_fsp_extension(vfs_handle_struct *handle, files_struct *fsp)
+{
+ struct vfs_fsp_data *curr;
+ struct vfs_fsp_data *prev;
+
+ for (curr = fsp->vfs_extension, prev = NULL;
+ curr;
+ prev = curr, curr = curr->next) {
+ if (curr->owner == handle) {
+ if (prev) {
+ prev->next = curr->next;
+ } else {
+ fsp->vfs_extension = curr->next;
+ }
+ if (curr->destroy) {
+ curr->destroy(EXT_DATA_AREA(curr));
+ }
+ TALLOC_FREE(curr);
+ return;
+ }
+ }
+}
+
+void vfs_remove_all_fsp_extensions(files_struct *fsp)
+{
+ struct vfs_fsp_data *curr;
+ struct vfs_fsp_data *next;
+
+ for (curr = fsp->vfs_extension; curr; curr = next) {
+
+ next = curr->next;
+ fsp->vfs_extension = next;
+
+ if (curr->destroy) {
+ curr->destroy(EXT_DATA_AREA(curr));
+ }
+ TALLOC_FREE(curr);
+ }
+}
+
+void *vfs_memctx_fsp_extension(vfs_handle_struct *handle,
+ const struct files_struct *fsp)
+{
+ struct vfs_fsp_data *head;
+
+ for (head = fsp->vfs_extension; head; head = head->next) {
+ if (head->owner == handle) {
+ return head;
+ }
+ }
+
+ return NULL;
+}
+
+void *vfs_fetch_fsp_extension(vfs_handle_struct *handle,
+ const struct files_struct *fsp)
+{
+ struct vfs_fsp_data *head;
+
+ head = (struct vfs_fsp_data *)vfs_memctx_fsp_extension(handle, fsp);
+ if (head != NULL) {
+ return EXT_DATA_AREA(head);
+ }
+
+ return NULL;
+}
+
+#undef EXT_DATA_AREA
+
+/*
+ * Ensure this module catches all VFS functions.
+ */
+#ifdef DEVELOPER
+void smb_vfs_assert_all_fns(const struct vfs_fn_pointers* fns,
+ const char *module)
+{
+ bool missing_fn = false;
+ unsigned int idx;
+ const uintptr_t *end = (const uintptr_t *)(fns + 1);
+
+ for (idx = 0; ((const uintptr_t *)fns + idx) < end; idx++) {
+ if (*((const uintptr_t *)fns + idx) == 0) {
+ DBG_ERR("VFS function at index %d not implemented "
+ "in module %s\n", idx, module);
+ missing_fn = true;
+ }
+ }
+
+ if (missing_fn) {
+ smb_panic("Required VFS function not implemented in module.\n");
+ }
+}
+#else
+void smb_vfs_assert_all_fns(const struct vfs_fn_pointers* fns,
+ const char *module)
+{
+}
+#endif
+
+/*****************************************************************
+ Generic VFS init.
+******************************************************************/
+
+bool smbd_vfs_init(connection_struct *conn)
+{
+ const char **vfs_objects;
+ unsigned int i = 0;
+ int j = 0;
+
+ /* Normal share - initialise with disk access functions */
+ vfs_init_default(conn);
+
+ /* No need to load vfs modules for printer connections */
+ if (conn->printer) {
+ return True;
+ }
+
+ if (lp_widelinks(SNUM(conn))) {
+ /*
+ * As the widelinks logic is now moving into a
+ * vfs_widelinks module, we need to custom load
+ * it after the default module is initialized.
+ * That way no changes to smb.conf files are
+ * needed.
+ */
+ bool ok = vfs_init_custom(conn, "widelinks");
+ if (!ok) {
+ DBG_ERR("widelinks enabled and vfs_init_custom "
+ "failed for vfs_widelinks module\n");
+ return false;
+ }
+ }
+
+ vfs_objects = lp_vfs_objects(SNUM(conn));
+
+ /* Override VFS functions if 'vfs object' was not specified*/
+ if (!vfs_objects || !vfs_objects[0])
+ return True;
+
+ for (i=0; vfs_objects[i] ;) {
+ i++;
+ }
+
+ for (j=i-1; j >= 0; j--) {
+ if (!vfs_init_custom(conn, vfs_objects[j])) {
+ DEBUG(0, ("smbd_vfs_init: vfs_init_custom failed for %s\n", vfs_objects[j]));
+ return False;
+ }
+ }
+ return True;
+}
+
+/*******************************************************************
+ Check if a file exists in the vfs.
+********************************************************************/
+
+NTSTATUS vfs_file_exist(connection_struct *conn, struct smb_filename *smb_fname)
+{
+ /* Only return OK if stat was successful and S_ISREG */
+ if ((SMB_VFS_STAT(conn, smb_fname) != -1) &&
+ S_ISREG(smb_fname->st.st_ex_mode)) {
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+bool vfs_valid_pread_range(off_t offset, size_t length)
+{
+ return sys_valid_io_range(offset, length);
+}
+
+bool vfs_valid_pwrite_range(off_t offset, size_t length)
+{
+ /*
+ * See MAXFILESIZE in [MS-FSA] 2.1.5.3 Server Requests a Write
+ */
+ static const uint64_t maxfilesize = 0xfffffff0000;
+ uint64_t last_byte_ofs;
+ bool ok;
+
+ ok = sys_valid_io_range(offset, length);
+ if (!ok) {
+ return false;
+ }
+
+ if (length == 0) {
+ return true;
+ }
+
+ last_byte_ofs = offset + length;
+ if (last_byte_ofs > maxfilesize) {
+ return false;
+ }
+
+ return true;
+}
+
+ssize_t vfs_pwrite_data(struct smb_request *req,
+ files_struct *fsp,
+ const char *buffer,
+ size_t N,
+ off_t offset)
+{
+ size_t total=0;
+ ssize_t ret;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(offset, N);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (req && req->unread_bytes) {
+ int sockfd = req->xconn->transport.sock;
+ SMB_ASSERT(req->unread_bytes == N);
+ /* VFS_RECVFILE must drain the socket
+ * before returning. */
+ req->unread_bytes = 0;
+ /*
+ * Leave the socket non-blocking and
+ * use SMB_VFS_RECVFILE. If it returns
+ * EAGAIN || EWOULDBLOCK temporarily set
+ * the socket blocking and retry
+ * the RECVFILE.
+ */
+ while (total < N) {
+ ret = SMB_VFS_RECVFILE(sockfd,
+ fsp,
+ offset + total,
+ N - total);
+ if (ret == 0 || (ret == -1 &&
+ (errno == EAGAIN ||
+ errno == EWOULDBLOCK))) {
+ int old_flags;
+ /* Ensure the socket is blocking. */
+ old_flags = fcntl(sockfd, F_GETFL, 0);
+ if (set_blocking(sockfd, true) == -1) {
+ return (ssize_t)-1;
+ }
+ ret = SMB_VFS_RECVFILE(sockfd,
+ fsp,
+ offset + total,
+ N - total);
+ if (fcntl(sockfd, F_SETFL, old_flags) == -1) {
+ return (ssize_t)-1;
+ }
+ if (ret == -1) {
+ return (ssize_t)-1;
+ }
+ total += ret;
+ return (ssize_t)total;
+ }
+ /* Any other error case. */
+ if (ret == -1) {
+ return ret;
+ }
+ total += ret;
+ }
+ return (ssize_t)total;
+ }
+
+ while (total < N) {
+ ret = SMB_VFS_PWRITE(fsp, buffer + total, N - total,
+ offset + total);
+
+ if (ret == -1)
+ return -1;
+ if (ret == 0)
+ return total;
+
+ total += ret;
+ }
+ return (ssize_t)total;
+}
+/****************************************************************************
+ An allocate file space call using the vfs interface.
+ Allocates space for a file from a filedescriptor.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_allocate_file_space(files_struct *fsp, uint64_t len)
+{
+ int ret;
+ connection_struct *conn = fsp->conn;
+ uint64_t space_avail;
+ uint64_t bsize,dfree,dsize;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Actually try and commit the space on disk....
+ */
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, len %.0f\n",
+ fsp_str_dbg(fsp), (double)len));
+
+ ok = vfs_valid_pwrite_range((off_t)len, 0);
+ if (!ok) {
+ DEBUG(0,("vfs_allocate_file_space: %s negative/invalid len "
+ "requested.\n", fsp_str_dbg(fsp)));
+ errno = EINVAL;
+ return -1;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ if (len == (uint64_t)fsp->fsp_name->st.st_ex_size)
+ return 0;
+
+ if (len < (uint64_t)fsp->fsp_name->st.st_ex_size) {
+ /* Shrink - use ftruncate. */
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, shrink. Current "
+ "size %.0f\n", fsp_str_dbg(fsp),
+ (double)fsp->fsp_name->st.st_ex_size));
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_ALLOC_SHRINK);
+
+ ret = SMB_VFS_FTRUNCATE(fsp, (off_t)len);
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_ALLOC_SHRINK);
+
+ return ret;
+ }
+
+ /* Grow - we need to test if we have enough space. */
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_ALLOC_GROW);
+
+ if (lp_strict_allocate(SNUM(fsp->conn))) {
+ /* See if we have a syscall that will allocate beyond
+ end-of-file without changing EOF. */
+ ret = SMB_VFS_FALLOCATE(fsp, VFS_FALLOCATE_FL_KEEP_SIZE,
+ 0, len);
+ } else {
+ ret = 0;
+ }
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_ALLOC_GROW);
+
+ if (ret == 0) {
+ /* We changed the allocation size on disk, but not
+ EOF - exactly as required. We're done ! */
+ return 0;
+ }
+
+ if (ret == -1 && errno == ENOSPC) {
+ return -1;
+ }
+
+ len -= fsp->fsp_name->st.st_ex_size;
+ len /= 1024; /* Len is now number of 1k blocks needed. */
+ space_avail =
+ get_dfree_info(conn, fsp->fsp_name, &bsize, &dfree, &dsize);
+ if (space_avail == (uint64_t)-1) {
+ return -1;
+ }
+
+ DEBUG(10,("vfs_allocate_file_space: file %s, grow. Current size %.0f, "
+ "needed blocks = %.0f, space avail = %.0f\n",
+ fsp_str_dbg(fsp), (double)fsp->fsp_name->st.st_ex_size, (double)len,
+ (double)space_avail));
+
+ if (len > space_avail) {
+ errno = ENOSPC;
+ return -1;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ A vfs set_filelen call.
+ set the length of a file from a filedescriptor.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_set_filelen(files_struct *fsp, off_t len)
+{
+ int ret;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(len, 0);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_SET_FILE_LEN);
+
+ DEBUG(10,("vfs_set_filelen: ftruncate %s to len %.0f\n",
+ fsp_str_dbg(fsp), (double)len));
+ if ((ret = SMB_VFS_FTRUNCATE(fsp, len)) != -1) {
+ notify_fname(fsp->conn, NOTIFY_ACTION_MODIFIED,
+ FILE_NOTIFY_CHANGE_SIZE
+ | FILE_NOTIFY_CHANGE_ATTRIBUTES,
+ fsp->fsp_name->base_name);
+ }
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_SET_FILE_LEN);
+
+ return ret;
+}
+
+/****************************************************************************
+ A slow version of fallocate. Fallback code if SMB_VFS_FALLOCATE
+ fails. Needs to be outside of the default version of SMB_VFS_FALLOCATE
+ as this is also called from the default SMB_VFS_FTRUNCATE code.
+ Always extends the file size.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+#define SPARSE_BUF_WRITE_SIZE (32*1024)
+
+int vfs_slow_fallocate(files_struct *fsp, off_t offset, off_t len)
+{
+ ssize_t pwrite_ret;
+ size_t total = 0;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(offset, len);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!sparse_buf) {
+ sparse_buf = SMB_CALLOC_ARRAY(char, SPARSE_BUF_WRITE_SIZE);
+ if (!sparse_buf) {
+ errno = ENOMEM;
+ return -1;
+ }
+ }
+
+ while (total < len) {
+ size_t curr_write_size = MIN(SPARSE_BUF_WRITE_SIZE, (len - total));
+
+ pwrite_ret = SMB_VFS_PWRITE(fsp, sparse_buf, curr_write_size, offset + total);
+ if (pwrite_ret == -1) {
+ int saved_errno = errno;
+ DEBUG(10,("vfs_slow_fallocate: SMB_VFS_PWRITE for file "
+ "%s failed with error %s\n",
+ fsp_str_dbg(fsp), strerror(saved_errno)));
+ errno = saved_errno;
+ return -1;
+ }
+ total += pwrite_ret;
+ }
+
+ return 0;
+}
+
+/****************************************************************************
+ A vfs fill sparse call.
+ Writes zeros from the end of file to len, if len is greater than EOF.
+ Used only by strict_sync.
+ Returns 0 on success, -1 on failure.
+****************************************************************************/
+
+int vfs_fill_sparse(files_struct *fsp, off_t len)
+{
+ int ret;
+ NTSTATUS status;
+ off_t offset;
+ size_t num_to_write;
+ bool ok;
+
+ ok = vfs_valid_pwrite_range(len, 0);
+ if (!ok) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ if (len <= fsp->fsp_name->st.st_ex_size) {
+ return 0;
+ }
+
+#ifdef S_ISFIFO
+ if (S_ISFIFO(fsp->fsp_name->st.st_ex_mode)) {
+ return 0;
+ }
+#endif
+
+ DEBUG(10,("vfs_fill_sparse: write zeros in file %s from len %.0f to "
+ "len %.0f (%.0f bytes)\n", fsp_str_dbg(fsp),
+ (double)fsp->fsp_name->st.st_ex_size, (double)len,
+ (double)(len - fsp->fsp_name->st.st_ex_size)));
+
+ contend_level2_oplocks_begin(fsp, LEVEL2_CONTEND_FILL_SPARSE);
+
+ offset = fsp->fsp_name->st.st_ex_size;
+ num_to_write = len - fsp->fsp_name->st.st_ex_size;
+
+ /* Only do this on non-stream file handles. */
+ if (!fsp_is_alternate_stream(fsp)) {
+ /* for allocation try fallocate first. This can fail on some
+ * platforms e.g. when the filesystem doesn't support it and no
+ * emulation is being done by the libc (like on AIX with JFS1). In that
+ * case we do our own emulation. fallocate implementations can
+ * return ENOTSUP or EINVAL in cases like that. */
+ ret = SMB_VFS_FALLOCATE(fsp, 0, offset, num_to_write);
+ if (ret == -1 && errno == ENOSPC) {
+ goto out;
+ }
+ if (ret == 0) {
+ goto out;
+ }
+ DEBUG(10,("vfs_fill_sparse: SMB_VFS_FALLOCATE failed with "
+ "error %d. Falling back to slow manual allocation\n", ret));
+ }
+
+ ret = vfs_slow_fallocate(fsp, offset, num_to_write);
+
+ out:
+
+ contend_level2_oplocks_end(fsp, LEVEL2_CONTEND_FILL_SPARSE);
+ return ret;
+}
+
+/*******************************************************************************
+ Set a fd into blocking/nonblocking mode through VFS
+*******************************************************************************/
+
+int vfs_set_blocking(files_struct *fsp, bool set)
+{
+ int val;
+#ifdef O_NONBLOCK
+#define FLAG_TO_SET O_NONBLOCK
+#else
+#ifdef SYSV
+#define FLAG_TO_SET O_NDELAY
+#else /* BSD */
+#define FLAG_TO_SET FNDELAY
+#endif
+#endif
+
+ if (fsp->fsp_flags.is_pathref) {
+ return 0;
+ }
+
+ val = SMB_VFS_FCNTL(fsp, F_GETFL, 0);
+ if (val == -1) {
+ return -1;
+ }
+
+ if (set) {
+ val &= ~FLAG_TO_SET;
+ } else {
+ val |= FLAG_TO_SET;
+ }
+
+ return SMB_VFS_FCNTL(fsp, F_SETFL, val);
+#undef FLAG_TO_SET
+}
+
+/****************************************************************************
+ Transfer some data (n bytes) between two file_struct's.
+****************************************************************************/
+
+static ssize_t vfs_pread_fn(void *file, void *buf, size_t len, off_t offset)
+{
+ struct files_struct *fsp = (struct files_struct *)file;
+
+ return SMB_VFS_PREAD(fsp, buf, len, offset);
+}
+
+static ssize_t vfs_pwrite_fn(void *file, const void *buf, size_t len, off_t offset)
+{
+ struct files_struct *fsp = (struct files_struct *)file;
+
+ return SMB_VFS_PWRITE(fsp, buf, len, offset);
+}
+
+off_t vfs_transfer_file(files_struct *in, files_struct *out, off_t n)
+{
+ return transfer_file_internal((void *)in, (void *)out, n,
+ vfs_pread_fn, vfs_pwrite_fn);
+}
+
+/*******************************************************************
+ A vfs_readdir wrapper which just returns the file name.
+********************************************************************/
+
+const char *vfs_readdirname(connection_struct *conn,
+ struct files_struct *dirfsp,
+ DIR *d,
+ char **talloced)
+{
+ struct dirent *ptr= NULL;
+ const char *dname;
+ char *translated;
+ NTSTATUS status;
+
+ if (d == NULL) {
+ return(NULL);
+ }
+
+ ptr = SMB_VFS_READDIR(conn, dirfsp, d);
+ if (!ptr)
+ return(NULL);
+
+ dname = ptr->d_name;
+
+ status = SMB_VFS_TRANSLATE_NAME(conn, dname, vfs_translate_to_windows,
+ talloc_tos(), &translated);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ *talloced = NULL;
+ return dname;
+ }
+ *talloced = translated;
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+ return translated;
+}
+
+/*******************************************************************
+ A wrapper for vfs_chdir().
+********************************************************************/
+
+int vfs_ChDir(connection_struct *conn, const struct smb_filename *smb_fname)
+{
+ int ret;
+ struct smb_filename *cwd = NULL;
+
+ if (!LastDir) {
+ LastDir = SMB_STRDUP("");
+ }
+
+ if (ISDOT(smb_fname->base_name)) {
+ /*
+ * passing a '.' is a noop,
+ * and we only expect this after
+ * everything is initialized.
+ *
+ * So the first vfs_ChDir() on a given
+ * connection_struct must not be '.'.
+ *
+ * Note: conn_new() sets
+ * conn->cwd_fsp->fh->fd = -1
+ * and vfs_ChDir() leaves with
+ * conn->cwd_fsp->fh->fd = AT_FDCWD
+ * on success!
+ */
+ if (fsp_get_pathref_fd(conn->cwd_fsp) != AT_FDCWD) {
+ /*
+ * This should never happen and
+ * we might change this to
+ * SMB_ASSERT() in future.
+ */
+ DBG_ERR("Called with '.' as first operation!\n");
+ log_stack_trace();
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+ }
+
+ if (smb_fname->base_name[0] == '/' &&
+ strcsequal(LastDir,smb_fname->base_name))
+ {
+ /*
+ * conn->cwd_fsp->fsp_name and the kernel
+ * are already correct, but conn->cwd_fsp->fh->fd
+ * might still be -1 as initialized in conn_new().
+ *
+ * This can happen when a client made a 2nd
+ * tree connect to a share with the same underlying
+ * path (may or may not the same share).
+ */
+ fsp_set_fd(conn->cwd_fsp, AT_FDCWD);
+ return 0;
+ }
+
+ DEBUG(4,("vfs_ChDir to %s\n", smb_fname->base_name));
+
+ ret = SMB_VFS_CHDIR(conn, smb_fname);
+ if (ret != 0) {
+ return -1;
+ }
+
+ /*
+ * Always replace conn->cwd_fsp. We
+ * don't know if it's been modified by
+ * VFS modules in the stack.
+ */
+ fsp_set_fd(conn->cwd_fsp, AT_FDCWD);
+
+ /* conn cache. */
+ cwd = vfs_GetWd(conn, conn);
+ if (cwd == NULL) {
+ /*
+ * vfs_GetWd() failed.
+ * We must be able to read cwd.
+ * Return to original directory
+ * and return -1.
+ */
+ int saved_errno = errno;
+
+ if (conn->cwd_fsp->fsp_name == NULL) {
+ /*
+ * Failed on the very first chdir()+getwd()
+ * for this connection. We can't
+ * continue.
+ */
+ smb_panic("conn->cwd getwd failed\n");
+ /* NOTREACHED */
+ return -1;
+ }
+
+ /* Return to the previous $cwd. */
+ ret = SMB_VFS_CHDIR(conn, conn->cwd_fsp->fsp_name);
+ if (ret != 0) {
+ smb_panic("conn->cwd getwd failed\n");
+ /* NOTREACHED */
+ return -1;
+ }
+ errno = saved_errno;
+ /* And fail the chdir(). */
+ return -1;
+ }
+
+ /* vfs_GetWd() succeeded. */
+ /* Replace global cache. */
+ SAFE_FREE(LastDir);
+ LastDir = SMB_STRDUP(smb_fname->base_name);
+
+ /*
+ * (Indirect) Callers of vfs_ChDir() may still hold references to the
+ * old conn->cwd_fsp->fsp_name. Move it to talloc_tos(), that way
+ * callers can use it for the lifetime of the SMB request.
+ */
+ talloc_move(talloc_tos(), &conn->cwd_fsp->fsp_name);
+
+ conn->cwd_fsp->fsp_name = talloc_move(conn->cwd_fsp, &cwd);
+
+ DBG_INFO("vfs_ChDir got %s\n", fsp_str_dbg(conn->cwd_fsp));
+
+ return ret;
+}
+
+/*******************************************************************
+ Return the absolute current directory path - given a UNIX pathname.
+ Note that this path is returned in DOS format, not UNIX
+ format. Note this can be called with conn == NULL.
+********************************************************************/
+
+struct smb_filename *vfs_GetWd(TALLOC_CTX *ctx, connection_struct *conn)
+{
+ struct smb_filename *current_dir_fname = NULL;
+ struct file_id key;
+ struct smb_filename *smb_fname_dot = NULL;
+ struct smb_filename *smb_fname_full = NULL;
+ struct smb_filename *result = NULL;
+
+ if (!lp_getwd_cache()) {
+ goto nocache;
+ }
+
+ smb_fname_dot = synthetic_smb_fname(ctx,
+ ".",
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname_dot == NULL) {
+ errno = ENOMEM;
+ goto out;
+ }
+
+ if (SMB_VFS_STAT(conn, smb_fname_dot) == -1) {
+ /*
+ * Known to fail for root: the directory may be NFS-mounted
+ * and exported with root_squash (so has no root access).
+ */
+ DEBUG(1,("vfs_GetWd: couldn't stat \".\" error %s "
+ "(NFS problem ?)\n", strerror(errno) ));
+ goto nocache;
+ }
+
+ key = vfs_file_id_from_sbuf(conn, &smb_fname_dot->st);
+
+ smb_fname_full = (struct smb_filename *)memcache_lookup_talloc(
+ smbd_memcache(),
+ GETWD_CACHE,
+ data_blob_const(&key, sizeof(key)));
+
+ if (smb_fname_full == NULL) {
+ goto nocache;
+ }
+
+ if ((SMB_VFS_STAT(conn, smb_fname_full) == 0) &&
+ (smb_fname_dot->st.st_ex_dev == smb_fname_full->st.st_ex_dev) &&
+ (smb_fname_dot->st.st_ex_ino == smb_fname_full->st.st_ex_ino) &&
+ (S_ISDIR(smb_fname_dot->st.st_ex_mode))) {
+ /*
+ * Ok, we're done
+ * Note: smb_fname_full is owned by smbd_memcache()
+ * so we must make a copy to return.
+ */
+ result = cp_smb_filename(ctx, smb_fname_full);
+ if (result == NULL) {
+ errno = ENOMEM;
+ }
+ goto out;
+ }
+
+ nocache:
+
+ /*
+ * We don't have the information to hand so rely on traditional
+ * methods. The very slow getcwd, which spawns a process on some
+ * systems, or the not quite so bad getwd.
+ */
+
+ current_dir_fname = SMB_VFS_GETWD(conn, ctx);
+ if (current_dir_fname == NULL) {
+ DEBUG(0, ("vfs_GetWd: SMB_VFS_GETWD call failed: %s\n",
+ strerror(errno)));
+ goto out;
+ }
+
+ if (lp_getwd_cache() && VALID_STAT(smb_fname_dot->st)) {
+ key = vfs_file_id_from_sbuf(conn, &smb_fname_dot->st);
+
+ /*
+ * smbd_memcache() will own current_dir_fname after the
+ * memcache_add_talloc call, so we must make
+ * a copy on ctx to return.
+ */
+ result = cp_smb_filename(ctx, current_dir_fname);
+ if (result == NULL) {
+ errno = ENOMEM;
+ }
+
+ /*
+ * Ensure the memory going into the cache
+ * doesn't have a destructor so it can be
+ * cleanly freed.
+ */
+ talloc_set_destructor(current_dir_fname, NULL);
+
+ memcache_add_talloc(smbd_memcache(),
+ GETWD_CACHE,
+ data_blob_const(&key, sizeof(key)),
+ &current_dir_fname);
+ /* current_dir_fname is now == NULL here. */
+ } else {
+ /* current_dir_fname is already allocated on ctx. */
+ result = current_dir_fname;
+ }
+
+ out:
+ TALLOC_FREE(smb_fname_dot);
+ /*
+ * Don't free current_dir_fname here. It's either been moved
+ * to the memcache or is being returned in result.
+ */
+ return result;
+}
+
+/*
+ * Ensure LSTAT is called for POSIX paths.
+ */
+int vfs_stat(struct connection_struct *conn,
+ struct smb_filename *smb_fname)
+{
+ if (smb_fname->flags & SMB_FILENAME_POSIX_PATH) {
+ return SMB_VFS_LSTAT(conn, smb_fname);
+ }
+ return SMB_VFS_STAT(conn, smb_fname);
+}
+
+/**
+ * XXX: This is temporary and there should be no callers of this once
+ * smb_filename is plumbed through all path based operations.
+ *
+ * Called when we know stream name parsing has already been done.
+ */
+int vfs_stat_smb_basename(struct connection_struct *conn,
+ const struct smb_filename *smb_fname_in,
+ SMB_STRUCT_STAT *psbuf)
+{
+ struct smb_filename smb_fname = {
+ .base_name = discard_const_p(char, smb_fname_in->base_name),
+ .flags = smb_fname_in->flags,
+ .twrp = smb_fname_in->twrp,
+ };
+ int ret;
+
+ ret = vfs_stat(conn, &smb_fname);
+ if (ret != -1) {
+ *psbuf = smb_fname.st;
+ }
+ return ret;
+}
+
+/**
+ * Ensure LSTAT is called for POSIX paths.
+ */
+
+NTSTATUS vfs_stat_fsp(files_struct *fsp)
+{
+ int ret;
+ struct stat_ex saved_stat = fsp->fsp_name->st;
+
+ if (fsp->fake_file_handle != NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ if (fsp->posix_flags & FSP_POSIX_FLAGS_OPEN) {
+ ret = SMB_VFS_LSTAT(fsp->conn, fsp->fsp_name);
+ } else {
+ ret = SMB_VFS_STAT(fsp->conn, fsp->fsp_name);
+ }
+ } else {
+ ret = SMB_VFS_FSTAT(fsp, &fsp->fsp_name->st);
+ }
+ if (ret == -1) {
+ return map_nt_error_from_unix(errno);
+ }
+ update_stat_ex_from_saved_stat(&fsp->fsp_name->st, &saved_stat);
+ fsp->fsp_flags.is_directory = S_ISDIR(fsp->fsp_name->st.st_ex_mode);
+ return NT_STATUS_OK;
+}
+
+void init_smb_file_time(struct smb_file_time *ft)
+{
+ *ft = (struct smb_file_time) {
+ .atime = make_omit_timespec(),
+ .ctime = make_omit_timespec(),
+ .mtime = make_omit_timespec(),
+ .create_time = make_omit_timespec()
+ };
+}
+
+/**
+ * Initialize num_streams and streams, then call VFS op streaminfo
+ */
+
+NTSTATUS vfs_fstreaminfo(struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams)
+{
+ *num_streams = 0;
+ *streams = NULL;
+
+ if (fsp == NULL) {
+ /*
+ * Callers may pass fsp == NULL when passing smb_fname->fsp of a
+ * symlink. This is ok, handle it here, by just return no
+ * streams on a symlink.
+ */
+ return NT_STATUS_OK;
+ }
+
+ if (fsp_get_pathref_fd(fsp) == -1) {
+ /*
+ * No streams on non-real files/directories.
+ */
+ return NT_STATUS_OK;
+ }
+
+ return SMB_VFS_FSTREAMINFO(fsp,
+ mem_ctx,
+ num_streams,
+ streams);
+}
+
+int vfs_fake_fd(void)
+{
+ int pipe_fds[2];
+ int ret;
+
+ /*
+ * Return a valid fd, but ensure any attempt to use
+ * it returns an error (EPIPE).
+ */
+ ret = pipe(pipe_fds);
+ if (ret != 0) {
+ return -1;
+ }
+
+ close(pipe_fds[1]);
+ return pipe_fds[0];
+}
+
+/*
+ * This is just a helper to make
+ * users of vfs_fake_fd() more symmetric
+ */
+int vfs_fake_fd_close(int fd)
+{
+ return close(fd);
+}
+
+/*
+ generate a file_id from a stat structure
+ */
+struct file_id vfs_file_id_from_sbuf(connection_struct *conn, const SMB_STRUCT_STAT *sbuf)
+{
+ return SMB_VFS_FILE_ID_CREATE(conn, sbuf);
+}
+
+NTSTATUS vfs_at_fspcwd(TALLOC_CTX *mem_ctx,
+ struct connection_struct *conn,
+ struct files_struct **_fsp)
+{
+ struct files_struct *fsp = NULL;
+
+ fsp = talloc_zero(mem_ctx, struct files_struct);
+ if (fsp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fsp_name = synthetic_smb_fname(fsp, ".", NULL, NULL, 0, 0);
+ if (fsp->fsp_name == NULL) {
+ TALLOC_FREE(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fh = fd_handle_create(fsp);
+ if (fsp->fh == NULL) {
+ TALLOC_FREE(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp_set_fd(fsp, AT_FDCWD);
+ fsp->fnum = FNUM_FIELD_INVALID;
+ fsp->conn = conn;
+
+ *_fsp = fsp;
+ return NT_STATUS_OK;
+}
+
+static struct smb_vfs_deny_state *smb_vfs_deny_global;
+
+void smb_vfs_assert_allowed(void)
+{
+ if (unlikely(smb_vfs_deny_global != NULL)) {
+ DBG_ERR("Called with VFS denied by %s\n",
+ smb_vfs_deny_global->location);
+ smb_panic("Called with VFS denied!");
+ }
+}
+
+void _smb_vfs_deny_push(struct smb_vfs_deny_state *state, const char *location)
+{
+ SMB_ASSERT(smb_vfs_deny_global != state);
+
+ *state = (struct smb_vfs_deny_state) {
+ .parent = smb_vfs_deny_global,
+ .location = location,
+ };
+
+ smb_vfs_deny_global = state;
+}
+
+void _smb_vfs_deny_pop(struct smb_vfs_deny_state *state, const char *location)
+{
+ SMB_ASSERT(smb_vfs_deny_global == state);
+
+ smb_vfs_deny_global = state->parent;
+
+ *state = (struct smb_vfs_deny_state) { .parent = NULL, };
+}
+
+#define VFS_FIND(__fn__) do { \
+ if (unlikely(smb_vfs_deny_global != NULL)) { \
+ DBG_ERR("Called with VFS denied by %s\n", \
+ smb_vfs_deny_global->location); \
+ smb_panic("Called with VFS denied!"); \
+ } \
+ while (handle->fns->__fn__##_fn==NULL) { \
+ handle = handle->next; \
+ } \
+} while(0)
+
+int smb_vfs_call_connect(struct vfs_handle_struct *handle,
+ const char *service, const char *user)
+{
+ VFS_FIND(connect);
+ return handle->fns->connect_fn(handle, service, user);
+}
+
+void smb_vfs_call_disconnect(struct vfs_handle_struct *handle)
+{
+ VFS_FIND(disconnect);
+ handle->fns->disconnect_fn(handle);
+}
+
+uint64_t smb_vfs_call_disk_free(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uint64_t *bsize,
+ uint64_t *dfree,
+ uint64_t *dsize)
+{
+ VFS_FIND(disk_free);
+ return handle->fns->disk_free_fn(handle, smb_fname,
+ bsize, dfree, dsize);
+}
+
+int smb_vfs_call_get_quota(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ enum SMB_QUOTA_TYPE qtype,
+ unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ VFS_FIND(get_quota);
+ return handle->fns->get_quota_fn(handle, smb_fname, qtype, id, qt);
+}
+
+int smb_vfs_call_set_quota(struct vfs_handle_struct *handle,
+ enum SMB_QUOTA_TYPE qtype, unid_t id,
+ SMB_DISK_QUOTA *qt)
+{
+ VFS_FIND(set_quota);
+ return handle->fns->set_quota_fn(handle, qtype, id, qt);
+}
+
+int smb_vfs_call_get_shadow_copy_data(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct shadow_copy_data *shadow_copy_data,
+ bool labels)
+{
+ VFS_FIND(get_shadow_copy_data);
+ return handle->fns->get_shadow_copy_data_fn(handle, fsp,
+ shadow_copy_data,
+ labels);
+}
+int smb_vfs_call_statvfs(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ struct vfs_statvfs_struct *statbuf)
+{
+ VFS_FIND(statvfs);
+ return handle->fns->statvfs_fn(handle, smb_fname, statbuf);
+}
+
+uint32_t smb_vfs_call_fs_capabilities(struct vfs_handle_struct *handle,
+ enum timestamp_set_resolution *p_ts_res)
+{
+ VFS_FIND(fs_capabilities);
+ return handle->fns->fs_capabilities_fn(handle, p_ts_res);
+}
+
+NTSTATUS smb_vfs_call_get_dfs_referrals(struct vfs_handle_struct *handle,
+ struct dfs_GetDFSReferral *r)
+{
+ VFS_FIND(get_dfs_referrals);
+ return handle->fns->get_dfs_referrals_fn(handle, r);
+}
+
+NTSTATUS smb_vfs_call_create_dfs_pathat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ const struct referral *reflist,
+ size_t referral_count)
+{
+ VFS_FIND(create_dfs_pathat);
+ return handle->fns->create_dfs_pathat_fn(handle,
+ dirfsp,
+ smb_fname,
+ reflist,
+ referral_count);
+}
+
+NTSTATUS smb_vfs_call_read_dfs_pathat(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ struct referral **ppreflist,
+ size_t *preferral_count)
+{
+ VFS_FIND(read_dfs_pathat);
+ return handle->fns->read_dfs_pathat_fn(handle,
+ mem_ctx,
+ dirfsp,
+ smb_fname,
+ ppreflist,
+ preferral_count);
+}
+
+DIR *smb_vfs_call_fdopendir(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const char *mask,
+ uint32_t attributes)
+{
+ VFS_FIND(fdopendir);
+ return handle->fns->fdopendir_fn(handle, fsp, mask, attributes);
+}
+
+struct dirent *smb_vfs_call_readdir(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ DIR *dirp)
+{
+ VFS_FIND(readdir);
+ return handle->fns->readdir_fn(handle, dirfsp, dirp);
+}
+
+void smb_vfs_call_rewind_dir(struct vfs_handle_struct *handle,
+ DIR *dirp)
+{
+ VFS_FIND(rewind_dir);
+ handle->fns->rewind_dir_fn(handle, dirp);
+}
+
+int smb_vfs_call_mkdirat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode)
+{
+ VFS_FIND(mkdirat);
+ return handle->fns->mkdirat_fn(handle,
+ dirfsp,
+ smb_fname,
+ mode);
+}
+
+int smb_vfs_call_closedir(struct vfs_handle_struct *handle,
+ DIR *dir)
+{
+ VFS_FIND(closedir);
+ return handle->fns->closedir_fn(handle, dir);
+}
+
+int smb_vfs_call_openat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ struct files_struct *fsp,
+ const struct vfs_open_how *how)
+{
+ VFS_FIND(openat);
+ return handle->fns->openat_fn(handle,
+ dirfsp,
+ smb_fname,
+ fsp,
+ how);
+}
+
+NTSTATUS smb_vfs_call_create_file(struct vfs_handle_struct *handle,
+ struct smb_request *req,
+ struct files_struct *dirfsp,
+ struct smb_filename *smb_fname,
+ uint32_t access_mask,
+ uint32_t share_access,
+ uint32_t create_disposition,
+ uint32_t create_options,
+ uint32_t file_attributes,
+ uint32_t oplock_request,
+ const struct smb2_lease *lease,
+ uint64_t allocation_size,
+ uint32_t private_flags,
+ struct security_descriptor *sd,
+ struct ea_list *ea_list,
+ files_struct **result,
+ int *pinfo,
+ const struct smb2_create_blobs *in_context_blobs,
+ struct smb2_create_blobs *out_context_blobs)
+{
+ VFS_FIND(create_file);
+ return handle->fns->create_file_fn(
+ handle, req, dirfsp, smb_fname,
+ access_mask, share_access, create_disposition, create_options,
+ file_attributes, oplock_request, lease, allocation_size,
+ private_flags, sd, ea_list,
+ result, pinfo, in_context_blobs, out_context_blobs);
+}
+
+int smb_vfs_call_close(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ VFS_FIND(close);
+ return handle->fns->close_fn(handle, fsp);
+}
+
+ssize_t smb_vfs_call_pread(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, void *data, size_t n,
+ off_t offset)
+{
+ VFS_FIND(pread);
+ return handle->fns->pread_fn(handle, fsp, data, n, offset);
+}
+
+struct smb_vfs_call_pread_state {
+ ssize_t (*recv_fn)(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state);
+ ssize_t retval;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_vfs_call_pread_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_pread_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_vfs_call_pread_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_pread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ VFS_FIND(pread_send);
+ state->recv_fn = handle->fns->pread_recv_fn;
+
+ subreq = handle->fns->pread_send_fn(handle, state, ev, fsp, data, n,
+ offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_vfs_call_pread_done, req);
+ return req;
+}
+
+static void smb_vfs_call_pread_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_pread_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pread_state);
+
+ state->retval = state->recv_fn(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+ssize_t SMB_VFS_PREAD_RECV(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_vfs_call_pread_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pread_state);
+ ssize_t retval;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->retval;
+ tevent_req_received(req);
+ return retval;
+}
+
+ssize_t smb_vfs_call_pwrite(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const void *data,
+ size_t n, off_t offset)
+{
+ VFS_FIND(pwrite);
+ return handle->fns->pwrite_fn(handle, fsp, data, n, offset);
+}
+
+struct smb_vfs_call_pwrite_state {
+ ssize_t (*recv_fn)(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state);
+ ssize_t retval;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_vfs_call_pwrite_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_pwrite_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp,
+ const void *data,
+ size_t n, off_t offset)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_vfs_call_pwrite_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_pwrite_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ VFS_FIND(pwrite_send);
+ state->recv_fn = handle->fns->pwrite_recv_fn;
+
+ subreq = handle->fns->pwrite_send_fn(handle, state, ev, fsp, data, n,
+ offset);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_vfs_call_pwrite_done, req);
+ return req;
+}
+
+static void smb_vfs_call_pwrite_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_pwrite_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pwrite_state);
+
+ state->retval = state->recv_fn(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+ssize_t SMB_VFS_PWRITE_RECV(struct tevent_req *req,
+ struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_vfs_call_pwrite_state *state = tevent_req_data(
+ req, struct smb_vfs_call_pwrite_state);
+ ssize_t retval;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->retval;
+ tevent_req_received(req);
+ return retval;
+}
+
+off_t smb_vfs_call_lseek(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t offset,
+ int whence)
+{
+ VFS_FIND(lseek);
+ return handle->fns->lseek_fn(handle, fsp, offset, whence);
+}
+
+ssize_t smb_vfs_call_sendfile(struct vfs_handle_struct *handle, int tofd,
+ files_struct *fromfsp, const DATA_BLOB *header,
+ off_t offset, size_t count)
+{
+ VFS_FIND(sendfile);
+ return handle->fns->sendfile_fn(handle, tofd, fromfsp, header, offset,
+ count);
+}
+
+ssize_t smb_vfs_call_recvfile(struct vfs_handle_struct *handle, int fromfd,
+ files_struct *tofsp, off_t offset,
+ size_t count)
+{
+ VFS_FIND(recvfile);
+ return handle->fns->recvfile_fn(handle, fromfd, tofsp, offset, count);
+}
+
+int smb_vfs_call_renameat(struct vfs_handle_struct *handle,
+ files_struct *srcfsp,
+ const struct smb_filename *smb_fname_src,
+ files_struct *dstfsp,
+ const struct smb_filename *smb_fname_dst)
+{
+ VFS_FIND(renameat);
+ return handle->fns->renameat_fn(handle,
+ srcfsp,
+ smb_fname_src,
+ dstfsp,
+ smb_fname_dst);
+}
+
+struct smb_vfs_call_fsync_state {
+ int (*recv_fn)(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state);
+ int retval;
+ struct vfs_aio_state vfs_aio_state;
+};
+
+static void smb_vfs_call_fsync_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_fsync_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct files_struct *fsp)
+{
+ struct tevent_req *req, *subreq;
+ struct smb_vfs_call_fsync_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_fsync_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ VFS_FIND(fsync_send);
+ state->recv_fn = handle->fns->fsync_recv_fn;
+
+ subreq = handle->fns->fsync_send_fn(handle, state, ev, fsp);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb_vfs_call_fsync_done, req);
+ return req;
+}
+
+static void smb_vfs_call_fsync_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_fsync_state *state = tevent_req_data(
+ req, struct smb_vfs_call_fsync_state);
+
+ state->retval = state->recv_fn(subreq, &state->vfs_aio_state);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->vfs_aio_state.error);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+int SMB_VFS_FSYNC_RECV(struct tevent_req *req, struct vfs_aio_state *vfs_aio_state)
+{
+ struct smb_vfs_call_fsync_state *state = tevent_req_data(
+ req, struct smb_vfs_call_fsync_state);
+ ssize_t retval;
+
+ if (tevent_req_is_unix_error(req, &vfs_aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+ *vfs_aio_state = state->vfs_aio_state;
+ retval = state->retval;
+ tevent_req_received(req);
+ return retval;
+}
+
+/*
+ * Synchronous version of fsync, built from backend
+ * async VFS primitives. Uses a temporary sub-event
+ * context (NOT NESTED).
+ */
+
+int smb_vfs_fsync_sync(files_struct *fsp)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_req *req = NULL;
+ struct vfs_aio_state aio_state = { 0 };
+ int ret = -1;
+ bool ok;
+ struct tevent_context *ev = samba_tevent_context_init(frame);
+
+ if (ev == NULL) {
+ goto out;
+ }
+
+ req = SMB_VFS_FSYNC_SEND(talloc_tos(), ev, fsp);
+ if (req == NULL) {
+ goto out;
+ }
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ goto out;
+ }
+
+ ret = SMB_VFS_FSYNC_RECV(req, &aio_state);
+
+ out:
+
+ TALLOC_FREE(frame);
+ if (aio_state.error != 0) {
+ errno = aio_state.error;
+ }
+ return ret;
+}
+
+int smb_vfs_call_stat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_fname)
+{
+ VFS_FIND(stat);
+ return handle->fns->stat_fn(handle, smb_fname);
+}
+
+int smb_vfs_call_fstat(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(fstat);
+ return handle->fns->fstat_fn(handle, fsp, sbuf);
+}
+
+int smb_vfs_call_lstat(struct vfs_handle_struct *handle,
+ struct smb_filename *smb_filename)
+{
+ VFS_FIND(lstat);
+ return handle->fns->lstat_fn(handle, smb_filename);
+}
+
+int smb_vfs_call_fstatat(
+ struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ SMB_STRUCT_STAT *sbuf,
+ int flags)
+{
+ VFS_FIND(fstatat);
+ return handle->fns->fstatat_fn(handle, dirfsp, smb_fname, sbuf, flags);
+}
+
+uint64_t smb_vfs_call_get_alloc_size(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(get_alloc_size);
+ return handle->fns->get_alloc_size_fn(handle, fsp, sbuf);
+}
+
+int smb_vfs_call_unlinkat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ int flags)
+{
+ VFS_FIND(unlinkat);
+ return handle->fns->unlinkat_fn(handle,
+ dirfsp,
+ smb_fname,
+ flags);
+}
+
+int smb_vfs_call_fchmod(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, mode_t mode)
+{
+ VFS_FIND(fchmod);
+ return handle->fns->fchmod_fn(handle, fsp, mode);
+}
+
+int smb_vfs_call_fchown(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, uid_t uid, gid_t gid)
+{
+ VFS_FIND(fchown);
+ return handle->fns->fchown_fn(handle, fsp, uid, gid);
+}
+
+int smb_vfs_call_lchown(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname,
+ uid_t uid,
+ gid_t gid)
+{
+ VFS_FIND(lchown);
+ return handle->fns->lchown_fn(handle, smb_fname, uid, gid);
+}
+
+int smb_vfs_call_chdir(struct vfs_handle_struct *handle,
+ const struct smb_filename *smb_fname)
+{
+ VFS_FIND(chdir);
+ return handle->fns->chdir_fn(handle, smb_fname);
+}
+
+struct smb_filename *smb_vfs_call_getwd(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx)
+{
+ VFS_FIND(getwd);
+ return handle->fns->getwd_fn(handle, ctx);
+}
+
+int smb_vfs_call_fntimes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct smb_file_time *ft)
+{
+ VFS_FIND(fntimes);
+ return handle->fns->fntimes_fn(handle, fsp, ft);
+}
+
+int smb_vfs_call_ftruncate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t offset)
+{
+ VFS_FIND(ftruncate);
+ return handle->fns->ftruncate_fn(handle, fsp, offset);
+}
+
+int smb_vfs_call_fallocate(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t mode,
+ off_t offset,
+ off_t len)
+{
+ VFS_FIND(fallocate);
+ return handle->fns->fallocate_fn(handle, fsp, mode, offset, len);
+}
+
+int smb_vfs_call_filesystem_sharemode(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t share_mode,
+ uint32_t access_mask)
+{
+ VFS_FIND(filesystem_sharemode);
+ return handle->fns->filesystem_sharemode_fn(handle,
+ fsp,
+ share_mode,
+ access_mask);
+}
+
+int smb_vfs_call_fcntl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int cmd, ...)
+{
+ int result;
+ va_list cmd_arg;
+
+ VFS_FIND(fcntl);
+
+ va_start(cmd_arg, cmd);
+ result = handle->fns->fcntl_fn(handle, fsp, cmd, cmd_arg);
+ va_end(cmd_arg);
+
+ return result;
+}
+
+int smb_vfs_call_linux_setlease(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int leasetype)
+{
+ VFS_FIND(linux_setlease);
+ return handle->fns->linux_setlease_fn(handle, fsp, leasetype);
+}
+
+int smb_vfs_call_symlinkat(struct vfs_handle_struct *handle,
+ const struct smb_filename *link_target,
+ struct files_struct *dirfsp,
+ const struct smb_filename *new_smb_fname)
+{
+ VFS_FIND(symlinkat);
+ return handle->fns->symlinkat_fn(handle,
+ link_target,
+ dirfsp,
+ new_smb_fname);
+}
+
+int smb_vfs_call_readlinkat(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ char *buf,
+ size_t bufsiz)
+{
+ VFS_FIND(readlinkat);
+ return handle->fns->readlinkat_fn(handle,
+ dirfsp,
+ smb_fname,
+ buf,
+ bufsiz);
+}
+
+int smb_vfs_call_linkat(struct vfs_handle_struct *handle,
+ struct files_struct *srcfsp,
+ const struct smb_filename *old_smb_fname,
+ struct files_struct *dstfsp,
+ const struct smb_filename *new_smb_fname,
+ int flags)
+{
+ VFS_FIND(linkat);
+ return handle->fns->linkat_fn(handle,
+ srcfsp,
+ old_smb_fname,
+ dstfsp,
+ new_smb_fname,
+ flags);
+}
+
+int smb_vfs_call_mknodat(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname,
+ mode_t mode,
+ SMB_DEV_T dev)
+{
+ VFS_FIND(mknodat);
+ return handle->fns->mknodat_fn(handle,
+ dirfsp,
+ smb_fname,
+ mode,
+ dev);
+}
+
+struct smb_filename *smb_vfs_call_realpath(struct vfs_handle_struct *handle,
+ TALLOC_CTX *ctx,
+ const struct smb_filename *smb_fname)
+{
+ VFS_FIND(realpath);
+ return handle->fns->realpath_fn(handle, ctx, smb_fname);
+}
+
+int smb_vfs_call_fchflags(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ unsigned int flags)
+{
+ VFS_FIND(fchflags);
+ return handle->fns->fchflags_fn(handle, fsp, flags);
+}
+
+struct file_id smb_vfs_call_file_id_create(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(file_id_create);
+ return handle->fns->file_id_create_fn(handle, sbuf);
+}
+
+uint64_t smb_vfs_call_fs_file_id(struct vfs_handle_struct *handle,
+ const SMB_STRUCT_STAT *sbuf)
+{
+ VFS_FIND(fs_file_id);
+ return handle->fns->fs_file_id_fn(handle, sbuf);
+}
+
+NTSTATUS smb_vfs_call_fstreaminfo(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ unsigned int *num_streams,
+ struct stream_struct **streams)
+{
+ VFS_FIND(fstreaminfo);
+ return handle->fns->fstreaminfo_fn(handle, fsp, mem_ctx,
+ num_streams, streams);
+}
+
+NTSTATUS smb_vfs_call_get_real_filename_at(struct vfs_handle_struct *handle,
+ struct files_struct *dirfsp,
+ const char *name,
+ TALLOC_CTX *mem_ctx,
+ char **found_name)
+{
+ VFS_FIND(get_real_filename_at);
+ return handle->fns->get_real_filename_at_fn(
+ handle, dirfsp, name, mem_ctx, found_name);
+}
+
+const char *smb_vfs_call_connectpath(struct vfs_handle_struct *handle,
+ const struct files_struct *dirfsp,
+ const struct smb_filename *smb_fname)
+{
+ VFS_FIND(connectpath);
+ return handle->fns->connectpath_fn(handle, dirfsp, smb_fname);
+}
+
+bool smb_vfs_call_strict_lock_check(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ struct lock_struct *plock)
+{
+ VFS_FIND(strict_lock_check);
+ return handle->fns->strict_lock_check_fn(handle, fsp, plock);
+}
+
+NTSTATUS smb_vfs_call_translate_name(struct vfs_handle_struct *handle,
+ const char *name,
+ enum vfs_translate_direction direction,
+ TALLOC_CTX *mem_ctx,
+ char **mapped_name)
+{
+ VFS_FIND(translate_name);
+ return handle->fns->translate_name_fn(handle, name, direction, mem_ctx,
+ mapped_name);
+}
+
+NTSTATUS smb_vfs_call_parent_pathname(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const struct smb_filename *smb_fname_in,
+ struct smb_filename **parent_dir_out,
+ struct smb_filename **atname_out)
+{
+ VFS_FIND(parent_pathname);
+ return handle->fns->parent_pathname_fn(handle,
+ mem_ctx,
+ smb_fname_in,
+ parent_dir_out,
+ atname_out);
+}
+
+NTSTATUS smb_vfs_call_fsctl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *ctx,
+ uint32_t function,
+ uint16_t req_flags,
+ const uint8_t *in_data,
+ uint32_t in_len,
+ uint8_t **out_data,
+ uint32_t max_out_len,
+ uint32_t *out_len)
+{
+ VFS_FIND(fsctl);
+ return handle->fns->fsctl_fn(handle, fsp, ctx, function, req_flags,
+ in_data, in_len, out_data, max_out_len,
+ out_len);
+}
+
+NTSTATUS smb_vfs_call_fget_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t *dosmode)
+{
+ VFS_FIND(fget_dos_attributes);
+ return handle->fns->fget_dos_attributes_fn(handle, fsp, dosmode);
+}
+
+NTSTATUS smb_vfs_call_fset_dos_attributes(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t dosmode)
+{
+ VFS_FIND(fset_dos_attributes);
+ return handle->fns->fset_dos_attributes_fn(handle, fsp, dosmode);
+}
+
+struct tevent_req *smb_vfs_call_offload_read_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t fsctl,
+ uint32_t ttl,
+ off_t offset,
+ size_t to_copy)
+{
+ VFS_FIND(offload_read_send);
+ return handle->fns->offload_read_send_fn(mem_ctx, ev, handle,
+ fsp, fsctl,
+ ttl, offset, to_copy);
+}
+
+NTSTATUS smb_vfs_call_offload_read_recv(struct tevent_req *req,
+ struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *flags,
+ uint64_t *xferlen,
+ DATA_BLOB *token_blob)
+{
+ VFS_FIND(offload_read_recv);
+ return handle->fns->offload_read_recv_fn(req, handle, mem_ctx, flags, xferlen, token_blob);
+}
+
+struct tevent_req *smb_vfs_call_offload_write_send(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t fsctl,
+ DATA_BLOB *token,
+ off_t transfer_offset,
+ struct files_struct *dest_fsp,
+ off_t dest_off,
+ off_t num)
+{
+ VFS_FIND(offload_write_send);
+ return handle->fns->offload_write_send_fn(handle, mem_ctx, ev, fsctl,
+ token, transfer_offset,
+ dest_fsp, dest_off, num);
+}
+
+NTSTATUS smb_vfs_call_offload_write_recv(struct vfs_handle_struct *handle,
+ struct tevent_req *req,
+ off_t *copied)
+{
+ VFS_FIND(offload_write_recv);
+ return handle->fns->offload_write_recv_fn(handle, req, copied);
+}
+
+struct smb_vfs_call_get_dos_attributes_state {
+ files_struct *dir_fsp;
+ NTSTATUS (*recv_fn)(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dosmode);
+ struct vfs_aio_state aio_state;
+ uint32_t dos_attributes;
+};
+
+static void smb_vfs_call_get_dos_attributes_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_get_dos_attributes_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ struct smb_filename *smb_fname)
+{
+ struct tevent_req *req = NULL;
+ struct smb_vfs_call_get_dos_attributes_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_get_dos_attributes_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ VFS_FIND(get_dos_attributes_send);
+
+ *state = (struct smb_vfs_call_get_dos_attributes_state) {
+ .dir_fsp = dir_fsp,
+ .recv_fn = handle->fns->get_dos_attributes_recv_fn,
+ };
+
+ subreq = handle->fns->get_dos_attributes_send_fn(mem_ctx,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_defer_callback(req, ev);
+
+ tevent_req_set_callback(subreq,
+ smb_vfs_call_get_dos_attributes_done,
+ req);
+
+ return req;
+}
+
+static void smb_vfs_call_get_dos_attributes_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb_vfs_call_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_vfs_call_get_dos_attributes_state);
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dir_fsp);
+ SMB_ASSERT(ok);
+
+ status = state->recv_fn(subreq,
+ &state->aio_state,
+ &state->dos_attributes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS smb_vfs_call_get_dos_attributes_recv(
+ struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ uint32_t *dos_attributes)
+{
+ struct smb_vfs_call_get_dos_attributes_state *state =
+ tevent_req_data(req,
+ struct smb_vfs_call_get_dos_attributes_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *aio_state = state->aio_state;
+ *dos_attributes = state->dos_attributes;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS smb_vfs_call_fget_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t *_compression_fmt)
+{
+ VFS_FIND(fget_compression);
+ return handle->fns->fget_compression_fn(handle, mem_ctx, fsp,
+ _compression_fmt);
+}
+
+NTSTATUS smb_vfs_call_set_compression(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct *fsp,
+ uint16_t compression_fmt)
+{
+ VFS_FIND(set_compression);
+ return handle->fns->set_compression_fn(handle, mem_ctx, fsp,
+ compression_fmt);
+}
+
+NTSTATUS smb_vfs_call_snap_check_path(vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *service_path,
+ char **base_volume)
+{
+ VFS_FIND(snap_check_path);
+ return handle->fns->snap_check_path_fn(handle, mem_ctx, service_path,
+ base_volume);
+}
+
+NTSTATUS smb_vfs_call_snap_create(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ const char *base_volume,
+ time_t *tstamp,
+ bool rw,
+ char **base_path,
+ char **snap_path)
+{
+ VFS_FIND(snap_create);
+ return handle->fns->snap_create_fn(handle, mem_ctx, base_volume, tstamp,
+ rw, base_path, snap_path);
+}
+
+NTSTATUS smb_vfs_call_snap_delete(struct vfs_handle_struct *handle,
+ TALLOC_CTX *mem_ctx,
+ char *base_path,
+ char *snap_path)
+{
+ VFS_FIND(snap_delete);
+ return handle->fns->snap_delete_fn(handle, mem_ctx, base_path,
+ snap_path);
+}
+
+NTSTATUS smb_vfs_call_fget_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info,
+ TALLOC_CTX *mem_ctx,
+ struct security_descriptor **ppdesc)
+{
+ VFS_FIND(fget_nt_acl);
+ return handle->fns->fget_nt_acl_fn(handle, fsp, security_info,
+ mem_ctx, ppdesc);
+}
+
+NTSTATUS smb_vfs_call_fset_nt_acl(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ uint32_t security_info_sent,
+ const struct security_descriptor *psd)
+{
+ VFS_FIND(fset_nt_acl);
+ return handle->fns->fset_nt_acl_fn(handle, fsp, security_info_sent,
+ psd);
+}
+
+NTSTATUS smb_vfs_call_audit_file(struct vfs_handle_struct *handle,
+ struct smb_filename *file,
+ struct security_acl *sacl,
+ uint32_t access_requested,
+ uint32_t access_denied)
+{
+ VFS_FIND(audit_file);
+ return handle->fns->audit_file_fn(handle,
+ file,
+ sacl,
+ access_requested,
+ access_denied);
+}
+
+SMB_ACL_T smb_vfs_call_sys_acl_get_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ TALLOC_CTX *mem_ctx)
+{
+ VFS_FIND(sys_acl_get_fd);
+ return handle->fns->sys_acl_get_fd_fn(handle, fsp, type, mem_ctx);
+}
+
+int smb_vfs_call_sys_acl_blob_get_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ char **blob_description,
+ DATA_BLOB *blob)
+{
+ VFS_FIND(sys_acl_blob_get_fd);
+ return handle->fns->sys_acl_blob_get_fd_fn(handle, fsp, mem_ctx, blob_description, blob);
+}
+
+int smb_vfs_call_sys_acl_set_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ SMB_ACL_TYPE_T type,
+ SMB_ACL_T theacl)
+{
+ VFS_FIND(sys_acl_set_fd);
+ return handle->fns->sys_acl_set_fd_fn(handle, fsp, type, theacl);
+}
+
+int smb_vfs_call_sys_acl_delete_def_fd(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ VFS_FIND(sys_acl_delete_def_fd);
+ return handle->fns->sys_acl_delete_def_fd_fn(handle, fsp);
+}
+
+struct smb_vfs_call_getxattrat_state {
+ files_struct *dir_fsp;
+ ssize_t (*recv_fn)(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value);
+ ssize_t retval;
+ uint8_t *xattr_value;
+ struct vfs_aio_state aio_state;
+};
+
+static void smb_vfs_call_getxattrat_done(struct tevent_req *subreq);
+
+struct tevent_req *smb_vfs_call_getxattrat_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct vfs_handle_struct *handle,
+ files_struct *dir_fsp,
+ const struct smb_filename *smb_fname,
+ const char *xattr_name,
+ size_t alloc_hint)
+{
+ struct tevent_req *req = NULL;
+ struct smb_vfs_call_getxattrat_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct smb_vfs_call_getxattrat_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ VFS_FIND(getxattrat_send);
+
+ *state = (struct smb_vfs_call_getxattrat_state) {
+ .dir_fsp = dir_fsp,
+ .recv_fn = handle->fns->getxattrat_recv_fn,
+ };
+
+ subreq = handle->fns->getxattrat_send_fn(mem_ctx,
+ ev,
+ handle,
+ dir_fsp,
+ smb_fname,
+ xattr_name,
+ alloc_hint);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_defer_callback(req, ev);
+
+ tevent_req_set_callback(subreq, smb_vfs_call_getxattrat_done, req);
+ return req;
+}
+
+static void smb_vfs_call_getxattrat_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb_vfs_call_getxattrat_state *state = tevent_req_data(
+ req, struct smb_vfs_call_getxattrat_state);
+ bool ok;
+
+ /*
+ * Make sure we run as the user again
+ */
+ ok = change_to_user_and_service_by_fsp(state->dir_fsp);
+ SMB_ASSERT(ok);
+
+ state->retval = state->recv_fn(subreq,
+ &state->aio_state,
+ state,
+ &state->xattr_value);
+ TALLOC_FREE(subreq);
+ if (state->retval == -1) {
+ tevent_req_error(req, state->aio_state.error);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+ssize_t smb_vfs_call_getxattrat_recv(struct tevent_req *req,
+ struct vfs_aio_state *aio_state,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **xattr_value)
+{
+ struct smb_vfs_call_getxattrat_state *state = tevent_req_data(
+ req, struct smb_vfs_call_getxattrat_state);
+ size_t xattr_size;
+
+ if (tevent_req_is_unix_error(req, &aio_state->error)) {
+ tevent_req_received(req);
+ return -1;
+ }
+
+ *aio_state = state->aio_state;
+ xattr_size = state->retval;
+ if (xattr_value != NULL) {
+ *xattr_value = talloc_move(mem_ctx, &state->xattr_value);
+ }
+
+ tevent_req_received(req);
+ return xattr_size;
+}
+
+ssize_t smb_vfs_call_fgetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ void *value, size_t size)
+{
+ VFS_FIND(fgetxattr);
+ return handle->fns->fgetxattr_fn(handle, fsp, name, value, size);
+}
+
+ssize_t smb_vfs_call_flistxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, char *list,
+ size_t size)
+{
+ VFS_FIND(flistxattr);
+ return handle->fns->flistxattr_fn(handle, fsp, list, size);
+}
+
+int smb_vfs_call_fremovexattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name)
+{
+ VFS_FIND(fremovexattr);
+ return handle->fns->fremovexattr_fn(handle, fsp, name);
+}
+
+int smb_vfs_call_fsetxattr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, const char *name,
+ const void *value, size_t size, int flags)
+{
+ VFS_FIND(fsetxattr);
+ return handle->fns->fsetxattr_fn(handle, fsp, name, value, size, flags);
+}
+
+bool smb_vfs_call_aio_force(struct vfs_handle_struct *handle,
+ struct files_struct *fsp)
+{
+ VFS_FIND(aio_force);
+ return handle->fns->aio_force_fn(handle, fsp);
+}
+
+NTSTATUS smb_vfs_call_durable_cookie(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *cookie)
+{
+ VFS_FIND(durable_cookie);
+ return handle->fns->durable_cookie_fn(handle, fsp, mem_ctx, cookie);
+}
+
+NTSTATUS smb_vfs_call_durable_disconnect(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *new_cookie)
+{
+ VFS_FIND(durable_disconnect);
+ return handle->fns->durable_disconnect_fn(handle, fsp, old_cookie,
+ mem_ctx, new_cookie);
+}
+
+NTSTATUS smb_vfs_call_durable_reconnect(struct vfs_handle_struct *handle,
+ struct smb_request *smb1req,
+ struct smbXsrv_open *op,
+ const DATA_BLOB old_cookie,
+ TALLOC_CTX *mem_ctx,
+ struct files_struct **fsp,
+ DATA_BLOB *new_cookie)
+{
+ VFS_FIND(durable_reconnect);
+ return handle->fns->durable_reconnect_fn(handle, smb1req, op,
+ old_cookie, mem_ctx, fsp,
+ new_cookie);
+}
+
+NTSTATUS smb_vfs_call_freaddir_attr(struct vfs_handle_struct *handle,
+ struct files_struct *fsp,
+ TALLOC_CTX *mem_ctx,
+ struct readdir_attr_data **attr_data)
+{
+ VFS_FIND(freaddir_attr);
+ return handle->fns->freaddir_attr_fn(handle,
+ fsp,
+ mem_ctx,
+ attr_data);
+}
+
+bool smb_vfs_call_lock(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, int op, off_t offset,
+ off_t count, int type)
+{
+ VFS_FIND(lock);
+ return handle->fns->lock_fn(handle, fsp, op, offset, count, type);
+}
+
+bool smb_vfs_call_getlock(struct vfs_handle_struct *handle,
+ struct files_struct *fsp, off_t *poffset,
+ off_t *pcount, int *ptype, pid_t *ppid)
+{
+ VFS_FIND(getlock);
+ return handle->fns->getlock_fn(handle, fsp, poffset, pcount, ptype,
+ ppid);
+}
+
+NTSTATUS smb_vfs_call_brl_lock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ struct lock_struct *plock)
+{
+ VFS_FIND(brl_lock_windows);
+ return handle->fns->brl_lock_windows_fn(handle, br_lck, plock);
+}
+
+bool smb_vfs_call_brl_unlock_windows(struct vfs_handle_struct *handle,
+ struct byte_range_lock *br_lck,
+ const struct lock_struct *plock)
+{
+ VFS_FIND(brl_unlock_windows);
+ return handle->fns->brl_unlock_windows_fn(handle, br_lck, plock);
+}
diff --git a/source3/torture/bench_pthreadpool.c b/source3/torture/bench_pthreadpool.c
new file mode 100644
index 0000000..3d581e9
--- /dev/null
+++ b/source3/torture/bench_pthreadpool.c
@@ -0,0 +1,68 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Little pthreadpool benchmark
+ *
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "../lib/pthreadpool/pthreadpool_pipe.h"
+#include "proto.h"
+
+extern int torture_numops;
+
+static void null_job(void *private_data)
+{
+ return;
+}
+
+bool run_bench_pthreadpool(int dummy)
+{
+ struct pthreadpool_pipe *pool;
+ int i, ret;
+
+ ret = pthreadpool_pipe_init(1, &pool);
+ if (ret != 0) {
+ d_fprintf(stderr, "pthreadpool_pipe_init failed: %s\n",
+ strerror(ret));
+ return false;
+ }
+
+ for (i=0; i<torture_numops; i++) {
+ int jobid;
+
+ ret = pthreadpool_pipe_add_job(pool, 0, null_job, NULL);
+ if (ret != 0) {
+ d_fprintf(stderr, "pthreadpool_pipe_add_job "
+ "failed: %s\n", strerror(ret));
+ break;
+ }
+ ret = pthreadpool_pipe_finished_jobs(pool, &jobid, 1);
+ if (ret < 0) {
+ d_fprintf(stderr, "pthreadpool_pipe_finished_job "
+ "failed: %s\n", strerror(-ret));
+ break;
+ }
+ }
+
+ if (ret != 1) {
+ return false;
+ }
+
+ ret = pthreadpool_pipe_destroy(pool);
+
+ return (ret == 0);
+}
diff --git a/source3/torture/cmd_vfs.c b/source3/torture/cmd_vfs.c
new file mode 100644
index 0000000..95b1e21
--- /dev/null
+++ b/source3/torture/cmd_vfs.c
@@ -0,0 +1,2362 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS module functions
+
+ Copyright (C) Simo Sorce 2002
+ Copyright (C) Eric Lorimer 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "smbd/smbd.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "vfstest.h"
+#include "../lib/util/util_pw.h"
+#include "libcli/security/security.h"
+#include "passdb/machine_sid.h"
+#include "source3/smbd/dir.h"
+
+static const char *null_string = "";
+
+static uint32_t ssf_flags(void)
+{
+ return lp_posix_pathnames() ? SMB_FILENAME_POSIX_PATH : 0;
+}
+
+static NTSTATUS cmd_load_module(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int i;
+
+ if (argc < 2) {
+ printf("Usage: load <modules>\n");
+ return NT_STATUS_OK;
+ }
+
+ for (i=argc-1;i>0;i--) {
+ if (!vfs_init_custom(vfs->conn, argv[i])) {
+ DEBUG(0, ("load: (vfs_init_custom failed for %s)\n", argv[i]));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ printf("load: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_populate(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ char c;
+ size_t size;
+ if (argc != 3) {
+ printf("Usage: populate <char> <size>\n");
+ return NT_STATUS_OK;
+ }
+ c = argv[1][0];
+ size = atoi(argv[2]);
+ vfs->data = talloc_array(mem_ctx, char, size);
+ if (vfs->data == NULL) {
+ printf("populate: error=-1 (not enough memory)");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ memset(vfs->data, c, size);
+ vfs->data_size = size;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_show_data(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ size_t offset;
+ size_t len;
+ if (argc != 1 && argc != 3) {
+ printf("Usage: showdata [<offset> <len>]\n");
+ return NT_STATUS_OK;
+ }
+ if (vfs->data == NULL || vfs->data_size == 0) {
+ printf("show_data: error=-1 (buffer empty)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (argc == 3) {
+ offset = atoi(argv[1]);
+ len = atoi(argv[2]);
+ } else {
+ offset = 0;
+ len = vfs->data_size;
+ }
+ if ((offset + len) > vfs->data_size) {
+ printf("show_data: error=-1 (not enough data in buffer)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ dump_data(0, (uint8_t *)(vfs->data) + offset, len);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_connect(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ SMB_VFS_CONNECT(vfs->conn, lp_servicename(talloc_tos(), lp_sub, SNUM(vfs->conn)), "vfstest");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_disconnect(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ SMB_VFS_DISCONNECT(vfs->conn);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_disk_free(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *smb_fname = NULL;
+ uint64_t diskfree, bsize, dfree, dsize;
+ if (argc != 2) {
+ printf("Usage: disk_free <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ argv[1],
+ NULL,
+ NULL,
+ 0,
+ ssf_flags());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ diskfree = SMB_VFS_DISK_FREE(vfs->conn, smb_fname,
+ &bsize, &dfree, &dsize);
+ printf("disk_free: %lu, bsize = %lu, dfree = %lu, dsize = %lu\n",
+ (unsigned long)diskfree,
+ (unsigned long)bsize,
+ (unsigned long)dfree,
+ (unsigned long)dsize);
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_opendir(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 2) {
+ printf("Usage: opendir <fname>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ argv[1],
+ NULL,
+ NULL,
+ 0,
+ ssf_flags());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = OpenDir(vfs->conn,
+ vfs->conn,
+ smb_fname,
+ NULL,
+ 0,
+ &vfs->currentdir);
+ if (!NT_STATUS_IS_OK(status)) {
+ int err = map_errno_from_nt_status(status);
+ printf("opendir error=%d (%s)\n", err, strerror(err));
+ TALLOC_FREE(smb_fname);
+ errno = err;
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ TALLOC_FREE(smb_fname);
+ printf("opendir: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_readdir(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_Dir *currentdir = vfs->currentdir;
+ files_struct *dirfsp = dir_hnd_fetch_fsp(currentdir);
+ connection_struct *conn = dirfsp->conn;
+ SMB_STRUCT_STAT st;
+ const char *dname = NULL;
+ struct smb_filename *fname = NULL;
+ char *talloced = NULL;
+ int ret;
+
+ if (vfs->currentdir == NULL) {
+ printf("readdir: error=-1 (no open directory)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ dname = ReadDirName(vfs->currentdir, &talloced);
+ if (dname == NULL) {
+ printf("readdir: NULL\n");
+ return NT_STATUS_OK;
+ }
+
+ fname = synthetic_smb_fname(
+ talloc_tos(), dname, NULL, 0, 0, ssf_flags());
+ if (fname == NULL) {
+ printf("readdir: no memory\n");
+ return NT_STATUS_OK;
+ }
+
+ printf("readdir: %s\n", dname);
+
+ ret = SMB_VFS_FSTATAT(conn, dirfsp, fname, &st, AT_SYMLINK_NOFOLLOW);
+
+ if ((ret == 0) && VALID_STAT(st)) {
+ time_t tmp_time;
+ printf(" stat available");
+ if (S_ISREG(st.st_ex_mode)) printf(" Regular File\n");
+ else if (S_ISDIR(st.st_ex_mode)) printf(" Directory\n");
+ else if (S_ISCHR(st.st_ex_mode)) printf(" Character Device\n");
+ else if (S_ISBLK(st.st_ex_mode)) printf(" Block Device\n");
+ else if (S_ISFIFO(st.st_ex_mode)) printf(" Fifo\n");
+ else if (S_ISLNK(st.st_ex_mode)) printf(" Symbolic Link\n");
+ else if (S_ISSOCK(st.st_ex_mode)) printf(" Socket\n");
+ printf(" Size: %10u", (unsigned int)st.st_ex_size);
+#ifdef HAVE_STAT_ST_BLOCKS
+ printf(" Blocks: %9u", (unsigned int)st.st_ex_blocks);
+#endif
+#ifdef HAVE_STAT_ST_BLKSIZE
+ printf(" IO Block: %u\n", (unsigned int)st.st_ex_blksize);
+#endif
+ printf(" Device: 0x%10x", (unsigned int)st.st_ex_dev);
+ printf(" Inode: %10u", (unsigned int)st.st_ex_ino);
+ printf(" Links: %10u\n", (unsigned int)st.st_ex_nlink);
+ printf(" Access: %05o", (int)((st.st_ex_mode) & 007777));
+ printf(" Uid: %5lu Gid: %5lu\n",
+ (unsigned long)st.st_ex_uid,
+ (unsigned long)st.st_ex_gid);
+ tmp_time = convert_timespec_to_time_t(st.st_ex_atime);
+ printf(" Access: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ printf(" Modify: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_ctime);
+ printf(" Change: %s", ctime(&tmp_time));
+ }
+
+ TALLOC_FREE(talloced);
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_mkdir(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *smb_fname = NULL;
+ int ret;
+
+ if (argc != 2) {
+ printf("Usage: mkdir <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ argv[1],
+ NULL,
+ NULL,
+ 0,
+ ssf_flags());
+
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_MKDIRAT(vfs->conn,
+ vfs->conn->cwd_fsp,
+ smb_fname,
+ 00755);
+ if (ret == -1) {
+ printf("mkdir error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("mkdir: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_closedir(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ if (vfs->currentdir == NULL) {
+ printf("closedir: failure (no directory open)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ TALLOC_FREE(vfs->currentdir);
+
+ printf("closedir: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_open(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct vfs_open_how how = { .mode = 0400, };
+ const char *flagstr;
+ files_struct *fsp;
+ struct files_struct *fspcwd = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+ int fd;
+
+ if (argc < 3 || argc > 5) {
+ printf("Usage: open <filename> <flags> <mode>\n");
+ printf(" flags: O = O_RDONLY\n");
+ printf(" R = O_RDWR\n");
+ printf(" W = O_WRONLY\n");
+ printf(" C = O_CREAT\n");
+ printf(" E = O_EXCL\n");
+ printf(" T = O_TRUNC\n");
+ printf(" A = O_APPEND\n");
+ printf(" N = O_NONBLOCK/O_NDELAY\n");
+#ifdef O_SYNC
+ printf(" S = O_SYNC\n");
+#endif
+#ifdef O_NOFOLLOW
+ printf(" F = O_NOFOLLOW\n");
+#endif
+ printf(" mode: see open.2\n");
+ printf(" mode is ignored if C flag not present\n");
+ printf(" mode defaults to 00400\n");
+ return NT_STATUS_OK;
+ }
+ flagstr = argv[2];
+ while (*flagstr) {
+ switch (*flagstr) {
+ case 'O':
+ how.flags |= O_RDONLY;
+ break;
+ case 'R':
+ how.flags |= O_RDWR;
+ break;
+ case 'W':
+ how.flags |= O_WRONLY;
+ break;
+ case 'C':
+ how.flags |= O_CREAT;
+ break;
+ case 'E':
+ how.flags |= O_EXCL;
+ break;
+ case 'T':
+ how.flags |= O_TRUNC;
+ break;
+ case 'A':
+ how.flags |= O_APPEND;
+ break;
+ case 'N':
+ how.flags |= O_NONBLOCK;
+ break;
+#ifdef O_SYNC
+ case 'S':
+ how.flags |= O_SYNC;
+ break;
+#endif
+#ifdef O_NOFOLLOW
+ case 'F':
+ how.flags |= O_NOFOLLOW;
+ break;
+#endif
+ default:
+ printf("open: error=-1 (invalid flag!)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ flagstr++;
+ }
+ if ((how.flags & O_CREAT) && argc == 4) {
+ short _mode = 0;
+
+ if (sscanf(argv[3], "%ho", &_mode) == 0) {
+ printf("open: error=-1 (invalid mode!)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ how.mode = _mode;
+ }
+
+ fsp = talloc_zero(vfs, struct files_struct);
+ if (fsp == NULL) {
+ goto nomem;
+ }
+ fsp->fh = fd_handle_create(fsp);
+ if (fsp->fh == NULL) {
+ goto nomem;
+ }
+ fsp->conn = vfs->conn;
+
+ smb_fname = synthetic_smb_fname_split(NULL,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ goto nomem;
+ }
+
+ fsp->fsp_name = smb_fname;
+
+ status = vfs_at_fspcwd(fsp, vfs->conn, &fspcwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ if (is_named_stream(smb_fname)) {
+ struct smb_filename *base_name = NULL;
+
+ base_name = cp_smb_filename_nostream(NULL, smb_fname);
+ if (base_name == NULL) {
+ goto nomem;
+ }
+
+ status = openat_pathref_fsp(fspcwd, base_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ TALLOC_FREE(fspcwd);
+
+ fsp->base_fsp = base_name->fsp;
+ }
+
+ fd = SMB_VFS_OPENAT(vfs->conn,
+ fspcwd,
+ smb_fname,
+ fsp,
+ &how);
+ if (fd == -1) {
+ printf("open: error=%d (%s)\n", errno, strerror(errno));
+ status = map_nt_error_from_unix(errno);
+ goto fail;
+ }
+ fsp_set_fd(fsp, fd);
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* If we have an fd, this stat should succeed. */
+ DEBUG(0,("Error doing fstat on open file %s "
+ "(%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status) ));
+ } else if (S_ISDIR(smb_fname->st.st_ex_mode)) {
+ errno = EISDIR;
+ status = NT_STATUS_FILE_IS_A_DIRECTORY;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fd_close(fsp);
+ goto fail;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(vfs->conn, &smb_fname->st);
+ fsp->vuid = UID_FIELD_INVALID;
+ fsp->file_pid = 0;
+ fsp->fsp_flags.can_lock = true;
+ fsp->fsp_flags.can_read = true;
+ fsp->fsp_flags.can_write = CAN_WRITE(vfs->conn);
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = false;
+
+ vfs->files[fsp_get_pathref_fd(fsp)] = fsp;
+ printf("open: fd=%d\n", fsp_get_pathref_fd(fsp));
+ return NT_STATUS_OK;
+
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+fail:
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(fsp);
+ return status;
+}
+
+
+static NTSTATUS cmd_pathfunc(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *smb_fname = NULL;
+ int ret = -1;
+
+ if (argc != 2) {
+ printf("Usage: %s <path>\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ argv[1],
+ NULL,
+ NULL,
+ 0,
+ ssf_flags());
+
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (strcmp("rmdir", argv[0]) == 0 ) {
+ ret = SMB_VFS_UNLINKAT(vfs->conn,
+ vfs->conn->cwd_fsp,
+ smb_fname,
+ AT_REMOVEDIR);
+ TALLOC_FREE(smb_fname);
+ } else if (strcmp("unlink", argv[0]) == 0 ) {
+ TALLOC_FREE(smb_fname);
+ /* unlink can be a stream:name */
+ smb_fname = synthetic_smb_fname_split(talloc_tos(),
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = SMB_VFS_UNLINKAT(vfs->conn,
+ vfs->conn->cwd_fsp,
+ smb_fname,
+ 0);
+ TALLOC_FREE(smb_fname);
+ } else if (strcmp("chdir", argv[0]) == 0 ) {
+ ret = SMB_VFS_CHDIR(vfs->conn, smb_fname);
+ TALLOC_FREE(smb_fname);
+ } else {
+ printf("%s: error=%d (invalid function name!)\n", argv[0], errno);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (ret == -1) {
+ printf("%s: error=%d (%s)\n", argv[0], errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("%s: ok\n", argv[0]);
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_close(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd;
+ NTSTATUS status;
+
+ if (argc != 2) {
+ printf("Usage: close <fd>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ if (vfs->files[fd] == NULL) {
+ printf("close: error=-1 (invalid file descriptor)\n");
+ return NT_STATUS_OK;
+ }
+
+ status = fd_close(vfs->files[fd]);
+ if (!NT_STATUS_IS_OK(status))
+ printf("close: error=%s\n", nt_errstr(status));
+ else
+ printf("close: ok\n");
+
+ TALLOC_FREE(vfs->files[fd]);
+ vfs->files[fd] = NULL;
+ return status;
+}
+
+
+static NTSTATUS cmd_read(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd;
+ size_t size;
+ ssize_t rsize;
+
+ if (argc != 3) {
+ printf("Usage: read <fd> <size>\n");
+ return NT_STATUS_OK;
+ }
+
+ /* do some error checking on these */
+ fd = atoi(argv[1]);
+ size = atoi(argv[2]);
+ vfs->data = talloc_array(mem_ctx, char, size);
+ if (vfs->data == NULL) {
+ printf("read: error=-1 (not enough memory)");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ vfs->data_size = size;
+
+ rsize = read_file(vfs->files[fd], vfs->data, 0, size);
+ if (rsize == -1) {
+ printf("read: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("read: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_write(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd, wsize;
+ size_t size;
+
+ if (argc != 3) {
+ printf("Usage: write <fd> <size>\n");
+ return NT_STATUS_OK;
+ }
+
+ /* some error checking should go here */
+ fd = atoi(argv[1]);
+ size = atoi(argv[2]);
+ if (vfs->data == NULL) {
+ printf("write: error=-1 (buffer empty, please populate it before writing)");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (vfs->data_size < size) {
+ printf("write: error=-1 (buffer too small, please put some more data in)");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ wsize = write_file(NULL, vfs->files[fd], vfs->data, 0, size);
+
+ if (wsize == -1) {
+ printf("write: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("write: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_lseek(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd, offset, whence;
+ off_t pos;
+
+ if (argc != 4) {
+ printf("Usage: lseek <fd> <offset> <whence>\n...where whence is 1 => SEEK_SET, 2 => SEEK_CUR, 3 => SEEK_END\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ offset = atoi(argv[2]);
+ whence = atoi(argv[3]);
+ switch (whence) {
+ case 1: whence = SEEK_SET; break;
+ case 2: whence = SEEK_CUR; break;
+ default: whence = SEEK_END;
+ }
+
+ pos = SMB_VFS_LSEEK(vfs->files[fd], offset, whence);
+ if (pos == (off_t)-1) {
+ printf("lseek: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("lseek: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_rename(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int ret;
+ struct smb_filename *smb_fname_src = NULL;
+ struct smb_filename *smb_fname_dst = NULL;
+
+ if (argc != 3) {
+ printf("Usage: rename <old> <new>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname_src = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname_src == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ smb_fname_dst = synthetic_smb_fname_split(mem_ctx,
+ argv[2],
+ lp_posix_pathnames());
+ if (smb_fname_dst == NULL) {
+ TALLOC_FREE(smb_fname_src);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_RENAMEAT(vfs->conn,
+ vfs->conn->cwd_fsp,
+ smb_fname_src,
+ vfs->conn->cwd_fsp,
+ smb_fname_dst);
+
+ TALLOC_FREE(smb_fname_src);
+ TALLOC_FREE(smb_fname_dst);
+ if (ret == -1) {
+ printf("rename: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("rename: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_fsync(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int ret, fd;
+ if (argc != 2) {
+ printf("Usage: fsync <fd>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ ret = smb_vfs_fsync_sync(vfs->files[fd]);
+ if (ret == -1) {
+ printf("fsync: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("fsync: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_stat(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int ret;
+ const char *user;
+ const char *group;
+ struct passwd *pwd = NULL;
+ struct group *grp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ SMB_STRUCT_STAT st;
+ time_t tmp_time;
+
+ if (argc != 2) {
+ printf("Usage: stat <fname>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_STAT(vfs->conn, smb_fname);
+ if (ret == -1) {
+ printf("stat: error=%d (%s)\n", errno, strerror(errno));
+ TALLOC_FREE(smb_fname);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ st = smb_fname->st;
+ TALLOC_FREE(smb_fname);
+
+ pwd = getpwuid(st.st_ex_uid);
+ if (pwd != NULL) user = pwd->pw_name;
+ else user = null_string;
+ grp = getgrgid(st.st_ex_gid);
+ if (grp != NULL) group = grp->gr_name;
+ else group = null_string;
+
+ printf("stat: ok\n");
+ printf(" File: %s", argv[1]);
+ if (S_ISREG(st.st_ex_mode)) printf(" Regular File\n");
+ else if (S_ISDIR(st.st_ex_mode)) printf(" Directory\n");
+ else if (S_ISCHR(st.st_ex_mode)) printf(" Character Device\n");
+ else if (S_ISBLK(st.st_ex_mode)) printf(" Block Device\n");
+ else if (S_ISFIFO(st.st_ex_mode)) printf(" Fifo\n");
+ else if (S_ISLNK(st.st_ex_mode)) printf(" Symbolic Link\n");
+ else if (S_ISSOCK(st.st_ex_mode)) printf(" Socket\n");
+ printf(" Size: %10u", (unsigned int)st.st_ex_size);
+#ifdef HAVE_STAT_ST_BLOCKS
+ printf(" Blocks: %9u", (unsigned int)st.st_ex_blocks);
+#endif
+#ifdef HAVE_STAT_ST_BLKSIZE
+ printf(" IO Block: %u\n", (unsigned int)st.st_ex_blksize);
+#endif
+ printf(" Device: 0x%10x", (unsigned int)st.st_ex_dev);
+ printf(" Inode: %10u", (unsigned int)st.st_ex_ino);
+ printf(" Links: %10u\n", (unsigned int)st.st_ex_nlink);
+ printf(" Access: %05o", (int)((st.st_ex_mode) & 007777));
+ printf(" Uid: %5lu/%.16s Gid: %5lu/%.16s\n", (unsigned long)st.st_ex_uid, user,
+ (unsigned long)st.st_ex_gid, group);
+ tmp_time = convert_timespec_to_time_t(st.st_ex_atime);
+ printf(" Access: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ printf(" Modify: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_ctime);
+ printf(" Change: %s", ctime(&tmp_time));
+
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_fstat(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd;
+ const char *user;
+ const char *group;
+ struct passwd *pwd = NULL;
+ struct group *grp = NULL;
+ SMB_STRUCT_STAT st;
+ time_t tmp_time;
+
+ if (argc != 2) {
+ printf("Usage: fstat <fd>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ if (fd < 0 || fd >= 1024) {
+ printf("fstat: error=%d (file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ if (vfs->files[fd] == NULL) {
+ printf("fstat: error=%d (invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ if (SMB_VFS_FSTAT(vfs->files[fd], &st) == -1) {
+ printf("fstat: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ pwd = getpwuid(st.st_ex_uid);
+ if (pwd != NULL) user = pwd->pw_name;
+ else user = null_string;
+ grp = getgrgid(st.st_ex_gid);
+ if (grp != NULL) group = grp->gr_name;
+ else group = null_string;
+
+ printf("fstat: ok\n");
+ if (S_ISREG(st.st_ex_mode)) printf(" Regular File\n");
+ else if (S_ISDIR(st.st_ex_mode)) printf(" Directory\n");
+ else if (S_ISCHR(st.st_ex_mode)) printf(" Character Device\n");
+ else if (S_ISBLK(st.st_ex_mode)) printf(" Block Device\n");
+ else if (S_ISFIFO(st.st_ex_mode)) printf(" Fifo\n");
+ else if (S_ISLNK(st.st_ex_mode)) printf(" Symbolic Link\n");
+ else if (S_ISSOCK(st.st_ex_mode)) printf(" Socket\n");
+ printf(" Size: %10u", (unsigned int)st.st_ex_size);
+#ifdef HAVE_STAT_ST_BLOCKS
+ printf(" Blocks: %9u", (unsigned int)st.st_ex_blocks);
+#endif
+#ifdef HAVE_STAT_ST_BLKSIZE
+ printf(" IO Block: %u\n", (unsigned int)st.st_ex_blksize);
+#endif
+ printf(" Device: 0x%10x", (unsigned int)st.st_ex_dev);
+ printf(" Inode: %10u", (unsigned int)st.st_ex_ino);
+ printf(" Links: %10u\n", (unsigned int)st.st_ex_nlink);
+ printf(" Access: %05o", (int)((st.st_ex_mode) & 007777));
+ printf(" Uid: %5lu/%.16s Gid: %5lu/%.16s\n", (unsigned long)st.st_ex_uid, user,
+ (unsigned long)st.st_ex_gid, group);
+ tmp_time = convert_timespec_to_time_t(st.st_ex_atime);
+ printf(" Access: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ printf(" Modify: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_ctime);
+ printf(" Change: %s", ctime(&tmp_time));
+
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_lstat(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ const char *user;
+ const char *group;
+ struct passwd *pwd = NULL;
+ struct group *grp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ SMB_STRUCT_STAT st;
+ time_t tmp_time;
+
+ if (argc != 2) {
+ printf("Usage: lstat <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (SMB_VFS_LSTAT(vfs->conn, smb_fname) == -1) {
+ printf("lstat: error=%d (%s)\n", errno, strerror(errno));
+ TALLOC_FREE(smb_fname);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ st = smb_fname->st;
+ TALLOC_FREE(smb_fname);
+
+ pwd = getpwuid(st.st_ex_uid);
+ if (pwd != NULL) user = pwd->pw_name;
+ else user = null_string;
+ grp = getgrgid(st.st_ex_gid);
+ if (grp != NULL) group = grp->gr_name;
+ else group = null_string;
+
+ printf("lstat: ok\n");
+ if (S_ISREG(st.st_ex_mode)) printf(" Regular File\n");
+ else if (S_ISDIR(st.st_ex_mode)) printf(" Directory\n");
+ else if (S_ISCHR(st.st_ex_mode)) printf(" Character Device\n");
+ else if (S_ISBLK(st.st_ex_mode)) printf(" Block Device\n");
+ else if (S_ISFIFO(st.st_ex_mode)) printf(" Fifo\n");
+ else if (S_ISLNK(st.st_ex_mode)) printf(" Symbolic Link\n");
+ else if (S_ISSOCK(st.st_ex_mode)) printf(" Socket\n");
+ printf(" Size: %10u", (unsigned int)st.st_ex_size);
+#ifdef HAVE_STAT_ST_BLOCKS
+ printf(" Blocks: %9u", (unsigned int)st.st_ex_blocks);
+#endif
+#ifdef HAVE_STAT_ST_BLKSIZE
+ printf(" IO Block: %u\n", (unsigned int)st.st_ex_blksize);
+#endif
+ printf(" Device: 0x%10x", (unsigned int)st.st_ex_dev);
+ printf(" Inode: %10u", (unsigned int)st.st_ex_ino);
+ printf(" Links: %10u\n", (unsigned int)st.st_ex_nlink);
+ printf(" Access: %05o", (int)((st.st_ex_mode) & 007777));
+ printf(" Uid: %5lu/%.16s Gid: %5lu/%.16s\n", (unsigned long)st.st_ex_uid, user,
+ (unsigned long)st.st_ex_gid, group);
+ tmp_time = convert_timespec_to_time_t(st.st_ex_atime);
+ printf(" Access: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_mtime);
+ printf(" Modify: %s", ctime(&tmp_time));
+ tmp_time = convert_timespec_to_time_t(st.st_ex_ctime);
+ printf(" Change: %s", ctime(&tmp_time));
+
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_chmod(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *smb_fname = NULL;
+ mode_t mode;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+ if (argc != 3) {
+ printf("Usage: chmod <path> <mode>\n");
+ return NT_STATUS_OK;
+ }
+
+ mode = atoi(argv[2]);
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (SMB_VFS_FCHMOD(pathref_fname->fsp, mode) == -1) {
+ printf("chmod: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("chmod: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_fchmod(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd;
+ mode_t mode;
+ if (argc != 3) {
+ printf("Usage: fchmod <fd> <mode>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ mode = atoi(argv[2]);
+ if (fd < 0 || fd >= 1024) {
+ printf("fchmod: error=%d (file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (vfs->files[fd] == NULL) {
+ printf("fchmod: error=%d (invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ if (SMB_VFS_FCHMOD(vfs->files[fd], mode) == -1) {
+ printf("fchmod: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("fchmod: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_fchown(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ uid_t uid;
+ gid_t gid;
+ int fd;
+ if (argc != 4) {
+ printf("Usage: fchown <fd> <uid> <gid>\n");
+ return NT_STATUS_OK;
+ }
+
+ uid = atoi(argv[2]);
+ gid = atoi(argv[3]);
+ fd = atoi(argv[1]);
+ if (fd < 0 || fd >= 1024) {
+ printf("fchown: failure=%d (file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (vfs->files[fd] == NULL) {
+ printf("fchown: error=%d (invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (SMB_VFS_FCHOWN(vfs->files[fd], uid, gid) == -1) {
+ printf("fchown error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("fchown: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_getwd(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *smb_fname = SMB_VFS_GETWD(vfs->conn, talloc_tos());
+ if (smb_fname == NULL) {
+ printf("getwd: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("getwd: %s\n", smb_fname->base_name);
+ TALLOC_FREE(smb_fname);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_utime(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_file_time ft;
+ struct files_struct *dirfsp = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 4) {
+ printf("Usage: utime <path> <access> <modify>\n");
+ return NT_STATUS_OK;
+ }
+
+ init_smb_file_time(&ft);
+
+ ft.atime = time_t_to_full_timespec(atoi(argv[2]));
+ ft.mtime = time_t_to_full_timespec(atoi(argv[3]));
+
+ status = filename_convert_dirfsp(mem_ctx,
+ vfs->conn,
+ argv[1],
+ 0, /* ucf_flags */
+ 0, /* twrp */
+ &dirfsp,
+ &smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("utime: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ if (SMB_VFS_FNTIMES(smb_fname->fsp, &ft) != 0) {
+ printf("utime: error=%d (%s)\n", errno, strerror(errno));
+ TALLOC_FREE(smb_fname);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ TALLOC_FREE(smb_fname);
+ printf("utime: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_ftruncate(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd;
+ off_t off;
+ if (argc != 3) {
+ printf("Usage: ftruncate <fd> <length>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ off = atoi(argv[2]);
+ if (fd < 0 || fd >= 1024) {
+ printf("ftruncate: error=%d (file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (vfs->files[fd] == NULL) {
+ printf("ftruncate: error=%d (invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ if (SMB_VFS_FTRUNCATE(vfs->files[fd], off) == -1) {
+ printf("ftruncate: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("ftruncate: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_lock(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int fd;
+ int op;
+ long offset;
+ long count;
+ int type;
+ const char *typestr;
+
+ if (argc != 6) {
+ printf("Usage: lock <fd> <op> <offset> <count> <type>\n");
+ printf(" ops: G = F_GETLK\n");
+ printf(" S = F_SETLK\n");
+ printf(" W = F_SETLKW\n");
+ printf(" type: R = F_RDLCK\n");
+ printf(" W = F_WRLCK\n");
+ printf(" U = F_UNLCK\n");
+ return NT_STATUS_OK;
+ }
+
+ if (sscanf(argv[1], "%d", &fd) == 0) {
+ printf("lock: error=-1 (error parsing fd)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ op = 0;
+ switch (*argv[2]) {
+ case 'G':
+ op = F_GETLK;
+ break;
+ case 'S':
+ op = F_SETLK;
+ break;
+ case 'W':
+ op = F_SETLKW;
+ break;
+ default:
+ printf("lock: error=-1 (invalid op flag!)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (sscanf(argv[3], "%ld", &offset) == 0) {
+ printf("lock: error=-1 (error parsing fd)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (sscanf(argv[4], "%ld", &count) == 0) {
+ printf("lock: error=-1 (error parsing fd)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ type = 0;
+ typestr = argv[5];
+ while(*typestr) {
+ switch (*typestr) {
+ case 'R':
+ type |= F_RDLCK;
+ break;
+ case 'W':
+ type |= F_WRLCK;
+ break;
+ case 'U':
+ type |= F_UNLCK;
+ break;
+ default:
+ printf("lock: error=-1 (invalid type flag!)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ typestr++;
+ }
+
+ printf("lock: debug lock(fd=%d, op=%d, offset=%ld, count=%ld, type=%d))\n", fd, op, offset, count, type);
+
+ if (SMB_VFS_LOCK(vfs->files[fd], op, offset, count, type) == False) {
+ printf("lock: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("lock: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_symlink(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ int ret;
+ char *target = NULL;
+ struct smb_filename target_fname;
+ struct smb_filename *new_smb_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 3) {
+ printf("Usage: symlink <path> <link>\n");
+ return NT_STATUS_OK;
+ }
+
+ new_smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[2],
+ lp_posix_pathnames());
+ if (new_smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ target = talloc_strdup(mem_ctx, argv[1]);
+ if (target == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ target_fname = (struct smb_filename) {
+ .base_name = target,
+ };
+
+ /* Removes @GMT tokens if any */
+ status = canonicalize_snapshot_path(&target_fname, UCF_GMT_PATHNAME, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = SMB_VFS_SYMLINKAT(vfs->conn,
+ &target_fname,
+ vfs->conn->cwd_fsp,
+ new_smb_fname);
+ if (ret == -1) {
+ printf("symlink: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("symlink: ok\n");
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_readlink(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ char buffer[PATH_MAX];
+ struct smb_filename *smb_fname = NULL;
+ int size;
+
+ if (argc != 2) {
+ printf("Usage: readlink <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ size = SMB_VFS_READLINKAT(vfs->conn,
+ vfs->conn->cwd_fsp,
+ smb_fname,
+ buffer,
+ PATH_MAX);
+
+ if (size == -1) {
+ printf("readlink: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ buffer[size] = '\0';
+ printf("readlink: %s\n", buffer);
+ return NT_STATUS_OK;
+}
+
+
+static NTSTATUS cmd_link(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *old_smb_fname = NULL;
+ struct smb_filename *new_smb_fname = NULL;
+ int ret;
+
+ if (argc != 3) {
+ printf("Usage: link <path> <link>\n");
+ return NT_STATUS_OK;
+ }
+
+ old_smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (old_smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ new_smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[2],
+ lp_posix_pathnames());
+ if (new_smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_LINKAT(vfs->conn,
+ vfs->conn->cwd_fsp,
+ old_smb_fname,
+ vfs->conn->cwd_fsp,
+ new_smb_fname,
+ 0);
+ if (ret == -1) {
+ printf("link: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("link: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_mknod(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ short _mode = 0;
+ mode_t mode;
+ unsigned int dev_val;
+ SMB_DEV_T dev;
+ struct smb_filename *smb_fname = NULL;
+ int ret;
+
+ if (argc != 4) {
+ printf("Usage: mknod <path> <mode> <dev>\n");
+ printf(" mode is octal\n");
+ printf(" dev is hex\n");
+ return NT_STATUS_OK;
+ }
+
+ if (sscanf(argv[2], "%ho", &_mode) == 0) {
+ printf("open: error=-1 (invalid mode!)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ mode = _mode;
+
+ if (sscanf(argv[3], "%x", &dev_val) == 0) {
+ printf("open: error=-1 (invalid dev!)\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ dev = (SMB_DEV_T)dev_val;
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = SMB_VFS_MKNODAT(vfs->conn,
+ vfs->conn->cwd_fsp,
+ smb_fname,
+ mode,
+ dev);
+
+ if (ret == -1) {
+ printf("mknod: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("mknod: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_realpath(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct smb_filename *smb_fname = NULL;
+
+ if (argc != 2) {
+ printf("Usage: realpath <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (SMB_VFS_REALPATH(vfs->conn, mem_ctx, smb_fname) == NULL) {
+ printf("realpath: error=%d (%s)\n", errno, strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ printf("realpath: ok\n");
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_getxattr(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ uint8_t *buf;
+ ssize_t ret;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 3) {
+ printf("Usage: getxattr <path> <xattr>\n");
+ return NT_STATUS_OK;
+ }
+
+ buf = NULL;
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ ret = SMB_VFS_FGETXATTR(pathref_fname->fsp,
+ argv[2],
+ buf,
+ talloc_get_size(buf));
+ if (ret == -1) {
+ int err = errno;
+ printf("getxattr returned (%s)\n", strerror(err));
+ return map_nt_error_from_unix(err);
+ }
+ buf = talloc_array(mem_ctx, uint8_t, ret);
+ if (buf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = SMB_VFS_FGETXATTR(pathref_fname->fsp,
+ argv[2],
+ buf,
+ talloc_get_size(buf));
+ if (ret == -1) {
+ int err = errno;
+ printf("getxattr returned (%s)\n", strerror(err));
+ return map_nt_error_from_unix(err);
+ }
+ dump_data_file(buf, talloc_get_size(buf), false, stdout);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_listxattr(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ char *buf, *p;
+ ssize_t ret;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+ if (argc != 2) {
+ printf("Usage: listxattr <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ buf = NULL;
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = SMB_VFS_FLISTXATTR(pathref_fname->fsp,
+ buf, talloc_get_size(buf));
+ if (ret == -1) {
+ int err = errno;
+ printf("listxattr returned (%s)\n", strerror(err));
+ return map_nt_error_from_unix(err);
+ }
+ buf = talloc_array(mem_ctx, char, ret);
+ if (buf == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret = SMB_VFS_FLISTXATTR(pathref_fname->fsp,
+ buf, talloc_get_size(buf));
+ if (ret == -1) {
+ int err = errno;
+ printf("listxattr returned (%s)\n", strerror(err));
+ return map_nt_error_from_unix(err);
+ }
+ if (ret == 0) {
+ return NT_STATUS_OK;
+ }
+ if (buf[ret-1] != '\0') {
+ printf("listxattr returned non 0-terminated strings\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ p = buf;
+ while (p < buf+ret) {
+ printf("%s\n", p);
+ p = strchr(p, 0);
+ p += 1;
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_fsetxattr(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ ssize_t ret;
+ int flags = 0;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+
+ if ((argc < 4) || (argc > 5)) {
+ printf("Usage: setxattr <path> <xattr> <value> [flags]\n");
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 5) {
+ flags = atoi(argv[4]);
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ret = SMB_VFS_FSETXATTR(pathref_fname->fsp, argv[2],
+ argv[3], strlen(argv[3]), flags);
+ if (ret == -1) {
+ int err = errno;
+ printf("fsetxattr returned (%s)\n", strerror(err));
+ return map_nt_error_from_unix(err);
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_removexattr(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ ssize_t ret;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 3) {
+ printf("Usage: removexattr <path> <xattr>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ ret = SMB_VFS_FREMOVEXATTR(pathref_fname->fsp, argv[2]);
+ if (ret == -1) {
+ int err = errno;
+ printf("removexattr returned (%s)\n", strerror(err));
+ return map_nt_error_from_unix(err);
+ }
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_fget_nt_acl(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ int fd;
+ NTSTATUS status;
+ struct security_descriptor *sd;
+
+ if (argc != 2) {
+ printf("Usage: fget_nt_acl <fd>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ if (fd < 0 || fd >= 1024) {
+ printf("fget_nt_acl: error=%d (file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (vfs->files[fd] == NULL) {
+ printf("fget_nt_acl: error=%d (invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(metadata_fsp(vfs->files[fd]),
+ SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL,
+ talloc_tos(), &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("fget_nt_acl returned (%s)\n", nt_errstr(status));
+ return status;
+ }
+ printf("%s\n", sddl_encode(talloc_tos(), sd, get_global_sam_sid()));
+ TALLOC_FREE(sd);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_get_nt_acl(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ NTSTATUS status;
+ struct security_descriptor *sd;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+
+ if (argc != 2) {
+ printf("Usage: get_nt_acl <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ argv[1],
+ NULL,
+ NULL,
+ 0,
+ ssf_flags());
+
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ return status;
+ }
+ status = SMB_VFS_FGET_NT_ACL(pathref_fname->fsp,
+ SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL,
+ talloc_tos(),
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("get_nt_acl returned (%s)\n", nt_errstr(status));
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return status;
+ }
+ printf("%s\n", sddl_encode(talloc_tos(), sd, get_global_sam_sid()));
+ TALLOC_FREE(sd);
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_fset_nt_acl(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ int fd;
+ NTSTATUS status;
+ struct security_descriptor *sd;
+
+ if (argc != 3) {
+ printf("Usage: fset_nt_acl <fd> <sddl>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ if (fd < 0 || fd >= 1024) {
+ printf("fset_nt_acl: error=%d (file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (vfs->files[fd] == NULL) {
+ printf("fset_nt_acl: error=%d (invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ sd = sddl_decode(talloc_tos(), argv[2], get_global_sam_sid());
+ if (!sd) {
+ printf("sddl_decode failed to parse %s as SDDL\n", argv[2]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = SMB_VFS_FSET_NT_ACL(
+ metadata_fsp(vfs->files[fd]),
+ SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL,
+ sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("fset_nt_acl returned (%s)\n", nt_errstr(status));
+ return status;
+ }
+ TALLOC_FREE(sd);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_set_nt_acl(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ struct vfs_open_how how = { .mode = 0400, };
+ files_struct *fsp;
+ struct files_struct *fspcwd = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+ struct security_descriptor *sd = NULL;
+ int fd;
+
+ if (argc != 3) {
+ printf("Usage: set_nt_acl <file> <sddl>\n");
+ return NT_STATUS_OK;
+ }
+
+
+ fsp = talloc_zero(vfs, struct files_struct);
+ if (fsp == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->fh = fd_handle_create(fsp);
+ if (fsp->fh == NULL) {
+ TALLOC_FREE(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+ fsp->conn = vfs->conn;
+
+ smb_fname = synthetic_smb_fname_split(NULL,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ TALLOC_FREE(fsp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ fsp->fsp_name = smb_fname;
+
+ status = vfs_at_fspcwd(fsp, vfs->conn, &fspcwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ how.flags = O_RDWR;
+ fd = SMB_VFS_OPENAT(vfs->conn,
+ fspcwd,
+ smb_fname,
+ fsp,
+ &how);
+ if (fd == -1 && errno == EISDIR) {
+#ifdef O_DIRECTORY
+ how.flags = O_RDONLY|O_DIRECTORY;
+#else
+ /* POSIX allows us to open a directory with O_RDONLY. */
+ how.flags = O_RDONLY;
+#endif
+ fd = SMB_VFS_OPENAT(vfs->conn,
+ fspcwd,
+ smb_fname,
+ fsp,
+ &how);
+ }
+ if (fd == -1) {
+ printf("open: error=%d (%s)\n", errno, strerror(errno));
+ TALLOC_FREE(fsp);
+ TALLOC_FREE(smb_fname);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ fsp_set_fd(fsp, fd);
+
+ status = vfs_stat_fsp(fsp);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* If we have an fd, this stat should succeed. */
+ DEBUG(0,("Error doing fstat on open file %s "
+ "(%s)\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status) ));
+ goto out;
+ }
+
+ fsp->file_id = vfs_file_id_from_sbuf(vfs->conn, &smb_fname->st);
+ fsp->vuid = UID_FIELD_INVALID;
+ fsp->file_pid = 0;
+ fsp->fsp_flags.can_lock = true;
+ fsp->fsp_flags.can_read = true;
+ fsp->fsp_flags.can_write = true;
+ fsp->print_file = NULL;
+ fsp->fsp_flags.modified = false;
+ fsp->sent_oplock_break = NO_BREAK_SENT;
+ fsp->fsp_flags.is_directory = S_ISDIR(smb_fname->st.st_ex_mode);
+
+ sd = sddl_decode(talloc_tos(), argv[2], get_global_sam_sid());
+ if (!sd) {
+ printf("sddl_decode failed to parse %s as SDDL\n", argv[2]);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ status = SMB_VFS_FSET_NT_ACL(
+ metadata_fsp(fsp),
+ SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL,
+ sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("fset_nt_acl returned (%s)\n", nt_errstr(status));
+ goto out;
+ }
+out:
+ TALLOC_FREE(sd);
+
+ status = fd_close(fsp);
+ if (!NT_STATUS_IS_OK(status))
+ printf("close: error= (%s)\n", nt_errstr(status));
+
+ TALLOC_FREE(fsp);
+
+ return status;
+}
+
+
+
+static NTSTATUS cmd_sys_acl_get_fd(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ int fd;
+ SMB_ACL_T acl;
+ char *acl_text;
+
+ if (argc != 2) {
+ printf("Usage: sys_acl_get_fd <fd>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ if (fd < 0 || fd >= 1024) {
+ printf("sys_acl_get_fd: error=%d (file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (vfs->files[fd] == NULL) {
+ printf("sys_acl_get_fd: error=%d (invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ acl = SMB_VFS_SYS_ACL_GET_FD(vfs->files[fd],
+ SMB_ACL_TYPE_ACCESS,
+ talloc_tos());
+ if (!acl) {
+ printf("sys_acl_get_fd failed (%s)\n", strerror(errno));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ acl_text = sys_acl_to_text(acl, NULL);
+ printf("%s", acl_text);
+ TALLOC_FREE(acl);
+ SAFE_FREE(acl_text);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_sys_acl_get_file(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ SMB_ACL_T acl;
+ char *acl_text;
+ int type;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 3) {
+ printf("Usage: sys_acl_get_file <path> <type>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(talloc_tos(),
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ type = atoi(argv[2]);
+
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ return status;
+ }
+
+ acl = SMB_VFS_SYS_ACL_GET_FD(pathref_fname->fsp,
+ type, talloc_tos());
+ if (!acl) {
+ printf("sys_acl_get_fd failed (%s)\n", strerror(errno));
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ acl_text = sys_acl_to_text(acl, NULL);
+ printf("%s", acl_text);
+ TALLOC_FREE(acl);
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ SAFE_FREE(acl_text);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_sys_acl_blob_get_file(struct vfs_state *vfs,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ char *description;
+ DATA_BLOB blob;
+ int ret;
+ size_t i;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 2) {
+ printf("Usage: sys_acl_blob_get_file <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ return status;
+ }
+
+ ret = SMB_VFS_SYS_ACL_BLOB_GET_FD(pathref_fname->fsp,
+ talloc_tos(),
+ &description,
+ &blob);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ printf("sys_acl_blob_get_file failed (%s)\n", strerror(errno));
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return status;
+ }
+ printf("Description: %s\n", description);
+ for (i = 0; i < blob.length; i++) {
+ printf("%.2x ", blob.data[i]);
+ }
+ printf("\n");
+
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_sys_acl_blob_get_fd(struct vfs_state *vfs,
+ TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ int fd;
+ char *description;
+ DATA_BLOB blob;
+ int ret;
+ size_t i;
+
+ if (argc != 2) {
+ printf("Usage: sys_acl_blob_get_fd <fd>\n");
+ return NT_STATUS_OK;
+ }
+
+ fd = atoi(argv[1]);
+ if (fd < 0 || fd >= 1024) {
+ printf("sys_acl_blob_get_fd: error=%d "
+ "(file descriptor out of range)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+ if (vfs->files[fd] == NULL) {
+ printf("sys_acl_blob_get_fd: error=%d "
+ "(invalid file descriptor)\n", EBADF);
+ return NT_STATUS_OK;
+ }
+
+ ret = SMB_VFS_SYS_ACL_BLOB_GET_FD(vfs->files[fd], talloc_tos(),
+ &description, &blob);
+ if (ret != 0) {
+ printf("sys_acl_blob_get_fd failed (%s)\n", strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+ printf("Description: %s\n", description);
+ for (i = 0; i < blob.length; i++) {
+ printf("%.2x ", blob.data[i]);
+ }
+ printf("\n");
+
+ return NT_STATUS_OK;
+}
+
+
+
+static NTSTATUS cmd_sys_acl_delete_def_file(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ int ret;
+ struct smb_filename *smb_fname = NULL;
+ struct smb_filename *pathref_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 2) {
+ printf("Usage: sys_acl_delete_def_file <path>\n");
+ return NT_STATUS_OK;
+ }
+
+ smb_fname = synthetic_smb_fname_split(mem_ctx,
+ argv[1],
+ lp_posix_pathnames());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = synthetic_pathref(mem_ctx,
+ vfs->conn->cwd_fsp,
+ smb_fname->base_name,
+ NULL,
+ NULL,
+ smb_fname->twrp,
+ smb_fname->flags,
+ &pathref_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(smb_fname);
+ return status;
+ }
+ if (!pathref_fname->fsp->fsp_flags.is_directory) {
+ printf("sys_acl_delete_def_file - %s is not a directory\n",
+ smb_fname->base_name);
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ret = SMB_VFS_SYS_ACL_DELETE_DEF_FD(pathref_fname->fsp);
+ if (ret == -1) {
+ int err = errno;
+ printf("sys_acl_delete_def_file failed (%s)\n", strerror(err));
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return map_nt_error_from_unix(err);
+ }
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(pathref_fname);
+ return NT_STATUS_OK;
+}
+
+/* Afaik translate name was first introduced with vfs_catia, to be able
+ to translate unix file/dir-names, containing invalid windows characters,
+ to valid windows names.
+ The used translation direction is always unix --> windows
+*/
+static NTSTATUS cmd_translate_name(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ const char *dname = NULL;
+ char *dname_talloced = NULL;
+ bool found = false;
+ char *translated = NULL;
+ struct smb_filename *smb_fname = NULL;
+ NTSTATUS status;
+
+ if (argc != 2) {
+ DEBUG(0, ("Usage: translate_name unix_filename\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ smb_fname = synthetic_smb_fname(talloc_tos(),
+ ".",
+ NULL,
+ NULL,
+ 0,
+ ssf_flags());
+ if (smb_fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = OpenDir(vfs->conn,
+ vfs->conn,
+ smb_fname,
+ NULL,
+ 0,
+ &vfs->currentdir);
+ if (!NT_STATUS_IS_OK(status)) {
+ int err = map_errno_from_nt_status(status);
+ DEBUG(0, ("cmd_translate_name: opendir error=%d (%s)\n",
+ err, strerror(err)));
+ TALLOC_FREE(smb_fname);
+ errno = err;
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ while (true) {
+ /* ReadDirName() returns Windows "encoding" */
+ dname = ReadDirName(vfs->currentdir, &dname_talloced);
+ if (dname == NULL) {
+ break;
+ }
+
+ /* Convert Windows "encoding" from ReadDirName() to UNIX */
+ status = SMB_VFS_TRANSLATE_NAME(vfs->conn,
+ dname,
+ vfs_translate_to_unix,
+ talloc_tos(),
+ &translated);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("file '%s' cannot be translated\n", argv[1]);
+ goto cleanup;
+ }
+
+ /*
+ * argv[1] uses UNIX "encoding", so compare with translation
+ * result.
+ */
+ if (strcmp(translated, argv[1]) == 0) {
+ found = true;
+ break;
+ }
+ TALLOC_FREE(dname_talloced);
+ TALLOC_FREE(translated);
+ };
+
+ if (!found) {
+ DEBUG(0, ("cmd_translate_name: file '%s' not found.\n",
+ argv[1]));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto cleanup;
+ }
+
+ /* translation success. But that could also mean
+ that translating "aaa" to "aaa" was successful :-(
+ */
+ DBG_ERR("file '%s' --> '%s'\n", argv[1], dname);
+ status = NT_STATUS_OK;
+
+cleanup:
+ TALLOC_FREE(dname_talloced);
+ TALLOC_FREE(translated);
+ TALLOC_FREE(smb_fname);
+ TALLOC_FREE(vfs->currentdir);
+ return status;
+}
+
+/*
+ * This is a quick hack to demonstrate a crash in the full_audit
+ * module when passing fsp->smb_fname into SMB_VFS_CREATE_FILE leading
+ * to an error.
+ *
+ * Feel free to expand with more options as needed
+ */
+static NTSTATUS cmd_create_file(
+ struct vfs_state *vfs,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct smb_filename *fname = NULL;
+ struct files_struct *fsp = NULL;
+ int info, ret;
+ NTSTATUS status;
+
+ if (argc != 2) {
+ DBG_ERR("Usage: create_file filename\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ fname = synthetic_smb_fname(
+ talloc_tos(), argv[1], NULL, NULL, 0, 0);
+ if (fname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret = vfs_stat(vfs->conn, fname);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(errno);
+ DBG_DEBUG("vfs_stat() failed: %s\n", strerror(errno));
+ TALLOC_FREE(fname);
+ return status;
+ }
+
+ status = openat_pathref_fsp(vfs->conn->cwd_fsp, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not open %s: %s\n",
+ fname->base_name,
+ nt_errstr(status));
+ TALLOC_FREE(fname);
+ return status;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ vfs->conn,
+ NULL,
+ NULL,
+
+ /*
+ * Using fname->fsp->fsp_name seems to be legal,
+ * there's code to handle this in
+ * create_file_unixpath(). And it is actually very
+ * worthwhile re-using the fsp_name, we can save quite
+ * a few copies of smb_filename with that.
+ */
+ fname->fsp->fsp_name,
+ SEC_FILE_ALL,
+ FILE_SHARE_NONE,
+ FILE_OPEN,
+ FILE_NON_DIRECTORY_FILE,
+ 0,
+ 0,
+ NULL,
+ 0,
+ 0,
+ NULL,
+ NULL,
+ &fsp,
+ &info,
+ NULL,
+ NULL
+ );
+ DBG_DEBUG("create_file returned %s\n", nt_errstr(status));
+
+ TALLOC_FREE(fname);
+
+ return NT_STATUS_OK;
+}
+
+struct cmd_set vfs_commands[] = {
+
+ { .name = "VFS Commands" },
+
+ { "load", cmd_load_module, "Load a module", "load <module.so>" },
+ { "populate", cmd_populate, "Populate a data buffer", "populate <char> <size>" },
+ { "showdata", cmd_show_data, "Show data currently in data buffer", "show_data [<offset> <len>]"},
+ { "connect", cmd_connect, "VFS connect()", "connect" },
+ { "disconnect", cmd_disconnect, "VFS disconnect()", "disconnect" },
+ { "disk_free", cmd_disk_free, "VFS disk_free()", "disk_free <path>" },
+ { "opendir", cmd_opendir, "VFS opendir()", "opendir <fname>" },
+ { "readdir", cmd_readdir, "VFS readdir()", "readdir" },
+ { "mkdir", cmd_mkdir, "VFS mkdir()", "mkdir <path>" },
+ { "rmdir", cmd_pathfunc, "VFS rmdir()", "rmdir <path>" },
+ { "closedir", cmd_closedir, "VFS closedir()", "closedir" },
+ { "open", cmd_open, "VFS open()", "open <fname> <flags> <mode>" },
+ { "close", cmd_close, "VFS close()", "close <fd>" },
+ { "read", cmd_read, "VFS read()", "read <fd> <size>" },
+ { "write", cmd_write, "VFS write()", "write <fd> <size>" },
+ { "lseek", cmd_lseek, "VFS lseek()", "lseek <fd> <offset> <whence>" },
+ { "rename", cmd_rename, "VFS rename()", "rename <old> <new>" },
+ { "fsync", cmd_fsync, "VFS fsync()", "fsync <fd>" },
+ { "stat", cmd_stat, "VFS stat()", "stat <fname>" },
+ { "fstat", cmd_fstat, "VFS fstat()", "fstat <fd>" },
+ { "lstat", cmd_lstat, "VFS lstat()", "lstat <fname>" },
+ { "unlink", cmd_pathfunc, "VFS unlink()", "unlink <fname>" },
+ { "chmod", cmd_chmod, "VFS chmod()", "chmod <path> <mode>" },
+ { "fchmod", cmd_fchmod, "VFS fchmod()", "fchmod <fd> <mode>" },
+ { "fchown", cmd_fchown, "VFS fchown()", "fchown <fd> <uid> <gid>" },
+ { "chdir", cmd_pathfunc, "VFS chdir()", "chdir <path>" },
+ { "getwd", cmd_getwd, "VFS getwd()", "getwd" },
+ { "utime", cmd_utime, "VFS utime()", "utime <path> <access> <modify>" },
+ { "ftruncate", cmd_ftruncate, "VFS ftruncate()", "ftruncate <fd> <length>" },
+ { "lock", cmd_lock, "VFS lock()", "lock <f> <op> <offset> <count> <type>" },
+ { "symlink", cmd_symlink, "VFS symlink()", "symlink <old> <new>" },
+ { "readlink", cmd_readlink, "VFS readlink()", "readlink <path>" },
+ { "link", cmd_link, "VFS link()", "link <oldpath> <newpath>" },
+ { "mknod", cmd_mknod, "VFS mknod()", "mknod <path> <mode> <dev>" },
+ { "realpath", cmd_realpath, "VFS realpath()", "realpath <path>" },
+ { "getxattr", cmd_getxattr, "VFS getxattr()",
+ "getxattr <path> <name>" },
+ { "listxattr", cmd_listxattr, "VFS listxattr()",
+ "listxattr <path>" },
+ { "fsetxattr", cmd_fsetxattr, "VFS fsetxattr()",
+ "fsetxattr <path> <name> <value> [<flags>]" },
+ { "removexattr", cmd_removexattr, "VFS removexattr()",
+ "removexattr <path> <name>\n" },
+ { "fget_nt_acl", cmd_fget_nt_acl, "VFS fget_nt_acl()",
+ "fget_nt_acl <fd>\n" },
+ { "get_nt_acl", cmd_get_nt_acl, "VFS get_nt_acl()",
+ "get_nt_acl <path>\n" },
+ { "fset_nt_acl", cmd_fset_nt_acl, "VFS fset_nt_acl()",
+ "fset_nt_acl <fd>\n" },
+ { "set_nt_acl", cmd_set_nt_acl, "VFS open() and fset_nt_acl()",
+ "set_nt_acl <file>\n" },
+ { "sys_acl_get_file", cmd_sys_acl_get_file, "VFS sys_acl_get_file()", "sys_acl_get_file <path>" },
+ { "sys_acl_get_fd", cmd_sys_acl_get_fd, "VFS sys_acl_get_fd()", "sys_acl_get_fd <fd>" },
+ { "sys_acl_blob_get_file", cmd_sys_acl_blob_get_file,
+ "VFS sys_acl_blob_get_file()", "sys_acl_blob_get_file <path>" },
+ { "sys_acl_blob_get_fd", cmd_sys_acl_blob_get_fd,
+ "VFS sys_acl_blob_get_fd()", "sys_acl_blob_get_fd <path>" },
+ { "sys_acl_delete_def_file", cmd_sys_acl_delete_def_file, "VFS sys_acl_delete_def_file()", "sys_acl_delete_def_file <path>" },
+
+
+#if defined(WITH_SMB1SERVER)
+ { "test_chain", cmd_test_chain, "test chain code",
+ "test_chain" },
+#endif
+ { "translate_name", cmd_translate_name, "VFS translate_name()", "translate_name unix_filename" },
+ { "create_file",
+ cmd_create_file,
+ "VFS create_file()",
+ "create_file <filename>"
+ },
+ {0}
+};
diff --git a/source3/torture/denytest.c b/source3/torture/denytest.c
new file mode 100644
index 0000000..34497c3
--- /dev/null
+++ b/source3/torture/denytest.c
@@ -0,0 +1,1600 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB torture tester - scanning functions
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+
+extern bool torture_showall;
+
+enum deny_result {A_0=0, A_X=1, A_R=2, A_W=3, A_RW=5};
+
+
+static const char *denystr(int denymode)
+{
+ struct {
+ int v;
+ const char *name;
+ } deny_modes[] = {
+ {DENY_DOS, "DENY_DOS"},
+ {DENY_ALL, "DENY_ALL"},
+ {DENY_WRITE, "DENY_WRITE"},
+ {DENY_READ, "DENY_READ"},
+ {DENY_NONE, "DENY_NONE"},
+ {DENY_FCB, "DENY_FCB"},
+ {-1, NULL}};
+ int i;
+ for (i=0;deny_modes[i].name;i++) {
+ if (deny_modes[i].v == denymode) return deny_modes[i].name;
+ }
+ return "DENY_XXX";
+}
+
+static const char *openstr(int mode)
+{
+ struct {
+ int v;
+ const char *name;
+ } open_modes[] = {
+ {O_RDWR, "O_RDWR"},
+ {O_RDONLY, "O_RDONLY"},
+ {O_WRONLY, "O_WRONLY"},
+ {-1, NULL}};
+ int i;
+ for (i=0;open_modes[i].name;i++) {
+ if (open_modes[i].v == mode) return open_modes[i].name;
+ }
+ return "O_XXX";
+}
+
+static const char *resultstr(enum deny_result res)
+{
+ struct {
+ enum deny_result res;
+ const char *name;
+ } results[] = {
+ {A_X, "X"},
+ {A_0, "-"},
+ {A_R, "R"},
+ {A_W, "W"},
+ {A_RW,"RW"}};
+ size_t i;
+ for (i=0;i<ARRAY_SIZE(results);i++) {
+ if (results[i].res == res) return results[i].name;
+ }
+ return "*";
+}
+
+static struct {
+ int isexe;
+ int mode1, deny1;
+ int mode2, deny2;
+ enum deny_result result;
+} denytable2[] = {
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_RW},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_R},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_W},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_READ, A_RW},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_READ, A_R},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_READ, A_W},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_NONE, A_RW},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_W},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_R},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_R},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_W},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_DOS, A_W},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_READ, A_W},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_RW},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_R},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_W},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_DOS, A_RW},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_W},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_READ, A_RW},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_READ, A_R},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_READ, A_W},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_DOS, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_R},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_R},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_R},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_W},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_READ, A_W},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_RW},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_R},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_W},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_READ, A_RW},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_READ, A_R},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_READ, A_W},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_0}
+};
+
+
+static struct {
+ int isexe;
+ int mode1, deny1;
+ int mode2, deny2;
+ enum deny_result result;
+} denytable1[] = {
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDWR, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_RW},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_R},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_W},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDONLY, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_READ, A_RW},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_READ, A_R},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_READ, A_W},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_NONE, A_RW},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_W},
+{1, O_WRONLY, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_R},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_R},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDWR, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_W},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_DOS, A_W},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_READ, A_W},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{1, O_WRONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDWR, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDWR, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_RW},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_R},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_W},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{1, O_RDONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{1, O_RDONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_DOS, A_RW},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_W},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_READ, A_RW},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_READ, A_R},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_READ, A_W},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{1, O_WRONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{1, O_WRONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDWR, DENY_FCB, O_RDWR, DENY_FCB, A_RW},
+{1, O_RDWR, DENY_FCB, O_RDONLY, DENY_FCB, A_RW},
+{1, O_RDWR, DENY_FCB, O_WRONLY, DENY_FCB, A_RW},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_DOS, A_RW},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_R},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_W},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{1, O_RDONLY, DENY_FCB, O_RDWR, DENY_FCB, A_RW},
+{1, O_RDONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_RW},
+{1, O_RDONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_RW},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_DOS, A_RW},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_R},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_W},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{1, O_WRONLY, DENY_FCB, O_RDWR, DENY_FCB, A_RW},
+{1, O_WRONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_RW},
+{1, O_WRONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_RW},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_DOS, O_RDWR, DENY_FCB, A_RW},
+{0, O_RDWR, DENY_DOS, O_RDONLY, DENY_FCB, A_RW},
+{0, O_RDWR, DENY_DOS, O_WRONLY, DENY_FCB, A_RW},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_R},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_DOS, A_RW},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_DOS, A_R},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_DOS, A_W},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_DOS, O_RDWR, DENY_FCB, A_RW},
+{0, O_WRONLY, DENY_DOS, O_RDONLY, DENY_FCB, A_RW},
+{0, O_WRONLY, DENY_DOS, O_WRONLY, DENY_FCB, A_RW},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_ALL, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_ALL, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_R},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_READ, A_R},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_NONE, A_R},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_WRITE, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_WRITE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDWR, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_W},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_READ, A_W},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_NONE, A_W},
+{0, O_WRONLY, DENY_READ, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_READ, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_READ, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDWR, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDWR, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_RW},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_R},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_W},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{0, O_RDONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{0, O_RDONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_DOS, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_READ, A_RW},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_READ, A_R},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_READ, A_W},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_NONE, A_RW},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_NONE, A_R},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_NONE, A_W},
+{0, O_WRONLY, DENY_NONE, O_RDWR, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_NONE, O_RDONLY, DENY_FCB, A_0},
+{0, O_WRONLY, DENY_NONE, O_WRONLY, DENY_FCB, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_DOS, A_RW},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_DOS, A_W},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDWR, DENY_FCB, O_RDWR, DENY_FCB, A_RW},
+{0, O_RDWR, DENY_FCB, O_RDONLY, DENY_FCB, A_RW},
+{0, O_RDWR, DENY_FCB, O_WRONLY, DENY_FCB, A_RW},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_DOS, A_RW},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_R},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_W},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{0, O_RDONLY, DENY_FCB, O_RDWR, DENY_FCB, A_RW},
+{0, O_RDONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_RW},
+{0, O_RDONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_RW},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_DOS, A_RW},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_DOS, A_R},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_DOS, A_W},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_ALL, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_WRITE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_READ, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_READ, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_NONE, A_0},
+{0, O_WRONLY, DENY_FCB, O_RDWR, DENY_FCB, A_RW},
+{0, O_WRONLY, DENY_FCB, O_RDONLY, DENY_FCB, A_RW},
+{0, O_WRONLY, DENY_FCB, O_WRONLY, DENY_FCB, A_RW}
+};
+
+
+static void progress_bar(unsigned i, unsigned total)
+{
+ if (i % 10 != 0) return;
+ printf("%5d/%5d\r", i, total);
+ fflush(stdout);
+}
+
+/*
+ this produces a matrix of deny mode behaviour for 1 connection
+ */
+bool torture_denytest1(int dummy)
+{
+ struct cli_state *cli1;
+ uint16_t fnum1, fnum2;
+ bool correct = True;
+ NTSTATUS ret1, ret2, status;
+ const char *fnames[2] = {"\\denytest1.dat", "\\denytest1.exe"};
+ size_t i, nread;
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ printf("starting denytest1\n");
+
+ for (i=0;i<2;i++) {
+ cli_unlink(cli1, fnames[i], FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_openx(cli1, fnames[i], O_RDWR|O_CREAT, DENY_NONE, &fnum1);
+ cli_writeall(cli1, fnum1, 0, (const uint8_t *)fnames[i], 0,
+ strlen(fnames[i]), NULL);
+ cli_close(cli1, fnum1);
+ }
+
+ printf("testing %ld entries\n", (unsigned long)ARRAY_SIZE(denytable1));
+
+ for (i=0; i<ARRAY_SIZE(denytable1); i++) {
+ enum deny_result res;
+ const char *fname = fnames[denytable1[i].isexe];
+
+ progress_bar(i, ARRAY_SIZE(denytable1));
+
+ ret1 = cli_openx(cli1, fname,
+ denytable1[i].mode1,
+ denytable1[i].deny1, &fnum1);
+ ret2 = cli_openx(cli1, fname,
+ denytable1[i].mode2,
+ denytable1[i].deny2, &fnum2);
+
+ if (!NT_STATUS_IS_OK(ret1)) {
+ res = A_X;
+ } else if (!NT_STATUS_IS_OK(ret2)) {
+ res = A_0;
+ } else {
+ char x = 1;
+ res = A_0;
+
+ status = cli_read(cli1, fnum2, (char *)&x, 0, 1,
+ &nread);
+ if (NT_STATUS_IS_OK(status) && nread == 1) {
+ res += A_R;
+ }
+ if (NT_STATUS_IS_OK(cli_writeall(cli1, fnum2, 0,
+ (uint8_t *)&x, 0, 1,
+ NULL))) {
+ res += A_W;
+ }
+ }
+
+ if (res != denytable1[i].result) {
+ correct = False;
+ }
+
+ if (torture_showall || res != denytable1[i].result) {
+ printf("%s %8s %10s %8s %10s %s (correct=%s)\n",
+ fname,
+ denystr(denytable1[i].deny1),
+ openstr(denytable1[i].mode1),
+ denystr(denytable1[i].deny2),
+ openstr(denytable1[i].mode2),
+ resultstr(res),
+ resultstr(denytable1[i].result));
+ }
+
+ if (NT_STATUS_IS_OK(ret1)) {
+ cli_close(cli1, fnum1);
+ }
+ if (NT_STATUS_IS_OK(ret2)) {
+ cli_close(cli1, fnum2);
+ }
+ }
+
+ for (i=0;i<2;i++) {
+ cli_unlink(cli1, fnames[i], FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+
+ printf("finished denytest1\n");
+ return correct;
+}
+
+
+/*
+ this produces a matrix of deny mode behaviour with 2 connections
+ */
+bool torture_denytest2(int dummy)
+{
+ static struct cli_state *cli1, *cli2;
+ uint16_t fnum1, fnum2;
+ int i;
+ bool correct = True;
+ NTSTATUS ret1, ret2, status;
+ const char *fnames[2] = {"\\denytest2.dat", "\\denytest2.exe"};
+ size_t nread;
+
+ if (!torture_open_connection(&cli1, 0) || !torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+
+ printf("starting denytest2\n");
+
+ for (i=0;i<2;i++) {
+ cli_unlink(cli1, fnames[i], FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_openx(cli1, fnames[i], O_RDWR|O_CREAT, DENY_NONE, &fnum1);
+ cli_writeall(cli1, fnum1, 0, (const uint8_t *)fnames[i], 0,
+ strlen(fnames[i]), NULL);
+ cli_close(cli1, fnum1);
+ }
+
+ for (i=0; i<ARRAY_SIZE(denytable2); i++) {
+ enum deny_result res;
+ const char *fname = fnames[denytable2[i].isexe];
+
+ progress_bar(i, ARRAY_SIZE(denytable2));
+
+ ret1 = cli_openx(cli1, fname,
+ denytable2[i].mode1,
+ denytable2[i].deny1, &fnum1);
+ ret2 = cli_openx(cli2, fname,
+ denytable2[i].mode2,
+ denytable2[i].deny2, &fnum2);
+
+ if (!NT_STATUS_IS_OK(ret1)) {
+ res = A_X;
+ } else if (!NT_STATUS_IS_OK(ret2)) {
+ res = A_0;
+ } else {
+ char x = 1;
+ res = A_0;
+
+ status = cli_read(cli2, fnum2, (char *)&x, 0, 1,
+ &nread);
+ if (NT_STATUS_IS_OK(status) && nread == 1) {
+ res += A_R;
+ }
+ if (NT_STATUS_IS_OK(cli_writeall(cli2, fnum2, 0,
+ (uint8_t *)&x, 0, 1,
+ NULL))) {
+ res += A_W;
+ }
+ }
+
+ if (res != denytable2[i].result) {
+ correct = False;
+ }
+
+ if (torture_showall || res != denytable2[i].result) {
+ printf("%s %8s %10s %8s %10s %s (correct=%s)\n",
+ fname,
+ denystr(denytable2[i].deny1),
+ openstr(denytable2[i].mode1),
+ denystr(denytable2[i].deny2),
+ openstr(denytable2[i].mode2),
+ resultstr(res),
+ resultstr(denytable2[i].result));
+ }
+
+ if (NT_STATUS_IS_OK(ret1)) {
+ cli_close(cli1, fnum1);
+ }
+ if (NT_STATUS_IS_OK(ret2)) {
+ cli_close(cli2, fnum2);
+ }
+ }
+
+ for (i=0;i<2;i++) {
+ cli_unlink(cli1, fnames[i], FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+ if (!torture_close_connection(cli2)) {
+ correct = False;
+ }
+
+ printf("finished denytest2\n");
+ return correct;
+}
+
diff --git a/source3/torture/locktest2.c b/source3/torture/locktest2.c
new file mode 100644
index 0000000..851d77b
--- /dev/null
+++ b/source3/torture/locktest2.c
@@ -0,0 +1,610 @@
+/*
+ Unix SMB/CIFS implementation.
+ byte range lock tester - with local filesystem support
+ Copyright (C) Andrew Tridgell 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "system/filesys.h"
+#include "locking/share_mode_lock.h"
+#include "locking/proto.h"
+#include "lib/util/string_wrappers.h"
+
+static fstring password;
+static fstring username;
+static int got_pass;
+static int numops = 1000;
+static bool showall;
+static bool analyze;
+static bool hide_unlock_fails;
+static bool use_oplocks;
+
+extern char *optarg;
+extern int optind;
+
+#define FILENAME "\\locktest.dat"
+#define LOCKRANGE 100
+#define LOCKBASE 0
+
+/*
+#define LOCKBASE (0x40000000 - 50)
+*/
+
+#define READ_PCT 50
+#define LOCK_PCT 25
+#define UNLOCK_PCT 65
+#define RANGE_MULTIPLE 1
+
+#define NSERVERS 2
+#define NCONNECTIONS 2
+#define NUMFSTYPES 2
+#define NFILES 2
+#define LOCK_TIMEOUT 0
+
+#define FSTYPE_SMB 0
+#define FSTYPE_NFS 1
+
+struct record {
+ char r1, r2;
+ char conn, f, fstype;
+ unsigned start, len;
+ char needed;
+};
+
+static struct record *recorded;
+
+static int try_open(struct cli_state *c, char *nfs, int fstype, const char *fname, int flags)
+{
+ char *path;
+
+ switch (fstype) {
+ case FSTYPE_SMB:
+ {
+ uint16_t fd;
+ if (!NT_STATUS_IS_OK(cli_openx(c, fname, flags, DENY_NONE, &fd))) {
+ return -1;
+ }
+ return fd;
+ }
+
+ case FSTYPE_NFS:
+ if (asprintf(&path, "%s%s", nfs, fname) > 0) {
+ int ret;
+ string_replace(path,'\\', '/');
+ ret = open(path, flags, 0666);
+ SAFE_FREE(path);
+ return ret;
+ }
+ break;
+ }
+
+ return -1;
+}
+
+static bool try_close(struct cli_state *c, int fstype, int fd)
+{
+ switch (fstype) {
+ case FSTYPE_SMB:
+ return NT_STATUS_IS_OK(cli_close(c, fd));
+
+ case FSTYPE_NFS:
+ return close(fd) == 0;
+ }
+
+ return False;
+}
+
+static bool try_lock(struct cli_state *c, int fstype,
+ int fd, unsigned start, unsigned len,
+ enum brl_type op)
+{
+ struct flock lock;
+
+ switch (fstype) {
+ case FSTYPE_SMB:
+ return NT_STATUS_IS_OK(cli_lock32(c, fd, start, len,
+ LOCK_TIMEOUT, op));
+
+ case FSTYPE_NFS:
+ lock.l_type = (op==READ_LOCK) ? F_RDLCK:F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = start;
+ lock.l_len = len;
+ lock.l_pid = getpid();
+ return fcntl(fd,F_SETLK,&lock) == 0;
+ }
+
+ return False;
+}
+
+static bool try_unlock(struct cli_state *c, int fstype,
+ int fd, unsigned start, unsigned len)
+{
+ struct flock lock;
+
+ switch (fstype) {
+ case FSTYPE_SMB:
+ return NT_STATUS_IS_OK(cli_unlock(c, fd, start, len));
+
+ case FSTYPE_NFS:
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = start;
+ lock.l_len = len;
+ lock.l_pid = getpid();
+ return fcntl(fd,F_SETLK,&lock) == 0;
+ }
+
+ return False;
+}
+
+static void print_brl(struct file_id id, struct server_id pid,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ br_off start, br_off size,
+ void *private_data)
+{
+ struct file_id_buf idbuf;
+
+ printf("%6d %s %s %.0f:%.0f(%.0f)\n",
+ (int)procid_to_pid(&pid),
+ file_id_str_buf(id, &idbuf),
+ lock_type==READ_LOCK?"R":"W",
+ (double)start, (double)start+size-1,(double)size);
+
+}
+
+/*****************************************************
+return a connection to a server
+*******************************************************/
+static struct cli_state *connect_one(char *share)
+{
+ struct cli_state *c;
+ char *server_n;
+ fstring server;
+ fstring myname;
+ static int count;
+ NTSTATUS nt_status;
+ bool use_kerberos = false;
+ bool fallback_after_kerberos = false;
+ bool use_ccache = false;
+ bool pw_nt_hash = false;
+ struct cli_credentials *creds = NULL;
+
+ fstrcpy(server,share+2);
+ share = strchr_m(server,'\\');
+ if (!share) return NULL;
+ *share = 0;
+ share++;
+
+ server_n = server;
+
+ if (!got_pass) {
+ char pwd[256] = {0};
+ int rc;
+
+ rc = samba_getpass("Password: ", pwd, sizeof(pwd), false, false);
+ if (rc == 0) {
+ fstrcpy(password, pwd);
+ }
+ }
+
+ creds = cli_session_creds_init(NULL,
+ username,
+ lp_workgroup(),
+ NULL, /* realm (use default) */
+ password,
+ use_kerberos,
+ fallback_after_kerberos,
+ use_ccache,
+ pw_nt_hash);
+ if (creds == NULL) {
+ DEBUG(0, ("cli_session_creds_init failed\n"));
+ return NULL;
+ }
+
+ slprintf(myname,sizeof(myname), "lock-%lu-%u", (unsigned long)getpid(), count++);
+
+ nt_status = cli_full_connection_creds(&c,
+ myname,
+ server_n,
+ NULL,
+ 0,
+ share,
+ "?????",
+ creds,
+ 0);
+ TALLOC_FREE(creds);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("cli_full_connection failed with error %s\n", nt_errstr(nt_status)));
+ return NULL;
+ }
+
+ c->use_oplocks = use_oplocks;
+
+ return c;
+}
+
+
+static void reconnect(struct cli_state *cli[NSERVERS][NCONNECTIONS],
+ char *nfs[NSERVERS],
+ int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES],
+ char *share1, char *share2)
+{
+ int server, conn, f, fstype;
+ char *share[2];
+ share[0] = share1;
+ share[1] = share2;
+
+ fstype = FSTYPE_SMB;
+
+ for (server=0;server<NSERVERS;server++)
+ for (conn=0;conn<NCONNECTIONS;conn++) {
+ if (cli[server][conn]) {
+ for (f=0;f<NFILES;f++) {
+ cli_close(cli[server][conn], fnum[server][fstype][conn][f]);
+ }
+ cli_ulogoff(cli[server][conn]);
+ cli_shutdown(cli[server][conn]);
+ }
+ cli[server][conn] = connect_one(share[server]);
+ if (!cli[server][conn]) {
+ DEBUG(0,("Failed to connect to %s\n", share[server]));
+ exit(1);
+ }
+ }
+}
+
+
+
+static bool test_one(struct cli_state *cli[NSERVERS][NCONNECTIONS],
+ char *nfs[NSERVERS],
+ int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES],
+ struct record *rec)
+{
+ unsigned conn = rec->conn;
+ unsigned f = rec->f;
+ unsigned fstype = rec->fstype;
+ unsigned start = rec->start;
+ unsigned len = rec->len;
+ unsigned r1 = rec->r1;
+ unsigned r2 = rec->r2;
+ enum brl_type op;
+ int server;
+ bool ret[NSERVERS];
+
+ if (r1 < READ_PCT) {
+ op = READ_LOCK;
+ } else {
+ op = WRITE_LOCK;
+ }
+
+ if (fstype >= NUMFSTYPES) {
+ return false;
+ }
+
+ if (r2 < LOCK_PCT) {
+ /* set a lock */
+ for (server=0;server<NSERVERS;server++) {
+ ret[server] = try_lock(cli[server][conn], fstype,
+ fnum[server][fstype][conn][f],
+ start, len, op);
+ }
+ if (showall || ret[0] != ret[1]) {
+ printf("lock conn=%u fstype=%u f=%u range=%u:%u(%u) op=%s -> %u:%u\n",
+ conn, fstype, f,
+ start, start+len-1, len,
+ op==READ_LOCK?"READ_LOCK":"WRITE_LOCK",
+ ret[0], ret[1]);
+ }
+ if (showall) brl_forall(print_brl, NULL);
+ if (ret[0] != ret[1]) return False;
+ } else if (r2 < LOCK_PCT+UNLOCK_PCT) {
+ /* unset a lock */
+ for (server=0;server<NSERVERS;server++) {
+ ret[server] = try_unlock(cli[server][conn], fstype,
+ fnum[server][fstype][conn][f],
+ start, len);
+ }
+ if (showall || (!hide_unlock_fails && (ret[0] != ret[1]))) {
+ printf("unlock conn=%u fstype=%u f=%u range=%u:%u(%u) -> %u:%u\n",
+ conn, fstype, f,
+ start, start+len-1, len,
+ ret[0], ret[1]);
+ }
+ if (showall) brl_forall(print_brl, NULL);
+ if (!hide_unlock_fails && ret[0] != ret[1]) return False;
+ } else {
+ /* reopen the file */
+ for (server=0;server<NSERVERS;server++) {
+ try_close(cli[server][conn], fstype, fnum[server][fstype][conn][f]);
+ fnum[server][fstype][conn][f] = try_open(cli[server][conn], nfs[server], fstype, FILENAME,
+ O_RDWR|O_CREAT);
+ if (fnum[server][fstype][conn][f] == -1) {
+ printf("failed to reopen on share1\n");
+ return False;
+ }
+ }
+ if (showall) {
+ printf("reopen conn=%u fstype=%u f=%u\n",
+ conn, fstype, f);
+ brl_forall(print_brl, NULL);
+ }
+ }
+ return True;
+}
+
+static void close_files(struct cli_state *cli[NSERVERS][NCONNECTIONS],
+ char *nfs[NSERVERS],
+ int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES])
+{
+ int server, conn, f, fstype;
+
+ for (server=0;server<NSERVERS;server++)
+ for (fstype=0;fstype<NUMFSTYPES;fstype++)
+ for (conn=0;conn<NCONNECTIONS;conn++)
+ for (f=0;f<NFILES;f++) {
+ if (fnum[server][fstype][conn][f] != -1) {
+ try_close(cli[server][conn], fstype, fnum[server][fstype][conn][f]);
+ fnum[server][fstype][conn][f] = -1;
+ }
+ }
+ for (server=0;server<NSERVERS;server++) {
+ cli_unlink(cli[server][0], FILENAME, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ }
+}
+
+static void open_files(struct cli_state *cli[NSERVERS][NCONNECTIONS],
+ char *nfs[NSERVERS],
+ int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES])
+{
+ int server, fstype, conn, f;
+
+ for (server=0;server<NSERVERS;server++)
+ for (fstype=0;fstype<NUMFSTYPES;fstype++)
+ for (conn=0;conn<NCONNECTIONS;conn++)
+ for (f=0;f<NFILES;f++) {
+ fnum[server][fstype][conn][f] = try_open(cli[server][conn], nfs[server], fstype, FILENAME,
+ O_RDWR|O_CREAT);
+ if (fnum[server][fstype][conn][f] == -1) {
+ fprintf(stderr,"Failed to open fnum[%u][%u][%u][%u]\n",
+ server, fstype, conn, f);
+ exit(1);
+ }
+ }
+}
+
+
+static int retest(struct cli_state *cli[NSERVERS][NCONNECTIONS],
+ char *nfs[NSERVERS],
+ int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES],
+ int n)
+{
+ int i;
+ printf("testing %u ...\n", n);
+ for (i=0; i<n; i++) {
+ if (i && i % 100 == 0) {
+ printf("%u\n", i);
+ }
+
+ if (recorded[i].needed &&
+ !test_one(cli, nfs, fnum, &recorded[i])) return i;
+ }
+ return n;
+}
+
+
+/* each server has two connections open to it. Each connection has two file
+ descriptors open on the file - 8 file descriptors in total
+
+ we then do random locking ops in tamdem on the 4 fnums from each
+ server and ensure that the results match
+ */
+static void test_locks(char *share1, char *share2, char *nfspath1, char *nfspath2)
+{
+ struct cli_state *cli[NSERVERS][NCONNECTIONS];
+ char *nfs[NSERVERS];
+ int fnum[NSERVERS][NUMFSTYPES][NCONNECTIONS][NFILES];
+ int n, i, n1;
+
+ nfs[0] = nfspath1;
+ nfs[1] = nfspath2;
+
+ ZERO_STRUCT(fnum);
+ ZERO_STRUCT(cli);
+
+ recorded = SMB_MALLOC_ARRAY(struct record, numops);
+
+ for (n=0; n<numops; n++) {
+ recorded[n].conn = random() % NCONNECTIONS;
+ recorded[n].fstype = random() % NUMFSTYPES;
+ recorded[n].f = random() % NFILES;
+ recorded[n].start = LOCKBASE + ((unsigned)random() % (LOCKRANGE-1));
+ recorded[n].len = 1 +
+ random() % (LOCKRANGE-(recorded[n].start-LOCKBASE));
+ recorded[n].start *= RANGE_MULTIPLE;
+ recorded[n].len *= RANGE_MULTIPLE;
+ recorded[n].r1 = random() % 100;
+ recorded[n].r2 = random() % 100;
+ recorded[n].needed = True;
+ }
+
+ reconnect(cli, nfs, fnum, share1, share2);
+ open_files(cli, nfs, fnum);
+ n = retest(cli, nfs, fnum, numops);
+
+ if (n == numops || !analyze) return;
+ n++;
+
+ while (1) {
+ n1 = n;
+
+ close_files(cli, nfs, fnum);
+ reconnect(cli, nfs, fnum, share1, share2);
+ open_files(cli, nfs, fnum);
+
+ for (i=0;i<n-1;i++) {
+ int m;
+ recorded[i].needed = False;
+
+ close_files(cli, nfs, fnum);
+ open_files(cli, nfs, fnum);
+
+ m = retest(cli, nfs, fnum, n);
+ if (m == n) {
+ recorded[i].needed = True;
+ } else {
+ if (i < m) {
+ memmove(&recorded[i], &recorded[i+1],
+ (m-i)*sizeof(recorded[0]));
+ }
+ n = m;
+ i--;
+ }
+ }
+
+ if (n1 == n) break;
+ }
+
+ close_files(cli, nfs, fnum);
+ reconnect(cli, nfs, fnum, share1, share2);
+ open_files(cli, nfs, fnum);
+ showall = True;
+ n1 = retest(cli, nfs, fnum, n);
+ if (n1 != n-1) {
+ printf("ERROR - inconsistent result (%u %u)\n", n1, n);
+ }
+ close_files(cli, nfs, fnum);
+
+ for (i=0;i<n;i++) {
+ printf("{%u, %u, %u, %u, %u, %u, %u, %u},\n",
+ recorded[i].r1,
+ recorded[i].r2,
+ recorded[i].conn,
+ recorded[i].fstype,
+ recorded[i].f,
+ recorded[i].start,
+ recorded[i].len,
+ recorded[i].needed);
+ }
+}
+
+
+
+static void usage(void)
+{
+ printf(
+"Usage:\n\
+ locktest //server1/share1 //server2/share2 /path1 /path2 [options..]\n\
+ options:\n\
+ -U user%%pass\n\
+ -s seed\n\
+ -o numops\n\
+ -u hide unlock fails\n\
+ -a (show all ops)\n\
+ -O use oplocks\n\
+");
+}
+
+/****************************************************************************
+ main program
+****************************************************************************/
+ int main(int argc,char *argv[])
+{
+ char *share1, *share2, *nfspath1, *nfspath2;
+ int opt;
+ char *p;
+ int seed;
+
+ setlinebuf(stdout);
+
+ if (argc < 5 || argv[1][0] == '-') {
+ usage();
+ exit(1);
+ }
+
+ setup_logging(argv[0], DEBUG_STDOUT);
+
+ share1 = argv[1];
+ share2 = argv[2];
+ nfspath1 = argv[3];
+ nfspath2 = argv[4];
+
+ all_string_sub(share1,"/","\\",0);
+ all_string_sub(share2,"/","\\",0);
+
+ argc -= 4;
+ argv += 4;
+
+ lp_load_global(get_dyn_CONFIGFILE());
+ load_interfaces();
+
+ if (getenv("USER")) {
+ fstrcpy(username,getenv("USER"));
+ }
+
+ seed = time(NULL);
+
+ while ((opt = getopt(argc, argv, "U:s:ho:aAW:O")) != EOF) {
+ switch (opt) {
+ case 'U':
+ fstrcpy(username,optarg);
+ p = strchr_m(username,'%');
+ if (p) {
+ *p = 0;
+ fstrcpy(password, p+1);
+ got_pass = 1;
+ }
+ break;
+ case 's':
+ seed = atoi(optarg);
+ break;
+ case 'u':
+ hide_unlock_fails = True;
+ break;
+ case 'o':
+ numops = atoi(optarg);
+ break;
+ case 'O':
+ use_oplocks = True;
+ break;
+ case 'a':
+ showall = True;
+ break;
+ case 'A':
+ analyze = True;
+ break;
+ case 'h':
+ usage();
+ exit(1);
+ default:
+ printf("Unknown option %c (%d)\n", (char)opt, opt);
+ exit(1);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ DEBUG(0,("seed=%u\n", seed));
+ srandom(seed);
+
+ locking_init_readonly();
+ test_locks(share1, share2, nfspath1, nfspath2);
+
+ return(0);
+}
diff --git a/source3/torture/mangle_test.c b/source3/torture/mangle_test.c
new file mode 100644
index 0000000..92754b9
--- /dev/null
+++ b/source3/torture/mangle_test.c
@@ -0,0 +1,223 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB torture tester - mangling test
+ Copyright (C) Andrew Tridgell 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+extern int torture_numops;
+
+static TDB_CONTEXT *tdb;
+
+#define NAME_LENGTH 20
+
+static unsigned total, collisions, failures;
+
+static bool test_one(struct cli_state *cli, const char *name)
+{
+ uint16_t fnum;
+ fstring shortname;
+ fstring name2;
+ NTSTATUS status;
+ TDB_DATA data;
+
+ total++;
+
+ status = cli_openx(cli, name, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", name, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close of %s failed (%s)\n", name, nt_errstr(status));
+ return False;
+ }
+
+ /* get the short name */
+ status = cli_qpathinfo_alt_name(cli, name, shortname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("query altname of %s failed (%s)\n", name, nt_errstr(status));
+ return False;
+ }
+
+ fstr_sprintf(name2, "\\mangle_test\\%s", shortname);
+ status = cli_unlink(cli, name2, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink of %s (%s) failed (%s)\n",
+ name2, name, nt_errstr(status));
+ return False;
+ }
+
+ /* recreate by short name */
+ status = cli_openx(cli, name2, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open2 of %s failed (%s)\n", name2, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close of %s failed (%s)\n", name, nt_errstr(status));
+ return False;
+ }
+
+ /* and unlink by long name */
+ status = cli_unlink(cli, name, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink2 of %s (%s) failed (%s)\n",
+ name, name2, nt_errstr(status));
+ failures++;
+ cli_unlink(cli, name2, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ return True;
+ }
+
+ /* see if the short name is already in the tdb */
+ data = tdb_fetch_bystring(tdb, shortname);
+ if (data.dptr) {
+ /* maybe its a duplicate long name? */
+ if (!strequal(name, (const char *)data.dptr)) {
+ /* we have a collision */
+ collisions++;
+ printf("Collision between %s and %s -> %s "
+ " (coll/tot: %u/%u)\n",
+ name, data.dptr, shortname, collisions, total);
+ }
+ free(data.dptr);
+ } else {
+ TDB_DATA namedata;
+ /* store it for later */
+ namedata.dptr = discard_const_p(uint8_t, name);
+ namedata.dsize = strlen(name)+1;
+ tdb_store_bystring(tdb, shortname, namedata, TDB_REPLACE);
+ }
+
+ return True;
+}
+
+
+static void gen_name(char *name)
+{
+ const char *chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._-$~... ";
+ unsigned max_idx = strlen(chars);
+ unsigned len;
+ int i;
+ char *p;
+
+ fstrcpy(name, "\\mangle_test\\");
+ p = name + strlen(name);
+
+ len = 1 + random() % NAME_LENGTH;
+
+ for (i=0;i<len;i++) {
+ p[i] = chars[random() % max_idx];
+ }
+
+ p[i] = 0;
+
+ if (strcmp(p, ".") == 0 || strcmp(p, "..") == 0) {
+ p[0] = '_';
+ }
+
+ /* have a high probability of a common lead char */
+ if (random() % 2 == 0) {
+ p[0] = 'A';
+ }
+
+ /* and a medium probability of a common lead string */
+ if (random() % 10 == 0) {
+ if (strlen(p) <= 5) {
+ fstrcpy(p, "ABCDE");
+ } else {
+ /* try not to kill off the null termination */
+ memcpy(p, "ABCDE", 5);
+ }
+ }
+
+ /* and a high probability of a good extension length */
+ if (random() % 2 == 0) {
+ char *s = strrchr(p, '.');
+ if (s) {
+ s[4] = 0;
+ }
+ }
+
+ /* ..... and a 100% proability of a file not ending in "." */
+ if (p[strlen(p)-1] == '.')
+ p[strlen(p)-1] = '_';
+}
+
+
+bool torture_mangle(int dummy)
+{
+ static struct cli_state *cli;
+ int i;
+ bool ret = True;
+
+ printf("starting mangle test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ /* we will use an internal tdb to store the names we have used */
+ tdb = tdb_open(NULL, 100000, TDB_INTERNAL, 0, 0);
+ if (!tdb) {
+ printf("ERROR: Failed to open tdb\n");
+ return False;
+ }
+
+ torture_deltree(cli, "\\mangle_test");
+
+ if (!NT_STATUS_IS_OK(cli_mkdir(cli, "\\mangle_test"))) {
+ printf("ERROR: Failed to make directory\n");
+ return False;
+ }
+
+ for (i=0;i<torture_numops;i++) {
+ fstring name;
+ ZERO_STRUCT(name);
+
+ gen_name(name);
+
+ if (!test_one(cli, name)) {
+ ret = False;
+ break;
+ }
+ if (total && total % 100 == 0) {
+ printf("collisions %u/%u - %.2f%% (%u failures)\r",
+ collisions, total, (100.0*collisions) / total, failures);
+ }
+ }
+
+ torture_deltree(cli, "\\mangle_test");
+
+ printf("\nTotal collisions %u/%u - %.2f%% (%u failures)\n",
+ collisions, total, (100.0*collisions) / total, failures);
+
+ torture_close_connection(cli);
+
+ printf("mangle test finished\n");
+ return (ret && (failures == 0));
+}
diff --git a/source3/torture/msg_sink.c b/source3/torture/msg_sink.c
new file mode 100644
index 0000000..3c3dda3
--- /dev/null
+++ b/source3/torture/msg_sink.c
@@ -0,0 +1,285 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Receive and count messages
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "messages.h"
+#include "lib/util/tevent_unix.h"
+#include <stdio.h>
+
+struct sink_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ int msg_type;
+ unsigned *counter;
+};
+
+static void sink_done(struct tevent_req *subreq);
+
+static struct tevent_req *sink_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int msg_type, unsigned *counter)
+{
+ struct tevent_req *req, *subreq;
+ struct sink_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct sink_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->msg_type = msg_type;
+ state->counter = counter;
+
+ subreq = messaging_read_send(state, state->ev, state->msg_ctx,
+ state->msg_type);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, sink_done, req);
+ return req;
+}
+
+static void sink_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct sink_state *state = tevent_req_data(
+ req, struct sink_state);
+ int ret;
+
+ ret = messaging_read_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+
+ *state->counter += 1;
+
+ subreq = messaging_read_send(state, state->ev, state->msg_ctx,
+ state->msg_type);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, sink_done, req);
+}
+
+static int sink_recv(struct tevent_req *req)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ return 0;
+}
+
+struct prcount_state {
+ struct tevent_context *ev;
+ struct timeval interval;
+ unsigned *counter;
+};
+
+static void prcount_waited(struct tevent_req *subreq);
+
+static struct tevent_req *prcount_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct timeval interval,
+ unsigned *counter)
+{
+ struct tevent_req *req, *subreq;
+ struct prcount_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct prcount_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->interval = interval;
+ state->counter = counter;
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ timeval_current_ofs(state->interval.tv_sec,
+ state->interval.tv_usec));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, prcount_waited, req);
+ return req;
+}
+
+static void prcount_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct prcount_state *state = tevent_req_data(
+ req, struct prcount_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ printf("%u\n", *state->counter);
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ timeval_current_ofs(state->interval.tv_sec,
+ state->interval.tv_usec));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, prcount_waited, req);
+}
+
+static int prcount_recv(struct tevent_req *req)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ return 0;
+}
+
+struct msgcount_state {
+ unsigned count;
+};
+
+static void msgcount_sunk(struct tevent_req *subreq);
+static void msgcount_printed(struct tevent_req *subreq);
+
+static struct tevent_req *msgcount_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int msg_type, struct timeval interval)
+{
+ struct tevent_req *req, *subreq;
+ struct msgcount_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct msgcount_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = sink_send(state, ev, msg_ctx, msg_type, &state->count);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, msgcount_sunk, req);
+
+ subreq = prcount_send(state, ev, interval, &state->count);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, msgcount_printed, req);
+
+ return req;
+}
+
+static void msgcount_sunk(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = sink_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void msgcount_printed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = prcount_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static int msgcount_recv(struct tevent_req *req)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ return 0;
+}
+
+int main(void)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct tevent_req *req;
+ int ret;
+ struct server_id id;
+ struct server_id_buf tmp;
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ perror("tevent_context_init failed");
+ return -1;
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ perror("messaging_init failed");
+ return -1;
+ }
+
+ id = messaging_server_id(msg_ctx);
+
+ printf("server_id: %s\n", server_id_str_buf(id, &tmp));
+
+ req = msgcount_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY,
+ timeval_set(1, 0));
+ if (req == NULL) {
+ perror("msgcount_send failed");
+ return -1;
+ }
+
+ if (!tevent_req_poll(req, ev)) {
+ perror("tevent_req_poll failed");
+ return -1;
+ }
+
+ ret = msgcount_recv(req);
+ printf("msgcount_recv returned %d\n", ret);
+
+ return 0;
+}
diff --git a/source3/torture/msg_source.c b/source3/torture/msg_source.c
new file mode 100644
index 0000000..e718018
--- /dev/null
+++ b/source3/torture/msg_source.c
@@ -0,0 +1,159 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Send messages once a second
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "messages.h"
+#include "lib/util/tevent_unix.h"
+#include <stdio.h>
+
+struct source_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ int msg_type;
+ struct timeval interval;
+ struct server_id dst;
+};
+
+static void source_waited(struct tevent_req *subreq);
+
+static struct tevent_req *source_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ int msg_type,
+ struct timeval interval,
+ struct server_id dst)
+{
+ struct tevent_req *req, *subreq;
+ struct source_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct source_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->msg_type = msg_type;
+ state->interval = interval;
+ state->dst = dst;
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ timeval_current_ofs(state->interval.tv_sec,
+ state->interval.tv_usec));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, source_waited, req);
+ return req;
+}
+
+static void source_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct source_state *state = tevent_req_data(
+ req, struct source_state);
+ bool ok;
+ uint8_t buf[200] = { };
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ messaging_send_buf(state->msg_ctx, state->dst, state->msg_type,
+ buf, sizeof(buf));
+
+ subreq = tevent_wakeup_send(
+ state, state->ev,
+ timeval_current_ofs(state->interval.tv_sec,
+ state->interval.tv_usec));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, source_waited, req);
+}
+
+static int source_recv(struct tevent_req *req)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ return 0;
+}
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct tevent_req *req;
+ int ret;
+ struct server_id my_id, id;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <dst>\n", argv[0]);
+ return -1;
+ }
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ ev = tevent_context_init(frame);
+ if (ev == NULL) {
+ perror("tevent_context_init failed");
+ return -1;
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ perror("messaging_init failed");
+ return -1;
+ }
+ my_id = messaging_server_id(msg_ctx);
+
+ id = server_id_from_string(my_id.vnn, argv[1]);
+ if (!procid_valid(&id)) {
+ fprintf(stderr, "pid %s invalid\n", argv[1]);
+ return -1;
+ }
+
+ req = source_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY,
+ timeval_set(0, 10000), id);
+ if (req == NULL) {
+ perror("source_send failed");
+ return -1;
+ }
+
+ if (!tevent_req_poll(req, ev)) {
+ perror("tevent_req_poll failed");
+ return -1;
+ }
+
+ ret = source_recv(req);
+
+ printf("source_recv returned %d\n", ret);
+
+ return 0;
+}
diff --git a/source3/torture/msgtest.c b/source3/torture/msgtest.c
new file mode 100644
index 0000000..1d2a8a9
--- /dev/null
+++ b/source3/torture/msgtest.c
@@ -0,0 +1,171 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Andrew Tridgell 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ test code for internal messaging
+ */
+
+#include "includes.h"
+#include "messages.h"
+
+static int pong_count;
+
+
+/****************************************************************************
+a useful function for testing the message system
+****************************************************************************/
+static void pong_message(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id pid,
+ DATA_BLOB *data)
+{
+ pong_count++;
+}
+
+ int main(int argc, char *argv[])
+{
+ struct tevent_context *evt_ctx;
+ struct messaging_context *msg_ctx;
+ pid_t pid;
+ int i, n;
+ char buf[12];
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ smb_init_locale();
+
+ setup_logging(argv[0], DEBUG_STDOUT);
+
+ lp_load_global(get_dyn_CONFIGFILE());
+
+ if (!(evt_ctx = samba_tevent_context_init(NULL)) ||
+ !(msg_ctx = messaging_init(NULL, evt_ctx))) {
+ fprintf(stderr, "could not init messaging context\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ if (argc != 3) {
+ fprintf(stderr, "%s: Usage - %s pid count\n", argv[0],
+ argv[0]);
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ pid = atoi(argv[1]);
+ n = atoi(argv[2]);
+
+ messaging_register(msg_ctx, NULL, MSG_PONG, pong_message);
+
+ for (i=0;i<n;i++) {
+ messaging_send(msg_ctx, pid_to_procid(pid), MSG_PING,
+ &data_blob_null);
+ }
+
+ while (pong_count < i) {
+ ret = tevent_loop_once(evt_ctx);
+ if (ret != 0) {
+ break;
+ }
+ }
+
+ /* Ensure all messages get through to ourselves. */
+ pong_count = 0;
+
+ strlcpy(buf, "1234567890", sizeof(buf));
+
+ for (i=0;i<n;i++) {
+ messaging_send(msg_ctx, messaging_server_id(msg_ctx), MSG_PING,
+ &data_blob_null);
+ messaging_send_buf(msg_ctx, messaging_server_id(msg_ctx),
+ MSG_PING,(uint8_t *)buf, 11);
+ }
+
+ /*
+ * We have to loop at least 2 times for
+ * each message as local ping messages are
+ * handled by an immediate callback, that
+ * has to be dispatched, which sends a pong
+ * message, which also has to be dispatched.
+ * Above we sent 2*n messages, which means
+ * we have to dispatch 4*n times.
+ */
+
+ while (pong_count < n*2) {
+ ret = tevent_loop_once(evt_ctx);
+ if (ret != 0) {
+ break;
+ }
+ }
+
+ if (pong_count != 2*n) {
+ fprintf(stderr, "Message count failed (%d).\n", pong_count);
+ }
+
+ /* Speed testing */
+
+ pong_count = 0;
+
+ {
+ struct timeval tv = timeval_current();
+ size_t timelimit = n;
+ size_t ping_count = 0;
+
+ printf("Sending pings for %d seconds\n", (int)timelimit);
+ while (timeval_elapsed(&tv) < timelimit) {
+ if(NT_STATUS_IS_OK(messaging_send_buf(
+ msg_ctx, pid_to_procid(pid),
+ MSG_PING,
+ (uint8_t *)buf, 11)))
+ ping_count++;
+ if(NT_STATUS_IS_OK(messaging_send(
+ msg_ctx, pid_to_procid(pid),
+ MSG_PING, &data_blob_null)))
+ ping_count++;
+
+ while (ping_count > pong_count + 20) {
+ ret = tevent_loop_once(evt_ctx);
+ if (ret != 0) {
+ break;
+ }
+ }
+ }
+
+ printf("waiting for %d remaining replies (done %d)\n",
+ (int)(ping_count - pong_count), pong_count);
+ while (timeval_elapsed(&tv) < 30 && pong_count < ping_count) {
+ ret = tevent_loop_once(evt_ctx);
+ if (ret != 0) {
+ break;
+ }
+ }
+
+ if (ping_count != pong_count) {
+ fprintf(stderr, "ping test failed! received %d, sent "
+ "%d\n", pong_count, (int)ping_count);
+ }
+
+ printf("ping rate of %.0f messages/sec\n",
+ (ping_count+pong_count)/timeval_elapsed(&tv));
+ }
+
+ TALLOC_FREE(frame);
+ return (0);
+}
+
diff --git a/source3/torture/nbench.c b/source3/torture/nbench.c
new file mode 100644
index 0000000..8646d7a
--- /dev/null
+++ b/source3/torture/nbench.c
@@ -0,0 +1,504 @@
+/*
+ Unix SMB/CIFS implementation.
+ In-memory cache
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "../lib/util/tevent_ntstatus.h"
+
+static long long int ival(const char *str)
+{
+ return strtoll(str, NULL, 0);
+}
+
+struct nbench_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *cliname;
+ FILE *loadfile;
+ struct ftable *ftable;
+ void (*bw_report)(size_t nread,
+ size_t nwritten,
+ void *private_data);
+ void *bw_report_private;
+};
+
+struct lock_info {
+ struct lock_info *next, *prev;
+ off_t offset;
+ int size;
+};
+
+struct createx_params {
+ char *fname;
+ unsigned int cr_options;
+ unsigned int cr_disposition;
+ int handle;
+};
+
+struct ftable {
+ struct ftable *next, *prev;
+ struct createx_params cp;
+ struct lock_info *locks;
+ uint16_t fnum; /* the fd that we got back from the server */
+};
+
+enum nbench_cmd {
+ NBENCH_CMD_NTCREATEX,
+ NBENCH_CMD_CLOSE,
+ NBENCH_CMD_RENAME,
+ NBENCH_CMD_UNLINK,
+ NBENCH_CMD_DELTREE,
+ NBENCH_CMD_RMDIR,
+ NBENCH_CMD_MKDIR,
+ NBENCH_CMD_QUERY_PATH_INFORMATION,
+ NBENCH_CMD_QUERY_FILE_INFORMATION,
+ NBENCH_CMD_QUERY_FS_INFORMATION,
+ NBENCH_CMD_SET_FILE_INFORMATION,
+ NBENCH_CMD_FIND_FIRST,
+ NBENCH_CMD_WRITEX,
+ NBENCH_CMD_WRITE,
+ NBENCH_CMD_LOCKX,
+ NBENCH_CMD_UNLOCKX,
+ NBENCH_CMD_READX,
+ NBENCH_CMD_FLUSH,
+ NBENCH_CMD_SLEEP,
+};
+
+struct nbench_cmd_struct {
+ char **params;
+ int num_params;
+ NTSTATUS status;
+ enum nbench_cmd cmd;
+};
+
+static struct nbench_cmd_struct *nbench_parse(TALLOC_CTX *mem_ctx,
+ const char *line)
+{
+ struct nbench_cmd_struct *result;
+ char *cmd;
+ char *status;
+
+ result = talloc(mem_ctx, struct nbench_cmd_struct);
+ if (result == NULL) {
+ return NULL;
+ }
+ result->params = str_list_make_shell(mem_ctx, line, " ");
+ if (result->params == NULL) {
+ goto fail;
+ }
+ result->num_params = talloc_array_length(result->params) - 1;
+ if (result->num_params < 2) {
+ goto fail;
+ }
+ status = result->params[result->num_params-1];
+ if (strncmp(status, "NT_STATUS_", 10) != 0 &&
+ strncmp(status, "0x", 2) != 0) {
+ goto fail;
+ }
+ /* accept numeric or string status codes */
+ if (strncmp(status, "0x", 2) == 0) {
+ result->status = NT_STATUS(strtoul(status, NULL, 16));
+ } else {
+ result->status = nt_status_string_to_code(status);
+ }
+
+ cmd = result->params[0];
+
+ if (!strcmp(cmd, "NTCreateX")) {
+ result->cmd = NBENCH_CMD_NTCREATEX;
+ } else if (!strcmp(cmd, "Close")) {
+ result->cmd = NBENCH_CMD_CLOSE;
+ } else if (!strcmp(cmd, "Rename")) {
+ result->cmd = NBENCH_CMD_RENAME;
+ } else if (!strcmp(cmd, "Unlink")) {
+ result->cmd = NBENCH_CMD_UNLINK;
+ } else if (!strcmp(cmd, "Deltree")) {
+ result->cmd = NBENCH_CMD_DELTREE;
+ } else if (!strcmp(cmd, "Rmdir")) {
+ result->cmd = NBENCH_CMD_RMDIR;
+ } else if (!strcmp(cmd, "Mkdir")) {
+ result->cmd = NBENCH_CMD_MKDIR;
+ } else if (!strcmp(cmd, "QUERY_PATH_INFORMATION")) {
+ result->cmd = NBENCH_CMD_QUERY_PATH_INFORMATION;
+ } else if (!strcmp(cmd, "QUERY_FILE_INFORMATION")) {
+ result->cmd = NBENCH_CMD_QUERY_FILE_INFORMATION;
+ } else if (!strcmp(cmd, "QUERY_FS_INFORMATION")) {
+ result->cmd = NBENCH_CMD_QUERY_FS_INFORMATION;
+ } else if (!strcmp(cmd, "SET_FILE_INFORMATION")) {
+ result->cmd = NBENCH_CMD_SET_FILE_INFORMATION;
+ } else if (!strcmp(cmd, "FIND_FIRST")) {
+ result->cmd = NBENCH_CMD_FIND_FIRST;
+ } else if (!strcmp(cmd, "WriteX")) {
+ result->cmd = NBENCH_CMD_WRITEX;
+ } else if (!strcmp(cmd, "Write")) {
+ result->cmd = NBENCH_CMD_WRITE;
+ } else if (!strcmp(cmd, "LockX")) {
+ result->cmd = NBENCH_CMD_LOCKX;
+ } else if (!strcmp(cmd, "UnlockX")) {
+ result->cmd = NBENCH_CMD_UNLOCKX;
+ } else if (!strcmp(cmd, "ReadX")) {
+ result->cmd = NBENCH_CMD_READX;
+ } else if (!strcmp(cmd, "Flush")) {
+ result->cmd = NBENCH_CMD_FLUSH;
+ } else if (!strcmp(cmd, "Sleep")) {
+ result->cmd = NBENCH_CMD_SLEEP;
+ } else {
+ goto fail;
+ }
+ return result;
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+static struct ftable *ft_find(struct ftable *ftlist, int handle)
+{
+ while (ftlist != NULL) {
+ if (ftlist->cp.handle == handle) {
+ return ftlist;
+ }
+ ftlist = ftlist->next;
+ }
+ return NULL;
+}
+
+struct nbench_cmd_state {
+ struct tevent_context *ev;
+ struct nbench_state *state;
+ struct nbench_cmd_struct *cmd;
+ struct ftable *ft;
+ bool eof;
+};
+
+static void nbench_cmd_done(struct tevent_req *subreq);
+
+static struct tevent_req *nbench_cmd_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct nbench_state *nb_state)
+{
+ struct tevent_req *req, *subreq;
+ struct nbench_cmd_state *state;
+ char line[1024];
+ size_t len;
+
+ req = tevent_req_create(mem_ctx, &state, struct nbench_cmd_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->state = nb_state;
+
+ if (fgets(line, sizeof(line), nb_state->loadfile) == NULL) {
+ tevent_req_nterror(req, NT_STATUS_END_OF_FILE);
+ return tevent_req_post(req, ev);
+ }
+ len = strlen(line);
+ if (len == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (line[len-1] == '\n') {
+ line[len-1] = '\0';
+ }
+
+ state->cmd = nbench_parse(state, line);
+ if (state->cmd == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ switch (state->cmd->cmd) {
+ case NBENCH_CMD_NTCREATEX: {
+ uint32_t desired_access;
+ uint32_t share_mode;
+ unsigned int flags = 0;
+
+ state->ft = talloc(state, struct ftable);
+ if (tevent_req_nomem(state->ft, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->ft->cp.fname = talloc_all_string_sub(
+ state->ft, state->cmd->params[1], "client1",
+ nb_state->cliname);
+ if (tevent_req_nomem(state->ft->cp.fname, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->ft->cp.cr_options = ival(state->cmd->params[2]);
+ state->ft->cp.cr_disposition = ival(state->cmd->params[3]);
+ state->ft->cp.handle = ival(state->cmd->params[4]);
+
+ if (state->ft->cp.cr_options & FILE_DIRECTORY_FILE) {
+ desired_access = SEC_FILE_READ_DATA;
+ } else {
+ desired_access =
+ SEC_FILE_READ_DATA |
+ SEC_FILE_WRITE_DATA |
+ SEC_FILE_READ_ATTRIBUTE |
+ SEC_FILE_WRITE_ATTRIBUTE;
+ flags = EXTENDED_RESPONSE_REQUIRED
+ | REQUEST_OPLOCK | REQUEST_BATCH_OPLOCK;
+ }
+ share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+
+ subreq = cli_ntcreate_send(
+ state, ev, nb_state->cli, state->ft->cp.fname, flags,
+ desired_access, 0, share_mode,
+ state->ft->cp.cr_disposition,
+ state->ft->cp.cr_options,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ break;
+ }
+ case NBENCH_CMD_CLOSE: {
+ state->ft = ft_find(state->state->ftable,
+ ival(state->cmd->params[1]));
+ if (state->ft == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ subreq = cli_close_send(state,
+ ev,
+ nb_state->cli,
+ state->ft->fnum,
+ 0);
+ break;
+ }
+ case NBENCH_CMD_MKDIR: {
+ char *fname;
+ fname = talloc_all_string_sub(
+ state, state->cmd->params[1], "client1",
+ nb_state->cliname);
+ if (tevent_req_nomem(state->ft->cp.fname, req)) {
+ return tevent_req_post(req, ev);
+ }
+ subreq = cli_mkdir_send(state, ev, nb_state->cli, fname);
+ break;
+ }
+ case NBENCH_CMD_QUERY_PATH_INFORMATION: {
+ char *fname;
+ fname = talloc_all_string_sub(
+ state, state->cmd->params[1], "client1",
+ nb_state->cliname);
+ if (tevent_req_nomem(state->ft->cp.fname, req)) {
+ return tevent_req_post(req, ev);
+ }
+ subreq = cli_qpathinfo_send(state, ev, nb_state->cli, fname,
+ ival(state->cmd->params[2]),
+ 0, CLI_BUFFER_SIZE);
+ break;
+ }
+ default:
+ tevent_req_nterror(req, NT_STATUS_NOT_IMPLEMENTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, nbench_cmd_done, req);
+ return req;
+}
+
+static bool status_wrong(struct tevent_req *req, NTSTATUS expected,
+ NTSTATUS status)
+{
+ if (NT_STATUS_EQUAL(expected, status)) {
+ return false;
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ tevent_req_nterror(req, status);
+ return true;
+}
+
+static void nbench_cmd_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nbench_cmd_state *state = tevent_req_data(
+ req, struct nbench_cmd_state);
+ struct nbench_state *nbstate = state->state;
+ NTSTATUS status;
+
+ switch (state->cmd->cmd) {
+ case NBENCH_CMD_NTCREATEX: {
+ struct ftable *ft;
+ status = cli_ntcreate_recv(subreq, &state->ft->fnum, NULL);
+ TALLOC_FREE(subreq);
+ if (status_wrong(req, state->cmd->status, status)) {
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return;
+ }
+ ft = talloc_move(nbstate, &state->ft);
+ DLIST_ADD(nbstate->ftable, ft);
+ break;
+ }
+ case NBENCH_CMD_CLOSE: {
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (status_wrong(req, state->cmd->status, status)) {
+ return;
+ }
+ DLIST_REMOVE(state->state->ftable, state->ft);
+ TALLOC_FREE(state->ft);
+ break;
+ }
+ case NBENCH_CMD_MKDIR: {
+ status = cli_mkdir_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (status_wrong(req, state->cmd->status, status)) {
+ return;
+ }
+ break;
+ }
+ case NBENCH_CMD_QUERY_PATH_INFORMATION: {
+ status = cli_qpathinfo_recv(subreq, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (status_wrong(req, state->cmd->status, status)) {
+ return;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS nbench_cmd_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void nbench_done(struct tevent_req *subreq);
+
+static struct tevent_req *nbench_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *cliname, FILE *loadfile,
+ void (*bw_report)(size_t nread, size_t nwritten, void *private_data),
+ void *bw_report_private)
+{
+ struct tevent_req *req, *subreq;
+ struct nbench_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct nbench_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->cliname = cliname;
+ state->loadfile = loadfile;
+ state->bw_report = bw_report;
+ state->bw_report_private = bw_report_private;
+
+ subreq = nbench_cmd_send(state, ev, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, nbench_done, req);
+ return req;
+}
+
+static void nbench_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct nbench_state *state = tevent_req_data(
+ req, struct nbench_state);
+ NTSTATUS status;
+
+ status = nbench_cmd_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) {
+ tevent_req_done(req);
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ subreq = nbench_cmd_send(state, state->ev, state);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, nbench_done, req);
+}
+
+static NTSTATUS nbench_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+bool run_nbench2(int dummy)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct cli_state *cli = NULL;
+ FILE *loadfile;
+ bool ret = false;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ loadfile = fopen("client.txt", "r");
+ if (loadfile == NULL) {
+ fprintf(stderr, "Could not open \"client.txt\": %s\n",
+ strerror(errno));
+ return false;
+ }
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ if (!torture_open_connection(&cli, 0)) {
+ goto fail;
+ }
+
+ req = nbench_send(talloc_tos(), ev, cli, "client1", loadfile,
+ NULL, NULL);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ goto fail;
+ }
+ status = nbench_recv(req);
+ TALLOC_FREE(req);
+ printf("nbench returned %s\n", nt_errstr(status));
+
+ ret = true;
+fail:
+ if (cli != NULL) {
+ torture_close_connection(cli);
+ }
+ TALLOC_FREE(ev);
+ if (loadfile != NULL) {
+ fclose(loadfile);
+ loadfile = NULL;
+ }
+ TALLOC_FREE(frame);
+ return ret;
+}
diff --git a/source3/torture/nbio.c b/source3/torture/nbio.c
new file mode 100644
index 0000000..4fedfc5
--- /dev/null
+++ b/source3/torture/nbio.c
@@ -0,0 +1,377 @@
+#define NBDEBUG 0
+
+/*
+ Unix SMB/CIFS implementation.
+ SMB torture tester
+ Copyright (C) Andrew Tridgell 1997-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+
+#define MAX_FILES 1000
+
+static char buf[70000];
+extern int line_count;
+extern int nbio_id;
+static int nprocs;
+static struct timeval nb_start;
+
+static struct {
+ int fd;
+ int handle;
+} ftable[MAX_FILES];
+
+static struct children {
+ double bytes_in, bytes_out;
+ int line;
+ int done;
+} *children;
+
+double nbio_total(void)
+{
+ int i;
+ double total = 0;
+ for (i=0;i<nprocs;i++) {
+ total += children[i].bytes_out + children[i].bytes_in;
+ }
+ return total;
+}
+
+void nb_alarm(int ignore)
+{
+ int i;
+ int lines=0, num_clients=0;
+ if (nbio_id != -1) return;
+
+ for (i=0;i<nprocs;i++) {
+ lines += children[i].line;
+ if (!children[i].done) num_clients++;
+ }
+
+ printf("%4d %8d %.2f MB/sec\r",
+ num_clients, lines/nprocs,
+ 1.0e-6 * nbio_total() / timeval_elapsed(&nb_start));
+
+ signal(SIGALRM, nb_alarm);
+ alarm(1);
+}
+
+void nbio_shmem(int n)
+{
+ nprocs = n;
+ children = (struct children *)anonymous_shared_allocate(sizeof(*children) * nprocs);
+ if (!children) {
+ printf("Failed to setup shared memory!\n");
+ exit(1);
+ }
+}
+
+#if 0
+static int ne_find_handle(int handle)
+{
+ int i;
+ children[nbio_id].line = line_count;
+ for (i=0;i<MAX_FILES;i++) {
+ if (ftable[i].handle == handle) return i;
+ }
+ return -1;
+}
+#endif
+
+static int find_handle(int handle)
+{
+ int i;
+ children[nbio_id].line = line_count;
+ for (i=0;i<MAX_FILES;i++) {
+ if (ftable[i].handle == handle) return i;
+ }
+ printf("(%d) ERROR: handle %d was not found\n",
+ line_count, handle);
+ exit(1);
+
+ return -1; /* Not reached */
+}
+
+
+static struct cli_state *c;
+
+static void sigsegv(int sig)
+{
+ char line[200];
+ printf("segv at line %d\n", line_count);
+ slprintf(line, sizeof(line), "/usr/X11R6/bin/xterm -e gdb /proc/%d/exe %d",
+ (int)getpid(), (int)getpid());
+ if (system(line) == -1) {
+ printf("system() failed\n");
+ }
+ exit(1);
+}
+
+void nb_setup(struct cli_state *cli)
+{
+ signal(SIGSEGV, sigsegv);
+ c = cli;
+ nb_start = timeval_current();
+ children[nbio_id].done = 0;
+}
+
+
+void nb_unlink(const char *fname)
+{
+ NTSTATUS status;
+
+ status = cli_unlink(c, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+#if NBDEBUG
+ printf("(%d) unlink %s failed (%s)\n",
+ line_count, fname, nt_errstr(status));
+#endif
+ }
+}
+
+
+void nb_createx(const char *fname,
+ unsigned create_options, unsigned create_disposition, int handle)
+{
+ uint16_t fd = (uint16_t)-1;
+ int i;
+ NTSTATUS status;
+ uint32_t desired_access;
+
+ if (create_options & FILE_DIRECTORY_FILE) {
+ desired_access = FILE_READ_DATA;
+ } else {
+ desired_access = FILE_READ_DATA | FILE_WRITE_DATA;
+ }
+
+ status = cli_ntcreate(c, fname, 0,
+ desired_access,
+ 0x0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ create_disposition,
+ create_options, 0, &fd, NULL);
+ if (!NT_STATUS_IS_OK(status) && handle != -1) {
+ printf("ERROR: cli_ntcreate failed for %s - %s\n",
+ fname, nt_errstr(status));
+ exit(1);
+ }
+ if (NT_STATUS_IS_OK(status) && handle == -1) {
+ printf("ERROR: cli_ntcreate succeeded for %s\n", fname);
+ exit(1);
+ }
+ if (fd == (uint16_t)-1) return;
+
+ for (i=0;i<MAX_FILES;i++) {
+ if (ftable[i].handle == 0) break;
+ }
+ if (i == MAX_FILES) {
+ printf("(%d) file table full for %s\n", line_count,
+ fname);
+ exit(1);
+ }
+ ftable[i].handle = handle;
+ ftable[i].fd = fd;
+}
+
+void nb_writex(int handle, int offset, int size, int ret_size)
+{
+ int i;
+ NTSTATUS status;
+
+ if (buf[0] == 0) memset(buf, 1, sizeof(buf));
+
+ i = find_handle(handle);
+ status = cli_writeall(c, ftable[i].fd, 0, (uint8_t *)buf, offset, size,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("(%d) ERROR: write failed on handle %d, fd %d "
+ "error %s\n", line_count, handle, ftable[i].fd,
+ nt_errstr(status));
+ exit(1);
+ }
+
+ children[nbio_id].bytes_out += ret_size;
+}
+
+void nb_readx(int handle, int offset, int size, int ret_size)
+{
+ int i;
+ NTSTATUS status;
+ size_t nread;
+
+ i = find_handle(handle);
+ status = cli_read(c, ftable[i].fd, buf, offset, size, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("(%d) ERROR: read failed on handle %d ofs=%d size=%d "
+ "fd %d nterror %s\n",
+ line_count, handle, offset, size,
+ ftable[i].fd, nt_errstr(status));
+ exit(1);
+ } else if (nread != ret_size) {
+ printf("(%d) ERROR: read failed on handle %d ofs=%d size=%d "
+ "nread=%lu ret_size=%d fd %d\n",
+ line_count, handle, offset, size, (unsigned long)nread,
+ ret_size, ftable[i].fd);
+ exit(1);
+ }
+ children[nbio_id].bytes_in += ret_size;
+}
+
+void nb_close(int handle)
+{
+ int i;
+ i = find_handle(handle);
+ if (!NT_STATUS_IS_OK(cli_close(c, ftable[i].fd))) {
+ printf("(%d) close failed on handle %d\n", line_count, handle);
+ exit(1);
+ }
+ ftable[i].handle = 0;
+}
+
+void nb_rmdir(const char *fname)
+{
+ NTSTATUS status;
+
+ status = cli_rmdir(c, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: rmdir %s failed (%s)\n",
+ fname, nt_errstr(status));
+ exit(1);
+ }
+}
+
+void nb_rename(const char *oldname, const char *newname)
+{
+ NTSTATUS status;
+
+ status = cli_rename(c, oldname, newname, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: rename %s %s failed (%s)\n",
+ oldname, newname, nt_errstr(status));
+ exit(1);
+ }
+}
+
+
+void nb_qpathinfo(const char *fname)
+{
+ cli_qpathinfo1(c, fname, NULL, NULL, NULL, NULL, NULL);
+}
+
+void nb_qfileinfo(int fnum)
+{
+ int i;
+ i = find_handle(fnum);
+ cli_qfileinfo_basic(c, ftable[i].fd, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL);
+}
+
+void nb_qfsinfo(int level)
+{
+ uint64_t bsize, total, avail;
+ /* this is not the right call - we need cli_qfsinfo() */
+ cli_disk_size(c, "", &bsize, &total, &avail);
+}
+
+static NTSTATUS find_fn(struct file_info *finfo, const char *name,
+ void *state)
+{
+ /* noop */
+ return NT_STATUS_OK;
+}
+
+void nb_findfirst(const char *mask)
+{
+ cli_list(c, mask, 0, find_fn, NULL);
+}
+
+void nb_flush(int fnum)
+{
+ int i;
+ i = find_handle(fnum);
+
+ cli_flush(NULL, c, i);
+}
+
+static int total_deleted;
+
+static NTSTATUS delete_fn(struct file_info *finfo,
+ const char *name, void *state)
+{
+ NTSTATUS status;
+ char *s, *n;
+ if (finfo->name[0] == '.') {
+ return NT_STATUS_OK;
+ }
+
+ n = SMB_STRDUP(name);
+ n[strlen(n)-1] = 0;
+ if (asprintf(&s, "%s%s", n, finfo->name) == -1) {
+ free(n);
+ printf("asprintf failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ char *s2;
+ if (asprintf(&s2, "%s\\*", s) == -1) {
+ printf("asprintf failed\n");
+ free(s);
+ free(n);
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = cli_list(c, s2, FILE_ATTRIBUTE_DIRECTORY, delete_fn, NULL);
+ free(s2);
+ if (!NT_STATUS_IS_OK(status)) {
+ free(s);
+ free(n);
+ return status;
+ }
+ nb_rmdir(s);
+ } else {
+ total_deleted++;
+ nb_unlink(s);
+ }
+ free(s);
+ free(n);
+ return NT_STATUS_OK;
+}
+
+void nb_deltree(const char *dname)
+{
+ char *mask;
+ if (asprintf(&mask, "%s\\*", dname) == -1) {
+ printf("asprintf failed\n");
+ return;
+ }
+
+ total_deleted = 0;
+ cli_list(c, mask, FILE_ATTRIBUTE_DIRECTORY, delete_fn, NULL);
+ free(mask);
+ cli_rmdir(c, dname);
+
+ if (total_deleted) printf("WARNING: Cleaned up %d files\n", total_deleted);
+}
+
+
+void nb_cleanup(void)
+{
+ cli_rmdir(c, "clients");
+ children[nbio_id].done = 1;
+}
diff --git a/source3/torture/pdbtest.c b/source3/torture/pdbtest.c
new file mode 100644
index 0000000..d153f3e
--- /dev/null
+++ b/source3/torture/pdbtest.c
@@ -0,0 +1,736 @@
+/*
+ Unix SMB/CIFS implementation.
+ passdb testing utility
+
+ Copyright (C) Wilco Baan Hofman 2006
+ Copyright (C) Jelmer Vernooij 2006
+ Copyright (C) Andrew Bartlett 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "passdb.h"
+
+#include "../librpc/gen_ndr/drsblobs.h"
+#include "../librpc/gen_ndr/ndr_drsblobs.h"
+#include "../libcli/security/dom_sid.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../auth/common_auth.h"
+#include "lib/tsocket/tsocket.h"
+#include "include/auth.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "auth/auth_sam_reply.h"
+
+#define TRUST_DOM "trustdom"
+#define TRUST_PWD "trustpwd1232"
+#define TRUST_SID "S-1-5-21-1111111111-2222222222-3333333333"
+
+static bool samu_correct(struct samu *s1, struct samu *s2)
+{
+ bool ret = True;
+ uint32_t s1_len, s2_len;
+ const char *s1_buf, *s2_buf;
+ const uint8_t *d1_buf, *d2_buf;
+ const struct dom_sid *s1_sid, *s2_sid;
+
+ /* Check Unix username */
+ s1_buf = pdb_get_username(s1);
+ s2_buf = pdb_get_username(s2);
+ if (s2_buf == NULL && s1_buf != NULL) {
+ DEBUG(0, ("Username is not set\n"));
+ ret = False;
+ } else if (s1_buf == NULL) {
+ /* Do nothing */
+ } else if (strcmp(s1_buf,s2_buf)) {
+ DEBUG(0, ("Username not written correctly, want %s, got \"%s\"\n",
+ pdb_get_username(s1),
+ pdb_get_username(s2)));
+ ret = False;
+ }
+
+ /* Check NT username */
+ s1_buf = pdb_get_nt_username(s1);
+ s2_buf = pdb_get_nt_username(s2);
+ if (s2_buf == NULL && s1_buf != NULL) {
+ DEBUG(0, ("NT Username is not set\n"));
+ ret = False;
+ } else if (s1_buf == NULL) {
+ /* Do nothing */
+ } else if (strcmp(s1_buf, s2_buf)) {
+ DEBUG(0, ("NT Username not written correctly, want \"%s\", got \"%s\"\n",
+ pdb_get_nt_username(s1),
+ pdb_get_nt_username(s2)));
+ ret = False;
+ }
+
+ /* Check acct ctrl */
+ if (pdb_get_acct_ctrl(s1) != pdb_get_acct_ctrl(s2)) {
+ DEBUG(0, ("Acct ctrl field not written correctly, want %d (0x%X), got %d (0x%X)\n",
+ pdb_get_acct_ctrl(s1),
+ pdb_get_acct_ctrl(s1),
+ pdb_get_acct_ctrl(s2),
+ pdb_get_acct_ctrl(s2)));
+ ret = False;
+ }
+
+ /* Check NT password */
+ d1_buf = pdb_get_nt_passwd(s1);
+ d2_buf = pdb_get_nt_passwd(s2);
+ if (d2_buf == NULL && d1_buf != NULL) {
+ DEBUG(0, ("NT password is not set\n"));
+ ret = False;
+ } else if (d1_buf == NULL) {
+ /* Do nothing */
+ } else if (memcmp(d1_buf, d2_buf, NT_HASH_LEN)) {
+ DEBUG(0, ("NT password not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check lanman password */
+ d1_buf = pdb_get_lanman_passwd(s1);
+ d2_buf = pdb_get_lanman_passwd(s2);
+ if (d2_buf == NULL && d1_buf != NULL) {
+ DEBUG(0, ("Lanman password is not set\n"));
+ } else if (d1_buf == NULL) {
+ /* Do nothing */
+ } else if (memcmp(d1_buf, d2_buf, NT_HASH_LEN)) {
+ DEBUG(0, ("Lanman password not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check password history */
+ d1_buf = pdb_get_pw_history(s1, &s1_len);
+ d2_buf = pdb_get_pw_history(s2, &s2_len);
+ if (d2_buf == NULL && d1_buf != NULL) {
+ DEBUG(0, ("Password history is not set\n"));
+ } else if (d1_buf == NULL) {
+ /* Do nothing */
+ } else if (s1_len != s2_len) {
+ DEBUG(0, ("Password history not written correctly, lengths differ, want %d, got %d\n",
+ s1_len, s2_len));
+ ret = False;
+ } else if (strncmp(s1_buf, s2_buf, s1_len)) {
+ DEBUG(0, ("Password history not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check logon time */
+ if (pdb_get_logon_time(s1) != pdb_get_logon_time(s2)) {
+ DEBUG(0, ("Logon time is not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check logoff time */
+ if (pdb_get_logoff_time(s1) != pdb_get_logoff_time(s2)) {
+ DEBUG(0, ("Logoff time is not written correctly: %s vs %s \n",
+ http_timestring(talloc_tos(), pdb_get_logoff_time(s1)),
+ http_timestring(talloc_tos(), pdb_get_logoff_time(s2))));
+ ret = False;
+ }
+
+ /* Check kickoff time */
+ if (pdb_get_kickoff_time(s1) != pdb_get_kickoff_time(s2)) {
+ DEBUG(0, ("Kickoff time is not written correctly: %s vs %s \n",
+ http_timestring(talloc_tos(), pdb_get_kickoff_time(s1)),
+ http_timestring(talloc_tos(), pdb_get_kickoff_time(s2))));
+ ret = False;
+ }
+
+ /* Check bad password time */
+ if (pdb_get_bad_password_time(s1) != pdb_get_bad_password_time(s2)) {
+ DEBUG(0, ("Bad password time is not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check password last set time */
+ if (pdb_get_pass_last_set_time(s1) != pdb_get_pass_last_set_time(s2)) {
+ DEBUG(0, ("Password last set time is not written correctly: %s vs %s \n",
+ http_timestring(talloc_tos(), pdb_get_pass_last_set_time(s1)),
+ http_timestring(talloc_tos(), pdb_get_pass_last_set_time(s2))));
+ ret = False;
+ }
+
+ /* Check password can change time */
+ if (pdb_get_pass_can_change_time(s1) != pdb_get_pass_can_change_time(s2)) {
+ DEBUG(0, ("Password can change time is not written correctly %s vs %s \n",
+ http_timestring(talloc_tos(), pdb_get_pass_can_change_time(s1)),
+ http_timestring(talloc_tos(), pdb_get_pass_can_change_time(s2))));
+ ret = False;
+ }
+
+ /* Check password must change time */
+ if (pdb_get_pass_must_change_time(s1) != pdb_get_pass_must_change_time(s2)) {
+ DEBUG(0, ("Password must change time is not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check logon divs */
+ if (pdb_get_logon_divs(s1) != pdb_get_logon_divs(s2)) {
+ DEBUG(0, ("Logon divs not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check logon hours */
+ if (pdb_get_hours_len(s1) != pdb_get_hours_len(s2)) {
+ DEBUG(0, ("Logon hours length not written correctly\n"));
+ ret = False;
+ } else if (pdb_get_hours_len(s1) != 0) {
+ d1_buf = pdb_get_hours(s1);
+ d2_buf = pdb_get_hours(s2);
+ if (d2_buf == NULL && d1_buf != NULL) {
+ DEBUG(0, ("Logon hours is not set\n"));
+ ret = False;
+ } else if (d1_buf == NULL) {
+ /* Do nothing */
+ } else if (memcmp(d1_buf, d2_buf, MAX_HOURS_LEN)) {
+ DEBUG(0, ("Logon hours is not written correctly\n"));
+ ret = False;
+ }
+ }
+
+ /* Check profile path */
+ s1_buf = pdb_get_profile_path(s1);
+ s2_buf = pdb_get_profile_path(s2);
+ if (s2_buf == NULL && s1_buf != NULL) {
+ DEBUG(0, ("Profile path is not set\n"));
+ ret = False;
+ } else if (s1_buf == NULL) {
+ /* Do nothing */
+ } else if (strcmp(s1_buf, s2_buf)) {
+ DEBUG(0, ("Profile path is not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check home dir */
+ s1_buf = pdb_get_homedir(s1);
+ s2_buf = pdb_get_homedir(s2);
+ if (s2_buf == NULL && s1_buf != NULL) {
+ DEBUG(0, ("Home dir is not set\n"));
+ ret = False;
+ } else if (s1_buf == NULL) {
+ /* Do nothing */
+ } else if (strcmp(s1_buf, s2_buf)) {
+ DEBUG(0, ("Home dir is not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check logon script */
+ s1_buf = pdb_get_logon_script(s1);
+ s2_buf = pdb_get_logon_script(s2);
+ if (s2_buf == NULL && s1_buf != NULL) {
+ DEBUG(0, ("Logon script not set\n"));
+ ret = False;
+ } else if (s1_buf == NULL) {
+ /* Do nothing */
+ } else if (strcmp(s1_buf, s2_buf)) {
+ DEBUG(0, ("Logon script is not written correctly\n"));
+ ret = False;
+ }
+
+ /* Check user and group sids */
+ s1_sid = pdb_get_user_sid(s1);
+ s2_sid = pdb_get_user_sid(s2);
+ if (s2_sid == NULL && s1_sid != NULL) {
+ DEBUG(0, ("USER SID not set\n"));
+ ret = False;
+ } else if (s1_sid == NULL) {
+ /* Do nothing */
+ } else if (!dom_sid_equal(s1_sid, s2_sid)) {
+ DEBUG(0, ("USER SID is not written correctly\n"));
+ ret = False;
+ }
+
+ return ret;
+}
+
+static bool test_auth(TALLOC_CTX *mem_ctx, struct samu *pdb_entry)
+{
+ struct auth_usersupplied_info *user_info;
+ struct auth_context *auth_context;
+ static const uint8_t challenge_8[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+ DATA_BLOB challenge = data_blob_const(challenge_8, sizeof(challenge_8));
+ struct tsocket_address *remote_address;
+ struct tsocket_address *local_address;
+ unsigned char local_nt_response[24];
+ DATA_BLOB nt_resp = data_blob_const(local_nt_response, sizeof(local_nt_response));
+ unsigned char local_nt_session_key[16];
+ struct netr_SamInfo3 *info3_sam, *info3_auth;
+ struct auth_serversupplied_info *server_info;
+ struct wbcAuthUserParams params = { .flags = 0 };
+ struct wbcAuthUserInfo *info = NULL;
+ struct wbcAuthErrorInfo *err = NULL;
+ wbcErr wbc_status;
+ struct netr_SamInfo6 *info6_wbc = NULL;
+ NTSTATUS status;
+ bool ok;
+ uint8_t authoritative = 1;
+ int rc;
+
+ rc = SMBOWFencrypt(pdb_get_nt_passwd(pdb_entry), challenge_8,
+ local_nt_response);
+ if (rc != 0) {
+ return False;
+ }
+
+ SMBsesskeygen_ntv1(pdb_get_nt_passwd(pdb_entry), local_nt_session_key);
+
+ if (tsocket_address_inet_from_strings(NULL, "ip", NULL, 0, &remote_address) != 0) {
+ return False;
+ }
+
+ if (tsocket_address_inet_from_strings(NULL, "ip", NULL, 0, &local_address) != 0) {
+ return False;
+ }
+
+ status = make_user_info(mem_ctx,
+ &user_info, pdb_get_username(pdb_entry), pdb_get_username(pdb_entry),
+ pdb_get_domain(pdb_entry), pdb_get_domain(pdb_entry), lp_netbios_name(),
+ remote_address,local_address, "pdbtest",
+ NULL, &nt_resp, NULL, NULL, NULL,
+ AUTH_PASSWORD_RESPONSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to test authentication with check_sam_security_info3: %s\n", nt_errstr(status)));
+ return False;
+ }
+
+ status = check_sam_security_info3(&challenge, NULL, user_info, &info3_sam);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to test authentication with check_sam_security_info3: %s\n", nt_errstr(status)));
+ return False;
+ }
+
+ if (memcmp(info3_sam->base.key.key, local_nt_session_key, 16) != 0) {
+ DEBUG(0, ("Returned NT session key is incorrect\n"));
+ return False;
+ }
+
+ status = make_auth3_context_for_ntlm(NULL, &auth_context);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to test authentication with check_sam_security_info3: %s\n", nt_errstr(status)));
+ return False;
+ }
+
+ ok = auth3_context_set_challenge(
+ auth_context, challenge.data, "fixed");
+ if (!ok) {
+ DBG_ERR("auth3_context_set_challenge failed\n");
+ return false;
+ }
+
+ status = auth_check_ntlm_password(mem_ctx,
+ auth_context,
+ user_info,
+ &server_info,
+ &authoritative);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to test authentication with auth module: "
+ "%s authoritative[%u].\n",
+ nt_errstr(status), authoritative));
+ return False;
+ }
+
+ info3_auth = talloc_zero(mem_ctx, struct netr_SamInfo3);
+ if (info3_auth == NULL) {
+ return False;
+ }
+
+ status = serverinfo_to_SamInfo3(server_info, info3_auth);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("serverinfo_to_SamInfo3 failed: %s\n",
+ nt_errstr(status)));
+ return False;
+ }
+
+ if (memcmp(info3_auth->base.key.key, local_nt_session_key, 16) != 0) {
+ DEBUG(0, ("Returned NT session key is incorrect\n"));
+ return False;
+ }
+
+ if (!dom_sid_equal(info3_sam->base.domain_sid, info3_auth->base.domain_sid)) {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(0, ("domain_sid in SAM info3 %s does not match domain_sid in AUTH info3 %s\n",
+ dom_sid_str_buf(info3_sam->base.domain_sid, &buf1),
+ dom_sid_str_buf(info3_auth->base.domain_sid,
+ &buf2)));
+ return False;
+ }
+
+ /* TODO:
+ * Compare more details from the two info3 structures,
+ * then test that an expired/disabled/pwdmustchange account
+ * returns the correct errors
+ */
+
+ params.parameter_control = user_info->logon_parameters;
+ params.parameter_control |= WBC_MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT |
+ WBC_MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT;
+ params.level = WBC_AUTH_USER_LEVEL_RESPONSE;
+
+ params.account_name = user_info->client.account_name;
+ params.domain_name = user_info->client.domain_name;
+ params.workstation_name = user_info->workstation_name;
+
+ memcpy(params.password.response.challenge,
+ challenge.data,
+ sizeof(params.password.response.challenge));
+
+ params.password.response.lm_length =
+ user_info->password.response.lanman.length;
+ params.password.response.nt_length =
+ user_info->password.response.nt.length;
+
+ params.password.response.lm_data =
+ user_info->password.response.lanman.data;
+ params.password.response.nt_data =
+ user_info->password.response.nt.data;
+
+ wbc_status = wbcAuthenticateUserEx(&params, &info, &err);
+ if (wbc_status != WBC_ERR_WINBIND_NOT_AVAILABLE) {
+ if (wbc_status == WBC_ERR_AUTH_ERROR) {
+ if (err) {
+ DEBUG(1, ("error was %s (0x%08x)\nerror message was '%s'\n",
+ err->nt_string, err->nt_status, err->display_string));
+ status = NT_STATUS(err->nt_status);
+ wbcFreeMemory(err);
+ } else {
+ status = NT_STATUS_LOGON_FAILURE;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+ } else if (!WBC_ERROR_IS_OK(wbc_status)) {
+ DEBUG(1, ("wbcAuthenticateUserEx: failed with %u - %s\n",
+ wbc_status, wbcErrorString(wbc_status)));
+ if (err) {
+ DEBUG(1, ("error was %s (0x%08x)\nerror message was '%s'\n",
+ err->nt_string, err->nt_status, err->display_string));
+ }
+ return false;
+ }
+ info6_wbc = wbcAuthUserInfo_to_netr_SamInfo6(mem_ctx, info);
+ wbcFreeMemory(info);
+ if (!info6_wbc) {
+ DEBUG(1, ("wbcAuthUserInfo_to_netr_SamInfo6 failed\n"));
+ return false;
+ }
+
+ if (memcmp(info6_wbc->base.key.key, local_nt_session_key, 16) != 0) {
+ DEBUG(0, ("Returned NT session key is incorrect\n"));
+ return false;
+ }
+
+ if (!dom_sid_equal(info3_sam->base.domain_sid, info6_wbc->base.domain_sid)) {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(0, ("domain_sid in SAM info3 %s does not match domain_sid in AUTH info3 %s\n",
+ dom_sid_str_buf(info3_sam->base.domain_sid,
+ &buf1),
+ dom_sid_str_buf(info6_wbc->base.domain_sid,
+ &buf2)));
+ return false;
+ }
+ }
+
+ return True;
+}
+
+static bool test_trusted_domains(TALLOC_CTX *ctx,
+ struct pdb_methods *pdb,
+ bool *error)
+{
+ NTSTATUS rv;
+ /* test trustdom calls */
+ struct pdb_trusted_domain *td;
+ struct pdb_trusted_domain *new_td;
+ struct trustAuthInOutBlob taiob;
+ struct AuthenticationInformation aia;
+ enum ndr_err_code ndr_err;
+ bool ok;
+
+ td = talloc_zero(ctx ,struct pdb_trusted_domain);
+ if (!td) {
+ fprintf(stderr, "talloc failed\n");
+ return false;
+ }
+
+ td->domain_name = talloc_strdup(td, TRUST_DOM);
+ td->netbios_name = talloc_strdup(td, TRUST_DOM);
+ if (!td->domain_name || !td->netbios_name) {
+ fprintf(stderr, "talloc failed\n");
+ return false;
+ }
+ ok = dom_sid_parse("S-1-5-21-123-456-789", &td->security_identifier);
+ if (!ok) {
+ fprintf(stderr, "dom_sid_parse S-1-5-21-123-456-789 failed\n");
+ return false;
+ }
+
+ td->trust_auth_incoming = data_blob_null;
+
+ ZERO_STRUCT(taiob);
+ ZERO_STRUCT(aia);
+ taiob.count = 1;
+ taiob.current.count = 1;
+ taiob.current.array = &aia;
+ unix_to_nt_time(&aia.LastUpdateTime, time(NULL));
+ aia.AuthType = TRUST_AUTH_TYPE_CLEAR;
+ aia.AuthInfo.clear.password = (uint8_t *) talloc_strdup(ctx, TRUST_PWD);
+ aia.AuthInfo.clear.size = strlen(TRUST_PWD);
+
+ taiob.previous.count = 0;
+ taiob.previous.array = NULL;
+
+ ndr_err = ndr_push_struct_blob(&td->trust_auth_outgoing,
+ td, &taiob,
+ (ndr_push_flags_fn_t) ndr_push_trustAuthInOutBlob);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ fprintf(stderr, "ndr_push_struct_blob failed.\n");
+ return false;
+ }
+
+ td->trust_direction = LSA_TRUST_DIRECTION_OUTBOUND;
+ td->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+ td->trust_attributes = 0;
+ td->trust_forest_trust_info = data_blob_null;
+
+ rv = pdb->set_trusted_domain(pdb, TRUST_DOM, td);
+ if (!NT_STATUS_IS_OK(rv)) {
+ fprintf(stderr, "Error in set_trusted_domain %s\n",
+ get_friendly_nt_error_msg(rv));
+ *error = true;
+ }
+
+ rv = pdb->get_trusted_domain(pdb, ctx, TRUST_DOM, &new_td);
+ if (!NT_STATUS_IS_OK(rv)) {
+ fprintf(stderr, "Error in get_trusted_domain %s\n",
+ get_friendly_nt_error_msg(rv));
+ *error = true;
+ }
+
+ if (!strequal(td->domain_name, new_td->domain_name) ||
+ !strequal(td->netbios_name, new_td->netbios_name) ||
+ !dom_sid_equal(&td->security_identifier,
+ &new_td->security_identifier) ||
+ td->trust_direction != new_td->trust_direction ||
+ td->trust_type != new_td->trust_type ||
+ td->trust_attributes != new_td->trust_attributes ||
+ td->trust_auth_incoming.length != new_td->trust_auth_incoming.length ||
+ td->trust_forest_trust_info.length != new_td->trust_forest_trust_info.length ||
+ data_blob_cmp(&td->trust_auth_outgoing, &new_td->trust_auth_outgoing) != 0) {
+ fprintf(stderr, "Old and new trusdet domain data do not match\n");
+ *error = true;
+ }
+
+ rv = pdb->del_trusted_domain(pdb, TRUST_DOM);
+ if (!NT_STATUS_IS_OK(rv)) {
+ fprintf(stderr, "Error in del_trusted_domain %s\n",
+ get_friendly_nt_error_msg(rv));
+ *error = true;
+ }
+
+ return true;
+}
+
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *ctx;
+ struct samu *out = NULL;
+ struct samu *in = NULL;
+ NTSTATUS rv;
+ int i;
+ int opt;
+ struct timeval tv;
+ bool error = False;
+ struct passwd *pwd;
+ uint8_t *buf;
+ uint32_t expire, min_age, history;
+ struct pdb_methods *pdb;
+ poptContext pc;
+ static const char *backend = NULL;
+ static const char *unix_user = "nobody";
+ bool ok;
+ struct poptOption long_options[] = {
+ {"username", 'u', POPT_ARG_STRING, &unix_user, 0, "Unix user to use for testing", "USERNAME" },
+ {"backend", 'b', POPT_ARG_STRING, &backend, 0, "Backend to use if not default", "BACKEND[:SETTINGS]" },
+ POPT_AUTOHELP
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ ctx = talloc_stackframe();
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ true /* require_smbconf */);
+ if (!ok) {
+ TALLOC_FREE(ctx);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context(getprogname(), argc, argv, long_options, 0);
+ if (pc == NULL) {
+ TALLOC_FREE(ctx);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "backend[:settings] username");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptFreeContext(pc);
+
+ if (backend == NULL) {
+ backend = lp_passdb_backend();
+ }
+
+ rv = make_pdb_method_name(&pdb, backend);
+ if (NT_STATUS_IS_ERR(rv)) {
+ fprintf(stderr, "Error initializing '%s': %s\n", backend, get_friendly_nt_error_msg(rv));
+ exit(1);
+ }
+
+ if (!(out = samu_new(ctx))) {
+ fprintf(stderr, "Can't create samu structure.\n");
+ exit(1);
+ }
+
+ if ((pwd = Get_Pwnam_alloc(ctx, unix_user)) == NULL) {
+ fprintf(stderr, "Error getting user information for %s\n", unix_user);
+ exit(1);
+ }
+
+ samu_set_unix(out, pwd);
+
+ pdb_set_profile_path(out, "\\\\torture\\profile", PDB_SET);
+ pdb_set_homedir(out, "\\\\torture\\home", PDB_SET);
+ pdb_set_logon_script(out, "torture_script.cmd", PDB_SET);
+
+ pdb_set_acct_ctrl(out, ACB_NORMAL, PDB_SET);
+
+ pdb_get_account_policy(PDB_POLICY_PASSWORD_HISTORY, &history);
+ if (history * PW_HISTORY_ENTRY_LEN < NT_HASH_LEN) {
+ buf = (uint8_t *)TALLOC(ctx, NT_HASH_LEN);
+ } else {
+ buf = (uint8_t *)TALLOC(ctx, history * PW_HISTORY_ENTRY_LEN);
+ }
+
+ /* Generate some random hashes */
+ GetTimeOfDay(&tv);
+ srand(tv.tv_usec);
+ for (i = 0; i < NT_HASH_LEN; i++) {
+ buf[i] = (uint8_t) rand();
+ }
+ pdb_set_nt_passwd(out, buf, PDB_SET);
+ for (i = 0; i < LM_HASH_LEN; i++) {
+ buf[i] = (uint8_t) rand();
+ }
+ pdb_set_lanman_passwd(out, buf, PDB_SET);
+ for (i = 0; i < history * PW_HISTORY_ENTRY_LEN; i++) {
+ buf[i] = (uint8_t) rand();
+ }
+ pdb_set_pw_history(out, buf, history, PDB_SET);
+
+ pdb_get_account_policy(PDB_POLICY_MAX_PASSWORD_AGE, &expire);
+ pdb_get_account_policy(PDB_POLICY_MIN_PASSWORD_AGE, &min_age);
+ pdb_set_pass_last_set_time(out, time(NULL), PDB_SET);
+
+ if (min_age == (uint32_t)-1) {
+ pdb_set_pass_can_change_time(out, 0, PDB_SET);
+ } else {
+ pdb_set_pass_can_change_time(out, time(NULL)+min_age, PDB_SET);
+ }
+
+ pdb_set_logon_time(out, time(NULL)-3600, PDB_SET);
+
+ pdb_set_logoff_time(out, time(NULL), PDB_SET);
+
+ pdb_set_kickoff_time(out, time(NULL)+3600, PDB_SET);
+
+ /* Create account */
+ if (!NT_STATUS_IS_OK(rv = pdb->add_sam_account(pdb, out))) {
+ fprintf(stderr, "Error in add_sam_account: %s\n",
+ get_friendly_nt_error_msg(rv));
+ exit(1);
+ }
+
+ if (!(in = samu_new(ctx))) {
+ fprintf(stderr, "Can't create samu structure.\n");
+ exit(1);
+ }
+
+ /* Get account information through getsampwnam() */
+ rv = pdb->getsampwnam(pdb, in, out->username);
+ if (NT_STATUS_IS_ERR(rv)) {
+ fprintf(stderr, "Error getting sampw of added user %s: %s\n",
+ out->username, nt_errstr(rv));
+ if (!NT_STATUS_IS_OK(rv = pdb->delete_sam_account(pdb, out))) {
+ fprintf(stderr, "Error in delete_sam_account %s\n",
+ get_friendly_nt_error_msg(rv));
+ }
+ TALLOC_FREE(ctx);
+ exit(1);
+ }
+
+ /* Verify integrity */
+ if (samu_correct(out, in)) {
+ printf("User info written correctly\n");
+ } else {
+ printf("User info NOT written correctly\n");
+ error = True;
+ }
+
+ if (test_auth(ctx, out)) {
+ printf("Authentication module test passed\n");
+ } else {
+ printf("Authentication module test failed!\n");
+ error = True;
+ }
+
+
+ /* Delete account */
+ if (!NT_STATUS_IS_OK(rv = pdb->delete_sam_account(pdb, out))) {
+ fprintf(stderr, "Error in delete_sam_account %s\n",
+ get_friendly_nt_error_msg(rv));
+ }
+
+ if (pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX) {
+ if (!test_trusted_domains(ctx, pdb, &error)) {
+ fprintf(stderr, "failed testing trusted domains.\n");
+ exit(1);
+ }
+ }
+
+ TALLOC_FREE(ctx);
+
+ if (error) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/source3/torture/proto.h b/source3/torture/proto.h
new file mode 100644
index 0000000..26b5120
--- /dev/null
+++ b/source3/torture/proto.h
@@ -0,0 +1,192 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ SMB torture tester - header file
+
+ Copyright (C) Andrew Tridgell 1997-1998
+ Copyright (C) Jeremy Allison 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __TORTURE_H__
+#define __TORTURE_H__
+
+struct cli_state;
+
+/* The following definitions come from torture/denytest.c */
+
+bool torture_denytest1(int dummy);
+bool torture_denytest2(int dummy);
+
+/* The following definitions come from torture/mangle_test.c */
+
+bool torture_mangle(int dummy);
+
+/* The following definitions come from torture/nbio.c */
+
+double nbio_total(void);
+void nb_alarm(int ignore);
+void nbio_shmem(int n);
+void nb_setup(struct cli_state *cli);
+void nb_unlink(const char *fname);
+void nb_createx(const char *fname,
+ unsigned create_options, unsigned create_disposition, int handle);
+void nb_writex(int handle, int offset, int size, int ret_size);
+void nb_readx(int handle, int offset, int size, int ret_size);
+void nb_close(int handle);
+void nb_rmdir(const char *fname);
+void nb_rename(const char *oldname, const char *newname);
+void nb_qpathinfo(const char *fname);
+void nb_qfileinfo(int fnum);
+void nb_qfsinfo(int level);
+void nb_findfirst(const char *mask);
+void nb_flush(int fnum);
+void nb_deltree(const char *dname);
+void nb_cleanup(void);
+
+/* The following definitions come from torture/scanner.c */
+
+bool torture_trans2_scan(int dummy);
+bool torture_nttrans_scan(int dummy);
+
+/* The following definitions come from torture/torture.c */
+
+bool smbcli_parse_unc(const char *unc_name, TALLOC_CTX *mem_ctx,
+ char **hostname, char **sharename);
+bool torture_open_connection_flags(struct cli_state **c, int conn_index, int flags);
+bool torture_open_connection(struct cli_state **c, int conn_index);
+bool torture_init_connection(struct cli_state **pcli);
+bool torture_cli_session_setup2(struct cli_state *cli, uint16_t *new_vuid);
+bool torture_close_connection(struct cli_state *c);
+bool torture_ioctl_test(int dummy);
+bool torture_chkpath_test(int dummy);
+NTSTATUS torture_setup_unix_extensions(struct cli_state *cli);
+void torture_conn_set_sockopt(struct cli_state *cli);
+void torture_deltree(struct cli_state *cli, const char *dname);
+
+NTSTATUS cli_qpathinfo1(struct cli_state *cli,
+ const char *fname,
+ time_t *change_time,
+ time_t *access_time,
+ time_t *write_time,
+ off_t *size,
+ uint32_t *pattr);
+
+/* The following definitions come from torture/utable.c */
+
+bool torture_utable(int dummy);
+bool torture_casetable(int dummy);
+
+/*
+ * Misc
+ */
+
+bool run_posix_append(int dummy);
+bool run_posix_ls_wildcard_test(int dummy);
+bool run_posix_ls_single_test(int dummy);
+bool run_posix_readlink_test(int dummy);
+bool run_posix_stat_test(int dummy);
+bool run_posix_symlink_parent_test(int dummy);
+bool run_posix_symlink_chmod_test(int dummy);
+bool run_posix_dir_default_acl_test(int dummy);
+bool run_case_insensitive_create(int dummy);
+bool run_posix_symlink_rename_test(int dummy);
+bool run_posix_symlink_getpathinfo_test(int dummy);
+bool run_posix_symlink_setpathinfo_test(int dummy);
+
+bool run_nbench2(int dummy);
+bool run_async_echo(int dummy);
+bool run_smb_any_connect(int dummy);
+bool run_addrchange(int dummy);
+bool run_str_match_mswild(int dummy);
+bool run_str_match_regex_sub1(int dummy);
+bool run_notify_online(int dummy);
+bool run_nttrans_create(int dummy);
+bool run_nttrans_fsctl(int dummy);
+bool run_smb2_basic(int dummy);
+bool run_smb2_negprot(int dummy);
+bool run_smb2_anonymous(int dummy);
+bool run_smb2_session_reconnect(int dummy);
+bool run_smb2_tcon_dependence(int dummy);
+bool run_smb2_multi_channel(int dummy);
+bool run_smb2_session_reauth(int dummy);
+bool run_smb2_ftruncate(int dummy);
+bool run_smb2_dir_fsync(int dummy);
+bool run_smb2_path_slash(int dummy);
+bool run_smb2_sacl(int dummy);
+bool run_smb2_quota1(int dummy);
+bool run_smb2_stream_acl(int dummy);
+bool run_smb2_dfs_paths(int dummy);
+bool run_smb2_non_dfs_share(int dummy);
+bool run_smb2_dfs_share_non_dfs_path(int dummy);
+bool run_smb2_dfs_filename_leading_backslash(int dummy);
+bool run_smb2_pipe_read_async_disconnect(int dummy);
+bool run_smb2_invalid_pipename(int dummy);
+bool run_smb1_dfs_paths(int dummy);
+bool run_smb1_dfs_search_paths(int dummy);
+bool run_smb1_dfs_operations(int dummy);
+bool run_smb1_dfs_check_badpath(int dummy);
+bool run_list_dir_async_test(int dummy);
+bool run_delete_on_close_non_empty(int dummy);
+bool run_delete_on_close_nonwrite_delete_yes_test(int dummy);
+bool run_delete_on_close_nonwrite_delete_no_test(int dummy);
+bool run_chain3(int dummy);
+bool run_local_conv_auth_info(int dummy);
+bool run_local_sprintf_append(int dummy);
+bool run_cleanup1(int dummy);
+bool run_cleanup2(int dummy);
+bool run_cleanup4(int dummy);
+bool run_notify_bench2(int dummy);
+bool run_notify_bench3(int dummy);
+bool run_dbwrap_watch1(int dummy);
+bool run_dbwrap_watch2(int dummy);
+bool run_dbwrap_watch3(int dummy);
+bool run_dbwrap_watch4(int dummy);
+bool run_dbwrap_do_locked1(int dummy);
+bool run_idmap_tdb_common_test(int dummy);
+bool run_local_dbwrap_ctdb1(int dummy);
+bool run_qpathinfo_bufsize(int dummy);
+bool run_bench_pthreadpool(int dummy);
+bool run_messaging_read1(int dummy);
+bool run_messaging_read2(int dummy);
+bool run_messaging_read3(int dummy);
+bool run_messaging_read4(int dummy);
+bool run_messaging_fdpass1(int dummy);
+bool run_messaging_fdpass2(int dummy);
+bool run_messaging_fdpass2a(int dummy);
+bool run_messaging_fdpass2b(int dummy);
+bool run_messaging_send_all(int dummy);
+bool run_oplock_cancel(int dummy);
+bool run_pthreadpool_tevent(int dummy);
+bool run_g_lock1(int dummy);
+bool run_g_lock2(int dummy);
+bool run_g_lock3(int dummy);
+bool run_g_lock4(int dummy);
+bool run_g_lock4a(int dummy);
+bool run_g_lock5(int dummy);
+bool run_g_lock6(int dummy);
+bool run_g_lock7(int dummy);
+bool run_g_lock8(int dummy);
+bool run_g_lock_ping_pong(int dummy);
+bool run_local_namemap_cache1(int dummy);
+bool run_local_idmap_cache1(int dummy);
+bool run_hidenewfiles(int dummy);
+bool run_hidenewfiles_showdirs(int dummy);
+bool run_readdir_timestamp(int dummy);
+bool run_ctdbd_conn1(int dummy);
+bool run_rpc_scale(int dummy);
+bool run_tdb_validate(int dummy);
+
+#endif /* __TORTURE_H__ */
diff --git a/source3/torture/scanner.c b/source3/torture/scanner.c
new file mode 100644
index 0000000..ad44f33
--- /dev/null
+++ b/source3/torture/scanner.c
@@ -0,0 +1,515 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB torture tester - scanning functions
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+#define VERBOSE 0
+#define OP_MIN 0
+#define OP_MAX 20
+
+#define DATA_SIZE 1024
+#define PARAM_SIZE 1024
+
+/****************************************************************************
+look for a partial hit
+****************************************************************************/
+static void trans2_check_hit(const char *format, int op, int level, NTSTATUS status)
+{
+ if (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_INVALID_LEVEL) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_NOT_IMPLEMENTED) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_NOT_SUPPORTED) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_UNSUCCESSFUL) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_INVALID_INFO_CLASS)) {
+ return;
+ }
+#if VERBOSE
+ printf("possible %s hit op=%3d level=%5d status=%s\n",
+ format, op, level, nt_errstr(status));
+#endif
+}
+
+/****************************************************************************
+check for existence of a trans2 call
+****************************************************************************/
+static NTSTATUS try_trans2(struct cli_state *cli,
+ int op,
+ uint8_t *param, uint8_t *data,
+ uint32_t param_len, uint32_t data_len,
+ uint32_t *rparam_len, uint32_t *rdata_len)
+{
+ uint16_t setup[1];
+ uint8_t *rparam=NULL, *rdata=NULL;
+ NTSTATUS status;
+
+ SSVAL(setup+0, 0, op);
+
+ status = cli_trans(talloc_tos(), cli, SMBtrans2,
+ NULL, -1, /* name, fid */
+ op, 0,
+ NULL, 0, 0, /* setup */
+ param, param_len, 2,
+ data, data_len, CLI_BUFFER_SIZE,
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ &rparam, 0, rparam_len,
+ &rdata, 0, rdata_len);
+
+ TALLOC_FREE(rdata);
+ TALLOC_FREE(rparam);
+
+ return status;
+}
+
+
+static NTSTATUS try_trans2_len(struct cli_state *cli,
+ const char *format,
+ int op, int level,
+ uint8_t *param, uint8_t *data,
+ uint32_t param_len, uint32_t *data_len,
+ uint32_t *rparam_len, uint32_t *rdata_len)
+{
+ NTSTATUS ret=NT_STATUS_OK;
+
+ ret = try_trans2(cli, op, param, data, param_len,
+ DATA_SIZE, rparam_len, rdata_len);
+#if VERBOSE
+ printf("op=%d level=%d ret=%s\n", op, level, nt_errstr(ret));
+#endif
+ if (!NT_STATUS_IS_OK(ret)) return ret;
+
+ *data_len = 0;
+ while (*data_len < DATA_SIZE) {
+ ret = try_trans2(cli, op, param, data, param_len,
+ *data_len, rparam_len, rdata_len);
+ if (NT_STATUS_IS_OK(ret)) break;
+ *data_len += 2;
+ }
+ if (NT_STATUS_IS_OK(ret)) {
+ printf("found %s level=%d data_len=%d rparam_len=%d rdata_len=%d\n",
+ format, level, *data_len, *rparam_len, *rdata_len);
+ } else {
+ trans2_check_hit(format, op, level, ret);
+ }
+ return ret;
+}
+
+/****************************************************************************
+check for existence of a trans2 call
+****************************************************************************/
+static bool scan_trans2(struct cli_state *cli, int op, int level,
+ int fnum, int dnum, const char *fname)
+{
+ uint32_t data_len = 0;
+ uint32_t param_len = 0;
+ uint32_t rparam_len, rdata_len;
+ uint8_t *param = NULL;
+ uint8_t data[DATA_SIZE];
+ const char *newfname;
+ const char *dname;
+ NTSTATUS status;
+
+ memset(data, 0, sizeof(data));
+ data_len = 4;
+
+ /* try with a info level only */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 2);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+
+ param_len = talloc_get_size(param);
+ status = try_trans2_len(cli, "void", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try with a file descriptor */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, fnum);
+ SSVAL(param, 2, level);
+ SSVAL(param, 4, 0);
+
+ param_len = talloc_get_size(param);
+ status = try_trans2_len(cli, "fnum", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+
+ /* try with a notify style */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, dnum);
+ SSVAL(param, 2, dnum);
+ SSVAL(param, 4, level);
+
+ param_len = talloc_get_size(param);
+ status = try_trans2_len(cli, "notify", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try with a file name */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+ SSVAL(param, 2, 0);
+ SSVAL(param, 4, 0);
+ param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
+ fname, strlen(fname)+1, NULL);
+ if (param == NULL) return True;
+
+ param_len = talloc_get_size(param);
+ status = try_trans2_len(cli, "fname", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try with a new file name */
+ newfname = "\\newfile.dat";
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+ SSVAL(param, 2, 0);
+ SSVAL(param, 4, 0);
+ param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
+ newfname, strlen(newfname)+1, NULL);
+ if (param == NULL) return True;
+
+ param_len = talloc_get_size(param);
+ status = try_trans2_len(cli, "newfile", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ cli_unlink(cli, newfname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, newfname);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try dfs style */
+ dname = "\\testdir";
+ cli_mkdir(cli, dname);
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 2);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+ param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
+ dname, strlen(dname)+1, NULL);
+ if (param == NULL) return True;
+
+ param_len = talloc_get_size(param);
+ status = try_trans2_len(cli, "dfs", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ cli_rmdir(cli, dname);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ return False;
+}
+
+
+bool torture_trans2_scan(int dummy)
+{
+ static struct cli_state *cli;
+ int op, level;
+ const char *fname = "\\scanner.dat";
+ uint16_t fnum, dnum;
+
+ printf("starting trans2 scan test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ if (!NT_STATUS_IS_OK(cli_openx(cli, fname, O_RDWR | O_CREAT | O_TRUNC,
+ DENY_NONE, &fnum))) {
+ printf("open of %s failed\n", fname);
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(cli_openx(cli, "\\", O_RDONLY, DENY_NONE, &dnum))) {
+ printf("open of \\ failed\n");
+ return false;
+ }
+
+ for (op=OP_MIN; op<=OP_MAX; op++) {
+ printf("Scanning op=%d\n", op);
+ for (level = 0; level <= 50; level++) {
+ scan_trans2(cli, op, level, fnum, dnum, fname);
+ }
+
+ for (level = 0x100; level <= 0x130; level++) {
+ scan_trans2(cli, op, level, fnum, dnum, fname);
+ }
+
+ for (level = 1000; level < 1050; level++) {
+ scan_trans2(cli, op, level, fnum, dnum, fname);
+ }
+ }
+
+ torture_close_connection(cli);
+
+ printf("trans2 scan finished\n");
+ return True;
+}
+
+
+
+
+/****************************************************************************
+look for a partial hit
+****************************************************************************/
+static void nttrans_check_hit(const char *format, int op, int level, NTSTATUS status)
+{
+ if (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_INVALID_LEVEL) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_NOT_IMPLEMENTED) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_NOT_SUPPORTED) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_UNSUCCESSFUL) ||
+ NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_INVALID_INFO_CLASS)) {
+ return;
+ }
+#if VERBOSE
+ printf("possible %s hit op=%3d level=%5d status=%s\n",
+ format, op, level, nt_errstr(status));
+#endif
+}
+
+/****************************************************************************
+check for existence of a nttrans call
+****************************************************************************/
+static NTSTATUS try_nttrans(struct cli_state *cli,
+ int op,
+ uint8_t *param, uint8_t *data,
+ int32_t param_len, uint32_t data_len,
+ uint32_t *rparam_len,
+ uint32_t *rdata_len)
+{
+ uint8_t *rparam=NULL, *rdata=NULL;
+ NTSTATUS status;
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, -1, /* name, fid */
+ op, 0,
+ NULL, 0, 0, /* setup */
+ param, param_len, 2,
+ data, data_len, CLI_BUFFER_SIZE,
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup */
+ &rparam, 0, rparam_len,
+ &rdata, 0, rdata_len);
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+
+ return status;
+}
+
+
+static NTSTATUS try_nttrans_len(struct cli_state *cli,
+ const char *format,
+ int op, int level,
+ uint8_t *param, uint8_t *data,
+ int param_len, uint32_t *data_len,
+ uint32_t *rparam_len, uint32_t *rdata_len)
+{
+ NTSTATUS ret=NT_STATUS_OK;
+
+ ret = try_nttrans(cli, op, param, data, param_len,
+ DATA_SIZE, rparam_len, rdata_len);
+#if VERBOSE
+ printf("op=%d level=%d ret=%s\n", op, level, nt_errstr(ret));
+#endif
+ if (!NT_STATUS_IS_OK(ret)) return ret;
+
+ *data_len = 0;
+ while (*data_len < DATA_SIZE) {
+ ret = try_nttrans(cli, op, param, data, param_len,
+ *data_len, rparam_len, rdata_len);
+ if (NT_STATUS_IS_OK(ret)) break;
+ *data_len += 2;
+ }
+ if (NT_STATUS_IS_OK(ret)) {
+ printf("found %s level=%d data_len=%d rparam_len=%d rdata_len=%d\n",
+ format, level, *data_len, *rparam_len, *rdata_len);
+ } else {
+ nttrans_check_hit(format, op, level, ret);
+ }
+ return ret;
+}
+
+/****************************************************************************
+check for existence of a nttrans call
+****************************************************************************/
+static bool scan_nttrans(struct cli_state *cli, int op, int level,
+ int fnum, int dnum, const char *fname)
+{
+ uint32_t data_len = 0;
+ uint32_t param_len = 0;
+ uint32_t rparam_len, rdata_len;
+ uint8_t *param = NULL;
+ uint8_t data[DATA_SIZE];
+ NTSTATUS status;
+ const char *newfname;
+ const char *dname;
+
+ memset(data, 0, sizeof(data));
+ data_len = 4;
+
+ /* try with a info level only */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 2);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+
+ param_len = talloc_get_size(param);
+ status = try_nttrans_len(cli, "void", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try with a file descriptor */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, fnum);
+ SSVAL(param, 2, level);
+ SSVAL(param, 4, 0);
+
+ param_len = talloc_get_size(param);
+ status = try_nttrans_len(cli, "fnum", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+
+ /* try with a notify style */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, dnum);
+ SSVAL(param, 2, dnum);
+ SSVAL(param, 4, level);
+
+ param_len = talloc_get_size(param);
+ status = try_nttrans_len(cli, "notify", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try with a file name */
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+ SSVAL(param, 2, 0);
+ SSVAL(param, 4, 0);
+ param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
+ fname, strlen(fname)+1, NULL);
+ if (param == NULL) return True;
+
+ param_len = talloc_get_size(param);
+ status = try_nttrans_len(cli, "fname", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try with a new file name */
+ newfname = "\\newfile.dat";
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+ SSVAL(param, 2, 0);
+ SSVAL(param, 4, 0);
+ param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
+ newfname, strlen(newfname)+1, NULL);
+ if (param == NULL) return True;
+
+ param_len = talloc_get_size(param);
+ status = try_nttrans_len(cli, "newfile", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ cli_unlink(cli, newfname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, newfname);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ /* try dfs style */
+ dname = "\\testdir";
+ cli_mkdir(cli, dname);
+ TALLOC_FREE(param);
+ param = talloc_array(talloc_tos(), uint8_t, 2);
+ if (param == NULL) return True;
+
+ SSVAL(param, 0, level);
+ param = trans2_bytes_push_str(param, smbXcli_conn_use_unicode(cli->conn),
+ dname, strlen(dname)+1, NULL);
+ if (param == NULL) return True;
+
+ param_len = talloc_get_size(param);
+ status = try_nttrans_len(cli, "dfs", op, level, param, data, param_len, &data_len,
+ &rparam_len, &rdata_len);
+ cli_rmdir(cli, dname);
+ if (NT_STATUS_IS_OK(status)) return True;
+
+ return False;
+}
+
+
+bool torture_nttrans_scan(int dummy)
+{
+ static struct cli_state *cli;
+ int op, level;
+ const char *fname = "\\scanner.dat";
+ uint16_t fnum, dnum;
+
+ printf("starting nttrans scan test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ cli_openx(cli, fname, O_RDWR | O_CREAT | O_TRUNC,
+ DENY_NONE, &fnum);
+ cli_openx(cli, "\\", O_RDONLY, DENY_NONE, &dnum);
+
+ for (op=OP_MIN; op<=OP_MAX; op++) {
+ printf("Scanning op=%d\n", op);
+ for (level = 0; level <= 50; level++) {
+ scan_nttrans(cli, op, level, fnum, dnum, fname);
+ }
+
+ for (level = 0x100; level <= 0x130; level++) {
+ scan_nttrans(cli, op, level, fnum, dnum, fname);
+ }
+
+ for (level = 1000; level < 1050; level++) {
+ scan_nttrans(cli, op, level, fnum, dnum, fname);
+ }
+ }
+
+ torture_close_connection(cli);
+
+ printf("nttrans scan finished\n");
+ return True;
+}
diff --git a/source3/torture/test_addrchange.c b/source3/torture/test_addrchange.c
new file mode 100644
index 0000000..9ccca1c
--- /dev/null
+++ b/source3/torture/test_addrchange.c
@@ -0,0 +1,94 @@
+/*
+ Unix SMB/CIFS implementation.
+ test for the addrchange functionality
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/addrchange.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "proto.h"
+
+extern int torture_numops;
+
+bool run_addrchange(int dummy)
+{
+ struct addrchange_context *ctx;
+ struct tevent_context *ev;
+ NTSTATUS status;
+ int i;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_fprintf(stderr, "tevent_context_init failed\n");
+ return false;
+ }
+
+ status = addrchange_context_create(talloc_tos(), &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "addrchange_context_create failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ for (i=0; i<torture_numops; i++) {
+ enum addrchange_type type;
+ struct sockaddr_storage addr;
+ struct tevent_req *req;
+ const char *typestr;
+ char addrstr[INET6_ADDRSTRLEN];
+
+ req = addrchange_send(talloc_tos(), ev, ctx);
+ if (req == NULL) {
+ d_fprintf(stderr, "addrchange_send failed\n");
+ return false;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ d_fprintf(stderr, "tevent_req_poll_ntstatus failed: "
+ "%s\n", nt_errstr(status));
+ TALLOC_FREE(req);
+ return false;
+ }
+
+ status = addrchange_recv(req, &type, &addr);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "addrchange_recv failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ switch(type) {
+ case ADDRCHANGE_ADD:
+ typestr = "add";
+ break;
+ case ADDRCHANGE_DEL:
+ typestr = "del";
+ break;
+ default:
+ typestr = talloc_asprintf(talloc_tos(), "unknown %d",
+ (int)type);
+ break;
+ }
+
+ printf("Got %s %s\n", typestr,
+ print_sockaddr(addrstr, sizeof(addrstr), &addr));
+ }
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(ev);
+ return true;
+}
diff --git a/source3/torture/test_async_echo.c b/source3/torture/test_async_echo.c
new file mode 100644
index 0000000..f21daa4
--- /dev/null
+++ b/source3/torture/test_async_echo.c
@@ -0,0 +1,148 @@
+/*
+ Unix SMB/CIFS implementation.
+ Run the async echo responder
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "rpc_client/cli_pipe.h"
+#include "librpc/gen_ndr/ndr_echo_c.h"
+
+static void rpccli_sleep_done(struct tevent_req *req)
+{
+ int *done = (int *)tevent_req_callback_data_void(req);
+ NTSTATUS status;
+ uint32_t result = UINT32_MAX;
+
+ status = dcerpc_echo_TestSleep_recv(req, talloc_tos(), &result);
+ TALLOC_FREE(req);
+ printf("sleep returned %s, %d\n", nt_errstr(status), (int)result);
+ *done -= 1;
+}
+
+static void cli_echo_done(struct tevent_req *req)
+{
+ int *done = (int *)tevent_req_callback_data_void(req);
+ NTSTATUS status;
+
+ status = cli_echo_recv(req);
+ TALLOC_FREE(req);
+ printf("echo returned %s\n", nt_errstr(status));
+ *done -= 1;
+}
+
+static void write_andx_done(struct tevent_req *req)
+{
+ int *done = (int *)tevent_req_callback_data_void(req);
+ NTSTATUS status;
+ size_t written;
+
+ status = cli_write_andx_recv(req, &written);
+ TALLOC_FREE(req);
+ printf("cli_write_andx returned %s\n", nt_errstr(status));
+ *done -= 1;
+}
+
+bool run_async_echo(int dummy)
+{
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *p;
+ struct dcerpc_binding_handle *b;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status;
+ bool ret = false;
+ int i, num_reqs;
+ uint8_t buf[65536];
+
+ printf("Starting ASYNC_ECHO\n");
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("tevent_context_init failed\n");
+ goto fail;
+ }
+
+ if (!torture_open_connection(&cli, 0)) {
+ printf("torture_open_connection failed\n");
+ goto fail;
+ }
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_rpcecho,
+ &p);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Could not open echo pipe: %s\n", nt_errstr(status));
+ goto fail;
+ }
+ b = p->binding_handle;
+
+ num_reqs = 0;
+
+ req = dcerpc_echo_TestSleep_send(ev, ev, b, 15);
+ if (req == NULL) {
+ printf("rpccli_echo_TestSleep_send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, rpccli_sleep_done, &num_reqs);
+ num_reqs += 1;
+
+ req = cli_echo_send(ev, ev, cli, 1, data_blob_const("hello", 5));
+ if (req == NULL) {
+ printf("cli_echo_send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, cli_echo_done, &num_reqs);
+ num_reqs += 1;
+
+ memset(buf, 0, sizeof(buf));
+
+ for (i=0; i<10; i++) {
+ req = cli_write_andx_send(ev, ev, cli, 4711, 0, buf, 0,
+ sizeof(buf));
+ if (req == NULL) {
+ printf("cli_write_andx_send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, write_andx_done, &num_reqs);
+ num_reqs += 1;
+
+ req = cli_echo_send(ev, ev, cli, 1,
+ data_blob_const("hello", 5));
+ if (req == NULL) {
+ printf("cli_echo_send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, cli_echo_done, &num_reqs);
+ num_reqs += 1;
+ }
+
+ while (num_reqs > 0) {
+ if (tevent_loop_once(ev) != 0) {
+ printf("tevent_loop_once failed\n");
+ goto fail;
+ }
+ }
+
+ TALLOC_FREE(p);
+
+ ret = true;
+fail:
+ if (cli != NULL) {
+ torture_close_connection(cli);
+ }
+ return ret;
+}
diff --git a/source3/torture/test_authinfo_structs.c b/source3/torture/test_authinfo_structs.c
new file mode 100644
index 0000000..0b5cff7
--- /dev/null
+++ b/source3/torture/test_authinfo_structs.c
@@ -0,0 +1,218 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test conversion form struct lsa_TrustDomainInfoAuthInfo to
+ struct trustAuthInOutBlob and back
+ Copyright (C) Sumit Bose 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "librpc/gen_ndr/lsa.h"
+#include "libcli/lsarpc/util_lsarpc.h"
+
+static bool cmp_TrustDomainInfoBuffer(struct lsa_TrustDomainInfoBuffer a,
+ struct lsa_TrustDomainInfoBuffer b)
+{
+ if (a.last_update_time != b. last_update_time ||
+ a.AuthType != b.AuthType ||
+ a.data.size != b.data.size ||
+ memcmp(a.data.data, b.data.data, a.data.size) !=0) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool cmp_auth_info(struct lsa_TrustDomainInfoAuthInfo *a,
+ struct lsa_TrustDomainInfoAuthInfo *b)
+{
+ size_t c;
+
+ if (a->incoming_count != b->incoming_count ||
+ a->outgoing_count != b->outgoing_count) {
+ return false;
+ }
+
+ for (c = 0; c < a->incoming_count; c++) {
+ if (!cmp_TrustDomainInfoBuffer(a->incoming_current_auth_info[c],
+ b->incoming_current_auth_info[c])) {
+ return false;
+ }
+
+ if (a->incoming_previous_auth_info != NULL &&
+ b->incoming_previous_auth_info != NULL) {
+ if (!cmp_TrustDomainInfoBuffer(a->incoming_previous_auth_info[c],
+ b->incoming_previous_auth_info[c])) {
+ return false;
+ }
+ } else if (a->incoming_previous_auth_info == NULL &&
+ b->incoming_previous_auth_info == NULL) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+
+ for (c = 0; c < a->outgoing_count; c++) {
+ if (!cmp_TrustDomainInfoBuffer(a->outgoing_current_auth_info[c],
+ b->outgoing_current_auth_info[c])) {
+ return false;
+ }
+
+ if (a->outgoing_previous_auth_info != NULL &&
+ b->outgoing_previous_auth_info != NULL) {
+ if (!cmp_TrustDomainInfoBuffer(a->outgoing_previous_auth_info[c],
+ b->outgoing_previous_auth_info[c])) {
+ return false;
+ }
+ } else if (a->outgoing_previous_auth_info == NULL &&
+ b->outgoing_previous_auth_info == NULL) {
+ continue;
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool covert_and_compare(struct lsa_TrustDomainInfoAuthInfo *auth_info)
+{
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx;
+ DATA_BLOB incoming;
+ DATA_BLOB outgoing;
+ struct lsa_TrustDomainInfoAuthInfo auth_info_out;
+ bool result = false;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ return false;
+ }
+
+ status = auth_info_2_auth_blob(tmp_ctx, auth_info, &incoming, &outgoing);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ status = auth_blob_2_auth_info(tmp_ctx, incoming, outgoing,
+ &auth_info_out);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_free(tmp_ctx);
+ return false;
+ }
+
+ result = cmp_auth_info(auth_info, &auth_info_out);
+ talloc_free(tmp_ctx);
+
+ return result;
+}
+
+bool run_local_conv_auth_info(int dummy)
+{
+ struct lsa_TrustDomainInfoAuthInfo auth_info;
+ struct lsa_TrustDomainInfoBuffer ic[1];
+ struct lsa_TrustDomainInfoBuffer ip[1];
+ struct lsa_TrustDomainInfoBuffer oc[2];
+ struct lsa_TrustDomainInfoBuffer op[2];
+ uint32_t version = 3;
+
+ ic[0].last_update_time = 12345;
+ ic[0].AuthType = TRUST_AUTH_TYPE_CLEAR;
+ ic[0].data.size = strlen("iPaSsWoRd");
+ ic[0].data.data = discard_const_p(uint8_t, "iPaSsWoRd");
+
+ ip[0].last_update_time = 67890;
+ ip[0].AuthType = TRUST_AUTH_TYPE_CLEAR;
+ ip[0].data.size = strlen("OlDiPaSsWoRd");
+ ip[0].data.data = discard_const_p(uint8_t, "OlDiPaSsWoRd");
+
+ oc[0].last_update_time = 24580;
+ oc[0].AuthType = TRUST_AUTH_TYPE_CLEAR;
+ oc[0].data.size = strlen("oPaSsWoRd");
+ oc[0].data.data = discard_const_p(uint8_t, "oPaSsWoRd");
+ oc[1].last_update_time = 24580;
+ oc[1].AuthType = TRUST_AUTH_TYPE_VERSION;
+ oc[1].data.size = 4;
+ oc[1].data.data = (uint8_t *) &version;
+
+ op[0].last_update_time = 13579;
+ op[0].AuthType = TRUST_AUTH_TYPE_CLEAR;
+ op[0].data.size = strlen("OlDoPaSsWoRd");
+ op[0].data.data = discard_const_p(uint8_t, "OlDoPaSsWoRd");
+ op[1].last_update_time = 24580;
+ op[1].AuthType = TRUST_AUTH_TYPE_VERSION;
+ op[1].data.size = 4;
+ op[1].data.data = (uint8_t *) &version;
+
+ auth_info.incoming_count = 0;
+ auth_info.incoming_current_auth_info = NULL;
+ auth_info.incoming_previous_auth_info = NULL;
+ auth_info.outgoing_count = 0;
+ auth_info.outgoing_current_auth_info = NULL;
+ auth_info.outgoing_previous_auth_info = NULL;
+
+ if (!covert_and_compare(&auth_info)) {
+ return false;
+ }
+
+ auth_info.incoming_count = 1;
+ auth_info.incoming_current_auth_info = ic;
+ auth_info.incoming_previous_auth_info = NULL;
+ auth_info.outgoing_count = 0;
+ auth_info.outgoing_current_auth_info = NULL;
+ auth_info.outgoing_previous_auth_info = NULL;
+
+ if (!covert_and_compare(&auth_info)) {
+ return false;
+ }
+
+ auth_info.incoming_count = 0;
+ auth_info.incoming_current_auth_info = NULL;
+ auth_info.incoming_previous_auth_info = NULL;
+ auth_info.outgoing_count = 2;
+ auth_info.outgoing_current_auth_info = oc;
+ auth_info.outgoing_previous_auth_info = NULL;
+
+ if (!covert_and_compare(&auth_info)) {
+ return false;
+ }
+
+ auth_info.incoming_count = 1;
+ auth_info.incoming_current_auth_info = ic;
+ auth_info.incoming_previous_auth_info = NULL;
+ auth_info.outgoing_count = 2;
+ auth_info.outgoing_current_auth_info = oc;
+ auth_info.outgoing_previous_auth_info = NULL;
+
+ if (!covert_and_compare(&auth_info)) {
+ return false;
+ }
+
+ auth_info.incoming_count = 1;
+ auth_info.incoming_current_auth_info = ic;
+ auth_info.incoming_previous_auth_info = ip;
+ auth_info.outgoing_count = 2;
+ auth_info.outgoing_current_auth_info = oc;
+ auth_info.outgoing_previous_auth_info = op;
+
+ if (!covert_and_compare(&auth_info)) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/torture/test_buffersize.c b/source3/torture/test_buffersize.c
new file mode 100644
index 0000000..217b148
--- /dev/null
+++ b/source3/torture/test_buffersize.c
@@ -0,0 +1,55 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test buffer sizes in cli_qpathinfo
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/secdesc.h"
+#include "libcli/security/security.h"
+#include "trans2.h"
+#include "source3/libsmb/clirap.h"
+
+bool run_qpathinfo_bufsize(int dummy)
+{
+ struct cli_state *cli = NULL;
+ bool ret = false;
+ int i;
+
+ printf("Starting qpathinfo_bufsize\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ printf("torture_open_connection failed\n");
+ goto fail;
+ }
+
+ for (i=0; i<500; i++) {
+ uint8_t *rdata;
+ uint32_t num_rdata;
+ cli_qpathinfo(cli, cli, "\\", SMB_FILE_ALL_INFORMATION,
+ 0, i, &rdata, &num_rdata);
+ }
+
+ ret = true;
+fail:
+ if (cli != NULL) {
+ torture_close_connection(cli);
+ }
+ return ret;
+}
diff --git a/source3/torture/test_case_insensitive.c b/source3/torture/test_case_insensitive.c
new file mode 100644
index 0000000..04e2dce
--- /dev/null
+++ b/source3/torture/test_case_insensitive.c
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ reproducer for bug 8042
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "system/filesys.h"
+#include "libsmb/libsmb.h"
+
+/*
+ * Regression test file creates on case insensitive file systems (e.g. OS/X)
+ * https://bugzilla.samba.org/show_bug.cgi?id=8042
+ */
+
+bool run_case_insensitive_create(int dummy)
+{
+ struct cli_state *cli;
+ uint16_t fnum;
+ NTSTATUS status;
+
+ printf("Starting case_insensitive_create\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ status = cli_mkdir(cli, "x");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_mkdir failed: %s\n", nt_errstr(status));
+ goto done;
+ }
+ status = cli_chkpath(cli, "X");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_chkpath failed: %s\n", nt_errstr(status));
+ goto rmdir;
+ }
+ status = cli_openx(cli, "x\\y", O_RDWR|O_CREAT, 0, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_openx failed: %s\n", nt_errstr(status));
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("Bug 8042 reappeared!!\n");
+ }
+ goto unlink;
+ }
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed: %s\n", nt_errstr(status));
+ goto done;
+ }
+unlink:
+ status = cli_unlink(cli, "x\\y", 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_unlink failed: %s\n", nt_errstr(status));
+ goto done;
+ }
+rmdir:
+ status = cli_rmdir(cli, "x");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed: %s\n", nt_errstr(status));
+ }
+done:
+ torture_close_connection(cli);
+ return NT_STATUS_IS_OK(status);
+}
diff --git a/source3/torture/test_chain3.c b/source3/torture/test_chain3.c
new file mode 100644
index 0000000..5320ef8
--- /dev/null
+++ b/source3/torture/test_chain3.c
@@ -0,0 +1,296 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test smbd chain routines
+
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "system/filesys.h"
+#include "async_smb.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/security/security.h"
+#include "libcli/smb/smbXcli_base.h"
+
+struct chain3_andx_state {
+ uint16_t fnum;
+ size_t written;
+ char str[6];
+};
+
+static void chain3_andx_open_done(struct tevent_req *subreq);
+static void chain3_andx_write_done(struct tevent_req *subreq);
+static void chain3_andx_close_done(struct tevent_req *subreq);
+
+static struct tevent_req *chain3_andx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname)
+{
+ struct tevent_req *req, *subreq;
+ struct tevent_req *smbreqs[3];
+ struct chain3_andx_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct chain3_andx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ strlcpy(state->str, "hello", sizeof(state->str));
+
+ subreq = cli_openx_create(state, ev, cli, fname,
+ O_CREAT|O_RDWR, 0, &smbreqs[0]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, chain3_andx_open_done, req);
+
+ subreq = cli_write_andx_create(state, ev, cli, 0, 0,
+ (const uint8_t *)state->str, 0,
+ strlen(state->str)+1,
+ smbreqs, 1, &smbreqs[1]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, chain3_andx_write_done, req);
+
+ subreq = cli_smb1_close_create(state, ev, cli, 0, &smbreqs[2]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, chain3_andx_close_done, req);
+
+ status = smb1cli_req_chain_submit(smbreqs, ARRAY_SIZE(smbreqs));
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void chain3_andx_open_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct chain3_andx_state *state = tevent_req_data(
+ req, struct chain3_andx_state);
+ NTSTATUS status;
+
+ status = cli_openx_recv(subreq, &state->fnum);
+ printf("cli_openx returned %s, fnum=%u\n", nt_errstr(status),
+ (unsigned)state->fnum);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+}
+
+static void chain3_andx_write_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct chain3_andx_state *state = tevent_req_data(
+ req, struct chain3_andx_state);
+ NTSTATUS status;
+
+ status = cli_write_andx_recv(subreq, &state->written);
+ printf("cli_write_andx returned %s, written=%u\n", nt_errstr(status),
+ (unsigned)state->written);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+}
+
+static void chain3_andx_close_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ printf("cli_close returned %s\n", nt_errstr(status));
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS chain3_andx_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct chain3_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *fname;
+ uint16_t fnum;
+};
+
+static void chain3_got_break(struct tevent_req *subreq);
+static void chain3_ntcreate_done(struct tevent_req *subreq);
+static void chain3_break_close_done(struct tevent_req *subreq);
+static void chain3_andx_done(struct tevent_req *subreq);
+
+static struct tevent_req *chain3_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct tevent_req *req, *subreq;
+ struct chain3_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct chain3_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->fname = "chain3.txt";
+
+ if (!torture_open_connection(&state->cli, 0)) {
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_oplock_break_waiter_send(
+ state, state->ev, state->cli);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, chain3_got_break, req);
+
+ subreq = cli_ntcreate_send(
+ state, state->ev, state->cli, state->fname,
+ REQUEST_OPLOCK|REQUEST_BATCH_OPLOCK,
+ GENERIC_READ_ACCESS|GENERIC_WRITE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, chain3_ntcreate_done, req);
+ return req;
+}
+
+static void chain3_got_break(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct chain3_state *state = tevent_req_data(
+ req, struct chain3_state);
+ uint16_t fnum;
+ uint8_t level;
+ NTSTATUS status;
+
+ status = cli_smb_oplock_break_waiter_recv(subreq, &fnum, &level);
+ TALLOC_FREE(subreq);
+ printf("cli_smb_oplock_break_waiter_recv returned %s\n",
+ nt_errstr(status));
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_close_send(state, state->ev, state->cli, fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, chain3_break_close_done, req);
+}
+
+static void chain3_break_close_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ printf("cli_close_recv returned %s\n", nt_errstr(status));
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+}
+
+static void chain3_ntcreate_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct chain3_state *state = tevent_req_data(
+ req, struct chain3_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->fnum, NULL);
+ TALLOC_FREE(subreq);
+ printf("cli_ntcreate returned %s, fnum=%u\n", nt_errstr(status),
+ (unsigned)state->fnum);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = chain3_andx_send(state, state->ev, state->cli, state->fname);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, chain3_andx_done, req);
+}
+
+static void chain3_andx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = chain3_andx_recv(subreq);
+ TALLOC_FREE(subreq);
+ printf("chain3_andx_recv returned %s\n", nt_errstr(status));
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS chain3_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+bool run_chain3(int dummy)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = chain3_send(frame, ev);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = chain3_recv(req);
+fail:
+ TALLOC_FREE(frame);
+ printf("run_chain3 returns %s\n", nt_errstr(status));
+ return NT_STATUS_IS_OK(status);
+}
diff --git a/source3/torture/test_cleanup.c b/source3/torture/test_cleanup.c
new file mode 100644
index 0000000..2ff5cf6
--- /dev/null
+++ b/source3/torture/test_cleanup.c
@@ -0,0 +1,240 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test cleanup behaviour
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/proto.h"
+#include "torture/proto.h"
+#include "system/filesys.h"
+#include "system/select.h"
+#include "libsmb/libsmb.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "libcli/security/security.h"
+#include "librpc/gen_ndr/open_files.h"
+
+bool run_cleanup1(int dummy)
+{
+ struct cli_state *cli;
+ const char *fname = "\\cleanup1";
+ uint16_t fnum;
+ NTSTATUS status;
+
+ printf("CLEANUP1: Checking that a conflicting share mode is cleaned "
+ "up\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT, DENY_ALL, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+ status = smbXcli_conn_samba_suicide(cli->conn, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_conn_samba_suicide failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (!torture_open_connection(&cli, 1)) {
+ return false;
+ }
+ status = cli_ntcreate(
+ cli, fname, 0,
+ FILE_GENERIC_READ|FILE_GENERIC_WRITE|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, FILE_DELETE_ON_CLOSE, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("2nd open of %s failed (%s)\n", fname,
+ nt_errstr(status));
+ return false;
+ }
+ cli_close(cli, fnum);
+
+ torture_close_connection(cli);
+ return NT_STATUS_IS_OK(status);
+}
+
+bool run_cleanup2(int dummy)
+{
+ struct cli_state *cli1, *cli2, *cli3;
+ const char *fname = "\\cleanup2";
+ uint16_t fnum1, fnum2, fnum3;
+ NTSTATUS status;
+ char buf;
+
+ printf("CLEANUP2: Checking that a conflicting brlock is cleaned up\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return false;
+ }
+ status = cli_ntcreate(
+ cli1, fname, 0, FILE_GENERIC_READ|FILE_GENERIC_WRITE,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+ status = cli_lock32(cli1, fnum1, 0, 1, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ if (!torture_open_connection(&cli3, 1)) {
+ return false;
+ }
+ status = cli_ntcreate(
+ cli3, fname, 0, FILE_GENERIC_READ|FILE_GENERIC_WRITE,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum3, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+ status = cli_lock32(cli3, fnum3, 1, 1, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_lock32(cli1, fnum1, 2, 1, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Check the file is indeed locked
+ */
+ if (!torture_open_connection(&cli2, 1)) {
+ return false;
+ }
+ status = cli_ntcreate(
+ cli2, fname, 0, FILE_GENERIC_READ|FILE_GENERIC_WRITE,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+ buf = 'x';
+ status = cli_smbwrite(cli2, fnum2, &buf, 0, 1, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ printf("write succeeded\n");
+ return false;
+ }
+
+ /*
+ * Kill the lock holder
+ */
+ status = smbXcli_conn_samba_suicide(cli1->conn, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_conn_samba_suicide failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Give the suicidal smbd a bit of time to really pass away
+ */
+ smb_msleep(1000);
+
+ status = cli_smbwrite(cli2, fnum2, &buf, 0, 1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed: %s\n", nt_errstr(status));
+ return false;
+ }
+ return true;
+}
+
+bool run_cleanup4(int dummy)
+{
+ struct cli_state *cli1, *cli2;
+ const char *fname = "\\cleanup4";
+ uint16_t fnum1, fnum2;
+ NTSTATUS status;
+
+ printf("CLEANUP4: Checking that a conflicting share mode is cleaned "
+ "up\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return false;
+ }
+ if (!torture_open_connection(&cli2, 0)) {
+ return false;
+ }
+
+ status = cli_ntcreate(
+ cli1, fname, 0,
+ FILE_GENERIC_READ|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("creating file failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = cli_ntcreate(
+ cli2, fname, 0,
+ FILE_GENERIC_READ|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("opening file 1st time failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smbXcli_conn_samba_suicide(cli1->conn, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_conn_samba_suicide failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * The next open will conflict with both opens above. The first open
+ * above will be correctly cleaned up. A bug in smbd iterating over
+ * the share mode array made it skip the share conflict check for the
+ * second open. Trigger this bug.
+ */
+
+ status = cli_ntcreate(
+ cli2, fname, 0,
+ FILE_GENERIC_WRITE|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ printf("opening file 2nd time returned: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/torture/test_ctdbd_conn.c b/source3/torture/test_ctdbd_conn.c
new file mode 100644
index 0000000..124a334
--- /dev/null
+++ b/source3/torture/test_ctdbd_conn.c
@@ -0,0 +1,312 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test async ctdb_req_send/recv
+ * Copyright (C) Volker Lendecke 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "ctdbd_conn.h"
+#include "lib/cluster_support.h"
+#include "ctdb/include/ctdb_protocol.h"
+#include "lib/util/tevent_unix.h"
+
+extern int torture_nprocs;
+extern int torture_numops;
+
+struct ctdb_echo_state {
+ struct ctdb_req_control_old req;
+ struct iovec iov[2];
+ TDB_DATA echodata;
+};
+
+static void ctdb_echo_done(struct tevent_req *subreq);
+
+static struct tevent_req *ctdb_echo_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ uint32_t delay)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct ctdb_echo_state *state = NULL;
+ struct ctdb_req_header *hdr = NULL;
+ uint32_t datalen;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct ctdb_echo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ hdr = &state->req.hdr;
+ ctdbd_prep_hdr_next_reqid(conn, hdr);
+ hdr->operation = CTDB_REQ_CONTROL;
+ state->req.opcode = CTDB_CONTROL_ECHO_DATA;
+
+ state->iov[0] = (struct iovec) {
+ .iov_base = &state->req,
+ .iov_len = offsetof(struct ctdb_req_control_old, data),
+ };
+
+ datalen = generate_random() % 1024;
+
+ state->echodata.dptr = talloc_array(state, uint8_t, datalen+8);
+ if (tevent_req_nomem(state->echodata.dptr, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->echodata.dsize = talloc_get_size(state->echodata.dptr);
+ generate_random_buffer(
+ state->echodata.dptr, state->echodata.dsize);
+
+ memcpy(state->echodata.dptr, &delay, sizeof(delay));
+ memcpy(state->echodata.dptr+4, &datalen, sizeof(datalen));
+
+ state->req.datalen = state->echodata.dsize;
+
+ state->iov[1] = (struct iovec) {
+ .iov_base = state->echodata.dptr,
+ .iov_len = state->echodata.dsize,
+ };
+
+ hdr->length =
+ offsetof(struct ctdb_req_control_old, data) +
+ state->req.datalen;
+
+ subreq = ctdbd_req_send(
+ state, ev, conn, state->iov, ARRAY_SIZE(state->iov));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ctdb_echo_done, req);
+
+ return req;
+}
+
+static void ctdb_echo_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct ctdb_echo_state *state = tevent_req_data(
+ req, struct ctdb_echo_state);
+ struct ctdb_req_header *hdr = NULL;
+ struct ctdb_reply_control_old *reply = NULL;
+ int cmp, ret;
+
+ ret = ctdbd_req_recv(subreq, state, &hdr);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ printf("ctdbd_req_recv(%"PRIu32") returned %d (%s)\n",
+ state->req.hdr.reqid,
+ ret,
+ strerror(ret));
+ return;
+ }
+ if (hdr->operation != CTDB_REPLY_CONTROL) {
+ printf("Expected CTDB_REPLY_CONTROL, got %"PRIu32"\n",
+ hdr->operation);
+ tevent_req_error(req, EIO);
+ return;
+ }
+ reply = (struct ctdb_reply_control_old *)hdr;
+ if (reply->status != 0) {
+ printf("reply->status = %"PRIi32"\n", reply->status);
+ tevent_req_error(req, EIO);
+ return;
+ }
+ if (reply->datalen != state->req.datalen) {
+ printf("state->echodata.dsize=%zu datalen=%"PRIu32"\n",
+ state->echodata.dsize,
+ reply->datalen);
+ tevent_req_error(req, EIO);
+ return;
+ }
+ cmp = memcmp(reply->data,
+ state->echodata.dptr,
+ state->echodata.dsize);
+ if (cmp != 0) {
+ printf("data mismatch\n");
+ tevent_req_error(req, EIO);
+ return;
+ }
+ TALLOC_FREE(reply);
+ tevent_req_done(req);
+}
+
+static int ctdb_echo_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+struct ctdb_ping_flood_state {
+ struct tevent_context *ev;
+ struct ctdbd_connection *conn;
+ size_t num_running;
+ bool done;
+};
+
+static void ctdb_ping_flood_next(struct tevent_req *subreq);
+static void ctdb_ping_flood_done(struct tevent_req *subreq);
+
+static struct tevent_req *ctdb_ping_flood_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct ctdbd_connection *conn,
+ size_t num_parallel,
+ unsigned usecs)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct ctdb_ping_flood_state *state = NULL;
+ size_t i;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct ctdb_ping_flood_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->conn = conn;
+
+ for (i=0; i<num_parallel; i++) {
+ subreq = ctdb_echo_send(
+ state,
+ state->ev,
+ state->conn,
+ generate_random() % 10);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ctdb_ping_flood_next, req);
+ }
+ state->num_running = num_parallel;
+
+ subreq = tevent_wakeup_send(
+ state,
+ ev,
+ tevent_timeval_current_ofs(0, usecs));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, ctdb_ping_flood_done, req);
+
+ return req;
+}
+
+static void ctdb_ping_flood_next(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct ctdb_ping_flood_state *state = tevent_req_data(
+ req, struct ctdb_ping_flood_state);
+ int ret;
+
+ ret = ctdb_echo_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ state->num_running -= 1;
+
+ if (state->done) {
+ if (state->num_running == 0) {
+ tevent_req_done(req);
+ }
+ return;
+ }
+
+ subreq = ctdb_echo_send(
+ state,
+ state->ev,
+ state->conn,
+ generate_random() % 10);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, ctdb_ping_flood_next, req);
+ state->num_running += 1;
+}
+
+static void ctdb_ping_flood_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct ctdb_ping_flood_state *state = tevent_req_data(
+ req, struct ctdb_ping_flood_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ state->done = true;
+}
+
+static int ctdb_ping_flood_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+bool run_ctdbd_conn1(int dummy)
+{
+ struct ctdbd_connection *conn = NULL;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ int ret;
+ bool ok;
+ bool result = false;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("samba_tevent_context_init failed\n");
+ goto done;
+ }
+
+ ret = ctdbd_init_async_connection(
+ ev, lp_ctdbd_socket(), 0, &conn);
+ if (ret != 0) {
+ printf("ctdbd_init_async_connection failed: %s\n",
+ strerror(ret));
+ goto done;
+ }
+
+ req = ctdb_ping_flood_send(
+ ev, ev, conn, torture_nprocs, torture_numops * 1000);
+ if (req == NULL) {
+ printf("ctdb_ping_flood_send failed\n");
+ goto done;
+ }
+
+ ok = tevent_req_poll_unix(req, ev, &ret);
+ if (!ok) {
+ printf("tevent_req_poll_unix failed: %s\n",
+ strerror(ret));
+ goto done;
+ }
+
+ ret = ctdb_ping_flood_recv(req);
+ TALLOC_FREE(req);
+ if (ret != 0) {
+ printf("ctdb_ping_flood failed: %s\n", strerror(ret));
+ goto done;
+ }
+
+ result = true;
+done:
+ TALLOC_FREE(conn);
+ return result;
+}
diff --git a/source3/torture/test_dbwrap_ctdb.c b/source3/torture/test_dbwrap_ctdb.c
new file mode 100644
index 0000000..e3a7c6a
--- /dev/null
+++ b/source3/torture/test_dbwrap_ctdb.c
@@ -0,0 +1,163 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test dbwrap_ctdb API
+ * Copyright (C) Volker Lendecke 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "system/filesys.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_ctdb.h"
+#include "messages.h"
+#include "lib/messages_ctdb.h"
+#include "lib/global_contexts.h"
+
+bool run_local_dbwrap_ctdb1(int dummy)
+{
+ struct db_context *db = NULL;
+ int res;
+ bool ret = false;
+ NTSTATUS status;
+ uint32_t val;
+ struct messaging_context *msg_ctx;
+
+ msg_ctx = global_messaging_context();
+
+ db = db_open_ctdb(
+ talloc_tos(),
+ msg_ctx,
+ "torture.tdb",
+ 0,
+ TDB_DEFAULT,
+ O_RDWR|O_CREAT,
+ 0755,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ perror("db_open_ctdb failed");
+ goto fail;
+ }
+
+ res = dbwrap_transaction_start(db);
+ if (res != 0) {
+ fprintf(stderr, "dbwrap_transaction_start failed");
+ goto fail;
+ }
+ res = dbwrap_transaction_cancel(db);
+ if (res != 0) {
+ fprintf(stderr, "dbwrap_transaction_cancel failed");
+ goto fail;
+ }
+
+ res = dbwrap_transaction_start(db);
+ if (res != 0) {
+ fprintf(stderr, "dbwrap_transaction_start failed");
+ goto fail;
+ }
+
+ status = dbwrap_store_uint32_bystring(db, "foo", 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "store_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ status = dbwrap_fetch_uint32_bystring(db, "foo", &val);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "fetch_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (val != 1) {
+ fprintf(stderr, "fetch_uint32 gave %u, expected 1",
+ (unsigned)val);
+ goto fail;
+ }
+
+ status = dbwrap_store_uint32_bystring(db, "bar", 5);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "store_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ status = dbwrap_fetch_uint32_bystring(db, "bar", &val);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "fetch_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (val != 5) {
+ fprintf(stderr, "fetch_uint32 gave %u, expected 5",
+ (unsigned)val);
+ goto fail;
+ }
+
+ status = dbwrap_store_uint32_bystring(db, "foo", 2);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "store_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ status = dbwrap_fetch_uint32_bystring(db, "foo", &val);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "fetch_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (val != 2) {
+ fprintf(stderr, "fetch_uint32 gave %u, expected 2",
+ (unsigned)val);
+ goto fail;
+ }
+
+ res = dbwrap_transaction_commit(db);
+ if (res != 0) {
+ fprintf(stderr, "dbwrap_transaction_commit failed");
+ goto fail;
+ }
+
+ /*
+ * check that the values have reached the disk
+ */
+ status = dbwrap_fetch_uint32_bystring(db, "foo", &val);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "fetch_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (val != 2) {
+ fprintf(stderr, "fetch_uint32 gave %u, expected 1",
+ (unsigned)val);
+ goto fail;
+ }
+
+ status = dbwrap_fetch_uint32_bystring(db, "bar", &val);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "fetch_uint32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (val != 5) {
+ fprintf(stderr, "fetch_uint32 gave %u, expected 1",
+ (unsigned)val);
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(db);
+ return ret;
+}
diff --git a/source3/torture/test_dbwrap_do_locked.c b/source3/torture/test_dbwrap_do_locked.c
new file mode 100644
index 0000000..93648ce
--- /dev/null
+++ b/source3/torture/test_dbwrap_do_locked.c
@@ -0,0 +1,161 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test dbwrap_watch API
+ * Copyright (C) Volker Lendecke 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "system/filesys.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/dbwrap/dbwrap_watch.h"
+#include "lib/util/util_tdb.h"
+#include "source3/include/util_tdb.h"
+#include "lib/global_contexts.h"
+
+struct do_locked1_state {
+ TDB_DATA value;
+ NTSTATUS status;
+};
+
+static void do_locked1_cb(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct do_locked1_state *state =
+ (struct do_locked1_state *)private_data;
+
+ state->status = dbwrap_record_store(rec, state->value, 0);
+}
+
+static void do_locked1_check(TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct do_locked1_state *state =
+ (struct do_locked1_state *)private_data;
+ int ret;
+
+ ret = tdb_data_cmp(value, state->value);
+ if (ret != 0) {
+ state->status = NT_STATUS_DATA_ERROR;
+ return;
+ }
+
+ state->status = NT_STATUS_OK;
+}
+
+static void do_locked1_del(
+ struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct do_locked1_state *state =
+ (struct do_locked1_state *)private_data;
+
+ state->status = dbwrap_record_delete(rec);
+}
+
+bool run_dbwrap_do_locked1(int dummy)
+{
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ struct db_context *backend;
+ struct db_context *db;
+ const char *dbname = "test_do_locked.tdb";
+ const char *keystr = "key";
+ TDB_DATA key = string_term_tdb_data(keystr);
+ const char *valuestr = "value";
+ TDB_DATA value = string_term_tdb_data(valuestr);
+ struct do_locked1_state state = { .value = value };
+ int ret = false;
+ NTSTATUS status;
+
+ ev = global_event_context();
+ if (ev == NULL) {
+ fprintf(stderr, "global_event_context() failed\n");
+ return false;
+ }
+ msg = global_messaging_context();
+ if (msg == NULL) {
+ fprintf(stderr, "global_messaging_context() failed\n");
+ return false;
+ }
+
+ backend = db_open(talloc_tos(), dbname, 0,
+ TDB_CLEAR_IF_FIRST, O_CREAT|O_RDWR, 0644,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (backend == NULL) {
+ fprintf(stderr, "db_open failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ db = db_open_watched(talloc_tos(), &backend, msg);
+ if (db == NULL) {
+ fprintf(stderr, "db_open_watched failed: %s\n",
+ strerror(errno));
+ return false;
+ }
+
+ status = dbwrap_do_locked(db, key, do_locked1_cb, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ fprintf(stderr, "store returned %s\n",
+ nt_errstr(state.status));
+ goto fail;
+ }
+
+ status = dbwrap_parse_record(db, key, do_locked1_check, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "dbwrap_parse_record failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ fprintf(stderr, "data compare returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = dbwrap_do_locked(db, key, do_locked1_del, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ fprintf(stderr, "delete returned %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = dbwrap_parse_record(db, key, do_locked1_check, &state);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ fprintf(stderr, "parse_record returned %s, "
+ "expected NOT_FOUND\n", nt_errstr(status));
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(db);
+ unlink(dbname);
+ return ret;
+}
diff --git a/source3/torture/test_dbwrap_watch.c b/source3/torture/test_dbwrap_watch.c
new file mode 100644
index 0000000..480b2c5
--- /dev/null
+++ b/source3/torture/test_dbwrap_watch.c
@@ -0,0 +1,467 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test dbwrap_watch API
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "system/filesys.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/dbwrap/dbwrap_watch.h"
+#include "lib/util/util_tdb.h"
+
+static bool test_dbwrap_watch_init(
+ TALLOC_CTX *mem_ctx,
+ const char *dbname,
+ struct tevent_context **pev,
+ struct messaging_context **pmsg,
+ struct db_context **pbackend,
+ struct db_context **pdb)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db = NULL;
+
+ ev = samba_tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ goto fail;
+ }
+
+ msg = messaging_init(ev, ev);
+ if (msg == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ goto fail;
+ }
+
+ backend = db_open(
+ msg,
+ dbname,
+ 0,
+ TDB_CLEAR_IF_FIRST,
+ O_CREAT|O_RDWR,
+ 0644,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (backend == NULL) {
+ fprintf(stderr, "db_open failed: %s\n", strerror(errno));
+ goto fail;
+ }
+
+ {
+ struct db_context *backend_copy = backend;
+
+ db = db_open_watched(ev, &backend_copy, msg);
+ if (db == NULL) {
+ fprintf(stderr, "db_open_watched failed\n");
+ goto fail;
+ }
+ }
+
+ if (pev != NULL) {
+ *pev = ev;
+ }
+ if (pmsg != NULL) {
+ *pmsg = msg;
+ }
+ if (pbackend != NULL) {
+ *pbackend = backend;
+ }
+ if (pdb != NULL) {
+ *pdb = db;
+ }
+ return true;
+
+fail:
+ TALLOC_FREE(backend);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return false;
+}
+
+bool run_dbwrap_watch1(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db = NULL;
+ const char *keystr = "key";
+ TDB_DATA key = string_term_tdb_data(keystr);
+ struct db_record *rec = NULL;
+ struct tevent_req *req = NULL;
+ NTSTATUS status;
+ bool ret = false;
+
+ ret = test_dbwrap_watch_init(
+ talloc_tos(), "test_watch.tdb", &ev, &msg, &backend, &db);
+ if (!ret) {
+ goto fail;
+ }
+
+ rec = dbwrap_fetch_locked(db, db, key);
+ if (rec == NULL) {
+ fprintf(stderr, "dbwrap_fetch_locked failed\n");
+ goto fail;
+ }
+ req = dbwrap_watched_watch_send(talloc_tos(), ev, rec,
+ 0, /* resume_instance */
+ (struct server_id){0});
+ if (req == NULL) {
+ fprintf(stderr, "dbwrap_record_watch_send failed\n");
+ goto fail;
+ }
+ TALLOC_FREE(rec);
+
+ status = dbwrap_store_int32_bystring(db, "different_key", 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "dbwrap_store_int32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = dbwrap_store_int32_bystring(db, keystr, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "dbwrap_store_int32 failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!tevent_req_poll(req, ev)) {
+ fprintf(stderr, "tevent_req_poll failed\n");
+ goto fail;
+ }
+
+ status = dbwrap_watched_watch_recv(req, NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "dbwrap_record_watch_recv failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ (void)unlink("test_watch.tdb");
+ ret = true;
+fail:
+ TALLOC_FREE(req);
+ TALLOC_FREE(rec);
+ TALLOC_FREE(db);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+/*
+ * Make sure dbwrap_parse_record does not return NT_STATUS_OK on
+ * invalid data
+ */
+
+bool run_dbwrap_watch2(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db = NULL;
+ const char *keystr = "key";
+ TDB_DATA key = string_term_tdb_data(keystr);
+ NTSTATUS status;
+ bool ret = false;
+
+ ret = test_dbwrap_watch_init(
+ talloc_tos(), "test_watch.tdb", &ev, &msg, &backend, &db);
+ if (!ret) {
+ goto fail;
+ }
+
+ /*
+ * Store invalid data (from the dbwrap_watch point of view)
+ * directly into the backend database
+ */
+ status = dbwrap_store_uint32_bystring(backend, keystr, UINT32_MAX);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "dbwrap_store_uint32_bystring failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = dbwrap_parse_record(db, key, NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ fprintf(stderr, "dbwrap_parse_record returned %s, expected "
+ "NT_STATUS_NOT_FOUND\n", nt_errstr(status));
+ goto fail;
+ }
+
+ (void)unlink("test_watch.tdb");
+ ret = true;
+fail:
+ TALLOC_FREE(db);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+/*
+ * Test autocleanup of dead watchers
+ */
+
+bool run_dbwrap_watch3(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db = NULL;
+ const char *keystr = "key";
+ TDB_DATA key = string_term_tdb_data(keystr);
+ NTSTATUS status;
+ bool ret = false;
+ pid_t child, waited;
+ int wstatus, exit_status;
+
+ BlockSignals(true, SIGCHLD);
+
+ child = fork();
+ if (child == -1) {
+ fprintf(stderr,
+ "fork failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+
+ ret = test_dbwrap_watch_init(
+ talloc_tos(), "test_watch.tdb", &ev, &msg, &backend, &db);
+ if (!ret) {
+ goto fail;
+ }
+
+ if (child == 0) {
+ struct db_record *rec = dbwrap_fetch_locked(db, db, key);
+ struct tevent_req *req = NULL;
+
+ if (rec == NULL) {
+ fprintf(stderr, "dbwrap_fetch_locked failed\n");
+ exit(1);
+ }
+
+ req = dbwrap_watched_watch_send(
+ db, ev, rec, 0, (struct server_id) { 0 });
+ if (req == NULL) {
+ fprintf(stderr, "dbwrap_watched_watch_send failed\n");
+ exit(2);
+ }
+
+ exit(0);
+ }
+
+ waited = waitpid(child, &wstatus, 0);
+ if (waited == -1) {
+ fprintf(stderr, "waitpid failed: %s\n", strerror(errno));
+ goto fail;
+ }
+ if (!WIFEXITED(wstatus)) {
+ fprintf(stderr, "child did not exit normally\n");
+ goto fail;
+ }
+
+ exit_status = WEXITSTATUS(wstatus);
+ if (exit_status != 0) {
+ fprintf(stderr, "exit status is %d\n", exit_status);
+ goto fail;
+ }
+
+ status = dbwrap_store_uint32_bystring(db, keystr, 1);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+ fprintf(stderr,
+ "dbwrap_store_uint32 returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ (void)unlink("test_watch.tdb");
+ ret = true;
+fail:
+ TALLOC_FREE(db);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+/*
+ * Test that we can't add two watchers in the same
+ * fetch_lock/do_locked round
+ */
+
+struct dbwrap_watch4_state {
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev;
+ struct db_context *db;
+ TDB_DATA key;
+
+ NTSTATUS status;
+
+ struct tevent_req *req1;
+ NTSTATUS status1;
+
+ struct tevent_req *req2;
+ NTSTATUS status2;
+};
+
+static void dbwrap_watch4_done1(struct tevent_req *subreq);
+static void dbwrap_watch4_done2(struct tevent_req *subreq);
+
+static void dbwrap_watch4_fn(struct db_record *rec,
+ TDB_DATA value,
+ void *private_data)
+{
+ struct dbwrap_watch4_state *state = private_data;
+ bool ok;
+
+ state->req1 = dbwrap_watched_watch_send(
+ state->mem_ctx, state->ev, rec, 0, (struct server_id) { .pid=0 });
+ if (state->req1 == NULL) {
+ goto nomem;
+ }
+ tevent_req_set_callback(state->req1, dbwrap_watch4_done1, state);
+ state->status1 = NT_STATUS_EVENT_PENDING;
+
+ ok = tevent_req_set_endtime(
+ state->req1, state->ev, timeval_current_ofs(1, 0));
+ if (!ok) {
+ goto nomem;
+ }
+
+ state->req2 = dbwrap_watched_watch_send(
+ state->mem_ctx, state->ev, rec, 0, (struct server_id) { .pid=0 });
+ if (state->req2 == NULL) {
+ goto nomem;
+ }
+ tevent_req_set_callback(state->req2, dbwrap_watch4_done2, state);
+ state->status2 = NT_STATUS_EVENT_PENDING;
+
+ ok = tevent_req_set_endtime(
+ state->req2, state->ev, timeval_current_ofs(1, 0));
+ if (!ok) {
+ goto nomem;
+ }
+
+ state->status = NT_STATUS_OK;
+ return;
+
+ nomem:
+ state->status = NT_STATUS_NO_MEMORY;
+}
+
+static void dbwrap_watch4_done1(struct tevent_req *subreq)
+{
+ struct dbwrap_watch4_state *state = tevent_req_callback_data_void(subreq);
+ state->status1 = dbwrap_watched_watch_recv(subreq, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ printf("req1 finished: %s\n", nt_errstr(state->status1));
+ state->req1 = NULL;
+}
+
+static void dbwrap_watch4_done2(struct tevent_req *subreq)
+{
+ struct dbwrap_watch4_state *state = tevent_req_callback_data_void(subreq);
+ state->status2 = dbwrap_watched_watch_recv(subreq, NULL, NULL, NULL);
+ TALLOC_FREE(subreq);
+ printf("req2 finished: %s\n", nt_errstr(state->status2));
+ state->req2 = NULL;
+}
+
+bool run_dbwrap_watch4(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct db_context *backend = NULL;
+ struct db_context *db = NULL;
+ const char *keystr = "key";
+ TDB_DATA key = string_term_tdb_data(keystr);
+ struct dbwrap_watch4_state state = { 0 };
+ NTSTATUS status;
+ bool ret = false;
+ bool ok;
+
+ ok = test_dbwrap_watch_init(
+ talloc_tos(), "test_watch.tdb", &ev, &msg, &backend, &db);
+ if (!ok) {
+ goto fail;
+ }
+
+ state = (struct dbwrap_watch4_state) {
+ .mem_ctx = talloc_tos(),
+ .ev = ev,
+ .db = db,
+ .key = key,
+ };
+
+ status = dbwrap_do_locked(db, key, dbwrap_watch4_fn, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "dbwrap_do_locked failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(state.status)) {
+ fprintf(stderr,
+ "dbwrap_watch4_fn failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = dbwrap_store(db, key, key, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "dbwrap_store failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ while (NT_STATUS_EQUAL(state.status1, NT_STATUS_EVENT_PENDING) ||
+ NT_STATUS_EQUAL(state.status2, NT_STATUS_EVENT_PENDING)) {
+ int res = tevent_loop_once(ev);
+ if (res != 0) {
+ fprintf(stderr,
+ "tevent_loop_once failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(state.status1)) {
+ fprintf(stderr,
+ "req1 returned %s\n",
+ nt_errstr(state.status1));
+ goto fail;
+ }
+
+ if (!NT_STATUS_EQUAL(state.status2, NT_STATUS_REQUEST_NOT_ACCEPTED)) {
+ fprintf(stderr,
+ "req2 returned %s\n",
+ nt_errstr(state.status2));
+ goto fail;
+ }
+
+ (void)unlink("test_watch.tdb");
+ ret = true;
+fail:
+ TALLOC_FREE(state.req2);
+ TALLOC_FREE(state.req1);
+ TALLOC_FREE(db);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
diff --git a/source3/torture/test_g_lock.c b/source3/torture/test_g_lock.c
new file mode 100644
index 0000000..fee9127
--- /dev/null
+++ b/source3/torture/test_g_lock.c
@@ -0,0 +1,1403 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test g_lock API
+ * Copyright (C) Volker Lendecke 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "system/filesys.h"
+#include "g_lock.h"
+#include "messages.h"
+#include "lib/util/server_id.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/util_tdb.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/global_contexts.h"
+
+static bool get_g_lock_ctx(TALLOC_CTX *mem_ctx,
+ struct tevent_context **ev,
+ struct messaging_context **msg,
+ struct g_lock_ctx **ctx)
+{
+ *ev = global_event_context();
+ if (*ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ return false;
+ }
+ *msg = global_messaging_context();
+ if (*msg == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ TALLOC_FREE(*ev);
+ return false;
+ }
+ *ctx = g_lock_ctx_init(*ev, *msg);
+ if (*ctx == NULL) {
+ fprintf(stderr, "g_lock_ctx_init failed\n");
+ TALLOC_FREE(*msg);
+ TALLOC_FREE(*ev);
+ return false;
+ }
+
+ return true;
+}
+
+bool run_g_lock1(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ const char *lockname = "lock1";
+ NTSTATUS status;
+ bool ret = false;
+ bool ok;
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ status = g_lock_lock(ctx, string_term_tdb_data(lockname), G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_lock(ctx, string_term_tdb_data(lockname), G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_WAS_LOCKED)) {
+ fprintf(stderr, "Double lock got %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_unlock(ctx, string_term_tdb_data(lockname));
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_unlock failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_unlock(ctx, string_term_tdb_data(lockname));
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ fprintf(stderr, "g_lock_unlock returned: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+struct lock2_parser_state {
+ uint8_t *rdata;
+ bool ok;
+};
+
+static void lock2_parser(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct lock2_parser_state *state = private_data;
+
+ if (datalen != sizeof(uint8_t)) {
+ return;
+ }
+ *state->rdata = *data;
+ state->ok = true;
+}
+
+/*
+ * Test g_lock_write_data
+ */
+
+bool run_g_lock2(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ const char *lockname = "lock2";
+ uint8_t data = 42;
+ uint8_t rdata;
+ struct lock2_parser_state state = { .rdata = &rdata };
+ NTSTATUS status;
+ bool ret = false;
+ bool ok;
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ status = g_lock_write_data(ctx, string_term_tdb_data(lockname),
+ &data, sizeof(data));
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_LOCKED)) {
+ fprintf(stderr, "unlocked g_lock_write_data returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_lock(ctx, string_term_tdb_data(lockname), G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_write_data(ctx, string_term_tdb_data(lockname),
+ &data, sizeof(data));
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_write_data failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_unlock(ctx, string_term_tdb_data(lockname));
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_unlock failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_dump(ctx, string_term_tdb_data(lockname),
+ lock2_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_dump failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!state.ok) {
+ fprintf(stderr, "Could not parse data\n");
+ goto fail;
+ }
+ if (rdata != data) {
+ fprintf(stderr, "Returned %"PRIu8", expected %"PRIu8"\n",
+ rdata, data);
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+struct lock3_parser_state {
+ struct server_id self;
+ enum g_lock_type lock_type;
+ bool ok;
+};
+
+static void lock3_parser(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct lock3_parser_state *state = private_data;
+ size_t num_locks = num_shared + ((exclusive.pid != 0) ? 1 : 0);
+ const struct server_id *pid;
+
+ if (datalen != 0) {
+ fprintf(stderr, "datalen=%zu\n", datalen);
+ return;
+ }
+ if (num_locks != 1) {
+ fprintf(stderr, "num_locks=%zu\n", num_locks);
+ return;
+ }
+
+ if (state->lock_type == G_LOCK_WRITE) {
+ if (exclusive.pid == 0) {
+ fprintf(stderr, "Found READ, expected WRITE\n");
+ return;
+ }
+ } else {
+ if (exclusive.pid != 0) {
+ fprintf(stderr, "Found WRITE, expected READ\n");
+ return;
+ }
+ }
+
+ pid = (exclusive.pid != 0) ? &exclusive : &shared[0];
+
+ if (!server_id_equal(pid, &state->self)) {
+ struct server_id_buf tmp1, tmp2;
+ fprintf(stderr, "found pid %s, expected %s\n",
+ server_id_str_buf(*pid, &tmp1),
+ server_id_str_buf(state->self, &tmp2));
+ return;
+ }
+
+ state->ok = true;
+}
+
+/*
+ * Test lock upgrade/downgrade
+ */
+
+bool run_g_lock3(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ const char *lockname = "lock3";
+ struct lock3_parser_state state;
+ NTSTATUS status;
+ bool ret = false;
+ bool ok;
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ state.self = messaging_server_id(msg);
+
+ status = g_lock_lock(ctx, string_term_tdb_data(lockname), G_LOCK_READ,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ state.lock_type = G_LOCK_READ;
+ state.ok = false;
+
+ status = g_lock_dump(ctx, string_term_tdb_data(lockname),
+ lock3_parser, &state);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+ fprintf(stderr, "g_lock_dump returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!state.ok) {
+ goto fail;
+ }
+
+ status = g_lock_lock(ctx, string_term_tdb_data(lockname), G_LOCK_UPGRADE,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ state.lock_type = G_LOCK_WRITE;
+ state.ok = false;
+
+ status = g_lock_dump(ctx, string_term_tdb_data(lockname),
+ lock3_parser, &state);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+ fprintf(stderr, "g_lock_dump returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!state.ok) {
+ goto fail;
+ }
+
+
+ ret = true;
+fail:
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+static bool lock4_child(const char *lockname,
+ enum g_lock_type lock_type,
+ int ready_pipe,
+ int exit_pipe)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ NTSTATUS status;
+ ssize_t n;
+ bool ok;
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ return false;
+ }
+
+ status = g_lock_lock(
+ ctx,
+ string_term_tdb_data(lockname),
+ lock_type,
+ (struct timeval) { .tv_sec = 1 },
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "child: g_lock_lock returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ n = sys_write(ready_pipe, &ok, sizeof(ok));
+ if (n != sizeof(ok)) {
+ fprintf(stderr, "child: write failed\n");
+ return false;
+ }
+
+ if (ok) {
+ n = sys_read(exit_pipe, &ok, sizeof(ok));
+ if (n != 0) {
+ fprintf(stderr, "child: read failed\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void lock4_done(struct tevent_req *subreq)
+{
+ int *done = tevent_req_callback_data_void(subreq);
+ NTSTATUS status;
+
+ status = g_lock_lock_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock_recv returned %s\n",
+ nt_errstr(status));
+ *done = -1;
+ return;
+ }
+ *done = 1;
+}
+
+static void lock4_waited(struct tevent_req *subreq)
+{
+ int *exit_pipe = tevent_req_callback_data_void(subreq);
+ pid_t child;
+ int status;
+ bool ok;
+
+ printf("waited\n");
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ fprintf(stderr, "tevent_wakeup_recv failed\n");
+ }
+ close(*exit_pipe);
+
+ child = wait(&status);
+
+ printf("child %d exited with %d\n", (int)child, status);
+}
+
+struct lock4_check_state {
+ struct server_id me;
+ bool ok;
+};
+
+static void lock4_check(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct lock4_check_state *state = private_data;
+ size_t num_locks = num_shared + ((exclusive.pid != 0) ? 1 : 0);
+
+ if (num_locks != 1) {
+ fprintf(stderr, "num_locks=%zu\n", num_locks);
+ return;
+ }
+
+ if (exclusive.pid == 0) {
+ fprintf(stderr, "Wrong lock type, not WRITE\n");
+ return;
+ }
+
+ if (!server_id_equal(&state->me, &exclusive)) {
+ struct server_id_buf buf1, buf2;
+ fprintf(stderr, "me=%s, locker=%s\n",
+ server_id_str_buf(state->me, &buf1),
+ server_id_str_buf(exclusive, &buf2));
+ return;
+ }
+
+ state->ok = true;
+}
+
+/*
+ * Test a lock conflict: Contend with a WRITE lock
+ */
+
+bool run_g_lock4(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ const char *lockname = "lock4";
+ TDB_DATA key = string_term_tdb_data(lockname);
+ pid_t child;
+ int ready_pipe[2];
+ int exit_pipe[2];
+ NTSTATUS status;
+ bool ret = false;
+ struct tevent_req *req;
+ bool ok;
+ int done;
+
+ if ((pipe(ready_pipe) != 0) || (pipe(exit_pipe) != 0)) {
+ perror("pipe failed");
+ return false;
+ }
+
+ child = fork();
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (child == -1) {
+ perror("fork failed");
+ return false;
+ }
+
+ if (child == 0) {
+ close(ready_pipe[0]);
+ close(exit_pipe[1]);
+ ok = lock4_child(
+ lockname, G_LOCK_WRITE, ready_pipe[1], exit_pipe[0]);
+ exit(ok ? 0 : 1);
+ }
+
+ close(ready_pipe[1]);
+ close(exit_pipe[0]);
+
+ if (sys_read(ready_pipe[0], &ok, sizeof(ok)) != sizeof(ok)) {
+ perror("read failed");
+ return false;
+ }
+
+ if (!ok) {
+ fprintf(stderr, "child returned error\n");
+ return false;
+ }
+
+ status = g_lock_lock(ctx, key, G_LOCK_WRITE,
+ (struct timeval) { .tv_usec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ fprintf(stderr, "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_lock(ctx, key, G_LOCK_READ,
+ (struct timeval) { .tv_usec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ fprintf(stderr, "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ req = g_lock_lock_send(ev, ev, ctx, key, G_LOCK_WRITE, NULL, NULL);
+ if (req == NULL) {
+ fprintf(stderr, "g_lock_lock send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, lock4_done, &done);
+
+ req = tevent_wakeup_send(ev, ev, timeval_current_ofs(1, 0));
+ if (req == NULL) {
+ fprintf(stderr, "tevent_wakeup_send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, lock4_waited, &exit_pipe[1]);
+
+ done = 0;
+
+ while (done == 0) {
+ int tevent_ret = tevent_loop_once(ev);
+ if (tevent_ret != 0) {
+ perror("tevent_loop_once failed");
+ goto fail;
+ }
+ }
+
+ {
+ struct lock4_check_state state = {
+ .me = messaging_server_id(msg)
+ };
+
+ status = g_lock_dump(ctx, key, lock4_check, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_dump failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!state.ok) {
+ fprintf(stderr, "lock4_check failed\n");
+ goto fail;
+ }
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+/*
+ * Test a lock conflict: Contend with a READ lock
+ */
+
+bool run_g_lock4a(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ const char *lockname = "lock4a";
+ TDB_DATA key = string_term_tdb_data(lockname);
+ pid_t child;
+ int ready_pipe[2];
+ int exit_pipe[2];
+ NTSTATUS status;
+ bool ret = false;
+ struct tevent_req *req;
+ bool ok;
+ int done;
+
+ if ((pipe(ready_pipe) != 0) || (pipe(exit_pipe) != 0)) {
+ perror("pipe failed");
+ return false;
+ }
+
+ child = fork();
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (child == -1) {
+ perror("fork failed");
+ return false;
+ }
+
+ if (child == 0) {
+ close(ready_pipe[0]);
+ close(exit_pipe[1]);
+ ok = lock4_child(
+ lockname, G_LOCK_READ, ready_pipe[1], exit_pipe[0]);
+ exit(ok ? 0 : 1);
+ }
+
+ close(ready_pipe[1]);
+ close(exit_pipe[0]);
+
+ if (sys_read(ready_pipe[0], &ok, sizeof(ok)) != sizeof(ok)) {
+ perror("read failed");
+ return false;
+ }
+
+ if (!ok) {
+ fprintf(stderr, "child returned error\n");
+ return false;
+ }
+
+ status = g_lock_lock(ctx, key, G_LOCK_WRITE,
+ (struct timeval) { .tv_usec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ fprintf(stderr, "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_lock(ctx, key, G_LOCK_READ,
+ (struct timeval) { .tv_usec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = g_lock_unlock(ctx, key);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "g_lock_unlock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ req = g_lock_lock_send(ev, ev, ctx, key, G_LOCK_WRITE, NULL, NULL);
+ if (req == NULL) {
+ fprintf(stderr, "g_lock_lock send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, lock4_done, &done);
+
+ req = tevent_wakeup_send(ev, ev, timeval_current_ofs(1, 0));
+ if (req == NULL) {
+ fprintf(stderr, "tevent_wakeup_send failed\n");
+ goto fail;
+ }
+ tevent_req_set_callback(req, lock4_waited, &exit_pipe[1]);
+
+ done = 0;
+
+ while (done == 0) {
+ int tevent_ret = tevent_loop_once(ev);
+ if (tevent_ret != 0) {
+ perror("tevent_loop_once failed");
+ goto fail;
+ }
+ }
+
+ {
+ struct lock4_check_state state = {
+ .me = messaging_server_id(msg)
+ };
+
+ status = g_lock_dump(ctx, key, lock4_check, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_dump failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!state.ok) {
+ fprintf(stderr, "lock4_check failed\n");
+ goto fail;
+ }
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+struct lock5_parser_state {
+ size_t num_locks;
+};
+
+static void lock5_parser(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct lock5_parser_state *state = private_data;
+ state->num_locks = num_shared + ((exclusive.pid != 0) ? 1 : 0);
+}
+
+/*
+ * Test heuristic cleanup
+ */
+
+bool run_g_lock5(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ const char *lockname = "lock5";
+ pid_t child;
+ int exit_pipe[2], ready_pipe[2];
+ NTSTATUS status;
+ size_t i, nprocs;
+ int ret;
+ bool ok;
+ ssize_t nread;
+ char c;
+
+ nprocs = 5;
+
+ if ((pipe(exit_pipe) != 0) || (pipe(ready_pipe) != 0)) {
+ perror("pipe failed");
+ return false;
+ }
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ fprintf(stderr, "get_g_lock_ctx failed");
+ return false;
+ }
+
+ for (i=0; i<nprocs; i++) {
+
+ child = fork();
+
+ if (child == -1) {
+ perror("fork failed");
+ return false;
+ }
+
+ if (child == 0) {
+ TALLOC_FREE(ctx);
+
+ status = reinit_after_fork(msg, ev, false);
+
+ close(ready_pipe[0]);
+ close(exit_pipe[1]);
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ fprintf(stderr, "get_g_lock_ctx failed");
+ exit(1);
+ }
+ status = g_lock_lock(ctx,
+ string_term_tdb_data(lockname),
+ G_LOCK_READ,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "child g_lock_lock failed %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+ close(ready_pipe[1]);
+ nread = sys_read(exit_pipe[0], &c, sizeof(c));
+ if (nread != 0) {
+ fprintf(stderr, "sys_read returned %zu (%s)\n",
+ nread, strerror(errno));
+ exit(1);
+ }
+ exit(0);
+ }
+ }
+
+ close(ready_pipe[1]);
+
+ nread = sys_read(ready_pipe[0], &c, sizeof(c));
+ if (nread != 0) {
+ fprintf(stderr, "sys_read returned %zu (%s)\n",
+ nread, strerror(errno));
+ return false;
+ }
+
+ close(exit_pipe[1]);
+
+ for (i=0; i<nprocs; i++) {
+ int child_status;
+ ret = waitpid(-1, &child_status, 0);
+ if (ret == -1) {
+ perror("waitpid failed");
+ return false;
+ }
+ }
+
+ for (i=0; i<nprocs; i++) {
+ struct lock5_parser_state state;
+
+ status = g_lock_dump(ctx, string_term_tdb_data(lockname),
+ lock5_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_dump returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (state.num_locks != (nprocs - i)) {
+ fprintf(stderr, "nlocks=%zu, expected %zu\n",
+ state.num_locks, (nprocs-i));
+ return false;
+ }
+
+ status = g_lock_lock(ctx, string_term_tdb_data(lockname),
+ G_LOCK_READ,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock failed %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ status = g_lock_unlock(ctx, string_term_tdb_data(lockname));
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_unlock failed %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ }
+
+
+ return true;
+}
+
+struct lock6_parser_state {
+ size_t num_locks;
+};
+
+static void lock6_parser(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct lock6_parser_state *state = private_data;
+ state->num_locks = num_shared + ((exclusive.pid != 0) ? 1 : 0);
+}
+
+/*
+ * Test cleanup with contention and stale locks
+ */
+
+bool run_g_lock6(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ TDB_DATA lockname = string_term_tdb_data("lock6");
+ pid_t child;
+ int exit_pipe[2], ready_pipe[2];
+ NTSTATUS status;
+ size_t i, nprocs;
+ int ret;
+ bool ok;
+ ssize_t nread;
+ char c;
+
+ if ((pipe(exit_pipe) != 0) || (pipe(ready_pipe) != 0)) {
+ perror("pipe failed");
+ return false;
+ }
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ fprintf(stderr, "get_g_lock_ctx failed");
+ return false;
+ }
+
+ /*
+ * Wipe all stale locks -- in clustered mode there's no
+ * CLEAR_IF_FIRST
+ */
+ status = g_lock_lock(ctx, lockname, G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ status = g_lock_unlock(ctx, lockname);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_unlock failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ nprocs = 2;
+ for (i=0; i<nprocs; i++) {
+
+ child = fork();
+
+ if (child == -1) {
+ perror("fork failed");
+ return false;
+ }
+
+ if (child == 0) {
+ TALLOC_FREE(ctx);
+
+ status = reinit_after_fork(msg, ev, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "reinit_after_fork failed: %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+
+ close(ready_pipe[0]);
+ close(exit_pipe[1]);
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ fprintf(stderr, "get_g_lock_ctx failed");
+ exit(1);
+ }
+ status = g_lock_lock(ctx,
+ lockname,
+ G_LOCK_READ,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "child g_lock_lock failed %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+ if (i == 0) {
+ exit(0);
+ }
+ close(ready_pipe[1]);
+ nread = sys_read(exit_pipe[0], &c, sizeof(c));
+ if (nread != 0) {
+ fprintf(stderr, "sys_read returned %zu (%s)\n",
+ nread, strerror(errno));
+ exit(1);
+ }
+ exit(0);
+ }
+ }
+
+ close(ready_pipe[1]);
+
+ nread = sys_read(ready_pipe[0], &c, sizeof(c));
+ if (nread != 0) {
+ fprintf(stderr, "sys_read returned %zd (%s)\n",
+ nread, strerror(errno));
+ return false;
+ }
+
+ {
+ int child_status;
+ ret = waitpid(-1, &child_status, 0);
+ if (ret == -1) {
+ perror("waitpid failed");
+ return false;
+ }
+ }
+
+ {
+ struct lock6_parser_state state;
+
+ status = g_lock_dump(ctx, lockname, lock6_parser, &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_dump returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (state.num_locks != nprocs) {
+ fprintf(stderr, "nlocks=%zu, expected %zu\n",
+ state.num_locks, nprocs);
+ return false;
+ }
+
+ status = g_lock_lock(ctx,
+ lockname,
+ G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
+ fprintf(stderr, "g_lock_lock should have failed with %s - %s\n",
+ nt_errstr(NT_STATUS_IO_TIMEOUT),
+ nt_errstr(status));
+ return false;
+ }
+
+ status = g_lock_lock(ctx, lockname, G_LOCK_READ,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ }
+
+ close(exit_pipe[1]);
+
+ {
+ int child_status;
+ ret = waitpid(-1, &child_status, 0);
+ if (ret == -1) {
+ perror("waitpid failed");
+ return false;
+ }
+ }
+
+ status = g_lock_lock(ctx, lockname, G_LOCK_UPGRADE,
+ (struct timeval) { .tv_sec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Test upgrade deadlock
+ */
+
+bool run_g_lock7(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ const char *lockname = "lock7";
+ TDB_DATA key = string_term_tdb_data(lockname);
+ pid_t child;
+ int ready_pipe[2];
+ int down_pipe[2];
+ ssize_t n;
+ NTSTATUS status;
+ bool ret = false;
+ bool ok = true;
+
+ if ((pipe(ready_pipe) != 0) || (pipe(down_pipe) != 0)) {
+ perror("pipe failed");
+ return false;
+ }
+
+ child = fork();
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (child == -1) {
+ perror("fork failed");
+ return false;
+ }
+
+ if (child == 0) {
+ struct tevent_req *req = NULL;
+
+ close(ready_pipe[0]);
+ ready_pipe[0] = -1;
+ close(down_pipe[1]);
+ down_pipe[1] = -1;
+
+ status = reinit_after_fork(msg, ev, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "reinit_after_fork failed: %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+
+ printf("%d: locking READ\n", (int)getpid());
+
+ status = g_lock_lock(
+ ctx,
+ key,
+ G_LOCK_READ,
+ (struct timeval) { .tv_usec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "g_lock_lock(READ) failed: %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+
+ ok = true;
+
+ n = sys_write(ready_pipe[1], &ok, sizeof(ok));
+ if (n != sizeof(ok)) {
+ fprintf(stderr,
+ "sys_write failed: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ n = sys_read(down_pipe[0], &ok, sizeof(ok));
+ if (n != sizeof(ok)) {
+ fprintf(stderr,
+ "sys_read failed: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ printf("%d: starting UPGRADE\n", (int)getpid());
+
+ req = g_lock_lock_send(
+ msg,
+ ev,
+ ctx,
+ key,
+ G_LOCK_UPGRADE,
+ NULL, NULL);
+ if (req == NULL) {
+ fprintf(stderr, "g_lock_lock_send(UPGRADE) failed\n");
+ exit(1);
+ }
+
+ n = sys_write(ready_pipe[1], &ok, sizeof(ok));
+ if (n != sizeof(ok)) {
+ fprintf(stderr,
+ "sys_write failed: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ exit(0);
+ }
+
+ close(ready_pipe[1]);
+ close(down_pipe[0]);
+
+ if (sys_read(ready_pipe[0], &ok, sizeof(ok)) != sizeof(ok)) {
+ perror("read failed");
+ return false;
+ }
+ if (!ok) {
+ fprintf(stderr, "child returned error\n");
+ return false;
+ }
+
+ status = g_lock_lock(
+ ctx,
+ key,
+ G_LOCK_READ,
+ (struct timeval) { .tv_usec = 1 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "g_lock_lock(READ) failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ n = sys_write(down_pipe[1], &ok, sizeof(ok));
+ if (n != sizeof(ok)) {
+ fprintf(stderr,
+ "sys_write failed: %s\n",
+ strerror(errno));
+ goto fail;
+ }
+
+ if (sys_read(ready_pipe[0], &ok, sizeof(ok)) != sizeof(ok)) {
+ perror("read failed");
+ goto fail;
+ }
+
+ status = g_lock_lock(
+ ctx,
+ key,
+ G_LOCK_UPGRADE,
+ (struct timeval) { .tv_sec = 10 },
+ NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_POSSIBLE_DEADLOCK)) {
+ fprintf(stderr,
+ "g_lock_lock returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+bool run_g_lock8(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ struct tevent_req *req = NULL;
+ TDB_DATA lockname = string_term_tdb_data("lock8");
+ NTSTATUS status;
+ bool ok;
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ fprintf(stderr, "get_g_lock_ctx failed");
+ return false;
+ }
+
+ req = g_lock_watch_data_send(
+ ev, ev, ctx, lockname, (struct server_id) { .pid = 0 });
+ if (req == NULL) {
+ fprintf(stderr, "get_g_lock_ctx failed");
+ return false;
+ }
+
+ status = g_lock_lock(
+ ctx,
+ lockname,
+ G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 999 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = g_lock_write_data(
+ ctx, lockname, lockname.dptr, lockname.dsize);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "g_lock_write_data failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = g_lock_write_data(ctx, lockname, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "g_lock_write_data failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = g_lock_unlock(ctx, lockname);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "g_lock_unlock failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ fprintf(stderr, "tevent_req_poll_ntstatus failed\n");
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "tevent_req_poll_ntstatus failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+extern int torture_numops;
+extern int torture_nprocs;
+
+static struct timeval tp1, tp2;
+
+static void start_timer(void)
+{
+ gettimeofday(&tp1,NULL);
+}
+
+static double end_timer(void)
+{
+ gettimeofday(&tp2,NULL);
+ return (tp2.tv_sec + (tp2.tv_usec*1.0e-6)) -
+ (tp1.tv_sec + (tp1.tv_usec*1.0e-6));
+}
+
+/*
+ * g_lock ping_pong
+ */
+
+bool run_g_lock_ping_pong(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *ctx = NULL;
+ fstring name;
+ NTSTATUS status;
+ int i = 0;
+ bool ret = false;
+ bool ok;
+ unsigned count = 0;
+
+ torture_nprocs = MAX(2, torture_nprocs);
+
+ ok = get_g_lock_ctx(talloc_tos(), &ev, &msg, &ctx);
+ if (!ok) {
+ goto fail;
+ }
+
+ start_timer();
+
+ snprintf(name, sizeof(name), "ping_pong_%d", i);
+
+ status = g_lock_lock(ctx, string_term_tdb_data(name), G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 60 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ for (i=0; i<torture_numops; i++) {
+
+ name[10] = '0' + ((i+1) % torture_nprocs);
+
+ status = g_lock_lock(ctx, string_term_tdb_data(name),
+ G_LOCK_WRITE,
+ (struct timeval) { .tv_sec = 60 },
+ NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_lock failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ name[10] = '0' + ((i) % torture_nprocs);
+
+ status = g_lock_unlock(ctx, string_term_tdb_data(name));
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "g_lock_unlock failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ count++;
+
+ if (end_timer() > 1.0) {
+ printf("%8u locks/sec\r",
+ (unsigned)(2*count/end_timer()));
+ fflush(stdout);
+ start_timer();
+ count=0;
+ }
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
diff --git a/source3/torture/test_hidenewfiles.c b/source3/torture/test_hidenewfiles.c
new file mode 100644
index 0000000..6d6811c
--- /dev/null
+++ b/source3/torture/test_hidenewfiles.c
@@ -0,0 +1,234 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test "hide new files timeout"
+ * Copyright (C) Volker Lendecke 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/security.h"
+
+static NTSTATUS servertime(
+ struct cli_state *cli, const char *fname, struct timeval *tv)
+{
+ struct smb_create_returns cr;
+ NTSTATUS status;
+ uint16_t fnum;
+
+ status = cli_ntcreate(
+ cli,
+ fname,
+ 0,
+ FILE_GENERIC_WRITE|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ 0,
+ FILE_CREATE,
+ FILE_DELETE_ON_CLOSE,
+ 0,
+ &fnum,
+ &cr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_ntcreate failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_close failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ nttime_to_timeval(tv, cr.creation_time);
+
+ return NT_STATUS_OK;
+}
+
+struct have_file_state {
+ bool found;
+ const char *fname;
+};
+
+static NTSTATUS have_file_fn(struct file_info *f,
+ const char *mask,
+ void *private_data)
+{
+ struct have_file_state *state = private_data;
+ state->found |= strequal(f->name, state->fname);
+ return NT_STATUS_OK;
+}
+
+static bool have_file(struct cli_state *cli, const char *fname)
+{
+ struct have_file_state state = { .fname = fname };
+ NTSTATUS status;
+
+ status = cli_list(
+ cli,
+ "*",
+ FILE_ATTRIBUTE_DIRECTORY|
+ FILE_ATTRIBUTE_SYSTEM|
+ FILE_ATTRIBUTE_HIDDEN,
+ have_file_fn,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_list failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ return state.found;
+}
+
+bool run_hidenewfiles(int dummy)
+{
+ const char *tsname = "timestamp.txt";
+ const char *fname = "new_hidden.txt";
+ struct cli_state *cli;
+ struct smb_create_returns cr;
+ struct timeval create_time;
+ uint16_t fnum;
+ NTSTATUS status;
+ bool ret = false;
+ bool gotit = false;
+ bool ok;
+
+ /* what is configured in smb.conf */
+ unsigned hideunreadable_seconds = 5;
+
+ ok = torture_open_connection_flags(&cli, 0, 0);
+ if (!ok) {
+ return false;
+ }
+
+ cli_unlink(cli, tsname, FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(
+ cli,
+ fname,
+ 0,
+ FILE_GENERIC_WRITE|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ 0,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ &cr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_ntcreate failed: %s\n", nt_errstr(status));
+ return false;
+ }
+ nttime_to_timeval(&create_time, cr.last_write_time);
+
+ while (!gotit) {
+ struct timeval now;
+ double age;
+
+ gotit = have_file(cli, fname);
+
+ status = servertime(cli, tsname, &now);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("servertime failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ age = timeval_elapsed2(&create_time, &now);
+
+ if ((age < hideunreadable_seconds) && gotit) {
+ d_printf("Found file at age of %f\n", age);
+ goto fail;
+ }
+ if ((age > (hideunreadable_seconds*10)) && !gotit) {
+ d_printf("Did not find file after %f seconds\n", age);
+ goto fail;
+ }
+ if (gotit) {
+ break;
+ }
+
+ smb_msleep(1000);
+ }
+
+ ret = true;
+fail:
+ cli_nt_delete_on_close(cli, fnum, true);
+ cli_close(cli, fnum);
+
+ return ret;
+}
+
+bool run_hidenewfiles_showdirs(int dummy)
+{
+ const char *dname = "dir";
+ const char *fname = "dir/x.txt";
+ struct cli_state *cli;
+ struct smb_create_returns cr;
+ struct timeval create_time;
+ uint16_t fnum = UINT16_MAX;
+ NTSTATUS status;
+ bool ret = false;
+ bool gotit = false;
+ bool ok;
+
+ ok = torture_open_connection_flags(&cli, 0, 0);
+ if (!ok) {
+ return false;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, dname);
+
+ status = cli_mkdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("mkdir(%s) failed: %s\n", dname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_ntcreate(
+ cli,
+ fname,
+ 0,
+ FILE_GENERIC_WRITE|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ 0,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ &cr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_ntcreate failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+ nttime_to_timeval(&create_time, cr.last_write_time);
+
+ gotit = have_file(cli, dname);
+ if (!gotit) {
+ d_printf("%s was hidden\n", dname);
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ if (fnum != UINT16_MAX) {
+ cli_nt_delete_on_close(cli, fnum, true);
+ cli_close(cli, fnum);
+ }
+ cli_rmdir(cli, dname);
+
+ return ret;
+}
diff --git a/source3/torture/test_idmap_cache.c b/source3/torture/test_idmap_cache.c
new file mode 100644
index 0000000..b9cba3b
--- /dev/null
+++ b/source3/torture/test_idmap_cache.c
@@ -0,0 +1,122 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test dbwrap_watch API
+ * Copyright (C) Volker Lendecke 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "lib/idmap_cache.h"
+#include "librpc/gen_ndr/idmap.h"
+#include "libcli/security/dom_sid.h"
+
+bool run_local_idmap_cache1(int dummy)
+{
+ struct dom_sid sid, found_sid;
+ struct unixid xid, found_xid;
+ bool ret = false;
+ bool expired = false;
+
+ xid = (struct unixid) { .id = 1234, .type = ID_TYPE_UID };
+ dom_sid_parse("S-1-5-21-2864185242-3846410404-2398417794-1235", &sid);
+ idmap_cache_set_sid2unixid(&sid, &xid);
+
+ ret = idmap_cache_find_sid2unixid(&sid, &found_xid, &expired);
+ if (!ret) {
+ fprintf(stderr, "idmap_cache_find_sid2unixid failed\n");
+ goto done;
+ }
+ if (expired) {
+ fprintf(stderr,
+ "idmap_cache_find_sid2unixid returned an expired "
+ "value\n");
+ goto done;
+ }
+ if ((xid.type != found_xid.type) || (xid.id != found_xid.id)) {
+ fprintf(stderr,
+ "idmap_cache_find_sid2unixid returned wrong "
+ "values\n");
+ goto done;
+ }
+
+ ret = idmap_cache_find_xid2sid(&xid, &found_sid, &expired);
+ if (!ret) {
+ fprintf(stderr, "idmap_cache_find_xid2sid failed\n");
+ goto done;
+ }
+ if (expired) {
+ fprintf(stderr,
+ "idmap_cache_find_xid2sid returned an expired "
+ "value\n");
+ goto done;
+ }
+ if (!dom_sid_equal(&sid, &found_sid)) {
+ fprintf(stderr,
+ "idmap_cache_find_xid2sid returned wrong sid\n");
+ goto done;
+ }
+
+ xid.type = ID_TYPE_GID;
+
+ ret = idmap_cache_find_xid2sid(&xid, &found_sid, &expired);
+ if (ret) {
+ fprintf(stderr,
+ "idmap_cache_find_xid2sid found a GID where it "
+ "should not\n");
+ goto done;
+ }
+
+ idmap_cache_del_sid(&sid);
+
+ xid.type = ID_TYPE_UID;
+ ret = idmap_cache_find_xid2sid(&xid, &found_sid, &expired);
+ if (ret) {
+ fprintf(stderr,
+ "idmap_cache_find_xid2sid found a UID where it "
+ "should not\n");
+ goto done;
+ }
+
+ /*
+ * Test that negative mappings can also be cached
+ */
+ sid = (struct dom_sid) {0};
+ xid = (struct unixid) { .id = 1234, .type = ID_TYPE_UID };
+ idmap_cache_set_sid2unixid(&sid, &xid);
+
+ ret = idmap_cache_find_xid2sid(&xid, &found_sid, &expired);
+ if (!ret) {
+ fprintf(stderr,
+ "idmap_cache_find_xid2sid failed to find "
+ "negative mapping\n");
+ goto done;
+ }
+ if (expired) {
+ fprintf(stderr,
+ "idmap_cache_find_xid2sid returned an expired "
+ "value\n");
+ goto done;
+ }
+ if (!dom_sid_equal(&sid, &found_sid)) {
+ fprintf(stderr,
+ "idmap_cache_find_xid2sid returned wrong sid\n");
+ goto done;
+ }
+
+ ret = true;
+done:
+ return ret;
+}
diff --git a/source3/torture/test_idmap_tdb_common.c b/source3/torture/test_idmap_tdb_common.c
new file mode 100644
index 0000000..825ee3e
--- /dev/null
+++ b/source3/torture/test_idmap_tdb_common.c
@@ -0,0 +1,1047 @@
+/*
+ Unix SMB/CIFS implementation.
+ IDMAP TDB common code tester
+
+ Copyright (C) Christian Ambach 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "torture/proto.h"
+#include "idmap.h"
+#include "winbindd/idmap_rw.h"
+#include "winbindd/idmap_tdb_common.h"
+#include "winbindd/winbindd.h"
+#include "winbindd/winbindd_proto.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/dom_sid.h"
+
+#define HWM_GROUP "GROUP HWM"
+#define HWM_USER "USER HWM"
+
+#define LOW_ID 100
+#define HIGH_ID 199
+
+#define DOM_SID1 "S-1-5-21-1234-5678-9012"
+#define DOM_SID2 "S-1-5-21-0123-5678-9012"
+#define DOM_SID3 "S-1-5-21-0012-5678-9012"
+#define DOM_SID4 "S-1-5-21-0001-5678-9012"
+#define DOM_SID5 "S-1-5-21-2345-5678-9012"
+#define DOM_SID6 "S-1-5-21-3456-5678-9012"
+
+/* overwrite some winbind internal functions */
+struct winbindd_domain *find_domain_from_name(const char *domain_name)
+{
+ return NULL;
+}
+
+bool get_global_winbindd_state_offline(void) {
+ return false;
+}
+
+bool winbindd_use_idmap_cache(void) {
+ return false;
+}
+
+static bool open_db(struct idmap_tdb_common_context *ctx)
+{
+ NTSTATUS status;
+ char *db_path;
+
+ if(ctx->db) {
+ /* already open */
+ return true;
+ }
+
+ db_path = talloc_asprintf(talloc_tos(), "%s/idmap_test.tdb",
+ lp_private_dir());
+ if(!db_path) {
+ DEBUG(0, ("Out of memory!\n"));
+ return false;
+ }
+
+ ctx->db = db_open(ctx, db_path, 0, TDB_DEFAULT,
+ O_RDWR | O_CREAT, 0600,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+
+ if(!ctx->db) {
+ DEBUG(0, ("Failed to open database: %s\n", strerror(errno)));
+ return false;
+ }
+
+ if(dbwrap_transaction_start(ctx->db) != 0) {
+ DEBUG(0, ("Failed to start transaction!\n"));
+ return false;
+ }
+
+ status = dbwrap_store_uint32_bystring(ctx->db, ctx->hwmkey_uid,
+ LOW_ID);
+ if(!NT_STATUS_IS_OK(status)) {
+ dbwrap_transaction_cancel(ctx->db);
+ return false;
+ }
+
+ status = dbwrap_store_uint32_bystring(ctx->db, ctx->hwmkey_gid,
+ LOW_ID);
+ if(!NT_STATUS_IS_OK(status)) {
+ dbwrap_transaction_cancel(ctx->db);
+ return false;
+ }
+
+ if(dbwrap_transaction_commit(ctx->db) != 0) {
+ DEBUG(0, ("Failed to commit transaction!\n"));
+ return false;
+ }
+
+ return true;
+}
+
+static NTSTATUS idmap_test_tdb_db_init(struct idmap_domain *dom)
+{
+ struct idmap_tdb_common_context *ret;
+
+ DBG_DEBUG("called for domain '%s'\n", dom->name);
+
+ ret = talloc_zero(dom, struct idmap_tdb_common_context);
+ if (ret == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ret->rw_ops = talloc_zero(ret, struct idmap_rw_ops);
+ if (ret->rw_ops == NULL) {
+ TALLOC_FREE(ret);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ret->max_id = HIGH_ID;
+ ret->hwmkey_uid = HWM_USER;
+ ret->hwmkey_gid = HWM_GROUP;
+
+ ret->rw_ops->get_new_id = idmap_tdb_common_get_new_id;
+ ret->rw_ops->set_mapping = idmap_tdb_common_set_mapping;
+
+ if (!open_db(ret)) {
+ TALLOC_FREE(ret);
+ return NT_STATUS_INTERNAL_ERROR;
+ };
+
+ dom->private_data = ret;
+
+ return NT_STATUS_OK;
+}
+
+static struct idmap_domain *createdomain(TALLOC_CTX *memctx)
+{
+ struct idmap_domain *dom;
+ struct idmap_methods *m;
+
+ dom = talloc_zero(memctx, struct idmap_domain);
+ dom->name = "*";
+ dom->low_id = LOW_ID;
+ dom->high_id = HIGH_ID;
+ dom->read_only = false;
+ m = talloc_zero(dom, struct idmap_methods);
+ *m = (struct idmap_methods) {
+ .init = idmap_test_tdb_db_init,
+ .sids_to_unixids = idmap_tdb_common_sids_to_unixids,
+ .unixids_to_sids = idmap_tdb_common_unixids_to_sids,
+ .allocate_id = idmap_tdb_common_get_new_id,
+ };
+ dom->methods = m;
+
+ return dom;
+}
+
+static bool test_getnewid1(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct unixid id;
+
+ id.type = ID_TYPE_UID;
+
+ status = idmap_tdb_common_get_new_id(dom, &id);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_getnewid1: Could not allocate id!\n"));
+ return false;
+ }
+
+ if(id.id == 0) {
+ DEBUG(0, ("test_getnewid1: Allocate returned "
+ "empty id!\n"));
+ return false;
+ }
+
+ if(id.id > HIGH_ID || id.id < LOW_ID) {
+ DEBUG(0, ("test_getnewid1: Allocate returned "
+ "out of range id!\n"));
+ return false;
+ }
+
+ DEBUG(0, ("test_getnewid1: PASSED!\n"));
+
+ return true;
+}
+
+static bool test_getnewid2(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct unixid id;
+ int i, left;
+
+ id.type = ID_TYPE_UID;
+
+ status = idmap_tdb_common_get_new_id(dom, &id);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_getnewid2: Could not allocate id!\n"));
+ return false;
+ }
+
+ if(id.id == 0) {
+ DEBUG(0, ("test_getnewid2: Allocate returned "
+ "empty id!\n"));
+ return false;
+ }
+
+ if(id.id > HIGH_ID || id.id < LOW_ID) {
+ DEBUG(0, ("test_getnewid2: Allocate returned "
+ "out of range id!\n"));
+ return false;
+ }
+
+ /* how many ids are left? */
+
+ left = HIGH_ID - id.id;
+
+ /* consume them all */
+ for(i = 0; i<left; i++) {
+
+ status = idmap_tdb_common_get_new_id(dom, &id);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_getnewid2: Allocate returned "
+ "error %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ if(id.id > HIGH_ID) {
+ DEBUG(0, ("test_getnewid2: Allocate returned "
+ "out of range id (%d)!\n", id.id));
+ return false;
+ }
+ }
+
+ /* one more must fail */
+ status = idmap_tdb_common_get_new_id(dom, &id);
+
+ if(NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_getnewid2: Could allocate id (%d) from "
+ "depleted pool!\n", id.id));
+ return false;
+ }
+
+ DEBUG(0, ("test_getnewid2: PASSED!\n"));
+
+ return true;
+}
+
+static bool test_setmap1(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map map;
+
+ ZERO_STRUCT(map);
+
+ /* test for correct return code with invalid data */
+
+ status = idmap_tdb_common_set_mapping(dom, NULL);
+ if(!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ DEBUG(0, ("test_setmap1: bad parameter handling!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_set_mapping(dom, &map);
+ if(!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ DEBUG(0, ("test_setmap1: bad parameter handling!\n"));
+ return false;
+ }
+
+ map.sid = dom_sid_parse_talloc(memctx, DOM_SID1 "-100");
+
+ map.xid.type = ID_TYPE_NOT_SPECIFIED;
+ map.xid.id = 4711;
+
+ status = idmap_tdb_common_set_mapping(dom, &map);
+ if(!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ DEBUG(0, ("test_setmap1: bad parameter handling!\n"));
+ return false;
+ }
+
+ /* now the good ones */
+ map.xid.type = ID_TYPE_UID;
+ map.xid.id = 0;
+
+ status = idmap_tdb_common_get_new_id(dom, &(map.xid));
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_setmap1: get_new_uid failed!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_set_mapping(dom, &map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_setmap1: setting UID mapping failed!\n"));
+ return false;
+ }
+
+ /* try to set the same mapping again as group (must fail) */
+
+ map.xid.type = ID_TYPE_GID;
+ status = idmap_tdb_common_set_mapping(dom, &map);
+ if(NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_setmap1: could create map for "
+ "group and user!\n"));
+ return false;
+ }
+
+ /* now a group with a different SID*/
+ map.xid.id = 0;
+
+ map.sid = dom_sid_parse_talloc(memctx, DOM_SID1 "-101");
+
+ status = idmap_tdb_common_get_new_id(dom, &(map.xid));
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_setmap1: get_new_gid failed!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_set_mapping(dom, &map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_setmap1: setting GID mapping failed!\n"));
+ return false;
+ }
+ DEBUG(0, ("test_setmap1: PASSED!\n"));
+
+ return true;
+}
+
+static bool test_sid2unixid1(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status1, status2, status3;
+ struct id_map map;
+
+ /* check for correct dealing with bad parameters */
+ status1 = idmap_tdb_common_sid_to_unixid(NULL, &map);
+ status2 = idmap_tdb_common_sid_to_unixid(dom, NULL);
+ status3 = idmap_tdb_common_sid_to_unixid(NULL, NULL);
+
+ if(!NT_STATUS_EQUAL(NT_STATUS_INVALID_PARAMETER, status1) ||
+ !NT_STATUS_EQUAL(NT_STATUS_INVALID_PARAMETER, status2) ||
+ !NT_STATUS_EQUAL(NT_STATUS_INVALID_PARAMETER, status3)) {
+ DEBUG(0, ("test_setmap1: bad parameter handling!\n"));
+ return false;
+ }
+
+ DEBUG(0, ("test_unixid2sid1: PASSED!\n"));
+
+ return true;
+}
+
+static bool test_sid2unixid2(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map uid_map, gid_map, test_map;
+ bool doagain = true;
+
+ ZERO_STRUCT(uid_map);
+ ZERO_STRUCT(gid_map);
+
+ /* create two mappings for a UID and GID */
+
+again:
+
+ uid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID2 "-1000");
+ uid_map.xid.type = ID_TYPE_UID;
+
+ gid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID2 "-1001");
+ gid_map.xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_new_mapping(dom, &uid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sid2unixid2: could not create uid map!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_new_mapping(dom, &gid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sid2unixid2: could not create gid map!\n"));
+ return false;
+ }
+
+ /* now read them back */
+ ZERO_STRUCT(test_map);
+ test_map.sid = uid_map.sid;
+
+ status = idmap_tdb_common_sid_to_unixid(dom, &test_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sid2unixid2: sid2unixid failed for uid!\n"));
+ return false;
+ }
+
+ if(test_map.xid.id!=uid_map.xid.id) {
+ DEBUG(0, ("test_sid2unixid2: sid2unixid returned wrong uid!\n"));
+ return false;
+ }
+
+ test_map.sid = gid_map.sid;
+
+ status = idmap_tdb_common_sid_to_unixid(dom, &test_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sid2unixid2: sid2unixid failed for gid!\n"));
+ return false;
+ }
+
+ if(test_map.xid.id!=gid_map.xid.id) {
+ DEBUG(0, ("test_sid2unixid2: sid2unixid returned wrong gid!\n"));
+ return false;
+ }
+
+ /*
+ * Go through the same tests again once to see if trying to recreate
+ * a mapping that was already created will work or not
+ */
+ if(doagain) {
+ doagain = false;
+ goto again;
+ }
+
+ DEBUG(0, ("test_sid2unixid2: PASSED!\n"));
+
+ return true;
+}
+
+static bool test_sids2unixids1(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map uid_map, gid_map, **test_maps;
+
+ ZERO_STRUCT(uid_map);
+ ZERO_STRUCT(gid_map);
+
+ /* create two mappings for a UID and GID */
+
+ uid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID4 "-1000");
+ uid_map.xid.type = ID_TYPE_UID;
+
+ gid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID4 "-1001");
+ gid_map.xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_new_mapping(dom, &uid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sids2unixids1: could not create uid map!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_new_mapping(dom, &gid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sids2unixids1: could not create gid map!\n"));
+ return false;
+ }
+
+ /* now read them back */
+ test_maps = talloc_zero_array(memctx, struct id_map*, 3);
+
+ test_maps[0] = talloc(test_maps, struct id_map);
+ test_maps[1] = talloc(test_maps, struct id_map);
+ test_maps[2] = NULL;
+
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ sid_copy(test_maps[0]->sid, uid_map.sid);
+ sid_copy(test_maps[1]->sid, gid_map.sid);
+
+ status = idmap_tdb_common_sids_to_unixids(dom, test_maps);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sids2sunixids1: sids2unixids failed!\n"));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ if(test_maps[0]->xid.id!=uid_map.xid.id ||
+ test_maps[1]->xid.id!=gid_map.xid.id ) {
+ DEBUG(0, ("test_sids2unixids1: sid2unixid returned wrong xid!\n"));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ DEBUG(0, ("test_sids2unixids1: PASSED!\n"));
+
+ talloc_free(test_maps);
+
+ return true;
+}
+
+static bool test_sids2unixids2(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map **test_maps;
+ struct unixid save;
+
+ test_maps = talloc_zero_array(memctx, struct id_map*, 3);
+
+ test_maps[0] = talloc(test_maps, struct id_map);
+ test_maps[1] = talloc(test_maps, struct id_map);
+ test_maps[2] = NULL;
+
+ /* ask for two new mappings for a UID and GID */
+ test_maps[0]->sid = dom_sid_parse_talloc(test_maps, DOM_SID4 "-1003");
+ test_maps[0]->xid.type = ID_TYPE_UID;
+ test_maps[1]->sid = dom_sid_parse_talloc(test_maps, DOM_SID4 "-1004");
+ test_maps[1]->xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_sids_to_unixids(dom, test_maps);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sids2sunixids2: sids2unixids "
+ "failed (%s)!\n", nt_errstr(status)));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ if(test_maps[0]->xid.id == 0 || test_maps[1]->xid.id == 0) {
+ DEBUG(0, ("test_sids2sunixids2: sids2unixids "
+ "returned zero ids!\n"));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ save = test_maps[1]->xid;
+
+ /* ask for a known and a new mapping at the same time */
+ talloc_free(test_maps);
+ test_maps = talloc_zero_array(memctx, struct id_map*, 3);
+ test_maps[0] = talloc(test_maps, struct id_map);
+ test_maps[1] = talloc(test_maps, struct id_map);
+ test_maps[2] = NULL;
+
+ test_maps[0]->sid = dom_sid_parse_talloc(test_maps, DOM_SID4 "-1004");
+ test_maps[0]->xid.type = ID_TYPE_GID;
+ test_maps[1]->sid = dom_sid_parse_talloc(test_maps, DOM_SID4 "-1005");
+ test_maps[1]->xid.type = ID_TYPE_UID;
+
+ status = idmap_tdb_common_sids_to_unixids(dom, test_maps);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sids2sunixids2: sids2unixids (2) "
+ "failed (%s)!\n", nt_errstr(status)));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ if(test_maps[0]->xid.type != save.type ||
+ test_maps[0]->xid.id != save.id) {
+ DEBUG(0, ("test_sids2sunixids2: second lookup returned "
+ "different value!\n"));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ if(test_maps[1]->xid.id == 0) {
+ DEBUG(0, ("test_sids2sunixids2: sids2unixids "
+ "returned zero id for mixed mapping request!\n"));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ DEBUG(0, ("test_sids2unixids2: PASSED!\n"));
+
+ talloc_free(test_maps);
+
+ return true;
+}
+
+static bool test_sids2unixids3(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map **test_maps;
+ bool retval = true;
+
+ /*
+ * check the mapping states:
+ * NONE_MAPPED, SOME_UNMAPPED, OK (all mapped)
+ *
+ * use the ids created by test_sids2unixids1
+ * need to make dom read-only
+ */
+
+ dom->read_only = true;
+
+ test_maps = talloc_zero_array(memctx, struct id_map*, 3);
+
+ test_maps[0] = talloc(test_maps, struct id_map);
+ test_maps[1] = talloc(test_maps, struct id_map);
+ test_maps[2] = NULL;
+
+ /* NONE_MAPPED first */
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[0]->sid = dom_sid_parse_talloc(test_maps,
+ "S-1-5-21-1-2-3-4");
+ test_maps[0]->xid.type = ID_TYPE_UID;
+
+ test_maps[1]->sid = dom_sid_parse_talloc(test_maps,
+ "S-1-5-21-1-2-3-5");
+ test_maps[1]->xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_sids_to_unixids(dom, test_maps);
+ if(!NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ DEBUG(0, ("test_sids2unixids3: incorrect status "
+ "(%s), expected NT_STATUS_NONE_MAPPED!\n",
+ nt_errstr(status)));
+ retval = false;
+ goto out;
+ }
+
+ /* SOME_UNMAPPED */
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[0]->sid = dom_sid_parse_talloc(test_maps,
+ DOM_SID4 "-1000");
+ test_maps[0]->xid.type = ID_TYPE_UID;
+ test_maps[1]->sid = dom_sid_parse_talloc(test_maps,
+ "S-1-5-21-1-2-3-5");
+ test_maps[1]->xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_sids_to_unixids(dom, test_maps);
+ if(!NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) {
+ DEBUG(0, ("test_sids2unixids3: incorrect status "
+ "(%s), expected STATUS_SOME_UNMAPPED!\n",
+ nt_errstr(status)));
+ retval = false;
+ goto out;
+ }
+
+ /* OK */
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[0]->sid = dom_sid_parse_talloc(test_maps,
+ DOM_SID4 "-1001");
+ test_maps[1]->sid = dom_sid_parse_talloc(test_maps,
+ DOM_SID4 "-1000");
+
+ status = idmap_tdb_common_sids_to_unixids(dom, test_maps);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_sids2unixids3: incorrect status "
+ "(%s), expected NT_STATUS_OK!\n",
+ nt_errstr(status)));
+ retval = false;
+ goto out;
+ }
+
+ DEBUG(0, ("test_sids2unixids3: PASSED!\n"));
+
+out:
+ talloc_free(test_maps);
+ dom->read_only = false;
+ return retval;
+}
+
+static bool test_unixid2sid1(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status1, status2, status3;
+ struct id_map map;
+
+ /* check for correct dealing with bad parameters */
+ status1 = idmap_tdb_common_unixid_to_sid(NULL, &map);
+ status2 = idmap_tdb_common_unixid_to_sid(dom, NULL);
+ status3 = idmap_tdb_common_unixid_to_sid(NULL, NULL);
+
+ if(!NT_STATUS_EQUAL(NT_STATUS_INVALID_PARAMETER, status1) ||
+ !NT_STATUS_EQUAL(NT_STATUS_INVALID_PARAMETER, status2) ||
+ !NT_STATUS_EQUAL(NT_STATUS_INVALID_PARAMETER, status3)) {
+ DEBUG(0, ("test_setmap1: bad parameter handling!\n"));
+ return false;
+ }
+
+ DEBUG(0, ("test_unixid2sid1: PASSED!\n"));
+
+ return true;
+}
+
+static bool test_unixid2sid2(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map *map;
+ bool retval = true;
+
+ /* ask for mapping that is outside of the range */
+ map = talloc(memctx, struct id_map);
+ map->sid = talloc(map, struct dom_sid);
+
+ map->xid.type = ID_TYPE_UID;
+ map->xid.id = HIGH_ID + 1;
+
+ status = idmap_tdb_common_unixid_to_sid(dom, map);
+ if(NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixid2sid2: unixid2sid returned "
+ "out-of-range result\n"));
+ retval = false;
+ goto out;
+ }
+
+ DEBUG(0, ("test_unixid2sid2: PASSED!\n"));
+out:
+ talloc_free(map);
+ return retval;
+
+}
+
+static bool test_unixid2sid3(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map uid_map, gid_map, test_map;
+ struct dom_sid testsid;
+
+ ZERO_STRUCT(uid_map);
+ ZERO_STRUCT(gid_map);
+
+ /* create two mappings for a UID and GID */
+ uid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID3 "-1000");
+ uid_map.xid.type = ID_TYPE_UID;
+
+ gid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID3 "-1001");
+ gid_map.xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_new_mapping(dom, &uid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixid2sid3: could not create uid map!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_new_mapping(dom, &gid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixid2sid3: could not create gid map!\n"));
+ return false;
+ }
+
+ /* now read them back */
+ ZERO_STRUCT(test_map);
+ test_map.xid.id = uid_map.xid.id;
+ test_map.xid.type = ID_TYPE_UID;
+ test_map.sid = &testsid;
+
+ status = idmap_tdb_common_unixid_to_sid(dom, &test_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixid2sid3: unixid2sid failed for uid!\n"));
+ return false;
+ }
+
+ if(test_map.xid.type!=uid_map.xid.type) {
+ DEBUG(0, ("test_unixid2sid3: unixid2sid returned wrong type!\n"));
+ return false;
+ }
+
+ if(!dom_sid_equal(test_map.sid, uid_map.sid)) {
+ DEBUG(0, ("test_unixid2sid3: unixid2sid returned wrong SID!\n"));
+ return false;
+ }
+
+ ZERO_STRUCT(test_map);
+ test_map.xid.id = gid_map.xid.id;
+ test_map.xid.type = ID_TYPE_GID;
+ test_map.sid = &testsid;
+
+ status = idmap_tdb_common_unixid_to_sid(dom, &test_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixid2sid3: unixid2sid failed for gid!\n"));
+ return false;
+ }
+
+ if(test_map.xid.type!=gid_map.xid.type) {
+ DEBUG(0, ("test_unixid2sid3: unixid2sid returned wrong type!\n"));
+ return false;
+ }
+
+ if(!dom_sid_equal(test_map.sid,gid_map.sid)) {
+ DEBUG(0, ("test_unixid2sid3: unixid2sid returned wrong SID!\n"));
+ return false;
+ }
+
+ DEBUG(0, ("test_unixid2sid3: PASSED!\n"));
+
+ return true;
+}
+
+static bool test_unixids2sids1(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map uid_map, gid_map, **test_maps;
+
+ ZERO_STRUCT(uid_map);
+ ZERO_STRUCT(gid_map);
+
+ /* create two mappings for a UID and GID */
+
+ uid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID5 "-1000");
+ uid_map.xid.type = ID_TYPE_UID;
+
+ gid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID5 "-1001");
+ gid_map.xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_new_mapping(dom, &uid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixids2sids1: could not create uid map!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_new_mapping(dom, &gid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixids2sids1: could not create gid map!\n"));
+ return false;
+ }
+
+ /* now read them back */
+ test_maps = talloc_zero_array(memctx, struct id_map*, 3);
+
+ test_maps[0] = talloc(test_maps, struct id_map);
+ test_maps[1] = talloc(test_maps, struct id_map);
+ test_maps[2] = NULL;
+
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[0]->xid.id = uid_map.xid.id;
+ test_maps[0]->xid.type = ID_TYPE_UID;
+ test_maps[1]->xid.id = gid_map.xid.id;
+ test_maps[1]->xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_unixids_to_sids(dom, test_maps);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixids2sids1: unixids2sids failed!\n"));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ if(!dom_sid_equal(test_maps[0]->sid, uid_map.sid) ||
+ !dom_sid_equal(test_maps[1]->sid, gid_map.sid) ) {
+ DEBUG(0, ("test_unixids2sids1: unixids2sids returned wrong sid!\n"));
+ talloc_free(test_maps);
+ return false;
+ }
+
+ DEBUG(0, ("test_unixids2sids1: PASSED!\n"));
+
+ talloc_free(test_maps);
+
+ return true;
+}
+
+static bool test_unixids2sids2(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map **test_maps;
+ bool retval = true;
+
+ test_maps = talloc_zero_array(memctx, struct id_map*, 3);
+
+ test_maps[0] = talloc(test_maps, struct id_map);
+ test_maps[1] = talloc(test_maps, struct id_map);
+ test_maps[2] = NULL;
+
+ /* ask for two unknown mappings for a UID and GID */
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[0]->xid.id = HIGH_ID - 1;
+ test_maps[0]->xid.type = ID_TYPE_UID;
+ test_maps[1]->xid.id = HIGH_ID - 1;
+ test_maps[1]->xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_unixids_to_sids(dom, test_maps);
+ if(NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixids2sids2: unixids2sids succeeded "
+ "unexpectedly!\n"));
+ retval = false;
+ goto out;
+ }
+
+ DEBUG(0, ("test_unixids2sids2: PASSED!\n"));
+
+out:
+ talloc_free(test_maps);
+
+ return retval;;
+}
+
+static bool test_unixids2sids3(TALLOC_CTX *memctx, struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ struct id_map uid_map, gid_map, **test_maps;
+ bool retval = true;
+
+ ZERO_STRUCT(uid_map);
+ ZERO_STRUCT(gid_map);
+
+ /* create two mappings for a UID and GID */
+ uid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID6 "-1000");
+ uid_map.xid.type = ID_TYPE_UID;
+
+ gid_map.sid = dom_sid_parse_talloc(memctx, DOM_SID6 "-1001");
+ gid_map.xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_new_mapping(dom, &uid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixids2sids3: could not create uid map!\n"));
+ return false;
+ }
+
+ status = idmap_tdb_common_new_mapping(dom, &gid_map);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixids2sids3: could not create gid map!\n"));
+ return false;
+ }
+
+ /*
+ * check the mapping states:
+ * NONE_MAPPED, SOME_UNMAPPED, OK (all mapped)
+ */
+ test_maps = talloc_zero_array(memctx, struct id_map*, 3);
+
+ test_maps[0] = talloc(test_maps, struct id_map);
+ test_maps[1] = talloc(test_maps, struct id_map);
+ test_maps[2] = NULL;
+
+ /* NONE_MAPPED first */
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+
+ test_maps[0]->xid.id = HIGH_ID - 1;
+ test_maps[0]->xid.type = ID_TYPE_UID;
+
+ test_maps[1]->xid.id = HIGH_ID - 1;
+ test_maps[1]->xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_unixids_to_sids(dom, test_maps);
+ if(!NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ DEBUG(0, ("test_unixids2sids3: incorrect status "
+ "(%s), expected NT_STATUS_NONE_MAPPED!\n",
+ nt_errstr(status)));
+ retval = false;
+ goto out;
+ }
+
+ /* SOME_UNMAPPED */
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[0]->xid = uid_map.xid;
+ test_maps[1]->xid.id = HIGH_ID - 1;
+ test_maps[1]->xid.type = ID_TYPE_GID;
+
+ status = idmap_tdb_common_unixids_to_sids(dom, test_maps);
+ if(!NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) {
+ DEBUG(0, ("test_unixids2sids3: incorrect status "
+ "(%s), expected STATUS_SOME_UNMAPPED!\n",
+ nt_errstr(status)));
+ retval = false;
+ goto out;
+ }
+
+ /* OK */
+ test_maps[0]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[1]->sid = talloc(test_maps, struct dom_sid);
+ test_maps[0]->xid = uid_map.xid;
+ test_maps[1]->xid = gid_map.xid;
+
+ status = idmap_tdb_common_unixids_to_sids(dom, test_maps);
+ if(!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("test_unixids2sids3: incorrect status "
+ "(%s), expected NT_STATUS_OK!\n",
+ nt_errstr(status)));
+ retval = false;
+ goto out;
+ }
+
+ DEBUG(0, ("test_unixids2sids3: PASSED!\n"));
+
+out:
+ talloc_free(test_maps);
+ return retval;
+}
+
+#define CHECKRESULT(r) if(!r) {TALLOC_FREE(stack); return r;}
+
+bool run_idmap_tdb_common_test(int dummy)
+{
+ bool result;
+ struct idmap_domain *dom;
+ TALLOC_CTX *stack = talloc_stackframe();
+ TALLOC_CTX *memctx = talloc_new(stack);
+ NTSTATUS status;
+
+ dom = createdomain(memctx);
+ if (dom == NULL) {
+ TALLOC_FREE(stack);
+ return false;
+ }
+
+ status = dom->methods->init(dom);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(stack);
+ return false;
+ }
+
+ /* test a single allocation from pool (no mapping) */
+ result = test_getnewid1(memctx, dom);
+ CHECKRESULT(result);
+
+ /* test idmap_tdb_common_set_mapping */
+ result = test_setmap1(memctx, dom);
+ CHECKRESULT(result);
+
+ /* test idmap_tdb_common_sid_to_unixid */
+ result = test_sid2unixid1(memctx, dom);
+ CHECKRESULT(result);
+ result = test_sid2unixid2(memctx, dom);
+ CHECKRESULT(result);
+
+ /* test idmap_tdb_common_sids_to_unixids */
+ result = test_sids2unixids1(memctx, dom);
+ CHECKRESULT(result);
+ result = test_sids2unixids2(memctx, dom);
+ CHECKRESULT(result);
+ result = test_sids2unixids3(memctx, dom);
+ CHECKRESULT(result);
+
+ /* test idmap_tdb_common_unixid_to_sid */
+ result = test_unixid2sid1(memctx, dom);
+ CHECKRESULT(result);
+ result = test_unixid2sid2(memctx, dom);
+ CHECKRESULT(result);
+ result = test_unixid2sid3(memctx, dom);
+ CHECKRESULT(result);
+
+ /* test idmap_tdb_common_unixids_to_sids */
+ result = test_unixids2sids1(memctx, dom);
+ CHECKRESULT(result);
+ result = test_unixids2sids2(memctx, dom);
+ CHECKRESULT(result);
+ result = test_unixids2sids3(memctx, dom);
+ CHECKRESULT(result);
+
+ /* test filling up the range */
+ result = test_getnewid2(memctx, dom);
+ CHECKRESULT(result);
+
+ talloc_free(stack);
+
+ return true;
+}
diff --git a/source3/torture/test_matching.c b/source3/torture/test_matching.c
new file mode 100644
index 0000000..647b758
--- /dev/null
+++ b/source3/torture/test_matching.c
@@ -0,0 +1,276 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba utility tests
+ Copyright (C) Stefan Metzmacher 2021
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/util_matching.h"
+#include "proto.h"
+
+bool run_str_match_mswild(int dummy)
+{
+ const char *namelist = "/abc*.txt/xyz*.dat/a0123456789Z/";
+ name_compare_entry *name_entries = NULL;
+ struct samba_path_matching *pmcs = NULL;
+ struct samba_path_matching *pmci = NULL;
+ const struct str_match_mswild_name {
+ const char *name;
+ ssize_t case_sensitive_idx;
+ ssize_t case_insensitive_idx;
+ } names[] = {{
+ .name = "/dir/abc123.txt",
+ .case_sensitive_idx = 0,
+ .case_insensitive_idx = 0,
+ },{
+ .name = "/dir/AbC123.TxT",
+ .case_sensitive_idx = -1,
+ .case_insensitive_idx = 0,
+ },{
+ .name = "/dir/xyz123.dat",
+ .case_sensitive_idx = 1,
+ .case_insensitive_idx = 1,
+ },{
+ .name = "/dir/XyZ123.DaT",
+ .case_sensitive_idx = -1,
+ .case_insensitive_idx = 1,
+ },{
+ .name = "/dir/aaa123.jpg",
+ .case_sensitive_idx = -1,
+ .case_insensitive_idx = -1,
+ },{
+ .name = "/dir/a0123456789Z",
+ .case_sensitive_idx = 2,
+ .case_insensitive_idx = 2,
+ },{
+ .name = "/dir/A0123456789z",
+ .case_sensitive_idx = -1,
+ .case_insensitive_idx = 2,
+ }};
+ NTSTATUS status;
+ size_t i;
+ bool ret = true;
+
+ d_fprintf(stderr, "namelist: %s\n", namelist);
+
+ set_namearray(&name_entries, namelist);
+ SMB_ASSERT(name_entries != NULL);
+
+ status = samba_path_matching_mswild_create(talloc_tos(),
+ true, /* case_sensitive */
+ namelist,
+ &pmcs);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+ status = samba_path_matching_mswild_create(talloc_tos(),
+ false, /* case_sensitive */
+ namelist,
+ &pmci);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+
+ for (i = 0; i < ARRAY_SIZE(names); i++) {
+ const struct str_match_mswild_name *n = &names[i];
+ bool case_sensitive_match;
+ bool case_insensitive_match;
+ ssize_t cs_match_idx = -1;
+ ssize_t ci_match_idx = -1;
+ ssize_t replace_start = -1;
+ ssize_t replace_end = -1;
+ bool ok = true;
+
+ case_sensitive_match = is_in_path(n->name,
+ name_entries,
+ true);
+ if (n->case_sensitive_idx != -1) {
+ ok &= case_sensitive_match;
+ } else {
+ ok &= !case_sensitive_match;
+ }
+ status = samba_path_matching_check_last_component(pmcs,
+ n->name,
+ &cs_match_idx,
+ &replace_start,
+ &replace_end);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+ SMB_ASSERT(replace_start == -1);
+ SMB_ASSERT(replace_end == -1);
+ if (n->case_sensitive_idx != cs_match_idx) {
+ ok = false;
+ }
+ case_insensitive_match = is_in_path(n->name,
+ name_entries,
+ false);
+ if (n->case_insensitive_idx != -1) {
+ ok &= case_insensitive_match;
+ } else {
+ ok &= !case_insensitive_match;
+ }
+ status = samba_path_matching_check_last_component(pmci,
+ n->name,
+ &ci_match_idx,
+ &replace_start,
+ &replace_end);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+ SMB_ASSERT(replace_start == -1);
+ SMB_ASSERT(replace_end == -1);
+ if (n->case_insensitive_idx != ci_match_idx) {
+ ok = false;
+ }
+
+ d_fprintf(stderr, "name[%s] "
+ "case_sensitive[TIDX=%zd;MATCH=%u;MIDX=%zd] "
+ "case_insensitive[TIDX=%zd;MATCH=%u;MIDX=%zd] "
+ "%s\n",
+ n->name,
+ n->case_sensitive_idx,
+ case_sensitive_match,
+ cs_match_idx,
+ n->case_insensitive_idx,
+ case_insensitive_match,
+ ci_match_idx,
+ ok ? "OK" : "FAIL");
+
+ ret &= ok;
+ }
+
+ return ret;
+}
+
+bool run_str_match_regex_sub1(int dummy)
+{
+ const char *invalidlist1 = "/Re7599Ex[0-9].*\\.txt/";
+ const char *invalidlist2 = "/Re7599Ex\\([0-9]\\).*\\.\\(txt\\)/";
+ const char *invalidlist3 = "/Re7599Ex\\([0-9]).*\\.txt/";
+ const char *invalidlist4 = "/Re7599Ex[0-9.*\\.txt/";
+ const char *namelist = "/Re7599Ex\\([0-9]\\).*\\.txt/test\\(.*\\).txt/^test\\([0-9]*\\).dat/";
+ struct samba_path_matching *pm = NULL;
+ const struct str_match_regex_sub1 {
+ const char *name;
+ ssize_t match_idx;
+ ssize_t sub_start;
+ ssize_t sub_end;
+ } names[] = {{
+ .name = "/dir/Re7599Ex567.txt",
+ .match_idx = 0,
+ .sub_start = 13,
+ .sub_end = 14,
+ },{
+ .name = "/dir/rE7599eX567.txt",
+ .match_idx = -1,
+ .sub_start = -1,
+ .sub_end = -1,
+ },{
+ .name = "/dir/Re7599Ex.txt",
+ .match_idx = -1,
+ .sub_start = -1,
+ .sub_end = -1,
+ },{
+ .name = "/dir/testabc123.txt",
+ .match_idx = 1,
+ .sub_start = 9,
+ .sub_end = 15,
+ },{
+ .name = "/dir/testabc123.tXt",
+ .match_idx = -1,
+ .sub_start = -1,
+ .sub_end = -1,
+ },{
+ .name = "/dir/test123.dat",
+ .match_idx = 2,
+ .sub_start = 9,
+ .sub_end = 12,
+ },{
+ .name = "/dir/tEst123.dat",
+ .match_idx = -1,
+ .sub_start = -1,
+ .sub_end = -1,
+ }};
+ NTSTATUS status;
+ size_t i;
+ bool ret = true;
+
+ d_fprintf(stderr, "invalidlist1: %s\n", invalidlist1);
+ status = samba_path_matching_regex_sub1_create(talloc_tos(),
+ invalidlist1,
+ &pm);
+ SMB_ASSERT(NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER));
+ d_fprintf(stderr, "invalidlist2: %s\n", invalidlist2);
+ status = samba_path_matching_regex_sub1_create(talloc_tos(),
+ invalidlist2,
+ &pm);
+ SMB_ASSERT(NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER));
+ d_fprintf(stderr, "invalidlist3: %s\n", invalidlist3);
+ status = samba_path_matching_regex_sub1_create(talloc_tos(),
+ invalidlist3,
+ &pm);
+ SMB_ASSERT(NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER));
+ d_fprintf(stderr, "invalidlist4: %s\n", invalidlist4);
+ status = samba_path_matching_regex_sub1_create(talloc_tos(),
+ invalidlist4,
+ &pm);
+ SMB_ASSERT(NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER));
+
+ d_fprintf(stderr, "namelist: %s\n", namelist);
+ status = samba_path_matching_regex_sub1_create(talloc_tos(),
+ namelist,
+ &pm);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+ for (i = 0; i < ARRAY_SIZE(names); i++) {
+ const struct str_match_regex_sub1 *n = &names[i];
+ ssize_t match_idx = -1;
+ ssize_t replace_start = -1;
+ ssize_t replace_end = -1;
+ bool ok = true;
+
+ status = samba_path_matching_check_last_component(pm,
+ n->name,
+ &match_idx,
+ &replace_start,
+ &replace_end);
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+ if (match_idx == -1) {
+ SMB_ASSERT(replace_start == -1);
+ SMB_ASSERT(replace_end == -1);
+ }
+ if (n->match_idx != match_idx) {
+ ok = false;
+ }
+ if (n->sub_start != replace_start) {
+ ok = false;
+ }
+ if (n->sub_end != replace_end) {
+ ok = false;
+ }
+
+ d_fprintf(stderr, "name[%s] "
+ "T[IDX=%zd;START=%zd;END=%zd] "
+ "M[[IDX=%zd;START=%zd;END=%zd] "
+ "%s\n",
+ n->name,
+ n->match_idx,
+ n->sub_start,
+ n->sub_end,
+ match_idx,
+ replace_start,
+ replace_end,
+ ok ? "OK" : "FAIL");
+
+ ret &= ok;
+ }
+
+ return ret;
+}
diff --git a/source3/torture/test_messaging_fd_passing.c b/source3/torture/test_messaging_fd_passing.c
new file mode 100644
index 0000000..5030b66
--- /dev/null
+++ b/source3/torture/test_messaging_fd_passing.c
@@ -0,0 +1,397 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test for fd passing with messaging
+
+ Copyright (C) Michael Adam 2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "lib/util/tevent_unix.h"
+#include "messages.h"
+
+/**
+ * test fdpass1:
+ *
+ * Try to pass an fd to the sending process - fails.
+ */
+bool run_messaging_fdpass1(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ bool retval = false;
+ int pipe_fds[2];
+ int pass_fds[1] = { 0 };
+ int ret;
+ NTSTATUS status;
+ struct server_id dst;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ goto fail;
+ }
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ goto fail;
+ }
+
+ dst = messaging_server_id(msg_ctx);
+
+ ret = pipe(pipe_fds);
+ if (ret != 0) {
+ perror("pipe failed");
+ goto fail;
+ }
+
+ pass_fds[0] = pipe_fds[0];
+
+ status = messaging_send_iov(msg_ctx, dst, MSG_PING, NULL, 0,
+ pass_fds, 1);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+ fprintf(stderr,
+ "messaging_send_iov gave: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ retval = true;
+
+fail:
+ TALLOC_FREE(frame);
+ return retval;
+}
+
+/**
+ * test fdpass2:
+ *
+ * - parent: create a child
+ * - parent: create a two pipes in the parent: up and down
+ * - parent: pass the up pipe's reading end and the down pipe's writing
+ * end to the child and close them
+ * - parent: write a number into the up pipe's writing end
+ * - child: read number from the passed reading fd (up)
+ * - child: write the read number to the passed writing fd (down)
+ * - parent: read number from the down pipe's reading end and compare with
+ * original number
+ */
+
+#define MSG_TORTURE_FDPASS2 0xF002
+
+static bool fdpass2_filter(struct messaging_rec *rec, void *private_data)
+{
+ if (rec->msg_type != MSG_TORTURE_FDPASS2) {
+ return false;
+ }
+
+ if (rec->num_fds != 2) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool fdpass2_child(int ready_fd)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool retval = false;
+ uint8_t c = 1;
+ struct tevent_req *subreq;
+ int ret;
+ ssize_t bytes;
+ int up_fd, down_fd;
+ struct messaging_rec *rec;
+ bool ok;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ fprintf(stderr, "child: tevent_context_init failed\n");
+ goto done;
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "child: messaging_init failed\n");
+ goto done;
+ }
+
+ /* Tell the parent we are ready to receive messages. */
+ bytes = write(ready_fd, &c, 1);
+ if (bytes != 1) {
+ perror("child: failed to write to ready_fd");
+ goto done;
+ }
+
+ subreq = messaging_filtered_read_send(frame, /* TALLOC_CTX */
+ ev, msg_ctx,
+ fdpass2_filter, NULL);
+ if (subreq == NULL) {
+ fprintf(stderr, "child: messaging_filtered_read_send failed\n");
+ goto done;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ fprintf(stderr, "child: tevent_req_poll failed\n");
+ goto done;
+ }
+
+ ret = messaging_filtered_read_recv(subreq, frame, &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ fprintf(stderr, "child: messaging_filtered_read_recv failed\n");
+ goto done;
+ }
+
+ SMB_ASSERT(rec->num_fds == 2);
+
+ /* Tell the parent we are done. */
+ bytes = write(ready_fd, &c, 1);
+ if (bytes != 1) {
+ perror("child: failed to write to ready_fd");
+ goto done;
+ }
+
+ up_fd = rec->fds[0];
+ down_fd = rec->fds[1];
+
+ bytes = read(up_fd, &c, 1);
+ if (bytes != 1) {
+ perror("child: read from up_fd failed");
+ goto done;
+ }
+
+ bytes = write(down_fd, &c, 1);
+ if (bytes != 1) {
+ perror("child: write to down_fd failed");
+ }
+
+ printf("child: done\n");
+
+ retval = true;
+
+done:
+ TALLOC_FREE(frame);
+ return retval;
+}
+
+struct child_done_state {
+ int fd;
+ bool done;
+};
+
+static void child_done_cb(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct child_done_state *state =
+ (struct child_done_state *)private_data;
+ char c = 0;
+ ssize_t bytes;
+
+ bytes = read(state->fd, &c, 1);
+ if (bytes != 1) {
+ perror("parent: read from ready_fd failed");
+ }
+
+ state->done = true;
+}
+
+static bool fdpass2_parent(pid_t child_pid, int ready_fd, size_t payload_size)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ bool retval = false;
+ int up_pipe[2];
+ int down_pipe[2];
+ int pass_fds[2] = { 0 };
+ int ret;
+ NTSTATUS status;
+ struct server_id dst;
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint8_t c1 = 1, c2, c;
+ ssize_t bytes;
+ struct iovec iov;
+ DATA_BLOB blob;
+ struct tevent_fd *child_done_fde;
+ struct child_done_state child_state;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ fprintf(stderr, "parent: tevent_context_init failed\n");
+ goto done;
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "parent: messaging_init failed\n");
+ goto done;
+ }
+
+ /* wait util the child is ready to receive messages */
+ bytes = read(ready_fd, &c, 1);
+ if (bytes != 1) {
+ perror("parent: read from ready_fd failed");
+ goto done;
+ }
+
+ ret = pipe(up_pipe);
+ if (ret != 0) {
+ perror("parent: pipe failed for up_pipe");
+ goto done;
+ }
+
+ ret = pipe(down_pipe);
+ if (ret != 0) {
+ perror("parent: pipe failed for down_pipe");
+ goto done;
+ }
+
+ child_state.fd = ready_fd;
+ child_state.done = false;
+
+ child_done_fde = tevent_add_fd(ev, ev, ready_fd, TEVENT_FD_READ,
+ child_done_cb, &child_state);
+ if (child_done_fde == NULL) {
+ fprintf(stderr,
+ "parent: failed tevent_add_fd for child done\n");
+ goto done;
+ }
+
+ pass_fds[0] = up_pipe[0];
+ pass_fds[1] = down_pipe[1];
+
+ dst = messaging_server_id(msg_ctx);
+ dst.pid = child_pid;
+
+ /*
+ * Send a certain payload with the fds, to test to test
+ * that fd-passing works when we have fragmentation and
+ * re-assembly of the datagrams.
+ *
+ * Fragmentation/queuing is triggered by a certain payload
+ * size. Payloads below that size use the fast path.
+ */
+ blob = data_blob_talloc_zero(frame, payload_size);
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ status = messaging_send_iov(msg_ctx, dst, MSG_TORTURE_FDPASS2, &iov, 1,
+ pass_fds, 2);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "parent: messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ printf("parent: waiting for child to confirm\n");
+
+ while (!child_state.done) {
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ fprintf(stderr, "parent: tevent_loop_once failed\n");
+ goto done;
+ }
+ }
+
+ printf("parent: child confirmed\n");
+
+ close(up_pipe[0]);
+ close(down_pipe[1]);
+
+ bytes = write(up_pipe[1], &c1, 1);
+ if (bytes != 1) {
+ perror("parent: write to up pipe failed");
+ goto done;
+ }
+
+ bytes = read(down_pipe[0], &c2, 1);
+ if (bytes != 1) {
+ perror("parent: read from down pipe failed");
+ goto done;
+ }
+
+ if (c1 != c2) {
+ fprintf(stderr, "parent: c1[%d] != c2[%d]\n", c1, c2);
+ goto done;
+ }
+
+ ret = waitpid(child_pid, NULL, 0);
+ if (ret == -1) {
+ perror("parent: waitpid failed");
+ goto done;
+ }
+
+ retval = true;
+
+done:
+ TALLOC_FREE(frame);
+ return retval;
+}
+
+static bool run_messaging_fdpass2_int(int dummy, size_t payload_size)
+{
+ bool retval = false;
+ pid_t child_pid;
+ int ready_pipe[2];
+ int ret;
+
+ ret = pipe(ready_pipe);
+ if (ret != 0) {
+ perror("parent: pipe failed for ready_pipe");
+ return retval;
+ }
+
+ child_pid = fork();
+ if (child_pid == -1) {
+ perror("fork failed");
+ } else if (child_pid == 0) {
+ close(ready_pipe[0]);
+ retval = fdpass2_child(ready_pipe[1]);
+ } else {
+ close(ready_pipe[1]);
+ retval = fdpass2_parent(child_pid, ready_pipe[0], payload_size);
+ }
+
+ return retval;
+}
+
+bool run_messaging_fdpass2(int dummy)
+{
+ return run_messaging_fdpass2_int(dummy, 1000*1000);
+}
+
+/**
+ * Variant of the FDPASS2 test that tests the non-queuing fast path
+ * with a small payload.
+ */
+bool run_messaging_fdpass2a(int dummy)
+{
+ return run_messaging_fdpass2_int(dummy, 1);
+}
+
+/**
+ * Variant of the FDPASS2 test that tests the non-queuing fast path
+ * without a payload.
+ */
+bool run_messaging_fdpass2b(int dummy)
+{
+ return run_messaging_fdpass2_int(dummy, 0);
+}
diff --git a/source3/torture/test_messaging_read.c b/source3/torture/test_messaging_read.c
new file mode 100644
index 0000000..555f084
--- /dev/null
+++ b/source3/torture/test_messaging_read.c
@@ -0,0 +1,706 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test for a messaging_read bug
+ Copyright (C) Volker Lendecke 2014
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "lib/util/tevent_unix.h"
+#include "messages.h"
+
+struct msg_count_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ uint32_t msg_type;
+ unsigned *count;
+};
+
+static void msg_count_done(struct tevent_req *subreq);
+
+static struct tevent_req *msg_count_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ uint32_t msg_type,
+ unsigned *count)
+{
+ struct tevent_req *req, *subreq;
+ struct msg_count_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct msg_count_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+ state->msg_type = msg_type;
+ state->count = count;
+
+ subreq = messaging_read_send(state, state->ev, state->msg_ctx,
+ state->msg_type);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, msg_count_done, req);
+ return req;
+}
+
+static void msg_count_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct msg_count_state *state = tevent_req_data(
+ req, struct msg_count_state);
+ int ret;
+
+ ret = messaging_read_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ *state->count += 1;
+
+ subreq = messaging_read_send(state, state->ev, state->msg_ctx,
+ state->msg_type);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, msg_count_done, req);
+}
+
+bool run_messaging_read1(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ struct tevent_req *req1 = NULL;
+ unsigned count1 = 0;
+ struct tevent_req *req2 = NULL;
+ unsigned count2 = 0;
+ NTSTATUS status;
+ bool retval = false;
+ int i;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ goto fail;
+ }
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ goto fail;
+ }
+
+ req1 = msg_count_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY, &count1);
+ if (req1 == NULL) {
+ fprintf(stderr, "msg_count_send failed\n");
+ goto fail;
+ }
+ req2 = msg_count_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY, &count2);
+ if (req1 == NULL) {
+ fprintf(stderr, "msg_count_send failed\n");
+ goto fail;
+ }
+ status = messaging_send_buf(msg_ctx, messaging_server_id(msg_ctx),
+ MSG_SMB_NOTIFY, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "messaging_send_buf failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ for (i=0; i<2; i++) {
+ if (tevent_loop_once(ev) != 0) {
+ fprintf(stderr, "tevent_loop_once failed\n");
+ goto fail;
+ }
+ }
+
+ printf("%u/%u\n", count1, count2);
+
+ if ((count1 != 1) || (count2 != 0)) {
+ fprintf(stderr, "Got %u/%u msgs, expected 1/0\n",
+ count1, count2);
+ goto fail;
+ }
+
+ retval = true;
+fail:
+ TALLOC_FREE(req1);
+ TALLOC_FREE(req2);
+ TALLOC_FREE(msg_ctx);
+ TALLOC_FREE(ev);
+ return retval;
+}
+
+struct msg_free_state {
+ struct tevent_req **to_free;
+};
+
+static void msg_free_done(struct tevent_req *subreq);
+
+static struct tevent_req *msg_free_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ uint32_t msg_type,
+ struct tevent_req **to_free)
+{
+ struct tevent_req *req, *subreq;
+ struct msg_free_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct msg_free_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->to_free = to_free;
+
+ subreq = messaging_read_send(state, ev, msg_ctx, msg_type);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, msg_free_done, req);
+ return req;
+}
+
+static void msg_free_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct msg_free_state *state = tevent_req_data(
+ req, struct msg_free_state);
+ int ret;
+
+ ret = messaging_read_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ TALLOC_FREE(*state->to_free);
+ tevent_req_done(req);
+}
+
+bool run_messaging_read2(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ struct tevent_req *req1 = NULL;
+ struct tevent_req *req2 = NULL;
+ unsigned count = 0;
+ NTSTATUS status;
+ bool retval = false;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ goto fail;
+ }
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ goto fail;
+ }
+
+ req1 = msg_free_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY, &req2);
+ if (req1 == NULL) {
+ fprintf(stderr, "msg_count_send failed\n");
+ goto fail;
+ }
+ req2 = msg_count_send(ev, ev, msg_ctx, MSG_SMB_NOTIFY, &count);
+ if (req1 == NULL) {
+ fprintf(stderr, "msg_count_send failed\n");
+ goto fail;
+ }
+ status = messaging_send_buf(msg_ctx, messaging_server_id(msg_ctx),
+ MSG_SMB_NOTIFY, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "messaging_send_buf failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!tevent_req_poll(req1, ev) != 0) {
+ fprintf(stderr, "tevent_req_poll failed\n");
+ goto fail;
+ }
+
+ if (count != 0) {
+ fprintf(stderr, "Got %u msgs, expected none\n", count);
+ goto fail;
+ }
+
+ retval = true;
+fail:
+ TALLOC_FREE(req1);
+ TALLOC_FREE(msg_ctx);
+ TALLOC_FREE(ev);
+ return retval;
+}
+
+struct msg_pingpong_state {
+ struct messaging_context *msg_ctx;
+};
+
+static void msg_pingpong_done(struct tevent_req *subreq);
+
+static struct tevent_req *msg_pingpong_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct server_id dst)
+{
+ struct tevent_req *req, *subreq;
+ struct msg_pingpong_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct msg_pingpong_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (!tevent_req_set_endtime(req, ev, timeval_current_ofs(10, 0))) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->msg_ctx = messaging_init(state, ev);
+ if (tevent_req_nomem(state->msg_ctx, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = messaging_send_buf(state->msg_ctx, dst, MSG_PING, NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("messaging_send_buf failed: %s\n", nt_errstr(status));
+ tevent_req_error(req, map_errno_from_nt_status(status));
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = messaging_read_send(state, ev, state->msg_ctx, MSG_PONG);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, msg_pingpong_done, req);
+ return req;
+}
+
+static void msg_pingpong_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = messaging_read_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_error(req, ret);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static int msg_pingpong_recv(struct tevent_req *req)
+{
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+ return 0;
+}
+
+static int msg_pingpong(struct server_id dst)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ int ret = ENOMEM;
+
+ ev = tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = msg_pingpong_send(ev, ev, dst);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ ret = errno;
+ goto fail;
+ }
+ ret = msg_pingpong_recv(req);
+fail:
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+static void ping_responder_exit(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ bool *done = private_data;
+
+ printf("Child: received write on exit-pipe\n");
+
+ *done = true;
+}
+
+static void ping_responder(int ready_pipe, int exit_pipe)
+{
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct tevent_fd *exit_handler;
+ char c = 0;
+ bool done = false;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ fprintf(stderr, "child tevent_context_init failed\n");
+ exit(1);
+ }
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "child messaging_init failed\n");
+ exit(1);
+ }
+ exit_handler = tevent_add_fd(ev, ev, exit_pipe, TEVENT_FD_READ,
+ ping_responder_exit, &done);
+ if (exit_handler == NULL) {
+ fprintf(stderr, "child tevent_add_fd failed\n");
+ exit(1);
+ }
+
+ if (write(ready_pipe, &c, 1) != 1) {
+ fprintf(stderr, "child messaging_init failed\n");
+ exit(1);
+ }
+
+ while (!done) {
+ int ret;
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ fprintf(stderr, "child tevent_loop_once failed\n");
+ exit(1);
+ }
+ }
+
+ printf("Child: done, exiting...\n");
+
+ TALLOC_FREE(msg_ctx);
+ TALLOC_FREE(ev);
+}
+
+bool run_messaging_read3(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ bool retval = false;
+ pid_t child;
+ int ready_pipe[2];
+ int exit_pipe[2];
+ int i, ret;
+ char c;
+ struct server_id dst;
+ ssize_t written;
+
+ if ((pipe(ready_pipe) != 0) || (pipe(exit_pipe) != 0)) {
+ perror("pipe failed");
+ return false;
+ }
+
+ child = fork();
+ if (child == -1) {
+ perror("fork failed");
+ return false;
+ }
+
+ if (child == 0) {
+ close(ready_pipe[0]);
+ close(exit_pipe[1]);
+ ping_responder(ready_pipe[1], exit_pipe[0]);
+ exit(0);
+ }
+ close(ready_pipe[1]);
+ close(exit_pipe[0]);
+
+ if (read(ready_pipe[0], &c, 1) != 1) {
+ perror("read failed");
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ goto fail;
+ }
+
+ dst = (struct server_id){ .pid = child, .vnn = NONCLUSTER_VNN, };
+
+ for (i=0; i<100; i++) {
+ ret = msg_pingpong(dst);
+ if (ret != 0){
+ fprintf(stderr, "msg_pingpong failed\n");
+ goto fail;
+ }
+ }
+
+ printf("Parent: telling child to exit\n");
+
+ written = write(exit_pipe[1], &c, 1);
+ if (written != 1) {
+ perror("write to exit_pipe failed");
+ goto fail;
+ }
+
+ ret = waitpid(child, NULL, 0);
+ if (ret == -1) {
+ perror("waitpid failed");
+ goto fail;
+ }
+
+ printf("Parent: child exited. Done\n");
+
+ retval = true;
+fail:
+ TALLOC_FREE(msg_ctx);
+ TALLOC_FREE(ev);
+ return retval;
+}
+
+/**
+ * read4:
+ *
+ * test transferring a big payload.
+ */
+
+#define MSG_TORTURE_READ4 0xF104
+
+static bool read4_child(int ready_fd)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool retval = false;
+ uint8_t c = 1;
+ struct tevent_req *subreq;
+ int ret;
+ ssize_t bytes;
+ struct messaging_rec *rec;
+ bool ok;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ fprintf(stderr, "child: tevent_context_init failed\n");
+ goto done;
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "child: messaging_init failed\n");
+ goto done;
+ }
+
+ printf("child: telling parent we're ready to receive messages\n");
+
+ /* Tell the parent we are ready to receive messages. */
+ bytes = write(ready_fd, &c, 1);
+ if (bytes != 1) {
+ perror("child: failed to write to ready_fd");
+ goto done;
+ }
+
+ printf("child: waiting for messages\n");
+
+ subreq = messaging_read_send(frame, /* TALLOC_CTX */
+ ev, msg_ctx,
+ MSG_TORTURE_READ4);
+ if (subreq == NULL) {
+ fprintf(stderr, "child: messaging_read_send failed\n");
+ goto done;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ fprintf(stderr, "child: tevent_req_poll failed\n");
+ goto done;
+ }
+
+ printf("child: receiving message\n");
+
+ ret = messaging_read_recv(subreq, frame, &rec);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ fprintf(stderr, "child: messaging_read_recv failed\n");
+ goto done;
+ }
+
+ printf("child: received message\n");
+
+ /* Tell the parent we are done. */
+ bytes = write(ready_fd, &c, 1);
+ if (bytes != 1) {
+ perror("child: failed to write to ready_fd");
+ goto done;
+ }
+
+ printf("child: done\n");
+
+ retval = true;
+
+done:
+ TALLOC_FREE(frame);
+ return retval;
+}
+
+struct child_done_state {
+ int fd;
+ bool done;
+};
+
+static void child_done_cb(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct child_done_state *state =
+ (struct child_done_state *)private_data;
+ char c = 0;
+ ssize_t bytes;
+
+ bytes = read(state->fd, &c, 1);
+ if (bytes != 1) {
+ perror("parent: read from ready_fd failed");
+ }
+
+ state->done = true;
+}
+
+static bool read4_parent(pid_t child_pid, int ready_fd)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ bool retval = false;
+ int ret;
+ NTSTATUS status;
+ struct server_id dst;
+ TALLOC_CTX *frame = talloc_stackframe();
+ uint8_t c;
+ ssize_t bytes;
+ struct iovec iov;
+ DATA_BLOB blob;
+ struct tevent_fd *child_done_fde;
+ struct child_done_state child_state;
+
+ /* wait until the child is ready to receive messages */
+ bytes = read(ready_fd, &c, 1);
+ if (bytes != 1) {
+ perror("parent: read from ready_fd failed");
+ goto done;
+ }
+
+ printf("parent: child is ready to receive messages\n");
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ fprintf(stderr, "parent: tevent_context_init failed\n");
+ goto done;
+ }
+
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "parent: messaging_init failed\n");
+ goto done;
+ }
+
+ child_state.fd = ready_fd;
+ child_state.done = false;
+
+ child_done_fde = tevent_add_fd(ev, ev, ready_fd, TEVENT_FD_READ,
+ child_done_cb, &child_state);
+ if (child_done_fde == NULL) {
+ fprintf(stderr,
+ "parent: failed tevent_add_fd for child done\n");
+ goto done;
+ }
+
+ /*
+ * Send a 1M payload with the message.
+ */
+ blob = data_blob_talloc_zero(frame, 1000*1000);
+ iov.iov_base = blob.data;
+ iov.iov_len = blob.length;
+
+ dst = messaging_server_id(msg_ctx);
+ dst.pid = child_pid;
+
+ printf("parent: sending message to child\n");
+
+ status = messaging_send_iov(msg_ctx, dst, MSG_TORTURE_READ4, &iov, 1,
+ NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "parent: messaging_send_iov failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ printf("parent: waiting for child to confirm\n");
+
+ while (!child_state.done) {
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ fprintf(stderr, "parent: tevent_loop_once failed\n");
+ goto done;
+ }
+ }
+
+ printf("parent: child confirmed\n");
+
+ ret = waitpid(child_pid, NULL, 0);
+ if (ret == -1) {
+ perror("parent: waitpid failed");
+ goto done;
+ }
+
+ printf("parent: done\n");
+
+ retval = true;
+
+done:
+ TALLOC_FREE(frame);
+ return retval;
+}
+
+bool run_messaging_read4(int dummy)
+{
+ bool retval = false;
+ pid_t child_pid;
+ int ready_pipe[2];
+ int ret;
+
+ ret = pipe(ready_pipe);
+ if (ret != 0) {
+ perror("parent: pipe failed for ready_pipe");
+ return retval;
+ }
+
+ child_pid = fork();
+ if (child_pid == -1) {
+ perror("fork failed");
+ } else if (child_pid == 0) {
+ close(ready_pipe[0]);
+ retval = read4_child(ready_pipe[1]);
+ } else {
+ close(ready_pipe[1]);
+ retval = read4_parent(child_pid, ready_pipe[0]);
+ }
+
+ return retval;
+}
diff --git a/source3/torture/test_messaging_send_all.c b/source3/torture/test_messaging_send_all.c
new file mode 100644
index 0000000..8512fa4
--- /dev/null
+++ b/source3/torture/test_messaging_send_all.c
@@ -0,0 +1,279 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test for a messaging_send_all bug
+ * Copyright (C) Volker Lendecke 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "lib/util/tevent_unix.h"
+#include "messages.h"
+#include "lib/async_req/async_sock.h"
+#include "lib/util/sys_rw.h"
+
+static pid_t fork_responder(struct messaging_context *msg_ctx,
+ int exit_pipe[2])
+{
+ struct tevent_context *ev = messaging_tevent_context(msg_ctx);
+ struct tevent_req *req;
+ pid_t child_pid;
+ int ready_pipe[2];
+ char c = 0;
+ bool ok;
+ int ret, err;
+ NTSTATUS status;
+ ssize_t nwritten;
+
+ ret = pipe(ready_pipe);
+ if (ret == -1) {
+ perror("pipe failed");
+ return -1;
+ }
+
+ child_pid = fork();
+ if (child_pid == -1) {
+ perror("fork failed");
+ close(ready_pipe[0]);
+ close(ready_pipe[1]);
+ return -1;
+ }
+
+ if (child_pid != 0) {
+ ssize_t nread;
+ close(ready_pipe[1]);
+ nread = read(ready_pipe[0], &c, 1);
+ close(ready_pipe[0]);
+ if (nread != 1) {
+ perror("read failed");
+ return -1;
+ }
+ return child_pid;
+ }
+
+ close(ready_pipe[0]);
+ close(exit_pipe[1]);
+
+ status = messaging_reinit(msg_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "messaging_reinit failed: %s\n",
+ nt_errstr(status));
+ close(ready_pipe[1]);
+ exit(1);
+ }
+
+ nwritten = sys_write(ready_pipe[1], &c, 1);
+ if (nwritten != 1) {
+ fprintf(stderr, "write failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ close(ready_pipe[1]);
+
+ req = wait_for_read_send(ev, ev, exit_pipe[0], false);
+ if (req == NULL) {
+ fprintf(stderr, "wait_for_read_send failed\n");
+ exit(1);
+ }
+
+ ok = tevent_req_poll_unix(req, ev, &err);
+ if (!ok) {
+ fprintf(stderr, "tevent_req_poll_unix failed: %s\n",
+ strerror(err));
+ exit(1);
+ }
+
+ exit(0);
+}
+
+struct messaging_send_all_state {
+ struct tevent_context *ev;
+ struct messaging_context *msg;
+ pid_t *senders;
+ size_t num_received;
+};
+
+static void collect_pong_received(struct tevent_req *subreq);
+
+static struct tevent_req *collect_pong_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg,
+ const pid_t *senders,
+ size_t num_senders)
+{
+ struct tevent_req *req, *subreq;
+ struct messaging_send_all_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct messaging_send_all_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->senders = talloc_memdup(
+ state, senders, num_senders * sizeof(pid_t));
+ if (tevent_req_nomem(state->senders, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->ev = ev;
+ state->msg = msg;
+
+ subreq = messaging_read_send(state, state->ev, state->msg, MSG_PONG);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, collect_pong_received, req);
+ return req;
+}
+
+static void collect_pong_received(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct messaging_send_all_state *state = tevent_req_data(
+ req, struct messaging_send_all_state);
+ size_t num_senders = talloc_array_length(state->senders);
+ size_t i;
+ struct messaging_rec *rec;
+ int ret;
+
+ ret = messaging_read_recv(subreq, state, &rec);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+
+ /*
+ * We need to make sure we don't receive our own broadcast!
+ */
+
+ if (rec->src.pid == (uint64_t)getpid()) {
+ fprintf(stderr, "Received my own broadcast!\n");
+ tevent_req_error(req, EMULTIHOP);
+ return;
+ }
+
+ for (i=0; i<num_senders; i++) {
+ if (state->senders[i] == (pid_t)rec->src.pid) {
+ printf("got message from %"PRIu64"\n", rec->src.pid);
+ state->senders[i] = 0;
+ state->num_received += 1;
+ break;
+ }
+ }
+
+ if (state->num_received == num_senders) {
+ printf("done\n");
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = messaging_read_send(state, state->ev, state->msg, MSG_PONG);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, collect_pong_received, req);
+}
+
+static int collect_pong_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+extern int torture_nprocs;
+
+bool run_messaging_send_all(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg_ctx = NULL;
+ int exit_pipe[2];
+ pid_t children[MAX(5, torture_nprocs)];
+ struct tevent_req *req;
+ size_t i;
+ bool ok;
+ int ret, err;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ return false;
+ }
+ msg_ctx = messaging_init(ev, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ return false;
+ }
+ ret = pipe(exit_pipe);
+ if (ret != 0) {
+ perror("parent: pipe failed for exit_pipe");
+ return false;
+ }
+
+ for (i=0; i<ARRAY_SIZE(children); i++) {
+ children[i] = fork_responder(msg_ctx, exit_pipe);
+ if (children[i] == -1) {
+ fprintf(stderr, "fork_responder(%zu) failed\n", i);
+ return false;
+ }
+ }
+
+ req = collect_pong_send(ev, ev, msg_ctx, children,
+ ARRAY_SIZE(children));
+ if (req == NULL) {
+ perror("collect_pong failed");
+ return false;
+ }
+
+ ok = tevent_req_set_endtime(req, ev,
+ tevent_timeval_current_ofs(10, 0));
+ if (!ok) {
+ perror("tevent_req_set_endtime failed");
+ return false;
+ }
+
+ messaging_send_all(msg_ctx, MSG_PING, NULL, 0);
+
+ ok = tevent_req_poll_unix(req, ev, &err);
+ if (!ok) {
+ perror("tevent_req_poll_unix failed");
+ return false;
+ }
+
+ ret = collect_pong_recv(req);
+ TALLOC_FREE(req);
+
+ if (ret != 0) {
+ fprintf(stderr, "collect_pong_send returned %s\n",
+ strerror(ret));
+ return false;
+ }
+
+ close(exit_pipe[1]);
+
+ for (i=0; i<ARRAY_SIZE(children); i++) {
+ pid_t child;
+ int status;
+
+ do {
+ child = waitpid(children[i], &status, 0);
+ } while ((child == -1) && (errno == EINTR));
+
+ if (child != children[i]) {
+ printf("waitpid(%d) failed\n", children[i]);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/source3/torture/test_namemap_cache.c b/source3/torture/test_namemap_cache.c
new file mode 100644
index 0000000..b30fe87
--- /dev/null
+++ b/source3/torture/test_namemap_cache.c
@@ -0,0 +1,270 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * namemap_cache.c
+ * Copyright (C) Volker Lendecke 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "lib/namemap_cache.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/gencache.h"
+
+static const struct dom_sid domsid = {
+ 1, 4, {0,0,0,0,0,5}, {21, 123, 456, 789}
+};
+
+static void namemap_cache1_fn1(const char *domain,
+ const char *name,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ bool *p_ok = private_data;
+ bool ok;
+
+ ok = strequal(domain, "nt authority");
+ ok &= strequal(name, "network");
+ ok &= (type == SID_NAME_WKN_GRP);
+
+ *p_ok = ok;
+}
+
+static void namemap_cache1_fn2(const struct dom_sid *sid,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ bool *p_ok = private_data;
+ bool ok;
+
+ ok = dom_sid_equal(sid, &global_sid_Network);
+ ok &= (type == SID_NAME_WKN_GRP);
+
+ *p_ok = ok;
+}
+
+static void namemap_cache1_fn3(const char *domain,
+ const char *name,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ bool *p_ok = private_data;
+ bool ok;
+
+ ok = strequal(domain, "");
+ ok &= strequal(name, "everyone");
+ ok &= (type == SID_NAME_WKN_GRP);
+
+ *p_ok = ok;
+}
+
+static void namemap_cache1_fn4(const struct dom_sid *sid,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ bool *p_ok = private_data;
+ bool ok;
+
+ ok = dom_sid_equal(sid, &global_sid_World);
+ ok &= (type == SID_NAME_WKN_GRP);
+
+ *p_ok = ok;
+}
+
+static void namemap_cache1_fn5(const char *domain,
+ const char *name,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ bool *p_ok = private_data;
+ bool ok;
+
+ ok = strequal(domain, "samba-dom");
+ ok &= strequal(name, "");
+ ok &= (type == SID_NAME_DOMAIN);
+
+ *p_ok = ok;
+}
+
+static void namemap_cache1_fn6(const struct dom_sid *sid,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ bool *p_ok = private_data;
+ bool ok;
+
+ ok = dom_sid_equal(sid, &domsid);
+ ok &= (type == SID_NAME_DOMAIN);
+
+ *p_ok = ok;
+}
+
+bool run_local_namemap_cache1(int dummy)
+{
+ bool found;
+ bool ok;
+
+ ok = gencache_set("SID2NAME/S-1-5-2", "invalid", time(NULL)+60);
+ if (!ok) {
+ fprintf(stderr, "gencache_set failed\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_sid(&global_sid_Network, namemap_cache1_fn1,
+ &found);
+ if (ok) {
+ fprintf(stderr, "namemap_cache_find_sid parsed valid value\n");
+ return false;
+ }
+
+ ok = namemap_cache_set_sid2name(&global_sid_Network,
+ "NT Authority", "Network",
+ SID_NAME_WKN_GRP,
+ time(NULL) + 60);
+ if (!ok) {
+ fprintf(stderr, "namemap_cache_set_sid2name failed\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_sid(&global_sid_Network, namemap_cache1_fn1,
+ &found);
+ if (!ok) {
+ fprintf(stderr, "namecache_find_sid failed\n");
+ return false;
+ }
+ if (!found) {
+ fprintf(stderr, "wrong values found\n");
+ return false;
+ }
+
+ ok = namemap_cache_set_name2sid("NT Authority", "Network",
+ &global_sid_Network,
+ SID_NAME_WKN_GRP,
+ time(NULL) + 60);
+ if (!ok) {
+ fprintf(stderr, "namemap_cache_set_name2sid failed\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_name("nt authority", "network",
+ namemap_cache1_fn2, &found);
+ if (!ok) {
+ fprintf(stderr, "namecache_find_name failed\n");
+ return false;
+ }
+ if (!found) {
+ fprintf(stderr, "wrong values found\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_name("foo", "bar", namemap_cache1_fn2, &found);
+ if (ok) {
+ fprintf(stderr,
+ "namemap_cache_find_name succeeded unexpectedly\n");
+ return false;
+ }
+
+ /*
+ * Test "" domain name
+ */
+
+ ok = namemap_cache_set_sid2name(&global_sid_World, "", "Everyone",
+ SID_NAME_WKN_GRP,
+ time(NULL) + 60);
+ if (!ok) {
+ fprintf(stderr, "namemap_cache_set_sid2name failed\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_sid(&global_sid_World, namemap_cache1_fn3,
+ &found);
+ if (!ok) {
+ fprintf(stderr, "namecache_find_sid failed\n");
+ return false;
+ }
+ if (!found) {
+ fprintf(stderr, "wrong values found\n");
+ return false;
+ }
+
+ ok = namemap_cache_set_name2sid("", "Everyone",
+ &global_sid_World, SID_NAME_WKN_GRP,
+ time(NULL) + 60);
+ if (!ok) {
+ fprintf(stderr, "namemap_cache_set failed\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_name("", "everyone",
+ namemap_cache1_fn4, &found);
+ if (!ok) {
+ fprintf(stderr, "namecache_find_name failed\n");
+ return false;
+ }
+ if (!found) {
+ fprintf(stderr, "wrong values found\n");
+ return false;
+ }
+
+ /*
+ * Test domain only
+ */
+
+ ok = namemap_cache_set_sid2name(&domsid, "SAMBA-DOM", "",
+ SID_NAME_DOMAIN,
+ time(NULL) + 60);
+ if (!ok) {
+ fprintf(stderr, "namemap_cache_set failed\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_sid(&domsid, namemap_cache1_fn5,
+ &found);
+ if (!ok) {
+ fprintf(stderr, "namecache_find_sid failed\n");
+ return false;
+ }
+ if (!found) {
+ fprintf(stderr, "wrong values found\n");
+ return false;
+ }
+
+ ok = namemap_cache_set_name2sid("SAMBA-DOM", "",
+ &domsid, SID_NAME_DOMAIN,
+ time(NULL) + 60);
+ if (!ok) {
+ fprintf(stderr, "namemap_cache_set failed\n");
+ return false;
+ }
+
+ ok = namemap_cache_find_name("samba-dom", "",
+ namemap_cache1_fn6, &found);
+ if (!ok) {
+ fprintf(stderr, "namecache_find_name failed\n");
+ return false;
+ }
+ if (!found) {
+ fprintf(stderr, "wrong values found\n");
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/torture/test_notify.c b/source3/torture/test_notify.c
new file mode 100644
index 0000000..b265845
--- /dev/null
+++ b/source3/torture/test_notify.c
@@ -0,0 +1,731 @@
+/*
+ Unix SMB/CIFS implementation.
+ Scalability test for notifies
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libcli/security/security.h"
+#include "lib/tevent_barrier.h"
+
+extern int torture_nprocs, torture_numops;
+
+struct wait_for_one_notify_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t dnum;
+ uint32_t filter;
+ bool recursive;
+ unsigned *num_notifies;
+};
+
+static void wait_for_one_notify_opened(struct tevent_req *subreq);
+static void wait_for_one_notify_chkpath_done(struct tevent_req *subreq);
+static void wait_for_one_notify_done(struct tevent_req *subreq);
+static void wait_for_one_notify_closed(struct tevent_req *subreq);
+
+static struct tevent_req *wait_for_one_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *path,
+ uint32_t filter,
+ bool recursive,
+ unsigned *num_notifies)
+{
+ struct tevent_req *req, *subreq;
+ struct wait_for_one_notify_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wait_for_one_notify_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->filter = filter;
+ state->recursive = recursive;
+ state->num_notifies = num_notifies;
+
+ subreq = cli_ntcreate_send(
+ state, state->ev, state->cli, path, 0,
+ MAXIMUM_ALLOWED_ACCESS,
+ 0, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, FILE_DIRECTORY_FILE,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wait_for_one_notify_opened, req);
+ return req;
+}
+
+static void wait_for_one_notify_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wait_for_one_notify_state *state = tevent_req_data(
+ req, struct wait_for_one_notify_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->dnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_notify_send(state, state->ev, state->cli, state->dnum,
+ 0xffff, state->filter, state->recursive);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wait_for_one_notify_done, req);
+
+ /*
+ * To make sure the notify received at the server, we do another no-op
+ * that is replied to.
+ */
+ subreq = cli_chkpath_send(state, state->ev, state->cli, "\\");
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wait_for_one_notify_chkpath_done, req);
+}
+
+static void wait_for_one_notify_chkpath_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wait_for_one_notify_state *state = tevent_req_data(
+ req, struct wait_for_one_notify_state);
+ NTSTATUS status;
+
+ status = cli_chkpath_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ *state->num_notifies += 1;
+}
+
+static void wait_for_one_notify_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wait_for_one_notify_state *state = tevent_req_data(
+ req, struct wait_for_one_notify_state);
+ uint32_t num_changes;
+ struct notify_change *changes;
+ NTSTATUS status;
+
+ status = cli_notify_recv(subreq, state, &num_changes, &changes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_close_send(state, state->ev, state->cli, state->dnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wait_for_one_notify_closed, req);
+}
+
+static void wait_for_one_notify_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wait_for_one_notify_state *state = tevent_req_data(
+ req, struct wait_for_one_notify_state);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ *state->num_notifies -= 1;
+ tevent_req_done(req);
+}
+
+static NTSTATUS wait_for_one_notify_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void notify_bench2_done(struct tevent_req *req);
+
+bool run_notify_bench2(int dummy)
+{
+ struct cli_state *cli;
+ struct cli_state **clis;
+ struct tevent_context *ev;
+ unsigned num_notifies = 0;
+ NTSTATUS status;
+ int i;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ printf("starting notify bench 2 test\n");
+
+ cli_rmdir(cli, "\\notify.dir\\subdir");
+ cli_rmdir(cli, "\\notify.dir");
+
+ status = cli_mkdir(cli, "\\notify.dir");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mkdir failed : %s\n", nt_errstr(status));
+ return false;
+ }
+
+ clis = talloc_array(talloc_tos(), struct cli_state *, torture_nprocs);
+ if (clis == NULL) {
+ printf("talloc failed\n");
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("tevent_context_create failed\n");
+ return false;
+ }
+
+ for (i=0; i<torture_nprocs; i++) {
+ int j;
+ if (!torture_open_connection(&clis[i], i)) {
+ return false;
+ }
+
+ for (j=0; j<torture_numops; j++) {
+ struct tevent_req *req;
+ req = wait_for_one_notify_send(
+ talloc_tos(), ev, clis[i], "\\notify.dir",
+ FILE_NOTIFY_CHANGE_ALL, true,
+ &num_notifies);
+ if (req == NULL) {
+ printf("wait_for_one_notify_send failed\n");
+ return false;
+ }
+ tevent_req_set_callback(req, notify_bench2_done, NULL);
+ }
+ }
+
+ while (num_notifies < (unsigned)(torture_nprocs * torture_numops)) {
+ int ret;
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ printf("tevent_loop_once failed: %s\n",
+ strerror(errno));
+ return false;
+ }
+ }
+
+ cli_mkdir(cli, "\\notify.dir\\subdir");
+
+ while (num_notifies > 0) {
+ int ret;
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ printf("tevent_loop_once failed: %s\n",
+ strerror(errno));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void notify_bench2_done(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ status = wait_for_one_notify_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("wait_for_one_notify returned %s\n",
+ nt_errstr(status));
+ }
+}
+
+/*
+ * This test creates a subdirectory. It then waits on a barrier before the
+ * notify is sent. Then it creates the notify. It then waits for another
+ * barrier, so that all of the notifies have gone through. It then creates
+ * another subdirectory, which will trigger notifications to be sent. When the
+ * notifies have been received, it waits once more before everything is
+ * cleaned up.
+ */
+
+struct notify_bench3_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *dir;
+ uint16_t dnum;
+ const char *subdir_path;
+ uint16_t subdir_dnum;
+ int wait_timeout;
+ struct tevent_barrier *small;
+ struct tevent_barrier *large;
+};
+
+static void notify_bench3_mkdir1_done(struct tevent_req *subreq);
+static void notify_bench3_before_notify(struct tevent_req *subreq);
+static void notify_bench3_chkpath_done(struct tevent_req *subreq);
+static void notify_bench3_before_mkdir2(struct tevent_req *subreq);
+static void notify_bench3_notify_done(struct tevent_req *subreq);
+static void notify_bench3_notifies_done(struct tevent_req *subreq);
+static void notify_bench3_mksubdir_done(struct tevent_req *subreq);
+static void notify_bench3_before_close_subdir(struct tevent_req *subreq);
+static void notify_bench3_close_subdir_done(struct tevent_req *subreq);
+static void notify_bench3_deleted_subdir(struct tevent_req *subreq);
+static void notify_bench3_deleted_subdirs(struct tevent_req *subreq);
+static void notify_bench3_del_on_close_set(struct tevent_req *subreq);
+static void notify_bench3_closed(struct tevent_req *subreq);
+
+static struct tevent_req *notify_bench3_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev, struct cli_state *cli,
+ const char *dir, const char *subdir_path,
+ struct tevent_barrier *small, struct tevent_barrier *large)
+{
+ struct tevent_req *req, *subreq;
+ struct notify_bench3_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct notify_bench3_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->dir = dir;
+ state->subdir_path = subdir_path;
+ state->small = small;
+ state->large = large;
+
+ subreq = cli_ntcreate_send(
+ state, state->ev, state->cli, state->dir, 0,
+ MAXIMUM_ALLOWED_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN_IF, FILE_DIRECTORY_FILE,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, notify_bench3_mkdir1_done, req);
+ return req;
+}
+
+static void notify_bench3_mkdir1_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->dnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = tevent_barrier_wait_send(state, state->ev, state->small);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_before_notify, req);
+}
+
+static void notify_bench3_before_notify(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ int ret;
+
+ ret = tevent_barrier_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+ subreq = cli_notify_send(state, state->ev, state->cli, state->dnum,
+ 0xffff, FILE_NOTIFY_CHANGE_ALL, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_notify_done, req);
+
+ /*
+ * To make sure the notify received at the server, we do another no-op
+ * that is replied to.
+ */
+ subreq = cli_chkpath_send(state, state->ev, state->cli, "\\");
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_chkpath_done, req);
+}
+
+static void notify_bench3_notify_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ uint32_t num_changes;
+ struct notify_change *changes;
+ NTSTATUS status;
+
+ status = cli_notify_recv(subreq, state, &num_changes, &changes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = tevent_barrier_wait_send(state, state->ev, state->large);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_notifies_done, req);
+}
+
+static void notify_bench3_notifies_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int ret;
+
+ ret = tevent_barrier_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+}
+
+static void notify_bench3_chkpath_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ NTSTATUS status;
+
+ status = cli_chkpath_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (state->subdir_path == NULL) {
+ return;
+ }
+ subreq = tevent_barrier_wait_send(state, state->ev, state->small);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_before_mkdir2, req);
+}
+
+static void notify_bench3_before_mkdir2(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ int ret;
+
+ ret = tevent_barrier_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+ subreq = cli_ntcreate_send(
+ state, state->ev, state->cli, state->subdir_path, 0,
+ MAXIMUM_ALLOWED_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ FILE_DIRECTORY_FILE,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_mksubdir_done, req);
+}
+
+static void notify_bench3_mksubdir_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->subdir_dnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = tevent_barrier_wait_send(state, state->ev, state->large);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_before_close_subdir,
+ req);
+}
+
+static void notify_bench3_before_close_subdir(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ int ret;
+
+ ret = tevent_barrier_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+ subreq = cli_close_send(state,
+ state->ev,
+ state->cli,
+ state->subdir_dnum,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_close_subdir_done, req);
+}
+
+static void notify_bench3_close_subdir_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_rmdir_send(state, state->ev, state->cli,
+ state->subdir_path);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_deleted_subdir, req);
+}
+
+static void notify_bench3_deleted_subdir(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ NTSTATUS status;
+
+ status = cli_rmdir_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = tevent_barrier_wait_send(state, state->ev, state->small);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_deleted_subdirs, req);
+}
+
+static void notify_bench3_deleted_subdirs(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ int ret;
+
+ ret = tevent_barrier_wait_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+ subreq = cli_nt_delete_on_close_send(state, state->ev, state->cli,
+ state->dnum, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_del_on_close_set, req);
+}
+
+static void notify_bench3_del_on_close_set(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_bench3_state *state = tevent_req_data(
+ req, struct notify_bench3_state);
+ NTSTATUS status;
+
+ status = cli_nt_delete_on_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_close_send(state, state->ev, state->cli, state->dnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_bench3_closed, req);
+}
+
+static void notify_bench3_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS notify_bench3_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static void notify_bench3_done(struct tevent_req *req)
+{
+ unsigned *num_done = (unsigned *)tevent_req_callback_data_void(req);
+ NTSTATUS status;
+
+ status = notify_bench3_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("notify_bench3 returned %s\n", nt_errstr(status));
+ }
+ *num_done += 1;
+}
+
+static void notify_bench3_barrier_cb(void *private_data)
+{
+ struct timeval *ts = (struct timeval *)private_data;
+ struct timeval now;
+
+ GetTimeOfDay(&now);
+ printf("barrier triggered: %f\n", timeval_elapsed2(ts, &now));
+ GetTimeOfDay(ts);
+}
+
+bool run_notify_bench3(int dummy)
+{
+ struct cli_state **clis;
+ struct tevent_context *ev;
+ struct tevent_barrier *small;
+ struct tevent_barrier *large;
+ int i;
+ unsigned num_done = 0;
+ struct timeval ts, now;
+
+ clis = talloc_array(talloc_tos(), struct cli_state *, torture_nprocs);
+ if (clis == NULL) {
+ printf("talloc failed\n");
+ return false;
+ }
+
+ GetTimeOfDay(&ts);
+
+ small = tevent_barrier_init(
+ talloc_tos(), torture_nprocs * torture_numops,
+ notify_bench3_barrier_cb, &ts);
+ if (small == NULL) {
+ return false;
+ }
+
+ large = tevent_barrier_init(
+ talloc_tos(), 2 * torture_nprocs * torture_numops,
+ notify_bench3_barrier_cb, &ts);
+ if (large == NULL) {
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("tevent_context_create failed\n");
+ return false;
+ }
+
+ for (i=0; i<torture_nprocs; i++) {
+ if (!torture_open_connection(&clis[i], i)) {
+ return false;
+ }
+ }
+
+ for (i=0; i<torture_nprocs; i++) {
+ int j;
+ for (j=0; j<torture_numops; j++) {
+ int idx = i * torture_numops + j;
+ struct tevent_req *req;
+ char *dirname, *subdirname;
+
+ dirname = talloc_asprintf(
+ talloc_tos(), "\\dir%.8d", idx);
+ if (dirname == NULL) {
+ return false;
+ }
+ subdirname = talloc_asprintf(
+ talloc_tos(), "\\dir%.8d\\subdir",
+ (idx + torture_numops + 1) %
+ (torture_nprocs * torture_numops));
+ if (subdirname == NULL) {
+ return false;
+ }
+
+ req = notify_bench3_send(
+ talloc_tos(), ev, clis[i], dirname,
+ subdirname, small, large);
+ if (req == NULL) {
+ return false;
+ }
+ tevent_req_set_callback(req, notify_bench3_done,
+ &num_done);
+ }
+ }
+
+ while (num_done < (unsigned)(torture_nprocs * torture_numops)) {
+ int ret;
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ printf("tevent_loop_once failed: %s\n",
+ strerror(errno));
+ return false;
+ }
+ }
+
+ GetTimeOfDay(&now);
+ printf("turndow: %f\n", timeval_elapsed2(&ts, &now));
+ TALLOC_FREE(small);
+ TALLOC_FREE(large);
+ return true;
+}
diff --git a/source3/torture/test_notify_online.c b/source3/torture/test_notify_online.c
new file mode 100644
index 0000000..d8a5d37
--- /dev/null
+++ b/source3/torture/test_notify_online.c
@@ -0,0 +1,293 @@
+/*
+ Unix SMB/CIFS implementation.
+ Make sure that for offline files pread and pwrite trigger a notify
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libcli/security/security.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "libsmb/libsmb.h"
+
+extern char *test_filename;
+
+struct notify_online_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t dnum;
+ const char *fname;
+ uint16_t fnum;
+ bool got_notify;
+};
+
+static void notify_online_opened_dir(struct tevent_req *subreq);
+static void notify_online_notify_callback(struct tevent_req *subreq);
+static void notify_online_opened_file(struct tevent_req *subreq);
+static void notify_online_sent_read(struct tevent_req *subreq);
+static void notify_online_sent_closefile(struct tevent_req *subreq);
+static void notify_online_waited(struct tevent_req *subreq);
+static void notify_online_sent_closedir(struct tevent_req *subreq);
+
+static struct tevent_req *notify_online_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, const char *dname, const char *fname)
+{
+ struct tevent_req *req, *subreq;
+ struct notify_online_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct notify_online_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fname = fname;
+
+ subreq = cli_ntcreate_send(
+ state, ev, cli, dname, EXTENDED_RESPONSE_REQUIRED,
+ SEC_FILE_READ_DATA, 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_OPEN, 0, SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, notify_online_opened_dir, req);
+ return req;
+}
+
+static void notify_online_opened_dir(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_online_state *state = tevent_req_data(
+ req, struct notify_online_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->dnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_notify_send(state, state->ev, state->cli, state->dnum,
+ 128, FILE_NOTIFY_CHANGE_ATTRIBUTES, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_online_notify_callback, req);
+
+ subreq = cli_ntcreate_send(
+ state, state->ev, state->cli, state->fname, 0,
+ GENERIC_READ_ACCESS, FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, FILE_NON_DIRECTORY_FILE,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_online_opened_file, req);
+}
+
+static void notify_online_notify_callback(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_online_state *state = tevent_req_data(
+ req, struct notify_online_state);
+ NTSTATUS status;
+ uint32_t num_changes;
+ struct notify_change *changes;
+
+ status = cli_notify_recv(subreq, state, &num_changes, &changes);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if ((num_changes == 1)
+ && (changes[0].action == NOTIFY_ACTION_MODIFIED)
+ && (strcmp(changes[0].name, state->fname) == 0)) {
+ state->got_notify = true;
+ }
+ tevent_req_done(req);
+}
+
+static void notify_online_opened_file(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_online_state *state = tevent_req_data(
+ req, struct notify_online_state);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->fnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_read_andx_send(
+ state, state->ev, state->cli, state->fnum, 0, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_online_sent_read, req);
+}
+
+static void notify_online_sent_read(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_online_state *state = tevent_req_data(
+ req, struct notify_online_state);
+ NTSTATUS status;
+ ssize_t received;
+ uint8_t *buf;
+
+ status = cli_read_andx_recv(subreq, &received, &buf);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = cli_close_send(state, state->ev, state->cli, state->fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_online_sent_closefile, req);
+}
+
+static void notify_online_sent_closefile(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_online_state *state = tevent_req_data(
+ req, struct notify_online_state);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ subreq = tevent_wakeup_send(
+ state, state->ev, timeval_current_ofs(10, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_online_waited, req);
+}
+
+static void notify_online_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct notify_online_state *state = tevent_req_data(
+ req, struct notify_online_state);
+
+ tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ subreq = cli_close_send(state, state->ev, state->cli, state->dnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, notify_online_sent_closedir, req);
+}
+
+static void notify_online_sent_closedir(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+}
+
+static NTSTATUS notify_online_recv(struct tevent_req *req, bool *got_notify)
+{
+ struct notify_online_state *state = tevent_req_data(
+ req, struct notify_online_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *got_notify = state->got_notify;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS notify_online(struct cli_state *cli,
+ const char *dirname, const char *filename,
+ bool *got_notify)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = notify_online_send(frame, ev, cli, dirname, filename);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = notify_online_recv(req, got_notify);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+bool run_notify_online(int dummy)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+ char *p;
+ const char *dir;
+ const char *file;
+ bool got_notify = false;
+
+ printf("Starting NOTIFY_ONLINE\n");
+
+ if (test_filename == NULL) {
+ fprintf(stderr, "<-f filename> missing\n");
+ return false;
+ }
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ p = strrchr(test_filename, '/');
+ if (p != NULL) {
+ dir = SMB_STRNDUP(test_filename, p-test_filename);
+ file = SMB_STRDUP(p+1);
+ } else {
+ dir = "";
+ file = test_filename;
+ }
+
+ status = notify_online(cli, dir, file, &got_notify);
+ d_printf("notify_online returned %s (%d)\n", nt_errstr(status),
+ (int)got_notify);
+ torture_close_connection(cli);
+ return NT_STATUS_IS_OK(status) && got_notify;
+}
diff --git a/source3/torture/test_nttrans_create.c b/source3/torture/test_nttrans_create.c
new file mode 100644
index 0000000..5e7ce7e
--- /dev/null
+++ b/source3/torture/test_nttrans_create.c
@@ -0,0 +1,108 @@
+/*
+ Unix SMB/CIFS implementation.
+ Basic test for share secdescs vs nttrans_create
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/secdesc.h"
+#include "libcli/security/security.h"
+
+bool run_nttrans_create(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status, status2;
+ bool ret = false;
+ struct security_ace ace;
+ struct security_acl acl;
+ struct security_descriptor *sd;
+ const char *fname = "transtest";
+ uint16_t fnum, fnum2;
+ struct dom_sid owner;
+
+ printf("Starting NTTRANS_CREATE\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ printf("torture_open_connection failed\n");
+ goto fail;
+ }
+
+ ZERO_STRUCT(ace);
+ ace.type = SEC_ACE_TYPE_ACCESS_ALLOWED;
+ ace.access_mask = SEC_RIGHTS_FILE_ALL & ~SEC_STD_WRITE_DAC;
+ sid_copy(&ace.trustee, &global_sid_World);
+
+ acl.revision = SECURITY_ACL_REVISION_NT4;
+ acl.size = 0;
+ acl.num_aces = 1;
+ acl.aces = &ace;
+
+ dom_sid_parse("S-1-22-1-1000", &owner);
+
+ sd = make_sec_desc(talloc_tos(),
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE|
+ SEC_DESC_DACL_PRESENT|SEC_DESC_OWNER_DEFAULTED|
+ SEC_DESC_GROUP_DEFAULTED,
+ NULL, NULL, NULL, &acl, NULL);
+ if (sd == NULL) {
+ d_fprintf(stderr, "make_sec_desc failed\n");
+ goto fail;
+ }
+
+ status = cli_nttrans_create(
+ cli, fname, 0, FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS|
+ READ_CONTROL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE| FILE_SHARE_DELETE,
+ FILE_CREATE, 0, 0, sd, NULL, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_nttrans_create returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ cli_query_secdesc(cli, fnum, talloc_tos(), NULL);
+
+ status2 = cli_ntcreate(cli, fname, 0, WRITE_DAC_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+
+ status = cli_nt_delete_on_close(cli, fnum, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_nt_delete_on_close returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (!NT_STATUS_EQUAL(status2, NT_STATUS_ACCESS_DENIED)) {
+ d_fprintf(stderr, "cli_ntcreate returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ if (cli != NULL) {
+ torture_close_connection(cli);
+ }
+ return ret;
+}
diff --git a/source3/torture/test_nttrans_fsctl.c b/source3/torture/test_nttrans_fsctl.c
new file mode 100644
index 0000000..aea80c5
--- /dev/null
+++ b/source3/torture/test_nttrans_fsctl.c
@@ -0,0 +1,288 @@
+/*
+ Unix SMB/CIFS implementation.
+ Basic test for NTTRANS FSCTL requests (copied from NTTRANS CREATE)
+ Copyright (C) Richard Sharpe 2011
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/security.h"
+
+bool run_nttrans_fsctl(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool ret = false;
+ const char *fname = "fsctltest";
+ uint16_t fnum;
+ uint16_t setup[4];
+ uint8_t *object_data = NULL;
+ uint8_t *ranges = NULL;
+ uint8_t range_data[16];
+ uint8_t *param_data = NULL;
+ uint8_t data[1] = { 0x1 };
+ uint32_t rdata_size;
+ uint32_t rparam_size;
+
+ printf("Starting NTTRANS_FSCTL\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ printf("torture_open_connection failed\n");
+ goto fail;
+ }
+
+ status = cli_nttrans_create(
+ cli, fname, 0, FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS|
+ READ_CONTROL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE| FILE_SHARE_DELETE,
+ FILE_CREATE, 0, 0, NULL, NULL, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_nttrans_create returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli, fnum, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_nt_delete_on_close returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* Fill in for FSCTL_SET_SPARSE and call cli_trans ... */
+ SIVAL(setup, 0, FSCTL_SET_SPARSE); /* returns value */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ data, 1, 1, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ NULL, 0, NULL, /* rparam, min_rparam, num_rparam */
+ NULL, 0, NULL); /* rdata, ... */
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_trans of FSCTL_SET_SPARSE returned %s instead of NT_STATUS_OK\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ printf("FSCTL_SET_SPARSE returned correct status \n");
+
+ /* Fill in for FSCTL_CREATE_OR_GET_OBJECT_ID and call cli_trans ... */
+ SIVAL(setup, 0, FSCTL_CREATE_OR_GET_OBJECT_ID); /* returns value */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ NULL, 0, 64, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ &param_data, 0, &rparam_size, /* rparam, min_rparam, num_rparam */
+ &object_data, 0, &rdata_size); /* rdata, ... */
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_trans of FSCTL_CREATE_OR_GET_OBJECT_ID returned %s instead of NT_STATUS_OK\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(object_data);
+ TALLOC_FREE(param_data);
+
+ printf("FSCTL_CREATE_OR_GET_OBJECT_ID returned correct status \n");
+
+ /* Fill in for FSCTL_GET_REPARSE_POINT and call cli_trans ... */
+ SIVAL(setup, 0, FSCTL_GET_REPARSE_POINT); /* returns NOT A REPARSE POINT */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ NULL, 0, 0, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ NULL, 0, NULL, /* rparam, min_rparam, num_rparam */
+ NULL, 0, NULL); /* rdata, ... */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_REPARSE_POINT)) {
+ d_fprintf(stderr, "cli_trans of FSCTL_GET_REPARSE_POINT returned %s instead of NT_STATUS_NOT_A_REPARSE_POINT\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ printf("FSCTL_GET_REPARSE_POINT returned correct status \n");
+
+ /* Fill in for FSCTL_SET_REPARSE_POINT and call cli_trans ... */
+ SIVAL(setup, 0, FSCTL_SET_REPARSE_POINT); /* returns INVALID_BUFFER_SIZE */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ NULL, 0, 0, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ NULL, 0, NULL, /* rparam, min_rparam, num_rparam */
+ NULL, 0, NULL); /* rdata, ... */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_BUFFER_SIZE)) {
+ d_fprintf(stderr,
+ "cli_trans of FSCTL_SET_REPARSE_POINT returned %s "
+ "instead of NT_STATUS_INVALID_BUFFER_SIZE\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ printf("FSCTL_SET_REPARSE_POINT returned correct status \n");
+
+ /*
+ * Fill in for FSCTL_GET_SHADOW_COPY_DATA and call cli_trans ... what
+ * we do is send an invalid data length to provoke an INVALID PARAMETER
+ * response.
+ */
+ SIVAL(setup, 0, FSCTL_GET_SHADOW_COPY_DATA); /* Should return IVN VAL */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ memset(range_data, 0, sizeof(range_data)); /* 0 and 0 */
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ NULL, 0, 8, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ NULL, 0, NULL, /* rparam, min_rparam, num_rparam */
+ &ranges, 0, &rdata_size); /* rdata, ... */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ d_fprintf(stderr, "cli_trans of FSCTL_QUERY_ALLOCATED_RANGES returned %s instead of NT_STATUS_INVALID_PARAMETER\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(ranges);
+
+ printf("FSCTL_GET_SHADOW_COPY_DATA returned correct status \n");
+ /*
+ * Fill in for FSCTL_FIND_FILES_BY and call cli_trans ... here we are
+ * only probing for its existence by provoking an INVALID PARAM
+ * response with a short and invalid SID in range_data
+ */
+ SIVAL(setup, 0, FSCTL_FIND_FILES_BY_SID); /* Should return 16 bytes */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ memset(range_data, 0, sizeof(range_data)); /* 0 and 0 */
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ range_data, 4, 16, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ NULL, 0, NULL, /* rparam, min_rparam, num_rparam */
+ &ranges, 0, &rdata_size); /* rdata, ... */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ d_fprintf(stderr, "cli_trans of FSCTL_QUERY_ALLOCATED_RANGES returned %s instead of NT_STATUS_INVALID_PARAMETER\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ printf("FSCTL_FIND_FILES_BY_SID returned correct status \n");
+
+ /* Fill in for FSCTL_QUERY_ALLOCATED_RANGES and call cli_trans ... */
+ SIVAL(setup, 0, FSCTL_QUERY_ALLOCATED_RANGES); /* Should return 16 bytes */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ memset(range_data, 0, sizeof(range_data)); /* 0 and 0 */
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ range_data, 16, 16, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ NULL, 0, NULL, /* rparam, min_rparam, num_rparam */
+ &ranges, 0, &rdata_size); /* rdata, ... */
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_trans of FSCTL_QUERY_ALLOCATED_RANGES returned %s instead of NT_STATUS_OK\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(ranges);
+
+ printf("FSCTL_QUERY_ALLOCATED_RANGES returned correct status \n");
+
+ /* Fill in for FSCTL_IS_VOLUME_DIRTY and call cli_trans ... */
+ SIVAL(setup, 0, FSCTL_IS_VOLUME_DIRTY); /* Should return INVAL PARAM */
+ SSVAL(setup, 4, fnum);
+ SCVAL(setup, 6, 0x1); /* It is an fsctl */
+ SCVAL(setup, 7, 0x0);
+
+ status = cli_trans(talloc_tos(), cli, SMBnttrans,
+ NULL, fnum,
+ NT_TRANSACT_IOCTL, 0,
+ setup, 4, 4,
+ NULL, 0, 0, /* param, param_num, max_param */
+ NULL, 0, 0, /* data, data_len, max_data */
+ NULL, /* recv_flags2 */
+ NULL, 0, NULL, /* rsetup, min_rsetup, num_rsetup */
+ NULL, 0, NULL, /* rparam, min_rparam, num_rparam */
+ NULL, 0, NULL); /* rdata, ... */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ d_fprintf(stderr, "cli_trans of FSCTL_IS_VOLUME_DIRTY returned %s instead of NT_STATUS_INVALID_PARAMETER\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ printf("FSCTL_IS_VOLUME_DIRTY returned correct status \n");
+
+ ret = true;
+fail:
+ if (cli != NULL) {
+ torture_close_connection(cli);
+ }
+ return ret;
+}
diff --git a/source3/torture/test_oplock_cancel.c b/source3/torture/test_oplock_cancel.c
new file mode 100644
index 0000000..86ce5b7
--- /dev/null
+++ b/source3/torture/test_oplock_cancel.c
@@ -0,0 +1,168 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test cleanup behaviour
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/proto.h"
+#include "torture/proto.h"
+#include "system/filesys.h"
+#include "system/select.h"
+#include "libsmb/libsmb.h"
+#include "libcli/smb/smbXcli_base.h"
+#include "libcli/security/security.h"
+#include "lib/util/tevent_ntstatus.h"
+
+struct create_cancel_state {
+ uint8_t dummy;
+};
+
+static void create_cancel_done(struct tevent_req *subreq);
+
+static struct tevent_req *create_cancel_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct cli_state *cli, const char *fname)
+{
+ struct tevent_req *req, *subreq;
+ struct create_cancel_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct create_cancel_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_ntcreate_send(
+ mem_ctx, ev, cli, fname, 0, FILE_GENERIC_READ,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN_IF, 0, SMB2_IMPERSONATION_IMPERSONATION, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ if (!tevent_req_cancel(subreq)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, create_cancel_done, req);
+ return req;
+}
+
+static void create_cancel_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, NULL, NULL);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED)) {
+ if (NT_STATUS_IS_OK(status)) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS create_cancel_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static NTSTATUS create_cancel(struct cli_state *cli, const char *fname)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = create_cancel_send(frame, ev, cli, fname);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+ status = create_cancel_recv(req);
+ fail:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+bool run_oplock_cancel(int dummy)
+{
+ struct cli_state *cli1, *cli2;
+ const char *fname = "oplock-cancel";
+ uint16_t fnum1;
+ NTSTATUS status;
+ /*
+ * Currently this test seems to work only
+ * with SMB2/3 and only against Samba.
+ *
+ * TODO: we should change our server
+ * to ignore cancel for SMB2 Create
+ * and behave like Windows.
+ */
+ int flags = CLI_FULL_CONNECTION_DISABLE_SMB1;
+
+ if (!torture_open_connection_flags(&cli1, 0, flags)) {
+ return false;
+ }
+ cli1->use_oplocks = true;
+
+ if (!torture_open_connection_flags(&cli2, 0, flags)) {
+ return false;
+ }
+ cli2->use_oplocks = true;
+
+ status = cli_ntcreate(
+ cli1, fname, 0, FILE_GENERIC_READ, FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN_IF, 0, 0,
+ &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_ntcreate failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = create_cancel(cli2, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("create_cancel failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ cli_close(cli1, fnum1);
+
+ TALLOC_FREE(cli1);
+
+ /*
+ * Give cli1's smbd time to inform cli2's smbd
+ */
+ smb_msleep(5000);
+
+ status = cli_unlink(cli2, fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_unlink failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/torture/test_posix.c b/source3/torture/test_posix.c
new file mode 100644
index 0000000..ca40336
--- /dev/null
+++ b/source3/torture/test_posix.c
@@ -0,0 +1,1972 @@
+/*
+ Unix SMB/CIFS implementation.
+ Copyright (C) Ralph Boehme 2020
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "libsmb/proto.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "util_sd.h"
+#include "trans2.h"
+
+extern struct cli_credentials *torture_creds;
+extern fstring host, workgroup, share, password, username, myname;
+
+struct posix_test_entry {
+ const char *name;
+ const char *target;
+ const char *expected;
+ uint32_t attr_win;
+ uint32_t attr_lin;
+ uint64_t returned_size;
+ bool ok;
+};
+
+enum client_flavour { WINDOWS, POSIX };
+
+struct posix_test_state {
+ enum client_flavour flavour;
+ struct posix_test_entry *entries;
+};
+
+static NTSTATUS posix_ls_fn(struct file_info *finfo,
+ const char *name,
+ void *_state)
+{
+ struct posix_test_state *state =
+ (struct posix_test_state *)_state;
+ struct posix_test_entry *e = state->entries;
+
+ for (; e->name != NULL; e++) {
+ uint32_t attr;
+ if (!strequal(finfo->name, e->expected)) {
+ continue;
+ }
+ if (state->flavour == WINDOWS) {
+ attr = e->attr_win;
+ } else {
+ attr = e->attr_lin;
+ }
+ if (attr != finfo->attr) {
+ break;
+ }
+ e->ok = true;
+ e->returned_size = finfo->size;
+ break;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void posix_test_entries_reset(struct posix_test_state *state)
+{
+ struct posix_test_entry *e = state->entries;
+
+ for (; e->name != NULL; e++) {
+ e->ok = false;
+ e->returned_size = 0;
+ }
+}
+
+static bool posix_test_entry_check(struct posix_test_state *state,
+ const char *name,
+ bool expected,
+ uint64_t expected_size)
+{
+ struct posix_test_entry *e = state->entries;
+ bool result = false;
+
+ for (; e->name != NULL; e++) {
+ if (strequal(name, e->name)) {
+ result = e->ok;
+ break;
+ }
+ }
+ if (e->name == NULL) {
+ printf("test failed, unknown name: %s\n", name);
+ return false;
+ }
+
+ if (expected == result) {
+ return true;
+ }
+
+ printf("test failed, %s: %s\n",
+ expected ? "missing" : "unexpected",
+ name);
+
+ return false;
+}
+
+/*
+ Test non-POSIX vs POSIX ls * of symlinks
+ */
+bool run_posix_ls_wildcard_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ struct cli_state *cli_win = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ const char *file = "file";
+ const char *symlnk_dangling = "dangling";
+ const char *symlnk_dst_dangling = "xxxxxxx";
+ const char *symlnk_in_share = "symlnk_in_share";
+ const char *symlnk_dst_in_share = file;
+ const char *symlnk_outside_share = "symlnk_outside_share";
+ const char *symlnk_dst_outside_share = "/etc/passwd";
+ struct posix_test_entry entries[] = {
+ {
+ .name = file,
+ .target = NULL,
+ .expected = file,
+ .attr_win = FILE_ATTRIBUTE_ARCHIVE,
+ .attr_lin = FILE_ATTRIBUTE_ARCHIVE,
+ }, {
+ .name = symlnk_dangling,
+ .target = symlnk_dst_dangling,
+ .expected = symlnk_dangling,
+ .attr_win = FILE_ATTRIBUTE_INVALID,
+ .attr_lin = FILE_ATTRIBUTE_NORMAL,
+ }, {
+ .name = symlnk_in_share,
+ .target = symlnk_dst_in_share,
+ .expected = symlnk_in_share,
+ .attr_win = FILE_ATTRIBUTE_ARCHIVE,
+ .attr_lin = FILE_ATTRIBUTE_NORMAL,
+ }, {
+ .name = symlnk_outside_share,
+ .target = symlnk_dst_outside_share,
+ .expected = symlnk_outside_share,
+ .attr_win = FILE_ATTRIBUTE_INVALID,
+ .attr_lin = FILE_ATTRIBUTE_NORMAL,
+ }, {
+ .name = NULL,
+ }
+ };
+ struct posix_test_state _state = {
+ .entries = entries,
+ };
+ struct posix_test_state *state = &_state;
+ int i;
+ bool correct = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-LS-WILDCARD test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (!torture_open_connection(&cli_win, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+ torture_conn_set_sockopt(cli_win);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ status = cli_posix_open(cli_unix,
+ file,
+ O_RDWR|O_CREAT,
+ 0666,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ file,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ for (i = 0; entries[i].name != NULL; i++) {
+ if (entries[i].target == NULL) {
+ continue;
+ }
+ status = cli_posix_symlink(cli_unix,
+ entries[i].target,
+ entries[i].name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX symlink of %s failed (%s)\n",
+ symlnk_dangling, nt_errstr(status));
+ goto out;
+ }
+ }
+
+ printf("Doing Windows ls *\n");
+ state->flavour = WINDOWS;
+
+ status = cli_list(cli_win, "*", 0, posix_ls_fn, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_list failed %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ if (!posix_test_entry_check(state, file, true, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state, symlnk_dangling, false, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state, symlnk_outside_share, false, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state, symlnk_in_share, true, 0)) {
+ goto out;
+ }
+
+ posix_test_entries_reset(state);
+
+ printf("Doing POSIX ls *\n");
+ state->flavour = POSIX;
+
+ status = cli_list(cli_unix, "*", 0, posix_ls_fn, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ if (!posix_test_entry_check(state, file, true, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_dangling,
+ true,
+ strlen(symlnk_dst_dangling)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_outside_share,
+ true,
+ strlen(symlnk_dst_outside_share)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_in_share,
+ true,
+ strlen(symlnk_dst_in_share))) {
+ goto out;
+ }
+
+ printf("POSIX-LS-WILDCARD test passed\n");
+ correct = true;
+
+out:
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+ if (!torture_close_connection(cli_win)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Test non-POSIX vs POSIX ls single of symlinks
+ */
+bool run_posix_ls_single_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ struct cli_state *cli_win = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ const char *file = "file";
+ const char *symlnk_dangling = "dangling";
+ const char *symlnk_dst_dangling = "xxxxxxx";
+ const char *symlnk_in_share = "symlnk_in_share";
+ const char *symlnk_dst_in_share = file;
+ const char *symlnk_outside_share = "symlnk_outside_share";
+ const char *symlnk_dst_outside_share = "/etc/passwd";
+ struct posix_test_entry entries[] = {
+ {
+ .name = file,
+ .target = NULL,
+ .expected = file,
+ .attr_win = FILE_ATTRIBUTE_ARCHIVE,
+ .attr_lin = FILE_ATTRIBUTE_ARCHIVE,
+ }, {
+ .name = symlnk_dangling,
+ .target = symlnk_dst_dangling,
+ .expected = symlnk_dangling,
+ .attr_win = FILE_ATTRIBUTE_INVALID,
+ .attr_lin = FILE_ATTRIBUTE_NORMAL,
+ }, {
+ .name = symlnk_in_share,
+ .target = symlnk_dst_in_share,
+ .expected = symlnk_in_share,
+ .attr_win = FILE_ATTRIBUTE_ARCHIVE,
+ .attr_lin = FILE_ATTRIBUTE_NORMAL,
+ }, {
+ .name = symlnk_outside_share,
+ .target = symlnk_dst_outside_share,
+ .expected = symlnk_outside_share,
+ .attr_win = FILE_ATTRIBUTE_INVALID,
+ .attr_lin = FILE_ATTRIBUTE_NORMAL,
+ }, {
+ .name = NULL,
+ }
+ };
+ struct posix_test_state _state = {
+ .entries = &entries[0],
+ };
+ struct posix_test_state *state = &_state;
+ int i;
+ bool correct = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-LS-SINGLE test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ if (!torture_init_connection(&cli_win)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ status = smbXcli_negprot(cli_win->conn,
+ cli_win->timeout,
+ lp_client_min_protocol(),
+ lp_client_max_protocol(),
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli_win, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_sesssetup returned %s\n", nt_errstr(status));
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ status = cli_tree_connect(cli_win, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ TALLOC_FREE(frame);
+ return false;
+ }
+ torture_conn_set_sockopt(cli_unix);
+ torture_conn_set_sockopt(cli_win);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ status = cli_posix_open(cli_unix,
+ file,
+ O_RDWR|O_CREAT,
+ 0666,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ file,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ for (i = 0; entries[i].name != NULL; i++) {
+ if (entries[i].target == NULL) {
+ continue;
+ }
+ status = cli_posix_symlink(cli_unix,
+ entries[i].target,
+ entries[i].name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX symlink of %s failed (%s)\n",
+ symlnk_dangling, nt_errstr(status));
+ goto out;
+ }
+ }
+
+ printf("Doing Windows ls single\n");
+ state->flavour = WINDOWS;
+
+ cli_list(cli_win, file, 0, posix_ls_fn, state);
+ cli_list(cli_win, symlnk_dangling, 0, posix_ls_fn, state);
+ cli_list(cli_win, symlnk_outside_share, 0, posix_ls_fn, state);
+ cli_list(cli_win, symlnk_in_share, 0, posix_ls_fn, state);
+
+ if (!posix_test_entry_check(state, file, true, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state, symlnk_dangling, false, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state, symlnk_outside_share, false, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state, symlnk_in_share, true, 0)) {
+ goto out;
+ }
+
+ posix_test_entries_reset(state);
+
+ printf("Doing POSIX ls single\n");
+ state->flavour = POSIX;
+
+ cli_list(cli_unix, file, 0, posix_ls_fn, state);
+ cli_list(cli_unix, symlnk_dangling, 0, posix_ls_fn, state);
+ cli_list(cli_unix, symlnk_outside_share, 0, posix_ls_fn, state);
+ cli_list(cli_unix, symlnk_in_share, 0, posix_ls_fn, state);
+
+ if (!posix_test_entry_check(state, file, true, 0)) {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_dangling,
+ true,
+ strlen(symlnk_dst_dangling)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_outside_share,
+ true,
+ strlen(symlnk_dst_outside_share)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_in_share,
+ true,
+ strlen(symlnk_dst_in_share))) {
+ goto out;
+ }
+
+ printf("POSIX-LS-SINGLE test passed\n");
+ correct = true;
+
+out:
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+ if (!torture_close_connection(cli_win)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Test POSIX readlink of symlinks
+ */
+bool run_posix_readlink_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ const char *file = "file";
+ const char *symlnk_dangling = "dangling";
+ const char *symlnk_dst_dangling = "xxxxxxx";
+ const char *symlnk_in_share = "symlnk_in_share";
+ const char *symlnk_dst_in_share = file;
+ const char *symlnk_outside_share = "symlnk_outside_share";
+ const char *symlnk_dst_outside_share = "/etc/passwd";
+ struct posix_test_entry entries[] = {
+ {
+ .name = symlnk_dangling,
+ .target = symlnk_dst_dangling,
+ .expected = symlnk_dangling,
+ }, {
+ .name = symlnk_in_share,
+ .target = symlnk_dst_in_share,
+ .expected = symlnk_in_share,
+ }, {
+ .name = symlnk_outside_share,
+ .target = symlnk_dst_outside_share,
+ .expected = symlnk_outside_share,
+ }, {
+ .name = NULL,
+ }
+ };
+ struct posix_test_state _state = {
+ .entries = &entries[0],
+ };
+ struct posix_test_state *state = &_state;
+ int i;
+ bool correct = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-READLINK test\n");
+ state->flavour = POSIX;
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ status = cli_posix_open(cli_unix,
+ file,
+ O_RDWR|O_CREAT,
+ 0666,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ file,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ for (i = 0; entries[i].name != NULL; i++) {
+ status = cli_posix_symlink(cli_unix,
+ entries[i].target,
+ entries[i].name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX symlink of %s failed (%s)\n",
+ symlnk_dangling, nt_errstr(status));
+ goto out;
+ }
+ }
+
+ for (i = 0; entries[i].name != NULL; i++) {
+ char *target = NULL;
+
+ status = cli_readlink(
+ cli_unix,
+ entries[i].name,
+ talloc_tos(),
+ &target,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX readlink on %s failed (%s)\n",
+ entries[i].name, nt_errstr(status));
+ goto out;
+ }
+ if (strequal(target, entries[i].target)) {
+ entries[i].ok = true;
+ entries[i].returned_size = strlen(target);
+ }
+ }
+
+ if (!posix_test_entry_check(state,
+ symlnk_dangling,
+ true,
+ strlen(symlnk_dst_dangling)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_outside_share,
+ true,
+ strlen(symlnk_dst_outside_share)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_in_share,
+ true,
+ strlen(symlnk_dst_in_share))) {
+ goto out;
+ }
+
+ printf("POSIX-READLINK test passed\n");
+ correct = true;
+
+out:
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Test POSIX stat of symlinks
+ */
+bool run_posix_stat_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ const char *file = "file";
+ const char *symlnk_dangling = "dangling";
+ const char *symlnk_dst_dangling = "xxxxxxx";
+ const char *symlnk_in_share = "symlnk_in_share";
+ const char *symlnk_dst_in_share = file;
+ const char *symlnk_outside_share = "symlnk_outside_share";
+ const char *symlnk_dst_outside_share = "/etc/passwd";
+ struct posix_test_entry entries[] = {
+ {
+ .name = symlnk_dangling,
+ .target = symlnk_dst_dangling,
+ .expected = symlnk_dangling,
+ }, {
+ .name = symlnk_in_share,
+ .target = symlnk_dst_in_share,
+ .expected = symlnk_in_share,
+ }, {
+ .name = symlnk_outside_share,
+ .target = symlnk_dst_outside_share,
+ .expected = symlnk_outside_share,
+ }, {
+ .name = NULL,
+ }
+ };
+ struct posix_test_state _state = {
+ .entries = &entries[0],
+ };
+ struct posix_test_state *state = &_state;
+ int i;
+ bool correct = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-STAT test\n");
+ state->flavour = POSIX;
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ status = cli_posix_open(cli_unix,
+ file,
+ O_RDWR|O_CREAT,
+ 0666,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ file,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ for (i = 0; entries[i].name != NULL; i++) {
+ status = cli_posix_symlink(cli_unix,
+ entries[i].target,
+ entries[i].name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX symlink of %s failed (%s)\n",
+ symlnk_dangling, nt_errstr(status));
+ goto out;
+ }
+ }
+
+ for (i = 0; entries[i].name != NULL; i++) {
+ SMB_STRUCT_STAT sbuf;
+
+ status = cli_posix_stat(cli_unix,
+ entries[i].name,
+ &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX stat on %s failed (%s)\n",
+ entries[i].name, nt_errstr(status));
+ continue;
+ }
+ entries[i].ok = true;
+ entries[i].returned_size = sbuf.st_ex_size;
+ }
+
+ if (!posix_test_entry_check(state,
+ symlnk_dangling,
+ true,
+ strlen(symlnk_dst_dangling)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_outside_share,
+ true,
+ strlen(symlnk_dst_outside_share)))
+ {
+ goto out;
+ }
+ if (!posix_test_entry_check(state,
+ symlnk_in_share,
+ true,
+ strlen(symlnk_dst_in_share))) {
+ goto out;
+ }
+
+ printf("POSIX-STAT test passed\n");
+ correct = true;
+
+out:
+ cli_posix_unlink(cli_unix, file);
+ cli_posix_unlink(cli_unix, symlnk_dangling);
+ cli_posix_unlink(cli_unix, symlnk_in_share);
+ cli_posix_unlink(cli_unix, symlnk_outside_share);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Test Creating files and directories directly
+ under a symlink.
+ */
+bool run_posix_symlink_parent_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ const char *parent_dir = "target_dir";
+ const char *parent_symlink = "symlink_to_target_dir";
+ const char *fname_real = "target_dir/file";
+ const char *dname_real = "target_dir/dir";
+ const char *fname_link = "symlink_to_target_dir/file";
+ const char *dname_link = "symlink_to_target_dir/dir";
+ const char *sname_link = "symlink_to_target_dir/symlink";
+ const char *hname_link = "symlink_to_target_dir/hardlink";
+ bool correct = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-SYMLINK-PARENT test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Start with a clean slate. */
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_rmdir(cli_unix, dname_real);
+ cli_posix_unlink(cli_unix, fname_link);
+ cli_posix_rmdir(cli_unix, dname_link);
+ cli_posix_unlink(cli_unix, sname_link);
+ cli_posix_unlink(cli_unix, hname_link);
+ cli_posix_unlink(cli_unix, parent_symlink);
+ cli_posix_rmdir(cli_unix, parent_dir);
+
+ /* Create parent_dir. */
+ status = cli_posix_mkdir(cli_unix, parent_dir, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed error %s\n",
+ parent_dir,
+ nt_errstr(status));
+ goto out;
+ }
+ /* Create symlink to parent_dir. */
+ status = cli_posix_symlink(cli_unix,
+ parent_dir,
+ parent_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ parent_symlink,
+ parent_dir,
+ nt_errstr(status));
+ goto out;
+ }
+ /* Try and create a directory under the symlink. */
+ status = cli_posix_mkdir(cli_unix, dname_link, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed error %s\n",
+ dname_link,
+ nt_errstr(status));
+ goto out;
+ }
+ /* Try and create a file under the symlink. */
+ status = cli_posix_open(cli_unix,
+ fname_link,
+ O_RDWR|O_CREAT,
+ 0666,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ fname_link,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Try and create a symlink to the file under the symlink. */
+ status = cli_posix_symlink(cli_unix,
+ fname_link,
+ sname_link);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ sname_link,
+ fname_link,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Try and create a hardlink to the file under the symlink. */
+ status = cli_posix_hardlink(cli_unix,
+ fname_link,
+ hname_link);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_hardlink of %s -> %s failed error %s\n",
+ hname_link,
+ fname_link,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure we can delete the symlink via the parent symlink */
+ status = cli_posix_unlink(cli_unix, sname_link);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_unlink of %s failed error %s\n",
+ sname_link,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure we can delete the hardlink via the parent symlink */
+ status = cli_posix_unlink(cli_unix, hname_link);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_unlink of %s failed error %s\n",
+ hname_link,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure we can delete the directory via the parent symlink */
+ status = cli_posix_rmdir(cli_unix, dname_link);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_rmdir of %s failed error %s\n",
+ dname_link,
+ nt_errstr(status));
+ goto out;
+ }
+ /* Ensure we can delete the file via the parent symlink */
+ status = cli_posix_unlink(cli_unix, fname_link);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_unlink of %s failed error %s\n",
+ fname_link,
+ nt_errstr(status));
+ goto out;
+ }
+
+ printf("POSIX-SYMLINK-PARENT test passed\n");
+ correct = true;
+
+out:
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli_unix, fnum);
+ }
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_rmdir(cli_unix, dname_real);
+ cli_posix_unlink(cli_unix, fname_link);
+ cli_posix_rmdir(cli_unix, dname_link);
+ cli_posix_unlink(cli_unix, sname_link);
+ cli_posix_unlink(cli_unix, hname_link);
+ cli_posix_unlink(cli_unix, parent_symlink);
+ cli_posix_rmdir(cli_unix, parent_dir);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Ensure we get an error when doing chmod on a symlink,
+ whether it is pointing to a real object or dangling.
+ */
+bool run_posix_symlink_chmod_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+ const char *fname_real = "file_real";
+ const char *fname_real_symlink = "file_real_symlink";
+ const char *nonexist = "nonexist";
+ const char *nonexist_symlink = "dangling_symlink";
+ bool correct = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-SYMLINK-CHMOD test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Start with a clean slate. */
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+
+ /* Create a real file. */
+ status = cli_posix_open(cli_unix,
+ fname_real,
+ O_RDWR|O_CREAT,
+ 0644,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Create symlink to real target. */
+ status = cli_posix_symlink(cli_unix,
+ fname_real,
+ fname_real_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ fname_real_symlink,
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* We should not be able to chmod symlinks that point to something. */
+ status = cli_posix_chmod(cli_unix, fname_real_symlink, 0777);
+
+ /* This should fail with something other than server crashed. */
+ if (NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_chmod of %s succeeded (should have failed)\n",
+ fname_real_symlink);
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_DISCONNECTED)) {
+ /* Oops. Server crashed. */
+ printf("cli_posix_chmod of %s failed error %s\n",
+ fname_real_symlink,
+ nt_errstr(status));
+ goto out;
+ }
+ /* Any other failure is ok. */
+
+ /* Now create symlink to non-existing target. */
+ status = cli_posix_symlink(cli_unix,
+ nonexist,
+ nonexist_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ nonexist_symlink,
+ nonexist,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* We should not be able to chmod symlinks that point to nothing. */
+ status = cli_posix_chmod(cli_unix, nonexist_symlink, 0777);
+
+ /* This should fail with something other than server crashed. */
+ if (NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_chmod of %s succeeded (should have failed)\n",
+ nonexist_symlink);
+ goto out;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_DISCONNECTED)) {
+ /* Oops. Server crashed. */
+ printf("cli_posix_chmod of %s failed error %s\n",
+ nonexist_symlink,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Any other failure is ok. */
+ printf("POSIX-SYMLINK-CHMOD test passed (expected failure was %s)\n",
+ nt_errstr(status));
+ correct = true;
+
+out:
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli_unix, fnum);
+ }
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Ensure we get an ACL containing OI|IO ACE entries
+ after we add a default POSIX ACL to a directory.
+ This will only ever be an SMB1 test as it depends
+ on POSIX ACL semantics.
+ */
+bool run_posix_dir_default_acl_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+ const char *dname = "dir_with_default_acl";
+ bool correct = false;
+ SMB_STRUCT_STAT sbuf;
+ size_t acl_size = 0;
+ char *aclbuf = NULL;
+ size_t num_file_acls = 0;
+ size_t num_dir_acls = 0;
+ size_t expected_buflen;
+ uint8_t def_acl[SMB_POSIX_ACL_HEADER_SIZE +
+ 5*SMB_POSIX_ACL_ENTRY_SIZE] = {0};
+ uint8_t *p = NULL;
+ uint32_t i = 0;
+ struct security_descriptor *sd = NULL;
+ bool got_inherit = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-DIR-DEFAULT-ACL test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Start with a clean slate. */
+ cli_posix_unlink(cli_unix, dname);
+ cli_posix_rmdir(cli_unix, dname);
+
+ status = cli_posix_mkdir(cli_unix, dname, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed error %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Do a posix stat to get the owner. */
+ status = cli_posix_stat(cli_unix, dname, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_stat of %s failed %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Get the ACL on the directory. */
+ status = cli_posix_getacl(cli_unix, dname, frame, &acl_size, &aclbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_getacl on %s failed %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (acl_size < 6 || SVAL(aclbuf,0) != SMB_POSIX_ACL_VERSION) {
+ printf("%s, unknown POSIX acl version %u.\n",
+ dname,
+ (unsigned int)CVAL(aclbuf,0) );
+ goto out;
+ }
+
+ num_file_acls = SVAL(aclbuf,2);
+ num_dir_acls = SVAL(aclbuf,4);
+
+ /*
+ * No overflow check, num_*_acls comes from a 16-bit value,
+ * and we expect expected_buflen (size_t) to be of at least 32
+ * bit.
+ */
+ expected_buflen = SMB_POSIX_ACL_HEADER_SIZE +
+ SMB_POSIX_ACL_ENTRY_SIZE*(num_file_acls+num_dir_acls);
+
+ if (acl_size != expected_buflen) {
+ printf("%s, incorrect POSIX acl buffer size "
+ "(should be %zu, was %zu).\n",
+ dname,
+ expected_buflen,
+ acl_size);
+ goto out;
+ }
+
+ if (num_dir_acls != 0) {
+ printf("%s, POSIX default acl already exists"
+ "(should be 0, was %zu).\n",
+ dname,
+ num_dir_acls);
+ goto out;
+ }
+
+ /*
+ * Get the Windows ACL on the directory.
+ * Make sure there are no inheritable entries.
+ */
+ status = cli_ntcreate(cli_unix,
+ dname,
+ 0,
+ SEC_STD_READ_CONTROL,
+ 0,
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ FILE_DIRECTORY_FILE,
+ 0x0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to open directory %s: %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_query_security_descriptor(cli_unix,
+ fnum,
+ SECINFO_DACL,
+ frame,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to get security descriptor on directory %s: %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+ struct security_ace *ace = &sd->dacl->aces[i];
+ if (ace->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ printf("security descriptor on directory %s already "
+ "contains inheritance flags\n",
+ dname);
+ sec_desc_print(NULL, stdout, sd, true);
+ goto out;
+ }
+ }
+
+ TALLOC_FREE(sd);
+
+ /* Construct a new default ACL. */
+ SSVAL(def_acl,0,SMB_POSIX_ACL_VERSION);
+ SSVAL(def_acl,2,SMB_POSIX_IGNORE_ACE_ENTRIES);
+ SSVAL(def_acl,4,5); /* num_dir_acls. */
+
+ p = def_acl + SMB_POSIX_ACL_HEADER_SIZE;
+
+ /* USER_OBJ. */
+ SCVAL(p,0,SMB_POSIX_ACL_USER_OBJ); /* tagtype. */
+ SCVAL(p,1,SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE);
+ p += SMB_POSIX_ACL_ENTRY_SIZE;
+
+ /* GROUP_OBJ. */
+ SCVAL(p,0,SMB_POSIX_ACL_GROUP_OBJ); /* tagtype. */
+ SCVAL(p,1,SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE);
+ p += SMB_POSIX_ACL_ENTRY_SIZE;
+
+ /* OTHER. */
+ SCVAL(p,0,SMB_POSIX_ACL_OTHER); /* tagtype. */
+ SCVAL(p,1,SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE);
+ p += SMB_POSIX_ACL_ENTRY_SIZE;
+
+ /* Explicit user. */
+ SCVAL(p,0,SMB_POSIX_ACL_USER); /* tagtype. */
+ SCVAL(p,1,SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE);
+ SIVAL(p,2,sbuf.st_ex_uid);
+ p += SMB_POSIX_ACL_ENTRY_SIZE;
+
+ /* MASK. */
+ SCVAL(p,0,SMB_POSIX_ACL_MASK); /* tagtype. */
+ SCVAL(p,1,SMB_POSIX_ACL_READ|SMB_POSIX_ACL_WRITE|SMB_POSIX_ACL_EXECUTE);
+ p += SMB_POSIX_ACL_ENTRY_SIZE;
+
+ /* Set the POSIX default ACL. */
+ status = cli_posix_setacl(cli_unix, dname, def_acl, sizeof(def_acl));
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_setacl on %s failed %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * Get the Windows ACL on the directory again.
+ * Now there should be inheritable entries.
+ */
+
+ status = cli_query_security_descriptor(cli_unix,
+ fnum,
+ SECINFO_DACL,
+ frame,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed (2) to get security descriptor "
+ "on directory %s: %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ for (i = 0; sd->dacl && i < sd->dacl->num_aces; i++) {
+ struct security_ace *ace = &sd->dacl->aces[i];
+ if (ace->flags & (SEC_ACE_FLAG_OBJECT_INHERIT|
+ SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ got_inherit = true;
+ break;
+ }
+ }
+
+ if (!got_inherit) {
+ printf("security descriptor on directory %s does not "
+ "contain inheritance flags\n",
+ dname);
+ sec_desc_print(NULL, stdout, sd, true);
+ goto out;
+ }
+
+ cli_close(cli_unix, fnum);
+ fnum = (uint16_t)-1;
+ printf("POSIX-DIR-DEFAULT-ACL test passed\n");
+ correct = true;
+
+out:
+
+ TALLOC_FREE(sd);
+
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli_unix, fnum);
+ }
+ cli_posix_unlink(cli_unix, dname);
+ cli_posix_rmdir(cli_unix, dname);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Ensure we can rename a symlink whether it is
+ pointing to a real object or dangling.
+ */
+bool run_posix_symlink_rename_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+ const char *fname_real = "file_real";
+ const char *fname_real_symlink = "file_real_symlink";
+ const char *fname_real_symlink_newname = "rename_file_real_symlink";
+ const char *nonexist = "nonexist";
+ const char *nonexist_symlink = "dangling_symlink";
+ const char *nonexist_symlink_newname = "dangling_symlink_rename";
+ bool correct = false;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-SYMLINK-RENAME test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Start with a clean slate. */
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, fname_real_symlink_newname);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+ cli_posix_unlink(cli_unix, nonexist_symlink_newname);
+
+ /* Create a real file. */
+ status = cli_posix_open(cli_unix,
+ fname_real,
+ O_RDWR|O_CREAT,
+ 0644,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Create symlink to real target. */
+ status = cli_posix_symlink(cli_unix,
+ fname_real,
+ fname_real_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ fname_real_symlink,
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure we can rename the symlink to the real file. */
+ status = cli_rename(cli_unix,
+ fname_real_symlink,
+ fname_real_symlink_newname,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_rename of %s -> %s failed %s\n",
+ fname_real_symlink,
+ fname_real_symlink_newname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now create symlink to non-existing target. */
+ status = cli_posix_symlink(cli_unix,
+ nonexist,
+ nonexist_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ nonexist_symlink,
+ nonexist,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure we can rename the dangling symlink. */
+ status = cli_rename(cli_unix,
+ nonexist_symlink,
+ nonexist_symlink_newname,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_rename of %s -> %s failed %s\n",
+ nonexist_symlink,
+ nonexist_symlink_newname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ printf("POSIX-SYMLINK-RENAME test passed\n");
+ correct = true;
+
+out:
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli_unix, fnum);
+ }
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, fname_real_symlink_newname);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+ cli_posix_unlink(cli_unix, nonexist_symlink_newname);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/* List of info levels to try with a POSIX symlink path. */
+
+static struct {
+ uint32_t level;
+ const char *name;
+} posix_smb1_qpath_array[] = {
+ { SMB_INFO_STANDARD, "SMB_INFO_STANDARD"},
+ { SMB_INFO_QUERY_EA_SIZE, "SMB_INFO_QUERY_EA_SIZE"},
+ { SMB_INFO_IS_NAME_VALID, "SMB_INFO_IS_NAME_VALID"},
+ { SMB_INFO_QUERY_EAS_FROM_LIST, "SMB_INFO_QUERY_EAS_FROM_LIST"},
+ { SMB_INFO_QUERY_ALL_EAS, "SMB_INFO_QUERY_ALL_EAS"},
+ { SMB_FILE_BASIC_INFORMATION, "SMB_FILE_BASIC_INFORMATION"},
+ { SMB_FILE_STANDARD_INFORMATION, "SMB_FILE_STANDARD_INFORMATION"},
+ { SMB_FILE_EA_INFORMATION, "SMB_FILE_EA_INFORMATION"},
+ { SMB_FILE_ALTERNATE_NAME_INFORMATION,"SMB_FILE_ALTERNATE_NAME_INFORMATION"},
+ { SMB_QUERY_FILE_NAME_INFO, "SMB_QUERY_FILE_NAME_INFO"},
+ { SMB_FILE_NORMALIZED_NAME_INFORMATION,"SMB_FILE_NORMALIZED_NAME_INFORMATION"},
+ { SMB_FILE_ALLOCATION_INFORMATION, "SMB_FILE_ALLOCATION_INFORMATION"},
+ { SMB_FILE_END_OF_FILE_INFORMATION, "SMB_FILE_END_OF_FILE_INFORMATION"},
+ { SMB_FILE_ALL_INFORMATION, "SMB_FILE_ALL_INFORMATION"},
+ { SMB_FILE_INTERNAL_INFORMATION, "SMB_FILE_INTERNAL_INFORMATION"},
+ { SMB_FILE_ACCESS_INFORMATION, "SMB_FILE_ACCESS_INFORMATION"},
+ { SMB_FILE_NAME_INFORMATION, "SMB_FILE_NAME_INFORMATION"},
+ { SMB_FILE_DISPOSITION_INFORMATION, "SMB_FILE_DISPOSITION_INFORMATION"},
+ { SMB_FILE_POSITION_INFORMATION, "SMB_FILE_POSITION_INFORMATION"},
+ { SMB_FILE_MODE_INFORMATION, "SMB_FILE_MODE_INFORMATION"},
+ { SMB_FILE_ALIGNMENT_INFORMATION, "SMB_FILE_ALIGNMENT_INFORMATION"},
+ { SMB_FILE_STREAM_INFORMATION, "SMB_FILE_STREAM_INFORMATION"},
+ { SMB_FILE_COMPRESSION_INFORMATION, "SMB_FILE_COMPRESSION_INFORMATION"},
+ { SMB_FILE_NETWORK_OPEN_INFORMATION, "SMB_FILE_NETWORK_OPEN_INFORMATION"},
+ { SMB_FILE_ATTRIBUTE_TAG_INFORMATION, "SMB_FILE_ATTRIBUTE_TAG_INFORMATION"},
+ { SMB_QUERY_FILE_UNIX_BASIC, "SMB_QUERY_FILE_UNIX_BASIC"},
+ { SMB_QUERY_FILE_UNIX_INFO2, "SMB_QUERY_FILE_UNIX_INFO2"},
+ { SMB_QUERY_FILE_UNIX_LINK, "SMB_QUERY_FILE_UNIX_LINK"},
+ { SMB_QUERY_POSIX_ACL, "SMB_QUERY_POSIX_ACL"},
+ { SMB_QUERY_POSIX_LOCK, "SMB_QUERY_POSIX_LOCK"},
+};
+
+static NTSTATUS do_qpath(TALLOC_CTX *ctx,
+ struct cli_state *cli_unix,
+ const char *fname,
+ size_t i)
+{
+ NTSTATUS status;
+
+ if (posix_smb1_qpath_array[i].level ==
+ SMB_INFO_QUERY_EAS_FROM_LIST) {
+ uint16_t setup;
+ uint8_t *param;
+ uint8_t data[8];
+ uint8_t *rparam = NULL;
+ uint8_t *rdata = NULL;
+ uint32_t rbytes = 0;
+
+ /* Set up an EA list with 'a' as the single name. */
+ SIVAL(data,0, 8);
+ SCVAL(data,4, 2); /* namelen. */
+ SCVAL(data,5, 'a');
+ SCVAL(data,6, '\0'); /* name. */
+ SCVAL(data,7, '\0'); /* padding. */
+
+ SSVAL(&setup, 0, TRANSACT2_QPATHINFO);
+
+ param = talloc_zero_array(ctx, uint8_t, 6);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ SSVAL(param, 0, SMB_INFO_QUERY_EAS_FROM_LIST);
+ param = trans2_bytes_push_str(param,
+ smbXcli_conn_use_unicode(cli_unix->conn),
+ fname,
+ strlen(fname)+1,
+ NULL);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_trans(ctx,
+ cli_unix,
+ SMBtrans2,
+ NULL,
+ -1,
+ 0,
+ 0,
+ &setup, 1, 0,
+ param, talloc_get_size(param), talloc_get_size(param),
+ data, 8, 0,
+ NULL,
+ NULL, 0, NULL,
+ &rparam, 0, &rbytes,
+ &rdata, 0, &rbytes);
+ TALLOC_FREE(rparam);
+ TALLOC_FREE(rdata);
+ } else {
+ uint8_t *rdata = NULL;
+ uint32_t num_rdata = 0;
+
+ status = cli_qpathinfo(ctx,
+ cli_unix,
+ fname,
+ posix_smb1_qpath_array[i].level,
+ 0, /* min_rdata */
+ 65534, /* max_rdata */
+ &rdata,
+ &num_rdata);
+ TALLOC_FREE(rdata);
+ }
+ /*
+ * We don't care what came back, so long as the
+ * server didn't crash.
+ */
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_CONNECTION_DISCONNECTED)) {
+ printf("cli_qpathinfo of %s failed error "
+ "NT_STATUS_CONNECTION_DISCONNECTED\n",
+ fname);
+ return status;
+ }
+
+ printf("cli_qpathinfo info %x (%s) of %s got %s "
+ "(this is not an error)\n",
+ (unsigned int)posix_smb1_qpath_array[i].level,
+ posix_smb1_qpath_array[i].name,
+ fname,
+ nt_errstr(status));
+
+ return NT_STATUS_OK;
+}
+
+/*
+ Ensure we can call SMB1 getpathinfo in a symlink,
+ pointing to a real object or dangling. We mostly
+ expect errors, but the server must not crash.
+ */
+bool run_posix_symlink_getpathinfo_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+ const char *fname_real = "file_getpath_real";
+ const char *fname_real_symlink = "file_real_getpath_symlink";
+ const char *nonexist = "nonexist_getpath";
+ const char *nonexist_symlink = "dangling_getpath_symlink";
+ bool correct = false;
+ size_t i;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-SYMLINK-GETPATHINFO test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Start with a clean slate. */
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+
+ /* Create a real file. */
+ status = cli_posix_open(cli_unix,
+ fname_real,
+ O_RDWR|O_CREAT,
+ 0644,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Create symlink to real target. */
+ status = cli_posix_symlink(cli_unix,
+ fname_real,
+ fname_real_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ fname_real_symlink,
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now create symlink to non-existing target. */
+ status = cli_posix_symlink(cli_unix,
+ nonexist,
+ nonexist_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ nonexist_symlink,
+ nonexist,
+ nt_errstr(status));
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(posix_smb1_qpath_array); i++) {
+ status = do_qpath(frame,
+ cli_unix,
+ fname_real_symlink,
+ i);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = do_qpath(frame,
+ cli_unix,
+ nonexist_symlink,
+ i);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ printf("POSIX-SYMLINK-GETPATHINFO test passed\n");
+ correct = true;
+
+out:
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli_unix, fnum);
+ }
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/* List of info levels to try with a POSIX symlink path. */
+
+static struct {
+ uint32_t level;
+ const char *name;
+ uint32_t data_len;
+} posix_smb1_setpath_array[] = {
+ { SMB_SET_FILE_UNIX_BASIC, "SMB_SET_FILE_UNIX_BASIC", 100},
+ { SMB_SET_FILE_UNIX_INFO2, "SMB_SET_FILE_UNIX_INFO2", 116},
+ { SMB_SET_FILE_UNIX_LINK, "SMB_SET_FILE_UNIX_LINK", 8},
+ { SMB_SET_FILE_UNIX_HLINK, "SMB_SET_FILE_UNIX_HLINK", 8},
+ { SMB_SET_POSIX_ACL, "SMB_SET_POSIX_ACL", 6},
+ { SMB_SET_POSIX_LOCK, "SMB_SET_POSIX_LOCK", 24},
+ { SMB_INFO_STANDARD, "SMB_INFO_STANDARD", 12},
+ { SMB_INFO_SET_EA, "SMB_INFO_SET_EA", 10},
+ { SMB_FILE_BASIC_INFORMATION, "SMB_FILE_BASIC_INFORMATION", 36},
+ { SMB_SET_FILE_ALLOCATION_INFO, "SMB_SET_FILE_ALLOCATION_INFO", 8},
+ { SMB_SET_FILE_END_OF_FILE_INFO,"SMB_SET_FILE_END_OF_FILE_INFO",8},
+ { SMB_SET_FILE_DISPOSITION_INFO,"SMB_SET_FILE_DISPOSITION_INFO",1},
+ { SMB_FILE_POSITION_INFORMATION,"SMB_FILE_POSITION_INFORMATION",8},
+ { SMB_FILE_FULL_EA_INFORMATION, "SMB_FILE_FULL_EA_INFORMATION",10},
+ { SMB_FILE_MODE_INFORMATION, "SMB_FILE_MODE_INFORMATION", 4},
+ { SMB_FILE_SHORT_NAME_INFORMATION,"SMB_FILE_SHORT_NAME_INFORMATION",12},
+ { SMB_FILE_RENAME_INFORMATION,"SMB_FILE_RENAME_INFORMATION", 20},
+ { SMB_FILE_LINK_INFORMATION, "SMB_FILE_LINK_INFORMATION", 20},
+};
+
+static NTSTATUS do_setpath(TALLOC_CTX *ctx,
+ struct cli_state *cli_unix,
+ const char *fname,
+ size_t i)
+{
+ NTSTATUS status;
+ uint8_t *data = NULL;
+
+ data = talloc_zero_array(ctx,
+ uint8_t,
+ posix_smb1_setpath_array[i].data_len);
+ if (data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_setpathinfo(cli_unix,
+ posix_smb1_setpath_array[i].level,
+ fname,
+ data,
+ posix_smb1_setpath_array[i].data_len);
+ TALLOC_FREE(data);
+
+ /*
+ * We don't care what came back, so long as the
+ * server didn't crash.
+ */
+ if (NT_STATUS_EQUAL(status,
+ NT_STATUS_CONNECTION_DISCONNECTED)) {
+ printf("cli_setpathinfo info %x (%s) of %s failed"
+ "error NT_STATUS_CONNECTION_DISCONNECTED\n",
+ (unsigned int)posix_smb1_setpath_array[i].level,
+ posix_smb1_setpath_array[i].name,
+ fname);
+ return status;
+ }
+
+ printf("cli_setpathinfo info %x (%s) of %s got %s "
+ "(this is not an error)\n",
+ (unsigned int)posix_smb1_setpath_array[i].level,
+ posix_smb1_setpath_array[i].name,
+ fname,
+ nt_errstr(status));
+
+ return NT_STATUS_OK;
+}
+
+/*
+ Ensure we can call SMB1 setpathinfo in a symlink,
+ pointing to a real object or dangling. We mostly
+ expect errors, but the server must not crash.
+ */
+bool run_posix_symlink_setpathinfo_test(int dummy)
+{
+ TALLOC_CTX *frame = NULL;
+ struct cli_state *cli_unix = NULL;
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+ const char *fname_real = "file_setpath_real";
+ const char *fname_real_symlink = "file_real_setpath_symlink";
+ const char *nonexist = "nonexist_setpath";
+ const char *nonexist_symlink = "dangling_setpath_symlink";
+ bool correct = false;
+ size_t i;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX-SYMLINK-SETPATHINFO test\n");
+
+ if (!torture_open_connection(&cli_unix, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ torture_conn_set_sockopt(cli_unix);
+
+ status = torture_setup_unix_extensions(cli_unix);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Start with a clean slate. */
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+
+ /* Create a real file. */
+ status = cli_posix_open(cli_unix,
+ fname_real,
+ O_RDWR|O_CREAT,
+ 0644,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_unix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Create symlink to real target. */
+ status = cli_posix_symlink(cli_unix,
+ fname_real,
+ fname_real_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ fname_real_symlink,
+ fname_real,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now create symlink to non-existing target. */
+ status = cli_posix_symlink(cli_unix,
+ nonexist,
+ nonexist_symlink);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed error %s\n",
+ nonexist_symlink,
+ nonexist,
+ nt_errstr(status));
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(posix_smb1_setpath_array); i++) {
+ status = do_setpath(frame,
+ cli_unix,
+ fname_real_symlink,
+ i);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = do_setpath(frame,
+ cli_unix,
+ nonexist_symlink,
+ i);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ printf("POSIX-SYMLINK-SETPATHINFO test passed\n");
+ correct = true;
+
+out:
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli_unix, fnum);
+ }
+ cli_posix_unlink(cli_unix, fname_real);
+ cli_posix_unlink(cli_unix, fname_real_symlink);
+ cli_posix_unlink(cli_unix, nonexist);
+ cli_posix_unlink(cli_unix, nonexist_symlink);
+
+ if (!torture_close_connection(cli_unix)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
diff --git a/source3/torture/test_posix_append.c b/source3/torture/test_posix_append.c
new file mode 100644
index 0000000..3abd448
--- /dev/null
+++ b/source3/torture/test_posix_append.c
@@ -0,0 +1,100 @@
+/*
+ Unix SMB/CIFS implementation.
+ reproducer for bug 6898
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+
+/*
+ * Make sure that GENERIC_WRITE does not trigger append. See
+ * https://bugzilla.samba.org/show_bug.cgi?id=6898
+ */
+
+bool run_posix_append(int dummy)
+{
+ struct cli_state *cli;
+ const char *fname = "append";
+ NTSTATUS status;
+ uint16_t fnum;
+ off_t size;
+ uint8_t c = '\0';
+ bool ret = false;
+
+ printf("Starting POSIX_APPEND\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ status = torture_setup_unix_extensions(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("torture_setup_unix_extensions failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_ntcreate(
+ cli, fname, 0,
+ GENERIC_WRITE_ACCESS|GENERIC_READ_ACCESS|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL|FILE_FLAG_POSIX_SEMANTICS,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF,
+ FILE_NON_DIRECTORY_FILE|FILE_DELETE_ON_CLOSE,
+ 0, &fnum, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * Write two bytes at offset 0. With bug 6898 we would end up
+ * with a file of 2 byte length.
+ */
+
+ status = cli_writeall(cli, fnum, 0, &c, 0, sizeof(c), NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_write failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+ status = cli_writeall(cli, fnum, 0, &c, 0, sizeof(c), NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_write failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_qfileinfo_basic(
+ cli, fnum, NULL, &size, NULL, NULL, NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_qfileinfo_basic failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ if (size != sizeof(c)) {
+ printf("BUG: Writing with O_APPEND!!\n");
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ torture_close_connection(cli);
+ return ret;
+}
diff --git a/source3/torture/test_pthreadpool_tevent.c b/source3/torture/test_pthreadpool_tevent.c
new file mode 100644
index 0000000..c90a394
--- /dev/null
+++ b/source3/torture/test_pthreadpool_tevent.c
@@ -0,0 +1,82 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Test pthreadpool_tevent
+ * Copyright (C) Volker Lendecke 2016
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/select.h"
+#include "proto.h"
+#include "lib/pthreadpool/pthreadpool_tevent.h"
+
+static void job_fn(void *private_data);
+
+bool run_pthreadpool_tevent(int dummy)
+{
+ struct tevent_context *ev;
+ struct pthreadpool_tevent *pool;
+ struct tevent_req *req;
+ int ret, val;
+ bool ok;
+
+ ev = tevent_context_init_byname(NULL, "poll");
+ if (ev == NULL) {
+ fprintf(stderr, "tevent_context_init failed\n");
+ return false;
+ }
+
+ ret = pthreadpool_tevent_init(ev, 100, &pool);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_tevent_init failed: %s\n",
+ strerror(ret));
+ return false;
+ }
+
+ val = -1;
+
+ req = pthreadpool_tevent_job_send(ev, ev, pool, job_fn, &val);
+ if (req == NULL) {
+ fprintf(stderr, "pthreadpool_tevent_job_send failed\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(req, ev);
+ if (!ok) {
+ fprintf(stderr, "tevent_req_poll failed\n");
+ return false;
+ }
+
+ ret = pthreadpool_tevent_job_recv(req);
+ if (ret != 0) {
+ fprintf(stderr, "pthreadpool_tevent_job failed: %s\n",
+ strerror(ret));
+ return false;
+ }
+
+ printf("%d\n", val);
+
+ TALLOC_FREE(pool);
+ TALLOC_FREE(ev);
+ return true;
+}
+
+static void job_fn(void *private_data)
+{
+ int *pret = private_data;
+ *pret = 4711;
+
+ poll(NULL, 0, 100);
+}
diff --git a/source3/torture/test_readdir_timestamp.c b/source3/torture/test_readdir_timestamp.c
new file mode 100644
index 0000000..0eba415
--- /dev/null
+++ b/source3/torture/test_readdir_timestamp.c
@@ -0,0 +1,533 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Copyright (C) Volker Lendecke 2020
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/smb_strtox.h"
+
+extern int torture_nprocs;
+extern int torture_numops;
+
+struct create_ts_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ unsigned timestamp_idx;
+ uint16_t fnum;
+};
+
+static void create_ts_opened(struct tevent_req *subreq);
+static void create_ts_setinfo_done(struct tevent_req *subreq);
+static void create_ts_waited(struct tevent_req *subreq);
+static void create_ts_written(struct tevent_req *subreq);
+static void create_ts_doc_done(struct tevent_req *subreq);
+
+static struct tevent_req *create_ts_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ unsigned timestamp_idx)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct create_ts_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct create_ts_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->timestamp_idx = timestamp_idx;
+
+ subreq = cli_ntcreate_send(
+ state,
+ ev,
+ cli,
+ fname,
+ 0, /* CreatFlags */
+ SEC_FILE_WRITE_ATTRIBUTE|
+ SEC_FILE_WRITE_DATA|
+ SEC_STD_DELETE, /* DesiredAccess */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_WRITE|FILE_SHARE_READ, /* ShareAccess */
+ FILE_OPEN_IF, /* CreateDisposition */
+ FILE_NON_DIRECTORY_FILE, /* CreateOptions */
+ 0, /* Impersonation */
+ 0); /* SecurityFlags */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, create_ts_opened, req);
+ return req;
+}
+
+static void create_ts_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct create_ts_state *state = tevent_req_data(
+ req, struct create_ts_state);
+ struct smb_create_returns cr;
+ struct timespec mtime;
+ NTSTATUS status;
+
+ status = cli_ntcreate_recv(subreq, &state->fnum, &cr);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ mtime = nt_time_to_unix_timespec(cr.last_write_time);
+
+ mtime.tv_sec &= ~(0xFFFFULL);
+ mtime.tv_sec |= (state->timestamp_idx & 0xFFFF);
+
+ subreq = cli_setfileinfo_ext_send(
+ state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* create */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* access */
+ mtime,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* change */
+ UINT32_MAX); /* attr */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, create_ts_setinfo_done, req);
+}
+
+static void create_ts_setinfo_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct create_ts_state *state = tevent_req_data(
+ req, struct create_ts_state);
+ NTSTATUS status;
+
+ status = cli_setfileinfo_ext_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = tevent_wakeup_send(
+ state, state->ev, timeval_current_ofs_msec(100));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, create_ts_waited, req);
+}
+
+static void create_ts_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct create_ts_state *state = tevent_req_data(
+ req, struct create_ts_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(subreq);
+ return;
+ }
+
+ subreq = cli_write_send(
+ state,
+ state->ev,
+ state->cli,
+ state->fnum,
+ 0,
+ (uint8_t *)&state->fnum,
+ 0,
+ sizeof(state->fnum));
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, create_ts_written, req);
+}
+
+static void create_ts_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct create_ts_state *state = tevent_req_data(
+ req, struct create_ts_state);
+ size_t written;
+ NTSTATUS status;
+
+ status = cli_write_recv(subreq, &written);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(subreq, status)) {
+ return;
+ }
+
+ subreq = cli_nt_delete_on_close_send(
+ state, state->ev, state->cli, state->fnum, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, create_ts_doc_done, req);
+}
+
+static void create_ts_doc_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_nt_delete_on_close_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS create_ts_recv(struct tevent_req *req, uint16_t *fnum)
+{
+ struct create_ts_state *state = tevent_req_data(
+ req, struct create_ts_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *fnum = state->fnum;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct create_ts_files_state {
+ size_t num_files;
+ size_t num_received;
+ uint16_t *fnums;
+};
+
+static void create_ts_files_done(struct tevent_req *subreq);
+
+static struct tevent_req *create_ts_files_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *prefix,
+ size_t idx,
+ size_t num_files)
+{
+ struct tevent_req *req = NULL;
+ struct create_ts_files_state *state = NULL;
+ size_t i;
+
+ req = tevent_req_create(mem_ctx, &state, struct create_ts_files_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->num_files = num_files;
+
+ state->fnums = talloc_array(state, uint16_t, num_files);
+ if (tevent_req_nomem(state->fnums, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_files; i++) {
+ struct tevent_req *subreq = NULL;
+ const char *fname = NULL;
+
+ fname = talloc_asprintf(state, "%s%zu_%zu", prefix, idx, i);
+ if (tevent_req_nomem(fname, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = create_ts_send(state, ev, cli, fname, i);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ talloc_steal(subreq, fname);
+
+ tevent_req_set_callback(subreq, create_ts_files_done, req);
+ }
+ return req;
+}
+
+static void create_ts_files_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct create_ts_files_state *state = tevent_req_data(
+ req, struct create_ts_files_state);
+ NTSTATUS status;
+
+ status = create_ts_recv(subreq, &state->fnums[state->num_received]);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->num_received += 1;
+ if (state->num_received == state->num_files) {
+ tevent_req_done(req);
+ }
+}
+
+static NTSTATUS create_ts_files_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, uint16_t **fnums)
+{
+ struct create_ts_files_state *state = tevent_req_data(
+ req, struct create_ts_files_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *fnums = talloc_move(mem_ctx, &state->fnums);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct create_files_state {
+ size_t num_reqs;
+ size_t num_received;
+ struct tevent_req **reqs;
+ uint16_t **fnums;
+};
+
+static void create_files_done(struct tevent_req *subreq);
+
+static struct tevent_req *create_files_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state **cli,
+ size_t num_cli,
+ const char *prefix,
+ size_t num_files)
+{
+ struct tevent_req *req = NULL;
+ struct create_files_state *state = NULL;
+ size_t i;
+
+ req = tevent_req_create(mem_ctx, &state, struct create_files_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->num_reqs = num_cli;
+
+ state->reqs = talloc_array(state, struct tevent_req *, num_cli);
+ if (tevent_req_nomem(state->reqs, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->fnums = talloc_array(state, uint16_t *, num_cli);
+ if (tevent_req_nomem(state->fnums, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_cli; i++) {
+ state->reqs[i] = create_ts_files_send(
+ state, ev, cli[i], prefix, i, num_files);
+ if (tevent_req_nomem(state->reqs[i], req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->reqs[i], create_files_done, req);
+ }
+ return req;
+}
+
+static void create_files_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct create_files_state *state = tevent_req_data(
+ req, struct create_files_state);
+ uint16_t *fnums = NULL;
+ NTSTATUS status;
+ size_t i;
+
+ status = create_ts_files_recv(subreq, state->fnums, &fnums);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ for (i=0; i<state->num_reqs; i++) {
+ if (state->reqs[i] == subreq) {
+ break;
+ }
+ }
+ if (i == state->num_reqs) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ TALLOC_FREE(subreq);
+ state->reqs[i] = NULL;
+ state->fnums[i] = fnums;
+
+ state->num_received += 1;
+
+ if (state->num_reqs == state->num_received) {
+ tevent_req_done(req);
+ }
+}
+
+static NTSTATUS create_files_recv(
+ struct tevent_req *req, TALLOC_CTX *mem_ctx, uint16_t ***fnums)
+{
+ struct create_files_state *state = tevent_req_data(
+ req, struct create_files_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *fnums = talloc_move(mem_ctx, &state->fnums);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct list_cb_state {
+ size_t found;
+ bool ok;
+};
+
+static NTSTATUS list_cb(
+ struct file_info *f,
+ const char *mask,
+ void *private_data)
+{
+ struct list_cb_state *state = private_data;
+ char *underbar = NULL;
+ unsigned long long int name_idx;
+ int err;
+
+ underbar = strchr(f->name, '_');
+ if (underbar == NULL) {
+ /* alien filename, . or ..? */
+ return NT_STATUS_OK;
+ }
+
+ name_idx = smb_strtoull(underbar+1, NULL, 10, &err, SMB_STR_STANDARD);
+ if (err != 0) {
+ /* non-numeric? */
+ return NT_STATUS_OK;
+ }
+
+ if ((name_idx & 0xFFFF) != (f->mtime_ts.tv_sec & 0xFFFF)) {
+ d_printf("idx=%llu, nsec=%ld\n",
+ name_idx,
+ f->mtime_ts.tv_nsec);
+ state->ok = false;
+ }
+ state->found += 1;
+
+ return NT_STATUS_OK;
+}
+
+bool run_readdir_timestamp(int dummy)
+{
+ struct cli_state **cli = NULL;
+ int i;
+ bool ret = false;
+ bool ok;
+ const char prefix[] = "readdir_ts/";
+ struct list_cb_state state = { .ok = true };
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ uint16_t **fnums = NULL;
+ NTSTATUS status;
+ size_t expected;
+
+ cli = talloc_array(talloc_tos(), struct cli_state *, torture_nprocs);
+ if (cli == NULL) {
+ d_printf("talloc_array failed\n");
+ goto fail;
+ }
+
+ for (i=0; i<torture_nprocs; i++) {
+ ok = torture_open_connection_flags(&cli[i], i, 0);
+ if (!ok) {
+ d_printf("torture_open_connection_flags(%d) failed\n",
+ i);
+ goto fail;
+ }
+ }
+
+ status = cli_mkdir(cli[0], "readdir_ts");
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_mkdir failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(cli);
+ if (ev == NULL) {
+ d_printf("samba_tevent_context_init() failed\n");
+ goto fail;
+ }
+
+ req = create_files_send(
+ cli, ev, cli, torture_nprocs, prefix, torture_numops);
+ if (req == NULL) {
+ d_printf("create_files_send() failed\n");
+ goto fail;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_printf("tevent_req_poll_ntstatus failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = create_files_recv(req, talloc_tos(), &fnums);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("create_files_recv failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_list(cli[0],
+ "readdir_ts\\*",
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN,
+ list_cb,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_list failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ expected = torture_nprocs * torture_numops;
+ if (state.found != expected) {
+ d_printf("Expected %zu, got %zu files\n",
+ expected,
+ state.found);
+ goto fail;
+ }
+ if (!state.ok) {
+ d_printf("timestamp mismatch\n");
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(cli);
+ return ret;
+}
diff --git a/source3/torture/test_rpc_scale.c b/source3/torture/test_rpc_scale.c
new file mode 100644
index 0000000..6ef26f3
--- /dev/null
+++ b/source3/torture/test_rpc_scale.c
@@ -0,0 +1,301 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "source3/include/includes.h"
+#include "source3/torture/proto.h"
+#include "source3/libsmb/libsmb.h"
+#include "librpc/gen_ndr/ndr_spoolss_c.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "source3/rpc_client/rpc_client.h"
+#include "source3/rpc_client/cli_pipe.h"
+#include "libcli/smb/smbXcli_base.h"
+
+extern int torture_nprocs;
+extern int torture_numops;
+
+struct rpc_scale_one_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ size_t num_iterations;
+ struct rpc_pipe_client *rpccli;
+ DATA_BLOB buffer;
+ uint32_t needed;
+ uint32_t num_printers;
+ union spoolss_PrinterInfo *printers;
+};
+
+static void rpc_scale_one_opened(struct tevent_req *subreq);
+static void rpc_scale_one_bound(struct tevent_req *subreq);
+static void rpc_scale_one_listed(struct tevent_req *subreq);
+
+static struct tevent_req *rpc_scale_one_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ size_t num_iterations)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct rpc_scale_one_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_scale_one_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->num_iterations = num_iterations;
+
+ subreq = rpc_pipe_open_np_send(
+ state, ev, cli, &ndr_table_spoolss);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_scale_one_opened, req);
+ return req;
+}
+
+static void rpc_scale_one_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_scale_one_state *state = tevent_req_data(
+ req, struct rpc_scale_one_state);
+ struct pipe_auth_data *auth = NULL;
+ NTSTATUS status;
+
+ status = rpc_pipe_open_np_recv(subreq, state, &state->rpccli);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ status = rpccli_anon_bind_data(state, &auth);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = rpc_pipe_bind_send(state, state->ev, state->rpccli, auth);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_scale_one_bound, req);
+}
+
+static void rpc_scale_one_bound(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_scale_one_state *state = tevent_req_data(
+ req, struct rpc_scale_one_state);
+ char *server = NULL;
+ NTSTATUS status;
+
+ status = rpc_pipe_bind_recv(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ server = talloc_asprintf(
+ state,
+ "\\%s\n",
+ smbXcli_conn_remote_name(state->cli->conn));
+ if (tevent_req_nomem(server, req)) {
+ return;
+ }
+ state->buffer = data_blob_talloc(state, NULL, 4096);
+ if (tevent_req_nomem(state->buffer.data, req)) {
+ return;
+ }
+
+ subreq = dcerpc_spoolss_EnumPrinters_send(
+ state,
+ state->ev,
+ state->rpccli->binding_handle,
+ PRINTER_ENUM_LOCAL,
+ server,
+ 1, /* level */
+ &state->buffer,
+ state->buffer.length,
+ &state->num_printers,
+ &state->printers,
+ &state->needed);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_scale_one_listed, req);
+}
+
+static void rpc_scale_one_listed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_scale_one_state *state = tevent_req_data(
+ req, struct rpc_scale_one_state);
+ NTSTATUS status;
+ WERROR result;
+
+ status = dcerpc_spoolss_EnumPrinters_recv(subreq, state, &result);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ status = werror_to_ntstatus(result);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ /*
+ * This will trigger a sync close. Making that async will be a
+ * lot of effort, and even with this being sync this test is
+ * nasty enough.
+ */
+ TALLOC_FREE(state->rpccli);
+
+ state->num_iterations -= 1;
+
+ if (state->num_iterations == 0) {
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = rpc_pipe_open_np_send(
+ state, state->ev, state->cli, &ndr_table_spoolss);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, rpc_scale_one_opened, req);
+}
+
+static NTSTATUS rpc_scale_one_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct rpc_scale_state {
+ size_t num_reqs;
+ size_t done;
+};
+
+static void rpc_scale_done(struct tevent_req *subreq);
+
+static struct tevent_req *rpc_scale_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state **clis)
+{
+ struct tevent_req *req = NULL;
+ struct rpc_scale_state *state = NULL;
+ size_t i, num_clis = talloc_array_length(clis);
+
+ req = tevent_req_create(mem_ctx, &state, struct rpc_scale_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->num_reqs = num_clis;
+
+ for (i=0; i<num_clis; i++) {
+ struct tevent_req *subreq = rpc_scale_one_send(
+ state, ev, clis[i], torture_numops);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, rpc_scale_done, req);
+ }
+ return req;
+}
+
+static void rpc_scale_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct rpc_scale_state *state = tevent_req_data(
+ req, struct rpc_scale_state);
+ NTSTATUS status;
+
+ status = rpc_scale_one_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->done += 1;
+
+ if (state->done == state->num_reqs) {
+ tevent_req_done(req);
+ }
+}
+
+static NTSTATUS rpc_scale_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+bool run_rpc_scale(int dummy)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cli_state **clis = NULL;
+ struct tevent_req *req = NULL;
+ struct tevent_context *ev = NULL;
+ bool ok, result = false;
+ NTSTATUS status;
+ int i;
+
+ clis = talloc_zero_array(
+ talloc_tos(), struct cli_state *, torture_nprocs);
+ if (clis == NULL) {
+ fprintf(stderr, "talloc failed\n");
+ goto fail;
+ }
+
+ for (i=0; i<torture_nprocs; i++) {
+ ok = torture_open_connection_flags(&clis[i], i, 0);
+ if (!ok) {
+ fprintf(stderr, "could not open connection %d\n", i);
+ goto fail;
+ }
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = rpc_scale_send(talloc_tos(), ev, clis);
+ if (req == NULL) {
+ goto fail;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ fprintf(stderr,
+ "rpc_scale_send failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = rpc_scale_recv(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "rpc_scale failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ result = true;
+fail:
+ TALLOC_FREE(frame);
+ return result;
+}
diff --git a/source3/torture/test_smb1_dfs.c b/source3/torture/test_smb1_dfs.c
new file mode 100644
index 0000000..4cd75c9
--- /dev/null
+++ b/source3/torture/test_smb1_dfs.c
@@ -0,0 +1,4284 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB1 DFS tests.
+ Copyright (C) Jeremy Allison 2022.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "client.h"
+#include "trans2.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/security/security.h"
+#include "libsmb/proto.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth_generic.h"
+#include "../librpc/ndr/libndr.h"
+#include "libsmb/clirap.h"
+#include "async_smb.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "lib/util/time_basic.h"
+
+extern fstring host, workgroup, share, password, username, myname;
+extern struct cli_credentials *torture_creds;
+
+/*
+ * Open an SMB1 file readonly and return the create time.
+ */
+static NTSTATUS get_smb1_crtime(struct cli_state *cli,
+ const char *pathname,
+ struct timespec *pcrtime)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0;
+ struct timespec crtime = {0};
+
+ /*
+ * Open the file.
+ */
+
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ pathname,
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Get the create time. Note - we can use
+ * a higher-level cli_XXX function here
+ * for SMB1 as cli_qfileinfo_basic()
+ * doesn't use any pathnames, only fnums
+ * so it isn't affected by DFS pathnames.
+ */
+ status = cli_qfileinfo_basic(cli,
+ fnum,
+ NULL, /* attr */
+ NULL, /* size */
+ &crtime, /* create_time */
+ NULL, /* access_time */
+ NULL, /* write_time */
+ NULL, /* change_time */
+ NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ *pcrtime = crtime;
+ }
+
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ return status;
+}
+
+/*
+ * Check a crtime matches a given SMB1 path.
+ */
+static bool smb1_crtime_matches(struct cli_state *cli,
+ const char *match_pathname,
+ struct timespec crtime_tomatch,
+ const char *test_pathname)
+{
+ struct timespec test_crtime = { 0 };
+ NTSTATUS status;
+ bool equal = false;
+
+ status = get_smb1_crtime(cli,
+ test_pathname,
+ &test_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s: Failed to get crtime "
+ "for %s, (%s)\n",
+ __func__,
+ test_pathname,
+ nt_errstr(status));
+ return false;
+ }
+ equal = (timespec_compare(&test_crtime, &crtime_tomatch) == 0);
+ if (!equal) {
+ struct timeval_buf test_buf;
+ struct timeval_buf tomatch_buf;
+ printf("%s: crtime mismatch "
+ "%s:crtime_tomatch=%s, %s:test_crtime = %s\n",
+ __func__,
+ match_pathname,
+ timespec_string_buf(&crtime_tomatch,
+ true,
+ &tomatch_buf),
+ test_pathname,
+ timespec_string_buf(&test_crtime,
+ true,
+ &test_buf));
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Delete an SMB1 file on a DFS share.
+ */
+static NTSTATUS smb1_dfs_delete(struct cli_state *cli,
+ const char *pathname)
+{
+ NTSTATUS status;
+ uint16_t fnum = 0;
+
+ /*
+ * Open the file.
+ */
+
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ pathname,
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Set delete on close. Note - we can use
+ * a higher-level cli_XXX function here
+ * for SMB1 as cli_nt_delete_on_close()
+ * doesn't use any pathnames, only fnums
+ * so it isn't affected by DFS pathnames.
+ */
+ /*
+ */
+ status = cli_nt_delete_on_close(cli, fnum, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+}
+
+static void smb1_mv_done(struct tevent_req *subreq);
+
+struct smb1_mv_state {
+ uint16_t vwv[1];
+};
+
+static struct tevent_req *smb1_mv_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name)
+{
+ uint8_t *bytes = NULL;
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct smb1_mv_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct smb1_mv_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ PUSH_LE_U16(state->vwv,
+ 0,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_DIRECTORY);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ src_dfs_name,
+ strlen(src_dfs_name)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ bytes = talloc_realloc(state,
+ bytes,
+ uint8_t,
+ talloc_get_size(bytes)+1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ bytes[talloc_get_size(bytes)-1] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ target_name,
+ strlen(target_name)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_send(state,
+ ev,
+ cli,
+ SMBmv,
+ 0, /* additional_flags */
+ 0, /* additional_flags2 */
+ 1,
+ state->vwv,
+ talloc_get_size(bytes),
+ bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb1_mv_done, req);
+ return req;
+}
+
+static void smb1_mv_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(subreq,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ tevent_req_simple_finish_ntstatus(subreq,
+ status);
+}
+
+static NTSTATUS smb1_mv_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/*
+ * Rename an SMB1 file on a DFS share. SMBmv version.
+ */
+static NTSTATUS smb1_mv(struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ frame = talloc_stackframe();
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = smb1_mv_send(frame,
+ ev,
+ cli,
+ src_dfs_name,
+ target_name);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = smb1_mv_recv(req);
+
+ fail:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static bool test_smb1_mv(struct cli_state *cli,
+ const char *src_dfs_name)
+{
+ struct timespec test_timespec = { 0 };
+ NTSTATUS status;
+
+ status = smb1_mv(cli,
+ src_dfs_name,
+ "BAD\\BAD\\renamed_file");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMBmv of %s -> %s should succeed "
+ "got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure we did rename. */
+ status = get_smb1_crtime(cli,
+ "BAD\\BAD\\renamed_file",
+ &test_timespec);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime "
+ "for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Put it back. */
+ status = smb1_mv(cli,
+ "BAD\\BAD\\renamed_file",
+ src_dfs_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMBmv of %s -> %s should succeed "
+ "got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\renamed_file",
+ src_dfs_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure we did put it back. */
+ status = get_smb1_crtime(cli,
+ src_dfs_name,
+ &test_timespec);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime "
+ "for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Try with a non-DFS name. */
+ status = smb1_mv(cli,
+ src_dfs_name,
+ "renamed_file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) {
+ /* Fails I think as target becomes "" on server. */
+ printf("%s:%d SMBmv of %s -> %s should get "
+ "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Try with a non-DFS name. */
+ status = smb1_mv(cli,
+ src_dfs_name,
+ "BAD\\renamed_file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) {
+ /* Fails I think as target becomes "" on server. */
+ printf("%s:%d SMBmv of %s -> %s should get "
+ "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+ return true;
+}
+
+static void smb1_setpathinfo_done(struct tevent_req *subreq);
+
+struct smb1_setpathinfo_state {
+ uint16_t setup;
+ uint8_t *param;
+ uint8_t *data;
+};
+
+static struct tevent_req *smb1_setpathinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name,
+ uint16_t info_level)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct smb1_setpathinfo_state *state = NULL;
+ smb_ucs2_t *converted_str = NULL;
+ size_t converted_size_bytes = 0;
+ bool ok = false;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct smb1_setpathinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ PUSH_LE_U16(&state->setup, 0, TRANSACT2_SETPATHINFO);
+
+ state->param = talloc_zero_array(state, uint8_t, 6);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+ PUSH_LE_U16(state->param, 0, info_level);
+
+ state->param = trans2_bytes_push_str(state->param,
+ smbXcli_conn_use_unicode(cli->conn),
+ src_dfs_name,
+ strlen(src_dfs_name)+1,
+ NULL);
+ if (tevent_req_nomem(state->param, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ ok = push_ucs2_talloc(state,
+ &converted_str,
+ target_name,
+ &converted_size_bytes);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * W2K8 insists the dest name is not null
+ * terminated. Remove the last 2 zero bytes
+ * and reduce the name length.
+ */
+
+ if (converted_size_bytes < 2) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ converted_size_bytes -= 2;
+
+ state->data = talloc_zero_array(state,
+ uint8_t,
+ 12 + converted_size_bytes);
+ if (tevent_req_nomem(state->data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ SIVAL(state->data, 8, converted_size_bytes);
+ memcpy(state->data + 12, converted_str, converted_size_bytes);
+
+ subreq = cli_trans_send(state, /* mem ctx. */
+ ev,/* event ctx. */
+ cli,/* cli_state. */
+ 0,/* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL,/* pipe name. */
+ -1,/* fid. */
+ 0,/* function. */
+ 0,/* flags. */
+ &state->setup,/* setup. */
+ 1,/* num setup uint16_t words. */
+ 0,/* max returned setup. */
+ state->param,/* param. */
+ talloc_get_size(state->param),/* num param. */
+ 2,/* max returned param. */
+ state->data,/* data. */
+ talloc_get_size(state->data),/* num data. */
+ 0);/* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb1_setpathinfo_done, req);
+ return req;
+}
+
+static void smb1_setpathinfo_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_trans_recv(subreq,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0,
+ NULL);
+ tevent_req_simple_finish_ntstatus(subreq,
+ status);
+}
+
+static NTSTATUS smb1_setpathinfo_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/*
+ * Rename or hardlink an SMB1 file on a DFS share. SMB1 setpathinfo
+ * (pathnames only) version.
+ */
+static NTSTATUS smb1_setpathinfo(struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name,
+ uint16_t info_level)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ frame = talloc_stackframe();
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = smb1_setpathinfo_send(frame,
+ ev,
+ cli,
+ src_dfs_name,
+ target_name,
+ info_level);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = smb1_setpathinfo_recv(req);
+
+ fail:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS smb1_setpathinfo_rename(struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name)
+{
+ return smb1_setpathinfo(cli,
+ src_dfs_name,
+ target_name,
+ SMB_FILE_RENAME_INFORMATION);
+}
+
+static bool test_smb1_setpathinfo_rename(struct cli_state *cli,
+ const char *src_dfs_name)
+{
+ struct timespec test_crtime = { 0 };
+ NTSTATUS status;
+ const char *putback_path = NULL;
+
+ /*
+ * On Windows, setpathinfo rename where the target contains
+ * any directory separator returns STATUS_NOT_SUPPORTED.
+ *
+ * MS-SMB behavior note: <133> Section 3.3.5.10.6:
+ *
+ * "If the file name pointed to by the FileName parameter of the
+ * FILE_RENAME_INFORMATION structure contains a separator character,
+ * then the request fails with STATUS_NOT_SUPPORTED."
+ */
+ status = smb1_setpathinfo_rename(cli,
+ src_dfs_name,
+ "BAD\\BAD\\renamed_file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ printf("%s:%d SMB1 setpathinfo rename of %s -> %s should get "
+ "NT_STATUS_NOT_SUPPORTED got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Try with a non-DFS name. */
+ status = smb1_setpathinfo_rename(cli,
+ src_dfs_name,
+ "renamed_file");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1 setpathinfo rename of %s -> %s "
+ "should succeed got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure we did rename. */
+ status = get_smb1_crtime(cli,
+ "BAD\\BAD\\renamed_file",
+ &test_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime "
+ "for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * To put it back we need to reverse the DFS-ness of src
+ * and destination paths.
+ */
+ putback_path = strrchr(src_dfs_name, '\\');
+ if (putback_path == NULL) {
+ printf("%s:%d non DFS path %s passed. Internal error\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name);
+ return false;
+ }
+ /* Walk past the last '\\' */
+ putback_path++;
+
+ /* Put it back. */
+ status = smb1_setpathinfo_rename(cli,
+ "BAD\\BAD\\renamed_file",
+ putback_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1 setpathinfo rename of %s -> %s "
+ "should succeed got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\renamed_file",
+ putback_path,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure we did rename. */
+ status = get_smb1_crtime(cli,
+ src_dfs_name,
+ &test_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime "
+ "for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+static NTSTATUS smb1_setpathinfo_hardlink(struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name)
+{
+ return smb1_setpathinfo(cli,
+ src_dfs_name,
+ target_name,
+ SMB_FILE_LINK_INFORMATION);
+}
+
+static bool test_smb1_setpathinfo_hardlink(struct cli_state *cli,
+ const char *src_dfs_name)
+{
+ NTSTATUS status;
+
+ /*
+ * On Windows, setpathinfo rename where the target contains
+ * any directory separator returns STATUS_NOT_SUPPORTED.
+ *
+ * MS-SMB behavior note: <133> Section 3.3.5.10.6:
+ *
+ * "If the file name pointed to by the FileName parameter of the
+ * FILE_RENAME_INFORMATION structure contains a separator character,
+ * then the request fails with STATUS_NOT_SUPPORTED."
+ *
+ * setpathinfo info level SMB_FILE_LINK_INFORMATION
+ * seems to do the same, but this could be an artifact
+ * of the Windows version tested (Win2K8). I will
+ * revisit this when I'm able to test against
+ * a later Windows version with a DFS server.
+ */
+ status = smb1_setpathinfo_hardlink(cli,
+ src_dfs_name,
+ "BAD\\BAD\\hlink");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ printf("%s:%d SMB1 setpathinfo hardlink of %s -> %s should get "
+ "NT_STATUS_NOT_SUPPORTED got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\hlink",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Try with a non-DFS name. */
+ /*
+ * At least on Windows 2008 this also fails with
+ * NT_STATUS_NOT_SUPPORTED, leading me to believe
+ * setting hardlinks is only supported via NTrename
+ * in SMB1.
+ */
+ status = smb1_setpathinfo_hardlink(cli,
+ src_dfs_name,
+ "hlink");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ printf("%s:%d SMB1 setpathinfo hardlink of %s -> %s should get "
+ "NT_STATUS_NOT_SUPPORTED got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "hlink",
+ nt_errstr(status));
+ return false;
+ }
+ return true;
+}
+
+static void smb1_ntrename_done(struct tevent_req *subreq);
+
+struct smb1_ntrename_state {
+ uint16_t vwv[4];
+};
+
+static struct tevent_req *smb1_ntrename_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name,
+ uint16_t rename_flag)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct smb1_ntrename_state *state = NULL;
+ uint8_t *bytes = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct smb1_ntrename_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ PUSH_LE_U16(state->vwv,
+ 0,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_DIRECTORY);
+ PUSH_LE_U16(state->vwv, 2, rename_flag);
+
+ bytes = talloc_array(state, uint8_t, 1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ src_dfs_name,
+ strlen(src_dfs_name)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes = talloc_realloc(state,
+ bytes,
+ uint8_t,
+ talloc_get_size(bytes)+1);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ bytes[talloc_get_size(bytes)-1] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ target_name,
+ strlen(target_name)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = cli_smb_send(state,
+ ev,
+ cli,
+ SMBntrename,
+ 0, /* additional_flags */
+ 0, /* additional_flags2 */
+ 4,
+ state->vwv,
+ talloc_get_size(bytes),
+ bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb1_ntrename_done, req);
+ return req;
+}
+
+static void smb1_ntrename_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_smb_recv(subreq,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS smb1_ntrename_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/*
+ * Rename or hardlink an SMB1 file on a DFS share. SMB1 ntrename version.
+ * (pathnames only).
+ */
+static NTSTATUS smb1_ntrename(struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name,
+ uint16_t rename_flag)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ frame = talloc_stackframe();
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = smb1_ntrename_send(frame,
+ ev,
+ cli,
+ src_dfs_name,
+ target_name,
+ rename_flag);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = smb1_ntrename_recv(req);
+
+ fail:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+/*
+ * Rename an SMB1 file on a DFS share. SMB1 ntrename version.
+ */
+static NTSTATUS smb1_ntrename_rename(struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name)
+{
+ return smb1_ntrename(cli,
+ src_dfs_name,
+ target_name,
+ RENAME_FLAG_RENAME);
+}
+
+
+static bool test_smb1_ntrename_rename(struct cli_state *cli,
+ const char *src_dfs_name)
+{
+ struct timespec test_crtime = { 0 };
+ NTSTATUS status;
+
+ /* Try with a non-DFS name. */
+ status = smb1_ntrename_rename(cli,
+ src_dfs_name,
+ "renamed_file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) {
+ /* Fails I think as target becomes "" on server. */
+ printf("%s:%d SMB1 ntrename rename of %s -> %s should get "
+ "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb1_ntrename_rename(cli,
+ src_dfs_name,
+ "BAD\\BAD\\renamed_file");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1 ntrename rename of %s -> %s should "
+ "succeed got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure we did rename. */
+ status = get_smb1_crtime(cli,
+ "BAD\\BAD\\renamed_file",
+ &test_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime "
+ "for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\renamed_file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Put it back. */
+ status = smb1_ntrename_rename(cli,
+ "BAD\\BAD\\renamed_file",
+ src_dfs_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1 ntrename rename of %s -> %s "
+ "should succeed got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\renamed_file",
+ src_dfs_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure we did rename. */
+ status = get_smb1_crtime(cli,
+ src_dfs_name,
+ &test_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime "
+ "for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Hard link an SMB1 file on a DFS share. SMB1 ntrename version.
+ */
+static NTSTATUS smb1_ntrename_hardlink(struct cli_state *cli,
+ const char *src_dfs_name,
+ const char *target_name)
+{
+ return smb1_ntrename(cli,
+ src_dfs_name,
+ target_name,
+ RENAME_FLAG_HARD_LINK);
+}
+
+static bool test_smb1_ntrename_hardlink(struct cli_state *cli,
+ const char *src_dfs_name)
+{
+ struct timespec test_crtime = { 0 };
+ NTSTATUS status;
+ bool retval = false;
+
+ /* Try with a non-DFS name. */
+ status = smb1_ntrename_hardlink(cli,
+ src_dfs_name,
+ "hlink");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_SYNTAX_BAD)) {
+ /* Fails I think as target becomes "" on server. */
+ printf("%s:%d SMB1 ntrename of %s -> %s should get "
+ "NT_STATUS_OBJECT_PATH_SYNTAX_BAD got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "hlink",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb1_ntrename_hardlink(cli,
+ src_dfs_name,
+ "BAD\\BAD\\hlink");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1 ntrename hardlink of %s -> %s "
+ "should succeed got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\hlink",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure we did hardlink. */
+ status = get_smb1_crtime(cli,
+ "BAD\\BAD\\hlink",
+ &test_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime "
+ "for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\hlink",
+ nt_errstr(status));
+ goto out;
+ }
+
+ retval = smb1_crtime_matches(cli,
+ "BAD\\BAD\\hlink",
+ test_crtime,
+ src_dfs_name);
+ if (!retval) {
+ printf("%s:%d smb1_crtime_matches failed for "
+ "%s %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\hlink");
+ goto out;
+ }
+
+ out:
+
+ /* Remove the hardlink to clean up. */
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink");
+ return retval;
+}
+
+static void smb1_setfileinfo_done(struct tevent_req *subreq);
+
+struct smb1_setfileinfo_state {
+ uint16_t setup;
+ uint8_t param[6];
+ uint8_t *data;
+};
+
+static struct tevent_req *smb1_setfileinfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ const char *target_name,
+ uint16_t info_level)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ struct smb1_setfileinfo_state *state = NULL;
+ smb_ucs2_t *converted_str = NULL;
+ size_t converted_size_bytes = 0;
+ bool ok = false;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct smb1_setfileinfo_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ PUSH_LE_U16(&state->setup, 0, TRANSACT2_SETPATHINFO);
+
+ PUSH_LE_U16(state->param, 0, fnum);
+ PUSH_LE_U16(state->param, 2, info_level);
+
+ ok = push_ucs2_talloc(state,
+ &converted_str,
+ target_name,
+ &converted_size_bytes);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * W2K8 insists the dest name is not null
+ * terminated. Remove the last 2 zero bytes
+ * and reduce the name length.
+ */
+
+ if (converted_size_bytes < 2) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ converted_size_bytes -= 2;
+
+ state->data = talloc_zero_array(state,
+ uint8_t,
+ 12 + converted_size_bytes);
+ if (tevent_req_nomem(state->data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ SIVAL(state->data, 8, converted_size_bytes);
+ memcpy(state->data + 12, converted_str, converted_size_bytes);
+
+ subreq = cli_trans_send(state, /* mem ctx. */
+ ev,/* event ctx. */
+ cli,/* cli_state. */
+ 0,/* additional_flags2 */
+ SMBtrans2, /* cmd. */
+ NULL,/* pipe name. */
+ -1,/* fid. */
+ 0,/* function. */
+ 0,/* flags. */
+ &state->setup,/* setup. */
+ 1,/* num setup uint16_t words. */
+ 0,/* max returned setup. */
+ state->param,/* param. */
+ 6,/* num param. */
+ 2,/* max returned param. */
+ state->data,/* data. */
+ talloc_get_size(state->data),/* num data. */
+ 0);/* max returned data. */
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb1_setfileinfo_done, req);
+ return req;
+}
+
+static void smb1_setfileinfo_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_trans_recv(subreq,
+ NULL,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ 0,
+ NULL);
+ tevent_req_simple_finish_ntstatus(subreq,
+ status);
+}
+
+static NTSTATUS smb1_setfileinfo_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+/*
+ * Rename or hardlink an SMB1 file on a DFS share.
+ * setfileinfo (file handle + target pathname) version.
+ */
+static NTSTATUS smb1_setfileinfo(struct cli_state *cli,
+ uint16_t fnum,
+ const char *target_name,
+ uint16_t info_level)
+{
+ TALLOC_CTX *frame = NULL;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ frame = talloc_stackframe();
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ req = smb1_setfileinfo_send(frame,
+ ev,
+ cli,
+ fnum,
+ target_name,
+ info_level);
+ if (req == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = smb1_setfileinfo_recv(req);
+
+ fail:
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS smb1_setfileinfo_rename(struct cli_state *cli,
+ uint16_t fnum,
+ const char *target_name)
+{
+ return smb1_setfileinfo(cli,
+ fnum,
+ target_name,
+ SMB_FILE_RENAME_INFORMATION);
+}
+
+/*
+ * On Windows, rename using a file handle as source
+ * is not supported.
+ */
+
+static bool test_smb1_setfileinfo_rename(struct cli_state *cli,
+ const char *src_dfs_name)
+{
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ bool retval = false;
+
+ /* First open the source file. */
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ src_dfs_name,
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d failed to open %s, %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * On Windows rename given a file handle returns
+ * NT_STATUS_UNSUCCESSFUL (not documented in MS-SMB).
+ */
+
+ status = smb1_setfileinfo_rename(cli,
+ fnum,
+ "BAD\\BAD\\renamed_file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) {
+ printf("%s:%d SMB1 setfileinfo rename of %s -> %s should get "
+ "NT_STATUS_UNSUCCESSFUL got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\hlink",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Try with a non-DFS name - still gets NT_STATUS_UNSUCCESSFUL. */
+ status = smb1_setfileinfo_rename(cli,
+ fnum,
+ "renamed_file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) {
+ printf("%s:%d SMB1 setfileinfo rename of %s -> %s should get "
+ "NT_STATUS_UNSUCCESSFUL got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "hlink",
+ nt_errstr(status));
+ goto out;
+ }
+
+ retval = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ }
+
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\renamed_file");
+ return retval;
+}
+
+
+static NTSTATUS smb1_setfileinfo_hardlink(struct cli_state *cli,
+ uint16_t fnum,
+ const char *target_name)
+{
+ return smb1_setfileinfo(cli,
+ fnum,
+ target_name,
+ SMB_FILE_LINK_INFORMATION);
+}
+
+/*
+ * On Windows, hardlink using a file handle as source
+ * is not supported.
+ */
+
+static bool test_smb1_setfileinfo_hardlink(struct cli_state *cli,
+ const char *src_dfs_name)
+{
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ bool retval = false;
+
+ /* First open the source file. */
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ src_dfs_name,
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_RIGHTS_FILE_READ, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d failed to open %s, %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * On Windows hardlink given a file handle returns
+ * NT_STATUS_UNSUCCESSFUL (not documented in MS-SMB).
+ */
+
+ status = smb1_setfileinfo_hardlink(cli,
+ fnum,
+ "BAD\\BAD\\hlink");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) {
+ printf("%s:%d SMB1 setfileinfo hardlink of %s -> %s should get "
+ "NT_STATUS_UNSUCCESSFUL got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "BAD\\BAD\\hlink",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Try with a non-DFS name - still gets NT_STATUS_UNSUCCESSFUL. */
+ status = smb1_setfileinfo_hardlink(cli,
+ fnum,
+ "hlink");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) {
+ printf("%s:%d SMB1 setfileinfo hardlink of %s -> %s should get "
+ "NT_STATUS_UNSUCCESSFUL got %s\n",
+ __FILE__,
+ __LINE__,
+ src_dfs_name,
+ "hlink",
+ nt_errstr(status));
+ goto out;
+ }
+
+ retval = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ }
+
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink");
+ return retval;
+}
+
+/*
+ * According to:
+
+ * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/dc9978d7-6299-4c5a-a22d-a039cdc716ea
+ *
+ * (Characters " \ / [ ] : | < > + = ; , * ?,
+ * and control characters in range 0x00 through
+ * 0x1F, inclusive, are illegal in a share name)
+ *
+ * But Windows server only checks in DFS sharenames ':'. All other
+ * share names are allowed.
+ */
+
+static bool test_smb1_dfs_sharenames(struct cli_state *cli,
+ const char *dfs_root_share_name,
+ struct timespec root_crtime)
+{
+ char test_path[20];
+ const char *test_str = "/[]:|<>+=;,*?";
+ const char *p;
+ unsigned int i;
+ bool crtime_matched = false;
+
+ /* Setup template pathname. */
+ memcpy(test_path, "\\SERVER\\X", 10);
+
+ /* Test invalid control characters. */
+ for (i = 1; i < 0x20; i++) {
+ test_path[8] = i;
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ test_path);
+ if (!crtime_matched) {
+ return false;
+ }
+ }
+
+ /* Test explicit invalid characters. */
+ for (p = test_str; *p != '\0'; p++) {
+ test_path[8] = *p;
+ if (*p == ':') {
+ /*
+ * Only ':' is treated as an INVALID sharename
+ * for a DFS SERVER\\SHARE path.
+ */
+ struct timespec test_crtime = { 0 };
+ NTSTATUS status = get_smb1_crtime(cli,
+ test_path,
+ &test_crtime);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) {
+ printf("%s:%d Open of %s should get "
+ "NT_STATUS_OBJECT_NAME_INVALID, got %s\n",
+ __FILE__,
+ __LINE__,
+ test_path,
+ nt_errstr(status));
+ return false;
+ }
+ } else {
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ test_path);
+ if (!crtime_matched) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/*
+ * "Raw" test of SMB1 paths to a DFS share.
+ * We must (mostly) use the lower level smb1cli_XXXX() interfaces,
+ * not the cli_XXX() ones here as the ultimate goal is to fix our
+ * cli_XXX() interfaces to work transparently over DFS.
+ *
+ * So here, we're testing the server code, not the client code.
+ *
+ * Passes cleanly against Windows.
+ */
+
+bool run_smb1_dfs_paths(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool dfs_supported = false;
+ char *dfs_root_share_name = NULL;
+ struct timespec root_crtime = { 0 };
+ struct timespec test_crtime = { 0 };
+ bool crtime_matched = false;
+ bool retval = false;
+ bool ok = false;
+ bool equal = false;
+ unsigned int i;
+ uint16_t fnum = (uint16_t)-1;
+
+ printf("Starting SMB1-DFS-PATHS\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ /* Ensure this is a DFS share. */
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb1.tcon);
+ if (!dfs_supported) {
+ printf("Share %s does not support DFS\n",
+ cli->share);
+ return false;
+ }
+
+ /* Start with an empty share. */
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\BAD");
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\file");
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\renamed_file");
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink");
+
+ /*
+ * Create the "official" DFS share root name.
+ */
+ dfs_root_share_name = talloc_asprintf(talloc_tos(),
+ "\\%s\\%s",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share);
+ if (dfs_root_share_name == NULL) {
+ printf("Out of memory\n");
+ return false;
+ }
+
+ /* Get the share root crtime. */
+ status = get_smb1_crtime(cli,
+ dfs_root_share_name,
+ &root_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime for share root %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ dfs_root_share_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Test the Windows algorithm for parsing DFS names.
+ */
+ /*
+ * A single "SERVER" element should open and match the share root.
+ */
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ smbXcli_conn_remote_name(cli->conn));
+ if (!crtime_matched) {
+ printf("%s:%d Failed to match crtime for %s\n",
+ __FILE__,
+ __LINE__,
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+
+ /* An "" (empty) server name should open and match the share root. */
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ "");
+ if (!crtime_matched) {
+ printf("%s:%d Failed to match crtime for %s\n",
+ __FILE__,
+ __LINE__,
+ "");
+ return false;
+ }
+
+ /*
+ * For SMB1 the server just strips off any number of leading '\\'
+ * characters. Show this is the case.
+ */
+ for (i = 0; i < 10; i++) {
+ char leading_backslash_name[20];
+ leading_backslash_name[i] = '\\';
+ memcpy(&leading_backslash_name[i+1],
+ "SERVER",
+ strlen("SERVER")+1);
+
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ leading_backslash_name);
+ if (!crtime_matched) {
+ printf("%s:%d Failed to match crtime for %s\n",
+ __FILE__,
+ __LINE__,
+ leading_backslash_name);
+ return false;
+ }
+ }
+
+ /* A "BAD" server name should open and match the share root. */
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ "BAD");
+ if (!crtime_matched) {
+ printf("%s:%d Failed to match crtime for %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD");
+ return false;
+ }
+ /*
+ * A "BAD\\BAD" server and share name should open
+ * and match the share root.
+ */
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ "BAD\\BAD");
+ if (!crtime_matched) {
+ printf("%s:%d Failed to match crtime for %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD");
+ return false;
+ }
+ /*
+ * Trying to open "BAD\\BAD\\BAD" should get
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND.
+ */
+ status = get_smb1_crtime(cli,
+ "BAD\\BAD\\BAD",
+ &test_crtime);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("%s:%d Open of %s should get "
+ "STATUS_OBJECT_NAME_NOT_FOUND, got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\BAD",
+ nt_errstr(status));
+ return false;
+ }
+ /*
+ * Trying to open "BAD\\BAD\\BAD\\BAD" should get
+ * NT_STATUS_OBJECT_PATH_NOT_FOUND.
+ */
+ status = get_smb1_crtime(cli,
+ "BAD\\BAD\\BAD\\BAD",
+ &test_crtime);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("%s:%d Open of %s should get "
+ "STATUS_OBJECT_NAME_NOT_FOUND, got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\BAD\\BAD",
+ nt_errstr(status));
+ return false;
+ }
+ /*
+ * Test for invalid pathname characters in the servername.
+ * They are ignored, and it still opens the share root.
+ */
+ crtime_matched = smb1_crtime_matches(cli,
+ dfs_root_share_name,
+ root_crtime,
+ "::::");
+ if (!crtime_matched) {
+ printf("%s:%d Failed to match crtime for %s\n",
+ __FILE__,
+ __LINE__,
+ "::::");
+ return false;
+ }
+
+ /*
+ * Test for invalid pathname characters in the sharename.
+ * Invalid sharename characters should still be flagged as
+ * NT_STATUS_OBJECT_NAME_INVALID. It turns out only ':'
+ * is considered an invalid sharename character.
+ */
+ ok = test_smb1_dfs_sharenames(cli,
+ dfs_root_share_name,
+ root_crtime);
+ if (!ok) {
+ return false;
+ }
+
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ "BAD\\BAD\\file",
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_CREATE, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1cli_ntcreatex on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Close "file" handle. */
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ fnum = (uint16_t)-1;
+
+ /*
+ * Trying to open "BAD\\BAD\\file" should now get
+ * a valid crtime.
+ */
+ status = get_smb1_crtime(cli,
+ "BAD\\BAD\\file",
+ &test_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Open of %s should succeed "
+ "got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * This crtime must be different from the root_crtime.
+ * This checks we're actually correctly reading crtimes
+ * from the filesystem.
+ */
+ equal = (timespec_compare(&test_crtime, &root_crtime) == 0);
+ if (equal) {
+ printf("%s:%d Error. crtime of %s must differ from "
+ "root_crtime\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file");
+ goto err;
+ }
+
+ /*
+ * Test different SMB1 renames
+ * and hard links.
+ */
+
+ /* SMBmv only does rename. */
+ ok = test_smb1_mv(cli,
+ "BAD\\BAD\\file");
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_setpathinfo_rename(cli,
+ "BAD\\BAD\\file");
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_setpathinfo_hardlink(cli,
+ "BAD\\BAD\\file");
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_setfileinfo_rename(cli,
+ "BAD\\BAD\\file");
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_setfileinfo_hardlink(cli,
+ "BAD\\BAD\\file");
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_ntrename_rename(cli,
+ "BAD\\BAD\\file");
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_ntrename_hardlink(cli,
+ "BAD\\BAD\\file");
+ if (!ok) {
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ if (fnum != (uint16_t)-1) {
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ }
+
+ /* Delete anything we made. */
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\BAD");
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\file");
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\renamed_file");
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\hlink");
+ return retval;
+}
+
+/*
+ * SMB1 Findfirst. This is a minimal implementation
+ * that expects all filename returns in one packet.
+ * We're only using this to test the search DFS pathname
+ * parsing.
+ */
+
+/****************************************************************************
+ Calculate a safe next_entry_offset.
+****************************************************************************/
+
+static size_t calc_next_entry_offset(const uint8_t *base,
+ const uint8_t *pdata_end)
+{
+ size_t next_entry_offset = (size_t)PULL_LE_U32(base,0);
+
+ if (next_entry_offset == 0 ||
+ base + next_entry_offset < base ||
+ base + next_entry_offset > pdata_end) {
+ next_entry_offset = pdata_end - base;
+ }
+ return next_entry_offset;
+}
+
+static size_t get_filename(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const uint8_t *base_ptr,
+ uint16_t recv_flags2,
+ const uint8_t *p,
+ const uint8_t *pdata_end,
+ struct file_info *finfo)
+{
+ size_t ret = 0;
+ const uint8_t *base = p;
+ size_t namelen = 0;
+ size_t slen = 0;
+
+ ZERO_STRUCTP(finfo);
+
+ if (pdata_end - base < 94) {
+ return pdata_end - base;
+ }
+ p += 4; /* next entry offset */
+ p += 4; /* fileindex */
+ /* Offset zero is "create time", not "change time". */
+ p += 8;
+ finfo->atime_ts = interpret_long_date(BVAL(p, 0));
+ p += 8;
+ finfo->mtime_ts = interpret_long_date(BVAL(p, 0));
+ p += 8;
+ finfo->ctime_ts = interpret_long_date(BVAL(p, 0));
+ p += 8;
+ finfo->size = PULL_LE_U64(p, 0);
+ p += 8;
+ p += 8; /* alloc size */
+ finfo->attr = PULL_LE_U32(p, 0);
+ p += 4;
+ namelen = PULL_LE_U32(p, 0);
+ p += 4;
+ p += 4; /* EA size */
+ slen = PULL_LE_U8(p, 0);
+ if (slen > 24) {
+ /* Bad short name length. */
+ return pdata_end - base;
+ }
+ p += 2;
+ ret = pull_string_talloc(ctx,
+ base_ptr,
+ recv_flags2,
+ &finfo->short_name,
+ p,
+ slen,
+ STR_UNICODE);
+ if (ret == (size_t)-1) {
+ return pdata_end - base;
+ }
+ p += 24; /* short name */
+ if (p + namelen < p || p + namelen > pdata_end) {
+ return pdata_end - base;
+ }
+ ret = pull_string_talloc(ctx,
+ base_ptr,
+ recv_flags2,
+ &finfo->name,
+ p,
+ namelen,
+ 0);
+ if (ret == (size_t)-1) {
+ return pdata_end - base;
+ }
+ return calc_next_entry_offset(base, pdata_end);
+}
+
+/* Single shot SMB1 TRANS2 FindFirst. */
+
+static NTSTATUS smb1_findfirst(TALLOC_CTX *mem_ctx,
+ struct cli_state *cli,
+ const char *search_name,
+ struct file_info **names,
+ size_t *num_names)
+{
+ NTSTATUS status;
+ uint16_t setup[1];
+ uint8_t *param = NULL;
+ uint16_t recv_flags2 = 0;
+ uint8_t *rparam = NULL;
+ uint32_t num_rparam = 0;
+ uint8_t *rdata = NULL;
+ uint32_t num_rdata = 0;
+ uint16_t num_names_returned = 0;
+ struct file_info *finfo = NULL;
+ uint8_t *p2 = NULL;
+ uint8_t *data_end = NULL;
+ uint16_t i = 0;
+
+ PUSH_LE_U16(&setup[0], 0, TRANSACT2_FINDFIRST);
+
+ param = talloc_array(mem_ctx, uint8_t, 12);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ PUSH_LE_U16(param, 0, FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN);
+ PUSH_LE_U16(param, 2, 1366); /* max_matches */
+ PUSH_LE_U16(param, 4, FLAG_TRANS2_FIND_CLOSE_IF_END);
+ PUSH_LE_U16(param, 6, SMB_FIND_FILE_BOTH_DIRECTORY_INFO); /* info_level */
+
+ param = trans2_bytes_push_str(param,
+ smbXcli_conn_use_unicode(cli->conn),
+ search_name,
+ strlen(search_name)+1,
+ NULL);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * A one shot SMB1 findfirst will be enough to
+ * return ".", "..", and "file".
+ */
+ status = cli_trans(mem_ctx,
+ cli,
+ SMBtrans2, /* cmd */
+ NULL, /* pipe_name */
+ 0, /* fid */
+ 0, /* function */
+ 0, /* flags */
+ &setup[0],
+ 1, /* num_setup uint16_t words */
+ 0, /* max returned setup */
+ param,
+ talloc_get_size(param), /* num_param */
+ 10, /* max returned param */
+ NULL, /* data */
+ 0, /* num_data */
+ SMB_BUFFER_SIZE_MAX, /* max returned data */
+ /* Return values from here on.. */
+ &recv_flags2, /* recv_flags2 */
+ NULL, /* rsetup */
+ 0, /* min returned rsetup */
+ NULL, /* num_rsetup */
+ &rparam,
+ 6, /* min returned rparam */
+ &num_rparam, /* number of returned rparam */
+ &rdata,
+ 0, /* min returned rdata */
+ &num_rdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ num_names_returned = PULL_LE_U16(rparam, 2);
+
+ finfo = talloc_array(mem_ctx, struct file_info, num_names_returned);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ p2 = rdata;
+ data_end = rdata + num_rdata;
+
+ for (i = 0; i < num_names_returned; i++) {
+ if (p2 >= data_end) {
+ break;
+ }
+ if (i == num_names_returned - 1) {
+ /* Last entry - fixup the last offset length. */
+ PUSH_LE_U32(p2, 0, PTR_DIFF((rdata + num_rdata), p2));
+ }
+
+ p2 += get_filename(mem_ctx,
+ cli,
+ rdata,
+ recv_flags2,
+ p2,
+ data_end,
+ &finfo[i]);
+
+ if (finfo->name == NULL) {
+ printf("%s:%d Unable to parse name from listing "
+ "of %s, position %u\n",
+ __FILE__,
+ __LINE__,
+ search_name,
+ (unsigned int)i);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ }
+ *num_names = i;
+ *names = finfo;
+ return NT_STATUS_OK;
+}
+
+/*
+ * Test a specific SMB1 findfirst path to see if it
+ * matches a given file array.
+ */
+static bool test_smb1_findfirst_path(struct cli_state *cli,
+ const char *search_path,
+ struct file_info *root_finfo,
+ size_t num_root_finfo)
+{
+ size_t i = 0;
+ size_t num_finfo = 0;
+ struct file_info *finfo = NULL;
+ NTSTATUS status;
+
+ status = smb1_findfirst(talloc_tos(),
+ cli,
+ search_path,
+ &finfo,
+ &num_finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1findfirst on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ search_path,
+ nt_errstr(status));
+ return false;
+ }
+
+ if (num_finfo != num_root_finfo) {
+ printf("%s:%d On %s, num_finfo = %zu, num_root_finfo = %zu\n",
+ __FILE__,
+ __LINE__,
+ search_path,
+ num_finfo,
+ num_root_finfo);
+ return false;
+ }
+ for (i = 0; i < num_finfo; i++) {
+ bool match = strequal_m(finfo[i].name,
+ root_finfo[i].name);
+ if (!match) {
+ printf("%s:%d Mismatch. For %s, at position %zu, "
+ "finfo[i].name = %s, "
+ "root_finfo[i].name = %s\n",
+ __FILE__,
+ __LINE__,
+ search_path,
+ i,
+ finfo[i].name,
+ root_finfo[i].name);
+ return false;
+ }
+ }
+ TALLOC_FREE(finfo);
+ return true;
+}
+
+/*
+ * "Raw" test of doing a SMB1 findfirst to a DFS share.
+ * We must (mostly) use the lower level smb1cli_XXXX() interfaces,
+ * not the cli_XXX() ones here as the ultimate goal is to fix our
+ * cli_XXX() interfaces to work transparently over DFS.
+ *
+ * So here, we're testing the server code, not the client code.
+ *
+ * Passes cleanly against Windows.
+ */
+
+bool run_smb1_dfs_search_paths(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool dfs_supported = false;
+ struct file_info *root_finfo = NULL;
+ size_t num_root_finfo = 0;
+ bool retval = false;
+ bool ok = false;
+ uint16_t fnum = (uint16_t)-1;
+
+ printf("Starting SMB1-DFS-SEARCH-PATHS\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ /* Ensure this is a DFS share. */
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb1.tcon);
+ if (!dfs_supported) {
+ printf("Share %s does not support DFS\n",
+ cli->share);
+ return false;
+ }
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\file");
+
+ /* Create a test file to search for. */
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ "BAD\\BAD\\file",
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_CREATE, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1cli_ntcreatex on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Close "file" handle. */
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ fnum = (uint16_t)-1;
+
+ /* Get the list of files in the share. */
+ status = smb1_findfirst(talloc_tos(),
+ cli,
+ "SERVER\\SHARE\\*",
+ &root_finfo,
+ &num_root_finfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1findfirst on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "SERVER\\SHARE\\*",
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Try different search names. They should
+ * all match the root directory list.
+ */
+ ok = test_smb1_findfirst_path(cli,
+ "\\SERVER\\SHARE\\*",
+ root_finfo,
+ num_root_finfo);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_findfirst_path(cli,
+ "*",
+ root_finfo,
+ num_root_finfo);
+ if (!ok) {
+ goto err;
+ }
+ ok = test_smb1_findfirst_path(cli,
+ "\\*",
+ root_finfo,
+ num_root_finfo);
+ if (!ok) {
+ goto err;
+ }
+ ok = test_smb1_findfirst_path(cli,
+ "\\SERVER\\*",
+ root_finfo,
+ num_root_finfo);
+ if (!ok) {
+ goto err;
+ }
+ retval = true;
+
+ err:
+
+ if (fnum != (uint16_t)-1) {
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ }
+
+ /* Delete anything we made. */
+ (void)smb1_dfs_delete(cli, "BAD\\BAD\\file");
+ return retval;
+}
+
+static bool smb1_create_testfile(struct cli_state *cli,
+ const char *path)
+{
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+
+ /* Create a test file. */
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ path,
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_CREATE, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1cli_ntcreatex on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ path,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Close "file" handle. */
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ return true;
+}
+
+static NTSTATUS smb1_unlink(struct cli_state *cli,
+ const char *path)
+{
+ uint16_t vwv[1];
+ uint8_t *bytes = NULL;
+
+ PUSH_LE_U16(vwv, 0, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return cli_smb(talloc_tos(),
+ cli,
+ SMBunlink, /* command. */
+ 0, /* additional_flags. */
+ 1, /* wct. */
+ vwv, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 0, /* min_wct. */
+ NULL, /* return wcount. */
+ NULL, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+}
+
+static bool test_smb1_unlink(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ bool ok = false;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\file");
+
+ /* Create a test file. */
+ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\file");
+ if (!ok) {
+ printf("%s:%d failed to create test file %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\file");
+ goto err;
+ }
+
+ status = smb1_unlink(cli, "file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1unlink of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "file",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_unlink(cli, "\\BAD\\file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1unlink of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\file",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_unlink(cli, "\\BAD\\BAD\\file");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1unlink on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\file",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\file");
+ return retval;
+}
+
+static NTSTATUS smb1_mkdir(struct cli_state *cli,
+ const char *path)
+{
+ uint8_t *bytes = NULL;
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return cli_smb(talloc_tos(),
+ cli,
+ SMBmkdir, /* command. */
+ 0, /* additional_flags. */
+ 0, /* wct. */
+ NULL, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 0, /* min_wct. */
+ NULL, /* return wcount. */
+ NULL, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+}
+
+static bool test_smb1_mkdir(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir");
+
+ status = smb1_mkdir(cli, "dir");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ printf("%s:%d SMB1mkdir of %s should get "
+ "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n",
+ __FILE__,
+ __LINE__,
+ "dir",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_mkdir(cli, "\\BAD\\dir");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ printf("%s:%d SMB1mkdir of %s should get "
+ "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\dir",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_mkdir(cli, "\\BAD\\BAD\\dir");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1mkdir on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\dir",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir");
+ return retval;
+}
+
+static NTSTATUS smb1_rmdir(struct cli_state *cli,
+ const char *path)
+{
+ uint8_t *bytes = NULL;
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return cli_smb(talloc_tos(),
+ cli,
+ SMBrmdir, /* command. */
+ 0, /* additional_flags. */
+ 0, /* wct. */
+ NULL, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 0, /* min_wct. */
+ NULL, /* return wcount. */
+ NULL, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+}
+
+static bool test_smb1_rmdir(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir");
+
+ status = smb1_mkdir(cli, "\\BAD\\BAD\\dir");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1rmdir on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\dir",
+ nt_errstr(status));
+ goto err;
+ }
+
+ status = smb1_rmdir(cli, "dir");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("%s:%d SMB1rmdir of %s should get "
+ "NT_STATUS_ACCESS_DENIED, got %s\n",
+ __FILE__,
+ __LINE__,
+ "dir",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_rmdir(cli, "\\BAD\\dir");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("%s:%d SMB1rmdir of %s should get "
+ "NT_STATUS_ACCESS_DENIED, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\dir",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_rmdir(cli, "\\BAD\\BAD\\dir");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1rmdir on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\dir",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\dir");
+ return retval;
+}
+
+static NTSTATUS smb1_ntcreatex(struct cli_state *cli,
+ const char *path)
+{
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+
+ status = smb1cli_ntcreatex(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ path,
+ OPLOCK_NONE, /* CreatFlags */
+ 0, /* RootDirectoryFid */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* DesiredAccess */
+ 0, /* AllocationSize */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* ShareAccess */
+ FILE_CREATE, /* CreateDisposition */
+ 0, /* CreateOptions */
+ 2, /* ImpersonationLevel */
+ 0, /* SecurityFlags */
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Close "file" handle. */
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ return NT_STATUS_OK;
+}
+
+static bool test_smb1_ntcreatex(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ntcreateXfile");
+
+ status = smb1_ntcreatex(cli, "ntcreateXfile");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ printf("%s:%d SMB1ntcreateX of %s should get "
+ "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n",
+ __FILE__,
+ __LINE__,
+ "ntcreateXfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_ntcreatex(cli, "\\BAD\\ntcreateXfile");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ printf("%s:%d SMB1ntcreateX of %s should get "
+ "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\ntcreateXfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_ntcreatex(cli, "\\BAD\\BAD\\ntcreateXfile");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1ntcreateX on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\ntcreateXfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ntcreateXfile");
+ return retval;
+}
+
+static NTSTATUS smb1_nttrans_create(struct cli_state *cli,
+ const char *path)
+{
+ uint8_t *param = NULL;
+ size_t converted_len = 0;
+ uint8_t *rparam = NULL;
+ uint32_t num_rparam = 0;
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+
+ param = talloc_zero_array(talloc_tos(), uint8_t, 53);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ param = trans2_bytes_push_str(param,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path),
+ &converted_len);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ PUSH_LE_U32(param, 8, SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE); /* DesiredAccess */
+ PUSH_LE_U32(param, 20, FILE_ATTRIBUTE_NORMAL);
+ PUSH_LE_U32(param, 24, FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE); /* ShareAccess */
+ PUSH_LE_U32(param, 28, FILE_CREATE);
+ PUSH_LE_U32(param, 44, converted_len);
+ PUSH_LE_U32(param, 48, 0x02); /* ImpersonationLevel */
+
+ status = cli_trans(talloc_tos(),
+ cli,
+ SMBnttrans, /* trans cmd */
+ NULL, /* pipe_name */
+ 0, /* fid */
+ NT_TRANSACT_CREATE, /* function */
+ 0, /* flags */
+ NULL, /* setup */
+ 0, /* num_setup */
+ 0, /* max_setup */
+ param, /* param */
+ talloc_get_size(param), /* num_param */
+ 128, /* max_param */
+ NULL, /* data */
+ 0, /* num_data */
+ 0, /* max_data */
+ NULL, /* recv_flags2 */
+ NULL, /* rsetup */
+ 0, /* min_rsetup */
+ NULL, /* num_rsetup */
+ &rparam, /* rparam */
+ 69, /* min_rparam */
+ &num_rparam, /* num_rparam */
+ NULL, /* rdata */
+ 0, /* min_rdata */
+ NULL); /* num_rdata */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ fnum = PULL_LE_U16(param, 2);
+ /* Close "file" handle. */
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ return NT_STATUS_OK;
+}
+
+static bool test_smb1_nttrans_create(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\nttransfile");
+
+ status = smb1_nttrans_create(cli, "nttransfile");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ printf("%s:%d SMB1trans NT_TRANSACT_CREATE of %s should get "
+ "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n",
+ __FILE__,
+ __LINE__,
+ "nttransfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_nttrans_create(cli, "\\BAD\\nttransfile");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ printf("%s:%d SMB1trans NT_TRANSACT_CREATE of %s should get "
+ "NT_STATUS_OBJECT_NAME_COLLISION, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\nttransfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_nttrans_create(cli, "\\BAD\\BAD\\nttransfile");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1trans NT_TRANSACT_CREATE on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\nttransfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\nttransfile");
+ return retval;
+}
+
+struct smb1_openx_state {
+ const char *fname;
+ uint16_t vwv[15];
+ uint16_t fnum;
+ struct iovec bytes;
+};
+
+static void smb1_openx_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb1_openx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *path)
+{
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+ uint16_t accessmode = 0;
+ struct smb1_openx_state *state = NULL;
+ uint8_t *bytes = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct smb1_openx_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ accessmode = (DENY_NONE<<4);
+ accessmode |= DOS_OPEN_RDONLY;
+
+ PUSH_LE_U8(state->vwv + 0, 0, 0xFF);
+ PUSH_LE_U16(state->vwv + 3, 0, accessmode);
+ PUSH_LE_U16(state->vwv + 4, 0,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_DIRECTORY);
+ PUSH_LE_U16(state->vwv + 8,
+ 0,
+ OPENX_FILE_CREATE_IF_NOT_EXIST| OPENX_FILE_EXISTS_FAIL);
+
+ bytes = talloc_array(state, uint8_t, 0);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (tevent_req_nomem(bytes, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->bytes.iov_base = (void *)bytes;
+ state->bytes.iov_len = talloc_get_size(bytes);
+ subreq = cli_smb_req_create(state,
+ ev,
+ cli,
+ SMBopenX, /* cmd */
+ 0, /* additional_flags */
+ 0, /* additional_flags2 */
+ 15, /* num_vwv */
+ state->vwv, /* vwv */
+ 1, /* iovcount */
+ &state->bytes); /* iovec */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, smb1_openx_done, req);
+
+ status = smb1cli_req_chain_submit(&subreq, 1);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void smb1_openx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct smb1_openx_state *state = tevent_req_data(
+ req, struct smb1_openx_state);
+ uint8_t wct = 0;
+ uint16_t *vwv = NULL;
+ NTSTATUS status;
+
+ status = cli_smb_recv(subreq,
+ state,
+ NULL, /* pinbuf */
+ 3, /* min_wct */
+ &wct, /* wct */
+ &vwv, /* vwv */
+ NULL, /* num_rbytes */
+ NULL); /* rbytes */
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->fnum = PULL_LE_U16(vwv+2, 0);
+ tevent_req_done(req);
+}
+
+static NTSTATUS smb1_openx_recv(struct tevent_req *req, uint16_t *pfnum)
+{
+ struct smb1_openx_state *state = tevent_req_data(
+ req, struct smb1_openx_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pfnum = state->fnum;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS smb1_openx(struct cli_state *cli, const char *path)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = smb1_openx_send(frame,
+ ev,
+ cli,
+ path);
+ if (req == NULL) {
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = smb1_openx_recv(req, &fnum);
+ fail:
+
+ /* Close "file" handle. */
+ if (fnum != (uint16_t)-1) {
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ }
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static bool test_smb1_openx(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\openxfile");
+
+ status = smb1_openx(cli, "openxfile");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1openx of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "openxfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_openx(cli, "\\BAD\\openxfile");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1openx of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\openxfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_openx(cli, "\\BAD\\BAD\\openxfile");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1openx on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\openxfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\openxfile");
+ return retval;
+}
+
+static NTSTATUS smb1_open(struct cli_state *cli,
+ const char *path,
+ uint16_t *pfnum)
+{
+ uint16_t vwv[2] = { 0, 0};
+ uint8_t *bytes = NULL;
+ uint16_t accessmode = 0;
+ uint16_t *return_words = NULL;
+ uint8_t return_wcount = 0;
+ NTSTATUS status;
+
+ accessmode = (DENY_NONE<<4);
+ accessmode |= DOS_OPEN_RDONLY;
+
+ PUSH_LE_U16(vwv + 0, 0, accessmode);
+ PUSH_LE_U16(vwv + 1, 0, FILE_ATTRIBUTE_NORMAL);
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_smb(talloc_tos(),
+ cli,
+ SMBopen, /* command. */
+ 0, /* additional_flags. */
+ 2, /* wct. */
+ vwv, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 7, /* min_wct. */
+ &return_wcount, /* return wcount. */
+ &return_words, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *pfnum = PULL_LE_U16(return_words, 0);
+ return status;
+}
+
+static bool test_smb1_open(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ bool ok = false;
+ bool equal = false;
+ uint16_t fnum = (uint16_t)-1;
+ struct timespec testfile_crtime = { 0 };
+ struct timespec open_crtime = { 0 };
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\openfile");
+
+ /* Create a test file. */
+ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\openfile");
+ if (!ok) {
+ printf("%s:%d failed to create test file %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\openfile");
+ goto err;
+ }
+
+ /* Get the test file crtime number. */
+ status = get_smb1_crtime(cli,
+ "\\BAD\\BAD\\openfile",
+ &testfile_crtime);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get crtime for %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\openfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ status = smb1_open(cli, "openfile", &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1open of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "openfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_open(cli, "\\BAD\\openfile", &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1open of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\openfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_open(cli, "\\BAD\\BAD\\openfile", &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d failed to open test file %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\openfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ status = cli_qfileinfo_basic(cli,
+ fnum,
+ NULL, /* attr */
+ NULL, /* size */
+ &open_crtime, /* create_time */
+ NULL, /* access_time */
+ NULL, /* write_time */
+ NULL, /* change_time */
+ NULL); /* ino */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d failed to get crtime of test file %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\openfile",
+ nt_errstr(status));
+ goto err;
+ }
+ equal = (timespec_compare(&testfile_crtime, &open_crtime) == 0);
+ if (!equal) {
+ printf("%s:%d crtime mismatch of test file %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\openfile");
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ /* Close "openfile" handle. */
+ if (fnum != (uint16_t)-1) {
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ }
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\openfile");
+ return retval;
+}
+
+static NTSTATUS smb1_create(struct cli_state *cli,
+ const char *path,
+ uint16_t smb1_operation,
+ uint16_t *pfnum)
+{
+ uint16_t vwv[3] = { 0, 0, 0};
+ uint8_t *bytes = NULL;
+ uint16_t *return_words = NULL;
+ uint8_t return_wcount = 0;
+ NTSTATUS status;
+
+ PUSH_LE_U16(vwv + 0, 0, FILE_ATTRIBUTE_NORMAL);
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_smb(talloc_tos(),
+ cli,
+ smb1_operation, /* command. */
+ 0, /* additional_flags. */
+ 3, /* wct. */
+ vwv, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 1, /* min_wct. */
+ &return_wcount, /* return wcount. */
+ &return_words, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *pfnum = PULL_LE_U16(return_words, 0);
+ return status;
+}
+
+static bool test_smb1_create(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ uint16_t fnum = (uint16_t)-1;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\createfile");
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\mknewfile");
+
+ status = smb1_create(cli, "createfile", SMBcreate, &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1create of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "createfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_create(cli, "\\BAD\\createfile", SMBcreate, &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1open of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\openfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_create(cli, "\\BAD\\BAD\\createfile", SMBcreate, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d failed to create file %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\createfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+
+ fnum = (uint16_t)-1;
+
+ /* Now do the same with SMBmknew */
+ status = smb1_create(cli, "mknewfile", SMBmknew, &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1mknew of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "mknewfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_create(cli, "\\BAD\\mknewfile", SMBmknew, &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1mknew of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\mknewfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_create(cli, "\\BAD\\BAD\\mknewfile", SMBmknew, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d failed to create file %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\mknewfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+
+ fnum = (uint16_t)-1;
+
+ retval = true;
+
+ err:
+
+ /* Close "openfile" handle. */
+ if (fnum != (uint16_t)-1) {
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ }
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\createfile");
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\mknewfile");
+ return retval;
+}
+
+static NTSTATUS smb1_getatr(struct cli_state *cli,
+ const char *path,
+ uint16_t *pattr)
+{
+ uint8_t *bytes = NULL;
+ uint16_t *return_words = NULL;
+ uint8_t return_wcount = 0;
+ NTSTATUS status;
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_smb(talloc_tos(),
+ cli,
+ SMBgetatr, /* command. */
+ 0, /* additional_flags. */
+ 0, /* wct. */
+ NULL, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 10, /* min_wct. */
+ &return_wcount, /* return wcount. */
+ &return_words, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *pattr = PULL_LE_U16(return_words, 0);
+ return status;
+}
+
+static bool test_smb1_getatr(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ bool ok = false;
+ uint16_t attrs = 0;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\getatrfile");
+
+ /* Create a test file. */
+ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\getatrfile");
+ if (!ok) {
+ printf("%s:%d failed to create test file %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\getatrfile");
+ goto err;
+ }
+
+ /*
+ * We expect this to succeed, but get attributes of
+ * the root directory.
+ */
+ status = smb1_getatr(cli, "getatrfile", &attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1getatr of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "getatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+ if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+ printf("%s:%d error expected SMB1getatr of file %s "
+ "to return directory attributes. Got 0x%x\n",
+ __FILE__,
+ __LINE__,
+ "getatrfile",
+ (unsigned int)attrs);
+ goto err;
+ }
+
+ /*
+ * We expect this to succeed, but get attributes of
+ * the root directory.
+ */
+ status = smb1_getatr(cli, "\\BAD\\getatrfile", &attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1getatr of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\getatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+ if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+ printf("%s:%d error expected SMB1getatr of file %s "
+ "to return directory attributes. Got 0x%x\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\getatrfile",
+ (unsigned int)attrs);
+ goto err;
+ }
+
+ /*
+ * We expect this to succeed, and get attributes of
+ * the testfile.
+ */
+ status = smb1_getatr(cli, "\\BAD\\BAD\\getatrfile", &attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1getatr of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\getatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+ if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
+ printf("%s:%d error expected SMB1getatr of file %s "
+ "to return non-directory attributes. Got 0x%x\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\getatrfile",
+ (unsigned int)attrs);
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\getatrfile");
+ return retval;
+}
+
+static NTSTATUS smb1_setatr(struct cli_state *cli,
+ const char *path,
+ uint16_t attr)
+{
+ uint16_t vwv[8] = { 0 };
+ uint8_t *bytes = NULL;
+ NTSTATUS status;
+
+ PUSH_LE_U16(vwv, 0, attr);
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = cli_smb(talloc_tos(),
+ cli,
+ SMBsetatr, /* command. */
+ 0, /* additional_flags. */
+ 8, /* wct. */
+ vwv, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 0, /* min_wct. */
+ NULL, /* return wcount. */
+ NULL, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return status;
+}
+
+static bool test_smb1_setatr(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ bool ok = false;
+ uint16_t file_attrs = 0;
+ uint16_t orig_file_attrs = 0;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\setatrfile");
+
+ /* Create a test file. */
+ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\setatrfile");
+ if (!ok) {
+ printf("%s:%d failed to create test file %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\setatrfile");
+ goto err;
+ }
+ /* Get it's original attributes. */
+ status = smb1_getatr(cli, "\\BAD\\BAD\\setatrfile", &orig_file_attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1getatr of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\setatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ if (orig_file_attrs & FILE_ATTRIBUTE_SYSTEM) {
+ printf("%s:%d orig_file_attrs of %s already has SYSTEM. "
+ "Test cannot proceed.\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\setatrfile");
+ goto err;
+ }
+
+ /*
+ * Seems we can't set attrs on the root of a share,
+ * even as Administrator.
+ */
+ status = smb1_setatr(cli, "setatrfile", FILE_ATTRIBUTE_SYSTEM);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("%s:%d SMB1setatr of %s should get "
+ "NT_STATUS_ACCESS_DENIED, got %s\n",
+ __FILE__,
+ __LINE__,
+ "setatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Seems we can't set attrs on the root of a share,
+ * even as Administrator.
+ */
+ status = smb1_setatr(cli, "\\BAD\\setatrfile", FILE_ATTRIBUTE_SYSTEM);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("%s:%d SMB1setatr of %s should get "
+ "NT_STATUS_ACCESS_DENIED, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\setatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ status = smb1_setatr(cli,
+ "\\BAD\\BAD\\setatrfile",
+ FILE_ATTRIBUTE_SYSTEM);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1setatr of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\setatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_getatr(cli, "\\BAD\\BAD\\setatrfile", &file_attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1getatr of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\setatrfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ if (file_attrs != FILE_ATTRIBUTE_SYSTEM) {
+ printf("%s:%d Failed to set SYSTEM attr on %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\setatrfile");
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\setatrfile");
+ return retval;
+}
+
+static NTSTATUS smb1_chkpath(struct cli_state *cli,
+ const char *path)
+{
+ uint8_t *bytes = NULL;
+ NTSTATUS status;
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = cli_smb(talloc_tos(),
+ cli,
+ SMBcheckpath, /* command. */
+ 0, /* additional_flags. */
+ 0, /* wct. */
+ NULL, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 0, /* min_wct. */
+ NULL, /* return wcount. */
+ NULL, /* return wvw. */
+ NULL, /* return byte count. */
+ NULL); /* return bytes. */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return status;
+}
+
+static bool test_smb1_chkpath(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ bool ok = false;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\chkpathfile");
+
+ /* Create a test file. */
+ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\chkpathfile");
+ if (!ok) {
+ printf("%s:%d failed to create test file %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\chkpathfile");
+ goto err;
+ }
+ /*
+ * Should succeed - "chkpathfile" maps to
+ * directory "".
+ */
+ status = smb1_chkpath(cli, "chkpathfile");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1chkpath of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "chkpathfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Should succeed - "\\BAD\\chkpathfile" maps to
+ * directory "".
+ */
+ status = smb1_chkpath(cli, "\\BAD\\chkpathfile");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1chkpath of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\chkpathfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Should fail - "\\BAD\\BAD\\chkpathfile" maps to the
+ * "\\BAD\\BAD\\chkpathfile", not a directory.
+ */
+ status = smb1_chkpath(cli, "\\BAD\\BAD\\chkpathfile");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_A_DIRECTORY)) {
+ printf("%s:%d SMB1chkpath of %s should get "
+ "NT_STATUS_NOT_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\chkpathfile",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\chkpathfile");
+ return retval;
+}
+
+/*
+ * Test BUG: https://bugzilla.samba.org/show_bug.cgi?id=15419
+ */
+
+static bool test_smb1_chkpath_bad(struct cli_state *cli)
+{
+ NTSTATUS status;
+
+ status = smb1_chkpath(cli, "\\x//\\/");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d SMB1chkpath of %s failed (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\x//\\/",
+ nt_errstr(status));
+ return false;
+ }
+ return true;
+}
+
+static NTSTATUS smb1_ctemp(struct cli_state *cli,
+ const char *path,
+ char **tmp_path)
+{
+ uint16_t vwv[3] = { 0 };
+ uint8_t *bytes = NULL;
+ NTSTATUS status;
+ uint16_t *return_words = NULL;
+ uint8_t return_wcount = 0;
+ uint32_t return_bytecount = 0;
+ uint8_t *return_bytes = NULL;
+ size_t sret = 0;
+ uint16_t fnum = (uint16_t)-1;
+
+ bytes = talloc_array(talloc_tos(), uint8_t, 1);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ bytes[0] = 4;
+ bytes = smb_bytes_push_str(bytes,
+ smbXcli_conn_use_unicode(cli->conn),
+ path,
+ strlen(path)+1,
+ NULL);
+ if (bytes == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = cli_smb(talloc_tos(),
+ cli,
+ SMBctemp, /* command. */
+ 0, /* additional_flags. */
+ 3, /* wct. */
+ vwv, /* vwv. */
+ talloc_get_size(bytes), /* num_bytes. */
+ bytes, /* bytes. */
+ NULL, /* result parent. */
+ 1, /* min_wct. */
+ &return_wcount, /* return wcount. */
+ &return_words, /* return wvw. */
+ &return_bytecount, /* return byte count. */
+ &return_bytes); /* return bytes. */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (return_wcount != 1) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ fnum = PULL_LE_U16(return_words, 0);
+
+ /* Delete the file by fnum. */
+ status = cli_nt_delete_on_close(cli, fnum, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ (void)smb1cli_close(cli->conn,
+ cli->timeout,
+ cli->smb1.pid,
+ cli->smb1.tcon,
+ cli->smb1.session,
+ fnum,
+ 0); /* last_modified */
+ fnum = (uint16_t)-1;
+
+ if (return_bytecount < 2) {
+ return NT_STATUS_DATA_ERROR;
+ }
+
+ sret = pull_string_talloc(talloc_tos(),
+ NULL,
+ 0,
+ tmp_path,
+ return_bytes,
+ return_bytecount,
+ STR_ASCII);
+ if (sret == 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return status;
+}
+
+static bool test_smb1_ctemp(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ char *retpath = NULL;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ctemp_dir");
+
+ status = smb1_mkdir(cli, "\\BAD\\BAD\\ctemp_dir");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to create %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\ctemp_dir",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Windows returns NT_STATUS_FILE_IS_A_DIRECTORY
+ * for all SMBctemp calls on a DFS share, no
+ * matter what we put in the pathname.
+ */
+
+ /*
+ * When we fix smbd we'll need to detect running
+ * in smbtorture3 against smbd here and modify
+ * the expected behavior. Windows is simply
+ * broken here.
+ */
+ status = smb1_ctemp(cli, "ctemp_dir", &retpath);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1ctemp of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "ctemp_dir",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_ctemp(cli, "\\BAD\\ctemp_dir", &retpath);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1ctemp of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\ctemp_dir",
+ nt_errstr(status));
+ goto err;
+ }
+ status = smb1_ctemp(cli, "\\BAD\\BAD\\ctemp_dir", &retpath);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ printf("%s:%d SMB1ctemp of %s should get "
+ "NT_STATUS_FILE_IS_A_DIRECTORY, got %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\ctemp_dir",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\ctemp_dir");
+ return retval;
+}
+
+static NTSTATUS smb1_qpathinfo(struct cli_state *cli,
+ const char *fname,
+ uint32_t *pattrs)
+{
+ NTSTATUS status;
+ uint8_t *param = NULL;
+ uint16_t setup[1] = { 0 };
+ uint8_t *rdata = NULL;
+ uint32_t num_rdata = 0;
+
+ PUSH_LE_U16(setup, 0, TRANSACT2_QPATHINFO);
+
+ param = talloc_zero_array(talloc_tos(), uint8_t, 6);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ PUSH_LE_U16(param, 0, SMB_QUERY_FILE_BASIC_INFO);
+
+ param = trans2_bytes_push_str(param,
+ smbXcli_conn_use_unicode(cli->conn),
+ fname,
+ strlen(fname)+1,
+ NULL);
+ if (param == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_trans(talloc_tos(),
+ cli,
+ SMBtrans2, /* cmd */
+ NULL, /* pipe_name */
+ 0, /* fid */
+ 0, /* function */
+ 0, /* flags */
+ &setup[0],
+ 1, /* num_setup uint16_t words */
+ 0, /* max returned setup */
+ param,
+ talloc_get_size(param), /* num_param */
+ 2, /* max returned param */
+ NULL, /* data */
+ 0, /* num_data */
+ SMB_BUFFER_SIZE_MAX, /* max returned data */
+ /* Return values from here on.. */
+ NULL, /* recv_flags2 */
+ NULL, /* rsetup */
+ 0, /* min returned rsetup */
+ NULL, /* num_rsetup */
+ NULL,
+ 0, /* min returned rparam */
+ NULL, /* number of returned rparam */
+ &rdata,
+ 36, /* min returned rdata */
+ &num_rdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *pattrs = PULL_LE_U32(rdata, 32);
+ return NT_STATUS_OK;
+}
+
+static bool test_smb1_qpathinfo(struct cli_state *cli)
+{
+ NTSTATUS status;
+ bool retval = false;
+ bool ok = false;
+ uint32_t attrs;
+
+ /* Start clean. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\qpathinfo_file");
+
+ /* Create a test file. */
+ ok = smb1_create_testfile(cli, "\\BAD\\BAD\\qpathinfo_file");
+ if (!ok) {
+ printf("%s:%d failed to create test file %s\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\qpathinfo_file");
+ goto err;
+ }
+
+ /* Should get root dir attrs. */
+ status = smb1_qpathinfo(cli, "qpathinfo_file", &attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1_qpathinfo failed %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "qpathinfo_file",
+ nt_errstr(status));
+ goto err;
+ }
+ if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+ printf("%s:%d expected FILE_ATTRIBUTE_DIRECTORY on %s "
+ "got attribute 0x%x\n",
+ __FILE__,
+ __LINE__,
+ "qpathinfo_file",
+ (unsigned int)attrs);
+ goto err;
+ }
+
+ /* Should get root dir attrs. */
+ status = smb1_qpathinfo(cli, "\\BAD\\qpathinfo_file", &attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1_qpathinfo failed %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\qpathinfo_file",
+ nt_errstr(status));
+ goto err;
+ }
+ if ((attrs & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+ printf("%s:%d expected FILE_ATTRIBUTE_DIRECTORY on %s "
+ "got attribute 0x%x\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\qpathinfo_file",
+ (unsigned int)attrs);
+ goto err;
+ }
+
+ /* Should get file attrs. */
+ status = smb1_qpathinfo(cli, "\\BAD\\BAD\\qpathinfo_file", &attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb1_qpathinfo failed %s (%s)\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\qpathinfo_file",
+ nt_errstr(status));
+ goto err;
+ }
+ if ((attrs & FILE_ATTRIBUTE_DIRECTORY) != 0) {
+ printf("%s:%d expected not FILE_ATTRIBUTE_DIRECTORY on %s "
+ "got attribute 0x%x\n",
+ __FILE__,
+ __LINE__,
+ "\\BAD\\BAD\\qpathinfo_file",
+ (unsigned int)attrs);
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\qpathinfo_file");
+ return retval;
+}
+
+/*
+ * "Raw" test of different SMB1 operations to a DFS share.
+ * We must (mostly) use the lower level smb1cli_XXXX() interfaces,
+ * not the cli_XXX() ones here as the ultimate goal is to fix our
+ * cli_XXX() interfaces to work transparently over DFS.
+ *
+ * So here, we're testing the server code, not the client code.
+ *
+ * Passes cleanly against Windows.
+ */
+
+bool run_smb1_dfs_operations(int dummy)
+{
+ struct cli_state *cli = NULL;
+ bool dfs_supported = false;
+ bool retval = false;
+ bool ok = false;
+
+ printf("Starting SMB1-DFS-OPS\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ /* Ensure this is a DFS share. */
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb1.tcon);
+ if (!dfs_supported) {
+ printf("Share %s does not support DFS\n",
+ cli->share);
+ return false;
+ }
+
+ ok = test_smb1_unlink(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_mkdir(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_rmdir(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_ntcreatex(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_nttrans_create(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_openx(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_open(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_create(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_getatr(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_setatr(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_chkpath(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_ctemp(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ ok = test_smb1_qpathinfo(cli);
+ if (!ok) {
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ /* Delete anything we made. */
+ (void)smb1_dfs_delete(cli, "\\BAD\\BAD\\file");
+ return retval;
+}
+
+/*
+ * Test BUG: https://bugzilla.samba.org/show_bug.cgi?id=15419
+ */
+
+bool run_smb1_dfs_check_badpath(int dummy)
+{
+ struct cli_state *cli = NULL;
+ bool dfs_supported = false;
+
+ printf("Starting SMB1-DFS-CHECK-BADPATH\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ /* Ensure this is a DFS share. */
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb1.tcon);
+ if (!dfs_supported) {
+ printf("Share %s does not support DFS\n",
+ cli->share);
+ return false;
+ }
+
+ return test_smb1_chkpath_bad(cli);
+}
diff --git a/source3/torture/test_smb2.c b/source3/torture/test_smb2.c
new file mode 100644
index 0000000..3b2e1e4
--- /dev/null
+++ b/source3/torture/test_smb2.c
@@ -0,0 +1,5471 @@
+/*
+ Unix SMB/CIFS implementation.
+ Initial test for the smb2 client lib
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+#include "client.h"
+#include "trans2.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/security/security.h"
+#include "libsmb/proto.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+#include "auth_generic.h"
+#include "../librpc/ndr/libndr.h"
+#include "libsmb/clirap.h"
+#include "libsmb/cli_smb2_fnum.h"
+
+extern fstring host, workgroup, share, password, username, myname;
+extern struct cli_credentials *torture_creds;
+
+bool run_smb2_basic(int dummy)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+ uint64_t fid_persistent, fid_volatile;
+ const char *hello = "Hello, world\n";
+ uint8_t *result;
+ uint32_t nread;
+ uint8_t *dir_data;
+ uint32_t dir_data_length;
+ uint32_t saved_tid = 0;
+ struct smbXcli_tcon *saved_tcon = NULL;
+ char *saved_share = NULL;
+ uint64_t saved_uid = 0;
+
+ printf("Starting SMB2-BASIC\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB2_02,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "smb2-basic.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_write(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, strlen(hello), 0, fid_persistent,
+ fid_volatile, 0, 0, (const uint8_t *)hello, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_write returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_read(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ if (nread != strlen(hello)) {
+ printf("smb2cli_read returned %d bytes, expected %d\n",
+ (int)nread, (int)strlen(hello));
+ return false;
+ }
+
+ if (memcmp(hello, result, nread) != 0) {
+ printf("smb2cli_read returned '%s', expected '%s'\n",
+ result, hello);
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_DIR_LIST|
+ SEC_DIR_READ_ATTRIBUTE, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_SYNCHRONOUS_IO_NONALERT|
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0, 0, fid_persistent, fid_volatile, "*", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ saved_tid = smb2cli_tcon_current_id(cli->smb2.tcon);
+ cli_state_save_tcon_share(cli, &saved_tcon, &saved_share);
+ cli->smb2.tcon = smbXcli_tcon_create(cli);
+ smb2cli_tcon_set_values(cli->smb2.tcon,
+ NULL, /* session */
+ saved_tid,
+ 0, /* type */
+ 0, /* flags */
+ 0, /* capabilities */
+ 0 /* maximal_access */);
+ status = smb2cli_tdis(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon);
+ cli_state_restore_tcon_share(cli, saved_tcon, saved_share);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_tdis returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_tdis(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED)) {
+ printf("2nd smb2cli_tdis returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ saved_uid = smb2cli_session_current_id(cli->smb2.session);
+ status = smb2cli_logoff(cli->conn, cli->timeout, cli->smb2.session);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_logoff returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ cli->smb2.session = smbXcli_session_create(cli, cli->conn);
+ if (cli->smb2.session == NULL) {
+ printf("smbXcli_session_create() returned NULL\n");
+ return false;
+ }
+
+ smb2cli_session_set_id_and_flags(cli->smb2.session, saved_uid, 0);
+
+ status = smb2cli_logoff(cli->conn, cli->timeout, cli->smb2.session);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ printf("2nd smb2cli_logoff returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+bool run_smb2_negprot(int dummy)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+ enum protocol_types protocol;
+ const char *name = NULL;
+
+ printf("Starting SMB2-NEGPROT\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_CORE,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ protocol = smbXcli_conn_protocol(cli->conn);
+ name = smb_protocol_types_string(protocol);
+
+ if (protocol >= PROTOCOL_SMB2_02) {
+ printf("Server supports %s\n", name);
+ } else {
+ printf("Server DOES NOT support SMB2, only %s\n", name);
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ protocol,
+ protocol,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_RESET) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_DISCONNECTED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_ABORTED)) {
+ printf("2nd smbXcli_negprot should disconnect - returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (smbXcli_conn_is_connected(cli->conn)) {
+ printf("2nd smbXcli_negprot should disconnect "
+ "- still connected\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool run_smb2_anonymous(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ struct cli_credentials *anon_creds = NULL;
+ bool guest = false;
+
+ printf("Starting SMB2-ANONYMOUS\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ anon_creds = cli_credentials_init_anon(talloc_tos());
+ if (anon_creds == NULL) {
+ printf("cli_credentials_init_anon failed\n");
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, anon_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ guest = smbXcli_session_is_guest(cli->smb2.session);
+ if (guest) {
+ printf("anonymous session should not have guest authentication\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool run_smb2_session_reconnect(int dummy)
+{
+ struct cli_state *cli1;
+ struct cli_state *cli2;
+ NTSTATUS status;
+ bool ok;
+ uint64_t fid_persistent, fid_volatile;
+ struct tevent_context *ev;
+ struct tevent_req *subreq;
+ DATA_BLOB in_blob = data_blob_null;
+ DATA_BLOB out_blob;
+ DATA_BLOB session_key;
+ struct auth_generic_state *auth_generic_state;
+ struct iovec *recv_iov;
+ const char *hello = "Hello, world\n";
+ uint8_t *result;
+ uint32_t nread;
+
+ printf("Starting SMB2-SESSION-RECONNECT\n");
+
+ if (!torture_init_connection(&cli1)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli1->conn,
+ cli1->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli1, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli1, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli1->conn,
+ cli1->timeout,
+ cli1->smb2.session,
+ cli1->smb2.tcon,
+ "session-reconnect.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create on cli1 %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_write(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, strlen(hello), 0, fid_persistent,
+ fid_volatile, 0, 0, (const uint8_t *)hello, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_write returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_read(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ if (nread != strlen(hello)) {
+ printf("smb2cli_read returned %d bytes, expected %d\n",
+ (int)nread, (int)strlen(hello));
+ return false;
+ }
+
+ if (memcmp(hello, result, nread) != 0) {
+ printf("smb2cli_read returned '%s', expected '%s'\n",
+ result, hello);
+ return false;
+ }
+
+ /* prepare second session */
+
+ if (!torture_init_connection(&cli2)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli2->conn,
+ cli2->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_prepare(talloc_tos(), &auth_generic_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_prepare returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ gensec_want_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+
+ status = auth_generic_set_creds(auth_generic_state, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_set_creds returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_start(auth_generic_state, GENSEC_OID_NTLMSSP);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_start returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("samba_tevent_context_init() returned NULL\n");
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), data_blob_null, &in_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("gensec_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ cli2->smb2.session = smbXcli_session_create(cli2, cli2->conn);
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ 0x0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ /* in_previous_session_id: */
+ smb2cli_session_current_id(cli1->smb2.session),
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ NULL, &out_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), out_blob, &in_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ 0x0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ /* in_previous_session_id: */
+ smb2cli_session_current_id(cli1->smb2.session),
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ &recv_iov, &out_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_session_key(auth_generic_state->gensec_security, talloc_tos(),
+ &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("gensec_session_key returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* check file operation on the old client */
+
+ status = smb2cli_flush(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli1, share, "?????", NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * checking file operations without signing.
+ * on w2k8r2 at least, flush, read and write also work the same way,
+ * while create gives ACCESS_DENIED without signing
+ */
+ status = smb2cli_flush(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli2->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED))
+ {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_write(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli2->smb2.tcon, strlen(hello), 0, fid_persistent,
+ fid_volatile, 0, 0, (const uint8_t *)hello, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED))
+ {
+ printf("smb2cli_write returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_read(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli2->smb2.tcon, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED))
+ {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ cli2->smb2.tcon,
+ "session-reconnect.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED)) {
+ printf("smb2cli_create on cli2 %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* now grab the session key and try with signing */
+
+ status = smb2cli_session_set_session_key(cli2->smb2.session,
+ session_key,
+ recv_iov);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_set_session_key %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* the tid seems to be irrelevant at this stage */
+
+ status = smb2cli_flush(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED))
+ {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_write(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli1->smb2.tcon, strlen(hello), 0, fid_persistent,
+ fid_volatile, 0, 0, (const uint8_t *)hello, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED))
+ {
+ printf("smb2cli_write returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_read(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli1->smb2.tcon, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED))
+ {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ cli1->smb2.tcon,
+ "session-reconnect.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED))
+ {
+ printf("smb2cli_create on cli2 %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* now do a new tcon and test file calls again */
+
+ status = cli_tree_connect(cli2, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ cli2->smb2.tcon,
+ "session-reconnect.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create on cli2 %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_write(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli2->smb2.tcon, strlen(hello), 0, fid_persistent,
+ fid_volatile, 0, 0, (const uint8_t *)hello, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_write returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli2->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_read(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli2->smb2.tcon, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ if (nread != strlen(hello)) {
+ printf("smb2cli_read returned %d bytes, expected %d\n",
+ (int)nread, (int)strlen(hello));
+ return false;
+ }
+
+ if (memcmp(hello, result, nread) != 0) {
+ printf("smb2cli_read returned '%s', expected '%s'\n",
+ result, hello);
+ return false;
+ }
+
+ return true;
+}
+
+bool run_smb2_tcon_dependence(int dummy)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+ uint64_t fid_persistent, fid_volatile;
+ const char *hello = "Hello, world\n";
+ uint8_t *result;
+ uint32_t nread;
+ struct smbXcli_tcon *tcon2;
+ uint32_t tcon2_id;
+
+ printf("Starting SMB2-TCON-DEPENDENCE\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "tcon_depedence.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create on cli %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_write(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, strlen(hello), 0, fid_persistent,
+ fid_volatile, 0, 0, (const uint8_t *)hello, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_write returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_read(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ if (nread != strlen(hello)) {
+ printf("smb2cli_read returned %d bytes, expected %d\n",
+ (int)nread, (int)strlen(hello));
+ return false;
+ }
+
+ if (memcmp(hello, result, nread) != 0) {
+ printf("smb2cli_read returned '%s', expected '%s'\n",
+ result, hello);
+ return false;
+ }
+
+ /* check behaviour with wrong tid... */
+
+ tcon2 = smbXcli_tcon_create(cli);
+ tcon2_id = smb2cli_tcon_current_id(cli->smb2.tcon);
+ tcon2_id++;
+ smb2cli_tcon_set_values(tcon2,
+ NULL, /* session */
+ tcon2_id,
+ 0, /* type */
+ 0, /* flags */
+ 0, /* capabilities */
+ 0 /* maximal_access */);
+
+ status = smb2cli_read(cli->conn, cli->timeout, cli->smb2.session,
+ tcon2, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED)) {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ talloc_free(tcon2);
+
+ return true;
+}
+
+bool run_smb2_multi_channel(int dummy)
+{
+ struct cli_state *cli1;
+ struct cli_state *cli2;
+ struct cli_state *cli3;
+ NTSTATUS status;
+ bool ok;
+ uint64_t fid_persistent, fid_volatile;
+ struct tevent_context *ev;
+ struct tevent_req *subreq;
+ DATA_BLOB in_blob = data_blob_null;
+ DATA_BLOB out_blob;
+ DATA_BLOB channel_session_key;
+ struct auth_generic_state *auth_generic_state;
+ struct iovec *recv_iov;
+ const char *hello = "Hello, world\n";
+ uint8_t *result;
+ uint32_t nread;
+ struct GUID saved_guid = cli_state_client_guid;
+
+ printf("Starting SMB2-MULTI-CHANNEL\n");
+
+ cli_state_client_guid = GUID_random();
+
+ if (!torture_init_connection(&cli1)) {
+ return false;
+ }
+
+ if (!torture_init_connection(&cli2)) {
+ return false;
+ }
+
+ if (!torture_init_connection(&cli3)) {
+ return false;
+ }
+
+ cli_state_client_guid = saved_guid;
+
+ status = smbXcli_negprot(cli1->conn,
+ cli1->timeout,
+ PROTOCOL_SMB3_00,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smbXcli_negprot(cli2->conn,
+ cli2->timeout,
+ PROTOCOL_SMB3_00,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smbXcli_negprot(cli3->conn,
+ cli3->timeout,
+ PROTOCOL_SMB3_00,
+ PROTOCOL_LATEST,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli1, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_sesssetup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli1, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_session_create_channel(cli2,
+ cli1->smb2.session,
+ cli2->conn,
+ &cli2->smb2.session);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_create_channel returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_prepare(talloc_tos(), &auth_generic_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_prepare returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ gensec_want_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+
+ status = auth_generic_set_creds(auth_generic_state, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_set_creds returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_start(auth_generic_state, GENSEC_OID_NTLMSSP);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_start returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("samba_tevent_context_init() returned NULL\n");
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), data_blob_null, &in_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("gensec_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ 0x01, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ NULL, &out_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), out_blob, &in_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ 0x01, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ &recv_iov, &out_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_session_key(auth_generic_state->gensec_security, talloc_tos(),
+ &channel_session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("gensec_session_key returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_session_set_channel_key(cli2->smb2.session,
+ channel_session_key,
+ recv_iov);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_set_channel_key %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_session_create_channel(cli3,
+ cli1->smb2.session,
+ cli3->conn,
+ &cli3->smb2.session);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_create_channel returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_prepare(talloc_tos(), &auth_generic_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_prepare returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ gensec_want_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+
+ status = auth_generic_set_creds(auth_generic_state, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_set_creds returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_start(auth_generic_state, GENSEC_OID_NTLMSSP);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_start returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), data_blob_null, &in_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("gensec_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli3->conn,
+ cli3->timeout,
+ cli3->smb2.session,
+ 0x01, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ NULL, &out_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), out_blob, &in_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli3->conn,
+ cli3->timeout,
+ cli3->smb2.session,
+ 0x01, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ &recv_iov, &out_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_session_key(auth_generic_state->gensec_security, talloc_tos(),
+ &channel_session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("gensec_session_key returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_session_set_channel_key(cli3->smb2.session,
+ channel_session_key,
+ recv_iov);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_set_channel_key %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ cli1->smb2.tcon,
+ "multi-channel.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create on cli2 %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_write(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, strlen(hello), 0, fid_persistent,
+ fid_volatile, 0, 0, (const uint8_t *)hello, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_write returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli3->conn, cli3->timeout, cli3->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_read(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli1->smb2.tcon, 0x10000, 0, fid_persistent,
+ fid_volatile, 2, 0,
+ talloc_tos(), &result, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_read returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ if (nread != strlen(hello)) {
+ printf("smb2cli_read returned %d bytes, expected %d\n",
+ (int)nread, (int)strlen(hello));
+ return false;
+ }
+
+ if (memcmp(hello, result, nread) != 0) {
+ printf("smb2cli_read returned '%s', expected '%s'\n",
+ result, hello);
+ return false;
+ }
+
+ status = auth_generic_client_prepare(talloc_tos(), &auth_generic_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_prepare returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ gensec_want_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+
+ status = auth_generic_set_creds(auth_generic_state, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_set_creds returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_start(auth_generic_state, GENSEC_OID_NTLMSSP);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_start returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), data_blob_null, &in_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("gensec_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli3->conn,
+ cli3->timeout,
+ cli3->smb2.session,
+ 0x0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ NULL, &out_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), out_blob, &in_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli3->conn, cli3->timeout, cli3->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli1->conn,
+ cli1->timeout,
+ cli1->smb2.session,
+ cli1->smb2.tcon,
+ "multi-channel-invalid.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_create %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ cli1->smb2.tcon,
+ "multi-channel-invalid.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_create %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli3->conn,
+ cli3->timeout,
+ cli3->smb2.session,
+ cli1->smb2.tcon,
+ "multi-channel-invalid.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_create %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli2->conn,
+ cli2->timeout,
+ cli2->smb2.session,
+ 0x0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ &recv_iov, &out_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli3->conn, cli3->timeout, cli3->smb2.session,
+ cli1->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli3->conn, cli3->timeout, cli3->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli2->conn, cli2->timeout, cli2->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli1->conn, cli1->timeout, cli1->smb2.session,
+ cli1->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_CLOSED)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+bool run_smb2_session_reauth(int dummy)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+ bool ok;
+ uint64_t fid_persistent, fid_volatile;
+ uint64_t dir_persistent, dir_volatile;
+ uint8_t *dir_data;
+ uint32_t dir_data_length;
+ struct tevent_context *ev;
+ struct tevent_req *subreq;
+ DATA_BLOB in_blob = data_blob_null;
+ DATA_BLOB out_blob;
+ DATA_BLOB in_input_buffer;
+ DATA_BLOB out_output_buffer;
+ uint8_t in_file_info_class;
+ struct auth_generic_state *auth_generic_state;
+ struct iovec *recv_iov;
+ uint32_t saved_tid;
+ struct smbXcli_tcon *saved_tcon;
+
+ printf("Starting SMB2-SESSION_REAUTH\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ /*
+ * PROTOCOL_SMB2_22 has a bug in win8pre0
+ * it behaves like PROTOCOL_SMB2_02
+ * and returns NT_STATUS_REQUEST_NOT_ACCEPTED,
+ * while it allows it on PROTOCOL_SMB2_10.
+ */
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_10,
+ PROTOCOL_SMB2_10,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_sesssetup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "session-reauth.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_DIR_LIST|
+ SEC_DIR_READ_ATTRIBUTE, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_SYNCHRONOUS_IO_NONALERT|
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &dir_persistent,
+ &dir_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0x3, 0, dir_persistent, dir_volatile,
+ "session-reauth.txt", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_prepare(talloc_tos(), &auth_generic_state);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_prepare returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ gensec_want_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+
+ status = auth_generic_set_creds(auth_generic_state, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_set_creds returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = auth_generic_client_start(auth_generic_state, GENSEC_OID_NTLMSSP);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_client_start returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("samba_tevent_context_init() returned NULL\n");
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), data_blob_null, &in_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("gensec_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ 0x0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ NULL, &out_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), out_blob, &in_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("auth_generic_update returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0x3, 0, dir_persistent, dir_volatile,
+ "session-reauth.txt", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * query_info seems to be a path based operation on Windows...
+ */
+ status = smb2cli_query_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_SECURITY,
+ 0, /* in_file_info_class */
+ 1024, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ SECINFO_OWNER, /* in_additional_info */
+ 0, /* in_flags */
+ fid_persistent,
+ fid_volatile,
+ talloc_tos(),
+ &out_output_buffer);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_query_info (security) returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ in_file_info_class = SMB_FILE_POSITION_INFORMATION - 1000;
+ status = smb2cli_query_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE,
+ in_file_info_class,
+ 1024, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ fid_persistent,
+ fid_volatile,
+ talloc_tos(),
+ &out_output_buffer);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_query_info (position) returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ in_input_buffer = data_blob_talloc(talloc_tos(), NULL, 8);
+ SBVAL(in_input_buffer.data, 0, 512);
+
+ in_file_info_class = SMB_FILE_POSITION_INFORMATION - 1000;
+ status = smb2cli_set_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE,
+ in_file_info_class,
+ &in_input_buffer,
+ 0, /* in_additional_info */
+ fid_persistent,
+ fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_set_info (position) returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "session-reauth-invalid.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_create %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_DIR_LIST|
+ SEC_DIR_READ_ATTRIBUTE, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_SYNCHRONOUS_IO_NONALERT|
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &dir_persistent,
+ &dir_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("smb2cli_create returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ saved_tid = smb2cli_tcon_current_id(cli->smb2.tcon);
+ saved_tcon = cli->smb2.tcon;
+ cli->smb2.tcon = smbXcli_tcon_create(cli);
+ smb2cli_tcon_set_values(cli->smb2.tcon,
+ NULL, /* session */
+ saved_tid,
+ 0, /* type */
+ 0, /* flags */
+ 0, /* capabilities */
+ 0 /* maximal_access */);
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+ talloc_free(cli->smb2.tcon);
+ cli->smb2.tcon = saved_tcon;
+
+ subreq = smb2cli_session_setup_send(talloc_tos(), ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ 0x0, /* in_flags */
+ SMB2_CAP_DFS, /* in_capabilities */
+ 0, /* in_channel */
+ 0, /* in_previous_session_id */
+ &in_blob); /* in_security_buffer */
+ if (subreq == NULL) {
+ printf("smb2cli_session_setup_send() returned NULL\n");
+ return false;
+ }
+
+ ok = tevent_req_poll(subreq, ev);
+ if (!ok) {
+ printf("tevent_req_poll() returned false\n");
+ return false;
+ }
+
+ status = smb2cli_session_setup_recv(subreq, talloc_tos(),
+ &recv_iov, &out_blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_session_setup_recv returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_SECURITY,
+ 0, /* in_file_info_class */
+ 1024, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ SECINFO_OWNER, /* in_additional_info */
+ 0, /* in_flags */
+ fid_persistent,
+ fid_volatile,
+ talloc_tos(),
+ &out_output_buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_info (security) returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ in_file_info_class = SMB_FILE_POSITION_INFORMATION - 1000;
+ status = smb2cli_query_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE,
+ in_file_info_class,
+ 1024, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ fid_persistent,
+ fid_volatile,
+ talloc_tos(),
+ &out_output_buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_info (position) returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ in_input_buffer = data_blob_talloc(talloc_tos(), NULL, 8);
+ SBVAL(in_input_buffer.data, 0, 512);
+
+ in_file_info_class = SMB_FILE_POSITION_INFORMATION - 1000;
+ status = smb2cli_set_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE,
+ in_file_info_class,
+ &in_input_buffer,
+ 0, /* in_additional_info */
+ fid_persistent,
+ fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_set_info (position) returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ in_file_info_class = SMB_FILE_POSITION_INFORMATION - 1000;
+ status = smb2cli_query_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE,
+ in_file_info_class,
+ 1024, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ fid_persistent,
+ fid_volatile,
+ talloc_tos(),
+ &out_output_buffer);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_info (position) returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "session-reauth.txt",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_ALL | SEC_FILE_ALL, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DELETE_ON_CLOSE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0x3, 0, dir_persistent, dir_volatile,
+ "session-reauth.txt", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, dir_persistent, dir_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ saved_tid = smb2cli_tcon_current_id(cli->smb2.tcon);
+ saved_tcon = cli->smb2.tcon;
+ cli->smb2.tcon = smbXcli_tcon_create(cli);
+ smb2cli_tcon_set_values(cli->smb2.tcon,
+ NULL, /* session */
+ saved_tid,
+ 0, /* type */
+ 0, /* flags */
+ 0, /* capabilities */
+ 0 /* maximal_access */);
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+ talloc_free(cli->smb2.tcon);
+ cli->smb2.tcon = saved_tcon;
+
+ return true;
+}
+
+static NTSTATUS check_size(struct cli_state *cli,
+ uint16_t fnum,
+ const char *fname,
+ size_t size)
+{
+ off_t size_read = 0;
+
+ NTSTATUS status = cli_qfileinfo_basic(cli,
+ fnum,
+ NULL,
+ &size_read,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_qfileinfo_basic of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (size != size_read) {
+ printf("size (%u) != size_read(%u) for %s\n",
+ (unsigned int)size,
+ (unsigned int)size_read,
+ fname);
+ /* Use EOF to mean bad size. */
+ return NT_STATUS_END_OF_FILE;
+ }
+ return NT_STATUS_OK;
+}
+
+/* Ensure cli_ftruncate() works for SMB2. */
+
+bool run_smb2_ftruncate(int dummy)
+{
+ struct cli_state *cli = NULL;
+ const char *fname = "smb2_ftruncate.txt";
+ uint16_t fnum = (uint16_t)-1;
+ bool correct = false;
+ size_t buflen = 1024*1024;
+ uint8_t *buf = NULL;
+ unsigned int i;
+ NTSTATUS status;
+
+ printf("Starting SMB2-FTRUNCATE\n");
+
+ if (!torture_init_connection(&cli)) {
+ goto fail;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB2_02,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ cli_setatr(cli, fname, 0, 0);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ buf = talloc_zero_array(cli, uint8_t, buflen);
+ if (buf == NULL) {
+ goto fail;
+ }
+
+ /* Write 1MB. */
+ status = cli_writeall(cli,
+ fnum,
+ 0,
+ buf,
+ 0,
+ buflen,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write of %u to %s failed (%s)\n",
+ (unsigned int)buflen,
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = check_size(cli, fnum, fname, buflen);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* Now ftruncate. */
+ for ( i = 0; i < 10; i++) {
+ status = cli_ftruncate(cli, fnum, i*1024);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ftruncate %u of %s failed (%s)\n",
+ (unsigned int)i*1024,
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+ status = check_size(cli, fnum, fname, i*1024);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ }
+
+ correct = true;
+
+ fail:
+
+ if (cli == NULL) {
+ return false;
+ }
+
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ }
+ cli_setatr(cli, fname, 0, 0);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+ return correct;
+}
+
+/* Ensure SMB2 flush on directories behaves correctly. */
+
+static bool test_dir_fsync(struct cli_state *cli, const char *path)
+{
+ NTSTATUS status;
+ uint64_t fid_persistent, fid_volatile;
+ uint8_t *dir_data = NULL;
+ uint32_t dir_data_length = 0;
+
+ /* Open directory - no write abilities. */
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ path,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_DIR_LIST|
+ SEC_DIR_READ_ATTRIBUTE, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_SYNCHRONOUS_IO_NONALERT|
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create '%s' (readonly) returned %s\n",
+ path,
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0, 0, fid_persistent, fid_volatile, "*", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Open directory no write access. Flush should fail. */
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("smb2cli_flush on a read-only directory returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Open directory write-attributes only. Flush should still fail. */
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ path,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_DIR_LIST|
+ SEC_DIR_WRITE_ATTRIBUTE|
+ SEC_DIR_READ_ATTRIBUTE, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_SYNCHRONOUS_IO_NONALERT|
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create '%s' (write attr) returned %s\n",
+ path,
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0, 0, fid_persistent, fid_volatile, "*", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("smb2cli_flush on a write-attributes directory "
+ "returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Open directory with SEC_DIR_ADD_FILE access. Flush should now succeed. */
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ path,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_DIR_LIST|
+ SEC_DIR_ADD_FILE, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_SYNCHRONOUS_IO_NONALERT|
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create '%s' (write FILE access) returned %s\n",
+ path,
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0, 0, fid_persistent, fid_volatile, "*", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush on a directory returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Open directory with SEC_DIR_ADD_FILE access. Flush should now succeed. */
+
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ path,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_DIR_LIST|
+ SEC_DIR_ADD_SUBDIR, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_SYNCHRONOUS_IO_NONALERT|
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create '%s' (write DIR access) returned %s\n",
+ path,
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_query_directory(
+ cli->conn, cli->timeout, cli->smb2.session, cli->smb2.tcon,
+ 1, 0, 0, fid_persistent, fid_volatile, "*", 0xffff,
+ talloc_tos(), &dir_data, &dir_data_length);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_query_directory returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_flush(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_flush on a directory returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb2cli_close(cli->conn, cli->timeout, cli->smb2.session,
+ cli->smb2.tcon, 0, fid_persistent, fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+
+ return true;
+}
+
+bool run_smb2_dir_fsync(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool bret = false;
+ const char *dname = "fsync_test_dir";
+
+ printf("Starting SMB2-DIR-FSYNC\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB2_02,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ (void)cli_rmdir(cli, dname);
+ status = cli_mkdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_mkdir(%s) returned %s\n",
+ dname,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Test on a subdirectory. */
+ bret = test_dir_fsync(cli, dname);
+ if (bret == false) {
+ (void)cli_rmdir(cli, dname);
+ return false;
+ }
+ (void)cli_rmdir(cli, dname);
+
+ /* Test on the root handle of a share. */
+ bret = test_dir_fsync(cli, "");
+ if (bret == false) {
+ return false;
+ }
+ return true;
+}
+
+bool run_smb2_path_slash(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ uint64_t fid_persistent;
+ uint64_t fid_volatile;
+ const char *dname_noslash = "smb2_dir_slash";
+ const char *dname_backslash = "smb2_dir_slash\\";
+ const char *dname_slash = "smb2_dir_slash/";
+ const char *fname_noslash = "smb2_file_slash";
+ const char *fname_backslash = "smb2_file_slash\\";
+ const char *fname_slash = "smb2_file_slash/";
+
+ printf("Starting SMB2-PATH-SLASH\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB2_02,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ (void)cli_unlink(cli, dname_noslash, 0);
+ (void)cli_rmdir(cli, dname_noslash);
+ (void)cli_unlink(cli, fname_noslash, 0);
+ (void)cli_rmdir(cli, fname_noslash);
+
+ /* Try to create a directory with the backslash name. */
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ dname_backslash,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ FILE_READ_DATA|FILE_READ_ATTRIBUTES, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ /* directory ending in '\\' should be success. */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_create '%s' returned %s - "
+ "should be NT_STATUS_OK\n",
+ dname_backslash,
+ nt_errstr(status));
+ return false;
+ }
+ status = smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0,
+ fid_persistent,
+ fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smb2cli_close returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ (void)cli_rmdir(cli, dname_noslash);
+
+ /* Try to create a directory with the slash name. */
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ dname_slash,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ FILE_READ_DATA|FILE_READ_ATTRIBUTES, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ /* directory ending in '/' is an error. */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) {
+ printf("smb2cli_create '%s' returned %s - "
+ "should be NT_STATUS_OBJECT_NAME_INVALID\n",
+ dname_slash,
+ nt_errstr(status));
+ if (NT_STATUS_IS_OK(status)) {
+ (void)smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0,
+ fid_persistent,
+ fid_volatile);
+ }
+ (void)cli_rmdir(cli, dname_noslash);
+ return false;
+ }
+
+ (void)cli_rmdir(cli, dname_noslash);
+
+ /* Try to create a file with the backslash name. */
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ fname_backslash,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ FILE_READ_DATA|FILE_READ_ATTRIBUTES, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_NON_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ /* file ending in '\\' should be error. */
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) {
+ printf("smb2cli_create '%s' returned %s - "
+ "should be NT_STATUS_OBJECT_NAME_INVALID\n",
+ fname_backslash,
+ nt_errstr(status));
+ if (NT_STATUS_IS_OK(status)) {
+ (void)smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0,
+ fid_persistent,
+ fid_volatile);
+ }
+ (void)cli_unlink(cli, fname_noslash, 0);
+ return false;
+ }
+
+ (void)cli_unlink(cli, fname_noslash, 0);
+
+ /* Try to create a file with the slash name. */
+ status = smb2cli_create(
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ fname_slash,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ FILE_READ_DATA|FILE_READ_ATTRIBUTES, /* desired_access, */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ FILE_NON_DIRECTORY_FILE, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+
+ /* file ending in '/' should be error. */
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) {
+ printf("smb2cli_create '%s' returned %s - "
+ "should be NT_STATUS_OBJECT_NAME_INVALID\n",
+ fname_slash,
+ nt_errstr(status));
+ if (NT_STATUS_IS_OK(status)) {
+ (void)smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0,
+ fid_persistent,
+ fid_volatile);
+ }
+ (void)cli_unlink(cli, fname_noslash, 0);
+ return false;
+ }
+
+ (void)cli_unlink(cli, fname_noslash, 0);
+ return true;
+}
+
+/*
+ * NB. This can only work against a server where
+ * the connecting user has been granted SeSecurityPrivilege.
+ *
+ * 1). Create a test file.
+ * 2). Open with SEC_FLAG_SYSTEM_SECURITY *only*. ACCESS_DENIED -
+ * NB. SMB2-only behavior.
+ * 3). Open with SEC_FLAG_SYSTEM_SECURITY|FILE_WRITE_ATTRIBUTES.
+ * 4). Write SACL. Should fail with ACCESS_DENIED (seems to need WRITE_DAC).
+ * 5). Close (3).
+ * 6). Open with SEC_FLAG_SYSTEM_SECURITY|SEC_STD_WRITE_DAC.
+ * 7). Write SACL. Success.
+ * 8). Close (4).
+ * 9). Open with SEC_FLAG_SYSTEM_SECURITY|READ_ATTRIBUTES.
+ * 10). Read SACL. Success.
+ * 11). Read DACL. Should fail with ACCESS_DENIED (no READ_CONTROL).
+ * 12). Close (9).
+ */
+
+bool run_smb2_sacl(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ struct security_descriptor *sd_dacl = NULL;
+ struct security_descriptor *sd_sacl = NULL;
+ const char *fname = "sacl_test_file";
+ uint16_t fnum = (uint16_t)-1;
+
+ printf("Starting SMB2-SACL\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ (void)cli_unlink(cli, fname, 0);
+
+ /* First create a file. */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ cli_close(cli, fnum);
+ fnum = (uint16_t)-1;
+
+ /*
+ * Now try to open with *only* SEC_FLAG_SYSTEM_SECURITY.
+ * This should fail with NT_STATUS_ACCESS_DENIED - but
+ * only against an SMB2 server. SMB1 allows this as tested
+ * in SMB1-SYSTEM-SECURITY.
+ */
+
+ status = cli_smb2_create_fnum(cli,
+ fname,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ SEC_FLAG_SYSTEM_SECURITY, /* desired access */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_NON_DIRECTORY_FILE, /* create_options, */
+ NULL, /* in_cblobs. */
+ &fnum, /* fnum */
+ NULL, /* smb_create_returns */
+ talloc_tos(), /* mem_ctx */
+ NULL); /* out_cblobs */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PRIVILEGE_NOT_HELD)) {
+ printf("SMB2-SACL-TEST can only work with a user "
+ "who has been granted SeSecurityPrivilege.\n"
+ "This is the "
+ "\"Manage auditing and security log\""
+ "privilege setting on Windows\n");
+ goto fail;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("open file %s with SEC_FLAG_SYSTEM_SECURITY only: "
+ "got %s - should fail with ACCESS_DENIED\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * Open with SEC_FLAG_SYSTEM_SECURITY|FILE_WRITE_ATTRIBUTES.
+ */
+
+ status = cli_smb2_create_fnum(cli,
+ fname,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ SEC_FLAG_SYSTEM_SECURITY|
+ FILE_WRITE_ATTRIBUTES, /* desired access */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_NON_DIRECTORY_FILE, /* create_options, */
+ NULL, /* in_cblobs. */
+ &fnum, /* fnum */
+ NULL, /* smb_create_returns */
+ talloc_tos(), /* mem_ctx */
+ NULL); /* out_cblobs */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s with (SEC_FLAG_SYSTEM_SECURITY|"
+ "FILE_WRITE_ATTRIBUTES) failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* Create an SD with a SACL. */
+ sd_sacl = security_descriptor_sacl_create(talloc_tos(),
+ 0,
+ NULL, /* owner. */
+ NULL, /* group. */
+ /* first ACE. */
+ SID_WORLD,
+ SEC_ACE_TYPE_SYSTEM_AUDIT,
+ SEC_GENERIC_ALL,
+ SEC_ACE_FLAG_FAILED_ACCESS,
+ NULL);
+
+ if (sd_sacl == NULL) {
+ printf("Out of memory creating SACL\n");
+ goto fail;
+ }
+
+ /*
+ * Write the SACL SD. This should fail
+ * even though we have SEC_FLAG_SYSTEM_SECURITY,
+ * as it seems to also need WRITE_DAC access.
+ */
+ status = cli_set_security_descriptor(cli,
+ fnum,
+ SECINFO_DACL|SECINFO_SACL,
+ sd_sacl);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("Writing SACL on file %s got (%s) "
+ "should have failed with ACCESS_DENIED.\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* And close. */
+ cli_smb2_close_fnum(cli, fnum);
+ fnum = (uint16_t)-1;
+
+ /*
+ * Open with SEC_FLAG_SYSTEM_SECURITY|SEC_STD_WRITE_DAC.
+ */
+
+ status = cli_smb2_create_fnum(cli,
+ fname,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ SEC_FLAG_SYSTEM_SECURITY|
+ SEC_STD_WRITE_DAC, /* desired access */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_NON_DIRECTORY_FILE, /* create_options, */
+ NULL, /* in_cblobs. */
+ &fnum, /* fnum */
+ NULL, /* smb_create_returns */
+ talloc_tos(), /* mem_ctx */
+ NULL); /* out_cblobs */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s with (SEC_FLAG_SYSTEM_SECURITY|"
+ "FILE_WRITE_ATTRIBUTES) failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * Write the SACL SD. This should now succeed
+ * as we have both SEC_FLAG_SYSTEM_SECURITY
+ * and WRITE_DAC access.
+ */
+ status = cli_set_security_descriptor(cli,
+ fnum,
+ SECINFO_DACL|SECINFO_SACL,
+ sd_sacl);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_set_security_descriptor SACL "
+ "on file %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* And close. */
+ cli_smb2_close_fnum(cli, fnum);
+ fnum = (uint16_t)-1;
+
+ /* We're done with the sacl we made. */
+ TALLOC_FREE(sd_sacl);
+
+ /*
+ * Now try to open with SEC_FLAG_SYSTEM_SECURITY|READ_ATTRIBUTES.
+ * This gives us access to the SACL.
+ */
+
+ status = cli_smb2_create_fnum(cli,
+ fname,
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ SEC_FLAG_SYSTEM_SECURITY|
+ FILE_READ_ATTRIBUTES, /* desired access */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_NON_DIRECTORY_FILE, /* create_options, */
+ NULL, /* in_cblobs. */
+ &fnum, /* fnum */
+ NULL, /* smb_create_returns */
+ talloc_tos(), /* mem_ctx */
+ NULL); /* out_cblobs */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s with (SEC_FLAG_SYSTEM_SECURITY|"
+ "FILE_READ_ATTRIBUTES) failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* Try and read the SACL - should succeed. */
+ status = cli_query_security_descriptor(
+ cli, fnum, SECINFO_SACL, talloc_tos(), &sd_sacl);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Read SACL from file %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(sd_sacl);
+
+ /*
+ * Try and read the DACL - should fail as we have
+ * no READ_DAC access.
+ */
+ status = cli_query_security_descriptor(
+ cli, fnum, SECINFO_DACL, talloc_tos(), &sd_sacl);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("Reading DACL on file %s got (%s) "
+ "should have failed with ACCESS_DENIED.\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (fnum != (uint16_t)-1) {
+ cli_smb2_close_fnum(cli, fnum);
+ fnum = (uint16_t)-1;
+ }
+
+ TALLOC_FREE(sd_dacl);
+ TALLOC_FREE(sd_sacl);
+
+ (void)cli_unlink(cli, fname, 0);
+ return true;
+
+ fail:
+
+ TALLOC_FREE(sd_dacl);
+ TALLOC_FREE(sd_sacl);
+
+ if (fnum != (uint16_t)-1) {
+ cli_smb2_close_fnum(cli, fnum);
+ fnum = (uint16_t)-1;
+ }
+
+ (void)cli_unlink(cli, fname, 0);
+ return false;
+}
+
+bool run_smb2_quota1(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+ SMB_NTQUOTA_STRUCT qt = {0};
+
+ printf("Starting SMB2-QUOTA1\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_smb2_create_fnum(
+ cli,
+ "\\",
+ (struct cli_smb2_create_flags){0},
+ SMB2_IMPERSONATION_IMPERSONATION,
+ SEC_GENERIC_READ, /* desired access */
+ 0, /* file_attributes, */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ FILE_DIRECTORY_FILE, /* create_options, */
+ NULL, /* in_cblobs. */
+ &fnum, /* fnum */
+ NULL, /* smb_create_returns */
+ NULL, /* mem_ctx */
+ NULL); /* out_cblobs */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_smb2_create_fnum failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_smb2_get_user_quota(cli, fnum, &qt);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) {
+ printf("cli_smb2_get_user_quota returned %s, expected "
+ "NT_STATUS_INVALID_HANDLE\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+bool run_smb2_stream_acl(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ uint16_t fnum = (uint16_t)-1;
+ const char *fname = "stream_acl_test_file";
+ const char *sname = "stream_acl_test_file:streamname";
+ struct security_descriptor *sd_dacl = NULL;
+ bool ret = false;
+
+ printf("SMB2 stream acl\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure file doesn't exist. */
+ (void)cli_unlink(cli, fname, 0);
+
+ /* Create the file. */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* Close the handle. */
+ cli_smb2_close_fnum(cli, fnum);
+ fnum = (uint16_t)-1;
+
+ /* Create the stream. */
+ status = cli_ntcreate(cli,
+ sname,
+ 0,
+ FILE_READ_DATA|
+ SEC_STD_READ_CONTROL|
+ SEC_STD_WRITE_DAC,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s failed (%s)\n",
+ sname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* Close the handle. */
+ cli_smb2_close_fnum(cli, fnum);
+ fnum = (uint16_t)-1;
+
+ /*
+ * Open the stream - for Samba this ensures
+ * we prove we have a pathref fsp.
+ */
+ status = cli_ntcreate(cli,
+ sname,
+ 0,
+ FILE_READ_DATA|
+ SEC_STD_READ_CONTROL|
+ SEC_STD_WRITE_DAC,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s failed (%s)\n",
+ sname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* Read the security descriptor off the stream handle. */
+ status = cli_query_security_descriptor(cli,
+ fnum,
+ SECINFO_DACL,
+ talloc_tos(),
+ &sd_dacl);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Reading DACL on stream %s got (%s)\n",
+ sname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (sd_dacl == NULL || sd_dacl->dacl == NULL ||
+ sd_dacl->dacl->num_aces < 1) {
+ printf("Invalid DACL returned on stream %s "
+ "(this should not happen)\n",
+ sname);
+ goto fail;
+ }
+
+ /*
+ * Ensure it allows FILE_READ_DATA in the first ace.
+ * It always should.
+ */
+ if ((sd_dacl->dacl->aces[0].access_mask & FILE_READ_DATA) == 0) {
+ printf("DACL->ace[0] returned on stream %s "
+ "doesn't have read access (should not happen)\n",
+ sname);
+ goto fail;
+ }
+
+ /* Remove FILE_READ_DATA from the first ace and set. */
+ sd_dacl->dacl->aces[0].access_mask &= ~FILE_READ_DATA;
+
+ status = cli_set_security_descriptor(cli,
+ fnum,
+ SECINFO_DACL,
+ sd_dacl);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Setting DACL on stream %s got (%s)\n",
+ sname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(sd_dacl);
+
+ /* Read again and check it changed. */
+ status = cli_query_security_descriptor(cli,
+ fnum,
+ SECINFO_DACL,
+ talloc_tos(),
+ &sd_dacl);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Reading DACL on stream %s got (%s)\n",
+ sname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ if (sd_dacl == NULL || sd_dacl->dacl == NULL ||
+ sd_dacl->dacl->num_aces < 1) {
+ printf("Invalid DACL (1) returned on stream %s "
+ "(this should not happen)\n",
+ sname);
+ goto fail;
+ }
+
+ /* FILE_READ_DATA should be gone from the first ace. */
+ if ((sd_dacl->dacl->aces[0].access_mask & FILE_READ_DATA) != 0) {
+ printf("DACL on stream %s did not change\n",
+ sname);
+ goto fail;
+ }
+
+ ret = true;
+
+ fail:
+
+ if (fnum != (uint16_t)-1) {
+ cli_smb2_close_fnum(cli, fnum);
+ fnum = (uint16_t)-1;
+ }
+
+ (void)cli_unlink(cli, fname, 0);
+ return ret;
+}
+
+static NTSTATUS list_fn(struct file_info *finfo,
+ const char *name,
+ void *state)
+{
+ bool *matched = (bool *)state;
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ *matched = true;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Must be run against a share with "smbd async dosmode = yes".
+ * Checks we can return DOS attriutes other than "N".
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14758
+ */
+
+bool run_list_dir_async_test(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ const char *dname = "ASYNC_DIR";
+ bool ret = false;
+ bool matched = false;
+
+ printf("SMB2 list dir async\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure directory doesn't exist. */
+ (void)cli_rmdir(cli, dname);
+
+ status = cli_mkdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_mkdir %s returned %s\n", dname, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_list(cli,
+ dname,
+ FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_DIRECTORY,
+ list_fn,
+ &matched);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_list %s returned %s\n", dname, nt_errstr(status));
+ goto fail;
+ }
+
+ if (!matched) {
+ printf("Failed to find %s\n", dname);
+ goto fail;
+ }
+
+ ret = true;
+
+ fail:
+
+ (void)cli_rmdir(cli, dname);
+ return ret;
+}
+
+/*
+ * Test delete a directory fails if a file is created
+ * in a directory after the delete on close is set.
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=14892
+ */
+
+bool run_delete_on_close_non_empty(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ const char *dname = "DEL_ON_CLOSE_DIR";
+ const char *fname = "DEL_ON_CLOSE_DIR\\testfile";
+ uint16_t fnum = (uint16_t)-1;
+ uint16_t fnum1 = (uint16_t)-1;
+ bool ret = false;
+
+ printf("SMB2 delete on close nonempty\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure directory doesn't exist. */
+ (void)cli_unlink(cli,
+ fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ (void)cli_rmdir(cli, dname);
+
+ /* Create target directory. */
+ status = cli_ntcreate(cli,
+ dname,
+ 0,
+ DELETE_ACCESS|FILE_READ_DATA,
+ FILE_ATTRIBUTE_DIRECTORY,
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_CREATE,
+ FILE_DIRECTORY_FILE,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate for directory %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now set the delete on close bit. */
+ status = cli_nt_delete_on_close(cli, fnum, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_cli_nt_delete_on_close set for directory "
+ "%s returned %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Create file inside target directory. */
+ /*
+ * NB. On Windows this will return NT_STATUS_DELETE_PENDING. Only on
+ * Samba will this succeed by default (the option "check parent
+ * directory delete on close" configures behaviour), but we're using
+ * this to test a race condition.
+ */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum1,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate for file %s returned %s\n",
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+ cli_close(cli, fnum1);
+ fnum1 = (uint16_t)-1;
+
+ /* Now the close should fail. */
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_DIRECTORY_NOT_EMPTY)) {
+ printf("cli_close for directory %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ ret = true;
+
+ out:
+
+ if (fnum1 != (uint16_t)-1) {
+ cli_close(cli, fnum1);
+ }
+ if (fnum != (uint16_t)-1) {
+ cli_nt_delete_on_close(cli, fnum, 0);
+ cli_close(cli, fnum);
+ }
+ (void)cli_unlink(cli,
+ fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ (void)cli_rmdir(cli, dname);
+ return ret;
+}
+
+static NTSTATUS check_empty_fn(struct file_info *finfo,
+ const char *mask,
+ void *private_data)
+{
+ unsigned int *pcount = (unsigned int *)private_data;
+
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
+ (*pcount)++;
+ return NT_STATUS_OK;
+ }
+ return NT_STATUS_DIRECTORY_NOT_EMPTY;
+}
+
+/*
+ * Test setting the delete on close bit on a directory
+ * containing an unwritable file fails or succeeds
+ * an a share set with "hide unwritable = yes"
+ * depending on the setting of "delete veto files".
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15023
+ *
+ * First version. With "delete veto files = yes"
+ * setting the delete on close should succeed.
+ */
+
+bool run_delete_on_close_nonwrite_delete_yes_test(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ const char *dname = "delete_veto_yes";
+ const char *list_dname = "delete_veto_yes\\*";
+ uint16_t fnum = (uint16_t)-1;
+ bool ret = false;
+ unsigned int list_count = 0;
+
+ printf("SMB2 delete on close nonwrite - delete veto yes\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure target directory is seen as empty. */
+ status = cli_list(cli,
+ list_dname,
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_SYSTEM,
+ check_empty_fn,
+ &list_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_list of %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ return false;
+ }
+ if (list_count != 2) {
+ printf("cli_list of %s returned a count of %u\n",
+ dname,
+ list_count);
+ return false;
+ }
+
+ /* Open target directory. */
+ status = cli_ntcreate(cli,
+ dname,
+ 0,
+ DELETE_ACCESS|FILE_READ_DATA,
+ FILE_ATTRIBUTE_DIRECTORY,
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ FILE_DIRECTORY_FILE,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate for directory %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now set the delete on close bit. */
+ status = cli_nt_delete_on_close(cli, fnum, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_cli_nt_delete_on_close set for directory "
+ "%s returned %s (should have succeeded)\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ ret = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ (void)cli_nt_delete_on_close(cli, fnum, 0);
+ (void)cli_close(cli, fnum);
+ }
+ return ret;
+}
+
+/*
+ * Test setting the delete on close bit on a directory
+ * containing an unwritable file fails or succeeds
+ * an a share set with "hide unwritable = yes"
+ * depending on the setting of "delete veto files".
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15023
+ *
+ * Second version. With "delete veto files = no"
+ * setting the delete on close should fail.
+ */
+
+bool run_delete_on_close_nonwrite_delete_no_test(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ const char *dname = "delete_veto_no";
+ const char *list_dname = "delete_veto_no\\*";
+ uint16_t fnum = (uint16_t)-1;
+ bool ret = false;
+ unsigned int list_count = 0;
+
+ printf("SMB2 delete on close nonwrite - delete veto yes\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure target directory is seen as empty. */
+ status = cli_list(cli,
+ list_dname,
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_SYSTEM,
+ check_empty_fn,
+ &list_count);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_list of %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ return false;
+ }
+ if (list_count != 2) {
+ printf("cli_list of %s returned a count of %u\n",
+ dname,
+ list_count);
+ return false;
+ }
+
+ /* Open target directory. */
+ status = cli_ntcreate(cli,
+ dname,
+ 0,
+ DELETE_ACCESS|FILE_READ_DATA,
+ FILE_ATTRIBUTE_DIRECTORY,
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ FILE_DIRECTORY_FILE,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate for directory %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now set the delete on close bit. */
+ status = cli_nt_delete_on_close(cli, fnum, 1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("cli_cli_nt_delete_on_close set for directory "
+ "%s returned NT_STATUS_OK "
+ "(should have failed)\n",
+ dname);
+ goto out;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_DIRECTORY_NOT_EMPTY)) {
+ printf("cli_cli_nt_delete_on_close set for directory "
+ "%s returned %s "
+ "(should have returned "
+ "NT_STATUS_DIRECTORY_NOT_EMPTY)\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ ret = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ (void)cli_nt_delete_on_close(cli, fnum, 0);
+ (void)cli_close(cli, fnum);
+ }
+ return ret;
+}
+
+/*
+ * Open an SMB2 file readonly and return the inode number.
+ */
+static NTSTATUS get_smb2_inode(struct cli_state *cli,
+ const char *pathname,
+ uint64_t *ino_ret)
+{
+ NTSTATUS status;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ DATA_BLOB outbuf = data_blob_null;
+ /*
+ * Open the file.
+ */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ pathname,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* struct symlink_reparse_struct */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Get the inode.
+ */
+ status = smb2cli_query_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE,
+ (SMB_FILE_ALL_INFORMATION - 1000), /* in_file_info_class */
+ 1024, /* in_max_output_length */
+ NULL, /* in_input_buffer */
+ 0, /* in_additional_info */
+ 0, /* in_flags */
+ fid_persistent,
+ fid_volatile,
+ talloc_tos(),
+ &outbuf);
+
+ if (NT_STATUS_IS_OK(status)) {
+ *ino_ret = PULL_LE_U64(outbuf.data, 0x40);
+ }
+
+ (void)smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0,
+ fid_persistent,
+ fid_volatile);
+ return status;
+}
+
+/*
+ * Check an inode matches a given SMB2 path.
+ */
+static bool smb2_inode_matches(struct cli_state *cli,
+ const char *match_pathname,
+ uint64_t ino_tomatch,
+ const char *test_pathname)
+{
+ uint64_t test_ino = 0;
+ NTSTATUS status;
+
+ status = get_smb2_inode(cli,
+ test_pathname,
+ &test_ino);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s: Failed to get ino "
+ "number for %s, (%s)\n",
+ __func__,
+ test_pathname,
+ nt_errstr(status));
+ return false;
+ }
+ if (test_ino != ino_tomatch) {
+ printf("%s: Inode mismatch, ino_tomatch (%s) "
+ "ino=%"PRIu64" test (%s) "
+ "ino=%"PRIu64"\n",
+ __func__,
+ match_pathname,
+ ino_tomatch,
+ test_pathname,
+ test_ino);
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Delete an SMB2 file on a DFS share.
+ */
+static NTSTATUS smb2_dfs_delete(struct cli_state *cli,
+ const char *pathname)
+{
+ NTSTATUS status;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ uint8_t data[1];
+ DATA_BLOB inbuf;
+
+ /*
+ * Open the file.
+ */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ pathname,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* struct symlink_reparse_struct */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * Set delete on close.
+ */
+ PUSH_LE_U8(&data[0], 0, 1);
+ inbuf.data = &data[0];
+ inbuf.length = 1;
+
+ status = smb2cli_set_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE, /* info_type. */
+ SMB_FILE_DISPOSITION_INFORMATION - 1000, /* info_class */
+ &inbuf,
+ 0, /* additional_info. */
+ fid_persistent,
+ fid_volatile);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0,
+ fid_persistent,
+ fid_volatile);
+ return status;
+}
+
+/*
+ * Rename or hardlink an SMB2 file on a DFS share.
+ */
+static NTSTATUS smb2_dfs_setinfo_name(struct cli_state *cli,
+ uint64_t fid_persistent,
+ uint64_t fid_volatile,
+ const char *newname,
+ bool do_rename)
+{
+ NTSTATUS status;
+ DATA_BLOB inbuf;
+ smb_ucs2_t *converted_str = NULL;
+ size_t converted_size_bytes = 0;
+ size_t inbuf_size;
+ uint8_t info_class = 0;
+ bool ok;
+
+ ok = push_ucs2_talloc(talloc_tos(),
+ &converted_str,
+ newname,
+ &converted_size_bytes);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ /*
+ * W2K8 insists the dest name is not null terminated. Remove
+ * the last 2 zero bytes and reduce the name length.
+ */
+ if (converted_size_bytes < 2) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ converted_size_bytes -= 2;
+ inbuf_size = 20 + converted_size_bytes;
+ if (inbuf_size < 20) {
+ /* Integer wrap check. */
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * The Windows 10 SMB2 server has a minimum length
+ * for a SMB2_FILE_RENAME_INFORMATION buffer of
+ * 24 bytes. It returns NT_STATUS_INFO_LENGTH_MISMATCH
+ * if the length is less.
+ */
+ inbuf_size = MAX(inbuf_size, 24);
+ inbuf = data_blob_talloc_zero(talloc_tos(), inbuf_size);
+ if (inbuf.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ PUSH_LE_U32(inbuf.data, 16, converted_size_bytes);
+ memcpy(inbuf.data + 20, converted_str, converted_size_bytes);
+ TALLOC_FREE(converted_str);
+
+ if (do_rename == true) {
+ info_class = SMB_FILE_RENAME_INFORMATION - 1000;
+ } else {
+ /* Hardlink. */
+ info_class = SMB_FILE_LINK_INFORMATION - 1000;
+ }
+
+ status = smb2cli_set_info(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ SMB2_0_INFO_FILE, /* info_type. */
+ info_class, /* info_class */
+ &inbuf,
+ 0, /* additional_info. */
+ fid_persistent,
+ fid_volatile);
+ return status;
+}
+
+static NTSTATUS smb2_dfs_rename(struct cli_state *cli,
+ uint64_t fid_persistent,
+ uint64_t fid_volatile,
+ const char *newname)
+{
+ return smb2_dfs_setinfo_name(cli,
+ fid_persistent,
+ fid_volatile,
+ newname,
+ true); /* do_rename */
+}
+
+static NTSTATUS smb2_dfs_hlink(struct cli_state *cli,
+ uint64_t fid_persistent,
+ uint64_t fid_volatile,
+ const char *newname)
+{
+ return smb2_dfs_setinfo_name(cli,
+ fid_persistent,
+ fid_volatile,
+ newname,
+ false); /* do_rename */
+}
+
+/*
+ * According to:
+
+ * https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/dc9978d7-6299-4c5a-a22d-a039cdc716ea
+ *
+ * (Characters " \ / [ ] : | < > + = ; , * ?,
+ * and control characters in range 0x00 through
+ * 0x1F, inclusive, are illegal in a share name)
+ *
+ * But Windows server only checks in DFS sharenames ':'. All other
+ * share names are allowed.
+ */
+
+static bool test_smb2_dfs_sharenames(struct cli_state *cli,
+ const char *dfs_root_share_name,
+ uint64_t root_ino)
+{
+ char test_path[9];
+ const char *test_str = "/[]:|<>+=;,*?";
+ const char *p;
+ unsigned int i;
+ bool ino_matched = false;
+
+ /* Setup template pathname. */
+ memcpy(test_path, "SERVER\\X", 9);
+
+ /* Test invalid control characters. */
+ for (i = 1; i < 0x20; i++) {
+ test_path[7] = i;
+ ino_matched = smb2_inode_matches(cli,
+ dfs_root_share_name,
+ root_ino,
+ test_path);
+ if (!ino_matched) {
+ return false;
+ }
+ }
+
+ /* Test explicit invalid characters. */
+ for (p = test_str; *p != '\0'; p++) {
+ test_path[7] = *p;
+ if (*p == ':') {
+ /*
+ * Only ':' is treated as an INVALID sharename
+ * for a DFS SERVER\\SHARE path.
+ */
+ uint64_t test_ino = 0;
+ NTSTATUS status = get_smb2_inode(cli,
+ test_path,
+ &test_ino);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_INVALID)) {
+ printf("%s:%d Open of %s should get "
+ "NT_STATUS_OBJECT_NAME_INVALID, got %s\n",
+ __FILE__,
+ __LINE__,
+ test_path,
+ nt_errstr(status));
+ return false;
+ }
+ } else {
+ ino_matched = smb2_inode_matches(cli,
+ dfs_root_share_name,
+ root_ino,
+ test_path);
+ if (!ino_matched) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/*
+ * "Raw" test of SMB2 paths to a DFS share.
+ * We must use the lower level smb2cli_XXXX() interfaces,
+ * not the cli_XXX() ones here as the ultimate goal is to fix our
+ * cli_XXX() interfaces to work transparently over DFS.
+ *
+ * So here, we're testing the server code, not the client code.
+ *
+ * Passes cleanly against Windows.
+ */
+
+bool run_smb2_dfs_paths(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool dfs_supported = false;
+ char *dfs_root_share_name = NULL;
+ uint64_t root_ino = 0;
+ uint64_t test_ino = 0;
+ bool ino_matched = false;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ bool retval = false;
+ bool ok = false;
+
+ printf("Starting SMB2-DFS-PATHS\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure this is a DFS share. */
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb2.tcon);
+ if (!dfs_supported) {
+ printf("Share %s does not support DFS\n",
+ cli->share);
+ return false;
+ }
+ /*
+ * Create the "official" DFS share root name.
+ * No SMB2 paths can start with '\\'.
+ */
+ dfs_root_share_name = talloc_asprintf(talloc_tos(),
+ "%s\\%s",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share);
+ if (dfs_root_share_name == NULL) {
+ printf("Out of memory\n");
+ return false;
+ }
+
+ /* Get the share root inode number. */
+ status = get_smb2_inode(cli,
+ dfs_root_share_name,
+ &root_ino);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Failed to get ino number for share root %s, (%s)\n",
+ __FILE__,
+ __LINE__,
+ dfs_root_share_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Test the Windows algorithm for parsing DFS names.
+ */
+ /*
+ * A single "SERVER" element should open and match the share root.
+ */
+ ino_matched = smb2_inode_matches(cli,
+ dfs_root_share_name,
+ root_ino,
+ smbXcli_conn_remote_name(cli->conn));
+ if (!ino_matched) {
+ printf("%s:%d Failed to match ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+
+ /*
+ * An "" DFS empty server name should open and match the share root on
+ * Windows 2008. Windows 2022 returns NT_STATUS_INVALID_PARAMETER
+ * for a DFS empty server name.
+ */
+ status = get_smb2_inode(cli,
+ "",
+ &test_ino);
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * Windows 2008 - open succeeded. Proceed to
+ * check ino number.
+ */
+ ino_matched = smb2_inode_matches(cli,
+ dfs_root_share_name,
+ root_ino,
+ "");
+ if (!ino_matched) {
+ printf("%s:%d Failed to match ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ "");
+ return false;
+ }
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ /*
+ * For Windows 2022 we expect to fail with
+ * NT_STATUS_INVALID_PARAMETER. Anything else is
+ * unexpected.
+ */
+ printf("%s:%d Unexpected error (%s) getting ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ nt_errstr(status),
+ "");
+ return false;
+ }
+ /* A "BAD" server name should open and match the share root. */
+ ino_matched = smb2_inode_matches(cli,
+ dfs_root_share_name,
+ root_ino,
+ "BAD");
+ if (!ino_matched) {
+ printf("%s:%d Failed to match ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD");
+ return false;
+ }
+ /*
+ * A "BAD\\BAD" server and share name should open
+ * and match the share root.
+ */
+ ino_matched = smb2_inode_matches(cli,
+ dfs_root_share_name,
+ root_ino,
+ "BAD\\BAD");
+ if (!ino_matched) {
+ printf("%s:%d Failed to match ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD");
+ return false;
+ }
+ /*
+ * Trying to open "BAD\\BAD\\BAD" should get
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND.
+ */
+ status = get_smb2_inode(cli,
+ "BAD\\BAD\\BAD",
+ &test_ino);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("%s:%d Open of %s should get "
+ "STATUS_OBJECT_NAME_NOT_FOUND, got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\BAD",
+ nt_errstr(status));
+ return false;
+ }
+ /*
+ * Trying to open "BAD\\BAD\\BAD\\BAD" should get
+ * NT_STATUS_OBJECT_PATH_NOT_FOUND.
+ */
+ status = get_smb2_inode(cli,
+ "BAD\\BAD\\BAD\\BAD",
+ &test_ino);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("%s:%d Open of %s should get "
+ "STATUS_OBJECT_NAME_NOT_FOUND, got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\BAD\\BAD",
+ nt_errstr(status));
+ return false;
+ }
+ /*
+ * Test for invalid pathname characters in the servername.
+ * They are ignored, and it still opens the share root.
+ */
+ ino_matched = smb2_inode_matches(cli,
+ dfs_root_share_name,
+ root_ino,
+ "::::");
+ if (!ino_matched) {
+ printf("%s:%d Failed to match ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ "::::");
+ return false;
+ }
+
+ /*
+ * Test for invalid pathname characters in the sharename.
+ * Invalid sharename characters should still be flagged as
+ * NT_STATUS_OBJECT_NAME_INVALID. It turns out only ':'
+ * is considered an invalid sharename character.
+ */
+ ok = test_smb2_dfs_sharenames(cli,
+ dfs_root_share_name,
+ root_ino);
+ if (!ok) {
+ return false;
+ }
+
+ /* Now create a file called "file". */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "BAD\\BAD\\file",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* struct symlink_reparse_struct */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb2cli_create on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file",
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Trying to open "BAD\\BAD\\file" should now get
+ * a valid inode.
+ */
+ status = get_smb2_inode(cli,
+ "BAD\\BAD\\file",
+ &test_ino);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Open of %s should succeed "
+ "got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Now show that renames use relative,
+ * not full DFS paths.
+ */
+
+ /* Full DFS path should fail. */
+ status = smb2_dfs_rename(cli,
+ fid_persistent,
+ fid_volatile,
+ "ANY\\NAME\\renamed_file");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("%s:%d Rename of %s -> %s should fail "
+ "with NT_STATUS_OBJECT_PATH_NOT_FOUND. Got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file",
+ "ANY\\NAME\\renamed_file",
+ nt_errstr(status));
+ goto err;
+ }
+ /* Relative DFS path should succeed. */
+ status = smb2_dfs_rename(cli,
+ fid_persistent,
+ fid_volatile,
+ "renamed_file");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d: Rename of %s -> %s should succeed. "
+ "Got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\file",
+ "renamed_file",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Trying to open "BAD\\BAD\\renamed_file" should now get
+ * a valid inode.
+ */
+ status = get_smb2_inode(cli,
+ "BAD\\BAD\\renamed_file",
+ &test_ino);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d: Open of %s should succeed "
+ "got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\renamed_file",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Now show that hard links use relative,
+ * not full DFS paths.
+ */
+
+ /* Full DFS path should fail. */
+ status = smb2_dfs_hlink(cli,
+ fid_persistent,
+ fid_volatile,
+ "ANY\\NAME\\hlink");
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("%s:%d Hlink of %s -> %s should fail "
+ "with NT_STATUS_OBJECT_PATH_NOT_FOUND. Got %s\n",
+ __FILE__,
+ __LINE__,
+ "ANY\\NAME\\renamed_file",
+ "ANY\\NAME\\hlink",
+ nt_errstr(status));
+ goto err;
+ }
+ /* Relative DFS path should succeed. */
+ status = smb2_dfs_hlink(cli,
+ fid_persistent,
+ fid_volatile,
+ "hlink");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d: Hlink of %s -> %s should succeed. "
+ "Got %s\n",
+ __FILE__,
+ __LINE__,
+ "ANY\\NAME\\renamed_file",
+ "hlink",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Trying to open "BAD\\BAD\\hlink" should now get
+ * a valid inode.
+ */
+ status = get_smb2_inode(cli,
+ "BAD\\BAD\\hlink",
+ &test_ino);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Open of %s should succeed "
+ "got %s\n",
+ __FILE__,
+ __LINE__,
+ "BAD\\BAD\\hlink",
+ nt_errstr(status));
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ if (fid_persistent != 0 || fid_volatile != 0) {
+ smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0, /* flags */
+ fid_persistent,
+ fid_volatile);
+ }
+ /* Delete anything we made. */
+ (void)smb2_dfs_delete(cli, "BAD\\BAD\\BAD");
+ (void)smb2_dfs_delete(cli, "BAD\\BAD\\file");
+ (void)smb2_dfs_delete(cli, "BAD\\BAD\\renamed_file");
+ (void)smb2_dfs_delete(cli, "BAD\\BAD\\hlink");
+ return retval;
+}
+
+/*
+ * Add a test that sends DFS paths and sets the
+ * SMB2 flag FLAGS2_DFS_PATHNAMES, but to a non-DFS
+ * share. Windows passes this (it just treats the
+ * pathnames as non-DFS and ignores the FLAGS2_DFS_PATHNAMES
+ * bit).
+ */
+
+bool run_smb2_non_dfs_share(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool dfs_supported = false;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ bool retval = false;
+ char *dfs_filename = NULL;
+
+ printf("Starting SMB2-DFS-NON-DFS-SHARE\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ /* Ensure this is *NOT* a DFS share. */
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb2.tcon);
+ if (dfs_supported) {
+ printf("Share %s is a DFS share.\n",
+ cli->share);
+ return false;
+ }
+ /*
+ * Force the share to be DFS, as far as the client
+ * is concerned.
+ */
+ smb2cli_tcon_set_values(cli->smb2.tcon,
+ cli->smb2.session,
+ smb2cli_tcon_current_id(cli->smb2.tcon),
+ 0,
+ smb2cli_tcon_flags(cli->smb2.tcon),
+ smb2cli_tcon_capabilities(cli->smb2.tcon) |
+ SMB2_SHARE_CAP_DFS,
+ 0);
+
+ /* Come up with a "valid" SMB2 DFS name. */
+ dfs_filename = talloc_asprintf(talloc_tos(),
+ "%s\\%s\\file",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share);
+ if (dfs_filename == NULL) {
+ printf("Out of memory\n");
+ return false;
+ }
+
+ /* Now try create dfs_filename. */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ dfs_filename,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs */
+ NULL); /* struct symlink_reparse_struct */
+ /*
+ * Should fail with NT_STATUS_OBJECT_PATH_NOT_FOUND, as
+ * even though we set the FLAGS2_DFS_PATHNAMES the server
+ * knows this isn't a DFS share and so treats BAD\\BAD as
+ * part of the filename.
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("%s:%d create of %s should fail "
+ "with NT_STATUS_OBJECT_PATH_NOT_FOUND. Got %s\n",
+ __FILE__,
+ __LINE__,
+ dfs_filename,
+ nt_errstr(status));
+ goto err;
+ }
+ /*
+ * Prove we can still use non-DFS pathnames, even though
+ * we are setting the FLAGS2_DFS_PATHNAMES in the SMB2
+ * request.
+ */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "file",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* struct symlink_reparse_struct */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb2cli_create on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "file",
+ nt_errstr(status));
+ return false;
+ }
+
+ retval = true;
+
+ err:
+
+ (void)smb2_dfs_delete(cli, dfs_filename);
+ (void)smb2_dfs_delete(cli, "file");
+ return retval;
+}
+
+/*
+ * Add a test that sends a non-DFS path and does not set the
+ * SMB2 flag FLAGS2_DFS_PATHNAMES to a DFS
+ * share. Windows passes this (it just treats the
+ * pathnames as non-DFS).
+ */
+
+bool run_smb2_dfs_share_non_dfs_path(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool dfs_supported = false;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ bool retval = false;
+ char *dfs_filename = NULL;
+ uint64_t root_ino = (uint64_t)-1;
+ bool ino_matched = false;
+
+ printf("Starting SMB2-DFS-SHARE-NON-DFS-PATH\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ /* Ensure this is a DFS share. */
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb2.tcon);
+ if (!dfs_supported) {
+ printf("Share %s is not a DFS share.\n",
+ cli->share);
+ return false;
+ }
+ /* Come up with a "valid" SMB2 DFS name. */
+ dfs_filename = talloc_asprintf(talloc_tos(),
+ "%s\\%s\\file",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share);
+ if (dfs_filename == NULL) {
+ printf("Out of memory\n");
+ return false;
+ }
+
+ /* Get the root of the share ino. */
+ status = get_smb2_inode(cli,
+ "SERVER\\SHARE",
+ &root_ino);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d get_smb2_inode on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "SERVER\\SHARE",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /* Create a dfs_filename. */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ dfs_filename,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* psymlink */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb2cli_create on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ dfs_filename,
+ nt_errstr(status));
+ goto err;
+ }
+
+ /* Close the handle we just opened. */
+ smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0, /* flags */
+ fid_persistent,
+ fid_volatile);
+
+ fid_persistent = 0;
+ fid_volatile = 0;
+
+ /*
+ * Force the share to be non-DFS, as far as the client
+ * is concerned.
+ */
+ smb2cli_tcon_set_values(cli->smb2.tcon,
+ cli->smb2.session,
+ smb2cli_tcon_current_id(cli->smb2.tcon),
+ 0,
+ smb2cli_tcon_flags(cli->smb2.tcon),
+ smb2cli_tcon_capabilities(cli->smb2.tcon) &
+ ~SMB2_SHARE_CAP_DFS,
+ 0);
+
+ /*
+ * Prove we can still use non-DFS pathnames on a DFS
+ * share so long as we don't set the FLAGS2_DFS_PATHNAMES
+ * in the SMB2 request.
+ */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "file",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* psymlink */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb2cli_create on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ "file",
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Show that now we're using non-DFS pathnames
+ * on a DFS share, "" opens the root of the share.
+ */
+ ino_matched = smb2_inode_matches(cli,
+ "SERVER\\SHARE",
+ root_ino,
+ "");
+ if (!ino_matched) {
+ printf("%s:%d Failed to match ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ "");
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ if (fid_volatile != 0) {
+ smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0, /* flags */
+ fid_persistent,
+ fid_volatile);
+ }
+ (void)smb2_dfs_delete(cli, "file");
+ (void)smb2_dfs_delete(cli, dfs_filename);
+ return retval;
+}
+
+/*
+ * "Raw" test of an SMB2 filename with one or more leading
+ * backslash characters to a DFS share.
+ *
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15277
+ *
+ * Once the server passes SMB2-DFS-PATHS we can
+ * fold this test into that one.
+ *
+ * Passes cleanly against Windows.
+ */
+
+bool run_smb2_dfs_filename_leading_backslash(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ bool dfs_supported = false;
+ char *dfs_filename_slash = NULL;
+ char *dfs_filename_slash_multi = NULL;
+ uint64_t file_ino = 0;
+ bool ino_matched = false;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ bool retval = false;
+
+ printf("Starting SMB2-DFS-FILENAME-LEADING-BACKSLASH\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, share, "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure this is a DFS share. */
+ dfs_supported = smbXcli_conn_dfs_supported(cli->conn);
+ if (!dfs_supported) {
+ printf("Server %s does not support DFS\n",
+ smbXcli_conn_remote_name(cli->conn));
+ return false;
+ }
+ dfs_supported = smbXcli_tcon_is_dfs_share(cli->smb2.tcon);
+ if (!dfs_supported) {
+ printf("Share %s does not support DFS\n",
+ cli->share);
+ return false;
+ }
+
+ /*
+ * Create the filename with one leading backslash.
+ */
+ dfs_filename_slash = talloc_asprintf(talloc_tos(),
+ "\\%s\\%s\\file",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share);
+ if (dfs_filename_slash == NULL) {
+ printf("Out of memory\n");
+ return false;
+ }
+
+ /*
+ * Create the filename with many leading backslashes.
+ */
+ dfs_filename_slash_multi = talloc_asprintf(talloc_tos(),
+ "\\\\\\\\%s\\%s\\file",
+ smbXcli_conn_remote_name(cli->conn),
+ cli->share);
+ if (dfs_filename_slash_multi == NULL) {
+ printf("Out of memory\n");
+ return false;
+ }
+
+ /*
+ * Trying to open "\\server\\share\\file" should get
+ * NT_STATUS_OBJECT_NAME_NOT_FOUND.
+ */
+ status = get_smb2_inode(cli,
+ dfs_filename_slash,
+ &file_ino);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("%s:%d Open of %s should get "
+ "STATUS_OBJECT_NAME_NOT_FOUND, got %s\n",
+ __FILE__,
+ __LINE__,
+ dfs_filename_slash,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Now create a file called "\\server\\share\\file". */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ dfs_filename_slash,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_STD_DELETE |
+ SEC_FILE_READ_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* struct symlink_reparse_struct */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb2cli_create on %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ dfs_filename_slash,
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Trying to open "\\server\\share\\file" should now get
+ * a valid inode.
+ */
+ status = get_smb2_inode(cli,
+ dfs_filename_slash,
+ &file_ino);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d Open of %s should succeed "
+ "got %s\n",
+ __FILE__,
+ __LINE__,
+ dfs_filename_slash,
+ nt_errstr(status));
+ goto err;
+ }
+
+ /*
+ * Trying to open "\\\\\\server\\share\\file" should now get
+ * a valid inode that matches. MacOSX-style of DFS name test.
+ */
+ ino_matched = smb2_inode_matches(cli,
+ dfs_filename_slash,
+ file_ino,
+ dfs_filename_slash_multi);
+ if (!ino_matched) {
+ printf("%s:%d Failed to match ino number for %s\n",
+ __FILE__,
+ __LINE__,
+ dfs_filename_slash_multi);
+ goto err;
+ }
+
+ retval = true;
+
+ err:
+
+ if (fid_persistent != 0 || fid_volatile != 0) {
+ smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0, /* flags */
+ fid_persistent,
+ fid_volatile);
+ }
+ /* Delete anything we made. */
+ (void)smb2_dfs_delete(cli, dfs_filename_slash);
+ return retval;
+}
+
+/*
+ * Ensure a named pipe async read followed by a disconnect
+ * doesn't crash the server (server crash checked for in
+ * containing test script:
+ * source3/script/tests/test_smbtorture_nocrash_s3.sh)
+ * BUG: https://bugzilla.samba.org/show_bug.cgi?id=15423
+ */
+
+bool run_smb2_pipe_read_async_disconnect(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ bool retval = false;
+
+ printf("Starting SMB2-PIPE-READ-ASYNC-DISCONNECT\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect_creds(cli, "IPC$", "IPC", torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect to IPC$ returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Open the SAMR pipe. */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ "SAMR",
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_FILE_READ_DATA|
+ SEC_FILE_WRITE_DATA, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_OPEN, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* psymlink */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s:%d smb2cli_create on SAMR returned %s\n",
+ __FILE__,
+ __LINE__,
+ nt_errstr(status));
+ goto err;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto err;
+ }
+
+ /* Start an async read. */
+ req = smb2cli_read_send(talloc_tos(),
+ ev,
+ cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 16*1024,
+ 0, /* offset */
+ fid_persistent,
+ fid_volatile,
+ 0, /* minimum_count */
+ 0); /* remaining_bytes */
+ if (req == NULL) {
+ goto err;
+ }
+
+ /* Force disconnect. */
+ smbXcli_conn_disconnect(cli->conn, NT_STATUS_LOCAL_DISCONNECT);
+ fid_volatile = 0;
+ retval = true;
+
+ err:
+
+ if (fid_volatile != 0) {
+ smb2cli_close(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ 0, /* flags */
+ fid_persistent,
+ fid_volatile);
+ }
+ return retval;
+}
+
+bool run_smb2_invalid_pipename(int dummy)
+{
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ uint64_t fid_persistent = 0;
+ uint64_t fid_volatile = 0;
+ const char *unknown_pipe = "badpipe";
+ const char *invalid_pipe = "../../../../../../../../../badpipe";
+
+ printf("Starting SMB2-INVALID-PIPENAME\n");
+
+ if (!torture_init_connection(&cli)) {
+ return false;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ PROTOCOL_SMB2_02,
+ PROTOCOL_SMB3_11,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("smbXcli_negprot returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_session_setup_creds(cli, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_session_setup returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_tree_connect(cli, "IPC$", "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_tree_connect returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Try and connect to an unknown pipename. */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ unknown_pipe,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_FILE_READ_DATA|
+ SEC_FILE_WRITE_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* struct symlink_reparse_struct */
+ /* We should get NT_STATUS_OBJECT_NAME_NOT_FOUND */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("%s:%d smb2cli_create on name %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ unknown_pipe,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Try and connect to an invalid pipename containing unix separators. */
+ status = smb2cli_create(cli->conn,
+ cli->timeout,
+ cli->smb2.session,
+ cli->smb2.tcon,
+ invalid_pipe,
+ SMB2_OPLOCK_LEVEL_NONE, /* oplock_level, */
+ SMB2_IMPERSONATION_IMPERSONATION, /* impersonation_level, */
+ SEC_STD_SYNCHRONIZE|
+ SEC_FILE_READ_DATA|
+ SEC_FILE_WRITE_DATA|
+ SEC_FILE_READ_ATTRIBUTE, /* desired_access, */
+ FILE_ATTRIBUTE_NORMAL, /* file_attributes, */
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, /* share_access, */
+ FILE_CREATE, /* create_disposition, */
+ 0, /* create_options, */
+ NULL, /* smb2_create_blobs *blobs */
+ &fid_persistent,
+ &fid_volatile,
+ NULL, /* struct smb_create_returns * */
+ talloc_tos(), /* mem_ctx. */
+ NULL, /* struct smb2_create_blobs * */
+ NULL); /* struct symlink_reparse_struct */
+ /*
+ * We should still get NT_STATUS_OBJECT_NAME_NOT_FOUND
+ * (tested against Windows 2022).
+ */
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("%s:%d smb2cli_create on name %s returned %s\n",
+ __FILE__,
+ __LINE__,
+ invalid_pipe,
+ nt_errstr(status));
+ return false;
+ }
+ return true;
+}
diff --git a/source3/torture/test_smbsock_any_connect.c b/source3/torture/test_smbsock_any_connect.c
new file mode 100644
index 0000000..a964e0f
--- /dev/null
+++ b/source3/torture/test_smbsock_any_connect.c
@@ -0,0 +1,47 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test the smb_any_connect functionality
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "torture/proto.h"
+
+bool run_smb_any_connect(int dummy)
+{
+ int fd;
+ NTSTATUS status;
+ struct sockaddr_storage addrs[5];
+ size_t chosen_index;
+ uint16_t port;
+
+ interpret_string_addr(&addrs[0], "192.168.99.5", 0);
+ interpret_string_addr(&addrs[1], "192.168.99.6", 0);
+ interpret_string_addr(&addrs[2], "192.168.99.7", 0);
+ interpret_string_addr(&addrs[3], "192.168.99.8", 0);
+ interpret_string_addr(&addrs[4], "192.168.99.9", 0);
+
+ status = smbsock_any_connect(addrs, NULL, NULL, NULL, NULL,
+ ARRAY_SIZE(addrs), 0, 0,
+ &fd, &chosen_index, &port);
+
+ d_printf("smbsock_any_connect returned %s (fd %d)\n",
+ nt_errstr(status), NT_STATUS_IS_OK(status) ? fd : -1);
+ if (NT_STATUS_IS_OK(status)) {
+ close(fd);
+ }
+ return true;
+}
diff --git a/source3/torture/test_tdb_validate.c b/source3/torture/test_tdb_validate.c
new file mode 100644
index 0000000..4768512
--- /dev/null
+++ b/source3/torture/test_tdb_validate.c
@@ -0,0 +1,68 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "source3/include/includes.h"
+#include <tdb.h>
+#include "source3/torture/proto.h"
+#include "source3/lib/tdb_validate.h"
+
+static int validate_fn(struct tdb_context *tdb, TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct tdb_validation_status *state = private_data;
+ state->success = false;
+ printf("validate_fn called\n");
+ return -1;
+}
+
+bool run_tdb_validate(int dummy)
+{
+ const char tdb_name[] = "tdb_validate.tdb";
+ bool result = false;
+ struct tdb_context *tdb = NULL;
+ char buf[] = "data";
+ TDB_DATA data = { .dptr = (uint8_t *)buf, .dsize = sizeof(buf), };
+ int ret;
+
+ unlink(tdb_name);
+
+ tdb = tdb_open(tdb_name, 0, 0, O_CREAT|O_EXCL|O_RDWR, 0600);
+ if (tdb == NULL) {
+ perror("Could not open tdb");
+ goto done;
+ }
+
+ ret = tdb_store(tdb, data, data, 0);
+ if (ret == -1) {
+ perror("tdb_store failed");
+ goto done;
+ }
+
+ ret = tdb_validate(tdb, validate_fn);
+ if (ret == 0) {
+ fprintf(stderr,
+ "tdb_validate succeeded where it should have "
+ "failed\n");
+ goto done;
+ }
+
+ result = true;
+done:
+ tdb_close(tdb);
+ unlink(tdb_name);
+ return result;
+}
diff --git a/source3/torture/torture.c b/source3/torture/torture.c
new file mode 100644
index 0000000..86a6e92
--- /dev/null
+++ b/source3/torture/torture.c
@@ -0,0 +1,16512 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB torture tester
+ Copyright (C) Andrew Tridgell 1997-1998
+ Copyright (C) Jeremy Allison 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/shmem.h"
+#include "libsmb/namequery.h"
+#include "wbc_async.h"
+#include "torture/proto.h"
+#include "libcli/security/security.h"
+#include "tldap.h"
+#include "tldap_util.h"
+#include "tldap_gensec_bind.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "../lib/util/memcache.h"
+#include "nsswitch/winbind_client.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "async_smb.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "trans2.h"
+#include "libsmb/nmblib.h"
+#include "../lib/util/tevent_ntstatus.h"
+#include "util_tdb.h"
+#include "../libcli/smb/read_smb.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "lib/util/sys_rw_data.h"
+#include "lib/util/base64.h"
+#include "lib/util/time.h"
+#include "lib/gencache.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/asn1.h"
+#include "lib/param/param.h"
+#include "auth/gensec/gensec.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+extern char *optarg;
+extern int optind;
+
+fstring host, workgroup, share, password, username, myname;
+struct cli_credentials *torture_creds;
+static const char *sockops="TCP_NODELAY";
+int torture_nprocs=1;
+static int port_to_use=0;
+int torture_numops=100;
+int torture_blocksize=1024*1024;
+static int procnum; /* records process count number when forking */
+static struct cli_state *current_cli;
+static fstring randomfname;
+static bool use_oplocks;
+static bool use_level_II_oplocks;
+static const char *client_txt = "client_oplocks.txt";
+static bool disable_spnego;
+static bool use_kerberos;
+static bool force_dos_errors;
+static fstring multishare_conn_fname;
+static bool use_multishare_conn = False;
+static bool do_encrypt;
+static const char *local_path = NULL;
+static enum smb_signing_setting signing_state = SMB_SIGNING_DEFAULT;
+char *test_filename;
+
+bool torture_showall = False;
+
+static double create_procs(bool (*fn)(int), bool *result);
+
+/********************************************************************
+ Ensure a connection is encrypted.
+********************************************************************/
+
+static bool force_cli_encryption(struct cli_state *c,
+ const char *sharename)
+{
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ NTSTATUS status;
+
+ if (!SERVER_HAS_UNIX_CIFS(c)) {
+ d_printf("Encryption required and "
+ "server that doesn't support "
+ "UNIX extensions - failing connect\n");
+ return false;
+ }
+
+ status = cli_unix_extensions_version(c, &major, &minor, &caplow,
+ &caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "can't get UNIX CIFS extensions "
+ "version from server: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ if (!(caplow & CIFS_UNIX_TRANSPORT_ENCRYPTION_CAP)) {
+ d_printf("Encryption required and "
+ "share %s doesn't support "
+ "encryption.\n", sharename);
+ return false;
+ }
+
+ status = cli_smb1_setup_encryption(c, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Encryption required and "
+ "setup failed with error %s.\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+
+static struct cli_state *open_nbt_connection(void)
+{
+ struct cli_state *c;
+ NTSTATUS status;
+ int flags = 0;
+
+ if (disable_spnego) {
+ flags |= CLI_FULL_CONNECTION_DONT_SPNEGO;
+ }
+
+ if (use_oplocks) {
+ flags |= CLI_FULL_CONNECTION_OPLOCKS;
+ }
+
+ if (use_level_II_oplocks) {
+ flags |= CLI_FULL_CONNECTION_LEVEL_II_OPLOCKS;
+ }
+
+ if (force_dos_errors) {
+ flags |= CLI_FULL_CONNECTION_FORCE_DOS_ERRORS;
+ }
+
+ status = cli_connect_nb(host, NULL, port_to_use, 0x20, myname,
+ signing_state, flags, &c);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to connect with %s. Error %s\n", host, nt_errstr(status) );
+ return NULL;
+ }
+
+ cli_set_timeout(c, 120000); /* set a really long timeout (2 minutes) */
+
+ return c;
+}
+
+/****************************************************************************
+ Send a corrupt session request. See rfc1002.txt 4.3 and 4.3.2.
+****************************************************************************/
+
+static bool cli_bad_session_request(int fd,
+ struct nmb_name *calling, struct nmb_name *called)
+{
+ TALLOC_CTX *frame;
+ uint8_t len_buf[4];
+ struct iovec iov[3];
+ ssize_t len;
+ uint8_t *inbuf;
+ int err;
+ bool ret = false;
+ uint8_t message_type;
+ uint8_t error;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+
+ frame = talloc_stackframe();
+
+ iov[0].iov_base = len_buf;
+ iov[0].iov_len = sizeof(len_buf);
+
+ /* put in the destination name */
+
+ iov[1].iov_base = name_mangle(talloc_tos(), called->name,
+ called->name_type);
+ if (iov[1].iov_base == NULL) {
+ goto fail;
+ }
+ iov[1].iov_len = name_len((unsigned char *)iov[1].iov_base,
+ talloc_get_size(iov[1].iov_base));
+
+ /* and my name */
+
+ iov[2].iov_base = name_mangle(talloc_tos(), calling->name,
+ calling->name_type);
+ if (iov[2].iov_base == NULL) {
+ goto fail;
+ }
+ iov[2].iov_len = name_len((unsigned char *)iov[2].iov_base,
+ talloc_get_size(iov[2].iov_base));
+
+ /* Deliberately corrupt the name len (first byte) */
+ *((uint8_t *)iov[2].iov_base) = 100;
+
+ /* send a session request (RFC 1002) */
+ /* setup the packet length
+ * Remove four bytes from the length count, since the length
+ * field in the NBT Session Service header counts the number
+ * of bytes which follow. The cli_send_smb() function knows
+ * about this and accounts for those four bytes.
+ * CRH.
+ */
+
+ _smb_setlen(len_buf, iov[1].iov_len + iov[2].iov_len);
+ SCVAL(len_buf,0,0x81);
+
+ len = write_data_iov(fd, iov, 3);
+ if (len == -1) {
+ goto fail;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = read_smb_send(frame, ev, fd);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ goto fail;
+ }
+ len = read_smb_recv(req, talloc_tos(), &inbuf, &err);
+ if (len == -1) {
+ errno = err;
+ goto fail;
+ }
+ TALLOC_FREE(ev);
+
+ message_type = CVAL(inbuf, 0);
+ if (message_type != 0x83) {
+ d_fprintf(stderr, "Expected msg type 0x83, got 0x%2.2x\n",
+ message_type);
+ goto fail;
+ }
+
+ if (smb_len(inbuf) != 1) {
+ d_fprintf(stderr, "Expected smb_len 1, got %d\n",
+ (int)smb_len(inbuf));
+ goto fail;
+ }
+
+ error = CVAL(inbuf, 4);
+ if (error != 0x82) {
+ d_fprintf(stderr, "Expected error 0x82, got %d\n",
+ (int)error);
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+/* Insert a NULL at the first separator of the given path and return a pointer
+ * to the remainder of the string.
+ */
+static char *
+terminate_path_at_separator(char * path)
+{
+ char * p;
+
+ if (!path) {
+ return NULL;
+ }
+
+ if ((p = strchr_m(path, '/'))) {
+ *p = '\0';
+ return p + 1;
+ }
+
+ if ((p = strchr_m(path, '\\'))) {
+ *p = '\0';
+ return p + 1;
+ }
+
+ /* No separator. */
+ return NULL;
+}
+
+/*
+ parse a //server/share type UNC name
+*/
+bool smbcli_parse_unc(const char *unc_name, TALLOC_CTX *mem_ctx,
+ char **hostname, char **sharename)
+{
+ char *p;
+
+ *hostname = *sharename = NULL;
+
+ if (strncmp(unc_name, "\\\\", 2) &&
+ strncmp(unc_name, "//", 2)) {
+ return False;
+ }
+
+ *hostname = talloc_strdup(mem_ctx, &unc_name[2]);
+ p = terminate_path_at_separator(*hostname);
+
+ if (p && *p) {
+ *sharename = talloc_strdup(mem_ctx, p);
+ terminate_path_at_separator(*sharename);
+ }
+
+ if (*hostname && *sharename) {
+ return True;
+ }
+
+ TALLOC_FREE(*hostname);
+ TALLOC_FREE(*sharename);
+ return False;
+}
+
+static bool torture_open_connection_share(struct cli_state **c,
+ const char *hostname,
+ const char *sharename,
+ int flags)
+{
+ NTSTATUS status;
+
+ status = cli_full_connection_creds(c,
+ myname,
+ hostname,
+ NULL, /* dest_ss */
+ port_to_use,
+ sharename,
+ "?????",
+ torture_creds,
+ flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("failed to open share connection: //%s/%s port:%d - %s\n",
+ hostname, sharename, port_to_use, nt_errstr(status));
+ return False;
+ }
+
+ cli_set_timeout(*c, 120000); /* set a really long timeout (2 minutes) */
+
+ if (do_encrypt) {
+ return force_cli_encryption(*c,
+ sharename);
+ }
+ return True;
+}
+
+bool torture_open_connection_flags(struct cli_state **c, int conn_index, int flags)
+{
+ char **unc_list = NULL;
+ int num_unc_names = 0;
+ bool result;
+
+ if (use_multishare_conn==True) {
+ char *h, *s;
+ unc_list = file_lines_load(multishare_conn_fname, &num_unc_names, 0, NULL);
+ if (!unc_list || num_unc_names <= 0) {
+ printf("Failed to load unc names list from '%s'\n", multishare_conn_fname);
+ exit(1);
+ }
+
+ if (!smbcli_parse_unc(unc_list[conn_index % num_unc_names],
+ NULL, &h, &s)) {
+ printf("Failed to parse UNC name %s\n",
+ unc_list[conn_index % num_unc_names]);
+ TALLOC_FREE(unc_list);
+ exit(1);
+ }
+
+ result = torture_open_connection_share(c, h, s, flags);
+
+ /* h, s were copied earlier */
+ TALLOC_FREE(unc_list);
+ return result;
+ }
+
+ return torture_open_connection_share(c, host, share, flags);
+}
+
+bool torture_open_connection(struct cli_state **c, int conn_index)
+{
+ int flags = CLI_FULL_CONNECTION_FORCE_SMB1;
+
+ if (use_oplocks) {
+ flags |= CLI_FULL_CONNECTION_OPLOCKS;
+ }
+ if (use_level_II_oplocks) {
+ flags |= CLI_FULL_CONNECTION_LEVEL_II_OPLOCKS;
+ }
+
+ return torture_open_connection_flags(c, conn_index, flags);
+}
+
+bool torture_init_connection(struct cli_state **pcli)
+{
+ struct cli_state *cli;
+
+ cli = open_nbt_connection();
+ if (cli == NULL) {
+ return false;
+ }
+
+ *pcli = cli;
+ return true;
+}
+
+bool torture_cli_session_setup2(struct cli_state *cli, uint16_t *new_vuid)
+{
+ uint16_t old_vuid = cli_state_get_uid(cli);
+ NTSTATUS status;
+ bool ret;
+
+ cli_state_set_uid(cli, 0);
+ status = cli_session_setup_creds(cli, torture_creds);
+ ret = NT_STATUS_IS_OK(status);
+ *new_vuid = cli_state_get_uid(cli);
+ cli_state_set_uid(cli, old_vuid);
+ return ret;
+}
+
+
+bool torture_close_connection(struct cli_state *c)
+{
+ bool ret = True;
+ NTSTATUS status;
+
+ status = cli_tdis(c);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("tdis failed (%s)\n", nt_errstr(status));
+ ret = False;
+ }
+
+ cli_shutdown(c);
+
+ return ret;
+}
+
+void torture_conn_set_sockopt(struct cli_state *cli)
+{
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+}
+
+static NTSTATUS torture_delete_fn(struct file_info *finfo,
+ const char *pattern,
+ void *state)
+{
+ NTSTATUS status;
+ char *filename = NULL;
+ char *dirname = NULL;
+ char *p = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct cli_state *cli = (struct cli_state *)state;
+
+ if (ISDOT(finfo->name) || ISDOTDOT(finfo->name)) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ dirname = talloc_strdup(frame, pattern);
+ if (dirname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ p = strrchr_m(dirname, '\\');
+ if (p != NULL) {
+ /* Remove the terminating '\' */
+ *p = '\0';
+ }
+ if (dirname[0] != '\0') {
+ filename = talloc_asprintf(frame,
+ "%s\\%s",
+ dirname,
+ finfo->name);
+ } else {
+ filename = talloc_asprintf(frame,
+ "%s",
+ finfo->name);
+ }
+ if (filename == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ char *subdirname = talloc_asprintf(frame,
+ "%s\\*",
+ filename);
+ if (subdirname == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+ status = cli_list(cli,
+ subdirname,
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_SYSTEM,
+ torture_delete_fn,
+ cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("torture_delete_fn: cli_list "
+ "of %s failed (%s)\n",
+ subdirname,
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+ status = cli_rmdir(cli, filename);
+ } else {
+ status = cli_unlink(cli,
+ filename,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ printf("torture_delete_fn: cli_rmdir"
+ " of %s failed (%s)\n",
+ filename,
+ nt_errstr(status));
+ } else {
+ printf("torture_delete_fn: cli_unlink"
+ " of %s failed (%s)\n",
+ filename,
+ nt_errstr(status));
+ }
+ }
+ TALLOC_FREE(frame);
+ return status;
+}
+
+void torture_deltree(struct cli_state *cli, const char *dname)
+{
+ char *mask = NULL;
+ NTSTATUS status;
+
+ /* It might be a file */
+ (void)cli_unlink(cli,
+ dname,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN);
+
+ mask = talloc_asprintf(cli,
+ "%s\\*",
+ dname);
+ if (mask == NULL) {
+ printf("torture_deltree: talloc_asprintf failed\n");
+ return;
+ }
+
+ status = cli_list(cli,
+ mask,
+ FILE_ATTRIBUTE_DIRECTORY |
+ FILE_ATTRIBUTE_HIDDEN|
+ FILE_ATTRIBUTE_SYSTEM,
+ torture_delete_fn,
+ cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("torture_deltree: cli_list of %s failed (%s)\n",
+ mask,
+ nt_errstr(status));
+ }
+ TALLOC_FREE(mask);
+ status = cli_rmdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("torture_deltree: cli_rmdir of %s failed (%s)\n",
+ dname,
+ nt_errstr(status));
+ }
+}
+
+/* check if the server produced the expected dos or nt error code */
+static bool check_both_error(int line, NTSTATUS status,
+ uint8_t eclass, uint32_t ecode, NTSTATUS nterr)
+{
+ if (NT_STATUS_IS_DOS(status)) {
+ uint8_t cclass;
+ uint32_t num;
+
+ /* Check DOS error */
+ cclass = NT_STATUS_DOS_CLASS(status);
+ num = NT_STATUS_DOS_CODE(status);
+
+ if (eclass != cclass || ecode != num) {
+ printf("unexpected error code class=%d code=%d\n",
+ (int)cclass, (int)num);
+ printf(" expected %d/%d %s (line=%d)\n",
+ (int)eclass, (int)ecode, nt_errstr(nterr), line);
+ return false;
+ }
+ } else {
+ /* Check NT error */
+ if (!NT_STATUS_EQUAL(nterr, status)) {
+ printf("unexpected error code %s\n",
+ nt_errstr(status));
+ printf(" expected %s (line=%d)\n",
+ nt_errstr(nterr), line);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/* check if the server produced the expected error code */
+static bool check_error(int line, NTSTATUS status,
+ uint8_t eclass, uint32_t ecode, NTSTATUS nterr)
+{
+ if (NT_STATUS_IS_DOS(status)) {
+ uint8_t cclass;
+ uint32_t num;
+
+ /* Check DOS error */
+
+ cclass = NT_STATUS_DOS_CLASS(status);
+ num = NT_STATUS_DOS_CODE(status);
+
+ if (eclass != cclass || ecode != num) {
+ printf("unexpected error code class=%d code=%d\n",
+ (int)cclass, (int)num);
+ printf(" expected %d/%d %s (line=%d)\n",
+ (int)eclass, (int)ecode, nt_errstr(nterr),
+ line);
+ return False;
+ }
+
+ } else {
+ /* Check NT error */
+
+ if (NT_STATUS_V(nterr) != NT_STATUS_V(status)) {
+ printf("unexpected error code %s\n",
+ nt_errstr(status));
+ printf(" expected %s (line=%d)\n", nt_errstr(nterr),
+ line);
+ return False;
+ }
+ }
+
+ return True;
+}
+
+NTSTATUS cli_qpathinfo1(struct cli_state *cli,
+ const char *fname,
+ time_t *change_time,
+ time_t *access_time,
+ time_t *write_time,
+ off_t *size,
+ uint32_t *pattr)
+{
+ int timezone = smb1cli_conn_server_time_zone(cli->conn);
+ time_t (*date_fn)(const void *buf, int serverzone) = NULL;
+ uint8_t *rdata = NULL;
+ uint32_t num_rdata;
+ NTSTATUS status;
+
+ status = cli_qpathinfo(talloc_tos(),
+ cli,
+ fname,
+ SMB_INFO_STANDARD,
+ 22,
+ CLI_BUFFER_SIZE,
+ &rdata,
+ &num_rdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (cli->win95) {
+ date_fn = make_unix_date;
+ } else {
+ date_fn = make_unix_date2;
+ }
+
+ if (change_time) {
+ *change_time = date_fn(rdata + 0, timezone);
+ }
+ if (access_time) {
+ *access_time = date_fn(rdata + 4, timezone);
+ }
+ if (write_time) {
+ *write_time = date_fn(rdata + 8, timezone);
+ }
+ if (size) {
+ *size = PULL_LE_U32(rdata, 12);
+ }
+ if (pattr) {
+ *pattr = PULL_LE_U16(rdata, l1_attrFile);
+ }
+ return NT_STATUS_OK;
+}
+
+static bool wait_lock(struct cli_state *c, int fnum, uint32_t offset, uint32_t len)
+{
+ NTSTATUS status;
+
+ status = cli_lock32(c, fnum, offset, len, -1, WRITE_LOCK);
+
+ while (!NT_STATUS_IS_OK(status)) {
+ if (!check_both_error(__LINE__, status, ERRDOS,
+ ERRlock, NT_STATUS_LOCK_NOT_GRANTED)) {
+ return false;
+ }
+
+ status = cli_lock32(c, fnum, offset, len, -1, WRITE_LOCK);
+ }
+
+ return true;
+}
+
+
+static bool rw_torture(struct cli_state *c)
+{
+ const char *lockfname = "\\torture.lck";
+ fstring fname;
+ uint16_t fnum;
+ uint16_t fnum2;
+ pid_t pid2, pid = getpid();
+ int i, j;
+ char buf[1024];
+ bool correct = True;
+ size_t nread = 0;
+ NTSTATUS status;
+
+ memset(buf, '\0', sizeof(buf));
+
+ status = cli_openx(c, lockfname, O_RDWR | O_CREAT | O_EXCL,
+ DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ status = cli_openx(c, lockfname, O_RDWR, DENY_NONE, &fnum2);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n",
+ lockfname, nt_errstr(status));
+ return False;
+ }
+
+ for (i=0;i<torture_numops;i++) {
+ unsigned n = (unsigned)sys_random()%10;
+
+ if (i % 10 == 0) {
+ printf("%d\r", i); fflush(stdout);
+ }
+ slprintf(fname, sizeof(fstring) - 1, "\\torture.%u", n);
+
+ if (!wait_lock(c, fnum2, n*sizeof(int), sizeof(int))) {
+ return False;
+ }
+
+ status = cli_openx(c, fname, O_RDWR | O_CREAT | O_TRUNC,
+ DENY_ALL, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open failed (%s)\n", nt_errstr(status));
+ correct = False;
+ break;
+ }
+
+ status = cli_writeall(c, fnum, 0, (uint8_t *)&pid, 0,
+ sizeof(pid), NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ for (j=0;j<50;j++) {
+ status = cli_writeall(c, fnum, 0, (uint8_t *)buf,
+ sizeof(pid)+(j*sizeof(buf)),
+ sizeof(buf), NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed (%s)\n",
+ nt_errstr(status));
+ correct = False;
+ }
+ }
+
+ pid2 = 0;
+
+ status = cli_read(c, fnum, (char *)&pid2, 0, sizeof(pid),
+ &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("read failed (%s)\n", nt_errstr(status));
+ correct = false;
+ } else if (nread != sizeof(pid)) {
+ printf("read/write compare failed: "
+ "recv %ld req %ld\n", (unsigned long)nread,
+ (unsigned long)sizeof(pid));
+ correct = false;
+ }
+
+ if (pid2 != pid) {
+ printf("data corruption!\n");
+ correct = False;
+ }
+
+ status = cli_close(c, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_unlink(c, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_unlock(c, fnum2, n*sizeof(int), sizeof(int));
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlock failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+ }
+
+ cli_close(c, fnum2);
+ cli_unlink(c, lockfname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("%d\n", i);
+
+ return correct;
+}
+
+static bool run_torture(int dummy)
+{
+ struct cli_state *cli;
+ bool ret;
+
+ cli = current_cli;
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ ret = rw_torture(cli);
+
+ if (!torture_close_connection(cli)) {
+ ret = False;
+ }
+
+ return ret;
+}
+
+static bool rw_torture3(struct cli_state *c, char *lockfname)
+{
+ uint16_t fnum = (uint16_t)-1;
+ unsigned int i = 0;
+ char buf[131072];
+ char buf_rd[131072];
+ unsigned count;
+ unsigned countprev = 0;
+ size_t sent = 0;
+ bool correct = True;
+ NTSTATUS status = NT_STATUS_OK;
+
+ srandom(1);
+ for (i = 0; i < sizeof(buf); i += sizeof(uint32_t))
+ {
+ SIVAL(buf, i, sys_random());
+ }
+
+ if (procnum == 0)
+ {
+ status = cli_unlink(
+ c, lockfname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s) (normal, this file should "
+ "not exist)\n", nt_errstr(status));
+ }
+
+ status = cli_openx(c, lockfname, O_RDWR | O_CREAT | O_EXCL,
+ DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("first open read/write of %s failed (%s)\n",
+ lockfname, nt_errstr(status));
+ return False;
+ }
+ }
+ else
+ {
+ for (i = 0; i < 500 && fnum == (uint16_t)-1; i++)
+ {
+ status = cli_openx(c, lockfname, O_RDONLY,
+ DENY_NONE, &fnum);
+ if (NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ smb_msleep(10);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("second open read-only of %s failed (%s)\n",
+ lockfname, nt_errstr(status));
+ return False;
+ }
+ }
+
+ i = 0;
+ for (count = 0; count < sizeof(buf); count += sent)
+ {
+ if (count >= countprev) {
+ printf("%d %8d\r", i, count);
+ fflush(stdout);
+ i++;
+ countprev += (sizeof(buf) / 20);
+ }
+
+ if (procnum == 0)
+ {
+ sent = ((unsigned)sys_random()%(20))+ 1;
+ if (sent > sizeof(buf) - count)
+ {
+ sent = sizeof(buf) - count;
+ }
+
+ status = cli_writeall(c, fnum, 0, (uint8_t *)buf+count,
+ count, sent, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed (%s)\n",
+ nt_errstr(status));
+ correct = False;
+ }
+ }
+ else
+ {
+ status = cli_read(c, fnum, buf_rd+count, count,
+ sizeof(buf)-count, &sent);
+ if(!NT_STATUS_IS_OK(status)) {
+ printf("read failed offset:%d size:%ld (%s)\n",
+ count, (unsigned long)sizeof(buf)-count,
+ nt_errstr(status));
+ correct = False;
+ sent = 0;
+ } else if (sent > 0) {
+ if (memcmp(buf_rd+count, buf+count, sent) != 0)
+ {
+ printf("read/write compare failed\n");
+ printf("offset: %d req %ld recvd %ld\n", count, (unsigned long)sizeof(buf)-count, (unsigned long)sent);
+ correct = False;
+ break;
+ }
+ }
+ }
+
+ }
+
+ status = cli_close(c, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ return correct;
+}
+
+static bool rw_torture2(struct cli_state *c1, struct cli_state *c2)
+{
+ const char *lockfname = "\\torture2.lck";
+ uint16_t fnum1;
+ uint16_t fnum2;
+ int i;
+ char buf[131072];
+ char buf_rd[131072];
+ bool correct = True;
+ size_t bytes_read;
+ NTSTATUS status;
+
+ status = cli_unlink(c1, lockfname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s) (normal, this file should not exist)\n", nt_errstr(status));
+ }
+
+ status = cli_openx(c1, lockfname, O_RDWR | O_CREAT | O_EXCL,
+ DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("first open read/write of %s failed (%s)\n",
+ lockfname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_openx(c2, lockfname, O_RDONLY, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("second open read-only of %s failed (%s)\n",
+ lockfname, nt_errstr(status));
+ cli_close(c1, fnum1);
+ return False;
+ }
+
+ for (i = 0; i < torture_numops; i++)
+ {
+ size_t buf_size = ((unsigned)sys_random()%(sizeof(buf)-1))+ 1;
+ if (i % 10 == 0) {
+ printf("%d\r", i); fflush(stdout);
+ }
+
+ generate_random_buffer((unsigned char *)buf, buf_size);
+
+ status = cli_writeall(c1, fnum1, 0, (uint8_t *)buf, 0,
+ buf_size, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed (%s)\n", nt_errstr(status));
+ correct = False;
+ break;
+ }
+
+ status = cli_read(c2, fnum2, buf_rd, 0, buf_size, &bytes_read);
+ if(!NT_STATUS_IS_OK(status)) {
+ printf("read failed (%s)\n", nt_errstr(status));
+ correct = false;
+ break;
+ } else if (bytes_read != buf_size) {
+ printf("read failed\n");
+ printf("read %ld, expected %ld\n",
+ (unsigned long)bytes_read,
+ (unsigned long)buf_size);
+ correct = False;
+ break;
+ }
+
+ if (memcmp(buf_rd, buf, buf_size) != 0)
+ {
+ printf("read/write compare failed\n");
+ correct = False;
+ break;
+ }
+ }
+
+ status = cli_close(c2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_close(c1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_unlink(c1, lockfname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ return correct;
+}
+
+static bool run_readwritetest(int dummy)
+{
+ struct cli_state *cli1, *cli2;
+ bool test1, test2 = False;
+
+ if (!torture_open_connection(&cli1, 0) || !torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ printf("starting readwritetest\n");
+
+ test1 = rw_torture2(cli1, cli2);
+ printf("Passed readwritetest v1: %s\n", BOOLSTR(test1));
+
+ if (test1) {
+ test2 = rw_torture2(cli1, cli1);
+ printf("Passed readwritetest v2: %s\n", BOOLSTR(test2));
+ }
+
+ if (!torture_close_connection(cli1)) {
+ test1 = False;
+ }
+
+ if (!torture_close_connection(cli2)) {
+ test2 = False;
+ }
+
+ return (test1 && test2);
+}
+
+static bool run_readwritemulti(int dummy)
+{
+ struct cli_state *cli;
+ bool test;
+
+ cli = current_cli;
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ printf("run_readwritemulti: fname %s\n", randomfname);
+ test = rw_torture3(cli, randomfname);
+
+ if (!torture_close_connection(cli)) {
+ test = False;
+ }
+
+ return test;
+}
+
+static bool run_readwritelarge_internal(void)
+{
+ static struct cli_state *cli1;
+ uint16_t fnum1;
+ const char *lockfname = "\\large.dat";
+ off_t fsize;
+ char buf[126*1024];
+ bool correct = True;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ memset(buf,'\0',sizeof(buf));
+
+ printf("starting readwritelarge_internal\n");
+
+ cli_unlink(cli1, lockfname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli1, lockfname, O_RDWR | O_CREAT | O_EXCL,
+ DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open read/write of %s failed (%s)\n", lockfname, nt_errstr(status));
+ return False;
+ }
+
+ cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 0, sizeof(buf), NULL);
+
+ status = cli_qfileinfo_basic(cli1, fnum1, NULL, &fsize, NULL, NULL,
+ NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("qfileinfo failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ if (fsize == sizeof(buf))
+ printf("readwritelarge_internal test 1 succeeded (size = %lx)\n",
+ (unsigned long)fsize);
+ else {
+ printf("readwritelarge_internal test 1 failed (size = %lx)\n",
+ (unsigned long)fsize);
+ correct = False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_unlink(cli1, lockfname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_openx(cli1, lockfname, O_RDWR | O_CREAT | O_EXCL,
+ DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open read/write of %s failed (%s)\n", lockfname, nt_errstr(status));
+ return False;
+ }
+
+ cli_smbwrite(cli1, fnum1, buf, 0, sizeof(buf), NULL);
+
+ status = cli_qfileinfo_basic(cli1, fnum1, NULL, &fsize, NULL, NULL,
+ NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("qfileinfo failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ if (fsize == sizeof(buf))
+ printf("readwritelarge_internal test 2 succeeded (size = %lx)\n",
+ (unsigned long)fsize);
+ else {
+ printf("readwritelarge_internal test 2 failed (size = %lx)\n",
+ (unsigned long)fsize);
+ correct = False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+ return correct;
+}
+
+static bool run_readwritelarge(int dummy)
+{
+ return run_readwritelarge_internal();
+}
+
+static bool run_readwritelarge_signtest(int dummy)
+{
+ bool ret;
+ signing_state = SMB_SIGNING_REQUIRED;
+ ret = run_readwritelarge_internal();
+ signing_state = SMB_SIGNING_DEFAULT;
+ return ret;
+}
+
+int line_count = 0;
+int nbio_id;
+
+#define ival(s) strtol(s, NULL, 0)
+
+/* run a test that simulates an approximate netbench client load */
+static bool run_netbench(int client)
+{
+ struct cli_state *cli;
+ int i;
+ char line[1024];
+ char cname[20];
+ FILE *f;
+ const char *params[20];
+ bool correct = True;
+
+ cli = current_cli;
+
+ nbio_id = client;
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ nb_setup(cli);
+
+ slprintf(cname,sizeof(cname)-1, "client%d", client);
+
+ f = fopen(client_txt, "r");
+
+ if (!f) {
+ perror(client_txt);
+ return False;
+ }
+
+ while (fgets(line, sizeof(line)-1, f)) {
+ char *saveptr;
+ line_count++;
+
+ line[strlen(line)-1] = 0;
+
+ /* printf("[%d] %s\n", line_count, line); */
+
+ all_string_sub(line,"client1", cname, sizeof(line));
+
+ /* parse the command parameters */
+ params[0] = strtok_r(line, " ", &saveptr);
+ i = 0;
+ while (params[i]) params[++i] = strtok_r(NULL, " ", &saveptr);
+
+ params[i] = "";
+
+ if (i < 2) continue;
+
+ if (!strncmp(params[0],"SMB", 3)) {
+ printf("ERROR: You are using a dbench 1 load file\n");
+ exit(1);
+ }
+
+ if (!strcmp(params[0],"NTCreateX")) {
+ nb_createx(params[1], ival(params[2]), ival(params[3]),
+ ival(params[4]));
+ } else if (!strcmp(params[0],"Close")) {
+ nb_close(ival(params[1]));
+ } else if (!strcmp(params[0],"Rename")) {
+ nb_rename(params[1], params[2]);
+ } else if (!strcmp(params[0],"Unlink")) {
+ nb_unlink(params[1]);
+ } else if (!strcmp(params[0],"Deltree")) {
+ nb_deltree(params[1]);
+ } else if (!strcmp(params[0],"Rmdir")) {
+ nb_rmdir(params[1]);
+ } else if (!strcmp(params[0],"QUERY_PATH_INFORMATION")) {
+ nb_qpathinfo(params[1]);
+ } else if (!strcmp(params[0],"QUERY_FILE_INFORMATION")) {
+ nb_qfileinfo(ival(params[1]));
+ } else if (!strcmp(params[0],"QUERY_FS_INFORMATION")) {
+ nb_qfsinfo(ival(params[1]));
+ } else if (!strcmp(params[0],"FIND_FIRST")) {
+ nb_findfirst(params[1]);
+ } else if (!strcmp(params[0],"WriteX")) {
+ nb_writex(ival(params[1]),
+ ival(params[2]), ival(params[3]), ival(params[4]));
+ } else if (!strcmp(params[0],"ReadX")) {
+ nb_readx(ival(params[1]),
+ ival(params[2]), ival(params[3]), ival(params[4]));
+ } else if (!strcmp(params[0],"Flush")) {
+ nb_flush(ival(params[1]));
+ } else {
+ printf("Unknown operation %s\n", params[0]);
+ exit(1);
+ }
+ }
+ fclose(f);
+
+ nb_cleanup();
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ return correct;
+}
+
+
+/* run a test that simulates an approximate netbench client load */
+static bool run_nbench(int dummy)
+{
+ double t;
+ bool correct = True;
+
+ nbio_shmem(torture_nprocs);
+
+ nbio_id = -1;
+
+ signal(SIGALRM, nb_alarm);
+ alarm(1);
+ t = create_procs(run_netbench, &correct);
+ alarm(0);
+
+ printf("\nThroughput %g MB/sec\n",
+ 1.0e-6 * nbio_total() / t);
+ return correct;
+}
+
+
+/*
+ This test checks for two things:
+
+ 1) correct support for retaining locks over a close (ie. the server
+ must not use posix semantics)
+ 2) support for lock timeouts
+ */
+static bool run_locktest1(int dummy)
+{
+ struct cli_state *cli1, *cli2;
+ const char *fname = "\\lockt1.lck";
+ uint16_t fnum1, fnum2, fnum3;
+ time_t t1, t2;
+ unsigned lock_timeout;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli1, 0) || !torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ printf("starting locktest1\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE,
+ &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_openx(cli2, fname, O_RDWR, DENY_NONE, &fnum3);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open3 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_lock32(cli1, fnum1, 0, 4, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock1 failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_lock32(cli2, fnum3, 0, 4, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("lock2 succeeded! This is a locking bug\n");
+ return false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_LOCK_NOT_GRANTED)) {
+ return false;
+ }
+ }
+
+ lock_timeout = (1 + (random() % 20));
+ printf("Testing lock timeout with timeout=%u\n", lock_timeout);
+ t1 = time(NULL);
+ status = cli_lock32(cli2, fnum3, 0, 4, lock_timeout * 1000, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("lock3 succeeded! This is a locking bug\n");
+ return false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_FILE_LOCK_CONFLICT)) {
+ return false;
+ }
+ }
+ t2 = time(NULL);
+
+ if (ABS(t2 - t1) < lock_timeout-1) {
+ printf("error: This server appears not to support timed lock requests\n");
+ }
+
+ printf("server slept for %u seconds for a %u second timeout\n",
+ (unsigned int)(t2-t1), lock_timeout);
+
+ status = cli_close(cli1, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_lock32(cli2, fnum3, 0, 4, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("lock4 succeeded! This is a locking bug\n");
+ return false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_FILE_LOCK_CONFLICT)) {
+ return false;
+ }
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli2, fnum3);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close3 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+
+ if (!torture_close_connection(cli1)) {
+ return False;
+ }
+
+ if (!torture_close_connection(cli2)) {
+ return False;
+ }
+
+ printf("Passed locktest1\n");
+ return True;
+}
+
+/*
+ this checks to see if a secondary tconx can use open files from an
+ earlier tconx
+ */
+static bool run_tcon_test(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname = "\\tcontest.tmp";
+ uint16_t fnum1;
+ uint32_t cnum1, cnum2, cnum3;
+ struct smbXcli_tcon *orig_tcon = NULL;
+ char *orig_share = NULL;
+ uint16_t vuid1, vuid2;
+ char buf[4];
+ bool ret = True;
+ NTSTATUS status;
+
+ memset(buf, '\0', sizeof(buf));
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ printf("starting tcontest\n");
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ cnum1 = cli_state_get_tid(cli);
+ vuid1 = cli_state_get_uid(cli);
+
+ status = cli_writeall(cli, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("initial write failed (%s)", nt_errstr(status));
+ return False;
+ }
+
+ cli_state_save_tcon_share(cli, &orig_tcon, &orig_share);
+
+ status = cli_tree_connect_creds(cli, share, "?????", torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s refused 2nd tree connect (%s)\n", host,
+ nt_errstr(status));
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ cli_shutdown(cli);
+ return False;
+ }
+
+ cnum2 = cli_state_get_tid(cli);
+ cnum3 = MAX(cnum1, cnum2) + 1; /* any invalid number */
+ vuid2 = cli_state_get_uid(cli) + 1;
+
+ /* try a write with the wrong tid */
+ cli_state_set_tid(cli, cnum2);
+
+ status = cli_writeall(cli, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("* server allows write with wrong TID\n");
+ ret = False;
+ } else {
+ printf("server fails write with wrong TID : %s\n",
+ nt_errstr(status));
+ }
+
+
+ /* try a write with an invalid tid */
+ cli_state_set_tid(cli, cnum3);
+
+ status = cli_writeall(cli, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("* server allows write with invalid TID\n");
+ ret = False;
+ } else {
+ printf("server fails write with invalid TID : %s\n",
+ nt_errstr(status));
+ }
+
+ /* try a write with an invalid vuid */
+ cli_state_set_uid(cli, vuid2);
+ cli_state_set_tid(cli, cnum1);
+
+ status = cli_writeall(cli, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("* server allows write with invalid VUID\n");
+ ret = False;
+ } else {
+ printf("server fails write with invalid VUID : %s\n",
+ nt_errstr(status));
+ }
+
+ cli_state_set_tid(cli, cnum1);
+ cli_state_set_uid(cli, vuid1);
+
+ status = cli_close(cli, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ cli_shutdown(cli);
+ return False;
+ }
+
+ cli_state_set_tid(cli, cnum2);
+
+ status = cli_tdis(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("secondary tdis failed (%s)\n", nt_errstr(status));
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ cli_shutdown(cli);
+ return False;
+ }
+
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+
+ cli_state_set_tid(cli, cnum1);
+
+ if (!torture_close_connection(cli)) {
+ return False;
+ }
+
+ return ret;
+}
+
+
+/*
+ checks for old style tcon support
+ */
+static bool run_tcon2_test(int dummy)
+{
+ static struct cli_state *cli;
+ uint16_t cnum, max_xmit;
+ char *service;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ printf("starting tcon2 test\n");
+
+ if (asprintf(&service, "\\\\%s\\%s", host, share) == -1) {
+ return false;
+ }
+
+ status = cli_raw_tcon(cli, service, password, "?????", &max_xmit, &cnum);
+
+ SAFE_FREE(service);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("tcon2 failed : %s\n", nt_errstr(status));
+ } else {
+ printf("tcon OK : max_xmit=%d cnum=%d\n",
+ (int)max_xmit, (int)cnum);
+ }
+
+ if (!torture_close_connection(cli)) {
+ return False;
+ }
+
+ printf("Passed tcon2 test\n");
+ return True;
+}
+
+static bool tcon_devtest(struct cli_state *cli,
+ const char *myshare, const char *devtype,
+ const char *return_devtype,
+ NTSTATUS expected_error)
+{
+ NTSTATUS status;
+ bool ret;
+
+ status = cli_tree_connect_creds(cli, myshare, devtype, torture_creds);
+
+ if (NT_STATUS_IS_OK(expected_error)) {
+ if (NT_STATUS_IS_OK(status)) {
+ if (return_devtype != NULL &&
+ strequal(cli->dev, return_devtype)) {
+ ret = True;
+ } else {
+ printf("tconX to share %s with type %s "
+ "succeeded but returned the wrong "
+ "device type (got [%s] but should have got [%s])\n",
+ myshare, devtype, cli->dev, return_devtype);
+ ret = False;
+ }
+ } else {
+ printf("tconX to share %s with type %s "
+ "should have succeeded but failed\n",
+ myshare, devtype);
+ ret = False;
+ }
+ cli_tdis(cli);
+ } else {
+ if (NT_STATUS_IS_OK(status)) {
+ printf("tconx to share %s with type %s "
+ "should have failed but succeeded\n",
+ myshare, devtype);
+ ret = False;
+ } else {
+ if (NT_STATUS_EQUAL(status, expected_error)) {
+ ret = True;
+ } else {
+ printf("Returned unexpected error\n");
+ ret = False;
+ }
+ }
+ }
+ return ret;
+}
+
+/*
+ checks for correct tconX support
+ */
+static bool run_tcon_devtype_test(int dummy)
+{
+ static struct cli_state *cli1 = NULL;
+ int flags = CLI_FULL_CONNECTION_FORCE_SMB1;
+ NTSTATUS status;
+ bool ret = True;
+
+ status = cli_full_connection_creds(&cli1,
+ myname,
+ host,
+ NULL, /* dest_ss */
+ port_to_use,
+ NULL, /* service */
+ NULL, /* service_type */
+ torture_creds,
+ flags);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("could not open connection\n");
+ return False;
+ }
+
+ if (!tcon_devtest(cli1, "IPC$", "A:", NULL, NT_STATUS_BAD_DEVICE_TYPE))
+ ret = False;
+
+ if (!tcon_devtest(cli1, "IPC$", "?????", "IPC", NT_STATUS_OK))
+ ret = False;
+
+ if (!tcon_devtest(cli1, "IPC$", "LPT:", NULL, NT_STATUS_BAD_DEVICE_TYPE))
+ ret = False;
+
+ if (!tcon_devtest(cli1, "IPC$", "IPC", "IPC", NT_STATUS_OK))
+ ret = False;
+
+ if (!tcon_devtest(cli1, "IPC$", "FOOBA", NULL, NT_STATUS_BAD_DEVICE_TYPE))
+ ret = False;
+
+ if (!tcon_devtest(cli1, share, "A:", "A:", NT_STATUS_OK))
+ ret = False;
+
+ if (!tcon_devtest(cli1, share, "?????", "A:", NT_STATUS_OK))
+ ret = False;
+
+ if (!tcon_devtest(cli1, share, "LPT:", NULL, NT_STATUS_BAD_DEVICE_TYPE))
+ ret = False;
+
+ if (!tcon_devtest(cli1, share, "IPC", NULL, NT_STATUS_BAD_DEVICE_TYPE))
+ ret = False;
+
+ if (!tcon_devtest(cli1, share, "FOOBA", NULL, NT_STATUS_BAD_DEVICE_TYPE))
+ ret = False;
+
+ cli_shutdown(cli1);
+
+ if (ret)
+ printf("Passed tcondevtest\n");
+
+ return ret;
+}
+
+
+/*
+ This test checks that
+
+ 1) the server supports multiple locking contexts on the one SMB
+ connection, distinguished by PID.
+
+ 2) the server correctly fails overlapping locks made by the same PID (this
+ goes against POSIX behaviour, which is why it is tricky to implement)
+
+ 3) the server denies unlock requests by an incorrect client PID
+*/
+static bool run_locktest2(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname = "\\lockt2.lck";
+ uint16_t fnum1, fnum2, fnum3;
+ bool correct = True;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ printf("starting locktest2\n");
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ cli_setpid(cli, 1);
+
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_openx(cli, fname, O_RDWR, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ cli_setpid(cli, 2);
+
+ status = cli_openx(cli, fname, O_RDWR, DENY_NONE, &fnum3);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open3 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ cli_setpid(cli, 1);
+
+ status = cli_lock32(cli, fnum1, 0, 4, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock1 failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_lock32(cli, fnum1, 0, 4, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("WRITE lock1 succeeded! This is a locking bug\n");
+ correct = false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_LOCK_NOT_GRANTED)) {
+ return false;
+ }
+ }
+
+ status = cli_lock32(cli, fnum2, 0, 4, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("WRITE lock2 succeeded! This is a locking bug\n");
+ correct = false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_LOCK_NOT_GRANTED)) {
+ return false;
+ }
+ }
+
+ status = cli_lock32(cli, fnum2, 0, 4, 0, READ_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("READ lock2 succeeded! This is a locking bug\n");
+ correct = false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_FILE_LOCK_CONFLICT)) {
+ return false;
+ }
+ }
+
+ status = cli_lock32(cli, fnum1, 100, 4, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock at 100 failed (%s)\n", nt_errstr(status));
+ }
+ cli_setpid(cli, 2);
+ if (NT_STATUS_IS_OK(cli_unlock(cli, fnum1, 100, 4))) {
+ printf("unlock at 100 succeeded! This is a locking bug\n");
+ correct = False;
+ }
+
+ status = cli_unlock(cli, fnum1, 0, 4);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("unlock1 succeeded! This is a locking bug\n");
+ correct = false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_RANGE_NOT_LOCKED)) {
+ return false;
+ }
+ }
+
+ status = cli_unlock(cli, fnum1, 0, 8);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("unlock2 succeeded! This is a locking bug\n");
+ correct = false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_RANGE_NOT_LOCKED)) {
+ return false;
+ }
+ }
+
+ status = cli_lock32(cli, fnum3, 0, 4, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("lock3 succeeded! This is a locking bug\n");
+ correct = false;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, ERRlock,
+ NT_STATUS_LOCK_NOT_GRANTED)) {
+ return false;
+ }
+ }
+
+ cli_setpid(cli, 1);
+
+ status = cli_close(cli, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli, fnum3);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close3 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("locktest2 finished\n");
+
+ return correct;
+}
+
+
+/*
+ This test checks that
+
+ 1) the server supports the full offset range in lock requests
+*/
+static bool run_locktest3(int dummy)
+{
+ static struct cli_state *cli1, *cli2;
+ const char *fname = "\\lockt3.lck";
+ uint16_t fnum1, fnum2;
+ int i;
+ uint32_t offset;
+ bool correct = True;
+ NTSTATUS status;
+
+#define NEXT_OFFSET offset += (~(uint32_t)0) / torture_numops
+
+ if (!torture_open_connection(&cli1, 0) || !torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ printf("starting locktest3\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE,
+ &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_openx(cli2, fname, O_RDWR, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ for (offset=i=0;i<torture_numops;i++) {
+ NEXT_OFFSET;
+
+ status = cli_lock32(cli1, fnum1, offset-1, 1, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock1 %d failed (%s)\n",
+ i,
+ nt_errstr(status));
+ return False;
+ }
+
+ status = cli_lock32(cli2, fnum2, offset-2, 1, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock2 %d failed (%s)\n",
+ i,
+ nt_errstr(status));
+ return False;
+ }
+ }
+
+ for (offset=i=0;i<torture_numops;i++) {
+ NEXT_OFFSET;
+
+ status = cli_lock32(cli1, fnum1, offset-2, 1, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("error: lock1 %d succeeded!\n", i);
+ return False;
+ }
+
+ status = cli_lock32(cli2, fnum2, offset-1, 1, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("error: lock2 %d succeeded!\n", i);
+ return False;
+ }
+
+ status = cli_lock32(cli1, fnum1, offset-1, 1, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("error: lock3 %d succeeded!\n", i);
+ return False;
+ }
+
+ status = cli_lock32(cli2, fnum2, offset-2, 1, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("error: lock4 %d succeeded!\n", i);
+ return False;
+ }
+ }
+
+ for (offset=i=0;i<torture_numops;i++) {
+ NEXT_OFFSET;
+
+ status = cli_unlock(cli1, fnum1, offset-1, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlock1 %d failed (%s)\n",
+ i,
+ nt_errstr(status));
+ return False;
+ }
+
+ status = cli_unlock(cli2, fnum2, offset-2, 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlock2 %d failed (%s)\n",
+ i,
+ nt_errstr(status));
+ return False;
+ }
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+
+ if (!torture_close_connection(cli2)) {
+ correct = False;
+ }
+
+ printf("finished locktest3\n");
+
+ return correct;
+}
+
+static bool test_cli_read(struct cli_state *cli, uint16_t fnum,
+ char *buf, off_t offset, size_t size,
+ size_t *nread, size_t expect)
+{
+ NTSTATUS status;
+ size_t l_nread;
+
+ status = cli_read(cli, fnum, buf, offset, size, &l_nread);
+
+ if(!NT_STATUS_IS_OK(status)) {
+ return false;
+ } else if (l_nread != expect) {
+ return false;
+ }
+
+ if (nread) {
+ *nread = l_nread;
+ }
+
+ return true;
+}
+
+#define EXPECTED(ret, v) if ((ret) != (v)) { \
+ printf("** "); correct = False; \
+ }
+
+/*
+ looks at overlapping locks
+*/
+static bool run_locktest4(int dummy)
+{
+ static struct cli_state *cli1, *cli2;
+ const char *fname = "\\lockt4.lck";
+ uint16_t fnum1, fnum2, f;
+ bool ret;
+ char buf[1000];
+ bool correct = True;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli1, 0) || !torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ printf("starting locktest4\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+ cli_openx(cli2, fname, O_RDWR, DENY_NONE, &fnum2);
+
+ memset(buf, 0, sizeof(buf));
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 0, sizeof(buf),
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to create file: %s\n", nt_errstr(status));
+ correct = False;
+ goto fail;
+ }
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 0, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 2, 4, 0, WRITE_LOCK));
+ EXPECTED(ret, False);
+ printf("the same process %s set overlapping write locks\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 10, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 12, 4, 0, READ_LOCK));
+ EXPECTED(ret, True);
+ printf("the same process %s set overlapping read locks\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 20, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli2, fnum2, 22, 4, 0, WRITE_LOCK));
+ EXPECTED(ret, False);
+ printf("a different connection %s set overlapping write locks\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 30, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli2, fnum2, 32, 4, 0, READ_LOCK));
+ EXPECTED(ret, True);
+ printf("a different connection %s set overlapping read locks\n", ret?"can":"cannot");
+
+ ret = (cli_setpid(cli1, 1),
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 40, 4, 0, WRITE_LOCK))) &&
+ (cli_setpid(cli1, 2),
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 42, 4, 0, WRITE_LOCK)));
+ EXPECTED(ret, False);
+ printf("a different pid %s set overlapping write locks\n", ret?"can":"cannot");
+
+ ret = (cli_setpid(cli1, 1),
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 50, 4, 0, READ_LOCK))) &&
+ (cli_setpid(cli1, 2),
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 52, 4, 0, READ_LOCK)));
+ EXPECTED(ret, True);
+ printf("a different pid %s set overlapping read locks\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 60, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 60, 4, 0, READ_LOCK));
+ EXPECTED(ret, True);
+ printf("the same process %s set the same read lock twice\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 70, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 70, 4, 0, WRITE_LOCK));
+ EXPECTED(ret, False);
+ printf("the same process %s set the same write lock twice\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 80, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 80, 4, 0, WRITE_LOCK));
+ EXPECTED(ret, False);
+ printf("the same process %s overlay a read lock with a write lock\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 90, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 90, 4, 0, READ_LOCK));
+ EXPECTED(ret, True);
+ printf("the same process %s overlay a write lock with a read lock\n", ret?"can":"cannot");
+
+ ret = (cli_setpid(cli1, 1),
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 100, 4, 0, WRITE_LOCK))) &&
+ (cli_setpid(cli1, 2),
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 100, 4, 0, READ_LOCK)));
+ EXPECTED(ret, False);
+ printf("a different pid %s overlay a write lock with a read lock\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 110, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 112, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 110, 6));
+ EXPECTED(ret, False);
+ printf("the same process %s coalesce read locks\n", ret?"can":"cannot");
+
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 120, 4, 0, WRITE_LOCK)) &&
+ test_cli_read(cli2, fnum2, buf, 120, 4, NULL, 4);
+ EXPECTED(ret, False);
+ printf("this server %s strict write locking\n", ret?"doesn't do":"does");
+
+ status = cli_lock32(cli1, fnum1, 130, 4, 0, READ_LOCK);
+ ret = NT_STATUS_IS_OK(status);
+ if (ret) {
+ status = cli_writeall(cli2, fnum2, 0, (uint8_t *)buf, 130, 4,
+ NULL);
+ ret = NT_STATUS_IS_OK(status);
+ }
+ EXPECTED(ret, False);
+ printf("this server %s strict read locking\n", ret?"doesn't do":"does");
+
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 140, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 140, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 140, 4)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 140, 4));
+ EXPECTED(ret, True);
+ printf("this server %s do recursive read locking\n", ret?"does":"doesn't");
+
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 150, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 150, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 150, 4)) &&
+ test_cli_read(cli2, fnum2, buf, 150, 4, NULL, 4) &&
+ !(NT_STATUS_IS_OK(cli_writeall(cli2, fnum2, 0, (uint8_t *)buf,
+ 150, 4, NULL))) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 150, 4));
+ EXPECTED(ret, True);
+ printf("this server %s do recursive lock overlays\n", ret?"does":"doesn't");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 160, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 160, 4)) &&
+ NT_STATUS_IS_OK(cli_writeall(cli2, fnum2, 0, (uint8_t *)buf,
+ 160, 4, NULL)) &&
+ test_cli_read(cli2, fnum2, buf, 160, 4, NULL, 4);
+ EXPECTED(ret, True);
+ printf("the same process %s remove a read lock using write locking\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 170, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 170, 4)) &&
+ NT_STATUS_IS_OK(cli_writeall(cli2, fnum2, 0, (uint8_t *)buf,
+ 170, 4, NULL)) &&
+ test_cli_read(cli2, fnum2, buf, 170, 4, NULL, 4);
+ EXPECTED(ret, True);
+ printf("the same process %s remove a write lock using read locking\n", ret?"can":"cannot");
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 190, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 190, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 190, 4)) &&
+ !NT_STATUS_IS_OK(cli_writeall(cli2, fnum2, 0, (uint8_t *)buf,
+ 190, 4, NULL)) &&
+ test_cli_read(cli2, fnum2, buf, 190, 4, NULL, 4);
+ EXPECTED(ret, True);
+ printf("the same process %s remove the first lock first\n", ret?"does":"doesn't");
+
+ cli_close(cli1, fnum1);
+ cli_close(cli2, fnum2);
+ cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum1);
+ cli_openx(cli1, fname, O_RDWR, DENY_NONE, &f);
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 0, 8, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, f, 0, 1, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_close(cli1, fnum1)) &&
+ NT_STATUS_IS_OK(cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum1)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 7, 1, 0, WRITE_LOCK));
+ cli_close(cli1, f);
+ cli_close(cli1, fnum1);
+ EXPECTED(ret, True);
+ printf("the server %s have the NT byte range lock bug\n", !ret?"does":"doesn't");
+
+ fail:
+ cli_close(cli1, fnum1);
+ cli_close(cli2, fnum2);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ torture_close_connection(cli1);
+ torture_close_connection(cli2);
+
+ printf("finished locktest4\n");
+ return correct;
+}
+
+/*
+ looks at lock upgrade/downgrade.
+*/
+static bool run_locktest5(int dummy)
+{
+ static struct cli_state *cli1, *cli2;
+ const char *fname = "\\lockt5.lck";
+ uint16_t fnum1, fnum2, fnum3;
+ bool ret;
+ char buf[1000];
+ bool correct = True;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli1, 0) || !torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ printf("starting locktest5\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+ cli_openx(cli2, fname, O_RDWR, DENY_NONE, &fnum2);
+ cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum3);
+
+ memset(buf, 0, sizeof(buf));
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 0, sizeof(buf),
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to create file: %s\n", nt_errstr(status));
+ correct = False;
+ goto fail;
+ }
+
+ /* Check for NT bug... */
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 0, 8, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum3, 0, 1, 0, READ_LOCK));
+ cli_close(cli1, fnum1);
+ cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum1);
+ status = cli_lock32(cli1, fnum1, 7, 1, 0, WRITE_LOCK);
+ ret = NT_STATUS_IS_OK(status);
+ EXPECTED(ret, True);
+ printf("this server %s the NT locking bug\n", ret ? "doesn't have" : "has");
+ cli_close(cli1, fnum1);
+ cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum1);
+ cli_unlock(cli1, fnum3, 0, 1);
+
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 0, 4, 0, WRITE_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 1, 1, 0, READ_LOCK));
+ EXPECTED(ret, True);
+ printf("the same process %s overlay a write with a read lock\n", ret?"can":"cannot");
+
+ status = cli_lock32(cli2, fnum2, 0, 4, 0, READ_LOCK);
+ ret = NT_STATUS_IS_OK(status);
+ EXPECTED(ret, False);
+
+ printf("a different process %s get a read lock on the first process lock stack\n", ret?"can":"cannot");
+
+ /* Unlock the process 2 lock. */
+ cli_unlock(cli2, fnum2, 0, 4);
+
+ status = cli_lock32(cli1, fnum3, 0, 4, 0, READ_LOCK);
+ ret = NT_STATUS_IS_OK(status);
+ EXPECTED(ret, False);
+
+ printf("the same process on a different fnum %s get a read lock\n", ret?"can":"cannot");
+
+ /* Unlock the process 1 fnum3 lock. */
+ cli_unlock(cli1, fnum3, 0, 4);
+
+ /* Stack 2 more locks here. */
+ ret = NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 0, 4, 0, READ_LOCK)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli1, fnum1, 0, 4, 0, READ_LOCK));
+
+ EXPECTED(ret, True);
+ printf("the same process %s stack read locks\n", ret?"can":"cannot");
+
+ /* Unlock the first process lock, then check this was the WRITE lock that was
+ removed. */
+
+ ret = NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 0, 4)) &&
+ NT_STATUS_IS_OK(cli_lock32(cli2, fnum2, 0, 4, 0, READ_LOCK));
+
+ EXPECTED(ret, True);
+ printf("the first unlock removes the %s lock\n", ret?"WRITE":"READ");
+
+ /* Unlock the process 2 lock. */
+ cli_unlock(cli2, fnum2, 0, 4);
+
+ /* We should have 3 stacked locks here. Ensure we need to do 3 unlocks. */
+
+ ret = NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 1, 1)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 0, 4)) &&
+ NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 0, 4));
+
+ EXPECTED(ret, True);
+ printf("the same process %s unlock the stack of 4 locks\n", ret?"can":"cannot");
+
+ /* Ensure the next unlock fails. */
+ ret = NT_STATUS_IS_OK(cli_unlock(cli1, fnum1, 0, 4));
+ EXPECTED(ret, False);
+ printf("the same process %s count the lock stack\n", !ret?"can":"cannot");
+
+ /* Ensure connection 2 can get a write lock. */
+ status = cli_lock32(cli2, fnum2, 0, 4, 0, WRITE_LOCK);
+ ret = NT_STATUS_IS_OK(status);
+ EXPECTED(ret, True);
+
+ printf("a different process %s get a write lock on the unlocked stack\n", ret?"can":"cannot");
+
+
+ fail:
+ cli_close(cli1, fnum1);
+ cli_close(cli2, fnum2);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+ if (!torture_close_connection(cli2)) {
+ correct = False;
+ }
+
+ printf("finished locktest5\n");
+
+ return correct;
+}
+
+/*
+ tries the unusual lockingX locktype bits
+*/
+static bool run_locktest6(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname[1] = { "\\lock6.txt" };
+ int i;
+ uint16_t fnum;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ printf("starting locktest6\n");
+
+ for (i=0;i<1;i++) {
+ printf("Testing %s\n", fname[i]);
+
+ cli_unlink(cli, fname[i], FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ cli_openx(cli, fname[i], O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ status = cli_locktype(cli, fnum, 0, 8, 0, LOCKING_ANDX_CHANGE_LOCKTYPE);
+ cli_close(cli, fnum);
+ printf("CHANGE_LOCKTYPE gave %s\n", nt_errstr(status));
+
+ cli_openx(cli, fname[i], O_RDWR, DENY_NONE, &fnum);
+ status = cli_locktype(cli, fnum, 0, 8, 0, LOCKING_ANDX_CANCEL_LOCK);
+ cli_close(cli, fnum);
+ printf("CANCEL_LOCK gave %s\n", nt_errstr(status));
+
+ cli_unlink(cli, fname[i], FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ }
+
+ torture_close_connection(cli);
+
+ printf("finished locktest6\n");
+ return True;
+}
+
+static bool run_locktest7(int dummy)
+{
+ struct cli_state *cli1;
+ const char *fname = "\\lockt7.lck";
+ uint16_t fnum1;
+ char buf[200];
+ bool correct = False;
+ size_t nread;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ printf("starting locktest7\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+
+ memset(buf, 0, sizeof(buf));
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 0, sizeof(buf),
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to create file: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ cli_setpid(cli1, 1);
+
+ status = cli_lock32(cli1, fnum1, 130, 4, 0, READ_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Unable to apply read lock on range 130:4, "
+ "error was %s\n", nt_errstr(status));
+ goto fail;
+ } else {
+ printf("pid1 successfully locked range 130:4 for READ\n");
+ }
+
+ status = cli_read(cli1, fnum1, buf, 130, 4, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid1 unable to read the range 130:4, error was %s\n",
+ nt_errstr(status));
+ goto fail;
+ } else if (nread != 4) {
+ printf("pid1 unable to read the range 130:4, "
+ "recv %ld req %d\n", (unsigned long)nread, 4);
+ goto fail;
+ } else {
+ printf("pid1 successfully read the range 130:4\n");
+ }
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid1 unable to write to the range 130:4, error was "
+ "%s\n", nt_errstr(status));
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ printf("Incorrect error (should be NT_STATUS_FILE_LOCK_CONFLICT)\n");
+ goto fail;
+ }
+ } else {
+ printf("pid1 successfully wrote to the range 130:4 (should be denied)\n");
+ goto fail;
+ }
+
+ cli_setpid(cli1, 2);
+
+ status = cli_read(cli1, fnum1, buf, 130, 4, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid2 unable to read the range 130:4, error was %s\n",
+ nt_errstr(status));
+ goto fail;
+ } else if (nread != 4) {
+ printf("pid2 unable to read the range 130:4, "
+ "recv %ld req %d\n", (unsigned long)nread, 4);
+ goto fail;
+ } else {
+ printf("pid2 successfully read the range 130:4\n");
+ }
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid2 unable to write to the range 130:4, error was "
+ "%s\n", nt_errstr(status));
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ printf("Incorrect error (should be NT_STATUS_FILE_LOCK_CONFLICT)\n");
+ goto fail;
+ }
+ } else {
+ printf("pid2 successfully wrote to the range 130:4 (should be denied)\n");
+ goto fail;
+ }
+
+ cli_setpid(cli1, 1);
+ cli_unlock(cli1, fnum1, 130, 4);
+
+ status = cli_lock32(cli1, fnum1, 130, 4, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Unable to apply write lock on range 130:4, error was %s\n", nt_errstr(status));
+ goto fail;
+ } else {
+ printf("pid1 successfully locked range 130:4 for WRITE\n");
+ }
+
+ status = cli_read(cli1, fnum1, buf, 130, 4, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid1 unable to read the range 130:4, error was %s\n",
+ nt_errstr(status));
+ goto fail;
+ } else if (nread != 4) {
+ printf("pid1 unable to read the range 130:4, "
+ "recv %ld req %d\n", (unsigned long)nread, 4);
+ goto fail;
+ } else {
+ printf("pid1 successfully read the range 130:4\n");
+ }
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid1 unable to write to the range 130:4, error was "
+ "%s\n", nt_errstr(status));
+ goto fail;
+ } else {
+ printf("pid1 successfully wrote to the range 130:4\n");
+ }
+
+ cli_setpid(cli1, 2);
+
+ status = cli_read(cli1, fnum1, buf, 130, 4, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid2 unable to read the range 130:4, error was "
+ "%s\n", nt_errstr(status));
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ printf("Incorrect error (should be NT_STATUS_FILE_LOCK_CONFLICT)\n");
+ goto fail;
+ }
+ } else {
+ printf("pid2 successfully read the range 130:4 (should be denied) recv %ld\n",
+ (unsigned long)nread);
+ goto fail;
+ }
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 130, 4, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("pid2 unable to write to the range 130:4, error was "
+ "%s\n", nt_errstr(status));
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ printf("Incorrect error (should be NT_STATUS_FILE_LOCK_CONFLICT)\n");
+ goto fail;
+ }
+ } else {
+ printf("pid2 successfully wrote to the range 130:4 (should be denied)\n");
+ goto fail;
+ }
+
+ cli_unlock(cli1, fnum1, 130, 0);
+ correct = True;
+
+fail:
+ cli_close(cli1, fnum1);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ torture_close_connection(cli1);
+
+ printf("finished locktest7\n");
+ return correct;
+}
+
+/*
+ * This demonstrates a problem with our use of GPFS share modes: A file
+ * descriptor sitting in the pending close queue holding a GPFS share mode
+ * blocks opening a file another time. Happens with Word 2007 temp files.
+ * With "posix locking = yes" and "gpfs:sharemodes = yes" enabled, the third
+ * open is denied with NT_STATUS_SHARING_VIOLATION.
+ */
+
+static bool run_locktest8(int dummy)
+{
+ struct cli_state *cli1;
+ const char *fname = "\\lockt8.lck";
+ uint16_t fnum1, fnum2;
+ char buf[200];
+ bool correct = False;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ printf("starting locktest8\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_WRITE,
+ &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_openx returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_openx second time returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_lock32(cli1, fnum2, 1, 1, 0, READ_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Unable to apply read lock on range 1:1, error was "
+ "%s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_close(fnum1) %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_openx third time returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ correct = true;
+
+fail:
+ cli_close(cli1, fnum1);
+ cli_close(cli1, fnum2);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ torture_close_connection(cli1);
+
+ printf("finished locktest8\n");
+ return correct;
+}
+
+/*
+ * This test is designed to be run in conjunction with
+ * external NFS or POSIX locks taken in the filesystem.
+ * It checks that the smbd server will block until the
+ * lock is released and then acquire it. JRA.
+ */
+
+static bool got_alarm;
+static struct cli_state *alarm_cli;
+
+static void alarm_handler(int dummy)
+{
+ got_alarm = True;
+}
+
+static void alarm_handler_parent(int dummy)
+{
+ smbXcli_conn_disconnect(alarm_cli->conn, NT_STATUS_LOCAL_DISCONNECT);
+}
+
+static void do_local_lock(const char *fname, int read_fd, int write_fd)
+{
+ int fd;
+ char c = '\0';
+ struct flock lock;
+ const char *local_pathname = NULL;
+ int ret;
+
+ local_pathname = talloc_asprintf(talloc_tos(),
+ "%s/%s", local_path, fname);
+ if (!local_pathname) {
+ printf("child: alloc fail\n");
+ exit(1);
+ }
+
+ unlink(local_pathname);
+ fd = open(local_pathname, O_RDWR|O_CREAT, 0666);
+ if (fd == -1) {
+ printf("child: open of %s failed %s.\n",
+ local_pathname, strerror(errno));
+ exit(1);
+ }
+
+ /* Now take a fcntl lock. */
+ lock.l_type = F_WRLCK;
+ lock.l_whence = SEEK_SET;
+ lock.l_start = 0;
+ lock.l_len = 4;
+ lock.l_pid = getpid();
+
+ ret = fcntl(fd,F_SETLK,&lock);
+ if (ret == -1) {
+ printf("child: failed to get lock 0:4 on file %s. Error %s\n",
+ local_pathname, strerror(errno));
+ exit(1);
+ } else {
+ printf("child: got lock 0:4 on file %s.\n",
+ local_pathname );
+ fflush(stdout);
+ }
+
+ CatchSignal(SIGALRM, alarm_handler);
+ alarm(5);
+ /* Signal the parent. */
+ if (write(write_fd, &c, 1) != 1) {
+ printf("child: start signal fail %s.\n",
+ strerror(errno));
+ exit(1);
+ }
+ alarm(0);
+
+ alarm(10);
+ /* Wait for the parent to be ready. */
+ if (read(read_fd, &c, 1) != 1) {
+ printf("child: reply signal fail %s.\n",
+ strerror(errno));
+ exit(1);
+ }
+ alarm(0);
+
+ sleep(5);
+ close(fd);
+ printf("child: released lock 0:4 on file %s.\n",
+ local_pathname );
+ fflush(stdout);
+ exit(0);
+}
+
+static bool _run_locktest9X(const char *fname, int timeout)
+{
+ struct cli_state *cli1;
+ char *fpath = talloc_asprintf(talloc_tos(), "\\%s", fname);
+ uint16_t fnum;
+ bool correct = False;
+ int pipe_in[2], pipe_out[2];
+ pid_t child_pid;
+ char c = '\0';
+ int ret;
+ struct timeval start;
+ double seconds;
+ NTSTATUS status;
+
+ printf("starting locktest9X: %s\n", fname);
+
+ if (local_path == NULL) {
+ d_fprintf(stderr, "locktest9X must be given a local path via -l <localpath>\n");
+ return false;
+ }
+
+ if (pipe(pipe_in) == -1 || pipe(pipe_out) == -1) {
+ return false;
+ }
+
+ child_pid = fork();
+ if (child_pid == -1) {
+ return false;
+ }
+
+ if (child_pid == 0) {
+ /* Child. */
+ do_local_lock(fname, pipe_out[0], pipe_in[1]);
+ exit(0);
+ }
+
+ close(pipe_out[0]);
+ close(pipe_in[1]);
+ pipe_out[0] = -1;
+ pipe_in[1] = -1;
+
+ /* Parent. */
+ ret = read(pipe_in[0], &c, 1);
+ if (ret != 1) {
+ d_fprintf(stderr, "failed to read start signal from child. %s\n",
+ strerror(errno));
+ return false;
+ }
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ status = cli_openx(cli1, fpath, O_RDWR, DENY_NONE,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_openx returned %s\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure the child has the lock. */
+ status = cli_lock32(cli1, fnum, 0, 4, 0, WRITE_LOCK);
+ if (NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "Got the lock on range 0:4 - this should not happen !\n");
+ goto fail;
+ } else {
+ d_printf("Child has the lock.\n");
+ }
+
+ /* Tell the child to wait 5 seconds then exit. */
+ ret = write(pipe_out[1], &c, 1);
+ if (ret != 1) {
+ d_fprintf(stderr, "failed to send exit signal to child. %s\n",
+ strerror(errno));
+ goto fail;
+ }
+
+ /* Wait 20 seconds for the lock. */
+ alarm_cli = cli1;
+ CatchSignal(SIGALRM, alarm_handler_parent);
+ alarm(20);
+
+ start = timeval_current();
+
+ status = cli_lock32(cli1, fnum, 0, 4, timeout, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "Unable to apply write lock on range 0:4, error was "
+ "%s\n", nt_errstr(status));
+ goto fail_nofd;
+ }
+ alarm(0);
+
+ seconds = timeval_elapsed(&start);
+
+ printf("Parent got the lock after %.2f seconds.\n",
+ seconds);
+
+ status = cli_close(cli1, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "cli_close(fnum1) %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ correct = true;
+
+fail:
+ cli_close(cli1, fnum);
+ torture_close_connection(cli1);
+
+fail_nofd:
+
+ printf("finished locktest9X: %s\n", fname);
+ return correct;
+}
+
+static bool run_locktest9a(int dummy)
+{
+ return _run_locktest9X("lock9a.dat", -1);
+}
+
+static bool run_locktest9b(int dummy)
+{
+ return _run_locktest9X("lock9b.dat", 10000);
+}
+
+struct locktest10_state {
+ bool ok;
+ bool done;
+};
+
+static void locktest10_lockingx_done(struct tevent_req *subreq);
+static void locktest10_read_andx_done(struct tevent_req *subreq);
+
+static bool run_locktest10(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct cli_state *cli1 = NULL;
+ struct cli_state *cli2 = NULL;
+ struct smb1_lock_element lck = { 0 };
+ struct tevent_req *reqs[2] = { NULL };
+ struct tevent_req *smbreqs[2] = { NULL };
+ const char fname[] = "\\lockt10.lck";
+ uint16_t fnum1, fnum2;
+ bool ret = false;
+ bool ok;
+ uint8_t data = 1;
+ struct locktest10_state state = { .ok = true };
+ NTSTATUS status;
+
+ printf("starting locktest10\n");
+
+ ev = samba_tevent_context_init(NULL);
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ goto done;
+ }
+
+ ok = torture_open_connection(&cli1, 0);
+ if (!ok) {
+ goto done;
+ }
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ ok = torture_open_connection(&cli2, 1);
+ if (!ok) {
+ goto done;
+ }
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ status = cli_openx(cli1, fname, O_CREAT|O_RDWR, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_openx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_writeall(cli1, fnum1, 0, &data, 0, sizeof(data), NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_writeall failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_openx(cli2, fname, O_CREAT|O_RDWR, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_openx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_locktype(
+ cli2, fnum2, 0, 1, 0, LOCKING_ANDX_EXCLUSIVE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_locktype failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ lck = (struct smb1_lock_element) {
+ .pid = cli_getpid(cli1), .offset = 0, .length = 1,
+ };
+
+ reqs[0] = cli_lockingx_create(
+ ev, /* mem_ctx */
+ ev, /* tevent_context */
+ cli1, /* cli */
+ fnum1, /* fnum */
+ LOCKING_ANDX_EXCLUSIVE_LOCK, /* typeoflock */
+ 0, /* newoplocklevel */
+ 1, /* timeout */
+ 0, /* num_unlocks */
+ NULL, /* unlocks */
+ 1, /* num_locks */
+ &lck, /* locks */
+ &smbreqs[0]); /* psmbreq */
+ if (reqs[0] == NULL) {
+ d_fprintf(stderr, "cli_lockingx_create failed\n");
+ goto done;
+ }
+ tevent_req_set_callback(reqs[0], locktest10_lockingx_done, &state);
+
+ reqs[1] = cli_read_andx_create(
+ ev, /* mem_ctx */
+ ev, /* ev */
+ cli1, /* cli */
+ fnum1, /* fnum */
+ 0, /* offset */
+ 1, /* size */
+ &smbreqs[1]); /* psmbreq */
+ if (reqs[1] == NULL) {
+ d_fprintf(stderr, "cli_read_andx_create failed\n");
+ goto done;
+ }
+ tevent_req_set_callback(reqs[1], locktest10_read_andx_done, &state);
+
+ status = smb1cli_req_chain_submit(smbreqs, ARRAY_SIZE(smbreqs));
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "smb1cli_req_chain_submit failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ while (!state.done) {
+ tevent_loop_once(ev);
+ }
+
+ torture_close_connection(cli1);
+
+ if (state.ok) {
+ ret = true;
+ }
+done:
+ return ret;
+}
+
+static void locktest10_lockingx_done(struct tevent_req *subreq)
+{
+ struct locktest10_state *state = tevent_req_callback_data_void(subreq);
+ NTSTATUS status;
+
+ status = cli_lockingx_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ d_printf("cli_lockingx returned %s\n", nt_errstr(status));
+ state->ok = false;
+ }
+}
+
+static void locktest10_read_andx_done(struct tevent_req *subreq)
+{
+ struct locktest10_state *state = tevent_req_callback_data_void(subreq);
+ ssize_t received = -1;
+ uint8_t *rcvbuf = NULL;
+ NTSTATUS status;
+
+ status = cli_read_andx_recv(subreq, &received, &rcvbuf);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_REQUEST_ABORTED)) {
+ d_printf("cli_read_andx returned %s\n", nt_errstr(status));
+ state->ok = false;
+ }
+
+ state->done = true;
+ TALLOC_FREE(subreq);
+}
+
+static bool run_locktest11(int dummy)
+{
+ struct cli_state *cli1;
+ const char *fname = "\\lockt11.lck";
+ NTSTATUS status;
+ uint16_t fnum;
+ bool ret = false;
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli1, fname, O_CREAT|O_RDWR, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_openx returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ /*
+ * Test that LOCKING_ANDX_CANCEL_LOCK without any locks
+ * returns NT_STATUS_OK
+ */
+
+ status = cli_lockingx(
+ cli1, /* cli */
+ fnum, /* fnum */
+ LOCKING_ANDX_CANCEL_LOCK, /* typeoflock */
+ 0, /* newoplocklevel */
+ 0, /* timeout */
+ 0, /* num_unlocks */
+ NULL, /* unlocks */
+ 0, /* num_locks */
+ NULL); /* locks */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_lockingX returned %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ cli_close(cli1, fnum);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ return ret;
+}
+
+struct deferred_close_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+};
+
+static void deferred_close_waited(struct tevent_req *subreq);
+static void deferred_close_done(struct tevent_req *subreq);
+
+static struct tevent_req *deferred_close_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int wait_secs,
+ struct cli_state *cli,
+ uint16_t fnum)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct deferred_close_state *state = NULL;
+ struct timeval wakeup_time = timeval_current_ofs(wait_secs, 0);
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct deferred_close_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fnum = fnum;
+
+ subreq = tevent_wakeup_send(state, state->ev, wakeup_time);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, deferred_close_waited, req);
+ return req;
+}
+
+static void deferred_close_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct deferred_close_state *state = tevent_req_data(
+ req, struct deferred_close_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ subreq = cli_close_send(state, state->ev, state->cli, state->fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, deferred_close_done, req);
+}
+
+static void deferred_close_done(struct tevent_req *subreq)
+{
+ NTSTATUS status = cli_close_recv(subreq);
+ tevent_req_simple_finish_ntstatus(subreq, status);
+}
+
+static NTSTATUS deferred_close_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct lockread_state {
+ struct smb1_lock_element lck;
+ struct tevent_req *reqs[2];
+ struct tevent_req *smbreqs[2];
+ NTSTATUS lock_status;
+ NTSTATUS read_status;
+ uint8_t *readbuf;
+};
+
+static void lockread_lockingx_done(struct tevent_req *subreq);
+static void lockread_read_andx_done(struct tevent_req *subreq);
+
+static struct tevent_req *lockread_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum)
+{
+ struct tevent_req *req = NULL;
+ struct lockread_state *state = NULL;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state, struct lockread_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->lck = (struct smb1_lock_element) {
+ .pid = cli_getpid(cli), .offset = 0, .length = 1,
+ };
+
+ state->reqs[0] = cli_lockingx_create(
+ ev, /* mem_ctx */
+ ev, /* tevent_context */
+ cli, /* cli */
+ fnum, /* fnum */
+ LOCKING_ANDX_EXCLUSIVE_LOCK, /* typeoflock */
+ 0, /* newoplocklevel */
+ 10000, /* timeout */
+ 0, /* num_unlocks */
+ NULL, /* unlocks */
+ 1, /* num_locks */
+ &state->lck, /* locks */
+ &state->smbreqs[0]); /* psmbreq */
+ if (tevent_req_nomem(state->reqs[0], req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->reqs[0], lockread_lockingx_done, req);
+
+ state->reqs[1] = cli_read_andx_create(
+ ev, /* mem_ctx */
+ ev, /* ev */
+ cli, /* cli */
+ fnum, /* fnum */
+ 0, /* offset */
+ 1, /* size */
+ &state->smbreqs[1]); /* psmbreq */
+ if (tevent_req_nomem(state->reqs[1], req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ state->reqs[1], lockread_read_andx_done, req);
+
+ status = smb1cli_req_chain_submit(state->smbreqs, 2);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void lockread_lockingx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct lockread_state *state = tevent_req_data(
+ req, struct lockread_state);
+ state->lock_status = cli_lockingx_recv(subreq);
+ TALLOC_FREE(subreq);
+ d_fprintf(stderr,
+ "lockingx returned %s\n",
+ nt_errstr(state->lock_status));
+}
+
+static void lockread_read_andx_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct lockread_state *state = tevent_req_data(
+ req, struct lockread_state);
+ ssize_t received = -1;
+ uint8_t *rcvbuf = NULL;
+
+ state->read_status = cli_read_andx_recv(subreq, &received, &rcvbuf);
+
+ d_fprintf(stderr,
+ "read returned %s\n",
+ nt_errstr(state->read_status));
+
+ if (!NT_STATUS_IS_OK(state->read_status)) {
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+ return;
+ }
+
+ if (received > 0) {
+ state->readbuf = talloc_memdup(state, rcvbuf, received);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nomem(state->readbuf, req)) {
+ return;
+ }
+ }
+ TALLOC_FREE(subreq);
+ tevent_req_done(req);
+}
+
+static NTSTATUS lockread_recv(
+ struct tevent_req *req,
+ NTSTATUS *lock_status,
+ NTSTATUS *read_status,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **read_buf)
+{
+ struct lockread_state *state = tevent_req_data(
+ req, struct lockread_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *lock_status = state->lock_status;
+ *read_status = state->read_status;
+ if (state->readbuf != NULL) {
+ *read_buf = talloc_move(mem_ctx, &state->readbuf);
+ } else {
+ *read_buf = NULL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct lock12_state {
+ uint8_t dummy;
+};
+
+static void lock12_closed(struct tevent_req *subreq);
+static void lock12_read(struct tevent_req *subreq);
+
+static struct tevent_req *lock12_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum1,
+ uint16_t fnum2)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct lock12_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct lock12_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = deferred_close_send(state, ev, 1, cli, fnum1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, lock12_closed, req);
+
+ subreq = lockread_send(state, ev, cli, fnum2);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, lock12_read, req);
+
+ return req;
+}
+
+static void lock12_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = deferred_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ DBG_DEBUG("close returned %s\n", nt_errstr(status));
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+}
+
+static void lock12_read(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct lock12_state *state = tevent_req_data(
+ req, struct lock12_state);
+ NTSTATUS status, lock_status, read_status;
+ uint8_t *buf = NULL;
+
+ status = lockread_recv(
+ subreq, &lock_status, &read_status, state, &buf);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status) ||
+ tevent_req_nterror(req, lock_status) ||
+ tevent_req_nterror(req, read_status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS lock12_recv(struct tevent_req *req)
+
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+}
+
+static bool run_locktest12(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ struct cli_state *cli = NULL;
+ const char fname[] = "\\lockt12.lck";
+ uint16_t fnum1, fnum2;
+ bool ret = false;
+ bool ok;
+ uint8_t data = 1;
+ NTSTATUS status;
+
+ printf("starting locktest12\n");
+
+ ev = samba_tevent_context_init(NULL);
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ goto done;
+ }
+
+ ok = torture_open_connection(&cli, 0);
+ if (!ok) {
+ goto done;
+ }
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = cli_openx(cli, fname, O_CREAT|O_RDWR, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_openx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_openx(cli, fname, O_CREAT|O_RDWR, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_openx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_writeall(cli, fnum1, 0, &data, 0, sizeof(data), NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_writeall failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_locktype(
+ cli, fnum1, 0, 1, 0, LOCKING_ANDX_EXCLUSIVE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_locktype failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ req = lock12_send(ev, ev, cli, fnum1, fnum2);
+ if (req == NULL) {
+ d_fprintf(stderr, "lock12_send failed\n");
+ goto done;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_fprintf(stderr, "tevent_req_poll_ntstatus failed\n");
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "tevent_req_poll_ntstatus returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = lock12_recv(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "lock12 returned %s\n", nt_errstr(status));
+ goto done;
+ }
+
+ ret = true;
+done:
+ if (cli != NULL) {
+ torture_close_connection(cli);
+ }
+ return ret;
+}
+
+struct lock_ntcancel_state {
+ struct timeval start;
+ struct smb1_lock_element lck;
+ struct tevent_req *subreq;
+};
+
+static void lock_ntcancel_waited(struct tevent_req *subreq);
+static void lock_ntcancel_done(struct tevent_req *subreq);
+
+static struct tevent_req *lock_ntcancel_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct lock_ntcancel_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct lock_ntcancel_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->lck = (struct smb1_lock_element) {
+ .pid = cli_getpid(cli), .offset = 0, .length = 1,
+ };
+ state->start = timeval_current();
+
+ state->subreq = cli_lockingx_send(
+ state, /* mem_ctx */
+ ev, /* tevent_context */
+ cli, /* cli */
+ fnum, /* fnum */
+ LOCKING_ANDX_EXCLUSIVE_LOCK, /* typeoflock */
+ 0, /* newoplocklevel */
+ 10000, /* timeout */
+ 0, /* num_unlocks */
+ NULL, /* unlocks */
+ 1, /* num_locks */
+ &state->lck); /* locks */
+ if (tevent_req_nomem(state->subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreq, lock_ntcancel_done, req);
+
+ subreq = tevent_wakeup_send(state, ev, timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, lock_ntcancel_waited, req);
+ return req;
+}
+
+static void lock_ntcancel_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct lock_ntcancel_state *state = tevent_req_data(
+ req, struct lock_ntcancel_state);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ ok = tevent_req_cancel(state->subreq);
+ if (!ok) {
+ d_fprintf(stderr, "Could not cancel subreq\n");
+ tevent_req_oom(req);
+ return;
+ }
+}
+
+static void lock_ntcancel_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct lock_ntcancel_state *state = tevent_req_data(
+ req, struct lock_ntcancel_state);
+ NTSTATUS status;
+ double elapsed;
+
+ status = cli_lockingx_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ d_printf("cli_lockingx returned %s\n", nt_errstr(status));
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+
+ elapsed = timeval_elapsed(&state->start);
+
+ if (elapsed > 3) {
+ d_printf("cli_lockingx was too slow, cancel did not work\n");
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS lock_ntcancel_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool run_locktest13(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ struct cli_state *cli = NULL;
+ const char fname[] = "\\lockt13.lck";
+ uint16_t fnum1, fnum2;
+ bool ret = false;
+ bool ok;
+ uint8_t data = 1;
+ NTSTATUS status;
+
+ printf("starting locktest13\n");
+
+ ev = samba_tevent_context_init(NULL);
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ goto done;
+ }
+
+ ok = torture_open_connection(&cli, 0);
+ if (!ok) {
+ goto done;
+ }
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = cli_openx(cli, fname, O_CREAT|O_RDWR, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_openx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_openx(cli, fname, O_CREAT|O_RDWR, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_openx failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_writeall(cli, fnum1, 0, &data, 0, sizeof(data), NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_writeall failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = cli_locktype(
+ cli, fnum1, 0, 1, 0, LOCKING_ANDX_EXCLUSIVE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_locktype failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ req = lock_ntcancel_send(ev, ev, cli, fnum2);
+ if (req == NULL) {
+ d_fprintf(stderr, "lock_ntcancel_send failed\n");
+ goto done;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_fprintf(stderr, "tevent_req_poll_ntstatus failed\n");
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "tevent_req_poll_ntstatus returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = lock_ntcancel_recv(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "lock_ntcancel returned %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = true;
+done:
+ if (cli != NULL) {
+ torture_close_connection(cli);
+ }
+ return ret;
+}
+
+/*
+test whether fnums and tids open on one VC are available on another (a major
+security hole)
+*/
+static bool run_fdpasstest(int dummy)
+{
+ struct cli_state *cli1, *cli2;
+ const char *fname = "\\fdpass.tst";
+ uint16_t fnum1;
+ char buf[1024];
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli1, 0) || !torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ printf("starting fdpasstest\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE,
+ &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_writeall(cli1, fnum1, 0, (const uint8_t *)"hello world\n", 0,
+ 13, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ cli_state_set_uid(cli2, cli_state_get_uid(cli1));
+ cli_state_set_tid(cli2, cli_state_get_tid(cli1));
+ cli_setpid(cli2, cli_getpid(cli1));
+
+ if (test_cli_read(cli2, fnum1, buf, 0, 13, NULL, 13)) {
+ printf("read succeeded! nasty security hole [%s]\n", buf);
+ return false;
+ }
+
+ cli_close(cli1, fnum1);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ torture_close_connection(cli1);
+ torture_close_connection(cli2);
+
+ printf("finished fdpasstest\n");
+ return True;
+}
+
+static bool run_fdsesstest(int dummy)
+{
+ struct cli_state *cli;
+ uint16_t new_vuid;
+ uint16_t saved_vuid;
+ uint32_t new_cnum;
+ uint32_t saved_cnum;
+ const char *fname = "\\fdsess.tst";
+ const char *fname1 = "\\fdsess1.tst";
+ uint16_t fnum1;
+ uint16_t fnum2;
+ char buf[1024];
+ bool ret = True;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli, 0))
+ return False;
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ if (!torture_cli_session_setup2(cli, &new_vuid))
+ return False;
+
+ saved_cnum = cli_state_get_tid(cli);
+ if (!NT_STATUS_IS_OK(cli_tree_connect(cli, share, "?????", NULL)))
+ return False;
+ new_cnum = cli_state_get_tid(cli);
+ cli_state_set_tid(cli, saved_cnum);
+
+ printf("starting fdsesstest\n");
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_writeall(cli, fnum1, 0, (const uint8_t *)"hello world\n", 0, 13,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ saved_vuid = cli_state_get_uid(cli);
+ cli_state_set_uid(cli, new_vuid);
+
+ if (test_cli_read(cli, fnum1, buf, 0, 13, NULL, 13)) {
+ printf("read succeeded with different vuid! "
+ "nasty security hole [%s]\n", buf);
+ ret = false;
+ }
+ /* Try to open a file with different vuid, samba cnum. */
+ if (NT_STATUS_IS_OK(cli_openx(cli, fname1, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum2))) {
+ printf("create with different vuid, same cnum succeeded.\n");
+ cli_close(cli, fnum2);
+ cli_unlink(cli, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ } else {
+ printf("create with different vuid, same cnum failed.\n");
+ printf("This will cause problems with service clients.\n");
+ ret = False;
+ }
+
+ cli_state_set_uid(cli, saved_vuid);
+
+ /* Try with same vuid, different cnum. */
+ cli_state_set_tid(cli, new_cnum);
+
+ if (test_cli_read(cli, fnum1, buf, 0, 13, NULL, 13)) {
+ printf("read succeeded with different cnum![%s]\n", buf);
+ ret = false;
+ }
+
+ cli_state_set_tid(cli, saved_cnum);
+ cli_close(cli, fnum1);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ torture_close_connection(cli);
+
+ printf("finished fdsesstest\n");
+ return ret;
+}
+
+/*
+ This test checks that
+
+ 1) the server does not allow an unlink on a file that is open
+*/
+static bool run_unlinktest(int dummy)
+{
+ struct cli_state *cli;
+ const char *fname = "\\unlink.tst";
+ uint16_t fnum;
+ bool correct = True;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ printf("starting unlink test\n");
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ cli_setpid(cli, 1);
+
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_unlink(cli, fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("error: server allowed unlink on an open file\n");
+ correct = False;
+ } else {
+ correct = check_error(__LINE__, status, ERRDOS, ERRbadshare,
+ NT_STATUS_SHARING_VIOLATION);
+ }
+
+ cli_close(cli, fnum);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("unlink test finished\n");
+
+ return correct;
+}
+
+
+/*
+test how many open files this server supports on the one socket
+*/
+static bool run_maxfidtest(int dummy)
+{
+ struct cli_state *cli;
+ fstring fname;
+ uint16_t fnums[0x11000];
+ int i;
+ int retries=4;
+ bool correct = True;
+ NTSTATUS status;
+
+ cli = current_cli;
+
+ if (retries <= 0) {
+ printf("failed to connect\n");
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ for (i=0; i<0x11000; i++) {
+ slprintf(fname,sizeof(fname)-1,"\\maxfid.%d.%d", i,(int)getpid());
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT|O_TRUNC, DENY_NONE,
+ &fnums[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n",
+ fname, nt_errstr(status));
+ printf("maximum fnum is %d\n", i);
+ break;
+ }
+ printf("%6d\r", i);
+ }
+ printf("%6d\n", i);
+ i--;
+
+ printf("cleaning up\n");
+ for (;i>=0;i--) {
+ slprintf(fname,sizeof(fname)-1,"\\maxfid.%d.%d", i,(int)getpid());
+ cli_close(cli, fnums[i]);
+
+ status = cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink of %s failed (%s)\n",
+ fname, nt_errstr(status));
+ correct = False;
+ }
+ printf("%6d\r", i);
+ }
+ printf("%6d\n", 0);
+
+ printf("maxfid test finished\n");
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+ return correct;
+}
+
+/* generate a random buffer */
+static void rand_buf(char *buf, int len)
+{
+ while (len--) {
+ *buf = (char)sys_random();
+ buf++;
+ }
+}
+
+/* send smb negprot commands, not reading the response */
+static bool run_negprot_nowait(int dummy)
+{
+ struct tevent_context *ev;
+ int i;
+ struct cli_state *cli;
+ bool correct = True;
+
+ printf("starting negprot nowait test\n");
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ return false;
+ }
+
+ if (!(cli = open_nbt_connection())) {
+ TALLOC_FREE(ev);
+ return False;
+ }
+
+ for (i=0;i<50000;i++) {
+ struct tevent_req *req;
+
+ req = smbXcli_negprot_send(
+ ev,
+ ev,
+ cli->conn,
+ cli->timeout,
+ PROTOCOL_CORE,
+ PROTOCOL_NT1,
+ 0,
+ NULL);
+ if (req == NULL) {
+ TALLOC_FREE(ev);
+ return false;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ d_fprintf(stderr, "tevent_req_poll failed: %s\n",
+ strerror(errno));
+ TALLOC_FREE(ev);
+ return false;
+ }
+ TALLOC_FREE(req);
+ }
+
+ if (torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("finished negprot nowait test\n");
+
+ return correct;
+}
+
+/* send smb negprot commands, not reading the response */
+static bool run_bad_nbt_session(int dummy)
+{
+ struct nmb_name called, calling;
+ struct sockaddr_storage ss;
+ NTSTATUS status;
+ int fd;
+ bool ret;
+
+ printf("starting bad nbt session test\n");
+
+ make_nmb_name(&calling, myname, 0x0);
+ make_nmb_name(&called , host, 0x20);
+
+ if (!resolve_name(host, &ss, 0x20, true)) {
+ d_fprintf(stderr, "Could not resolve name %s\n", host);
+ return false;
+ }
+
+ status = open_socket_out(&ss, NBT_SMB_PORT, 10000, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "open_socket_out failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ ret = cli_bad_session_request(fd, &calling, &called);
+ close(fd);
+ if (!ret) {
+ d_fprintf(stderr, "open_socket_out failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ printf("finished bad nbt session test\n");
+ return true;
+}
+
+/* send random IPC commands */
+static bool run_randomipc(int dummy)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rdrcnt,rprcnt;
+ char param[1024];
+ int api, param_len, i;
+ struct cli_state *cli;
+ bool correct = True;
+ int count = 50000;
+
+ printf("starting random ipc test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ for (i=0;i<count;i++) {
+ api = sys_random() % 500;
+ param_len = (sys_random() % 64);
+
+ rand_buf(param, param_len);
+
+ SSVAL(param,0,api);
+
+ cli_api(cli,
+ param, param_len, 8,
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt);
+ if (i % 100 == 0) {
+ printf("%d/%d\r", i,count);
+ }
+ }
+ printf("%d/%d\n", i, count);
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ printf("finished random ipc test\n");
+
+ return correct;
+}
+
+
+
+static void browse_callback(const char *sname, uint32_t stype,
+ const char *comment, void *state)
+{
+ printf("\t%20.20s %08x %s\n", sname, stype, comment);
+}
+
+
+
+/*
+ This test checks the browse list code
+
+*/
+static bool run_browsetest(int dummy)
+{
+ static struct cli_state *cli;
+ bool correct = True;
+
+ printf("starting browse test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ printf("domain list:\n");
+ cli_NetServerEnum(cli, cli->server_domain,
+ SV_TYPE_DOMAIN_ENUM,
+ browse_callback, NULL);
+
+ printf("machine list:\n");
+ cli_NetServerEnum(cli, cli->server_domain,
+ SV_TYPE_ALL,
+ browse_callback, NULL);
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("browse test finished\n");
+
+ return correct;
+
+}
+
+static bool check_attributes(struct cli_state *cli,
+ const char *fname,
+ uint32_t expected_attrs)
+{
+ uint32_t attrs = 0;
+ NTSTATUS status = cli_getatr(cli,
+ fname,
+ &attrs,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_getatr failed with %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ if (attrs != expected_attrs) {
+ printf("Attributes incorrect 0x%x, should be 0x%x\n",
+ (unsigned int)attrs,
+ (unsigned int)expected_attrs);
+ return false;
+ }
+ return true;
+}
+
+/*
+ This checks how the getatr calls works
+*/
+static bool run_attrtest(int dummy)
+{
+ struct cli_state *cli;
+ uint16_t fnum;
+ time_t t, t2;
+ const char *fname = "\\attrib123456789.tst";
+ bool correct = True;
+ NTSTATUS status;
+
+ printf("starting attrib test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ /* Ensure we can't unlink with out-of-range (unknown) attribute. */
+ status = cli_unlink(cli, fname, 0x20000);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ correct = false;
+ goto out;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_openx(cli, fname,
+ O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, &fnum);
+ cli_close(cli, fnum);
+
+ status = cli_getatr(cli, fname, NULL, NULL, &t);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("getatr failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ if (labs(t - time(NULL)) > 60*60*24*10) {
+ printf("ERROR: SMBgetatr bug. time is %s",
+ ctime(&t));
+ t = time(NULL);
+ correct = True;
+ }
+
+ t2 = t-60*60*24; /* 1 day ago */
+
+ /* Ensure we can't set with out-of-range (unknown) attribute. */
+ status = cli_setatr(cli, fname, 0x20000, t2);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) {
+ correct = false;
+ goto out;
+ }
+
+ status = cli_setatr(cli, fname, 0, t2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("setatr failed (%s)\n", nt_errstr(status));
+ correct = True;
+ }
+
+ status = cli_getatr(cli, fname, NULL, NULL, &t);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("getatr failed (%s)\n", nt_errstr(status));
+ correct = True;
+ }
+
+ if (t != t2) {
+ printf("ERROR: getatr/setatr bug. times are\n%s",
+ ctime(&t));
+ printf("%s", ctime(&t2));
+ correct = True;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Check cli_setpathinfo_ext() */
+ /* Re-create the file. */
+ status = cli_openx(cli, fname,
+ O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to recreate %s (%s)\n",
+ fname, nt_errstr(status));
+ correct = false;
+ }
+ cli_close(cli, fnum);
+
+ status = cli_setpathinfo_ext(
+ cli,
+ fname,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* create */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* access */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* write */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* change */
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_READONLY);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_setpathinfo_ext failed with %s\n",
+ nt_errstr(status));
+ correct = false;
+ }
+
+ /* Check attributes are correct. */
+ correct = check_attributes(cli,
+ fname,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_READONLY);
+ if (correct == false) {
+ goto out;
+ }
+
+ /* Setting to FILE_ATTRIBUTE_NORMAL should be ignored. */
+ status = cli_setpathinfo_ext(
+ cli,
+ fname,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* create */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* access */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* write */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* change */
+ FILE_ATTRIBUTE_NORMAL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_setpathinfo_ext failed with %s\n",
+ nt_errstr(status));
+ correct = false;
+ }
+
+ /* Check attributes are correct. */
+ correct = check_attributes(cli,
+ fname,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_READONLY);
+ if (correct == false) {
+ goto out;
+ }
+
+ /* Setting to (uint16_t)-1 should also be ignored. */
+ status = cli_setpathinfo_ext(
+ cli,
+ fname,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* create */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* access */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* write */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* change */
+ (uint32_t)-1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_setpathinfo_ext failed with %s\n",
+ nt_errstr(status));
+ correct = false;
+ }
+
+ /* Check attributes are correct. */
+ correct = check_attributes(cli,
+ fname,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_READONLY);
+ if (correct == false) {
+ goto out;
+ }
+
+ /* Setting to 0 should clear them all. */
+ status = cli_setpathinfo_ext(
+ cli,
+ fname,
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* create */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* access */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* write */
+ (struct timespec) { .tv_nsec = SAMBA_UTIME_OMIT }, /* change */
+ 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_setpathinfo_ext failed with %s\n",
+ nt_errstr(status));
+ correct = false;
+ }
+
+ /* Check attributes are correct. */
+ correct = check_attributes(cli,
+ fname,
+ FILE_ATTRIBUTE_NORMAL);
+ if (correct == false) {
+ goto out;
+ }
+
+ out:
+
+ cli_unlink(cli,
+ fname,
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_HIDDEN|
+ FILE_ATTRIBUTE_READONLY);
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("attrib test finished\n");
+
+ return correct;
+}
+
+static NTSTATUS cli_qfilename(
+ struct cli_state *cli,
+ uint16_t fnum,
+ TALLOC_CTX *mem_ctx,
+ char **_name)
+{
+ uint16_t recv_flags2;
+ uint8_t *rdata;
+ uint32_t num_rdata;
+ NTSTATUS status;
+ char *name = NULL;
+ uint32_t namelen;
+
+ status = cli_qfileinfo(talloc_tos(), cli, fnum,
+ SMB_QUERY_FILE_NAME_INFO,
+ 4, CLI_BUFFER_SIZE, &recv_flags2,
+ &rdata, &num_rdata);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ namelen = IVAL(rdata, 0);
+ if (namelen > (num_rdata - 4)) {
+ TALLOC_FREE(rdata);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ pull_string_talloc(mem_ctx,
+ (const char *)rdata,
+ recv_flags2,
+ &name,
+ rdata + 4,
+ namelen,
+ STR_UNICODE);
+ if (name == NULL) {
+ status = map_nt_error_from_unix(errno);
+ TALLOC_FREE(rdata);
+ return status;
+ }
+
+ *_name = name;
+ TALLOC_FREE(rdata);
+ return NT_STATUS_OK;
+}
+
+/*
+ This checks a couple of trans2 calls
+*/
+static bool run_trans2test(int dummy)
+{
+ struct cli_state *cli;
+ uint16_t fnum;
+ off_t size;
+ time_t c_time, a_time, m_time;
+ struct timespec c_time_ts, a_time_ts, m_time_ts, w_time_ts, m_time2_ts;
+ const char *fname = "\\trans2.tst";
+ const char *dname = "\\trans2";
+ const char *fname2 = "\\trans2\\trans2.tst";
+ char *pname = NULL;
+ bool correct = True;
+ NTSTATUS status;
+ uint32_t fs_attr;
+ uint64_t ino;
+
+ printf("starting trans2 test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ /* Ensure ino is zero, SMB2 gets a real one. */
+ ino = 0;
+ } else {
+ /* Ensure ino is -1, SMB1 never gets a real one. */
+ ino = (uint64_t)-1;
+ }
+
+ status = cli_get_fs_attr_info(cli, &fs_attr);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: cli_get_fs_attr_info returned %s\n",
+ nt_errstr(status));
+ correct = false;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_openx(cli, fname, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, &fnum);
+ status = cli_qfileinfo_basic(cli, fnum, NULL, &size, &c_time_ts,
+ &a_time_ts, &w_time_ts, &m_time_ts, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: qfileinfo failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_qfilename(cli, fnum, talloc_tos(), &pname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: qfilename failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+ else if (strcmp(pname, fname)) {
+ printf("qfilename gave different name? [%s] [%s]\n",
+ fname, pname);
+ correct = False;
+ }
+
+ cli_close(cli, fnum);
+
+ sleep(2);
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ status = cli_openx(cli, fname, O_RDWR | O_CREAT | O_TRUNC, DENY_NONE,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+ cli_close(cli, fnum);
+
+ status = cli_qpathinfo1(cli, fname, &c_time, &a_time, &m_time, &size,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: qpathinfo failed (%s)\n", nt_errstr(status));
+ correct = False;
+ } else {
+ time_t t = time(NULL);
+
+ if (c_time != m_time) {
+ printf("create time=%s", ctime(&c_time));
+ printf("modify time=%s", ctime(&m_time));
+ printf("This system appears to have sticky create times\n");
+ }
+ if ((labs(a_time - t) > 60) && (a_time % (60*60) == 0)) {
+ printf("access time=%s", ctime(&a_time));
+ printf("This system appears to set a midnight access time\n");
+ correct = False;
+ }
+
+ if (labs(m_time - t) > 60*60*24*7) {
+ printf("ERROR: totally incorrect times - maybe word reversed? mtime=%s", ctime(&m_time));
+ correct = False;
+ }
+ }
+
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_openx(cli, fname,
+ O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, &fnum);
+ cli_close(cli, fnum);
+ status = cli_qpathinfo2(cli,
+ fname,
+ &c_time_ts,
+ &a_time_ts,
+ &w_time_ts,
+ &m_time_ts,
+ &size,
+ NULL,
+ &ino,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: qpathinfo2 failed (%s)\n", nt_errstr(status));
+ correct = False;
+ } else {
+ if (w_time_ts.tv_sec < 60*60*24*2) {
+ printf("write time=%s", ctime(&w_time_ts.tv_sec));
+ printf("This system appears to set a initial 0 write time\n");
+ correct = False;
+ }
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ /* SMB2 should always return an inode. */
+ if (ino == 0) {
+ printf("SMB2 bad inode (0)\n");
+ correct = false;
+ }
+ } else {
+ /* SMB1 must always return zero here. */
+ if (ino != 0) {
+ printf("SMB1 bad inode (!0)\n");
+ correct = false;
+ }
+ }
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+
+ /* check if the server updates the directory modification time
+ when creating a new file */
+ status = cli_mkdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: mkdir failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+ sleep(3);
+ status = cli_qpathinfo2(cli,
+ "\\trans2\\",
+ &c_time_ts,
+ &a_time_ts,
+ &w_time_ts,
+ &m_time_ts,
+ &size,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: qpathinfo2 failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ cli_openx(cli, fname2,
+ O_RDWR | O_CREAT | O_TRUNC, DENY_NONE, &fnum);
+ cli_writeall(cli, fnum, 0, (uint8_t *)&fnum, 0, sizeof(fnum), NULL);
+ cli_close(cli, fnum);
+ status = cli_qpathinfo2(cli,
+ "\\trans2\\",
+ &c_time_ts,
+ &a_time_ts,
+ &w_time_ts,
+ &m_time2_ts,
+ &size,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: qpathinfo2 failed (%s)\n", nt_errstr(status));
+ correct = False;
+ } else {
+ if (memcmp(&m_time_ts, &m_time2_ts, sizeof(struct timespec))
+ == 0) {
+ printf("This system does not update directory modification times\n");
+ correct = False;
+ }
+ }
+ cli_unlink(cli, fname2, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, dname);
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("trans2 test finished\n");
+
+ return correct;
+}
+
+/*
+ This checks new W2K calls.
+*/
+
+static NTSTATUS new_trans(struct cli_state *pcli, int fnum, int level)
+{
+ uint8_t *buf = NULL;
+ uint32_t len;
+ NTSTATUS status;
+
+ status = cli_qfileinfo(talloc_tos(), pcli, fnum, level, 0,
+ CLI_BUFFER_SIZE, NULL, &buf, &len);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: qfileinfo (%d) failed (%s)\n", level,
+ nt_errstr(status));
+ } else {
+ printf("qfileinfo: level %d, len = %u\n", level, len);
+ dump_data(0, (uint8_t *)buf, len);
+ printf("\n");
+ }
+ TALLOC_FREE(buf);
+ return status;
+}
+
+static bool run_w2ktest(int dummy)
+{
+ struct cli_state *cli;
+ uint16_t fnum;
+ const char *fname = "\\w2ktest\\w2k.tst";
+ int level;
+ bool correct = True;
+
+ printf("starting w2k test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ cli_openx(cli, fname,
+ O_RDWR | O_CREAT , DENY_NONE, &fnum);
+
+ for (level = 1004; level < 1040; level++) {
+ new_trans(cli, fnum, level);
+ }
+
+ cli_close(cli, fnum);
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("w2k test finished\n");
+
+ return correct;
+}
+
+
+/*
+ this is a harness for some oplock tests
+ */
+static bool run_oplock1(int dummy)
+{
+ struct cli_state *cli1;
+ const char *fname = "\\lockt1.lck";
+ uint16_t fnum1;
+ bool correct = True;
+ NTSTATUS status;
+
+ printf("starting oplock test 1\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ cli1->use_oplocks = True;
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE,
+ &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ cli1->use_oplocks = False;
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+
+ printf("finished oplock test 1\n");
+
+ return correct;
+}
+
+static bool run_oplock2(int dummy)
+{
+ struct cli_state *cli1, *cli2;
+ const char *fname = "\\lockt2.lck";
+ uint16_t fnum1, fnum2;
+ int saved_use_oplocks = use_oplocks;
+ char buf[4];
+ bool correct = True;
+ volatile bool *shared_correct;
+ size_t nread;
+ NTSTATUS status;
+
+ shared_correct = (volatile bool *)anonymous_shared_allocate(sizeof(bool));
+ *shared_correct = True;
+
+ use_level_II_oplocks = True;
+ use_oplocks = True;
+
+ printf("starting oplock test 2\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ use_level_II_oplocks = False;
+ use_oplocks = saved_use_oplocks;
+ return False;
+ }
+
+ if (!torture_open_connection(&cli2, 1)) {
+ use_level_II_oplocks = False;
+ use_oplocks = saved_use_oplocks;
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE,
+ &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ /* Don't need the globals any more. */
+ use_level_II_oplocks = False;
+ use_oplocks = saved_use_oplocks;
+
+ if (fork() == 0) {
+ /* Child code */
+ status = cli_openx(cli2, fname, O_RDWR, DENY_NONE, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("second open of %s failed (%s)\n", fname, nt_errstr(status));
+ *shared_correct = False;
+ exit(0);
+ }
+
+ sleep(2);
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ *shared_correct = False;
+ }
+
+ exit(0);
+ }
+
+ sleep(2);
+
+ /* Ensure cli1 processes the break. Empty file should always return 0
+ * bytes. */
+ status = cli_read(cli1, fnum1, buf, 0, 4, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("read on fnum1 failed (%s)\n", nt_errstr(status));
+ correct = false;
+ } else if (nread != 0) {
+ printf("read on empty fnum1 failed. recv %ld expected %d\n",
+ (unsigned long)nread, 0);
+ correct = false;
+ }
+
+ /* Should now be at level II. */
+ /* Test if sending a write locks causes a break to none. */
+ status = cli_lock32(cli1, fnum1, 0, 4, 0, READ_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ cli_unlock(cli1, fnum1, 0, 4);
+
+ sleep(2);
+
+ status = cli_lock32(cli1, fnum1, 0, 4, 0, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("lock failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ cli_unlock(cli1, fnum1, 0, 4);
+
+ sleep(2);
+
+ cli_read(cli1, fnum1, buf, 0, 4, NULL);
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ sleep(4);
+
+ status = cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+
+ if (!*shared_correct) {
+ correct = False;
+ }
+
+ printf("finished oplock test 2\n");
+
+ return correct;
+}
+
+struct oplock4_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ bool *got_break;
+ uint16_t *fnum2;
+};
+
+static void oplock4_got_break(struct tevent_req *req);
+static void oplock4_got_open(struct tevent_req *req);
+
+static bool run_oplock4(int dummy)
+{
+ struct tevent_context *ev;
+ struct cli_state *cli1, *cli2;
+ struct tevent_req *oplock_req, *open_req;
+ const char *fname = "\\lockt4.lck";
+ const char *fname_ln = "\\lockt4_ln.lck";
+ uint16_t fnum1, fnum2;
+ int saved_use_oplocks = use_oplocks;
+ NTSTATUS status;
+ bool correct = true;
+
+ bool got_break;
+
+ struct oplock4_state *state;
+
+ printf("starting oplock test 4\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ use_level_II_oplocks = false;
+ use_oplocks = saved_use_oplocks;
+ return false;
+ }
+
+ if (!torture_open_connection(&cli2, 1)) {
+ use_level_II_oplocks = false;
+ use_oplocks = saved_use_oplocks;
+ return false;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname_ln, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ /* Create the file. */
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE,
+ &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Now create a hardlink. */
+ status = cli_hardlink(cli1, fname, fname_ln);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("nt hardlink failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Prove that opening hardlinks cause deny modes to conflict. */
+ status = cli_openx(cli1, fname, O_RDWR, DENY_ALL, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_openx(cli1, fname_ln, O_RDWR, DENY_NONE, &fnum2);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("open of %s succeeded - should fail with sharing violation.\n",
+ fname_ln);
+ return false;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ printf("open of %s should fail with sharing violation. Got %s\n",
+ fname_ln, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ cli1->use_oplocks = true;
+ cli2->use_oplocks = true;
+
+ status = cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("tevent_context_init failed\n");
+ return false;
+ }
+
+ state = talloc(ev, struct oplock4_state);
+ if (state == NULL) {
+ printf("talloc failed\n");
+ return false;
+ }
+ state->ev = ev;
+ state->cli = cli1;
+ state->got_break = &got_break;
+ state->fnum2 = &fnum2;
+
+ oplock_req = cli_smb_oplock_break_waiter_send(
+ talloc_tos(), ev, cli1);
+ if (oplock_req == NULL) {
+ printf("cli_smb_oplock_break_waiter_send failed\n");
+ return false;
+ }
+ tevent_req_set_callback(oplock_req, oplock4_got_break, state);
+
+ open_req = cli_openx_send(
+ talloc_tos(), ev, cli2, fname_ln, O_RDWR, DENY_NONE);
+ if (open_req == NULL) {
+ printf("cli_openx_send failed\n");
+ return false;
+ }
+ tevent_req_set_callback(open_req, oplock4_got_open, state);
+
+ got_break = false;
+ fnum2 = 0xffff;
+
+ while (!got_break || fnum2 == 0xffff) {
+ int ret;
+ ret = tevent_loop_once(ev);
+ if (ret == -1) {
+ printf("tevent_loop_once failed: %s\n",
+ strerror(errno));
+ return false;
+ }
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ correct = false;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ correct = false;
+ }
+
+ status = cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ correct = false;
+ }
+
+ status = cli_unlink(cli1, fname_ln, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ correct = false;
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = false;
+ }
+
+ if (!got_break) {
+ correct = false;
+ }
+
+ printf("finished oplock test 4\n");
+
+ return correct;
+}
+
+static void oplock4_got_break(struct tevent_req *req)
+{
+ struct oplock4_state *state = tevent_req_callback_data(
+ req, struct oplock4_state);
+ uint16_t fnum;
+ uint8_t level;
+ NTSTATUS status;
+
+ status = cli_smb_oplock_break_waiter_recv(req, &fnum, &level);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_smb_oplock_break_waiter_recv returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+ *state->got_break = true;
+
+ req = cli_oplock_ack_send(state, state->ev, state->cli, fnum,
+ NO_OPLOCK);
+ if (req == NULL) {
+ printf("cli_oplock_ack_send failed\n");
+ return;
+ }
+}
+
+static void oplock4_got_open(struct tevent_req *req)
+{
+ struct oplock4_state *state = tevent_req_callback_data(
+ req, struct oplock4_state);
+ NTSTATUS status;
+
+ status = cli_openx_recv(req, state->fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_openx_recv returned %s\n", nt_errstr(status));
+ *state->fnum2 = 0xffff;
+ }
+}
+
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+
+struct oplock5_state {
+ int pipe_down_fd;
+};
+
+/*
+ * Async open the file that has a kernel oplock, do an echo to get
+ * that 100% across, close the file to signal to the child fd that the
+ * oplock can be dropped, wait for the open reply.
+ */
+
+static void oplock5_opened(struct tevent_req *subreq);
+static void oplock5_pong(struct tevent_req *subreq);
+static void oplock5_timedout(struct tevent_req *subreq);
+
+static struct tevent_req *oplock5_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *fname,
+ int pipe_down_fd)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct oplock5_state *state = NULL;
+ static uint8_t data = 0;
+
+ req = tevent_req_create(mem_ctx, &state, struct oplock5_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->pipe_down_fd = pipe_down_fd;
+
+ subreq = cli_ntcreate_send(
+ state,
+ ev,
+ cli,
+ fname,
+ 0, /* CreatFlags */
+ SEC_FILE_READ_DATA, /* DesiredAccess */
+ FILE_ATTRIBUTE_NORMAL, /* FileAttributes */
+ FILE_SHARE_WRITE|FILE_SHARE_READ, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ FILE_NON_DIRECTORY_FILE, /* CreateOptions */
+ 0, /* Impersonation */
+ 0); /* SecurityFlags */
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, oplock5_opened, req);
+
+ subreq = cli_echo_send(
+ state,
+ ev,
+ cli,
+ 1,
+ (DATA_BLOB) { .data = &data, .length = sizeof(data) });
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, oplock5_pong, req);
+
+ subreq = tevent_wakeup_send(state, ev, timeval_current_ofs(20, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, oplock5_timedout, req);
+
+ return req;
+}
+
+static void oplock5_opened(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+ uint16_t fnum;
+
+ status = cli_ntcreate_recv(subreq, &fnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void oplock5_pong(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct oplock5_state *state = tevent_req_data(
+ req, struct oplock5_state);
+ NTSTATUS status;
+
+ status = cli_echo_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ close(state->pipe_down_fd);
+}
+
+static void oplock5_timedout(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ bool ok;
+
+ ok = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ tevent_req_nterror(req, NT_STATUS_TIMEOUT);
+}
+
+static NTSTATUS oplock5_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool run_oplock5(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ struct cli_state *cli = NULL;
+ const char *fname = "oplock5.txt";
+ int pipe_down[2], pipe_up[2];
+ pid_t child_pid;
+ uint8_t c = '\0';
+ NTSTATUS status;
+ int ret;
+ bool ok;
+
+ printf("starting oplock5\n");
+
+ if (local_path == NULL) {
+ d_fprintf(stderr, "oplock5 must be given a local path via "
+ "-l <localpath>\n");
+ return false;
+ }
+
+ ret = pipe(pipe_down);
+ if (ret == -1) {
+ d_fprintf(stderr, "pipe() failed: %s\n", strerror(errno));
+ return false;
+ }
+ ret = pipe(pipe_up);
+ if (ret == -1) {
+ d_fprintf(stderr, "pipe() failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ child_pid = fork();
+ if (child_pid == -1) {
+ d_fprintf(stderr, "fork() failed: %s\n", strerror(errno));
+ return false;
+ }
+
+ if (child_pid == 0) {
+ char *local_file = NULL;
+ int fd;
+
+ close(pipe_down[1]);
+ close(pipe_up[0]);
+
+ local_file = talloc_asprintf(
+ talloc_tos(), "%s/%s", local_path, fname);
+ if (local_file == 0) {
+ c = 1;
+ goto do_write;
+ }
+ fd = open(local_file, O_RDWR|O_CREAT, 0644);
+ if (fd == -1) {
+ d_fprintf(stderr,
+ "open(%s) in child failed: %s\n",
+ local_file,
+ strerror(errno));
+ c = 2;
+ goto do_write;
+ }
+
+ signal(SIGIO, SIG_IGN);
+
+ ret = fcntl(fd, F_SETLEASE, F_WRLCK);
+ if (ret == -1) {
+ d_fprintf(stderr,
+ "SETLEASE in child failed: %s\n",
+ strerror(errno));
+ c = 3;
+ goto do_write;
+ }
+
+ do_write:
+ ret = sys_write(pipe_up[1], &c, sizeof(c));
+ if (ret == -1) {
+ d_fprintf(stderr,
+ "sys_write failed: %s\n",
+ strerror(errno));
+ exit(4);
+ }
+ ret = sys_read(pipe_down[0], &c, sizeof(c));
+ if (ret == -1) {
+ d_fprintf(stderr,
+ "sys_read failed: %s\n",
+ strerror(errno));
+ exit(5);
+ }
+ exit(0);
+ }
+
+ close(pipe_up[1]);
+ close(pipe_down[0]);
+
+ ret = sys_read(pipe_up[0], &c, sizeof(c));
+ if (ret != 1) {
+ d_fprintf(stderr,
+ "sys_read failed: %s\n",
+ strerror(errno));
+ return false;
+ }
+ if (c != 0) {
+ d_fprintf(stderr, "got error code %"PRIu8"\n", c);
+ return false;
+ }
+
+ ok = torture_open_connection(&cli, 0);
+ if (!ok) {
+ d_fprintf(stderr, "torture_open_connection failed\n");
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ return false;
+ }
+
+ req = oplock5_send(ev, ev, cli, fname, pipe_down[1]);
+ if (req == NULL) {
+ d_fprintf(stderr, "oplock5_send failed\n");
+ return false;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_fprintf(stderr,
+ "tevent_req_poll_ntstatus failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = oplock5_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "oplock5 failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+#endif /* HAVE_KERNEL_OPLOCKS_LINUX */
+
+/*
+ Test delete on close semantics.
+ */
+static bool run_deletetest(int dummy)
+{
+ struct cli_state *cli1 = NULL;
+ struct cli_state *cli2 = NULL;
+ const char *fname = "\\delete.file";
+ uint16_t fnum1 = (uint16_t)-1;
+ uint16_t fnum2 = (uint16_t)-1;
+ bool correct = false;
+ NTSTATUS status;
+
+ printf("starting delete test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ /* Test 1 - this should delete the file on close. */
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_ALL_ACCESS|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ FILE_DELETE_ON_CLOSE, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[1] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[1] close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_openx(cli1, fname, O_RDWR, DENY_NONE, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[1] open of %s succeeded (should fail)\n", fname);
+ goto fail;
+ }
+
+ printf("first delete on close test succeeded.\n");
+
+ /* Test 2 - this should delete the file on close. */
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[2] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[2] setting delete_on_close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[2] close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_NONE, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[2] open of %s succeeded should have been deleted on close !\n", fname);
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[2] close failed (%s)\n", nt_errstr(status));
+ }
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ goto fail;
+ }
+
+ printf("second delete on close test succeeded.\n");
+
+ /* Test 3 - ... */
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[3] open - 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail with a sharing violation - open for delete is only compatible
+ with SHARE_DELETE. */
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[3] open - 2 of %s succeeded - should have failed.\n", fname);
+ goto fail;
+ }
+
+ /* This should succeed. */
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[3] open - 3 of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[3] setting delete_on_close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[3] close 1 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[3] close 2 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail - file should no longer be there. */
+
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_NONE, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[3] open of %s succeeded should have been deleted on close !\n", fname);
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[3] close failed (%s)\n", nt_errstr(status));
+ }
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ goto fail;
+ }
+
+ printf("third delete on close test succeeded.\n");
+
+ /* Test 4 ... */
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[4] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should succeed. */
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[4] open - 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[4] close - 1 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[4] setting delete_on_close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail - no more opens once delete on close set. */
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[4] open - 3 of %s succeeded ! Should have failed.\n", fname );
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[4] close - 2 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ printf("fourth delete on close test succeeded.\n");
+
+ /* Test 5 ... */
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[5] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail - only allowed on NT opens with DELETE access. */
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[5] setting delete_on_close on OpenX file succeeded - should fail !\n");
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[5] close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ printf("fifth delete on close test succeeded.\n");
+
+ /* Test 6 ... */
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, FILE_READ_DATA|FILE_WRITE_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[6] open of %s failed (%s)\n", fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail - only allowed on NT opens with DELETE access. */
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[6] setting delete_on_close on file with no delete access succeeded - should fail !\n");
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[6] close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ printf("sixth delete on close test succeeded.\n");
+
+ /* Test 7 ... */
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[7] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[7] setting delete_on_close on file failed !\n");
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[7] unsetting delete_on_close on file failed !\n");
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[7] close - 1 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* This next open should succeed - we reset the flag. */
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[7] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[7] close - 2 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ printf("seventh delete on close test succeeded.\n");
+
+ /* Test 8 ... */
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!torture_open_connection(&cli2, 1)) {
+ printf("[8] failed to open second connection.\n");
+ goto fail;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ status = cli_ntcreate(cli1, fname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[8] open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[8] open 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[8] setting delete_on_close on file failed !\n");
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[8] close - 1 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[8] close - 2 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail.. */
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_NONE, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[8] open of %s succeeded should have been deleted on close !\n", fname);
+ goto fail;
+ }
+
+ printf("eighth delete on close test succeeded.\n");
+
+ /* Test 9 ... */
+
+ /* This should fail - we need to set DELETE_ACCESS. */
+ status = cli_ntcreate(cli1, fname, 0, FILE_READ_DATA|FILE_WRITE_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF,
+ FILE_DELETE_ON_CLOSE, 0, &fnum1, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[9] open of %s succeeded should have failed!\n", fname);
+ goto fail;
+ }
+
+ printf("ninth delete on close test succeeded.\n");
+
+ /* Test 10 ... */
+
+ status = cli_ntcreate(cli1, fname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, FILE_DELETE_ON_CLOSE,
+ 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[10] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should delete the file. */
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[10] close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail.. */
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_NONE, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[10] open of %s succeeded should have been deleted on close !\n", fname);
+ goto fail;
+ }
+
+ printf("tenth delete on close test succeeded.\n");
+
+ /* Test 11 ... */
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Can we open a read-only file with delete access? */
+
+ /* Create a readonly file. */
+ status = cli_ntcreate(cli1, fname, 0, FILE_READ_DATA|FILE_WRITE_DATA,
+ FILE_ATTRIBUTE_READONLY, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[11] open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[11] close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* Now try open for delete access. */
+ status = cli_ntcreate(cli1, fname, 0,
+ FILE_READ_ATTRIBUTES|DELETE_ACCESS,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[11] open of %s failed: %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ cli_close(cli1, fnum1);
+
+ printf("eleventh delete on close test succeeded.\n");
+
+ /*
+ * Test 12
+ * like test 4 but with initial delete on close
+ */
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OVERWRITE_IF,
+ FILE_DELETE_ON_CLOSE, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] open 2 of %s failed(%s).\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] close 1 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] setting delete_on_close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /* This should fail - no more opens once delete on close set. */
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[12] open 3 of %s succeeded - should fail).\n", fname);
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] unsetting delete_on_close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] open 4 of %s failed (%s)\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] close 2 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("[12] close 3 failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * setting delete on close on the handle does
+ * not unset the initial delete on close...
+ */
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0, 0, &fnum2, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("[12] open 5 of %s succeeded - should fail).\n", fname);
+ goto fail;
+ } else if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("ntcreate returned %s, expected "
+ "NT_STATUS_OBJECT_NAME_NOT_FOUND\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ printf("twelfth delete on close test succeeded.\n");
+
+
+ printf("finished delete test\n");
+
+ correct = true;
+
+ fail:
+ /* FIXME: This will crash if we aborted before cli2 got
+ * initialized, because these functions don't handle
+ * uninitialized connections. */
+
+ if (fnum1 != (uint16_t)-1) cli_close(cli1, fnum1);
+ if (fnum2 != (uint16_t)-1) cli_close(cli1, fnum2);
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (cli1 && !torture_close_connection(cli1)) {
+ correct = False;
+ }
+ if (cli2 && !torture_close_connection(cli2)) {
+ correct = False;
+ }
+ return correct;
+}
+
+struct delete_stream_state {
+ bool closed;
+};
+
+static void delete_stream_unlinked(struct tevent_req *subreq);
+static void delete_stream_closed(struct tevent_req *subreq);
+
+static struct tevent_req *delete_stream_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *base_fname,
+ uint16_t stream_fnum)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct delete_stream_state *state = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct delete_stream_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ subreq = cli_unlink_send(
+ state,
+ ev,
+ cli,
+ base_fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, delete_stream_unlinked, req);
+
+ subreq = cli_close_send(state, ev, cli, stream_fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, delete_stream_closed, req);
+
+ return req;
+}
+
+static void delete_stream_unlinked(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct delete_stream_state *state = tevent_req_data(
+ req, struct delete_stream_state);
+ NTSTATUS status;
+
+ status = cli_unlink_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ printf("cli_unlink returned %s\n",
+ nt_errstr(status));
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+ if (!state->closed) {
+ /* close reply should have come in first */
+ printf("Not closed\n");
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void delete_stream_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct delete_stream_state *state = tevent_req_data(
+ req, struct delete_stream_state);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ /* also waiting for the unlink to come back */
+ state->closed = true;
+}
+
+static NTSTATUS delete_stream_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool run_delete_stream(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ struct cli_state *cli = NULL;
+ const char fname[] = "delete_stream";
+ const char fname_stream[] = "delete_stream:Zone.Identifier:$DATA";
+ uint16_t fnum1, fnum2;
+ NTSTATUS status;
+ bool ok;
+
+ printf("Starting stream delete test\n");
+
+ ok = torture_open_connection(&cli, 0);
+ if (!ok) {
+ return false;
+ }
+
+ cli_setatr(cli, fname, 0, 0);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Create the file. */
+ status = cli_ntcreate(
+ cli,
+ fname,
+ 0,
+ READ_CONTROL_ACCESS,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0x0,
+ 0x0,
+ &fnum1,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_ntcreate of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ return false;
+ }
+ status = cli_close(cli, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_close of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Now create the stream. */
+ status = cli_ntcreate(
+ cli,
+ fname_stream,
+ 0,
+ FILE_WRITE_DATA,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_CREATE,
+ 0x0,
+ 0x0,
+ &fnum1,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "cli_ntcreate of %s failed (%s)\n",
+ fname_stream,
+ nt_errstr(status));
+ return false;
+ }
+
+ /* open it a second time */
+
+ status = cli_ntcreate(
+ cli,
+ fname_stream,
+ 0,
+ FILE_WRITE_DATA,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN,
+ 0x0,
+ 0x0,
+ &fnum2,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "2nd cli_ntcreate of %s failed (%s)\n",
+ fname_stream,
+ nt_errstr(status));
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ return false;
+ }
+
+ req = delete_stream_send(ev, ev, cli, fname, fnum1);
+ if (req == NULL) {
+ d_fprintf(stderr, "delete_stream_send failed\n");
+ return false;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_fprintf(stderr,
+ "tevent_req_poll_ntstatus failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = delete_stream_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "delete_stream failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "close failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = cli_unlink(
+ cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "unlink failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ Exercise delete on close semantics - use on the PRINT1 share in torture
+ testing.
+ */
+static bool run_delete_print_test(int dummy)
+{
+ struct cli_state *cli1 = NULL;
+ const char *fname = "print_delete.file";
+ uint16_t fnum1 = (uint16_t)-1;
+ bool correct = false;
+ const char *buf = "print file data\n";
+ NTSTATUS status;
+
+ printf("starting print delete test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_ALL_ACCESS|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_writeall(cli1,
+ fnum1,
+ 0,
+ (const uint8_t *)buf,
+ 0, /* offset */
+ strlen(buf), /* size */
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("writing print file data failed (%s)\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_nt_delete_on_close(cli1, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("setting delete_on_close failed (%s)\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ printf("finished print delete test\n");
+
+ correct = true;
+
+ fail:
+
+ if (fnum1 != (uint16_t)-1) {
+ cli_close(cli1, fnum1);
+ }
+
+ if (cli1 && !torture_close_connection(cli1)) {
+ correct = false;
+ }
+ return correct;
+}
+
+static bool run_deletetest_ln(int dummy)
+{
+ struct cli_state *cli;
+ const char *fname = "\\delete1";
+ const char *fname_ln = "\\delete1_ln";
+ uint16_t fnum;
+ uint16_t fnum1;
+ NTSTATUS status;
+ bool correct = true;
+ time_t t;
+
+ printf("starting deletetest-ln\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli, fname_ln, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ /* Create the file. */
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Now create a hardlink. */
+ status = cli_hardlink(cli, fname, fname_ln);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("nt hardlink failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ /* Open the original file. */
+ status = cli_ntcreate(cli, fname, 0, FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN_IF, 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ntcreate of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ /* Unlink the hard link path. */
+ status = cli_ntcreate(cli, fname_ln, 0, DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ntcreate of %s failed (%s)\n", fname_ln, nt_errstr(status));
+ return false;
+ }
+ status = cli_nt_delete_on_close(cli, fnum1, true);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) failed to set delete_on_close %s: %s\n",
+ __location__, fname_ln, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close %s failed (%s)\n",
+ fname_ln, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close %s failed (%s)\n",
+ fname, nt_errstr(status));
+ return false;
+ }
+
+ /* Ensure the original file is still there. */
+ status = cli_getatr(cli, fname, NULL, NULL, &t);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s getatr on file %s failed (%s)\n",
+ __location__,
+ fname,
+ nt_errstr(status));
+ correct = False;
+ }
+
+ /* Ensure the link path is gone. */
+ status = cli_getatr(cli, fname_ln, NULL, NULL, &t);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("%s, getatr for file %s returned wrong error code %s "
+ "- should have been deleted\n",
+ __location__,
+ fname_ln, nt_errstr(status));
+ correct = False;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli, fname_ln, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+
+ printf("finished deletetest-ln\n");
+
+ return correct;
+}
+
+/*
+ print out server properties
+ */
+static bool run_properties(int dummy)
+{
+ struct cli_state *cli;
+ bool correct = True;
+
+ printf("starting properties test\n");
+
+ ZERO_STRUCT(cli);
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ d_printf("Capabilities 0x%08x\n", smb1cli_conn_capabilities(cli->conn));
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ return correct;
+}
+
+
+
+/* FIRST_DESIRED_ACCESS 0xf019f */
+#define FIRST_DESIRED_ACCESS FILE_READ_DATA|FILE_WRITE_DATA|FILE_APPEND_DATA|\
+ FILE_READ_EA| /* 0xf */ \
+ FILE_WRITE_EA|FILE_READ_ATTRIBUTES| /* 0x90 */ \
+ FILE_WRITE_ATTRIBUTES| /* 0x100 */ \
+ DELETE_ACCESS|READ_CONTROL_ACCESS|\
+ WRITE_DAC_ACCESS|WRITE_OWNER_ACCESS /* 0xf0000 */
+/* SECOND_DESIRED_ACCESS 0xe0080 */
+#define SECOND_DESIRED_ACCESS FILE_READ_ATTRIBUTES| /* 0x80 */ \
+ READ_CONTROL_ACCESS|WRITE_DAC_ACCESS|\
+ WRITE_OWNER_ACCESS /* 0xe0000 */
+
+#if 0
+#define THIRD_DESIRED_ACCESS FILE_READ_ATTRIBUTES| /* 0x80 */ \
+ READ_CONTROL_ACCESS|WRITE_DAC_ACCESS|\
+ FILE_READ_DATA|\
+ WRITE_OWNER_ACCESS /* */
+#endif
+
+/*
+ Test ntcreate calls made by xcopy
+ */
+static bool run_xcopy(int dummy)
+{
+ static struct cli_state *cli1;
+ const char *fname = "\\test.txt";
+ bool correct = True;
+ uint16_t fnum1, fnum2;
+ NTSTATUS status;
+
+ printf("starting xcopy test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ status = cli_ntcreate(cli1, fname, 0, FIRST_DESIRED_ACCESS,
+ FILE_ATTRIBUTE_ARCHIVE, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0x4044, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("First open failed - %s\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli1, fname, 0, SECOND_DESIRED_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN, 0x200000, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("second open failed - %s\n", nt_errstr(status));
+ return False;
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+
+ return correct;
+}
+
+/*
+ Test rename on files open with share delete and no share delete.
+ */
+static bool run_rename(int dummy)
+{
+ static struct cli_state *cli1;
+ const char *fname = "\\test.txt";
+ const char *fname1 = "\\test1.txt";
+ bool correct = True;
+ uint16_t fnum1;
+ uint32_t attr;
+ NTSTATUS status;
+
+ printf("starting rename test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("First open failed - %s\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_rename(cli1, fname, fname1, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("First rename failed (SHARE_READ) (this is correct) - %s\n", nt_errstr(status));
+ } else {
+ printf("First rename succeeded (SHARE_READ) - this should have failed !\n");
+ correct = False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close - 1 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS, FILE_ATTRIBUTE_NORMAL,
+#if 0
+ FILE_SHARE_DELETE|FILE_SHARE_NONE,
+#else
+ FILE_SHARE_DELETE|FILE_SHARE_READ,
+#endif
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Second open failed - %s\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_rename(cli1, fname, fname1, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Second rename failed (SHARE_DELETE | SHARE_READ) - this should have succeeded - %s\n", nt_errstr(status));
+ correct = False;
+ } else {
+ printf("Second rename succeeded (SHARE_DELETE | SHARE_READ)\n");
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close - 2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, READ_CONTROL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Third open failed - %s\n", nt_errstr(status));
+ return False;
+ }
+
+
+ status = cli_rename(cli1, fname, fname1, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Third rename failed (SHARE_NONE) - this should have succeeded - %s\n", nt_errstr(status));
+ correct = False;
+ } else {
+ printf("Third rename succeeded (SHARE_NONE)\n");
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close - 3 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /*----*/
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Fourth open failed - %s\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_rename(cli1, fname, fname1, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Fourth rename failed (SHARE_READ | SHARE_WRITE) (this is correct) - %s\n", nt_errstr(status));
+ } else {
+ printf("Fourth rename succeeded (SHARE_READ | SHARE_WRITE) - this should have failed !\n");
+ correct = False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close - 4 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /*--*/
+
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_READ_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Fifth open failed - %s\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_rename(cli1, fname, fname1, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Fifth rename failed (SHARE_READ | SHARE_WRITE | SHARE_DELETE) - this should have succeeded - %s ! \n", nt_errstr(status));
+ correct = False;
+ } else {
+ printf("Fifth rename succeeded (SHARE_READ | SHARE_WRITE | SHARE_DELETE) (this is correct) - %s\n", nt_errstr(status));
+ }
+
+ /*--*/
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close - 5 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ /* Check that the renamed file has FILE_ATTRIBUTE_ARCHIVE. */
+ status = cli_getatr(cli1, fname1, &attr, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("getatr on file %s failed - %s ! \n",
+ fname1, nt_errstr(status));
+ correct = False;
+ } else {
+ if (attr != FILE_ATTRIBUTE_ARCHIVE) {
+ printf("Renamed file %s has wrong attr 0x%x "
+ "(should be 0x%x)\n",
+ fname1,
+ attr,
+ (unsigned int)FILE_ATTRIBUTE_ARCHIVE);
+ correct = False;
+ } else {
+ printf("Renamed file %s has archive bit set\n", fname1);
+ }
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname1, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+
+ return correct;
+}
+
+/*
+ Test rename into a directory with an ACL denying it.
+ */
+static bool run_rename_access(int dummy)
+{
+ static struct cli_state *cli = NULL;
+ static struct cli_state *posix_cli = NULL;
+ const char *src = "test.txt";
+ const char *dname = "dir";
+ const char *dst = "dir\\test.txt";
+ const char *dsrc = "test.dir";
+ const char *ddst = "dir\\test.dir";
+ uint16_t fnum = (uint16_t)-1;
+ struct security_descriptor *sd = NULL;
+ struct security_descriptor *newsd = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+ printf("starting rename access test\n");
+
+ /* Windows connection. */
+ if (!torture_open_connection(&cli, 0)) {
+ goto fail;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ /* Posix connection. */
+ if (!torture_open_connection(&posix_cli, 0)) {
+ goto fail;
+ }
+
+ smbXcli_conn_set_sockopt(posix_cli->conn, sockops);
+
+ status = torture_setup_unix_extensions(posix_cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+
+ /* Start with a clean slate. */
+ cli_unlink(cli, src, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli, dst, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, dsrc);
+ cli_rmdir(cli, ddst);
+ cli_rmdir(cli, dname);
+
+ /*
+ * Setup the destination directory with a DENY ACE to
+ * prevent new files within it.
+ */
+ status = cli_ntcreate(cli,
+ dname,
+ 0,
+ FILE_READ_ATTRIBUTES|READ_CONTROL_ACCESS|
+ WRITE_DAC_ACCESS|FILE_READ_DATA|
+ WRITE_OWNER_ACCESS,
+ FILE_ATTRIBUTE_DIRECTORY,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_CREATE,
+ FILE_DIRECTORY_FILE,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s - %s\n", dname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_query_secdesc(cli,
+ fnum,
+ frame,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_query_secdesc failed for %s (%s)\n",
+ dname, nt_errstr(status));
+ goto fail;
+ }
+
+ newsd = security_descriptor_dacl_create(frame,
+ 0,
+ NULL,
+ NULL,
+ SID_WORLD,
+ SEC_ACE_TYPE_ACCESS_DENIED,
+ SEC_DIR_ADD_FILE|SEC_DIR_ADD_SUBDIR,
+ 0,
+ NULL);
+ if (newsd == NULL) {
+ goto fail;
+ }
+ sd->dacl = security_acl_concatenate(frame,
+ newsd->dacl,
+ sd->dacl);
+ if (sd->dacl == NULL) {
+ goto fail;
+ }
+ status = cli_set_secdesc(cli, fnum, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_set_secdesc failed for %s (%s)\n",
+ dname, nt_errstr(status));
+ goto fail;
+ }
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed for %s (%s)\n",
+ dname, nt_errstr(status));
+ goto fail;
+ }
+ /* Now go around the back and chmod to 777 via POSIX. */
+ status = cli_posix_chmod(posix_cli, dname, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_chmod failed for %s (%s)\n",
+ dname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Check we can't create a file within dname via Windows. */
+ status = cli_openx(cli, dst, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ cli_close(posix_cli, fnum);
+ printf("Create of %s should be ACCESS denied, was %s\n",
+ dst, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Make the sample file/directory. */
+ status = cli_openx(cli, src, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", src, nt_errstr(status));
+ goto fail;
+ }
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed (%s)\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_mkdir(cli, dsrc);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_mkdir of %s failed (%s)\n",
+ dsrc, nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * OK - renames of the new file and directory into the
+ * dst directory should fail.
+ */
+
+ status = cli_rename(cli, src, dst, false);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("rename of %s -> %s should be ACCESS denied, was %s\n",
+ src, dst, nt_errstr(status));
+ goto fail;
+ }
+ status = cli_rename(cli, dsrc, ddst, false);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("rename of %s -> %s should be ACCESS denied, was %s\n",
+ src, dst, nt_errstr(status));
+ goto fail;
+ }
+
+ TALLOC_FREE(frame);
+ return true;
+
+ fail:
+
+ if (posix_cli) {
+ torture_close_connection(posix_cli);
+ }
+
+ if (cli) {
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ }
+ cli_unlink(cli, src,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli, dst,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, dsrc);
+ cli_rmdir(cli, ddst);
+ cli_rmdir(cli, dname);
+
+ torture_close_connection(cli);
+ }
+
+ TALLOC_FREE(frame);
+ return false;
+}
+
+/*
+ Test owner rights ACE.
+ */
+static bool run_owner_rights(int dummy)
+{
+ static struct cli_state *cli = NULL;
+ const char *fname = "owner_rights.txt";
+ uint16_t fnum = (uint16_t)-1;
+ struct security_descriptor *sd = NULL;
+ struct security_descriptor *newsd = NULL;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+ printf("starting owner rights test\n");
+
+ /* Windows connection. */
+ if (!torture_open_connection(&cli, 0)) {
+ goto fail;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ /* Start with a clean slate. */
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Create the test file. */
+ /* Now try and open for read and write-dac. */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Get the original SD. */
+ status = cli_query_secdesc(cli,
+ fnum,
+ frame,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_query_secdesc failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * Add an "owner-rights" ACE denying WRITE_DATA,
+ * and an "owner-rights" ACE allowing READ_DATA.
+ */
+
+ newsd = security_descriptor_dacl_create(frame,
+ 0,
+ NULL,
+ NULL,
+ SID_OWNER_RIGHTS,
+ SEC_ACE_TYPE_ACCESS_DENIED,
+ FILE_WRITE_DATA,
+ 0,
+ SID_OWNER_RIGHTS,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ FILE_READ_DATA,
+ 0,
+ NULL);
+ if (newsd == NULL) {
+ goto fail;
+ }
+ sd->dacl = security_acl_concatenate(frame,
+ newsd->dacl,
+ sd->dacl);
+ if (sd->dacl == NULL) {
+ goto fail;
+ }
+ status = cli_set_secdesc(cli, fnum, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_set_secdesc failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Try and open for FILE_WRITE_DATA */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ FILE_WRITE_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("Open of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Now try and open for FILE_READ_DATA */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Restore clean slate. */
+ TALLOC_FREE(sd);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Create the test file. */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Get the original SD. */
+ status = cli_query_secdesc(cli,
+ fnum,
+ frame,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_query_secdesc failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * Add an "owner-rights ACE denying WRITE_DATA,
+ * and an "owner-rights ACE allowing READ_DATA|WRITE_DATA.
+ */
+
+ newsd = security_descriptor_dacl_create(frame,
+ 0,
+ NULL,
+ NULL,
+ SID_OWNER_RIGHTS,
+ SEC_ACE_TYPE_ACCESS_DENIED,
+ FILE_WRITE_DATA,
+ 0,
+ SID_OWNER_RIGHTS,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ FILE_READ_DATA|FILE_WRITE_DATA,
+ 0,
+ NULL);
+ if (newsd == NULL) {
+ goto fail;
+ }
+ sd->dacl = security_acl_concatenate(frame,
+ newsd->dacl,
+ sd->dacl);
+ if (sd->dacl == NULL) {
+ goto fail;
+ }
+ status = cli_set_secdesc(cli, fnum, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_set_secdesc failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Try and open for FILE_WRITE_DATA */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ FILE_WRITE_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("Open of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Now try and open for FILE_READ_DATA */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Restore clean slate. */
+ TALLOC_FREE(sd);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+
+ /* Create the test file. */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /* Get the original SD. */
+ status = cli_query_secdesc(cli,
+ fnum,
+ frame,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_query_secdesc failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+
+ /*
+ * Add an "authenticated users" ACE allowing READ_DATA,
+ * add an "owner-rights" denying READ_DATA,
+ * and an "authenticated users" ACE allowing WRITE_DATA.
+ */
+
+ newsd = security_descriptor_dacl_create(frame,
+ 0,
+ NULL,
+ NULL,
+ SID_NT_AUTHENTICATED_USERS,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ FILE_READ_DATA,
+ 0,
+ SID_OWNER_RIGHTS,
+ SEC_ACE_TYPE_ACCESS_DENIED,
+ FILE_READ_DATA,
+ 0,
+ SID_NT_AUTHENTICATED_USERS,
+ SEC_ACE_TYPE_ACCESS_ALLOWED,
+ FILE_WRITE_DATA,
+ 0,
+ NULL);
+ if (newsd == NULL) {
+ printf("newsd == NULL\n");
+ goto fail;
+ }
+ sd->dacl = security_acl_concatenate(frame,
+ newsd->dacl,
+ sd->dacl);
+ if (sd->dacl == NULL) {
+ printf("sd->dacl == NULL\n");
+ goto fail;
+ }
+ status = cli_set_secdesc(cli, fnum, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_set_secdesc failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Now try and open for FILE_READ_DATA|FILE_WRITE_DATA */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ FILE_READ_DATA|FILE_WRITE_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed for %s (%s)\n",
+ fname, nt_errstr(status));
+ goto fail;
+ }
+
+ cli_unlink(cli, fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ TALLOC_FREE(frame);
+ return true;
+
+ fail:
+
+ if (cli) {
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ }
+ cli_unlink(cli, fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ torture_close_connection(cli);
+ }
+
+ TALLOC_FREE(frame);
+ return false;
+}
+
+/*
+ * Test SMB1-specific open with SEC_FLAG_SYSTEM_SECURITY.
+ * Note this test only works with a user with SeSecurityPrivilege set.
+ *
+ * NB. This is also tested in samba3.base.createx_access
+ * but this makes it very explicit what we're looking for.
+ */
+static bool run_smb1_system_security(int dummy)
+{
+ static struct cli_state *cli = NULL;
+ const char *fname = "system_security.txt";
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+ printf("starting smb1 system security test\n");
+
+ /* SMB1 connection - torture_open_connection() forces this. */
+ if (!torture_open_connection(&cli, 0)) {
+ goto fail;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ /* Start with a clean slate. */
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Create the test file. */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Create of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli, fnum);
+
+ /* Open with SEC_FLAG_SYSTEM_SECURITY only. */
+ /*
+ * On SMB1 this succeeds - SMB2 it fails,
+ * see the SMB2-SACL test.
+ */
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ SEC_FLAG_SYSTEM_SECURITY,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of %s - %s\n", fname, nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_close(cli, fnum);
+
+ cli_unlink(cli, fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ torture_close_connection(cli);
+ TALLOC_FREE(frame);
+ return true;
+
+ fail:
+
+ if (cli) {
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ }
+ cli_unlink(cli, fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ torture_close_connection(cli);
+ }
+
+ TALLOC_FREE(frame);
+ return false;
+}
+
+static bool run_pipe_number(int dummy)
+{
+ struct cli_state *cli1;
+ const char *pipe_name = "\\SPOOLSS";
+ uint16_t fnum;
+ int num_pipes = 0;
+ NTSTATUS status;
+
+ printf("starting pipenumber test\n");
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ while(1) {
+ status = cli_ntcreate(cli1, pipe_name, 0, FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN_IF, 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Open of pipe %s failed with error (%s)\n", pipe_name, nt_errstr(status));
+ break;
+ }
+ num_pipes++;
+ printf("\r%6d", num_pipes);
+ }
+
+ printf("pipe_number test - we can open %d %s pipes.\n", num_pipes, pipe_name );
+ torture_close_connection(cli1);
+ return True;
+}
+
+/*
+ Test open mode returns on read-only files.
+ */
+static bool run_opentest(int dummy)
+{
+ static struct cli_state *cli1;
+ static struct cli_state *cli2;
+ const char *fname = "\\readonly.file";
+ uint16_t fnum1, fnum2;
+ char buf[20];
+ off_t fsize;
+ bool correct = True;
+ char *tmp_path;
+ NTSTATUS status;
+
+ printf("starting open test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_setatr(cli1, fname, FILE_ATTRIBUTE_READONLY, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_setatr failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_WRITE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ /* This will fail - but the error should be ERRnoaccess, not ERRbadshare. */
+ status = cli_openx(cli1, fname, O_RDWR, DENY_ALL, &fnum2);
+
+ if (check_error(__LINE__, status, ERRDOS, ERRnoaccess,
+ NT_STATUS_ACCESS_DENIED)) {
+ printf("correct error code ERRDOS/ERRnoaccess returned\n");
+ }
+
+ printf("finished open test 1\n");
+
+ cli_close(cli1, fnum1);
+
+ /* Now try not readonly and ensure ERRbadshare is returned. */
+
+ cli_setatr(cli1, fname, 0, 0);
+
+ status = cli_openx(cli1, fname, O_RDONLY, DENY_WRITE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ /* This will fail - but the error should be ERRshare. */
+ status = cli_openx(cli1, fname, O_RDWR, DENY_ALL, &fnum2);
+
+ if (check_error(__LINE__, status, ERRDOS, ERRbadshare,
+ NT_STATUS_SHARING_VIOLATION)) {
+ printf("correct error code ERRDOS/ERRbadshare returned\n");
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("finished open test 2\n");
+
+ /* Test truncate open disposition on file opened for read. */
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("(3) open (1) of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ /* write 20 bytes. */
+
+ memset(buf, '\0', 20);
+
+ status = cli_writeall(cli1, fnum1, 0, (uint8_t *)buf, 0, 20, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("write failed (%s)\n", nt_errstr(status));
+ correct = False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("(3) close1 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ /* Ensure size == 20. */
+ status = cli_getatr(cli1, fname, NULL, &fsize, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("(3) getatr failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ if (fsize != 20) {
+ printf("(3) file size != 20\n");
+ return False;
+ }
+
+ /* Now test if we can truncate a file opened for readonly. */
+ status = cli_openx(cli1, fname, O_RDONLY|O_TRUNC, DENY_NONE, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("(3) open (2) of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ /* Ensure size == 0. */
+ status = cli_getatr(cli1, fname, NULL, &fsize, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("(3) getatr failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ if (fsize != 0) {
+ printf("(3) file size != 0\n");
+ return False;
+ }
+ printf("finished open test 3\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("Do ctemp tests\n");
+ status = cli_ctemp(cli1, talloc_tos(), "\\", &fnum1, &tmp_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ctemp failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ printf("ctemp gave path %s\n", tmp_path);
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close of temp failed (%s)\n", nt_errstr(status));
+ }
+
+ status = cli_unlink(cli1, tmp_path, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink of temp failed (%s)\n", nt_errstr(status));
+ }
+
+ /* Test the non-io opens... */
+
+ if (!torture_open_connection(&cli2, 1)) {
+ return False;
+ }
+
+ cli_setatr(cli2, fname, 0, 0);
+ cli_unlink(cli2, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ printf("TEST #1 testing 2 non-io opens (no delete)\n");
+ status = cli_ntcreate(cli1, fname, 0, FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #1 open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0, FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OPEN_IF, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #1 open 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #1 close 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #1 close 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("non-io open test #1 passed.\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("TEST #2 testing 2 non-io opens (first with delete)\n");
+
+ status = cli_ntcreate(cli1, fname, 0,
+ DELETE_ACCESS|FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #2 open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0, FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OPEN_IF, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #2 open 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #2 close 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #2 close 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("non-io open test #2 passed.\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("TEST #3 testing 2 non-io opens (second with delete)\n");
+
+ status = cli_ntcreate(cli1, fname, 0, FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #3 open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0,
+ DELETE_ACCESS|FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OPEN_IF, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #3 open 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #3 close 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #3 close 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("non-io open test #3 passed.\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("TEST #4 testing 2 non-io opens (both with delete)\n");
+
+ status = cli_ntcreate(cli1, fname, 0,
+ DELETE_ACCESS|FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #4 open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0,
+ DELETE_ACCESS|FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OPEN_IF, 0, 0, &fnum2, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("TEST #4 open 2 of %s SUCCEEDED - should have failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("TEST #4 open 2 of %s gave %s (correct error should be %s)\n", fname, nt_errstr(status), "sharing violation");
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #4 close 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("non-io open test #4 passed.\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("TEST #5 testing 2 non-io opens (both with delete - both with file share delete)\n");
+
+ status = cli_ntcreate(cli1, fname, 0,
+ DELETE_ACCESS|FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #5 open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0,
+ DELETE_ACCESS|FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_DELETE,
+ FILE_OPEN_IF, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #5 open 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #5 close 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #5 close 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("non-io open test #5 passed.\n");
+
+ printf("TEST #6 testing 1 non-io open, one io open\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #6 open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0, FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,
+ FILE_OPEN_IF, 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #6 open 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #6 close 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #6 close 2 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("non-io open test #6 passed.\n");
+
+ printf("TEST #7 testing 1 non-io open, one io open with delete\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #7 open 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_ntcreate(cli2, fname, 0,
+ DELETE_ACCESS|FILE_READ_ATTRIBUTES,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_DELETE,
+ FILE_OPEN_IF, 0, 0, &fnum2, NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("TEST #7 open 2 of %s SUCCEEDED - should have failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("TEST #7 open 2 of %s gave %s (correct error should be %s)\n", fname, nt_errstr(status), "sharing violation");
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #7 close 1 of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ printf("non-io open test #7 passed.\n");
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("TEST #8 testing open without WRITE_ATTRIBUTES, updating close write time.\n");
+ status = cli_ntcreate(cli1, fname, 0, FILE_WRITE_DATA, FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #8 open of %s failed (%s)\n", fname, nt_errstr(status));
+ correct = false;
+ goto out;
+ }
+
+ /* Write to ensure we have to update the file time. */
+ status = cli_writeall(cli1, fnum1, 0, (const uint8_t *)"TEST DATA\n", 0, 10,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #8 cli_write failed: %s\n", nt_errstr(status));
+ correct = false;
+ goto out;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("TEST #8 close of %s failed (%s)\n", fname, nt_errstr(status));
+ correct = false;
+ }
+
+ out:
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+ if (!torture_close_connection(cli2)) {
+ correct = False;
+ }
+
+ return correct;
+}
+
+NTSTATUS torture_setup_unix_extensions(struct cli_state *cli)
+{
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+ NTSTATUS status;
+
+ if (!SERVER_HAS_UNIX_CIFS(cli)) {
+ printf("Server doesn't support UNIX CIFS extensions.\n");
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = cli_unix_extensions_version(cli, &major, &minor, &caplow,
+ &caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Server didn't return UNIX CIFS extensions: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ status = cli_set_unix_extensions_capabilities(cli, major, minor,
+ caplow, caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Server doesn't support setting UNIX CIFS extensions: "
+ "%s.\n", nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ Test POSIX open /mkdir calls.
+ */
+static bool run_simple_posix_open_test(int dummy)
+{
+ static struct cli_state *cli1;
+ const char *fname = "posix:file";
+ const char *hname = "posix:hlink";
+ const char *sname = "posix:symlink";
+ const char *dname = "posix:dir";
+ char buf[10];
+ char *target = NULL;
+ uint16_t fnum1 = (uint16_t)-1;
+ SMB_STRUCT_STAT sbuf;
+ bool correct = false;
+ NTSTATUS status;
+ size_t nread;
+ const char *fname_windows = "windows_file";
+ uint16_t fnum2 = (uint16_t)-1;
+ bool ok;
+
+ printf("Starting simple POSIX open test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_posix_unlink(cli1, fname);
+ cli_setatr(cli1, dname, 0, 0);
+ cli_posix_rmdir(cli1, dname);
+ cli_setatr(cli1, hname, 0, 0);
+ cli_posix_unlink(cli1, hname);
+ cli_setatr(cli1, sname, 0, 0);
+ cli_posix_unlink(cli1, sname);
+ cli_setatr(cli1, fname_windows, 0, 0);
+ cli_posix_unlink(cli1, fname_windows);
+
+ /* Create a directory. */
+ status = cli_posix_mkdir(cli1, dname, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX mkdir of %s failed (%s)\n", dname, nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_open(cli1, fname, O_RDWR|O_CREAT|O_EXCL,
+ 0600, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX create of %s failed (%s)\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Test ftruncate - set file size. */
+ status = cli_ftruncate(cli1, fnum1, 1000);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ftruncate failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure st_size == 1000 */
+ status = cli_posix_stat(cli1, fname, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("stat failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ if (sbuf.st_ex_size != 1000) {
+ printf("ftruncate - stat size (%u) != 1000\n", (unsigned int)sbuf.st_ex_size);
+ goto out;
+ }
+
+ /* Ensure st_mode == 0600 */
+ if ((sbuf.st_ex_mode & 07777) != 0600) {
+ printf("posix_open - bad permissions 0%o != 0600\n",
+ (unsigned int)(sbuf.st_ex_mode & 07777));
+ goto out;
+ }
+
+ /* Test ftruncate - set file size back to zero. */
+ status = cli_ftruncate(cli1, fnum1, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ftruncate failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Now open the file again for read only. */
+ status = cli_posix_open(cli1, fname, O_RDONLY, 0, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX open of %s failed (%s)\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Now unlink while open. */
+ status = cli_posix_unlink(cli1, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX unlink of %s failed (%s)\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close(2) failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure the file has gone. */
+ status = cli_posix_open(cli1, fname, O_RDONLY, 0, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("POSIX open of %s succeeded, should have been deleted.\n", fname);
+ goto out;
+ }
+
+ /* Create again to test open with O_TRUNC. */
+ status = cli_posix_open(cli1, fname, O_RDWR|O_CREAT|O_EXCL, 0600, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX create of %s failed (%s)\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Test ftruncate - set file size. */
+ status = cli_ftruncate(cli1, fnum1, 1000);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ftruncate failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure st_size == 1000 */
+ status = cli_posix_stat(cli1, fname, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("stat failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ if (sbuf.st_ex_size != 1000) {
+ printf("ftruncate - stat size (%u) != 1000\n", (unsigned int)sbuf.st_ex_size);
+ goto out;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close(2) failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Re-open with O_TRUNC. */
+ status = cli_posix_open(cli1, fname, O_WRONLY|O_TRUNC, 0600, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX create of %s failed (%s)\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure st_size == 0 */
+ status = cli_posix_stat(cli1, fname, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("stat failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ if (sbuf.st_ex_size != 0) {
+ printf("O_TRUNC - stat size (%u) != 0\n", (unsigned int)sbuf.st_ex_size);
+ goto out;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_unlink(cli1, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX unlink of %s failed (%s)\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_open(cli1, dname, O_RDONLY, 0, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX open directory O_RDONLY of %s failed (%s)\n",
+ dname, nt_errstr(status));
+ goto out;
+ }
+
+ cli_close(cli1, fnum1);
+
+ /* What happens when we try and POSIX open a directory for write ? */
+ status = cli_posix_open(cli1, dname, O_RDWR, 0, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("POSIX open of directory %s succeeded, "
+ "should have failed.\n",
+ dname);
+ goto out;
+ } else {
+ if (!check_both_error(__LINE__, status, ERRDOS, EISDIR,
+ NT_STATUS_FILE_IS_A_DIRECTORY)) {
+ goto out;
+ }
+ }
+
+ /* Create the file. */
+ status = cli_posix_open(cli1, fname, O_RDWR|O_CREAT|O_EXCL,
+ 0600, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX create of %s failed (%s)\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Write some data into it. */
+ status = cli_writeall(cli1, fnum1, 0, (const uint8_t *)"TEST DATA\n", 0, 10,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_write failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ cli_close(cli1, fnum1);
+
+ /* Now create a hardlink. */
+ status = cli_posix_hardlink(cli1, fname, hname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX hardlink of %s failed (%s)\n", hname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Now create a symlink. */
+ status = cli_posix_symlink(cli1, fname, sname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX symlink of %s failed (%s)\n", sname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Open the hardlink for read. */
+ status = cli_posix_open(cli1, hname, O_RDONLY, 0, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX open of %s failed (%s)\n", hname, nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_read(cli1, fnum1, buf, 0, 10, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX read of %s failed (%s)\n", hname,
+ nt_errstr(status));
+ goto out;
+ } else if (nread != 10) {
+ printf("POSIX read of %s failed. Received %ld, expected %d\n",
+ hname, (unsigned long)nread, 10);
+ goto out;
+ }
+
+ if (memcmp(buf, "TEST DATA\n", 10)) {
+ printf("invalid data read from hardlink\n");
+ goto out;
+ }
+
+ /* Do a POSIX lock/unlock. */
+ status = cli_posix_lock(cli1, fnum1, 0, 100, true, READ_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX lock failed %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Punch a hole in the locked area. */
+ status = cli_posix_unlock(cli1, fnum1, 10, 80);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX unlock failed %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ cli_close(cli1, fnum1);
+
+ /* Open the symlink for read - this should fail. A POSIX
+ client should not be doing opens on a symlink. */
+ status = cli_posix_open(cli1, sname, O_RDONLY, 0, &fnum1);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("POSIX open of %s succeeded (should have failed)\n", sname);
+ goto out;
+ }
+ ok = check_both_error(
+ __LINE__, status, ERRDOS, ERRbadpath,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ if (!ok) {
+ printf("POSIX open of %s should have failed "
+ "with NT_STATUS_OBJECT_NAME_NOT_FOUND, "
+ "failed with %s instead.\n",
+ sname, nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_readlink(cli1, sname, talloc_tos(), &target, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX readlink on %s failed (%s)\n", sname, nt_errstr(status));
+ goto out;
+ }
+
+ if (strcmp(target, fname) != 0) {
+ printf("POSIX readlink on %s failed to match name %s (read %s)\n",
+ sname, fname, target);
+ goto out;
+ }
+
+ status = cli_posix_rmdir(cli1, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX rmdir failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Check directory opens with a specific permission. */
+ status = cli_posix_mkdir(cli1, dname, 0700);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX mkdir of %s failed (%s)\n", dname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure st_mode == 0700 */
+ status = cli_posix_stat(cli1, dname, &sbuf);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("stat failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+
+ if ((sbuf.st_ex_mode & 07777) != 0700) {
+ printf("posix_mkdir - bad permissions 0%o != 0700\n",
+ (unsigned int)(sbuf.st_ex_mode & 07777));
+ goto out;
+ }
+
+ /*
+ * Now create a Windows file, and attempt a POSIX unlink.
+ * This should fail with a sharing violation but due to:
+ *
+ * [Bug 9571] Unlink after open causes smbd to panic
+ *
+ * ensure we've fixed the lock ordering violation.
+ */
+
+ status = cli_ntcreate(cli1, fname_windows, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0x0, 0x0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Windows create of %s failed (%s)\n", fname_windows,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now try posix_unlink. */
+ status = cli_posix_unlink(cli1, fname_windows);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ printf("POSIX unlink of %s should fail "
+ "with NT_STATUS_SHARING_VIOLATION "
+ "got %s instead !\n",
+ fname_windows,
+ nt_errstr(status));
+ goto out;
+ }
+
+ cli_close(cli1, fnum2);
+
+ printf("Simple POSIX open test passed\n");
+ correct = true;
+
+ out:
+
+ if (fnum1 != (uint16_t)-1) {
+ cli_close(cli1, fnum1);
+ fnum1 = (uint16_t)-1;
+ }
+
+ if (fnum2 != (uint16_t)-1) {
+ cli_close(cli1, fnum2);
+ fnum2 = (uint16_t)-1;
+ }
+
+ cli_setatr(cli1, sname, 0, 0);
+ cli_posix_unlink(cli1, sname);
+ cli_setatr(cli1, hname, 0, 0);
+ cli_posix_unlink(cli1, hname);
+ cli_setatr(cli1, fname, 0, 0);
+ cli_posix_unlink(cli1, fname);
+ cli_setatr(cli1, dname, 0, 0);
+ cli_posix_rmdir(cli1, dname);
+ cli_setatr(cli1, fname_windows, 0, 0);
+ cli_posix_unlink(cli1, fname_windows);
+
+ if (!torture_close_connection(cli1)) {
+ correct = false;
+ }
+
+ return correct;
+}
+
+/*
+ Test POSIX and Windows ACLs are rejected on symlinks.
+ */
+static bool run_acl_symlink_test(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname = "posix_file";
+ const char *sname = "posix_symlink";
+ uint16_t fnum = (uint16_t)-1;
+ bool correct = false;
+ NTSTATUS status;
+ char *posix_acl = NULL;
+ size_t posix_acl_len = 0;
+ char *posix_acl_sym = NULL;
+ size_t posix_acl_len_sym = 0;
+ struct security_descriptor *sd = NULL;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+
+ printf("Starting acl symlink test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+ cli_setatr(cli, sname, 0, 0);
+ cli_posix_unlink(cli, sname);
+
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ READ_CONTROL_ACCESS,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0x0,
+ 0x0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Get the Windows ACL on the file. */
+ status = cli_query_secdesc(cli,
+ fnum,
+ frame,
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_query_secdesc failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Get the POSIX ACL on the file. */
+ status = cli_posix_getacl(cli,
+ fname,
+ frame,
+ &posix_acl_len,
+ &posix_acl);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_getacl failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Now create a symlink. */
+ status = cli_posix_symlink(cli, fname, sname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed (%s)\n",
+ sname,
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Open a handle on the symlink for SD set/get should fail. */
+ status = cli_ntcreate(cli,
+ sname,
+ 0,
+ READ_CONTROL_ACCESS|SEC_STD_WRITE_DAC,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0x0,
+ 0x0,
+ &fnum,
+ NULL);
+
+ if (NT_STATUS_IS_OK(status)) {
+ printf("Symlink open for getsd/setsd of %s "
+ "succeeded (should fail)\n",
+ sname);
+ goto out;
+ }
+
+ /* Try a stat-open on the symlink, should also fail. */
+ status = cli_ntcreate(cli,
+ sname,
+ 0,
+ FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0x0,
+ 0x0,
+ &fnum,
+ NULL);
+
+ if (NT_STATUS_IS_OK(status)) {
+ printf("Stat-open of symlink succeeded (should fail)\n");
+ goto out;
+ }
+
+ /* Get the POSIX ACL on the symlink pathname. Should fail. */
+ status = cli_posix_getacl(cli,
+ sname,
+ frame,
+ &posix_acl_len_sym,
+ &posix_acl_sym);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("cli_posix_getacl on a symlink gave %s. "
+ "Should be NT_STATUS_ACCESS_DENIED.\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Set the POSIX ACL on the symlink pathname. Should fail. */
+ status = cli_posix_setacl(cli,
+ sname,
+ posix_acl,
+ posix_acl_len);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("cli_posix_setacl on a symlink gave %s. "
+ "Should be NT_STATUS_ACCESS_DENIED.\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ printf("ACL symlink test passed\n");
+ correct = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ fnum = (uint16_t)-1;
+ }
+
+ cli_setatr(cli, sname, 0, 0);
+ cli_posix_unlink(cli, sname);
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Test POSIX can delete a file containing streams.
+ */
+static bool run_posix_stream_delete(int dummy)
+{
+ struct cli_state *cli1 = NULL;
+ struct cli_state *cli2 = NULL;
+ const char *fname = "streamfile";
+ const char *stream_fname = "streamfile:Zone.Identifier:$DATA";
+ uint16_t fnum1 = (uint16_t)-1;
+ bool correct = false;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX stream delete test\n");
+
+ if (!torture_open_connection(&cli1, 0) ||
+ !torture_open_connection(&cli2, 1)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli2);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Create the file. */
+ status = cli_ntcreate(cli1,
+ fname,
+ 0,
+ READ_CONTROL_ACCESS,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0x0,
+ 0x0,
+ &fnum1,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+ fnum1 = (uint16_t)-1;
+
+ /* Now create the stream. */
+ status = cli_ntcreate(cli1,
+ stream_fname,
+ 0,
+ FILE_WRITE_DATA,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_CREATE,
+ 0x0,
+ 0x0,
+ &fnum1,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate of %s failed (%s)\n",
+ stream_fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Leave the stream handle open... */
+
+ /* POSIX unlink should fail. */
+ status = cli_posix_unlink(cli2, fname);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_unlink of %s succeeded, should have failed\n",
+ fname);
+ goto out;
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_SHARING_VIOLATION)) {
+ printf("cli_posix_unlink of %s failed with (%s) "
+ "should have been NT_STATUS_SHARING_VIOLATION\n",
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Close the stream handle. */
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close of %s failed (%s)\n",
+ stream_fname,
+ nt_errstr(status));
+ goto out;
+ }
+ fnum1 = (uint16_t)-1;
+
+ /* POSIX unlink after stream handle closed should succeed. */
+ status = cli_posix_unlink(cli2, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_unlink of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ printf("POSIX stream delete test passed\n");
+ correct = true;
+
+ out:
+
+ if (fnum1 != (uint16_t)-1) {
+ cli_close(cli1, fnum1);
+ fnum1 = (uint16_t)-1;
+ }
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!torture_close_connection(cli1)) {
+ correct = false;
+ }
+ if (!torture_close_connection(cli2)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Test setting EA's are rejected on symlinks.
+ */
+static bool run_ea_symlink_test(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname = "posix_file_ea";
+ const char *sname = "posix_symlink_ea";
+ const char *ea_name = "testea_name";
+ const char *ea_value = "testea_value";
+ uint16_t fnum = (uint16_t)-1;
+ bool correct = false;
+ NTSTATUS status;
+ size_t i, num_eas;
+ struct ea_struct *eas = NULL;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+
+ printf("Starting EA symlink test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+ cli_setatr(cli, sname, 0, 0);
+ cli_posix_unlink(cli, sname);
+
+ status = cli_ntcreate(cli,
+ fname,
+ 0,
+ READ_CONTROL_ACCESS,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_CREATE,
+ 0x0,
+ 0x0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_ntcreate of %s failed (%s)\n",
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ /* Set an EA on the path. */
+ status = cli_set_ea_path(cli,
+ fname,
+ ea_name,
+ ea_value,
+ strlen(ea_value)+1);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_set_ea_path failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now create a symlink. */
+ status = cli_posix_symlink(cli, fname, sname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed (%s)\n",
+ sname,
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Get the EA list on the path. Should return value set. */
+ status = cli_get_ea_list_path(cli,
+ fname,
+ frame,
+ &num_eas,
+ &eas);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_get_ea_list_path failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Ensure the EA we set is there. */
+ for (i=0; i<num_eas; i++) {
+ if (strcmp(eas[i].name, ea_name) == 0 &&
+ eas[i].value.length == strlen(ea_value)+1 &&
+ memcmp(eas[i].value.data,
+ ea_value,
+ eas[i].value.length) == 0) {
+ break;
+ }
+ }
+
+ if (i == num_eas) {
+ printf("Didn't find EA on pathname %s\n",
+ fname);
+ goto out;
+ }
+
+ num_eas = 0;
+ TALLOC_FREE(eas);
+
+ /* Get the EA list on the symlink. Should return empty list. */
+ status = cli_get_ea_list_path(cli,
+ sname,
+ frame,
+ &num_eas,
+ &eas);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_get_ea_list_path failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (num_eas != 0) {
+ printf("cli_get_ea_list_path failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Set an EA on the symlink. Should fail. */
+ status = cli_set_ea_path(cli,
+ sname,
+ ea_name,
+ ea_value,
+ strlen(ea_value)+1);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("cli_set_ea_path on a symlink gave %s. "
+ "Should be NT_STATUS_ACCESS_DENIED.\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ printf("EA symlink test passed\n");
+ correct = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ fnum = (uint16_t)-1;
+ }
+
+ cli_setatr(cli, sname, 0, 0);
+ cli_posix_unlink(cli, sname);
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+/*
+ Test POSIX locks are OFD-locks.
+ */
+static bool run_posix_ofd_lock_test(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname = "posix_file";
+ uint16_t fnum1 = (uint16_t)-1;
+ uint16_t fnum2 = (uint16_t)-1;
+ bool correct = false;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX ofd-lock test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+
+ /* Open the file twice. */
+ status = cli_posix_open(cli, fname, O_RDWR|O_CREAT|O_EXCL,
+ 0600, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("First POSIX open of %s failed\n", fname);
+ goto out;
+ }
+
+ status = cli_posix_open(cli, fname, O_RDWR, 0, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("First POSIX open of %s failed\n", fname);
+ goto out;
+ }
+
+ /* Set a 0-50 lock on fnum1. */
+ status = cli_posix_lock(cli, fnum1, 0, 50, false, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX lock (1) failed %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Set a 60-100 lock on fnum2. */
+ status = cli_posix_lock(cli, fnum2, 60, 100, false, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX lock (2) failed %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* close fnum1 - 0-50 lock should go away. */
+ status = cli_close(cli, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+ fnum1 = (uint16_t)-1;
+
+ /* Change the lock context. */
+ cli_setpid(cli, cli_getpid(cli) + 1);
+
+ /* Re-open fnum1. */
+ status = cli_posix_open(cli, fname, O_RDWR, 0, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Third POSIX open of %s failed\n", fname);
+ goto out;
+ }
+
+ /* 60-100 lock should still be there. */
+ status = cli_posix_lock(cli, fnum1, 60, 100, false, WRITE_LOCK);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_FILE_LOCK_CONFLICT)) {
+ printf("POSIX lock 60-100 not there %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* 0-50 lock should be gone. */
+ status = cli_posix_lock(cli, fnum1, 0, 50, false, WRITE_LOCK);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX lock 0-50 failed %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ printf("POSIX OFD lock test passed\n");
+ correct = true;
+
+ out:
+
+ if (fnum1 != (uint16_t)-1) {
+ cli_close(cli, fnum1);
+ fnum1 = (uint16_t)-1;
+ }
+ if (fnum2 != (uint16_t)-1) {
+ cli_close(cli, fnum2);
+ fnum2 = (uint16_t)-1;
+ }
+
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+struct posix_blocking_state {
+ struct tevent_context *ev;
+ struct cli_state *cli1;
+ uint16_t fnum1;
+ struct cli_state *cli2;
+ uint16_t fnum2;
+ bool gotblocked;
+ bool gotecho;
+};
+
+static void posix_blocking_locked(struct tevent_req *subreq);
+static void posix_blocking_gotblocked(struct tevent_req *subreq);
+static void posix_blocking_gotecho(struct tevent_req *subreq);
+static void posix_blocking_unlocked(struct tevent_req *subreq);
+
+static struct tevent_req *posix_blocking_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli1,
+ uint16_t fnum1,
+ struct cli_state *cli2,
+ uint16_t fnum2)
+{
+ struct tevent_req *req = NULL, *subreq = NULL;
+ struct posix_blocking_state *state = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct posix_blocking_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli1 = cli1;
+ state->fnum1 = fnum1;
+ state->cli2 = cli2;
+ state->fnum2 = fnum2;
+
+ subreq = cli_posix_lock_send(
+ state,
+ state->ev,
+ state->cli1,
+ state->fnum1,
+ 0,
+ 1,
+ false,
+ WRITE_LOCK);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, posix_blocking_locked, req);
+ return req;
+}
+
+static void posix_blocking_locked(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct posix_blocking_state *state = tevent_req_data(
+ req, struct posix_blocking_state);
+ NTSTATUS status;
+
+ status = cli_posix_lock_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = cli_posix_lock_send(
+ state,
+ state->ev,
+ state->cli2,
+ state->fnum2,
+ 0,
+ 1,
+ true,
+ WRITE_LOCK);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, posix_blocking_gotblocked, req);
+
+ /* Make sure the blocking request is delivered */
+ subreq = cli_echo_send(
+ state,
+ state->ev,
+ state->cli2,
+ 1,
+ (DATA_BLOB) { .data = (uint8_t *)state, .length = 1 });
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, posix_blocking_gotecho, req);
+}
+
+static void posix_blocking_gotblocked(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct posix_blocking_state *state = tevent_req_data(
+ req, struct posix_blocking_state);
+ NTSTATUS status;
+
+ status = cli_posix_lock_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (!state->gotecho) {
+ printf("blocked req got through before echo\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_LOCK_SEQUENCE);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void posix_blocking_gotecho(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct posix_blocking_state *state = tevent_req_data(
+ req, struct posix_blocking_state);
+ NTSTATUS status;
+
+ status = cli_echo_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (state->gotblocked) {
+ printf("blocked req got through before echo\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_LOCK_SEQUENCE);
+ return;
+ }
+ state->gotecho = true;
+
+ subreq = cli_posix_lock_send(
+ state,
+ state->ev,
+ state->cli1,
+ state->fnum1,
+ 0,
+ 1,
+ false,
+ UNLOCK_LOCK);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, posix_blocking_unlocked, req);
+}
+
+static void posix_blocking_unlocked(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_posix_lock_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ /* tevent_req_done in posix_blocking_gotlocked */
+}
+
+static NTSTATUS posix_blocking_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool run_posix_blocking_lock(int dummy)
+{
+ struct tevent_context *ev = NULL;
+ struct cli_state *cli1 = NULL, *cli2 = NULL;
+ const char *fname = "posix_blocking";
+ uint16_t fnum1 = UINT16_MAX, fnum2 = UINT16_MAX;
+ struct tevent_req *req = NULL;
+ NTSTATUS status;
+ bool ret = false;
+ bool ok;
+
+ printf("Starting posix blocking lock test\n");
+
+ ev = samba_tevent_context_init(NULL);
+ if (ev == NULL) {
+ return false;
+ }
+
+ ok = torture_open_connection(&cli1, 0);
+ if (!ok) {
+ goto fail;
+ }
+ ok = torture_open_connection(&cli2, 0);
+ if (!ok) {
+ goto fail;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli1);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ status = torture_setup_unix_extensions(cli2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_posix_unlink(cli1, fname);
+
+ status = cli_posix_open(cli1, fname, O_RDWR|O_CREAT|O_EXCL,
+ 0600, &fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("First POSIX open of %s failed: %s\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_posix_open(cli2, fname, O_RDWR, 0600, &fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Second POSIX open of %s failed: %s\n",
+ fname,
+ nt_errstr(status));
+ goto fail;
+ }
+
+ req = posix_blocking_send(ev, ev, cli1, fnum1, cli2, fnum2);
+ if (req == NULL) {
+ printf("cli_posix_blocking failed\n");
+ goto fail;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ printf("tevent_req_poll_ntstatus failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ status = posix_blocking_recv(req);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("posix_blocking_recv returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ ret = true;
+fail:
+
+ if (fnum1 != UINT16_MAX) {
+ cli_close(cli1, fnum1);
+ fnum1 = UINT16_MAX;
+ }
+ if (fnum2 != UINT16_MAX) {
+ cli_close(cli2, fnum2);
+ fnum2 = UINT16_MAX;
+ }
+
+ if (cli1 != NULL) {
+ cli_setatr(cli1, fname, 0, 0);
+ cli_posix_unlink(cli1, fname);
+ }
+
+ ok = true;
+
+ if (cli1 != NULL) {
+ ok &= torture_close_connection(cli1);
+ cli1 = NULL;
+ }
+ if (cli2 != NULL) {
+ ok &= torture_close_connection(cli2);
+ cli2 = NULL;
+ }
+
+ if (!ok) {
+ ret = false;
+ }
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+/*
+ Test POSIX mkdir is case-sensitive.
+ */
+static bool run_posix_mkdir_test(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname_foo = "POSIX_foo";
+ const char *fname_foo_Foo = "POSIX_foo/Foo";
+ const char *fname_foo_foo = "POSIX_foo/foo";
+ const char *fname_Foo = "POSIX_Foo";
+ const char *fname_Foo_Foo = "POSIX_Foo/Foo";
+ const char *fname_Foo_foo = "POSIX_Foo/foo";
+ bool correct = false;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+ uint16_t fnum = (uint16_t)-1;
+
+ frame = talloc_stackframe();
+
+ printf("Starting POSIX mkdir test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ cli_posix_rmdir(cli, fname_foo_foo);
+ cli_posix_rmdir(cli, fname_foo_Foo);
+ cli_posix_rmdir(cli, fname_foo);
+
+ cli_posix_rmdir(cli, fname_Foo_foo);
+ cli_posix_rmdir(cli, fname_Foo_Foo);
+ cli_posix_rmdir(cli, fname_Foo);
+
+ /*
+ * Create a file POSIX_foo then try
+ * and use it in a directory path by
+ * doing mkdir POSIX_foo/bar.
+ * The mkdir should fail with
+ * NT_STATUS_OBJECT_PATH_NOT_FOUND
+ */
+
+ status = cli_posix_open(cli,
+ fname_foo,
+ O_RDWR|O_CREAT,
+ 0666,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open of %s failed error %s\n",
+ fname_foo,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_mkdir(cli, fname_foo_foo, 0777);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("cli_posix_mkdir of %s should fail with "
+ "NT_STATUS_OBJECT_PATH_NOT_FOUND got "
+ "%s instead\n",
+ fname_foo_foo,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_close failed %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum = (uint16_t)-1;
+
+ status = cli_posix_unlink(cli, fname_foo);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_unlink of %s failed error %s\n",
+ fname_foo,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /*
+ * Now we've deleted everything, posix_mkdir, posix_rmdir,
+ * posix_open, posix_unlink, on
+ * POSIX_foo/foo should return NT_STATUS_OBJECT_PATH_NOT_FOUND
+ * not silently create POSIX_foo/foo.
+ */
+
+ status = cli_posix_mkdir(cli, fname_foo_foo, 0777);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("cli_posix_mkdir of %s should fail with "
+ "NT_STATUS_OBJECT_PATH_NOT_FOUND got "
+ "%s instead\n",
+ fname_foo_foo,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_rmdir(cli, fname_foo_foo);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("cli_posix_rmdir of %s should fail with "
+ "NT_STATUS_OBJECT_PATH_NOT_FOUND got "
+ "%s instead\n",
+ fname_foo_foo,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_open(cli,
+ fname_foo_foo,
+ O_RDWR|O_CREAT,
+ 0666,
+ &fnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("cli_posix_open of %s should fail with "
+ "NT_STATUS_OBJECT_PATH_NOT_FOUND got "
+ "%s instead\n",
+ fname_foo_foo,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_unlink(cli, fname_foo_foo);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND)) {
+ printf("cli_posix_unlink of %s should fail with "
+ "NT_STATUS_OBJECT_PATH_NOT_FOUND got "
+ "%s instead\n",
+ fname_foo_foo,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_mkdir(cli, fname_foo, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed\n", fname_foo);
+ goto out;
+ }
+
+ status = cli_posix_mkdir(cli, fname_Foo, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed\n", fname_Foo);
+ goto out;
+ }
+
+ status = cli_posix_mkdir(cli, fname_foo_foo, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed\n", fname_foo_foo);
+ goto out;
+ }
+
+ status = cli_posix_mkdir(cli, fname_foo_Foo, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed\n", fname_foo_Foo);
+ goto out;
+ }
+
+ status = cli_posix_mkdir(cli, fname_Foo_foo, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed\n", fname_Foo_foo);
+ goto out;
+ }
+
+ status = cli_posix_mkdir(cli, fname_Foo_Foo, 0777);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s failed\n", fname_Foo_Foo);
+ goto out;
+ }
+
+ printf("POSIX mkdir test passed\n");
+ correct = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ fnum = (uint16_t)-1;
+ }
+
+ cli_posix_rmdir(cli, fname_foo_foo);
+ cli_posix_rmdir(cli, fname_foo_Foo);
+ cli_posix_rmdir(cli, fname_foo);
+
+ cli_posix_rmdir(cli, fname_Foo_foo);
+ cli_posix_rmdir(cli, fname_Foo_Foo);
+ cli_posix_rmdir(cli, fname_Foo);
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+struct posix_acl_oplock_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ bool *got_break;
+ bool *acl_ret;
+ NTSTATUS status;
+};
+
+static void posix_acl_oplock_got_break(struct tevent_req *req)
+{
+ struct posix_acl_oplock_state *state = tevent_req_callback_data(
+ req, struct posix_acl_oplock_state);
+ uint16_t fnum;
+ uint8_t level;
+ NTSTATUS status;
+
+ status = cli_smb_oplock_break_waiter_recv(req, &fnum, &level);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_smb_oplock_break_waiter_recv returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+ *state->got_break = true;
+
+ req = cli_oplock_ack_send(state, state->ev, state->cli, fnum,
+ NO_OPLOCK);
+ if (req == NULL) {
+ printf("cli_oplock_ack_send failed\n");
+ return;
+ }
+}
+
+static void posix_acl_oplock_got_acl(struct tevent_req *req)
+{
+ struct posix_acl_oplock_state *state = tevent_req_callback_data(
+ req, struct posix_acl_oplock_state);
+ size_t ret_size = 0;
+ char *ret_data = NULL;
+
+ state->status = cli_posix_getacl_recv(req,
+ state,
+ &ret_size,
+ &ret_data);
+
+ if (!NT_STATUS_IS_OK(state->status)) {
+ printf("cli_posix_getacl_recv returned %s\n",
+ nt_errstr(state->status));
+ }
+ *state->acl_ret = true;
+}
+
+static bool run_posix_acl_oplock_test(int dummy)
+{
+ struct tevent_context *ev;
+ struct cli_state *cli1, *cli2;
+ struct tevent_req *oplock_req, *getacl_req;
+ const char *fname = "posix_acl_oplock";
+ uint16_t fnum;
+ int saved_use_oplocks = use_oplocks;
+ NTSTATUS status;
+ bool correct = true;
+ bool got_break = false;
+ bool acl_ret = false;
+
+ struct posix_acl_oplock_state *state;
+
+ printf("starting posix_acl_oplock test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ use_level_II_oplocks = false;
+ use_oplocks = saved_use_oplocks;
+ return false;
+ }
+
+ if (!torture_open_connection(&cli2, 1)) {
+ use_level_II_oplocks = false;
+ use_oplocks = saved_use_oplocks;
+ return false;
+ }
+
+ /* Setup posix on cli2 only. */
+ status = torture_setup_unix_extensions(cli2);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+ smbXcli_conn_set_sockopt(cli2->conn, sockops);
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Create the file on the Windows connection. */
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli1, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close1 failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ cli1->use_oplocks = true;
+
+ /* Open with oplock. */
+ status = cli_ntcreate(cli1,
+ fname,
+ 0,
+ FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN,
+ 0,
+ 0,
+ &fnum,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ printf("tevent_context_init failed\n");
+ return false;
+ }
+
+ state = talloc_zero(ev, struct posix_acl_oplock_state);
+ if (state == NULL) {
+ printf("talloc failed\n");
+ return false;
+ }
+ state->ev = ev;
+ state->cli = cli1;
+ state->got_break = &got_break;
+ state->acl_ret = &acl_ret;
+
+ oplock_req = cli_smb_oplock_break_waiter_send(
+ talloc_tos(), ev, cli1);
+ if (oplock_req == NULL) {
+ printf("cli_smb_oplock_break_waiter_send failed\n");
+ return false;
+ }
+ tevent_req_set_callback(oplock_req, posix_acl_oplock_got_break, state);
+
+ /* Get ACL on POSIX connection - should break oplock. */
+ getacl_req = cli_posix_getacl_send(talloc_tos(),
+ ev,
+ cli2,
+ fname);
+ if (getacl_req == NULL) {
+ printf("cli_posix_getacl_send failed\n");
+ return false;
+ }
+ tevent_req_set_callback(getacl_req, posix_acl_oplock_got_acl, state);
+
+ while (!got_break || !acl_ret) {
+ int ret;
+ ret = tevent_loop_once(ev);
+ if (ret == -1) {
+ printf("tevent_loop_once failed: %s\n",
+ strerror(errno));
+ return false;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(state->status)) {
+ printf("getacl failed (%s)\n", nt_errstr(state->status));
+ correct = false;
+ }
+
+ status = cli_close(cli1, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close2 failed (%s)\n", nt_errstr(status));
+ correct = false;
+ }
+
+ status = cli_unlink(cli1,
+ fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ correct = false;
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = false;
+ }
+ if (!torture_close_connection(cli2)) {
+ correct = false;
+ }
+
+ if (!got_break) {
+ correct = false;
+ }
+
+ printf("finished posix acl oplock test\n");
+
+ return correct;
+}
+
+static bool run_posix_acl_shareroot_test(int dummy)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+ bool correct = false;
+ char *posix_acl = NULL;
+ size_t posix_acl_len = 0;
+ uint16_t num_file_acls = 0;
+ uint16_t num_dir_acls = 0;
+ uint16_t i;
+ uint32_t expected_size = 0;
+ bool got_user = false;
+ bool got_group = false;
+ bool got_other = false;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+
+ printf("starting posix_acl_shareroot test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to setup unix extensions\n");
+ goto out;
+ }
+
+ /* Get the POSIX ACL on the root of the share. */
+ status = cli_posix_getacl(cli,
+ ".",
+ frame,
+ &posix_acl_len,
+ &posix_acl);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_getacl of '.' failed (%s)\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (posix_acl_len < 6 ||
+ SVAL(posix_acl,0) != SMB_POSIX_ACL_VERSION) {
+ printf("getfacl ., unknown POSIX acl version %u.\n",
+ (unsigned int)CVAL(posix_acl,0) );
+ goto out;
+ }
+
+ num_file_acls = SVAL(posix_acl,2);
+ num_dir_acls = SVAL(posix_acl,4);
+ expected_size = SMB_POSIX_ACL_HEADER_SIZE +
+ SMB_POSIX_ACL_ENTRY_SIZE*
+ (num_file_acls+num_dir_acls);
+
+ if (posix_acl_len != expected_size) {
+ printf("incorrect POSIX acl buffer size "
+ "(should be %u, was %u).\n",
+ (unsigned int)expected_size,
+ (unsigned int)posix_acl_len);
+ goto out;
+ }
+
+ /*
+ * We don't need to know what the ACL's are
+ * we just need to know we have at least 3
+ * file entries (u,g,o).
+ */
+
+ for (i = 0; i < num_file_acls; i++) {
+ unsigned char tagtype =
+ CVAL(posix_acl,
+ SMB_POSIX_ACL_HEADER_SIZE+
+ (i*SMB_POSIX_ACL_ENTRY_SIZE));
+
+ switch(tagtype) {
+ case SMB_POSIX_ACL_USER_OBJ:
+ got_user = true;
+ break;
+ case SMB_POSIX_ACL_GROUP_OBJ:
+ got_group = true;
+ break;
+ case SMB_POSIX_ACL_OTHER:
+ got_other = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!got_user) {
+ printf("Missing user entry\n");
+ goto out;
+ }
+
+ if (!got_group) {
+ printf("Missing group entry\n");
+ goto out;
+ }
+
+ if (!got_other) {
+ printf("Missing other entry\n");
+ goto out;
+ }
+
+ correct = true;
+
+ out:
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+
+ printf("finished posix acl shareroot test\n");
+ TALLOC_FREE(frame);
+
+ return correct;
+}
+
+static uint32_t open_attrs_table[] = {
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_ATTRIBUTE_ARCHIVE,
+ FILE_ATTRIBUTE_READONLY,
+ FILE_ATTRIBUTE_HIDDEN,
+ FILE_ATTRIBUTE_SYSTEM,
+
+ FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY,
+ FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN,
+ FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM,
+ FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN,
+ FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM,
+ FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM,
+
+ FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN,
+ FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM,
+ FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM,
+ FILE_ATTRIBUTE_HIDDEN,FILE_ATTRIBUTE_SYSTEM,
+};
+
+struct trunc_open_results {
+ unsigned int num;
+ uint32_t init_attr;
+ uint32_t trunc_attr;
+ uint32_t result_attr;
+};
+
+static struct trunc_open_results attr_results[] = {
+ { 0, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_ARCHIVE },
+ { 1, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_ARCHIVE },
+ { 2, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY },
+ { 16, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_ARCHIVE },
+ { 17, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_ARCHIVE },
+ { 18, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY },
+ { 51, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN },
+ { 54, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN },
+ { 56, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN },
+ { 68, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM },
+ { 71, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM },
+ { 73, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM },
+ { 99, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN,FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN },
+ { 102, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN },
+ { 104, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN },
+ { 116, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM },
+ { 119, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM },
+ { 121, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM },
+ { 170, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN },
+ { 173, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM },
+ { 227, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN },
+ { 230, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_HIDDEN },
+ { 232, FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN },
+ { 244, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM },
+ { 247, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_SYSTEM },
+ { 249, FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM, FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM }
+};
+
+static bool run_openattrtest(int dummy)
+{
+ static struct cli_state *cli1;
+ const char *fname = "\\openattr.file";
+ uint16_t fnum1;
+ bool correct = True;
+ uint32_t attr;
+ unsigned int i, j, k, l;
+ NTSTATUS status;
+
+ printf("starting open attr test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ for (k = 0, i = 0; i < sizeof(open_attrs_table)/sizeof(uint32_t); i++) {
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli1, fname, 0, FILE_WRITE_DATA,
+ open_attrs_table[i], FILE_SHARE_NONE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open %d (1) of %s failed (%s)\n", i, fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close %d (1) of %s failed (%s)\n", i, fname, nt_errstr(status));
+ return False;
+ }
+
+ for (j = 0; j < sizeof(open_attrs_table)/sizeof(uint32_t); j++) {
+ status = cli_ntcreate(cli1, fname, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA,
+ open_attrs_table[j],
+ FILE_SHARE_NONE, FILE_OVERWRITE,
+ 0, 0, &fnum1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ for (l = 0; l < sizeof(attr_results)/sizeof(struct trunc_open_results); l++) {
+ if (attr_results[l].num == k) {
+ printf("[%d] trunc open 0x%x -> 0x%x of %s failed - should have succeeded !(0x%x:%s)\n",
+ k, open_attrs_table[i],
+ open_attrs_table[j],
+ fname, NT_STATUS_V(status), nt_errstr(status));
+ correct = False;
+ }
+ }
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ printf("[%d] trunc open 0x%x -> 0x%x failed with wrong error code %s\n",
+ k, open_attrs_table[i], open_attrs_table[j],
+ nt_errstr(status));
+ correct = False;
+ }
+#if 0
+ printf("[%d] trunc open 0x%x -> 0x%x failed\n", k, open_attrs_table[i], open_attrs_table[j]);
+#endif
+ k++;
+ continue;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close %d (2) of %s failed (%s)\n", j, fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_getatr(cli1, fname, &attr, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("getatr(2) failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+#if 0
+ printf("[%d] getatr check [0x%x] trunc [0x%x] got attr 0x%x\n",
+ k, open_attrs_table[i], open_attrs_table[j], attr );
+#endif
+
+ for (l = 0; l < sizeof(attr_results)/sizeof(struct trunc_open_results); l++) {
+ if (attr_results[l].num == k) {
+ if (attr != attr_results[l].result_attr ||
+ open_attrs_table[i] != attr_results[l].init_attr ||
+ open_attrs_table[j] != attr_results[l].trunc_attr) {
+ printf("getatr check failed. [0x%x] trunc [0x%x] got attr 0x%x, should be 0x%x\n",
+ open_attrs_table[i],
+ open_attrs_table[j],
+ (unsigned int)attr,
+ attr_results[l].result_attr);
+ correct = False;
+ }
+ break;
+ }
+ }
+ k++;
+ }
+ }
+
+ cli_setatr(cli1, fname, 0, 0);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ printf("open attr test %s.\n", correct ? "passed" : "failed");
+
+ if (!torture_close_connection(cli1)) {
+ correct = False;
+ }
+ return correct;
+}
+
+static NTSTATUS list_fn(struct file_info *finfo,
+ const char *name, void *state)
+{
+ int *matched = (int *)state;
+ if (matched != NULL) {
+ *matched += 1;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ test directory listing speed
+ */
+static bool run_dirtest(int dummy)
+{
+ int i;
+ static struct cli_state *cli;
+ uint16_t fnum;
+ struct timeval core_start;
+ bool correct = True;
+ int matched;
+
+ printf("starting directory test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ srandom(0);
+ for (i=0;i<torture_numops;i++) {
+ fstring fname;
+ slprintf(fname, sizeof(fname), "\\%x", (int)random());
+ if (!NT_STATUS_IS_OK(cli_openx(cli, fname, O_RDWR|O_CREAT, DENY_NONE, &fnum))) {
+ fprintf(stderr,"Failed to open %s\n", fname);
+ return False;
+ }
+ cli_close(cli, fnum);
+ }
+
+ core_start = timeval_current();
+
+ matched = 0;
+ cli_list(cli, "a*.*", 0, list_fn, &matched);
+ printf("Matched %d\n", matched);
+
+ matched = 0;
+ cli_list(cli, "b*.*", 0, list_fn, &matched);
+ printf("Matched %d\n", matched);
+
+ matched = 0;
+ cli_list(cli, "xyzabc", 0, list_fn, &matched);
+ printf("Matched %d\n", matched);
+
+ printf("dirtest core %g seconds\n", timeval_elapsed(&core_start));
+
+ srandom(0);
+ for (i=0;i<torture_numops;i++) {
+ fstring fname;
+ slprintf(fname, sizeof(fname), "\\%x", (int)random());
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ }
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("finished dirtest\n");
+
+ return correct;
+}
+
+static NTSTATUS del_fn(struct file_info *finfo, const char *mask,
+ void *state)
+{
+ struct cli_state *pcli = (struct cli_state *)state;
+ fstring fname;
+ slprintf(fname, sizeof(fname), "\\LISTDIR\\%s", finfo->name);
+
+ if (strcmp(finfo->name, ".") == 0 || strcmp(finfo->name, "..") == 0)
+ return NT_STATUS_OK;
+
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ if (!NT_STATUS_IS_OK(cli_rmdir(pcli, fname)))
+ printf("del_fn: failed to rmdir %s\n,", fname );
+ } else {
+ if (!NT_STATUS_IS_OK(cli_unlink(pcli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN)))
+ printf("del_fn: failed to unlink %s\n,", fname );
+ }
+ return NT_STATUS_OK;
+}
+
+
+/*
+ send a raw ioctl - used by the torture code
+*/
+static NTSTATUS cli_raw_ioctl(struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t code,
+ DATA_BLOB *blob)
+{
+ uint16_t vwv[3];
+ NTSTATUS status;
+
+ PUSH_LE_U16(vwv + 0, 0, fnum);
+ PUSH_LE_U16(vwv + 1, 0, code >> 16);
+ PUSH_LE_U16(vwv + 2, 0, (code & 0xFFFF));
+
+ status = cli_smb(talloc_tos(),
+ cli,
+ SMBioctl,
+ 0,
+ 3,
+ vwv,
+ 0,
+ NULL,
+ NULL,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *blob = data_blob_null;
+ return NT_STATUS_OK;
+}
+
+/*
+ sees what IOCTLs are supported
+ */
+bool torture_ioctl_test(int dummy)
+{
+ static struct cli_state *cli;
+ uint16_t device, function;
+ uint16_t fnum;
+ const char *fname = "\\ioctl.dat";
+ DATA_BLOB blob;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ printf("starting ioctl test\n");
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_openx(cli, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open of %s failed (%s)\n", fname, nt_errstr(status));
+ return False;
+ }
+
+ status = cli_raw_ioctl(cli, fnum, 0x2d0000 | (0x0420<<2), &blob);
+ printf("ioctl device info: %s\n", nt_errstr(status));
+
+ status = cli_raw_ioctl(cli, fnum, IOCTL_QUERY_JOB_INFO, &blob);
+ printf("ioctl job info: %s\n", nt_errstr(status));
+
+ for (device=0;device<0x100;device++) {
+ printf("ioctl test with device = 0x%x\n", device);
+ for (function=0;function<0x100;function++) {
+ uint32_t code = (device<<16) | function;
+
+ status = cli_raw_ioctl(cli, fnum, code, &blob);
+
+ if (NT_STATUS_IS_OK(status)) {
+ printf("ioctl 0x%x OK : %d bytes\n", (int)code,
+ (int)blob.length);
+ data_blob_free(&blob);
+ }
+ }
+ }
+
+ if (!torture_close_connection(cli)) {
+ return False;
+ }
+
+ return True;
+}
+
+
+/*
+ tries variants of chkpath
+ */
+bool torture_chkpath_test(int dummy)
+{
+ static struct cli_state *cli;
+ uint16_t fnum;
+ bool ret;
+ NTSTATUS status;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ printf("starting chkpath test\n");
+
+ /* cleanup from an old run */
+ torture_deltree(cli, "\\chkpath.dir");
+
+ status = cli_mkdir(cli, "\\chkpath.dir");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mkdir1 failed : %s\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_mkdir(cli, "\\chkpath.dir\\dir2");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mkdir2 failed : %s\n", nt_errstr(status));
+ return False;
+ }
+
+ status = cli_openx(cli, "\\chkpath.dir\\foo.txt", O_RDWR|O_CREAT|O_EXCL,
+ DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open1 failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+ cli_close(cli, fnum);
+
+ status = cli_chkpath(cli, "\\chkpath.dir");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("chkpath1 failed: %s\n", nt_errstr(status));
+ ret = False;
+ }
+
+ status = cli_chkpath(cli, "\\chkpath.dir\\dir2");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("chkpath2 failed: %s\n", nt_errstr(status));
+ ret = False;
+ }
+
+ status = cli_chkpath(cli, "\\chkpath.dir\\foo.txt");
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = check_error(__LINE__, status, ERRDOS, ERRbadpath,
+ NT_STATUS_NOT_A_DIRECTORY);
+ } else {
+ printf("* chkpath on a file should fail\n");
+ ret = False;
+ }
+
+ status = cli_chkpath(cli, "\\chkpath.dir\\bar.txt");
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = check_error(__LINE__, status, ERRDOS, ERRbadfile,
+ NT_STATUS_OBJECT_NAME_NOT_FOUND);
+ } else {
+ printf("* chkpath on a non existent file should fail\n");
+ ret = False;
+ }
+
+ status = cli_chkpath(cli, "\\chkpath.dir\\dirxx\\bar.txt");
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = check_error(__LINE__, status, ERRDOS, ERRbadpath,
+ NT_STATUS_OBJECT_PATH_NOT_FOUND);
+ } else {
+ printf("* chkpath on a non existent component should fail\n");
+ ret = False;
+ }
+
+ torture_deltree(cli, "\\chkpath.dir");
+
+ if (!torture_close_connection(cli)) {
+ return False;
+ }
+
+ return ret;
+}
+
+static bool run_eatest(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname = "\\eatest.txt";
+ bool correct = True;
+ uint16_t fnum;
+ size_t i, num_eas;
+ struct ea_struct *ea_list = NULL;
+ TALLOC_CTX *mem_ctx = talloc_init("eatest");
+ NTSTATUS status;
+
+ printf("starting eatest\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ talloc_destroy(mem_ctx);
+ return False;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ status = cli_ntcreate(cli, fname, 0,
+ FIRST_DESIRED_ACCESS, FILE_ATTRIBUTE_ARCHIVE,
+ FILE_SHARE_NONE, FILE_OVERWRITE_IF,
+ 0x4044, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open failed - %s\n", nt_errstr(status));
+ talloc_destroy(mem_ctx);
+ return False;
+ }
+
+ for (i = 0; i < 10; i++) {
+ fstring ea_name, ea_val;
+
+ slprintf(ea_name, sizeof(ea_name), "EA_%zu", i);
+ memset(ea_val, (char)i+1, i+1);
+ status = cli_set_ea_fnum(cli, fnum, ea_name, ea_val, i+1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ea_set of name %s failed - %s\n", ea_name,
+ nt_errstr(status));
+ talloc_destroy(mem_ctx);
+ return False;
+ }
+ }
+
+ cli_close(cli, fnum);
+ for (i = 0; i < 10; i++) {
+ fstring ea_name, ea_val;
+
+ slprintf(ea_name, sizeof(ea_name), "EA_%zu", i+10);
+ memset(ea_val, (char)i+1, i+1);
+ status = cli_set_ea_path(cli, fname, ea_name, ea_val, i+1);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ea_set of name %s failed - %s\n", ea_name,
+ nt_errstr(status));
+ talloc_destroy(mem_ctx);
+ return False;
+ }
+ }
+
+ status = cli_get_ea_list_path(cli, fname, mem_ctx, &num_eas, &ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ea_get list failed - %s\n", nt_errstr(status));
+ correct = False;
+ }
+
+ printf("num_eas = %d\n", (int)num_eas);
+
+ if (num_eas != 20) {
+ printf("Should be 20 EA's stored... failing.\n");
+ correct = False;
+ }
+
+ for (i = 0; i < num_eas; i++) {
+ printf("%zu: ea_name = %s. Val = ", i, ea_list[i].name);
+ dump_data(0, ea_list[i].value.data,
+ ea_list[i].value.length);
+ }
+
+ /* Setting EA's to zero length deletes them. Test this */
+ printf("Now deleting all EA's - case independent....\n");
+
+#if 1
+ cli_set_ea_path(cli, fname, "", "", 0);
+#else
+ for (i = 0; i < 20; i++) {
+ fstring ea_name;
+ slprintf(ea_name, sizeof(ea_name), "ea_%d", i);
+ status = cli_set_ea_path(cli, fname, ea_name, "", 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ea_set of name %s failed - %s\n", ea_name,
+ nt_errstr(status));
+ talloc_destroy(mem_ctx);
+ return False;
+ }
+ }
+#endif
+
+ status = cli_get_ea_list_path(cli, fname, mem_ctx, &num_eas, &ea_list);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ea_get list failed - %s\n", nt_errstr(status));
+ correct = False;
+ }
+
+ printf("num_eas = %d\n", (int)num_eas);
+ for (i = 0; i < num_eas; i++) {
+ printf("%zu: ea_name = %s. Val = ", i, ea_list[i].name);
+ dump_data(0, ea_list[i].value.data,
+ ea_list[i].value.length);
+ }
+
+ if (num_eas != 0) {
+ printf("deleting EA's failed.\n");
+ correct = False;
+ }
+
+ /* Try and delete a non existent EA. */
+ status = cli_set_ea_path(cli, fname, "foo", "", 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("deleting non-existent EA 'foo' should succeed. %s\n",
+ nt_errstr(status));
+ correct = False;
+ }
+
+ talloc_destroy(mem_ctx);
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ return correct;
+}
+
+static bool run_dirtest1(int dummy)
+{
+ int i;
+ static struct cli_state *cli;
+ uint16_t fnum;
+ int num_seen;
+ bool correct = True;
+
+ printf("starting directory test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ cli_list(cli, "\\LISTDIR\\*", 0, del_fn, cli);
+ cli_list(cli, "\\LISTDIR\\*", FILE_ATTRIBUTE_DIRECTORY, del_fn, cli);
+ cli_rmdir(cli, "\\LISTDIR");
+ cli_mkdir(cli, "\\LISTDIR");
+
+ /* Create 1000 files and 1000 directories. */
+ for (i=0;i<1000;i++) {
+ fstring fname;
+ slprintf(fname, sizeof(fname), "\\LISTDIR\\f%d", i);
+ if (!NT_STATUS_IS_OK(cli_ntcreate(cli, fname, 0, GENERIC_ALL_ACCESS, FILE_ATTRIBUTE_ARCHIVE,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OVERWRITE_IF,
+ 0, 0, &fnum, NULL))) {
+ fprintf(stderr,"Failed to open %s\n", fname);
+ return False;
+ }
+ cli_close(cli, fnum);
+ }
+ for (i=0;i<1000;i++) {
+ fstring fname;
+ slprintf(fname, sizeof(fname), "\\LISTDIR\\d%d", i);
+ if (!NT_STATUS_IS_OK(cli_mkdir(cli, fname))) {
+ fprintf(stderr,"Failed to open %s\n", fname);
+ return False;
+ }
+ }
+
+ /* Now ensure that doing an old list sees both files and directories. */
+ num_seen = 0;
+ cli_list_old(cli, "\\LISTDIR\\*", FILE_ATTRIBUTE_DIRECTORY, list_fn, &num_seen);
+ printf("num_seen = %d\n", num_seen );
+ /* We should see 100 files + 1000 directories + . and .. */
+ if (num_seen != 2002)
+ correct = False;
+
+ /* Ensure if we have the "must have" bits we only see the
+ * relevant entries.
+ */
+ num_seen = 0;
+ cli_list_old(cli, "\\LISTDIR\\*", (FILE_ATTRIBUTE_DIRECTORY<<8)|FILE_ATTRIBUTE_DIRECTORY, list_fn, &num_seen);
+ printf("num_seen = %d\n", num_seen );
+ if (num_seen != 1002)
+ correct = False;
+
+ num_seen = 0;
+ cli_list_old(cli, "\\LISTDIR\\*", (FILE_ATTRIBUTE_ARCHIVE<<8)|FILE_ATTRIBUTE_DIRECTORY, list_fn, &num_seen);
+ printf("num_seen = %d\n", num_seen );
+ if (num_seen != 1000)
+ correct = False;
+
+ /* Delete everything. */
+ cli_list(cli, "\\LISTDIR\\*", 0, del_fn, cli);
+ cli_list(cli, "\\LISTDIR\\*", FILE_ATTRIBUTE_DIRECTORY, del_fn, cli);
+ cli_rmdir(cli, "\\LISTDIR");
+
+#if 0
+ printf("Matched %d\n", cli_list(cli, "a*.*", 0, list_fn, NULL));
+ printf("Matched %d\n", cli_list(cli, "b*.*", 0, list_fn, NULL));
+ printf("Matched %d\n", cli_list(cli, "xyzabc", 0, list_fn, NULL));
+#endif
+
+ if (!torture_close_connection(cli)) {
+ correct = False;
+ }
+
+ printf("finished dirtest1\n");
+
+ return correct;
+}
+
+static bool run_error_map_extract(int dummy) {
+
+ static struct cli_state *c_dos;
+ static struct cli_state *c_nt;
+ NTSTATUS status;
+
+ uint32_t error;
+
+ uint32_t errnum;
+ uint8_t errclass;
+
+ NTSTATUS nt_status;
+
+ fstring user;
+
+ /* NT-Error connection */
+
+ disable_spnego = true;
+ if (!(c_nt = open_nbt_connection())) {
+ disable_spnego = false;
+ return False;
+ }
+ disable_spnego = false;
+
+ status = smbXcli_negprot(c_nt->conn,
+ c_nt->timeout,
+ PROTOCOL_CORE,
+ PROTOCOL_NT1,
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s rejected the NT-error negprot (%s)\n", host,
+ nt_errstr(status));
+ cli_shutdown(c_nt);
+ return False;
+ }
+
+ status = cli_session_setup_anon(c_nt);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s rejected the NT-error initial session setup (%s)\n",host, nt_errstr(status));
+ return False;
+ }
+
+ /* DOS-Error connection */
+
+ disable_spnego = true;
+ force_dos_errors = true;
+ if (!(c_dos = open_nbt_connection())) {
+ disable_spnego = false;
+ force_dos_errors = false;
+ return False;
+ }
+ disable_spnego = false;
+ force_dos_errors = false;
+
+ status = smbXcli_negprot(c_dos->conn,
+ c_dos->timeout,
+ PROTOCOL_CORE,
+ PROTOCOL_NT1,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s rejected the DOS-error negprot (%s)\n", host,
+ nt_errstr(status));
+ cli_shutdown(c_dos);
+ return False;
+ }
+
+ status = cli_session_setup_anon(c_dos);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("%s rejected the DOS-error initial session setup (%s)\n",
+ host, nt_errstr(status));
+ return False;
+ }
+
+ c_nt->map_dos_errors = false;
+ c_dos->map_dos_errors = false;
+
+ for (error=(0xc0000000 | 0x1); error < (0xc0000000| 0xFFF); error++) {
+ struct cli_credentials *user_creds = NULL;
+
+ fstr_sprintf(user, "%X", error);
+
+ user_creds = cli_session_creds_init(talloc_tos(),
+ user,
+ workgroup,
+ NULL, /* realm */
+ password,
+ false, /* use_kerberos */
+ false, /* fallback_after_kerberos */
+ false, /* use_ccache */
+ false); /* password_is_nt_hash */
+ if (user_creds == NULL) {
+ printf("cli_session_creds_init(%s) failed\n", user);
+ return false;
+ }
+
+ status = cli_session_setup_creds(c_nt, user_creds);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("/** Session setup succeeded. This shouldn't happen...*/\n");
+ }
+
+ /* Case #1: 32-bit NT errors */
+ if (!NT_STATUS_IS_DOS(status)) {
+ nt_status = status;
+ } else {
+ printf("/** Dos error on NT connection! (%s) */\n",
+ nt_errstr(status));
+ nt_status = NT_STATUS(0xc0000000);
+ }
+
+ status = cli_session_setup_creds(c_dos, user_creds);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("/** Session setup succeeded. This shouldn't happen...*/\n");
+ }
+
+ /* Case #1: 32-bit NT errors */
+ if (NT_STATUS_IS_DOS(status)) {
+ printf("/** NT error on DOS connection! (%s) */\n",
+ nt_errstr(status));
+ errnum = errclass = 0;
+ } else {
+ errclass = NT_STATUS_DOS_CLASS(status);
+ errnum = NT_STATUS_DOS_CODE(status);
+ }
+
+ if (NT_STATUS_V(nt_status) != error) {
+ printf("/*\t{ This NT error code was 'sqashed'\n\t from %s to %s \n\t during the session setup }\n*/\n",
+ get_nt_error_c_code(talloc_tos(), NT_STATUS(error)),
+ get_nt_error_c_code(talloc_tos(), nt_status));
+ }
+
+ printf("\t{%s,\t%s,\t%s},\n",
+ smb_dos_err_class(errclass),
+ smb_dos_err_name(errclass, errnum),
+ get_nt_error_c_code(talloc_tos(), NT_STATUS(error)));
+
+ TALLOC_FREE(user_creds);
+ }
+ return True;
+}
+
+static bool run_sesssetup_bench(int dummy)
+{
+ static struct cli_state *c;
+ const char *fname = "\\file.dat";
+ uint16_t fnum;
+ NTSTATUS status;
+ int i;
+
+ if (!torture_open_connection(&c, 0)) {
+ return false;
+ }
+
+ status = cli_ntcreate(c, fname, 0, GENERIC_ALL_ACCESS|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ FILE_DELETE_ON_CLOSE, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open %s failed: %s\n", fname, nt_errstr(status));
+ return false;
+ }
+
+ for (i=0; i<torture_numops; i++) {
+ status = cli_session_setup_creds(c, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) cli_session_setup_creds failed: %s\n",
+ __location__, nt_errstr(status));
+ return false;
+ }
+
+ d_printf("\r%d ", (int)cli_state_get_uid(c));
+
+ status = cli_ulogoff(c);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) cli_ulogoff failed: %s\n",
+ __location__, nt_errstr(status));
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool subst_test(const char *str, const char *user, const char *domain,
+ uid_t uid, gid_t gid, const char *expected)
+{
+ char *subst;
+ bool result = true;
+
+ subst = talloc_sub_specified(talloc_tos(), str, user, NULL, domain, uid, gid);
+
+ if (strcmp(subst, expected) != 0) {
+ printf("sub_specified(%s, %s, %s, %d, %d) returned [%s], expected "
+ "[%s]\n", str, user, domain, (int)uid, (int)gid, subst,
+ expected);
+ result = false;
+ }
+
+ TALLOC_FREE(subst);
+ return result;
+}
+
+static void chain1_open_completion(struct tevent_req *req)
+{
+ uint16_t fnum;
+ NTSTATUS status;
+ status = cli_openx_recv(req, &fnum);
+ TALLOC_FREE(req);
+
+ d_printf("cli_openx_recv returned %s: %d\n",
+ nt_errstr(status),
+ NT_STATUS_IS_OK(status) ? fnum : -1);
+}
+
+static void chain1_write_completion(struct tevent_req *req)
+{
+ size_t written;
+ NTSTATUS status;
+ status = cli_write_andx_recv(req, &written);
+ TALLOC_FREE(req);
+
+ d_printf("cli_write_andx_recv returned %s: %d\n",
+ nt_errstr(status),
+ NT_STATUS_IS_OK(status) ? (int)written : -1);
+}
+
+static void chain1_close_completion(struct tevent_req *req)
+{
+ NTSTATUS status;
+ bool *done = (bool *)tevent_req_callback_data_void(req);
+
+ status = cli_close_recv(req);
+ *done = true;
+
+ TALLOC_FREE(req);
+
+ d_printf("cli_close returned %s\n", nt_errstr(status));
+}
+
+static bool run_chain1(int dummy)
+{
+ struct cli_state *cli1;
+ struct tevent_context *evt = samba_tevent_context_init(NULL);
+ struct tevent_req *reqs[3], *smbreqs[3];
+ bool done = false;
+ const char *str = "foobar";
+ const char *fname = "\\test_chain";
+ NTSTATUS status;
+
+ printf("starting chain1 test\n");
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ reqs[0] = cli_openx_create(talloc_tos(), evt, cli1, fname,
+ O_CREAT|O_RDWR, 0, &smbreqs[0]);
+ if (reqs[0] == NULL) return false;
+ tevent_req_set_callback(reqs[0], chain1_open_completion, NULL);
+
+
+ reqs[1] = cli_write_andx_create(talloc_tos(), evt, cli1, 0, 0,
+ (const uint8_t *)str, 0, strlen(str)+1,
+ smbreqs, 1, &smbreqs[1]);
+ if (reqs[1] == NULL) return false;
+ tevent_req_set_callback(reqs[1], chain1_write_completion, NULL);
+
+ reqs[2] = cli_smb1_close_create(talloc_tos(), evt, cli1, 0, &smbreqs[2]);
+ if (reqs[2] == NULL) return false;
+ tevent_req_set_callback(reqs[2], chain1_close_completion, &done);
+
+ status = smb1cli_req_chain_submit(smbreqs, ARRAY_SIZE(smbreqs));
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ while (!done) {
+ tevent_loop_once(evt);
+ }
+
+ torture_close_connection(cli1);
+ return True;
+}
+
+static void chain2_sesssetup_completion(struct tevent_req *req)
+{
+ NTSTATUS status;
+ status = cli_session_setup_guest_recv(req);
+ d_printf("sesssetup returned %s\n", nt_errstr(status));
+}
+
+static void chain2_tcon_completion(struct tevent_req *req)
+{
+ bool *done = (bool *)tevent_req_callback_data_void(req);
+ NTSTATUS status;
+ status = cli_tcon_andx_recv(req);
+ d_printf("tcon_and_x returned %s\n", nt_errstr(status));
+ *done = true;
+}
+
+static bool run_chain2(int dummy)
+{
+ struct cli_state *cli1;
+ struct tevent_context *evt = samba_tevent_context_init(NULL);
+ struct tevent_req *reqs[2], *smbreqs[2];
+ bool done = false;
+ NTSTATUS status;
+ int flags = CLI_FULL_CONNECTION_FORCE_SMB1;
+
+ printf("starting chain2 test\n");
+ status = cli_start_connection(&cli1, lp_netbios_name(), host, NULL,
+ port_to_use, SMB_SIGNING_DEFAULT, flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ reqs[0] = cli_session_setup_guest_create(talloc_tos(), evt, cli1,
+ &smbreqs[0]);
+ if (reqs[0] == NULL) return false;
+ tevent_req_set_callback(reqs[0], chain2_sesssetup_completion, NULL);
+
+ reqs[1] = cli_tcon_andx_create(talloc_tos(), evt, cli1, "IPC$",
+ "?????", NULL, 0, &smbreqs[1]);
+ if (reqs[1] == NULL) return false;
+ tevent_req_set_callback(reqs[1], chain2_tcon_completion, &done);
+
+ status = smb1cli_req_chain_submit(smbreqs, ARRAY_SIZE(smbreqs));
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ while (!done) {
+ tevent_loop_once(evt);
+ }
+
+ torture_close_connection(cli1);
+ return True;
+}
+
+
+struct torture_createdel_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+};
+
+static void torture_createdel_created(struct tevent_req *subreq);
+static void torture_createdel_closed(struct tevent_req *subreq);
+
+static struct tevent_req *torture_createdel_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *name)
+{
+ struct tevent_req *req, *subreq;
+ struct torture_createdel_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct torture_createdel_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+
+ subreq = cli_ntcreate_send(
+ state, ev, cli, name, 0,
+ FILE_READ_DATA|FILE_WRITE_DATA|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN_IF, FILE_DELETE_ON_CLOSE,
+ SMB2_IMPERSONATION_IMPERSONATION, 0);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, torture_createdel_created, req);
+ return req;
+}
+
+static void torture_createdel_created(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct torture_createdel_state *state = tevent_req_data(
+ req, struct torture_createdel_state);
+ NTSTATUS status;
+ uint16_t fnum;
+
+ status = cli_ntcreate_recv(subreq, &fnum, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("cli_ntcreate_recv returned %s\n",
+ nt_errstr(status)));
+ return;
+ }
+
+ subreq = cli_close_send(state, state->ev, state->cli, fnum, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, torture_createdel_closed, req);
+}
+
+static void torture_createdel_closed(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = cli_close_recv(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DEBUG(10, ("cli_close_recv returned %s\n", nt_errstr(status)));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS torture_createdel_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct torture_createdels_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ const char *base_name;
+ int sent;
+ int received;
+ int num_files;
+ struct tevent_req **reqs;
+};
+
+static void torture_createdels_done(struct tevent_req *subreq);
+
+static struct tevent_req *torture_createdels_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ const char *base_name,
+ int num_parallel,
+ int num_files)
+{
+ struct tevent_req *req;
+ struct torture_createdels_state *state;
+ int i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct torture_createdels_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->base_name = talloc_strdup(state, base_name);
+ if (tevent_req_nomem(state->base_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->num_files = MAX(num_parallel, num_files);
+ state->sent = 0;
+ state->received = 0;
+
+ state->reqs = talloc_array(state, struct tevent_req *, num_parallel);
+ if (tevent_req_nomem(state->reqs, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_parallel; i++) {
+ char *name;
+
+ name = talloc_asprintf(state, "%s%8.8d", state->base_name,
+ state->sent);
+ if (tevent_req_nomem(name, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->reqs[i] = torture_createdel_send(
+ state->reqs, state->ev, state->cli, name);
+ if (tevent_req_nomem(state->reqs[i], req)) {
+ return tevent_req_post(req, ev);
+ }
+ name = talloc_move(state->reqs[i], &name);
+ tevent_req_set_callback(state->reqs[i],
+ torture_createdels_done, req);
+ state->sent += 1;
+ }
+ return req;
+}
+
+static void torture_createdels_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct torture_createdels_state *state = tevent_req_data(
+ req, struct torture_createdels_state);
+ size_t i, num_parallel = talloc_array_length(state->reqs);
+ NTSTATUS status;
+ char *name;
+
+ status = torture_createdel_recv(subreq);
+ if (!NT_STATUS_IS_OK(status)){
+ DEBUG(10, ("torture_createdel_recv returned %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(subreq);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ for (i=0; i<num_parallel; i++) {
+ if (subreq == state->reqs[i]) {
+ break;
+ }
+ }
+ if (i == num_parallel) {
+ DEBUG(10, ("received something we did not send\n"));
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+ TALLOC_FREE(state->reqs[i]);
+
+ if (state->sent >= state->num_files) {
+ tevent_req_done(req);
+ return;
+ }
+
+ name = talloc_asprintf(state, "%s%8.8d", state->base_name,
+ state->sent);
+ if (tevent_req_nomem(name, req)) {
+ return;
+ }
+ state->reqs[i] = torture_createdel_send(state->reqs, state->ev,
+ state->cli, name);
+ if (tevent_req_nomem(state->reqs[i], req)) {
+ return;
+ }
+ name = talloc_move(state->reqs[i], &name);
+ tevent_req_set_callback(state->reqs[i], torture_createdels_done, req);
+ state->sent += 1;
+}
+
+static NTSTATUS torture_createdels_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct swallow_notify_state {
+ struct tevent_context *ev;
+ struct cli_state *cli;
+ uint16_t fnum;
+ uint32_t completion_filter;
+ bool recursive;
+ bool (*fn)(uint32_t action, const char *name, void *priv);
+ void *priv;
+};
+
+static void swallow_notify_done(struct tevent_req *subreq);
+
+static struct tevent_req *swallow_notify_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli,
+ uint16_t fnum,
+ uint32_t completion_filter,
+ bool recursive,
+ bool (*fn)(uint32_t action,
+ const char *name,
+ void *priv),
+ void *priv)
+{
+ struct tevent_req *req, *subreq;
+ struct swallow_notify_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct swallow_notify_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->cli = cli;
+ state->fnum = fnum;
+ state->completion_filter = completion_filter;
+ state->recursive = recursive;
+ state->fn = fn;
+ state->priv = priv;
+
+ subreq = cli_notify_send(state, state->ev, state->cli, state->fnum,
+ 0xffff, state->completion_filter,
+ state->recursive);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, swallow_notify_done, req);
+ return req;
+}
+
+static void swallow_notify_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct swallow_notify_state *state = tevent_req_data(
+ req, struct swallow_notify_state);
+ NTSTATUS status;
+ uint32_t i, num_changes;
+ struct notify_change *changes;
+
+ status = cli_notify_recv(subreq, state, &num_changes, &changes);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("cli_notify_recv returned %s\n",
+ nt_errstr(status)));
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ for (i=0; i<num_changes; i++) {
+ state->fn(changes[i].action, changes[i].name, state->priv);
+ }
+ TALLOC_FREE(changes);
+
+ subreq = cli_notify_send(state, state->ev, state->cli, state->fnum,
+ 0xffff, state->completion_filter,
+ state->recursive);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, swallow_notify_done, req);
+}
+
+static bool print_notifies(uint32_t action, const char *name, void *priv)
+{
+ if (DEBUGLEVEL > 5) {
+ d_printf("%d %s\n", (int)action, name);
+ }
+ return true;
+}
+
+static void notify_bench_done(struct tevent_req *req)
+{
+ int *num_finished = (int *)tevent_req_callback_data_void(req);
+ *num_finished += 1;
+}
+
+static bool run_notify_bench(int dummy)
+{
+ const char *dname = "\\notify-bench";
+ struct tevent_context *ev;
+ NTSTATUS status;
+ uint16_t dnum;
+ struct tevent_req *req1;
+ struct tevent_req *req2 = NULL;
+ int i, num_unc_names;
+ int num_finished = 0;
+
+ printf("starting notify-bench test\n");
+
+ if (use_multishare_conn) {
+ char **unc_list;
+ unc_list = file_lines_load(multishare_conn_fname,
+ &num_unc_names, 0, NULL);
+ if (!unc_list || num_unc_names <= 0) {
+ d_printf("Failed to load unc names list from '%s'\n",
+ multishare_conn_fname);
+ return false;
+ }
+ TALLOC_FREE(unc_list);
+ } else {
+ num_unc_names = 1;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_printf("tevent_context_init failed\n");
+ return false;
+ }
+
+ for (i=0; i<num_unc_names; i++) {
+ struct cli_state *cli;
+ char *base_fname;
+
+ base_fname = talloc_asprintf(talloc_tos(), "%s\\file%3.3d.",
+ dname, i);
+ if (base_fname == NULL) {
+ return false;
+ }
+
+ if (!torture_open_connection(&cli, i)) {
+ return false;
+ }
+
+ status = cli_ntcreate(cli, dname, 0,
+ MAXIMUM_ALLOWED_ACCESS,
+ 0, FILE_SHARE_READ|FILE_SHARE_WRITE|
+ FILE_SHARE_DELETE,
+ FILE_OPEN_IF, FILE_DIRECTORY_FILE, 0,
+ &dnum, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Could not create %s: %s\n", dname,
+ nt_errstr(status));
+ return false;
+ }
+
+ req1 = swallow_notify_send(talloc_tos(), ev, cli, dnum,
+ FILE_NOTIFY_CHANGE_FILE_NAME |
+ FILE_NOTIFY_CHANGE_DIR_NAME |
+ FILE_NOTIFY_CHANGE_ATTRIBUTES |
+ FILE_NOTIFY_CHANGE_LAST_WRITE,
+ false, print_notifies, NULL);
+ if (req1 == NULL) {
+ d_printf("Could not create notify request\n");
+ return false;
+ }
+
+ req2 = torture_createdels_send(talloc_tos(), ev, cli,
+ base_fname, 10, torture_numops);
+ if (req2 == NULL) {
+ d_printf("Could not create createdels request\n");
+ return false;
+ }
+ TALLOC_FREE(base_fname);
+
+ tevent_req_set_callback(req2, notify_bench_done,
+ &num_finished);
+ }
+
+ while (num_finished < num_unc_names) {
+ int ret;
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ d_printf("tevent_loop_once failed\n");
+ return false;
+ }
+ }
+
+ if (!tevent_req_poll(req2, ev)) {
+ d_printf("tevent_req_poll failed\n");
+ }
+
+ status = torture_createdels_recv(req2);
+ d_printf("torture_createdels_recv returned %s\n", nt_errstr(status));
+
+ return true;
+}
+
+static bool run_mangle1(int dummy)
+{
+ struct cli_state *cli;
+ const char *fname = "this_is_a_long_fname_to_be_mangled.txt";
+ uint16_t fnum;
+ fstring alt_name;
+ NTSTATUS status;
+
+ printf("starting mangle1 test\n");
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = cli_ntcreate(cli, fname, 0, GENERIC_ALL_ACCESS|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open %s failed: %s\n", fname, nt_errstr(status));
+ return false;
+ }
+ cli_close(cli, fnum);
+
+ status = cli_qpathinfo_alt_name(cli, fname, alt_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_qpathinfo_alt_name failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ d_printf("alt_name: %s\n", alt_name);
+
+ status = cli_openx(cli, alt_name, O_RDONLY, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_openx(%s) failed: %s\n", alt_name,
+ nt_errstr(status));
+ return false;
+ }
+ cli_close(cli, fnum);
+
+ status = cli_qpathinfo1(cli, alt_name, NULL, NULL, NULL, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_qpathinfo1(%s) failed: %s\n", alt_name,
+ nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+static NTSTATUS mangle_illegal_list_shortname_fn(struct file_info *f,
+ const char *mask,
+ void *state)
+{
+ if (f->short_name == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (strlen(f->short_name) == 0) {
+ return NT_STATUS_OK;
+ }
+
+ printf("unexpected shortname: %s\n", f->short_name);
+
+ return NT_STATUS_OBJECT_NAME_INVALID;
+}
+
+static NTSTATUS mangle_illegal_list_name_fn(struct file_info *f,
+ const char *mask,
+ void *state)
+{
+ char *name = state;
+
+ printf("name: %s\n", f->name);
+ fstrcpy(name, f->name);
+ return NT_STATUS_OK;
+}
+
+static bool run_mangle_illegal(int dummy)
+{
+ struct cli_state *cli = NULL;
+ struct cli_state *cli_posix = NULL;
+ const char *fname = "\\MANGLE_ILLEGAL\\this_is_a_long_fname_to_be_mangled.txt";
+ const char *illegal_fname = "MANGLE_ILLEGAL/foo:bar";
+ char *mangled_path = NULL;
+ uint16_t fnum;
+ fstring name;
+ fstring alt_name;
+ NTSTATUS status;
+
+ printf("starting mangle-illegal test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ if (!torture_open_connection(&cli_posix, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli_posix->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli_posix);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ cli_rmdir(cli, "\\MANGLE_ILLEGAL");
+ status = cli_mkdir(cli, "\\MANGLE_ILLEGAL");
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mkdir1 failed : %s\n", nt_errstr(status));
+ return False;
+ }
+
+ /*
+ * Create a file with illegal NTFS characters and test that we
+ * get a usable mangled name
+ */
+
+ cli_setatr(cli_posix, illegal_fname, 0, 0);
+ cli_posix_unlink(cli_posix, illegal_fname);
+
+ status = cli_posix_open(cli_posix, illegal_fname, O_RDWR|O_CREAT|O_EXCL,
+ 0600, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("POSIX create of %s failed (%s)\n",
+ illegal_fname, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_close(cli_posix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("close failed (%s)\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_list(cli, "\\MANGLE_ILLEGAL\\*", 0, mangle_illegal_list_name_fn, &name);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_list failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ mangled_path = talloc_asprintf(talloc_tos(), "\\MANGLE_ILLEGAL\\%s", name);
+ if (mangled_path == NULL) {
+ return false;
+ }
+
+ status = cli_openx(cli, mangled_path, O_RDONLY, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_openx(%s) failed: %s\n", mangled_path, nt_errstr(status));
+ TALLOC_FREE(mangled_path);
+ return false;
+ }
+ TALLOC_FREE(mangled_path);
+ cli_close(cli, fnum);
+
+ cli_setatr(cli_posix, illegal_fname, 0, 0);
+ cli_posix_unlink(cli_posix, illegal_fname);
+
+ /*
+ * Create a file with a long name and check that we got *no* short name.
+ */
+
+ status = cli_ntcreate(cli, fname, 0, GENERIC_ALL_ACCESS|DELETE_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open %s failed: %s\n", fname, nt_errstr(status));
+ return false;
+ }
+ cli_close(cli, fnum);
+
+ status = cli_list(cli, fname, 0, mangle_illegal_list_shortname_fn, &alt_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_list failed\n");
+ return false;
+ }
+
+ cli_unlink(cli, fname, 0);
+ cli_rmdir(cli, "\\MANGLE_ILLEGAL");
+
+ if (!torture_close_connection(cli_posix)) {
+ return false;
+ }
+
+ if (!torture_close_connection(cli)) {
+ return false;
+ }
+
+ return true;
+}
+
+static size_t null_source(uint8_t *buf, size_t n, void *priv)
+{
+ size_t *to_pull = (size_t *)priv;
+ size_t thistime = *to_pull;
+
+ thistime = MIN(thistime, n);
+ if (thistime == 0) {
+ return 0;
+ }
+
+ memset(buf, 0, thistime);
+ *to_pull -= thistime;
+ return thistime;
+}
+
+static bool run_windows_write(int dummy)
+{
+ struct cli_state *cli1;
+ uint16_t fnum;
+ int i;
+ bool ret = false;
+ const char *fname = "\\writetest.txt";
+ struct timeval start_time;
+ double seconds;
+ double kbytes;
+ NTSTATUS status;
+
+ printf("starting windows_write test\n");
+ if (!torture_open_connection(&cli1, 0)) {
+ return False;
+ }
+
+ status = cli_openx(cli1, fname, O_RDWR|O_CREAT|O_EXCL, DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("open failed (%s)\n", nt_errstr(status));
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli1->conn, sockops);
+
+ start_time = timeval_current();
+
+ for (i=0; i<torture_numops; i++) {
+ uint8_t c = 0;
+ off_t start = i * torture_blocksize;
+ size_t to_pull = torture_blocksize - 1;
+
+ status = cli_writeall(cli1, fnum, 0, &c,
+ start + torture_blocksize - 1, 1, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_write failed: %s\n", nt_errstr(status));
+ goto fail;
+ }
+
+ status = cli_push(cli1, fnum, 0, i * torture_blocksize, torture_blocksize,
+ null_source, &to_pull);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_push returned: %s\n", nt_errstr(status));
+ goto fail;
+ }
+ }
+
+ seconds = timeval_elapsed(&start_time);
+ kbytes = (double)torture_blocksize * torture_numops;
+ kbytes /= 1024;
+
+ printf("Wrote %d kbytes in %.2f seconds: %d kb/sec\n", (int)kbytes,
+ (double)seconds, (int)(kbytes/seconds));
+
+ ret = true;
+ fail:
+ cli_close(cli1, fnum);
+ cli_unlink(cli1, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ torture_close_connection(cli1);
+ return ret;
+}
+
+static size_t calc_expected_return(struct cli_state *cli, size_t len_requested)
+{
+ size_t max_pdu = 0x1FFFF;
+
+ if (cli->server_posix_capabilities & CIFS_UNIX_LARGE_READ_CAP) {
+ max_pdu = 0xFFFFFF;
+ }
+
+ if (smb1cli_conn_signing_is_active(cli->conn)) {
+ max_pdu = 0x1FFFF;
+ }
+
+ if (smb1cli_conn_encryption_on(cli->conn)) {
+ max_pdu = CLI_BUFFER_SIZE;
+ }
+
+ if ((len_requested & 0xFFFF0000) == 0xFFFF0000) {
+ len_requested &= 0xFFFF;
+ }
+
+ return MIN(len_requested,
+ max_pdu - (MIN_SMB_SIZE + VWV(12) + 1 /* padding byte */));
+}
+
+static bool check_read_call(struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t *buf,
+ size_t len_requested)
+{
+ NTSTATUS status;
+ struct tevent_req *subreq = NULL;
+ ssize_t len_read = 0;
+ size_t len_expected = 0;
+ struct tevent_context *ev = NULL;
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ return false;
+ }
+
+ subreq = cli_read_andx_send(talloc_tos(),
+ ev,
+ cli,
+ fnum,
+ 0,
+ len_requested);
+
+ if (!tevent_req_poll_ntstatus(subreq, ev, &status)) {
+ return false;
+ }
+
+ status = cli_read_andx_recv(subreq, &len_read, &buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_read_andx_recv failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ TALLOC_FREE(subreq);
+ TALLOC_FREE(ev);
+
+ len_expected = calc_expected_return(cli, len_requested);
+
+ if (len_expected > 0x10000 && len_read == 0x10000) {
+ /* Windows servers only return a max of 0x10000,
+ doesn't matter if you set CAP_LARGE_READX in
+ the client sessionsetupX call or not. */
+ d_printf("Windows server - returned 0x10000 on a read of 0x%x\n",
+ (unsigned int)len_requested);
+ } else if (len_read != len_expected) {
+ d_printf("read of 0x%x failed: got 0x%x, expected 0x%x\n",
+ (unsigned int)len_requested,
+ (unsigned int)len_read,
+ (unsigned int)len_expected);
+ return false;
+ } else {
+ d_printf("Correct read reply.\n");
+ }
+
+ return true;
+}
+
+/* Test large readX variants. */
+static bool large_readx_tests(struct cli_state *cli,
+ uint16_t fnum,
+ uint8_t *buf)
+{
+ /* A read of 0xFFFF0001 should *always* return 1 byte. */
+ if (check_read_call(cli, fnum, buf, 0xFFFF0001) == false) {
+ return false;
+ }
+ /* A read of 0x10000 should return 0x10000 bytes. */
+ if (check_read_call(cli, fnum, buf, 0x10000) == false) {
+ return false;
+ }
+ /* A read of 0x10000 should return 0x10001 bytes. */
+ if (check_read_call(cli, fnum, buf, 0x10001) == false) {
+ return false;
+ }
+ /* A read of 0x1FFFF - (MIN_SMB_SIZE + VWV(12) should return
+ the requested number of bytes. */
+ if (check_read_call(cli, fnum, buf, 0x1FFFF - (MIN_SMB_SIZE + VWV(12))) == false) {
+ return false;
+ }
+ /* A read of 1MB should return 1MB bytes (on Samba). */
+ if (check_read_call(cli, fnum, buf, 0x100000) == false) {
+ return false;
+ }
+
+ if (check_read_call(cli, fnum, buf, 0x20001) == false) {
+ return false;
+ }
+ if (check_read_call(cli, fnum, buf, 0x22000001) == false) {
+ return false;
+ }
+ if (check_read_call(cli, fnum, buf, 0xFFFE0001) == false) {
+ return false;
+ }
+ return true;
+}
+
+static bool run_large_readx(int dummy)
+{
+ uint8_t *buf = NULL;
+ struct cli_state *cli1 = NULL;
+ struct cli_state *cli2 = NULL;
+ bool correct = false;
+ const char *fname = "\\large_readx.dat";
+ NTSTATUS status;
+ uint16_t fnum1 = UINT16_MAX;
+ uint32_t normal_caps = 0;
+ size_t file_size = 20*1024*1024;
+ TALLOC_CTX *frame = talloc_stackframe();
+ size_t i;
+ struct {
+ const char *name;
+ enum smb_signing_setting signing_setting;
+ enum protocol_types protocol;
+ } runs[] = {
+ {
+ .name = "NT1",
+ .signing_setting = SMB_SIGNING_IF_REQUIRED,
+ .protocol = PROTOCOL_NT1,
+ },{
+ .name = "NT1 - SIGNING_REQUIRED",
+ .signing_setting = SMB_SIGNING_REQUIRED,
+ .protocol = PROTOCOL_NT1,
+ },
+ };
+
+ printf("starting large_readx test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ goto out;
+ }
+
+ normal_caps = smb1cli_conn_capabilities(cli1->conn);
+
+ if (!(normal_caps & CAP_LARGE_READX)) {
+ d_printf("Server doesn't have CAP_LARGE_READX 0x%x\n",
+ (unsigned int)normal_caps);
+ goto out;
+ }
+
+ /* Create a file of size 4MB. */
+ status = cli_ntcreate(cli1, fname, 0, GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ 0, 0, &fnum1, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open %s failed: %s\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ /* Write file_size bytes. */
+ buf = talloc_zero_array(frame, uint8_t, file_size);
+ if (buf == NULL) {
+ goto out;
+ }
+
+ status = cli_writeall(cli1,
+ fnum1,
+ 0,
+ buf,
+ 0,
+ file_size,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_writeall failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_close failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ fnum1 = UINT16_MAX;
+
+ for (i=0; i < ARRAY_SIZE(runs); i++) {
+ enum smb_signing_setting saved_signing_setting = signing_state;
+ uint16_t fnum2 = -1;
+
+ if (do_encrypt &&
+ (runs[i].signing_setting == SMB_SIGNING_REQUIRED))
+ {
+ d_printf("skip[%u] - %s\n", (unsigned)i, runs[i].name);
+ continue;
+ }
+
+ d_printf("run[%u] - %s\n", (unsigned)i, runs[i].name);
+
+ signing_state = runs[i].signing_setting;
+ cli2 = open_nbt_connection();
+ signing_state = saved_signing_setting;
+ if (cli2 == NULL) {
+ goto out;
+ }
+
+ status = smbXcli_negprot(cli2->conn,
+ cli2->timeout,
+ runs[i].protocol,
+ runs[i].protocol,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = cli_session_setup_creds(cli2, torture_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = cli_tree_connect(cli2,
+ share,
+ "?????",
+ password);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ cli_set_timeout(cli2, 120000); /* set a really long timeout (2 minutes) */
+
+ normal_caps = smb1cli_conn_capabilities(cli2->conn);
+
+ if (!(normal_caps & CAP_LARGE_READX)) {
+ d_printf("Server doesn't have CAP_LARGE_READX 0x%x\n",
+ (unsigned int)normal_caps);
+ goto out;
+ }
+
+ if (do_encrypt) {
+ if (force_cli_encryption(cli2, share) == false) {
+ goto out;
+ }
+ } else if (SERVER_HAS_UNIX_CIFS(cli2)) {
+ uint16_t major, minor;
+ uint32_t caplow, caphigh;
+
+ status = cli_unix_extensions_version(cli2,
+ &major, &minor,
+ &caplow, &caphigh);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ status = cli_ntcreate(cli2, fname, 0, FILE_READ_DATA,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN,
+ 0, 0, &fnum2, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Second open %s failed: %s\n", fname, nt_errstr(status));
+ goto out;
+ }
+
+ /* All reads must return less than file_size bytes. */
+ if (!large_readx_tests(cli2, fnum2, buf)) {
+ goto out;
+ }
+
+ status = cli_close(cli2, fnum2);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_close failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+ fnum2 = -1;
+
+ if (!torture_close_connection(cli2)) {
+ goto out;
+ }
+ cli2 = NULL;
+ }
+
+ correct = true;
+ printf("Success on large_readx test\n");
+
+ out:
+
+ if (cli2) {
+ if (!torture_close_connection(cli2)) {
+ correct = false;
+ }
+ }
+
+ if (cli1) {
+ if (fnum1 != UINT16_MAX) {
+ status = cli_close(cli1, fnum1);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_close failed: %s\n", nt_errstr(status));
+ }
+ fnum1 = UINT16_MAX;
+ }
+
+ status = cli_unlink(cli1, fname,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("unlink failed (%s)\n", nt_errstr(status));
+ }
+
+ if (!torture_close_connection(cli1)) {
+ correct = false;
+ }
+ }
+
+ TALLOC_FREE(frame);
+
+ printf("finished large_readx test\n");
+ return correct;
+}
+
+static NTSTATUS msdfs_attribute_list_fn(struct file_info *finfo,
+ const char *mask,
+ void *private_data)
+{
+ uint32_t *p_attr = (uint32_t *)private_data;
+
+ if (strequal(finfo->name, test_filename)) {
+ *p_attr = finfo->attr;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool run_msdfs_attribute(int dummy)
+{
+ static struct cli_state *cli;
+ bool correct = false;
+ uint32_t attr = 0;
+ NTSTATUS status;
+
+ printf("Starting MSDFS-ATTRIBUTE test\n");
+
+ if (test_filename == NULL || test_filename[0] == '\0') {
+ printf("MSDFS-ATTRIBUTE test "
+ "needs -f filename-of-msdfs-link\n");
+ return false;
+ }
+
+ /*
+ * NB. We use torture_open_connection_flags() not
+ * torture_open_connection() as the latter forces
+ * SMB1.
+ */
+ if (!torture_open_connection_flags(&cli, 0, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = cli_list(cli,
+ "*",
+ FILE_ATTRIBUTE_DIRECTORY,
+ msdfs_attribute_list_fn,
+ &attr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_list failed with %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+ if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
+ printf("file %s should have "
+ "FILE_ATTRIBUTE_REPARSE_POINT set. attr = 0x%x\n",
+ test_filename,
+ (unsigned int)attr);
+ goto out;
+ }
+
+ if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
+ printf("file %s should have "
+ "FILE_ATTRIBUTE_DIRECTORY set. attr = 0x%x\n",
+ test_filename,
+ (unsigned int)attr);
+ goto out;
+ }
+
+ correct = true;
+
+ out:
+
+ torture_close_connection(cli);
+ return correct;
+}
+
+static bool run_cli_echo(int dummy)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+
+ printf("starting cli_echo test\n");
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = cli_echo(cli, 5, data_blob_const("hello", 5));
+
+ d_printf("cli_echo returned %s\n", nt_errstr(status));
+
+ torture_close_connection(cli);
+ return NT_STATUS_IS_OK(status);
+}
+
+static int splice_status(off_t written, void *priv)
+{
+ return true;
+}
+
+static bool run_cli_splice(int dummy)
+{
+ uint8_t *buf = NULL;
+ struct cli_state *cli1 = NULL;
+ bool correct = false;
+ const char *fname_src = "\\splice_src.dat";
+ const char *fname_dst = "\\splice_dst.dat";
+ NTSTATUS status;
+ uint16_t fnum1 = UINT16_MAX;
+ uint16_t fnum2 = UINT16_MAX;
+ size_t file_size = 2*1024*1024;
+ size_t splice_size = 1*1024*1024 + 713;
+ uint8_t digest1[16], digest2[16];
+ off_t written = 0;
+ size_t nread = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ printf("starting cli_splice test\n");
+
+ if (!torture_open_connection(&cli1, 0)) {
+ goto out;
+ }
+
+ cli_unlink(cli1, fname_src,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname_dst,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ /* Create a file */
+ status = cli_ntcreate(cli1, fname_src, 0, GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ 0, 0, &fnum1, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open %s failed: %s\n", fname_src, nt_errstr(status));
+ goto out;
+ }
+
+ /* Write file_size bytes - must be bigger than splice_size. */
+ buf = talloc_zero_array(frame, uint8_t, file_size);
+ if (buf == NULL) {
+ d_printf("talloc_fail\n");
+ goto out;
+ }
+
+ /* Fill it with random numbers. */
+ generate_random_buffer(buf, file_size);
+
+ /* MD5 the first 1MB + 713 bytes. */
+ gnutls_hash_fast(GNUTLS_DIG_MD5,
+ buf,
+ splice_size,
+ digest1);
+
+ status = cli_writeall(cli1,
+ fnum1,
+ 0,
+ buf,
+ 0,
+ file_size,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_writeall failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_ntcreate(cli1, fname_dst, 0, GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, 0, FILE_OVERWRITE_IF,
+ 0, 0, &fnum2, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open %s failed: %s\n", fname_dst, nt_errstr(status));
+ goto out;
+ }
+
+ /* Now splice 1MB + 713 bytes. */
+ status = cli_splice(cli1,
+ cli1,
+ fnum1,
+ fnum2,
+ splice_size,
+ 0,
+ 0,
+ &written,
+ splice_status,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_splice failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ /* Clear the old buffer. */
+ memset(buf, '\0', file_size);
+
+ /* Read the new file. */
+ status = cli_read(cli1, fnum2, (char *)buf, 0, splice_size, &nread);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("cli_read failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+ if (nread != splice_size) {
+ d_printf("bad read of 0x%x, should be 0x%x\n",
+ (unsigned int)nread,
+ (unsigned int)splice_size);
+ goto out;
+ }
+
+ /* MD5 the first 1MB + 713 bytes. */
+ gnutls_hash_fast(GNUTLS_DIG_MD5,
+ buf,
+ splice_size,
+ digest2);
+
+ /* Must be the same. */
+ if (memcmp(digest1, digest2, 16) != 0) {
+ d_printf("bad MD5 compare\n");
+ goto out;
+ }
+
+ correct = true;
+ printf("Success on cli_splice test\n");
+
+ out:
+
+ if (cli1) {
+ if (fnum1 != UINT16_MAX) {
+ cli_close(cli1, fnum1);
+ }
+ if (fnum2 != UINT16_MAX) {
+ cli_close(cli1, fnum2);
+ }
+
+ cli_unlink(cli1, fname_src,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_unlink(cli1, fname_dst,
+ FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (!torture_close_connection(cli1)) {
+ correct = false;
+ }
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+static bool run_uid_regression_test(int dummy)
+{
+ static struct cli_state *cli;
+ int16_t old_vuid;
+ int32_t old_cnum;
+ bool correct = True;
+ struct smbXcli_tcon *tcon_copy = NULL;
+ NTSTATUS status;
+
+ printf("starting uid regression test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ /* Ok - now save then logoff our current user. */
+ old_vuid = cli_state_get_uid(cli);
+
+ status = cli_ulogoff(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) cli_ulogoff failed: %s\n",
+ __location__, nt_errstr(status));
+ correct = false;
+ goto out;
+ }
+
+ cli_state_set_uid(cli, old_vuid);
+
+ /* Try an operation. */
+ status = cli_mkdir(cli, "\\uid_reg_test");
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) cli_mkdir succeeded\n",
+ __location__);
+ correct = false;
+ goto out;
+ } else {
+ /* Should be bad uid. */
+ if (!check_error(__LINE__, status, ERRSRV, ERRbaduid,
+ NT_STATUS_USER_SESSION_DELETED)) {
+ correct = false;
+ goto out;
+ }
+ }
+
+ old_cnum = cli_state_get_tid(cli);
+ /*
+ * This is an SMB1-only test.
+ * Copy the tcon, not "save/restore".
+ *
+ * In SMB1 the cli_tdis() below frees
+ * cli->smb1.tcon so we need a copy
+ * of the struct to put back for the
+ * second tdis call with invalid vuid.
+ *
+ * This is a test-only hack. Real client code
+ * uses cli_state_save_tcon_share()/cli_state_restore_tcon_share().
+ */
+ tcon_copy = smbXcli_tcon_copy(cli, cli->smb1.tcon);
+ if (tcon_copy == NULL) {
+ correct = false;
+ goto out;
+ }
+
+ /* Now try a SMBtdis with the invalid vuid set to zero. */
+ cli_state_set_uid(cli, 0);
+
+ /* This should succeed. */
+ status = cli_tdis(cli);
+
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("First tdis with invalid vuid should succeed.\n");
+ } else {
+ d_printf("First tdis failed (%s)\n", nt_errstr(status));
+ correct = false;
+ cli->smb1.tcon = tcon_copy;
+ goto out;
+ }
+
+ cli->smb1.tcon = tcon_copy;
+ cli_state_set_uid(cli, old_vuid);
+ cli_state_set_tid(cli, old_cnum);
+
+ /* This should fail. */
+ status = cli_tdis(cli);
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("Second tdis with invalid vuid should fail - succeeded instead !.\n");
+ correct = false;
+ goto out;
+ } else {
+ /* Should be bad tid. */
+ if (!check_error(__LINE__, status, ERRSRV, ERRinvnid,
+ NT_STATUS_NETWORK_NAME_DELETED)) {
+ correct = false;
+ goto out;
+ }
+ }
+
+ cli_rmdir(cli, "\\uid_reg_test");
+
+ out:
+
+ cli_shutdown(cli);
+ return correct;
+}
+
+
+static const char *illegal_chars = "*\\/?<>|\":";
+static char force_shortname_chars[] = " +,.[];=\177";
+
+static NTSTATUS shortname_del_fn(struct file_info *finfo,
+ const char *mask, void *state)
+{
+ struct cli_state *pcli = (struct cli_state *)state;
+ fstring fname;
+ NTSTATUS status = NT_STATUS_OK;
+
+ slprintf(fname, sizeof(fname), "\\shortname\\%s", finfo->name);
+
+ if (strcmp(finfo->name, ".") == 0 || strcmp(finfo->name, "..") == 0)
+ return NT_STATUS_OK;
+
+ if (finfo->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ status = cli_rmdir(pcli, fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("del_fn: failed to rmdir %s\n,", fname );
+ }
+ } else {
+ status = cli_unlink(pcli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("del_fn: failed to unlink %s\n,", fname );
+ }
+ }
+ return status;
+}
+
+struct sn_state {
+ int matched;
+ int i;
+ bool val;
+};
+
+static NTSTATUS shortname_list_fn(struct file_info *finfo,
+ const char *name, void *state)
+{
+ struct sn_state *s = (struct sn_state *)state;
+ int i = s->i;
+
+#if 0
+ printf("shortname list: i = %d, name = |%s|, shortname = |%s|\n",
+ i, finfo->name, finfo->short_name);
+#endif
+
+ if (strchr(force_shortname_chars, i)) {
+ if (!finfo->short_name) {
+ /* Shortname not created when it should be. */
+ d_printf("(%s) ERROR: Shortname was not created for file %s containing %d\n",
+ __location__, finfo->name, i);
+ s->val = true;
+ }
+ } else if (finfo->short_name){
+ /* Shortname created when it should not be. */
+ d_printf("(%s) ERROR: Shortname %s was created for file %s\n",
+ __location__, finfo->short_name, finfo->name);
+ s->val = true;
+ }
+ s->matched += 1;
+ return NT_STATUS_OK;
+}
+
+static bool run_shortname_test(int dummy)
+{
+ static struct cli_state *cli;
+ bool correct = True;
+ int i;
+ struct sn_state s;
+ char fname[40];
+ NTSTATUS status;
+
+ printf("starting shortname test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ cli_list(cli, "\\shortname\\*", 0, shortname_del_fn, cli);
+ cli_list(cli, "\\shortname\\*", FILE_ATTRIBUTE_DIRECTORY, shortname_del_fn, cli);
+ cli_rmdir(cli, "\\shortname");
+
+ status = cli_mkdir(cli, "\\shortname");
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) cli_mkdir of \\shortname failed: %s\n",
+ __location__, nt_errstr(status));
+ correct = false;
+ goto out;
+ }
+
+ if (strlcpy(fname, "\\shortname\\", sizeof(fname)) >= sizeof(fname)) {
+ correct = false;
+ goto out;
+ }
+ if (strlcat(fname, "test .txt", sizeof(fname)) >= sizeof(fname)) {
+ correct = false;
+ goto out;
+ }
+
+ s.val = false;
+
+ for (i = 32; i < 128; i++) {
+ uint16_t fnum = (uint16_t)-1;
+
+ s.i = i;
+
+ if (strchr(illegal_chars, i)) {
+ continue;
+ }
+ fname[15] = i;
+
+ status = cli_ntcreate(cli, fname, 0, GENERIC_ALL_ACCESS, FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OVERWRITE_IF, 0, 0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) cli_nt_create of %s failed: %s\n",
+ __location__, fname, nt_errstr(status));
+ correct = false;
+ goto out;
+ }
+ cli_close(cli, fnum);
+
+ s.matched = 0;
+ status = cli_list(cli, "\\shortname\\test*.*", 0,
+ shortname_list_fn, &s);
+ if (s.matched != 1) {
+ d_printf("(%s) failed to list %s: %s\n",
+ __location__, fname, nt_errstr(status));
+ correct = false;
+ goto out;
+ }
+
+ status = cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("(%s) failed to delete %s: %s\n",
+ __location__, fname, nt_errstr(status));
+ correct = false;
+ goto out;
+ }
+
+ if (s.val) {
+ correct = false;
+ goto out;
+ }
+ }
+
+ out:
+
+ cli_list(cli, "\\shortname\\*", 0, shortname_del_fn, cli);
+ cli_list(cli, "\\shortname\\*", FILE_ATTRIBUTE_DIRECTORY, shortname_del_fn, cli);
+ cli_rmdir(cli, "\\shortname");
+ torture_close_connection(cli);
+ return correct;
+}
+
+TLDAPRC callback_code;
+
+static void pagedsearch_cb(struct tevent_req *req)
+{
+ TLDAPRC rc;
+ struct tldap_message *msg;
+ char *dn;
+
+ rc = tldap_search_paged_recv(req, talloc_tos(), &msg);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ d_printf("tldap_search_paged_recv failed: %s\n",
+ tldap_rc2string(rc));
+ callback_code = rc;
+ return;
+ }
+ if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) {
+ TALLOC_FREE(msg);
+ return;
+ }
+ if (!tldap_entry_dn(msg, &dn)) {
+ d_printf("tldap_entry_dn failed\n");
+ return;
+ }
+ d_printf("%s\n", dn);
+ TALLOC_FREE(msg);
+}
+
+enum tldap_extended_val {
+ EXTENDED_ZERO = 0,
+ EXTENDED_ONE = 1,
+ EXTENDED_NONE = 2,
+};
+
+/*
+ * Construct an extended dn control with either no value, 0 or 1
+ *
+ * No value and 0 are equivalent (non-hyphenated GUID)
+ * 1 has the hyphenated GUID
+ */
+static struct tldap_control *
+tldap_build_extended_control(enum tldap_extended_val val)
+{
+ struct tldap_control empty_control;
+ struct asn1_data *data;
+
+ ZERO_STRUCT(empty_control);
+
+ if (val != EXTENDED_NONE) {
+ data = asn1_init(talloc_tos(), ASN1_MAX_TREE_DEPTH);
+
+ if (!data) {
+ return NULL;
+ }
+
+ if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) {
+ return NULL;
+ }
+
+ if (!asn1_write_Integer(data, (int)val)) {
+ return NULL;
+ }
+
+ if (!asn1_pop_tag(data)) {
+ return NULL;
+ }
+
+ if (!asn1_blob(data, &empty_control.value)) {
+ return NULL;
+ }
+ }
+
+ empty_control.oid = "1.2.840.113556.1.4.529";
+ empty_control.critical = true;
+
+ return tldap_add_control(talloc_tos(), NULL, 0, &empty_control);
+
+}
+
+static bool tldap_test_dn_guid_format(struct tldap_context *ld, const char *basedn,
+ enum tldap_extended_val control_val)
+{
+ struct tldap_control *control = tldap_build_extended_control(control_val);
+ char *dn = NULL;
+ struct tldap_message **msg;
+ TLDAPRC rc;
+
+ rc = tldap_search(ld, basedn, TLDAP_SCOPE_BASE,
+ "(objectClass=*)", NULL, 0, 0,
+ control, 1, NULL,
+ 0, 0, 0, 0, talloc_tos(), &msg);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ d_printf("tldap_search for domain DN failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+
+ if (!tldap_entry_dn(msg[0], &dn)) {
+ d_printf("tldap_search domain DN fetch failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+
+ d_printf("%s\n", dn);
+ {
+ uint32_t time_low;
+ uint32_t time_mid, time_hi_and_version;
+ uint32_t clock_seq[2];
+ uint32_t node[6];
+ char next;
+
+ switch (control_val) {
+ case EXTENDED_NONE:
+ case EXTENDED_ZERO:
+ /*
+ * When reading GUIDs with hyphens, scanf will treat
+ * hyphen as a hex character (and counts as part of the
+ * width). This creates leftover GUID string which we
+ * check will for with 'next' and closing '>'.
+ */
+ if (12 == sscanf(dn, "<GUID=%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x>%c",
+ &time_low, &time_mid,
+ &time_hi_and_version, &clock_seq[0],
+ &clock_seq[1], &node[0], &node[1],
+ &node[2], &node[3], &node[4],
+ &node[5], &next)) {
+ /* This GUID is good */
+ } else {
+ d_printf("GUID format in control (no hyphens) doesn't match output\n");
+ return false;
+ }
+
+ break;
+ case EXTENDED_ONE:
+ if (12 == sscanf(dn,
+ "<GUID=%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x>%c",
+ &time_low, &time_mid,
+ &time_hi_and_version, &clock_seq[0],
+ &clock_seq[1], &node[0], &node[1],
+ &node[2], &node[3], &node[4],
+ &node[5], &next)) {
+ /* This GUID is good */
+ } else {
+ d_printf("GUID format in control (with hyphens) doesn't match output\n");
+ return false;
+ }
+
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool run_tldap(int dummy)
+{
+ struct tldap_context *ld;
+ int fd;
+ TLDAPRC rc;
+ NTSTATUS status;
+ struct sockaddr_storage addr;
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ char *basedn;
+ const char *filter;
+
+ if (!resolve_name(host, &addr, 0, false)) {
+ d_printf("could not find host %s\n", host);
+ return false;
+ }
+ status = open_socket_out(&addr, 389, 9999, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("open_socket_out failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ ld = tldap_context_create(talloc_tos(), fd);
+ if (ld == NULL) {
+ close(fd);
+ d_printf("tldap_context_create failed\n");
+ return false;
+ }
+
+ rc = tldap_fetch_rootdse(ld);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ d_printf("tldap_fetch_rootdse failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+
+ basedn = tldap_talloc_single_attribute(
+ tldap_rootdse(ld), "defaultNamingContext", talloc_tos());
+ if (basedn == NULL) {
+ d_printf("no defaultNamingContext\n");
+ return false;
+ }
+ d_printf("defaultNamingContext: %s\n", basedn);
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_printf("tevent_context_init failed\n");
+ return false;
+ }
+
+ rc = tldap_gensec_bind(ld, torture_creds, "ldap", host, NULL,
+ loadparm_init_s3(talloc_tos(),
+ loadparm_s3_helpers()),
+ GENSEC_FEATURE_SIGN | GENSEC_FEATURE_SEAL);
+
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ d_printf("tldap_gensec_bind failed\n");
+ return false;
+ }
+
+ callback_code = TLDAP_SUCCESS;
+
+ req = tldap_search_paged_send(talloc_tos(), ev, ld, basedn,
+ TLDAP_SCOPE_SUB, "(objectclass=*)",
+ NULL, 0, 0,
+ NULL, 0, NULL, 0, 0, 0, 0, 5);
+ if (req == NULL) {
+ d_printf("tldap_search_paged_send failed\n");
+ return false;
+ }
+ tevent_req_set_callback(req, pagedsearch_cb, NULL);
+
+ tevent_req_poll(req, ev);
+
+ TALLOC_FREE(req);
+
+ rc = callback_code;
+
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ d_printf("tldap_search with paging failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+
+ /* test search filters against rootDSE */
+ filter = "(&(|(name=samba)(nextRid<=10000000)(usnChanged>=10)(samba~=ambas)(!(name=s*m*a)))"
+ "(|(name:=samba)(name:dn:2.5.13.5:=samba)(:dn:2.5.13.5:=samba)(!(name=*samba))))";
+
+ rc = tldap_search(ld, "", TLDAP_SCOPE_BASE, filter,
+ NULL, 0, 0, NULL, 0, NULL, 0, 0, 0, 0,
+ talloc_tos(), NULL);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ d_printf("tldap_search with complex filter failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+
+ /*
+ * Tests to check for regression of:
+ *
+ * https://bugzilla.samba.org/show_bug.cgi?id=14029
+ *
+ * TLDAP used here to pick apart the original string DN (with GUID)
+ */
+ if (!tldap_test_dn_guid_format(ld, basedn, EXTENDED_NONE)) {
+ d_printf("tldap_search with extended dn (no val) failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+ if (!tldap_test_dn_guid_format(ld, basedn, EXTENDED_ZERO)) {
+ d_printf("tldap_search with extended dn (0) failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+ if (!tldap_test_dn_guid_format(ld, basedn, EXTENDED_ONE)) {
+ d_printf("tldap_search with extended dn (1) failed: %s\n",
+ tldap_errstr(talloc_tos(), ld, rc));
+ return false;
+ }
+
+ TALLOC_FREE(ld);
+ return true;
+}
+
+/* Torture test to ensure no regression of :
+https://bugzilla.samba.org/show_bug.cgi?id=7084
+*/
+
+static bool run_dir_createtime(int dummy)
+{
+ struct cli_state *cli;
+ const char *dname = "\\testdir_createtime";
+ const char *fname = "\\testdir_createtime\\testfile";
+ NTSTATUS status;
+ struct timespec create_time;
+ struct timespec create_time1;
+ uint16_t fnum;
+ bool ret = false;
+ uint64_t ino;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ /* Ensure ino is zero, SMB2 gets a real one. */
+ ino = 0;
+ } else {
+ /* Ensure ino is -1, SMB1 never gets a real one. */
+ ino = (uint64_t)-1;
+ }
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, dname);
+
+ status = cli_mkdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mkdir failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_qpathinfo2(cli,
+ dname,
+ &create_time,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &ino,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_qpathinfo2 returned %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (smbXcli_conn_protocol(cli->conn) >= PROTOCOL_SMB2_02) {
+ /* SMB2 should always return an inode. */
+ if (ino == 0) {
+ printf("SMB2 bad inode (0)\n");
+ goto out;
+ }
+ } else {
+ /* SMB1 must always return zero here. */
+ if (ino != 0) {
+ printf("SMB1 bad inode (!0)\n");
+ goto out;
+ }
+ }
+
+ /* Sleep 3 seconds, then create a file. */
+ sleep(3);
+
+ status = cli_openx(cli, fname, O_RDWR | O_CREAT | O_EXCL,
+ DENY_NONE, &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_openx failed: %s\n", nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_qpathinfo2(cli,
+ dname,
+ &create_time1,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_qpathinfo2 (2) returned %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (timespec_compare(&create_time1, &create_time)) {
+ printf("run_dir_createtime: create time was updated (error)\n");
+ } else {
+ printf("run_dir_createtime: create time was not updated (correct)\n");
+ ret = true;
+ }
+
+ out:
+
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+ cli_rmdir(cli, dname);
+ if (!torture_close_connection(cli)) {
+ ret = false;
+ }
+ return ret;
+}
+
+
+static bool run_streamerror(int dummy)
+{
+ struct cli_state *cli;
+ const char *dname = "\\testdir_streamerror";
+ const char *streamname =
+ "testdir_streamerror:{4c8cc155-6c1e-11d1-8e41-00c04fb9386d}:$DATA";
+ NTSTATUS status;
+ time_t change_time, access_time, write_time;
+ off_t size;
+ uint16_t fnum;
+ uint32_t attr;
+ bool ret = true;
+
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ torture_deltree(cli, dname);
+
+ status = cli_mkdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mkdir failed: %s\n", nt_errstr(status));
+ return false;
+ }
+
+ status = cli_qpathinfo1(cli, streamname, &change_time, &access_time,
+ &write_time, &size, &attr);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("pathinfo returned %s, expected "
+ "NT_STATUS_OBJECT_NAME_NOT_FOUND\n",
+ nt_errstr(status));
+ ret = false;
+ }
+
+ status = cli_ntcreate(cli, streamname, 0x16,
+ FILE_READ_DATA|FILE_READ_EA|
+ FILE_READ_ATTRIBUTES|READ_CONTROL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,
+ FILE_OPEN, 0, 0, &fnum, NULL);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ printf("ntcreate returned %s, expected "
+ "NT_STATUS_OBJECT_NAME_NOT_FOUND\n",
+ nt_errstr(status));
+ ret = false;
+ }
+
+
+ cli_rmdir(cli, dname);
+ return ret;
+}
+
+struct pidtest_state {
+ bool success;
+ uint16_t vwv[1];
+ DATA_BLOB data;
+};
+
+static void pid_echo_done(struct tevent_req *subreq);
+
+static struct tevent_req *pid_echo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct cli_state *cli)
+{
+ struct tevent_req *req, *subreq;
+ struct pidtest_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct pidtest_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ SSVAL(state->vwv, 0, 1);
+ state->data = data_blob_const("hello", 5);
+
+ subreq = smb1cli_req_send(state,
+ ev,
+ cli->conn,
+ SMBecho,
+ 0, 0, /* *_flags */
+ 0, 0, /* *_flags2 */
+ cli->timeout,
+ 0xDEADBEEF, /* pid */
+ NULL, /* tcon */
+ NULL, /* session */
+ ARRAY_SIZE(state->vwv), state->vwv,
+ state->data.length, state->data.data);
+
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, pid_echo_done, req);
+ return req;
+}
+
+static void pid_echo_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct pidtest_state *state = tevent_req_data(
+ req, struct pidtest_state);
+ NTSTATUS status;
+ uint32_t num_bytes;
+ uint8_t *bytes = NULL;
+ struct iovec *recv_iov = NULL;
+ uint8_t *phdr = NULL;
+ uint16_t pidlow = 0;
+ uint16_t pidhigh = 0;
+ struct smb1cli_req_expected_response expected[] = {
+ {
+ .status = NT_STATUS_OK,
+ .wct = 1,
+ },
+ };
+
+ status = smb1cli_req_recv(subreq, state,
+ &recv_iov,
+ &phdr,
+ NULL, /* pwct */
+ NULL, /* pvwv */
+ NULL, /* pvwv_offset */
+ &num_bytes,
+ &bytes,
+ NULL, /* pbytes_offset */
+ NULL, /* pinbuf */
+ expected, ARRAY_SIZE(expected));
+
+ TALLOC_FREE(subreq);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ if (num_bytes != state->data.length) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ if (memcmp(bytes, state->data.data, num_bytes) != 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ /* Check pid low/high == DEADBEEF */
+ pidlow = SVAL(phdr, HDR_PID);
+ if (pidlow != 0xBEEF){
+ printf("Incorrect pidlow 0x%x, should be 0xBEEF\n",
+ (unsigned int)pidlow);
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+ pidhigh = SVAL(phdr, HDR_PIDHIGH);
+ if (pidhigh != 0xDEAD){
+ printf("Incorrect pidhigh 0x%x, should be 0xDEAD\n",
+ (unsigned int)pidhigh);
+ tevent_req_nterror(req, NT_STATUS_INVALID_NETWORK_RESPONSE);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS pid_echo_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool run_pidhigh(int dummy)
+{
+ bool success = false;
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ printf("starting pid high test\n");
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ req = pid_echo_send(frame, ev, cli);
+ if (req == NULL) {
+ goto fail;
+ }
+
+ if (!tevent_req_poll_ntstatus(req, ev, &status)) {
+ goto fail;
+ }
+
+ status = pid_echo_recv(req);
+ if (NT_STATUS_IS_OK(status)) {
+ printf("pid high test ok\n");
+ success = true;
+ }
+
+ fail:
+
+ TALLOC_FREE(frame);
+ torture_close_connection(cli);
+ return success;
+}
+
+/*
+ Test Windows open on a bad POSIX symlink.
+ */
+static bool run_symlink_open_test(int dummy)
+{
+ static struct cli_state *cli;
+ const char *fname = "non_existant_file";
+ const char *sname = "dangling_symlink";
+ uint16_t fnum = (uint16_t)-1;
+ bool correct = false;
+ NTSTATUS status;
+ TALLOC_CTX *frame = NULL;
+
+ frame = talloc_stackframe();
+
+ printf("Starting Windows bad symlink open test\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ /* Ensure nothing exists. */
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+ cli_setatr(cli, sname, 0, 0);
+ cli_posix_unlink(cli, sname);
+
+ /* Create a symlink pointing nowhere. */
+ status = cli_posix_symlink(cli, fname, sname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_symlink of %s -> %s failed (%s)\n",
+ sname,
+ fname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Now ensure that a Windows open doesn't hang. */
+ status = cli_ntcreate(cli,
+ sname,
+ 0,
+ FILE_READ_DATA|FILE_WRITE_DATA,
+ 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ FILE_OPEN_IF,
+ 0x0,
+ 0x0,
+ &fnum,
+ NULL);
+
+ /*
+ * We get either NT_STATUS_OBJECT_NAME_NOT_FOUND or
+ * NT_STATUS_OBJECT_PATH_NOT_FOUND depending on if
+ * we use O_NOFOLLOW on the server or not.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_PATH_NOT_FOUND))
+ {
+ correct = true;
+ } else {
+ printf("cli_ntcreate of %s returned %s - should return"
+ " either (%s) or (%s)\n",
+ sname,
+ nt_errstr(status),
+ nt_errstr(NT_STATUS_OBJECT_NAME_NOT_FOUND),
+ nt_errstr(NT_STATUS_OBJECT_PATH_NOT_FOUND));
+ goto out;
+ }
+
+ correct = true;
+
+ out:
+
+ if (fnum != (uint16_t)-1) {
+ cli_close(cli, fnum);
+ fnum = (uint16_t)-1;
+ }
+
+ cli_setatr(cli, sname, 0, 0);
+ cli_posix_unlink(cli, sname);
+ cli_setatr(cli, fname, 0, 0);
+ cli_posix_unlink(cli, fname);
+
+ if (!torture_close_connection(cli)) {
+ correct = false;
+ }
+
+ TALLOC_FREE(frame);
+ return correct;
+}
+
+static NTSTATUS smb1_wild_mangle_list_fn(struct file_info *finfo,
+ const char *name,
+ void *state)
+{
+ char **mangled_name_return = (char **)state;
+ bool is_mangled = strchr(finfo->name, '~');
+
+ if (is_mangled) {
+ *mangled_name_return = talloc_strdup(NULL, finfo->name);
+ if (*mangled_name_return == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+static bool run_smb1_wild_mangle_unlink_test(int dummy)
+{
+ static struct cli_state *cli_posix = NULL;
+ static struct cli_state *cli = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ bool correct = false;
+ const char *dname = "smb1_wild_mangle_unlink";
+ const char *aname = "smb1_wild_mangle_unlink/a";
+ const char *star_name = "smb1_wild_mangle_unlink/*";
+ char *windows_unlink_name = NULL;
+ char *mangled_name = NULL;
+ NTSTATUS status;
+
+ printf("Starting SMB1 wild mangle unlink test\n");
+
+ /* Open a Windows connection. */
+ if (!torture_open_connection(&cli, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ /* Open a POSIX connection. */
+ if (!torture_open_connection(&cli_posix, 0)) {
+ goto out;
+ }
+
+ smbXcli_conn_set_sockopt(cli_posix->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli_posix);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("server doesn't support POSIX\n");
+ goto out;
+ }
+
+ /* Start fresh. */
+ torture_deltree(cli, dname);
+
+ /*
+ * Create two files - 'a' and '*'.
+ * We need POSIX extensions for this as '*'
+ * is not a valid Windows name.
+ */
+
+ status = cli_mkdir(cli, dname);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_mkdir of %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_open(cli_posix,
+ aname,
+ O_RDWR|O_CREAT|O_EXCL,
+ 0660,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open (create) of %s returned %s\n",
+ aname,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_posix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = cli_posix_open(cli_posix,
+ star_name,
+ O_RDWR|O_CREAT|O_EXCL,
+ 0660,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open (create) of %s returned %s\n",
+ star_name,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_posix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = cli_list(cli,
+ star_name,
+ 0,
+ smb1_wild_mangle_list_fn,
+ &mangled_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_list of %s returned %s\n",
+ star_name,
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (mangled_name == NULL) {
+ goto out;
+ }
+
+ printf("mangled_name = %s\n",
+ mangled_name);
+
+ /*
+ * Try a Windows unlink with the mangled name.
+ * This should *NOT* unlink the 'a' name.
+ */
+
+ windows_unlink_name = talloc_asprintf(cli_posix,
+ "%s\\%s",
+ dname,
+ mangled_name);
+
+ status = cli_unlink(cli, windows_unlink_name, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_unlink of %s returned %s\n",
+ windows_unlink_name,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Does 'a' still exist ? */
+ status = cli_posix_open(cli_posix,
+ aname,
+ O_RDONLY,
+ 0,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open O_RNONLY of %s returned %s\n",
+ aname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli_posix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ correct = true;
+
+ out:
+
+ TALLOC_FREE(windows_unlink_name);
+ TALLOC_FREE(mangled_name);
+
+ if (cli != NULL) {
+ torture_deltree(cli, dname);
+ torture_close_connection(cli);
+ }
+
+ if (cli_posix != NULL) {
+ torture_close_connection(cli_posix);
+ }
+
+ return correct;
+}
+
+static bool run_smb1_wild_mangle_rename_test(int dummy)
+{
+ static struct cli_state *cli_posix = NULL;
+ static struct cli_state *cli = NULL;
+ uint16_t fnum = (uint16_t)-1;
+ bool correct = false;
+ const char *dname = "smb1_wild_mangle_rename";
+ const char *fooname = "smb1_wild_mangle_rename/foo";
+ const char *foostar_name = "smb1_wild_mangle_rename/fo*";
+ const char *wild_name = "smb1_wild_mangle_rename/*";
+ char *windows_rename_src = NULL;
+ const char *windows_rename_dst = "smb1_wild_mangle_rename\\bar";
+ char *mangled_name = NULL;
+ NTSTATUS status;
+
+ printf("Starting SMB1 wild mangle rename test\n");
+
+ if (!torture_open_connection(&cli_posix, 0)) {
+ return false;
+ }
+
+ smbXcli_conn_set_sockopt(cli_posix->conn, sockops);
+
+ status = torture_setup_unix_extensions(cli_posix);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("server doesn't support POSIX\n");
+ return false;
+ }
+
+ /* Open a Windows connection. */
+ if (!torture_open_connection(&cli, 0)) {
+ goto out;
+ }
+
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ /* Ensure we start from fresh. */
+ torture_deltree(cli, dname);
+
+ /*
+ * Create two files - 'foo' and 'fo*'.
+ * We need POSIX extensions for this as 'fo*'
+ * is not a valid Windows name.
+ */
+
+ status = cli_posix_mkdir(cli_posix, dname, 0770);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_mkdir of %s returned %s\n",
+ dname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_posix_open(cli_posix,
+ fooname,
+ O_RDWR|O_CREAT|O_EXCL,
+ 0660,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open (create) of %s returned %s\n",
+ fooname,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_posix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ status = cli_posix_open(cli_posix,
+ foostar_name,
+ O_RDWR|O_CREAT|O_EXCL,
+ 0660,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open (create) of %s returned %s\n",
+ foostar_name,
+ nt_errstr(status));
+ goto out;
+ }
+ status = cli_close(cli_posix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * Get the mangled name. We can re-use the
+ * previous smb1_wild_mangle_list_fn for this.
+ */
+
+ status = cli_list(cli,
+ wild_name,
+ 0,
+ smb1_wild_mangle_list_fn,
+ &mangled_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_list of %s returned %s\n",
+ wild_name,
+ nt_errstr(status));
+ goto out;
+ }
+
+ if (mangled_name == NULL) {
+ goto out;
+ }
+
+ printf("mangled_name = %s\n",
+ mangled_name);
+
+ /*
+ * Try a Windows rename with the mangled name.
+ * This should *NOT* rename the 'foo' name.
+ */
+
+ windows_rename_src = talloc_asprintf(cli_posix,
+ "%s\\%s",
+ dname,
+ mangled_name);
+
+ status = cli_rename(cli,
+ windows_rename_src,
+ windows_rename_dst,
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_rename of %s -> %s returned %s\n",
+ windows_rename_src,
+ windows_rename_dst,
+ nt_errstr(status));
+ goto out;
+ }
+
+ /* Does 'foo' still exist ? */
+ status = cli_posix_open(cli_posix,
+ fooname,
+ O_RDONLY,
+ 0,
+ &fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("cli_posix_open O_RNONLY of %s returned %s\n",
+ fooname,
+ nt_errstr(status));
+ goto out;
+ }
+
+ status = cli_close(cli_posix, fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ correct = true;
+
+ out:
+
+ TALLOC_FREE(mangled_name);
+ TALLOC_FREE(windows_rename_src);
+
+ if (cli != NULL) {
+ torture_deltree(cli, dname);
+ torture_close_connection(cli);
+ }
+
+ torture_close_connection(cli_posix);
+
+ return correct;
+}
+
+/*
+ * Only testing minimal time strings, as the others
+ * need (locale-dependent) guessing at what strftime does and
+ * even may differ in builds.
+ */
+static bool timesubst_test(void)
+{
+ TALLOC_CTX *ctx = NULL;
+ /* Sa 23. Dez 04:33:20 CET 2017 */
+ const struct timeval tv = { 1514000000, 123 };
+ const char* expect_minimal = "20171223_033320";
+ const char* expect_minus = "20171223_033320_000123";
+ char *s;
+ char *env_tz, *orig_tz = NULL;
+ bool result = true;
+
+ ctx = talloc_new(NULL);
+
+ env_tz = getenv("TZ");
+ if(env_tz) {
+ orig_tz = talloc_strdup(ctx, env_tz);
+ }
+ setenv("TZ", "UTC", 1);
+
+ s = minimal_timeval_string(ctx, &tv, false);
+
+ if(!s || strcmp(s, expect_minimal)) {
+ printf("minimal_timeval_string(ctx, tv, false) returned [%s], expected "
+ "[%s]\n", s ? s : "<nil>", expect_minimal);
+ result = false;
+ }
+ TALLOC_FREE(s);
+ s = minimal_timeval_string(ctx, &tv, true);
+ if(!s || strcmp(s, expect_minus)) {
+ printf("minimal_timeval_string(ctx, tv, true) returned [%s], expected "
+ "[%s]\n", s ? s : "<nil>", expect_minus);
+ result = false;
+ }
+ TALLOC_FREE(s);
+
+ if(orig_tz) {
+ setenv("TZ", orig_tz, 1);
+ }
+
+ TALLOC_FREE(ctx);
+ return result;
+}
+
+static bool run_local_substitute(int dummy)
+{
+ bool ok = true;
+
+ ok &= subst_test("%U", "bla", "", -1, -1, "bla");
+ ok &= subst_test("%u%U", "bla", "", -1, -1, "blabla");
+ ok &= subst_test("%g", "", "", -1, -1, "NO_GROUP");
+ ok &= subst_test("%G", "", "", -1, -1, "NO_GROUP");
+ ok &= subst_test("%g", "", "", -1, 0, gidtoname(0));
+ ok &= subst_test("%G", "", "", -1, 0, gidtoname(0));
+ ok &= subst_test("%D%u", "u", "dom", -1, 0, "domu");
+ ok &= subst_test("%i %I", "", "", -1, -1, "0.0.0.0 0.0.0.0");
+ ok &= subst_test("%j %J", "", "", -1, -1, "0_0_0_0 0_0_0_0");
+ /* Substitution depends on current time, so better test the underlying
+ formatting function. At least covers %t. */
+ ok &= timesubst_test();
+
+ /* Different captialization rules in sub_basic... */
+
+ ok &= (strcmp(talloc_sub_basic(talloc_tos(), "BLA", "dom", "%U%D"),
+ "blaDOM") == 0);
+
+ return ok;
+}
+
+static bool run_local_base64(int dummy)
+{
+ int i;
+ bool ret = true;
+
+ for (i=1; i<2000; i++) {
+ DATA_BLOB blob1, blob2;
+ char *b64;
+
+ blob1.data = talloc_array(talloc_tos(), uint8_t, i);
+ blob1.length = i;
+ generate_random_buffer(blob1.data, blob1.length);
+
+ b64 = base64_encode_data_blob(talloc_tos(), blob1);
+ if (b64 == NULL) {
+ d_fprintf(stderr, "base64_encode_data_blob failed "
+ "for %d bytes\n", i);
+ ret = false;
+ }
+ blob2 = base64_decode_data_blob(b64);
+ TALLOC_FREE(b64);
+
+ if (data_blob_cmp(&blob1, &blob2)) {
+ d_fprintf(stderr, "data_blob_cmp failed for %d "
+ "bytes\n", i);
+ ret = false;
+ }
+ TALLOC_FREE(blob1.data);
+ data_blob_free(&blob2);
+ }
+ return ret;
+}
+
+static void parse_fn(const struct gencache_timeout *t,
+ DATA_BLOB blob,
+ void *private_data)
+{
+ return;
+}
+
+static bool run_local_gencache(int dummy)
+{
+ char *val;
+ time_t tm;
+ DATA_BLOB blob;
+ char v;
+ struct memcache *mem;
+ int i;
+
+ mem = memcache_init(NULL, 0);
+ if (mem == NULL) {
+ d_printf("%s: memcache_init failed\n", __location__);
+ return false;
+ }
+ memcache_set_global(mem);
+
+ if (!gencache_set("foo", "bar", time(NULL) + 1000)) {
+ d_printf("%s: gencache_set() failed\n", __location__);
+ return False;
+ }
+
+ if (!gencache_get("foo", NULL, NULL, NULL)) {
+ d_printf("%s: gencache_get() failed\n", __location__);
+ return False;
+ }
+
+ for (i=0; i<1000000; i++) {
+ gencache_parse("foo", parse_fn, NULL);
+ }
+
+ if (!gencache_get("foo", talloc_tos(), &val, &tm)) {
+ d_printf("%s: gencache_get() failed\n", __location__);
+ return False;
+ }
+ TALLOC_FREE(val);
+
+ if (!gencache_get("foo", talloc_tos(), &val, &tm)) {
+ d_printf("%s: gencache_get() failed\n", __location__);
+ return False;
+ }
+
+ if (strcmp(val, "bar") != 0) {
+ d_printf("%s: gencache_get() returned %s, expected %s\n",
+ __location__, val, "bar");
+ TALLOC_FREE(val);
+ return False;
+ }
+
+ TALLOC_FREE(val);
+
+ if (!gencache_del("foo")) {
+ d_printf("%s: gencache_del() failed\n", __location__);
+ return False;
+ }
+ if (gencache_del("foo")) {
+ d_printf("%s: second gencache_del() succeeded\n",
+ __location__);
+ return False;
+ }
+
+ if (gencache_get("foo", talloc_tos(), &val, &tm)) {
+ d_printf("%s: gencache_get() on deleted entry "
+ "succeeded\n", __location__);
+ return False;
+ }
+
+ blob = data_blob_string_const_null("bar");
+ tm = time(NULL) + 60;
+
+ if (!gencache_set_data_blob("foo", blob, tm)) {
+ d_printf("%s: gencache_set_data_blob() failed\n", __location__);
+ return False;
+ }
+
+ if (!gencache_get_data_blob("foo", talloc_tos(), &blob, NULL, NULL)) {
+ d_printf("%s: gencache_get_data_blob() failed\n", __location__);
+ return False;
+ }
+
+ if (strcmp((const char *)blob.data, "bar") != 0) {
+ d_printf("%s: gencache_get_data_blob() returned %s, expected %s\n",
+ __location__, (const char *)blob.data, "bar");
+ data_blob_free(&blob);
+ return False;
+ }
+
+ data_blob_free(&blob);
+
+ if (!gencache_del("foo")) {
+ d_printf("%s: gencache_del() failed\n", __location__);
+ return False;
+ }
+ if (gencache_del("foo")) {
+ d_printf("%s: second gencache_del() succeeded\n",
+ __location__);
+ return False;
+ }
+
+ if (gencache_get_data_blob("foo", talloc_tos(), &blob, NULL, NULL)) {
+ d_printf("%s: gencache_get_data_blob() on deleted entry "
+ "succeeded\n", __location__);
+ return False;
+ }
+
+ v = 1;
+ blob.data = (uint8_t *)&v;
+ blob.length = sizeof(v);
+
+ if (!gencache_set_data_blob("blob", blob, tm)) {
+ d_printf("%s: gencache_set_data_blob() failed\n",
+ __location__);
+ return false;
+ }
+ if (gencache_get("blob", talloc_tos(), &val, &tm)) {
+ d_printf("%s: gencache_get succeeded\n", __location__);
+ return false;
+ }
+
+ return True;
+}
+
+static bool rbt_testflags(struct db_context *db, const char *key,
+ const char *value)
+{
+ bool ret = false;
+ NTSTATUS status;
+ struct db_record *rec;
+
+ rec = dbwrap_fetch_locked(db, db, string_tdb_data(key));
+ if (rec == NULL) {
+ d_fprintf(stderr, "fetch_locked failed\n");
+ goto done;
+ }
+
+ status = dbwrap_record_store(rec, string_tdb_data(value), TDB_MODIFY);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ d_fprintf(stderr, "store TDB_MODIFY unexpected status: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dbwrap_record_store(rec, string_tdb_data("overwriteme"),
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "store TDB_INSERT failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dbwrap_record_store(rec, string_tdb_data(value), TDB_INSERT);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ d_fprintf(stderr, "store TDB_INSERT unexpected status: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dbwrap_record_store(rec, string_tdb_data(value), TDB_MODIFY);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "store TDB_MODIFY failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = true;
+done:
+ TALLOC_FREE(rec);
+ return ret;
+}
+
+static bool rbt_testval(struct db_context *db, const char *key,
+ const char *value)
+{
+ struct db_record *rec;
+ TDB_DATA data = string_tdb_data(value);
+ bool ret = false;
+ NTSTATUS status;
+ TDB_DATA dbvalue;
+
+ rec = dbwrap_fetch_locked(db, db, string_tdb_data(key));
+ if (rec == NULL) {
+ d_fprintf(stderr, "fetch_locked failed\n");
+ goto done;
+ }
+ status = dbwrap_record_store(rec, data, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "store failed: %s\n", nt_errstr(status));
+ goto done;
+ }
+ TALLOC_FREE(rec);
+
+ rec = dbwrap_fetch_locked(db, db, string_tdb_data(key));
+ if (rec == NULL) {
+ d_fprintf(stderr, "second fetch_locked failed\n");
+ goto done;
+ }
+
+ dbvalue = dbwrap_record_get_value(rec);
+ if ((dbvalue.dsize != data.dsize)
+ || (memcmp(dbvalue.dptr, data.dptr, data.dsize) != 0)) {
+ d_fprintf(stderr, "Got wrong data back\n");
+ goto done;
+ }
+
+ ret = true;
+ done:
+ TALLOC_FREE(rec);
+ return ret;
+}
+
+static int local_rbtree_traverse_read(struct db_record *rec, void *private_data)
+{
+ int *count2 = (int *)private_data;
+ (*count2)++;
+ return 0;
+}
+
+static int local_rbtree_traverse_delete(struct db_record *rec, void *private_data)
+{
+ int *count2 = (int *)private_data;
+ (*count2)++;
+ dbwrap_record_delete(rec);
+ return 0;
+}
+
+static bool run_local_rbtree(int dummy)
+{
+ struct db_context *db;
+ bool ret = false;
+ int i;
+ NTSTATUS status;
+ int count = 0;
+ int count2 = 0;
+
+ db = db_open_rbt(NULL);
+
+ if (db == NULL) {
+ d_fprintf(stderr, "db_open_rbt failed\n");
+ return false;
+ }
+
+ if (!rbt_testflags(db, "firstkey", "firstval")) {
+ goto done;
+ }
+
+ for (i = 0; i < 999; i++) {
+ char key[sizeof("key-9223372036854775807")];
+ char value[sizeof("value-9223372036854775807")];
+
+ snprintf(key, sizeof(key), "key%ld", random());
+ snprintf(value, sizeof(value) ,"value%ld", random());
+
+ if (!rbt_testval(db, key, value)) {
+ goto done;
+ }
+
+ snprintf(value, sizeof(value) ,"value%ld", random());
+
+ if (!rbt_testval(db, key, value)) {
+ goto done;
+ }
+ }
+
+ ret = true;
+ count = 0; count2 = 0;
+ status = dbwrap_traverse_read(db, local_rbtree_traverse_read,
+ &count2, &count);
+ printf("%s: read1: %d %d, %s\n", __func__, count, count2, nt_errstr(status));
+ if ((count != count2) || (count != 1000)) {
+ ret = false;
+ }
+ count = 0; count2 = 0;
+ status = dbwrap_traverse(db, local_rbtree_traverse_delete,
+ &count2, &count);
+ printf("%s: delete: %d %d, %s\n", __func__, count, count2, nt_errstr(status));
+ if ((count != count2) || (count != 1000)) {
+ ret = false;
+ }
+ count = 0; count2 = 0;
+ status = dbwrap_traverse_read(db, local_rbtree_traverse_read,
+ &count2, &count);
+ printf("%s: read2: %d %d, %s\n", __func__, count, count2, nt_errstr(status));
+ if ((count != count2) || (count != 0)) {
+ ret = false;
+ }
+
+ done:
+ TALLOC_FREE(db);
+ return ret;
+}
+
+
+/*
+ local test for character set functions
+
+ This is a very simple test for the functionality in convert_string_error()
+ */
+static bool run_local_convert_string(int dummy)
+{
+ TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+ const char *test_strings[2] = { "March", "M\303\244rz" };
+ char dst[7];
+ int i;
+
+ for (i=0; i<2; i++) {
+ const char *str = test_strings[i];
+ int len = strlen(str);
+ size_t converted_size;
+ bool ret;
+
+ memset(dst, 'X', sizeof(dst));
+
+ /* first try with real source length */
+ ret = convert_string_error(CH_UNIX, CH_UTF8,
+ str, len,
+ dst, sizeof(dst),
+ &converted_size);
+ if (ret != true) {
+ d_fprintf(stderr, "Failed to convert '%s' to CH_DISPLAY\n", str);
+ goto failed;
+ }
+
+ if (converted_size != len) {
+ d_fprintf(stderr, "Converted size of '%s' should be %d - got %d\n",
+ str, len, (int)converted_size);
+ goto failed;
+ }
+
+ if (strncmp(str, dst, converted_size) != 0) {
+ d_fprintf(stderr, "Expected '%s' to match '%s'\n", str, dst);
+ goto failed;
+ }
+
+ if (strlen(str) != converted_size) {
+ d_fprintf(stderr, "Expected '%s' length %d - got %d\n", str,
+ (int)strlen(str), (int)converted_size);
+ goto failed;
+ }
+
+ if (dst[converted_size] != 'X') {
+ d_fprintf(stderr, "Expected no termination of '%s'\n", dst);
+ goto failed;
+ }
+
+ /* now with srclen==-1, this causes the nul to be
+ * converted too */
+ ret = convert_string_error(CH_UNIX, CH_UTF8,
+ str, -1,
+ dst, sizeof(dst),
+ &converted_size);
+ if (ret != true) {
+ d_fprintf(stderr, "Failed to convert '%s' to CH_DISPLAY\n", str);
+ goto failed;
+ }
+
+ if (converted_size != len+1) {
+ d_fprintf(stderr, "Converted size of '%s' should be %d - got %d\n",
+ str, len, (int)converted_size);
+ goto failed;
+ }
+
+ if (strncmp(str, dst, converted_size) != 0) {
+ d_fprintf(stderr, "Expected '%s' to match '%s'\n", str, dst);
+ goto failed;
+ }
+
+ if (len+1 != converted_size) {
+ d_fprintf(stderr, "Expected '%s' length %d - got %d\n", str,
+ len+1, (int)converted_size);
+ goto failed;
+ }
+
+ if (dst[converted_size] != 'X') {
+ d_fprintf(stderr, "Expected no termination of '%s'\n", dst);
+ goto failed;
+ }
+
+ }
+
+
+ TALLOC_FREE(tmp_ctx);
+ return true;
+failed:
+ TALLOC_FREE(tmp_ctx);
+ return false;
+}
+
+static bool run_local_string_to_sid(int dummy) {
+ struct dom_sid sid;
+
+ if (string_to_sid(&sid, "S--1-5-32-545")) {
+ printf("allowing S--1-5-32-545\n");
+ return false;
+ }
+ if (string_to_sid(&sid, "S-1-5-32-+545")) {
+ printf("allowing S-1-5-32-+545\n");
+ return false;
+ }
+ if (string_to_sid(&sid, "S-1-2-3-4-5-6-7-8-9-0-1-2-3-4-5-6-7-8-9-0")) {
+ printf("allowing S-1-2-3-4-5-6-7-8-9-0-1-2-3-4-5-6-7-8-9-0\n");
+ return false;
+ }
+ if (string_to_sid(&sid, "S-1-5-32-545-abc")) {
+ printf("allowing S-1-5-32-545-abc\n");
+ return false;
+ }
+ if (string_to_sid(&sid, "S-300-5-32-545")) {
+ printf("allowing S-300-5-32-545\n");
+ return false;
+ }
+ if (string_to_sid(&sid, "S-1-0xfffffffffffffe-32-545")) {
+ printf("allowing S-1-0xfffffffffffffe-32-545\n");
+ return false;
+ }
+ if (string_to_sid(&sid, "S-1-0xffffffffffff-5294967297-545")) {
+ printf("allowing S-1-0xffffffffffff-5294967297-545\n");
+ return false;
+ }
+ if (!string_to_sid(&sid, "S-1-0xfffffffffffe-32-545")) {
+ printf("could not parse S-1-0xfffffffffffe-32-545\n");
+ return false;
+ }
+ if (!string_to_sid(&sid, "S-1-5-32-545")) {
+ printf("could not parse S-1-5-32-545\n");
+ return false;
+ }
+ if (!dom_sid_equal(&sid, &global_sid_Builtin_Users)) {
+ struct dom_sid_buf buf;
+ printf("mis-parsed S-1-5-32-545 as %s\n",
+ dom_sid_str_buf(&sid, &buf));
+ return false;
+ }
+ return true;
+}
+
+static bool sid_to_string_test(const char *expected) {
+ char *str;
+ bool res = true;
+ struct dom_sid sid;
+
+ if (!string_to_sid(&sid, expected)) {
+ printf("could not parse %s\n", expected);
+ return false;
+ }
+
+ str = dom_sid_string(NULL, &sid);
+ if (strcmp(str, expected)) {
+ printf("Comparison failed (%s != %s)\n", str, expected);
+ res = false;
+ }
+ TALLOC_FREE(str);
+ return res;
+}
+
+static bool run_local_sid_to_string(int dummy) {
+ if (!sid_to_string_test("S-1-0xffffffffffff-1-1-1-1-1-1-1-1-1-1-1-1"))
+ return false;
+ if (!sid_to_string_test("S-1-545"))
+ return false;
+ if (!sid_to_string_test("S-255-3840-1-1-1-1"))
+ return false;
+ return true;
+}
+
+static bool run_local_binary_to_sid(int dummy) {
+ ssize_t ret;
+ struct dom_sid *sid = talloc(NULL, struct dom_sid);
+ static const uint8_t good_binary_sid[] = {
+ 0x1, /* revision number */
+ 15, /* num auths */
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, /* id_auth */
+ 0x1, 0x1, 0x1, 0x1, /* auth[0] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[1] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[2] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[3] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[4] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[5] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[6] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[7] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[8] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[9] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[10] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[11] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[12] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[13] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[14] */
+ };
+
+ static const uint8_t long_binary_sid[] = {
+ 0x1, /* revision number */
+ 15, /* num auths */
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, /* id_auth */
+ 0x1, 0x1, 0x1, 0x1, /* auth[0] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[1] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[2] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[3] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[4] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[5] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[6] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[7] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[8] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[9] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[10] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[11] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[12] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[13] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[14] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[15] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[16] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[17] */
+ };
+
+ static const uint8_t long_binary_sid2[] = {
+ 0x1, /* revision number */
+ 32, /* num auths */
+ 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, /* id_auth */
+ 0x1, 0x1, 0x1, 0x1, /* auth[0] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[1] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[2] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[3] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[4] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[5] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[6] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[7] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[8] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[9] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[10] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[11] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[12] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[13] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[14] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[15] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[16] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[17] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[18] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[19] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[20] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[21] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[22] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[23] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[24] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[25] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[26] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[27] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[28] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[29] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[30] */
+ 0x1, 0x1, 0x1, 0x1, /* auth[31] */
+ };
+
+ ret = sid_parse(good_binary_sid, sizeof(good_binary_sid), sid);
+ if (ret == -1) {
+ return false;
+ }
+ ret = sid_parse(long_binary_sid2, sizeof(long_binary_sid2), sid);
+ if (ret != -1) {
+ return false;
+ }
+ ret = sid_parse(long_binary_sid, sizeof(long_binary_sid), sid);
+ if (ret != -1) {
+ return false;
+ }
+ return true;
+}
+
+/* Split a path name into filename and stream name components. Canonicalise
+ * such that an implicit $DATA token is always explicit.
+ *
+ * The "specification" of this function can be found in the
+ * run_local_stream_name() function in torture.c, I've tried those
+ * combinations against a W2k3 server.
+ */
+
+static NTSTATUS split_ntfs_stream_name(TALLOC_CTX *mem_ctx, const char *fname,
+ char **pbase, char **pstream)
+{
+ char *base = NULL;
+ char *stream = NULL;
+ char *sname; /* stream name */
+ const char *stype; /* stream type */
+
+ DEBUG(10, ("split_ntfs_stream_name called for [%s]\n", fname));
+
+ sname = strchr_m(fname, ':');
+
+ if (sname == NULL) {
+ if (pbase != NULL) {
+ base = talloc_strdup(mem_ctx, fname);
+ NT_STATUS_HAVE_NO_MEMORY(base);
+ }
+ goto done;
+ }
+
+ if (pbase != NULL) {
+ base = talloc_strndup(mem_ctx, fname, PTR_DIFF(sname, fname));
+ NT_STATUS_HAVE_NO_MEMORY(base);
+ }
+
+ sname += 1;
+
+ stype = strchr_m(sname, ':');
+
+ if (stype == NULL) {
+ sname = talloc_strdup(mem_ctx, sname);
+ stype = "$DATA";
+ }
+ else {
+ if (strcasecmp_m(stype, ":$DATA") != 0) {
+ /*
+ * If there is an explicit stream type, so far we only
+ * allow $DATA. Is there anything else allowed? -- vl
+ */
+ DEBUG(10, ("[%s] is an invalid stream type\n", stype));
+ TALLOC_FREE(base);
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+ sname = talloc_strndup(mem_ctx, sname, PTR_DIFF(stype, sname));
+ stype += 1;
+ }
+
+ if (sname == NULL) {
+ TALLOC_FREE(base);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (sname[0] == '\0') {
+ /*
+ * no stream name, so no stream
+ */
+ goto done;
+ }
+
+ if (pstream != NULL) {
+ stream = talloc_asprintf(mem_ctx, "%s:%s", sname, stype);
+ if (stream == NULL) {
+ TALLOC_FREE(sname);
+ TALLOC_FREE(base);
+ return NT_STATUS_NO_MEMORY;
+ }
+ /*
+ * upper-case the type field
+ */
+ (void)strupper_m(strchr_m(stream, ':')+1);
+ }
+
+ done:
+ if (pbase != NULL) {
+ *pbase = base;
+ }
+ if (pstream != NULL) {
+ *pstream = stream;
+ }
+ return NT_STATUS_OK;
+}
+
+static bool test_stream_name(const char *fname, const char *expected_base,
+ const char *expected_stream,
+ NTSTATUS expected_status)
+{
+ NTSTATUS status;
+ char *base = NULL;
+ char *stream = NULL;
+
+ status = split_ntfs_stream_name(talloc_tos(), fname, &base, &stream);
+ if (!NT_STATUS_EQUAL(status, expected_status)) {
+ goto error;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return true;
+ }
+
+ if (base == NULL) goto error;
+
+ if (strcmp(expected_base, base) != 0) goto error;
+
+ if ((expected_stream != NULL) && (stream == NULL)) goto error;
+ if ((expected_stream == NULL) && (stream != NULL)) goto error;
+
+ if ((stream != NULL) && (strcmp(expected_stream, stream) != 0))
+ goto error;
+
+ TALLOC_FREE(base);
+ TALLOC_FREE(stream);
+ return true;
+
+ error:
+ d_fprintf(stderr, "Do test_stream(%s, %s, %s, %s)\n",
+ fname, expected_base ? expected_base : "<NULL>",
+ expected_stream ? expected_stream : "<NULL>",
+ nt_errstr(expected_status));
+ d_fprintf(stderr, "-> base=%s, stream=%s, status=%s\n",
+ base ? base : "<NULL>", stream ? stream : "<NULL>",
+ nt_errstr(status));
+ TALLOC_FREE(base);
+ TALLOC_FREE(stream);
+ return false;
+}
+
+static bool run_local_stream_name(int dummy)
+{
+ bool ret = true;
+
+ ret &= test_stream_name(
+ "bla", "bla", NULL, NT_STATUS_OK);
+ ret &= test_stream_name(
+ "bla::$DATA", "bla", NULL, NT_STATUS_OK);
+ ret &= test_stream_name(
+ "bla:blub:", "bla", NULL, NT_STATUS_OBJECT_NAME_INVALID);
+ ret &= test_stream_name(
+ "bla::", NULL, NULL, NT_STATUS_OBJECT_NAME_INVALID);
+ ret &= test_stream_name(
+ "bla::123", "bla", NULL, NT_STATUS_OBJECT_NAME_INVALID);
+ ret &= test_stream_name(
+ "bla:$DATA", "bla", "$DATA:$DATA", NT_STATUS_OK);
+ ret &= test_stream_name(
+ "bla:x:$DATA", "bla", "x:$DATA", NT_STATUS_OK);
+ ret &= test_stream_name(
+ "bla:x", "bla", "x:$DATA", NT_STATUS_OK);
+
+ return ret;
+}
+
+static bool data_blob_equal(DATA_BLOB a, DATA_BLOB b)
+{
+ if (a.length != b.length) {
+ printf("a.length=%d != b.length=%d\n",
+ (int)a.length, (int)b.length);
+ return false;
+ }
+ if (memcmp(a.data, b.data, a.length) != 0) {
+ printf("a.data and b.data differ\n");
+ return false;
+ }
+ return true;
+}
+
+static bool run_local_memcache(int dummy)
+{
+ struct memcache *cache;
+ DATA_BLOB k1, k2, k3, k4, k5;
+ DATA_BLOB d1, d3;
+ DATA_BLOB v1, v3;
+
+ TALLOC_CTX *mem_ctx;
+ char *ptr1 = NULL;
+ char *ptr2 = NULL;
+ char *ptr3 = NULL;
+
+ char *str1, *str2;
+ size_t size1, size2;
+ bool ret = false;
+
+ mem_ctx = talloc_init("foo");
+ if (mem_ctx == NULL) {
+ return false;
+ }
+
+ /* STAT_CACHE TESTS */
+
+ cache = memcache_init(NULL, sizeof(void *) == 8 ? 200 : 100);
+
+ if (cache == NULL) {
+ printf("memcache_init failed\n");
+ return false;
+ }
+
+ d1 = data_blob_const("d1", 2);
+ d3 = data_blob_const("d3", 2);
+
+ k1 = data_blob_const("d1", 2);
+ k2 = data_blob_const("d2", 2);
+ k3 = data_blob_const("d3", 2);
+ k4 = data_blob_const("d4", 2);
+ k5 = data_blob_const("d5", 2);
+
+ memcache_add(cache, STAT_CACHE, k1, d1);
+
+ if (!memcache_lookup(cache, STAT_CACHE, k1, &v1)) {
+ printf("could not find k1\n");
+ return false;
+ }
+ if (!data_blob_equal(d1, v1)) {
+ return false;
+ }
+
+ memcache_add(cache, STAT_CACHE, k1, d3);
+
+ if (!memcache_lookup(cache, STAT_CACHE, k1, &v3)) {
+ printf("could not find replaced k1\n");
+ return false;
+ }
+ if (!data_blob_equal(d3, v3)) {
+ return false;
+ }
+
+ TALLOC_FREE(cache);
+
+ /* GETWD_CACHE TESTS */
+ str1 = talloc_strdup(mem_ctx, "string1");
+ if (str1 == NULL) {
+ return false;
+ }
+ ptr2 = str1; /* Keep an alias for comparison. */
+
+ str2 = talloc_strdup(mem_ctx, "string2");
+ if (str2 == NULL) {
+ return false;
+ }
+
+ cache = memcache_init(NULL, sizeof(void *) == 8 ? 200 : 100);
+ if (cache == NULL) {
+ printf("memcache_init failed\n");
+ return false;
+ }
+
+ memcache_add_talloc(cache, GETWD_CACHE, k2, &str1);
+ /* str1 == NULL now. */
+ ptr1 = memcache_lookup_talloc(cache, GETWD_CACHE, k2);
+ if (ptr1 == NULL) {
+ printf("could not find k2\n");
+ return false;
+ }
+ if (ptr1 != ptr2) {
+ printf("fetch of k2 got wrong string\n");
+ return false;
+ }
+
+ /* Add a blob to ensure k2 gets purged. */
+ d3 = data_blob_talloc_zero(mem_ctx, 180);
+ memcache_add(cache, STAT_CACHE, k3, d3);
+
+ ptr2 = memcache_lookup_talloc(cache, GETWD_CACHE, k2);
+ if (ptr2 != NULL) {
+ printf("Did find k2, should have been purged\n");
+ return false;
+ }
+
+ /*
+ * Test that talloc size also is accounted in memcache and
+ * causes purge of other object.
+ */
+
+ str1 = talloc_zero_size(mem_ctx, 100);
+ str2 = talloc_zero_size(mem_ctx, 100);
+
+ memcache_add_talloc(cache, GETWD_CACHE, k4, &str1);
+ memcache_add_talloc(cache, GETWD_CACHE, k5, &str1);
+
+ ptr3 = memcache_lookup_talloc(cache, GETWD_CACHE, k4);
+ if (ptr3 != NULL) {
+ printf("Did find k4, should have been purged\n");
+ return false;
+ }
+
+ /*
+ * Test that adding a duplicate non-talloced
+ * key/value on top of a talloced key/value takes account
+ * of the talloc_freed value size.
+ */
+ TALLOC_FREE(cache);
+ TALLOC_FREE(mem_ctx);
+
+ mem_ctx = talloc_init("key_replace");
+ if (mem_ctx == NULL) {
+ return false;
+ }
+
+ cache = memcache_init(NULL, sizeof(void *) == 8 ? 200 : 100);
+ if (cache == NULL) {
+ return false;
+ }
+
+ /*
+ * Add a 100 byte talloced string. This will
+ * store a (4 or 8 byte) pointer and record the
+ * total talloced size.
+ */
+ str1 = talloc_zero_size(mem_ctx, 100);
+ memcache_add_talloc(cache, GETWD_CACHE, k4, &str1);
+ /*
+ * Now overwrite with a small talloced
+ * value. This should fit in the existing size
+ * and the total talloced size should be removed
+ * from the cache size.
+ */
+ str1 = talloc_zero_size(mem_ctx, 2);
+ memcache_add_talloc(cache, GETWD_CACHE, k4, &str1);
+ /*
+ * Now store a 20 byte string. If the
+ * total talloced size wasn't accounted for
+ * and removed in the overwrite, then this
+ * will evict k4.
+ */
+ str2 = talloc_zero_size(mem_ctx, 20);
+ memcache_add_talloc(cache, GETWD_CACHE, k5, &str2);
+
+ ptr3 = memcache_lookup_talloc(cache, GETWD_CACHE, k4);
+ if (ptr3 == NULL) {
+ printf("Did not find k4, should not have been purged\n");
+ return false;
+ }
+
+ TALLOC_FREE(cache);
+ TALLOC_FREE(mem_ctx);
+
+ mem_ctx = talloc_init("foo");
+ if (mem_ctx == NULL) {
+ return false;
+ }
+
+ cache = memcache_init(NULL, 0);
+ if (cache == NULL) {
+ return false;
+ }
+
+ str1 = talloc_strdup(mem_ctx, "string1");
+ if (str1 == NULL) {
+ return false;
+ }
+ str2 = talloc_strdup(mem_ctx, "string2");
+ if (str2 == NULL) {
+ return false;
+ }
+ memcache_add_talloc(cache, SINGLETON_CACHE_TALLOC,
+ data_blob_string_const("torture"), &str1);
+ size1 = talloc_total_size(cache);
+
+ memcache_add_talloc(cache, SINGLETON_CACHE_TALLOC,
+ data_blob_string_const("torture"), &str2);
+ size2 = talloc_total_size(cache);
+
+ printf("size1=%d, size2=%d\n", (int)size1, (int)size2);
+
+ if (size2 > size1) {
+ printf("memcache leaks memory!\n");
+ goto fail;
+ }
+
+ ret = true;
+ fail:
+ TALLOC_FREE(cache);
+ return ret;
+}
+
+static void wbclient_done(struct tevent_req *req)
+{
+ wbcErr wbc_err;
+ struct winbindd_response *wb_resp;
+ int *i = (int *)tevent_req_callback_data_void(req);
+
+ wbc_err = wb_trans_recv(req, req, &wb_resp);
+ TALLOC_FREE(req);
+ *i += 1;
+ d_printf("wb_trans_recv %d returned %s\n", *i, wbcErrorString(wbc_err));
+}
+
+static bool run_wbclient_multi_ping(int dummy)
+{
+ struct tevent_context *ev;
+ struct wb_context **wb_ctx;
+ struct winbindd_request wb_req;
+ bool result = false;
+ int i, j;
+
+ BlockSignals(True, SIGPIPE);
+
+ ev = tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ wb_ctx = talloc_array(ev, struct wb_context *, torture_nprocs);
+ if (wb_ctx == NULL) {
+ goto fail;
+ }
+
+ ZERO_STRUCT(wb_req);
+ wb_req.cmd = WINBINDD_PING;
+
+ d_printf("torture_nprocs=%d, numops=%d\n", (int)torture_nprocs, (int)torture_numops);
+
+ for (i=0; i<torture_nprocs; i++) {
+ wb_ctx[i] = wb_context_init(ev, NULL);
+ if (wb_ctx[i] == NULL) {
+ goto fail;
+ }
+ for (j=0; j<torture_numops; j++) {
+ struct tevent_req *req;
+ req = wb_trans_send(ev, ev, wb_ctx[i],
+ (j % 2) == 0, &wb_req);
+ if (req == NULL) {
+ goto fail;
+ }
+ tevent_req_set_callback(req, wbclient_done, &i);
+ }
+ }
+
+ i = 0;
+
+ while (i < torture_nprocs * torture_numops) {
+ tevent_loop_once(ev);
+ }
+
+ result = true;
+ fail:
+ TALLOC_FREE(ev);
+ return result;
+}
+
+static bool dbtrans_inc(struct db_context *db)
+{
+ struct db_record *rec;
+ uint32_t val;
+ bool ret = false;
+ NTSTATUS status;
+ TDB_DATA value;
+
+ rec = dbwrap_fetch_locked(db, db, string_term_tdb_data("transtest"));
+ if (rec == NULL) {
+ printf(__location__ "fetch_lock failed\n");
+ return false;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ if (value.dsize != sizeof(uint32_t)) {
+ printf(__location__ "value.dsize = %d\n",
+ (int)value.dsize);
+ goto fail;
+ }
+
+ memcpy(&val, value.dptr, sizeof(val));
+ val += 1;
+
+ status = dbwrap_record_store(
+ rec, make_tdb_data((uint8_t *)&val, sizeof(val)), 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf(__location__ "store failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+
+ ret = true;
+fail:
+ TALLOC_FREE(rec);
+ return ret;
+}
+
+static bool run_local_dbtrans(int dummy)
+{
+ struct db_context *db;
+ struct db_record *rec;
+ NTSTATUS status;
+ uint32_t initial;
+ int res;
+ TDB_DATA value;
+
+ db = db_open(talloc_tos(), "transtest.tdb", 0, TDB_DEFAULT,
+ O_RDWR|O_CREAT, 0600, DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ printf("Could not open transtest.db\n");
+ return false;
+ }
+
+ res = dbwrap_transaction_start(db);
+ if (res != 0) {
+ printf(__location__ "transaction_start failed\n");
+ return false;
+ }
+
+ rec = dbwrap_fetch_locked(db, db, string_term_tdb_data("transtest"));
+ if (rec == NULL) {
+ printf(__location__ "fetch_lock failed\n");
+ return false;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ if (value.dptr == NULL) {
+ initial = 0;
+ status = dbwrap_record_store(
+ rec, make_tdb_data((uint8_t *)&initial,
+ sizeof(initial)),
+ 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf(__location__ "store returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ }
+
+ TALLOC_FREE(rec);
+
+ res = dbwrap_transaction_commit(db);
+ if (res != 0) {
+ printf(__location__ "transaction_commit failed\n");
+ return false;
+ }
+
+ while (true) {
+ uint32_t val, val2;
+ int i;
+
+ res = dbwrap_transaction_start(db);
+ if (res != 0) {
+ printf(__location__ "transaction_start failed\n");
+ break;
+ }
+
+ status = dbwrap_fetch_uint32_bystring(db, "transtest", &val);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf(__location__ "dbwrap_fetch_uint32 failed: %s\n",
+ nt_errstr(status));
+ break;
+ }
+
+ for (i=0; i<10; i++) {
+ if (!dbtrans_inc(db)) {
+ return false;
+ }
+ }
+
+ status = dbwrap_fetch_uint32_bystring(db, "transtest", &val2);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf(__location__ "dbwrap_fetch_uint32 failed: %s\n",
+ nt_errstr(status));
+ break;
+ }
+
+ if (val2 != val + 10) {
+ printf(__location__ "val=%d, val2=%d\n",
+ (int)val, (int)val2);
+ break;
+ }
+
+ printf("val2=%d\r", val2);
+
+ res = dbwrap_transaction_commit(db);
+ if (res != 0) {
+ printf(__location__ "transaction_commit failed\n");
+ break;
+ }
+ }
+
+ TALLOC_FREE(db);
+ return true;
+}
+
+/*
+ * Just a dummy test to be run under a debugger. There's no real way
+ * to inspect the tevent_poll specific function from outside of
+ * tevent_poll.c.
+ */
+
+static bool run_local_tevent_poll(int dummy)
+{
+ struct tevent_context *ev;
+ struct tevent_fd *fd1, *fd2;
+ bool result = false;
+
+ ev = tevent_context_init_byname(NULL, "poll");
+ if (ev == NULL) {
+ d_fprintf(stderr, "tevent_context_init_byname failed\n");
+ goto fail;
+ }
+
+ fd1 = tevent_add_fd(ev, ev, 2, 0, NULL, NULL);
+ if (fd1 == NULL) {
+ d_fprintf(stderr, "tevent_add_fd failed\n");
+ goto fail;
+ }
+ fd2 = tevent_add_fd(ev, ev, 3, 0, NULL, NULL);
+ if (fd2 == NULL) {
+ d_fprintf(stderr, "tevent_add_fd failed\n");
+ goto fail;
+ }
+ TALLOC_FREE(fd2);
+
+ fd2 = tevent_add_fd(ev, ev, 1, 0, NULL, NULL);
+ if (fd2 == NULL) {
+ d_fprintf(stderr, "tevent_add_fd failed\n");
+ goto fail;
+ }
+
+ result = true;
+fail:
+ TALLOC_FREE(ev);
+ return result;
+}
+
+static bool run_local_hex_encode_buf(int dummy)
+{
+ char buf[17];
+ uint8_t src[8];
+ size_t i;
+
+ for (i=0; i<sizeof(src); i++) {
+ src[i] = i;
+ }
+ hex_encode_buf(buf, src, sizeof(src));
+ if (strcmp(buf, "0001020304050607") != 0) {
+ return false;
+ }
+ hex_encode_buf(buf, NULL, 0);
+ if (buf[0] != '\0') {
+ return false;
+ }
+ return true;
+}
+
+static const char *remove_duplicate_addrs2_test_strings_vector[] = {
+ "0.0.0.0",
+ "::0",
+ "1.2.3.1",
+ "0.0.0.0",
+ "0.0.0.0",
+ "1.2.3.2",
+ "1.2.3.3",
+ "1.2.3.4",
+ "1.2.3.5",
+ "::0",
+ "1.2.3.6",
+ "1.2.3.7",
+ "::0",
+ "::0",
+ "::0",
+ "1.2.3.8",
+ "1.2.3.9",
+ "1.2.3.10",
+ "1.2.3.11",
+ "1.2.3.12",
+ "1.2.3.13",
+ "1001:1111:1111:1000:0:1111:1111:1111",
+ "1.2.3.1",
+ "1.2.3.2",
+ "1.2.3.3",
+ "1.2.3.12",
+ "::0",
+ "::0"
+};
+
+static const char *remove_duplicate_addrs2_test_strings_result[] = {
+ "1.2.3.1",
+ "1.2.3.2",
+ "1.2.3.3",
+ "1.2.3.4",
+ "1.2.3.5",
+ "1.2.3.6",
+ "1.2.3.7",
+ "1.2.3.8",
+ "1.2.3.9",
+ "1.2.3.10",
+ "1.2.3.11",
+ "1.2.3.12",
+ "1.2.3.13",
+ "1001:1111:1111:1000:0:1111:1111:1111"
+};
+
+static bool run_local_remove_duplicate_addrs2(int dummy)
+{
+ struct samba_sockaddr test_vector[28];
+ size_t count, i;
+
+ /* Construct the sockaddr_storage test vector. */
+ for (i = 0; i < 28; i++) {
+ struct addrinfo hints;
+ struct addrinfo *res = NULL;
+ int ret;
+
+ memset(&hints, '\0', sizeof(hints));
+ hints.ai_flags = AI_NUMERICHOST;
+ ret = getaddrinfo(remove_duplicate_addrs2_test_strings_vector[i],
+ NULL,
+ &hints,
+ &res);
+ if (ret) {
+ fprintf(stderr, "getaddrinfo failed on [%s]\n",
+ remove_duplicate_addrs2_test_strings_vector[i]);
+ return false;
+ }
+ memset(&test_vector[i], '\0', sizeof(test_vector[i]));
+ memcpy(&test_vector[i].u.ss,
+ res->ai_addr,
+ res->ai_addrlen);
+ freeaddrinfo(res);
+ }
+
+ count = remove_duplicate_addrs2(test_vector, i);
+
+ if (count != 14) {
+ fprintf(stderr, "count wrong (%zu) should be 14\n",
+ count);
+ return false;
+ }
+
+ for (i = 0; i < count; i++) {
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), &test_vector[i].u.ss);
+
+ if (strcmp(addr, remove_duplicate_addrs2_test_strings_result[i]) != 0) {
+ fprintf(stderr, "mismatch on [%zu] [%s] [%s]\n",
+ i,
+ addr,
+ remove_duplicate_addrs2_test_strings_result[i]);
+ return false;
+ }
+ }
+
+ printf("run_local_remove_duplicate_addrs2: success\n");
+ return true;
+}
+
+static bool run_local_tdb_opener(int dummy)
+{
+ TDB_CONTEXT *t;
+ unsigned v = 0;
+
+ while (1) {
+ t = tdb_open("test.tdb", 1000, TDB_CLEAR_IF_FIRST,
+ O_RDWR|O_CREAT, 0755);
+ if (t == NULL) {
+ perror("tdb_open failed");
+ return false;
+ }
+ tdb_close(t);
+
+ v += 1;
+ printf("\r%u", v);
+ }
+ return true;
+}
+
+static bool run_local_tdb_writer(int dummy)
+{
+ TDB_CONTEXT *t;
+ unsigned v = 0;
+ TDB_DATA val;
+
+ t = tdb_open("test.tdb", 1000, 0, O_RDWR|O_CREAT, 0755);
+ if (t == 0) {
+ perror("tdb_open failed");
+ return 1;
+ }
+
+ val.dptr = (uint8_t *)&v;
+ val.dsize = sizeof(v);
+
+ while (1) {
+ TDB_DATA data;
+ int ret;
+
+ ret = tdb_store(t, val, val, 0);
+ if (ret != 0) {
+ printf("%s\n", tdb_errorstr(t));
+ }
+ v += 1;
+ printf("\r%u", v);
+
+ data = tdb_fetch(t, val);
+ if (data.dptr != NULL) {
+ SAFE_FREE(data.dptr);
+ }
+ }
+ return true;
+}
+
+static bool run_local_canonicalize_path(int dummy)
+{
+ const char *src[] = {
+ "/foo/..",
+ "/..",
+ "/foo/bar/../baz",
+ "/foo/././",
+ "/../foo",
+ ".././././",
+ ".././././../../../boo",
+ "./..",
+ "/",
+ "/../../",
+ "/foo/../",
+ "/./././",
+ "/./././.",
+ "/.../././.",
+ "/./././.foo",
+ "/./././.foo.",
+ "/./././foo.",
+ "/foo/bar/..",
+ "/foo/bar/../baz/",
+ "////////////////",
+ "/////////./././././.",
+ "/./.././../.boo/../baz",
+ "/a/component/path",
+ "/a/component/path/",
+ "/a/component/path/..",
+ "/a/component/../path/",
+ "///a/./././///component/../////path/",
+ NULL
+ };
+ const char *dst[] = {
+ "/",
+ "/",
+ "/foo/baz",
+ "/foo",
+ "/foo",
+ "/",
+ "/boo",
+ "/",
+ "/",
+ "/",
+ "/",
+ "/",
+ "/",
+ "/...",
+ "/.foo",
+ "/.foo.",
+ "/foo.",
+ "/foo",
+ "/foo/baz",
+ "/",
+ "/",
+ "/baz",
+ "/a/component/path",
+ "/a/component/path",
+ "/a/component",
+ "/a/path",
+ "/a/path",
+ NULL
+ };
+ unsigned int i;
+
+ for (i = 0; src[i] != NULL; i++) {
+ char *d = canonicalize_absolute_path(talloc_tos(), src[i]);
+ if (d == NULL) {
+ perror("talloc fail\n");
+ return false;
+ }
+ if (strcmp(d, dst[i]) != 0) {
+ d_fprintf(stderr,
+ "canonicalize mismatch %s -> %s != %s",
+ src[i], d, dst[i]);
+ return false;
+ }
+ talloc_free(d);
+ }
+ return true;
+}
+struct session_setup_nt1_truncated_state {
+ uint16_t vwv[13];
+ uint8_t bytes[20];
+};
+
+static void smb1_session_setup_nt1_truncated_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb1_session_setup_nt1_truncated_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXcli_conn *conn)
+{
+ uint16_t *vwv = NULL;
+ uint8_t *bytes = NULL;
+ const char *pass = "12345678";
+ const char *uname = "z";
+ struct session_setup_nt1_truncated_state *state = NULL;
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct session_setup_nt1_truncated_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ vwv = &state->vwv[0];
+ bytes = &state->bytes[0];
+
+ SCVAL(vwv+0, 0, 0xff);
+ SCVAL(vwv+0, 1, 0);
+ SSVAL(vwv+1, 0, 0);
+ SSVAL(vwv+2, 0, 8192);
+ SSVAL(vwv+3, 0, 2);
+ SSVAL(vwv+4, 0, 1);
+ SIVAL(vwv+5, 0, 0);
+ SSVAL(vwv+7, 0, strlen(pass)); /* OEMPasswordLen */
+ SSVAL(vwv+8, 0, 0); /* UnicodePasswordLen */
+ SSVAL(vwv+9, 0, 0); /* reserved */
+ SSVAL(vwv+10, 0, 0); /* reserved */
+ SIVAL(vwv+11, 0, CAP_STATUS32);
+
+ memcpy(bytes, pass, strlen(pass));
+ bytes += strlen(pass);
+ memcpy(bytes, uname, strlen(uname)+1);
+
+ subreq = smb1cli_req_send(state, ev, conn,
+ SMBsesssetupX,
+ 0, /* additional_flags */
+ 0, /* clear_flags */
+ 0, /* additional_flags2 */
+ 0, /* clear_flags2 */
+ 10000, /* timeout_msec */
+ getpid(),
+ NULL, /* tcon */
+ NULL, /* session */
+ 13, /* wct */
+ state->vwv,
+ strlen(pass), /* Truncate length at password. */
+ state->bytes);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb1_session_setup_nt1_truncated_done,
+ req);
+ return req;
+}
+
+static void smb1_session_setup_nt1_truncated_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct session_setup_nt1_truncated_state *state =
+ tevent_req_data(req,
+ struct session_setup_nt1_truncated_state);
+ NTSTATUS status;
+ struct smb1cli_req_expected_response expected[] = {
+ {
+ .status = NT_STATUS_OK,
+ .wct = 3,
+ },
+ };
+
+ status = smb1cli_req_recv(subreq, state,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* pvwv_offset */
+ NULL,
+ NULL,
+ NULL, /* pbytes_offset */
+ NULL,
+ expected, ARRAY_SIZE(expected));
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smb1_session_setup_nt1_truncated_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool run_smb1_truncated_sesssetup(int dummy)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ struct smbXcli_conn *conn;
+ struct sockaddr_storage ss;
+ NTSTATUS status;
+ int fd;
+ bool ok;
+
+ printf("Starting send truncated SMB1 sesssetup.\n");
+
+ ok = resolve_name(host, &ss, 0x20, true);
+ if (!ok) {
+ d_fprintf(stderr, "Could not resolve name %s\n", host);
+ return false;
+ }
+
+ status = open_socket_out(&ss, 445, 10000, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "open_socket_out failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ conn = smbXcli_conn_create(talloc_tos(), fd, host, SMB_SIGNING_OFF, 0,
+ NULL, 0, NULL);
+ if (conn == NULL) {
+ d_fprintf(stderr, "smbXcli_conn_create failed\n");
+ return false;
+ }
+
+ status = smbXcli_negprot(conn,
+ 0,
+ PROTOCOL_NT1,
+ PROTOCOL_NT1,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "smbXcli_negprot failed!\n");
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ return false;
+ }
+
+ req = smb1_session_setup_nt1_truncated_send(ev, ev, conn);
+ if (req == NULL) {
+ d_fprintf(stderr, "smb1_session_setup_nt1_truncated_send failed\n");
+ return false;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_fprintf(stderr, "tevent_req_poll failed with status %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb1_session_setup_nt1_truncated_recv(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "smb1_session_setup_nt1_truncated_recv returned "
+ "%s, expected NT_STATUS_OK\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ TALLOC_FREE(conn);
+ return true;
+}
+
+struct smb1_negotiate_exit_state {
+ int dummy;
+};
+
+static void smb1_negotiate_exit_done(struct tevent_req *subreq);
+
+static struct tevent_req *smb1_negotiate_exit_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXcli_conn *conn)
+{
+ struct smb1_negotiate_exit_state *state = NULL;
+ struct tevent_req *req = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx,
+ &state,
+ struct smb1_negotiate_exit_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = smb1cli_req_send(state, ev, conn,
+ SMBexit,
+ 0, /* additional_flags */
+ 0, /* clear_flags */
+ 0, /* additional_flags2 */
+ 0, /* clear_flags2 */
+ 10000, /* timeout_msec */
+ getpid(),
+ NULL, /* tcon */
+ NULL, /* session */
+ 0, /* wct */
+ NULL,
+ 0,
+ NULL);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ smb1_negotiate_exit_done,
+ req);
+ return req;
+}
+
+static void smb1_negotiate_exit_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct smb1_negotiate_exit_state *state =
+ tevent_req_data(req,
+ struct smb1_negotiate_exit_state);
+ NTSTATUS status;
+ struct smb1cli_req_expected_response expected[] = {
+ {
+ .status = NT_STATUS_OK,
+ .wct = 0,
+ },
+ };
+
+ status = smb1cli_req_recv(subreq, state,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* pvwv_offset */
+ NULL,
+ NULL,
+ NULL, /* pbytes_offset */
+ NULL,
+ expected, ARRAY_SIZE(expected));
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS smb1_negotiate_exit_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+static bool do_smb1_exit(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct smbXcli_conn *conn)
+{
+ struct tevent_req *req;
+ bool ok;
+ NTSTATUS status;
+ NTSTATUS expected_status = NT_STATUS_DOS(ERRSRV, ERRinvnid);;
+
+ req = smb1_negotiate_exit_send(ev, ev, conn);
+ if (req == NULL) {
+ d_fprintf(stderr, "smb1_negotiate_exit_send failed\n");
+ return false;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_fprintf(stderr, "tevent_req_poll failed with status %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ status = smb1_negotiate_exit_recv(req);
+ if (!NT_STATUS_EQUAL(status, expected_status)) {
+ d_fprintf(stderr, "smb1_negotiate_exit_recv returned "
+ "%s, expected ERRSRV, ERRinvnid\n",
+ nt_errstr(status));
+ return false;
+ }
+ return true;
+}
+
+static bool run_smb1_negotiate_exit(int dummy)
+{
+ struct tevent_context *ev;
+ struct smbXcli_conn *conn;
+ struct sockaddr_storage ss;
+ NTSTATUS status;
+ int fd;
+ bool ok;
+
+ printf("Starting send SMB1 negotiate+exit.\n");
+
+ ok = resolve_name(host, &ss, 0x20, true);
+ if (!ok) {
+ d_fprintf(stderr, "Could not resolve name %s\n", host);
+ return false;
+ }
+
+ status = open_socket_out(&ss, 445, 10000, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "open_socket_out failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ conn = smbXcli_conn_create(talloc_tos(), fd, host, SMB_SIGNING_OFF, 0,
+ NULL, 0, NULL);
+ if (conn == NULL) {
+ d_fprintf(stderr, "smbXcli_conn_create failed\n");
+ return false;
+ }
+
+ status = smbXcli_negprot(conn,
+ 0,
+ PROTOCOL_NT1,
+ PROTOCOL_NT1,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "smbXcli_negprot failed!\n");
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ return false;
+ }
+
+ /*
+ * Call do_smb1_exit twice to catch a server crash, the
+ * server sends the first return code then crashes.
+ */
+ ok = do_smb1_exit(ev, ev, conn);
+ if (!ok) {
+ d_fprintf(stderr, "do_smb1_exit (1) failed\n");
+ return false;
+ }
+ ok = do_smb1_exit(ev, ev, conn);
+ if (!ok) {
+ d_fprintf(stderr, "do_smb1_exit (2) failed\n");
+ return false;
+ }
+
+ TALLOC_FREE(conn);
+ return true;
+}
+
+static bool run_smb1_negotiate_tcon(int dummy)
+{
+ struct cli_state *cli = NULL;
+ uint16_t cnum = 0;
+ uint16_t max_xmit = 0;
+ NTSTATUS status;
+
+ printf("Starting send SMB1 negotiate+tcon.\n");
+ cli = open_nbt_connection();
+ if (cli == NULL) {
+ d_fprintf(stderr, "open_nbt_connection failed!\n");
+ return false;
+ }
+ smbXcli_conn_set_sockopt(cli->conn, sockops);
+
+ status = smbXcli_negprot(cli->conn,
+ 0,
+ PROTOCOL_NT1,
+ PROTOCOL_NT1,
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "smbXcli_negprot failed %s!\n",
+ nt_errstr(status));
+ return false;
+ }
+ status = cli_raw_tcon(cli,
+ share,
+ "",
+ "?????",
+ &max_xmit,
+ &cnum);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ d_fprintf(stderr, "cli_raw_tcon failed - got %s "
+ "(should get NT_STATUS_ACCESS_DENIED)!\n",
+ nt_errstr(status));
+ return false;
+ }
+ return true;
+}
+
+static bool run_ign_bad_negprot(int dummy)
+{
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ struct smbXcli_conn *conn;
+ struct sockaddr_storage ss;
+ NTSTATUS status;
+ int fd;
+ bool ok;
+
+ printf("starting ignore bad negprot\n");
+
+ ok = resolve_name(host, &ss, 0x20, true);
+ if (!ok) {
+ d_fprintf(stderr, "Could not resolve name %s\n", host);
+ return false;
+ }
+
+ status = open_socket_out(&ss, 445, 10000, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "open_socket_out failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ conn = smbXcli_conn_create(talloc_tos(), fd, host, SMB_SIGNING_OFF, 0,
+ NULL, 0, NULL);
+ if (conn == NULL) {
+ d_fprintf(stderr, "smbXcli_conn_create failed\n");
+ return false;
+ }
+
+ status = smbXcli_negprot(conn,
+ 0,
+ PROTOCOL_CORE,
+ PROTOCOL_CORE,
+ NULL,
+ NULL,
+ NULL);
+ if (NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "smbXcli_negprot succeeded!\n");
+ return false;
+ }
+
+ ev = samba_tevent_context_init(talloc_tos());
+ if (ev == NULL) {
+ d_fprintf(stderr, "samba_tevent_context_init failed\n");
+ return false;
+ }
+
+ req = smb1cli_session_setup_nt1_send(
+ ev, ev, conn, 0, getpid(), NULL, 65503, 2, 1, 0, "", "",
+ data_blob_null, data_blob_null, 0x40,
+ "Windows 2000 2195", "Windows 2000 5.0");
+ if (req == NULL) {
+ d_fprintf(stderr, "smb1cli_session_setup_nt1_send failed\n");
+ return false;
+ }
+
+ ok = tevent_req_poll_ntstatus(req, ev, &status);
+ if (!ok) {
+ d_fprintf(stderr, "tevent_req_poll failed\n");
+ return false;
+ }
+
+ status = smb1cli_session_setup_nt1_recv(req, NULL, NULL, NULL, NULL,
+ NULL, NULL);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_CONNECTION_RESET)) {
+ d_fprintf(stderr, "smb1cli_session_setup_nt1_recv returned "
+ "%s, expected NT_STATUS_CONNECTION_RESET\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ TALLOC_FREE(conn);
+
+ printf("starting ignore bad negprot\n");
+
+ return true;
+}
+
+
+static double create_procs(bool (*fn)(int), bool *result)
+{
+ int i, status;
+ volatile pid_t *child_status;
+ volatile bool *child_status_out;
+ int synccount;
+ int tries = 8;
+ struct timeval start;
+
+ synccount = 0;
+
+ child_status = (volatile pid_t *)anonymous_shared_allocate(sizeof(pid_t)*torture_nprocs);
+ if (!child_status) {
+ printf("Failed to setup shared memory\n");
+ return -1;
+ }
+
+ child_status_out = (volatile bool *)anonymous_shared_allocate(sizeof(bool)*torture_nprocs);
+ if (!child_status_out) {
+ printf("Failed to setup result status shared memory\n");
+ return -1;
+ }
+
+ for (i = 0; i < torture_nprocs; i++) {
+ child_status[i] = 0;
+ child_status_out[i] = True;
+ }
+
+ start = timeval_current();
+
+ for (i=0;i<torture_nprocs;i++) {
+ procnum = i;
+ if (fork() == 0) {
+ pid_t mypid = getpid();
+ sys_srandom(((int)mypid) ^ ((int)time(NULL)));
+
+ slprintf(myname,sizeof(myname),"CLIENT%d", i);
+
+ while (1) {
+ if (torture_open_connection(&current_cli, i)) break;
+ if (tries-- == 0) {
+ printf("pid %d failed to start\n", (int)getpid());
+ _exit(1);
+ }
+ smb_msleep(10);
+ }
+
+ child_status[i] = getpid();
+
+ while (child_status[i] && timeval_elapsed(&start) < 5) smb_msleep(2);
+
+ child_status_out[i] = fn(i);
+ _exit(0);
+ }
+ }
+
+ do {
+ synccount = 0;
+ for (i=0;i<torture_nprocs;i++) {
+ if (child_status[i]) synccount++;
+ }
+ if (synccount == torture_nprocs) break;
+ smb_msleep(10);
+ } while (timeval_elapsed(&start) < 30);
+
+ if (synccount != torture_nprocs) {
+ printf("FAILED TO START %d CLIENTS (started %d)\n", torture_nprocs, synccount);
+ *result = False;
+ return timeval_elapsed(&start);
+ }
+
+ /* start the client load */
+ start = timeval_current();
+
+ for (i=0;i<torture_nprocs;i++) {
+ child_status[i] = 0;
+ }
+
+ printf("%d clients started\n", torture_nprocs);
+
+ for (i=0;i<torture_nprocs;i++) {
+ while (waitpid(0, &status, 0) == -1 && errno == EINTR) /* noop */ ;
+ }
+
+ printf("\n");
+
+ for (i=0;i<torture_nprocs;i++) {
+ if (!child_status_out[i]) {
+ *result = False;
+ }
+ }
+ return timeval_elapsed(&start);
+}
+
+#define FLAG_MULTIPROC 1
+
+static struct {
+ const char *name;
+ bool (*fn)(int);
+ unsigned flags;
+} torture_ops[] = {
+ {
+ .name = "FDPASS",
+ .fn = run_fdpasstest,
+ },
+ {
+ .name = "LOCK1",
+ .fn = run_locktest1,
+ },
+ {
+ .name = "LOCK2",
+ .fn = run_locktest2,
+ },
+ {
+ .name = "LOCK3",
+ .fn = run_locktest3,
+ },
+ {
+ .name = "LOCK4",
+ .fn = run_locktest4,
+ },
+ {
+ .name = "LOCK5",
+ .fn = run_locktest5,
+ },
+ {
+ .name = "LOCK6",
+ .fn = run_locktest6,
+ },
+ {
+ .name = "LOCK7",
+ .fn = run_locktest7,
+ },
+ {
+ .name = "LOCK8",
+ .fn = run_locktest8,
+ },
+ {
+ .name = "LOCK9A",
+ .fn = run_locktest9a,
+ },
+ {
+ .name = "LOCK9B",
+ .fn = run_locktest9b,
+ },
+ {
+ .name = "LOCK10",
+ .fn = run_locktest10,
+ },
+ {
+ .name = "LOCK11",
+ .fn = run_locktest11,
+ },
+ {
+ .name = "LOCK12",
+ .fn = run_locktest12,
+ },
+ {
+ .name = "LOCK13",
+ .fn = run_locktest13,
+ },
+ {
+ .name = "UNLINK",
+ .fn = run_unlinktest,
+ },
+ {
+ .name = "BROWSE",
+ .fn = run_browsetest,
+ },
+ {
+ .name = "ATTR",
+ .fn = run_attrtest,
+ },
+ {
+ .name = "TRANS2",
+ .fn = run_trans2test,
+ },
+ {
+ .name = "MAXFID",
+ .fn = run_maxfidtest,
+ .flags = FLAG_MULTIPROC,
+ },
+ {
+ .name = "TORTURE",
+ .fn = run_torture,
+ .flags = FLAG_MULTIPROC,
+ },
+ {
+ .name = "RANDOMIPC",
+ .fn = run_randomipc,
+ },
+ {
+ .name = "NEGNOWAIT",
+ .fn = run_negprot_nowait,
+ },
+ {
+ .name = "NBENCH",
+ .fn = run_nbench,
+ },
+ {
+ .name = "NBENCH2",
+ .fn = run_nbench2,
+ },
+ {
+ .name = "OPLOCK1",
+ .fn = run_oplock1,
+ },
+ {
+ .name = "OPLOCK2",
+ .fn = run_oplock2,
+ },
+ {
+ .name = "OPLOCK4",
+ .fn = run_oplock4,
+ },
+#ifdef HAVE_KERNEL_OPLOCKS_LINUX
+ {
+ .name = "OPLOCK5",
+ .fn = run_oplock5,
+ },
+#endif
+ {
+ .name = "DIR",
+ .fn = run_dirtest,
+ },
+ {
+ .name = "DIR1",
+ .fn = run_dirtest1,
+ },
+ {
+ .name = "DIR-CREATETIME",
+ .fn = run_dir_createtime,
+ },
+ {
+ .name = "DENY1",
+ .fn = torture_denytest1,
+ },
+ {
+ .name = "DENY2",
+ .fn = torture_denytest2,
+ },
+ {
+ .name = "TCON",
+ .fn = run_tcon_test,
+ },
+ {
+ .name = "TCONDEV",
+ .fn = run_tcon_devtype_test,
+ },
+ {
+ .name = "RW1",
+ .fn = run_readwritetest,
+ },
+ {
+ .name = "RW2",
+ .fn = run_readwritemulti,
+ .flags = FLAG_MULTIPROC
+ },
+ {
+ .name = "RW3",
+ .fn = run_readwritelarge,
+ },
+ {
+ .name = "RW-SIGNING",
+ .fn = run_readwritelarge_signtest,
+ },
+ {
+ .name = "OPEN",
+ .fn = run_opentest,
+ },
+ {
+ .name = "POSIX",
+ .fn = run_simple_posix_open_test,
+ },
+ {
+ .name = "POSIX-APPEND",
+ .fn = run_posix_append,
+ },
+ {
+ .name = "POSIX-SYMLINK-ACL",
+ .fn = run_acl_symlink_test,
+ },
+ {
+ .name = "POSIX-SYMLINK-EA",
+ .fn = run_ea_symlink_test,
+ },
+ {
+ .name = "POSIX-STREAM-DELETE",
+ .fn = run_posix_stream_delete,
+ },
+ {
+ .name = "POSIX-OFD-LOCK",
+ .fn = run_posix_ofd_lock_test,
+ },
+ {
+ .name = "POSIX-BLOCKING-LOCK",
+ .fn = run_posix_blocking_lock,
+ },
+ {
+ .name = "POSIX-MKDIR",
+ .fn = run_posix_mkdir_test,
+ },
+ {
+ .name = "POSIX-ACL-OPLOCK",
+ .fn = run_posix_acl_oplock_test,
+ },
+ {
+ .name = "POSIX-ACL-SHAREROOT",
+ .fn = run_posix_acl_shareroot_test,
+ },
+ {
+ .name = "POSIX-LS-WILDCARD",
+ .fn = run_posix_ls_wildcard_test,
+ },
+ {
+ .name = "POSIX-LS-SINGLE",
+ .fn = run_posix_ls_single_test,
+ },
+ {
+ .name = "POSIX-READLINK",
+ .fn = run_posix_readlink_test,
+ },
+ {
+ .name = "POSIX-STAT",
+ .fn = run_posix_stat_test,
+ },
+ {
+ .name = "POSIX-SYMLINK-PARENT",
+ .fn = run_posix_symlink_parent_test,
+ },
+ {
+ .name = "POSIX-SYMLINK-CHMOD",
+ .fn = run_posix_symlink_chmod_test,
+ },
+ {
+ .name = "POSIX-SYMLINK-RENAME",
+ .fn = run_posix_symlink_rename_test,
+ },
+ {
+ .name = "POSIX-DIR-DEFAULT-ACL",
+ .fn = run_posix_dir_default_acl_test,
+ },
+ {
+ .name = "POSIX-SYMLINK-GETPATHINFO",
+ .fn = run_posix_symlink_getpathinfo_test,
+ },
+ {
+ .name = "POSIX-SYMLINK-SETPATHINFO",
+ .fn = run_posix_symlink_setpathinfo_test,
+ },
+ {
+ .name = "WINDOWS-BAD-SYMLINK",
+ .fn = run_symlink_open_test,
+ },
+ {
+ .name = "SMB1-WILD-MANGLE-UNLINK",
+ .fn = run_smb1_wild_mangle_unlink_test,
+ },
+ {
+ .name = "SMB1-WILD-MANGLE-RENAME",
+ .fn = run_smb1_wild_mangle_rename_test,
+ },
+ {
+ .name = "CASE-INSENSITIVE-CREATE",
+ .fn = run_case_insensitive_create,
+ },
+ {
+ .name = "ASYNC-ECHO",
+ .fn = run_async_echo,
+ },
+ {
+ .name = "UID-REGRESSION-TEST",
+ .fn = run_uid_regression_test,
+ },
+ {
+ .name = "SHORTNAME-TEST",
+ .fn = run_shortname_test,
+ },
+ {
+ .name = "ADDRCHANGE",
+ .fn = run_addrchange,
+ },
+#if 1
+ {
+ .name = "OPENATTR",
+ .fn = run_openattrtest,
+ },
+#endif
+ {
+ .name = "XCOPY",
+ .fn = run_xcopy,
+ },
+ {
+ .name = "RENAME",
+ .fn = run_rename,
+ },
+ {
+ .name = "RENAME-ACCESS",
+ .fn = run_rename_access,
+ },
+ {
+ .name = "OWNER-RIGHTS",
+ .fn = run_owner_rights,
+ },
+ {
+ .name = "DELETE",
+ .fn = run_deletetest,
+ },
+ {
+ .name = "DELETE-STREAM",
+ .fn = run_delete_stream,
+ },
+ {
+ .name = "DELETE-PRINT",
+ .fn = run_delete_print_test,
+ },
+ {
+ .name = "DELETE-LN",
+ .fn = run_deletetest_ln,
+ },
+ {
+ .name = "PROPERTIES",
+ .fn = run_properties,
+ },
+ {
+ .name = "MANGLE",
+ .fn = torture_mangle,
+ },
+ {
+ .name = "MANGLE1",
+ .fn = run_mangle1,
+ },
+ {
+ .name = "MANGLE-ILLEGAL",
+ .fn = run_mangle_illegal,
+ },
+ {
+ .name = "W2K",
+ .fn = run_w2ktest,
+ },
+ {
+ .name = "TRANS2SCAN",
+ .fn = torture_trans2_scan,
+ },
+ {
+ .name = "NTTRANSSCAN",
+ .fn = torture_nttrans_scan,
+ },
+ {
+ .name = "UTABLE",
+ .fn = torture_utable,
+ },
+ {
+ .name = "CASETABLE",
+ .fn = torture_casetable,
+ },
+ {
+ .name = "ERRMAPEXTRACT",
+ .fn = run_error_map_extract,
+ },
+ {
+ .name = "PIPE_NUMBER",
+ .fn = run_pipe_number,
+ },
+ {
+ .name = "TCON2",
+ .fn = run_tcon2_test,
+ },
+ {
+ .name = "IOCTL",
+ .fn = torture_ioctl_test,
+ },
+ {
+ .name = "CHKPATH",
+ .fn = torture_chkpath_test,
+ },
+ {
+ .name = "FDSESS",
+ .fn = run_fdsesstest,
+ },
+ {
+ .name = "EATEST",
+ .fn = run_eatest,
+ },
+ {
+ .name = "SESSSETUP_BENCH",
+ .fn = run_sesssetup_bench,
+ },
+ {
+ .name = "CHAIN1",
+ .fn = run_chain1,
+ },
+ {
+ .name = "CHAIN2",
+ .fn = run_chain2,
+ },
+ {
+ .name = "CHAIN3",
+ .fn = run_chain3,
+ },
+ {
+ .name = "WINDOWS-WRITE",
+ .fn = run_windows_write,
+ },
+ {
+ .name = "LARGE_READX",
+ .fn = run_large_readx,
+ },
+ {
+ .name = "MSDFS-ATTRIBUTE",
+ .fn = run_msdfs_attribute,
+ },
+ {
+ .name = "NTTRANS-CREATE",
+ .fn = run_nttrans_create,
+ },
+ {
+ .name = "NTTRANS-FSCTL",
+ .fn = run_nttrans_fsctl,
+ },
+ {
+ .name = "CLI_ECHO",
+ .fn = run_cli_echo,
+ },
+ {
+ .name = "CLI_SPLICE",
+ .fn = run_cli_splice,
+ },
+ {
+ .name = "TLDAP",
+ .fn = run_tldap,
+ },
+ {
+ .name = "STREAMERROR",
+ .fn = run_streamerror,
+ },
+ {
+ .name = "NOTIFY-BENCH",
+ .fn = run_notify_bench,
+ },
+ {
+ .name = "NOTIFY-BENCH2",
+ .fn = run_notify_bench2,
+ },
+ {
+ .name = "NOTIFY-BENCH3",
+ .fn = run_notify_bench3,
+ },
+ {
+ .name = "BAD-NBT-SESSION",
+ .fn = run_bad_nbt_session,
+ },
+ {
+ .name = "IGN-BAD-NEGPROT",
+ .fn = run_ign_bad_negprot,
+ },
+ {
+ .name = "SMB-ANY-CONNECT",
+ .fn = run_smb_any_connect,
+ },
+ {
+ .name = "NOTIFY-ONLINE",
+ .fn = run_notify_online,
+ },
+ {
+ .name = "SMB2-BASIC",
+ .fn = run_smb2_basic,
+ },
+ {
+ .name = "SMB2-NEGPROT",
+ .fn = run_smb2_negprot,
+ },
+ {
+ .name = "SMB2-ANONYMOUS",
+ .fn = run_smb2_anonymous,
+ },
+ {
+ .name = "SMB2-SESSION-RECONNECT",
+ .fn = run_smb2_session_reconnect,
+ },
+ {
+ .name = "SMB2-TCON-DEPENDENCE",
+ .fn = run_smb2_tcon_dependence,
+ },
+ {
+ .name = "SMB2-MULTI-CHANNEL",
+ .fn = run_smb2_multi_channel,
+ },
+ {
+ .name = "SMB2-SESSION-REAUTH",
+ .fn = run_smb2_session_reauth,
+ },
+ {
+ .name = "SMB2-FTRUNCATE",
+ .fn = run_smb2_ftruncate,
+ },
+ {
+ .name = "SMB2-DIR-FSYNC",
+ .fn = run_smb2_dir_fsync,
+ },
+ {
+ .name = "SMB2-PATH-SLASH",
+ .fn = run_smb2_path_slash,
+ },
+ {
+ .name = "SMB1-SYSTEM-SECURITY",
+ .fn = run_smb1_system_security,
+ },
+ {
+ .name = "SMB2-SACL",
+ .fn = run_smb2_sacl,
+ },
+ {
+ .name = "SMB2-QUOTA1",
+ .fn = run_smb2_quota1,
+ },
+ {
+ .name = "SMB2-INVALID-PIPENAME",
+ .fn = run_smb2_invalid_pipename,
+ },
+ {
+ .name = "SMB2-STREAM-ACL",
+ .fn = run_smb2_stream_acl,
+ },
+ {
+ .name = "SMB2-LIST-DIR-ASYNC",
+ .fn = run_list_dir_async_test,
+ },
+ {
+ .name = "SMB2-DEL-ON-CLOSE-NONEMPTY",
+ .fn = run_delete_on_close_non_empty,
+ },
+ {
+ .name = "SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-YES",
+ .fn = run_delete_on_close_nonwrite_delete_yes_test,
+ },
+ {
+ .name = "SMB2-DEL-ON-CLOSE-NONWRITE-DELETE-NO",
+ .fn = run_delete_on_close_nonwrite_delete_no_test,
+ },
+ {
+ .name = "SMB2-DFS-PATHS",
+ .fn = run_smb2_dfs_paths,
+ },
+ {
+ .name = "SMB2-NON-DFS-SHARE",
+ .fn = run_smb2_non_dfs_share,
+ },
+ {
+ .name = "SMB2-DFS-SHARE-NON-DFS-PATH",
+ .fn = run_smb2_dfs_share_non_dfs_path,
+ },
+ {
+ .name = "SMB2-DFS-FILENAME-LEADING-BACKSLASH",
+ .fn = run_smb2_dfs_filename_leading_backslash,
+ },
+ {
+ .name = "SMB2-PIPE-READ-ASYNC-DISCONNECT",
+ .fn = run_smb2_pipe_read_async_disconnect,
+ },
+ {
+ .name = "SMB1-TRUNCATED-SESSSETUP",
+ .fn = run_smb1_truncated_sesssetup,
+ },
+ {
+ .name = "SMB1-NEGOTIATE-EXIT",
+ .fn = run_smb1_negotiate_exit,
+ },
+ {
+ .name = "SMB1-NEGOTIATE-TCON",
+ .fn = run_smb1_negotiate_tcon,
+ },
+ {
+ .name = "SMB1-DFS-PATHS",
+ .fn = run_smb1_dfs_paths,
+ },
+ {
+ .name = "SMB1-DFS-SEARCH-PATHS",
+ .fn = run_smb1_dfs_search_paths,
+ },
+ {
+ .name = "SMB1-DFS-OPERATIONS",
+ .fn = run_smb1_dfs_operations,
+ },
+ {
+ .name = "SMB1-DFS-BADPATH",
+ .fn = run_smb1_dfs_check_badpath,
+ },
+ {
+ .name = "CLEANUP1",
+ .fn = run_cleanup1,
+ },
+ {
+ .name = "CLEANUP2",
+ .fn = run_cleanup2,
+ },
+ {
+ .name = "CLEANUP4",
+ .fn = run_cleanup4,
+ },
+ {
+ .name = "OPLOCK-CANCEL",
+ .fn = run_oplock_cancel,
+ },
+ {
+ .name = "PIDHIGH",
+ .fn = run_pidhigh,
+ },
+ {
+ .name = "LOCAL-SUBSTITUTE",
+ .fn = run_local_substitute,
+ },
+ {
+ .name = "LOCAL-GENCACHE",
+ .fn = run_local_gencache,
+ },
+ {
+ .name = "LOCAL-DBWRAP-WATCH1",
+ .fn = run_dbwrap_watch1,
+ },
+ {
+ .name = "LOCAL-DBWRAP-WATCH2",
+ .fn = run_dbwrap_watch2,
+ },
+ {
+ .name = "LOCAL-DBWRAP-WATCH3",
+ .fn = run_dbwrap_watch3,
+ },
+ {
+ .name = "LOCAL-DBWRAP-WATCH4",
+ .fn = run_dbwrap_watch4,
+ },
+ {
+ .name = "LOCAL-DBWRAP-DO-LOCKED1",
+ .fn = run_dbwrap_do_locked1,
+ },
+ {
+ .name = "LOCAL-MESSAGING-READ1",
+ .fn = run_messaging_read1,
+ },
+ {
+ .name = "LOCAL-MESSAGING-READ2",
+ .fn = run_messaging_read2,
+ },
+ {
+ .name = "LOCAL-MESSAGING-READ3",
+ .fn = run_messaging_read3,
+ },
+ {
+ .name = "LOCAL-MESSAGING-READ4",
+ .fn = run_messaging_read4,
+ },
+ {
+ .name = "LOCAL-MESSAGING-FDPASS1",
+ .fn = run_messaging_fdpass1,
+ },
+ {
+ .name = "LOCAL-MESSAGING-FDPASS2",
+ .fn = run_messaging_fdpass2,
+ },
+ {
+ .name = "LOCAL-MESSAGING-FDPASS2a",
+ .fn = run_messaging_fdpass2a,
+ },
+ {
+ .name = "LOCAL-MESSAGING-FDPASS2b",
+ .fn = run_messaging_fdpass2b,
+ },
+ {
+ .name = "LOCAL-MESSAGING-SEND-ALL",
+ .fn = run_messaging_send_all,
+ },
+ {
+ .name = "LOCAL-BASE64",
+ .fn = run_local_base64,
+ },
+ {
+ .name = "LOCAL-RBTREE",
+ .fn = run_local_rbtree,
+ },
+ {
+ .name = "LOCAL-MEMCACHE",
+ .fn = run_local_memcache,
+ },
+ {
+ .name = "LOCAL-STREAM-NAME",
+ .fn = run_local_stream_name,
+ },
+ {
+ .name = "LOCAL-STR-MATCH-MSWILD",
+ .fn = run_str_match_mswild,
+ },
+ {
+ .name = "LOCAL-STR-MATCH-REGEX-SUB1",
+ .fn = run_str_match_regex_sub1,
+ },
+ {
+ .name = "WBCLIENT-MULTI-PING",
+ .fn = run_wbclient_multi_ping,
+ },
+ {
+ .name = "LOCAL-string_to_sid",
+ .fn = run_local_string_to_sid,
+ },
+ {
+ .name = "LOCAL-sid_to_string",
+ .fn = run_local_sid_to_string,
+ },
+ {
+ .name = "LOCAL-binary_to_sid",
+ .fn = run_local_binary_to_sid,
+ },
+ {
+ .name = "LOCAL-DBTRANS",
+ .fn = run_local_dbtrans,
+ },
+ {
+ .name = "LOCAL-TEVENT-POLL",
+ .fn = run_local_tevent_poll,
+ },
+ {
+ .name = "LOCAL-CONVERT-STRING",
+ .fn = run_local_convert_string,
+ },
+ {
+ .name = "LOCAL-CONV-AUTH-INFO",
+ .fn = run_local_conv_auth_info,
+ },
+ {
+ .name = "LOCAL-hex_encode_buf",
+ .fn = run_local_hex_encode_buf,
+ },
+ {
+ .name = "LOCAL-IDMAP-TDB-COMMON",
+ .fn = run_idmap_tdb_common_test,
+ },
+ {
+ .name = "LOCAL-remove_duplicate_addrs2",
+ .fn = run_local_remove_duplicate_addrs2,
+ },
+ {
+ .name = "local-tdb-opener",
+ .fn = run_local_tdb_opener,
+ },
+ {
+ .name = "local-tdb-writer",
+ .fn = run_local_tdb_writer,
+ },
+ {
+ .name = "LOCAL-DBWRAP-CTDB1",
+ .fn = run_local_dbwrap_ctdb1,
+ },
+ {
+ .name = "LOCAL-BENCH-PTHREADPOOL",
+ .fn = run_bench_pthreadpool,
+ },
+ {
+ .name = "LOCAL-PTHREADPOOL-TEVENT",
+ .fn = run_pthreadpool_tevent,
+ },
+ {
+ .name = "LOCAL-G-LOCK1",
+ .fn = run_g_lock1,
+ },
+ {
+ .name = "LOCAL-G-LOCK2",
+ .fn = run_g_lock2,
+ },
+ {
+ .name = "LOCAL-G-LOCK3",
+ .fn = run_g_lock3,
+ },
+ {
+ .name = "LOCAL-G-LOCK4",
+ .fn = run_g_lock4,
+ },
+ {
+ .name = "LOCAL-G-LOCK4A",
+ .fn = run_g_lock4a,
+ },
+ {
+ .name = "LOCAL-G-LOCK5",
+ .fn = run_g_lock5,
+ },
+ {
+ .name = "LOCAL-G-LOCK6",
+ .fn = run_g_lock6,
+ },
+ {
+ .name = "LOCAL-G-LOCK7",
+ .fn = run_g_lock7,
+ },
+ {
+ .name = "LOCAL-G-LOCK8",
+ .fn = run_g_lock8,
+ },
+ {
+ .name = "LOCAL-G-LOCK-PING-PONG",
+ .fn = run_g_lock_ping_pong,
+ },
+ {
+ .name = "LOCAL-CANONICALIZE-PATH",
+ .fn = run_local_canonicalize_path,
+ },
+ {
+ .name = "LOCAL-NAMEMAP-CACHE1",
+ .fn = run_local_namemap_cache1,
+ },
+ {
+ .name = "LOCAL-IDMAP-CACHE1",
+ .fn = run_local_idmap_cache1,
+ },
+ {
+ .name = "qpathinfo-bufsize",
+ .fn = run_qpathinfo_bufsize,
+ },
+ {
+ .name = "hide-new-files-timeout",
+ .fn = run_hidenewfiles,
+ },
+ {
+ .name = "hide-new-files-timeout-showdirs",
+ .fn = run_hidenewfiles_showdirs,
+ },
+#ifdef CLUSTER_SUPPORT
+ {
+ .name = "ctdbd-conn1",
+ .fn = run_ctdbd_conn1,
+ },
+#endif
+ {
+ .name = "readdir-timestamp",
+ .fn = run_readdir_timestamp,
+ },
+ {
+ .name = "rpc-scale",
+ .fn = run_rpc_scale,
+ },
+ {
+ .name = "LOCAL-TDB-VALIDATE",
+ .fn = run_tdb_validate,
+ },
+ {
+ .name = NULL,
+ },
+};
+
+/****************************************************************************
+run a specified test or "ALL"
+****************************************************************************/
+static bool run_test(const char *name)
+{
+ bool ret = True;
+ bool result = True;
+ bool found = False;
+ int i;
+ double t;
+ if (strequal(name,"ALL")) {
+ for (i=0;torture_ops[i].name;i++) {
+ run_test(torture_ops[i].name);
+ }
+ found = True;
+ }
+
+ for (i=0;torture_ops[i].name;i++) {
+ fstr_sprintf(randomfname, "\\XX%x",
+ (unsigned)random());
+
+ if (strequal(name, torture_ops[i].name)) {
+ found = True;
+ printf("Running %s\n", name);
+ if (torture_ops[i].flags & FLAG_MULTIPROC) {
+ t = create_procs(torture_ops[i].fn, &result);
+ if (!result) {
+ ret = False;
+ printf("TEST %s FAILED!\n", name);
+ }
+ } else {
+ struct timeval start;
+ start = timeval_current();
+ if (!torture_ops[i].fn(0)) {
+ ret = False;
+ printf("TEST %s FAILED!\n", name);
+ }
+ t = timeval_elapsed(&start);
+ }
+ printf("%s took %g secs\n\n", name, t);
+ }
+ }
+
+ if (!found) {
+ printf("Did not find a test named %s\n", name);
+ ret = False;
+ }
+
+ return ret;
+}
+
+
+static void usage(void)
+{
+ int i;
+
+ printf("WARNING samba4 test suite is much more complete nowadays.\n");
+ printf("Please use samba4 torture.\n\n");
+
+ printf("Usage: smbtorture //server/share <options> TEST1 TEST2 ...\n");
+
+ printf("\t-d debuglevel\n");
+ printf("\t-U user%%pass\n");
+ printf("\t-k use kerberos\n");
+ printf("\t-N numprocs\n");
+ printf("\t-n my_netbios_name\n");
+ printf("\t-W workgroup\n");
+ printf("\t-o num_operations\n");
+ printf("\t-O socket_options\n");
+ printf("\t-m maximum protocol\n");
+ printf("\t-L use oplocks\n");
+ printf("\t-c CLIENT.TXT specify client load file for NBENCH\n");
+ printf("\t-A showall\n");
+ printf("\t-p port\n");
+ printf("\t-s seed\n");
+ printf("\t-b unclist_filename specify multiple shares for multiple connections\n");
+ printf("\t-f filename filename to test\n");
+ printf("\t-e encrypt\n");
+ printf("\n\n");
+
+ printf("tests are:");
+ for (i=0;torture_ops[i].name;i++) {
+ printf(" %s", torture_ops[i].name);
+ }
+ printf("\n");
+
+ printf("default test is ALL\n");
+
+ exit(1);
+}
+
+/****************************************************************************
+ main program
+****************************************************************************/
+ int main(int argc,char *argv[])
+{
+ int opt, i;
+ char *p;
+ int gotuser = 0;
+ int gotpass = 0;
+ bool correct = True;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int seed = time(NULL);
+ struct loadparm_context *lp_ctx = NULL;
+
+#ifdef HAVE_SETBUFFER
+ setbuffer(stdout, NULL, 0);
+#endif
+
+ setup_logging("smbtorture", DEBUG_STDOUT);
+
+ smb_init_locale();
+ fault_setup();
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ fprintf(stderr,
+ "Failed to initialise the global parameter structure.\n");
+ return 1;
+ }
+
+ if (is_default_dyn_CONFIGFILE()) {
+ if(getenv("SMB_CONF_PATH")) {
+ set_dyn_CONFIGFILE(getenv("SMB_CONF_PATH"));
+ }
+ }
+ lp_load_global(get_dyn_CONFIGFILE());
+ load_interfaces();
+
+ if (argc < 2) {
+ usage();
+ }
+
+ for(p = argv[1]; *p; p++)
+ if(*p == '\\')
+ *p = '/';
+
+ if (strncmp(argv[1], "//", 2)) {
+ usage();
+ }
+
+ fstrcpy(host, &argv[1][2]);
+ p = strchr_m(&host[2],'/');
+ if (!p) {
+ usage();
+ }
+ *p = 0;
+ fstrcpy(share, p+1);
+
+ fstrcpy(myname, get_myname(talloc_tos()));
+ if (!*myname) {
+ fprintf(stderr, "Failed to get my hostname.\n");
+ return 1;
+ }
+
+ if (*username == 0 && getenv("LOGNAME")) {
+ fstrcpy(username,getenv("LOGNAME"));
+ }
+
+ argc--;
+ argv++;
+
+ fstrcpy(workgroup, lp_workgroup());
+
+ while ((opt = getopt(argc, argv, "p:hW:U:n:N:O:o:m:Ll:d:Aec:ks:b:B:f:"))
+ != EOF) {
+ switch (opt) {
+ case 'p':
+ port_to_use = atoi(optarg);
+ break;
+ case 's':
+ seed = atoi(optarg);
+ break;
+ case 'W':
+ fstrcpy(workgroup,optarg);
+ break;
+ case 'm':
+ lpcfg_set_cmdline(lp_ctx, "client max protocol", optarg);
+ break;
+ case 'N':
+ torture_nprocs = atoi(optarg);
+ break;
+ case 'o':
+ torture_numops = atoi(optarg);
+ break;
+ case 'd':
+ lpcfg_set_cmdline(lp_ctx, "log level", optarg);
+ break;
+ case 'O':
+ sockops = optarg;
+ break;
+ case 'L':
+ use_oplocks = True;
+ break;
+ case 'l':
+ local_path = optarg;
+ break;
+ case 'A':
+ torture_showall = True;
+ break;
+ case 'n':
+ fstrcpy(myname, optarg);
+ break;
+ case 'c':
+ client_txt = optarg;
+ break;
+ case 'e':
+ do_encrypt = true;
+ break;
+ case 'k':
+#ifdef HAVE_KRB5
+ use_kerberos = True;
+#else
+ d_printf("No kerberos support compiled in\n");
+ exit(1);
+#endif
+ break;
+ case 'U':
+ gotuser = 1;
+ fstrcpy(username,optarg);
+ p = strchr_m(username,'%');
+ if (p) {
+ *p = 0;
+ fstrcpy(password, p+1);
+ gotpass = 1;
+ }
+ break;
+ case 'b':
+ fstrcpy(multishare_conn_fname, optarg);
+ use_multishare_conn = True;
+ break;
+ case 'B':
+ torture_blocksize = atoi(optarg);
+ break;
+ case 'f':
+ test_filename = SMB_STRDUP(optarg);
+ break;
+ default:
+ printf("Unknown option %c (%d)\n", (char)opt, opt);
+ usage();
+ }
+ }
+
+ d_printf("using seed %d\n", seed);
+
+ srandom(seed);
+
+ if(use_kerberos && !gotuser) gotpass = True;
+
+ while (!gotpass) {
+ char pwd[256] = {0};
+ int rc;
+
+ rc = samba_getpass("Password:", pwd, sizeof(pwd), false, false);
+ if (rc == 0) {
+ fstrcpy(password, pwd);
+ gotpass = 1;
+ }
+ }
+
+ printf("host=%s share=%s user=%s myname=%s\n",
+ host, share, username, myname);
+
+ torture_creds = cli_session_creds_init(frame,
+ username,
+ workgroup,
+ NULL, /* realm */
+ password,
+ use_kerberos,
+ false, /* fallback_after_kerberos */
+ false, /* use_ccache */
+ false); /* password_is_nt_hash */
+ if (torture_creds == NULL) {
+ d_printf("cli_session_creds_init() failed.\n");
+ exit(1);
+ }
+
+ if (argc == optind) {
+ correct = run_test("ALL");
+ } else {
+ for (i=optind;i<argc;i++) {
+ if (!run_test(argv[i])) {
+ correct = False;
+ }
+ }
+ }
+
+ TALLOC_FREE(frame);
+
+ if (correct) {
+ return(0);
+ } else {
+ return(1);
+ }
+}
diff --git a/source3/torture/utable.c b/source3/torture/utable.c
new file mode 100644
index 0000000..059cae9
--- /dev/null
+++ b/source3/torture/utable.c
@@ -0,0 +1,210 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB torture tester - unicode table dumper
+ Copyright (C) Andrew Tridgell 2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "torture/proto.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "lib/util/string_wrappers.h"
+
+bool torture_utable(int dummy)
+{
+ struct cli_state *cli;
+ fstring fname, alt_name;
+ uint16_t fnum;
+ smb_ucs2_t c2;
+ int c, len, fd;
+ int chars_allowed=0, alt_allowed=0;
+ uint8_t valid[0x10000];
+
+ printf("starting utable\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ memset(valid, 0, sizeof(valid));
+
+ torture_deltree(cli, "\\utable");
+ cli_mkdir(cli, "\\utable");
+
+ for (c=1; c < 0x10000; c++) {
+ size_t size = 0;
+ char *p;
+
+ SSVAL(&c2, 0, c);
+ fstrcpy(fname, "\\utable\\x");
+ p = fname+strlen(fname);
+ if (!convert_string(CH_UTF16LE, CH_UNIX,
+ &c2, 2,
+ p, sizeof(fname)-strlen(fname),&size)) {
+ d_printf("convert_string %s failed !\n", fname);
+ continue;
+ }
+ len = size;
+ p[len] = 0;
+ fstrcat(fname,"_a_long_extension");
+
+ if (!NT_STATUS_IS_OK(cli_openx(cli, fname, O_RDWR | O_CREAT | O_TRUNC,
+ DENY_NONE, &fnum))) {
+ continue;
+ }
+
+ chars_allowed++;
+
+ cli_qpathinfo_alt_name(cli, fname, alt_name);
+
+ if (strncmp(alt_name, "X_A_L", 5) != 0) {
+ alt_allowed++;
+ valid[c] = 1;
+ d_printf("fname=[%s] alt_name=[%s]\n", fname, alt_name);
+ }
+
+ cli_close(cli, fnum);
+ cli_unlink(cli, fname, FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
+
+ if (c % 100 == 0) {
+ printf("%d (%d/%d)\r", c, chars_allowed, alt_allowed);
+ }
+ }
+ printf("%d (%d/%d)\n", c, chars_allowed, alt_allowed);
+
+ cli_rmdir(cli, "\\utable");
+
+ d_printf("%d chars allowed %d alt chars allowed\n", chars_allowed, alt_allowed);
+
+ fd = open("valid.dat", O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd == -1) {
+ d_printf("Failed to create valid.dat - %s", strerror(errno));
+ return False;
+ }
+ if (write(fd, valid, 0x10000) != 0x10000) {
+ d_printf("Failed to create valid.dat - %s", strerror(errno));
+ close(fd);
+ return false;
+ }
+ close(fd);
+ d_printf("wrote valid.dat\n");
+
+ return True;
+}
+
+
+static char *form_name(int c)
+{
+ static fstring fname;
+ smb_ucs2_t c2;
+ char *p;
+ size_t len = 0;
+
+ fstrcpy(fname, "\\utable\\");
+ p = fname+strlen(fname);
+ SSVAL(&c2, 0, c);
+
+ if (!convert_string(CH_UTF16LE, CH_UNIX,
+ &c2, 2,
+ p, sizeof(fname)-strlen(fname), &len)) {
+ d_printf("form_name: convert string %s failed\n",
+ fname);
+ return NULL;
+ }
+ p[len] = 0;
+ return fname;
+}
+
+bool torture_casetable(int dummy)
+{
+ static struct cli_state *cli;
+ char *fname;
+ uint16_t fnum;
+ int c, i;
+#define MAX_EQUIVALENCE 8
+ smb_ucs2_t equiv[0x10000][MAX_EQUIVALENCE];
+ printf("starting casetable\n");
+
+ if (!torture_open_connection(&cli, 0)) {
+ return False;
+ }
+
+ memset(equiv, 0, sizeof(equiv));
+
+ torture_deltree(cli, "\\utable");
+ if (!NT_STATUS_IS_OK(cli_mkdir(cli, "\\utable"))) {
+ printf("Failed to create utable directory!\n");
+ return False;
+ }
+
+ for (c=1; c < 0x10000; c++) {
+ off_t size;
+
+ if (c == '.' || c == '\\') continue;
+
+ printf("%04x (%c)\n", c, isprint(c)?c:'.');
+
+ fname = form_name(c);
+ if (!NT_STATUS_IS_OK(cli_ntcreate(cli, fname, 0,
+ GENERIC_ALL_ACCESS,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_NONE,
+ FILE_OPEN_IF, 0, 0, &fnum, NULL))) {
+ printf("Failed to create file with char %04x\n", c);
+ continue;
+ }
+
+ size = 0;
+
+ if (!NT_STATUS_IS_OK(cli_qfileinfo_basic(
+ cli, fnum, NULL, &size,
+ NULL, NULL, NULL, NULL, NULL))) {
+ continue;
+ }
+
+ if (size > 0) {
+ /* found a character equivalence! */
+ int c2[MAX_EQUIVALENCE];
+
+ if (size/sizeof(int) >= MAX_EQUIVALENCE) {
+ printf("too many chars match?? size=%ld c=0x%04x\n",
+ (unsigned long)size, c);
+ cli_close(cli, fnum);
+ return False;
+ }
+
+ cli_read(cli, fnum, (char *)c2, 0, size, NULL);
+ printf("%04x: ", c);
+ equiv[c][0] = c;
+ for (i=0; i<size/sizeof(int); i++) {
+ printf("%04x ", c2[i]);
+ equiv[c][i+1] = c2[i];
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+
+ cli_writeall(cli, fnum, 0, (uint8_t *)&c, size, sizeof(c),
+ NULL);
+ cli_close(cli, fnum);
+ }
+
+ torture_deltree(cli, "\\utable");
+
+ return True;
+}
diff --git a/source3/torture/vfstest.c b/source3/torture/vfstest.c
new file mode 100644
index 0000000..b25dfdc
--- /dev/null
+++ b/source3/torture/vfstest.c
@@ -0,0 +1,650 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS module tester
+
+ Copyright (C) Simo Sorce 2002
+ Copyright (C) Eric Lorimer 2002
+ Copyright (C) Jelmer Vernooij 2002,2003
+
+ Most of this code was ripped off of rpcclient.
+ Copyright (C) Tim Potter 2000-2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "locking/share_mode_lock.h"
+#include "smbd/smbd.h"
+#include "smbd/globals.h"
+#include "lib/cmdline/cmdline.h"
+#include "vfstest.h"
+#include "../libcli/smbreadline/smbreadline.h"
+#include "auth.h"
+#include "serverid.h"
+#include "messages.h"
+#include "libcli/security/security.h"
+#include "lib/smbd_shim.h"
+#include "system/filesys.h"
+#include "lib/global_contexts.h"
+#include "lib/param/param.h"
+
+/* List to hold groups of commands */
+static struct cmd_list {
+ struct cmd_list *prev, *next;
+ struct cmd_set *cmd_set;
+} *cmd_list;
+
+/* shall we do talloc_report after each command? */
+static int memreports = 0;
+
+/****************************************************************************
+handle completion of commands for readline
+****************************************************************************/
+static char **completion_fn(const char *text, int start, int end)
+{
+#define MAX_COMPLETIONS 100
+ char **matches;
+ int i, count=0;
+ struct cmd_list *commands = cmd_list;
+
+ if (start)
+ return NULL;
+
+ /* make sure we have a list of valid commands */
+ if (!commands)
+ return NULL;
+
+ matches = SMB_MALLOC_ARRAY(char *, MAX_COMPLETIONS);
+ if (!matches) return NULL;
+
+ matches[count++] = SMB_STRDUP(text);
+ if (!matches[0]) return NULL;
+
+ while (commands && count < MAX_COMPLETIONS-1)
+ {
+ if (!commands->cmd_set)
+ break;
+
+ for (i=0; commands->cmd_set[i].name; i++)
+ {
+ if ((strncmp(text, commands->cmd_set[i].name, strlen(text)) == 0) &&
+ commands->cmd_set[i].fn)
+ {
+ matches[count] = SMB_STRDUP(commands->cmd_set[i].name);
+ if (!matches[count])
+ return NULL;
+ count++;
+ }
+ }
+
+ commands = commands->next;
+ }
+
+ if (count == 2) {
+ SAFE_FREE(matches[0]);
+ matches[0] = SMB_STRDUP(matches[1]);
+ }
+ matches[count] = NULL;
+ return matches;
+}
+
+static char *next_command(TALLOC_CTX *ctx, char **cmdstr)
+{
+ char *command;
+ char *p;
+
+ if (!cmdstr || !(*cmdstr))
+ return NULL;
+
+ p = strchr_m(*cmdstr, ';');
+ if (p)
+ *p = '\0';
+ command = talloc_strdup(ctx, *cmdstr);
+
+ /* Pass back the remaining cmdstring
+ (a trailing delimiter ";" does also work),
+ or NULL at last cmdstring.
+ */
+ *cmdstr = p ? p + 1 : p;
+
+ return command;
+}
+
+/* Load specified configuration file */
+static NTSTATUS cmd_conf(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ if (argc != 2) {
+ printf("Usage: %s <smb.conf>\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (!lp_load_with_shares(argv[1])) {
+ printf("Error loading \"%s\"\n", argv[1]);
+ return NT_STATUS_OK;
+ }
+
+ printf("\"%s\" successfully loaded\n", argv[1]);
+ return NT_STATUS_OK;
+}
+
+/* Display help on commands */
+static NTSTATUS cmd_help(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ struct cmd_list *tmp;
+ struct cmd_set *tmp_set;
+
+ /* Usage */
+ if (argc > 2) {
+ printf("Usage: %s [command]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ /* Help on one command */
+
+ if (argc == 2) {
+ for (tmp = cmd_list; tmp; tmp = tmp->next) {
+
+ tmp_set = tmp->cmd_set;
+
+ while(tmp_set->name) {
+ if (strequal(argv[1], tmp_set->name)) {
+ if (tmp_set->usage &&
+ tmp_set->usage[0])
+ printf("%s\n", tmp_set->usage);
+ else
+ printf("No help for %s\n", tmp_set->name);
+
+ return NT_STATUS_OK;
+ }
+
+ tmp_set++;
+ }
+ }
+
+ printf("No such command: %s\n", argv[1]);
+ return NT_STATUS_OK;
+ }
+
+ /* List all commands */
+
+ for (tmp = cmd_list; tmp; tmp = tmp->next) {
+
+ tmp_set = tmp->cmd_set;
+
+ while(tmp_set->name) {
+
+ printf("%15s\t\t%s\n", tmp_set->name,
+ tmp_set->description ? tmp_set->description:
+ "");
+
+ tmp_set++;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* Change the debug level */
+static NTSTATUS cmd_debuglevel(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ if (argc > 2) {
+ printf("Usage: %s [debuglevel]\n", argv[0]);
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 2) {
+ struct loadparm_context *lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", argv[1]);
+ }
+
+ printf("debuglevel is %d\n", DEBUGLEVEL);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_freemem(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ /* Cleanup */
+ talloc_destroy(mem_ctx);
+ mem_ctx = NULL;
+ vfs->data = NULL;
+ vfs->data_size = 0;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cmd_quit(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc, const char **argv)
+{
+ /* Cleanup */
+ talloc_destroy(mem_ctx);
+
+ exit(0);
+ return NT_STATUS_OK; /* NOTREACHED */
+}
+
+static struct cmd_set vfstest_commands[] = {
+
+ { .name = "GENERAL OPTIONS" },
+
+ { "conf", cmd_conf, "Load smb configuration file", "conf <smb.conf>" },
+ { "help", cmd_help, "Get help on commands", "" },
+ { "?", cmd_help, "Get help on commands", "" },
+ { "debuglevel", cmd_debuglevel, "Set debug level", "" },
+ { "freemem", cmd_freemem, "Free currently allocated buffers", "" },
+ { "exit", cmd_quit, "Exit program", "" },
+ { "quit", cmd_quit, "Exit program", "" },
+
+ { .name = NULL }
+};
+
+static struct cmd_set separator_command[] = {
+ {
+ .name = "---------------",
+ .description = "----------------------"
+ },
+ {
+ .name = NULL,
+ },
+};
+
+
+extern struct cmd_set vfs_commands[];
+static struct cmd_set *vfstest_command_list[] = {
+ vfstest_commands,
+ vfs_commands,
+ NULL
+};
+
+static void add_command_set(struct cmd_set *cmd_set)
+{
+ struct cmd_list *entry;
+
+ if (!(entry = SMB_MALLOC_P(struct cmd_list))) {
+ DEBUG(0, ("out of memory\n"));
+ return;
+ }
+
+ ZERO_STRUCTP(entry);
+
+ entry->cmd_set = cmd_set;
+ DLIST_ADD(cmd_list, entry);
+}
+
+static NTSTATUS do_cmd(struct vfs_state *vfs, struct cmd_set *cmd_entry, char *cmd)
+{
+ const char *p = cmd;
+ const char **argv = NULL;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ char *buf;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ int argc = 0;
+
+ /* Count number of arguments first time through the loop then
+ allocate memory and strdup them. */
+
+ again:
+ while(next_token_talloc(mem_ctx, &p, &buf, " ")) {
+ if (argv) {
+ argv[argc] = talloc_strdup(argv, buf);
+ }
+ argc++;
+ }
+
+ if (!argv) {
+ /* Create argument list */
+
+ argv = talloc_zero_array(mem_ctx, const char *, argc);
+ if (argv == NULL) {
+ fprintf(stderr, "out of memory\n");
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ p = cmd;
+ argc = 0;
+
+ goto again;
+ }
+
+ /* Call the function */
+
+ if (cmd_entry->fn) {
+ /* Run command */
+ result = cmd_entry->fn(vfs, mem_ctx, argc, (const char **)argv);
+ } else {
+ fprintf (stderr, "Invalid command\n");
+ goto done;
+ }
+
+ done:
+
+ /* Cleanup */
+
+ if (argv) {
+ char **_argv = discard_const_p(char *, argv);
+ TALLOC_FREE(_argv);
+ argv = NULL;
+ }
+
+ if (memreports != 0) {
+ talloc_report_full(mem_ctx, stdout);
+ }
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+/* Process a command entered at the prompt or as part of -c */
+static NTSTATUS process_cmd(struct vfs_state *vfs, char *cmd)
+{
+ struct cmd_list *temp_list;
+ bool found = False;
+ char *buf;
+ const char *p = cmd;
+ NTSTATUS result = NT_STATUS_OK;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ int len = 0;
+
+ if (cmd[strlen(cmd) - 1] == '\n')
+ cmd[strlen(cmd) - 1] = '\0';
+
+ if (!next_token_talloc(mem_ctx, &p, &buf, " ")) {
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ /* Strip the trailing \n if it exists */
+ len = strlen(buf);
+ if (buf[len-1] == '\n')
+ buf[len-1] = '\0';
+
+ /* Search for matching commands */
+
+ for (temp_list = cmd_list; temp_list; temp_list = temp_list->next) {
+ struct cmd_set *temp_set = temp_list->cmd_set;
+
+ while(temp_set->name) {
+ if (strequal(buf, temp_set->name)) {
+ found = True;
+ result = do_cmd(vfs, temp_set, cmd);
+
+ goto done;
+ }
+ temp_set++;
+ }
+ }
+
+ done:
+ if (!found && buf[0]) {
+ printf("command not found: %s\n", buf);
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ printf("result was %s\n", nt_errstr(result));
+ }
+
+ TALLOC_FREE(mem_ctx);
+ return result;
+}
+
+static void process_file(struct vfs_state *pvfs, char *filename) {
+ FILE *file;
+ char command[3 * PATH_MAX];
+
+ if (*filename == '-') {
+ file = stdin;
+ } else {
+ file = fopen(filename, "r");
+ if (file == NULL) {
+ printf("vfstest: error reading file (%s)!", filename);
+ printf("errno n.%d: %s", errno, strerror(errno));
+ exit(-1);
+ }
+ }
+
+ while (fgets(command, 3 * PATH_MAX, file) != NULL) {
+ process_cmd(pvfs, command);
+ }
+
+ if (file != stdin) {
+ fclose(file);
+ }
+}
+
+static void vfstest_exit_server(const char * const reason) _NORETURN_;
+static void vfstest_exit_server(const char * const reason)
+{
+ DEBUG(3,("Server exit (%s)\n", (reason ? reason : "")));
+ exit(0);
+}
+
+static void vfstest_exit_server_cleanly(const char * const reason) _NORETURN_;
+static void vfstest_exit_server_cleanly(const char * const reason)
+{
+ vfstest_exit_server("normal exit");
+}
+
+struct smb_request *vfstest_get_smbreq(TALLOC_CTX *mem_ctx,
+ struct vfs_state *vfs)
+{
+ struct smb_request *result;
+ uint8_t *inbuf;
+
+ result = talloc_zero(mem_ctx, struct smb_request);
+ if (result == NULL) {
+ return NULL;
+ }
+ result->sconn = vfs->conn->sconn;
+ result->mid = ++vfs->mid;
+
+ inbuf = talloc_array(result, uint8_t, smb_size);
+ if (inbuf == NULL) {
+ goto fail;
+ }
+ SSVAL(inbuf, smb_mid, result->mid);
+ smb_setlen(inbuf, smb_size-4);
+ result->inbuf = inbuf;
+ return result;
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+/* Main function */
+
+int main(int argc, const char *argv[])
+{
+ char *cmdstr = NULL;
+ struct cmd_set **cmd_set;
+ struct conn_struct_tos *c = NULL;
+ struct vfs_state *vfs;
+ int opt;
+ int i;
+ char *filename = NULL;
+ char *cwd = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct auth_session_info *session_info = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+ bool ok;
+
+ /* make sure the vars that get altered (4th field) are in
+ a fixed location or certain compilers complain */
+ poptContext pc;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "file",
+ .shortName = 'f',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &filename,
+ },
+ {
+ .longName = "command",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &cmdstr,
+ .val = 0,
+ .descrip = "Execute specified list of commands",
+ },
+ {
+ .longName = "memreport",
+ .shortName = 'm',
+ .argInfo = POPT_ARG_INT,
+ .arg = &memreports,
+ .descrip = "Report memory left on talloc stackframe after each command",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ static const struct smbd_shim vfstest_shim_fns =
+ {
+ .exit_server = vfstest_exit_server,
+ .exit_server_cleanly = vfstest_exit_server_cleanly,
+ };
+
+ smb_init_locale();
+
+ setlinebuf(stdout);
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context("vfstest", argc, argv, long_options, 0);
+ if (pc == NULL) {
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptFreeContext(pc);
+
+ /* we want total control over the permissions on created files,
+ so set our umask to 0 */
+ umask(0);
+
+ /* TODO: check output */
+ reload_services(NULL, NULL, false);
+
+ per_thread_cwd_check();
+
+ set_smbd_shim(&vfstest_shim_fns);
+
+ /* Load command lists */
+
+ cmd_set = vfstest_command_list;
+
+ while(*cmd_set) {
+ add_command_set(*cmd_set);
+ add_command_set(separator_command);
+ cmd_set++;
+ }
+
+ /* some basic initialization stuff */
+ sec_init();
+ init_guest_session_info(frame);
+ locking_init();
+ vfs = talloc_zero(frame, struct vfs_state);
+ if (vfs == NULL) {
+ return 1;
+ }
+ status = make_session_info_guest(vfs, &session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+
+ /* Provided by libreplace if not present. Always mallocs. */
+ cwd = get_current_dir_name();
+ if (cwd == NULL) {
+ return -1;
+ }
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ -1,
+ cwd,
+ session_info,
+ &c);
+ SAFE_FREE(cwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return 1;
+ }
+ vfs->conn = c->conn;
+
+ vfs->conn->share_access = FILE_GENERIC_ALL;
+ vfs->conn->read_only = false;
+
+ file_init(vfs->conn->sconn);
+ for (i=0; i < 1024; i++)
+ vfs->files[i] = NULL;
+
+ if (!posix_locking_init(false)) {
+ return 1;
+ }
+
+ /* Do we have a file input? */
+ if (filename && filename[0]) {
+ process_file(vfs, filename);
+ return 0;
+ }
+
+ /* Do anything specified with -c */
+ if (cmdstr && cmdstr[0]) {
+ char *cmd;
+ char *p = cmdstr;
+
+ while((cmd=next_command(frame, &p)) != NULL) {
+ status = process_cmd(vfs, cmd);
+ }
+
+ TALLOC_FREE(cmd);
+ return NT_STATUS_IS_OK(status) ? 0 : 1;
+ }
+
+ /* Loop around accepting commands */
+
+ while(1) {
+ char *line = NULL;
+
+ line = smb_readline("vfstest $> ", NULL, completion_fn);
+
+ if (line == NULL) {
+ break;
+ }
+
+ if (line[0] != '\n') {
+ status = process_cmd(vfs, line);
+ }
+ SAFE_FREE(line);
+ }
+
+ TALLOC_FREE(vfs);
+ TALLOC_FREE(frame);
+ return NT_STATUS_IS_OK(status) ? 0 : 1;
+}
diff --git a/source3/torture/vfstest.h b/source3/torture/vfstest.h
new file mode 100644
index 0000000..bc28d49
--- /dev/null
+++ b/source3/torture/vfstest.h
@@ -0,0 +1,51 @@
+/*
+ Unix SMB/CIFS implementation.
+ VFS module tester
+
+ Copyright (C) Simo Sorce 2002
+ Copyright (C) Eric Lorimer 2002
+
+ Most of this code was ripped off of rpcclient.
+ Copyright (C) Tim Potter 2000-2001
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+struct func_entry {
+ char *name;
+ int (*fn)(struct connection_struct *conn, const char *path);
+};
+
+struct vfs_state {
+ struct connection_struct *conn;
+ uint64_t mid;
+ struct files_struct *files[1024];
+ struct smb_Dir *currentdir;
+ void *data;
+ size_t data_size;
+};
+
+struct smb_request *vfstest_get_smbreq(TALLOC_CTX *mem_ctx,
+ struct vfs_state *vfs);
+
+struct cmd_set {
+ const char *name;
+ NTSTATUS (*fn)(struct vfs_state *vfs, TALLOC_CTX *mem_ctx, int argc,
+ const char **argv);
+ const char *description;
+ const char *usage;
+};
+
+NTSTATUS cmd_test_chain(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv);
diff --git a/source3/torture/vfstest_chain.c b/source3/torture/vfstest_chain.c
new file mode 100644
index 0000000..174117b
--- /dev/null
+++ b/source3/torture/vfstest_chain.c
@@ -0,0 +1,342 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test smbd chain routines
+
+ Copyright (C) Volker Lendecke 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "vfstest.h"
+#include "smbd/smbd.h"
+
+static const uint8_t nonchain1_data[] =
+{0x00,0x00,0x00,0xBE,0xFF,0x53,0x4D,0x42,0x72,0x00,0x00,0x00,0x00,0x18,0x43
+,0xC8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+,0xFE,0xFF,0x00,0x00,0x01,0x00,0x00,0x9B,0x00,0x02,0x50,0x43,0x20,0x4E,0x45
+,0x54,0x57,0x4F,0x52,0x4B,0x20,0x50,0x52,0x4F,0x47,0x52,0x41,0x4D,0x20,0x31
+,0x2E,0x30,0x00,0x02,0x4D,0x49,0x43,0x52,0x4F,0x53,0x4F,0x46,0x54,0x20,0x4E
+,0x45,0x54,0x57,0x4F,0x52,0x4B,0x53,0x20,0x31,0x2E,0x30,0x33,0x00,0x02,0x4D
+,0x49,0x43,0x52,0x4F,0x53,0x4F,0x46,0x54,0x20,0x4E,0x45,0x54,0x57,0x4F,0x52
+,0x4B,0x53,0x20,0x33,0x2E,0x30,0x00,0x02,0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x31
+,0x2E,0x30,0x00,0x02,0x4C,0x4D,0x31,0x2E,0x32,0x58,0x30,0x30,0x32,0x00,0x02
+,0x44,0x4F,0x53,0x20,0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x32,0x2E,0x31,0x00,0x02
+,0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x32,0x2E,0x31,0x00,0x02,0x53,0x61,0x6D,0x62
+,0x61,0x00,0x02,0x4E,0x54,0x20,0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x20,0x31,0x2E
+,0x30,0x00,0x02,0x4E,0x54,0x20,0x4C,0x4D,0x20,0x30,0x2E,0x31,0x32,0x00};
+
+static const uint8_t nonchain2_data[] =
+{0x00,0x00,0x00,0xA4,0xFF,0x53,0x4D,0x42,0x73,0x00,0x00,0x00,0x00,0x18,0x43
+,0xC8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF
+,0x00,0xE7,0x00,0x00,0x02,0x00,0x0C,0xFF,0x00,0x00,0x00,0xFF,0xFF,0x02,0x00
+,0x01,0x00,0x00,0x00,0x00,0x00,0x53,0x00,0x00,0x00,0x00,0x00,0x54,0x00,0x00
+,0x80,0x69,0x00,0x60,0x51,0x06,0x06,0x2B,0x06,0x01,0x05,0x05,0x02,0xA0,0x47
+,0x30,0x45,0xA0,0x0E,0x30,0x0C,0x06,0x0A,0x2B,0x06,0x01,0x04,0x01,0x82,0x37
+,0x02,0x02,0x0A,0xA2,0x33,0x04,0x31,0x4E,0x54,0x4C,0x4D,0x53,0x53,0x50,0x00
+,0x01,0x00,0x00,0x00,0x15,0x82,0x08,0x60,0x09,0x00,0x09,0x00,0x20,0x00,0x00
+,0x00,0x08,0x00,0x08,0x00,0x29,0x00,0x00,0x00,0x57,0x4F,0x52,0x4B,0x47,0x52
+,0x4F,0x55,0x50,0x46,0x52,0x45,0x45,0x42,0x53,0x44,0x38,0x55,0x00,0x6E,0x00
+,0x69,0x00,0x78,0x00,0x00,0x00,0x53,0x00,0x61,0x00,0x6D,0x00,0x62,0x00,0x61
+,0x00,0x00,0x00};
+
+static const uint8_t chain1_data[] =
+{0x00,0x00,0x00,0x57,0xFF,0x53,0x4D,0x42,0x2D,0x00,0x00,0x00,0x00,0x88,0x03
+,0xC8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00
+,0x00,0xE7,0x64,0x00,0x05,0x00,0x0F,0x2F,0x00,0x44,0x00,0x67,0x19,0x20,0x00
+,0x40,0xDD,0x44,0x4F,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x01
+,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x04,0x00
+,0x54,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+,0x00};
+
+static const uint8_t chain2_data[] =
+{0x00,0x00,0x00,0x8C,0xFF,0x53,0x4D,0x42,0x73,0x00,0x00,0x00,0x00,0x18,0x43
+,0xC8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF
+,0x03,0xE7,0x00,0x00,0x02,0x00,0x0D,0x75,0x00,0x58,0x00,0xFF,0xFF,0x02,0x00
+,0x03,0xE7,0x04,0xE7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54
+,0x00,0x00,0x00,0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x55,0x00,0x6E,0x00,0x69
+,0x00,0x78,0x00,0x00,0x00,0x53,0x00,0x61,0x00,0x6D,0x00,0x62,0x00,0x61,0x00
+,0x00,0x00,0x04,0xFF,0xFF,0x00,0x00,0x08,0x00,0x01,0x00,0x29,0x00,0x00,0x5C
+,0x00,0x5C,0x00,0x31,0x00,0x32,0x00,0x37,0x00,0x2E,0x00,0x30,0x00,0x2E,0x00
+,0x30,0x00,0x2E,0x00,0x31,0x00,0x5C,0x00,0x49,0x00,0x50,0x00,0x43,0x00,0x24
+,0x00,0x00,0x00,0x3F,0x3F,0x3F,0x3F,0x3F,0x00};
+
+static const uint8_t bug_8360_data[] =
+{0x00,0x00,0x00,0xE9,0xFF,0x53,0x4D,0x42,0x2F,0x00,0x00,0x00,0x00,0x08,0x03
+,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00
+,0x94,0x00,0x64,0x00,0xA5,0x45,0x0C,0x0A,0x00,0x3C,0x00,0xFA,0x4B,0x00,0x00
+,0x00,0x00,0xFE,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x9D,0x00,0x4C
+,0x00,0x9E,0x00,0x00,0x05,0xFA,0x4B,0x03,0x00,0x90,0x26,0x00,0x00,0x00,0x00
+,0x00,0x00,0x41,0x4E,0x4D,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58
+,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58,0x58};
+
+static const uint8_t invalid1_data[] =
+{0x00,0x00,0x0A,0x2E,0xFF,0x53,0x4D,0x42,0x72,0x00,0x00,0x00,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9,0xA9
+,0xA9,0xA9,0xA9,0xA9,0xA9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD5,0x15,0x00,0x00,0x81,0x0B,0x00,0x77
+,0x00,0x02,0x50,0x43,0x20,0x4E,0x45,0x54,0x57,0x4F,0x52,0x4B,0x20,0x50,0x52
+,0x4F,0x47,0x52,0x41,0x4D,0x20,0x31,0x2E,0x30,0x00,0x02,0x4D,0x49,0x43,0x52
+,0x4F,0x53,0x4F,0x46,0x54,0x20,0x4E,0x45,0x54,0x57,0x4F,0x52,0x4B,0x53,0x20
+,0x33,0x2E,0x30,0x00,0x02,0x44,0x4F,0x53,0x20,0x4C,0x4D,0x31,0x2E,0x32,0x58
+,0x30,0x30,0x32,0x00,0x02,0x44,0x4F,0x53,0x20,0x4C,0x41,0x4E,0x4D,0x41,0x4E
+,0x32,0x2E,0x31,0x00,0x02,0x57,0x69,0x6E,0x64,0x6F,0x77,0x73,0x20,0x66,0x6F
+,0x72,0x20,0x57,0x6F,0x72,0x6B,0x67,0x72,0x6F,0x75,0x70,0x73,0x20,0x33,0x2E
+,0x31,0x61,0x00,0x02,0x4E,0x54,0x20,0x4C,0x4D,0x20,0x30,0x2E,0x31,0x32,0x00};
+
+static const uint8_t invalid2_data[] =
+{0x00,0x00,0x01,0x60,0xFF,0x53,0x4D,0x42,0x73,0x00,0x00,0x00,0x00,0x18,0x07
+,0x00,0x00,0x00,0x00,0x00,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74
+,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00
+,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74
+,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00
+,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74
+,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00
+,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74
+,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00
+,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74
+,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00
+,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74
+,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00
+,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74
+,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00,0x74,0x00
+,0x74,0x00,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFE,0x00
+,0x00,0x04,0x00,0x0D,0x75,0x00,0x54,0x00,0x68,0x0B,0x02,0x00,0x00,0x00,0x04
+,0x06,0x03,0x80,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0xD4,0x00,0x00,0x00
+,0x17,0x00,0x00,0x00,0x57,0x69,0x6E,0x64,0x6F,0x77,0x73,0x20,0x37,0x20,0x50
+,0x72,0x6F,0x00,0x57,0x49,0x4E,0x37,0x00,0x00,0x00,0x04,0xFF,0x00,0x91,0x00
+,0x08,0x00,0x18,0x00,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+,0x5C,0x5C,0x31,0x39,0x32,0x2E,0x31,0x36,0x38,0x2E,0x31,0x2E,0x38,0x36,0x5C
+,0x49,0x50,0x43,0x24,0x00,0x3F,0x3F,0x3F,0x3F,0x3F,0x00};
+/* end binary data. size = 356 bytes */
+
+NTSTATUS cmd_test_chain(struct vfs_state *vfs, TALLOC_CTX *mem_ctx,
+ int argc, const char **argv)
+{
+ bool ret = true;
+ unsigned chain_length;
+ struct smb_request **requests;
+
+ ret &= !smb1_is_chain(nonchain1_data);
+ ret &= !smb1_is_chain(nonchain2_data);
+
+ ret &= smb1_is_chain(chain1_data);
+
+ chain_length = smb1_chain_length(chain1_data);
+ ret &= (chain_length == 3);
+
+ ret &= smb1_is_chain(chain2_data);
+
+ chain_length = smb1_chain_length(chain2_data);
+ ret &= (chain_length == 2);
+
+ ret &= smb1_is_chain(bug_8360_data);
+
+ chain_length = smb1_chain_length(bug_8360_data);
+ ret &= (chain_length == 2);
+
+ ret &= !smb1_is_chain(invalid1_data);
+
+ chain_length = smb1_chain_length(invalid1_data);
+ ret &= (chain_length == 1);
+
+ ret &= !smb1_is_chain(invalid2_data);
+
+ chain_length = smb1_chain_length(invalid2_data);
+ ret &= (chain_length == 0);
+
+ ret &= smb1_parse_chain(talloc_tos(), chain1_data,
+ NULL, false, 0,
+ &requests, &chain_length);
+ ret &= (chain_length == 3);
+
+ ret &= smb1_parse_chain(talloc_tos(), chain2_data,
+ NULL, false, 0,
+ &requests, &chain_length);
+ ret &= (chain_length == 2);
+
+ return ret ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
diff --git a/source3/torture/wbc_async.c b/source3/torture/wbc_async.c
new file mode 100644
index 0000000..9560e36
--- /dev/null
+++ b/source3/torture/wbc_async.c
@@ -0,0 +1,758 @@
+/*
+ Unix SMB/CIFS implementation.
+ Infrastructure for async winbind requests
+ Copyright (C) Volker Lendecke 2008
+
+ ** NOTE! The following LGPL license applies to the wbclient
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "replace.h"
+#include "system/filesys.h"
+#include "system/network.h"
+#include <talloc.h>
+#include <tevent.h>
+#include "lib/async_req/async_sock.h"
+#include "nsswitch/winbind_struct_protocol.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "wbc_async.h"
+#include "lib/util/blocking.h"
+
+wbcErr map_wbc_err_from_errno(int error)
+{
+ switch(error) {
+ case EPERM:
+ case EACCES:
+ return WBC_ERR_AUTH_ERROR;
+ case ENOMEM:
+ return WBC_ERR_NO_MEMORY;
+ case EIO:
+ default:
+ return WBC_ERR_UNKNOWN_FAILURE;
+ }
+}
+
+bool tevent_req_is_wbcerr(struct tevent_req *req, wbcErr *pwbc_err)
+{
+ enum tevent_req_state state;
+ uint64_t error;
+ if (!tevent_req_is_error(req, &state, &error)) {
+ *pwbc_err = WBC_ERR_SUCCESS;
+ return false;
+ }
+
+ switch (state) {
+ case TEVENT_REQ_USER_ERROR:
+ *pwbc_err = error;
+ break;
+ case TEVENT_REQ_TIMED_OUT:
+ *pwbc_err = WBC_ERR_UNKNOWN_FAILURE;
+ break;
+ case TEVENT_REQ_NO_MEMORY:
+ *pwbc_err = WBC_ERR_NO_MEMORY;
+ break;
+ default:
+ *pwbc_err = WBC_ERR_UNKNOWN_FAILURE;
+ break;
+ }
+ return true;
+}
+
+wbcErr tevent_req_simple_recv_wbcerr(struct tevent_req *req)
+{
+ wbcErr wbc_err;
+
+ if (tevent_req_is_wbcerr(req, &wbc_err)) {
+ return wbc_err;
+ }
+
+ return WBC_ERR_SUCCESS;
+}
+
+struct wbc_debug_ops {
+ void (*debug)(void *context, enum wbcDebugLevel level,
+ const char *fmt, va_list ap) PRINTF_ATTRIBUTE(3,0);
+ void *context;
+};
+
+struct wb_context {
+ struct tevent_queue *queue;
+ int fd;
+ bool is_priv;
+ const char *dir;
+ struct wbc_debug_ops debug_ops;
+};
+
+static int make_nonstd_fd(int fd)
+{
+ size_t i;
+ int sys_errno = 0;
+ int fds[3];
+ size_t num_fds = 0;
+
+ if (fd == -1) {
+ return -1;
+ }
+ while (fd < 3) {
+ fds[num_fds++] = fd;
+ fd = dup(fd);
+ if (fd == -1) {
+ sys_errno = errno;
+ break;
+ }
+ }
+ for (i=0; i<num_fds; i++) {
+ close(fds[i]);
+ }
+ if (fd == -1) {
+ errno = sys_errno;
+ }
+ return fd;
+}
+
+/****************************************************************************
+ Set a fd into blocking/nonblocking mode.
+ Set close on exec also.
+****************************************************************************/
+
+static int make_safe_fd(int fd)
+{
+ int result, flags;
+ int new_fd = make_nonstd_fd(fd);
+
+ if (new_fd == -1) {
+ goto fail;
+ }
+
+ result = set_blocking(new_fd, false);
+ if (result == -1) {
+ goto fail;
+ }
+
+ /* Socket should be closed on exec() */
+#ifdef FD_CLOEXEC
+ result = flags = fcntl(new_fd, F_GETFD, 0);
+ if (flags >= 0) {
+ flags |= FD_CLOEXEC;
+ result = fcntl( new_fd, F_SETFD, flags );
+ }
+ if (result < 0) {
+ goto fail;
+ }
+#endif
+ return new_fd;
+
+ fail:
+ if (new_fd != -1) {
+ int sys_errno = errno;
+ close(new_fd);
+ errno = sys_errno;
+ }
+ return -1;
+}
+
+/* Just put a prototype to avoid moving the whole function around */
+static const char *winbindd_socket_dir(void);
+
+struct wb_context *wb_context_init(TALLOC_CTX *mem_ctx, const char* dir)
+{
+ struct wb_context *result;
+
+ result = talloc_zero(mem_ctx, struct wb_context);
+ if (result == NULL) {
+ return NULL;
+ }
+ result->queue = tevent_queue_create(result, "wb_trans");
+ if (result->queue == NULL) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ result->fd = -1;
+ result->is_priv = false;
+
+ if (dir != NULL) {
+ result->dir = talloc_strdup(result, dir);
+ } else {
+ result->dir = winbindd_socket_dir();
+ }
+ if (result->dir == NULL) {
+ TALLOC_FREE(result);
+ return NULL;
+ }
+ return result;
+}
+
+struct wb_connect_state {
+ int dummy;
+};
+
+static void wbc_connect_connected(struct tevent_req *subreq);
+
+static struct tevent_req *wb_connect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ const char *dir)
+{
+ struct tevent_req *result, *subreq;
+ struct wb_connect_state *state;
+ struct sockaddr_un sunaddr;
+ struct stat st;
+ char *path = NULL;
+ wbcErr wbc_err;
+
+ result = tevent_req_create(mem_ctx, &state, struct wb_connect_state);
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (wb_ctx->fd != -1) {
+ close(wb_ctx->fd);
+ wb_ctx->fd = -1;
+ }
+
+ /* Check permissions on unix socket directory */
+
+ if (lstat(dir, &st) == -1) {
+ wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
+ goto post_status;
+ }
+
+ if (!S_ISDIR(st.st_mode) ||
+ (st.st_uid != 0 && st.st_uid != geteuid())) {
+ wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
+ goto post_status;
+ }
+
+ /* Connect to socket */
+
+ path = talloc_asprintf(mem_ctx, "%s/%s", dir,
+ WINBINDD_SOCKET_NAME);
+ if (path == NULL) {
+ goto nomem;
+ }
+
+ sunaddr.sun_family = AF_UNIX;
+ strlcpy(sunaddr.sun_path, path, sizeof(sunaddr.sun_path));
+ TALLOC_FREE(path);
+
+ /* If socket file doesn't exist, don't bother trying to connect
+ with retry. This is an attempt to make the system usable when
+ the winbindd daemon is not running. */
+
+ if ((lstat(sunaddr.sun_path, &st) == -1)
+ || !S_ISSOCK(st.st_mode)
+ || (st.st_uid != 0 && st.st_uid != geteuid())) {
+ wbc_err = WBC_ERR_WINBIND_NOT_AVAILABLE;
+ goto post_status;
+ }
+
+ wb_ctx->fd = make_safe_fd(socket(AF_UNIX, SOCK_STREAM, 0));
+ if (wb_ctx->fd == -1) {
+ wbc_err = map_wbc_err_from_errno(errno);
+ goto post_status;
+ }
+
+ subreq = async_connect_send(mem_ctx, ev, wb_ctx->fd,
+ (struct sockaddr *)(void *)&sunaddr,
+ sizeof(sunaddr), NULL, NULL, NULL);
+ if (subreq == NULL) {
+ goto nomem;
+ }
+ tevent_req_set_callback(subreq, wbc_connect_connected, result);
+ return result;
+
+ post_status:
+ tevent_req_error(result, wbc_err);
+ return tevent_req_post(result, ev);
+ nomem:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+static void wbc_connect_connected(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ int res, err;
+
+ res = async_connect_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (res == -1) {
+ tevent_req_error(req, map_wbc_err_from_errno(err));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static wbcErr wb_connect_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_wbcerr(req);
+}
+
+static const char *winbindd_socket_dir(void)
+{
+ if (nss_wrapper_enabled()) {
+ const char *env_dir;
+
+ env_dir = getenv("SELFTEST_WINBINDD_SOCKET_DIR");
+ if (env_dir != NULL) {
+ return env_dir;
+ }
+ }
+
+ return WINBINDD_SOCKET_DIR;
+}
+
+struct wb_open_pipe_state {
+ struct wb_context *wb_ctx;
+ struct tevent_context *ev;
+ bool need_priv;
+ struct winbindd_request wb_req;
+};
+
+static void wb_open_pipe_connect_nonpriv_done(struct tevent_req *subreq);
+static void wb_open_pipe_ping_done(struct tevent_req *subreq);
+static void wb_open_pipe_getpriv_done(struct tevent_req *subreq);
+static void wb_open_pipe_connect_priv_done(struct tevent_req *subreq);
+
+static struct tevent_req *wb_open_pipe_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ bool need_priv)
+{
+ struct tevent_req *result, *subreq;
+ struct wb_open_pipe_state *state;
+
+ result = tevent_req_create(mem_ctx, &state, struct wb_open_pipe_state);
+ if (result == NULL) {
+ return NULL;
+ }
+ state->wb_ctx = wb_ctx;
+ state->ev = ev;
+ state->need_priv = need_priv;
+
+ if (wb_ctx->fd != -1) {
+ close(wb_ctx->fd);
+ wb_ctx->fd = -1;
+ }
+
+ subreq = wb_connect_send(state, ev, wb_ctx, wb_ctx->dir);
+ if (subreq == NULL) {
+ goto fail;
+ }
+ tevent_req_set_callback(subreq, wb_open_pipe_connect_nonpriv_done,
+ result);
+ return result;
+
+ fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+static void wb_open_pipe_connect_nonpriv_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_open_pipe_state *state = tevent_req_data(
+ req, struct wb_open_pipe_state);
+ wbcErr wbc_err;
+
+ wbc_err = wb_connect_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!WBC_ERROR_IS_OK(wbc_err)) {
+ state->wb_ctx->is_priv = true;
+ tevent_req_error(req, wbc_err);
+ return;
+ }
+
+ ZERO_STRUCT(state->wb_req);
+ state->wb_req.cmd = WINBINDD_INTERFACE_VERSION;
+ state->wb_req.pid = getpid();
+ (void)snprintf(state->wb_req.client_name,
+ sizeof(state->wb_req.client_name),
+ "%s",
+ "TORTURE");
+
+ subreq = wb_simple_trans_send(state, state->ev, NULL,
+ state->wb_ctx->fd, &state->wb_req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_open_pipe_ping_done, req);
+}
+
+static void wb_open_pipe_ping_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_open_pipe_state *state = tevent_req_data(
+ req, struct wb_open_pipe_state);
+ struct winbindd_response *wb_resp;
+ int ret, err;
+
+ ret = wb_simple_trans_recv(subreq, state, &wb_resp, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, map_wbc_err_from_errno(err));
+ return;
+ }
+
+ if (!state->need_priv) {
+ tevent_req_done(req);
+ return;
+ }
+
+ state->wb_req.cmd = WINBINDD_PRIV_PIPE_DIR;
+ state->wb_req.pid = getpid();
+
+ subreq = wb_simple_trans_send(state, state->ev, NULL,
+ state->wb_ctx->fd, &state->wb_req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_open_pipe_getpriv_done, req);
+}
+
+static void wb_open_pipe_getpriv_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_open_pipe_state *state = tevent_req_data(
+ req, struct wb_open_pipe_state);
+ struct winbindd_response *wb_resp = NULL;
+ int ret, err;
+
+ ret = wb_simple_trans_recv(subreq, state, &wb_resp, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, map_wbc_err_from_errno(err));
+ return;
+ }
+
+ close(state->wb_ctx->fd);
+ state->wb_ctx->fd = -1;
+
+ subreq = wb_connect_send(state, state->ev, state->wb_ctx,
+ (char *)wb_resp->extra_data.data);
+ TALLOC_FREE(wb_resp);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_open_pipe_connect_priv_done, req);
+}
+
+static void wb_open_pipe_connect_priv_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_open_pipe_state *state = tevent_req_data(
+ req, struct wb_open_pipe_state);
+ wbcErr wbc_err;
+
+ wbc_err = wb_connect_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!WBC_ERROR_IS_OK(wbc_err)) {
+ tevent_req_error(req, wbc_err);
+ return;
+ }
+ state->wb_ctx->is_priv = true;
+ tevent_req_done(req);
+}
+
+static wbcErr wb_open_pipe_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_wbcerr(req);
+}
+
+struct wb_trans_state {
+ struct wb_trans_state *prev, *next;
+ struct wb_context *wb_ctx;
+ struct tevent_context *ev;
+ struct winbindd_request *wb_req;
+ struct winbindd_response *wb_resp;
+ bool need_priv;
+};
+
+static bool closed_fd(int fd)
+{
+ struct timeval tv;
+ fd_set r_fds;
+ int selret;
+
+ if (fd == -1) {
+ return true;
+ }
+
+ FD_ZERO(&r_fds);
+ FD_SET(fd, &r_fds);
+ ZERO_STRUCT(tv);
+
+ selret = select(fd+1, &r_fds, NULL, NULL, &tv);
+ if (selret == -1) {
+ return true;
+ }
+ if (selret == 0) {
+ return false;
+ }
+ return (FD_ISSET(fd, &r_fds));
+}
+
+static void wb_trans_trigger(struct tevent_req *req, void *private_data);
+static void wb_trans_connect_done(struct tevent_req *subreq);
+static void wb_trans_done(struct tevent_req *subreq);
+static void wb_trans_retry_wait_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_trans_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx, bool need_priv,
+ struct winbindd_request *wb_req)
+{
+ struct tevent_req *req;
+ struct wb_trans_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_trans_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->wb_ctx = wb_ctx;
+ state->ev = ev;
+ state->wb_req = wb_req;
+ state->need_priv = need_priv;
+
+ if (!tevent_queue_add(wb_ctx->queue, ev, req, wb_trans_trigger,
+ NULL)) {
+ tevent_req_oom(req);
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static void wb_trans_trigger(struct tevent_req *req, void *private_data)
+{
+ struct wb_trans_state *state = tevent_req_data(
+ req, struct wb_trans_state);
+ struct tevent_req *subreq;
+
+ if ((state->wb_ctx->fd != -1) && closed_fd(state->wb_ctx->fd)) {
+ close(state->wb_ctx->fd);
+ state->wb_ctx->fd = -1;
+ }
+
+ if ((state->wb_ctx->fd == -1)
+ || (state->need_priv && !state->wb_ctx->is_priv)) {
+ subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
+ state->need_priv);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_trans_connect_done, req);
+ return;
+ }
+
+ state->wb_req->pid = getpid();
+
+ subreq = wb_simple_trans_send(state, state->ev, NULL,
+ state->wb_ctx->fd, state->wb_req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_trans_done, req);
+}
+
+static bool wb_trans_retry(struct tevent_req *req,
+ struct wb_trans_state *state,
+ wbcErr wbc_err)
+{
+ struct tevent_req *subreq;
+
+ if (WBC_ERROR_IS_OK(wbc_err)) {
+ return false;
+ }
+
+ if (wbc_err == WBC_ERR_WINBIND_NOT_AVAILABLE) {
+ /*
+ * Winbind not around or we can't connect to the pipe. Fail
+ * immediately.
+ */
+ tevent_req_error(req, wbc_err);
+ return true;
+ }
+
+ /*
+ * The transfer as such failed, retry after one second
+ */
+
+ if (state->wb_ctx->fd != -1) {
+ close(state->wb_ctx->fd);
+ state->wb_ctx->fd = -1;
+ }
+
+ subreq = tevent_wakeup_send(state, state->ev,
+ tevent_timeval_current_ofs(1, 0));
+ if (tevent_req_nomem(subreq, req)) {
+ return true;
+ }
+ tevent_req_set_callback(subreq, wb_trans_retry_wait_done, req);
+ return true;
+}
+
+static void wb_trans_retry_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_trans_state *state = tevent_req_data(
+ req, struct wb_trans_state);
+ bool ret;
+
+ ret = tevent_wakeup_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!ret) {
+ tevent_req_error(req, WBC_ERR_UNKNOWN_FAILURE);
+ return;
+ }
+
+ subreq = wb_open_pipe_send(state, state->ev, state->wb_ctx,
+ state->need_priv);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_trans_connect_done, req);
+}
+
+static void wb_trans_connect_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_trans_state *state = tevent_req_data(
+ req, struct wb_trans_state);
+ wbcErr wbc_err;
+
+ wbc_err = wb_open_pipe_recv(subreq);
+ TALLOC_FREE(subreq);
+
+ if (wb_trans_retry(req, state, wbc_err)) {
+ return;
+ }
+
+ subreq = wb_simple_trans_send(state, state->ev, NULL,
+ state->wb_ctx->fd, state->wb_req);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_trans_done, req);
+}
+
+static void wb_trans_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_trans_state *state = tevent_req_data(
+ req, struct wb_trans_state);
+ int ret, err;
+
+ ret = wb_simple_trans_recv(subreq, state, &state->wb_resp, &err);
+ TALLOC_FREE(subreq);
+ if ((ret == -1)
+ && wb_trans_retry(req, state, map_wbc_err_from_errno(err))) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+wbcErr wb_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct winbindd_response **presponse)
+{
+ struct wb_trans_state *state = tevent_req_data(
+ req, struct wb_trans_state);
+ wbcErr wbc_err;
+
+ if (tevent_req_is_wbcerr(req, &wbc_err)) {
+ return wbc_err;
+ }
+
+ *presponse = talloc_move(mem_ctx, &state->wb_resp);
+ return WBC_ERR_SUCCESS;
+}
+
+/********************************************************************
+ * Debug wrapper functions, modeled (with lot's of code copied as is)
+ * after the tevent debug wrapper functions
+ ********************************************************************/
+
+/*
+ this allows the user to choose their own debug function
+*/
+int wbcSetDebug(struct wb_context *wb_ctx,
+ void (*debug)(void *context,
+ enum wbcDebugLevel level,
+ const char *fmt,
+ va_list ap) PRINTF_ATTRIBUTE(3,0),
+ void *context)
+{
+ wb_ctx->debug_ops.debug = debug;
+ wb_ctx->debug_ops.context = context;
+ return 0;
+}
+
+/*
+ debug function for wbcSetDebugStderr
+*/
+static void wbcDebugStderr(void *private_data,
+ enum wbcDebugLevel level,
+ const char *fmt,
+ va_list ap) PRINTF_ATTRIBUTE(3,0);
+static void wbcDebugStderr(void *private_data,
+ enum wbcDebugLevel level,
+ const char *fmt, va_list ap)
+{
+ if (level <= WBC_DEBUG_WARNING) {
+ vfprintf(stderr, fmt, ap);
+ }
+}
+
+/*
+ convenience function to setup debug messages on stderr
+ messages of level WBC_DEBUG_WARNING and higher are printed
+*/
+int wbcSetDebugStderr(struct wb_context *wb_ctx)
+{
+ return wbcSetDebug(wb_ctx, wbcDebugStderr, wb_ctx);
+}
+
+/*
+ * log a message
+ *
+ * The default debug action is to ignore debugging messages.
+ * This is the most appropriate action for a library.
+ * Applications using the library must decide where to
+ * redirect debugging messages
+*/
+void wbcDebug(struct wb_context *wb_ctx, enum wbcDebugLevel level,
+ const char *fmt, ...)
+{
+ va_list ap;
+ if (!wb_ctx) {
+ return;
+ }
+ if (wb_ctx->debug_ops.debug == NULL) {
+ return;
+ }
+ va_start(ap, fmt);
+ wb_ctx->debug_ops.debug(wb_ctx->debug_ops.context, level, fmt, ap);
+ va_end(ap);
+}
diff --git a/source3/torture/wbc_async.h b/source3/torture/wbc_async.h
new file mode 100644
index 0000000..9cd6a93
--- /dev/null
+++ b/source3/torture/wbc_async.h
@@ -0,0 +1,171 @@
+/*
+ Unix SMB/CIFS implementation.
+ Headers for the async winbind client library
+ Copyright (C) Volker Lendecke 2008
+
+ ** NOTE! The following LGPL license applies to the wbclient
+ ** library. This does NOT imply that all of Samba is released
+ ** under the LGPL
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _WBC_ASYNC_H_
+#define _WBC_ASYNC_H_
+
+#include <talloc.h>
+#include <tevent.h>
+#include "nsswitch/libwbclient/wbclient.h"
+#include "nsswitch/wb_reqtrans.h"
+
+struct wb_context;
+struct winbindd_request;
+struct winbindd_response;
+
+enum wbcDebugLevel {
+ WBC_DEBUG_FATAL,
+ WBC_DEBUG_ERROR,
+ WBC_DEBUG_WARNING,
+ WBC_DEBUG_TRACE
+};
+
+struct tevent_req *wb_trans_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx, bool need_priv,
+ struct winbindd_request *wb_req);
+wbcErr wb_trans_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct winbindd_response **presponse);
+struct wb_context *wb_context_init(TALLOC_CTX *mem_ctx, const char* dir);
+int wbcSetDebug(struct wb_context *wb_ctx,
+ void (*debug)(void *context,
+ enum wbcDebugLevel level,
+ const char *fmt,
+ va_list ap) PRINTF_ATTRIBUTE(3,0),
+ void *context);
+int wbcSetDebugStderr(struct wb_context *wb_ctx);
+void wbcDebug(struct wb_context *wb_ctx, enum wbcDebugLevel level,
+ const char *fmt, ...) PRINTF_ATTRIBUTE(3,0);
+
+/* Definitions from wb_reqtrans.c */
+wbcErr map_wbc_err_from_errno(int error);
+
+bool tevent_req_is_wbcerr(struct tevent_req *req, wbcErr *pwbc_err);
+wbcErr tevent_req_simple_recv_wbcerr(struct tevent_req *req);
+
+/* Async functions from wbc_idmap.c */
+
+struct tevent_req *wbcSidToUid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ const struct wbcDomainSid *sid);
+wbcErr wbcSidToUid_recv(struct tevent_req *req, uid_t *puid);
+
+struct tevent_req *wbcUidToSid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ uid_t uid);
+wbcErr wbcUidToSid_recv(struct tevent_req *req, struct wbcDomainSid *psid);
+
+struct tevent_req *wbcSidToGid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ const struct wbcDomainSid *sid);
+wbcErr wbcSidToGid_recv(struct tevent_req *req, gid_t *pgid);
+
+struct tevent_req *wbcGidToSid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ gid_t gid);
+wbcErr wbcGidToSid_recv(struct tevent_req *req, struct wbcDomainSid *psid);
+
+/* Async functions from wbc_pam.c */
+struct tevent_req *wbcAuthenticateUserEx_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ const struct wbcAuthUserParams *params);
+wbcErr wbcAuthenticateUserEx_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct wbcAuthUserInfo **info,
+ struct wbcAuthErrorInfo **error);
+
+/* Async functions from wbc_sid.c */
+struct tevent_req *wbcLookupName_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ const char *domain,
+ const char *name);
+wbcErr wbcLookupName_recv(struct tevent_req *req,
+ struct wbcDomainSid *sid,
+ enum wbcSidType *name_type);
+struct tevent_req *wbcLookupSid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ const struct wbcDomainSid *sid);
+wbcErr wbcLookupSid_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char **pdomain,
+ char **pname,
+ enum wbcSidType *pname_type);
+
+/* Async functions from wbc_util.c */
+
+struct tevent_req *wbcPing_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx);
+wbcErr wbcPing_recv(struct tevent_req *req);
+
+struct tevent_req *wbcInterfaceVersion_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx);
+wbcErr wbcInterfaceVersion_recv(struct tevent_req *req,
+ uint32_t *interface_version);
+
+struct tevent_req *wbcInfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx);
+wbcErr wbcInfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char *winbind_separator,
+ char **version_string);
+
+struct tevent_req *wbcNetbiosName_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx);
+wbcErr wbcNetbiosName_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char **netbios_name);
+
+struct tevent_req *wbcDomainName_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx);
+wbcErr wbcDomainName_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ char **netbios_name);
+
+struct tevent_req *wbcInterfaceDetails_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx);
+wbcErr wbcInterfaceDetails_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct wbcInterfaceDetails **details);
+
+struct tevent_req *wbcDomainInfo_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct wb_context *wb_ctx,
+ const char *domain);
+wbcErr wbcDomainInfo_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct wbcDomainInfo **dinfo);
+
+#endif /*_WBC_ASYNC_H_*/
diff --git a/source3/torture/wscript_build b/source3/torture/wscript_build
new file mode 100644
index 0000000..1d25200
--- /dev/null
+++ b/source3/torture/wscript_build
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_BINARY('locktest2',
+ source='locktest2.c',
+ deps='''
+ talloc
+ smbconf
+ libsmb
+ LOCKING
+ ''',
+ for_selftest=True)
+
+TORTURE3_ADDITIONAL_SOURCE=""
+
+if bld.env.with_ctdb:
+ TORTURE3_ADDITIONAL_SOURCE += ' test_ctdbd_conn.c'
+
+bld.SAMBA3_BINARY('smbtorture' + bld.env.suffix3,
+ source='''
+ torture.c
+ nbio.c
+ scanner.c
+ utable.c
+ denytest.c
+ mangle_test.c
+ nbench.c
+ test_async_echo.c
+ test_addrchange.c
+ test_matching.c
+ test_posix_append.c
+ test_posix.c
+ test_nttrans_create.c
+ test_nttrans_fsctl.c
+ test_case_insensitive.c
+ test_notify_online.c
+ test_chain3.c
+ test_smb2.c
+ test_smb1_dfs.c
+ test_authinfo_structs.c
+ test_smbsock_any_connect.c
+ test_cleanup.c
+ test_notify.c
+ ../lib/tevent_barrier.c
+ test_dbwrap_watch.c
+ test_dbwrap_do_locked.c
+ test_idmap_tdb_common.c
+ test_dbwrap_ctdb.c
+ test_buffersize.c
+ test_messaging_read.c
+ test_messaging_fd_passing.c
+ test_messaging_send_all.c
+ test_oplock_cancel.c
+ test_pthreadpool_tevent.c
+ bench_pthreadpool.c
+ wbc_async.c
+ test_g_lock.c
+ test_namemap_cache.c
+ test_idmap_cache.c
+ test_hidenewfiles.c
+ test_readdir_timestamp.c
+ test_rpc_scale.c
+ test_tdb_validate.c
+ ''' + TORTURE3_ADDITIONAL_SOURCE,
+ deps='''
+ talloc
+ smbconf
+ libsmb
+ msrpc3
+ TLDAP
+ RPC_NDR_ECHO
+ WB_REQTRANS
+ LOCKING
+ NDR_OPEN_FILES
+ idmap
+ IDMAP_TDB_COMMON
+ libcli_lsa3
+ samba-cluster-support
+ util_sd
+ TDB_VALIDATE
+ ''',
+ cflags='-DWINBINDD_SOCKET_DIR=\"%s\"' % bld.env.WINBINDD_SOCKET_DIR,
+ for_selftest=True)
+
+bld.SAMBA3_BINARY('msgtest',
+ source='msgtest.c',
+ deps='''
+ talloc
+ smbconf
+ ''',
+ install=False)
+
+bld.SAMBA3_BINARY('msg_sink',
+ source='msg_sink.c',
+ deps='''
+ talloc
+ smbconf
+ ''',
+ install=False)
+
+bld.SAMBA3_BINARY('msg_source',
+ source='msg_source.c',
+ deps='''
+ talloc
+ smbconf
+ ''',
+ install=False)
+
+bld.SAMBA3_BINARY('pdbtest',
+ source='pdbtest.c',
+ deps='''
+ talloc
+ pdb
+ CMDLINE_S3
+ AUTH_COMMON
+ auth
+ ''',
+ for_selftest=True)
+
+if bld.CONFIG_SET('WITH_SMB1SERVER'):
+ SMB1_SOURCES = 'vfstest_chain.c'
+else:
+ SMB1_SOURCES = ''
+
+bld.SAMBA3_BINARY('vfstest',
+ source='''
+ cmd_vfs.c
+ vfstest.c
+ ''' + SMB1_SOURCES,
+ deps='''
+ vfs
+ CMDLINE_S3
+ smbconf
+ SMBREADLINE
+ ''',
+ for_selftest=True)
diff --git a/source3/utils/async-tracker.c b/source3/utils/async-tracker.c
new file mode 100644
index 0000000..7b6c2c0
--- /dev/null
+++ b/source3/utils/async-tracker.c
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2011, Nokia <ivan.frade@nokia.com>
+ * Copyright (C) 2015, Noel Power <nopower@suse.com>
+ * Copyright (C) 2016, Ralph Boehme <slow@samba.org.>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "includes.h"
+#include "lib/util/debug.h"
+#include "lib/cmdline/cmdline.h"
+#include "param.h"
+/*
+ * glib uses TRUE and FALSE which was redefined by "includes.h" to be
+ * unusable, undefine so glib can establish its own working
+ * replacement.
+ */
+#undef TRUE
+#undef FALSE
+#include <glib.h>
+#include <libtracker-sparql/tracker-sparql.h>
+#include "lib/tevent_glib_glue.h"
+
+enum loop_type {TEVENT_LOOP, GLIB_LOOP};
+
+struct test_state {
+ enum loop_type loop_type;
+ TrackerSparqlConnection *connection;
+ GCancellable *cancellable;
+ GTimer *timer;
+ GMainLoop *loop;
+ struct tevent_context *ev;
+ struct tevent_glib_glue *glue;
+};
+
+static void cleanup(struct test_state *state)
+{
+ g_cancellable_cancel(state->cancellable);
+ g_object_unref(state->cancellable);
+ g_timer_destroy(state->timer);
+ if (state->connection != NULL) {
+ g_object_unref(state->connection);
+ state->connection = NULL;
+ }
+ if (state->loop_type == GLIB_LOOP) {
+ g_main_loop_quit(state->loop);
+ } else {
+ samba_tevent_glib_glue_quit(state->glue);
+ }
+}
+
+static void cursor_cb(GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ struct test_state *state = talloc_get_type_abort(
+ user_data, struct test_state);
+ TrackerSparqlCursor *cursor = NULL;
+ GError *error = NULL;
+ gboolean more_results;
+ static gint i = 0;
+
+ cursor = TRACKER_SPARQL_CURSOR(object);
+ more_results = tracker_sparql_cursor_next_finish(cursor,
+ res,
+ &error);
+ if (error) {
+ g_critical("Could not run cursor next: %s", error->message);
+
+ if (cursor != NULL) {
+ g_object_unref(cursor);
+ }
+
+ g_error_free(error);
+ cleanup(state);
+ return;
+ }
+
+ if (!more_results) {
+ g_print("\n");
+ g_print("\nAsync cursor next took: %.6f (for all %d results)\n",
+ g_timer_elapsed (state->timer, NULL), i);
+
+ g_object_unref(cursor);
+ cleanup(state);
+ return;
+ }
+
+ if (i++ < 5) {
+ int num_cols = tracker_sparql_cursor_get_n_columns(cursor);
+ int col;
+
+ if (i == 1) {
+ g_print("Printing first 5 results:\n");
+ }
+ for (col = 0; col < num_cols; col++) {
+ g_print(" %s ", tracker_sparql_cursor_get_string(
+ cursor, col, NULL));
+ if (col == num_cols -1 ) {
+ g_print("\n");
+ }
+ }
+
+ if (i == 5) {
+ g_print(" ...\n");
+ g_print(" Printing nothing for remaining results\n");
+ }
+ }
+
+ tracker_sparql_cursor_next_async(cursor,
+ state->cancellable,
+ cursor_cb,
+ state);
+}
+
+static void query_cb(GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ struct test_state *state = talloc_get_type_abort(
+ user_data, struct test_state);
+ TrackerSparqlCursor *cursor = NULL;
+ GError *error = NULL;
+
+ g_print("Async query took: %.6f\n", g_timer_elapsed(state->timer, NULL));
+
+ cursor = tracker_sparql_connection_query_finish(
+ TRACKER_SPARQL_CONNECTION(object),
+ res,
+ &error);
+ if (error) {
+ g_critical("Could not run query: %s", error->message);
+
+ if (cursor) {
+ g_object_unref(cursor);
+ }
+
+ g_error_free(error);
+ cleanup(state);
+ return;
+ }
+
+ g_timer_start(state->timer);
+
+ tracker_sparql_cursor_next_async(cursor,
+ state->cancellable,
+ cursor_cb,
+ state);
+}
+
+static void connection_cb(GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ struct test_state *state = talloc_get_type_abort(
+ user_data, struct test_state);
+ GError *error = NULL;
+
+ g_print("Async connection took: %.6f\n",
+ g_timer_elapsed(state->timer, NULL));
+
+ state->connection = tracker_sparql_connection_get_finish(res, &error);
+ if (error) {
+ g_critical("Could not connect: %s", error->message);
+ g_error_free(error);
+ cleanup(state);
+ return;
+ }
+
+ g_timer_start(state->timer);
+
+ tracker_sparql_connection_query_async(
+ state->connection,
+ "SELECT ?name nie:mimeType(?s) nfo:fileName(?s) "
+ "WHERE { {?s nie:url ?name}}",
+ state->cancellable,
+ query_cb,
+ state);
+}
+
+static void debug_fn(void *private_data,
+ enum tevent_debug_level level,
+ const char *fmt,
+ va_list ap)
+{
+ dbgtext_va(fmt, ap);
+}
+
+int main(int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx = NULL;
+ struct test_state *state = NULL;
+ int c;
+ poptContext pc;
+ bool ok;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "tevent",
+ .shortName = 't',
+ .argInfo = POPT_ARG_NONE,
+ .val = 'v',
+ .descrip = "Use tevent loop",
+ },
+ {
+ .longName = "glib",
+ .shortName = 'g',
+ .argInfo = POPT_ARG_NONE,
+ .val = 'g',
+ .descrip = "Use glib loop",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ mem_ctx = talloc_new(NULL);
+ if (mem_ctx == NULL) {
+ exit(1);
+ }
+
+ state = talloc_zero(mem_ctx, struct test_state);
+ if (state == NULL) {
+ exit(1);
+ }
+
+ state->loop_type = TEVENT_LOOP;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(mem_ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ true /* require_smbconf */);
+ if (!ok) {
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ while ((c = poptGetNextOpt(pc)) != -1) {
+ switch (c) {
+ case 'g':
+ state->loop_type = GLIB_LOOP;
+ break;
+ case 't':
+ state->loop_type = TEVENT_LOOP;
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(c));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ if (state->loop_type == GLIB_LOOP) {
+ state->loop = g_main_loop_new(NULL, false);
+ } else {
+ state->ev = tevent_context_init(mem_ctx);
+ if (CHECK_DEBUGLVL(10)) {
+ tevent_set_debug(state->ev, debug_fn, NULL);
+ }
+ state->glue = samba_tevent_glib_glue_create(
+ mem_ctx, state->ev, g_main_context_default());
+ if (state->glue == NULL) {
+ printf("tevent_glib_glue_create failed\n");
+ exit(1);
+ }
+ }
+
+ state->timer = g_timer_new();
+ state->cancellable = g_cancellable_new();
+
+ tracker_sparql_connection_get_async(state->cancellable,
+ connection_cb,
+ state);
+
+ if (state->loop_type == GLIB_LOOP) {
+ printf("entering g_main_loop_run\n");
+ g_main_loop_run(state->loop);
+ } else {
+ printf("entering tevent_loop_wait\n");
+ tevent_loop_wait(state->ev);
+
+ TALLOC_FREE(state->glue);
+ TALLOC_FREE(state->ev);
+ }
+
+ TALLOC_FREE(mem_ctx);
+ poptFreeContext(pc);
+
+ return 0;
+}
diff --git a/source3/utils/clirap2.c b/source3/utils/clirap2.c
new file mode 100644
index 0000000..b2e5187
--- /dev/null
+++ b/source3/utils/clirap2.c
@@ -0,0 +1,2552 @@
+/*
+ Samba Unix/Linux SMB client library
+ More client RAP (SMB Remote Procedure Calls) functions
+ Copyright (C) 2001 Steve French (sfrench@us.ibm.com)
+ Copyright (C) 2001 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2007 Jeremy Allison. jra@samba.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*****************************************************/
+/* */
+/* Additional RAP functionality */
+/* */
+/* RAP is the original SMB RPC, documented */
+/* by Microsoft and X/Open in the 1990s and */
+/* supported by most SMB/CIFS servers although */
+/* it is unlikely that any one implementation */
+/* supports all RAP command codes since some */
+/* are quite obsolete and a few are specific */
+/* to a particular network operating system */
+/* */
+/* Although it has largely been replaced */
+/* for complex remote administration and management */
+/* (of servers) by the relatively newer */
+/* DCE/RPC based remote API (which better handles */
+/* large >64K data structures), there are many */
+/* important administrative and resource location */
+/* tasks and user tasks (e.g. password change) */
+/* that are performed via RAP. */
+/* */
+/* Although a few of the RAP calls are implemented */
+/* in the Samba client library already (clirap.c) */
+/* the new ones are in clirap2.c for easy patching */
+/* and integration and a corresponding header */
+/* file, rap.h, has been created. */
+/* */
+/* This is based on data from the CIFS spec */
+/* and the LAN Server and LAN Manager */
+/* Programming Reference books and published */
+/* RAP document and CIFS forum postings and */
+/* lots of trial and error */
+/* */
+/* Function names changed from API_ (as they are */
+/* in the CIFS specification) to RAP_ in order */
+/* to avoid confusion with other API calls */
+/* sent via DCE RPC */
+/* */
+/*****************************************************/
+
+/*****************************************************/
+/* */
+/* cifsrap.c already includes support for: */
+/* */
+/* WshareEnum ( API number 0, level 1) */
+/* NetServerEnum2 (API num 104, level 1) */
+/* WWkstaUserLogon (132) */
+/* SamOEMchgPasswordUser2_P (214) */
+/* */
+/* cifsprint.c already includes support for: */
+/* */
+/* WPrintJobEnum (API num 76, level 2) */
+/* WPrintJobDel (API num 81) */
+/* */
+/*****************************************************/
+
+#include "includes.h"
+#include "libsmb/libsmb.h"
+#include "../librpc/gen_ndr/rap.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "clirap2.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+#define WORDSIZE 2
+#define DWORDSIZE 4
+
+#define PUTBYTE(p,b) do {SCVAL(p,0,b); p++;} while(0)
+
+#define GETBYTE(p,b,endp) \
+ do {\
+ if (p+1 < endp) {\
+ b = CVAL(p,0);\
+ }\
+ p++;\
+ } while(0)
+
+#define PUTWORD(p,w) do {SSVAL(p,0,w); p += WORDSIZE;} while(0)
+
+#define GETWORD(p,w,endp) \
+ do {\
+ if (p+WORDSIZE < endp) {\
+ w = SVAL(p,0);\
+ }\
+ p += WORDSIZE;\
+ } while(0)
+
+#define PUTDWORD(p,d) do {SIVAL(p,0,d); p += DWORDSIZE;} while(0)
+
+#define GETDWORD(p,d,endp) \
+ do {\
+ if (p+DWORDSIZE < endp) {\
+ d = IVAL(p,0);\
+ }\
+ p += DWORDSIZE;\
+ } while(0)
+
+#define GETRES(p,endp) ((p && p+2 < endp) ? SVAL(p,0) : -1)
+
+/**
+ * Skip past some strings in a buffer - old version - no checks.
+ * **/
+
+static char *push_skip_string(char *buf)
+{
+ buf += strlen(buf) + 1;
+ return(buf);
+}
+
+/* put string s at p with max len n and increment p past string */
+#define PUTSTRING(p,s,n) \
+ do {\
+ push_ascii(p,s?s:"",n?n:256,STR_TERMINATE);\
+ p = push_skip_string(p);\
+ } while(0)
+
+/* put string s and p, using fixed len l, and increment p by l */
+#define PUTSTRINGF(p,s,l) \
+ do {\
+ push_ascii(p,s?s:"",l,STR_TERMINATE);\
+ p += l;\
+ } while (0)
+
+/* put string pointer at p, supplying offset o from rdata r, store */
+/* dword offset at p, increment p by 4 and o by length of s. This */
+/* means on the first call, you must calc the offset yourself! */
+
+#define PUTSTRINGP(p,s,r,o) \
+ do {\
+ if (s) {\
+ push_ascii(r+o,s,strlen(s)+1,STR_TERMINATE);\
+ PUTDWORD(p,o);\
+ o += strlen(s) + 1;\
+ } else {\
+ PUTDWORD(p,0);\
+ }\
+ }while(0);
+
+/* get asciiz string dest from src, return increment past string */
+
+static size_t rap_getstring(TALLOC_CTX *ctx, char *src, char **dest, const char *endp)
+{
+ char *p1;
+ size_t len;
+
+ *dest = NULL;
+ for (p1 = src, len = 0; *p1 && p1 < endp; len++)
+ p1++;
+ if (!*p1) {
+ len++;
+ }
+ pull_string_talloc(ctx,src,0,dest,src,len,STR_ASCII);
+ return len;
+}
+
+/* get fixed length l string dest from src, return increment for src */
+
+static size_t rap_getstringf(char *src, char *dest, size_t l, size_t dlen, char *endp)
+{
+ char *p1;
+ size_t len;
+
+ if (dlen) {
+ dest[0] = '\0';
+ }
+ for (p1 = src, len = 0; *p1 && p1 < endp; len++) {
+ p1++;
+ }
+ if (!*p1) {
+ len++;
+ }
+ if (len > l) {
+ len = l;
+ }
+ if (len) {
+ pull_ascii(dest,src,len,len,STR_ASCII);
+ }
+ return l;
+}
+
+/* get string dest from offset (obtained at p) from rdata r - converter c */
+static size_t rap_getstringp(TALLOC_CTX *ctx, char *p, char **dest, char *r, uint16_t c, char *endp)
+{
+ uint32_t off = 0;
+ const char *src;
+ size_t len=0;
+
+ *dest = NULL;
+ if (p+4 < endp) {
+ GETDWORD(p,off,endp);
+ off &= 0x0000FFFF; /* mask the obsolete segment number from the offset */
+ off -= c;
+ }
+ if (r+off > endp || r+off < r) {
+ src="";
+ len=1;
+ } else {
+ const char *p1;
+ src=r+off;
+ for (p1 = src, len = 0; *p1 && p1 < endp; len++) {
+ p1++;
+ }
+ if (!*p1) {
+ len++;
+ }
+ }
+ pull_string_talloc(ctx,src,0,dest,src,len,STR_ASCII);
+ return 4;
+}
+
+static char *make_header(char *param, uint16_t apinum, const char *reqfmt, const char *datafmt)
+{
+ PUTWORD(param,apinum);
+ if (reqfmt)
+ PUTSTRING(param,reqfmt,0);
+ else
+ *param++ = (char) 0;
+
+ if (datafmt)
+ PUTSTRING(param,datafmt,0);
+ else
+ *param++ = (char) 0;
+
+ return param;
+}
+
+/****************************************************************************
+ call a NetGroupDelete - delete user group from remote server
+****************************************************************************/
+
+int cli_NetGroupDelete(struct cli_state *cli, const char *group_name)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupDel_REQ) /* parm string */
+ +1 /* no ret string */
+ +RAP_GROUPNAME_LEN /* group to del */
+ +WORDSIZE]; /* reserved word */
+
+ /* now send a SMBtrans command with api GroupDel */
+ p = make_header(param, RAP_WGroupDel, RAP_NetGroupDel_REQ, NULL);
+ PUTSTRING(p, group_name, RAP_GROUPNAME_LEN);
+ PUTWORD(p,0); /* reserved word MBZ on input */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 200, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+
+ if (res == 0) {
+ /* nothing to do */
+ } else if ((res == 5) || (res == 65)) {
+ DEBUG(1, ("Access Denied\n"));
+ } else if (res == 2220) {
+ DEBUG (1, ("Group does not exist\n"));
+ } else {
+ DEBUG(4,("NetGroupDelete res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetGroupDelete failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ call a NetGroupAdd - add user group to remote server
+****************************************************************************/
+
+int cli_NetGroupAdd(struct cli_state *cli, struct rap_group_info_1 *grinfo)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupAdd_REQ) /* req string */
+ +sizeof(RAP_GROUP_INFO_L1) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* reserved word */
+
+ /* offset into data of free format strings. Will be updated */
+ /* by PUTSTRINGP macro and end up with total data length. */
+ int soffset = RAP_GROUPNAME_LEN + 1 + DWORDSIZE;
+ char *data;
+ size_t data_size;
+
+ /* Allocate data. */
+ data_size = MAX(soffset + strlen(grinfo->comment) + 1, 1024);
+
+ data = SMB_MALLOC_ARRAY(char, data_size);
+ if (!data) {
+ DEBUG (1, ("Malloc fail\n"));
+ return -1;
+ }
+
+ /* now send a SMBtrans command with api WGroupAdd */
+
+ p = make_header(param, RAP_WGroupAdd,
+ RAP_NetGroupAdd_REQ, RAP_GROUP_INFO_L1);
+ PUTWORD(p, 1); /* info level */
+ PUTWORD(p, 0); /* reserved word 0 */
+
+ p = data;
+ PUTSTRINGF(p, (const char *)grinfo->group_name, RAP_GROUPNAME_LEN);
+ PUTBYTE(p, 0); /* pad byte 0 */
+ PUTSTRINGP(p, grinfo->comment, data, soffset);
+
+ if (cli_api(cli,
+ param, sizeof(param), 1024, /* Param, length, maxlen */
+ data, soffset, data_size, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+
+ if (res == 0) {
+ /* nothing to do */
+ } else if ((res == 5) || (res == 65)) {
+ DEBUG(1, ("Access Denied\n"));
+ } else if (res == 2223) {
+ DEBUG (1, ("Group already exists\n"));
+ } else {
+ DEBUG(4,("NetGroupAdd res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetGroupAdd failed\n"));
+ }
+
+ SAFE_FREE(data);
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetGroupEnum - try and list user groups on a different host.
+****************************************************************************/
+
+int cli_RNetGroupEnum(struct cli_state *cli, void (*fn)(const char *, const char *, void *), void *state)
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupEnum_REQ) /* parm string */
+ +sizeof(RAP_GROUP_INFO_L1) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WGroupEnum,
+ RAP_NetGroupEnum_REQ, RAP_GROUP_INFO_L1);
+ PUTWORD(p,1); /* Info level 1 */ /* add level 0 */
+ PUTWORD(p,0xFFE0); /* Return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),8,
+ NULL, 0, 0xFFE0 /* data area size */,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rdrcnt;
+
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+ if(cli->rap_error == 234) {
+ DEBUG(1,("Not all group names were returned (such as those longer than 21 characters)\n"));
+ } else if (cli->rap_error != 0) {
+ DEBUG(1,("NetGroupEnum gave error %d\n", cli->rap_error));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetGroupEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ char *endp = rparam + rprcnt;
+ int i, converter = 0, count = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ p = rparam + WORDSIZE; /* skip result */
+ GETWORD(p, converter, endp);
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata; i<count && p < endp;i++) {
+ char *comment = NULL;
+ char groupname[RAP_GROUPNAME_LEN];
+
+ p += rap_getstringf(p,
+ groupname,
+ RAP_GROUPNAME_LEN,
+ RAP_GROUPNAME_LEN,
+ endp);
+ p++; /* pad byte */
+ p += rap_getstringp(frame,
+ p,
+ &comment,
+ rdata,
+ converter,
+ endp);
+
+ if (!comment || !groupname[0]) {
+ break;
+ }
+
+ fn(groupname, comment, cli);
+ }
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetGroupEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+int cli_RNetGroupEnum0(struct cli_state *cli,
+ void (*fn)(const char *, void *),
+ void *state)
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupEnum_REQ) /* parm string */
+ +sizeof(RAP_GROUP_INFO_L0) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WGroupEnum,
+ RAP_NetGroupEnum_REQ, RAP_GROUP_INFO_L0);
+ PUTWORD(p,0); /* Info level 0 */ /* Hmmm. I *very* much suspect this
+ is the resume count, at least
+ that's what smbd believes... */
+ PUTWORD(p,0xFFE0); /* Return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),8,
+ NULL, 0, 0xFFE0 /* data area size */,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam+rprcnt;
+ res = GETRES(rparam,endp);
+ cli->rap_error = res;
+ if(cli->rap_error == 234) {
+ DEBUG(1,("Not all group names were returned (such as those longer than 21 characters)\n"));
+ } else if (cli->rap_error != 0) {
+ DEBUG(1,("NetGroupEnum gave error %d\n", cli->rap_error));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetGroupEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ char *endp = rparam + rprcnt;
+ int i, count = 0;
+
+ p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata; i<count && p < endp;i++) {
+ char groupname[RAP_GROUPNAME_LEN];
+
+ p += rap_getstringf(p,
+ groupname,
+ RAP_GROUPNAME_LEN,
+ RAP_GROUPNAME_LEN,
+ endp);
+ if (groupname[0]) {
+ fn(groupname, cli);
+ }
+ }
+ } else {
+ DEBUG(4,("NetGroupEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+int cli_NetGroupDelUser(struct cli_state * cli, const char *group_name, const char *user_name)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupDelUser_REQ) /* parm string */
+ +1 /* no ret string */
+ +RAP_GROUPNAME_LEN /* group name */
+ +RAP_USERNAME_LEN]; /* user to del */
+
+ /* now send a SMBtrans command with api GroupMemberAdd */
+ p = make_header(param, RAP_WGroupDelUser, RAP_NetGroupDelUser_REQ, NULL);
+ PUTSTRING(p,group_name,RAP_GROUPNAME_LEN);
+ PUTSTRING(p,user_name,RAP_USERNAME_LEN);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 200, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+
+ switch(res) {
+ case 0:
+ break;
+ case 5:
+ case 65:
+ DEBUG(1, ("Access Denied\n"));
+ break;
+ case 50:
+ DEBUG(1, ("Not supported by server\n"));
+ break;
+ case 2220:
+ DEBUG(1, ("Group does not exist\n"));
+ break;
+ case 2221:
+ DEBUG(1, ("User does not exist\n"));
+ break;
+ case 2237:
+ DEBUG(1, ("User is not in group\n"));
+ break;
+ default:
+ DEBUG(4,("NetGroupDelUser res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetGroupDelUser failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+int cli_NetGroupAddUser(struct cli_state * cli, const char *group_name, const char *user_name)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupAddUser_REQ) /* parm string */
+ +1 /* no ret string */
+ +RAP_GROUPNAME_LEN /* group name */
+ +RAP_USERNAME_LEN]; /* user to add */
+
+ /* now send a SMBtrans command with api GroupMemberAdd */
+ p = make_header(param, RAP_WGroupAddUser, RAP_NetGroupAddUser_REQ, NULL);
+ PUTSTRING(p,group_name,RAP_GROUPNAME_LEN);
+ PUTSTRING(p,user_name,RAP_USERNAME_LEN);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 200, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+
+ switch(res) {
+ case 0:
+ break;
+ case 5:
+ case 65:
+ DEBUG(1, ("Access Denied\n"));
+ break;
+ case 50:
+ DEBUG(1, ("Not supported by server\n"));
+ break;
+ case 2220:
+ DEBUG(1, ("Group does not exist\n"));
+ break;
+ case 2221:
+ DEBUG(1, ("User does not exist\n"));
+ break;
+ default:
+ DEBUG(4,("NetGroupAddUser res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetGroupAddUser failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+
+int cli_NetGroupGetUsers(struct cli_state * cli, const char *group_name, void (*fn)(const char *, void *), void *state )
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupGetUsers_REQ)/* parm string */
+ +sizeof(RAP_GROUP_USERS_INFO_0) /* return string */
+ +RAP_GROUPNAME_LEN /* group name */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+
+ /* now send a SMBtrans command with api GroupGetUsers */
+ p = make_header(param, RAP_WGroupGetUsers,
+ RAP_NetGroupGetUsers_REQ, RAP_GROUP_USERS_INFO_0);
+ PUTSTRING(p,group_name,RAP_GROUPNAME_LEN-1);
+ PUTWORD(p,0); /* info level 0 */
+ PUTWORD(p,0xFFE0); /* return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),PTR_DIFF(p,param),
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+ cli->rap_error = res;
+ if (res != 0) {
+ DEBUG(1,("NetGroupGetUsers gave error %d\n", res));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetGroupGetUsers no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ char *endp = rparam + rprcnt;
+ int i, count = 0;
+ char username[RAP_USERNAME_LEN];
+
+ p = rparam + WORDSIZE + WORDSIZE;
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata; i<count && p < endp; i++) {
+ p += rap_getstringf(p,
+ username,
+ RAP_USERNAME_LEN,
+ RAP_USERNAME_LEN,
+ endp);
+ if (username[0]) {
+ fn(username, state);
+ }
+ }
+ } else {
+ DEBUG(4,("NetGroupGetUsers res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ return res;
+}
+
+int cli_NetUserGetGroups(struct cli_state * cli, const char *user_name, void (*fn)(const char *, void *), void *state )
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetUserGetGroups_REQ)/* parm string */
+ +sizeof(RAP_GROUP_USERS_INFO_0) /* return string */
+ +RAP_USERNAME_LEN /* user name */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+
+ /* now send a SMBtrans command with api GroupGetUsers */
+ p = make_header(param, RAP_WUserGetGroups,
+ RAP_NetUserGetGroups_REQ, RAP_GROUP_USERS_INFO_0);
+ PUTSTRING(p,user_name,RAP_USERNAME_LEN-1);
+ PUTWORD(p,0); /* info level 0 */
+ PUTWORD(p,0xFFE0); /* return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),PTR_DIFF(p,param),
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+ cli->rap_error = res;
+ if (res != 0) {
+ DEBUG(1,("NetUserGetGroups gave error %d\n", res));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetUserGetGroups no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ char *endp = rparam + rprcnt;
+ int i, count = 0;
+ char groupname[RAP_GROUPNAME_LEN];
+
+ p = rparam + WORDSIZE + WORDSIZE;
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata; i<count && p < endp; i++) {
+ p += rap_getstringf(p,
+ groupname,
+ RAP_GROUPNAME_LEN,
+ RAP_GROUPNAME_LEN,
+ endp);
+ if (groupname[0]) {
+ fn(groupname, state);
+ }
+ }
+ } else {
+ DEBUG(4,("NetUserGetGroups res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ return res;
+}
+
+/****************************************************************************
+ Call a NetUserDelete - delete user from remote server.
+****************************************************************************/
+
+int cli_NetUserDelete(struct cli_state *cli, const char * user_name )
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetGroupDel_REQ) /* parm string */
+ +1 /* no ret string */
+ +RAP_USERNAME_LEN /* user to del */
+ +WORDSIZE]; /* reserved word */
+
+ /* now send a SMBtrans command with api UserDel */
+ p = make_header(param, RAP_WUserDel, RAP_NetGroupDel_REQ, NULL);
+ PUTSTRING(p, user_name, RAP_USERNAME_LEN);
+ PUTWORD(p,0); /* reserved word MBZ on input */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 200, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+
+ if (res == 0) {
+ /* nothing to do */
+ } else if ((res == 5) || (res == 65)) {
+ DEBUG(1, ("Access Denied\n"));
+ } else if (res == 2221) {
+ DEBUG (1, ("User does not exist\n"));
+ } else {
+ DEBUG(4,("NetUserDelete res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetUserDelete failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetUserAdd - add user to remote server.
+****************************************************************************/
+
+int cli_NetUserAdd(struct cli_state *cli, struct rap_user_info_1 * userinfo )
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetUserAdd2_REQ) /* req string */
+ +sizeof(RAP_USER_INFO_L1) /* data string */
+ +WORDSIZE /* info level */
+ +WORDSIZE /* buffer length */
+ +WORDSIZE]; /* reserved */
+
+ char data[1024];
+ /* offset into data of free format strings. Will be updated */
+ /* by PUTSTRINGP macro and end up with total data length. */
+ int soffset=RAP_USERNAME_LEN+1 /* user name + pad */
+ + RAP_UPASSWD_LEN /* password */
+ + DWORDSIZE /* password age */
+ + WORDSIZE /* privilege */
+ + DWORDSIZE /* home dir ptr */
+ + DWORDSIZE /* comment ptr */
+ + WORDSIZE /* flags */
+ + DWORDSIZE; /* login script ptr*/
+
+ /* now send a SMBtrans command with api NetUserAdd */
+ p = make_header(param, RAP_WUserAdd2,
+ RAP_NetUserAdd2_REQ, RAP_USER_INFO_L1);
+
+ PUTWORD(p, 1); /* info level */
+ PUTWORD(p, 0); /* pwencrypt */
+ PUTWORD(p, MIN(strlen((const char *)userinfo->passwrd),
+ RAP_UPASSWD_LEN));
+
+ p = data;
+ memset(data, '\0', soffset);
+
+ PUTSTRINGF(p, (const char *)userinfo->user_name, RAP_USERNAME_LEN);
+ PUTBYTE(p, 0); /* pad byte 0 */
+ PUTSTRINGF(p, (const char *)userinfo->passwrd, RAP_UPASSWD_LEN);
+ PUTDWORD(p, 0); /* pw age - n.a. on user add */
+ PUTWORD(p, userinfo->priv);
+ PUTSTRINGP(p, userinfo->home_dir, data, soffset);
+ PUTSTRINGP(p, userinfo->comment, data, soffset);
+ PUTWORD(p, userinfo->userflags);
+ PUTSTRINGP(p, userinfo->logon_script, data, soffset);
+
+ if (cli_api(cli,
+ param, sizeof(param), 1024, /* Param, length, maxlen */
+ data, soffset, sizeof(data), /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+
+ if (res == 0) {
+ /* nothing to do */
+ } else if ((res == 5) || (res == 65)) {
+ DEBUG(1, ("Access Denied\n"));
+ } else if (res == 2224) {
+ DEBUG (1, ("User already exists\n"));
+ } else {
+ DEBUG(4,("NetUserAdd res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetUserAdd failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+call a NetUserEnum - try and list users on a different host
+****************************************************************************/
+
+int cli_RNetUserEnum(struct cli_state *cli, void (*fn)(const char *, const char *, const char *, const char *, void *), void *state)
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetUserEnum_REQ) /* parm string */
+ +sizeof(RAP_USER_INFO_L1) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WUserEnum,
+ RAP_NetUserEnum_REQ, RAP_USER_INFO_L1);
+ PUTWORD(p,1); /* Info level 1 */
+ PUTWORD(p,0xFF00); /* Return buffer size */
+
+ /* BB Fix handling of large numbers of users to be returned */
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),8,
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+ cli->rap_error = res;
+ if (cli->rap_error != 0) {
+ DEBUG(1,("NetUserEnum gave error %d\n", cli->rap_error));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetUserEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ int i, converter = 0, count = 0;
+ char username[RAP_USERNAME_LEN];
+ char userpw[RAP_UPASSWD_LEN];
+ char *endp = rparam + rprcnt;
+ char *comment, *homedir, *logonscript;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ p = rparam + WORDSIZE; /* skip result */
+ GETWORD(p, converter, endp);
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata;i<count && p < endp;i++) {
+ p += rap_getstringf(p,
+ username,
+ RAP_USERNAME_LEN,
+ RAP_USERNAME_LEN,
+ endp);
+ p++; /* pad byte */
+ p += rap_getstringf(p,
+ userpw,
+ RAP_UPASSWD_LEN,
+ RAP_UPASSWD_LEN,
+ endp);
+ p += DWORDSIZE; /* skip password age */
+ p += WORDSIZE; /* skip priv: 0=guest, 1=user, 2=admin */
+ p += rap_getstringp(frame,
+ p,
+ &homedir,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &comment,
+ rdata,
+ converter,
+ endp);
+ p += WORDSIZE; /* skip flags */
+ p += rap_getstringp(frame,
+ p,
+ &logonscript,
+ rdata,
+ converter,
+ endp);
+ if (username[0] && comment &&
+ homedir && logonscript) {
+ fn(username,
+ comment,
+ homedir,
+ logonscript,
+ cli);
+ }
+ }
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetUserEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+int cli_RNetUserEnum0(struct cli_state *cli,
+ void (*fn)(const char *, void *),
+ void *state)
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetUserEnum_REQ) /* parm string */
+ +sizeof(RAP_USER_INFO_L0) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WUserEnum,
+ RAP_NetUserEnum_REQ, RAP_USER_INFO_L0);
+ PUTWORD(p,0); /* Info level 1 */
+ PUTWORD(p,0xFF00); /* Return buffer size */
+
+ /* BB Fix handling of large numbers of users to be returned */
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),8,
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+ cli->rap_error = res;
+ if (cli->rap_error != 0) {
+ DEBUG(1,("NetUserEnum gave error %d\n", cli->rap_error));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetUserEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ int i, count = 0;
+ char *endp = rparam + rprcnt;
+ char username[RAP_USERNAME_LEN];
+
+ p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata;i<count && p < endp;i++) {
+ p += rap_getstringf(p,
+ username,
+ RAP_USERNAME_LEN,
+ RAP_USERNAME_LEN,
+ endp);
+ if (username[0]) {
+ fn(username, cli);
+ }
+ }
+ } else {
+ DEBUG(4,("NetUserEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetFileClose2 - close open file on another session to server.
+****************************************************************************/
+
+int cli_NetFileClose(struct cli_state *cli, uint32_t file_id )
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_WFileClose2_REQ) /* req string */
+ +1 /* no ret string */
+ +DWORDSIZE]; /* file ID */
+ int res = -1;
+
+ /* now send a SMBtrans command with api RNetShareEnum */
+ p = make_header(param, RAP_WFileClose2, RAP_WFileClose2_REQ, NULL);
+ PUTDWORD(p, file_id);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 200, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+
+ if (res == 0) {
+ /* nothing to do */
+ } else if (res == 2314){
+ DEBUG(1, ("NetFileClose2 - attempt to close non-existent file open instance\n"));
+ } else {
+ DEBUG(4,("NetFileClose2 res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetFileClose2 failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetFileGetInfo - get information about server file opened from other
+ workstation.
+****************************************************************************/
+
+int cli_NetFileGetInfo(struct cli_state *cli, uint32_t file_id, void (*fn)(const char *, const char *, uint16_t, uint16_t, uint32_t))
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_WFileGetInfo2_REQ) /* req string */
+ +sizeof(RAP_FILE_INFO_L3) /* return string */
+ +DWORDSIZE /* file ID */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+
+ /* now send a SMBtrans command with api RNetShareEnum */
+ p = make_header(param, RAP_WFileGetInfo2,
+ RAP_WFileGetInfo2_REQ, RAP_FILE_INFO_L3);
+ PUTDWORD(p, file_id);
+ PUTWORD(p, 3); /* info level */
+ PUTWORD(p, 0x1000); /* buffer size */
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 0x1000, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+ if (res == 0 || res == ERRmoredata) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ int converter = 0,id = 0, perms = 0, locks = 0;
+ char *fpath, *fuser;
+
+ p = rparam + WORDSIZE; /* skip result */
+ GETWORD(p, converter, endp);
+
+ p = rdata;
+ endp = rdata + rdrcnt;
+
+ GETDWORD(p, id, endp);
+ GETWORD(p, perms, endp);
+ GETWORD(p, locks, endp);
+
+ p += rap_getstringp(frame,
+ p,
+ &fpath,
+ rdata,
+ converter,
+ endp);
+ rap_getstringp(frame,
+ p,
+ &fuser,
+ rdata,
+ converter,
+ endp);
+
+ if (fpath && fuser) {
+ fn(fpath, fuser, perms, locks, id);
+ }
+
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetFileGetInfo2 res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetFileGetInfo2 failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+* Call a NetFileEnum2 - list open files on an SMB server
+*
+* PURPOSE: Remotes a NetFileEnum API call to the current server or target
+* server listing the files open via the network (and their
+* corresponding open instance ids)
+*
+* Dependencies: none
+*
+* Parameters:
+* cli - pointer to cli_state structure
+* user - if present, return only files opened by this remote user
+* base_path - if present, return only files opened below this
+* base path
+* fn - display function to invoke for each entry in the result
+*
+*
+* Returns:
+* True - success
+* False - failure
+*
+****************************************************************************/
+
+int cli_NetFileEnum(struct cli_state *cli, const char * user,
+ const char * base_path,
+ void (*fn)(const char *, const char *, uint16_t, uint16_t,
+ uint32_t))
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_WFileEnum2_REQ) /* req string */
+ +sizeof(RAP_FILE_INFO_L3) /* return string */
+ +1024 /* base path (opt) */
+ +RAP_USERNAME_LEN /* user name (opt) */
+ +WORDSIZE /* info level */
+ +WORDSIZE /* buffer size */
+ +DWORDSIZE /* resume key ? */
+ +DWORDSIZE]; /* resume key ? */
+ int count = -1;
+ int res = -1;
+
+ /* now send a SMBtrans command with api RNetShareEnum */
+ p = make_header(param, RAP_WFileEnum2,
+ RAP_WFileEnum2_REQ, RAP_FILE_INFO_L3);
+
+ PUTSTRING(p, base_path, 1024);
+ PUTSTRING(p, user, RAP_USERNAME_LEN);
+ PUTWORD(p, 3); /* info level */
+ PUTWORD(p, 0xFF00); /* buffer size */
+ PUTDWORD(p, 0); /* zero out the resume key */
+ PUTDWORD(p, 0); /* or is this one the resume key? */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 0xFF00, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+
+ if (res == 0 || res == ERRmoredata) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ int converter = 0, i;
+
+ p = rparam + WORDSIZE; /* skip result */
+ GETWORD(p, converter, endp);
+ GETWORD(p, count, endp);
+
+ p = rdata;
+ endp = rdata + rdrcnt;
+ for (i=0; i<count && p < endp; i++) {
+ int id = 0, perms = 0, locks = 0;
+ char *fpath, *fuser;
+
+ GETDWORD(p, id, endp);
+ GETWORD(p, perms, endp);
+ GETWORD(p, locks, endp);
+ p += rap_getstringp(frame,
+ p,
+ &fpath,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &fuser,
+ rdata,
+ converter,
+ endp);
+
+ if (fpath && fuser) {
+ fn(fpath, fuser, perms, locks, id);
+ }
+ } /* BB fix ERRmoredata case to send resume request */
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetFileEnum2 res=%d\n", res));
+ }
+ } else {
+ DEBUG(4,("NetFileEnum2 failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return count;
+}
+
+/****************************************************************************
+ Call a NetShareAdd - share/export directory on remote server.
+****************************************************************************/
+
+int cli_NetShareAdd(struct cli_state *cli, struct rap_share_info_2 * sinfo )
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_WShareAdd_REQ) /* req string */
+ +sizeof(RAP_SHARE_INFO_L2) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* reserved word */
+ char data[1024];
+ /* offset to free format string section following fixed length data. */
+ /* will be updated by PUTSTRINGP macro and will end up with total len */
+ int soffset = RAP_SHARENAME_LEN + 1 /* share name + pad */
+ + WORDSIZE /* share type */
+ + DWORDSIZE /* comment pointer */
+ + WORDSIZE /* permissions */
+ + WORDSIZE /* max users */
+ + WORDSIZE /* active users */
+ + DWORDSIZE /* share path */
+ + RAP_SPASSWD_LEN + 1; /* share password + pad */
+
+ memset(param,'\0',sizeof(param));
+ /* now send a SMBtrans command with api RNetShareAdd */
+ p = make_header(param, RAP_WshareAdd,
+ RAP_WShareAdd_REQ, RAP_SHARE_INFO_L2);
+ PUTWORD(p, 2); /* info level */
+ PUTWORD(p, 0); /* reserved word 0 */
+
+ p = data;
+ PUTSTRINGF(p, (const char *)sinfo->share_name, RAP_SHARENAME_LEN);
+ PUTBYTE(p, 0); /* pad byte 0 */
+
+ PUTWORD(p, sinfo->share_type);
+ PUTSTRINGP(p, sinfo->comment, data, soffset);
+ PUTWORD(p, sinfo->perms);
+ PUTWORD(p, sinfo->maximum_users);
+ PUTWORD(p, sinfo->active_users);
+ PUTSTRINGP(p, sinfo->path, data, soffset);
+ PUTSTRINGF(p, (const char *)sinfo->password, RAP_SPASSWD_LEN);
+ SCVAL(p,-1,0x0A); /* required 0x0A at end of password */
+
+ if (cli_api(cli,
+ param, sizeof(param), 1024, /* Param, length, maxlen */
+ data, soffset, sizeof(data), /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+
+ if (res == 0) {
+ /* nothing to do */
+ } else {
+ DEBUG(4,("NetShareAdd res=%d\n", res));
+ }
+ } else {
+ DEBUG(4,("NetShareAdd failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetShareDelete - unshare exported directory on remote server.
+****************************************************************************/
+
+int cli_NetShareDelete(struct cli_state *cli, const char * share_name )
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ int res = -1;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_WShareDel_REQ) /* req string */
+ +1 /* no ret string */
+ +RAP_SHARENAME_LEN /* share to del */
+ +WORDSIZE]; /* reserved word */
+
+ /* now send a SMBtrans command with api RNetShareDelete */
+ p = make_header(param, RAP_WshareDel, RAP_WShareDel_REQ, NULL);
+ PUTSTRING(p,share_name,RAP_SHARENAME_LEN);
+ PUTWORD(p,0); /* reserved word MBZ on input */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 200, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+
+ if (res == 0) {
+ /* nothing to do */
+ } else {
+ DEBUG(4,("NetShareDelete res=%d\n", res));
+ }
+ } else {
+ DEBUG(4,("NetShareDelete failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/*************************************************************************
+*
+* Function Name: cli_get_pdc_name
+*
+* PURPOSE: Remotes a NetServerEnum API call to the current server
+* requesting the name of a server matching the server
+* type of SV_TYPE_DOMAIN_CTRL (PDC).
+*
+* Dependencies: none
+*
+* Parameters:
+* cli - pointer to cli_state structure
+* workgroup - pointer to string containing name of domain
+* pdc_name - pointer to string that will contain PDC name
+* on successful return
+*
+* Returns:
+* True - success
+* False - failure
+*
+************************************************************************/
+
+bool cli_get_pdc_name(struct cli_state *cli, const char *workgroup, char **pdc_name)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rdrcnt,rprcnt;
+ char *p;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetServerEnum2_REQ) /* req string */
+ +sizeof(RAP_SERVER_INFO_L1) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE /* buffer size */
+ +DWORDSIZE /* server type */
+ +RAP_MACHNAME_LEN]; /* workgroup */
+ int count = -1;
+ int res = -1;
+
+ *pdc_name = NULL;
+
+ /* send a SMBtrans command with api NetServerEnum */
+ p = make_header(param, RAP_NetServerEnum2,
+ RAP_NetServerEnum2_REQ, RAP_SERVER_INFO_L1);
+ PUTWORD(p, 1); /* info level */
+ PUTWORD(p, CLI_BUFFER_SIZE);
+ PUTDWORD(p, SV_TYPE_DOMAIN_CTRL);
+ PUTSTRING(p, workgroup, RAP_MACHNAME_LEN);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 8, /* params, length, max */
+ NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */
+ &rparam, &rprcnt, /* return params, return size */
+ &rdata, &rdrcnt /* return data, return size */
+ )) {
+
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+
+ /*
+ * We only really care to copy a name if the
+ * API succeeded and we got back a name.
+ */
+ if (cli->rap_error == 0) {
+ p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */
+ GETWORD(p, count, endp);
+ p = rdata;
+ endp = rdata + rdrcnt;
+
+ if (count > 0) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *dcname;
+ rap_getstring(frame,
+ p,
+ &dcname,
+ endp);
+ if (dcname) {
+ *pdc_name = SMB_STRDUP(dcname);
+ }
+ TALLOC_FREE(frame);
+ }
+ } else {
+ DEBUG(4, ("cli_get_pdc_name: machine %s failed the "
+ "NetServerEnum call. Error was : %s.\n",
+ smbXcli_conn_remote_name(cli->conn),
+ win_errstr(W_ERROR(cli->rap_error))));
+ }
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return(count > 0);
+}
+
+bool cli_get_server_name(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ char **servername)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rdrcnt,rprcnt;
+ char *p;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_WserverGetInfo_REQ) /* req string */
+ +sizeof(RAP_SERVER_INFO_L1) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ bool res = false;
+ char *endp;
+ fstring tmp;
+
+ /* send a SMBtrans command with api NetServerGetInfo */
+ p = make_header(param, RAP_WserverGetInfo,
+ RAP_WserverGetInfo_REQ, RAP_SERVER_INFO_L1);
+ PUTWORD(p, 1); /* info level */
+ PUTWORD(p, CLI_BUFFER_SIZE);
+
+ if (!cli_api(cli,
+ param, PTR_DIFF(p,param), 8, /* params, length, max */
+ NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */
+ &rparam, &rprcnt, /* return params, return size */
+ &rdata, &rdrcnt /* return data, return size */
+ )) {
+ goto failed;
+ }
+
+ endp = rparam + rprcnt;
+ if (GETRES(rparam, endp) != 0) {
+ goto failed;
+ }
+
+ if (rdrcnt < 16) {
+ DEBUG(10, ("invalid data count %d, expected >= 16\n", rdrcnt));
+ goto failed;
+ }
+
+ if (pull_ascii(tmp, rdata, sizeof(tmp)-1, 16, STR_TERMINATE) == -1) {
+ DEBUG(10, ("pull_ascii failed\n"));
+ goto failed;
+ }
+
+ if (!(*servername = talloc_strdup(mem_ctx, tmp))) {
+ DEBUG(1, ("talloc_strdup failed\n"));
+ goto failed;
+ }
+
+ res = true;
+
+ failed:
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+ return res;
+}
+
+/*************************************************************************
+*
+* Function Name: cli_ns_check_server_type
+*
+* PURPOSE: Remotes a NetServerEnum2 API call to the current server
+* requesting server_info_0 level information of machines
+* matching the given server type. If the returned server
+* list contains the machine name contained in smbXcli_conn_remote_name(->conn)
+* then we conclude the server type checks out. This routine
+* is useful to retrieve list of server's of a certain
+* type when all you have is a null session connection and
+* can't remote API calls such as NetWkstaGetInfo or
+* NetServerGetInfo.
+*
+* Dependencies: none
+*
+* Parameters:
+* cli - pointer to cli_state structure
+* workgroup - pointer to string containing domain
+* stype - server type
+*
+* Returns:
+* True - success
+* False - failure
+*
+************************************************************************/
+
+bool cli_ns_check_server_type(struct cli_state *cli, char *workgroup, uint32_t stype)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rdrcnt,rprcnt;
+ char *p;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetServerEnum2_REQ) /* req string */
+ +sizeof(RAP_SERVER_INFO_L0) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE /* buffer size */
+ +DWORDSIZE /* server type */
+ +RAP_MACHNAME_LEN]; /* workgroup */
+ bool found_server = false;
+ int res = -1;
+ const char *remote_name = smbXcli_conn_remote_name(cli->conn);
+
+ /* send a SMBtrans command with api NetServerEnum */
+ p = make_header(param, RAP_NetServerEnum2,
+ RAP_NetServerEnum2_REQ, RAP_SERVER_INFO_L0);
+ PUTWORD(p, 0); /* info level 0 */
+ PUTWORD(p, CLI_BUFFER_SIZE);
+ PUTDWORD(p, stype);
+ PUTSTRING(p, workgroup, RAP_MACHNAME_LEN);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 8, /* params, length, max */
+ NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */
+ &rparam, &rprcnt, /* return params, return size */
+ &rdata, &rdrcnt /* return data, return size */
+ )) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+ cli->rap_error = res;
+
+ if (res == 0 || res == ERRmoredata) {
+ int i, count = 0;
+
+ p = rparam + WORDSIZE + WORDSIZE;
+ GETWORD(p, count,endp);
+
+ p = rdata;
+ endp = rdata + rdrcnt;
+ for (i = 0;i < count && p < endp;i++, p += 16) {
+ char ret_server[RAP_MACHNAME_LEN];
+
+ p += rap_getstringf(p,
+ ret_server,
+ RAP_MACHNAME_LEN,
+ RAP_MACHNAME_LEN,
+ endp);
+ if (strequal(ret_server, remote_name)) {
+ found_server = true;
+ break;
+ }
+ }
+ } else {
+ DEBUG(4, ("cli_ns_check_server_type: machine %s "
+ "failed the NetServerEnum call. Error was : "
+ "%s.\n", remote_name,
+ win_errstr(W_ERROR(cli->rap_error))));
+ }
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return found_server;
+}
+
+/****************************************************************************
+ Perform a NetWkstaUserLogoff.
+****************************************************************************/
+
+bool cli_NetWkstaUserLogoff(struct cli_state *cli, const char *user, const char *workstation)
+{
+ char *rparam = NULL;
+ char *rdata = NULL;
+ char *p;
+ unsigned int rdrcnt,rprcnt;
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetWkstaUserLogoff_REQ) /* req string */
+ +sizeof(RAP_USER_LOGOFF_INFO_L1) /* return string */
+ +RAP_USERNAME_LEN+1 /* user name+pad */
+ +RAP_MACHNAME_LEN /* wksta name */
+ +WORDSIZE /* buffer size */
+ +WORDSIZE]; /* buffer size? */
+ char upperbuf[MAX(RAP_USERNAME_LEN,RAP_MACHNAME_LEN)];
+ int res = -1;
+ char *tmp = NULL;
+
+ memset(param, 0, sizeof(param));
+
+ /* send a SMBtrans command with api NetWkstaUserLogoff */
+ p = make_header(param, RAP_WWkstaUserLogoff,
+ RAP_NetWkstaUserLogoff_REQ, RAP_USER_LOGOFF_INFO_L1);
+ PUTDWORD(p, 0); /* Null pointer */
+ PUTDWORD(p, 0); /* Null pointer */
+ strlcpy(upperbuf, user, sizeof(upperbuf));
+ if (!strupper_m(upperbuf)) {
+ return false;
+ }
+ tmp = upperbuf;
+ PUTSTRINGF(p, tmp, RAP_USERNAME_LEN);
+ p++; /* strange format, but ok */
+ strlcpy(upperbuf, workstation, sizeof(upperbuf));
+ if (!strupper_m(upperbuf)) {
+ return false;
+ }
+ tmp = upperbuf;
+ PUTSTRINGF(p, tmp, RAP_MACHNAME_LEN);
+ PUTWORD(p, CLI_BUFFER_SIZE);
+ PUTWORD(p, CLI_BUFFER_SIZE);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),1024, /* param, length, max */
+ NULL, 0, CLI_BUFFER_SIZE, /* data, length, max */
+ &rparam, &rprcnt, /* return params, return size */
+ &rdata, &rdrcnt /* return data, return size */
+ )) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam,endp);
+ cli->rap_error = res;
+
+ if (cli->rap_error != 0) {
+ DEBUG(4,("NetwkstaUserLogoff gave error %d\n", cli->rap_error));
+ }
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+ return (cli->rap_error == 0);
+}
+
+int cli_NetPrintQEnum(struct cli_state *cli,
+ void (*qfn)(const char*,uint16_t,uint16_t,uint16_t,const char*,const char*,const char*,const char*,const char*,uint16_t,uint16_t),
+ void (*jfn)(uint16_t,const char*,const char*,const char*,const char*,uint16_t,uint16_t,const char*,unsigned int,unsigned int,const char*))
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetPrintQEnum_REQ) /* req string */
+ +sizeof(RAP_PRINTQ_INFO_L2) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE /* buffer size */
+ +sizeof(RAP_SMB_PRINT_JOB_L1)]; /* more ret data */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0',sizeof(param));
+ p = make_header(param, RAP_WPrintQEnum,
+ RAP_NetPrintQEnum_REQ, RAP_PRINTQ_INFO_L2);
+ PUTWORD(p,2); /* Info level 2 */
+ PUTWORD(p,0xFFE0); /* Return buffer size */
+ PUTSTRING(p, RAP_SMB_PRINT_JOB_L1, 0);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),1024,
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+ if (res != 0) {
+ DEBUG(1,("NetPrintQEnum gave error %d\n", res));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetPrintQEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *endp = rparam + rprcnt;
+ int i, converter = 0, count = 0;
+
+ p = rparam + WORDSIZE;
+ GETWORD(p, converter, endp);
+ GETWORD(p, count, endp);
+
+ p = rdata;
+ endp = rdata + rdrcnt;
+ for (i=0;i<count && p < endp;i++) {
+ char qname[RAP_SHARENAME_LEN];
+ char *sep_file, *print_proc, *dest, *parms, *comment;
+ uint16_t jobcount = 0, priority = 0;
+ uint16_t start_time = 0, until_time = 0, status = 0;
+
+ p += rap_getstringf(p,
+ qname,
+ RAP_SHARENAME_LEN,
+ RAP_SHARENAME_LEN,
+ endp);
+ p++; /* pad */
+ GETWORD(p, priority, endp);
+ GETWORD(p, start_time, endp);
+ GETWORD(p, until_time, endp);
+ p += rap_getstringp(frame,
+ p,
+ &sep_file,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &print_proc,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &dest,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &parms,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &comment,
+ rdata,
+ converter,
+ endp);
+ GETWORD(p, status, endp);
+ GETWORD(p, jobcount, endp);
+
+ if (sep_file && print_proc && dest && parms &&
+ comment) {
+ qfn(qname, priority, start_time, until_time, sep_file, print_proc,
+ dest, parms, comment, status, jobcount);
+ }
+
+ if (jobcount) {
+ int j;
+ for (j=0;j<jobcount;j++) {
+ uint16_t jid = 0, pos = 0, fsstatus = 0;
+ char ownername[RAP_USERNAME_LEN];
+ char notifyname[RAP_MACHNAME_LEN];
+ char datatype[RAP_DATATYPE_LEN];
+ char *jparms, *jstatus, *jcomment;
+ unsigned int submitted = 0, jsize = 0;
+
+ GETWORD(p, jid, endp);
+ p += rap_getstringf(p,
+ ownername,
+ RAP_USERNAME_LEN,
+ RAP_USERNAME_LEN,
+ endp);
+ p++; /* pad byte */
+ p += rap_getstringf(p,
+ notifyname,
+ RAP_MACHNAME_LEN,
+ RAP_MACHNAME_LEN,
+ endp);
+ p += rap_getstringf(p,
+ datatype,
+ RAP_DATATYPE_LEN,
+ RAP_DATATYPE_LEN,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &jparms,
+ rdata,
+ converter,
+ endp);
+ GETWORD(p, pos, endp);
+ GETWORD(p, fsstatus, endp);
+ p += rap_getstringp(frame,
+ p,
+ &jstatus,
+ rdata,
+ converter,
+ endp);
+ GETDWORD(p, submitted, endp);
+ GETDWORD(p, jsize, endp);
+ p += rap_getstringp(frame,
+ p,
+ &jcomment,
+ rdata,
+ converter,
+ endp);
+
+ if (jparms && jstatus && jcomment) {
+ jfn(jid, ownername, notifyname, datatype, jparms, pos, fsstatus,
+ jstatus, submitted, jsize, jcomment);
+ }
+ }
+ }
+ }
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetPrintQEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+int cli_NetPrintQGetInfo(struct cli_state *cli, const char *printer,
+ void (*qfn)(const char*,uint16_t,uint16_t,uint16_t,const char*,const char*,const char*,const char*,const char*,uint16_t,uint16_t),
+ void (*jfn)(uint16_t,const char*,const char*,const char*,const char*,uint16_t,uint16_t,const char*,unsigned int,unsigned int,const char*))
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetPrintQGetInfo_REQ) /* req string */
+ +sizeof(RAP_PRINTQ_INFO_L2) /* return string */
+ +RAP_SHARENAME_LEN /* printer name */
+ +WORDSIZE /* info level */
+ +WORDSIZE /* buffer size */
+ +sizeof(RAP_SMB_PRINT_JOB_L1)]; /* more ret data */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0',sizeof(param));
+ p = make_header(param, RAP_WPrintQGetInfo,
+ RAP_NetPrintQGetInfo_REQ, RAP_PRINTQ_INFO_L2);
+ PUTSTRING(p, printer, RAP_SHARENAME_LEN-1);
+ PUTWORD(p, 2); /* Info level 2 */
+ PUTWORD(p,0xFFE0); /* Return buffer size */
+ PUTSTRING(p, RAP_SMB_PRINT_JOB_L1, 0);
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),1024,
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+ if (res != 0) {
+ DEBUG(1,("NetPrintQGetInfo gave error %d\n", res));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetPrintQGetInfo no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *endp = rparam + rprcnt;
+ int rsize = 0, converter = 0;
+ char qname[RAP_SHARENAME_LEN];
+ char *sep_file, *print_proc, *dest, *parms, *comment;
+ uint16_t jobcount = 0, priority = 0;
+ uint16_t start_time = 0, until_time = 0, status = 0;
+
+ p = rparam + WORDSIZE;
+ GETWORD(p, converter, endp);
+ GETWORD(p, rsize, endp);
+
+ p = rdata;
+ endp = rdata + rdrcnt;
+ p += rap_getstringf(p,
+ qname,
+ RAP_SHARENAME_LEN,
+ RAP_SHARENAME_LEN,
+ endp);
+ p++; /* pad */
+ GETWORD(p, priority, endp);
+ GETWORD(p, start_time, endp);
+ GETWORD(p, until_time, endp);
+ p += rap_getstringp(frame,
+ p,
+ &sep_file,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &print_proc,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &dest,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &parms,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &comment,
+ rdata,
+ converter,
+ endp);
+ GETWORD(p, status, endp);
+ GETWORD(p, jobcount, endp);
+
+ if (sep_file && print_proc && dest &&
+ parms && comment) {
+ qfn(qname, priority, start_time, until_time, sep_file, print_proc,
+ dest, parms, comment, status, jobcount);
+ }
+ if (jobcount) {
+ int j;
+ for (j=0;(j<jobcount)&&(PTR_DIFF(p,rdata)< rsize)&&
+ p<endp;j++) {
+ uint16_t jid = 0, pos = 0, fsstatus = 0;
+ char ownername[RAP_USERNAME_LEN];
+ char notifyname[RAP_MACHNAME_LEN];
+ char datatype[RAP_DATATYPE_LEN];
+ char *jparms, *jstatus, *jcomment;
+ unsigned int submitted = 0, jsize = 0;
+
+ GETWORD(p, jid, endp);
+ p += rap_getstringf(p,
+ ownername,
+ RAP_USERNAME_LEN,
+ RAP_USERNAME_LEN,
+ endp);
+ p++; /* pad byte */
+ p += rap_getstringf(p,
+ notifyname,
+ RAP_MACHNAME_LEN,
+ RAP_MACHNAME_LEN,
+ endp);
+ p += rap_getstringf(p,
+ datatype,
+ RAP_DATATYPE_LEN,
+ RAP_DATATYPE_LEN,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &jparms,
+ rdata,
+ converter,
+ endp);
+ GETWORD(p, pos,endp);
+ GETWORD(p, fsstatus,endp);
+ p += rap_getstringp(frame,
+ p,
+ &jstatus,
+ rdata,
+ converter,
+ endp);
+ GETDWORD(p, submitted,endp);
+ GETDWORD(p, jsize,endp);
+ p += rap_getstringp(frame,
+ p,
+ &jcomment,
+ rdata,
+ converter,
+ endp);
+
+ if (jparms && jstatus && jcomment) {
+ jfn(jid, ownername, notifyname, datatype, jparms, pos, fsstatus,
+ jstatus, submitted, jsize, jcomment);
+ }
+ }
+ }
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetPrintQGetInfo res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetServiceEnum - list running services on a different host.
+****************************************************************************/
+
+int cli_RNetServiceEnum(struct cli_state *cli, void (*fn)(const char *, const char *, void *), void *state)
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetServiceEnum_REQ) /* parm string */
+ +sizeof(RAP_SERVICE_INFO_L2) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WServiceEnum,
+ RAP_NetServiceEnum_REQ, RAP_SERVICE_INFO_L2);
+ PUTWORD(p,2); /* Info level 2 */
+ PUTWORD(p,0xFFE0); /* Return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),8,
+ NULL, 0, 0xFFE0 /* data area size */,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+ if(cli->rap_error == 234) {
+ DEBUG(1,("Not all service names were returned (such as those longer than 15 characters)\n"));
+ } else if (cli->rap_error != 0) {
+ DEBUG(1,("NetServiceEnum gave error %d\n", cli->rap_error));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetServiceEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ char *endp = rparam + rprcnt;
+ int i, count = 0;
+
+ p = rparam + WORDSIZE + WORDSIZE; /* skip result and converter */
+ GETWORD(p, count,endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata;i<count && p < endp;i++) {
+ char comment[RAP_SRVCCMNT_LEN];
+ char servicename[RAP_SRVCNAME_LEN];
+
+ p += rap_getstringf(p,
+ servicename,
+ RAP_SRVCNAME_LEN,
+ RAP_SRVCNAME_LEN,
+ endp);
+ p+=8; /* pass status words */
+ p += rap_getstringf(p,
+ comment,
+ RAP_SRVCCMNT_LEN,
+ RAP_SRVCCMNT_LEN,
+ endp);
+
+ if (servicename[0]) {
+ fn(servicename, comment, cli); /* BB add status too */
+ }
+ }
+ } else {
+ DEBUG(4,("NetServiceEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetSessionEnum - list workstations with sessions to an SMB server.
+****************************************************************************/
+
+int cli_NetSessionEnum(struct cli_state *cli, void (*fn)(char *, char *, uint16_t, uint16_t, uint16_t, unsigned int, unsigned int, unsigned int, char *))
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetSessionEnum_REQ) /* parm string */
+ +sizeof(RAP_SESSION_INFO_L2) /* return string */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WsessionEnum,
+ RAP_NetSessionEnum_REQ, RAP_SESSION_INFO_L2);
+ PUTWORD(p,2); /* Info level 2 */
+ PUTWORD(p,0xFF); /* Return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),8,
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+ if (res != 0) {
+ DEBUG(1,("NetSessionEnum gave error %d\n", res));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetSessionEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *endp = rparam + rprcnt;
+ int i, converter = 0, count = 0;
+
+ p = rparam + WORDSIZE;
+ GETWORD(p, converter, endp);
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata;i<count && p < endp;i++) {
+ char *wsname, *username, *clitype_name;
+ uint16_t num_conns = 0, num_opens = 0, num_users = 0;
+ unsigned int sess_time = 0, idle_time = 0, user_flags = 0;
+
+ p += rap_getstringp(frame,
+ p,
+ &wsname,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &username,
+ rdata,
+ converter,
+ endp);
+ GETWORD(p, num_conns, endp);
+ GETWORD(p, num_opens, endp);
+ GETWORD(p, num_users, endp);
+ GETDWORD(p, sess_time, endp);
+ GETDWORD(p, idle_time, endp);
+ GETDWORD(p, user_flags, endp);
+ p += rap_getstringp(frame,
+ p,
+ &clitype_name,
+ rdata,
+ converter,
+ endp);
+
+ if (wsname && username && clitype_name) {
+ fn(wsname, username, num_conns, num_opens, num_users, sess_time,
+ idle_time, user_flags, clitype_name);
+ }
+ }
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetSessionEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetSessionGetInfo - get information about other session to an SMB server.
+****************************************************************************/
+
+int cli_NetSessionGetInfo(struct cli_state *cli, const char *workstation,
+ void (*fn)(const char *, const char *, uint16_t, uint16_t, uint16_t, unsigned int, unsigned int, unsigned int, const char *))
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetSessionGetInfo_REQ) /* req string */
+ +sizeof(RAP_SESSION_INFO_L2) /* return string */
+ +RAP_MACHNAME_LEN /* wksta name */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ char *endp;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WsessionGetInfo,
+ RAP_NetSessionGetInfo_REQ, RAP_SESSION_INFO_L2);
+ PUTSTRING(p, workstation, RAP_MACHNAME_LEN-1);
+ PUTWORD(p,2); /* Info level 2 */
+ PUTWORD(p,0xFF); /* Return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),PTR_DIFF(p,param),
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+ if (cli->rap_error != 0) {
+ DEBUG(1,("NetSessionGetInfo gave error %d\n", cli->rap_error));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetSessionGetInfo no data returned\n"));
+ goto out;
+ }
+
+ endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+
+ if (res == 0 || res == ERRmoredata) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ int converter = 0;
+ char *wsname, *username, *clitype_name;
+ uint16_t num_conns = 0, num_opens = 0, num_users = 0;
+ unsigned int sess_time = 0, idle_time = 0, user_flags = 0;
+
+ p = rparam + WORDSIZE;
+ GETWORD(p, converter,endp);
+
+ p = rdata;
+ endp = rdata + rdrcnt;
+ p += rap_getstringp(frame,
+ p,
+ &wsname,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &username,
+ rdata,
+ converter,
+ endp);
+ GETWORD(p, num_conns, endp);
+ GETWORD(p, num_opens, endp);
+ GETWORD(p, num_users, endp);
+ GETDWORD(p, sess_time, endp);
+ GETDWORD(p, idle_time, endp);
+ GETDWORD(p, user_flags, endp);
+ rap_getstringp(frame,
+ p,
+ &clitype_name,
+ rdata,
+ converter,
+ endp);
+
+ if (wsname && username && clitype_name) {
+ fn(wsname, username, num_conns, num_opens, num_users, sess_time,
+ idle_time, user_flags, clitype_name);
+ }
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetSessionGetInfo res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+/****************************************************************************
+ Call a NetSessionDel - close a session to an SMB server.
+****************************************************************************/
+
+int cli_NetSessionDel(struct cli_state *cli, const char *workstation)
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetSessionDel_REQ) /* req string */
+ +1 /* no return string */
+ +RAP_MACHNAME_LEN /* workstation name */
+ +WORDSIZE]; /* reserved (0) */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WsessionDel, RAP_NetSessionDel_REQ, NULL);
+ PUTSTRING(p, workstation, RAP_MACHNAME_LEN-1);
+ PUTWORD(p,0); /* reserved word of 0 */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param), 1024, /* Param, length, maxlen */
+ NULL, 0, 200, /* data, length, maxlen */
+ &rparam, &rprcnt, /* return params, length */
+ &rdata, &rdrcnt)) /* return data, length */
+ {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+
+ if (res == 0) {
+ /* nothing to do */
+ } else {
+ DEBUG(4,("NetFileClose2 res=%d\n", res));
+ }
+ } else {
+ res = -1;
+ DEBUG(4,("NetFileClose2 failed\n"));
+ }
+
+ SAFE_FREE(rparam);
+ SAFE_FREE(rdata);
+
+ return res;
+}
+
+int cli_NetConnectionEnum(struct cli_state *cli, const char *qualifier,
+ void (*fn)(uint16_t conid, uint16_t contype,
+ uint16_t numopens, uint16_t numusers,
+ uint32_t contime, const char *username,
+ const char *netname))
+{
+ char param[WORDSIZE /* api number */
+ +sizeof(RAP_NetConnectionEnum_REQ) /* req string */
+ +sizeof(RAP_CONNECTION_INFO_L1) /* return string */
+ +RAP_MACHNAME_LEN /* wksta name */
+ +WORDSIZE /* info level */
+ +WORDSIZE]; /* buffer size */
+ char *p;
+ char *rparam = NULL;
+ char *rdata = NULL;
+ unsigned int rprcnt, rdrcnt;
+ int res = -1;
+
+ memset(param, '\0', sizeof(param));
+ p = make_header(param, RAP_WconnectionEnum,
+ RAP_NetConnectionEnum_REQ, RAP_CONNECTION_INFO_L1);
+ PUTSTRING(p, qualifier, RAP_MACHNAME_LEN-1);/* Workstation name */
+ PUTWORD(p,1); /* Info level 1 */
+ PUTWORD(p,0xFFE0); /* Return buffer size */
+
+ if (cli_api(cli,
+ param, PTR_DIFF(p,param),PTR_DIFF(p,param),
+ NULL, 0, CLI_BUFFER_SIZE,
+ &rparam, &rprcnt,
+ &rdata, &rdrcnt)) {
+ char *endp = rparam + rprcnt;
+ res = GETRES(rparam, endp);
+ cli->rap_error = res;
+ if (res != 0) {
+ DEBUG(1,("NetConnectionEnum gave error %d\n", res));
+ }
+ }
+
+ if (!rdata) {
+ DEBUG(4,("NetConnectionEnum no data returned\n"));
+ goto out;
+ }
+
+ if (res == 0 || res == ERRmoredata) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *endp = rparam + rprcnt;
+ int i, converter = 0, count = 0;
+
+ p = rparam + WORDSIZE;
+ GETWORD(p, converter, endp);
+ GETWORD(p, count, endp);
+
+ endp = rdata + rdrcnt;
+ for (i=0,p=rdata;i<count && p < endp;i++) {
+ char *netname, *username;
+ uint16_t conn_id = 0, conn_type = 0, num_opens = 0, num_users = 0;
+ unsigned int conn_time = 0;
+
+ GETWORD(p,conn_id, endp);
+ GETWORD(p,conn_type, endp);
+ GETWORD(p,num_opens, endp);
+ GETWORD(p,num_users, endp);
+ GETDWORD(p,conn_time, endp);
+ p += rap_getstringp(frame,
+ p,
+ &username,
+ rdata,
+ converter,
+ endp);
+ p += rap_getstringp(frame,
+ p,
+ &netname,
+ rdata,
+ converter,
+ endp);
+
+ if (username && netname) {
+ fn(conn_id, conn_type, num_opens, num_users, conn_time,
+ username, netname);
+ }
+ }
+ TALLOC_FREE(frame);
+ } else {
+ DEBUG(4,("NetConnectionEnum res=%d\n", res));
+ }
+
+ out:
+
+ SAFE_FREE(rdata);
+ SAFE_FREE(rparam);
+ return res;
+}
diff --git a/source3/utils/clirap2.h b/source3/utils/clirap2.h
new file mode 100644
index 0000000..275d491
--- /dev/null
+++ b/source3/utils/clirap2.h
@@ -0,0 +1,80 @@
+/*
+ Samba Unix/Linux SMB client library
+ More client RAP (SMB Remote Procedure Calls) functions
+ Copyright (C) 2001 Steve French (sfrench@us.ibm.com)
+ Copyright (C) 2001 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2007 Jeremy Allison. jra@samba.org
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Gerald (Jerry) Carter 2004
+ Copyright (C) James Peach 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __UTILS_CLIRAP2_H__
+#define __UTILS_CLIRAP2_H__
+
+#include "../libsmb/clirap.h"
+
+struct rap_group_info_1;
+struct rap_user_info_1;
+struct rap_share_info_2;
+
+int cli_NetGroupDelete(struct cli_state *cli, const char *group_name);
+int cli_NetGroupAdd(struct cli_state *cli, struct rap_group_info_1 *grinfo);
+int cli_RNetGroupEnum(struct cli_state *cli, void (*fn)(const char *, const char *, void *), void *state);
+int cli_RNetGroupEnum0(struct cli_state *cli,
+ void (*fn)(const char *, void *),
+ void *state);
+int cli_NetGroupDelUser(struct cli_state * cli, const char *group_name, const char *user_name);
+int cli_NetGroupAddUser(struct cli_state * cli, const char *group_name, const char *user_name);
+int cli_NetGroupGetUsers(struct cli_state * cli, const char *group_name, void (*fn)(const char *, void *), void *state );
+int cli_NetUserGetGroups(struct cli_state * cli, const char *user_name, void (*fn)(const char *, void *), void *state );
+int cli_NetUserDelete(struct cli_state *cli, const char * user_name );
+int cli_NetUserAdd(struct cli_state *cli, struct rap_user_info_1 * userinfo );
+int cli_RNetUserEnum(struct cli_state *cli, void (*fn)(const char *, const char *, const char *, const char *, void *), void *state);
+int cli_RNetUserEnum0(struct cli_state *cli,
+ void (*fn)(const char *, void *),
+ void *state);
+int cli_NetFileClose(struct cli_state *cli, uint32_t file_id );
+int cli_NetFileGetInfo(struct cli_state *cli, uint32_t file_id, void (*fn)(const char *, const char *, uint16_t, uint16_t, uint32_t));
+int cli_NetFileEnum(struct cli_state *cli, const char * user,
+ const char * base_path,
+ void (*fn)(const char *, const char *, uint16_t, uint16_t,
+ uint32_t));
+int cli_NetShareAdd(struct cli_state *cli, struct rap_share_info_2 * sinfo );
+int cli_NetShareDelete(struct cli_state *cli, const char * share_name );
+bool cli_get_pdc_name(struct cli_state *cli, const char *workgroup, char **pdc_name);
+bool cli_get_server_name(TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ char **servername);
+bool cli_ns_check_server_type(struct cli_state *cli, char *workgroup, uint32_t stype);
+bool cli_NetWkstaUserLogoff(struct cli_state *cli, const char *user, const char *workstation);
+int cli_NetPrintQEnum(struct cli_state *cli,
+ void (*qfn)(const char*,uint16_t,uint16_t,uint16_t,const char*,const char*,const char*,const char*,const char*,uint16_t,uint16_t),
+ void (*jfn)(uint16_t,const char*,const char*,const char*,const char*,uint16_t,uint16_t,const char*,unsigned int,unsigned int,const char*));
+int cli_NetPrintQGetInfo(struct cli_state *cli, const char *printer,
+ void (*qfn)(const char*,uint16_t,uint16_t,uint16_t,const char*,const char*,const char*,const char*,const char*,uint16_t,uint16_t),
+ void (*jfn)(uint16_t,const char*,const char*,const char*,const char*,uint16_t,uint16_t,const char*,unsigned int,unsigned int,const char*));
+int cli_RNetServiceEnum(struct cli_state *cli, void (*fn)(const char *, const char *, void *), void *state);
+int cli_NetSessionEnum(struct cli_state *cli, void (*fn)(char *, char *, uint16_t, uint16_t, uint16_t, unsigned int, unsigned int, unsigned int, char *));
+int cli_NetSessionGetInfo(struct cli_state *cli, const char *workstation,
+ void (*fn)(const char *, const char *, uint16_t, uint16_t, uint16_t, unsigned int, unsigned int, unsigned int, const char *));
+int cli_NetSessionDel(struct cli_state *cli, const char *workstation);
+int cli_NetConnectionEnum(struct cli_state *cli, const char *qualifier,
+ void (*fn)(uint16_t conid, uint16_t contype,
+ uint16_t numopens, uint16_t numusers,
+ uint32_t contime, const char *username,
+ const char *netname));
+
+#endif /* __UTILS_CLIRAP2_H__ */
diff --git a/source3/utils/conn_tdb.c b/source3/utils/conn_tdb.c
new file mode 100644
index 0000000..3724bd4
--- /dev/null
+++ b/source3/utils/conn_tdb.c
@@ -0,0 +1,173 @@
+/*
+ Unix SMB/CIFS implementation.
+ Low-level connections.tdb access functions
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "smbd/globals.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "messages.h"
+#include "conn_tdb.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+struct connections_forall_state {
+ struct db_context *session_by_pid;
+ int (*fn)(const struct connections_data *data,
+ void *private_data);
+ void *private_data;
+ int count;
+};
+
+struct connections_forall_session {
+ uid_t uid;
+ gid_t gid;
+ fstring machine;
+ fstring addr;
+ uint16_t cipher;
+ uint16_t dialect;
+ uint16_t signing;
+ uint8_t signing_flags;
+};
+
+static int collect_sessions_fn(struct smbXsrv_session_global0 *global,
+ void *connections_forall_state)
+{
+ NTSTATUS status;
+ struct connections_forall_state *state =
+ (struct connections_forall_state*)connections_forall_state;
+
+ uint32_t id = global->session_global_id;
+ struct connections_forall_session sess;
+
+ if (global->auth_session_info == NULL) {
+ sess.uid = -1;
+ sess.gid = -1;
+ } else {
+ sess.uid = global->auth_session_info->unix_token->uid;
+ sess.gid = global->auth_session_info->unix_token->gid;
+ }
+ fstrcpy(sess.machine, global->channels[0].remote_name);
+ fstrcpy(sess.addr, global->channels[0].remote_address);
+ sess.cipher = global->channels[0].encryption_cipher;
+ sess.signing = global->channels[0].signing_algo;
+ sess.dialect = global->connection_dialect;
+ sess.signing_flags = global->signing_flags;
+
+ status = dbwrap_store(state->session_by_pid,
+ make_tdb_data((void*)&id, sizeof(id)),
+ make_tdb_data((void*)&sess, sizeof(sess)),
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to store record: %s\n", nt_errstr(status)));
+ }
+ return 0;
+}
+
+static int traverse_tcon_fn(struct smbXsrv_tcon_global0 *global,
+ void *connections_forall_state)
+{
+ NTSTATUS status;
+ struct connections_forall_state *state =
+ (struct connections_forall_state*)connections_forall_state;
+
+ struct connections_data data;
+
+ uint32_t sess_id = global->session_global_id;
+ struct connections_forall_session sess = {
+ .uid = -1,
+ .gid = -1,
+ };
+
+ TDB_DATA val = tdb_null;
+
+ /*
+ * Note: that share_name is defined as array without a pointer.
+ * that's why it's always a valid pointer here.
+ */
+ if (strlen(global->share_name) == 0) {
+ /*
+ * when a smbXsrv_tcon is created it's created
+ * with empty share_name first in order to allocate
+ * an id, before filling in the details.
+ */
+ return 0;
+ }
+
+ status = dbwrap_fetch(state->session_by_pid, state,
+ make_tdb_data((void*)&sess_id, sizeof(sess_id)),
+ &val);
+ if (NT_STATUS_IS_OK(status)) {
+ memcpy((uint8_t *)&sess, val.dptr, val.dsize);
+ }
+
+ ZERO_STRUCT(data);
+
+ data.pid = global->server_id;
+ data.cnum = global->tcon_global_id;
+ data.sess_id = sess_id;
+ fstrcpy(data.servicename, global->share_name);
+ data.uid = sess.uid;
+ data.gid = sess.gid;
+ fstrcpy(data.addr, sess.addr);
+ fstrcpy(data.machine, sess.machine);
+ data.start = global->creation_time;
+ data.encryption_flags = global->encryption_flags;
+ data.cipher = sess.cipher;
+ data.dialect = sess.dialect;
+ data.signing = sess.signing;
+ data.signing_flags = global->signing_flags;
+
+ state->count++;
+
+ return state->fn(&data, state->private_data);
+}
+
+int connections_forall_read(int (*fn)(const struct connections_data *data,
+ void *private_data),
+ void *private_data)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct connections_forall_state *state =
+ talloc_zero(talloc_tos(), struct connections_forall_state);
+ NTSTATUS status;
+ int ret = -1;
+
+ state->session_by_pid = db_open_rbt(state);
+ state->fn = fn;
+ state->private_data = private_data;
+ status = smbXsrv_session_global_traverse(collect_sessions_fn, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to traverse sessions: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = smbXsrv_tcon_global_traverse(traverse_tcon_fn, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to traverse tree connects: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ ret = state->count;
+done:
+ talloc_free(frame);
+ return ret;
+}
diff --git a/source3/utils/conn_tdb.h b/source3/utils/conn_tdb.h
new file mode 100644
index 0000000..2a6e04e
--- /dev/null
+++ b/source3/utils/conn_tdb.h
@@ -0,0 +1,45 @@
+/*
+ Unix SMB/CIFS implementation.
+ Low-level connections.tdb access functions
+ Copyright (C) Volker Lendecke 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* key and data in the connections database - used in smbstatus and smbd */
+
+#include "source3/include/includes.h"
+
+struct connections_data {
+ struct server_id pid;
+ int cnum;
+ uint32_t sess_id;
+ uid_t uid;
+ gid_t gid;
+ fstring servicename;
+ fstring addr;
+ fstring machine;
+ NTTIME start;
+ uint8_t encryption_flags;
+ uint16_t cipher;
+ uint16_t dialect;
+ uint8_t signing_flags;
+ uint16_t signing;
+};
+
+/* The following definitions come from lib/conn_tdb.c */
+
+int connections_forall_read(int (*fn)(const struct connections_data *data,
+ void *private_data),
+ void *private_data);
diff --git a/source3/utils/dbwrap_tool.c b/source3/utils/dbwrap_tool.c
new file mode 100644
index 0000000..eb97d64
--- /dev/null
+++ b/source3/utils/dbwrap_tool.c
@@ -0,0 +1,593 @@
+/*
+ Samba Unix/Linux CIFS implementation
+
+ low level TDB/CTDB tool using the dbwrap interface
+
+ Copyright (C) 2009 Michael Adam <obnox@samba.org>
+ Copyright (C) 2011 Bjoern Baumbach <bb@sernet.de>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/cmdline/cmdline.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "cmdline_contexts.h"
+#include "lib/param/param.h"
+
+enum dbwrap_op { OP_FETCH, OP_STORE, OP_DELETE, OP_ERASE, OP_LISTKEYS,
+ OP_EXISTS };
+
+enum dbwrap_type { TYPE_INT32, TYPE_UINT32, TYPE_STRING, TYPE_HEX, TYPE_NONE };
+
+static int dbwrap_tool_fetch_int32(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ int32_t value;
+ NTSTATUS status;
+
+ status = dbwrap_fetch_int32_bystring(db, keyname, &value);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Error fetching int32 from key '%s': %s\n",
+ keyname, nt_errstr(status));
+ return -1;
+ }
+ d_printf("%d\n", value);
+
+ return 0;
+}
+
+static int dbwrap_tool_fetch_uint32(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ uint32_t value;
+ NTSTATUS ret;
+
+ ret = dbwrap_fetch_uint32_bystring(db, keyname, &value);
+ if (NT_STATUS_IS_OK(ret)) {
+ d_printf("%u\n", value);
+ return 0;
+ } else {
+ d_fprintf(stderr, "ERROR: could not fetch uint32 key '%s': "
+ "%s\n", nt_errstr(ret), keyname);
+ return -1;
+ }
+}
+
+static int dbwrap_tool_fetch_string(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ TDB_DATA tdbdata;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ int ret;
+
+ status = dbwrap_fetch_bystring(db, tmp_ctx, keyname, &tdbdata);
+ if (NT_STATUS_IS_OK(status)) {
+ d_printf("%-*.*s\n", (int)tdbdata.dsize, (int)tdbdata.dsize,
+ tdbdata.dptr);
+ ret = 0;
+ } else {
+ d_fprintf(stderr, "ERROR: could not fetch string key '%s': "
+ "%s\n", nt_errstr(status), keyname);
+ ret = -1;
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int dbwrap_tool_fetch_hex(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ TDB_DATA tdbdata;
+ DATA_BLOB datablob;
+ NTSTATUS status;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ char *hex_string;
+ int ret;
+
+ status = dbwrap_fetch_bystring(db, tmp_ctx, keyname, &tdbdata);
+ if (NT_STATUS_IS_OK(status)) {
+ datablob.data = tdbdata.dptr;
+ datablob.length = tdbdata.dsize;
+
+ hex_string = data_blob_hex_string_upper(tmp_ctx, &datablob);
+ if (hex_string == NULL) {
+ d_fprintf(stderr, "ERROR: could not get hex string "
+ "from data blob\n");
+ ret = -1;
+ } else {
+ d_printf("%s\n", hex_string);
+ ret = 0;
+ }
+ } else {
+ d_fprintf(stderr, "ERROR: could not fetch hex key '%s': "
+ "%s\n", nt_errstr(status), keyname);
+ ret = -1;
+ }
+
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static int dbwrap_tool_store_int32(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ NTSTATUS status;
+ int32_t value = (int32_t)strtol(data, NULL, 10);
+
+ if (dbwrap_is_persistent(db)) {
+ status = dbwrap_trans_store_int32_bystring(db, keyname, value);
+ } else {
+ status = dbwrap_store_int32_bystring(db, keyname, value);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "ERROR: could not store int32 key '%s': %s\n",
+ keyname, nt_errstr(status));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dbwrap_tool_store_uint32(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ NTSTATUS status;
+ uint32_t value = (uint32_t)strtol(data, NULL, 10);
+
+ if (dbwrap_is_persistent(db)) {
+ status = dbwrap_trans_store_uint32_bystring(db, keyname, value);
+ } else {
+ status = dbwrap_store_uint32_bystring(db, keyname, value);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "ERROR: could not store uint32 key '%s': %s\n",
+ keyname, nt_errstr(status));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dbwrap_tool_store_string(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ NTSTATUS status;
+ TDB_DATA tdbdata;
+
+ tdbdata = string_term_tdb_data(data);
+
+ if (dbwrap_is_persistent(db)) {
+ status = dbwrap_trans_store_bystring(db, keyname,
+ tdbdata,
+ TDB_REPLACE);
+ } else {
+ status = dbwrap_store_bystring(db, keyname,
+ tdbdata,
+ TDB_REPLACE);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "ERROR: could not store string key '%s': %s\n",
+ keyname, nt_errstr(status));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dbwrap_tool_store_hex(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ NTSTATUS status;
+ DATA_BLOB datablob;
+ TDB_DATA tdbdata;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ datablob = strhex_to_data_blob(tmp_ctx, data);
+ if(strlen(data) > 0 && datablob.length == 0) {
+ d_fprintf(stderr,
+ "ERROR: could not convert hex string to data blob\n"
+ " Not a valid hex string?\n");
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ tdbdata.dptr = (unsigned char *)datablob.data;
+ tdbdata.dsize = datablob.length;
+
+ if (dbwrap_is_persistent(db)) {
+ status = dbwrap_trans_store_bystring(db, keyname,
+ tdbdata,
+ TDB_REPLACE);
+ } else {
+ status = dbwrap_store_bystring(db, keyname,
+ tdbdata,
+ TDB_REPLACE);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "ERROR: could not store string key '%s': %s\n",
+ keyname, nt_errstr(status));
+ talloc_free(tmp_ctx);
+ return -1;
+ }
+
+ talloc_free(tmp_ctx);
+ return 0;
+}
+
+static int dbwrap_tool_delete(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ NTSTATUS status;
+
+ if (dbwrap_is_persistent(db)) {
+ status = dbwrap_trans_delete_bystring(db, keyname);
+ } else {
+ status = dbwrap_delete_bystring(db, keyname);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "ERROR deleting record %s : %s\n",
+ keyname, nt_errstr(status));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int dbwrap_tool_exists(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ bool result;
+
+ result = dbwrap_exists(db, string_term_tdb_data(keyname));
+
+ if (result) {
+ d_fprintf(stdout, "Key %s exists\n", keyname);
+ } else {
+ d_fprintf(stdout, "Key %s does not exist\n", keyname);
+ }
+
+ return (result)?0:1;
+}
+
+/**
+ * dbwrap_tool_erase: erase the whole data base
+ * the keyname argument is not used.
+ */
+static int dbwrap_tool_erase(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ int ret;
+
+ ret = dbwrap_wipe(db);
+
+ if (ret != 0) {
+ d_fprintf(stderr, "ERROR erasing the database\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int listkey_fn(struct db_record *rec, void *private_data)
+{
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ size_t length = key.dsize;
+ unsigned char *p = (unsigned char *)key.dptr;
+
+ while (length--) {
+ if (isprint(*p) && !strchr("\"\\", *p)) {
+ d_printf("%c", *p);
+ } else {
+ d_printf("\\%02X", *p);
+ }
+ p++;
+ }
+
+ d_printf("\n");
+
+ return 0;
+}
+
+static int dbwrap_tool_listkeys(struct db_context *db,
+ const char *keyname,
+ const char *data)
+{
+ NTSTATUS status;
+
+ status = dbwrap_traverse_read(db, listkey_fn, NULL, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "ERROR listing db keys\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+struct dbwrap_op_dispatch_table {
+ enum dbwrap_op op;
+ enum dbwrap_type type;
+ int (*cmd)(struct db_context *db,
+ const char *keyname,
+ const char *data);
+};
+
+struct dbwrap_op_dispatch_table dispatch_table[] = {
+ { OP_FETCH, TYPE_INT32, dbwrap_tool_fetch_int32 },
+ { OP_FETCH, TYPE_UINT32, dbwrap_tool_fetch_uint32 },
+ { OP_FETCH, TYPE_STRING, dbwrap_tool_fetch_string },
+ { OP_FETCH, TYPE_HEX, dbwrap_tool_fetch_hex },
+ { OP_STORE, TYPE_INT32, dbwrap_tool_store_int32 },
+ { OP_STORE, TYPE_UINT32, dbwrap_tool_store_uint32 },
+ { OP_STORE, TYPE_STRING, dbwrap_tool_store_string },
+ { OP_STORE, TYPE_HEX, dbwrap_tool_store_hex },
+ { OP_DELETE, TYPE_INT32, dbwrap_tool_delete },
+ { OP_ERASE, TYPE_INT32, dbwrap_tool_erase },
+ { OP_LISTKEYS, TYPE_INT32, dbwrap_tool_listkeys },
+ { OP_EXISTS, TYPE_STRING, dbwrap_tool_exists },
+ { 0, 0, NULL },
+};
+
+int main(int argc, const char **argv)
+{
+ struct tevent_context *evt_ctx;
+ struct messaging_context *msg_ctx;
+ struct db_context *db;
+
+ uint16_t count;
+
+ const char *dbname;
+ const char *opname;
+ enum dbwrap_op op;
+ const char *keyname = "";
+ const char *keytype = "int32";
+ enum dbwrap_type type;
+ const char *valuestr = "0";
+ int persistent = 0;
+ int non_persistent = 0;
+ int tdb_flags = TDB_DEFAULT;
+
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+
+ int ret = 1;
+ bool ok;
+
+ struct poptOption popt_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_SAMBA
+ { "non-persistent", 0, POPT_ARG_NONE, &non_persistent, 0,
+ "treat the database as non-persistent "
+ "(CAVEAT: This mode might wipe your database!)",
+ NULL },
+ { "persistent", 0, POPT_ARG_NONE, &persistent, 0,
+ "treat the database as persistent",
+ NULL },
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ int opt;
+ const char **extra_argv;
+ int extra_argc = 0;
+ poptContext pc;
+
+ smb_init_locale();
+
+ setup_logging(argv[0], DEBUG_DEFAULT_STDERR);
+
+ ok = samba_cmdline_init(mem_ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ popt_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (!ok) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ default:
+ fprintf(stderr, "Invalid option %s: %s\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ goto done;
+ }
+ }
+
+ /* setup the remaining options for the main program to use */
+ extra_argv = poptGetArgs(pc);
+ if (extra_argv) {
+ extra_argv++;
+ while (extra_argv[extra_argc]) extra_argc++;
+ }
+
+ if ((extra_argc < 2) || (extra_argc > 5)) {
+ d_fprintf(stderr,
+ "USAGE: %s [options] <database> <op> [<key> [<type> "
+ "[<value>]]]\n"
+ " ops: fetch, store, delete, exists, "
+ "erase, listkeys\n"
+ " types: int32, uint32, string, hex\n",
+ argv[0]);
+ goto done;
+ }
+
+ if ((persistent + non_persistent) != 1) {
+ d_fprintf(stderr, "ERROR: you must specify exactly one "
+ "of --persistent and --non-persistent\n");
+ goto done;
+ }
+ if (non_persistent == 1) {
+ tdb_flags |= TDB_CLEAR_IF_FIRST;
+ }
+
+ dbname = extra_argv[0];
+ opname = extra_argv[1];
+
+ if (strcmp(opname, "store") == 0) {
+ if (extra_argc != 5) {
+ d_fprintf(stderr, "ERROR: operation 'store' requires "
+ "value argument\n");
+ goto done;
+ }
+ valuestr = extra_argv[4];
+ keytype = extra_argv[3];
+ keyname = extra_argv[2];
+ op = OP_STORE;
+ } else if (strcmp(opname, "fetch") == 0) {
+ if (extra_argc != 4) {
+ d_fprintf(stderr, "ERROR: operation 'fetch' requires "
+ "type but not value argument\n");
+ goto done;
+ }
+ op = OP_FETCH;
+ keytype = extra_argv[3];
+ keyname = extra_argv[2];
+ } else if (strcmp(opname, "delete") == 0) {
+ if (extra_argc != 3) {
+ d_fprintf(stderr, "ERROR: operation 'delete' does "
+ "not allow type nor value argument\n");
+ goto done;
+ }
+ keyname = extra_argv[2];
+ op = OP_DELETE;
+ } else if (strcmp(opname, "erase") == 0) {
+ if (extra_argc != 2) {
+ d_fprintf(stderr, "ERROR: operation 'erase' does "
+ "not take a key argument\n");
+ goto done;
+ }
+ op = OP_ERASE;
+ } else if (strcmp(opname, "listkeys") == 0) {
+ if (extra_argc != 2) {
+ d_fprintf(stderr, "ERROR: operation 'listkeys' does "
+ "not take a key argument\n");
+ goto done;
+ }
+ op = OP_LISTKEYS;
+ } else if (strcmp(opname, "exists") == 0) {
+ if (extra_argc != 3) {
+ d_fprintf(stderr, "ERROR: operation 'exists' does "
+ "not allow type nor value argument\n");
+ goto done;
+ }
+ keyname = extra_argv[2];
+ op = OP_EXISTS;
+ keytype = "string";
+ } else {
+ d_fprintf(stderr,
+ "ERROR: invalid op '%s' specified\n"
+ " supported ops: fetch, store, delete, exists, "
+ "erase, listkeys\n",
+ opname);
+ goto done;
+ }
+
+ if (strcmp(keytype, "int32") == 0) {
+ type = TYPE_INT32;
+ } else if (strcmp(keytype, "uint32") == 0) {
+ type = TYPE_UINT32;
+ } else if (strcmp(keytype, "string") == 0) {
+ type = TYPE_STRING;
+ } else if (strcmp(keytype, "hex") == 0) {
+ type = TYPE_HEX;
+ } else if (strcmp(keytype, "none") == 0) {
+ type = TYPE_NONE;
+ } else {
+ d_fprintf(stderr, "ERROR: invalid type '%s' specified.\n"
+ " supported types: int32, uint32, "
+ "string, hex, none\n",
+ keytype);
+ goto done;
+ }
+
+ evt_ctx = samba_tevent_context_init(mem_ctx);
+ if (evt_ctx == NULL) {
+ d_fprintf(stderr, "ERROR: could not init event context\n");
+ goto done;
+ }
+
+ msg_ctx = messaging_init(mem_ctx, evt_ctx);
+ if (msg_ctx == NULL) {
+ d_fprintf(stderr, "ERROR: could not init messaging context\n");
+ goto done;
+ }
+
+ switch (op) {
+ case OP_FETCH:
+ case OP_STORE:
+ case OP_DELETE:
+ case OP_ERASE:
+ case OP_LISTKEYS:
+ case OP_EXISTS:
+ db = db_open(mem_ctx, dbname, 0, tdb_flags, O_RDWR | O_CREAT,
+ 0644, DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ d_fprintf(stderr, "ERROR: could not open dbname\n");
+ goto done;
+ }
+ break;
+ default:
+ db = NULL;
+ break;
+ }
+
+ for (count = 0; dispatch_table[count].cmd != NULL; count++) {
+ if ((op == dispatch_table[count].op) &&
+ (type == dispatch_table[count].type))
+ {
+ ret = dispatch_table[count].cmd(db, keyname, valuestr);
+ break;
+ }
+ }
+
+done:
+ poptFreeContext(pc);
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
diff --git a/source3/utils/dbwrap_torture.c b/source3/utils/dbwrap_torture.c
new file mode 100644
index 0000000..ec33853
--- /dev/null
+++ b/source3/utils/dbwrap_torture.c
@@ -0,0 +1,366 @@
+/*
+ Samba Linux/Unix CIFS implementation
+
+ simple tool to test persistent databases
+
+ Copyright (C) Michael Adam 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/cmdline/cmdline.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+#include "lib/param/param.h"
+
+#if 0
+#include "lib/events/events.h"
+#include "system/filesys.h"
+#include "popt.h"
+#include "cmdline.h"
+
+#include <sys/time.h>
+#include <time.h>
+#endif
+
+#define DEFAULT_DB_NAME "transaction.tdb"
+
+static int timelimit = 10;
+static int torture_delay = 0;
+static int verbose = 0;
+static int no_trans = 0;
+static const char *db_name = DEFAULT_DB_NAME;
+
+
+static unsigned int pnn;
+
+static TDB_DATA old_data;
+
+static int success = true;
+
+static void print_counters(void)
+{
+ int i;
+ uint32_t *old_counters;
+
+ printf("[%4u] Counters: ", (unsigned int)getpid());
+ old_counters = (uint32_t *)old_data.dptr;
+ for (i=0; i < old_data.dsize/sizeof(uint32_t); i++) {
+ printf("%6u ", old_counters[i]);
+ }
+ printf("\n");
+}
+
+static void each_second(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval t,
+ void *private_data)
+{
+ struct db_context *db = talloc_get_type(private_data, struct db_context);
+
+ print_counters();
+
+ tevent_add_timer(ev, db, timeval_current_ofs(1, 0), each_second, db);
+}
+
+static bool check_counters(struct db_context *db, TDB_DATA data)
+{
+ int i;
+ uint32_t *counters, *old_counters;
+
+ counters = (uint32_t *)data.dptr;
+ old_counters = (uint32_t *)old_data.dptr;
+
+ /* check that all the counters are monotonic increasing */
+ for (i=0; i < old_data.dsize/sizeof(uint32_t); i++) {
+ if (counters[i] < old_counters[i]) {
+ printf("[%4u] ERROR: counters has decreased for node %u From %u to %u\n",
+ (unsigned int)getpid(), i, old_counters[i], counters[i]);
+ success = false;
+ return false;
+ }
+ }
+
+ if (old_data.dsize != data.dsize) {
+ old_data.dsize = data.dsize;
+ old_data.dptr = (unsigned char*)talloc_realloc_size(db, old_data.dptr, old_data.dsize);
+ }
+
+ memcpy(old_data.dptr, data.dptr, data.dsize);
+ if (verbose) print_counters();
+
+ return true;
+}
+
+
+static void do_sleep(unsigned int sec)
+{
+ unsigned int i;
+
+ if (sec == 0) {
+ return;
+ }
+
+ for (i=0; i<sec; i++) {
+ if (verbose) printf(".");
+ sleep(1);
+ }
+
+ if (verbose) printf("\n");
+}
+
+static void test_store_records(struct db_context *db, struct tevent_context *ev)
+{
+ TDB_DATA key;
+ uint32_t *counters;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct timeval start;
+
+ key = string_term_tdb_data("testkey");
+
+ start = timeval_current();
+ while ((timelimit == 0) || (timeval_elapsed(&start) < timelimit)) {
+ struct db_record *rec;
+ TDB_DATA data;
+ TDB_DATA value;
+ int ret;
+ NTSTATUS status;
+
+ if (!no_trans) {
+ if (verbose) DEBUG(1, ("starting transaction\n"));
+ ret = dbwrap_transaction_start(db);
+ if (ret != 0) {
+ DEBUG(0, ("Failed to start transaction on node "
+ "%d\n", pnn));
+ goto fail;
+ }
+ if (verbose) DEBUG(1, ("transaction started\n"));
+ do_sleep(torture_delay);
+ }
+
+ if (verbose) DEBUG(1, ("calling fetch_lock\n"));
+ rec = dbwrap_fetch_locked(db, tmp_ctx, key);
+ if (rec == NULL) {
+ DEBUG(0, ("Failed to fetch record\n"));
+ goto fail;
+ }
+ if (verbose) DEBUG(1, ("fetched record ok\n"));
+ do_sleep(torture_delay);
+ value = dbwrap_record_get_value(rec);
+
+ data.dsize = MAX(value.dsize, sizeof(uint32_t) * (pnn+1));
+ data.dptr = (unsigned char *)talloc_zero_size(tmp_ctx,
+ data.dsize);
+ if (data.dptr == NULL) {
+ DEBUG(0, ("Failed to allocate data\n"));
+ goto fail;
+ }
+ memcpy(data.dptr, value.dptr, value.dsize);
+
+ counters = (uint32_t *)data.dptr;
+
+ /* bump our counter */
+ counters[pnn]++;
+
+ if (verbose) DEBUG(1, ("storing data\n"));
+ status = dbwrap_record_store(rec, data, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to store record\n"));
+ if (!no_trans) {
+ ret = dbwrap_transaction_cancel(db);
+ if (ret != 0) {
+ DEBUG(0, ("Error cancelling transaction.\n"));
+ }
+ }
+ goto fail;
+ }
+ talloc_free(rec);
+ if (verbose) DEBUG(1, ("stored data ok\n"));
+ do_sleep(torture_delay);
+
+ if (!no_trans) {
+ if (verbose) DEBUG(1, ("calling transaction_commit\n"));
+ ret = dbwrap_transaction_commit(db);
+ if (ret != 0) {
+ DEBUG(0, ("Failed to commit transaction\n"));
+ goto fail;
+ }
+ if (verbose) DEBUG(1, ("transaction committed\n"));
+ }
+
+ /* store the counters and verify that they are sane */
+ if (verbose || (pnn == 0)) {
+ if (!check_counters(db, data)) {
+ goto fail;
+ }
+ }
+ talloc_free(data.dptr);
+
+ do_sleep(torture_delay);
+ }
+
+ goto done;
+
+fail:
+ success = false;
+
+done:
+ talloc_free(tmp_ctx);
+ return;
+}
+
+/*
+ main program
+*/
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *mem_ctx;
+ struct tevent_context *ev_ctx;
+ struct messaging_context *msg_ctx;
+ struct db_context *db;
+
+ int unsafe_writes = 0;
+ struct poptOption popt_options[] = {
+ POPT_AUTOHELP
+ POPT_COMMON_SAMBA
+ { "timelimit", 't', POPT_ARG_INT, &timelimit, 0, "timelimit", "INTEGER" },
+ { "delay", 'D', POPT_ARG_INT, &torture_delay, 0, "delay (in seconds) between operations", "INTEGER" },
+ { "verbose", 'v', POPT_ARG_NONE, &verbose, 0, "switch on verbose mode", NULL },
+ { "db-name", 'N', POPT_ARG_STRING, &db_name, 0, "name of the test db", "NAME" },
+ { "no-trans", 'n', POPT_ARG_NONE, &no_trans, 0, "use fetch_lock/record store instead of transactions", NULL },
+ { "unsafe-writes", 'u', POPT_ARG_NONE, &unsafe_writes, 0, "do not use tdb transactions when writing", NULL },
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ int opt;
+ const char **extra_argv;
+ int extra_argc = 0;
+ poptContext pc;
+ int tdb_flags;
+ bool ok;
+ int ret = 1;
+ struct loadparm_context *lp_ctx = NULL;
+
+ mem_ctx = talloc_stackframe();
+
+ if (verbose) {
+ setbuf(stdout, (char *)NULL); /* don't buffer */
+ } else {
+ setlinebuf(stdout);
+ }
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(mem_ctx,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ popt_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(mem_ctx);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ default:
+ fprintf(stderr, "Invalid option %s: %s\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ goto done;
+ }
+ }
+
+ /* setup the remaining options for the main program to use */
+ extra_argv = poptGetArgs(pc);
+ if (extra_argv) {
+ extra_argv++;
+ while (extra_argv[extra_argc]) extra_argc++;
+ }
+
+ ev_ctx = samba_tevent_context_init(mem_ctx);
+ if (ev_ctx == NULL) {
+ d_fprintf(stderr, "ERROR: could not init event context\n");
+ goto done;
+ }
+
+ msg_ctx = messaging_init(mem_ctx, ev_ctx);
+ if (msg_ctx == NULL) {
+ d_fprintf(stderr, "ERROR: could not init messaging context\n");
+ goto done;
+ }
+
+ if (unsafe_writes == 1) {
+ tdb_flags = TDB_NOSYNC;
+ } else {
+ tdb_flags = TDB_DEFAULT;
+ }
+
+ if (no_trans) {
+ tdb_flags |= TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH;
+ }
+
+ db = db_open(mem_ctx, db_name, 0, tdb_flags, O_RDWR | O_CREAT, 0644,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+
+ if (db == NULL) {
+ d_fprintf(stderr, "failed to open db '%s': %s\n", db_name,
+ strerror(errno));
+ goto done;
+ }
+
+ if (get_my_vnn() == NONCLUSTER_VNN) {
+ set_my_vnn(0);
+ }
+ pnn = get_my_vnn();
+
+ printf("Starting test on node %u. running for %u seconds. "
+ "sleep delay: %u seconds.\n", pnn, timelimit, torture_delay);
+
+ if (!verbose && (pnn == 0)) {
+ tevent_add_timer(ev_ctx, db, timeval_current_ofs(1, 0), each_second, db);
+ }
+
+ test_store_records(db, ev_ctx);
+
+ if (verbose || (pnn == 0)) {
+ if (success != true) {
+ printf("The test FAILED\n");
+ ret = 2;
+ } else {
+ printf("SUCCESS!\n");
+ ret = 0;
+ }
+ }
+
+done:
+ poptFreeContext(pc);
+ talloc_free(mem_ctx);
+ return ret;
+}
diff --git a/source3/utils/destroy_netlogon_creds_cli.c b/source3/utils/destroy_netlogon_creds_cli.c
new file mode 100644
index 0000000..a2e1952
--- /dev/null
+++ b/source3/utils/destroy_netlogon_creds_cli.c
@@ -0,0 +1,136 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Garble the netlogon_creds_cli key for testing purposes
+ * Copyright (C) Volker Lendecke 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include <talloc.h>
+#include <tevent.h>
+#include "messages.h"
+#include "lib/util/talloc_stack.h"
+#include "lib/param/loadparm.h"
+#include "lib/param/param.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_open.h"
+
+int main(int argc, const char *argv[])
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+ struct loadparm_context *lp_ctx;
+ struct db_context *global_db;
+ struct netlogon_creds_cli_context *ctx;
+ struct netlogon_creds_CredentialState *creds;
+ NTSTATUS status;
+ int ret = 1;
+
+ smb_init_locale();
+
+ if (!lp_load_global(get_dyn_CONFIGFILE())) {
+ fprintf(stderr, "error opening config file %s. Error was %s\n",
+ get_dyn_CONFIGFILE(), strerror(errno));
+ goto done;
+ }
+
+ if (argc != 4) {
+ fprintf(stderr, "usage: %s cli_computer domain dc\n", argv[0]);
+ goto done;
+ }
+
+ lp_ctx = loadparm_init_s3(mem_ctx, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ fprintf(stderr, "loadparm_init_s3 failed\n");
+ goto done;
+ }
+
+ ev = samba_tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ fprintf(stderr, "samba3_tevent_context_init failed\n");
+ goto done;
+ }
+ msg_ctx = messaging_init(mem_ctx, ev);
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "messaging_init failed\n");
+ goto done;
+ }
+
+ global_db = db_open(
+ mem_ctx,
+ lpcfg_private_db_path(mem_ctx, lp_ctx, "netlogon_creds_cli"),
+ 0, TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH,
+ O_RDWR|O_CREAT, 0600, DBWRAP_LOCK_ORDER_2,
+ DBWRAP_FLAG_OPTIMIZE_READONLY_ACCESS);
+ if (global_db == NULL) {
+ fprintf(stderr, "db_open failed\n");
+ goto done;
+ }
+
+ status = netlogon_creds_cli_set_global_db(lp_ctx, &global_db);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "netlogon_creds_cli_set_global_db failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = netlogon_creds_cli_context_global(
+ lp_ctx,
+ msg_ctx,
+ talloc_asprintf(mem_ctx, "%s$", argv[1]),
+ SEC_CHAN_WKSTA,
+ argv[3],
+ argv[2],
+ "",
+ mem_ctx,
+ &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "netlogon_creds_cli_context_global failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = netlogon_creds_cli_lock(ctx,
+ mem_ctx,
+ &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "netlogon_creds_cli_get failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ creds->session_key[0]++;
+
+ status = netlogon_creds_cli_store(ctx, creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "netlogon_creds_cli_store failed: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ TALLOC_FREE(creds);
+
+ ret = 0;
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
diff --git a/source3/utils/eventlogadm.c b/source3/utils/eventlogadm.c
new file mode 100644
index 0000000..f831927
--- /dev/null
+++ b/source3/utils/eventlogadm.c
@@ -0,0 +1,506 @@
+
+/*
+ * Samba Unix/Linux SMB client utility
+ * Write Eventlog records to a tdb, perform other eventlog related functions
+ *
+ *
+ * Copyright (C) Brian Moran 2005.
+ * Copyright (C) Guenther Deschner 2009.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "lib/eventlog/eventlog.h"
+#include "registry.h"
+#include "registry/reg_api.h"
+#include "registry/reg_init_basic.h"
+#include "registry/reg_util_token.h"
+#include "registry/reg_backend_db.h"
+#include "../libcli/registry/util_reg.h"
+#include "cmdline_contexts.h"
+#include "lib/util/string_wrappers.h"
+
+extern int optind;
+extern char *optarg;
+
+int opt_debug = 0;
+
+static void usage( char *s )
+{
+ printf( "\nUsage: %s [OPTION]\n\n", s );
+ printf( " -o write <Eventlog Name> \t\t\t\t\tWrites records to eventlog from STDIN\n" );
+ printf( " -o addsource <EventlogName> <sourcename> <msgfileDLLname> \tAdds the specified source & DLL eventlog registry entry\n" );
+ printf( " -o dump <Eventlog Name> <starting_record>\t\t\t\t\tDump stored eventlog entries on STDOUT\n" );
+ printf( "\nMiscellaneous options:\n" );
+ printf( " -s <filename>\t\t\t\t\t\t\tUse configuration file <filename>.\n");
+ printf( " -d\t\t\t\t\t\t\t\tturn debug on\n" );
+ printf( " -h\t\t\t\t\t\t\t\tdisplay help\n\n" );
+}
+
+static void display_eventlog_names( void )
+{
+ const char **elogs;
+ int i;
+
+ elogs = lp_eventlog_list( );
+ printf( "Active eventlog names:\n" );
+ printf( "--------------------------------------\n" );
+ if ( elogs ) {
+ for ( i = 0; elogs[i]; i++ ) {
+ printf( "\t%s\n", elogs[i] );
+ }
+ }
+ else
+ printf( "\t<None specified>\n");
+}
+
+/*********************************************************************
+ for an eventlog, add in a source name. If the eventlog doesn't
+ exist (not in the list) do nothing. If a source for the log
+ already exists, change the information (remove, replace)
+*********************************************************************/
+static bool eventlog_add_source( const char *eventlog, const char *sourcename,
+ const char *messagefile )
+{
+ /* Find all of the eventlogs, add keys for each of them */
+ /* need to add to the value KEY_EVENTLOG/<eventlog>/Sources string (Creating if necessary)
+ need to add KEY of source to KEY_EVENTLOG/<eventlog>/<source> */
+
+ const char **elogs = lp_eventlog_list( );
+ const char **wrklist, **wp;
+ char *evtlogpath = NULL;
+ int ii = 0;
+ bool already_in;
+ int i;
+ int numsources = 0;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ WERROR werr;
+ struct registry_key *key_hive, *key_eventlog, *key_source;
+ struct security_token *token = NULL;
+ const char *hive_name, *relpath;
+ enum winreg_CreateAction action;
+ struct registry_value *value;
+ static const uint32_t ACCESS = REG_KEY_READ | REG_KEY_WRITE;
+ bool ret = false;
+
+ if (!elogs) {
+ d_printf("No Eventlogs configured\n");
+ goto done;
+ }
+
+ for ( i = 0; elogs[i]; i++ ) {
+ if ( strequal( elogs[i], eventlog ) )
+ break;
+ }
+
+ if ( !elogs[i] ) {
+ d_printf("Eventlog [%s] not found in list of valid event logs\n",
+ eventlog);
+ goto done;
+ }
+
+ /* have to assume that the evenlog key itself exists at this point */
+ /* add in a key of [sourcename] under the eventlog key */
+
+ /* todo add to Sources */
+
+ evtlogpath = talloc_asprintf(ctx, "%s\\%s", KEY_EVENTLOG, eventlog);
+ if (!evtlogpath) {
+ d_printf("Out of memory\n");
+ goto done;
+ }
+
+ relpath = evtlogpath + sizeof(KEY_EVENTLOG);
+ hive_name = talloc_strndup(ctx, evtlogpath, relpath - evtlogpath);
+ if (!hive_name) {
+ d_printf("Out of memory\n");
+ goto done;
+ }
+ relpath++;
+
+ werr = ntstatus_to_werror(registry_create_admin_token(ctx, &token));
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to create admin token: %s\n", win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_openhive(ctx, hive_name, ACCESS, token, &key_hive);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to open hive [%s]: %s\n", hive_name, win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_openkey(ctx, key_hive, relpath, ACCESS, &key_eventlog);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to open key [%s]: %s\n", evtlogpath, win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_queryvalue(ctx, key_eventlog, "Sources", &value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to get value \"Sources\" for [%s]: %s\n", evtlogpath, win_errstr(werr));
+ goto done;
+ }
+ /* perhaps this adding a new string to a multi_sz should be a fn? */
+ /* check to see if it's there already */
+
+ if ( value->type != REG_MULTI_SZ ) {
+ d_printf("Wrong type for \"Sources\", should be REG_MULTI_SZ\n");
+ goto done;
+ }
+ /* convert to a 'regulah' chars to do some comparisons */
+
+ already_in = false;
+ wrklist = NULL;
+ dump_data(1, value->data.data, value->data.length);
+
+ if (!pull_reg_multi_sz(ctx, &value->data, &wrklist)) {
+ d_printf("Failed to pull REG_MULTI_SZ from \"Sources\"\n");
+ goto done;
+ }
+
+ for (ii=0; wrklist[ii]; ii++) {
+ numsources++;
+ }
+
+ if (numsources > 0) {
+ /* see if it's in there already */
+ wp = wrklist;
+
+ while (wp && *wp ) {
+ if ( strequal( *wp, sourcename ) ) {
+ d_printf("Source name [%s] already in list for [%s] \n",
+ sourcename, eventlog);
+ already_in = true;
+ break;
+ }
+ wp++;
+ }
+ } else {
+ d_printf("Nothing in the sources list, this might be a problem\n");
+ }
+
+ if ( !already_in ) {
+ /* make a new list with an additional entry; copy values, add another */
+ wp = talloc_realloc(ctx, wrklist, const char *, numsources + 2 );
+ if ( !wp ) {
+ d_printf("Out of memory\n");
+ goto done;
+ }
+
+ wp[numsources] = sourcename;
+ wp[numsources+1] = NULL;
+ if (!push_reg_multi_sz(ctx, &value->data, wp)) {
+ d_printf("Failed to push Sources\n");
+ goto done;
+ }
+ dump_data( 1, value->data.data, value->data.length);
+ werr = reg_setvalue(key_eventlog, "Sources", value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to set value Sources: %s\n", win_errstr(werr));
+ goto done;
+ }
+ } else {
+ d_printf("Source name [%s] found in existing list of sources\n",
+ sourcename);
+ }
+
+ werr = reg_createkey(ctx, key_eventlog, sourcename, ACCESS, &key_source, &action);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to create subkey \"%s\" of \"%s\": %s\n", sourcename, evtlogpath, win_errstr(werr));
+ goto done;
+ }
+
+ if (action == REG_CREATED_NEW_KEY) {
+ d_printf(" Source name [%s] for eventlog [%s] didn't exist, adding \n",
+ sourcename, eventlog);
+ }
+
+ /* at this point KEY_EVENTLOG/<eventlog>/<sourcename> key is in there. Now need to add EventMessageFile */
+
+ /* now add the values to the KEY_EVENTLOG/Application form key */
+ d_printf("Storing EventMessageFile [%s] to eventlog path of [%s]\n",
+ messagefile, evtlogpath);
+
+ if (!push_reg_sz(ctx, &value->data, messagefile)) {
+ d_printf("Failed to push \"EventMessageFile\"\n");
+ goto done;
+ }
+ value->type = REG_SZ;
+
+ werr = reg_setvalue(key_source, "EventMessageFile", value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to set value \"EventMessageFile\": %s\n", win_errstr(werr));
+ return false;
+ }
+ ret = true;
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+static int DoAddSourceCommand( int argc, char **argv, bool debugflag, char *exename )
+{
+ WERROR werr;
+
+ if ( argc < 3 ) {
+ printf( "need more arguments:\n" );
+ printf( "-o addsource EventlogName SourceName /path/to/EventMessageFile.dll\n" );
+ return -1;
+ }
+
+ /* must open the registry before we access it */
+ werr = registry_init_common();
+ if (!W_ERROR_IS_OK(werr)) {
+ printf("Can't open the registry: %s.\n", win_errstr(werr));
+ return -1;
+ }
+ werr = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(werr)) {
+ printf("Can't start transaction on registry: %s.\n", win_errstr(werr));
+ return -1;
+ }
+
+ if ( !eventlog_add_source( argv[0], argv[1], argv[2] ) ) {
+ regdb_transaction_cancel();
+ return -2;
+ }
+ werr = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(werr)) {
+ printf("Failed to commit transaction on registry: %s.\n", win_errstr(werr));
+ return -1;
+ }
+ return 0;
+}
+
+static int DoWriteCommand( int argc, char **argv, bool debugflag, char *exename )
+{
+ FILE *f1;
+ char *argfname;
+ ELOG_TDB *etdb;
+ NTSTATUS status;
+
+ /* fixed constants are bad bad bad */
+ char linein[1024];
+ bool is_eor;
+ struct eventlog_Record_tdb ee;
+ uint32_t record_number = 0;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+
+ f1 = stdin;
+ if ( !f1 ) {
+ printf( "Can't open STDIN\n" );
+ return -1;
+ }
+
+ if ( debugflag ) {
+ printf( "Starting write for eventlog [%s]\n", argv[0] );
+ display_eventlog_names( );
+ }
+
+ argfname = argv[0];
+
+ if ( !( etdb = elog_open_tdb( argfname, False, False ) ) ) {
+ printf( "can't open the eventlog TDB (%s)\n", argfname );
+ return -1;
+ }
+
+ ZERO_STRUCT( ee ); /* MUST initialize between records */
+
+ while ( !feof( f1 ) ) {
+ if (fgets( linein, sizeof( linein ) - 1, f1 ) == NULL) {
+ break;
+ }
+ if ((strlen(linein) > 0)
+ && (linein[strlen(linein)-1] == '\n')) {
+ linein[strlen(linein)-1] = 0;
+ }
+
+ if ( debugflag )
+ printf( "Read line [%s]\n", linein );
+
+ is_eor = False;
+
+
+ parse_logentry( mem_ctx, ( char * ) &linein, &ee, &is_eor );
+ /* should we do something with the return code? */
+
+ if ( is_eor ) {
+ fixup_eventlog_record_tdb( &ee );
+
+ if ( opt_debug )
+ printf( "record number [%d], tg [%d] , tw [%d]\n",
+ ee.record_number, (int)ee.time_generated, (int)ee.time_written );
+
+ if ( ee.time_generated != 0 ) {
+
+ /* printf("Writing to the event log\n"); */
+
+ status = evlog_push_record_tdb( mem_ctx, ELOG_TDB_CTX(etdb),
+ &ee, &record_number );
+ if ( !NT_STATUS_IS_OK(status) ) {
+ printf( "Can't write to the event log: %s\n",
+ nt_errstr(status) );
+ } else {
+ if ( opt_debug )
+ printf( "Wrote record %d\n",
+ record_number );
+ }
+ } else {
+ if ( opt_debug )
+ printf( "<null record>\n" );
+ }
+ ZERO_STRUCT( ee ); /* MUST initialize between records */
+ }
+ }
+
+ elog_close_tdb( etdb , False );
+
+ return 0;
+}
+
+static int DoDumpCommand(int argc, char **argv, bool debugflag, char *exename)
+{
+ ELOG_TDB *etdb;
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ uint32_t count = 1;
+
+ if (argc > 2) {
+ return -1;
+ }
+
+ if (argc > 1) {
+ count = atoi(argv[1]);
+ }
+
+ etdb = elog_open_tdb(argv[0], false, true);
+ if (!etdb) {
+ printf("can't open the eventlog TDB (%s)\n", argv[0]);
+ return -1;
+ }
+
+ while (1) {
+
+ struct eventlog_Record_tdb *r;
+ char *s;
+
+ r = evlog_pull_record_tdb(mem_ctx, etdb->tdb, count);
+ if (!r) {
+ break;
+ }
+
+ printf("displaying record: %d\n", count);
+
+ s = NDR_PRINT_STRUCT_STRING(mem_ctx, eventlog_Record_tdb, r);
+ if (s) {
+ printf("%s\n", s);
+ talloc_free(s);
+ }
+ count++;
+ }
+
+ elog_close_tdb(etdb, false);
+
+ return 0;
+}
+
+/* would be nice to use the popT stuff here, however doing so forces us to drag in a lot of other infrastructure */
+
+int main( int argc, char *argv[] )
+{
+ int opt, rc;
+ char *exename;
+ char *configfile = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+
+ fstring opname;
+
+ smb_init_locale();
+
+ opt_debug = 0; /* todo set this from getopts */
+
+ exename = argv[0];
+
+ /* default */
+
+ fstrcpy( opname, "write" ); /* the default */
+
+#if 0 /* TESTING CODE */
+ eventlog_add_source( "System", "TestSourceX", "SomeTestPathX" );
+#endif
+ while ( ( opt = getopt( argc, argv, "dho:s:" ) ) != EOF ) {
+ switch ( opt ) {
+
+ case 'o':
+ fstrcpy( opname, optarg );
+ break;
+
+ case 'h':
+ usage( exename );
+ display_eventlog_names( );
+ exit( 0 );
+ break;
+
+ case 'd':
+ opt_debug = 1;
+ break;
+ case 's':
+ configfile = talloc_strdup(frame, optarg);
+ break;
+
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if ( argc < 1 ) {
+ printf( "\nNot enough arguments!\n" );
+ usage( exename );
+ exit( 1 );
+ }
+
+ if ( configfile == NULL ) {
+ lp_load_global(get_dyn_CONFIGFILE());
+ } else if (!lp_load_global(configfile)) {
+ printf("Unable to parse configfile '%s'\n",configfile);
+ exit( 1 );
+ }
+
+ /* note that the separate command types should call usage if they need to... */
+ while ( 1 ) {
+ if ( !strcasecmp_m( opname, "addsource" ) ) {
+ rc = DoAddSourceCommand( argc, argv, opt_debug,
+ exename );
+ break;
+ }
+ if ( !strcasecmp_m( opname, "write" ) ) {
+ rc = DoWriteCommand( argc, argv, opt_debug, exename );
+ break;
+ }
+ if ( !strcasecmp_m( opname, "dump" ) ) {
+ rc = DoDumpCommand( argc, argv, opt_debug, exename );
+ break;
+ }
+ printf( "unknown command [%s]\n", opname );
+ usage( exename );
+ exit( 1 );
+ break;
+ }
+ TALLOC_FREE(frame);
+ return rc;
+}
diff --git a/source3/utils/interact.c b/source3/utils/interact.c
new file mode 100644
index 0000000..f8fed6d
--- /dev/null
+++ b/source3/utils/interact.c
@@ -0,0 +1,135 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Functions to interact with an user.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Aug 2011
+ *
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+
+#include "interact.h"
+
+#include <termios.h>
+
+static const char* get_editor(void) {
+ static char editor[64] = {0};
+
+ if (editor[0] == '\0') {
+ const char *tmp = getenv("VISUAL");
+ if (tmp == NULL) {
+ tmp = getenv("EDITOR");
+ }
+ if (tmp == NULL) {
+ tmp = "vi";
+ }
+ snprintf(editor, sizeof(editor), "%s", tmp);
+ }
+
+ return editor;
+}
+
+int interact_prompt(const char* msg, const char* acc, char def) {
+ struct termios old_tio, new_tio;
+ int c;
+
+ tcgetattr(STDIN_FILENO, &old_tio);
+ new_tio=old_tio;
+ new_tio.c_lflag &=(~ICANON & ~ECHO);
+ tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
+
+ do {
+ d_printf("%s? [%c]\n", msg, def);
+ fflush(stdout);
+ c = getchar();
+ if (c == '\n') {
+ c = def;
+ break;
+ }
+ else if (strchr(acc, tolower(c)) != NULL) {
+ break;
+ }
+ d_printf("Invalid input '%c'\n", c);
+ } while(c != EOF);
+ tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
+ return c;
+}
+
+
+char* interact_edit(TALLOC_CTX* mem_ctx, const char* str) {
+ char fname[] = "/tmp/net_idmap_check.XXXXXX";
+ char buf[128];
+ char* ret = NULL;
+ FILE* file;
+ mode_t mask;
+ int fd;
+
+ mask = umask(S_IRWXO | S_IRWXG);
+ fd = mkstemp(fname);
+ umask(mask);
+ if (fd == -1) {
+ DEBUG(0, ("failed to mkstemp %s: %s\n", fname,
+ strerror(errno)));
+ return NULL;
+ }
+
+ file = fdopen(fd, "w");
+ if (!file) {
+ DEBUG(0, ("failed to open %s for writing: %s\n", fname,
+ strerror(errno)));
+ close(fd);
+ unlink(fname);
+ return NULL;
+ }
+
+ fprintf(file, "%s", str);
+ fclose(file);
+
+ snprintf(buf, sizeof(buf), "%s %s\n", get_editor(), fname);
+ if (system(buf) != 0) {
+ DEBUG(0, ("failed to start editor %s: %s\n", buf,
+ strerror(errno)));
+ unlink(fname);
+ return NULL;
+ }
+
+ file = fopen(fname, "r");
+ if (!file) {
+ DEBUG(0, ("failed to open %s for reading: %s\n", fname,
+ strerror(errno)));
+ unlink(fname);
+ return NULL;
+ }
+ while ( fgets(buf, sizeof(buf), file) ) {
+ ret = talloc_strdup_append(ret, buf);
+ }
+ fclose(file);
+ unlink(fname);
+
+ return talloc_steal(mem_ctx, ret);
+}
+
+
+
+/*Local Variables:*/
+/*mode: c*/
+/*End:*/
diff --git a/source3/utils/interact.h b/source3/utils/interact.h
new file mode 100644
index 0000000..c719d09
--- /dev/null
+++ b/source3/utils/interact.h
@@ -0,0 +1,36 @@
+/* * Samba Unix/Linux SMB client library
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Functions to interact with an user.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Aug 2011
+ */
+
+#ifndef __INTERACT_H
+#define __INTERACT_H
+#include <talloc.h>
+
+char* interact_edit(TALLOC_CTX* mem_ctx, const char* str);
+int interact_prompt(const char* msg, const char* accept, char def);
+
+
+
+#endif /* __INTERACT_H */
+
+/*Local Variables:*/
+/*mode: c++*/
+/*End:*/
diff --git a/source3/utils/log2pcaphex.c b/source3/utils/log2pcaphex.c
new file mode 100644
index 0000000..8113d8e
--- /dev/null
+++ b/source3/utils/log2pcaphex.c
@@ -0,0 +1,408 @@
+/*
+ Unix SMB/CIFS implementation.
+ Utility to extract pcap files from samba (log level 10) log files
+
+ Copyright (C) Jelmer Vernooij 2003
+ Thanks to Tim Potter for the genial idea
+
+ Portions (from capconvert.c) (C) Andrew Tridgell 1997
+ Portions (from text2pcap.c) (C) Ashok Narayanan 2001
+
+ Example:
+ Output NBSS(SMB) packets in hex and convert to pcap adding
+ Eth/IP/TCP headers
+
+ log2pcap -h < samba.log | text2pcap -T 139,139 - samba.pcap
+
+ Output directly to pcap format without Eth headers or TCP
+ sequence numbers
+
+ log2pcap samba.log samba.pcap
+
+ TODO:
+ - Hex to text2pcap outputs are not properly parsed in Wireshark
+ the NBSS or SMB level. This is a bug.
+ - Writing directly to pcap format doesn't include sequence numbers
+ in the TCP packets
+ - Check if a packet is a response or request and set IP to/from
+ addresses accordingly. Currently all packets come from the same
+ dummy IP and go to the same dummy IP
+ - Add a message when done parsing about the number of pacekts
+ processed
+ - Parse NBSS packet header data from log file
+ - Have correct IP and TCP checksums.
+
+ Warning:
+ Samba log level 10 outputs a max of 512 bytes from the packet data
+ section. Packets larger than this will be truncated.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include <popt.h>
+
+/* We don't care about the paranoid malloc checker in this standalone
+ program */
+#undef malloc
+
+#include <assert.h>
+
+int quiet = 0;
+int hexformat = 0;
+
+#define itoa(a) ((a) < 0xa?'0'+(a):'A' + (a-0xa))
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#define TCPDUMP_MAGIC 0xa1b2c3d4
+
+/* tcpdump file format */
+struct tcpdump_file_header {
+ uint32_t magic;
+ uint16_t major;
+ uint16_t minor;
+ int32_t zone;
+ uint32_t sigfigs;
+ uint32_t snaplen;
+ uint32_t linktype;
+};
+
+struct tcpdump_packet {
+ struct timeval ts;
+ uint32_t caplen;
+ uint32_t len;
+};
+
+typedef struct {
+ uint8_t ver_hdrlen;
+ uint8_t dscp;
+ uint16_t packet_length;
+ uint16_t identification;
+ uint8_t flags;
+ uint8_t fragment;
+ uint8_t ttl;
+ uint8_t protocol;
+ uint16_t hdr_checksum;
+ uint32_t src_addr;
+ uint32_t dest_addr;
+} hdr_ip_t;
+
+static hdr_ip_t HDR_IP = {0x45, 0, 0, 0x3412, 0, 0, 0xff, 6, 0, 0x01010101, 0x02020202};
+
+typedef struct {
+ uint16_t source_port;
+ uint16_t dest_port;
+ uint32_t seq_num;
+ uint32_t ack_num;
+ uint8_t hdr_length;
+ uint8_t flags;
+ uint16_t window;
+ uint16_t checksum;
+ uint16_t urg;
+} hdr_tcp_t;
+
+static hdr_tcp_t HDR_TCP = {139, 139, 0, 0, 0x50, 0, 0, 0, 0};
+
+static void print_pcap_header(FILE *out)
+{
+ struct tcpdump_file_header h;
+ h.magic = TCPDUMP_MAGIC;
+ h.major = 2;
+ h.minor = 4;
+ h.zone = 0;
+ h.sigfigs = 0;
+ h.snaplen = 102400; /* As long packets as possible */
+ h.linktype = 101; /* Raw IP */
+ fwrite(&h, sizeof(struct tcpdump_file_header), 1, out);
+}
+
+static void print_pcap_packet(FILE *out, unsigned char *data, long length,
+ long caplen)
+{
+ struct tcpdump_packet p;
+ p.ts.tv_usec = 0;
+ p.ts.tv_sec = 0;
+ p.caplen = caplen;
+ p.len = length;
+ fwrite(&p, sizeof(struct tcpdump_packet), 1, out);
+ fwrite(data, sizeof(unsigned char), caplen, out);
+}
+
+static void print_hex_packet(FILE *out, unsigned char *data, long length)
+{
+ long i,cur = 0;
+ while(cur < length) {
+ fprintf(out, "%06lX ", cur);
+ for(i = cur; i < length && i < cur + 16; i++) {
+ fprintf(out, "%02x ", data[i]);
+ }
+ cur = i;
+ fprintf(out, "\n");
+ }
+}
+
+static void print_netbios_packet(FILE *out, unsigned char *data, long length,
+ long actual_length)
+{
+ unsigned char *newdata; long offset = 0;
+ long newlen;
+
+ newlen = length+sizeof(HDR_IP)+sizeof(HDR_TCP);
+ newdata = (unsigned char *)malloc(newlen);
+
+ HDR_IP.packet_length = htons(newlen);
+ HDR_TCP.window = htons(0x2000);
+ HDR_TCP.source_port = HDR_TCP.dest_port = htons(139);
+
+ memcpy(newdata+offset, &HDR_IP, sizeof(HDR_IP));offset+=sizeof(HDR_IP);
+ memcpy(newdata+offset, &HDR_TCP, sizeof(HDR_TCP));offset+=sizeof(HDR_TCP);
+ memcpy(newdata+offset,data,length);
+
+ print_pcap_packet(out, newdata, newlen, actual_length+offset);
+ free(newdata);
+}
+
+unsigned char *curpacket = NULL;
+unsigned short curpacket_len = 0;
+long line_num = 0;
+
+/* Read the log message produced by lib/util.c:show_msg() containing the:
+ * SMB_HEADER
+ * SMB_PARAMETERS
+ * SMB_DATA.ByteCount
+ *
+ * Example:
+ * [2007/04/08 20:41:39, 5] lib/util.c:show_msg(516)
+ * size=144
+ * smb_com=0x73
+ * smb_rcls=0
+ * smb_reh=0
+ * smb_err=0
+ * smb_flg=136
+ * smb_flg2=49153
+ * smb_tid=1
+ * smb_pid=65279
+ * smb_uid=0
+ * smb_mid=64
+ * smt_wct=3
+ * smb_vwv[ 0]= 117 (0x75)
+ * smb_vwv[ 1]= 128 (0x80)
+ * smb_vwv[ 2]= 1 (0x1)
+ * smb_bcc=87
+ */
+static void read_log_msg(FILE *in, unsigned char **_buffer,
+ unsigned short *buffersize, long *data_offset,
+ long *data_length)
+{
+ unsigned char *buffer;
+ int tmp; long i;
+ assert(fscanf(in, " size=%hu\n", buffersize)); line_num++;
+ buffer = (unsigned char *)malloc(*buffersize+4); /* +4 for NBSS Header */
+ memset(buffer, 0, *buffersize+4);
+ /* NetBIOS Session Service */
+ buffer[0] = 0x00;
+ buffer[1] = 0x00;
+ memcpy(buffer+2, &buffersize, 2); /* TODO: need to copy as little-endian regardless of platform */
+ /* SMB Packet */
+ buffer[4] = 0xFF;
+ buffer[5] = 'S';
+ buffer[6] = 'M';
+ buffer[7] = 'B';
+ assert(fscanf(in, " smb_com=0x%x\n", &tmp)); buffer[smb_com] = tmp; line_num++;
+ assert(fscanf(in, " smb_rcls=%d\n", &tmp)); buffer[smb_rcls] = tmp; line_num++;
+ assert(fscanf(in, " smb_reh=%d\n", &tmp)); buffer[smb_reh] = tmp; line_num++;
+ assert(fscanf(in, " smb_err=%d\n", &tmp)); memcpy(buffer+smb_err, &tmp, 2); line_num++;
+ assert(fscanf(in, " smb_flg=%d\n", &tmp)); buffer[smb_flg] = tmp; line_num++;
+ assert(fscanf(in, " smb_flg2=%d\n", &tmp)); memcpy(buffer+smb_flg2, &tmp, 2); line_num++;
+ assert(fscanf(in, " smb_tid=%d\n", &tmp)); memcpy(buffer+smb_tid, &tmp, 2); line_num++;
+ assert(fscanf(in, " smb_pid=%d\n", &tmp)); memcpy(buffer+smb_pid, &tmp, 2); line_num++;
+ assert(fscanf(in, " smb_uid=%d\n", &tmp)); memcpy(buffer+smb_uid, &tmp, 2); line_num++;
+ assert(fscanf(in, " smb_mid=%d\n", &tmp)); memcpy(buffer+smb_mid, &tmp, 2); line_num++;
+ assert(fscanf(in, " smt_wct=%d\n", &tmp)); buffer[smb_wct] = tmp; line_num++;
+ for(i = 0; i < buffer[smb_wct]; i++) {
+ assert(fscanf(in, " smb_vwv[%*3d]=%*5d (0x%X)\n", &tmp)); line_num++;
+ memcpy(buffer+smb_vwv+i*2, &tmp, 2);
+ }
+
+ *data_offset = smb_vwv+buffer[smb_wct]*2;
+ assert(fscanf(in, " smb_bcc=%ld\n", data_length)); buffer[(*data_offset)] = *data_length; line_num++;
+ (*data_offset)+=2;
+ *_buffer = buffer;
+}
+
+/* Read the log message produced by lib/util.c:dump_data() containing:
+ * SMB_DATA.Bytes
+ *
+ * Example:
+ * [2007/04/08 20:41:39, 10] lib/util.c:dump_data(2243)
+ * [000] 00 55 00 6E 00 69 00 78 00 00 00 53 00 61 00 6D .U.n.i.x ...S.a.m
+ * [010] 00 62 00 61 00 20 00 33 00 2E 00 30 00 2E 00 32 .b.a. .3 ...0...2
+ * [020] 00 34 00 2D 00 49 00 73 00 69 00 6C 00 6F 00 6E .4.-.I.s .i.l.o.n
+ * [030] 00 20 00 4F 00 6E 00 65 00 46 00 53 00 20 00 76 . .O.n.e .F.S. .v
+ * [040] 00 34 00 2E 00 30 00 00 00 49 00 53 00 49 00 4C .4...0.. .I.S.I.L
+ * [050] 00 4F 00 4E 00 00 00 .O.N...
+ */
+static long read_log_data(FILE *in, unsigned char *buffer, long data_length)
+{
+ long i, addr; char real[2][16]; int ret;
+ unsigned int tmp;
+ for(i = 0; i < data_length; i++) {
+ if(i % 16 == 0){
+ if(i != 0) {
+ /* Read and discard the ascii data after each line. */
+ assert(fscanf(in, " %8c %8c\n", real[0], real[1]) == 2);
+ }
+ ret = fscanf(in, " [%03lX]", &addr); line_num++;
+ if(!ret) {
+ if(!quiet)
+ fprintf(stderr, "%ld: Only first %ld bytes are logged, "
+ "packet trace will be incomplete\n", line_num, i-1);
+ return i-1;
+ }
+ assert(addr == i);
+ }
+ if(!fscanf(in, "%02X", &tmp)) {
+ if(!quiet)
+ fprintf(stderr, "%ld: Log message formatted incorrectly. "
+ "Only first %ld bytes are logged, packet trace will "
+ "be incomplete\n", line_num, i-1);
+ while ((tmp = getc(in)) != '\n');
+ return i-1;
+ }
+ buffer[i] = tmp;
+ }
+
+ /* Consume the newline so we don't increment num_lines twice */
+ while ((tmp = getc(in)) != '\n');
+ return data_length;
+}
+
+int main(int argc, const char **argv)
+{
+ const char *infile, *outfile;
+ FILE *out, *in;
+ int opt;
+ poptContext pc;
+ char buffer[4096];
+ long data_offset = 0;
+ long data_length = 0;
+ long data_bytes_read = 0;
+ size_t in_packet = 0;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "quiet",
+ .shortName = 'q',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &quiet,
+ .val = 0,
+ .descrip = "Be quiet, don't output warnings",
+ },
+ {
+ .longName = "hex",
+ .shortName = 'h',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &hexformat,
+ .val = 0,
+ .descrip = "Output format readable by text2pcap",
+ },
+ POPT_TABLEEND
+ };
+
+ pc = poptGetContext(NULL, argc, argv, long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ poptSetOtherOptionHelp(pc, "[<infile> [<outfile>]]");
+
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptGetArg(pc); /* Drop argv[0], the program name */
+
+ infile = poptGetArg(pc);
+
+ if(infile) {
+ in = fopen(infile, "r");
+ if(!in) {
+ perror("fopen");
+ poptFreeContext(pc);
+ return 1;
+ }
+ } else in = stdin;
+
+ outfile = poptGetArg(pc);
+
+ if(outfile) {
+ out = fopen(outfile, "w+");
+ if(!out) {
+ perror("fopen");
+ fprintf(stderr, "Can't find %s, using stdout...\n", outfile);
+ poptFreeContext(pc);
+ return 1;
+ }
+ }
+
+ if(!outfile) out = stdout;
+
+ if(!hexformat)print_pcap_header(out);
+
+ while(!feof(in)) {
+ char *p;
+ p = fgets(buffer, sizeof(buffer), in);
+ if (p == NULL) {
+ fprintf(stderr, "error reading from input file\n");
+ break;
+ }
+ line_num++;
+ if(buffer[0] == '[') { /* Header */
+ if(strstr(buffer, "show_msg")) {
+ in_packet++;
+ if(in_packet == 1)continue;
+ read_log_msg(in, &curpacket, &curpacket_len, &data_offset, &data_length);
+ } else if(in_packet && strstr(buffer, "dump_data")) {
+ data_bytes_read = read_log_data(in, curpacket+data_offset, data_length);
+ } else {
+ if(in_packet){
+ if(hexformat) print_hex_packet(out, curpacket, curpacket_len);
+ else print_netbios_packet(out, curpacket, curpacket_len, data_bytes_read+data_offset);
+ free(curpacket);
+ }
+ in_packet = 0;
+ }
+ }
+ }
+
+ if (in != stdin) {
+ fclose(in);
+ }
+
+ if (out != stdout) {
+ fclose(out);
+ }
+
+ poptFreeContext(pc);
+ return 0;
+}
diff --git a/source3/utils/mdsearch.c b/source3/utils/mdsearch.c
new file mode 100644
index 0000000..0f5b887
--- /dev/null
+++ b/source3/utils/mdsearch.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2019, Ralph Boehme <slow@samba.org.>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include "includes.h"
+#include "lib/util/debug.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/cmdline_contexts.h"
+#include "param.h"
+#include "client.h"
+#include "libsmb/proto.h"
+#include "librpc/rpc/rpc_common.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_mdssvc.h"
+#include "librpc/gen_ndr/ndr_mdssvc_c.h"
+
+static char *opt_path;
+static int opt_live;
+
+int main(int argc, char **argv)
+{
+ const char **const_argv = discard_const_p(const char *, argv);
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+ struct tevent_context *ev = NULL;
+ struct cli_credentials *creds = NULL;
+ struct rpc_pipe_client *rpccli = NULL;
+ struct mdscli_ctx *mdscli_ctx = NULL;
+ struct mdscli_search_ctx *search = NULL;
+ const char *server = NULL;
+ const char *share = NULL;
+ const char *mds_query = NULL;
+ struct cli_state *cli = NULL;
+ char *basepath = NULL;
+ uint32_t flags = CLI_FULL_CONNECTION_IPC;
+ uint64_t *cnids = NULL;
+ size_t ncnids;
+ size_t i;
+ int opt;
+ poptContext pc;
+ NTSTATUS status;
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "path",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_path,
+ .descrip = "Server-relative search path",
+ },
+ {
+ .longName = "live",
+ .shortName = 'L',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_live,
+ .descrip = "live query",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S3
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "1");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ const_argv,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+
+ poptSetOtherOptionHelp(pc, "mdsearch [OPTIONS] <server> <share> <query>\n");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ DBG_ERR("Invalid option %s: %s\n",
+ poptBadOption(pc, 0),
+ poptStrerror(opt));
+ poptPrintHelp(pc, stderr, 0);
+ goto fail;
+ }
+
+ poptGetArg(pc); /* Drop argv[0], the program name */
+ server = poptGetArg(pc);
+ share = poptGetArg(pc);
+ mds_query = poptGetArg(pc);
+
+ if (server == NULL || mds_query == NULL) {
+ poptPrintHelp(pc, stderr, 0);
+ goto fail;
+ }
+
+ samba_cmdline_burn(argc, argv);
+
+ if ((server[0] == '/' && server[1] == '/') ||
+ (server[0] == '\\' && server[1] == '\\'))
+ {
+ server += 2;
+ }
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+
+ cmdline_messaging_context(get_dyn_CONFIGFILE());
+
+ creds = samba_cmdline_get_creds();
+
+ status = cli_full_connection_creds(&cli,
+ lp_netbios_name(),
+ server,
+ NULL,
+ 0,
+ "IPC$",
+ "IPC",
+ creds,
+ flags);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Cannot connect to server: %s\n", nt_errstr(status));
+ goto fail_free_messaging;
+ }
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_mdssvc, &rpccli);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail_free_messaging;
+ }
+
+ status = mdscli_connect(frame,
+ rpccli->binding_handle,
+ share,
+ "/foo/bar",
+ &mdscli_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to connect mdssvc\n");
+ goto fail_free_messaging;
+ }
+
+ if (opt_path == NULL) {
+ basepath = mdscli_get_basepath(frame, mdscli_ctx);
+ } else {
+ basepath = talloc_strdup(frame, opt_path);
+ }
+ if (basepath == NULL) {
+ goto fail_free_messaging;
+ }
+
+ status = mdscli_search(frame,
+ mdscli_ctx,
+ mds_query,
+ basepath,
+ opt_live == 1 ? true : false,
+ &search);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mdscli_search failed\n");
+ goto fail_free_messaging;
+ }
+
+ if (!opt_live) {
+ sleep(1);
+ }
+
+ while (true) {
+ status = mdscli_get_results(frame,
+ search,
+ &cnids);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_MATCHES)) {
+ if (opt_live) {
+ sleep(1);
+ continue;
+ }
+ break;
+ }
+
+ ncnids = talloc_array_length(cnids);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_PENDING) &&
+ ncnids == 0)
+ {
+ sleep(1);
+ continue;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mdscli_get_results failed\n");
+ goto fail_free_messaging;
+ }
+
+ if (ncnids == 0) {
+ break;
+ }
+
+ for (i = 0; i < ncnids; i++) {
+ char *path = NULL;
+
+ status = mdscli_get_path(frame,
+ mdscli_ctx,
+ cnids[i],
+ &path);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Get path for CNID 0x%"PRIx64" failed\n",
+ cnids[i]);
+ goto fail_free_messaging;
+ }
+ printf("%s\n", path);
+ TALLOC_FREE(path);
+ }
+ }
+
+ status = mdscli_close_search(&search);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mdscli_close_search failed\n");
+ goto fail_free_messaging;
+ }
+
+ status = mdscli_disconnect(mdscli_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("mdscli_disconnect failed\n");
+ goto fail_free_messaging;
+ }
+
+ cmdline_messaging_context_free();
+ TALLOC_FREE(frame);
+ poptFreeContext(pc);
+ return 0;
+
+fail_free_messaging:
+ cmdline_messaging_context_free();
+fail:
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ return 1;
+}
diff --git a/source3/utils/mvxattr.c b/source3/utils/mvxattr.c
new file mode 100644
index 0000000..dd8da79
--- /dev/null
+++ b/source3/utils/mvxattr.c
@@ -0,0 +1,227 @@
+/*
+ Unix SMB/CIFS implementation.
+ xattr renaming
+ Copyright (C) Ralph Boehme 2017
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include <popt.h>
+#include <ftw.h>
+
+static struct rename_xattr_state {
+ int follow_symlink;
+ int print;
+ int force;
+ int verbose;
+ char *xattr_from;
+ char *xattr_to;
+} state;
+
+static int rename_xattr(const char *path,
+ const struct stat *sb,
+ int typeflag,
+ struct FTW *ftwbuf)
+{
+ ssize_t len;
+ int ret;
+
+ if (typeflag == FTW_SL) {
+ d_printf("Ignoring symlink %s\n", path);
+ return 0;
+ }
+
+ if (state.verbose) {
+ d_printf("%s\n", path);
+ }
+
+ len = getxattr(path, state.xattr_from, NULL, 0);
+ if (len < 0) {
+ if (errno == ENOATTR) {
+ return 0;
+ }
+ d_printf("getxattr [%s] failed [%s]\n",
+ path, strerror(errno));
+ return -1;
+ }
+
+ {
+ uint8_t buf[len];
+
+ len = getxattr(path, state.xattr_from, &buf[0], len);
+ if (len == -1) {
+ d_printf("getxattr [%s] failed [%s]\n",
+ path, strerror(errno));
+ return -1;
+ }
+
+ ret = setxattr(path, state.xattr_to, &buf[0], len, XATTR_CREATE);
+ if (ret != 0) {
+ if (errno != EEXIST) {
+ d_printf("setxattr [%s] failed [%s]\n",
+ path, strerror(errno));
+ return -1;
+ }
+ if (!state.force) {
+ d_printf("destination [%s:%s] exists, use -f to force\n",
+ path, state.xattr_to);
+ return -1;
+ }
+ ret = setxattr(path, state.xattr_to, &buf[0], len, XATTR_REPLACE);
+ if (ret != 0) {
+ d_printf("setxattr [%s:%s] failed [%s]\n",
+ path, state.xattr_to, strerror(errno));
+ return -1;
+ }
+ }
+
+ ret = removexattr(path, state.xattr_from);
+ if (ret != 0) {
+ d_printf("removexattr [%s:%s] failed [%s]\n",
+ path, state.xattr_from, strerror(errno));
+ return -1;
+ }
+
+ if (state.print) {
+ d_printf("Renamed %s to %s on %s\n",
+ state.xattr_from, state.xattr_to, path);
+ }
+ }
+
+ return 0;
+}
+
+int main(int argc, const char *argv[])
+{
+ int c;
+ const char *path = NULL;
+ poptContext pc = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "from",
+ .shortName = 's',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &state.xattr_from,
+ .val = 's',
+ .descrip = "xattr source name",
+ },
+ {
+ .longName = "to",
+ .shortName = 'd',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &state.xattr_to,
+ .val = 'd',
+ .descrip = "xattr destination name",
+ },
+ {
+ .longName = "follow-symlinks",
+ .shortName = 'l',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &state.follow_symlink,
+ .val = 'l',
+ .descrip = "follow symlinks, the default is to "
+ "ignore them",
+ },
+ {
+ .longName = "print",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &state.print,
+ .val = 'p',
+ .descrip = "print files where the xattr got "
+ "renamed",
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &state.verbose,
+ .val = 'v',
+ .descrip = "print files as they are checked",
+ },
+ {
+ .longName = "force",
+ .shortName = 'f',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &state.force,
+ .val = 'f',
+ .descrip = "force overwriting of destination xattr",
+ },
+ POPT_TABLEEND
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *s = NULL;
+ int ret = 0;
+
+ if (getuid() != 0) {
+ d_printf("%s only works as root!\n", argv[0]);
+ ret = 1;
+ goto done;
+ }
+
+ pc = poptGetContext(NULL, argc, argv, long_options, 0);
+ poptSetOtherOptionHelp(pc, "-s STRING -d STRING PATH [PATH ...]");
+
+ while ((c = poptGetNextOpt(pc)) != -1) {
+ switch (c) {
+ case 's':
+ s = poptGetOptArg(pc);
+ state.xattr_from = talloc_strdup(frame, s);
+ if (state.xattr_from == NULL) {
+ ret = 1;
+ goto done;
+ }
+ break;
+ case 'd':
+ s = poptGetOptArg(pc);
+ state.xattr_to = talloc_strdup(frame, s);
+ if (state.xattr_to == NULL) {
+ ret = 1;
+ goto done;
+ }
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(c));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ if (state.xattr_from == NULL || state.xattr_to == NULL) {
+ poptPrintUsage(pc, stderr, 0);
+ ret = 1;
+ goto done;
+ }
+
+ if (poptPeekArg(pc) == NULL) {
+ poptPrintUsage(pc, stderr, 0);
+ ret = 1;
+ goto done;
+ }
+
+ while ((path = poptGetArg(pc)) != NULL) {
+ ret = nftw(path, rename_xattr, 256,
+ state.follow_symlink ? 0 : FTW_PHYS);
+ }
+
+done:
+ poptFreeContext(pc);
+
+ TALLOC_FREE(frame);
+ return ret;
+}
diff --git a/source3/utils/net.c b/source3/utils/net.c
new file mode 100644
index 0000000..badf2e0
--- /dev/null
+++ b/source3/utils/net.c
@@ -0,0 +1,1450 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2001 Steve French (sfrench@us.ibm.com)
+ Copyright (C) 2001 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2001 Andrew Bartlett (abartlet@samba.org)
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ Originally written by Steve and Jim. Largely rewritten by tridge in
+ November 2001.
+
+ Reworked again by abartlet in December 2001
+
+ Another overhaul, moving functionality into plug-ins loaded on demand by Kai
+ in May 2008.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*****************************************************/
+/* */
+/* Distributed SMB/CIFS Server Management Utility */
+/* */
+/* The intent was to make the syntax similar */
+/* to the NET utility (first developed in DOS */
+/* with additional interesting & useful functions */
+/* added in later SMB server network operating */
+/* systems). */
+/* */
+/*****************************************************/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "utils/net.h"
+#include "secrets.h"
+#include "lib/netapi/netapi.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "messages.h"
+#include "cmdline_contexts.h"
+#include "lib/gencache.h"
+#include "auth/credentials/credentials.h"
+#include "source3/utils/passwd_proto.h"
+#include "auth/gensec/gensec.h"
+#include "lib/param/param.h"
+
+#ifdef WITH_FAKE_KASERVER
+#include "utils/net_afs.h"
+#endif
+
+/***********************************************************************/
+/* end of internationalization section */
+/***********************************************************************/
+
+enum netr_SchannelType get_sec_channel_type(const char *param)
+{
+ if (!(param && *param)) {
+ return get_default_sec_channel();
+ } else {
+ if (strequal(param, "PDC")) {
+ return SEC_CHAN_BDC;
+ } else if (strequal(param, "BDC")) {
+ return SEC_CHAN_BDC;
+ } else if (strequal(param, "MEMBER")) {
+ return SEC_CHAN_WKSTA;
+#if 0
+ } else if (strequal(param, "DOMAIN")) {
+ return SEC_CHAN_DOMAIN;
+#endif
+ } else {
+ return get_default_sec_channel();
+ }
+ }
+}
+
+static int net_changetrustpw(struct net_context *c, int argc, const char **argv)
+{
+ net_warn_member_options();
+
+ if (net_ads_check_our_domain(c) == 0)
+ return net_ads_changetrustpw(c, argc, argv);
+
+ return net_rpc_changetrustpw(c, argc, argv);
+}
+
+static void set_line_buffering(FILE *f)
+{
+ setvbuf(f, NULL, _IOLBF, 0);
+}
+
+static int net_primarytrust_dumpinfo(struct net_context *c, int argc,
+ const char **argv)
+{
+ int role = lp_server_role();
+ const char *domain = lp_workgroup();
+ struct secrets_domain_info1 *info = NULL;
+ bool include_secrets = c->opt_force;
+ char *str = NULL;
+ NTSTATUS status;
+
+ if (role >= ROLE_ACTIVE_DIRECTORY_DC) {
+ d_printf(_("net primarytrust dumpinfo is only supported "
+ "on a DOMAIN_MEMBER for now.\n"));
+ return 1;
+ }
+
+ net_warn_member_options();
+
+ if (c->opt_stdin) {
+ set_line_buffering(stdin);
+ set_line_buffering(stdout);
+ set_line_buffering(stderr);
+ }
+
+ status = secrets_fetch_or_upgrade_domain_info(domain,
+ talloc_tos(),
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Unable to fetch the information for domain[%s] "
+ "in the secrets database.\n"),
+ domain);
+ return 1;
+ }
+
+ str = secrets_domain_info_string(info, info, domain, include_secrets);
+ if (str == NULL) {
+ d_fprintf(stderr, "secrets_domain_info_string() failed.\n");
+ return 1;
+ }
+
+ d_printf("%s", str);
+ if (!c->opt_force) {
+ d_printf(_("The password values are only included using "
+ "-f flag.\n"));
+ }
+
+ TALLOC_FREE(info);
+ return 0;
+}
+
+/**
+ * Entrypoint for 'net primarytrust' code.
+ *
+ * @param argc Standard argc.
+ * @param argv Standard argv without initial components.
+ *
+ * @return Integer status (0 means success).
+ */
+
+static int net_primarytrust(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ .funcname = "dumpinfo",
+ .fn = net_primarytrust_dumpinfo,
+ .valid_transports = NET_TRANSPORT_LOCAL,
+ .description = N_("Dump the details of the "
+ "workstation trust"),
+ .usage = N_(" net [options] primarytrust "
+ "dumpinfo'\n"
+ " Dump the details of the "
+ "workstation trust in "
+ "secrets.tdb.\n"
+ " Requires the -f flag to "
+ "include the password values."),
+ },
+ {
+ .funcname = NULL,
+ },
+ };
+
+ return net_run_function(c, argc, argv, "net primarytrust", func);
+}
+
+static int net_changesecretpw(struct net_context *c, int argc,
+ const char **argv)
+{
+ char *trust_pw;
+ int role = lp_server_role();
+
+ if (role != ROLE_DOMAIN_MEMBER) {
+ d_printf(_("Machine account password change only supported on a DOMAIN_MEMBER.\n"
+ "Do NOT use this function unless you know what it does!\n"
+ "This function will change the ADS Domain member "
+ "machine account password in the secrets.tdb file!\n"));
+ return 1;
+ }
+
+ net_warn_member_options();
+
+ if(c->opt_force) {
+ struct secrets_domain_info1 *info = NULL;
+ struct secrets_domain_info1_change *prev = NULL;
+ NTSTATUS status;
+ struct timeval tv = timeval_current();
+ NTTIME now = timeval_to_nttime(&tv);
+
+ if (c->opt_stdin) {
+ set_line_buffering(stdin);
+ set_line_buffering(stdout);
+ set_line_buffering(stderr);
+ }
+
+ trust_pw = get_pass(_("Enter machine password: "), c->opt_stdin);
+ if (trust_pw == NULL) {
+ d_fprintf(stderr,
+ _("Error in reading machine password\n"));
+ return 1;
+ }
+
+ status = secrets_prepare_password_change(lp_workgroup(),
+ "localhost",
+ trust_pw,
+ talloc_tos(),
+ &info, &prev);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Unable to write the machine account password in the secrets database"));
+ return 1;
+ }
+ if (prev != NULL) {
+ d_fprintf(stderr,
+ _("Pending machine account password change found - aborting."));
+ status = secrets_failed_password_change("localhost",
+ NT_STATUS_REQUEST_NOT_ACCEPTED,
+ NT_STATUS_NOT_COMMITTED,
+ info);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Failed to abort machine account password change"));
+ }
+ return 1;
+ }
+ status = secrets_finish_password_change("localhost", now, info);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Unable to write the machine account password in the secrets database"));
+ return 1;
+ }
+
+ d_printf(_("Modified trust account password in secrets database\n"));
+ }
+ else {
+ d_printf(_("Machine account password change requires the -f flag.\n"
+ "Do NOT use this function unless you know what it does!\n"
+ "This function will change the ADS Domain member "
+ "machine account password in the secrets.tdb file!\n"));
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Set the authorised user for winbindd access in secrets.tdb
+ */
+static int net_setauthuser(struct net_context *c, int argc, const char **argv)
+{
+ const char *password = NULL;
+ bool ok;
+
+ if (!secrets_init()) {
+ d_fprintf(stderr, _("Failed to open secrets.tdb.\n"));
+ return 1;
+ }
+
+ /* Delete the settings. */
+ if (argc >= 1) {
+ if (strncmp(argv[0], "delete", 6) != 0) {
+ d_fprintf(stderr,_("Usage:\n"));
+ d_fprintf(stderr,
+ _(" net setauthuser -U user[%%password] \n"
+ " Set the auth user account to user"
+ "password. Prompt for password if not "
+ "specified.\n"));
+ d_fprintf(stderr,
+ _(" net setauthuser delete\n"
+ " Delete the auth user setting.\n"));
+ return 1;
+ }
+ secrets_delete_entry(SECRETS_AUTH_USER);
+ secrets_delete_entry(SECRETS_AUTH_DOMAIN);
+ secrets_delete_entry(SECRETS_AUTH_PASSWORD);
+ return 0;
+ }
+
+ if (!c->opt_user_specified) {
+ d_fprintf(stderr, _("Usage:\n"));
+ d_fprintf(stderr,
+ _(" net setauthuser -U user[%%password]\n"
+ " Set the auth user account to user"
+ "password. Prompt for password if not "
+ "specified.\n"));
+ d_fprintf(stderr,
+ _(" net setauthuser delete\n"
+ " Delete the auth user setting.\n"));
+ return 1;
+ }
+
+ password = net_prompt_pass(c, _("the auth user"));
+ if (password == NULL) {
+ d_fprintf(stderr,_("Failed to get the auth users password.\n"));
+ return 1;
+ }
+
+ ok = secrets_store_creds(c->creds);
+ if (!ok) {
+ d_fprintf(stderr, _("Failed storing auth user credentials\n"));
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief Get the auth user settings
+ */
+static int net_getauthuser(struct net_context *c, int argc, const char **argv)
+{
+ char *user, *domain, *password;
+
+ /* Lift data from secrets file */
+
+ secrets_fetch_ipc_userpass(&user, &domain, &password);
+
+ if ((!user || !*user) && (!domain || !*domain ) &&
+ (!password || !*password)){
+
+ SAFE_FREE(user);
+ SAFE_FREE(domain);
+ BURN_FREE_STR(password);
+ d_printf(_("No authorised user configured\n"));
+ return 0;
+ }
+
+ /* Pretty print authorised user info */
+
+ d_printf("%s%s%s%s%s\n", domain ? domain : "",
+ domain ? lp_winbind_separator(): "", user,
+ password ? "%" : "", password ? password : "");
+
+ SAFE_FREE(user);
+ SAFE_FREE(domain);
+ BURN_FREE_STR(password);
+
+ return 0;
+}
+/*
+ Retrieve our local SID or the SID for the specified name
+ */
+static int net_getlocalsid(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+ const char *name;
+ struct dom_sid_buf sid_str;
+
+ if (argc >= 1) {
+ name = argv[0];
+ }
+ else {
+ name = lp_netbios_name();
+ }
+
+ if(!initialize_password_db(false, NULL)) {
+ d_fprintf(stderr, _("WARNING: Could not open passdb\n"));
+ return 1;
+ }
+
+ /* first check to see if we can even access secrets, so we don't
+ panic when we can't. */
+
+ if (!secrets_init()) {
+ d_fprintf(stderr,
+ _("Unable to open secrets.tdb. Can't fetch domain "
+ "SID for name: %s\n"), name);
+ return 1;
+ }
+
+ /* Generate one, if it doesn't exist */
+ get_global_sam_sid();
+
+ if (!secrets_fetch_domain_sid(name, &sid)) {
+ DEBUG(0, ("Can't fetch domain SID for name: %s\n", name));
+ return 1;
+ }
+ d_printf(_("SID for domain %s is: %s\n"),
+ name,
+ dom_sid_str_buf(&sid, &sid_str));
+ return 0;
+}
+
+static int net_setlocalsid(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+
+ if ( (argc != 1)
+ || (strncmp(argv[0], "S-1-5-21-", strlen("S-1-5-21-")) != 0)
+ || (!string_to_sid(&sid, argv[0]))
+ || (sid.num_auths != 4)) {
+ d_printf(_("Usage:"));
+ d_printf(" net setlocalsid S-1-5-21-x-y-z\n");
+ return 1;
+ }
+
+ if (!secrets_store_domain_sid(lp_netbios_name(), &sid)) {
+ DEBUG(0,("Can't store domain SID as a pdc/bdc.\n"));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int net_setdomainsid(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+
+ if ( (argc != 1)
+ || (strncmp(argv[0], "S-1-5-21-", strlen("S-1-5-21-")) != 0)
+ || (!string_to_sid(&sid, argv[0]))
+ || (sid.num_auths != 4)) {
+ d_printf(_("Usage:"));
+ d_printf(" net setdomainsid S-1-5-21-x-y-z\n");
+ return 1;
+ }
+
+ if (!secrets_store_domain_sid(lp_workgroup(), &sid)) {
+ DEBUG(0,("Can't store domain SID.\n"));
+ return 1;
+ }
+
+ return 0;
+}
+
+static int net_getdomainsid(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid domain_sid;
+ struct dom_sid_buf sid_str;
+
+ if (argc > 0) {
+ d_printf(_("Usage:"));
+ d_printf(" net getdomainsid\n");
+ return 1;
+ }
+
+ if(!initialize_password_db(false, NULL)) {
+ d_fprintf(stderr, _("WARNING: Could not open passdb\n"));
+ return 1;
+ }
+
+ /* first check to see if we can even access secrets, so we don't
+ panic when we can't. */
+
+ if (!secrets_init()) {
+ d_fprintf(stderr, _("Unable to open secrets.tdb. Can't fetch "
+ "domain SID for name: %s\n"),
+ get_global_sam_name());
+ return 1;
+ }
+
+ /* Generate one, if it doesn't exist */
+ get_global_sam_sid();
+
+ if (!IS_DC) {
+ if (!secrets_fetch_domain_sid(lp_netbios_name(), &domain_sid)) {
+ d_fprintf(stderr, _("Could not fetch local SID\n"));
+ return 1;
+ }
+ d_printf(_("SID for local machine %s is: %s\n"),
+ lp_netbios_name(),
+ dom_sid_str_buf(&domain_sid, &sid_str));
+ }
+ if (!secrets_fetch_domain_sid(c->opt_workgroup, &domain_sid)) {
+ d_fprintf(stderr, _("Could not fetch domain SID\n"));
+ return 1;
+ }
+
+ d_printf(_("SID for domain %s is: %s\n"),
+ c->opt_workgroup,
+ dom_sid_str_buf(&domain_sid, &sid_str));
+
+ return 0;
+}
+
+static bool search_maxrid(struct pdb_search *search, const char *type,
+ uint32_t *max_rid)
+{
+ struct samr_displayentry *entries;
+ uint32_t i, num_entries;
+
+ if (search == NULL) {
+ d_fprintf(stderr, _("get_maxrid: Could not search %s\n"), type);
+ return false;
+ }
+
+ num_entries = pdb_search_entries(search, 0, 0xffffffff, &entries);
+ for (i=0; i<num_entries; i++)
+ *max_rid = MAX(*max_rid, entries[i].rid);
+ TALLOC_FREE(search);
+ return true;
+}
+
+static uint32_t get_maxrid(void)
+{
+ uint32_t max_rid = 0;
+
+ if (!search_maxrid(pdb_search_users(talloc_tos(), 0), "users", &max_rid))
+ return 0;
+
+ if (!search_maxrid(pdb_search_groups(talloc_tos()), "groups", &max_rid))
+ return 0;
+
+ if (!search_maxrid(pdb_search_aliases(talloc_tos(),
+ get_global_sam_sid()),
+ "aliases", &max_rid))
+ return 0;
+
+ return max_rid;
+}
+
+static int net_maxrid(struct net_context *c, int argc, const char **argv)
+{
+ uint32_t rid;
+
+ if (argc != 0) {
+ d_fprintf(stderr, "%s net maxrid\n", _("Usage:"));
+ return 1;
+ }
+
+ if ((rid = get_maxrid()) == 0) {
+ d_fprintf(stderr, _("can't get current maximum rid\n"));
+ return 1;
+ }
+
+ d_printf(_("Currently used maximum rid: %d\n"), rid);
+
+ return 0;
+}
+
+/* main function table */
+static struct functable net_func[] = {
+ {
+ "rpc",
+ net_rpc,
+ NET_TRANSPORT_RPC,
+ N_("Run functions using RPC transport"),
+ N_(" Use 'net help rpc' to get more extensive information "
+ "about 'net rpc' commands.")
+ },
+ {
+ "rap",
+ net_rap,
+ NET_TRANSPORT_RAP,
+ N_("Run functions using RAP transport"),
+ N_(" Use 'net help rap' to get more extensive information "
+ "about 'net rap' commands.")
+ },
+ {
+ "ads",
+ net_ads,
+ NET_TRANSPORT_ADS,
+ N_("Run functions using ADS transport"),
+ N_(" Use 'net help ads' to get more extensive information "
+ "about 'net ads' commands.")
+ },
+
+ /* eventually these should auto-choose the transport ... */
+ {
+ "file",
+ net_file,
+ NET_TRANSPORT_RPC | NET_TRANSPORT_RAP,
+ N_("Functions on remote opened files"),
+ N_(" Use 'net help file' to get more information about 'net "
+ "file' commands.")
+ },
+ {
+ "share",
+ net_share,
+ NET_TRANSPORT_RPC | NET_TRANSPORT_RAP,
+ N_("Functions on shares"),
+ N_(" Use 'net help share' to get more information about 'net "
+ "share' commands.")
+ },
+ {
+ "session",
+ net_rap_session,
+ NET_TRANSPORT_RAP,
+ N_("Manage sessions"),
+ N_(" Use 'net help session' to get more information about "
+ "'net session' commands.")
+ },
+ {
+ "server",
+ net_rap_server,
+ NET_TRANSPORT_RAP,
+ N_("List servers in workgroup"),
+ N_(" Use 'net help server' to get more information about 'net "
+ "server' commands.")
+ },
+ {
+ "domain",
+ net_rap_domain,
+ NET_TRANSPORT_RAP,
+ N_("List domains/workgroups on network"),
+ N_(" Use 'net help domain' to get more information about 'net "
+ "domain' commands.")
+ },
+ {
+ "printq",
+ net_rap_printq,
+ NET_TRANSPORT_RAP,
+ N_("Modify printer queue"),
+ N_(" Use 'net help printq' to get more information about 'net "
+ "printq' commands.")
+ },
+ {
+ "user",
+ net_user,
+ NET_TRANSPORT_ADS | NET_TRANSPORT_RPC | NET_TRANSPORT_RAP,
+ N_("Manage users"),
+ N_(" Use 'net help user' to get more information about 'net "
+ "user' commands.")
+ },
+ {
+ "group",
+ net_group,
+ NET_TRANSPORT_ADS | NET_TRANSPORT_RPC | NET_TRANSPORT_RAP,
+ N_("Manage groups"),
+ N_(" Use 'net help group' to get more information about 'net "
+ "group' commands.")
+ },
+ {
+ "groupmap",
+ net_groupmap,
+ NET_TRANSPORT_LOCAL,
+ N_("Manage group mappings"),
+ N_(" Use 'net help groupmap' to get more information about "
+ "'net groupmap' commands.")
+ },
+ {
+ "sam",
+ net_sam,
+ NET_TRANSPORT_LOCAL,
+ N_("Functions on the SAM database"),
+ N_(" Use 'net help sam' to get more information about 'net "
+ "sam' commands.")
+ },
+ {
+ "validate",
+ net_rap_validate,
+ NET_TRANSPORT_RAP,
+ N_("Validate username and password"),
+ N_(" Use 'net help validate' to get more information about "
+ "'net validate' commands.")
+ },
+ {
+ "groupmember",
+ net_rap_groupmember,
+ NET_TRANSPORT_RAP,
+ N_("Modify group memberships"),
+ N_(" Use 'net help groupmember' to get more information about "
+ "'net groupmember' commands.")
+ },
+ { "admin",
+ net_rap_admin,
+ NET_TRANSPORT_RAP,
+ N_("Execute remote command on a remote OS/2 server"),
+ N_(" Use 'net help admin' to get more information about 'net "
+ "admin' commands.")
+ },
+ { "service",
+ net_rap_service,
+ NET_TRANSPORT_RAP,
+ N_("List/modify running services"),
+ N_(" Use 'net help service' to get more information about "
+ "'net service' commands.")
+ },
+ {
+ "password",
+ net_rap_password,
+ NET_TRANSPORT_RAP,
+ N_("Change user password on target server"),
+ N_(" Use 'net help password' to get more information about "
+ "'net password' commands.")
+ },
+ {
+ "primarytrust",
+ net_primarytrust,
+ NET_TRANSPORT_RPC,
+ N_("Run functions related to the primary workstation trust."),
+ N_(" Use 'net help primarytrust' to get more extensive information "
+ "about 'net primarytrust' commands.")
+ },
+ { "changetrustpw",
+ net_changetrustpw,
+ NET_TRANSPORT_ADS | NET_TRANSPORT_RPC,
+ N_("Change the trust password"),
+ N_(" Use 'net help changetrustpw' to get more information "
+ "about 'net changetrustpw'.")
+ },
+ { "changesecretpw",
+ net_changesecretpw,
+ NET_TRANSPORT_LOCAL,
+ N_("Change the secret password"),
+ N_(" net [options] changesecretpw\n"
+ " Change the ADS domain member machine account password "
+ "in secrets.tdb.\n"
+ " Do NOT use this function unless you know what it does.\n"
+ " Requires the -f flag to work.")
+ },
+ {
+ "setauthuser",
+ net_setauthuser,
+ NET_TRANSPORT_LOCAL,
+ N_("Set the winbind auth user"),
+ N_(" net -U user[%%password] [-W domain] setauthuser\n"
+ " Set the auth user, password (and optionally domain\n"
+ " Will prompt for password if not given.\n"
+ " net setauthuser delete\n"
+ " Delete the existing auth user settings.")
+ },
+ {
+ "getauthuser",
+ net_getauthuser,
+ NET_TRANSPORT_LOCAL,
+ N_("Get the winbind auth user settings"),
+ N_(" net getauthuser\n"
+ " Get the current winbind auth user settings.")
+ },
+ { "time",
+ net_time,
+ NET_TRANSPORT_LOCAL,
+ N_("Show/set time"),
+ N_(" Use 'net help time' to get more information about 'net "
+ "time' commands.")
+ },
+ { "lookup",
+ net_lookup,
+ NET_TRANSPORT_LOCAL,
+ N_("Look up host names/IP addresses"),
+ N_(" Use 'net help lookup' to get more information about 'net "
+ "lookup' commands.")
+ },
+ { "g_lock",
+ net_g_lock,
+ NET_TRANSPORT_LOCAL,
+ N_("Manipulate the global lock table"),
+ N_(" Use 'net help g_lock' to get more information about "
+ "'net g_lock' commands.")
+ },
+ { "join",
+ net_join,
+ NET_TRANSPORT_ADS | NET_TRANSPORT_RPC,
+ N_("Join a domain/AD"),
+ N_(" Use 'net help join' to get more information about 'net "
+ "join'.")
+ },
+ { "offlinejoin",
+ net_offlinejoin,
+ NET_TRANSPORT_ADS | NET_TRANSPORT_RPC,
+ N_("Perform offline domain join"),
+ N_(" Use 'net help offlinejoin' to get more information about 'net "
+ "offlinejoin'.")
+ },
+ { "dom",
+ net_dom,
+ NET_TRANSPORT_LOCAL,
+ N_("Join/unjoin (remote) machines to/from a domain/AD"),
+ N_(" Use 'net help dom' to get more information about 'net "
+ "dom' commands.")
+ },
+ { "cache",
+ net_cache,
+ NET_TRANSPORT_LOCAL,
+ N_("Operate on the cache tdb file"),
+ N_(" Use 'net help cache' to get more information about 'net "
+ "cache' commands.")
+ },
+ { "getlocalsid",
+ net_getlocalsid,
+ NET_TRANSPORT_LOCAL,
+ N_("Get the SID for the local domain"),
+ N_(" net getlocalsid")
+ },
+ { "setlocalsid",
+ net_setlocalsid,
+ NET_TRANSPORT_LOCAL,
+ N_("Set the SID for the local domain"),
+ N_(" net setlocalsid S-1-5-21-x-y-z")
+ },
+ { "setdomainsid",
+ net_setdomainsid,
+ NET_TRANSPORT_LOCAL,
+ N_("Set domain SID on member servers"),
+ N_(" net setdomainsid S-1-5-21-x-y-z")
+ },
+ { "getdomainsid",
+ net_getdomainsid,
+ NET_TRANSPORT_LOCAL,
+ N_("Get domain SID on member servers"),
+ N_(" net getdomainsid")
+ },
+ { "maxrid",
+ net_maxrid,
+ NET_TRANSPORT_LOCAL,
+ N_("Display the maximum RID currently used"),
+ N_(" net maxrid")
+ },
+ { "idmap",
+ net_idmap,
+ NET_TRANSPORT_LOCAL,
+ N_("IDmap functions"),
+ N_(" Use 'net help idmap to get more information about 'net "
+ "idmap' commands.")
+ },
+ { "status",
+ net_status,
+ NET_TRANSPORT_LOCAL,
+ N_("Display server status"),
+ N_(" Use 'net help status' to get more information about 'net "
+ "status' commands.")
+ },
+ { "usershare",
+ net_usershare,
+ NET_TRANSPORT_LOCAL,
+ N_("Manage user-modifiable shares"),
+ N_(" Use 'net help usershare to get more information about "
+ "'net usershare' commands.")
+ },
+ { "usersidlist",
+ net_usersidlist,
+ NET_TRANSPORT_RPC,
+ N_("Display list of all users with SID"),
+ N_(" Use 'net help usersidlist' to get more information about "
+ "'net usersidlist'.")
+ },
+ { "conf",
+ net_conf,
+ NET_TRANSPORT_LOCAL,
+ N_("Manage Samba registry based configuration"),
+ N_(" Use 'net help conf' to get more information about 'net "
+ "conf' commands.")
+ },
+ { "registry",
+ net_registry,
+ NET_TRANSPORT_LOCAL,
+ N_("Manage the Samba registry"),
+ N_(" Use 'net help registry' to get more information about "
+ "'net registry' commands.")
+ },
+ { "eventlog",
+ net_eventlog,
+ NET_TRANSPORT_LOCAL,
+ N_("Process Win32 *.evt eventlog files"),
+ N_(" Use 'net help eventlog' to get more information about "
+ "'net eventlog' commands.")
+ },
+ { "printing",
+ net_printing,
+ NET_TRANSPORT_LOCAL,
+ N_("Process tdb printer files"),
+ N_(" Use 'net help printing' to get more information about "
+ "'net printing' commands.")
+ },
+
+ { "serverid",
+ net_serverid,
+ NET_TRANSPORT_LOCAL,
+ N_("Manage the serverid tdb"),
+ N_(" Use 'net help serverid' to get more information about "
+ "'net serverid' commands.")
+ },
+
+ { "notify",
+ net_notify,
+ NET_TRANSPORT_LOCAL,
+ N_("notifyd client code"),
+ N_(" Use 'net help notify' to get more information about "
+ "'net notify' commands.")
+ },
+
+ { "tdb",
+ net_tdb,
+ NET_TRANSPORT_LOCAL,
+ N_("Show information from tdb records"),
+ N_(" Use 'net help tdb' to get more information about "
+ "'net tdb' commands.")
+ },
+
+ { "vfs",
+ net_vfs,
+ NET_TRANSPORT_LOCAL,
+ N_("Filesystem operation through the VFS stack"),
+ N_(" Use 'net help vfs' to get more information about "
+ "'net vfs' commands.")
+ },
+
+ { "witness",
+ net_witness,
+ NET_TRANSPORT_LOCAL,
+ N_("Manage witness registrations"),
+ N_(" Use 'net help witness' to get more information about "
+ "'net witness' commands.")
+ },
+
+#ifdef WITH_FAKE_KASERVER
+ { "afs",
+ net_afs,
+ NET_TRANSPORT_LOCAL,
+ N_("Manage AFS tokens"),
+ N_(" Use 'net help afs' to get more information about 'net "
+ "afs' commands.")
+ },
+#endif
+
+ { "help",
+ net_help,
+ NET_TRANSPORT_LOCAL,
+ N_("Print usage information"),
+ N_(" Use 'net help help' to list usage information for 'net' "
+ "commands.")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+};
+
+
+/****************************************************************************
+ main program
+****************************************************************************/
+ int main(int argc, char **argv)
+{
+ int opt,i;
+ int rc = 0;
+ int argc_new = 0;
+ const char ** argv_new;
+ const char **argv_const = discard_const_p(const char *, argv);
+ poptContext pc;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct net_context *c = talloc_zero(frame, struct net_context);
+ bool ok;
+
+ struct poptOption long_options[] = {
+ {
+ .longName = "help",
+ .shortName = 'h',
+ .argInfo = POPT_ARG_NONE,
+ .val = 'h',
+ },
+ {
+ .longName = "target-workgroup",
+ .shortName = 'w',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_target_workgroup,
+ },
+ {
+ .longName = "ipaddress",
+ .shortName = 'I',
+ .argInfo = POPT_ARG_STRING,
+ .arg = 0,
+ .val = 'I',
+ },
+ {
+ .longName = "port",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_port,
+ },
+ {
+ .longName = "myname",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_requester_name,
+ },
+ {
+ .longName = "server",
+ .shortName = 'S',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_host,
+ },
+ {
+ .longName = "container",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_container,
+ },
+ {
+ .longName = "comment",
+ .shortName = 'C',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_comment,
+ },
+ {
+ .longName = "maxusers",
+ .shortName = 'M',
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_maxusers,
+ },
+ {
+ .longName = "flags",
+ .shortName = 'F',
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_flags,
+ },
+ {
+ .longName = "long",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_long_list_entries,
+ },
+ {
+ .longName = "reboot",
+ .shortName = 'r',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_reboot,
+ },
+ {
+ .longName = "force",
+ .shortName = 'f',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_force,
+ },
+ {
+ .longName = "stdin",
+ .shortName = 'i',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_stdin,
+ },
+ {
+ .longName = "timeout",
+ .shortName = 't',
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_timeout,
+ },
+ {
+ .longName = "request-timeout",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_request_timeout,
+ },
+ {
+ .longName = "use-ccache",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_ccache,
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_verbose,
+ },
+ {
+ .longName = "test",
+ .shortName = 'T',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_testmode,
+ },
+ /* Options for 'net groupmap set' */
+ {
+ .longName = "local",
+ .shortName = 'L',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_localgroup,
+ },
+ {
+ .longName = "domain",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_domaingroup,
+ },
+ {
+ .longName = "ntname",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_newntname,
+ },
+ {
+ .longName = "rid",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_rid,
+ },
+ /* Options for 'net rpc share migrate' */
+ {
+ .longName = "acls",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_acls,
+ },
+ {
+ .longName = "attrs",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_attrs,
+ },
+ {
+ .longName = "timestamps",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_timestamps,
+ },
+ {
+ .longName = "exclude",
+ .shortName = 'X',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_exclude,
+ },
+ {
+ .longName = "destination",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_destination,
+ },
+ {
+ .longName = "tallocreport",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->do_talloc_report,
+ },
+ /* Options for 'net rpc vampire (keytab)' */
+ {
+ .longName = "force-full-repl",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_force_full_repl,
+ },
+ {
+ .longName = "single-obj-repl",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_single_obj_repl,
+ },
+ {
+ .longName = "clean-old-entries",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_clean_old_entries,
+ },
+ /* Options for 'net idmap'*/
+ {
+ .longName = "db",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_db,
+ },
+ {
+ .longName = "lock",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_lock,
+ },
+ {
+ .longName = "auto",
+ .shortName = 'a',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_auto,
+ },
+ {
+ .longName = "repair",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_repair,
+ },
+ /* Options for 'net registry check'*/
+ {
+ .longName = "reg-version",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_reg_version,
+ },
+ {
+ .longName = "output",
+ .shortName = 'o',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_output,
+ },
+ {
+ .longName = "wipe",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_wipe,
+ },
+ /* Options for 'net registry import' */
+ {
+ .longName = "precheck",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_precheck,
+ },
+ /* Options for 'net ads join or leave' */
+ {
+ .longName = "no-dns-updates",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_no_dns_updates,
+ },
+ {
+ .longName = "keep-account",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_keep_account,
+ },
+ {
+ .longName = "json",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_json,
+ },
+ /* Options for 'net vfs' */
+ {
+ .longName = "continue",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_continue_on_error,
+ .descrip = "Continue on errors",
+ },
+ {
+ .longName = "recursive",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_recursive,
+ .descrip = "Traverse directory hierarchy",
+ },
+ {
+ .longName = "follow-symlinks",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_follow_symlink,
+ .descrip = "follow symlinks",
+ },
+ /* Options for 'net ads dns register' */
+ {
+ .longName = "dns-ttl",
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_dns_ttl,
+ .descrip = "TTL in seconds of DNS records",
+ },
+ /* Options for 'net witness {list,...}' */
+ {
+ .longName = "witness-registration",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_witness_registration,
+ },
+ {
+ .longName = "witness-net-name",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_witness_net_name,
+ },
+ {
+ .longName = "witness-share-name",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_witness_share_name,
+ },
+ {
+ .longName = "witness-ip-address",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_witness_ip_address,
+ },
+ {
+ .longName = "witness-client-computer-name",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_witness_client_computer_name,
+ },
+ {
+ .longName = "witness-apply-to-all",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &c->opt_witness_apply_to_all,
+ },
+ {
+ .longName = "witness-new-ip",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_witness_new_ip,
+ },
+ {
+ .longName = "witness-new-node",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &c->opt_witness_new_node,
+ },
+ {
+ .longName = "witness-forced-response",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &c->opt_witness_forced_response,
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_COMMON_VERSION
+ POPT_LEGACY_S3
+ POPT_TABLEEND
+ };
+
+ /* Ignore possible SIGPIPE upon ldap_unbind when over TLS */
+ BlockSignals(True, SIGPIPE);
+
+ zero_sockaddr(&c->opt_dest_ip);
+ c->opt_witness_new_node = -2;
+
+ smb_init_locale();
+
+ setlocale(LC_ALL, "");
+#if defined(HAVE_BINDTEXTDOMAIN)
+ bindtextdomain(MODULE_NAME, get_dyn_LOCALEDIR());
+#endif
+#if defined(HAVE_TEXTDOMAIN)
+ textdomain(MODULE_NAME);
+#endif
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ c->lp_ctx = samba_cmdline_get_lp_ctx();
+ /* set default debug level to 0 regardless of what smb.conf sets */
+ lpcfg_set_cmdline(c->lp_ctx, "log level", "0");
+ c->private_data = net_func;
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv_const,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'h':
+ c->display_usage = true;
+ break;
+ case 'I':
+ if (!interpret_string_addr(&c->opt_dest_ip,
+ poptGetOptArg(pc), 0)) {
+ d_fprintf(stderr, _("\nInvalid ip address specified\n"));
+ } else {
+ c->opt_have_ip = true;
+ }
+ break;
+ default:
+ d_fprintf(stderr, _("\nInvalid option %s: %s\n"),
+ poptBadOption(pc, 0), poptStrerror(opt));
+ net_help(c, argc, argv_const);
+ exit(1);
+ }
+ }
+
+ c->creds = samba_cmdline_get_creds();
+
+ {
+ enum credentials_obtained username_obtained =
+ CRED_UNINITIALISED;
+ enum smb_encryption_setting encrypt_state =
+ cli_credentials_get_smb_encryption(c->creds);
+ enum credentials_use_kerberos krb5_state =
+ cli_credentials_get_kerberos_state(c->creds);
+ uint32_t gensec_features;
+
+ c->opt_user_name = cli_credentials_get_username_and_obtained(
+ c->creds,
+ &username_obtained);
+ c->opt_user_specified = (username_obtained == CRED_SPECIFIED);
+
+ c->opt_workgroup = cli_credentials_get_domain(c->creds);
+
+ c->smb_encrypt = (encrypt_state == SMB_ENCRYPTION_REQUIRED);
+
+ c->opt_kerberos = (krb5_state > CRED_USE_KERBEROS_DESIRED);
+
+ gensec_features = cli_credentials_get_gensec_features(c->creds);
+ c->opt_ccache = (gensec_features & GENSEC_FEATURE_NTLM_CCACHE);
+ }
+
+ c->msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
+
+#if defined(HAVE_BIND_TEXTDOMAIN_CODESET)
+ /* Bind our gettext results to 'unix charset'
+
+ This ensures that the translations and any embedded strings are in the
+ same charset. It won't be the one from the user's locale (we no
+ longer auto-detect that), but it will be self-consistent.
+ */
+ bind_textdomain_codeset(MODULE_NAME, lp_unix_charset());
+#endif
+
+ argv_new = (const char **)poptGetArgs(pc);
+
+ argc_new = argc;
+ for (i=0; i<argc; i++) {
+ if (argv_new[i] == NULL) {
+ argc_new = i;
+ break;
+ }
+ }
+
+ if (c->do_talloc_report) {
+ talloc_enable_leak_report();
+ }
+
+ if (c->opt_requester_name) {
+ lpcfg_set_cmdline(c->lp_ctx, "netbios name", c->opt_requester_name);
+ }
+
+ if (!c->opt_target_workgroup) {
+ c->opt_target_workgroup = talloc_strdup(c, lp_workgroup());
+ }
+
+ load_interfaces();
+
+ /* this makes sure that when we do things like call scripts,
+ that it won't assert because we are not root */
+ sec_init();
+
+ samba_cmdline_burn(argc, argv);
+
+ rc = net_run_function(c, argc_new-1, argv_new+1, "net", net_func);
+
+ DEBUG(2,("return code = %d\n", rc));
+
+ libnetapi_free(c->netapi_ctx);
+
+ poptFreeContext(pc);
+
+ cmdline_messaging_context_free();
+
+ gfree_all();
+
+ TALLOC_FREE(frame);
+ return rc;
+}
diff --git a/source3/utils/net.h b/source3/utils/net.h
new file mode 100644
index 0000000..fca3a99
--- /dev/null
+++ b/source3/utils/net.h
@@ -0,0 +1,208 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2001 Andrew Bartlett (abartlet@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/*
+ * A function of this type is passed to the '
+ * run_rpc_command' wrapper. Must go before the net_proto.h
+ * include
+ */
+
+struct cli_state;
+
+#include "../librpc/gen_ndr/lsa.h"
+
+#include "intl.h"
+#ifdef HAVE_LIBINTL_H
+#include <libintl.h>
+#endif
+
+#if defined(HAVE_GETTEXT) && !defined(__LCLINT__)
+#define _(foo) gettext(foo)
+#else
+#define _(foo) foo
+#endif
+
+#define MODULE_NAME "net"
+
+struct net_context {
+ const char *opt_requester_name;
+ const char *opt_host;
+ const char *opt_password;
+ const char *opt_user_name;
+ bool opt_user_specified;
+ const char *opt_workgroup;
+ int opt_long_list_entries;
+ int opt_reboot;
+ int opt_force;
+ int opt_stdin;
+ int opt_port;
+ int opt_verbose;
+ int opt_maxusers;
+ const char *opt_comment;
+ const char *opt_container;
+ int opt_flags;
+ int opt_timeout;
+ int opt_request_timeout;
+ const char *opt_target_workgroup;
+ int opt_machine_pass;
+ int opt_localgroup;
+ int opt_domaingroup;
+ int do_talloc_report;
+ const char *opt_newntname;
+ int opt_rid;
+ int opt_acls;
+ int opt_attrs;
+ int opt_timestamps;
+ const char *opt_exclude;
+ const char *opt_destination;
+ int opt_testmode;
+ int opt_kerberos;
+ int opt_force_full_repl;
+ int opt_ccache;
+ int opt_single_obj_repl;
+ int opt_clean_old_entries;
+ const char *opt_db;
+ int opt_lock;
+ int opt_auto;
+ int opt_repair;
+ int opt_reg_version;
+ const char *opt_output;
+ int opt_wipe;
+ const char *opt_precheck;
+ int opt_no_dns_updates;
+ int opt_keep_account;
+ int opt_json;
+ int opt_continue_on_error;
+ int opt_recursive;
+ int opt_follow_symlink;
+ int opt_dns_ttl;
+ const char *opt_witness_registration;
+ const char *opt_witness_net_name;
+ const char *opt_witness_share_name;
+ const char *opt_witness_ip_address;
+ const char *opt_witness_client_computer_name;
+ int opt_witness_apply_to_all;
+ const char *opt_witness_new_ip;
+ int opt_witness_new_node;
+ const char *opt_witness_forced_response;
+
+ int opt_have_ip;
+ struct sockaddr_storage opt_dest_ip;
+ bool smb_encrypt;
+ struct libnetapi_ctx *netapi_ctx;
+ struct messaging_context *msg_ctx;
+ struct netlogon_creds_cli_context *netlogon_creds;
+ struct cli_credentials *creds;
+ struct loadparm_context *lp_ctx;
+
+ bool display_usage;
+ void *private_data;
+};
+
+struct net_dc_info {
+ bool is_dc;
+ bool is_pdc;
+ bool is_ad;
+ bool is_mixed_mode;
+ const char *netbios_domain_name;
+ const char *dns_domain_name;
+ const char *forest_name;
+};
+
+#define NET_TRANSPORT_LOCAL 0x01
+#define NET_TRANSPORT_RAP 0x02
+#define NET_TRANSPORT_RPC 0x04
+#define NET_TRANSPORT_ADS 0x08
+
+struct functable {
+ const char *funcname;
+ int (*fn)(struct net_context *c, int argc, const char **argv);
+ int valid_transports;
+ const char *description;
+ const char *usage;
+};
+
+typedef NTSTATUS (*rpc_command_fn)(struct net_context *c,
+ const struct dom_sid *,
+ const char *,
+ struct cli_state *cli,
+ struct rpc_pipe_client *,
+ TALLOC_CTX *,
+ int,
+ const char **);
+
+typedef struct copy_clistate {
+ TALLOC_CTX *mem_ctx;
+ struct cli_state *cli_share_src;
+ struct cli_state *cli_share_dst;
+ char *cwd;
+ uint16_t attribute;
+ struct net_context *c;
+}copy_clistate;
+
+struct rpc_sh_ctx {
+ struct cli_state *cli;
+
+ struct dom_sid *domain_sid;
+ const char *domain_name;
+
+ const char *whoami;
+ const char *thiscmd;
+ struct rpc_sh_cmd *cmds;
+ struct rpc_sh_ctx *parent;
+};
+
+struct rpc_sh_cmd {
+ const char *name;
+ struct rpc_sh_cmd *(*sub)(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx);
+ const struct ndr_interface_table *table;
+ NTSTATUS (*fn)(struct net_context *c, TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv);
+ const char *help;
+};
+
+enum netdom_domain_t { ND_TYPE_NT4, ND_TYPE_AD };
+
+/* INCLUDE FILES */
+
+#include "utils/net_proto.h"
+#include "utils/net_help_common.h"
+
+/* MACROS & DEFINES */
+
+#define NET_FLAGS_MASTER 0x00000001
+#define NET_FLAGS_DMB 0x00000002
+#define NET_FLAGS_LOCALHOST_DEFAULT_INSANE 0x00000004 /* Would it be insane to set 'localhost'
+ as the default remote host for this
+ operation? For example, localhost
+ is insane for a 'join' operation. */
+#define NET_FLAGS_PDC 0x00000008 /* PDC only */
+#define NET_FLAGS_ANONYMOUS 0x00000010 /* use an anonymous connection */
+#define NET_FLAGS_NO_PIPE 0x00000020 /* don't open an RPC pipe */
+#define NET_FLAGS_SIGN 0x00000040 /* sign RPC connection */
+#define NET_FLAGS_SEAL 0x00000080 /* seal RPC connection */
+#define NET_FLAGS_TCP 0x00000100 /* use ncacn_ip_tcp */
+#define NET_FLAGS_EXPECT_FALLBACK 0x00000200 /* the caller will fallback */
+
+/* net share operation modes */
+#define NET_MODE_SHARE_MIGRATE 1
+
diff --git a/source3/utils/net_ads.c b/source3/utils/net_ads.c
new file mode 100644
index 0000000..d95a209
--- /dev/null
+++ b/source3/utils/net_ads.c
@@ -0,0 +1,4184 @@
+/*
+ Samba Unix/Linux SMB client library
+ net ads commands
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2001 Remus Koos (remuskoos@yahoo.com)
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2006 Gerald (Jerry) Carter (jerry@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "libsmb/namequery.h"
+#include "rpc_client/cli_pipe.h"
+#include "librpc/gen_ndr/ndr_krb5pac.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "ads.h"
+#include "libads/cldap.h"
+#include "../lib/addns/dnsquery.h"
+#include "../libds/common/flags.h"
+#include "librpc/gen_ndr/libnet_join.h"
+#include "libnet/libnet_join.h"
+#include "smb_krb5.h"
+#include "secrets.h"
+#include "krb5_env.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "lib/param/loadparm.h"
+#include "utils/net_dns.h"
+#include "auth/kerberos/pac_utils.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+#include "audit_logging.h" /* various JSON helpers */
+#include "auth/common_auth.h"
+#endif /* [HAVE_JANSSON] */
+
+#ifdef HAVE_ADS
+
+/* when we do not have sufficient input parameters to contact a remote domain
+ * we always fall back to our own realm - Guenther*/
+
+static const char *assume_own_realm(struct net_context *c)
+{
+ if (!c->opt_host && strequal(lp_workgroup(), c->opt_target_workgroup)) {
+ return lp_realm();
+ }
+
+ return NULL;
+}
+
+#ifdef HAVE_JANSSON
+
+/*
+ * note: JSON output deliberately bypasses gettext so as to provide the same
+ * output irrespective of the locale.
+ */
+
+static int output_json(const struct json_object *jsobj)
+{
+ TALLOC_CTX *ctx = NULL;
+ char *json = NULL;
+
+ if (json_is_invalid(jsobj)) {
+ return -1;
+ }
+
+ ctx = talloc_new(NULL);
+ if (ctx == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ return -1;
+ }
+
+ json = json_to_string(ctx, jsobj);
+ if (!json) {
+ d_fprintf(stderr, _("error encoding to JSON\n"));
+ return -1;
+ }
+
+ d_printf("%s\n", json);
+ TALLOC_FREE(ctx);
+
+ return 0;
+}
+
+static int net_ads_cldap_netlogon_json
+ (ADS_STRUCT *ads,
+ const char *addr,
+ const struct NETLOGON_SAM_LOGON_RESPONSE_EX *reply)
+{
+ struct json_object jsobj = json_new_object();
+ struct json_object flagsobj = json_new_object();
+ char response_type [32] = { '\0' };
+ int ret = 0;
+
+ if (json_is_invalid(&jsobj) || json_is_invalid(&flagsobj)) {
+ d_fprintf(stderr, _("error setting up JSON value\n"));
+
+ goto failure;
+ }
+
+ switch (reply->command) {
+ case LOGON_SAM_LOGON_USER_UNKNOWN_EX:
+ strncpy(response_type,
+ "LOGON_SAM_LOGON_USER_UNKNOWN_EX",
+ sizeof(response_type));
+ break;
+ case LOGON_SAM_LOGON_RESPONSE_EX:
+ strncpy(response_type, "LOGON_SAM_LOGON_RESPONSE_EX",
+ sizeof(response_type));
+ break;
+ default:
+ snprintf(response_type, sizeof(response_type), "0x%x",
+ reply->command);
+ break;
+ }
+
+ ret = json_add_string(&jsobj, "Information for Domain Controller",
+ addr);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "Response Type", response_type);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_guid(&jsobj, "GUID", &reply->domain_uuid);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is a PDC",
+ reply->server_type & NBT_SERVER_PDC);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is a GC of the forest",
+ reply->server_type & NBT_SERVER_GC);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is an LDAP server",
+ reply->server_type & NBT_SERVER_LDAP);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Supports DS",
+ reply->server_type & NBT_SERVER_DS);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is running a KDC",
+ reply->server_type & NBT_SERVER_KDC);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is running time services",
+ reply->server_type & NBT_SERVER_TIMESERV);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is the closest DC",
+ reply->server_type & NBT_SERVER_CLOSEST);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is writable",
+ reply->server_type & NBT_SERVER_WRITABLE);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Has a hardware clock",
+ reply->server_type & NBT_SERVER_GOOD_TIMESERV);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj,
+ "Is a non-domain NC serviced by LDAP server",
+ reply->server_type & NBT_SERVER_NDNC);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool
+ (&flagsobj, "Is NT6 DC that has some secrets",
+ reply->server_type & NBT_SERVER_SELECT_SECRET_DOMAIN_6);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool
+ (&flagsobj, "Is NT6 DC that has all secrets",
+ reply->server_type & NBT_SERVER_FULL_SECRET_DOMAIN_6);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Runs Active Directory Web Services",
+ reply->server_type & NBT_SERVER_ADS_WEB_SERVICE);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Runs on Windows 2012 or later",
+ reply->server_type & NBT_SERVER_DS_8);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Runs on Windows 2012R2 or later",
+ reply->server_type & NBT_SERVER_DS_9);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Runs on Windows 2016 or later",
+ reply->server_type & NBT_SERVER_DS_10);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Has a DNS name",
+ reply->server_type & NBT_SERVER_HAS_DNS_NAME);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is a default NC",
+ reply->server_type & NBT_SERVER_IS_DEFAULT_NC);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flagsobj, "Is the forest root",
+ reply->server_type & NBT_SERVER_FOREST_ROOT);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "Forest", reply->forest);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "Domain", reply->dns_domain);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "Domain Controller", reply->pdc_dns_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+
+ ret = json_add_string(&jsobj, "Pre-Win2k Domain", reply->domain_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "Pre-Win2k Hostname", reply->pdc_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ if (*reply->user_name) {
+ ret = json_add_string(&jsobj, "User name", reply->user_name);
+ if (ret != 0) {
+ goto failure;
+ }
+ }
+
+ ret = json_add_string(&jsobj, "Server Site Name", reply->server_site);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "Client Site Name", reply->client_site);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int(&jsobj, "NT Version", reply->nt_version);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int(&jsobj, "LMNT Token", reply->lmnt_token);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int(&jsobj, "LM20 Token", reply->lm20_token);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_object(&jsobj, "Flags", &flagsobj);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = output_json(&jsobj);
+ json_free(&jsobj); /* frees flagsobj recursively */
+
+ return ret;
+
+failure:
+ json_free(&flagsobj);
+ json_free(&jsobj);
+
+ return ret;
+}
+
+#else /* [HAVE_JANSSON] */
+
+static int net_ads_cldap_netlogon_json
+ (ADS_STRUCT *ads,
+ const char *addr,
+ const struct NETLOGON_SAM_LOGON_RESPONSE_EX * reply)
+{
+ d_fprintf(stderr, _("JSON support not available\n"));
+
+ return -1;
+}
+
+#endif /* [HAVE_JANSSON] */
+
+/*
+ do a cldap netlogon query
+*/
+static int net_ads_cldap_netlogon(struct net_context *c, ADS_STRUCT *ads)
+{
+ char addr[INET6_ADDRSTRLEN];
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX reply;
+
+ print_sockaddr(addr, sizeof(addr), &ads->ldap.ss);
+
+ if ( !ads_cldap_netlogon_5(talloc_tos(), &ads->ldap.ss, ads->server.realm, &reply ) ) {
+ d_fprintf(stderr, _("CLDAP query failed!\n"));
+ return -1;
+ }
+
+ if (c->opt_json) {
+ return net_ads_cldap_netlogon_json(ads, addr, &reply);
+ }
+
+ d_printf(_("Information for Domain Controller: %s\n\n"),
+ addr);
+
+ d_printf(_("Response Type: "));
+ switch (reply.command) {
+ case LOGON_SAM_LOGON_USER_UNKNOWN_EX:
+ d_printf("LOGON_SAM_LOGON_USER_UNKNOWN_EX\n");
+ break;
+ case LOGON_SAM_LOGON_RESPONSE_EX:
+ d_printf("LOGON_SAM_LOGON_RESPONSE_EX\n");
+ break;
+ default:
+ d_printf("0x%x\n", reply.command);
+ break;
+ }
+
+ d_printf(_("GUID: %s\n"), GUID_string(talloc_tos(),&reply.domain_uuid));
+
+ d_printf(_("Flags:\n"
+ "\tIs a PDC: %s\n"
+ "\tIs a GC of the forest: %s\n"
+ "\tIs an LDAP server: %s\n"
+ "\tSupports DS: %s\n"
+ "\tIs running a KDC: %s\n"
+ "\tIs running time services: %s\n"
+ "\tIs the closest DC: %s\n"
+ "\tIs writable: %s\n"
+ "\tHas a hardware clock: %s\n"
+ "\tIs a non-domain NC serviced by LDAP server: %s\n"
+ "\tIs NT6 DC that has some secrets: %s\n"
+ "\tIs NT6 DC that has all secrets: %s\n"
+ "\tRuns Active Directory Web Services: %s\n"
+ "\tRuns on Windows 2012 or later: %s\n"
+ "\tRuns on Windows 2012R2 or later: %s\n"
+ "\tRuns on Windows 2016 or later: %s\n"
+ "\tHas a DNS name: %s\n"
+ "\tIs a default NC: %s\n"
+ "\tIs the forest root: %s\n"),
+ (reply.server_type & NBT_SERVER_PDC) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_GC) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_LDAP) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_DS) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_KDC) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_TIMESERV) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_CLOSEST) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_WRITABLE) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_GOOD_TIMESERV) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_NDNC) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_SELECT_SECRET_DOMAIN_6) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_FULL_SECRET_DOMAIN_6) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_ADS_WEB_SERVICE) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_DS_8) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_DS_9) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_DS_10) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_HAS_DNS_NAME) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_IS_DEFAULT_NC) ? _("yes") : _("no"),
+ (reply.server_type & NBT_SERVER_FOREST_ROOT) ? _("yes") : _("no"));
+
+
+ printf(_("Forest: %s\n"), reply.forest);
+ printf(_("Domain: %s\n"), reply.dns_domain);
+ printf(_("Domain Controller: %s\n"), reply.pdc_dns_name);
+
+ printf(_("Pre-Win2k Domain: %s\n"), reply.domain_name);
+ printf(_("Pre-Win2k Hostname: %s\n"), reply.pdc_name);
+
+ if (*reply.user_name) printf(_("User name: %s\n"), reply.user_name);
+
+ printf(_("Server Site Name: %s\n"), reply.server_site);
+ printf(_("Client Site Name: %s\n"), reply.client_site);
+
+ d_printf(_("NT Version: %d\n"), reply.nt_version);
+ d_printf(_("LMNT Token: %.2x\n"), reply.lmnt_token);
+ d_printf(_("LM20 Token: %.2x\n"), reply.lm20_token);
+
+ return 0;
+}
+
+/*
+ this implements the CLDAP based netlogon lookup requests
+ for finding the domain controller of a ADS domain
+*/
+static int net_ads_lookup(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf("%s\n"
+ "net ads lookup\n"
+ " %s",
+ _("Usage:"),
+ _("Find the ADS DC using CLDAP lookup.\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup_nobind(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Didn't find the cldap server!\n"));
+ goto out;
+ }
+
+ if (!ads->config.realm) {
+ ads->config.realm = talloc_strdup(ads, c->opt_target_workgroup);
+ if (ads->config.realm == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+ ads->ldap.port = 389;
+ }
+
+ ret = net_ads_cldap_netlogon(c, ads);
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+
+#ifdef HAVE_JANSSON
+
+static int net_ads_info_json(ADS_STRUCT *ads)
+{
+ int ret = 0;
+ char addr[INET6_ADDRSTRLEN];
+ time_t pass_time;
+ struct json_object jsobj = json_new_object();
+
+ if (json_is_invalid(&jsobj)) {
+ d_fprintf(stderr, _("error setting up JSON value\n"));
+
+ goto failure;
+ }
+
+ pass_time = secrets_fetch_pass_last_set_time(ads->server.workgroup);
+
+ print_sockaddr(addr, sizeof(addr), &ads->ldap.ss);
+
+ ret = json_add_string (&jsobj, "LDAP server", addr);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string (&jsobj, "LDAP server name",
+ ads->config.ldap_server_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string (&jsobj, "Realm", ads->config.realm);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string (&jsobj, "Bind Path", ads->config.bind_path);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int (&jsobj, "LDAP port", ads->ldap.port);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int (&jsobj, "Server time", ads->config.current_time);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string (&jsobj, "KDC server", ads->auth.kdc_server);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int (&jsobj, "Server time offset",
+ ads->auth.time_offset);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int (&jsobj, "Last machine account password change",
+ pass_time);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = output_json(&jsobj);
+failure:
+ json_free(&jsobj);
+
+ return ret;
+}
+
+#else /* [HAVE_JANSSON] */
+
+static int net_ads_info_json(ADS_STRUCT *ads)
+{
+ d_fprintf(stderr, _("JSON support not available\n"));
+
+ return -1;
+}
+
+#endif /* [HAVE_JANSSON] */
+
+
+
+static int net_ads_info(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ char addr[INET6_ADDRSTRLEN];
+ time_t pass_time;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf("%s\n"
+ "net ads info\n"
+ " %s",
+ _("Usage:"),
+ _("Display information about an Active Directory "
+ "server.\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup_nobind(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Didn't find the ldap server!\n"));
+ goto out;
+ }
+
+ if (!ads || !ads->config.realm) {
+ d_fprintf(stderr, _("Didn't find the ldap server!\n"));
+ goto out;
+ }
+
+ /* Try to set the server's current time since we didn't do a full
+ TCP LDAP session initially */
+
+ if ( !ADS_ERR_OK(ads_current_time( ads )) ) {
+ d_fprintf( stderr, _("Failed to get server's current time!\n"));
+ }
+
+ if (c->opt_json) {
+ ret = net_ads_info_json(ads);
+ goto out;
+ }
+
+ pass_time = secrets_fetch_pass_last_set_time(ads->server.workgroup);
+
+ print_sockaddr(addr, sizeof(addr), &ads->ldap.ss);
+
+ d_printf(_("LDAP server: %s\n"), addr);
+ d_printf(_("LDAP server name: %s\n"), ads->config.ldap_server_name);
+ d_printf(_("Realm: %s\n"), ads->config.realm);
+ d_printf(_("Bind Path: %s\n"), ads->config.bind_path);
+ d_printf(_("LDAP port: %d\n"), ads->ldap.port);
+ d_printf(_("Server time: %s\n"),
+ http_timestring(tmp_ctx, ads->config.current_time));
+
+ d_printf(_("KDC server: %s\n"), ads->auth.kdc_server );
+ d_printf(_("Server time offset: %d\n"), ads->auth.time_offset );
+
+ d_printf(_("Last machine account password change: %s\n"),
+ http_timestring(tmp_ctx, pass_time));
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static ADS_STATUS ads_startup_int(struct net_context *c,
+ bool only_own_domain,
+ uint32_t auth_flags,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **ads_ret)
+{
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ bool need_password = false;
+ bool second_time = false;
+ char *cp;
+ const char *realm = NULL;
+ bool tried_closest_dc = false;
+ enum credentials_use_kerberos krb5_state =
+ CRED_USE_KERBEROS_DISABLED;
+
+ /* lp_realm() should be handled by a command line param,
+ However, the join requires that realm be set in smb.conf
+ and compares our realm with the remote server's so this is
+ ok until someone needs more flexibility */
+
+ *ads_ret = NULL;
+
+retry_connect:
+ if (only_own_domain) {
+ realm = lp_realm();
+ } else {
+ realm = assume_own_realm(c);
+ }
+
+ ads = ads_init(mem_ctx,
+ realm,
+ c->opt_target_workgroup,
+ c->opt_host,
+ ADS_SASL_PLAIN);
+ if (ads == NULL) {
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ if (!c->opt_user_name) {
+ c->opt_user_name = "administrator";
+ }
+
+ if (c->opt_user_specified) {
+ need_password = true;
+ }
+
+retry:
+ if (!c->opt_password && need_password && !c->opt_machine_pass) {
+ c->opt_password = net_prompt_pass(c, c->opt_user_name);
+ if (!c->opt_password) {
+ TALLOC_FREE(ads);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ }
+
+ if (c->opt_password) {
+ use_in_memory_ccache();
+ ADS_TALLOC_CONST_FREE(ads->auth.password);
+ ads->auth.password = talloc_strdup(ads, c->opt_password);
+ if (ads->auth.password == NULL) {
+ TALLOC_FREE(ads);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+ }
+
+ ADS_TALLOC_CONST_FREE(ads->auth.user_name);
+ ads->auth.user_name = talloc_strdup(ads, c->opt_user_name);
+ if (ads->auth.user_name == NULL) {
+ TALLOC_FREE(ads);
+ return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ }
+
+ ads->auth.flags |= auth_flags;
+
+ /* The ADS code will handle FIPS mode */
+ krb5_state = cli_credentials_get_kerberos_state(c->creds);
+ switch (krb5_state) {
+ case CRED_USE_KERBEROS_REQUIRED:
+ ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ ads->auth.flags &= ~ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DESIRED:
+ ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DISABLED:
+ ads->auth.flags |= ADS_AUTH_DISABLE_KERBEROS;
+ ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ }
+
+ /*
+ * If the username is of the form "name@realm",
+ * extract the realm and convert to upper case.
+ * This is only used to establish the connection.
+ */
+ if ((cp = strchr_m(ads->auth.user_name, '@'))!=0) {
+ *cp++ = '\0';
+ ADS_TALLOC_CONST_FREE(ads->auth.realm);
+ ads->auth.realm = talloc_asprintf_strupper_m(ads, "%s", cp);
+ if (ads->auth.realm == NULL) {
+ TALLOC_FREE(ads);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ } else if (ads->auth.realm == NULL) {
+ const char *c_realm = cli_credentials_get_realm(c->creds);
+
+ if (c_realm != NULL) {
+ ads->auth.realm = talloc_strdup(ads, c_realm);
+ if (ads->auth.realm == NULL) {
+ TALLOC_FREE(ads);
+ return ADS_ERROR(LDAP_NO_MEMORY);
+ }
+ }
+ }
+
+ status = ads_connect(ads);
+
+ if (!ADS_ERR_OK(status)) {
+
+ if (NT_STATUS_EQUAL(ads_ntstatus(status),
+ NT_STATUS_NO_LOGON_SERVERS)) {
+ DEBUG(0,("ads_connect: %s\n", ads_errstr(status)));
+ TALLOC_FREE(ads);
+ return status;
+ }
+
+ if (!need_password && !second_time && !(auth_flags & ADS_AUTH_NO_BIND)) {
+ need_password = true;
+ second_time = true;
+ goto retry;
+ } else {
+ TALLOC_FREE(ads);
+ return status;
+ }
+ }
+
+ /* when contacting our own domain, make sure we use the closest DC.
+ * This is done by reconnecting to ADS because only the first call to
+ * ads_connect will give us our own sitename */
+
+ if ((only_own_domain || !c->opt_host) && !tried_closest_dc) {
+
+ tried_closest_dc = true; /* avoid loop */
+
+ if (!ads_closest_dc(ads)) {
+
+ namecache_delete(ads->server.realm, 0x1C);
+ namecache_delete(ads->server.workgroup, 0x1C);
+
+ TALLOC_FREE(ads);
+
+ goto retry_connect;
+ }
+ }
+
+ *ads_ret = talloc_move(mem_ctx, &ads);
+ return status;
+}
+
+ADS_STATUS ads_startup(struct net_context *c,
+ bool only_own_domain,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **ads)
+{
+ return ads_startup_int(c, only_own_domain, 0, mem_ctx, ads);
+}
+
+ADS_STATUS ads_startup_nobind(struct net_context *c,
+ bool only_own_domain,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **ads)
+{
+ return ads_startup_int(c,
+ only_own_domain,
+ ADS_AUTH_NO_BIND,
+ mem_ctx,
+ ads);
+}
+
+/*
+ Check to see if connection can be made via ads.
+ ads_startup() stores the password in opt_password if it needs to so
+ that rpc or rap can use it without re-prompting.
+*/
+static int net_ads_check_int(struct net_context *c,
+ const char *realm,
+ const char *workgroup,
+ const char *host)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ int ret = -1;
+
+ ads = ads_init(tmp_ctx, realm, workgroup, host, ADS_SASL_PLAIN);
+ if (ads == NULL) {
+ goto out;
+ }
+
+ ads->auth.flags |= ADS_AUTH_NO_BIND;
+
+ status = ads_connect(ads);
+ if ( !ADS_ERR_OK(status) ) {
+ goto out;
+ }
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+int net_ads_check_our_domain(struct net_context *c)
+{
+ return net_ads_check_int(c, lp_realm(), lp_workgroup(), NULL);
+}
+
+int net_ads_check(struct net_context *c)
+{
+ return net_ads_check_int(c, NULL, c->opt_workgroup, c->opt_host);
+}
+
+/*
+ determine the netbios workgroup name for a domain
+ */
+static int net_ads_workgroup(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ struct NETLOGON_SAM_LOGON_RESPONSE_EX reply;
+ bool ok = false;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf ("%s\n"
+ "net ads workgroup\n"
+ " %s\n",
+ _("Usage:"),
+ _("Print the workgroup name"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup_nobind(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Didn't find the cldap server!\n"));
+ goto out;
+ }
+
+ if (!ads->config.realm) {
+ ads->config.realm = talloc_strdup(ads, c->opt_target_workgroup);
+ if (ads->config.realm == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+ ads->ldap.port = 389;
+ }
+
+ ok = ads_cldap_netlogon_5(tmp_ctx,
+ &ads->ldap.ss, ads->server.realm, &reply);
+ if (!ok) {
+ d_fprintf(stderr, _("CLDAP query failed!\n"));
+ goto out;
+ }
+
+ d_printf(_("Workgroup: %s\n"), reply.domain_name);
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return ret;
+}
+
+
+
+static bool usergrp_display(ADS_STRUCT *ads, char *field, void **values, void *data_area)
+{
+ char **disp_fields = (char **) data_area;
+
+ if (!field) { /* must be end of record */
+ if (disp_fields[0]) {
+ if (!strchr_m(disp_fields[0], '$')) {
+ if (disp_fields[1])
+ d_printf("%-21.21s %s\n",
+ disp_fields[0], disp_fields[1]);
+ else
+ d_printf("%s\n", disp_fields[0]);
+ }
+ }
+ SAFE_FREE(disp_fields[0]);
+ SAFE_FREE(disp_fields[1]);
+ return true;
+ }
+ if (!values) /* must be new field, indicate string field */
+ return true;
+ if (strcasecmp_m(field, "sAMAccountName") == 0) {
+ disp_fields[0] = SMB_STRDUP((char *) values[0]);
+ }
+ if (strcasecmp_m(field, "description") == 0)
+ disp_fields[1] = SMB_STRDUP((char *) values[0]);
+ return true;
+}
+
+static int net_ads_user_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_user_usage(c, argc, argv);
+}
+
+static int ads_user_add(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ char *upn, *userdn;
+ LDAPMessage *res=NULL;
+ int rc = -1;
+ char *ou_str = NULL;
+
+ if (argc < 1 || c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_user_usage(c, argc, argv);
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = ads_find_user_acct(ads, &res, argv[0]);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_user_add: %s\n"), ads_errstr(status));
+ goto done;
+ }
+
+ if (ads_count_replies(ads, res)) {
+ d_fprintf(stderr, _("ads_user_add: User %s already exists\n"),
+ argv[0]);
+ goto done;
+ }
+
+ if (c->opt_container) {
+ ou_str = SMB_STRDUP(c->opt_container);
+ } else {
+ ou_str = ads_default_ou_string(ads, DS_GUID_USERS_CONTAINER);
+ }
+
+ status = ads_add_user_acct(ads, argv[0], ou_str, c->opt_comment);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Could not add user %s: %s\n"), argv[0],
+ ads_errstr(status));
+ goto done;
+ }
+
+ /* if no password is to be set, we're done */
+ if (argc == 1) {
+ d_printf(_("User %s added\n"), argv[0]);
+ rc = 0;
+ goto done;
+ }
+
+ /* try setting the password */
+ upn = talloc_asprintf(tmp_ctx,
+ "%s@%s",
+ argv[0],
+ ads->config.realm);
+ if (upn == NULL) {
+ goto done;
+ }
+
+ status = ads_krb5_set_password(ads->auth.kdc_server, upn, argv[1],
+ ads->auth.time_offset);
+ if (ADS_ERR_OK(status)) {
+ d_printf(_("User %s added\n"), argv[0]);
+ rc = 0;
+ goto done;
+ }
+ TALLOC_FREE(upn);
+
+ /* password didn't set, delete account */
+ d_fprintf(stderr, _("Could not add user %s. "
+ "Error setting password %s\n"),
+ argv[0], ads_errstr(status));
+
+ ads_msgfree(ads, res);
+ res = NULL;
+
+ status=ads_find_user_acct(ads, &res, argv[0]);
+ if (ADS_ERR_OK(status)) {
+ userdn = ads_get_dn(ads, tmp_ctx, res);
+ ads_del_dn(ads, userdn);
+ TALLOC_FREE(userdn);
+ }
+
+ done:
+ ads_msgfree(ads, res);
+ SAFE_FREE(ou_str);
+ TALLOC_FREE(tmp_ctx);
+ return rc;
+}
+
+static int ads_user_info(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+ wbcErr wbc_status;
+ const char *attrs[] = {"memberOf", "primaryGroupID", NULL};
+ char *searchstring = NULL;
+ char **grouplist = NULL;
+ char *primary_group = NULL;
+ char *escaped_user = NULL;
+ struct dom_sid primary_group_sid;
+ uint32_t group_rid;
+ enum wbcSidType type;
+
+ if (argc < 1 || c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_user_usage(c, argc, argv);
+ }
+
+ escaped_user = escape_ldap_string(tmp_ctx, argv[0]);
+ if (!escaped_user) {
+ d_fprintf(stderr,
+ _("ads_user_info: failed to escape user %s\n"),
+ argv[0]);
+ goto out;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ searchstring = talloc_asprintf(tmp_ctx,
+ "(sAMAccountName=%s)",
+ escaped_user);
+ if (searchstring == NULL) {
+ goto out;
+ }
+
+ status = ads_search(ads, &res, searchstring, attrs);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_search: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ if (!ads_pull_uint32(ads, res, "primaryGroupID", &group_rid)) {
+ d_fprintf(stderr, _("ads_pull_uint32 failed\n"));
+ goto out;
+ }
+
+ status = ads_domain_sid(ads, &primary_group_sid);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_domain_sid: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ sid_append_rid(&primary_group_sid, group_rid);
+
+ wbc_status = wbcLookupSid((struct wbcDomainSid *)&primary_group_sid,
+ NULL, /* don't look up domain */
+ &primary_group,
+ &type);
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ d_fprintf(stderr, "wbcLookupSid: %s\n",
+ wbcErrorString(wbc_status));
+ goto out;
+ }
+
+ d_printf("%s\n", primary_group);
+
+ wbcFreeMemory(primary_group);
+
+ grouplist = ldap_get_values((LDAP *)ads->ldap.ld,
+ (LDAPMessage *)res, "memberOf");
+
+ if (grouplist) {
+ int i;
+ char **groupname;
+ for (i=0;grouplist[i];i++) {
+ groupname = ldap_explode_dn(grouplist[i], 1);
+ d_printf("%s\n", groupname[0]);
+ ldap_value_free(groupname);
+ }
+ ldap_value_free(grouplist);
+ }
+
+ ret = 0;
+out:
+ TALLOC_FREE(escaped_user);
+ TALLOC_FREE(searchstring);
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int ads_user_delete(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ char *userdn = NULL;
+ int ret = -1;
+
+ if (argc < 1) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_user_usage(c, argc, argv);
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_find_user_acct(ads, &res, argv[0]);
+ if (!ADS_ERR_OK(status) || ads_count_replies(ads, res) != 1) {
+ d_printf(_("User %s does not exist.\n"), argv[0]);
+ goto out;
+ }
+
+ userdn = ads_get_dn(ads, tmp_ctx, res);
+ if (userdn == NULL) {
+ goto out;
+ }
+
+ status = ads_del_dn(ads, userdn);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Error deleting user %s: %s\n"), argv[0],
+ ads_errstr(status));
+ goto out;
+ }
+
+ d_printf(_("User %s deleted\n"), argv[0]);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+int net_ads_user(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ ads_user_add,
+ NET_TRANSPORT_ADS,
+ N_("Add an AD user"),
+ N_("net ads user add\n"
+ " Add an AD user")
+ },
+ {
+ "info",
+ ads_user_info,
+ NET_TRANSPORT_ADS,
+ N_("Display information about an AD user"),
+ N_("net ads user info\n"
+ " Display information about an AD user")
+ },
+ {
+ "delete",
+ ads_user_delete,
+ NET_TRANSPORT_ADS,
+ N_("Delete an AD user"),
+ N_("net ads user delete\n"
+ " Delete an AD user")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *shortattrs[] = {"sAMAccountName", NULL};
+ const char *longattrs[] = {"sAMAccountName", "description", NULL};
+ char *disp_fields[2] = {NULL, NULL};
+ int ret = -1;
+
+ if (argc > 0) {
+ TALLOC_FREE(tmp_ctx);
+ return net_run_function(c, argc, argv, "net ads user", func);
+ }
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads user\n"
+ " %s\n",
+ _("Usage:"),
+ _("List AD users"));
+ net_display_usage_from_functable(func);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (c->opt_long_list_entries)
+ d_printf(_("\nUser name Comment"
+ "\n-----------------------------\n"));
+
+ status = ads_do_search_all_fn(ads,
+ ads->config.bind_path,
+ LDAP_SCOPE_SUBTREE,
+ "(objectCategory=user)",
+ c->opt_long_list_entries ?
+ longattrs : shortattrs,
+ usergrp_display,
+ disp_fields);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_group_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_group_usage(c, argc, argv);
+}
+
+static int ads_group_add(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+ char *ou_str = NULL;
+
+ if (argc < 1 || c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_group_usage(c, argc, argv);
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_find_user_acct(ads, &res, argv[0]);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_group_add: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ if (ads_count_replies(ads, res)) {
+ d_fprintf(stderr, _("ads_group_add: Group %s already exists\n"), argv[0]);
+ goto out;
+ }
+
+ if (c->opt_container) {
+ ou_str = SMB_STRDUP(c->opt_container);
+ } else {
+ ou_str = ads_default_ou_string(ads, DS_GUID_USERS_CONTAINER);
+ }
+
+ status = ads_add_group_acct(ads, argv[0], ou_str, c->opt_comment);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Could not add group %s: %s\n"), argv[0],
+ ads_errstr(status));
+ goto out;
+ }
+
+ d_printf(_("Group %s added\n"), argv[0]);
+
+ ret = 0;
+ out:
+ ads_msgfree(ads, res);
+ SAFE_FREE(ou_str);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int ads_group_delete(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ char *groupdn = NULL;
+ int ret = -1;
+
+ if (argc < 1 || c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_group_usage(c, argc, argv);
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_find_user_acct(ads, &res, argv[0]);
+ if (!ADS_ERR_OK(status) || ads_count_replies(ads, res) != 1) {
+ d_printf(_("Group %s does not exist.\n"), argv[0]);
+ goto out;
+ }
+
+ groupdn = ads_get_dn(ads, tmp_ctx, res);
+ if (groupdn == NULL) {
+ goto out;
+ }
+
+ status = ads_del_dn(ads, groupdn);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Error deleting group %s: %s\n"), argv[0],
+ ads_errstr(status));
+ goto out;
+ }
+ d_printf(_("Group %s deleted\n"), argv[0]);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+int net_ads_group(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ ads_group_add,
+ NET_TRANSPORT_ADS,
+ N_("Add an AD group"),
+ N_("net ads group add\n"
+ " Add an AD group")
+ },
+ {
+ "delete",
+ ads_group_delete,
+ NET_TRANSPORT_ADS,
+ N_("Delete an AD group"),
+ N_("net ads group delete\n"
+ " Delete an AD group")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *shortattrs[] = {"sAMAccountName", NULL};
+ const char *longattrs[] = {"sAMAccountName", "description", NULL};
+ char *disp_fields[2] = {NULL, NULL};
+ int ret = -1;
+
+ if (argc >= 0) {
+ TALLOC_FREE(tmp_ctx);
+ return net_run_function(c, argc, argv, "net ads group", func);
+ }
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads group\n"
+ " %s\n",
+ _("Usage:"),
+ _("List AD groups"));
+ net_display_usage_from_functable(func);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (c->opt_long_list_entries)
+ d_printf(_("\nGroup name Comment"
+ "\n-----------------------------\n"));
+
+ status = ads_do_search_all_fn(ads,
+ ads->config.bind_path,
+ LDAP_SCOPE_SUBTREE,
+ "(objectCategory=group)",
+ c->opt_long_list_entries ?
+ longattrs : shortattrs,
+ usergrp_display,
+ disp_fields);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_status(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads status\n"
+ " %s\n",
+ _("Usage:"),
+ _("Display machine account details"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ net_warn_member_options();
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_find_machine_acct(ads, &res, lp_netbios_name());
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_find_machine_acct: %s\n"),
+ ads_errstr(status));
+ goto out;
+ }
+
+ if (ads_count_replies(ads, res) == 0) {
+ d_fprintf(stderr, _("No machine account for '%s' found\n"),
+ lp_netbios_name());
+ goto out;
+ }
+
+ ads_dump(ads, res);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+/*******************************************************************
+ Leave an AD domain. Windows XP disables the machine account.
+ We'll try the same. The old code would do an LDAP delete.
+ That only worked using the machine creds because added the machine
+ with full control to the computer object's ACL.
+*******************************************************************/
+
+static int net_ads_leave(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct libnet_UnjoinCtx *r = NULL;
+ WERROR werr;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads leave [--keep-account]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Leave an AD domain"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (!*lp_realm()) {
+ d_fprintf(stderr, _("No realm set, are we joined ?\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (!c->opt_kerberos) {
+ use_in_memory_ccache();
+ }
+
+ if (!c->msg_ctx) {
+ d_fprintf(stderr, _("Could not initialise message context. "
+ "Try running as root\n"));
+ goto done;
+ }
+
+ werr = libnet_init_UnjoinCtx(tmp_ctx, &r);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("Could not initialise unjoin context.\n"));
+ goto done;
+ }
+
+ r->in.debug = true;
+ r->in.use_kerberos = c->opt_kerberos;
+ r->in.dc_name = c->opt_host;
+ r->in.domain_name = lp_realm();
+ r->in.admin_account = c->opt_user_name;
+ r->in.admin_password = net_prompt_pass(c, c->opt_user_name);
+ r->in.modify_config = lp_config_backend_is_registry();
+
+ /* Try to delete it, but if that fails, disable it. The
+ WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE really means "disable */
+ r->in.unjoin_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE;
+ if (c->opt_keep_account) {
+ r->in.delete_machine_account = false;
+ } else {
+ r->in.delete_machine_account = true;
+ }
+
+ r->in.msg_ctx = c->msg_ctx;
+
+ werr = libnet_Unjoin(tmp_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf(_("Failed to leave domain: %s\n"),
+ r->out.error_string ? r->out.error_string :
+ get_friendly_werror_msg(werr));
+ goto done;
+ }
+
+ if (r->out.deleted_machine_account) {
+ d_printf(_("Deleted account for '%s' in realm '%s'\n"),
+ r->in.machine_name, r->out.dns_domain_name);
+ ret = 0;
+ goto done;
+ }
+
+ /* We couldn't delete it - see if the disable succeeded. */
+ if (r->out.disabled_machine_account) {
+ d_printf(_("Disabled account for '%s' in realm '%s'\n"),
+ r->in.machine_name, r->out.dns_domain_name);
+ ret = 0;
+ goto done;
+ }
+
+ /* Based on what we requested, we shouldn't get here, but if
+ we did, it means the secrets were removed, and therefore
+ we have left the domain */
+ d_fprintf(stderr, _("Machine '%s' Left domain '%s'\n"),
+ r->in.machine_name, r->out.dns_domain_name);
+
+ ret = 0;
+ done:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static ADS_STATUS net_ads_join_ok(struct net_context *c)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ fstring dc_name;
+ struct sockaddr_storage dcip;
+
+ if (!secrets_init()) {
+ DEBUG(1,("Failed to initialise secrets database\n"));
+ TALLOC_FREE(tmp_ctx);
+ return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED);
+ }
+
+ net_warn_member_options();
+
+ net_use_krb_machine_account(c);
+
+ get_dc_name(lp_workgroup(), lp_realm(), dc_name, &dcip);
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ADS_ERROR_NT(NT_STATUS_OK);
+out:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/*
+ check that an existing join is OK
+ */
+int net_ads_testjoin(struct net_context *c, int argc, const char **argv)
+{
+ ADS_STATUS status;
+ use_in_memory_ccache();
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads testjoin\n"
+ " %s\n",
+ _("Usage:"),
+ _("Test if the existing join is ok"));
+ return -1;
+ }
+
+ net_warn_member_options();
+
+ /* Display success or failure */
+ status = net_ads_join_ok(c);
+ if (!ADS_ERR_OK(status)) {
+ fprintf(stderr, _("Join to domain is not valid: %s\n"),
+ get_friendly_nt_error_msg(ads_ntstatus(status)));
+ return -1;
+ }
+
+ printf(_("Join is OK\n"));
+ return 0;
+}
+
+/*******************************************************************
+ Simple config checks before beginning the join
+ ********************************************************************/
+
+static WERROR check_ads_config( void )
+{
+ if (lp_server_role() != ROLE_DOMAIN_MEMBER ) {
+ d_printf(_("Host is not configured as a member server.\n"));
+ return WERR_INVALID_DOMAIN_ROLE;
+ }
+
+ if (strlen(lp_netbios_name()) > 15) {
+ d_printf(_("Our netbios name can be at most 15 chars long, "
+ "\"%s\" is %u chars long\n"), lp_netbios_name(),
+ (unsigned int)strlen(lp_netbios_name()));
+ return WERR_INVALID_COMPUTERNAME;
+ }
+
+ if ( lp_security() == SEC_ADS && !*lp_realm()) {
+ d_fprintf(stderr, _("realm must be set in %s for ADS "
+ "join to succeed.\n"), get_dyn_CONFIGFILE());
+ return WERR_INVALID_PARAMETER;
+ }
+
+ return WERR_OK;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static int net_ads_join_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net ads join [--no-dns-updates] [options]\n"
+ "Valid options:\n"));
+ d_printf(_(" dnshostname=FQDN Set the dnsHostName attribute during the join.\n"
+ " The default is in the form netbiosname.dnsdomain\n"));
+ d_printf(_(" createupn[=UPN] Set the userPrincipalName attribute during the join.\n"
+ " The default UPN is in the form host/netbiosname@REALM.\n"));
+ d_printf(_(" createcomputer=OU Precreate the computer account in a specific OU.\n"
+ " The OU string read from top to bottom without RDNs\n"
+ " and delimited by a '/'.\n"
+ " E.g. \"createcomputer=Computers/Servers/Unix\"\n"
+ " NB: A backslash '\\' is used as escape at multiple\n"
+ " levels and may need to be doubled or even\n"
+ " quadrupled. It is not used as a separator.\n"));
+ d_printf(_(" machinepass=PASS Set the machine password to a specific value during\n"
+ " the join. The default password is random.\n"));
+ d_printf(_(" osName=string Set the operatingSystem attribute during the join.\n"));
+ d_printf(_(" osVer=string Set the operatingSystemVersion attribute during join.\n"
+ " NB: osName and osVer must be specified together for\n"
+ " either to take effect. The operatingSystemService\n"
+ " attribute is then also set along with the two\n"
+ " other attributes.\n"));
+ d_printf(_(" osServicePack=string Set the operatingSystemServicePack attribute\n"
+ " during the join.\n"
+ " NB: If not specified then by default the samba\n"
+ " version string is used instead.\n"));
+ return -1;
+}
+
+
+int net_ads_join(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct libnet_JoinCtx *r = NULL;
+ const char *domain = lp_realm();
+ WERROR werr = WERR_NERR_SETUPNOTJOINED;
+ bool createupn = false;
+ const char *dnshostname = NULL;
+ const char *machineupn = NULL;
+ const char *machine_password = NULL;
+ const char *create_in_ou = NULL;
+ int i;
+ const char *os_name = NULL;
+ const char *os_version = NULL;
+ const char *os_servicepack = NULL;
+ bool modify_config = lp_config_backend_is_registry();
+ enum libnetjoin_JoinDomNameType domain_name_type = JoinDomNameTypeDNS;
+ int ret = -1;
+
+ if (c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_join_usage(c, argc, argv);
+ }
+
+ net_warn_member_options();
+
+ if (!modify_config) {
+ werr = check_ads_config();
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("Invalid configuration. Exiting....\n"));
+ goto fail;
+ }
+ }
+
+ if (!c->opt_kerberos) {
+ use_in_memory_ccache();
+ }
+
+ werr = libnet_init_JoinCtx(tmp_ctx, &r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* process additional command line args */
+
+ for ( i=0; i<argc; i++ ) {
+ if ( !strncasecmp_m(argv[i], "dnshostname", strlen("dnshostname")) ) {
+ dnshostname = get_string_param(argv[i]);
+ }
+ else if ( !strncasecmp_m(argv[i], "createupn", strlen("createupn")) ) {
+ createupn = true;
+ machineupn = get_string_param(argv[i]);
+ }
+ else if ( !strncasecmp_m(argv[i], "createcomputer", strlen("createcomputer")) ) {
+ if ( (create_in_ou = get_string_param(argv[i])) == NULL ) {
+ d_fprintf(stderr, _("Please supply a valid OU path.\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "osName", strlen("osName")) ) {
+ if ( (os_name = get_string_param(argv[i])) == NULL ) {
+ d_fprintf(stderr, _("Please supply a operating system name.\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "osVer", strlen("osVer")) ) {
+ if ( (os_version = get_string_param(argv[i])) == NULL ) {
+ d_fprintf(stderr, _("Please supply a valid operating system version.\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "osServicePack", strlen("osServicePack")) ) {
+ if ( (os_servicepack = get_string_param(argv[i])) == NULL ) {
+ d_fprintf(stderr, _("Please supply a valid servicepack identifier.\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto fail;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "machinepass", strlen("machinepass")) ) {
+ if ( (machine_password = get_string_param(argv[i])) == NULL ) {
+ d_fprintf(stderr, _("Please supply a valid password to set as trust account password.\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto fail;
+ }
+ } else {
+ domain = argv[i];
+ if (strchr(domain, '.') == NULL) {
+ domain_name_type = JoinDomNameTypeUnknown;
+ } else {
+ domain_name_type = JoinDomNameTypeDNS;
+ }
+ }
+ }
+
+ if (!*domain) {
+ d_fprintf(stderr, _("Please supply a valid domain name\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ if (!c->msg_ctx) {
+ d_fprintf(stderr, _("Could not initialise message context. "
+ "Try running as root\n"));
+ werr = WERR_ACCESS_DENIED;
+ goto fail;
+ }
+
+ /* Do the domain join here */
+
+ r->in.domain_name = domain;
+ r->in.domain_name_type = domain_name_type;
+ r->in.create_upn = createupn;
+ r->in.upn = machineupn;
+ r->in.dnshostname = dnshostname;
+ r->in.account_ou = create_in_ou;
+ r->in.os_name = os_name;
+ r->in.os_version = os_version;
+ r->in.os_servicepack = os_servicepack;
+ r->in.dc_name = c->opt_host;
+ r->in.admin_account = c->opt_user_name;
+ r->in.admin_password = net_prompt_pass(c, c->opt_user_name);
+ r->in.machine_password = machine_password;
+ r->in.debug = true;
+ r->in.use_kerberos = c->opt_kerberos;
+ r->in.modify_config = modify_config;
+ r->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE |
+ WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED;
+ r->in.msg_ctx = c->msg_ctx;
+
+ werr = libnet_Join(tmp_ctx, r);
+ if (W_ERROR_EQUAL(werr, WERR_NERR_DCNOTFOUND) &&
+ strequal(domain, lp_realm())) {
+ r->in.domain_name = lp_workgroup();
+ r->in.domain_name_type = JoinDomNameTypeNBT;
+ werr = libnet_Join(tmp_ctx, r);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* Check the short name of the domain */
+
+ if (!modify_config && !strequal(lp_workgroup(), r->out.netbios_domain_name)) {
+ d_printf(_("The workgroup in %s does not match the short\n"
+ "domain name obtained from the server.\n"
+ "Using the name [%s] from the server.\n"
+ "You should set \"workgroup = %s\" in %s.\n"),
+ get_dyn_CONFIGFILE(), r->out.netbios_domain_name,
+ r->out.netbios_domain_name, get_dyn_CONFIGFILE());
+ }
+
+ d_printf(_("Using short domain name -- %s\n"), r->out.netbios_domain_name);
+
+ if (r->out.dns_domain_name) {
+ d_printf(_("Joined '%s' to dns domain '%s'\n"), r->in.machine_name,
+ r->out.dns_domain_name);
+ } else {
+ d_printf(_("Joined '%s' to domain '%s'\n"), r->in.machine_name,
+ r->out.netbios_domain_name);
+ }
+
+ /* print out informative error string in case there is one */
+ if (r->out.error_string != NULL) {
+ d_printf("%s\n", r->out.error_string);
+ }
+
+ /*
+ * We try doing the dns update (if it was compiled in
+ * and if it was not disabled on the command line).
+ * If the dns update fails, we still consider the join
+ * operation as succeeded if we came this far.
+ */
+ if (!c->opt_no_dns_updates) {
+ net_ads_join_dns_updates(c, tmp_ctx, r);
+ }
+
+ ret = 0;
+
+fail:
+ if (ret != 0) {
+ /* issue an overall failure message at the end. */
+ d_printf(_("Failed to join domain: %s\n"),
+ r && r->out.error_string ? r->out.error_string :
+ get_friendly_werror_msg(werr));
+ }
+
+ TALLOC_FREE(tmp_ctx);
+
+ return ret;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static int net_ads_dns_register(struct net_context *c, int argc, const char **argv)
+{
+#if defined(HAVE_KRB5)
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ NTSTATUS ntstatus;
+ const char *hostname = NULL;
+ const char **addrs_list = NULL;
+ struct sockaddr_storage *addrs = NULL;
+ int num_addrs = 0;
+ int count;
+ int ret = -1;
+
+#ifdef DEVELOPER
+ talloc_enable_leak_report();
+#endif
+
+ if (argc <= 1 && lp_clustering() && lp_cluster_addresses() == NULL) {
+ d_fprintf(stderr, _("Refusing DNS updates with automatic "
+ "detection of addresses in a clustered "
+ "setup.\n"));
+ c->display_usage = true;
+ }
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads dns register [hostname [IP [IP...]]] "
+ "[--force] [--dns-ttl TTL]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Register hostname with DNS\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (argc >= 1) {
+ hostname = argv[0];
+ }
+
+ if (argc > 1) {
+ num_addrs = argc - 1;
+ addrs_list = &argv[1];
+ } else if (lp_clustering()) {
+ addrs_list = lp_cluster_addresses();
+ num_addrs = str_list_length(addrs_list);
+ }
+
+ if (num_addrs > 0) {
+ addrs = talloc_zero_array(tmp_ctx,
+ struct sockaddr_storage,
+ num_addrs);
+ if (addrs == NULL) {
+ d_fprintf(stderr, _("Error allocating memory!\n"));
+ goto out;
+ }
+ }
+
+ for (count = 0; count < num_addrs; count++) {
+ if (!interpret_string_addr(&addrs[count], addrs_list[count], 0)) {
+ d_fprintf(stderr, "%s '%s'.\n",
+ _("Cannot interpret address"),
+ addrs_list[count]);
+ goto out;
+ }
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if ( !ADS_ERR_OK(status) ) {
+ DEBUG(1, ("error on ads_startup: %s\n", ads_errstr(status)));
+ goto out;
+ }
+
+ ntstatus = net_update_dns_ext(c,
+ tmp_ctx,
+ ads,
+ hostname,
+ addrs,
+ num_addrs,
+ false);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ d_fprintf( stderr, _("DNS update failed!\n") );
+ goto out;
+ }
+
+ d_fprintf( stderr, _("Successfully registered hostname with DNS\n") );
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return ret;
+#else
+ d_fprintf(stderr,
+ _("DNS update support not enabled at compile time!\n"));
+ return -1;
+#endif
+}
+
+static int net_ads_dns_unregister(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+#if defined(HAVE_KRB5)
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ NTSTATUS ntstatus;
+ const char *hostname = NULL;
+ int ret = -1;
+
+#ifdef DEVELOPER
+ talloc_enable_leak_report();
+#endif
+
+ if (argc != 1) {
+ c->display_usage = true;
+ }
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads dns unregister [hostname]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Remove all IP Address entries for a given\n"
+ " hostname from the Active Directory server.\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ /* Get the hostname for un-registering */
+ hostname = argv[0];
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if ( !ADS_ERR_OK(status) ) {
+ DEBUG(1, ("error on ads_startup: %s\n", ads_errstr(status)));
+ goto out;
+ }
+
+ ntstatus = net_update_dns_ext(c,
+ tmp_ctx,
+ ads,
+ hostname,
+ NULL,
+ 0,
+ true);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ d_fprintf( stderr, _("DNS update failed!\n") );
+ goto out;
+ }
+
+ d_fprintf( stderr, _("Successfully un-registered hostname from DNS\n"));
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return ret;
+#else
+ d_fprintf(stderr,
+ _("DNS update support not enabled at compile time!\n"));
+ return -1;
+#endif
+}
+
+
+static int net_ads_dns_async(struct net_context *c, int argc, const char **argv)
+{
+ size_t num_names = 0;
+ char **hostnames = NULL;
+ size_t i = 0;
+ struct samba_sockaddr *addrs = NULL;
+ NTSTATUS status;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf( "%s\n"
+ " %s\n"
+ " %s\n",
+ _("Usage:"),
+ _("net ads dns async <name>\n"),
+ _(" Async look up hostname from the DNS server\n"
+ " hostname\tName to look up\n"));
+ return -1;
+ }
+
+ status = ads_dns_lookup_a(talloc_tos(),
+ argv[0],
+ &num_names,
+ &hostnames,
+ &addrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Looking up A record for %s got error %s\n",
+ argv[0],
+ nt_errstr(status));
+ return -1;
+ }
+ d_printf("Async A record lookup - got %u names for %s\n",
+ (unsigned int)num_names,
+ argv[0]);
+ for (i = 0; i < num_names; i++) {
+ char addr_buf[INET6_ADDRSTRLEN];
+ print_sockaddr(addr_buf,
+ sizeof(addr_buf),
+ &addrs[i].u.ss);
+ d_printf("hostname[%u] = %s, IPv4addr = %s\n",
+ (unsigned int)i,
+ hostnames[i],
+ addr_buf);
+ }
+
+#if defined(HAVE_IPV6)
+ status = ads_dns_lookup_aaaa(talloc_tos(),
+ argv[0],
+ &num_names,
+ &hostnames,
+ &addrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Looking up AAAA record for %s got error %s\n",
+ argv[0],
+ nt_errstr(status));
+ return -1;
+ }
+ d_printf("Async AAAA record lookup - got %u names for %s\n",
+ (unsigned int)num_names,
+ argv[0]);
+ for (i = 0; i < num_names; i++) {
+ char addr_buf[INET6_ADDRSTRLEN];
+ print_sockaddr(addr_buf,
+ sizeof(addr_buf),
+ &addrs[i].u.ss);
+ d_printf("hostname[%u] = %s, IPv6addr = %s\n",
+ (unsigned int)i,
+ hostnames[i],
+ addr_buf);
+ }
+#endif
+ return 0;
+}
+
+
+static int net_ads_dns(struct net_context *c, int argc, const char *argv[])
+{
+ struct functable func[] = {
+ {
+ "register",
+ net_ads_dns_register,
+ NET_TRANSPORT_ADS,
+ N_("Add host dns entry to AD"),
+ N_("net ads dns register\n"
+ " Add host dns entry to AD")
+ },
+ {
+ "unregister",
+ net_ads_dns_unregister,
+ NET_TRANSPORT_ADS,
+ N_("Remove host dns entry from AD"),
+ N_("net ads dns unregister\n"
+ " Remove host dns entry from AD")
+ },
+ {
+ "async",
+ net_ads_dns_async,
+ NET_TRANSPORT_ADS,
+ N_("Look up host"),
+ N_("net ads dns async\n"
+ " Look up host using async DNS")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads dns", func);
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+int net_ads_printer_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+"\nnet ads printer search <printer>"
+"\n\tsearch for a printer in the directory\n"
+"\nnet ads printer info <printer> <server>"
+"\n\tlookup info in directory for printer on server"
+"\n\t(note: printer defaults to \"*\", server defaults to local)\n"
+"\nnet ads printer publish <printername>"
+"\n\tpublish printer in directory"
+"\n\t(note: printer name is required)\n"
+"\nnet ads printer remove <printername>"
+"\n\tremove printer from directory"
+"\n\t(note: printer name is required)\n"));
+ return -1;
+}
+
+/*******************************************************************
+ ********************************************************************/
+
+static int net_ads_printer_search(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads printer search\n"
+ " %s\n",
+ _("Usage:"),
+ _("List printers in the AD"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_find_printers(ads, &res);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_find_printer: %s\n"),
+ ads_errstr(status));
+ goto out;
+ }
+
+ if (ads_count_replies(ads, res) == 0) {
+ d_fprintf(stderr, _("No results found\n"));
+ goto out;
+ }
+
+ ads_dump(ads, res);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_printer_info(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *servername = NULL;
+ const char *printername = NULL;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads printer info [printername [servername]]\n"
+ " Display printer info from AD\n"
+ " printername\tPrinter name or wildcard\n"
+ " servername\tName of the print server\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (argc > 0) {
+ printername = argv[0];
+ } else {
+ printername = "*";
+ }
+
+ if (argc > 1) {
+ servername = argv[1];
+ } else {
+ servername = lp_netbios_name();
+ }
+
+ status = ads_find_printer_on_server(ads, &res, printername, servername);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Server '%s' not found: %s\n"),
+ servername, ads_errstr(status));
+ goto out;
+ }
+
+ if (ads_count_replies(ads, res) == 0) {
+ d_fprintf(stderr, _("Printer '%s' not found\n"), printername);
+ goto out;
+ }
+
+ ads_dump(ads, res);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_printer_publish(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *servername = NULL;
+ const char *printername = NULL;
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct sockaddr_storage server_ss = { 0 };
+ NTSTATUS nt_status;
+ ADS_MODLIST mods = NULL;
+ char *prt_dn = NULL;
+ char *srv_dn = NULL;
+ char **srv_cn = NULL;
+ char *srv_cn_escaped = NULL;
+ char *printername_escaped = NULL;
+ LDAPMessage *res = NULL;
+ bool ok;
+ int ret = -1;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads printer publish <printername> [servername]\n"
+ " Publish printer in AD\n"
+ " printername\tName of the printer\n"
+ " servername\tName of the print server\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ mods = ads_init_mods(tmp_ctx);
+ if (mods == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ printername = argv[0];
+
+ if (argc == 2) {
+ servername = argv[1];
+ } else {
+ servername = lp_netbios_name();
+ }
+
+ /* Get printer data from SPOOLSS */
+
+ ok = resolve_name(servername, &server_ss, 0x20, false);
+ if (!ok) {
+ d_fprintf(stderr, _("Could not find server %s\n"),
+ servername);
+ goto out;
+ }
+
+ cli_credentials_set_kerberos_state(c->creds,
+ CRED_USE_KERBEROS_REQUIRED,
+ CRED_SPECIFIED);
+
+ nt_status = cli_full_connection_creds(&cli, lp_netbios_name(), servername,
+ &server_ss, 0,
+ "IPC$", "IPC",
+ c->creds,
+ CLI_FULL_CONNECTION_IPC);
+
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ d_fprintf(stderr, _("Unable to open a connection to %s to "
+ "obtain data for %s\n"),
+ servername, printername);
+ goto out;
+ }
+
+ /* Publish on AD server */
+
+ ads_find_machine_acct(ads, &res, servername);
+
+ if (ads_count_replies(ads, res) == 0) {
+ d_fprintf(stderr, _("Could not find machine account for server "
+ "%s\n"),
+ servername);
+ goto out;
+ }
+
+ srv_dn = ldap_get_dn((LDAP *)ads->ldap.ld, (LDAPMessage *)res);
+ srv_cn = ldap_explode_dn(srv_dn, 1);
+
+ srv_cn_escaped = escape_rdn_val_string_alloc(srv_cn[0]);
+ printername_escaped = escape_rdn_val_string_alloc(printername);
+ if (!srv_cn_escaped || !printername_escaped) {
+ SAFE_FREE(srv_cn_escaped);
+ SAFE_FREE(printername_escaped);
+ d_fprintf(stderr, _("Internal error, out of memory!"));
+ goto out;
+ }
+
+ prt_dn = talloc_asprintf(tmp_ctx,
+ "cn=%s-%s,%s",
+ srv_cn_escaped,
+ printername_escaped,
+ srv_dn);
+ if (prt_dn == NULL) {
+ SAFE_FREE(srv_cn_escaped);
+ SAFE_FREE(printername_escaped);
+ d_fprintf(stderr, _("Internal error, out of memory!"));
+ goto out;
+ }
+
+ SAFE_FREE(srv_cn_escaped);
+ SAFE_FREE(printername_escaped);
+
+ nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_spoolss, &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr, _("Unable to open a connection to the spoolss pipe on %s\n"),
+ servername);
+ goto out;
+ }
+
+ if (!W_ERROR_IS_OK(get_remote_printer_publishing_data(pipe_hnd,
+ tmp_ctx,
+ &mods,
+ printername))) {
+ goto out;
+ }
+
+ status = ads_add_printer_entry(ads, prt_dn, tmp_ctx, &mods);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, "ads_publish_printer: %s\n",
+ ads_errstr(status));
+ goto out;
+ }
+
+ d_printf("published printer\n");
+
+ ret = 0;
+out:
+ talloc_destroy(tmp_ctx);
+
+ return ret;
+}
+
+static int net_ads_printer_remove(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *servername = NULL;
+ char *prt_dn = NULL;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads printer remove <printername> [servername]\n"
+ " Remove a printer from the AD\n"
+ " printername\tName of the printer\n"
+ " servername\tName of the print server\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (argc > 1) {
+ servername = argv[1];
+ } else {
+ servername = lp_netbios_name();
+ }
+
+ status = ads_find_printer_on_server(ads, &res, argv[0], servername);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_find_printer_on_server: %s\n"),
+ ads_errstr(status));
+ goto out;
+ }
+
+ if (ads_count_replies(ads, res) == 0) {
+ d_fprintf(stderr, _("Printer '%s' not found\n"), argv[1]);
+ goto out;
+ }
+
+ prt_dn = ads_get_dn(ads, tmp_ctx, res);
+ if (prt_dn == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+
+ status = ads_del_dn(ads, prt_dn);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("ads_del_dn: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_printer(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "search",
+ net_ads_printer_search,
+ NET_TRANSPORT_ADS,
+ N_("Search for a printer"),
+ N_("net ads printer search\n"
+ " Search for a printer")
+ },
+ {
+ "info",
+ net_ads_printer_info,
+ NET_TRANSPORT_ADS,
+ N_("Display printer information"),
+ N_("net ads printer info\n"
+ " Display printer information")
+ },
+ {
+ "publish",
+ net_ads_printer_publish,
+ NET_TRANSPORT_ADS,
+ N_("Publish a printer"),
+ N_("net ads printer publish\n"
+ " Publish a printer")
+ },
+ {
+ "remove",
+ net_ads_printer_remove,
+ NET_TRANSPORT_ADS,
+ N_("Delete a printer"),
+ N_("net ads printer remove\n"
+ " Delete a printer")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads printer", func);
+}
+
+
+static int net_ads_password(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ const char *auth_principal = cli_credentials_get_username(c->creds);
+ const char *auth_password = cli_credentials_get_password(c->creds);
+ const char *realm = NULL;
+ char *new_password = NULL;
+ char *chr = NULL;
+ char *prompt = NULL;
+ const char *user = NULL;
+ char pwd[256] = {0};
+ ADS_STATUS status;
+ int ret = 0;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads password <username>\n"
+ " Change password for user\n"
+ " username\tName of user to change password for\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (auth_principal == NULL || auth_password == NULL) {
+ d_fprintf(stderr, _("You must supply an administrator "
+ "username/password\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (argc < 1) {
+ d_fprintf(stderr, _("ERROR: You must say which username to "
+ "change password for\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (strchr_m(argv[0], '@')) {
+ user = talloc_strdup(tmp_ctx, argv[0]);
+ } else {
+ user = talloc_asprintf(tmp_ctx, "%s@%s", argv[0], lp_realm());
+ }
+ if (user == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+
+ use_in_memory_ccache();
+ chr = strchr_m(auth_principal, '@');
+ if (chr) {
+ realm = ++chr;
+ } else {
+ realm = lp_realm();
+ }
+
+ /* use the realm so we can eventually change passwords for users
+ in realms other than default */
+ ads = ads_init(tmp_ctx,
+ realm,
+ c->opt_workgroup,
+ c->opt_host,
+ ADS_SASL_PLAIN);
+ if (ads == NULL) {
+ goto out;
+ }
+
+ /* we don't actually need a full connect, but it's the easy way to
+ fill in the KDC's address */
+ ads_connect(ads);
+
+ if (!ads->config.realm) {
+ d_fprintf(stderr, _("Didn't find the kerberos server!\n"));
+ goto out;
+ }
+
+ if (argv[1] != NULL) {
+ new_password = talloc_strdup(tmp_ctx, argv[1]);
+ } else {
+ int rc;
+
+ prompt = talloc_asprintf(tmp_ctx, _("Enter new password for %s:"), user);
+ if (prompt == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+
+ rc = samba_getpass(prompt, pwd, sizeof(pwd), false, true);
+ if (rc < 0) {
+ goto out;
+ }
+ new_password = talloc_strdup(tmp_ctx, pwd);
+ memset(pwd, '\0', sizeof(pwd));
+ }
+
+ if (new_password == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+
+ status = kerberos_set_password(ads->auth.kdc_server,
+ auth_principal,
+ auth_password,
+ user,
+ new_password,
+ ads->auth.time_offset);
+ memset(new_password, '\0', strlen(new_password));
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Password change failed: %s\n"),
+ ads_errstr(status));
+ goto out;
+ }
+
+ d_printf(_("Password change for %s completed.\n"), user);
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+int net_ads_changetrustpw(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ char *host_principal = NULL;
+ char *my_name = NULL;
+ ADS_STATUS status;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads changetrustpw\n"
+ " %s\n",
+ _("Usage:"),
+ _("Change the machine account's trust password"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (!secrets_init()) {
+ DEBUG(1,("Failed to initialise secrets database\n"));
+ goto out;
+ }
+
+ net_warn_member_options();
+
+ net_use_krb_machine_account(c);
+
+ use_in_memory_ccache();
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ my_name = talloc_asprintf_strlower_m(tmp_ctx, "%s", lp_netbios_name());
+ if (my_name == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+
+ host_principal = talloc_asprintf(tmp_ctx, "%s$@%s", my_name, ads->config.realm);
+ if (host_principal == NULL) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ goto out;
+ }
+
+ d_printf(_("Changing password for principal: %s\n"), host_principal);
+
+ status = ads_change_trust_account_password(ads, host_principal);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("Password change failed: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ d_printf(_("Password change for principal %s succeeded.\n"), host_principal);
+
+ if (USE_SYSTEM_KEYTAB) {
+ d_printf(_("Attempting to update system keytab with new password.\n"));
+ if (ads_keytab_create_default(ads)) {
+ d_printf(_("Failed to update system keytab.\n"));
+ }
+ }
+
+ ret = 0;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return ret;
+}
+
+/*
+ help for net ads search
+*/
+static int net_ads_search_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "\nnet ads search <expression> <attributes...>\n"
+ "\nPerform a raw LDAP search on a ADS server and dump the results.\n"
+ "The expression is a standard LDAP search expression, and the\n"
+ "attributes are a list of LDAP fields to show in the results.\n\n"
+ "Example: net ads search '(objectCategory=group)' sAMAccountName\n\n"
+ ));
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+
+/*
+ general ADS search function. Useful in diagnosing problems in ADS
+*/
+static int net_ads_search(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *ldap_exp = NULL;
+ const char **attrs = NULL;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+
+ if (argc < 1 || c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_search_usage(c, argc, argv);
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ ldap_exp = argv[0];
+ attrs = (argv + 1);
+
+ status = ads_do_search_retry(ads,
+ ads->config.bind_path,
+ LDAP_SCOPE_SUBTREE,
+ ldap_exp,
+ attrs,
+ &res);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("search failed: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ d_printf(_("Got %d replies\n\n"), ads_count_replies(ads, res));
+
+ /* dump the results */
+ ads_dump(ads, res);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+
+/*
+ help for net ads search
+*/
+static int net_ads_dn_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "\nnet ads dn <dn> <attributes...>\n"
+ "\nperform a raw LDAP search on a ADS server and dump the results\n"
+ "The DN standard LDAP DN, and the attributes are a list of LDAP fields \n"
+ "to show in the results\n\n"
+ "Example: net ads dn 'CN=administrator,CN=Users,DC=my,DC=domain' sAMAccountName\n\n"
+ "Note: the DN must be provided properly escaped. See RFC 4514 for details\n\n"
+ ));
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+
+/*
+ general ADS search function. Useful in diagnosing problems in ADS
+*/
+static int net_ads_dn(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *dn = NULL;
+ const char **attrs = NULL;
+ LDAPMessage *res = NULL;
+ int ret = -1;
+
+ if (argc < 1 || c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_dn_usage(c, argc, argv);
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ dn = argv[0];
+ attrs = (argv + 1);
+
+ status = ads_do_search_all(ads,
+ dn,
+ LDAP_SCOPE_BASE,
+ "(objectclass=*)",
+ attrs,
+ &res);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("search failed: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ d_printf("Got %d replies\n\n", ads_count_replies(ads, res));
+
+ /* dump the results */
+ ads_dump(ads, res);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+/*
+ help for net ads sid search
+*/
+static int net_ads_sid_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "\nnet ads sid <sid> <attributes...>\n"
+ "\nperform a raw LDAP search on a ADS server and dump the results\n"
+ "The SID is in string format, and the attributes are a list of LDAP fields \n"
+ "to show in the results\n\n"
+ "Example: net ads sid 'S-1-5-32' distinguishedName\n\n"
+ ));
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+
+/*
+ general ADS search function. Useful in diagnosing problems in ADS
+*/
+static int net_ads_sid(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ const char *sid_string = NULL;
+ const char **attrs = NULL;
+ LDAPMessage *res = NULL;
+ struct dom_sid sid = { 0 };
+ int ret = -1;
+
+ if (argc < 1 || c->display_usage) {
+ TALLOC_FREE(tmp_ctx);
+ return net_ads_sid_usage(c, argc, argv);
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ sid_string = argv[0];
+ attrs = (argv + 1);
+
+ if (!string_to_sid(&sid, sid_string)) {
+ d_fprintf(stderr, _("could not convert sid\n"));
+ goto out;
+ }
+
+ status = ads_search_retry_sid(ads, &res, &sid, attrs);
+ if (!ADS_ERR_OK(status)) {
+ d_fprintf(stderr, _("search failed: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ d_printf(_("Got %d replies\n\n"), ads_count_replies(ads, res));
+
+ /* dump the results */
+ ads_dump(ads, res);
+
+ ret = 0;
+out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_keytab_flush(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads keytab flush\n"
+ " %s\n",
+ _("Usage:"),
+ _("Delete the whole keytab"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (!c->opt_user_specified && c->opt_password == NULL) {
+ net_use_krb_machine_account(c);
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ ret = ads_keytab_flush(ads);
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_keytab_add(struct net_context *c,
+ int argc,
+ const char **argv,
+ bool update_ads)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ int i;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads keytab add <principal> [principal ...]\n"
+ " Add principals to local keytab\n"
+ " principal\tKerberos principal to add to "
+ "keytab\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ net_warn_member_options();
+
+ d_printf(_("Processing principals to add...\n"));
+
+ if (!c->opt_user_specified && c->opt_password == NULL) {
+ net_use_krb_machine_account(c);
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ for (ret = 0, i = 0; i < argc; i++) {
+ ret |= ads_keytab_add_entry(ads, argv[i], update_ads);
+ }
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_keytab_add_default(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ return net_ads_keytab_add(c, argc, argv, false);
+}
+
+static int net_ads_keytab_add_update_ads(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ return net_ads_keytab_add(c, argc, argv, true);
+}
+
+static int net_ads_keytab_delete(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ int i;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads keytab delete <principal> [principal ...]\n"
+ " Remove entries for service principal, "
+ " from the keytab file only."
+ " Remove principals from local keytab\n"
+ " principal\tKerberos principal to remove from "
+ "keytab\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ d_printf(_("Processing principals to delete...\n"));
+
+ if (!c->opt_user_specified && c->opt_password == NULL) {
+ net_use_krb_machine_account(c);
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ for (ret = 0, i = 0; i < argc; i++) {
+ ret |= ads_keytab_delete_entry(ads, argv[i]);
+ }
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_keytab_create(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads keytab create\n"
+ " %s\n",
+ _("Usage:"),
+ _("Create new default keytab"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ net_warn_member_options();
+
+ if (!c->opt_user_specified && c->opt_password == NULL) {
+ net_use_krb_machine_account(c);
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ ret = ads_keytab_create_default(ads);
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_keytab_list(struct net_context *c, int argc, const char **argv)
+{
+ const char *keytab = NULL;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads keytab list [keytab]\n"
+ " List a local keytab\n"
+ " keytab\tKeytab to list\n"));
+ return -1;
+ }
+
+ if (argc >= 1) {
+ keytab = argv[0];
+ }
+
+ return ads_keytab_list(keytab);
+}
+
+
+int net_ads_keytab(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ net_ads_keytab_add_default,
+ NET_TRANSPORT_ADS,
+ N_("Add a service principal"),
+ N_("net ads keytab add\n"
+ " Add a service principal, updates keytab file only.")
+ },
+ {
+ "delete",
+ net_ads_keytab_delete,
+ NET_TRANSPORT_ADS,
+ N_("Delete a service principal"),
+ N_("net ads keytab delete\n"
+ " Remove entries for service principal, from the keytab file only.")
+ },
+ {
+ "add_update_ads",
+ net_ads_keytab_add_update_ads,
+ NET_TRANSPORT_ADS,
+ N_("Add a service principal"),
+ N_("net ads keytab add_update_ads\n"
+ " Add a service principal, depending on the param passed may update ADS computer object in addition to the keytab file.")
+ },
+ {
+ "create",
+ net_ads_keytab_create,
+ NET_TRANSPORT_ADS,
+ N_("Create a fresh keytab"),
+ N_("net ads keytab create\n"
+ " Create a fresh keytab or update existing one.")
+ },
+ {
+ "flush",
+ net_ads_keytab_flush,
+ NET_TRANSPORT_ADS,
+ N_("Remove all keytab entries"),
+ N_("net ads keytab flush\n"
+ " Remove all keytab entries")
+ },
+ {
+ "list",
+ net_ads_keytab_list,
+ NET_TRANSPORT_ADS,
+ N_("List a keytab"),
+ N_("net ads keytab list\n"
+ " List a keytab")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (!USE_KERBEROS_KEYTAB) {
+ d_printf(_("\nWarning: \"kerberos method\" must be set to a "
+ "keytab method to use keytab functions.\n"));
+ }
+
+ return net_run_function(c, argc, argv, "net ads keytab", func);
+}
+
+static int net_ads_kerberos_renew(struct net_context *c, int argc, const char **argv)
+{
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads kerberos renew\n"
+ " %s\n",
+ _("Usage:"),
+ _("Renew TGT from existing credential cache"));
+ return -1;
+ }
+
+ ret = smb_krb5_renew_ticket(NULL, NULL, NULL, NULL);
+ if (ret) {
+ d_printf(_("failed to renew kerberos ticket: %s\n"),
+ error_message(ret));
+ }
+ return ret;
+}
+
+static int net_ads_kerberos_pac_common(struct net_context *c, int argc, const char **argv,
+ struct PAC_DATA_CTR **pac_data_ctr)
+{
+ NTSTATUS status;
+ int ret = -1;
+ const char *impersonate_princ_s = NULL;
+ const char *local_service = NULL;
+ int i;
+
+ for (i=0; i<argc; i++) {
+ if (strnequal(argv[i], "impersonate", strlen("impersonate"))) {
+ impersonate_princ_s = get_string_param(argv[i]);
+ if (impersonate_princ_s == NULL) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "local_service", strlen("local_service"))) {
+ local_service = get_string_param(argv[i]);
+ if (local_service == NULL) {
+ return -1;
+ }
+ }
+ }
+
+ if (local_service == NULL) {
+ local_service = talloc_asprintf(c, "%s$@%s",
+ lp_netbios_name(), lp_realm());
+ if (local_service == NULL) {
+ goto out;
+ }
+ }
+
+ c->opt_password = net_prompt_pass(c, c->opt_user_name);
+
+ status = kerberos_return_pac(c,
+ c->opt_user_name,
+ c->opt_password,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ true,
+ true,
+ 2592000, /* one month */
+ impersonate_princ_s,
+ local_service,
+ NULL,
+ NULL,
+ pac_data_ctr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("failed to query kerberos PAC: %s\n"),
+ nt_errstr(status));
+ goto out;
+ }
+
+ ret = 0;
+ out:
+ return ret;
+}
+
+static int net_ads_kerberos_pac_dump(struct net_context *c, int argc, const char **argv)
+{
+ struct PAC_DATA_CTR *pac_data_ctr = NULL;
+ int i, num_buffers;
+ int ret = -1;
+ enum PAC_TYPE type = 0;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads kerberos pac dump [impersonate=string] [local_service=string] [pac_buffer_type=int]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Dump the Kerberos PAC"));
+ return -1;
+ }
+
+ for (i=0; i<argc; i++) {
+ if (strnequal(argv[i], "pac_buffer_type", strlen("pac_buffer_type"))) {
+ type = get_int_param(argv[i]);
+ }
+ }
+
+ ret = net_ads_kerberos_pac_common(c, argc, argv, &pac_data_ctr);
+ if (ret) {
+ return ret;
+ }
+
+ if (type == 0) {
+
+ char *s = NULL;
+
+ s = NDR_PRINT_STRUCT_STRING(c, PAC_DATA,
+ pac_data_ctr->pac_data);
+ if (s != NULL) {
+ d_printf(_("The Pac: %s\n"), s);
+ talloc_free(s);
+ }
+
+ return 0;
+ }
+
+ num_buffers = pac_data_ctr->pac_data->num_buffers;
+
+ for (i=0; i<num_buffers; i++) {
+
+ char *s = NULL;
+
+ if (pac_data_ctr->pac_data->buffers[i].type != type) {
+ continue;
+ }
+
+ s = NDR_PRINT_UNION_STRING(c, PAC_INFO, type,
+ pac_data_ctr->pac_data->buffers[i].info);
+ if (s != NULL) {
+ d_printf(_("The Pac: %s\n"), s);
+ talloc_free(s);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+static int net_ads_kerberos_pac_save(struct net_context *c, int argc, const char **argv)
+{
+ struct PAC_DATA_CTR *pac_data_ctr = NULL;
+ char *filename = NULL;
+ int ret = -1;
+ int i;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads kerberos pac save [impersonate=string] [local_service=string] [filename=string]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Save the Kerberos PAC"));
+ return -1;
+ }
+
+ for (i=0; i<argc; i++) {
+ if (strnequal(argv[i], "filename", strlen("filename"))) {
+ filename = get_string_param(argv[i]);
+ if (filename == NULL) {
+ return -1;
+ }
+ }
+ }
+
+ ret = net_ads_kerberos_pac_common(c, argc, argv, &pac_data_ctr);
+ if (ret) {
+ return ret;
+ }
+
+ if (filename == NULL) {
+ d_printf(_("please define \"filename=<filename>\" to save the PAC\n"));
+ return -1;
+ }
+
+ /* save the raw format */
+ if (!file_save(filename, pac_data_ctr->pac_blob.data, pac_data_ctr->pac_blob.length)) {
+ d_printf(_("failed to save PAC in %s\n"), filename);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int net_ads_kerberos_pac(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "dump",
+ net_ads_kerberos_pac_dump,
+ NET_TRANSPORT_ADS,
+ N_("Dump Kerberos PAC"),
+ N_("net ads kerberos pac dump\n"
+ " Dump a Kerberos PAC to stdout")
+ },
+ {
+ "save",
+ net_ads_kerberos_pac_save,
+ NET_TRANSPORT_ADS,
+ N_("Save Kerberos PAC"),
+ N_("net ads kerberos pac save\n"
+ " Save a Kerberos PAC in a file")
+ },
+
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads kerberos pac", func);
+}
+
+static int net_ads_kerberos_kinit(struct net_context *c, int argc, const char **argv)
+{
+ int ret = -1;
+ NTSTATUS status;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads kerberos kinit\n"
+ " %s\n",
+ _("Usage:"),
+ _("Get Ticket Granting Ticket (TGT) for the user"));
+ return -1;
+ }
+
+ c->opt_password = net_prompt_pass(c, c->opt_user_name);
+
+ ret = kerberos_kinit_password_ext(c->opt_user_name,
+ c->opt_password,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ true,
+ true,
+ 2592000, /* one month */
+ NULL,
+ NULL,
+ NULL,
+ &status);
+ if (ret) {
+ d_printf(_("failed to kinit password: %s\n"),
+ nt_errstr(status));
+ }
+ return ret;
+}
+
+int net_ads_kerberos(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "kinit",
+ net_ads_kerberos_kinit,
+ NET_TRANSPORT_ADS,
+ N_("Retrieve Ticket Granting Ticket (TGT)"),
+ N_("net ads kerberos kinit\n"
+ " Receive Ticket Granting Ticket (TGT)")
+ },
+ {
+ "renew",
+ net_ads_kerberos_renew,
+ NET_TRANSPORT_ADS,
+ N_("Renew Ticket Granting Ticket from credential cache"),
+ N_("net ads kerberos renew\n"
+ " Renew Ticket Granting Ticket (TGT) from "
+ "credential cache")
+ },
+ {
+ "pac",
+ net_ads_kerberos_pac,
+ NET_TRANSPORT_ADS,
+ N_("Dump Kerberos PAC"),
+ N_("net ads kerberos pac\n"
+ " Dump Kerberos PAC")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads kerberos", func);
+}
+
+static int net_ads_setspn_list(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ bool ok = false;
+ int ret = -1;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads setspn list <machinename>\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (argc) {
+ ok = ads_setspn_list(ads, argv[0]);
+ } else {
+ ok = ads_setspn_list(ads, lp_netbios_name());
+ }
+
+ ret = ok ? 0 : -1;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_setspn_add(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ bool ok = false;
+ int ret = -1;
+
+ if (c->display_usage || argc < 1) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads setspn add <machinename> SPN\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (argc > 1) {
+ ok = ads_setspn_add(ads, argv[0], argv[1]);
+ } else {
+ ok = ads_setspn_add(ads, lp_netbios_name(), argv[0]);
+ }
+
+ ret = ok ? 0 : -1;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_setspn_delete(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ bool ok = false;
+ int ret = -1;
+
+ if (c->display_usage || argc < 1) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net ads setspn delete <machinename> SPN\n"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, true, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (argc > 1) {
+ ok = ads_setspn_delete(ads, argv[0], argv[1]);
+ } else {
+ ok = ads_setspn_delete(ads, lp_netbios_name(), argv[0]);
+ }
+
+ ret = ok ? 0 : -1;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+int net_ads_setspn(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ net_ads_setspn_list,
+ NET_TRANSPORT_ADS,
+ N_("List Service Principal Names (SPN)"),
+ N_("net ads setspn list machine\n"
+ " List Service Principal Names (SPN)")
+ },
+ {
+ "add",
+ net_ads_setspn_add,
+ NET_TRANSPORT_ADS,
+ N_("Add Service Principal Names (SPN)"),
+ N_("net ads setspn add machine spn\n"
+ " Add Service Principal Names (SPN)")
+ },
+ {
+ "delete",
+ net_ads_setspn_delete,
+ NET_TRANSPORT_ADS,
+ N_("Delete Service Principal Names (SPN)"),
+ N_("net ads setspn delete machine spn\n"
+ " Delete Service Principal Names (SPN)")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads setspn", func);
+}
+
+static int net_ads_enctype_lookup_account(struct net_context *c,
+ ADS_STRUCT *ads,
+ const char *account,
+ LDAPMessage **res,
+ const char **enctype_str)
+{
+ const char *filter;
+ const char *attrs[] = {
+ "msDS-SupportedEncryptionTypes",
+ NULL
+ };
+ int count;
+ int ret = -1;
+ ADS_STATUS status;
+
+ filter = talloc_asprintf(c, "(&(objectclass=user)(sAMAccountName=%s))",
+ account);
+ if (filter == NULL) {
+ goto done;
+ }
+
+ status = ads_search(ads, res, filter, attrs);
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("no account found with filter: %s\n"), filter);
+ goto done;
+ }
+
+ count = ads_count_replies(ads, *res);
+ switch (count) {
+ case 1:
+ break;
+ case 0:
+ d_printf(_("no account found with filter: %s\n"), filter);
+ goto done;
+ default:
+ d_printf(_("multiple accounts found with filter: %s\n"), filter);
+ goto done;
+ }
+
+ if (enctype_str) {
+ *enctype_str = ads_pull_string(ads, c, *res,
+ "msDS-SupportedEncryptionTypes");
+ if (*enctype_str == NULL) {
+ d_printf(_("no msDS-SupportedEncryptionTypes attribute found\n"));
+ goto done;
+ }
+ }
+
+ ret = 0;
+ done:
+ return ret;
+}
+
+static void net_ads_enctype_dump_enctypes(const char *username,
+ const char *enctype_str)
+{
+ int enctypes = atoi(enctype_str);
+
+ d_printf(_("'%s' uses \"msDS-SupportedEncryptionTypes\": %d (0x%08x)\n"),
+ username, enctypes, enctypes);
+
+ printf("[%s] 0x%08x DES-CBC-CRC\n",
+ enctypes & ENC_CRC32 ? "X" : " ",
+ ENC_CRC32);
+ printf("[%s] 0x%08x DES-CBC-MD5\n",
+ enctypes & ENC_RSA_MD5 ? "X" : " ",
+ ENC_RSA_MD5);
+ printf("[%s] 0x%08x RC4-HMAC\n",
+ enctypes & ENC_RC4_HMAC_MD5 ? "X" : " ",
+ ENC_RC4_HMAC_MD5);
+ printf("[%s] 0x%08x AES128-CTS-HMAC-SHA1-96\n",
+ enctypes & ENC_HMAC_SHA1_96_AES128 ? "X" : " ",
+ ENC_HMAC_SHA1_96_AES128);
+ printf("[%s] 0x%08x AES256-CTS-HMAC-SHA1-96\n",
+ enctypes & ENC_HMAC_SHA1_96_AES256 ? "X" : " ",
+ ENC_HMAC_SHA1_96_AES256);
+ printf("[%s] 0x%08x AES256-CTS-HMAC-SHA1-96-SK\n",
+ enctypes & ENC_HMAC_SHA1_96_AES256_SK ? "X" : " ",
+ ENC_HMAC_SHA1_96_AES256_SK);
+ printf("[%s] 0x%08x RESOURCE-SID-COMPRESSION-DISABLED\n",
+ enctypes & KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED ? "X" : " ",
+ KERB_ENCTYPE_RESOURCE_SID_COMPRESSION_DISABLED);
+}
+
+static int net_ads_enctypes_list(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STATUS status;
+ ADS_STRUCT *ads = NULL;
+ LDAPMessage *res = NULL;
+ const char *str = NULL;
+ int ret = -1;
+
+ if (c->display_usage || (argc < 1)) {
+ d_printf( "%s\n"
+ "net ads enctypes list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List supported enctypes"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ ret = net_ads_enctype_lookup_account(c, ads, argv[0], &res, &str);
+ if (ret) {
+ goto out;
+ }
+
+ net_ads_enctype_dump_enctypes(argv[0], str);
+
+ ret = 0;
+ out:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_enctypes_set(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ int ret = -1;
+ ADS_STATUS status;
+ ADS_STRUCT *ads = NULL;
+ LDAPMessage *res = NULL;
+ const char *etype_list_str = NULL;
+ const char *dn = NULL;
+ ADS_MODLIST mods = NULL;
+ uint32_t etype_list;
+ const char *str = NULL;
+
+ if (c->display_usage || argc < 1) {
+ d_printf( "%s\n"
+ "net ads enctypes set <sAMAccountName> [enctypes]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Set supported enctypes"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ ret = net_ads_enctype_lookup_account(c, ads, argv[0], &res, NULL);
+ if (ret) {
+ goto done;
+ }
+
+ dn = ads_get_dn(ads, tmp_ctx, res);
+ if (dn == NULL) {
+ goto done;
+ }
+
+ etype_list = 0;
+ etype_list |= ENC_RC4_HMAC_MD5;
+ etype_list |= ENC_HMAC_SHA1_96_AES128;
+ etype_list |= ENC_HMAC_SHA1_96_AES256;
+
+ if (argv[1] != NULL) {
+ sscanf(argv[1], "%i", &etype_list);
+ }
+
+ etype_list_str = talloc_asprintf(tmp_ctx, "%d", etype_list);
+ if (!etype_list_str) {
+ goto done;
+ }
+
+ mods = ads_init_mods(tmp_ctx);
+ if (!mods) {
+ goto done;
+ }
+
+ status = ads_mod_str(tmp_ctx, &mods, "msDS-SupportedEncryptionTypes",
+ etype_list_str);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = ads_gen_mod(ads, dn, mods);
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("failed to add msDS-SupportedEncryptionTypes: %s\n"),
+ ads_errstr(status));
+ goto done;
+ }
+
+ ads_msgfree(ads, res);
+ res = NULL;
+
+ ret = net_ads_enctype_lookup_account(c, ads, argv[0], &res, &str);
+ if (ret) {
+ goto done;
+ }
+
+ net_ads_enctype_dump_enctypes(argv[0], str);
+
+ ret = 0;
+ done:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_enctypes_delete(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ int ret = -1;
+ ADS_STATUS status;
+ ADS_STRUCT *ads = NULL;
+ LDAPMessage *res = NULL;
+ const char *dn = NULL;
+ ADS_MODLIST mods = NULL;
+
+ if (c->display_usage || argc < 1) {
+ d_printf( "%s\n"
+ "net ads enctypes delete <sAMAccountName>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Delete supported enctypes"));
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ status = ads_startup(c, false, tmp_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ ret = net_ads_enctype_lookup_account(c, ads, argv[0], &res, NULL);
+ if (ret) {
+ goto done;
+ }
+
+ dn = ads_get_dn(ads, tmp_ctx, res);
+ if (dn == NULL) {
+ goto done;
+ }
+
+ mods = ads_init_mods(tmp_ctx);
+ if (!mods) {
+ goto done;
+ }
+
+ status = ads_mod_str(tmp_ctx, &mods, "msDS-SupportedEncryptionTypes", NULL);
+ if (!ADS_ERR_OK(status)) {
+ goto done;
+ }
+
+ status = ads_gen_mod(ads, dn, mods);
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("failed to remove msDS-SupportedEncryptionTypes: %s\n"),
+ ads_errstr(status));
+ goto done;
+ }
+
+ ret = 0;
+
+ done:
+ ads_msgfree(ads, res);
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static int net_ads_enctypes(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ net_ads_enctypes_list,
+ NET_TRANSPORT_ADS,
+ N_("List the supported encryption types"),
+ N_("net ads enctypes list\n"
+ " List the supported encryption types")
+ },
+ {
+ "set",
+ net_ads_enctypes_set,
+ NET_TRANSPORT_ADS,
+ N_("Set the supported encryption types"),
+ N_("net ads enctypes set\n"
+ " Set the supported encryption types")
+ },
+ {
+ "delete",
+ net_ads_enctypes_delete,
+ NET_TRANSPORT_ADS,
+ N_("Delete the supported encryption types"),
+ N_("net ads enctypes delete\n"
+ " Delete the supported encryption types")
+ },
+
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads enctypes", func);
+}
+
+
+int net_ads(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "info",
+ net_ads_info,
+ NET_TRANSPORT_ADS,
+ N_("Display details on remote ADS server"),
+ N_("net ads info\n"
+ " Display details on remote ADS server")
+ },
+ {
+ "join",
+ net_ads_join,
+ NET_TRANSPORT_ADS,
+ N_("Join the local machine to ADS realm"),
+ N_("net ads join\n"
+ " Join the local machine to ADS realm")
+ },
+ {
+ "testjoin",
+ net_ads_testjoin,
+ NET_TRANSPORT_ADS,
+ N_("Validate machine account"),
+ N_("net ads testjoin\n"
+ " Validate machine account")
+ },
+ {
+ "leave",
+ net_ads_leave,
+ NET_TRANSPORT_ADS,
+ N_("Remove the local machine from ADS"),
+ N_("net ads leave\n"
+ " Remove the local machine from ADS")
+ },
+ {
+ "status",
+ net_ads_status,
+ NET_TRANSPORT_ADS,
+ N_("Display machine account details"),
+ N_("net ads status\n"
+ " Display machine account details")
+ },
+ {
+ "user",
+ net_ads_user,
+ NET_TRANSPORT_ADS,
+ N_("List/modify users"),
+ N_("net ads user\n"
+ " List/modify users")
+ },
+ {
+ "group",
+ net_ads_group,
+ NET_TRANSPORT_ADS,
+ N_("List/modify groups"),
+ N_("net ads group\n"
+ " List/modify groups")
+ },
+ {
+ "dns",
+ net_ads_dns,
+ NET_TRANSPORT_ADS,
+ N_("Issue dynamic DNS update"),
+ N_("net ads dns\n"
+ " Issue dynamic DNS update")
+ },
+ {
+ "password",
+ net_ads_password,
+ NET_TRANSPORT_ADS,
+ N_("Change user passwords"),
+ N_("net ads password\n"
+ " Change user passwords")
+ },
+ {
+ "changetrustpw",
+ net_ads_changetrustpw,
+ NET_TRANSPORT_ADS,
+ N_("Change trust account password"),
+ N_("net ads changetrustpw\n"
+ " Change trust account password")
+ },
+ {
+ "printer",
+ net_ads_printer,
+ NET_TRANSPORT_ADS,
+ N_("List/modify printer entries"),
+ N_("net ads printer\n"
+ " List/modify printer entries")
+ },
+ {
+ "search",
+ net_ads_search,
+ NET_TRANSPORT_ADS,
+ N_("Issue LDAP search using filter"),
+ N_("net ads search\n"
+ " Issue LDAP search using filter")
+ },
+ {
+ "dn",
+ net_ads_dn,
+ NET_TRANSPORT_ADS,
+ N_("Issue LDAP search by DN"),
+ N_("net ads dn\n"
+ " Issue LDAP search by DN")
+ },
+ {
+ "sid",
+ net_ads_sid,
+ NET_TRANSPORT_ADS,
+ N_("Issue LDAP search by SID"),
+ N_("net ads sid\n"
+ " Issue LDAP search by SID")
+ },
+ {
+ "workgroup",
+ net_ads_workgroup,
+ NET_TRANSPORT_ADS,
+ N_("Display workgroup name"),
+ N_("net ads workgroup\n"
+ " Display the workgroup name")
+ },
+ {
+ "lookup",
+ net_ads_lookup,
+ NET_TRANSPORT_ADS,
+ N_("Perform CLDAP query on DC"),
+ N_("net ads lookup\n"
+ " Find the ADS DC using CLDAP lookups")
+ },
+ {
+ "keytab",
+ net_ads_keytab,
+ NET_TRANSPORT_ADS,
+ N_("Manage local keytab file"),
+ N_("net ads keytab\n"
+ " Manage local keytab file")
+ },
+ {
+ "setspn",
+ net_ads_setspn,
+ NET_TRANSPORT_ADS,
+ N_("Manage Service Principal Names (SPN)s"),
+ N_("net ads spnset\n"
+ " Manage Service Principal Names (SPN)s")
+ },
+ {
+ "gpo",
+ net_ads_gpo,
+ NET_TRANSPORT_ADS,
+ N_("Manage group policy objects"),
+ N_("net ads gpo\n"
+ " Manage group policy objects")
+ },
+ {
+ "kerberos",
+ net_ads_kerberos,
+ NET_TRANSPORT_ADS,
+ N_("Manage kerberos keytab"),
+ N_("net ads kerberos\n"
+ " Manage kerberos keytab")
+ },
+ {
+ "enctypes",
+ net_ads_enctypes,
+ NET_TRANSPORT_ADS,
+ N_("List/modify supported encryption types"),
+ N_("net ads enctypes\n"
+ " List/modify enctypes")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads", func);
+}
+
+#else
+
+static int net_ads_noads(void)
+{
+ d_fprintf(stderr, _("ADS support not compiled in\n"));
+ return -1;
+}
+
+int net_ads_keytab(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+int net_ads_kerberos(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+int net_ads_setspn(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+int net_ads_changetrustpw(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+int net_ads_join(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+int net_ads_user(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+int net_ads_group(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+int net_ads_gpo(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+/* this one shouldn't display a message */
+int net_ads_check(struct net_context *c)
+{
+ return -1;
+}
+
+int net_ads_check_our_domain(struct net_context *c)
+{
+ return -1;
+}
+
+int net_ads(struct net_context *c, int argc, const char **argv)
+{
+ return net_ads_noads();
+}
+
+#endif /* HAVE_ADS */
diff --git a/source3/utils/net_ads_gpo.c b/source3/utils/net_ads_gpo.c
new file mode 100644
index 0000000..12d8e22
--- /dev/null
+++ b/source3/utils/net_ads_gpo.c
@@ -0,0 +1,428 @@
+/*
+ Samba Unix/Linux SMB client library
+ net ads commands for Group Policy
+ Copyright (C) 2005-2008 Guenther Deschner (gd@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "ads.h"
+#include "../libgpo/gpo.h"
+#include "libgpo/gpo_proto.h"
+#include "../libds/common/flags.h"
+
+#ifdef HAVE_ADS
+
+static int net_ads_gpo_list_all(struct net_context *c, int argc, const char **argv)
+{
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ int num_reply = 0;
+ LDAPMessage *msg = NULL;
+ struct GROUP_POLICY_OBJECT gpo;
+ TALLOC_CTX *mem_ctx;
+ char *dn;
+ const char *attrs[] = {
+ "versionNumber",
+ "flags",
+ "gPCFileSysPath",
+ "displayName",
+ "name",
+ "gPCMachineExtensionNames",
+ "gPCUserExtensionNames",
+ "ntSecurityDescriptor",
+ NULL
+ };
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net ads gpo listall\n"
+ " %s\n",
+ _("Usage:"),
+ _("List all GPOs on the DC"));
+ return 0;
+ }
+
+ mem_ctx = talloc_init("net_ads_gpo_list_all");
+ if (mem_ctx == NULL) {
+ return -1;
+ }
+
+ status = ads_startup(c, false, mem_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_do_search_all_sd_flags(ads, ads->config.bind_path,
+ LDAP_SCOPE_SUBTREE,
+ "(objectclass=groupPolicyContainer)",
+ attrs,
+ SECINFO_DACL,
+ &res);
+
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("search failed: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+ num_reply = ads_count_replies(ads, res);
+
+ d_printf(_("Got %d replies\n\n"), num_reply);
+
+ /* dump the results */
+ for (msg = ads_first_entry(ads, res);
+ msg;
+ msg = ads_next_entry(ads, msg)) {
+
+ if ((dn = ads_get_dn(ads, mem_ctx, msg)) == NULL) {
+ goto out;
+ }
+
+ status = ads_parse_gpo(ads, mem_ctx, msg, dn, &gpo);
+
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("ads_parse_gpo failed: %s\n"),
+ ads_errstr(status));
+ goto out;
+ }
+
+ dump_gpo(&gpo, 0);
+ }
+
+out:
+ ads_msgfree(ads, res);
+
+ TALLOC_FREE(mem_ctx);
+
+ return 0;
+}
+
+static int net_ads_gpo_list(struct net_context *c, int argc, const char **argv)
+{
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS status;
+ LDAPMessage *res = NULL;
+ TALLOC_CTX *mem_ctx;
+ const char *dn = NULL;
+ uint32_t uac = 0;
+ uint32_t flags = 0;
+ struct GROUP_POLICY_OBJECT *gpo_list;
+ struct security_token *token = NULL;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s\n%s",
+ _("Usage:"),
+ _("net ads gpo list <username|machinename>"),
+ _(" Lists all GPOs for machine/user\n"
+ " username\tUser to list GPOs for\n"
+ " machinename\tMachine to list GPOs for\n"));
+ return -1;
+ }
+
+ mem_ctx = talloc_init("net_ads_gpo_list");
+ if (mem_ctx == NULL) {
+ goto out;
+ }
+
+ status = ads_startup(c, false, mem_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_find_samaccount(ads, mem_ctx, argv[0], &uac, &dn);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (uac & UF_WORKSTATION_TRUST_ACCOUNT) {
+ flags |= GPO_LIST_FLAG_MACHINE;
+ }
+
+ d_printf(_("%s: '%s' has dn: '%s'\n"),
+ (uac & UF_WORKSTATION_TRUST_ACCOUNT) ? _("machine") : _("user"),
+ argv[0], dn);
+
+ if (uac & UF_WORKSTATION_TRUST_ACCOUNT) {
+ status = gp_get_machine_token(ads, mem_ctx, dn, &token);
+ } else {
+ status = ads_get_sid_token(ads, mem_ctx, dn, &token);
+ }
+
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_get_gpo_list(ads, mem_ctx, dn, flags, token, &gpo_list);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ dump_gpo_list(gpo_list, 0);
+
+out:
+ ads_msgfree(ads, res);
+
+ talloc_destroy(mem_ctx);
+
+ return 0;
+}
+
+static int net_ads_gpo_link_get(struct net_context *c, int argc, const char **argv)
+{
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ TALLOC_CTX *mem_ctx;
+ struct GP_LINK gp_link;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s\n%s",
+ _("Usage:"),
+ _("net ads gpo linkget <container>"),
+ _(" Lists gPLink of a container\n"
+ " container\tContainer to get link for\n"));
+ return -1;
+ }
+
+ mem_ctx = talloc_init("add_gpo_link");
+ if (mem_ctx == NULL) {
+ return -1;
+ }
+
+ status = ads_startup(c, false, mem_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_get_gpo_link(ads, mem_ctx, argv[0], &gp_link);
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("get link for %s failed: %s\n"), argv[0],
+ ads_errstr(status));
+ goto out;
+ }
+
+ dump_gplink(&gp_link);
+
+out:
+ talloc_destroy(mem_ctx);
+
+ return 0;
+}
+
+static int net_ads_gpo_link_add(struct net_context *c, int argc, const char **argv)
+{
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ uint32_t gpo_opt = 0;
+ TALLOC_CTX *mem_ctx;
+
+ if (argc < 2 || c->display_usage) {
+ d_printf("%s\n%s\n%s",
+ _("Usage:"),
+ _("net ads gpo linkadd <linkdn> <gpodn> [options]"),
+ _(" Link a container to a GPO\n"
+ " linkdn\tContainer to link to a GPO\n"
+ " gpodn\tGPO to link container to\n"));
+ d_printf(_("note: DNs must be provided properly escaped.\n"
+ "See RFC 4514 for details\n"));
+ return -1;
+ }
+
+ mem_ctx = talloc_init("add_gpo_link");
+ if (mem_ctx == NULL) {
+ return -1;
+ }
+
+ if (argc == 3) {
+ gpo_opt = atoi(argv[2]);
+ }
+
+ status = ads_startup(c, false, mem_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_add_gpo_link(ads, mem_ctx, argv[0], argv[1], gpo_opt);
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("link add failed: %s\n"), ads_errstr(status));
+ goto out;
+ }
+
+out:
+ talloc_destroy(mem_ctx);
+
+ return 0;
+}
+
+#if 0 /* broken */
+
+static int net_ads_gpo_link_delete(struct net_context *c, int argc, const char **argv)
+{
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ TALLOC_CTX *mem_ctx;
+
+ if (argc < 2 || c->display_usage) {
+ d_printf("Usage:\n"
+ "net ads gpo linkdelete <linkdn> <gpodn>\n"
+ " Delete a GPO link\n"
+ " <linkdn>\tContainer to delete GPO from\n"
+ " <gpodn>\tGPO to delete from container\n");
+ return -1;
+ }
+
+ mem_ctx = talloc_init("delete_gpo_link");
+ if (mem_ctx == NULL) {
+ return -1;
+ }
+
+ status = ads_startup(c, false, mem_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ status = ads_delete_gpo_link(ads, mem_ctx, argv[0], argv[1]);
+ if (!ADS_ERR_OK(status)) {
+ d_printf("delete link failed: %s\n", ads_errstr(status));
+ goto out;
+ }
+
+out:
+ talloc_destroy(mem_ctx);
+
+ return 0;
+}
+
+#endif
+
+/*
+Arguments:
+- struct net_context *: Pointer to net_context*
+- argc: Number of command line arguments passed to 'net ads gpo getgpo' command
+- **argv: Command line argument string passed to 'net ads gpo getgpo' command
+
+This function performs following operations:
+1. Create talloc context using talloc_init
+2. Perform ads_startup()
+3. Call ads_get_gpo() to retrieve gpo details inside 'struct GROUP_POLICY_OBJECT'
+4. Call dumps_gpo() to dump GPO on stdout
+*/
+static int net_ads_gpo_get_gpo(struct net_context *c, int argc, const char **argv)
+{
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ TALLOC_CTX *mem_ctx;
+ struct GROUP_POLICY_OBJECT gpo;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s\n%s",
+ _("Usage:"),
+ _("net ads gpo getgpo <gpo>"),
+ _(" List specified GPO\n"
+ " gpo\t\tGPO to list\n"));
+ return -1;
+ }
+
+ mem_ctx = talloc_init("ads_gpo_get_gpo");
+ if (mem_ctx == NULL) {
+ return -1;
+ }
+
+ status = ads_startup(c, false, mem_ctx, &ads);
+ if (!ADS_ERR_OK(status)) {
+ goto out;
+ }
+
+ if (strnequal(argv[0], "CN={", strlen("CN={"))) {
+ status = ads_get_gpo(ads, mem_ctx, argv[0], NULL, NULL, &gpo);
+ } else {
+ status = ads_get_gpo(ads, mem_ctx, NULL, argv[0], NULL, &gpo);
+ }
+
+ if (!ADS_ERR_OK(status)) {
+ d_printf(_("get gpo for [%s] failed: %s\n"), argv[0],
+ ads_errstr(status));
+ goto out;
+ }
+
+ dump_gpo(&gpo, 0);
+
+out:
+ talloc_destroy(mem_ctx);
+
+ return 0;
+}
+
+int net_ads_gpo(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "getgpo",
+ net_ads_gpo_get_gpo,
+ NET_TRANSPORT_ADS,
+ N_("List specified GPO"),
+ N_("net ads gpo getgpo\n"
+ " List specified GPO")
+ },
+ {
+ "linkadd",
+ net_ads_gpo_link_add,
+ NET_TRANSPORT_ADS,
+ N_("Link a container to a GPO"),
+ N_("net ads gpo linkadd\n"
+ " Link a container to a GPO")
+ },
+#if 0
+ {
+ "linkdelete",
+ net_ads_gpo_link_delete,
+ NET_TRANSPORT_ADS,
+ "Delete GPO link from a container",
+ "net ads gpo linkdelete\n"
+ " Delete GPO link from a container"
+ },
+#endif
+ {
+ "linkget",
+ net_ads_gpo_link_get,
+ NET_TRANSPORT_ADS,
+ N_("Lists gPLink of container"),
+ N_("net ads gpo linkget\n"
+ " Lists gPLink of container")
+ },
+ {
+ "list",
+ net_ads_gpo_list,
+ NET_TRANSPORT_ADS,
+ N_("Lists all GPOs for machine/user"),
+ N_("net ads gpo list\n"
+ " Lists all GPOs for machine/user")
+ },
+ {
+ "listall",
+ net_ads_gpo_list_all,
+ NET_TRANSPORT_ADS,
+ N_("Lists all GPOs on a DC"),
+ N_("net ads gpo listall\n"
+ " Lists all GPOs on a DC")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net ads gpo", func);
+}
+
+#endif /* HAVE_ADS */
diff --git a/source3/utils/net_ads_join_dns.c b/source3/utils/net_ads_join_dns.c
new file mode 100644
index 0000000..3437f96
--- /dev/null
+++ b/source3/utils/net_ads_join_dns.c
@@ -0,0 +1,342 @@
+/*
+ Samba Unix/Linux SMB client library
+ net ads dns internal functions
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2001 Remus Koos (remuskoos@yahoo.com)
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2006 Gerald (Jerry) Carter (jerry@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "../lib/addns/dnsquery.h"
+#include "secrets.h"
+#include "krb5_env.h"
+#include "utils/net_dns.h"
+#include "lib/util/string_wrappers.h"
+
+#ifdef HAVE_ADS
+
+/*******************************************************************
+ Send a DNS update request
+*******************************************************************/
+
+#if defined(HAVE_KRB5)
+#include "../lib/addns/dns.h"
+
+void use_in_memory_ccache(void) {
+ /* Use in-memory credentials cache so we do not interfere with
+ * existing credentials */
+ setenv(KRB5_ENV_CCNAME, "MEMORY:net_ads", 1);
+}
+
+static NTSTATUS net_update_dns_internal(struct net_context *c,
+ TALLOC_CTX *ctx, ADS_STRUCT *ads,
+ const char *machine_name,
+ const struct sockaddr_storage *addrs,
+ int num_addrs, bool remove_host)
+{
+ struct dns_rr_ns *nameservers = NULL;
+ size_t ns_count = 0, i;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ DNS_ERROR dns_err;
+ fstring dns_server;
+ const char *dnsdomain = NULL;
+ char *root_domain = NULL;
+ uint32_t ttl = 3600;
+
+ if (c->opt_dns_ttl > 0) {
+ ttl = MIN(c->opt_dns_ttl, UINT32_MAX);
+ }
+
+ if ( (dnsdomain = strchr_m( machine_name, '.')) == NULL ) {
+ d_printf(_("No DNS domain configured for %s. "
+ "Unable to perform DNS Update.\n"), machine_name);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+ dnsdomain++;
+
+ status = ads_dns_lookup_ns(ctx,
+ dnsdomain,
+ &nameservers,
+ &ns_count);
+ if ( !NT_STATUS_IS_OK(status) || (ns_count == 0)) {
+ /* Child domains often do not have NS records. Look
+ for the NS record for the forest root domain
+ (rootDomainNamingContext in therootDSE) */
+
+ const char *rootname_attrs[] = { "rootDomainNamingContext", NULL };
+ LDAPMessage *msg = NULL;
+ char *root_dn;
+ ADS_STATUS ads_status;
+
+ if ( !ads->ldap.ld ) {
+ ads_status = ads_connect( ads );
+ if ( !ADS_ERR_OK(ads_status) ) {
+ DEBUG(0,("net_update_dns_internal: Failed to connect to our DC!\n"));
+ goto done;
+ }
+ }
+
+ ads_status = ads_do_search(ads, "", LDAP_SCOPE_BASE,
+ "(objectclass=*)", rootname_attrs, &msg);
+ if (!ADS_ERR_OK(ads_status)) {
+ goto done;
+ }
+
+ root_dn = ads_pull_string(ads, ctx, msg, "rootDomainNamingContext");
+ if ( !root_dn ) {
+ ads_msgfree( ads, msg );
+ goto done;
+ }
+
+ root_domain = ads_build_domain( root_dn );
+
+ /* cleanup */
+ ads_msgfree( ads, msg );
+
+ /* try again for NS servers */
+
+ status = ads_dns_lookup_ns(ctx,
+ root_domain,
+ &nameservers,
+ &ns_count);
+
+ if ( !NT_STATUS_IS_OK(status) || (ns_count == 0)) {
+ DEBUG(3,("net_update_dns_internal: Failed to find name server for the %s "
+ "realm\n", ads->config.realm));
+ if (ns_count == 0) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ goto done;
+ }
+
+ dnsdomain = root_domain;
+
+ }
+
+ for (i=0; i < ns_count; i++) {
+
+ uint32_t flags = DNS_UPDATE_SIGNED |
+ DNS_UPDATE_UNSIGNED |
+ DNS_UPDATE_UNSIGNED_SUFFICIENT |
+ DNS_UPDATE_PROBE |
+ DNS_UPDATE_PROBE_SUFFICIENT;
+
+ if (c->opt_force) {
+ flags &= ~DNS_UPDATE_PROBE_SUFFICIENT;
+ flags &= ~DNS_UPDATE_UNSIGNED_SUFFICIENT;
+ }
+
+ /*
+ * Do not return after PROBE completion if this function
+ * is called for DNS removal.
+ */
+ if (remove_host) {
+ flags &= ~DNS_UPDATE_PROBE_SUFFICIENT;
+ }
+
+ status = NT_STATUS_UNSUCCESSFUL;
+
+ /* Now perform the dns update - we'll try non-secure and if we fail,
+ we'll follow it up with a secure update */
+
+ fstrcpy( dns_server, nameservers[i].hostname );
+
+ dns_err = DoDNSUpdate(dns_server,
+ dnsdomain,
+ machine_name,
+ addrs,
+ num_addrs,
+ flags,
+ ttl,
+ remove_host);
+ if (ERR_DNS_IS_OK(dns_err)) {
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ if (ERR_DNS_EQUAL(dns_err, ERROR_DNS_INVALID_NAME_SERVER) ||
+ ERR_DNS_EQUAL(dns_err, ERROR_DNS_CONNECTION_FAILED) ||
+ ERR_DNS_EQUAL(dns_err, ERROR_DNS_SOCKET_ERROR)) {
+ DEBUG(1,("retrying DNS update with next nameserver after receiving %s\n",
+ dns_errstr(dns_err)));
+ continue;
+ }
+
+ d_printf(_("DNS Update for %s failed: %s\n"),
+ machine_name, dns_errstr(dns_err));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+done:
+
+ SAFE_FREE( root_domain );
+
+ return status;
+}
+
+NTSTATUS net_update_dns_ext(struct net_context *c,
+ TALLOC_CTX *mem_ctx, ADS_STRUCT *ads,
+ const char *hostname,
+ struct sockaddr_storage *iplist,
+ int num_addrs, bool remove_host)
+{
+ struct sockaddr_storage *iplist_alloc = NULL;
+ fstring machine_name;
+ NTSTATUS status;
+
+ if (hostname) {
+ fstrcpy(machine_name, hostname);
+ } else {
+ name_to_fqdn( machine_name, lp_netbios_name() );
+ }
+ if (!strlower_m( machine_name )) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * If remove_host is true, then remove all IP addresses associated with
+ * this hostname from the AD server.
+ */
+ if (!remove_host && (num_addrs == 0 || iplist == NULL)) {
+ /*
+ * Get our ip address
+ * (not the 127.0.0.x address but a real ip address)
+ */
+ num_addrs = get_my_ip_address(&iplist_alloc);
+ if ( num_addrs <= 0 ) {
+ DEBUG(4, ("net_update_dns_ext: Failed to find my "
+ "non-loopback IP addresses!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ iplist = iplist_alloc;
+ }
+
+ status = net_update_dns_internal(c, mem_ctx, ads, machine_name,
+ iplist, num_addrs, remove_host);
+
+ SAFE_FREE(iplist_alloc);
+ return status;
+}
+
+static NTSTATUS net_update_dns(struct net_context *c, TALLOC_CTX *mem_ctx, ADS_STRUCT *ads, const char *hostname)
+{
+ NTSTATUS status;
+
+ status = net_update_dns_ext(c, mem_ctx, ads, hostname, NULL, 0, false);
+ return status;
+}
+#endif
+
+void net_ads_join_dns_updates(struct net_context *c, TALLOC_CTX *ctx, struct libnet_JoinCtx *r)
+{
+#if defined(HAVE_KRB5)
+ ADS_STRUCT *ads_dns = NULL;
+ int ret;
+ NTSTATUS status;
+ char *machine_password = NULL;
+
+ /*
+ * In a clustered environment, don't do dynamic dns updates:
+ * Registering the set of ip addresses that are assigned to
+ * the interfaces of the node that performs the join does usually
+ * not have the desired effect, since the local interfaces do not
+ * carry the complete set of the cluster's public IP addresses.
+ * And it can also contain internal addresses that should not
+ * be visible to the outside at all.
+ * In order to do dns updates in a clustererd setup, use
+ * net ads dns register.
+ */
+ if (lp_clustering()) {
+ d_fprintf(stderr, _("Not doing automatic DNS update in a "
+ "clustered setup.\n"));
+ return;
+ }
+
+ if (!r->out.domain_is_ad) {
+ return;
+ }
+
+ /*
+ * We enter this block with user creds.
+ * kinit with the machine password to do dns update.
+ */
+
+ ads_dns = ads_init(ctx,
+ lp_realm(),
+ NULL,
+ r->in.dc_name,
+ ADS_SASL_PLAIN);
+ if (ads_dns == NULL) {
+ d_fprintf(stderr, _("DNS update failed: out of memory!\n"));
+ goto done;
+ }
+
+ use_in_memory_ccache();
+
+ ads_dns->auth.user_name = talloc_asprintf(ads_dns,
+ "%s$",
+ lp_netbios_name());
+ if (ads_dns->auth.user_name == NULL) {
+ d_fprintf(stderr, _("DNS update failed: out of memory\n"));
+ goto done;
+ }
+
+ machine_password = secrets_fetch_machine_password(
+ r->out.netbios_domain_name, NULL, NULL);
+ if (machine_password != NULL) {
+ ads_dns->auth.password = talloc_strdup(ads_dns,
+ machine_password);
+ SAFE_FREE(machine_password);
+ if (ads_dns->auth.password == NULL) {
+ d_fprintf(stderr,
+ _("DNS update failed: out of memory\n"));
+ goto done;
+ }
+ }
+
+ ads_dns->auth.realm = talloc_asprintf_strupper_m(ads_dns, "%s", r->out.dns_domain_name);
+ if (ads_dns->auth.realm == NULL) {
+ d_fprintf(stderr, _("talloc_asprintf_strupper_m %s failed\n"),
+ ads_dns->auth.realm);
+ goto done;
+ }
+
+ ret = ads_kinit_password(ads_dns);
+ if (ret != 0) {
+ d_fprintf(stderr,
+ _("DNS update failed: kinit failed: %s\n"),
+ error_message(ret));
+ goto done;
+ }
+
+ status = net_update_dns(c, ctx, ads_dns, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf( stderr, _("DNS update failed: %s\n"),
+ nt_errstr(status));
+ }
+
+done:
+ TALLOC_FREE(ads_dns);
+#endif
+
+ return;
+}
+
+#endif /* HAVE_ADS */
diff --git a/source3/utils/net_afs.c b/source3/utils/net_afs.c
new file mode 100644
index 0000000..36d4310
--- /dev/null
+++ b/source3/utils/net_afs.c
@@ -0,0 +1,127 @@
+/*
+ Samba Unix/Linux SMB client library
+ net afs commands
+ Copyright (C) 2003 Volker Lendecke (vl@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "utils/net_afs.h"
+#include "secrets.h"
+#include "system/filesys.h"
+#include "lib/afs/afs_funcs.h"
+#include "lib/afs/afs_settoken.h"
+
+#ifdef WITH_FAKE_KASERVER
+
+int net_afs_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(" net afs key filename\n"
+ "\tImports a OpenAFS KeyFile into our secrets.tdb\n\n"));
+ d_printf(_(" net afs impersonate <user> <cell>\n"
+ "\tCreates a token for user@cell\n\n"));
+ return -1;
+}
+
+int net_afs_key(struct net_context *c, int argc, const char **argv)
+{
+ int fd;
+ struct afs_keyfile keyfile;
+
+ if (argc != 2) {
+ d_printf("%s net afs key <keyfile> cell\n", _("Usage:"));
+ return -1;
+ }
+
+ if (!secrets_init()) {
+ d_fprintf(stderr, _("Could not open secrets.tdb\n"));
+ return -1;
+ }
+
+ if ((fd = open(argv[0], O_RDONLY, 0)) < 0) {
+ d_fprintf(stderr, _("Could not open %s\n"), argv[0]);
+ return -1;
+ }
+
+ if (read(fd, &keyfile, sizeof(keyfile)) != sizeof(keyfile)) {
+ d_fprintf(stderr, _("Could not read keyfile\n"));
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ if (!secrets_store_afs_keyfile(argv[1], &keyfile)) {
+ d_fprintf(stderr, _("Could not write keyfile to secrets.tdb\n"));
+ ZERO_STRUCT(keyfile);
+ return -1;
+ }
+
+ ZERO_STRUCT(keyfile);
+ return 0;
+}
+
+int net_afs_impersonate(struct net_context *c, int argc,
+ const char **argv)
+{
+ char *token;
+
+ if (argc != 2) {
+ d_fprintf(stderr, "%s net afs impersonate <user> <cell>\n",
+ _("Usage:"));
+ exit(1);
+ }
+
+ token = afs_createtoken_str(argv[0], argv[1]);
+
+ if (token == NULL) {
+ fprintf(stderr, _("Could not create token\n"));
+ exit(1);
+ }
+
+ if (!afs_settoken_str(token)) {
+ fprintf(stderr, _("Could not set token into kernel\n"));
+ exit(1);
+ }
+
+ printf(_("Success: %s@%s\n"), argv[0], argv[1]);
+ return 0;
+}
+
+int net_afs(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "key",
+ net_afs_key,
+ NET_TRANSPORT_LOCAL,
+ N_("Import an OpenAFS keyfile"),
+ N_("net afs key <filename>\n"
+ " Import kefile from <filename>.")
+ },
+ {
+ "impersonate",
+ net_afs_impersonate,
+ NET_TRANSPORT_LOCAL,
+ N_("Get a user token"),
+ N_("net afs impersonate <user> <cell>\n"
+ " Create token for user@cell")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+ return net_run_function(c, argc, argv, "net afs", func);
+}
+
+#endif /* WITH_FAKE_KASERVER */
diff --git a/source3/utils/net_afs.h b/source3/utils/net_afs.h
new file mode 100644
index 0000000..31606dd
--- /dev/null
+++ b/source3/utils/net_afs.h
@@ -0,0 +1,29 @@
+/*
+ Samba Unix/Linux SMB client library
+ net afs commands
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _NET_AFS_H_
+#define _NET_AFS_H_
+
+int net_afs_usage(struct net_context *c, int argc, const char **argv);
+int net_afs_key(struct net_context *c, int argc, const char **argv);
+int net_afs_impersonate(struct net_context *c, int argc,
+ const char **argv);
+int net_afs(struct net_context *c, int argc, const char **argv);
+
+#endif /*_NET_AFS_H_*/
diff --git a/source3/utils/net_cache.c b/source3/utils/net_cache.c
new file mode 100644
index 0000000..a5a67ea
--- /dev/null
+++ b/source3/utils/net_cache.c
@@ -0,0 +1,652 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) Rafal Szczesniak 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "net.h"
+#include "libsmb/samlogon_cache.h"
+#include "../librpc/gen_ndr/netlogon.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/util/strv.h"
+#include "lib/gencache.h"
+
+/**
+ * @file net_cache.c
+ * @brief This is part of the net tool which is basically command
+ * line wrapper for gencache.c functions (mainly for testing)
+ *
+ **/
+
+
+/*
+ * These routines are used via gencache_iterate() to display the cache's contents
+ * (print_cache_entry) and to flush it (delete_cache_entry).
+ * Both of them are defined by first arg of gencache_iterate() routine.
+ */
+static void print_cache_entry(const char* keystr, DATA_BLOB value,
+ const time_t timeout, void* dptr)
+{
+ char *timeout_str;
+ char *alloc_str = NULL;
+ const char *datastr;
+ char *datastr_free = NULL;
+ time_t now_t = time(NULL);
+ struct tm timeout_tm, now_tm;
+ struct tm *ptimeout_tm, *pnow_tm;
+
+ ptimeout_tm = localtime_r(&timeout, &timeout_tm);
+ if (ptimeout_tm == NULL) {
+ return;
+ }
+ pnow_tm = localtime_r(&now_t, &now_tm);
+ if (pnow_tm == NULL) {
+ return;
+ }
+
+ /* form up timeout string depending whether it's today's date or not */
+ if (timeout_tm.tm_year != now_tm.tm_year ||
+ timeout_tm.tm_mon != now_tm.tm_mon ||
+ timeout_tm.tm_mday != now_tm.tm_mday) {
+
+ timeout_str = asctime(&timeout_tm);
+ if (!timeout_str) {
+ return;
+ }
+ timeout_str[strlen(timeout_str) - 1] = '\0'; /* remove tailing CR */
+ } else {
+ if (asprintf(&alloc_str, "%.2d:%.2d:%.2d", timeout_tm.tm_hour,
+ timeout_tm.tm_min, timeout_tm.tm_sec) == -1) {
+ return;
+ }
+ timeout_str = alloc_str;
+ }
+
+ datastr = (char *)value.data;
+
+ if (strnequal(keystr, "NAME2SID/", strlen("NAME2SID/"))) {
+ const char *strv = (char *)value.data;
+ size_t strv_len = value.length;
+ const char *sid = strv_len_next(strv, strv_len, NULL);
+ const char *type = strv_len_next(strv, strv_len, sid);
+ datastr = talloc_asprintf(talloc_tos(), "%s (%s)", sid, type);
+ }
+
+ if (strnequal(keystr, "SID2NAME/", strlen("SID2NAME/"))) {
+ const char *strv = (char *)value.data;
+ size_t strv_len = value.length;
+ const char *domain = strv_len_next(strv, strv_len, NULL);
+ const char *name = strv_len_next(strv, strv_len, domain);
+ const char *type = strv_len_next(strv, strv_len, name);
+ datastr = talloc_asprintf(talloc_tos(), "%s\\%s (%s)",
+ domain, name, type);
+ }
+
+ if ((value.length > 0) && (value.data[value.length-1] != '\0')) {
+ datastr_free = talloc_asprintf(
+ talloc_tos(), "<binary length %d>",
+ (int)value.length);
+ datastr = datastr_free;
+ if (datastr == NULL) {
+ datastr = "<binary>";
+ }
+ }
+
+ d_printf(_("Key: %s\t Timeout: %s\t Value: %s %s\n"), keystr,
+ timeout_str, datastr, timeout > now_t ? "": _("(expired)"));
+
+ SAFE_FREE(alloc_str);
+}
+
+static void delete_cache_entry(const char* keystr, const char* datastr,
+ const time_t timeout, void* dptr)
+{
+ if (!gencache_del(keystr))
+ d_fprintf(stderr, _("Couldn't delete entry! key = %s\n"),
+ keystr);
+}
+
+
+/**
+ * Parse text representation of timeout value
+ *
+ * @param timeout_str string containing text representation of the timeout
+ * @return numeric timeout of time_t type
+ **/
+static time_t parse_timeout(const char* timeout_str)
+{
+ char sign = '\0', *number = NULL, unit = '\0';
+ int len, number_begin, number_end;
+ time_t timeout;
+
+ /* sign detection */
+ if (timeout_str[0] == '!' || timeout_str[0] == '+') {
+ sign = timeout_str[0];
+ number_begin = 1;
+ } else {
+ number_begin = 0;
+ }
+
+ /* unit detection */
+ len = strlen(timeout_str);
+ switch (timeout_str[len - 1]) {
+ case 's':
+ case 'm':
+ case 'h':
+ case 'd':
+ case 'w': unit = timeout_str[len - 1];
+ }
+
+ /* number detection */
+ len = (sign) ? strlen(&timeout_str[number_begin]) : len;
+ number_end = (unit) ? len - 1 : len;
+ number = SMB_STRNDUP(&timeout_str[number_begin], number_end);
+
+ /* calculate actual timeout value */
+ timeout = (time_t)atoi(number);
+
+ switch (unit) {
+ case 'm': timeout *= 60; break;
+ case 'h': timeout *= 60*60; break;
+ case 'd': timeout *= 60*60*24; break;
+ case 'w': timeout *= 60*60*24*7; break; /* that's fair enough, I think :) */
+ }
+
+ switch (sign) {
+ case '!': timeout = time(NULL) - timeout; break;
+ case '+':
+ default: timeout += time(NULL); break;
+ }
+
+ if (number) SAFE_FREE(number);
+ return timeout;
+}
+
+
+/**
+ * Add an entry to the cache. If it does exist, then set it.
+ *
+ * @param c A net_context structure
+ * @param argv key, value and timeout are passed in command line
+ * @return 0 on success, otherwise failure
+ **/
+static int net_cache_add(struct net_context *c, int argc, const char **argv)
+{
+ const char *keystr, *datastr, *timeout_str;
+ time_t timeout;
+
+ if (argc < 3 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net cache add <key string> <data string> "
+ "<timeout>\n"));
+ return -1;
+ }
+
+ keystr = argv[0];
+ datastr = argv[1];
+ timeout_str = argv[2];
+
+ /* parse timeout given in command line */
+ timeout = parse_timeout(timeout_str);
+ if (!timeout) {
+ d_fprintf(stderr, _("Invalid timeout argument.\n"));
+ return -1;
+ }
+
+ if (gencache_set(keystr, datastr, timeout)) {
+ d_printf(_("New cache entry stored successfully.\n"));
+ return 0;
+ }
+
+ d_fprintf(stderr, _("Entry couldn't be added. Perhaps there's already such a key.\n"));
+ return -1;
+}
+
+/**
+ * Delete an entry in the cache
+ *
+ * @param c A net_context structure
+ * @param argv key to delete an entry of
+ * @return 0 on success, otherwise failure
+ **/
+static int net_cache_del(struct net_context *c, int argc, const char **argv)
+{
+ const char *keystr = argv[0];
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net cache del <key string>\n"));
+ return -1;
+ }
+
+ if(gencache_del(keystr)) {
+ d_printf(_("Entry deleted.\n"));
+ return 0;
+ }
+
+ d_fprintf(stderr, _("Couldn't delete specified entry\n"));
+ return -1;
+}
+
+
+/**
+ * Get and display an entry from the cache
+ *
+ * @param c A net_context structure
+ * @param argv key to search an entry of
+ * @return 0 on success, otherwise failure
+ **/
+static int net_cache_get(struct net_context *c, int argc, const char **argv)
+{
+ const char* keystr = argv[0];
+ DATA_BLOB value;
+ time_t timeout;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net cache get <key>\n"));
+ return -1;
+ }
+
+ if (gencache_get_data_blob(keystr, NULL, &value, &timeout, NULL)) {
+ print_cache_entry(keystr, value, timeout, NULL);
+ data_blob_free(&value);
+ return 0;
+ }
+
+ d_fprintf(stderr, _("Failed to find entry\n"));
+ return -1;
+}
+
+
+/**
+ * Search an entry/entries in the cache
+ *
+ * @param c A net_context structure
+ * @param argv key pattern to match the entries to
+ * @return 0 on success, otherwise failure
+ **/
+static int net_cache_search(struct net_context *c, int argc, const char **argv)
+{
+ const char* pattern;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net cache search <pattern>\n"));
+ return -1;
+ }
+
+ pattern = argv[0];
+ gencache_iterate_blobs(print_cache_entry, NULL, pattern);
+ return 0;
+}
+
+
+/**
+ * List the contents of the cache
+ *
+ * @param c A net_context structure
+ * @param argv ignored in this functionality
+ * @return always returns 0
+ **/
+static int net_cache_list(struct net_context *c, int argc, const char **argv)
+{
+ const char* pattern = "*";
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net cache list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List all cache entries."));
+ return 0;
+ }
+ gencache_iterate_blobs(print_cache_entry, NULL, pattern);
+ return 0;
+}
+
+
+/**
+ * Flush the whole cache
+ *
+ * @param c A net_context structure
+ * @param argv ignored in this functionality
+ * @return always returns 0
+ **/
+static int net_cache_flush(struct net_context *c, int argc, const char **argv)
+{
+ const char* pattern = "*";
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net cache flush\n"
+ " %s",
+ _("Usage:"),
+ _("Delete all cache entries."));
+ return 0;
+ }
+ gencache_iterate(delete_cache_entry, NULL, pattern);
+ return 0;
+}
+
+static int netsamlog_cache_for_all_cb(const char *sid_str,
+ time_t when_cached,
+ struct netr_SamInfo3 *info3,
+ void *private_data)
+{
+ struct net_context *c = (struct net_context *)private_data;
+ char *name = NULL;
+
+ name = talloc_asprintf(c, "%s\\%s",
+ info3->base.logon_domain.string,
+ info3->base.account_name.string);
+ if (name == NULL) {
+ return -1;
+ }
+
+ d_printf("%-50s %-40s %s\n",
+ sid_str,
+ name,
+ timestring(c, when_cached));
+
+ return 0;
+}
+
+static int net_cache_samlogon_list(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ int ret;
+
+ d_printf("%-50s %-40s When cached\n", "SID", "Name");
+ d_printf("------------------------------------------------------------"
+ "------------------------------------------------------------"
+ "----\n");
+
+ ret = netsamlog_cache_for_all(netsamlog_cache_for_all_cb, c);
+ if (ret == -1) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int net_cache_samlogon_show(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ const char *sid_str = argv[0];
+ struct dom_sid sid;
+ struct dom_sid *user_sids = NULL;
+ uint32_t num_user_sids;
+ struct netr_SamInfo3 *info3 = NULL;
+ char *name = NULL;
+ uint32_t i;
+ NTSTATUS status;
+ bool ok;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n"
+ "net cache samlogon show SID\n"
+ " %s\n",
+ _("Usage:"),
+ _("Show samlogon cache entry for SID."));
+ return 0;
+ }
+
+ ok = string_to_sid(&sid, sid_str);
+ if (!ok) {
+ d_printf("String to SID failed for %s\n", sid_str);
+ return -1;
+ }
+
+ info3 = netsamlogon_cache_get(c, &sid);
+ if (info3 == NULL) {
+ d_printf("SID %s not found in samlogon cache\n", sid_str);
+ return -1;
+ }
+
+ name = talloc_asprintf(c, "%s\\%s",
+ info3->base.logon_domain.string,
+ info3->base.account_name.string);
+ if (name == NULL) {
+ return -1;
+ }
+
+ d_printf("Name: %s\n", name);
+
+ status = sid_array_from_info3(c,
+ info3,
+ &user_sids,
+ &num_user_sids,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(user_sids);
+ d_printf("sid_array_from_info3 failed for %s\n", sid_str);
+ return -1;
+ }
+
+ for (i = 0; i < num_user_sids; i++) {
+ struct dom_sid_buf buf;
+ d_printf("SID %2" PRIu32 ": %s\n",
+ i,
+ dom_sid_str_buf(&user_sids[i], &buf));
+ }
+
+ TALLOC_FREE(user_sids);
+
+ return 0;
+}
+
+static int net_cache_samlogon_ndrdump(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ const char *sid_str = NULL;
+ struct dom_sid sid;
+ struct netr_SamInfo3 *info3 = NULL;
+ struct ndr_print *ndr_print = NULL;
+ bool ok;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf( "%s\n"
+ "net cache samlogon ndrdump SID\n"
+ " %s\n",
+ _("Usage:"),
+ _("Show samlogon cache entry for SID."));
+ return 0;
+ }
+
+ sid_str = argv[0];
+
+ ok = string_to_sid(&sid, sid_str);
+ if (!ok) {
+ d_printf("String to SID failed for %s\n", sid_str);
+ return -1;
+ }
+
+ info3 = netsamlogon_cache_get(c, &sid);
+ if (info3 == NULL) {
+ d_printf("SID %s not found in samlogon cache\n", sid_str);
+ return -1;
+ }
+
+ ndr_print = talloc_zero(c, struct ndr_print);
+ if (ndr_print == NULL) {
+ d_printf("Could not allocate memory.\n");
+ return -1;
+ }
+
+ ndr_print->print = ndr_print_printf_helper;
+ ndr_print->depth = 1;
+ ndr_print_netr_SamInfo3(ndr_print, "netr_SamInfo3", info3);
+ TALLOC_FREE(ndr_print);
+
+ return 0;
+}
+
+static int net_cache_samlogon_delete(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ const char *sid_str = argv[0];
+ struct dom_sid sid;
+ bool ok;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf( "%s\n"
+ "net cache samlogon delete SID\n"
+ " %s\n",
+ _("Usage:"),
+ _("Delete samlogon cache entry for SID."));
+ return 0;
+ }
+
+ ok = string_to_sid(&sid, sid_str);
+ if (!ok) {
+ d_printf("String to SID failed for %s\n", sid_str);
+ return -1;
+ }
+
+ netsamlogon_clear_cached_user(&sid);
+
+ return 0;
+}
+
+static int net_cache_samlogon(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ net_cache_samlogon_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List samlogon cache"),
+ N_("net cache samlogon list\n"
+ " List samlogon cachen\n")
+ },
+ {
+ "show",
+ net_cache_samlogon_show,
+ NET_TRANSPORT_LOCAL,
+ N_("Show samlogon cache entry"),
+ N_("net cache samlogon show SID\n"
+ " Show samlogon cache entry\n")
+ },
+ {
+ "ndrdump",
+ net_cache_samlogon_ndrdump,
+ NET_TRANSPORT_LOCAL,
+ N_("Dump the samlogon cache entry NDR blob"),
+ N_("net cache samlogon ndrdump SID\n"
+ " Dump the samlogon cache entry NDR blob\n")
+ },
+ {
+ "delete",
+ net_cache_samlogon_delete,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete samlogon cache entry"),
+ N_("net cache samlogon delete SID\n"
+ " Delete samlogon cache entry\n")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net cache samlogon", func);
+}
+
+/**
+ * Entry point to 'net cache' subfunctionality
+ *
+ * @param c A net_context structure
+ * @param argv arguments passed to further called functions
+ * @return whatever further functions return
+ **/
+int net_cache(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ net_cache_add,
+ NET_TRANSPORT_LOCAL,
+ N_("Add new cache entry"),
+ N_("net cache add <key string> <data string> <timeout>\n"
+ " Add new cache entry.\n"
+ " key string\tKey string to add cache data under.\n"
+ " data string\tData to store under given key.\n"
+ " timeout\tTimeout for cache data.")
+ },
+ {
+ "del",
+ net_cache_del,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete existing cache entry by key"),
+ N_("net cache del <key string>\n"
+ " Delete existing cache entry by key.\n"
+ " key string\tKey string to delete.")
+ },
+ {
+ "get",
+ net_cache_get,
+ NET_TRANSPORT_LOCAL,
+ N_("Get cache entry by key"),
+ N_("net cache get <key string>\n"
+ " Get cache entry by key.\n"
+ " key string\tKey string to look up cache entry for.")
+
+ },
+ {
+ "search",
+ net_cache_search,
+ NET_TRANSPORT_LOCAL,
+ N_("Search entry by pattern"),
+ N_("net cache search <pattern>\n"
+ " Search entry by pattern.\n"
+ " pattern\tPattern to search for in cache.")
+ },
+ {
+ "list",
+ net_cache_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List all cache entries"),
+ N_("net cache list\n"
+ " List all cache entries")
+ },
+ {
+ "flush",
+ net_cache_flush,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete all cache entries"),
+ N_("net cache flush\n"
+ " Delete all cache entries")
+ },
+ {
+ "samlogon",
+ net_cache_samlogon,
+ NET_TRANSPORT_LOCAL,
+ N_("List contents of the samlogon cache"),
+ N_("net cache samlogon\n"
+ " List contents of the samlogon cache")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net cache", func);
+}
diff --git a/source3/utils/net_conf.c b/source3/utils/net_conf.c
new file mode 100644
index 0000000..c16f240
--- /dev/null
+++ b/source3/utils/net_conf.c
@@ -0,0 +1,1305 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Local configuration interface
+ * Copyright (C) Michael Adam 2007-2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This is an interface to Samba's configuration as made available
+ * by the libsmbconf interface (source/lib/smbconf/smbconf.c).
+ *
+ * This currently supports local interaction with the configuration
+ * stored in the registry. But other backends and remote access via
+ * rpc might get implemented in the future.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "utils/net.h"
+#include "utils/net_conf_util.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_init.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "lib/param/loadparm.h"
+
+/**********************************************************************
+ *
+ * usage functions
+ *
+ **********************************************************************/
+
+static int net_conf_list_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s net conf list\n", _("Usage:"));
+ return -1;
+}
+
+static int net_conf_import_usage(struct net_context *c, int argc,
+ const char**argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf import [--test|-T] <filename> "
+ "[<servicename>]\n"
+ "\t[--test|-T] testmode - do not act, just print "
+ "what would be done\n"
+ "\t<servicename> only import service <servicename>, "
+ "ignore the rest\n"));
+ return -1;
+}
+
+static int net_conf_listshares_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet conf listshares\n", _("Usage:"));
+ return -1;
+}
+
+static int net_conf_drop_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet conf drop\n", _("Usage:"));
+ return -1;
+}
+
+static int net_conf_showshare_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net conf showshare <sharename>\n"));
+ return -1;
+}
+
+static int net_conf_addshare_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf addshare <sharename> <path> "
+ "[writeable={y|N} [guest_ok={y|N} [<comment>]]]\n"
+ "\t<sharename> the new share name.\n"
+ "\t<path> the path on the filesystem to export.\n"
+ "\twriteable={y|N} set \"writeable to \"yes\" or "
+ "\"no\" (default) on this share.\n"
+ "\tguest_ok={y|N} set \"guest ok\" to \"yes\" or "
+ "\"no\" (default) on this share.\n"
+ "\t<comment> optional comment for the new share.\n"));
+ return -1;
+}
+
+static int net_conf_delshare_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net conf delshare <sharename>\n"));
+ return -1;
+}
+
+static int net_conf_setparm_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf setparm <section> <param> <value>\n"));
+ return -1;
+}
+
+static int net_conf_getparm_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf getparm <section> <param>\n"));
+ return -1;
+}
+
+static int net_conf_delparm_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf delparm <section> <param>\n"));
+ return -1;
+}
+
+static int net_conf_getincludes_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf getincludes <section>\n"));
+ return -1;
+}
+
+static int net_conf_setincludes_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf setincludes <section> [<filename>]*\n"));
+ return -1;
+}
+
+static int net_conf_delincludes_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net conf delincludes <section>\n"));
+ return -1;
+}
+
+
+/**********************************************************************
+ *
+ * Helper functions
+ *
+ **********************************************************************/
+
+/**
+ * This functions process a service previously loaded with libsmbconf.
+ */
+static sbcErr import_process_service(struct net_context *c,
+ struct smbconf_ctx *conf_ctx,
+ struct smbconf_service *service)
+{
+ sbcErr err = SBC_ERR_OK;
+
+ if (c->opt_testmode) {
+ uint32_t idx;
+ const char *indent = "";
+ if (service->name != NULL) {
+ d_printf("[%s]\n", service->name);
+ indent = "\t";
+ }
+ for (idx = 0; idx < service->num_params; idx++) {
+ d_printf("%s%s = %s\n", indent,
+ service->param_names[idx],
+ service->param_values[idx]);
+ }
+ d_printf("\n");
+ goto done;
+ }
+
+ if (smbconf_share_exists(conf_ctx, service->name)) {
+ err = smbconf_delete_share(conf_ctx, service->name);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+ }
+
+ err = smbconf_create_set_share(conf_ctx, service);
+
+done:
+ return err;
+}
+
+
+/**********************************************************************
+ *
+ * the main conf functions
+ *
+ **********************************************************************/
+
+static int net_conf_list(struct net_context *c, struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ sbcErr err;
+ int ret = -1;
+ TALLOC_CTX *mem_ctx;
+ uint32_t num_shares;
+ uint32_t share_count, param_count;
+ struct smbconf_service **shares = NULL;
+
+ mem_ctx = talloc_stackframe();
+
+ if (argc != 0 || c->display_usage) {
+ net_conf_list_usage(c, argc, argv);
+ goto done;
+ }
+
+ err = smbconf_get_config(conf_ctx, mem_ctx, &num_shares, &shares);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error getting config: %s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+
+ for (share_count = 0; share_count < num_shares; share_count++) {
+ const char *indent = "";
+ if (shares[share_count]->name != NULL) {
+ d_printf("[%s]\n", shares[share_count]->name);
+ indent = "\t";
+ }
+ for (param_count = 0;
+ param_count < shares[share_count]->num_params;
+ param_count++)
+ {
+ d_printf("%s%s = %s\n",
+ indent,
+ shares[share_count]->param_names[param_count],
+ shares[share_count]->param_values[param_count]);
+ }
+ d_printf("\n");
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_import(struct net_context *c, struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ int ret = -1;
+ const char *filename = NULL;
+ const char *servicename = NULL;
+ char *conf_source = NULL;
+ TALLOC_CTX *mem_ctx;
+ struct smbconf_ctx *txt_ctx;
+ sbcErr err;
+
+ if (c->display_usage)
+ return net_conf_import_usage(c, argc, argv);
+
+ mem_ctx = talloc_stackframe();
+
+ switch (argc) {
+ case 0:
+ default:
+ net_conf_import_usage(c, argc, argv);
+ goto done;
+ case 2:
+ servicename = talloc_strdup(mem_ctx, argv[1]);
+ if (servicename == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ FALL_THROUGH;
+ case 1:
+ filename = argv[0];
+ break;
+ }
+
+ DEBUG(3,("net_conf_import: reading configuration from file %s.\n",
+ filename));
+
+ conf_source = talloc_asprintf(mem_ctx, "file:%s", filename);
+ if (conf_source == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ err = smbconf_init(mem_ctx, &txt_ctx, conf_source);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error loading file '%s': %s\n"), filename,
+ sbcErrorString(err));
+ goto done;
+ }
+
+ if (c->opt_testmode) {
+ d_printf(_("\nTEST MODE - "
+ "would import the following configuration:\n\n"));
+ }
+
+ if (servicename != NULL) {
+ struct smbconf_service *service = NULL;
+
+ err = smbconf_get_share(txt_ctx, mem_ctx,
+ servicename,
+ &service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto cancel;
+ }
+
+ err = smbconf_transaction_start(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error starting transaction: %s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+
+ err = import_process_service(c, conf_ctx, service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error importing service %s: %s\n"),
+ servicename, sbcErrorString(err));
+ goto cancel;
+ }
+ } else {
+ struct smbconf_service **services = NULL;
+ uint32_t num_shares, sidx;
+
+ err = smbconf_get_config(txt_ctx, mem_ctx,
+ &num_shares,
+ &services);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto cancel;
+ }
+ if (!c->opt_testmode) {
+ if (!SBC_ERROR_IS_OK(smbconf_drop(conf_ctx))) {
+ goto cancel;
+ }
+ }
+
+ /*
+ * Wrap the importing of shares into a transaction,
+ * but only 100 at a time, in order to save memory.
+ * The allocated memory accumulates across the actions
+ * within the transaction, and for me, some 1500
+ * imported shares, the MAX_TALLOC_SIZE of 256 MB
+ * was exceeded.
+ */
+ err = smbconf_transaction_start(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error starting transaction: %s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+
+ for (sidx = 0; sidx < num_shares; sidx++) {
+ err = import_process_service(c, conf_ctx,
+ services[sidx]);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error importing service %s: %s\n"),
+ services[sidx]->name,
+ sbcErrorString(err));
+ goto cancel;
+ }
+
+ if (sidx % 100) {
+ continue;
+ }
+
+ err = smbconf_transaction_commit(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error committing transaction: "
+ "%s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+ err = smbconf_transaction_start(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error starting transaction: %s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+ }
+ }
+
+ err = smbconf_transaction_commit(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error committing transaction: %s\n"),
+ sbcErrorString(err));
+ } else {
+ ret = 0;
+ }
+
+ goto done;
+
+cancel:
+ err = smbconf_transaction_cancel(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error cancelling transaction: %s\n"),
+ sbcErrorString(err));
+ }
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_listshares(struct net_context *c,
+ struct smbconf_ctx *conf_ctx, int argc,
+ const char **argv)
+{
+ sbcErr err;
+ int ret = -1;
+ uint32_t count, num_shares = 0;
+ char **share_names = NULL;
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_stackframe();
+
+ if (argc != 0 || c->display_usage) {
+ net_conf_listshares_usage(c, argc, argv);
+ goto done;
+ }
+
+ err = smbconf_get_share_names(conf_ctx, mem_ctx, &num_shares,
+ &share_names);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto done;
+ }
+
+ for (count = 0; count < num_shares; count++)
+ {
+ d_printf("%s\n", share_names[count]);
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_drop(struct net_context *c, struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ int ret = -1;
+ sbcErr err;
+
+ if (argc != 0 || c->display_usage) {
+ net_conf_drop_usage(c, argc, argv);
+ goto done;
+ }
+
+ err = smbconf_drop(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error deleting configuration: %s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ return ret;
+}
+
+static int net_conf_showshare(struct net_context *c,
+ struct smbconf_ctx *conf_ctx, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ sbcErr err;
+ const char *sharename = NULL;
+ TALLOC_CTX *mem_ctx;
+ uint32_t count;
+ struct smbconf_service *service = NULL;
+
+ mem_ctx = talloc_stackframe();
+
+ if (argc != 1 || c->display_usage) {
+ net_conf_showshare_usage(c, argc, argv);
+ goto done;
+ }
+
+ sharename = talloc_strdup(mem_ctx, argv[0]);
+ if (sharename == NULL) {
+ d_printf("error: out of memory!\n");
+ goto done;
+ }
+
+ err = smbconf_get_share(conf_ctx, mem_ctx, sharename, &service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error getting share parameters: %s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+
+ d_printf("[%s]\n", service->name);
+
+ for (count = 0; count < service->num_params; count++) {
+ d_printf("\t%s = %s\n", service->param_names[count],
+ service->param_values[count]);
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+/**
+ * Add a share, with a couple of standard parameters, partly optional.
+ *
+ * This is a high level utility function of the net conf utility,
+ * not a direct frontend to the smbconf API.
+ */
+static int net_conf_addshare(struct net_context *c,
+ struct smbconf_ctx *conf_ctx, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ sbcErr err;
+ char *sharename = NULL;
+ const char *path = NULL;
+ const char *comment = NULL;
+ const char *guest_ok = "no";
+ const char *writeable = "no";
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (c->display_usage) {
+ net_conf_addshare_usage(c, argc, argv);
+ ret = 0;
+ goto done;
+ }
+
+ switch (argc) {
+ case 0:
+ case 1:
+ default:
+ net_conf_addshare_usage(c, argc, argv);
+ goto done;
+ case 5:
+ comment = argv[4];
+
+ FALL_THROUGH;
+ case 4:
+ if (!strnequal(argv[3], "guest_ok=", 9)) {
+ net_conf_addshare_usage(c, argc, argv);
+ goto done;
+ }
+ switch (argv[3][9]) {
+ case 'y':
+ case 'Y':
+ guest_ok = "yes";
+ break;
+ case 'n':
+ case 'N':
+ guest_ok = "no";
+ break;
+ default:
+ net_conf_addshare_usage(c, argc, argv);
+ goto done;
+ }
+
+ FALL_THROUGH;
+ case 3:
+ if (!strnequal(argv[2], "writeable=", 10)) {
+ net_conf_addshare_usage(c, argc, argv);
+ goto done;
+ }
+ switch (argv[2][10]) {
+ case 'y':
+ case 'Y':
+ writeable = "yes";
+ break;
+ case 'n':
+ case 'N':
+ writeable = "no";
+ break;
+ default:
+ net_conf_addshare_usage(c, argc, argv);
+ goto done;
+ }
+
+ FALL_THROUGH;
+ case 2:
+ path = argv[1];
+ sharename = talloc_strdup(mem_ctx, argv[0]);
+ if (sharename == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ break;
+ }
+
+ /*
+ * validate arguments
+ */
+
+ /* validate share name */
+
+ if (!validate_net_name(sharename, INVALID_SHARENAME_CHARS,
+ strlen(sharename)))
+ {
+ d_fprintf(stderr, _("ERROR: share name %s contains "
+ "invalid characters (any of %s)\n"),
+ sharename, INVALID_SHARENAME_CHARS);
+ goto done;
+ }
+
+ if (strequal(sharename, GLOBAL_NAME)) {
+ d_fprintf(stderr,
+ _("ERROR: 'global' is not a valid share name.\n"));
+ goto done;
+ }
+
+ if (smbconf_share_exists(conf_ctx, sharename)) {
+ d_fprintf(stderr, _("ERROR: share %s already exists.\n"),
+ sharename);
+ goto done;
+ }
+
+ /* validate path */
+
+ if (path[0] != '/') {
+ bool ok = false;
+
+ if (strequal(sharename, HOMES_NAME) && path[0] == '\0') {
+ /* The homes share can be an empty path. */
+ ok = true;
+ }
+ if (!ok) {
+ d_fprintf(stderr,
+ _("Error: path '%s' is not an absolute path.\n"),
+ path);
+ goto done;
+ }
+ }
+
+ /*
+ * start a transaction
+ */
+
+ err = smbconf_transaction_start(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf("error starting transaction: %s\n",
+ sbcErrorString(err));
+ goto done;
+ }
+
+ /*
+ * create the share
+ */
+
+ err = smbconf_create_share(conf_ctx, sharename);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error creating share %s: %s\n"),
+ sharename, sbcErrorString(err));
+ goto cancel;
+ }
+
+ /*
+ * fill the share with parameters
+ */
+
+ err = smbconf_set_parameter(conf_ctx, sharename, "path", path);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error setting parameter %s: %s\n"),
+ "path", sbcErrorString(err));
+ goto cancel;
+ }
+
+ if (comment != NULL) {
+ err = smbconf_set_parameter(conf_ctx, sharename, "comment",
+ comment);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error setting parameter %s: %s\n"),
+ "comment", sbcErrorString(err));
+ goto cancel;
+ }
+ }
+
+ err = smbconf_set_parameter(conf_ctx, sharename, "guest ok", guest_ok);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error setting parameter %s: %s\n"),
+ "'guest ok'", sbcErrorString(err));
+ goto cancel;
+ }
+
+ err = smbconf_set_parameter(conf_ctx, sharename, "writeable",
+ writeable);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error setting parameter %s: %s\n"),
+ "writeable", sbcErrorString(err));
+ goto cancel;
+ }
+
+ /*
+ * commit the whole thing
+ */
+
+ err = smbconf_transaction_commit(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf("error committing transaction: %s\n",
+ sbcErrorString(err));
+ } else {
+ ret = 0;
+ }
+
+ goto done;
+
+cancel:
+ err = smbconf_transaction_cancel(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf("error cancelling transaction: %s\n",
+ sbcErrorString(err));
+ }
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_delshare(struct net_context *c,
+ struct smbconf_ctx *conf_ctx, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ const char *sharename = NULL;
+ sbcErr err;
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (argc != 1 || c->display_usage) {
+ net_conf_delshare_usage(c, argc, argv);
+ goto done;
+ }
+ sharename = talloc_strdup(mem_ctx, argv[0]);
+ if (sharename == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ err = smbconf_delete_share(conf_ctx, sharename);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error deleting share %s: %s\n"),
+ sharename, sbcErrorString(err));
+ goto done;
+ }
+
+ status = delete_share_security(sharename);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ d_fprintf(stderr, _("deleting share acl failed for %s: %s\n"),
+ sharename, nt_errstr(status));
+ goto done;
+ }
+
+ ret = 0;
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_setparm(struct net_context *c, struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ int ret = -1;
+ sbcErr err;
+ char *service = NULL;
+ char *param = NULL;
+ const char *value_str = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (argc != 3 || c->display_usage) {
+ net_conf_setparm_usage(c, argc, argv);
+ goto done;
+ }
+ /*
+ * NULL service name means "dangling parameters" to libsmbconf.
+ * We use the empty string from the command line for this purpose.
+ */
+ if (strlen(argv[0]) != 0) {
+ service = talloc_strdup(mem_ctx, argv[0]);
+ if (service == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+ }
+ param = strlower_talloc(mem_ctx, argv[1]);
+ if (param == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+ value_str = argv[2];
+
+ if (!net_conf_param_valid(service,param, value_str)) {
+ goto done;
+ }
+
+ err = smbconf_transaction_start(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error starting transaction: %s\n"),
+ sbcErrorString(err));
+ goto done;
+ }
+
+ if (!smbconf_share_exists(conf_ctx, service)) {
+ err = smbconf_create_share(conf_ctx, service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error creating share '%s': %s\n"),
+ service, sbcErrorString(err));
+ goto cancel;
+ }
+ }
+
+ err = smbconf_set_parameter(conf_ctx, service, param, value_str);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error setting value '%s': %s\n"),
+ param, sbcErrorString(err));
+ goto cancel;
+ }
+
+ err = smbconf_transaction_commit(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error committing transaction: %s\n"),
+ sbcErrorString(err));
+ } else {
+ ret = 0;
+ }
+
+ goto done;
+
+cancel:
+ err = smbconf_transaction_cancel(conf_ctx);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error cancelling transaction: %s\n"),
+ sbcErrorString(err));
+ }
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_getparm(struct net_context *c, struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ int ret = -1;
+ sbcErr err;
+ char *service = NULL;
+ char *param = NULL;
+ char *valstr = NULL;
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_stackframe();
+
+ if (argc != 2 || c->display_usage) {
+ net_conf_getparm_usage(c, argc, argv);
+ goto done;
+ }
+ /*
+ * NULL service name means "dangling parameters" to libsmbconf.
+ * We use the empty string from the command line for this purpose.
+ */
+ if (strlen(argv[0]) != 0) {
+ service = talloc_strdup(mem_ctx, argv[0]);
+ if (service == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+ }
+ param = strlower_talloc(mem_ctx, argv[1]);
+ if (param == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ err = smbconf_get_parameter(conf_ctx, mem_ctx, service, param, &valstr);
+ if (SBC_ERROR_EQUAL(err, SBC_ERR_NO_SUCH_SERVICE)) {
+ d_fprintf(stderr,
+ _("Error: given service '%s' does not exist.\n"),
+ service);
+ goto done;
+ } else if (SBC_ERROR_EQUAL(err, SBC_ERR_INVALID_PARAM)) {
+ d_fprintf(stderr,
+ _("Error: given parameter '%s' is not set.\n"),
+ param);
+ goto done;
+ } else if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error getting value '%s': %s.\n"),
+ param, sbcErrorString(err));
+ goto done;
+ }
+
+ d_printf("%s\n", valstr);
+
+ ret = 0;
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_delparm(struct net_context *c, struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ int ret = -1;
+ sbcErr err;
+ char *service = NULL;
+ char *param = NULL;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (argc != 2 || c->display_usage) {
+ net_conf_delparm_usage(c, argc, argv);
+ goto done;
+ }
+ /*
+ * NULL service name means "dangling parameters" to libsmbconf.
+ * We use the empty string from the command line for this purpose.
+ */
+ if (strlen(argv[0]) != 0) {
+ service = talloc_strdup(mem_ctx, argv[0]);
+ if (service == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+ }
+ param = strlower_talloc(mem_ctx, argv[1]);
+ if (param == NULL) {
+ d_printf("error: out of memory!\n");
+ goto done;
+ }
+
+ err = smbconf_delete_parameter(conf_ctx, service, param);
+ if (SBC_ERROR_EQUAL(err, SBC_ERR_NO_SUCH_SERVICE)) {
+ d_fprintf(stderr,
+ _("Error: given service '%s' does not exist.\n"),
+ service);
+ goto done;
+ } else if (SBC_ERROR_EQUAL(err, SBC_ERR_INVALID_PARAM)) {
+ d_fprintf(stderr,
+ _("Error: given parameter '%s' is not set.\n"),
+ param);
+ goto done;
+ } else if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("Error deleting value '%s': %s.\n"),
+ param, sbcErrorString(err));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_getincludes(struct net_context *c,
+ struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ sbcErr err;
+ uint32_t num_includes;
+ uint32_t count;
+ char *service;
+ char **includes = NULL;
+ int ret = -1;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (argc != 1 || c->display_usage) {
+ net_conf_getincludes_usage(c, argc, argv);
+ goto done;
+ }
+
+ service = talloc_strdup(mem_ctx, argv[0]);
+ if (service == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ err = smbconf_get_includes(conf_ctx, mem_ctx, service,
+ &num_includes, &includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error getting includes: %s\n"), sbcErrorString(err));
+ goto done;
+ }
+
+ for (count = 0; count < num_includes; count++) {
+ d_printf("include = %s\n", includes[count]);
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_setincludes(struct net_context *c,
+ struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ sbcErr err;
+ char *service;
+ uint32_t num_includes;
+ const char **includes;
+ int ret = -1;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (argc < 1 || c->display_usage) {
+ net_conf_setincludes_usage(c, argc, argv);
+ goto done;
+ }
+
+ service = talloc_strdup(mem_ctx, argv[0]);
+ if (service == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ num_includes = argc - 1;
+ if (num_includes == 0) {
+ includes = NULL;
+ } else {
+ includes = argv + 1;
+ }
+
+ err = smbconf_set_includes(conf_ctx, service, num_includes, includes);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error setting includes: %s\n"), sbcErrorString(err));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_conf_delincludes(struct net_context *c,
+ struct smbconf_ctx *conf_ctx,
+ int argc, const char **argv)
+{
+ sbcErr err;
+ char *service;
+ int ret = -1;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+
+ if (argc != 1 || c->display_usage) {
+ net_conf_delincludes_usage(c, argc, argv);
+ goto done;
+ }
+
+ service = talloc_strdup(mem_ctx, argv[0]);
+ if (service == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto done;
+ }
+
+ err = smbconf_delete_includes(conf_ctx, service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_printf(_("error deleting includes: %s\n"), sbcErrorString(err));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+
+/**********************************************************************
+ *
+ * Wrapper and net_conf_run_function mechanism.
+ *
+ **********************************************************************/
+
+/**
+ * Wrapper function to call the main conf functions.
+ * The wrapper calls handles opening and closing of the
+ * configuration.
+ */
+static int net_conf_wrap_function(struct net_context *c,
+ int (*fn)(struct net_context *,
+ struct smbconf_ctx *,
+ int, const char **),
+ int argc, const char **argv)
+{
+ sbcErr err;
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct smbconf_ctx *conf_ctx;
+ int ret = -1;
+
+ err = smbconf_init(mem_ctx, &conf_ctx, "registry:");
+ if (!SBC_ERROR_IS_OK(err)) {
+ talloc_free(mem_ctx);
+ return -1;
+ }
+
+ ret = fn(c, conf_ctx, argc, argv);
+
+ smbconf_shutdown(conf_ctx);
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/*
+ * We need a functable struct of our own, because the
+ * functions are called through a wrapper that handles
+ * the opening and closing of the configuration, and so on.
+ */
+struct conf_functable {
+ const char *funcname;
+ int (*fn)(struct net_context *c, struct smbconf_ctx *ctx, int argc,
+ const char **argv);
+ int valid_transports;
+ const char *description;
+ const char *usage;
+};
+
+/**
+ * This imitates net_run_function but calls the main functions
+ * through the wrapper net_conf_wrap_function().
+ */
+static int net_conf_run_function(struct net_context *c, int argc,
+ const char **argv, const char *whoami,
+ struct conf_functable *table)
+{
+ int i;
+
+ if (argc != 0) {
+ for (i=0; table[i].funcname; i++) {
+ if (strcasecmp_m(argv[0], table[i].funcname) == 0)
+ return net_conf_wrap_function(c, table[i].fn,
+ argc-1,
+ argv+1);
+ }
+ }
+
+ d_printf(_("Usage:\n"));
+ for (i=0; table[i].funcname; i++) {
+ if (c->display_usage == false)
+ d_printf("%s %-15s %s\n", whoami, table[i].funcname,
+ table[i].description);
+ else
+ d_printf("%s\n", table[i].usage);
+ }
+
+ return c->display_usage?0:-1;
+}
+
+/*
+ * Entry-point for all the CONF functions.
+ */
+
+int net_conf(struct net_context *c, int argc, const char **argv)
+{
+ int ret = -1;
+ struct conf_functable func_table[] = {
+ {
+ "list",
+ net_conf_list,
+ NET_TRANSPORT_LOCAL,
+ N_("Dump the complete configuration in smb.conf like "
+ "format."),
+ N_("net conf list\n"
+ " Dump the complete configuration in smb.conf "
+ "like format.")
+
+ },
+ {
+ "import",
+ net_conf_import,
+ NET_TRANSPORT_LOCAL,
+ N_("Import configuration from file in smb.conf "
+ "format."),
+ N_("net conf import\n"
+ " Import configuration from file in smb.conf "
+ "format.")
+ },
+ {
+ "listshares",
+ net_conf_listshares,
+ NET_TRANSPORT_LOCAL,
+ N_("List the share names."),
+ N_("net conf listshares\n"
+ " List the share names.")
+ },
+ {
+ "drop",
+ net_conf_drop,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete the complete configuration."),
+ N_("net conf drop\n"
+ " Delete the complete configuration.")
+ },
+ {
+ "showshare",
+ net_conf_showshare,
+ NET_TRANSPORT_LOCAL,
+ N_("Show the definition of a share."),
+ N_("net conf showshare\n"
+ " Show the definition of a share.")
+ },
+ {
+ "addshare",
+ net_conf_addshare,
+ NET_TRANSPORT_LOCAL,
+ N_("Create a new share."),
+ N_("net conf addshare\n"
+ " Create a new share.")
+ },
+ {
+ "delshare",
+ net_conf_delshare,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a share."),
+ N_("net conf delshare\n"
+ " Delete a share.")
+ },
+ {
+ "setparm",
+ net_conf_setparm,
+ NET_TRANSPORT_LOCAL,
+ N_("Store a parameter."),
+ N_("net conf setparm\n"
+ " Store a parameter.")
+ },
+ {
+ "getparm",
+ net_conf_getparm,
+ NET_TRANSPORT_LOCAL,
+ N_("Retrieve the value of a parameter."),
+ N_("net conf getparm\n"
+ " Retrieve the value of a parameter.")
+ },
+ {
+ "delparm",
+ net_conf_delparm,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a parameter."),
+ N_("net conf delparm\n"
+ " Delete a parameter.")
+ },
+ {
+ "getincludes",
+ net_conf_getincludes,
+ NET_TRANSPORT_LOCAL,
+ N_("Show the includes of a share definition."),
+ N_("net conf getincludes\n"
+ " Show the includes of a share definition.")
+ },
+ {
+ "setincludes",
+ net_conf_setincludes,
+ NET_TRANSPORT_LOCAL,
+ N_("Set includes for a share."),
+ N_("net conf setincludes\n"
+ " Set includes for a share.")
+ },
+ {
+ "delincludes",
+ net_conf_delincludes,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete includes from a share definition."),
+ N_("net conf delincludes\n"
+ " Delete includes from a share definition.")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ ret = net_conf_run_function(c, argc, argv, "net conf", func_table);
+
+ return ret;
+}
+
diff --git a/source3/utils/net_conf_util.c b/source3/utils/net_conf_util.c
new file mode 100644
index 0000000..ec6a479
--- /dev/null
+++ b/source3/utils/net_conf_util.c
@@ -0,0 +1,69 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Configuration interface
+ *
+ * Copyright (C) Michael Adam 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Utility functions for net conf and net rpc conf.
+ */
+
+#include "includes.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "lib/param/loadparm.h"
+#include "net_conf_util.h"
+
+bool net_conf_param_valid(const char *service,
+ const char *param,
+ const char *valstr)
+{
+ const char *canon_param, *canon_valstr;
+
+ if (!lp_parameter_is_valid(param)) {
+ d_fprintf(stderr, "Invalid parameter '%s' given.\n", param);
+ return false;
+ }
+
+ if (!smbconf_reg_parameter_is_valid(param)) {
+ d_fprintf(stderr, "Parameter '%s' not allowed in registry.\n",
+ param);
+ return false;
+ }
+
+ if (!strequal(service, GLOBAL_NAME) && lp_parameter_is_global(param)) {
+ d_fprintf(stderr, "Global parameter '%s' not allowed in "
+ "service definition ('%s').\n", param, service);
+ return false;
+ }
+
+ if (!lp_canonicalize_parameter_with_value(param, valstr,
+ &canon_param,
+ &canon_valstr))
+ {
+ /*
+ * We already know the parameter name is valid.
+ * So the value must be invalid.
+ */
+ d_fprintf(stderr, "invalid value '%s' given for "
+ "parameter '%s'\n", valstr, param);
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/utils/net_conf_util.h b/source3/utils/net_conf_util.h
new file mode 100644
index 0000000..798b399
--- /dev/null
+++ b/source3/utils/net_conf_util.h
@@ -0,0 +1,33 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Configuration interface
+ *
+ * Copyright (C) Michael Adam 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NET_CONF_UTIL_H__
+#define __NET_CONF_UTIL_H__
+
+/*
+ * Utility functions for net conf and net rpc conf.
+ */
+
+bool net_conf_param_valid(const char *service,
+ const char *param,
+ const char *valstr);
+
+#endif /* __NET_CONF_UTIL_H__ */
diff --git a/source3/utils/net_dns.c b/source3/utils/net_dns.c
new file mode 100644
index 0000000..9850ba4
--- /dev/null
+++ b/source3/utils/net_dns.c
@@ -0,0 +1,224 @@
+/*
+ Samba Unix/Linux Dynamic DNS Update
+ net ads commands
+
+ Copyright (C) Krishna Ganugapati (krishnag@centeris.com) 2006
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "../lib/addns/dns.h"
+#include "utils/net_dns.h"
+
+#if defined(HAVE_KRB5)
+
+/*********************************************************************
+*********************************************************************/
+
+DNS_ERROR DoDNSUpdate(char *pszServerName,
+ const char *pszDomainName,
+ const char *pszHostName,
+ const struct sockaddr_storage *sslist,
+ size_t num_addrs,
+ uint32_t flags,
+ uint32_t ttl,
+ bool remove_host)
+{
+ DNS_ERROR err;
+ struct dns_connection *conn;
+ TALLOC_CTX *mem_ctx;
+ OM_uint32 minor;
+ struct dns_update_request *req, *resp;
+
+ DEBUG(10,("DoDNSUpdate called with flags: 0x%08x\n", flags));
+
+ if (!(flags & DNS_UPDATE_SIGNED) &&
+ !(flags & DNS_UPDATE_UNSIGNED) &&
+ !(flags & DNS_UPDATE_PROBE)) {
+ return ERROR_DNS_INVALID_PARAMETER;
+ }
+
+ if ( !remove_host && ((num_addrs <= 0) || !sslist) ) {
+ return ERROR_DNS_INVALID_PARAMETER;
+ }
+
+ if (!(mem_ctx = talloc_init("DoDNSUpdate"))) {
+ return ERROR_DNS_NO_MEMORY;
+ }
+
+ err = dns_open_connection( pszServerName, DNS_TCP, mem_ctx, &conn );
+ if (!ERR_DNS_IS_OK(err)) {
+ goto error;
+ }
+
+ if (flags & DNS_UPDATE_PROBE) {
+
+ /*
+ * Probe if everything's fine
+ */
+
+ err = dns_create_probe(mem_ctx, pszDomainName, pszHostName,
+ num_addrs, sslist, &req);
+ if (!ERR_DNS_IS_OK(err)) goto error;
+
+ err = dns_update_transaction(mem_ctx, conn, req, &resp);
+
+ if (!ERR_DNS_IS_OK(err)) {
+ DEBUG(3,("DoDNSUpdate: failed to probe DNS\n"));
+ goto error;
+ }
+
+ if ((dns_response_code(resp->flags) == DNS_NO_ERROR) &&
+ (flags & DNS_UPDATE_PROBE_SUFFICIENT)) {
+ TALLOC_FREE(mem_ctx);
+ return ERROR_DNS_SUCCESS;
+ }
+ }
+
+ if (flags & DNS_UPDATE_UNSIGNED) {
+
+ /*
+ * First try without signing
+ */
+
+ err = dns_create_update_request(mem_ctx,
+ pszDomainName,
+ pszHostName,
+ sslist,
+ num_addrs,
+ ttl,
+ &req);
+ if (!ERR_DNS_IS_OK(err)) goto error;
+
+ err = dns_update_transaction(mem_ctx, conn, req, &resp);
+ if (!ERR_DNS_IS_OK(err)) {
+ DEBUG(3,("DoDNSUpdate: unsigned update failed\n"));
+ goto error;
+ }
+
+ if ((dns_response_code(resp->flags) == DNS_NO_ERROR) &&
+ (flags & DNS_UPDATE_UNSIGNED_SUFFICIENT)) {
+ TALLOC_FREE(mem_ctx);
+ return ERROR_DNS_SUCCESS;
+ }
+ }
+
+ /*
+ * Okay, we have to try with signing
+ */
+ if (flags & DNS_UPDATE_SIGNED) {
+ gss_ctx_id_t gss_context;
+ char *keyname;
+
+ err = dns_create_update_request(mem_ctx,
+ pszDomainName,
+ pszHostName,
+ sslist,
+ num_addrs,
+ ttl,
+ &req);
+ if (!ERR_DNS_IS_OK(err)) goto error;
+
+ if (!(keyname = dns_generate_keyname( mem_ctx ))) {
+ err = ERROR_DNS_NO_MEMORY;
+ goto error;
+ }
+
+ err = dns_negotiate_sec_ctx( pszDomainName, pszServerName,
+ keyname, &gss_context, DNS_SRV_ANY );
+
+ /* retry using the Windows 2000 DNS hack */
+ if (!ERR_DNS_IS_OK(err)) {
+ err = dns_negotiate_sec_ctx( pszDomainName, pszServerName,
+ keyname, &gss_context,
+ DNS_SRV_WIN2000 );
+ }
+
+ if (!ERR_DNS_IS_OK(err))
+ goto error;
+
+ err = dns_sign_update(req, gss_context, keyname,
+ "gss.microsoft.com", time(NULL), 3600);
+
+ gss_delete_sec_context(&minor, &gss_context, GSS_C_NO_BUFFER);
+
+ if (!ERR_DNS_IS_OK(err)) goto error;
+
+ err = dns_update_transaction(mem_ctx, conn, req, &resp);
+ if (!ERR_DNS_IS_OK(err)) goto error;
+
+ err = (dns_response_code(resp->flags) == DNS_NO_ERROR) ?
+ ERROR_DNS_SUCCESS : ERROR_DNS_UPDATE_FAILED;
+
+ if (!ERR_DNS_IS_OK(err)) {
+ DEBUG(3,("DoDNSUpdate: signed update failed\n"));
+ }
+ }
+
+
+error:
+ TALLOC_FREE(mem_ctx);
+ return err;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+int get_my_ip_address( struct sockaddr_storage **pp_ss )
+
+{
+ int i, n;
+ struct sockaddr_storage *list = NULL;
+ int count = 0;
+
+ /* Honor the configured list of interfaces to register */
+
+ load_interfaces();
+ n = iface_count();
+
+ if (n <= 0) {
+ return -1;
+ }
+
+ if ( (list = SMB_MALLOC_ARRAY( struct sockaddr_storage, n )) == NULL ) {
+ return -1;
+ }
+
+ for ( i=0; i<n; i++ ) {
+ const struct sockaddr_storage *nic_sa_storage = NULL;
+
+ if ((nic_sa_storage = iface_n_sockaddr_storage(i)) == NULL)
+ continue;
+
+ /* Don't register loopback addresses */
+ if (is_loopback_addr((const struct sockaddr *)nic_sa_storage)) {
+ continue;
+ }
+
+ /* Don't register link-local addresses */
+ if (is_linklocal_addr(nic_sa_storage)) {
+ continue;
+ }
+
+ memcpy(&list[count++], nic_sa_storage, sizeof(struct sockaddr_storage));
+ }
+ *pp_ss = list;
+
+ return count;
+}
+
+#endif /* defined(HAVE_KRB5) */
diff --git a/source3/utils/net_dns.h b/source3/utils/net_dns.h
new file mode 100644
index 0000000..4569e1c
--- /dev/null
+++ b/source3/utils/net_dns.h
@@ -0,0 +1,44 @@
+/*
+ Samba Unix/Linux Dynamic DNS Update
+ net ads commands
+
+ Copyright (C) Krishna Ganugapati (krishnag@centeris.com) 2006
+ Copyright (C) Gerald Carter 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* flags for DoDNSUpdate */
+
+#define DNS_UPDATE_SIGNED 0x01
+#define DNS_UPDATE_SIGNED_SUFFICIENT 0x02
+#define DNS_UPDATE_UNSIGNED 0x04
+#define DNS_UPDATE_UNSIGNED_SUFFICIENT 0x08
+#define DNS_UPDATE_PROBE 0x10
+#define DNS_UPDATE_PROBE_SUFFICIENT 0x20
+
+#if defined(HAVE_KRB5)
+
+#include "../lib/addns/dns.h"
+
+DNS_ERROR DoDNSUpdate(char *pszServerName,
+ const char *pszDomainName,
+ const char *pszHostName,
+ const struct sockaddr_storage *sslist,
+ size_t num_addrs,
+ uint32_t flags,
+ uint32_t ttl,
+ bool remove_host);
+
+#endif /* defined(HAVE_KRB5) */
diff --git a/source3/utils/net_dom.c b/source3/utils/net_dom.c
new file mode 100644
index 0000000..4342990
--- /dev/null
+++ b/source3/utils/net_dom.c
@@ -0,0 +1,385 @@
+/*
+ Samba Unix/Linux SMB client library
+ net dom commands for remote join/unjoin
+ Copyright (C) 2007,2009 Günther Deschner
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "../librpc/gen_ndr/ndr_initshutdown.h"
+#include "../librpc/gen_ndr/ndr_winreg.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_net.h"
+#include "libsmb/libsmb.h"
+
+int net_dom_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net dom join "
+ "<domain=DOMAIN> <ou=OU> <account=ACCOUNT> "
+ "<password=PASSWORD> <reboot>\n Join a remote machine\n"));
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net dom unjoin "
+ "<account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+ " Unjoin a remote machine\n"));
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net dom renamecomputer "
+ "<newname=NEWNAME> "
+ "<account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+ " Rename joined computer\n"));
+
+ return -1;
+}
+
+static int net_dom_unjoin(struct net_context *c, int argc, const char **argv)
+{
+ const char *server_name = NULL;
+ const char *account = NULL;
+ const char *password = NULL;
+ uint32_t unjoin_flags = NETSETUP_ACCT_DELETE |
+ NETSETUP_JOIN_DOMAIN |
+ NETSETUP_IGNORE_UNSUPPORTED_FLAGS;
+ struct cli_state *cli = NULL;
+ bool do_reboot = false;
+ NTSTATUS ntstatus;
+ NET_API_STATUS status;
+ int ret = -1;
+ int i;
+
+ if (argc < 1 || c->display_usage) {
+ return net_dom_usage(c, argc, argv);
+ }
+
+ if (c->opt_host) {
+ server_name = c->opt_host;
+ }
+
+ for (i=0; i<argc; i++) {
+ if (strnequal(argv[i], "account", strlen("account"))) {
+ account = get_string_param(argv[i]);
+ if (!account) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "password", strlen("password"))) {
+ password = get_string_param(argv[i]);
+ if (!password) {
+ return -1;
+ }
+ }
+ if (strequal(argv[i], "reboot")) {
+ do_reboot = true;
+ }
+ }
+
+ if (do_reboot) {
+ ntstatus = net_make_ipc_connection_ex(c, c->opt_workgroup,
+ server_name, NULL, 0,
+ &cli);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ return -1;
+ }
+ }
+
+ status = NetUnjoinDomain(server_name, account, password, unjoin_flags);
+ if (status != 0) {
+ printf(_("Failed to unjoin domain: %s\n"),
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ goto done;
+ }
+
+ if (do_reboot) {
+ c->opt_comment = _("Shutting down due to a domain membership "
+ "change");
+ c->opt_reboot = true;
+ c->opt_timeout = 30;
+
+ ret = run_rpc_command(c, cli,
+ &ndr_table_initshutdown,
+ 0, rpc_init_shutdown_internals,
+ argc, argv);
+ if (ret == 0) {
+ goto done;
+ }
+
+ ret = run_rpc_command(c, cli, &ndr_table_winreg, 0,
+ rpc_reg_shutdown_internals,
+ argc, argv);
+ goto done;
+ }
+
+ ret = 0;
+
+ done:
+ if (cli) {
+ cli_shutdown(cli);
+ }
+
+ return ret;
+}
+
+static int net_dom_join(struct net_context *c, int argc, const char **argv)
+{
+ const char *server_name = NULL;
+ const char *domain_name = NULL;
+ const char *account_ou = NULL;
+ const char *Account = NULL;
+ const char *password = NULL;
+ uint32_t join_flags = NETSETUP_ACCT_CREATE |
+ NETSETUP_JOIN_DOMAIN;
+ struct cli_state *cli = NULL;
+ bool do_reboot = false;
+ NTSTATUS ntstatus;
+ NET_API_STATUS status;
+ int ret = -1;
+ int i;
+
+ if (argc < 1 || c->display_usage) {
+ return net_dom_usage(c, argc, argv);
+ }
+
+ net_warn_member_options();
+
+ if (c->opt_host) {
+ server_name = c->opt_host;
+ }
+
+ if (c->opt_force) {
+ join_flags |= NETSETUP_DOMAIN_JOIN_IF_JOINED;
+ }
+
+ for (i=0; i<argc; i++) {
+ if (strnequal(argv[i], "ou", strlen("ou"))) {
+ account_ou = get_string_param(argv[i]);
+ if (!account_ou) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "domain", strlen("domain"))) {
+ domain_name = get_string_param(argv[i]);
+ if (!domain_name) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "account", strlen("account"))) {
+ Account = get_string_param(argv[i]);
+ if (!Account) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "password", strlen("password"))) {
+ password = get_string_param(argv[i]);
+ if (!password) {
+ return -1;
+ }
+ }
+ if (strequal(argv[i], "reboot")) {
+ do_reboot = true;
+ }
+ }
+
+ if (do_reboot) {
+ ntstatus = net_make_ipc_connection_ex(c, c->opt_workgroup,
+ server_name, NULL, 0,
+ &cli);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ return -1;
+ }
+ }
+
+ /* check if domain is a domain or a workgroup */
+
+ status = NetJoinDomain(server_name, domain_name, account_ou,
+ Account, password, join_flags);
+ if (status != 0) {
+ printf(_("Failed to join domain: %s\n"),
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ goto done;
+ }
+
+ if (do_reboot) {
+ c->opt_comment = _("Shutting down due to a domain membership "
+ "change");
+ c->opt_reboot = true;
+ c->opt_timeout = 30;
+
+ ret = run_rpc_command(c, cli, &ndr_table_initshutdown, 0,
+ rpc_init_shutdown_internals,
+ argc, argv);
+ if (ret == 0) {
+ goto done;
+ }
+
+ ret = run_rpc_command(c, cli, &ndr_table_winreg, 0,
+ rpc_reg_shutdown_internals,
+ argc, argv);
+ goto done;
+ }
+
+ ret = 0;
+
+ done:
+ if (cli) {
+ cli_shutdown(cli);
+ }
+
+ return ret;
+}
+
+static int net_dom_renamecomputer(struct net_context *c, int argc, const char **argv)
+{
+ const char *server_name = NULL;
+ const char *account = NULL;
+ const char *password = NULL;
+ const char *newname = NULL;
+ uint32_t rename_options = NETSETUP_ACCT_CREATE;
+ struct cli_state *cli = NULL;
+ bool do_reboot = false;
+ NTSTATUS ntstatus;
+ NET_API_STATUS status;
+ int ret = -1;
+ int i;
+
+ if (argc < 1 || c->display_usage) {
+ return net_dom_usage(c, argc, argv);
+ }
+
+ if (c->opt_host) {
+ server_name = c->opt_host;
+ }
+
+ for (i=0; i<argc; i++) {
+ if (strnequal(argv[i], "account", strlen("account"))) {
+ account = get_string_param(argv[i]);
+ if (!account) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "password", strlen("password"))) {
+ password = get_string_param(argv[i]);
+ if (!password) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "newname", strlen("newname"))) {
+ newname = get_string_param(argv[i]);
+ if (!newname) {
+ return -1;
+ }
+ }
+ if (strequal(argv[i], "reboot")) {
+ do_reboot = true;
+ }
+ }
+
+ if (do_reboot) {
+ ntstatus = net_make_ipc_connection_ex(c, c->opt_workgroup,
+ server_name, NULL, 0,
+ &cli);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ return -1;
+ }
+ }
+
+ status = NetRenameMachineInDomain(server_name, newname,
+ account, password, rename_options);
+ if (status != 0) {
+ printf(_("Failed to rename machine: "));
+ if (status == W_ERROR_V(WERR_NERR_SETUPNOTJOINED)) {
+ printf(_("Computer is not joined to a Domain\n"));
+ goto done;
+ }
+ printf("%s\n",
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ goto done;
+ }
+
+ if (do_reboot) {
+ c->opt_comment = _("Shutting down due to a computer rename");
+ c->opt_reboot = true;
+ c->opt_timeout = 30;
+
+ ret = run_rpc_command(c, cli,
+ &ndr_table_initshutdown,
+ 0, rpc_init_shutdown_internals,
+ argc, argv);
+ if (ret == 0) {
+ goto done;
+ }
+
+ ret = run_rpc_command(c, cli, &ndr_table_winreg, 0,
+ rpc_reg_shutdown_internals,
+ argc, argv);
+ goto done;
+ }
+
+ ret = 0;
+
+ done:
+ if (cli) {
+ cli_shutdown(cli);
+ }
+
+ return ret;
+}
+
+int net_dom(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+
+ struct functable func[] = {
+ {
+ "join",
+ net_dom_join,
+ NET_TRANSPORT_LOCAL,
+ N_("Join a remote machine"),
+ N_("net dom join <domain=DOMAIN> <ou=OU> "
+ "<account=ACCOUNT> <password=PASSWORD> <reboot>\n"
+ " Join a remote machine")
+ },
+ {
+ "unjoin",
+ net_dom_unjoin,
+ NET_TRANSPORT_LOCAL,
+ N_("Unjoin a remote machine"),
+ N_("net dom unjoin <account=ACCOUNT> "
+ "<password=PASSWORD> <reboot>\n"
+ " Unjoin a remote machine")
+ },
+ {
+ "renamecomputer",
+ net_dom_renamecomputer,
+ NET_TRANSPORT_LOCAL,
+ N_("Rename a computer that is joined to a domain"),
+ N_("net dom renamecomputer <newname=NEWNAME> "
+ "<account=ACCOUNT> <password=PASSWORD> "
+ "<reboot>\n"
+ " Rename joined computer")
+ },
+
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (status != 0) {
+ return -1;
+ }
+
+ return net_run_function(c, argc, argv, "net dom", func);
+}
diff --git a/source3/utils/net_eventlog.c b/source3/utils/net_eventlog.c
new file mode 100644
index 0000000..24dbab9
--- /dev/null
+++ b/source3/utils/net_eventlog.c
@@ -0,0 +1,275 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Local win32 eventlog interface
+ *
+ * Copyright (C) Guenther Deschner 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "lib/eventlog/eventlog.h"
+
+/**
+ * Dump an *evt win32 eventlog file
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int net_eventlog_dump(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ struct EVENTLOG_EVT_FILE evt;
+ char *s;
+
+ if (argc < 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\nnet eventlog dump <file.evt>\n",
+ _("Usage:"));
+ goto done;
+ }
+
+ blob.data = (uint8_t *)file_load(argv[0], &blob.length, 0, ctx);
+ if (!blob.data) {
+ d_fprintf(stderr, _("failed to load evt file: %s\n"), argv[0]);
+ goto done;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, ctx, &evt,
+ (ndr_pull_flags_fn_t)ndr_pull_EVENTLOG_EVT_FILE);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_fprintf(stderr, _("evt pull failed: %s\n"),
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ s = NDR_PRINT_STRUCT_STRING(ctx, EVENTLOG_EVT_FILE, &evt);
+ if (s) {
+ printf("%s\n", s);
+ }
+
+ ret = 0;
+ done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/**
+ * Import an *evt win32 eventlog file to internal tdb representation
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int net_eventlog_import(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ NTSTATUS status;
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ uint32_t num_records = 0;
+ uint32_t i;
+ ELOG_TDB *etdb = NULL;
+
+ struct EVENTLOGHEADER evt_header;
+ struct EVENTLOG_EVT_FILE evt;
+
+ if (argc < 2 || c->display_usage) {
+ d_fprintf(stderr,
+ "%s\nnet eventlog import <file> <eventlog>\n",
+ _("Usage:"));
+ goto done;
+ }
+
+ blob.data = (uint8_t *)file_load(argv[0], &blob.length, 0, ctx);
+ if (!blob.data) {
+ d_fprintf(stderr, _("failed to load evt file: %s\n"), argv[0]);
+ goto done;
+ }
+
+ /* dump_data(0, blob.data, blob.length); */
+ ndr_err = ndr_pull_struct_blob(&blob, ctx, &evt_header,
+ (ndr_pull_flags_fn_t)ndr_pull_EVENTLOGHEADER);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_fprintf(stderr, _("evt header pull failed: %s\n"),
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ if (evt_header.Flags & ELF_LOGFILE_HEADER_WRAP) {
+ d_fprintf(stderr, _("input file is wrapped, cannot proceed\n"));
+ goto done;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, ctx, &evt,
+ (ndr_pull_flags_fn_t)ndr_pull_EVENTLOG_EVT_FILE);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_fprintf(stderr, _("evt pull failed: %s\n"),
+ ndr_errstr(ndr_err));
+ goto done;
+ }
+
+ /* NDR_PRINT_DEBUG(EVENTLOG_EVT_FILE, &evt); */
+
+ etdb = elog_open_tdb(argv[1], false, false);
+ if (!etdb) {
+ d_fprintf(stderr, _("can't open the eventlog TDB (%s)\n"),
+ argv[1]);
+ goto done;
+ }
+
+ num_records = evt.hdr.CurrentRecordNumber - evt.hdr.OldestRecordNumber;
+
+ for (i=0; i<num_records; i++) {
+ uint32_t record_number;
+ struct eventlog_Record_tdb e;
+
+ status = evlog_evt_entry_to_tdb_entry(ctx, &evt.records[i], &e);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = evlog_push_record_tdb(ctx, ELOG_TDB_CTX(etdb),
+ &e, &record_number);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("can't write to the eventlog: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ }
+
+ printf(_("wrote %d entries to tdb\n"), i);
+
+ ret = 0;
+ done:
+
+ elog_close_tdb(etdb, false);
+
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/**
+ * Export internal eventlog tdb representation to an *evt win32 eventlog file
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int net_eventlog_export(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ NTSTATUS status;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ DATA_BLOB blob;
+ uint32_t num_records = 0;
+ ELOG_TDB *etdb = NULL;
+
+ if (argc < 2 || c->display_usage) {
+ d_fprintf(stderr,
+ "%s\nnet eventlog export <file> <eventlog>\n",
+ _("Usage:"));
+ goto done;
+ }
+
+ etdb = elog_open_tdb(argv[1], false, true);
+ if (!etdb) {
+ d_fprintf(stderr, _("can't open the eventlog TDB (%s)\n"),
+ argv[1]);
+ goto done;
+ }
+
+ status = evlog_convert_tdb_to_evt(ctx, etdb, &blob, &num_records);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (!file_save(argv[0], blob.data, blob.length)) {
+ d_fprintf(stderr, _("failed to save evt file: %s\n"), argv[0]);
+ goto done;
+ }
+
+ ret = 0;
+ done:
+
+ elog_close_tdb(etdb, false);
+
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/**
+ * 'net rpc eventlog' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_eventlog(struct net_context *c, int argc, const char **argv)
+{
+ int ret = -1;
+
+ struct functable func[] = {
+ {
+ "dump",
+ net_eventlog_dump,
+ NET_TRANSPORT_LOCAL,
+ N_("Dump eventlog"),
+ N_("net eventlog dump\n"
+ " Dump win32 *.evt eventlog file")
+ },
+ {
+ "import",
+ net_eventlog_import,
+ NET_TRANSPORT_LOCAL,
+ N_("Import eventlog"),
+ N_("net eventlog import\n"
+ " Import win32 *.evt eventlog file")
+ },
+ {
+ "export",
+ net_eventlog_export,
+ NET_TRANSPORT_LOCAL,
+ N_("Export eventlog"),
+ N_("net eventlog export\n"
+ " Export win32 *.evt eventlog file")
+ },
+
+
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ ret = net_run_function(c, argc, argv, "net eventlog", func);
+
+ return ret;
+}
diff --git a/source3/utils/net_file.c b/source3/utils/net_file.c
new file mode 100644
index 0000000..71a7e05
--- /dev/null
+++ b/source3/utils/net_file.c
@@ -0,0 +1,57 @@
+/*
+ Samba Unix/Linux SMB client library
+ net file commands
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2002 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+
+int net_file_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net [<method>] file [misc. options] [targets]\n"
+ "\tlists all open files on file server\n"));
+ d_printf(_("net [<method>] file USER <username> "
+ "[misc. options] [targets]"
+ "\n\tlists all files opened by username on file server\n"));
+ d_printf(_("net [<method>] file CLOSE <id> [misc. options] [targets]\n"
+ "\tcloses specified file on target server\n"));
+ d_printf(_("net [rap] file INFO <id> [misc. options] [targets]\n"
+ "\tdisplays information about the specified open file\n"));
+
+ net_common_methods_usage(c, argc, argv);
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+int net_file(struct net_context *c, int argc, const char **argv)
+{
+ if (argc < 1)
+ return net_file_usage(c, argc, argv);
+
+ if (strcasecmp_m(argv[0], "HELP") == 0) {
+ net_file_usage(c, argc, argv);
+ return 0;
+ }
+
+ if (net_rpc_check(c, 0))
+ return net_rpc_file(c, argc, argv);
+ return net_rap_file(c, argc, argv);
+}
+
+
diff --git a/source3/utils/net_g_lock.c b/source3/utils/net_g_lock.c
new file mode 100644
index 0000000..a100028
--- /dev/null
+++ b/source3/utils/net_g_lock.c
@@ -0,0 +1,267 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Interface to the g_lock facility
+ * Copyright (C) Volker Lendecke 2009
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "net.h"
+#include "lib/util/server_id.h"
+#include "g_lock.h"
+#include "messages.h"
+#include "lib/util/util_tdb.h"
+
+static bool net_g_lock_init(TALLOC_CTX *mem_ctx,
+ struct tevent_context **pev,
+ struct messaging_context **pmsg,
+ struct g_lock_ctx **pg_ctx)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *g_ctx = NULL;
+
+ ev = samba_tevent_context_init(mem_ctx);
+ if (ev == NULL) {
+ d_fprintf(stderr, "ERROR: could not init event context\n");
+ goto fail;
+ }
+ msg = messaging_init(mem_ctx, ev);
+ if (msg == NULL) {
+ d_fprintf(stderr, "ERROR: could not init messaging context\n");
+ goto fail;
+ }
+ g_ctx = g_lock_ctx_init(mem_ctx, msg);
+ if (g_ctx == NULL) {
+ d_fprintf(stderr, "ERROR: could not init g_lock context\n");
+ goto fail;
+ }
+
+ *pev = ev;
+ *pmsg = msg;
+ *pg_ctx = g_ctx;
+ return true;
+fail:
+ TALLOC_FREE(g_ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return false;
+}
+
+static int net_g_lock_do(struct net_context *c, int argc, const char **argv)
+{
+ struct g_lock_ctx *ctx = NULL;
+ TDB_DATA key = {0};
+ const char *cmd = NULL;
+ int timeout;
+ NTSTATUS status;
+ int result = -1;
+
+ if (argc != 3) {
+ d_printf("Usage: net g_lock do <lockname> <timeout> "
+ "<command>\n");
+ return -1;
+ }
+ key = string_term_tdb_data(argv[0]);
+ timeout = atoi(argv[1]);
+ cmd = argv[2];
+
+ ctx = g_lock_ctx_init(c, c->msg_ctx);
+ if (ctx == NULL) {
+ d_fprintf(stderr, _("g_lock_ctx_init failed\n"));
+ return -1;
+ }
+ status = g_lock_lock(
+ ctx,
+ key,
+ G_LOCK_WRITE,
+ timeval_set(timeout / 1000, timeout % 1000),
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("g_lock_lock failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ result = system(cmd);
+
+ g_lock_unlock(ctx, key);
+
+ if (result == -1) {
+ d_fprintf(stderr, "ERROR: system() returned %s\n",
+ strerror(errno));
+ goto done;
+ }
+ d_fprintf(stderr, "command returned %d\n", result);
+
+done:
+ TALLOC_FREE(ctx);
+ return result;
+}
+
+static void net_g_lock_dump_fn(struct server_id exclusive,
+ size_t num_shared,
+ const struct server_id *shared,
+ const uint8_t *data,
+ size_t datalen,
+ void *private_data)
+{
+ struct server_id_buf idbuf;
+
+ if (exclusive.pid != 0) {
+ d_printf("%s: WRITE\n",
+ server_id_str_buf(exclusive, &idbuf));
+ } else {
+ size_t i;
+ for (i=0; i<num_shared; i++) {
+ d_printf("%s: READ\n",
+ server_id_str_buf(shared[i], &idbuf));
+ }
+ }
+ dump_data_file(data, datalen, true, stdout);
+}
+
+static int net_g_lock_dump(struct net_context *c, int argc, const char **argv)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *g_ctx = NULL;
+ int ret = -1;
+
+ if (argc != 1) {
+ d_printf("Usage: net g_lock dump <lockname>\n");
+ return -1;
+ }
+
+ if (!net_g_lock_init(talloc_tos(), &ev, &msg, &g_ctx)) {
+ goto done;
+ }
+
+ (void)g_lock_dump(g_ctx, string_term_tdb_data(argv[0]),
+ net_g_lock_dump_fn, NULL);
+
+ ret = 0;
+done:
+ TALLOC_FREE(g_ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret;
+}
+
+static int net_g_lock_dumpall_fn(TDB_DATA key, void *private_data)
+{
+ struct g_lock_ctx *g_ctx = talloc_get_type_abort(
+ private_data, struct g_lock_ctx);
+
+ dump_data_file(key.dptr, key.dsize, true, stdout);
+ g_lock_dump(g_ctx, key, net_g_lock_dump_fn, NULL);
+ printf("\n");
+
+ return 0;
+}
+
+static int net_g_lock_dumpall(
+ struct net_context *c, int argc, const char **argv)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *g_ctx = NULL;
+ int ret = -1;
+
+ if (argc != 0) {
+ d_printf("Usage: net g_lock locks\n");
+ return -1;
+ }
+
+ if (!net_g_lock_init(talloc_tos(), &ev, &msg, &g_ctx)) {
+ goto done;
+ }
+
+ ret = g_lock_locks(g_ctx, net_g_lock_dumpall_fn, g_ctx);
+done:
+ TALLOC_FREE(g_ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret < 0 ? -1 : ret;
+}
+
+static int net_g_lock_locks_fn(TDB_DATA key, void *private_data)
+{
+ dump_data_file(key.dptr, key.dsize, true, stdout);
+ return 0;
+}
+
+static int net_g_lock_locks(struct net_context *c, int argc, const char **argv)
+{
+ struct tevent_context *ev = NULL;
+ struct messaging_context *msg = NULL;
+ struct g_lock_ctx *g_ctx = NULL;
+ int ret = -1;
+
+ if (argc != 0) {
+ d_printf("Usage: net g_lock locks\n");
+ return -1;
+ }
+
+ if (!net_g_lock_init(talloc_tos(), &ev, &msg, &g_ctx)) {
+ goto done;
+ }
+
+ ret = g_lock_locks(g_ctx, net_g_lock_locks_fn, NULL);
+done:
+ TALLOC_FREE(g_ctx);
+ TALLOC_FREE(msg);
+ TALLOC_FREE(ev);
+ return ret < 0 ? -1 : ret;
+}
+
+int net_g_lock(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "do",
+ net_g_lock_do,
+ NET_TRANSPORT_LOCAL,
+ N_("Execute a shell command under a lock"),
+ N_("net g_lock do <lock name> <timeout> <command>\n")
+ },
+ {
+ "locks",
+ net_g_lock_locks,
+ NET_TRANSPORT_LOCAL,
+ N_("List all locknames"),
+ N_("net g_lock locks\n")
+ },
+ {
+ "dump",
+ net_g_lock_dump,
+ NET_TRANSPORT_LOCAL,
+ N_("Dump a g_lock locking table"),
+ N_("net g_lock dump <lock name>\n")
+ },
+ {
+ "dumpall",
+ net_g_lock_dumpall,
+ NET_TRANSPORT_LOCAL,
+ N_("Dump all g_lock locking tables"),
+ N_("net g_lock dumpall\n")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net g_lock", func);
+}
diff --git a/source3/utils/net_group.c b/source3/utils/net_group.c
new file mode 100644
index 0000000..505856a
--- /dev/null
+++ b/source3/utils/net_group.c
@@ -0,0 +1,68 @@
+/*
+ Samba Unix/Linux SMB client library
+ net group commands
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2002 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+
+int net_group_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net [<method>] group [misc. options] [targets]"
+ "\n\tList user groups\n\n"));
+ d_printf(_("net rpc group LIST [global|local|builtin]* [misc. options]"
+ "\n\tList specific user groups\n\n"));
+ d_printf(_("net [<method>] group DELETE <name> "
+ "[misc. options] [targets]"
+ "\n\tDelete specified group\n"));
+ d_printf(_("\nnet [<method>] group ADD <name> [-C comment] "
+ "[-c container] [misc. options] [targets]\n"
+ "\tCreate specified group\n"));
+ d_printf(_("\nnet rpc group MEMBERS <name>\n\tList Group Members\n\n"));
+ d_printf(_("\nnet rpc group ADDMEM <group> <member>\n"
+ "\tAdd Group Members\n\n"));
+ d_printf(_("\nnet rpc group DELMEM <group> <member>\n"
+ "\tDelete Group Members\n\n"));
+ net_common_methods_usage(c, argc, argv);
+ net_common_flags_usage(c, argc, argv);
+ d_printf(_("\t-C or --comment=<comment>\tdescriptive comment "
+ "(for add only)\n"));
+ d_printf(_("\t-c or --container=<container>\tLDAP container, "
+ "defaults to cn=Users (for add in ADS only)\n"));
+ d_printf(_("\t-L or --localgroup\t\tWhen adding groups, "
+ "create a local group (alias)\n"));
+ return -1;
+}
+
+int net_group(struct net_context *c, int argc, const char **argv)
+{
+ if (argc < 1)
+ return net_group_usage(c, argc, argv);
+
+ if (strcasecmp_m(argv[0], "HELP") == 0) {
+ net_group_usage(c, argc, argv);
+ return 0;
+ }
+
+ if (net_ads_check(c) == 0)
+ return net_ads_group(c, argc, argv);
+
+ return net_rap_group(c, argc, argv);
+}
+
diff --git a/source3/utils/net_groupmap.c b/source3/utils/net_groupmap.c
new file mode 100644
index 0000000..4f36d45
--- /dev/null
+++ b/source3/utils/net_groupmap.c
@@ -0,0 +1,1023 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * RPC Pipe client / server routines
+ * Copyright (C) Andrew Tridgell 1992-2000,
+ * Copyright (C) Jean François Micouleau 1998-2001.
+ * Copyright (C) Gerald Carter 2003,
+ * Copyright (C) Volker Lendecke 2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "utils/net.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "lib/util/string_wrappers.h"
+
+/*********************************************************
+ Figure out if the input was an NT group or a SID string.
+ Return the SID.
+**********************************************************/
+static bool get_sid_from_input(struct dom_sid *sid, char *input)
+{
+ GROUP_MAP *map;
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return false;
+ }
+
+ if (strncasecmp_m( input, "S-", 2)) {
+ /* Perhaps its the NT group name? */
+ if (!pdb_getgrnam(map, input)) {
+ printf(_("NT Group %s doesn't exist in mapping DB\n"),
+ input);
+ TALLOC_FREE(map);
+ return false;
+ } else {
+ *sid = map->sid;
+ }
+ } else {
+ if (!string_to_sid(sid, input)) {
+ printf(_("converting sid %s from a string failed!\n"),
+ input);
+ TALLOC_FREE(map);
+ return false;
+ }
+ }
+ TALLOC_FREE(map);
+ return true;
+}
+
+/*********************************************************
+ Dump a GROUP_MAP entry to stdout (long or short listing)
+**********************************************************/
+
+static void print_map_entry (const GROUP_MAP *map, bool long_list)
+{
+ struct dom_sid_buf buf;
+
+ if (!long_list)
+ d_printf("%s (%s) -> %s\n", map->nt_name,
+ dom_sid_str_buf(&map->sid, &buf),
+ gidtoname(map->gid));
+ else {
+ d_printf("%s\n", map->nt_name);
+ d_printf(_("\tSID : %s\n"),
+ dom_sid_str_buf(&map->sid, &buf));
+ d_printf(_("\tUnix gid : %u\n"), (unsigned int)map->gid);
+ d_printf(_("\tUnix group: %s\n"), gidtoname(map->gid));
+ d_printf(_("\tGroup type: %s\n"),
+ sid_type_lookup(map->sid_name_use));
+ d_printf(_("\tComment : %s\n"), map->comment);
+ }
+
+}
+/*********************************************************
+ List the groups.
+**********************************************************/
+static int net_groupmap_list(struct net_context *c, int argc, const char **argv)
+{
+ size_t entries;
+ bool long_list = false;
+ size_t i;
+ fstring ntgroup = "";
+ fstring sid_string = "";
+ const char list_usage_str[] = N_("net groupmap list [verbose] "
+ "[ntgroup=NT group] [sid=SID]\n"
+ " verbose\tPrint verbose list\n"
+ " ntgroup\tNT group to list\n"
+ " sid\tSID of group to list");
+
+ if (c->display_usage) {
+ d_printf("%s\n%s\n", _("Usage: "), list_usage_str);
+ return 0;
+ }
+
+ if (c->opt_verbose || c->opt_long_list_entries)
+ long_list = true;
+
+ /* get the options */
+ for ( i=0; i<argc; i++ ) {
+ if ( !strcasecmp_m(argv[i], "verbose")) {
+ long_list = true;
+ }
+ else if ( !strncasecmp_m(argv[i], "ntgroup", strlen("ntgroup")) ) {
+ fstrcpy( ntgroup, get_string_param( argv[i] ) );
+ if ( !ntgroup[0] ) {
+ d_fprintf(stderr, _("must supply a name\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "sid", strlen("sid")) ) {
+ fstrcpy( sid_string, get_string_param( argv[i] ) );
+ if ( !sid_string[0] ) {
+ d_fprintf(stderr, _("must supply a SID\n"));
+ return -1;
+ }
+ }
+ else {
+ d_fprintf(stderr, _("Bad option: %s\n"), argv[i]);
+ d_printf("%s\n%s\n", _("Usage:"), list_usage_str);
+ return -1;
+ }
+ }
+
+ /* list a single group is given a name */
+ if ( ntgroup[0] || sid_string[0] ) {
+ struct dom_sid sid;
+ GROUP_MAP *map;
+
+ if ( sid_string[0] )
+ strlcpy(ntgroup, sid_string, sizeof(ntgroup));
+
+ if (!get_sid_from_input(&sid, ntgroup)) {
+ return -1;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return -1;
+ }
+
+ /* Get the current mapping from the database */
+ if(!pdb_getgrsid(map, sid)) {
+ d_fprintf(stderr,
+ _("Failure to find local group SID in the "
+ "database\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ print_map_entry(map, long_list );
+ TALLOC_FREE(map);
+ }
+ else {
+ GROUP_MAP **maps = NULL;
+ bool ok = false;
+ /* enumerate all group mappings */
+ ok = pdb_enum_group_mapping(NULL, SID_NAME_UNKNOWN,
+ &maps, &entries,
+ ENUM_ALL_MAPPED);
+ if (!ok) {
+ return -1;
+ }
+
+ for (i=0; i<entries; i++) {
+ print_map_entry(maps[i], long_list);
+ }
+
+ TALLOC_FREE(maps);
+ }
+
+ return 0;
+}
+
+/*********************************************************
+ Add a new group mapping entry
+**********************************************************/
+
+static int net_groupmap_add(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+ fstring ntgroup = "";
+ fstring unixgrp = "";
+ fstring string_sid = "";
+ fstring type = "";
+ fstring ntcomment = "";
+ enum lsa_SidType sid_type = SID_NAME_DOM_GRP;
+ uint32_t rid = 0;
+ gid_t gid;
+ int i;
+ GROUP_MAP *map;
+
+ const char *name_type;
+ const char add_usage_str[] = N_("net groupmap add "
+ "{rid=<int>|sid=<string>}"
+ " unixgroup=<string> "
+ "[type=<domain|local|builtin>] "
+ "[ntgroup=<string>] "
+ "[comment=<string>]");
+
+ name_type = "domain group";
+
+ if (c->display_usage) {
+ d_printf("%s\n%s\n", _("Usage:\n"), add_usage_str);
+ return 0;
+ }
+
+ /* get the options */
+ for ( i=0; i<argc; i++ ) {
+ if ( !strncasecmp_m(argv[i], "rid", strlen("rid")) ) {
+ rid = get_int_param(argv[i]);
+ if ( rid < DOMAIN_RID_ADMINS ) {
+ d_fprintf(stderr,
+ _("RID must be greater than %d\n"),
+ (uint32_t)DOMAIN_RID_ADMINS-1);
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "unixgroup", strlen("unixgroup")) ) {
+ fstrcpy( unixgrp, get_string_param( argv[i] ) );
+ if ( !unixgrp[0] ) {
+ d_fprintf(stderr,_( "must supply a name\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "ntgroup", strlen("ntgroup")) ) {
+ fstrcpy( ntgroup, get_string_param( argv[i] ) );
+ if ( !ntgroup[0] ) {
+ d_fprintf(stderr, _("must supply a name\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "sid", strlen("sid")) ) {
+ fstrcpy( string_sid, get_string_param( argv[i] ) );
+ if ( !string_sid[0] ) {
+ d_fprintf(stderr, _("must supply a SID\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "comment", strlen("comment")) ) {
+ fstrcpy( ntcomment, get_string_param( argv[i] ) );
+ if ( !ntcomment[0] ) {
+ d_fprintf(stderr,
+ _("must supply a comment string\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "type", strlen("type")) ) {
+ fstrcpy( type, get_string_param( argv[i] ) );
+ switch ( type[0] ) {
+ case 'b':
+ case 'B':
+ sid_type = SID_NAME_WKN_GRP;
+ name_type = "wellknown group";
+ break;
+ case 'd':
+ case 'D':
+ sid_type = SID_NAME_DOM_GRP;
+ name_type = "domain group";
+ break;
+ case 'l':
+ case 'L':
+ sid_type = SID_NAME_ALIAS;
+ name_type = "alias (local) group";
+ break;
+ default:
+ d_fprintf(stderr,
+ _("unknown group type %s\n"),
+ type);
+ return -1;
+ }
+ }
+ else {
+ d_fprintf(stderr, _("Bad option: %s\n"), argv[i]);
+ return -1;
+ }
+ }
+
+ if ( !unixgrp[0] ) {
+ d_printf("%s\n%s\n", _("Usage:\n"), add_usage_str);
+ return -1;
+ }
+
+ if ( (gid = nametogid(unixgrp)) == (gid_t)-1 ) {
+ d_fprintf(stderr, _("Can't lookup UNIX group %s\n"), unixgrp);
+ return -1;
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return -1;
+ }
+ /* Default is domain group. */
+ map->sid_name_use = SID_NAME_DOM_GRP;
+ if (pdb_getgrgid(map, gid)) {
+ struct dom_sid_buf buf;
+ d_printf(_("Unix group %s already mapped to SID %s\n"),
+ unixgrp, dom_sid_str_buf(&map->sid, &buf));
+ TALLOC_FREE(map);
+ return -1;
+ }
+ TALLOC_FREE(map);
+
+ if ( (rid == 0) && (string_sid[0] == '\0') ) {
+ d_printf(_("No rid or sid specified, choosing a RID\n"));
+ if (pdb_capabilities() & PDB_CAP_STORE_RIDS) {
+ if (!pdb_new_rid(&rid)) {
+ d_printf(_("Could not get new RID\n"));
+ }
+ } else {
+ rid = algorithmic_pdb_gid_to_group_rid(gid);
+ }
+ d_printf(_("Got RID %d\n"), rid);
+ }
+
+ /* append the rid to our own domain/machine SID if we don't have a full SID */
+ if ( !string_sid[0] ) {
+ sid_compose(&sid, get_global_sam_sid(), rid);
+ sid_to_fstring(string_sid, &sid);
+ }
+
+ if (!ntcomment[0]) {
+ switch (sid_type) {
+ case SID_NAME_WKN_GRP:
+ fstrcpy(ntcomment, "Wellknown Unix group");
+ break;
+ case SID_NAME_DOM_GRP:
+ fstrcpy(ntcomment, "Domain Unix group");
+ break;
+ case SID_NAME_ALIAS:
+ fstrcpy(ntcomment, "Local Unix group");
+ break;
+ default:
+ fstrcpy(ntcomment, "Unix group");
+ break;
+ }
+ }
+
+ if (!ntgroup[0] )
+ strlcpy(ntgroup, unixgrp, sizeof(ntgroup));
+
+ if (!NT_STATUS_IS_OK(add_initial_entry(gid, string_sid, sid_type, ntgroup, ntcomment))) {
+ d_fprintf(stderr, _("adding entry for group %s failed!\n"), ntgroup);
+ return -1;
+ }
+
+ d_printf(_("Successfully added group %s to the mapping db as a %s\n"),
+ ntgroup, name_type);
+ return 0;
+}
+
+static int net_groupmap_modify(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+ GROUP_MAP *map = NULL;
+ fstring ntcomment = "";
+ fstring type = "";
+ fstring ntgroup = "";
+ fstring unixgrp = "";
+ fstring sid_string = "";
+ enum lsa_SidType sid_type = SID_NAME_UNKNOWN;
+ int i;
+ gid_t gid;
+ const char modify_usage_str[] = N_("net groupmap modify "
+ "{ntgroup=<string>|sid=<SID>} "
+ "[comment=<string>] "
+ "[unixgroup=<string>] "
+ "[type=<domain|local>]");
+
+ if (c->display_usage) {
+ d_printf("%s\n%s\n", _("Usage:\n"), modify_usage_str);
+ return 0;
+ }
+
+ /* get the options */
+ for ( i=0; i<argc; i++ ) {
+ if ( !strncasecmp_m(argv[i], "ntgroup", strlen("ntgroup")) ) {
+ fstrcpy( ntgroup, get_string_param( argv[i] ) );
+ if ( !ntgroup[0] ) {
+ d_fprintf(stderr, _("must supply a name\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "sid", strlen("sid")) ) {
+ fstrcpy( sid_string, get_string_param( argv[i] ) );
+ if ( !sid_string[0] ) {
+ d_fprintf(stderr, _("must supply a name\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "comment", strlen("comment")) ) {
+ fstrcpy( ntcomment, get_string_param( argv[i] ) );
+ if ( !ntcomment[0] ) {
+ d_fprintf(stderr,
+ _("must supply a comment string\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "unixgroup", strlen("unixgroup")) ) {
+ fstrcpy( unixgrp, get_string_param( argv[i] ) );
+ if ( !unixgrp[0] ) {
+ d_fprintf(stderr,
+ _("must supply a group name\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "type", strlen("type")) ) {
+ fstrcpy( type, get_string_param( argv[i] ) );
+ switch ( type[0] ) {
+ case 'd':
+ case 'D':
+ sid_type = SID_NAME_DOM_GRP;
+ break;
+ case 'l':
+ case 'L':
+ sid_type = SID_NAME_ALIAS;
+ break;
+ }
+ }
+ else {
+ d_fprintf(stderr, _("Bad option: %s\n"), argv[i]);
+ return -1;
+ }
+ }
+
+ if ( !ntgroup[0] && !sid_string[0] ) {
+ d_printf("%s\n%s\n", _("Usage:\n"), modify_usage_str);
+ return -1;
+ }
+
+ /* give preference to the SID; if both the ntgroup name and SID
+ are defined, use the SID and assume that the group name could be a
+ new name */
+
+ if ( sid_string[0] ) {
+ if (!get_sid_from_input(&sid, sid_string)) {
+ return -1;
+ }
+ }
+ else {
+ if (!get_sid_from_input(&sid, ntgroup)) {
+ return -1;
+ }
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ return -1;
+ }
+
+ /* Get the current mapping from the database */
+ if(!pdb_getgrsid(map, sid)) {
+ d_fprintf(stderr,
+ _("Failed to find local group SID in the database\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ /*
+ * Allow changing of group type only between domain and local
+ * We disallow changing Builtin groups !!! (SID problem)
+ */
+ if (sid_type == SID_NAME_UNKNOWN) {
+ d_fprintf(stderr, _("Can't map to an unknown group type.\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ if (map->sid_name_use == SID_NAME_WKN_GRP) {
+ d_fprintf(stderr,
+ _("You can only change between domain and local "
+ "groups.\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ map->sid_name_use = sid_type;
+
+ /* Change comment if new one */
+ if (ntcomment[0]) {
+ map->comment = talloc_strdup(map, ntcomment);
+ if (!map->comment) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ return -1;
+ }
+ }
+
+ if (ntgroup[0]) {
+ map->nt_name = talloc_strdup(map, ntgroup);
+ if (!map->nt_name) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ return -1;
+ }
+ }
+
+ if ( unixgrp[0] ) {
+ gid = nametogid( unixgrp );
+ if ( gid == -1 ) {
+ d_fprintf(stderr, _("Unable to lookup UNIX group %s. "
+ "Make sure the group exists.\n"),
+ unixgrp);
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ map->gid = gid;
+ }
+
+ if (!NT_STATUS_IS_OK(pdb_update_group_mapping_entry(map))) {
+ d_fprintf(stderr, _("Could not update group database\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ d_printf(_("Updated mapping entry for %s\n"), map->nt_name);
+
+ TALLOC_FREE(map);
+ return 0;
+}
+
+static int net_groupmap_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+ fstring ntgroup = "";
+ fstring sid_string = "";
+ int i;
+ const char delete_usage_str[] = N_("net groupmap delete "
+ "{ntgroup=<string>|sid=<SID>}");
+
+ if (c->display_usage) {
+ d_printf("%s\n%s\n", _("Usage:\n"), delete_usage_str);
+ return 0;
+ }
+
+ /* get the options */
+ for ( i=0; i<argc; i++ ) {
+ if ( !strncasecmp_m(argv[i], "ntgroup", strlen("ntgroup")) ) {
+ fstrcpy( ntgroup, get_string_param( argv[i] ) );
+ if ( !ntgroup[0] ) {
+ d_fprintf(stderr, _("must supply a name\n"));
+ return -1;
+ }
+ }
+ else if ( !strncasecmp_m(argv[i], "sid", strlen("sid")) ) {
+ fstrcpy( sid_string, get_string_param( argv[i] ) );
+ if ( !sid_string[0] ) {
+ d_fprintf(stderr, _("must supply a SID\n"));
+ return -1;
+ }
+ }
+ else {
+ d_fprintf(stderr, _("Bad option: %s\n"), argv[i]);
+ return -1;
+ }
+ }
+
+ if ( !ntgroup[0] && !sid_string[0]) {
+ d_printf("%s\n%s\n", _("Usage:\n"), delete_usage_str);
+ return -1;
+ }
+
+ /* give preference to the SID if we have that */
+
+ if ( sid_string[0] )
+ strlcpy(ntgroup, sid_string, sizeof(ntgroup));
+
+ if ( !get_sid_from_input(&sid, ntgroup) ) {
+ d_fprintf(stderr, _("Unable to resolve group %s to a SID\n"),
+ ntgroup);
+ return -1;
+ }
+
+ if ( !NT_STATUS_IS_OK(pdb_delete_group_mapping_entry(sid)) ) {
+ d_fprintf(stderr,
+ _("Failed to remove group %s from the mapping db!\n"),
+ ntgroup);
+ return -1;
+ }
+
+ d_printf(_("Successfully removed %s from the mapping db\n"), ntgroup);
+
+ return 0;
+}
+
+static int net_groupmap_set(struct net_context *c, int argc, const char **argv)
+{
+ const char *ntgroup = NULL;
+ struct group *grp = NULL;
+ GROUP_MAP *map;
+ bool have_map = false;
+
+ if ((argc < 1) || (argc > 2) || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net groupmap set \"NT Group\" "
+ "[\"unix group\"] [-C \"comment\"] [-L] [-D]\n"));
+ return -1;
+ }
+
+ if ( c->opt_localgroup && c->opt_domaingroup ) {
+ d_printf(_("Can only specify -L or -D, not both\n"));
+ return -1;
+ }
+
+ ntgroup = argv[0];
+
+ if (argc == 2) {
+ grp = getgrnam(argv[1]);
+
+ if (grp == NULL) {
+ d_fprintf(stderr, _("Could not find unix group %s\n"),
+ argv[1]);
+ return -1;
+ }
+ }
+
+ map = talloc_zero(NULL, GROUP_MAP);
+ if (!map) {
+ d_printf(_("Out of memory!\n"));
+ return -1;
+ }
+
+ have_map = pdb_getgrnam(map, ntgroup);
+
+ if (!have_map) {
+ struct dom_sid sid;
+ have_map = ( (strncmp(ntgroup, "S-", 2) == 0) &&
+ string_to_sid(&sid, ntgroup) &&
+ pdb_getgrsid(map, sid) );
+ }
+
+ if (!have_map) {
+
+ /* Ok, add it */
+
+ if (grp == NULL) {
+ d_fprintf(stderr,
+ _("Could not find group mapping for %s\n"),
+ ntgroup);
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ map->gid = grp->gr_gid;
+
+ if (c->opt_rid == 0) {
+ if ( pdb_capabilities() & PDB_CAP_STORE_RIDS ) {
+ if ( !pdb_new_rid((uint32_t *)&c->opt_rid) ) {
+ d_fprintf( stderr,
+ _("Could not allocate new RID\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+ } else {
+ c->opt_rid = algorithmic_pdb_gid_to_group_rid(map->gid);
+ }
+ }
+
+ sid_compose(&map->sid, get_global_sam_sid(), c->opt_rid);
+
+ map->sid_name_use = SID_NAME_DOM_GRP;
+ map->nt_name = talloc_strdup(map, ntgroup);
+ map->comment = talloc_strdup(map, "");
+ if (!map->nt_name || !map->comment) {
+ d_printf(_("Out of memory!\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(pdb_add_group_mapping_entry(map))) {
+ d_fprintf(stderr,
+ _("Could not add mapping entry for %s\n"),
+ ntgroup);
+ TALLOC_FREE(map);
+ return -1;
+ }
+ }
+
+ /* Now we have a mapping entry, update that stuff */
+
+ if ( c->opt_localgroup || c->opt_domaingroup ) {
+ if (map->sid_name_use == SID_NAME_WKN_GRP) {
+ d_fprintf(stderr,
+ _("Can't change type of the BUILTIN "
+ "group %s\n"),
+ map->nt_name);
+ TALLOC_FREE(map);
+ return -1;
+ }
+ }
+
+ if (c->opt_localgroup)
+ map->sid_name_use = SID_NAME_ALIAS;
+
+ if (c->opt_domaingroup)
+ map->sid_name_use = SID_NAME_DOM_GRP;
+
+ /* The case (opt_domaingroup && opt_localgroup) was tested for above */
+
+ if ((c->opt_comment != NULL) && (strlen(c->opt_comment) > 0)) {
+ map->comment = talloc_strdup(map, c->opt_comment);
+ if (!map->comment) {
+ d_printf(_("Out of memory!\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+ }
+
+ if ((c->opt_newntname != NULL) && (strlen(c->opt_newntname) > 0)) {
+ map->nt_name = talloc_strdup(map, c->opt_newntname);
+ if (!map->nt_name) {
+ d_printf(_("Out of memory!\n"));
+ TALLOC_FREE(map);
+ return -1;
+ }
+ }
+
+ if (grp != NULL)
+ map->gid = grp->gr_gid;
+
+ if (!NT_STATUS_IS_OK(pdb_update_group_mapping_entry(map))) {
+ d_fprintf(stderr, _("Could not update group mapping for %s\n"),
+ ntgroup);
+ TALLOC_FREE(map);
+ return -1;
+ }
+
+ TALLOC_FREE(map);
+ return 0;
+}
+
+static int net_groupmap_cleanup(struct net_context *c, int argc, const char **argv)
+{
+ GROUP_MAP **maps = NULL;
+ size_t i, entries;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net groupmap cleanup\n"
+ " %s\n",
+ _("Usage:"),
+ _("Delete all group mappings"));
+ return 0;
+ }
+
+ if (!pdb_enum_group_mapping(NULL, SID_NAME_UNKNOWN, &maps, &entries,
+ ENUM_ALL_MAPPED)) {
+ d_fprintf(stderr, _("Could not list group mappings\n"));
+ return -1;
+ }
+
+ for (i=0; i<entries; i++) {
+
+ if (maps[i]->gid == -1)
+ printf(_("Group %s is not mapped\n"),
+ maps[i]->nt_name);
+
+ if (!sid_check_is_in_our_sam(&maps[i]->sid) &&
+ !sid_check_is_in_builtin(&maps[i]->sid))
+ {
+ struct dom_sid_buf buf;
+ printf(_("Deleting mapping for NT Group %s, sid %s\n"),
+ maps[i]->nt_name,
+ dom_sid_str_buf(&maps[i]->sid, &buf));
+ pdb_delete_group_mapping_entry(maps[i]->sid);
+ }
+ }
+
+ TALLOC_FREE(maps);
+ return 0;
+}
+
+static int net_groupmap_addmem(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid alias, member;
+
+ if ( (argc != 2) ||
+ c->display_usage ||
+ !string_to_sid(&alias, argv[0]) ||
+ !string_to_sid(&member, argv[1]) ) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net groupmap addmem alias-sid member-sid\n"));
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(pdb_add_aliasmem(&alias, &member))) {
+ d_fprintf(stderr, _("Could not add sid %s to alias %s\n"),
+ argv[1], argv[0]);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int net_groupmap_delmem(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid alias, member;
+
+ if ( (argc != 2) ||
+ c->display_usage ||
+ !string_to_sid(&alias, argv[0]) ||
+ !string_to_sid(&member, argv[1]) ) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net groupmap delmem alias-sid member-sid\n"));
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(pdb_del_aliasmem(&alias, &member))) {
+ d_fprintf(stderr, _("Could not delete sid %s from alias %s\n"),
+ argv[1], argv[0]);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int net_groupmap_listmem(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid alias;
+ struct dom_sid *members;
+ size_t i, num;
+
+ if ( (argc != 1) ||
+ c->display_usage ||
+ !string_to_sid(&alias, argv[0]) ) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net groupmap listmem alias-sid\n"));
+ return -1;
+ }
+
+ members = NULL;
+ num = 0;
+
+ if (!NT_STATUS_IS_OK(pdb_enum_aliasmem(&alias, talloc_tos(),
+ &members, &num))) {
+ d_fprintf(stderr, _("Could not list members for sid %s\n"),
+ argv[0]);
+ return -1;
+ }
+
+ for (i = 0; i < num; i++) {
+ struct dom_sid_buf buf;
+ printf("%s\n", dom_sid_str_buf(&(members[i]), &buf));
+ }
+
+ TALLOC_FREE(members);
+
+ return 0;
+}
+
+static bool print_alias_memberships(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *member)
+{
+ uint32_t *alias_rids;
+ size_t i, num_alias_rids;
+ struct dom_sid_buf buf;
+
+ alias_rids = NULL;
+ num_alias_rids = 0;
+
+ if (!NT_STATUS_IS_OK(pdb_enum_alias_memberships(
+ mem_ctx, domain_sid, member, 1,
+ &alias_rids, &num_alias_rids))) {
+ d_fprintf(stderr, _("Could not list memberships for sid %s\n"),
+ dom_sid_str_buf(member, &buf));
+ return false;
+ }
+
+ for (i = 0; i < num_alias_rids; i++) {
+ struct dom_sid alias;
+ sid_compose(&alias, domain_sid, alias_rids[i]);
+ printf("%s\n", dom_sid_str_buf(&alias, &buf));
+ }
+
+ return true;
+}
+
+static int net_groupmap_memberships(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ struct dom_sid *domain_sid, member;
+
+ if ( (argc != 1) ||
+ c->display_usage ||
+ !string_to_sid(&member, argv[0]) ) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net groupmap memberships sid\n"));
+ return -1;
+ }
+
+ mem_ctx = talloc_init("net_groupmap_memberships");
+ if (mem_ctx == NULL) {
+ d_fprintf(stderr, _("talloc_init failed\n"));
+ return -1;
+ }
+
+ domain_sid = get_global_sam_sid();
+ if (domain_sid == NULL) {
+ d_fprintf(stderr, _("Could not get domain sid\n"));
+ return -1;
+ }
+
+ if (!print_alias_memberships(mem_ctx, domain_sid, &member) ||
+ !print_alias_memberships(mem_ctx, &global_sid_Builtin, &member))
+ return -1;
+
+ talloc_destroy(mem_ctx);
+
+ return 0;
+}
+
+/***********************************************************
+ migrated functionality from smbgroupedit
+ **********************************************************/
+int net_groupmap(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ net_groupmap_add,
+ NET_TRANSPORT_LOCAL,
+ N_("Create a new group mapping"),
+ N_("net groupmap add\n"
+ " Create a new group mapping")
+ },
+ {
+ "modify",
+ net_groupmap_modify,
+ NET_TRANSPORT_LOCAL,
+ N_("Update a group mapping"),
+ N_("net groupmap modify\n"
+ " Modify an existing group mapping")
+ },
+ {
+ "delete",
+ net_groupmap_delete,
+ NET_TRANSPORT_LOCAL,
+ N_("Remove a group mapping"),
+ N_("net groupmap delete\n"
+ " Remove a group mapping")
+ },
+ {
+ "set",
+ net_groupmap_set,
+ NET_TRANSPORT_LOCAL,
+ N_("Set group mapping"),
+ N_("net groupmap set\n"
+ " Set a group mapping")
+ },
+ {
+ "cleanup",
+ net_groupmap_cleanup,
+ NET_TRANSPORT_LOCAL,
+ N_("Remove foreign group mapping entries"),
+ N_("net groupmap cleanup\n"
+ " Remove foreign group mapping entries")
+ },
+ {
+ "addmem",
+ net_groupmap_addmem,
+ NET_TRANSPORT_LOCAL,
+ N_("Add a foreign alias member"),
+ N_("net groupmap addmem\n"
+ " Add a foreign alias member")
+ },
+ {
+ "delmem",
+ net_groupmap_delmem,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete foreign alias member"),
+ N_("net groupmap delmem\n"
+ " Delete foreign alias member")
+ },
+ {
+ "listmem",
+ net_groupmap_listmem,
+ NET_TRANSPORT_LOCAL,
+ N_("List foreign group members"),
+ N_("net groupmap listmem\n"
+ " List foreign alias members")
+ },
+ {
+ "memberships",
+ net_groupmap_memberships,
+ NET_TRANSPORT_LOCAL,
+ N_("List foreign group memberships"),
+ N_("net groupmap memberships\n"
+ " List foreign group memberships")
+ },
+ {
+ "list",
+ net_groupmap_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List current group map"),
+ N_("net groupmap list\n"
+ " List current group map")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c,argc, argv, "net groupmap", func);
+}
+
diff --git a/source3/utils/net_help.c b/source3/utils/net_help.c
new file mode 100644
index 0000000..4aba1c5
--- /dev/null
+++ b/source3/utils/net_help.c
@@ -0,0 +1,69 @@
+/*
+ Samba Unix/Linux SMB client library
+ net help commands
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+
+static int net_usage(struct net_context *c, int argc, const char **argv);
+
+static int net_help_usage(struct net_context *c, int argc, const char **argv)
+{
+ c->display_usage = true;
+ return net_usage(c, argc, argv);
+}
+
+static int net_usage(struct net_context *c, int argc, const char **argv)
+{
+ struct functable *table = (struct functable*) c->private_data;
+ int i;
+
+ d_printf(_("Usage:\n"));
+ for (i=0; table[i].funcname != NULL; i++) {
+ if (c->display_usage) {
+ d_printf(_("net %s usage:\n"), table[i].funcname);
+ d_printf("\n%s\n\n", _(table[i].usage));
+ } else {
+ d_printf("%s %-15s %s\n", "net", table[i].funcname,
+ _(table[i].description));
+ }
+
+ }
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+/*
+ handle "net help *" subcommands
+*/
+int net_help(struct net_context *c, int argc, const char **argv)
+{
+ struct functable *func = (struct functable *)c->private_data;
+
+ if (argc == 0) {
+ return net_usage(c, argc, argv);
+ }
+
+ if (strcasecmp_m(argv[0], "help") == 0) {
+ return net_help_usage(c, argc, argv);
+ }
+
+ c->display_usage = true;
+ return net_run_function(c, argc, argv, "net help", func);
+}
diff --git a/source3/utils/net_help_common.c b/source3/utils/net_help_common.c
new file mode 100644
index 0000000..a861d3f
--- /dev/null
+++ b/source3/utils/net_help_common.c
@@ -0,0 +1,95 @@
+/*
+ Samba Unix/Linux SMB client library
+ net help commands
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+
+int net_common_methods_usage(struct net_context *c, int argc, const char**argv)
+{
+ d_printf(_("Valid methods: (auto-detected if not specified)\n"));
+ d_printf(_("\tads\t\t\t\tActive Directory (LDAP/Kerberos)\n"));
+ d_printf(_("\trpc\t\t\t\tDCE-RPC\n"));
+ d_printf(_("\trap\t\t\t\tRAP (older systems)\n"));
+ d_printf("\n");
+ return 0;
+}
+
+int net_common_flags_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("Valid targets: choose one (none defaults to localhost)\n"));
+ d_printf(_("\t-S|--server=<server>\t\t\tserver name\n"));
+ d_printf(_("\t-I|--ipaddress=<ipaddr>\t\t\taddress of target server\n"));
+ d_printf(_("\t-w|--target-workgroup=<wg>\t\ttarget workgroup or domain\n"));
+
+ d_printf("\n");
+ d_printf(_("Valid misc options are:\n")); /* misc options */
+ d_printf(_("\t-p|--port=<port>\t\t\tconnection port on target\n"));
+ d_printf(_("\t--myname=<name>\t\t\t\tclient name\n"));
+ d_printf(_("\t--long\t\t\t\t\tDisplay full information\n"));
+
+ d_printf("\n");
+ d_printf(_("Valid common options are:\n")); /* misc options */
+ d_printf(_("\t-d|--debuglevel=<level>\t\t\tdebug level (0-10)\n"));
+ d_printf(_("\t--debug-stdout\t\t\t\tSend debug output to standard "
+ "output\n"));
+ d_printf(_("\t--configfile=<path>\t\t\tpathname of smb.conf file\n"));
+ d_printf(_("\t--option=name=value\t\t\tSet smb.conf option from "
+ "command line\n"));
+ d_printf(_("\t-l|--log-basename=LOGFILEBASE\t\tBasename for "
+ "log/debug files\n"));
+ d_printf(_("\t--leak-report\t\t\t\tenable talloc leak reporting on "
+ "exit\n"));
+ d_printf(_("\t--leak-report-full\t\t\tenable full talloc leak "
+ "reporting on exit\n"));
+ d_printf(_("\t-V|--version\t\t\t\tPrint samba version information\n"));
+
+ d_printf("\n");
+ d_printf(_("Valid connection options are:\n")); /* misc options */
+ d_printf(_("\t-R|--name-resolve=NAME-RESOLVE-ORDER\tUse these name "
+ "resolution services only\n"));
+ d_printf(_("\t-O|--socket-options=SOCKETOPTIONS\tsocket options to use\n"));
+ d_printf(_("\t-m|--max-protocol=MAXPROTOCOL\t\tSet max protocol level\n"));
+ d_printf(_("\t-n|--netbiosname=NETBIOSNAME\t\tPrimary netbios name\n"));
+ d_printf(_("\t--netbios-scope=SCOPE\t\t\tUse this Netbios scope\n"));
+ d_printf(_("\t-W|--workgroup=WORKGROUP\t\tSet the workgroup name\n"));
+ d_printf(_("\t--realm=REALM\t\t\t\tSet the realm name\n"));
+
+ d_printf("\n");
+ d_printf(_("Valid credential options are:\n")); /* misc options */
+ d_printf(_("\t-U|--user=[DOMAIN/]USERNAME[%%PASSWORD]\tSet the "
+ "network username\n"));
+ d_printf(_("\t-N|--no-pass\t\t\t\tDon't ask for a password\n"));
+ d_printf(_("\t--password=STRING\t\t\tSet a password\n"));
+ d_printf(_("\t--pw-nt-hash\t\t\t\tThe supplied password is the NT hash\n"));
+ d_printf(_("\t-A|--authentication-file=FILE\t\tGet the "
+ "credentials from a file\n"));
+ d_printf(_("\t-P|--machine-pass\t\t\tUse stored machine account password\n"));
+ d_printf(_("\t--simple-bind-dn=DN\t\t\tDN to use for a simple bind\n"));
+ d_printf(_("\t--use-kerberos=desired|required|off\tUse kerberos "
+ "authentication\n"));
+ d_printf(_("\t--use-krb5-ccache=CCACHE\t\tCredentials cache location "
+ "for Kerberos\n"));
+ d_printf(_("\t--use-winbind-ccache\t\t\tUse the winbind ccache for "
+ "authentication\n"));
+ d_printf(_("\t--client-protection=sign|encrypt|off\tConfigure used "
+ "protection for client connections\n"));
+
+ return -1;
+}
+
diff --git a/source3/utils/net_help_common.h b/source3/utils/net_help_common.h
new file mode 100644
index 0000000..ed85993
--- /dev/null
+++ b/source3/utils/net_help_common.h
@@ -0,0 +1,49 @@
+/*
+ Samba Unix/Linux SMB client library
+ net help commands
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _NET_HELP_COMMON_H_
+#define _NET_HELP_COMMON_H_
+
+/**
+ * Get help for common methods.
+ *
+ * This will output some help for using the ADS/RPC/RAP transports.
+ *
+ * @param c A net_context structure
+ * @param argc Normal argc with previous parameters removed
+ * @param argv Normal argv with previous parameters removed
+ * @return 0 on success, nonzero on failure.
+ */
+int net_common_methods_usage(struct net_context *c, int argc, const char**argv);
+
+/**
+ * Get help for common flags.
+ *
+ * This will output some help for using common flags.
+ *
+ * @param c A net_context structure
+ * @param argc Normal argc with previous parameters removed
+ * @param argv Normal argv with previous parameters removed
+ * @return 0 on success, nonzero on failure.
+ */
+int net_common_flags_usage(struct net_context *c, int argc, const char **argv);
+
+
+#endif /* _NET_HELP_COMMON_H_*/
+
diff --git a/source3/utils/net_idmap.c b/source3/utils/net_idmap.c
new file mode 100644
index 0000000..027b741
--- /dev/null
+++ b/source3/utils/net_idmap.c
@@ -0,0 +1,1413 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2003 Andrew Bartlett (abartlet@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "utils/net.h"
+#include "secrets.h"
+#include "idmap.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "net_idmap_check.h"
+#include "util_tdb.h"
+#include "idmap_autorid_tdb.h"
+#include "lib/util/smb_strtox.h"
+
+#define ALLOC_CHECK(mem) do { \
+ if (!mem) { \
+ d_fprintf(stderr, _("Out of memory!\n")); \
+ talloc_free(ctx); \
+ return -1; \
+ } } while(0)
+
+enum idmap_dump_backend {
+ TDB,
+ AUTORID
+};
+
+struct net_idmap_ctx {
+ enum idmap_dump_backend backend;
+};
+
+static int net_idmap_dump_one_autorid_entry(struct db_record *rec,
+ void *unused)
+{
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+ value = dbwrap_record_get_value(rec);
+
+ if (strncmp((char *)key.dptr, "CONFIG", 6) == 0) {
+ char *config = talloc_array(talloc_tos(), char, value.dsize+1);
+ memcpy(config, value.dptr, value.dsize);
+ config[value.dsize] = '\0';
+ printf("CONFIG: %s\n", config);
+ talloc_free(config);
+ return 0;
+ }
+
+ if (strncmp((char *)key.dptr, "NEXT RANGE", 10) == 0) {
+ printf("RANGE HWM: %"PRIu32"\n", IVAL(value.dptr, 0));
+ return 0;
+ }
+
+ if (strncmp((char *)key.dptr, "NEXT ALLOC UID", 14) == 0) {
+ printf("UID HWM: %"PRIu32"\n", IVAL(value.dptr, 0));
+ return 0;
+ }
+
+ if (strncmp((char *)key.dptr, "NEXT ALLOC GID", 14) == 0) {
+ printf("GID HWM: %"PRIu32"\n", IVAL(value.dptr, 0));
+ return 0;
+ }
+
+ if (strncmp((char *)key.dptr, "UID", 3) == 0 ||
+ strncmp((char *)key.dptr, "GID", 3) == 0)
+ {
+ /* mapped entry from allocation pool */
+ printf("%s %s\n", value.dptr, key.dptr);
+ return 0;
+ }
+
+ if ((strncmp((char *)key.dptr, "S-1-5-", 6) == 0 ||
+ strncmp((char *)key.dptr, "ALLOC", 5) == 0) &&
+ value.dsize == sizeof(uint32_t))
+ {
+ /* this is a domain range assignment */
+ uint32_t range = IVAL(value.dptr, 0);
+ printf("RANGE %"PRIu32": %s\n", range, key.dptr);
+ return 0;
+ }
+
+ return 0;
+}
+
+/***********************************************************
+ Helper function for net_idmap_dump. Dump one entry.
+ **********************************************************/
+static int net_idmap_dump_one_tdb_entry(struct db_record *rec,
+ void *unused)
+{
+ TDB_DATA key;
+ TDB_DATA value;
+
+ key = dbwrap_record_get_key(rec);
+ value = dbwrap_record_get_value(rec);
+
+ if (strcmp((char *)key.dptr, "USER HWM") == 0) {
+ printf(_("USER HWM %d\n"), IVAL(value.dptr,0));
+ return 0;
+ }
+
+ if (strcmp((char *)key.dptr, "GROUP HWM") == 0) {
+ printf(_("GROUP HWM %d\n"), IVAL(value.dptr,0));
+ return 0;
+ }
+
+ if (strncmp((char *)key.dptr, "S-", 2) != 0) {
+ return 0;
+ }
+
+ printf("%s %s\n", value.dptr, key.dptr);
+ return 0;
+}
+
+/* returns db path for idmap backend alloced on talloc_tos */
+static char *net_idmap_dbfile(struct net_context *c,
+ struct net_idmap_ctx *ctx)
+{
+ char *dbfile = NULL;
+ const char *backend = NULL;
+
+ backend = lp_idmap_default_backend();
+ if (!backend) {
+ d_printf(_("Internal error: 'idmap config * : backend' is not set!\n"));
+ return NULL;
+ }
+
+ if (c->opt_db != NULL) {
+ dbfile = talloc_strdup(talloc_tos(), c->opt_db);
+ if (dbfile == NULL) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ }
+ } else if (strequal(backend, "tdb")) {
+ dbfile = state_path(talloc_tos(), "winbindd_idmap.tdb");
+ if (dbfile == NULL) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ }
+ ctx->backend = TDB;
+ } else if (strequal(backend, "tdb2")) {
+ dbfile = talloc_asprintf(talloc_tos(), "%s/idmap2.tdb",
+ lp_private_dir());
+ if (dbfile == NULL) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ }
+ ctx->backend = TDB;
+ } else if (strequal(backend, "autorid")) {
+ dbfile = state_path(talloc_tos(), "autorid.tdb");
+ if (dbfile == NULL) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ }
+ ctx->backend = AUTORID;
+ } else {
+ char *_backend = talloc_strdup(talloc_tos(), backend);
+ char* args = strchr(_backend, ':');
+ if (args != NULL) {
+ *args = '\0';
+ }
+
+ d_printf(_("Sorry, 'idmap backend = %s' is currently not supported\n"),
+ _backend);
+
+ talloc_free(_backend);
+ }
+
+ return dbfile;
+}
+
+static bool net_idmap_opendb_autorid(TALLOC_CTX *mem_ctx,
+ struct net_context *c,
+ bool readonly,
+ struct db_context **db)
+{
+ bool ret = false;
+ char *dbfile = NULL;
+ struct net_idmap_ctx ctx = { .backend = AUTORID };
+
+ if (c == NULL) {
+ goto done;
+ }
+
+ dbfile = net_idmap_dbfile(c, &ctx);
+ if (dbfile == NULL) {
+ goto done;
+ }
+
+ if (ctx.backend != AUTORID) {
+ d_fprintf(stderr, _("Unsupported backend\n"));
+ goto done;
+ }
+
+ if (readonly) {
+ *db = db_open(mem_ctx, dbfile, 0, TDB_DEFAULT, O_RDONLY, 0,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (*db == NULL) {
+ d_fprintf(stderr,
+ _("Could not open autorid db (%s): %s\n"),
+ dbfile, strerror(errno));
+ goto done;
+ }
+ } else {
+ NTSTATUS status;
+ status = idmap_autorid_db_init(dbfile, mem_ctx, db);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Error calling idmap_autorid_db_init: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ }
+
+ ret = true;
+
+done:
+ talloc_free(dbfile);
+ return ret;
+}
+
+
+/***********************************************************
+ Dump the current idmap
+ **********************************************************/
+static int net_idmap_dump(struct net_context *c, int argc, const char **argv)
+{
+ struct db_context *db;
+ TALLOC_CTX *mem_ctx;
+ const char* dbfile;
+ NTSTATUS status;
+ int ret = -1;
+ struct net_idmap_ctx ctx = { .backend = TDB };
+
+ if ( argc > 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap dump [[--db=]<inputfile>]\n"
+ " Dump current ID mapping.\n"
+ " inputfile\tTDB file to read mappings from.\n"));
+ return c->display_usage?0:-1;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ dbfile = (argc > 0) ? argv[0] : net_idmap_dbfile(c, &ctx);
+ if (dbfile == NULL) {
+ goto done;
+ }
+ d_fprintf(stderr, _("dumping id mapping from %s\n"), dbfile);
+
+ db = db_open(mem_ctx, dbfile, 0, TDB_DEFAULT, O_RDONLY, 0,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ d_fprintf(stderr, _("Could not open idmap db (%s): %s\n"),
+ dbfile, strerror(errno));
+ goto done;
+ }
+
+ if (ctx.backend == AUTORID) {
+ status = dbwrap_traverse_read(db,
+ net_idmap_dump_one_autorid_entry,
+ NULL, NULL);
+ } else {
+ status = dbwrap_traverse_read(db,
+ net_idmap_dump_one_tdb_entry,
+ NULL, NULL);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("error traversing the database\n"));
+ ret = -1;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/***********************************************************
+ Write entries from stdin to current local idmap
+ **********************************************************/
+
+static int net_idmap_store_id_mapping(struct db_context *db,
+ enum id_type type,
+ unsigned long idval,
+ const char *sid_string)
+{
+ NTSTATUS status;
+ char *idstr = NULL;
+
+ switch(type) {
+ case ID_TYPE_UID:
+ idstr = talloc_asprintf(talloc_tos(), "UID %lu", idval);
+ break;
+ case ID_TYPE_GID:
+ idstr = talloc_asprintf(talloc_tos(), "GID %lu", idval);
+ break;
+ default:
+ d_fprintf(stderr, "Invalid id mapping type: %d\n", type);
+ return -1;
+ }
+
+ status = dbwrap_store_bystring(db, idstr,
+ string_term_tdb_data(sid_string),
+ TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "Error storing ID -> SID: "
+ "%s\n", nt_errstr(status));
+ talloc_free(idstr);
+ return -1;
+ }
+ status = dbwrap_store_bystring(db, sid_string,
+ string_term_tdb_data(idstr),
+ TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "Error storing SID -> ID: "
+ "%s\n", nt_errstr(status));
+ talloc_free(idstr);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int net_idmap_restore(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ FILE *input = NULL;
+ struct db_context *db;
+ const char *dbfile = NULL;
+ int ret = 0;
+ struct net_idmap_ctx ctx = { .backend = TDB };
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap restore [--db=<TDB>] [<inputfile>]\n"
+ " Restore ID mappings from file\n"
+ " TDB\tFile to store ID mappings to."
+ " inputfile\tFile to load ID mappings from. If not "
+ "given, load data from stdin.\n"));
+ return 0;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ dbfile = net_idmap_dbfile(c, &ctx);
+
+ if (dbfile == NULL) {
+ ret = -1;
+ goto done;
+ }
+
+ if (ctx.backend != TDB) {
+ d_fprintf(stderr, _("Sorry, restoring of non-TDB databases is "
+ "currently not supported\n"));
+ ret = -1;
+ goto done;
+ }
+
+ d_fprintf(stderr, _("restoring id mapping to %s\n"), dbfile);
+
+ if (argc == 1) {
+ input = fopen(argv[0], "r");
+ if (input == NULL) {
+ d_fprintf(stderr, _("Could not open input file (%s): %s\n"),
+ argv[0], strerror(errno));
+ ret = -1;
+ goto done;
+ }
+ } else {
+ input = stdin;
+ }
+
+ db = db_open(mem_ctx, dbfile, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0644,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ d_fprintf(stderr, _("Could not open idmap db (%s): %s\n"),
+ dbfile, strerror(errno));
+ ret = -1;
+ goto done;
+ }
+
+ if (dbwrap_transaction_start(db) != 0) {
+ d_fprintf(stderr, _("Failed to start transaction.\n"));
+ ret = -1;
+ goto done;
+ }
+
+ while (!feof(input)) {
+ char line[128], sid_string[128];
+ int len;
+ unsigned long idval;
+ NTSTATUS status;
+
+ if (fgets(line, 127, input) == NULL)
+ break;
+
+ len = strlen(line);
+
+ if ( (len > 0) && (line[len-1] == '\n') )
+ line[len-1] = '\0';
+
+ if (sscanf(line, "GID %lu %127s", &idval, sid_string) == 2)
+ {
+ ret = net_idmap_store_id_mapping(db, ID_TYPE_GID,
+ idval, sid_string);
+ if (ret != 0) {
+ break;
+ }
+ } else if (sscanf(line, "UID %lu %127s", &idval, sid_string) == 2)
+ {
+ ret = net_idmap_store_id_mapping(db, ID_TYPE_UID,
+ idval, sid_string);
+ if (ret != 0) {
+ break;
+ }
+ } else if (sscanf(line, "USER HWM %lu", &idval) == 1) {
+ status = dbwrap_store_int32_bystring(
+ db, "USER HWM", idval);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Could not store USER HWM: %s\n"),
+ nt_errstr(status));
+ break;
+ }
+ } else if (sscanf(line, "GROUP HWM %lu", &idval) == 1) {
+ status = dbwrap_store_int32_bystring(
+ db, "GROUP HWM", idval);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Could not store GROUP HWM: %s\n"),
+ nt_errstr(status));
+ break;
+ }
+ } else {
+ d_fprintf(stderr, _("ignoring invalid line [%s]\n"),
+ line);
+ continue;
+ }
+ }
+
+ if (ret == 0) {
+ if(dbwrap_transaction_commit(db) != 0) {
+ d_fprintf(stderr, _("Failed to commit transaction.\n"));
+ ret = -1;
+ }
+ } else {
+ if (dbwrap_transaction_cancel(db) != 0) {
+ d_fprintf(stderr, _("Failed to cancel transaction.\n"));
+ }
+ }
+
+done:
+ if ((input != NULL) && (input != stdin)) {
+ fclose(input);
+ }
+
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static
+NTSTATUS dbwrap_delete_mapping(struct db_context *db, TDB_DATA key1, bool force)
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ bool is_valid_mapping;
+ NTSTATUS status = NT_STATUS_OK;
+ TDB_DATA val1, val2;
+
+ ZERO_STRUCT(val1);
+ ZERO_STRUCT(val2);
+
+ status = dbwrap_fetch(db, mem_ctx, key1, &val1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to fetch: %.*s\n", (int)key1.dsize, key1.dptr));
+ goto done;
+ }
+
+ if (val1.dptr == NULL) {
+ DEBUG(1, ("invalid mapping: %.*s -> empty value\n",
+ (int)key1.dsize, key1.dptr));
+ status = NT_STATUS_FILE_INVALID;
+ goto done;
+ }
+
+ DEBUG(2, ("mapping: %.*s -> %.*s\n",
+ (int)key1.dsize, key1.dptr, (int)val1.dsize, val1.dptr));
+
+ status = dbwrap_fetch(db, mem_ctx, val1, &val2);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to fetch: %.*s\n", (int)val1.dsize, val1.dptr));
+ goto done;
+ }
+
+ is_valid_mapping = tdb_data_equal(key1, val2);
+
+ if (!is_valid_mapping) {
+ DEBUG(1, ("invalid mapping: %.*s -> %.*s -> %.*s\n",
+ (int)key1.dsize, key1.dptr,
+ (int)val1.dsize, val1.dptr,
+ (int)val2.dsize, val2.dptr));
+ if ( !force ) {
+ status = NT_STATUS_FILE_INVALID;
+ goto done;
+ }
+ }
+
+ status = dbwrap_delete(db, key1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to delete: %.*s\n", (int)key1.dsize, key1.dptr));
+ goto done;
+ }
+
+ if (!is_valid_mapping) {
+ goto done;
+ }
+
+ status = dbwrap_delete(db, val1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("failed to delete: %.*s\n", (int)val1.dsize, val1.dptr));
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return status;
+}
+
+static
+NTSTATUS delete_mapping_action(struct db_context *db, void* data)
+{
+ return dbwrap_delete_mapping(db, *(TDB_DATA*)data, false);
+}
+static
+NTSTATUS delete_mapping_action_force(struct db_context *db, void* data)
+{
+ return dbwrap_delete_mapping(db, *(TDB_DATA*)data, true);
+}
+
+/***********************************************************
+ Delete a SID mapping from a winbindd_idmap.tdb
+ **********************************************************/
+static bool delete_args_ok(int argc, const char **argv)
+{
+ if (argc != 1)
+ return false;
+ if (strncmp(argv[0], "S-", 2) == 0)
+ return true;
+ if (strncmp(argv[0], "GID ", 4) == 0)
+ return true;
+ if (strncmp(argv[0], "UID ", 4) == 0)
+ return true;
+ return false;
+}
+
+static int net_idmap_delete_mapping(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ struct db_context *db;
+ TALLOC_CTX *mem_ctx;
+ TDB_DATA key;
+ NTSTATUS status;
+ const char* dbfile;
+ struct net_idmap_ctx ctx = { .backend = TDB };
+
+ if ( !delete_args_ok(argc,argv) || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap delete mapping [-f] [--db=<TDB>] <ID>\n"
+ " Delete mapping of ID from TDB.\n"
+ " -f\tforce\n"
+ " TDB\tidmap database\n"
+ " ID\tSID|GID|UID\n"));
+ return c->display_usage ? 0 : -1;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ dbfile = net_idmap_dbfile(c, &ctx);
+ if (dbfile == NULL) {
+ goto done;
+ }
+ d_fprintf(stderr, _("deleting id mapping from %s\n"), dbfile);
+
+ db = db_open(mem_ctx, dbfile, 0, TDB_DEFAULT, O_RDWR, 0,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (db == NULL) {
+ d_fprintf(stderr, _("Could not open idmap db (%s): %s\n"),
+ dbfile, strerror(errno));
+ goto done;
+ }
+
+ key = string_term_tdb_data(argv[0]);
+
+ status = dbwrap_trans_do(db, (c->opt_force
+ ? delete_mapping_action_force
+ : delete_mapping_action), &key);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("could not delete mapping: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ ret = 0;
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static bool parse_uint32(const char *str, uint32_t *result)
+{
+ unsigned long val;
+ int error = 0;
+
+ val = smb_strtoul(str, NULL, 10, &error, SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ return false;
+ }
+
+ *result = val; /* Potential crop */
+ return true;
+}
+
+static void net_idmap_autorid_delete_range_usage(void)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap delete range [-f] [--db=<TDB>] <RANGE>|(<SID>[ <INDEX>])\n"
+ " Delete a domain range mapping from the database.\n"
+ " -f\tforce\n"
+ " TDB\tidmap database\n"
+ " RANGE\tthe range number to delete\n"
+ " SID\t\tSID of the domain\n"
+ " INDEX\trange index number do delete for the domain\n"));
+}
+
+static int net_idmap_autorid_delete_range(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ struct db_context *db = NULL;
+ NTSTATUS status;
+ uint32_t rangenum;
+ uint32_t range_index;
+ const char *domsid;
+ TALLOC_CTX *mem_ctx = NULL;
+ bool ok;
+ bool force = (c->opt_force != 0);
+
+ if (c->display_usage) {
+ net_idmap_autorid_delete_range_usage();
+ return 0;
+ }
+
+ if (argc < 1 || argc > 2) {
+ net_idmap_autorid_delete_range_usage();
+ return -1;
+ }
+
+ mem_ctx = talloc_stackframe();
+ if (!net_idmap_opendb_autorid(mem_ctx, c, false, &db)) {
+ goto done;
+ }
+
+ ok = parse_uint32(argv[0], &rangenum);
+ if (ok) {
+ d_printf("%s: %"PRIu32"\n", _("Deleting range number"),
+ rangenum);
+
+ status = idmap_autorid_delete_range_by_num(db, rangenum,
+ force);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "%s: %s\n",
+ _("Failed to delete domain range mapping"),
+ nt_errstr(status));
+ } else {
+ ret = 0;
+ }
+
+ goto done;
+ }
+
+ domsid = argv[0];
+ range_index = 0;
+
+ if (argc == 2) {
+ ok = parse_uint32(argv[1], &range_index);
+ if (!ok) {
+ d_printf("%s: %s\n",
+ _("Invalid index specification"), argv[1]);
+ net_idmap_autorid_delete_range_usage();
+ goto done;
+ }
+ }
+
+ status = idmap_autorid_delete_range_by_sid(db, domsid, range_index,
+ force);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "%s: %s\n",
+ _("Failed to delete domain range mapping"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static void net_idmap_autorid_delete_ranges_usage(void)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap delete ranges [-f] [--db=<TDB>] <SID>\n"
+ " Delete all domain range mappings for a given domain.\n"
+ " -f\tforce\n"
+ " TDB\tidmap database\n"
+ " SID\t\tSID of the domain\n"));
+}
+
+static int net_idmap_autorid_delete_ranges(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ struct db_context *db = NULL;
+ NTSTATUS status;
+ const char *domsid;
+ TALLOC_CTX *mem_ctx = NULL;
+ bool force = (c->opt_force != 0);
+ int count = 0;
+
+ if (c->display_usage) {
+ net_idmap_autorid_delete_ranges_usage();
+ return 0;
+ }
+
+ if (argc != 1) {
+ net_idmap_autorid_delete_ranges_usage();
+ return -1;
+ }
+
+ domsid = argv[0];
+
+ mem_ctx = talloc_stackframe();
+ if (!net_idmap_opendb_autorid(mem_ctx, c, false, &db)) {
+ goto done;
+ }
+
+ status = idmap_autorid_delete_domain_ranges(db, domsid, force, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "%s %s: %s\n",
+ _("Failed to delete domain range mappings for "
+ "domain"),
+ domsid,
+ nt_errstr(status));
+ goto done;
+ }
+
+ d_printf(_("deleted %d domain mappings\n"), count);
+
+ ret = 0;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static int net_idmap_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "mapping",
+ net_idmap_delete_mapping,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete ID mapping"),
+ N_("net idmap delete mapping <ID>\n"
+ " Delete ID mapping")
+ },
+ {
+ "range",
+ net_idmap_autorid_delete_range,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a domain range mapping"),
+ N_("net idmap delete range <RANGE>|(<SID>[ <INDEX>])\n"
+ " Delete a domain range mapping")
+ },
+ {
+ "ranges",
+ net_idmap_autorid_delete_ranges,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete all domain range mappings for a given "
+ "domain"),
+ N_("net idmap delete ranges <SID>\n"
+ " Delete a domain range mapping")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net idmap delete", func);
+}
+
+
+static int net_idmap_set_mapping(struct net_context *c,
+ int argc, const char **argv)
+{
+ d_printf("%s\n", _("Not implemented yet"));
+ return -1;
+}
+
+static void net_idmap_autorid_set_range_usage(void)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap set range"
+ " <range> <SID> [<index>] [--db=<inputfile>]\n"
+ " Store a domain-range mapping for a given domain.\n"
+ " range\tRange number to be set for the domain\n"
+ " SID\t\tSID of the domain\n"
+ " index\trange-index number to be set for the domain\n"
+ " inputfile\tTDB file to add mapping to.\n"));
+}
+
+static int net_idmap_autorid_set_range(struct net_context *c,
+ int argc, const char **argv)
+{
+ int ret = -1;
+ TALLOC_CTX *mem_ctx;
+ struct db_context *db = NULL;
+ const char *domsid;
+ uint32_t rangenum;
+ uint32_t range_index = 0;
+ NTSTATUS status;
+ bool ok;
+
+ if (c->display_usage) {
+ net_idmap_autorid_set_range_usage();
+ return 0;
+ }
+
+ if (argc < 2 || argc > 3) {
+ net_idmap_autorid_set_range_usage();
+ return -1;
+ }
+
+ ok = parse_uint32(argv[0], &rangenum);
+ if (!ok) {
+ d_printf("%s: %s\n", _("Invalid range specification"),
+ argv[0]);
+ net_idmap_autorid_set_range_usage();
+ return -1;
+ }
+
+ domsid = argv[1];
+
+ if (argc == 3) {
+ ok = parse_uint32(argv[2], &range_index);
+ if (!ok) {
+ d_printf("%s: %s\n",
+ _("Invalid index specification"), argv[2]);
+ net_idmap_autorid_set_range_usage();
+ return -1;
+ }
+ }
+
+ mem_ctx = talloc_stackframe();
+ if (!net_idmap_opendb_autorid(mem_ctx, c, false, &db)) {
+ goto done;
+ }
+
+ status = idmap_autorid_setrange(db, domsid, range_index, rangenum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "%s: %s\n",
+ _("Failed to save domain mapping"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static bool idmap_store_secret(const char *backend,
+ const char *domain,
+ const char *identity,
+ const char *secret)
+{
+ char *tmp;
+ int r;
+ bool ret;
+
+ r = asprintf(&tmp, "IDMAP_%s_%s", backend, domain);
+
+ if (r < 0) return false;
+
+ /* make sure the key is case insensitive */
+ if (!strupper_m(tmp)) {
+ free(tmp);
+ return false;
+ }
+ ret = secrets_store_generic(tmp, identity, secret);
+
+ free(tmp);
+ return ret;
+}
+
+
+static int net_idmap_secret(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *ctx;
+ const char *secret;
+ const char *dn;
+ char *domain;
+ char *backend;
+ char *opt = NULL;
+ bool ret;
+
+ if (argc != 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:\n"),
+ _("net idmap set secret <DOMAIN> <secret>\n"
+ " Set the secret for the specified domain\n"
+ " DOMAIN\tDomain to set secret for.\n"
+ " secret\tNew secret to set.\n"));
+ return c->display_usage?0:-1;
+ }
+
+ secret = argv[1];
+
+ ctx = talloc_new(NULL);
+ ALLOC_CHECK(ctx);
+
+ domain = talloc_strdup(ctx, argv[0]);
+ ALLOC_CHECK(domain);
+
+ opt = talloc_asprintf(ctx, "idmap config %s", domain);
+ ALLOC_CHECK(opt);
+
+ backend = talloc_strdup(ctx, lp_parm_const_string(-1, opt, "backend", "tdb"));
+ ALLOC_CHECK(backend);
+
+ if ((!backend) || (!strequal(backend, "ldap") &&
+ !strequal(backend, "rfc2307"))) {
+ d_fprintf(stderr,
+ _("The only currently supported backend are LDAP "
+ "and rfc2307\n"));
+ talloc_free(ctx);
+ return -1;
+ }
+
+ dn = lp_parm_const_string(-1, opt, "ldap_user_dn", NULL);
+ if ( ! dn) {
+ d_fprintf(stderr,
+ _("Missing ldap_user_dn option for domain %s\n"),
+ domain);
+ talloc_free(ctx);
+ return -1;
+ }
+
+ ret = idmap_store_secret("ldap", domain, dn, secret);
+
+ if ( ! ret) {
+ d_fprintf(stderr, _("Failed to store secret\n"));
+ talloc_free(ctx);
+ return -1;
+ }
+
+ d_printf(_("Secret stored\n"));
+ return 0;
+}
+
+static int net_idmap_autorid_set_config(struct net_context *c,
+ int argc, const char **argv)
+{
+ int ret = -1;
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx;
+ struct db_context *db = NULL;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap set config <config>"
+ " [--db=<inputfile>]\n"
+ " Update CONFIG entry in autorid.\n"
+ " config\tConfig string to be stored\n"
+ " inputfile\tTDB file to update config.\n"));
+ return c->display_usage ? 0 : -1;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ if (!net_idmap_opendb_autorid(mem_ctx, c, false, &db)) {
+ goto done;
+ }
+
+ status = idmap_autorid_saveconfigstr(db, argv[0]);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Error storing the config in the database: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static int net_idmap_set(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "mapping",
+ net_idmap_set_mapping,
+ NET_TRANSPORT_LOCAL,
+ N_("Not implemented yet"),
+ N_("net idmap set mapping\n"
+ " Not implemented yet")
+ },
+ {
+ "range",
+ net_idmap_autorid_set_range,
+ NET_TRANSPORT_LOCAL,
+ N_("Store a domain-range mapping"),
+ N_("net idmap set range\n"
+ " Store a domain-range mapping")
+ },
+ {
+ "config",
+ net_idmap_autorid_set_config,
+ NET_TRANSPORT_LOCAL,
+ N_("Save the global configuration in the autorid database"),
+ N_("net idmap set config \n"
+ " Save the global configuration in the autorid database ")
+ },
+ {
+ "secret",
+ net_idmap_secret,
+ NET_TRANSPORT_LOCAL,
+ N_("Set secret for specified domain"),
+ N_("net idmap set secret <DOMAIN> <secret>\n"
+ " Set secret for specified domain")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net idmap set", func);
+}
+
+static void net_idmap_autorid_get_range_usage(void)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap get range <SID> [<index>] [--db=<inputfile>]\n"
+ " Get the range for a given domain and index.\n"
+ " SID\t\tSID of the domain\n"
+ " index\trange-index number to be retrieved\n"
+ " inputfile\tTDB file to add mapping to.\n"));
+}
+
+
+static int net_idmap_autorid_get_range(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ TALLOC_CTX *mem_ctx;
+ struct db_context *db = NULL;
+ const char *domsid;
+ uint32_t rangenum;
+ uint32_t range_index = 0;
+ uint32_t low_id;
+ NTSTATUS status;
+ char *keystr;
+ bool ok;
+
+ if (c->display_usage) {
+ net_idmap_autorid_get_range_usage();
+ return 0;
+ }
+
+ if (argc < 1 || argc > 2) {
+ net_idmap_autorid_get_range_usage();
+ return -1;
+ }
+
+ domsid = argv[0];
+
+ if (argc == 2) {
+ ok = parse_uint32(argv[1], &range_index);
+ if (!ok) {
+ d_printf("%s: %s\n",
+ _("Invalid index specification"), argv[1]);
+ net_idmap_autorid_get_range_usage();
+ return -1;
+ }
+ }
+
+ mem_ctx = talloc_stackframe();
+ if (!net_idmap_opendb_autorid(mem_ctx, c, true, &db)) {
+ goto done;
+ }
+
+ status = idmap_autorid_getrange(db, domsid, range_index, &rangenum,
+ &low_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "%s: %s\n",
+ _("Failed to load domain range"), nt_errstr(status));
+ goto done;
+ }
+
+ if (range_index == 0) {
+ keystr = talloc_strdup(mem_ctx, domsid);
+ } else {
+ keystr = talloc_asprintf(mem_ctx, "%s#%"PRIu32, domsid,
+ range_index);
+ }
+
+ printf("RANGE %"PRIu32": %s (low id: %"PRIu32")\n",
+ rangenum, keystr, low_id);
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+static NTSTATUS net_idmap_autorid_print_range(struct db_context *db,
+ const char *domsid,
+ uint32_t range_index,
+ uint32_t rangenum,
+ void *private_data)
+{
+ if (range_index == 0) {
+ printf("RANGE %"PRIu32": %s\n", rangenum, domsid);
+ } else {
+ printf("RANGE %"PRIu32": %s#%"PRIu32"\n", rangenum, domsid,
+ range_index);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void net_idmap_autorid_get_ranges_usage(void)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap get ranges [<SID>] [--db=<inputfile>]\n"
+ " Get all ranges for a given domain.\n"
+ " SID\t\tSID of the domain - list all ranges if omitted\n"
+ " inputfile\tTDB file to add mapping to.\n"));
+}
+
+static int net_idmap_autorid_get_ranges(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ TALLOC_CTX *mem_ctx;
+ struct db_context *db = NULL;
+ const char *domsid;
+ NTSTATUS status;
+
+ if (c->display_usage) {
+ net_idmap_autorid_get_ranges_usage();
+ return 0;
+ }
+
+ if (argc == 0) {
+ domsid = NULL;
+ } else if (argc == 1) {
+ domsid = argv[0];
+ } else {
+ net_idmap_autorid_get_ranges_usage();
+ return -1;
+ }
+
+ mem_ctx = talloc_stackframe();
+ if (!net_idmap_opendb_autorid(mem_ctx, c, true, &db)) {
+ goto done;
+ }
+
+ status = idmap_autorid_iterate_domain_ranges_read(db,
+ domsid,
+ net_idmap_autorid_print_range,
+ NULL, /* private_data */
+ NULL /* count */);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "%s: %s\n",
+ _("Error getting domain ranges"), nt_errstr(status));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static int net_idmap_autorid_get_config(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ char *config;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+ struct db_context *db = NULL;
+
+ if (argc > 0 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap get config"
+ " [--db=<inputfile>]\n"
+ " Get CONFIG entry from autorid database\n"
+ " inputfile\tTDB file to read config from.\n"));
+ return c->display_usage ? 0 : -1;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ if (!net_idmap_opendb_autorid(mem_ctx, c, true, &db)) {
+ goto done;
+ }
+
+ status = idmap_autorid_getconfigstr(db, mem_ctx, &config);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "%s: %s\n",
+ _("Error: unable to read config entry"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ printf("CONFIG: %s\n", config);
+ ret = 0;
+
+done:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+
+static int net_idmap_get(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "range",
+ net_idmap_autorid_get_range,
+ NET_TRANSPORT_LOCAL,
+ N_("Get the range for a domain and range-index"),
+ N_("net idmap get range\n"
+ " Get the range for a domain and range-index")
+ },
+ {
+ "ranges",
+ net_idmap_autorid_get_ranges,
+ NET_TRANSPORT_LOCAL,
+ N_("Get all ranges for a domain"),
+ N_("net idmap get ranges <SID>\n"
+ " Get all ranges for a domain")
+ },
+ {
+ "config",
+ net_idmap_autorid_get_config,
+ NET_TRANSPORT_LOCAL,
+ N_("Get the global configuration from the autorid database"),
+ N_("net idmap get config \n"
+ " Get the global configuration from the autorid database ")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net idmap get", func);
+}
+
+static int net_idmap_check(struct net_context *c, int argc, const char **argv)
+{
+ char *dbfile;
+ struct check_options opts;
+ struct net_idmap_ctx ctx = { .backend = TDB };
+ int ret;
+
+ if ( argc > 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net idmap check [-v] [-r] [-a] [-T] [-f] [-l] [[--db=]<TDB>]\n"
+ " Check an idmap database.\n"
+ " --verbose,-v\tverbose\n"
+ " --repair,-r\trepair\n"
+ " --auto,-a\tnoninteractive mode\n"
+ " --test,-T\tdry run\n"
+ " --force,-f\tforce\n"
+ " --lock,-l\tlock db while doing the check\n"
+ " TDB\tidmap database\n"));
+ return c->display_usage ? 0 : -1;
+ }
+
+ if (argc > 0) {
+ dbfile = talloc_strdup(talloc_tos(), argv[0]);
+ } else {
+ dbfile = net_idmap_dbfile(c, &ctx);
+ }
+ if (dbfile == NULL) {
+ return -1;
+ }
+
+ if (ctx.backend != TDB) {
+ d_fprintf(stderr, _("Sorry, checking of non-TDB databases is "
+ "currently not supported\n"));
+ talloc_free(dbfile);
+ return -1;
+ }
+
+ d_fprintf(stderr, _("check database: %s\n"), dbfile);
+
+ opts = (struct check_options) {
+ .lock = c->opt_lock || c->opt_long_list_entries,
+ .test = c->opt_testmode,
+ .automatic = c->opt_auto,
+ .verbose = c->opt_verbose,
+ .force = c->opt_force,
+ .repair = c->opt_repair || c->opt_reboot,
+ };
+
+ ret = net_idmap_check_db(dbfile, &opts);
+ talloc_free(dbfile);
+ return ret;
+}
+
+/***********************************************************
+ Look at the current idmap
+ **********************************************************/
+int net_idmap(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "dump",
+ net_idmap_dump,
+ NET_TRANSPORT_LOCAL,
+ N_("Dump the current ID mapping database"),
+ N_("net idmap dump\n"
+ " Dump the current ID mappings")
+ },
+ {
+ "restore",
+ net_idmap_restore,
+ NET_TRANSPORT_LOCAL,
+ N_("Restore entries from a file or stdin"),
+ N_("net idmap restore\n"
+ " Restore entries from stdin")
+ },
+ {
+ "get",
+ net_idmap_get,
+ NET_TRANSPORT_LOCAL,
+ N_("Read data from the ID mapping database"),
+ N_("net idmap get\n"
+ " Read data from the ID mapping database")
+ },
+ {
+ "set",
+ net_idmap_set,
+ NET_TRANSPORT_LOCAL,
+ N_("Write data to the ID mapping database"),
+ N_("net idmap set\n"
+ " Write data to the ID mapping database")
+ },
+ {
+ "delete",
+ net_idmap_delete,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete entries from the ID mapping database"),
+ N_("net idmap delete\n"
+ " Delete entries from the ID mapping database")
+ },
+ {
+ "check",
+ net_idmap_check,
+ NET_TRANSPORT_LOCAL,
+ N_("Check id mappings"),
+ N_("net idmap check\n"
+ " Check id mappings")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net idmap", func);
+}
+
+
diff --git a/source3/utils/net_idmap_check.c b/source3/utils/net_idmap_check.c
new file mode 100644
index 0000000..51f4a40
--- /dev/null
+++ b/source3/utils/net_idmap_check.c
@@ -0,0 +1,974 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Check the idmap database.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Mar 2011
+ */
+
+#include "net_idmap_check.h"
+#include "includes.h"
+#include "system/filesys.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "net.h"
+#include "../libcli/security/dom_sid.h"
+#include "cbuf.h"
+#include "srprs.h"
+#include "util_tdb.h"
+#include "interact.h"
+
+static int traverse_commit(struct db_record *diff_rec, void* data);
+static int traverse_check(struct db_record *rec, void* data);
+
+/* TDB_DATA *******************************************************************/
+static char* print_data(TALLOC_CTX* mem_ctx, TDB_DATA d);
+static TDB_DATA parse_data(TALLOC_CTX* mem_ctx, const char** ptr);
+
+/* record *********************************************************************/
+
+enum DT {
+ DT_INV = 0,
+ DT_SID, DT_UID, DT_GID,
+ DT_HWM, DT_VER, DT_SEQ,
+};
+
+struct record {
+ enum DT key_type, val_type;
+ TDB_DATA key, val;
+ struct dom_sid sid;
+ long unsigned id;
+};
+
+static struct record* parse_record(TALLOC_CTX* ctx, TDB_DATA key, TDB_DATA val);
+static struct record* reverse_record(struct record* rec);
+
+static bool is_invalid(const struct record* r) {
+ return (r->key_type == DT_INV) || (r->val_type == DT_INV);
+}
+
+static bool is_map(const struct record* r) {
+ return (r->key_type == DT_SID)
+ || (r->key_type == DT_UID) || (r->key_type == DT_GID);
+}
+
+/* action *********************************************************************/
+
+typedef struct check_action {
+ void (*fmt)(struct check_action *a,
+ struct record *r,
+ TDB_DATA *v);
+ const char* name;
+ const char* prompt;
+ const char* answers;
+ char auto_action;
+ char default_action;
+ bool verbose;
+} check_action;
+
+struct check_actions {
+ check_action invalid_record;
+ check_action missing_reverse;
+ check_action invalid_mapping;
+ check_action invalid_edit;
+ check_action record_exists;
+ check_action no_version;
+ check_action wrong_version;
+ check_action invalid_hwm;
+ check_action commit;
+ check_action valid_mapping;
+ check_action valid_other;
+ check_action invalid_diff;
+};
+
+static void invalid_mapping_fmt(struct check_action *a,
+ struct record *r,
+ TDB_DATA *v)
+{
+ d_printf("%1$s: %2$s -> %3$s\n(%4$s <- %3$s)\n",
+ a->name,
+ print_data(r, r->key),
+ print_data(r, r->val),
+ (v ? print_data(r, *v) : ""));
+}
+
+static void record_exists_fmt(struct check_action *a,
+ struct record *r,
+ TDB_DATA *v)
+{
+ d_printf("%1$s: %2$s\n-%4$s\n+%3$s\n",
+ a->name,
+ print_data(r, r->key),
+ print_data(r, r->val),
+ (v ? print_data(r, *v) : ""));
+}
+
+static void valid_mapping_fmt(struct check_action *a,
+ struct record *r,
+ TDB_DATA *v)
+{
+ d_printf("%1$s: %2$s <-> %3$s\n",
+ a->name,
+ print_data(r, r->key),
+ print_data(r, r->val));
+}
+
+static struct check_actions
+check_actions_init(const struct check_options* opts) {
+ struct check_actions ret = {
+ .invalid_record = (check_action) {
+ .name = "Invalid record",
+ .prompt = "[e]dit/[d]elete/[D]elete all"
+ "/[s]kip/[S]kip all",
+ .answers = "eds",
+ .default_action = 'e',
+ .verbose = true,
+ },
+ .missing_reverse = (check_action) {
+ .name = "Missing reverse mapping for",
+ .prompt = "[f]ix/[F]ix all/[e]dit/[d]elete/[D]elete all"
+ "/[s]kip/[S]kip all",
+ .answers = "feds",
+ .default_action = 'f',
+ .verbose = true,
+ },
+ .invalid_mapping = (check_action) {
+ .fmt = invalid_mapping_fmt,
+ .name = "Invalid mapping",
+ .prompt = "[e]dit/[d]elete/[D]elete all"
+ "/[s]kip/[S]kip all",
+ .answers = "eds",
+ .default_action = 'd',
+ .verbose = true,
+ },
+ .invalid_edit = (check_action) {
+ .name = "Invalid record",
+ .prompt = "[e]dit/[d]elete/[D]elete all"
+ "/[s]kip/[S]kip all",
+ .answers = "eds",
+ .default_action = 'e',
+ .verbose = true,
+ },
+ .record_exists = (check_action) {
+ .fmt = record_exists_fmt,
+ .name = "Record exists",
+ .prompt = "[o]verwrite/[O]verwrite all/[e]dit"
+ "/[d]elete/[D]elete all/[s]kip/[S]kip all",
+ .answers = "oeds",
+ .default_action = 'o',
+ .verbose = true,
+ },
+ .no_version = (check_action) {
+ .prompt = "[f]ix/[s]kip/[a]bort",
+ .answers = "fsa",
+ .default_action = 'f',
+ },
+ .wrong_version = (check_action) {
+ .prompt = "[f]ix/[s]kip/[a]bort",
+ .answers = "fsa",
+ .default_action = 'a',
+ },
+ .invalid_hwm = (check_action) {
+ .prompt = "[f]ix/[s]kip",
+ .answers = "fs",
+ .default_action = 'f',
+ },
+ .commit = (check_action) {
+ .prompt = "[c]ommit/[l]ist/[s]kip",
+ .answers = "cls",
+ .default_action = 'l',
+ .verbose = true,
+ },
+ .valid_mapping = (check_action) {
+ .fmt = valid_mapping_fmt,
+ .name = "Mapping",
+ .auto_action = 's',
+ .verbose = opts->verbose,
+ },
+ .valid_other = (check_action) {
+ .name = "Other",
+ .auto_action = 's',
+ .verbose = opts->verbose,
+ },
+ .invalid_diff = (check_action) {
+ .prompt = "[s]kip/[S]kip all/[c]ommit/[C]ommit all"
+ "/[a]bort",
+ .answers = "sca",
+ .default_action = 's',
+ },
+ };
+
+ if (!opts->repair) {
+ ret.invalid_record.auto_action = 's';
+ ret.missing_reverse.auto_action = 's';
+ ret.invalid_mapping.auto_action = 's';
+ ret.no_version.auto_action = 's';
+ ret.wrong_version.auto_action = 's';
+ ret.invalid_hwm.auto_action = 's';
+ ret.commit.auto_action = 's';
+ }
+
+ if (opts->automatic) {
+ ret.invalid_record.auto_action = 'd'; /* delete */
+ ret.missing_reverse.auto_action = 'f'; /* fix */
+ ret.invalid_mapping.auto_action = 'd'; /* delete */
+ ret.no_version.auto_action = 'f'; /* fix */
+ ret.wrong_version.auto_action = 'a'; /* abort */
+ ret.invalid_hwm.auto_action = 'f'; /* fix */
+ ret.commit.auto_action = 'c'; /* commit */
+ ret.invalid_diff.auto_action = 'a'; /* abort */
+ if (opts->force) {
+ ret.wrong_version.auto_action = 'f'; /* fix */
+ ret.invalid_diff.auto_action = 'c'; /* commit */
+ }
+ }
+ if (opts->test) {
+ ret.invalid_diff.auto_action = 'c'; /* commit */
+/* ret.commit.auto_action = 'c';*/ /* commit */
+ }
+
+ return ret;
+}
+
+static char get_action(struct check_action* a, struct record* r, TDB_DATA* v) {
+ char ret;
+ if (a->verbose && (r != NULL)) {
+ if (!a->fmt) {
+ d_printf("%s: %s ", a->name, print_data(r, r->key));
+ if (is_map(r)) {
+ d_printf("-> %s\n", print_data(r, r->val));
+ } else if (r->key_type == DT_HWM ||
+ r->key_type == DT_VER ||
+ r->key_type == DT_SEQ)
+ {
+ d_printf(": %ld\n", r->id);
+ } else {
+ d_printf("\n");
+ }
+ } else {
+ a->fmt(a, r, v);
+ }
+ }
+
+ if (a->auto_action != '\0') {
+ return a->auto_action;
+ }
+
+ ret = interact_prompt(a->prompt, a->answers, a->default_action);
+
+ if (isupper(ret)) {
+ ret = tolower(ret);
+ a->auto_action = ret;
+ }
+ a->default_action = ret;
+ return ret;
+}
+
+/* *************************************************************************/
+
+typedef struct {
+ TDB_DATA oval, nval;
+} TDB_DATA_diff;
+
+static TDB_DATA pack_diff(TDB_DATA_diff* diff) {
+ return (TDB_DATA) {
+ .dptr = (uint8_t *)diff,
+ .dsize = sizeof(TDB_DATA_diff),
+ };
+}
+
+static TDB_DATA_diff unpack_diff(TDB_DATA data) {
+ assert(data.dsize == sizeof(TDB_DATA_diff));
+ return *(TDB_DATA_diff*)data.dptr;
+}
+
+#define DEBUG_DIFF(LEV,MEM,MSG,KEY,OLD,NEW) \
+ DEBUG(LEV, ("%s: %s\n", MSG, print_data(MEM, KEY))); \
+ if (!tdb_data_is_empty(OLD)) { \
+ DEBUGADD(LEV, ("-%s\n", print_data(MEM, OLD))); \
+ } \
+ if (!tdb_data_is_empty(NEW)) { \
+ DEBUGADD(LEV, ("+%s\n", print_data(MEM, NEW))); \
+ }
+
+struct check_ctx {
+ int oflags;
+ char* name;
+ bool transaction;
+ struct db_context *db;
+ struct db_context *diff;
+ struct check_actions action;
+
+ uint32_t uid_hwm;
+ uint32_t gid_hwm;
+
+ unsigned n_invalid_record;
+ unsigned n_missing_reverse;
+ unsigned n_invalid_mappping;
+ unsigned n_map;
+ unsigned n_other;
+ unsigned n_diff;
+ struct check_options opts;
+};
+
+
+static void adjust_hwm(struct check_ctx* ctx, const struct record* r);
+
+static int add_record(struct check_ctx* ctx, TDB_DATA key, TDB_DATA value)
+{
+ NTSTATUS status;
+ TDB_DATA_diff diff;
+ TALLOC_CTX* mem = talloc_new(ctx->diff);
+ TDB_DATA recvalue;
+ struct db_record *rec = dbwrap_fetch_locked(ctx->diff, mem, key);
+
+ if (rec == NULL) {
+ return -1;
+ }
+
+ recvalue = dbwrap_record_get_value(rec);
+
+ if (recvalue.dptr == 0) { /* first entry */
+ status = dbwrap_fetch(ctx->db, ctx->diff, key, &diff.oval);
+ if (!NT_STATUS_IS_OK(status)) {
+ diff.oval = tdb_null;
+ }
+ } else {
+ diff = unpack_diff(recvalue);
+ talloc_free(diff.nval.dptr);
+ }
+ diff.nval = tdb_data_talloc_copy(ctx->diff, value);
+
+ DEBUG_DIFF(2, mem, "TDB DIFF", key, diff.oval, diff.nval);
+
+ status = dbwrap_record_store(rec, pack_diff(&diff), 0);
+
+ talloc_free(mem);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("could not store record %s\n", nt_errstr(status)));
+ return -1;
+ }
+ ctx->n_diff ++;
+ return 0;
+}
+
+static int del_record(struct check_ctx* ctx, TDB_DATA key) {
+ return add_record(ctx, key, tdb_null);
+}
+
+static TDB_DATA
+fetch_record(struct check_ctx* ctx, TALLOC_CTX* mem_ctx, TDB_DATA key)
+{
+ TDB_DATA tmp;
+ NTSTATUS status;
+
+ status = dbwrap_fetch(ctx->diff, mem_ctx, key, &tmp);
+
+ if (NT_STATUS_IS_OK(status)) {
+ TDB_DATA_diff diff = unpack_diff(tmp);
+ TDB_DATA ret = tdb_data_talloc_copy(mem_ctx, diff.nval);
+ talloc_free(tmp.dptr);
+ return ret;
+ }
+
+ status = dbwrap_fetch(ctx->db, mem_ctx, key, &tmp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return tdb_null;
+ }
+
+ return tmp;
+}
+
+static void edit_record(struct record* r) {
+ TALLOC_CTX* mem = talloc_new(r);
+ cbuf* ost = cbuf_new(mem);
+ const char* str;
+ struct record* nr;
+ TDB_DATA key;
+ TDB_DATA val;
+ cbuf_printf(ost, "%s %s\n",
+ print_data(mem, r->key), print_data(mem, r->val));
+ str = interact_edit(mem, cbuf_gets(ost, 0));
+ key = parse_data(mem, &str);
+ val = parse_data(mem, &str);
+ nr = parse_record(talloc_parent(r), key, val);
+ if (nr != NULL) {
+ *r = *nr;
+ }
+ talloc_free(mem);
+}
+
+static bool check_version(struct check_ctx* ctx) {
+ static const char* key = "IDMAP_VERSION";
+ uint32_t version;
+ NTSTATUS status;
+ char action = 's';
+ struct check_actions* act = &ctx->action;
+
+ status = dbwrap_fetch_uint32_bystring(ctx->db, key, &version);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("No version number, assume 2\n");
+ action = get_action(&act->no_version, NULL, NULL);
+ } else if (version != 2) {
+ d_printf("Wrong version number %d, should be 2\n", version);
+ action = get_action(&act->wrong_version, NULL, NULL);
+ }
+ switch (action) {
+ case 's':
+ break;
+ case 'f':
+ SIVAL(&version, 0, 2);
+ add_record(ctx, string_term_tdb_data(key),
+ make_tdb_data((uint8_t *)&version, sizeof(uint32_t)));
+ break;
+ case 'a':
+ return false;
+ default:
+ assert(false);
+ }
+ return true;
+}
+
+static void check_hwm(struct check_ctx* ctx, const char* key, uint32_t target) {
+ uint32_t hwm;
+ char action = 's';
+ NTSTATUS status;
+ struct check_actions* act = &ctx->action;
+
+ status = dbwrap_fetch_uint32_bystring(ctx->db, key, &hwm);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("No %s should be %d\n", key, target);
+ action = get_action(&act->invalid_hwm, NULL, NULL);
+ } else if (target < hwm) {
+ d_printf("Invalid %s %d: should be %d\n", key, hwm, target);
+ action = get_action(&act->invalid_hwm, NULL, NULL);
+ }
+ if (action == 'f') {
+ SIVAL(&hwm, 0, target);
+ add_record(ctx, string_term_tdb_data(key),
+ make_tdb_data((uint8_t *)&hwm, sizeof(uint32_t)));
+ }
+}
+
+int traverse_check(struct db_record *rec, void* data) {
+ struct check_ctx* ctx = (struct check_ctx*)data;
+ struct check_actions* act = &ctx->action;
+ TALLOC_CTX* mem = talloc_new(ctx->diff);
+ TDB_DATA key;
+ TDB_DATA value;
+ struct record *r;
+ char action = 's';
+
+ key = dbwrap_record_get_key(rec);
+ value = dbwrap_record_get_value(rec);
+
+ r = parse_record(mem, key, value);
+
+ if (is_invalid(r)) {
+ action = get_action(&act->invalid_record, r, NULL);
+ ctx->n_invalid_record++;
+ } else if (is_map(r)) {
+ TDB_DATA back = fetch_record(ctx, mem, r->val);
+ if (back.dptr == NULL) {
+ action = get_action(&act->missing_reverse, r, NULL);
+ ctx->n_missing_reverse++;
+ } else if (!tdb_data_equal(r->key, back)) {
+ action = get_action(&act->invalid_mapping, r, &back);
+ ctx->n_invalid_mappping++;
+ } else {
+ if (r->key_type == DT_SID) {
+ action = get_action(&act->valid_mapping, r, NULL);
+ ctx->n_map++;
+ } else {
+ action = get_action(&act->valid_mapping, NULL,
+ NULL);
+ }
+ }
+ adjust_hwm(ctx, r);
+ } else {
+ action = get_action(&act->valid_other, r, NULL);
+ ctx->n_other++;
+ }
+
+ while (action) {
+ switch (action) {
+ case 's': /* skip */
+ break;
+ case 'd': /* delete */
+ del_record(ctx, key);
+ break;
+ case 'f': /* add reverse mapping */
+ add_record(ctx, value, key);
+ break;
+ case 'e': /* edit */
+ edit_record(r);
+ action = 'o';
+ if (is_invalid(r)) {
+ action = get_action(&act->invalid_edit, r,NULL);
+ continue;
+ }
+ if (!tdb_data_equal(key, r->key)) {
+ TDB_DATA oval = fetch_record(ctx, mem, r->key);
+ if (!tdb_data_is_empty(oval) &&
+ !tdb_data_equal(oval, r->val))
+ {
+ action = get_action(&act->record_exists,
+ r, &oval);
+ if (action != 'o') {
+ continue;
+ }
+ }
+ }
+ if (is_map(r)) {
+ TDB_DATA okey = fetch_record(ctx, mem, r->val);
+ if (!tdb_data_is_empty(okey) &&
+ !tdb_data_equal(okey, r->key))
+ {
+ action = get_action(&act->record_exists,
+ reverse_record(r),
+ &okey);
+ }
+ }
+ continue;
+ case 'o': /* overwrite */
+ adjust_hwm(ctx, r);
+ if (!tdb_data_equal(key, r->key)) {
+ del_record(ctx, key);
+ }
+ add_record(ctx, r->key, r->val);
+ if (is_map(r)) {
+ add_record(ctx, r->val, r->key);
+ }
+ }
+ action = '\0';
+ };
+
+ talloc_free(mem);
+
+ return 0;
+}
+
+/******************************************************************************/
+
+void adjust_hwm(struct check_ctx* ctx, const struct record* r) {
+ enum DT type = (r->key_type == DT_SID) ? r->val_type : r->key_type;
+ if (type == DT_UID) {
+ ctx->uid_hwm = MAX(ctx->uid_hwm, r->id);
+ } else if (type == DT_GID) {
+ ctx->gid_hwm = MAX(ctx->gid_hwm, r->id);
+ }
+}
+
+static bool is_cstr(TDB_DATA str) {
+ return !tdb_data_is_empty(str) && str.dptr[str.dsize-1] == '\0';
+}
+
+static bool parse_sid (TDB_DATA str, enum DT* type, struct dom_sid* sid) {
+ struct dom_sid tmp;
+ const char* s = (const char*)str.dptr;
+ if ((s[0] == 'S') && string_to_sid(&tmp, s)) {
+ *sid = tmp;
+ *type = DT_SID;
+ return true;
+ }
+ return false;
+}
+
+static bool parse_xid(TDB_DATA str, enum DT* type, unsigned long* id) {
+ char c, t;
+ unsigned long tmp;
+ if (sscanf((const char*)str.dptr, "%cID %lu%c", &c, &tmp, &t) == 2) {
+ if (c == 'U') {
+ *id = tmp;
+ *type = DT_UID;
+ return true;
+ } else if (c == 'G') {
+ *id = tmp;
+ *type = DT_GID;
+ return true;
+ }
+ }
+ return false;
+}
+
+
+struct record*
+parse_record(TALLOC_CTX* mem_ctx, TDB_DATA key, TDB_DATA val)
+{
+ struct record* ret = talloc_zero(mem_ctx, struct record);
+ if (ret == NULL) {
+ DEBUG(0, ("Out of memory.\n"));
+ return NULL;
+ }
+ ret->key = tdb_data_talloc_copy(ret, key);
+ ret->val = tdb_data_talloc_copy(ret, val);
+ if ((ret->key.dptr == NULL && key.dptr != NULL) ||
+ (ret->val.dptr == NULL && val.dptr != NULL))
+ {
+ talloc_free(ret);
+ DEBUG(0, ("Out of memory.\n"));
+ return NULL;
+ }
+ assert((ret->key_type == DT_INV) && (ret->val_type == DT_INV));
+
+ if (!is_cstr(key)) {
+ return ret;
+ }
+ if (parse_sid(key, &ret->key_type, &ret->sid)) {
+ parse_xid(val, &ret->val_type, &ret->id);
+ } else if (parse_xid(key, &ret->key_type, &ret->id)) {
+ if (is_cstr(val)) {
+ parse_sid(val, &ret->val_type, &ret->sid);
+ }
+ } else if (strcmp((const char*)key.dptr, "USER HWM") == 0) {
+ ret->key_type = DT_HWM;
+ if (val.dsize == 4) {
+ ret->id = IVAL(val.dptr,0);
+ ret->val_type = DT_UID;
+ }
+ } else if (strcmp((const char*)key.dptr, "GROUP HWM") == 0) {
+ ret->key_type = DT_HWM;
+ if (val.dsize == 4) {
+ ret->id = IVAL(val.dptr,0);
+ ret->val_type = DT_GID;
+ }
+ } else if (strcmp((const char*)key.dptr, "IDMAP_VERSION") == 0) {
+ ret->key_type = DT_VER;
+ if (val.dsize == 4) {
+ ret->id = IVAL(val.dptr,0);
+ ret->val_type = DT_VER;
+ }
+ } else if (strcmp((const char*)key.dptr, "__db_sequence_number__") == 0) {
+ ret->key_type = DT_SEQ;
+ if (val.dsize == 8) {
+ ret->id = *(uint64_t*)val.dptr;
+ ret->val_type = DT_SEQ;
+ }
+ }
+
+ return ret;
+}
+
+struct record* reverse_record(struct record* in)
+{
+ return parse_record(talloc_parent(in), in->val, in->key);
+}
+
+
+/******************************************************************************/
+
+
+char* print_data(TALLOC_CTX* mem_ctx, TDB_DATA d)
+{
+ if (!tdb_data_is_empty(d)) {
+ char* ret = NULL;
+ cbuf* ost = cbuf_new(mem_ctx);
+ int len = cbuf_print_quoted(ost, (const char*)d.dptr, d.dsize);
+ if (len != -1) {
+ cbuf_swapptr(ost, &ret, 0);
+ talloc_steal(mem_ctx, ret);
+ }
+ talloc_free(ost);
+ return ret;
+ }
+ return talloc_strdup(mem_ctx, "<NULL>");
+}
+
+
+TDB_DATA parse_data(TALLOC_CTX* mem_ctx, const char** ptr) {
+ cbuf* ost = cbuf_new(mem_ctx);
+ TDB_DATA ret = tdb_null;
+ srprs_skipws(ptr);
+ if (srprs_quoted(ptr, ost)) {
+ ret.dsize = cbuf_getpos(ost);
+ ret.dptr = (uint8_t *)talloc_steal(mem_ctx, cbuf_gets(ost,0));
+ }
+ talloc_free(ost);
+ return ret;
+}
+
+static int traverse_print_diff(struct db_record *rec, void* data) {
+ struct check_ctx* ctx = (struct check_ctx*)data;
+ TDB_DATA key;
+ TDB_DATA value;
+ TDB_DATA_diff diff;
+ TALLOC_CTX* mem = talloc_new(ctx->diff);
+
+ key = dbwrap_record_get_key(rec);
+ value = dbwrap_record_get_value(rec);
+ diff = unpack_diff(value);
+
+ DEBUG_DIFF(0, mem, "DIFF", key, diff.oval, diff.nval);
+
+ talloc_free(mem);
+ return 0;
+}
+
+
+static int traverse_commit(struct db_record *diff_rec, void* data) {
+ struct check_ctx* ctx = (struct check_ctx*)data;
+ TDB_DATA key;
+ TDB_DATA diff_value;
+ TDB_DATA_diff diff;
+ TDB_DATA value;
+ TALLOC_CTX* mem = talloc_new(ctx->diff);
+ int ret = -1;
+ NTSTATUS status;
+ struct check_actions* act = &ctx->action;
+ struct db_record* rec;
+
+ key = dbwrap_record_get_key(diff_rec);
+ diff_value = dbwrap_record_get_value(diff_rec);
+ diff = unpack_diff(diff_value);
+
+ rec = dbwrap_fetch_locked(ctx->db, mem, key);
+ if (rec == NULL) {
+ goto done;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ if (!tdb_data_equal(value, diff.oval)) {
+ char action;
+
+ d_printf("Warning: record has changed: %s\n"
+ "expected: %s got %s\n", print_data(mem, key),
+ print_data(mem, diff.oval),
+ print_data(mem, value));
+
+ action = get_action(&act->invalid_diff, NULL, NULL);
+ if (action == 's') {
+ ret = 0;
+ goto done;
+ } else if (action == 'a') {
+ goto done;
+ }
+ }
+
+ DEBUG_DIFF(0, mem, "Commit", key, diff.oval, diff.nval);
+
+ if (tdb_data_is_empty(diff.nval)) {
+ status = dbwrap_record_delete(rec);
+ } else {
+ status = dbwrap_record_store(rec, diff.nval, 0);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("could not store record %s\n", nt_errstr(status)));
+ if (!ctx->opts.force) {
+ goto done;
+ }
+ }
+ ret = 0;
+done:
+ talloc_free(mem);
+ return ret;
+}
+
+static struct check_ctx*
+check_init(TALLOC_CTX* mem_ctx, const struct check_options* o)
+{
+ struct check_ctx* ctx = talloc_zero(mem_ctx, struct check_ctx);
+ if (ctx == NULL) {
+ DEBUG(0, (_("No memory\n")));
+ return NULL;
+ }
+
+ ctx->diff = db_open_rbt(ctx);
+ if (ctx->diff == NULL) {
+ talloc_free(ctx);
+ DEBUG(0, (_("No memory\n")));
+ return NULL;
+ }
+
+ ctx->action = check_actions_init(o);
+ ctx->opts = *o;
+ return ctx;
+}
+
+static bool check_open_db(struct check_ctx* ctx, const char* name, int oflags)
+{
+ if (name == NULL) {
+ d_fprintf(stderr, _("Error: name == NULL in check_open_db().\n"));
+ return false;
+ }
+
+ if (ctx->db != NULL) {
+ if ((ctx->oflags == oflags) && (strcmp(ctx->name, name))) {
+ return true;
+ } else {
+ TALLOC_FREE(ctx->db);
+ }
+ }
+
+ ctx->db = db_open(ctx, name, 0, TDB_DEFAULT, oflags, 0,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (ctx->db == NULL) {
+ d_fprintf(stderr,
+ _("Could not open idmap db (%s) for writing: %s\n"),
+ name, strerror(errno));
+ return false;
+ }
+
+ if (ctx->name != name) {
+ TALLOC_FREE(ctx->name);
+ ctx->name = talloc_strdup(ctx, name);
+ }
+
+ ctx->oflags = oflags;
+ return true;
+}
+
+static bool check_do_checks(struct check_ctx* ctx)
+{
+ NTSTATUS status;
+
+ if (!check_version(ctx)) {
+ return false;
+ }
+
+ status = dbwrap_traverse(ctx->db, traverse_check, ctx, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to traverse %s\n", ctx->name));
+ return false;
+ }
+
+ check_hwm(ctx, "USER HWM", ctx->uid_hwm + 1);
+ check_hwm(ctx, "GROUP HWM", ctx->gid_hwm + 1);
+
+ return true;
+}
+
+static void check_summary(const struct check_ctx* ctx)
+{
+ d_printf("uid hwm: %d\ngid hwm: %d\n", ctx->uid_hwm, ctx->gid_hwm);
+ d_printf("mappings: %d\nother: %d\n", ctx->n_map, ctx->n_other);
+ d_printf("invalid records: %d\nmissing links: %d\ninvalid links: %d\n",
+ ctx->n_invalid_record, ctx->n_missing_reverse,
+ ctx->n_invalid_mappping);
+ d_printf("%u changes:\n", ctx->n_diff);
+}
+
+static bool check_transaction_start(struct check_ctx* ctx) {
+ return (dbwrap_transaction_start(ctx->db) == 0);
+}
+
+static bool check_transaction_commit(struct check_ctx* ctx) {
+ return (dbwrap_transaction_commit(ctx->db) == 0);
+}
+
+static bool check_transaction_cancel(struct check_ctx* ctx) {
+ return (dbwrap_transaction_cancel(ctx->db) == 0);
+}
+
+
+static void check_diff_list(struct check_ctx* ctx) {
+ NTSTATUS status = dbwrap_traverse(ctx->diff, traverse_print_diff, ctx, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("failed to traverse diff\n"));
+ }
+
+}
+
+static bool check_commit(struct check_ctx* ctx)
+{
+ struct check_actions* act = &ctx->action;
+ char action;
+ NTSTATUS status = NT_STATUS_OK;
+
+ check_summary(ctx);
+
+ if (ctx->n_diff == 0) {
+ return true;
+ }
+
+ while ((action = get_action(&act->commit, NULL, NULL)) == 'l') {
+ check_diff_list(ctx);
+ }
+ if (action == 's') {
+ return true;
+ }
+ assert(action == 'c');
+
+ if (!check_open_db(ctx, ctx->name, O_RDWR)) {
+ return false;
+ }
+
+ if (!check_transaction_start(ctx)) {
+ return false;
+ }
+
+ status = dbwrap_traverse(ctx->diff, traverse_commit, ctx, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ check_transaction_cancel(ctx);
+ return false;
+ }
+ if (ctx->opts.test) { /*get_action? */
+ return check_transaction_cancel(ctx);
+ } else {
+ return check_transaction_commit(ctx);
+ }
+}
+
+int net_idmap_check_db(const char* db, const struct check_options* o)
+{
+ int ret = -1;
+ TALLOC_CTX* mem_ctx = talloc_stackframe();
+ struct check_ctx* ctx = check_init(mem_ctx, o);
+
+ if (!o->automatic && !isatty(STDIN_FILENO)) {
+ DEBUG(0, ("Interactive use needs tty, use --auto\n"));
+ goto done;
+ }
+ if (o->lock) {
+ if (check_open_db(ctx, db, O_RDWR)
+ && check_transaction_start(ctx))
+ {
+ if ( check_do_checks(ctx)
+ && check_commit(ctx)
+ && check_transaction_commit(ctx))
+ {
+ ret = 0;
+ } else {
+ check_transaction_cancel(ctx);
+ }
+ }
+ } else {
+ if (check_open_db(ctx, db, O_RDONLY)
+ && check_do_checks(ctx)
+ && check_commit(ctx))
+ {
+ ret = 0;
+ }
+ }
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+
+/*Local Variables:*/
+/*mode: c*/
+/*End:*/
diff --git a/source3/utils/net_idmap_check.h b/source3/utils/net_idmap_check.h
new file mode 100644
index 0000000..4176342
--- /dev/null
+++ b/source3/utils/net_idmap_check.h
@@ -0,0 +1,48 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Check the idmap database.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Mar 2011
+ */
+
+#ifndef NET_IDMAP_CHECK_H
+#define NET_IDMAP_CHECK_H
+
+#include <stdbool.h>
+
+struct net_context;
+
+struct check_options {
+ bool test;
+ bool verbose;
+ bool lock;
+ bool automatic;
+ bool force;
+ bool repair;
+};
+
+int net_idmap_check_db(const char* db, const struct check_options* opts);
+
+#endif /* NET_IDMAP_CHECK_H */
+
+/*Local Variables:*/
+/*mode: c*/
+/*End:*/
diff --git a/source3/utils/net_join.c b/source3/utils/net_join.c
new file mode 100644
index 0000000..f67f08f
--- /dev/null
+++ b/source3/utils/net_join.c
@@ -0,0 +1,55 @@
+/*
+ Samba Unix/Linux SMB client library
+ net join commands
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+
+int net_join_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("\nnet [<method>] join [misc. options]\n"
+ "\tjoins this server to a domain\n"));
+ d_printf(_("Valid methods: (auto-detected if not specified)\n"));
+ d_printf(_("\tads\t\t\t\tActive Directory (LDAP/Kerberos)\n"));
+ d_printf(_("\trpc\t\t\t\tDCE-RPC\n"));
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+int net_join(struct net_context *c, int argc, const char **argv)
+{
+ if ((argc > 0) && (strcasecmp_m(argv[0], "HELP") == 0)) {
+ net_join_usage(c, argc, argv);
+ return 0;
+ }
+
+ net_warn_member_options();
+
+ if (net_ads_check_our_domain(c) == 0) {
+ if (net_ads_join(c, argc, argv) == 0)
+ return 0;
+ else
+ d_fprintf(stderr,
+ _("ADS join did not work, falling back to "
+ "RPC...\n"));
+ }
+ return net_rpc_join(c, argc, argv);
+}
+
+
diff --git a/source3/utils/net_lookup.c b/source3/utils/net_lookup.c
new file mode 100644
index 0000000..570135a
--- /dev/null
+++ b/source3/utils/net_lookup.c
@@ -0,0 +1,542 @@
+/*
+ Samba Unix/Linux SMB client library
+ net lookup command
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "libsmb/namequery.h"
+#include "libads/sitename_cache.h"
+#include "lib/addns/dnsquery_srv.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "smb_krb5.h"
+#include "../libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "libsmb/dsgetdcname.h"
+
+int net_lookup_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+" net lookup [host] HOSTNAME[#<type>]\n\tgives IP for a hostname\n\n"
+" net lookup ldap [domain] [sitename]\n\tgives IP of domain's ldap server\n\n"
+" net lookup kdc [realm]\n\tgives IP of realm's kerberos KDC\n\n"
+" net lookup pdc [domain|realm]\n\tgives IP of realm's kerberos KDC\n\n"
+" net lookup dc [domain]\n\tgives IP of domains Domain Controllers\n\n"
+" net lookup master [domain|wg]\n\tgive IP of master browser\n\n"
+" net lookup name [name]\n\tLookup name's sid and type\n\n"
+" net lookup sid [sid]\n\tGive sid's name and type\n\n"
+" net lookup dsgetdcname [name] [flags] [sitename]\n\n"
+));
+ return -1;
+}
+
+/* lookup a hostname giving an IP */
+static int net_lookup_host(struct net_context *c, int argc, const char **argv)
+{
+ struct sockaddr_storage ss;
+ int name_type = 0x20;
+ char addr[INET6_ADDRSTRLEN];
+ const char *name = argv[0];
+ char *p;
+
+ if (argc == 0)
+ return net_lookup_usage(c, argc, argv);
+
+ p = strchr_m(name,'#');
+ if (p) {
+ *p = '\0';
+ sscanf(++p,"%x",&name_type);
+ }
+
+ if (!resolve_name(name, &ss, name_type, false)) {
+ /* we deliberately use DEBUG() here to send it to stderr
+ so scripts aren't mucked up */
+ DEBUG(0,("Didn't find %s#%02x\n", name, name_type));
+ return -1;
+ }
+
+ print_sockaddr(addr, sizeof(addr), &ss);
+ d_printf("%s\n", addr);
+ return 0;
+}
+
+#ifdef HAVE_ADS
+static void print_ldap_srvlist(struct dns_rr_srv *dclist, size_t numdcs)
+{
+ size_t i;
+
+ for ( i=0; i<numdcs; i++ ) {
+ struct dns_rr_srv *dc = &dclist[i];
+ size_t j;
+
+ for (j=0; j<dc->num_ips; j++) {
+ struct sockaddr_storage *ss = &dc->ss_s[j];
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), ss);
+#ifdef HAVE_IPV6
+ if (ss->ss_family == AF_INET6) {
+ d_printf("[%s]:%"PRIu16"\n", addr, dc->port);
+ }
+#endif
+ if (ss->ss_family == AF_INET) {
+ d_printf("%s:%"PRIu16"\n", addr, dc->port);
+ }
+ }
+ }
+}
+#endif
+
+static int net_lookup_ldap(struct net_context *c, int argc, const char **argv)
+{
+#ifdef HAVE_ADS
+ const char *domain;
+ struct sockaddr_storage ss;
+ struct dns_rr_srv *dcs = NULL;
+ size_t numdcs = 0;
+ const char *sitename = NULL;
+ TALLOC_CTX *ctx;
+ NTSTATUS status;
+ int ret;
+ char h_name[MAX_DNS_NAME_LENGTH];
+ char *query = NULL;
+
+ if (argc > 0)
+ domain = argv[0];
+ else
+ domain = c->opt_target_workgroup;
+
+ if (argc > 1) {
+ sitename = argv[1];
+ }
+
+ if ( (ctx = talloc_init("net_lookup_ldap")) == NULL ) {
+ d_fprintf(stderr,"net_lookup_ldap: talloc_init() %s!\n",
+ _("failed"));
+ return -1;
+ }
+
+ if (sitename == NULL) {
+ sitename = sitename_fetch(ctx, domain);
+ }
+ query = ads_dns_query_string_dcs(ctx, domain);
+
+ DEBUG(9, ("Lookup up ldap for domain %s\n", domain));
+
+ status = ads_dns_query_srv(
+ ctx,
+ lp_get_async_dns_timeout(),
+ sitename,
+ query,
+ &dcs,
+ &numdcs);
+ if ( NT_STATUS_IS_OK(status) && numdcs ) {
+ print_ldap_srvlist(dcs, numdcs);
+ TALLOC_FREE( ctx );
+ return 0;
+ }
+
+ DEBUG(9, ("Looking up PDC for domain %s\n", domain));
+ if (!get_pdc_ip(domain, &ss)) {
+ TALLOC_FREE( ctx );
+ return -1;
+ }
+
+ ret = sys_getnameinfo((struct sockaddr *)&ss,
+ sizeof(struct sockaddr_storage),
+ h_name, sizeof(h_name),
+ NULL, 0,
+ NI_NAMEREQD);
+
+ if (ret) {
+ TALLOC_FREE( ctx );
+ return -1;
+ }
+
+ DEBUG(9, ("Found PDC with DNS name %s\n", h_name));
+ domain = strchr(h_name, '.');
+ if (!domain) {
+ TALLOC_FREE( ctx );
+ return -1;
+ }
+ domain++;
+
+ DEBUG(9, ("Looking up ldap for domain %s\n", domain));
+
+ status = ads_dns_query_srv(
+ ctx,
+ lp_get_async_dns_timeout(),
+ sitename,
+ query,
+ &dcs,
+ &numdcs);
+ if ( NT_STATUS_IS_OK(status) && numdcs ) {
+ print_ldap_srvlist(dcs, numdcs);
+ TALLOC_FREE( ctx );
+ return 0;
+ }
+
+ TALLOC_FREE( ctx );
+
+ return -1;
+#endif
+ DEBUG(1,("No ADS support\n"));
+ return -1;
+}
+
+static int net_lookup_dc(struct net_context *c, int argc, const char **argv)
+{
+ struct samba_sockaddr *sa_list = NULL;
+ struct sockaddr_storage ss;
+ char *pdc_str = NULL;
+ const char *domain = NULL;
+ char *sitename = NULL;
+ size_t count = 0;
+ size_t i;
+ char addr[INET6_ADDRSTRLEN];
+ bool sec_ads = (lp_security() == SEC_ADS);
+ NTSTATUS status;
+
+ if (sec_ads) {
+ domain = lp_realm();
+ } else {
+ domain = c->opt_target_workgroup;
+ }
+
+ if (argc > 0)
+ domain=argv[0];
+
+ /* first get PDC */
+ if (!get_pdc_ip(domain, &ss))
+ return -1;
+
+ print_sockaddr(addr, sizeof(addr), &ss);
+ if (asprintf(&pdc_str, "%s", addr) == -1) {
+ return -1;
+ }
+ d_printf("%s\n", pdc_str);
+
+ sitename = sitename_fetch(talloc_tos(), domain);
+ status = get_sorted_dc_list(talloc_tos(),
+ domain,
+ sitename,
+ &sa_list,
+ &count,
+ sec_ads);
+ if (!NT_STATUS_IS_OK(status)) {
+ SAFE_FREE(pdc_str);
+ TALLOC_FREE(sitename);
+ return 0;
+ }
+ TALLOC_FREE(sitename);
+ for (i=0;i<count;i++) {
+ print_sockaddr(addr, sizeof(addr), &sa_list[i].u.ss);
+ if (!strequal(pdc_str, addr))
+ d_printf("%s\n", addr);
+ }
+ TALLOC_FREE(sa_list);
+ SAFE_FREE(pdc_str);
+ return 0;
+}
+
+static int net_lookup_pdc(struct net_context *c, int argc, const char **argv)
+{
+ struct sockaddr_storage ss;
+ char *pdc_str = NULL;
+ const char *domain;
+ char addr[INET6_ADDRSTRLEN];
+
+ if (lp_security() == SEC_ADS) {
+ domain = lp_realm();
+ } else {
+ domain = c->opt_target_workgroup;
+ }
+
+ if (argc > 0)
+ domain=argv[0];
+
+ /* first get PDC */
+ if (!get_pdc_ip(domain, &ss))
+ return -1;
+
+ print_sockaddr(addr, sizeof(addr), &ss);
+ if (asprintf(&pdc_str, "%s", addr) == -1) {
+ return -1;
+ }
+ d_printf("%s\n", pdc_str);
+ SAFE_FREE(pdc_str);
+ return 0;
+}
+
+
+static int net_lookup_master(struct net_context *c, int argc, const char **argv)
+{
+ struct sockaddr_storage master_ss;
+ const char *domain = c->opt_target_workgroup;
+ char addr[INET6_ADDRSTRLEN];
+
+ if (argc > 0)
+ domain=argv[0];
+
+ if (!find_master_ip(domain, &master_ss))
+ return -1;
+ print_sockaddr(addr, sizeof(addr), &master_ss);
+ d_printf("%s\n", addr);
+ return 0;
+}
+
+static int net_lookup_kdc(struct net_context *c, int argc, const char **argv)
+{
+#ifdef HAVE_KRB5
+ krb5_error_code rc;
+ krb5_context ctx;
+ struct samba_sockaddr *kdcs = NULL;
+ const char *realm;
+ char **get_host_realms = NULL;
+ size_t num_kdcs = 0;
+ size_t i;
+ NTSTATUS status;
+
+ rc = smb_krb5_init_context_common(&ctx);
+ if (rc) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(rc));
+ return -1;
+ }
+
+ if (argc > 0) {
+ realm = argv[0];
+ } else if (lp_realm() && *lp_realm()) {
+ realm = lp_realm();
+ } else {
+ rc = krb5_get_host_realm(ctx, NULL, &get_host_realms);
+ if (rc) {
+ DEBUG(1,("krb5_gethost_realm failed (%s)\n",
+ error_message(rc)));
+ krb5_free_context(ctx);
+ return -1;
+ }
+ realm = (const char *) *get_host_realms;
+ }
+
+ status = get_kdc_list(talloc_tos(),
+ realm,
+ NULL,
+ &kdcs,
+ &num_kdcs);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("get_kdc_list failed (%s)\n",
+ nt_errstr(status));
+ krb5_free_host_realm(ctx, get_host_realms);
+ krb5_free_context(ctx);
+ return -1;
+ }
+
+ for (i = 0; i < num_kdcs; i++) {
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), &kdcs[i].u.ss);
+
+ d_printf("%s:88\n", addr);
+ }
+
+ krb5_free_host_realm(ctx, get_host_realms);
+ krb5_free_context(ctx);
+ TALLOC_FREE(kdcs);
+ return 0;
+#endif
+ DEBUG(1, ("No kerberos support\n"));
+ return -1;
+}
+
+static int net_lookup_name(struct net_context *c, int argc, const char **argv)
+{
+ const char *dom, *name;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ enum lsa_SidType type;
+
+ if (argc != 1) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net lookup name <name>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_ALL,
+ &dom, &name, &sid, &type)) {
+ d_printf(_("Could not lookup name %s\n"), argv[0]);
+ return -1;
+ }
+
+ d_printf("%s %d (%s) %s\\%s\n", dom_sid_str_buf(&sid, &buf),
+ type, sid_type_lookup(type), dom, name);
+ return 0;
+}
+
+static int net_lookup_sid(struct net_context *c, int argc, const char **argv)
+{
+ const char *dom, *name;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ enum lsa_SidType type;
+
+ if (argc != 1) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net lookup sid <sid>\n"));
+ return -1;
+ }
+
+ if (!string_to_sid(&sid, argv[0])) {
+ d_printf(_("Could not convert %s to SID\n"), argv[0]);
+ return -1;
+ }
+
+ if (!lookup_sid(talloc_tos(), &sid,
+ &dom, &name, &type)) {
+ d_printf(_("Could not lookup name %s\n"), argv[0]);
+ return -1;
+ }
+
+ d_printf("%s %d (%s) %s\\%s\n", dom_sid_str_buf(&sid, &buf),
+ type, sid_type_lookup(type), dom, name);
+ return 0;
+}
+
+static int net_lookup_dsgetdcname(struct net_context *c, int argc, const char **argv)
+{
+ NTSTATUS status;
+ const char *domain_name = NULL;
+ const char *site_name = NULL;
+ uint32_t flags = 0;
+ struct netr_DsRGetDCNameInfo *info = NULL;
+ TALLOC_CTX *mem_ctx;
+ char *s = NULL;
+
+ if (argc < 1 || argc > 3) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net lookup dsgetdcname "
+ "<name> <flags> <sitename>\n"));
+ return -1;
+ }
+
+ mem_ctx = talloc_init("net_lookup_dsgetdcname");
+ if (!mem_ctx) {
+ return -1;
+ }
+
+ domain_name = argv[0];
+
+ if (argc >= 2) {
+ sscanf(argv[1], "%x", &flags);
+ }
+
+ if (flags == 0) {
+ flags = DS_DIRECTORY_SERVICE_REQUIRED;
+ }
+
+ if (argc == 3) {
+ site_name = argv[2];
+ }
+
+ if (!c->msg_ctx) {
+ d_fprintf(stderr, _("Could not initialise message context. "
+ "Try running as root\n"));
+ return -1;
+ }
+
+ status = dsgetdcname(mem_ctx, c->msg_ctx, domain_name, NULL, site_name,
+ flags, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("failed with: %s\n"), nt_errstr(status));
+ TALLOC_FREE(mem_ctx);
+ return -1;
+ }
+
+ s = NDR_PRINT_STRUCT_STRING(mem_ctx, netr_DsRGetDCNameInfo, info);
+ printf("%s\n", s);
+ TALLOC_FREE(s);
+
+ TALLOC_FREE(mem_ctx);
+ return 0;
+}
+
+
+/* lookup hosts or IP addresses using internal samba lookup fns */
+int net_lookup(struct net_context *c, int argc, const char **argv)
+{
+ int i;
+
+ struct functable table[] = {
+ {
+ .funcname = "HOST",
+ .fn = net_lookup_host,
+ },
+ {
+ .funcname = "LDAP",
+ .fn = net_lookup_ldap,
+ },
+ {
+ .funcname = "DC",
+ .fn = net_lookup_dc,
+ },
+ {
+ .funcname = "PDC",
+ .fn = net_lookup_pdc,
+ },
+ {
+ .funcname = "MASTER",
+ .fn = net_lookup_master,
+ },
+ {
+ .funcname = "KDC",
+ .fn = net_lookup_kdc,
+ },
+ {
+ .funcname = "NAME",
+ .fn = net_lookup_name,
+ },
+ {
+ .funcname = "SID",
+ .fn = net_lookup_sid,
+ },
+ {
+ .funcname = "DSGETDCNAME",
+ .fn = net_lookup_dsgetdcname,
+ },
+ {
+ .funcname = NULL,
+ },
+ };
+
+ if (argc < 1) {
+ d_printf(_("\nUsage: \n"));
+ return net_lookup_usage(c, argc, argv);
+ }
+ for (i=0; table[i].funcname; i++) {
+ if (strcasecmp_m(argv[0], table[i].funcname) == 0)
+ return table[i].fn(c, argc-1, argv+1);
+ }
+
+ /* Default to lookup a hostname so 'net lookup foo#1b' can be
+ used instead of 'net lookup host foo#1b'. The host syntax
+ is a bit confusing as non #00 names can't really be
+ considered hosts as such. */
+
+ return net_lookup_host(c, argc, argv);
+}
diff --git a/source3/utils/net_notify.c b/source3/utils/net_notify.c
new file mode 100644
index 0000000..eddfb0e
--- /dev/null
+++ b/source3/utils/net_notify.c
@@ -0,0 +1,199 @@
+/*
+ * Samba Unix/Linux notifyd client code
+ * Copyright (C) 2015 Volker Lendecke <vl@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "lib/util/server_id.h"
+#include "lib/util/tevent_unix.h"
+#include "lib/util/server_id_db.h"
+#include "messages.h"
+#include "source3/smbd/notifyd/notifyd.h"
+
+static void net_notify_got_event(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct notify_event_msg *event_msg;
+
+ if (data->length < offsetof(struct notify_event_msg, path) + 1) {
+ d_fprintf(stderr, "message too short\n");
+ return;
+ }
+ if (data->data[data->length-1] != 0) {
+ d_fprintf(stderr, "path not 0-terminated\n");
+ return;
+ }
+
+ event_msg = (struct notify_event_msg *)data->data;
+
+ d_printf("%u %s\n", (unsigned)event_msg->action,
+ event_msg->path);
+}
+
+static int net_notify_listen(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct messaging_context *msg_ctx = c->msg_ctx;
+ struct tevent_context *ev = messaging_tevent_context(msg_ctx);
+ struct server_id_db *names_db = messaging_names_db(msg_ctx);
+ struct server_id notifyd;
+ struct server_id_buf idbuf;
+ struct notify_rec_change_msg msg;
+ struct iovec iov[2];
+ NTSTATUS status;
+ bool ok;
+
+ if (argc != 3) {
+ d_printf("Usage: net notify listen <path> <filter> "
+ "<subdir-filter>\n");
+ return -1;
+ }
+
+ ok = server_id_db_lookup_one(names_db, "notify-daemon", &notifyd);
+ if (!ok) {
+ fprintf(stderr, "no notify daemon found\n");
+ return -1;
+ }
+
+ printf("notify daemon: %s\n", server_id_str_buf(notifyd, &idbuf));
+
+ msg = (struct notify_rec_change_msg) {
+ .instance.filter = atoi(argv[1]),
+ .instance.subdir_filter = atoi(argv[2])
+ };
+ iov[0] = (struct iovec) {
+ .iov_base = &msg,
+ .iov_len = offsetof(struct notify_rec_change_msg, path)
+ };
+ iov[1] = (struct iovec) {
+ .iov_base = discard_const_p(char, argv[0]),
+ .iov_len = strlen(argv[0])+1
+ };
+
+ status = messaging_register(c->msg_ctx, NULL, MSG_PVFS_NOTIFY,
+ net_notify_got_event);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "messaging_register failed: %s\n",
+ nt_errstr(status));
+ return -1;
+ }
+
+ status = messaging_send_iov(
+ c->msg_ctx, notifyd, MSG_SMB_NOTIFY_REC_CHANGE,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "Sending rec_change to %s returned %s\n",
+ server_id_str_buf(notifyd, &idbuf),
+ nt_errstr(status));
+ return -1;
+ }
+
+ while (true) {
+ int ret;
+
+ ret = tevent_loop_once(ev);
+ if (ret != 0) {
+ d_fprintf(stderr, "tevent_loop_once failed: %s\n",
+ strerror(errno));
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int net_notify_trigger(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct messaging_context *msg_ctx = c->msg_ctx;
+ struct server_id_db *names_db = messaging_names_db(msg_ctx);
+ struct server_id notifyd;
+ struct server_id_buf idbuf;
+ struct notify_trigger_msg msg;
+ struct iovec iov[2];
+ NTSTATUS status;
+ bool ok;
+
+ if (argc != 3) {
+ d_printf("Usage: net notify trigger <path> <action> "
+ "<filter>\n");
+ return -1;
+ }
+
+ ok = server_id_db_lookup_one(names_db, "notify-daemon", &notifyd);
+ if (!ok) {
+ fprintf(stderr, "no notify daemon found\n");
+ return -1;
+ }
+
+ printf("notify daemon: %s\n", server_id_str_buf(notifyd, &idbuf));
+
+ msg = (struct notify_trigger_msg) {
+ .action = atoi(argv[1]), .filter = atoi(argv[2])
+ };
+
+ iov[0] = (struct iovec) {
+ .iov_base = &msg,
+ .iov_len = offsetof(struct notify_trigger_msg, path)
+ };
+ iov[1] = (struct iovec) {
+ .iov_base = discard_const_p(char, argv[0]),
+ .iov_len = strlen(argv[0])+1
+ };
+
+ status = messaging_send_iov(
+ c->msg_ctx, notifyd, MSG_SMB_NOTIFY_TRIGGER,
+ iov, ARRAY_SIZE(iov), NULL, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Sending rec_change to %s returned %s\n",
+ server_id_str_buf(notifyd, &idbuf),
+ nt_errstr(status));
+ return -1;
+ }
+
+ return 0;
+}
+
+int net_notify(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ { "listen",
+ net_notify_listen,
+ NET_TRANSPORT_LOCAL,
+ N_("Register for a path and listen for changes"),
+ N_("net notify listen <path>")
+ },
+ { "trigger",
+ net_notify_trigger,
+ NET_TRANSPORT_LOCAL,
+ N_("Simulate a trigger action"),
+ N_("net notify trigger <path> <action> <filter>")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (c->msg_ctx == NULL) {
+ d_fprintf(stderr, "No connection to messaging, need to run "
+ "as root\n");
+ return -1;
+ }
+
+ return net_run_function(c, argc, argv, "net notify", func);
+}
diff --git a/source3/utils/net_offlinejoin.c b/source3/utils/net_offlinejoin.c
new file mode 100644
index 0000000..3ec5c97
--- /dev/null
+++ b/source3/utils/net_offlinejoin.c
@@ -0,0 +1,600 @@
+/*
+ Samba Unix/Linux SMB client library
+ net join commands
+ Copyright (C) 2021 Guenther Deschner (gd@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include <netapi.h>
+#include "netapi/netapi_net.h"
+#include "libcli/registry/util_reg.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/cmdline/cmdline.h"
+
+int net_offlinejoin_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("\nnet offlinejoin [misc. options]\n"
+ "\tjoins a computer to a domain\n"));
+ d_printf(_("Valid commands:\n"));
+ d_printf(_("\tprovision\t\t\tProvision machine account in AD\n"));
+ d_printf(_("\trequestodj\t\t\tRequest offline domain join\n"));
+ d_printf(_("\tcomposeodj\t\t\tCompose offline domain join blob\n"));
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+int net_offlinejoin(struct net_context *c, int argc, const char **argv)
+{
+ int ret;
+ NET_API_STATUS status;
+
+ if ((argc > 0) && (strcasecmp_m(argv[0], "HELP") == 0)) {
+ net_offlinejoin_usage(c, argc, argv);
+ return 0;
+ }
+
+ if (argc == 0) {
+ net_offlinejoin_usage(c, argc, argv);
+ return -1;
+ }
+
+ net_warn_member_options();
+
+ status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (status != 0) {
+ return -1;
+ }
+
+ if (c->opt_kerberos) {
+ libnetapi_set_use_kerberos(c->netapi_ctx);
+ }
+
+ if (strcasecmp_m(argv[0], "provision") == 0) {
+ ret = net_offlinejoin_provision(c, argc, argv);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (strcasecmp_m(argv[0], "requestodj") == 0) {
+ ret = net_offlinejoin_requestodj(c, argc, argv);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ if (strcasecmp_m(argv[0], "composeodj") == 0) {
+ ret = net_offlinejoin_composeodj(c, argc, argv);
+ if (ret != 0) {
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int net_offlinejoin_provision_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("\nnet offlinejoin provision [misc. options]\n"
+ "\tProvisions machine account in AD\n"));
+ d_printf(_("Valid options:\n"));
+ d_printf(_("\tdomain=<DOMAIN>\t\t\t\tDefines AD Domain to join\n"));
+ d_printf(_("\tmachine_name=<MACHINE_NAME>\t\tDefines the machine account name\n"));
+ d_printf(_("\tmachine_account_ou=<OU>\t\t\tDefines the machine account organizational unit DN\n"));
+ d_printf(_("\tdcname=<DCNAME>\t\t\t\tSpecify a Domain Controller to join to\n"));
+ d_printf(_("\tdefpwd\t\t\t\t\tUse default machine account password\n"));
+ d_printf(_("\treuse\t\t\t\t\tReuse existing machine account in AD\n"));
+ d_printf(_("\tsavefile=<FILENAME>\t\t\tFile to store the ODJ data\n"));
+ d_printf(_("\tprintblob\t\t\t\tPrint the base64 encoded ODJ data on stdout\n"));
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+int net_offlinejoin_provision(struct net_context *c,
+ int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ const char *dcname = NULL;
+ const char *domain = NULL;
+ const char *machine_name = NULL;
+ const char *machine_account_ou = NULL;
+ const char *provision_text_data = NULL;
+ uint32_t options = 0;
+ const char *savefile = NULL;
+ bool printblob = false;
+ int i;
+
+ if (c->display_usage || argc == 1) {
+ return net_offlinejoin_provision_usage(c, argc, argv);
+ }
+
+ /* process additional command line args */
+
+ for (i = 0; i < argc; i++) {
+
+ if (strnequal(argv[i], "domain", strlen("domain"))) {
+ domain = get_string_param(argv[i]);
+ if (domain == NULL) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "machine_name", strlen("machine_name"))) {
+ machine_name = get_string_param(argv[i]);
+ if (machine_name == NULL) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "machine_account_ou", strlen("machine_account_ou"))) {
+ machine_account_ou = get_string_param(argv[i]);
+ if (machine_account_ou == NULL) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "dcname", strlen("dcname"))) {
+ dcname = get_string_param(argv[i]);
+ if (dcname == NULL) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "defpwd", strlen("defpwd"))) {
+ options |= NETSETUP_PROVISION_USE_DEFAULT_PASSWORD;
+ }
+ if (strnequal(argv[i], "reuse", strlen("reuse"))) {
+ options |= NETSETUP_PROVISION_REUSE_ACCOUNT;
+ }
+ if (strnequal(argv[i], "savefile", strlen("savefile"))) {
+ savefile = get_string_param(argv[i]);
+ if (savefile == NULL) {
+ return -1;
+ }
+ }
+ if (strnequal(argv[i], "printblob", strlen("printblob"))) {
+ printblob = true;
+ }
+ }
+
+ if (domain == NULL) {
+ d_printf("Failed to provision computer account: %s\n",
+ libnetapi_errstr(W_ERROR_V(WERR_INVALID_DOMAINNAME)));
+ return -1;
+ }
+
+ if (machine_name == NULL) {
+ d_printf("Failed to provision computer account: %s\n",
+ libnetapi_errstr(W_ERROR_V(WERR_INVALID_COMPUTERNAME)));
+ return -1;
+ }
+
+ status = NetProvisionComputerAccount(domain,
+ machine_name,
+ machine_account_ou,
+ dcname,
+ options,
+ NULL,
+ 0,
+ &provision_text_data);
+ if (status != 0) {
+ d_printf("Failed to provision computer account: %s\n",
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ return status;
+ }
+
+ if (savefile != NULL) {
+
+ DATA_BLOB ucs2_blob, blob;
+ bool ok;
+
+ /*
+ * Windows produces and consumes UTF16/UCS2 encoded blobs
+ * so we also do it for compatibility. Someone may provision an
+ * account for a Windows machine with samba.
+ */
+ ok = push_reg_sz(c, &ucs2_blob, provision_text_data);
+ if (!ok) {
+ return -1;
+ }
+
+ /* Add the unicode BOM mark */
+ blob = data_blob_talloc(c, NULL, ucs2_blob.length + 2);
+ if (blob.data == NULL) {
+ d_printf("Failed to allocate blob: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ blob.data[0] = 0xff;
+ blob.data[1] = 0xfe;
+
+ memcpy(blob.data + 2, ucs2_blob.data, ucs2_blob.length);
+
+ ok = file_save(savefile, blob.data, blob.length);
+ if (!ok) {
+ d_printf("Failed to save %s: %s\n", savefile,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ d_printf("Successfully provisioned computer '%s' in domain '%s'\n",
+ machine_name, domain);
+
+ if (printblob) {
+ printf("%s\n", provision_text_data);
+ }
+
+ return 0;
+}
+
+static int net_offlinejoin_requestodj_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("\nnet offlinejoin requestodj [misc. options]\n"
+ "\tRequests offline domain join\n"));
+ d_printf(_("Valid options:\n"));
+ d_printf(_("\t-i\t\t\t\t\tRead ODJ data from STDIN\n"));
+ d_printf(_("\tloadfile=<FILENAME>\t\t\tFile that provides the ODJ data\n"));
+ /*d_printf(_("\tlocalos\t\t\t\t\tModify the local machine\n"));*/
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+int net_offlinejoin_requestodj(struct net_context *c,
+ int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ uint8_t *provision_bin_data = NULL;
+ size_t provision_bin_data_size = 0;
+ uint32_t options = NETSETUP_PROVISION_ONLINE_CALLER;
+ const char *windows_path = NULL;
+ int i;
+
+ if (c->display_usage) {
+ return net_offlinejoin_requestodj_usage(c, argc, argv);
+ }
+
+ /* process additional command line args */
+
+ for (i = 0; i < argc; i++) {
+
+ if (strnequal(argv[i], "loadfile", strlen("loadfile"))) {
+ const char *loadfile = NULL;
+
+ loadfile = get_string_param(argv[i]);
+ if (loadfile == NULL) {
+ return -1;
+ }
+
+ provision_bin_data =
+ (uint8_t *)file_load(loadfile,
+ &provision_bin_data_size,
+ 0,
+ c);
+ if (provision_bin_data == NULL) {
+ d_printf("Failed to read loadfile: %s\n",
+ loadfile);
+ return -1;
+ }
+ }
+#if 0
+ if (strnequal(argv[i], "localos", strlen("localos"))) {
+ options |= NETSETUP_PROVISION_ONLINE_CALLER;
+ }
+#endif
+ }
+
+ if (c->opt_stdin) {
+ if (isatty(STDIN_FILENO) == 1) {
+ d_fprintf(stderr,
+ "hint: stdin waiting for ODJ blob, end "
+ "with <crtl-D>.\n");
+ }
+ provision_bin_data =
+ (uint8_t *)fd_load(STDIN_FILENO,
+ &provision_bin_data_size, 0, c);
+ if (provision_bin_data == NULL) {
+ d_printf("Failed to read ODJ blob from stdin\n");
+ return -1;
+ }
+
+ /* Strip last newline */
+ if (provision_bin_data[provision_bin_data_size - 1] == '\n') {
+ provision_bin_data[provision_bin_data_size - 1] = '\0';
+ }
+ }
+
+ if (provision_bin_data == NULL || provision_bin_data_size == 0) {
+ d_printf("Please provide provision data either from file "
+ "(using loadfile parameter) or from stdin (-i)\n");
+ return -1;
+ }
+ if (provision_bin_data_size > UINT32_MAX) {
+ d_printf("provision binary data size too big: %zu\n",
+ provision_bin_data_size);
+ return -1;
+ }
+
+ status = NetRequestOfflineDomainJoin(provision_bin_data,
+ provision_bin_data_size,
+ options,
+ windows_path);
+ if (status != 0 && status != 0x00000a99) {
+ /* NERR_JoinPerformedMustRestart */
+ printf("Failed to call NetRequestOfflineDomainJoin: %s\n",
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ return -1;
+ }
+
+ d_printf("Successfully requested Offline Domain Join\n");
+
+ return 0;
+}
+
+static int net_offlinejoin_composeodj_usage(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ d_printf(_("\nnet offlinejoin composeodj [misc. options]\n"
+ "\tComposes offline domain join blob\n"));
+ d_printf(_("Valid options:\n"));
+ d_printf(_("\tdomain_sid=<SID>\t\t\tThe domain SID\n"));
+ d_printf(_("\tdomain_guid=<GUID>\t\t\tThe domain GUID\n"));
+ d_printf(_("\tforest_name=<NAME>\t\t\tThe forest name\n"));
+ d_printf(_("\tdomain_is_nt4\t\t\t\tThe domain not AD but NT4\n"));
+ d_printf(_("\tsavefile=<FILENAME>\t\t\tFile to store the ODJ data\n"));
+ d_printf(_("\tprintblob\t\t\t\tPrint the base64 encoded ODJ data on stdout\n"));
+ net_common_flags_usage(c, argc, argv);
+ d_printf(_("Example:\n"));
+ d_printf("\tnet offlinejoin composeodj --realm=<realm> "
+ "--workgroup=<domain> domain_sid=<sid> domain_guid=<guid> "
+ "forest_name=<name> -S <dc name> -I <dc address> "
+ "--password=<password> printblob\n");
+ return -1;
+}
+
+int net_offlinejoin_composeodj(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ NET_API_STATUS status;
+ const char *dns_domain_name = NULL;
+ const char *netbios_domain_name = NULL;
+ const char *machine_account_name = NULL;
+ const char *machine_account_password = NULL;
+ const char *domain_sid_str = NULL;
+ const char *domain_guid_str = NULL;
+ struct dom_sid domain_sid;
+ struct GUID domain_guid;
+ const char *forest_name = NULL;
+ const char *dc_name = NULL;
+ char dc_address[INET6_ADDRSTRLEN] = { 0 };
+ bool domain_is_ad = true;
+ const char *provision_text_data = NULL;
+ const char *savefile = NULL;
+ bool printblob = false;
+ enum credentials_obtained obtained;
+ bool ok;
+ NTSTATUS ntstatus;
+ int i;
+
+ if (c->display_usage || argc < 4) {
+ return net_offlinejoin_composeodj_usage(c, argc, argv);
+ }
+
+ dns_domain_name = cli_credentials_get_realm(creds);
+ netbios_domain_name = cli_credentials_get_domain(creds);
+
+ machine_account_name = cli_credentials_get_username_and_obtained(creds, &obtained);
+ if (obtained < CRED_CALLBACK_RESULT) {
+ const char *netbios_name = cli_credentials_get_workstation(creds);
+ cli_credentials_set_username(
+ creds,
+ talloc_asprintf(c, "%s$", netbios_name),
+ CRED_SPECIFIED);
+ }
+
+ machine_account_name = cli_credentials_get_username(creds);
+ machine_account_password = cli_credentials_get_password(creds);
+ dc_name = c->opt_host;
+
+ if (c->opt_have_ip) {
+ struct sockaddr_in *in4 = NULL;
+ struct sockaddr_in6 *in6 = NULL;
+ const char *p = NULL;
+
+ switch(c->opt_dest_ip.ss_family) {
+ case AF_INET:
+ in4 = (struct sockaddr_in *)&c->opt_dest_ip;
+ p = inet_ntop(AF_INET, &in4->sin_addr, dc_address, sizeof(dc_address));
+ break;
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *)&c->opt_dest_ip;
+ p = inet_ntop(AF_INET6, &in6->sin6_addr, dc_address, sizeof(dc_address));
+ break;
+ default:
+ d_printf("Unknown IP address family\n");
+ return -1;
+ }
+
+ if (p == NULL) {
+ d_fprintf(stderr, "Failed to parse IP address: %s\n", strerror(errno));
+ return -1;
+ }
+ }
+
+ /* process additional command line args */
+
+ for (i = 0; i < argc; i++) {
+ if (strnequal(argv[i], "domain_sid", strlen("domain_sid"))) {
+ domain_sid_str = get_string_param(argv[i]);
+ if (domain_sid_str == NULL) {
+ return -1;
+ }
+ }
+
+ if (strnequal(argv[i], "domain_guid", strlen("domain_guid"))) {
+ domain_guid_str = get_string_param(argv[i]);
+ if (domain_guid_str == NULL) {
+ return -1;
+ }
+ }
+
+ if (strnequal(argv[i], "forest_name", strlen("forest_name"))) {
+ forest_name = get_string_param(argv[i]);
+ if (forest_name == NULL) {
+ return -1;
+ }
+ }
+
+ if (strnequal(argv[i], "savefile", strlen("savefile"))) {
+ savefile = get_string_param(argv[i]);
+ if (savefile == NULL) {
+ return -1;
+ }
+ }
+
+ if (strnequal(argv[i], "printblob", strlen("printblob"))) {
+ printblob = true;
+ }
+
+ if (strnequal(argv[i], "domain_is_nt4", strlen("domain_is_nt4"))) {
+ domain_is_ad = false;
+ }
+ }
+
+ /* Check command line arguments */
+
+ if (savefile == NULL && !printblob) {
+ d_printf("Choose either save the blob to a file or print it\n");
+ return -1;
+ }
+
+ if (dns_domain_name == NULL) {
+ d_printf("Please provide a valid realm parameter (--realm)\n");
+ return -1;
+ }
+
+ if (netbios_domain_name == NULL) {
+ d_printf("Please provide a valid domain parameter (--workgroup)\n");
+ return -1;
+ }
+
+ if (dc_name == NULL) {
+ d_printf("Please provide a valid DC name parameter (-S)\n");
+ return -1;
+ }
+
+ if (strlen(dc_address) == 0) {
+ d_printf("Please provide a valid domain controller address parameter (-I)\n");
+ return -1;
+ }
+
+ if (machine_account_name == NULL) {
+ d_printf("Please provide a valid netbios name parameter\n");
+ return -1;
+ }
+
+ if (machine_account_password == NULL) {
+ d_printf("Please provide a valid password parameter\n");
+ return -1;
+ }
+
+ if (domain_sid_str == NULL) {
+ d_printf("Please provide a valid <domain_sid> parameter\n");
+ return -1;
+ }
+
+ if (domain_guid_str == NULL) {
+ d_printf("Please provide a valid <domain_guid> parameter\n");
+ return -1;
+ }
+
+ if (forest_name == NULL) {
+ d_printf("Please provide a valid <forest_name> parameter\n");
+ return -1;
+ }
+
+ ok = dom_sid_parse(domain_sid_str, &domain_sid);
+ if (!ok) {
+ d_fprintf(stderr, _("Failed to parse domain SID\n"));
+ return -1;
+ }
+
+ ntstatus = GUID_from_string(domain_guid_str, &domain_guid);
+ if (NT_STATUS_IS_ERR(ntstatus)) {
+ d_fprintf(stderr, _("Failed to parse domain GUID\n"));
+ return -1;
+ }
+
+ status = NetComposeOfflineDomainJoin(dns_domain_name,
+ netbios_domain_name,
+ (struct domsid *)&domain_sid,
+ &domain_guid,
+ forest_name,
+ machine_account_name,
+ machine_account_password,
+ dc_name,
+ dc_address,
+ domain_is_ad,
+ NULL,
+ 0,
+ &provision_text_data);
+ if (status != 0) {
+ d_printf("Failed to compose offline domain join blob: %s\n",
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ return status;
+ }
+
+ if (savefile != NULL) {
+ DATA_BLOB ucs2_blob, blob;
+
+ /*
+ * Windows produces and consumes UTF16/UCS2 encoded blobs
+ * so we also do it for compatibility. Someone may provision an
+ * account for a Windows machine with samba.
+ */
+ ok = push_reg_sz(c, &ucs2_blob, provision_text_data);
+ if (!ok) {
+ return -1;
+ }
+
+ /* Add the unicode BOM mark */
+ blob = data_blob_talloc(c, NULL, ucs2_blob.length + 2);
+ if (blob.data == NULL) {
+ d_printf("Failed to allocate blob: %s\n",
+ strerror(errno));
+ return -1;
+ }
+
+ blob.data[0] = 0xff;
+ blob.data[1] = 0xfe;
+
+ memcpy(blob.data + 2, ucs2_blob.data, ucs2_blob.length);
+
+ ok = file_save(savefile, blob.data, blob.length);
+ if (!ok) {
+ d_printf("Failed to save %s: %s\n", savefile,
+ strerror(errno));
+ return -1;
+ }
+ }
+
+ if (printblob) {
+ printf("%s\n", provision_text_data);
+ }
+
+ return 0;
+}
diff --git a/source3/utils/net_printing.c b/source3/utils/net_printing.c
new file mode 100644
index 0000000..04a3acc
--- /dev/null
+++ b/source3/utils/net_printing.c
@@ -0,0 +1,592 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Local printing tdb migration interface
+
+ Copyright (C) Guenther Deschner 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "utils/net.h"
+#include "rpc_client/rpc_client.h"
+#include "rpc_client/cli_pipe.h"
+#include "librpc/gen_ndr/ndr_ntprinting.h"
+#include "librpc/gen_ndr/ndr_spoolss.h"
+#include "../libcli/security/security.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_winreg.h"
+#include "util_tdb.h"
+#include "printing/nt_printing_migrate.h"
+#include "lib/param/param.h"
+
+#define FORMS_PREFIX "FORMS/"
+#define FORMS_PREFIX_LEN 6
+#define DRIVERS_PREFIX "DRIVERS/"
+#define DRIVERS_PREFIX_LEN 8
+#define PRINTERS_PREFIX "PRINTERS/"
+#define PRINTERS_PREFIX_LEN 9
+#define SECDESC_PREFIX "SECDESC/"
+#define SECDESC_PREFIX_LEN 8
+
+#define ARG_ENCODING "encoding="
+
+struct printing_opts {
+ const char *encoding;
+ const char *tdb;
+};
+
+static NTSTATUS printing_parse_args(TALLOC_CTX *mem_ctx,
+ struct printing_opts **popts,
+ int argc, const char **argv)
+{
+ size_t c;
+ struct printing_opts *o;
+
+ if (argc == 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ o = talloc_zero(mem_ctx, struct printing_opts);
+ if (o == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ for (c = 0; c < argc; c++) {
+ if (strnequal(argv[c], ARG_ENCODING, sizeof(ARG_ENCODING) - 1)) {
+ o->encoding = talloc_strdup(o,
+ argv[c] + sizeof(ARG_ENCODING) - 1);
+ if (o->encoding == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ o->tdb = talloc_strdup(o, argv[c]);
+ if (o->tdb == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ }
+
+ *popts = o;
+ return NT_STATUS_OK;
+}
+
+static void dump_form(TALLOC_CTX *mem_ctx,
+ const char *key_name,
+ unsigned char *data,
+ size_t length)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ char *s;
+ struct ntprinting_form r;
+
+ printf("found form: %s\n", key_name);
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_ntprinting_form);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_fprintf(stderr, _("form pull failed: %s\n"),
+ ndr_errstr(ndr_err));
+ return;
+ }
+
+ s = NDR_PRINT_STRUCT_STRING(mem_ctx, ntprinting_form, &r);
+ if (s) {
+ printf("%s\n", s);
+ }
+}
+
+static void dump_driver(TALLOC_CTX *mem_ctx,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ char *s;
+ struct ntprinting_driver r;
+
+ printf("found driver: %s\n", key_name);
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ if (do_string_conversion) {
+ r.string_flags = LIBNDR_FLAG_STR_ASCII;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_ntprinting_driver);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_fprintf(stderr, _("driver pull failed: %s\n"),
+ ndr_errstr(ndr_err));
+ return;
+ }
+
+ s = NDR_PRINT_STRUCT_STRING(mem_ctx, ntprinting_driver, &r);
+ if (s) {
+ printf("%s\n", s);
+ }
+}
+
+static void dump_printer(TALLOC_CTX *mem_ctx,
+ const char *key_name,
+ unsigned char *data,
+ size_t length,
+ bool do_string_conversion)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ char *s;
+ struct ntprinting_printer r;
+
+ printf("found printer: %s\n", key_name);
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ if (do_string_conversion) {
+ r.info.string_flags = LIBNDR_FLAG_STR_ASCII;
+ }
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_ntprinting_printer);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_fprintf(stderr, _("printer pull failed: %s\n"),
+ ndr_errstr(ndr_err));
+ return;
+ }
+
+ s = NDR_PRINT_STRUCT_STRING(mem_ctx, ntprinting_printer, &r);
+ if (s) {
+ printf("%s\n", s);
+ }
+}
+
+static void dump_sd(TALLOC_CTX *mem_ctx,
+ const char *key_name,
+ unsigned char *data,
+ size_t length)
+{
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ char *s;
+ struct sec_desc_buf r;
+
+ printf("found security descriptor: %s\n", key_name);
+
+ blob = data_blob_const(data, length);
+
+ ZERO_STRUCT(r);
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &r,
+ (ndr_pull_flags_fn_t)ndr_pull_sec_desc_buf);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_fprintf(stderr, _("security descriptor pull failed: %s\n"),
+ ndr_errstr(ndr_err));
+ return;
+ }
+
+ s = NDR_PRINT_STRUCT_STRING(mem_ctx, sec_desc_buf, &r);
+ if (s) {
+ printf("%s\n", s);
+ }
+}
+
+
+static int net_printing_dump(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ TDB_CONTEXT *tdb;
+ TDB_DATA kbuf, newkey, dbuf;
+ struct printing_opts *o;
+ const char *save_dos_charset = lp_dos_charset();
+ bool do_string_conversion = false;
+ NTSTATUS status;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf( "%s\n"
+ "net printing dump [options] <file.tdb>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Dump formatted printer information of the tdb."));
+ d_printf(_("Valid options:\n"));
+ d_printf(_(" encoding=<CP> Set the Code Page of the tdb file.\n"
+ " See iconv -l for the list of CP values\n"
+ " (CP1252 is Western latin1, CP1251 is Cyrillic).\n"));
+ goto done;
+ }
+
+ status = printing_parse_args(ctx, &o, argc, argv);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("failed to parse arguments\n"));
+ goto done;
+ }
+
+ tdb = tdb_open_log(o->tdb, 0, TDB_DEFAULT, O_RDONLY, 0600);
+ if (!tdb) {
+ d_fprintf(stderr, _("failed to open tdb file: %s\n"), o->tdb);
+ goto done;
+ }
+
+ if (o->encoding != NULL) {
+ lpcfg_set_cmdline(c->lp_ctx, "dos charset", o->encoding);
+ d_fprintf(stderr, _("do string conversion from %s to %s\n"),
+ lp_dos_charset(), lp_unix_charset());
+ do_string_conversion = true;
+ }
+
+ for (kbuf = tdb_firstkey(tdb);
+ kbuf.dptr;
+ newkey = tdb_nextkey(tdb, kbuf), free(kbuf.dptr), kbuf=newkey)
+ {
+ int cmp;
+
+ dbuf = tdb_fetch(tdb, kbuf);
+ if (!dbuf.dptr) {
+ continue;
+ }
+
+ cmp = strncmp((const char *)kbuf.dptr,
+ FORMS_PREFIX,
+ FORMS_PREFIX_LEN);
+ if (cmp == 0) {
+ char *key_name = NULL;
+ size_t converted_size = 0;
+ bool ok;
+
+ ok = pull_ascii_talloc(ctx,
+ &key_name,
+ (const char *) kbuf.dptr + strlen(FORMS_PREFIX),
+ &converted_size);
+ if (!ok) {
+ continue;
+ }
+
+ dump_form(ctx, key_name, dbuf.dptr, dbuf.dsize);
+ TALLOC_FREE(key_name);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+
+ cmp = strncmp((const char *)kbuf.dptr,
+ DRIVERS_PREFIX,
+ DRIVERS_PREFIX_LEN);
+ if (cmp == 0) {
+ char *key_name = NULL;
+ size_t converted_size = 0;
+ bool ok;
+
+ ok = pull_ascii_talloc(ctx,
+ &key_name,
+ (const char *) kbuf.dptr + strlen(DRIVERS_PREFIX),
+ &converted_size);
+ if (!ok) {
+ continue;
+ }
+
+ dump_driver(ctx,
+ key_name,
+ dbuf.dptr,
+ dbuf.dsize,
+ do_string_conversion);
+ TALLOC_FREE(key_name);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+
+ cmp = strncmp((const char *)kbuf.dptr,
+ PRINTERS_PREFIX,
+ PRINTERS_PREFIX_LEN);
+ if (cmp == 0) {
+ char *key_name = NULL;
+ size_t converted_size = 0;
+ bool ok;
+
+ ok = pull_ascii_talloc(ctx,
+ &key_name,
+ (const char *) kbuf.dptr + strlen(PRINTERS_PREFIX),
+ &converted_size);
+ if (!ok) {
+ continue;
+ }
+
+ dump_printer(ctx,
+ key_name,
+ dbuf.dptr,
+ dbuf.dsize,
+ do_string_conversion);
+ TALLOC_FREE(key_name);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+
+ cmp = strncmp((const char *)kbuf.dptr,
+ SECDESC_PREFIX,
+ SECDESC_PREFIX_LEN);
+ if (cmp == 0) {
+ dump_sd(ctx, (const char *)kbuf.dptr+strlen(SECDESC_PREFIX), dbuf.dptr, dbuf.dsize);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+
+ }
+
+ ret = 0;
+
+ done:
+ lpcfg_set_cmdline(c->lp_ctx, "dos charset", save_dos_charset);
+ talloc_free(ctx);
+ return ret;
+}
+
+static NTSTATUS printing_migrate_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *winreg_pipe,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct printing_opts *o;
+ TALLOC_CTX *tmp_ctx;
+ TDB_CONTEXT *tdb;
+ TDB_DATA kbuf, newkey, dbuf;
+ NTSTATUS status;
+ const char *save_dos_charset = lp_dos_charset();
+ bool do_string_conversion = false;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = printing_parse_args(tmp_ctx, &o, argc, argv);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("failed to parse arguments\n"));
+ goto done;
+ }
+
+ tdb = tdb_open_log(o->tdb, 0, TDB_DEFAULT, O_RDONLY, 0600);
+ if (tdb == NULL) {
+ d_fprintf(stderr, _("failed to open tdb file: %s\n"), o->tdb);
+ status = NT_STATUS_NO_SUCH_FILE;
+ goto done;
+ }
+
+ if (o->encoding != NULL) {
+ lpcfg_set_cmdline(c->lp_ctx, "dos charset", o->encoding);
+ d_fprintf(stderr, _("do string conversion from %s to %s\n"),
+ lp_dos_charset(), lp_unix_charset());
+ do_string_conversion = true;
+ }
+
+ for (kbuf = tdb_firstkey(tdb);
+ kbuf.dptr;
+ newkey = tdb_nextkey(tdb, kbuf), free(kbuf.dptr), kbuf = newkey)
+ {
+ int cmp;
+
+ dbuf = tdb_fetch(tdb, kbuf);
+ if (!dbuf.dptr) {
+ continue;
+ }
+
+ cmp = strncmp((const char *) kbuf.dptr,
+ FORMS_PREFIX,
+ FORMS_PREFIX_LEN);
+ if (cmp == 0) {
+ char *key_name = NULL;
+ size_t converted_size = 0;
+ bool ok;
+
+ ok = pull_ascii_talloc(tmp_ctx,
+ &key_name,
+ (const char *) kbuf.dptr + strlen(FORMS_PREFIX),
+ &converted_size);
+ if (!ok) {
+ continue;
+ }
+
+ printing_tdb_migrate_form(tmp_ctx,
+ winreg_pipe,
+ key_name,
+ dbuf.dptr,
+ dbuf.dsize);
+ TALLOC_FREE(key_name);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+
+ cmp = strncmp((const char *) kbuf.dptr,
+ DRIVERS_PREFIX,
+ DRIVERS_PREFIX_LEN);
+ if (cmp == 0) {
+ char *key_name = NULL;
+ size_t converted_size = 0;
+ bool ok;
+
+ ok = pull_ascii_talloc(tmp_ctx,
+ &key_name,
+ (const char *) kbuf.dptr + strlen(DRIVERS_PREFIX),
+ &converted_size);
+ if (!ok) {
+ continue;
+ }
+
+ printing_tdb_migrate_driver(tmp_ctx,
+ winreg_pipe,
+ key_name,
+ dbuf.dptr,
+ dbuf.dsize,
+ do_string_conversion);
+ TALLOC_FREE(key_name);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+
+ cmp = strncmp((const char *) kbuf.dptr,
+ PRINTERS_PREFIX,
+ PRINTERS_PREFIX_LEN);
+ if (cmp == 0) {
+ char *key_name = NULL;
+ size_t converted_size = 0;
+ bool ok;
+
+ ok = pull_ascii_talloc(tmp_ctx,
+ &key_name,
+ (const char *) kbuf.dptr + strlen(PRINTERS_PREFIX),
+ &converted_size);
+ if (!ok) {
+ continue;
+ }
+
+ printing_tdb_migrate_printer(tmp_ctx,
+ winreg_pipe,
+ key_name,
+ dbuf.dptr,
+ dbuf.dsize,
+ do_string_conversion);
+ TALLOC_FREE(key_name);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+ SAFE_FREE(dbuf.dptr);
+ }
+
+ for (kbuf = tdb_firstkey(tdb);
+ kbuf.dptr;
+ newkey = tdb_nextkey(tdb, kbuf), free(kbuf.dptr), kbuf = newkey)
+ {
+ dbuf = tdb_fetch(tdb, kbuf);
+ if (!dbuf.dptr) {
+ continue;
+ }
+
+ if (strncmp((const char *) kbuf.dptr, SECDESC_PREFIX, strlen(SECDESC_PREFIX)) == 0) {
+ printing_tdb_migrate_secdesc(tmp_ctx,
+ winreg_pipe,
+ (const char *) kbuf.dptr + strlen(SECDESC_PREFIX),
+ dbuf.dptr,
+ dbuf.dsize);
+ SAFE_FREE(dbuf.dptr);
+ continue;
+ }
+ SAFE_FREE(dbuf.dptr);
+
+ }
+
+ status = NT_STATUS_OK;
+
+ done:
+ lpcfg_set_cmdline(c->lp_ctx, "dos charset", save_dos_charset);
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static int net_printing_migrate(struct net_context *c,
+ int argc,
+ const char **argv)
+{
+ if (argc < 1 || c->display_usage) {
+ d_printf( "%s\n"
+ "net printing migrate [options] <file.tdb>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate tdb printing files to new storage"));
+ d_printf(_("Valid options:\n"));
+ d_printf(_(" encoding=<CP> Set the Code Page of the tdb file.\n"
+ " See iconv -l for the list of CP values\n"
+ " (CP1252 is Western latin1, CP1251 is Cyrillic).\n"));
+ return 0;
+ }
+
+ return run_rpc_command(c,
+ NULL,
+ &ndr_table_winreg,
+ 0,
+ printing_migrate_internal,
+ argc,
+ argv);
+}
+/**
+ * 'net printing' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_printing(struct net_context *c, int argc, const char **argv)
+{
+ int ret = -1;
+
+ struct functable func[] = {
+ {
+ "dump",
+ net_printing_dump,
+ NET_TRANSPORT_LOCAL,
+ N_("Dump printer databases"),
+ N_("net printing dump\n"
+ " Dump tdb printing file")
+ },
+
+ {
+ "migrate",
+ net_printing_migrate,
+ NET_TRANSPORT_LOCAL | NET_TRANSPORT_RPC,
+ N_("Migrate printer databases"),
+ N_("net printing migrate\n"
+ " Migrate tdb printing files to new storage")
+ },
+
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ ret = net_run_function(c, argc, argv, "net printing", func);
+
+ return ret;
+}
diff --git a/source3/utils/net_proto.h b/source3/utils/net_proto.h
new file mode 100644
index 0000000..ccfa89a
--- /dev/null
+++ b/source3/utils/net_proto.h
@@ -0,0 +1,488 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * collected prototypes header
+ *
+ * frozen from "make proto" in May 2008
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NET_PROTO_H_
+#define _NET_PROTO_H_
+
+#include "ads.h"
+#include "libads/ads_status.h"
+#include "librpc/gen_ndr/libnet_join.h"
+
+/* The following definitions come from utils/net.c */
+
+enum netr_SchannelType get_sec_channel_type(const char *param);
+
+/* The following definitions come from utils/net_ads.c */
+struct ads_struct;
+ADS_STATUS ads_startup(struct net_context *c,
+ bool only_own_domain,
+ TALLOC_CTX *mem_ctx,
+ struct ads_struct **ads);
+ADS_STATUS ads_startup_nobind(struct net_context *c,
+ bool only_own_domain,
+ TALLOC_CTX *mem_ctx,
+ struct ads_struct **ads);
+int net_ads_check_our_domain(struct net_context *c);
+int net_ads_check(struct net_context *c);
+int net_ads_user(struct net_context *c, int argc, const char **argv);
+int net_ads_group(struct net_context *c, int argc, const char **argv);
+int net_ads_testjoin(struct net_context *c, int argc, const char **argv);
+int net_ads_join(struct net_context *c, int argc, const char **argv);
+int net_ads_printer_usage(struct net_context *c, int argc, const char **argv);
+int net_ads_changetrustpw(struct net_context *c, int argc, const char **argv);
+int net_ads_keytab(struct net_context *c, int argc, const char **argv);
+int net_ads_kerberos(struct net_context *c, int argc, const char **argv);
+int net_ads_setspn(struct net_context *c, int argc, const char **argv);
+int net_ads(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_ads_join_dns.c */
+void use_in_memory_ccache(void);
+NTSTATUS net_update_dns_ext(struct net_context *c,
+ TALLOC_CTX *mem_ctx, ADS_STRUCT *ads,
+ const char *hostname,
+ struct sockaddr_storage *iplist,
+ int num_addrs, bool remove_host);
+void net_ads_join_dns_updates(struct net_context *c, TALLOC_CTX *ctx, struct libnet_JoinCtx *r);
+
+/* The following definitions come from utils/net_ads_gpo.c */
+
+int net_ads_gpo(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_cache.c */
+
+int net_cache(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_conf.c */
+
+int net_conf(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_dns.c */
+
+int get_my_ip_address( struct sockaddr_storage **pp_ss );
+
+/* The following definitions come from utils/net_dom.c */
+
+int net_dom_usage(struct net_context *c, int argc, const char **argv);
+int net_dom(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_file.c */
+
+int net_file_usage(struct net_context *c, int argc, const char **argv);
+int net_file(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_group.c */
+
+int net_group_usage(struct net_context *c, int argc, const char **argv);
+int net_group(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_groupmap.c */
+
+int net_groupmap_usage(struct net_context *c, int argc, const char **argv);
+int net_groupmap(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_help.c */
+
+int net_help(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_idmap.c */
+
+int net_idmap(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_join.c */
+
+int net_join_usage(struct net_context *c, int argc, const char **argv);
+int net_join(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from source3/utils/net_offlinejoin.c */
+
+int net_offlinejoin_usage(struct net_context *c, int argc, const char **argv);
+int net_offlinejoin(struct net_context *c, int argc, const char **argv);
+int net_offlinejoin_provision(struct net_context *c,
+ int argc, const char **argv);
+int net_offlinejoin_requestodj(struct net_context *c,
+ int argc, const char **argv);
+int net_offlinejoin_composeodj(struct net_context *c,
+ int argc, const char **argv);
+
+/* The following definitions come from utils/net_lookup.c */
+
+int net_lookup_usage(struct net_context *c, int argc, const char **argv);
+int net_lookup(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rap.c */
+
+int net_rap_file_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_file(struct net_context *c, int argc, const char **argv);
+int net_rap_share_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_share(struct net_context *c, int argc, const char **argv);
+int net_rap_session_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_session(struct net_context *c, int argc, const char **argv);
+int net_rap_server_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_server(struct net_context *c, int argc, const char **argv);
+int net_rap_domain_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_domain(struct net_context *c, int argc, const char **argv);
+int net_rap_printq_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_printq(struct net_context *c, int argc, const char **argv);
+int net_rap_user(struct net_context *c, int argc, const char **argv);
+int net_rap_group_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_group(struct net_context *c, int argc, const char **argv);
+int net_rap_groupmember_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_groupmember(struct net_context *c, int argc, const char **argv);
+int net_rap_validate_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_validate(struct net_context *c, int argc, const char **argv);
+int net_rap_service_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_service(struct net_context *c, int argc, const char **argv);
+int net_rap_password_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_password(struct net_context *c, int argc, const char **argv);
+int net_rap_admin_usage(struct net_context *c, int argc, const char **argv);
+int net_rap_admin(struct net_context *c, int argc, const char **argv);
+int net_rap(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_registry.c */
+
+int net_registry(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc.c */
+
+NTSTATUS net_get_remote_domain_sid(struct cli_state *cli, TALLOC_CTX *mem_ctx,
+ struct dom_sid **domain_sid,
+ const char **domain_name);
+int run_rpc_command(struct net_context *c,
+ struct cli_state *cli_arg,
+ const struct ndr_interface_table *table,
+ int conn_flags,
+ rpc_command_fn fn,
+ int argc,
+ const char **argv);
+int net_rpc_changetrustpw(struct net_context *c, int argc, const char **argv);
+int net_rpc_testjoin(struct net_context *c, int argc, const char **argv);
+int net_rpc_join(struct net_context *c, int argc, const char **argv);
+NTSTATUS rpc_info_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+int net_rpc_info(struct net_context *c, int argc, const char **argv);
+int net_rpc_getsid(struct net_context *c, int argc, const char **argv);
+int net_rpc_user(struct net_context *c, int argc, const char **argv);
+struct rpc_sh_cmd *net_rpc_user_edit_cmds(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx);
+struct rpc_sh_cmd *net_rpc_user_cmds(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx);
+int net_rpc_group(struct net_context *c, int argc, const char **argv);
+bool copy_top_level_perms(struct net_context *c,
+ struct copy_clistate *cp_clistate,
+ const char *sharename);
+int net_usersidlist(struct net_context *c, int argc, const char **argv);
+int net_usersidlist_usage(struct net_context *c, int argc, const char **argv);
+int net_rpc_share(struct net_context *c, int argc, const char **argv);
+struct rpc_sh_cmd *net_rpc_share_cmds(struct net_context *c, TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx);
+int net_rpc_file(struct net_context *c, int argc, const char **argv);
+NTSTATUS rpc_init_shutdown_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_reg_shutdown_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+bool net_rpc_check(struct net_context *c, unsigned flags);
+int rpc_printer_migrate(struct net_context *c, int argc, const char **argv);
+int rpc_printer_usage(struct net_context *c, int argc, const char **argv);
+int net_rpc_printer(struct net_context *c, int argc, const char **argv);
+int net_rpc(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc_audit.c */
+
+int net_rpc_audit(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc_printer.c */
+
+NTSTATUS net_copy_fileattr(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct cli_state *cli_share_src,
+ struct cli_state *cli_share_dst,
+ const char *src_name, const char *dst_name,
+ bool copy_acls, bool copy_attrs,
+ bool copy_timestamps, bool is_file);
+NTSTATUS net_copy_file(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct cli_state *cli_share_src,
+ struct cli_state *cli_share_dst,
+ const char *src_name, const char *dst_name,
+ bool copy_acls, bool copy_attrs,
+ bool copy_timestamps, bool is_file);
+NTSTATUS rpc_printer_list_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_driver_list_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_publish_publish_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_publish_unpublish_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_publish_update_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_publish_list_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_migrate_security_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_migrate_forms_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_migrate_drivers_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_migrate_printers_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+NTSTATUS rpc_printer_migrate_settings_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv);
+
+/* The following definitions come from utils/net_rpc_registry.c */
+
+int net_rpc_registry(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc_rights.c */
+
+int net_rpc_rights(struct net_context *c, int argc, const char **argv);
+struct rpc_sh_cmd *net_rpc_rights_cmds(struct net_context *c, TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx);
+
+/* The following definitions come from utils/net_rpc_samsync.c */
+
+int rpc_vampire_usage(struct net_context *c, int argc, const char **argv);
+int rpc_vampire_passdb(struct net_context *c, int argc, const char **argv);
+int rpc_vampire_keytab(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc_service.c */
+
+const char *svc_status_string( uint32_t state );
+int net_rpc_service(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc_sh_acct.c */
+
+struct rpc_sh_cmd *net_rpc_acct_cmds(struct net_context *c, TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx);
+
+/* The following definitions come from utils/net_rpc_shell.c */
+
+int net_rpc_shell(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_sam.c */
+
+int net_sam(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_share.c */
+
+int net_share_usage(struct net_context *c, int argc, const char **argv);
+int net_share(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_status.c */
+
+int net_status_usage(struct net_context *c, int argc, const char **argv);
+int net_status(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_time.c */
+
+int net_time_usage(struct net_context *c, int argc, const char **argv);
+int net_time(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_user.c */
+
+int net_user_usage(struct net_context *c, int argc, const char **argv);
+int net_user(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_usershare.c */
+
+int net_usershare_usage(struct net_context *c, int argc, const char **argv);
+int net_usershare_help(struct net_context *c, int argc, const char **argv);
+int net_usershare(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_eventlog.c */
+
+int net_eventlog(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_printing.c */
+
+int net_printing(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_serverid.c */
+
+int net_serverid(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_util.c */
+
+NTSTATUS net_rpc_lookup_name(struct net_context *c,
+ TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ const char *name, const char **ret_domain,
+ const char **ret_name, struct dom_sid *ret_sid,
+ enum lsa_SidType *ret_type);
+NTSTATUS connect_to_service(struct net_context *c,
+ struct cli_state **cli_ctx,
+ const struct sockaddr_storage *server_ss,
+ const char *server_name,
+ const char *service_name,
+ const char *service_type);
+NTSTATUS connect_to_ipc(struct net_context *c,
+ struct cli_state **cli_ctx,
+ const struct sockaddr_storage *server_ss,
+ const char *server_name);
+NTSTATUS connect_to_ipc_anonymous(struct net_context *c,
+ struct cli_state **cli_ctx,
+ const struct sockaddr_storage *server_ss,
+ const char *server_name);
+NTSTATUS connect_dst_pipe(struct net_context *c, struct cli_state **cli_dst,
+ struct rpc_pipe_client **pp_pipe_hnd,
+ const struct ndr_interface_table *table);
+int net_use_krb_machine_account(struct net_context *c);
+bool net_find_server(struct net_context *c,
+ const char *domain,
+ unsigned flags,
+ struct sockaddr_storage *server_ss,
+ char **server_name);
+bool net_find_pdc(struct sockaddr_storage *server_ss,
+ fstring server_name,
+ const char *domain_name);
+NTSTATUS net_make_ipc_connection(struct net_context *c, unsigned flags,
+ struct cli_state **pcli);
+NTSTATUS net_make_ipc_connection_ex(struct net_context *c ,const char *domain,
+ const char *server,
+ const struct sockaddr_storage *pss,
+ unsigned flags, struct cli_state **pcli);
+const char *net_prompt_pass(struct net_context *c, const char *user);
+int net_run_function(struct net_context *c, int argc, const char **argv,
+ const char *whoami, struct functable *table);
+void net_display_usage_from_functable(struct functable *table);
+
+void net_warn_member_options(void);
+
+const char *net_share_type_str(int num_type);
+
+NTSTATUS net_scan_dc(struct net_context *c,
+ struct cli_state *cli,
+ struct net_dc_info *dc_info);
+
+/* The following definitions come from utils/netlookup.c */
+
+NTSTATUS net_lookup_name_from_sid(struct net_context *c,
+ TALLOC_CTX *ctx,
+ struct dom_sid *psid,
+ const char **ppdomain,
+ const char **ppname);
+NTSTATUS net_lookup_sid_from_name(struct net_context *c, TALLOC_CTX *ctx,
+ const char *full_name, struct dom_sid *pret_sid);
+
+/* The following definitions come from utils/net_g_lock.c */
+int net_g_lock(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc_trust.c */
+int net_rpc_trust(struct net_context *c, int argc, const char **argv);
+
+/* The following definitions come from utils/net_rpc_conf.c */
+int net_rpc_conf(struct net_context *c, int argc, const char **argv);
+
+int net_notify(struct net_context *c, int argc, const char **argv);
+
+int net_tdb(struct net_context *c, int argc, const char **argv);
+
+int net_vfs(struct net_context *c, int argc, const char **argv);
+
+int net_witness(struct net_context *c, int argc, const char **argv);
+
+#endif /* _NET_PROTO_H_ */
diff --git a/source3/utils/net_rap.c b/source3/utils/net_rap.c
new file mode 100644
index 0000000..9818623
--- /dev/null
+++ b/source3/utils/net_rap.c
@@ -0,0 +1,1386 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2001 Steve French (sfrench@us.ibm.com)
+ Copyright (C) 2001 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2001 Andrew Bartlett (abartlet@samba.org)
+
+ Originally written by Steve and Jim. Largely rewritten by tridge in
+ November 2001.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "../librpc/gen_ndr/rap.h"
+#include "../librpc/gen_ndr/svcctl.h"
+#include "utils/net.h"
+#include "libsmb/libsmb.h"
+#include "clirap2.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/* The following messages were for error checking that is not properly
+ reported at the moment. Which should be reinstated? */
+#define ERRMSG_TARGET_WG_NOT_VALID "\nTarget workgroup option not valid "\
+ "except on net rap server command, ignored"
+#define ERRMSG_INVALID_HELP_OPTION "\nInvalid help option\n"
+
+#define ERRMSG_BOTH_SERVER_IPADDRESS "\nTarget server and IP address both "\
+ "specified. Do not set both at the same time. The target IP address was used\n"
+
+static int errmsg_not_implemented(void)
+{
+ d_printf(_("\nNot implemented\n"));
+ return 0;
+}
+
+int net_rap_file_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_file_usage(c, argc, argv);
+}
+
+/***************************************************************************
+ list info on an open file
+***************************************************************************/
+static void file_fn(const char * pPath, const char * pUser, uint16_t perms,
+ uint16_t locks, uint32_t id)
+{
+ d_printf("%-7.1d %-20.20s 0x%-4.2x %-6.1d %s\n",
+ id, pUser, perms, locks, pPath);
+}
+
+static void one_file_fn(const char *pPath, const char *pUser, uint16_t perms,
+ uint16_t locks, uint32_t id)
+{
+ d_printf(_("File ID %d\n"
+ "User name %s\n"
+ "Locks 0x%-4.2x\n"
+ "Path %s\n"
+ "Permissions 0x%x\n"),
+ id, pUser, locks, pPath, perms);
+}
+
+
+static int rap_file_close(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ if (argc == 0 || c->display_usage) {
+ return net_rap_file_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetFileClose(cli, atoi(argv[0]));
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_file_info(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ if (argc == 0 || c->display_usage)
+ return net_rap_file_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetFileGetInfo(cli, atoi(argv[0]), one_file_fn);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_file_user(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (argc == 0 || c->display_usage)
+ return net_rap_file_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ /* list open files */
+
+ d_printf(_("\nEnumerating open files on remote server:\n\n"
+ "\nFileId Opened by Perms Locks Path \n"
+ "------ --------- ----- ----- ---- \n"));
+ ret = cli_NetFileEnum(cli, argv[0], NULL, file_fn);
+
+ if (ret == -1)
+ d_printf(_("\nOperation not supported by server!\n\n"));
+
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_file(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "close",
+ rap_file_close,
+ NET_TRANSPORT_RAP,
+ N_("Close specified file on server"),
+ N_("net rap file close\n"
+ " Close specified file on server")
+ },
+ {
+ "user",
+ rap_file_user,
+ NET_TRANSPORT_RAP,
+ N_("List all files opened by username"),
+ N_("net rap file user\n"
+ " List all files opened by username")
+ },
+ {
+ "info",
+ rap_file_info,
+ NET_TRANSPORT_RAP,
+ N_("Display info about an opened file"),
+ N_("net rap file info\n"
+ " Display info about an opened file")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ struct cli_state *cli;
+ int ret;
+
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rap file\n"
+ " List all open files on rempte "
+ "server\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ /* list open files */
+
+ d_printf(_("\nEnumerating open files on remote server:\n\n"
+ "\nFileId Opened by Perms Locks Path\n"
+ "------ --------- ----- ----- ----\n"));
+ ret = cli_NetFileEnum(cli, NULL, NULL, file_fn);
+
+ if (ret == -1)
+ d_printf(_("\nOperation not supported by server!\n\n"));
+
+ cli_shutdown(cli);
+ return ret;
+ }
+
+ return net_run_function(c, argc, argv, "net rap file", func);
+}
+
+int net_rap_share_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_share_usage(c, argc, argv);
+}
+
+static void long_share_fn(const char *share_name, uint32_t type,
+ const char *comment, void *state)
+{
+ d_printf("%-12s %-8.8s %-50s\n",
+ share_name, net_share_type_str(type), comment);
+}
+
+static void share_fn(const char *share_name, uint32_t type,
+ const char *comment, void *state)
+{
+ d_printf("%s\n", share_name);
+}
+
+static int rap_share_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (argc == 0 || c->display_usage) {
+ return net_rap_share_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetShareDelete(cli, argv[0]);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_share_add(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ struct rap_share_info_2 sinfo;
+ char *p;
+ char *sharename;
+
+ if (argc == 0 || c->display_usage) {
+ return net_rap_share_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ sharename = SMB_STRDUP(argv[0]);
+ p = strchr(sharename, '=');
+ if (p == NULL) {
+ d_printf(_("Server path not specified\n"));
+ SAFE_FREE(sharename);
+ return net_rap_share_usage(c, argc, argv);
+ }
+ *p = 0;
+ strlcpy((char *)sinfo.share_name, sharename, sizeof(sinfo.share_name));
+ sinfo.reserved1 = '\0';
+ sinfo.share_type = 0;
+ sinfo.comment = c->opt_comment ? smb_xstrdup(c->opt_comment) : "";
+ sinfo.perms = 0;
+ sinfo.maximum_users = c->opt_maxusers;
+ sinfo.active_users = 0;
+ sinfo.path = p+1;
+ memset(sinfo.password, '\0', sizeof(sinfo.password));
+ sinfo.reserved2 = '\0';
+
+ ret = cli_NetShareAdd(cli, &sinfo);
+ cli_shutdown(cli);
+ SAFE_FREE(sharename);
+ return ret;
+}
+
+
+int net_rap_share(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "delete",
+ rap_share_delete,
+ NET_TRANSPORT_RAP,
+ N_("Delete a share from server"),
+ N_("net rap share delete\n"
+ " Delete a share from server")
+ },
+ {
+ "close",
+ rap_share_delete,
+ NET_TRANSPORT_RAP,
+ N_("Delete a share from server"),
+ N_("net rap share close\n"
+ " Delete a share from server\n"
+ " Alias for net rap share delete")
+ },
+ {
+ "add",
+ rap_share_add,
+ NET_TRANSPORT_RAP,
+ N_("Add a share to server"),
+ N_("net rap share add\n"
+ " Add a share to server")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ struct cli_state *cli;
+ int ret;
+
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rap share\n"
+ " List all shares on remote server\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ if (c->opt_long_list_entries) {
+ d_printf(_(
+ "\nEnumerating shared resources (exports) on remote server:\n\n"
+ "\nShare name Type Description\n"
+ "---------- ---- -----------\n"));
+ ret = cli_RNetShareEnum(cli, long_share_fn, NULL);
+ } else {
+ ret = cli_RNetShareEnum(cli, share_fn, NULL);
+ }
+ cli_shutdown(cli);
+ return ret;
+ }
+
+ return net_run_function(c, argc, argv, "net rap share", func);
+}
+
+int net_rap_session_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "\nnet rap session [misc. options] [targets]"
+ "\n\tenumerates all active SMB/CIFS sessions on target server\n"));
+ d_printf(_(
+ "\nnet rap session DELETE <client_name> [misc. options] [targets] \n"
+ "\tor"
+ "\nnet rap session CLOSE <client_name> [misc. options] [targets]"
+ "\n\tDeletes (closes) a session from specified client to server\n"));
+ d_printf(_(
+ "\nnet rap session INFO <client_name>"
+ "\n\tEnumerates all open files in specified session\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+static void list_sessions_func(char *wsname, char *username, uint16_t conns,
+ uint16_t opens, uint16_t users, uint32_t sess_time,
+ uint32_t idle_time, uint32_t user_flags, char *clitype)
+{
+ int hrs = idle_time / 3600;
+ int min = (idle_time / 60) % 60;
+ int sec = idle_time % 60;
+
+ d_printf("\\\\%-18.18s %-20.20s %-18.18s %5d %2.2d:%2.2d:%2.2d\n",
+ wsname, username, clitype, opens, hrs, min, sec);
+}
+
+static void display_session_func(const char *wsname, const char *username,
+ uint16_t conns, uint16_t opens, uint16_t users,
+ uint32_t sess_time, uint32_t idle_time,
+ uint32_t user_flags, const char *clitype)
+{
+ int ihrs = idle_time / 3600;
+ int imin = (idle_time / 60) % 60;
+ int isec = idle_time % 60;
+ int shrs = sess_time / 3600;
+ int smin = (sess_time / 60) % 60;
+ int ssec = sess_time % 60;
+ d_printf(_("User name %-20.20s\n"
+ "Computer %-20.20s\n"
+ "Guest logon %-20.20s\n"
+ "Client Type %-40.40s\n"
+ "Sess time %2.2d:%2.2d:%2.2d\n"
+ "Idle time %2.2d:%2.2d:%2.2d\n"),
+ username, wsname,
+ (user_flags&0x0)?_("yes"):_("no"), clitype,
+ shrs, smin, ssec, ihrs, imin, isec);
+}
+
+static void display_conns_func(uint16_t conn_id, uint16_t conn_type, uint16_t opens,
+ uint16_t users, uint32_t conn_time,
+ const char *username, const char *netname)
+{
+ d_printf("%-14.14s %-8.8s %5d\n",
+ netname, net_share_type_str(conn_type), opens);
+}
+
+static int rap_session_info(struct net_context *c, int argc, const char **argv)
+{
+ const char *sessname;
+ struct cli_state *cli;
+ int ret;
+
+ if (argc == 0 || c->display_usage)
+ return net_rap_session_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ sessname = argv[0];
+
+ ret = cli_NetSessionGetInfo(cli, sessname, display_session_func);
+ if (ret < 0) {
+ cli_shutdown(cli);
+ return ret;
+ }
+
+ d_printf(_("Share name Type # Opens\n-------------------------"
+ "-----------------------------------------------------\n"));
+ ret = cli_NetConnectionEnum(cli, sessname, display_conns_func);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_session_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (argc == 0 || c->display_usage)
+ return net_rap_session_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetSessionDel(cli, argv[0]);
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_session(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "info",
+ rap_session_info,
+ NET_TRANSPORT_RAP,
+ N_("Display information about session"),
+ N_("net rap session info\n"
+ " Display information about session")
+ },
+ {
+ "delete",
+ rap_session_delete,
+ NET_TRANSPORT_RAP,
+ N_("Close specified session"),
+ N_("net rap session delete\n"
+ " Close specified session\n"
+ " Alias for net rap session close")
+ },
+ {
+ "close",
+ rap_session_delete,
+ NET_TRANSPORT_RAP,
+ N_("Close specified session"),
+ N_("net rap session close\n"
+ " Close specified session")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ struct cli_state *cli;
+ int ret;
+
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rap session\n"
+ " List all open sessions on remote "
+ "server\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ d_printf(_("Computer User name "
+ "Client Type Opens Idle time\n"
+ "------------------------------------------"
+ "------------------------------------\n"));
+ ret = cli_NetSessionEnum(cli, list_sessions_func);
+
+ cli_shutdown(cli);
+ return ret;
+ }
+
+ return net_run_function(c, argc, argv, "net rap session", func);
+}
+
+/****************************************************************************
+list a server name
+****************************************************************************/
+static void display_server_func(const char *name, uint32_t m,
+ const char *comment, void * reserved)
+{
+ d_printf("\t%-16.16s %s\n", name, comment);
+}
+
+static int net_rap_server_name(struct net_context *c, int argc, const char *argv[])
+{
+ struct cli_state *cli;
+ char *name;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rap server name\n"
+ " Get the name of the server\n"));
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ if (!cli_get_server_name(NULL, cli, &name)) {
+ d_fprintf(stderr, _("cli_get_server_name failed\n"));
+ cli_shutdown(cli);
+ return -1;
+ }
+
+ d_printf(_("Server name = %s\n"), name);
+
+ TALLOC_FREE(name);
+ cli_shutdown(cli);
+ return 0;
+}
+
+static int net_rap_server_domain(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rap server domain\n"
+ " Enumerate servers in this domain/workgroup\n"));
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ d_printf(_("\nEnumerating servers in this domain or workgroup: \n\n"
+ "\tServer name Server description\n"
+ "\t------------- ----------------------------\n"));
+
+ ret = cli_NetServerEnum(cli, cli->server_domain, SV_TYPE_ALL,
+ display_server_func,NULL);
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_server(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "name",
+ net_rap_server_name,
+ NET_TRANSPORT_RAP,
+ N_("Get the name of the server"),
+ N_("net rap server name\n"
+ " Get the name of the server")
+ },
+ {
+ "domain",
+ net_rap_server_domain,
+ NET_TRANSPORT_RAP,
+ N_("Get the servers in this domain/workgroup"),
+ N_("net rap server domain\n"
+ " Get the servers in this domain/workgroup")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ /* smb4k uses 'net [rap|rpc] server domain' to query servers in a domain */
+ /* Fall through for 'domain', any other forms will cause to show usage message */
+ return net_run_function(c, argc, argv, "net rap server", func);
+
+}
+
+int net_rap_domain_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net rap domain [misc. options] [target]\n\tlists the"
+ " domains or workgroups visible on the current network\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+int net_rap_domain(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (c->display_usage)
+ return net_rap_domain_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ d_printf(_("\nEnumerating domains:\n\n"
+ "\tDomain name Server name of Browse Master\n"
+ "\t------------- ----------------------------\n"));
+
+ ret = cli_NetServerEnum(cli, cli->server_domain, SV_TYPE_DOMAIN_ENUM,
+ display_server_func,NULL);
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_printq_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "net rap printq [misc. options] [targets]\n"
+ "\tor\n"
+ "net rap printq info [<queue_name>] [misc. options] [targets]\n"
+ "\tlists the specified queue and jobs on the target server.\n"
+ "\tIf the queue name is not specified, all queues are listed.\n\n"));
+ d_printf(_(
+ "net rap printq delete [<queue name>] [misc. options] [targets]\n"
+ "\tdeletes the specified job number on the target server, or the\n"
+ "\tprinter queue if no job number is specified\n"));
+
+ net_common_flags_usage(c, argc, argv);
+
+ return -1;
+}
+
+static void enum_queue(const char *queuename, uint16_t pri, uint16_t start,
+ uint16_t until, const char *sep, const char *pproc,
+ const char *dest, const char *qparms,
+ const char *qcomment, uint16_t status, uint16_t jobcount)
+{
+ d_printf(_("%-17.17s Queue %5d jobs "),
+ queuename, jobcount);
+
+ switch (status) {
+ case 0:
+ d_printf(_("*Printer Active*\n"));
+ break;
+ case 1:
+ d_printf(_("*Printer Paused*\n"));
+ break;
+ case 2:
+ d_printf(_("*Printer error*\n"));
+ break;
+ case 3:
+ d_printf(_("*Delete Pending*\n"));
+ break;
+ default:
+ d_printf(_("**UNKNOWN STATUS**\n"));
+ }
+}
+
+static void enum_jobs(uint16_t jobid, const char *ownername,
+ const char *notifyname, const char *datatype,
+ const char *jparms, uint16_t pos, uint16_t status,
+ const char *jstatus, unsigned int submitted, unsigned int jobsize,
+ const char *comment)
+{
+ d_printf(" %-23.23s %5d %9d ",
+ ownername, jobid, jobsize);
+ switch (status) {
+ case 0:
+ d_printf(_("Waiting\n"));
+ break;
+ case 1:
+ d_printf(_("Held in queue\n"));
+ break;
+ case 2:
+ d_printf(_("Spooling\n"));
+ break;
+ case 3:
+ d_printf(_("Printing\n"));
+ break;
+ default:
+ d_printf(_("**UNKNOWN STATUS**\n"));
+ }
+}
+
+#define PRINTQ_ENUM_DISPLAY \
+ _("Print queues at \\\\%s\n\n"\
+ "Name Job # Size Status\n\n"\
+ "------------------------------------------------------------------"\
+ "-------------\n")
+
+static int rap_printq_info(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (argc == 0 || c->display_usage)
+ return net_rap_printq_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ d_printf(PRINTQ_ENUM_DISPLAY, smbXcli_conn_remote_name(cli->conn)); /* list header */
+ ret = cli_NetPrintQGetInfo(cli, argv[0], enum_queue, enum_jobs);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_printq_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ NTSTATUS status;
+
+ if (argc == 0 || c->display_usage)
+ return net_rap_printq_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ status = cli_printjob_del(cli, atoi(argv[0]));
+ cli_shutdown(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+ return 0;
+}
+
+int net_rap_printq(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ struct functable func[] = {
+ {
+ "info",
+ rap_printq_info,
+ NET_TRANSPORT_RAP,
+ N_("Display info about print queues and jobs"),
+ N_("net rap printq info [queue]\n"
+ " Display info about print jobs in queue.\n"
+ " If queue is not specified, all queues are "
+ "listed")
+ },
+ {
+ "delete",
+ rap_printq_delete,
+ NET_TRANSPORT_RAP,
+ N_("Delete print job(s)"),
+ N_("net rap printq delete\n"
+ " Delete print job(s)")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rap printq\n"
+ " List the print queue\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ d_printf(PRINTQ_ENUM_DISPLAY, smbXcli_conn_remote_name(cli->conn)); /* list header */
+ ret = cli_NetPrintQEnum(cli, enum_queue, enum_jobs);
+ cli_shutdown(cli);
+ return ret;
+ }
+
+ return net_run_function(c, argc, argv, "net rap printq", func);
+}
+
+static int net_rap_user_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_user_usage(c, argc, argv);
+}
+
+static void user_fn(const char *user_name, void *state)
+{
+ d_printf("%-21.21s\n", user_name);
+}
+
+static void long_user_fn(const char *user_name, const char *comment,
+ const char * home_dir, const char * logon_script,
+ void *state)
+{
+ d_printf("%-21.21s %s\n",
+ user_name, comment);
+}
+
+static void group_member_fn(const char *user_name, void *state)
+{
+ d_printf("%-21.21s\n", user_name);
+}
+
+static int rap_user_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (argc == 0 || c->display_usage) {
+ return net_rap_user_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetUserDelete(cli, argv[0]);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_user_add(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ struct rap_user_info_1 userinfo;
+
+ if (argc == 0 || c->display_usage) {
+ return net_rap_user_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ strlcpy((char *)userinfo.user_name, argv[0], sizeof(userinfo.user_name));
+ if (c->opt_flags == 0)
+ c->opt_flags = 0x21;
+
+ userinfo.userflags = c->opt_flags;
+ userinfo.reserved1 = '\0';
+ userinfo.comment = smb_xstrdup(c->opt_comment ? c->opt_comment : "");
+ userinfo.priv = 1;
+ userinfo.home_dir = NULL;
+ userinfo.logon_script = NULL;
+ userinfo.passwrd[0] = '\0';
+
+ ret = cli_NetUserAdd(cli, &userinfo);
+
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_user_info(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ if (argc == 0 || c->display_usage) {
+ return net_rap_user_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetUserGetGroups(cli, argv[0], group_member_fn, NULL);
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_user(struct net_context *c, int argc, const char **argv)
+{
+ int ret = -1;
+ struct functable func[] = {
+ {
+ "add",
+ rap_user_add,
+ NET_TRANSPORT_RAP,
+ N_("Add specified user"),
+ N_("net rap user add\n"
+ " Add specified user")
+ },
+ {
+ "info",
+ rap_user_info,
+ NET_TRANSPORT_RAP,
+ N_("List domain groups of specified user"),
+ N_("net rap user info\n"
+ " List domain groups of specified user")
+
+ },
+ {
+ "delete",
+ rap_user_delete,
+ NET_TRANSPORT_RAP,
+ N_("Remove specified user"),
+ N_("net rap user delete\n"
+ " Remove specified user")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ struct cli_state *cli;
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rap user\n"
+ " List all users\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ goto done;
+ if (c->opt_long_list_entries) {
+ d_printf(_("\nUser name Comment"
+ "\n-----------------------------\n"));
+ ret = cli_RNetUserEnum(cli, long_user_fn, NULL);
+ cli_shutdown(cli);
+ goto done;
+ }
+ ret = cli_RNetUserEnum0(cli, user_fn, NULL);
+ cli_shutdown(cli);
+ goto done;
+ }
+
+ ret = net_run_function(c, argc, argv, "net rap user", func);
+ done:
+ if (ret != 0) {
+ DEBUG(1, (_("Net user returned: %d\n"), ret));
+ }
+ return ret;
+}
+
+
+int net_rap_group_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_group_usage(c, argc, argv);
+}
+
+static void long_group_fn(const char *group_name, const char *comment,
+ void *state)
+{
+ d_printf("%-21.21s %s\n", group_name, comment);
+}
+
+static void group_fn(const char *group_name, void *state)
+{
+ d_printf("%-21.21s\n", group_name);
+}
+
+static int rap_group_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ if (argc == 0 || c->display_usage) {
+ return net_rap_group_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetGroupDelete(cli, argv[0]);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_group_add(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ struct rap_group_info_1 grinfo;
+
+ if (argc == 0 || c->display_usage) {
+ return net_rap_group_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ /* BB check for length 21 or smaller explicitly ? BB */
+ strlcpy((char *)grinfo.group_name, argv[0], sizeof(grinfo.group_name));
+ grinfo.reserved1 = '\0';
+ grinfo.comment = smb_xstrdup(c->opt_comment ? c->opt_comment : "");
+
+ ret = cli_NetGroupAdd(cli, &grinfo);
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_group(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ rap_group_add,
+ NET_TRANSPORT_RAP,
+ N_("Add specified group"),
+ N_("net rap group add\n"
+ " Add specified group")
+ },
+ {
+ "delete",
+ rap_group_delete,
+ NET_TRANSPORT_RAP,
+ N_("Delete specified group"),
+ N_("net rap group delete\n"
+ " Delete specified group")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ struct cli_state *cli;
+ int ret;
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rap group\n"
+ " List all groups\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+ if (c->opt_long_list_entries) {
+ d_printf(_("Group name Comment\n"
+ "-----------------------------\n"));
+ ret = cli_RNetGroupEnum(cli, long_group_fn, NULL);
+ cli_shutdown(cli);
+ return ret;
+ }
+ ret = cli_RNetGroupEnum0(cli, group_fn, NULL);
+ cli_shutdown(cli);
+ return ret;
+ }
+
+ return net_run_function(c, argc, argv, "net rap group", func);
+}
+
+int net_rap_groupmember_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "net rap groupmember LIST <group> [misc. options] [targets]"
+ "\n\t Enumerate users in a group\n"
+ "\nnet rap groupmember DELETE <group> <user> [misc. options] "
+ "[targets]\n\t Delete specified user from specified group\n"
+ "\nnet rap groupmember ADD <group> <user> [misc. options] [targets]"
+ "\n\t Add specified user to specified group\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+
+static int rap_groupmember_add(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ if (argc != 2 || c->display_usage) {
+ return net_rap_groupmember_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetGroupAddUser(cli, argv[0], argv[1]);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_groupmember_delete(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ if (argc != 2 || c->display_usage) {
+ return net_rap_groupmember_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetGroupDelUser(cli, argv[0], argv[1]);
+ cli_shutdown(cli);
+ return ret;
+}
+
+static int rap_groupmember_list(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+ if (argc == 0 || c->display_usage) {
+ return net_rap_groupmember_usage(c, argc, argv);
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ ret = cli_NetGroupGetUsers(cli, argv[0], group_member_fn, NULL );
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_groupmember(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ rap_groupmember_add,
+ NET_TRANSPORT_RAP,
+ N_("Add specified user to group"),
+ N_("net rap groupmember add\n"
+ " Add specified user to group")
+ },
+ {
+ "list",
+ rap_groupmember_list,
+ NET_TRANSPORT_RAP,
+ N_("List users in group"),
+ N_("net rap groupmember list\n"
+ " List users in group")
+ },
+ {
+ "delete",
+ rap_groupmember_delete,
+ NET_TRANSPORT_RAP,
+ N_("Remove user from group"),
+ N_("net rap groupmember delete\n"
+ " Remove user from group")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rap groupmember", func);
+}
+
+int net_rap_validate_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net rap validate <username> [password]\n"
+ "\tValidate user and password to check whether they"
+ " can access target server or domain\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+int net_rap_validate(struct net_context *c, int argc, const char **argv)
+{
+ return errmsg_not_implemented();
+}
+
+int net_rap_service_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net rap service [misc. options] [targets] \n"
+ "\tlists all running service daemons on target server\n"));
+ d_printf(_("\nnet rap service START <name> [service startup arguments]"
+ " [misc. options] [targets]"
+ "\n\tStart named service on remote server\n"));
+ d_printf(_("\nnet rap service STOP <name> [misc. options] [targets]\n"
+ "\n\tStop named service on remote server\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+static int rap_service_start(struct net_context *c, int argc, const char **argv)
+{
+ return errmsg_not_implemented();
+}
+
+static int rap_service_stop(struct net_context *c, int argc, const char **argv)
+{
+ return errmsg_not_implemented();
+}
+
+static void service_fn(const char *service_name, const char *dummy,
+ void *state)
+{
+ d_printf("%-21.21s\n", service_name);
+}
+
+int net_rap_service(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "start",
+ rap_service_start,
+ NET_TRANSPORT_RAP,
+ N_("Start service on remote server"),
+ N_("net rap service start\n"
+ " Start service on remote server")
+ },
+ {
+ "stop",
+ rap_service_stop,
+ NET_TRANSPORT_RAP,
+ N_("Stop named serve on remote server"),
+ N_("net rap service stop\n"
+ " Stop named serve on remote server")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ struct cli_state *cli;
+ int ret;
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rap service\n"
+ " List services on remote server\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ if (c->opt_long_list_entries) {
+ d_printf(_("Service name Comment\n"
+ "-----------------------------\n"));
+ ret = cli_RNetServiceEnum(cli, long_group_fn, NULL);
+ if (ret) {
+ cli_shutdown(cli);
+ return ret;
+ }
+ }
+ ret = cli_RNetServiceEnum(cli, service_fn, NULL);
+ cli_shutdown(cli);
+ return ret;
+ }
+
+ return net_run_function(c, argc, argv, "net rap service", func);
+}
+
+int net_rap_password_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "net rap password <user> <oldpwo> <newpw> [misc. options] [target]\n"
+ "\tchanges the password for the specified user at target\n"));
+
+ return -1;
+}
+
+
+int net_rap_password(struct net_context *c, int argc, const char **argv)
+{
+ struct cli_state *cli;
+ int ret;
+
+ if (argc < 3 || c->display_usage)
+ return net_rap_password_usage(c, argc, argv);
+
+ if (!NT_STATUS_IS_OK(net_make_ipc_connection(c, 0, &cli)))
+ return -1;
+
+ /* BB Add check for password lengths? */
+ ret = cli_oem_change_password(cli, argv[0], argv[2], argv[1]);
+ cli_shutdown(cli);
+ return ret;
+}
+
+int net_rap_admin_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "net rap admin <remote command> [cmd args [env]] [misc. options] [targets]"
+ "\n\texecutes a remote command on an os/2 target server\n"));
+
+ return -1;
+}
+
+
+int net_rap_admin(struct net_context *c, int argc, const char **argv)
+{
+ return errmsg_not_implemented();
+}
+
+/* Entry-point for all the RAP functions. */
+
+int net_rap(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "file",
+ net_rap_file,
+ NET_TRANSPORT_RAP,
+ N_("List open files"),
+ N_("net rap file\n"
+ " List open files")
+ },
+ {
+ "share",
+ net_rap_share,
+ NET_TRANSPORT_RAP,
+ N_("List shares exported by server"),
+ N_("net rap share\n"
+ " List shares exported by server")
+ },
+ {
+ "session",
+ net_rap_session,
+ NET_TRANSPORT_RAP,
+ N_("List open sessions"),
+ N_("net rap session\n"
+ " List open sessions")
+ },
+ {
+ "server",
+ net_rap_server,
+ NET_TRANSPORT_RAP,
+ N_("List servers in workgroup"),
+ N_("net rap server\n"
+ " List servers in domain/workgroup")
+ },
+ {
+ "domain",
+ net_rap_domain,
+ NET_TRANSPORT_RAP,
+ N_("List domains in network"),
+ N_("net rap domain\n"
+ " List domains in network")
+ },
+ {
+ "printq",
+ net_rap_printq,
+ NET_TRANSPORT_RAP,
+ N_("List printer queues on server"),
+ N_("net rap printq\n"
+ " List printer queues on server")
+ },
+ {
+ "user",
+ net_rap_user,
+ NET_TRANSPORT_RAP,
+ N_("List users"),
+ N_("net rap user\n"
+ " List users")
+ },
+ {
+ "group",
+ net_rap_group,
+ NET_TRANSPORT_RAP,
+ N_("List user groups"),
+ N_("net rap group\n"
+ " List user groups")
+ },
+ {
+ "validate",
+ net_rap_validate,
+ NET_TRANSPORT_RAP,
+ N_("Check username/password"),
+ N_("net rap validate\n"
+ " Check username/password")
+ },
+ {
+ "groupmember",
+ net_rap_groupmember,
+ NET_TRANSPORT_RAP,
+ N_("List/modify group memberships"),
+ N_("net rap groupmember\n"
+ " List/modify group memberships")
+ },
+ {
+ "admin",
+ net_rap_admin,
+ NET_TRANSPORT_RAP,
+ N_("Execute commands on remote OS/2"),
+ N_("net rap admin\n"
+ " Execute commands on remote OS/2")
+ },
+ {
+ "service",
+ net_rap_service,
+ NET_TRANSPORT_RAP,
+ N_("Start/stop remote service"),
+ N_("net rap service\n"
+ " Start/stop remote service")
+ },
+ {
+ "password",
+ net_rap_password,
+ NET_TRANSPORT_RAP,
+ N_("Change user password"),
+ N_("net rap password\n"
+ " Change user password")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rap", func);
+}
+
diff --git a/source3/utils/net_registry.c b/source3/utils/net_registry.c
new file mode 100644
index 0000000..5d1314e
--- /dev/null
+++ b/source3/utils/net_registry.c
@@ -0,0 +1,1732 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Local registry interface
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "registry/reg_api.h"
+#include "registry/reg_util_token.h"
+#include "registry/reg_init_basic.h"
+#include "utils/net.h"
+#include "utils/net_registry_util.h"
+#include "include/g_lock.h"
+#include "registry/reg_backend_db.h"
+#include "registry/reg_import.h"
+#include "registry/reg_format.h"
+#include "registry/reg_api_util.h"
+#include <assert.h>
+#include "../libcli/security/display_sec.h"
+#include "../libcli/security/sddl.h"
+#include "../libcli/registry/util_reg.h"
+#include "passdb/machine_sid.h"
+#include "net_registry_check.h"
+#include "lib/util/util_tdb.h"
+#include "lib/util/smb_strtox.h"
+
+/*
+ *
+ * Helper functions
+ *
+ */
+
+/**
+ * split given path into hive and remaining path and open the hive key
+ */
+static WERROR open_hive(TALLOC_CTX *ctx, const char *path,
+ uint32_t desired_access,
+ struct registry_key **hive,
+ char **subkeyname)
+{
+ WERROR werr;
+ struct security_token *token = NULL;
+ char *hivename = NULL;
+ char *tmp_subkeyname = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if ((hive == NULL) || (subkeyname == NULL)) {
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ werr = split_hive_key(tmp_ctx, path, &hivename, &tmp_subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ *subkeyname = talloc_strdup(ctx, tmp_subkeyname);
+ if (*subkeyname == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ werr = ntstatus_to_werror(registry_create_admin_token(tmp_ctx, &token));
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = reg_openhive(ctx, hivename, desired_access, token, hive);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return werr;
+}
+
+static WERROR open_key(TALLOC_CTX *ctx, const char *path,
+ uint32_t desired_access,
+ struct registry_key **key)
+{
+ WERROR werr;
+ char *subkey_name = NULL;
+ struct registry_key *hive = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if ((path == NULL) || (key == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = open_hive(tmp_ctx, path, desired_access, &hive, &subkey_name);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_hive failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_openkey(ctx, hive, subkey_name, desired_access, key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_openkey failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return werr;
+}
+
+static WERROR registry_enumkey(struct registry_key *parent, const char *keyname,
+ bool recursive)
+{
+ WERROR werr;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ char *subkey_name;
+ NTTIME modtime;
+ uint32_t count;
+ char *valname = NULL;
+ struct registry_value *valvalue = NULL;
+ struct registry_key *key = NULL;
+
+ werr = reg_openkey(ctx, parent, keyname, REG_KEY_READ, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ if (recursive) {
+ printf("[%s]\n\n", key->key->name);
+ } else {
+ for (count = 0;
+ werr = reg_enumkey(ctx, key, count, &subkey_name, &modtime),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ print_registry_key(subkey_name, &modtime);
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ goto done;
+ }
+ }
+
+ for (count = 0;
+ werr = reg_enumvalue(ctx, key, count, &valname, &valvalue),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ print_registry_value_with_name(valname, valvalue);
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ goto done;
+ }
+
+ if (!recursive) {
+ werr = WERR_OK;
+ goto done;
+ }
+
+ for (count = 0;
+ werr = reg_enumkey(ctx, key, count, &subkey_name, &modtime),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ werr = registry_enumkey(key, subkey_name, recursive);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+done:
+ TALLOC_FREE(ctx);
+ return werr;
+}
+
+
+
+/*
+ *
+ * the main "net registry" function implementations
+ *
+ */
+static int net_registry_enumerate(struct net_context *c, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ struct registry_key *key = NULL;
+ char *name = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ int ret = -1;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry enumerate <path>\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry enumerate 'HKLM\\Software\\Samba'\n"));
+ goto done;
+ }
+
+ werr = open_hive(ctx, argv[0], REG_KEY_READ, &key, &name);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_key failed: %s\n"), win_errstr(werr));
+ goto done;
+ }
+
+ werr = registry_enumkey(key, name, c->opt_reboot);
+ if (W_ERROR_IS_OK(werr)) {
+ ret = 0;
+ }
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_enumerate_recursive(struct net_context *c, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ struct registry_key *key = NULL;
+ char *name = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ int ret = -1;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry enumerate <path>\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry enumerate 'HKLM\\Software\\Samba'\n"));
+ goto done;
+ }
+
+ werr = open_hive(ctx, argv[0], REG_KEY_READ, &key, &name);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_key failed: %s\n"), win_errstr(werr));
+ goto done;
+ }
+
+ werr = registry_enumkey(key, name, true);
+ if (W_ERROR_IS_OK(werr)) {
+ ret = 0;
+ }
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+
+static int net_registry_createkey(struct net_context *c, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ enum winreg_CreateAction action;
+ char *subkeyname = NULL;
+ struct registry_key *hivekey = NULL;
+ struct registry_key *subkey = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ int ret = -1;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry createkey <path>\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry createkey "
+ "'HKLM\\Software\\Samba\\smbconf.127.0.0.1'\n"));
+ goto done;
+ }
+ if (strlen(argv[0]) == 0) {
+ d_fprintf(stderr, _("error: zero length key name given\n"));
+ goto done;
+ }
+
+ werr = open_hive(ctx, argv[0], REG_KEY_WRITE, &hivekey, &subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_hive failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_createkey(ctx, hivekey, subkeyname, REG_KEY_WRITE,
+ &subkey, &action);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_createkey failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+ switch (action) {
+ case REG_ACTION_NONE:
+ d_printf(_("createkey did nothing -- huh?\n"));
+ break;
+ case REG_CREATED_NEW_KEY:
+ d_printf(_("createkey created %s\n"), argv[0]);
+ break;
+ case REG_OPENED_EXISTING_KEY:
+ d_printf(_("createkey opened existing %s\n"), argv[0]);
+ break;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_deletekey_internal(struct net_context *c, int argc,
+ const char **argv,
+ bool recursive)
+{
+ WERROR werr;
+ char *subkeyname = NULL;
+ struct registry_key *hivekey = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ int ret = -1;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry deletekey <path>\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry deletekey "
+ "'HKLM\\Software\\Samba\\smbconf.127.0.0.1'\n"));
+ goto done;
+ }
+ if (strlen(argv[0]) == 0) {
+ d_fprintf(stderr, _("error: zero length key name given\n"));
+ goto done;
+ }
+
+ werr = open_hive(ctx, argv[0], REG_KEY_WRITE, &hivekey, &subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, "open_hive %s: %s\n", _("failed"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ if (recursive) {
+ werr = reg_deletekey_recursive(hivekey, subkeyname);
+ } else {
+ werr = reg_deletekey(hivekey, subkeyname);
+ }
+ if (!W_ERROR_IS_OK(werr) &&
+ !(c->opt_force && W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)))
+ {
+ d_fprintf(stderr, "reg_deletekey %s: %s\n", _("failed"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_deletekey(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_registry_deletekey_internal(c, argc, argv, false);
+}
+
+static int net_registry_deletekey_recursive(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_registry_deletekey_internal(c, argc, argv, true);
+}
+
+static int net_registry_getvalue_internal(struct net_context *c, int argc,
+ const char **argv, bool raw)
+{
+ WERROR werr;
+ int ret = -1;
+ struct registry_key *key = NULL;
+ struct registry_value *value = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net registry getvalue <key> <valuename>\n"));
+ goto done;
+ }
+
+ werr = open_key(ctx, argv[0], REG_KEY_READ, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_key failed: %s\n"), win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_queryvalue(ctx, key, argv[1], &value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_queryvalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ print_registry_value(value, raw);
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_getvalue(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_registry_getvalue_internal(c, argc, argv, false);
+}
+
+static int net_registry_getvalueraw(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_registry_getvalue_internal(c, argc, argv, true);
+}
+
+static int net_registry_getvaluesraw(struct net_context *c, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ int ret = -1;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ uint32_t idx;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "usage: net rpc registry getvaluesraw "
+ "<key>\n");
+ goto done;
+ }
+
+ werr = open_key(ctx, argv[0], REG_KEY_READ, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, "open_key failed: %s\n", win_errstr(werr));
+ goto done;
+ }
+
+ idx = 0;
+ while (true) {
+ struct registry_value *val;
+
+ werr = reg_enumvalue(talloc_tos(), key, idx, NULL, &val);
+
+ if (W_ERROR_EQUAL(werr, WERR_NO_MORE_ITEMS)) {
+ ret = 0;
+ break;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ break;
+ }
+ print_registry_value(val, true);
+ TALLOC_FREE(val);
+ idx += 1;
+ }
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_setvalue(struct net_context *c, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ struct registry_value value;
+ struct registry_key *key = NULL;
+ int ret = -1;
+ TALLOC_CTX *ctx = talloc_stackframe();
+
+ if (argc < 4 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net registry setvalue <key> <valuename> "
+ "<type> [<val>]+\n"));
+ goto done;
+ }
+
+ if (!strequal(argv[2], "multi_sz") && (argc != 4)) {
+ d_fprintf(stderr, _("Too many args for type %s\n"), argv[2]);
+ goto done;
+ }
+
+ if (strequal(argv[2], "dword")) {
+ int error = 0;
+ uint32_t v;
+
+ v = smb_strtoul(argv[3], NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+
+ value.type = REG_DWORD;
+ value.data = data_blob_talloc(ctx, NULL, 4);
+ SIVAL(value.data.data, 0, v);
+ } else if (strequal(argv[2], "sz")) {
+ value.type = REG_SZ;
+ if (!push_reg_sz(ctx, &value.data, argv[3])) {
+ goto done;
+ }
+ } else if (strequal(argv[2], "multi_sz")) {
+ const char **array;
+ int count = argc - 3;
+ int i;
+ value.type = REG_MULTI_SZ;
+ array = talloc_zero_array(ctx, const char *, count + 1);
+ if (array == NULL) {
+ goto done;
+ }
+ for (i=0; i < count; i++) {
+ array[i] = talloc_strdup(array, argv[count+i]);
+ if (array[i] == NULL) {
+ goto done;
+ }
+ }
+ if (!push_reg_multi_sz(ctx, &value.data, array)) {
+ goto done;
+ }
+ } else {
+ d_fprintf(stderr, _("type \"%s\" not implemented\n"), argv[2]);
+ goto done;
+ }
+
+ werr = open_key(ctx, argv[0], REG_KEY_WRITE, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_key failed: %s\n"), win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_setvalue(key, argv[1], &value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_setvalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_increment(struct net_context *c, int argc,
+ const char **argv)
+{
+ TDB_DATA lock_key = string_term_tdb_data("registry_increment_lock");
+ struct g_lock_ctx *ctx = NULL;
+ const char *keyname = NULL;
+ struct registry_key *key = NULL;
+ const char *valuename = NULL;
+ struct registry_value *value = NULL;
+ uint32_t v;
+ uint32_t increment;
+ uint32_t newvalue;
+ NTSTATUS status;
+ WERROR werr;
+ int ret = -1;
+
+ if (argc < 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net registry increment <key> <valuename> "
+ "[<increment>]\n"));
+ goto done;
+ }
+
+ keyname = argv[0];
+ valuename = argv[1];
+
+ increment = 1;
+ if (argc == 3) {
+ int error = 0;
+
+ increment = smb_strtoul(
+ argv[2], NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ goto done;
+ }
+ }
+
+ ctx = g_lock_ctx_init(c, c->msg_ctx);
+ if (ctx == NULL) {
+ d_fprintf(stderr, _("g_lock_ctx_init failed\n"));
+ goto done;
+ }
+
+ status = g_lock_lock(ctx,
+ lock_key,
+ G_LOCK_WRITE,
+ timeval_set(600, 0),
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("g_lock_lock failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ werr = open_key(c, keyname, REG_KEY_READ|REG_KEY_WRITE, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_key failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_queryvalue(key, key, valuename, &value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_queryvalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ if (value->type != REG_DWORD) {
+ d_fprintf(stderr, _("value not a DWORD: %s\n"),
+ str_regtype(value->type));
+ goto done;
+ }
+
+ if (value->data.length < 4) {
+ d_fprintf(stderr, _("value too short for regular DWORD\n"));
+ goto done;
+ }
+
+ v = IVAL(value->data.data, 0);
+ v += increment;
+ newvalue = v;
+
+ SIVAL(value->data.data, 0, v);
+
+ werr = reg_setvalue(key, valuename, value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_setvalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("increment failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ g_lock_unlock(ctx, lock_key);
+
+ d_printf(_("%"PRIu32"\n"), newvalue);
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(value);
+ TALLOC_FREE(key);
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_deletevalue(struct net_context *c, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ int ret = -1;
+
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net registry deletevalue <key> <valuename>\n"));
+ goto done;
+ }
+
+ werr = open_key(ctx, argv[0], REG_KEY_WRITE, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_key failed: %s\n"), win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_deletevalue(key, argv[1]);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_deletevalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static WERROR net_registry_getsd_internal(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ const char *keyname,
+ struct security_descriptor **sd)
+{
+ WERROR werr;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ uint32_t access_mask = REG_KEY_READ |
+ SEC_FLAG_MAXIMUM_ALLOWED |
+ SEC_FLAG_SYSTEM_SECURITY;
+
+ /*
+ * net_rpc_regsitry uses SEC_FLAG_SYSTEM_SECURITY, but access
+ * is denied with these perms right now...
+ */
+ access_mask = REG_KEY_READ;
+
+ if (sd == NULL) {
+ d_fprintf(stderr, _("internal error: invalid argument\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (strlen(keyname) == 0) {
+ d_fprintf(stderr, _("error: zero length key name given\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ werr = open_key(ctx, keyname, access_mask, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, "%s%s\n", _("open_key failed: "),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_getkeysecurity(mem_ctx, key, sd);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, "%s%s\n", _("reg_getkeysecurity failed: "),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+done:
+ TALLOC_FREE(ctx);
+ return werr;
+}
+
+static int net_registry_getsd(struct net_context *c, int argc,
+ const char **argv)
+{
+ WERROR werr;
+ int ret = -1;
+ struct security_descriptor *secdesc = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry getsd <path>\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry getsd 'HKLM\\Software\\Samba'\n"));
+ goto done;
+ }
+
+ werr = net_registry_getsd_internal(c, ctx, argv[0], &secdesc);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ display_sec_desc(secdesc);
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static int net_registry_getsd_sddl(struct net_context *c,
+ int argc, const char **argv)
+{
+ WERROR werr;
+ int ret = -1;
+ struct security_descriptor *secdesc = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry getsd_sddl <path>\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry getsd_sddl 'HKLM\\Software\\Samba'\n"));
+ goto done;
+ }
+
+ werr = net_registry_getsd_internal(c, ctx, argv[0], &secdesc);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ d_printf("%s\n", sddl_encode(ctx, secdesc, get_global_sam_sid()));
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+static WERROR net_registry_setsd_internal(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ const char *keyname,
+ struct security_descriptor *sd)
+{
+ WERROR werr;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ uint32_t access_mask = REG_KEY_WRITE |
+ SEC_FLAG_MAXIMUM_ALLOWED |
+ SEC_FLAG_SYSTEM_SECURITY;
+
+ /*
+ * net_rpc_regsitry uses SEC_FLAG_SYSTEM_SECURITY, but access
+ * is denied with these perms right now...
+ */
+ access_mask = REG_KEY_WRITE;
+
+ if (strlen(keyname) == 0) {
+ d_fprintf(stderr, _("error: zero length key name given\n"));
+ werr = WERR_INVALID_PARAMETER;
+ goto done;
+ }
+
+ werr = open_key(ctx, keyname, access_mask, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, "%s%s\n", _("open_key failed: "),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = reg_setkeysecurity(key, sd);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, "%s%s\n", _("reg_setkeysecurity failed: "),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = WERR_OK;
+
+done:
+ TALLOC_FREE(ctx);
+ return werr;
+}
+
+static int net_registry_setsd_sddl(struct net_context *c,
+ int argc, const char **argv)
+{
+ WERROR werr;
+ int ret = -1;
+ struct security_descriptor *secdesc = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+
+ if (argc != 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry setsd_sddl <path> <security_descriptor>\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry setsd_sddl 'HKLM\\Software\\Samba'\n"));
+ goto done;
+ }
+
+ secdesc = sddl_decode(ctx, argv[1], get_global_sam_sid());
+ if (secdesc == NULL) {
+ goto done;
+ }
+
+ werr = net_registry_setsd_internal(c, ctx, argv[0], secdesc);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+
+/******************************************************************************/
+/**
+ * @defgroup net_registry net registry
+ */
+
+/**
+ * @defgroup net_registry_import Import
+ * @ingroup net_registry
+ * @{
+ */
+
+struct import_ctx {
+ TALLOC_CTX *mem_ctx;
+};
+
+
+static WERROR import_create_key(struct import_ctx *ctx,
+ struct registry_key *parent,
+ const char *name, void **pkey, bool *existing)
+{
+ WERROR werr;
+ TALLOC_CTX *mem_ctx = talloc_new(ctx->mem_ctx);
+
+ struct registry_key *key = NULL;
+ enum winreg_CreateAction action;
+
+ if (parent == NULL) {
+ char *subkeyname = NULL;
+ werr = open_hive(mem_ctx, name, REG_KEY_WRITE,
+ &parent, &subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_hive failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+ name = subkeyname;
+ }
+
+ action = REG_ACTION_NONE;
+ werr = reg_createkey(mem_ctx, parent, name, REG_KEY_WRITE,
+ &key, &action);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_createkey failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ if (action == REG_ACTION_NONE) {
+ d_fprintf(stderr, _("createkey did nothing -- huh?\n"));
+ werr = WERR_CREATE_FAILED;
+ goto done;
+ }
+
+ if (existing != NULL) {
+ *existing = (action == REG_OPENED_EXISTING_KEY);
+ }
+
+ if (pkey!=NULL) {
+ *pkey = talloc_steal(ctx->mem_ctx, key);
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static WERROR import_close_key(struct import_ctx *ctx,
+ struct registry_key *key)
+{
+ return WERR_OK;
+}
+
+static WERROR import_delete_key(struct import_ctx *ctx,
+ struct registry_key *parent, const char *name)
+{
+ WERROR werr;
+ TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+
+ if (parent == NULL) {
+ char *subkeyname = NULL;
+ werr = open_hive(mem_ctx, name, REG_KEY_WRITE,
+ &parent, &subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_hive failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+ name = subkeyname;
+ }
+
+ werr = reg_deletekey_recursive(parent, name);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, "reg_deletekey_recursive %s: %s\n",
+ _("failed"), win_errstr(werr));
+ goto done;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static WERROR import_create_val (struct import_ctx *ctx,
+ struct registry_key *parent, const char *name,
+ const struct registry_value *value)
+{
+ WERROR werr;
+
+ if (parent == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = reg_setvalue(parent, name, value);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_setvalue failed: %s\n"),
+ win_errstr(werr));
+ }
+ return werr;
+}
+
+static WERROR import_delete_val (struct import_ctx *ctx,
+ struct registry_key *parent, const char *name)
+{
+ WERROR werr;
+
+ if (parent == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ werr = reg_deletevalue(parent, name);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_deletevalue failed: %s\n"),
+ win_errstr(werr));
+ }
+
+ return werr;
+}
+
+struct precheck_ctx {
+ TALLOC_CTX *mem_ctx;
+ bool failed;
+};
+
+static WERROR precheck_create_key(struct precheck_ctx *ctx,
+ struct registry_key *parent,
+ const char *name, void **pkey, bool *existing)
+{
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct registry_key *key = NULL;
+
+ if (parent == NULL) {
+ char *subkeyname = NULL;
+ werr = open_hive(frame, name, REG_KEY_READ,
+ &parent, &subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Precheck: open_hive of [%s] failed: %s\n",
+ name, win_errstr(werr));
+ goto done;
+ }
+ name = subkeyname;
+ }
+
+ werr = reg_openkey(frame, parent, name, 0, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Precheck: openkey [%s] failed: %s\n",
+ name, win_errstr(werr));
+ goto done;
+ }
+
+ if (existing != NULL) {
+ *existing = true;
+ }
+
+ if (pkey != NULL) {
+ *pkey = talloc_steal(ctx->mem_ctx, key);
+ }
+
+done:
+ talloc_free(frame);
+ ctx->failed = !W_ERROR_IS_OK(werr);
+ return werr;
+}
+
+static WERROR precheck_close_key(struct precheck_ctx *ctx,
+ struct registry_key *key)
+{
+ talloc_free(key);
+ return WERR_OK;
+}
+
+static WERROR precheck_delete_key(struct precheck_ctx *ctx,
+ struct registry_key *parent, const char *name)
+{
+ WERROR werr;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct registry_key *key;
+
+ if (parent == NULL) {
+ char *subkeyname = NULL;
+ werr = open_hive(frame, name, REG_KEY_READ,
+ &parent, &subkeyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Precheck: open_hive of [%s] failed: %s\n",
+ name, win_errstr(werr));
+ goto done;
+ }
+ name = subkeyname;
+ }
+
+ werr = reg_openkey(ctx->mem_ctx, parent, name, 0, &key);
+ if (W_ERROR_IS_OK(werr)) {
+ d_printf("Precheck: key [%s\\%s] should not exist\n",
+ parent->key->name, name);
+ werr = WERR_FILE_EXISTS;
+ } else if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) {
+ werr = WERR_OK;
+ } else {
+ d_printf("Precheck: openkey [%s\\%s] failed: %s\n",
+ parent->key->name, name, win_errstr(werr));
+ }
+
+done:
+ talloc_free(frame);
+ ctx->failed = !W_ERROR_IS_OK(werr);
+ return werr;
+}
+
+static int registry_value_cmp(
+ const struct registry_value* v1, const struct registry_value* v2)
+{
+ if (v1->type == v2->type) {
+ return data_blob_cmp(&v1->data, &v2->data);
+ }
+ return v1->type - v2->type;
+}
+
+static WERROR precheck_create_val(struct precheck_ctx *ctx,
+ struct registry_key *parent,
+ const char *name,
+ const struct registry_value *value)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct registry_value *old;
+ WERROR werr;
+
+ SMB_ASSERT(parent);
+
+ werr = reg_queryvalue(frame, parent, name, &old);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Precheck: queryvalue \"%s\" of [%s] failed: %s\n",
+ name, parent->key->name, win_errstr(werr));
+ goto done;
+ }
+ if (registry_value_cmp(value, old) != 0) {
+ d_printf("Precheck: unexpected value \"%s\" of key [%s]\n",
+ name, parent->key->name);
+ ctx->failed = true;
+ }
+done:
+ talloc_free(frame);
+ return werr;
+}
+
+static WERROR precheck_delete_val(struct precheck_ctx *ctx,
+ struct registry_key *parent,
+ const char *name)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct registry_value *old;
+ WERROR werr;
+
+ SMB_ASSERT(parent);
+
+ werr = reg_queryvalue(frame, parent, name, &old);
+ if (W_ERROR_IS_OK(werr)) {
+ d_printf("Precheck: value \"%s\" of key [%s] should not exist\n",
+ name, parent->key->name);
+ werr = WERR_FILE_EXISTS;
+ } else if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) {
+ werr = WERR_OK;
+ } else {
+ printf("Precheck: queryvalue \"%s\" of key [%s] failed: %s\n",
+ name, parent->key->name, win_errstr(werr));
+ }
+
+ talloc_free(frame);
+ ctx->failed = !W_ERROR_IS_OK(werr);
+ return werr;
+}
+
+static bool import_precheck(const char *fname, const char *parse_options)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct precheck_ctx precheck_ctx = {
+ .mem_ctx = mem_ctx,
+ .failed = false,
+ };
+ struct reg_import_callback precheck_callback = {
+ .openkey = NULL,
+ .closekey = (reg_import_callback_closekey_t)&precheck_close_key,
+ .createkey = (reg_import_callback_createkey_t)&precheck_create_key,
+ .deletekey = (reg_import_callback_deletekey_t)&precheck_delete_key,
+ .deleteval = (reg_import_callback_deleteval_t)&precheck_delete_val,
+ .setval = {
+ .registry_value = (reg_import_callback_setval_registry_value_t)
+ &precheck_create_val,
+ },
+ .setval_type = REGISTRY_VALUE,
+ .data = &precheck_ctx
+ };
+ struct reg_parse_callback *parse_callback;
+ int ret;
+
+ if (!fname) {
+ return true;
+ }
+
+ parse_callback = reg_import_adapter(mem_ctx, precheck_callback);
+ if (parse_callback == NULL) {
+ d_printf("talloc failed\n");
+ return false;
+ }
+
+ ret = reg_parse_file(fname, parse_callback, parse_options);
+
+ if (ret < 0 || precheck_ctx.failed) {
+ d_printf("Precheck failed\n");
+ return false;
+ }
+ return true;
+}
+
+static int import_with_precheck_action(const char *import_fname,
+ const char *precheck_fname,
+ const char *parse_options)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct import_ctx import_ctx = {
+ .mem_ctx = frame,
+ };
+ struct reg_import_callback import_callback = {
+ .openkey = NULL,
+ .closekey = (reg_import_callback_closekey_t)&import_close_key,
+ .createkey = (reg_import_callback_createkey_t)&import_create_key,
+ .deletekey = (reg_import_callback_deletekey_t)&import_delete_key,
+ .deleteval = (reg_import_callback_deleteval_t)&import_delete_val,
+ .setval = {
+ .registry_value = (reg_import_callback_setval_registry_value_t)
+ &import_create_val,
+ },
+ .setval_type = REGISTRY_VALUE,
+ .data = &import_ctx
+ };
+ struct reg_parse_callback *parse_callback;
+ int ret = -1;
+ bool precheck_ok;
+
+ precheck_ok = import_precheck(precheck_fname, parse_options);
+ if (!precheck_ok) {
+ goto done;
+ }
+
+ parse_callback = reg_import_adapter(frame, import_callback);
+ if (parse_callback == NULL) {
+ d_printf("talloc failed\n");
+ goto done;
+ }
+
+ ret = reg_parse_file(import_fname, parse_callback, parse_options);
+
+done:
+ talloc_free(frame);
+ return ret;
+}
+
+static int net_registry_import(struct net_context *c, int argc,
+ const char **argv)
+{
+ const char *parse_options = (argc > 1) ? argv[1] : NULL;
+ int ret = -1;
+ WERROR werr;
+
+ if (argc < 1 || argc > 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry import <reg> [options]\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry import file.reg enc=CP1252\n"));
+ return -1;
+ }
+
+ werr = regdb_open();
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to open regdb: %s\n", win_errstr(werr));
+ return -1;
+ }
+
+ werr = regdb_transaction_start();
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to start transaction on regdb: %s\n",
+ win_errstr(werr));
+ goto done;
+ }
+
+ ret = import_with_precheck_action(argv[0], c->opt_precheck,
+ parse_options);
+
+ if (ret < 0) {
+ d_printf("Transaction canceled!\n");
+ regdb_transaction_cancel();
+ goto done;
+ }
+
+ SMB_ASSERT(ret == 0);
+
+ if (c->opt_testmode) {
+ d_printf("Testmode: not committing changes.\n");
+ regdb_transaction_cancel();
+ goto done;
+ }
+
+ werr = regdb_transaction_commit();
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("Failed to commit transaction on regdb: %s\n",
+ win_errstr(werr));
+ ret = -1;
+ }
+
+done:
+ regdb_close();
+ return ret;
+}
+/**@}*/
+
+/******************************************************************************/
+
+/**
+ * @defgroup net_registry_export Export
+ * @ingroup net_registry
+ * @{
+ */
+
+static int registry_export(TALLOC_CTX *ctx, /*const*/ struct registry_key *key,
+ struct reg_format *f)
+{
+ int ret=-1;
+ WERROR werr;
+ uint32_t count;
+
+ struct registry_value *valvalue = NULL;
+ char *valname = NULL;
+
+ char *subkey_name = NULL;
+ NTTIME modtime = 0;
+
+ reg_format_registry_key(f, key, false);
+
+ /* print values */
+ for (count = 0;
+ werr = reg_enumvalue(ctx, key, count, &valname, &valvalue),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ reg_format_registry_value(f, valname, valvalue);
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ d_fprintf(stderr, _("reg_enumvalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ /* recurse on subkeys */
+ for (count = 0;
+ werr = reg_enumkey(ctx, key, count, &subkey_name, &modtime),
+ W_ERROR_IS_OK(werr);
+ count++)
+ {
+ struct registry_key *subkey = NULL;
+
+ werr = reg_openkey(ctx, key, subkey_name, REG_KEY_READ,
+ &subkey);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("reg_openkey failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ registry_export(ctx, subkey, f);
+ TALLOC_FREE(subkey);
+ }
+ if (!W_ERROR_EQUAL(WERR_NO_MORE_ITEMS, werr)) {
+ d_fprintf(stderr, _("reg_enumkey failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+ ret = 0;
+done:
+ return ret;
+}
+
+static int net_registry_export(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret=-1;
+ WERROR werr;
+ struct registry_key *key = NULL;
+ TALLOC_CTX *ctx = talloc_stackframe();
+ struct reg_format *f=NULL;
+
+ if (argc < 2 || argc > 3 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry export <path> <file> [opt]\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry export 'HKLM\\Software\\Samba' "
+ "samba.reg regedit5\n"));
+ goto done;
+ }
+
+ werr = open_key(ctx, argv[0], REG_KEY_READ, &key);
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("open_key failed: %s\n"), win_errstr(werr));
+ goto done;
+ }
+
+ f = reg_format_file(ctx, argv[1], (argc > 2) ? argv[2] : NULL);
+ if (f == NULL) {
+ d_fprintf(stderr, _("open file failed: %s\n"), strerror(errno));
+ goto done;
+ }
+
+ ret = registry_export(ctx, key, f);
+
+done:
+ TALLOC_FREE(ctx);
+ return ret;
+}
+/**@}*/
+
+/******************************************************************************/
+/**
+ * @defgroup net_registry_convert Convert
+ * @ingroup net_registry
+ * @{
+ */
+
+static int net_registry_convert(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret;
+ TALLOC_CTX *mem_ctx;
+ const char *in_opt = NULL;
+ const char *out_opt = NULL;
+
+ if (argc < 2 || argc > 4|| c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry convert <in> <out> [in_opt] [out_opt]\n"
+ "net registry convert <in> <out> [out_opt]\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net registry convert in.reg out.reg regedit4,enc=CP1252\n"));
+ return -1;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ switch (argc ) {
+ case 2:
+ break;
+ case 3:
+ out_opt = argv[2];
+ break;
+ case 4:
+ out_opt = argv[3];
+ in_opt = argv[2];
+ break;
+ default:
+ assert(false);
+ }
+
+
+ ret = reg_parse_file(argv[0], (struct reg_parse_callback*)
+ reg_format_file(mem_ctx, argv[1], out_opt),
+ in_opt);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+/**@}*/
+
+static int net_registry_check(struct net_context *c, int argc,
+ const char **argv)
+{
+ char *dbfile;
+ struct check_options opts;
+ int ret;
+
+ if (argc > 1|| c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net registry check [-vraTfl] [-o <ODB>] [--wipe] [<TDB>]\n"
+ " Check a registry database.\n"
+ " -v|--verbose\t be verbose\n"
+ " -r|--repair\t\t interactive repair mode\n"
+ " -a|--auto\t\t noninteractive repair mode\n"
+ " -T|--test\t\t dry run\n"
+ " -f|--force\t\t force\n"
+ " -l|--lock\t\t lock <TDB> while doing the check\n"
+ " -o|--output=<ODB>\t output database\n"
+ " --reg-version=n\t assume database format version {n|1,2,3}\n"
+ " --wipe\t\t create a new database from scratch\n"
+ " --db=<TDB>\t\t registry database to open\n"));
+ return c->display_usage ? 0 : -1;
+ }
+
+ if (c->opt_db != NULL) {
+ dbfile = talloc_strdup(talloc_tos(), c->opt_db);
+ } else if (argc > 0) {
+ dbfile = talloc_strdup(talloc_tos(), argv[0]);
+ } else {
+ dbfile = state_path(talloc_tos(), "registry.tdb");
+ }
+ if (dbfile == NULL) {
+ return -1;
+ }
+
+ opts = (struct check_options) {
+ .lock = c->opt_lock || c->opt_long_list_entries,
+ .test = c->opt_testmode,
+ .automatic = c->opt_auto,
+ .verbose = c->opt_verbose,
+ .force = c->opt_force,
+ .repair = c->opt_repair || c->opt_reboot,
+ .version = c->opt_reg_version,
+ .output = c->opt_output,
+ .wipe = c->opt_wipe,
+ .implicit_db = (c->opt_db == NULL) && (argc == 0),
+ };
+
+ ret = net_registry_check_db(dbfile, &opts);
+ talloc_free(dbfile);
+ return ret;
+}
+
+
+/******************************************************************************/
+
+int net_registry(struct net_context *c, int argc, const char **argv)
+{
+ int ret = -1;
+
+ struct functable func[] = {
+ {
+ "enumerate",
+ net_registry_enumerate,
+ NET_TRANSPORT_LOCAL,
+ N_("Enumerate registry keys and values"),
+ N_("net registry enumerate\n"
+ " Enumerate registry keys and values")
+ },
+ {
+ "enumerate_recursive",
+ net_registry_enumerate_recursive,
+ NET_TRANSPORT_LOCAL,
+ N_("Enumerate registry keys and values"),
+ N_("net registry enumerate_recursive\n"
+ " Enumerate registry keys and values")
+ },
+ {
+ "createkey",
+ net_registry_createkey,
+ NET_TRANSPORT_LOCAL,
+ N_("Create a new registry key"),
+ N_("net registry createkey\n"
+ " Create a new registry key")
+ },
+ {
+ "deletekey",
+ net_registry_deletekey,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a registry key"),
+ N_("net registry deletekey\n"
+ " Delete a registry key")
+ },
+ {
+ "deletekey_recursive",
+ net_registry_deletekey_recursive,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a registry key with subkeys"),
+ N_("net registry deletekey_recursive\n"
+ " Delete a registry key with subkeys")
+ },
+ {
+ "getvalue",
+ net_registry_getvalue,
+ NET_TRANSPORT_LOCAL,
+ N_("Print a registry value"),
+ N_("net registry getvalue\n"
+ " Print a registry value")
+ },
+ {
+ "getvalueraw",
+ net_registry_getvalueraw,
+ NET_TRANSPORT_LOCAL,
+ N_("Print a registry value (raw format)"),
+ N_("net registry getvalueraw\n"
+ " Print a registry value (raw format)")
+ },
+ {
+ "getvaluesraw",
+ net_registry_getvaluesraw,
+ NET_TRANSPORT_LOCAL,
+ "Print all values of a key in raw format",
+ "net registry getvaluesraw <key>\n"
+ " Print a registry value (raw format)"
+ },
+ {
+ "setvalue",
+ net_registry_setvalue,
+ NET_TRANSPORT_LOCAL,
+ N_("Set a new registry value"),
+ N_("net registry setvalue\n"
+ " Set a new registry value")
+ },
+ {
+ "increment",
+ net_registry_increment,
+ NET_TRANSPORT_LOCAL,
+ N_("Increment a DWORD registry value under a lock"),
+ N_("net registry increment\n"
+ " Increment a DWORD registry value under a lock")
+ },
+ {
+ "deletevalue",
+ net_registry_deletevalue,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a registry value"),
+ N_("net registry deletevalue\n"
+ " Delete a registry value")
+ },
+ {
+ "getsd",
+ net_registry_getsd,
+ NET_TRANSPORT_LOCAL,
+ N_("Get security descriptor"),
+ N_("net registry getsd\n"
+ " Get security descriptor")
+ },
+ {
+ "getsd_sddl",
+ net_registry_getsd_sddl,
+ NET_TRANSPORT_LOCAL,
+ N_("Get security descriptor in sddl format"),
+ N_("net registry getsd_sddl\n"
+ " Get security descriptor in sddl format")
+ },
+ {
+ "setsd_sddl",
+ net_registry_setsd_sddl,
+ NET_TRANSPORT_LOCAL,
+ N_("Set security descriptor from sddl format string"),
+ N_("net registry setsd_sddl\n"
+ " Set security descriptor from sddl format string")
+ },
+ {
+ "import",
+ net_registry_import,
+ NET_TRANSPORT_LOCAL,
+ N_("Import .reg file"),
+ N_("net registry import\n"
+ " Import .reg file")
+ },
+ {
+ "export",
+ net_registry_export,
+ NET_TRANSPORT_LOCAL,
+ N_("Export .reg file"),
+ N_("net registry export\n"
+ " Export .reg file")
+ },
+ {
+ "convert",
+ net_registry_convert,
+ NET_TRANSPORT_LOCAL,
+ N_("Convert .reg file"),
+ N_("net registry convert\n"
+ " Convert .reg file")
+ },
+ {
+ "check",
+ net_registry_check,
+ NET_TRANSPORT_LOCAL,
+ N_("Check a registry database"),
+ N_("net registry check\n"
+ " Check a registry database")
+ },
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ if (!c->display_usage
+ && argc > 0
+ && (strcasecmp_m(argv[0], "convert") != 0)
+ && (strcasecmp_m(argv[0], "check") != 0))
+ {
+ if (!W_ERROR_IS_OK(registry_init_basic())) {
+ return -1;
+ }
+ }
+
+ ret = net_run_function(c, argc, argv, "net registry", func);
+
+ return ret;
+}
diff --git a/source3/utils/net_registry_check.c b/source3/utils/net_registry_check.c
new file mode 100644
index 0000000..6cb9256
--- /dev/null
+++ b/source3/utils/net_registry_check.c
@@ -0,0 +1,1324 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Check the registry database.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Mar 2011
+ */
+
+#include "net_registry_check.h"
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "net.h"
+#include "libcli/security/dom_sid.h"
+#include "libcli/security/secdesc.h"
+#include "cbuf.h"
+#include "srprs.h"
+#include <termios.h>
+#include "util_tdb.h"
+#include "registry/reg_db.h"
+#include "libcli/registry/util_reg.h"
+#include "registry/reg_parse_internal.h"
+#include "interact.h"
+
+/*
+ check tree:
+ + every key has a subkeylist
+ + every key is referenced by the subkeylist of its parent
+ check path:
+ + starts with valid hive
+ + UTF-8 (option to convert ???)
+ + only uppercase
+ + separator ???
+ check value:
+ + REG_DWORD has size 4
+ + REG_QWORD has size 8
+ + STRINGS are zero terminated UTF-16
+*/
+
+struct regval {
+ char *name;
+ uint32_t type;
+ DATA_BLOB data;
+};
+
+struct regkey {
+ char *name;
+ char *path;
+ bool has_subkeylist;
+ bool needs_update;
+ struct regkey *parent;
+ size_t nsubkeys;
+ struct regkey **subkeys;
+ size_t nvalues;
+ struct regval **values;
+ struct security_descriptor *sd;
+};
+
+struct check_ctx {
+ char *fname;
+ struct check_options opt;
+
+ uint32_t version;
+ char sep;
+ struct db_context *idb;
+ struct db_context *odb;
+
+ struct regkey *root; /*dummy key to hold all basekeys*/
+ struct db_context *reg;
+ struct db_context *del;
+
+ bool transaction;
+ char auto_action;
+ char default_action;
+};
+
+static void* talloc_array_append(void *mem_ctx, void* array[], void *ptr)
+{
+ size_t size = array ? talloc_array_length(array) : 1;
+ void **tmp = talloc_realloc(mem_ctx, array, void*, size + 1);
+ if (tmp == NULL) {
+ talloc_free(array);
+ return NULL;
+ }
+ tmp[size-1] = ptr;
+ tmp[size] = NULL;
+ return tmp;
+}
+
+static void regkey_add_subkey(struct regkey *key, struct regkey *subkey)
+{
+ key->subkeys = (struct regkey**)
+ talloc_array_append(key, (void**)key->subkeys, subkey);
+ if (key->subkeys != NULL) {
+ key->nsubkeys++;
+ }
+}
+
+static struct regval* regval_copy(TALLOC_CTX *mem_ctx, const struct regval *val)
+{
+ struct regval *ret = talloc_zero(mem_ctx, struct regval);
+ if (ret == NULL) {
+ goto fail;
+ }
+
+ ret->name = talloc_strdup(ret, val->name);
+ if (ret->name == NULL) {
+ goto fail;
+ }
+
+ ret->data = data_blob_dup_talloc(ret, val->data);
+ if (ret->data.data == NULL) {
+ goto fail;
+ }
+
+ ret->type = val->type;
+
+ return ret;
+fail:
+ talloc_free(ret);
+ return NULL;
+}
+
+static void regkey_add_regval(struct regkey *key, struct regval *val)
+{
+ key->values = (struct regval**)
+ talloc_array_append(key, (void**)key->values, val);
+ if (key->values != NULL) {
+ key->nvalues++;
+ }
+}
+
+static bool tdb_data_read_uint32(TDB_DATA *buf, uint32_t *result)
+{
+ const size_t len = sizeof(uint32_t);
+ if (buf->dsize >= len) {
+ *result = IVAL(buf->dptr, 0);
+ buf->dptr += len;
+ buf->dsize -= len;
+ return true;
+ }
+ return false;
+}
+
+static bool tdb_data_read_cstr(TDB_DATA *buf, char **result)
+{
+ const size_t len = strnlen((char*)buf->dptr, buf->dsize) + 1;
+ if (buf->dsize >= len) {
+ *result = (char*)buf->dptr;
+ buf->dptr += len;
+ buf->dsize -= len;
+ return true;
+ }
+ return false;
+}
+
+static bool tdb_data_read_blob(TDB_DATA *buf, DATA_BLOB *result)
+{
+ TDB_DATA tmp = *buf;
+ uint32_t len;
+ if (!tdb_data_read_uint32(&tmp, &len)) {
+ return false;
+ }
+ if (tmp.dsize >= len) {
+ *buf = tmp;
+ result->data = tmp.dptr;
+ result->length = len;
+ buf->dptr += len;
+ buf->dsize -= len;
+ return true;
+ }
+ return false;
+}
+
+static bool tdb_data_read_regval(TDB_DATA *buf, struct regval *result)
+{
+ TDB_DATA tmp = *buf;
+ struct regval value;
+ if (!tdb_data_read_cstr(&tmp, &value.name)
+ || !tdb_data_read_uint32(&tmp, &value.type)
+ || !tdb_data_read_blob(&tmp, &value.data))
+ {
+ return false;
+ }
+ *buf = tmp;
+ *result = value;
+ return true;
+}
+
+static bool tdb_data_is_cstr(TDB_DATA d) {
+ if (tdb_data_is_empty(d) || (d.dptr[d.dsize-1] != '\0')) {
+ return false;
+ }
+ return strlen((char *)d.dptr) == d.dsize-1;
+}
+
+static TDB_DATA cbuf_make_tdb_data(cbuf *b)
+{
+ return make_tdb_data((void*)cbuf_gets(b, 0), cbuf_getpos(b));
+}
+
+static void remove_all(char *str, char c)
+{
+ char *out=str;
+ while (*str) {
+ if (*str != c) {
+ *out = *str;
+ out++;
+ }
+ str++;
+ }
+ *out = '\0';
+}
+
+static char* parent_path(const char *path, char sep)
+{
+ const char *p = strrchr(path, sep);
+ return p ? talloc_strndup(talloc_tos(), path, p-path) : NULL;
+}
+
+/* return the regkey corresponding to path, create if not yet existing */
+static struct regkey*
+check_ctx_lookup_key(struct check_ctx *ctx, const char *path) {
+ struct regkey *ret = NULL;
+ NTSTATUS status;
+ TDB_DATA val = tdb_null;
+
+ if ( path == NULL) {
+ return ctx->root;
+ }
+
+ status = dbwrap_fetch(ctx->reg, ctx, string_term_tdb_data(path), &val);
+ if (NT_STATUS_IS_OK(status)) {
+ if (ctx->opt.verbose) {
+ printf("Open: %s\n", path);
+ }
+ ret = *(struct regkey**)val.dptr;
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ /* not yet existing, create */
+ char *pp;
+ if (ctx->opt.verbose) {
+ printf("New: %s\n", path);
+ }
+ ret = talloc_zero(ctx, struct regkey);
+ if (ret == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+ ret->path = talloc_strdup(ret, path);
+
+ pp = parent_path(path, ctx->sep);
+ ret->parent = check_ctx_lookup_key(ctx, pp);
+ regkey_add_subkey(ret->parent, ret);
+ TALLOC_FREE(pp);
+
+ /* the dummy root key has no subkeylist so set the name */
+ if (ret->parent == ctx->root) {
+ ret->name = talloc_strdup(ret, path);
+ }
+
+ dbwrap_store(ctx->reg, string_term_tdb_data(path),
+ make_tdb_data((void*)&ret, sizeof(ret)), 0);
+ } else {
+ DEBUG(0, ("lookup key: failed to fetch %s: %s\n", path,
+ nt_errstr(status)));
+ }
+done:
+ talloc_free(val.dptr);
+ return ret;
+}
+
+static struct check_ctx* check_ctx_create(TALLOC_CTX *mem_ctx, const char *db,
+ const struct check_options *opt)
+{
+ struct check_ctx *ctx = talloc_zero(mem_ctx, struct check_ctx);
+
+ ctx->opt = *opt;
+ ctx->reg = db_open_rbt(ctx);
+ ctx->del = db_open_rbt(ctx);
+ ctx->root = talloc_zero(ctx, struct regkey);
+ ctx->fname = talloc_strdup(ctx, db);
+
+ if (opt->automatic && (opt->output == NULL)) {
+ ctx->opt.repair = true;
+ ctx->opt.output = ctx->fname;
+ }
+
+ if (opt->repair) {
+ if (opt->output) {
+ d_fprintf(stderr, "You can not specify --output "
+ "with --repair\n");
+ goto fail;
+ } else {
+ ctx->opt.output = ctx->fname;
+ }
+ }
+
+ ctx->default_action = 'r';
+ return ctx;
+fail:
+ talloc_free(ctx);
+ return NULL;
+}
+
+static bool check_ctx_open_output(struct check_ctx *ctx)
+{
+ int oflags = O_RDWR | O_CREAT ;
+
+ if (ctx->opt.output == NULL) {
+ return true;
+ }
+
+ if (!ctx->opt.repair) {
+ if (!ctx->opt.wipe) {
+ oflags |= O_EXCL;
+ }
+ ctx->opt.wipe = true;
+ }
+
+ ctx->odb = db_open(ctx, ctx->opt.output, 0, TDB_DEFAULT, oflags, 0644,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (ctx->odb == NULL) {
+ d_fprintf(stderr,
+ _("Could not open db (%s) for writing: %s\n"),
+ ctx->opt.output, strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+
+static bool check_ctx_open_input(struct check_ctx *ctx) {
+ ctx->idb = db_open(ctx, ctx->fname, 0, TDB_DEFAULT, O_RDONLY, 0,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (ctx->idb == NULL) {
+ d_fprintf(stderr,
+ _("Could not open db (%s) for reading: %s\n"),
+ ctx->fname, strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+static bool check_ctx_transaction_start(struct check_ctx *ctx) {
+ if (ctx->odb == NULL) {
+ return true;
+ }
+ if (dbwrap_transaction_start(ctx->odb) != 0) {
+ DEBUG(0, ("transaction_start failed\n"));
+ return false;
+ }
+ ctx->transaction = true;
+ return true;
+}
+
+static void check_ctx_transaction_stop(struct check_ctx *ctx, bool ok) {
+ if (!ctx->transaction) {
+ return;
+ }
+ if (!ctx->opt.test && ok) {
+ d_printf("Committing changes\n");
+ if (dbwrap_transaction_commit(ctx->odb) != 0) {
+ DEBUG(0, ("transaction_commit failed\n"));
+ }
+ } else {
+ d_printf("Discarding changes\n");
+ dbwrap_transaction_cancel(ctx->odb);
+ }
+}
+
+static bool read_info(struct check_ctx *ctx, const char *key, TDB_DATA val)
+{
+ if (val.dsize==sizeof(uint32_t) && strcmp(key, "version")==0) {
+ uint32_t v = IVAL(val.dptr, 0);
+ printf("INFO: %s = %d\n", key, v);
+ return true;
+ }
+ printf("INFO: %s = <invalid>\n", key);
+ return false;
+}
+
+static bool is_all_upper(const char *str) {
+ bool ret;
+ char *tmp = talloc_strdup(talloc_tos(), str);
+ if (!strupper_m(tmp)) {
+ talloc_free(tmp);
+ return false;
+ }
+ ret = (strcmp(tmp, str) == 0);
+ talloc_free(tmp);
+ return ret;
+}
+
+static void move_to_back(struct regkey *key, struct regkey *subkey)
+{
+ struct regkey **ptr;
+ size_t nidx;
+
+ DEBUG(5, ("Move to back subkey \"%s\" of \"%s\"\n",
+ subkey->path, key->path));
+
+ for (ptr=key->subkeys; *ptr != subkey; ptr++)
+ ;
+
+ nidx = ptr + 1 - key->subkeys;
+ memmove(ptr, ptr+1, (key->nsubkeys - nidx) * sizeof(*ptr));
+
+ key->subkeys[key->nsubkeys-1] = subkey;
+}
+
+static void set_subkey_name(struct check_ctx *ctx, struct regkey *key,
+ const char *name, int nlen)
+{
+ char *path = key->path;
+ TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+ char *p;
+ struct regkey *subkey;
+ char *nname = talloc_strndup(mem_ctx, name, nlen);
+ remove_all(nname, ctx->sep);
+
+ if (strncmp(name, nname, nlen) != 0) {
+ /* XXX interaction: delete/edit */
+ printf("Warning: invalid name: \"%s\" replace with \"%s\"\n",
+ name, nname);
+ key->needs_update = true;
+ }
+ p = talloc_asprintf_strupper_m(mem_ctx, "%s%c%s",
+ path, ctx->sep, nname);
+ subkey = check_ctx_lookup_key(ctx, p);
+ if (subkey->name) {
+ bool do_replace = false;
+
+ if (strcmp(subkey->name, nname) != 0) {
+ int action;
+ char default_action;
+
+ if (is_all_upper(nname)) {
+ default_action = 'o';
+ } else {
+ default_action = 'n';
+ }
+
+ printf("Conflicting subkey names of [%s]: "
+ "old: \"%s\", new: \"%s\"\n",
+ key->path, subkey->name, nname);
+
+ if (ctx->opt.output == NULL || ctx->opt.automatic) {
+ action = default_action;
+ } else {
+ do {
+ action = interact_prompt(
+ "choose spelling [o]ld, [n]ew,"
+ "or [e]dit", "one",
+ default_action);
+ if (action == 'e') {
+ printf("Sorry, edit is not yet "
+ "implemented here...\n");
+ }
+ } while (action == 'e');
+ }
+
+ if (action == 'n') {
+ do_replace = true;
+ }
+ }
+
+ if (do_replace) {
+ if (ctx->opt.verbose) {
+ printf("Replacing name: %s: \"%s\""
+ " -> \"%s\"\n", path,
+ subkey->name, nname);
+ }
+ TALLOC_FREE(subkey->name);
+ subkey->name = talloc_steal(subkey, nname);
+ key->needs_update = true;
+ }
+ } else {
+ if (ctx->opt.verbose) {
+ printf("Set name: %s: \"%s\"\n", path, nname);
+ }
+ subkey->name = talloc_steal(subkey, nname);
+ }
+
+ move_to_back(key, subkey);
+ TALLOC_FREE(mem_ctx);
+}
+
+static void
+read_subkeys(struct check_ctx *ctx, const char *path, TDB_DATA val, bool update)
+{
+ uint32_t num_items, found_items = 0;
+ char *subkey;
+ struct regkey *key = check_ctx_lookup_key(ctx, path);
+
+ key->needs_update |= update;
+
+ /* printf("SUBKEYS: %s\n", path); */
+ if (key->has_subkeylist) {
+ printf("Duplicate subkeylist \"%s\"\n",
+ path);
+ found_items = key->nsubkeys;
+ }
+
+ /* exists as defined by regdb_key_exists() */
+ key->has_subkeylist = true;
+
+ /* name is set if a key is referenced by the */
+ /* subkeylist of its parent. */
+
+ if (!tdb_data_read_uint32(&val, &num_items) ) {
+ printf("Invalid subkeylist: \"%s\"\n", path);
+ return;
+ }
+
+ while (tdb_data_read_cstr(&val, &subkey)) {
+ /* printf(" SUBKEY: %s\n", subkey); */
+ set_subkey_name(ctx, key, subkey, strlen(subkey));
+ found_items++;
+ }
+
+ if (val.dsize != 0) {
+ printf("Subkeylist of \"%s\": trailing: \"%.*s\"\n",
+ path, (int)val.dsize, val.dptr);
+ /* ask: best effort, delete or edit?*/
+ set_subkey_name(ctx, key, (char*)val.dptr, val.dsize);
+ found_items++;
+ key->needs_update = true;
+ }
+
+ if (num_items != found_items) {
+ printf("Subkeylist of \"%s\": invalid number of subkeys, "
+ "expected: %d got: %d\n", path, num_items, found_items);
+ key->needs_update = true;
+ }
+
+}
+
+static void read_values(struct check_ctx *ctx, const char *path, TDB_DATA val)
+{
+ struct regkey *key = check_ctx_lookup_key(ctx, path);
+ uint32_t num_items, found_items;
+ struct regval value;
+
+ /* printf("VALUES: %s\n", path); */
+
+ if (!tdb_data_read_uint32(&val, &num_items) ) {
+ printf("Invalid valuelist: \"%s\"\n", path);
+ return;
+ }
+
+ found_items=0;
+ while (tdb_data_read_regval(&val, &value)) {
+ /* printf(" VAL: %s type: %s(%d) length: %d\n", value.name, */
+ /* str_regtype(value.type), value.type, */
+ /* (int)value.data.length); */
+ regkey_add_regval(key, regval_copy(key, &value));
+ found_items++;
+ }
+
+ if (num_items != found_items) {
+ printf("Valuelist of \"%s\": invalid number of values, "
+ "expected: %d got: %d\n", path, num_items, found_items);
+ key->needs_update = true;
+ }
+
+ if (val.dsize != 0) {
+ printf("Valuelist of \"%s\": trailing: \"%*s\"\n", path,
+ (int)val.dsize, val.dptr);
+ key->needs_update = true;
+ /* XXX best effort ??? */
+ /* ZERO_STRUCT(value); */
+ /* if (tdb_data_read_cstr(&val, &value.name) */
+ /* && tdb_data_read_uint32(&val, &value.type)) */
+ /* { */
+ /* uint32_t len = -1; */
+ /* tdb_data_read_uint32(&val, &len); */
+ /* ... */
+ /* found_items ++; */
+ /* regkey_add_regval(key, regval_copy(key, value)); */
+ /* } */
+ }
+ if (found_items == 0) {
+ printf("Valuelist of \"%s\" empty\n", path);
+ key->needs_update = true;
+ }
+}
+
+static bool read_sorted(struct check_ctx *ctx, const char *path, TDB_DATA val)
+{
+ if (ctx->version >= 3) {
+ return false;
+ }
+
+ if ((val.dptr == NULL) || (val.dsize<4)) {
+ return false;
+ }
+
+ /* ToDo: check */
+ /* struct regkey *key = check_ctx_lookup_key(ctx, path); */
+ /* printf("SORTED: %s\n", path); */
+ return true;
+}
+
+static bool read_sd(struct check_ctx *ctx, const char *path, TDB_DATA val)
+{
+ NTSTATUS status;
+ struct regkey *key = check_ctx_lookup_key(ctx, path);
+ /* printf("SD: %s\n", path); */
+
+ status = unmarshall_sec_desc(key, val.dptr, val.dsize, &key->sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to read SD of %s: %s\n",
+ path, nt_errstr(status)));
+ }
+ return true;
+}
+
+static bool srprs_path(const char **ptr, const char* prefix, char sep,
+ const char **ppath)
+{
+ const char *path, *pos = *ptr;
+ if (prefix != NULL) {
+ if (!srprs_str(&pos, prefix, -1) || !srprs_char(&pos, sep) ) {
+ return false;
+ }
+ }
+ path = pos;
+ if ( !srprs_hive(&pos, NULL) ) {
+ return false;
+ }
+ if ( !srprs_eos(&pos) && !srprs_char(&pos, sep) ) {
+ return false;
+ }
+ *ppath = path;
+ *ptr = strchr(pos, '\0');
+ return true;
+}
+
+/* Fixme: this doesn't work in the general multibyte char case.
+ see string_replace()
+*/
+static bool normalize_path_internal(char* path, char sep) {
+ size_t len = strlen(path);
+ const char *orig = talloc_strndup(talloc_tos(), path, len);
+ char *optr = path, *iptr = path;
+ bool changed;
+
+ while (*iptr == sep ) {
+ iptr++;
+ }
+ while (*iptr) {
+ *optr = *iptr;
+ if (*iptr == sep) {
+ while (*iptr == sep) {
+ iptr++;
+ }
+ if (*iptr) {
+ optr++;
+ }
+ } else {
+ iptr++;
+ optr++;
+ }
+ }
+ *optr = '\0';
+
+ if (!strupper_m(path)) {
+ talloc_free(discard_const(orig));
+ return false;
+ }
+ changed = (strcmp(orig, path) != 0);
+ talloc_free(discard_const(orig));
+ return changed;
+}
+
+static bool normalize_path(char* path, char sep) {
+ static const char* SEPS = "\\/";
+ char* firstsep = strpbrk(path, SEPS);
+ bool wrong_sep = (firstsep && (*firstsep != sep));
+
+ assert (strchr(SEPS, sep));
+
+ if (wrong_sep) {
+ string_replace(path, *firstsep, sep);
+ }
+ return normalize_path_internal(path, sep) || wrong_sep;
+}
+
+static int check_tdb_action(struct db_record *rec, void *check_ctx)
+{
+ struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+ TALLOC_CTX *frame = talloc_stackframe();
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ TDB_DATA rec_key = dbwrap_record_get_key(rec);
+ char *key;
+ bool invalid_path = false;
+ bool once_more;
+ bool first_iter = true;
+
+ if (!tdb_data_is_cstr(rec_key)) {
+ printf("Key is not zero terminated: \"%.*s\"\ntry to go on.\n",
+ (int)rec_key.dsize, rec_key.dptr);
+ invalid_path = true;
+ }
+ key = talloc_strndup(frame, (char*)rec_key.dptr, rec_key.dsize);
+
+ do {
+ const char *path, *pos = key;
+ once_more = false;
+
+ if (srprs_str(&pos, "INFO/", -1)) {
+ if ( read_info(ctx, pos, val) ) {
+ break;
+ }
+ invalid_path = true;
+ /* ask: mark invalid */
+ } else if (srprs_str(&pos, "__db_sequence_number__", -1)) {
+ printf("Skip key: \"%.*s\"\n",
+ (int)rec_key.dsize, rec_key.dptr);
+ /* skip: do nothing + break */
+ break;
+
+ } else if (normalize_path(key, ctx->sep)) {
+ printf("Unnormal key: \"%.*s\"\n",
+ (int)rec_key.dsize, rec_key.dptr);
+ printf("Normalize to: \"%s\"\n", key);
+ invalid_path = true;
+ } else if (srprs_path(&pos, NULL,
+ ctx->sep, &path))
+ {
+ read_subkeys(ctx, path, val, invalid_path);
+ break;
+ } else if (srprs_path(&pos, REG_VALUE_PREFIX,
+ ctx->sep, &path))
+ {
+ read_values(ctx, path, val);
+ break;
+ } else if (srprs_path(&pos, REG_SECDESC_PREFIX,
+ ctx->sep, &path))
+ {
+ read_sd(ctx, path, val);
+ break;
+ } else if (srprs_path(&pos, REG_SORTED_SUBKEYS_PREFIX,
+ ctx->sep, &path))
+ {
+ if (!read_sorted(ctx, path, val)) {
+ /* delete: mark invalid + break */
+ printf("Invalid sorted subkeys for: \"%s\"\n", path);
+ invalid_path = true;
+ key = NULL;
+ }
+ break;
+ } else {
+ printf("Unrecognized key: \"%.*s\"\n",
+ (int)rec_key.dsize, rec_key.dptr);
+ invalid_path = true;
+ }
+
+ if (invalid_path) {
+ unsigned char action;
+ if (ctx->opt.output == NULL) {
+ action = first_iter ? 'r' : 's';
+ } else if (ctx->opt.automatic) {
+ action = first_iter ? 'r' : 'd';
+ } else if (ctx->auto_action != '\0') {
+ action = ctx->auto_action;
+ } else {
+ action = interact_prompt("[s]kip,[S]kip all,"
+ "[d]elete,[D]elete all"
+ ",[e]dit,[r]etry"
+ , "sder",
+ ctx->default_action);
+ }
+ if (isupper(action)) {
+ action = tolower(action);
+ ctx->auto_action = action;
+ }
+ ctx->default_action = action;
+ switch (action) {
+ case 's': /* skip */
+ invalid_path = false;
+ break;
+ case 'd': /* delete */
+ invalid_path = true;
+ key = NULL;
+ break;
+ case 'e': /* edit */ {
+ char *p = interact_edit(frame, key);
+ if (p) {
+ talloc_free(key);
+ key = p;
+ }
+ FALL_THROUGH;
+ }
+ case 'r': /* retry */
+ once_more = true;
+ break;
+ }
+ }
+ first_iter = false;
+ } while (once_more);
+
+ if (invalid_path) {
+ dbwrap_store(ctx->del, rec_key, string_term_tdb_data(key), 0);
+ }
+
+ talloc_free(frame);
+ return 0;
+}
+
+static bool get_version(struct check_ctx *ctx) {
+ static const uint32_t curr_version = REGDB_CODE_VERSION;
+ uint32_t version = ctx->opt.version ? ctx->opt.version : curr_version;
+ uint32_t info_version = 0;
+ NTSTATUS status;
+
+ status = dbwrap_fetch_uint32_bystring(ctx->idb, "INFO/version",
+ &info_version);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Warning: no INFO/version found!\n");
+ /* info_version = guess_version(ctx); */
+ }
+
+ if (ctx->opt.version) {
+ version = ctx->opt.version;
+ } else if (ctx->opt.implicit_db) {
+ version = curr_version;
+ } else {
+ version = info_version;
+ }
+
+ if (!version) {
+ printf("Couldn't determine registry format version, "
+ "specify with --reg-version\n");
+ return false;
+ }
+
+
+ if ( version != info_version ) {
+ if (ctx->opt.force || !ctx->opt.repair) {
+ printf("Warning: overwrite registry format "
+ "version %d with %d\n", info_version, version);
+ } else {
+ printf("Warning: found registry format version %d but "
+ "expected %d, use --force to proceed.\n", info_version, version);
+ return false;
+ }
+ }
+
+ ctx->version = version;
+ ctx->sep = (version > 1) ? '\\' : '/';
+
+ return true;
+}
+
+static bool
+dbwrap_store_verbose(struct db_context *db, const char *key, TDB_DATA nval)
+{
+ TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+ TDB_DATA oval;
+ NTSTATUS status;
+
+ status = dbwrap_fetch_bystring(db, mem_ctx, key, &oval);
+ if (NT_STATUS_IS_OK(status)) {
+ if (tdb_data_equal(nval, oval)) {
+ goto done;
+ }
+ printf("store %s:\n overwrite: %s\n with: %s\n", key,
+ tdb_data_string(mem_ctx, oval),
+ tdb_data_string(mem_ctx, nval));
+
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ printf("store %s:\n write: %s\n", key,
+ tdb_data_string(mem_ctx, nval));
+ } else {
+ printf ("store %s:\n failed to fetch old value: %s\n", key,
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dbwrap_store_bystring(db, key, nval, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf ("store %s failed: %s\n", key, nt_errstr(status));
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return NT_STATUS_IS_OK(status);
+}
+
+static bool
+dbwrap_store_uint32_verbose(struct db_context *db, const char *key, uint32_t nval)
+{
+ uint32_t oval;
+ NTSTATUS status;
+
+ status = dbwrap_fetch_uint32_bystring(db, key, &oval);
+ if (NT_STATUS_IS_OK(status)) {
+ if (nval == oval) {
+ goto done;
+ }
+ printf("store %s:\n overwrite: %d\n with: %d\n", key,
+ (int)oval, (int)nval);
+
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ printf("store %s:\n write: %d\n", key, (int)nval);
+ } else {
+ printf ("store %s:\n failed to fetch old value: %s\n", key,
+ nt_errstr(status));
+ goto done;
+ }
+
+ status = dbwrap_store_uint32_bystring(db, key, nval);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf ("store %s failed: %s\n", key, nt_errstr(status));
+ }
+
+done:
+ return NT_STATUS_IS_OK(status);
+}
+
+static int cmp_keynames(char **p1, char **p2)
+{
+ return strcasecmp_m(*p1, *p2);
+}
+
+static bool
+write_subkeylist(struct db_context *db, struct regkey *key, char sep)
+{
+ cbuf *buf = cbuf_new(talloc_tos());
+ size_t i;
+ bool ret;
+
+ cbuf_putdw(buf, key->nsubkeys);
+
+ for (i=0; i < key->nsubkeys; i++) {
+ struct regkey *subkey = key->subkeys[i];
+ const char *name = subkey->name;
+ if (name == NULL) {
+ printf("Warning: no explicit name for key %s\n",
+ subkey->path);
+ name = strrchr_m(subkey->path, sep);
+ assert(name);
+ name ++;
+ }
+ cbuf_puts(buf, name, -1);
+ cbuf_putc(buf, '\0');
+ }
+
+ ret = dbwrap_store_verbose(db, key->path, cbuf_make_tdb_data(buf));
+
+ talloc_free(buf);
+ return ret;
+}
+
+static bool write_sorted(struct db_context *db, struct regkey *key, char sep)
+{
+ cbuf *buf = cbuf_new(talloc_tos());
+ char *path;
+ size_t i;
+ bool ret = false;
+ char **sorted = talloc_zero_array(buf, char*, key->nsubkeys);
+ int offset = (1 + key->nsubkeys) * sizeof(uint32_t);
+
+ for (i=0; i < key->nsubkeys; i++) {
+ sorted[i] = talloc_strdup_upper(sorted, key->subkeys[i]->name);
+ }
+ TYPESAFE_QSORT(sorted, key->nsubkeys, cmp_keynames);
+
+ cbuf_putdw(buf, key->nsubkeys);
+ for (i=0; i < key->nsubkeys; i++) {
+ cbuf_putdw(buf, offset);
+ offset += strlen(sorted[i]) + 1;
+ }
+ for (i=0; i < key->nsubkeys; i++) {
+ cbuf_puts(buf, sorted[i], -1);
+ cbuf_putc(buf, '\0');
+ }
+
+ path = talloc_asprintf(buf, "%s%c%s", REG_SORTED_SUBKEYS_PREFIX, sep,
+ key->path);
+ if (path == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ ret = dbwrap_store_verbose(db, path, cbuf_make_tdb_data(buf));
+done:
+ talloc_free(buf);
+ return ret;
+}
+
+static bool write_values(struct db_context *db, struct regkey *key, char sep)
+{
+ cbuf *buf = cbuf_new(talloc_tos());
+ char *path;
+ size_t i;
+ bool ret = false;
+
+ cbuf_putdw(buf, key->nvalues);
+ for (i=0; i < key->nvalues; i++) {
+ struct regval *val = key->values[i];
+ cbuf_puts(buf, val->name, -1);
+ cbuf_putc(buf, '\0');
+ cbuf_putdw(buf, val->type);
+ cbuf_putdw(buf, val->data.length);
+ cbuf_puts(buf, (void*)val->data.data, val->data.length);
+ }
+
+ path = talloc_asprintf(buf, "%s%c%s", REG_VALUE_PREFIX, sep, key->path);
+ if (path == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ ret = dbwrap_store_verbose(db, path, cbuf_make_tdb_data(buf));
+done:
+ talloc_free(buf);
+ return ret;
+}
+
+static bool write_sd(struct db_context *db, struct regkey *key, char sep)
+{
+ TDB_DATA sd;
+ NTSTATUS status;
+ char *path;
+ bool ret = false;
+ TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+
+ status = marshall_sec_desc(mem_ctx, key->sd, &sd.dptr, &sd.dsize);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("marshall sec desc %s failed: %s\n",
+ key->path, nt_errstr(status));
+ goto done;
+ }
+ path = talloc_asprintf(mem_ctx, "%s%c%s", REG_SECDESC_PREFIX,
+ sep, key->path);
+ if (path == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ ret = dbwrap_store_verbose(db, path, sd);
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+
+static int check_write_db_action(struct db_record *rec, void *check_ctx)
+{
+ struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+ TDB_DATA rec_val = dbwrap_record_get_value(rec);
+ struct regkey *key = *(struct regkey**)rec_val.dptr;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /* write subkeylist */
+ if ((ctx->version > 2) || (key->nsubkeys > 0) || (key->has_subkeylist)) {
+ write_subkeylist(ctx->odb, key, ctx->sep);
+ }
+
+ /* write sorted subkeys */
+ if ((ctx->version < 3) && (key->nsubkeys > 0)) {
+ write_sorted(ctx->odb, key, ctx->sep);
+ }
+
+ /* write value list */
+ if (key->nvalues > 0) {
+ write_values(ctx->odb, key, ctx->sep);
+ }
+
+ /* write sd */
+ if (key->sd) {
+ write_sd(ctx->odb, key, ctx->sep);
+ }
+
+ talloc_free(frame);
+ return 0;
+}
+
+static int fix_tree_action(struct db_record *rec, void *check_ctx)
+{
+ struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+ TDB_DATA rec_key = dbwrap_record_get_key(rec);
+ TDB_DATA rec_val = dbwrap_record_get_value(rec);
+ struct regkey* key = *(struct regkey**)rec_val.dptr;
+ if (ctx->opt.verbose) {
+ printf("Check Tree: %s\n", key->path);
+ }
+
+ assert (strncmp(key->path, (char*)rec_key.dptr, rec_key.dsize) == 0);
+
+ /* assert(dbwrap_exists(ctx->db, string_term_tdb_data(key->path)) */
+ /* == key->exists); */
+
+ if (key->needs_update) {
+ printf("Update key: \"%s\"\n", key->path);
+ if ((ctx->version > 2) || (key->nsubkeys > 0)) {
+ write_subkeylist(ctx->odb, key, ctx->sep);
+ }
+ if ((ctx->version <= 2) && (key->nsubkeys > 0)) {
+ write_sorted(ctx->odb, key, ctx->sep);
+ }
+ if (key->nvalues > 0) {
+ write_values(ctx->odb, key, ctx->sep);
+ }
+ if (key->sd) {
+ write_sd(ctx->odb, key, ctx->sep);
+ }
+ } else if (!key->has_subkeylist) {
+ if ((ctx->version > 2) || (key->nsubkeys > 0)) {
+ printf("Missing subkeylist: %s\n", key->path);
+ write_subkeylist(ctx->odb, key, ctx->sep);
+ }
+ }
+
+ if (key->name == NULL && key->parent->has_subkeylist) {
+ printf("Key not referenced by the its parents subkeylist: %s\n",
+ key->path);
+ write_subkeylist(ctx->odb, key->parent, ctx->sep);
+ }
+
+/* XXX check that upcase(name) matches last part of path ??? */
+
+ return 0;
+}
+
+
+/* give the same warnings as fix_tree_action */
+static int check_tree_action(struct db_record *rec, void *check_ctx)
+{
+ struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+ TDB_DATA rec_key = dbwrap_record_get_key(rec);
+ TDB_DATA rec_val = dbwrap_record_get_value(rec);
+ struct regkey* key = *(struct regkey**)rec_val.dptr;
+ if (ctx->opt.verbose) {
+ printf("Check Tree: %s\n", key->path);
+ }
+
+ assert (strncmp(key->path, (char*)rec_key.dptr, rec_key.dsize) == 0);
+
+ if (!key->has_subkeylist) {
+ if ((ctx->version > 2) || (key->nsubkeys > 0)) {
+ printf("Missing subkeylist: %s\n", key->path);
+ }
+ }
+
+ if (key->name == NULL && key->parent->has_subkeylist) {
+ printf("Key not referenced by the its parents subkeylist: %s\n",
+ key->path);
+ }
+
+ return 0;
+}
+
+static int delete_invalid_action(struct db_record *rec, void* check_ctx)
+{
+ NTSTATUS status;
+ struct check_ctx *ctx = (struct check_ctx*)check_ctx;
+ TDB_DATA rec_key = dbwrap_record_get_key(rec);
+ TDB_DATA rec_val = dbwrap_record_get_value(rec);
+
+
+ printf("Delete key: \"%.*s\"",(int)rec_key.dsize, rec_key.dptr);
+ if (rec_val.dsize > 0) {
+ printf(" in favour of \"%s\"\n", rec_val.dptr);
+ } else {
+ putc('\n', stdout);
+ }
+
+ status = dbwrap_delete(ctx->odb, rec_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("delete key \"%.*s\" failed!\n",
+ (int)rec_key.dsize, rec_key.dptr);
+ return -1;
+ }
+ return 0;
+}
+
+static bool check_ctx_check_tree(struct check_ctx *ctx) {
+ NTSTATUS status;
+
+ status = dbwrap_traverse(ctx->reg, check_tree_action, ctx, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("check traverse failed: %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ return true;
+}
+static bool check_ctx_fix_inplace(struct check_ctx *ctx) {
+ NTSTATUS status;
+ status = dbwrap_traverse(ctx->reg, fix_tree_action, ctx, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("fix traverse failed: %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ status = dbwrap_traverse(ctx->del, delete_invalid_action, ctx, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("delete traverse failed: %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ if (!dbwrap_store_uint32_verbose(ctx->odb, "INFO/version", ctx->version)) {
+ DEBUG(0, ("storing version failed: %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ return true;
+}
+
+static bool check_ctx_write_new_db(struct check_ctx *ctx) {
+ NTSTATUS status;
+
+ assert(ctx->odb);
+
+ if (ctx->opt.wipe) {
+ int ret = dbwrap_wipe(ctx->odb);
+ if (ret != 0) {
+ DEBUG(0, ("wiping %s failed\n", ctx->opt.output));
+ return false;
+ }
+ }
+
+ status = dbwrap_traverse(ctx->reg, check_write_db_action, ctx, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("traverse2 failed: %s\n", nt_errstr(status)));
+ return false;
+ }
+
+ status = dbwrap_store_uint32_bystring(ctx->odb, "INFO/version",
+ ctx->version);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("write version failed: %s\n", nt_errstr(status)));
+ return false;
+ }
+ return true;
+}
+
+int net_registry_check_db(const char *name, const struct check_options *opt)
+{
+ NTSTATUS status;
+ int ret = -1;
+ struct check_ctx *ctx = check_ctx_create(talloc_tos(), name, opt);
+ if (ctx==NULL) {
+ goto done;
+ }
+
+ d_printf("Check database: %s\n", name);
+
+ /* 1. open output RW */
+ if (!check_ctx_open_output(ctx)) {
+ goto done;
+ }
+
+ /* 2. open input RO */
+ if (!check_ctx_open_input(ctx)) {
+ goto done;
+ }
+
+ if (opt->lock && !check_ctx_transaction_start(ctx)) {
+ goto done;
+ }
+
+ if (!get_version(ctx)) {
+ goto done;
+ }
+
+ status = dbwrap_traverse_read(ctx->idb, check_tdb_action, ctx, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("check traverse failed: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ if (!opt->lock && !check_ctx_transaction_start(ctx)) {
+ goto done;
+ }
+
+ if (ctx->opt.repair && !ctx->opt.wipe) {
+ if (!check_ctx_fix_inplace(ctx)) {
+ goto done;
+ }
+ } else {
+ if (!check_ctx_check_tree(ctx)) {
+ goto done;
+ }
+ if (ctx->odb) {
+ if (!check_ctx_write_new_db(ctx)) {
+ goto done;
+ }
+ }
+ }
+ ret = 0;
+done:
+ check_ctx_transaction_stop(ctx, ret == 0);
+
+ talloc_free(ctx);
+ return ret;
+}
+
+/*Local Variables:*/
+/*mode: c*/
+/*End:*/
diff --git a/source3/utils/net_registry_check.h b/source3/utils/net_registry_check.h
new file mode 100644
index 0000000..6ed68d8
--- /dev/null
+++ b/source3/utils/net_registry_check.h
@@ -0,0 +1,52 @@
+/*
+ * Samba Unix/Linux SMB client library
+ *
+ * Copyright (C) Gregor Beck 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @brief Check the registry database.
+ * @author Gregor Beck <gb@sernet.de>
+ * @date Jun 2011
+ */
+
+#ifndef NET_REGISTRY_CHECK_H
+#define NET_REGISTRY_CHECK_H
+
+#include <stdbool.h>
+
+struct net_context;
+
+struct check_options {
+ bool test;
+ bool verbose;
+ bool lock;
+ bool automatic;
+ bool force;
+ bool repair;
+ int version;
+ const char *output;
+ bool wipe;
+ bool implicit_db;
+};
+
+int net_registry_check_db(const char* db, const struct check_options* opts);
+
+#endif /* NET_REGISTRY_CHECK_H */
+
+/*Local Variables:*/
+/*mode: c*/
+/*End:*/
diff --git a/source3/utils/net_registry_util.c b/source3/utils/net_registry_util.c
new file mode 100644
index 0000000..50b8a8a
--- /dev/null
+++ b/source3/utils/net_registry_util.c
@@ -0,0 +1,177 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * registry utility functions
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "registry.h"
+#include "utils/net_registry_util.h"
+#include "utils/net.h"
+#include "../libcli/registry/util_reg.h"
+
+void print_registry_key(const char *keyname, NTTIME *modtime)
+{
+ const char *ts = _("None");
+ char *freeme = NULL;
+
+ if (modtime != 0) {
+ freeme = http_timestring(talloc_tos(),
+ nt_time_to_unix(*modtime));
+ ts = freeme;
+ }
+
+ d_printf(_("Keyname = %s\n"), keyname);
+ d_printf(_("Modtime = %s\n"), ts);
+ d_printf("\n");
+
+ TALLOC_FREE(freeme);
+}
+
+void print_registry_value(const struct registry_value *valvalue, bool raw)
+{
+ if (!raw) {
+ d_printf(_("Type = %s\n"),
+ str_regtype(valvalue->type));
+ }
+ switch(valvalue->type) {
+ case REG_DWORD: {
+ uint32_t v = 0;
+ if (valvalue->data.length >= 4) {
+ v = IVAL(valvalue->data.data, 0);
+ }
+ if (!raw) {
+ d_printf(_("Value = "));
+ }
+ d_printf("%u\n", v);
+ break;
+ }
+ case REG_SZ:
+ case REG_EXPAND_SZ: {
+ const char *s;
+
+ if (!pull_reg_sz(talloc_tos(), &valvalue->data, &s)) {
+ break;
+ }
+ if (!raw) {
+ d_printf(_("Value = \""));
+ }
+ d_printf("%s", s);
+ if (!raw) {
+ d_printf("\"");
+ }
+ d_printf("\n");
+ break;
+ }
+ case REG_MULTI_SZ: {
+ uint32_t j;
+ const char **a;
+
+ if (!pull_reg_multi_sz(talloc_tos(), &valvalue->data, &a)) {
+ break;
+ }
+ for (j = 0; a[j] != NULL; j++) {
+ if (!raw) {
+ d_printf(_("Value[%3.3d] = \""), j);
+ }
+ d_printf("%s", a[j]);
+ if (!raw) {
+ d_printf("\"");
+ }
+ d_printf("\n");
+ }
+ break;
+ }
+ case REG_BINARY:
+ if (!raw) {
+ d_printf(_("Value = "));
+ }
+ d_printf(_("%d bytes\n"), (int)valvalue->data.length);
+ break;
+ default:
+ if (!raw) {
+ d_printf(_("Value = "));
+ }
+ d_printf(_("<unprintable>\n"));
+ break;
+ }
+}
+
+void print_registry_value_with_name(const char *valname,
+ const struct registry_value *valvalue)
+{
+ d_printf(_("Valuename = %s\n"), valname);
+ print_registry_value(valvalue, false);
+ d_printf("\n");
+}
+
+/**
+ * Split path into hive name and subkeyname
+ * normalizations performed:
+ * - if the path contains no '\\' characters,
+ * assume that the legacy format of using '/'
+ * as a separator is used and convert '/' to '\\'
+ * - strip trailing '\\' chars
+ */
+WERROR split_hive_key(TALLOC_CTX *ctx, const char *path, char **hivename,
+ char **subkeyname)
+{
+ char *p;
+ const char *tmp_subkeyname;
+
+ if ((path == NULL) || (hivename == NULL) || (subkeyname == NULL)) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (strlen(path) == 0) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ if (strchr(path, '\\') == NULL) {
+ *hivename = talloc_string_sub(ctx, path, "/", "\\");
+ } else {
+ *hivename = talloc_strdup(ctx, path);
+ }
+
+ if (*hivename == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* strip trailing '\\' chars */
+ p = strrchr(*hivename, '\\');
+ while ((p != NULL) && (p[1] == '\0')) {
+ *p = '\0';
+ p = strrchr(*hivename, '\\');
+ }
+
+ p = strchr(*hivename, '\\');
+
+ if ((p == NULL) || (*p == '\0')) {
+ /* just the hive - no subkey given */
+ tmp_subkeyname = "";
+ } else {
+ *p = '\0';
+ tmp_subkeyname = p+1;
+ }
+ *subkeyname = talloc_strdup(ctx, tmp_subkeyname);
+ if (*subkeyname == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ return WERR_OK;
+}
diff --git a/source3/utils/net_registry_util.h b/source3/utils/net_registry_util.h
new file mode 100644
index 0000000..61fd834
--- /dev/null
+++ b/source3/utils/net_registry_util.h
@@ -0,0 +1,41 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * registry utility functions
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __NET_REGISTRY_UTIL_H__
+#define __NET_REGISTRY_UTIL_H__
+
+void print_registry_key(const char *keyname, NTTIME *modtime);
+
+void print_registry_value(const struct registry_value *valvalue, bool raw);
+
+void print_registry_value_with_name(const char *valname,
+ const struct registry_value *valvalue);
+
+/**
+ * Split path into hive name and subkeyname
+ * normalizations performed:
+ * - convert '/' to '\\'
+ * - strip trailing '\\' chars
+ */
+WERROR split_hive_key(TALLOC_CTX *ctx, const char *path, char **hivename,
+ char **subkeyname);
+
+#endif
diff --git a/source3/utils/net_rpc.c b/source3/utils/net_rpc.c
new file mode 100644
index 0000000..2a12b1a
--- /dev/null
+++ b/source3/utils/net_rpc.c
@@ -0,0 +1,8408 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2001 Andrew Bartlett (abartlet@samba.org)
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2004,2008 Guenther Deschner (gd@samba.org)
+ Copyright (C) 2005 Jeremy Allison (jra@samba.org)
+ Copyright (C) 2006 Jelmer Vernooij (jelmer@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "libsmb/namequery.h"
+#include "rpc_client/cli_pipe.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/cli_samr.h"
+#include "rpc_client/init_samr.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "../librpc/gen_ndr/ndr_spoolss.h"
+#include "../librpc/gen_ndr/ndr_initshutdown_c.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "secrets.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_net.h"
+#include "librpc/gen_ndr/libnet_join.h"
+#include "libnet/libnet_join.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "clirap2.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "passdb.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libsmb/dsgetdcname.h"
+#include "lib/util/string_wrappers.h"
+
+static int net_mode_share;
+static NTSTATUS sync_files(struct copy_clistate *cp_clistate, const char *mask);
+
+/**
+ * @file net_rpc.c
+ *
+ * @brief RPC based subcommands for the 'net' utility.
+ *
+ * This file should contain much of the functionality that used to
+ * be found in rpcclient, except that the commands should change
+ * less often, and the functionality should be sane (the user is not
+ * expected to know a rid/sid before they conduct an operation etc.)
+ *
+ * @todo Perhaps eventually these should be split out into a number
+ * of files, as this could get quite big.
+ **/
+
+
+/**
+ * Many of the RPC functions need the domain sid. This function gets
+ * it at the start of every run
+ *
+ * @param cli A cli_state already connected to the remote machine
+ *
+ * @return The Domain SID of the remote machine.
+ **/
+
+NTSTATUS net_get_remote_domain_sid(struct cli_state *cli, TALLOC_CTX *mem_ctx,
+ struct dom_sid **domain_sid,
+ const char **domain_name)
+{
+ struct rpc_pipe_client *lsa_pipe = NULL;
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ struct dcerpc_binding_handle *b;
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &lsa_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not initialise lsa pipe\n"));
+ return status;
+ }
+
+ b = lsa_pipe->binding_handle;
+
+ status = rpccli_lsa_open_policy(lsa_pipe, mem_ctx, false,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "open_policy %s: %s\n",
+ _("failed"),
+ nt_errstr(status));
+ return status;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ d_fprintf(stderr, "lsaquery %s: %s\n",
+ _("failed"),
+ nt_errstr(status));
+ return status;
+ }
+
+ *domain_name = info->account_domain.name.string;
+ *domain_sid = info->account_domain.sid;
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ TALLOC_FREE(lsa_pipe);
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Run a single RPC command, from start to finish.
+ *
+ * @param pipe_name the pipe to connect to (usually a PIPE_ constant)
+ * @param conn_flag a NET_FLAG_ combination. Passed to
+ * net_make_ipc_connection.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ * @return A shell status integer (0 for success).
+ */
+
+int run_rpc_command(struct net_context *c,
+ struct cli_state *cli_arg,
+ const struct ndr_interface_table *table,
+ int conn_flags,
+ rpc_command_fn fn,
+ int argc,
+ const char **argv)
+{
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS nt_status;
+ struct dom_sid *domain_sid;
+ const char *domain_name;
+ int ret = -1;
+
+ /* make use of cli_state handed over as an argument, if possible */
+ if (!cli_arg) {
+ nt_status = net_make_ipc_connection(c, conn_flags, &cli);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("failed to make ipc connection: %s\n",
+ nt_errstr(nt_status)));
+ return -1;
+ }
+ } else {
+ cli = cli_arg;
+ }
+
+ if (!cli) {
+ return -1;
+ }
+
+ /* Create mem_ctx */
+
+ if (!(mem_ctx = talloc_init("run_rpc_command"))) {
+ DEBUG(0, ("talloc_init() failed\n"));
+ goto fail;
+ }
+
+ nt_status = net_get_remote_domain_sid(cli, mem_ctx, &domain_sid,
+ &domain_name);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ goto fail;
+ }
+
+ if (!(conn_flags & NET_FLAGS_NO_PIPE)) {
+ if (lp_client_schannel()
+ && (ndr_syntax_id_equal(&table->syntax_id,
+ &ndr_table_netlogon.syntax_id))) {
+ const char *remote_name =
+ smbXcli_conn_remote_name(cli->conn);
+ const struct sockaddr_storage *remote_sockaddr =
+ smbXcli_conn_remote_sockaddr(cli->conn);
+
+ /* Always try and create an schannel netlogon pipe. */
+ TALLOC_FREE(c->netlogon_creds);
+ nt_status = cli_rpc_pipe_open_schannel(
+ cli, c->msg_ctx, table, NCACN_NP,
+ domain_name,
+ remote_name,
+ remote_sockaddr,
+ &pipe_hnd, c, &c->netlogon_creds);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Could not initialise schannel netlogon pipe. Error was %s\n",
+ nt_errstr(nt_status) ));
+ goto fail;
+ }
+ } else {
+ if (conn_flags & NET_FLAGS_SEAL) {
+ nt_status = cli_rpc_pipe_open_with_creds(
+ cli, table,
+ (conn_flags & NET_FLAGS_TCP) ?
+ NCACN_IP_TCP : NCACN_NP,
+ DCERPC_AUTH_TYPE_NTLMSSP,
+ DCERPC_AUTH_LEVEL_PRIVACY,
+ smbXcli_conn_remote_name(cli->conn),
+ smbXcli_conn_remote_sockaddr(cli->conn),
+ c->creds, &pipe_hnd);
+ } else {
+ nt_status = cli_rpc_pipe_open_noauth(
+ cli, table,
+ &pipe_hnd);
+ }
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Could not initialise pipe %s. Error was %s\n",
+ table->name,
+ nt_errstr(nt_status) ));
+ goto fail;
+ }
+ }
+ }
+
+ nt_status = fn(c, domain_sid, domain_name, cli, pipe_hnd, mem_ctx, argc, argv);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("rpc command function failed! (%s)\n", nt_errstr(nt_status)));
+ } else {
+ ret = 0;
+ DEBUG(5, ("rpc command function succeeded\n"));
+ }
+
+ if (!(conn_flags & NET_FLAGS_NO_PIPE)) {
+ if (pipe_hnd) {
+ TALLOC_FREE(pipe_hnd);
+ }
+ }
+
+fail:
+ /* close the connection only if it was opened here */
+ if (!cli_arg) {
+ cli_shutdown(cli);
+ }
+
+ talloc_destroy(mem_ctx);
+ return ret;
+}
+
+/**
+ * Force a change of the trust account password.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_changetrustpw_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ const char *dcname = NULL;
+
+ if (cli == NULL) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ dcname = smbXcli_conn_remote_name(cli->conn);
+
+ status = trust_pw_change(c->netlogon_creds,
+ c->msg_ctx,
+ pipe_hnd->binding_handle,
+ c->opt_target_workgroup,
+ dcname,
+ true); /* force */
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Failed to change machine account password: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Force a change of the trust account password.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+int net_rpc_changetrustpw(struct net_context *c, int argc, const char **argv)
+{
+ int conn_flags = NET_FLAGS_PDC;
+
+ if (!c->opt_user_specified && !c->opt_kerberos) {
+ conn_flags |= NET_FLAGS_ANONYMOUS;
+ }
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc changetrustpw\n"
+ " %s\n",
+ _("Usage:"),
+ _("Change the machine trust password"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_netlogon,
+ conn_flags,
+ rpc_changetrustpw_internals,
+ argc, argv);
+}
+
+/**
+ * Join a domain, the old way. This function exists to allow
+ * the message to be displayed when oldjoin was explicitly
+ * requested, but not when it was implied by "net rpc join".
+ *
+ * This uses 'machinename' as the initial password, and changes it.
+ *
+ * The password should be created with 'server manager' or equiv first.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int net_rpc_oldjoin(struct net_context *c, int argc, const char **argv)
+{
+ struct libnet_JoinCtx *r = NULL;
+ TALLOC_CTX *mem_ctx;
+ WERROR werr;
+ const char *domain = lp_workgroup(); /* FIXME */
+ bool modify_config = lp_config_backend_is_registry();
+ enum netr_SchannelType sec_chan_type;
+ char *pw = NULL;
+
+ if (c->display_usage) {
+ d_printf("Usage:\n"
+ "net rpc oldjoin\n"
+ " Join a domain the old way\n");
+ return 0;
+ }
+
+ net_warn_member_options();
+
+ mem_ctx = talloc_init("net_rpc_oldjoin");
+ if (!mem_ctx) {
+ return -1;
+ }
+
+ werr = libnet_init_JoinCtx(mem_ctx, &r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /*
+ check what type of join - if the user wants to join as
+ a BDC, the server must agree that we are a BDC.
+ */
+ if (argc >= 0) {
+ sec_chan_type = get_sec_channel_type(argv[0]);
+ } else {
+ sec_chan_type = get_sec_channel_type(NULL);
+ }
+
+ if (!c->msg_ctx) {
+ d_fprintf(stderr, _("Could not initialise message context. "
+ "Try running as root\n"));
+ werr = WERR_ACCESS_DENIED;
+ goto fail;
+ }
+
+ pw = talloc_strndup(r, lp_netbios_name(), 14);
+ if (pw == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+
+ r->in.msg_ctx = c->msg_ctx;
+ r->in.domain_name = domain;
+ r->in.secure_channel_type = sec_chan_type;
+ r->in.dc_name = c->opt_host;
+ r->in.admin_account = "";
+ r->in.admin_password = strlower_talloc(r, pw);
+ if (r->in.admin_password == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+ r->in.debug = true;
+ r->in.modify_config = modify_config;
+ r->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_JOIN_UNSECURE |
+ WKSSVC_JOIN_FLAGS_MACHINE_PWD_PASSED;
+
+ werr = libnet_Join(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* Check the short name of the domain */
+
+ if (!modify_config && !strequal(lp_workgroup(), r->out.netbios_domain_name)) {
+ d_printf("The workgroup in %s does not match the short\n", get_dyn_CONFIGFILE());
+ d_printf("domain name obtained from the server.\n");
+ d_printf("Using the name [%s] from the server.\n", r->out.netbios_domain_name);
+ d_printf("You should set \"workgroup = %s\" in %s.\n",
+ r->out.netbios_domain_name, get_dyn_CONFIGFILE());
+ }
+
+ d_printf("Using short domain name -- %s\n", r->out.netbios_domain_name);
+
+ if (r->out.dns_domain_name) {
+ d_printf("Joined '%s' to realm '%s'\n", r->in.machine_name,
+ r->out.dns_domain_name);
+ } else {
+ d_printf("Joined '%s' to domain '%s'\n", r->in.machine_name,
+ r->out.netbios_domain_name);
+ }
+
+ /* print out informative error string in case there is one */
+ if (r->out.error_string != NULL) {
+ d_printf("%s\n", r->out.error_string);
+ }
+
+ TALLOC_FREE(mem_ctx);
+
+ return 0;
+
+fail:
+ if (c->opt_flags & NET_FLAGS_EXPECT_FALLBACK) {
+ goto cleanup;
+ }
+
+ /* issue an overall failure message at the end. */
+ d_fprintf(stderr, _("Failed to join domain: %s\n"),
+ r && r->out.error_string ? r->out.error_string :
+ get_friendly_werror_msg(werr));
+
+cleanup:
+ TALLOC_FREE(mem_ctx);
+
+ return -1;
+}
+
+/**
+ * check that a join is OK
+ *
+ * @return A shell status integer (0 for success)
+ *
+ **/
+int net_rpc_testjoin(struct net_context *c, int argc, const char **argv)
+{
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx;
+ const char *domain = c->opt_target_workgroup;
+ const char *dc = c->opt_host;
+
+ if (c->display_usage) {
+ d_printf("Usage\n"
+ "net rpc testjoin\n"
+ " Test if a join is OK\n");
+ return 0;
+ }
+
+ net_warn_member_options();
+
+ mem_ctx = talloc_init("net_rpc_testjoin");
+ if (!mem_ctx) {
+ return -1;
+ }
+
+ if (!dc) {
+ struct netr_DsRGetDCNameInfo *info;
+
+ if (!c->msg_ctx) {
+ d_fprintf(stderr, _("Could not initialise message context. "
+ "Try running as root\n"));
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ status = dsgetdcname(mem_ctx,
+ c->msg_ctx,
+ domain,
+ NULL,
+ NULL,
+ DS_RETURN_DNS_NAME,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ dc = strip_hostname(info->dc_unc);
+ }
+
+ /* Display success or failure */
+ status = libnet_join_ok(c->msg_ctx,
+ c->opt_workgroup,
+ dc,
+ c->opt_kerberos);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,"Join to domain '%s' is not valid: %s\n",
+ domain, nt_errstr(status));
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ printf("Join to '%s' is OK\n",domain);
+ talloc_destroy(mem_ctx);
+
+ return 0;
+}
+
+/**
+ * Join a domain using the administrator username and password
+ *
+ * @param argc Standard main() style argc
+ * @param argc Standard main() style argv. Initial components are already
+ * stripped. Currently not used.
+ * @return A shell status integer (0 for success)
+ *
+ **/
+
+static int net_rpc_join_newstyle(struct net_context *c, int argc, const char **argv)
+{
+ struct libnet_JoinCtx *r = NULL;
+ TALLOC_CTX *mem_ctx;
+ WERROR werr;
+ const char *domain = lp_workgroup(); /* FIXME */
+ bool modify_config = lp_config_backend_is_registry();
+ enum netr_SchannelType sec_chan_type;
+
+ if (c->display_usage) {
+ d_printf("Usage:\n"
+ "net rpc join\n"
+ " Join a domain the new way\n");
+ return 0;
+ }
+
+ net_warn_member_options();
+
+ mem_ctx = talloc_init("net_rpc_join_newstyle");
+ if (!mem_ctx) {
+ return -1;
+ }
+
+ werr = libnet_init_JoinCtx(mem_ctx, &r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /*
+ check what type of join - if the user wants to join as
+ a BDC, the server must agree that we are a BDC.
+ */
+ if (argc >= 0) {
+ sec_chan_type = get_sec_channel_type(argv[0]);
+ } else {
+ sec_chan_type = get_sec_channel_type(NULL);
+ }
+
+ if (!c->msg_ctx) {
+ d_fprintf(stderr, _("Could not initialise message context. "
+ "Try running as root\n"));
+ werr = WERR_ACCESS_DENIED;
+ goto fail;
+ }
+
+ r->in.msg_ctx = c->msg_ctx;
+ r->in.domain_name = domain;
+ r->in.secure_channel_type = sec_chan_type;
+ r->in.dc_name = c->opt_host;
+ r->in.admin_account = c->opt_user_name;
+ r->in.admin_password = net_prompt_pass(c, c->opt_user_name);
+ r->in.debug = true;
+ r->in.use_kerberos = c->opt_kerberos;
+ r->in.modify_config = modify_config;
+ r->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE |
+ WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED;
+
+ werr = libnet_Join(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto fail;
+ }
+
+ /* Check the short name of the domain */
+
+ if (!modify_config && !strequal(lp_workgroup(), r->out.netbios_domain_name)) {
+ d_printf("The workgroup in %s does not match the short\n", get_dyn_CONFIGFILE());
+ d_printf("domain name obtained from the server.\n");
+ d_printf("Using the name [%s] from the server.\n", r->out.netbios_domain_name);
+ d_printf("You should set \"workgroup = %s\" in %s.\n",
+ r->out.netbios_domain_name, get_dyn_CONFIGFILE());
+ }
+
+ d_printf("Using short domain name -- %s\n", r->out.netbios_domain_name);
+
+ if (r->out.dns_domain_name) {
+ d_printf("Joined '%s' to realm '%s'\n", r->in.machine_name,
+ r->out.dns_domain_name);
+ } else {
+ d_printf("Joined '%s' to domain '%s'\n", r->in.machine_name,
+ r->out.netbios_domain_name);
+ }
+
+ /* print out informative error string in case there is one */
+ if (r->out.error_string != NULL) {
+ d_printf("%s\n", r->out.error_string);
+ }
+
+ TALLOC_FREE(mem_ctx);
+
+ return 0;
+
+fail:
+ /* issue an overall failure message at the end. */
+ d_printf("Failed to join domain: %s\n",
+ r && r->out.error_string ? r->out.error_string :
+ get_friendly_werror_msg(werr));
+
+ TALLOC_FREE(mem_ctx);
+
+ return -1;
+}
+
+/**
+ * 'net rpc join' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * Main 'net_rpc_join()' (where the admin username/password is used) is
+ * in net_rpc_join.c.
+ * Try to just change the password, but if that doesn't work, use/prompt
+ * for a username/password.
+ **/
+
+int net_rpc_join(struct net_context *c, int argc, const char **argv)
+{
+ int ret;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc join -U <username>[%%password] <type>\n"
+ " Join a domain\n"
+ " username\tName of the admin user"
+ " password\tPassword of the admin user, will "
+ "prompt if not specified\n"
+ " type\tCan be one of the following:\n"
+ "\t\tMEMBER\tJoin as member server (default)\n"
+ "\t\tBDC\tJoin as BDC\n"
+ "\t\tPDC\tJoin as PDC\n"));
+ return 0;
+ }
+
+ if (lp_server_role() == ROLE_STANDALONE) {
+ d_printf(_("cannot join as standalone machine\n"));
+ return -1;
+ }
+
+ net_warn_member_options();
+
+ if (strlen(lp_netbios_name()) > 15) {
+ d_printf(_("Our netbios name can be at most 15 chars long, "
+ "\"%s\" is %u chars long\n"),
+ lp_netbios_name(), (unsigned int)strlen(lp_netbios_name()));
+ return -1;
+ }
+
+ c->opt_flags |= NET_FLAGS_EXPECT_FALLBACK;
+ ret = net_rpc_oldjoin(c, argc, argv);
+ c->opt_flags &= ~NET_FLAGS_EXPECT_FALLBACK;
+ if (ret == 0) {
+ return 0;
+ }
+
+ return net_rpc_join_newstyle(c, argc, argv);
+}
+
+/**
+ * display info about a rpc domain
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_info_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ union samr_DomainInfo *info = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* Get sam policy handle */
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not connect to SAM: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Could not connect to SAM: %s\n"),
+ nt_errstr(result));
+ goto done;
+ }
+
+ /* Get domain policy handle */
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, domain_sid),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not open domain: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Could not open domain: %s\n"),
+ nt_errstr(result));
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ &domain_pol,
+ 2,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = result;
+ if (NT_STATUS_IS_OK(result)) {
+ struct dom_sid_buf sid_str;
+
+ d_printf(_("Domain Name: %s\n"),
+ info->general.domain_name.string);
+ d_printf(_("Domain SID: %s\n"),
+ dom_sid_str_buf(domain_sid, &sid_str));
+ d_printf(_("Sequence number: %llu\n"),
+ (unsigned long long)info->general.sequence_num);
+ d_printf(_("Num users: %u\n"), info->general.num_users);
+ d_printf(_("Num domain groups: %u\n"),info->general.num_groups);
+ d_printf(_("Num local groups: %u\n"),info->general.num_aliases);
+ }
+
+ done:
+ return status;
+}
+
+/**
+ * 'net rpc info' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_rpc_info(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc info\n"
+ " %s\n",
+ _("Usage:"),
+ _("Display information about the domain"));
+ return 0;
+ }
+
+ net_warn_member_options();
+
+ return run_rpc_command(c, NULL, &ndr_table_samr,
+ NET_FLAGS_PDC, rpc_info_internals,
+ argc, argv);
+}
+
+/**
+ * Fetch domain SID into the local secrets.tdb.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_getsid_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dom_sid_buf sid_str;
+
+ d_printf(_("Storing SID %s for Domain %s in secrets.tdb\n"),
+ dom_sid_str_buf(domain_sid, &sid_str),
+ domain_name);
+
+ if (!secrets_store_domain_sid(domain_name, domain_sid)) {
+ DEBUG(0,("Can't store domain SID\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * 'net rpc getsid' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_rpc_getsid(struct net_context *c, int argc, const char **argv)
+{
+ int conn_flags = NET_FLAGS_PDC;
+
+ if (!c->opt_user_specified && !c->opt_kerberos) {
+ conn_flags |= NET_FLAGS_ANONYMOUS;
+ }
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc getsid\n"
+ " %s\n",
+ _("Usage:"),
+ _("Fetch domain SID into local secrets.tdb"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_samr,
+ conn_flags,
+ rpc_getsid_internals,
+ argc, argv);
+}
+
+/****************************************************************************/
+
+/**
+ * Basic usage function for 'net rpc user'.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+static int rpc_user_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_user_usage(c, argc, argv);
+}
+
+/**
+ * Add a new user to a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_user_add(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct USER_INFO_1 info1;
+ uint32_t parm_error = 0;
+
+ if (argc < 1 || c->display_usage) {
+ rpc_user_usage(c, argc, argv);
+ return 0;
+ }
+
+ ZERO_STRUCT(info1);
+
+ info1.usri1_name = argv[0];
+ if (argc == 2) {
+ info1.usri1_password = argv[1];
+ }
+
+ status = NetUserAdd(c->opt_host, 1, (uint8_t *)&info1, &parm_error);
+
+ if (status != 0) {
+ d_fprintf(stderr,_("Failed to add user '%s' with error: %s.\n"),
+ argv[0], libnetapi_get_error_string(c->netapi_ctx,
+ status));
+ return -1;
+ } else {
+ d_printf(_("Added user '%s'.\n"), argv[0]);
+ }
+
+ return 0;
+}
+
+/**
+ * Rename a user on a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_user_rename(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct USER_INFO_0 u0;
+ uint32_t parm_err = 0;
+
+ if (argc != 2 || c->display_usage) {
+ rpc_user_usage(c, argc, argv);
+ return 0;
+ }
+
+ u0.usri0_name = argv[1];
+
+ status = NetUserSetInfo(c->opt_host, argv[0],
+ 0, (uint8_t *)&u0, &parm_err);
+ if (status) {
+ d_fprintf(stderr,
+ _("Failed to rename user from %s to %s - %s\n"),
+ argv[0], argv[1],
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ } else {
+ d_printf(_("Renamed user from %s to %s\n"), argv[0], argv[1]);
+ }
+
+ return status;
+}
+
+/**
+ * Set a user's primary group
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_user_setprimarygroup(struct net_context *c, int argc,
+ const char **argv)
+{
+ NET_API_STATUS status;
+ uint8_t *buffer;
+ struct GROUP_INFO_2 *g2;
+ struct USER_INFO_1051 u1051;
+ uint32_t parm_err = 0;
+
+ if (argc != 2 || c->display_usage) {
+ rpc_user_usage(c, argc, argv);
+ return 0;
+ }
+
+ status = NetGroupGetInfo(c->opt_host, argv[1], 2, &buffer);
+ if (status) {
+ d_fprintf(stderr, _("Failed to find group name %s -- %s\n"),
+ argv[1],
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ return status;
+ }
+ g2 = (struct GROUP_INFO_2 *)buffer;
+
+ u1051.usri1051_primary_group_id = g2->grpi2_group_id;
+
+ NetApiBufferFree(buffer);
+
+ status = NetUserSetInfo(c->opt_host, argv[0], 1051,
+ (uint8_t *)&u1051, &parm_err);
+ if (status) {
+ d_fprintf(stderr,
+ _("Failed to set user's primary group %s to %s - "
+ "%s\n"), argv[0], argv[1],
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ } else {
+ d_printf(_("Set primary group of user %s to %s\n"), argv[0],
+ argv[1]);
+ }
+ return status;
+}
+
+/**
+ * Delete a user from a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_user_delete(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+
+ if (argc < 1 || c->display_usage) {
+ rpc_user_usage(c, argc, argv);
+ return 0;
+ }
+
+ status = NetUserDel(c->opt_host, argv[0]);
+
+ if (status != 0) {
+ d_fprintf(stderr, _("Failed to delete user '%s' with: %s.\n"),
+ argv[0],
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ return -1;
+ } else {
+ d_printf(_("Deleted user '%s'.\n"), argv[0]);
+ }
+
+ return 0;
+}
+
+/**
+ * Set a user's password on a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_user_password(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ char *prompt = NULL;
+ struct USER_INFO_1003 u1003;
+ uint32_t parm_err = 0;
+ int ret;
+
+ if (argc < 1 || c->display_usage) {
+ rpc_user_usage(c, argc, argv);
+ return 0;
+ }
+
+ if (argv[1]) {
+ u1003.usri1003_password = argv[1];
+ } else {
+ char pwd[256] = {0};
+ ret = asprintf(&prompt, _("Enter new password for %s:"),
+ argv[0]);
+ if (ret == -1) {
+ return -1;
+ }
+
+ ret = samba_getpass(prompt, pwd, sizeof(pwd), false, false);
+ SAFE_FREE(prompt);
+ if (ret < 0) {
+ return -1;
+ }
+
+ u1003.usri1003_password = talloc_strdup(c, pwd);
+ if (u1003.usri1003_password == NULL) {
+ return -1;
+ }
+ }
+
+ status = NetUserSetInfo(c->opt_host, argv[0], 1003, (uint8_t *)&u1003, &parm_err);
+
+ /* Display results */
+ if (status != 0) {
+ d_fprintf(stderr,
+ _("Failed to set password for '%s' with error: %s.\n"),
+ argv[0], libnetapi_get_error_string(c->netapi_ctx,
+ status));
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * List a user's groups from a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success)
+ **/
+
+static int rpc_user_info(struct net_context *c, int argc, const char **argv)
+
+{
+ NET_API_STATUS status;
+ struct GROUP_USERS_INFO_0 *u0 = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t i;
+
+
+ if (argc < 1 || c->display_usage) {
+ rpc_user_usage(c, argc, argv);
+ return 0;
+ }
+
+ status = NetUserGetGroups(c->opt_host,
+ argv[0],
+ 0,
+ (uint8_t **)(void *)&u0,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries);
+ if (status != 0) {
+ d_fprintf(stderr,
+ _("Failed to get groups for '%s' with error: %s.\n"),
+ argv[0], libnetapi_get_error_string(c->netapi_ctx,
+ status));
+ return -1;
+ }
+
+ for (i=0; i < entries_read; i++) {
+ printf("%s\n", u0->grui0_name);
+ u0++;
+ }
+
+ return 0;
+}
+
+/**
+ * List users on a remote RPC server.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static int rpc_user_list(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ uint32_t start_idx=0, num_entries, i, loop_count = 0;
+ struct NET_DISPLAY_USER *info = NULL;
+ void *buffer = NULL;
+
+ /* Query domain users */
+ if (c->opt_long_list_entries)
+ d_printf(_("\nUser name Comment"
+ "\n-----------------------------\n"));
+ do {
+ uint32_t max_entries, max_size;
+
+ dcerpc_get_query_dispinfo_params(
+ loop_count, &max_entries, &max_size);
+
+ status = NetQueryDisplayInformation(c->opt_host,
+ 1,
+ start_idx,
+ max_entries,
+ max_size,
+ &num_entries,
+ &buffer);
+ if (status != 0 && status != ERROR_MORE_DATA) {
+ return status;
+ }
+
+ info = (struct NET_DISPLAY_USER *)buffer;
+
+ for (i = 0; i < num_entries; i++) {
+
+ if (c->opt_long_list_entries)
+ printf("%-21.21s %s\n", info->usri1_name,
+ info->usri1_comment);
+ else
+ printf("%s\n", info->usri1_name);
+ info++;
+ }
+
+ NetApiBufferFree(buffer);
+
+ loop_count++;
+ start_idx += num_entries;
+
+ } while (status == ERROR_MORE_DATA);
+
+ return status;
+}
+
+/**
+ * 'net rpc user' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_rpc_user(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+
+ struct functable func[] = {
+ {
+ "add",
+ rpc_user_add,
+ NET_TRANSPORT_RPC,
+ N_("Add specified user"),
+ N_("net rpc user add\n"
+ " Add specified user")
+ },
+ {
+ "info",
+ rpc_user_info,
+ NET_TRANSPORT_RPC,
+ N_("List domain groups of user"),
+ N_("net rpc user info\n"
+ " List domain groups of user")
+ },
+ {
+ "delete",
+ rpc_user_delete,
+ NET_TRANSPORT_RPC,
+ N_("Remove specified user"),
+ N_("net rpc user delete\n"
+ " Remove specified user")
+ },
+ {
+ "password",
+ rpc_user_password,
+ NET_TRANSPORT_RPC,
+ N_("Change user password"),
+ N_("net rpc user password\n"
+ " Change user password")
+ },
+ {
+ "rename",
+ rpc_user_rename,
+ NET_TRANSPORT_RPC,
+ N_("Rename specified user"),
+ N_("net rpc user rename\n"
+ " Rename specified user")
+ },
+ {
+ "setprimarygroup",
+ rpc_user_setprimarygroup,
+ NET_TRANSPORT_RPC,
+ "Set a user's primary group",
+ "net rpc user setprimarygroup\n"
+ " Set a user's primary group"
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (status != 0) {
+ return -1;
+ }
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc user\n"
+ " %s\n",
+ _("Usage:"),
+ _("List all users"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ return rpc_user_list(c, argc, argv);
+ }
+
+ return net_run_function(c, argc, argv, "net rpc user", func);
+}
+
+static NTSTATUS rpc_sh_user_list(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return werror_to_ntstatus(W_ERROR(rpc_user_list(c, argc, argv)));
+}
+
+static NTSTATUS rpc_sh_user_info(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return werror_to_ntstatus(W_ERROR(rpc_user_info(c, argc, argv)));
+}
+
+static NTSTATUS rpc_sh_handle_user(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv,
+ NTSTATUS (*fn)(
+ struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *user_hnd,
+ int argc, const char **argv))
+{
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ NTSTATUS status, result;
+ struct dom_sid sid;
+ uint32_t rid;
+ enum lsa_SidType type;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc == 0) {
+ d_fprintf(stderr, "%s %s <username>\n", _("Usage:"),
+ ctx->whoami);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(connect_pol);
+ ZERO_STRUCT(domain_pol);
+ ZERO_STRUCT(user_pol);
+
+ status = net_rpc_lookup_name(c, mem_ctx, ctx->cli,
+ argv[0], NULL, NULL, &sid, &type);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not lookup %s: %s\n"), argv[0],
+ nt_errstr(status));
+ goto done;
+ }
+
+ if (type != SID_NAME_USER) {
+ d_fprintf(stderr, _("%s is a %s, not a user\n"), argv[0],
+ sid_type_lookup(type));
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ if (!sid_peek_check_rid(ctx->domain_sid, &sid, &rid)) {
+ d_fprintf(stderr, _("%s is not in our domain\n"), argv[0]);
+ status = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ ctx->domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ rid,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = fn(c, mem_ctx, ctx, pipe_hnd, &user_pol, argc-1, argv+1);
+
+ done:
+ if (is_valid_policy_hnd(&user_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ }
+ if (is_valid_policy_hnd(&domain_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ }
+ if (is_valid_policy_hnd(&connect_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ }
+ return status;
+}
+
+static NTSTATUS rpc_sh_user_show_internals(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *user_hnd,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ union samr_UserInfo *info = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 0) {
+ d_fprintf(stderr, "%s %s show <username>\n", _("Usage:"),
+ ctx->whoami);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ user_hnd,
+ 21,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ d_printf(_("user rid: %d, group rid: %d\n"),
+ info->info21.rid,
+ info->info21.primary_gid);
+
+ return result;
+}
+
+static NTSTATUS rpc_sh_user_show(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_handle_user(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ rpc_sh_user_show_internals);
+}
+
+#define FETCHSTR(name, rec) \
+do { if (strequal(ctx->thiscmd, name)) { \
+ oldval = talloc_strdup(mem_ctx, info->info21.rec.string); } \
+} while (0);
+
+#define SETSTR(name, rec, flag) \
+do { if (strequal(ctx->thiscmd, name)) { \
+ init_lsa_String(&(info->info21.rec), argv[0]); \
+ info->info21.fields_present |= SAMR_FIELD_##flag; } \
+} while (0);
+
+static NTSTATUS rpc_sh_user_str_edit_internals(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *user_hnd,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ const char *username;
+ const char *oldval = "";
+ union samr_UserInfo *info = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc > 1) {
+ d_fprintf(stderr, "%s %s <username> [new value|NULL]\n",
+ _("Usage:"), ctx->whoami);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ user_hnd,
+ 21,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ username = talloc_strdup(mem_ctx, info->info21.account_name.string);
+
+ FETCHSTR("fullname", full_name);
+ FETCHSTR("homedir", home_directory);
+ FETCHSTR("homedrive", home_drive);
+ FETCHSTR("logonscript", logon_script);
+ FETCHSTR("profilepath", profile_path);
+ FETCHSTR("description", description);
+
+ if (argc == 0) {
+ d_printf(_("%s's %s: [%s]\n"), username, ctx->thiscmd, oldval);
+ goto done;
+ }
+
+ if (strcmp(argv[0], "NULL") == 0) {
+ argv[0] = "";
+ }
+
+ ZERO_STRUCT(info->info21);
+
+ SETSTR("fullname", full_name, FULL_NAME);
+ SETSTR("homedir", home_directory, HOME_DIRECTORY);
+ SETSTR("homedrive", home_drive, HOME_DRIVE);
+ SETSTR("logonscript", logon_script, LOGON_SCRIPT);
+ SETSTR("profilepath", profile_path, PROFILE_PATH);
+ SETSTR("description", description, DESCRIPTION);
+
+ status = dcerpc_samr_SetUserInfo(b, mem_ctx,
+ user_hnd,
+ 21,
+ info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = result;
+
+ d_printf(_("Set %s's %s from [%s] to [%s]\n"), username,
+ ctx->thiscmd, oldval, argv[0]);
+
+ done:
+
+ return status;
+}
+
+#define HANDLEFLG(name, rec) \
+do { if (strequal(ctx->thiscmd, name)) { \
+ oldval = (oldflags & ACB_##rec) ? "yes" : "no"; \
+ if (newval) { \
+ newflags = oldflags | ACB_##rec; \
+ } else { \
+ newflags = oldflags & ~ACB_##rec; \
+ } } } while (0);
+
+static NTSTATUS rpc_sh_user_str_edit(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_handle_user(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ rpc_sh_user_str_edit_internals);
+}
+
+static NTSTATUS rpc_sh_user_flag_edit_internals(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *user_hnd,
+ int argc, const char **argv)
+{
+ NTSTATUS status, result;
+ const char *username;
+ const char *oldval = "unknown";
+ uint32_t oldflags, newflags;
+ bool newval;
+ union samr_UserInfo *info = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if ((argc > 1) ||
+ ((argc == 1) && !strequal(argv[0], "yes") &&
+ !strequal(argv[0], "no"))) {
+ /* TRANSLATORS: The yes|no here are program keywords. Please do
+ not translate. */
+ d_fprintf(stderr, _("Usage: %s <username> [yes|no]\n"),
+ ctx->whoami);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ newval = strequal(argv[0], "yes");
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ user_hnd,
+ 21,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ username = talloc_strdup(mem_ctx, info->info21.account_name.string);
+ oldflags = info->info21.acct_flags;
+ newflags = info->info21.acct_flags;
+
+ HANDLEFLG("disabled", DISABLED);
+ HANDLEFLG("pwnotreq", PWNOTREQ);
+ HANDLEFLG("autolock", AUTOLOCK);
+ HANDLEFLG("pwnoexp", PWNOEXP);
+
+ if (argc == 0) {
+ d_printf(_("%s's %s flag: %s\n"), username, ctx->thiscmd,
+ oldval);
+ goto done;
+ }
+
+ ZERO_STRUCT(info->info21);
+
+ info->info21.acct_flags = newflags;
+ info->info21.fields_present = SAMR_FIELD_ACCT_FLAGS;
+
+ status = dcerpc_samr_SetUserInfo(b, mem_ctx,
+ user_hnd,
+ 21,
+ info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = result;
+ if (NT_STATUS_IS_OK(result)) {
+ d_printf(_("Set %s's %s flag from [%s] to [%s]\n"), username,
+ ctx->thiscmd, oldval, argv[0]);
+ }
+
+ done:
+
+ return status;
+}
+
+static NTSTATUS rpc_sh_user_flag_edit(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_handle_user(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ rpc_sh_user_flag_edit_internals);
+}
+
+struct rpc_sh_cmd *net_rpc_user_edit_cmds(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx)
+{
+ static struct rpc_sh_cmd cmds[] = {
+
+ { "fullname", NULL, &ndr_table_samr, rpc_sh_user_str_edit,
+ N_("Show/Set a user's full name") },
+
+ { "homedir", NULL, &ndr_table_samr, rpc_sh_user_str_edit,
+ N_("Show/Set a user's home directory") },
+
+ { "homedrive", NULL, &ndr_table_samr, rpc_sh_user_str_edit,
+ N_("Show/Set a user's home drive") },
+
+ { "logonscript", NULL, &ndr_table_samr, rpc_sh_user_str_edit,
+ N_("Show/Set a user's logon script") },
+
+ { "profilepath", NULL, &ndr_table_samr, rpc_sh_user_str_edit,
+ N_("Show/Set a user's profile path") },
+
+ { "description", NULL, &ndr_table_samr, rpc_sh_user_str_edit,
+ N_("Show/Set a user's description") },
+
+ { "disabled", NULL, &ndr_table_samr, rpc_sh_user_flag_edit,
+ N_("Show/Set whether a user is disabled") },
+
+ { "autolock", NULL, &ndr_table_samr, rpc_sh_user_flag_edit,
+ N_("Show/Set whether a user locked out") },
+
+ { "pwnotreq", NULL, &ndr_table_samr, rpc_sh_user_flag_edit,
+ N_("Show/Set whether a user does not need a password") },
+
+ { "pwnoexp", NULL, &ndr_table_samr, rpc_sh_user_flag_edit,
+ N_("Show/Set whether a user's password does not expire") },
+
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ return cmds;
+}
+
+struct rpc_sh_cmd *net_rpc_user_cmds(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx)
+{
+ static struct rpc_sh_cmd cmds[] = {
+
+ { "list", NULL, &ndr_table_samr, rpc_sh_user_list,
+ N_("List available users") },
+
+ { "info", NULL, &ndr_table_samr, rpc_sh_user_info,
+ N_("List the domain groups a user is member of") },
+
+ { "show", NULL, &ndr_table_samr, rpc_sh_user_show,
+ N_("Show info about a user") },
+
+ { "edit", net_rpc_user_edit_cmds, 0, NULL,
+ N_("Show/Modify a user's fields") },
+
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ return cmds;
+}
+
+/****************************************************************************/
+
+/**
+ * Basic usage function for 'net rpc group'.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+static int rpc_group_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_group_usage(c, argc, argv);
+}
+
+/**
+ * Delete group on a remote RPC server.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_group_delete_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, group_pol, user_pol;
+ bool group_is_primary = false;
+ NTSTATUS status, result;
+ uint32_t group_rid;
+ struct samr_RidAttrArray *rids = NULL;
+ /* char **names; */
+ uint32_t i;
+ /* struct samr_RidWithAttribute *user_gids; */
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ struct samr_Ids group_rids, name_types;
+ struct lsa_String lsa_acct_name;
+ union samr_UserInfo *info = NULL;
+
+ if (argc < 1 || c->display_usage) {
+ rpc_group_usage(c, argc,argv);
+ return NT_STATUS_OK; /* ok? */
+ }
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Request samr_Connect2 failed\n"));
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Request samr_Connect2 failed\n"));
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, domain_sid),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Request open_domain failed\n"));
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Request open_domain failed\n"));
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, argv[0]);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &group_rids,
+ &name_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Lookup of '%s' failed\n"),argv[0]);
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Lookup of '%s' failed\n"),argv[0]);
+ goto done;
+ }
+ if (group_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ switch (name_types.ids[0])
+ {
+ case SID_NAME_DOM_GRP:
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ group_rids.ids[0],
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Request open_group failed"));
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Request open_group failed"));
+ goto done;
+ }
+
+ group_rid = group_rids.ids[0];
+
+ status = dcerpc_samr_QueryGroupMember(b, mem_ctx,
+ &group_pol,
+ &rids,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Unable to query group members of %s"),
+ argv[0]);
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr,
+ _("Unable to query group members of %s"),
+ argv[0]);
+ goto done;
+ }
+
+ if (c->opt_verbose) {
+ d_printf(
+ _("Domain Group %s (rid: %d) has %d members\n"),
+ argv[0],group_rid, rids->count);
+ }
+
+ /* Check if group is anyone's primary group */
+ for (i = 0; i < rids->count; i++)
+ {
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ rids->rids[i],
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Unable to open group member %d\n"),
+ rids->rids[i]);
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr,
+ _("Unable to open group member %d\n"),
+ rids->rids[i]);
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryUserInfo(b, mem_ctx,
+ &user_pol,
+ 21,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Unable to lookup userinfo for group "
+ "member %d\n"),
+ rids->rids[i]);
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr,
+ _("Unable to lookup userinfo for group "
+ "member %d\n"),
+ rids->rids[i]);
+ goto done;
+ }
+
+ if (info->info21.primary_gid == group_rid) {
+ if (c->opt_verbose) {
+ d_printf(_("Group is primary group "
+ "of %s\n"),
+ info->info21.account_name.string);
+ }
+ group_is_primary = true;
+ }
+
+ dcerpc_samr_Close(b, mem_ctx, &user_pol, &result);
+ }
+
+ if (group_is_primary) {
+ d_fprintf(stderr, _("Unable to delete group because "
+ "some of it's members have it as primary "
+ "group\n"));
+ status = NT_STATUS_MEMBERS_PRIMARY_GROUP;
+ goto done;
+ }
+
+ /* remove all group members */
+ for (i = 0; i < rids->count; i++)
+ {
+ if (c->opt_verbose)
+ d_printf(_("Remove group member %d..."),
+ rids->rids[i]);
+ status = dcerpc_samr_DeleteGroupMember(b, mem_ctx,
+ &group_pol,
+ rids->rids[i],
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = result;
+ if (NT_STATUS_IS_OK(result)) {
+ if (c->opt_verbose)
+ d_printf(_("ok\n"));
+ } else {
+ if (c->opt_verbose)
+ d_printf("%s\n", _("failed"));
+ goto done;
+ }
+ }
+
+ status = dcerpc_samr_DeleteDomainGroup(b, mem_ctx,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ status = result;
+
+ break;
+ /* removing a local group is easier... */
+ case SID_NAME_ALIAS:
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ group_rids.ids[0],
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Request open_alias failed\n"));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Request open_alias failed\n"));
+ goto done;
+ }
+
+ status = dcerpc_samr_DeleteDomAlias(b, mem_ctx,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+
+ status = result;
+
+ break;
+ default:
+ d_fprintf(stderr, _("%s is of type %s. This command is only "
+ "for deleting local or global groups\n"),
+ argv[0],sid_type_lookup(name_types.ids[0]));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ if (c->opt_verbose)
+ d_printf(_("Deleted %s '%s'\n"),
+ sid_type_lookup(name_types.ids[0]), argv[0]);
+ } else {
+ d_fprintf(stderr, _("Deleting of %s failed: %s\n"), argv[0],
+ get_friendly_nt_error_msg(status));
+ }
+
+ done:
+ return status;
+
+}
+
+static int rpc_group_delete(struct net_context *c, int argc, const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_group_delete_internals, argc,argv);
+}
+
+static int rpc_group_add_internals(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct GROUP_INFO_1 info1;
+ uint32_t parm_error = 0;
+
+ if (argc != 1 || c->display_usage) {
+ rpc_group_usage(c, argc, argv);
+ return 0;
+ }
+
+ ZERO_STRUCT(info1);
+
+ info1.grpi1_name = argv[0];
+ if (c->opt_comment && strlen(c->opt_comment) > 0) {
+ info1.grpi1_comment = c->opt_comment;
+ }
+
+ status = NetGroupAdd(c->opt_host, 1, (uint8_t *)&info1, &parm_error);
+
+ if (status != 0) {
+ d_fprintf(stderr,
+ _("Failed to add group '%s' with error: %s.\n"),
+ argv[0], libnetapi_get_error_string(c->netapi_ctx,
+ status));
+ return -1;
+ } else {
+ d_printf(_("Added group '%s'.\n"), argv[0]);
+ }
+
+ return 0;
+}
+
+static int rpc_alias_add_internals(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct LOCALGROUP_INFO_1 info1;
+ uint32_t parm_error = 0;
+
+ if (argc != 1 || c->display_usage) {
+ rpc_group_usage(c, argc, argv);
+ return 0;
+ }
+
+ ZERO_STRUCT(info1);
+
+ info1.lgrpi1_name = argv[0];
+ if (c->opt_comment && strlen(c->opt_comment) > 0) {
+ info1.lgrpi1_comment = c->opt_comment;
+ }
+
+ status = NetLocalGroupAdd(c->opt_host, 1, (uint8_t *)&info1, &parm_error);
+
+ if (status != 0) {
+ d_fprintf(stderr,
+ _("Failed to add alias '%s' with error: %s.\n"),
+ argv[0], libnetapi_get_error_string(c->netapi_ctx,
+ status));
+ return -1;
+ } else {
+ d_printf(_("Added alias '%s'.\n"), argv[0]);
+ }
+
+ return 0;
+}
+
+static int rpc_group_add(struct net_context *c, int argc, const char **argv)
+{
+ if (c->opt_localgroup)
+ return rpc_alias_add_internals(c, argc, argv);
+
+ return rpc_group_add_internals(c, argc, argv);
+}
+
+static NTSTATUS get_sid_from_name(struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ struct dom_sid *sids = NULL;
+ enum lsa_SidType *types = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle lsa_pol;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b;
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, false,
+ SEC_FLAG_MAXIMUM_ALLOWED, &lsa_pol);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpccli_lsa_lookup_names(pipe_hnd, mem_ctx, &lsa_pol, 1,
+ &name, NULL, 1, &sids, &types);
+
+ if (NT_STATUS_IS_OK(status)) {
+ sid_copy(sid, &sids[0]);
+ *type = types[0];
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &lsa_pol, &result);
+
+ done:
+ if (pipe_hnd) {
+ TALLOC_FREE(pipe_hnd);
+ }
+
+ if (!NT_STATUS_IS_OK(status) && (strncasecmp_m(name, "S-", 2) == 0)) {
+
+ /* Try as S-1-5-whatever */
+
+ struct dom_sid tmp_sid;
+
+ if (string_to_sid(&tmp_sid, name)) {
+ sid_copy(sid, &tmp_sid);
+ *type = SID_NAME_UNKNOWN;
+ status = NT_STATUS_OK;
+ }
+ }
+
+ return status;
+}
+
+static NTSTATUS rpc_add_groupmem(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ const char *member)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t group_rid;
+ struct policy_handle group_pol;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ struct samr_Ids rids, rid_types;
+ struct lsa_String lsa_acct_name;
+
+ struct dom_sid sid;
+
+ sid_copy(&sid, group_sid);
+
+ if (!sid_split_rid(&sid, &group_rid)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* Get sam policy handle */
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ /* Get domain policy handle */
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ init_lsa_String(&lsa_acct_name, member);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &rid_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not lookup up group member %s\n"),
+ member);
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Could not lookup up group member %s\n"),
+ member);
+ goto done;
+ }
+ if (rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (rid_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ group_rid,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_AddGroupMember(b, mem_ctx,
+ &group_pol,
+ rids.ids[0],
+ 0x0005, /* unknown flags */
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+
+ done:
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ return status;
+}
+
+static NTSTATUS rpc_add_aliasmem(struct rpc_pipe_client *pipe_hnd,
+ struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *alias_sid,
+ const char *member)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t alias_rid;
+ struct policy_handle alias_pol;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ struct dom_sid member_sid;
+ enum lsa_SidType member_type;
+
+ struct dom_sid sid;
+
+ sid_copy(&sid, alias_sid);
+
+ if (!sid_split_rid(&sid, &alias_rid)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ result = get_sid_from_name(cli, mem_ctx,
+ member, &member_sid, &member_type);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Could not lookup up group member %s\n"),
+ member);
+ return result;
+ }
+
+ /* Get sam policy handle */
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get domain policy handle */
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ alias_rid,
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ status = dcerpc_samr_AddAliasMember(b, mem_ctx,
+ &alias_pol,
+ &member_sid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = result;
+
+ done:
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ return status;
+}
+
+static NTSTATUS rpc_group_addmem_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dom_sid group_sid;
+ enum lsa_SidType group_type;
+
+ if (argc != 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc group addmem <group> <member>\n"
+ " Add a member to a group\n"
+ " group\tGroup to add member to\n"
+ " member\tMember to add to group\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (!NT_STATUS_IS_OK(get_sid_from_name(cli, mem_ctx, argv[0],
+ &group_sid, &group_type))) {
+ d_fprintf(stderr, _("Could not lookup group name %s\n"),
+ argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (group_type == SID_NAME_DOM_GRP) {
+ NTSTATUS result = rpc_add_groupmem(pipe_hnd, mem_ctx,
+ &group_sid, argv[1]);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Could not add %s to %s: %s\n"),
+ argv[1], argv[0], nt_errstr(result));
+ }
+ return result;
+ }
+
+ if (group_type == SID_NAME_ALIAS) {
+ NTSTATUS result = rpc_add_aliasmem(pipe_hnd, cli, mem_ctx,
+ &group_sid, argv[1]);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Could not add %s to %s: %s\n"),
+ argv[1], argv[0], nt_errstr(result));
+ }
+ return result;
+ }
+
+ d_fprintf(stderr, _("Can only add members to global or local groups "
+ "which %s is not\n"), argv[0]);
+
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+static int rpc_group_addmem(struct net_context *c, int argc, const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_group_addmem_internals,
+ argc, argv);
+}
+
+static NTSTATUS rpc_del_groupmem(struct net_context *c,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ const char *member)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t group_rid;
+ struct policy_handle group_pol;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ struct samr_Ids rids, rid_types;
+ struct lsa_String lsa_acct_name;
+
+ struct dom_sid sid;
+
+ sid_copy(&sid, group_sid);
+
+ if (!sid_split_rid(&sid, &group_rid))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ /* Get sam policy handle */
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+
+ /* Get domain policy handle */
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ init_lsa_String(&lsa_acct_name, member);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &rid_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not lookup up group member %s\n"),
+ member);
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("Could not lookup up group member %s\n"),
+ member);
+ goto done;
+ }
+ if (rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (rid_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ group_rid,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_DeleteGroupMember(b, mem_ctx,
+ &group_pol,
+ rids.ids[0],
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+ done:
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ return status;
+}
+
+static NTSTATUS rpc_del_aliasmem(struct rpc_pipe_client *pipe_hnd,
+ struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *alias_sid,
+ const char *member)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t alias_rid;
+ struct policy_handle alias_pol;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ struct dom_sid member_sid;
+ enum lsa_SidType member_type;
+
+ struct dom_sid sid;
+
+ sid_copy(&sid, alias_sid);
+
+ if (!sid_split_rid(&sid, &alias_rid))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ result = get_sid_from_name(cli, mem_ctx,
+ member, &member_sid, &member_type);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Could not lookup up group member %s\n"),
+ member);
+ return result;
+ }
+
+ /* Get sam policy handle */
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get domain policy handle */
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ alias_rid,
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ status = dcerpc_samr_DeleteAliasMember(b, mem_ctx,
+ &alias_pol,
+ &member_sid,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = result;
+
+ done:
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ return status;
+}
+
+static NTSTATUS rpc_group_delmem_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dom_sid group_sid;
+ enum lsa_SidType group_type;
+
+ if (argc != 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc group delmem <group> <member>\n"
+ " Delete a member from a group\n"
+ " group\tGroup to delete member from\n"
+ " member\tMember to delete from group\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (!NT_STATUS_IS_OK(get_sid_from_name(cli, mem_ctx, argv[0],
+ &group_sid, &group_type))) {
+ d_fprintf(stderr, _("Could not lookup group name %s\n"),
+ argv[0]);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (group_type == SID_NAME_DOM_GRP) {
+ NTSTATUS result = rpc_del_groupmem(c, pipe_hnd, mem_ctx,
+ &group_sid, argv[1]);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Could not del %s from %s: %s\n"),
+ argv[1], argv[0], nt_errstr(result));
+ }
+ return result;
+ }
+
+ if (group_type == SID_NAME_ALIAS) {
+ NTSTATUS result = rpc_del_aliasmem(pipe_hnd, cli, mem_ctx,
+ &group_sid, argv[1]);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Could not del %s from %s: %s\n"),
+ argv[1], argv[0], nt_errstr(result));
+ }
+ return result;
+ }
+
+ d_fprintf(stderr, _("Can only delete members from global or local "
+ "groups which %s is not\n"), argv[0]);
+
+ return NT_STATUS_UNSUCCESSFUL;
+}
+
+static int rpc_group_delmem(struct net_context *c, int argc, const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_group_delmem_internals,
+ argc, argv);
+}
+
+/**
+ * List groups on a remote RPC server.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passes through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_group_list_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ uint32_t start_idx=0, max_entries=250, num_entries, i, loop_count = 0;
+ struct samr_SamArray *groups = NULL;
+ bool global = false;
+ bool local = false;
+ bool builtin = false;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc group list [global] [local] [builtin]\n"
+ " List groups on RPC server\n"
+ " global\tList global groups\n"
+ " local\tList local groups\n"
+ " builtin\tList builtin groups\n"
+ " If none of global, local or builtin is "
+ "specified, all three options are considered "
+ "set\n"));
+ return NT_STATUS_OK;
+ }
+
+ if (argc == 0) {
+ global = true;
+ local = true;
+ builtin = true;
+ }
+
+ for (i=0; i<argc; i++) {
+ if (strequal(argv[i], "global"))
+ global = true;
+
+ if (strequal(argv[i], "local"))
+ local = true;
+
+ if (strequal(argv[i], "builtin"))
+ builtin = true;
+ }
+
+ /* Get sam policy handle */
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, domain_sid),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Query domain groups */
+ if (c->opt_long_list_entries)
+ d_printf(_("\nGroup name Comment"
+ "\n-----------------------------\n"));
+ do {
+ uint32_t max_size, total_size, returned_size;
+ union samr_DispInfo info;
+
+ if (!global) break;
+
+ dcerpc_get_query_dispinfo_params(
+ loop_count, &max_entries, &max_size);
+
+ status = dcerpc_samr_QueryDisplayInfo(b, mem_ctx,
+ &domain_pol,
+ 3,
+ start_idx,
+ max_entries,
+ max_size,
+ &total_size,
+ &returned_size,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ num_entries = info.info3.count;
+ start_idx += info.info3.count;
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES))
+ break;
+
+ for (i = 0; i < num_entries; i++) {
+
+ const char *group = NULL;
+ const char *desc = NULL;
+
+ group = info.info3.entries[i].account_name.string;
+ desc = info.info3.entries[i].description.string;
+
+ if (c->opt_long_list_entries)
+ printf("%-21.21s %-50.50s\n",
+ group, desc);
+ else
+ printf("%s\n", group);
+ }
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+ /* query domain aliases */
+ start_idx = 0;
+ do {
+ if (!local) break;
+
+ status = dcerpc_samr_EnumDomainAliases(b, mem_ctx,
+ &domain_pol,
+ &start_idx,
+ &groups,
+ 0xffff,
+ &num_entries,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES))
+ break;
+
+ for (i = 0; i < num_entries; i++) {
+
+ const char *description = NULL;
+
+ if (c->opt_long_list_entries) {
+
+ struct policy_handle alias_pol;
+ union samr_AliasInfo *info = NULL;
+ NTSTATUS _result;
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ 0x8,
+ groups->entries[i].idx,
+ &alias_pol,
+ &_result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(_result)) {
+ status = dcerpc_samr_QueryAliasInfo(b, mem_ctx,
+ &alias_pol,
+ 3,
+ &info,
+ &_result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(_result)) {
+ status = dcerpc_samr_Close(b, mem_ctx,
+ &alias_pol,
+ &_result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(_result)) {
+ description = info->description.string;
+ }
+ }
+ }
+ }
+
+ if (description != NULL) {
+ printf("%-21.21s %-50.50s\n",
+ groups->entries[i].name.string,
+ description);
+ } else {
+ printf("%s\n", groups->entries[i].name.string);
+ }
+ }
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ /* Get builtin policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, &global_sid_Builtin),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* query builtin aliases */
+ start_idx = 0;
+ do {
+ if (!builtin) break;
+
+ status = dcerpc_samr_EnumDomainAliases(b, mem_ctx,
+ &domain_pol,
+ &start_idx,
+ &groups,
+ max_entries,
+ &num_entries,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ status = result;
+ break;
+ }
+
+ for (i = 0; i < num_entries; i++) {
+
+ const char *description = NULL;
+
+ if (c->opt_long_list_entries) {
+
+ struct policy_handle alias_pol;
+ union samr_AliasInfo *info = NULL;
+ NTSTATUS _result;
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ 0x8,
+ groups->entries[i].idx,
+ &alias_pol,
+ &_result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(_result)) {
+ status = dcerpc_samr_QueryAliasInfo(b, mem_ctx,
+ &alias_pol,
+ 3,
+ &info,
+ &_result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(_result)) {
+ status = dcerpc_samr_Close(b, mem_ctx,
+ &alias_pol,
+ &_result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(_result)) {
+ description = info->description.string;
+ }
+ }
+ }
+ }
+
+ if (description != NULL) {
+ printf("%-21.21s %-50.50s\n",
+ groups->entries[i].name.string,
+ description);
+ } else {
+ printf("%s\n", groups->entries[i].name.string);
+ }
+ }
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ status = result;
+
+ done:
+ return status;
+}
+
+static int rpc_group_list(struct net_context *c, int argc, const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_group_list_internals,
+ argc, argv);
+}
+
+static NTSTATUS rpc_list_group_members(struct net_context *c,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const struct dom_sid *domain_sid,
+ struct policy_handle *domain_pol,
+ uint32_t rid)
+{
+ NTSTATUS result, status;
+ struct policy_handle group_pol;
+ uint32_t num_members, *group_rids;
+ uint32_t i;
+ struct samr_RidAttrArray *rids = NULL;
+ struct lsa_Strings names;
+ struct samr_Ids types;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ rid,
+ &group_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ status = dcerpc_samr_QueryGroupMember(b, mem_ctx,
+ &group_pol,
+ &rids,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ num_members = rids->count;
+ group_rids = rids->rids;
+
+ while (num_members > 0) {
+ uint32_t this_time = 512;
+
+ if (num_members < this_time)
+ this_time = num_members;
+
+ status = dcerpc_samr_LookupRids(b, mem_ctx,
+ domain_pol,
+ this_time,
+ group_rids,
+ &names,
+ &types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+ if (names.count != this_time) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (types.count != this_time) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ /* We only have users as members, but make the output
+ the same as the output of alias members */
+
+ for (i = 0; i < this_time; i++) {
+
+ if (c->opt_long_list_entries) {
+ struct dom_sid sid;
+ struct dom_sid_buf sid_str;
+
+ sid_compose(&sid, domain_sid, group_rids[i]);
+
+ printf("%s %s\\%s %d\n",
+ dom_sid_str_buf(&sid, &sid_str),
+ domain_name,
+ names.names[i].string,
+ SID_NAME_USER);
+ } else {
+ printf("%s\\%s\n", domain_name,
+ names.names[i].string);
+ }
+ }
+
+ num_members -= this_time;
+ group_rids += 512;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS rpc_list_alias_members(struct net_context *c,
+ struct rpc_pipe_client *pipe_hnd,
+ struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *domain_pol,
+ uint32_t rid)
+{
+ NTSTATUS result, status;
+ struct rpc_pipe_client *lsa_pipe;
+ struct policy_handle alias_pol, lsa_pol;
+ uint32_t num_members;
+ struct dom_sid *alias_sids;
+ char **domains;
+ char **names;
+ enum lsa_SidType *types;
+ uint32_t i;
+ struct lsa_SidArray sid_array;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ rid,
+ &alias_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ status = dcerpc_samr_GetMembersInAlias(b, mem_ctx,
+ &alias_pol,
+ &sid_array,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Couldn't list alias members\n"));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Couldn't list alias members\n"));
+ return result;
+ }
+
+ num_members = sid_array.num_sids;
+
+ if (num_members == 0) {
+ return NT_STATUS_OK;
+ }
+
+ result = cli_rpc_pipe_open_noauth(cli,
+ &ndr_table_lsarpc,
+ &lsa_pipe);
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Couldn't open LSA pipe. Error was %s\n"),
+ nt_errstr(result) );
+ return result;
+ }
+
+ result = rpccli_lsa_open_policy(lsa_pipe, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED, &lsa_pol);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Couldn't open LSA policy handle\n"));
+ TALLOC_FREE(lsa_pipe);
+ return result;
+ }
+
+ alias_sids = talloc_zero_array(mem_ctx, struct dom_sid, num_members);
+ if (!alias_sids) {
+ d_fprintf(stderr, _("Out of memory\n"));
+ TALLOC_FREE(lsa_pipe);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_members; i++) {
+ sid_copy(&alias_sids[i], sid_array.sids[i].sid);
+ }
+
+ result = rpccli_lsa_lookup_sids(lsa_pipe, mem_ctx, &lsa_pol,
+ num_members, alias_sids,
+ &domains, &names, &types);
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) {
+ d_fprintf(stderr, _("Couldn't lookup SIDs\n"));
+ TALLOC_FREE(lsa_pipe);
+ return result;
+ }
+
+ for (i = 0; i < num_members; i++) {
+ struct dom_sid_buf sid_str;
+ dom_sid_str_buf(&alias_sids[i], &sid_str);
+
+ if (c->opt_long_list_entries) {
+ printf("%s %s\\%s %d\n", sid_str.buf,
+ domains[i] ? domains[i] : _("*unknown*"),
+ names[i] ? names[i] : _("*unknown*"), types[i]);
+ } else {
+ if (domains[i])
+ printf("%s\\%s\n", domains[i], names[i]);
+ else
+ printf("%s\n", sid_str.buf);
+ }
+ }
+
+ TALLOC_FREE(lsa_pipe);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS rpc_group_members_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS result, status;
+ struct policy_handle connect_pol, domain_pol;
+ struct samr_Ids rids, rid_types;
+ struct lsa_String lsa_acct_name;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* Get sam policy handle */
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, domain_sid),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ init_lsa_String(&lsa_acct_name, argv[0]); /* sure? */
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &rid_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+
+ /* Ok, did not find it in the global sam, try with builtin */
+
+ struct dom_sid sid_Builtin;
+
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+
+ sid_copy(&sid_Builtin, &global_sid_Builtin);
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ &sid_Builtin,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Couldn't find group %s\n"),
+ argv[0]);
+ return result;
+ }
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &rids,
+ &rid_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, _("Couldn't find group %s\n"),
+ argv[0]);
+ return result;
+ }
+ }
+
+ if (rids.count != 1) {
+ d_fprintf(stderr, _("Couldn't find group %s\n"),
+ argv[0]);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (rid_types.count != 1) {
+ d_fprintf(stderr, _("Couldn't find group %s\n"),
+ argv[0]);
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+
+ if (rid_types.ids[0] == SID_NAME_DOM_GRP) {
+ return rpc_list_group_members(c, pipe_hnd, mem_ctx, domain_name,
+ domain_sid, &domain_pol,
+ rids.ids[0]);
+ }
+
+ if (rid_types.ids[0] == SID_NAME_ALIAS) {
+ return rpc_list_alias_members(c, pipe_hnd, cli, mem_ctx, &domain_pol,
+ rids.ids[0]);
+ }
+
+ return NT_STATUS_NO_SUCH_GROUP;
+}
+
+static int rpc_group_members(struct net_context *c, int argc, const char **argv)
+{
+ if (argc != 1 || c->display_usage) {
+ return rpc_group_usage(c, argc, argv);
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_group_members_internals,
+ argc, argv);
+}
+
+static int rpc_group_rename_internals(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct GROUP_INFO_0 g0;
+ uint32_t parm_err;
+
+ if (argc != 2) {
+ d_printf(_("Usage:\n"));
+ d_printf("net rpc group rename group newname\n");
+ return -1;
+ }
+
+ g0.grpi0_name = argv[1];
+
+ status = NetGroupSetInfo(c->opt_host,
+ argv[0],
+ 0,
+ (uint8_t *)&g0,
+ &parm_err);
+
+ if (status != 0) {
+ d_fprintf(stderr, _("Renaming group %s failed with: %s\n"),
+ argv[0], libnetapi_get_error_string(c->netapi_ctx,
+ status));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int rpc_group_rename(struct net_context *c, int argc, const char **argv)
+{
+ if (argc != 2 || c->display_usage) {
+ return rpc_group_usage(c, argc, argv);
+ }
+
+ return rpc_group_rename_internals(c, argc, argv);
+}
+
+/**
+ * 'net rpc group' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_rpc_group(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+
+ struct functable func[] = {
+ {
+ "add",
+ rpc_group_add,
+ NET_TRANSPORT_RPC,
+ N_("Create specified group"),
+ N_("net rpc group add\n"
+ " Create specified group")
+ },
+ {
+ "delete",
+ rpc_group_delete,
+ NET_TRANSPORT_RPC,
+ N_("Delete specified group"),
+ N_("net rpc group delete\n"
+ " Delete specified group")
+ },
+ {
+ "addmem",
+ rpc_group_addmem,
+ NET_TRANSPORT_RPC,
+ N_("Add member to group"),
+ N_("net rpc group addmem\n"
+ " Add member to group")
+ },
+ {
+ "delmem",
+ rpc_group_delmem,
+ NET_TRANSPORT_RPC,
+ N_("Remove member from group"),
+ N_("net rpc group delmem\n"
+ " Remove member from group")
+ },
+ {
+ "list",
+ rpc_group_list,
+ NET_TRANSPORT_RPC,
+ N_("List groups"),
+ N_("net rpc group list\n"
+ " List groups")
+ },
+ {
+ "members",
+ rpc_group_members,
+ NET_TRANSPORT_RPC,
+ N_("List group members"),
+ N_("net rpc group members\n"
+ " List group members")
+ },
+ {
+ "rename",
+ rpc_group_rename,
+ NET_TRANSPORT_RPC,
+ N_("Rename group"),
+ N_("net rpc group rename\n"
+ " Rename group")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (status != 0) {
+ return -1;
+ }
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rpc group\n"
+ " Alias for net rpc group list global "
+ "local builtin\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_group_list_internals,
+ argc, argv);
+ }
+
+ return net_run_function(c, argc, argv, "net rpc group", func);
+}
+
+/****************************************************************************/
+
+static int rpc_share_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_share_usage(c, argc, argv);
+}
+
+/**
+ * Add a share on a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_share_add(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ char *sharename;
+ char *path;
+ uint32_t type = STYPE_DISKTREE; /* only allow disk shares to be added */
+ uint32_t num_users=0, perms=0;
+ char *password=NULL; /* don't allow a share password */
+ struct SHARE_INFO_2 i2;
+ uint32_t parm_error = 0;
+
+ if ((argc < 1) || !strchr(argv[0], '=') || c->display_usage) {
+ return rpc_share_usage(c, argc, argv);
+ }
+
+ if ((sharename = talloc_strdup(c, argv[0])) == NULL) {
+ return -1;
+ }
+
+ path = strchr(sharename, '=');
+ if (!path) {
+ return -1;
+ }
+
+ *path++ = '\0';
+
+ i2.shi2_netname = sharename;
+ i2.shi2_type = type;
+ i2.shi2_remark = c->opt_comment;
+ i2.shi2_permissions = perms;
+ i2.shi2_max_uses = c->opt_maxusers;
+ i2.shi2_current_uses = num_users;
+ i2.shi2_path = path;
+ i2.shi2_passwd = password;
+
+ status = NetShareAdd(c->opt_host,
+ 2,
+ (uint8_t *)&i2,
+ &parm_error);
+ if (status != 0) {
+ printf(_("NetShareAdd failed with: %s\n"),
+ libnetapi_get_error_string(c->netapi_ctx, status));
+ }
+
+ return status;
+}
+
+/**
+ * Delete a share on a remote RPC server.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_share_delete(struct net_context *c, int argc, const char **argv)
+{
+ if (argc < 1 || c->display_usage) {
+ return rpc_share_usage(c, argc, argv);
+ }
+
+ return NetShareDel(c->opt_host, argv[0], 0);
+}
+
+/**
+ * Formatted print of share info
+ *
+ * @param r pointer to SHARE_INFO_1 to format
+ **/
+
+static void display_share_info_1(struct net_context *c,
+ struct SHARE_INFO_1 *r)
+{
+ if (c->opt_long_list_entries) {
+ d_printf("%-12s %-8.8s %-50s\n",
+ r->shi1_netname,
+ net_share_type_str(r->shi1_type & ~(STYPE_TEMPORARY|STYPE_HIDDEN)),
+ r->shi1_remark);
+ } else {
+ d_printf("%s\n", r->shi1_netname);
+ }
+}
+
+static WERROR get_share_info(struct net_context *c,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ uint32_t level,
+ int argc,
+ const char **argv,
+ struct srvsvc_NetShareInfoCtr *info_ctr)
+{
+ WERROR result;
+ NTSTATUS status;
+ union srvsvc_NetShareInfo info;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* no specific share requested, enumerate all */
+ if (argc == 0) {
+
+ uint32_t preferred_len = 0xffffffff;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+
+ info_ctr->level = level;
+
+ status = dcerpc_srvsvc_NetShareEnumAll(b, mem_ctx,
+ pipe_hnd->desthost,
+ info_ctr,
+ preferred_len,
+ &total_entries,
+ &resume_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return ntstatus_to_werror(status);
+ }
+ return result;
+ }
+
+ /* request just one share */
+ status = dcerpc_srvsvc_NetShareGetInfo(b, mem_ctx,
+ pipe_hnd->desthost,
+ argv[0],
+ level,
+ &info,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ /* construct ctr */
+ ZERO_STRUCTP(info_ctr);
+
+ info_ctr->level = level;
+
+ switch (level) {
+ case 1:
+ {
+ struct srvsvc_NetShareCtr1 *ctr1;
+
+ ctr1 = talloc_zero(mem_ctx, struct srvsvc_NetShareCtr1);
+ W_ERROR_HAVE_NO_MEMORY(ctr1);
+
+ ctr1->count = 1;
+ ctr1->array = info.info1;
+
+ info_ctr->ctr.ctr1 = ctr1;
+
+ break;
+ }
+ case 2:
+ {
+ struct srvsvc_NetShareCtr2 *ctr2;
+
+ ctr2 = talloc_zero(mem_ctx, struct srvsvc_NetShareCtr2);
+ W_ERROR_HAVE_NO_MEMORY(ctr2);
+
+ ctr2->count = 1;
+ ctr2->array = info.info2;
+
+ info_ctr->ctr.ctr2 = ctr2;
+
+ break;
+ }
+ case 502:
+ {
+ struct srvsvc_NetShareCtr502 *ctr502;
+
+ ctr502 = talloc_zero(mem_ctx, struct srvsvc_NetShareCtr502);
+ W_ERROR_HAVE_NO_MEMORY(ctr502);
+
+ ctr502->count = 1;
+ ctr502->array = info.info502;
+
+ info_ctr->ctr.ctr502 = ctr502;
+
+ break;
+ }
+ } /* switch */
+done:
+ return result;
+}
+
+/***
+ * 'net rpc share list' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+static int rpc_share_list(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ struct SHARE_INFO_1 *i1 = NULL;
+ uint32_t entries_read = 0;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ uint32_t i, level = 1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc share list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List shares on remote server"));
+ return 0;
+ }
+
+ status = NetShareEnum(c->opt_host,
+ level,
+ (uint8_t **)(void *)&i1,
+ (uint32_t)-1,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+ if (status != 0) {
+ goto done;
+ }
+
+ /* Display results */
+
+ if (c->opt_long_list_entries) {
+ d_printf(_(
+ "\nEnumerating shared resources (exports) on remote server:\n\n"
+ "\nShare name Type Description\n"
+ "---------- ---- -----------\n"));
+ }
+ for (i = 0; i < entries_read; i++)
+ display_share_info_1(c, &i1[i]);
+ done:
+ return status;
+}
+
+static bool check_share_availability(struct cli_state *cli, const char *netname)
+{
+ NTSTATUS status;
+
+ status = cli_tree_connect(cli, netname, "A:", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("skipping [%s]: not a file share.\n"), netname);
+ return false;
+ }
+
+ status = cli_tdis(cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("cli_tdis returned %s\n"), nt_errstr(status));
+ return false;
+ }
+
+ return true;
+}
+
+static bool check_share_sanity(struct net_context *c, struct cli_state *cli,
+ const char *netname, uint32_t type)
+{
+ /* only support disk shares */
+ if (! ( type == STYPE_DISKTREE || type == (STYPE_DISKTREE | STYPE_HIDDEN)) ) {
+ printf(_("share [%s] is not a diskshare (type: %x)\n"), netname,
+ type);
+ return false;
+ }
+
+ /* skip builtin shares */
+ /* FIXME: should print$ be added too ? */
+ if (strequal(netname,"IPC$") || strequal(netname,"ADMIN$") ||
+ strequal(netname,"global"))
+ return false;
+
+ if (c->opt_exclude && in_list(netname, c->opt_exclude, false)) {
+ printf(_("excluding [%s]\n"), netname);
+ return false;
+ }
+
+ return check_share_availability(cli, netname);
+}
+
+/**
+ * Migrate shares from a remote RPC server to the local RPC server.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_share_migrate_shares_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct srvsvc_NetShareInfoCtr ctr_src;
+ uint32_t i;
+ struct rpc_pipe_client *srvsvc_pipe = NULL;
+ struct cli_state *cli_dst = NULL;
+ uint32_t level = 502; /* includes secdesc */
+ uint32_t parm_error = 0;
+ struct dcerpc_binding_handle *b;
+
+ result = get_share_info(c, pipe_hnd, mem_ctx, level, argc, argv,
+ &ctr_src);
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* connect destination PI_SRVSVC */
+ nt_status = connect_dst_pipe(c, &cli_dst, &srvsvc_pipe,
+ &ndr_table_srvsvc);
+ if (!NT_STATUS_IS_OK(nt_status))
+ return nt_status;
+
+ b = srvsvc_pipe->binding_handle;
+
+ for (i = 0; i < ctr_src.ctr.ctr502->count; i++) {
+
+ union srvsvc_NetShareInfo info;
+ struct srvsvc_NetShareInfo502 info502 =
+ ctr_src.ctr.ctr502->array[i];
+
+ /* reset error-code */
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ if (!check_share_sanity(c, cli, info502.name, info502.type))
+ continue;
+
+ /* finally add the share on the dst server */
+
+ printf(_("migrating: [%s], path: %s, comment: %s, without "
+ "share-ACLs\n"),
+ info502.name, info502.path, info502.comment);
+
+ info.info502 = &info502;
+
+ nt_status = dcerpc_srvsvc_NetShareAdd(b, mem_ctx,
+ srvsvc_pipe->desthost,
+ 502,
+ &info,
+ &parm_error,
+ &result);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf(_("cannot add share: %s\n"),
+ nt_errstr(nt_status));
+ goto done;
+ }
+ if (W_ERROR_V(result) == W_ERROR_V(WERR_FILE_EXISTS)) {
+ printf(_(" [%s] does already exist\n"),
+ info502.name);
+ continue;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ nt_status = werror_to_ntstatus(result);
+ printf(_("cannot add share: %s\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ if (cli_dst) {
+ cli_shutdown(cli_dst);
+ }
+
+ return nt_status;
+
+}
+
+/**
+ * Migrate shares from a RPC server to another.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_share_migrate_shares(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc share migrate shares\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate shares to local server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_srvsvc, 0,
+ rpc_share_migrate_shares_internals,
+ argc, argv);
+}
+
+/**
+ * Copy a file/dir
+ *
+ * @param f file_info
+ * @param mask current search mask
+ * @param state arg-pointer
+ *
+ **/
+static NTSTATUS copy_fn(struct file_info *f,
+ const char *mask, void *state)
+{
+ static NTSTATUS nt_status;
+ static struct copy_clistate *local_state;
+ static fstring filename, new_mask;
+ fstring dir;
+ char *old_dir;
+ struct net_context *c;
+
+ local_state = (struct copy_clistate *)state;
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ c = local_state->c;
+
+ if (strequal(f->name, ".") || strequal(f->name, ".."))
+ return NT_STATUS_OK;
+
+ DEBUG(3,("got mask: %s, name: %s\n", mask, f->name));
+
+ /* DIRECTORY */
+ if (f->attr & FILE_ATTRIBUTE_DIRECTORY) {
+
+ DEBUG(3,("got dir: %s\n", f->name));
+
+ fstrcpy(dir, local_state->cwd);
+ fstrcat(dir, "\\");
+ fstrcat(dir, f->name);
+
+ switch (net_mode_share)
+ {
+ case NET_MODE_SHARE_MIGRATE:
+ /* create that directory */
+ nt_status = net_copy_file(c, local_state->mem_ctx,
+ local_state->cli_share_src,
+ local_state->cli_share_dst,
+ dir, dir,
+ c->opt_acls? true : false,
+ c->opt_attrs? true : false,
+ c->opt_timestamps? true:false,
+ false);
+ break;
+ default:
+ d_fprintf(stderr, _("Unsupported mode %d\n"), net_mode_share);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf(_("could not handle dir %s: %s\n"),
+ dir, nt_errstr(nt_status));
+ return nt_status;
+ }
+
+ /* search below that directory */
+ if (strlcpy(new_mask, dir, sizeof(new_mask)) >= sizeof(new_mask)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (strlcat(new_mask, "\\*", sizeof(new_mask)) >= sizeof(new_mask)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ old_dir = local_state->cwd;
+ local_state->cwd = dir;
+ nt_status = sync_files(local_state, new_mask);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf(_("could not handle files\n"));
+ }
+ local_state->cwd = old_dir;
+
+ return nt_status;
+ }
+
+
+ /* FILE */
+ fstrcpy(filename, local_state->cwd);
+ fstrcat(filename, "\\");
+ fstrcat(filename, f->name);
+
+ DEBUG(3,("got file: %s\n", filename));
+
+ switch (net_mode_share)
+ {
+ case NET_MODE_SHARE_MIGRATE:
+ nt_status = net_copy_file(c, local_state->mem_ctx,
+ local_state->cli_share_src,
+ local_state->cli_share_dst,
+ filename, filename,
+ c->opt_acls? true : false,
+ c->opt_attrs? true : false,
+ c->opt_timestamps? true: false,
+ true);
+ break;
+ default:
+ d_fprintf(stderr, _("Unsupported file mode %d\n"),
+ net_mode_share);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status))
+ printf(_("could not handle file %s: %s\n"),
+ filename, nt_errstr(nt_status));
+ return nt_status;
+}
+
+/**
+ * sync files, can be called recursively to list files
+ * and then call copy_fn for each file
+ *
+ * @param cp_clistate pointer to the copy_clistate we work with
+ * @param mask the current search mask
+ *
+ * @return Boolean result
+ **/
+static NTSTATUS sync_files(struct copy_clistate *cp_clistate, const char *mask)
+{
+ struct cli_state *targetcli;
+ char *targetpath = NULL;
+ NTSTATUS status;
+
+ DEBUG(3,("calling cli_list with mask: %s\n", mask));
+
+ status = cli_resolve_path(talloc_tos(), "", NULL,
+ cp_clistate->cli_share_src,
+ mask, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("cli_resolve_path %s failed with error: "
+ "%s\n"),
+ mask, nt_errstr(status));
+ return status;
+ }
+
+ status = cli_list(targetcli, targetpath, cp_clistate->attribute,
+ copy_fn, cp_clistate);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("listing %s failed with error: %s\n"),
+ mask, nt_errstr(status));
+ }
+
+ return status;
+}
+
+
+/**
+ * Set the top level directory permissions before we do any further copies.
+ * Should set up ACL inheritance.
+ **/
+
+bool copy_top_level_perms(struct net_context *c,
+ struct copy_clistate *cp_clistate,
+ const char *sharename)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ switch (net_mode_share) {
+ case NET_MODE_SHARE_MIGRATE:
+ DEBUG(3,("calling net_copy_fileattr for '.' directory in share %s\n", sharename));
+ nt_status = net_copy_fileattr(c,
+ cp_clistate->mem_ctx,
+ cp_clistate->cli_share_src,
+ cp_clistate->cli_share_dst,
+ "\\", "\\",
+ c->opt_acls? true : false,
+ c->opt_attrs? true : false,
+ c->opt_timestamps? true: false,
+ false);
+ break;
+ default:
+ d_fprintf(stderr, _("Unsupported mode %d\n"), net_mode_share);
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf(_("Could handle directory attributes for top level "
+ "directory of share %s. Error %s\n"),
+ sharename, nt_errstr(nt_status));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Sync all files inside a remote share to another share (over smb).
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_share_migrate_files_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct srvsvc_NetShareInfoCtr ctr_src;
+ uint32_t i;
+ uint32_t level = 502;
+ struct copy_clistate cp_clistate;
+ bool got_src_share = false;
+ bool got_dst_share = false;
+ const char *mask = "\\*";
+ char *dst = NULL;
+
+ dst = SMB_STRDUP(c->opt_destination?c->opt_destination:"127.0.0.1");
+ if (dst == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ result = get_share_info(c, pipe_hnd, mem_ctx, level, argc, argv,
+ &ctr_src);
+
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ for (i = 0; i < ctr_src.ctr.ctr502->count; i++) {
+
+ struct srvsvc_NetShareInfo502 info502 =
+ ctr_src.ctr.ctr502->array[i];
+
+ if (!check_share_sanity(c, cli, info502.name, info502.type))
+ continue;
+
+ /* one might not want to mirror whole discs :) */
+ if (strequal(info502.name, "print$") || info502.name[1] == '$') {
+ d_printf(_("skipping [%s]: builtin/hidden share\n"),
+ info502.name);
+ continue;
+ }
+
+ switch (net_mode_share)
+ {
+ case NET_MODE_SHARE_MIGRATE:
+ printf("syncing");
+ break;
+ default:
+ d_fprintf(stderr, _("Unsupported mode %d\n"),
+ net_mode_share);
+ break;
+ }
+ printf(_(" [%s] files and directories %s ACLs, %s DOS "
+ "Attributes %s\n"),
+ info502.name,
+ c->opt_acls ? _("including") : _("without"),
+ c->opt_attrs ? _("including") : _("without"),
+ c->opt_timestamps ? _("(preserving timestamps)") : "");
+
+ cp_clistate.mem_ctx = mem_ctx;
+ cp_clistate.cli_share_src = NULL;
+ cp_clistate.cli_share_dst = NULL;
+ cp_clistate.cwd = NULL;
+ cp_clistate.attribute = FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_DIRECTORY;
+ cp_clistate.c = c;
+
+ /* open share source */
+ nt_status = connect_to_service(c, &cp_clistate.cli_share_src,
+ smbXcli_conn_remote_sockaddr(cli->conn),
+ smbXcli_conn_remote_name(cli->conn),
+ info502.name, "A:");
+ if (!NT_STATUS_IS_OK(nt_status))
+ goto done;
+
+ got_src_share = true;
+
+ if (net_mode_share == NET_MODE_SHARE_MIGRATE) {
+ /* open share destination */
+ nt_status = connect_to_service(c, &cp_clistate.cli_share_dst,
+ NULL, dst, info502.name, "A:");
+ if (!NT_STATUS_IS_OK(nt_status))
+ goto done;
+
+ got_dst_share = true;
+ }
+
+ if (!copy_top_level_perms(c, &cp_clistate, info502.name)) {
+ d_fprintf(stderr, _("Could not handle the top level "
+ "directory permissions for the "
+ "share: %s\n"), info502.name);
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ nt_status = sync_files(&cp_clistate, mask);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr, _("could not handle files for share: "
+ "%s\n"), info502.name);
+ goto done;
+ }
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+
+ if (got_src_share)
+ cli_shutdown(cp_clistate.cli_share_src);
+
+ if (got_dst_share)
+ cli_shutdown(cp_clistate.cli_share_dst);
+
+ SAFE_FREE(dst);
+ return nt_status;
+
+}
+
+static int rpc_share_migrate_files(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net share migrate files\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate files to local server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_srvsvc, 0,
+ rpc_share_migrate_files_internals,
+ argc, argv);
+}
+
+/**
+ * Migrate share-ACLs from a remote RPC server to the local RPC server.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_share_migrate_security_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ WERROR result;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct srvsvc_NetShareInfoCtr ctr_src;
+ union srvsvc_NetShareInfo info;
+ uint32_t i;
+ struct rpc_pipe_client *srvsvc_pipe = NULL;
+ struct cli_state *cli_dst = NULL;
+ uint32_t level = 502; /* includes secdesc */
+ uint32_t parm_error = 0;
+ struct dcerpc_binding_handle *b;
+
+ result = get_share_info(c, pipe_hnd, mem_ctx, level, argc, argv,
+ &ctr_src);
+
+ if (!W_ERROR_IS_OK(result))
+ goto done;
+
+ /* connect destination PI_SRVSVC */
+ nt_status = connect_dst_pipe(c, &cli_dst, &srvsvc_pipe,
+ &ndr_table_srvsvc);
+ if (!NT_STATUS_IS_OK(nt_status))
+ return nt_status;
+
+ b = srvsvc_pipe->binding_handle;
+
+ for (i = 0; i < ctr_src.ctr.ctr502->count; i++) {
+
+ struct srvsvc_NetShareInfo502 info502 =
+ ctr_src.ctr.ctr502->array[i];
+
+ /* reset error-code */
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ if (!check_share_sanity(c, cli, info502.name, info502.type))
+ continue;
+
+ printf(_("migrating: [%s], path: %s, comment: %s, including "
+ "share-ACLs\n"),
+ info502.name, info502.path, info502.comment);
+
+ if (c->opt_verbose)
+ display_sec_desc(info502.sd_buf.sd);
+
+ /* FIXME: shouldn't we be able to just set the security descriptor ? */
+ info.info502 = &info502;
+
+ /* finally modify the share on the dst server */
+ nt_status = dcerpc_srvsvc_NetShareSetInfo(b, mem_ctx,
+ srvsvc_pipe->desthost,
+ info502.name,
+ level,
+ &info,
+ &parm_error,
+ &result);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf(_("cannot set share-acl: %s\n"),
+ nt_errstr(nt_status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ nt_status = werror_to_ntstatus(result);
+ printf(_("cannot set share-acl: %s\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ if (cli_dst) {
+ cli_shutdown(cli_dst);
+ }
+
+ return nt_status;
+
+}
+
+/**
+ * Migrate share-acls from a RPC server to another.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_share_migrate_security(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc share migrate security\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate share-acls to local server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_srvsvc, 0,
+ rpc_share_migrate_security_internals,
+ argc, argv);
+}
+
+/**
+ * Migrate shares (including share-definitions, share-acls and files with acls/attrs)
+ * from one server to another.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ *
+ **/
+static int rpc_share_migrate_all(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc share migrate all\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrates shares including all share settings"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ /* order is important. we don't want to be locked out by the share-acl
+ * before copying files - gd */
+
+ ret = run_rpc_command(c, NULL, &ndr_table_srvsvc, 0,
+ rpc_share_migrate_shares_internals, argc, argv);
+ if (ret)
+ return ret;
+
+ ret = run_rpc_command(c, NULL, &ndr_table_srvsvc, 0,
+ rpc_share_migrate_files_internals, argc, argv);
+ if (ret)
+ return ret;
+
+ return run_rpc_command(c, NULL, &ndr_table_srvsvc, 0,
+ rpc_share_migrate_security_internals, argc,
+ argv);
+}
+
+
+/**
+ * 'net rpc share migrate' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+static int rpc_share_migrate(struct net_context *c, int argc, const char **argv)
+{
+
+ struct functable func[] = {
+ {
+ "all",
+ rpc_share_migrate_all,
+ NET_TRANSPORT_RPC,
+ N_("Migrate shares from remote to local server"),
+ N_("net rpc share migrate all\n"
+ " Migrate shares from remote to local server")
+ },
+ {
+ "files",
+ rpc_share_migrate_files,
+ NET_TRANSPORT_RPC,
+ N_("Migrate files from remote to local server"),
+ N_("net rpc share migrate files\n"
+ " Migrate files from remote to local server")
+ },
+ {
+ "security",
+ rpc_share_migrate_security,
+ NET_TRANSPORT_RPC,
+ N_("Migrate share-ACLs from remote to local server"),
+ N_("net rpc share migrate security\n"
+ " Migrate share-ACLs from remote to local server")
+ },
+ {
+ "shares",
+ rpc_share_migrate_shares,
+ NET_TRANSPORT_RPC,
+ N_("Migrate shares from remote to local server"),
+ N_("net rpc share migrate shares\n"
+ " Migrate shares from remote to local server")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ net_mode_share = NET_MODE_SHARE_MIGRATE;
+
+ return net_run_function(c, argc, argv, "net rpc share migrate", func);
+}
+
+struct full_alias {
+ struct dom_sid sid;
+ uint32_t num_members;
+ struct dom_sid *members;
+};
+
+static int num_server_aliases;
+static struct full_alias *server_aliases;
+
+/*
+ * Add an alias to the static list.
+ */
+static void push_alias(struct full_alias *alias)
+{
+ size_t array_size;
+
+ if (server_aliases == NULL) {
+ server_aliases = talloc_array(NULL, struct full_alias, 100);
+ if (server_aliases == NULL) {
+ smb_panic("talloc_array failed");
+ }
+ }
+
+ array_size = talloc_array_length(server_aliases);
+ if (array_size == num_server_aliases) {
+ server_aliases = talloc_realloc(NULL, server_aliases,
+ struct full_alias, array_size + 100);
+ if (server_aliases == NULL) {
+ smb_panic("talloc_realloc failed");
+ }
+ }
+
+ server_aliases[num_server_aliases] = *alias;
+ num_server_aliases += 1;
+}
+
+/*
+ * For a specific domain on the server, fetch all the aliases
+ * and their members. Add all of them to the server_aliases.
+ */
+
+static NTSTATUS rpc_fetch_domain_aliases(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *connect_pol,
+ const struct dom_sid *domain_sid)
+{
+ uint32_t start_idx, max_entries, num_entries, i;
+ struct samr_SamArray *groups = NULL;
+ NTSTATUS result, status;
+ struct policy_handle domain_pol;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, domain_sid),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ start_idx = 0;
+ max_entries = 250;
+
+ do {
+ status = dcerpc_samr_EnumDomainAliases(b, mem_ctx,
+ &domain_pol,
+ &start_idx,
+ &groups,
+ max_entries,
+ &num_entries,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ for (i = 0; i < num_entries; i++) {
+
+ struct policy_handle alias_pol;
+ struct full_alias alias;
+ struct lsa_SidArray sid_array;
+ int j;
+ NTSTATUS _result;
+
+ status = dcerpc_samr_OpenAlias(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ groups->entries[i].idx,
+ &alias_pol,
+ &_result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(_result)) {
+ status = _result;
+ goto done;
+ }
+
+ status = dcerpc_samr_GetMembersInAlias(b, mem_ctx,
+ &alias_pol,
+ &sid_array,
+ &_result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(_result)) {
+ status = _result;
+ goto done;
+ }
+
+ alias.num_members = sid_array.num_sids;
+
+ status = dcerpc_samr_Close(b, mem_ctx, &alias_pol, &_result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(_result)) {
+ status = _result;
+ goto done;
+ }
+
+ alias.members = NULL;
+
+ if (alias.num_members > 0) {
+ alias.members = SMB_MALLOC_ARRAY(struct dom_sid, alias.num_members);
+ if (alias.members == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (j = 0; j < alias.num_members; j++)
+ sid_copy(&alias.members[j],
+ sid_array.sids[j].sid);
+ }
+
+ sid_compose(&alias.sid, domain_sid,
+ groups->entries[i].idx);
+
+ push_alias(&alias);
+ }
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ status = NT_STATUS_OK;
+
+ done:
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+
+ return status;
+}
+
+/*
+ * Dump server_aliases as names for debugging purposes.
+ */
+
+static NTSTATUS rpc_aliaslist_dump(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ uint32_t i;
+ NTSTATUS result;
+ struct policy_handle lsa_pol;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ result = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &lsa_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ for (i=0; i<num_server_aliases; i++) {
+ char **names;
+ char **domains;
+ enum lsa_SidType *types;
+ int j;
+
+ struct full_alias *alias = &server_aliases[i];
+
+ result = rpccli_lsa_lookup_sids(pipe_hnd, mem_ctx, &lsa_pol, 1,
+ &alias->sid,
+ &domains, &names, &types);
+ if (!NT_STATUS_IS_OK(result))
+ continue;
+
+ DEBUG(1, ("%s\\%s %d: ", domains[0], names[0], types[0]));
+
+ if (alias->num_members == 0) {
+ DEBUG(1, ("\n"));
+ continue;
+ }
+
+ result = rpccli_lsa_lookup_sids(pipe_hnd, mem_ctx, &lsa_pol,
+ alias->num_members,
+ alias->members,
+ &domains, &names, &types);
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED))
+ continue;
+
+ for (j=0; j<alias->num_members; j++)
+ DEBUG(1, ("%s\\%s (%d); ",
+ domains[j] ? domains[j] : "*unknown*",
+ names[j] ? names[j] : "*unknown*",types[j]));
+ DEBUG(1, ("\n"));
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &lsa_pol, &result);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Fetch a list of all server aliases and their members into
+ * server_aliases.
+ */
+
+static NTSTATUS rpc_aliaslist_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS result, status;
+ struct policy_handle connect_pol;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = rpc_fetch_domain_aliases(pipe_hnd, mem_ctx, &connect_pol,
+ &global_sid_Builtin);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_fetch_domain_aliases(pipe_hnd, mem_ctx, &connect_pol,
+ domain_sid);
+
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ done:
+ return status;
+}
+
+struct user_token {
+ fstring name;
+ struct security_token *token;
+};
+
+static void add_sid_to_token(struct security_token *token, const struct dom_sid *sid)
+{
+ NTSTATUS status = add_sid_to_array_unique(token, sid,
+ &token->sids, &token->num_sids);
+ /*
+ * This is both very unlikely and mostly harmless in a command
+ * line tool
+ */
+ SMB_ASSERT(NT_STATUS_IS_OK(status));
+}
+
+static void init_user_token(struct user_token *token_list,
+ struct security_token **token,
+ struct dom_sid *user_sid)
+{
+ /*
+ * This token is not from the auth stack, only has user SIDs
+ * and must fail if conditional ACEs are found in the security
+ * descriptor
+ */
+ *token = security_token_initialise(token_list, CLAIMS_EVALUATION_INVALID_STATE);
+ SMB_ASSERT(*token);
+
+ add_sid_to_token(*token,
+ user_sid);
+
+ add_sid_to_token(*token,
+ &global_sid_World);
+
+ add_sid_to_token(*token,
+ &global_sid_Network);
+
+ add_sid_to_token(*token,
+ &global_sid_Authenticated_Users);
+}
+
+static void dump_user_token(struct user_token *token)
+{
+ uint32_t i;
+
+ d_printf("%s\n", token->name);
+
+ for (i=0; i<token->token->num_sids; i++) {
+ struct dom_sid_buf buf;
+ d_printf(" %s\n",
+ dom_sid_str_buf(&token->token->sids[i], &buf));
+ }
+}
+
+static bool is_alias_member(struct dom_sid *sid, struct full_alias *alias)
+{
+ uint32_t i;
+
+ for (i=0; i<alias->num_members; i++) {
+ if (dom_sid_equal(sid, &alias->members[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void collect_sid_memberships(struct security_token *token, struct dom_sid sid)
+{
+ int i;
+
+ for (i=0; i<num_server_aliases; i++) {
+ if (is_alias_member(&sid, &server_aliases[i]))
+ add_sid_to_token(token, &server_aliases[i].sid);
+ }
+}
+
+/*
+ * We got a user token with all the SIDs we can know about without asking the
+ * server directly. These are the user and domain group sids. All of these can
+ * be members of aliases. So scan the list of aliases for each of the SIDs and
+ * add them to the token.
+ */
+
+static void collect_alias_memberships(struct security_token *token)
+{
+ int num_global_sids = token->num_sids;
+ int i;
+
+ for (i=0; i<num_global_sids; i++) {
+ collect_sid_memberships(token, token->sids[i]);
+ }
+}
+
+static bool get_user_sids(const char *domain, const char *user,
+ struct user_token *token_list,
+ struct security_token **token)
+{
+ wbcErr wbc_status = WBC_ERR_UNKNOWN_FAILURE;
+ enum wbcSidType type;
+ fstring full_name;
+ struct wbcDomainSid wsid;
+ char sid_str[WBC_SID_STRING_BUFLEN];
+ struct dom_sid user_sid;
+ uint32_t num_groups;
+ gid_t *groups = NULL;
+ uint32_t i;
+
+ fstr_sprintf(full_name, "%s%c%s",
+ domain, *lp_winbind_separator(), user);
+
+ /* First let's find out the user sid */
+
+ wbc_status = wbcLookupName(domain, user, &wsid, &type);
+
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ DEBUG(1, ("winbind could not find %s: %s\n",
+ full_name, wbcErrorString(wbc_status)));
+ return false;
+ }
+
+ wbcSidToStringBuf(&wsid, sid_str, sizeof(sid_str));
+
+ if (type != WBC_SID_NAME_USER) {
+ DEBUG(1, ("%s is not a user\n", full_name));
+ return false;
+ }
+
+ if (!string_to_sid(&user_sid, sid_str)) {
+ DEBUG(1,("Could not convert sid %s from string\n", sid_str));
+ return false;
+ }
+
+ init_user_token(token_list, token, &user_sid);
+
+ /* And now the groups winbind knows about */
+
+ wbc_status = wbcGetGroups(full_name, &num_groups, &groups);
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ DEBUG(1, ("winbind could not get groups of %s: %s\n",
+ full_name, wbcErrorString(wbc_status)));
+ return false;
+ }
+
+ for (i = 0; i < num_groups; i++) {
+ gid_t gid = groups[i];
+ struct dom_sid sid;
+ bool ok;
+
+ wbc_status = wbcGidToSid(gid, &wsid);
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ DEBUG(1, ("winbind could not find SID of gid %u: %s\n",
+ (unsigned int)gid, wbcErrorString(wbc_status)));
+ wbcFreeMemory(groups);
+ return false;
+ }
+
+ wbcSidToStringBuf(&wsid, sid_str, sizeof(sid_str));
+
+ DEBUG(3, (" %s\n", sid_str));
+
+ ok = string_to_sid(&sid, sid_str);
+ if (!ok) {
+ DEBUG(1, ("Failed to convert string to SID\n"));
+ wbcFreeMemory(groups);
+ return false;
+ }
+ add_sid_to_token(*token, &sid);
+ }
+ wbcFreeMemory(groups);
+
+ return true;
+}
+
+/**
+ * Get a list of all user tokens we want to look at
+ **/
+
+static bool get_user_tokens(struct net_context *c, int *num_tokens,
+ struct user_token **user_tokens)
+{
+ wbcErr wbc_status = WBC_ERR_UNKNOWN_FAILURE;
+ uint32_t i, num_users;
+ const char **users;
+ struct user_token *result;
+ TALLOC_CTX *frame = NULL;
+
+ if (lp_winbind_use_default_domain() &&
+ (c->opt_target_workgroup == NULL)) {
+ d_fprintf(stderr, _("winbind use default domain = yes set, "
+ "please specify a workgroup\n"));
+ return false;
+ }
+
+ /* Send request to winbind daemon */
+
+ wbc_status = wbcListUsers(NULL, &num_users, &users);
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ DEBUG(1, (_("winbind could not list users: %s\n"),
+ wbcErrorString(wbc_status)));
+ return false;
+ }
+
+ result = talloc_zero_array(NULL, struct user_token, num_users);
+
+ if (result == NULL) {
+ DBG_ERR("Could not talloc token array\n");
+ wbcFreeMemory(users);
+ return false;
+ }
+
+ frame = talloc_stackframe();
+ for (i=0; i < num_users; i++) {
+ fstring domain, user;
+ char *p;
+
+ fstrcpy(result[i].name, users[i]);
+
+ p = strchr(users[i], *lp_winbind_separator());
+
+ DEBUG(3, ("%s\n", users[i]));
+
+ if (p == NULL) {
+ fstrcpy(domain, c->opt_target_workgroup);
+ fstrcpy(user, users[i]);
+ } else {
+ *p++ = '\0';
+ fstrcpy(domain, users[i]);
+ if (!strupper_m(domain)) {
+ DEBUG(1, ("strupper_m %s failed\n", domain));
+ wbcFreeMemory(users);
+ return false;
+ }
+ fstrcpy(user, p);
+ }
+
+ get_user_sids(domain, user, result, &(result[i].token));
+ }
+ TALLOC_FREE(frame);
+ wbcFreeMemory(users);
+
+ *num_tokens = num_users;
+ *user_tokens = result;
+
+ return true;
+}
+
+static bool get_user_tokens_from_file(FILE *f,
+ int *num_tokens,
+ struct user_token **tokens)
+{
+ struct user_token *token = NULL;
+
+ while (!feof(f)) {
+ fstring line;
+
+ if (fgets(line, sizeof(line)-1, f) == NULL) {
+ return true;
+ }
+
+ if ((strlen(line) > 0) && (line[strlen(line)-1] == '\n')) {
+ line[strlen(line)-1] = '\0';
+ }
+
+ if (line[0] == ' ') {
+ /* We have a SID */
+
+ struct dom_sid sid;
+ if(!string_to_sid(&sid, &line[1])) {
+ DEBUG(1,("get_user_tokens_from_file: Could "
+ "not convert sid %s \n",&line[1]));
+ return false;
+ }
+
+ if (token == NULL) {
+ DEBUG(0, ("File does not begin with username\n"));
+ return false;
+ }
+
+ add_sid_to_token(token->token, &sid);
+ continue;
+ }
+
+ /* And a new user... */
+
+ *num_tokens += 1;
+ *tokens = talloc_realloc(NULL,
+ *tokens,
+ struct user_token,
+ *num_tokens);
+ if (*tokens == NULL) {
+ DBG_ERR("Could not talloc_realloc tokens\n");
+ return false;
+ }
+
+ token = &((*tokens)[*num_tokens-1]);
+
+ if (strlcpy(token->name, line, sizeof(token->name)) >= sizeof(token->name)) {
+ return false;
+ }
+ token->token
+ = security_token_initialise(*tokens,
+ CLAIMS_EVALUATION_INVALID_STATE);
+ if (token->token == NULL) {
+ DBG_ERR("security_token_initialise() failed: "
+ "Could not allocate security_token with \n");
+ return false;
+ }
+
+ continue;
+ }
+
+ return false;
+}
+
+
+/*
+ * Show the list of all users that have access to a share
+ */
+
+static void show_userlist(struct rpc_pipe_client *pipe_hnd,
+ struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *netname,
+ int num_tokens,
+ struct user_token *tokens)
+{
+ uint16_t fnum;
+ struct security_descriptor *share_sd = NULL;
+ struct security_descriptor *root_sd = NULL;
+ int i;
+ union srvsvc_NetShareInfo info;
+ WERROR result;
+ NTSTATUS status;
+ struct smbXcli_tcon *orig_tcon = NULL;
+ char *orig_share = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_srvsvc_NetShareGetInfo(b, mem_ctx,
+ pipe_hnd->desthost,
+ netname,
+ 502,
+ &info,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status) || !W_ERROR_IS_OK(result)) {
+ DEBUG(1, ("Could not query secdesc for share %s\n",
+ netname));
+ return;
+ }
+
+ share_sd = info.info502->sd_buf.sd;
+ if (share_sd == NULL) {
+ DEBUG(1, ("Got no secdesc for share %s\n",
+ netname));
+ }
+
+ if (cli_state_has_tcon(cli)) {
+ cli_state_save_tcon_share(cli, &orig_tcon, &orig_share);
+ }
+
+ if (!NT_STATUS_IS_OK(cli_tree_connect(cli, netname, "A:", NULL))) {
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ return;
+ }
+
+ if (!NT_STATUS_IS_OK(cli_ntcreate(cli, "\\", 0, READ_CONTROL_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0x0, 0x0, &fnum, NULL))) {
+ cli_query_secdesc(cli, fnum, mem_ctx, &root_sd);
+ }
+
+ for (i=0; i<num_tokens; i++) {
+ uint32_t acc_granted;
+
+ if (share_sd != NULL) {
+ status = se_access_check(share_sd, tokens[i].token,
+ 1, &acc_granted);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not check share_sd for "
+ "user %s\n",
+ tokens[i].name));
+ continue;
+ }
+ }
+
+ if (root_sd == NULL) {
+ d_printf(" %s\n", tokens[i].name);
+ continue;
+ }
+
+ status = se_access_check(root_sd, tokens[i].token,
+ 1, &acc_granted);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not check root_sd for user %s\n",
+ tokens[i].name));
+ continue;
+ }
+ d_printf(" %s\n", tokens[i].name);
+ }
+
+ if (fnum != (uint16_t)-1)
+ cli_close(cli, fnum);
+ cli_tdis(cli);
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+
+ return;
+}
+
+/**
+ * List shares on a remote RPC server, including the security descriptors.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_share_allowedusers_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ bool r;
+ FILE *f;
+ NTSTATUS nt_status = NT_STATUS_OK;
+ uint32_t total_entries = 0;
+ uint32_t resume_handle = 0;
+ uint32_t preferred_len = 0xffffffff;
+ uint32_t i;
+ struct dcerpc_binding_handle *b = NULL;
+ struct srvsvc_NetShareInfoCtr info_ctr;
+ struct srvsvc_NetShareCtr1 ctr1;
+ WERROR result;
+
+ struct user_token *tokens = NULL;
+ int num_tokens = 0;
+
+ if (argc == 0) {
+ f = stdin;
+ } else {
+ if (strequal(argv[0], "-")) {
+ f = stdin;
+ } else {
+ f = fopen(argv[0], "r");
+ }
+ argv++;
+ argc--;
+ }
+
+ if (f == NULL) {
+ DEBUG(0, ("Could not open userlist: %s\n", strerror(errno)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ r = get_user_tokens_from_file(f, &num_tokens, &tokens);
+
+ if (f != stdin)
+ fclose(f);
+
+ if (!r) {
+ DEBUG(0, ("Could not read users from file\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ for (i=0; i<num_tokens; i++)
+ collect_alias_memberships(tokens[i].token);
+
+ ZERO_STRUCT(info_ctr);
+ ZERO_STRUCT(ctr1);
+
+ info_ctr.level = 1;
+ info_ctr.ctr.ctr1 = &ctr1;
+
+ b = pipe_hnd->binding_handle;
+
+ if (argc != 0) {
+ /* Show results only for shares listed on the command line. */
+ while (*argv) {
+ const char *netname = *argv++;
+ d_printf("%s\n", netname);
+ show_userlist(pipe_hnd, cli, mem_ctx, netname,
+ num_tokens, tokens);
+ }
+ goto done;
+ }
+
+ /* Issue the NetShareEnum RPC call and retrieve the response */
+ nt_status = dcerpc_srvsvc_NetShareEnumAll(b,
+ talloc_tos(),
+ pipe_hnd->desthost,
+ &info_ctr,
+ preferred_len,
+ &total_entries,
+ &resume_handle,
+ &result);
+
+ /* Was it successful? */
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ /* Nope. Go clean up. */
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ /* Nope. Go clean up. */
+ nt_status = werror_to_ntstatus(result);
+ goto done;
+ }
+
+ if (total_entries == 0) {
+ goto done;
+ }
+
+ /* For each returned entry... */
+ for (i = 0; i < info_ctr.ctr.ctr1->count; i++) {
+ const char *netname = info_ctr.ctr.ctr1->array[i].name;
+
+ if (info_ctr.ctr.ctr1->array[i].type != STYPE_DISKTREE) {
+ continue;
+ }
+
+ d_printf("%s\n", netname);
+
+ show_userlist(pipe_hnd, cli, mem_ctx, netname,
+ num_tokens, tokens);
+ }
+ done:
+ TALLOC_FREE(tokens);
+ TALLOC_FREE(server_aliases);
+
+ return nt_status;
+}
+
+static int rpc_share_allowedusers(struct net_context *c, int argc,
+ const char **argv)
+{
+ int result;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc share allowedusers\n"
+ " %s\n",
+ _("Usage:"),
+ _("List allowed users"));
+ return 0;
+ }
+
+ result = run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_aliaslist_internals,
+ argc, argv);
+ if (result != 0)
+ return result;
+
+ result = run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_aliaslist_dump,
+ argc, argv);
+ if (result != 0)
+ return result;
+
+ return run_rpc_command(c, NULL, &ndr_table_srvsvc, 0,
+ rpc_share_allowedusers_internals,
+ argc, argv);
+}
+
+int net_usersidlist(struct net_context *c, int argc, const char **argv)
+{
+ int num_tokens = 0;
+ struct user_token *tokens = NULL;
+ int i;
+
+ if (argc != 0) {
+ net_usersidlist_usage(c, argc, argv);
+ return 0;
+ }
+
+ if (!get_user_tokens(c, &num_tokens, &tokens)) {
+ DEBUG(0, ("Could not get the user/sid list\n"));
+ return -1;
+ }
+
+ for (i=0; i<num_tokens; i++) {
+ dump_user_token(&tokens[i]);
+ }
+
+ TALLOC_FREE(tokens);
+ return 0;
+}
+
+int net_usersidlist_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net usersidlist\n"
+ "\tprints out a list of all users the running winbind knows\n"
+ "\tabout, together with all their SIDs. This is used as\n"
+ "\tinput to the 'net rpc share allowedusers' command.\n\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+/**
+ * 'net rpc share' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_rpc_share(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+
+ struct functable func[] = {
+ {
+ "add",
+ rpc_share_add,
+ NET_TRANSPORT_RPC,
+ N_("Add share"),
+ N_("net rpc share add\n"
+ " Add share")
+ },
+ {
+ "delete",
+ rpc_share_delete,
+ NET_TRANSPORT_RPC,
+ N_("Remove share"),
+ N_("net rpc share delete\n"
+ " Remove share")
+ },
+ {
+ "allowedusers",
+ rpc_share_allowedusers,
+ NET_TRANSPORT_RPC,
+ N_("List allowed users"),
+ N_("net rpc share allowedusers\n"
+ " List allowed users")
+ },
+ {
+ "migrate",
+ rpc_share_migrate,
+ NET_TRANSPORT_RPC,
+ N_("Migrate share to local server"),
+ N_("net rpc share migrate\n"
+ " Migrate share to local server")
+ },
+ {
+ "list",
+ rpc_share_list,
+ NET_TRANSPORT_RPC,
+ N_("List shares"),
+ N_("net rpc share list\n"
+ " List shares")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (status != 0) {
+ return -1;
+ }
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc share\n"
+ " List shares\n"
+ " Alias for net rpc share list\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ return rpc_share_list(c, argc, argv);
+ }
+
+ return net_run_function(c, argc, argv, "net rpc share", func);
+}
+
+static NTSTATUS rpc_sh_share_list(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+
+ return werror_to_ntstatus(W_ERROR(rpc_share_list(c, argc, argv)));
+}
+
+static NTSTATUS rpc_sh_share_add(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ uint32_t parm_err = 0;
+ struct SHARE_INFO_2 i2;
+
+ if ((argc < 2) || (argc > 3)) {
+ d_fprintf(stderr, _("Usage: %s <share> <path> [comment]\n"),
+ ctx->whoami);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ i2.shi2_netname = argv[0];
+ i2.shi2_type = STYPE_DISKTREE;
+ i2.shi2_remark = (argc == 3) ? argv[2] : "";
+ i2.shi2_permissions = 0;
+ i2.shi2_max_uses = 0;
+ i2.shi2_current_uses = 0;
+ i2.shi2_path = argv[1];
+ i2.shi2_passwd = NULL;
+
+ status = NetShareAdd(pipe_hnd->desthost,
+ 2,
+ (uint8_t *)&i2,
+ &parm_err);
+
+ return werror_to_ntstatus(W_ERROR(status));
+}
+
+static NTSTATUS rpc_sh_share_delete(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, "%s %s <share>\n", _("Usage:"), ctx->whoami);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return werror_to_ntstatus(W_ERROR(NetShareDel(pipe_hnd->desthost, argv[0], 0)));
+}
+
+static NTSTATUS rpc_sh_share_info(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ union srvsvc_NetShareInfo info;
+ WERROR result;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1) {
+ d_fprintf(stderr, "%s %s <share>\n", _("Usage:"), ctx->whoami);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_srvsvc_NetShareGetInfo(b, mem_ctx,
+ pipe_hnd->desthost,
+ argv[0],
+ 2,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ d_printf(_("Name: %s\n"), info.info2->name);
+ d_printf(_("Comment: %s\n"), info.info2->comment);
+ d_printf(_("Path: %s\n"), info.info2->path);
+ d_printf(_("Password: %s\n"), info.info2->password);
+
+ done:
+ return werror_to_ntstatus(result);
+}
+
+struct rpc_sh_cmd *net_rpc_share_cmds(struct net_context *c, TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx)
+{
+ static struct rpc_sh_cmd cmds[] = {
+
+ { "list", NULL, &ndr_table_srvsvc, rpc_sh_share_list,
+ N_("List available shares") },
+
+ { "add", NULL, &ndr_table_srvsvc, rpc_sh_share_add,
+ N_("Add a share") },
+
+ { "delete", NULL, &ndr_table_srvsvc, rpc_sh_share_delete,
+ N_("Delete a share") },
+
+ { "info", NULL, &ndr_table_srvsvc, rpc_sh_share_info,
+ N_("Get information about a share") },
+
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ return cmds;
+}
+
+/****************************************************************************/
+
+static int rpc_file_usage(struct net_context *c, int argc, const char **argv)
+{
+ return net_file_usage(c, argc, argv);
+}
+
+/**
+ * Close a file on a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_file_close(struct net_context *c, int argc, const char **argv)
+{
+ if (argc < 1 || c->display_usage) {
+ return rpc_file_usage(c, argc, argv);
+ }
+
+ return NetFileClose(c->opt_host, atoi(argv[0]));
+}
+
+/**
+ * Formatted print of open file info
+ *
+ * @param r struct FILE_INFO_3 contents
+ **/
+
+static void display_file_info_3(struct FILE_INFO_3 *r)
+{
+ d_printf("%-7.1" PRIu32 " %-20.20s 0x%-4.2x %-6.1u %s\n",
+ r->fi3_id, r->fi3_username, r->fi3_permissions,
+ r->fi3_num_locks, r->fi3_pathname);
+}
+
+/**
+ * List files for a user on a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success)..
+ **/
+
+static int rpc_file_user(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+ uint32_t preferred_len = 0xffffffff, i;
+ char *username=NULL;
+ uint32_t total_entries = 0;
+ uint32_t entries_read = 0;
+ uint32_t resume_handle = 0;
+ struct FILE_INFO_3 *i3 = NULL;
+
+ if (c->display_usage) {
+ return rpc_file_usage(c, argc, argv);
+ }
+
+ /* if argc > 0, must be user command */
+ if (argc > 0) {
+ username = smb_xstrdup(argv[0]);
+ }
+
+ status = NetFileEnum(c->opt_host,
+ NULL,
+ username,
+ 3,
+ (uint8_t **)(void *)&i3,
+ preferred_len,
+ &entries_read,
+ &total_entries,
+ &resume_handle);
+
+ if (status != 0) {
+ goto done;
+ }
+
+ /* Display results */
+
+ d_printf(_(
+ "\nEnumerating open files on remote server:\n\n"
+ "\nFileId Opened by Perms Locks Path"
+ "\n------ --------- ----- ----- ---- \n"));
+ for (i = 0; i < entries_read; i++) {
+ display_file_info_3(&i3[i]);
+ }
+ done:
+ SAFE_FREE(username);
+ return status;
+}
+
+/**
+ * 'net rpc file' entrypoint.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_rpc_file(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+
+ struct functable func[] = {
+ {
+ "close",
+ rpc_file_close,
+ NET_TRANSPORT_RPC,
+ N_("Close opened file"),
+ N_("net rpc file close\n"
+ " Close opened file")
+ },
+ {
+ "user",
+ rpc_file_user,
+ NET_TRANSPORT_RPC,
+ N_("List files opened by user"),
+ N_("net rpc file user\n"
+ " List files opened by user")
+ },
+#if 0
+ {
+ "info",
+ rpc_file_info,
+ NET_TRANSPORT_RPC,
+ N_("Display information about opened file"),
+ N_("net rpc file info\n"
+ " Display information about opened file")
+ },
+#endif
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (status != 0) {
+ return -1;
+ }
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rpc file\n"
+ " List opened files\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ return rpc_file_user(c, argc, argv);
+ }
+
+ return net_run_function(c, argc, argv, "net rpc file", func);
+}
+
+/**
+ * ABORT the shutdown of a remote RPC Server, over initshutdown pipe.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure.
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_shutdown_abort_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR result;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_initshutdown_Abort(b, mem_ctx, NULL, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (W_ERROR_IS_OK(result)) {
+ d_printf(_("\nShutdown successfully aborted\n"));
+ DEBUG(5,("cmd_shutdown_abort: query succeeded\n"));
+ } else
+ DEBUG(5,("cmd_shutdown_abort: query failed\n"));
+
+ return werror_to_ntstatus(result);
+}
+
+/**
+ * ABORT the shutdown of a remote RPC Server, over winreg pipe.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure.
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_reg_shutdown_abort_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ result = dcerpc_winreg_AbortSystemShutdown(b, mem_ctx, NULL, &werr);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(5,("cmd_reg_abort_shutdown: query failed\n"));
+ return result;
+ }
+ if (W_ERROR_IS_OK(werr)) {
+ d_printf(_("\nShutdown successfully aborted\n"));
+ DEBUG(5,("cmd_reg_abort_shutdown: query succeeded\n"));
+ } else
+ DEBUG(5,("cmd_reg_abort_shutdown: query failed\n"));
+
+ return werror_to_ntstatus(werr);
+}
+
+/**
+ * ABORT the shutdown of a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_shutdown_abort(struct net_context *c, int argc,
+ const char **argv)
+{
+ int rc = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc abortshutdown\n"
+ " %s\n",
+ _("Usage:"),
+ _("Abort a scheduled shutdown"));
+ return 0;
+ }
+
+ rc = run_rpc_command(c, NULL, &ndr_table_initshutdown, 0,
+ rpc_shutdown_abort_internals, argc, argv);
+
+ if (rc == 0)
+ return rc;
+
+ DEBUG(1, ("initshutdown pipe didn't work, trying winreg pipe\n"));
+
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_reg_shutdown_abort_internals,
+ argc, argv);
+}
+
+/**
+ * Shut down a remote RPC Server via initshutdown pipe.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure.
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_init_shutdown_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ WERROR result;
+ const char *msg = N_("This machine will be shutdown shortly");
+ uint32_t timeout = 20;
+ struct lsa_StringLarge msg_string;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (c->opt_comment) {
+ msg = c->opt_comment;
+ }
+ if (c->opt_timeout) {
+ timeout = c->opt_timeout;
+ }
+
+ msg_string.string = msg;
+
+ /* create an entry */
+ status = dcerpc_initshutdown_Init(b, mem_ctx, NULL,
+ &msg_string, timeout, c->opt_force, c->opt_reboot,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (W_ERROR_IS_OK(result)) {
+ d_printf(_("\nShutdown of remote machine succeeded\n"));
+ DEBUG(5,("Shutdown of remote machine succeeded\n"));
+ } else {
+ DEBUG(1,("Shutdown of remote machine failed!\n"));
+ }
+ return werror_to_ntstatus(result);
+}
+
+/**
+ * Shut down a remote RPC Server via winreg pipe.
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure.
+ * @param domain_sid The domain sid acquired from the remote server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_reg_shutdown_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ const char *msg = N_("This machine will be shutdown shortly");
+ uint32_t timeout = 20;
+ struct lsa_StringLarge msg_string;
+ NTSTATUS result;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (c->opt_comment) {
+ msg = c->opt_comment;
+ }
+ msg_string.string = msg;
+
+ if (c->opt_timeout) {
+ timeout = c->opt_timeout;
+ }
+
+ /* create an entry */
+ result = dcerpc_winreg_InitiateSystemShutdown(b, mem_ctx, NULL,
+ &msg_string, timeout, c->opt_force, c->opt_reboot,
+ &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ d_fprintf(stderr, "\nShutdown of remote machine failed\n");
+ return result;
+ }
+
+ if (W_ERROR_IS_OK(werr)) {
+ d_printf(_("\nShutdown of remote machine succeeded\n"));
+ } else {
+ d_fprintf(stderr, "\nShutdown of remote machine failed\n");
+ if ( W_ERROR_EQUAL(werr, WERR_MACHINE_LOCKED) )
+ d_fprintf(stderr, "\nMachine locked, use -f switch to force\n");
+ else
+ d_fprintf(stderr, "\nresult was: %s\n", win_errstr(werr));
+ }
+
+ return werror_to_ntstatus(werr);
+}
+
+/**
+ * Shut down a remote RPC server.
+ *
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+
+static int rpc_shutdown(struct net_context *c, int argc, const char **argv)
+{
+ int rc = -1;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc shutdown\n"
+ " %s\n",
+ _("Usage:"),
+ _("Shut down a remote RPC server"));
+ return 0;
+ }
+
+ rc = run_rpc_command(c, NULL, &ndr_table_initshutdown, 0,
+ rpc_init_shutdown_internals, argc, argv);
+
+ if (rc) {
+ DEBUG(1, ("initshutdown pipe failed, trying winreg pipe\n"));
+ rc = run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_reg_shutdown_internals, argc, argv);
+ }
+
+ return rc;
+}
+
+/***************************************************************************
+ NT Domain trusts code (i.e. 'net rpc trustdom' functionality)
+ ***************************************************************************/
+
+/**
+ * Add interdomain trust account to the RPC server.
+ * All parameters (except for argc and argv) are passed by run_rpc_command
+ * function.
+ *
+ * @param c A net_context structure.
+ * @param domain_sid The domain sid acquired from the server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return normal NTSTATUS return code.
+ */
+
+static NTSTATUS rpc_trustdom_add_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ NTSTATUS status, result;
+ char *acct_name;
+ struct lsa_String lsa_acct_name;
+ uint32_t acb_info;
+ uint32_t acct_flags=0;
+ uint32_t user_rid;
+ uint32_t access_granted = 0;
+ union samr_UserInfo info;
+ unsigned int orig_timeout;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ DATA_BLOB session_key = data_blob_null;
+ TALLOC_CTX *frame = NULL;
+
+ if (argc != 2) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net rpc trustdom add <domain_name> "
+ "<trust password>\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ frame = talloc_stackframe();
+
+ /*
+ * Make valid trusting domain account (ie. uppercased and with '$' appended)
+ */
+
+ if (asprintf(&acct_name, "%s$", argv[0]) < 0) {
+ status = NT_STATUS_NO_MEMORY;
+ }
+
+ if (!strupper_m(acct_name)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, acct_name);
+
+ status = cli_get_session_key(frame, pipe_hnd, &session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error getting session_key of SAM pipe. Error was %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /* Get samr policy handle */
+ status = dcerpc_samr_Connect2(b, frame,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get domain policy handle */
+ status = dcerpc_samr_OpenDomain(b, frame,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, domain_sid),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* This call can take a long time - allow the server to time out.
+ * 35 seconds should do it. */
+
+ orig_timeout = rpccli_set_timeout(pipe_hnd, 35000);
+
+ /* Create trusting domain's account */
+ acb_info = ACB_NORMAL;
+ acct_flags = SEC_GENERIC_READ | SEC_GENERIC_WRITE | SEC_GENERIC_EXECUTE |
+ SEC_STD_WRITE_DAC | SEC_STD_DELETE |
+ SAMR_USER_ACCESS_SET_PASSWORD |
+ SAMR_USER_ACCESS_GET_ATTRIBUTES |
+ SAMR_USER_ACCESS_SET_ATTRIBUTES;
+
+ status = dcerpc_samr_CreateUser2(b, frame,
+ &domain_pol,
+ &lsa_acct_name,
+ acb_info,
+ acct_flags,
+ &user_pol,
+ &access_granted,
+ &user_rid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ /* And restore our original timeout. */
+ rpccli_set_timeout(pipe_hnd, orig_timeout);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_printf(_("net rpc trustdom add: create user %s failed %s\n"),
+ acct_name, nt_errstr(result));
+ goto done;
+ }
+
+ {
+ struct samr_CryptPassword crypt_pwd;
+
+ ZERO_STRUCT(info.info23);
+
+ status = init_samr_CryptPassword(argv[1],
+ &session_key,
+ &crypt_pwd);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ info.info23.info.fields_present = SAMR_FIELD_ACCT_FLAGS |
+ SAMR_FIELD_NT_PASSWORD_PRESENT;
+ info.info23.info.acct_flags = ACB_DOMTRUST;
+ info.info23.password = crypt_pwd;
+
+ status = dcerpc_samr_SetUserInfo2(b, frame,
+ &user_pol,
+ 23,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ DEBUG(0,("Could not set trust account password: %s\n",
+ nt_errstr(result)));
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+ done:
+ SAFE_FREE(acct_name);
+ data_blob_clear_free(&session_key);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/**
+ * Create interdomain trust account for a remote domain.
+ *
+ * @param argc Standard argc.
+ * @param argv Standard argv without initial components.
+ *
+ * @return Integer status (0 means success).
+ **/
+
+static int rpc_trustdom_add(struct net_context *c, int argc, const char **argv)
+{
+ if (argc > 0 && !c->display_usage) {
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_trustdom_add_internals, argc, argv);
+ } else {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc trustdom add <domain_name> <trust "
+ "password>\n"));
+ return -1;
+ }
+}
+
+
+/**
+ * Remove interdomain trust account from the RPC server.
+ * All parameters (except for argc and argv) are passed by run_rpc_command
+ * function.
+ *
+ * @param c A net_context structure.
+ * @param domain_sid The domain sid acquired from the server.
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return normal NTSTATUS return code.
+ */
+
+static NTSTATUS rpc_trustdom_del_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle connect_pol, domain_pol, user_pol;
+ NTSTATUS status, result;
+ char *acct_name;
+ struct dom_sid trust_acct_sid;
+ struct samr_Ids user_rids, name_types;
+ struct lsa_String lsa_acct_name;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net rpc trustdom del <domain_name>\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Make valid trusting domain account (ie. uppercased and with '$' appended)
+ */
+ acct_name = talloc_asprintf(mem_ctx, "%s$", argv[0]);
+
+ if (acct_name == NULL)
+ return NT_STATUS_NO_MEMORY;
+
+ if (!strupper_m(acct_name)) {
+ TALLOC_FREE(acct_name);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Get samr policy handle */
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get domain policy handle */
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ discard_const_p(struct dom_sid2, domain_sid),
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ init_lsa_String(&lsa_acct_name, acct_name);
+
+ status = dcerpc_samr_LookupNames(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &lsa_acct_name,
+ &user_rids,
+ &name_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("net rpc trustdom del: LookupNames on user %s "
+ "failed %s\n"),
+ acct_name, nt_errstr(status));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_printf(_("net rpc trustdom del: LookupNames on user %s "
+ "failed %s\n"),
+ acct_name, nt_errstr(result) );
+ goto done;
+ }
+ if (user_rids.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+ if (name_types.count != 1) {
+ status = NT_STATUS_INVALID_NETWORK_RESPONSE;
+ goto done;
+ }
+
+ status = dcerpc_samr_OpenUser(b, mem_ctx,
+ &domain_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ user_rids.ids[0],
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("net rpc trustdom del: OpenUser on user %s failed "
+ "%s\n"),
+ acct_name, nt_errstr(status) );
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_printf(_("net rpc trustdom del: OpenUser on user %s failed "
+ "%s\n"),
+ acct_name, nt_errstr(result) );
+ goto done;
+ }
+
+ /* append the rid to the domain sid */
+ if (!sid_compose(&trust_acct_sid, domain_sid, user_rids.ids[0])) {
+ goto done;
+ }
+
+ /* remove the sid */
+
+ status = dcerpc_samr_RemoveMemberFromForeignDomain(b, mem_ctx,
+ &user_pol,
+ &trust_acct_sid,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("net rpc trustdom del: RemoveMemberFromForeignDomain"
+ " on user %s failed %s\n"),
+ acct_name, nt_errstr(status));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_printf(_("net rpc trustdom del: RemoveMemberFromForeignDomain"
+ " on user %s failed %s\n"),
+ acct_name, nt_errstr(result) );
+ goto done;
+ }
+
+
+ /* Delete user */
+
+ status = dcerpc_samr_DeleteUser(b, mem_ctx,
+ &user_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("net rpc trustdom del: DeleteUser on user %s failed "
+ "%s\n"),
+ acct_name, nt_errstr(status));
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ result = status;
+ d_printf(_("net rpc trustdom del: DeleteUser on user %s failed "
+ "%s\n"),
+ acct_name, nt_errstr(result) );
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ d_printf(_("Could not set trust account password: %s\n"),
+ nt_errstr(result));
+ goto done;
+ }
+
+ done:
+ return status;
+}
+
+/**
+ * Delete interdomain trust account for a remote domain.
+ *
+ * @param argc Standard argc.
+ * @param argv Standard argv without initial components.
+ *
+ * @return Integer status (0 means success).
+ **/
+
+static int rpc_trustdom_del(struct net_context *c, int argc, const char **argv)
+{
+ if (argc > 0 && !c->display_usage) {
+ return run_rpc_command(c, NULL, &ndr_table_samr, 0,
+ rpc_trustdom_del_internals, argc, argv);
+ } else {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc trustdom del <domain>\n"));
+ return -1;
+ }
+}
+
+static NTSTATUS rpc_trustdom_get_pdc(struct net_context *c,
+ struct cli_state *cli,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name)
+{
+ char *dc_name = NULL;
+ const char *buffer = NULL;
+ struct rpc_pipe_client *netr;
+ NTSTATUS status;
+ WERROR result;
+ struct dcerpc_binding_handle *b;
+
+ /* Use NetServerEnum2 */
+
+ if (cli_get_pdc_name(cli, domain_name, &dc_name)) {
+ SAFE_FREE(dc_name);
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(1,("NetServerEnum2 error: Couldn't find primary domain controller "
+ "for domain %s\n", domain_name));
+
+ /* Try netr_GetDcName */
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_netlogon,
+ &netr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ b = netr->binding_handle;
+
+ status = dcerpc_netr_GetDcName(b, mem_ctx,
+ netr->desthost,
+ domain_name,
+ &buffer,
+ &result);
+ TALLOC_FREE(netr);
+
+ if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(result)) {
+ return status;
+ }
+
+ DEBUG(1,("netr_GetDcName error: Couldn't find primary domain controller "
+ "for domain %s\n", domain_name));
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/**
+ * Establish trust relationship to a trusting domain.
+ * Interdomain account must already be created on remote PDC.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard argc.
+ * @param argv Standard argv without initial components.
+ *
+ * @return Integer status (0 means success).
+ **/
+
+static int rpc_trustdom_establish(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct cli_state *cli = NULL;
+ struct sockaddr_storage server_ss;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct policy_handle connect_hnd;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS nt_status, result;
+ struct dom_sid *domain_sid;
+ char* domain_name;
+ char* acct_name;
+ const char *pwd = NULL;
+ fstring pdc_name;
+ union lsa_PolicyInformation *info = NULL;
+ struct dcerpc_binding_handle *b;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ /*
+ * Connect to \\server\ipc$ as 'our domain' account with password
+ */
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc trustdom establish <domain_name>\n"));
+ return -1;
+ }
+
+ domain_name = smb_xstrdup(argv[0]);
+ if (!strupper_m(domain_name)) {
+ SAFE_FREE(domain_name);
+ return -1;
+ }
+
+ /* account name used at first is our domain's name with '$' */
+ if (asprintf(&acct_name, "%s$", lp_workgroup()) == -1) {
+ return -1;
+ }
+ if (!strupper_m(acct_name)) {
+ SAFE_FREE(domain_name);
+ SAFE_FREE(acct_name);
+ return -1;
+ }
+ cli_credentials_set_username(c->creds, acct_name, CRED_SPECIFIED);
+
+ /*
+ * opt_workgroup will be used by connection functions further,
+ * hence it should be set to remote domain name instead of ours
+ */
+ if (c->opt_workgroup) {
+ c->opt_workgroup = smb_xstrdup(domain_name);
+ };
+
+ /* find the domain controller */
+ if (!net_find_pdc(&server_ss, pdc_name, domain_name)) {
+ DEBUG(0, ("Couldn't find domain controller for domain %s\n", domain_name));
+ return -1;
+ }
+
+ /* connect to ipc$ as username/password */
+ nt_status = connect_to_ipc(c, &cli, &server_ss, pdc_name);
+ if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT)) {
+
+ /* Is it trusting domain account for sure ? */
+ DEBUG(0, ("Couldn't verify trusting domain account. Error was %s\n",
+ nt_errstr(nt_status)));
+ return -1;
+ }
+
+ /* store who we connected to */
+
+ saf_store( domain_name, pdc_name );
+
+ /*
+ * Connect to \\server\ipc$ again (this time anonymously)
+ */
+
+ nt_status = connect_to_ipc_anonymous(c, &cli, &server_ss,
+ (char*)pdc_name);
+
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("Couldn't connect to domain %s controller. Error was %s.\n",
+ domain_name, nt_errstr(nt_status)));
+ return -1;
+ }
+
+ if (!(mem_ctx = talloc_init("establishing trust relationship to "
+ "domain %s", domain_name))) {
+ DEBUG(0, ("talloc_init() failed\n"));
+ cli_shutdown(cli);
+ return -1;
+ }
+
+ /* Make sure we're talking to a proper server */
+
+ nt_status = rpc_trustdom_get_pdc(c, cli, mem_ctx, domain_name);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ /*
+ * Call LsaOpenPolicy and LsaQueryInfo
+ */
+
+ nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Could not initialise lsa pipe. Error was %s\n", nt_errstr(nt_status) ));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ nt_status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ pipe_hnd->srv_name_slash,
+ true,
+ KEY_QUERY_VALUE,
+ &out_version,
+ &out_revision_info,
+ &connect_hnd,
+ &result);
+ if (any_nt_status_not_ok(nt_status, result, &nt_status)) {
+ DBG_ERR("Couldn't open policy handle: %s\n",
+ nt_errstr(nt_status));
+ cli_shutdown(cli);
+ talloc_free(mem_ctx);
+ return -1;
+ }
+
+ /* Querying info level 5 */
+
+ nt_status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &connect_hnd,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &info,
+ &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("LSA Query Info failed. Returned error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+ if (NT_STATUS_IS_ERR(result)) {
+ DEBUG(0, ("LSA Query Info failed. Returned error was %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ domain_sid = info->account_domain.sid;
+
+ /* There should be actually query info level 3 (following nt serv behaviour),
+ but I still don't know if it's _really_ necessary */
+
+ /*
+ * Store the password in secrets db
+ */
+
+ pwd = cli_credentials_get_password(c->creds);
+
+ if (!pdb_set_trusteddom_pw(domain_name, pwd, domain_sid)) {
+ DEBUG(0, ("Storing password for trusted domain failed.\n"));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ /*
+ * Close the pipes and clean up
+ */
+
+ nt_status = dcerpc_lsa_Close(b, mem_ctx, &connect_hnd, &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("Couldn't close LSA pipe. Error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ cli_shutdown(cli);
+
+ talloc_destroy(mem_ctx);
+
+ d_printf(_("Trust to domain %s established\n"), domain_name);
+ return 0;
+}
+
+/**
+ * Revoke trust relationship to the remote domain.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard argc.
+ * @param argv Standard argv without initial components.
+ *
+ * @return Integer status (0 means success).
+ **/
+
+static int rpc_trustdom_revoke(struct net_context *c, int argc,
+ const char **argv)
+{
+ char* domain_name;
+ int rc = -1;
+
+ if (argc < 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc trustdom revoke <domain_name>\n"
+ " Revoke trust relationship\n"
+ " domain_name\tName of domain to revoke trust\n"));
+ return -1;
+ }
+
+ /* generate upper cased domain name */
+ domain_name = smb_xstrdup(argv[0]);
+ if (!strupper_m(domain_name)) {
+ SAFE_FREE(domain_name);
+ return -1;
+ }
+
+ /* delete password of the trust */
+ if (!pdb_del_trusteddom_pw(domain_name)) {
+ DEBUG(0, ("Failed to revoke relationship to the trusted domain %s\n",
+ domain_name));
+ goto done;
+ };
+
+ rc = 0;
+done:
+ SAFE_FREE(domain_name);
+ return rc;
+}
+
+static NTSTATUS rpc_query_domain_sid(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dom_sid_buf sid_str;
+ d_printf("%s\n", dom_sid_str_buf(domain_sid, &sid_str));
+ return NT_STATUS_OK;
+}
+
+static void print_trusted_domain(struct dom_sid *dom_sid, const char *trusted_dom_name)
+{
+ struct dom_sid_buf sid_str;
+
+ d_printf("%-20s%s\n",
+ trusted_dom_name,
+ dom_sid_str_buf(dom_sid, &sid_str));
+}
+
+static NTSTATUS vampire_trusted_domain(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *pol,
+ struct dom_sid dom_sid,
+ const char *trusted_dom_name)
+{
+ NTSTATUS nt_status, result;
+ union lsa_TrustedDomainInfo *info = NULL;
+ char *cleartextpwd = NULL;
+ DATA_BLOB session_key;
+ DATA_BLOB data = data_blob_null;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ nt_status = dcerpc_lsa_QueryTrustedDomainInfoBySid(b, mem_ctx,
+ pol,
+ &dom_sid,
+ LSA_TRUSTED_DOMAIN_INFO_PASSWORD,
+ &info,
+ &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0,("Could not query trusted domain info. Error was %s\n",
+ nt_errstr(nt_status)));
+ goto done;
+ }
+ if (NT_STATUS_IS_ERR(result)) {
+ nt_status = result;
+ DEBUG(0,("Could not query trusted domain info. Error was %s\n",
+ nt_errstr(result)));
+ goto done;
+ }
+
+ data = data_blob(info->password.password->data,
+ info->password.password->length);
+
+ nt_status = cli_get_session_key(mem_ctx, pipe_hnd, &session_key);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Could not retrieve session key: %s\n", nt_errstr(nt_status)));
+ goto done;
+ }
+
+ cleartextpwd = sess_decrypt_string(mem_ctx, &data, &session_key);
+ data_blob_free(&session_key);
+
+ if (cleartextpwd == NULL) {
+ DEBUG(0,("retrieved NULL password\n"));
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (!pdb_set_trusteddom_pw(trusted_dom_name, cleartextpwd, &dom_sid)) {
+ DEBUG(0, ("Storing password for trusted domain failed.\n"));
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+#ifdef DEBUG_PASSWORD
+ {
+ struct dom_sid_buf buf;
+ DEBUG(100,("successfully vampired trusted domain [%s], "
+ "sid: [%s], password: [%s]\n",
+ trusted_dom_name,
+ dom_sid_str_buf(&dom_sid, &buf),
+ cleartextpwd));
+ }
+#endif
+
+done:
+ SAFE_FREE(cleartextpwd);
+ data_blob_free(&data);
+
+ return nt_status;
+}
+
+static int rpc_trustdom_vampire(struct net_context *c, int argc,
+ const char **argv)
+{
+ /* common variables */
+ TALLOC_CTX* mem_ctx;
+ struct cli_state *cli = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ NTSTATUS nt_status, result;
+ const char *domain_name = NULL;
+ struct policy_handle connect_hnd;
+ union lsa_PolicyInformation *info = NULL;
+
+ /* trusted domains listing variables */
+ unsigned int enum_ctx = 0;
+ struct lsa_DomainList dom_list;
+ fstring pdc_name;
+ struct dcerpc_binding_handle *b;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc trustdom vampire\n"
+ " %s\n",
+ _("Usage:"),
+ _("Vampire trust relationship from remote server"));
+ return 0;
+ }
+
+ /*
+ * Listing trusted domains (stored in secrets.tdb, if local)
+ */
+
+ mem_ctx = talloc_init("trust relationships vampire");
+
+ /*
+ * set domain and pdc name to local samba server (default)
+ * or to remote one given in command line
+ */
+
+ if (strcasecmp_m(c->opt_workgroup, lp_workgroup())) {
+ domain_name = c->opt_workgroup;
+ c->opt_target_workgroup = c->opt_workgroup;
+ } else {
+ fstrcpy(pdc_name, lp_netbios_name());
+ domain_name = talloc_strdup(mem_ctx, lp_workgroup());
+ c->opt_target_workgroup = domain_name;
+ };
+
+ /* open \PIPE\lsarpc and open policy handle */
+ nt_status = net_make_ipc_connection(c, NET_FLAGS_PDC, &cli);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Couldn't connect to domain controller: %s\n",
+ nt_errstr(nt_status)));
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Could not initialise lsa pipe. Error was %s\n",
+ nt_errstr(nt_status) ));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ b = pipe_hnd->binding_handle;
+
+ nt_status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ pipe_hnd->srv_name_slash,
+ false,
+ KEY_QUERY_VALUE,
+ &out_version,
+ &out_revision_info,
+ &connect_hnd,
+ &result);
+ if (any_nt_status_not_ok(nt_status, result, &nt_status)) {
+ DBG_ERR("Couldn't open policy handle: %s\n",
+ nt_errstr(nt_status));
+ cli_shutdown(cli);
+ talloc_free(mem_ctx);
+ return -1;
+ }
+
+ /* query info level 5 to obtain sid of a domain being queried */
+ nt_status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &connect_hnd,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &info,
+ &result);
+
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("LSA Query Info failed. Returned error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+ if (NT_STATUS_IS_ERR(result)) {
+ DEBUG(0, ("LSA Query Info failed. Returned error was %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ /*
+ * Keep calling LsaEnumTrustdom over opened pipe until
+ * the end of enumeration is reached
+ */
+
+ d_printf(_("Vampire trusted domains:\n\n"));
+
+ do {
+ uint32_t i;
+
+ nt_status = dcerpc_lsa_EnumTrustDom(b, mem_ctx,
+ &connect_hnd,
+ &enum_ctx,
+ &dom_list,
+ (uint32_t)-1,
+ &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("Couldn't enumerate trusted domains. Error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+ if (NT_STATUS_IS_ERR(result)) {
+ nt_status = result;
+ DEBUG(0, ("Couldn't enumerate trusted domains. Error was %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+
+ for (i = 0; i < dom_list.count; i++) {
+
+ print_trusted_domain(dom_list.domains[i].sid,
+ dom_list.domains[i].name.string);
+
+ nt_status = vampire_trusted_domain(pipe_hnd, mem_ctx, &connect_hnd,
+ *dom_list.domains[i].sid,
+ dom_list.domains[i].name.string);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+ };
+
+ /*
+ * in case of no trusted domains say something rather
+ * than just display blank line
+ */
+ if (!dom_list.count) d_printf(_("none\n"));
+
+ } while (NT_STATUS_EQUAL(nt_status, STATUS_MORE_ENTRIES));
+
+ /* close this connection before doing next one */
+ nt_status = dcerpc_lsa_Close(b, mem_ctx, &connect_hnd, &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("Couldn't properly close lsa policy handle. Error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ /* close lsarpc pipe and connection to IPC$ */
+ cli_shutdown(cli);
+
+ talloc_destroy(mem_ctx);
+ return 0;
+}
+
+static int rpc_trustdom_list(struct net_context *c, int argc, const char **argv)
+{
+ /* common variables */
+ TALLOC_CTX* mem_ctx;
+ struct cli_state *cli = NULL, *remote_cli = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ NTSTATUS nt_status, result;
+ const char *domain_name = NULL;
+ struct dom_sid *queried_dom_sid;
+ int ascii_dom_name_len;
+ struct policy_handle connect_hnd;
+ union lsa_PolicyInformation *info = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+
+ /* trusted domains listing variables */
+ unsigned int num_domains, enum_ctx = 0;
+ uint32_t i;
+ struct lsa_DomainList dom_list;
+ fstring pdc_name;
+ bool found_domain;
+
+ /* trusting domains listing variables */
+ struct policy_handle domain_hnd;
+ struct samr_SamArray *trusts = NULL;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc trustdom list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List incoming and outgoing trust relationships"));
+ return 0;
+ }
+
+ /*
+ * Listing trusted domains (stored in secrets.tdb, if local)
+ */
+
+ mem_ctx = talloc_init("trust relationships listing");
+
+ /*
+ * set domain and pdc name to local samba server (default)
+ * or to remote one given in command line
+ */
+
+ if (strcasecmp_m(c->opt_workgroup, lp_workgroup())) {
+ domain_name = c->opt_workgroup;
+ c->opt_target_workgroup = c->opt_workgroup;
+ } else {
+ fstrcpy(pdc_name, lp_netbios_name());
+ domain_name = talloc_strdup(mem_ctx, lp_workgroup());
+ c->opt_target_workgroup = domain_name;
+ };
+
+ /* open \PIPE\lsarpc and open policy handle */
+ nt_status = net_make_ipc_connection(c, NET_FLAGS_PDC, &cli);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Couldn't connect to domain controller: %s\n",
+ nt_errstr(nt_status)));
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Could not initialise lsa pipe. Error was %s\n",
+ nt_errstr(nt_status) ));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ b = pipe_hnd->binding_handle;
+
+ nt_status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ pipe_hnd->srv_name_slash,
+ true,
+ KEY_QUERY_VALUE,
+ &out_version,
+ &out_revision_info,
+ &connect_hnd,
+ &result);
+ if (any_nt_status_not_ok(nt_status, result, &nt_status)) {
+ DBG_ERR("Couldn't open policy handle: %s\n",
+ nt_errstr(nt_status));
+ cli_shutdown(cli);
+ talloc_free(mem_ctx);
+ return -1;
+ }
+
+ /* query info level 5 to obtain sid of a domain being queried */
+ nt_status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &connect_hnd,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &info,
+ &result);
+
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("LSA Query Info failed. Returned error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+ if (NT_STATUS_IS_ERR(result)) {
+ DEBUG(0, ("LSA Query Info failed. Returned error was %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+
+ queried_dom_sid = info->account_domain.sid;
+
+ /*
+ * Keep calling LsaEnumTrustdom over opened pipe until
+ * the end of enumeration is reached
+ */
+
+ d_printf(_("Trusted domains list:\n\n"));
+
+ found_domain = false;
+
+ do {
+ nt_status = dcerpc_lsa_EnumTrustDom(b, mem_ctx,
+ &connect_hnd,
+ &enum_ctx,
+ &dom_list,
+ (uint32_t)-1,
+ &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("Couldn't enumerate trusted domains. Error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+ if (NT_STATUS_IS_ERR(result)) {
+ DEBUG(0, ("Couldn't enumerate trusted domains. Error was %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+
+ for (i = 0; i < dom_list.count; i++) {
+ print_trusted_domain(dom_list.domains[i].sid,
+ dom_list.domains[i].name.string);
+ found_domain = true;
+ };
+
+
+ } while (NT_STATUS_EQUAL(nt_status, STATUS_MORE_ENTRIES));
+
+ /*
+ * in case of no trusted domains say something rather
+ * than just display blank line
+ */
+ if (!found_domain) {
+ d_printf(_("none\n"));
+ }
+
+ /* close this connection before doing next one */
+ nt_status = dcerpc_lsa_Close(b, mem_ctx, &connect_hnd, &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("Couldn't properly close lsa policy handle. Error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ TALLOC_FREE(pipe_hnd);
+
+ /*
+ * Listing trusting domains (stored in passdb backend, if local)
+ */
+
+ d_printf(_("\nTrusting domains list:\n\n"));
+
+ /*
+ * Open \PIPE\samr and get needed policy handles
+ */
+ nt_status = cli_rpc_pipe_open_noauth(cli, &ndr_table_samr,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Could not initialise samr pipe. Error was %s\n", nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ b = pipe_hnd->binding_handle;
+
+ /* SamrConnect2 */
+ nt_status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ SAMR_ACCESS_LOOKUP_DOMAIN,
+ &connect_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Couldn't open SAMR policy handle. Error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+ if (!NT_STATUS_IS_OK(result)) {
+ nt_status = result;
+ DEBUG(0, ("Couldn't open SAMR policy handle. Error was %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ /* SamrOpenDomain - we have to open domain policy handle in order to be
+ able to enumerate accounts*/
+ nt_status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_hnd,
+ SAMR_DOMAIN_ACCESS_ENUM_ACCOUNTS,
+ queried_dom_sid,
+ &domain_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Couldn't open domain object. Error was %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+ if (!NT_STATUS_IS_OK(result)) {
+ nt_status = result;
+ DEBUG(0, ("Couldn't open domain object. Error was %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ /*
+ * perform actual enumeration
+ */
+
+ found_domain = false;
+
+ enum_ctx = 0; /* reset enumeration context from last enumeration */
+ do {
+
+ nt_status = dcerpc_samr_EnumDomainUsers(b, mem_ctx,
+ &domain_hnd,
+ &enum_ctx,
+ ACB_DOMTRUST,
+ &trusts,
+ 0xffff,
+ &num_domains,
+ &result);
+ if (NT_STATUS_IS_ERR(nt_status)) {
+ DEBUG(0, ("Couldn't enumerate accounts. Error was: %s\n",
+ nt_errstr(nt_status)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+ if (NT_STATUS_IS_ERR(result)) {
+ nt_status = result;
+ DEBUG(0, ("Couldn't enumerate accounts. Error was: %s\n",
+ nt_errstr(result)));
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ };
+
+ for (i = 0; i < num_domains; i++) {
+
+ char *str = discard_const_p(char, trusts->entries[i].name.string);
+
+ found_domain = true;
+
+ /*
+ * get each single domain's sid (do we _really_ need this ?):
+ * 1) connect to domain's pdc
+ * 2) query the pdc for domain's sid
+ */
+
+ /* get rid of '$' tail */
+ ascii_dom_name_len = strlen(str);
+ if (ascii_dom_name_len && ascii_dom_name_len < FSTRING_LEN)
+ str[ascii_dom_name_len - 1] = '\0';
+
+ /* set opt_* variables to remote domain */
+ if (!strupper_m(str)) {
+ cli_shutdown(cli);
+ talloc_destroy(mem_ctx);
+ return -1;
+ }
+ c->opt_workgroup = talloc_strdup(mem_ctx, str);
+ c->opt_target_workgroup = c->opt_workgroup;
+
+ d_printf("%-20s", str);
+
+ /* connect to remote domain controller */
+ nt_status = net_make_ipc_connection(c,
+ NET_FLAGS_PDC | NET_FLAGS_ANONYMOUS,
+ &remote_cli);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ /* query for domain's sid */
+ if (run_rpc_command(
+ c, remote_cli,
+ &ndr_table_lsarpc, 0,
+ rpc_query_domain_sid, argc,
+ argv))
+ d_printf(_("strange - couldn't get domain's sid\n"));
+
+ cli_shutdown(remote_cli);
+
+ } else {
+ d_fprintf(stderr, _("domain controller is not "
+ "responding: %s\n"),
+ nt_errstr(nt_status));
+ d_printf(_("couldn't get domain's sid\n"));
+ }
+ }
+
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ if (!found_domain) {
+ d_printf("none\n");
+ }
+
+ /* close opened samr and domain policy handles */
+ nt_status = dcerpc_samr_Close(b, mem_ctx, &domain_hnd, &result);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Couldn't properly close domain policy handle for domain %s\n", domain_name));
+ };
+
+ nt_status = dcerpc_samr_Close(b, mem_ctx, &connect_hnd, &result);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("Couldn't properly close samr policy handle for domain %s\n", domain_name));
+ };
+
+ /* close samr pipe and connection to IPC$ */
+ cli_shutdown(cli);
+
+ talloc_destroy(mem_ctx);
+ return 0;
+}
+
+/**
+ * Entrypoint for 'net rpc trustdom' code.
+ *
+ * @param argc Standard argc.
+ * @param argv Standard argv without initial components.
+ *
+ * @return Integer status (0 means success).
+ */
+
+static int rpc_trustdom(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "add",
+ rpc_trustdom_add,
+ NET_TRANSPORT_RPC,
+ N_("Add trusting domain's account"),
+ N_("net rpc trustdom add\n"
+ " Add trusting domain's account")
+ },
+ {
+ "del",
+ rpc_trustdom_del,
+ NET_TRANSPORT_RPC,
+ N_("Remove trusting domain's account"),
+ N_("net rpc trustdom del\n"
+ " Remove trusting domain's account")
+ },
+ {
+ "establish",
+ rpc_trustdom_establish,
+ NET_TRANSPORT_RPC,
+ N_("Establish outgoing trust relationship"),
+ N_("net rpc trustdom establish\n"
+ " Establish outgoing trust relationship")
+ },
+ {
+ "revoke",
+ rpc_trustdom_revoke,
+ NET_TRANSPORT_RPC,
+ N_("Revoke outgoing trust relationship"),
+ N_("net rpc trustdom revoke\n"
+ " Revoke outgoing trust relationship")
+ },
+ {
+ "list",
+ rpc_trustdom_list,
+ NET_TRANSPORT_RPC,
+ N_("List in- and outgoing domain trusts"),
+ N_("net rpc trustdom list\n"
+ " List in- and outgoing domain trusts")
+ },
+ {
+ "vampire",
+ rpc_trustdom_vampire,
+ NET_TRANSPORT_RPC,
+ N_("Vampire trusts from remote server"),
+ N_("net rpc trustdom vampire\n"
+ " Vampire trusts from remote server")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rpc trustdom", func);
+}
+
+/**
+ * Check if a server will take rpc commands
+ * @param flags Type of server to connect to (PDC, DMB, localhost)
+ * if the host is not explicitly specified
+ * @return bool (true means rpc supported)
+ */
+bool net_rpc_check(struct net_context *c, unsigned flags)
+{
+ struct cli_state *cli;
+ bool ret = false;
+ struct sockaddr_storage server_ss;
+ char *server_name = NULL;
+ NTSTATUS status;
+
+ /* flags (i.e. server type) may depend on command */
+ if (!net_find_server(c, NULL, flags, &server_ss, &server_name))
+ return false;
+
+ status = cli_connect_nb(server_name, &server_ss, 0, 0x20,
+ lp_netbios_name(), SMB_SIGNING_IPC_DEFAULT,
+ 0, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ DBG_ERR("NetBIOS support disabled, unable to connect\n");
+ }
+ return false;
+ }
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ lp_client_min_protocol(),
+ lp_client_max_protocol(),
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (smbXcli_conn_protocol(cli->conn) < PROTOCOL_NT1)
+ goto done;
+
+ ret = true;
+ done:
+ cli_shutdown(cli);
+ return ret;
+}
+
+/* synchronise sam database via samsync rpc calls */
+static int rpc_vampire(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "keytab",
+ rpc_vampire_keytab,
+ NET_TRANSPORT_RPC,
+ N_("Dump remote SAM database to Kerberos Keytab"),
+ N_("net rpc vampire keytab\n"
+ " Dump remote SAM database to Kerberos keytab "
+ "file")
+ },
+ {
+ "passdb",
+ rpc_vampire_passdb,
+ NET_TRANSPORT_RPC,
+ N_("Dump remote SAM database to passdb"),
+ N_("net rpc vampire passdb\n"
+ " Dump remote SAM database to passdb")
+ },
+
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc vampire\n"
+ " %s\n",
+ _("Usage:"),
+ _("Vampire remote SAM database"));
+ return 0;
+ }
+
+ return rpc_vampire_passdb(c, argc, argv);
+ }
+
+ return net_run_function(c, argc, argv, "net rpc vampire", func);
+}
+
+/**
+ * Migrate everything from a print server.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ *
+ * The order is important !
+ * To successfully add drivers the print queues have to exist !
+ * Applying ACLs should be the last step, because you're easily locked out.
+ *
+ **/
+static int rpc_printer_migrate_all(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer migrate all\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate everything from a print server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ ret = run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_printers_internals, argc,
+ argv);
+ if (ret)
+ return ret;
+
+ ret = run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_drivers_internals, argc,
+ argv);
+ if (ret)
+ return ret;
+
+ ret = run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_forms_internals, argc, argv);
+ if (ret)
+ return ret;
+
+ ret = run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_settings_internals, argc,
+ argv);
+ if (ret)
+ return ret;
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_security_internals, argc,
+ argv);
+
+}
+
+/**
+ * Migrate print drivers from a print server.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_migrate_drivers(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer migrate drivers\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate print-drivers from a print-server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_drivers_internals,
+ argc, argv);
+}
+
+/**
+ * Migrate print-forms from a print-server.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_migrate_forms(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer migrate forms\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate print-forms from a print-server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_forms_internals,
+ argc, argv);
+}
+
+/**
+ * Migrate printers from a print-server.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_migrate_printers(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer migrate printers\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate printers from a print-server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_printers_internals,
+ argc, argv);
+}
+
+/**
+ * Migrate printer-ACLs from a print-server
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_migrate_security(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer migrate security\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate printer-ACLs from a print-server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_security_internals,
+ argc, argv);
+}
+
+/**
+ * Migrate printer-settings from a print-server.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_migrate_settings(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer migrate settings\n"
+ " %s\n",
+ _("Usage:"),
+ _("Migrate printer-settings from a "
+ "print-server"));
+ return 0;
+ }
+
+ if (!c->opt_host) {
+ d_printf(_("no server to migrate\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_migrate_settings_internals,
+ argc, argv);
+}
+
+/**
+ * 'net rpc printer' entrypoint.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int rpc_printer_migrate(struct net_context *c, int argc, const char **argv)
+{
+
+ /* ouch: when addriver and setdriver are called from within
+ rpc_printer_migrate_drivers_internals, the printer-queue already
+ *has* to exist */
+
+ struct functable func[] = {
+ {
+ "all",
+ rpc_printer_migrate_all,
+ NET_TRANSPORT_RPC,
+ N_("Migrate all from remote to local print server"),
+ N_("net rpc printer migrate all\n"
+ " Migrate all from remote to local print server")
+ },
+ {
+ "drivers",
+ rpc_printer_migrate_drivers,
+ NET_TRANSPORT_RPC,
+ N_("Migrate drivers to local server"),
+ N_("net rpc printer migrate drivers\n"
+ " Migrate drivers to local server")
+ },
+ {
+ "forms",
+ rpc_printer_migrate_forms,
+ NET_TRANSPORT_RPC,
+ N_("Migrate forms to local server"),
+ N_("net rpc printer migrate forms\n"
+ " Migrate forms to local server")
+ },
+ {
+ "printers",
+ rpc_printer_migrate_printers,
+ NET_TRANSPORT_RPC,
+ N_("Migrate printers to local server"),
+ N_("net rpc printer migrate printers\n"
+ " Migrate printers to local server")
+ },
+ {
+ "security",
+ rpc_printer_migrate_security,
+ NET_TRANSPORT_RPC,
+ N_("Migrate printer ACLs to local server"),
+ N_("net rpc printer migrate security\n"
+ " Migrate printer ACLs to local server")
+ },
+ {
+ "settings",
+ rpc_printer_migrate_settings,
+ NET_TRANSPORT_RPC,
+ N_("Migrate printer settings to local server"),
+ N_("net rpc printer migrate settings\n"
+ " Migrate printer settings to local server")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rpc printer migrate",func);
+}
+
+
+/**
+ * List printers on a remote RPC server.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_list(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List printers on a remote RPC server"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_list_internals,
+ argc, argv);
+}
+
+/**
+ * List printer-drivers on a remote RPC server.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_driver_list(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer driver\n"
+ " %s\n",
+ _("Usage:"),
+ _("List printer-drivers on a remote RPC server"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_driver_list_internals,
+ argc, argv);
+}
+
+/**
+ * Publish printer in ADS via MSRPC.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_publish_publish(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer publish publish\n"
+ " %s\n",
+ _("Usage:"),
+ _("Publish printer in ADS via MSRPC"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_publish_publish_internals,
+ argc, argv);
+}
+
+/**
+ * Update printer in ADS via MSRPC.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_publish_update(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer publish update\n"
+ " %s\n",
+ _("Usage:"),
+ _("Update printer in ADS via MSRPC"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_publish_update_internals,
+ argc, argv);
+}
+
+/**
+ * UnPublish printer in ADS via MSRPC.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_publish_unpublish(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer publish unpublish\n"
+ " %s\n",
+ _("Usage:\n"),
+ _("UnPublish printer in ADS via MSRPC"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_publish_unpublish_internals,
+ argc, argv);
+}
+
+/**
+ * List published printers via MSRPC.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_publish_list(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc printer publish list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List published printers via MSRPC"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_publish_list_internals,
+ argc, argv);
+}
+
+
+/**
+ * Publish printer in ADS.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ *
+ * @return A shell status integer (0 for success).
+ **/
+static int rpc_printer_publish(struct net_context *c, int argc,
+ const char **argv)
+{
+
+ struct functable func[] = {
+ {
+ "publish",
+ rpc_printer_publish_publish,
+ NET_TRANSPORT_RPC,
+ N_("Publish printer in AD"),
+ N_("net rpc printer publish publish\n"
+ " Publish printer in AD")
+ },
+ {
+ "update",
+ rpc_printer_publish_update,
+ NET_TRANSPORT_RPC,
+ N_("Update printer in AD"),
+ N_("net rpc printer publish update\n"
+ " Update printer in AD")
+ },
+ {
+ "unpublish",
+ rpc_printer_publish_unpublish,
+ NET_TRANSPORT_RPC,
+ N_("Unpublish printer"),
+ N_("net rpc printer publish unpublish\n"
+ " Unpublish printer")
+ },
+ {
+ "list",
+ rpc_printer_publish_list,
+ NET_TRANSPORT_RPC,
+ N_("List published printers"),
+ N_("net rpc printer publish list\n"
+ " List published printers")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rpc printer publish\n"
+ " List published printers\n"
+ " Alias of net rpc printer publish "
+ "list\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_publish_list_internals,
+ argc, argv);
+ }
+
+ return net_run_function(c, argc, argv, "net rpc printer publish",func);
+
+}
+
+
+/**
+ * Display rpc printer help page.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+int rpc_printer_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net rpc printer LIST [printer] [misc. options] [targets]\n"
+ "\tlists all printers on print-server\n\n"));
+ d_printf(_("net rpc printer DRIVER [printer] [misc. options] [targets]\n"
+ "\tlists all printer-drivers on print-server\n\n"));
+ d_printf(_("net rpc printer PUBLISH action [printer] [misc. options] [targets]\n"
+ "\tpublishes printer settings in Active Directory\n"
+ "\taction can be one of PUBLISH, UPDATE, UNPUBLISH or LIST\n\n"));
+ d_printf(_("net rpc printer MIGRATE PRINTERS [printer] [misc. options] [targets]"
+ "\n\tmigrates printers from remote to local server\n\n"));
+ d_printf(_("net rpc printer MIGRATE SETTINGS [printer] [misc. options] [targets]"
+ "\n\tmigrates printer-settings from remote to local server\n\n"));
+ d_printf(_("net rpc printer MIGRATE DRIVERS [printer] [misc. options] [targets]"
+ "\n\tmigrates printer-drivers from remote to local server\n\n"));
+ d_printf(_("net rpc printer MIGRATE FORMS [printer] [misc. options] [targets]"
+ "\n\tmigrates printer-forms from remote to local server\n\n"));
+ d_printf(_("net rpc printer MIGRATE SECURITY [printer] [misc. options] [targets]"
+ "\n\tmigrates printer-ACLs from remote to local server\n\n"));
+ d_printf(_("net rpc printer MIGRATE ALL [printer] [misc. options] [targets]"
+ "\n\tmigrates drivers, forms, queues, settings and acls from\n"
+ "\tremote to local print-server\n\n"));
+ net_common_methods_usage(c, argc, argv);
+ net_common_flags_usage(c, argc, argv);
+ d_printf(_(
+ "\t-v or --verbose\t\t\tgive verbose output\n"
+ "\t --destination\t\tmigration target server (default: localhost)\n"));
+
+ return -1;
+}
+
+/**
+ * 'net rpc printer' entrypoint.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+int net_rpc_printer(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ rpc_printer_list,
+ NET_TRANSPORT_RPC,
+ N_("List all printers on print server"),
+ N_("net rpc printer list\n"
+ " List all printers on print server")
+ },
+ {
+ "migrate",
+ rpc_printer_migrate,
+ NET_TRANSPORT_RPC,
+ N_("Migrate printer to local server"),
+ N_("net rpc printer migrate\n"
+ " Migrate printer to local server")
+ },
+ {
+ "driver",
+ rpc_printer_driver_list,
+ NET_TRANSPORT_RPC,
+ N_("List printer drivers"),
+ N_("net rpc printer driver\n"
+ " List printer drivers")
+ },
+ {
+ "publish",
+ rpc_printer_publish,
+ NET_TRANSPORT_RPC,
+ N_("Publish printer in AD"),
+ N_("net rpc printer publish\n"
+ " Publish printer in AD")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc == 0) {
+ if (c->display_usage) {
+ d_printf(_("Usage:\n"));
+ d_printf(_("net rpc printer\n"
+ " List printers\n"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+ return run_rpc_command(c, NULL, &ndr_table_spoolss, 0,
+ rpc_printer_list_internals,
+ argc, argv);
+ }
+
+ return net_run_function(c, argc, argv, "net rpc printer", func);
+}
+
+/**
+ * 'net rpc' entrypoint.
+ *
+ * @param c A net_context structure.
+ * @param argc Standard main() style argc.
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped.
+ **/
+
+int net_rpc(struct net_context *c, int argc, const char **argv)
+{
+ NET_API_STATUS status;
+
+ struct functable func[] = {
+ {
+ "audit",
+ net_rpc_audit,
+ NET_TRANSPORT_RPC,
+ N_("Modify global audit settings"),
+ N_("net rpc audit\n"
+ " Modify global audit settings")
+ },
+ {
+ "info",
+ net_rpc_info,
+ NET_TRANSPORT_RPC,
+ N_("Show basic info about a domain"),
+ N_("net rpc info\n"
+ " Show basic info about a domain")
+ },
+ {
+ "join",
+ net_rpc_join,
+ NET_TRANSPORT_RPC,
+ N_("Join a domain"),
+ N_("net rpc join\n"
+ " Join a domain")
+ },
+ {
+ "oldjoin",
+ net_rpc_oldjoin,
+ NET_TRANSPORT_RPC,
+ N_("Join a domain created in server manager"),
+ N_("net rpc oldjoin\n"
+ " Join a domain created in server manager")
+ },
+ {
+ "testjoin",
+ net_rpc_testjoin,
+ NET_TRANSPORT_RPC,
+ N_("Test that a join is valid"),
+ N_("net rpc testjoin\n"
+ " Test that a join is valid")
+ },
+ {
+ "user",
+ net_rpc_user,
+ NET_TRANSPORT_RPC,
+ N_("List/modify users"),
+ N_("net rpc user\n"
+ " List/modify users")
+ },
+ {
+ "password",
+ rpc_user_password,
+ NET_TRANSPORT_RPC,
+ N_("Change a user password"),
+ N_("net rpc password\n"
+ " Change a user password\n"
+ " Alias for net rpc user password")
+ },
+ {
+ "group",
+ net_rpc_group,
+ NET_TRANSPORT_RPC,
+ N_("List/modify groups"),
+ N_("net rpc group\n"
+ " List/modify groups")
+ },
+ {
+ "share",
+ net_rpc_share,
+ NET_TRANSPORT_RPC,
+ N_("List/modify shares"),
+ N_("net rpc share\n"
+ " List/modify shares")
+ },
+ {
+ "file",
+ net_rpc_file,
+ NET_TRANSPORT_RPC,
+ N_("List open files"),
+ N_("net rpc file\n"
+ " List open files")
+ },
+ {
+ "printer",
+ net_rpc_printer,
+ NET_TRANSPORT_RPC,
+ N_("List/modify printers"),
+ N_("net rpc printer\n"
+ " List/modify printers")
+ },
+ {
+ "changetrustpw",
+ net_rpc_changetrustpw,
+ NET_TRANSPORT_RPC,
+ N_("Change trust account password"),
+ N_("net rpc changetrustpw\n"
+ " Change trust account password")
+ },
+ {
+ "trustdom",
+ rpc_trustdom,
+ NET_TRANSPORT_RPC,
+ N_("Modify domain trusts"),
+ N_("net rpc trustdom\n"
+ " Modify domain trusts")
+ },
+ {
+ "abortshutdown",
+ rpc_shutdown_abort,
+ NET_TRANSPORT_RPC,
+ N_("Abort a remote shutdown"),
+ N_("net rpc abortshutdown\n"
+ " Abort a remote shutdown")
+ },
+ {
+ "shutdown",
+ rpc_shutdown,
+ NET_TRANSPORT_RPC,
+ N_("Shutdown a remote server"),
+ N_("net rpc shutdown\n"
+ " Shutdown a remote server")
+ },
+ {
+ "vampire",
+ rpc_vampire,
+ NET_TRANSPORT_RPC,
+ N_("Sync a remote NT PDC's data into local passdb"),
+ N_("net rpc vampire\n"
+ " Sync a remote NT PDC's data into local passdb")
+ },
+ {
+ "getsid",
+ net_rpc_getsid,
+ NET_TRANSPORT_RPC,
+ N_("Fetch the domain sid into local secrets.tdb"),
+ N_("net rpc getsid\n"
+ " Fetch the domain sid into local secrets.tdb")
+ },
+ {
+ "rights",
+ net_rpc_rights,
+ NET_TRANSPORT_RPC,
+ N_("Manage privileges assigned to SID"),
+ N_("net rpc rights\n"
+ " Manage privileges assigned to SID")
+ },
+ {
+ "service",
+ net_rpc_service,
+ NET_TRANSPORT_RPC,
+ N_("Start/stop/query remote services"),
+ N_("net rpc service\n"
+ " Start/stop/query remote services")
+ },
+ {
+ "registry",
+ net_rpc_registry,
+ NET_TRANSPORT_RPC,
+ N_("Manage registry hives"),
+ N_("net rpc registry\n"
+ " Manage registry hives")
+ },
+ {
+ "shell",
+ net_rpc_shell,
+ NET_TRANSPORT_RPC,
+ N_("Open interactive shell on remote server"),
+ N_("net rpc shell\n"
+ " Open interactive shell on remote server")
+ },
+ {
+ "trust",
+ net_rpc_trust,
+ NET_TRANSPORT_RPC,
+ N_("Manage trusts"),
+ N_("net rpc trust\n"
+ " Manage trusts")
+ },
+ {
+ "conf",
+ net_rpc_conf,
+ NET_TRANSPORT_RPC,
+ N_("Configure a remote samba server"),
+ N_("net rpc conf\n"
+ " Configure a remote samba server")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (status != 0) {
+ return -1;
+ }
+
+ return net_run_function(c, argc, argv, "net rpc", func);
+}
diff --git a/source3/utils/net_rpc_audit.c b/source3/utils/net_rpc_audit.c
new file mode 100644
index 0000000..336f6a8
--- /dev/null
+++ b/source3/utils/net_rpc_audit.c
@@ -0,0 +1,540 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2006,2008 Guenther Deschner
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+
+/********************************************************************
+********************************************************************/
+
+static int net_help_audit(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net rpc audit list View configured Auditing policies\n"));
+ d_printf(_("net rpc audit enable Enable Auditing\n"));
+ d_printf(_("net rpc audit disable Disable Auditing\n"));
+ d_printf(_("net rpc audit get <category> View configured Auditing policy setting\n"));
+ d_printf(_("net rpc audit set <category> <policy> Set Auditing policies\n\n"));
+ d_printf(_("\tcategory can be one of: SYSTEM, LOGON, OBJECT, PRIVILEGE, PROCESS, POLICY, SAM, DIRECTORY or ACCOUNT\n"));
+ d_printf(_("\tpolicy can be one of: SUCCESS, FAILURE, ALL or NONE\n\n"));
+
+ return -1;
+}
+
+/********************************************************************
+********************************************************************/
+
+static void print_auditing_category(const char *policy, const char *value)
+{
+ if (policy == NULL) {
+ policy = N_("Unknown");
+ }
+ if (value == NULL) {
+ value = N_("Invalid");
+ }
+
+ d_printf(_("\t%-30s%s\n"), policy, value);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_audit_get_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ int i;
+ uint32_t audit_category;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc < 1 || argc > 2) {
+ d_printf(_("insufficient arguments\n"));
+ net_help_audit(c, argc, argv);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!get_audit_category_from_param(argv[0], &audit_category)) {
+ d_printf(_("invalid auditing category: %s\n"), argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_AUDIT_EVENTS,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ for (i=0; i < info->audit_events.count; i++) {
+
+ const char *val = NULL, *policy = NULL;
+
+ if (i != audit_category) {
+ continue;
+ }
+
+ val = audit_policy_str(mem_ctx, info->audit_events.settings[i]);
+ policy = audit_description_str(i);
+ print_auditing_category(policy, val);
+ }
+
+ done:
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("failed to get auditing policy: %s\n"),
+ nt_errstr(status));
+ }
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_audit_set_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ uint32_t audit_policy, audit_category;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc < 2 || argc > 3) {
+ d_printf(_("insufficient arguments\n"));
+ net_help_audit(c, argc, argv);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!get_audit_category_from_param(argv[0], &audit_category)) {
+ d_printf(_("invalid auditing category: %s\n"), argv[0]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ audit_policy = LSA_AUDIT_POLICY_CLEAR;
+
+ if (strequal(argv[1], "Success")) {
+ audit_policy |= LSA_AUDIT_POLICY_SUCCESS;
+ } else if (strequal(argv[1], "Failure")) {
+ audit_policy |= LSA_AUDIT_POLICY_FAILURE;
+ } else if (strequal(argv[1], "All")) {
+ audit_policy |= LSA_AUDIT_POLICY_ALL;
+ } else if (strequal(argv[1], "None")) {
+ audit_policy = LSA_AUDIT_POLICY_CLEAR;
+ } else {
+ d_printf(_("invalid auditing policy: %s\n"), argv[1]);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_AUDIT_EVENTS,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ info->audit_events.settings[audit_category] = audit_policy;
+
+ status = dcerpc_lsa_SetInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_AUDIT_EVENTS,
+ info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_AUDIT_EVENTS,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+
+ {
+ const char *val = audit_policy_str(mem_ctx, info->audit_events.settings[audit_category]);
+ const char *policy = audit_description_str(audit_category);
+ print_auditing_category(policy, val);
+ }
+
+ done:
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("failed to set audit policy: %s\n"),
+ nt_errstr(status));
+ }
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_audit_enable_internal_ext(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ bool enable)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_AUDIT_EVENTS,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ info->audit_events.auditing_mode = enable;
+
+ status = dcerpc_lsa_SetInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_AUDIT_EVENTS,
+ info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ done:
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("%s: %s\n"),
+ enable ? _("failed to enable audit policy"):
+ _("failed to disable audit policy"),
+ nt_errstr(status));
+ }
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_audit_disable_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ return rpc_audit_enable_internal_ext(pipe_hnd, mem_ctx, argc, argv,
+ false);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_audit_enable_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ return rpc_audit_enable_internal_ext(pipe_hnd, mem_ctx, argc, argv,
+ true);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_audit_list_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ union lsa_PolicyInformation *info = NULL;
+ int i;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_AUDIT_EVENTS,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ printf(_("Auditing:\t\t"));
+ switch (info->audit_events.auditing_mode) {
+ case true:
+ printf(_("Enabled"));
+ break;
+ case false:
+ printf(_("Disabled"));
+ break;
+ default:
+ printf(_("unknown (%d)"),
+ info->audit_events.auditing_mode);
+ break;
+ }
+ printf("\n");
+
+ printf(_("Auditing categories:\t%d\n"), info->audit_events.count);
+ printf(_("Auditing settings:\n"));
+
+ for (i=0; i < info->audit_events.count; i++) {
+ const char *val = audit_policy_str(mem_ctx, info->audit_events.settings[i]);
+ const char *policy = audit_description_str(i);
+ print_auditing_category(policy, val);
+ }
+
+ done:
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("failed to list auditing policies: %s\n"),
+ nt_errstr(status));
+ }
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_audit_get(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc audit get\n"
+ " %s\n",
+ _("Usage:"),
+ _("View configured audit setting"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_audit_get_internal, argc, argv);
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_audit_set(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc audit set\n"
+ " %s\n",
+ _("Usage:"),
+ _("Set audit policies"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_audit_set_internal, argc, argv);
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_audit_enable(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc audit enable\n"
+ " %s\n",
+ _("Usage:"),
+ _("Enable auditing"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_audit_enable_internal, argc, argv);
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_audit_disable(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc audit disable\n"
+ " %s\n",
+ _("Usage:"),
+ _("Disable auditing"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_audit_disable_internal, argc, argv);
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_audit_list(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc audit list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List auditing settings"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_audit_list_internal, argc, argv);
+}
+
+/********************************************************************
+********************************************************************/
+
+int net_rpc_audit(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "get",
+ rpc_audit_get,
+ NET_TRANSPORT_RPC,
+ N_("View configured auditing settings"),
+ N_("net rpc audit get\n"
+ " View configured auditing settings")
+ },
+ {
+ "set",
+ rpc_audit_set,
+ NET_TRANSPORT_RPC,
+ N_("Set auditing policies"),
+ N_("net rpc audit set\n"
+ " Set auditing policies")
+ },
+ {
+ "enable",
+ rpc_audit_enable,
+ NET_TRANSPORT_RPC,
+ N_("Enable auditing"),
+ N_("net rpc audit enable\n"
+ " Enable auditing")
+ },
+ {
+ "disable",
+ rpc_audit_disable,
+ NET_TRANSPORT_RPC,
+ N_("Disable auditing"),
+ N_("net rpc audit disable\n"
+ " Disable auditing")
+ },
+ {
+ "list",
+ rpc_audit_list,
+ NET_TRANSPORT_RPC,
+ N_("List configured auditing settings"),
+ N_("net rpc audit list\n"
+ " List configured auditing settings")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rpc audit", func);
+}
diff --git a/source3/utils/net_rpc_conf.c b/source3/utils/net_rpc_conf.c
new file mode 100644
index 0000000..25a7b43
--- /dev/null
+++ b/source3/utils/net_rpc_conf.c
@@ -0,0 +1,2483 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Local configuration interface
+ * Copyright (C) Vicentiu Ciorbaru 2011
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This is an interface to Samba's configuration.
+ *
+ * This tool supports local as well as remote interaction via rpc
+ * with the configuration stored in the registry.
+ */
+
+
+#include "includes.h"
+#include "utils/net.h"
+#include "utils/net_conf_util.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/init_samr.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "../libcli/registry/util_reg.h"
+#include "rpc_client/cli_winreg.h"
+#include "lib/smbconf/smbconf.h"
+#include "lib/smbconf/smbconf_init.h"
+#include "lib/smbconf/smbconf_reg.h"
+#include "lib/param/loadparm.h"
+
+
+
+/* internal functions */
+/**********************************************************
+ *
+ * usage functions
+ *
+ **********************************************************/
+const char confpath[100] = "Software\\Samba\\smbconf";
+
+static int rpc_conf_list_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s net rpc conf list\n", _("Usage:"));
+ return -1;
+}
+
+static int rpc_conf_listshares_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s net rpc conf listshares\n", _("Usage:"));
+ return -1;
+}
+
+static int rpc_conf_delshare_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc conf delshare <sharename>\n"));
+ return -1;
+}
+
+static int rpc_conf_addshare_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net rpc conf addshare <sharename> <path> "
+ "[writeable={y|N} [guest_ok={y|N} [<comment>]]]\n"
+ "\t<sharename> the new share name.\n"
+ "\t<path> the path on the filesystem to export.\n"
+ "\twriteable={y|N} set \"writeable to \"yes\" or "
+ "\"no\" (default) on this share.\n"
+ "\tguest_ok={y|N} set \"guest ok\" to \"yes\" or "
+ "\"no\" (default) on this share.\n"
+ "\t<comment> optional comment for the new share.\n"));
+ return -1;
+
+}
+
+static int rpc_conf_import_usage(struct net_context *c, int argc,
+ const char**argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net rpc conf import [--test|-T] <filename> "
+ "[<servicename>]\n"
+ "\t[--test|-T] testmode - do not act, just print "
+ "what would be done\n"
+ "\t<servicename> only import service <servicename>, "
+ "ignore the rest\n"));
+ return -1;
+}
+
+static int rpc_conf_showshare_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc conf showshare <sharename>\n"));
+ return -1;
+}
+
+static int rpc_conf_drop_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet rpc conf drop\n", _("Usage:"));
+ return -1;
+}
+
+static int rpc_conf_getparm_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet rpc conf getparm <sharename> <parameter>\n",
+ _("Usage:"));
+ return -1;
+}
+
+static int rpc_conf_setparm_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net rpc conf setparm <section> <param> <value>\n"));
+ return -1;
+}
+
+static int rpc_conf_delparm_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet rpc conf delparm <sharename> <parameter>\n",
+ _("Usage:"));
+ return -1;
+}
+
+static int rpc_conf_getincludes_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet rpc conf getincludes <sharename>\n",
+ _("Usage:"));
+ return -1;
+}
+
+static int rpc_conf_setincludes_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet rpc conf setincludes <sharename> [<filename>]*\n",
+ _("Usage:"));
+ return -1;
+}
+
+static int rpc_conf_delincludes_usage(struct net_context *c, int argc,
+ const char **argv)
+{
+ d_printf("%s\nnet rpc conf delincludes <sharename>\n",
+ _("Usage:"));
+ return -1;
+}
+
+/**********************************************************
+ *
+ * helper functions
+ *
+ **********************************************************/
+
+/*
+ * The function deletes a registry value with the name 'value' from the share
+ * with the name 'share_name'. 'parent_hnd' is the handle for the smbconf key.
+ */
+static NTSTATUS rpc_conf_del_value(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct policy_handle *parent_hnd,
+ const char *share_name,
+ const char *value,
+ WERROR *werr)
+{
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR result = WERR_OK;
+ WERROR _werr;
+
+ struct winreg_String keyname, valuename;
+ struct policy_handle child_hnd;
+
+ ZERO_STRUCT(child_hnd);
+ ZERO_STRUCT(keyname);
+ ZERO_STRUCT(valuename);
+
+ keyname.name = share_name;
+ valuename.name = value;
+
+ status = dcerpc_winreg_OpenKey(b, frame, parent_hnd, keyname, 0,
+ REG_KEY_WRITE, &child_hnd, &result);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to open key '%s': %s\n"),
+ keyname.name, nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(result))) {
+ d_fprintf(stderr, _("Failed to open key '%s': %s\n"),
+ keyname.name, win_errstr(result));
+ goto error;
+ }
+
+ status = dcerpc_winreg_DeleteValue(b,
+ frame,
+ &child_hnd,
+ valuename,
+ &result);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to delete value %s\n"),
+ nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(result))) {
+ if (W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND)){
+ result = WERR_OK;
+ goto error;
+ }
+
+ d_fprintf(stderr, _("Failed to delete value %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+
+error:
+ *werr = result;
+
+ dcerpc_winreg_CloseKey(b, frame, &child_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;;
+
+}
+
+/*
+ * The function sets a share in the registry with the parameters
+ * held in the smbconf_service struct
+ */
+static NTSTATUS rpc_conf_set_share(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct policy_handle *parent_hnd,
+ struct smbconf_service *service,
+ WERROR *werr)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR result = WERR_OK;
+ WERROR _werr;
+ enum winreg_CreateAction action;
+ uint32_t i, j;
+
+ const char **includes;
+
+ struct winreg_String wkey, wkeyclass;
+ struct policy_handle share_hnd;
+
+ ZERO_STRUCT(share_hnd);
+ ZERO_STRUCT(wkey);
+ ZERO_STRUCT(wkeyclass);
+
+ wkey.name = service->name;
+ wkeyclass.name = "";
+ action = REG_ACTION_NONE;
+
+ status = dcerpc_winreg_CreateKey(b,
+ frame,
+ parent_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ REG_KEY_ALL,
+ NULL,
+ &share_hnd,
+ &action,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("winreg_CreateKey: Could not create smbconf key\n");
+ goto error;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ d_printf("winreg_CreateKey: Could not create smbconf key\n");
+ goto error;
+ }
+
+ for (i = 0; i < service->num_params; i++) {
+ if (strequal(service->param_names[i], "include") == 0)
+ {
+
+ status = dcerpc_winreg_set_sz(frame, b, &share_hnd,
+ service->param_names[i],
+ service->param_values[i],
+ &result);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr,
+ "ERROR: Share: '%s'\n"
+ "Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ service->name,
+ service->param_names[i],
+ service->param_values[i],
+ nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(result))) {
+ d_fprintf(stderr,
+ "ERROR: Share: '%s'\n"
+ "Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ service->name,
+ service->param_names[i],
+ service->param_values[i],
+ win_errstr(result));
+ goto error;
+ }
+ } else {
+
+ includes = talloc_zero_array(frame,
+ const char *,
+ service->num_params + 1);
+ if (includes == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, "ERROR: out of memory\n");
+ goto error;
+ }
+
+ for (j = i; j < service->num_params; j++) {
+
+ includes[j - i] = talloc_strdup(
+ frame,
+ service->param_values[j]);
+
+ if (includes[j-i] == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, "ERROR: out of memory\n");
+ goto error;
+ }
+ }
+
+ status = dcerpc_winreg_set_multi_sz(frame, b, &share_hnd,
+ "includes",
+ includes,
+ &result);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, "ERROR: Share: '%s'\n"
+ "Could not set includes\n %s\n",
+ service->name,
+ nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(result))) {
+ d_fprintf(stderr, "ERROR: Share: '%s'\n"
+ "Could not set includes\n %s\n",
+ service->name,
+ win_errstr(result));
+ goto error;
+ }
+
+ i = service->num_params;
+ }
+ }
+
+error:
+ /* in case of error, should it delete the created key? */
+ if (!(W_ERROR_IS_OK(result))) {
+ status = werror_to_ntstatus(result);
+
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &share_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+
+}
+
+/*
+ * The function opens the registry database and retrieves
+ * as a smbconf_service struct the share with the name
+ * 'share_name'
+ */
+static NTSTATUS rpc_conf_get_share(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct policy_handle *parent_hnd,
+ const char *share_name,
+ struct smbconf_service *share,
+ WERROR *werr)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR result = WERR_OK;
+ WERROR _werr;
+ struct policy_handle child_hnd;
+ int32_t includes_cnt, includes_idx = -1;
+ uint32_t num_vals, num_subkeys, i, param_cnt = 0;
+ const char **val_names;
+ const char **subkeys = NULL;
+ enum winreg_Type *types;
+ DATA_BLOB *data;
+ struct winreg_String key = { 0, };
+ const char **multi_s = NULL;
+ const char *s = NULL;
+ struct smbconf_service tmp_share;
+
+ ZERO_STRUCT(tmp_share);
+
+ /*
+ * Determine correct upper/lowercase.
+ */
+ status = dcerpc_winreg_enum_keys(frame,
+ b,
+ parent_hnd,
+ &num_subkeys,
+ &subkeys,
+ &result);
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to enumerate shares: %s\n"),
+ nt_errstr(status));
+ goto error;
+ }
+ if (!(W_ERROR_IS_OK(result))) {
+ d_fprintf(stderr, _("Failed to enumerate shares: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+
+ for (i = 0; i < num_subkeys; i++) {
+ if (!strequal(share_name, subkeys[i])) {
+ continue;
+ }
+
+ key.name = subkeys[i];
+ }
+
+ if (key.name == NULL) {
+ d_fprintf(stderr, _("Could not find share.\n"));
+ goto error;
+ }
+
+ status = dcerpc_winreg_OpenKey(b, frame, parent_hnd, key, 0,
+ REG_KEY_READ, &child_hnd, &result);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to open subkey: %s\n"),
+ nt_errstr(status));
+ goto error;
+ }
+ if (!(W_ERROR_IS_OK(result))) {
+ d_fprintf(stderr, _("Failed to open subkey: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ /* get all the info from the share key */
+ status = dcerpc_winreg_enumvals(frame,
+ b,
+ &child_hnd,
+ &num_vals,
+ &val_names,
+ &types,
+ &data,
+ &result);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to enumerate values: %s\n"),
+ nt_errstr(status));
+ goto error;
+ }
+ if (!(W_ERROR_IS_OK(result))) {
+ d_fprintf(stderr, _("Failed to enumerate values: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ /* check for includes */
+ for (i = 0; i < num_vals; i++) {
+ if (strcmp(val_names[i], "includes") == 0){
+ if (!pull_reg_multi_sz(frame,
+ &data[i],
+ &multi_s))
+ {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr,
+ _("Failed to enumerate values: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ includes_idx = i;
+ }
+ }
+ /* count the number of includes */
+ includes_cnt = 0;
+ if (includes_idx != -1) {
+ for (includes_cnt = 0;
+ multi_s[includes_cnt] != NULL;
+ includes_cnt ++);
+ }
+ /* place the name of the share in the smbconf_service struct */
+ tmp_share.name = talloc_strdup(frame, key.name);
+ if (tmp_share.name == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ /* place the number of parameters in the smbconf_service struct */
+ tmp_share.num_params = num_vals;
+ if (includes_idx != -1) {
+ tmp_share.num_params = num_vals + includes_cnt - 1;
+ }
+ /* allocate memory for the param_names and param_values lists */
+ tmp_share.param_names = talloc_zero_array(frame, char *, tmp_share.num_params);
+ if (tmp_share.param_names == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ tmp_share.param_values = talloc_zero_array(frame, char *, tmp_share.num_params);
+ if (tmp_share.param_values == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ /* place all params except includes */
+ for (i = 0; i < num_vals; i++) {
+ if (strcmp(val_names[i], "includes") != 0) {
+ if (!pull_reg_sz(frame, &data[i], &s)) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr,
+ _("Failed to enumerate values: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ /* place param_names */
+ tmp_share.param_names[param_cnt] = talloc_strdup(frame, val_names[i]);
+ if (tmp_share.param_names[param_cnt] == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+
+ /* place param_values */
+ tmp_share.param_values[param_cnt++] = talloc_strdup(frame, s);
+ if (tmp_share.param_values[param_cnt - 1] == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ }
+ }
+ /* place the includes last */
+ for (i = 0; i < includes_cnt; i++) {
+ tmp_share.param_names[param_cnt] = talloc_strdup(frame, "include");
+ if (tmp_share.param_names[param_cnt] == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+
+ tmp_share.param_values[param_cnt++] = talloc_strdup(frame, multi_s[i]);
+ if (tmp_share.param_values[param_cnt - 1] == NULL) {
+ result = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+ }
+
+ /* move everything to the main memory ctx */
+ for (i = 0; i < param_cnt; i++) {
+ tmp_share.param_names[i] = talloc_move(mem_ctx, &tmp_share.param_names[i]);
+ tmp_share.param_values[i] = talloc_move(mem_ctx, &tmp_share.param_values[i]);
+ }
+
+ tmp_share.name = talloc_move(mem_ctx, &tmp_share.name);
+ tmp_share.param_names = talloc_move(mem_ctx, &tmp_share.param_names);
+ tmp_share.param_values = talloc_move(mem_ctx, &tmp_share.param_values);
+ /* out parameter */
+ *share = tmp_share;
+error:
+ /* close child */
+ dcerpc_winreg_CloseKey(b, frame, &child_hnd, &_werr);
+ *werr = result;
+ TALLOC_FREE(frame);
+ return status;
+}
+
+/*
+ * The function prints the shares held as smbconf_service structs
+ * in a smbconf file format.
+ */
+static int rpc_conf_print_shares(uint32_t num_shares,
+ struct smbconf_service *shares)
+{
+
+ uint32_t share_count, param_count;
+ const char *indent = "\t";
+
+ if (num_shares == 0) {
+ return 0;
+ }
+
+ for (share_count = 0; share_count < num_shares; share_count++) {
+ d_printf("\n");
+ if (shares[share_count].name != NULL) {
+ d_printf("[%s]\n", shares[share_count].name);
+ }
+
+ for (param_count = 0;
+ param_count < shares[share_count].num_params;
+ param_count++)
+ {
+ d_printf("%s%s = %s\n",
+ indent,
+ shares[share_count].param_names[param_count],
+ shares[share_count].param_values[param_count]);
+ }
+ }
+ d_printf("\n");
+
+ return 0;
+
+}
+
+/*
+ * The function opens the registry key
+ * HKLM/Software/Samba/smbconf with the give access_mask
+ */
+static NTSTATUS rpc_conf_open_conf(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ uint32_t access_mask,
+ struct policy_handle *hive_hnd,
+ struct policy_handle *key_hnd,
+ WERROR *werr)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR result = WERR_OK;
+ WERROR _werr;
+ struct policy_handle tmp_hive_hnd, tmp_key_hnd;
+ struct winreg_String key;
+
+ ZERO_STRUCT(key);
+
+ status = dcerpc_winreg_OpenHKLM(b, frame, NULL,
+ access_mask, &tmp_hive_hnd, &result);
+
+ /*
+ * print no error messages if it is a read only open
+ * and key does not exist
+ * error still gets returned
+ */
+
+ if (access_mask == REG_KEY_READ &&
+ W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND))
+ {
+ goto error;
+ }
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to open hive: %s\n"),
+ nt_errstr(status));
+ goto error;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ d_fprintf(stderr, _("Failed to open hive: %s\n"),
+ win_errstr(result));
+ goto error;
+ }
+
+ key.name = confpath;
+ status = dcerpc_winreg_OpenKey(b, frame, &tmp_hive_hnd, key, 0,
+ access_mask, &tmp_key_hnd, &result);
+
+ /*
+ * print no error messages if it is a read only open
+ * and key does not exist
+ * error still gets returned
+ */
+
+ if (access_mask == REG_KEY_READ &&
+ W_ERROR_EQUAL(result, WERR_FILE_NOT_FOUND))
+ {
+ goto error;
+ }
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to open smbconf key: %s\n"),
+ nt_errstr(status));
+ dcerpc_winreg_CloseKey(b, frame, &tmp_hive_hnd, &_werr);
+ goto error;
+ }
+ if (!(W_ERROR_IS_OK(result))) {
+ d_fprintf(stderr, _("Failed to open smbconf key: %s\n"),
+ win_errstr(result));
+ dcerpc_winreg_CloseKey(b, frame, &tmp_hive_hnd, &_werr);
+ goto error;
+ }
+
+ *hive_hnd = tmp_hive_hnd;
+ *key_hnd = tmp_key_hnd;
+
+error:
+ TALLOC_FREE(frame);
+ *werr = result;
+
+ return status;
+}
+
+/**********************************************************
+ *
+ * internal functions that provide the functionality
+ * net rpc conf
+ *
+ **********************************************************/
+
+static NTSTATUS rpc_conf_listshares_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+ uint32_t num_subkeys;
+ uint32_t i;
+ const char **subkeys = NULL;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 0 || c->display_usage) {
+ rpc_conf_listshares_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ status = dcerpc_winreg_enum_keys(frame,
+ b,
+ &key_hnd,
+ &num_subkeys,
+ &subkeys,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to enumerate keys: %s\n"),
+ nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, _("Failed to enumerate keys: %s\n"),
+ win_errstr(werr));
+ goto error;
+ }
+
+ for (i = 0; i < num_subkeys; i++) {
+ d_printf("%s\n", subkeys[i]);
+ }
+
+error:
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;;
+}
+
+static NTSTATUS rpc_conf_delshare_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 1 || c->display_usage) {
+ rpc_conf_delshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_ALL,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ status = dcerpc_winreg_delete_subkeys_recursive(frame,
+ b,
+ &key_hnd,
+ REG_KEY_ALL,
+ argv[0],
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "winreg_delete_subkeys: Could not delete key %s: %s\n",
+ argv[0], nt_errstr(status));
+ goto error;
+ }
+
+ if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)){
+ d_fprintf(stderr, _("ERROR: Key does not exist\n"));
+ }
+
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr,
+ "winreg_delete_subkeys: Could not delete key %s: %s\n",
+ argv[0], win_errstr(werr));
+ goto error;
+ }
+
+error:
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+
+ return status;
+}
+
+static NTSTATUS rpc_conf_list_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+ uint32_t num_subkeys;
+ uint32_t i;
+ struct smbconf_service *shares;
+ const char **subkeys = NULL;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 0 || c->display_usage) {
+ rpc_conf_list_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ status = dcerpc_winreg_enum_keys(frame,
+ b,
+ &key_hnd,
+ &num_subkeys,
+ &subkeys,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("Failed to enumerate keys: %s\n"),
+ nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, _("Failed to enumerate keys: %s\n"),
+ win_errstr(werr));
+ goto error;
+ }
+
+ if (num_subkeys == 0) {
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ /* get info from each subkey */
+ shares = talloc_zero_array(frame, struct smbconf_service, num_subkeys);
+ if (shares == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create shares: %s\n"),
+ win_errstr(werr));
+ goto error;
+
+ }
+
+ for (i = 0; i < num_subkeys; i++) {
+ /* get each share and place it in the shares array */
+ status = rpc_conf_get_share(frame,
+ b,
+ &key_hnd,
+ subkeys[i],
+ &shares[i],
+ &werr);
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ }
+ /* print the shares array */
+ rpc_conf_print_shares(num_subkeys, shares);
+
+error:
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+
+}
+
+static NTSTATUS rpc_conf_drop_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+ const char *keyname = confpath;
+ struct winreg_String wkey, wkeyclass;
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 0 || c->display_usage) {
+ rpc_conf_drop_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_ALL,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ status = dcerpc_winreg_delete_subkeys_recursive(frame,
+ b,
+ &hive_hnd,
+ REG_KEY_ALL,
+ keyname,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("winreg_delete_subkeys: Could not delete key %s: %s\n",
+ keyname, nt_errstr(status));
+ goto error;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("winreg_delete_subkeys: Could not delete key %s: %s\n",
+ keyname, win_errstr(werr));
+ goto error;
+ }
+
+ ZERO_STRUCT(wkey);
+ wkey.name = keyname;
+ ZERO_STRUCT(wkeyclass);
+ wkeyclass.name = "";
+ action = REG_ACTION_NONE;
+
+ status = dcerpc_winreg_CreateKey(b,
+ frame,
+ &hive_hnd,
+ wkey,
+ wkeyclass,
+ 0,
+ REG_KEY_ALL,
+ NULL,
+ &key_hnd,
+ &action,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("winreg_CreateKey: Could not create smbconf key\n");
+ goto error;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_printf("winreg_CreateKey: Could not create smbconf key\n");
+ goto error;
+ }
+
+
+error:
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS rpc_conf_import_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ struct policy_handle hive_hnd, key_hnd;
+
+ const char *filename = NULL;
+ const char *servicename = NULL;
+ char *conf_source = NULL;
+ TALLOC_CTX *frame;
+ struct smbconf_ctx *txt_ctx;
+ struct smbconf_service *service = NULL;
+ struct smbconf_service **services = NULL;
+ uint32_t num_shares, i;
+ sbcErr err = SBC_ERR_UNKNOWN_FAILURE;
+
+ WERROR werr = WERR_OK;
+ NTSTATUS status = NT_STATUS_OK;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+ frame = talloc_stackframe();
+
+ if (c->display_usage) {
+ rpc_conf_import_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ switch (argc) {
+ case 0:
+ default:
+ rpc_conf_import_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ case 2:
+ servicename = talloc_strdup(frame, argv[1]);
+ if (servicename == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto error;
+ }
+
+ FALL_THROUGH;
+ case 1:
+ filename = argv[0];
+ break;
+ }
+
+ DEBUG(3,("rpc_conf_import: reading configuration from file %s.\n",
+ filename));
+
+ conf_source = talloc_asprintf(frame, "file:%s", filename);
+ if (conf_source == NULL) {
+ d_fprintf(stderr, _("error: out of memory!\n"));
+ goto error;
+ }
+
+ err = smbconf_init(frame, &txt_ctx, conf_source);
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, _("error loading file '%s': %s\n"), filename,
+ sbcErrorString(err));
+ goto error;
+ }
+
+ if (c->opt_testmode) {
+ d_printf(_("\nTEST MODE - "
+ "would import the following configuration:\n\n"));
+ }
+
+ if (servicename != NULL) {
+ err = smbconf_get_share(txt_ctx, frame,
+ servicename,
+ &service);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto error;
+ }
+
+ num_shares = 1;
+
+ } else {
+
+ err = smbconf_get_config(txt_ctx, frame,
+ &num_shares,
+ &services);
+ if (!SBC_ERROR_IS_OK(err)) {
+ goto error;
+ }
+ }
+
+ if (c->opt_testmode) {
+ if (servicename != NULL) {
+ rpc_conf_print_shares(1, service);
+ }
+ for (i = 0; i < num_shares; i++) {
+ rpc_conf_print_shares(1, services[i]);
+ }
+ goto error;
+ }
+
+ status = rpc_conf_drop_internal(c,
+ domain_sid,
+ domain_name,
+ cli,
+ pipe_hnd,
+ frame,
+ 0,
+ NULL );
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ if (servicename != NULL) {
+ status = rpc_conf_set_share(frame,
+ b,
+ &key_hnd,
+ service,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ } else {
+
+ for (i = 0; i < num_shares; i++) {
+ status = rpc_conf_set_share(frame,
+ b,
+ &key_hnd,
+ services[i],
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ }
+ }
+
+error:
+ if (!SBC_ERROR_IS_OK(err)) {
+ d_fprintf(stderr, "ERROR: %s\n", sbcErrorString(err));
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS rpc_conf_showshare_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+ struct smbconf_service *service = NULL;
+ const char *sharename = NULL;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 1 || c->display_usage) {
+ rpc_conf_showshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ sharename = talloc_strdup(frame, argv[0]);
+ if (sharename == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(werr));
+ goto error;
+ }
+
+ service = talloc(frame, struct smbconf_service);
+ if (service == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ d_fprintf(stderr, _("Failed to create share: %s\n"),
+ win_errstr(werr));
+ goto error;
+ }
+
+ status = rpc_conf_get_share(frame,
+ b,
+ &key_hnd,
+ sharename,
+ service,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ rpc_conf_print_shares(1, service);
+
+error:
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS rpc_conf_addshare_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd, share_hnd;
+ char *sharename = NULL;
+ const char *path = NULL;
+ const char *comment = NULL;
+ const char *guest_ok = "no";
+ const char *read_only = "yes";
+ struct winreg_String key, keyclass;
+ enum winreg_CreateAction action = 0;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+ ZERO_STRUCT(share_hnd);
+
+ ZERO_STRUCT(key);
+ ZERO_STRUCT(keyclass);
+
+ if (c->display_usage) {
+ rpc_conf_addshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ switch (argc) {
+ case 0:
+ case 1:
+ default:
+ rpc_conf_addshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ case 5:
+ comment = argv[4];
+
+ FALL_THROUGH;
+ case 4:
+ if (!strnequal(argv[3], "guest_ok=", 9)) {
+ rpc_conf_addshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+ switch (argv[3][9]) {
+ case 'y':
+ case 'Y':
+ guest_ok = "yes";
+ break;
+ case 'n':
+ case 'N':
+ guest_ok = "no";
+ break;
+ default:
+ rpc_conf_addshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ FALL_THROUGH;
+ case 3:
+ if (!strnequal(argv[2], "writeable=", 10)) {
+ rpc_conf_addshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+ switch (argv[2][10]) {
+ case 'y':
+ case 'Y':
+ read_only = "no";
+ break;
+ case 'n':
+ case 'N':
+ read_only = "yes";
+ break;
+ default:
+ rpc_conf_addshare_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ FALL_THROUGH;
+ case 2:
+ path = argv[1];
+ sharename = talloc_strdup(frame, argv[0]);
+ if (sharename == NULL) {
+ d_printf(_("error: out of memory!\n"));
+ goto error;
+ }
+
+ break;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ key.name = argv[0];
+ keyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(b, frame, &key_hnd, key, keyclass,
+ 0, REG_KEY_READ, NULL, &share_hnd,
+ &action, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ argv[0], nt_errstr(status));
+ goto error;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ argv[0], win_errstr(werr));
+ goto error;
+ }
+
+ switch (action) {
+ case REG_ACTION_NONE:
+ werr = WERR_CREATE_FAILED;
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ argv[0], win_errstr(werr));
+ goto error;
+ case REG_CREATED_NEW_KEY:
+ DEBUG(5, ("net rpc conf setincludes:"
+ "createkey created %s\n", argv[0]));
+ break;
+ case REG_OPENED_EXISTING_KEY:
+ d_fprintf(stderr, _("ERROR: Share '%s' already exists\n"), argv[0]);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ /* set the path parameter */
+ status = dcerpc_winreg_set_sz(frame, b, &share_hnd,
+ "path", path, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "path", path, nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "path", path, win_errstr(werr));
+ goto error;
+ }
+
+ /* set the writeable parameter */
+ status = dcerpc_winreg_set_sz(frame, b, &share_hnd,
+ "read only", read_only, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "read only", read_only, nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "read only", read_only, win_errstr(werr));
+ goto error;
+ }
+
+ /* set the guest ok parameter */
+ status = dcerpc_winreg_set_sz(frame, b, &share_hnd,
+ "guest ok", guest_ok, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "guest ok", guest_ok, nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "guest ok", guest_ok, win_errstr(werr));
+ goto error;
+ }
+
+ if (argc == 5) {
+ /* set the comment parameter */
+ status = dcerpc_winreg_set_sz(frame, b, &share_hnd,
+ "comment", comment, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "comment", comment, nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ "comment", comment, win_errstr(werr));
+ goto error;
+ }
+ }
+error:
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &share_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS rpc_conf_getparm_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+ struct smbconf_service *service = NULL;
+
+ bool param_is_set = false;
+ uint32_t param_count;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 2 || c->display_usage) {
+ rpc_conf_getparm_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+
+ service = talloc(frame, struct smbconf_service);
+
+ status = rpc_conf_get_share(frame,
+ b,
+ &key_hnd,
+ argv[0],
+ service,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) {
+ d_fprintf(stderr, _("ERROR: Share %s does not exist\n"),
+ argv[0]);
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ for (param_count = 0;
+ param_count < service->num_params;
+ param_count++)
+ {
+ /* should includes also be printed? */
+ if (strcmp(service->param_names[param_count], argv[1]) == 0) {
+ d_printf(_("%s\n"),
+ service->param_values[param_count]);
+ param_is_set = true;
+ }
+ }
+
+ if (!param_is_set) {
+ d_fprintf(stderr, _("ERROR: Given parameter '%s' has not been set\n"),
+ argv[1]);
+ werr = WERR_FILE_NOT_FOUND;
+ goto error;
+ }
+
+error:
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+
+}
+
+static NTSTATUS rpc_conf_setparm_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd, share_hnd;
+
+ struct winreg_String key, keyclass;
+ enum winreg_CreateAction action = 0;
+
+ const char *service_name, *param_name, *valstr;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+ ZERO_STRUCT(share_hnd);
+
+ ZERO_STRUCT(key);
+ ZERO_STRUCT(keyclass);
+
+ if (argc != 3 || c->display_usage) {
+ rpc_conf_setparm_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ service_name = argv[0];
+ param_name = argv[1];
+ valstr = argv[2];
+
+ key.name = service_name;
+ keyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(b, frame, &key_hnd, key, keyclass,
+ 0, REG_KEY_READ, NULL, &share_hnd,
+ &action, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ service_name, nt_errstr(status));
+ goto error;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ service_name, win_errstr(werr));
+ goto error;
+ }
+
+ switch (action) {
+ case REG_ACTION_NONE:
+ werr = WERR_CREATE_FAILED;
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ service_name, win_errstr(werr));
+ goto error;
+ case REG_CREATED_NEW_KEY:
+ DEBUG(5, ("net rpc conf setparm:"
+ "createkey created %s\n", service_name));
+ break;
+ case REG_OPENED_EXISTING_KEY:
+ DEBUG(5, ("net rpc conf setparm:"
+ "createkey opened existing %s\n",
+ service_name));
+
+ /* delete possibly existing value */
+ status = rpc_conf_del_value(frame,
+ b,
+ &key_hnd,
+ service_name,
+ param_name,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ break;
+ }
+
+ /*
+ * check if parameter is valid for writing
+ */
+
+ if (!net_conf_param_valid(service_name, param_name, valstr)) {
+ werr = WERR_INVALID_PARAMETER;
+ goto error;
+ }
+
+ /* set the parameter */
+ status = dcerpc_winreg_set_sz(frame, b, &share_hnd,
+ param_name, valstr, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ param_name, valstr, nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, "ERROR: Could not set parameter '%s'"
+ " with value %s\n %s\n",
+ param_name, valstr, win_errstr(werr));
+ goto error;
+ }
+
+error:
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &share_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS rpc_conf_delparm_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 2 || c->display_usage) {
+ rpc_conf_delparm_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ status = rpc_conf_del_value(frame,
+ b,
+ &key_hnd,
+ argv[0],
+ argv[1],
+ &werr);
+
+error:
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+
+}
+
+static NTSTATUS rpc_conf_getincludes_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+ struct smbconf_service *service = NULL;
+
+ uint32_t param_count;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 1 || c->display_usage) {
+ rpc_conf_getincludes_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ service = talloc(frame, struct smbconf_service);
+
+ status = rpc_conf_get_share(frame,
+ b,
+ &key_hnd,
+ argv[0],
+ service,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ for (param_count = 0;
+ param_count < service->num_params;
+ param_count++)
+ {
+ if (strcmp(service->param_names[param_count], "include") == 0) {
+ d_printf(_("%s = %s\n"),
+ service->param_names[param_count],
+ service->param_values[param_count]);
+ }
+ }
+
+error:
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+
+}
+
+static NTSTATUS rpc_conf_setincludes_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd, share_hnd;
+
+ struct winreg_String key, keyclass;
+ enum winreg_CreateAction action = 0;
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+ ZERO_STRUCT(share_hnd);
+
+ ZERO_STRUCT(key);
+ ZERO_STRUCT(keyclass);
+
+ if (argc < 1 || c->display_usage) {
+ rpc_conf_setincludes_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ key.name = argv[0];
+ keyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(b, frame, &key_hnd, key, keyclass,
+ 0, REG_KEY_READ, NULL, &share_hnd,
+ &action, &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ argv[0], nt_errstr(status));
+ goto error;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ argv[0], win_errstr(werr));
+ goto error;
+ }
+
+ switch (action) {
+ case REG_ACTION_NONE:
+ /* Is there any other way to treat this? */
+ werr = WERR_CREATE_FAILED;
+ d_fprintf(stderr, _("ERROR: Could not create share key '%s'\n%s\n"),
+ argv[0], win_errstr(werr));
+ goto error;
+ case REG_CREATED_NEW_KEY:
+ DEBUG(5, ("net rpc conf setincludes:"
+ "createkey created %s\n", argv[0]));
+ break;
+ case REG_OPENED_EXISTING_KEY:
+ DEBUG(5, ("net rpc conf setincludes:"
+ "createkey opened existing %s\n", argv[0]));
+
+ /* delete possibly existing value */
+ status = rpc_conf_del_value(frame,
+ b,
+ &key_hnd,
+ argv[0],
+ "includes",
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+ break;
+ }
+
+ /* set the 'includes' values */
+ status = dcerpc_winreg_set_multi_sz(frame, b, &share_hnd,
+ "includes", argv + 1, &werr);
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr, "ERROR: Could not set includes\n %s\n",
+ nt_errstr(status));
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ d_fprintf(stderr, "ERROR: Could not set includes\n %s\n",
+ win_errstr(werr));
+ goto error;
+ }
+
+error:
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &share_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS rpc_conf_delincludes_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_OK;
+ WERROR werr = WERR_OK;
+ WERROR _werr;
+
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* key info */
+ struct policy_handle hive_hnd, key_hnd;
+
+
+ ZERO_STRUCT(hive_hnd);
+ ZERO_STRUCT(key_hnd);
+
+
+ if (argc != 1 || c->display_usage) {
+ rpc_conf_delincludes_usage(c, argc, argv);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ status = rpc_conf_open_conf(frame,
+ b,
+ REG_KEY_READ,
+ &hive_hnd,
+ &key_hnd,
+ &werr);
+
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ goto error;
+ }
+
+ status = rpc_conf_del_value(frame,
+ b,
+ &key_hnd,
+ argv[0],
+ "includes",
+ &werr);
+
+error:
+
+ if (!(W_ERROR_IS_OK(werr))) {
+ status = werror_to_ntstatus(werr);
+ }
+
+ dcerpc_winreg_CloseKey(b, frame, &hive_hnd, &_werr);
+ dcerpc_winreg_CloseKey(b, frame, &key_hnd, &_werr);
+
+ TALLOC_FREE(frame);
+ return status;
+
+}
+
+/**********************************************************
+ *
+ * Functions that run the rpc commands for net rpc conf modules
+ *
+ **********************************************************/
+
+static int rpc_conf_drop(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_drop_internal, argc, argv );
+
+}
+
+static int rpc_conf_showshare(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_showshare_internal, argc, argv );
+}
+
+static int rpc_conf_addshare(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_addshare_internal, argc, argv );
+}
+
+static int rpc_conf_listshares(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_listshares_internal, argc, argv );
+}
+
+static int rpc_conf_list(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_list_internal, argc, argv );
+}
+
+static int rpc_conf_import(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_import_internal, argc, argv );
+}
+static int rpc_conf_delshare(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_delshare_internal, argc, argv );
+}
+
+static int rpc_conf_getparm(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_getparm_internal, argc, argv );
+}
+
+static int rpc_conf_setparm(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_setparm_internal, argc, argv );
+}
+static int rpc_conf_delparm(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_delparm_internal, argc, argv );
+}
+
+static int rpc_conf_getincludes(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_getincludes_internal, argc, argv );
+}
+
+static int rpc_conf_setincludes(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_setincludes_internal, argc, argv );
+}
+
+static int rpc_conf_delincludes(struct net_context *c, int argc,
+ const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_conf_delincludes_internal, argc, argv );
+}
+
+/* function calls */
+int net_rpc_conf(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct functable func_table[] = {
+ {
+ "list",
+ rpc_conf_list,
+ NET_TRANSPORT_RPC,
+ N_("Dump the complete remote configuration in smb.conf like "
+ "format."),
+ N_("net rpc conf list\n"
+ " Dump the complete remote configuration in smb.conf "
+ "like format.")
+
+ },
+ {
+ "import",
+ rpc_conf_import,
+ NET_TRANSPORT_RPC,
+ N_("Import configuration from file in smb.conf "
+ "format."),
+ N_("net rpc conf import\n"
+ " Import configuration from file in smb.conf "
+ "format.")
+ },
+ {
+ "listshares",
+ rpc_conf_listshares,
+ NET_TRANSPORT_RPC,
+ N_("List the remote share names."),
+ N_("net rpc conf list\n"
+ " List the remote share names.")
+
+ },
+ {
+ "drop",
+ rpc_conf_drop,
+ NET_TRANSPORT_RPC,
+ N_("Delete the complete remote configuration."),
+ N_("net rpc conf drop\n"
+ " Delete the complete remote configuration.")
+
+ },
+ {
+ "showshare",
+ rpc_conf_showshare,
+ NET_TRANSPORT_RPC,
+ N_("Show the definition of a remote share."),
+ N_("net rpc conf showshare\n"
+ " Show the definition of a remote share.")
+
+ },
+ {
+ "addshare",
+ rpc_conf_addshare,
+ NET_TRANSPORT_RPC,
+ N_("Create a new remote share."),
+ N_("net rpc conf addshare\n"
+ " Create a new remote share.")
+ },
+ {
+ "delshare",
+ rpc_conf_delshare,
+ NET_TRANSPORT_RPC,
+ N_("Delete a remote share."),
+ N_("net rpc conf delshare\n"
+ " Delete a remote share.")
+ },
+ {
+ "getparm",
+ rpc_conf_getparm,
+ NET_TRANSPORT_RPC,
+ N_("Retrieve the value of a parameter."),
+ N_("net rpc conf getparm\n"
+ " Retrieve the value of a parameter.")
+ },
+ {
+ "setparm",
+ rpc_conf_setparm,
+ NET_TRANSPORT_RPC,
+ N_("Store a parameter."),
+ N_("net rpc conf setparm\n"
+ " Store a parameter.")
+ },
+ {
+ "delparm",
+ rpc_conf_delparm,
+ NET_TRANSPORT_RPC,
+ N_("Delete a parameter."),
+ N_("net rpc conf delparm\n"
+ " Delete a parameter.")
+ },
+ {
+ "getincludes",
+ rpc_conf_getincludes,
+ NET_TRANSPORT_RPC,
+ N_("Show the includes of a share definition."),
+ N_("net rpc conf getincludes\n"
+ " Show the includes of a share definition.")
+ },
+ {
+ "setincludes",
+ rpc_conf_setincludes,
+ NET_TRANSPORT_RPC,
+ N_("Set includes for a share."),
+ N_("net rpc conf setincludes\n"
+ " Set includes for a share.")
+ },
+ {
+ "delincludes",
+ rpc_conf_delincludes,
+ NET_TRANSPORT_RPC,
+ N_("Delete includes from a share definition."),
+ N_("net rpc conf delincludes\n"
+ " Delete includes from a share definition.")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rpc conf", func_table);
+
+}
diff --git a/source3/utils/net_rpc_printer.c b/source3/utils/net_rpc_printer.c
new file mode 100644
index 0000000..f72203f
--- /dev/null
+++ b/source3/utils/net_rpc_printer.c
@@ -0,0 +1,2624 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2004,2009 Guenther Deschner (gd@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "includes.h"
+#include "system/filesys.h"
+#include "utils/net.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_spoolss_c.h"
+#include "rpc_client/cli_spoolss.h"
+#include "rpc_client/init_spoolss.h"
+#include "nt_printing.h"
+#include "registry.h"
+#include "../libcli/security/security.h"
+#include "../libcli/registry/util_reg.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "auth/gensec/gensec.h"
+#include "auth/credentials/credentials.h"
+#include "lib/util/string_wrappers.h"
+
+
+/**
+ * This display-printdriver-functions was borrowed from rpcclient/cmd_spoolss.c.
+ * It is here for debugging purpose and should be removed later on.
+ **/
+
+/****************************************************************************
+ Printer info level 3 display function.
+****************************************************************************/
+
+static void display_print_driver3(struct spoolss_DriverInfo3 *r)
+{
+ int i;
+
+ if (!r) {
+ return;
+ }
+
+ printf(_("Printer Driver Info 3:\n"));
+ printf(_("\tVersion: [%x]\n"), r->version);
+ printf(_("\tDriver Name: [%s]\n"), r->driver_name);
+ printf(_("\tArchitecture: [%s]\n"), r->architecture);
+ printf(_("\tDriver Path: [%s]\n"), r->driver_path);
+ printf(_("\tDatafile: [%s]\n"), r->data_file);
+ printf(_("\tConfigfile: [%s]\n\n"), r->config_file);
+ printf(_("\tHelpfile: [%s]\n\n"), r->help_file);
+
+ for (i=0; r->dependent_files && r->dependent_files[i] != NULL; i++) {
+ printf(_("\tDependentfiles: [%s]\n"), r->dependent_files[i]);
+ }
+
+ printf("\n");
+
+ printf(_("\tMonitorname: [%s]\n"), r->monitor_name);
+ printf(_("\tDefaultdatatype: [%s]\n\n"), r->default_datatype);
+}
+
+static void display_reg_value(const char *subkey, const char *name, struct registry_value *value)
+{
+ const char *text;
+
+ switch(value->type) {
+ case REG_DWORD:
+ if (value->data.length == sizeof(uint32_t)) {
+ d_printf(_("\t[%s:%s]: REG_DWORD: 0x%08x\n"), subkey,
+ name, IVAL(value->data.data,0));
+ } else {
+ d_printf(_("\t[%s:%s]: REG_DWORD: <invalid>\n"), subkey,
+ name);
+ }
+ break;
+
+ case REG_SZ:
+ pull_reg_sz(talloc_tos(), &value->data, &text);
+ if (!text) {
+ break;
+ }
+ d_printf(_("\t[%s:%s]: REG_SZ: %s\n"), subkey, name, text);
+ break;
+
+ case REG_BINARY:
+ d_printf(_("\t[%s:%s]: REG_BINARY: unknown length value not "
+ "displayed\n"),
+ subkey, name);
+ break;
+
+ case REG_MULTI_SZ: {
+ uint32_t i;
+ const char **values;
+
+ if (!pull_reg_multi_sz(NULL, &value->data, &values)) {
+ d_printf("pull_reg_multi_sz failed\n");
+ break;
+ }
+
+ printf("%s: REG_MULTI_SZ: \n", name);
+ for (i=0; values[i] != NULL; i++) {
+ d_printf("%s\n", values[i]);
+ }
+ TALLOC_FREE(values);
+ break;
+ }
+
+ default:
+ d_printf(_("\t%s: unknown type %d\n"), name, value->type);
+ }
+
+}
+
+/**
+ * Copies ACLs, DOS-attributes and timestamps from one
+ * file or directory from one connected share to another connected share
+ *
+ * @param c A net_context structure
+ * @param mem_ctx A talloc-context
+ * @param cli_share_src A connected cli_state
+ * @param cli_share_dst A connected cli_state
+ * @param src_file The source file-name
+ * @param dst_file The destination file-name
+ * @param copy_acls Whether to copy acls
+ * @param copy_attrs Whether to copy DOS attributes
+ * @param copy_timestamps Whether to preserve timestamps
+ * @param is_file Whether this file is a file or a dir
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS net_copy_fileattr(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct cli_state *cli_share_src,
+ struct cli_state *cli_share_dst,
+ const char *src_name, const char *dst_name,
+ bool copy_acls, bool copy_attrs,
+ bool copy_timestamps, bool is_file)
+{
+ NTSTATUS nt_status;
+ uint16_t fnum_src = 0;
+ uint16_t fnum_dst = 0;
+ struct security_descriptor *sd = NULL;
+ uint32_t attr = (uint32_t)-1;
+ struct timespec f_create_time = { .tv_nsec = SAMBA_UTIME_OMIT };
+ struct timespec f_access_time = { .tv_nsec = SAMBA_UTIME_OMIT };
+ struct timespec f_write_time = { .tv_nsec = SAMBA_UTIME_OMIT };
+ struct timespec f_change_time = { .tv_nsec = SAMBA_UTIME_OMIT };
+
+ if (!copy_timestamps && !copy_acls && !copy_attrs)
+ return NT_STATUS_OK;
+
+ /* open file/dir on the originating server */
+
+ DEBUGADD(3,("opening %s %s on originating server\n",
+ is_file?"file":"dir", src_name));
+
+ nt_status = cli_ntcreate(cli_share_src, src_name, 0,
+ READ_CONTROL_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN,
+ 0x0, 0x0, &fnum_src, NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUGADD(0,("cannot open %s %s on originating server %s\n",
+ is_file?"file":"dir", src_name, nt_errstr(nt_status)));
+ goto out;
+ }
+
+ if (copy_acls) {
+ /* get the security descriptor */
+ nt_status = cli_query_secdesc(cli_share_src, fnum_src,
+ mem_ctx, &sd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("failed to get security descriptor: %s\n",
+ nt_errstr(nt_status)));
+ goto out;
+ }
+
+ if (c->opt_verbose && DEBUGLEVEL >= 3)
+ display_sec_desc(sd);
+ }
+
+ if (copy_attrs || copy_timestamps) {
+
+ /* get file attributes */
+ nt_status = cli_qfileinfo_basic(
+ cli_share_src,
+ fnum_src,
+ copy_attrs ? &attr : NULL,
+ NULL, /* size */
+ copy_timestamps ? &f_create_time : NULL,
+ copy_timestamps ? &f_access_time : NULL,
+ copy_timestamps ? &f_write_time : NULL,
+ copy_timestamps ? &f_change_time : NULL,
+ NULL); /* ino */
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("failed to get file-attrs: %s\n",
+ nt_errstr(nt_status)));
+ goto out;
+ }
+ }
+
+ /* open the file/dir on the destination server */
+ nt_status = cli_ntcreate(cli_share_dst, dst_name, 0,
+ WRITE_DAC_ACCESS | WRITE_OWNER_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN,
+ 0x0, 0x0, &fnum_dst, NULL);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("failed to open %s on the destination server: %s: %s\n",
+ is_file?"file":"dir", dst_name, nt_errstr(nt_status)));
+ goto out;
+ }
+
+ if (copy_acls) {
+ /* set acls */
+ nt_status = cli_set_secdesc(cli_share_dst, fnum_dst, sd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("could not set secdesc on %s: %s\n",
+ dst_name, nt_errstr(nt_status)));
+ goto out;
+ }
+ }
+
+ if (copy_timestamps || copy_attrs) {
+
+ nt_status = cli_setfileinfo_ext(
+ cli_share_dst,
+ fnum_dst,
+ f_create_time,
+ f_access_time,
+ f_write_time,
+ f_change_time,
+ attr);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DBG_ERR("failed to set file-attrs: %s\n",
+ nt_errstr(nt_status));
+ goto out;
+ }
+ }
+
+
+ /* closing files */
+ nt_status = cli_close(cli_share_src, fnum_src);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr,
+ _("could not close %s on originating server: %s\n"),
+ is_file?"file":"dir", nt_errstr(nt_status));
+ goto out;
+ }
+
+ nt_status = cli_close(cli_share_dst, fnum_dst);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr,
+ _("could not close %s on destination server: %s\n"),
+ is_file?"file":"dir", nt_errstr(nt_status));
+ goto out;
+ }
+
+
+ nt_status = NT_STATUS_OK;
+
+out:
+
+ /* cleaning up */
+ if (fnum_src)
+ cli_close(cli_share_src, fnum_src);
+
+ if (fnum_dst)
+ cli_close(cli_share_dst, fnum_dst);
+
+ return nt_status;
+}
+
+/**
+ * Copy a file or directory from a connected share to another connected share
+ *
+ * @param c A net_context structure
+ * @param mem_ctx A talloc-context
+ * @param cli_share_src A connected cli_state
+ * @param cli_share_dst A connected cli_state
+ * @param src_file The source file-name
+ * @param dst_file The destination file-name
+ * @param copy_acls Whether to copy acls
+ * @param copy_attrs Whether to copy DOS attributes
+ * @param copy_timestamps Whether to preserve timestamps
+ * @param is_file Whether this file is a file or a dir
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS net_copy_file(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct cli_state *cli_share_src,
+ struct cli_state *cli_share_dst,
+ const char *src_name, const char *dst_name,
+ bool copy_acls, bool copy_attrs,
+ bool copy_timestamps, bool is_file)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint16_t fnum_src = 0;
+ uint16_t fnum_dst = 0;
+ static int io_bufsize = 64512;
+ int read_size = io_bufsize;
+ char *data = NULL;
+ off_t nread = 0;
+
+
+ if (!src_name || !dst_name)
+ goto out;
+
+ if (cli_share_src == NULL || cli_share_dst == NULL)
+ goto out;
+
+ /* open on the originating server */
+ DEBUGADD(3,("opening %s %s on originating server\n",
+ is_file ? "file":"dir", src_name));
+ if (is_file)
+ nt_status = cli_open(cli_share_src, src_name, O_RDONLY, DENY_NONE, &fnum_src);
+ else
+ nt_status = cli_ntcreate(cli_share_src, src_name, 0, READ_CONTROL_ACCESS, 0,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0x0, 0x0, &fnum_src, NULL);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUGADD(0,("cannot open %s %s on originating server %s\n",
+ is_file ? "file":"dir",
+ src_name, nt_errstr(nt_status)));
+ goto out;
+ }
+
+
+ if (is_file) {
+
+ /* open file on the destination server */
+ DEBUGADD(3,("opening file %s on destination server\n", dst_name));
+ nt_status = cli_open(cli_share_dst, dst_name,
+ O_RDWR|O_CREAT|O_TRUNC, DENY_NONE, &fnum_dst);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUGADD(1,("cannot create file %s on destination server: %s\n",
+ dst_name, nt_errstr(nt_status)));
+ goto out;
+ }
+
+ /* allocate memory */
+ if (!(data = (char *)SMB_MALLOC(read_size))) {
+ d_fprintf(stderr, _("malloc fail for size %d\n"),
+ read_size);
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ }
+
+
+ if (c->opt_verbose) {
+
+ d_printf(_("copying [\\\\%s\\%s%s] => [\\\\%s\\%s%s] "
+ "%s ACLs and %s DOS Attributes %s\n"),
+ smbXcli_conn_remote_name(cli_share_src->conn),
+ cli_share_src->share, src_name,
+ smbXcli_conn_remote_name(cli_share_dst->conn),
+ cli_share_dst->share, dst_name,
+ copy_acls ? _("with") : _("without"),
+ copy_attrs ? _("with") : _("without"),
+ copy_timestamps ? _("(preserving timestamps)") : "" );
+ }
+
+
+ while (is_file) {
+
+ /* copying file */
+ size_t n;
+
+ nt_status = cli_read(cli_share_src, fnum_src, data, nread,
+ read_size, &n);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr,
+ _("Error reading file [\\\\%s\\%s%s]: %s\n"),
+ smbXcli_conn_remote_name(cli_share_src->conn),
+ cli_share_src->share,
+ src_name, nt_errstr(nt_status));
+ goto out;
+ }
+
+ if (n == 0)
+ break;
+
+ nt_status = cli_writeall(cli_share_dst, fnum_dst, 0,
+ (uint8_t *)data, nread, n, NULL);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr,
+ _("Error writing file: [\\\\%s\\%s%s]: %s\n"),
+ smbXcli_conn_remote_name(cli_share_dst->conn),
+ cli_share_dst->share,
+ dst_name, nt_errstr(nt_status));
+ goto out;
+ }
+
+ nread += n;
+ }
+
+
+ if (!is_file && !NT_STATUS_IS_OK(cli_chkpath(cli_share_dst, dst_name))) {
+
+ /* creating dir */
+ DEBUGADD(3,("creating dir %s on the destination server\n",
+ dst_name));
+
+ nt_status = cli_mkdir(cli_share_dst, dst_name);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("cannot create directory %s: %s\n",
+ dst_name, nt_errstr(nt_status)));
+ nt_status = NT_STATUS_NO_SUCH_FILE;
+ }
+
+
+ nt_status = cli_chkpath(cli_share_dst, dst_name);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr,
+ _("cannot check for directory %s: %s\n"),
+ dst_name, nt_errstr(nt_status));
+ goto out;
+ }
+ }
+
+
+ /* closing files */
+ nt_status = cli_close(cli_share_src, fnum_src);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr,
+ _("could not close file on originating server: %s\n"),
+ nt_errstr(nt_status));
+ goto out;
+ }
+
+ if (is_file) {
+ nt_status = cli_close(cli_share_dst, fnum_dst);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr,
+ _("could not close file on destination server: %s\n"),
+ nt_errstr(nt_status));
+ goto out;
+ }
+ }
+
+ /* possibly we have to copy some file-attributes / acls / sd */
+ nt_status = net_copy_fileattr(c, mem_ctx, cli_share_src, cli_share_dst,
+ src_name, dst_name, copy_acls,
+ copy_attrs, copy_timestamps, is_file);
+ if (!NT_STATUS_IS_OK(nt_status))
+ goto out;
+
+
+ nt_status = NT_STATUS_OK;
+
+out:
+
+ /* cleaning up */
+ if (fnum_src)
+ cli_close(cli_share_src, fnum_src);
+
+ if (fnum_dst)
+ cli_close(cli_share_dst, fnum_dst);
+
+ SAFE_FREE(data);
+
+ return nt_status;
+}
+
+/**
+ * Copy a driverfile from on connected share to another connected share
+ * This silently assumes that a driver-file is picked up from
+ *
+ * \\src_server\print$\{arch}\{version}\file
+ *
+ * and copied to
+ *
+ * \\dst_server\print$\{arch}\file
+ *
+ * to be added via setdriver-calls later.
+ * @param c A net_context structure
+ * @param mem_ctx A talloc-context
+ * @param cli_share_src A cli_state connected to source print$-share
+ * @param cli_share_dst A cli_state connected to destination print$-share
+ * @param file The file-name to be copied
+ * @param short_archi The name of the driver-architecture (short form)
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS net_copy_driverfile(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct cli_state *cli_share_src,
+ struct cli_state *cli_share_dst,
+ const char *file, const char *short_archi) {
+
+ const char *p;
+ char *src_name;
+ char *dst_name;
+ char *version = NULL;
+ char *filename = NULL;
+ char *tok;
+
+ if (!file) {
+ return NT_STATUS_OK;
+ }
+
+ /* scroll through the file until we have the part
+ beyond archi_table.short_archi */
+ p = file;
+ while (next_token_talloc(mem_ctx, &p, &tok, "\\")) {
+ if (strequal(tok, short_archi)) {
+ next_token_talloc(mem_ctx, &p, &version, "\\");
+ next_token_talloc(mem_ctx, &p, &filename, "\\");
+ }
+ }
+
+ if (version == NULL || filename == NULL) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* build source file name */
+ src_name = talloc_asprintf(mem_ctx, "\\%s\\%s\\%s",
+ short_archi, version, filename);
+ if (src_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* create destination file name */
+ dst_name = talloc_asprintf(mem_ctx, "\\%s\\%s", short_archi, filename);
+ if (dst_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+
+ /* finally copy the file */
+ return net_copy_file(c, mem_ctx, cli_share_src, cli_share_dst,
+ src_name, dst_name, false, false, false, true);
+}
+
+/**
+ * Check for existing Architecture directory on a given server
+ *
+ * @param cli_share A cli_state connected to a print$-share
+ * @param short_archi The Architecture for the print-driver
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS check_arch_dir(struct cli_state *cli_share, const char *short_archi)
+{
+
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ char *dir;
+
+ if (asprintf(&dir, "\\%s", short_archi) < 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10,("creating print-driver dir for architecture: %s\n",
+ short_archi));
+
+ nt_status = cli_mkdir(cli_share, dir);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1,("cannot create directory %s: %s\n",
+ dir, nt_errstr(nt_status)));
+ }
+
+ nt_status = cli_chkpath(cli_share, dir);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr, _("cannot check %s: %s\n"),
+ dir, nt_errstr(nt_status));
+ goto out;
+ }
+
+ nt_status = NT_STATUS_OK;
+
+out:
+ SAFE_FREE(dir);
+ return nt_status;
+}
+
+/**
+ * Copy a print-driver (level 3) from one connected print$-share to another
+ * connected print$-share
+ *
+ * @param c A net_context structure
+ * @param mem_ctx A talloc-context
+ * @param cli_share_src A cli_state connected to a print$-share
+ * @param cli_share_dst A cli_state connected to a print$-share
+ * @param short_archi The Architecture for the print-driver
+ * @param i1 The DRIVER_INFO_3-struct
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS copy_print_driver_3(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct cli_state *cli_share_src,
+ struct cli_state *cli_share_dst,
+ const char *short_archi,
+ struct spoolss_DriverInfo3 *r)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ int i;
+
+ if (r == NULL) {
+ return nt_status;
+ }
+
+ if (c->opt_verbose)
+ d_printf(_("copying driver: [%s], for architecture: [%s], "
+ "version: [%d]\n"),
+ r->driver_name, short_archi, r->version);
+
+ nt_status = net_copy_driverfile(c, mem_ctx, cli_share_src, cli_share_dst,
+ r->driver_path, short_archi);
+ if (!NT_STATUS_IS_OK(nt_status))
+ return nt_status;
+
+ nt_status = net_copy_driverfile(c, mem_ctx, cli_share_src, cli_share_dst,
+ r->data_file, short_archi);
+ if (!NT_STATUS_IS_OK(nt_status))
+ return nt_status;
+
+ nt_status = net_copy_driverfile(c, mem_ctx, cli_share_src, cli_share_dst,
+ r->config_file, short_archi);
+ if (!NT_STATUS_IS_OK(nt_status))
+ return nt_status;
+
+ nt_status = net_copy_driverfile(c, mem_ctx, cli_share_src, cli_share_dst,
+ r->help_file, short_archi);
+ if (!NT_STATUS_IS_OK(nt_status))
+ return nt_status;
+
+ for (i=0; r->dependent_files[i] != NULL; i++) {
+
+ nt_status = net_copy_driverfile(c, mem_ctx,
+ cli_share_src, cli_share_dst,
+ r->dependent_files[i], short_archi);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * net_spoolss-functions
+ * =====================
+ *
+ * the net_spoolss-functions aim to simplify spoolss-client-functions
+ * required during the migration-process wrt buffer-sizes, returned
+ * error-codes, etc.
+ *
+ * this greatly reduces the complexitiy of the migrate-functions.
+ *
+ **/
+
+static bool net_spoolss_enum_printers(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ char *name,
+ uint32_t flags,
+ uint32_t level,
+ uint32_t *num_printers,
+ union spoolss_PrinterInfo **info)
+{
+ WERROR result;
+
+ /* enum printers */
+
+ result = rpccli_spoolss_enumprinters(pipe_hnd, mem_ctx,
+ flags,
+ name,
+ level,
+ 0,
+ num_printers,
+ info);
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("cannot enum printers: %s\n"), win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+static bool net_spoolss_open_printer_ex(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ const char *printername,
+ uint32_t access_required,
+ struct policy_handle *hnd)
+{
+ struct cli_credentials *creds = gensec_get_credentials(pipe_hnd->auth->auth_ctx);
+ const char *username = cli_credentials_get_username(creds);
+ WERROR result;
+ fstring printername2;
+
+ fstrcpy(printername2, pipe_hnd->srv_name_slash);
+ fstrcat(printername2, "\\");
+ fstrcat(printername2, printername);
+
+ DEBUG(10,("connecting to: %s as %s for %s and access: %x\n",
+ pipe_hnd->srv_name_slash, username, printername2, access_required));
+
+ /* open printer */
+ result = rpccli_spoolss_openprinter_ex(pipe_hnd, mem_ctx,
+ printername2,
+ access_required,
+ hnd);
+
+ /* be more verbose */
+ if (W_ERROR_V(result) == W_ERROR_V(WERR_ACCESS_DENIED)) {
+ d_fprintf(stderr,
+ _("no access to printer [%s] on [%s] for user [%s] "
+ "granted\n"),
+ printername2, pipe_hnd->srv_name_slash, username);
+ return false;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ d_fprintf(stderr,_("cannot open printer %s on server %s: %s\n"),
+ printername2, pipe_hnd->srv_name_slash, win_errstr(result));
+ return false;
+ }
+
+ DEBUG(2,("got printer handle for printer: %s, server: %s\n",
+ printername2, pipe_hnd->srv_name_slash));
+
+ return true;
+}
+
+static bool net_spoolss_getprinter(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hnd,
+ uint32_t level,
+ union spoolss_PrinterInfo *info)
+{
+ WERROR result;
+
+ /* getprinter call */
+ result = rpccli_spoolss_getprinter(pipe_hnd, mem_ctx,
+ hnd,
+ level,
+ 0, /* offered */
+ info);
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("cannot get printer-info: %s\n"), win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+static bool net_spoolss_setprinter(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hnd,
+ uint32_t level,
+ union spoolss_PrinterInfo *info)
+{
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ WERROR result;
+ NTSTATUS status;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_SetPrinterInfo2 info2;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+
+ ZERO_STRUCT(devmode_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ /* setprinter call */
+
+ info_ctr.level = level;
+ switch (level) {
+ case 0:
+ info_ctr.info.info0 = (struct spoolss_SetPrinterInfo0 *)
+ (void *)&info->info0;
+ break;
+ case 1:
+ info_ctr.info.info1 = (struct spoolss_SetPrinterInfo1 *)
+ (void *)&info->info1;
+ break;
+ case 2:
+ spoolss_printerinfo2_to_setprinterinfo2(&info->info2, &info2);
+ info_ctr.info.info2 = &info2;
+ break;
+ case 3:
+ info_ctr.info.info3 = (struct spoolss_SetPrinterInfo3 *)
+ (void *)&info->info3;
+ break;
+ case 4:
+ info_ctr.info.info4 = (struct spoolss_SetPrinterInfo4 *)
+ (void *)&info->info4;
+ break;
+ case 5:
+ info_ctr.info.info5 = (struct spoolss_SetPrinterInfo5 *)
+ (void *)&info->info5;
+ break;
+ case 6:
+ info_ctr.info.info6 = (struct spoolss_SetPrinterInfo6 *)
+ (void *)&info->info6;
+ break;
+ case 7:
+ info_ctr.info.info7 = (struct spoolss_SetPrinterInfo7 *)
+ (void *)&info->info7;
+ break;
+#if 0 /* FIXME GD */
+ case 8:
+ info_ctr.info.info8 = (struct spoolss_SetPrinterInfo8 *)
+ (void *)&info->info8;
+ break;
+ case 9:
+ info_ctr.info.info9 = (struct spoolss_SetPrinterInfo9 *)
+ (void *)&info->info9;
+ break;
+#endif
+ default:
+ break; /* FIXME */
+ }
+
+ status = dcerpc_spoolss_SetPrinter(b, mem_ctx,
+ hnd,
+ &info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ 0, /* command */
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf(_("cannot set printer-info: %s\n"), nt_errstr(status));
+ return false;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("cannot set printer-info: %s\n"), win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+
+static bool net_spoolss_setprinterdata(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hnd,
+ const char *value_name,
+ enum winreg_Type type,
+ uint8_t *data,
+ uint32_t offered)
+{
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ WERROR result;
+ NTSTATUS status;
+
+ /* setprinterdata call */
+ status = dcerpc_spoolss_SetPrinterData(b, mem_ctx,
+ hnd,
+ value_name,
+ type,
+ data,
+ offered,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf (_("unable to set printerdata: %s\n"),
+ nt_errstr(status));
+ return false;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf (_("unable to set printerdata: %s\n"),
+ win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+
+static bool net_spoolss_enumprinterkey(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hnd,
+ const char *keyname,
+ const char ***keylist)
+{
+ WERROR result;
+
+ /* enumprinterkey call */
+ result = rpccli_spoolss_enumprinterkey(pipe_hnd, mem_ctx, hnd, keyname, keylist, 0);
+
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("enumprinterkey failed: %s\n"), win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+static bool net_spoolss_enumprinterdataex(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ uint32_t offered,
+ struct policy_handle *hnd,
+ const char *keyname,
+ uint32_t *count,
+ struct spoolss_PrinterEnumValues **info)
+{
+ WERROR result;
+
+ /* enumprinterdataex call */
+ result = rpccli_spoolss_enumprinterdataex(pipe_hnd, mem_ctx,
+ hnd,
+ keyname,
+ 0, /* offered */
+ count,
+ info);
+
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("enumprinterdataex failed: %s\n"), win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+
+static bool net_spoolss_setprinterdataex(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hnd,
+ const char *keyname,
+ const char *name,
+ struct registry_value *value)
+{
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ WERROR result;
+ NTSTATUS status;
+
+ /* setprinterdataex call */
+ status = dcerpc_spoolss_SetPrinterDataEx(b, mem_ctx,
+ hnd,
+ keyname,
+ name,
+ value->type,
+ value->data.data,
+ value->data.length,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf(_("could not set printerdataex: %s\n"),
+ nt_errstr(status));
+ return false;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("could not set printerdataex: %s\n"),
+ win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+static bool net_spoolss_enumforms(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hnd,
+ int level,
+ uint32_t *num_forms,
+ union spoolss_FormInfo **forms)
+{
+ WERROR result;
+
+ /* enumforms call */
+ result = rpccli_spoolss_enumforms(pipe_hnd, mem_ctx,
+ hnd,
+ level,
+ 0,
+ num_forms,
+ forms);
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("could not enum forms: %s\n"), win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+static bool net_spoolss_enumprinterdrivers (struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ uint32_t level, const char *env,
+ uint32_t *count,
+ union spoolss_DriverInfo **info)
+{
+ WERROR result;
+
+ /* enumprinterdrivers call */
+ result = rpccli_spoolss_enumprinterdrivers(pipe_hnd, mem_ctx,
+ pipe_hnd->srv_name_slash,
+ env,
+ level,
+ 0,
+ count,
+ info);
+ if (!W_ERROR_IS_OK(result)) {
+ if (W_ERROR_V(result) != W_ERROR_V(WERR_INVALID_ENVIRONMENT)) {
+ printf(_("cannot enum drivers for environment %s: %s\n"), env,
+ win_errstr(result));
+ return false;
+ } else {
+ printf(_("Server does not support environment [%s]\n"),
+ env);
+ }
+ }
+
+ return true;
+}
+
+static bool net_spoolss_getprinterdriver(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hnd, uint32_t level,
+ const char *env, int version,
+ union spoolss_DriverInfo *info)
+{
+ WERROR result;
+ uint32_t server_major_version;
+ uint32_t server_minor_version;
+
+ /* getprinterdriver call */
+ result = rpccli_spoolss_getprinterdriver2(pipe_hnd, mem_ctx,
+ hnd,
+ env,
+ level,
+ 0,
+ version,
+ 2,
+ info,
+ &server_major_version,
+ &server_minor_version);
+ if (!W_ERROR_IS_OK(result)) {
+ DEBUG(1,("cannot get driver (for architecture: %s): %s\n",
+ env, win_errstr(result)));
+ if (W_ERROR_V(result) != W_ERROR_V(WERR_UNKNOWN_PRINTER_DRIVER) &&
+ W_ERROR_V(result) != W_ERROR_V(WERR_INVALID_ENVIRONMENT)) {
+ printf(_("cannot get driver: %s\n"),
+ win_errstr(result));
+ }
+ return false;
+ }
+
+ return true;
+}
+
+
+static bool net_spoolss_addprinterdriver(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx, uint32_t level,
+ union spoolss_DriverInfo *info)
+{
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ WERROR result;
+ NTSTATUS status;
+ struct spoolss_AddDriverInfoCtr info_ctr;
+
+ info_ctr.level = level;
+
+ switch (level) {
+ case 2:
+ info_ctr.info.info2 = (struct spoolss_AddDriverInfo2 *)
+ (void *)&info->info2;
+ break;
+ case 3:
+ info_ctr.info.info3 = (struct spoolss_AddDriverInfo3 *)
+ (void *)&info->info3;
+ break;
+ default:
+ printf(_("unsupported info level: %d\n"), level);
+ return false;
+ }
+
+ /* addprinterdriver call */
+ status = dcerpc_spoolss_AddPrinterDriver(b, mem_ctx,
+ pipe_hnd->srv_name_slash,
+ &info_ctr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf(_("cannot add driver: %s\n"), nt_errstr(status));
+ return false;
+ }
+ /* be more verbose */
+ if (W_ERROR_V(result) == W_ERROR_V(WERR_ACCESS_DENIED)) {
+ printf(_("You are not allowed to add drivers\n"));
+ return false;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ printf(_("cannot add driver: %s\n"), win_errstr(result));
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * abstraction function to get uint32_t num_printers and PRINTER_INFO_CTR ctr
+ * for a single printer or for all printers depending on argc/argv
+ **/
+
+static bool get_printer_info(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int level,
+ int argc,
+ const char **argv,
+ uint32_t *num_printers,
+ union spoolss_PrinterInfo **info_p)
+{
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ struct policy_handle hnd;
+ WERROR werr;
+
+ /* no arguments given, enumerate all printers */
+ if (argc == 0) {
+
+ if (!net_spoolss_enum_printers(pipe_hnd, mem_ctx, NULL,
+ PRINTER_ENUM_LOCAL|PRINTER_ENUM_SHARED,
+ level, num_printers, info_p))
+ return false;
+
+ goto out;
+ }
+
+ /* argument given, get a single printer by name */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, argv[0],
+ MAXIMUM_ALLOWED_ACCESS,
+ &hnd))
+ return false;
+
+ *info_p = talloc_zero(mem_ctx, union spoolss_PrinterInfo);
+ if (*info_p == NULL) {
+ return false;
+ }
+
+ if (!net_spoolss_getprinter(pipe_hnd, mem_ctx, &hnd, level, *info_p)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &werr);
+ return false;
+ }
+
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &werr);
+
+ *num_printers = 1;
+
+out:
+ DEBUG(3,("got %d printers\n", *num_printers));
+
+ return true;
+
+}
+
+/**
+ * List print-queues (including local printers that are not shared)
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_list_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i, num_printers;
+ uint32_t level = 2;
+ const char *printername, *sharename;
+ union spoolss_PrinterInfo *info;
+
+ printf("listing printers\n");
+
+ if (!get_printer_info(pipe_hnd, mem_ctx, level, argc, argv, &num_printers, &info))
+ return nt_status;
+
+ for (i = 0; i < num_printers; i++) {
+
+ /* do some initialization */
+ printername = info[i].info2.printername;
+ sharename = info[i].info2.sharename;
+
+ if (printername && sharename) {
+ d_printf(_("printer %d: %s, shared as: %s\n"),
+ i+1, printername, sharename);
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * List printer-drivers from a server
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_driver_list_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i;
+ uint32_t level = 3;
+ union spoolss_DriverInfo *info;
+
+ printf(_("listing printer-drivers\n"));
+
+ for (i=0; archi_table[i].long_archi!=NULL; i++) {
+
+ uint32_t d, num_drivers;
+
+ /* enum remote drivers */
+ if (!net_spoolss_enumprinterdrivers(pipe_hnd, mem_ctx, level,
+ archi_table[i].long_archi,
+ &num_drivers, &info)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (num_drivers == 0) {
+ d_printf(_("no drivers found on server for "
+ "architecture: [%s].\n"),
+ archi_table[i].long_archi);
+ continue;
+ }
+
+ d_printf(_("got %d printer-drivers for architecture: [%s]\n"),
+ num_drivers, archi_table[i].long_archi);
+
+
+ /* do something for all drivers for architecture */
+ for (d = 0; d < num_drivers; d++) {
+ display_print_driver3(&info[d].info3);
+ }
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ return nt_status;
+
+}
+
+/**
+ * Publish print-queues with args-wrapper
+ *
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ * @param action
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+static NTSTATUS rpc_printer_publish_internals_args(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv,
+ uint32_t action)
+{
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i, num_printers;
+ uint32_t level = 7;
+ const char *printername, *sharename;
+ union spoolss_PrinterInfo *info_enum;
+ union spoolss_PrinterInfo info;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+ struct spoolss_DevmodeContainer devmode_ctr;
+ struct sec_desc_buf secdesc_ctr;
+ struct policy_handle hnd = { 0, };
+ WERROR result;
+ const char *action_str;
+
+ if (!get_printer_info(pipe_hnd, mem_ctx, 2, argc, argv, &num_printers, &info_enum))
+ return nt_status;
+
+ for (i = 0; i < num_printers; i++) {
+
+ /* do some initialization */
+ printername = info_enum[i].info2.printername;
+ sharename = info_enum[i].info2.sharename;
+ if (!printername || !sharename) {
+ goto done;
+ }
+
+ /* open printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, sharename,
+ PRINTER_ALL_ACCESS, &hnd))
+ goto done;
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd, mem_ctx, &hnd, level, &info))
+ goto done;
+
+ /* check action and set string */
+ switch (action) {
+ case DSPRINT_PUBLISH:
+ action_str = N_("published");
+ break;
+ case DSPRINT_UPDATE:
+ action_str = N_("updated");
+ break;
+ case DSPRINT_UNPUBLISH:
+ action_str = N_("unpublished");
+ break;
+ default:
+ action_str = N_("unknown action");
+ printf(_("unknown action: %d\n"), action);
+ break;
+ }
+
+ info.info7.action = action;
+ info_ctr.level = 7;
+ info_ctr.info.info7 = (struct spoolss_SetPrinterInfo7 *)
+ (void *)&info.info7;
+
+ ZERO_STRUCT(devmode_ctr);
+ ZERO_STRUCT(secdesc_ctr);
+
+ nt_status = dcerpc_spoolss_SetPrinter(b, mem_ctx,
+ &hnd,
+ &info_ctr,
+ &devmode_ctr,
+ &secdesc_ctr,
+ 0, /* command */
+ &result);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf(_("cannot set printer-info: %s\n"),
+ nt_errstr(nt_status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result) && !W_ERROR_EQUAL(result, WERR_IO_PENDING)) {
+ if ((action == DSPRINT_UPDATE) && W_ERROR_EQUAL(result, W_ERROR(0x80070002))) {
+ printf(_("printer not published yet\n"));
+ } else {
+ printf(_("cannot set printer-info: %s\n"),
+ win_errstr(result));
+ }
+ nt_status = werror_to_ntstatus(result);
+ goto done;
+ }
+
+ printf(_("successfully %s printer %s in Active Directory\n"),
+ action_str, sharename);
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ if (is_valid_policy_hnd(&hnd)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &result);
+ }
+
+ return nt_status;
+}
+
+NTSTATUS rpc_printer_publish_publish_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ return rpc_printer_publish_internals_args(pipe_hnd, mem_ctx, argc, argv, DSPRINT_PUBLISH);
+}
+
+NTSTATUS rpc_printer_publish_unpublish_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ return rpc_printer_publish_internals_args(pipe_hnd, mem_ctx, argc, argv, DSPRINT_UNPUBLISH);
+}
+
+NTSTATUS rpc_printer_publish_update_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ return rpc_printer_publish_internals_args(pipe_hnd, mem_ctx, argc, argv, DSPRINT_UPDATE);
+}
+
+/**
+ * List print-queues w.r.t. their publishing state
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_publish_list_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i, num_printers;
+ uint32_t level = 7;
+ const char *printername, *sharename;
+ union spoolss_PrinterInfo *info_enum;
+ union spoolss_PrinterInfo info;
+ struct policy_handle hnd = { 0, };
+ int state;
+ WERROR werr;
+
+ if (!get_printer_info(pipe_hnd, mem_ctx, 2, argc, argv, &num_printers, &info_enum))
+ return nt_status;
+
+ for (i = 0; i < num_printers; i++) {
+
+ /* do some initialization */
+ printername = info_enum[i].info2.printername;
+ sharename = info_enum[i].info2.sharename;
+
+ if (!printername || !sharename) {
+ goto done;
+ }
+
+ /* open printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, sharename,
+ PRINTER_ALL_ACCESS, &hnd))
+ goto done;
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd, mem_ctx, &hnd, level, &info))
+ goto done;
+
+ if (!info.info7.guid) {
+ goto done;
+ }
+ state = info.info7.action;
+ switch (state) {
+ case DSPRINT_PUBLISH:
+ printf(_("printer [%s] is published"),
+ sharename);
+ if (c->opt_verbose)
+ printf(_(", guid: %s"),info.info7.guid);
+ printf("\n");
+ break;
+ case DSPRINT_UNPUBLISH:
+ printf(_("printer [%s] is unpublished\n"),
+ sharename);
+ break;
+ case DSPRINT_UPDATE:
+ printf(_("printer [%s] is currently updating\n"),
+ sharename);
+ break;
+ default:
+ printf(_("unknown state: %d\n"), state);
+ break;
+ }
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ if (is_valid_policy_hnd(&hnd)) {
+ dcerpc_spoolss_ClosePrinter(b, mem_ctx, &hnd, &werr);
+ }
+
+ return nt_status;
+}
+
+/**
+ * Migrate Printer-ACLs from a source server to the destination server
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_migrate_security_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b_src = pipe_hnd->binding_handle;
+ /* TODO: what now, info2 or info3 ?
+ convince jerry that we should add clientside setacls level 3 at least
+ */
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i = 0;
+ uint32_t num_printers;
+ uint32_t level = 2;
+ const char *printername, *sharename;
+ struct rpc_pipe_client *pipe_hnd_dst = NULL;
+ struct dcerpc_binding_handle *b_dst = NULL;
+ struct policy_handle hnd_src = { 0, };
+ struct policy_handle hnd_dst = { 0, };
+ union spoolss_PrinterInfo *info_enum;
+ struct cli_state *cli_dst = NULL;
+ union spoolss_PrinterInfo info_src, info_dst;
+ WERROR werr;
+
+ DEBUG(3,("copying printer ACLs\n"));
+
+ /* connect destination PI_SPOOLSS */
+ nt_status = connect_dst_pipe(c, &cli_dst, &pipe_hnd_dst,
+ &ndr_table_spoolss);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ b_dst = pipe_hnd_dst->binding_handle;
+
+ /* enum source printers */
+ if (!get_printer_info(pipe_hnd, mem_ctx, level, argc, argv, &num_printers, &info_enum)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (!num_printers) {
+ printf (_("no printers found on server.\n"));
+ nt_status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /* do something for all printers */
+ for (i = 0; i < num_printers; i++) {
+
+ /* do some initialization */
+ printername = info_enum[i].info2.printername;
+ sharename = info_enum[i].info2.sharename;
+
+ if (!printername || !sharename) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* we can reset NT_STATUS here because we do not
+ get any real NT_STATUS-codes anymore from now on */
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ d_printf(_("migrating printer ACLs for: [%s] / [%s]\n"),
+ printername, sharename);
+
+ /* according to msdn you have specify these access-rights
+ to see the security descriptor
+ - READ_CONTROL (DACL)
+ - ACCESS_SYSTEM_SECURITY (SACL)
+ */
+
+ /* open src printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, sharename,
+ MAXIMUM_ALLOWED_ACCESS, &hnd_src))
+ goto done;
+
+ /* open dst printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd_dst, mem_ctx, sharename,
+ PRINTER_ALL_ACCESS, &hnd_dst))
+ goto done;
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, level, &info_dst))
+ goto done;
+
+ /* check for existing src printer */
+ if (!net_spoolss_getprinter(pipe_hnd, mem_ctx, &hnd_src, 3, &info_src))
+ goto done;
+
+ /* Copy Security Descriptor */
+
+ /* copy secdesc (info level 2) */
+ info_dst.info2.devmode = NULL;
+ if (info_src.info3.secdesc == NULL) {
+ info_dst.info2.secdesc = NULL;
+ } else {
+ info_dst.info2.secdesc
+ = security_descriptor_copy(mem_ctx,
+ info_src.info3.secdesc);
+ if (info_dst.info2.secdesc == NULL) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ if (c->opt_verbose)
+ display_sec_desc(info_dst.info2.secdesc);
+
+ if (!net_spoolss_setprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, 2, &info_dst))
+ goto done;
+
+ DEBUGADD(1,("\tSetPrinter of SECDESC succeeded\n"));
+
+
+ /* close printer handles here */
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &werr);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &werr);
+ }
+
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &werr);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &werr);
+ }
+
+ if (cli_dst) {
+ cli_shutdown(cli_dst);
+ }
+ return nt_status;
+}
+
+/**
+ * Migrate printer-forms from a src server to the dst server
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_migrate_forms_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b_src = pipe_hnd->binding_handle;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ WERROR result;
+ uint32_t i, f;
+ uint32_t num_printers;
+ uint32_t level = 1;
+ const char *printername, *sharename;
+ struct rpc_pipe_client *pipe_hnd_dst = NULL;
+ struct dcerpc_binding_handle *b_dst = NULL;
+ struct policy_handle hnd_src = { 0, };
+ struct policy_handle hnd_dst = { 0, };
+ union spoolss_PrinterInfo *info_enum;
+ union spoolss_PrinterInfo info_dst;
+ uint32_t num_forms;
+ union spoolss_FormInfo *forms;
+ struct cli_state *cli_dst = NULL;
+
+ DEBUG(3,("copying forms\n"));
+
+ /* connect destination PI_SPOOLSS */
+ nt_status = connect_dst_pipe(c, &cli_dst, &pipe_hnd_dst,
+ &ndr_table_spoolss);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ b_dst = pipe_hnd_dst->binding_handle;
+
+ /* enum src printers */
+ if (!get_printer_info(pipe_hnd, mem_ctx, 2, argc, argv, &num_printers, &info_enum)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (!num_printers) {
+ printf (_("no printers found on server.\n"));
+ nt_status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /* do something for all printers */
+ for (i = 0; i < num_printers; i++) {
+
+ /* do some initialization */
+ printername = info_enum[i].info2.printername;
+ sharename = info_enum[i].info2.sharename;
+
+ if (!printername || !sharename) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ /* we can reset NT_STATUS here because we do not
+ get any real NT_STATUS-codes anymore from now on */
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ d_printf(_("migrating printer forms for: [%s] / [%s]\n"),
+ printername, sharename);
+
+
+ /* open src printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, sharename,
+ MAXIMUM_ALLOWED_ACCESS, &hnd_src))
+ goto done;
+
+ /* open dst printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd_dst, mem_ctx, sharename,
+ PRINTER_ALL_ACCESS, &hnd_dst))
+ goto done;
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, level, &info_dst))
+ goto done;
+
+ /* finally migrate forms */
+ if (!net_spoolss_enumforms(pipe_hnd, mem_ctx, &hnd_src, level, &num_forms, &forms))
+ goto done;
+
+ DEBUG(1,("got %d forms for printer\n", num_forms));
+
+
+ for (f = 0; f < num_forms; f++) {
+
+ struct spoolss_AddFormInfoCtr info_ctr;
+ NTSTATUS status;
+
+ /* only migrate FORM_PRINTER types, according to jerry
+ FORM_BUILTIN-types are hard-coded in samba */
+ if (forms[f].info1.flags != SPOOLSS_FORM_PRINTER)
+ continue;
+
+ if (c->opt_verbose)
+ d_printf(_("\tmigrating form # %d [%s] of type "
+ "[%d]\n"),
+ f, forms[f].info1.form_name,
+ forms[f].info1.flags);
+ info_ctr.level = 1;
+ info_ctr.info.info1 = (struct spoolss_AddFormInfo1 *)
+ (void *)&forms[f].info1;
+
+ /* FIXME: there might be something wrong with samba's
+ builtin-forms */
+ status = dcerpc_spoolss_AddForm(b_dst, mem_ctx,
+ &hnd_dst,
+ &info_ctr,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(_("\tdcerpc_spoolss_AddForm form %d: [%s] - %s\n"),
+ f, forms[f].info1.form_name, nt_errstr(status));
+ continue;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ d_printf(_("\tAddForm form %d: [%s] refused.\n"),
+ f, forms[f].info1.form_name);
+ continue;
+ }
+
+ DEBUGADD(1,("\tAddForm of [%s] succeeded\n",
+ forms[f].info1.form_name));
+ }
+
+
+ /* close printer handles here */
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &result);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &result);
+ }
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &result);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &result);
+ }
+
+ if (cli_dst) {
+ cli_shutdown(cli_dst);
+ }
+ return nt_status;
+}
+
+/**
+ * Migrate printer-drivers from a src server to the dst server
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_migrate_drivers_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b_src = pipe_hnd->binding_handle;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i, p;
+ uint32_t num_printers;
+ uint32_t level = 3;
+ const char *printername, *sharename;
+ bool got_src_driver_share = false;
+ bool got_dst_driver_share = false;
+ struct rpc_pipe_client *pipe_hnd_dst = NULL;
+ struct dcerpc_binding_handle *b_dst = NULL;
+ struct policy_handle hnd_src = { 0, };
+ struct policy_handle hnd_dst = { 0, };
+ union spoolss_DriverInfo drv_info_src;
+ union spoolss_PrinterInfo *info_enum;
+ union spoolss_PrinterInfo info_dst;
+ struct cli_state *cli_dst = NULL;
+ struct cli_state *cli_share_src = NULL;
+ struct cli_state *cli_share_dst = NULL;
+ const char *drivername = NULL;
+ WERROR werr;
+
+ DEBUG(3,("copying printer-drivers\n"));
+
+ nt_status = connect_dst_pipe(c, &cli_dst, &pipe_hnd_dst,
+ &ndr_table_spoolss);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ b_dst = pipe_hnd_dst->binding_handle;
+
+ /* open print$-share on the src server */
+ nt_status = connect_to_service(c, &cli_share_src,
+ smbXcli_conn_remote_sockaddr(cli->conn),
+ smbXcli_conn_remote_name(cli->conn),
+ "print$", "A:");
+ if (!NT_STATUS_IS_OK(nt_status))
+ goto done;
+
+ got_src_driver_share = true;
+
+
+ /* open print$-share on the dst server */
+ nt_status = connect_to_service(c, &cli_share_dst,
+ smbXcli_conn_remote_sockaddr(cli_dst->conn),
+ smbXcli_conn_remote_name(cli_dst->conn),
+ "print$", "A:");
+ if (!NT_STATUS_IS_OK(nt_status))
+ return nt_status;
+
+ got_dst_driver_share = true;
+
+
+ /* enum src printers */
+ if (!get_printer_info(pipe_hnd, mem_ctx, 2, argc, argv, &num_printers, &info_enum)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (num_printers == 0) {
+ printf (_("no printers found on server.\n"));
+ nt_status = NT_STATUS_OK;
+ goto done;
+ }
+
+
+ /* do something for all printers */
+ for (p = 0; p < num_printers; p++) {
+
+ /* do some initialization */
+ printername = info_enum[p].info2.printername;
+ sharename = info_enum[p].info2.sharename;
+
+ if (!printername || !sharename) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* we can reset NT_STATUS here because we do not
+ get any real NT_STATUS-codes anymore from now on */
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ d_printf(_("migrating printer driver for: [%s] / [%s]\n"),
+ printername, sharename);
+
+ /* open dst printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd_dst, mem_ctx, sharename,
+ PRINTER_ALL_ACCESS, &hnd_dst))
+ goto done;
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, 2, &info_dst))
+ goto done;
+
+
+ /* open src printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, sharename,
+ MAXIMUM_ALLOWED_ACCESS,
+ &hnd_src))
+ goto done;
+
+ /* in a first step call getdriver for each shared printer (per arch)
+ to get a list of all files that have to be copied */
+
+ for (i=0; archi_table[i].long_archi!=NULL; i++) {
+
+ /* getdriver src */
+ if (!net_spoolss_getprinterdriver(pipe_hnd, mem_ctx, &hnd_src,
+ level, archi_table[i].long_archi,
+ archi_table[i].version, &drv_info_src))
+ continue;
+
+ drivername = drv_info_src.info3.driver_name;
+
+ if (c->opt_verbose)
+ display_print_driver3(&drv_info_src.info3);
+
+ /* check arch dir */
+ nt_status = check_arch_dir(cli_share_dst, archi_table[i].short_archi);
+ if (!NT_STATUS_IS_OK(nt_status))
+ goto done;
+
+
+ /* copy driver-files */
+ nt_status = copy_print_driver_3(c, mem_ctx, cli_share_src, cli_share_dst,
+ archi_table[i].short_archi,
+ &drv_info_src.info3);
+ if (!NT_STATUS_IS_OK(nt_status))
+ goto done;
+
+
+ /* adddriver dst */
+ if (!net_spoolss_addprinterdriver(pipe_hnd_dst, mem_ctx, level, &drv_info_src)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ DEBUGADD(1,("Successfully added driver [%s] for printer [%s]\n",
+ drivername, printername));
+
+ }
+
+ if (!drivername || strlen(drivername) == 0) {
+ DEBUGADD(1,("Did not get driver for printer %s\n",
+ printername));
+ goto done;
+ }
+
+ /* setdriver dst */
+ info_dst.info2.drivername = drivername;
+
+ if (!net_spoolss_setprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, 2, &info_dst)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ DEBUGADD(1,("Successfully set driver %s for printer %s\n",
+ drivername, printername));
+
+ /* close dst */
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &werr);
+ }
+
+ /* close src */
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &werr);
+ }
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &werr);
+ }
+
+ /* close src */
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &werr);
+ }
+
+ if (cli_dst) {
+ cli_shutdown(cli_dst);
+ }
+
+ if (got_src_driver_share)
+ cli_shutdown(cli_share_src);
+
+ if (got_dst_driver_share)
+ cli_shutdown(cli_share_dst);
+
+ return nt_status;
+
+}
+
+/**
+ * Migrate printer-queues from a src to the dst server
+ * (requires a working "addprinter command" to be installed for the local smbd)
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_migrate_printers_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b_src = pipe_hnd->binding_handle;
+ WERROR result;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i = 0, num_printers;
+ uint32_t level = 2;
+ union spoolss_PrinterInfo info_dst, info_src;
+ union spoolss_PrinterInfo *info_enum;
+ struct cli_state *cli_dst = NULL;
+ struct policy_handle hnd_src = { 0, };
+ struct policy_handle hnd_dst = { 0, };
+ const char *printername, *sharename;
+ struct rpc_pipe_client *pipe_hnd_dst = NULL;
+ struct dcerpc_binding_handle *b_dst = NULL;
+ struct spoolss_SetPrinterInfoCtr info_ctr;
+
+ DEBUG(3,("copying printers\n"));
+
+ /* connect destination PI_SPOOLSS */
+ nt_status = connect_dst_pipe(c, &cli_dst, &pipe_hnd_dst,
+ &ndr_table_spoolss);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ b_dst = pipe_hnd_dst->binding_handle;
+
+ /* enum printers */
+ if (!get_printer_info(pipe_hnd, mem_ctx, level, argc, argv, &num_printers, &info_enum)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (!num_printers) {
+ printf (_("no printers found on server.\n"));
+ nt_status = NT_STATUS_OK;
+ goto done;
+ }
+
+ /* do something for all printers */
+ for (i = 0; i < num_printers; i++) {
+
+ struct spoolss_SetPrinterInfo2 info2;
+
+ /* do some initialization */
+ printername = info_enum[i].info2.printername;
+ sharename = info_enum[i].info2.sharename;
+
+ if (!printername || !sharename) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ /* we can reset NT_STATUS here because we do not
+ get any real NT_STATUS-codes anymore from now on */
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ d_printf(_("migrating printer queue for: [%s] / [%s]\n"),
+ printername, sharename);
+
+ /* open dst printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd_dst, mem_ctx, sharename,
+ PRINTER_ALL_ACCESS, &hnd_dst)) {
+
+ DEBUG(1,("could not open printer: %s\n", sharename));
+ }
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, level, &info_dst)) {
+ printf (_("could not get printer, creating printer.\n"));
+ } else {
+ DEBUG(1,("printer already exists: %s\n", sharename));
+ /* close printer handle here - dst only, not got src yet. */
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &result);
+ }
+ continue;
+ }
+
+ /* now get again src printer ctr via getprinter,
+ we first need a handle for that */
+
+ /* open src printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, sharename,
+ MAXIMUM_ALLOWED_ACCESS, &hnd_src))
+ goto done;
+
+ /* getprinter on the src server */
+ if (!net_spoolss_getprinter(pipe_hnd, mem_ctx, &hnd_src, level, &info_src))
+ goto done;
+
+ /* copy each src printer to a dst printer 1:1,
+ maybe some values have to be changed though */
+ d_printf(_("creating printer: %s\n"), printername);
+
+ info_ctr.level = level;
+ spoolss_printerinfo2_to_setprinterinfo2(&info_src.info2, &info2);
+ info_ctr.info.info2 = &info2;
+
+ result = rpccli_spoolss_addprinterex(pipe_hnd_dst,
+ mem_ctx,
+ &info_ctr);
+
+ if (W_ERROR_IS_OK(result))
+ d_printf (_("printer [%s] successfully added.\n"),
+ printername);
+ else if (W_ERROR_V(result) == W_ERROR_V(WERR_PRINTER_ALREADY_EXISTS))
+ d_fprintf (stderr, _("printer [%s] already exists.\n"),
+ printername);
+ else {
+ d_fprintf (stderr, _("could not create printer [%s]\n"),
+ printername);
+ goto done;
+ }
+
+ /* close printer handles here */
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &result);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &result);
+ }
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &result);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &result);
+ }
+
+ if (cli_dst) {
+ cli_shutdown(cli_dst);
+ }
+ return nt_status;
+}
+
+/**
+ * Migrate Printer-Settings from a src server to the dst server
+ * (for this to work, printers and drivers already have to be migrated earlier)
+ *
+ * All parameters are provided by the run_rpc_command function, except for
+ * argc, argv which are passed through.
+ *
+ * @param c A net_context structure
+ * @param domain_sid The domain sid acquired from the remote server
+ * @param cli A cli_state connected to the server.
+ * @param mem_ctx Talloc context, destroyed on completion of the function.
+ * @param argc Standard main() style argc
+ * @param argv Standard main() style argv. Initial components are already
+ * stripped
+ *
+ * @return Normal NTSTATUS return.
+ **/
+
+NTSTATUS rpc_printer_migrate_settings_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct dcerpc_binding_handle *b_src = pipe_hnd->binding_handle;
+
+ /* FIXME: Here the nightmare begins */
+
+ WERROR result;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t i = 0, j = 0;
+ uint32_t num_printers;
+ uint32_t level = 2;
+ const char *printername, *sharename;
+ struct rpc_pipe_client *pipe_hnd_dst = NULL;
+ struct dcerpc_binding_handle *b_dst = NULL;
+ struct policy_handle hnd_src = { 0, };
+ struct policy_handle hnd_dst = { 0, };
+ union spoolss_PrinterInfo *info_enum;
+ union spoolss_PrinterInfo info_dst_publish;
+ union spoolss_PrinterInfo info_dst;
+ struct cli_state *cli_dst = NULL;
+ const char *longname;
+ const char **keylist = NULL;
+
+ /* FIXME GD */
+ ZERO_STRUCT(info_dst_publish);
+
+ DEBUG(3,("copying printer settings\n"));
+
+ /* connect destination PI_SPOOLSS */
+ nt_status = connect_dst_pipe(c, &cli_dst, &pipe_hnd_dst,
+ &ndr_table_spoolss);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+ b_dst = pipe_hnd_dst->binding_handle;
+
+ /* enum src printers */
+ if (!get_printer_info(pipe_hnd, mem_ctx, level, argc, argv, &num_printers, &info_enum)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ if (!num_printers) {
+ printf (_("no printers found on server.\n"));
+ nt_status = NT_STATUS_OK;
+ goto done;
+ }
+
+
+ /* needed for dns-strings in regkeys */
+ longname = get_mydnsfullname();
+ if (!longname) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* do something for all printers */
+ for (i = 0; i < num_printers; i++) {
+
+ uint32_t value_needed;
+ uint32_t data_needed;
+ enum winreg_Type type;
+ struct spoolss_EnumPrinterData r;
+
+ /* do some initialization */
+ printername = info_enum[i].info2.printername;
+ sharename = info_enum[i].info2.sharename;
+
+ if (!printername || !sharename) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ /* we can reset NT_STATUS here because we do not
+ get any real NT_STATUS-codes anymore from now on */
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ d_printf(_("migrating printer settings for: [%s] / [%s]\n"),
+ printername, sharename);
+
+
+ /* open src printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd, mem_ctx, sharename,
+ MAXIMUM_ALLOWED_ACCESS, &hnd_src))
+ goto done;
+
+ /* open dst printer handle */
+ if (!net_spoolss_open_printer_ex(pipe_hnd_dst, mem_ctx, sharename,
+ PRINTER_ALL_ACCESS, &hnd_dst))
+ goto done;
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd_dst, mem_ctx, &hnd_dst,
+ level, &info_dst))
+ goto done;
+
+
+ /* STEP 1: COPY DEVICE-MODE and other
+ PRINTER_INFO_2-attributes
+ */
+
+ info_dst.info2 = info_enum[i].info2;
+
+ /* why is the port always disconnected when the printer
+ is correctly installed (incl. driver ???) */
+ info_dst.info2.portname = SAMBA_PRINTER_PORT_NAME;
+
+ /* check if printer is published */
+ if (info_enum[i].info2.attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
+
+ /* check for existing dst printer */
+ if (!net_spoolss_getprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, 7, &info_dst_publish))
+ goto done;
+
+ info_dst_publish.info7.action = DSPRINT_PUBLISH;
+
+ /* ignore false from setprinter due to WERR_IO_PENDING */
+ net_spoolss_setprinter(pipe_hnd_dst, mem_ctx, &hnd_dst, 7, &info_dst_publish);
+
+ DEBUG(3,("republished printer\n"));
+ }
+
+ if (info_enum[i].info2.devmode != NULL) {
+
+ /* copy devmode (info level 2) */
+ info_dst.info2.devmode = info_enum[i].info2.devmode;
+
+ /* do not copy security descriptor (we have another
+ * command for that) */
+ info_dst.info2.secdesc = NULL;
+
+#if 0
+ info_dst.info2.devmode.devicename =
+ talloc_asprintf(mem_ctx, "\\\\%s\\%s",
+ longname, printername);
+ if (!info_dst.info2.devmode.devicename) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+#endif
+ if (!net_spoolss_setprinter(pipe_hnd_dst, mem_ctx, &hnd_dst,
+ level, &info_dst))
+ goto done;
+
+ DEBUGADD(1,("\tSetPrinter of DEVICEMODE succeeded\n"));
+ }
+
+ /* STEP 2: COPY REGISTRY VALUES */
+
+ /* please keep in mind that samba parse_spools gives horribly
+ crippled results when used to rpccli_spoolss_enumprinterdataex
+ a win2k3-server. (Bugzilla #1851)
+ FIXME: IIRC I've seen it too on a win2k-server
+ */
+
+ r.in.handle = &hnd_src;
+ r.in.enum_index = 0;
+ r.in.value_offered = 0;
+ r.in.data_offered = 0;
+ r.out.value_name = NULL;
+ r.out.value_needed = &value_needed;
+ r.out.type = &type;
+ r.out.data = NULL;
+ r.out.data_needed = &data_needed;
+
+ /* enumerate data on src handle */
+ nt_status = dcerpc_spoolss_EnumPrinterData_r(b_src, mem_ctx, &r);
+
+ r.in.data_offered = *r.out.data_needed;
+ r.in.value_offered = *r.out.value_needed;
+ r.out.data = talloc_zero_array(mem_ctx, uint8_t, r.in.data_offered);
+ r.out.value_name = talloc_zero_array(mem_ctx, char, r.in.value_offered);
+
+ /* loop for all printerdata of "PrinterDriverData" */
+ while (NT_STATUS_IS_OK(nt_status) && W_ERROR_IS_OK(r.out.result)) {
+
+ r.in.enum_index++;
+
+ nt_status = dcerpc_spoolss_EnumPrinterData_r(b_src, mem_ctx, &r);
+
+ /* loop for all reg_keys */
+ if (NT_STATUS_IS_OK(nt_status) && W_ERROR_IS_OK(r.out.result)) {
+
+ /* display_value */
+ if (c->opt_verbose) {
+ struct registry_value v;
+ v.type = *r.out.type;
+ v.data = data_blob_const(
+ r.out.data, r.in.data_offered);
+
+ display_reg_value(SPOOL_PRINTERDATA_KEY,
+ r.out.value_name, &v);
+ }
+
+ /* set_value */
+ if (!net_spoolss_setprinterdata(pipe_hnd_dst, mem_ctx,
+ &hnd_dst, r.out.value_name,
+ *r.out.type, r.out.data, r.in.data_offered))
+ goto done;
+
+ DEBUGADD(1,("\tSetPrinterData of [%s] succeeded\n",
+ r.out.value_name));
+ }
+ }
+
+ /* STEP 3: COPY SUBKEY VALUES */
+
+ /* here we need to enum all printer_keys and then work
+ on the result with enum_printer_key_ex. nt4 does not
+ respond to enumprinterkey, win2k does, so continue
+ in case of an error */
+
+ if (!net_spoolss_enumprinterkey(pipe_hnd, mem_ctx, &hnd_src, "", &keylist)) {
+ printf(_("got no key-data\n"));
+ continue;
+ }
+
+
+ /* work on a list of printer keys
+ each key has to be enumerated to get all required
+ information. information is then set via setprinterdataex-calls */
+
+ if (keylist == NULL)
+ continue;
+
+ for (i=0; keylist && keylist[i] != NULL; i++) {
+
+ const char *subkey = keylist[i];
+ uint32_t count;
+ struct spoolss_PrinterEnumValues *info;
+
+ /* enumerate all src subkeys */
+ if (!net_spoolss_enumprinterdataex(pipe_hnd, mem_ctx, 0,
+ &hnd_src, subkey,
+ &count, &info)) {
+ goto done;
+ }
+
+ for (j=0; j < count; j++) {
+
+ struct registry_value value;
+ const char *value_name = info[j].value_name;
+ bool ok;
+
+ value.type = REG_SZ;
+
+ /* although samba replies with sane data in most cases we
+ should try to avoid writing wrong registry data */
+
+ if (strequal(value_name, SPOOL_REG_PORTNAME)) {
+ /* although windows uses a multi-sz, we use a sz */
+ ok = push_reg_sz(mem_ctx, &value.data, SAMBA_PRINTER_PORT_NAME);
+ if (!ok) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+ else if (strequal(value_name, SPOOL_REG_UNCNAME)) {
+ char *unc_name;
+ if (asprintf(&unc_name, "\\\\%s\\%s", longname, sharename) < 0) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ ok = push_reg_sz(mem_ctx, &value.data, unc_name);
+ if (!ok) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ free(unc_name);
+ }
+ else if (strequal(value_name, SPOOL_REG_URL)) {
+ continue;
+#if 0
+ /* FIXME: should we really do that ??? */
+ if (asprintf(&url, "http://%s:631/printers/%s", longname, sharename) < 0) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ push_reg_sz(mem_ctx, NULL, &value.data, url);
+ free(url);
+#endif
+ }
+ else if (strequal(value_name, SPOOL_REG_SERVERNAME)) {
+ ok = push_reg_sz(mem_ctx, &value.data, longname);
+ if (!ok) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+ else if (strequal(value_name, SPOOL_REG_SHORTSERVERNAME)) {
+ ok = push_reg_sz(mem_ctx, &value.data, lp_netbios_name());
+ if (!ok) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+ else {
+ value.type = info[j].type;
+ value.data = *info[j].data;
+ }
+
+ if (c->opt_verbose) {
+ display_reg_value(subkey, value_name, &value);
+ }
+
+ /* here we have to set all subkeys on the dst server */
+ if (!net_spoolss_setprinterdataex(pipe_hnd_dst, mem_ctx, &hnd_dst,
+ subkey, value_name, &value))
+ {
+ goto done;
+ }
+
+ DEBUGADD(1,("\tSetPrinterDataEx of key [%s\\%s] succeeded\n",
+ subkey, info[j].value_name));
+
+ }
+ }
+
+ TALLOC_FREE(keylist);
+
+ /* close printer handles here */
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &result);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &result);
+ }
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ if (is_valid_policy_hnd(&hnd_src)) {
+ dcerpc_spoolss_ClosePrinter(b_src, mem_ctx, &hnd_src, &result);
+ }
+
+ if (is_valid_policy_hnd(&hnd_dst)) {
+ dcerpc_spoolss_ClosePrinter(b_dst, mem_ctx, &hnd_dst, &result);
+ }
+
+ if (cli_dst) {
+ cli_shutdown(cli_dst);
+ }
+ return nt_status;
+}
diff --git a/source3/utils/net_rpc_registry.c b/source3/utils/net_rpc_registry.c
new file mode 100644
index 0000000..cec5459
--- /dev/null
+++ b/source3/utils/net_rpc_registry.c
@@ -0,0 +1,2126 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+
+ Copyright (C) Gerald (Jerry) Carter 2005-2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "rpc_client/rpc_client.h"
+#include "registry.h"
+#include "utils/net.h"
+#include "utils/net_registry_util.h"
+#include "registry/regfio.h"
+#include "../librpc/gen_ndr/ndr_winreg_c.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "registry/reg_format.h"
+#include "registry/reg_import.h"
+#include <assert.h>
+#include "../libcli/security/display_sec.h"
+#include "../libcli/registry/util_reg.h"
+#include "client.h"
+#include "lib/util/smb_strtox.h"
+
+
+/*******************************************************************
+ connect to a registry hive root (open a registry policy)
+*******************************************************************/
+
+static NTSTATUS dcerpc_winreg_Connect(struct dcerpc_binding_handle *b, TALLOC_CTX *mem_ctx,
+ uint32_t reg_type, uint32_t access_mask,
+ struct policy_handle *reg_hnd, WERROR *werr)
+{
+ ZERO_STRUCTP(reg_hnd);
+
+ switch (reg_type)
+ {
+ case HKEY_CLASSES_ROOT:
+ return dcerpc_winreg_OpenHKCR(b, mem_ctx, NULL,
+ access_mask, reg_hnd, werr);
+
+ case HKEY_LOCAL_MACHINE:
+ return dcerpc_winreg_OpenHKLM(b, mem_ctx, NULL,
+ access_mask, reg_hnd, werr);
+
+ case HKEY_USERS:
+ return dcerpc_winreg_OpenHKU(b, mem_ctx, NULL,
+ access_mask, reg_hnd, werr);
+
+ case HKEY_CURRENT_USER:
+ return dcerpc_winreg_OpenHKCU(b, mem_ctx, NULL,
+ access_mask, reg_hnd, werr);
+
+ case HKEY_PERFORMANCE_DATA:
+ return dcerpc_winreg_OpenHKPD(b, mem_ctx, NULL,
+ access_mask, reg_hnd, werr);
+
+ default:
+ /* fall through to end of function */
+ break;
+ }
+
+ return NT_STATUS_INVALID_PARAMETER;
+}
+
+static bool reg_hive_key(TALLOC_CTX *ctx, const char *fullname,
+ uint32_t *reg_type, const char **key_name)
+{
+ WERROR werr;
+ char *hivename = NULL;
+ char *tmp_keyname = NULL;
+ bool ret = false;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ werr = split_hive_key(tmp_ctx, fullname, &hivename, &tmp_keyname);
+ if (!W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+
+ *key_name = talloc_strdup(ctx, tmp_keyname);
+ if (*key_name == NULL) {
+ goto done;
+ }
+
+ if (strequal(hivename, "HKLM") ||
+ strequal(hivename, "HKEY_LOCAL_MACHINE"))
+ {
+ (*reg_type) = HKEY_LOCAL_MACHINE;
+ } else if (strequal(hivename, "HKCR") ||
+ strequal(hivename, "HKEY_CLASSES_ROOT"))
+ {
+ (*reg_type) = HKEY_CLASSES_ROOT;
+ } else if (strequal(hivename, "HKU") ||
+ strequal(hivename, "HKEY_USERS"))
+ {
+ (*reg_type) = HKEY_USERS;
+ } else if (strequal(hivename, "HKCU") ||
+ strequal(hivename, "HKEY_CURRENT_USER"))
+ {
+ (*reg_type) = HKEY_CURRENT_USER;
+ } else if (strequal(hivename, "HKPD") ||
+ strequal(hivename, "HKEY_PERFORMANCE_DATA"))
+ {
+ (*reg_type) = HKEY_PERFORMANCE_DATA;
+ } else {
+ DEBUG(10,("reg_hive_key: unrecognised hive key %s\n",
+ fullname));
+ goto done;
+ }
+
+ ret = true;
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return ret;
+}
+
+static NTSTATUS registry_openkey(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ const char *name, uint32_t access_mask,
+ struct policy_handle *hive_hnd,
+ struct policy_handle *key_hnd)
+{
+ uint32_t hive;
+ NTSTATUS status;
+ WERROR werr;
+ struct winreg_String key;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(key);
+
+ if (!reg_hive_key(mem_ctx, name, &hive, &key.name)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_winreg_Connect(b, mem_ctx, hive, access_mask,
+ hive_hnd, &werr);
+ if (!(NT_STATUS_IS_OK(status))) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+
+ status = dcerpc_winreg_OpenKey(b, mem_ctx, hive_hnd, key, 0,
+ access_mask, key_hnd, &werr);
+ if (!(NT_STATUS_IS_OK(status))) {
+ dcerpc_winreg_CloseKey(b, mem_ctx, hive_hnd, &werr);
+ return status;
+ }
+ if (!(W_ERROR_IS_OK(werr))) {
+ WERROR _werr;
+ dcerpc_winreg_CloseKey(b, mem_ctx, hive_hnd, &_werr);
+ return werror_to_ntstatus(werr);
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS registry_enumkeys(TALLOC_CTX *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *key_hnd,
+ uint32_t *pnum_keys, char ***pnames,
+ char ***pclasses, NTTIME ***pmodtimes)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t num_subkeys, max_subkeylen, max_classlen;
+ uint32_t num_values, max_valnamelen, max_valbufsize;
+ uint32_t i;
+ NTTIME last_changed_time;
+ uint32_t secdescsize;
+ struct winreg_String classname;
+ char **names, **classes;
+ NTTIME **modtimes;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (!(mem_ctx = talloc_new(ctx))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(classname);
+ status = dcerpc_winreg_QueryInfoKey(
+ b, mem_ctx, key_hnd, &classname, &num_subkeys,
+ &max_subkeylen, &max_classlen, &num_values, &max_valnamelen,
+ &max_valbufsize, &secdescsize, &last_changed_time, &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto error;
+ }
+
+ if (num_subkeys == 0) {
+ *pnum_keys = 0;
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ if ((!(names = talloc_zero_array(mem_ctx, char *, num_subkeys))) ||
+ (!(classes = talloc_zero_array(mem_ctx, char *, num_subkeys))) ||
+ (!(modtimes = talloc_zero_array(mem_ctx, NTTIME *,
+ num_subkeys)))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ for (i=0; i<num_subkeys; i++) {
+ char c, n;
+ struct winreg_StringBuf class_buf;
+ struct winreg_StringBuf name_buf;
+ NTTIME modtime;
+
+ c = '\0';
+ class_buf.name = &c;
+ class_buf.size = max_classlen+2;
+
+ n = '\0';
+ name_buf.name = &n;
+ name_buf.size = max_subkeylen+2;
+
+ ZERO_STRUCT(modtime);
+
+ status = dcerpc_winreg_EnumKey(b, mem_ctx, key_hnd,
+ i, &name_buf, &class_buf,
+ &modtime, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ if (W_ERROR_EQUAL(werr,
+ WERR_NO_MORE_ITEMS) ) {
+ status = NT_STATUS_OK;
+ break;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto error;
+ }
+
+ classes[i] = NULL;
+
+ if (class_buf.name &&
+ (!(classes[i] = talloc_strdup(classes, class_buf.name)))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ if (!(names[i] = talloc_strdup(names, name_buf.name))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ if ((!(modtimes[i] = (NTTIME *)talloc_memdup(
+ modtimes, &modtime, sizeof(modtime))))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ }
+
+ *pnum_keys = num_subkeys;
+
+ if (pnames) {
+ *pnames = talloc_move(ctx, &names);
+ }
+ if (pclasses) {
+ *pclasses = talloc_move(ctx, &classes);
+ }
+ if (pmodtimes) {
+ *pmodtimes = talloc_move(ctx, &modtimes);
+ }
+
+ status = NT_STATUS_OK;
+
+ error:
+ TALLOC_FREE(mem_ctx);
+ return status;
+}
+
+static NTSTATUS registry_enumvalues(TALLOC_CTX *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *key_hnd,
+ uint32_t *pnum_values, char ***pvalnames,
+ struct registry_value ***pvalues)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t num_subkeys, max_subkeylen, max_classlen;
+ uint32_t num_values, max_valnamelen, max_valbufsize;
+ uint32_t i;
+ NTTIME last_changed_time;
+ uint32_t secdescsize;
+ struct winreg_String classname;
+ struct registry_value **values;
+ char **names;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (!(mem_ctx = talloc_new(ctx))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(classname);
+ status = dcerpc_winreg_QueryInfoKey(
+ b, mem_ctx, key_hnd, &classname, &num_subkeys,
+ &max_subkeylen, &max_classlen, &num_values, &max_valnamelen,
+ &max_valbufsize, &secdescsize, &last_changed_time, &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto error;
+ }
+
+ if (num_values == 0) {
+ *pnum_values = 0;
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ if ((!(names = talloc_array(mem_ctx, char *, num_values))) ||
+ (!(values = talloc_array(mem_ctx, struct registry_value *,
+ num_values)))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ for (i=0; i<num_values; i++) {
+ enum winreg_Type type = REG_NONE;
+ uint8_t *data = NULL;
+ uint32_t data_size;
+ uint32_t value_length;
+
+ char n;
+ struct winreg_ValNameBuf name_buf;
+ WERROR err;
+
+ n = '\0';
+ name_buf.name = &n;
+ name_buf.size = max_valnamelen + 2;
+
+ data_size = max_valbufsize;
+ data = (uint8_t *)TALLOC(mem_ctx, data_size);
+ value_length = 0;
+
+ status = dcerpc_winreg_EnumValue(b, mem_ctx, key_hnd,
+ i, &name_buf, &type,
+ data, &data_size,
+ &value_length, &err);
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if ( W_ERROR_EQUAL(err,
+ WERR_NO_MORE_ITEMS) ) {
+ status = NT_STATUS_OK;
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(err)) {
+ status = werror_to_ntstatus(err);
+ goto error;
+ }
+
+ if (name_buf.name == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ if (!(names[i] = talloc_strdup(names, name_buf.name))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ values[i] = talloc_zero(values, struct registry_value);
+ if (values[i] == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ values[i]->type = type;
+ values[i]->data = data_blob_talloc(values[i], data, data_size);
+ }
+
+ *pnum_values = num_values;
+
+ if (pvalnames) {
+ *pvalnames = talloc_move(ctx, &names);
+ }
+ if (pvalues) {
+ *pvalues = talloc_move(ctx, &values);
+ }
+
+ status = NT_STATUS_OK;
+
+ error:
+ TALLOC_FREE(mem_ctx);
+ return status;
+}
+
+static NTSTATUS registry_enumvalues2(TALLOC_CTX *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *key_hnd,
+ uint32_t *pnum_values, char ***pvalnames,
+ struct regval_blob ***pvalues)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t num_subkeys, max_subkeylen, max_classlen;
+ uint32_t num_values, max_valnamelen, max_valbufsize;
+ uint32_t i;
+ NTTIME last_changed_time;
+ uint32_t secdescsize;
+ struct winreg_String classname;
+ struct regval_blob **values;
+ char **names;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (!(mem_ctx = talloc_new(ctx))) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCT(classname);
+ status = dcerpc_winreg_QueryInfoKey(
+ b, mem_ctx, key_hnd, &classname, &num_subkeys,
+ &max_subkeylen, &max_classlen, &num_values, &max_valnamelen,
+ &max_valbufsize, &secdescsize, &last_changed_time, &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ goto error;
+ }
+
+ if (num_values == 0) {
+ *pnum_values = 0;
+ TALLOC_FREE(mem_ctx);
+ return NT_STATUS_OK;
+ }
+
+ if ((!(names = talloc_array(mem_ctx, char *, num_values))) ||
+ (!(values = talloc_array(mem_ctx, struct regval_blob *,
+ num_values)))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ for (i=0; i<num_values; i++) {
+ enum winreg_Type type = REG_NONE;
+ uint8_t *data = NULL;
+ uint32_t data_size;
+ uint32_t value_length;
+
+ char n;
+ struct winreg_ValNameBuf name_buf;
+ WERROR err;
+
+ n = '\0';
+ name_buf.name = &n;
+ name_buf.size = max_valnamelen + 2;
+
+ data_size = max_valbufsize;
+ data = (uint8_t *)TALLOC(mem_ctx, data_size);
+ value_length = 0;
+
+ status = dcerpc_winreg_EnumValue(b, mem_ctx, key_hnd,
+ i, &name_buf, &type,
+ data, &data_size,
+ &value_length, &err);
+ if (!(NT_STATUS_IS_OK(status))) {
+ goto error;
+ }
+
+ if ( W_ERROR_EQUAL(err, WERR_NO_MORE_ITEMS) ) {
+ status = NT_STATUS_OK;
+ break;
+ }
+
+ if (!W_ERROR_IS_OK(err)) {
+ status = werror_to_ntstatus(err);
+ goto error;
+ }
+
+ if (name_buf.name == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ if (!(names[i] = talloc_strdup(names, name_buf.name))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ assert(value_length<=data_size); /*??? */
+
+ values[i] = regval_compose(values,
+ name_buf.name,
+ type,
+ data, value_length);
+ if (!values[i]) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ }
+
+ *pnum_values = num_values;
+
+ if (pvalnames) {
+ *pvalnames = talloc_move(ctx, &names);
+ }
+ if (pvalues) {
+ *pvalues = talloc_move(ctx, &values);
+ }
+
+ status = NT_STATUS_OK;
+
+ error:
+ TALLOC_FREE(mem_ctx);
+ return status;
+}
+
+static NTSTATUS registry_getsd(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *b,
+ struct policy_handle *key_hnd,
+ uint32_t sec_info,
+ struct KeySecurityData *sd,
+ WERROR *werr)
+{
+ return dcerpc_winreg_GetKeySecurity(b, mem_ctx, key_hnd,
+ sec_info, sd, werr);
+}
+
+
+static NTSTATUS registry_setvalue(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ struct policy_handle *key_hnd,
+ const char *name,
+ const struct registry_value *value)
+{
+ struct winreg_String name_string;
+ NTSTATUS result;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(name_string);
+
+ name_string.name = name;
+ result = dcerpc_winreg_SetValue(b, mem_ctx, key_hnd,
+ name_string, value->type,
+ value->data.data, value->data.length, &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ return werror_to_ntstatus(werr);
+}
+
+static NTSTATUS rpc_registry_setvalue_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hive_hnd, key_hnd;
+ NTSTATUS status;
+ WERROR werr;
+ struct registry_value value;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = registry_openkey(mem_ctx, pipe_hnd, argv[0],
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hive_hnd, &key_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_openkey failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!strequal(argv[2], "multi_sz") && (argc != 4)) {
+ d_fprintf(stderr, _("Too many args for type %s\n"), argv[2]);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (strequal(argv[2], "dword")) {
+ int error = 0;
+ uint32_t v;
+
+ v = smb_strtoul(argv[3], NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ goto error;
+ }
+
+ value.type = REG_DWORD;
+ value.data = data_blob_talloc(mem_ctx, NULL, 4);
+ SIVAL(value.data.data, 0, v);
+ }
+ else if (strequal(argv[2], "sz")) {
+ value.type = REG_SZ;
+ if (!push_reg_sz(mem_ctx, &value.data, argv[3])) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+ }
+ else {
+ d_fprintf(stderr, _("type \"%s\" not implemented\n"), argv[2]);
+ status = NT_STATUS_NOT_IMPLEMENTED;
+ goto error;
+ }
+
+ status = registry_setvalue(mem_ctx, pipe_hnd, &key_hnd,
+ argv[1], &value);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_setvalue failed: %s\n"),
+ nt_errstr(status));
+ }
+
+ error:
+ dcerpc_winreg_CloseKey(b, mem_ctx, &key_hnd, &werr);
+ dcerpc_winreg_CloseKey(b, mem_ctx, &hive_hnd, &werr);
+
+ return NT_STATUS_OK;
+}
+
+static int rpc_registry_setvalue(struct net_context *c, int argc,
+ const char **argv )
+{
+ if (argc < 4 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net rpc registry setvalue <key> <valuename> "
+ "<type> [<val>]+\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_setvalue_internal, argc, argv );
+}
+
+static NTSTATUS rpc_registry_deletevalue_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hive_hnd, key_hnd;
+ NTSTATUS status;
+ WERROR werr;
+ struct winreg_String valuename;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(valuename);
+
+ status = registry_openkey(mem_ctx, pipe_hnd, argv[0],
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hive_hnd, &key_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_openkey failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ valuename.name = argv[1];
+
+ status = dcerpc_winreg_DeleteValue(b, mem_ctx, &key_hnd,
+ valuename, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_deletevalue failed: %s\n"),
+ nt_errstr(status));
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ d_fprintf(stderr, _("registry_deletevalue failed: %s\n"),
+ win_errstr(werr));
+ }
+
+ dcerpc_winreg_CloseKey(b, mem_ctx, &key_hnd, &werr);
+ dcerpc_winreg_CloseKey(b, mem_ctx, &hive_hnd, &werr);
+
+ return status;
+}
+
+static int rpc_registry_deletevalue(struct net_context *c, int argc,
+ const char **argv )
+{
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net rpc registry deletevalue <key> <valuename>\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_deletevalue_internal, argc, argv );
+}
+
+static NTSTATUS rpc_registry_getvalue_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ bool raw,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle hive_hnd, key_hnd;
+ NTSTATUS status;
+ WERROR werr;
+ struct winreg_String valuename;
+ struct registry_value *value = NULL;
+ enum winreg_Type type = REG_NONE;
+ uint32_t data_size = 0;
+ uint32_t value_length = 0;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(valuename);
+
+ status = registry_openkey(tmp_ctx, pipe_hnd, argv[0],
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hive_hnd, &key_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_openkey failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ valuename.name = argv[1];
+
+ value = talloc_zero(tmp_ctx, struct registry_value);
+ if (value == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * call QueryValue once with data == NULL to get the
+ * needed memory size to be allocated, then allocate
+ * data buffer and call again.
+ */
+ status = dcerpc_winreg_QueryValue(b, tmp_ctx, &key_hnd,
+ &valuename,
+ &type,
+ NULL,
+ &data_size,
+ &value_length,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_queryvalue failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ d_fprintf(stderr, _("registry_queryvalue failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ value->data = data_blob_talloc(tmp_ctx, NULL, data_size);
+
+ status = dcerpc_winreg_QueryValue(b, tmp_ctx, &key_hnd,
+ &valuename,
+ &type,
+ value->data.data,
+ &data_size,
+ &value_length,
+ &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_queryvalue failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ d_fprintf(stderr, _("registry_queryvalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+
+ value->type = type;
+
+ print_registry_value(value, raw);
+
+done:
+ dcerpc_winreg_CloseKey(b, tmp_ctx, &key_hnd, &werr);
+ dcerpc_winreg_CloseKey(b, tmp_ctx, &hive_hnd, &werr);
+
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+static NTSTATUS rpc_registry_getvalue_full(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ return rpc_registry_getvalue_internal(c, domain_sid, domain_name,
+ cli, pipe_hnd, mem_ctx, false,
+ argc, argv);
+}
+
+static int rpc_registry_getvalue(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net rpc registry getvalue <key> <valuename>\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_getvalue_full, argc, argv);
+}
+
+static NTSTATUS rpc_registry_getvalue_raw(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ return rpc_registry_getvalue_internal(c, domain_sid, domain_name,
+ cli, pipe_hnd, mem_ctx, true,
+ argc, argv);
+}
+
+static int rpc_registry_getvalueraw(struct net_context *c, int argc,
+ const char **argv)
+{
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net rpc registry getvalue <key> <valuename>\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_getvalue_raw, argc, argv);
+}
+
+static NTSTATUS rpc_registry_createkey_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ uint32_t hive;
+ struct policy_handle hive_hnd, key_hnd;
+ struct winreg_String key, keyclass;
+ enum winreg_CreateAction action;
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(key);
+ ZERO_STRUCT(keyclass);
+
+ if (!reg_hive_key(mem_ctx, argv[0], &hive, &key.name)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_winreg_Connect(b, mem_ctx, hive,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hive_hnd, &werr);
+ if (!(NT_STATUS_IS_OK(status))) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+
+ action = REG_ACTION_NONE;
+ keyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(b, mem_ctx, &hive_hnd, key,
+ keyclass, 0, REG_KEY_READ, NULL,
+ &key_hnd, &action, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("createkey returned %s\n"),
+ nt_errstr(status));
+ dcerpc_winreg_CloseKey(b, mem_ctx, &hive_hnd, &werr);
+ return status;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ WERROR _werr;
+ d_fprintf(stderr, _("createkey returned %s\n"),
+ win_errstr(werr));
+ dcerpc_winreg_CloseKey(b, mem_ctx, &hive_hnd, &_werr);
+ return werror_to_ntstatus(werr);
+ }
+
+ switch (action) {
+ case REG_ACTION_NONE:
+ d_printf(_("createkey did nothing -- huh?\n"));
+ break;
+ case REG_CREATED_NEW_KEY:
+ d_printf(_("createkey created %s\n"), argv[0]);
+ break;
+ case REG_OPENED_EXISTING_KEY:
+ d_printf(_("createkey opened existing %s\n"), argv[0]);
+ break;
+ }
+
+ dcerpc_winreg_CloseKey(b, mem_ctx, &key_hnd, &werr);
+ dcerpc_winreg_CloseKey(b, mem_ctx, &hive_hnd, &werr);
+
+ return status;
+}
+
+static int rpc_registry_createkey(struct net_context *c, int argc,
+ const char **argv )
+{
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net rpc registry createkey <key>\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_createkey_internal, argc, argv );
+}
+
+static NTSTATUS rpc_registry_deletekey_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ uint32_t hive;
+ struct policy_handle hive_hnd;
+ struct winreg_String key;
+ NTSTATUS status;
+ WERROR werr;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(key);
+
+ if (!reg_hive_key(mem_ctx, argv[0], &hive, &key.name)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = dcerpc_winreg_Connect(b, mem_ctx, hive,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &hive_hnd, &werr);
+ if (!(NT_STATUS_IS_OK(status))) {
+ return status;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+
+ status = dcerpc_winreg_DeleteKey(b, mem_ctx, &hive_hnd, key, &werr);
+ if (is_valid_policy_hnd(&hive_hnd)) {
+ WERROR _werr;
+ dcerpc_winreg_CloseKey(b, mem_ctx, &hive_hnd, &_werr);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("deletekey returned %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("deletekey returned %s\n"),
+ win_errstr(werr));
+ return werror_to_ntstatus(werr);
+ }
+
+ return status;
+}
+
+static int rpc_registry_deletekey(struct net_context *c, int argc, const char **argv )
+{
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net rpc registry deletekey <key>\n"));
+ return -1;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_deletekey_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_registry_enumerate_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle pol_hive, pol_key;
+ NTSTATUS status;
+ WERROR werr;
+ uint32_t num_subkeys = 0;
+ uint32_t num_values = 0;
+ char **names = NULL, **classes = NULL;
+ NTTIME **modtimes = NULL;
+ uint32_t i;
+ struct registry_value **values = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc registry enumerate <path>\n"));
+ d_printf("%s net rpc registry enumerate "
+ "'HKLM\\Software\\Samba'\n", _("Example:"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = registry_openkey(mem_ctx, pipe_hnd, argv[0], REG_KEY_READ,
+ &pol_hive, &pol_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_openkey failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ status = registry_enumkeys(mem_ctx, pipe_hnd, &pol_key, &num_subkeys,
+ &names, &classes, &modtimes);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("enumerating keys failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ for (i=0; i<num_subkeys; i++) {
+ print_registry_key(names[i], modtimes[i]);
+ }
+
+ status = registry_enumvalues(mem_ctx, pipe_hnd, &pol_key, &num_values,
+ &names, &values);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("enumerating values failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ for (i=0; i<num_values; i++) {
+ print_registry_value_with_name(names[i], values[i]);
+ }
+
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_key, &werr);
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_hive, &werr);
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_registry_enumerate(struct net_context *c, int argc,
+ const char **argv )
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_enumerate_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_registry_save_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ WERROR result = WERR_GEN_FAILURE;
+ struct policy_handle pol_hive, pol_key;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ struct winreg_String filename;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc registry backup <path> <file> \n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = registry_openkey(mem_ctx, pipe_hnd, argv[0], REG_KEY_ALL,
+ &pol_hive, &pol_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_openkey failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ filename.name = argv[1];
+ status = dcerpc_winreg_SaveKey(b, mem_ctx, &pol_key, &filename, NULL, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Unable to save [%s] to %s:%s\n"), argv[0],
+ pipe_hnd->desthost, argv[1]);
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ status = werror_to_ntstatus(result);
+ d_fprintf(stderr, _("Unable to save [%s] to %s:%s\n"), argv[0],
+ pipe_hnd->desthost, argv[1]);
+ }
+
+ /* cleanup */
+
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_key, &result);
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_hive, &result);
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_registry_save(struct net_context *c, int argc, const char **argv )
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_save_internal, argc, argv );
+}
+
+
+/********************************************************************
+********************************************************************/
+
+static void dump_values( REGF_NK_REC *nk )
+{
+ int i, j;
+ const char *data_str = NULL;
+ uint32_t data_size, data;
+ DATA_BLOB blob;
+
+ if ( !nk->values )
+ return;
+
+ for ( i=0; i<nk->num_values; i++ ) {
+ d_printf( "\"%s\" = ", nk->values[i].valuename ? nk->values[i].valuename : "(default)" );
+ d_printf( "(%s) ", str_regtype( nk->values[i].type ) );
+
+ data_size = nk->values[i].data_size & ~VK_DATA_IN_OFFSET;
+ switch ( nk->values[i].type ) {
+ case REG_SZ:
+ blob = data_blob_const(nk->values[i].data, data_size);
+ if (!pull_reg_sz(talloc_tos(), &blob,
+ &data_str)) {
+ data_str = NULL;
+ }
+ if (!data_str) {
+ break;
+ }
+ d_printf( "%s", data_str );
+ break;
+ case REG_MULTI_SZ:
+ case REG_EXPAND_SZ:
+ for ( j=0; j<data_size; j++ ) {
+ d_printf( "%c", nk->values[i].data[j] );
+ }
+ break;
+ case REG_DWORD:
+ data = IVAL( nk->values[i].data, 0 );
+ d_printf("0x%x", data );
+ break;
+ case REG_BINARY:
+ for ( j=0; j<data_size; j++ ) {
+ d_printf( "%x", nk->values[i].data[j] );
+ }
+ break;
+ default:
+ d_printf(_("unknown"));
+ break;
+ }
+
+ d_printf( "\n" );
+ }
+
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool dump_registry_tree( REGF_FILE *file, REGF_NK_REC *nk, const char *parent )
+{
+ REGF_NK_REC *key;
+
+ /* depth first dump of the registry tree */
+
+ while ( (key = regfio_fetch_subkey( file, nk )) ) {
+ char *regpath;
+ if (asprintf(&regpath, "%s\\%s", parent, key->keyname) < 0) {
+ break;
+ }
+ d_printf("[%s]\n", regpath );
+ dump_values( key );
+ d_printf("\n");
+ dump_registry_tree( file, key, regpath );
+ SAFE_FREE(regpath);
+ }
+
+ return true;
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool write_registry_tree( REGF_FILE *infile, REGF_NK_REC *nk,
+ REGF_NK_REC *parent, REGF_FILE *outfile,
+ const char *parentpath )
+{
+ REGF_NK_REC *key, *subkey;
+ struct regval_ctr *values = NULL;
+ struct regsubkey_ctr *subkeys = NULL;
+ int i;
+ char *path = NULL;
+ WERROR werr;
+
+ werr = regsubkey_ctr_init(infile->mem_ctx, &subkeys);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0, ("write_registry_tree: regsubkey_ctr_init failed: "
+ "%s\n", win_errstr(werr)));
+ return false;
+ }
+
+ werr = regval_ctr_init(subkeys, &values);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("write_registry_tree: talloc() failed!\n"));
+ TALLOC_FREE(subkeys);
+ return false;
+ }
+
+ /* copy values into the struct regval_ctr */
+
+ for ( i=0; i<nk->num_values; i++ ) {
+ regval_ctr_addvalue( values, nk->values[i].valuename, nk->values[i].type,
+ nk->values[i].data, (nk->values[i].data_size & ~VK_DATA_IN_OFFSET) );
+ }
+
+ /* copy subkeys into the struct regsubkey_ctr */
+
+ while ( (subkey = regfio_fetch_subkey( infile, nk )) ) {
+ regsubkey_ctr_addkey( subkeys, subkey->keyname );
+ }
+
+ key = regfio_write_key( outfile, nk->keyname, values, subkeys, nk->sec_desc->sec_desc, parent );
+
+ /* write each one of the subkeys out */
+
+ path = talloc_asprintf(subkeys,
+ "%s%s%s",
+ parentpath,
+ parent ? "\\" : "",
+ nk->keyname);
+ if (!path) {
+ TALLOC_FREE(subkeys);
+ return false;
+ }
+
+ nk->subkey_index = 0;
+ while ( (subkey = regfio_fetch_subkey( infile, nk )) ) {
+ write_registry_tree( infile, subkey, key, outfile, path );
+ }
+
+ d_printf("[%s]\n", path );
+ TALLOC_FREE(subkeys);
+
+ return true;
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_registry_dump(struct net_context *c, int argc, const char **argv)
+{
+ REGF_FILE *registry;
+ REGF_NK_REC *nk;
+
+ if (argc != 1 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc registry dump <file> \n"));
+ return -1;
+ }
+
+ d_printf(_("Opening %s...."), argv[0]);
+ if ( !(registry = regfio_open( argv[0], O_RDONLY, 0)) ) {
+ d_fprintf(stderr, _("Failed to open %s for reading\n"),argv[0]);
+ return 1;
+ }
+ d_printf(_("ok\n"));
+
+ /* get the root of the registry file */
+
+ if ((nk = regfio_rootkey( registry )) == NULL) {
+ d_fprintf(stderr, _("Could not get rootkey\n"));
+ regfio_close( registry );
+ return 1;
+ }
+ d_printf("[%s]\n", nk->keyname);
+ dump_values( nk );
+ d_printf("\n");
+
+ dump_registry_tree( registry, nk, nk->keyname );
+
+#if 0
+ talloc_report_full( registry->mem_ctx, stderr );
+#endif
+ d_printf(_("Closing registry..."));
+ regfio_close( registry );
+ d_printf(_("ok\n"));
+
+ return 0;
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_registry_copy(struct net_context *c, int argc, const char **argv )
+{
+ REGF_FILE *infile = NULL, *outfile = NULL;
+ REGF_NK_REC *nk;
+ int result = 1;
+
+ if (argc != 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc registry copy <srcfile> <newfile>\n"));
+ return -1;
+ }
+
+ d_printf(_("Opening %s...."), argv[0]);
+ if ( !(infile = regfio_open( argv[0], O_RDONLY, 0 )) ) {
+ d_fprintf(stderr, _("Failed to open %s for reading\n"),argv[0]);
+ return 1;
+ }
+ d_printf(_("ok\n"));
+
+ d_printf(_("Opening %s...."), argv[1]);
+ if ( !(outfile = regfio_open( argv[1], (O_RDWR|O_CREAT|O_TRUNC),
+ (S_IRUSR|S_IWUSR) )) ) {
+ d_fprintf(stderr, _("Failed to open %s for writing\n"),argv[1]);
+ goto out;
+ }
+ d_printf(_("ok\n"));
+
+ /* get the root of the registry file */
+
+ if ((nk = regfio_rootkey( infile )) == NULL) {
+ d_fprintf(stderr, _("Could not get rootkey\n"));
+ goto out;
+ }
+ d_printf(_("RootKey: [%s]\n"), nk->keyname);
+
+ write_registry_tree( infile, nk, NULL, outfile, "" );
+
+ result = 0;
+
+out:
+
+ d_printf(_("Closing %s..."), argv[1]);
+ if (outfile) {
+ regfio_close( outfile );
+ }
+ d_printf(_("ok\n"));
+
+ d_printf(_("Closing %s..."), argv[0]);
+ if (infile) {
+ regfio_close( infile );
+ }
+ d_printf(_("ok\n"));
+
+ return( result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_registry_getsd_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle pol_hive, pol_key;
+ NTSTATUS status;
+ WERROR werr;
+ enum ndr_err_code ndr_err;
+ struct KeySecurityData *sd = NULL;
+ uint32_t sec_info;
+ DATA_BLOB blob;
+ struct security_descriptor sec_desc;
+ uint32_t access_mask = SEC_FLAG_MAXIMUM_ALLOWED |
+ SEC_FLAG_SYSTEM_SECURITY;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc <1 || argc > 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc registry getsd <path> <secinfo>\n"));
+ d_printf("%s net rpc registry getsd "
+ "'HKLM\\Software\\Samba'\n", _("Example:"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = registry_openkey(mem_ctx, pipe_hnd, argv[0],
+ access_mask,
+ &pol_hive, &pol_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_openkey failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ sd = talloc_zero(mem_ctx, struct KeySecurityData);
+ if (!sd) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ sd->size = 0x1000;
+
+ if (argc >= 2) {
+ sscanf(argv[1], "%x", &sec_info);
+ } else {
+ sec_info = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL;
+ }
+
+ status = registry_getsd(mem_ctx, b, &pol_key, sec_info, sd, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("getting sd failed: %s\n"),
+ nt_errstr(status));
+ goto out;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ d_fprintf(stderr, _("getting sd failed: %s\n"),
+ win_errstr(werr));
+ goto out;
+ }
+
+ blob.data = sd->data;
+ blob.length = sd->size;
+
+ ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &sec_desc,
+ (ndr_pull_flags_fn_t)ndr_pull_security_descriptor);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ goto out;
+ }
+ status = NT_STATUS_OK;
+
+ display_sec_desc(&sec_desc);
+
+ out:
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_key, &werr);
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_hive, &werr);
+
+ return status;
+}
+
+
+static int rpc_registry_getsd(struct net_context *c, int argc, const char **argv)
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_getsd_internal, argc, argv);
+}
+
+/********************************************************************
+ ********************************************************************/
+/**
+ * @defgroup net_rpc_registry net rpc registry
+ */
+
+/**
+ * @defgroup net_rpc_registry_export Export
+ * @ingroup net_rpc_registry
+ * @{
+ */
+
+static NTSTATUS registry_export(struct rpc_pipe_client* pipe_hnd,
+ TALLOC_CTX* ctx,
+ struct policy_handle* key_hnd,
+ struct reg_format* f,
+ const char* parentfullname,
+ const char* name)
+{
+ NTSTATUS status;
+ uint32_t num_subkeys = 0;
+ uint32_t num_values = 0;
+ char **names = NULL, **classes = NULL;
+ NTTIME **modtimes = NULL;
+ struct regval_blob **values = NULL;
+ uint32_t i;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ TALLOC_CTX* mem_ctx = talloc_new(ctx);
+
+
+ const char* fullname = name
+ ? talloc_asprintf(mem_ctx, "%s\\%s", parentfullname, name)
+ : parentfullname;
+ reg_format_key(f, &fullname, 1, false);
+
+ status = registry_enumvalues2(mem_ctx, pipe_hnd, key_hnd, &num_values,
+ &names, &values);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("enumerating values failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ for (i=0; i<num_values; i++) {
+ reg_format_regval_blob(f, names[i], values[i]);
+ }
+
+
+ status = registry_enumkeys(mem_ctx, pipe_hnd, key_hnd, &num_subkeys,
+ &names, &classes, &modtimes);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("enumerating keys failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ for (i=0; i<num_subkeys; i++) {
+ struct policy_handle subkey_hnd;
+ struct winreg_String key;
+ WERROR werr;
+ ZERO_STRUCT(key);
+ /* key.name = talloc_strdup(mem_ctx, names[i]); ??? */
+ key.name = names[i];
+
+ status = dcerpc_winreg_OpenKey(b, mem_ctx, key_hnd, key,
+ 0, REG_KEY_READ,
+ &subkey_hnd, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("dcerpc_winreg_OpenKey failed: %s %s\n"),
+ names[i], nt_errstr(status));
+ continue;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ status = werror_to_ntstatus(werr);
+ d_fprintf(stderr,
+ _("dcerpc_winreg_OpenKey failed: %s %s\n"),
+ names[i], win_errstr(werr));
+ continue;
+ }
+
+ status = registry_export(pipe_hnd, mem_ctx, &subkey_hnd,
+ f, fullname, names[i]);
+ if (!(NT_STATUS_IS_OK(status))) {
+ d_fprintf(stderr,
+ _("export key failed: %s %s\n"),
+ names[i], nt_errstr(status));
+ }
+ dcerpc_winreg_CloseKey(b, mem_ctx,
+ &subkey_hnd, &werr);
+ }
+done:
+ talloc_free(mem_ctx);
+ return status;
+}
+
+static NTSTATUS rpc_registry_export_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle pol_hive, pol_key;
+ NTSTATUS status;
+ WERROR werr;
+ struct reg_format* f;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc < 2 || argc > 3 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc registry export <path> <file> [opt]\n"));
+ d_printf("%s net rpc registry export "
+ "'HKLM\\Software\\Samba' samba.reg\n", _("Example:"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = registry_openkey(mem_ctx, pipe_hnd, argv[0], REG_KEY_READ,
+ &pol_hive, &pol_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_openkey failed: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ f = reg_format_file(mem_ctx, argv[1], (argc > 2) ? argv[2] : NULL);
+ if (f == NULL) {
+ d_fprintf(stderr, _("open file failed: %s\n"), strerror(errno));
+ return map_nt_error_from_unix(errno);
+ }
+
+ status = registry_export(pipe_hnd, mem_ctx, &pol_key,
+ f, argv[0], NULL );
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_key, &werr);
+ dcerpc_winreg_CloseKey(b, mem_ctx, &pol_hive, &werr);
+
+ return status;
+}
+/********************************************************************
+ ********************************************************************/
+
+static int rpc_registry_export(struct net_context *c, int argc,
+ const char **argv )
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_export_internal, argc, argv );
+}
+
+/**@}*/
+
+/********************************************************************
+ ********************************************************************/
+
+/**
+ * @defgroup net_rpc_registry_import Import
+ * @ingroup net_rpc_registry
+ * @{
+ */
+
+struct import_ctx {
+ struct rpc_pipe_client *pipe_hnd;
+ TALLOC_CTX *mem_ctx;
+};
+
+static WERROR import_create_key(struct import_ctx* ctx,
+ struct policy_handle* parent, const char* name,
+ void** pkey, bool* existing)
+{
+ WERROR werr;
+ NTSTATUS status;
+ void* mem_ctx = talloc_new(ctx->mem_ctx);
+
+ struct policy_handle* key = NULL;
+ struct policy_handle hive;
+ struct winreg_String keyclass, keyname;
+ enum winreg_CreateAction action = REG_ACTION_NONE;
+ struct dcerpc_binding_handle *b = ctx->pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(keyname);
+ keyname.name = name;
+
+ if (parent == NULL) {
+ uint32_t hive_idx = 0;
+ if (!reg_hive_key(mem_ctx, name, &hive_idx, &keyname.name)) {
+ werr = WERR_FOOBAR;
+ goto done;
+ }
+
+ status = dcerpc_winreg_Connect(b, mem_ctx,
+ hive_idx, SEC_FLAG_MAXIMUM_ALLOWED,
+ &hive, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("dcerpc_winreg_Connect returned %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("dcerpc_winreg_Connect returned %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ parent = &hive;
+ }
+
+ key = talloc_zero(mem_ctx, struct policy_handle);
+ if (key == NULL) {
+ werr = WERR_NOT_ENOUGH_MEMORY;
+ goto done;
+ }
+
+ ZERO_STRUCT(keyclass);
+ keyclass.name = "";
+
+ status = dcerpc_winreg_CreateKey(b, mem_ctx,
+ parent, keyname,
+ keyclass, 0, REG_KEY_READ, NULL,
+ key, &action, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("dcerpc_winreg_CreateKey returned %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("dcerpc_winreg_CreateKey returned %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ switch (action) {
+ case REG_CREATED_NEW_KEY:
+ d_printf(_("createkey created %s\n"), name);
+ if (existing != NULL)
+ *existing = false;
+ break;
+
+ case REG_OPENED_EXISTING_KEY:
+ d_printf(_("createkey opened existing %s\n"), name);
+ if (existing != NULL)
+ *existing = true;
+ break;
+
+ case REG_ACTION_NONE:
+ d_printf(_("createkey did nothing -- huh?\n"));
+ werr = WERR_CREATE_FAILED;
+ break;
+ default:
+ assert(false);
+ }
+
+done:
+ if ( parent == &hive ) {
+ WERROR _result;
+ dcerpc_winreg_CloseKey(b, mem_ctx,
+ parent, &_result);
+ }
+
+ if (pkey!=NULL) {
+ *pkey = talloc_steal(ctx->mem_ctx, key);
+ }
+
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static WERROR import_delete_key(struct import_ctx* ctx,
+ struct policy_handle* parent, const char* name)
+{
+ WERROR werr;
+ NTSTATUS status;
+ void* mem_ctx = talloc_new(ctx->mem_ctx);
+ struct winreg_String keyname = { 0, };
+ struct policy_handle hive;
+ struct dcerpc_binding_handle *b = ctx->pipe_hnd->binding_handle;
+
+ keyname.name = name;
+
+ if (parent == NULL) {
+ uint32_t hive_idx;
+ if (!reg_hive_key(mem_ctx, name, &hive_idx, &keyname.name)) {
+ werr = WERR_FOOBAR;
+ goto done;
+ }
+
+ status = dcerpc_winreg_Connect(b, mem_ctx, hive_idx,
+ SEC_FLAG_MAXIMUM_ALLOWED, &hive,
+ &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("dcerpc_winreg_Connect returned %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("dcerpc_winreg_Connect returned %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ parent = &hive;
+ }
+
+ status = dcerpc_winreg_DeleteKey(b, mem_ctx, parent,
+ keyname, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("dcerpc_winreg_DeleteKey returned %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("dcerpc_winreg_DeleteKey returned %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+done:
+ if ( parent == &hive ) {
+ WERROR _result;
+ dcerpc_winreg_CloseKey(b, mem_ctx, parent, &_result);
+ }
+
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static WERROR import_close_key(struct import_ctx* ctx,
+ struct policy_handle* key)
+{
+ WERROR werr;
+ NTSTATUS status;
+ void* mem_ctx = talloc_new(ctx->mem_ctx);
+ struct dcerpc_binding_handle *b = ctx->pipe_hnd->binding_handle;
+
+ status = dcerpc_winreg_CloseKey(b, mem_ctx, key, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("dcerpc_winreg_CloseKey returned %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("dcerpc_winreg_CloseKey returned %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+ werr = (talloc_free(key) == 0) ? WERR_OK : WERR_GEN_FAILURE;
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static WERROR import_create_val(struct import_ctx* ctx,
+ struct policy_handle* parent, const char* name,
+ uint32_t type, const uint8_t* val, uint32_t len)
+{
+ WERROR werr;
+ NTSTATUS status;
+ void* mem_ctx = talloc_new(ctx->mem_ctx);
+ struct winreg_String valuename;
+ struct dcerpc_binding_handle *b = ctx->pipe_hnd->binding_handle;
+
+ if (parent == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(valuename);
+ valuename.name = name;
+
+ status = dcerpc_winreg_SetValue(b, mem_ctx, parent,
+ valuename, type,
+ (uint8_t *)discard_const(val), len, &werr);
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("registry_setvalue failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ d_fprintf(stderr, _("registry_setvalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+static WERROR import_delete_val(struct import_ctx* ctx,
+ struct policy_handle* parent, const char* name)
+{
+ WERROR werr;
+ NTSTATUS status;
+ void* mem_ctx = talloc_new(ctx->mem_ctx);
+ struct winreg_String valuename;
+ struct dcerpc_binding_handle *b = ctx->pipe_hnd->binding_handle;
+
+ if (parent == NULL) {
+ return WERR_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(valuename);
+ valuename.name = name;
+
+ status = dcerpc_winreg_DeleteValue(b, mem_ctx,
+ parent, valuename, &werr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ werr = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("registry_deletevalue failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("registry_deletevalue failed: %s\n"),
+ win_errstr(werr));
+ goto done;
+ }
+
+done:
+ talloc_free(mem_ctx);
+ return werr;
+}
+
+
+
+static NTSTATUS rpc_registry_import_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct import_ctx import_ctx;
+
+ struct reg_import_callback import_callback = {
+ .openkey = NULL,
+ .closekey = (reg_import_callback_closekey_t)&import_close_key,
+ .createkey = (reg_import_callback_createkey_t)&import_create_key,
+ .deletekey = (reg_import_callback_deletekey_t)&import_delete_key,
+ .deleteval = (reg_import_callback_deleteval_t)&import_delete_val,
+ .setval = {
+ .blob = (reg_import_callback_setval_blob_t)&import_create_val,
+ },
+ .setval_type = BLOB,
+ .data = &import_ctx
+ };
+
+ int ret;
+ if (argc < 1 || argc > 2 || c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc registry import <file> [options]\n"));
+ d_printf("%s net rpc registry export "
+ "samba.reg enc=CP1252,flags=0\n", _("Example:"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ZERO_STRUCT(import_ctx);
+ import_ctx.pipe_hnd = pipe_hnd;
+ import_ctx.mem_ctx = mem_ctx;
+ ret = reg_parse_file(argv[0],
+ reg_import_adapter(import_ctx.mem_ctx,
+ import_callback
+ ),
+ (argc > 1) ? argv[1] : NULL
+ );
+
+ return ret==0 ? NT_STATUS_OK : NT_STATUS_UNSUCCESSFUL;
+}
+
+/********************************************************************
+ ********************************************************************/
+
+static int rpc_registry_import(struct net_context *c, int argc,
+ const char **argv )
+{
+ return run_rpc_command(c, NULL, &ndr_table_winreg, 0,
+ rpc_registry_import_internal, argc, argv );
+}
+
+/**@}*/
+/********************************************************************
+ ********************************************************************/
+
+int net_rpc_registry(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "enumerate",
+ rpc_registry_enumerate,
+ NET_TRANSPORT_RPC,
+ N_("Enumerate registry keys and values"),
+ N_("net rpc registry enumerate\n"
+ " Enumerate registry keys and values")
+ },
+ {
+ "createkey",
+ rpc_registry_createkey,
+ NET_TRANSPORT_RPC,
+ N_("Create a new registry key"),
+ N_("net rpc registry createkey\n"
+ " Create a new registry key")
+ },
+ {
+ "deletekey",
+ rpc_registry_deletekey,
+ NET_TRANSPORT_RPC,
+ N_("Delete a registry key"),
+ N_("net rpc registry deletekey\n"
+ " Delete a registry key")
+ },
+ {
+ "getvalue",
+ rpc_registry_getvalue,
+ NET_TRANSPORT_RPC,
+ N_("Print a registry value"),
+ N_("net rpc registry getvalue\n"
+ " Print a registry value")
+ },
+ {
+ "getvalueraw",
+ rpc_registry_getvalueraw,
+ NET_TRANSPORT_RPC,
+ N_("Print a registry value"),
+ N_("net rpc registry getvalueraw\n"
+ " Print a registry value (raw version)")
+ },
+ {
+ "setvalue",
+ rpc_registry_setvalue,
+ NET_TRANSPORT_RPC,
+ N_("Set a new registry value"),
+ N_("net rpc registry setvalue\n"
+ " Set a new registry value")
+ },
+ {
+ "deletevalue",
+ rpc_registry_deletevalue,
+ NET_TRANSPORT_RPC,
+ N_("Delete a registry value"),
+ N_("net rpc registry deletevalue\n"
+ " Delete a registry value")
+ },
+ {
+ "save",
+ rpc_registry_save,
+ NET_TRANSPORT_RPC,
+ N_("Save a registry file"),
+ N_("net rpc registry save\n"
+ " Save a registry file")
+ },
+ {
+ "dump",
+ rpc_registry_dump,
+ NET_TRANSPORT_RPC,
+ N_("Dump a registry file"),
+ N_("net rpc registry dump\n"
+ " Dump a registry file")
+ },
+ {
+ "copy",
+ rpc_registry_copy,
+ NET_TRANSPORT_RPC,
+ N_("Copy a registry file"),
+ N_("net rpc registry copy\n"
+ " Copy a registry file")
+ },
+ {
+ "getsd",
+ rpc_registry_getsd,
+ NET_TRANSPORT_RPC,
+ N_("Get security descriptor"),
+ N_("net rpc registry getsd\n"
+ " Get security descriptor")
+ },
+ {
+ "import",
+ rpc_registry_import,
+ NET_TRANSPORT_RPC,
+ N_("Import .reg file"),
+ N_("net rpc registry import\n"
+ " Import .reg file")
+ },
+ {
+ "export",
+ rpc_registry_export,
+ NET_TRANSPORT_RPC,
+ N_("Export .reg file"),
+ N_("net rpc registry export\n"
+ " Export .reg file")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+ return net_run_function(c, argc, argv, "net rpc registry", func);
+}
diff --git a/source3/utils/net_rpc_rights.c b/source3/utils/net_rpc_rights.c
new file mode 100644
index 0000000..267ce65
--- /dev/null
+++ b/source3/utils/net_rpc_rights.c
@@ -0,0 +1,798 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) Gerald (Jerry) Carter 2004
+ Copyright (C) Guenther Deschner 2008
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "includes.h"
+#include "utils/net.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "rpc_client/init_lsa.h"
+#include "../libcli/security/security.h"
+#include "lib/util/string_wrappers.h"
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS sid_to_name(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid *sid,
+ fstring name)
+{
+ struct policy_handle pol;
+ enum lsa_SidType *sid_types = NULL;
+ NTSTATUS status, result;
+ char **domains = NULL, **names = NULL;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED, &pol);
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ status = rpccli_lsa_lookup_sids(pipe_hnd, mem_ctx, &pol, 1, sid, &domains, &names, &sid_types);
+
+ if ( NT_STATUS_IS_OK(status) ) {
+ if ( *domains[0] )
+ fstr_sprintf( name, "%s\\%s", domains[0], names[0] );
+ else
+ fstrcpy( name, names[0] );
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS name_to_sid(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct dom_sid *sid, const char *name)
+{
+ struct policy_handle pol;
+ enum lsa_SidType *sid_types;
+ NTSTATUS status, result;
+ struct dom_sid *sids;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* maybe its a raw SID */
+ if (dom_sid_parse(name, sid)) {
+ return NT_STATUS_OK;
+ }
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED, &pol);
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ status = rpccli_lsa_lookup_names(pipe_hnd, mem_ctx, &pol, 1, &name,
+ NULL, 1, &sids, &sid_types);
+
+ if ( NT_STATUS_IS_OK(status) )
+ sid_copy( sid, &sids[0] );
+
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS enum_privileges(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *ctx,
+ struct policy_handle *pol )
+{
+ NTSTATUS status, result;
+ uint32_t enum_context = 0;
+ uint32_t pref_max_length=0x1000;
+ uint32_t i;
+ uint16_t lang_id=0;
+ uint16_t lang_id_sys=0;
+ uint16_t lang_id_desc;
+ struct lsa_StringLarge *description = NULL;
+ struct lsa_PrivArray priv_array;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_lsa_EnumPrivs(b, ctx,
+ pol,
+ &enum_context,
+ &priv_array,
+ pref_max_length,
+ &result);
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ /* Print results */
+
+ for (i = 0; i < priv_array.count; i++) {
+
+ struct lsa_String lsa_name;
+
+ d_printf("%30s ",
+ priv_array.privs[i].name.string ? priv_array.privs[i].name.string : "*unknown*" );
+
+ /* try to get the description */
+
+ init_lsa_String(&lsa_name, priv_array.privs[i].name.string);
+
+ status = dcerpc_lsa_LookupPrivDisplayName(b, ctx,
+ pol,
+ &lsa_name,
+ lang_id,
+ lang_id_sys,
+ &description,
+ &lang_id_desc,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("??????\n");
+ continue;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ d_printf("??????\n");
+ continue;
+ }
+
+ d_printf("%s\n", description ? description->string : "??????");
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS check_privilege_for_user(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *ctx,
+ struct policy_handle *pol,
+ struct dom_sid *sid,
+ const char *right)
+{
+ NTSTATUS status, result;
+ struct lsa_RightSet rights;
+ uint32_t i;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_lsa_EnumAccountRights(b, ctx,
+ pol,
+ sid,
+ &rights,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ if (rights.count == 0) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ for (i = 0; i < rights.count; i++) {
+ if (strcasecmp_m(rights.names[i].string, right) == 0) {
+ return NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS enum_privileges_for_user(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *ctx,
+ struct policy_handle *pol,
+ struct dom_sid *sid )
+{
+ NTSTATUS status, result;
+ struct lsa_RightSet rights;
+ uint32_t i;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_lsa_EnumAccountRights(b, ctx,
+ pol,
+ sid,
+ &rights,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ if (rights.count == 0) {
+ d_printf(_("No privileges assigned\n"));
+ }
+
+ for (i = 0; i < rights.count; i++) {
+ printf("%s\n", rights.names[i].string);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS enum_accounts_for_privilege(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *ctx,
+ struct policy_handle *pol,
+ const char *privilege)
+{
+ NTSTATUS status, result;
+ uint32_t enum_context=0;
+ uint32_t pref_max_length=0x1000;
+ struct lsa_SidArray sid_array;
+ uint32_t i;
+ fstring name;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_lsa_EnumAccounts(b, ctx,
+ pol,
+ &enum_context,
+ &sid_array,
+ pref_max_length,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ d_printf("%s:\n", privilege);
+
+ for ( i=0; i<sid_array.num_sids; i++ ) {
+
+ status = check_privilege_for_user(pipe_hnd, ctx, pol,
+ sid_array.sids[i].sid,
+ privilege);
+
+ if ( ! NT_STATUS_IS_OK(status)) {
+ if ( ! NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ return status;
+ }
+ continue;
+ }
+
+ /* try to convert the SID to a name. Fall back to
+ printing the raw SID if necessary */
+ status = sid_to_name( pipe_hnd, ctx, sid_array.sids[i].sid, name );
+ if ( !NT_STATUS_IS_OK (status) )
+ sid_to_fstring(name, sid_array.sids[i].sid);
+
+ d_printf(" %s\n", name);
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS enum_privileges_for_accounts(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *ctx,
+ struct policy_handle *pol)
+{
+ NTSTATUS status, result;
+ uint32_t enum_context=0;
+ uint32_t pref_max_length=0x1000;
+ struct lsa_SidArray sid_array;
+ uint32_t i;
+ fstring name;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = dcerpc_lsa_EnumAccounts(b, ctx,
+ pol,
+ &enum_context,
+ &sid_array,
+ pref_max_length,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ for ( i=0; i<sid_array.num_sids; i++ ) {
+
+ /* try to convert the SID to a name. Fall back to
+ printing the raw SID if necessary */
+
+ status = sid_to_name(pipe_hnd, ctx, sid_array.sids[i].sid, name);
+ if ( !NT_STATUS_IS_OK (status) )
+ sid_to_fstring(name, sid_array.sids[i].sid);
+
+ d_printf("%s\n", name);
+
+ status = enum_privileges_for_user(pipe_hnd, ctx, pol,
+ sid_array.sids[i].sid);
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ d_printf("\n");
+ }
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_rights_list_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ struct dom_sid sid;
+ fstring privname;
+ struct lsa_String lsa_name;
+ struct lsa_StringLarge *description = NULL;
+ uint16_t lang_id = 0;
+ uint16_t lang_id_sys = 0;
+ uint16_t lang_id_desc;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ status = rpccli_lsa_open_policy(pipe_hnd, mem_ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED, &pol);
+
+ if ( !NT_STATUS_IS_OK(status) )
+ return status;
+
+ /* backwards compatibility; just list available privileges if no argument */
+
+ if (argc == 0) {
+ status = enum_privileges(pipe_hnd, mem_ctx, &pol );
+ goto done;
+ }
+
+ if (strequal(argv[0], "privileges")) {
+ int i = 1;
+
+ if (argv[1] == NULL) {
+ status = enum_privileges(pipe_hnd, mem_ctx, &pol );
+ goto done;
+ }
+
+ while ( argv[i] != NULL ) {
+ fstrcpy(privname, argv[i]);
+ init_lsa_String(&lsa_name, argv[i]);
+ i++;
+
+ /* verify that this is a valid privilege for error reporting */
+ status = dcerpc_lsa_LookupPrivDisplayName(b, mem_ctx,
+ &pol,
+ &lsa_name,
+ lang_id,
+ lang_id_sys,
+ &description,
+ &lang_id_desc,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ continue;
+ }
+ status = result;
+ if ( !NT_STATUS_IS_OK(result) ) {
+ if ( NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_PRIVILEGE))
+ d_fprintf(stderr, _("No such privilege "
+ "exists: %s.\n"), privname);
+ else
+ d_fprintf(stderr, _("Error resolving "
+ "privilege display name "
+ "[%s].\n"),
+ nt_errstr(result));
+ continue;
+ }
+
+ status = enum_accounts_for_privilege(pipe_hnd, mem_ctx, &pol, privname);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Error enumerating "
+ "accounts for privilege %s [%s].\n"),
+ privname, nt_errstr(status));
+ continue;
+ }
+ }
+ goto done;
+ }
+
+ /* special case to enumerate all privileged SIDs with associated rights */
+
+ if (strequal( argv[0], "accounts")) {
+ int i = 1;
+
+ if (argv[1] == NULL) {
+ status = enum_privileges_for_accounts(pipe_hnd, mem_ctx, &pol);
+ goto done;
+ }
+
+ while (argv[i] != NULL) {
+ status = name_to_sid(pipe_hnd, mem_ctx, &sid, argv[i]);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = enum_privileges_for_user(pipe_hnd, mem_ctx, &pol, &sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ i++;
+ }
+ goto done;
+ }
+
+ /* backward compatibility: if no keyword provided, treat the key
+ as an account name */
+ if (argc > 1) {
+ d_printf("%s net rpc rights list [[accounts|privileges] "
+ "[name|SID]]\n", _("Usage:"));
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ status = name_to_sid(pipe_hnd, mem_ctx, &sid, argv[0]);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ status = enum_privileges_for_user(pipe_hnd, mem_ctx, &pol, &sid );
+
+done:
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_rights_grant_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle dom_pol = {
+ .handle_type = 0,
+ };
+ NTSTATUS status, result;
+ struct lsa_RightSet rights;
+ int i;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ struct dom_sid sid;
+
+ if (argc < 2 ) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net rpc rights grant <name|SID> <rights...>\n"));
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(pipe_hnd, mem_ctx, &sid, argv[0]);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))
+ status = NT_STATUS_NO_SUCH_USER;
+
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ pipe_hnd->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ DBG_DEBUG("Couldn't open policy handle: %s\n",
+ nt_errstr(status));
+ goto done;
+ }
+
+ rights.count = argc-1;
+ rights.names = talloc_array(mem_ctx, struct lsa_StringLarge,
+ rights.count);
+ if (rights.names == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i<argc-1; i++) {
+ init_lsa_StringLarge(&rights.names[i], argv[i+1]);
+ }
+
+ status = dcerpc_lsa_AddAccountRights(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ &rights,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto done;
+ }
+
+ d_printf(_("Successfully granted rights.\n"));
+
+ done:
+ if ( !NT_STATUS_IS_OK(status) ) {
+ d_fprintf(stderr, _("Failed to grant privileges for %s (%s)\n"),
+ argv[0], nt_errstr(status));
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_rights_revoke_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle dom_pol;
+ NTSTATUS status, result;
+ struct lsa_RightSet rights;
+ struct dom_sid sid;
+ int i;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+ union lsa_revision_info out_revision_info = {
+ .info1 =
+ {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (argc < 2 ) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _(" net rpc rights revoke <name|SID> <rights...>\n"));
+ return NT_STATUS_OK;
+ }
+
+ status = name_to_sid(pipe_hnd, mem_ctx, &sid, argv[0]);
+ if (!NT_STATUS_IS_OK(status))
+ return status;
+
+ status = dcerpc_lsa_open_policy_fallback(b,
+ mem_ctx,
+ pipe_hnd->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &dom_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ DBG_DEBUG("Couldn't open policy handle: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ rights.count = argc-1;
+ rights.names = talloc_array(mem_ctx, struct lsa_StringLarge,
+ rights.count);
+ if (!rights.names) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<argc-1; i++) {
+ init_lsa_StringLarge(&rights.names[i], argv[i+1]);
+ }
+
+ status = dcerpc_lsa_RemoveAccountRights(b, mem_ctx,
+ &dom_pol,
+ &sid,
+ false,
+ &rights,
+ &result);
+ if (!NT_STATUS_IS_OK(status))
+ goto done;
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ d_printf(_("Successfully revoked rights.\n"));
+
+done:
+ if ( !NT_STATUS_IS_OK(status) ) {
+ d_fprintf(stderr,_("Failed to revoke privileges for %s (%s)\n"),
+ argv[0], nt_errstr(status));
+ }
+
+ dcerpc_lsa_Close(b, mem_ctx, &dom_pol, &result);
+
+ return status;
+}
+
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_rights_list(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc rights list [{accounts|privileges} "
+ "[name|SID]]\n"
+ " View available/assigned privileges\n"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_rights_list_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_rights_grant(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc rights grant <name|SID> <right>\n"
+ " Assign privilege[s]\n"));
+ d_printf(_("For example:\n"
+ " net rpc rights grant 'VALE\\biddle' "
+ "SePrintOperatorPrivilege SeDiskOperatorPrivilege\n"
+ " would grant the printer admin and disk manager "
+ "rights to the user 'VALE\\biddle'\n"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_rights_grant_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_rights_revoke(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc rights revoke <name|SID> <right>\n"
+ " Revoke privilege[s]\n"));
+ d_printf(_("For example:\n"
+ " net rpc rights revoke 'VALE\\biddle' "
+ "SePrintOperatorPrivilege SeDiskOperatorPrivilege\n"
+ " would revoke the printer admin and disk manager"
+ " rights from the user 'VALE\\biddle'\n"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_lsarpc, 0,
+ rpc_rights_revoke_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+int net_rpc_rights(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ rpc_rights_list,
+ NET_TRANSPORT_RPC,
+ N_("View available/assigned privileges"),
+ N_("net rpc rights list\n"
+ " View available/assigned privileges")
+ },
+ {
+ "grant",
+ rpc_rights_grant,
+ NET_TRANSPORT_RPC,
+ N_("Assign privilege[s]"),
+ N_("net rpc rights grant\n"
+ " Assign privilege[s]")
+ },
+ {
+ "revoke",
+ rpc_rights_revoke,
+ NET_TRANSPORT_RPC,
+ N_("Revoke privilege[s]"),
+ N_("net rpc rights revoke\n"
+ " Revoke privilege[s]")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rpc rights", func);
+}
+
+static NTSTATUS rpc_sh_rights_list(struct net_context *c,
+ TALLOC_CTX *mem_ctx, struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_rights_list_internal(c, ctx->domain_sid, ctx->domain_name,
+ ctx->cli, pipe_hnd, mem_ctx,
+ argc, argv);
+}
+
+static NTSTATUS rpc_sh_rights_grant(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_rights_grant_internal(c, ctx->domain_sid, ctx->domain_name,
+ ctx->cli, pipe_hnd, mem_ctx,
+ argc, argv);
+}
+
+static NTSTATUS rpc_sh_rights_revoke(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_rights_revoke_internal(c, ctx->domain_sid, ctx->domain_name,
+ ctx->cli, pipe_hnd, mem_ctx,
+ argc, argv);
+}
+
+struct rpc_sh_cmd *net_rpc_rights_cmds(struct net_context *c, TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx)
+{
+ static struct rpc_sh_cmd cmds[] = {
+
+ { "list", NULL, &ndr_table_lsarpc, rpc_sh_rights_list,
+ N_("View available or assigned privileges") },
+
+ { "grant", NULL, &ndr_table_lsarpc, rpc_sh_rights_grant,
+ N_("Assign privilege[s]") },
+
+ { "revoke", NULL, &ndr_table_lsarpc, rpc_sh_rights_revoke,
+ N_("Revoke privilege[s]") },
+
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ return cmds;
+}
+
diff --git a/source3/utils/net_rpc_samsync.c b/source3/utils/net_rpc_samsync.c
new file mode 100644
index 0000000..e295d6a
--- /dev/null
+++ b/source3/utils/net_rpc_samsync.c
@@ -0,0 +1,257 @@
+/*
+ Unix SMB/CIFS implementation.
+ dump the remote SAM using rpc samsync operations
+
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Tim Potter 2001,2002
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2005
+ Modified by Volker Lendecke 2002
+ Copyright (C) Jeremy Allison 2005.
+ Copyright (C) Guenther Deschner 2008.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../librpc/gen_ndr/ndr_drsuapi.h"
+#include "libnet/libnet_dssync.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+
+/**
+ * Basic usage function for 'net rpc vampire'
+ *
+ * @param c A net_context structure
+ * @param argc Standard main() style argc
+ * @param argc Standard main() style argv. Initial components are already
+ * stripped
+ **/
+
+int rpc_vampire_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net rpc vampire ([ldif [<ldif-filename>] | [keytab] "
+ "[<keytab-filename]) [options]\n"
+ "\t to pull accounts from a remote PDC where we are a BDC\n"
+ "\t\t no args puts accounts in local passdb from smb.conf\n"
+ "\t\t ldif - put accounts in ldif format (file defaults to "
+ "/tmp/tmp.ldif)\n"
+ "\t\t keytab - put account passwords in krb5 keytab "
+ "(defaults to system keytab)\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+static NTSTATUS rpc_vampire_ds_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ struct dssync_context *ctx = NULL;
+
+ if (!dom_sid_equal(domain_sid, get_global_sam_sid())) {
+ struct dom_sid_buf buf1, buf2;
+ d_printf(_("Cannot import users from %s at this time, "
+ "as the current domain:\n\t%s: %s\nconflicts "
+ "with the remote domain\n\t%s: %s\n"
+ "Perhaps you need to set: \n\n\tsecurity=user\n\t"
+ "workgroup=%s\n\n in your smb.conf?\n"),
+ domain_name,
+ get_global_sam_name(),
+ dom_sid_str_buf(get_global_sam_sid(), &buf1),
+ domain_name,
+ dom_sid_str_buf(domain_sid, &buf2),
+ domain_name);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ status = libnet_dssync_init_context(mem_ctx,
+ &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ctx->cli = pipe_hnd;
+ ctx->domain_name = domain_name;
+ ctx->ops = &libnet_dssync_passdb_ops;
+
+ status = libnet_dssync(mem_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status) && ctx->error_message) {
+ d_fprintf(stderr, "%s\n", ctx->error_message);
+ goto out;
+ }
+
+ if (ctx->result_message) {
+ d_fprintf(stdout, "%s\n", ctx->result_message);
+ }
+
+ out:
+ TALLOC_FREE(ctx);
+
+ return status;
+}
+
+int rpc_vampire_passdb(struct net_context *c, int argc, const char **argv)
+{
+ int ret = 0;
+ NTSTATUS status;
+ struct cli_state *cli = NULL;
+ struct net_dc_info dc_info;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc vampire passdb\n"
+ " %s\n",
+ _("Usage:"),
+ _("Dump remote SAM database to passdb"));
+ return 0;
+ }
+
+ status = net_make_ipc_connection(c, 0, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ status = net_scan_dc(c, cli, &dc_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ if (!dc_info.is_ad) {
+ printf(_("DC is not running Active Directory, exiting\n"));
+ return -1;
+ }
+
+ if (!c->opt_force) {
+ d_printf( "%s\n"
+ "net rpc vampire passdb\n"
+ " %s\n",
+ _("Usage:"),
+ _("Should not be used against Active Directory, maybe use --force"));
+ return -1;
+ }
+
+ ret = run_rpc_command(c, cli, &ndr_table_drsuapi,
+ NET_FLAGS_SEAL | NET_FLAGS_TCP,
+ rpc_vampire_ds_internals, argc, argv);
+ return ret;
+}
+
+static NTSTATUS rpc_vampire_keytab_ds_internals(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ struct dssync_context *ctx = NULL;
+
+ status = libnet_dssync_init_context(mem_ctx,
+ &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ctx->force_full_replication = c->opt_force_full_repl ? true : false;
+ ctx->clean_old_entries = c->opt_clean_old_entries ? true : false;
+
+ if (argc < 1) {
+ /* the caller should ensure that a filename is provided */
+ return NT_STATUS_INVALID_PARAMETER;
+ } else {
+ ctx->output_filename = argv[0];
+ }
+
+ if (argc >= 2) {
+ ctx->object_dns = &argv[1];
+ ctx->object_count = argc - 1;
+ ctx->single_object_replication = c->opt_single_obj_repl ? true
+ : false;
+ }
+
+ ctx->cli = pipe_hnd;
+ ctx->domain_name = domain_name;
+ ctx->ops = &libnet_dssync_keytab_ops;
+
+ status = libnet_dssync(mem_ctx, ctx);
+ if (!NT_STATUS_IS_OK(status) && ctx->error_message) {
+ d_fprintf(stderr, "%s\n", ctx->error_message);
+ goto out;
+ }
+
+ if (ctx->result_message) {
+ d_fprintf(stdout, "%s\n", ctx->result_message);
+ }
+
+ out:
+ TALLOC_FREE(ctx);
+
+ return status;
+}
+
+/**
+ * Basic function for 'net rpc vampire keytab'
+ *
+ * @param c A net_context structure
+ * @param argc Standard main() style argc
+ * @param argc Standard main() style argv. Initial components are already
+ * stripped
+ **/
+
+int rpc_vampire_keytab(struct net_context *c, int argc, const char **argv)
+{
+ int ret = 0;
+ NTSTATUS status;
+ struct cli_state *cli = NULL;
+ struct net_dc_info dc_info;
+
+ if (c->display_usage || (argc < 1)) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net rpc vampire keytab <keytabfile>\n"
+ " Dump remote SAM database to Kerberos keytab "
+ "file\n"));
+ return 0;
+ }
+
+ status = net_make_ipc_connection(c, 0, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ status = net_scan_dc(c, cli, &dc_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ if (!dc_info.is_ad) {
+ printf(_("DC is not running Active Directory, exiting\n"));
+ return -1;
+ }
+
+ ret = run_rpc_command(c, cli, &ndr_table_drsuapi,
+ NET_FLAGS_SEAL | NET_FLAGS_TCP,
+ rpc_vampire_keytab_ds_internals, argc, argv);
+ return ret;
+}
diff --git a/source3/utils/net_rpc_service.c b/source3/utils/net_rpc_service.c
new file mode 100644
index 0000000..a0fbc51
--- /dev/null
+++ b/source3/utils/net_rpc_service.c
@@ -0,0 +1,1138 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) Gerald (Jerry) Carter 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_svcctl.h"
+#include "../librpc/gen_ndr/ndr_svcctl_c.h"
+#include "lib/util/string_wrappers.h"
+
+struct svc_state_msg {
+ uint32_t flag;
+ const char *message;
+};
+
+static struct svc_state_msg state_msg_table[] = {
+ { SVCCTL_STOPPED, N_("stopped") },
+ { SVCCTL_START_PENDING, N_("start pending") },
+ { SVCCTL_STOP_PENDING, N_("stop pending") },
+ { SVCCTL_RUNNING, N_("running") },
+ { SVCCTL_CONTINUE_PENDING, N_("resume pending") },
+ { SVCCTL_PAUSE_PENDING, N_("pause pending") },
+ { SVCCTL_PAUSED, N_("paused") },
+ { 0, NULL }
+};
+
+
+/********************************************************************
+********************************************************************/
+const char *svc_status_string( uint32_t state )
+{
+ fstring msg;
+ int i;
+
+ fstr_sprintf( msg, _("Unknown State [%d]"), state );
+
+ for ( i=0; state_msg_table[i].message; i++ ) {
+ if ( state_msg_table[i].flag == state ) {
+ fstrcpy( msg, state_msg_table[i].message );
+ break;
+ }
+ }
+
+ return talloc_strdup(talloc_tos(), msg);
+}
+
+/********************************************************************
+********************************************************************/
+
+static WERROR open_service(struct dcerpc_binding_handle *b,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hSCM,
+ const char *service,
+ uint32_t access_mask,
+ struct policy_handle *hService)
+{
+ NTSTATUS status;
+ WERROR result;
+
+ status = dcerpc_svcctl_OpenServiceW(b, mem_ctx,
+ hSCM,
+ service,
+ access_mask,
+ hService,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Failed to open service. [%s]\n"),
+ nt_errstr(status));
+ return result;
+ }
+ if (!W_ERROR_IS_OK(result) ) {
+ d_fprintf(stderr, _("Failed to open service. [%s]\n"),
+ win_errstr(result));
+ return result;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static WERROR open_scm(struct dcerpc_binding_handle *b,
+ TALLOC_CTX *mem_ctx,
+ const char *server_name,
+ uint32_t access_mask,
+ struct policy_handle *hSCM)
+{
+ NTSTATUS status;
+ WERROR result;
+
+ status = dcerpc_svcctl_OpenSCManagerW(b, mem_ctx,
+ server_name,
+ NULL,
+ access_mask,
+ hSCM,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr,
+ _("Failed to open Service Control Manager. [%s]\n"),
+ nt_errstr(status));
+ return result;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ d_fprintf(stderr,
+ _("Failed to open Service Control Manager. [%s]\n"),
+ win_errstr(result));
+ return result;
+ }
+
+ return WERR_OK;
+}
+
+/********************************************************************
+********************************************************************/
+
+static WERROR query_service_state(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hSCM,
+ const char *service,
+ uint32_t *state )
+{
+ struct policy_handle hService;
+ struct SERVICE_STATUS service_status;
+ WERROR result = WERR_GEN_FAILURE;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* now cycle until the status is actually 'watch_state' */
+
+ result = open_service(b, mem_ctx, hSCM, service,
+ SC_RIGHT_SVC_QUERY_STATUS,
+ &hService);
+ if (!W_ERROR_IS_OK(result) ) {
+ return result;
+ }
+
+ status = dcerpc_svcctl_QueryServiceStatus(b, mem_ctx,
+ &hService,
+ &service_status,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ goto done;
+ }
+
+ *state = service_status.state;
+
+ done:
+ if (is_valid_policy_hnd(&hService)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hService, &_result);
+ }
+
+ return result;
+}
+
+/********************************************************************
+********************************************************************/
+
+static WERROR watch_service_state(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hSCM,
+ const char *service,
+ uint32_t watch_state,
+ uint32_t *final_state )
+{
+ uint32_t i;
+ uint32_t state = 0;
+ WERROR result = WERR_GEN_FAILURE;
+
+
+ i = 0;
+ while ( (state != watch_state ) && i<30 ) {
+ /* get the status */
+
+ result = query_service_state(pipe_hnd, mem_ctx, hSCM, service, &state );
+ if ( !W_ERROR_IS_OK(result) ) {
+ break;
+ }
+
+ d_printf(".");
+ i++;
+ usleep( 100 );
+ }
+ d_printf("\n");
+
+ *final_state = state;
+
+ return result;
+}
+
+/********************************************************************
+********************************************************************/
+
+static WERROR control_service(struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ struct policy_handle *hSCM,
+ const char *service,
+ uint32_t control,
+ uint32_t watch_state )
+{
+ struct policy_handle hService;
+ WERROR result = WERR_GEN_FAILURE;
+ NTSTATUS status;
+ struct SERVICE_STATUS service_status;
+ uint32_t state = 0;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ /* Open the Service */
+
+ result = open_service(b, mem_ctx, hSCM, service,
+ (SC_RIGHT_SVC_STOP|SC_RIGHT_SVC_PAUSE_CONTINUE),
+ &hService);
+ if (!W_ERROR_IS_OK(result) ) {
+ return result;
+ }
+
+ /* get the status */
+
+ status = dcerpc_svcctl_ControlService(b, mem_ctx,
+ &hService,
+ control,
+ &service_status,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Control service request failed. [%s]\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result) ) {
+ d_fprintf(stderr, _("Control service request failed. [%s]\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ /* loop -- checking the state until we are where we want to be */
+
+ result = watch_service_state(pipe_hnd, mem_ctx, hSCM, service, watch_state, &state );
+
+ d_printf(_("%s service is %s.\n"), service, svc_status_string(state));
+
+done:
+ if (is_valid_policy_hnd(&hService)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hService, &_result);
+ }
+
+ return result;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_list_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hSCM;
+ struct ENUM_SERVICE_STATUSW *services = NULL;
+ WERROR result = WERR_GEN_FAILURE;
+ NTSTATUS status;
+ int i;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ uint8_t *buffer;
+ uint32_t buf_size = 0;
+ uint32_t bytes_needed = 0;
+ uint32_t num_services = 0;
+ uint32_t resume_handle = 0;
+
+ if (argc != 0 ) {
+ d_printf("%s net rpc service list\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_ENUMERATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ buffer = talloc_array(mem_ctx, uint8_t, buf_size);
+ if (buffer == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ do {
+ status = dcerpc_svcctl_EnumServicesStatusW(b, mem_ctx,
+ &hSCM,
+ SERVICE_TYPE_WIN32,
+ SERVICE_STATE_ALL,
+ buffer,
+ buf_size,
+ &bytes_needed,
+ &num_services,
+ &resume_handle,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ _("Failed to enumerate services. [%s]\n"),
+ nt_errstr(status));
+ break;
+ }
+
+ if (W_ERROR_EQUAL(result, WERR_MORE_DATA) && bytes_needed > 0) {
+ buf_size = bytes_needed;
+ buffer = talloc_realloc(mem_ctx, buffer, uint8_t, bytes_needed);
+ if (buffer == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+ continue;
+ }
+
+ if (!W_ERROR_IS_OK(result)) {
+ status = werror_to_ntstatus(result);
+ d_fprintf(stderr,
+ _("Failed to enumerate services. [%s]\n"),
+ win_errstr(result));
+ break;
+ }
+
+ if ( num_services == 0 ) {
+ d_printf(_("No services returned\n"));
+ break;
+ }
+
+ {
+ enum ndr_err_code ndr_err;
+ DATA_BLOB blob;
+ struct ndr_pull *ndr;
+
+ blob.length = buf_size;
+ blob.data = talloc_steal(mem_ctx, buffer);
+
+ services = talloc_array(mem_ctx, struct ENUM_SERVICE_STATUSW, num_services);
+ if (!services) {
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ ndr = ndr_pull_init_blob(&blob, mem_ctx);
+ if (ndr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ break;
+ }
+
+ ndr_err = ndr_pull_ENUM_SERVICE_STATUSW_array(
+ ndr, num_services, services);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ break;
+ }
+
+ for ( i=0; i<num_services; i++ ) {
+ d_printf("%-20s \"%s\"\n",
+ services[i].service_name,
+ services[i].display_name);
+ }
+ }
+
+ } while (W_ERROR_EQUAL(result, WERR_MORE_DATA));
+
+done:
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return status;
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_status_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hSCM, hService;
+ WERROR result = WERR_GEN_FAILURE;
+ NTSTATUS status;
+ struct SERVICE_STATUS service_status;
+ struct QUERY_SERVICE_CONFIG config;
+ uint32_t buf_size = sizeof(config);
+ uint32_t ret_size = 0;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1 ) {
+ d_printf("%s net rpc service status <service>\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ /* Open the Service Control Manager */
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_ENUMERATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ /* Open the Service */
+
+ result = open_service(b, mem_ctx, &hSCM, argv[0],
+ (SC_RIGHT_SVC_QUERY_STATUS|SC_RIGHT_SVC_QUERY_CONFIG),
+ &hService);
+ if (!W_ERROR_IS_OK(result) ) {
+ goto done;
+ }
+
+ /* get the status */
+
+ status = dcerpc_svcctl_QueryServiceStatus(b, mem_ctx,
+ &hService,
+ &service_status,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Query status request failed. [%s]\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ if (!W_ERROR_IS_OK(result) ) {
+ d_fprintf(stderr, _("Query status request failed. [%s]\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ d_printf(_("%s service is %s.\n"), argv[0],
+ svc_status_string(service_status.state));
+
+ /* get the config */
+
+ status = dcerpc_svcctl_QueryServiceConfigW(b, mem_ctx,
+ &hService,
+ &config,
+ buf_size,
+ &ret_size,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Query config request failed. [%s]\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ if (W_ERROR_EQUAL(result, WERR_INSUFFICIENT_BUFFER)) {
+ buf_size = ret_size;
+ status = dcerpc_svcctl_QueryServiceConfigW(b, mem_ctx,
+ &hService,
+ &config,
+ buf_size,
+ &ret_size,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Query config request failed. [%s]\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ }
+
+ if (!W_ERROR_IS_OK(result) ) {
+ d_fprintf(stderr, _("Query config request failed. [%s]\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ /* print out the configuration information for the service */
+
+ d_printf(_("Configuration details:\n"));
+ d_printf(_("\tControls Accepted = 0x%x\n"),
+ service_status.controls_accepted);
+ d_printf(_("\tService Type = 0x%x\n"), config.service_type);
+ d_printf(_("\tStart Type = 0x%x\n"), config.start_type);
+ d_printf(_("\tError Control = 0x%x\n"), config.error_control);
+ d_printf(_("\tTag ID = 0x%x\n"), config.tag_id);
+
+ if (config.executablepath) {
+ d_printf(_("\tExecutable Path = %s\n"),
+ config.executablepath);
+ }
+
+ if (config.loadordergroup) {
+ d_printf(_("\tLoad Order Group = %s\n"),
+ config.loadordergroup);
+ }
+
+ if (config.dependencies) {
+ d_printf(_("\tDependencies = %s\n"),
+ config.dependencies);
+ }
+
+ if (config.startname) {
+ d_printf(_("\tStart Name = %s\n"), config.startname);
+ }
+
+ if (config.displayname) {
+ d_printf(_("\tDisplay Name = %s\n"),
+ config.displayname);
+ }
+
+done:
+ if (is_valid_policy_hnd(&hService)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hService, &_result);
+ }
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_stop_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hSCM;
+ WERROR result = WERR_GEN_FAILURE;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1 ) {
+ d_printf("%s net rpc service status <service>\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ /* Open the Service Control Manager */
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_ENUMERATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ result = control_service(pipe_hnd, mem_ctx, &hSCM, argv[0],
+ SVCCTL_CONTROL_STOP, SVCCTL_STOPPED );
+
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_pause_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hSCM;
+ WERROR result = WERR_GEN_FAILURE;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1 ) {
+ d_printf("%s net rpc service status <service>\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ /* Open the Service Control Manager */
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_ENUMERATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ result = control_service(pipe_hnd, mem_ctx, &hSCM, argv[0],
+ SVCCTL_CONTROL_PAUSE, SVCCTL_PAUSED );
+
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_resume_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hSCM;
+ WERROR result = WERR_GEN_FAILURE;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1 ) {
+ d_printf("%s net rpc service status <service>\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ /* Open the Service Control Manager */
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_ENUMERATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ result = control_service(pipe_hnd, mem_ctx, &hSCM, argv[0],
+ SVCCTL_CONTROL_CONTINUE, SVCCTL_RUNNING );
+
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_start_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv )
+{
+ struct policy_handle hSCM, hService;
+ WERROR result = WERR_GEN_FAILURE;
+ NTSTATUS status;
+ uint32_t state = 0;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1 ) {
+ d_printf("%s net rpc service status <service>\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ /* Open the Service Control Manager */
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_ENUMERATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+
+ /* Open the Service */
+
+ result = open_service(b, mem_ctx, &hSCM, argv[0],
+ SC_RIGHT_SVC_START,
+ &hService);
+ if (!W_ERROR_IS_OK(result) ) {
+ goto done;
+ }
+
+ /* get the status */
+
+ status = dcerpc_svcctl_StartServiceW(b, mem_ctx,
+ &hService,
+ 0,
+ NULL,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Query status request failed. [%s]\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result) ) {
+ d_fprintf(stderr, _("Query status request failed. [%s]\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ result = watch_service_state(pipe_hnd, mem_ctx, &hSCM, argv[0], SVCCTL_RUNNING, &state );
+
+ if ( W_ERROR_IS_OK(result) && (state == SVCCTL_RUNNING) )
+ d_printf(_("Successfully started service: %s\n"),
+ argv[0] );
+ else
+ d_fprintf(stderr,_("Failed to start service: %s [%s]\n"),
+ argv[0], win_errstr(result) );
+
+done:
+ if (is_valid_policy_hnd(&hService)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hService, &_result);
+ }
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_delete_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle hSCM, hService;
+ WERROR result = WERR_GEN_FAILURE;
+ NTSTATUS status;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 1 ) {
+ d_printf("%s net rpc service delete <service>\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(hSCM);
+ ZERO_STRUCT(hService);
+
+ /* Open the Service Control Manager */
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_ENUMERATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ /* Open the Service */
+
+ result = open_service(b, mem_ctx, &hSCM, argv[0],
+ SERVICE_ALL_ACCESS,
+ &hService);
+ if (!W_ERROR_IS_OK(result) ) {
+ goto done;
+ }
+
+ /* Delete the Service */
+
+ status = dcerpc_svcctl_DeleteService(b, mem_ctx,
+ &hService,
+ &result);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Delete service request failed. [%s]\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ d_fprintf(stderr, _("Delete service request failed. [%s]\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ d_printf(_("Successfully deleted Service: %s\n"), argv[0]);
+
+ done:
+ if (is_valid_policy_hnd(&hService)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hService, &_result);
+ }
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static NTSTATUS rpc_service_create_internal(struct net_context *c,
+ const struct dom_sid *domain_sid,
+ const char *domain_name,
+ struct cli_state *cli,
+ struct rpc_pipe_client *pipe_hnd,
+ TALLOC_CTX *mem_ctx,
+ int argc,
+ const char **argv)
+{
+ struct policy_handle hSCM, hService;
+ WERROR result = WERR_GEN_FAILURE;
+ NTSTATUS status;
+ const char *ServiceName;
+ const char *DisplayName;
+ const char *binary_path;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ if (argc != 3) {
+ d_printf("%s net rpc service create <service> "
+ "<displayname> <binarypath>\n", _("Usage:"));
+ return NT_STATUS_OK;
+ }
+
+ ZERO_STRUCT(hSCM);
+ ZERO_STRUCT(hService);
+
+ /* Open the Service Control Manager */
+ result = open_scm(b, mem_ctx, pipe_hnd->srv_name_slash,
+ SC_RIGHT_MGR_CREATE_SERVICE,
+ &hSCM);
+ if (!W_ERROR_IS_OK(result)) {
+ return werror_to_ntstatus(result);
+ }
+
+ /* Create the service */
+
+ ServiceName = argv[0];
+ DisplayName = argv[1];
+ binary_path = argv[2];
+
+ status = dcerpc_svcctl_CreateServiceW(b, mem_ctx,
+ &hSCM,
+ ServiceName,
+ DisplayName,
+ SERVICE_ALL_ACCESS,
+ SERVICE_TYPE_WIN32_OWN_PROCESS,
+ SVCCTL_DEMAND_START,
+ SVCCTL_SVC_ERROR_NORMAL,
+ binary_path,
+ NULL, /* LoadOrderGroupKey */
+ NULL, /* TagId */
+ NULL, /* dependencies */
+ 0, /* dependencies_size */
+ NULL, /* service_start_name */
+ NULL, /* password */
+ 0, /* password_size */
+ &hService,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ result = ntstatus_to_werror(status);
+ d_fprintf(stderr, _("Create service request failed. [%s]\n"),
+ nt_errstr(status));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(result)) {
+ d_fprintf(stderr, _("Create service request failed. [%s]\n"),
+ win_errstr(result));
+ goto done;
+ }
+
+ d_printf(_("Successfully created Service: %s\n"), argv[0]);
+
+ done:
+ if (is_valid_policy_hnd(&hService)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hService, &_result);
+ }
+ if (is_valid_policy_hnd(&hSCM)) {
+ WERROR _result;
+ dcerpc_svcctl_CloseServiceHandle(b, mem_ctx, &hSCM, &_result);
+ }
+
+ return werror_to_ntstatus(result);
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_list(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service list\n"
+ " %s\n",
+ _("Usage:"),
+ _("View configured Win32 services"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_list_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_start(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service start <service>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Start a Win32 service"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_start_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_stop(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service stop <service>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Stop a Win32 service"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_stop_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_resume(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service resume <service>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Resume a Win32 service"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_resume_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_pause(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service pause <service>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Pause a Win32 service"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_pause_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_status(struct net_context *c, int argc, const char **argv )
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service status <service>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Show the current status of a service"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_status_internal, argc, argv );
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_delete(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service delete <service>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Delete a Win32 service"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_delete_internal, argc, argv);
+}
+
+/********************************************************************
+********************************************************************/
+
+static int rpc_service_create(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net rpc service create <service>\n"
+ " %s\n",
+ _("Usage:"),
+ _("Create a Win32 service"));
+ return 0;
+ }
+
+ return run_rpc_command(c, NULL, &ndr_table_svcctl, 0,
+ rpc_service_create_internal, argc, argv);
+}
+
+/********************************************************************
+********************************************************************/
+
+int net_rpc_service(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ rpc_service_list,
+ NET_TRANSPORT_RPC,
+ N_("View configured Win32 services"),
+ N_("net rpc service list\n"
+ " View configured Win32 services")
+ },
+ {
+ "start",
+ rpc_service_start,
+ NET_TRANSPORT_RPC,
+ N_("Start a service"),
+ N_("net rpc service start\n"
+ " Start a service")
+ },
+ {
+ "stop",
+ rpc_service_stop,
+ NET_TRANSPORT_RPC,
+ N_("Stop a service"),
+ N_("net rpc service stop\n"
+ " Stop a service")
+ },
+ {
+ "pause",
+ rpc_service_pause,
+ NET_TRANSPORT_RPC,
+ N_("Pause a service"),
+ N_("net rpc service pause\n"
+ " Pause a service")
+ },
+ {
+ "resume",
+ rpc_service_resume,
+ NET_TRANSPORT_RPC,
+ N_("Resume a paused service"),
+ N_("net rpc service resume\n"
+ " Resume a service")
+ },
+ {
+ "status",
+ rpc_service_status,
+ NET_TRANSPORT_RPC,
+ N_("View current status of a service"),
+ N_("net rpc service status\n"
+ " View current status of a service")
+ },
+ {
+ "delete",
+ rpc_service_delete,
+ NET_TRANSPORT_RPC,
+ N_("Delete a service"),
+ N_("net rpc service delete\n"
+ " Deletes a service")
+ },
+ {
+ "create",
+ rpc_service_create,
+ NET_TRANSPORT_RPC,
+ N_("Create a service"),
+ N_("net rpc service create\n"
+ " Creates a service")
+ },
+
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rpc service",func);
+}
diff --git a/source3/utils/net_rpc_sh_acct.c b/source3/utils/net_rpc_sh_acct.c
new file mode 100644
index 0000000..3fc6568
--- /dev/null
+++ b/source3/utils/net_rpc_sh_acct.c
@@ -0,0 +1,489 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2006 Volker Lendecke (vl@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "includes.h"
+#include "utils/net.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "../libcli/security/security.h"
+
+/*
+ * Do something with the account policies. Read them all, run a function on
+ * them and possibly write them back. "fn" has to return the container index
+ * it has modified, it can return 0 for no change.
+ */
+
+static NTSTATUS rpc_sh_acct_do(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv,
+ int (*fn)(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv))
+{
+ struct policy_handle connect_pol, domain_pol;
+ NTSTATUS status, result;
+ union samr_DomainInfo *info1 = NULL;
+ union samr_DomainInfo *info3 = NULL;
+ union samr_DomainInfo *info12 = NULL;
+ int store;
+ struct dcerpc_binding_handle *b = pipe_hnd->binding_handle;
+
+ ZERO_STRUCT(connect_pol);
+ ZERO_STRUCT(domain_pol);
+
+ /* Get sam policy handle */
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ pipe_hnd->desthost,
+ MAXIMUM_ALLOWED_ACCESS,
+ &connect_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ /* Get domain policy handle */
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &connect_pol,
+ MAXIMUM_ALLOWED_ACCESS,
+ ctx->domain_sid,
+ &domain_pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ &domain_pol,
+ 1,
+ &info1,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("query_domain_info level 1 failed: %s\n"),
+ nt_errstr(result));
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ &domain_pol,
+ 3,
+ &info3,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("query_domain_info level 3 failed: %s\n"),
+ nt_errstr(result));
+ goto done;
+ }
+
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ &domain_pol,
+ 12,
+ &info12,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ d_fprintf(stderr, _("query_domain_info level 12 failed: %s\n"),
+ nt_errstr(result));
+ goto done;
+ }
+
+ store = fn(c, mem_ctx, ctx, &info1->info1, &info3->info3,
+ &info12->info12, argc, argv);
+
+ if (store <= 0) {
+ /* Don't save anything */
+ goto done;
+ }
+
+ switch (store) {
+ case 1:
+ status = dcerpc_samr_SetDomainInfo(b, mem_ctx,
+ &domain_pol,
+ 1,
+ info1,
+ &result);
+ break;
+ case 3:
+ status = dcerpc_samr_SetDomainInfo(b, mem_ctx,
+ &domain_pol,
+ 3,
+ info3,
+ &result);
+ break;
+ case 12:
+ status = dcerpc_samr_SetDomainInfo(b, mem_ctx,
+ &domain_pol,
+ 12,
+ info12,
+ &result);
+ break;
+ default:
+ d_fprintf(stderr, _("Got unexpected info level %d\n"), store);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+
+ done:
+ if (is_valid_policy_hnd(&domain_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &domain_pol, &result);
+ }
+ if (is_valid_policy_hnd(&connect_pol)) {
+ dcerpc_samr_Close(b, mem_ctx, &connect_pol, &result);
+ }
+
+ return status;
+}
+
+static int account_show(struct net_context *c,
+ TALLOC_CTX *mem_ctx, struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 0) {
+ d_fprintf(stderr, "%s %s\n", _("Usage:"), ctx->whoami);
+ return -1;
+ }
+
+ d_printf(_("Minimum password length: %d\n"), i1->min_password_length);
+ d_printf(_("Password history length: %d\n"),
+ i1->password_history_length);
+
+ d_printf(_("Minimum password age: "));
+ if (!nt_time_is_zero((NTTIME *)&i1->min_password_age)) {
+ time_t t = nt_time_to_unix_abs((NTTIME *)&i1->min_password_age);
+ d_printf(_("%d seconds\n"), (int)t);
+ } else {
+ d_printf(_("not set\n"));
+ }
+
+ d_printf(_("Maximum password age: "));
+ if (nt_time_is_set((NTTIME *)&i1->max_password_age)) {
+ time_t t = nt_time_to_unix_abs((NTTIME *)&i1->max_password_age);
+ d_printf(_("%d seconds\n"), (int)t);
+ } else {
+ d_printf(_("not set\n"));
+ }
+
+ d_printf(_("Bad logon attempts: %d\n"), i12->lockout_threshold);
+
+ if (i12->lockout_threshold != 0) {
+
+ d_printf(_("Account lockout duration: "));
+ if (nt_time_is_set(&i12->lockout_duration)) {
+ time_t t = nt_time_to_unix_abs(&i12->lockout_duration);
+ d_printf(_("%d seconds\n"), (int)t);
+ } else {
+ d_printf(_("not set\n"));
+ }
+
+ d_printf(_("Bad password count reset after: "));
+ if (nt_time_is_set(&i12->lockout_window)) {
+ time_t t = nt_time_to_unix_abs(&i12->lockout_window);
+ d_printf(_("%d seconds\n"), (int)t);
+ } else {
+ d_printf(_("not set\n"));
+ }
+ }
+
+ d_printf(_("Disconnect users when logon hours expire: %s\n"),
+ nt_time_is_zero(&i3->force_logoff_time) ? _("yes") : _("no"));
+
+ d_printf(_("User must logon to change password: %s\n"),
+ (i1->password_properties & 0x2) ? _("yes") : _("no"));
+
+ return 0; /* Don't save */
+}
+
+static NTSTATUS rpc_sh_acct_pol_show(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv) {
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_show);
+}
+
+static int account_set_badpw(struct net_context *c,
+ TALLOC_CTX *mem_ctx, struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, "%s %s <count>\n", _("Usage:"), ctx->whoami);
+ return -1;
+ }
+
+ i12->lockout_threshold = atoi(argv[0]);
+ d_printf(_("Setting bad password count to %d\n"),
+ i12->lockout_threshold);
+
+ return 12;
+}
+
+static NTSTATUS rpc_sh_acct_set_badpw(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_set_badpw);
+}
+
+static int account_set_lockduration(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, _("Usage: %s <count>\n"), ctx->whoami);
+ return -1;
+ }
+
+ unix_to_nt_time_abs(&i12->lockout_duration, atoi(argv[0]));
+ d_printf(_("Setting lockout duration to %d seconds\n"),
+ (int)nt_time_to_unix_abs(&i12->lockout_duration));
+
+ return 12;
+}
+
+static NTSTATUS rpc_sh_acct_set_lockduration(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_set_lockduration);
+}
+
+static int account_set_resetduration(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, _("Usage: %s <count>\n"), ctx->whoami);
+ return -1;
+ }
+
+ unix_to_nt_time_abs(&i12->lockout_window, atoi(argv[0]));
+ d_printf(_("Setting bad password reset duration to %d seconds\n"),
+ (int)nt_time_to_unix_abs(&i12->lockout_window));
+
+ return 12;
+}
+
+static NTSTATUS rpc_sh_acct_set_resetduration(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_set_resetduration);
+}
+
+static int account_set_minpwage(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, _("Usage: %s <count>\n"), ctx->whoami);
+ return -1;
+ }
+
+ unix_to_nt_time_abs((NTTIME *)&i1->min_password_age, atoi(argv[0]));
+ d_printf(_("Setting minimum password age to %d seconds\n"),
+ (int)nt_time_to_unix_abs((NTTIME *)&i1->min_password_age));
+
+ return 1;
+}
+
+static NTSTATUS rpc_sh_acct_set_minpwage(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_set_minpwage);
+}
+
+static int account_set_maxpwage(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, _("Usage: %s <count>\n"), ctx->whoami);
+ return -1;
+ }
+
+ unix_to_nt_time_abs((NTTIME *)&i1->max_password_age, atoi(argv[0]));
+ d_printf(_("Setting maximum password age to %d seconds\n"),
+ (int)nt_time_to_unix_abs((NTTIME *)&i1->max_password_age));
+
+ return 1;
+}
+
+static NTSTATUS rpc_sh_acct_set_maxpwage(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_set_maxpwage);
+}
+
+static int account_set_minpwlen(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, _("Usage: %s <count>\n"), ctx->whoami);
+ return -1;
+ }
+
+ i1->min_password_length = atoi(argv[0]);
+ d_printf(_("Setting minimum password length to %d\n"),
+ i1->min_password_length);
+
+ return 1;
+}
+
+static NTSTATUS rpc_sh_acct_set_minpwlen(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_set_minpwlen);
+}
+
+static int account_set_pwhistlen(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct samr_DomInfo1 *i1,
+ struct samr_DomInfo3 *i3,
+ struct samr_DomInfo12 *i12,
+ int argc, const char **argv)
+{
+ if (argc != 1) {
+ d_fprintf(stderr, _("Usage: %s <count>\n"), ctx->whoami);
+ return -1;
+ }
+
+ i1->password_history_length = atoi(argv[0]);
+ d_printf(_("Setting password history length to %d\n"),
+ i1->password_history_length);
+
+ return 1;
+}
+
+static NTSTATUS rpc_sh_acct_set_pwhistlen(struct net_context *c,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_sh_acct_do(c, mem_ctx, ctx, pipe_hnd, argc, argv,
+ account_set_pwhistlen);
+}
+
+struct rpc_sh_cmd *net_rpc_acct_cmds(struct net_context *c, TALLOC_CTX *mem_ctx,
+ struct rpc_sh_ctx *ctx)
+{
+ static struct rpc_sh_cmd cmds[9] = {
+ { "show", NULL, &ndr_table_samr, rpc_sh_acct_pol_show,
+ N_("Show current account policy settings") },
+ { "badpw", NULL, &ndr_table_samr, rpc_sh_acct_set_badpw,
+ N_("Set bad password count before lockout") },
+ { "lockduration", NULL, &ndr_table_samr, rpc_sh_acct_set_lockduration,
+ N_("Set account lockout duration") },
+ { "resetduration", NULL, &ndr_table_samr,
+ rpc_sh_acct_set_resetduration,
+ N_("Set bad password count reset duration") },
+ { "minpwage", NULL, &ndr_table_samr, rpc_sh_acct_set_minpwage,
+ N_("Set minimum password age") },
+ { "maxpwage", NULL, &ndr_table_samr, rpc_sh_acct_set_maxpwage,
+ N_("Set maximum password age") },
+ { "minpwlen", NULL, &ndr_table_samr, rpc_sh_acct_set_minpwlen,
+ N_("Set minimum password length") },
+ { "pwhistlen", NULL, &ndr_table_samr, rpc_sh_acct_set_pwhistlen,
+ N_("Set the password history length") },
+ { NULL, NULL, 0, NULL, NULL }
+ };
+
+ return cmds;
+}
diff --git a/source3/utils/net_rpc_shell.c b/source3/utils/net_rpc_shell.c
new file mode 100644
index 0000000..1ea7080
--- /dev/null
+++ b/source3/utils/net_rpc_shell.c
@@ -0,0 +1,306 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Shell around net rpc subcommands
+ * Copyright (C) Volker Lendecke 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "utils/net.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_samr.h"
+#include "lib/netapi/netapi.h"
+#include "lib/netapi/netapi_net.h"
+#include "../libcli/smbreadline/smbreadline.h"
+#include "libsmb/libsmb.h"
+#include "libcli/security/dom_sid.h"
+
+#include <popt.h>
+
+static NTSTATUS rpc_sh_info(struct net_context *c,
+ TALLOC_CTX *mem_ctx, struct rpc_sh_ctx *ctx,
+ struct rpc_pipe_client *pipe_hnd,
+ int argc, const char **argv)
+{
+ return rpc_info_internals(c, ctx->domain_sid, ctx->domain_name,
+ ctx->cli, pipe_hnd, mem_ctx,
+ argc, argv);
+}
+
+static struct rpc_sh_ctx *this_ctx;
+
+static char **completion_fn(const char *text, int start, int end)
+{
+ char **cmds = NULL;
+ int n_cmds = 0;
+ struct rpc_sh_cmd *c;
+
+ if (start != 0) {
+ return NULL;
+ }
+
+ ADD_TO_ARRAY(NULL, char *, SMB_STRDUP(text), &cmds, &n_cmds);
+
+ for (c = this_ctx->cmds; c->name != NULL; c++) {
+ bool match = (strncmp(text, c->name, strlen(text)) == 0);
+
+ if (match) {
+ ADD_TO_ARRAY(NULL, char *, SMB_STRDUP(c->name),
+ &cmds, &n_cmds);
+ }
+ }
+
+ if (n_cmds == 2) {
+ SAFE_FREE(cmds[0]);
+ cmds[0] = cmds[1];
+ n_cmds -= 1;
+ }
+
+ ADD_TO_ARRAY(NULL, char *, NULL, &cmds, &n_cmds);
+ return cmds;
+}
+
+static NTSTATUS net_sh_run(struct net_context *c,
+ struct rpc_sh_ctx *ctx, struct rpc_sh_cmd *cmd,
+ int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ NTSTATUS status;
+
+ mem_ctx = talloc_new(ctx);
+ if (mem_ctx == NULL) {
+ d_fprintf(stderr, _("talloc_new failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cli_rpc_pipe_open_noauth(ctx->cli, cmd->table,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not open pipe: %s\n"),
+ nt_errstr(status));
+ return status;
+ }
+
+ status = cmd->fn(c, mem_ctx, ctx, pipe_hnd, argc, argv);
+
+ TALLOC_FREE(pipe_hnd);
+
+ talloc_destroy(mem_ctx);
+
+ return status;
+}
+
+static bool net_sh_process(struct net_context *c,
+ struct rpc_sh_ctx *ctx,
+ int argc, const char **argv)
+{
+ struct rpc_sh_cmd *cmd;
+ struct rpc_sh_ctx *new_ctx;
+ NTSTATUS status;
+
+ if (argc == 0) {
+ return true;
+ }
+
+ if (ctx == this_ctx) {
+
+ /* We've been called from the cmd line */
+ if (strequal(argv[0], "..") &&
+ (this_ctx->parent != NULL)) {
+ new_ctx = this_ctx->parent;
+ TALLOC_FREE(this_ctx);
+ this_ctx = new_ctx;
+ return true;
+ }
+ }
+
+ if (strequal(argv[0], "exit") ||
+ strequal(argv[0], "quit") ||
+ strequal(argv[0], "q")) {
+ return false;
+ }
+
+ if (strequal(argv[0], "help") || strequal(argv[0], "?")) {
+ for (cmd = ctx->cmds; cmd->name != NULL; cmd++) {
+ if (ctx != this_ctx) {
+ d_printf("%s ", ctx->whoami);
+ }
+ d_printf("%-15s %s\n", cmd->name, cmd->help);
+ }
+ return true;
+ }
+
+ for (cmd = ctx->cmds; cmd->name != NULL; cmd++) {
+ if (strequal(cmd->name, argv[0])) {
+ break;
+ }
+ }
+
+ if (cmd->name == NULL) {
+ /* None found */
+ d_fprintf(stderr,_( "%s: unknown cmd\n"), argv[0]);
+ return true;
+ }
+
+ new_ctx = talloc(ctx, struct rpc_sh_ctx);
+ if (new_ctx == NULL) {
+ d_fprintf(stderr, _("talloc failed\n"));
+ return false;
+ }
+ new_ctx->cli = ctx->cli;
+ new_ctx->whoami = talloc_asprintf(new_ctx, "%s %s",
+ ctx->whoami, cmd->name);
+ new_ctx->thiscmd = talloc_strdup(new_ctx, cmd->name);
+
+ if (cmd->sub != NULL) {
+ new_ctx->cmds = cmd->sub(c, new_ctx, ctx);
+ } else {
+ new_ctx->cmds = NULL;
+ }
+
+ new_ctx->parent = ctx;
+ new_ctx->domain_name = ctx->domain_name;
+ new_ctx->domain_sid = ctx->domain_sid;
+
+ argc -= 1;
+ argv += 1;
+
+ if (cmd->sub != NULL) {
+ if (argc == 0) {
+ this_ctx = new_ctx;
+ return true;
+ }
+ return net_sh_process(c, new_ctx, argc, argv);
+ }
+
+ status = net_sh_run(c, new_ctx, cmd, argc, argv);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("%s failed: %s\n"), new_ctx->whoami,
+ nt_errstr(status));
+ }
+
+ return true;
+}
+
+static struct rpc_sh_cmd sh_cmds[6] = {
+
+ { "info", NULL, &ndr_table_samr, rpc_sh_info,
+ N_("Print information about the domain connected to") },
+
+ { "rights", net_rpc_rights_cmds, 0, NULL,
+ N_("List/Grant/Revoke user rights") },
+
+ { "share", net_rpc_share_cmds, 0, NULL,
+ N_("List/Add/Remove etc shares") },
+
+ { "user", net_rpc_user_cmds, 0, NULL,
+ N_("List/Add/Remove user info") },
+
+ { "account", net_rpc_acct_cmds, 0, NULL,
+ N_("Show/Change account policy settings") },
+
+ { NULL, NULL, 0, NULL, NULL }
+};
+
+int net_rpc_shell(struct net_context *c, int argc, const char **argv)
+{
+ NTSTATUS status;
+ struct rpc_sh_ctx *ctx;
+ struct dom_sid_buf buf;
+ NET_API_STATUS net_api_status;
+
+ if (argc != 0 || c->display_usage) {
+ d_printf("%s\nnet rpc shell\n", _("Usage:"));
+ return -1;
+ }
+
+ net_api_status = libnetapi_net_init(&c->netapi_ctx, c->lp_ctx, c->creds);
+ if (net_api_status != 0) {
+ return -1;
+ }
+
+ ctx = talloc(NULL, struct rpc_sh_ctx);
+ if (ctx == NULL) {
+ d_fprintf(stderr, _("talloc failed\n"));
+ return -1;
+ }
+
+ status = net_make_ipc_connection(c, 0, &(ctx->cli));
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not open connection: %s\n"),
+ nt_errstr(status));
+ return -1;
+ }
+
+ ctx->cmds = sh_cmds;
+ ctx->whoami = "net rpc";
+ ctx->parent = NULL;
+
+ status = net_get_remote_domain_sid(ctx->cli, ctx, &ctx->domain_sid,
+ &ctx->domain_name);
+ if (!NT_STATUS_IS_OK(status)) {
+ return -1;
+ }
+
+ d_printf(_("Talking to domain %s (%s)\n"), ctx->domain_name,
+ dom_sid_str_buf(ctx->domain_sid, &buf));
+
+ this_ctx = ctx;
+
+ while(1) {
+ char *prompt = NULL;
+ char *line = NULL;
+ int ret;
+
+ if (asprintf(&prompt, "%s> ", this_ctx->whoami) < 0) {
+ break;
+ }
+
+ line = smb_readline(prompt, NULL, completion_fn);
+ SAFE_FREE(prompt);
+
+ if (line == NULL) {
+ break;
+ }
+
+ ret = poptParseArgvString(line, &argc, &argv);
+ if (ret == POPT_ERROR_NOARG) {
+ SAFE_FREE(line);
+ continue;
+ }
+ if (ret != 0) {
+ d_fprintf(stderr, _("cmdline invalid: %s\n"),
+ poptStrerror(ret));
+ SAFE_FREE(line);
+ return false;
+ }
+
+ if ((line[0] != '\n') &&
+ (!net_sh_process(c, this_ctx, argc, argv))) {
+ SAFE_FREE(line);
+ break;
+ }
+ SAFE_FREE(line);
+ }
+
+ cli_shutdown(ctx->cli);
+
+ TALLOC_FREE(ctx);
+
+ return 0;
+}
diff --git a/source3/utils/net_rpc_trust.c b/source3/utils/net_rpc_trust.c
new file mode 100644
index 0000000..a3354ad
--- /dev/null
+++ b/source3/utils/net_rpc_trust.c
@@ -0,0 +1,735 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+ Copyright (C) 2011 Sumit Bose (sbose@redhat.com)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+
+#include "includes.h"
+#include "utils/net.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "../libcli/security/dom_sid.h"
+#include "libsmb/libsmb.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#define ARG_OTHERSERVER "otherserver="
+#define ARG_OTHERUSER "otheruser="
+#define ARG_OTHERDOMAINSID "otherdomainsid="
+#define ARG_OTHERDOMAIN "otherdomain="
+#define ARG_OTHERNETBIOSDOMAIN "other_netbios_domain="
+#define ARG_TRUSTPW "trustpw="
+
+enum trust_op {
+ TRUST_CREATE,
+ TRUST_DELETE
+};
+
+struct other_dom_data {
+ char *host;
+ char *user_name;
+ char *domain_sid_str;
+ char *dns_domain_name;
+ char *domain_name;
+};
+
+struct dom_data {
+ struct dom_sid *domsid;
+ char *dns_domain_name;
+ char *domain_name;
+};
+
+static NTSTATUS close_handle(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *bind_hnd,
+ struct policy_handle *pol_hnd)
+{
+ NTSTATUS status;
+ NTSTATUS result;
+
+ status = dcerpc_lsa_Close(bind_hnd, mem_ctx, pol_hnd, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_lsa_Close failed with error [%s].\n",
+ nt_errstr(status)));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0, ("lsa close failed with error [%s].\n",
+ nt_errstr(result)));
+ return result;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS delete_trust(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *bind_hnd,
+ struct policy_handle *pol_hnd,
+ struct dom_sid *domsid)
+{
+ NTSTATUS status;
+ struct lsa_DeleteTrustedDomain dr;
+
+ dr.in.handle = pol_hnd;
+ dr.in.dom_sid = domsid;
+
+ status = dcerpc_lsa_DeleteTrustedDomain_r(bind_hnd, mem_ctx, &dr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_lsa_DeleteTrustedDomain_r failed with [%s]\n",
+ nt_errstr(status)));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(dr.out.result)) {
+ DEBUG(0, ("DeleteTrustedDomain returned [%s]\n",
+ nt_errstr(dr.out.result)));
+ return dr.out.result;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS create_trust(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *bind_hnd,
+ struct policy_handle *pol_hnd,
+ const char *trust_name,
+ const char *trust_name_dns,
+ struct dom_sid *domsid,
+ struct lsa_TrustDomainInfoAuthInfoInternal *authinfo)
+{
+ NTSTATUS status;
+ struct lsa_CreateTrustedDomainEx2 r;
+ struct lsa_TrustDomainInfoInfoEx trustinfo;
+ struct policy_handle trustdom_handle;
+ bool is_nt4 = trust_name_dns == NULL;
+
+ if (!is_nt4) {
+ fprintf(stdout, "Creating AD trust\n");
+ trustinfo.trust_type = LSA_TRUST_TYPE_UPLEVEL;
+ trustinfo.trust_attributes = LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE;
+ } else {
+ fprintf(stdout, "Creating NT4 trust\n");
+ trustinfo.trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+ trustinfo.trust_attributes = 0;
+ trust_name_dns = trust_name;
+ }
+
+ trustinfo.sid = domsid;
+ trustinfo.netbios_name.string = trust_name;
+ trustinfo.domain_name.string = trust_name_dns;
+
+ trustinfo.trust_direction = LSA_TRUST_DIRECTION_INBOUND |
+ LSA_TRUST_DIRECTION_OUTBOUND;
+
+ r.in.policy_handle = pol_hnd;
+ r.in.info = &trustinfo;
+ r.in.auth_info_internal = authinfo;
+ r.in.access_mask = LSA_TRUSTED_SET_POSIX | LSA_TRUSTED_SET_AUTH |
+ LSA_TRUSTED_QUERY_DOMAIN_NAME;
+ r.out.trustdom_handle = &trustdom_handle;
+
+ status = dcerpc_lsa_CreateTrustedDomainEx2_r(bind_hnd, mem_ctx, &r);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_lsa_CreateTrustedDomainEx2_r failed "
+ "with error [%s].\n", nt_errstr(status)));
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(r.out.result)) {
+ DEBUG(0, ("CreateTrustedDomainEx2_r returned [%s].\n",
+ nt_errstr(r.out.result)));
+ return r.out.result;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS get_domain_info(TALLOC_CTX *mem_ctx,
+ struct dcerpc_binding_handle *bind_hdn,
+ struct policy_handle *pol_hnd,
+ struct dom_data *dom_data)
+{
+ NTSTATUS status;
+ struct lsa_QueryInfoPolicy2 qr;
+ struct dom_sid_buf buf;
+
+ qr.in.handle = pol_hnd;
+ qr.in.level = LSA_POLICY_INFO_DNS;
+
+ status = dcerpc_lsa_QueryInfoPolicy2_r(bind_hdn, mem_ctx, &qr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("dcerpc_lsa_QueryInfoPolicy2_r failed "
+ "with error [%s].\n", nt_errstr(status)));
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(qr.out.result)) {
+ DEBUG(0, ("QueryInfoPolicy2 returned [%s].\n",
+ nt_errstr(qr.out.result)));
+ return qr.out.result;
+ }
+
+ dom_data->domain_name = talloc_strdup(mem_ctx,
+ (*qr.out.info)->dns.name.string);
+ dom_data->dns_domain_name = talloc_strdup(mem_ctx,
+ (*qr.out.info)->dns.dns_domain.string);
+ dom_data->domsid = dom_sid_dup(mem_ctx, (*qr.out.info)->dns.sid);
+ if (dom_data->domain_name == NULL ||
+ dom_data->dns_domain_name == NULL ||
+ dom_data->domsid == NULL) {
+ DEBUG(0, ("Copying domain data failed.\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(0, ("Got the following domain info [%s][%s][%s].\n",
+ dom_data->domain_name, dom_data->dns_domain_name,
+ dom_sid_str_buf(dom_data->domsid, &buf)));
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS connect_and_get_info(TALLOC_CTX *mem_ctx,
+ struct net_context *net_ctx,
+ struct cli_state **cli,
+ struct rpc_pipe_client **pipe_hnd,
+ struct policy_handle *pol_hnd,
+ struct dom_data *dom_data,
+ DATA_BLOB *session_key)
+{
+ NTSTATUS status;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ uint32_t out_version = 0;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+
+ status = net_make_ipc_connection_ex(net_ctx, NULL, NULL, NULL,
+ NET_FLAGS_PDC, cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to connect to [%s] with error [%s]\n",
+ net_ctx->opt_host, nt_errstr(status)));
+ return status;
+ }
+
+ status = cli_rpc_pipe_open_noauth(*cli, &ndr_table_lsarpc, pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to initialise lsa pipe with error [%s]\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(
+ (*pipe_hnd)->binding_handle,
+ mem_ctx,
+ (*pipe_hnd)->srv_name_slash,
+ false,
+ LSA_POLICY_VIEW_LOCAL_INFORMATION |
+ LSA_POLICY_TRUST_ADMIN |
+ LSA_POLICY_CREATE_SECRET,
+ &out_version,
+ &out_revision_info,
+ pol_hnd,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ DBG_ERR("Failed to open policy handle: %s\n",
+ nt_errstr(result));
+ return status;
+ }
+
+ status = get_domain_info(mem_ctx, (*pipe_hnd)->binding_handle,
+ pol_hnd, dom_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("get_domain_info failed with error [%s].\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ status = cli_get_session_key(mem_ctx, *pipe_hnd, session_key);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Error getting session_key of LSA pipe. Error was %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static bool get_trust_domain_passwords_auth_blob(TALLOC_CTX *mem_ctx,
+ const char *password,
+ DATA_BLOB *auth_blob)
+{
+ struct trustDomainPasswords auth_struct;
+ struct AuthenticationInformation *auth_info_array;
+ enum ndr_err_code ndr_err;
+ size_t converted_size;
+
+ generate_random_buffer(auth_struct.confounder,
+ sizeof(auth_struct.confounder));
+
+ auth_info_array = talloc_array(mem_ctx,
+ struct AuthenticationInformation, 1);
+ if (auth_info_array == NULL) {
+ return false;
+ }
+
+ auth_info_array[0].AuthType = TRUST_AUTH_TYPE_CLEAR;
+ if (!convert_string_talloc(mem_ctx, CH_UNIX, CH_UTF16, password,
+ strlen(password),
+ &auth_info_array[0].AuthInfo.clear.password,
+ &converted_size)) {
+ return false;
+ }
+
+ auth_info_array[0].AuthInfo.clear.size = converted_size;
+
+ auth_struct.outgoing.count = 1;
+ auth_struct.outgoing.current.count = 1;
+ auth_struct.outgoing.current.array = auth_info_array;
+ auth_struct.outgoing.previous.count = 0;
+ auth_struct.outgoing.previous.array = NULL;
+
+ auth_struct.incoming.count = 1;
+ auth_struct.incoming.current.count = 1;
+ auth_struct.incoming.current.array = auth_info_array;
+ auth_struct.incoming.previous.count = 0;
+ auth_struct.incoming.previous.array = NULL;
+
+ ndr_err = ndr_push_struct_blob(auth_blob, mem_ctx, &auth_struct,
+ (ndr_push_flags_fn_t)ndr_push_trustDomainPasswords);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ return false;
+ }
+
+ return true;
+}
+
+static int parse_trust_args(TALLOC_CTX *mem_ctx, int argc, const char **argv, struct other_dom_data **_o, char **_trustpw)
+{
+ size_t c;
+ struct other_dom_data *o = NULL;
+ char *trustpw = NULL;
+ int ret = EFAULT;
+
+ if (argc == 0) {
+ return EINVAL;
+ }
+
+ o = talloc_zero(mem_ctx, struct other_dom_data);
+ if (o == NULL) {
+ DEBUG(0, ("talloc_zero failed.\n"));
+ return ENOMEM;
+ }
+
+ for (c = 0; c < argc; c++) {
+ if (strnequal(argv[c], ARG_OTHERSERVER, sizeof(ARG_OTHERSERVER)-1)) {
+ o->host = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERSERVER)-1);
+ if (o->host == NULL) {
+ ret = ENOMEM;
+ goto failed;
+ }
+ } else if (strnequal(argv[c], ARG_OTHERUSER, sizeof(ARG_OTHERUSER)-1)) {
+ o->user_name = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERUSER)-1);
+ if (o->user_name == NULL) {
+ ret = ENOMEM;
+ goto failed;
+ }
+ } else if (strnequal(argv[c], ARG_OTHERDOMAINSID, sizeof(ARG_OTHERDOMAINSID)-1)) {
+ o->domain_sid_str = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERDOMAINSID)-1);
+ if (o->domain_sid_str == NULL) {
+ ret = ENOMEM;
+ goto failed;
+ }
+ } else if (strnequal(argv[c], ARG_OTHERDOMAIN, sizeof(ARG_OTHERDOMAIN)-1)) {
+ o->dns_domain_name = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERDOMAIN)-1);
+ if (o->dns_domain_name == NULL) {
+ ret = ENOMEM;
+ goto failed;
+ }
+ } else if (strnequal(argv[c], ARG_OTHERNETBIOSDOMAIN, sizeof(ARG_OTHERNETBIOSDOMAIN)-1)) {
+ o->domain_name = talloc_strdup(o, argv[c] + sizeof(ARG_OTHERNETBIOSDOMAIN)-1);
+ if (o->domain_name == NULL) {
+ ret = ENOMEM;
+ goto failed;
+ }
+ } else if (strnequal(argv[c], ARG_TRUSTPW, sizeof(ARG_TRUSTPW)-1)) {
+ trustpw = talloc_strdup(mem_ctx, argv[c] + sizeof(ARG_TRUSTPW)-1);
+ if (trustpw == NULL) {
+ ret = ENOMEM;
+ goto failed;
+ }
+ } else {
+ DEBUG(0, ("Unsupported option [%s].\n", argv[c]));
+ ret = EINVAL;
+ goto failed;
+ }
+ }
+
+ *_o = o;
+ *_trustpw = trustpw;
+
+ return 0;
+
+failed:
+ talloc_free(o);
+ talloc_free(trustpw);
+ return ret;
+}
+
+static void print_trust_delete_usage(void)
+{
+ d_printf( "%s\n"
+ "net rpc trust delete [options]\n"
+ "\nOptions:\n"
+ "\totherserver=DC in other domain\n"
+ "\totheruser=Admin user in other domain\n"
+ "\totherdomainsid=SID of other domain\n"
+ "\nExamples:\n"
+ "\tnet rpc trust delete otherserver=oname otheruser=ouser -S lname -U luser\n"
+ "\tnet rpc trust delete otherdomainsid=S-... -S lname -U luser\n"
+ " %s\n",
+ _("Usage:"),
+ _("Remove trust between two domains"));
+}
+
+static void print_trust_usage(void)
+{
+ d_printf( "%s\n"
+ "net rpc trust create [options]\n"
+ "\nOptions:\n"
+ "\totherserver=DC in other domain\n"
+ "\totheruser=Admin user in other domain\n"
+ "\totherdomainsid=SID of other domain\n"
+ "\tother_netbios_domain=NetBIOS/short name of other domain\n"
+ "\totherdomain=Full/DNS name of other domain (if not used, create an NT4 trust)\n"
+ "\ttrustpw=Trust password\n"
+ "\nExamples:\n"
+ "\tnet rpc trust create otherserver=oname otheruser=ouser -S lname -U luser\n"
+ "\tnet rpc trust create otherdomainsid=S-... other_netbios_domain=odom otherdomain=odom.org trustpw=secret -S lname -U luser\n"
+ " %s\n",
+ _("Usage:"),
+ _("Create trust between two domains"));
+}
+
+static int rpc_trust_common(struct net_context *net_ctx, int argc,
+ const char **argv, enum trust_op op)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+ int ret;
+ int success = -1;
+ struct cli_state *cli[2] = {NULL, NULL};
+ struct rpc_pipe_client *pipe_hnd[2] = {NULL, NULL};
+ DATA_BLOB session_key[2];
+ struct policy_handle pol_hnd[2];
+ struct lsa_TrustDomainInfoAuthInfoInternal authinfo;
+ DATA_BLOB auth_blob;
+ char *trust_pw = NULL;
+ struct other_dom_data *other_dom_data;
+ struct net_context *other_net_ctx = NULL;
+ struct dom_data dom_data[2];
+ void (*usage)(void);
+
+ ZERO_STRUCT(session_key);
+
+ switch (op) {
+ case TRUST_CREATE:
+ usage = print_trust_usage;
+ break;
+ case TRUST_DELETE:
+ usage = print_trust_delete_usage;
+ break;
+ default:
+ DEBUG(0, ("Unsupported trust operation.\n"));
+ return -1;
+ }
+
+ if (net_ctx->display_usage) {
+ usage();
+ return 0;
+ }
+
+ mem_ctx = talloc_init("trust op");
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("talloc_init failed.\n"));
+ return -1;
+ }
+
+ ret = parse_trust_args(mem_ctx, argc, argv, &other_dom_data, &trust_pw);
+ if (ret != 0) {
+ if (ret == EINVAL) {
+ usage();
+ } else {
+ DEBUG(0, ("Failed to parse arguments.\n"));
+ }
+ goto done;
+ }
+
+ if (other_dom_data->host != 0) {
+ other_net_ctx = talloc_zero(other_dom_data, struct net_context);
+ if (other_net_ctx == NULL) {
+ DEBUG(0, ("talloc_zero failed.\n"));
+ goto done;
+ }
+
+ other_net_ctx->opt_host = other_dom_data->host;
+ other_net_ctx->creds = cli_credentials_init(other_net_ctx);
+ cli_credentials_parse_string(other_net_ctx->creds,
+ other_dom_data->user_name,
+ CRED_SPECIFIED);
+ } else {
+ dom_data[1].domsid = dom_sid_parse_talloc(mem_ctx,
+ other_dom_data->domain_sid_str);
+ dom_data[1].domain_name = other_dom_data->domain_name;
+ dom_data[1].dns_domain_name = other_dom_data->dns_domain_name;
+
+ if (dom_data[1].dns_domain_name == NULL) {
+ fprintf(stdout, "No DNS domain name passed, "
+ "assuming NT4 trust!\n");
+ }
+
+ if (dom_data[1].domsid == NULL ||
+ (op == TRUST_CREATE &&
+ (dom_data[1].domain_name == NULL))) {
+ DEBUG(0, ("Missing required argument.\n"));
+ usage();
+ goto done;
+ }
+ }
+
+ status = connect_and_get_info(mem_ctx, net_ctx, &cli[0], &pipe_hnd[0],
+ &pol_hnd[0], &dom_data[0], &session_key[0]);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("connect_and_get_info failed with error [%s]\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ if (other_net_ctx != NULL) {
+ status = connect_and_get_info(mem_ctx, other_net_ctx,
+ &cli[1], &pipe_hnd[1],
+ &pol_hnd[1], &dom_data[1],
+ &session_key[1]);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("connect_and_get_info failed with error [%s]\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ if (op == TRUST_CREATE) {
+ gnutls_cipher_hd_t cipher_hnd = NULL;
+ gnutls_datum_t enc_session_key = {
+ .data = session_key[0].data,
+ .size = session_key[0].length,
+ };
+ int rc;
+
+ if (trust_pw == NULL) {
+ if (other_net_ctx == NULL) {
+ DEBUG(0, ("Missing either trustpw or otherhost.\n"));
+ goto done;
+ }
+
+ DEBUG(0, ("Using random trust password.\n"));
+ trust_pw = trust_pw_new_value(mem_ctx,
+ SEC_CHAN_DOMAIN,
+ SEC_DOMAIN);
+ if (trust_pw == NULL) {
+ DEBUG(0, ("generate_random_password failed.\n"));
+ goto done;
+ }
+ } else {
+ DEBUG(0, ("Using user provided password.\n"));
+ }
+
+ if (!get_trust_domain_passwords_auth_blob(mem_ctx, trust_pw,
+ &auth_blob)) {
+ DEBUG(0, ("get_trust_domain_passwords_auth_blob failed\n"));
+ goto done;
+ }
+
+ authinfo.auth_blob.data = (uint8_t *)talloc_memdup(
+ mem_ctx,
+ auth_blob.data,
+ auth_blob.length);
+ if (authinfo.auth_blob.data == NULL) {
+ goto done;
+ }
+ authinfo.auth_blob.size = auth_blob.length;
+
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &enc_session_key,
+ NULL);
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto done;
+ }
+ rc = gnutls_cipher_encrypt(cipher_hnd,
+ authinfo.auth_blob.data,
+ authinfo.auth_blob.size);
+ gnutls_cipher_deinit(cipher_hnd);
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto done;
+ }
+
+ status = create_trust(mem_ctx, pipe_hnd[0]->binding_handle,
+ &pol_hnd[0],
+ dom_data[1].domain_name,
+ dom_data[1].dns_domain_name,
+ dom_data[1].domsid,
+ &authinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("create_trust failed with error [%s].\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ if (other_net_ctx != NULL) {
+ talloc_free(authinfo.auth_blob.data);
+ authinfo.auth_blob.data = (uint8_t *)talloc_memdup(
+ mem_ctx,
+ auth_blob.data,
+ auth_blob.length);
+ if (authinfo.auth_blob.data == NULL) {
+ goto done;
+ }
+ authinfo.auth_blob.size = auth_blob.length;
+
+ enc_session_key = (gnutls_datum_t) {
+ .data = session_key[1].data,
+ .size = session_key[1].length,
+ };
+
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &enc_session_key,
+ NULL);
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto done;
+ }
+ rc = gnutls_cipher_encrypt(cipher_hnd,
+ authinfo.auth_blob.data,
+ authinfo.auth_blob.size);
+ gnutls_cipher_deinit(cipher_hnd);
+ if (rc < 0) {
+ status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID);
+ goto done;
+ }
+
+ status = create_trust(mem_ctx,
+ pipe_hnd[1]->binding_handle,
+ &pol_hnd[1],
+ dom_data[0].domain_name,
+ dom_data[0].dns_domain_name,
+ dom_data[0].domsid, &authinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("create_trust failed with error [%s].\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+ } else if (op == TRUST_DELETE) {
+ status = delete_trust(mem_ctx, pipe_hnd[0]->binding_handle,
+ &pol_hnd[0], dom_data[1].domsid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("delete_trust failed with [%s].\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ if (other_net_ctx != NULL) {
+ status = delete_trust(mem_ctx,
+ pipe_hnd[1]->binding_handle,
+ &pol_hnd[1], dom_data[0].domsid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("delete_trust failed with [%s].\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+ }
+
+ status = close_handle(mem_ctx, pipe_hnd[0]->binding_handle,
+ &pol_hnd[0]);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("close_handle failed with error [%s].\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ if (other_net_ctx != NULL) {
+ status = close_handle(mem_ctx, pipe_hnd[1]->binding_handle,
+ &pol_hnd[1]);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("close_handle failed with error [%s].\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ success = 0;
+
+done:
+ data_blob_clear_free(&session_key[0]);
+ data_blob_clear_free(&session_key[1]);
+ cli_shutdown(cli[0]);
+ cli_shutdown(cli[1]);
+ talloc_destroy(mem_ctx);
+ return success;
+}
+
+static int rpc_trust_create(struct net_context *net_ctx, int argc,
+ const char **argv)
+{
+ return rpc_trust_common(net_ctx, argc, argv, TRUST_CREATE);
+}
+
+static int rpc_trust_delete(struct net_context *net_ctx, int argc,
+ const char **argv)
+{
+ return rpc_trust_common(net_ctx, argc, argv, TRUST_DELETE);
+}
+
+int net_rpc_trust(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "create",
+ rpc_trust_create,
+ NET_TRANSPORT_RPC,
+ N_("Create trusts"),
+ N_("net rpc trust create\n"
+ " Create trusts")
+ },
+ {
+ "delete",
+ rpc_trust_delete,
+ NET_TRANSPORT_RPC,
+ N_("Remove trusts"),
+ N_("net rpc trust delete\n"
+ " Remove trusts")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net rpc trust", func);
+}
diff --git a/source3/utils/net_sam.c b/source3/utils/net_sam.c
new file mode 100644
index 0000000..5c1e007
--- /dev/null
+++ b/source3/utils/net_sam.c
@@ -0,0 +1,2308 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Local SAM access routines
+ * Copyright (C) Volker Lendecke 2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "utils/net.h"
+#include "../librpc/gen_ndr/samr.h"
+#include "smbldap.h"
+#include "../libcli/security/security.h"
+#include "lib/winbind_util.h"
+#include "passdb.h"
+#include "passdb/pdb_ldap_util.h"
+#include "passdb/pdb_ldap_schema.h"
+#include "lib/privileges.h"
+#include "secrets.h"
+#include "idmap.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+/*
+ * Set a user's data
+ */
+
+static int net_sam_userset(struct net_context *c, int argc, const char **argv,
+ const char *field,
+ bool (*fn)(struct samu *, const char *,
+ enum pdb_value_state))
+{
+ struct samu *sam_acct = NULL;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ NTSTATUS status;
+
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n", _("Usage:"));
+ d_fprintf(stderr, _("net sam set %s <user> <value>\n"),
+ field);
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find name %s\n"), argv[0]);
+ return -1;
+ }
+
+ if (type != SID_NAME_USER) {
+ d_fprintf(stderr, _("%s is a %s, not a user\n"), argv[0],
+ sid_type_lookup(type));
+ return -1;
+ }
+
+ if ( !(sam_acct = samu_new( NULL )) ) {
+ d_fprintf(stderr, _("Internal error\n"));
+ return -1;
+ }
+
+ if (!pdb_getsampwsid(sam_acct, &sid)) {
+ d_fprintf(stderr, _("Loading user %s failed\n"), argv[0]);
+ return -1;
+ }
+
+ if (!fn(sam_acct, argv[1], PDB_CHANGED)) {
+ d_fprintf(stderr, _("Internal error\n"));
+ return -1;
+ }
+
+ status = pdb_update_sam_account(sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Updating sam account %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ TALLOC_FREE(sam_acct);
+
+ d_printf(_("Updated %s for %s\\%s to %s\n"), field, dom, name, argv[1]);
+ return 0;
+}
+
+static int net_sam_set_fullname(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_userset(c, argc, argv, "fullname",
+ pdb_set_fullname);
+}
+
+static int net_sam_set_logonscript(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_userset(c, argc, argv, "logonscript",
+ pdb_set_logon_script);
+}
+
+static int net_sam_set_profilepath(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_userset(c, argc, argv, "profilepath",
+ pdb_set_profile_path);
+}
+
+static int net_sam_set_homedrive(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_userset(c, argc, argv, "homedrive",
+ pdb_set_dir_drive);
+}
+
+static int net_sam_set_homedir(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_userset(c, argc, argv, "homedir",
+ pdb_set_homedir);
+}
+
+static int net_sam_set_workstations(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_userset(c, argc, argv, "workstations",
+ pdb_set_workstations);
+}
+
+/*
+ * Set account flags
+ */
+
+static int net_sam_set_userflag(struct net_context *c, int argc,
+ const char **argv, const char *field,
+ uint16_t flag)
+{
+ struct samu *sam_acct = NULL;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ NTSTATUS status;
+ uint32_t acct_flags;
+
+ if ((argc != 2) || c->display_usage ||
+ (!strequal(argv[1], "yes") &&
+ !strequal(argv[1], "no"))) {
+ d_fprintf(stderr, "%s\n", _("Usage:"));
+ d_fprintf(stderr, _("net sam set %s <user> [yes|no]\n"),
+ field);
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find name %s\n"), argv[0]);
+ return -1;
+ }
+
+ if (type != SID_NAME_USER) {
+ d_fprintf(stderr, _("%s is a %s, not a user\n"), argv[0],
+ sid_type_lookup(type));
+ return -1;
+ }
+
+ if ( !(sam_acct = samu_new( NULL )) ) {
+ d_fprintf(stderr, _("Internal error\n"));
+ return -1;
+ }
+
+ if (!pdb_getsampwsid(sam_acct, &sid)) {
+ d_fprintf(stderr, _("Loading user %s failed\n"), argv[0]);
+ return -1;
+ }
+
+ acct_flags = pdb_get_acct_ctrl(sam_acct);
+
+ if (strequal(argv[1], "yes")) {
+ acct_flags |= flag;
+ } else {
+ acct_flags &= ~flag;
+ }
+
+ pdb_set_acct_ctrl(sam_acct, acct_flags, PDB_CHANGED);
+
+ status = pdb_update_sam_account(sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Updating sam account %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ TALLOC_FREE(sam_acct);
+
+ d_fprintf(stderr, _("Updated flag %s for %s\\%s to %s\n"), field, dom,
+ name, argv[1]);
+ return 0;
+}
+
+static int net_sam_set_disabled(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_set_userflag(c, argc, argv, "disabled", ACB_DISABLED);
+}
+
+static int net_sam_set_pwnotreq(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_set_userflag(c, argc, argv, "pwnotreq", ACB_PWNOTREQ);
+}
+
+static int net_sam_set_autolock(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_set_userflag(c, argc, argv, "autolock", ACB_AUTOLOCK);
+}
+
+static int net_sam_set_pwnoexp(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_set_userflag(c, argc, argv, "pwnoexp", ACB_PWNOEXP);
+}
+
+/*
+ * Set pass last change time, based on force pass change now
+ */
+
+static int net_sam_set_pwdmustchangenow(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct samu *sam_acct = NULL;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ NTSTATUS status;
+
+ if ((argc != 2) || c->display_usage ||
+ (!strequal(argv[1], "yes") &&
+ !strequal(argv[1], "no"))) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam set pwdmustchangenow <user> [yes|no]\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find name %s\n"), argv[0]);
+ return -1;
+ }
+
+ if (type != SID_NAME_USER) {
+ d_fprintf(stderr, _("%s is a %s, not a user\n"), argv[0],
+ sid_type_lookup(type));
+ return -1;
+ }
+
+ if ( !(sam_acct = samu_new( NULL )) ) {
+ d_fprintf(stderr, _("Internal error\n"));
+ return -1;
+ }
+
+ if (!pdb_getsampwsid(sam_acct, &sid)) {
+ d_fprintf(stderr, _("Loading user %s failed\n"), argv[0]);
+ return -1;
+ }
+
+ if (strequal(argv[1], "yes")) {
+ pdb_set_pass_last_set_time(sam_acct, 0, PDB_CHANGED);
+ } else {
+ pdb_set_pass_last_set_time(sam_acct, time(NULL), PDB_CHANGED);
+ }
+
+ status = pdb_update_sam_account(sam_acct);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Updating sam account %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ TALLOC_FREE(sam_acct);
+
+ d_fprintf(stderr, _("Updated 'user must change password at next logon' "
+ "for %s\\%s to %s\n"), dom,
+ name, argv[1]);
+ return 0;
+}
+
+
+/*
+ * Set a user's or a group's comment
+ */
+
+static int net_sam_set_comment(struct net_context *c, int argc,
+ const char **argv)
+{
+ GROUP_MAP *map;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ NTSTATUS status;
+
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam set comment <name> <comment>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find name %s\n"), argv[0]);
+ return -1;
+ }
+
+ if (type == SID_NAME_USER) {
+ return net_sam_userset(c, argc, argv, "comment",
+ pdb_set_acct_desc);
+ }
+
+ if ((type != SID_NAME_DOM_GRP) && (type != SID_NAME_ALIAS) &&
+ (type != SID_NAME_WKN_GRP)) {
+ d_fprintf(stderr, _("%s is a %s, not a group\n"), argv[0],
+ sid_type_lookup(type));
+ return -1;
+ }
+
+ map = talloc_zero(talloc_tos(), GROUP_MAP);
+ if (!map) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ return -1;
+ }
+
+ if (!pdb_getgrsid(map, sid)) {
+ d_fprintf(stderr, _("Could not load group %s\n"), argv[0]);
+ return -1;
+ }
+
+ map->comment = talloc_strdup(map, argv[1]);
+ if (!map->comment) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ return -1;
+ }
+
+ status = pdb_update_group_mapping_entry(map);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Updating group mapping entry failed with "
+ "%s\n"), nt_errstr(status));
+ return -1;
+ }
+
+ d_printf("Updated comment of group %s\\%s to %s\n", dom, name,
+ argv[1]);
+
+ TALLOC_FREE(map);
+ return 0;
+}
+
+static int net_sam_set(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "homedir",
+ net_sam_set_homedir,
+ NET_TRANSPORT_LOCAL,
+ N_("Change a user's home directory"),
+ N_("net sam set homedir\n"
+ " Change a user's home directory")
+ },
+ {
+ "profilepath",
+ net_sam_set_profilepath,
+ NET_TRANSPORT_LOCAL,
+ N_("Change a user's profile path"),
+ N_("net sam set profilepath\n"
+ " Change a user's profile path")
+ },
+ {
+ "comment",
+ net_sam_set_comment,
+ NET_TRANSPORT_LOCAL,
+ N_("Change a users or groups description"),
+ N_("net sam set comment\n"
+ " Change a users or groups description")
+ },
+ {
+ "fullname",
+ net_sam_set_fullname,
+ NET_TRANSPORT_LOCAL,
+ N_("Change a user's full name"),
+ N_("net sam set fullname\n"
+ " Change a user's full name")
+ },
+ {
+ "logonscript",
+ net_sam_set_logonscript,
+ NET_TRANSPORT_LOCAL,
+ N_("Change a user's logon script"),
+ N_("net sam set logonscript\n"
+ " Change a user's logon script")
+ },
+ {
+ "homedrive",
+ net_sam_set_homedrive,
+ NET_TRANSPORT_LOCAL,
+ N_("Change a user's home drive"),
+ N_("net sam set homedrive\n"
+ " Change a user's home drive")
+ },
+ {
+ "workstations",
+ net_sam_set_workstations,
+ NET_TRANSPORT_LOCAL,
+ N_("Change a user's allowed workstations"),
+ N_("net sam set workstations\n"
+ " Change a user's allowed workstations")
+ },
+ {
+ "disabled",
+ net_sam_set_disabled,
+ NET_TRANSPORT_LOCAL,
+ N_("Disable/Enable a user"),
+ N_("net sam set disable\n"
+ " Disable/Enable a user")
+ },
+ {
+ "pwnotreq",
+ net_sam_set_pwnotreq,
+ NET_TRANSPORT_LOCAL,
+ N_("Disable/Enable the password not required flag"),
+ N_("net sam set pwnotreq\n"
+ " Disable/Enable the password not required flag")
+ },
+ {
+ "autolock",
+ net_sam_set_autolock,
+ NET_TRANSPORT_LOCAL,
+ N_("Disable/Enable a user's lockout flag"),
+ N_("net sam set autolock\n"
+ " Disable/Enable a user's lockout flag")
+ },
+ {
+ "pwnoexp",
+ net_sam_set_pwnoexp,
+ NET_TRANSPORT_LOCAL,
+ N_("Disable/Enable whether a user's pw does not "
+ "expire"),
+ N_("net sam set pwnoexp\n"
+ " Disable/Enable whether a user's pw does not "
+ "expire")
+ },
+ {
+ "pwdmustchangenow",
+ net_sam_set_pwdmustchangenow,
+ NET_TRANSPORT_LOCAL,
+ N_("Force users password must change at next logon"),
+ N_("net sam set pwdmustchangenow\n"
+ " Force users password must change at next logon")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net sam set", func);
+}
+
+/*
+ * Manage account policies
+ */
+
+static int net_sam_policy_set(struct net_context *c, int argc, const char **argv)
+{
+ const char *account_policy = NULL;
+ uint32_t value = 0;
+ uint32_t old_value = 0;
+ enum pdb_policy_type field;
+ int err = 0;
+
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam policy set \"<account policy>\" <value>\n"));
+ return -1;
+ }
+
+ account_policy = argv[0];
+ field = account_policy_name_to_typenum(account_policy);
+
+ if (strequal(argv[1], "forever") || strequal(argv[1], "never")
+ || strequal(argv[1], "off")) {
+ value = -1;
+ }
+ else {
+ value = smb_strtoul(argv[1],
+ NULL,
+ 10,
+ &err,
+ SMB_STR_FULL_STR_CONV);
+
+ if (err != 0) {
+ d_printf(_("Unable to set policy \"%s\"! Invalid value "
+ "\"%s\".\n"),
+ account_policy, argv[1]);
+ return -1;
+ }
+ }
+
+ if (field == 0) {
+ const char **names;
+ int i, count;
+
+ account_policy_names_list(talloc_tos(), &names, &count);
+ d_fprintf(stderr, _("No account policy \"%s\"!\n\n"), argv[0]);
+ d_fprintf(stderr, _("Valid account policies are:\n"));
+
+ for (i=0; i<count; i++) {
+ d_fprintf(stderr, "%s\n", names[i]);
+ }
+
+ TALLOC_FREE(names);
+
+ return -1;
+ }
+
+ if (!pdb_get_account_policy(field, &old_value)) {
+ d_fprintf(stderr, _("Valid account policy, but unable to fetch "
+ "value!\n"));
+ } else {
+ d_printf(_("Account policy \"%s\" value was: %d\n"),
+ account_policy, old_value);
+ }
+
+ if (!pdb_set_account_policy(field, value)) {
+ d_fprintf(stderr, _("Valid account policy, but unable to "
+ "set value!\n"));
+ return -1;
+ } else {
+ d_printf(_("Account policy \"%s\" value is now: %d\n"),
+ account_policy, value);
+ }
+
+ return 0;
+}
+
+static int net_sam_policy_show(struct net_context *c, int argc, const char **argv)
+{
+ const char *account_policy = NULL;
+ uint32_t old_value;
+ enum pdb_policy_type field;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam policy show \"<account policy>\"\n"));
+ return -1;
+ }
+
+ account_policy = argv[0];
+ field = account_policy_name_to_typenum(account_policy);
+
+ if (field == 0) {
+ const char **names;
+ int count;
+ int i;
+ account_policy_names_list(talloc_tos(), &names, &count);
+ d_fprintf(stderr, _("No account policy by that name!\n"));
+ if (count != 0) {
+ d_fprintf(stderr, _("Valid account policies "
+ "are:\n"));
+ for (i=0; i<count; i++) {
+ d_fprintf(stderr, "%s\n", names[i]);
+ }
+ }
+ TALLOC_FREE(names);
+ return -1;
+ }
+
+ if (!pdb_get_account_policy(field, &old_value)) {
+ fprintf(stderr, _("Valid account policy, but unable to "
+ "fetch value!\n"));
+ return -1;
+ }
+
+ printf(_("Account policy \"%s\" description: %s\n"),
+ account_policy, account_policy_get_desc(field));
+ printf(_("Account policy \"%s\" value is: %d\n"), account_policy,
+ old_value);
+ return 0;
+}
+
+static int net_sam_policy_list(struct net_context *c, int argc, const char **argv)
+{
+ const char **names;
+ int count;
+ int i;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net sam policy list\n"
+ " %s\n",
+ _("Usage:"),
+ _("List account policies"));
+ return 0;
+ }
+
+ account_policy_names_list(talloc_tos(), &names, &count);
+ if (count != 0) {
+ d_fprintf(stderr, _("Valid account policies "
+ "are:\n"));
+ for (i = 0; i < count ; i++) {
+ d_fprintf(stderr, "%s\n", names[i]);
+ }
+ }
+ TALLOC_FREE(names);
+ return -1;
+}
+
+static int net_sam_policy(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ net_sam_policy_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List account policies"),
+ N_("net sam policy list\n"
+ " List account policies")
+ },
+ {
+ "show",
+ net_sam_policy_show,
+ NET_TRANSPORT_LOCAL,
+ N_("Show account policies"),
+ N_("net sam policy show\n"
+ " Show account policies")
+ },
+ {
+ "set",
+ net_sam_policy_set,
+ NET_TRANSPORT_LOCAL,
+ N_("Change account policies"),
+ N_("net sam policy set\n"
+ " Change account policies")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net sam policy", func);
+}
+
+static int net_sam_rights_list(struct net_context *c, int argc,
+ const char **argv)
+{
+ enum sec_privilege privilege;
+
+ if (argc > 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam rights list [privilege name]\n"));
+ return -1;
+ }
+
+ if (argc == 0) {
+ int i;
+ int num = num_privileges_in_short_list();
+
+ for (i=0; i<num; i++) {
+ d_printf("%s\n", sec_privilege_name_from_index(i));
+ }
+ return 0;
+ }
+
+ privilege = sec_privilege_id(argv[0]);
+
+ if (privilege != SEC_PRIV_INVALID) {
+ struct dom_sid *sids;
+ int i, num_sids;
+ NTSTATUS status;
+
+ status = privilege_enum_sids(privilege, talloc_tos(),
+ &sids, &num_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not list rights: %s\n"),
+ nt_errstr(status));
+ return -1;
+ }
+
+ for (i=0; i<num_sids; i++) {
+ const char *dom, *name;
+ enum lsa_SidType type;
+ struct dom_sid_buf buf;
+
+ if (lookup_sid(talloc_tos(), &sids[i], &dom, &name,
+ &type)) {
+ d_printf("%s\\%s\n", dom, name);
+ }
+ else {
+ d_printf("%s\n",
+ dom_sid_str_buf(&sids[i], &buf));
+ }
+ }
+ return 0;
+ }
+
+ return -1;
+}
+
+static int net_sam_rights_grant(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ int i;
+
+ if (argc < 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam rights grant <name> <rights> ...\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find name %s\n"), argv[0]);
+ return -1;
+ }
+
+ for (i=1; i < argc; i++) {
+ enum sec_privilege privilege = sec_privilege_id(argv[i]);
+ if (privilege == SEC_PRIV_INVALID) {
+ d_fprintf(stderr, _("%s unknown\n"), argv[i]);
+ return -1;
+ }
+
+ if (!grant_privilege_by_name(&sid, argv[i])) {
+ d_fprintf(stderr, _("Could not grant privilege\n"));
+ return -1;
+ }
+
+ d_printf(_("Granted %s to %s\\%s\n"), argv[i], dom, name);
+ }
+
+ return 0;
+}
+
+static int net_sam_rights_revoke(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ int i;
+
+ if (argc < 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam rights revoke <name> <rights>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find name %s\n"), argv[0]);
+ return -1;
+ }
+
+ for (i=1; i < argc; i++) {
+ enum sec_privilege privilege = sec_privilege_id(argv[i]);
+ if (privilege == SEC_PRIV_INVALID) {
+ d_fprintf(stderr, _("%s unknown\n"), argv[i]);
+ return -1;
+ }
+
+ if (!revoke_privilege_by_name(&sid, argv[i])) {
+ d_fprintf(stderr, _("Could not revoke privilege\n"));
+ return -1;
+ }
+
+ d_printf(_("Revoked %s from %s\\%s\n"), argv[i], dom, name);
+ }
+
+ return 0;
+}
+
+static int net_sam_rights(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ net_sam_rights_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List possible user rights"),
+ N_("net sam rights list\n"
+ " List possible user rights")
+ },
+ {
+ "grant",
+ net_sam_rights_grant,
+ NET_TRANSPORT_LOCAL,
+ N_("Grant right(s)"),
+ N_("net sam rights grant\n"
+ " Grant right(s)")
+ },
+ {
+ "revoke",
+ net_sam_rights_revoke,
+ NET_TRANSPORT_LOCAL,
+ N_("Revoke right(s)"),
+ N_("net sam rights revoke\n"
+ " Revoke right(s)")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+ return net_run_function(c, argc, argv, "net sam rights", func);
+}
+
+/*
+ * Map a unix group to a domain group
+ */
+
+static NTSTATUS map_unix_group(const struct group *grp, GROUP_MAP *map)
+{
+ const char *dom, *name;
+ uint32_t rid;
+
+ if (pdb_getgrgid(map, grp->gr_gid)) {
+ return NT_STATUS_GROUP_EXISTS;
+ }
+
+ map->gid = grp->gr_gid;
+
+ if (lookup_name(talloc_tos(), grp->gr_name, LOOKUP_NAME_LOCAL,
+ &dom, &name, NULL, NULL)) {
+
+ map->nt_name = talloc_asprintf(map, "Unix Group %s",
+ grp->gr_name);
+
+ DEBUG(5, ("%s exists as %s\\%s, retrying as \"%s\"\n",
+ grp->gr_name, dom, name, map->nt_name));
+ }
+
+ if (lookup_name(talloc_tos(), grp->gr_name, LOOKUP_NAME_LOCAL,
+ NULL, NULL, NULL, NULL)) {
+ DEBUG(3, ("\"%s\" exists, can't map it\n", grp->gr_name));
+ return NT_STATUS_GROUP_EXISTS;
+ }
+
+ if (pdb_capabilities() & PDB_CAP_STORE_RIDS) {
+ if (!pdb_new_rid(&rid)) {
+ DEBUG(3, ("Could not get a new RID for %s\n",
+ grp->gr_name));
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ } else {
+ rid = algorithmic_pdb_gid_to_group_rid( grp->gr_gid );
+ }
+
+ sid_compose(&map->sid, get_global_sam_sid(), rid);
+ map->sid_name_use = SID_NAME_DOM_GRP;
+ map->comment = talloc_asprintf(map, "Unix Group %s", grp->gr_name);
+
+ return pdb_add_group_mapping_entry(map);
+}
+
+static int net_sam_mapunixgroup(struct net_context *c, int argc, const char **argv)
+{
+ NTSTATUS status;
+ GROUP_MAP *map;
+ struct group *grp;
+ struct dom_sid_buf buf;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam mapunixgroup <name>\n"));
+ return -1;
+ }
+
+ grp = getgrnam(argv[0]);
+ if (grp == NULL) {
+ d_fprintf(stderr, _("Could not find group %s\n"), argv[0]);
+ return -1;
+ }
+
+ map = talloc_zero(talloc_tos(), GROUP_MAP);
+ if (!map) {
+ d_fprintf(stderr, _("Out of memory!\n"));
+ return -1;
+ }
+
+ status = map_unix_group(grp, map);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Mapping group %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ d_printf(_("Mapped unix group %s to SID %s\n"), argv[0],
+ dom_sid_str_buf(&map->sid, &buf));
+
+ TALLOC_FREE(map);
+ return 0;
+}
+
+/*
+ * Remove a group mapping
+ */
+
+static NTSTATUS unmap_unix_group(const struct group *grp)
+{
+ struct dom_sid dom_sid;
+ struct unixid id;
+
+ if (!lookup_name(talloc_tos(), grp->gr_name, LOOKUP_NAME_LOCAL,
+ NULL, NULL, NULL, NULL)) {
+ DEBUG(3, ("\"%s\" does not exist, can't unmap it\n", grp->gr_name));
+ return NT_STATUS_NO_SUCH_GROUP;
+ }
+
+ id.id = grp->gr_gid;
+ id.type = ID_TYPE_GID;
+ if (!pdb_id_to_sid(&id, &dom_sid)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return pdb_delete_group_mapping_entry(dom_sid);
+}
+
+static int net_sam_unmapunixgroup(struct net_context *c, int argc, const char **argv)
+{
+ NTSTATUS status;
+ struct group *grp;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam unmapunixgroup <name>\n"));
+ return -1;
+ }
+
+ grp = getgrnam(argv[0]);
+ if (grp == NULL) {
+ d_fprintf(stderr, _("Could not find mapping for group %s.\n"),
+ argv[0]);
+ return -1;
+ }
+
+ status = unmap_unix_group(grp);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Unmapping group %s failed with %s.\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ d_printf(_("Unmapped unix group %s.\n"), argv[0]);
+
+ return 0;
+}
+
+/*
+ * Create a domain group
+ */
+
+static int net_sam_createdomaingroup(struct net_context *c, int argc,
+ const char **argv)
+{
+ NTSTATUS status;
+ uint32_t rid;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam createdomaingroup <name>\n"));
+ return -1;
+ }
+
+ status = pdb_create_dom_group(talloc_tos(), argv[0], &rid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Creating %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ d_printf(_("Created domain group %s with RID %d\n"), argv[0], rid);
+
+ return 0;
+}
+
+/*
+ * Delete a domain group
+ */
+
+static int net_sam_deletedomaingroup(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct dom_sid sid;
+ uint32_t rid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ NTSTATUS status;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam deletelocalgroup <name>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find %s.\n"), argv[0]);
+ return -1;
+ }
+
+ if (type != SID_NAME_DOM_GRP) {
+ d_fprintf(stderr, _("%s is a %s, not a domain group.\n"),
+ argv[0], sid_type_lookup(type));
+ return -1;
+ }
+
+ sid_peek_rid(&sid, &rid);
+
+ status = pdb_delete_dom_group(talloc_tos(), rid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,_("Deleting domain group %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ d_printf(_("Deleted domain group %s.\n"), argv[0]);
+
+ return 0;
+}
+
+/*
+ * Create a local group
+ */
+
+static int net_sam_createlocalgroup(struct net_context *c, int argc, const char **argv)
+{
+ NTSTATUS status;
+ uint32_t rid;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam createlocalgroup <name>\n"));
+ return -1;
+ }
+
+ if (!winbind_ping()) {
+ d_fprintf(stderr, _("winbind seems not to run. "
+ "createlocalgroup only works when winbind runs.\n"));
+ return -1;
+ }
+
+ status = pdb_create_alias(argv[0], &rid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Creating %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ d_printf(_("Created local group %s with RID %d\n"), argv[0], rid);
+
+ return 0;
+}
+
+/*
+ * Delete a local group
+ */
+
+static int net_sam_deletelocalgroup(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *dom, *name;
+ NTSTATUS status;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam deletelocalgroup <name>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr,_("Could not find %s.\n"), argv[0]);
+ return -1;
+ }
+
+ if (type != SID_NAME_ALIAS) {
+ d_fprintf(stderr, _("%s is a %s, not a local group.\n"),argv[0],
+ sid_type_lookup(type));
+ return -1;
+ }
+
+ status = pdb_delete_alias(&sid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Deleting local group %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ d_printf(_("Deleted local group %s.\n"), argv[0]);
+
+ return 0;
+}
+
+/*
+ * Create a builtin group
+ */
+
+static int net_sam_createbuiltingroup(struct net_context *c, int argc, const char **argv)
+{
+ NTSTATUS status;
+ uint32_t rid;
+ enum lsa_SidType type;
+ fstring groupname;
+ struct dom_sid sid;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam createbuiltingroup <name>\n"));
+ return -1;
+ }
+
+ if (!winbind_ping()) {
+ d_fprintf(stderr, _("winbind seems not to run. "
+ "createbuiltingroup only works when winbind "
+ "runs.\n"));
+ return -1;
+ }
+
+ /* validate the name and get the group */
+
+ fstrcpy( groupname, "BUILTIN\\" );
+ fstrcat( groupname, argv[0] );
+
+ if ( !lookup_name(talloc_tos(), groupname, LOOKUP_NAME_ALL, NULL,
+ NULL, &sid, &type)) {
+ d_fprintf(stderr, _("%s is not a BUILTIN group\n"), argv[0]);
+ return -1;
+ }
+
+ if ( !sid_peek_rid( &sid, &rid ) ) {
+ d_fprintf(stderr, _("Failed to get RID for %s\n"), argv[0]);
+ return -1;
+ }
+
+ status = pdb_create_builtin(rid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Creating %s failed with %s\n"),
+ argv[0], nt_errstr(status));
+ return -1;
+ }
+
+ d_printf(_("Created BUILTIN group %s with RID %d\n"), argv[0], rid);
+
+ return 0;
+}
+
+/*
+ * Add a group member
+ */
+
+static int net_sam_addmem(struct net_context *c, int argc, const char **argv)
+{
+ const char *groupdomain, *groupname, *memberdomain, *membername;
+ struct dom_sid group, member;
+ enum lsa_SidType grouptype, membertype;
+ NTSTATUS status;
+
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam addmem <group> <member>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &groupdomain, &groupname, &group, &grouptype)) {
+ d_fprintf(stderr, _("Could not find group %s\n"), argv[0]);
+ return -1;
+ }
+
+ /* check to see if the member to be added is a name or a SID */
+
+ if (!lookup_name(talloc_tos(), argv[1], LOOKUP_NAME_LOCAL,
+ &memberdomain, &membername, &member, &membertype))
+ {
+ /* try it as a SID */
+
+ if ( !string_to_sid( &member, argv[1] ) ) {
+ d_fprintf(stderr, _("Could not find member %s\n"),
+ argv[1]);
+ return -1;
+ }
+
+ if ( !lookup_sid(talloc_tos(), &member, &memberdomain,
+ &membername, &membertype) )
+ {
+ d_fprintf(stderr, _("Could not resolve SID %s\n"),
+ argv[1]);
+ return -1;
+ }
+ }
+
+ if ((grouptype == SID_NAME_ALIAS) || (grouptype == SID_NAME_WKN_GRP)) {
+ if ((membertype != SID_NAME_USER) &&
+ (membertype != SID_NAME_ALIAS) &&
+ (membertype != SID_NAME_DOM_GRP)) {
+ d_fprintf(stderr, _("Can't add %s: only users, domain "
+ "groups and domain local groups "
+ "can be added. %s is a %s\n"),
+ argv[0], argv[1],
+ sid_type_lookup(membertype));
+ return -1;
+ }
+ status = pdb_add_aliasmem(&group, &member);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Adding local group member failed "
+ "with %s\n"), nt_errstr(status));
+ return -1;
+ }
+ } else if (grouptype == SID_NAME_DOM_GRP) {
+ uint32_t grouprid, memberrid;
+
+ sid_peek_rid(&group, &grouprid);
+ sid_peek_rid(&member, &memberrid);
+
+ status = pdb_add_groupmem(talloc_tos(), grouprid, memberrid);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Adding domain group member failed "
+ "with %s\n"), nt_errstr(status));
+ return -1;
+ }
+ } else {
+ d_fprintf(stderr, _("Can only add members to local groups so "
+ "far, %s is a %s\n"), argv[0],
+ sid_type_lookup(grouptype));
+ return -1;
+ }
+
+ d_printf(_("Added %s\\%s to %s\\%s\n"), memberdomain, membername,
+ groupdomain, groupname);
+
+ return 0;
+}
+
+/*
+ * Delete a group member
+ */
+
+static int net_sam_delmem(struct net_context *c, int argc, const char **argv)
+{
+ const char *groupdomain, *groupname;
+ const char *memberdomain = NULL;
+ const char *membername = NULL;
+ struct dom_sid group, member;
+ enum lsa_SidType grouptype;
+ NTSTATUS status;
+
+ if (argc != 2 || c->display_usage) {
+ d_fprintf(stderr,"%s\n%s",
+ _("Usage:"),
+ _("net sam delmem <group> <member>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &groupdomain, &groupname, &group, &grouptype)) {
+ d_fprintf(stderr, _("Could not find group %s\n"), argv[0]);
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[1], LOOKUP_NAME_LOCAL,
+ &memberdomain, &membername, &member, NULL)) {
+ if (!string_to_sid(&member, argv[1])) {
+ d_fprintf(stderr, _("Could not find member %s\n"),
+ argv[1]);
+ return -1;
+ }
+ }
+
+ if ((grouptype == SID_NAME_ALIAS) ||
+ (grouptype == SID_NAME_WKN_GRP)) {
+ status = pdb_del_aliasmem(&group, &member);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,_("Deleting local group member failed "
+ "with %s\n"), nt_errstr(status));
+ return -1;
+ }
+ } else if (grouptype == SID_NAME_DOM_GRP) {
+ uint32_t grouprid, memberrid;
+
+ sid_peek_rid(&group, &grouprid);
+ sid_peek_rid(&member, &memberrid);
+
+ status = pdb_del_groupmem(talloc_tos(), grouprid, memberrid);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Deleting domain group member "
+ "failed with %s\n"), nt_errstr(status));
+ return -1;
+ }
+ } else {
+ d_fprintf(stderr, _("Can only delete members from local groups "
+ "so far, %s is a %s\n"), argv[0],
+ sid_type_lookup(grouptype));
+ return -1;
+ }
+
+ if (membername != NULL) {
+ d_printf(_("Deleted %s\\%s from %s\\%s\n"),
+ memberdomain, membername, groupdomain, groupname);
+ } else {
+ struct dom_sid_buf buf;
+ d_printf(_("Deleted %s from %s\\%s\n"),
+ dom_sid_str_buf(&member, &buf),
+ groupdomain,
+ groupname);
+ }
+
+ return 0;
+}
+
+/*
+ * List group members
+ */
+
+static int net_sam_listmem(struct net_context *c, int argc, const char **argv)
+{
+ const char *groupdomain, *groupname;
+ struct dom_sid group;
+ struct dom_sid *members = NULL;
+ size_t i, num_members = 0;
+ enum lsa_SidType grouptype;
+ NTSTATUS status;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam listmem <group>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &groupdomain, &groupname, &group, &grouptype)) {
+ d_fprintf(stderr, _("Could not find group %s\n"), argv[0]);
+ return -1;
+ }
+
+ if ((grouptype == SID_NAME_ALIAS) ||
+ (grouptype == SID_NAME_WKN_GRP)) {
+ status = pdb_enum_aliasmem(&group, talloc_tos(), &members,
+ &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Listing group members failed with "
+ "%s\n"), nt_errstr(status));
+ return -1;
+ }
+ } else if (grouptype == SID_NAME_DOM_GRP) {
+ uint32_t *rids;
+
+ status = pdb_enum_group_members(talloc_tos(), &group,
+ &rids, &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Listing group members failed with "
+ "%s\n"), nt_errstr(status));
+ return -1;
+ }
+
+ members = talloc_array(talloc_tos(), struct dom_sid,
+ num_members);
+ if (members == NULL) {
+ TALLOC_FREE(rids);
+ return -1;
+ }
+
+ for (i=0; i<num_members; i++) {
+ sid_compose(&members[i], get_global_sam_sid(),
+ rids[i]);
+ }
+ TALLOC_FREE(rids);
+ } else {
+ d_fprintf(stderr,_("Can only list local group members so far.\n"
+ "%s is a %s\n"), argv[0], sid_type_lookup(grouptype));
+ return -1;
+ }
+
+ d_printf(_("%s\\%s has %u members\n"), groupdomain, groupname,
+ (unsigned int)num_members);
+ for (i=0; i<num_members; i++) {
+ const char *dom, *name;
+ if (lookup_sid(talloc_tos(), &members[i], &dom, &name, NULL)) {
+ d_printf(" %s\\%s\n", dom, name);
+ } else {
+ struct dom_sid_buf buf;
+ d_printf(" %s\n",
+ dom_sid_str_buf(&members[i], &buf));
+ }
+ }
+
+ TALLOC_FREE(members);
+
+ return 0;
+}
+
+/*
+ * Do the listing
+ */
+static int net_sam_do_list(struct net_context *c, int argc, const char **argv,
+ struct pdb_search *search, const char *what)
+{
+ bool verbose = (argc == 1);
+
+ if ((argc > 1) || c->display_usage ||
+ ((argc == 1) && !strequal(argv[0], "verbose"))) {
+ d_fprintf(stderr, "%s\n", _("Usage:"));
+ d_fprintf(stderr, _("net sam list %s [verbose]\n"), what);
+ return -1;
+ }
+
+ if (search == NULL) {
+ d_fprintf(stderr, _("Could not start search\n"));
+ return -1;
+ }
+
+ while (true) {
+ struct samr_displayentry entry;
+ if (!search->next_entry(search, &entry)) {
+ break;
+ }
+ if (verbose) {
+ d_printf("%s:%d:%s\n",
+ entry.account_name,
+ entry.rid,
+ entry.description);
+ } else {
+ d_printf("%s\n", entry.account_name);
+ }
+ }
+
+ TALLOC_FREE(search);
+ return 0;
+}
+
+static int net_sam_list_users(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_do_list(c, argc, argv,
+ pdb_search_users(talloc_tos(), ACB_NORMAL),
+ "users");
+}
+
+static int net_sam_list_groups(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_do_list(c, argc, argv, pdb_search_groups(talloc_tos()),
+ "groups");
+}
+
+static int net_sam_list_localgroups(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_do_list(c, argc, argv,
+ pdb_search_aliases(talloc_tos(),
+ get_global_sam_sid()),
+ "localgroups");
+}
+
+static int net_sam_list_builtin(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_do_list(c, argc, argv,
+ pdb_search_aliases(talloc_tos(),
+ &global_sid_Builtin),
+ "builtin");
+}
+
+static int net_sam_list_workstations(struct net_context *c, int argc,
+ const char **argv)
+{
+ return net_sam_do_list(c, argc, argv,
+ pdb_search_users(talloc_tos(), ACB_WSTRUST),
+ "workstations");
+}
+
+/*
+ * List stuff
+ */
+
+static int net_sam_list(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "users",
+ net_sam_list_users,
+ NET_TRANSPORT_LOCAL,
+ N_("List SAM users"),
+ N_("net sam list users\n"
+ " List SAM users")
+ },
+ {
+ "groups",
+ net_sam_list_groups,
+ NET_TRANSPORT_LOCAL,
+ N_("List SAM groups"),
+ N_("net sam list groups\n"
+ " List SAM groups")
+ },
+ {
+ "localgroups",
+ net_sam_list_localgroups,
+ NET_TRANSPORT_LOCAL,
+ N_("List SAM local groups"),
+ N_("net sam list localgroups\n"
+ " List SAM local groups")
+ },
+ {
+ "builtin",
+ net_sam_list_builtin,
+ NET_TRANSPORT_LOCAL,
+ N_("List builtin groups"),
+ N_("net sam list builtin\n"
+ " List builtin groups")
+ },
+ {
+ "workstations",
+ net_sam_list_workstations,
+ NET_TRANSPORT_LOCAL,
+ N_("List domain member workstations"),
+ N_("net sam list workstations\n"
+ " List domain member workstations")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net sam list", func);
+}
+
+/*
+ * Show details of SAM entries
+ */
+
+static int net_sam_show(struct net_context *c, int argc, const char **argv)
+{
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ enum lsa_SidType type;
+ const char *dom, *name;
+
+ if (argc != 1 || c->display_usage) {
+ d_fprintf(stderr, "%s\n%s",
+ _("Usage:"),
+ _("net sam show <name>\n"));
+ return -1;
+ }
+
+ if (!lookup_name(talloc_tos(), argv[0], LOOKUP_NAME_LOCAL,
+ &dom, &name, &sid, &type)) {
+ d_fprintf(stderr, _("Could not find name %s\n"), argv[0]);
+ return -1;
+ }
+
+ d_printf(_("%s\\%s is a %s with SID %s\n"), dom, name,
+ sid_type_lookup(type), dom_sid_str_buf(&sid, &buf));
+
+ return 0;
+}
+
+#ifdef HAVE_LDAP
+
+/*
+ * Init an LDAP tree with default users and Groups
+ * if ldapsam:editposix is enabled
+ */
+
+static int net_sam_provision(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *tc;
+ char *ldap_bk;
+ char *ldap_uri = NULL;
+ char *p;
+ struct smbldap_state *state = NULL;
+ GROUP_MAP *gmap = NULL;
+ struct dom_sid gsid;
+ gid_t domusers_gid = -1;
+ gid_t domadmins_gid = -1;
+ struct samu *samuser;
+ struct passwd *pwd;
+ bool is_ipa = false;
+ char *bind_dn = NULL;
+ char *bind_secret = NULL;
+ NTSTATUS status;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net sam provision\n"
+ " %s\n",
+ _("Usage:"),
+ _("Init an LDAP tree with default users/groups"));
+ return 0;
+ }
+
+ tc = talloc_new(NULL);
+ if (!tc) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ return -1;
+ }
+
+ if ((ldap_bk = talloc_strdup(tc, lp_passdb_backend())) == NULL) {
+ d_fprintf(stderr, _("talloc failed\n"));
+ talloc_free(tc);
+ return -1;
+ }
+ p = strchr(ldap_bk, ':');
+ if (p) {
+ *p = 0;
+ ldap_uri = talloc_strdup(tc, p+1);
+ trim_char(ldap_uri, ' ', ' ');
+ }
+
+ trim_char(ldap_bk, ' ', ' ');
+
+ if (strcmp(ldap_bk, "IPA_ldapsam") == 0 ) {
+ is_ipa = true;
+ }
+
+ if (strcmp(ldap_bk, "ldapsam") != 0 && !is_ipa ) {
+ d_fprintf(stderr,
+ _("Provisioning works only with ldapsam backend\n"));
+ goto failed;
+ }
+
+ if (!lp_parm_bool(-1, "ldapsam", "trusted", false) ||
+ !lp_parm_bool(-1, "ldapsam", "editposix", false)) {
+
+ d_fprintf(stderr, _("Provisioning works only if ldapsam:trusted"
+ " and ldapsam:editposix are enabled.\n"));
+ goto failed;
+ }
+
+ if (!is_ipa && !winbind_ping()) {
+ d_fprintf(stderr, _("winbind seems not to run. Provisioning "
+ "LDAP only works when winbind runs.\n"));
+ goto failed;
+ }
+
+ if (!fetch_ldap_pw(&bind_dn, &bind_secret)) {
+ d_fprintf(stderr, _("Failed to retrieve LDAP password from secrets.tdb\n"));
+ goto failed;
+ }
+
+ status = smbldap_init(tc, NULL, ldap_uri, false, bind_dn, bind_secret, &state);
+
+ BURN_FREE_STR(bind_secret);
+ SAFE_FREE(bind_dn);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Unable to connect to the LDAP server.\n"));
+ goto failed;
+ }
+
+ d_printf(_("Checking for Domain Users group.\n"));
+
+ sid_compose(&gsid, get_global_sam_sid(), DOMAIN_RID_USERS);
+
+ gmap = talloc_zero(tc, GROUP_MAP);
+ if (!gmap) {
+ d_printf(_("Out of memory!\n"));
+ goto failed;
+ }
+
+ if (!pdb_getgrsid(gmap, gsid)) {
+ struct dom_sid_buf gsid_str;
+ LDAPMod **mods = NULL;
+ char *dn;
+ char *uname;
+ char *wname;
+ char *gidstr;
+ char *gtype;
+ int rc;
+
+ d_printf(_("Adding the Domain Users group.\n"));
+
+ /* lets allocate a new groupid for this group */
+ if (is_ipa) {
+ domusers_gid = 999;
+ } else {
+ if (!winbind_allocate_gid(&domusers_gid)) {
+ d_fprintf(stderr, _("Unable to allocate a new gid to "
+ "create Domain Users group!\n"));
+ goto domu_done;
+ }
+ }
+
+ uname = talloc_strdup(tc, "domusers");
+ wname = talloc_strdup(tc, "Domain Users");
+ dn = talloc_asprintf(tc, "cn=%s,%s", "domusers",
+ lp_ldap_group_suffix(talloc_tos()));
+ gidstr = talloc_asprintf(tc, "%u", (unsigned int)domusers_gid);
+ gtype = talloc_asprintf(tc, "%d", SID_NAME_DOM_GRP);
+
+ if (!uname || !wname || !dn || !gidstr || !gtype) {
+ d_fprintf(stderr, "Out of Memory!\n");
+ goto failed;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_POSIXGROUP);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_GROUPMAP);
+ if (is_ipa) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "groupofnames");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "nestedgroup");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "ipausergroup");
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", uname);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "displayName", wname);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaSid",
+ dom_sid_str_buf(&gsid, &gsid_str));
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaGroupType", gtype);
+
+ smbldap_talloc_autofree_ldapmod(tc, mods);
+
+ rc = smbldap_add(state, dn, mods);
+
+ if (rc != LDAP_SUCCESS) {
+ d_fprintf(stderr, _("Failed to add Domain Users group "
+ "to ldap directory\n"));
+ }
+
+ if (is_ipa) {
+ if (!pdb_getgrsid(gmap, gsid)) {
+ d_fprintf(stderr, _("Failed to read just "
+ "created domain group.\n"));
+ goto failed;
+ } else {
+ domusers_gid = gmap->gid;
+ }
+ }
+ } else {
+ domusers_gid = gmap->gid;
+ d_printf(_("found!\n"));
+ }
+
+domu_done:
+
+ d_printf(_("Checking for Domain Admins group.\n"));
+
+ sid_compose(&gsid, get_global_sam_sid(), DOMAIN_RID_ADMINS);
+
+ if (!pdb_getgrsid(gmap, gsid)) {
+ struct dom_sid_buf gsid_str;
+ LDAPMod **mods = NULL;
+ char *dn;
+ char *uname;
+ char *wname;
+ char *gidstr;
+ char *gtype;
+ int rc;
+
+ d_printf(_("Adding the Domain Admins group.\n"));
+
+ /* lets allocate a new groupid for this group */
+ if (is_ipa) {
+ domadmins_gid = 999;
+ } else {
+ if (!winbind_allocate_gid(&domadmins_gid)) {
+ d_fprintf(stderr, _("Unable to allocate a new gid to "
+ "create Domain Admins group!\n"));
+ goto doma_done;
+ }
+ }
+
+ uname = talloc_strdup(tc, "domadmins");
+ wname = talloc_strdup(tc, "Domain Admins");
+ dn = talloc_asprintf(tc, "cn=%s,%s", "domadmins",
+ lp_ldap_group_suffix(talloc_tos()));
+ gidstr = talloc_asprintf(tc, "%u", (unsigned int)domadmins_gid);
+ gtype = talloc_asprintf(tc, "%d", SID_NAME_DOM_GRP);
+
+ if (!uname || !wname || !dn || !gidstr || !gtype) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_POSIXGROUP);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_GROUPMAP);
+ if (is_ipa) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "groupofnames");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "nestedgroup");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "ipausergroup");
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", uname);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "displayName", wname);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaSid",
+ dom_sid_str_buf(&gsid, &gsid_str));
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaGroupType", gtype);
+
+ smbldap_talloc_autofree_ldapmod(tc, mods);
+
+ rc = smbldap_add(state, dn, mods);
+
+ if (rc != LDAP_SUCCESS) {
+ d_fprintf(stderr, _("Failed to add Domain Admins group "
+ "to ldap directory\n"));
+ }
+
+ if (is_ipa) {
+ if (!pdb_getgrsid(gmap, gsid)) {
+ d_fprintf(stderr, _("Failed to read just "
+ "created domain group.\n"));
+ goto failed;
+ } else {
+ domadmins_gid = gmap->gid;
+ }
+ }
+ } else {
+ domadmins_gid = gmap->gid;
+ d_printf(_("found!\n"));
+ }
+
+doma_done:
+
+ d_printf(_("Check for Administrator account.\n"));
+
+ samuser = samu_new(tc);
+ if (!samuser) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+
+ if (!pdb_getsampwnam(samuser, "Administrator")) {
+ LDAPMod **mods = NULL;
+ struct dom_sid sid;
+ struct dom_sid_buf sid_str;
+ char *dn;
+ char *name;
+ char *uidstr;
+ char *gidstr;
+ char *shell;
+ char *dir;
+ char *princ;
+ uid_t uid;
+ int rc;
+
+ d_printf(_("Adding the Administrator user.\n"));
+
+ if (domadmins_gid == -1) {
+ d_fprintf(stderr,
+ _("Can't create Administrator user, Domain "
+ "Admins group not available!\n"));
+ goto done;
+ }
+
+ if (is_ipa) {
+ uid = 999;
+ } else {
+ if (!winbind_allocate_uid(&uid)) {
+ d_fprintf(stderr,
+ _("Unable to allocate a new uid to create "
+ "the Administrator user!\n"));
+ goto done;
+ }
+ }
+
+ name = talloc_strdup(tc, "Administrator");
+ dn = talloc_asprintf(tc, "uid=Administrator,%s",
+ lp_ldap_user_suffix(talloc_tos()));
+ uidstr = talloc_asprintf(tc, "%u", (unsigned int)uid);
+ gidstr = talloc_asprintf(tc, "%u", (unsigned int)domadmins_gid);
+ dir = talloc_sub_specified(tc, lp_template_homedir(),
+ "Administrator",
+ NULL,
+ get_global_sam_name(),
+ uid, domadmins_gid);
+ shell = talloc_sub_specified(tc, lp_template_shell(),
+ "Administrator",
+ NULL,
+ get_global_sam_name(),
+ uid, domadmins_gid);
+
+ if (!name || !dn || !uidstr || !gidstr || !dir || !shell) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+
+ sid_compose(&sid, get_global_sam_sid(), DOMAIN_RID_ADMINISTRATOR);
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_ACCOUNT);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_POSIXACCOUNT);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_SAMBASAMACCOUNT);
+ if (is_ipa) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "person");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "organizationalperson");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "inetorgperson");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "inetuser");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "krbprincipalaux");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "krbticketpolicyaux");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sn", name);
+ princ = talloc_asprintf(tc, "%s@%s", name, lp_realm());
+ if (!princ) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "krbPrincipalName", princ);
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "uid", name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "displayName", name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "uidNumber", uidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "homeDirectory", dir);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "loginShell", shell);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaSID",
+ dom_sid_str_buf(&sid, &sid_str));
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaAcctFlags",
+ pdb_encode_acct_ctrl(ACB_NORMAL|ACB_DISABLED,
+ NEW_PW_FORMAT_SPACE_PADDED_LEN));
+
+ smbldap_talloc_autofree_ldapmod(tc, mods);
+
+ rc = smbldap_add(state, dn, mods);
+
+ if (rc != LDAP_SUCCESS) {
+ d_fprintf(stderr, _("Failed to add Administrator user "
+ "to ldap directory\n"));
+ }
+
+ if (is_ipa) {
+ if (!pdb_getsampwnam(samuser, "Administrator")) {
+ d_fprintf(stderr, _("Failed to read just "
+ "created user.\n"));
+ goto failed;
+ }
+ }
+ } else {
+ d_printf(_("found!\n"));
+ }
+
+ d_printf(_("Checking for Guest user.\n"));
+
+ samuser = samu_new(tc);
+ if (!samuser) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+
+ if (!pdb_getsampwnam(samuser, lp_guest_account())) {
+ LDAPMod **mods = NULL;
+ struct dom_sid sid;
+ struct dom_sid_buf sid_str;
+ char *dn;
+ char *uidstr;
+ char *gidstr;
+ int rc;
+
+ d_printf(_("Adding the Guest user.\n"));
+
+ sid_compose(&sid, get_global_sam_sid(), DOMAIN_RID_GUEST);
+
+ pwd = Get_Pwnam_alloc(tc, lp_guest_account());
+
+ if (!pwd) {
+ if (domusers_gid == -1) {
+ d_fprintf(stderr,
+ _("Can't create Guest user, Domain "
+ "Users group not available!\n"));
+ goto done;
+ }
+ if ((pwd = talloc(tc, struct passwd)) == NULL) {
+ d_fprintf(stderr, _("talloc failed\n"));
+ goto done;
+ }
+ pwd->pw_name = talloc_strdup(pwd, lp_guest_account());
+
+ if (is_ipa) {
+ pwd->pw_uid = 999;
+ } else {
+ if (!winbind_allocate_uid(&(pwd->pw_uid))) {
+ d_fprintf(stderr,
+ _("Unable to allocate a new uid to "
+ "create the Guest user!\n"));
+ goto done;
+ }
+ }
+ pwd->pw_gid = domusers_gid;
+ pwd->pw_dir = talloc_strdup(tc, "/");
+ pwd->pw_shell = talloc_strdup(tc, "/bin/false");
+ if (!pwd->pw_dir || !pwd->pw_shell) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+ }
+
+ dn = talloc_asprintf(tc, "uid=%s,%s", pwd->pw_name,
+ lp_ldap_user_suffix (talloc_tos()));
+ uidstr = talloc_asprintf(tc, "%u", (unsigned int)pwd->pw_uid);
+ gidstr = talloc_asprintf(tc, "%u", (unsigned int)pwd->pw_gid);
+ if (!dn || !uidstr || !gidstr) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_ACCOUNT);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_POSIXACCOUNT);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_SAMBASAMACCOUNT);
+ if (is_ipa) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "person");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "organizationalperson");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "inetorgperson");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "inetuser");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "krbprincipalaux");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "krbticketpolicyaux");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sn", pwd->pw_name);
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "uid", pwd->pw_name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", pwd->pw_name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "displayName", pwd->pw_name);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "uidNumber", uidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr);
+ if ((pwd->pw_dir != NULL) && (pwd->pw_dir[0] != '\0')) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "homeDirectory", pwd->pw_dir);
+ }
+ if ((pwd->pw_shell != NULL) && (pwd->pw_shell[0] != '\0')) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "loginShell", pwd->pw_shell);
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaSID",
+ dom_sid_str_buf(&sid, &sid_str));
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaAcctFlags",
+ pdb_encode_acct_ctrl(ACB_NORMAL|ACB_DISABLED,
+ NEW_PW_FORMAT_SPACE_PADDED_LEN));
+
+ smbldap_talloc_autofree_ldapmod(tc, mods);
+
+ rc = smbldap_add(state, dn, mods);
+
+ if (rc != LDAP_SUCCESS) {
+ d_fprintf(stderr, _("Failed to add Guest user to "
+ "ldap directory\n"));
+ }
+
+ if (is_ipa) {
+ if (!pdb_getsampwnam(samuser, lp_guest_account())) {
+ d_fprintf(stderr, _("Failed to read just "
+ "created user.\n"));
+ goto failed;
+ }
+ }
+ } else {
+ d_printf(_("found!\n"));
+ }
+
+ d_printf(_("Checking Guest's group.\n"));
+
+ pwd = Get_Pwnam_alloc(tc, lp_guest_account());
+ if (!pwd) {
+ d_fprintf(stderr,
+ _("Failed to find just created Guest account!\n"
+ " Is nss properly configured?!\n"));
+ goto failed;
+ }
+
+ if (pwd->pw_gid == domusers_gid) {
+ d_printf(_("found!\n"));
+ goto done;
+ }
+
+ if (!pdb_getgrgid(gmap, pwd->pw_gid)) {
+ struct dom_sid_buf gsid_str;
+ LDAPMod **mods = NULL;
+ char *dn;
+ char *uname;
+ char *wname;
+ char *gidstr;
+ char *gtype;
+ int rc;
+
+ d_printf(_("Adding the Domain Guests group.\n"));
+
+ uname = talloc_strdup(tc, "domguests");
+ wname = talloc_strdup(tc, "Domain Guests");
+ dn = talloc_asprintf(tc, "cn=%s,%s", "domguests",
+ lp_ldap_group_suffix(talloc_tos()));
+ gidstr = talloc_asprintf(tc, "%u", (unsigned int)pwd->pw_gid);
+ gtype = talloc_asprintf(tc, "%d", SID_NAME_DOM_GRP);
+
+ if (!uname || !wname || !dn || !gidstr || !gtype) {
+ d_fprintf(stderr, _("Out of Memory!\n"));
+ goto failed;
+ }
+
+ sid_compose(&gsid, get_global_sam_sid(), DOMAIN_RID_GUESTS);
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_POSIXGROUP);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_GROUPMAP);
+ if (is_ipa) {
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "groupofnames");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "nestedgroup");
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", "ipausergroup");
+ }
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "cn", uname);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "displayName", wname);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "gidNumber", gidstr);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaSid",
+ dom_sid_str_buf(&gsid, &gsid_str));
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "sambaGroupType", gtype);
+
+ smbldap_talloc_autofree_ldapmod(tc, mods);
+
+ rc = smbldap_add(state, dn, mods);
+
+ if (rc != LDAP_SUCCESS) {
+ d_fprintf(stderr,
+ _("Failed to add Domain Guests group to ldap "
+ "directory\n"));
+ }
+ } else {
+ d_printf(_("found!\n"));
+ }
+
+
+done:
+ talloc_free(state);
+ return 0;
+
+failed:
+ talloc_free(state);
+ return -1;
+}
+
+#endif
+
+/***********************************************************
+ migrated functionality from smbgroupedit
+ **********************************************************/
+int net_sam(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "createbuiltingroup",
+ net_sam_createbuiltingroup,
+ NET_TRANSPORT_LOCAL,
+ N_("Create a new BUILTIN group"),
+ N_("net sam createbuiltingroup\n"
+ " Create a new BUILTIN group")
+ },
+ {
+ "createlocalgroup",
+ net_sam_createlocalgroup,
+ NET_TRANSPORT_LOCAL,
+ N_("Create a new local group"),
+ N_("net sam createlocalgroup\n"
+ " Create a new local group")
+ },
+ {
+ "createdomaingroup",
+ net_sam_createdomaingroup,
+ NET_TRANSPORT_LOCAL,
+ N_("Create a new group"),
+ N_("net sam createdomaingroup\n"
+ " Create a new group")
+ },
+ {
+ "deletelocalgroup",
+ net_sam_deletelocalgroup,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete an existing local group"),
+ N_("net sam deletelocalgroup\n"
+ " Delete an existing local group")
+ },
+ {
+ "deletedomaingroup",
+ net_sam_deletedomaingroup,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a domain group"),
+ N_("net sam deletedomaingroup\n"
+ " Delete a group")
+ },
+ {
+ "mapunixgroup",
+ net_sam_mapunixgroup,
+ NET_TRANSPORT_LOCAL,
+ N_("Map a unix group to a domain group"),
+ N_("net sam mapunixgroup\n"
+ " Map a unix group to a domain group")
+ },
+ {
+ "unmapunixgroup",
+ net_sam_unmapunixgroup,
+ NET_TRANSPORT_LOCAL,
+ N_("Remove a group mapping of an unix group to a "
+ "domain group"),
+ N_("net sam unmapunixgroup\n"
+ " Remove a group mapping of an unix group to a "
+ "domain group")
+ },
+ {
+ "addmem",
+ net_sam_addmem,
+ NET_TRANSPORT_LOCAL,
+ N_("Add a member to a group"),
+ N_("net sam addmem\n"
+ " Add a member to a group")
+ },
+ {
+ "delmem",
+ net_sam_delmem,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete a member from a group"),
+ N_("net sam delmem\n"
+ " Delete a member from a group")
+ },
+ {
+ "listmem",
+ net_sam_listmem,
+ NET_TRANSPORT_LOCAL,
+ N_("List group members"),
+ N_("net sam listmem\n"
+ " List group members")
+ },
+ {
+ "list",
+ net_sam_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List users, groups and local groups"),
+ N_("net sam list\n"
+ " List users, groups and local groups")
+ },
+ {
+ "show",
+ net_sam_show,
+ NET_TRANSPORT_LOCAL,
+ N_("Show details of a SAM entry"),
+ N_("net sam show\n"
+ " Show details of a SAM entry")
+ },
+ {
+ "set",
+ net_sam_set,
+ NET_TRANSPORT_LOCAL,
+ N_("Set details of a SAM account"),
+ N_("net sam set\n"
+ " Set details of a SAM account")
+ },
+ {
+ "policy",
+ net_sam_policy,
+ NET_TRANSPORT_LOCAL,
+ N_("Set account policies"),
+ N_("net sam policy\n"
+ " Set account policies")
+ },
+ {
+ "rights",
+ net_sam_rights,
+ NET_TRANSPORT_LOCAL,
+ N_("Manipulate user privileges"),
+ N_("net sam rights\n"
+ " Manipulate user privileges")
+ },
+#ifdef HAVE_LDAP
+ {
+ "provision",
+ net_sam_provision,
+ NET_TRANSPORT_LOCAL,
+ N_("Provision a clean user database"),
+ N_("net sam privison\n"
+ " Provision a clear user database")
+ },
+#endif
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (getuid() != 0) {
+ d_fprintf(stderr, _("You are not root, most things won't "
+ "work\n"));
+ }
+
+ return net_run_function(c, argc, argv, "net sam", func);
+}
+
diff --git a/source3/utils/net_serverid.c b/source3/utils/net_serverid.c
new file mode 100644
index 0000000..435b63b
--- /dev/null
+++ b/source3/utils/net_serverid.c
@@ -0,0 +1,703 @@
+/*
+ Samba Unix/Linux SMB client library
+ net serverid commands
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "lib/util/server_id.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_rbt.h"
+#include "serverid.h"
+#include "session.h"
+#include "smbd/globals.h"
+#include "smbd/smbXsrv_open.h"
+#include "util_tdb.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
+
+struct wipedbs_record_marker {
+ struct wipedbs_record_marker *prev, *next;
+ TDB_DATA key, val;
+ const char *desc;
+};
+
+struct wipedbs_server_data {
+ struct server_id server_id;
+ const char *server_id_str;
+ bool exists;
+ struct wipedbs_record_marker *session_records;
+ struct wipedbs_record_marker *tcon_records;
+ struct wipedbs_record_marker *open_records;
+};
+
+struct wipedbs_state {
+ struct db_context *id2server_data;
+ struct {
+ struct {
+ int total;
+ int existing;
+ int disconnected;
+ } server;
+ struct {
+ int total;
+ int disconnected;
+ int todelete;
+ int failure;
+ } session, tcon, open;
+ int open_timed_out;
+ } stat;
+ struct server_id *server_ids;
+ bool *server_exists;
+ int idx;
+ struct db_context *session_db;
+ struct db_context *tcon_db;
+ struct db_context *open_db;
+ struct timeval now;
+ bool testmode;
+ bool verbose;
+};
+
+static struct wipedbs_server_data *get_server_data(struct wipedbs_state *state,
+ const struct server_id *id)
+{
+ struct wipedbs_server_data *ret = NULL;
+ TDB_DATA key, val = tdb_null;
+ NTSTATUS status;
+
+ key = make_tdb_data((const void*)&id->unique_id, sizeof(id->unique_id));
+ status = dbwrap_fetch(state->id2server_data, talloc_tos(), key, &val);
+ if (NT_STATUS_IS_OK(status)) {
+ ret = *(struct wipedbs_server_data**) val.dptr;
+ TALLOC_FREE(val.dptr);
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ struct server_id_buf idbuf;
+
+ server_id_str_buf(*id, &idbuf);
+
+ ret = talloc_zero(state->id2server_data,
+ struct wipedbs_server_data);
+ if (ret == NULL) {
+ DEBUG(0, ("Failed to allocate server entry for %s\n",
+ idbuf.buf));
+ goto done;
+ }
+ ret->server_id = *id;
+ ret->server_id_str = talloc_strdup(ret, idbuf.buf);
+ ret->exists = true;
+ val = make_tdb_data((const void*)&ret, sizeof(ret));
+ status = dbwrap_store(state->id2server_data,
+ key, val, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to store server entry for %s: %s\n",
+ idbuf.buf, nt_errstr(status)));
+ }
+ goto done;
+ } else {
+ struct server_id_buf idbuf;
+ DEBUG(0, ("Failed to fetch server entry for %s: %s\n",
+ server_id_str_buf(*id, &idbuf), nt_errstr(status)));
+ goto done;
+ }
+ if (!server_id_equal(id, &ret->server_id)) {
+ struct server_id_buf idbuf1, idbuf2;
+ DEBUG(0, ("uniq id collision for %s and %s\n",
+ server_id_str_buf(*id, &idbuf1),
+ server_id_str_buf(ret->server_id, &idbuf2)));
+ smb_panic("server_id->unique_id not unique!");
+ }
+done:
+ return ret;
+}
+
+static int wipedbs_traverse_sessions(struct smbXsrv_session_global0 *session,
+ void *wipedbs_state)
+{
+ struct wipedbs_state *state =
+ talloc_get_type_abort(wipedbs_state,
+ struct wipedbs_state);
+ struct wipedbs_server_data *sd;
+ struct wipedbs_record_marker *rec;
+ TDB_DATA tmp;
+ int ret = -1;
+
+ assert(session->num_channels == 1);
+
+ state->stat.session.total++;
+
+ sd = get_server_data(state, &session->channels[0].server_id);
+ if (sd == NULL) {
+ goto done;
+ }
+
+ if (server_id_is_disconnected(&sd->server_id)) {
+ state->stat.session.disconnected++;
+ }
+
+ rec = talloc_zero(sd, struct wipedbs_record_marker);
+ if (rec == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ tmp = dbwrap_record_get_key(session->db_rec);
+ rec->key = tdb_data_talloc_copy(rec, tmp);
+ tmp = dbwrap_record_get_value(session->db_rec);
+ rec->val = tdb_data_talloc_copy(rec, tmp);
+
+ rec->desc = talloc_asprintf(
+ rec, "session[global: %u wire: %llu]",
+ session->session_global_id,
+ (long long unsigned)session->session_wire_id);
+
+ if ((rec->key.dptr == NULL) || (rec->val.dptr == NULL) ||
+ (rec->desc == NULL))
+ {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ state->session_db = dbwrap_record_get_db(session->db_rec);
+
+ DLIST_ADD(sd->session_records, rec);
+ ret = 0;
+done:
+ return ret;
+}
+
+static int wipedbs_traverse_tcon(struct smbXsrv_tcon_global0 *tcon,
+ void *wipedbs_state)
+{
+ struct wipedbs_state *state =
+ talloc_get_type_abort(wipedbs_state,
+ struct wipedbs_state);
+ struct wipedbs_server_data *sd;
+ struct wipedbs_record_marker *rec;
+ TDB_DATA tmp;
+ int ret = -1;
+
+ state->stat.tcon.total++;
+
+ sd = get_server_data(state, &tcon->server_id);
+ if (sd == NULL) {
+ goto done;
+ }
+
+ if (server_id_is_disconnected(&sd->server_id)) {
+ state->stat.tcon.disconnected++;
+ }
+
+ rec = talloc_zero(sd, struct wipedbs_record_marker);
+ if (rec == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ tmp = dbwrap_record_get_key(tcon->db_rec);
+ rec->key = tdb_data_talloc_copy(rec, tmp);
+ tmp = dbwrap_record_get_value(tcon->db_rec);
+ rec->val = tdb_data_talloc_copy(rec, tmp);
+
+ rec->desc = talloc_asprintf(
+ rec, "tcon[global: %u wire: %u session: %u share: %s]",
+ tcon->tcon_global_id, tcon->tcon_wire_id,
+ tcon->session_global_id, tcon->share_name);
+
+ if ((rec->key.dptr == NULL) || (rec->val.dptr == NULL) ||
+ (rec->desc == NULL))
+ {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ state->tcon_db = dbwrap_record_get_db(tcon->db_rec);
+
+ DLIST_ADD(sd->tcon_records, rec);
+ ret = 0;
+
+done:
+ return ret;
+}
+
+static int wipedbs_traverse_open(struct db_record *db_rec,
+ struct smbXsrv_open_global0 *open,
+ void *wipedbs_state)
+{
+ struct wipedbs_state *state =
+ talloc_get_type_abort(wipedbs_state,
+ struct wipedbs_state);
+ struct wipedbs_server_data *sd;
+ struct wipedbs_record_marker *rec;
+ TDB_DATA tmp;
+ int ret = -1;
+
+ state->stat.open.total++;
+
+ sd = get_server_data(state, &open->server_id);
+ if (sd == NULL) {
+ goto done;
+ }
+
+ if (server_id_is_disconnected(&sd->server_id)) {
+ struct timeval disconnect_time;
+ int64_t tdiff;
+ bool reached;
+
+ state->stat.open.disconnected++;
+
+ nttime_to_timeval(&disconnect_time, open->disconnect_time);
+ tdiff = usec_time_diff(&state->now, &disconnect_time);
+ reached = (tdiff >= INT64_C(1000)*open->durable_timeout_msec);
+
+ if (state->verbose) {
+ TALLOC_CTX *mem_ctx = talloc_new(talloc_tos());
+ enum ndr_err_code ndr_err;
+ struct vfs_default_durable_cookie cookie;
+
+ ndr_err = ndr_pull_struct_blob(
+ &open->backend_cookie, mem_ctx, &cookie,
+ (ndr_pull_flags_fn_t)ndr_pull_vfs_default_durable_cookie);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ d_printf("ndr_pull_struct_blob failed\n");
+ ret = -1;
+ goto done;
+ }
+
+ d_printf("open[%s/%s id: 0x%" PRIx32 "] disconnected at "
+ "[%s] %us ago with timeout of %us "
+ "-%s reached\n",
+ cookie.servicepath, cookie.base_name,
+ open->open_global_id,
+ nt_time_string(mem_ctx, open->disconnect_time),
+ (unsigned)(tdiff/1000000),
+ open->durable_timeout_msec / 1000,
+ reached ? "" : " not");
+ talloc_free(mem_ctx);
+ }
+
+ if (!reached) {
+ ret = 0;
+ goto done;
+ }
+ state->stat.open_timed_out++;
+ }
+
+ rec = talloc_zero(sd, struct wipedbs_record_marker);
+ if (rec == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ tmp = dbwrap_record_get_key(db_rec);
+ rec->key = tdb_data_talloc_copy(rec, tmp);
+ tmp = dbwrap_record_get_value(db_rec);
+ rec->val = tdb_data_talloc_copy(rec, tmp);
+
+ rec->desc = talloc_asprintf(
+ rec, "open[global: %u persistent: %llu volatile: %llu]",
+ open->open_global_id,
+ (long long unsigned)open->open_persistent_id,
+ (long long unsigned)open->open_volatile_id);
+
+ if ((rec->key.dptr == NULL) || (rec->val.dptr == NULL) ||
+ (rec->desc == NULL))
+ {
+ DEBUG(0, ("Out of memory!\n"));
+ goto done;
+ }
+
+ state->open_db = dbwrap_record_get_db(db_rec);
+
+ DLIST_ADD(sd->open_records, rec);
+ ret = 0;
+
+done:
+ return ret;
+}
+
+static int wipedbs_traverse_nop(struct db_record *rec, void *private_data)
+{
+ return 0;
+}
+
+static int wipedbs_traverse_fill_ids(struct db_record *rec, void *wipedbs_state)
+{
+ struct wipedbs_state *state = talloc_get_type_abort(
+ wipedbs_state, struct wipedbs_state);
+
+ TDB_DATA val = dbwrap_record_get_value(rec);
+
+ struct wipedbs_server_data *sd = talloc_get_type_abort(
+ *(void**)val.dptr, struct wipedbs_server_data);
+
+ state->server_ids[state->idx] = sd->server_id;
+ state->idx++;
+ return 0;
+}
+
+static int wipedbs_traverse_set_exists(struct db_record *rec,
+ void *wipedbs_state)
+{
+ struct wipedbs_state *state = talloc_get_type_abort(
+ wipedbs_state, struct wipedbs_state);
+
+ TDB_DATA val = dbwrap_record_get_value(rec);
+
+ struct wipedbs_server_data *sd = talloc_get_type_abort(
+ *(void**)val.dptr, struct wipedbs_server_data);
+
+ /* assume a stable traverse order for rbt */
+ SMB_ASSERT(server_id_equal(&state->server_ids[state->idx],
+ &sd->server_id));
+ sd->exists = state->server_exists[state->idx];
+
+ if (sd->exists) {
+ state->stat.server.existing++;
+ }
+ if (server_id_is_disconnected(&sd->server_id)) {
+ state->stat.server.disconnected++;
+ }
+
+ state->idx++;
+ return 0;
+}
+
+static bool serverids_exist(const struct server_id *ids, int num_ids,
+ bool *results)
+{
+ int i;
+
+ for (i=0; i<num_ids; i++) {
+ results[i] = serverid_exists(&ids[i]);
+ }
+
+ return true;
+}
+
+
+static NTSTATUS wipedbs_check_server_exists(struct wipedbs_state *state)
+{
+ NTSTATUS status;
+ bool ok;
+ int num_servers;
+
+ status = dbwrap_traverse_read(state->id2server_data,
+ wipedbs_traverse_nop, NULL, &num_servers);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to traverse temporary database\n"));
+ goto done;
+ }
+ state->stat.server.total = num_servers;
+
+ state->server_ids = talloc_array(state, struct server_id, num_servers);
+ state->server_exists = talloc_array(state, bool, num_servers);
+ if (state->server_ids == NULL || state->server_exists == NULL) {
+ DEBUG(0, ("Out of memory\n"));
+ goto done;
+ }
+
+ state->idx = 0;
+ status = dbwrap_traverse_read(state->id2server_data,
+ wipedbs_traverse_fill_ids,
+ state, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to traverse temporary database\n"));
+ goto done;
+ }
+
+ ok = serverids_exist(state->server_ids, num_servers, state->server_exists);
+ if (!ok) {
+ DEBUG(0, ("Calling serverids_exist failed\n"));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ state->idx = 0;
+ status = dbwrap_traverse_read(state->id2server_data,
+ wipedbs_traverse_set_exists, state, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to traverse temporary database\n"));
+ goto done;
+ }
+done:
+ TALLOC_FREE(state->server_ids);
+ TALLOC_FREE(state->server_exists);
+ return status;
+}
+
+struct wipedbs_delete_state {
+ struct wipedbs_record_marker *cur;
+ bool verbose;
+ bool dry_run;
+ size_t total;
+ size_t num;
+};
+
+static void wipedbs_delete_fn(
+ struct db_record *rec, TDB_DATA value, void *private_data)
+{
+ struct db_context *db = dbwrap_record_get_db(rec);
+ struct wipedbs_delete_state *state = private_data;
+ struct wipedbs_record_marker *cur = state->cur;
+ NTSTATUS status = NT_STATUS_OK;
+
+ state->total += 1;
+
+ if (!tdb_data_equal(value, cur->val)) {
+ DBG_ERR("Warning: record <%s> from %s changed,"
+ "skip record!\n",
+ cur->desc, dbwrap_name(db));
+ return;
+ }
+
+ if (state->verbose) {
+ d_printf("deleting %s\n", cur->desc);
+ }
+
+ if (!state->dry_run) {
+ status = dbwrap_record_delete(rec);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to delete record <%s> from %s: %s\n",
+ cur->desc,
+ dbwrap_name(db),
+ nt_errstr(status));
+ return;
+ }
+
+ state->num += 1;
+}
+
+static int wipedbs_delete_records(struct db_context *db,
+ struct wipedbs_record_marker *records,
+ bool dry_run, bool verbose, int *count)
+{
+ struct wipedbs_delete_state state = {
+ .verbose = verbose, .dry_run = dry_run,
+ };
+
+ if (db == NULL) {
+ return 0;
+ }
+
+ for (state.cur = records;
+ state.cur != NULL;
+ state.cur = state.cur->next) {
+
+ NTSTATUS status = dbwrap_do_locked(
+ db, state.cur->key, wipedbs_delete_fn, &state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dbwrap_do_locked failed for record <%s> "
+ "from %s\n",
+ state.cur->desc,
+ dbwrap_name(db));
+ }
+ }
+
+ if (verbose) {
+ d_printf("Deleted %zu of %zu records from %s\n",
+ state.num,
+ state.total,
+ dbwrap_name(db));
+ }
+
+ if (count) {
+ *count += state.total;
+ }
+
+ return state.total - state.num;
+}
+
+static int wipedbs_traverse_server_data(struct db_record *rec,
+ void *wipedbs_state)
+{
+ struct wipedbs_state *state = talloc_get_type_abort(
+ wipedbs_state, struct wipedbs_state);
+ bool dry_run = state->testmode;
+ TDB_DATA val = dbwrap_record_get_value(rec);
+ int ret;
+ struct wipedbs_server_data *sd = talloc_get_type_abort(
+ *(void**)val.dptr, struct wipedbs_server_data);
+
+ if (state->verbose) {
+ d_printf("Server: '%s' %s\n", sd->server_id_str,
+ sd->exists ?
+ "exists" :
+ "does not exist, cleaning up...");
+ }
+
+ if (sd->exists) {
+ return 0;
+ }
+
+ ret = wipedbs_delete_records(state->session_db, sd->session_records,
+ dry_run, state->verbose,
+ &state->stat.session.todelete);
+ state->stat.session.failure += ret;
+
+ ret = wipedbs_delete_records(state->tcon_db, sd->tcon_records,
+ dry_run, state->verbose,
+ &state->stat.tcon.todelete);
+ state->stat.tcon.failure += ret;
+
+ ret = wipedbs_delete_records(state->open_db, sd->open_records,
+ dry_run, state->verbose,
+ &state->stat.open.todelete);
+ state->stat.open.failure += ret;
+
+ return 0;
+}
+
+static int net_serverid_wipedbs(struct net_context *c, int argc,
+ const char **argv)
+{
+ int ret = -1;
+ NTSTATUS status;
+ struct wipedbs_state *state = talloc_zero(talloc_tos(),
+ struct wipedbs_state);
+
+ if (c->display_usage) {
+ d_printf("%s\n%s",
+ _("Usage:"),
+ _("net serverid wipedbs [--test] [--verbose]\n"));
+ d_printf("%s\n%s",
+ _("Example:"),
+ _("net serverid wipedbs -v\n"));
+ return -1;
+ }
+
+ state->now = timeval_current();
+ state->testmode = c->opt_testmode;
+ state->verbose = c->opt_verbose;
+
+ state->id2server_data = db_open_rbt(state);
+ if (state->id2server_data == NULL) {
+ DEBUG(0, ("Failed to open temporary database\n"));
+ goto done;
+ }
+
+ status = smbXsrv_session_global_traverse(wipedbs_traverse_sessions,
+ state);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = smbXsrv_tcon_global_traverse(wipedbs_traverse_tcon, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = smbXsrv_open_global_traverse(wipedbs_traverse_open, state);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = wipedbs_check_server_exists(state);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dbwrap_traverse_read(state->id2server_data,
+ wipedbs_traverse_server_data,
+ state, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Failed to traverse db: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ d_printf("Found %d serverids, %d alive and %d disconnected\n",
+ state->stat.server.total,
+ state->stat.server.existing,
+ state->stat.server.disconnected);
+ d_printf("Found %d sessions, %d alive and %d disconnected"
+ ", cleaned up %d of %d entries\n",
+ state->stat.session.total,
+ state->stat.session.total - state->stat.session.todelete,
+ state->stat.session.disconnected,
+ state->stat.session.todelete - state->stat.session.failure,
+ state->stat.session.todelete);
+ d_printf("Found %d tcons, %d alive and %d disconnected"
+ ", cleaned up %d of %d entries\n",
+ state->stat.tcon.total,
+ state->stat.tcon.total - state->stat.tcon.todelete,
+ state->stat.tcon.disconnected,
+ state->stat.tcon.todelete - state->stat.tcon.failure,
+ state->stat.tcon.todelete);
+ d_printf("Found %d opens, %d alive, %d disconnected and %d timed out"
+ ", cleaned up %d of %d entries\n",
+ state->stat.open.total,
+ state->stat.open.total - state->stat.open.todelete
+ - (state->stat.open.disconnected - state->stat.open_timed_out),
+ state->stat.open.disconnected,
+ state->stat.open_timed_out,
+ state->stat.open.todelete - state->stat.open.failure,
+ state->stat.open.todelete);
+
+ ret = 0;
+done:
+ talloc_free(state);
+ return ret;
+}
+
+static int net_serverid_exists(struct net_context *c, int argc,
+ const char **argv)
+{
+ struct server_id pid;
+ bool ok;
+
+ if ((argc != 1) || (c->display_usage)) {
+ d_printf("Usage:\n"
+ "net serverid exists <serverid>\n");
+ return -1;
+ }
+
+ pid = server_id_from_string(get_my_vnn(), argv[0]);
+ ok = serverid_exists(&pid);
+
+ if (ok) {
+ d_printf("%s exists\n", argv[0]);
+ } else {
+ d_printf("%s does not exist\n", argv[0]);
+ }
+
+ return 0;
+}
+
+int net_serverid(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "wipedbs",
+ net_serverid_wipedbs,
+ NET_TRANSPORT_LOCAL,
+ N_("Clean dead entries from temporary databases"),
+ N_("net serverid wipedbs\n"
+ " Clean dead entries from temporary databases")
+ },
+ {
+ "exists",
+ net_serverid_exists,
+ NET_TRANSPORT_LOCAL,
+ N_("Show existence of a serverid"),
+ N_("net serverid exists <id>")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net serverid", func);
+}
diff --git a/source3/utils/net_share.c b/source3/utils/net_share.c
new file mode 100644
index 0000000..56bb2c9
--- /dev/null
+++ b/source3/utils/net_share.c
@@ -0,0 +1,75 @@
+/*
+ Samba Unix/Linux SMB client library
+ net share commands
+ Copyright (C) 2002 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+
+int net_share_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "\nnet [<method>] share [misc. options] [targets] \n"
+ "\tenumerates all exported resources (network shares) "
+ "on target server\n\n"
+ "net [<method>] share ADD <name=serverpath> [misc. options] [targets]"
+ "\n\tadds a share from a server (makes the export active)\n\n"
+ "net [<method>] share DELETE <sharename> [misc. options] [targets]"
+ "\n\tdeletes a share from a server (makes the export inactive)\n\n"
+ "net [<method>] share ALLOWEDUSERS [misc. options] [<filename>|- "
+ " [targets]]"
+ "\n\tshows a list of shares (either from [targets] or by default all"
+ "\n\tshares) together with all users allowed to access them. This"
+ "\n\tneeds the output of 'net usersidlist' on stdin or in <filename>."
+ "\n\n"
+ "net [<method>] share MIGRATE FILES <sharename> [misc. options] [targets]"
+ "\n\tMigrates files from remote to local server\n\n"
+ "net [<method>] share MIGRATE SHARES <sharename> [misc. options] [targets]"
+ "\n\tMigrates shares from remote to local server\n\n"
+ "net [<method>] share MIGRATE SECURITY <sharename> [misc. options] [targets]"
+ "\n\tMigrates share-ACLs from remote to local server\n\n"
+ "net [<method>] share MIGRATE ALL <sharename> [misc. options] [targets]"
+ "\n\tMigrates shares (including directories, files) from remote\n"
+ "\tto local server\n\n"
+ ));
+ net_common_methods_usage(c, argc, argv);
+ net_common_flags_usage(c, argc, argv);
+ d_printf(_(
+ "\t-C or --comment=<comment>\tdescriptive comment (for add only)\n"
+ "\t-M or --maxusers=<num>\t\tmax users allowed for share\n"
+ "\t --acls\t\t\tcopies ACLs as well\n"
+ "\t --attrs\t\t\tcopies DOS Attributes as well\n"
+ "\t --timestamps\t\tpreserve timestamps while copying files\n"
+ "\t --destination\t\tmigration target server (default: localhost)\n"
+ "\t-e or --exclude\t\t\tlist of shares to be excluded from mirroring\n"
+ "\t-v or --verbose\t\t\tgive verbose output\n"));
+ return -1;
+}
+
+int net_share(struct net_context *c, int argc, const char **argv)
+{
+ if (argc > 0 && strcasecmp_m(argv[0], "HELP") == 0) {
+ net_share_usage(c, argc, argv);
+ return 0;
+ }
+
+ if (net_rpc_check(c, 0))
+ return net_rpc_share(c, argc, argv);
+ return net_rap_share(c, argc, argv);
+}
+
diff --git a/source3/utils/net_status.c b/source3/utils/net_status.c
new file mode 100644
index 0000000..a22b45c
--- /dev/null
+++ b/source3/utils/net_status.c
@@ -0,0 +1,246 @@
+/*
+ Samba Unix/Linux SMB client library
+ net status command -- possible replacement for smbstatus
+ Copyright (C) 2003 Volker Lendecke (vl@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "utils/net.h"
+#include "session.h"
+#include "messages.h"
+#include "conn_tdb.h"
+
+int net_status_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(" net status sessions [parseable] "
+ "Show list of open sessions\n"));
+ d_printf(_(" net status shares [parseable] "
+ "Show list of open shares\n"));
+ return -1;
+}
+
+static int show_session(const char *key, struct sessionid *session,
+ void *private_data)
+{
+ struct server_id_buf tmp;
+ bool *parseable = (bool *)private_data;
+
+ if (!process_exists(session->pid)) {
+ return 0;
+ }
+
+ if (*parseable) {
+ d_printf("%s\\%s\\%s\\%s\\%s\n",
+ server_id_str_buf(session->pid, &tmp),
+ uidtoname(session->uid),
+ gidtoname(session->gid),
+ session->remote_machine, session->hostname);
+ } else {
+ d_printf("%7s %-12s %-12s %-12s (%s)\n",
+ server_id_str_buf(session->pid, &tmp),
+ uidtoname(session->uid),
+ gidtoname(session->gid),
+ session->remote_machine, session->hostname);
+ }
+
+ return 0;
+}
+
+static int net_status_sessions(struct net_context *c, int argc, const char **argv)
+{
+ bool parseable;
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net status sessions [parseable]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Display open user sessions.\n"
+ " If parseable is specified, output is machine-"
+ "readable."));
+ return 0;
+ }
+
+ if (argc == 0) {
+ parseable = false;
+ } else if ((argc == 1) && strequal(argv[0], "parseable")) {
+ parseable = true;
+ } else {
+ return net_status_usage(c, argc, argv);
+ }
+
+ if (!parseable) {
+ d_printf(_("PID Username Group Machine"
+ " \n"
+ "-------------------------------------------"
+ "------------------------\n"));
+ }
+
+ sessionid_traverse_read(show_session, &parseable);
+ return 0;
+}
+
+static int show_share(const struct connections_data *crec,
+ void *state)
+{
+ struct server_id_buf tmp;
+
+ if (crec->cnum == TID_FIELD_INVALID)
+ return 0;
+
+ if (!process_exists(crec->pid)) {
+ return 0;
+ }
+
+ d_printf("%-10.10s %s %-12s %s",
+ crec->servicename, server_id_str_buf(crec->pid, &tmp),
+ crec->machine,
+ time_to_asc(nt_time_to_unix(crec->start)));
+
+ return 0;
+}
+
+struct sessionids {
+ int num_entries;
+ struct sessionid *entries;
+};
+
+static int collect_pids(const char *key, struct sessionid *session,
+ void *private_data)
+{
+ struct sessionids *ids = (struct sessionids *)private_data;
+
+ if (!process_exists(session->pid))
+ return 0;
+
+ ids->num_entries += 1;
+ ids->entries = SMB_REALLOC_ARRAY(ids->entries, struct sessionid, ids->num_entries);
+ if (!ids->entries) {
+ ids->num_entries = 0;
+ return 0;
+ }
+ ids->entries[ids->num_entries-1] = *session;
+
+ return 0;
+}
+
+static int show_share_parseable(const struct connections_data *crec,
+ void *state)
+{
+ struct sessionids *ids = (struct sessionids *)state;
+ struct server_id_buf tmp;
+ int i;
+ bool guest = true;
+
+ if (crec->cnum == TID_FIELD_INVALID)
+ return 0;
+
+ if (!process_exists(crec->pid)) {
+ return 0;
+ }
+
+ for (i=0; i<ids->num_entries; i++) {
+ struct server_id id = ids->entries[i].pid;
+ if (server_id_equal(&id, &crec->pid)) {
+ guest = false;
+ break;
+ }
+ }
+
+ d_printf("%s\\%s\\%s\\%s\\%s\\%s\\%s",
+ crec->servicename, server_id_str_buf(crec->pid, &tmp),
+ guest ? "" : uidtoname(ids->entries[i].uid),
+ guest ? "" : gidtoname(ids->entries[i].gid),
+ crec->machine,
+ guest ? "" : ids->entries[i].hostname,
+ time_to_asc(nt_time_to_unix(crec->start)));
+
+ return 0;
+}
+
+static int net_status_shares_parseable(struct net_context *c, int argc, const char **argv)
+{
+ struct sessionids ids;
+
+ ids.num_entries = 0;
+ ids.entries = NULL;
+
+ sessionid_traverse_read(collect_pids, &ids);
+
+ connections_forall_read(show_share_parseable, &ids);
+
+ SAFE_FREE(ids.entries);
+
+ return 0;
+}
+
+static int net_status_shares(struct net_context *c, int argc, const char **argv)
+{
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net status shares [parseable]\n"
+ " %s\n",
+ _("Usage:"),
+ _("Display open user shares.\n"
+ " If parseable is specified, output is machine-"
+ "readable."));
+ return 0;
+ }
+
+ if (argc == 0) {
+
+ d_printf(_("\nService pid machine "
+ "Connected at\n"
+ "-------------------------------------"
+ "------------------\n"));
+
+ connections_forall_read(show_share, NULL);
+
+ return 0;
+ }
+
+ if ((argc != 1) || !strequal(argv[0], "parseable")) {
+ return net_status_usage(c, argc, argv);
+ }
+
+ return net_status_shares_parseable(c, argc, argv);
+}
+
+int net_status(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "sessions",
+ net_status_sessions,
+ NET_TRANSPORT_LOCAL,
+ N_("Show list of open sessions"),
+ N_("net status sessions [parseable]\n"
+ " If parseable is specified, output is presented "
+ "in a machine-parseable fashion.")
+ },
+ {
+ "shares",
+ net_status_shares,
+ NET_TRANSPORT_LOCAL,
+ N_("Show list of open shares"),
+ N_("net status shares [parseable]\n"
+ " If parseable is specified, output is presented "
+ "in a machine-parseable fashion.")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+ return net_run_function(c, argc, argv, "net status", func);
+}
diff --git a/source3/utils/net_tdb.c b/source3/utils/net_tdb.c
new file mode 100644
index 0000000..29585eb
--- /dev/null
+++ b/source3/utils/net_tdb.c
@@ -0,0 +1,105 @@
+/*
+ * Samba Unix/Linux client library
+ * net tdb commands to query tdb record information
+ * Copyright (C) 2016, 2017 Christof Schmitt <cs@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "locking/share_mode_lock.h"
+#include "locking/proto.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "librpc/gen_ndr/ndr_open_files.h"
+
+static int net_tdb_locking(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *mem_ctx = talloc_stackframe();
+ struct share_mode_lock *lock;
+ DATA_BLOB blob = { .data = NULL };
+ struct file_id id = { .inode = 0 };
+ int ret = -1;
+ bool ok;
+
+ if (argc < 1) {
+ d_printf("Usage: net tdb locking <key> [ dump ]\n");
+ goto out;
+ }
+
+ ok = locking_init_readonly();
+ if (!ok) {
+ d_printf("locking_init_readonly failed\n");
+ goto out;
+ }
+
+ blob = strhex_to_data_blob(mem_ctx, argv[0]);
+ if (blob.length != sizeof(struct file_id)) {
+ d_printf("Invalid length %zu of key, expected %zu\n",
+ blob.length,
+ sizeof(struct file_id));
+ goto out;
+ }
+
+ memcpy(&id, blob.data, blob.length);
+
+ lock = fetch_share_mode_unlocked(mem_ctx, id);
+ if (lock == NULL) {
+ d_printf("Record with key %s not found.\n", argv[1]);
+ goto out;
+ }
+
+ if (argc == 2 && strequal(argv[1], "dump")) {
+ char *dump = share_mode_data_dump(mem_ctx, lock);
+ d_printf("%s\n", dump);
+ TALLOC_FREE(dump);
+ } else {
+ NTSTATUS status;
+ size_t num_share_modes = 0;
+
+ status = share_mode_count_entries(id, &num_share_modes);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr,
+ "Could not count share entries: %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ d_printf("Share path: %s\n",
+ share_mode_servicepath(lock));
+ d_printf("Name: %s\n",
+ share_mode_filename(mem_ctx, lock));
+ d_printf("Number of share modes: %zu\n", num_share_modes);
+ }
+
+ ret = 0;
+out:
+ TALLOC_FREE(mem_ctx);
+ return ret;
+}
+
+int net_tdb(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ { "locking",
+ net_tdb_locking,
+ NET_TRANSPORT_LOCAL,
+ N_("Show information for a record in locking.tdb"),
+ N_("net tdb locking <key>")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net tdb", func);
+}
diff --git a/source3/utils/net_time.c b/source3/utils/net_time.c
new file mode 100644
index 0000000..f58d62b
--- /dev/null
+++ b/source3/utils/net_time.c
@@ -0,0 +1,262 @@
+/*
+ Samba Unix/Linux SMB client library
+ net time command
+ Copyright (C) 2001 Andrew Tridgell (tridge@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "includes.h"
+#include "utils/net.h"
+#include "libsmb/nmblib.h"
+#include "libsmb/namequery.h"
+#include "libsmb/libsmb.h"
+#include "../libcli/smb/smbXcli_base.h"
+
+/*
+ return the time on a server. This does not require any authentication
+*/
+static time_t cli_servertime(const char *host,
+ const struct sockaddr_storage *dest_ss,
+ int *zone)
+{
+ time_t ret = 0;
+ struct cli_state *cli = NULL;
+ NTSTATUS status;
+
+ status = cli_connect_nb(host, dest_ss, 0, 0x20, lp_netbios_name(),
+ SMB_SIGNING_DEFAULT, 0, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ fprintf(stderr, "Can't contact server %s. NetBIOS support disabled,"
+ " Error %s\n", host, nt_errstr(status));
+ } else {
+ fprintf(stderr, "Can't contact server %s. Error %s\n",
+ host, nt_errstr(status));
+ }
+ goto done;
+ }
+
+ status = smbXcli_negprot(cli->conn,
+ cli->timeout,
+ lp_client_min_protocol(),
+ lp_client_max_protocol(),
+ NULL,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, _("Protocol negotiation failed: %s\n"),
+ nt_errstr(status));
+ goto done;
+ }
+
+ ret = cli_state_server_time(cli);
+ if (zone) *zone = smb1cli_conn_server_time_zone(cli->conn);
+
+done:
+ if (cli) {
+ cli_shutdown(cli);
+ }
+ return ret;
+}
+
+/* find the servers time on the opt_host host */
+static time_t nettime(struct net_context *c, int *zone)
+{
+ return cli_servertime(c->opt_host,
+ c->opt_have_ip? &c->opt_dest_ip : NULL, zone);
+}
+
+/* return a time as a string ready to be passed to /bin/date */
+static const char *systime(time_t t)
+{
+ struct tm *tm;
+
+ tm = localtime(&t);
+ if (!tm) {
+ return "unknown";
+ }
+
+ return talloc_asprintf(talloc_tos(), "%02d%02d%02d%02d%04d.%02d",
+ tm->tm_mon+1, tm->tm_mday, tm->tm_hour,
+ tm->tm_min, tm->tm_year + 1900, tm->tm_sec);
+}
+
+int net_time_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+"net time\n\tdisplays time on a server (-S server)\n\n"
+"net time system\n\tdisplays time on a server (-S server) in a format ready for /bin/date\n\n"
+"net time set\n\truns /bin/date with the time from the server (-S server)\n\n"
+"net time zone\n\tdisplays the timezone in hours from GMT on the remote server (-S server)\n\n"
+"\n"));
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+/* try to set the system clock */
+static int net_time_set(struct net_context *c, int argc, const char **argv)
+{
+ struct timeval tv;
+ int result;
+
+ if (c->display_usage || c->opt_host == NULL) {
+ d_printf( "%s\n"
+ "net time set\n"
+ " %s\n",
+ _("Usage:"),
+ _("Set local time to that of remote time "
+ "server (-S server) "));
+ return 0;
+ }
+
+ tv.tv_sec = nettime(c, NULL);
+ tv.tv_usec=0;
+
+ if (tv.tv_sec == 0) return -1;
+
+ result = settimeofday(&tv,NULL);
+
+ if (result)
+ d_fprintf(stderr, _("setting system clock failed. Error was (%s)\n"),
+ strerror(errno));
+
+ return result;
+}
+
+/* display the time on a remote box in a format ready for /bin/date */
+static int net_time_system(struct net_context *c, int argc, const char **argv)
+{
+ time_t t;
+
+ if (c->display_usage || c->opt_host == NULL) {
+ d_printf( "%s\n"
+ "net time system\n"
+ " %s\n",
+ _("Usage:"),
+ _("Output remote time server (-S server) "
+ "time in a format ready for /bin/date"));
+ return 0;
+ }
+
+ t = nettime(c, NULL);
+ if (t == 0) return -1;
+
+ printf("%s\n", systime(t));
+
+ return 0;
+}
+
+/* display the remote time server's offset to UTC */
+static int net_time_zone(struct net_context *c, int argc, const char **argv)
+{
+ int zone = 0;
+ int hours, mins;
+ char zsign;
+ time_t t;
+
+ if (c->display_usage || c->opt_host == NULL) {
+ d_printf( "%s\n"
+ "net time zone\n"
+ " %s\n",
+ _("Usage:"),
+ _("Display the remote time server's (-S server) "
+ "offset to UTC"));
+ return 0;
+ }
+
+ t = nettime(c, &zone);
+
+ if (t == 0) return -1;
+
+ zsign = (zone > 0) ? '-' : '+';
+ if (zone < 0) zone = -zone;
+
+ zone /= 60;
+ hours = zone / 60;
+ mins = zone % 60;
+
+ printf("%c%02d%02d\n", zsign, hours, mins);
+
+ return 0;
+}
+
+/* display or set the time on a host */
+int net_time(struct net_context *c, int argc, const char **argv)
+{
+ time_t t;
+ struct functable func[] = {
+ {
+ "system",
+ net_time_system,
+ NET_TRANSPORT_LOCAL,
+ N_("Display time ready for /bin/date"),
+ N_("net time system\n"
+ " Display time ready for /bin/date")
+ },
+ {
+ "set",
+ net_time_set,
+ NET_TRANSPORT_LOCAL,
+ N_("Set the system time from time server"),
+ N_("net time set\n"
+ " Set the system time from time server")
+ },
+ {
+ "zone",
+ net_time_zone,
+ NET_TRANSPORT_LOCAL,
+ N_("Display timezone offset from UTC"),
+ N_("net time zone\n"
+ " Display timezone offset from UTC")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (argc != 0) {
+ return net_run_function(c, argc, argv, "net time", func);
+ }
+
+ if (c->display_usage) {
+ d_printf( "%s\n"
+ "net time\n"
+ " %s\n",
+ _("Usage:"),
+ _("Display the remote time server's time"));
+ net_display_usage_from_functable(func);
+ return 0;
+ }
+
+ if (c->opt_host == NULL && !c->opt_have_ip) {
+ bool ok;
+
+ ok = find_master_ip(c->opt_target_workgroup, &c->opt_dest_ip);
+ if (!ok) {
+ d_fprintf(stderr,
+ _("Could not locate a time server. "
+ "Try specifying a target host.\n"));
+ net_time_usage(c, argc, argv);
+ return -1;
+ }
+ c->opt_have_ip = true;
+ }
+
+ /* default - print the time */
+ t = cli_servertime(c->opt_host,
+ c->opt_have_ip? &c->opt_dest_ip : NULL,
+ NULL);
+ if (t == 0) return -1;
+
+ d_printf("%s", ctime(&t));
+ return 0;
+}
diff --git a/source3/utils/net_user.c b/source3/utils/net_user.c
new file mode 100644
index 0000000..9fb6f80
--- /dev/null
+++ b/source3/utils/net_user.c
@@ -0,0 +1,67 @@
+/*
+ Samba Unix/Linux SMB client library
+ net user commands
+ Copyright (C) 2002 Jim McDonough (jmcd@us.ibm.com)
+ Copyright (C) 2002 Andrew Tridgell (tridge@samba.org)
+ Copyright (C) 2008 Kai Blin (kai@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+
+int net_user_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("\nnet [<method>] user [misc. options] [targets]"
+ "\n\tList users\n\n"));
+ d_printf(_("net [<method>] user DELETE <name> [misc. options] [targets]"
+ "\n\tDelete specified user\n"));
+ d_printf(_("\nnet [<method>] user INFO <name> [misc. options] [targets]"
+ "\n\tList the domain groups of the specified user\n"));
+ d_printf(_("\nnet [<method>] user ADD <name> [password] [-c container] "
+ "[-F user flags] [misc. options]"
+ " [targets]\n\tAdd specified user\n"));
+ d_printf(_("\nnet [<method>] user RENAME <oldusername> <newusername>"
+ " [targets]\n\tRename specified user\n\n"));
+
+ net_common_methods_usage(c, argc, argv);
+ net_common_flags_usage(c, argc, argv);
+ d_printf(_("\t-C or --comment=<comment>\tdescriptive comment "
+ "(for add only)\n"));
+ d_printf(_("\t-c or --container=<container>\tLDAP container, defaults "
+ "to cn=Users (for add in ADS only)\n"));
+ return -1;
+}
+
+int net_user(struct net_context *c, int argc, const char **argv)
+{
+ if (argc < 1)
+ return net_user_usage(c, argc, argv);
+
+ if (strcasecmp_m(argv[0], "HELP") == 0) {
+ net_user_usage(c, argc, argv);
+ return 0;
+ }
+
+ if (net_ads_check(c) == 0)
+ return net_ads_user(c, argc, argv);
+
+ /* if server is not specified, default to PDC? */
+ if (net_rpc_check(c, NET_FLAGS_PDC))
+ return net_rpc_user(c, argc, argv);
+
+ return net_rap_user(c, argc, argv);
+}
+
diff --git a/source3/utils/net_usershare.c b/source3/utils/net_usershare.c
new file mode 100644
index 0000000..e5826ee
--- /dev/null
+++ b/source3/utils/net_usershare.c
@@ -0,0 +1,1172 @@
+/*
+ Samba Unix/Linux SMB client library
+ Distributed SMB/CIFS Server Management Utility
+
+ Copyright (C) Jeremy Allison (jra@samba.org) 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "system/filesys.h"
+#include "utils/net.h"
+#include "../libcli/security/security.h"
+#include "lib/util/string_wrappers.h"
+
+struct {
+ const char *us_errstr;
+ enum usershare_err us_err;
+} us_errs [] = {
+ {"",USERSHARE_OK},
+ {N_("Malformed usershare file"), USERSHARE_MALFORMED_FILE},
+ {N_("Bad version number"), USERSHARE_BAD_VERSION},
+ {N_("Malformed path entry"), USERSHARE_MALFORMED_PATH},
+ {N_("Malformed comment entryfile"), USERSHARE_MALFORMED_COMMENT_DEF},
+ {N_("Malformed acl definition"), USERSHARE_MALFORMED_ACL_DEF},
+ {N_("Acl parse error"), USERSHARE_ACL_ERR},
+ {N_("Path not absolute"), USERSHARE_PATH_NOT_ABSOLUTE},
+ {N_("Path is denied"), USERSHARE_PATH_IS_DENIED},
+ {N_("Path not allowed"), USERSHARE_PATH_NOT_ALLOWED},
+ {N_("Path is not a directory"), USERSHARE_PATH_NOT_DIRECTORY},
+ {N_("System error"), USERSHARE_POSIX_ERR},
+ {N_("Malformed sharename definition"), USERSHARE_MALFORMED_SHARENAME_DEF},
+ {N_("Bad sharename (doesn't match filename)"), USERSHARE_BAD_SHARENAME},
+ {NULL,(enum usershare_err)-1}
+};
+
+static const char *get_us_error_code(enum usershare_err us_err)
+{
+ char *result;
+ int idx = 0;
+
+ while (us_errs[idx].us_errstr != NULL) {
+ if (us_errs[idx].us_err == us_err) {
+ return us_errs[idx].us_errstr;
+ }
+ idx++;
+ }
+
+ result = talloc_asprintf(talloc_tos(), _("Usershare error code (0x%x)"),
+ (unsigned int)us_err);
+ SMB_ASSERT(result != NULL);
+ return result;
+}
+
+/* The help subsystem for the USERSHARE subcommand */
+
+static int net_usershare_add_usage(struct net_context *c, int argc, const char **argv)
+{
+ char chr = *lp_winbind_separator();
+ d_printf(_(
+ "net usershare add [--long] <sharename> <path> [<comment>] [<acl>] [<guest_ok=[y|n]>]\n"
+ "\tAdds the specified share name for this user.\n"
+ "\t<sharename> is the new share name.\n"
+ "\t<path> is the path on the filesystem to export.\n"
+ "\t<comment> is the optional comment for the new share.\n"
+ "\t<acl> is an optional share acl in the format \"DOMAIN%cname:X,DOMAIN%cname:X,....\"\n"
+ "\t<guest_ok=y> if present sets \"guest ok = yes\" on this usershare.\n"
+ "\t\t\"X\" represents a permission and can be any one of the characters f, r or d\n"
+ "\t\twhere \"f\" means full control, \"r\" means read-only, \"d\" means deny access.\n"
+ "\t\tname may be a domain user or group. For local users use the local server name "
+ "instead of \"DOMAIN\"\n"
+ "\t\tThe default acl is \"Everyone:r\" which allows everyone read-only access.\n"
+ "\tAdd --long to print the info on the newly added share.\n"),
+ chr, chr );
+ return -1;
+}
+
+static int net_usershare_delete_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "net usershare delete <sharename>\n"
+ "\tdeletes the specified share name for this user.\n"));
+ return -1;
+}
+
+static int net_usershare_info_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "net usershare info [--long] [wildcard sharename]\n"
+ "\tPrints out the path, comment and acl elements of shares that match the wildcard.\n"
+ "\tBy default only gives info on shares owned by the current user\n"
+ "\tAdd --long to apply this to all shares\n"
+ "\tOmit the sharename or use a wildcard of '*' to see all shares\n"));
+ return -1;
+}
+
+static int net_usershare_list_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_(
+ "net usershare list [--long] [wildcard sharename]\n"
+ "\tLists the names of all shares that match the wildcard.\n"
+ "\tBy default only lists shares owned by the current user\n"
+ "\tAdd --long to apply this to all shares\n"
+ "\tOmit the sharename or use a wildcard of '*' to see all shares\n"));
+ return -1;
+}
+
+int net_usershare_usage(struct net_context *c, int argc, const char **argv)
+{
+ d_printf(_("net usershare add <sharename> <path> [<comment>] [<acl>] [<guest_ok=[y|n]>] to "
+ "add or change a user defined share.\n"
+ "net usershare delete <sharename> to delete a user defined share.\n"
+ "net usershare info [--long] [wildcard sharename] to print info about a user defined share.\n"
+ "net usershare list [--long] [wildcard sharename] to list user defined shares.\n"
+ "net usershare help\n"
+ "\nType \"net usershare help <option>\" to get more information on that option\n\n"));
+
+ net_common_flags_usage(c, argc, argv);
+ return -1;
+}
+
+/***************************************************************************
+***************************************************************************/
+
+static char *get_basepath(TALLOC_CTX *ctx)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *basepath = lp_usershare_path(ctx, lp_sub);
+
+ if (!basepath) {
+ return NULL;
+ }
+ if ((basepath[0] != '\0') && (basepath[strlen(basepath)-1] == '/')) {
+ basepath[strlen(basepath)-1] = '\0';
+ }
+ return basepath;
+}
+
+/***************************************************************************
+ Delete a single userlevel share.
+***************************************************************************/
+
+static int net_usershare_delete(struct net_context *c, int argc, const char **argv)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ char *us_path;
+ char *sharename;
+
+ if (argc != 1 || c->display_usage) {
+ return net_usershare_delete_usage(c, argc, argv);
+ }
+
+ if ((sharename = strlower_talloc(talloc_tos(), argv[0])) == NULL) {
+ d_fprintf(stderr, _("strlower_talloc failed\n"));
+ return -1;
+ }
+
+ if (!validate_net_name(sharename, INVALID_SHARENAME_CHARS, strlen(sharename))) {
+ d_fprintf(stderr, _("net usershare delete: share name %s contains "
+ "invalid characters (any of %s)\n"),
+ sharename, INVALID_SHARENAME_CHARS);
+ TALLOC_FREE(sharename);
+ return -1;
+ }
+
+ us_path = talloc_asprintf(talloc_tos(),
+ "%s/%s",
+ lp_usershare_path(talloc_tos(), lp_sub),
+ sharename);
+ if (!us_path) {
+ TALLOC_FREE(sharename);
+ return -1;
+ }
+
+ if (unlink(us_path) != 0) {
+ d_fprintf(stderr, _("net usershare delete: unable to remove usershare %s. "
+ "Error was %s\n"),
+ us_path, strerror(errno));
+ TALLOC_FREE(sharename);
+ return -1;
+ }
+ TALLOC_FREE(sharename);
+ return 0;
+}
+
+/***************************************************************************
+ Data structures to handle a list of usershare files.
+***************************************************************************/
+
+struct file_list {
+ struct file_list *next, *prev;
+ const char *pathname;
+};
+
+static struct file_list *flist;
+
+/***************************************************************************
+***************************************************************************/
+
+static int get_share_list(TALLOC_CTX *ctx, const char *wcard, bool only_ours)
+{
+ DIR *dp;
+ struct dirent *de;
+ uid_t myuid = geteuid();
+ struct file_list *fl = NULL;
+ char *basepath = get_basepath(ctx);
+
+ if (!basepath) {
+ return -1;
+ }
+ dp = opendir(basepath);
+ if (!dp) {
+ d_fprintf(stderr,
+ _("get_share_list: cannot open usershare directory %s. "
+ "Error %s\n"),
+ basepath, strerror(errno) );
+ return -1;
+ }
+
+ while((de = readdir(dp)) != 0) {
+ SMB_STRUCT_STAT sbuf;
+ char *path;
+ const char *n = de->d_name;
+
+ /* Ignore . and .. */
+ if (*n == '.') {
+ if ((n[1] == '\0') || (n[1] == '.' && n[2] == '\0')) {
+ continue;
+ }
+ }
+
+ if (!validate_net_name(n, INVALID_SHARENAME_CHARS, strlen(n))) {
+ d_fprintf(stderr,
+ _("get_share_list: ignoring bad share "
+ "name %s\n"), n);
+ continue;
+ }
+ path = talloc_asprintf(ctx,
+ "%s/%s",
+ basepath,
+ n);
+ if (!path) {
+ closedir(dp);
+ return -1;
+ }
+
+ if (sys_lstat(path, &sbuf, false) != 0) {
+ d_fprintf(stderr,
+ _("get_share_list: can't lstat file %s. Error "
+ "was %s\n"),
+ path, strerror(errno) );
+ continue;
+ }
+
+ if (!S_ISREG(sbuf.st_ex_mode)) {
+ d_fprintf(stderr,
+ _("get_share_list: file %s is not a regular "
+ "file. Ignoring.\n"),
+ path );
+ continue;
+ }
+
+ if (only_ours && sbuf.st_ex_uid != myuid) {
+ continue;
+ }
+
+ if (!unix_wild_match(wcard, n)) {
+ continue;
+ }
+
+ /* (Finally) - add to list. */
+ fl = talloc(ctx, struct file_list);
+ if (!fl) {
+ closedir(dp);
+ return -1;
+ }
+ fl->pathname = talloc_strdup(ctx, n);
+ if (!fl->pathname) {
+ closedir(dp);
+ return -1;
+ }
+
+ DLIST_ADD(flist, fl);
+ }
+
+ closedir(dp);
+ return 0;
+}
+
+enum us_priv_op { US_LIST_OP, US_INFO_OP};
+
+struct us_priv_info {
+ TALLOC_CTX *ctx;
+ enum us_priv_op op;
+ struct net_context *c;
+};
+
+/***************************************************************************
+ Call a function for every share on the list.
+***************************************************************************/
+
+static int process_share_list(int (*fn)(struct file_list *, void *), void *priv)
+{
+ struct file_list *fl;
+ int ret = 0;
+
+ for (fl = flist; fl; fl = fl->next) {
+ ret = (*fn)(fl, priv);
+ }
+
+ return ret;
+}
+
+/***************************************************************************
+ Info function.
+***************************************************************************/
+
+static int info_fn(struct file_list *fl, void *priv)
+{
+ SMB_STRUCT_STAT sbuf;
+ char **lines = NULL;
+ struct us_priv_info *pi = (struct us_priv_info *)priv;
+ TALLOC_CTX *ctx = pi->ctx;
+ struct net_context *c = pi->c;
+ int fd = -1;
+ int numlines = 0;
+ struct security_descriptor *psd = NULL;
+ char *basepath;
+ char *sharepath = NULL;
+ char *comment = NULL;
+ char *cp_sharename = NULL;
+ char *acl_str;
+ int num_aces;
+ char sep_str[2];
+ enum usershare_err us_err;
+ bool guest_ok = false;
+
+ sep_str[0] = *lp_winbind_separator();
+ sep_str[1] = '\0';
+
+ basepath = get_basepath(ctx);
+ if (!basepath) {
+ return -1;
+ }
+ basepath = talloc_asprintf_append(basepath,
+ "/%s",
+ fl->pathname);
+ if (!basepath) {
+ return -1;
+ }
+
+#ifdef O_NOFOLLOW
+ fd = open(basepath, O_RDONLY|O_NOFOLLOW, 0);
+#else
+ fd = open(basepath, O_RDONLY, 0);
+#endif
+
+ if (fd == -1) {
+ d_fprintf(stderr, _("info_fn: unable to open %s. %s\n"),
+ basepath, strerror(errno) );
+ return -1;
+ }
+
+ /* Paranoia... */
+ if (sys_fstat(fd, &sbuf, false) != 0) {
+ d_fprintf(stderr,
+ _("info_fn: can't fstat file %s. Error was %s\n"),
+ basepath, strerror(errno) );
+ close(fd);
+ return -1;
+ }
+
+ if (!S_ISREG(sbuf.st_ex_mode)) {
+ d_fprintf(stderr,
+ _("info_fn: file %s is not a regular file. Ignoring.\n"),
+ basepath );
+ close(fd);
+ return -1;
+ }
+
+ lines = fd_lines_load(fd, &numlines, 10240, NULL);
+ close(fd);
+
+ if (lines == NULL) {
+ return -1;
+ }
+
+ /* Ensure it's well formed. */
+ us_err = parse_usershare_file(ctx, &sbuf, fl->pathname, -1, lines, numlines,
+ &sharepath,
+ &comment,
+ &cp_sharename,
+ &psd,
+ &guest_ok);
+
+ TALLOC_FREE(lines);
+
+ if (us_err != USERSHARE_OK) {
+ d_fprintf(stderr,
+ _("info_fn: file %s is not a well formed usershare "
+ "file.\n"),
+ basepath );
+ d_fprintf(stderr, _("info_fn: Error was %s.\n"),
+ get_us_error_code(us_err) );
+ return -1;
+ }
+
+ acl_str = talloc_strdup(ctx, "usershare_acl=");
+ if (!acl_str) {
+ return -1;
+ }
+
+ for (num_aces = 0; num_aces < psd->dacl->num_aces; num_aces++) {
+ const char *domain;
+ const char *name;
+ NTSTATUS ntstatus;
+
+ ntstatus = net_lookup_name_from_sid(c, ctx,
+ &psd->dacl->aces[num_aces].trustee,
+ &domain, &name);
+
+ if (NT_STATUS_IS_OK(ntstatus)) {
+ if (domain && *domain) {
+ acl_str = talloc_asprintf_append(acl_str,
+ "%s%s",
+ domain,
+ sep_str);
+ if (!acl_str) {
+ return -1;
+ }
+ }
+ acl_str = talloc_asprintf_append(acl_str,
+ "%s",
+ name);
+ if (!acl_str) {
+ return -1;
+ }
+
+ } else {
+ struct dom_sid_buf sidstr;
+
+ acl_str = talloc_asprintf_append(
+ acl_str,
+ "%s",
+ dom_sid_str_buf(
+ &psd->dacl->aces[num_aces].trustee,
+ &sidstr));
+ if (!acl_str) {
+ return -1;
+ }
+ }
+ acl_str = talloc_asprintf_append(acl_str, ":");
+ if (!acl_str) {
+ return -1;
+ }
+
+ if (psd->dacl->aces[num_aces].type == SEC_ACE_TYPE_ACCESS_DENIED) {
+ acl_str = talloc_asprintf_append(acl_str, "D,");
+ if (!acl_str) {
+ return -1;
+ }
+ } else {
+ if (psd->dacl->aces[num_aces].access_mask & GENERIC_ALL_ACCESS) {
+ acl_str = talloc_asprintf_append(acl_str, "F,");
+ } else {
+ acl_str = talloc_asprintf_append(acl_str, "R,");
+ }
+ if (!acl_str) {
+ return -1;
+ }
+ }
+ }
+
+ /* NOTE: This is smb.conf-like output. Do not translate. */
+ if (pi->op == US_INFO_OP) {
+ d_printf("[%s]\n", cp_sharename );
+ d_printf("path=%s\n", sharepath );
+ d_printf("comment=%s\n", comment);
+ d_printf("%s\n", acl_str);
+ d_printf("guest_ok=%c\n\n", guest_ok ? 'y' : 'n');
+ } else if (pi->op == US_LIST_OP) {
+ d_printf("%s\n", cp_sharename);
+ }
+
+ return 0;
+}
+
+/***************************************************************************
+ Print out info (internal detail) on userlevel shares.
+***************************************************************************/
+
+static int net_usershare_info(struct net_context *c, int argc, const char **argv)
+{
+ fstring wcard;
+ bool only_ours = true;
+ int ret = -1;
+ struct us_priv_info pi;
+ TALLOC_CTX *ctx;
+
+ fstrcpy(wcard, "*");
+
+ if (c->display_usage)
+ return net_usershare_info_usage(c, argc, argv);
+
+ if (c->opt_long_list_entries) {
+ only_ours = false;
+ }
+
+ switch (argc) {
+ case 0:
+ break;
+ case 1:
+ fstrcpy(wcard, argv[0]);
+ break;
+ default:
+ return net_usershare_info_usage(c, argc, argv);
+ }
+
+ if (!strlower_m(wcard)) {
+ return -1;
+ }
+
+ ctx = talloc_init("share_info");
+ ret = get_share_list(ctx, wcard, only_ours);
+ if (ret) {
+ return ret;
+ }
+
+ pi.ctx = ctx;
+ pi.op = US_INFO_OP;
+ pi.c = c;
+
+ ret = process_share_list(info_fn, &pi);
+ talloc_destroy(ctx);
+ return ret;
+}
+
+/***************************************************************************
+ Count the current total number of usershares.
+***************************************************************************/
+
+static int count_num_usershares(void)
+{
+ DIR *dp;
+ struct dirent *de;
+ int num_usershares = 0;
+ TALLOC_CTX *ctx = talloc_tos();
+ char *basepath = get_basepath(ctx);
+
+ if (!basepath) {
+ return -1;
+ }
+
+ dp = opendir(basepath);
+ if (!dp) {
+ d_fprintf(stderr,
+ _("count_num_usershares: cannot open usershare "
+ "directory %s. Error %s\n"),
+ basepath, strerror(errno) );
+ return -1;
+ }
+
+ while((de = readdir(dp)) != 0) {
+ SMB_STRUCT_STAT sbuf;
+ char *path;
+ const char *n = de->d_name;
+
+ /* Ignore . and .. */
+ if (*n == '.') {
+ if ((n[1] == '\0') || (n[1] == '.' && n[2] == '\0')) {
+ continue;
+ }
+ }
+
+ if (!validate_net_name(n, INVALID_SHARENAME_CHARS, strlen(n))) {
+ d_fprintf(stderr,
+ _("count_num_usershares: ignoring bad share "
+ "name %s\n"), n);
+ continue;
+ }
+ path = talloc_asprintf(ctx,
+ "%s/%s",
+ basepath,
+ n);
+ if (!path) {
+ closedir(dp);
+ return -1;
+ }
+
+ if (sys_lstat(path, &sbuf, false) != 0) {
+ d_fprintf(stderr,
+ _("count_num_usershares: can't lstat file %s. "
+ "Error was %s\n"),
+ path, strerror(errno) );
+ continue;
+ }
+
+ if (!S_ISREG(sbuf.st_ex_mode)) {
+ d_fprintf(stderr,
+ _("count_num_usershares: file %s is not a "
+ "regular file. Ignoring.\n"),
+ path );
+ continue;
+ }
+ num_usershares++;
+ }
+
+ closedir(dp);
+ return num_usershares;
+}
+
+/***************************************************************************
+ Add a single userlevel share.
+***************************************************************************/
+
+static int net_usershare_add(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *ctx = talloc_stackframe();
+ SMB_STRUCT_STAT sbuf;
+ SMB_STRUCT_STAT lsbuf;
+ char *sharename;
+ const char *cp_sharename;
+ char *full_path;
+ char *full_path_tmp;
+ const char *us_path;
+ const char *us_comment;
+ const char *arg_acl;
+ char *us_acl;
+ char *file_img;
+ int num_aces = 0;
+ int i;
+ int tmpfd;
+ const char *pacl;
+ size_t to_write;
+ uid_t myeuid = geteuid();
+ bool guest_ok = false;
+ int num_usershares;
+ mode_t mask;
+
+ us_comment = "";
+ arg_acl = "S-1-1-0:R";
+
+ if (c->display_usage) {
+ TALLOC_FREE(ctx);
+ return net_usershare_add_usage(c, argc, argv);
+ }
+
+ switch (argc) {
+ case 0:
+ case 1:
+ default:
+ TALLOC_FREE(ctx);
+ return net_usershare_add_usage(c, argc, argv);
+ case 2:
+ cp_sharename = argv[0];
+ sharename = strlower_talloc(ctx, argv[0]);
+ us_path = argv[1];
+ break;
+ case 3:
+ cp_sharename = argv[0];
+ sharename = strlower_talloc(ctx, argv[0]);
+ us_path = argv[1];
+ us_comment = argv[2];
+ break;
+ case 4:
+ cp_sharename = argv[0];
+ sharename = strlower_talloc(ctx, argv[0]);
+ us_path = argv[1];
+ us_comment = argv[2];
+ arg_acl = argv[3];
+ break;
+ case 5:
+ cp_sharename = argv[0];
+ sharename = strlower_talloc(ctx, argv[0]);
+ us_path = argv[1];
+ us_comment = argv[2];
+ arg_acl = argv[3];
+ if (strlen(arg_acl) == 0) {
+ arg_acl = "S-1-1-0:R";
+ }
+ if (!strnequal(argv[4], "guest_ok=", 9)) {
+ TALLOC_FREE(ctx);
+ return net_usershare_add_usage(c, argc, argv);
+ }
+ switch (argv[4][9]) {
+ case 'y':
+ case 'Y':
+ guest_ok = true;
+ break;
+ case 'n':
+ case 'N':
+ guest_ok = false;
+ break;
+ default:
+ TALLOC_FREE(ctx);
+ return net_usershare_add_usage(c, argc, argv);
+ }
+ break;
+ }
+
+ /* Ensure we're under the "usershare max shares" number. Advisory only. */
+ num_usershares = count_num_usershares();
+ if (num_usershares >= lp_usershare_max_shares()) {
+ d_fprintf(stderr,
+ _("net usershare add: maximum number of allowed "
+ "usershares (%d) reached\n"),
+ lp_usershare_max_shares() );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ if (!validate_net_name(sharename, INVALID_SHARENAME_CHARS, strlen(sharename))) {
+ d_fprintf(stderr, _("net usershare add: share name %s contains "
+ "invalid characters (any of %s)\n"),
+ sharename, INVALID_SHARENAME_CHARS);
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* Disallow shares the same as users. */
+ if (getpwnam(sharename)) {
+ d_fprintf(stderr,
+ _("net usershare add: share name %s is already a valid "
+ "system user name\n"),
+ sharename );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* Construct the full path for the usershare file. */
+ full_path = get_basepath(ctx);
+ if (!full_path) {
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+ full_path_tmp = talloc_asprintf(ctx,
+ "%s/:tmpXXXXXX",
+ full_path);
+ if (!full_path_tmp) {
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ full_path = talloc_asprintf_append(full_path,
+ "/%s",
+ sharename);
+ if (!full_path) {
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* The path *must* be absolute. */
+ if (us_path[0] != '/') {
+ d_fprintf(stderr,
+ _("net usershare add: path %s is not an absolute "
+ "path.\n"),
+ us_path);
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* Check the directory to be shared exists. */
+ if (sys_stat(us_path, &sbuf, false) != 0) {
+ d_fprintf(stderr,
+ _("net usershare add: cannot stat path %s to ensure "
+ "this is a directory. Error was %s\n"),
+ us_path, strerror(errno) );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ if (!S_ISDIR(sbuf.st_ex_mode)) {
+ d_fprintf(stderr,
+ _("net usershare add: path %s is not a directory.\n"),
+ us_path );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* If we're not root, check if we're restricted to sharing out directories
+ that we own only. */
+
+ if ((myeuid != 0) && lp_usershare_owner_only() && (myeuid != sbuf.st_ex_uid)) {
+ d_fprintf(stderr, _("net usershare add: cannot share path %s as "
+ "we are restricted to only sharing directories we own.\n"
+ "\tAsk the administrator to add the line \"usershare owner only = false\" \n"
+ "\tto the [global] section of the smb.conf to allow this.\n"),
+ us_path );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* No validation needed on comment. Now go through and validate the
+ acl string. Convert names to SID's as needed. Then run it through
+ parse_usershare_acl to ensure it's valid. */
+
+ /* Start off the string we'll append to. */
+ us_acl = talloc_strdup(ctx, "");
+ if (!us_acl) {
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ pacl = arg_acl;
+ num_aces = 1;
+
+ /* Add the number of ',' characters to get the number of aces. */
+ num_aces += count_chars(pacl,',');
+
+ for (i = 0; i < num_aces; i++) {
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ const char *pcolon = strchr_m(pacl, ':');
+ const char *name;
+
+ if (pcolon == NULL) {
+ d_fprintf(stderr,
+ _("net usershare add: malformed acl %s "
+ "(missing ':').\n"),
+ pacl );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ switch(pcolon[1]) {
+ case 'f':
+ case 'F':
+ case 'd':
+ case 'r':
+ case 'R':
+ break;
+ default:
+ d_fprintf(stderr,
+ _("net usershare add: malformed acl %s "
+ "(access control must be 'r', 'f', "
+ "or 'd')\n"),
+ pacl );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ if (pcolon[2] != ',' && pcolon[2] != '\0') {
+ d_fprintf(stderr,
+ _("net usershare add: malformed terminating "
+ "character for acl %s\n"),
+ pacl );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* Get the name */
+ if ((name = talloc_strndup(ctx, pacl, pcolon - pacl)) == NULL) {
+ d_fprintf(stderr, _("talloc_strndup failed\n"));
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+ if (!string_to_sid(&sid, name)) {
+ /* Convert to a SID */
+ NTSTATUS ntstatus = net_lookup_sid_from_name(c, ctx, name, &sid);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ d_fprintf(stderr,
+ _("net usershare add: cannot convert "
+ "name \"%s\" to a SID. %s."),
+ name, get_friendly_nt_error_msg(ntstatus) );
+ if (NT_STATUS_EQUAL(ntstatus, NT_STATUS_CONNECTION_REFUSED)) {
+ d_fprintf(stderr,
+ _(" Maybe smbd is not running.\n"));
+ } else {
+ d_fprintf(stderr, "\n");
+ }
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+ }
+ us_acl = talloc_asprintf_append(
+ us_acl,
+ "%s:%c,",
+ dom_sid_str_buf(&sid, &buf),
+ pcolon[1]);
+ if (us_acl == NULL) {
+ d_fprintf(stderr,
+ _("net usershare add: talloc_asprintf_append() failed\n"));
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* Move to the next ACL entry. */
+ if (pcolon[2] == ',') {
+ pacl = &pcolon[3];
+ }
+ }
+
+ /* Remove the last ',' */
+ us_acl[strlen(us_acl)-1] = '\0';
+
+ if (guest_ok && !lp_usershare_allow_guests()) {
+ d_fprintf(stderr, _("net usershare add: guest_ok=y requested "
+ "but the \"usershare allow guests\" parameter is not "
+ "enabled by this server.\n"));
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* Create a temporary filename for this share. */
+ mask = umask(S_IRWXO | S_IRWXG);
+ tmpfd = mkstemp(full_path_tmp);
+ umask(mask);
+
+ if (tmpfd == -1) {
+ d_fprintf(stderr,
+ _("net usershare add: cannot create tmp file %s\n"),
+ full_path_tmp );
+ TALLOC_FREE(ctx);
+ return -1;
+ }
+
+ /* Ensure we opened the file we thought we did. */
+ if (sys_lstat(full_path_tmp, &lsbuf, false) != 0) {
+ d_fprintf(stderr,
+ _("net usershare add: cannot lstat tmp file %s\n"),
+ full_path_tmp );
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+
+ /* Check this is the same as the file we opened. */
+ if (sys_fstat(tmpfd, &sbuf, false) != 0) {
+ d_fprintf(stderr,
+ _("net usershare add: cannot fstat tmp file %s\n"),
+ full_path_tmp );
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+
+ if (!S_ISREG(sbuf.st_ex_mode) || sbuf.st_ex_dev != lsbuf.st_ex_dev || sbuf.st_ex_ino != lsbuf.st_ex_ino) {
+ d_fprintf(stderr,
+ _("net usershare add: tmp file %s is not a regular "
+ "file ?\n"),
+ full_path_tmp );
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+
+ if (fchmod(tmpfd, 0644) == -1) {
+ d_fprintf(stderr,
+ _("net usershare add: failed to fchmod tmp file %s "
+ "to 0644\n"),
+ full_path_tmp );
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+
+ /* Create the in-memory image of the file. */
+ file_img = talloc_strdup(ctx, "#VERSION 2\npath=");
+ if (file_img == NULL) {
+ d_fprintf(stderr,
+ _("net usershare add: talloc_strdup() failed\n"));
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+ file_img = talloc_asprintf_append(file_img,
+ "%s\ncomment=%s\nusershare_acl=%s\n"
+ "guest_ok=%c\nsharename=%s\n",
+ us_path,
+ us_comment,
+ us_acl,
+ guest_ok ? 'y' : 'n',
+ cp_sharename);
+ if (file_img == NULL) {
+ d_fprintf(stderr,
+ _("net usershare add: talloc_asprintf_append() failed\n"));
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+
+ to_write = strlen(file_img);
+
+ if (write(tmpfd, file_img, to_write) != to_write) {
+ d_fprintf(stderr,
+ _("net usershare add: failed to write %u bytes to "
+ "file %s. Error was %s\n"),
+ (unsigned int)to_write, full_path_tmp, strerror(errno));
+ unlink(full_path_tmp);
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+
+ /* Attempt to replace any existing share by this name. */
+ if (rename(full_path_tmp, full_path) != 0) {
+ unlink(full_path_tmp);
+ d_fprintf(stderr,
+ _("net usershare add: failed to add share %s. Error "
+ "was %s\n"),
+ sharename, strerror(errno));
+ TALLOC_FREE(ctx);
+ close(tmpfd);
+ return -1;
+ }
+
+ close(tmpfd);
+
+ if (c->opt_long_list_entries) {
+ const char *my_argv[2];
+ my_argv[0] = sharename;
+ my_argv[1] = NULL;
+ net_usershare_info(c, 1, my_argv);
+ }
+
+ TALLOC_FREE(ctx);
+ return 0;
+}
+
+#if 0
+/***************************************************************************
+ List function.
+***************************************************************************/
+
+static int list_fn(struct file_list *fl, void *priv)
+{
+ d_printf("%s\n", fl->pathname);
+ return 0;
+}
+#endif
+
+/***************************************************************************
+ List userlevel shares.
+***************************************************************************/
+
+static int net_usershare_list(struct net_context *c, int argc,
+ const char **argv)
+{
+ fstring wcard;
+ bool only_ours = true;
+ int ret = -1;
+ struct us_priv_info pi;
+ TALLOC_CTX *ctx;
+
+ fstrcpy(wcard, "*");
+
+ if (c->display_usage)
+ return net_usershare_list_usage(c, argc, argv);
+
+ if (c->opt_long_list_entries) {
+ only_ours = false;
+ }
+
+ switch (argc) {
+ case 0:
+ break;
+ case 1:
+ fstrcpy(wcard, argv[0]);
+ break;
+ default:
+ return net_usershare_list_usage(c, argc, argv);
+ }
+
+ if (!strlower_m(wcard)) {
+ return -1;
+ }
+
+ ctx = talloc_init("share_list");
+ ret = get_share_list(ctx, wcard, only_ours);
+ if (ret) {
+ return ret;
+ }
+
+ pi.ctx = ctx;
+ pi.op = US_LIST_OP;
+ pi.c = c;
+
+ ret = process_share_list(info_fn, &pi);
+ talloc_destroy(ctx);
+ return ret;
+}
+
+/***************************************************************************
+ Entry-point for all the USERSHARE functions.
+***************************************************************************/
+
+int net_usershare(struct net_context *c, int argc, const char **argv)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ DIR *dp;
+
+ struct functable func[] = {
+ {
+ "add",
+ net_usershare_add,
+ NET_TRANSPORT_LOCAL,
+ N_("Add/modify user defined share"),
+ N_("net usershare add\n"
+ " Add/modify user defined share")
+ },
+ {
+ "delete",
+ net_usershare_delete,
+ NET_TRANSPORT_LOCAL,
+ N_("Delete user defined share"),
+ N_("net usershare delete\n"
+ " Delete user defined share")
+ },
+ {
+ "info",
+ net_usershare_info,
+ NET_TRANSPORT_LOCAL,
+ N_("Display information about a user defined share"),
+ N_("net usershare info\n"
+ " Display information about a user defined share")
+ },
+ {
+ "list",
+ net_usershare_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List user defined shares"),
+ N_("net usershare list\n"
+ " List user defined shares")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ if (lp_usershare_max_shares() == 0) {
+ d_fprintf(stderr,
+ _("net usershare: usershares are currently "
+ "disabled\n"));
+ return -1;
+ }
+
+ dp = opendir(lp_usershare_path(talloc_tos(), lp_sub));
+ if (!dp) {
+ int err = errno;
+ d_fprintf(stderr,
+ _("net usershare: cannot open usershare directory %s. "
+ "Error %s\n"),
+ lp_usershare_path(talloc_tos(), lp_sub), strerror(err) );
+ if (err == EACCES) {
+ d_fprintf(stderr,
+ _("You do not have permission to create a "
+ "usershare. Ask your administrator to grant "
+ "you permissions to create a share.\n"));
+ } else if (err == ENOENT) {
+ d_fprintf(stderr,
+ _("Please ask your system administrator to "
+ "enable user sharing.\n"));
+ }
+ return -1;
+ }
+ closedir(dp);
+
+ return net_run_function(c, argc, argv, "net usershare", func);
+}
diff --git a/source3/utils/net_util.c b/source3/utils/net_util.c
new file mode 100644
index 0000000..f3b7755
--- /dev/null
+++ b/source3/utils/net_util.c
@@ -0,0 +1,614 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Helper routines for net
+ * Copyright (C) Volker Lendecke 2006
+ * Copyright (C) Kai Blin 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "includes.h"
+#include "utils/net.h"
+#include "libsmb/namequery.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../librpc/gen_ndr/ndr_dssetup_c.h"
+#include "secrets.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "lib/param/param.h"
+#include "auth/gensec/gensec.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "lib/cmdline/cmdline.h"
+
+NTSTATUS net_rpc_lookup_name(struct net_context *c,
+ TALLOC_CTX *mem_ctx, struct cli_state *cli,
+ const char *name, const char **ret_domain,
+ const char **ret_name, struct dom_sid *ret_sid,
+ enum lsa_SidType *ret_type)
+{
+ struct rpc_pipe_client *lsa_pipe = NULL;
+ struct policy_handle pol;
+ NTSTATUS status, result;
+ const char **dom_names;
+ struct dom_sid *sids;
+ enum lsa_SidType *types;
+ struct dcerpc_binding_handle *b;
+
+ ZERO_STRUCT(pol);
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &lsa_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, _("Could not initialise lsa pipe\n"));
+ return status;
+ }
+
+ b = lsa_pipe->binding_handle;
+
+ status = rpccli_lsa_open_policy(lsa_pipe, mem_ctx, false,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_fprintf(stderr, "open_policy %s: %s\n", _("failed"),
+ nt_errstr(status));
+ return status;
+ }
+
+ status = rpccli_lsa_lookup_names(lsa_pipe, mem_ctx, &pol, 1,
+ &name, &dom_names, 1, &sids, &types);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* This can happen easily, don't log an error */
+ goto done;
+ }
+
+ if (ret_domain != NULL) {
+ *ret_domain = dom_names[0];
+ }
+ if (ret_name != NULL) {
+ *ret_name = talloc_strdup(mem_ctx, name);
+ }
+ if (ret_sid != NULL) {
+ sid_copy(ret_sid, &sids[0]);
+ }
+ if (ret_type != NULL) {
+ *ret_type = types[0];
+ }
+
+ done:
+ if (is_valid_policy_hnd(&pol)) {
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ }
+ TALLOC_FREE(lsa_pipe);
+
+ return status;
+}
+
+/****************************************************************************
+ Connect to \\server\service.
+****************************************************************************/
+
+NTSTATUS connect_to_service(struct net_context *c,
+ struct cli_state **cli_ctx,
+ const struct sockaddr_storage *server_ss,
+ const char *server_name,
+ const char *service_name,
+ const char *service_type)
+{
+ NTSTATUS nt_status;
+ int flags = 0;
+
+ if (strequal(service_type, "IPC")) {
+ flags |= CLI_FULL_CONNECTION_IPC;
+ }
+
+ nt_status = cli_full_connection_creds(cli_ctx, NULL, server_name,
+ server_ss, c->opt_port,
+ service_name, service_type,
+ c->creds,
+ flags);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr, _("Could not connect to server %s\n"),
+ server_name);
+
+ /* Display a nicer message depending on the result */
+
+ if (NT_STATUS_V(nt_status) ==
+ NT_STATUS_V(NT_STATUS_LOGON_FAILURE))
+ d_fprintf(stderr,
+ _("The username or password was not "
+ "correct.\n"));
+
+ if (NT_STATUS_V(nt_status) ==
+ NT_STATUS_V(NT_STATUS_ACCOUNT_LOCKED_OUT))
+ d_fprintf(stderr, _("The account was locked out.\n"));
+
+ if (NT_STATUS_V(nt_status) ==
+ NT_STATUS_V(NT_STATUS_ACCOUNT_DISABLED))
+ d_fprintf(stderr, _("The account was disabled.\n"));
+ return nt_status;
+ }
+
+ return nt_status;
+}
+
+/****************************************************************************
+ Connect to \\server\ipc$.
+****************************************************************************/
+
+NTSTATUS connect_to_ipc(struct net_context *c,
+ struct cli_state **cli_ctx,
+ const struct sockaddr_storage *server_ss,
+ const char *server_name)
+{
+ return connect_to_service(c, cli_ctx, server_ss, server_name, "IPC$",
+ "IPC");
+}
+
+/****************************************************************************
+ Connect to \\server\ipc$ anonymously.
+****************************************************************************/
+
+NTSTATUS connect_to_ipc_anonymous(struct net_context *c,
+ struct cli_state **cli_ctx,
+ const struct sockaddr_storage *server_ss,
+ const char *server_name)
+{
+ NTSTATUS nt_status;
+ struct cli_credentials *anon_creds = NULL;
+
+ anon_creds = cli_credentials_init_anon(c);
+ if (anon_creds == NULL) {
+ DBG_ERR("cli_credentials_init_anon() failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nt_status = cli_full_connection_creds(cli_ctx, c->opt_requester_name,
+ server_name, server_ss, c->opt_port,
+ "IPC$", "IPC",
+ anon_creds,
+ CLI_FULL_CONNECTION_IPC);
+
+ if (NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ } else {
+ DEBUG(1,("Cannot connect to server (anonymously). Error was %s\n", nt_errstr(nt_status)));
+ return nt_status;
+ }
+}
+
+/**
+ * Connect a server and open a given pipe
+ *
+ * @param cli_dst A cli_state
+ * @param pipe The pipe to open
+ * @param got_pipe boolean that stores if we got a pipe
+ *
+ * @return Normal NTSTATUS return.
+ **/
+NTSTATUS connect_dst_pipe(struct net_context *c, struct cli_state **cli_dst,
+ struct rpc_pipe_client **pp_pipe_hnd,
+ const struct ndr_interface_table *table)
+{
+ NTSTATUS nt_status;
+ char *server_name = SMB_STRDUP("127.0.0.1");
+ struct cli_state *cli_tmp = NULL;
+ struct rpc_pipe_client *pipe_hnd = NULL;
+
+ if (server_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (c->opt_destination) {
+ SAFE_FREE(server_name);
+ if ((server_name = SMB_STRDUP(c->opt_destination)) == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ /* make a connection to a named pipe */
+ nt_status = connect_to_ipc(c, &cli_tmp, NULL, server_name);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ SAFE_FREE(server_name);
+ return nt_status;
+ }
+
+ nt_status = cli_rpc_pipe_open_noauth(cli_tmp, table,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0, ("couldn't not initialize pipe\n"));
+ cli_shutdown(cli_tmp);
+ SAFE_FREE(server_name);
+ return nt_status;
+ }
+
+ *cli_dst = cli_tmp;
+ *pp_pipe_hnd = pipe_hnd;
+ SAFE_FREE(server_name);
+
+ return nt_status;
+}
+
+/****************************************************************************
+ Use the local machine account (krb) and password for this session.
+****************************************************************************/
+
+int net_use_krb_machine_account(struct net_context *c)
+{
+ char *user_name = NULL;
+
+ if (!secrets_init()) {
+ d_fprintf(stderr,_("ERROR: Unable to open secrets database\n"));
+ exit(1);
+ }
+
+ c->opt_password = secrets_fetch_machine_password(
+ c->opt_target_workgroup, NULL, NULL);
+ if (asprintf(&user_name, "%s$@%s", lp_netbios_name(), lp_realm()) == -1) {
+ return -1;
+ }
+ c->opt_user_name = user_name;
+ c->opt_user_specified = true;
+
+ cli_credentials_set_machine_account(c->creds, c->lp_ctx);
+ return 0;
+}
+
+bool net_find_server(struct net_context *c,
+ const char *domain,
+ unsigned flags,
+ struct sockaddr_storage *server_ss,
+ char **server_name)
+{
+ const char *d = domain ? domain : c->opt_target_workgroup;
+
+ if (c->opt_host) {
+ *server_name = SMB_STRDUP(c->opt_host);
+ }
+
+ if (c->opt_have_ip) {
+ *server_ss = c->opt_dest_ip;
+ if (!*server_name) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr), &c->opt_dest_ip);
+ *server_name = SMB_STRDUP(addr);
+ }
+ } else if (*server_name) {
+ /* resolve the IP address */
+ if (!resolve_name(*server_name, server_ss, 0x20, false)) {
+ DEBUG(1,("Unable to resolve server name\n"));
+ return false;
+ }
+ } else if (flags & NET_FLAGS_PDC) {
+ fstring dc_name;
+ struct sockaddr_storage pdc_ss;
+
+ if (!get_pdc_ip(d, &pdc_ss)) {
+ DEBUG(1,("Unable to resolve PDC server address\n"));
+ return false;
+ }
+
+ if (is_zero_addr(&pdc_ss)) {
+ return false;
+ }
+
+ if (!name_status_find(d, 0x1b, 0x20, &pdc_ss, dc_name)) {
+ return false;
+ }
+
+ *server_name = SMB_STRDUP(dc_name);
+ *server_ss = pdc_ss;
+ } else if (flags & NET_FLAGS_DMB) {
+ struct sockaddr_storage msbrow_ss;
+ char addr[INET6_ADDRSTRLEN];
+
+ /* if (!resolve_name(MSBROWSE, &msbrow_ip, 1, false)) */
+ if (!resolve_name(d, &msbrow_ss, 0x1B, false)) {
+ DEBUG(1,("Unable to resolve domain browser via name lookup\n"));
+ return false;
+ }
+ *server_ss = msbrow_ss;
+ print_sockaddr(addr, sizeof(addr), server_ss);
+ *server_name = SMB_STRDUP(addr);
+ } else if (flags & NET_FLAGS_MASTER) {
+ struct sockaddr_storage brow_ss;
+ char addr[INET6_ADDRSTRLEN];
+ if (!resolve_name(d, &brow_ss, 0x1D, false)) {
+ /* go looking for workgroups */
+ DEBUG(1,("Unable to resolve master browser via name lookup\n"));
+ return false;
+ }
+ *server_ss = brow_ss;
+ print_sockaddr(addr, sizeof(addr), server_ss);
+ *server_name = SMB_STRDUP(addr);
+ } else if (!(flags & NET_FLAGS_LOCALHOST_DEFAULT_INSANE)) {
+ if (!interpret_string_addr(server_ss,
+ "127.0.0.1", AI_NUMERICHOST)) {
+ DEBUG(1,("Unable to resolve 127.0.0.1\n"));
+ return false;
+ }
+ *server_name = SMB_STRDUP("127.0.0.1");
+ }
+
+ if (!*server_name) {
+ DEBUG(1,("no server to connect to\n"));
+ return false;
+ }
+
+ return true;
+}
+
+bool net_find_pdc(struct sockaddr_storage *server_ss,
+ fstring server_name,
+ const char *domain_name)
+{
+ if (!get_pdc_ip(domain_name, server_ss)) {
+ return false;
+ }
+ if (is_zero_addr(server_ss)) {
+ return false;
+ }
+
+ if (!name_status_find(domain_name, 0x1b, 0x20, server_ss, server_name)) {
+ return false;
+ }
+
+ return true;
+}
+
+NTSTATUS net_make_ipc_connection(struct net_context *c, unsigned flags,
+ struct cli_state **pcli)
+{
+ return net_make_ipc_connection_ex(c, c->opt_workgroup, NULL, NULL, flags, pcli);
+}
+
+NTSTATUS net_make_ipc_connection_ex(struct net_context *c ,const char *domain,
+ const char *server,
+ const struct sockaddr_storage *pss,
+ unsigned flags, struct cli_state **pcli)
+{
+ char *server_name = NULL;
+ struct sockaddr_storage server_ss;
+ struct cli_state *cli = NULL;
+ NTSTATUS nt_status;
+
+ if ( !server || !pss ) {
+ if (!net_find_server(c, domain, flags, &server_ss,
+ &server_name)) {
+ d_fprintf(stderr, _("Unable to find a suitable server "
+ "for domain %s\n"), domain);
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ } else {
+ server_name = SMB_STRDUP( server );
+ server_ss = *pss;
+ }
+
+ if (flags & NET_FLAGS_ANONYMOUS) {
+ nt_status = connect_to_ipc_anonymous(c, &cli, &server_ss,
+ server_name);
+ } else {
+ nt_status = connect_to_ipc(c, &cli, &server_ss,
+ server_name);
+ }
+
+ /* store the server in the affinity cache if it was a PDC */
+
+ if ( (flags & NET_FLAGS_PDC) && NT_STATUS_IS_OK(nt_status) )
+ saf_store(cli->server_domain, server_name);
+
+ SAFE_FREE(server_name);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_fprintf(stderr, _("Connection failed: %s\n"),
+ nt_errstr(nt_status));
+ cli = NULL;
+ } else if (c->opt_request_timeout) {
+ cli_set_timeout(cli, c->opt_request_timeout * 1000);
+ }
+
+done:
+ if (pcli != NULL) {
+ *pcli = cli;
+ }
+ return nt_status;
+}
+
+/****************************************************************************
+****************************************************************************/
+
+/* TODO FIXME: Pass cli_creds via net_context and get rid of this function. */
+const char *net_prompt_pass(struct net_context *c, const char *user)
+{
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+
+ if (c->opt_password == NULL) {
+ c->opt_password = cli_credentials_get_password(creds);
+ }
+
+ return c->opt_password;
+}
+
+int net_run_function(struct net_context *c, int argc, const char **argv,
+ const char *whoami, struct functable *table)
+{
+ int i;
+
+ if (argc != 0) {
+ for (i=0; table[i].funcname != NULL; i++) {
+ if (strcasecmp_m(argv[0], table[i].funcname) == 0)
+ return table[i].fn(c, argc-1, argv+1);
+ }
+ }
+
+ if (c->display_usage == false) {
+ d_fprintf(stderr, _("Invalid command: %s %s\n"), whoami,
+ (argc > 0)?argv[0]:"");
+ }
+ d_printf(_("Usage:\n"));
+ for (i=0; table[i].funcname != NULL; i++) {
+ if(c->display_usage == false)
+ d_printf("%s %-15s %s\n", whoami, table[i].funcname,
+ _(table[i].description));
+ else
+ d_printf("%s\n", _(table[i].usage));
+ }
+
+ return c->display_usage?0:-1;
+}
+
+void net_display_usage_from_functable(struct functable *table)
+{
+ int i;
+ for (i=0; table[i].funcname != NULL; i++) {
+ d_printf("%s\n", _(table[i].usage));
+ }
+}
+
+void net_warn_member_options(void)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx != NULL) {
+ netlogon_creds_cli_warn_options(lp_ctx);
+ }
+
+ TALLOC_FREE(frame);
+}
+
+const char *net_share_type_str(int num_type)
+{
+ switch(num_type) {
+ case 0: return _("Disk");
+ case 1: return _("Print");
+ case 2: return _("Dev");
+ case 3: return _("IPC");
+ default: return _("Unknown");
+ }
+}
+
+static NTSTATUS net_scan_dc_noad(struct net_context *c,
+ struct cli_state *cli,
+ struct net_dc_info *dc_info)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct rpc_pipe_client *pipe_hnd = NULL;
+ struct dcerpc_binding_handle *b;
+ NTSTATUS status, result;
+ struct policy_handle pol;
+ union lsa_PolicyInformation *info;
+
+ ZERO_STRUCTP(dc_info);
+ ZERO_STRUCT(pol);
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc,
+ &pipe_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ b = pipe_hnd->binding_handle;
+
+ status = dcerpc_lsa_open_policy(b, mem_ctx,
+ false,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(b, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto done;
+ }
+
+ dc_info->netbios_domain_name = talloc_strdup(mem_ctx, info->account_domain.name.string);
+ if (dc_info->netbios_domain_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ done:
+ if (is_valid_policy_hnd(&pol)) {
+ dcerpc_lsa_Close(b, mem_ctx, &pol, &result);
+ }
+
+ TALLOC_FREE(pipe_hnd);
+
+ return status;
+}
+
+NTSTATUS net_scan_dc(struct net_context *c,
+ struct cli_state *cli,
+ struct net_dc_info *dc_info)
+{
+ TALLOC_CTX *mem_ctx = talloc_tos();
+ struct rpc_pipe_client *dssetup_pipe = NULL;
+ struct dcerpc_binding_handle *dssetup_handle = NULL;
+ union dssetup_DsRoleInfo info;
+ NTSTATUS status;
+ WERROR werr;
+
+ ZERO_STRUCTP(dc_info);
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_dssetup,
+ &dssetup_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("net_scan_dc: failed to open dssetup pipe with %s, "
+ "retrying with lsa pipe\n", nt_errstr(status)));
+ return net_scan_dc_noad(c, cli, dc_info);
+ }
+ dssetup_handle = dssetup_pipe->binding_handle;
+
+ status = dcerpc_dssetup_DsRoleGetPrimaryDomainInformation(dssetup_handle, mem_ctx,
+ DS_ROLE_BASIC_INFORMATION,
+ &info,
+ &werr);
+ TALLOC_FREE(dssetup_pipe);
+
+ if (NT_STATUS_IS_OK(status)) {
+ status = werror_to_ntstatus(werr);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ dc_info->is_dc = (info.basic.role & (DS_ROLE_PRIMARY_DC|DS_ROLE_BACKUP_DC));
+ dc_info->is_pdc = (info.basic.role & DS_ROLE_PRIMARY_DC);
+ dc_info->is_ad = (info.basic.flags & DS_ROLE_PRIMARY_DS_RUNNING);
+ dc_info->is_mixed_mode = (info.basic.flags & DS_ROLE_PRIMARY_DS_MIXED_MODE);
+ dc_info->netbios_domain_name = talloc_strdup(mem_ctx, info.basic.domain);
+ dc_info->dns_domain_name = talloc_strdup(mem_ctx, info.basic.dns_domain);
+ dc_info->forest_name = talloc_strdup(mem_ctx, info.basic.forest);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/utils/net_vfs.c b/source3/utils/net_vfs.c
new file mode 100644
index 0000000..410eef3
--- /dev/null
+++ b/source3/utils/net_vfs.c
@@ -0,0 +1,469 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Distributed SMB/CIFS Server Management Utility
+ * Copyright (C) 2019 Ralph Boehme <slow@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include <talloc.h>
+#include <tevent.h>
+#include <ftw.h>
+#include "system/filesys.h"
+#include "system/passwd.h"
+#include "lib/param/loadparm.h"
+#include "lib/param/param.h"
+#include "libcli/security/security.h"
+#include "smbd/proto.h"
+#include "locking/share_mode_lock.h"
+#include "locking/proto.h"
+#include "auth.h"
+#include "client.h"
+#include "util_sd.h"
+#include "lib/adouble.h"
+#include "lib/string_replace.h"
+#include "utils/net.h"
+#include "lib/global_contexts.h"
+
+#define NET_VFS_CMD_STREAM_TO_ADOUBLE "stream2adouble"
+
+static struct net_vfs_state {
+ TALLOC_CTX *mem_ctx;
+ struct net_context *c;
+ struct auth_session_info *session_info;
+ struct conn_struct_tos *conn_tos;
+} state;
+
+static void net_vfs_usage(void)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ "net vfs [OPTIONS] <share> ....\n");
+}
+
+static void net_vfs_getntacl_usage(void)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ "net vfs getntacl <share> <path>\n");
+}
+
+static void net_vfs_stream_to_appledouble_usage(void)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ "net vfs " NET_VFS_CMD_STREAM_TO_ADOUBLE
+ " [OPTIONS] <share> <path> [<path> ...]\n"
+ "Options:\n"
+ " --verbose verbose output\n"
+ " --continue continue on error\n"
+ " --recursive traverse directory hierarchy\n"
+ " --follow-symlinks follow symlinks\n");
+}
+
+static bool net_vfs_make_session_info(struct auth_session_info **session_info)
+{
+ NTSTATUS status;
+
+ if (non_root_mode()) {
+ struct passwd *p = NULL;
+
+ p = getpwuid(geteuid());
+ if (p == NULL) {
+ fprintf(stderr, "getpwuid(%d) failed\n", geteuid());
+ return false;
+ }
+
+ status = make_session_info_from_username(state.mem_ctx,
+ p->pw_name,
+ false,
+ session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "session_info from username failed\n");
+ return false;
+ }
+
+ return true;
+ }
+
+ status = init_system_session_info(state.mem_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "init_system_session_info failed\n");
+ return false;
+ }
+
+ status = make_session_info_system(state.mem_ctx, session_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "make_session_info_system failed\n");
+ return false;
+ }
+
+ return true;
+}
+
+static int net_vfs_init(struct net_context *c, int argc, const char **argv)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *service = NULL;
+ char *share_root = NULL;
+ int snum;
+ NTSTATUS status;
+ bool ok;
+ int rc = 1;
+
+ state = (struct net_vfs_state) {
+ .c = c,
+ .mem_ctx = c,
+ };
+
+ if (argc < 1) {
+ net_vfs_usage();
+ goto done;
+ }
+
+ if (geteuid() != 0 && !uid_wrapper_enabled()) {
+ fprintf(stderr, "'net vfs' must be run as root.\n");
+ goto done;
+ }
+
+ smb_init_locale();
+ umask(0);
+ sec_init();
+ setup_logging("net", DEBUG_STDOUT);
+ lpcfg_set_cmdline(c->lp_ctx, "log level", "0");
+
+ ok = lp_load_with_registry_shares(get_dyn_CONFIGFILE());
+ if (!ok) {
+ fprintf(stderr, "lp_load_with_registry_shares failed\n");
+ goto done;
+ }
+
+ ok = locking_init();
+ if (!ok) {
+ fprintf(stderr, "locking init failed\n");
+ goto done;
+ }
+
+ ok = net_vfs_make_session_info(&state.session_info);
+ if (!ok) {
+ goto done;
+ }
+
+ service = argv[0];
+ snum = lp_servicenumber(service);
+ if (snum == -1) {
+ fprintf(stderr, "unknown service: %s\n", service);
+ goto done;
+ }
+
+ share_root = lp_path(state.mem_ctx, lp_sub, snum);
+ if (share_root == NULL) {
+ fprintf(stderr, "Failed to find share root for service: %s\n",
+ service);
+ goto done;
+ }
+
+ status = create_conn_struct_tos_cwd(global_messaging_context(),
+ snum,
+ share_root,
+ state.session_info,
+ &state.conn_tos);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ state.conn_tos->conn->share_access = FILE_GENERIC_ALL;
+ state.conn_tos->conn->read_only = false;
+ file_init(state.conn_tos->conn->sconn);
+
+ ok = become_user_without_service_by_session(state.conn_tos->conn,
+ state.session_info);
+ if (!ok) {
+ fprintf(stderr,
+ "become_user_without_service_by_session failed\n");
+ goto done;
+ }
+
+ rc = 0;
+done:
+ return rc;
+}
+
+static int net_vfs_get_ntacl(struct net_context *net,
+ int argc,
+ const char **argv)
+{
+ const char *path = NULL;
+ struct smb_filename *smb_fname = NULL;
+ files_struct *fsp = NULL;
+ struct security_descriptor *sd = NULL;
+ NTSTATUS status;
+ int ret;
+ int rc = 1;
+
+ if (argc < 2 || net->display_usage) {
+ net_vfs_getntacl_usage();
+ goto done;
+ }
+
+ ret = net_vfs_init(net, argc, argv);
+ if (ret != 0) {
+ goto done;
+ }
+
+ path = argv[1];
+ smb_fname = synthetic_smb_fname(state.mem_ctx,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ goto done;
+ }
+
+ ret = SMB_VFS_STAT(state.conn_tos->conn, smb_fname);
+ if (ret != 0) {
+ fprintf(stderr, "stat [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname), strerror(errno));
+ goto done;
+ }
+
+ status = openat_pathref_fsp(state.conn_tos->conn->cwd_fsp, smb_fname);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("openat_pathref_fsp [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ goto done;
+ }
+
+ status = SMB_VFS_CREATE_FILE(
+ state.conn_tos->conn,
+ NULL, /* req */
+ NULL,
+ smb_fname,
+ FILE_READ_ATTRIBUTES|READ_CONTROL_ACCESS,
+ FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN,
+ 0, /* create_options */
+ 0, /* file_attributes */
+ INTERNAL_OPEN_ONLY, /* oplock_request */
+ NULL, /* lease */
+ 0, /* allocation_size */
+ 0, /* private_flags */
+ NULL, /* sd */
+ NULL, /* ea_list */
+ &fsp,
+ NULL, /* info */
+ NULL, NULL); /* create context */
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("SMB_VFS_CREATE_FILE [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ goto done;
+ }
+
+ status = SMB_VFS_FGET_NT_ACL(fsp,
+ SECINFO_OWNER|SECINFO_GROUP|SECINFO_DACL,
+ talloc_tos(),
+ &sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("SMB_VFS_FGET_NT_ACL [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname), nt_errstr(status));
+ goto done;
+ }
+
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("close_file [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ goto done;
+ }
+
+ sec_desc_print(NULL, stdout, sd, true);
+
+ rc = 0;
+done:
+ TALLOC_FREE(sd);
+
+ if (fsp != NULL) {
+ status = close_file_free(NULL, &fsp, NORMAL_CLOSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("close_file_free() [%s] failed: %s\n",
+ smb_fname_str_dbg(smb_fname),
+ nt_errstr(status));
+ rc = 1;
+ }
+ }
+ return rc;
+}
+
+static bool do_unfruit(const char *path)
+{
+ struct smb_filename *smb_fname = NULL;
+ char *p = NULL;
+ bool converted;
+ int ret;
+ bool ok;
+
+ p = strrchr_m(path, '/');
+ if (p != NULL) {
+ if (p[1] == '.' && p[2] == '_') {
+ return true;
+ }
+ }
+
+ smb_fname = synthetic_smb_fname(state.mem_ctx,
+ path,
+ NULL,
+ NULL,
+ 0,
+ 0);
+ if (smb_fname == NULL) {
+ return false;
+ }
+
+ ret = SMB_VFS_STAT(state.conn_tos->conn, smb_fname);
+ if (ret != 0) {
+ fprintf(stderr, "%s: %s\n", path, strerror(errno));
+ if (state.c->opt_continue_on_error) {
+ return true;
+ }
+ return false;
+ }
+
+ ok = ad_unconvert(state.mem_ctx,
+ state.conn_tos->conn->vfs_handles,
+ macos_string_replace_map,
+ smb_fname,
+ &converted);
+ if (!ok) {
+ fprintf(stderr, "Converting failed: %s\n", path);
+ if (state.c->opt_continue_on_error) {
+ return true;
+ }
+ return false;
+ }
+
+ if (converted) {
+ fprintf(stdout, "Converted: %s\n", path);
+ } else if (state.c->opt_verbose) {
+ fprintf(stdout, "%s\n", path);
+ }
+ return true;
+}
+
+static int nftw_cb(const char *path,
+ const struct stat *sb,
+ int typeflag,
+ struct FTW *ftwbuf)
+{
+ bool ok;
+
+ if (typeflag == FTW_SL) {
+ if (state.c->opt_verbose) {
+ fprintf(stdout, "Ignoring symlink: %s\n", path);
+ }
+ return 0;
+ }
+
+ ok = do_unfruit(path);
+ if (!ok) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int net_vfs_stream_to_appledouble(struct net_context *net,
+ int argc,
+ const char **argv)
+{
+ int i;
+ int ret;
+ bool ok;
+ int rc = 1;
+
+ if (argc < 2 || net->display_usage) {
+ net_vfs_stream_to_appledouble_usage();
+ goto done;
+ }
+
+ ret = net_vfs_init(net, argc, argv);
+ if (ret != 0) {
+ goto done;
+ }
+
+ for (i = 1; i < argc; i++) {
+ const char *path = argv[i];
+
+ if (path[0] == '/') {
+ fprintf(stderr, "ignoring absolute path: %s\n", path);
+ if (state.c->opt_continue_on_error) {
+ continue;
+ }
+ goto done;
+ }
+
+ if (!state.c->opt_recursive) {
+ ok = do_unfruit(path);
+ if (!ok) {
+ if (!state.c->opt_continue_on_error) {
+ goto done;
+ }
+ }
+ continue;
+ }
+
+ ret = nftw(path,
+ nftw_cb,
+ 256,
+ state.c->opt_follow_symlink ? 0 : FTW_PHYS);
+ if (ret != 0) {
+ fprintf(stderr, "%s: %s\n", path, strerror(errno));
+ if (!state.c->opt_continue_on_error) {
+ goto done;
+ }
+ }
+ }
+
+ rc = 0;
+
+done:
+ return rc;
+}
+
+static struct functable func[] = {
+ {
+ "getntacl",
+ net_vfs_get_ntacl,
+ NET_TRANSPORT_LOCAL,
+ N_("Display security descriptor of a file or directory"),
+ N_("net vfs getntacl <share> <path> [<path> ...]")
+ },
+ {
+ NET_VFS_CMD_STREAM_TO_ADOUBLE,
+ net_vfs_stream_to_appledouble,
+ NET_TRANSPORT_LOCAL,
+ N_("Convert streams to AppleDouble files"),
+ N_("net vfs " NET_VFS_CMD_STREAM_TO_ADOUBLE " [OPTIONS] <share> <path> [<path> ...]")
+ },
+ {NULL, NULL, 0, NULL, NULL}
+};
+
+int net_vfs(struct net_context *c, int argc, const char **argv)
+{
+ return net_run_function(c, argc, argv, "net vfs", func);
+}
diff --git a/source3/utils/net_witness.c b/source3/utils/net_witness.c
new file mode 100644
index 0000000..accff5b
--- /dev/null
+++ b/source3/utils/net_witness.c
@@ -0,0 +1,2361 @@
+/*
+ * Samba Unix/Linux client library
+ * net witness commands to manage smb witness registrations
+ * Copyright (C) 2023 Stefan Metzmacher
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "utils/net.h"
+#include "messages.h"
+#include "serverid.h"
+#include "lib/util/util_tdb.h"
+#include "source3/include/util_tdb.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "lib/dbwrap/dbwrap_open.h"
+#include "lib/param/param.h"
+#include "librpc/gen_ndr/ndr_rpcd_witness.h"
+#include <regex.h>
+
+struct json_object;
+
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+#include "audit_logging.h" /* various JSON helpers */
+#endif /* HAVE_JANSSON */
+
+#undef strcasecmp
+
+static struct db_context *net_witness_open_registration_db(void)
+{
+ static struct db_context *db;
+ char *global_path = NULL;
+
+ if (db != NULL) {
+ return db;
+ }
+
+ global_path = lock_path(talloc_tos(), "rpcd_witness_registration.tdb");
+ if (global_path == NULL) {
+ return NULL;
+ }
+
+ db = db_open(NULL,
+ global_path,
+ 0, /* hash_size */
+ TDB_DEFAULT |
+ TDB_CLEAR_IF_FIRST |
+ TDB_INCOMPATIBLE_HASH,
+ O_RDONLY,
+ 0600,
+ DBWRAP_LOCK_ORDER_1,
+ DBWRAP_FLAG_NONE);
+ TALLOC_FREE(global_path);
+ if (db == NULL) {
+ return NULL;
+ }
+
+ return db;
+}
+
+struct net_witness_scan_registrations_action_state {
+ bool (*prepare_fn)(void *private_data);
+ bool (*match_fn)(void *private_data, const struct rpcd_witness_registration *rg);
+ NTSTATUS (*process_fn)(void *private_data, const struct rpcd_witness_registration *rg);
+ void *private_data;
+};
+
+struct net_witness_scan_registrations_regex {
+ regex_t regex;
+ bool valid;
+};
+
+struct net_witness_scan_registrations_state {
+ struct net_context *c;
+ struct net_witness_scan_registrations_regex net_name;
+ struct net_witness_scan_registrations_regex share_name;
+ struct net_witness_scan_registrations_regex ip_address;
+ struct net_witness_scan_registrations_regex client_computer;
+ struct json_object *message_json;
+#ifdef HAVE_JANSSON
+ struct json_object filters_json;
+ struct json_object registrations_json;
+#endif
+ const struct net_witness_scan_registrations_action_state *action;
+ NTSTATUS error;
+};
+
+static bool net_witness_scan_registrations_regex_init(
+ struct net_witness_scan_registrations_state *state,
+ struct net_witness_scan_registrations_regex *r,
+ const char *option, const char *value);
+static bool net_witness_scan_registrations_regex_match(
+ struct net_witness_scan_registrations_regex *r,
+ const char *name, const char *value);
+static void net_witness_scan_registrations_regex_free(
+ struct net_witness_scan_registrations_regex *r);
+
+static bool net_witness_scan_registrations_match(
+ struct net_witness_scan_registrations_state *state,
+ const struct rpcd_witness_registration *rg)
+{
+ if (state->net_name.valid) {
+ bool match;
+
+ match = net_witness_scan_registrations_regex_match(
+ &state->net_name,
+ "net_name",
+ rg->net_name);
+ if (!match) {
+ return false;
+ }
+ }
+
+ if (state->share_name.valid) {
+ bool match;
+
+ match = net_witness_scan_registrations_regex_match(
+ &state->share_name,
+ "share_name",
+ rg->share_name);
+ if (!match) {
+ return false;
+ }
+ }
+
+ if (state->ip_address.valid) {
+ bool match;
+
+ match = net_witness_scan_registrations_regex_match(
+ &state->ip_address,
+ "ip_address",
+ rg->ip_address);
+ if (!match) {
+ return false;
+ }
+ }
+
+ if (state->client_computer.valid) {
+ bool match;
+
+ match = net_witness_scan_registrations_regex_match(
+ &state->client_computer,
+ "client_computer_name",
+ rg->client_computer_name);
+ if (!match) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool net_witness_scan_registrations_regex_init(
+ struct net_witness_scan_registrations_state *state,
+ struct net_witness_scan_registrations_regex *r,
+ const char *option, const char *value)
+{
+#ifdef HAVE_JANSSON
+ struct net_context *c = state->c;
+#endif /* HAVE_JANSSON */
+ int ret;
+
+ r->valid = false;
+
+ if (value == NULL) {
+ return true;
+ }
+
+ ret = regcomp(&r->regex, value, REG_EXTENDED|REG_ICASE|REG_NOSUB);
+ if (ret != 0) {
+ fstring buf = { 0,};
+ regerror(ret, &r->regex, buf, sizeof(buf));
+ d_printf("regcomp(%s) failed for %s: "
+ "%d: %s\n", value, option, ret, buf);
+ return false;
+ }
+
+#ifdef HAVE_JANSSON
+ if (c->opt_json) {
+ ret = json_add_string(&state->filters_json,
+ option,
+ value);
+ if (ret != 0) {
+ return false;
+ }
+ }
+#endif /* HAVE_JANSSON */
+
+ r->valid = true;
+ return true;
+}
+
+static bool net_witness_scan_registrations_regex_match(
+ struct net_witness_scan_registrations_regex *r,
+ const char *name, const char *value)
+{
+ int ret;
+
+ if (!r->valid) {
+ return false;
+ }
+
+ if (value == NULL) {
+ /*
+ * without a share name,
+ * we match against an empty
+ * string.
+ */
+ value = "";
+ }
+
+ ret = regexec(&r->regex, value, 0, NULL, 0);
+ if (ret == REG_NOMATCH) {
+ return false;
+ }
+
+ return true;
+}
+
+static void net_witness_scan_registrations_regex_free(
+ struct net_witness_scan_registrations_regex *r)
+{
+ if (r->valid) {
+ regfree(&r->regex);
+ r->valid = false;
+ }
+}
+
+static bool net_witness_scan_registrations_init(
+ struct net_witness_scan_registrations_state *state)
+{
+ struct net_context *c = state->c;
+ bool ok;
+
+ if (c->opt_json) {
+#ifdef HAVE_JANSSON
+ state->filters_json = json_new_object();
+ if (json_is_invalid(&state->filters_json)) {
+ return false;
+ }
+
+ if (c->opt_witness_registration != NULL) {
+ int ret;
+
+ ret = json_add_string(&state->filters_json,
+ "--witness-registration",
+ c->opt_witness_registration);
+ if (ret != 0) {
+ return false;
+ }
+ }
+
+ if (c->opt_witness_apply_to_all != 0) {
+ int ret;
+
+ ret = json_add_bool(&state->filters_json,
+ "--witness-apply-to-all",
+ c->opt_witness_apply_to_all != 0);
+ if (ret != 0) {
+ return false;
+ }
+ }
+
+ state->registrations_json = json_new_object();
+ if (json_is_invalid(&state->registrations_json)) {
+ return false;
+ }
+#else /* not HAVE_JANSSON */
+ d_fprintf(stderr, _("JSON support not available\n"));
+ return false;
+#endif /* not HAVE_JANSSON */
+ }
+
+ ok = net_witness_scan_registrations_regex_init(state,
+ &state->net_name,
+ "--witness-net-name",
+ c->opt_witness_net_name);
+ if (!ok) {
+ return false;
+ }
+
+ ok = net_witness_scan_registrations_regex_init(state,
+ &state->share_name,
+ "--witness-share-name",
+ c->opt_witness_share_name);
+ if (!ok) {
+ return false;
+ }
+
+ ok = net_witness_scan_registrations_regex_init(state,
+ &state->ip_address,
+ "--witness-ip-address",
+ c->opt_witness_ip_address);
+ if (!ok) {
+ return false;
+ }
+
+ ok = net_witness_scan_registrations_regex_init(state,
+ &state->client_computer,
+ "--witness-client-computer-name",
+ c->opt_witness_client_computer_name);
+ if (!ok) {
+ return false;
+ }
+
+ ok = state->action->prepare_fn(state->action->private_data);
+ if (!ok) {
+ return false;
+ }
+
+ if (!c->opt_json) {
+ d_printf("%-36s %-20s %-15s %-20s %s\n",
+ "Registration-UUID:",
+ "NetName",
+ "ShareName",
+ "IpAddress",
+ "ClientComputerName");
+ d_printf("%-36s-%-20s-%-15s-%-20s-%s\n",
+ "------------------------------------",
+ "--------------------",
+ "------------------",
+ "--------------------",
+ "------------------");
+ }
+
+ return true;
+}
+
+static bool net_witness_scan_registrations_finish(
+ struct net_witness_scan_registrations_state *state)
+{
+#ifdef HAVE_JANSSON
+ struct net_context *c = state->c;
+ struct json_object root_json = json_empty_object;
+ TALLOC_CTX *frame = NULL;
+ const char *json_str = NULL;
+ int ret;
+
+ if (!c->opt_json) {
+ return true;
+ }
+
+ frame = talloc_stackframe();
+
+ root_json = json_new_object();
+ if (json_is_invalid(&root_json)) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ ret = json_add_object(&root_json,
+ "filters",
+ &state->filters_json);
+ if (ret != 0) {
+ json_free(&root_json);
+ TALLOC_FREE(frame);
+ return false;
+ }
+ state->filters_json = json_empty_object;
+
+ if (state->message_json != NULL) {
+ ret = json_add_object(&root_json,
+ "message",
+ state->message_json);
+ if (ret != 0) {
+ json_free(&root_json);
+ TALLOC_FREE(frame);
+ return false;
+ }
+ *state->message_json = json_empty_object;
+ }
+
+ ret = json_add_object(&root_json,
+ "registrations",
+ &state->registrations_json);
+ if (ret != 0) {
+ json_free(&root_json);
+ TALLOC_FREE(frame);
+ return false;
+ }
+ state->registrations_json = json_empty_object;
+
+ json_str = json_to_string(frame, &root_json);
+ json_free(&root_json);
+ if (json_str == NULL) {
+ TALLOC_FREE(frame);
+ return false;
+ }
+
+ d_printf("%s\n", json_str);
+ TALLOC_FREE(frame);
+ return true;
+#else /* not HAVE_JANSSON */
+ return true;
+#endif /* not HAVE_JANSSON */
+}
+
+static void net_witness_scan_registrations_free(
+ struct net_witness_scan_registrations_state *state)
+{
+#ifdef HAVE_JANSSON
+ if (!json_is_invalid(&state->filters_json)) {
+ json_free(&state->filters_json);
+ }
+ if (!json_is_invalid(&state->registrations_json)) {
+ json_free(&state->registrations_json);
+ }
+#endif /* HAVE_JANSSON */
+
+ net_witness_scan_registrations_regex_free(&state->net_name);
+ net_witness_scan_registrations_regex_free(&state->share_name);
+ net_witness_scan_registrations_regex_free(&state->ip_address);
+ net_witness_scan_registrations_regex_free(&state->client_computer);
+}
+
+#ifdef HAVE_JANSSON
+static int dump_registration_json(struct json_object *registrations_json,
+ const char *key_str,
+ const struct rpcd_witness_registration *rg)
+{
+ struct json_object jsobj = json_empty_object;
+ struct json_object flags_json = json_empty_object;
+ struct json_object context_json = json_empty_object;
+ struct json_object serverid_json = json_empty_object;
+ struct json_object auth_json = json_empty_object;
+ struct json_object connection_json = json_empty_object;
+ struct timeval tv;
+ struct dom_sid_buf sid_buf;
+ int ret = 0;
+
+ jsobj = json_new_object();
+ if (json_is_invalid(&jsobj)) {
+ d_fprintf(stderr, _("error setting up JSON value\n"));
+ goto failure;
+ }
+
+ ret = json_add_flags32(&jsobj, "version", rg->version);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "net_name", rg->net_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "share_name", rg->share_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "ip_address", rg->ip_address);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&jsobj, "client_computer_name", rg->client_computer_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ flags_json = json_new_object();
+ if (json_is_invalid(&flags_json)) {
+ goto failure;
+ }
+
+ ret = json_add_bool(&flags_json, "WITNESS_REGISTER_IP_NOTIFICATION",
+ (rg->flags & WITNESS_REGISTER_IP_NOTIFICATION) ?
+ true : false);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int(&flags_json, "int", rg->flags);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_flags32(&flags_json, "hex", rg->flags);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_object(&jsobj, "flags", &flags_json);
+ if (ret != 0) {
+ goto failure;
+ }
+ flags_json = json_empty_object;
+
+ ret = json_add_int(&jsobj, "timeout", rg->timeout);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ context_json = json_new_object();
+ if (json_is_invalid(&context_json)) {
+ goto failure;
+ }
+
+ ret = json_add_int(&context_json, "handle_type", rg->context_handle.handle_type);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_guid(&context_json, "uuid", &rg->context_handle.uuid);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_object(&jsobj, "context_handle", &context_json);
+ if (ret != 0) {
+ goto failure;
+ }
+ context_json = json_empty_object;
+
+ serverid_json = json_new_object();
+ if (json_is_invalid(&serverid_json)) {
+ goto failure;
+ }
+
+ ret = json_add_int(&serverid_json, "pid", rg->server_id.pid);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int(&serverid_json, "task_id", rg->server_id.task_id);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int(&serverid_json, "vnn", rg->server_id.vnn);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_int(&serverid_json, "unique_id", rg->server_id.unique_id);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_object(&jsobj, "server_id", &serverid_json);
+ if (ret != 0) {
+ goto failure;
+ }
+ serverid_json = json_empty_object;
+
+ auth_json = json_new_object();
+ if (json_is_invalid(&auth_json)) {
+ goto failure;
+ }
+
+ ret = json_add_string(&auth_json, "account_name", rg->account_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&auth_json, "domain_name", rg->domain_name);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&auth_json,
+ "account_sid",
+ dom_sid_str_buf(&rg->account_sid, &sid_buf));
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_object(&jsobj, "auth", &auth_json);
+ if (ret != 0) {
+ goto failure;
+ }
+ auth_json = json_empty_object;
+
+ connection_json = json_new_object();
+ if (json_is_invalid(&connection_json)) {
+ goto failure;
+ }
+
+ ret = json_add_string(&connection_json, "local_address", rg->local_address);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_string(&connection_json, "remote_address", rg->remote_address);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_object(&jsobj, "connection", &connection_json);
+ if (ret != 0) {
+ goto failure;
+ }
+ connection_json = json_empty_object;
+
+ nttime_to_timeval(&tv, rg->registration_time);
+ ret = json_add_time(&jsobj, "registration_time", tv);
+ if (ret != 0) {
+ goto failure;
+ }
+
+ ret = json_add_object(registrations_json, key_str, &jsobj);
+ if (ret != 0) {
+ goto failure;
+ }
+ jsobj = json_empty_object;
+
+failure:
+ if (!json_is_invalid(&connection_json)) {
+ json_free(&connection_json);
+ }
+ if (!json_is_invalid(&auth_json)) {
+ json_free(&auth_json);
+ }
+ if (!json_is_invalid(&serverid_json)) {
+ json_free(&serverid_json);
+ }
+ if (!json_is_invalid(&context_json)) {
+ json_free(&context_json);
+ }
+ if (!json_is_invalid(&flags_json)) {
+ json_free(&flags_json);
+ }
+ if (!json_is_invalid(&jsobj)) {
+ json_free(&jsobj);
+ }
+
+ return ret;
+}
+#endif /* HAVE_JANSSON */
+
+static NTSTATUS net_witness_scan_registrations_dump_rg(
+ struct net_witness_scan_registrations_state *state,
+ const struct rpcd_witness_registration *rg)
+{
+ struct net_context *c = state->c;
+ struct GUID_txt_buf key_buf;
+ const char *key_str = GUID_buf_string(&rg->context_handle.uuid, &key_buf);
+
+ if (c->opt_json) {
+#ifdef HAVE_JANSSON
+ int ret;
+
+ ret = dump_registration_json(&state->registrations_json,
+ key_str,
+ rg);
+ if (ret != 0) {
+ d_fprintf(stderr, "dump_registration_json(%s) failed\n",
+ key_str);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+#endif /* HAVE_JANSSON */
+ return NT_STATUS_OK;
+ }
+
+ d_printf("%-36s %-20s %-15s %-20s %s\n",
+ key_str,
+ rg->net_name,
+ rg->share_name ? rg->share_name : "''",
+ rg->ip_address,
+ rg->client_computer_name);
+
+ return NT_STATUS_OK;
+}
+
+static void net_witness_scan_registrations_parser(TDB_DATA key,
+ TDB_DATA val,
+ void *private_data)
+{
+ struct net_witness_scan_registrations_state *state =
+ (struct net_witness_scan_registrations_state *)private_data;
+ DATA_BLOB val_blob = data_blob_const(val.dptr, val.dsize);
+ struct rpcd_witness_registration rg;
+ enum ndr_err_code ndr_err;
+ TALLOC_CTX *frame = NULL;
+ bool match = false;
+
+ if (val_blob.length == 0) {
+ return;
+ }
+
+ frame = talloc_stackframe();
+
+ ndr_err = ndr_pull_struct_blob(&val_blob, frame, &rg,
+ (ndr_pull_flags_fn_t)ndr_pull_rpcd_witness_registration);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_WARNING("Invalid record in rpcd_witness_registration.tdb:"
+ "key '%s' ndr_pull_struct_blob - %s\n",
+ tdb_data_dbg(key),
+ ndr_errstr(ndr_err));
+ state->error = ndr_map_error2ntstatus(ndr_err);
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ if (!serverid_exists(&rg.server_id)) {
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(rpcd_witness_registration, &rg);
+ }
+
+ match = net_witness_scan_registrations_match(state, &rg);
+ if (!NT_STATUS_IS_OK(state->error)) {
+ TALLOC_FREE(frame);
+ return;
+ }
+ if (!match) {
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ match = state->action->match_fn(state->action->private_data, &rg);
+ if (!match) {
+ TALLOC_FREE(frame);
+ return;
+ }
+
+ state->error = state->action->process_fn(state->action->private_data, &rg);
+ if (NT_STATUS_IS_OK(state->error)) {
+ state->error = net_witness_scan_registrations_dump_rg(state,
+ &rg);
+ }
+ TALLOC_FREE(frame);
+}
+
+static int net_witness_scan_registrations_traverse_cb(struct db_record *rec, void *private_data)
+{
+ struct net_witness_scan_registrations_state *state =
+ (struct net_witness_scan_registrations_state *)private_data;
+ TDB_DATA key = dbwrap_record_get_key(rec);
+ TDB_DATA val = dbwrap_record_get_value(rec);
+
+ net_witness_scan_registrations_parser(key, val, private_data);
+
+ if (!NT_STATUS_IS_OK(state->error)) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int net_witness_scan_registrations(struct net_context *c,
+ struct json_object *message_json,
+ const struct net_witness_scan_registrations_action_state *action)
+{
+ struct net_witness_scan_registrations_state state = {
+ .c = c,
+ .message_json = message_json,
+ .action = action,
+ };
+ struct db_context *db = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ db = net_witness_open_registration_db();
+ if (db == NULL) {
+ d_printf("net_witness_open_registration_db() failed\n");
+ return -1;
+ }
+
+ ok = net_witness_scan_registrations_init(&state);
+ if (!ok) {
+ d_printf("net_witness_scan_registrations_init() failed\n");
+ return -1;
+ }
+
+ if (c->opt_witness_registration != NULL) {
+ const char *key_str = c->opt_witness_registration;
+ DATA_BLOB key_blob = data_blob_string_const(key_str);
+ TDB_DATA key = make_tdb_data(key_blob.data, key_blob.length);
+
+ status = dbwrap_parse_record(db,
+ key,
+ net_witness_scan_registrations_parser,
+ &state);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("dbwrap_parse_record(%s) failed: %s\n",
+ key_str, nt_errstr(status));
+ net_witness_scan_registrations_free(&state);
+ return -1;
+ }
+ if (!NT_STATUS_IS_OK(state.error)) {
+ d_printf("net_witness_scan_registrations_parser(%s) failed: %s\n",
+ key_str, nt_errstr(state.error));
+ net_witness_scan_registrations_free(&state);
+ return -1;
+ }
+ } else {
+ status = dbwrap_traverse_read(db,
+ net_witness_scan_registrations_traverse_cb,
+ &state,
+ NULL); /* count */
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("dbwrap_traverse_read() failed\n");
+ net_witness_scan_registrations_free(&state);
+ return -1;
+ }
+ if (!NT_STATUS_IS_OK(state.error)) {
+ d_printf("net_witness_scan_registrations_traverse_cb() failed: %s\n",
+ nt_errstr(state.error));
+ net_witness_scan_registrations_free(&state);
+ return -1;
+ }
+ }
+
+ ok = net_witness_scan_registrations_finish(&state);
+ if (!ok) {
+ d_printf("net_witness_scan_registrations_finish() failed\n");
+ return -1;
+ }
+
+ net_witness_scan_registrations_free(&state);
+ return 0;
+}
+
+struct net_witness_list_state {
+ struct net_context *c;
+};
+
+static bool net_witness_list_prepare_fn(void *private_data)
+{
+ return true;
+}
+
+static bool net_witness_list_match_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ return true;
+}
+
+static NTSTATUS net_witness_list_process_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ return NT_STATUS_OK;
+}
+
+static void net_witness_filter_usage(void)
+{
+ d_printf(" Note: Only supported with clustering=yes!\n\n");
+ d_printf(" Machine readable output can be generated with "
+ "the following option:\n"
+ "\n"
+ " --json\n"
+ "\n");
+ d_printf(" The selection of registrations can be limited by "
+ "the following options:\n"
+ "\n"
+ " --witness-registration=REGISTRATION_UUID\n"
+ " This does a direct lookup for REGISTRATION_UUID\n"
+ " instead of doing a database traversal.\n"
+ "\n"
+ " The following options all take a "
+ "POSIX Extended Regular Expression,\n"
+ " which can further filter the selection of "
+ "registrations.\n"
+ " These options are applied as logical AND, "
+ "but each REGEX \n"
+ " allows specifying multiple strings using "
+ "the pipe symbol.\n"
+ "\n"
+ " --witness-net-name=REGEX\n"
+ " This specifies the 'server name' the client\n"
+ " registered for monitoring.\n"
+ "\n"
+ " --witness-share-name=REGEX\n"
+ " This specifies the 'share name' the client\n"
+ " registered for monitoring.\n"
+ " Note that the share name is optional in the\n"
+ " registration, otherwise an empty string is \n"
+ " matched.\n"
+ "\n"
+ " --witness-ip-address=REGEX\n"
+ " This specifies the ip address the client\n"
+ " registered for monitoring.\n"
+ "\n"
+ " --witness-client-computer-name=REGEX\n"
+ " This specifies the client computer name the client\n"
+ " specified in the registration.\n"
+ " Note it is just a string chosen by the "
+ "client itself.\n"
+ "\n");
+}
+
+static void net_witness_list_usage(void)
+{
+ d_printf("%s\n"
+ "net witness list\n"
+ " %s\n\n",
+ _("Usage:"),
+ _("List witness registrations "
+ "from rpcd_witness_registration.tdb"));
+ net_witness_filter_usage();
+}
+
+static int net_witness_list(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct net_witness_list_state state = { .c = c, };
+ struct net_witness_scan_registrations_action_state action = {
+ .prepare_fn = net_witness_list_prepare_fn,
+ .match_fn = net_witness_list_match_fn,
+ .process_fn = net_witness_list_process_fn,
+ .private_data = &state,
+ };
+ int ret = -1;
+
+ if (c->display_usage) {
+ net_witness_list_usage();
+ goto out;
+ }
+
+ if (argc != 0) {
+ net_witness_list_usage();
+ goto out;
+ }
+
+ if (!lp_clustering()) {
+ d_printf("ERROR: Only supported with clustering=yes!\n\n");
+ goto out;
+ }
+
+ ret = net_witness_scan_registrations(c, NULL, &action);
+ if (ret != 0) {
+ d_printf("net_witness_scan_registrations() failed\n");
+ goto out;
+ }
+
+ ret = 0;
+out:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+struct net_witness_client_move_state {
+ struct net_context *c;
+ struct rpcd_witness_registration_updateB m;
+ char *headline;
+};
+
+static bool net_witness_client_move_prepare_fn(void *private_data)
+{
+ struct net_witness_client_move_state *state =
+ (struct net_witness_client_move_state *)private_data;
+
+ if (state->headline != NULL) {
+ d_printf("%s\n", state->headline);
+ TALLOC_FREE(state->headline);
+ }
+
+ return true;
+}
+
+static bool net_witness_client_move_match_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ return true;
+}
+
+static NTSTATUS net_witness_client_move_process_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ struct net_witness_client_move_state *state =
+ (struct net_witness_client_move_state *)private_data;
+ struct net_context *c = state->c;
+ struct rpcd_witness_registration_updateB update = {
+ .context_handle = rg->context_handle,
+ .type = state->m.type,
+ .update = state->m.update,
+ };
+ DATA_BLOB blob = { .length = 0, };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ if (state->headline != NULL) {
+ d_printf("%s\n", state->headline);
+ TALLOC_FREE(state->headline);
+ }
+
+ SMB_ASSERT(update.type != 0);
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update,
+ (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ status = messaging_send(c->msg_ctx,
+ rg->server_id,
+ MSG_RPCD_WITNESS_REGISTRATION_UPDATE,
+ &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send() - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void net_witness_update_usage(void)
+{
+ d_printf(" If the update should be applied to all registrations\n"
+ " it needs to be explicitly specified:\n"
+ "\n"
+ " --witness-apply-to-all\n"
+ " This selects all registrations.\n"
+ " Note: This is mutual exclusive to "
+ "the above options.\n"
+ "\n");
+}
+
+static bool net_witness_verify_update_options(struct net_context *c)
+{
+ if (c->opt_witness_registration == NULL &&
+ c->opt_witness_net_name == NULL &&
+ c->opt_witness_share_name == NULL &&
+ c->opt_witness_ip_address == NULL &&
+ c->opt_witness_client_computer_name == NULL &&
+ c->opt_witness_apply_to_all == 0)
+ {
+ d_printf("--witness-apply-to-all or "
+ "at least one of following requires:\n"
+ "--witness-registration\n"
+ "--witness-net-name\n"
+ "--witness-share-name\n"
+ "--witness-ip-address\n"
+ "--witness-client-computer-name\n");
+ return false;
+ }
+
+ if (c->opt_witness_apply_to_all == 0) {
+ return true;
+ }
+
+ if (c->opt_witness_registration != NULL ||
+ c->opt_witness_net_name != NULL ||
+ c->opt_witness_share_name != NULL ||
+ c->opt_witness_ip_address != NULL ||
+ c->opt_witness_client_computer_name != NULL)
+ {
+ d_printf("--witness-apply-to-all not allowed "
+ "together with the following options:\n"
+ "--witness-registration\n"
+ "--witness-net-name\n"
+ "--witness-share-name\n"
+ "--witness-ip-address\n"
+ "--witness-client-computer-name\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void net_witness_move_usage(const char *name)
+{
+ d_printf(" The content of the %s notification contains ip addresses\n"
+ " specified by (exactly one) of the following options:\n"
+ "\n"
+ " --witness-new-node=NODEID\n"
+ " By specifying a NODEID all ip addresses\n"
+ " currently available on the given node are\n"
+ " included in the response.\n"
+ " By specifying '-1' as NODEID all ip addresses\n"
+ " of the cluster are included in the response.\n"
+ "\n"
+ " --witness-new-ip=IPADDRESS\n"
+ " By specifying an IPADDRESS only the specified\n"
+ " ip address is included in the response.\n"
+ "\n",
+ name);
+}
+
+static bool net_witness_verify_move_options(struct net_context *c,
+ uint32_t *new_node,
+ bool *is_ipv4,
+ bool *is_ipv6)
+{
+ bool ok;
+
+ *new_node = NONCLUSTER_VNN;
+ *is_ipv4 = false;
+ *is_ipv6 = false;
+
+ ok = net_witness_verify_update_options(c);
+ if (!ok) {
+ return false;
+ }
+
+ if (c->opt_witness_new_ip != NULL &&
+ c->opt_witness_new_node != -2)
+ {
+ d_printf("--witness-new-ip and "
+ "--witness-new-node are not allowed together\n");
+ return false;
+ }
+
+ if (c->opt_witness_new_ip == NULL &&
+ c->opt_witness_new_node == -2)
+ {
+ d_printf("--witness-new-ip or --witness-new-node required\n");
+ return false;
+ }
+
+ if (c->opt_witness_new_node != -2) {
+ *new_node = c->opt_witness_new_node;
+ return true;
+ }
+
+ if (is_ipaddress_v4(c->opt_witness_new_ip)) {
+ *is_ipv4 = true;
+ return true;
+ }
+
+ if (is_ipaddress_v6(c->opt_witness_new_ip)) {
+ *is_ipv6 = true;
+ return true;
+ }
+
+ d_printf("Invalid ip address for --witness-new-ip=%s\n",
+ c->opt_witness_new_ip);
+ return false;
+}
+
+#ifdef HAVE_JANSSON
+static bool net_witness_move_message_json(struct net_context *c,
+ const char *msg_type,
+ struct json_object *pmessage_json)
+{
+ struct json_object message_json = json_empty_object;
+ int ret;
+
+ message_json = json_new_object();
+ if (json_is_invalid(&message_json)) {
+ return false;
+ }
+
+ ret = json_add_string(&message_json,
+ "type",
+ msg_type);
+ if (ret != 0) {
+ json_free(&message_json);
+ return false;
+ }
+
+ if (c->opt_witness_new_ip != NULL) {
+ ret = json_add_string(&message_json,
+ "new_ip",
+ c->opt_witness_new_ip);
+ if (ret != 0) {
+ return false;
+ }
+ } else if (c->opt_witness_new_node != -1) {
+ ret = json_add_int(&message_json,
+ "new_node",
+ c->opt_witness_new_node);
+ if (ret != 0) {
+ return false;
+ }
+ } else {
+ ret = json_add_bool(&message_json,
+ "all_nodes",
+ true);
+ if (ret != 0) {
+ return false;
+ }
+ }
+
+ *pmessage_json = message_json;
+ return true;
+}
+#endif /* HAVE_JANSSON */
+
+static void net_witness_client_move_usage(void)
+{
+ d_printf("%s\n"
+ "net witness client-move\n"
+ " %s\n\n",
+ _("Usage:"),
+ _("Generate client move notifications for "
+ "witness registrations to a new ip or node"));
+ net_witness_filter_usage();
+ net_witness_update_usage();
+ net_witness_move_usage("CLIENT_MOVE");
+}
+
+static int net_witness_client_move(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct net_witness_client_move_state state = { .c = c, };
+ struct rpcd_witness_registration_updateB *m = &state.m;
+#ifdef HAVE_JANSSON
+ struct json_object _message_json = json_empty_object;
+#endif /* HAVE_JANSSON */
+ struct json_object *message_json = NULL;
+ struct net_witness_scan_registrations_action_state action = {
+ .prepare_fn = net_witness_client_move_prepare_fn,
+ .match_fn = net_witness_client_move_match_fn,
+ .process_fn = net_witness_client_move_process_fn,
+ .private_data = &state,
+ };
+ int ret = -1;
+ const char *msg_type = NULL;
+ uint32_t new_node = NONCLUSTER_VNN;
+ bool is_ipv4 = false;
+ bool is_ipv6 = false;
+ bool ok;
+
+ if (c->display_usage) {
+ net_witness_client_move_usage();
+ goto out;
+ }
+
+ if (argc != 0) {
+ net_witness_client_move_usage();
+ goto out;
+ }
+
+ if (!lp_clustering()) {
+ d_printf("ERROR: Only supported with clustering=yes!\n\n");
+ goto out;
+ }
+
+ ok = net_witness_verify_move_options(c, &new_node, &is_ipv4, &is_ipv6);
+ if (!ok) {
+ goto out;
+ }
+
+ if (is_ipv4) {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV4;
+ m->update.client_move_to_ipv4.new_ipv4 = c->opt_witness_new_ip;
+ msg_type = "CLIENT_MOVE_TO_IPV4";
+ state.headline = talloc_asprintf(frame,
+ "CLIENT_MOVE_TO_IPV4: %s",
+ c->opt_witness_new_ip);
+ if (state.headline == NULL) {
+ goto out;
+ }
+ } else if (is_ipv6) {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_IPV6;
+ m->update.client_move_to_ipv6.new_ipv6 = c->opt_witness_new_ip;
+ msg_type = "CLIENT_MOVE_TO_IPV6";
+ state.headline = talloc_asprintf(frame,
+ "CLIENT_MOVE_TO_IPV6: %s",
+ c->opt_witness_new_ip);
+ if (state.headline == NULL) {
+ goto out;
+ }
+ } else if (new_node != NONCLUSTER_VNN) {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE;
+ m->update.client_move_to_node.new_node = new_node;
+ msg_type = "CLIENT_MOVE_TO_NODE";
+ state.headline = talloc_asprintf(frame,
+ "CLIENT_MOVE_TO_NODE: %u",
+ new_node);
+ if (state.headline == NULL) {
+ goto out;
+ }
+ } else {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_CLIENT_MOVE_TO_NODE;
+ m->update.client_move_to_node.new_node = NONCLUSTER_VNN;
+ msg_type = "CLIENT_MOVE_TO_NODE";
+ state.headline = talloc_asprintf(frame,
+ "CLIENT_MOVE_TO_NODE: ALL");
+ if (state.headline == NULL) {
+ goto out;
+ }
+ }
+
+#ifdef HAVE_JANSSON
+ if (c->opt_json) {
+ TALLOC_FREE(state.headline);
+
+ ok = net_witness_move_message_json(c,
+ msg_type,
+ &_message_json);
+ if (!ok) {
+ d_printf("net_witness_move_message_json(%s) failed\n",
+ msg_type);
+ goto out;
+ }
+
+ message_json = &_message_json;
+ }
+#else /* not HAVE_JANSSON */
+ (void)msg_type;
+#endif /* not HAVE_JANSSON */
+
+ ret = net_witness_scan_registrations(c, message_json, &action);
+ if (ret != 0) {
+ d_printf("net_witness_scan_registrations() failed\n");
+ goto out;
+ }
+
+ ret = 0;
+out:
+#ifdef HAVE_JANSSON
+ if (!json_is_invalid(&_message_json)) {
+ json_free(&_message_json);
+ }
+#endif /* HAVE_JANSSON */
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+struct net_witness_share_move_state {
+ struct net_context *c;
+ struct rpcd_witness_registration_updateB m;
+ char *headline;
+};
+
+static bool net_witness_share_move_prepare_fn(void *private_data)
+{
+ struct net_witness_share_move_state *state =
+ (struct net_witness_share_move_state *)private_data;
+
+ if (state->headline != NULL) {
+ d_printf("%s\n", state->headline);
+ TALLOC_FREE(state->headline);
+ }
+
+ return true;
+}
+
+static bool net_witness_share_move_match_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ if (rg->share_name == NULL) {
+ return false;
+ }
+
+ return true;
+}
+
+static NTSTATUS net_witness_share_move_process_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ struct net_witness_share_move_state *state =
+ (struct net_witness_share_move_state *)private_data;
+ struct net_context *c = state->c;
+ struct rpcd_witness_registration_updateB update = {
+ .context_handle = rg->context_handle,
+ .type = state->m.type,
+ .update = state->m.update,
+ };
+ DATA_BLOB blob = { .length = 0, };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ SMB_ASSERT(update.type != 0);
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update,
+ (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ status = messaging_send(c->msg_ctx,
+ rg->server_id,
+ MSG_RPCD_WITNESS_REGISTRATION_UPDATE,
+ &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send() - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void net_witness_share_move_usage(void)
+{
+ d_printf("%s\n"
+ "net witness share-move\n"
+ " %s\n\n",
+ _("Usage:"),
+ _("Generate share move notifications for "
+ "witness registrations to a new ip or node"));
+ net_witness_filter_usage();
+ net_witness_update_usage();
+ d_printf(" Note: This only applies to registrations with "
+ "a non empty share name!\n\n");
+ net_witness_move_usage("SHARE_MOVE");
+}
+
+static int net_witness_share_move(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct net_witness_share_move_state state = { .c = c, };
+ struct rpcd_witness_registration_updateB *m = &state.m;
+#ifdef HAVE_JANSSON
+ struct json_object _message_json = json_empty_object;
+#endif /* HAVE_JANSSON */
+ struct json_object *message_json = NULL;
+ struct net_witness_scan_registrations_action_state action = {
+ .prepare_fn = net_witness_share_move_prepare_fn,
+ .match_fn = net_witness_share_move_match_fn,
+ .process_fn = net_witness_share_move_process_fn,
+ .private_data = &state,
+ };
+ int ret = -1;
+ const char *msg_type = NULL;
+ uint32_t new_node = NONCLUSTER_VNN;
+ bool is_ipv4 = false;
+ bool is_ipv6 = false;
+ bool ok;
+
+ if (c->display_usage) {
+ net_witness_share_move_usage();
+ goto out;
+ }
+
+ if (argc != 0) {
+ net_witness_share_move_usage();
+ goto out;
+ }
+
+ if (!lp_clustering()) {
+ d_printf("ERROR: Only supported with clustering=yes!\n\n");
+ goto out;
+ }
+
+ ok = net_witness_verify_move_options(c, &new_node, &is_ipv4, &is_ipv6);
+ if (!ok) {
+ goto out;
+ }
+
+ if (is_ipv4) {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV4;
+ m->update.share_move_to_ipv4.new_ipv4 = c->opt_witness_new_ip;
+ msg_type = "SHARE_MOVE_TO_IPV4";
+ state.headline = talloc_asprintf(frame,
+ "SHARE_MOVE_TO_IPV4: %s",
+ c->opt_witness_new_ip);
+ if (state.headline == NULL) {
+ goto out;
+ }
+ } else if (is_ipv6) {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_IPV6;
+ m->update.share_move_to_ipv6.new_ipv6 = c->opt_witness_new_ip;
+ msg_type = "SHARE_MOVE_TO_IPV6";
+ state.headline = talloc_asprintf(frame,
+ "SHARE_MOVE_TO_IPV6: %s",
+ c->opt_witness_new_ip);
+ if (state.headline == NULL) {
+ goto out;
+ }
+ } else if (new_node != NONCLUSTER_VNN) {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE;
+ m->update.share_move_to_node.new_node = new_node;
+ msg_type = "SHARE_MOVE_TO_NODE";
+ state.headline = talloc_asprintf(frame,
+ "SHARE_MOVE_TO_NODE: %u",
+ new_node);
+ if (state.headline == NULL) {
+ goto out;
+ }
+ } else {
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_SHARE_MOVE_TO_NODE;
+ m->update.share_move_to_node.new_node = NONCLUSTER_VNN;
+ msg_type = "SHARE_MOVE_TO_NODE";
+ state.headline = talloc_asprintf(frame,
+ "SHARE_MOVE_TO_NODE: ALL");
+ if (state.headline == NULL) {
+ goto out;
+ }
+ }
+
+#ifdef HAVE_JANSSON
+ if (c->opt_json) {
+ TALLOC_FREE(state.headline);
+
+ ok = net_witness_move_message_json(c,
+ msg_type,
+ &_message_json);
+ if (!ok) {
+ d_printf("net_witness_move_message_json(%s) failed\n",
+ msg_type);
+ goto out;
+ }
+
+ message_json = &_message_json;
+ }
+#else /* not HAVE_JANSSON */
+ (void)msg_type;
+#endif /* not HAVE_JANSSON */
+
+ ret = net_witness_scan_registrations(c, message_json, &action);
+ if (ret != 0) {
+ d_printf("net_witness_scan_registrations() failed\n");
+ goto out;
+ }
+
+ ret = 0;
+out:
+#ifdef HAVE_JANSSON
+ if (!json_is_invalid(&_message_json)) {
+ json_free(&_message_json);
+ }
+#endif /* HAVE_JANSSON */
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+struct net_witness_force_unregister_state {
+ struct net_context *c;
+ struct rpcd_witness_registration_updateB m;
+ char *headline;
+};
+
+static bool net_witness_force_unregister_prepare_fn(void *private_data)
+{
+ struct net_witness_force_unregister_state *state =
+ (struct net_witness_force_unregister_state *)private_data;
+
+ if (state->headline != NULL) {
+ d_printf("%s\n", state->headline);
+ TALLOC_FREE(state->headline);
+ }
+
+ return true;
+}
+
+static bool net_witness_force_unregister_match_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ return true;
+}
+
+static NTSTATUS net_witness_force_unregister_process_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ struct net_witness_force_unregister_state *state =
+ (struct net_witness_force_unregister_state *)private_data;
+ struct net_context *c = state->c;
+ struct rpcd_witness_registration_updateB update = {
+ .context_handle = rg->context_handle,
+ .type = state->m.type,
+ .update = state->m.update,
+ };
+ DATA_BLOB blob = { .length = 0, };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ SMB_ASSERT(update.type != 0);
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update,
+ (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ status = messaging_send(c->msg_ctx,
+ rg->server_id,
+ MSG_RPCD_WITNESS_REGISTRATION_UPDATE,
+ &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send() - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void net_witness_force_unregister_usage(void)
+{
+ d_printf("%s\n"
+ "net witness force-unregister\n"
+ " %s\n\n",
+ _("Usage:"),
+ _("Force unregistrations for witness registrations"));
+ net_witness_filter_usage();
+ net_witness_update_usage();
+ d_printf(" The selected registrations are removed on "
+ "the server and\n"
+ " any pending AsyncNotify request will get "
+ "a NOT_FOUND error.\n"
+ "\n"
+ " Typically this triggers a clean re-registration "
+ "on the client.\n"
+ "\n");
+}
+
+static int net_witness_force_unregister(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct net_witness_force_unregister_state state = { .c = c, };
+ struct rpcd_witness_registration_updateB *m = &state.m;
+#ifdef HAVE_JANSSON
+ struct json_object _message_json = json_empty_object;
+#endif /* HAVE_JANSSON */
+ struct json_object *message_json = NULL;
+ struct net_witness_scan_registrations_action_state action = {
+ .prepare_fn = net_witness_force_unregister_prepare_fn,
+ .match_fn = net_witness_force_unregister_match_fn,
+ .process_fn = net_witness_force_unregister_process_fn,
+ .private_data = &state,
+ };
+ int ret = -1;
+ bool ok;
+
+ if (c->display_usage) {
+ net_witness_force_unregister_usage();
+ goto out;
+ }
+
+ if (argc != 0) {
+ net_witness_force_unregister_usage();
+ goto out;
+ }
+
+ if (!lp_clustering()) {
+ d_printf("ERROR: Only supported with clustering=yes!\n\n");
+ goto out;
+ }
+
+ ok = net_witness_verify_update_options(c);
+ if (!ok) {
+ goto out;
+ }
+
+ m->type = RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_UNREGISTER;
+
+ state.headline = talloc_asprintf(frame, "FORCE_UNREGISTER:");
+ if (state.headline == NULL) {
+ goto out;
+ }
+
+#ifdef HAVE_JANSSON
+ if (c->opt_json) {
+ TALLOC_FREE(state.headline);
+
+ _message_json = json_new_object();
+ if (json_is_invalid(&_message_json)) {
+ goto out;
+ }
+
+ ret = json_add_string(&_message_json,
+ "type",
+ "FORCE_UNREGISTER");
+ if (ret != 0) {
+ goto out;
+ }
+
+ message_json = &_message_json;
+ }
+#endif /* HAVE_JANSSON */
+
+ ret = net_witness_scan_registrations(c, message_json, &action);
+ if (ret != 0) {
+ d_printf("net_witness_scan_registrations() failed\n");
+ goto out;
+ }
+
+ ret = 0;
+out:
+#ifdef HAVE_JANSSON
+ if (!json_is_invalid(&_message_json)) {
+ json_free(&_message_json);
+ }
+#endif /* HAVE_JANSSON */
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+struct net_witness_force_response_state {
+ struct net_context *c;
+ struct rpcd_witness_registration_updateB m;
+#ifdef HAVE_JANSSON
+ struct json_object json_root;
+#endif /* HAVE_JANSSON */
+ char *headline;
+};
+
+#ifdef HAVE_JANSSON
+static NTSTATUS net_witness_force_response_parse_rc(
+ struct net_witness_force_response_state *state,
+ json_t *jsmsg,
+ TALLOC_CTX *mem_ctx,
+ size_t mi,
+ union witness_notifyResponse_message *message)
+{
+ struct witness_ResourceChange *rc = &message->resource_change;
+ json_t *jsctype = NULL;
+ json_int_t ctype;
+ json_t *jscname = NULL;
+ const char *cname = NULL;
+
+ if (!json_is_object(jsmsg)) {
+ DBG_ERR("'message[%zu]' needs to be an object\n", mi);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ jsctype = json_object_get(jsmsg, "type");
+ if (jsctype == NULL) {
+ DBG_ERR("%s: INVALID_PARAMETER\n", __location__);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (!json_is_integer(jsctype)) {
+ DBG_ERR("%s: INVALID_PARAMETER\n", __location__);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ctype = json_integer_value(jsctype);
+
+ jscname = json_object_get(jsmsg, "name");
+ if (jscname == NULL) {
+ DBG_ERR("%s: INVALID_PARAMETER\n", __location__);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (!json_is_string(jscname)) {
+ DBG_ERR("%s: INVALID_PARAMETER\n", __location__);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ cname = json_string_value(jscname);
+
+ rc->type = ctype;
+ rc->name = talloc_strdup(mem_ctx, cname);
+ if (rc->name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS net_witness_force_response_parse_ipl(
+ struct net_witness_force_response_state *state,
+ json_t *jsmsg,
+ TALLOC_CTX *mem_ctx,
+ size_t mi,
+ union witness_notifyResponse_message *message)
+{
+ struct witness_IPaddrInfoList *ipl =
+ &message->client_move;
+ size_t ai, num_addrs = 0;
+ struct witness_IPaddrInfo *addrs = NULL;
+
+ if (!json_is_array(jsmsg)) {
+ DBG_ERR("'messages[%zu]' needs to be an array\n", mi);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ num_addrs = json_array_size(jsmsg);
+ if (num_addrs > UINT32_MAX) {
+ DBG_ERR("Too many elements in 'messages[%zu]': %zu\n",
+ mi, num_addrs);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ addrs = talloc_zero_array(mem_ctx,
+ struct witness_IPaddrInfo,
+ num_addrs);
+ if (addrs == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (ai = 0; ai < num_addrs; ai++) {
+ struct witness_IPaddrInfo *info =
+ &addrs[ai];
+ json_t *jsaddr = json_array_get(jsmsg, ai);
+ json_t *jsflags = NULL;
+ json_int_t flags;
+ json_t *jsipv4 = NULL;
+ const char *ipv4 = NULL;
+ json_t *jsipv6 = NULL;
+ const char *ipv6 = NULL;
+
+ if (!json_is_object(jsaddr)) {
+ DBG_ERR("'messages[%zu][%zu]' needs to be an object\n",
+ mi, ai);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ jsflags = json_object_get(jsaddr, "flags");
+ if (jsflags == NULL) {
+ DBG_ERR("'messages[%zu][%zu]['flags']' missing\n",
+ mi, ai);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (!json_is_integer(jsflags)) {
+ DBG_ERR("'messages[%zu][%zu]['flags']' "
+ "needs to be an integer\n",
+ mi, ai);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ flags = json_integer_value(jsflags);
+
+ jsipv4 = json_object_get(jsaddr, "ipv4");
+ if (jsipv4 != NULL) {
+ if (!json_is_string(jsipv4)) {
+ DBG_ERR("'messages[%zu][%zu]['ipv4']' "
+ "needs to be a string\n",
+ mi, ai);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ipv4 = json_string_value(jsipv4);
+ if (!is_ipaddress_v4(ipv4)) {
+ DBG_ERR("'messages[%zu][%zu]['ipv4']' "
+ "needs to be a valid ipv4 address\n",
+ mi, ai);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ ipv4 = "0.0.0.0";
+ }
+
+ jsipv6 = json_object_get(jsaddr, "ipv6");
+ if (jsipv6 != NULL) {
+ if (!json_is_string(jsipv6)) {
+ DBG_ERR("'messages[%zu][%zu]['ipv6']' "
+ "needs to be a string\n",
+ mi, ai);
+ DBG_ERR("%s: INVALID_PARAMETER\n", __location__);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ ipv6 = json_string_value(jsipv6);
+ if (!is_ipaddress_v6(ipv6)) {
+ DBG_ERR("'messages[%zu][%zu]['ipv4']' "
+ "needs to be a valid ipv6 address\n",
+ mi, ai);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else {
+ ipv6 = "::";
+ }
+
+ info->flags = flags;
+ info->ipv4 = talloc_strdup(addrs, ipv4);
+ if (info->ipv4 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ info->ipv6 = talloc_strdup(addrs, ipv6);
+ if (info->ipv6 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ ipl->num = num_addrs;
+ ipl->addr = addrs;
+
+ return NT_STATUS_OK;
+}
+#endif /* HAVE_JANSSON */
+
+static NTSTATUS net_witness_force_response_parse(struct net_witness_force_response_state *state)
+{
+#ifdef HAVE_JANSSON
+ struct net_context *c = state->c;
+ struct rpcd_witness_registration_update_force_response *force = NULL;
+ struct witness_notifyResponse *response = NULL;
+ size_t mi, num_messages = 0;
+ union witness_notifyResponse_message *messages = NULL;
+ json_t *jsroot = NULL;
+ json_t *jsresult = NULL;
+ json_t *jsresponse = NULL;
+ json_t *jstype = NULL;
+ json_t *jsmessages = NULL;
+
+ if (c->opt_witness_forced_response != NULL) {
+ const char *str = c->opt_witness_forced_response;
+ size_t flags = JSON_REJECT_DUPLICATES;
+ json_error_t jserror;
+
+ jsroot = json_loads(str, flags, &jserror);
+ if (jsroot == NULL) {
+ DBG_ERR("Invalid JSON in "
+ "--witness-forced-response='%s'\n",
+ str);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ state->json_root = (struct json_object) {
+ .root = jsroot,
+ .valid = true,
+ };
+ }
+
+ state->m.type = RPCD_WITNESS_REGISTRATION_UPDATE_FORCE_RESPONSE;
+ force = &state->m.update.force_response;
+ force->response = NULL;
+ force->result = WERR_OK;
+
+ if (jsroot == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ jsresult = json_object_get(jsroot, "result");
+ if (jsresult != NULL) {
+ int val_type = json_typeof(jsresult);
+
+ switch (val_type) {
+ case JSON_INTEGER: {
+ json_int_t val = json_integer_value(jsresult);
+
+ if (val > UINT32_MAX) {
+ DBG_ERR("Invalid 'result' value: %d\n",
+ (int) val);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (val < 0) {
+ DBG_ERR("invalid 'result' value: %d\n",
+ (int) val);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ force->result = W_ERROR(val);
+ }; break;
+ default:
+ DBG_ERR("Invalid json type for 'result' - needs integer\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ jsresponse = json_object_get(jsroot, "response");
+ if (jsresponse == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (!json_is_object(jsresponse)) {
+ DBG_ERR("Invalid json type 'response' needs object\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ response = talloc_zero(talloc_tos(), struct witness_notifyResponse);
+ if (response == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ jstype = json_object_get(jsresponse, "type");
+ if (jstype == NULL) {
+ DBG_ERR("Missing 'type' element in 'response'\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ {
+ int val_type = json_typeof(jstype);
+
+ switch (val_type) {
+ case JSON_INTEGER: {
+ json_int_t val = json_integer_value(jstype);
+
+ if (val > WITNESS_NOTIFY_IP_CHANGE) {
+ DBG_ERR("invalid 'type' value in 'response': "
+ "%d\n", (int) val);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ if (val < WITNESS_NOTIFY_RESOURCE_CHANGE) {
+ DBG_ERR("invalid 'type' value in 'response': "
+ "%d\n", (int) val);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ response->type = val;
+ }; break;
+ default:
+ DBG_ERR("Invalid json type for 'type' in 'response' "
+ "- needs integer\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ force->response = response;
+
+ jsmessages = json_object_get(jsresponse, "messages");
+ if (jsmessages == NULL) {
+ return NT_STATUS_OK;
+ }
+
+ if (!json_is_array(jsmessages)) {
+ DBG_ERR("'messages' in 'response' needs to be an array\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ num_messages = json_array_size(jsmessages);
+ if (num_messages > UINT32_MAX) {
+ DBG_ERR("Too many elements in 'messages': %zu\n",
+ num_messages);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ messages = talloc_zero_array(response,
+ union witness_notifyResponse_message,
+ num_messages);
+ if (messages == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (mi = 0; mi < num_messages; mi++) {
+ json_t *jsmsg = json_array_get(jsmessages, mi);
+ union witness_notifyResponse_message *message = &messages[mi];
+ NTSTATUS status;
+
+ switch (response->type) {
+ case WITNESS_NOTIFY_RESOURCE_CHANGE:
+ status = net_witness_force_response_parse_rc(state,
+ jsmsg,
+ messages,
+ mi,
+ message);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *fn =
+ "net_witness_force_response_parse_rc";
+ DBG_ERR("%s failed: %s\n",
+ fn, nt_errstr(status));
+ return status;
+ }
+
+ break;
+ case WITNESS_NOTIFY_CLIENT_MOVE:
+ case WITNESS_NOTIFY_SHARE_MOVE:
+ case WITNESS_NOTIFY_IP_CHANGE:
+ status = net_witness_force_response_parse_ipl(state,
+ jsmsg,
+ messages,
+ mi,
+ message);
+ if (!NT_STATUS_IS_OK(status)) {
+ const char *fn =
+ "net_witness_force_response_parse_ipl";
+ DBG_ERR("%s failed: %s\n",
+ fn, nt_errstr(status));
+ return status;
+ }
+
+ break;
+ }
+ }
+
+ response->num = num_messages;
+ response->messages = messages;
+
+ return NT_STATUS_OK;
+#else /* not HAVE_JANSSON */
+ d_fprintf(stderr, _("JSON support not available\n"));
+ return NT_STATUS_NOT_IMPLEMENTED;
+#endif /* not HAVE_JANSSON */
+}
+
+static bool net_witness_force_response_prepare_fn(void *private_data)
+{
+ struct net_witness_force_response_state *state =
+ (struct net_witness_force_response_state *)private_data;
+
+ if (state->headline != NULL) {
+ d_printf("%s\n", state->headline);
+ TALLOC_FREE(state->headline);
+ }
+
+ return true;
+}
+
+static bool net_witness_force_response_match_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ return true;
+}
+
+static NTSTATUS net_witness_force_response_process_fn(void *private_data,
+ const struct rpcd_witness_registration *rg)
+{
+ struct net_witness_force_response_state *state =
+ (struct net_witness_force_response_state *)private_data;
+ struct net_context *c = state->c;
+ struct rpcd_witness_registration_updateB update = {
+ .context_handle = rg->context_handle,
+ .type = state->m.type,
+ .update = state->m.update,
+ };
+ DATA_BLOB blob = { .length = 0, };
+ enum ndr_err_code ndr_err;
+ NTSTATUS status;
+
+ SMB_ASSERT(update.type != 0);
+
+ if (DEBUGLVL(DBGLVL_DEBUG)) {
+ NDR_PRINT_DEBUG(rpcd_witness_registration_updateB, &update);
+ }
+
+ ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &update,
+ (ndr_push_flags_fn_t)ndr_push_rpcd_witness_registration_updateB);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ status = ndr_map_error2ntstatus(ndr_err);
+ DBG_ERR("ndr_push_struct_blob - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ status = messaging_send(c->msg_ctx,
+ rg->server_id,
+ MSG_RPCD_WITNESS_REGISTRATION_UPDATE,
+ &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("messaging_send() - %s\n", nt_errstr(status));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static void net_witness_force_response_usage(void)
+{
+ d_printf("%s\n"
+ "net witness force-response\n"
+ " %s\n\n",
+ _("Usage:"),
+ _("Force an AsyncNotify response based on "
+ "json input (mostly for testing)"));
+ net_witness_filter_usage();
+ net_witness_update_usage();
+ d_printf(" Note this is designed for testing and debugging!\n"
+ "\n"
+ " In short it is not designed to be used by "
+ "administrators,\n"
+ " but developers and automated tests.\n"
+ "\n"
+ " By default an empty response with WERR_OK is generated,\n"
+ " but basically any valid response can be specified by a\n"
+ " specifying a JSON string:\n"
+ "\n"
+ " --witness-forced-response=JSON\n"
+ " This allows the generation of very complex\n"
+ " witness_notifyResponse structures.\n"
+ "\n"
+ " As this is for developers, please read the code\n"
+ " in order to understand all possible values\n"
+ " of the JSON string format...\n"
+ "\n"
+ " Simple examples are:\n"
+ "\n"
+ "# Resource Change:\n%s\n"
+ "\n"
+ "# Client Move:\n%s\n"
+ "\n"
+ "# Share Move:\n%s\n"
+ "\n"
+ "# IP Change:\n%s\n"
+ "\n",
+ "'{ \"result\": 0, \"response\": { \"type\": 1, "
+ "\"messages\": [ { "
+ "\"type\": 255 , "
+ "\"name\": \"some-resource-name\" "
+ "} ]"
+ "}}'",
+ "'{ \"result\": 0, \"response\": { \"type\": 2, "
+ "\"messages\": ["
+ "[{ "
+ "\"flags\": 9, "
+ "\"ipv4\": \"10.0.10.1\" "
+ "}]"
+ "]"
+ "}}'",
+ "'{ \"result\": 0, \"response\": { \"type\": 3, "
+ "\"messages\": ["
+ "[{ "
+ "\"flags\": 9, "
+ "\"ipv4\": \"10.0.10.1\" "
+ "}]"
+ "]"
+ "}}'",
+ "'{ \"result\": 0, \"response\": { \"type\": 4, "
+ "\"messages\": ["
+ "[{ "
+ "\"flags\": 9, "
+ "\"ipv4\": \"10.0.10.1\" "
+ "}]"
+ "]"
+ "}}'");
+}
+
+static int net_witness_force_response(struct net_context *c, int argc, const char **argv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct net_witness_force_response_state state = { .c = c, };
+#ifdef HAVE_JANSSON
+ struct json_object _message_json = json_empty_object;
+#endif /* HAVE_JANSSON */
+ struct json_object *message_json = NULL;
+ struct net_witness_scan_registrations_action_state action = {
+ .prepare_fn = net_witness_force_response_prepare_fn,
+ .match_fn = net_witness_force_response_match_fn,
+ .process_fn = net_witness_force_response_process_fn,
+ .private_data = &state,
+ };
+ NTSTATUS status;
+ int ret = -1;
+ bool ok;
+
+ if (c->display_usage) {
+ net_witness_force_response_usage();
+ goto out;
+ }
+
+ if (argc != 0) {
+ net_witness_force_response_usage();
+ goto out;
+ }
+
+ if (!lp_clustering()) {
+ d_printf("ERROR: Only supported with clustering=yes!\n\n");
+ goto out;
+ }
+
+ ok = net_witness_verify_update_options(c);
+ if (!ok) {
+ goto out;
+ }
+
+ status = net_witness_force_response_parse(&state);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("net_witness_force_response_parse failed: %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ state.headline = talloc_asprintf(frame, "FORCE_RESPONSE:%s%s",
+ c->opt_witness_forced_response != NULL ?
+ " " : "",
+ c->opt_witness_forced_response != NULL ?
+ c->opt_witness_forced_response : "");
+
+ if (state.headline == NULL) {
+ goto out;
+ }
+
+#ifdef HAVE_JANSSON
+ if (c->opt_json) {
+ TALLOC_FREE(state.headline);
+
+ _message_json = json_new_object();
+ if (json_is_invalid(&_message_json)) {
+ goto out;
+ }
+
+ ret = json_add_string(&_message_json,
+ "type",
+ "FORCE_RESPONSE");
+ if (ret != 0) {
+ goto out;
+ }
+
+ if (!json_is_invalid(&state.json_root)) {
+ ret = json_add_object(&_message_json,
+ "json",
+ &state.json_root);
+ if (ret != 0) {
+ goto out;
+ }
+ state.json_root = json_empty_object;
+ }
+ message_json = &_message_json;
+ }
+#endif /* HAVE_JANSSON */
+
+ ret = net_witness_scan_registrations(c, message_json, &action);
+ if (ret != 0) {
+ d_printf("net_witness_scan_registrations() failed\n");
+ goto out;
+ }
+
+ ret = 0;
+out:
+#ifdef HAVE_JANSSON
+ if (!json_is_invalid(&_message_json)) {
+ json_free(&_message_json);
+ }
+ if (!json_is_invalid(&state.json_root)) {
+ json_free(&state.json_root);
+ }
+#endif /* HAVE_JANSSON */
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+int net_witness(struct net_context *c, int argc, const char **argv)
+{
+ struct functable func[] = {
+ {
+ "list",
+ net_witness_list,
+ NET_TRANSPORT_LOCAL,
+ N_("List witness registrations "
+ "from rpcd_witness_registration.tdb"),
+ N_("net witness list\n"
+ " List witness registrations "
+ "from rpcd_witness_registration.tdb"),
+ },
+ {
+ "client-move",
+ net_witness_client_move,
+ NET_TRANSPORT_LOCAL,
+ N_("Generate client move notifications for "
+ "witness registrations to a new ip or node"),
+ N_("net witness client-move\n"
+ " Generate client move notifications for "
+ "witness registrations to a new ip or node"),
+ },
+ {
+ "share-move",
+ net_witness_share_move,
+ NET_TRANSPORT_LOCAL,
+ N_("Generate share move notifications for "
+ "witness registrations to a new ip or node"),
+ N_("net witness share-move\n"
+ " Generate share move notifications for "
+ "witness registrations to a new ip or node"),
+ },
+ {
+ "force-unregister",
+ net_witness_force_unregister,
+ NET_TRANSPORT_LOCAL,
+ N_("Force unregistrations for witness registrations"),
+ N_("net witness force-unregister\n"
+ " Force unregistrations for "
+ "witness registrations"),
+ },
+ {
+ "force-response",
+ net_witness_force_response,
+ NET_TRANSPORT_LOCAL,
+ N_("Force an AsyncNotify response based on "
+ "json input (mostly for testing)"),
+ N_("net witness force-response\n"
+ " Force an AsyncNotify response based on "
+ "json input (mostly for testing)"),
+ },
+ {NULL, NULL, 0, NULL, NULL}
+ };
+
+ return net_run_function(c, argc, argv, "net witness", func);
+}
diff --git a/source3/utils/netlookup.c b/source3/utils/netlookup.c
new file mode 100644
index 0000000..aaf78b0
--- /dev/null
+++ b/source3/utils/netlookup.c
@@ -0,0 +1,218 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Name lookup.
+
+ Copyright (C) Jeremy Allison 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/net.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "libsmb/libsmb.h"
+
+/********************************************************
+ Connection cachine struct. Goes away when ctx destroyed.
+********************************************************/
+
+struct con_struct {
+ bool failed_connect;
+ NTSTATUS err;
+ struct cli_state *cli;
+ struct rpc_pipe_client *lsapipe;
+ struct policy_handle pol;
+};
+
+static struct con_struct *cs;
+
+/********************************************************
+ Close connection on context destruction.
+********************************************************/
+
+static int cs_destructor(struct con_struct *p)
+{
+ if (cs->cli) {
+ cli_shutdown(cs->cli);
+ }
+ cs = NULL;
+ return 0;
+}
+
+/********************************************************
+ Create the connection to localhost.
+********************************************************/
+
+static struct con_struct *create_cs(struct net_context *c,
+ TALLOC_CTX *ctx, NTSTATUS *perr)
+{
+ NTSTATUS nt_status;
+ struct sockaddr_storage loopback_ss;
+ struct cli_credentials *anon_creds = NULL;
+
+ *perr = NT_STATUS_OK;
+
+ if (!interpret_string_addr(&loopback_ss, "127.0.0.1", AI_NUMERICHOST)) {
+ *perr = NT_STATUS_INVALID_PARAMETER;
+ return NULL;
+ }
+
+ if (cs) {
+ if (cs->failed_connect) {
+ *perr = cs->err;
+ return NULL;
+ }
+ return cs;
+ }
+
+ cs = talloc(ctx, struct con_struct);
+ if (!cs) {
+ *perr = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ anon_creds = cli_credentials_init_anon(cs);
+ if (anon_creds == NULL) {
+ TALLOC_FREE(cs);
+ *perr = NT_STATUS_NO_MEMORY;
+ return NULL;
+ }
+
+ ZERO_STRUCTP(cs);
+ talloc_set_destructor(cs, cs_destructor);
+
+ nt_status = cli_full_connection_creds(&cs->cli, lp_netbios_name(), lp_netbios_name(),
+ &loopback_ss, 0,
+ "IPC$", "IPC",
+ anon_creds,
+ CLI_FULL_CONNECTION_IPC);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(2,("create_cs: Connect failed. Error was %s\n", nt_errstr(nt_status)));
+ cs->failed_connect = true;
+ cs->err = nt_status;
+ *perr = nt_status;
+ return NULL;
+ }
+
+ nt_status = cli_rpc_pipe_open_noauth(cs->cli,
+ &ndr_table_lsarpc,
+ &cs->lsapipe);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(2,("create_cs: open LSA pipe failed. Error was %s\n", nt_errstr(nt_status)));
+ cs->failed_connect = true;
+ cs->err = nt_status;
+ *perr = nt_status;
+ return NULL;
+ }
+
+ nt_status = rpccli_lsa_open_policy(cs->lsapipe, ctx, true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &cs->pol);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(2,("create_cs: rpccli_lsa_open_policy failed. Error was %s\n", nt_errstr(nt_status)));
+ cs->failed_connect = true;
+ cs->err = nt_status;
+ *perr = nt_status;
+ return NULL;
+ }
+
+ return cs;
+}
+
+/********************************************************
+ Do a lookup_sids call to localhost.
+ Check if the local machine is authoritative for this sid. We can't
+ check if this is our SID as that's stored in the root-read-only
+ secrets.tdb.
+ The local smbd will also ask winbindd for us, so we don't have to.
+********************************************************/
+
+NTSTATUS net_lookup_name_from_sid(struct net_context *c,
+ TALLOC_CTX *ctx,
+ struct dom_sid *psid,
+ const char **ppdomain,
+ const char **ppname)
+{
+ NTSTATUS nt_status;
+ struct con_struct *csp = NULL;
+ char **domains;
+ char **names;
+ enum lsa_SidType *types;
+
+ *ppdomain = NULL;
+ *ppname = NULL;
+
+ csp = create_cs(c, ctx, &nt_status);
+ if (csp == NULL) {
+ return nt_status;
+ }
+
+ nt_status = rpccli_lsa_lookup_sids(csp->lsapipe, ctx,
+ &csp->pol,
+ 1, psid,
+ &domains,
+ &names,
+ &types);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ *ppdomain = domains[0];
+ *ppname = names[0];
+ /* Don't care about type here. */
+
+ /* Converted OK */
+ return NT_STATUS_OK;
+}
+
+/********************************************************
+ Do a lookup_names call to localhost.
+********************************************************/
+
+NTSTATUS net_lookup_sid_from_name(struct net_context *c, TALLOC_CTX *ctx,
+ const char *full_name, struct dom_sid *pret_sid)
+{
+ NTSTATUS nt_status;
+ struct con_struct *csp = NULL;
+ struct dom_sid *sids = NULL;
+ enum lsa_SidType *types = NULL;
+
+ csp = create_cs(c, ctx, &nt_status);
+ if (csp == NULL) {
+ return nt_status;
+ }
+
+ nt_status = rpccli_lsa_lookup_names(csp->lsapipe, ctx,
+ &csp->pol,
+ 1,
+ &full_name,
+ NULL, 1,
+ &sids, &types);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return nt_status;
+ }
+
+ *pret_sid = sids[0];
+
+ /* Converted OK */
+ return NT_STATUS_OK;
+}
diff --git a/source3/utils/nmblookup.c b/source3/utils/nmblookup.c
new file mode 100644
index 0000000..9523f71
--- /dev/null
+++ b/source3/utils/nmblookup.c
@@ -0,0 +1,468 @@
+/*
+ Unix SMB/CIFS implementation.
+ NBT client - used to lookup netbios names
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Jelmer Vernooij 2003 (Conversion to popt)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "libsmb/nmblib.h"
+#include "libsmb/namequery.h"
+#include "lib/util/string_wrappers.h"
+
+static bool give_flags = false;
+static bool use_bcast = true;
+static bool got_bcast = false;
+static struct sockaddr_storage bcast_addr;
+static bool recursion_desired = false;
+static bool translate_addresses = false;
+static int ServerFD= -1;
+static bool RootPort = false;
+static bool find_status = false;
+
+/****************************************************************************
+ Open the socket communication.
+**************************************************************************/
+
+static bool open_sockets(void)
+{
+ struct sockaddr_storage ss;
+ const char *sock_addr = lp_nbt_client_socket_address();
+
+ if (!interpret_string_addr(&ss, sock_addr,
+ AI_NUMERICHOST|AI_PASSIVE)) {
+ DEBUG(0,("open_sockets: unable to get socket address "
+ "from string %s\n", sock_addr));
+ return false;
+ }
+ ServerFD = open_socket_in(
+ SOCK_DGRAM, &ss, (RootPort ? 137 : 0), true);
+ if (ServerFD < 0) {
+ if (RootPort) {
+ DBG_ERR("open_socket_in failed: %s\n",
+ strerror(-ServerFD));
+ } else {
+ DBG_NOTICE("open_socket_in failed: %s\n",
+ strerror(-ServerFD));
+ }
+ return false;
+ }
+
+ set_socket_options( ServerFD, "SO_BROADCAST" );
+
+ DEBUG(3, ("Socket opened.\n"));
+ return true;
+}
+
+/****************************************************************************
+turn a node status flags field into a string
+****************************************************************************/
+static char *node_status_flags(unsigned char flags)
+{
+ static fstring ret;
+ fstrcpy(ret,"");
+
+ fstrcat(ret, (flags & 0x80) ? "<GROUP> " : " ");
+ if ((flags & 0x60) == 0x00) fstrcat(ret,"B ");
+ if ((flags & 0x60) == 0x20) fstrcat(ret,"P ");
+ if ((flags & 0x60) == 0x40) fstrcat(ret,"M ");
+ if ((flags & 0x60) == 0x60) fstrcat(ret,"H ");
+ if (flags & 0x10) fstrcat(ret,"<DEREGISTERING> ");
+ if (flags & 0x08) fstrcat(ret,"<CONFLICT> ");
+ if (flags & 0x04) fstrcat(ret,"<ACTIVE> ");
+ if (flags & 0x02) fstrcat(ret,"<PERMANENT> ");
+
+ return ret;
+}
+
+/****************************************************************************
+ Turn the NMB Query flags into a string.
+****************************************************************************/
+
+static char *query_flags(int flags)
+{
+ static fstring ret1;
+ fstrcpy(ret1, "");
+
+ if (flags & NM_FLAGS_RS) fstrcat(ret1, "Response ");
+ if (flags & NM_FLAGS_AA) fstrcat(ret1, "Authoritative ");
+ if (flags & NM_FLAGS_TC) fstrcat(ret1, "Truncated ");
+ if (flags & NM_FLAGS_RD) fstrcat(ret1, "Recursion_Desired ");
+ if (flags & NM_FLAGS_RA) fstrcat(ret1, "Recursion_Available ");
+ if (flags & NM_FLAGS_B) fstrcat(ret1, "Broadcast ");
+
+ return ret1;
+}
+
+/****************************************************************************
+ Do a node status query.
+****************************************************************************/
+
+static bool do_node_status(const char *name,
+ int type,
+ struct sockaddr_storage *pss)
+{
+ struct nmb_name nname;
+ size_t count = 0;
+ size_t i, j;
+ struct node_status *addrs;
+ struct node_status_extra extra;
+ fstring cleanname;
+ char addr[INET6_ADDRSTRLEN];
+ NTSTATUS status;
+
+ print_sockaddr(addr, sizeof(addr), pss);
+ d_printf("Looking up status of %s\n",addr);
+ make_nmb_name(&nname, name, type);
+ status = node_status_query(talloc_tos(), &nname, pss,
+ &addrs, &count, &extra);
+ if (NT_STATUS_IS_OK(status)) {
+ for (i=0;i<count;i++) {
+ pull_ascii_fstring(cleanname, addrs[i].name);
+ for (j=0;cleanname[j];j++) {
+ if (!isprint((int)cleanname[j])) {
+ cleanname[j] = '.';
+ }
+ }
+ d_printf("\t%-15s <%02x> - %s\n",
+ cleanname,addrs[i].type,
+ node_status_flags(addrs[i].flags));
+ }
+ d_printf("\n\tMAC Address = %02X-%02X-%02X-%02X-%02X-%02X\n",
+ extra.mac_addr[0], extra.mac_addr[1],
+ extra.mac_addr[2], extra.mac_addr[3],
+ extra.mac_addr[4], extra.mac_addr[5]);
+ d_printf("\n");
+ TALLOC_FREE(addrs);
+ return true;
+ } else {
+ d_printf("No reply from %s\n\n",addr);
+ return false;
+ }
+}
+
+
+/****************************************************************************
+ Send out one query.
+****************************************************************************/
+
+static bool query_one(const char *lookup, unsigned int lookup_type)
+{
+ size_t j, count = 0;
+ uint8_t flags = 0;
+ struct sockaddr_storage *ip_list=NULL;
+ NTSTATUS status = NT_STATUS_NOT_FOUND;
+
+ if (got_bcast) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr), &bcast_addr);
+ d_printf("querying %s on %s\n", lookup, addr);
+ status = name_query(lookup,lookup_type,use_bcast,
+ use_bcast?true:recursion_desired,
+ &bcast_addr, talloc_tos(),
+ &ip_list, &count, &flags);
+ } else {
+ status = name_resolve_bcast(talloc_tos(),
+ lookup,
+ lookup_type,
+ &ip_list,
+ &count);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (give_flags) {
+ d_printf("Flags: %s\n", query_flags(flags));
+ }
+
+ for (j=0;j<count;j++) {
+ char addr[INET6_ADDRSTRLEN];
+ if (translate_addresses) {
+ char h_name[MAX_DNS_NAME_LENGTH];
+ h_name[0] = '\0';
+ if (sys_getnameinfo((const struct sockaddr *)&ip_list[j],
+ sizeof(struct sockaddr_storage),
+ h_name, sizeof(h_name),
+ NULL, 0,
+ NI_NAMEREQD)) {
+ continue;
+ }
+ d_printf("%s, ", h_name);
+ }
+ print_sockaddr(addr, sizeof(addr), &ip_list[j]);
+ d_printf("%s %s<%02x>\n", addr,lookup, lookup_type);
+ /* We can only do find_status if the ip address returned
+ was valid - ie. name_query returned true.
+ */
+ if (find_status) {
+ if (!do_node_status(lookup, lookup_type, &ip_list[j])) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+ }
+ }
+
+ TALLOC_FREE(ip_list);
+
+ return NT_STATUS_IS_OK(status);
+}
+
+
+/****************************************************************************
+ main program
+****************************************************************************/
+enum nmblookup_cmdline_options {
+ CMDLINE_RECURSIVE = 1,
+};
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ unsigned int lookup_type = 0x0;
+ fstring lookup;
+ static bool find_master=False;
+ static bool lookup_by_ip = False;
+ poptContext pc = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ int rc = 0;
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "broadcast",
+ .shortName = 'B',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'B',
+ .descrip = "Specify address to use for broadcasts",
+ .argDescrip = "BROADCAST-ADDRESS",
+ },
+ {
+ .longName = "flags",
+ .shortName = 'f',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'f',
+ .descrip = "List the NMB flags returned",
+ },
+ {
+ .longName = "unicast",
+ .shortName = 'U',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'U',
+ .descrip = "Specify address to use for unicast",
+ },
+ {
+ .longName = "master-browser",
+ .shortName = 'M',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'M',
+ .descrip = "Search for a master browser",
+ },
+ {
+ .longName = "recursion",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = CMDLINE_RECURSIVE,
+ .descrip = "Set recursion desired in package",
+ },
+ {
+ .longName = "status",
+ .shortName = 'S',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'S',
+ .descrip = "Lookup node status as well",
+ },
+ {
+ .longName = "translate",
+ .shortName = 'T',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'T',
+ .descrip = "Translate IP addresses into names",
+ },
+ {
+ .longName = "root-port",
+ .shortName = 'r',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'r',
+ .descrip = "Use root port 137 (Win95 only replies to this)",
+ },
+ {
+ .longName = "lookup-by-ip",
+ .shortName = 'A',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'A',
+ .descrip = "Do a node status on <name> as an IP Address",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ *lookup = 0;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "<NODE> ...");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'f':
+ give_flags = true;
+ break;
+ case 'M':
+ find_master = true;
+ break;
+ case CMDLINE_RECURSIVE:
+ recursion_desired = true;
+ break;
+ case 'S':
+ find_status = true;
+ break;
+ case 'r':
+ RootPort = true;
+ break;
+ case 'A':
+ lookup_by_ip = true;
+ break;
+ case 'B':
+ if (interpret_string_addr(&bcast_addr,
+ poptGetOptArg(pc),
+ NI_NUMERICHOST)) {
+ got_bcast = True;
+ use_bcast = True;
+ }
+ break;
+ case 'U':
+ if (interpret_string_addr(&bcast_addr,
+ poptGetOptArg(pc),
+ 0)) {
+ got_bcast = True;
+ use_bcast = False;
+ }
+ break;
+ case 'T':
+ translate_addresses = !translate_addresses;
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptGetArg(pc); /* Remove argv[0] */
+
+ if(!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ rc = 1;
+ goto out;
+ }
+
+ if (!open_sockets()) {
+ rc = 1;
+ goto out;
+ }
+
+ while(poptPeekArg(pc)) {
+ char *p;
+ struct in_addr ip;
+ size_t nbt_len;
+
+ fstrcpy(lookup,poptGetArg(pc));
+
+ if(lookup_by_ip) {
+ struct sockaddr_storage ss;
+ ip = interpret_addr2(lookup);
+ in_addr_to_sockaddr_storage(&ss, ip);
+ fstrcpy(lookup,"*");
+ if (!do_node_status(lookup, lookup_type, &ss)) {
+ rc = 1;
+ }
+ continue;
+ }
+
+ if (find_master) {
+ if (*lookup == '-') {
+ fstrcpy(lookup,"\01\02__MSBROWSE__\02");
+ lookup_type = 1;
+ } else {
+ lookup_type = 0x1d;
+ }
+ }
+
+ p = strchr_m(lookup,'#');
+ if (p) {
+ *p = '\0';
+ sscanf(++p,"%x",&lookup_type);
+ }
+
+ nbt_len = strlen(lookup);
+ if (nbt_len > MAX_NETBIOSNAME_LEN - 1) {
+ d_printf("The specified netbios name [%s] is too long!\n",
+ lookup);
+ continue;
+ }
+
+
+ if (!query_one(lookup, lookup_type)) {
+ rc = 1;
+ d_printf( "name_query failed to find name %s", lookup );
+ if( 0 != lookup_type ) {
+ d_printf( "#%02x", lookup_type );
+ }
+ d_printf( "\n" );
+ }
+ }
+
+out:
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ return rc;
+}
diff --git a/source3/utils/ntlm_auth.c b/source3/utils/ntlm_auth.c
new file mode 100644
index 0000000..6660a31
--- /dev/null
+++ b/source3/utils/ntlm_auth.c
@@ -0,0 +1,2856 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind status program.
+
+ Copyright (C) Tim Potter 2000-2003
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2004
+ Copyright (C) Francesco Chemolli <kinkie@kame.usr.dsi.unimi.it> 2000
+ Copyright (C) Robert O'Callahan 2006 (added cached credential code).
+ Copyright (C) Kai Blin <kai@samba.org> 2008
+ Copyright (C) Simo Sorce 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/param/param.h"
+#include "lib/cmdline/cmdline.h"
+#include "libcli/security/security.h"
+#include "utils/ntlm_auth.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "auth/ntlmssp/ntlmssp.h"
+#include "auth/gensec/gensec.h"
+#include "auth/gensec/gensec_internal.h"
+#include "auth/credentials/credentials.h"
+#include "librpc/crypto/gse.h"
+#include "smb_krb5.h"
+#include "lib/util/tiniparser.h"
+#include "librpc/gen_ndr/krb5pac.h"
+#include "auth/common_auth.h"
+#include "source3/include/auth.h"
+#include "source3/auth/proto.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "nsswitch/winbind_struct_protocol.h"
+#include "nsswitch/libwbclient/wbclient_internal.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/base64.h"
+#include "cmdline_contexts.h"
+#include "lib/util/tevent_ntstatus.h"
+#include "lib/util/string_wrappers.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#ifdef HAVE_KRB5
+#include "auth/kerberos/pac_utils.h"
+#endif
+
+#ifndef PAM_WINBIND_CONFIG_FILE
+#define PAM_WINBIND_CONFIG_FILE "/etc/security/pam_winbind.conf"
+#endif
+
+#define WINBIND_KRB5_AUTH 0x00000080
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define INITIAL_BUFFER_SIZE 300
+#define MAX_BUFFER_SIZE 630000
+
+enum stdio_helper_mode {
+ SQUID_2_4_BASIC,
+ SQUID_2_5_BASIC,
+ SQUID_2_5_NTLMSSP,
+ NTLMSSP_CLIENT_1,
+ GSS_SPNEGO_SERVER,
+ GSS_SPNEGO_CLIENT,
+ NTLM_SERVER_1,
+ NTLM_CHANGE_PASSWORD_1,
+ NUM_HELPER_MODES
+};
+
+enum ntlm_auth_cli_state {
+ CLIENT_INITIAL = 0,
+ CLIENT_RESPONSE,
+ CLIENT_FINISHED,
+ CLIENT_ERROR
+};
+
+struct ntlm_auth_state {
+ TALLOC_CTX *mem_ctx;
+ enum stdio_helper_mode helper_mode;
+ enum ntlm_auth_cli_state cli_state;
+ struct ntlmssp_state *ntlmssp_state;
+ uint32_t neg_flags;
+ char *want_feature_list;
+ bool have_session_key;
+ DATA_BLOB session_key;
+ DATA_BLOB initial_message;
+ void *gensec_private_1;
+};
+typedef void (*stdio_helper_function)(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state, char *buf,
+ int length, void **private2);
+
+static void manage_gensec_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ char *buf, int length, void **private1);
+
+static void manage_squid_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ stdio_helper_function fn, void **private2);
+
+static void manage_squid_basic_request (enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2);
+
+static void manage_squid_ntlmssp_request (enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2);
+
+static void manage_client_ntlmssp_request (enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2);
+
+static void manage_gss_spnego_request (enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2);
+
+static void manage_gss_spnego_client_request (enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2);
+
+static void manage_ntlm_server_1_request (enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2);
+
+static void manage_ntlm_change_password_1_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2);
+
+static const struct {
+ enum stdio_helper_mode mode;
+ const char *name;
+ stdio_helper_function fn;
+} stdio_helper_protocols[] = {
+ { SQUID_2_4_BASIC, "squid-2.4-basic", manage_squid_basic_request},
+ { SQUID_2_5_BASIC, "squid-2.5-basic", manage_squid_basic_request},
+ { SQUID_2_5_NTLMSSP, "squid-2.5-ntlmssp", manage_squid_ntlmssp_request},
+ { NTLMSSP_CLIENT_1, "ntlmssp-client-1", manage_client_ntlmssp_request},
+ { GSS_SPNEGO_SERVER, "gss-spnego", manage_gss_spnego_request},
+ { GSS_SPNEGO_CLIENT, "gss-spnego-client", manage_gss_spnego_client_request},
+ { NTLM_SERVER_1, "ntlm-server-1", manage_ntlm_server_1_request},
+ { NTLM_CHANGE_PASSWORD_1, "ntlm-change-password-1", manage_ntlm_change_password_1_request},
+ { NUM_HELPER_MODES, NULL, NULL}
+};
+
+const char *opt_username;
+const char *opt_domain;
+const char *opt_workstation;
+const char *opt_password;
+static DATA_BLOB opt_challenge;
+static DATA_BLOB opt_lm_response;
+static DATA_BLOB opt_nt_response;
+static int request_lm_key;
+static int request_user_session_key;
+static int use_cached_creds;
+static int offline_logon;
+static int opt_allow_mschapv2;
+
+static const char *require_membership_of;
+static const char *require_membership_of_sid;
+static const char *opt_pam_winbind_conf;
+
+const char *opt_target_service;
+const char *opt_target_hostname;
+
+
+/* This is a bit hairy, but the basic idea is to do a password callback
+ to the calling application. The callback comes from within gensec */
+
+static void manage_gensec_get_pw_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state, char *buf, int length,
+ void **password)
+{
+ DATA_BLOB in;
+ if (strlen(buf) < 2) {
+ DEBUG(1, ("query [%s] invalid\n", buf));
+ printf("BH Query invalid\n");
+ return;
+ }
+
+ if (strlen(buf) > 3) {
+ in = base64_decode_data_blob(buf + 3);
+ } else {
+ in = data_blob(NULL, 0);
+ }
+
+ if (strncmp(buf, "PW ", 3) == 0) {
+
+ *password = talloc_strndup(NULL,
+ (const char *)in.data, in.length);
+
+ if (*password == NULL) {
+ DEBUG(1, ("Out of memory\n"));
+ printf("BH Out of memory\n");
+ data_blob_free(&in);
+ return;
+ }
+
+ printf("OK\n");
+ data_blob_free(&in);
+ return;
+ }
+ DEBUG(1, ("Asked for (and expected) a password\n"));
+ printf("BH Expected a password\n");
+ data_blob_free(&in);
+}
+
+/**
+ * Callback for password credentials. This is not async, and when
+ * GENSEC and the credentials code is made async, it will look rather
+ * different.
+ */
+
+static const char *get_password(struct cli_credentials *credentials)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *password = NULL;
+ struct ntlm_auth_state *state;
+
+ state = talloc_zero(frame, struct ntlm_auth_state);
+ if (state == NULL) {
+ DEBUG(0, ("squid_stream: Failed to talloc ntlm_auth_state\n"));
+ fprintf(stderr, "ERR\n");
+ exit(1);
+ }
+
+ state->mem_ctx = state;
+
+ /* Ask for a password */
+ printf("PW\n");
+
+ manage_squid_request(NUM_HELPER_MODES /* bogus */, NULL, state, manage_gensec_get_pw_request, (void **)&password);
+ talloc_steal(credentials, password);
+ TALLOC_FREE(frame);
+ return password;
+}
+
+/**
+ * A limited set of features are defined with text strings as needed
+ * by ntlm_auth
+ *
+ */
+static void gensec_want_feature_list(struct gensec_security *state, char* feature_list)
+{
+ if (in_list("NTLMSSP_FEATURE_SESSION_KEY", feature_list, true)) {
+ DEBUG(10, ("want GENSEC_FEATURE_SESSION_KEY\n"));
+ gensec_want_feature(state, GENSEC_FEATURE_SESSION_KEY);
+ }
+ if (in_list("NTLMSSP_FEATURE_SIGN", feature_list, true)) {
+ DEBUG(10, ("want GENSEC_FEATURE_SIGN\n"));
+ gensec_want_feature(state, GENSEC_FEATURE_SIGN);
+ }
+ if (in_list("NTLMSSP_FEATURE_SEAL", feature_list, true)) {
+ DEBUG(10, ("want GENSEC_FEATURE_SEAL\n"));
+ gensec_want_feature(state, GENSEC_FEATURE_SEAL);
+ }
+ if (in_list("NTLMSSP_FEATURE_CCACHE", feature_list, true)) {
+ DEBUG(10, ("want GENSEC_FEATURE_NTLM_CCACHE\n"));
+ gensec_want_feature(state, GENSEC_FEATURE_NTLM_CCACHE);
+ }
+}
+
+static char winbind_separator(void)
+{
+ struct wbcInterfaceDetails *details;
+ wbcErr ret;
+ static bool got_sep;
+ static char sep;
+
+ if (got_sep)
+ return sep;
+
+ ret = wbcInterfaceDetails(&details);
+ if (!WBC_ERROR_IS_OK(ret)) {
+ d_fprintf(stderr, "could not obtain winbind separator!\n");
+ return *lp_winbind_separator();
+ }
+
+ sep = details->winbind_separator;
+
+ wbcFreeMemory(details);
+
+ got_sep = True;
+
+ if (!sep) {
+ d_fprintf(stderr, "winbind separator was NULL!\n");
+ return *lp_winbind_separator();
+ }
+
+ return sep;
+}
+
+const char *get_winbind_domain(void)
+{
+ struct wbcInterfaceDetails *details;
+ wbcErr ret;
+
+ static fstring winbind_domain;
+ if (*winbind_domain) {
+ return winbind_domain;
+ }
+
+ /* Send off request */
+
+ ret = wbcInterfaceDetails(&details);
+ if (!WBC_ERROR_IS_OK(ret)) {
+ DEBUG(1, ("could not obtain winbind domain name!\n"));
+ return lp_workgroup();
+ }
+
+ fstrcpy(winbind_domain, details->netbios_domain);
+
+ wbcFreeMemory(details);
+
+ return winbind_domain;
+
+}
+
+const char *get_winbind_netbios_name(void)
+{
+ struct wbcInterfaceDetails *details;
+ wbcErr ret;
+
+ static fstring winbind_netbios_name;
+
+ if (*winbind_netbios_name) {
+ return winbind_netbios_name;
+ }
+
+ /* Send off request */
+
+ ret = wbcInterfaceDetails(&details);
+ if (!WBC_ERROR_IS_OK(ret)) {
+ DEBUG(1, ("could not obtain winbind netbios name!\n"));
+ return lp_netbios_name();
+ }
+
+ fstrcpy(winbind_netbios_name, details->netbios_name);
+
+ wbcFreeMemory(details);
+
+ return winbind_netbios_name;
+
+}
+
+DATA_BLOB get_challenge(void)
+{
+ static DATA_BLOB chal;
+ if (opt_challenge.length)
+ return opt_challenge;
+
+ chal = data_blob(NULL, 8);
+
+ generate_random_buffer(chal.data, chal.length);
+ return chal;
+}
+
+/* Copy of parse_domain_user from winbindd_util.c. Parse a string of the
+ form DOMAIN/user into a domain and a user */
+
+static bool parse_ntlm_auth_domain_user(const char *domuser, fstring domain,
+ fstring user)
+{
+
+ char *p = strchr(domuser,winbind_separator());
+
+ if (!p) {
+ return False;
+ }
+
+ fstrcpy(user, p+1);
+ fstrcpy(domain, domuser);
+ domain[PTR_DIFF(p, domuser)] = 0;
+ return strupper_m(domain);
+}
+
+static bool get_require_membership_sid(void) {
+ fstring domain, name, sidbuf;
+ struct wbcDomainSid sid;
+ enum wbcSidType type;
+ wbcErr ret;
+
+ if (!require_membership_of) {
+ return True;
+ }
+
+ if (require_membership_of_sid) {
+ return True;
+ }
+
+ /* Otherwise, ask winbindd for the name->sid request */
+
+ if (!parse_ntlm_auth_domain_user(require_membership_of,
+ domain, name)) {
+ DEBUG(0, ("Could not parse %s into separate domain/name parts!\n",
+ require_membership_of));
+ return False;
+ }
+
+ ret = wbcLookupName(domain, name, &sid, &type);
+ if (!WBC_ERROR_IS_OK(ret)) {
+ DEBUG(0, ("Winbindd lookupname failed to resolve %s into a SID!\n",
+ require_membership_of));
+ return False;
+ }
+
+ wbcSidToStringBuf(&sid, sidbuf, sizeof(sidbuf));
+
+ require_membership_of_sid = SMB_STRDUP(sidbuf);
+
+ if (require_membership_of_sid)
+ return True;
+
+ return False;
+}
+
+/*
+ * Get some configuration from pam_winbind.conf to see if we
+ * need to contact trusted domain
+ */
+
+int get_pam_winbind_config(void)
+{
+ int ctrl = 0;
+ struct tiniparser_dictionary *d = NULL;
+
+ if (!opt_pam_winbind_conf || !*opt_pam_winbind_conf) {
+ opt_pam_winbind_conf = PAM_WINBIND_CONFIG_FILE;
+ }
+
+ d = tiniparser_load(opt_pam_winbind_conf);
+
+ if (!d) {
+ return 0;
+ }
+
+ if (tiniparser_getboolean(d, "global:krb5_auth", false)) {
+ ctrl |= WINBIND_KRB5_AUTH;
+ }
+
+ tiniparser_freedict(d);
+
+ return ctrl;
+}
+
+/* Authenticate a user with a plaintext password */
+
+static bool check_plaintext_auth(const char *user, const char *pass,
+ bool stdout_diagnostics)
+{
+ struct winbindd_request request;
+ struct winbindd_response response;
+ wbcErr ret;
+
+ if (!get_require_membership_sid()) {
+ return False;
+ }
+
+ /* Send off request */
+
+ ZERO_STRUCT(request);
+ ZERO_STRUCT(response);
+
+ fstrcpy(request.data.auth.user, user);
+ fstrcpy(request.data.auth.pass, pass);
+ if (require_membership_of_sid) {
+ strlcpy(request.data.auth.require_membership_of_sid,
+ require_membership_of_sid,
+ sizeof(request.data.auth.require_membership_of_sid));
+ }
+
+ if (offline_logon) {
+ request.flags |= WBFLAG_PAM_CACHED_LOGIN;
+ }
+
+ ret = wbcRequestResponse(NULL, WINBINDD_PAM_AUTH,
+ &request, &response);
+
+ /* Display response */
+
+ if (stdout_diagnostics) {
+ if (!WBC_ERROR_IS_OK(ret) && (response.data.auth.nt_status == 0)) {
+ d_fprintf(stderr, "Reading winbind reply failed! (0x01)\n");
+ }
+
+ d_printf("%s: %s (0x%x)\n",
+ response.data.auth.nt_status_string,
+ response.data.auth.error_string,
+ response.data.auth.nt_status);
+ } else {
+ if (!WBC_ERROR_IS_OK(ret) && (response.data.auth.nt_status == 0)) {
+ DEBUG(1, ("Reading winbind reply failed! (0x01)\n"));
+ }
+
+ DEBUG(3, ("%s: %s (0x%x)\n",
+ response.data.auth.nt_status_string,
+ response.data.auth.error_string,
+ response.data.auth.nt_status));
+ }
+
+ return WBC_ERROR_IS_OK(ret);
+}
+
+/* authenticate a user with an encrypted username/password */
+
+NTSTATUS contact_winbind_auth_crap(const char *username,
+ const char *domain,
+ const char *workstation,
+ const DATA_BLOB *challenge,
+ const DATA_BLOB *lm_response,
+ const DATA_BLOB *nt_response,
+ uint32_t flags,
+ uint32_t extra_logon_parameters,
+ uint8_t lm_key[8],
+ uint8_t user_session_key[16],
+ uint8_t *pauthoritative,
+ char **error_string,
+ char **unix_name)
+{
+ NTSTATUS nt_status;
+ wbcErr ret;
+ struct winbindd_request request;
+ struct winbindd_response response;
+
+ *pauthoritative = 1;
+
+ if (!get_require_membership_sid()) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(request);
+ ZERO_STRUCT(response);
+
+ request.flags = flags;
+
+ request.data.auth_crap.logon_parameters = extra_logon_parameters
+ | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT | MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT;
+
+ if (opt_allow_mschapv2) {
+ request.data.auth_crap.logon_parameters |= MSV1_0_ALLOW_MSVCHAPV2;
+ }
+
+ if (require_membership_of_sid)
+ fstrcpy(request.data.auth_crap.require_membership_of_sid, require_membership_of_sid);
+
+ fstrcpy(request.data.auth_crap.user, username);
+ fstrcpy(request.data.auth_crap.domain, domain);
+
+ fstrcpy(request.data.auth_crap.workstation,
+ workstation);
+
+ memcpy(request.data.auth_crap.chal, challenge->data, MIN(challenge->length, 8));
+
+ if (lm_response && lm_response->length) {
+ size_t capped_lm_response_len = MIN(
+ lm_response->length,
+ sizeof(request.data.auth_crap.lm_resp));
+
+ memcpy(request.data.auth_crap.lm_resp,
+ lm_response->data,
+ capped_lm_response_len);
+ request.data.auth_crap.lm_resp_len = capped_lm_response_len;
+ }
+
+ if (nt_response && nt_response->length) {
+ if (nt_response->length > sizeof(request.data.auth_crap.nt_resp)) {
+ request.flags = request.flags | WBFLAG_BIG_NTLMV2_BLOB;
+ request.extra_len = nt_response->length;
+ request.extra_data.data = SMB_MALLOC_ARRAY(char, request.extra_len);
+ if (request.extra_data.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memcpy(request.extra_data.data, nt_response->data,
+ nt_response->length);
+
+ } else {
+ memcpy(request.data.auth_crap.nt_resp,
+ nt_response->data, nt_response->length);
+ }
+ request.data.auth_crap.nt_resp_len = nt_response->length;
+ }
+
+ ret = wbcRequestResponsePriv(
+ NULL,
+ WINBINDD_PAM_AUTH_CRAP,
+ &request,
+ &response);
+ SAFE_FREE(request.extra_data.data);
+
+ /* Display response */
+
+ if (!WBC_ERROR_IS_OK(ret) && (response.data.auth.nt_status == 0)) {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ if (error_string)
+ *error_string = smb_xstrdup("Reading winbind reply failed!");
+ winbindd_free_response(&response);
+ return nt_status;
+ }
+
+ nt_status = (NT_STATUS(response.data.auth.nt_status));
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ if (error_string)
+ *error_string = smb_xstrdup(response.data.auth.error_string);
+ *pauthoritative = response.data.auth.authoritative;
+ winbindd_free_response(&response);
+ return nt_status;
+ }
+
+ if ((flags & WBFLAG_PAM_LMKEY) && lm_key) {
+ memcpy(lm_key, response.data.auth.first_8_lm_hash,
+ sizeof(response.data.auth.first_8_lm_hash));
+ }
+ if ((flags & WBFLAG_PAM_USER_SESSION_KEY) && user_session_key) {
+ memcpy(user_session_key, response.data.auth.user_session_key,
+ sizeof(response.data.auth.user_session_key));
+ }
+
+ if (flags & WBFLAG_PAM_UNIX_NAME) {
+ *unix_name = SMB_STRDUP(response.data.auth.unix_username);
+ if (!*unix_name) {
+ winbindd_free_response(&response);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ winbindd_free_response(&response);
+ return nt_status;
+}
+
+/* contact server to change user password using auth crap */
+static NTSTATUS contact_winbind_change_pswd_auth_crap(const char *username,
+ const char *domain,
+ const DATA_BLOB new_nt_pswd,
+ const DATA_BLOB old_nt_hash_enc,
+ const DATA_BLOB new_lm_pswd,
+ const DATA_BLOB old_lm_hash_enc,
+ char **error_string)
+{
+ NTSTATUS nt_status;
+ wbcErr ret;
+ struct winbindd_request request;
+ struct winbindd_response response;
+
+ if (!get_require_membership_sid())
+ {
+ if(error_string)
+ *error_string = smb_xstrdup("Can't get membership sid.");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(request);
+ ZERO_STRUCT(response);
+
+ if(username != NULL)
+ fstrcpy(request.data.chng_pswd_auth_crap.user, username);
+ if(domain != NULL)
+ fstrcpy(request.data.chng_pswd_auth_crap.domain,domain);
+
+ if(new_nt_pswd.length)
+ {
+ memcpy(request.data.chng_pswd_auth_crap.new_nt_pswd, new_nt_pswd.data, sizeof(request.data.chng_pswd_auth_crap.new_nt_pswd));
+ request.data.chng_pswd_auth_crap.new_nt_pswd_len = new_nt_pswd.length;
+ }
+
+ if(old_nt_hash_enc.length)
+ {
+ memcpy(request.data.chng_pswd_auth_crap.old_nt_hash_enc, old_nt_hash_enc.data, sizeof(request.data.chng_pswd_auth_crap.old_nt_hash_enc));
+ request.data.chng_pswd_auth_crap.old_nt_hash_enc_len = old_nt_hash_enc.length;
+ }
+
+ if(new_lm_pswd.length)
+ {
+ memcpy(request.data.chng_pswd_auth_crap.new_lm_pswd, new_lm_pswd.data, sizeof(request.data.chng_pswd_auth_crap.new_lm_pswd));
+ request.data.chng_pswd_auth_crap.new_lm_pswd_len = new_lm_pswd.length;
+ }
+
+ if(old_lm_hash_enc.length)
+ {
+ memcpy(request.data.chng_pswd_auth_crap.old_lm_hash_enc, old_lm_hash_enc.data, sizeof(request.data.chng_pswd_auth_crap.old_lm_hash_enc));
+ request.data.chng_pswd_auth_crap.old_lm_hash_enc_len = old_lm_hash_enc.length;
+ }
+
+ ret = wbcRequestResponse(NULL, WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP,
+ &request, &response);
+
+ /* Display response */
+
+ if (!WBC_ERROR_IS_OK(ret) && (response.data.auth.nt_status == 0))
+ {
+ nt_status = NT_STATUS_UNSUCCESSFUL;
+ if (error_string)
+ *error_string = smb_xstrdup("Reading winbind reply failed!");
+ winbindd_free_response(&response);
+ return nt_status;
+ }
+
+ nt_status = (NT_STATUS(response.data.auth.nt_status));
+ if (!NT_STATUS_IS_OK(nt_status))
+ {
+ if (error_string)
+ *error_string = smb_xstrdup(response.data.auth.error_string);
+ winbindd_free_response(&response);
+ return nt_status;
+ }
+
+ winbindd_free_response(&response);
+
+ return nt_status;
+}
+
+/*
+ * This function does not create a full auth_session_info, just enough
+ * for the caller to get the "unix" username
+ */
+static NTSTATUS ntlm_auth_generate_session_info(struct auth4_context *auth_context,
+ TALLOC_CTX *mem_ctx,
+ void *server_returned_info,
+ const char *original_user_name,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info_out)
+{
+ const char *unix_username = (const char *)server_returned_info;
+ struct dom_sid *sids = NULL;
+ struct auth_session_info *session_info = NULL;
+
+ session_info = talloc_zero(mem_ctx, struct auth_session_info);
+ if (session_info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ session_info->unix_info = talloc_zero(session_info, struct auth_user_info_unix);
+ if (session_info->unix_info == NULL) {
+ TALLOC_FREE(session_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ session_info->unix_info->unix_name = talloc_strdup(session_info->unix_info,
+ unix_username);
+ if (session_info->unix_info->unix_name == NULL) {
+ TALLOC_FREE(session_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * This is not a full session_info - it is not created
+ * correctly and misses any claims etc, because all we
+ * actually use in the caller is the unix username.
+ *
+ * Therefore so no claims need to be added and
+ * se_access_check() will never run.
+ */
+ session_info->security_token
+ = security_token_initialise(talloc_tos(),
+ CLAIMS_EVALUATION_INVALID_STATE);
+ if (session_info->security_token == NULL) {
+ TALLOC_FREE(session_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sids = talloc_zero_array(session_info->security_token,
+ struct dom_sid, 3);
+ if (sids == NULL) {
+ TALLOC_FREE(session_info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ sid_copy(&sids[0], &global_sid_World);
+ sid_copy(&sids[1], &global_sid_Network);
+ sid_copy(&sids[2], &global_sid_Authenticated_Users);
+
+ session_info->security_token->num_sids = talloc_array_length(sids);
+ session_info->security_token->sids = sids;
+
+ *session_info_out = session_info;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ntlm_auth_generate_session_info_pac(struct auth4_context *auth_ctx,
+ TALLOC_CTX *mem_ctx,
+ struct smb_krb5_context *smb_krb5_context,
+ DATA_BLOB *pac_blob,
+ const char *princ_name,
+ const struct tsocket_address *remote_address,
+ uint32_t session_info_flags,
+ struct auth_session_info **session_info)
+{
+ TALLOC_CTX *tmp_ctx;
+ struct PAC_LOGON_INFO *logon_info = NULL;
+ char *unixuser;
+ NTSTATUS status;
+ const char *domain = "";
+ const char *user = "";
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (pac_blob) {
+#ifdef HAVE_KRB5
+ status = kerberos_pac_logon_info(tmp_ctx, *pac_blob, NULL, NULL,
+ NULL, NULL, 0, &logon_info);
+#else
+ status = NT_STATUS_ACCESS_DENIED;
+#endif
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ } else {
+ status = NT_STATUS_ACCESS_DENIED;
+ DBG_WARNING("Kerberos ticket for[%s] has no PAC: %s\n",
+ princ_name, nt_errstr(status));
+ goto done;
+ }
+
+ if (logon_info->info3.base.account_name.string != NULL) {
+ user = logon_info->info3.base.account_name.string;
+ } else {
+ user = "";
+ }
+ if (logon_info->info3.base.logon_domain.string != NULL) {
+ domain = logon_info->info3.base.logon_domain.string;
+ } else {
+ domain = "";
+ }
+
+ if (strlen(user) == 0 || strlen(domain) == 0) {
+ status = NT_STATUS_ACCESS_DENIED;
+ DBG_WARNING("Kerberos ticket for[%s] has invalid "
+ "account_name[%s]/logon_domain[%s]: %s\n",
+ princ_name,
+ logon_info->info3.base.account_name.string,
+ logon_info->info3.base.logon_domain.string,
+ nt_errstr(status));
+ goto done;
+ }
+
+ DBG_NOTICE("Kerberos ticket principal name is [%s] "
+ "account_name[%s]/logon_domain[%s]\n",
+ princ_name, user, domain);
+
+ if (!strequal(domain, lp_workgroup())) {
+ if (!lp_allow_trusted_domains()) {
+ status = NT_STATUS_LOGON_FAILURE;
+ goto done;
+ }
+ }
+
+ unixuser = talloc_asprintf(tmp_ctx, "%s%c%s", domain, winbind_separator(), user);
+ if (!unixuser) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = ntlm_auth_generate_session_info(auth_ctx, mem_ctx, unixuser, NULL, session_info_flags, session_info);
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+
+
+/**
+ * Return the challenge as determined by the authentication subsystem
+ * @return an 8 byte random challenge
+ */
+
+static NTSTATUS ntlm_auth_get_challenge(struct auth4_context *auth_ctx,
+ uint8_t chal[8])
+{
+ if (auth_ctx->challenge.data.length == 8) {
+ DEBUG(5, ("auth_get_challenge: returning previous challenge by module %s (normal)\n",
+ auth_ctx->challenge.set_by));
+ memcpy(chal, auth_ctx->challenge.data.data, 8);
+ return NT_STATUS_OK;
+ }
+
+ if (!auth_ctx->challenge.set_by) {
+ generate_random_buffer(chal, 8);
+
+ auth_ctx->challenge.data = data_blob_talloc(auth_ctx, chal, 8);
+ NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.data.data);
+ auth_ctx->challenge.set_by = "random";
+ }
+
+ DEBUG(10,("auth_get_challenge: challenge set by %s\n",
+ auth_ctx->challenge.set_by));
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * NTLM2 authentication modifies the effective challenge,
+ * @param challenge The new challenge value
+ */
+static NTSTATUS ntlm_auth_set_challenge(struct auth4_context *auth_ctx, const uint8_t chal[8], const char *set_by)
+{
+ auth_ctx->challenge.set_by = talloc_strdup(auth_ctx, set_by);
+ NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.set_by);
+
+ auth_ctx->challenge.data = data_blob_talloc(auth_ctx, chal, 8);
+ NT_STATUS_HAVE_NO_MEMORY(auth_ctx->challenge.data.data);
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Check the password on an NTLMSSP login.
+ *
+ * Return the session keys used on the connection.
+ */
+
+struct winbind_pw_check_state {
+ uint8_t authoritative;
+ void *server_info;
+ DATA_BLOB nt_session_key;
+ DATA_BLOB lm_session_key;
+};
+
+static struct tevent_req *winbind_pw_check_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth4_context *auth4_context,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct winbind_pw_check_state *state = NULL;
+ NTSTATUS nt_status;
+ char *error_string = NULL;
+ uint8_t lm_key[8];
+ uint8_t user_sess_key[16];
+ char *unix_name = NULL;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct winbind_pw_check_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ nt_status = contact_winbind_auth_crap(
+ user_info->client.account_name,
+ user_info->client.domain_name,
+ user_info->workstation_name,
+ &auth4_context->challenge.data,
+ &user_info->password.response.lanman,
+ &user_info->password.response.nt,
+ WBFLAG_PAM_LMKEY |
+ WBFLAG_PAM_USER_SESSION_KEY |
+ WBFLAG_PAM_UNIX_NAME,
+ 0,
+ lm_key, user_sess_key,
+ &state->authoritative,
+ &error_string,
+ &unix_name);
+
+ if (tevent_req_nterror(req, nt_status)) {
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCESS_DENIED)) {
+ DBG_ERR("Login for user [%s]\\[%s]@[%s] failed due "
+ "to [%s]\n",
+ user_info->client.domain_name,
+ user_info->client.account_name,
+ user_info->workstation_name,
+ error_string ?
+ error_string :
+ "unknown error (NULL)");
+ } else {
+ DBG_NOTICE("Login for user [%s]\\[%s]@[%s] failed due "
+ "to [%s]\n",
+ user_info->client.domain_name,
+ user_info->client.account_name,
+ user_info->workstation_name,
+ error_string ?
+ error_string :
+ "unknown error (NULL)");
+ }
+ goto done;
+ }
+
+ if (!all_zero(lm_key, 8)) {
+ state->lm_session_key = data_blob_talloc(state, NULL, 16);
+ if (tevent_req_nomem(state->lm_session_key.data, req)) {
+ goto done;
+ }
+ memcpy(state->lm_session_key.data, lm_key, 8);
+ memset(state->lm_session_key.data+8, '\0', 8);
+ }
+ if (!all_zero(user_sess_key, 16)) {
+ state->nt_session_key = data_blob_talloc(
+ state, user_sess_key, 16);
+ if (tevent_req_nomem(state->nt_session_key.data, req)) {
+ goto done;
+ }
+ }
+ state->server_info = talloc_strdup(state, unix_name);
+ if (tevent_req_nomem(state->server_info, req)) {
+ goto done;
+ }
+ tevent_req_done(req);
+
+done:
+ SAFE_FREE(error_string);
+ SAFE_FREE(unix_name);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS winbind_pw_check_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *pauthoritative,
+ void **server_returned_info,
+ DATA_BLOB *nt_session_key,
+ DATA_BLOB *lm_session_key)
+{
+ struct winbind_pw_check_state *state = tevent_req_data(
+ req, struct winbind_pw_check_state);
+ NTSTATUS status;
+
+ if (pauthoritative != NULL) {
+ *pauthoritative = state->authoritative;
+ }
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (server_returned_info != NULL) {
+ *server_returned_info = talloc_move(
+ mem_ctx, &state->server_info);
+ }
+ if (nt_session_key != NULL) {
+ *nt_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ mem_ctx, &state->nt_session_key.data),
+ .length = state->nt_session_key.length,
+ };
+ }
+ if (lm_session_key != NULL) {
+ *lm_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ mem_ctx, &state->lm_session_key.data),
+ .length = state->lm_session_key.length,
+ };
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct local_pw_check_state {
+ uint8_t authoritative;
+ void *server_info;
+ DATA_BLOB nt_session_key;
+ DATA_BLOB lm_session_key;
+};
+
+static struct tevent_req *local_pw_check_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct auth4_context *auth4_context,
+ const struct auth_usersupplied_info *user_info)
+{
+ struct tevent_req *req = NULL;
+ struct local_pw_check_state *state = NULL;
+ struct samr_Password lm_pw, nt_pw;
+ NTSTATUS nt_status;
+
+ req = tevent_req_create(
+ mem_ctx, &state, struct local_pw_check_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->authoritative = 1;
+
+ nt_lm_owf_gen (opt_password, nt_pw.hash, lm_pw.hash);
+
+ nt_status = ntlm_password_check(
+ state,
+ true,
+ NTLM_AUTH_ON,
+ 0,
+ &auth4_context->challenge.data,
+ &user_info->password.response.lanman,
+ &user_info->password.response.nt,
+ user_info->client.account_name,
+ user_info->client.account_name,
+ user_info->client.domain_name,
+ &lm_pw,
+ &nt_pw,
+ &state->nt_session_key,
+ &state->lm_session_key);
+
+ if (tevent_req_nterror(req, nt_status)) {
+ DBG_NOTICE("Login for user [%s]\\[%s]@[%s] failed due to "
+ "[%s]\n",
+ user_info->client.domain_name,
+ user_info->client.account_name,
+ user_info->workstation_name,
+ nt_errstr(nt_status));
+ return tevent_req_post(req, ev);
+ }
+
+ state->server_info = talloc_asprintf(
+ state,
+ "%s%c%s",
+ user_info->client.domain_name,
+ *lp_winbind_separator(),
+ user_info->client.account_name);
+ if (tevent_req_nomem(state->server_info, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS local_pw_check_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t *pauthoritative,
+ void **server_returned_info,
+ DATA_BLOB *nt_session_key,
+ DATA_BLOB *lm_session_key)
+{
+ struct local_pw_check_state *state = tevent_req_data(
+ req, struct local_pw_check_state);
+ NTSTATUS status;
+
+ if (pauthoritative != NULL) {
+ *pauthoritative = state->authoritative;
+ }
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (server_returned_info != NULL) {
+ *server_returned_info = talloc_move(
+ mem_ctx, &state->server_info);
+ }
+ if (nt_session_key != NULL) {
+ *nt_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ mem_ctx, &state->nt_session_key.data),
+ .length = state->nt_session_key.length,
+ };
+ }
+ if (lm_session_key != NULL) {
+ *lm_session_key = (DATA_BLOB) {
+ .data = talloc_move(
+ mem_ctx, &state->lm_session_key.data),
+ .length = state->lm_session_key.length,
+ };
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS ntlm_auth_prepare_gensec_client(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct gensec_security **gensec_security_out)
+{
+ struct gensec_security *gensec_security = NULL;
+ NTSTATUS nt_status;
+ TALLOC_CTX *tmp_ctx;
+ const struct gensec_security_ops **backends = NULL;
+ struct gensec_settings *gensec_settings = NULL;
+ size_t idx = 0;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ gensec_settings = lpcfg_gensec_settings(tmp_ctx, lp_ctx);
+ if (gensec_settings == NULL) {
+ DEBUG(10, ("lpcfg_gensec_settings failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ backends = talloc_zero_array(gensec_settings,
+ const struct gensec_security_ops *, 4);
+ if (backends == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ gensec_settings->backends = backends;
+
+ gensec_init();
+
+ /* These need to be in priority order, krb5 before NTLMSSP */
+#if defined(HAVE_KRB5)
+ backends[idx++] = &gensec_gse_krb5_security_ops;
+#endif
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_NTLMSSP);
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_SPNEGO);
+
+ nt_status = gensec_client_start(NULL, &gensec_security,
+ gensec_settings);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ talloc_unlink(tmp_ctx, gensec_settings);
+
+ if (opt_target_service != NULL) {
+ nt_status = gensec_set_target_service(gensec_security,
+ opt_target_service);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+ }
+
+ if (opt_target_hostname != NULL) {
+ nt_status = gensec_set_target_hostname(gensec_security,
+ opt_target_hostname);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+ }
+
+ *gensec_security_out = talloc_steal(mem_ctx, gensec_security);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static struct auth4_context *make_auth4_context_ntlm_auth(TALLOC_CTX *mem_ctx, bool local_pw)
+{
+ struct auth4_context *auth4_context = talloc_zero(mem_ctx, struct auth4_context);
+ if (auth4_context == NULL) {
+ DEBUG(10, ("failed to allocate auth4_context\n"));
+ return NULL;
+ }
+ auth4_context->generate_session_info = ntlm_auth_generate_session_info;
+ auth4_context->generate_session_info_pac = ntlm_auth_generate_session_info_pac;
+ auth4_context->get_ntlm_challenge = ntlm_auth_get_challenge;
+ auth4_context->set_ntlm_challenge = ntlm_auth_set_challenge;
+ if (local_pw) {
+ auth4_context->check_ntlm_password_send = local_pw_check_send;
+ auth4_context->check_ntlm_password_recv = local_pw_check_recv;
+ } else {
+ auth4_context->check_ntlm_password_send =
+ winbind_pw_check_send;
+ auth4_context->check_ntlm_password_recv =
+ winbind_pw_check_recv;
+ }
+ auth4_context->private_data = NULL;
+ return auth4_context;
+}
+
+static NTSTATUS ntlm_auth_prepare_gensec_server(TALLOC_CTX *mem_ctx,
+ struct loadparm_context *lp_ctx,
+ struct gensec_security **gensec_security_out)
+{
+ struct gensec_security *gensec_security;
+ NTSTATUS nt_status;
+
+ TALLOC_CTX *tmp_ctx;
+ const struct gensec_security_ops **backends;
+ struct gensec_settings *gensec_settings;
+ size_t idx = 0;
+ struct cli_credentials *server_credentials;
+
+ struct auth4_context *auth4_context;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ NT_STATUS_HAVE_NO_MEMORY(tmp_ctx);
+
+ auth4_context = make_auth4_context_ntlm_auth(tmp_ctx, opt_password);
+ if (auth4_context == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ gensec_settings = lpcfg_gensec_settings(tmp_ctx, lp_ctx);
+ if (lp_ctx == NULL) {
+ DEBUG(10, ("lpcfg_gensec_settings failed\n"));
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * This should be a 'netbios domain -> DNS domain'
+ * mapping, and can currently validly return NULL on
+ * poorly configured systems.
+ *
+ * This is used for the NTLMSSP server
+ *
+ */
+ if (opt_password) {
+ gensec_settings->server_netbios_name = lp_netbios_name();
+ gensec_settings->server_netbios_domain = lp_workgroup();
+ } else {
+ gensec_settings->server_netbios_name = get_winbind_netbios_name();
+ gensec_settings->server_netbios_domain = get_winbind_domain();
+ }
+
+ gensec_settings->server_dns_domain = strlower_talloc(gensec_settings,
+ get_mydnsdomname(talloc_tos()));
+ gensec_settings->server_dns_name = strlower_talloc(gensec_settings,
+ get_mydnsfullname());
+
+ backends = talloc_zero_array(gensec_settings,
+ const struct gensec_security_ops *, 4);
+
+ if (backends == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ gensec_settings->backends = backends;
+
+ gensec_init();
+
+ /* These need to be in priority order, krb5 before NTLMSSP */
+#if defined(HAVE_KRB5)
+ backends[idx++] = &gensec_gse_krb5_security_ops;
+#endif
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_NTLMSSP);
+
+ backends[idx++] = gensec_security_by_oid(NULL, GENSEC_OID_SPNEGO);
+
+ /*
+ * This is anonymous for now, because we just use it
+ * to set the kerberos state at the moment
+ */
+ server_credentials = cli_credentials_init_anon(tmp_ctx);
+ if (!server_credentials) {
+ DBG_ERR("Failed to init server credentials\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ cli_credentials_set_conf(server_credentials, lp_ctx);
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC || lp_security() == SEC_ADS || USE_KERBEROS_KEYTAB) {
+ cli_credentials_set_kerberos_state(server_credentials,
+ CRED_USE_KERBEROS_DESIRED,
+ CRED_SPECIFIED);
+ } else {
+ cli_credentials_set_kerberos_state(server_credentials,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+ }
+
+ nt_status = gensec_server_start(tmp_ctx, gensec_settings,
+ auth4_context, &gensec_security);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ gensec_set_credentials(gensec_security, server_credentials);
+
+ /*
+ * TODO: Allow the caller to pass their own description here
+ * via a command-line option
+ */
+ nt_status = gensec_set_target_service_description(gensec_security,
+ "ntlm_auth");
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ TALLOC_FREE(tmp_ctx);
+ return nt_status;
+ }
+
+ talloc_unlink(tmp_ctx, lp_ctx);
+ talloc_unlink(tmp_ctx, server_credentials);
+ talloc_unlink(tmp_ctx, gensec_settings);
+ talloc_unlink(tmp_ctx, auth4_context);
+
+ *gensec_security_out = talloc_steal(mem_ctx, gensec_security);
+ TALLOC_FREE(tmp_ctx);
+ return NT_STATUS_OK;
+}
+
+static void manage_client_ntlmssp_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2)
+{
+ manage_gensec_request(stdio_helper_mode, lp_ctx, buf, length, &state->gensec_private_1);
+ return;
+}
+
+static void manage_squid_basic_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2)
+{
+ char *user, *pass;
+ user=buf;
+
+ pass=(char *)memchr(buf,' ',length);
+ if (!pass) {
+ DEBUG(2, ("Password not found. Denying access\n"));
+ printf("ERR\n");
+ return;
+ }
+ *pass='\0';
+ pass++;
+
+ if (state->helper_mode == SQUID_2_5_BASIC) {
+ char *end = rfc1738_unescape(user);
+ if (end == NULL || (end - user) != strlen(user)) {
+ DEBUG(2, ("Badly rfc1738 encoded username: %s; "
+ "denying access\n", user));
+ printf("ERR\n");
+ return;
+ }
+ end = rfc1738_unescape(pass);
+ if (end == NULL || (end - pass) != strlen(pass)) {
+ DEBUG(2, ("Badly encoded password for %s; "
+ "denying access\n", user));
+ printf("ERR\n");
+ return;
+ }
+ }
+
+ if (check_plaintext_auth(user, pass, False)) {
+ printf("OK\n");
+ } else {
+ printf("ERR\n");
+ }
+}
+
+static void manage_gensec_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ char *buf, int length, void **private1)
+{
+ DATA_BLOB in;
+ DATA_BLOB out = data_blob(NULL, 0);
+ char *out_base64 = NULL;
+ const char *reply_arg = NULL;
+ struct gensec_ntlm_state {
+ struct gensec_security *gensec_state;
+ const char *set_password;
+ };
+ struct gensec_ntlm_state *state;
+
+ NTSTATUS nt_status;
+ bool first = false;
+ const char *reply_code;
+ struct cli_credentials *creds;
+
+ static char *want_feature_list = NULL;
+ static DATA_BLOB session_key;
+
+ TALLOC_CTX *mem_ctx;
+
+ mem_ctx = talloc_named(NULL, 0, "manage_gensec_request internal mem_ctx");
+ if (mem_ctx == NULL) {
+ printf("BH No Memory\n");
+ exit(1);
+ }
+
+ if (*private1) {
+ state = talloc_get_type(*private1, struct gensec_ntlm_state);
+ if (state == NULL) {
+ DBG_WARNING("*private1 is of type %s\n",
+ talloc_get_name(*private1));
+ printf("BH *private1 is of type %s\n",
+ talloc_get_name(*private1));
+ exit(1);
+ }
+ } else {
+ state = talloc_zero(NULL, struct gensec_ntlm_state);
+ if (!state) {
+ printf("BH No Memory\n");
+ exit(1);
+ }
+ *private1 = state;
+ if (opt_password) {
+ state->set_password = opt_password;
+ }
+ }
+
+ if (strlen(buf) < 2) {
+ DEBUG(1, ("query [%s] invalid\n", buf));
+ printf("BH Query invalid\n");
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ if (strlen(buf) > 3) {
+ if(strncmp(buf, "SF ", 3) == 0) {
+ DEBUG(10, ("Setting flags to negotiate\n"));
+ talloc_free(want_feature_list);
+ want_feature_list = talloc_strndup(state, buf+3, strlen(buf)-3);
+ printf("OK\n");
+ talloc_free(mem_ctx);
+ return;
+ }
+ in = base64_decode_data_blob_talloc(mem_ctx, buf + 3);
+ } else {
+ in = data_blob(NULL, 0);
+ }
+
+ if (strncmp(buf, "YR", 2) == 0) {
+ if (state->gensec_state) {
+ talloc_free(state->gensec_state);
+ state->gensec_state = NULL;
+ }
+ } else if ( (strncmp(buf, "OK", 2) == 0)) {
+ /* Just return BH, like ntlm_auth from Samba 3 does. */
+ printf("BH Command expected\n");
+ talloc_free(mem_ctx);
+ return;
+ } else if ( (strncmp(buf, "TT ", 3) != 0) &&
+ (strncmp(buf, "KK ", 3) != 0) &&
+ (strncmp(buf, "AF ", 3) != 0) &&
+ (strncmp(buf, "NA ", 3) != 0) &&
+ (strncmp(buf, "UG", 2) != 0) &&
+ (strncmp(buf, "PW ", 3) != 0) &&
+ (strncmp(buf, "GK", 2) != 0) &&
+ (strncmp(buf, "GF", 2) != 0)) {
+ DEBUG(1, ("SPNEGO request [%s] invalid prefix\n", buf));
+ printf("BH SPNEGO request invalid prefix\n");
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ /* setup gensec */
+ if (!(state->gensec_state)) {
+ switch (stdio_helper_mode) {
+ case GSS_SPNEGO_CLIENT:
+ /*
+ * cached credentials are only supported by
+ * NTLMSSP_CLIENT_1 for now.
+ */
+ use_cached_creds = false;
+ FALL_THROUGH;
+ case NTLMSSP_CLIENT_1:
+ /* setup the client side */
+
+ if (state->set_password != NULL) {
+ use_cached_creds = false;
+ }
+
+ if (use_cached_creds) {
+ struct wbcCredentialCacheParams params;
+ struct wbcCredentialCacheInfo *info = NULL;
+ struct wbcAuthErrorInfo *error = NULL;
+ wbcErr wbc_status;
+
+ params.account_name = opt_username;
+ params.domain_name = opt_domain;
+ params.level = WBC_CREDENTIAL_CACHE_LEVEL_NTLMSSP;
+ params.num_blobs = 0;
+ params.blobs = NULL;
+
+ wbc_status = wbcCredentialCache(&params, &info,
+ &error);
+ wbcFreeMemory(error);
+ if (!WBC_ERROR_IS_OK(wbc_status)) {
+ use_cached_creds = false;
+ }
+ wbcFreeMemory(info);
+ }
+
+ nt_status = ntlm_auth_prepare_gensec_client(state, lp_ctx,
+ &state->gensec_state);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf("BH GENSEC mech failed to start: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ creds = cli_credentials_init(state->gensec_state);
+ cli_credentials_set_conf(creds, lp_ctx);
+ if (opt_username) {
+ cli_credentials_set_username(creds, opt_username, CRED_SPECIFIED);
+ }
+ if (opt_domain) {
+ cli_credentials_set_domain(creds, opt_domain, CRED_SPECIFIED);
+ }
+ if (use_cached_creds) {
+ gensec_want_feature(state->gensec_state,
+ GENSEC_FEATURE_NTLM_CCACHE);
+ } else if (state->set_password) {
+ cli_credentials_set_password(creds, state->set_password, CRED_SPECIFIED);
+ } else {
+ cli_credentials_set_password_callback(creds, get_password);
+ }
+ if (opt_workstation) {
+ cli_credentials_set_workstation(creds, opt_workstation, CRED_SPECIFIED);
+ }
+
+ gensec_set_credentials(state->gensec_state, creds);
+
+ break;
+ case GSS_SPNEGO_SERVER:
+ case SQUID_2_5_NTLMSSP:
+ {
+ nt_status = ntlm_auth_prepare_gensec_server(state, lp_ctx,
+ &state->gensec_state);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf("BH GENSEC mech failed to start: %s\n",
+ nt_errstr(nt_status));
+ talloc_free(mem_ctx);
+ return;
+ }
+ break;
+ }
+ default:
+ talloc_free(mem_ctx);
+ abort();
+ }
+
+ gensec_want_feature_list(state->gensec_state, want_feature_list);
+
+ /* Session info is not complete, do not pass to auth log */
+ gensec_want_feature(state->gensec_state, GENSEC_FEATURE_NO_AUTHZ_LOG);
+
+ switch (stdio_helper_mode) {
+ case GSS_SPNEGO_CLIENT:
+ case GSS_SPNEGO_SERVER:
+ nt_status = gensec_start_mech_by_oid(state->gensec_state, GENSEC_OID_SPNEGO);
+ if (!in.length) {
+ first = true;
+ }
+ break;
+ case NTLMSSP_CLIENT_1:
+ if (!in.length) {
+ first = true;
+ }
+ FALL_THROUGH;
+ case SQUID_2_5_NTLMSSP:
+ nt_status = gensec_start_mech_by_oid(state->gensec_state, GENSEC_OID_NTLMSSP);
+ break;
+ default:
+ talloc_free(mem_ctx);
+ abort();
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("GENSEC mech failed to start: %s\n", nt_errstr(nt_status)));
+ printf("BH GENSEC mech failed to start\n");
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ }
+
+ /* update */
+
+ if (strncmp(buf, "PW ", 3) == 0) {
+ state->set_password = talloc_strndup(state,
+ (const char *)in.data,
+ in.length);
+
+ cli_credentials_set_password(gensec_get_credentials(state->gensec_state),
+ state->set_password,
+ CRED_SPECIFIED);
+ printf("OK\n");
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ if (strncmp(buf, "GK", 2) == 0) {
+ char *base64_key;
+ DEBUG(10, ("Requested session key\n"));
+ nt_status = gensec_session_key(state->gensec_state, mem_ctx, &session_key);
+ if(!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(1, ("gensec_session_key failed: %s\n", nt_errstr(nt_status)));
+ printf("BH No session key\n");
+ talloc_free(mem_ctx);
+ return;
+ } else {
+ base64_key = base64_encode_data_blob(state, session_key);
+ SMB_ASSERT(base64_key != NULL);
+ printf("GK %s\n", base64_key);
+ talloc_free(base64_key);
+ }
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ if (strncmp(buf, "GF", 2) == 0) {
+ uint32_t neg_flags;
+
+ DEBUG(10, ("Requested negotiated NTLMSSP feature flags\n"));
+
+ neg_flags = gensec_ntlmssp_neg_flags(state->gensec_state);
+ if (neg_flags == 0) {
+ printf("BH\n");
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ printf("GF 0x%08x\n", neg_flags);
+ talloc_free(mem_ctx);
+ return;
+ }
+
+ nt_status = gensec_update(state->gensec_state, mem_ctx, in, &out);
+
+ /* don't leak 'bad password'/'no such user' info to the network client */
+ nt_status = nt_status_squash(nt_status);
+
+ if (out.length) {
+ out_base64 = base64_encode_data_blob(mem_ctx, out);
+ SMB_ASSERT(out_base64 != NULL);
+ } else {
+ out_base64 = NULL;
+ }
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ reply_arg = "*";
+ if (first && state->gensec_state->gensec_role == GENSEC_CLIENT) {
+ reply_code = "YR";
+ } else if (state->gensec_state->gensec_role == GENSEC_CLIENT) {
+ reply_code = "KK";
+ } else if (state->gensec_state->gensec_role == GENSEC_SERVER) {
+ reply_code = "TT";
+ } else {
+ abort();
+ }
+
+
+ } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCESS_DENIED)) {
+ reply_code = "BH NT_STATUS_ACCESS_DENIED";
+ reply_arg = nt_errstr(nt_status);
+ DEBUG(1, ("GENSEC login failed: %s\n", nt_errstr(nt_status)));
+ } else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_UNSUCCESSFUL)) {
+ reply_code = "BH NT_STATUS_UNSUCCESSFUL";
+ reply_arg = nt_errstr(nt_status);
+ DEBUG(1, ("GENSEC login failed: %s\n", nt_errstr(nt_status)));
+ } else if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_code = "NA";
+ reply_arg = nt_errstr(nt_status);
+ DEBUG(1, ("GENSEC login failed: %s\n", nt_errstr(nt_status)));
+ } else if /* OK */ (state->gensec_state->gensec_role == GENSEC_SERVER) {
+ struct auth_session_info *session_info;
+
+ nt_status = gensec_session_info(state->gensec_state, mem_ctx, &session_info);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ reply_code = "BH Failed to retrieve session info";
+ reply_arg = nt_errstr(nt_status);
+ DEBUG(1, ("GENSEC failed to retrieve the session info: %s\n", nt_errstr(nt_status)));
+ } else {
+
+ reply_code = "AF";
+ reply_arg = talloc_strdup(state->gensec_state, session_info->unix_info->unix_name);
+ if (reply_arg == NULL) {
+ reply_code = "BH out of memory";
+ reply_arg = nt_errstr(NT_STATUS_NO_MEMORY);
+ }
+ talloc_free(session_info);
+ }
+ } else if (state->gensec_state->gensec_role == GENSEC_CLIENT) {
+ reply_code = "AF";
+ reply_arg = out_base64;
+ } else {
+ abort();
+ }
+
+ switch (stdio_helper_mode) {
+ case GSS_SPNEGO_SERVER:
+ printf("%s %s %s\n", reply_code,
+ out_base64 ? out_base64 : "*",
+ reply_arg ? reply_arg : "*");
+ break;
+ default:
+ if (out_base64) {
+ printf("%s %s\n", reply_code, out_base64);
+ } else if (reply_arg) {
+ printf("%s %s\n", reply_code, reply_arg);
+ } else {
+ printf("%s\n", reply_code);
+ }
+ }
+
+ talloc_free(mem_ctx);
+ return;
+}
+
+static void manage_gss_spnego_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2)
+{
+ manage_gensec_request(stdio_helper_mode, lp_ctx, buf, length, &state->gensec_private_1);
+ return;
+}
+
+static void manage_squid_ntlmssp_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2)
+{
+ manage_gensec_request(stdio_helper_mode, lp_ctx, buf, length, &state->gensec_private_1);
+ return;
+}
+
+static void manage_gss_spnego_client_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2)
+{
+ manage_gensec_request(stdio_helper_mode, lp_ctx, buf, length, &state->gensec_private_1);
+ return;
+}
+
+static void manage_ntlm_server_1_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2)
+{
+ char *request, *parameter;
+ static DATA_BLOB challenge;
+ static DATA_BLOB lm_response;
+ static DATA_BLOB nt_response;
+ static char *full_username;
+ static char *username;
+ static char *domain;
+ static char *plaintext_password;
+ static bool ntlm_server_1_user_session_key;
+ static bool ntlm_server_1_lm_session_key;
+
+ if (strequal(buf, ".")) {
+ if (!full_username && !username) {
+ printf("Error: No username supplied!\n");
+ } else if (plaintext_password) {
+ /* handle this request as plaintext */
+ if (!full_username) {
+ if (asprintf(&full_username, "%s%c%s", domain, winbind_separator(), username) == -1) {
+ printf("Error: Out of memory in "
+ "asprintf!\n.\n");
+ return;
+ }
+ }
+ if (check_plaintext_auth(full_username, plaintext_password, False)) {
+ printf("Authenticated: Yes\n");
+ } else {
+ printf("Authenticated: No\n");
+ }
+ } else if (!lm_response.data && !nt_response.data) {
+ printf("Error: No password supplied!\n");
+ } else if (!challenge.data) {
+ printf("Error: No lanman-challenge supplied!\n");
+ } else {
+ char *error_string = NULL;
+ uchar lm_key[8];
+ uchar user_session_key[16];
+ uint32_t flags = 0;
+ NTSTATUS nt_status;
+ if (full_username && !username) {
+ fstring fstr_user;
+ fstring fstr_domain;
+
+ if (!parse_ntlm_auth_domain_user(full_username, fstr_user, fstr_domain)) {
+ /* username might be 'tainted', don't print into our new-line deleimianted stream */
+ printf("Error: Could not parse into "
+ "domain and username\n");
+ }
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ username = smb_xstrdup(fstr_user);
+ domain = smb_xstrdup(fstr_domain);
+ }
+
+ if (opt_password) {
+ DATA_BLOB nt_session_key, lm_session_key;
+ struct samr_Password lm_pw, nt_pw;
+ TALLOC_CTX *mem_ctx = talloc_new(NULL);
+ ZERO_STRUCT(user_session_key);
+ ZERO_STRUCT(lm_key);
+
+ nt_lm_owf_gen (opt_password, nt_pw.hash, lm_pw.hash);
+ nt_status = ntlm_password_check(mem_ctx,
+ true,
+ NTLM_AUTH_ON,
+ 0,
+ &challenge,
+ &lm_response,
+ &nt_response,
+ username,
+ username,
+ domain,
+ &lm_pw, &nt_pw,
+ &nt_session_key,
+ &lm_session_key);
+ error_string = smb_xstrdup(get_friendly_nt_error_msg(nt_status));
+ if (ntlm_server_1_user_session_key) {
+ if (nt_session_key.length == sizeof(user_session_key)) {
+ memcpy(user_session_key,
+ nt_session_key.data,
+ sizeof(user_session_key));
+ }
+ }
+ if (ntlm_server_1_lm_session_key) {
+ if (lm_session_key.length == sizeof(lm_key)) {
+ memcpy(lm_key,
+ lm_session_key.data,
+ sizeof(lm_key));
+ }
+ }
+ TALLOC_FREE(mem_ctx);
+
+ } else {
+ uint8_t authoritative = 1;
+
+ if (!domain) {
+ domain = smb_xstrdup(get_winbind_domain());
+ }
+
+ if (ntlm_server_1_lm_session_key)
+ flags |= WBFLAG_PAM_LMKEY;
+
+ if (ntlm_server_1_user_session_key)
+ flags |= WBFLAG_PAM_USER_SESSION_KEY;
+
+ nt_status = contact_winbind_auth_crap(username,
+ domain,
+ lp_netbios_name(),
+ &challenge,
+ &lm_response,
+ &nt_response,
+ flags, 0,
+ lm_key,
+ user_session_key,
+ &authoritative,
+ &error_string,
+ NULL);
+ }
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf("Authenticated: No\n");
+ printf("Authentication-Error: %s\n.\n",
+ error_string);
+ } else {
+ char *hex_lm_key;
+ char *hex_user_session_key;
+
+ printf("Authenticated: Yes\n");
+
+ if (ntlm_server_1_lm_session_key
+ && (!all_zero(lm_key,
+ sizeof(lm_key)))) {
+ hex_lm_key = hex_encode_talloc(NULL,
+ (const unsigned char *)lm_key,
+ sizeof(lm_key));
+ printf("LANMAN-Session-Key: %s\n",
+ hex_lm_key);
+ TALLOC_FREE(hex_lm_key);
+ }
+
+ if (ntlm_server_1_user_session_key
+ && (!all_zero(user_session_key,
+ sizeof(user_session_key)))) {
+ hex_user_session_key = hex_encode_talloc(NULL,
+ (const unsigned char *)user_session_key,
+ sizeof(user_session_key));
+ printf("User-Session-Key: %s\n",
+ hex_user_session_key);
+ TALLOC_FREE(hex_user_session_key);
+ }
+ }
+ SAFE_FREE(error_string);
+ }
+ /* clear out the state */
+ challenge = data_blob_null;
+ nt_response = data_blob_null;
+ lm_response = data_blob_null;
+ SAFE_FREE(full_username);
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ SAFE_FREE(plaintext_password);
+ ntlm_server_1_user_session_key = False;
+ ntlm_server_1_lm_session_key = False;
+ printf(".\n");
+
+ return;
+ }
+
+ request = buf;
+
+ /* Indicates a base64 encoded structure */
+ parameter = strstr_m(request, ":: ");
+ if (!parameter) {
+ parameter = strstr_m(request, ": ");
+
+ if (!parameter) {
+ DEBUG(0, ("Parameter not found!\n"));
+ printf("Error: Parameter not found!\n.\n");
+ return;
+ }
+
+ parameter[0] ='\0';
+ parameter++;
+ parameter[0] ='\0';
+ parameter++;
+
+ } else {
+ parameter[0] ='\0';
+ parameter++;
+ parameter[0] ='\0';
+ parameter++;
+ parameter[0] ='\0';
+ parameter++;
+
+ base64_decode_inplace(parameter);
+ }
+
+ if (strequal(request, "LANMAN-Challenge")) {
+ challenge = strhex_to_data_blob(NULL, parameter);
+ if (challenge.length != 8) {
+ printf("Error: hex decode of %s failed! "
+ "(got %d bytes, expected 8)\n.\n",
+ parameter,
+ (int)challenge.length);
+ challenge = data_blob_null;
+ }
+ } else if (strequal(request, "NT-Response")) {
+ nt_response = strhex_to_data_blob(NULL, parameter);
+ if (nt_response.length < 24) {
+ printf("Error: hex decode of %s failed! "
+ "(only got %d bytes, needed at least 24)\n.\n",
+ parameter,
+ (int)nt_response.length);
+ nt_response = data_blob_null;
+ }
+ } else if (strequal(request, "LANMAN-Response")) {
+ lm_response = strhex_to_data_blob(NULL, parameter);
+ if (lm_response.length != 24) {
+ printf("Error: hex decode of %s failed! "
+ "(got %d bytes, expected 24)\n.\n",
+ parameter,
+ (int)lm_response.length);
+ lm_response = data_blob_null;
+ }
+ } else if (strequal(request, "Password")) {
+ plaintext_password = smb_xstrdup(parameter);
+ } else if (strequal(request, "NT-Domain")) {
+ domain = smb_xstrdup(parameter);
+ } else if (strequal(request, "Username")) {
+ username = smb_xstrdup(parameter);
+ } else if (strequal(request, "Full-Username")) {
+ full_username = smb_xstrdup(parameter);
+ } else if (strequal(request, "Request-User-Session-Key")) {
+ ntlm_server_1_user_session_key = strequal(parameter, "Yes");
+ } else if (strequal(request, "Request-LanMan-Session-Key")) {
+ ntlm_server_1_lm_session_key = strequal(parameter, "Yes");
+ } else {
+ printf("Error: Unknown request %s\n.\n", request);
+ }
+}
+
+static void manage_ntlm_change_password_1_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ char *buf, int length, void **private2)
+{
+ char *request, *parameter;
+ static DATA_BLOB new_nt_pswd;
+ static DATA_BLOB old_nt_hash_enc;
+ static DATA_BLOB new_lm_pswd;
+ static DATA_BLOB old_lm_hash_enc;
+ static char *full_username = NULL;
+ static char *username = NULL;
+ static char *domain = NULL;
+ static char *newpswd = NULL;
+ static char *oldpswd = NULL;
+
+ if (strequal(buf, ".")) {
+ if(newpswd && oldpswd) {
+ uchar old_nt_hash[16];
+ uchar old_lm_hash[16];
+ uchar new_nt_hash[16];
+ uchar new_lm_hash[16];
+
+ gnutls_cipher_hd_t cipher_hnd = NULL;
+ gnutls_datum_t old_nt_key = {
+ .data = old_nt_hash,
+ .size = sizeof(old_nt_hash),
+ };
+ int rc;
+
+ new_nt_pswd = data_blob(NULL, 516);
+ old_nt_hash_enc = data_blob(NULL, 16);
+
+ /* Calculate the MD4 hash (NT compatible) of the
+ * password */
+ E_md4hash(oldpswd, old_nt_hash);
+ E_md4hash(newpswd, new_nt_hash);
+
+ /* E_deshash returns false for 'long'
+ passwords (> 14 DOS chars).
+
+ Therefore, don't send a buffer
+ encrypted with the truncated hash
+ (it could allow an even easier
+ attack on the password)
+
+ Likewise, obey the admin's restriction
+ */
+
+ rc = gnutls_cipher_init(&cipher_hnd,
+ GNUTLS_CIPHER_ARCFOUR_128,
+ &old_nt_key,
+ NULL);
+ if (rc < 0) {
+ DBG_ERR("gnutls_cipher_init failed: %s\n",
+ gnutls_strerror(rc));
+ if (rc == GNUTLS_E_UNWANTED_ALGORITHM) {
+ DBG_ERR("Running in FIPS mode, NTLM blocked\n");
+ }
+ return;
+ }
+
+ if (lp_client_lanman_auth() &&
+ E_deshash(newpswd, new_lm_hash) &&
+ E_deshash(oldpswd, old_lm_hash)) {
+ new_lm_pswd = data_blob(NULL, 516);
+ old_lm_hash_enc = data_blob(NULL, 16);
+ encode_pw_buffer(new_lm_pswd.data, newpswd,
+ STR_UNICODE);
+
+ rc = gnutls_cipher_encrypt(cipher_hnd,
+ new_lm_pswd.data,
+ 516);
+ if (rc < 0) {
+ gnutls_cipher_deinit(cipher_hnd);
+ return;
+ }
+ rc = E_old_pw_hash(new_nt_hash, old_lm_hash,
+ old_lm_hash_enc.data);
+ if (rc != 0) {
+ DBG_ERR("E_old_pw_hash failed: %s\n",
+ gnutls_strerror(rc));
+ return;
+ }
+ } else {
+ new_lm_pswd.data = NULL;
+ new_lm_pswd.length = 0;
+ old_lm_hash_enc.data = NULL;
+ old_lm_hash_enc.length = 0;
+ }
+
+ encode_pw_buffer(new_nt_pswd.data, newpswd,
+ STR_UNICODE);
+
+ rc = gnutls_cipher_encrypt(cipher_hnd,
+ new_nt_pswd.data,
+ 516);
+ gnutls_cipher_deinit(cipher_hnd);
+ if (rc < 0) {
+ return;
+ }
+ rc = E_old_pw_hash(new_nt_hash, old_nt_hash,
+ old_nt_hash_enc.data);
+ if (rc != 0) {
+ DBG_ERR("E_old_pw_hash failed: %s\n",
+ gnutls_strerror(rc));
+ return;
+ }
+
+ ZERO_ARRAY(old_nt_hash);
+ ZERO_ARRAY(old_lm_hash);
+ ZERO_ARRAY(new_nt_hash);
+ ZERO_ARRAY(new_lm_hash);
+ }
+
+ if (!full_username && !username) {
+ printf("Error: No username supplied!\n");
+ } else if ((!new_nt_pswd.data || !old_nt_hash_enc.data) &&
+ (!new_lm_pswd.data || old_lm_hash_enc.data) ) {
+ printf("Error: No NT or LM password "
+ "blobs supplied!\n");
+ } else {
+ char *error_string = NULL;
+
+ if (full_username && !username) {
+ fstring fstr_user;
+ fstring fstr_domain;
+
+ if (!parse_ntlm_auth_domain_user(full_username,
+ fstr_user,
+ fstr_domain)) {
+ /* username might be 'tainted', don't
+ * print into our new-line
+ * deleimianted stream */
+ printf("Error: Could not "
+ "parse into domain and "
+ "username\n");
+ SAFE_FREE(username);
+ username = smb_xstrdup(full_username);
+ } else {
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ username = smb_xstrdup(fstr_user);
+ domain = smb_xstrdup(fstr_domain);
+ }
+
+ }
+
+ if(!NT_STATUS_IS_OK(contact_winbind_change_pswd_auth_crap(
+ username, domain,
+ new_nt_pswd,
+ old_nt_hash_enc,
+ new_lm_pswd,
+ old_lm_hash_enc,
+ &error_string))) {
+ printf("Password-Change: No\n");
+ printf("Password-Change-Error: %s\n.\n",
+ error_string);
+ } else {
+ printf("Password-Change: Yes\n");
+ }
+
+ SAFE_FREE(error_string);
+ }
+ /* clear out the state */
+ new_nt_pswd = data_blob_null;
+ old_nt_hash_enc = data_blob_null;
+ new_lm_pswd = data_blob_null;
+ old_nt_hash_enc = data_blob_null;
+ SAFE_FREE(full_username);
+ SAFE_FREE(username);
+ SAFE_FREE(domain);
+ SAFE_FREE(newpswd);
+ SAFE_FREE(oldpswd);
+ printf(".\n");
+
+ return;
+ }
+
+ request = buf;
+
+ /* Indicates a base64 encoded structure */
+ parameter = strstr_m(request, ":: ");
+ if (!parameter) {
+ parameter = strstr_m(request, ": ");
+
+ if (!parameter) {
+ DEBUG(0, ("Parameter not found!\n"));
+ printf("Error: Parameter not found!\n.\n");
+ return;
+ }
+
+ parameter[0] ='\0';
+ parameter++;
+ parameter[0] ='\0';
+ parameter++;
+ } else {
+ parameter[0] ='\0';
+ parameter++;
+ parameter[0] ='\0';
+ parameter++;
+ parameter[0] ='\0';
+ parameter++;
+
+ base64_decode_inplace(parameter);
+ }
+
+ if (strequal(request, "new-nt-password-blob")) {
+ new_nt_pswd = strhex_to_data_blob(NULL, parameter);
+ if (new_nt_pswd.length != 516) {
+ printf("Error: hex decode of %s failed! "
+ "(got %d bytes, expected 516)\n.\n",
+ parameter,
+ (int)new_nt_pswd.length);
+ new_nt_pswd = data_blob_null;
+ }
+ } else if (strequal(request, "old-nt-hash-blob")) {
+ old_nt_hash_enc = strhex_to_data_blob(NULL, parameter);
+ if (old_nt_hash_enc.length != 16) {
+ printf("Error: hex decode of %s failed! "
+ "(got %d bytes, expected 16)\n.\n",
+ parameter,
+ (int)old_nt_hash_enc.length);
+ old_nt_hash_enc = data_blob_null;
+ }
+ } else if (strequal(request, "new-lm-password-blob")) {
+ new_lm_pswd = strhex_to_data_blob(NULL, parameter);
+ if (new_lm_pswd.length != 516) {
+ printf("Error: hex decode of %s failed! "
+ "(got %d bytes, expected 516)\n.\n",
+ parameter,
+ (int)new_lm_pswd.length);
+ new_lm_pswd = data_blob_null;
+ }
+ }
+ else if (strequal(request, "old-lm-hash-blob")) {
+ old_lm_hash_enc = strhex_to_data_blob(NULL, parameter);
+ if (old_lm_hash_enc.length != 16)
+ {
+ printf("Error: hex decode of %s failed! "
+ "(got %d bytes, expected 16)\n.\n",
+ parameter,
+ (int)old_lm_hash_enc.length);
+ old_lm_hash_enc = data_blob_null;
+ }
+ } else if (strequal(request, "nt-domain")) {
+ domain = smb_xstrdup(parameter);
+ } else if(strequal(request, "username")) {
+ username = smb_xstrdup(parameter);
+ } else if(strequal(request, "full-username")) {
+ username = smb_xstrdup(parameter);
+ } else if(strequal(request, "new-password")) {
+ newpswd = smb_xstrdup(parameter);
+ } else if (strequal(request, "old-password")) {
+ oldpswd = smb_xstrdup(parameter);
+ } else {
+ printf("Error: Unknown request %s\n.\n", request);
+ }
+}
+
+static void manage_squid_request(enum stdio_helper_mode stdio_helper_mode,
+ struct loadparm_context *lp_ctx,
+ struct ntlm_auth_state *state,
+ stdio_helper_function fn, void **private2)
+{
+ char *buf;
+ char tmp[INITIAL_BUFFER_SIZE+1];
+ int length, buf_size = 0;
+ char *c;
+
+ buf = talloc_strdup(state->mem_ctx, "");
+ if (!buf) {
+ DEBUG(0, ("Failed to allocate input buffer.\n"));
+ fprintf(stderr, "ERR\n");
+ exit(1);
+ }
+
+ do {
+
+ /* this is not a typo - x_fgets doesn't work too well under
+ * squid */
+ if (fgets(tmp, sizeof(tmp)-1, stdin) == NULL) {
+ if (ferror(stdin)) {
+ DEBUG(1, ("fgets() failed! dying..... errno=%d "
+ "(%s)\n", ferror(stdin),
+ strerror(ferror(stdin))));
+
+ exit(1);
+ }
+ exit(0);
+ }
+
+ buf = talloc_strdup_append_buffer(buf, tmp);
+ buf_size += INITIAL_BUFFER_SIZE;
+
+ if (buf_size > MAX_BUFFER_SIZE) {
+ DEBUG(2, ("Oversized message\n"));
+ fprintf(stderr, "ERR\n");
+ talloc_free(buf);
+ return;
+ }
+
+ c = strchr(buf, '\n');
+ } while (c == NULL);
+
+ *c = '\0';
+ length = c-buf;
+
+ DEBUG(10, ("Got '%s' from squid (length: %d).\n",buf,length));
+
+ if (buf[0] == '\0') {
+ DEBUG(2, ("Invalid Request\n"));
+ fprintf(stderr, "ERR\n");
+ talloc_free(buf);
+ return;
+ }
+
+ fn(stdio_helper_mode, lp_ctx, state, buf, length, private2);
+ talloc_free(buf);
+}
+
+
+static void squid_stream(enum stdio_helper_mode stdio_mode,
+ struct loadparm_context *lp_ctx,
+ stdio_helper_function fn) {
+ TALLOC_CTX *mem_ctx;
+ struct ntlm_auth_state *state;
+
+ /* initialize FDescs */
+ setbuf(stdout, NULL);
+ setbuf(stderr, NULL);
+
+ mem_ctx = talloc_init("ntlm_auth");
+ if (!mem_ctx) {
+ DEBUG(0, ("squid_stream: Failed to create talloc context\n"));
+ fprintf(stderr, "ERR\n");
+ exit(1);
+ }
+
+ state = talloc_zero(mem_ctx, struct ntlm_auth_state);
+ if (!state) {
+ DEBUG(0, ("squid_stream: Failed to talloc ntlm_auth_state\n"));
+ fprintf(stderr, "ERR\n");
+ exit(1);
+ }
+
+ state->mem_ctx = mem_ctx;
+ state->helper_mode = stdio_mode;
+
+ while(1) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ manage_squid_request(stdio_mode, lp_ctx, state, fn, NULL);
+ TALLOC_FREE(frame);
+ }
+}
+
+
+/* Authenticate a user with a challenge/response */
+
+static bool check_auth_crap(void)
+{
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+ char lm_key[8];
+ char user_session_key[16];
+ char *hex_lm_key;
+ char *hex_user_session_key;
+ char *error_string;
+ uint8_t authoritative = 1;
+
+ setbuf(stdout, NULL);
+
+ if (request_lm_key)
+ flags |= WBFLAG_PAM_LMKEY;
+
+ if (request_user_session_key)
+ flags |= WBFLAG_PAM_USER_SESSION_KEY;
+
+ flags |= WBFLAG_PAM_NT_STATUS_SQUASH;
+
+ nt_status = contact_winbind_auth_crap(opt_username, opt_domain,
+ opt_workstation,
+ &opt_challenge,
+ &opt_lm_response,
+ &opt_nt_response,
+ flags, 0,
+ (unsigned char *)lm_key,
+ (unsigned char *)user_session_key,
+ &authoritative,
+ &error_string, NULL);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ printf("%s (0x%x)\n", error_string,
+ NT_STATUS_V(nt_status));
+ SAFE_FREE(error_string);
+ return False;
+ }
+
+ if (request_lm_key
+ && (!all_zero((uint8_t *)lm_key, sizeof(lm_key)))) {
+ hex_lm_key = hex_encode_talloc(talloc_tos(), (const unsigned char *)lm_key,
+ sizeof(lm_key));
+ printf("LM_KEY: %s\n", hex_lm_key);
+ TALLOC_FREE(hex_lm_key);
+ }
+ if (request_user_session_key
+ && (!all_zero((uint8_t *)user_session_key,
+ sizeof(user_session_key)))) {
+ hex_user_session_key = hex_encode_talloc(talloc_tos(), (const unsigned char *)user_session_key,
+ sizeof(user_session_key));
+ printf("NT_KEY: %s\n", hex_user_session_key);
+ TALLOC_FREE(hex_user_session_key);
+ }
+
+ return True;
+}
+
+/* Main program */
+
+enum {
+ OPT_USERNAME = 1000,
+ OPT_DOMAIN,
+ OPT_WORKSTATION,
+ OPT_CHALLENGE,
+ OPT_RESPONSE,
+ OPT_LM,
+ OPT_NT,
+ OPT_PASSWORD,
+ OPT_LM_KEY,
+ OPT_USER_SESSION_KEY,
+ OPT_DIAGNOSTICS,
+ OPT_REQUIRE_MEMBERSHIP,
+ OPT_USE_CACHED_CREDS,
+ OPT_ALLOW_MSCHAPV2,
+ OPT_PAM_WINBIND_CONF,
+ OPT_TARGET_SERVICE,
+ OPT_TARGET_HOSTNAME,
+ OPT_OFFLINE_LOGON
+};
+
+ int main(int argc, const char **argv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int opt;
+ const char *helper_protocol = NULL;
+ int diagnostics = 0;
+
+ const char *hex_challenge = NULL;
+ const char *hex_lm_response = NULL;
+ const char *hex_nt_response = NULL;
+ struct loadparm_context *lp_ctx;
+ poptContext pc;
+ bool ok;
+
+ /* NOTE: DO NOT change this interface without considering the implications!
+ This is an external interface, which other programs will use to interact
+ with this helper.
+ */
+
+ /* We do not use single-letter command abbreviations, because they harm future
+ interface stability. */
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "helper-protocol",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &helper_protocol,
+ .val = OPT_DOMAIN,
+ .descrip = "operate as a stdio-based helper",
+ .argDescrip = "helper protocol to use"
+ },
+ {
+ .longName = "username",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_username,
+ .val = OPT_USERNAME,
+ .descrip = "username"
+ },
+ {
+ .longName = "domain",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_domain,
+ .val = OPT_DOMAIN,
+ .descrip = "domain name"
+ },
+ {
+ .longName = "workstation",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_workstation,
+ .val = OPT_WORKSTATION,
+ .descrip = "workstation"
+ },
+ {
+ .longName = "challenge",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &hex_challenge,
+ .val = OPT_CHALLENGE,
+ .descrip = "challenge (HEX encoded)"
+ },
+ {
+ .longName = "lm-response",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &hex_lm_response,
+ .val = OPT_LM,
+ .descrip = "LM Response to the challenge (HEX encoded)"
+ },
+ {
+ .longName = "nt-response",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &hex_nt_response,
+ .val = OPT_NT,
+ .descrip = "NT or NTLMv2 Response to the challenge (HEX encoded)"
+ },
+ {
+ .longName = "password",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_password,
+ .val = OPT_PASSWORD,
+ .descrip = "User's plaintext password"
+ },
+ {
+ .longName = "request-lm-key",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &request_lm_key,
+ .val = OPT_LM_KEY,
+ .descrip = "Retrieve LM session key (or, with --diagnostics, expect LM support)"
+ },
+ {
+ .longName = "request-nt-key",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &request_user_session_key,
+ .val = OPT_USER_SESSION_KEY,
+ .descrip = "Retrieve User (NT) session key"
+ },
+ {
+ .longName = "use-cached-creds",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &use_cached_creds,
+ .val = OPT_USE_CACHED_CREDS,
+ .descrip = "Use cached credentials if no password is given"
+ },
+ {
+ .longName = "allow-mschapv2",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_allow_mschapv2,
+ .val = OPT_ALLOW_MSCHAPV2,
+ .descrip = "Explicitly allow MSCHAPv2",
+ },
+ {
+ .longName = "offline-logon",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &offline_logon,
+ .val = OPT_OFFLINE_LOGON,
+ .descrip = "Use cached passwords when DC is offline"
+ },
+ {
+ .longName = "diagnostics",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &diagnostics,
+ .val = OPT_DIAGNOSTICS,
+ .descrip = "Perform diagnostics on the authentication chain"
+ },
+ {
+ .longName = "require-membership-of",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &require_membership_of,
+ .val = OPT_REQUIRE_MEMBERSHIP,
+ .descrip = "Require that a user be a member of this group (either name or SID) for authentication to succeed",
+ },
+ {
+ .longName = "pam-winbind-conf",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_pam_winbind_conf,
+ .val = OPT_PAM_WINBIND_CONF,
+ .descrip = "Require that request must set WBFLAG_PAM_CONTACT_TRUSTDOM when krb5 auth is required",
+ },
+ {
+ .longName = "target-service",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_target_service,
+ .val = OPT_TARGET_SERVICE,
+ .descrip = "Target service (eg http)",
+ },
+ {
+ .longName = "target-hostname",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt_target_hostname,
+ .val = OPT_TARGET_HOSTNAME,
+ .descrip = "Target hostname",
+ },
+ POPT_COMMON_DEBUG_ONLY
+ POPT_COMMON_CONFIG_ONLY
+ POPT_COMMON_OPTION_ONLY
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ /* Samba client initialisation */
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case OPT_CHALLENGE:
+ opt_challenge = strhex_to_data_blob(NULL, hex_challenge);
+ if (opt_challenge.length != 8) {
+ fprintf(stderr, "hex decode of %s failed! "
+ "(only got %d bytes)\n",
+ hex_challenge,
+ (int)opt_challenge.length);
+ exit(1);
+ }
+ break;
+ case OPT_LM:
+ opt_lm_response = strhex_to_data_blob(NULL, hex_lm_response);
+ if (opt_lm_response.length != 24) {
+ fprintf(stderr, "hex decode of %s failed! "
+ "(only got %d bytes)\n",
+ hex_lm_response,
+ (int)opt_lm_response.length);
+ exit(1);
+ }
+ break;
+
+ case OPT_NT:
+ opt_nt_response = strhex_to_data_blob(NULL, hex_nt_response);
+ if (opt_nt_response.length < 24) {
+ fprintf(stderr, "hex decode of %s failed! "
+ "(only got %d bytes)\n",
+ hex_nt_response,
+ (int)opt_nt_response.length);
+ exit(1);
+ }
+ break;
+
+ case OPT_REQUIRE_MEMBERSHIP:
+ if (strncasecmp_m("S-", require_membership_of, 2) == 0) {
+ require_membership_of_sid = require_membership_of;
+ }
+ break;
+
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ if (opt_username) {
+ char *domain = SMB_STRDUP(opt_username);
+ char *p = strchr_m(domain, *lp_winbind_separator());
+ if (p) {
+ opt_username = p+1;
+ *p = '\0';
+ if (opt_domain && !strequal(opt_domain, domain)) {
+ fprintf(stderr, "Domain specified in username (%s) "
+ "doesn't match specified domain (%s)!\n\n",
+ domain, opt_domain);
+ poptPrintHelp(pc, stderr, 0);
+ exit(1);
+ }
+ opt_domain = domain;
+ } else {
+ SAFE_FREE(domain);
+ }
+ }
+
+ /* Note: if opt_domain is "" then send no domain */
+ if (opt_domain == NULL) {
+ opt_domain = get_winbind_domain();
+ }
+
+ if (opt_workstation == NULL) {
+ opt_workstation = "";
+ }
+
+ lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ fprintf(stderr, "loadparm_init_s3() failed!\n");
+ exit(1);
+ }
+
+ if (helper_protocol) {
+ int i;
+ for (i=0; i<NUM_HELPER_MODES; i++) {
+ if (strcmp(helper_protocol, stdio_helper_protocols[i].name) == 0) {
+ squid_stream(stdio_helper_protocols[i].mode, lp_ctx, stdio_helper_protocols[i].fn);
+ exit(0);
+ }
+ }
+ fprintf(stderr, "unknown helper protocol [%s]\n\n"
+ "Valid helper protools:\n\n", helper_protocol);
+
+ for (i=0; i<NUM_HELPER_MODES; i++) {
+ fprintf(stderr, "%s\n",
+ stdio_helper_protocols[i].name);
+ }
+
+ exit(1);
+ }
+
+ if (!opt_username || !*opt_username) {
+ fprintf(stderr, "username must be specified!\n\n");
+ poptPrintHelp(pc, stderr, 0);
+ exit(1);
+ }
+
+ if (opt_challenge.length) {
+ if (!check_auth_crap()) {
+ exit(1);
+ }
+ exit(0);
+ }
+
+ if (!opt_password) {
+ char pwd[256] = {0};
+ int rc;
+
+ rc = samba_getpass("Password: ", pwd, sizeof(pwd), false, false);
+ if (rc == 0) {
+ opt_password = SMB_STRDUP(pwd);
+ }
+ }
+
+ if (diagnostics) {
+ if (!diagnose_ntlm_auth(request_lm_key)) {
+ poptFreeContext(pc);
+ return 1;
+ }
+ } else {
+ fstring user;
+
+ fstr_sprintf(user, "%s%c%s", opt_domain, winbind_separator(), opt_username);
+ if (!check_plaintext_auth(user, opt_password, True)) {
+ poptFreeContext(pc);
+ return 1;
+ }
+ }
+
+ /* Exit code */
+ gfree_all();
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/utils/ntlm_auth.h b/source3/utils/ntlm_auth.h
new file mode 100644
index 0000000..fb1dd62
--- /dev/null
+++ b/source3/utils/ntlm_auth.h
@@ -0,0 +1,26 @@
+/*
+ Samba Unix/Linux NTLM authentication tool
+
+ Copyright (C) 2001 Andrew Bartlett (abartlet@samba.org)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "utils/ntlm_auth_proto.h"
+
+/* Some of the popt variables are needed in the diagnostics code */
+extern const char *opt_username;
+extern const char *opt_domain;
+extern const char *opt_workstation;
+extern const char *opt_password;
+
diff --git a/source3/utils/ntlm_auth_diagnostics.c b/source3/utils/ntlm_auth_diagnostics.c
new file mode 100644
index 0000000..6a76e73
--- /dev/null
+++ b/source3/utils/ntlm_auth_diagnostics.c
@@ -0,0 +1,724 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind status program.
+
+ Copyright (C) Tim Potter 2000-2003
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-2004
+ Copyright (C) Francesco Chemolli <kinkie@kame.usr.dsi.unimi.it> 2000
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "utils/ntlm_auth.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "nsswitch/winbind_client.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+enum ntlm_break {
+ BREAK_NONE,
+ BREAK_LM,
+ BREAK_NT,
+ NO_LM,
+ NO_NT
+};
+
+/*
+ Authenticate a user with a challenge/response, checking session key
+ and valid authentication types
+*/
+
+/*
+ * Test the normal 'LM and NTLM' combination
+ */
+
+static bool test_lm_ntlm_broken(enum ntlm_break break_which,
+ bool lanman_support_expected)
+{
+ bool pass = True;
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+ DATA_BLOB lm_response = data_blob(NULL, 24);
+ DATA_BLOB nt_response = data_blob(NULL, 24);
+ DATA_BLOB session_key = data_blob(NULL, 16);
+ uint8_t authoritative = 1;
+ uchar lm_key[8];
+ uchar user_session_key[16];
+ uchar lm_hash[16];
+ uchar nt_hash[16];
+ DATA_BLOB chall = get_challenge();
+ char *error_string;
+
+ ZERO_STRUCT(lm_key);
+ ZERO_STRUCT(user_session_key);
+
+ flags |= WBFLAG_PAM_LMKEY;
+ flags |= WBFLAG_PAM_USER_SESSION_KEY;
+
+ SMBencrypt(opt_password,chall.data,lm_response.data);
+ E_deshash(opt_password, lm_hash);
+
+ SMBNTencrypt(opt_password,chall.data,nt_response.data);
+
+ E_md4hash(opt_password, nt_hash);
+ SMBsesskeygen_ntv1(nt_hash, session_key.data);
+
+ switch (break_which) {
+ case BREAK_NONE:
+ break;
+ case BREAK_LM:
+ lm_response.data[0]++;
+ break;
+ case BREAK_NT:
+ nt_response.data[0]++;
+ break;
+ case NO_LM:
+ data_blob_free(&lm_response);
+ break;
+ case NO_NT:
+ data_blob_free(&nt_response);
+ break;
+ }
+
+ nt_status = contact_winbind_auth_crap(opt_username, opt_domain,
+ opt_workstation,
+ &chall,
+ &lm_response,
+ &nt_response,
+ flags, 0,
+ lm_key,
+ user_session_key,
+ &authoritative,
+ &error_string, NULL);
+
+ data_blob_free(&lm_response);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_printf("%s (0x%x)\n",
+ error_string,
+ NT_STATUS_V(nt_status));
+ SAFE_FREE(error_string);
+ return break_which == BREAK_NT;
+ }
+
+ /* If we are told the DC is Samba4, expect an LM key of zeros */
+ if (!lanman_support_expected) {
+ if (!all_zero(lm_key,
+ sizeof(lm_key))) {
+ DEBUG(1, ("LM Key does not match expectations!\n"));
+ DEBUG(1, ("lm_key:\n"));
+ dump_data(1, lm_key, 8);
+ DEBUG(1, ("expected: all zeros\n"));
+ pass = False;
+ }
+ } else {
+ if (memcmp(lm_hash, lm_key,
+ sizeof(lm_key)) != 0) {
+ DEBUG(1, ("LM Key does not match expectations!\n"));
+ DEBUG(1, ("lm_key:\n"));
+ dump_data(1, lm_key, 8);
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, lm_hash, 8);
+ pass = False;
+ }
+ }
+
+ if (break_which == NO_NT) {
+ if (memcmp(lm_hash, user_session_key,
+ 8) != 0) {
+ DEBUG(1, ("NT Session Key does not match expectations (should be LM hash)!\n"));
+ DEBUG(1, ("user_session_key:\n"));
+ dump_data(1, user_session_key, sizeof(user_session_key));
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, lm_hash, sizeof(lm_hash));
+ pass = False;
+ }
+ } else {
+ if (memcmp(session_key.data, user_session_key,
+ sizeof(user_session_key)) != 0) {
+ DEBUG(1, ("NT Session Key does not match expectations!\n"));
+ DEBUG(1, ("user_session_key:\n"));
+ dump_data(1, user_session_key, 16);
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, session_key.data, session_key.length);
+ pass = False;
+ }
+ }
+ return pass;
+}
+
+/*
+ * Test LM authentication, no NT response supplied
+ */
+
+static bool test_lm(bool lanman_support_expected)
+{
+
+ return test_lm_ntlm_broken(NO_NT, lanman_support_expected);
+}
+
+/*
+ * Test the NTLM response only, no LM.
+ */
+
+static bool test_ntlm(bool lanman_support_expected)
+{
+ return test_lm_ntlm_broken(NO_LM, lanman_support_expected);
+}
+
+/*
+ * Test the NTLM response only, but in the LM field.
+ */
+
+static bool test_ntlm_in_lm(bool lanman_support_expected)
+{
+ bool pass = True;
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+ DATA_BLOB nt_response = data_blob(NULL, 24);
+ uint8_t authoritative = 1;
+ uchar lm_key[8];
+ uchar lm_hash[16];
+ uchar user_session_key[16];
+ DATA_BLOB chall = get_challenge();
+ char *error_string;
+
+ ZERO_STRUCT(user_session_key);
+
+ flags |= WBFLAG_PAM_LMKEY;
+ flags |= WBFLAG_PAM_USER_SESSION_KEY;
+
+ SMBNTencrypt(opt_password,chall.data,nt_response.data);
+
+ E_deshash(opt_password, lm_hash);
+
+ nt_status = contact_winbind_auth_crap(opt_username, opt_domain,
+ opt_workstation,
+ &chall,
+ &nt_response,
+ NULL,
+ flags, 0,
+ lm_key,
+ user_session_key,
+ &authoritative,
+ &error_string, NULL);
+
+ data_blob_free(&nt_response);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_printf("%s (0x%x)\n",
+ error_string,
+ NT_STATUS_V(nt_status));
+ SAFE_FREE(error_string);
+ return False;
+ }
+
+ /* If we are told the DC is Samba4, expect an LM key of zeros */
+ if (!lanman_support_expected) {
+ if (!all_zero(lm_key,
+ sizeof(lm_key))) {
+ DEBUG(1, ("LM Key does not match expectations!\n"));
+ DEBUG(1, ("lm_key:\n"));
+ dump_data(1, lm_key, 8);
+ DEBUG(1, ("expected: all zeros\n"));
+ pass = False;
+ }
+ if (!all_zero(user_session_key,
+ sizeof(user_session_key))) {
+ DEBUG(1, ("Session Key (normally first 8 lm hash) does not match expectations!\n"));
+ DEBUG(1, ("user_session_key:\n"));
+ dump_data(1, user_session_key, 16);
+ DEBUG(1, ("expected all zeros:\n"));
+ pass = False;
+ }
+ } else {
+ if (memcmp(lm_hash, lm_key,
+ sizeof(lm_key)) != 0) {
+ DEBUG(1, ("LM Key does not match expectations!\n"));
+ DEBUG(1, ("lm_key:\n"));
+ dump_data(1, lm_key, 8);
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, lm_hash, 8);
+ pass = False;
+ }
+ if (memcmp(lm_hash, user_session_key, 8) != 0) {
+ DEBUG(1, ("Session Key (first 8 lm hash) does not match expectations!\n"));
+ DEBUG(1, ("user_session_key:\n"));
+ dump_data(1, user_session_key, 16);
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, lm_hash, 8);
+ pass = False;
+ }
+ }
+ return pass;
+}
+
+/*
+ * Test the NTLM response only, but in the both the NT and LM fields.
+ */
+
+static bool test_ntlm_in_both(bool lanman_support_expected)
+{
+ bool pass = True;
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+ DATA_BLOB nt_response = data_blob(NULL, 24);
+ DATA_BLOB session_key = data_blob(NULL, 16);
+ uint8_t authoritative = 1;
+ uint8_t lm_key[8];
+ uint8_t lm_hash[16];
+ uint8_t user_session_key[16];
+ uint8_t nt_hash[16];
+ DATA_BLOB chall = get_challenge();
+ char *error_string;
+
+ ZERO_STRUCT(lm_key);
+ ZERO_STRUCT(user_session_key);
+
+ flags |= WBFLAG_PAM_LMKEY;
+ flags |= WBFLAG_PAM_USER_SESSION_KEY;
+
+ SMBNTencrypt(opt_password,chall.data,nt_response.data);
+ E_md4hash(opt_password, nt_hash);
+ SMBsesskeygen_ntv1(nt_hash, session_key.data);
+
+ E_deshash(opt_password, lm_hash);
+
+ nt_status = contact_winbind_auth_crap(opt_username, opt_domain,
+ opt_workstation,
+ &chall,
+ &nt_response,
+ &nt_response,
+ flags, 0,
+ lm_key,
+ user_session_key,
+ &authoritative,
+ &error_string, NULL);
+
+ data_blob_free(&nt_response);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_printf("%s (0x%x)\n",
+ error_string,
+ NT_STATUS_V(nt_status));
+ SAFE_FREE(error_string);
+ return False;
+ }
+
+ /* If we are told the DC is Samba4, expect an LM key of zeros */
+ if (!lanman_support_expected) {
+ if (!all_zero(lm_key,
+ sizeof(lm_key))) {
+ DEBUG(1, ("LM Key does not match expectations!\n"));
+ DEBUG(1, ("lm_key:\n"));
+ dump_data(1, lm_key, 8);
+ DEBUG(1, ("expected: all zeros\n"));
+ pass = False;
+ }
+ } else {
+ if (memcmp(lm_hash, lm_key,
+ sizeof(lm_key)) != 0) {
+ DEBUG(1, ("LM Key does not match expectations!\n"));
+ DEBUG(1, ("lm_key:\n"));
+ dump_data(1, lm_key, 8);
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, lm_hash, 8);
+ pass = False;
+ }
+ }
+ if (memcmp(session_key.data, user_session_key,
+ sizeof(user_session_key)) != 0) {
+ DEBUG(1, ("NT Session Key does not match expectations!\n"));
+ DEBUG(1, ("user_session_key:\n"));
+ dump_data(1, user_session_key, 16);
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, session_key.data, session_key.length);
+ pass = False;
+ }
+
+
+ return pass;
+}
+
+/*
+ * Test the NTLMv2 and LMv2 responses
+ */
+
+static bool test_lmv2_ntlmv2_broken(enum ntlm_break break_which)
+{
+ bool pass = True;
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+ DATA_BLOB ntlmv2_response = data_blob_null;
+ DATA_BLOB lmv2_response = data_blob_null;
+ DATA_BLOB ntlmv2_session_key = data_blob_null;
+ DATA_BLOB names_blob = NTLMv2_generate_names_blob(NULL, get_winbind_netbios_name(), get_winbind_domain());
+ uint8_t authoritative = 1;
+ uchar user_session_key[16];
+ DATA_BLOB chall = get_challenge();
+ char *error_string;
+
+ ZERO_STRUCT(user_session_key);
+
+ flags |= WBFLAG_PAM_USER_SESSION_KEY;
+
+ if (!SMBNTLMv2encrypt(NULL, opt_username, opt_domain, opt_password, &chall,
+ &names_blob,
+ &lmv2_response, &ntlmv2_response, NULL,
+ &ntlmv2_session_key)) {
+ data_blob_free(&names_blob);
+ return False;
+ }
+ data_blob_free(&names_blob);
+
+ switch (break_which) {
+ case BREAK_NONE:
+ break;
+ case BREAK_LM:
+ lmv2_response.data[0]++;
+ break;
+ case BREAK_NT:
+ ntlmv2_response.data[0]++;
+ break;
+ case NO_LM:
+ data_blob_free(&lmv2_response);
+ break;
+ case NO_NT:
+ data_blob_free(&ntlmv2_response);
+ break;
+ }
+
+ nt_status = contact_winbind_auth_crap(opt_username, opt_domain,
+ opt_workstation,
+ &chall,
+ &lmv2_response,
+ &ntlmv2_response,
+ flags, 0,
+ NULL,
+ user_session_key,
+ &authoritative,
+ &error_string, NULL);
+
+ data_blob_free(&lmv2_response);
+ data_blob_free(&ntlmv2_response);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_printf("%s (0x%x)\n",
+ error_string,
+ NT_STATUS_V(nt_status));
+ SAFE_FREE(error_string);
+ return break_which == BREAK_NT;
+ }
+
+ if (break_which != NO_NT && break_which != BREAK_NT && memcmp(ntlmv2_session_key.data, user_session_key,
+ sizeof(user_session_key)) != 0) {
+ DEBUG(1, ("USER (NTLMv2) Session Key does not match expectations!\n"));
+ DEBUG(1, ("user_session_key:\n"));
+ dump_data(1, user_session_key, 16);
+ DEBUG(1, ("expected:\n"));
+ dump_data(1, ntlmv2_session_key.data, ntlmv2_session_key.length);
+ pass = False;
+ }
+ return pass;
+}
+
+/*
+ * Test the NTLMv2 and LMv2 responses
+ */
+
+static bool test_lmv2_ntlmv2(bool lanman_support_expected)
+{
+ return test_lmv2_ntlmv2_broken(BREAK_NONE);
+}
+
+/*
+ * Test the LMv2 response only
+ */
+
+static bool test_lmv2(bool lanman_support_expected)
+{
+ return test_lmv2_ntlmv2_broken(NO_NT);
+}
+
+/*
+ * Test the NTLMv2 response only
+ */
+
+static bool test_ntlmv2(bool lanman_support_expected)
+{
+ return test_lmv2_ntlmv2_broken(NO_LM);
+}
+
+static bool test_lm_ntlm(bool lanman_support_expected)
+{
+ return test_lm_ntlm_broken(BREAK_NONE, lanman_support_expected);
+}
+
+static bool test_ntlm_lm_broken(bool lanman_support_expected)
+{
+ return test_lm_ntlm_broken(BREAK_LM, lanman_support_expected);
+}
+
+static bool test_ntlm_ntlm_broken(bool lanman_support_expected)
+{
+ return test_lm_ntlm_broken(BREAK_NT, lanman_support_expected);
+}
+
+static bool test_ntlmv2_lmv2_broken(bool lanman_support_expected)
+{
+ return test_lmv2_ntlmv2_broken(BREAK_LM);
+}
+
+static bool test_ntlmv2_ntlmv2_broken(bool lanman_support_expected)
+{
+ return test_lmv2_ntlmv2_broken(BREAK_NT);
+}
+
+static bool test_plaintext(enum ntlm_break break_which)
+{
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+ DATA_BLOB nt_response = data_blob_null;
+ DATA_BLOB lm_response = data_blob_null;
+ char *password;
+ smb_ucs2_t *nt_response_ucs2;
+ size_t converted_size;
+ uint8_t authoritative = 1;
+ uchar user_session_key[16];
+ uchar lm_key[16];
+ static const uchar zeros[8] = { 0, };
+ DATA_BLOB chall = data_blob(zeros, sizeof(zeros));
+ char *error_string;
+
+ ZERO_STRUCT(user_session_key);
+
+ flags |= WBFLAG_PAM_LMKEY;
+ flags |= WBFLAG_PAM_USER_SESSION_KEY;
+
+ if (!push_ucs2_talloc(talloc_tos(), &nt_response_ucs2, opt_password,
+ &converted_size))
+ {
+ DEBUG(0, ("push_ucs2_talloc failed!\n"));
+ exit(1);
+ }
+
+ nt_response.data = (unsigned char *)nt_response_ucs2;
+ nt_response.length = strlen_w(nt_response_ucs2)*sizeof(smb_ucs2_t);
+
+ if ((password = strupper_talloc(talloc_tos(), opt_password)) == NULL) {
+ DEBUG(0, ("strupper_talloc() failed!\n"));
+ exit(1);
+ }
+
+ if (!convert_string_talloc(talloc_tos(), CH_UNIX,
+ CH_DOS, password,
+ strlen(password)+1,
+ &lm_response.data,
+ &lm_response.length)) {
+ DEBUG(0, ("convert_string_talloc failed!\n"));
+ exit(1);
+ }
+
+ TALLOC_FREE(password);
+
+ switch (break_which) {
+ case BREAK_NONE:
+ break;
+ case BREAK_LM:
+ lm_response.data[0]++;
+ break;
+ case BREAK_NT:
+ nt_response.data[0]++;
+ break;
+ case NO_LM:
+ TALLOC_FREE(lm_response.data);
+ lm_response.length = 0;
+ break;
+ case NO_NT:
+ TALLOC_FREE(nt_response.data);
+ nt_response.length = 0;
+ break;
+ }
+
+ nt_status = contact_winbind_auth_crap(opt_username, opt_domain,
+ opt_workstation,
+ &chall,
+ &lm_response,
+ &nt_response,
+ flags, MSV1_0_CLEARTEXT_PASSWORD_ALLOWED,
+ lm_key,
+ user_session_key,
+ &authoritative,
+ &error_string, NULL);
+
+ TALLOC_FREE(nt_response.data);
+ TALLOC_FREE(lm_response.data);
+ data_blob_free(&chall);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ d_printf("%s (0x%x)\n",
+ error_string,
+ NT_STATUS_V(nt_status));
+ SAFE_FREE(error_string);
+ return break_which == BREAK_NT;
+ }
+
+ return break_which != BREAK_NT;
+}
+
+static bool test_plaintext_none_broken(bool lanman_support_expected) {
+ return test_plaintext(BREAK_NONE);
+}
+
+static bool test_plaintext_lm_broken(bool lanman_support_expected) {
+ return test_plaintext(BREAK_LM);
+}
+
+static bool test_plaintext_nt_broken(bool lanman_support_expected) {
+ return test_plaintext(BREAK_NT);
+}
+
+static bool test_plaintext_nt_only(bool lanman_support_expected) {
+ return test_plaintext(NO_LM);
+}
+
+static bool test_plaintext_lm_only(bool lanman_support_expected) {
+ return test_plaintext(NO_NT);
+}
+
+/*
+ Tests:
+
+ - LM only
+ - NT and LM
+ - NT
+ - NT in LM field
+ - NT in both fields
+ - NTLMv2
+ - NTLMv2 and LMv2
+ - LMv2
+ - plaintext tests (in challenge-response fields)
+
+ check we get the correct session key in each case
+ check what values we get for the LM session key
+
+*/
+
+static const struct ntlm_tests {
+ bool (*fn)(bool lanman_support_expected);
+ const char *name;
+ bool lanman;
+} test_table[] = {
+ {
+ .fn = test_lm,
+ .name = "LM",
+ .lanman = true
+ },
+ {
+ .fn = test_lm_ntlm,
+ .name = "LM and NTLM"
+ },
+ {
+ .fn = test_ntlm,
+ .name = "NTLM"
+ },
+ {
+ .fn = test_ntlm_in_lm,
+ .name = "NTLM in LM"
+ },
+ {
+ .fn = test_ntlm_in_both,
+ .name = "NTLM in both"
+ },
+ {
+ .fn = test_ntlmv2,
+ .name = "NTLMv2"
+ },
+ {
+ .fn = test_lmv2_ntlmv2,
+ .name = "NTLMv2 and LMv2"
+ },
+ {
+ .fn = test_lmv2,
+ .name = "LMv2"
+ },
+ {
+ .fn = test_ntlmv2_lmv2_broken,
+ .name = "NTLMv2 and LMv2, LMv2 broken"
+ },
+ {
+ .fn = test_ntlmv2_ntlmv2_broken,
+ .name = "NTLMv2 and LMv2, NTLMv2 broken"
+ },
+ {
+ .fn = test_ntlm_lm_broken,
+ .name = "NTLM and LM, LM broken"
+ },
+ {
+ .fn = test_ntlm_ntlm_broken,
+ .name = "NTLM and LM, NTLM broken"
+ },
+ {
+ .fn = test_plaintext_none_broken,
+ .name = "Plaintext"
+ },
+ {
+ .fn = test_plaintext_lm_broken,
+ .name = "Plaintext LM broken"
+ },
+ {
+ .fn = test_plaintext_nt_broken,
+ .name = "Plaintext NT broken"
+ },
+ {
+ .fn = test_plaintext_nt_only,
+ .name = "Plaintext NT only"
+ },
+ {
+ .fn = test_plaintext_lm_only,
+ .name = "Plaintext LM only",
+ .lanman = true
+ },
+ {
+ .fn = NULL
+ }
+};
+
+bool diagnose_ntlm_auth(bool lanman_support_expected)
+{
+ unsigned int i;
+ bool pass = True;
+
+ for (i=0; test_table[i].fn; i++) {
+ bool test_pass = test_table[i].fn(lanman_support_expected);
+ if (!lanman_support_expected
+ && test_table[i].lanman) {
+ if (test_pass) {
+ DBG_ERR("Test %s unexpectedly passed "
+ "(server should have rejected LM)!\n",
+ test_table[i].name);
+ pass = false;
+ }
+ } else if (!test_pass) {
+ DBG_ERR("Test %s failed!\n", test_table[i].name);
+ pass = False;
+ }
+ }
+
+ return pass;
+}
+
diff --git a/source3/utils/ntlm_auth_proto.h b/source3/utils/ntlm_auth_proto.h
new file mode 100644
index 0000000..ed6d5f4
--- /dev/null
+++ b/source3/utils/ntlm_auth_proto.h
@@ -0,0 +1,51 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * collected prototypes header
+ *
+ * frozen from "make proto" in May 2008
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NTLM_AUTH_PROTO_H_
+#define _NTLM_AUTH_PROTO_H_
+
+
+/* The following definitions come from utils/ntlm_auth.c */
+
+const char *get_winbind_domain(void);
+const char *get_winbind_netbios_name(void);
+DATA_BLOB get_challenge(void) ;
+NTSTATUS contact_winbind_auth_crap(const char *username,
+ const char *domain,
+ const char *workstation,
+ const DATA_BLOB *challenge,
+ const DATA_BLOB *lm_response,
+ const DATA_BLOB *nt_response,
+ uint32_t flags,
+ uint32_t extra_logon_parameters,
+ uint8_t lm_key[8],
+ uint8_t user_session_key[16],
+ uint8_t *pauthoritative,
+ char **error_string,
+ char **unix_name);
+
+/* The following definitions come from utils/ntlm_auth_diagnostics.c */
+
+bool diagnose_ntlm_auth(bool lanman_support_expected);
+int get_pam_winbind_config(void);
+
+#endif /* _NTLM_AUTH_PROTO_H_ */
diff --git a/source3/utils/passwd_proto.h b/source3/utils/passwd_proto.h
new file mode 100644
index 0000000..4099899
--- /dev/null
+++ b/source3/utils/passwd_proto.h
@@ -0,0 +1,31 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * collected prototypes header
+ *
+ * frozen from "make proto" in May 2008
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _PASSWD_PROTO_H_
+#define _PASSWD_PROTO_H_
+
+
+/* The following definitions come from utils/passwd_util.c */
+
+char *get_pass( const char *prompt, bool stdin_get);
+
+#endif /* _PASSWD_PROTO_H_ */
diff --git a/source3/utils/passwd_util.c b/source3/utils/passwd_util.c
new file mode 100644
index 0000000..edd2c52
--- /dev/null
+++ b/source3/utils/passwd_util.c
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ passdb editing frontend
+
+ Copyright (C) Jeremy Allison 1998
+ Copyright (C) Andrew Tridgell 1998
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Simo Sorce 2000
+ Copyright (C) Martin Pool 2001
+ Copyright (C) Gerald Carter 2002
+ Copyright (C) Andrew Bartlett 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "passwd_proto.h"
+
+/*************************************************************
+ Utility function to prompt for passwords from stdin. Each
+ password entered must end with a newline.
+*************************************************************/
+static char *stdin_new_passwd( void)
+{
+ static fstring new_pw;
+ size_t len;
+
+ ZERO_ARRAY(new_pw);
+
+ /*
+ * if no error is reported from fgets() and string at least contains
+ * the newline that ends the password, then replace the newline with
+ * a null terminator.
+ */
+ if ( fgets(new_pw, sizeof(new_pw), stdin) == NULL) {
+ return NULL;
+ }
+ if ((len = strlen(new_pw)) > 0) {
+ if(new_pw[len-1] == '\n')
+ new_pw[len - 1] = 0;
+ }
+ return(new_pw);
+}
+
+/*************************************************************
+ Utility function to get passwords via tty or stdin
+ Used if the '-s' (smbpasswd) or '-t' (pdbedit) option is set
+ to silently get passwords to enable scripting.
+*************************************************************/
+char *get_pass( const char *prompt, bool stdin_get)
+{
+ char pwd[256] = {0};
+ char *p;
+ int rc;
+
+ if (stdin_get) {
+ p = stdin_new_passwd();
+ if (p == NULL) {
+ return NULL;
+ }
+ } else {
+ rc = samba_getpass(prompt, pwd, sizeof(pwd), false, false);
+ if (rc < 0) {
+ return NULL;
+ }
+ p = pwd;
+ }
+ return smb_xstrdup( p);
+}
diff --git a/source3/utils/pdbedit.c b/source3/utils/pdbedit.c
new file mode 100644
index 0000000..eb6b982
--- /dev/null
+++ b/source3/utils/pdbedit.c
@@ -0,0 +1,1414 @@
+/*
+ Unix SMB/CIFS implementation.
+ passdb editing frontend
+
+ Copyright (C) Simo Sorce 2000-2009
+ Copyright (C) Andrew Bartlett 2001
+ Copyright (C) Jelmer Vernooij 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "../librpc/gen_ndr/samr.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "cmdline_contexts.h"
+#include "passwd_proto.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/param/param.h"
+
+#define BIT_BACKEND 0x00000004
+#define BIT_VERBOSE 0x00000008
+#define BIT_SPSTYLE 0x00000010
+#define BIT_CAN_CHANGE 0x00000020
+#define BIT_MUST_CHANGE 0x00000040
+#define BIT_USERSIDS 0x00000080
+#define BIT_FULLNAME 0x00000100
+#define BIT_HOMEDIR 0x00000200
+#define BIT_HDIRDRIVE 0x00000400
+#define BIT_LOGSCRIPT 0x00000800
+#define BIT_PROFILE 0x00001000
+#define BIT_MACHINE 0x00002000
+#define BIT_USERDOMAIN 0x00004000
+#define BIT_USER 0x00008000
+#define BIT_LIST 0x00010000
+#define BIT_MODIFY 0x00020000
+#define BIT_CREATE 0x00040000
+#define BIT_DELETE 0x00080000
+#define BIT_ACCPOLICY 0x00100000
+#define BIT_ACCPOLVAL 0x00200000
+#define BIT_ACCTCTRL 0x00400000
+#define BIT_RESERV_7 0x00800000
+#define BIT_IMPORT 0x01000000
+#define BIT_EXPORT 0x02000000
+#define BIT_FIX_INIT 0x04000000
+#define BIT_BADPWRESET 0x08000000
+#define BIT_LOGONHOURS 0x10000000
+#define BIT_KICKOFFTIME 0x20000000
+#define BIT_DESCRIPTION 0x40000000
+#define BIT_PWSETNTHASH 0x80000000
+
+#define MASK_ALWAYS_GOOD 0x0000001F
+#define MASK_USER_GOOD 0xE0405FE0
+
+static int get_sid_from_cli_string(struct dom_sid *sid, const char *str_sid)
+{
+ uint32_t rid;
+
+ if (!string_to_sid(sid, str_sid)) {
+ /* not a complete sid, may be a RID,
+ * try building a SID */
+
+ if (sscanf(str_sid, "%u", &rid) != 1) {
+ fprintf(stderr, "Error passed string is not "
+ "a complete SID or RID!\n");
+ return -1;
+ }
+ sid_compose(sid, get_global_sam_sid(), rid);
+ }
+
+ return 0;
+}
+
+/*********************************************************
+ Add all currently available users to another db
+ ********************************************************/
+
+static int export_database (struct pdb_methods *in,
+ struct pdb_methods *out,
+ const char *username)
+{
+ NTSTATUS status;
+ struct pdb_search *u_search;
+ struct samr_displayentry userentry;
+
+ DEBUG(3, ("export_database: username=\"%s\"\n", username ? username : "(NULL)"));
+
+ u_search = pdb_search_init(talloc_tos(), PDB_USER_SEARCH);
+ if (u_search == NULL) {
+ DEBUG(0, ("pdb_search_init failed\n"));
+ return 1;
+ }
+
+ if (!in->search_users(in, u_search, 0)) {
+ DEBUG(0, ("Could not start searching users\n"));
+ TALLOC_FREE(u_search);
+ return 1;
+ }
+
+ while (u_search->next_entry(u_search, &userentry)) {
+ struct samu *user;
+ struct samu *account;
+ struct dom_sid user_sid;
+
+ DEBUG(4, ("Processing account %s\n", userentry.account_name));
+
+ if ((username != NULL)
+ && (strcmp(username, userentry.account_name) != 0)) {
+ /*
+ * ignore unwanted users
+ */
+ continue;
+ }
+
+ user = samu_new(talloc_tos());
+ if (user == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ break;
+ }
+
+ sid_compose(&user_sid, get_global_sam_sid(), userentry.rid);
+
+ status = in->getsampwsid(in, user, &user_sid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("getsampwsid failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(user);
+ continue;
+ }
+
+ account = samu_new(NULL);
+ if (account == NULL) {
+ fprintf(stderr, "export_database: Memory allocation "
+ "failure!\n");
+ TALLOC_FREE( user );
+ TALLOC_FREE(u_search);
+ return 1;
+ }
+
+ printf("Importing account for %s...", user->username);
+ status = out->getsampwnam(out, account, user->username);
+
+ if (NT_STATUS_IS_OK(status)) {
+ status = out->update_sam_account( out, user );
+ } else {
+ status = out->add_sam_account(out, user);
+ }
+
+ if ( NT_STATUS_IS_OK(status) ) {
+ printf( "ok\n");
+ } else {
+ printf( "failed\n");
+ }
+
+ TALLOC_FREE( account );
+ TALLOC_FREE( user );
+ }
+
+ TALLOC_FREE(u_search);
+
+ return 0;
+}
+
+/*********************************************************
+ Add all currently available group mappings to another db
+ ********************************************************/
+
+static int export_groups (struct pdb_methods *in, struct pdb_methods *out)
+{
+ GROUP_MAP **maps = NULL;
+ size_t i, entries = 0;
+ NTSTATUS status;
+
+ status = in->enum_group_mapping(in, get_global_sam_sid(),
+ SID_NAME_DOM_GRP, &maps, &entries, False);
+
+ if ( NT_STATUS_IS_ERR(status) ) {
+ fprintf(stderr, "Unable to enumerate group map entries.\n");
+ return 1;
+ }
+
+ for (i=0; i<entries; i++) {
+ out->add_group_mapping_entry(out, maps[i]);
+ }
+
+ TALLOC_FREE(maps);
+
+ return 0;
+}
+
+/*********************************************************
+ Reset account policies to their default values and remove marker
+ ********************************************************/
+
+static int reinit_account_policies (void)
+{
+ int i;
+
+ for (i=1; decode_account_policy_name(i) != NULL; i++) {
+ uint32_t policy_value;
+ if (!account_policy_get_default(i, &policy_value)) {
+ fprintf(stderr, "Can't get default account policy\n");
+ return -1;
+ }
+ if (!account_policy_set(i, policy_value)) {
+ fprintf(stderr, "Can't set account policy in tdb\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+/*********************************************************
+ Add all currently available account policy from tdb to one backend
+ ********************************************************/
+
+static int export_account_policies (struct pdb_methods *in, struct pdb_methods *out)
+{
+ int i;
+
+ for ( i=1; decode_account_policy_name(i) != NULL; i++ ) {
+ uint32_t policy_value;
+ NTSTATUS status;
+
+ status = in->get_account_policy(in, i, &policy_value);
+
+ if ( NT_STATUS_IS_ERR(status) ) {
+ fprintf(stderr, "Unable to get account policy from %s\n", in->name);
+ return -1;
+ }
+
+ status = out->set_account_policy(out, i, policy_value);
+
+ if ( NT_STATUS_IS_ERR(status) ) {
+ fprintf(stderr, "Unable to migrate account policy to %s\n", out->name);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+
+/*********************************************************
+ Print info from sam structure
+**********************************************************/
+
+static int print_sam_info (struct samu *sam_pwent, bool verbosity, bool smbpwdstyle)
+{
+ uid_t uid;
+ time_t tmp;
+
+ /* TODO: check if entry is a user or a workstation */
+ if (!sam_pwent) return -1;
+
+ if (verbosity) {
+ char temp[44];
+ const uint8_t *hours;
+ struct dom_sid_buf buf;
+
+ printf ("Unix username: %s\n", pdb_get_username(sam_pwent));
+ printf ("NT username: %s\n", pdb_get_nt_username(sam_pwent));
+ printf ("Account Flags: %s\n", pdb_encode_acct_ctrl(pdb_get_acct_ctrl(sam_pwent), NEW_PW_FORMAT_SPACE_PADDED_LEN));
+ printf ("User SID: %s\n",
+ dom_sid_str_buf(pdb_get_user_sid(sam_pwent), &buf));
+ printf ("Primary Group SID: %s\n",
+ dom_sid_str_buf(pdb_get_group_sid(sam_pwent), &buf));
+ printf ("Full Name: %s\n", pdb_get_fullname(sam_pwent));
+ printf ("Home Directory: %s\n", pdb_get_homedir(sam_pwent));
+ printf ("HomeDir Drive: %s\n", pdb_get_dir_drive(sam_pwent));
+ printf ("Logon Script: %s\n", pdb_get_logon_script(sam_pwent));
+ printf ("Profile Path: %s\n", pdb_get_profile_path(sam_pwent));
+ printf ("Domain: %s\n", pdb_get_domain(sam_pwent));
+ printf ("Account desc: %s\n", pdb_get_acct_desc(sam_pwent));
+ printf ("Workstations: %s\n", pdb_get_workstations(sam_pwent));
+ printf ("Munged dial: %s\n", pdb_get_munged_dial(sam_pwent));
+
+ tmp = pdb_get_logon_time(sam_pwent);
+ printf ("Logon time: %s\n",
+ tmp ? http_timestring(talloc_tos(), tmp) : "0");
+
+ tmp = pdb_get_logoff_time(sam_pwent);
+ printf ("Logoff time: %s\n",
+ tmp ? http_timestring(talloc_tos(), tmp) : "0");
+
+ tmp = pdb_get_kickoff_time(sam_pwent);
+ printf ("Kickoff time: %s\n",
+ tmp ? http_timestring(talloc_tos(), tmp) : "0");
+
+ tmp = pdb_get_pass_last_set_time(sam_pwent);
+ printf ("Password last set: %s\n",
+ tmp ? http_timestring(talloc_tos(), tmp) : "0");
+
+ tmp = pdb_get_pass_can_change_time(sam_pwent);
+ printf ("Password can change: %s\n",
+ tmp ? http_timestring(talloc_tos(), tmp) : "0");
+
+ tmp = pdb_get_pass_must_change_time(sam_pwent);
+ printf ("Password must change: %s\n",
+ tmp ? http_timestring(talloc_tos(), tmp) : "0");
+
+ tmp = pdb_get_bad_password_time(sam_pwent);
+ printf ("Last bad password : %s\n",
+ tmp ? http_timestring(talloc_tos(), tmp) : "0");
+ printf ("Bad password count : %d\n",
+ pdb_get_bad_password_count(sam_pwent));
+
+ hours = pdb_get_hours(sam_pwent);
+ pdb_sethexhours(temp, hours);
+ printf ("Logon hours : %s\n", temp);
+ if (smbpwdstyle){
+ pdb_sethexpwd(temp, pdb_get_lanman_passwd(sam_pwent), pdb_get_acct_ctrl(sam_pwent));
+ printf ("LM hash : %s\n", temp);
+ pdb_sethexpwd(temp, pdb_get_nt_passwd(sam_pwent), pdb_get_acct_ctrl(sam_pwent));
+ printf ("NT hash : %s\n", temp);
+ }
+
+ } else if (smbpwdstyle) {
+ char lm_passwd[33];
+ char nt_passwd[33];
+
+ uid = nametouid(pdb_get_username(sam_pwent));
+ pdb_sethexpwd(lm_passwd, pdb_get_lanman_passwd(sam_pwent), pdb_get_acct_ctrl(sam_pwent));
+ pdb_sethexpwd(nt_passwd, pdb_get_nt_passwd(sam_pwent), pdb_get_acct_ctrl(sam_pwent));
+
+ printf("%s:%lu:%s:%s:%s:LCT-%08X:\n",
+ pdb_get_username(sam_pwent),
+ (unsigned long)uid,
+ lm_passwd,
+ nt_passwd,
+ pdb_encode_acct_ctrl(pdb_get_acct_ctrl(sam_pwent),NEW_PW_FORMAT_SPACE_PADDED_LEN),
+ (uint32_t)convert_time_t_to_uint32_t(pdb_get_pass_last_set_time(sam_pwent)));
+ } else {
+ uid = nametouid(pdb_get_username(sam_pwent));
+ printf ("%s:%lu:%s\n", pdb_get_username(sam_pwent), (unsigned long)uid,
+ pdb_get_fullname(sam_pwent));
+ }
+
+ return 0;
+}
+
+/*********************************************************
+ Get an Print User Info
+**********************************************************/
+
+static int print_user_info(const char *username,
+ bool verbosity, bool smbpwdstyle)
+{
+ struct samu *sam_pwent = NULL;
+ bool bret;
+ int ret;
+
+ sam_pwent = samu_new(NULL);
+ if (!sam_pwent) {
+ return -1;
+ }
+
+ bret = pdb_getsampwnam(sam_pwent, username);
+ if (!bret) {
+ fprintf (stderr, "Username not found!\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+
+ ret = print_sam_info(sam_pwent, verbosity, smbpwdstyle);
+
+ TALLOC_FREE(sam_pwent);
+ return ret;
+}
+
+/*********************************************************
+ List Users
+**********************************************************/
+static int print_users_list(bool verbosity, bool smbpwdstyle)
+{
+ struct pdb_search *u_search;
+ struct samr_displayentry userentry;
+ struct samu *sam_pwent;
+ TALLOC_CTX *tosctx;
+ struct dom_sid user_sid;
+ bool bret;
+ int ret;
+
+ tosctx = talloc_tos();
+ if (!tosctx) {
+ DEBUG(0, ("talloc failed\n"));
+ return 1;
+ }
+
+ u_search = pdb_search_users(tosctx, 0);
+ if (!u_search) {
+ DEBUG(0, ("User Search failed!\n"));
+ ret = 1;
+ goto done;
+ }
+
+ while (u_search->next_entry(u_search, &userentry)) {
+
+ sam_pwent = samu_new(tosctx);
+ if (sam_pwent == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ ret = 1;
+ goto done;
+ }
+
+ sid_compose(&user_sid, get_global_sam_sid(), userentry.rid);
+
+ bret = pdb_getsampwsid(sam_pwent, &user_sid);
+ if (!bret) {
+ DEBUG(2, ("getsampwsid failed\n"));
+ TALLOC_FREE(sam_pwent);
+ continue;
+ }
+
+ if (verbosity) {
+ printf ("---------------\n");
+ }
+ print_sam_info(sam_pwent, verbosity, smbpwdstyle);
+ TALLOC_FREE(sam_pwent);
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(tosctx);
+ return ret;
+}
+
+/*********************************************************
+ Fix a list of Users for uninitialised passwords
+**********************************************************/
+static int fix_users_list(void)
+{
+ struct pdb_search *u_search;
+ struct samr_displayentry userentry;
+ struct samu *sam_pwent;
+ TALLOC_CTX *tosctx;
+ struct dom_sid user_sid;
+ NTSTATUS status;
+ bool bret;
+ int ret;
+
+ tosctx = talloc_tos();
+ if (!tosctx) {
+ fprintf(stderr, "Out of memory!\n");
+ return 1;
+ }
+
+ u_search = pdb_search_users(tosctx, 0);
+ if (!u_search) {
+ fprintf(stderr, "User Search failed!\n");
+ ret = 1;
+ goto done;
+ }
+
+ while (u_search->next_entry(u_search, &userentry)) {
+
+ sam_pwent = samu_new(tosctx);
+ if (sam_pwent == NULL) {
+ fprintf(stderr, "Out of memory!\n");
+ ret = 1;
+ goto done;
+ }
+
+ sid_compose(&user_sid, get_global_sam_sid(), userentry.rid);
+
+ bret = pdb_getsampwsid(sam_pwent, &user_sid);
+ if (!bret) {
+ DEBUG(2, ("getsampwsid failed\n"));
+ TALLOC_FREE(sam_pwent);
+ continue;
+ }
+
+ status = pdb_update_sam_account(sam_pwent);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Update of user %s failed!\n",
+ pdb_get_username(sam_pwent));
+ }
+ TALLOC_FREE(sam_pwent);
+ }
+
+ ret = 0;
+
+done:
+ TALLOC_FREE(tosctx);
+ return ret;
+}
+
+/*********************************************************
+ Set User Info
+**********************************************************/
+
+static int set_user_info(const char *username, const char *fullname,
+ const char *homedir, const char *acct_desc,
+ const char *drive, const char *script,
+ const char *profile, const char *account_control,
+ const char *user_sid, const char *user_domain,
+ const bool badpw, const bool hours,
+ const char *kickoff_time, const char *str_hex_pwd)
+{
+ bool updated_autolock = False, updated_badpw = False;
+ struct samu *sam_pwent;
+ uint8_t hours_array[MAX_HOURS_LEN];
+ uint32_t hours_len;
+ uint32_t acb_flags;
+ uint32_t not_settable;
+ uint32_t new_flags;
+ struct dom_sid u_sid;
+ bool ret;
+
+ sam_pwent = samu_new(NULL);
+ if (!sam_pwent) {
+ return 1;
+ }
+
+ ret = pdb_getsampwnam(sam_pwent, username);
+ if (!ret) {
+ fprintf (stderr, "Username not found!\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+
+ if (hours) {
+ hours_len = pdb_get_hours_len(sam_pwent);
+ memset(hours_array, 0xff, hours_len);
+
+ pdb_set_hours(sam_pwent, hours_array, hours_len, PDB_CHANGED);
+ }
+
+ if (!pdb_update_autolock_flag(sam_pwent, &updated_autolock)) {
+ DEBUG(2,("pdb_update_autolock_flag failed.\n"));
+ }
+
+ if (!pdb_update_bad_password_count(sam_pwent, &updated_badpw)) {
+ DEBUG(2,("pdb_update_bad_password_count failed.\n"));
+ }
+
+ if (fullname)
+ pdb_set_fullname(sam_pwent, fullname, PDB_CHANGED);
+ if (acct_desc)
+ pdb_set_acct_desc(sam_pwent, acct_desc, PDB_CHANGED);
+ if (homedir)
+ pdb_set_homedir(sam_pwent, homedir, PDB_CHANGED);
+ if (drive)
+ pdb_set_dir_drive(sam_pwent,drive, PDB_CHANGED);
+ if (script)
+ pdb_set_logon_script(sam_pwent, script, PDB_CHANGED);
+ if (profile)
+ pdb_set_profile_path (sam_pwent, profile, PDB_CHANGED);
+ if (user_domain)
+ pdb_set_domain(sam_pwent, user_domain, PDB_CHANGED);
+
+ if (account_control) {
+ not_settable = ~(ACB_DISABLED | ACB_HOMDIRREQ |
+ ACB_PWNOTREQ | ACB_PWNOEXP | ACB_AUTOLOCK);
+
+ new_flags = pdb_decode_acct_ctrl(account_control);
+
+ if (new_flags & not_settable) {
+ fprintf(stderr, "Can only set [NDHLX] flags\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+
+ acb_flags = pdb_get_acct_ctrl(sam_pwent);
+
+ pdb_set_acct_ctrl(sam_pwent,
+ (acb_flags & not_settable) | new_flags,
+ PDB_CHANGED);
+ }
+ if (user_sid) {
+ if (get_sid_from_cli_string(&u_sid, user_sid)) {
+ fprintf(stderr, "Failed to parse SID\n");
+ return -1;
+ }
+ pdb_set_user_sid(sam_pwent, &u_sid, PDB_CHANGED);
+ }
+
+ if (badpw) {
+ pdb_set_bad_password_count(sam_pwent, 0, PDB_CHANGED);
+ pdb_set_bad_password_time(sam_pwent, 0, PDB_CHANGED);
+ }
+
+ if (kickoff_time) {
+ time_t value = get_time_t_max();
+
+ if (strcmp(kickoff_time, "never") != 0) {
+ int error = 0;
+ uint32_t num;
+
+ num = smb_strtoul(kickoff_time,
+ NULL,
+ 10,
+ &error,
+ SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ fprintf(stderr, "Failed to parse kickoff time\n");
+ return -1;
+ }
+
+ value = convert_uint32_t_to_time_t(num);
+ }
+
+ pdb_set_kickoff_time(sam_pwent, value, PDB_CHANGED);
+ }
+ if (str_hex_pwd) {
+ unsigned char new_nt_p16[NT_HASH_LEN];
+ if(strlen(str_hex_pwd) != (NT_HASH_LEN *2)){
+ fprintf(stderr, "Invalid hash\n");
+ return -1;
+ }
+
+ pdb_gethexpwd(str_hex_pwd, new_nt_p16);
+
+ if (!pdb_set_nt_passwd (sam_pwent, new_nt_p16 , PDB_CHANGED)) {
+ fprintf(stderr, "Failed to set password from nt-hash\n");
+ return -1;
+ }
+
+ if (!pdb_set_pass_last_set_time (sam_pwent, time(NULL), PDB_CHANGED)){
+ fprintf(stderr, "Failed to set last password set time\n");
+ return -1;
+ }
+ if (!pdb_update_history(sam_pwent, new_nt_p16)){
+ fprintf(stderr, "Failed to update password history\n");
+ return -1;
+ }
+ }
+
+ if (NT_STATUS_IS_OK(pdb_update_sam_account(sam_pwent))) {
+
+ print_user_info(username, True, (str_hex_pwd != NULL ));
+ } else {
+ fprintf (stderr, "Unable to modify entry!\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+ TALLOC_FREE(sam_pwent);
+ return 0;
+}
+
+static int set_machine_info(const char *machinename,
+ const char *account_control,
+ const char *machine_sid)
+{
+ struct samu *sam_pwent = NULL;
+ TALLOC_CTX *tosctx;
+ uint32_t acb_flags;
+ uint32_t not_settable;
+ uint32_t new_flags;
+ struct dom_sid m_sid;
+ char *name;
+ int len;
+ bool ret;
+
+ len = strlen(machinename);
+ if (len == 0) {
+ fprintf(stderr, "No machine name given\n");
+ return -1;
+ }
+
+ tosctx = talloc_tos();
+ if (!tosctx) {
+ fprintf(stderr, "Out of memory!\n");
+ return -1;
+ }
+
+ sam_pwent = samu_new(tosctx);
+ if (!sam_pwent) {
+ return 1;
+ }
+
+ if (machinename[len-1] == '$') {
+ name = talloc_strdup(sam_pwent, machinename);
+ } else {
+ name = talloc_asprintf(sam_pwent, "%s$", machinename);
+ }
+ if (!name) {
+ fprintf(stderr, "Out of memory!\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+
+ if (!strlower_m(name)) {
+ fprintf(stderr, "strlower_m %s failed\n", name);
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+
+ ret = pdb_getsampwnam(sam_pwent, name);
+ if (!ret) {
+ fprintf (stderr, "Username not found!\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+
+ if (account_control) {
+ not_settable = ~(ACB_DISABLED);
+
+ new_flags = pdb_decode_acct_ctrl(account_control);
+
+ if (new_flags & not_settable) {
+ fprintf(stderr, "Can only set [D] flags\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+
+ acb_flags = pdb_get_acct_ctrl(sam_pwent);
+
+ pdb_set_acct_ctrl(sam_pwent,
+ (acb_flags & not_settable) | new_flags,
+ PDB_CHANGED);
+ }
+ if (machine_sid) {
+ if (get_sid_from_cli_string(&m_sid, machine_sid)) {
+ fprintf(stderr, "Failed to parse SID\n");
+ return -1;
+ }
+ pdb_set_user_sid(sam_pwent, &m_sid, PDB_CHANGED);
+ }
+
+ if (NT_STATUS_IS_OK(pdb_update_sam_account(sam_pwent))) {
+ print_user_info(name, True, False);
+ } else {
+ fprintf (stderr, "Unable to modify entry!\n");
+ TALLOC_FREE(sam_pwent);
+ return -1;
+ }
+ TALLOC_FREE(sam_pwent);
+ return 0;
+}
+
+/*********************************************************
+ Add New User
+**********************************************************/
+static int new_user(const char *username, const char *fullname,
+ const char *homedir, const char *drive,
+ const char *script, const char *profile,
+ char *user_sid, bool stdin_get)
+{
+ char *pwd1 = NULL, *pwd2 = NULL;
+ char *err = NULL, *msg = NULL;
+ struct samu *sam_pwent = NULL;
+ TALLOC_CTX *tosctx;
+ NTSTATUS status;
+ struct dom_sid u_sid;
+ int flags;
+ int ret = -1;
+
+ tosctx = talloc_tos();
+ if (!tosctx) {
+ fprintf(stderr, "Out of memory!\n");
+ return -1;
+ }
+
+ if (user_sid) {
+ if (get_sid_from_cli_string(&u_sid, user_sid)) {
+ fprintf(stderr, "Failed to parse SID\n");
+ return -1;
+ }
+ }
+
+ pwd1 = get_pass( "new password:", stdin_get);
+ if (pwd1 == NULL) {
+ fprintf(stderr, "Failed to read passwords.\n");
+ goto done;
+ }
+ pwd2 = get_pass( "retype new password:", stdin_get);
+ if (pwd2 == NULL) {
+ fprintf(stderr, "Failed to read passwords.\n");
+ goto done;
+ }
+ ret = strcmp(pwd1, pwd2);
+ if (ret != 0) {
+ fprintf (stderr, "Passwords do not match!\n");
+ goto done;
+ }
+
+ flags = LOCAL_ADD_USER | LOCAL_SET_PASSWORD;
+
+ status = local_password_change(username, flags, pwd1, &err, &msg);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (err) fprintf(stderr, "%s", err);
+ ret = -1;
+ goto done;
+ }
+
+ sam_pwent = samu_new(tosctx);
+ if (!sam_pwent) {
+ fprintf(stderr, "Out of memory!\n");
+ ret = -1;
+ goto done;
+ }
+
+ if (!pdb_getsampwnam(sam_pwent, username)) {
+ fprintf(stderr, "User %s not found!\n", username);
+ ret = -1;
+ goto done;
+ }
+
+ if (fullname)
+ pdb_set_fullname(sam_pwent, fullname, PDB_CHANGED);
+ if (homedir)
+ pdb_set_homedir(sam_pwent, homedir, PDB_CHANGED);
+ if (drive)
+ pdb_set_dir_drive(sam_pwent, drive, PDB_CHANGED);
+ if (script)
+ pdb_set_logon_script(sam_pwent, script, PDB_CHANGED);
+ if (profile)
+ pdb_set_profile_path(sam_pwent, profile, PDB_CHANGED);
+ if (user_sid)
+ pdb_set_user_sid(sam_pwent, &u_sid, PDB_CHANGED);
+
+ status = pdb_update_sam_account(sam_pwent);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "Failed to modify entry for user %s.!\n",
+ username);
+ ret = -1;
+ goto done;
+ }
+
+ print_user_info(username, True, False);
+ ret = 0;
+
+done:
+ if (pwd1) memset(pwd1, 0, strlen(pwd1));
+ if (pwd2) memset(pwd2, 0, strlen(pwd2));
+ SAFE_FREE(pwd1);
+ SAFE_FREE(pwd2);
+ SAFE_FREE(err);
+ SAFE_FREE(msg);
+ TALLOC_FREE(sam_pwent);
+ return ret;
+}
+
+/*********************************************************
+ Add New Machine
+**********************************************************/
+
+static int new_machine(const char *machinename, char *machine_sid)
+{
+ char *err = NULL, *msg = NULL;
+ struct samu *sam_pwent = NULL;
+ TALLOC_CTX *tosctx;
+ NTSTATUS status;
+ struct dom_sid m_sid;
+ char *compatpwd;
+ char *name;
+ int flags;
+ int len;
+ int ret;
+
+ len = strlen(machinename);
+ if (len == 0) {
+ fprintf(stderr, "No machine name given\n");
+ return -1;
+ }
+
+ tosctx = talloc_tos();
+ if (!tosctx) {
+ fprintf(stderr, "Out of memory!\n");
+ return -1;
+ }
+
+ if (machine_sid) {
+ if (get_sid_from_cli_string(&m_sid, machine_sid)) {
+ fprintf(stderr, "Failed to parse SID\n");
+ return -1;
+ }
+ }
+
+ compatpwd = talloc_strdup(tosctx, machinename);
+ if (!compatpwd) {
+ fprintf(stderr, "Out of memory!\n");
+ return -1;
+ }
+
+ if (machinename[len-1] == '$') {
+ name = talloc_strdup(tosctx, machinename);
+ compatpwd[len-1] = '\0';
+ } else {
+ name = talloc_asprintf(tosctx, "%s$", machinename);
+ }
+ if (!name) {
+ fprintf(stderr, "Out of memory!\n");
+ return -1;
+ }
+
+ if (!strlower_m(name)) {
+ fprintf(stderr, "strlower_m %s failed\n", name);
+ return -1;
+ }
+
+ flags = LOCAL_ADD_USER | LOCAL_TRUST_ACCOUNT | LOCAL_SET_PASSWORD;
+
+ status = local_password_change(name, flags, compatpwd, &err, &msg);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (err) fprintf(stderr, "%s", err);
+ ret = -1;
+ }
+
+ sam_pwent = samu_new(tosctx);
+ if (!sam_pwent) {
+ fprintf(stderr, "Out of memory!\n");
+ ret = -1;
+ goto done;
+ }
+
+ if (!pdb_getsampwnam(sam_pwent, name)) {
+ fprintf(stderr, "Machine %s not found!\n", name);
+ ret = -1;
+ goto done;
+ }
+
+ if (machine_sid)
+ pdb_set_user_sid(sam_pwent, &m_sid, PDB_CHANGED);
+
+ status = pdb_update_sam_account(sam_pwent);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr,
+ "Failed to modify entry for %s.!\n", name);
+ ret = -1;
+ goto done;
+ }
+
+ print_user_info(name, True, False);
+ ret = 0;
+
+done:
+ SAFE_FREE(err);
+ SAFE_FREE(msg);
+ TALLOC_FREE(sam_pwent);
+ return ret;
+}
+
+/*********************************************************
+ Delete user entry
+**********************************************************/
+
+static int delete_user_entry(const char *username)
+{
+ struct samu *samaccount;
+
+ samaccount = samu_new(NULL);
+ if (!samaccount) {
+ fprintf(stderr, "Out of memory!\n");
+ return -1;
+ }
+
+ if (!pdb_getsampwnam(samaccount, username)) {
+ fprintf (stderr,
+ "user %s does not exist in the passdb\n", username);
+ TALLOC_FREE(samaccount);
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(pdb_delete_sam_account(samaccount))) {
+ fprintf (stderr, "Unable to delete user %s\n", username);
+ TALLOC_FREE(samaccount);
+ return -1;
+ }
+
+ TALLOC_FREE(samaccount);
+ return 0;
+}
+
+/*********************************************************
+ Delete machine entry
+**********************************************************/
+
+static int delete_machine_entry(const char *machinename)
+{
+ struct samu *samaccount = NULL;
+ const char *name;
+
+ if (strlen(machinename) == 0) {
+ fprintf(stderr, "No machine name given\n");
+ return -1;
+ }
+
+ samaccount = samu_new(NULL);
+ if (!samaccount) {
+ fprintf(stderr, "Out of memory!\n");
+ return -1;
+ }
+
+ if (machinename[strlen(machinename)-1] != '$') {
+ name = talloc_asprintf(samaccount, "%s$", machinename);
+ } else {
+ name = machinename;
+ }
+
+ if (!pdb_getsampwnam(samaccount, name)) {
+ fprintf (stderr,
+ "machine %s does not exist in the passdb\n", name);
+ TALLOC_FREE(samaccount);
+ return -1;
+ }
+
+ if (!NT_STATUS_IS_OK(pdb_delete_sam_account(samaccount))) {
+ fprintf (stderr, "Unable to delete machine %s\n", name);
+ TALLOC_FREE(samaccount);
+ return -1;
+ }
+
+ TALLOC_FREE(samaccount);
+ return 0;
+}
+
+/*********************************************************
+ Start here.
+**********************************************************/
+
+int main(int argc, const char **argv)
+{
+ static int list_users = False;
+ static int verbose = False;
+ static int spstyle = False;
+ static int machine = False;
+ static int add_user = False;
+ static int delete_user = False;
+ static int modify_user = False;
+ uint32_t setparms, checkparms;
+ int opt;
+ static char *full_name = NULL;
+ static char *acct_desc = NULL;
+ static const char *user_name = NULL;
+ static char *home_dir = NULL;
+ static char *home_drive = NULL;
+ static const char *backend = NULL;
+ static char *backend_in = NULL;
+ static char *backend_out = NULL;
+ static int transfer_groups = False;
+ static int transfer_account_policies = False;
+ static int reset_account_policies = False;
+ static int force_initialised_password = False;
+ static char *logon_script = NULL;
+ static char *profile_path = NULL;
+ static char *user_domain = NULL;
+ static char *account_control = NULL;
+ static char *account_policy = NULL;
+ static char *user_sid = NULL;
+ static char *machine_sid = NULL;
+ static long int account_policy_value = 0;
+ bool account_policy_value_set = False;
+ static int badpw_reset = False;
+ static int hours_reset = False;
+ static char *pwd_time_format = NULL;
+ static int pw_from_stdin = False;
+ struct pdb_methods *bin, *bout;
+ static char *kickoff_time = NULL;
+ static char *str_hex_pwd = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+ NTSTATUS status;
+ poptContext pc;
+ bool ok;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {"list", 'L', POPT_ARG_NONE, &list_users, 0, "list all users", NULL},
+ {"verbose", 'v', POPT_ARG_NONE, &verbose, 0, "be verbose", NULL },
+ {"smbpasswd-style", 'w',POPT_ARG_NONE, &spstyle, 0, "give output in smbpasswd style", NULL},
+ {"user", 'u', POPT_ARG_STRING, &user_name, 0, "use username", "USER" },
+ {"account-desc", 'N', POPT_ARG_STRING, &acct_desc, 0, "set account description", NULL},
+ {"fullname", 'f', POPT_ARG_STRING, &full_name, 0, "set full name", NULL},
+ {"homedir", 'h', POPT_ARG_STRING, &home_dir, 0, "set home directory", NULL},
+ {"drive", 'D', POPT_ARG_STRING, &home_drive, 0, "set home drive", NULL},
+ {"script", 'S', POPT_ARG_STRING, &logon_script, 0, "set logon script", NULL},
+ {"profile", 'p', POPT_ARG_STRING, &profile_path, 0, "set profile path", NULL},
+ {"domain", 'I', POPT_ARG_STRING, &user_domain, 0, "set a users' domain", NULL},
+ {"user SID", 'U', POPT_ARG_STRING, &user_sid, 0, "set user SID or RID", NULL},
+ {"machine SID", 'M', POPT_ARG_STRING, &machine_sid, 0, "set machine SID or RID", NULL},
+ {"create", 'a', POPT_ARG_NONE, &add_user, 0, "create user", NULL},
+ {"modify", 'r', POPT_ARG_NONE, &modify_user, 0, "modify user", NULL},
+ {"machine", 'm', POPT_ARG_NONE, &machine, 0, "account is a machine account", NULL},
+ {"delete", 'x', POPT_ARG_NONE, &delete_user, 0, "delete user", NULL},
+ {"backend", 'b', POPT_ARG_STRING, &backend, 0, "use different passdb backend as default backend", NULL},
+ {"import", 'i', POPT_ARG_STRING, &backend_in, 0, "import user accounts from this backend", NULL},
+ {"export", 'e', POPT_ARG_STRING, &backend_out, 0, "export user accounts to this backend", NULL},
+ {"group", 'g', POPT_ARG_NONE, &transfer_groups, 0, "use -i and -e for groups", NULL},
+ {"policies", 'y', POPT_ARG_NONE, &transfer_account_policies, 0, "use -i and -e to move account policies between backends", NULL},
+ {"policies-reset", 0, POPT_ARG_NONE, &reset_account_policies, 0, "restore default policies", NULL},
+ {"account-policy", 'P', POPT_ARG_STRING, &account_policy, 0,"value of an account policy (like maximum password age)",NULL},
+ {"value", 'C', POPT_ARG_LONG, &account_policy_value, 'C',"set the account policy to this value", NULL},
+ {"account-control", 'c', POPT_ARG_STRING, &account_control, 0, "Values of account control", NULL},
+ {"force-initialized-passwords", 0, POPT_ARG_NONE, &force_initialised_password, 0, "Force initialization of corrupt password strings in a passdb backend", NULL},
+ {"bad-password-count-reset", 'z', POPT_ARG_NONE, &badpw_reset, 0, "reset bad password count", NULL},
+ {"logon-hours-reset", 'Z', POPT_ARG_NONE, &hours_reset, 0, "reset logon hours", NULL},
+ {"time-format", 0, POPT_ARG_STRING, &pwd_time_format, 0, "The time format for time parameters", NULL },
+ {"password-from-stdin", 't', POPT_ARG_NONE, &pw_from_stdin, 0, "get password from standard in", NULL},
+ {"kickoff-time", 'K', POPT_ARG_STRING, &kickoff_time, 0, "set the kickoff time", NULL},
+ {"set-nt-hash", 0, POPT_ARG_STRING, &str_hex_pwd, 0, "set password from nt-hash", NULL},
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ bin = bout = NULL;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'C':
+ account_policy_value_set = True;
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptGetArg(pc); /* Drop argv[0], the program name */
+
+ if (user_name == NULL) {
+ if (poptPeekArg(pc)) {
+ user_name = talloc_strdup(frame, poptGetArg(pc));
+ if (user_name == NULL) {
+ fprintf(stderr, "out of memory\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ }
+ }
+
+ setparms = (backend ? BIT_BACKEND : 0) +
+ (verbose ? BIT_VERBOSE : 0) +
+ (spstyle ? BIT_SPSTYLE : 0) +
+ (full_name ? BIT_FULLNAME : 0) +
+ (home_dir ? BIT_HOMEDIR : 0) +
+ (home_drive ? BIT_HDIRDRIVE : 0) +
+ (logon_script ? BIT_LOGSCRIPT : 0) +
+ (profile_path ? BIT_PROFILE : 0) +
+ (user_domain ? BIT_USERDOMAIN : 0) +
+ (machine ? BIT_MACHINE : 0) +
+ (user_name ? BIT_USER : 0) +
+ (list_users ? BIT_LIST : 0) +
+ (force_initialised_password ? BIT_FIX_INIT : 0) +
+ (user_sid ? BIT_USERSIDS : 0) +
+ (machine_sid ? BIT_USERSIDS : 0) +
+ (modify_user ? BIT_MODIFY : 0) +
+ (add_user ? BIT_CREATE : 0) +
+ (delete_user ? BIT_DELETE : 0) +
+ (account_control ? BIT_ACCTCTRL : 0) +
+ (account_policy ? BIT_ACCPOLICY : 0) +
+ (account_policy_value_set ? BIT_ACCPOLVAL : 0) +
+ (backend_in ? BIT_IMPORT : 0) +
+ (backend_out ? BIT_EXPORT : 0) +
+ (badpw_reset ? BIT_BADPWRESET : 0) +
+ (hours_reset ? BIT_LOGONHOURS : 0) +
+ (kickoff_time ? BIT_KICKOFFTIME : 0) +
+ (str_hex_pwd ? BIT_PWSETNTHASH : 0 ) +
+ (acct_desc ? BIT_DESCRIPTION : 0);
+
+
+ if (setparms & BIT_BACKEND) {
+ /* HACK: set the global passdb backend by overwriting globals.
+ * This way we can use regular pdb functions for default
+ * operations that do not involve passdb migrations */
+ lpcfg_set_cmdline(lp_ctx, "passdb backend", backend);
+ } else {
+ backend = lp_passdb_backend();
+ }
+
+ if (!initialize_password_db(False, NULL)) {
+ fprintf(stderr, "Can't initialize passdb backend.\n");
+ exit(1);
+ }
+
+ /* the lowest bit options are always accepted */
+ checkparms = setparms & ~MASK_ALWAYS_GOOD;
+
+ if (checkparms & BIT_FIX_INIT) {
+ poptFreeContext(pc);
+ return fix_users_list();
+ }
+
+ /* account policy operations */
+ if ((checkparms & BIT_ACCPOLICY) && !(checkparms & ~(BIT_ACCPOLICY + BIT_ACCPOLVAL))) {
+ uint32_t value;
+ enum pdb_policy_type field = account_policy_name_to_typenum(account_policy);
+ if (field == 0) {
+ const char **names;
+ int count;
+ int i;
+ account_policy_names_list(talloc_tos(), &names, &count);
+ fprintf(stderr, "No account policy by that name!\n");
+ if (count !=0) {
+ fprintf(stderr, "Account policy names are:\n");
+ for (i = 0; i < count ; i++) {
+ d_fprintf(stderr, "%s\n", names[i]);
+ }
+ }
+ TALLOC_FREE(names);
+ exit(1);
+ }
+ if (!pdb_get_account_policy(field, &value)) {
+ fprintf(stderr, "valid account policy, but unable to fetch value!\n");
+ if (!account_policy_value_set)
+ exit(1);
+ }
+ printf("account policy \"%s\" description: %s\n", account_policy, account_policy_get_desc(field));
+ if (account_policy_value_set) {
+ printf("account policy \"%s\" value was: %u\n", account_policy, value);
+ if (!pdb_set_account_policy(field, account_policy_value)) {
+ fprintf(stderr, "valid account policy, but unable to set value!\n");
+ exit(1);
+ }
+ printf("account policy \"%s\" value is now: %lu\n", account_policy, account_policy_value);
+ exit(0);
+ } else {
+ printf("account policy \"%s\" value is: %u\n", account_policy, value);
+ exit(0);
+ }
+ }
+
+ if (reset_account_policies) {
+ if (reinit_account_policies()) {
+ exit(1);
+ }
+
+ exit(0);
+ }
+
+ /* import and export operations */
+
+ if (((checkparms & BIT_IMPORT) ||
+ (checkparms & BIT_EXPORT)) &&
+ !(checkparms & ~(BIT_IMPORT +BIT_EXPORT +BIT_USER))) {
+
+ poptFreeContext(pc);
+
+ if (backend_in) {
+ status = make_pdb_method_name(&bin, backend_in);
+ } else {
+ status = make_pdb_method_name(&bin, backend);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "Unable to initialize %s.\n",
+ backend_in ? backend_in : backend);
+ return 1;
+ }
+
+ if (backend_out) {
+ status = make_pdb_method_name(&bout, backend_out);
+ } else {
+ status = make_pdb_method_name(&bout, backend);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "Unable to initialize %s.\n",
+ backend_out ? backend_out : backend);
+ return 1;
+ }
+
+ if (transfer_account_policies) {
+
+ if (!(checkparms & BIT_USER)) {
+ return export_account_policies(bin, bout);
+ }
+
+ } else if (transfer_groups) {
+
+ if (!(checkparms & BIT_USER)) {
+ return export_groups(bin, bout);
+ }
+
+ } else {
+ return export_database(bin, bout,
+ (checkparms & BIT_USER) ? user_name : NULL);
+ }
+ }
+
+ /* if BIT_USER is defined but nothing else then threat it as -l -u for compatibility */
+ /* fake up BIT_LIST if only BIT_USER is defined */
+ if ((checkparms & BIT_USER) && !(checkparms & ~BIT_USER)) {
+ checkparms += BIT_LIST;
+ }
+
+ /* modify flag is optional to maintain backwards compatibility */
+ /* fake up BIT_MODIFY if BIT_USER and at least one of MASK_USER_GOOD is defined */
+ if (!((checkparms & ~MASK_USER_GOOD) & ~BIT_USER) && (checkparms & MASK_USER_GOOD)) {
+ checkparms += BIT_MODIFY;
+ }
+
+ /* list users operations */
+ if (checkparms & BIT_LIST) {
+ if (!(checkparms & ~BIT_LIST)) {
+ poptFreeContext(pc);
+ return print_users_list(verbose, spstyle);
+ }
+ if (!(checkparms & ~(BIT_USER + BIT_LIST))) {
+ poptFreeContext(pc);
+ return print_user_info(user_name, verbose, spstyle);
+ }
+ }
+
+ /* mask out users options */
+ checkparms &= ~MASK_USER_GOOD;
+
+ /* if bad password count is reset, we must be modifying */
+ if (checkparms & BIT_BADPWRESET) {
+ checkparms |= BIT_MODIFY;
+ checkparms &= ~BIT_BADPWRESET;
+ }
+
+ /* if logon hours is reset, must modify */
+ if (checkparms & BIT_LOGONHOURS) {
+ checkparms |= BIT_MODIFY;
+ checkparms &= ~BIT_LOGONHOURS;
+ }
+
+ /* account operation */
+ if ((checkparms & BIT_CREATE) || (checkparms & BIT_MODIFY) || (checkparms & BIT_DELETE)) {
+ /* check use of -u option */
+ if (!(checkparms & BIT_USER)) {
+ fprintf (stderr, "Username not specified! (use -u option)\n");
+ poptFreeContext(pc);
+ return -1;
+ }
+
+ /* account creation operations */
+ if (!(checkparms & ~(BIT_CREATE + BIT_USER + BIT_MACHINE))) {
+ poptFreeContext(pc);
+ if (checkparms & BIT_MACHINE) {
+ return new_machine(user_name, machine_sid);
+ } else {
+ return new_user(user_name, full_name,
+ home_dir, home_drive,
+ logon_script, profile_path,
+ user_sid, pw_from_stdin);
+ }
+ }
+
+ /* account deletion operations */
+ if (!(checkparms & ~(BIT_DELETE + BIT_USER + BIT_MACHINE))) {
+ poptFreeContext(pc);
+ if (checkparms & BIT_MACHINE) {
+ return delete_machine_entry(user_name);
+ } else {
+ return delete_user_entry(user_name);
+ }
+ }
+
+ /* account modification operations */
+ if (!(checkparms & ~(BIT_MODIFY + BIT_USER + BIT_MACHINE))) {
+ poptFreeContext(pc);
+ if (checkparms & BIT_MACHINE) {
+ return set_machine_info(user_name,
+ account_control,
+ machine_sid);
+ } else {
+ return set_user_info(user_name, full_name,
+ home_dir, acct_desc,
+ home_drive, logon_script,
+ profile_path, account_control,
+ user_sid, user_domain,
+ badpw_reset, hours_reset,
+ kickoff_time, str_hex_pwd);
+ }
+ }
+ }
+
+ if (setparms >= 0x20) {
+ fprintf (stderr, "Incompatible or insufficient options on command line!\n");
+ }
+ poptPrintHelp(pc, stderr, 0);
+
+ gfree_all();
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ return 1;
+}
diff --git a/source3/utils/profiles.c b/source3/utils/profiles.c
new file mode 100644
index 0000000..ab1eb26
--- /dev/null
+++ b/source3/utils/profiles.c
@@ -0,0 +1,365 @@
+/*
+ Samba Unix/Linux SMB client utility profiles.c
+
+ Copyright (C) Richard Sharpe, <rsharpe@richardsharpe.com> 2002
+ Copyright (C) Jelmer Vernooij (conversion to popt) 2003
+ Copyright (C) Gerald (Jerry) Carter 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/cmdline/cmdline.h"
+#include "registry/reg_objects.h"
+#include "registry/regfio.h"
+#include "../libcli/security/security.h"
+
+/* GLOBAL VARIABLES */
+
+struct dom_sid old_sid, new_sid;
+int change = 0, new_val = 0;
+int opt_verbose = False;
+
+/********************************************************************
+********************************************************************/
+
+static void verbose_output(const char *format, ...) PRINTF_ATTRIBUTE(1,2);
+static void verbose_output(const char *format, ...)
+{
+ va_list args;
+ char *var = NULL;
+
+ if (!opt_verbose) {
+ return;
+ }
+
+ va_start(args, format);
+ if ((vasprintf(&var, format, args)) == -1) {
+ va_end(args);
+ return;
+ }
+
+ fprintf(stdout, "%s", var);
+ va_end(args);
+ SAFE_FREE(var);
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool swap_sid_in_acl( struct security_descriptor *sd, struct dom_sid *s1, struct dom_sid *s2 )
+{
+ struct security_acl *theacl;
+ int i;
+ bool update = False;
+ struct dom_sid_buf buf;
+
+ verbose_output(" Owner SID: %s\n",
+ dom_sid_str_buf(sd->owner_sid, &buf));
+ if ( dom_sid_equal( sd->owner_sid, s1 ) ) {
+ sid_copy( sd->owner_sid, s2 );
+ update = True;
+ verbose_output(" New Owner SID: %s\n",
+ dom_sid_str_buf(sd->owner_sid, &buf));
+
+ }
+
+ verbose_output(" Group SID: %s\n",
+ dom_sid_str_buf(sd->group_sid, &buf));
+ if ( dom_sid_equal( sd->group_sid, s1 ) ) {
+ sid_copy( sd->group_sid, s2 );
+ update = True;
+ verbose_output(" New Group SID: %s\n",
+ dom_sid_str_buf(sd->group_sid, &buf));
+ }
+
+ theacl = sd->dacl;
+ verbose_output(" DACL: %d entries:\n", theacl->num_aces);
+ for ( i=0; i<theacl->num_aces; i++ ) {
+ verbose_output(" Trustee SID: %s\n",
+ dom_sid_str_buf(&theacl->aces[i].trustee,
+ &buf));
+ if ( dom_sid_equal( &theacl->aces[i].trustee, s1 ) ) {
+ sid_copy( &theacl->aces[i].trustee, s2 );
+ update = True;
+ verbose_output(
+ " New Trustee SID: %s\n",
+ dom_sid_str_buf(&theacl->aces[i].trustee,
+ &buf));
+ }
+ }
+
+#if 0
+ theacl = sd->sacl;
+ verbose_output(" SACL: %d entries: \n", theacl->num_aces);
+ for ( i=0; i<theacl->num_aces; i++ ) {
+ verbose_output(" Trustee SID: %s\n",
+ dom_sid_str_buf(&theacl->aces[i].trustee,
+ &buf));
+ if ( dom_sid_equal( &theacl->aces[i].trustee, s1 ) ) {
+ sid_copy( &theacl->aces[i].trustee, s2 );
+ update = True;
+ verbose_output(
+ " New Trustee SID: %s\n",
+ dom_sid_str_buf(&theacl->aces[i].trustee,
+ &buf));
+ }
+ }
+#endif
+ return update;
+}
+
+/********************************************************************
+********************************************************************/
+
+static bool copy_registry_tree( REGF_FILE *infile, REGF_NK_REC *nk,
+ REGF_NK_REC *parent, REGF_FILE *outfile,
+ const char *parentpath )
+{
+ REGF_NK_REC *key, *subkey;
+ struct security_descriptor *new_sd;
+ struct regval_ctr *values;
+ struct regsubkey_ctr *subkeys;
+ int i;
+ char *path;
+ WERROR werr;
+
+ /* swap out the SIDs in the security descriptor */
+
+ if (nk->sec_desc->sec_desc == NULL) {
+ fprintf(stderr, "Invalid (NULL) security descriptor!\n");
+ return false;
+ }
+
+ new_sd = security_descriptor_copy(outfile->mem_ctx,
+ nk->sec_desc->sec_desc);
+ if (new_sd == NULL) {
+ fprintf(stderr, "Failed to copy security descriptor!\n");
+ return False;
+ }
+
+ verbose_output("ACL for %s%s%s\n", parentpath, parent ? "\\" : "", nk->keyname);
+ swap_sid_in_acl( new_sd, &old_sid, &new_sid );
+
+ werr = regsubkey_ctr_init(NULL, &subkeys);
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("copy_registry_tree: talloc() failure!\n"));
+ return False;
+ }
+
+ werr = regval_ctr_init(subkeys, &values);
+ if (!W_ERROR_IS_OK(werr)) {
+ TALLOC_FREE( subkeys );
+ DEBUG(0,("copy_registry_tree: talloc() failure!\n"));
+ return False;
+ }
+
+ /* copy values into the struct regval_ctr */
+
+ for ( i=0; i<nk->num_values; i++ ) {
+ regval_ctr_addvalue( values, nk->values[i].valuename, nk->values[i].type,
+ nk->values[i].data, (nk->values[i].data_size & ~VK_DATA_IN_OFFSET) );
+ }
+
+ /* copy subkeys into the struct regsubkey_ctr */
+
+ while ( (subkey = regfio_fetch_subkey( infile, nk )) ) {
+ regsubkey_ctr_addkey( subkeys, subkey->keyname );
+ }
+
+ key = regfio_write_key( outfile, nk->keyname, values, subkeys, new_sd, parent );
+
+ /* write each one of the subkeys out */
+
+ path = talloc_asprintf(subkeys, "%s%s%s",
+ parentpath, parent ? "\\" : "",nk->keyname);
+ if (!path) {
+ TALLOC_FREE( subkeys );
+ return false;
+ }
+
+ nk->subkey_index = 0;
+ while ((subkey = regfio_fetch_subkey(infile, nk))) {
+ if (!copy_registry_tree( infile, subkey, key, outfile, path)) {
+ TALLOC_FREE(subkeys);
+ return false;
+ }
+ }
+
+
+ verbose_output("[%s]\n", path);
+
+ /* values is a talloc()'d child of subkeys here so just throw it all away */
+ TALLOC_FREE(subkeys);
+
+ return True;
+}
+
+/*********************************************************************
+*********************************************************************/
+
+int main( int argc, const char *argv[] )
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ int opt;
+ REGF_FILE *infile, *outfile;
+ REGF_NK_REC *nk;
+ char *orig_filename, *new_filename;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "change-sid",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'c',
+ .descrip = "Provides SID to change",
+ },
+ {
+ .longName = "new-sid",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'n',
+ .descrip = "Provides SID to change to",
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt_verbose,
+ .val = 'v',
+ .descrip = "Verbose output",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ poptContext pc;
+ bool ok;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "<profilefile>");
+
+ /* Now, process the arguments */
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'c':
+ change = 1;
+ if (!string_to_sid(&old_sid, poptGetOptArg(pc))) {
+ fprintf(stderr, "Argument to -c should be a SID in form of S-1-5-...\n");
+ poptPrintUsage(pc, stderr, 0);
+ exit(254);
+ }
+ break;
+
+ case 'n':
+ new_val = 1;
+ if (!string_to_sid(&new_sid, poptGetOptArg(pc))) {
+ fprintf(stderr, "Argument to -n should be a SID in form of S-1-5-...\n");
+ poptPrintUsage(pc, stderr, 0);
+ exit(253);
+ }
+ break;
+
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptGetArg(pc);
+
+ if (!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+
+ if ((!change && new_val) || (change && !new_val)) {
+ fprintf(stderr, "You must specify both -c and -n if one or the other is set!\n");
+ poptPrintUsage(pc, stderr, 0);
+ exit(252);
+ }
+
+ orig_filename = talloc_strdup(frame, poptPeekArg(pc));
+ if (!orig_filename) {
+ exit(ENOMEM);
+ }
+ new_filename = talloc_asprintf(frame,
+ "%s.new",
+ orig_filename);
+ if (!new_filename) {
+ exit(ENOMEM);
+ }
+
+ if (!(infile = regfio_open( orig_filename, O_RDONLY, 0))) {
+ fprintf( stderr, "Failed to open %s!\n", orig_filename );
+ fprintf( stderr, "Error was (%s)\n", strerror(errno) );
+ exit (1);
+ }
+
+ if ( !(outfile = regfio_open( new_filename, (O_RDWR|O_CREAT|O_TRUNC),
+ (S_IRUSR|S_IWUSR) )) ) {
+ fprintf( stderr, "Failed to open new file %s!\n", new_filename );
+ fprintf( stderr, "Error was (%s)\n", strerror(errno) );
+ exit (1);
+ }
+
+ /* actually do the update now */
+
+ if ((nk = regfio_rootkey( infile )) == NULL) {
+ fprintf(stderr, "Could not get rootkey\n");
+ exit(3);
+ }
+
+ if (!copy_registry_tree( infile, nk, NULL, outfile, "")) {
+ fprintf(stderr, "Failed to write updated registry file!\n");
+ exit(2);
+ }
+
+ /* cleanup */
+
+ regfio_close(infile);
+ regfio_close(outfile);
+
+ poptFreeContext(pc);
+
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/utils/py_net.c b/source3/utils/py_net.c
new file mode 100644
index 0000000..5e81b8f
--- /dev/null
+++ b/source3/utils/py_net.c
@@ -0,0 +1,369 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba python bindings to s3 libnet library
+
+ Copyright (C) David Mulder <dmulder@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lib/replace/system/python.h"
+#include "includes.h"
+#include <pytalloc.h>
+#include "python/modules.h"
+#include "python/py3compat.h"
+#include "rpc_client/rpc_client.h"
+#include <sys/socket.h>
+#include "net.h"
+#include "auth/credentials/credentials.h"
+#include "auth/credentials/pycredentials.h"
+#include "lib/cmdline_contexts.h"
+#include "param/loadparm.h"
+#include "param/s3_param.h"
+#include "param/pyparam.h"
+#include "py_net.h"
+#include "librpc/gen_ndr/libnet_join.h"
+#include "libnet/libnet_join.h"
+#include "libcli/security/dom_sid.h"
+#include "dynconfig/dynconfig.h"
+
+static WERROR check_ads_config(struct loadparm_context *lp_ctx)
+{
+ if (lpcfg_server_role(lp_ctx) != ROLE_DOMAIN_MEMBER ) {
+ d_printf(_("Host is not configured as a member server.\n"));
+ return WERR_INVALID_DOMAIN_ROLE;
+ }
+
+ if (strlen(lpcfg_netbios_name(lp_ctx)) > 15) {
+ d_printf(_("Our netbios name can be at most 15 chars long, "
+ "\"%s\" is %u chars long\n"), lpcfg_netbios_name(lp_ctx),
+ (unsigned int)strlen(lpcfg_netbios_name(lp_ctx)));
+ return WERR_INVALID_COMPUTERNAME;
+ }
+
+ if ( lpcfg_security(lp_ctx) == SEC_ADS && !*lpcfg_realm(lp_ctx)) {
+ d_fprintf(stderr, _("realm must be set in %s for ADS "
+ "join to succeed.\n"), get_dyn_CONFIGFILE());
+ return WERR_INVALID_PARAMETER;
+ }
+
+ return WERR_OK;
+}
+
+static PyObject *py_net_join_member(py_net_Object *self, PyObject *args, PyObject *kwargs)
+{
+ struct libnet_JoinCtx *r = NULL;
+ struct net_context *c;
+ WERROR werr;
+ PyObject *result;
+ TALLOC_CTX *mem_ctx;
+ int no_dns_updates = false, debug = false;
+ bool modify_config = lp_config_backend_is_registry();
+ const char *kwnames[] = { "dnshostname", "createupn", "createcomputer",
+ "osName", "osVer", "osServicePack",
+ "machinepass", "debug", "noDnsUpdates", NULL };
+
+ mem_ctx = talloc_new(self->mem_ctx);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+ c = talloc_zero(mem_ctx, struct net_context);
+ c->msg_ctx = mem_ctx;
+
+ werr = libnet_init_JoinCtx(mem_ctx, &r);
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|sssssszpp:Join",
+ discard_const_p(char *, kwnames),
+ &r->in.dnshostname,
+ &r->in.upn,
+ &r->in.account_ou,
+ &r->in.os_name,
+ &r->in.os_version,
+ &r->in.os_servicepack,
+ &r->in.machine_password,
+ &debug,
+ &no_dns_updates)) {
+ talloc_free(mem_ctx);
+ PyErr_FromString(_("Invalid arguments\n"));
+ return NULL;
+ }
+
+ if (!modify_config) {
+ werr = check_ads_config(self->lp_ctx);
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_SetWERROR_and_string(werr,
+ _("Invalid configuration. Exiting....\n"));
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+ }
+
+ r->in.domain_name = lpcfg_realm(self->lp_ctx);
+ r->in.domain_name_type = JoinDomNameTypeDNS;
+ r->in.create_upn = r->in.upn != NULL ? true : false;
+ r->in.dc_name = self->server_address;
+ r->in.admin_account = cli_credentials_get_username(self->creds);
+ r->in.admin_password = cli_credentials_get_password(self->creds);
+ r->in.use_kerberos = cli_credentials_get_kerberos_state(self->creds);
+ r->in.modify_config = modify_config;
+ r->in.join_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_CREATE |
+ WKSSVC_JOIN_FLAGS_DOMAIN_JOIN_IF_JOINED;
+ r->in.msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
+ r->in.debug = debug;
+ c->opt_user_name = r->in.admin_account;
+ c->opt_password = r->in.admin_password;
+ c->opt_kerberos = r->in.use_kerberos;
+
+ werr = libnet_Join(mem_ctx, r);
+ if (W_ERROR_EQUAL(werr, WERR_NERR_DCNOTFOUND)) {
+ r->in.domain_name = lpcfg_workgroup(self->lp_ctx);
+ r->in.domain_name_type = JoinDomNameTypeNBT;
+ werr = libnet_Join(mem_ctx, r);
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_SetWERROR_and_string(werr,
+ r->out.error_string
+ ? r->out.error_string
+ : get_friendly_werror_msg(werr));
+ talloc_free(mem_ctx);
+ return NULL;
+ }
+
+ /*
+ * Check the short name of the domain
+ */
+
+ if (!modify_config && !strequal(lpcfg_workgroup(self->lp_ctx), r->out.netbios_domain_name)) {
+ d_printf(_("The workgroup in %s does not match the short\n"
+ "domain name obtained from the server.\n"
+ "Using the name [%s] from the server.\n"
+ "You should set \"workgroup = %s\" in %s.\n"),
+ get_dyn_CONFIGFILE(), r->out.netbios_domain_name,
+ r->out.netbios_domain_name, get_dyn_CONFIGFILE());
+ }
+
+ /*
+ * We try doing the dns update (if it was compiled in
+ * and if it was not disabled on the command line).
+ * If the dns update fails, we still consider the join
+ * operation as succeeded if we came this far.
+ */
+ if (!no_dns_updates) {
+ net_ads_join_dns_updates(c, mem_ctx, r);
+ }
+
+ result = Py_BuildValue("ss", dom_sid_string(mem_ctx, r->out.domain_sid),
+ r->out.dns_domain_name);
+
+ talloc_free(mem_ctx);
+
+ return result;
+}
+
+static const char py_net_join_member_doc[] = "join_member(dnshostname, createupn, createcomputer, osName, osVer, osServicePack, machinepass) -> (domain_sid, domain_name)\n\n" \
+"Join the domain with the specified name.";
+
+static PyObject *py_net_leave(py_net_Object *self, PyObject *args, PyObject *kwargs)
+{
+ struct libnet_UnjoinCtx *r = NULL;
+ WERROR werr;
+ TALLOC_CTX *mem_ctx;
+ int keep_account = false, debug = false;
+ const char *kwnames[] = { "keepAccount", "debug", NULL };
+
+ mem_ctx = talloc_new(self->mem_ctx);
+ if (mem_ctx == NULL) {
+ PyErr_NoMemory();
+ return NULL;
+ }
+
+ if (!*lpcfg_realm(self->lp_ctx)) {
+ PyErr_FromString(_("No realm set, are we joined ?\n"));
+ return NULL;
+ }
+
+ werr = libnet_init_UnjoinCtx(mem_ctx, &r);
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_SetWERROR_and_string(werr,
+ _("Could not initialise unjoin context.\n"));
+ return NULL;
+ }
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|pp:Leave",
+ discard_const_p(char *, kwnames),
+ &keep_account, &debug)) {
+ talloc_free(mem_ctx);
+ PyErr_FromString(_("Invalid arguments\n"));
+ return NULL;
+ }
+
+ r->in.use_kerberos = cli_credentials_get_kerberos_state(self->creds);
+ r->in.dc_name = self->server_address;
+ r->in.domain_name = lpcfg_realm(self->lp_ctx);
+ r->in.admin_account = cli_credentials_get_username(self->creds);
+ r->in.admin_password = cli_credentials_get_password(self->creds);
+ r->in.modify_config = lp_config_backend_is_registry();
+ r->in.debug = debug;
+
+ /*
+ * Try to delete it, but if that fails, disable it. The
+ * WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE really means "disable"
+ */
+ r->in.unjoin_flags = WKSSVC_JOIN_FLAGS_JOIN_TYPE |
+ WKSSVC_JOIN_FLAGS_ACCOUNT_DELETE;
+ if (keep_account) {
+ r->in.delete_machine_account = false;
+ } else {
+ r->in.delete_machine_account = true;
+ }
+
+ r->in.msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
+
+ werr = libnet_Unjoin(mem_ctx, r);
+ if (!W_ERROR_IS_OK(werr)) {
+ PyErr_SetWERROR_and_string(werr,
+ r->out.error_string
+ ? r->out.error_string
+ : get_friendly_werror_msg(werr));
+ Py_RETURN_FALSE;
+ }
+
+ if (r->out.deleted_machine_account) {
+ d_printf(_("Deleted account for '%s' in realm '%s'\n"),
+ r->in.machine_name, r->out.dns_domain_name);
+ Py_RETURN_TRUE;
+ }
+
+ if (r->out.disabled_machine_account) {
+ d_printf(_("Disabled account for '%s' in realm '%s'\n"),
+ r->in.machine_name, r->out.dns_domain_name);
+ werr = WERR_OK;
+ Py_RETURN_TRUE;
+ }
+
+ /*
+ * Based on what we requested, we shouldn't get here, but if
+ * we did, it means the secrets were removed, and therefore
+ * we have left the domain.
+ */
+ d_fprintf(stderr, _("Machine '%s' Left domain '%s'\n"),
+ r->in.machine_name, r->out.dns_domain_name);
+ Py_RETURN_TRUE;
+}
+
+static const char py_net_leave_doc[] = "leave(keepAccount) -> success\n\n" \
+"Leave the joined domain.";
+
+static PyMethodDef net_obj_methods[] = {
+ {
+ .ml_name = "join_member",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction,
+ py_net_join_member),
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = py_net_join_member_doc
+ },
+ {
+ .ml_name = "leave",
+ .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction,
+ py_net_leave),
+ .ml_flags = METH_VARARGS|METH_KEYWORDS,
+ .ml_doc = py_net_leave_doc
+ },
+ { .ml_name = NULL }
+};
+
+static void py_net_dealloc(py_net_Object *self)
+{
+ talloc_free(self->mem_ctx);
+ PyObject_Del(self);
+}
+
+static PyObject *net_obj_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+ PyObject *py_creds, *py_lp = Py_None;
+ const char *kwnames[] = { "creds", "lp", "server", NULL };
+ py_net_Object *ret;
+ const char *server_address = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|Oz",
+ discard_const_p(char *, kwnames), &py_creds, &py_lp,
+ &server_address)) {
+ PyErr_FromString(_("Invalid arguments\n"));
+ return NULL;
+ }
+
+ ret = PyObject_New(py_net_Object, type);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ ret->ev = samba_tevent_context_init(NULL);
+ ret->mem_ctx = talloc_stackframe();
+
+ ret->lp_ctx = lpcfg_from_py_object(ret->mem_ctx, py_lp);
+ if (ret->lp_ctx == NULL) {
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ ret->server_address = server_address;
+
+ ret->creds = cli_credentials_from_py_object(py_creds);
+ if (ret->creds == NULL) {
+ PyErr_SetString(PyExc_TypeError, "Expected credentials object");
+ Py_DECREF(ret);
+ return NULL;
+ }
+
+ return (PyObject *)ret;
+}
+
+
+PyTypeObject py_net_Type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "net_s3.Net",
+ .tp_basicsize = sizeof(py_net_Object),
+ .tp_dealloc = (destructor)py_net_dealloc,
+ .tp_methods = net_obj_methods,
+ .tp_new = net_obj_new,
+};
+
+static struct PyModuleDef moduledef = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "net",
+ .m_size = -1,
+};
+
+MODULE_INIT_FUNC(net_s3)
+{
+ PyObject *m;
+
+ if (PyType_Ready(&py_net_Type) < 0)
+ return NULL;
+
+ m = PyModule_Create(&moduledef);
+ if (m == NULL)
+ return NULL;
+
+ Py_INCREF(&py_net_Type);
+ PyModule_AddObject(m, "Net", (PyObject *)&py_net_Type);
+
+ return m;
+}
diff --git a/source3/utils/py_net.h b/source3/utils/py_net.h
new file mode 100644
index 0000000..9ace71b
--- /dev/null
+++ b/source3/utils/py_net.h
@@ -0,0 +1,26 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba python bindings to s3 libnet library
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+typedef struct {
+ PyObject_HEAD
+ TALLOC_CTX *mem_ctx;
+ struct cli_credentials *creds;
+ struct loadparm_context *lp_ctx;
+ const char *server_address;
+ struct tevent_context *ev;
+} py_net_Object;
diff --git a/source3/utils/regedit.c b/source3/utils/regedit.c
new file mode 100644
index 0000000..3259076
--- /dev/null
+++ b/source3/utils/regedit.c
@@ -0,0 +1,835 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/param/param.h"
+#include "lib/util/data_blob.h"
+#include "lib/registry/registry.h"
+#include "regedit.h"
+#include "regedit_treeview.h"
+#include "regedit_valuelist.h"
+#include "regedit_dialog.h"
+#include "regedit_list.h"
+#include <ncurses.h>
+#include <menu.h>
+#include <panel.h>
+
+#define KEY_START_X 0
+#define KEY_START_Y 1
+#define KEY_WIDTH (COLS / 4)
+#define KEY_HEIGHT (LINES - KEY_START_Y - 2)
+#define VAL_START_X KEY_WIDTH
+#define VAL_START_Y 1
+#define VAL_WIDTH (COLS - KEY_WIDTH)
+#define VAL_HEIGHT (LINES - VAL_START_Y - 2)
+
+#define HELP1_START_Y (LINES - 2)
+#define HELP1_START_X 0
+#define HELP1_WIDTH (LINES)
+#define HELP2_START_Y (LINES - 1)
+#define HELP2_START_X 0
+#define HELP2_WIDTH (LINES)
+#define PATH_START_Y 0
+#define PATH_START_X 6
+#define PATH_MAX_Y (COLS - 1)
+#define PATH_WIDTH (COLS - 6)
+#define PATH_WIDTH_MAX 1024
+
+struct regedit {
+ struct registry_context *registry_context;
+ WINDOW *main_window;
+ WINDOW *path_label;
+ size_t path_len;
+ struct value_list *vl;
+ struct tree_view *keys;
+ bool tree_input;
+ struct regedit_search_opts active_search;
+};
+
+static struct regedit *regedit_main = NULL;
+
+static void show_path(struct regedit *regedit)
+{
+ int start_pad = 0;
+ int start_win = PATH_START_X;
+
+ if (PATH_START_X + regedit->path_len > COLS) {
+ start_pad = 3 + PATH_START_X + regedit->path_len - COLS;
+ mvprintw(PATH_START_Y, start_win, "...");
+ start_win += 3;
+ }
+ copywin(regedit->path_label, regedit->main_window, 0, start_pad,
+ PATH_START_Y, start_win, PATH_START_Y, PATH_MAX_Y, false);
+
+ mvchgat(0, 0, COLS, A_BOLD, PAIR_YELLOW_CYAN, NULL);
+}
+
+static void print_path(struct regedit *regedit, struct tree_node *node)
+{
+ regedit->path_len = tree_node_print_path(regedit->path_label, node);
+ show_path(regedit);
+}
+
+static void print_help(struct regedit *regedit)
+{
+ const char *khelp = "[n] New Key [s] New Subkey [d] Del Key "
+ "[LEFT] Ascend [RIGHT] Descend";
+ const char *vhelp = "[n] New Value [d] Del Value [ENTER] Edit "
+ "[b] Edit binary";
+ const char *msg = "KEYS";
+ const char *help = khelp;
+ const char *genhelp = "[TAB] Switch sections [q] Quit "
+ "[UP] List up [DOWN] List down "
+ "[/] Search [x] Next";
+ int i, pad;
+
+ if (!regedit->tree_input) {
+ msg = "VALUES";
+ help = vhelp;
+ }
+
+ move(HELP1_START_Y, HELP1_START_X);
+ clrtoeol();
+ attron(COLOR_PAIR(PAIR_BLACK_CYAN));
+ mvaddstr(HELP1_START_Y, HELP1_START_X, help);
+ pad = COLS - strlen(msg) - strlen(help);
+ for (i = 0; i < pad; ++i) {
+ addch(' ');
+ }
+ attroff(COLOR_PAIR(PAIR_BLACK_CYAN));
+ attron(COLOR_PAIR(PAIR_YELLOW_CYAN) | A_BOLD);
+ addstr(msg);
+ attroff(COLOR_PAIR(PAIR_YELLOW_CYAN) | A_BOLD);
+
+ move(HELP2_START_Y, HELP2_START_X);
+ clrtoeol();
+ mvaddstr(HELP2_START_Y, HELP2_START_X, genhelp);
+}
+
+static void print_heading(struct regedit *regedit)
+{
+ if (regedit->tree_input) {
+ tree_view_set_selected(regedit->keys, true);
+ value_list_set_selected(regedit->vl, false);
+ } else {
+ tree_view_set_selected(regedit->keys, false);
+ value_list_set_selected(regedit->vl, true);
+ }
+
+ print_help(regedit);
+}
+
+static void load_values(struct regedit *regedit)
+{
+ struct tree_node *node;
+
+ node = tree_view_get_current_node(regedit->keys);
+ value_list_load(regedit->vl, node->key);
+}
+
+static void add_reg_key(struct regedit *regedit, struct tree_node *node,
+ bool subkey)
+{
+ const char *name;
+ const char *msg;
+
+ if (!subkey && tree_node_is_top_level(node)) {
+ return;
+ }
+
+ msg = "Enter name of new key";
+ if (subkey) {
+ msg = "Enter name of new subkey";
+ }
+ dialog_input(regedit, &name, "New Key", "%s", msg);
+ if (name) {
+ WERROR rv;
+ struct registry_key *new_key;
+ struct tree_node *new_node = NULL;
+ struct tree_node *list;
+ struct tree_node *parent;
+
+ if (subkey) {
+ parent = node;
+ list = node->child_head;
+ } else {
+ parent = node->parent;
+ list = tree_node_first(node);
+ SMB_ASSERT(list != NULL);
+ }
+ rv = reg_key_add_name(regedit, parent->key, name,
+ NULL, NULL, &new_key);
+ if (W_ERROR_IS_OK(rv)) {
+ /* The list of subkeys may not be present in
+ cache yet, so if not, don't bother allocating
+ a new node for the key. */
+ if (list) {
+ new_node = tree_node_new(parent, parent,
+ name, new_key);
+ SMB_ASSERT(new_node);
+ tree_node_insert_sorted(list, new_node);
+ } else {
+ /* Reopen the parent key to make sure the
+ new subkey will be noticed. */
+ tree_node_reopen_key(regedit->registry_context,
+ parent);
+ }
+
+ list = tree_node_first(node);
+ tree_view_clear(regedit->keys);
+ tree_view_update(regedit->keys, list);
+ if (!subkey) {
+ node = new_node;
+ }
+ tree_view_set_current_node(regedit->keys, node);
+ load_values(regedit);
+ } else {
+ msg = get_friendly_werror_msg(rv);
+ dialog_notice(regedit, DIA_ALERT, "New Key",
+ "Failed to create key: %s", msg);
+ }
+ talloc_free(discard_const(name));
+ }
+}
+
+enum search_flags {
+ SEARCH_NEXT = (1<<0),
+ SEARCH_PREV = (1<<1),
+ SEARCH_REPEAT = (1<<2)
+};
+static WERROR regedit_search(struct regedit *regedit, struct tree_node *node,
+ struct value_item *vitem, unsigned flags)
+{
+ struct regedit_search_opts *opts;
+ struct tree_node *found;
+ struct value_item *found_value;
+ bool search_key, need_sync;
+ char *save_value_name;
+ WERROR rv;
+ bool (*iterate)(struct tree_node **, bool, WERROR *);
+ struct value_item *(*find_item)(struct value_list *,
+ struct value_item *,
+ const char *,
+ regedit_search_match_fn_t);
+
+ opts = &regedit->active_search;
+
+ if (!opts->query || !opts->match) {
+ return WERR_OK;
+ }
+
+ SMB_ASSERT(opts->search_key || opts->search_value);
+
+ rv = WERR_OK;
+ found = NULL;
+ found_value = NULL;
+ save_value_name = NULL;
+ search_key = opts->search_key;
+ need_sync = false;
+ iterate = tree_node_next;
+ find_item = value_list_find_next_item;
+
+ if (flags & SEARCH_PREV) {
+ iterate = tree_node_prev;
+ find_item = value_list_find_prev_item;
+ }
+
+ if (opts->search_value) {
+ struct value_item *it;
+
+ it = value_list_get_current_item(regedit->vl);
+ if (it) {
+ save_value_name = talloc_strdup(regedit,
+ it->value_name);
+ if (save_value_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+
+ if (vitem) {
+ search_key = false;
+ }
+ }
+
+ if (!vitem && (flags & SEARCH_REPEAT)) {
+ if (opts->search_value) {
+ search_key = false;
+ } else if (!iterate(&node, opts->search_recursive, &rv)) {
+ beep();
+ return rv;
+ }
+ }
+
+ do {
+ if (search_key) {
+ SMB_ASSERT(opts->search_key == true);
+ if (opts->match(node->name, opts->query)) {
+ found = node;
+ } else if (opts->search_value) {
+ search_key = false;
+ }
+ }
+ if (!search_key) {
+ SMB_ASSERT(opts->search_value == true);
+ if (!vitem) {
+ rv = value_list_load_quick(regedit->vl,
+ node->key);
+ if (!W_ERROR_IS_OK(rv)) {
+ goto out;
+ }
+ need_sync = true;
+ }
+ found_value = find_item(regedit->vl, vitem, opts->query,
+ opts->match);
+ if (found_value) {
+ found = node;
+ } else {
+ vitem = NULL;
+ search_key = opts->search_key;
+ }
+ }
+ } while (!found && iterate(&node, opts->search_recursive, &rv));
+
+ if (!W_ERROR_IS_OK(rv)) {
+ goto out;
+ }
+
+ if (found) {
+ /* Put the cursor on the node that was found */
+ if (!tree_view_is_node_visible(regedit->keys, found)) {
+ tree_view_update(regedit->keys,
+ tree_node_first(found));
+ print_path(regedit, found);
+ }
+ tree_view_set_current_node(regedit->keys, found);
+ if (found_value) {
+ if (need_sync) {
+ value_list_sync(regedit->vl);
+ }
+ value_list_set_current_item(regedit->vl, found_value);
+ regedit->tree_input = false;
+ } else {
+ load_values(regedit);
+ regedit->tree_input = true;
+ }
+ tree_view_show(regedit->keys);
+ value_list_show(regedit->vl);
+ print_heading(regedit);
+ } else {
+ if (need_sync) {
+ load_values(regedit);
+ value_list_set_current_item_by_name(regedit->vl,
+ save_value_name);
+ }
+ beep();
+ }
+
+out:
+ talloc_free(save_value_name);
+
+ return rv;
+}
+
+static void regedit_search_repeat(struct regedit *regedit, unsigned flags)
+{
+ struct tree_node *node;
+ struct value_item *vitem;
+ struct regedit_search_opts *opts;
+
+ opts = &regedit->active_search;
+ if (opts->query == NULL) {
+ return;
+ }
+
+ node = tree_view_get_current_node(regedit->keys);
+ vitem = NULL;
+ if (opts->search_value && !regedit->tree_input) {
+ vitem = value_list_get_current_item(regedit->vl);
+ }
+ regedit_search(regedit, node, vitem, flags | SEARCH_REPEAT);
+}
+
+static void handle_tree_input(struct regedit *regedit, int c)
+{
+ struct tree_node *node;
+
+ switch (c) {
+ case KEY_DOWN:
+ tree_view_driver(regedit->keys, ML_CURSOR_DOWN);
+ load_values(regedit);
+ break;
+ case KEY_UP:
+ tree_view_driver(regedit->keys, ML_CURSOR_UP);
+ load_values(regedit);
+ break;
+ case KEY_NPAGE:
+ tree_view_driver(regedit->keys, ML_CURSOR_PGDN);
+ load_values(regedit);
+ break;
+ case KEY_PPAGE:
+ tree_view_driver(regedit->keys, ML_CURSOR_PGUP);
+ load_values(regedit);
+ break;
+ case KEY_HOME:
+ tree_view_driver(regedit->keys, ML_CURSOR_HOME);
+ load_values(regedit);
+ break;
+ case KEY_END:
+ tree_view_driver(regedit->keys, ML_CURSOR_END);
+ load_values(regedit);
+ break;
+ case '\n':
+ case KEY_ENTER:
+ case KEY_RIGHT:
+ node = tree_view_get_current_node(regedit->keys);
+ if (node && tree_node_has_children(node)) {
+ WERROR rv;
+
+ rv = tree_node_load_children(node);
+ if (W_ERROR_IS_OK(rv)) {
+ print_path(regedit, node->child_head);
+ tree_view_update(regedit->keys, node->child_head);
+ value_list_load(regedit->vl, node->child_head->key);
+ } else {
+ const char *msg = get_friendly_werror_msg(rv);
+ dialog_notice(regedit, DIA_ALERT, "Loading Subkeys",
+ "Failed to load subkeys: %s", msg);
+ }
+ }
+ break;
+ case KEY_LEFT:
+ node = tree_view_get_current_node(regedit->keys);
+ if (node && !tree_node_is_top_level(node)) {
+ print_path(regedit, node->parent);
+ node = node->parent;
+ tree_view_update(regedit->keys, tree_node_first(node));
+ tree_view_set_current_node(regedit->keys, node);
+ value_list_load(regedit->vl, node->key);
+ }
+ break;
+ case 'n':
+ case 'N':
+ node = tree_view_get_current_node(regedit->keys);
+ add_reg_key(regedit, node, false);
+ break;
+ case 's':
+ case 'S':
+ node = tree_view_get_current_node(regedit->keys);
+ add_reg_key(regedit, node, true);
+ break;
+ case 'd':
+ case 'D': {
+ int sel;
+
+ node = tree_view_get_current_node(regedit->keys);
+ if (tree_node_is_top_level(node)) {
+ break;
+ }
+ sel = dialog_notice(regedit, DIA_CONFIRM,
+ "Delete Key",
+ "Really delete key \"%s\"?",
+ node->name);
+ if (sel == DIALOG_OK) {
+ WERROR rv;
+ struct tree_node *pop;
+ struct tree_node *parent = node->parent;
+
+ rv = reg_key_del(node, parent->key, node->name);
+ if (W_ERROR_IS_OK(rv)) {
+ tree_node_reopen_key(regedit->registry_context,
+ parent);
+ tree_view_clear(regedit->keys);
+ pop = tree_node_pop(&node);
+ talloc_free(pop);
+ node = parent->child_head;
+ if (node == NULL) {
+ node = tree_node_first(parent);
+ print_path(regedit, node);
+ }
+ tree_view_update(regedit->keys, node);
+ value_list_load(regedit->vl, node->key);
+ } else {
+ const char *msg = get_friendly_werror_msg(rv);
+ dialog_notice(regedit, DIA_ALERT, "Delete Key",
+ "Failed to delete key: %s", msg);
+ }
+ }
+ break;
+ }
+ }
+
+ tree_view_show(regedit->keys);
+ value_list_show(regedit->vl);
+}
+
+static void handle_value_input(struct regedit *regedit, int c)
+{
+ struct value_item *vitem;
+ bool binmode = false;
+ WERROR err;
+ int sel;
+
+ switch (c) {
+ case KEY_DOWN:
+ value_list_driver(regedit->vl, ML_CURSOR_DOWN);
+ break;
+ case KEY_UP:
+ value_list_driver(regedit->vl, ML_CURSOR_UP);
+ break;
+ case KEY_NPAGE:
+ value_list_driver(regedit->vl, ML_CURSOR_PGDN);
+ break;
+ case KEY_PPAGE:
+ value_list_driver(regedit->vl, ML_CURSOR_PGUP);
+ break;
+ case KEY_HOME:
+ value_list_driver(regedit->vl, ML_CURSOR_HOME);
+ break;
+ case KEY_END:
+ value_list_driver(regedit->vl, ML_CURSOR_END);
+ break;
+ case 'b':
+ case 'B':
+ binmode = true;
+
+ FALL_THROUGH;
+ case '\n':
+ case KEY_ENTER:
+ vitem = value_list_get_current_item(regedit->vl);
+ if (vitem) {
+ struct tree_node *node;
+ const char *name = NULL;
+ node = tree_view_get_current_node(regedit->keys);
+ sel = dialog_edit_value(regedit, node->key, vitem->type,
+ vitem, binmode, &err, &name);
+ if (!W_ERROR_IS_OK(err)) {
+ const char *msg = get_friendly_werror_msg(err);
+ dialog_notice(regedit, DIA_ALERT, "Error",
+ "Error editing value:\n%s", msg);
+ } else if (sel == DIALOG_OK) {
+ tree_node_reopen_key(regedit->registry_context,
+ node);
+ value_list_load(regedit->vl, node->key);
+ value_list_set_current_item_by_name(regedit->vl,
+ name);
+ talloc_free(discard_const(name));
+ }
+ }
+ break;
+ case 'n':
+ case 'N': {
+ int new_type;
+
+ sel = dialog_select_type(regedit, &new_type);
+ if (sel == DIALOG_OK) {
+ struct tree_node *node;
+ const char *name = NULL;
+ node = tree_view_get_current_node(regedit->keys);
+ sel = dialog_edit_value(regedit, node->key, new_type,
+ NULL, false, &err, &name);
+ if (!W_ERROR_IS_OK(err)) {
+ const char *msg = get_friendly_werror_msg(err);
+ dialog_notice(regedit, DIA_ALERT, "Error",
+ "Error creating value:\n%s", msg);
+ } else if (sel == DIALOG_OK) {
+ tree_node_reopen_key(regedit->registry_context,
+ node);
+ value_list_load(regedit->vl, node->key);
+ value_list_set_current_item_by_name(regedit->vl,
+ name);
+ talloc_free(discard_const(name));
+ }
+ }
+ break;
+ }
+ case 'd':
+ case 'D':
+ vitem = value_list_get_current_item(regedit->vl);
+ if (vitem) {
+ sel = dialog_notice(regedit, DIA_CONFIRM,
+ "Delete Value",
+ "Really delete value \"%s\"?",
+ vitem->value_name);
+ if (sel == DIALOG_OK) {
+ struct tree_node *node;
+ node = tree_view_get_current_node(regedit->keys);
+ reg_del_value(regedit, node->key,
+ vitem->value_name);
+ tree_node_reopen_key(regedit->registry_context,
+ node);
+ value_list_load(regedit->vl, node->key);
+ }
+ }
+ break;
+ }
+
+ value_list_show(regedit->vl);
+}
+
+static bool find_substring(const char *haystack, const char *needle)
+{
+ return strstr(haystack, needle) != NULL;
+}
+
+static bool find_substring_nocase(const char *haystack, const char *needle)
+{
+ return strcasestr(haystack, needle) != NULL;
+}
+
+static void handle_main_input(struct regedit *regedit, int c)
+{
+ switch (c) {
+ case 18: { /* CTRL-R */
+ struct tree_node *root, *node;
+ const char **path;
+
+ node = tree_view_get_current_node(regedit->keys);
+ path = tree_node_get_path(regedit, node);
+ SMB_ASSERT(path != NULL);
+
+ root = tree_node_new_root(regedit, regedit->registry_context);
+ SMB_ASSERT(root != NULL);
+
+ tree_view_set_root(regedit->keys, root);
+ tree_view_set_path(regedit->keys, path);
+ node = tree_view_get_current_node(regedit->keys);
+ value_list_load(regedit->vl, node->key);
+ tree_view_show(regedit->keys);
+ value_list_show(regedit->vl);
+ print_path(regedit, node);
+ talloc_free(discard_const(path));
+ break;
+ }
+ case 'f':
+ case 'F':
+ case '/': {
+ int rv;
+ struct regedit_search_opts *opts;
+ struct tree_node *node;
+
+ opts = &regedit->active_search;
+ rv = dialog_search_input(regedit, opts);
+ if (rv == DIALOG_OK) {
+ SMB_ASSERT(opts->query != NULL);
+ opts->match = find_substring_nocase;
+ node = regedit->keys->root->child_head;
+ if (opts->search_case) {
+ opts->match = find_substring;
+ }
+ if (!opts->search_recursive) {
+ node = tree_view_get_current_node(regedit->keys);
+ node = tree_node_first(node);
+ }
+ regedit_search(regedit, node, NULL, SEARCH_NEXT);
+ }
+ break;
+ }
+ case 'x':
+ regedit_search_repeat(regedit, SEARCH_NEXT);
+ break;
+ case 'X':
+ regedit_search_repeat(regedit, SEARCH_PREV);
+ break;
+ case '\t':
+ regedit->tree_input = !regedit->tree_input;
+ print_heading(regedit);
+ break;
+ default:
+ if (regedit->tree_input) {
+ handle_tree_input(regedit, c);
+ } else {
+ handle_value_input(regedit, c);
+ }
+ }
+}
+
+int regedit_getch(void)
+{
+ int c;
+
+ SMB_ASSERT(regedit_main);
+
+ c = getch();
+ if (c == KEY_RESIZE) {
+ tree_view_resize(regedit_main->keys, KEY_HEIGHT, KEY_WIDTH,
+ KEY_START_Y, KEY_START_X);
+ value_list_resize(regedit_main->vl, VAL_HEIGHT, VAL_WIDTH,
+ VAL_START_Y, VAL_START_X);
+ print_heading(regedit_main);
+ show_path(regedit_main);
+ }
+
+ return c;
+}
+
+static void regedit_panic_handler(const char *msg)
+{
+ endwin();
+ smb_panic_log(msg);
+ smb_panic_s3(msg);
+}
+
+static void display_window(TALLOC_CTX *mem_ctx, struct registry_context *ctx)
+{
+ struct regedit *regedit;
+ struct tree_node *root;
+ bool colors;
+ int key;
+
+ initscr();
+
+ cbreak();
+ noecho();
+
+ fault_configure(regedit_panic_handler);
+
+ colors = has_colors();
+ if (colors) {
+ start_color();
+ use_default_colors();
+ assume_default_colors(COLOR_WHITE, COLOR_BLUE);
+ init_pair(PAIR_YELLOW_CYAN, COLOR_YELLOW, COLOR_CYAN);
+ init_pair(PAIR_BLACK_CYAN, COLOR_BLACK, COLOR_CYAN);
+ init_pair(PAIR_YELLOW_BLUE, COLOR_YELLOW, COLOR_BLUE);
+ }
+
+ regedit = talloc_zero(mem_ctx, struct regedit);
+ SMB_ASSERT(regedit != NULL);
+ regedit_main = regedit;
+
+ regedit->registry_context = ctx;
+ regedit->main_window = stdscr;
+ keypad(regedit->main_window, TRUE);
+
+ mvwprintw(regedit->main_window, 0, 0, "Path: ");
+ regedit->path_label = newpad(1, PATH_WIDTH_MAX);
+ SMB_ASSERT(regedit->path_label);
+ wprintw(regedit->path_label, "/");
+ show_path(regedit_main);
+
+ root = tree_node_new_root(regedit, ctx);
+ SMB_ASSERT(root != NULL);
+
+ regedit->keys = tree_view_new(regedit, root, KEY_HEIGHT, KEY_WIDTH,
+ KEY_START_Y, KEY_START_X);
+ SMB_ASSERT(regedit->keys != NULL);
+
+ regedit->vl = value_list_new(regedit, VAL_HEIGHT, VAL_WIDTH,
+ VAL_START_Y, VAL_START_X);
+ SMB_ASSERT(regedit->vl != NULL);
+
+ regedit->tree_input = true;
+ print_heading(regedit);
+
+ tree_view_show(regedit->keys);
+ load_values(regedit);
+ value_list_show(regedit->vl);
+
+ update_panels();
+ doupdate();
+
+ do {
+ key = regedit_getch();
+
+ handle_main_input(regedit, key);
+ update_panels();
+ doupdate();
+ } while (key != 'q' && key != 'Q');
+
+ endwin();
+}
+
+int main(int argc, char **argv)
+{
+ const char **argv_const = discard_const_p(const char *, argv);
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ /* ... */
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ int opt;
+ poptContext pc;
+ TALLOC_CTX *frame;
+ struct registry_context *ctx;
+ WERROR rv;
+ bool ok;
+ struct loadparm_context *lp_ctx = NULL;
+
+ frame = talloc_stackframe();
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+
+ /* process options */
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv_const,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ poptFreeContext(pc);
+ samba_cmdline_burn(argc, argv);
+
+ rv = reg_open_samba3(frame, &ctx);
+ if (!W_ERROR_IS_OK(rv)) {
+ fprintf(stderr, "Unable to open registry: %s\n",
+ win_errstr(rv));
+ TALLOC_FREE(frame);
+
+ return 1;
+ }
+
+ display_window(frame, ctx);
+
+ gfree_all();
+
+ TALLOC_FREE(frame);
+
+ return 0;
+}
diff --git a/source3/utils/regedit.h b/source3/utils/regedit.h
new file mode 100644
index 0000000..55a25c9
--- /dev/null
+++ b/source3/utils/regedit.h
@@ -0,0 +1,77 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REGEDIT_H_
+#define _REGEDIT_H_
+
+struct registry_context;
+struct security_token;
+struct registry_key;
+
+struct samba3_registry_key {
+ struct registry_key *key;
+};
+
+WERROR reg_openhive_wrap(TALLOC_CTX *ctx, const char *hive,
+ struct samba3_registry_key *key);
+WERROR reg_openkey_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *parent,
+ const char *name, struct samba3_registry_key *key);
+WERROR reg_enumvalue_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *key,
+ uint32_t idx, char **name, uint32_t *type,
+ DATA_BLOB *data);
+WERROR reg_queryvalue_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *key,
+ const char *name, uint32_t *type, DATA_BLOB *data);
+WERROR reg_enumkey_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *key,
+ uint32_t idx, char **name, NTTIME *last_write_time);
+WERROR reg_createkey_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *parent,
+ const char *subkeypath,
+ struct samba3_registry_key *pkey);
+WERROR reg_deletekey_wrap(struct samba3_registry_key *parent,
+ const char *path);
+WERROR reg_deletevalue_wrap(struct samba3_registry_key *key, const char *name);
+WERROR reg_queryinfokey_wrap(struct samba3_registry_key *key,
+ uint32_t *num_subkeys, uint32_t *max_subkeylen,
+ uint32_t *max_subkeysize, uint32_t *num_values,
+ uint32_t *max_valnamelen,
+ uint32_t *max_valbufsize, uint32_t *secdescsize,
+ NTTIME *last_changed_time);
+WERROR reg_setvalue_wrap(struct samba3_registry_key *key, const char *name,
+ uint32_t type, const DATA_BLOB data);
+WERROR reg_init_wrap(void);
+
+WERROR reg_open_samba3(TALLOC_CTX *mem_ctx, struct registry_context **ctx);
+
+int regedit_getch(void);
+
+typedef bool (*regedit_search_match_fn_t)(const char *, const char *);
+
+struct regedit_search_opts {
+ const char *query;
+ regedit_search_match_fn_t match;
+ bool search_key;
+ bool search_value;
+ bool search_recursive;
+ bool search_case;
+};
+
+#define PAIR_YELLOW_CYAN 1
+#define PAIR_BLACK_CYAN 2
+#define PAIR_YELLOW_BLUE 3
+
+#endif
diff --git a/source3/utils/regedit_dialog.c b/source3/utils/regedit_dialog.c
new file mode 100644
index 0000000..d1cb45f
--- /dev/null
+++ b/source3/utils/regedit_dialog.c
@@ -0,0 +1,2328 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "regedit.h"
+#include "regedit_dialog.h"
+#include "regedit_valuelist.h"
+#include "regedit_hexedit.h"
+#include "util_reg.h"
+#include "lib/registry/registry.h"
+#include "lib/util/smb_strtox.h"
+#include <stdarg.h>
+#include <form.h>
+
+static char *string_trim_n(TALLOC_CTX *ctx, const char *buf, size_t n)
+{
+ char *str;
+
+ str = talloc_strndup(ctx, buf, n);
+
+ if (str) {
+ trim_string(str, " ", " ");
+ }
+
+ return str;
+}
+
+static char *string_trim(TALLOC_CTX *ctx, const char *buf)
+{
+ char *str;
+
+ str = talloc_strdup(ctx, buf);
+
+ if (str) {
+ trim_string(str, " ", " ");
+ }
+
+ return str;
+}
+
+static int dialog_free(struct dialog *dia)
+{
+ dialog_destroy(dia);
+
+ return 0;
+}
+
+static bool default_validator(struct dialog *dia, struct dialog_section *sect,
+ void *arg)
+{
+ return true;
+}
+
+struct dialog *dialog_new(TALLOC_CTX *ctx, short color, const char *title,
+ int y, int x)
+{
+ struct dialog *dia;
+
+ dia = talloc_zero(ctx, struct dialog);
+ if (dia == NULL) {
+ return NULL;
+ }
+
+ talloc_set_destructor(dia, dialog_free);
+
+ dia->title = talloc_strdup(dia, title);
+ if (dia->title == NULL) {
+ goto fail;
+ }
+ dia->x = x;
+ dia->y = y;
+ dia->color = color;
+ dia->submit = default_validator;
+
+ return dia;
+
+fail:
+ talloc_free(dia);
+
+ return NULL;
+
+}
+
+void dialog_set_submit_cb(struct dialog *dia, dialog_submit_cb cb, void *arg)
+{
+ dia->submit = cb;
+ dia->submit_arg = arg;
+}
+
+static void center_above_window(int *nlines, int *ncols, int *y, int *x)
+{
+ int centery, centerx;
+
+ centery = LINES / 2;
+ centerx = COLS / 2;
+ *y = 0;
+ *x = 0;
+
+ if (*nlines > LINES) {
+ *nlines = LINES;
+ }
+ if (*ncols > COLS) {
+ *ncols = COLS;
+ }
+
+ if (*nlines/2 < centery) {
+ *y = centery - *nlines / 2;
+ }
+ if (*ncols/2 < centerx) {
+ *x = centerx - *ncols / 2;
+ }
+}
+
+void dialog_section_destroy(struct dialog_section *section)
+{
+ if (section->ops->destroy) {
+ section->ops->destroy(section);
+ }
+ if (section->window) {
+ delwin(section->window);
+ section->window = NULL;
+ }
+}
+
+void dialog_section_init(struct dialog_section *section,
+ const struct dialog_section_ops *ops,
+ int nlines, int ncols)
+{
+ section->ops = ops;
+ section->nlines = nlines;
+ section->ncols = ncols;
+}
+
+const char *dialog_section_get_name(struct dialog_section *section)
+{
+ return section->name;
+}
+
+void dialog_section_set_name(struct dialog_section *section, const char *name)
+{
+ TALLOC_FREE(section->name);
+ section->name = talloc_strdup(section, name);
+}
+
+void dialog_section_set_justify(struct dialog_section *section,
+ enum section_justify justify)
+{
+ section->justify = justify;
+}
+
+/* append a section to the dialog's circular list */
+void dialog_append_section(struct dialog *dia,
+ struct dialog_section *section)
+{
+ SMB_ASSERT(section != NULL);
+
+ if (!dia->head_section) {
+ dia->head_section = section;
+ }
+ if (dia->tail_section) {
+ dia->tail_section->next = section;
+ }
+ section->prev = dia->tail_section;
+ section->next = dia->head_section;
+ dia->head_section->prev = section;
+ dia->tail_section = section;
+}
+
+struct dialog_section *dialog_find_section(struct dialog *dia, const char *name)
+{
+ struct dialog_section *section = dia->head_section;
+
+ do {
+ if (section->name && strequal(section->name, name)) {
+ return section;
+ }
+ section = section->next;
+ } while (section != dia->head_section);
+
+ return NULL;
+}
+
+static void section_on_input(struct dialog *dia, int c)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section->ops->on_input) {
+ return;
+ }
+ section->ops->on_input(dia, section, c);
+}
+
+static bool section_on_tab(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section || !section->ops->on_tab) {
+ return false;
+ }
+ return section->ops->on_tab(dia, section);
+}
+
+static bool section_on_btab(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section || !section->ops->on_btab) {
+ return false;
+ }
+ return section->ops->on_btab(dia, section);
+}
+
+static bool section_on_up(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section || !section->ops->on_up) {
+ return false;
+ }
+ return section->ops->on_up(dia, section);
+}
+
+static bool section_on_down(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section || !section->ops->on_down) {
+ return false;
+ }
+ return section->ops->on_down(dia, section);
+}
+
+static bool section_on_left(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section || !section->ops->on_left) {
+ return false;
+ }
+ return section->ops->on_left(dia, section);
+}
+
+static bool section_on_right(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section || !section->ops->on_right) {
+ return false;
+ }
+ return section->ops->on_right(dia, section);
+}
+
+static enum dialog_action section_on_enter(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section || !section->ops->on_enter) {
+ return DIALOG_OK;
+ }
+ return section->ops->on_enter(dia, section);
+}
+
+static bool section_on_focus(struct dialog *dia, bool forward)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (!section->ops->on_focus) {
+ return false;
+ }
+ return section->ops->on_focus(dia, section, forward);
+}
+
+static void section_on_leave_focus(struct dialog *dia)
+{
+ struct dialog_section *section = dia->current_section;
+
+ if (section->ops->on_leave_focus) {
+ section->ops->on_leave_focus(dia, section);
+ }
+}
+
+static void section_set_next_focus(struct dialog *dia)
+{
+ section_on_leave_focus(dia);
+
+ do {
+ dia->current_section = dia->current_section->next;
+ } while (!section_on_focus(dia, true));
+}
+
+static void section_set_previous_focus(struct dialog *dia)
+{
+ section_on_leave_focus(dia);
+
+ do {
+ dia->current_section = dia->current_section->prev;
+ } while (!section_on_focus(dia, false));
+}
+
+WERROR dialog_create(struct dialog *dia)
+{
+ WERROR rv = WERR_OK;
+ int row, col;
+ int nlines, ncols;
+ struct dialog_section *section;
+
+ nlines = 0;
+ ncols = 0;
+ SMB_ASSERT(dia->head_section != NULL);
+
+ /* calculate total size based on sections */
+ section = dia->head_section;
+ do {
+ nlines += section->nlines;
+ ncols = MAX(ncols, section->ncols);
+ section = section->next;
+ } while (section != dia->head_section);
+
+ /* fill in widths for sections that expand */
+ section = dia->head_section;
+ do {
+ if (section->ncols < 0) {
+ section->ncols = ncols;
+ }
+ section = section->next;
+ } while (section != dia->head_section);
+
+ /* create window for dialog */
+ nlines += 4;
+ ncols += 6;
+ dia->pad = newpad(nlines, ncols);
+ if (dia->pad == NULL) {
+ rv = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+ dia->centered = false;
+ if (dia->y < 0 || dia->x < 0) {
+ dia->centered = true;
+ center_above_window(&nlines, &ncols, &dia->y, &dia->x);
+ }
+ dia->window = newwin(nlines, ncols, dia->y, dia->x);
+ if (dia->window == NULL) {
+ rv = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+ dia->panel = new_panel(dia->window);
+ if (dia->panel == NULL) {
+ rv = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+
+ /* setup color and border */
+ getmaxyx(dia->pad, nlines, ncols);
+ wbkgdset(dia->pad, ' ' | COLOR_PAIR(dia->color));
+ wclear(dia->pad);
+ mvwhline(dia->pad, 1, 2, 0, ncols - 4);
+ mvwhline(dia->pad, nlines - 2, 2, 0, ncols - 4);
+ mvwvline(dia->pad, 2, 1, 0, nlines - 4);
+ mvwvline(dia->pad, 2, ncols - 2, 0, nlines - 4);
+ mvwaddch(dia->pad, 1, 1, ACS_ULCORNER);
+ mvwaddch(dia->pad, 1, ncols - 2, ACS_URCORNER);
+ mvwaddch(dia->pad, nlines - 2, 1, ACS_LLCORNER);
+ mvwaddch(dia->pad, nlines - 2, ncols - 2, ACS_LRCORNER);
+ col = ncols / 2 - MIN(strlen(dia->title) + 2, ncols) / 2;
+ mvwprintw(dia->pad, 1, col, " %s ", dia->title);
+
+ /* create subwindows for each section */
+ row = 2;
+ section = dia->head_section;
+ do {
+ col = 3;
+
+ switch (section->justify) {
+ case SECTION_JUSTIFY_LEFT:
+ break;
+ case SECTION_JUSTIFY_CENTER:
+ col += (ncols - 6)/ 2 - section->ncols / 2;
+ break;
+ case SECTION_JUSTIFY_RIGHT:
+ break;
+ }
+
+ section->window = subpad(dia->pad, section->nlines,
+ section->ncols, row, col);
+ if (section->window == NULL) {
+ rv = WERR_NOT_ENOUGH_MEMORY;
+ goto fail;
+ }
+ SMB_ASSERT(section->ops->create != NULL);
+ rv = section->ops->create(dia, section);
+ row += section->nlines;
+ section = section->next;
+ } while (section != dia->head_section && W_ERROR_IS_OK(rv));
+
+ dia->current_section = dia->head_section;
+ section_set_next_focus(dia);
+
+fail:
+ return rv;
+}
+
+void dialog_show(struct dialog *dia)
+{
+ int nlines, ncols;
+ int pad_y, pad_x;
+ int y, x;
+ int rv;
+
+ touchwin(dia->pad);
+ getmaxyx(dia->window, nlines, ncols);
+ getmaxyx(dia->pad, pad_y, pad_x);
+ y = 0;
+ if (pad_y > nlines) {
+ y = (pad_y - nlines) / 2;
+ }
+ x = 0;
+ if (pad_x > ncols) {
+ x = (pad_x - ncols) / 2;
+ }
+ rv = copywin(dia->pad, dia->window, y, x, 0, 0,
+ nlines - 1, ncols - 1, false);
+ SMB_ASSERT(rv == OK);
+
+ getyx(dia->pad, pad_y, pad_x);
+ wmove(dia->window, pad_y - y, pad_x - x);
+ touchwin(dia->window);
+ wnoutrefresh(dia->window);
+}
+
+void dialog_destroy(struct dialog *dia)
+{
+ struct dialog_section *section;
+
+ section = dia->head_section;
+ do {
+ dialog_section_destroy(section);
+ section = section->next;
+ } while (section != dia->head_section);
+
+ if (dia->panel) {
+ del_panel(dia->panel);
+ dia->panel = NULL;
+ }
+ if (dia->window) {
+ delwin(dia->window);
+ dia->window = NULL;
+ }
+}
+
+static int dialog_getch(struct dialog *dia)
+{
+ int c;
+
+ c = regedit_getch();
+ if (c == KEY_RESIZE) {
+ int nlines, ncols, y, x;
+ int pad_nlines, pad_ncols;
+ int win_nlines, win_ncols;
+
+ getmaxyx(dia->window, win_nlines, win_ncols);
+ getmaxyx(dia->pad, pad_nlines, pad_ncols);
+ getbegyx(dia->window, y, x);
+
+ nlines = pad_nlines;
+ ncols = pad_ncols;
+
+ if (dia->centered) {
+ center_above_window(&nlines, &ncols, &y, &x);
+ } else {
+ if (nlines + y > LINES) {
+ if (nlines > LINES) {
+ y = 0;
+ } else {
+ y = LINES - nlines;
+ }
+ }
+ if (ncols + x > COLS) {
+ if (ncols > COLS) {
+ x = 0;
+ } else {
+ x = COLS - ncols;
+ }
+ }
+ }
+ if (nlines != win_nlines || ncols != win_ncols) {
+ wresize(dia->window, nlines, ncols);
+ replace_panel(dia->panel, dia->window);
+ }
+ move_panel(dia->panel, y, x);
+ }
+
+ return c;
+}
+
+bool dialog_handle_input(struct dialog *dia, WERROR *err,
+ enum dialog_action *action)
+{
+ int c;
+
+ *err = WERR_OK;
+
+ c = dialog_getch(dia);
+
+ switch (c) {
+ case '\t':
+ if (!section_on_tab(dia)) {
+ section_set_next_focus(dia);
+ }
+ break;
+ case KEY_BTAB:
+ if (!section_on_btab(dia)) {
+ section_set_previous_focus(dia);
+ }
+ break;
+ case KEY_UP:
+ if (!section_on_up(dia)) {
+ section_set_previous_focus(dia);
+ }
+ break;
+ case KEY_DOWN:
+ if (!section_on_down(dia)) {
+ section_set_next_focus(dia);
+ }
+ break;
+ case KEY_LEFT:
+ if (!section_on_left(dia)) {
+ section_set_previous_focus(dia);
+ }
+ break;
+ case KEY_RIGHT:
+ if (!section_on_right(dia)) {
+ section_set_next_focus(dia);
+ }
+ break;
+ case '\n':
+ case KEY_ENTER:
+ *action = section_on_enter(dia);
+ switch (*action) {
+ case DIALOG_IGNORE:
+ break;
+ case DIALOG_CANCEL:
+ return false;
+ case DIALOG_OK:
+ return !dia->submit(dia, dia->current_section,
+ dia->submit_arg);
+ }
+ break;
+ case 27: /* ESC */
+ return false;
+ default:
+ section_on_input(dia, c);
+ break;
+ }
+
+ return true;
+}
+
+void dialog_modal_loop(struct dialog *dia, WERROR *err,
+ enum dialog_action *action)
+{
+ do {
+ dialog_show(dia);
+ update_panels();
+ doupdate();
+ } while (dialog_handle_input(dia, err, action));
+}
+
+/* text label */
+struct dialog_section_label {
+ struct dialog_section section;
+ char **text;
+};
+
+static WERROR label_create(struct dialog *dia, struct dialog_section *section)
+{
+ int row;
+ struct dialog_section_label *label =
+ talloc_get_type_abort(section, struct dialog_section_label);
+
+ for (row = 0; row < section->nlines; ++row) {
+ mvwaddstr(section->window, row, 0, label->text[row]);
+ }
+
+ return WERR_OK;
+}
+
+struct dialog_section_ops label_ops = {
+ .create = label_create,
+};
+
+static int label_free(struct dialog_section_label *label)
+{
+ dialog_section_destroy(&label->section);
+ return 0;
+}
+
+struct dialog_section *dialog_section_label_new_va(TALLOC_CTX *ctx,
+ const char *msg, va_list ap)
+{
+ struct dialog_section_label *label;
+ char *tmp, *ptmp, *line, *saveptr;
+ int nlines, ncols;
+
+ label = talloc_zero(ctx, struct dialog_section_label);
+ if (label == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(label, label_free);
+ tmp = talloc_vasprintf(label, msg, ap);
+ if (tmp == NULL) {
+ goto fail;
+ }
+
+ for (nlines = 0, ncols = 0, ptmp = tmp;
+ (line = strtok_r(ptmp, "\n", &saveptr)) != NULL;
+ ++nlines) {
+ ptmp = NULL;
+ label->text = talloc_realloc(label, label->text,
+ char *, nlines + 1);
+ if (label->text == NULL) {
+ goto fail;
+ }
+ ncols = MAX(ncols, strlen(line));
+ label->text[nlines] = talloc_strdup(label->text, line);
+ if (label->text[nlines] == NULL) {
+ goto fail;
+ }
+ }
+ talloc_free(tmp);
+ dialog_section_init(&label->section, &label_ops, nlines, ncols);
+
+ return &label->section;
+
+fail:
+ talloc_free(label);
+ return NULL;
+}
+
+struct dialog_section *dialog_section_label_new(TALLOC_CTX *ctx,
+ const char *msg, ...)
+{
+ va_list ap;
+ struct dialog_section *rv;
+
+ va_start(ap, msg);
+ rv = dialog_section_label_new_va(ctx, msg, ap);
+ va_end(ap);
+
+ return rv;
+}
+
+/* horizontal separator */
+struct dialog_section_hsep {
+ struct dialog_section section;
+ int sep;
+};
+
+static WERROR hsep_create(struct dialog *dia, struct dialog_section *section)
+{
+ int y, x;
+ struct dialog_section_hsep *hsep =
+ talloc_get_type_abort(section, struct dialog_section_hsep);
+
+ whline(section->window, hsep->sep, section->ncols);
+
+ if (hsep->sep == 0 || hsep->sep == ACS_HLINE) {
+ /* change the border characters around this section to
+ tee chars */
+ getparyx(section->window, y, x);
+ mvwaddch(dia->pad, y, x - 1, ACS_HLINE);
+ mvwaddch(dia->pad, y, x - 2, ACS_LTEE);
+ mvwaddch(dia->pad, y, x + section->ncols, ACS_HLINE);
+ mvwaddch(dia->pad, y, x + section->ncols + 1, ACS_RTEE);
+ }
+
+ return WERR_OK;
+}
+
+struct dialog_section_ops hsep_ops = {
+ .create = hsep_create
+};
+
+static int hsep_free(struct dialog_section_hsep *hsep)
+{
+ dialog_section_destroy(&hsep->section);
+ return 0;
+}
+
+struct dialog_section *dialog_section_hsep_new(TALLOC_CTX *ctx, int sep)
+{
+ struct dialog_section_hsep *hsep;
+
+ hsep = talloc_zero(ctx, struct dialog_section_hsep);
+ if (hsep) {
+ talloc_set_destructor(hsep, hsep_free);
+ dialog_section_init(&hsep->section, &hsep_ops, 1, -1);
+ hsep->sep = sep;
+ }
+
+ return &hsep->section;
+}
+
+/* text input field */
+struct dialog_section_text_field {
+ struct dialog_section section;
+ unsigned opts;
+ FIELD *field[2];
+ FORM *form;
+ int length;
+};
+
+static int get_cursor_col(struct dialog_section_text_field *field)
+{
+ int col;
+
+ col = field->form->curcol + field->form->begincol;
+
+ return col;
+}
+
+static WERROR text_field_create(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ text_field->field[0] = new_field(section->nlines, section->ncols,
+ 0, 0, 0, 0);
+ if (text_field->field[0] == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ set_field_back(text_field->field[0], A_REVERSE);
+ set_field_opts(text_field->field[0], text_field->opts);
+
+ text_field->form = new_form(text_field->field);
+ if (text_field->form == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ set_form_win(text_field->form, dia->window);
+ set_form_sub(text_field->form, section->window);
+ set_current_field(text_field->form, text_field->field[0]);
+ post_form(text_field->form);
+
+ return WERR_OK;
+}
+
+static void text_field_destroy(struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ if (text_field->form) {
+ unpost_form(text_field->form);
+ free_form(text_field->form);
+ text_field->form = NULL;
+ }
+ if (text_field->field[0]) {
+ free_field(text_field->field[0]);
+ text_field->field[0] = NULL;
+ }
+}
+
+static void text_field_on_input(struct dialog *dia,
+ struct dialog_section *section,
+ int c)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ switch (c) {
+ case KEY_BACKSPACE:
+ if (text_field->length) {
+ text_field->length--;
+ }
+ form_driver(text_field->form, REQ_DEL_PREV);
+ break;
+ case '\x7f':
+ case KEY_DC:
+ if (text_field->length) {
+ text_field->length--;
+ }
+ form_driver(text_field->form, REQ_DEL_CHAR);
+ break;
+ default:
+ text_field->length++;
+ form_driver(text_field->form, c);
+ break;
+ }
+}
+
+static bool text_field_on_up(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ if (section->nlines > 1) {
+ form_driver(text_field->form, REQ_UP_CHAR);
+ return true;
+ }
+ return false;
+}
+
+static bool text_field_on_down(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ if (section->nlines > 1) {
+ form_driver(text_field->form, REQ_DOWN_CHAR);
+ return true;
+ }
+ return false;
+}
+
+static bool text_field_on_left(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ form_driver(text_field->form, REQ_LEFT_CHAR);
+
+ return true;
+}
+
+static bool text_field_on_right(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ if (section->nlines > 1 ||
+ get_cursor_col(text_field) < text_field->length) {
+ form_driver(text_field->form, REQ_RIGHT_CHAR);
+ }
+
+ return true;
+}
+
+static enum dialog_action text_field_on_enter(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ if (section->nlines > 1) {
+ text_field->length += text_field->form->cols;
+ form_driver(text_field->form, REQ_NEW_LINE);
+ return DIALOG_IGNORE;
+ }
+
+ return DIALOG_OK;
+}
+
+static bool text_field_on_focus(struct dialog *dia,
+ struct dialog_section *section, bool forward)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ pos_form_cursor(text_field->form);
+
+ return true;
+}
+
+struct dialog_section_ops text_field_ops = {
+ .create = text_field_create,
+ .destroy = text_field_destroy,
+ .on_input = text_field_on_input,
+ .on_up = text_field_on_up,
+ .on_down = text_field_on_down,
+ .on_left = text_field_on_left,
+ .on_right = text_field_on_right,
+ .on_enter = text_field_on_enter,
+ .on_focus = text_field_on_focus
+};
+
+static int text_field_free(struct dialog_section_text_field *text_field)
+{
+ dialog_section_destroy(&text_field->section);
+ return 0;
+}
+
+struct dialog_section *dialog_section_text_field_new(TALLOC_CTX *ctx,
+ int height, int width)
+{
+ struct dialog_section_text_field *text_field;
+
+ text_field = talloc_zero(ctx, struct dialog_section_text_field);
+ if (text_field == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(text_field, text_field_free);
+ dialog_section_init(&text_field->section, &text_field_ops,
+ height, width);
+ text_field->opts = O_ACTIVE | O_PUBLIC | O_EDIT | O_VISIBLE | O_NULLOK;
+
+ return &text_field->section;
+}
+
+const char *dialog_section_text_field_get(TALLOC_CTX *ctx,
+ struct dialog_section *section)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ form_driver(text_field->form, REQ_VALIDATION);
+
+ return string_trim(ctx, field_buffer(text_field->field[0], 0));
+}
+
+void dialog_section_text_field_set(struct dialog_section *section,
+ const char *s)
+{
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ text_field->length = strlen(s);
+ set_field_buffer(text_field->field[0], 0, s);
+}
+
+const char **dialog_section_text_field_get_lines(TALLOC_CTX *ctx,
+ struct dialog_section *section)
+{
+ int rows, cols, max;
+ const char **arr;
+ size_t i;
+ const char *buf;
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ form_driver(text_field->form, REQ_VALIDATION);
+ buf = field_buffer(text_field->field[0], 0);
+
+ dynamic_field_info(text_field->field[0], &rows, &cols, &max);
+
+ arr = talloc_zero_array(ctx, const char *, rows + 1);
+ if (arr == NULL) {
+ return NULL;
+ }
+ for (i = 0; *buf; ++i, buf += cols) {
+ SMB_ASSERT(i < rows);
+ arr[i] = string_trim_n(arr, buf, cols);
+ }
+
+ return arr;
+}
+
+WERROR dialog_section_text_field_set_lines(TALLOC_CTX *ctx,
+ struct dialog_section *section,
+ const char **array)
+{
+ int rows, cols, max;
+ size_t padding, length, idx;
+ const char **arrayp;
+ char *buf = NULL;
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ dynamic_field_info(text_field->field[0], &rows, &cols, &max);
+ /* try to fit each string on it's own line. each line
+ needs to be padded with whitespace manually, since
+ ncurses fields do not have newlines. */
+ for (idx = 0, arrayp = array; *arrayp != NULL; ++arrayp) {
+ length = MIN(strlen(*arrayp), cols);
+ padding = cols - length;
+ buf = talloc_realloc(ctx, buf, char,
+ talloc_array_length(buf) +
+ length + padding + 1);
+ if (buf == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ memcpy(&buf[idx], *arrayp, length);
+ idx += length;
+ memset(&buf[idx], ' ', padding);
+ idx += padding;
+ buf[idx] = '\0';
+ }
+
+ set_field_buffer(text_field->field[0], 0, buf);
+ talloc_free(buf);
+
+ return WERR_OK;
+}
+
+bool dialog_section_text_field_get_int(struct dialog_section *section,
+ long long *out)
+{
+ bool rv;
+ const char *buf;
+ char *endp;
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ form_driver(text_field->form, REQ_VALIDATION);
+
+ buf = string_trim(section, field_buffer(text_field->field[0], 0));
+ if (buf == NULL) {
+ return false;
+ }
+ *out = strtoll(buf, &endp, 0);
+ rv = true;
+ if (endp == buf || endp == NULL || endp[0] != '\0') {
+ rv = false;
+ }
+
+ return rv;
+}
+
+
+bool dialog_section_text_field_get_uint(struct dialog_section *section,
+ unsigned long long *out)
+{
+ const char *buf;
+ int error = 0;
+ struct dialog_section_text_field *text_field =
+ talloc_get_type_abort(section, struct dialog_section_text_field);
+
+ form_driver(text_field->form, REQ_VALIDATION);
+
+ buf = string_trim(section, field_buffer(text_field->field[0], 0));
+ if (buf == NULL) {
+ return false;
+ }
+ *out = smb_strtoull(buf, NULL, 0, &error, SMB_STR_FULL_STR_CONV);
+ if (error != 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/* hex editor field */
+struct dialog_section_hexedit {
+ struct dialog_section section;
+ struct hexedit *buf;
+};
+
+#define HEXEDIT_MIN_SIZE 1
+static WERROR hexedit_create(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ hexedit->buf = hexedit_new(dia, section->window, NULL,
+ HEXEDIT_MIN_SIZE);
+ if (hexedit->buf == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ hexedit_refresh(hexedit->buf);
+
+ return WERR_OK;
+}
+
+static void hexedit_destroy(struct dialog_section *section)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ if (hexedit->buf) {
+ TALLOC_FREE(hexedit->buf);
+ }
+}
+
+static void hexedit_on_input(struct dialog *dia,
+ struct dialog_section *section,
+ int c)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ switch (c) {
+ case KEY_BACKSPACE:
+ hexedit_driver(hexedit->buf, HE_BACKSPACE);
+ break;
+ case '\x7f':
+ case KEY_DC:
+ hexedit_driver(hexedit->buf, HE_DELETE);
+ break;
+ default:
+ hexedit_driver(hexedit->buf, c);
+ break;
+ }
+}
+
+static bool hexedit_on_up(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ hexedit_driver(hexedit->buf, HE_CURSOR_UP);
+
+ return true;
+}
+
+static bool hexedit_on_down(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ hexedit_driver(hexedit->buf, HE_CURSOR_DOWN);
+
+ return true;
+}
+
+static bool hexedit_on_left(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ hexedit_driver(hexedit->buf, HE_CURSOR_LEFT);
+
+ return true;
+}
+
+static bool hexedit_on_right(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ hexedit_driver(hexedit->buf, HE_CURSOR_RIGHT);
+
+ return true;
+}
+
+static enum dialog_action hexedit_on_enter(struct dialog *dia,
+ struct dialog_section *section)
+{
+ return DIALOG_IGNORE;
+}
+
+static bool hexedit_on_focus(struct dialog *dia,
+ struct dialog_section *section, bool forward)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ hexedit_set_cursor(hexedit->buf);
+
+ return true;
+}
+
+struct dialog_section_ops hexedit_ops = {
+ .create = hexedit_create,
+ .destroy = hexedit_destroy,
+ .on_input = hexedit_on_input,
+ .on_up = hexedit_on_up,
+ .on_down = hexedit_on_down,
+ .on_left = hexedit_on_left,
+ .on_right = hexedit_on_right,
+ .on_enter = hexedit_on_enter,
+ .on_focus = hexedit_on_focus
+};
+
+static int hexedit_free(struct dialog_section_hexedit *hexedit)
+{
+ dialog_section_destroy(&hexedit->section);
+ return 0;
+}
+
+struct dialog_section *dialog_section_hexedit_new(TALLOC_CTX *ctx, int height)
+{
+ struct dialog_section_hexedit *hexedit;
+
+ hexedit = talloc_zero(ctx, struct dialog_section_hexedit);
+ if (hexedit == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(hexedit, hexedit_free);
+ dialog_section_init(&hexedit->section, &hexedit_ops,
+ height, LINE_WIDTH);
+
+ return &hexedit->section;
+}
+
+WERROR dialog_section_hexedit_set_buf(struct dialog_section *section,
+ const void *data, size_t size)
+{
+ WERROR rv;
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ SMB_ASSERT(hexedit->buf != NULL);
+
+ rv = hexedit_set_buf(hexedit->buf, data, size);
+ if (W_ERROR_IS_OK(rv)) {
+ hexedit_refresh(hexedit->buf);
+ hexedit_set_cursor(hexedit->buf);
+ }
+
+ return rv;
+}
+
+void dialog_section_hexedit_get_buf(struct dialog_section *section,
+ const void **data, size_t *size)
+{
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ SMB_ASSERT(hexedit->buf != NULL);
+ *data = hexedit_get_buf(hexedit->buf);
+ *size = hexedit_get_buf_len(hexedit->buf);
+}
+
+WERROR dialog_section_hexedit_resize(struct dialog_section *section,
+ size_t size)
+{
+ WERROR rv;
+ struct dialog_section_hexedit *hexedit =
+ talloc_get_type_abort(section, struct dialog_section_hexedit);
+
+ SMB_ASSERT(hexedit->buf != NULL);
+ rv = hexedit_resize_buffer(hexedit->buf, size);
+ if (W_ERROR_IS_OK(rv)) {
+ hexedit_refresh(hexedit->buf);
+ }
+
+ return rv;
+}
+
+
+/* button box */
+struct dialog_section_buttons {
+ struct dialog_section section;
+ struct button_spec *spec;
+ int current_button;
+};
+
+static void buttons_unhighlight(struct dialog_section_buttons *buttons)
+{
+ short pair;
+ attr_t attr;
+
+ /*
+ * Some GCC versions will complain if the macro version of
+ * wattr_get is used. So we should enforce the use of the
+ * function instead. See:
+ * http://lists.gnu.org/archive/html/bug-ncurses/2013-12/msg00017.html
+ */
+ (wattr_get)(buttons->section.window, &attr, &pair, NULL);
+ mvwchgat(buttons->section.window, 0, 0, -1, A_NORMAL, pair, NULL);
+ wnoutrefresh(buttons->section.window);
+}
+
+static void buttons_highlight(struct dialog_section_buttons *buttons)
+{
+ struct button_spec *spec = &buttons->spec[buttons->current_button];
+ short pair;
+ attr_t attr;
+
+ /*
+ * Some GCC versions will complain if the macro version of
+ * wattr_get is used. So we should enforce the use of the
+ * function instead. See:
+ * http://lists.gnu.org/archive/html/bug-ncurses/2013-12/msg00017.html
+ */
+ (wattr_get)(buttons->section.window, &attr, &pair, NULL);
+ mvwchgat(buttons->section.window, 0, 0, -1, A_NORMAL, pair, NULL);
+ mvwchgat(buttons->section.window, 0, spec->col,
+ strlen(spec->label), A_REVERSE, pair, NULL);
+ wmove(buttons->section.window, 0, spec->col + 2);
+ wcursyncup(buttons->section.window);
+ wnoutrefresh(buttons->section.window);
+}
+
+static bool buttons_highlight_next(struct dialog_section_buttons *buttons)
+{
+ if (buttons->current_button < talloc_array_length(buttons->spec) - 1) {
+ buttons->current_button++;
+ buttons_highlight(buttons);
+ return true;
+ }
+ return false;
+}
+
+static bool buttons_highlight_previous(struct dialog_section_buttons *buttons)
+{
+ if (buttons->current_button > 0) {
+ buttons->current_button--;
+ buttons_highlight(buttons);
+ return true;
+ }
+ return false;
+}
+
+static WERROR buttons_create(struct dialog *dia,
+ struct dialog_section *section)
+{
+ size_t i, nbuttons;
+ struct dialog_section_buttons *buttons =
+ talloc_get_type_abort(section, struct dialog_section_buttons);
+
+ nbuttons = talloc_array_length(buttons->spec);
+ for (i = 0; i < nbuttons; ++i) {
+ struct button_spec *spec = &buttons->spec[i];
+ mvwaddstr(section->window, 0, spec->col, spec->label);
+ }
+
+ buttons->current_button = 0;
+
+ return WERR_OK;
+}
+
+static bool buttons_on_btab(struct dialog *dia, struct dialog_section *section)
+{
+ struct dialog_section_buttons *buttons =
+ talloc_get_type_abort(section, struct dialog_section_buttons);
+
+ return buttons_highlight_previous(buttons);
+}
+
+static bool buttons_on_tab(struct dialog *dia, struct dialog_section *section)
+{
+ struct dialog_section_buttons *buttons =
+ talloc_get_type_abort(section, struct dialog_section_buttons);
+
+ return buttons_highlight_next(buttons);
+}
+
+static enum dialog_action buttons_on_enter(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_buttons *buttons =
+ talloc_get_type_abort(section, struct dialog_section_buttons);
+ struct button_spec *spec = &buttons->spec[buttons->current_button];
+
+ if (spec->on_enter) {
+ return spec->on_enter(dia, section);
+ }
+
+ return spec->action;
+}
+
+static bool buttons_on_focus(struct dialog *dia,
+ struct dialog_section *section,
+ bool forward)
+{
+ struct dialog_section_buttons *buttons =
+ talloc_get_type_abort(section, struct dialog_section_buttons);
+
+ if (forward) {
+ buttons->current_button = 0;
+ } else {
+ buttons->current_button = talloc_array_length(buttons->spec) - 1;
+ }
+ buttons_highlight(buttons);
+
+ return true;
+}
+
+static void buttons_on_leave_focus(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_buttons *buttons =
+ talloc_get_type_abort(section, struct dialog_section_buttons);
+ buttons_unhighlight(buttons);
+}
+
+struct dialog_section_ops buttons_ops = {
+ .create = buttons_create,
+ .on_tab = buttons_on_tab,
+ .on_btab = buttons_on_btab,
+ .on_up = buttons_on_btab,
+ .on_down = buttons_on_tab,
+ .on_left = buttons_on_btab,
+ .on_right = buttons_on_tab,
+ .on_enter = buttons_on_enter,
+ .on_focus = buttons_on_focus,
+ .on_leave_focus = buttons_on_leave_focus
+};
+
+static int buttons_free(struct dialog_section_buttons *buttons)
+{
+ dialog_section_destroy(&buttons->section);
+ return 0;
+}
+
+struct dialog_section *dialog_section_buttons_new(TALLOC_CTX *ctx,
+ const struct button_spec *spec)
+{
+ struct dialog_section_buttons *buttons;
+ size_t i, nbuttons;
+ int width;
+
+ buttons = talloc_zero(ctx, struct dialog_section_buttons);
+ if (buttons == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(buttons, buttons_free);
+
+ for (nbuttons = 0; spec[nbuttons].label; ++nbuttons) {
+ }
+ buttons->spec = talloc_zero_array(buttons, struct button_spec, nbuttons);
+ if (buttons->spec == NULL) {
+ goto fail;
+ }
+
+ for (width = 0, i = 0; i < nbuttons; ++i) {
+ buttons->spec[i] = spec[i];
+ buttons->spec[i].label = talloc_asprintf(buttons->spec,
+ "[ %s ]",
+ spec[i].label);
+ if (!buttons->spec[i].label) {
+ goto fail;
+ }
+
+ buttons->spec[i].col = width;
+ width += strlen(buttons->spec[i].label);
+ if (i != nbuttons - 1) {
+ ++width;
+ }
+ }
+
+ dialog_section_init(&buttons->section, &buttons_ops, 1, width);
+
+ return &buttons->section;
+
+fail:
+ talloc_free(buttons);
+ return NULL;
+}
+
+/* options */
+struct dialog_section_options {
+ struct dialog_section section;
+ struct option_spec *spec;
+ int current_option;
+ bool single_select;
+};
+
+static void options_unhighlight(struct dialog_section_options *options)
+{
+ short pair;
+ attr_t attr;
+ size_t row;
+
+ /*
+ * Some GCC versions will complain if the macro version of
+ * wattr_get is used. So we should enforce the use of the
+ * function instead. See:
+ * http://lists.gnu.org/archive/html/bug-ncurses/2013-12/msg00017.html
+ */
+ (wattr_get)(options->section.window, &attr, &pair, NULL);
+ for (row = 0; row < options->section.nlines; ++row) {
+ mvwchgat(options->section.window, row, 0, -1, A_NORMAL, pair, NULL);
+ }
+ wnoutrefresh(options->section.window);
+}
+
+static void options_highlight(struct dialog_section_options *options)
+{
+ struct option_spec *spec = &options->spec[options->current_option];
+ short pair;
+ attr_t attr;
+ size_t row;
+
+ /*
+ * Some GCC versions will complain if the macro version of
+ * wattr_get is used. So we should enforce the use of the
+ * function instead. See:
+ * http://lists.gnu.org/archive/html/bug-ncurses/2013-12/msg00017.html
+ */
+ (wattr_get)(options->section.window, &attr, &pair, NULL);
+ for (row = 0; row < options->section.nlines; ++row) {
+ mvwchgat(options->section.window, row, 0, -1, A_NORMAL, pair, NULL);
+ }
+ mvwchgat(options->section.window, spec->row, spec->col,
+ strlen(spec->label), A_REVERSE, pair, NULL);
+ wmove(options->section.window, spec->row, spec->col + 4);
+ wcursyncup(options->section.window);
+ wnoutrefresh(options->section.window);
+}
+
+static void options_render_state(struct dialog_section_options *options)
+{
+ size_t i, noptions;
+
+ noptions = talloc_array_length(options->spec);
+ for (i = 0; i < noptions; ++i) {
+ struct option_spec *spec = &options->spec[i];
+ char c = ' ';
+ if (*spec->state)
+ c = 'x';
+ mvwaddch(options->section.window,
+ spec->row, spec->col + 1, c);
+ wnoutrefresh(options->section.window);
+ }
+}
+
+static bool options_highlight_next(struct dialog_section_options *options)
+{
+ if (options->current_option < talloc_array_length(options->spec) - 1) {
+ options->current_option++;
+ options_highlight(options);
+ return true;
+ }
+ return false;
+}
+
+static bool options_highlight_previous(struct dialog_section_options *options)
+{
+ if (options->current_option > 0) {
+ options->current_option--;
+ options_highlight(options);
+ return true;
+ }
+ return false;
+}
+
+static WERROR options_create(struct dialog *dia,
+ struct dialog_section *section)
+{
+ size_t i, noptions;
+ struct dialog_section_options *options =
+ talloc_get_type_abort(section, struct dialog_section_options);
+
+ noptions = talloc_array_length(options->spec);
+ for (i = 0; i < noptions; ++i) {
+ struct option_spec *spec = &options->spec[i];
+ mvwaddstr(section->window, spec->row, spec->col,
+ spec->label);
+ }
+
+ options->current_option = 0;
+ options_render_state(options);
+
+ return WERR_OK;
+}
+
+static bool options_on_btab(struct dialog *dia, struct dialog_section *section)
+{
+ struct dialog_section_options *options =
+ talloc_get_type_abort(section, struct dialog_section_options);
+
+ return options_highlight_previous(options);
+}
+
+static bool options_on_tab(struct dialog *dia, struct dialog_section *section)
+{
+ struct dialog_section_options *options =
+ talloc_get_type_abort(section, struct dialog_section_options);
+
+ return options_highlight_next(options);
+}
+
+static void options_on_input(struct dialog *dia, struct dialog_section *section, int c)
+{
+ struct dialog_section_options *options =
+ talloc_get_type_abort(section, struct dialog_section_options);
+
+ if (c == ' ') {
+ struct option_spec *spec = &options->spec[options->current_option];
+ if (options->single_select) {
+ size_t i, noptions;
+ noptions = talloc_array_length(options->spec);
+ for (i = 0; i < noptions; ++i) {
+ *(options->spec[i].state) = false;
+ }
+ }
+ *spec->state = !*spec->state;
+ options_unhighlight(options);
+ options_render_state(options);
+ options_highlight(options);
+ }
+}
+
+static enum dialog_action options_on_enter(struct dialog *dia, struct dialog_section *section)
+{
+ options_on_input(dia, section, ' ');
+ return DIALOG_OK;
+}
+
+static bool options_on_focus(struct dialog *dia,
+ struct dialog_section *section,
+ bool forward)
+{
+ struct dialog_section_options *options =
+ talloc_get_type_abort(section, struct dialog_section_options);
+
+ if (forward) {
+ options->current_option = 0;
+ } else {
+ options->current_option = talloc_array_length(options->spec) - 1;
+ }
+ options_highlight(options);
+
+ return true;
+}
+
+static void options_on_leave_focus(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section_options *options =
+ talloc_get_type_abort(section, struct dialog_section_options);
+ options_unhighlight(options);
+}
+
+struct dialog_section_ops options_ops = {
+ .create = options_create,
+ .on_tab = options_on_tab,
+ .on_btab = options_on_btab,
+ .on_up = options_on_btab,
+ .on_down = options_on_tab,
+ .on_left = options_on_btab,
+ .on_right = options_on_tab,
+ .on_input = options_on_input,
+ .on_enter = options_on_enter,
+ .on_focus = options_on_focus,
+ .on_leave_focus = options_on_leave_focus
+};
+
+static int options_free(struct dialog_section_options *options)
+{
+ dialog_section_destroy(&options->section);
+ return 0;
+}
+
+struct dialog_section *dialog_section_options_new(TALLOC_CTX *ctx,
+ const struct option_spec *spec,
+ int maxcol, bool single_select)
+{
+ struct dialog_section_options *options;
+ size_t i, noptions;
+ int width, maxwidth, maxrows;
+
+ options = talloc_zero(ctx, struct dialog_section_options);
+ if (options == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(options, options_free);
+
+ for (noptions = 0; spec[noptions].label; ++noptions) {
+ }
+ options->spec = talloc_zero_array(options, struct option_spec, noptions);
+ if (options->spec == NULL) {
+ goto fail;
+ }
+
+ maxrows = noptions / maxcol;
+ if (noptions % maxcol) {
+ ++maxrows;
+ }
+
+ for (width = 0, maxwidth = 0, i = 0; i < noptions; ++i) {
+ options->spec[i] = spec[i];
+ options->spec[i].label = talloc_asprintf(options->spec,
+ "[ ] %s",
+ spec[i].label);
+ if (!options->spec[i].label) {
+ goto fail;
+ }
+
+ options->spec[i].col = maxwidth;
+ options->spec[i].row = i % maxrows;
+ width = MAX(strlen(options->spec[i].label), width);
+ if (options->spec[i].row == maxrows - 1 || i == noptions - 1) {
+ maxwidth += width + 1;
+ width = 0;
+ }
+ }
+
+ dialog_section_init(&options->section, &options_ops, maxrows, maxwidth - 1);
+ options->single_select = single_select;
+
+ return &options->section;
+
+fail:
+ talloc_free(options);
+ return NULL;
+}
+
+
+enum input_type {
+ DLG_IN_LONG,
+ DLG_IN_ULONG,
+ DLG_IN_STR,
+};
+
+struct input_req {
+ TALLOC_CTX *ctx;
+ enum input_type type;
+ union {
+ void *out;
+ unsigned long *out_ulong;
+ long *out_long;
+ const char **out_str;
+ } out;
+};
+
+static bool input_on_submit(struct dialog *dia, struct dialog_section *section,
+ void *arg)
+{
+ struct input_req *req = arg;
+ struct dialog_section *data;
+ unsigned long long out_ulong;
+ long long out_long;
+
+ data = dialog_find_section(dia, "input");
+
+ switch (req->type) {
+ case DLG_IN_LONG:
+ if (!dialog_section_text_field_get_int(data, &out_long)) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Input must be a number.");
+ return false;
+ }
+ if (out_long < LONG_MIN || out_long > LONG_MAX) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Number is out of range.");
+ return false;
+ }
+ *req->out.out_long = out_long;
+ break;
+ case DLG_IN_ULONG:
+ if (!dialog_section_text_field_get_uint(data, &out_ulong)) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Input must be a number greater than zero.");
+ return false;
+ }
+ if (out_ulong > ULONG_MAX) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Number is out of range.");
+ return false;
+ }
+ *req->out.out_ulong = out_ulong;
+ break;
+ case DLG_IN_STR:
+ *req->out.out_str = dialog_section_text_field_get(req->ctx, data);
+ break;
+ }
+
+ return true;
+}
+
+static int dialog_input_internal(TALLOC_CTX *ctx, void *output,
+ enum input_type type,
+ const char *title,
+ const char *msg, va_list ap)
+ PRINTF_ATTRIBUTE(5,0);
+
+static int dialog_input_internal(TALLOC_CTX *ctx, void *output,
+ enum input_type type,
+ const char *title,
+ const char *msg, va_list ap)
+{
+ WERROR err;
+ struct input_req req;
+ enum dialog_action action;
+ struct dialog *dia;
+ struct dialog_section *section;
+ struct button_spec spec[] = {
+ {.label = "OK", .action = DIALOG_OK},
+ {.label = "Cancel", .action = DIALOG_CANCEL},
+ { 0 }
+ };
+
+ req.ctx = ctx;
+ req.type = type;
+ req.out.out = output;
+ *req.out.out_str = NULL;
+
+ dia = dialog_new(ctx, PAIR_BLACK_CYAN, title, -1, -1);
+ dialog_set_submit_cb(dia, input_on_submit, &req);
+ section = dialog_section_label_new_va(dia, msg, ap);
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, ' ');
+ dialog_append_section(dia, section);
+ section = dialog_section_text_field_new(dia, 1, -1);
+ dialog_section_set_name(section, "input");
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, 0);
+ dialog_append_section(dia, section);
+ section = dialog_section_buttons_new(dia, spec);
+ dialog_section_set_justify(section, SECTION_JUSTIFY_CENTER);
+ dialog_append_section(dia, section);
+
+ dialog_create(dia);
+ dialog_show(dia);
+ dialog_modal_loop(dia, &err, &action);
+ talloc_free(dia);
+
+ return action;
+}
+
+int dialog_input(TALLOC_CTX *ctx, const char **output, const char *title,
+ const char *msg, ...)
+{
+ va_list ap;
+ int rv;
+
+ va_start(ap, msg);
+ rv = dialog_input_internal(ctx, output, DLG_IN_STR, title, msg, ap);
+ va_end(ap);
+
+ return rv;
+}
+
+int dialog_input_ulong(TALLOC_CTX *ctx, unsigned long *output,
+ const char *title, const char *msg, ...)
+{
+ va_list ap;
+ int rv;
+
+ va_start(ap, msg);
+ rv = dialog_input_internal(ctx, output, DLG_IN_ULONG, title, msg, ap);
+ va_end(ap);
+
+ return rv;
+}
+
+int dialog_input_long(TALLOC_CTX *ctx, long *output,
+ const char *title, const char *msg, ...)
+{
+ va_list ap;
+ int rv;
+
+ va_start(ap, msg);
+ rv = dialog_input_internal(ctx, output, DLG_IN_LONG, title, msg, ap);
+ va_end(ap);
+
+ return rv;
+}
+
+int dialog_notice(TALLOC_CTX *ctx, enum dialog_type type,
+ const char *title, const char *msg, ...)
+{
+ va_list ap;
+ WERROR err;
+ enum dialog_action action;
+ struct dialog *dia;
+ struct dialog_section *section;
+ struct button_spec spec[3];
+
+ memset(&spec, '\0', sizeof(spec));
+ spec[0].label = "OK";
+ spec[0].action = DIALOG_OK;
+ if (type == DIA_CONFIRM) {
+ spec[1].label = "Cancel";
+ spec[1].action = DIALOG_CANCEL;
+ }
+
+ dia = dialog_new(ctx, PAIR_BLACK_CYAN, title, -1, -1);
+ va_start(ap, msg);
+ section = dialog_section_label_new_va(dia, msg, ap);
+ va_end(ap);
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, 0);
+ dialog_append_section(dia, section);
+ section = dialog_section_buttons_new(dia, spec);
+ dialog_section_set_justify(section, SECTION_JUSTIFY_CENTER);
+ dialog_append_section(dia, section);
+
+ dialog_create(dia);
+ dialog_show(dia);
+ dialog_modal_loop(dia, &err, &action);
+ talloc_free(dia);
+
+ return action;
+}
+
+
+struct edit_req {
+ uint32_t type;
+ uint32_t mode;
+ struct registry_key *key;
+ const struct value_item *vitem;
+};
+
+static WERROR fill_value_buffer(struct dialog *dia, struct edit_req *edit)
+{
+ char *tmp;
+ struct dialog_section *data;
+
+ if (edit->vitem == NULL) {
+ return WERR_OK;
+ }
+
+ data = dialog_find_section(dia, "data");
+ SMB_ASSERT(data != NULL);
+
+ switch (edit->mode) {
+ case REG_DWORD: {
+ uint32_t v = 0;
+ if (edit->vitem->data.length >= 4) {
+ v = IVAL(edit->vitem->data.data, 0);
+ }
+ tmp = talloc_asprintf(dia, "%u", (unsigned)v);
+ if (tmp == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ dialog_section_text_field_set(data, tmp);
+ talloc_free(tmp);
+ break;
+ }
+ case REG_SZ:
+ case REG_EXPAND_SZ: {
+ const char *s;
+
+ if (!pull_reg_sz(dia, &edit->vitem->data, &s)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ dialog_section_text_field_set(data, s);
+ break;
+ }
+ case REG_MULTI_SZ: {
+ const char **array;
+
+ if (!pull_reg_multi_sz(dia, &edit->vitem->data, &array)) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ return dialog_section_text_field_set_lines(dia, data, array);
+ }
+ case REG_BINARY:
+ default:
+ return dialog_section_hexedit_set_buf(data,
+ edit->vitem->data.data,
+ edit->vitem->data.length);
+ }
+
+ return WERR_OK;
+}
+
+static bool value_exists(TALLOC_CTX *ctx, const struct registry_key *key,
+ const char *name)
+{
+ uint32_t type;
+ DATA_BLOB blob;
+ WERROR rv;
+
+ rv = reg_key_get_value_by_name(ctx, key, name, &type, &blob);
+
+ return W_ERROR_IS_OK(rv);
+}
+
+static bool edit_on_submit(struct dialog *dia, struct dialog_section *section,
+ void *arg)
+{
+ struct edit_req *edit = arg;
+ WERROR rv;
+ DATA_BLOB blob;
+ const char *name;
+ struct dialog_section *name_section, *data;
+
+ name_section = dialog_find_section(dia, "name");
+ if (name_section) {
+ name = dialog_section_text_field_get(dia, name_section);
+ if (*name == '\0') {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Value name must not be blank.");
+ return false;
+ }
+ if (value_exists(dia, edit->key, name)) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Value named \"%s\" already exists.",
+ name);
+ return false;
+ }
+ } else {
+ SMB_ASSERT(edit->vitem);
+ name = edit->vitem->value_name;
+ }
+ SMB_ASSERT(name);
+
+ data = dialog_find_section(dia, "data");
+ SMB_ASSERT(data != NULL);
+
+ rv = WERR_OK;
+ switch (edit->mode) {
+ case REG_DWORD: {
+ unsigned long long v;
+ uint32_t val;
+
+ if (!dialog_section_text_field_get_uint(data, &v)) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "REG_DWORD value must be an integer.");
+ return false;
+ }
+ if (v > UINT32_MAX) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "REG_DWORD value must less than %lu.",
+ (unsigned long)UINT32_MAX);
+ return false;
+ }
+ val = (uint32_t)v;
+ blob = data_blob_talloc(dia, NULL, sizeof(val));
+ SIVAL(blob.data, 0, val);
+ break;
+ }
+ case REG_SZ:
+ case REG_EXPAND_SZ: {
+ const char *buf;
+
+ buf = dialog_section_text_field_get(dia, data);
+ if (!buf || !push_reg_sz(dia, &blob, buf)) {
+ rv = WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ }
+ case REG_MULTI_SZ: {
+ const char **lines;
+
+ lines = dialog_section_text_field_get_lines(dia, data);
+ if (!lines || !push_reg_multi_sz(dia, &blob, lines)) {
+ rv = WERR_NOT_ENOUGH_MEMORY;
+ }
+ break;
+ }
+ case REG_BINARY: {
+ const void *buf;
+ size_t len;
+
+ dialog_section_hexedit_get_buf(data, &buf, &len);
+ blob = data_blob_talloc(dia, buf, len);
+ break;
+ }
+ }
+
+ if (W_ERROR_IS_OK(rv)) {
+ rv = reg_val_set(edit->key, name, edit->type, blob);
+ }
+
+ if (!W_ERROR_IS_OK(rv)) {
+ const char *msg = get_friendly_werror_msg(rv);
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Error saving value:\n%s", msg);
+
+ return false;
+ }
+
+ return true;
+
+}
+
+static enum dialog_action edit_on_resize(struct dialog *dia,
+ struct dialog_section *section)
+{
+ struct dialog_section *data;
+ unsigned long size;
+ int rv;
+
+ data = dialog_find_section(dia, "data");
+ rv = dialog_input_ulong(dia, &size, "Resize", "Enter size of buffer");
+ if (rv == DIALOG_OK) {
+ dialog_section_hexedit_resize(data, size);
+ }
+
+ return DIALOG_IGNORE;
+}
+
+int dialog_edit_value(TALLOC_CTX *ctx, struct registry_key *key,
+ uint32_t type, const struct value_item *vitem,
+ bool force_binary, WERROR *err,
+ const char **name)
+{
+ enum dialog_action action;
+ struct dialog *dia;
+ struct dialog_section *section;
+ struct edit_req edit;
+ struct button_spec buttons[] = {
+ {.label = "OK", .action = DIALOG_OK},
+ {.label = "Cancel", .action = DIALOG_CANCEL},
+ { 0 }
+ };
+ struct button_spec buttons_hexedit[] = {
+ {.label = "OK", .action = DIALOG_OK},
+ {.label = "Resize Buffer", .on_enter = edit_on_resize},
+ {.label = "Cancel", .action = DIALOG_CANCEL},
+ { 0 }
+ };
+
+
+ edit.key = key;
+ edit.vitem = vitem;
+ edit.type = type;
+ edit.mode = type;
+ if (force_binary || (vitem && vitem->unprintable)) {
+ edit.mode = REG_BINARY;
+ }
+
+ dia = dialog_new(ctx, PAIR_BLACK_CYAN, "Edit Value", -1, -1);
+ dialog_set_submit_cb(dia, edit_on_submit, &edit);
+
+ section = dialog_section_label_new(dia, "Type");
+ dialog_append_section(dia, section);
+ section = dialog_section_label_new(dia, "%s",
+ str_regtype(type));
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, ' ');
+ dialog_append_section(dia, section);
+
+ section = dialog_section_label_new(dia, "Name");
+ dialog_append_section(dia, section);
+ if (vitem) {
+ section = dialog_section_label_new(dia, "%s",
+ vitem->value_name);
+ } else {
+ section = dialog_section_text_field_new(dia, 1, 50);
+ dialog_section_set_name(section, "name");
+ }
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, ' ');
+ dialog_append_section(dia, section);
+
+ section = dialog_section_label_new(dia, "Data");
+ dialog_append_section(dia, section);
+
+ switch (edit.mode) {
+ case REG_DWORD:
+ case REG_SZ:
+ case REG_EXPAND_SZ:
+ section = dialog_section_text_field_new(dia, 1, 50);
+ break;
+ case REG_MULTI_SZ:
+ section = dialog_section_text_field_new(dia, 10, 50);
+ break;
+ case REG_BINARY:
+ default:
+ section = dialog_section_hexedit_new(dia, 10);
+ break;
+ }
+
+ dialog_section_set_name(section, "data");
+ dialog_append_section(dia, section);
+
+ section = dialog_section_hsep_new(dia, 0);
+ dialog_append_section(dia, section);
+ if (edit.mode == REG_BINARY) {
+ section = dialog_section_buttons_new(dia, buttons_hexedit);
+ } else {
+ section = dialog_section_buttons_new(dia, buttons);
+ }
+ dialog_section_set_justify(section, SECTION_JUSTIFY_CENTER);
+ dialog_append_section(dia, section);
+
+ dialog_create(dia);
+
+ *err = fill_value_buffer(dia, &edit);
+ if (!W_ERROR_IS_OK(*err)) {
+ return DIALOG_CANCEL;
+ }
+
+ dialog_show(dia);
+ dialog_modal_loop(dia, err, &action);
+
+ if (action == DIALOG_OK && name) {
+ if (vitem) {
+ *name = talloc_strdup(ctx, vitem->value_name);
+ } else if ((section = dialog_find_section(dia, "name"))) {
+ *name = dialog_section_text_field_get(ctx, section);
+ }
+ }
+
+ talloc_free(dia);
+
+ return action;
+}
+
+int dialog_select_type(TALLOC_CTX *ctx, int *type)
+{
+ WERROR err;
+ enum dialog_action action;
+ struct dialog *dia;
+ struct dialog_section *section;
+ const char *reg_types[] = {
+ "REG_BINARY",
+ "REG_DWORD",
+ "REG_EXPAND_SZ",
+ "REG_MULTI_SZ",
+ "REG_SZ"
+ };
+ #define NTYPES ARRAY_SIZE(reg_types)
+ struct button_spec spec[] = {
+ {.label = "OK", .action = DIALOG_OK},
+ {.label = "Cancel", .action = DIALOG_CANCEL},
+ { 0 }
+ };
+ bool flags[NTYPES] = { true };
+ struct option_spec opsec[NTYPES + 1];
+ unsigned i;
+
+ memset(&opsec, '\0', sizeof(opsec));
+ for (i = 0; i < NTYPES; ++i) {
+ opsec[i].label = reg_types[i];
+ opsec[i].state = &flags[i];
+ }
+
+ dia = dialog_new(ctx, PAIR_BLACK_CYAN, "New Value", -1, -1);
+
+ section = dialog_section_label_new(dia, "Select type for new value:");
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, ' ');
+ dialog_append_section(dia, section);
+ section = dialog_section_options_new(dia, opsec, 2, true);
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, 0);
+ dialog_append_section(dia, section);
+ section = dialog_section_buttons_new(dia, spec);
+ dialog_section_set_justify(section, SECTION_JUSTIFY_CENTER);
+ dialog_append_section(dia, section);
+
+ dialog_create(dia);
+ dialog_show(dia);
+
+ dialog_modal_loop(dia, &err, &action);
+ if (action == DIALOG_OK) {
+ for (i = 0; i < NTYPES; ++i) {
+ if (flags[i]) {
+ *type = regtype_by_string(reg_types[i]);
+ break;
+ }
+ }
+ }
+
+ talloc_free(dia);
+
+ return action;
+}
+
+struct search_req {
+ TALLOC_CTX *ctx;
+ struct regedit_search_opts *opts;
+};
+
+static bool search_on_submit(struct dialog *dia, struct dialog_section *section,
+ void *arg)
+{
+ struct search_req *search = arg;
+ struct dialog_section *query;
+
+ query = dialog_find_section(dia, "query");
+ SMB_ASSERT(query != NULL);
+
+ if (!search->opts->search_key && !search->opts->search_value) {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Must search a key and/or a value");
+ return false;
+ }
+
+ talloc_free(discard_const(search->opts->query));
+ search->opts->query = dialog_section_text_field_get(search->ctx, query);
+ SMB_ASSERT(search->opts->query != NULL);
+ if (search->opts->query[0] == '\0') {
+ dialog_notice(dia, DIA_ALERT, "Error",
+ "Query must not be blank.");
+ return false;
+ }
+
+ return true;
+}
+
+int dialog_search_input(TALLOC_CTX *ctx, struct regedit_search_opts *opts)
+{
+ WERROR err;
+ enum dialog_action action;
+ struct dialog *dia;
+ struct dialog_section *section, *query;
+ struct search_req search;
+ struct button_spec spec[] = {
+ {.label = "Search", .action = DIALOG_OK},
+ {.label = "Cancel", .action = DIALOG_CANCEL},
+ { 0 }
+ };
+ struct option_spec search_opts[] = {
+ {.label = "Search Keys", .state = &opts->search_key},
+ {.label = "Search Values", .state = &opts->search_value},
+ {.label = "Recursive", .state = &opts->search_recursive},
+ {.label = "Case Sensitive", .state = &opts->search_case},
+ { 0 }
+ };
+
+ if (!opts->search_key && !opts->search_value) {
+ opts->search_key = true;
+ }
+
+ search.ctx = ctx;
+ search.opts = opts;
+ dia = dialog_new(ctx, PAIR_BLACK_CYAN, "Search", -1, -1);
+ dialog_set_submit_cb(dia, search_on_submit, &search);
+ section = dialog_section_label_new(dia, "Query");
+ dialog_append_section(dia, section);
+ query = dialog_section_text_field_new(dia, 1, -1);
+ dialog_section_set_name(query, "query");
+ dialog_append_section(dia, query);
+ section = dialog_section_hsep_new(dia, 0);
+ dialog_append_section(dia, section);
+ section = dialog_section_options_new(dia, search_opts, 2, false);
+ dialog_append_section(dia, section);
+ section = dialog_section_hsep_new(dia, 0);
+ dialog_append_section(dia, section);
+ section = dialog_section_buttons_new(dia, spec);
+ dialog_section_set_justify(section, SECTION_JUSTIFY_CENTER);
+ dialog_append_section(dia, section);
+
+ dialog_create(dia);
+ if (opts->query) {
+ dialog_section_text_field_set(query, opts->query);
+ }
+
+ dialog_modal_loop(dia, &err, &action);
+ talloc_free(dia);
+
+ return action;
+}
diff --git a/source3/utils/regedit_dialog.h b/source3/utils/regedit_dialog.h
new file mode 100644
index 0000000..b8bc3bf
--- /dev/null
+++ b/source3/utils/regedit_dialog.h
@@ -0,0 +1,240 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REGEDIT_DIALOG_H_
+#define _REGEDIT_DIALOG_H_
+
+#include <ncurses.h>
+#include <panel.h>
+#include <menu.h>
+
+struct dialog;
+struct dialog_section;
+
+/* dialog submit cb. return true to close dialog, false to keep
+ it open */
+typedef bool (*dialog_submit_cb)(struct dialog *, struct dialog_section *,
+ void *);
+
+struct dialog {
+ char *title;
+ WINDOW *window;
+ WINDOW *pad;
+ PANEL *panel;
+ int x;
+ int y;
+ short color;
+ bool centered;
+ dialog_submit_cb submit;
+ void *submit_arg;
+ struct dialog_section *head_section;
+ struct dialog_section *tail_section;
+ struct dialog_section *current_section;
+};
+
+enum dialog_action {
+ DIALOG_IGNORE,
+ DIALOG_OK,
+ DIALOG_CANCEL
+};
+
+struct dialog_section_ops {
+ /* create section */
+ WERROR (*create)(struct dialog *, struct dialog_section *);
+
+ /* (optional) cleanup the section */
+ void (*destroy)(struct dialog_section *);
+
+ /* (optional) handle character input */
+ void (*on_input)(struct dialog *, struct dialog_section *, int c);
+
+ /* (optional) handle a tab character. return true if section dealt
+ with the tab internally, or false to advance focus to
+ the next dialog section. */
+ bool (*on_tab)(struct dialog *, struct dialog_section *);
+
+ /* (optional) handle a btab character. return true if section dealt
+ with the tab internally, or false to move focus to
+ the previous dialog section. */
+ bool (*on_btab)(struct dialog *, struct dialog_section *);
+
+ /* */
+ bool (*on_up)(struct dialog *, struct dialog_section *);
+
+ /* */
+ bool (*on_down)(struct dialog *, struct dialog_section *);
+
+ /* */
+ bool (*on_left)(struct dialog *, struct dialog_section *);
+
+ /* */
+ bool (*on_right)(struct dialog *, struct dialog_section *);
+
+ /* (optional) handle enter key. return DIALOG_OK to submit
+ dialog, DIALOG_CANCEL to close dialog, or DIALOG_IGNORE to
+ handle the enter internally. */
+ enum dialog_action (*on_enter)(struct dialog *,
+ struct dialog_section *);
+
+ /* (optional) called when this section is about to take focus. forward
+ is set to true when focus has landed here from forward traversal,
+ such as from a tab. return true to accept focus, false to pass to an
+ adjacent section. */
+ bool (*on_focus)(struct dialog *, struct dialog_section *, bool forward);
+
+ /* (optional) called when focus is leaving this section */
+ void (*on_leave_focus)(struct dialog *, struct dialog_section *);
+};
+
+enum section_justify {
+ SECTION_JUSTIFY_LEFT,
+ SECTION_JUSTIFY_CENTER,
+ SECTION_JUSTIFY_RIGHT,
+};
+
+struct dialog_section {
+ char *name;
+ int nlines;
+ int ncols;
+ WINDOW *window;
+ enum section_justify justify;
+ const struct dialog_section_ops *ops;
+ struct dialog_section *next;
+ struct dialog_section *prev;
+};
+
+struct dialog *dialog_new(TALLOC_CTX *ctx, short color,
+ const char *title, int y, int x);
+
+void dialog_section_destroy(struct dialog_section *section);
+void dialog_section_init(struct dialog_section *section,
+ const struct dialog_section_ops *ops,
+ int nlines, int ncols);
+
+void dialog_section_set_name(struct dialog_section *section, const char *name);
+const char *dialog_section_get_name(struct dialog_section *section);
+void dialog_section_set_justify(struct dialog_section *section,
+ enum section_justify justify);
+
+void dialog_append_section(struct dialog *dia,
+ struct dialog_section *section);
+struct dialog_section *dialog_find_section(struct dialog *dia,
+ const char *name);
+
+WERROR dialog_create(struct dialog *dia);
+void dialog_show(struct dialog *dia);
+void dialog_destroy(struct dialog *dia);
+void dialog_set_submit_cb(struct dialog *dia, dialog_submit_cb cb, void *arg);
+bool dialog_handle_input(struct dialog *dia, WERROR *err,
+ enum dialog_action *action);
+void dialog_modal_loop(struct dialog *dia, WERROR *err,
+ enum dialog_action *action);
+
+struct dialog_section *dialog_section_label_new_va(TALLOC_CTX *ctx,
+ const char *msg,
+ va_list ap)
+ PRINTF_ATTRIBUTE(2,0);
+struct dialog_section *dialog_section_label_new(TALLOC_CTX *ctx,
+ const char *msg, ...)
+ PRINTF_ATTRIBUTE(2,3);
+
+struct dialog_section *dialog_section_hsep_new(TALLOC_CTX *ctx, int sep);
+
+
+struct dialog_section *dialog_section_text_field_new(TALLOC_CTX *ctx,
+ int height, int width);
+const char *dialog_section_text_field_get(TALLOC_CTX *ctx,
+ struct dialog_section *section);
+const char **dialog_section_text_field_get_lines(TALLOC_CTX *ctx,
+ struct dialog_section *section);
+bool dialog_section_text_field_get_int(struct dialog_section *section,
+ long long *out);
+bool dialog_section_text_field_get_uint(struct dialog_section *section,
+ unsigned long long *out);
+void dialog_section_text_field_set(struct dialog_section *section,
+ const char *s);
+WERROR dialog_section_text_field_set_lines(TALLOC_CTX *ctx,
+ struct dialog_section *section,
+ const char **array);
+
+struct dialog_section *dialog_section_hexedit_new(TALLOC_CTX *ctx, int height);
+WERROR dialog_section_hexedit_set_buf(struct dialog_section *section,
+ const void *data, size_t size);
+void dialog_section_hexedit_get_buf(struct dialog_section *section,
+ const void **data, size_t *size);
+WERROR dialog_section_hexedit_resize(struct dialog_section *section,
+ size_t size);
+
+struct button_spec {
+ const char *label;
+ enum dialog_action (*on_enter)(struct dialog *,
+ struct dialog_section *);
+ enum dialog_action action;
+
+ /* internal */
+ int col;
+};
+struct dialog_section *dialog_section_buttons_new(TALLOC_CTX *ctx,
+ const struct button_spec *spec);
+
+struct option_spec {
+ const char *label;
+ bool *state;
+
+ /* internal */
+ int col;
+ int row;
+};
+struct dialog_section *dialog_section_options_new(TALLOC_CTX *ctx,
+ const struct option_spec *spec,
+ int maxcol, bool single_select);
+
+enum dialog_type {
+ DIA_ALERT,
+ DIA_CONFIRM
+};
+
+int dialog_notice(TALLOC_CTX *ctx, enum dialog_type type,
+ const char *title, const char *msg, ...)
+ PRINTF_ATTRIBUTE(4,5);
+
+int dialog_input(TALLOC_CTX *ctx, const char **output, const char *title,
+ const char *msg, ...) PRINTF_ATTRIBUTE(4,5);
+int dialog_input_long(TALLOC_CTX *ctx, long *output,
+ const char *title, const char *msg, ...)
+ PRINTF_ATTRIBUTE(4,5);
+int dialog_input_ulong(TALLOC_CTX *ctx, unsigned long *output,
+ const char *title, const char *msg, ...)
+ PRINTF_ATTRIBUTE(4,5);
+
+struct registry_key;
+struct value_item;
+
+int dialog_edit_value(TALLOC_CTX *ctx, struct registry_key *key,
+ uint32_t type, const struct value_item *vitem,
+ bool force_binary, WERROR *err,
+ const char **name);
+
+int dialog_select_type(TALLOC_CTX *ctx, int *type);
+
+struct regedit_search_opts;
+
+int dialog_search_input(TALLOC_CTX *ctx, struct regedit_search_opts *opts);
+
+#endif
diff --git a/source3/utils/regedit_hexedit.c b/source3/utils/regedit_hexedit.c
new file mode 100644
index 0000000..413e563
--- /dev/null
+++ b/source3/utils/regedit_hexedit.c
@@ -0,0 +1,563 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "regedit_hexedit.h"
+
+/*
+ offset hex1 hex2 ascii
+ 00000000 FF FF FF FF FF FF FF FF ........
+*/
+
+#define HEX_COL1 10
+#define HEX_COL1_END 21
+#define HEX_COL2 23
+#define HEX_COL2_END 34
+#define ASCII_COL 36
+#define ASCII_COL_END LINE_WIDTH
+#define BYTES_PER_LINE 8
+
+struct hexedit {
+ size_t offset;
+ size_t len;
+ size_t alloc_size;
+ int cursor_y;
+ int cursor_x;
+ size_t cursor_offset;
+ size_t cursor_line_offset;
+ int nibble;
+ uint8_t *data;
+ WINDOW *win;
+};
+
+static int max_rows(WINDOW *win)
+{
+ int maxy;
+
+ maxy = getmaxy(win);
+
+ return maxy - 1;
+}
+
+struct hexedit *hexedit_new(TALLOC_CTX *ctx, WINDOW *parent, const void *data,
+ size_t sz)
+{
+ WERROR rv;
+ struct hexedit *buf;
+
+ buf = talloc_zero(ctx, struct hexedit);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ buf->win = parent;
+
+ rv = hexedit_set_buf(buf, data, sz);
+ if (!W_ERROR_IS_OK(rv)) {
+ goto fail;
+ }
+
+ return buf;
+
+fail:
+ talloc_free(buf);
+
+ return NULL;
+}
+
+WERROR hexedit_set_buf(struct hexedit *buf, const void *data, size_t sz)
+{
+ TALLOC_FREE(buf->data);
+
+ buf->data = talloc_zero_array(buf, uint8_t, sz);
+ if (buf->data == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (data != NULL) {
+ memcpy(buf->data, data, sz);
+ }
+
+ buf->len = sz;
+ buf->alloc_size = sz;
+ buf->cursor_x = HEX_COL1;
+ buf->cursor_y = 0;
+ buf->cursor_offset = 0;
+ buf->cursor_line_offset = 0;
+ buf->nibble = 0;
+
+ return WERR_OK;
+}
+
+const void *hexedit_get_buf(struct hexedit *buf)
+{
+ return buf->data;
+}
+
+size_t hexedit_get_buf_len(struct hexedit *buf)
+{
+ return buf->len;
+}
+
+static size_t bytes_per_screen(WINDOW *win)
+{
+ return max_rows(win) * BYTES_PER_LINE;
+}
+
+void hexedit_set_cursor(struct hexedit *buf)
+{
+ wmove(buf->win, max_rows(buf->win), 0);
+ wattron(buf->win, A_REVERSE | A_STANDOUT);
+ wclrtoeol(buf->win);
+ if (buf->cursor_offset < buf->len) {
+ wprintw(buf->win, "Len:%lu Off:%lu Val:0x%X", buf->len,
+ buf->cursor_offset, buf->data[buf->cursor_offset]);
+ } else {
+ wprintw(buf->win, "Len:%lu Off:%lu", buf->len,
+ buf->cursor_offset);
+ }
+ wattroff(buf->win, A_REVERSE | A_STANDOUT);
+ wmove(buf->win, buf->cursor_y, buf->cursor_x);
+ wcursyncup(buf->win);
+ wsyncup(buf->win);
+ untouchwin(buf->win);
+}
+
+void hexedit_refresh(struct hexedit *buf)
+{
+ size_t end;
+ size_t lineno;
+ size_t off;
+
+ werase(buf->win);
+ if (buf->len == 0) {
+ mvwprintw(buf->win, 0, 0, "%08X", 0);
+ return;
+ }
+
+ end = buf->offset + bytes_per_screen(buf->win);
+ if (end > buf->len) {
+ end = buf->len;
+ }
+
+ for (off = buf->offset, lineno = 0;
+ off < end;
+ off += BYTES_PER_LINE, ++lineno) {
+ uint8_t *line = buf->data + off;
+ size_t i, endline;
+
+ wmove(buf->win, lineno, 0);
+ wprintw(buf->win, "%08zX ", off);
+
+ endline = BYTES_PER_LINE;
+
+ if (off + BYTES_PER_LINE > buf->len) {
+ endline = buf->len - off;
+ }
+
+ for (i = 0; i < endline; ++i) {
+ wprintw(buf->win, "%02X", line[i]);
+ if (i + 1 < endline) {
+ if (i == 3) {
+ wprintw(buf->win, " ");
+ } else {
+ wprintw(buf->win, " ");
+ }
+ }
+ }
+
+ wmove(buf->win, lineno, ASCII_COL);
+ for (i = 0; i < endline; ++i) {
+ if (isprint(line[i])) {
+ waddch(buf->win, line[i]);
+ } else {
+ waddch(buf->win, '.');
+ }
+ }
+ }
+}
+
+static void calc_cursor_offset(struct hexedit *buf)
+{
+ buf->cursor_offset = buf->offset + buf->cursor_y * BYTES_PER_LINE +
+ buf->cursor_line_offset;
+}
+
+static int offset_to_hex_col(size_t pos)
+{
+ switch (pos) {
+ case 0:
+ return HEX_COL1;
+ case 1:
+ return HEX_COL1 + 3;
+ case 2:
+ return HEX_COL1 + 6;
+ case 3:
+ return HEX_COL1 + 9;
+
+ case 4:
+ return HEX_COL2;
+ case 5:
+ return HEX_COL2 + 3;
+ case 6:
+ return HEX_COL2 + 6;
+ case 7:
+ return HEX_COL2 + 9;
+ }
+
+ return -1;
+}
+
+static bool scroll_up(struct hexedit *buf)
+{
+ if (buf->offset == 0) {
+ return false;
+ }
+
+ buf->offset -= BYTES_PER_LINE;
+
+ return true;
+}
+
+static void cursor_down(struct hexedit *buf)
+{
+ size_t space;
+ bool need_refresh = false;
+
+ space = buf->offset + (buf->cursor_y + 1) * BYTES_PER_LINE;
+ if (space > buf->len) {
+ return;
+ }
+
+ if (buf->cursor_y + 1 == max_rows(buf->win)) {
+ buf->offset += BYTES_PER_LINE;
+ need_refresh = true;
+ } else {
+ buf->cursor_y++;
+ }
+
+ if (buf->cursor_offset + BYTES_PER_LINE > buf->len) {
+ buf->nibble = 0;
+ buf->cursor_offset = buf->len;
+ buf->cursor_line_offset = buf->len - space;
+ if (buf->cursor_x >= ASCII_COL) {
+ buf->cursor_x = ASCII_COL + buf->cursor_line_offset;
+ } else {
+ buf->cursor_x = offset_to_hex_col(buf->cursor_line_offset);
+ }
+ }
+ if (need_refresh) {
+ hexedit_refresh(buf);
+ }
+ calc_cursor_offset(buf);
+}
+
+static void cursor_up(struct hexedit *buf)
+{
+ if (buf->cursor_y == 0) {
+ if (scroll_up(buf)) {
+ hexedit_refresh(buf);
+ }
+ } else {
+ buf->cursor_y--;
+ }
+
+ calc_cursor_offset(buf);
+}
+
+static bool is_over_gap(struct hexedit *buf)
+{
+ int col;
+
+ if (buf->cursor_x < ASCII_COL) {
+ if (buf->cursor_x >= HEX_COL2) {
+ col = buf->cursor_x - HEX_COL2;
+ } else {
+ col = buf->cursor_x - HEX_COL1;
+ }
+
+ switch (col) {
+ case 2:
+ case 5:
+ case 8:
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void cursor_left(struct hexedit *buf)
+{
+ if (buf->cursor_x == HEX_COL1) {
+ return;
+ }
+ if (buf->cursor_x == HEX_COL2) {
+ buf->cursor_x = HEX_COL1_END - 1;
+ buf->cursor_line_offset = 3;
+ buf->nibble = 1;
+ } else if (buf->cursor_x == ASCII_COL) {
+ size_t off = buf->offset + buf->cursor_y * BYTES_PER_LINE;
+ if (off + 7 >= buf->len) {
+ size_t lastpos = buf->len - off;
+ buf->cursor_x = offset_to_hex_col(lastpos) + 1;
+ buf->cursor_line_offset = lastpos;
+ } else {
+ buf->cursor_x = HEX_COL2_END - 1;
+ buf->cursor_line_offset = 7;
+ }
+ buf->nibble = 1;
+ } else {
+ if (buf->cursor_x > ASCII_COL || buf->nibble == 0) {
+ buf->cursor_line_offset--;
+ }
+ buf->cursor_x--;
+ buf->nibble = !buf->nibble;
+ }
+
+ if (is_over_gap(buf)) {
+ buf->cursor_x--;
+ }
+
+ calc_cursor_offset(buf);
+}
+
+static void cursor_right(struct hexedit *buf)
+{
+ int new_x = buf->cursor_x + 1;
+
+ if (new_x == ASCII_COL_END) {
+ return;
+ }
+ if ((buf->cursor_x >= ASCII_COL || buf->nibble == 1) &&
+ buf->cursor_offset == buf->len) {
+ if (buf->cursor_x < ASCII_COL) {
+ new_x = ASCII_COL;
+ buf->cursor_line_offset = 0;
+ buf->nibble = 0;
+ } else {
+ return;
+ }
+ }
+ if (new_x == HEX_COL1_END) {
+ new_x = HEX_COL2;
+ buf->cursor_line_offset = 4;
+ buf->nibble = 0;
+ } else if (new_x == HEX_COL2_END) {
+ new_x = ASCII_COL;
+ buf->cursor_line_offset = 0;
+ buf->nibble = 0;
+ } else {
+ if (buf->cursor_x >= ASCII_COL || buf->nibble == 1) {
+ buf->cursor_line_offset++;
+ }
+ buf->nibble = !buf->nibble;
+ }
+
+ buf->cursor_x = new_x;
+
+ if (is_over_gap(buf)) {
+ buf->cursor_x++;
+ }
+
+ calc_cursor_offset(buf);
+}
+
+static void do_edit(struct hexedit *buf, int c)
+{
+ uint8_t *byte;
+
+ if (buf->cursor_offset == buf->len) {
+ hexedit_resize_buffer(buf, buf->len + 1);
+ }
+
+ byte = buf->data + buf->cursor_offset;
+
+ if (buf->cursor_x >= ASCII_COL) {
+ *byte = (uint8_t)c;
+
+ mvwprintw(buf->win, buf->cursor_y,
+ offset_to_hex_col(buf->cursor_line_offset), "%X", c);
+ if (!isprint(c)) {
+ c = '.';
+ }
+ mvwaddch(buf->win, buf->cursor_y,
+ ASCII_COL + buf->cursor_line_offset, c);
+ if (buf->cursor_x + 1 != ASCII_COL_END) {
+ cursor_right(buf);
+ } else {
+ cursor_down(buf);
+ }
+ } else {
+ if (!isxdigit(c)) {
+ return;
+ }
+ c = toupper(c);
+ waddch(buf->win, c);
+
+ if (isdigit(c)) {
+ c = c - '0';
+ } else {
+ c = c - 'A' + 10;
+ }
+ if (buf->nibble == 0) {
+ *byte = (*byte & 0x0f) | c << 4;
+ } else {
+ *byte = (*byte & 0xf0) | c;
+ }
+
+ c = *byte;
+ if (!isprint(c)) {
+ c = '.';
+ }
+ mvwaddch(buf->win, buf->cursor_y,
+ ASCII_COL + buf->cursor_line_offset, c);
+
+ if (buf->cursor_x + 1 != HEX_COL2_END) {
+ cursor_right(buf);
+ } else {
+ cursor_down(buf);
+ }
+ }
+
+ hexedit_refresh(buf);
+}
+
+static void erase_at(struct hexedit *buf, size_t pos)
+{
+ if (pos >= buf->len) {
+ return;
+ }
+
+ if (pos < buf->len - 1) {
+ /* squeeze the character out of the buffer */
+ uint8_t *p = buf->data + pos;
+ uint8_t *end = buf->data + buf->len;
+ memmove(p, p + 1, end - p - 1);
+ }
+
+ buf->len--;
+ hexedit_refresh(buf);
+}
+
+static void do_backspace(struct hexedit *buf)
+{
+ size_t off;
+ bool do_erase = true;
+
+ if (buf->cursor_offset == 0) {
+ return;
+ }
+
+ off = buf->cursor_offset;
+ if (buf->cursor_x == ASCII_COL) {
+ cursor_up(buf);
+ buf->cursor_line_offset = 7;
+ buf->cursor_x = ASCII_COL_END - 1;
+ calc_cursor_offset(buf);
+ } else if (buf->cursor_x == HEX_COL1) {
+ cursor_up(buf);
+ buf->cursor_line_offset = 7;
+ buf->cursor_x = HEX_COL2_END - 1;
+ buf->nibble = 1;
+ calc_cursor_offset(buf);
+ } else {
+ if (buf->cursor_x < ASCII_COL && buf->nibble) {
+ do_erase = false;
+ }
+ cursor_left(buf);
+ }
+ if (do_erase) {
+ erase_at(buf, off - 1);
+ }
+}
+
+static void do_delete(struct hexedit *buf)
+{
+ erase_at(buf, buf->cursor_offset);
+}
+
+void hexedit_driver(struct hexedit *buf, int c)
+{
+ switch (c) {
+ case HE_CURSOR_UP:
+ cursor_up(buf);
+ break;
+ case HE_CURSOR_DOWN:
+ cursor_down(buf);
+ break;
+ case HE_CURSOR_LEFT:
+ cursor_left(buf);
+ break;
+ case HE_CURSOR_RIGHT:
+ cursor_right(buf);
+ break;
+ case HE_CURSOR_PGUP:
+ break;
+ case HE_CURSOR_PGDN:
+ break;
+ case HE_BACKSPACE:
+ do_backspace(buf);
+ break;
+ case HE_DELETE:
+ do_delete(buf);
+ break;
+ default:
+ do_edit(buf, c & 0xff);
+ break;
+ }
+
+ hexedit_set_cursor(buf);
+}
+
+WERROR hexedit_resize_buffer(struct hexedit *buf, size_t newsz)
+{
+ /* reset the cursor if it'll be out of bounds
+ after the resize */
+ if (buf->cursor_offset > newsz) {
+ buf->cursor_y = 0;
+ buf->cursor_x = HEX_COL1;
+ buf->offset = 0;
+ buf->cursor_offset = 0;
+ buf->cursor_line_offset = 0;
+ buf->nibble = 0;
+ }
+
+ if (newsz > buf->len) {
+ if (newsz > buf->alloc_size) {
+ uint8_t *d;
+ buf->alloc_size *= 2;
+ if (newsz > buf->alloc_size) {
+ buf->alloc_size = newsz;
+ }
+ d = talloc_realloc(buf, buf->data, uint8_t,
+ buf->alloc_size);
+ if (d == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ buf->data = d;
+ }
+ memset(buf->data + buf->len, '\0', newsz - buf->len);
+ buf->len = newsz;
+ } else {
+ buf->len = newsz;
+ }
+
+ return WERR_OK;
+}
diff --git a/source3/utils/regedit_hexedit.h b/source3/utils/regedit_hexedit.h
new file mode 100644
index 0000000..dfbe27a
--- /dev/null
+++ b/source3/utils/regedit_hexedit.h
@@ -0,0 +1,49 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _HEXEDIT_H_
+#define _HEXEDIT_H_
+
+#include <ncurses.h>
+
+enum {
+ HE_CURSOR_UP = 0x1000,
+ HE_CURSOR_DOWN = 0x1100,
+ HE_CURSOR_LEFT = 0x1200,
+ HE_CURSOR_RIGHT = 0x1300,
+ HE_CURSOR_PGUP = 0x1400,
+ HE_CURSOR_PGDN = 0x1500,
+ HE_BACKSPACE = 0x1600,
+ HE_DELETE = 0x1700,
+};
+
+#define LINE_WIDTH 44
+struct hexedit;
+
+struct hexedit *hexedit_new(TALLOC_CTX *ctx, WINDOW *parent, const void *data,
+ size_t sz);
+WERROR hexedit_set_buf(struct hexedit *buf, const void *data, size_t sz);
+const void *hexedit_get_buf(struct hexedit *buf);
+size_t hexedit_get_buf_len(struct hexedit *buf);
+void hexedit_set_cursor(struct hexedit *buf);
+void hexedit_refresh(struct hexedit *buf);
+void hexedit_driver(struct hexedit *buf, int c);
+WERROR hexedit_resize_buffer(struct hexedit *buf, size_t newsz);
+
+#endif
diff --git a/source3/utils/regedit_list.c b/source3/utils/regedit_list.c
new file mode 100644
index 0000000..b5405f2
--- /dev/null
+++ b/source3/utils/regedit_list.c
@@ -0,0 +1,591 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "regedit_list.h"
+#include "regedit.h"
+
+struct multilist {
+ WINDOW *window;
+ WINDOW *pad;
+
+ unsigned window_height;
+ unsigned window_width;
+ unsigned start_row;
+ unsigned cursor_row;
+
+ unsigned ncols;
+ struct multilist_column *columns;
+
+ const void *data;
+ unsigned nrows;
+ const void *current_row;
+ const struct multilist_accessors *cb;
+};
+
+/* data getters */
+static const void *data_get_first_row(struct multilist *list)
+{
+ SMB_ASSERT(list->cb->get_first_row);
+ return list->cb->get_first_row(list->data);
+}
+
+static const void *data_get_next_row(struct multilist *list, const void *row)
+{
+ SMB_ASSERT(list->cb->get_next_row);
+ return list->cb->get_next_row(list->data, row);
+}
+
+static const void *data_get_prev_row(struct multilist *list, const void *row)
+{
+ const void *tmp, *next;
+
+ if (list->cb->get_prev_row) {
+ return list->cb->get_prev_row(list->data, row);
+ }
+
+ tmp = data_get_first_row(list);
+ if (tmp == row) {
+ return NULL;
+ }
+
+ for (; tmp && (next = data_get_next_row(list, tmp)) != row;
+ tmp = next) {
+ }
+
+ SMB_ASSERT(tmp != NULL);
+
+ return tmp;
+}
+
+static unsigned data_get_row_count(struct multilist *list)
+{
+ unsigned i;
+ const void *row;
+
+ if (list->cb->get_row_count)
+ return list->cb->get_row_count(list->data);
+
+ for (i = 0, row = data_get_first_row(list);
+ row != NULL;
+ ++i, row = data_get_next_row(list, row)) {
+ }
+
+ return i;
+}
+
+static const void *data_get_row_n(struct multilist *list, size_t n)
+{
+ unsigned i;
+ const void *row;
+
+ if (list->cb->get_row_n)
+ return list->cb->get_row_n(list->data, n);
+
+ for (i = 0, row = data_get_first_row(list);
+ i < n && row != NULL;
+ ++i, row = data_get_next_row(list, row)) {
+ }
+
+ return row;
+}
+
+static const char *data_get_column_header(struct multilist *list, unsigned col)
+{
+ SMB_ASSERT(list->cb->get_column_header);
+ return list->cb->get_column_header(list->data, col);
+}
+
+static const char *data_get_item_label(struct multilist *list, const void *row,
+ unsigned col)
+{
+ SMB_ASSERT(list->cb->get_item_label);
+ return list->cb->get_item_label(row, col);
+}
+
+static const char *data_get_item_prefix(struct multilist *list, const void *row,
+ unsigned col)
+{
+ if (list->cb->get_item_prefix)
+ return list->cb->get_item_prefix(row, col);
+ return "";
+}
+
+static int multilist_free(struct multilist *list)
+{
+ if (list->pad) {
+ delwin(list->pad);
+ }
+
+ return 0;
+}
+
+struct multilist *multilist_new(TALLOC_CTX *ctx, WINDOW *window,
+ const struct multilist_accessors *cb,
+ unsigned ncol)
+{
+ struct multilist *list;
+
+ SMB_ASSERT(ncol > 0);
+
+ list = talloc_zero(ctx, struct multilist);
+ if (list == NULL) {
+ return NULL;
+ }
+ talloc_set_destructor(list, multilist_free);
+
+ list->cb = cb;
+ list->ncols = ncol;
+ list->columns = talloc_zero_array(list, struct multilist_column, ncol);
+ if (list->columns == NULL) {
+ talloc_free(list);
+ return NULL;
+ }
+ multilist_set_window(list, window);
+
+ return list;
+}
+
+struct multilist_column *multilist_column_config(struct multilist *list,
+ unsigned col)
+{
+ SMB_ASSERT(col < list->ncols);
+ return &list->columns[col];
+}
+
+static void put_padding(WINDOW *win, size_t col_width, size_t item_len)
+{
+ size_t amt;
+
+ SMB_ASSERT(item_len <= col_width);
+
+ amt = col_width - item_len;
+ while (amt--) {
+ waddch(win, ' ');
+ }
+}
+
+static void put_item(struct multilist *list, WINDOW *win, unsigned col,
+ const char *item, int attr)
+{
+ bool append_sep = true;
+ unsigned i;
+ size_t len;
+ struct multilist_column *col_info;
+ bool trim = false;
+
+ SMB_ASSERT(col < list->ncols);
+ SMB_ASSERT(item != NULL);
+
+ if (col == list->ncols - 1) {
+ append_sep = false;
+ }
+ col_info = &list->columns[col];
+
+ len = strlen(item);
+ if (len > col_info->width) {
+ len = col_info->width;
+ trim = true;
+ }
+
+ if (col_info->align_right) {
+ put_padding(win, col_info->width, len);
+ }
+ for (i = 0; i < len; ++i) {
+ if (i == len - 1 && trim) {
+ waddch(win, '~' | attr);
+ } else {
+ waddch(win, item[i] | attr);
+ }
+ }
+ if (!col_info->align_right) {
+ put_padding(win, col_info->width, len);
+ }
+
+ if (append_sep) {
+ waddch(win, ' ');
+ waddch(win, '|');
+ waddch(win, ' ');
+ }
+}
+
+static void put_header(struct multilist *list)
+{
+ unsigned col;
+ const char *header;
+
+ if (!list->cb->get_column_header) {
+ return;
+ }
+
+ wmove(list->window, 0, 0);
+ for (col = 0; col < list->ncols; ++col) {
+ header = data_get_column_header(list, col);
+ SMB_ASSERT(header != NULL);
+ put_item(list, list->window, col, header,
+ A_BOLD | COLOR_PAIR(PAIR_YELLOW_BLUE));
+ }
+}
+
+static WERROR put_data(struct multilist *list)
+{
+ const void *row;
+ int ypos;
+ unsigned col;
+ const char *prefix, *item;
+ char *tmp;
+
+ for (ypos = 0, row = data_get_first_row(list);
+ row != NULL;
+ row = data_get_next_row(list, row), ++ypos) {
+ wmove(list->pad, ypos, 0);
+ for (col = 0; col < list->ncols; ++col) {
+ prefix = data_get_item_prefix(list, row, col);
+ SMB_ASSERT(prefix != NULL);
+ item = data_get_item_label(list, row, col);
+ SMB_ASSERT(item != NULL);
+ tmp = talloc_asprintf(list, "%s%s", prefix, item);
+ if (tmp == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ put_item(list, list->pad, col, tmp, 0);
+ talloc_free(tmp);
+ }
+ }
+
+ return WERR_OK;
+}
+
+#define MIN_WIDTH 3
+static struct multilist_column *find_widest_column(struct multilist *list)
+{
+ unsigned col;
+ struct multilist_column *colp;
+
+ SMB_ASSERT(list->ncols > 0);
+ colp = &list->columns[0];
+
+ for (col = 1; col < list->ncols; ++col) {
+ if (list->columns[col].width > colp->width) {
+ colp = &list->columns[col];
+ }
+ }
+
+ if (colp->width < MIN_WIDTH) {
+ return NULL;
+ }
+
+ return colp;
+}
+
+static WERROR calc_column_widths(struct multilist *list)
+{
+ const void *row;
+ unsigned col;
+ size_t len;
+ const char *item;
+ size_t width, total_width, overflow;
+ struct multilist_column *colp;
+
+ /* calculate the maximum widths for each column */
+ for (col = 0; col < list->ncols; ++col) {
+ len = 0;
+ if (list->cb->get_column_header) {
+ item = data_get_column_header(list, col);
+ len = strlen(item);
+ }
+ list->columns[col].width = len;
+ }
+
+ for (row = data_get_first_row(list);
+ row != NULL;
+ row = data_get_next_row(list, row)) {
+ for (col = 0; col < list->ncols; ++col) {
+ item = data_get_item_prefix(list, row, col);
+ SMB_ASSERT(item != NULL);
+ len = strlen(item);
+
+ item = data_get_item_label(list, row, col);
+ SMB_ASSERT(item != NULL);
+ len += strlen(item);
+ if (len > list->columns[col].width) {
+ list->columns[col].width = len;
+ }
+ }
+ }
+
+ /* calculate row width */
+ for (width = 0, col = 0; col < list->ncols; ++col) {
+ width += list->columns[col].width;
+ }
+ /* width including column spacing and separations */
+ total_width = width + (list->ncols - 1) * 3;
+ /* if everything fits, we're done */
+ if (total_width <= list->window_width) {
+ return WERR_OK;
+ }
+
+ overflow = total_width - list->window_width;
+
+ /* attempt to trim as much as possible to fit all the columns to
+ the window */
+ while (overflow && (colp = find_widest_column(list))) {
+ colp->width--;
+ overflow--;
+ }
+
+ return WERR_OK;
+}
+
+static void highlight_current_row(struct multilist *list)
+{
+ mvwchgat(list->pad, list->cursor_row, 0, -1, A_REVERSE, 0, NULL);
+}
+
+static void unhighlight_current_row(struct multilist *list)
+{
+ mvwchgat(list->pad, list->cursor_row, 0, -1, A_NORMAL, 0, NULL);
+}
+
+const void *multilist_get_data(struct multilist *list)
+{
+ return list->data;
+}
+
+WERROR multilist_set_data(struct multilist *list, const void *data)
+{
+ WERROR rv;
+
+ SMB_ASSERT(list->window != NULL);
+ list->data = data;
+
+ calc_column_widths(list);
+
+ if (list->pad) {
+ delwin(list->pad);
+ }
+ /* construct a pad that is exactly the width of the window, and
+ as tall as required to fit all data rows. */
+ list->nrows = data_get_row_count(list);
+ list->pad = newpad(MAX(list->nrows, 1), list->window_width);
+ if (list->pad == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ /* add the column headers to the window and render all rows to
+ the pad. */
+ werase(list->window);
+ put_header(list);
+ rv = put_data(list);
+ if (!W_ERROR_IS_OK(rv)) {
+ return rv;
+ }
+
+ /* initialize the cursor */
+ list->start_row = 0;
+ list->cursor_row = 0;
+ list->current_row = data_get_first_row(list);
+ highlight_current_row(list);
+
+ return WERR_OK;
+}
+
+static int get_window_height(struct multilist *list)
+{
+ int height;
+
+ height = list->window_height;
+ if (list->cb->get_column_header) {
+ height--;
+ }
+
+ return height;
+}
+
+static void fix_start_row(struct multilist *list)
+{
+ int height;
+
+ /* adjust start_row so that the cursor appears on the screen */
+
+ height = get_window_height(list);
+ if (list->cursor_row < list->start_row) {
+ list->start_row = list->cursor_row;
+ } else if (list->cursor_row >= list->start_row + height) {
+ list->start_row = list->cursor_row - height + 1;
+ }
+ if (list->nrows > height && list->nrows - list->start_row < height) {
+ list->start_row = list->nrows - height;
+ }
+}
+
+WERROR multilist_set_window(struct multilist *list, WINDOW *window)
+{
+ int maxy, maxx;
+ bool rerender = false;
+
+ getmaxyx(window, maxy, maxx);
+
+ /* rerender pad if window width is different. */
+ if (list->data && maxx != list->window_width) {
+ rerender = true;
+ }
+
+ list->window = window;
+ list->window_width = maxx;
+ list->window_height = maxy;
+ list->start_row = 0;
+ if (rerender) {
+ const void *row = multilist_get_current_row(list);
+ WERROR rv = multilist_set_data(list, list->data);
+ if (W_ERROR_IS_OK(rv) && row) {
+ multilist_set_current_row(list, row);
+ }
+ return rv;
+ } else {
+ put_header(list);
+ fix_start_row(list);
+ }
+
+ return WERR_OK;
+}
+
+void multilist_refresh(struct multilist *list)
+{
+ int window_start_row, height;
+
+ if (list->nrows == 0) {
+ return;
+ }
+
+ /* copy from pad, starting at start_row, to the window, accounting
+ for the column header (if present). */
+ height = MIN(list->window_height, list->nrows);
+ window_start_row = 0;
+ if (list->cb->get_column_header) {
+ window_start_row = 1;
+ if (height < list->window_height) {
+ height++;
+ }
+ }
+ copywin(list->pad, list->window, list->start_row, 0,
+ window_start_row, 0, height - 1, list->window_width - 1,
+ false);
+}
+
+void multilist_driver(struct multilist *list, int c)
+{
+ unsigned page;
+ const void *tmp = NULL;
+
+ if (list->nrows == 0) {
+ return;
+ }
+
+ switch (c) {
+ case ML_CURSOR_UP:
+ if (list->cursor_row == 0) {
+ return;
+ }
+ unhighlight_current_row(list);
+ list->cursor_row--;
+ tmp = data_get_prev_row(list, list->current_row);
+ break;
+ case ML_CURSOR_DOWN:
+ if (list->cursor_row == list->nrows - 1) {
+ return;
+ }
+ unhighlight_current_row(list);
+ list->cursor_row++;
+ tmp = data_get_next_row(list, list->current_row);
+ break;
+ case ML_CURSOR_PGUP:
+ if (list->cursor_row == 0) {
+ return;
+ }
+ unhighlight_current_row(list);
+ page = get_window_height(list);
+ if (page > list->cursor_row) {
+ list->cursor_row = 0;
+ } else {
+ list->cursor_row -= page;
+ list->start_row -= page;
+ }
+ tmp = data_get_row_n(list, list->cursor_row);
+ break;
+ case ML_CURSOR_PGDN:
+ if (list->cursor_row == list->nrows - 1) {
+ return;
+ }
+ unhighlight_current_row(list);
+ page = get_window_height(list);
+ if (page > list->nrows - list->cursor_row - 1) {
+ list->cursor_row = list->nrows - 1;
+ } else {
+ list->cursor_row += page;
+ list->start_row += page;
+ }
+ tmp = data_get_row_n(list, list->cursor_row);
+ break;
+ case ML_CURSOR_HOME:
+ if (list->cursor_row == 0) {
+ return;
+ }
+ unhighlight_current_row(list);
+ list->cursor_row = 0;
+ tmp = data_get_row_n(list, list->cursor_row);
+ break;
+ case ML_CURSOR_END:
+ if (list->cursor_row == list->nrows - 1) {
+ return;
+ }
+ unhighlight_current_row(list);
+ list->cursor_row = list->nrows - 1;
+ tmp = data_get_row_n(list, list->cursor_row);
+ break;
+ }
+
+ SMB_ASSERT(tmp);
+ list->current_row = tmp;
+ highlight_current_row(list);
+ fix_start_row(list);
+}
+
+const void *multilist_get_current_row(struct multilist *list)
+{
+ return list->current_row;
+}
+
+void multilist_set_current_row(struct multilist *list, const void *row)
+{
+ unsigned i;
+ const void *tmp;
+
+ for (i = 0, tmp = data_get_first_row(list);
+ tmp != NULL;
+ ++i, tmp = data_get_next_row(list, tmp)) {
+ if (tmp == row) {
+ unhighlight_current_row(list);
+ list->cursor_row = i;
+ list->current_row = row;
+ highlight_current_row(list);
+ fix_start_row(list);
+ return;
+ }
+ }
+}
diff --git a/source3/utils/regedit_list.h b/source3/utils/regedit_list.h
new file mode 100644
index 0000000..abd6ffd
--- /dev/null
+++ b/source3/utils/regedit_list.h
@@ -0,0 +1,82 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REGEDIT_LIST_H_
+#define _REGEDIT_LIST_H_
+
+#include "includes.h"
+#include <ncurses.h>
+
+struct multilist_accessors {
+ /* (optional) return the column header for col */
+ const char *(*get_column_header)(const void *data, unsigned col);
+
+ /* return a pointer to the first row of data */
+ const void *(*get_first_row)(const void *data);
+
+ /* (optional) return a count of all data rows */
+ size_t (*get_row_count)(const void *data);
+
+ /* return the next row or NULL if there aren't any more */
+ const void *(*get_next_row)(const void *data, const void *row);
+
+ /* (optional) return the previous row or NULL if row is on top. */
+ const void *(*get_prev_row)(const void *data, const void *row);
+
+ /* (optional) return row n of data */
+ const void *(*get_row_n)(const void *data, size_t n);
+
+ /* return the label for row and col */
+ const char *(*get_item_label)(const void *row, unsigned col);
+
+ /* (optional) return a prefix string to be prepended to an item's
+ label. */
+ const char *(*get_item_prefix)(const void *row, unsigned col);
+};
+
+struct multilist_column {
+ size_t width;
+ unsigned int align_right:1;
+};
+
+struct multilist;
+
+struct multilist *multilist_new(TALLOC_CTX *ctx, WINDOW *window,
+ const struct multilist_accessors *cb,
+ unsigned ncol);
+struct multilist_column *multilist_column_config(struct multilist *list,
+ unsigned col);
+WERROR multilist_set_window(struct multilist *list, WINDOW *window);
+const void *multilist_get_data(struct multilist *list);
+WERROR multilist_set_data(struct multilist *list, const void *data);
+void multilist_refresh(struct multilist *list);
+
+enum {
+ ML_CURSOR_UP,
+ ML_CURSOR_DOWN,
+ ML_CURSOR_PGUP,
+ ML_CURSOR_PGDN,
+ ML_CURSOR_HOME,
+ ML_CURSOR_END
+};
+void multilist_driver(struct multilist *list, int c);
+const void *multilist_get_current_row(struct multilist *list);
+void multilist_set_current_row(struct multilist *list, const void *row);
+
+#endif
diff --git a/source3/utils/regedit_samba3.c b/source3/utils/regedit_samba3.c
new file mode 100644
index 0000000..a1f8b39
--- /dev/null
+++ b/source3/utils/regedit_samba3.c
@@ -0,0 +1,244 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* s3 registry backend, adapted from rpc backend */
+
+#include "includes.h"
+#include "lib/registry/registry.h"
+#include "regedit.h"
+
+struct samba3_key {
+ struct registry_key key;
+ struct samba3_registry_key s3key;
+};
+
+struct samba3_registry_context {
+ struct registry_context context;
+};
+
+static struct registry_operations reg_backend_s3;
+
+static struct {
+ uint32_t hkey;
+ const char *name;
+} known_hives[] = {
+ { HKEY_LOCAL_MACHINE, "HKLM" },
+ { HKEY_CURRENT_USER, "HKCU" },
+ { HKEY_CLASSES_ROOT, "HKCR" },
+ { HKEY_PERFORMANCE_DATA, "HKPD" },
+ { HKEY_USERS, "HKU" },
+ { HKEY_DYN_DATA, "HKDD" },
+ { HKEY_CURRENT_CONFIG, "HKCC" },
+ { 0, NULL }
+};
+
+static WERROR samba3_get_predefined_key(struct registry_context *ctx,
+ uint32_t hkey_type,
+ struct registry_key **k)
+{
+ int n;
+ const char *name;
+ struct samba3_key *mykeydata;
+
+ *k = NULL;
+ name = NULL;
+
+ for(n = 0; known_hives[n].hkey; n++) {
+ if(known_hives[n].hkey == hkey_type) {
+ name = known_hives[n].name;
+ break;
+ }
+ }
+
+ if (name == NULL) {
+ DEBUG(1, ("No such hive %d\n", hkey_type));
+ return WERR_NO_MORE_ITEMS;
+ }
+
+ mykeydata = talloc_zero(ctx, struct samba3_key);
+ W_ERROR_HAVE_NO_MEMORY(mykeydata);
+ mykeydata->key.context = ctx;
+ *k = (struct registry_key *)mykeydata;
+
+ return reg_openhive_wrap(ctx, name, &mykeydata->s3key);
+}
+
+static WERROR samba3_open_key(TALLOC_CTX *mem_ctx, struct registry_key *h,
+ const char *name, struct registry_key **key)
+{
+ struct samba3_key *parentkeydata, *mykeydata;
+
+ parentkeydata = talloc_get_type(h, struct samba3_key);
+
+ mykeydata = talloc_zero(mem_ctx, struct samba3_key);
+ W_ERROR_HAVE_NO_MEMORY(mykeydata);
+ mykeydata->key.context = h->context;
+ *key = (struct registry_key *)mykeydata;
+
+ return reg_openkey_wrap(mem_ctx, &parentkeydata->s3key,
+ name, &mykeydata->s3key);
+}
+
+static WERROR samba3_get_value_by_index(TALLOC_CTX *mem_ctx,
+ const struct registry_key *parent,
+ uint32_t n,
+ const char **value_name,
+ uint32_t *type,
+ DATA_BLOB *data)
+{
+ struct samba3_key *mykeydata;
+
+ mykeydata = talloc_get_type(parent, struct samba3_key);
+
+ return reg_enumvalue_wrap(mem_ctx, &mykeydata->s3key, n,
+ discard_const(value_name), type, data);
+}
+
+static WERROR samba3_get_value_by_name(TALLOC_CTX *mem_ctx,
+ const struct registry_key *parent,
+ const char *value_name,
+ uint32_t *type,
+ DATA_BLOB *data)
+{
+ struct samba3_key *mykeydata;
+
+ mykeydata = talloc_get_type(parent, struct samba3_key);
+
+ return reg_queryvalue_wrap(mem_ctx, &mykeydata->s3key,
+ value_name, type, data);
+}
+
+static WERROR samba3_get_subkey_by_index(TALLOC_CTX *mem_ctx,
+ const struct registry_key *parent,
+ uint32_t n,
+ const char **name,
+ const char **keyclass,
+ NTTIME *last_changed_time)
+{
+ struct samba3_key *mykeydata;
+
+ mykeydata = talloc_get_type(parent, struct samba3_key);
+
+ *keyclass = NULL;
+
+ return reg_enumkey_wrap(mem_ctx, &mykeydata->s3key, n,
+ discard_const(name), last_changed_time);
+}
+
+static WERROR samba3_add_key(TALLOC_CTX *mem_ctx,
+ struct registry_key *parent, const char *path,
+ const char *key_class,
+ struct security_descriptor *sec,
+ struct registry_key **key)
+{
+ struct samba3_key *parentkd;
+ struct samba3_key *newkd;
+
+ parentkd = talloc_get_type(parent, struct samba3_key);
+ newkd = talloc_zero(mem_ctx, struct samba3_key);
+
+ W_ERROR_HAVE_NO_MEMORY(newkd);
+ newkd->key.context = parent->context;
+ *key = (struct registry_key *)newkd;
+
+ return reg_createkey_wrap(mem_ctx, &parentkd->s3key, path,
+ &newkd->s3key);
+}
+
+static WERROR samba3_del_key(TALLOC_CTX *mem_ctx, struct registry_key *parent,
+ const char *name)
+{
+ struct samba3_key *mykeydata;
+
+ mykeydata = talloc_get_type(parent, struct samba3_key);
+
+ return reg_deletekey_wrap(&mykeydata->s3key, name);
+}
+
+static WERROR samba3_del_value(TALLOC_CTX *mem_ctx, struct registry_key *key,
+ const char *name)
+{
+ struct samba3_key *mykeydata = talloc_get_type(key, struct samba3_key);
+
+ return reg_deletevalue_wrap(&mykeydata->s3key, name);
+}
+
+static WERROR samba3_set_value(struct registry_key *key, const char *name,
+ uint32_t type, const DATA_BLOB data)
+{
+ struct samba3_key *mykeydata = talloc_get_type(key, struct samba3_key);
+
+ return reg_setvalue_wrap(&mykeydata->s3key, name, type, data);
+}
+
+static WERROR samba3_get_info(TALLOC_CTX *mem_ctx,
+ const struct registry_key *key,
+ const char **classname,
+ uint32_t *num_subkeys,
+ uint32_t *num_values,
+ NTTIME *last_changed_time,
+ uint32_t *max_subkeylen,
+ uint32_t *max_valnamelen,
+ uint32_t *max_valbufsize)
+{
+ struct samba3_key *mykeydata = talloc_get_type(key, struct samba3_key);
+ uint32_t max_subkeysize, secdescsize;
+
+ return reg_queryinfokey_wrap(&mykeydata->s3key, num_subkeys,
+ max_subkeylen, &max_subkeysize,
+ num_values, max_valnamelen,
+ max_valbufsize, &secdescsize,
+ last_changed_time);
+}
+
+static struct registry_operations reg_backend_s3 = {
+ .name = "samba3",
+ .open_key = samba3_open_key,
+ .get_predefined_key = samba3_get_predefined_key,
+ .enum_key = samba3_get_subkey_by_index,
+ .enum_value = samba3_get_value_by_index,
+ .get_value = samba3_get_value_by_name,
+ .set_value = samba3_set_value,
+ .delete_value = samba3_del_value,
+ .create_key = samba3_add_key,
+ .delete_key = samba3_del_key,
+ .get_key_info = samba3_get_info,
+};
+
+WERROR reg_open_samba3(TALLOC_CTX *mem_ctx, struct registry_context **ctx)
+{
+ WERROR rv;
+ struct samba3_registry_context *rctx;
+
+ /* initialize s3 registry */
+ rv = reg_init_wrap();
+ if (!W_ERROR_IS_OK(rv)) {
+ return rv;
+ }
+
+ rctx = talloc_zero(mem_ctx, struct samba3_registry_context);
+ if (rctx == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ *ctx = (struct registry_context *)rctx;
+ (*ctx)->ops = &reg_backend_s3;
+
+ return WERR_OK;
+}
diff --git a/source3/utils/regedit_treeview.c b/source3/utils/regedit_treeview.c
new file mode 100644
index 0000000..a3df94d
--- /dev/null
+++ b/source3/utils/regedit_treeview.c
@@ -0,0 +1,705 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "regedit_treeview.h"
+#include "regedit_list.h"
+#include "lib/registry/registry.h"
+
+#define HEADING_X 3
+
+static int tree_node_free(struct tree_node *node)
+{
+ DEBUG(9, ("tree_node_free('%s', %p)\n", node->name, node));
+ return 0;
+}
+
+struct tree_node *tree_node_new(TALLOC_CTX *ctx, struct tree_node *parent,
+ const char *name, struct registry_key *key)
+{
+ struct tree_node *node;
+
+ node = talloc_zero(ctx, struct tree_node);
+ if (!node) {
+ return NULL;
+ }
+ talloc_set_destructor(node, tree_node_free);
+ DEBUG(9, ("tree_node_new('%s', %p)\n", name, node));
+
+ node->name = talloc_strdup(node, name);
+ if (!node->name) {
+ talloc_free(node);
+ return NULL;
+ }
+
+ if (key) {
+ node->key = talloc_steal(node, key);
+ }
+
+ if (parent) {
+ /* Check if this node is the first descendant of parent. */
+ if (!parent->child_head) {
+ parent->child_head = node;
+ }
+ node->parent = parent;
+ }
+
+ return node;
+}
+
+/* prepare a root node with all available hives as children */
+struct tree_node *tree_node_new_root(TALLOC_CTX *ctx,
+ struct registry_context *regctx)
+{
+ const char *hives[] = {
+ "HKEY_CLASSES_ROOT",
+ "HKEY_CURRENT_USER",
+ "HKEY_LOCAL_MACHINE",
+ "HKEY_PERFORMANCE_DATA",
+ "HKEY_USERS",
+ "HKEY_CURRENT_CONFIG",
+ "HKEY_DYN_DATA",
+ "HKEY_PERFORMANCE_TEXT",
+ "HKEY_PERFORMANCE_NLSTEXT",
+ NULL
+ };
+ struct tree_node *root, *prev, *node;
+ struct registry_key *key;
+ WERROR rv;
+ size_t i;
+
+ root = tree_node_new(ctx, NULL, "ROOT", NULL);
+ if (root == NULL) {
+ return NULL;
+ }
+ prev = NULL;
+
+ for (i = 0; hives[i] != NULL; ++i) {
+ rv = reg_get_predefined_key_by_name(regctx, hives[i], &key);
+ if (!W_ERROR_IS_OK(rv)) {
+ continue;
+ }
+
+ node = tree_node_new(root, root, hives[i], key);
+ if (node == NULL) {
+ return NULL;
+ }
+ if (prev) {
+ tree_node_append(prev, node);
+ }
+ prev = node;
+ }
+
+ return root;
+}
+
+void tree_node_append(struct tree_node *left, struct tree_node *right)
+{
+ if (left->next) {
+ right->next = left->next;
+ left->next->previous = right;
+ }
+ left->next = right;
+ right->previous = left;
+}
+
+void tree_node_append_last(struct tree_node *list, struct tree_node *node)
+{
+ tree_node_append(tree_node_last(list), node);
+}
+
+struct tree_node *tree_node_pop(struct tree_node **plist)
+{
+ struct tree_node *node;
+
+ node = *plist;
+
+ if (node == NULL)
+ return NULL;
+
+ *plist = node->previous;
+ if (*plist == NULL) {
+ *plist = node->next;
+ }
+ if (node->previous) {
+ node->previous->next = node->next;
+ }
+ if (node->next) {
+ node->next->previous = node->previous;
+ }
+ if (node->parent && node->parent->child_head == node) {
+ node->parent->child_head = node->next;
+ }
+ node->next = NULL;
+ node->previous = NULL;
+
+ return node;
+}
+
+struct tree_node *tree_node_first(struct tree_node *list)
+{
+ /* Grab the first node in this list from the parent if available. */
+ if (list->parent) {
+ return list->parent->child_head;
+ }
+
+ while (list && list->previous) {
+ list = list->previous;
+ }
+
+ return list;
+}
+
+struct tree_node *tree_node_last(struct tree_node *list)
+{
+ while (list && list->next) {
+ list = list->next;
+ }
+
+ return list;
+}
+
+static uint32_t get_num_subkeys(struct tree_node *node)
+{
+ const char *classname;
+ uint32_t num_subkeys;
+ uint32_t num_values;
+ NTTIME last_change_time;
+ uint32_t max_subkeynamelen;
+ uint32_t max_valnamelen;
+ uint32_t max_valbufsize;
+ WERROR rv;
+
+ rv = reg_key_get_info(node, node->key, &classname, &num_subkeys,
+ &num_values, &last_change_time,
+ &max_subkeynamelen, &max_valnamelen,
+ &max_valbufsize);
+
+ if (W_ERROR_IS_OK(rv)) {
+ return num_subkeys;
+ }
+
+ return 0;
+}
+
+WERROR tree_node_reopen_key(struct registry_context *ctx,
+ struct tree_node *node)
+{
+ SMB_ASSERT(node->parent != NULL);
+ SMB_ASSERT(node->name != NULL);
+ TALLOC_FREE(node->key);
+
+ if (tree_node_is_top_level(node)) {
+ WERROR rv;
+ struct registry_key *key;
+ rv = reg_get_predefined_key_by_name(ctx, node->name, &key);
+ if (W_ERROR_IS_OK(rv)) {
+ node->key = talloc_steal(node, key);
+ }
+ return rv;
+ }
+
+ return reg_open_key(node, node->parent->key, node->name, &node->key);
+}
+
+bool tree_node_has_children(struct tree_node *node)
+{
+ if (node->child_head) {
+ return true;
+ }
+
+ return get_num_subkeys(node) > 0;
+}
+
+static int node_cmp(struct tree_node **a, struct tree_node **b)
+{
+ return strcmp((*a)->name, (*b)->name);
+}
+
+void tree_node_insert_sorted(struct tree_node *list, struct tree_node *node)
+{
+ list = tree_node_first(list);
+
+ if (node_cmp(&list, &node) >= 0) {
+ tree_node_append(node, list);
+ if (list->parent) {
+ list->parent->child_head = node;
+ }
+ return;
+ }
+
+ while (list->next && node_cmp(&list->next, &node) < 0) {
+ list = list->next;
+ }
+
+ tree_node_append(list, node);
+}
+
+WERROR tree_node_load_children(struct tree_node *node)
+{
+ struct registry_key *key;
+ const char *reg_key_name, *klass;
+ NTTIME modified;
+ uint32_t i, nsubkeys, count;
+ WERROR rv;
+ struct tree_node *prev, **array;
+
+ /* does this node already have it's children loaded? */
+ if (node->child_head)
+ return WERR_OK;
+
+ nsubkeys = get_num_subkeys(node);
+ if (nsubkeys == 0)
+ return WERR_OK;
+
+ array = talloc_zero_array(node, struct tree_node *, nsubkeys);
+ if (array == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (count = 0, i = 0; i < nsubkeys; ++i) {
+ rv = reg_key_get_subkey_by_index(node, node->key, i,
+ &reg_key_name, &klass,
+ &modified);
+ if (!W_ERROR_IS_OK(rv)) {
+ goto finish;
+ }
+
+ rv = reg_open_key(node, node->key, reg_key_name, &key);
+ if (!W_ERROR_IS_OK(rv)) {
+ continue;
+ }
+
+ array[count] = tree_node_new(array, node, reg_key_name, key);
+ if (array[count] == NULL) {
+ rv = WERR_NOT_ENOUGH_MEMORY;
+ goto finish;
+ }
+ ++count;
+ }
+
+ if (count) {
+ TYPESAFE_QSORT(array, count, node_cmp);
+
+ for (i = 1, prev = array[0]; i < count; ++i) {
+ talloc_steal(node, array[i]);
+ tree_node_append(prev, array[i]);
+ prev = array[i];
+ }
+ node->child_head = talloc_steal(node, array[0]);
+
+ rv = WERR_OK;
+ }
+
+finish:
+ talloc_free(array);
+
+ return rv;
+}
+
+static WERROR next_depth_first(struct tree_node **node)
+{
+ WERROR rv = WERR_OK;
+
+ SMB_ASSERT(node != NULL && *node != NULL);
+
+ if (tree_node_has_children(*node)) {
+ /* 1. If the node has children, go to the first one. */
+ rv = tree_node_load_children(*node);
+ if (W_ERROR_IS_OK(rv)) {
+ SMB_ASSERT((*node)->child_head != NULL);
+ *node = (*node)->child_head;
+ }
+ } else if ((*node)->next) {
+ /* 2. If there's a node directly after this one, go there */
+ *node = (*node)->next;
+ } else {
+ /* 3. Otherwise, go up the hierarchy to find the next one */
+ do {
+ *node = (*node)->parent;
+ if (*node && (*node)->next) {
+ *node = (*node)->next;
+ break;
+ }
+ } while (*node);
+ }
+
+ return rv;
+}
+
+static WERROR prev_depth_first(struct tree_node **node)
+{
+ WERROR rv = WERR_OK;
+
+ SMB_ASSERT(node != NULL && *node != NULL);
+
+ if ((*node)->previous) {
+ *node = (*node)->previous;
+ while (tree_node_has_children(*node)) {
+ rv = tree_node_load_children(*node);
+ if (W_ERROR_IS_OK(rv)) {
+ SMB_ASSERT((*node)->child_head != NULL);
+ *node = tree_node_last((*node)->child_head);
+ }
+ }
+ } else if (!tree_node_is_top_level(*node)) {
+ *node = (*node)->parent;
+ } else {
+ *node = NULL;
+ }
+
+ return rv;
+}
+
+bool tree_node_next(struct tree_node **node, bool depth, WERROR *err)
+{
+ *err = WERR_OK;
+
+ if (*node == NULL) {
+ return false;
+ }
+
+ if (depth) {
+ *err = next_depth_first(node);
+ } else {
+ *node = (*node)->next;
+ }
+
+ return *node != NULL && W_ERROR_IS_OK(*err);
+}
+
+bool tree_node_prev(struct tree_node **node, bool depth, WERROR *err)
+{
+ *err = WERR_OK;
+
+ if (*node == NULL) {
+ return false;
+ }
+
+ if (depth) {
+ *err = prev_depth_first(node);
+ } else {
+ *node = (*node)->previous;
+ }
+
+ return *node != NULL && W_ERROR_IS_OK(*err);
+}
+
+void tree_view_clear(struct tree_view *view)
+{
+ multilist_set_data(view->list, NULL);
+}
+
+WERROR tree_view_set_root(struct tree_view *view, struct tree_node *root)
+{
+ multilist_set_data(view->list, NULL);
+ talloc_free(view->root);
+ view->root = root;
+ return tree_view_update(view, root->child_head);
+}
+
+WERROR tree_view_set_path(struct tree_view *view, const char **path)
+{
+ struct tree_node *top, *node;
+ WERROR rv;
+
+ top = view->root->child_head;
+ while (*path) {
+ for (node = top; node != NULL; node = node->next) {
+ if (strcmp(*path, node->name) == 0) {
+ if (path[1] && tree_node_has_children(node)) {
+ rv = tree_node_load_children(node);
+ if (!W_ERROR_IS_OK(rv)) {
+ return rv;
+ }
+ SMB_ASSERT(node->child_head);
+ top = node->child_head;
+ break;
+ } else {
+ tree_view_update(view, top);
+ tree_view_set_current_node(view, node);
+ return WERR_OK;
+ }
+ }
+ }
+ ++path;
+ }
+
+ return WERR_OK;
+}
+
+WERROR tree_view_update(struct tree_view *view, struct tree_node *list)
+{
+ WERROR rv;
+
+ rv = multilist_set_data(view->list, list);
+ if (W_ERROR_IS_OK(rv)) {
+ multilist_refresh(view->list);
+ }
+
+ return rv;
+}
+
+/* is this node in the current level? */
+bool tree_view_is_node_visible(struct tree_view *view, struct tree_node *node)
+{
+ const struct tree_node *first;
+
+ first = multilist_get_data(view->list);
+
+ return first && first->parent == node->parent;
+}
+
+void tree_view_set_current_node(struct tree_view *view, struct tree_node *node)
+{
+ multilist_set_current_row(view->list, node);
+}
+
+struct tree_node *tree_view_get_current_node(struct tree_view *view)
+{
+ const void *row = multilist_get_current_row(view->list);
+ return talloc_get_type_abort(row, struct tree_node);
+}
+
+void tree_view_driver(struct tree_view *view, int c)
+{
+ multilist_driver(view->list, c);
+}
+
+void tree_view_set_selected(struct tree_view *view, bool reverse)
+{
+ attr_t attr = A_NORMAL;
+
+ if (reverse) {
+ attr = A_REVERSE;
+ }
+ mvwchgat(view->window, 0, HEADING_X, 3, attr, 0, NULL);
+}
+
+void tree_view_show(struct tree_view *view)
+{
+ multilist_refresh(view->list);
+ touchwin(view->window);
+ wnoutrefresh(view->window);
+ wnoutrefresh(view->sub);
+}
+
+static int tree_view_free(struct tree_view *view)
+{
+ if (view->panel) {
+ del_panel(view->panel);
+ }
+ if (view->sub) {
+ delwin(view->sub);
+ }
+ if (view->window) {
+ delwin(view->window);
+ }
+
+ return 0;
+}
+
+static const char *tv_get_column_header(const void *data, unsigned col)
+{
+ SMB_ASSERT(col == 0);
+ return "Name";
+}
+
+static const void *tv_get_first_row(const void *data)
+{
+ if (data == NULL) {
+ return NULL;
+ }
+
+ return talloc_get_type_abort(data, struct tree_node);
+}
+
+static const void *tv_get_next_row(const void *data, const void *row)
+{
+ const struct tree_node *node;
+ SMB_ASSERT(row != NULL);
+ node = talloc_get_type_abort(row, struct tree_node);
+ return node->next;
+}
+
+static const void *tv_get_prev_row(const void *data, const void *row)
+{
+ const struct tree_node *node;
+ SMB_ASSERT(row != NULL);
+ node = talloc_get_type_abort(row, struct tree_node);
+ return node->previous;
+}
+
+static const char *tv_get_item_prefix(const void *row, unsigned col)
+{
+ struct tree_node *node;
+
+ SMB_ASSERT(col == 0);
+ SMB_ASSERT(row != NULL);
+ node = talloc_get_type_abort(row, struct tree_node);
+ if (tree_node_has_children(node)) {
+ return "+";
+ }
+ return " ";
+}
+
+static const char *tv_get_item_label(const void *row, unsigned col)
+{
+ const struct tree_node *node;
+ SMB_ASSERT(col == 0);
+ SMB_ASSERT(row != NULL);
+ node = talloc_get_type_abort(row, struct tree_node);
+ return node->name;
+}
+
+static struct multilist_accessors tv_accessors = {
+ .get_column_header = tv_get_column_header,
+ .get_first_row = tv_get_first_row,
+ .get_next_row = tv_get_next_row,
+ .get_prev_row = tv_get_prev_row,
+ .get_item_prefix = tv_get_item_prefix,
+ .get_item_label = tv_get_item_label
+};
+
+struct tree_view *tree_view_new(TALLOC_CTX *ctx, struct tree_node *root,
+ int nlines, int ncols, int begin_y,
+ int begin_x)
+{
+ struct tree_view *view;
+
+ view = talloc_zero(ctx, struct tree_view);
+ if (view == NULL) {
+ return NULL;
+ }
+
+ talloc_set_destructor(view, tree_view_free);
+
+ view->window = newwin(nlines, ncols, begin_y, begin_x);
+ if (view->window == NULL) {
+ goto fail;
+ }
+ view->sub = subwin(view->window, nlines - 2, ncols - 2,
+ begin_y + 1, begin_x + 1);
+ if (view->sub == NULL) {
+ goto fail;
+ }
+ box(view->window, 0, 0);
+ mvwprintw(view->window, 0, HEADING_X, "Key");
+
+ view->panel = new_panel(view->window);
+ if (view->panel == NULL) {
+ goto fail;
+ }
+ view->root = root;
+
+ view->list = multilist_new(view, view->sub, &tv_accessors, 1);
+ if (view->list == NULL) {
+ goto fail;
+ }
+ tree_view_update(view, root->child_head);
+
+ return view;
+
+fail:
+ talloc_free(view);
+
+ return NULL;
+}
+
+void tree_view_resize(struct tree_view *view, int nlines, int ncols,
+ int begin_y, int begin_x)
+{
+ WINDOW *nwin, *nsub;
+
+ nwin = newwin(nlines, ncols, begin_y, begin_x);
+ if (nwin == NULL) {
+ return;
+ }
+ nsub = subwin(nwin, nlines - 2, ncols - 2, begin_y + 1, begin_x + 1);
+ if (nsub == NULL) {
+ delwin(nwin);
+ return;
+ }
+ replace_panel(view->panel, nwin);
+ delwin(view->sub);
+ delwin(view->window);
+ view->window = nwin;
+ view->sub = nsub;
+ box(view->window, 0, 0);
+ mvwprintw(view->window, 0, HEADING_X, "Key");
+ multilist_set_window(view->list, view->sub);
+ tree_view_show(view);
+}
+
+const char **tree_node_get_path(TALLOC_CTX *ctx, struct tree_node *node)
+{
+ const char **array;
+ size_t nitems, idx;
+ struct tree_node *p;
+
+ for (nitems = 0, p = node; !tree_node_is_root(p); p = p->parent) {
+ ++nitems;
+ }
+
+ array = talloc_zero_array(ctx, const char *, nitems + 1);
+ if (array == NULL) {
+ return NULL;
+ }
+
+ for (idx = nitems - 1, p = node;
+ !tree_node_is_root(p);
+ p = p->parent, --idx) {
+ array[idx] = talloc_strdup(array, p->name);
+ if (array[idx] == NULL) {
+ talloc_free(discard_const(array));
+ return NULL;
+ }
+ }
+
+ return array;
+}
+
+/* print the path of node to label */
+size_t tree_node_print_path(WINDOW *label, struct tree_node *node)
+{
+ size_t len = 1;
+ const char **path;
+ TALLOC_CTX *frame;
+
+ if (node == NULL)
+ return 0;
+
+ werase(label);
+ wprintw(label, "/");
+
+ if (tree_node_is_top_level(node))
+ return 0;
+
+ frame = talloc_stackframe();
+ path = tree_node_get_path(frame, node->parent);
+
+ while (*path) {
+ len += strlen(*path) + 1;
+ wprintw(label, "%s/", *path);
+ ++path;
+ }
+
+ talloc_free(frame);
+
+ return len;
+}
diff --git a/source3/utils/regedit_treeview.h b/source3/utils/regedit_treeview.h
new file mode 100644
index 0000000..4b892bb
--- /dev/null
+++ b/source3/utils/regedit_treeview.h
@@ -0,0 +1,89 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REGEDIT_TREEVIEW_H_
+#define _REGEDIT_TREEVIEW_H_
+
+#include "includes.h"
+#include <ncurses.h>
+#include <panel.h>
+
+struct registry_key;
+
+struct tree_node {
+
+ char *name;
+ struct registry_key *key;
+
+ struct tree_node *parent;
+ struct tree_node *child_head;
+ struct tree_node *previous;
+ struct tree_node *next;
+};
+
+struct multilist;
+
+struct tree_view {
+
+ struct tree_node *root;
+ WINDOW *window;
+ WINDOW *sub;
+ PANEL *panel;
+ struct multilist *list;
+};
+
+struct registry_context;
+
+struct tree_node *tree_node_new(TALLOC_CTX *ctx, struct tree_node *parent,
+ const char *name, struct registry_key *key);
+struct tree_node *tree_node_new_root(TALLOC_CTX *ctx,
+ struct registry_context *regctx);
+#define tree_node_is_root(node) ((node)->key == NULL)
+#define tree_node_is_top_level(node) tree_node_is_root((node)->parent)
+void tree_node_append(struct tree_node *left, struct tree_node *right);
+struct tree_node *tree_node_pop(struct tree_node **plist);
+struct tree_node *tree_node_first(struct tree_node *list);
+struct tree_node *tree_node_last(struct tree_node *list);
+bool tree_node_next(struct tree_node **node, bool depth, WERROR *err);
+bool tree_node_prev(struct tree_node **node, bool depth, WERROR *err);
+void tree_node_append_last(struct tree_node *list, struct tree_node *node);
+size_t tree_node_print_path(WINDOW *label, struct tree_node *node);
+const char **tree_node_get_path(TALLOC_CTX *ctx, struct tree_node *node);
+struct tree_view *tree_view_new(TALLOC_CTX *ctx, struct tree_node *root,
+ int nlines, int ncols,
+ int begin_y, int begin_x);
+void tree_view_set_selected(struct tree_view *view, bool select);
+void tree_view_resize(struct tree_view *view, int nlines, int ncols,
+ int begin_y, int begin_x);
+void tree_view_show(struct tree_view *view);
+void tree_view_clear(struct tree_view *view);
+WERROR tree_view_set_root(struct tree_view *view, struct tree_node *root);
+WERROR tree_view_set_path(struct tree_view *view, const char **path);
+WERROR tree_view_update(struct tree_view *view, struct tree_node *list);
+WERROR tree_node_reopen_key(struct registry_context *ctx,
+ struct tree_node *node);
+bool tree_node_has_children(struct tree_node *node);
+WERROR tree_node_load_children(struct tree_node *node);
+void tree_node_insert_sorted(struct tree_node *list, struct tree_node *node);
+bool tree_view_is_node_visible(struct tree_view *view, struct tree_node *node);
+void tree_view_set_current_node(struct tree_view *view, struct tree_node *node);
+struct tree_node *tree_view_get_current_node(struct tree_view *view);
+void tree_view_driver(struct tree_view *view, int c);
+
+#endif
diff --git a/source3/utils/regedit_valuelist.c b/source3/utils/regedit_valuelist.c
new file mode 100644
index 0000000..78ea3fa
--- /dev/null
+++ b/source3/utils/regedit_valuelist.c
@@ -0,0 +1,496 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "regedit.h"
+#include "regedit_valuelist.h"
+#include "regedit_list.h"
+#include "lib/registry/registry.h"
+
+#define HEADING_X 3
+
+static int value_list_free(struct value_list *vl)
+{
+ if (vl->panel) {
+ del_panel(vl->panel);
+ }
+ if (vl->sub) {
+ delwin(vl->sub);
+ }
+ if (vl->window) {
+ delwin(vl->window);
+ }
+
+ return 0;
+}
+
+static const char *vl_get_column_header(const void *data, unsigned col)
+{
+ switch (col) {
+ case 0:
+ return "Name";
+ case 1:
+ return "Type";
+ case 2:
+ return "Data";
+ }
+
+ return "???";
+}
+
+static const void *vl_get_first_row(const void *data)
+{
+ const struct value_list *vl;
+
+ if (data) {
+ vl = talloc_get_type_abort(data, struct value_list);
+ if (vl->nvalues) {
+ return &vl->values[0];
+ }
+ }
+
+ return NULL;
+}
+
+static const void *vl_get_next_row(const void *data, const void *row)
+{
+ const struct value_list *vl;
+ const struct value_item *value = row;
+
+ SMB_ASSERT(data != NULL);
+ SMB_ASSERT(value != NULL);
+ vl = talloc_get_type_abort(data, struct value_list);
+ if (value == &vl->values[vl->nvalues - 1]) {
+ return NULL;
+ }
+
+ return value + 1;
+}
+
+static const void *vl_get_prev_row(const void *data, const void *row)
+{
+ const struct value_list *vl;
+ const struct value_item *value = row;
+
+ SMB_ASSERT(data != NULL);
+ SMB_ASSERT(value != NULL);
+ vl = talloc_get_type_abort(data, struct value_list);
+ if (value == &vl->values[0]) {
+ return NULL;
+ }
+
+ return value - 1;
+}
+
+static const char *vl_get_item_label(const void *row, unsigned col)
+{
+ const struct value_item *value = row;
+
+ SMB_ASSERT(value != NULL);
+ SMB_ASSERT(value->value_name != NULL);
+ switch (col) {
+ case 0:
+ return value->value_name;
+ case 1:
+ return str_regtype(value->type);
+ case 2:
+ if (value->value) {
+ return value->value;
+ }
+ return "";
+ }
+
+ return "???";
+}
+
+static struct multilist_accessors vl_accessors = {
+ .get_column_header = vl_get_column_header,
+ .get_first_row = vl_get_first_row,
+ .get_next_row = vl_get_next_row,
+ .get_prev_row = vl_get_prev_row,
+ .get_item_label = vl_get_item_label
+};
+
+struct value_list *value_list_new(TALLOC_CTX *ctx, int nlines, int ncols,
+ int begin_y, int begin_x)
+{
+ struct value_list *vl;
+
+ vl = talloc_zero(ctx, struct value_list);
+ if (vl == NULL) {
+ return NULL;
+ }
+
+ talloc_set_destructor(vl, value_list_free);
+
+ vl->window = newwin(nlines, ncols, begin_y, begin_x);
+ if (vl->window == NULL) {
+ goto fail;
+ }
+ vl->sub = subwin(vl->window, nlines - 2, ncols - 2,
+ begin_y + 1, begin_x + 1);
+ if (vl->sub == NULL) {
+ goto fail;
+ }
+ box(vl->window, 0, 0);
+ mvwprintw(vl->window, 0, HEADING_X, "Value");
+
+ vl->panel = new_panel(vl->window);
+ if (vl->panel == NULL) {
+ goto fail;
+ }
+
+ vl->list = multilist_new(vl, vl->sub, &vl_accessors, 3);
+ if (vl->list == NULL) {
+ goto fail;
+ }
+
+ return vl;
+
+fail:
+ talloc_free(vl);
+
+ return NULL;
+}
+
+void value_list_set_selected(struct value_list *vl, bool reverse)
+{
+ attr_t attr = A_NORMAL;
+
+ if (reverse) {
+ attr = A_REVERSE;
+ }
+ mvwchgat(vl->window, 0, HEADING_X, 5, attr, 0, NULL);
+}
+
+void value_list_resize(struct value_list *vl, int nlines, int ncols,
+ int begin_y, int begin_x)
+{
+ WINDOW *nwin, *nsub;
+
+ nwin = newwin(nlines, ncols, begin_y, begin_x);
+ if (nwin == NULL) {
+ return;
+ }
+ nsub = subwin(nwin, nlines - 2, ncols - 2, begin_y + 1, begin_x + 1);
+ if (nsub == NULL) {
+ delwin(nwin);
+ return;
+ }
+ replace_panel(vl->panel, nwin);
+ delwin(vl->sub);
+ delwin(vl->window);
+ vl->window = nwin;
+ vl->sub = nsub;
+ box(vl->window, 0, 0);
+ mvwprintw(vl->window, 0, HEADING_X, "Value");
+ multilist_set_window(vl->list, vl->sub);
+ value_list_show(vl);
+}
+
+static uint32_t get_num_values(TALLOC_CTX *ctx, const struct registry_key *key)
+{
+ const char *classname;
+ uint32_t num_subkeys;
+ uint32_t num_values;
+ NTTIME last_change_time;
+ uint32_t max_subkeynamelen;
+ uint32_t max_valnamelen;
+ uint32_t max_valbufsize;
+ WERROR rv;
+
+ rv = reg_key_get_info(ctx, key, &classname, &num_subkeys,
+ &num_values, &last_change_time,
+ &max_subkeynamelen, &max_valnamelen,
+ &max_valbufsize);
+
+ if (W_ERROR_IS_OK(rv)) {
+ return num_values;
+ }
+
+ return 0;
+}
+
+void value_list_show(struct value_list *vl)
+{
+ multilist_refresh(vl->list);
+ touchwin(vl->window);
+ wnoutrefresh(vl->window);
+ wnoutrefresh(vl->sub);
+}
+
+static bool string_is_printable(const char *s)
+{
+ const char *p;
+
+ for (p = s; *p; ++p) {
+ if (!isprint(*p)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static WERROR append_data_summary(TALLOC_CTX *ctx, struct value_item *vitem)
+{
+ char *tmp = NULL;
+
+/* This is adapted from print_registry_value() in net_registry_util.c */
+
+ switch(vitem->type) {
+ case REG_DWORD: {
+ uint32_t v = 0;
+ if (vitem->data.length >= 4) {
+ v = IVAL(vitem->data.data, 0);
+ }
+ tmp = talloc_asprintf(ctx, "0x%08x (%u)", v, v);
+ break;
+ }
+ case REG_SZ:
+ case REG_EXPAND_SZ: {
+ const char *s;
+
+ if (!pull_reg_sz(ctx, &vitem->data, &s)) {
+ break;
+ }
+ vitem->unprintable = !string_is_printable(s);
+ if (vitem->unprintable) {
+ tmp = talloc_asprintf(ctx, "(unprintable)");
+ } else {
+ tmp = talloc_asprintf(ctx, "%s", s);
+ }
+ break;
+ }
+ case REG_MULTI_SZ: {
+ size_t i, len;
+ const char **a;
+ const char *val;
+
+ if (!pull_reg_multi_sz(ctx, &vitem->data, &a)) {
+ break;
+ }
+ for (len = 0; a[len] != NULL; ++len) {
+ }
+ tmp = talloc_asprintf(ctx, "(%u) ", (unsigned)len);
+ if (tmp == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ for (i = 0; i < len; ++i) {
+ if (!string_is_printable(a[i])) {
+ val = "(unprintable)";
+ vitem->unprintable = true;
+ } else {
+ val = a[i];
+ }
+ if (i == len - 1) {
+ tmp = talloc_asprintf_append(tmp,
+ "[%u]=\"%s\"",
+ (unsigned)i, val);
+ } else {
+ tmp = talloc_asprintf_append(tmp,
+ "[%u]=\"%s\", ",
+ (unsigned)i, val);
+ }
+ if (tmp == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+ break;
+ }
+ case REG_BINARY:
+ tmp = talloc_asprintf(ctx, "(%d bytes)",
+ (int)vitem->data.length);
+ break;
+ default:
+ tmp = talloc_asprintf(ctx, "(unknown)");
+ break;
+ }
+
+ if (tmp == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ vitem->value = tmp;
+
+ return WERR_OK;
+}
+
+static int vitem_cmp(struct value_item *a, struct value_item *b)
+{
+ return strcmp(a->value_name, b->value_name);
+}
+
+/* load only the value names into memory to enable searching */
+WERROR value_list_load_quick(struct value_list *vl, struct registry_key *key)
+{
+ uint32_t nvalues;
+ uint32_t idx;
+ struct value_item *vitem, *new_items;
+ WERROR rv;
+
+ multilist_set_data(vl->list, NULL);
+ vl->nvalues = 0;
+ TALLOC_FREE(vl->values);
+
+ nvalues = get_num_values(vl, key);
+ if (nvalues == 0) {
+ return WERR_OK;
+ }
+
+ new_items = talloc_zero_array(vl, struct value_item, nvalues);
+ if (new_items == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ for (idx = 0; idx < nvalues; ++idx) {
+ vitem = &new_items[idx];
+ rv = reg_key_get_value_by_index(new_items, key, idx,
+ &vitem->value_name,
+ &vitem->type,
+ &vitem->data);
+ if (!W_ERROR_IS_OK(rv)) {
+ talloc_free(new_items);
+ return rv;
+ }
+ }
+
+ TYPESAFE_QSORT(new_items, nvalues, vitem_cmp);
+ vl->nvalues = nvalues;
+ vl->values = new_items;
+
+ return rv;
+}
+
+/* sync up the UI with the list */
+WERROR value_list_sync(struct value_list *vl)
+{
+ uint32_t idx;
+ WERROR rv;
+
+ for (idx = 0; idx < vl->nvalues; ++idx) {
+ rv = append_data_summary(vl->values, &vl->values[idx]);
+ if (!W_ERROR_IS_OK(rv)) {
+ return rv;
+ }
+ }
+
+ rv = multilist_set_data(vl->list, vl);
+ if (W_ERROR_IS_OK(rv)) {
+ multilist_refresh(vl->list);
+ }
+
+ return rv;
+}
+
+WERROR value_list_load(struct value_list *vl, struct registry_key *key)
+{
+ WERROR rv;
+
+ rv = value_list_load_quick(vl, key);
+ if (!W_ERROR_IS_OK(rv)) {
+ return rv;
+ }
+
+ rv = value_list_sync(vl);
+
+ return rv;
+}
+
+struct value_item *value_list_find_next_item(struct value_list *vl,
+ struct value_item *vitem,
+ const char *s,
+ regedit_search_match_fn_t match)
+{
+ struct value_item *end;
+
+ if (!vl->values) {
+ return NULL;
+ }
+
+ if (vitem) {
+ ++vitem;
+ } else {
+ vitem = &vl->values[0];
+ }
+
+ for (end = &vl->values[vl->nvalues]; vitem < end; ++vitem) {
+ if (match(vitem->value_name, s)) {
+ return vitem;
+ }
+ }
+
+ return NULL;
+}
+
+struct value_item *value_list_find_prev_item(struct value_list *vl,
+ struct value_item *vitem,
+ const char *s,
+ regedit_search_match_fn_t match)
+{
+ struct value_item *end;
+
+ if (!vl->values) {
+ return NULL;
+ }
+
+ if (vitem) {
+ --vitem;
+ } else {
+ vitem = &vl->values[vl->nvalues - 1];
+ }
+
+ for (end = &vl->values[-1]; vitem > end; --vitem) {
+ if (match(vitem->value_name, s)) {
+ return vitem;
+ }
+ }
+
+ return NULL;
+}
+
+struct value_item *value_list_get_current_item(struct value_list *vl)
+{
+ return discard_const_p(struct value_item,
+ multilist_get_current_row(vl->list));
+}
+
+void value_list_set_current_item_by_name(struct value_list *vl,
+ const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < vl->nvalues; ++i) {
+ if (strequal(vl->values[i].value_name, name)) {
+ multilist_set_current_row(vl->list, &vl->values[i]);
+ return;
+ }
+ }
+}
+
+void value_list_set_current_item(struct value_list *vl,
+ const struct value_item *item)
+{
+ multilist_set_current_row(vl->list, item);
+}
+
+void value_list_driver(struct value_list *vl, int c)
+{
+ multilist_driver(vl->list, c);
+}
diff --git a/source3/utils/regedit_valuelist.h b/source3/utils/regedit_valuelist.h
new file mode 100644
index 0000000..1178389
--- /dev/null
+++ b/source3/utils/regedit_valuelist.h
@@ -0,0 +1,72 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _REGEDIT_VALUELIST_H_
+#define _REGEDIT_VALUELIST_H_
+
+#include <ncurses.h>
+#include <panel.h>
+
+struct registry_key;
+
+struct value_item {
+ uint32_t type;
+ DATA_BLOB data;
+ const char *value_name;
+ char *value;
+ bool unprintable;
+};
+
+struct multilist;
+
+struct value_list {
+ WINDOW *window;
+ WINDOW *sub;
+ PANEL *panel;
+ size_t nvalues;
+ struct value_item *values;
+ struct multilist *list;
+};
+struct value_list *value_list_new(TALLOC_CTX *ctx, int nlines, int ncols,
+ int begin_y, int begin_x);
+void value_list_show(struct value_list *vl);
+void value_list_set_selected(struct value_list *vl, bool select);
+const char **value_list_load_names(TALLOC_CTX *ctx, struct registry_key *key);
+WERROR value_list_load(struct value_list *vl, struct registry_key *key);
+void value_list_resize(struct value_list *vl, int nlines, int ncols,
+ int begin_y, int begin_x);
+struct value_item *value_list_get_current_item(struct value_list *vl);
+void value_list_set_current_item(struct value_list *vl,
+ const struct value_item *item);
+void value_list_set_current_item_by_name(struct value_list *vl,
+ const char *name);
+void value_list_driver(struct value_list *vl, int c);
+
+WERROR value_list_load_quick(struct value_list *vl, struct registry_key *key);
+WERROR value_list_sync(struct value_list *vl);
+struct value_item *value_list_find_next_item(struct value_list *vl,
+ struct value_item *vitem,
+ const char *s,
+ regedit_search_match_fn_t match);
+struct value_item *value_list_find_prev_item(struct value_list *vl,
+ struct value_item *vitem,
+ const char *s,
+ regedit_search_match_fn_t match);
+
+#endif
diff --git a/source3/utils/regedit_wrap.c b/source3/utils/regedit_wrap.c
new file mode 100644
index 0000000..93297eb
--- /dev/null
+++ b/source3/utils/regedit_wrap.c
@@ -0,0 +1,143 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Registry Editor
+ * Copyright (C) Christopher Davis 2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Wrap s3 registry API calls to avoid conflicts with 'struct registry_key',
+ etc, in s4 libregistry. */
+
+#include "includes.h"
+#include "registry.h"
+#include "registry/reg_api.h"
+#include "registry/reg_init_basic.h"
+#include "registry/reg_util_token.h"
+
+#include "regedit.h"
+
+WERROR reg_openhive_wrap(TALLOC_CTX *ctx, const char *hive,
+ struct samba3_registry_key *pkey)
+{
+ struct security_token *token;
+ WERROR rv;
+
+ SMB_ASSERT(pkey->key == NULL);
+
+ rv = ntstatus_to_werror(registry_create_admin_token(ctx, &token));
+ if (!W_ERROR_IS_OK(rv)) {
+ return rv;
+ }
+
+ return reg_openhive(ctx, hive, REG_KEY_READ | REG_KEY_WRITE, token,
+ &pkey->key);
+}
+
+WERROR reg_openkey_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *parent,
+ const char *name, struct samba3_registry_key *pkey)
+{
+ SMB_ASSERT(pkey->key == NULL);
+ return reg_openkey(ctx, parent->key, name,
+ REG_KEY_READ | REG_KEY_WRITE, &pkey->key);
+}
+
+WERROR reg_enumvalue_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *key,
+ uint32_t idx, char **name, uint32_t *type,
+ DATA_BLOB *data)
+{
+ struct registry_value *val = NULL;
+ WERROR rv;
+
+ rv = reg_enumvalue(ctx, key->key, idx, name, &val);
+
+ if (val && W_ERROR_IS_OK(rv)) {
+ *type = (uint32_t)val->type;
+ *data = val->data;
+ }
+
+ return rv;
+}
+
+WERROR reg_queryvalue_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *key,
+ const char *name, uint32_t *type, DATA_BLOB *data)
+{
+ struct registry_value *val = NULL;
+ WERROR rv;
+
+ rv = reg_queryvalue(ctx, key->key, name, &val);
+
+ if (val && W_ERROR_IS_OK(rv)) {
+ *type = (uint32_t)val->type;
+ *data = val->data;
+ }
+
+ return rv;
+}
+
+WERROR reg_enumkey_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *key,
+ uint32_t idx, char **name, NTTIME *last_write_time)
+{
+ return reg_enumkey(ctx, key->key, idx, name, last_write_time);
+}
+
+WERROR reg_createkey_wrap(TALLOC_CTX *ctx, struct samba3_registry_key *parent,
+ const char *subkeypath,
+ struct samba3_registry_key *pkey)
+{
+ enum winreg_CreateAction act;
+
+ SMB_ASSERT(pkey->key == NULL);
+ return reg_createkey(ctx, parent->key, subkeypath,
+ REG_KEY_READ | REG_KEY_WRITE, &pkey->key, &act);
+}
+
+WERROR reg_deletekey_wrap(struct samba3_registry_key *parent, const char *path)
+{
+ return reg_deletekey(parent->key, path);
+}
+
+WERROR reg_deletevalue_wrap(struct samba3_registry_key *key, const char *name)
+{
+ return reg_deletevalue(key->key, name);
+}
+
+WERROR reg_queryinfokey_wrap(struct samba3_registry_key *key,
+ uint32_t *num_subkeys, uint32_t *max_subkeylen,
+ uint32_t *max_subkeysize, uint32_t *num_values,
+ uint32_t *max_valnamelen,
+ uint32_t *max_valbufsize, uint32_t *secdescsize,
+ NTTIME *last_changed_time)
+{
+ return reg_queryinfokey(key->key, num_subkeys, max_subkeylen,
+ max_subkeysize, num_values, max_valnamelen,
+ max_valbufsize, secdescsize,
+ last_changed_time);
+}
+
+WERROR reg_setvalue_wrap(struct samba3_registry_key *key, const char *name,
+ uint32_t type, const DATA_BLOB data)
+{
+ struct registry_value val;
+
+ val.type = type;
+ val.data = data;
+
+ return reg_setvalue(key->key, name, &val);
+}
+
+WERROR reg_init_wrap(void)
+{
+ return registry_init_basic();
+}
diff --git a/source3/utils/sharesec.c b/source3/utils/sharesec.c
new file mode 100644
index 0000000..a6481e2
--- /dev/null
+++ b/source3/utils/sharesec.c
@@ -0,0 +1,613 @@
+/*
+ * Unix SMB/Netbios implementation.
+ * Utility for managing share permissions
+ *
+ * Copyright (C) Tim Potter 2000
+ * Copyright (C) Jeremy Allison 2000
+ * Copyright (C) Jelmer Vernooij 2003
+ * Copyright (C) Gerald (Jerry) Carter 2005.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+struct cli_state;
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+#include "util_sd.h"
+#include "cmdline_contexts.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/param/param.h"
+
+static TALLOC_CTX *ctx;
+
+enum acl_mode { SMB_ACL_DELETE,
+ SMB_ACL_MODIFY,
+ SMB_ACL_ADD,
+ SMB_ACL_SET,
+ SMB_SD_DELETE,
+ SMB_SD_SETSDDL,
+ SMB_SD_VIEWSDDL,
+ SMB_ACL_VIEW,
+ SMB_ACL_VIEW_ALL };
+
+/********************************************************************
+********************************************************************/
+
+static struct security_descriptor* parse_acl_string(TALLOC_CTX *mem_ctx, const char *szACL, size_t *sd_size )
+{
+ struct security_descriptor *sd = NULL;
+ struct security_ace *ace;
+ struct security_acl *theacl;
+ int num_ace;
+ const char *pacl;
+ int i;
+
+ if ( !szACL )
+ return NULL;
+
+ pacl = szACL;
+ num_ace = count_chars( pacl, ',' ) + 1;
+
+ if ( !(ace = talloc_zero_array( mem_ctx, struct security_ace, num_ace )) )
+ return NULL;
+
+ for ( i=0; i<num_ace; i++ ) {
+ char *end_acl = strchr_m( pacl, ',' );
+ fstring acl_string;
+
+ strncpy( acl_string, pacl, MIN( PTR_DIFF( end_acl, pacl ), sizeof(fstring)-1) );
+ acl_string[MIN( PTR_DIFF( end_acl, pacl ), sizeof(fstring)-1)] = '\0';
+
+ if ( !parse_ace(NULL, &ace[i], acl_string ) )
+ return NULL;
+
+ pacl = end_acl;
+ pacl++;
+ }
+
+ if ( !(theacl = make_sec_acl( mem_ctx, NT4_ACL_REVISION, num_ace, ace )) )
+ return NULL;
+
+ sd = make_sec_desc( mem_ctx, SD_REVISION, SEC_DESC_SELF_RELATIVE,
+ NULL, NULL, NULL, theacl, sd_size);
+
+ return sd;
+}
+
+/* add an ACE to a list of ACEs in a struct security_acl */
+static bool add_ace(TALLOC_CTX *mem_ctx, struct security_acl **the_acl, struct security_ace *ace)
+{
+ struct security_acl *acl = *the_acl;
+
+ if (acl == NULL) {
+ acl = make_sec_acl(mem_ctx, 3, 1, ace);
+ if (acl == NULL) {
+ return false;
+ }
+ }
+
+ if (acl->num_aces == UINT32_MAX) {
+ return false;
+ }
+ ADD_TO_ARRAY(
+ acl, struct security_ace, *ace, &acl->aces, &acl->num_aces);
+ *the_acl = acl;
+ return True;
+}
+
+/* The MSDN is contradictory over the ordering of ACE entries in an ACL.
+ However NT4 gives a "The information may have been modified by a
+ computer running Windows NT 5.0" if denied ACEs do not appear before
+ allowed ACEs. */
+
+static int ace_compare(struct security_ace *ace1, struct security_ace *ace2)
+{
+ if (security_ace_equal(ace1, ace2))
+ return 0;
+
+ if (ace1->type != ace2->type)
+ return ace2->type - ace1->type;
+
+ if (dom_sid_compare(&ace1->trustee, &ace2->trustee))
+ return dom_sid_compare(&ace1->trustee, &ace2->trustee);
+
+ if (ace1->flags != ace2->flags)
+ return ace1->flags - ace2->flags;
+
+ if (ace1->access_mask != ace2->access_mask)
+ return ace1->access_mask - ace2->access_mask;
+
+ if (ace1->size != ace2->size)
+ return ace1->size - ace2->size;
+
+ return memcmp(ace1, ace2, sizeof(struct security_ace));
+}
+
+static void sort_acl(struct security_acl *the_acl)
+{
+ uint32_t i;
+ if (!the_acl) return;
+
+ TYPESAFE_QSORT(the_acl->aces, the_acl->num_aces, ace_compare);
+
+ for (i=1;i<the_acl->num_aces;) {
+ if (security_ace_equal(&the_acl->aces[i-1],
+ &the_acl->aces[i])) {
+ ARRAY_DEL_ELEMENT(
+ the_acl->aces, i, the_acl->num_aces);
+ the_acl->num_aces--;
+ } else {
+ i++;
+ }
+ }
+}
+
+
+static int change_share_sec(TALLOC_CTX *mem_ctx, const char *sharename, char *the_acl, enum acl_mode mode)
+{
+ struct security_descriptor *sd = NULL;
+ struct security_descriptor *old = NULL;
+ size_t sd_size = 0;
+ uint32_t i, j;
+ NTSTATUS status;
+
+ if (mode != SMB_ACL_SET && mode != SMB_SD_DELETE) {
+ if (!(old = get_share_security( mem_ctx, sharename, &sd_size )) ) {
+ fprintf(stderr, "Unable to retrieve permissions for share "
+ "[%s]\n", sharename);
+ return -1;
+ }
+ }
+
+ if ( (mode != SMB_ACL_VIEW && mode != SMB_SD_DELETE) &&
+ !(sd = parse_acl_string(mem_ctx, the_acl, &sd_size )) ) {
+ fprintf( stderr, "Failed to parse acl\n");
+ return -1;
+ }
+
+ switch (mode) {
+ case SMB_ACL_VIEW_ALL:
+ /* should not happen */
+ return 0;
+ case SMB_ACL_VIEW:
+ sec_desc_print(NULL, stdout, old, false);
+ return 0;
+ case SMB_ACL_DELETE:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ bool found = False;
+
+ for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+ if (security_ace_equal(&sd->dacl->aces[i],
+ &old->dacl->aces[j])) {
+ uint32_t k;
+ for (k=j; k<old->dacl->num_aces-1;k++) {
+ old->dacl->aces[k] = old->dacl->aces[k+1];
+ }
+ old->dacl->num_aces--;
+ found = True;
+ break;
+ }
+ }
+
+ if (!found) {
+ printf("ACL for ACE:");
+ print_ace(NULL, stdout, &sd->dacl->aces[i], false);
+ printf(" not found\n");
+ }
+ }
+ break;
+ case SMB_ACL_MODIFY:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ bool found = False;
+
+ for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+ if (dom_sid_equal(&sd->dacl->aces[i].trustee,
+ &old->dacl->aces[j].trustee)) {
+ old->dacl->aces[j] = sd->dacl->aces[i];
+ found = True;
+ }
+ }
+
+ if (!found) {
+ struct dom_sid_buf buf;
+ printf("ACL for SID %s not found\n",
+ dom_sid_str_buf(&sd->dacl->aces[i].trustee, &buf));
+ }
+ }
+
+ if (sd->owner_sid) {
+ old->owner_sid = sd->owner_sid;
+ }
+
+ if (sd->group_sid) {
+ old->group_sid = sd->group_sid;
+ }
+ break;
+ case SMB_ACL_ADD:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ add_ace(mem_ctx, &old->dacl, &sd->dacl->aces[i]);
+ }
+ break;
+ case SMB_ACL_SET:
+ old = sd;
+ break;
+ case SMB_SD_DELETE:
+ status = delete_share_security(sharename);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf( stderr, "Failed to delete security descriptor for "
+ "share [%s]\n", sharename );
+ return -1;
+ }
+ return 0;
+ default:
+ fprintf(stderr, "invalid command\n");
+ return -1;
+ }
+
+ /* Denied ACE entries must come before allowed ones */
+ sort_acl(old->dacl);
+
+ status = set_share_security(sharename, old);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf( stderr, "Failed to store acl for share [%s]\n", sharename );
+ return 2;
+ }
+ return 0;
+}
+
+static int set_sharesec_sddl(const char *sharename, const char *sddl)
+{
+ struct security_descriptor *sd;
+ NTSTATUS status;
+
+ sd = sddl_decode(talloc_tos(), sddl, get_global_sam_sid());
+ if (sd == NULL) {
+ fprintf(stderr, "Failed to parse acl\n");
+ return -1;
+ }
+
+ status = set_share_security(sharename, sd);
+ TALLOC_FREE(sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ fprintf(stderr, "Failed to store acl for share [%s]\n",
+ sharename);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int view_sharesec_sddl(const char *sharename)
+{
+ struct security_descriptor *sd;
+ size_t sd_size;
+ char *acl;
+
+ sd = get_share_security(talloc_tos(), sharename, &sd_size);
+ if (sd == NULL) {
+ fprintf(stderr, "Unable to retrieve permissions for share "
+ "[%s]\n", sharename);
+ return -1;
+ }
+
+ acl = sddl_encode(talloc_tos(), sd, get_global_sam_sid());
+ TALLOC_FREE(sd);
+ if (acl == NULL) {
+ fprintf(stderr, "Unable to sddl-encode permissions for share "
+ "[%s]\n", sharename);
+ return -1;
+ }
+ printf("%s\n", acl);
+ TALLOC_FREE(acl);
+ return 0;
+}
+
+/********************************************************************
+ main program
+********************************************************************/
+
+enum {
+ OPT_VIEW_ALL = 1000,
+ OPT_VIEW_SDDL,
+};
+
+int main(int argc, const char *argv[])
+{
+ int opt;
+ int retval = 0;
+ enum acl_mode mode = SMB_ACL_SET;
+ static char *the_acl = NULL;
+ fstring sharename;
+ bool force_acl = False;
+ int snum;
+ poptContext pc;
+ bool initialize_sid = False;
+ bool ok;
+ struct loadparm_context *lp_ctx = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "remove",
+ .shortName = 'r',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &the_acl,
+ .val = 'r',
+ .descrip = "Remove ACEs",
+ .argDescrip = "ACL",
+ },
+ {
+ .longName = "modify",
+ .shortName = 'm',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &the_acl,
+ .val = 'm',
+ .descrip = "Modify existing ACEs",
+ .argDescrip = "ACL",
+ },
+ {
+ .longName = "add",
+ .shortName = 'a',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &the_acl,
+ .val = 'a',
+ .descrip = "Add ACEs",
+ .argDescrip = "ACL",
+ },
+ {
+ .longName = "replace",
+ .shortName = 'R',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &the_acl,
+ .val = 'R',
+ .descrip = "Overwrite share permission ACL",
+ .argDescrip = "ACLS",
+ },
+ {
+ .longName = "delete",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'D',
+ .descrip = "Delete the entire security descriptor",
+ },
+ {
+ .longName = "setsddl",
+ .shortName = 'S',
+ .argInfo = POPT_ARG_STRING,
+ .arg = the_acl,
+ .val = 'S',
+ .descrip = "Set the SD in sddl format",
+ },
+ {
+ .longName = "viewsddl",
+ .argInfo = POPT_ARG_NONE,
+ .arg = the_acl,
+ .val = OPT_VIEW_SDDL,
+ .descrip = "View the SD in sddl format",
+ },
+ {
+ .longName = "view",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'v',
+ .descrip = "View current share permissions",
+ },
+ {
+ .longName = "view-all",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = OPT_VIEW_ALL,
+ .descrip = "View all current share permissions",
+ },
+ {
+ .longName = "machine-sid",
+ .shortName = 'M',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'M',
+ .descrip = "Initialize the machine SID",
+ },
+ {
+ .longName = "force",
+ .shortName = 'F',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'F',
+ .descrip = "Force storing the ACL",
+ .argDescrip = "ACLS",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ if ( !(ctx = talloc_stackframe()) ) {
+ fprintf( stderr, "Failed to initialize talloc context!\n");
+ return -1;
+ }
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(ctx,
+ SAMBA_CMDLINE_CONFIG_NONE,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(ctx);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ /* set default debug level to 1 regardless of what smb.conf sets */
+ lpcfg_set_cmdline(lp_ctx, "log level", "1");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(ctx);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "sharename\n");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'r':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_DELETE;
+ break;
+
+ case 'm':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_MODIFY;
+ break;
+
+ case 'a':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_ADD;
+ break;
+
+ case 'R':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_SET;
+ break;
+
+ case 'D':
+ mode = SMB_SD_DELETE;
+ break;
+
+ case 'S':
+ mode = SMB_SD_SETSDDL;
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ break;
+
+ case OPT_VIEW_SDDL:
+ mode = SMB_SD_VIEWSDDL;
+ break;
+
+ case 'v':
+ mode = SMB_ACL_VIEW;
+ break;
+
+ case 'F':
+ force_acl = True;
+ break;
+
+ case 'M':
+ initialize_sid = True;
+ break;
+ case OPT_VIEW_ALL:
+ mode = SMB_ACL_VIEW_ALL;
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ setlinebuf(stdout);
+
+ lp_load_with_registry_shares(get_dyn_CONFIGFILE());
+
+ /* check for initializing secrets.tdb first */
+
+ if ( initialize_sid ) {
+ struct dom_sid *sid = get_global_sam_sid();
+ struct dom_sid_buf buf;
+
+ if ( !sid ) {
+ fprintf( stderr, "Failed to retrieve Machine SID!\n");
+ retval = 3;
+ goto done;
+ }
+
+ printf ("%s\n", dom_sid_str_buf(sid, &buf) );
+ retval = 0;
+ goto done;
+ }
+
+ if ( mode == SMB_ACL_VIEW && force_acl ) {
+ fprintf( stderr, "Invalid combination of -F and -v\n");
+ retval = -1;
+ goto done;
+ }
+
+ if (mode == SMB_ACL_VIEW_ALL) {
+ int i;
+
+ for (i=0; i<lp_numservices(); i++) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char *service = lp_servicename(frame, lp_sub, i);
+
+ if (service == NULL) {
+ continue;
+ }
+
+ printf("[%s]\n", service);
+ change_share_sec(frame, service, NULL, SMB_ACL_VIEW);
+ printf("\n");
+ TALLOC_FREE(frame);
+ }
+ goto done;
+ }
+
+ /* get the sharename */
+
+ if(!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ retval = -1;
+ goto done;
+ }
+
+ fstrcpy(sharename, poptGetArg(pc));
+
+ snum = lp_servicenumber( sharename );
+
+ if ( snum == -1 && !force_acl ) {
+ fprintf( stderr, "Invalid sharename: %s\n", sharename);
+ retval = -1;
+ goto done;
+ }
+
+ switch (mode) {
+ case SMB_SD_SETSDDL:
+ retval = set_sharesec_sddl(sharename, the_acl);
+ break;
+ case SMB_SD_VIEWSDDL:
+ retval = view_sharesec_sddl(sharename);
+ break;
+ default:
+ retval = change_share_sec(ctx, sharename, the_acl, mode);
+ break;
+ }
+
+done:
+ gfree_all();
+ poptFreeContext(pc);
+ talloc_destroy(ctx);
+
+ return retval;
+}
diff --git a/source3/utils/smbcacls.c b/source3/utils/smbcacls.c
new file mode 100644
index 0000000..ff11ba4
--- /dev/null
+++ b/source3/utils/smbcacls.c
@@ -0,0 +1,2614 @@
+/*
+ Unix SMB/CIFS implementation.
+ ACL get/set utility
+
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jeremy Allison 2000
+ Copyright (C) Jelmer Vernooij 2003
+ Copyright (C) Noel Power <noel.power@suse.com> 2013
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clirap.h"
+#include "passdb/machine_sid.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "util_sd.h"
+#include "lib/param/param.h"
+
+static char DIRSEP_CHAR = '\\';
+
+static int inheritance = 0;
+static const char *save_file = NULL;
+static const char *restore_file = NULL;
+static int recurse;
+static int test_args;
+static int sddl;
+static int query_sec_info = -1;
+static int set_sec_info = -1;
+static bool want_mxac;
+
+static const char *domain_sid = NULL;
+
+enum acl_mode {SMB_ACL_SET, SMB_ACL_DELETE, SMB_ACL_MODIFY, SMB_ACL_ADD };
+enum chown_mode {REQUEST_NONE, REQUEST_CHOWN, REQUEST_CHGRP, REQUEST_INHERIT};
+enum exit_values {EXIT_OK, EXIT_FAILED, EXIT_PARSE_ERROR};
+
+struct cacl_callback_state {
+ struct cli_credentials *creds;
+ struct cli_state *cli;
+ struct security_descriptor *aclsd;
+ struct security_acl *acl_to_add;
+ enum acl_mode mode;
+ char *the_acl;
+ bool acl_no_propagate;
+ bool numeric;
+};
+
+static NTSTATUS cli_lsa_lookup_domain_sid(struct cli_state *cli,
+ struct dom_sid *sid)
+{
+ union lsa_PolicyInformation *info = NULL;
+ struct smbXcli_tcon *orig_tcon = NULL;
+ char *orig_share = NULL;
+ struct rpc_pipe_client *rpc_pipe = NULL;
+ struct policy_handle handle;
+ NTSTATUS status, result;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (cli_state_has_tcon(cli)) {
+ cli_state_save_tcon_share(cli, &orig_tcon, &orig_share);
+ }
+
+ status = cli_tree_connect(cli, "IPC$", "?????", NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = cli_rpc_pipe_open_noauth(cli, &ndr_table_lsarpc, &rpc_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto tdis;
+ }
+
+ status = rpccli_lsa_open_policy(rpc_pipe, frame, True,
+ GENERIC_EXECUTE_ACCESS, &handle);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto tdis;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy2(rpc_pipe->binding_handle,
+ frame, &handle,
+ LSA_POLICY_INFO_DOMAIN,
+ &info, &result);
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ goto tdis;
+ }
+
+ *sid = *info->domain.sid;
+
+tdis:
+ TALLOC_FREE(rpc_pipe);
+ cli_tdis(cli);
+done:
+ cli_state_restore_tcon_share(cli, orig_tcon, orig_share);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static struct dom_sid *get_domain_sid(struct cli_state *cli)
+{
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ struct dom_sid *sid = talloc(talloc_tos(), struct dom_sid);
+ if (sid == NULL) {
+ DEBUG(0, ("Out of memory\n"));
+ return NULL;
+ }
+
+ if (domain_sid) {
+ if (!dom_sid_parse(domain_sid, sid)) {
+ DEBUG(0,("failed to parse domain sid\n"));
+ TALLOC_FREE(sid);
+ }
+ } else {
+ status = cli_lsa_lookup_domain_sid(cli, sid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("failed to lookup domain sid: %s\n", nt_errstr(status)));
+ TALLOC_FREE(sid);
+ }
+
+ }
+
+ DEBUG(2,("Domain SID: %s\n", dom_sid_str_buf(sid, &buf)));
+ return sid;
+}
+
+/* add an ACE to a list of ACEs in a struct security_acl */
+static bool add_ace_with_ctx(TALLOC_CTX *ctx, struct security_acl **the_acl,
+ const struct security_ace *ace)
+
+{
+ struct security_acl *acl = *the_acl;
+
+ if (acl == NULL) {
+ acl = make_sec_acl(ctx, 3, 0, NULL);
+ if (acl == NULL) {
+ return false;
+ }
+ }
+
+ if (acl->num_aces == UINT32_MAX) {
+ return false;
+ }
+ ADD_TO_ARRAY(
+ acl, struct security_ace, *ace, &acl->aces, &acl->num_aces);
+ *the_acl = acl;
+ return True;
+}
+
+static bool add_ace(struct security_acl **the_acl, struct security_ace *ace)
+{
+ return add_ace_with_ctx(talloc_tos(), the_acl, ace);
+}
+
+/* parse a ascii version of a security descriptor */
+static struct security_descriptor *sec_desc_parse(TALLOC_CTX *ctx, struct cli_state *cli, char *str)
+{
+ const char *p = str;
+ char *tok;
+ struct security_descriptor *ret = NULL;
+ size_t sd_size;
+ struct dom_sid owner_sid = { .num_auths = 0 };
+ bool have_owner = false;
+ struct dom_sid group_sid = { .num_auths = 0 };
+ bool have_group = false;
+ struct security_acl *dacl=NULL;
+ int revision=1;
+
+ while (next_token_talloc(ctx, &p, &tok, "\t,\r\n")) {
+ if (strncmp(tok,"REVISION:", 9) == 0) {
+ revision = strtol(tok+9, NULL, 16);
+ continue;
+ }
+
+ if (strncmp(tok,"OWNER:", 6) == 0) {
+ if (have_owner) {
+ printf("Only specify owner once\n");
+ goto done;
+ }
+ if (!StringToSid(cli, &owner_sid, tok+6)) {
+ printf("Failed to parse owner sid\n");
+ goto done;
+ }
+ have_owner = true;
+ continue;
+ }
+
+ if (strncmp(tok,"GROUP:", 6) == 0) {
+ if (have_group) {
+ printf("Only specify group once\n");
+ goto done;
+ }
+ if (!StringToSid(cli, &group_sid, tok+6)) {
+ printf("Failed to parse group sid\n");
+ goto done;
+ }
+ have_group = true;
+ continue;
+ }
+
+ if (strncmp(tok,"ACL:", 4) == 0) {
+ struct security_ace ace;
+ if (!parse_ace(cli, &ace, tok+4)) {
+ goto done;
+ }
+ if(!add_ace(&dacl, &ace)) {
+ printf("Failed to add ACL %s\n", tok);
+ goto done;
+ }
+ continue;
+ }
+
+ printf("Failed to parse token '%s' in security descriptor,\n", tok);
+ goto done;
+ }
+
+ ret = make_sec_desc(
+ ctx,
+ revision,
+ SEC_DESC_SELF_RELATIVE,
+ have_owner ? &owner_sid : NULL,
+ have_group ? &group_sid : NULL,
+ NULL,
+ dacl,
+ &sd_size);
+
+done:
+ return ret;
+}
+
+/*****************************************************
+get fileinfo for filename
+*******************************************************/
+static uint16_t get_fileinfo(struct cli_state *cli, const char *filename)
+{
+ uint16_t fnum = (uint16_t)-1;
+ NTSTATUS status;
+ struct smb_create_returns cr = {0};
+
+ /* The desired access below is the only one I could find that works
+ with NT4, W2KP and Samba */
+
+ status = cli_ntcreate(
+ cli, /* cli */
+ filename, /* fname */
+ 0, /* CreatFlags */
+ READ_CONTROL_ACCESS, /* CreatFlags */
+ 0, /* FileAttributes */
+ FILE_SHARE_READ|
+ FILE_SHARE_WRITE, /* ShareAccess */
+ FILE_OPEN, /* CreateDisposition */
+ 0x0, /* CreateOptions */
+ 0x0, /* SecurityFlags */
+ &fnum, /* pfid */
+ &cr); /* cr */
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to open %s: %s\n", filename, nt_errstr(status));
+ return 0;
+ }
+
+ cli_close(cli, fnum);
+ return cr.file_attributes;
+}
+
+/*****************************************************
+get sec desc for filename
+*******************************************************/
+static struct security_descriptor *get_secdesc_with_ctx(TALLOC_CTX *ctx,
+ struct cli_state *cli,
+ const char *filename)
+{
+ uint16_t fnum = (uint16_t)-1;
+ struct security_descriptor *sd;
+ NTSTATUS status;
+ uint32_t sec_info;
+ uint32_t desired_access = 0;
+
+ if (query_sec_info == -1) {
+ sec_info = SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL;
+ } else {
+ sec_info = query_sec_info;
+ }
+
+ if (sec_info & (SECINFO_OWNER | SECINFO_GROUP | SECINFO_DACL)) {
+ desired_access |= SEC_STD_READ_CONTROL;
+ }
+ if (sec_info & SECINFO_SACL) {
+ desired_access |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+
+ if (desired_access == 0) {
+ desired_access |= SEC_STD_READ_CONTROL;
+ }
+
+ status = cli_ntcreate(cli, filename, 0, desired_access,
+ 0, FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0x0, 0x0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to open %s: %s\n", filename, nt_errstr(status));
+ return NULL;
+ }
+
+ status = cli_query_security_descriptor(cli, fnum, sec_info,
+ ctx, &sd);
+
+ cli_close(cli, fnum);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to get security descriptor: %s\n",
+ nt_errstr(status));
+ return NULL;
+ }
+ return sd;
+}
+
+static struct security_descriptor *get_secdesc(struct cli_state *cli,
+ const char *filename)
+{
+ return get_secdesc_with_ctx(talloc_tos(), cli, filename);
+}
+/*****************************************************
+set sec desc for filename
+*******************************************************/
+static bool set_secdesc(struct cli_state *cli, const char *filename,
+ struct security_descriptor *sd)
+{
+ uint16_t fnum = (uint16_t)-1;
+ bool result=true;
+ NTSTATUS status;
+ uint32_t desired_access = 0;
+ uint32_t sec_info;
+
+ if (set_sec_info == -1) {
+ sec_info = 0;
+
+ if (sd->dacl || (sd->type & SEC_DESC_DACL_PRESENT)) {
+ sec_info |= SECINFO_DACL;
+ }
+ if (sd->sacl || (sd->type & SEC_DESC_SACL_PRESENT)) {
+ sec_info |= SECINFO_SACL;
+ }
+ if (sd->owner_sid) {
+ sec_info |= SECINFO_OWNER;
+ }
+ if (sd->group_sid) {
+ sec_info |= SECINFO_GROUP;
+ }
+ } else {
+ sec_info = set_sec_info;
+ }
+
+ /* Make the desired_access more specific. */
+ if (sec_info & SECINFO_DACL) {
+ desired_access |= SEC_STD_WRITE_DAC;
+ }
+ if (sec_info & SECINFO_SACL) {
+ desired_access |= SEC_FLAG_SYSTEM_SECURITY;
+ }
+ if (sec_info & (SECINFO_OWNER | SECINFO_GROUP)) {
+ desired_access |= SEC_STD_WRITE_OWNER;
+ }
+
+ status = cli_ntcreate(cli, filename, 0,
+ desired_access,
+ 0, FILE_SHARE_READ|FILE_SHARE_WRITE,
+ FILE_OPEN, 0x0, 0x0, &fnum, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to open %s: %s\n", filename, nt_errstr(status));
+ return false;
+ }
+
+ status = cli_set_security_descriptor(cli, fnum, sec_info, sd);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("ERROR: security descriptor set failed: %s\n",
+ nt_errstr(status));
+ result=false;
+ }
+
+ cli_close(cli, fnum);
+ return result;
+}
+
+/*****************************************************
+get maximum access for a file
+*******************************************************/
+static int cacl_mxac(struct cli_state *cli, const char *filename)
+{
+ NTSTATUS status;
+ uint32_t mxac;
+
+ status = cli_query_mxac(cli, filename, &mxac);
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Failed to get mxac: %s\n", nt_errstr(status));
+ return EXIT_FAILED;
+ }
+
+ printf("Maximum access: 0x%x\n", mxac);
+
+ return EXIT_OK;
+}
+
+
+/*****************************************************
+dump the acls for a file
+*******************************************************/
+static int cacl_dump(struct cli_state *cli, const char *filename, bool numeric)
+{
+ struct security_descriptor *sd;
+ int ret;
+
+ if (test_args) {
+ return EXIT_OK;
+ }
+
+ sd = get_secdesc(cli, filename);
+ if (sd == NULL) {
+ return EXIT_FAILED;
+ }
+
+ if (sddl) {
+ char *str = sddl_encode(talloc_tos(), sd, get_domain_sid(cli));
+ if (str == NULL) {
+ return EXIT_FAILED;
+ }
+ printf("%s\n", str);
+ TALLOC_FREE(str);
+ } else {
+ sec_desc_print(cli, stdout, sd, numeric);
+ }
+
+ if (want_mxac) {
+ ret = cacl_mxac(cli, filename);
+ if (ret != EXIT_OK) {
+ return ret;
+ }
+ }
+
+ return EXIT_OK;
+}
+
+/*****************************************************
+Change the ownership or group ownership of a file. Just
+because the NT docs say this can't be done :-). JRA.
+*******************************************************/
+
+static int owner_set(struct cli_state *cli, enum chown_mode change_mode,
+ const char *filename, const char *new_username)
+{
+ struct dom_sid sid;
+ struct security_descriptor *sd;
+ size_t sd_size;
+
+ if (!StringToSid(cli, &sid, new_username))
+ return EXIT_PARSE_ERROR;
+
+ sd = make_sec_desc(talloc_tos(),
+ SECURITY_DESCRIPTOR_REVISION_1,
+ SEC_DESC_SELF_RELATIVE,
+ (change_mode == REQUEST_CHOWN) ? &sid : NULL,
+ (change_mode == REQUEST_CHGRP) ? &sid : NULL,
+ NULL, NULL, &sd_size);
+
+ if (!set_secdesc(cli, filename, sd)) {
+ return EXIT_FAILED;
+ }
+
+ return EXIT_OK;
+}
+
+
+/* The MSDN is contradictory over the ordering of ACE entries in an
+ ACL. However NT4 gives a "The information may have been modified
+ by a computer running Windows NT 5.0" if denied ACEs do not appear
+ before allowed ACEs. At
+ http://technet.microsoft.com/en-us/library/cc781716.aspx the
+ canonical order is specified as "Explicit Deny, Explicit Allow,
+ Inherited ACEs unchanged" */
+
+static int ace_compare(struct security_ace *ace1, struct security_ace *ace2)
+{
+ if (security_ace_equal(ace1, ace2))
+ return 0;
+
+ if ((ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) &&
+ !(ace2->flags & SEC_ACE_FLAG_INHERITED_ACE))
+ return 1;
+ if (!(ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) &&
+ (ace2->flags & SEC_ACE_FLAG_INHERITED_ACE))
+ return -1;
+ if ((ace1->flags & SEC_ACE_FLAG_INHERITED_ACE) &&
+ (ace2->flags & SEC_ACE_FLAG_INHERITED_ACE))
+ return ace1 - ace2;
+
+ if (ace1->type != ace2->type)
+ return ace2->type - ace1->type;
+
+ if (dom_sid_compare(&ace1->trustee, &ace2->trustee))
+ return dom_sid_compare(&ace1->trustee, &ace2->trustee);
+
+ if (ace1->flags != ace2->flags)
+ return ace1->flags - ace2->flags;
+
+ if (ace1->access_mask != ace2->access_mask)
+ return ace1->access_mask - ace2->access_mask;
+
+ if (ace1->size != ace2->size)
+ return ace1->size - ace2->size;
+
+ return memcmp(ace1, ace2, sizeof(struct security_ace));
+}
+
+static void sort_acl(struct security_acl *the_acl)
+{
+ uint32_t i;
+ if (!the_acl) return;
+
+ TYPESAFE_QSORT(the_acl->aces, the_acl->num_aces, ace_compare);
+
+ for (i=1;i<the_acl->num_aces;) {
+ if (security_ace_equal(&the_acl->aces[i-1],
+ &the_acl->aces[i])) {
+ ARRAY_DEL_ELEMENT(
+ the_acl->aces, i, the_acl->num_aces);
+ the_acl->num_aces--;
+ } else {
+ i++;
+ }
+ }
+}
+
+/*****************************************************
+set the ACLs on a file given a security descriptor
+*******************************************************/
+
+static int cacl_set_from_sd(struct cli_state *cli, const char *filename,
+ struct security_descriptor *sd, enum acl_mode mode,
+ bool numeric)
+{
+ struct security_descriptor *old = NULL;
+ uint32_t i, j;
+ size_t sd_size;
+ int result = EXIT_OK;
+
+ if (!sd) return EXIT_PARSE_ERROR;
+ if (test_args) return EXIT_OK;
+
+ if (mode != SMB_ACL_SET) {
+ /*
+ * Do not fetch old ACL when it will be overwritten
+ * completely with a new one.
+ */
+ old = get_secdesc(cli, filename);
+
+ if (!old) {
+ return EXIT_FAILED;
+ }
+ }
+
+ /* the logic here is rather more complex than I would like */
+ switch (mode) {
+ case SMB_ACL_DELETE:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ bool found = False;
+
+ for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+ if (security_ace_equal(&sd->dacl->aces[i],
+ &old->dacl->aces[j])) {
+ uint32_t k;
+ for (k=j; k<old->dacl->num_aces-1;k++) {
+ old->dacl->aces[k] = old->dacl->aces[k+1];
+ }
+ old->dacl->num_aces--;
+ found = True;
+ break;
+ }
+ }
+
+ if (!found) {
+ printf("ACL for ACE:");
+ print_ace(cli, stdout, &sd->dacl->aces[i],
+ numeric);
+ printf(" not found\n");
+ }
+ }
+ break;
+
+ case SMB_ACL_MODIFY:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ bool found = False;
+
+ for (j=0;old->dacl && j<old->dacl->num_aces;j++) {
+ if (dom_sid_equal(&sd->dacl->aces[i].trustee,
+ &old->dacl->aces[j].trustee)) {
+ old->dacl->aces[j] = sd->dacl->aces[i];
+ found = True;
+ }
+ }
+
+ if (!found) {
+ fstring str;
+
+ SidToString(cli, str,
+ &sd->dacl->aces[i].trustee,
+ numeric);
+ printf("ACL for SID %s not found\n", str);
+ }
+ }
+
+ if (sd->owner_sid) {
+ old->owner_sid = sd->owner_sid;
+ }
+
+ if (sd->group_sid) {
+ old->group_sid = sd->group_sid;
+ }
+
+ break;
+
+ case SMB_ACL_ADD:
+ for (i=0;sd->dacl && i<sd->dacl->num_aces;i++) {
+ add_ace(&old->dacl, &sd->dacl->aces[i]);
+ }
+ break;
+
+ case SMB_ACL_SET:
+ old = sd;
+ break;
+ }
+
+ /* Denied ACE entries must come before allowed ones */
+ sort_acl(old->dacl);
+
+ /* Create new security descriptor and set it */
+
+ /* We used to just have "WRITE_DAC_ACCESS" without WRITE_OWNER.
+ But if we're sending an owner, even if it's the same as the one
+ that already exists then W2K3 insists we open with WRITE_OWNER access.
+ I need to check that setting a SD with no owner set works against WNT
+ and W2K. JRA.
+ */
+
+ sd = make_sec_desc(talloc_tos(),old->revision, old->type,
+ old->owner_sid, old->group_sid,
+ NULL, old->dacl, &sd_size);
+
+ if (!set_secdesc(cli, filename, sd)) {
+ result = EXIT_FAILED;
+ }
+
+ return result;
+}
+
+/*****************************************************
+set the ACLs on a file given an ascii description
+*******************************************************/
+
+static int cacl_set(struct cli_state *cli, const char *filename,
+ char *the_acl, enum acl_mode mode, bool numeric)
+{
+ struct security_descriptor *sd = NULL;
+
+ if (sddl) {
+ const char *msg = NULL;
+ size_t msg_offset = 0;
+ enum ace_condition_flags flags =
+ ACE_CONDITION_FLAG_ALLOW_DEVICE;
+ sd = sddl_decode_err_msg(talloc_tos(),
+ the_acl,
+ get_domain_sid(cli),
+ flags,
+ &msg,
+ &msg_offset);
+ if (sd == NULL) {
+ DBG_ERR("could not decode '%s'\n", the_acl);
+ if (msg != NULL) {
+ DBG_ERR(" %*c\n",
+ (int)msg_offset, '^');
+ DBG_ERR("error '%s'\n", msg);
+ }
+ }
+ } else {
+ sd = sec_desc_parse(talloc_tos(), cli, the_acl);
+ }
+
+ if (sd == NULL) {
+ return EXIT_PARSE_ERROR;
+ }
+ if (test_args) {
+ return EXIT_OK;
+ }
+ return cacl_set_from_sd(cli, filename, sd, mode, numeric);
+}
+
+/*****************************************************
+set the inherit on a file
+*******************************************************/
+static int inherit(struct cli_state *cli, const char *filename,
+ const char *type)
+{
+ struct security_descriptor *old,*sd;
+ uint32_t oldattr;
+ size_t sd_size;
+ int result = EXIT_OK;
+
+ old = get_secdesc(cli, filename);
+
+ if (!old) {
+ return EXIT_FAILED;
+ }
+
+ oldattr = get_fileinfo(cli,filename);
+
+ if (strcmp(type,"allow")==0) {
+ if ((old->type & SEC_DESC_DACL_PROTECTED) ==
+ SEC_DESC_DACL_PROTECTED) {
+ uint32_t i;
+ char *parentname,*temp;
+ struct security_descriptor *parent;
+ temp = talloc_strdup(talloc_tos(), filename);
+
+ old->type=old->type & (~SEC_DESC_DACL_PROTECTED);
+
+ /* look at parent and copy in all its inheritable ACL's. */
+ string_replace(temp, '\\', '/');
+ if (!parent_dirname(talloc_tos(),temp,&parentname,NULL)) {
+ return EXIT_FAILED;
+ }
+ string_replace(parentname, '/', '\\');
+ parent = get_secdesc(cli,parentname);
+ if (parent == NULL) {
+ return EXIT_FAILED;
+ }
+ for (i=0;i<parent->dacl->num_aces;i++) {
+ struct security_ace *ace=&parent->dacl->aces[i];
+ /* Add inherited flag to all aces */
+ ace->flags=ace->flags|
+ SEC_ACE_FLAG_INHERITED_ACE;
+ if ((oldattr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
+ if ((ace->flags & SEC_ACE_FLAG_CONTAINER_INHERIT) ==
+ SEC_ACE_FLAG_CONTAINER_INHERIT) {
+ add_ace(&old->dacl, ace);
+ }
+ } else {
+ if ((ace->flags & SEC_ACE_FLAG_OBJECT_INHERIT) ==
+ SEC_ACE_FLAG_OBJECT_INHERIT) {
+ /* clear flags for files */
+ ace->flags=0;
+ add_ace(&old->dacl, ace);
+ }
+ }
+ }
+ } else {
+ printf("Already set to inheritable permissions.\n");
+ return EXIT_FAILED;
+ }
+ } else if (strcmp(type,"remove")==0) {
+ if ((old->type & SEC_DESC_DACL_PROTECTED) !=
+ SEC_DESC_DACL_PROTECTED) {
+ old->type=old->type | SEC_DESC_DACL_PROTECTED;
+
+ /* remove all inherited ACL's. */
+ if (old->dacl) {
+ int i;
+ struct security_acl *temp=old->dacl;
+ old->dacl=make_sec_acl(talloc_tos(), 3, 0, NULL);
+ for (i=temp->num_aces-1;i>=0;i--) {
+ struct security_ace *ace=&temp->aces[i];
+ /* Remove all ace with INHERITED flag set */
+ if ((ace->flags & SEC_ACE_FLAG_INHERITED_ACE) !=
+ SEC_ACE_FLAG_INHERITED_ACE) {
+ add_ace(&old->dacl,ace);
+ }
+ }
+ }
+ } else {
+ printf("Already set to no inheritable permissions.\n");
+ return EXIT_FAILED;
+ }
+ } else if (strcmp(type,"copy")==0) {
+ if ((old->type & SEC_DESC_DACL_PROTECTED) !=
+ SEC_DESC_DACL_PROTECTED) {
+ old->type=old->type | SEC_DESC_DACL_PROTECTED;
+
+ /*
+ * convert all inherited ACL's to non
+ * inherited ACL's.
+ */
+ if (old->dacl) {
+ uint32_t i;
+ for (i=0;i<old->dacl->num_aces;i++) {
+ struct security_ace *ace=&old->dacl->aces[i];
+ /* Remove INHERITED FLAG from all aces */
+ ace->flags=ace->flags&(~SEC_ACE_FLAG_INHERITED_ACE);
+ }
+ }
+ } else {
+ printf("Already set to no inheritable permissions.\n");
+ return EXIT_FAILED;
+ }
+ }
+
+ /* Denied ACE entries must come before allowed ones */
+ sort_acl(old->dacl);
+
+ sd = make_sec_desc(talloc_tos(),old->revision, old->type,
+ old->owner_sid, old->group_sid,
+ NULL, old->dacl, &sd_size);
+
+ if (!set_secdesc(cli, filename, sd)) {
+ result = EXIT_FAILED;
+ }
+
+ return result;
+}
+
+/*****************************************************
+ Return a connection to a server.
+*******************************************************/
+static struct cli_state *connect_one(struct cli_credentials *creds,
+ const char *server, const char *share)
+{
+ struct cli_state *c = NULL;
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+
+ nt_status = cli_full_connection_creds(&c, lp_netbios_name(), server,
+ NULL, 0,
+ share, "?????",
+ creds,
+ flags);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("cli_full_connection failed! (%s)\n", nt_errstr(nt_status)));
+ return NULL;
+ }
+
+ return c;
+}
+
+/*
+ * Process resulting combination of mask & fname ensuring
+ * terminated with wildcard
+ */
+static char *build_dirname(TALLOC_CTX *ctx,
+ const char *mask, char *dir, char *fname)
+{
+ char *mask2 = NULL;
+ char *p = NULL;
+
+ mask2 = talloc_strdup(ctx, mask);
+ if (!mask2) {
+ return NULL;
+ }
+ p = strrchr_m(mask2, DIRSEP_CHAR);
+ if (p) {
+ p[1] = 0;
+ } else {
+ mask2[0] = '\0';
+ }
+ mask2 = talloc_asprintf_append(mask2,
+ "%s\\*",
+ fname);
+ return mask2;
+}
+
+/*
+ * Returns a copy of the ACL flags in ace modified according
+ * to some inheritance rules.
+ * a) SEC_ACE_FLAG_INHERITED_ACE is propagated to children
+ * b) SEC_ACE_FLAG_INHERIT_ONLY is set on container children for OI (only)
+ * c) SEC_ACE_FLAG_OBJECT_INHERIT & SEC_ACE_FLAG_CONTAINER_INHERIT are
+ * stripped from flags to be propagated to non-container children
+ * d) SEC_ACE_FLAG_OBJECT_INHERIT & SEC_ACE_FLAG_CONTAINER_INHERIT are
+ * stripped from flags to be propagated if the NP flag
+ * SEC_ACE_FLAG_NO_PROPAGATE_INHERIT is present
+ */
+
+static uint8_t get_flags_to_propagate(bool is_container,
+ struct security_ace *ace)
+{
+ uint8_t newflags = ace->flags;
+ /* OBJECT inheritance */
+ bool acl_objinherit = (ace->flags &
+ SEC_ACE_FLAG_OBJECT_INHERIT) == SEC_ACE_FLAG_OBJECT_INHERIT;
+ /* CONTAINER inheritance */
+ bool acl_cntrinherit = (ace->flags &
+ SEC_ACE_FLAG_CONTAINER_INHERIT) ==
+ SEC_ACE_FLAG_CONTAINER_INHERIT;
+ /* PROHIBIT inheritance */
+ bool prohibit_inheritance = ((ace->flags &
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT) ==
+ SEC_ACE_FLAG_NO_PROPAGATE_INHERIT);
+
+ /* Assume we are not propagating the ACE */
+
+ newflags &= ~SEC_ACE_FLAG_INHERITED_ACE;
+ /* all children need to have the SEC_ACE_FLAG_INHERITED_ACE set */
+ if (acl_cntrinherit || acl_objinherit) {
+ /*
+ * object inherit ( alone ) on a container needs
+ * SEC_ACE_FLAG_INHERIT_ONLY
+ */
+ if (is_container) {
+ if (acl_objinherit && !acl_cntrinherit) {
+ newflags |= SEC_ACE_FLAG_INHERIT_ONLY;
+ }
+ /*
+ * this is tricky, the only time we would not
+ * propagate the ace for a container is if
+ * prohibit_inheritance is set and object inheritance
+ * alone is set
+ */
+ if ((prohibit_inheritance
+ && acl_objinherit
+ && !acl_cntrinherit) == false) {
+ newflags |= SEC_ACE_FLAG_INHERITED_ACE;
+ }
+ } else {
+ /*
+ * don't apply object/container inheritance flags to
+ * non dirs
+ */
+ newflags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT
+ | SEC_ACE_FLAG_CONTAINER_INHERIT
+ | SEC_ACE_FLAG_INHERIT_ONLY);
+ /*
+ * only apply ace to file if object inherit
+ */
+ if (acl_objinherit) {
+ newflags |= SEC_ACE_FLAG_INHERITED_ACE;
+ }
+ }
+
+ /* if NP is specified strip NP and all OI/CI INHERIT flags */
+ if (prohibit_inheritance) {
+ newflags &= ~(SEC_ACE_FLAG_OBJECT_INHERIT
+ | SEC_ACE_FLAG_CONTAINER_INHERIT
+ | SEC_ACE_FLAG_INHERIT_ONLY
+ | SEC_ACE_FLAG_NO_PROPAGATE_INHERIT);
+ }
+ }
+ return newflags;
+}
+
+/*
+ * This function builds a new acl for 'caclfile', first it removes any
+ * existing inheritable ace(s) from the current acl of caclfile, secondly it
+ * applies any inheritable acls of the parent of caclfile ( inheritable acls of
+ * caclfile's parent are passed via acl_to_add member of cbstate )
+ *
+ */
+static NTSTATUS propagate_inherited_aces(char *caclfile,
+ struct cacl_callback_state *cbstate)
+{
+ TALLOC_CTX *aclctx = NULL;
+ NTSTATUS status;
+ int result;
+ int fileattr;
+ struct security_descriptor *old = NULL;
+ bool is_container = false;
+ struct security_acl *acl_to_add = cbstate->acl_to_add;
+ struct security_acl *acl_to_remove = NULL;
+ uint32_t i, j;
+
+ aclctx = talloc_new(NULL);
+ if (aclctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ old = get_secdesc_with_ctx(aclctx, cbstate->cli, caclfile);
+
+ if (!old) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ /* inhibit propagation? */
+ if ((old->type & SEC_DESC_DACL_PROTECTED) ==
+ SEC_DESC_DACL_PROTECTED){
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ fileattr = get_fileinfo(cbstate->cli, caclfile);
+ is_container = (fileattr & FILE_ATTRIBUTE_DIRECTORY);
+
+ /* find acl(s) that are inherited */
+ for (j = 0; old->dacl && j < old->dacl->num_aces; j++) {
+
+ if (old->dacl->aces[j].flags & SEC_ACE_FLAG_INHERITED_ACE) {
+ if (!add_ace_with_ctx(aclctx, &acl_to_remove,
+ &old->dacl->aces[j])) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ }
+
+ /* remove any acl(s) that are inherited */
+ if (acl_to_remove) {
+ for (i = 0; i < acl_to_remove->num_aces; i++) {
+ struct security_ace ace = acl_to_remove->aces[i];
+ for (j = 0; old->dacl && j < old->dacl->num_aces; j++) {
+
+ if (security_ace_equal(&ace,
+ &old->dacl->aces[j])) {
+ uint32_t k;
+ for (k = j; k < old->dacl->num_aces-1;
+ k++) {
+ old->dacl->aces[k] =
+ old->dacl->aces[k+1];
+ }
+ old->dacl->num_aces--;
+ break;
+ }
+ }
+ }
+ }
+ /* propagate any inheritable ace to be added */
+ if (acl_to_add) {
+ for (i = 0; i < acl_to_add->num_aces; i++) {
+ struct security_ace ace = acl_to_add->aces[i];
+ bool is_objectinherit = (ace.flags &
+ SEC_ACE_FLAG_OBJECT_INHERIT) ==
+ SEC_ACE_FLAG_OBJECT_INHERIT;
+ bool is_inherited;
+ /* don't propagate flags to a file unless OI */
+ if (!is_objectinherit && !is_container) {
+ continue;
+ }
+ /*
+ * adjust flags according to inheritance
+ * rules
+ */
+ ace.flags = get_flags_to_propagate(is_container, &ace);
+ is_inherited = (ace.flags &
+ SEC_ACE_FLAG_INHERITED_ACE) ==
+ SEC_ACE_FLAG_INHERITED_ACE;
+ /* don't propagate non inherited flags */
+ if (!is_inherited) {
+ continue;
+ }
+ if (!add_ace_with_ctx(aclctx, &old->dacl, &ace)) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ }
+
+ result = cacl_set_from_sd(cbstate->cli, caclfile,
+ old,
+ SMB_ACL_SET, cbstate->numeric);
+ if (result != EXIT_OK) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(aclctx);
+ return status;
+}
+
+/*
+ * Returns true if 'ace' contains SEC_ACE_FLAG_OBJECT_INHERIT or
+ * SEC_ACE_FLAG_CONTAINER_INHERIT
+ */
+static bool is_inheritable_ace(struct security_ace *ace)
+{
+ uint8_t flags = ace->flags;
+ if (flags & (SEC_ACE_FLAG_OBJECT_INHERIT
+ | SEC_ACE_FLAG_CONTAINER_INHERIT)) {
+ return true;
+ }
+ return false;
+}
+
+/* This method does some basic sanity checking with respect to automatic
+ * inheritance. e.g. it checks if it is possible to do a set, it detects illegal
+ * attempts to set inherited permissions directly. Additionally this method
+ * does some basic initialisation for instance it parses the ACL passed on the
+ * command line.
+ */
+static NTSTATUS prepare_inheritance_propagation(TALLOC_CTX *ctx, char *filename,
+ struct cacl_callback_state *cbstate)
+{
+ NTSTATUS result;
+ char *the_acl = cbstate->the_acl;
+ struct cli_state *cli = cbstate->cli;
+ enum acl_mode mode = cbstate->mode;
+ struct security_descriptor *sd = NULL;
+ struct security_descriptor *old = NULL;
+ uint32_t j;
+ bool propagate = false;
+
+ old = get_secdesc_with_ctx(ctx, cli, filename);
+ if (old == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* parse acl passed on the command line */
+ if (sddl) {
+ const char *msg = NULL;
+ size_t msg_offset = 0;
+ enum ace_condition_flags flags =
+ ACE_CONDITION_FLAG_ALLOW_DEVICE;
+
+ cbstate->aclsd = sddl_decode_err_msg(ctx,
+ the_acl,
+ get_domain_sid(cli),
+ flags,
+ &msg,
+ &msg_offset);
+ if (cbstate->aclsd == NULL) {
+ DBG_ERR("could not decode '%s'\n", the_acl);
+ if (msg != NULL) {
+ DBG_ERR(" %*c\n",
+ (int)msg_offset, '^');
+ DBG_ERR("error '%s'\n", msg);
+ }
+ }
+ } else {
+ cbstate->aclsd = sec_desc_parse(ctx, cli, the_acl);
+ }
+
+ if (!cbstate->aclsd) {
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ sd = cbstate->aclsd;
+
+ /* set operation if inheritance is enabled doesn't make sense */
+ if (mode == SMB_ACL_SET && ((old->type & SEC_DESC_DACL_PROTECTED) !=
+ SEC_DESC_DACL_PROTECTED)){
+ d_printf("Inheritance enabled at %s, can't apply set operation\n",filename);
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+
+ }
+
+ /*
+ * search command line acl for any illegal SEC_ACE_FLAG_INHERITED_ACE
+ * flags that are set
+ */
+ for (j = 0; sd->dacl && j < sd->dacl->num_aces; j++) {
+ struct security_ace *ace = &sd->dacl->aces[j];
+ if (ace->flags & SEC_ACE_FLAG_INHERITED_ACE) {
+ d_printf("Illegal parameter %s\n", the_acl);
+ result = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ if (!propagate) {
+ if (is_inheritable_ace(ace)) {
+ propagate = true;
+ }
+ }
+ }
+
+ result = NT_STATUS_OK;
+out:
+ cbstate->acl_no_propagate = !propagate;
+ return result;
+}
+
+/*
+ * This method builds inheritable ace(s) from filename (which should be
+ * a container) that need propagating to children in order to provide
+ * automatic inheritance. Those inheritable ace(s) are stored in
+ * acl_to_add member of cbstate for later processing
+ * (see propagate_inherited_aces)
+ */
+static NTSTATUS get_inheritable_aces(TALLOC_CTX *ctx, char *filename,
+ struct cacl_callback_state *cbstate)
+{
+ NTSTATUS result;
+ struct cli_state *cli = NULL;
+ struct security_descriptor *sd = NULL;
+ struct security_acl *acl_to_add = NULL;
+ uint32_t j;
+
+ cli = cbstate->cli;
+ sd = get_secdesc_with_ctx(ctx, cli, filename);
+
+ if (sd == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Check if any inheritance related flags are used, if not then
+ * nothing to do. At the same time populate acls for inheritance
+ * related ace(s) that need to be added to or deleted from children as
+ * a result of inheritance propagation.
+ */
+
+ for (j = 0; sd->dacl && j < sd->dacl->num_aces; j++) {
+ struct security_ace *ace = &sd->dacl->aces[j];
+ if (is_inheritable_ace(ace)) {
+ bool added = add_ace_with_ctx(ctx, &acl_to_add, ace);
+ if (!added) {
+ result = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ }
+ cbstate->acl_to_add = acl_to_add;
+ result = NT_STATUS_OK;
+out:
+ return result;
+}
+
+/*
+ * Callback handler to handle child elements processed by cli_list, we attempt
+ * to propagate inheritable ace(s) to each child via the function
+ * propagate_inherited_aces. Children that are themselves directories are passed
+ * to cli_list again ( to descend the directory structure )
+ */
+static NTSTATUS cacl_set_cb(struct file_info *f,
+ const char *mask, void *state)
+{
+ struct cacl_callback_state *cbstate =
+ (struct cacl_callback_state *)state;
+ struct cli_state *cli = NULL;
+ struct cli_credentials *creds = NULL;
+
+ TALLOC_CTX *dirctx = NULL;
+ NTSTATUS status;
+ struct cli_state *targetcli = NULL;
+
+ char *dir = NULL;
+ char *dir_end = NULL;
+ char *mask2 = NULL;
+ char *targetpath = NULL;
+ char *caclfile = NULL;
+
+ dirctx = talloc_new(NULL);
+ if (!dirctx) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ cli = cbstate->cli;
+ creds = cbstate->creds;
+
+ /* Work out the directory. */
+ dir = talloc_strdup(dirctx, mask);
+ if (!dir) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ dir_end = strrchr(dir, DIRSEP_CHAR);
+ if (dir_end != NULL) {
+ *dir_end = '\0';
+ }
+
+ if (!f->name || !f->name[0]) {
+ d_printf("Empty dir name returned. Possible server misconfiguration.\n");
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ if (f->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ struct cacl_callback_state dir_cbstate;
+ uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY
+ | FILE_ATTRIBUTE_SYSTEM
+ | FILE_ATTRIBUTE_HIDDEN;
+ dir_end = NULL;
+
+ /* ignore special '.' & '..' */
+ if ((f->name == NULL) || ISDOT(f->name) || ISDOTDOT(f->name)) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ mask2 = build_dirname(dirctx, mask, dir, f->name);
+ if (mask2 == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ /* check for dfs */
+ status = cli_resolve_path(dirctx, "", creds, cli,
+ mask2, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * prepare path to caclfile, remove any existing wildcard
+ * chars and convert path separators.
+ */
+
+ caclfile = talloc_strdup(dirctx, targetpath);
+ if (!caclfile) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ dir_end = strrchr(caclfile, '*');
+ if (dir_end != NULL) {
+ *dir_end = '\0';
+ }
+
+ string_replace(caclfile, '/', '\\');
+ /*
+ * make directory specific copy of cbstate here
+ * (for this directory level) to be available as
+ * the parent cbstate for the children of this directory.
+ * Note: cbstate is overwritten for the current file being
+ * processed.
+ */
+ dir_cbstate = *cbstate;
+ dir_cbstate.cli = targetcli;
+
+ /*
+ * propagate any inherited ace from our parent
+ */
+ status = propagate_inherited_aces(caclfile, &dir_cbstate);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * get inheritable ace(s) for this dir/container
+ * that will be propagated to its children
+ */
+ status = get_inheritable_aces(dirctx, caclfile,
+ &dir_cbstate);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ /*
+ * ensure cacl_set_cb gets called for children
+ * of this directory (targetpath)
+ */
+ status = cli_list(targetcli, targetpath,
+ attribute, cacl_set_cb,
+ (void *)&dir_cbstate);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ } else {
+ /*
+ * build full path to caclfile and replace '/' with '\' so
+ * other utility functions can deal with it
+ */
+
+ targetpath = talloc_asprintf(dirctx, "%s/%s", dir, f->name);
+ if (!targetpath) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ string_replace(targetpath, '/', '\\');
+
+ /* attempt to propagate any inherited ace to file caclfile */
+ status = propagate_inherited_aces(targetpath, cbstate);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+ status = NT_STATUS_OK;
+out:
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("error %s: processing %s\n",
+ nt_errstr(status),
+ targetpath);
+ }
+ TALLOC_FREE(dirctx);
+ return status;
+}
+
+
+/*
+ * Wrapper around cl_list to descend the directory tree pointed to by 'filename',
+ * helper callback function 'cacl_set_cb' handles the child elements processed
+ * by cli_list.
+ */
+static int inheritance_cacl_set(char *filename,
+ struct cacl_callback_state *cbstate)
+{
+ int result;
+ NTSTATUS ntstatus;
+ int fileattr;
+ char *mask = NULL;
+ struct cli_state *cli = cbstate->cli;
+ TALLOC_CTX *ctx = NULL;
+ bool isdirectory = false;
+ uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM
+ | FILE_ATTRIBUTE_HIDDEN;
+ ctx = talloc_init("inherit_set");
+ if (ctx == NULL) {
+ d_printf("out of memory\n");
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ /* ensure we have a filename that starts with '\' */
+ if (!filename || *filename != DIRSEP_CHAR) {
+ /* illegal or no filename */
+ result = EXIT_FAILED;
+ d_printf("illegal or missing name '%s'\n", filename);
+ goto out;
+ }
+
+
+ fileattr = get_fileinfo(cli, filename);
+ isdirectory = (fileattr & FILE_ATTRIBUTE_DIRECTORY)
+ == FILE_ATTRIBUTE_DIRECTORY;
+
+ /*
+ * if we've got as far as here then we have already evaluated
+ * the args.
+ */
+ if (test_args) {
+ result = EXIT_OK;
+ goto out;
+ }
+
+ mask = NULL;
+ /* make sure we have a trailing '\*' for directory */
+ if (!isdirectory) {
+ mask = talloc_strdup(ctx, filename);
+ } else if (strlen(filename) > 1) {
+ /*
+ * if the passed file name doesn't have a trailing '\'
+ * append it.
+ */
+ char *name_end = strrchr(filename, DIRSEP_CHAR);
+ if (name_end != filename + strlen(filename) + 1) {
+ mask = talloc_asprintf(ctx, "%s\\*", filename);
+ } else {
+ mask = talloc_strdup(ctx, filename);
+ }
+ } else {
+ /* filename is a single '\', just append '*' */
+ mask = talloc_asprintf_append(mask, "%s*", filename);
+ }
+
+ if (!mask) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ /*
+ * prepare for automatic propagation of the acl passed on the
+ * cmdline.
+ */
+
+ ntstatus = prepare_inheritance_propagation(ctx, filename,
+ cbstate);
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ d_printf("error: %s processing %s\n",
+ nt_errstr(ntstatus), filename);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ result = cacl_set_from_sd(cli, filename, cbstate->aclsd,
+ cbstate->mode, cbstate->numeric);
+
+ /*
+ * strictly speaking it could be considered an error if a file was
+ * specified with '--propagate-inheritance'. However we really want
+ * to eventually get rid of '--propagate-inheritance' so we will be
+ * more forgiving here and instead just exit early.
+ */
+ if (!isdirectory || (result != EXIT_OK)) {
+ goto out;
+ }
+
+ /* check if there is actually any need to propagate */
+ if (cbstate->acl_no_propagate) {
+ goto out;
+ }
+ /* get inheritable attributes this parent container (e.g. filename) */
+ ntstatus = get_inheritable_aces(ctx, filename, cbstate);
+ if (NT_STATUS_IS_OK(ntstatus)) {
+ /* process children */
+ ntstatus = cli_list(cli, mask, attribute,
+ cacl_set_cb,
+ (void *)cbstate);
+ }
+
+ if (!NT_STATUS_IS_OK(ntstatus)) {
+ d_printf("error: %s processing %s\n",
+ nt_errstr(ntstatus), filename);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+out:
+ TALLOC_FREE(ctx);
+ return result;
+}
+
+struct diritem {
+ struct diritem *prev, *next;
+ /*
+ * dirname and targetpath below are sanitized,
+ * e.g.
+ * + start and end with '\'
+ * + have no trailing '*'
+ * + all '/' have been converted to '\'
+ */
+ char *dirname;
+ char *targetpath;
+ struct cli_state *targetcli;
+};
+
+struct save_restore_stats
+{
+ int success;
+ int failure;
+};
+
+struct dump_context {
+ struct diritem *list;
+ struct cli_credentials *creds;
+ struct cli_state *cli;
+ struct save_restore_stats *stats;
+ int save_fd;
+ struct diritem *dir;
+ NTSTATUS status;
+};
+
+static int write_dacl(struct dump_context *ctx,
+ struct cli_state *cli,
+ const char *filename,
+ const char *origfname)
+{
+ struct security_descriptor *sd = NULL;
+ char *str = NULL;
+ const char *output_fmt = "%s\r\n%s\r\n";
+ const char *tmp = NULL;
+ char *out_str = NULL;
+ uint8_t *dest = NULL;
+ ssize_t s_len;
+ size_t d_len;
+ bool ok;
+ int result;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (test_args) {
+ return EXIT_OK;
+ }
+
+ if (ctx->save_fd < 0) {
+ DBG_ERR("error processing %s no file descriptor\n", filename);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ sd = get_secdesc(cli, filename);
+ if (sd == NULL) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ sd->owner_sid = NULL;
+ sd->group_sid = NULL;
+
+ str = sddl_encode(frame, sd, get_domain_sid(cli));
+ if (str == NULL) {
+ DBG_ERR("error processing %s couldn't encode DACL\n", filename);
+ result = EXIT_FAILED;
+ goto out;
+ }
+ /*
+ * format of icacls save file is
+ * a line containing the path of the file/dir
+ * followed by a line containing the sddl format
+ * of the dacl.
+ * The format of the strings are null terminated
+ * 16-bit Unicode. Each line is terminated by "\r\n"
+ */
+
+ tmp = origfname;
+ /* skip leading '\' */
+ if (tmp[0] == '\\') {
+ tmp++;
+ }
+ out_str = talloc_asprintf(frame, output_fmt, tmp, str);
+
+ if (out_str == NULL) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ s_len = strlen(out_str);
+
+ ok = convert_string_talloc(out_str,
+ CH_UNIX,
+ CH_UTF16,
+ out_str,
+ s_len, (void **)(void *)&dest, &d_len);
+ if (!ok) {
+ DBG_ERR("error processing %s out of memory\n", tmp);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ if (write(ctx->save_fd, dest, d_len) != d_len) {
+ DBG_ERR("error processing %s failed to write to file.\n", tmp);
+ result = EXIT_FAILED;
+ goto out;
+ }
+ fsync(ctx->save_fd);
+
+ result = EXIT_OK;
+ ctx->stats->success += 1;
+ fprintf(stdout, "Successfully processed file: %s\n", tmp);
+out:
+ TALLOC_FREE(frame);
+ if (result != EXIT_OK) {
+ ctx->stats->failure += 1;
+ }
+ return result;
+}
+
+/*
+ * Sanitize directory name.
+ * Given a directory name 'dir' ensure it;
+ * o starts with '\'
+ * o ends with '\'
+ * o doesn't end with trailing '*'
+ * o ensure all '/' are converted to '\'
+ */
+
+static char *sanitize_dirname(TALLOC_CTX *ctx,
+ const char *dir)
+{
+ char *mask = NULL;
+ char *name_end = NULL;
+
+ mask = talloc_strdup(ctx, dir);
+ name_end = strrchr(mask, '*');
+ if (name_end) {
+ *name_end = '\0';
+ }
+
+ name_end = strrchr(mask, DIRSEP_CHAR);
+
+ if (strlen(mask) > 0 && name_end != mask + (strlen(mask) - 1)) {
+ mask = talloc_asprintf(ctx, "%s\\", mask);
+ }
+
+ string_replace(mask, '/', '\\');
+ return mask;
+}
+
+/*
+ * Process each entry (child) of a directory.
+ * Each entry, regardless of whether it is itself a file or directory
+ * has it's dacl written to the restore/save file.
+ * Each directory is saved to context->list (for further processing)
+ * write_dacl will update the stats (success/fail)
+ */
+static NTSTATUS cacl_dump_dacl_cb(struct file_info *f,
+ const char *mask, void *state)
+{
+ struct dump_context *ctx = talloc_get_type_abort(state,
+ struct dump_context);
+
+ NTSTATUS status;
+
+ char *mask2 = NULL;
+ char *targetpath = NULL;
+ char *unresolved = NULL;
+
+ /*
+ * if we have already encountered an error
+ * bail out
+ */
+ if (!NT_STATUS_IS_OK(ctx->status)) {
+ return ctx->status;
+ }
+
+ if (!f->name || !f->name[0]) {
+ DBG_ERR("Empty dir name returned. Possible server "
+ "misconfiguration.\n");
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ mask2 = sanitize_dirname(ctx, mask);
+ if (!mask2) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ if (f->attr & FILE_ATTRIBUTE_DIRECTORY) {
+ struct diritem *item = NULL;
+
+ /* ignore special '.' & '..' */
+ if ((f->name == NULL) || ISDOT(f->name) || ISDOTDOT(f->name)) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ /* Work out the directory. */
+ unresolved = sanitize_dirname(ctx, ctx->dir->dirname);
+ if (!unresolved) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ unresolved = talloc_asprintf(ctx, "%s%s", unresolved, f->name);
+
+ if (unresolved == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ item = talloc_zero(ctx, struct diritem);
+ if (item == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ item->dirname = unresolved;
+
+ mask2 = talloc_asprintf(ctx, "%s%s", mask2, f->name);
+ if (!mask2) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ status = cli_resolve_path(ctx, "", ctx->creds, ctx->cli,
+ mask2, &item->targetcli, &targetpath);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("error failed to resolve: %s\n",
+ nt_errstr(status));
+ goto out;
+ }
+
+ item->targetpath = sanitize_dirname(ctx, targetpath);
+ if (!item->targetpath) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (write_dacl(ctx,
+ item->targetcli,
+ item->targetpath, unresolved) != EXIT_OK) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ /*
+ * cli_list happily ignores error encountered
+ * when processing the callback so we need
+ * to save any error status encountered while
+ * processing directories (so we can stop recursing
+ * those as soon as possible).
+ * Changing the current behaviour of the callback
+ * handling by cli_list would be I think be too
+ * risky.
+ */
+ ctx->status = status;
+ goto out;
+ }
+
+ DLIST_ADD_END(ctx->list, item);
+
+ } else {
+ unresolved = sanitize_dirname(ctx, ctx->dir->dirname);
+ if (!unresolved) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ unresolved = talloc_asprintf(ctx, "%s%s", unresolved, f->name);
+
+ if (!unresolved) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ /*
+ * build full path to the file and replace '/' with '\' so
+ * other utility functions can deal with it
+ */
+
+ targetpath = talloc_asprintf(ctx, "%s%s", mask2, f->name);
+
+ if (!targetpath) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (write_dacl(ctx,
+ ctx->dir->targetcli,
+ targetpath, unresolved) != EXIT_OK) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ /*
+ * cli_list happily ignores error encountered
+ * when processing the callback so we need
+ * to save any error status encountered while
+ * processing directories (so we can stop recursing
+ * those as soon as possible).
+ * Changing the current behaviour of the callback
+ * handling by cli_list would be I think be too
+ * risky.
+ */
+ ctx->status = status;
+ goto out;
+ }
+ }
+ status = NT_STATUS_OK;
+out:
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("error %s: processing %s\n",
+ nt_errstr(status), targetpath);
+ }
+ return status;
+}
+
+/*
+ * dump_ctx contains a list of directories to be processed
+ * + each directory 'dir' is scanned by cli_list, the cli_list
+ * callback 'cacl_dump_dacl_cb' writes out the dacl of each
+ * child of 'dir' (regardless of whether it is a dir or file)
+ * to the restore/save file. Additionally any directories encountered
+ * are returned in the passed in dump_ctx->list member
+ * + the directory list returned from cli_list is passed and processed
+ * by recursively calling dump_dacl_dirtree
+ *
+ */
+static int dump_dacl_dirtree(struct dump_context *dump_ctx)
+{
+ struct diritem *item = NULL;
+ struct dump_context *new_dump_ctx = NULL;
+ int result;
+ for (item = dump_ctx->list; item; item = item->next) {
+ uint16_t attribute = FILE_ATTRIBUTE_DIRECTORY
+ | FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN;
+ NTSTATUS status;
+ char *mask = NULL;
+
+ new_dump_ctx = talloc_zero(dump_ctx, struct dump_context);
+
+ if (new_dump_ctx == NULL) {
+ DBG_ERR("out of memory\n");
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ if (item->targetcli == NULL) {
+ status = cli_resolve_path(new_dump_ctx,
+ "",
+ dump_ctx->creds,
+ dump_ctx->cli,
+ item->dirname,
+ &item->targetcli,
+ &item->targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to resolve path %s "
+ "error: %s\n",
+ item->dirname, nt_errstr(status));
+ result = EXIT_FAILED;
+ goto out;
+ }
+ }
+ new_dump_ctx->creds = dump_ctx->creds;
+ new_dump_ctx->save_fd = dump_ctx->save_fd;
+ new_dump_ctx->stats = dump_ctx->stats;
+ new_dump_ctx->dir = item;
+ new_dump_ctx->cli = item->targetcli;
+
+ mask = talloc_asprintf(new_dump_ctx, "%s*",
+ new_dump_ctx->dir->targetpath);
+ status = cli_list(new_dump_ctx->dir->targetcli,
+ mask,
+ attribute, cacl_dump_dacl_cb, new_dump_ctx);
+
+ if (!NT_STATUS_IS_OK(status) ||
+ !NT_STATUS_IS_OK(new_dump_ctx->status)) {
+ NTSTATUS tmpstatus;
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * cli_list failed for some reason
+ * so we need to update the failure stat
+ */
+ new_dump_ctx->stats->failure += 1;
+ tmpstatus = status;
+ } else {
+ /* cacl_dump_dacl_cb should have updated stat */
+ tmpstatus = new_dump_ctx->status;
+ }
+ DBG_ERR("error %s: processing %s\n",
+ nt_errstr(tmpstatus), item->dirname);
+ result = EXIT_FAILED;
+ goto out;
+ }
+ result = dump_dacl_dirtree(new_dump_ctx);
+ if (result != EXIT_OK) {
+ goto out;
+ }
+ }
+
+ result = EXIT_OK;
+out:
+ TALLOC_FREE(new_dump_ctx);
+ return result;
+}
+
+static int cacl_dump_dacl(struct cli_state *cli,
+ struct cli_credentials *creds,
+ char *filename)
+{
+ int fileattr;
+ char *mask = NULL;
+ TALLOC_CTX *ctx = NULL;
+ bool isdirectory = false;
+ int result;
+ struct dump_context *dump_ctx = NULL;
+ struct save_restore_stats stats = {0};
+ struct diritem *item = NULL;
+ struct cli_state *targetcli = NULL;
+ char *targetpath = NULL;
+ NTSTATUS status;
+
+ ctx = talloc_init("cacl_dump");
+ if (ctx == NULL) {
+ DBG_ERR("out of memory\n");
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ dump_ctx = talloc_zero(ctx, struct dump_context);
+ if (dump_ctx == NULL) {
+ DBG_ERR("out of memory\n");
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ dump_ctx->save_fd = open(save_file,
+ O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR);
+
+ if (dump_ctx->save_fd < 0) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ dump_ctx->creds = creds;
+ dump_ctx->cli = cli;
+ dump_ctx->stats = &stats;
+
+ /* ensure we have a filename that starts with '\' */
+ if (!filename || *filename != DIRSEP_CHAR) {
+ /* illegal or no filename */
+ result = EXIT_FAILED;
+ DBG_ERR("illegal or missing name '%s'\n", filename);
+ goto out;
+ }
+
+ status = cli_resolve_path(dump_ctx, "",
+ dump_ctx->creds,
+ dump_ctx->cli,
+ filename, &targetcli, &targetpath);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed resolve %s\n", filename);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ fileattr = get_fileinfo(targetcli, targetpath);
+ isdirectory = (fileattr & FILE_ATTRIBUTE_DIRECTORY)
+ == FILE_ATTRIBUTE_DIRECTORY;
+
+ /*
+ * if we've got as far as here then we have already evaluated
+ * the args.
+ */
+ if (test_args) {
+ result = EXIT_OK;
+ goto out;
+ }
+
+ mask = NULL;
+ /* make sure we have a trailing '\*' for directory */
+ if (!isdirectory) {
+ mask = talloc_strdup(ctx, filename);
+ } else if (strlen(filename) > 1) {
+ mask = sanitize_dirname(ctx, filename);
+ } else {
+ /* filename is a single '\' */
+ mask = talloc_strdup(ctx, filename);
+ }
+ if (!mask) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ write_dacl(dump_ctx, targetcli, targetpath, filename);
+ if (isdirectory && recurse) {
+ item = talloc_zero(dump_ctx, struct diritem);
+ if (!item) {
+ result = EXIT_FAILED;
+ goto out;
+ }
+ item->dirname = mask;
+ DLIST_ADD_END(dump_ctx->list, item);
+ dump_dacl_dirtree(dump_ctx);
+ }
+
+ fprintf(stdout, "Successfully processed %d files: "
+ "Failed processing %d files\n",
+ dump_ctx->stats->success, dump_ctx->stats->failure);
+ result = EXIT_OK;
+out:
+ if (dump_ctx && dump_ctx->save_fd > 0) {
+ close(dump_ctx->save_fd);
+ }
+ TALLOC_FREE(ctx);
+ return result;
+}
+
+struct restore_dacl {
+ const char *path;
+ struct security_descriptor *sd;
+};
+
+/*
+ * Restore dacls from 'savefile' produced by
+ * 'icacls name /save' or 'smbcacls --save'
+ */
+static int cacl_restore(struct cli_state *cli,
+ struct cli_credentials *creds,
+ bool numeric, const char *restorefile)
+{
+ int restore_fd;
+ int result;
+ struct save_restore_stats stats = { 0 };
+
+ char **lines = NULL;
+ char *content = NULL;
+ char *convert_content = NULL;
+ size_t content_size;
+ struct restore_dacl *entries = NULL;
+ int numlines, i = 0;
+ bool ok;
+ struct dom_sid *sid = NULL;
+
+ if (restorefile == NULL) {
+ DBG_ERR("No restore file specified\n");
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ if (test_args) {
+ result = EXIT_OK;
+ goto out;
+ }
+
+ restore_fd = open(restorefile, O_RDONLY, S_IRUSR | S_IWUSR);
+ if (restore_fd < 0) {
+ DBG_ERR("Failed to open %s.\n", restorefile);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ content = fd_load(restore_fd, &content_size, 0, talloc_tos());
+
+ close(restore_fd);
+
+ if (content == NULL) {
+ DBG_ERR("Failed to load content from %s.\n", restorefile);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ ok = convert_string_talloc(talloc_tos(),
+ CH_UTF16,
+ CH_UNIX,
+ content,
+ utf16_len_n(content, content_size),
+ (void **)(void *)&convert_content,
+ &content_size);
+
+ TALLOC_FREE(content);
+
+ if (!ok) {
+ DBG_ERR("Failed to convert content from %s "
+ "to CH_UNIX.\n", restorefile);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ lines = file_lines_parse(convert_content,
+ content_size, &numlines, talloc_tos());
+
+ if (lines == NULL) {
+ DBG_ERR("Failed to parse lines from content of %s.",
+ restorefile);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ entries = talloc_zero_array(lines, struct restore_dacl, numlines / 2);
+
+ if (entries == NULL) {
+ DBG_ERR("error processing %s, out of memory\n", restorefile);
+ result = EXIT_FAILED;
+ goto out;
+ }
+
+ sid = get_domain_sid(cli);
+
+ while (i < numlines) {
+ int index = i / 2;
+ int first_line = (i % 2) == 0;
+
+ if (first_line) {
+ char *tmp = NULL;
+ tmp = lines[i];
+ /* line can be blank if root of share */
+ if (strlen(tmp) == 0) {
+ entries[index].path = talloc_strdup(lines,
+ "\\");
+ } else {
+ entries[index].path = lines[i];
+ }
+ } else {
+ const char *msg = NULL;
+ size_t msg_offset = 0;
+ enum ace_condition_flags flags =
+ ACE_CONDITION_FLAG_ALLOW_DEVICE;
+ entries[index].sd = sddl_decode_err_msg(lines,
+ lines[i],
+ sid,
+ flags,
+ &msg,
+ &msg_offset);
+ if(entries[index].sd == NULL) {
+ DBG_ERR("could not decode '%s'\n", lines[i]);
+ if (msg != NULL) {
+ DBG_ERR(" %*c\n",
+ (int)msg_offset, '^');
+ DBG_ERR("error '%s'\n", msg);
+ }
+ result = EXIT_FAILED;
+ goto out;
+ }
+ entries[index].sd->type |=
+ SEC_DESC_DACL_AUTO_INHERIT_REQ;
+ entries[index].sd->type |= SEC_DESC_SACL_AUTO_INHERITED;
+ }
+ i++;
+ }
+ for (i = 0; i < (numlines / 2); i++) {
+ int mode = SMB_ACL_SET;
+ int set_result;
+ struct cli_state *targetcli = NULL;
+ char *targetpath = NULL;
+ NTSTATUS status;
+
+ /* check for dfs */
+ status = cli_resolve_path(talloc_tos(),
+ "",
+ creds,
+ cli,
+ entries[i].path,
+ &targetcli, &targetpath);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ printf("Error failed to process file: %s\n",
+ entries[i].path);
+ stats.failure += 1;
+ continue;
+ }
+
+ set_result = cacl_set_from_sd(targetcli,
+ targetpath,
+ entries[i].sd, mode, numeric);
+
+ if (set_result == EXIT_OK) {
+ printf("Successfully processed file: %s\n",
+ entries[i].path);
+ stats.success += 1;
+ } else {
+ printf("Error failed to process file: %s\n",
+ entries[i].path);
+ stats.failure += 1;
+ }
+ }
+
+ result = EXIT_OK;
+out:
+ TALLOC_FREE(lines);
+ fprintf(stdout, "Successfully processed %d files: "
+ "Failed processing %d files\n", stats.success, stats.failure);
+ return result;
+}
+
+/****************************************************************************
+ main program
+****************************************************************************/
+int main(int argc, char *argv[])
+{
+ const char **argv_const = discard_const_p(const char *, argv);
+ char *share;
+ int opt;
+ enum acl_mode mode = SMB_ACL_SET;
+ static char *the_acl = NULL;
+ enum chown_mode change_mode = REQUEST_NONE;
+ int result;
+ char *path;
+ char *filename = NULL;
+ poptContext pc;
+ /* numeric is set when the user wants numeric SIDs and ACEs rather
+ than going via LSA calls to resolve them */
+ int numeric = 0;
+ struct cli_state *targetcli = NULL;
+ struct cli_credentials *creds = NULL;
+ char *targetfile = NULL;
+ NTSTATUS status;
+ bool ok;
+ struct loadparm_context *lp_ctx = NULL;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "delete",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'D',
+ .descrip = "Delete an acl",
+ .argDescrip = "ACL",
+ },
+ {
+ .longName = "modify",
+ .shortName = 'M',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'M',
+ .descrip = "Modify an acl",
+ .argDescrip = "ACL",
+ },
+ {
+ .longName = "add",
+ .shortName = 'a',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'a',
+ .descrip = "Add an acl",
+ .argDescrip = "ACL",
+ },
+ {
+ .longName = "set",
+ .shortName = 'S',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'S',
+ .descrip = "Set acls",
+ .argDescrip = "ACLS",
+ },
+ {
+ .longName = "chown",
+ .shortName = 'C',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'C',
+ .descrip = "Change ownership of a file",
+ .argDescrip = "USERNAME",
+ },
+ {
+ .longName = "chgrp",
+ .shortName = 'G',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'G',
+ .descrip = "Change group ownership of a file",
+ .argDescrip = "GROUPNAME",
+ },
+ {
+ .longName = "inherit",
+ .shortName = 'I',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'I',
+ .descrip = "Inherit allow|remove|copy",
+ },
+ {
+ .longName = "propagate-inheritance",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &inheritance,
+ .val = 1,
+ .descrip = "Supports propagation of inheritable ACE(s) when used in conjunction with add, delete, set or modify",
+ },
+ {
+ .longName = "save",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &save_file,
+ .val = 1,
+ .descrip = "stores the DACLs in sddl format of the "
+ "specified file or folder for later use "
+ "with restore. SACLS, owner or integrity"
+ " labels are not stored",
+ },
+ {
+ .longName = "restore",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &restore_file,
+ .val = 1,
+ .descrip = "applies the stored DACLS to files in "
+ "directory.",
+ },
+ {
+ .longName = "recurse",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &recurse,
+ .val = 1,
+ .descrip = "indicates the operation is performed "
+ "on directory and all files/directories"
+ " below. (only applies to save option)",
+ },
+ {
+ .longName = "numeric",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &numeric,
+ .val = 1,
+ .descrip = "Don't resolve sids or masks to names",
+ },
+ {
+ .longName = "sddl",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &sddl,
+ .val = 1,
+ .descrip = "Output and input acls in sddl format",
+ },
+ {
+ .longName = "query-security-info",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &query_sec_info,
+ .val = 1,
+ .descrip = "The security-info flags for queries"
+ },
+ {
+ .longName = "set-security-info",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &set_sec_info,
+ .val = 1,
+ .descrip = "The security-info flags for modifications"
+ },
+ {
+ .longName = "test-args",
+ .shortName = 't',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &test_args,
+ .val = 1,
+ .descrip = "Test arguments"
+ },
+ {
+ .longName = "domain-sid",
+ .shortName = 0,
+ .argInfo = POPT_ARG_STRING,
+ .arg = &domain_sid,
+ .val = 0,
+ .descrip = "Domain SID for sddl",
+ .argDescrip = "SID"},
+ {
+ .longName = "maximum-access",
+ .shortName = 'x',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'x',
+ .descrip = "Query maximum permissions",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S3
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ struct cli_state *cli;
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char *owner_username = "";
+ char *server;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ /* set default debug level to 1 regardless of what smb.conf sets */
+ lpcfg_set_cmdline(lp_ctx, "log level", "1");
+
+ setlinebuf(stdout);
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv_const,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "//server1/share1 filename\nACLs look like: "
+ "'ACL:user:[ALLOWED|DENIED]/flags/permissions'");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'S':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_SET;
+ break;
+
+ case 'D':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_DELETE;
+ break;
+
+ case 'M':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_MODIFY;
+ break;
+
+ case 'a':
+ the_acl = smb_xstrdup(poptGetOptArg(pc));
+ mode = SMB_ACL_ADD;
+ break;
+
+ case 'C':
+ owner_username = poptGetOptArg(pc);
+ change_mode = REQUEST_CHOWN;
+ break;
+
+ case 'G':
+ owner_username = poptGetOptArg(pc);
+ change_mode = REQUEST_CHGRP;
+ break;
+
+ case 'I':
+ owner_username = poptGetOptArg(pc);
+ change_mode = REQUEST_INHERIT;
+ break;
+ case 'm':
+ lpcfg_set_cmdline(lp_ctx, "client max protocol", poptGetOptArg(pc));
+ break;
+ case 'x':
+ want_mxac = true;
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+ if (inheritance && !the_acl) {
+ poptPrintUsage(pc, stderr, 0);
+ return -1;
+ }
+
+ if(!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ return -1;
+ }
+
+ path = talloc_strdup(frame, poptGetArg(pc));
+ if (!path) {
+ return -1;
+ }
+
+ if (strncmp(path, "\\\\", 2) && strncmp(path, "//", 2)) {
+ printf("Invalid argument: %s\n", path);
+ return -1;
+ }
+
+ if(!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ return -1;
+ }
+
+ filename = talloc_strdup(frame, poptGetArg(pc));
+ if (!filename) {
+ return -1;
+ }
+
+ poptFreeContext(pc);
+ samba_cmdline_burn(argc, argv);
+
+ string_replace(path,'/','\\');
+
+ server = talloc_strdup(frame, path+2);
+ if (!server) {
+ return -1;
+ }
+ share = strchr_m(server,'\\');
+ if (share == NULL) {
+ printf("Invalid argument\n");
+ return -1;
+ }
+
+ *share = 0;
+ share++;
+
+ creds = samba_cmdline_get_creds();
+
+ /* Make connection to server */
+ if (!test_args) {
+ cli = connect_one(creds, server, share);
+ if (!cli) {
+ exit(EXIT_FAILED);
+ }
+ } else {
+ exit(0);
+ }
+
+ string_replace(filename, '/', '\\');
+ if (filename[0] != '\\') {
+ filename = talloc_asprintf(frame,
+ "\\%s",
+ filename);
+ if (!filename) {
+ return -1;
+ }
+ }
+
+ status = cli_resolve_path(frame,
+ "",
+ creds,
+ cli,
+ filename,
+ &targetcli,
+ &targetfile);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("cli_resolve_path failed for %s! (%s)\n", filename, nt_errstr(status)));
+ return -1;
+ }
+
+ /* Perform requested action */
+
+ if (change_mode == REQUEST_INHERIT) {
+ result = inherit(targetcli, targetfile, owner_username);
+ } else if (change_mode != REQUEST_NONE) {
+ result = owner_set(targetcli, change_mode, targetfile, owner_username);
+ } else if (the_acl) {
+ if (inheritance) {
+ struct cacl_callback_state cbstate = {
+ .creds = creds,
+ .cli = targetcli,
+ .mode = mode,
+ .the_acl = the_acl,
+ .numeric = numeric,
+ };
+ result = inheritance_cacl_set(targetfile, &cbstate);
+ } else {
+ result = cacl_set(targetcli,
+ targetfile,
+ the_acl,
+ mode,
+ numeric);
+ }
+ } else {
+ if (save_file || restore_file) {
+ sddl = 1;
+ if (save_file) {
+ result = cacl_dump_dacl(cli, creds, filename);
+ } else {
+ result = cacl_restore(targetcli,
+ creds,
+ numeric, restore_file);
+ }
+ } else {
+ result = cacl_dump(targetcli, targetfile, numeric);
+ }
+ }
+
+ gfree_all();
+ TALLOC_FREE(frame);
+
+ return result;
+}
diff --git a/source3/utils/smbcontrol.c b/source3/utils/smbcontrol.c
new file mode 100644
index 0000000..b318f55
--- /dev/null
+++ b/source3/utils/smbcontrol.c
@@ -0,0 +1,1870 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Send messages to other Samba daemons
+
+ Copyright (C) Tim Potter 2003
+ Copyright (C) Andrew Tridgell 1994-1998
+ Copyright (C) Martin Pool 2001-2002
+ Copyright (C) Simo Sorce 2002
+ Copyright (C) James Peach 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/util/server_id.h"
+#include "lib/cmdline/cmdline.h"
+#include "librpc/gen_ndr/spoolss.h"
+#include "nt_printing.h"
+#include "printing/notify.h"
+#include "libsmb/nmblib.h"
+#include "messages.h"
+#include "util_tdb.h"
+#include "../lib/util/pidfile.h"
+#include "serverid.h"
+#include "lib/util/server_id_db.h"
+#include "cmdline_contexts.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "lib/param/param.h"
+
+#ifdef HAVE_LIBUNWIND_H
+#include <libunwind.h>
+#endif
+
+#ifdef HAVE_LIBUNWIND_PTRACE_H
+#include <libunwind-ptrace.h>
+#endif
+
+#ifdef HAVE_SYS_PTRACE_H
+#include <sys/ptrace.h>
+#endif
+
+/* Default timeout value when waiting for replies (in seconds) */
+
+#define DEFAULT_TIMEOUT 10
+
+static int timeout = DEFAULT_TIMEOUT;
+static int num_replies; /* Used by message callback fns */
+
+/* Send a message to a destination pid. Zero means broadcast smbd. */
+
+static bool send_message(struct messaging_context *msg_ctx,
+ struct server_id pid, int msg_type,
+ const void *buf, int len)
+{
+ if (procid_to_pid(&pid) != 0)
+ return NT_STATUS_IS_OK(
+ messaging_send_buf(msg_ctx, pid, msg_type,
+ (const uint8_t *)buf, len));
+
+ messaging_send_all(msg_ctx, msg_type, buf, len);
+
+ return true;
+}
+
+static void smbcontrol_timeout(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ bool *timed_out = (bool *)private_data;
+ TALLOC_FREE(te);
+ *timed_out = True;
+}
+
+/* Wait for one or more reply messages */
+
+static void wait_replies(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ bool multiple_replies)
+{
+ struct tevent_timer *te;
+ bool timed_out = False;
+
+ te = tevent_add_timer(ev_ctx, NULL,
+ timeval_current_ofs(timeout, 0),
+ smbcontrol_timeout, (void *)&timed_out);
+ if (te == NULL) {
+ DEBUG(0, ("tevent_add_timer failed\n"));
+ return;
+ }
+
+ while (!timed_out) {
+ int ret;
+ if (num_replies > 0 && !multiple_replies)
+ break;
+ ret = tevent_loop_once(ev_ctx);
+ if (ret != 0) {
+ break;
+ }
+ }
+}
+
+/* Message handler callback that displays the PID and a string on stdout */
+
+static void print_pid_string_cb(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id pid,
+ DATA_BLOB *data)
+{
+ struct server_id_buf pidstr;
+
+ printf("PID %s: %.*s", server_id_str_buf(pid, &pidstr),
+ (int)data->length, (const char *)data->data);
+ num_replies++;
+}
+
+/* Send no message. Useful for testing. */
+
+static bool do_noop(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> noop\n");
+ return False;
+ }
+
+ /* Move along, nothing to see here */
+
+ return True;
+}
+
+/* Send a debug string */
+
+static bool do_debug(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> debug "
+ "<debug-string>\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_DEBUG, argv[1],
+ strlen(argv[1]) + 1);
+}
+
+
+static bool do_idmap(struct tevent_context *ev,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ static const char* usage = "Usage: "
+ "smbcontrol <dest> idmap <cmd> [arg]\n"
+ "\tcmd:"
+ "\tdelete \"UID <uid>\"|\"GID <gid>\"|<sid>\n"
+ "\t\tkill \"UID <uid>\"|\"GID <gid>\"|<sid>\n";
+ const char* arg = NULL;
+ int arglen = 0;
+ int msg_type;
+
+ switch (argc) {
+ case 2:
+ break;
+ case 3:
+ arg = argv[2];
+ arglen = strlen(arg) + 1;
+ break;
+ default:
+ fprintf(stderr, "%s", usage);
+ return false;
+ }
+
+ if (strcmp(argv[1], "delete") == 0) {
+ msg_type = ID_CACHE_DELETE;
+ }
+ else if (strcmp(argv[1], "kill") == 0) {
+ msg_type = ID_CACHE_KILL;
+ }
+ else if (strcmp(argv[1], "help") == 0) {
+ fprintf(stdout, "%s", usage);
+ return true;
+ }
+ else {
+ fprintf(stderr, "%s", usage);
+ return false;
+ }
+
+ return send_message(msg_ctx, pid, msg_type, arg, arglen);
+}
+
+
+#if defined(HAVE_LIBUNWIND_PTRACE) && defined(HAVE_LINUX_PTRACE)
+
+/* Return the name of a process given it's PID. This will only work on Linux,
+ * but that's probably moot since this whole stack tracing implementation is
+ * Linux-specific anyway.
+ */
+static const char * procname(pid_t pid, char * buf, size_t bufsz)
+{
+ char path[64];
+ FILE * fp;
+
+ snprintf(path, sizeof(path), "/proc/%llu/cmdline",
+ (unsigned long long)pid);
+ if ((fp = fopen(path, "r")) == NULL) {
+ return NULL;
+ }
+
+ fgets(buf, bufsz, fp);
+
+ fclose(fp);
+ return buf;
+}
+
+static void print_stack_trace(pid_t pid, int * count)
+{
+ void * pinfo = NULL;
+ unw_addr_space_t aspace = NULL;
+ unw_cursor_t cursor;
+ unw_word_t ip, sp;
+
+ char nbuf[256];
+ unw_word_t off;
+
+ int ret;
+
+ if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) {
+ fprintf(stderr,
+ "Failed to attach to process %llu: %s\n",
+ (unsigned long long)pid, strerror(errno));
+ return;
+ }
+
+ /* Wait until the attach is complete. */
+ waitpid(pid, NULL, 0);
+
+ if (((pinfo = _UPT_create(pid)) == NULL) ||
+ ((aspace = unw_create_addr_space(&_UPT_accessors, 0)) == NULL)) {
+ /* Probably out of memory. */
+ fprintf(stderr,
+ "Unable to initialize stack unwind for process %llu\n",
+ (unsigned long long)pid);
+ goto cleanup;
+ }
+
+ if ((ret = unw_init_remote(&cursor, aspace, pinfo))) {
+ fprintf(stderr,
+ "Unable to unwind stack for process %llu: %s\n",
+ (unsigned long long)pid, unw_strerror(ret));
+ goto cleanup;
+ }
+
+ if (*count > 0) {
+ printf("\n");
+ }
+
+ if (procname(pid, nbuf, sizeof(nbuf))) {
+ printf("Stack trace for process %llu (%s):\n",
+ (unsigned long long)pid, nbuf);
+ } else {
+ printf("Stack trace for process %llu:\n",
+ (unsigned long long)pid);
+ }
+
+ while (unw_step(&cursor) > 0) {
+ ip = sp = off = 0;
+ unw_get_reg(&cursor, UNW_REG_IP, &ip);
+ unw_get_reg(&cursor, UNW_REG_SP, &sp);
+
+ ret = unw_get_proc_name(&cursor, nbuf, sizeof(nbuf), &off);
+ if (ret != 0 && ret != -UNW_ENOMEM) {
+ snprintf(nbuf, sizeof(nbuf), "<unknown symbol>");
+ }
+ printf(" %s + %#llx [ip=%#llx] [sp=%#llx]\n",
+ nbuf, (long long)off, (long long)ip,
+ (long long)sp);
+ }
+
+ (*count)++;
+
+cleanup:
+ if (aspace) {
+ unw_destroy_addr_space(aspace);
+ }
+
+ if (pinfo) {
+ _UPT_destroy(pinfo);
+ }
+
+ ptrace(PTRACE_DETACH, pid, NULL, NULL);
+}
+
+static int stack_trace_server(pid_t pid, void *priv)
+{
+ print_stack_trace(pid, (int *)priv);
+ return 0;
+}
+
+static bool do_daemon_stack_trace(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ pid_t dest;
+ int count = 0;
+
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> stacktrace\n");
+ return False;
+ }
+
+ dest = procid_to_pid(&pid);
+
+ if (dest != 0) {
+ /* It would be nice to be able to make sure that this PID is
+ * the PID of a smbd/winbind/nmbd process, not some random PID
+ * the user liked the look of. It doesn't seem like it's worth
+ * the effort at the moment, however.
+ */
+ print_stack_trace(dest, &count);
+ } else {
+ messaging_dgm_forall(stack_trace_server, &count);
+ }
+
+ return True;
+}
+
+#else /* defined(HAVE_LIBUNWIND_PTRACE) && defined(HAVE_LINUX_PTRACE) */
+
+static bool do_daemon_stack_trace(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ fprintf(stderr,
+ "Daemon stack tracing is not supported on this platform\n");
+ return False;
+}
+
+#endif /* defined(HAVE_LIBUNWIND_PTRACE) && defined(HAVE_LINUX_PTRACE) */
+
+/* Inject a fault (fatal signal) into a running smbd */
+
+static bool do_inject_fault(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> inject "
+ "<bus|hup|term|internal|segv>\n");
+ return False;
+ }
+
+#if !defined(DEVELOPER) && !defined(ENABLE_SELFTEST)
+ fprintf(stderr, "Fault injection is only available in "
+ "developer and self test builds\n");
+ return False;
+#else /* DEVELOPER || ENABLE_SELFTEST */
+ {
+ int sig = 0;
+
+ if (strcmp(argv[1], "bus") == 0) {
+ sig = SIGBUS;
+ } else if (strcmp(argv[1], "hup") == 0) {
+ sig = SIGHUP;
+ } else if (strcmp(argv[1], "term") == 0) {
+ sig = SIGTERM;
+ } else if (strcmp(argv[1], "segv") == 0) {
+ sig = SIGSEGV;
+ } else if (strcmp(argv[1], "internal") == 0) {
+ /* Force an internal error, ie. an unclean exit. */
+ sig = -1;
+ } else {
+ fprintf(stderr, "Unknown signal name '%s'\n", argv[1]);
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_SMB_INJECT_FAULT,
+ &sig, sizeof(int));
+ }
+#endif /* DEVELOPER || ENABLE_SELFTEST */
+}
+
+static bool do_sleep(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+#if defined(DEVELOPER) || defined(ENABLE_SELFTEST)
+ unsigned int seconds;
+ long input;
+ const long MAX_SLEEP = 60 * 60; /* One hour maximum sleep */
+#endif
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> sleep seconds\n");
+ return False;
+ }
+
+#if !defined(DEVELOPER) && !defined(ENABLE_SELFTEST)
+ fprintf(stderr, "Sleep is only available in "
+ "developer and self test builds\n");
+ return False;
+#else /* DEVELOPER || ENABLE_SELFTEST */
+
+ input = atol(argv[1]);
+ if (input < 1 || input > MAX_SLEEP) {
+ fprintf(stderr,
+ "Invalid duration for sleep '%s'\n"
+ "It should be at least 1 second and no more than %ld\n",
+ argv[1],
+ MAX_SLEEP);
+ return False;
+ }
+ seconds = input;
+ return send_message(msg_ctx, pid,
+ MSG_SMB_SLEEP,
+ &seconds,
+ sizeof(unsigned int));
+#endif /* DEVELOPER || ENABLE_SELFTEST */
+}
+
+/* Force a browser election */
+
+static bool do_election(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> force-election\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_FORCE_ELECTION, NULL, 0);
+}
+
+/* Ping a samba daemon process */
+
+static void pong_cb(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id pid,
+ DATA_BLOB *data)
+{
+ struct server_id_buf src_string;
+ printf("PONG from pid %s\n", server_id_str_buf(pid, &src_string));
+ num_replies++;
+}
+
+static bool do_ping(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> ping\n");
+ return False;
+ }
+
+ /* Send a message and register our interest in a reply */
+
+ if (!send_message(msg_ctx, pid, MSG_PING, NULL, 0))
+ return False;
+
+ messaging_register(msg_ctx, NULL, MSG_PONG, pong_cb);
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ /* No replies were received within the timeout period */
+
+ if (num_replies == 0)
+ printf("No replies received\n");
+
+ messaging_deregister(msg_ctx, MSG_PONG, NULL);
+
+ return num_replies;
+}
+
+/* Set profiling options */
+
+static bool do_profile(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ int v;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> profile "
+ "<off|count|on|flush>\n");
+ return False;
+ }
+
+ if (strcmp(argv[1], "off") == 0) {
+ v = 0;
+ } else if (strcmp(argv[1], "count") == 0) {
+ v = 1;
+ } else if (strcmp(argv[1], "on") == 0) {
+ v = 2;
+ } else if (strcmp(argv[1], "flush") == 0) {
+ v = 3;
+ } else {
+ fprintf(stderr, "Unknown profile command '%s'\n", argv[1]);
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_PROFILE, &v, sizeof(int));
+}
+
+/* Return the profiling level */
+
+static void profilelevel_cb(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id pid,
+ DATA_BLOB *data)
+{
+ int level;
+ const char *s;
+
+ num_replies++;
+
+ if (data->length != sizeof(int)) {
+ fprintf(stderr, "invalid message length %ld returned\n",
+ (unsigned long)data->length);
+ return;
+ }
+
+ memcpy(&level, data->data, sizeof(int));
+
+ switch (level) {
+ case 0:
+ s = "not enabled";
+ break;
+ case 1:
+ s = "off";
+ break;
+ case 3:
+ s = "count only";
+ break;
+ case 7:
+ s = "count and time";
+ break;
+ default:
+ s = "BOGUS";
+ break;
+ }
+
+ printf("Profiling %s on pid %u\n",s,(unsigned int)procid_to_pid(&pid));
+}
+
+static void profilelevel_rqst(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id pid,
+ DATA_BLOB *data)
+{
+ int v = 0;
+
+ /* Send back a dummy reply */
+
+ send_message(msg_ctx, pid, MSG_PROFILELEVEL, &v, sizeof(int));
+}
+
+static bool do_profilelevel(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> profilelevel\n");
+ return False;
+ }
+
+ /* Send a message and register our interest in a reply */
+
+ if (!send_message(msg_ctx, pid, MSG_REQ_PROFILELEVEL, NULL, 0))
+ return False;
+
+ messaging_register(msg_ctx, NULL, MSG_PROFILELEVEL, profilelevel_cb);
+ messaging_register(msg_ctx, NULL, MSG_REQ_PROFILELEVEL,
+ profilelevel_rqst);
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ /* No replies were received within the timeout period */
+
+ if (num_replies == 0)
+ printf("No replies received\n");
+
+ messaging_deregister(msg_ctx, MSG_PROFILE, NULL);
+
+ return num_replies;
+}
+
+/* Display debug level settings */
+
+static bool do_debuglevel(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> debuglevel\n");
+ return False;
+ }
+
+ /* Send a message and register our interest in a reply */
+
+ if (!send_message(msg_ctx, pid, MSG_REQ_DEBUGLEVEL, NULL, 0))
+ return False;
+
+ messaging_register(msg_ctx, NULL, MSG_DEBUGLEVEL, print_pid_string_cb);
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ /* No replies were received within the timeout period */
+
+ if (num_replies == 0)
+ printf("No replies received\n");
+
+ messaging_deregister(msg_ctx, MSG_DEBUGLEVEL, NULL);
+
+ return num_replies;
+}
+
+/* Send a print notify message */
+
+static bool do_printnotify(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ const char *cmd;
+
+ /* Check for subcommand */
+
+ if (argc == 1) {
+ fprintf(stderr, "Must specify subcommand:\n");
+ fprintf(stderr, "\tqueuepause <printername>\n");
+ fprintf(stderr, "\tqueueresume <printername>\n");
+ fprintf(stderr, "\tjobpause <printername> <unix jobid>\n");
+ fprintf(stderr, "\tjobresume <printername> <unix jobid>\n");
+ fprintf(stderr, "\tjobdelete <printername> <unix jobid>\n");
+ fprintf(stderr, "\tprinter <printername> <comment|port|"
+ "driver> <value>\n");
+
+ return False;
+ }
+
+ cmd = argv[1];
+
+ if (strcmp(cmd, "queuepause") == 0) {
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: smbcontrol <dest> printnotify"
+ " queuepause <printername>\n");
+ return False;
+ }
+
+ notify_printer_status_byname(ev_ctx, msg_ctx, argv[2],
+ PRINTER_STATUS_PAUSED);
+
+ goto send;
+
+ } else if (strcmp(cmd, "queueresume") == 0) {
+
+ if (argc != 3) {
+ fprintf(stderr, "Usage: smbcontrol <dest> printnotify"
+ " queuereume <printername>\n");
+ return False;
+ }
+
+ notify_printer_status_byname(ev_ctx, msg_ctx, argv[2],
+ PRINTER_STATUS_OK);
+
+ goto send;
+
+ } else if (strcmp(cmd, "jobpause") == 0) {
+ int jobid;
+
+ if (argc != 4) {
+ fprintf(stderr, "Usage: smbcontrol <dest> printnotify"
+ " jobpause <printername> <unix-jobid>\n");
+ return False;
+ }
+
+ jobid = atoi(argv[3]);
+
+ notify_job_status_byname(
+ ev_ctx, msg_ctx,
+ argv[2], jobid, JOB_STATUS_PAUSED,
+ SPOOLSS_NOTIFY_MSG_UNIX_JOBID);
+
+ goto send;
+
+ } else if (strcmp(cmd, "jobresume") == 0) {
+ int jobid;
+
+ if (argc != 4) {
+ fprintf(stderr, "Usage: smbcontrol <dest> printnotify"
+ " jobpause <printername> <unix-jobid>\n");
+ return False;
+ }
+
+ jobid = atoi(argv[3]);
+
+ notify_job_status_byname(
+ ev_ctx, msg_ctx,
+ argv[2], jobid, JOB_STATUS_QUEUED,
+ SPOOLSS_NOTIFY_MSG_UNIX_JOBID);
+
+ goto send;
+
+ } else if (strcmp(cmd, "jobdelete") == 0) {
+ int jobid;
+
+ if (argc != 4) {
+ fprintf(stderr, "Usage: smbcontrol <dest> printnotify"
+ " jobpause <printername> <unix-jobid>\n");
+ return False;
+ }
+
+ jobid = atoi(argv[3]);
+
+ notify_job_status_byname(
+ ev_ctx, msg_ctx,
+ argv[2], jobid, JOB_STATUS_DELETING,
+ SPOOLSS_NOTIFY_MSG_UNIX_JOBID);
+
+ notify_job_status_byname(
+ ev_ctx, msg_ctx,
+ argv[2], jobid, JOB_STATUS_DELETING|
+ JOB_STATUS_DELETED,
+ SPOOLSS_NOTIFY_MSG_UNIX_JOBID);
+
+ goto send;
+
+ } else if (strcmp(cmd, "printer") == 0) {
+ uint32_t attribute;
+
+ if (argc != 5) {
+ fprintf(stderr, "Usage: smbcontrol <dest> printnotify "
+ "printer <printername> <comment|port|driver> "
+ "<value>\n");
+ return False;
+ }
+
+ if (strcmp(argv[3], "comment") == 0) {
+ attribute = PRINTER_NOTIFY_FIELD_COMMENT;
+ } else if (strcmp(argv[3], "port") == 0) {
+ attribute = PRINTER_NOTIFY_FIELD_PORT_NAME;
+ } else if (strcmp(argv[3], "driver") == 0) {
+ attribute = PRINTER_NOTIFY_FIELD_DRIVER_NAME;
+ } else {
+ fprintf(stderr, "Invalid printer command '%s'\n",
+ argv[3]);
+ return False;
+ }
+
+ notify_printer_byname(ev_ctx, msg_ctx, argv[2], attribute,
+ discard_const_p(char, argv[4]));
+
+ goto send;
+ }
+
+ fprintf(stderr, "Invalid subcommand '%s'\n", cmd);
+ return False;
+
+send:
+ print_notify_send_messages(msg_ctx, 0);
+ return True;
+}
+
+/* Close a share */
+
+static bool do_closeshare(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> close-share "
+ "<sharename>\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_SMB_FORCE_TDIS, argv[1],
+ strlen(argv[1]) + 1);
+}
+
+/*
+ * Close a share if access denied by now
+ **/
+
+static bool do_close_denied_share(
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> close-denied-share "
+ "<sharename>\n");
+ return False;
+ }
+
+ return send_message(
+ msg_ctx,
+ pid,
+ MSG_SMB_FORCE_TDIS_DENIED,
+ argv[1],
+ strlen(argv[1]) + 1);
+}
+
+/* Kill a client by IP address */
+static bool do_kill_client_by_ip(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> kill-client-ip "
+ "<IP address>\n");
+ return false;
+ }
+
+ if (!is_ipaddress_v4(argv[1]) && !is_ipaddress_v6(argv[1])) {
+ fprintf(stderr, "%s is not a valid IP address!\n", argv[1]);
+ return false;
+ }
+
+ return send_message(msg_ctx, pid, MSG_SMB_KILL_CLIENT_IP,
+ argv[1], strlen(argv[1]) + 1);
+}
+
+/* Tell winbindd an IP got dropped */
+
+static bool do_ip_dropped(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> ip-dropped "
+ "<ip-address>\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_WINBIND_IP_DROPPED, argv[1],
+ strlen(argv[1]) + 1);
+}
+
+/* Display talloc pool usage */
+
+static bool do_poolusage(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id dst,
+ const int argc, const char **argv)
+{
+ pid_t pid = procid_to_pid(&dst);
+ int stdout_fd = 1;
+
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> pool-usage\n");
+ return False;
+ }
+
+ if (pid == 0) {
+ fprintf(stderr, "Can only send to a specific PID\n");
+ return false;
+ }
+
+ messaging_send_iov(
+ msg_ctx,
+ dst,
+ MSG_REQ_POOL_USAGE,
+ NULL,
+ 0,
+ &stdout_fd,
+ 1);
+
+ return true;
+}
+
+static bool do_rpc_dump_status(
+ struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id dst,
+ const int argc,
+ const char **argv)
+{
+ pid_t pid = procid_to_pid(&dst);
+ int stdout_fd = 1;
+
+ if (argc != 1) {
+ fprintf(stderr,
+ "Usage: smbcontrol <dest> rpc-dump-status\n");
+ return False;
+ }
+
+ if (pid == 0) {
+ fprintf(stderr, "Can only send to a specific PID\n");
+ return false;
+ }
+
+ messaging_send_iov(
+ msg_ctx,
+ dst,
+ MSG_RPC_DUMP_STATUS,
+ NULL,
+ 0,
+ &stdout_fd,
+ 1);
+
+ return true;
+}
+
+/* Fetch and print the ringbuf log */
+
+static void print_ringbuf_log_cb(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id pid,
+ DATA_BLOB *data)
+{
+ printf("%s", (const char *)data->data);
+ num_replies++;
+}
+
+static bool do_ringbuflog(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> ringbuf-log\n");
+ return false;
+ }
+
+ messaging_register(msg_ctx, NULL, MSG_RINGBUF_LOG,
+ print_ringbuf_log_cb);
+
+ /* Send a message and register our interest in a reply */
+
+ if (!send_message(msg_ctx, pid, MSG_REQ_RINGBUF_LOG, NULL, 0)) {
+ return false;
+ }
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ /* No replies were received within the timeout period */
+
+ if (num_replies == 0) {
+ printf("No replies received\n");
+ }
+
+ messaging_deregister(msg_ctx, MSG_RINGBUF_LOG, NULL);
+
+ return num_replies != 0;
+}
+
+/* Perform a dmalloc mark */
+
+static bool do_dmalloc_mark(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> dmalloc-mark\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_REQ_DMALLOC_MARK, NULL, 0);
+}
+
+/* Perform a dmalloc changed */
+
+static bool do_dmalloc_changed(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> "
+ "dmalloc-log-changed\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_REQ_DMALLOC_LOG_CHANGED,
+ NULL, 0);
+}
+
+static void print_uint32_cb(struct messaging_context *msg, void *private_data,
+ uint32_t msg_type, struct server_id pid,
+ DATA_BLOB *data)
+{
+ uint32_t num_children;
+
+ if (data->length != sizeof(uint32_t)) {
+ printf("Invalid response: %d bytes long\n",
+ (int)data->length);
+ goto done;
+ }
+ num_children = IVAL(data->data, 0);
+ printf("%u children\n", (unsigned)num_children);
+done:
+ num_replies++;
+}
+
+static bool do_num_children(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> num-children\n");
+ return False;
+ }
+
+ messaging_register(msg_ctx, NULL, MSG_SMB_NUM_CHILDREN,
+ print_uint32_cb);
+
+ /* Send a message and register our interest in a reply */
+
+ if (!send_message(msg_ctx, pid, MSG_SMB_TELL_NUM_CHILDREN, NULL, 0))
+ return false;
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ /* No replies were received within the timeout period */
+
+ if (num_replies == 0)
+ printf("No replies received\n");
+
+ messaging_deregister(msg_ctx, MSG_SMB_NUM_CHILDREN, NULL);
+
+ return num_replies;
+}
+
+static bool do_msg_cleanup(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ int ret;
+
+ ret = messaging_cleanup(msg_ctx, pid.pid);
+
+ printf("cleanup(%u) returned %s\n", (unsigned)pid.pid,
+ ret ? strerror(ret) : "ok");
+
+ return (ret == 0);
+}
+
+/* Shutdown a server process */
+
+static bool do_shutdown(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> shutdown\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_SHUTDOWN, NULL, 0);
+}
+
+/* Notify a driver upgrade */
+
+static bool do_drvupgrade(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> drvupgrade "
+ "<driver-name>\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_PRINTER_DRVUPGRADE, argv[1],
+ strlen(argv[1]) + 1);
+}
+
+static bool do_winbind_online(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ TDB_CONTEXT *tdb;
+ char *db_path;
+
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol winbindd online\n");
+ return False;
+ }
+
+ db_path = state_path(talloc_tos(), "winbindd_cache.tdb");
+ if (db_path == NULL) {
+ return false;
+ }
+
+ /* Remove the entry in the winbindd_cache tdb to tell a later
+ starting winbindd that we're online. */
+
+ tdb = tdb_open_log(db_path, 0, TDB_DEFAULT, O_RDWR, 0600);
+ if (!tdb) {
+ fprintf(stderr, "Cannot open the tdb %s for writing.\n",
+ db_path);
+ TALLOC_FREE(db_path);
+ return False;
+ }
+
+ TALLOC_FREE(db_path);
+ tdb_delete_bystring(tdb, "WINBINDD_OFFLINE");
+ tdb_close(tdb);
+
+ return send_message(msg_ctx, pid, MSG_WINBIND_ONLINE, NULL, 0);
+}
+
+static bool do_winbind_offline(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ TDB_CONTEXT *tdb;
+ bool ret = False;
+ int retry = 0;
+ char *db_path;
+
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol winbindd offline\n");
+ return False;
+ }
+
+ db_path = state_path(talloc_tos(), "winbindd_cache.tdb");
+ if (db_path == NULL) {
+ return false;
+ }
+
+ /* Create an entry in the winbindd_cache tdb to tell a later
+ starting winbindd that we're offline. We may actually create
+ it here... */
+
+ tdb = tdb_open_log(db_path,
+ WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE,
+ TDB_DEFAULT|TDB_INCOMPATIBLE_HASH /* TDB_CLEAR_IF_FIRST */,
+ O_RDWR|O_CREAT, 0600);
+
+ if (!tdb) {
+ fprintf(stderr, "Cannot open the tdb %s for writing.\n",
+ db_path);
+ TALLOC_FREE(db_path);
+ return False;
+ }
+ TALLOC_FREE(db_path);
+
+ /* There's a potential race condition that if a child
+ winbindd detects a domain is online at the same time
+ we're trying to tell it to go offline that it might
+ delete the record we add between us adding it and
+ sending the message. Minimize this by retrying up to
+ 5 times. */
+
+ for (retry = 0; retry < 5; retry++) {
+ uint8_t buf[4];
+ TDB_DATA d = { .dptr = buf, .dsize = sizeof(buf) };
+
+ SIVAL(buf, 0, time(NULL));
+
+ tdb_store_bystring(tdb, "WINBINDD_OFFLINE", d, TDB_INSERT);
+
+ ret = send_message(msg_ctx, pid, MSG_WINBIND_OFFLINE,
+ NULL, 0);
+
+ /* Check that the entry "WINBINDD_OFFLINE" still exists. */
+ d = tdb_fetch_bystring( tdb, "WINBINDD_OFFLINE" );
+ if (d.dptr != NULL && d.dsize == 4) {
+ SAFE_FREE(d.dptr);
+ break;
+ }
+
+ SAFE_FREE(d.dptr);
+ DEBUG(10,("do_winbind_offline: offline state not set - retrying.\n"));
+ }
+
+ tdb_close(tdb);
+ return ret;
+}
+
+static bool do_winbind_onlinestatus(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol winbindd onlinestatus\n");
+ return False;
+ }
+
+ messaging_register(msg_ctx, NULL, MSG_WINBIND_ONLINESTATUS,
+ print_pid_string_cb);
+
+ if (!send_message(msg_ctx, pid, MSG_WINBIND_ONLINESTATUS, NULL, 0)) {
+ return False;
+ }
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ /* No replies were received within the timeout period */
+
+ if (num_replies == 0)
+ printf("No replies received\n");
+
+ messaging_deregister(msg_ctx, MSG_WINBIND_ONLINESTATUS, NULL);
+
+ return num_replies;
+}
+
+static bool do_winbind_dump_domain_list(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ const char *domain = NULL;
+ int domain_len = 0;
+
+ if (argc < 1 || argc > 2) {
+ fprintf(stderr, "Usage: smbcontrol <dest> dump-domain-list "
+ "<domain>\n");
+ return false;
+ }
+
+ if (argc == 2) {
+ domain = argv[1];
+ domain_len = strlen(argv[1]) + 1;
+ }
+
+ messaging_register(msg_ctx, NULL, MSG_WINBIND_DUMP_DOMAIN_LIST,
+ print_pid_string_cb);
+
+ if (!send_message(msg_ctx, pid, MSG_WINBIND_DUMP_DOMAIN_LIST,
+ domain, domain_len))
+ {
+ return false;
+ }
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ /* No replies were received within the timeout period */
+
+ if (num_replies == 0) {
+ printf("No replies received\n");
+ }
+
+ messaging_deregister(msg_ctx, MSG_WINBIND_DUMP_DOMAIN_LIST, NULL);
+
+ return num_replies;
+}
+
+static bool do_msg_disconnect_dc(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> disconnect-dc\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_WINBIND_DISCONNECT_DC, NULL, 0);
+}
+
+static void winbind_validate_cache_cb(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id pid,
+ DATA_BLOB *data)
+{
+ struct server_id_buf src_string;
+ printf("Winbindd cache is %svalid. (answer from pid %s)\n",
+ (*(data->data) == 0 ? "" : "NOT "),
+ server_id_str_buf(pid, &src_string));
+ num_replies++;
+}
+
+static bool do_winbind_validate_cache(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ struct server_id myid;
+
+ myid = messaging_server_id(msg_ctx);
+
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol winbindd validate-cache\n");
+ return False;
+ }
+
+ messaging_register(msg_ctx, NULL, MSG_WINBIND_VALIDATE_CACHE,
+ winbind_validate_cache_cb);
+
+ if (!send_message(msg_ctx, pid, MSG_WINBIND_VALIDATE_CACHE, &myid,
+ sizeof(myid))) {
+ return False;
+ }
+
+ wait_replies(ev_ctx, msg_ctx, procid_to_pid(&pid) == 0);
+
+ if (num_replies == 0) {
+ printf("No replies received\n");
+ }
+
+ messaging_deregister(msg_ctx, MSG_WINBIND_VALIDATE_CACHE, NULL);
+
+ return num_replies;
+}
+
+static bool do_reload_certs(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol ldap_server reload-certs \n");
+ return false;
+ }
+
+ return send_message(msg_ctx, pid, MSG_RELOAD_TLS_CERTIFICATES, NULL, 0);
+}
+static bool do_reload_config(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> reload-config\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_SMB_CONF_UPDATED, NULL, 0);
+}
+
+static bool do_reload_printers(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol <dest> reload-printers\n");
+ return False;
+ }
+
+ return send_message(msg_ctx, pid, MSG_PRINTER_PCAP, NULL, 0);
+}
+
+static void my_make_nmb_name( struct nmb_name *n, const char *name, int type)
+{
+ fstring unix_name;
+ memset( (char *)n, '\0', sizeof(struct nmb_name) );
+ fstrcpy(unix_name, name);
+ (void)strupper_m(unix_name);
+ push_ascii(n->name, unix_name, sizeof(n->name), STR_TERMINATE);
+ n->name_type = (unsigned int)type & 0xFF;
+ push_ascii(n->scope, lp_netbios_scope(), 64, STR_TERMINATE);
+}
+
+static bool do_nodestatus(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ struct packet_struct p;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: smbcontrol nmbd nodestatus <ip>\n");
+ return False;
+ }
+
+ ZERO_STRUCT(p);
+
+ p.ip = interpret_addr2(argv[1]);
+ p.port = 137;
+ p.packet_type = NMB_PACKET;
+
+ p.packet.nmb.header.name_trn_id = 10;
+ p.packet.nmb.header.opcode = 0;
+ p.packet.nmb.header.response = False;
+ p.packet.nmb.header.nm_flags.bcast = False;
+ p.packet.nmb.header.nm_flags.recursion_available = False;
+ p.packet.nmb.header.nm_flags.recursion_desired = False;
+ p.packet.nmb.header.nm_flags.trunc = False;
+ p.packet.nmb.header.nm_flags.authoritative = False;
+ p.packet.nmb.header.rcode = 0;
+ p.packet.nmb.header.qdcount = 1;
+ p.packet.nmb.header.ancount = 0;
+ p.packet.nmb.header.nscount = 0;
+ p.packet.nmb.header.arcount = 0;
+ my_make_nmb_name(&p.packet.nmb.question.question_name, "*", 0x00);
+ p.packet.nmb.question.question_type = 0x21;
+ p.packet.nmb.question.question_class = 0x1;
+
+ return send_message(msg_ctx, pid, MSG_SEND_PACKET, &p, sizeof(p));
+}
+
+static bool do_notify_cleanup(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv)
+{
+ if (argc != 1) {
+ fprintf(stderr, "Usage: smbcontrol smbd notify-cleanup\n");
+ return false;
+ }
+ return send_message(msg_ctx, pid, MSG_SMB_NOTIFY_CLEANUP, NULL, 0);
+}
+
+/* A list of message type supported */
+
+static const struct {
+ const char *name; /* Option name */
+ bool (*fn)(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ const struct server_id pid,
+ const int argc, const char **argv);
+ const char *help; /* Short help text */
+} msg_types[] = {
+ {
+ .name = "debug",
+ .fn = do_debug,
+ .help = "Set debuglevel",
+ },
+ {
+ .name = "idmap",
+ .fn = do_idmap,
+ .help = "Manipulate idmap cache",
+ },
+ {
+ .name = "force-election",
+ .fn = do_election,
+ .help = "Force a browse election",
+ },
+ {
+ .name = "ping",
+ .fn = do_ping,
+ .help = "Elicit a response",
+ },
+ {
+ .name = "profile",
+ .fn = do_profile,
+ .help = "",
+ },
+ {
+ .name = "inject",
+ .fn = do_inject_fault,
+ .help = "Inject a fatal signal into a running smbd"},
+ {
+ .name = "stacktrace",
+ .fn = do_daemon_stack_trace,
+ .help = "Display a stack trace of a daemon",
+ },
+ {
+ .name = "profilelevel",
+ .fn = do_profilelevel,
+ .help = "",
+ },
+ {
+ .name = "debuglevel",
+ .fn = do_debuglevel,
+ .help = "Display current debuglevels",
+ },
+ {
+ .name = "printnotify",
+ .fn = do_printnotify,
+ .help = "Send a print notify message",
+ },
+ {
+ .name = "close-share",
+ .fn = do_closeshare,
+ .help = "Forcibly disconnect a share",
+ },
+ {
+ .name = "close-denied-share",
+ .fn = do_close_denied_share,
+ .help = "Forcibly disconnect users from shares disallowed now",
+ },
+ {
+ .name = "kill-client-ip",
+ .fn = do_kill_client_by_ip,
+ .help = "Forcibly disconnect a client with a specific IP address",
+ },
+ {
+ .name = "ip-dropped",
+ .fn = do_ip_dropped,
+ .help = "Tell winbind that an IP got dropped",
+ },
+ {
+ .name = "pool-usage",
+ .fn = do_poolusage,
+ .help = "Display talloc memory usage",
+ },
+ {
+ .name = "rpc-dump-status",
+ .fn = do_rpc_dump_status,
+ .help = "Display rpc status",
+ },
+ {
+ .name = "ringbuf-log",
+ .fn = do_ringbuflog,
+ .help = "Display ringbuf log",
+ },
+ {
+ .name = "dmalloc-mark",
+ .fn = do_dmalloc_mark,
+ .help = "",
+ },
+ {
+ .name = "dmalloc-log-changed",
+ .fn = do_dmalloc_changed,
+ .help = "",
+ },
+ {
+ .name = "shutdown",
+ .fn = do_shutdown,
+ .help = "Shut down daemon",
+ },
+ {
+ .name = "drvupgrade",
+ .fn = do_drvupgrade,
+ .help = "Notify a printer driver has changed",
+ },
+ {
+ .name = "reload-certs",
+ .fn = do_reload_certs,
+ .help = "Reload TLS certificates"
+ },
+ {
+ .name = "reload-config",
+ .fn = do_reload_config,
+ .help = "Force smbd or winbindd to reload config file"},
+ {
+ .name = "reload-printers",
+ .fn = do_reload_printers,
+ .help = "Force smbd to reload printers"},
+ {
+ .name = "nodestatus",
+ .fn = do_nodestatus,
+ .help = "Ask nmbd to do a node status request"},
+ {
+ .name = "online",
+ .fn = do_winbind_online,
+ .help = "Ask winbind to go into online state"},
+ {
+ .name = "offline",
+ .fn = do_winbind_offline,
+ .help = "Ask winbind to go into offline state"},
+ {
+ .name = "onlinestatus",
+ .fn = do_winbind_onlinestatus,
+ .help = "Request winbind online status"},
+ {
+ .name = "validate-cache" ,
+ .fn = do_winbind_validate_cache,
+ .help = "Validate winbind's credential cache",
+ },
+ {
+ .name = "dump-domain-list",
+ .fn = do_winbind_dump_domain_list,
+ .help = "Dump winbind domain list"},
+ {
+ .name = "disconnect-dc",
+ .fn = do_msg_disconnect_dc,
+ },
+ {
+ .name = "notify-cleanup",
+ .fn = do_notify_cleanup,
+ },
+ {
+ .name = "num-children",
+ .fn = do_num_children,
+ .help = "Print number of smbd child processes",
+ },
+ {
+ .name = "msg-cleanup",
+ .fn = do_msg_cleanup,
+ },
+ {
+ .name = "noop",
+ .fn = do_noop,
+ .help = "Do nothing",
+ },
+ {
+ .name = "sleep",
+ .fn = do_sleep,
+ .help = "Cause the target process to sleep",
+ },
+ { .name = NULL, },
+};
+
+/* Display usage information */
+
+static void usage(poptContext pc)
+{
+ int i;
+
+ poptPrintHelp(pc, stderr, 0);
+
+ fprintf(stderr, "\n");
+ fprintf(stderr, "<destination> is one of \"nmbd\", \"smbd\", \"winbindd\", "
+ "\"ldap_server\" or a process ID\n");
+
+ fprintf(stderr, "\n");
+ fprintf(stderr, "<message-type> is one of:\n");
+
+ for (i = 0; msg_types[i].name; i++) {
+ const char *help = msg_types[i].help;
+ if (help == NULL) {
+ help = "";
+ }
+ fprintf(stderr, "\t%-30s%s\n", msg_types[i].name, help);
+ }
+
+ fprintf(stderr, "\n");
+
+ exit(1);
+}
+
+/* Return the pid number for a string destination */
+
+static struct server_id parse_dest(struct messaging_context *msg,
+ const char *dest)
+{
+ struct server_id result = {
+ .pid = (uint64_t)-1,
+ };
+ pid_t pid;
+ struct server_id_db *names_db = NULL;
+ bool ok;
+
+ /* Zero is a special return value for broadcast to all processes */
+
+ if (strequal(dest, "all")) {
+ return interpret_pid(MSG_BROADCAST_PID_STR);
+ }
+
+ /* Try self - useful for testing */
+
+ if (strequal(dest, "self")) {
+ return messaging_server_id(msg);
+ }
+
+ /* Fix winbind typo. */
+ if (strequal(dest, "winbind")) {
+ dest = "winbindd";
+ }
+
+ /* Check for numeric pid number */
+ result = interpret_pid(dest);
+
+ /* Zero isn't valid if not "all". */
+ if (result.pid && procid_valid(&result)) {
+ return result;
+ }
+
+ /* Look up other destinations in pidfile directory */
+
+ if ((pid = pidfile_pid(lp_pid_directory(), dest)) != 0) {
+ return pid_to_procid(pid);
+ }
+
+ names_db = messaging_names_db(msg);
+ if (names_db == NULL) {
+ goto fail;
+ }
+ ok = server_id_db_lookup_one(names_db, dest, &result);
+ if (ok) {
+ return result;
+ }
+
+fail:
+ fprintf(stderr,"Can't find pid for destination '%s'\n", dest);
+
+ return result;
+}
+
+/* Execute smbcontrol command */
+
+static bool do_command(struct tevent_context *ev_ctx,
+ struct messaging_context *msg_ctx,
+ int argc, const char **argv)
+{
+ const char *dest = argv[0], *command = argv[1];
+ struct server_id pid;
+ int i;
+
+ /* Check destination */
+
+ pid = parse_dest(msg_ctx, dest);
+ if (!procid_valid(&pid)) {
+ return False;
+ }
+
+ /* Check command */
+
+ for (i = 0; msg_types[i].name; i++) {
+ if (strequal(command, msg_types[i].name))
+ return msg_types[i].fn(ev_ctx, msg_ctx, pid,
+ argc - 1, argv + 1);
+ }
+
+ fprintf(stderr, "smbcontrol: unknown command '%s'\n", command);
+
+ return False;
+}
+
+static void smbcontrol_help(poptContext pc,
+ enum poptCallbackReason preason,
+ struct poptOption * poption,
+ const char * parg,
+ void * pdata)
+{
+ if (poption->shortName != '?') {
+ poptPrintUsage(pc, stdout, 0);
+ } else {
+ usage(pc);
+ }
+
+ exit(0);
+}
+
+struct poptOption help_options[] = {
+ { NULL, '\0', POPT_ARG_CALLBACK, (void *)&smbcontrol_help, '\0',
+ NULL, NULL },
+ { "help", '?', 0, NULL, '?', "Show this help message", NULL },
+ { "usage", '\0', 0, NULL, 'u', "Display brief usage message", NULL },
+ {0}
+} ;
+
+/* Main program */
+
+int main(int argc, const char **argv)
+{
+ poptContext pc;
+ int opt;
+ struct tevent_context *evt_ctx;
+ struct messaging_context *msg_ctx;
+
+ struct poptOption long_options[] = {
+ /* POPT_AUTOHELP */
+ { NULL, '\0', POPT_ARG_INCLUDE_TABLE, help_options,
+ 0, "Help options:", NULL },
+ { "timeout", 't', POPT_ARG_INT, &timeout, 't',
+ "Set timeout value in seconds", "TIMEOUT" },
+
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+ int ret = 0;
+ bool ok;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+
+ /* Parse command line arguments using popt */
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "[OPTION...] <destination> <message-type> "
+ "<parameters>");
+
+ if (argc == 1)
+ usage(pc);
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ case 't': /* --timeout */
+ break;
+ default:
+ fprintf(stderr, "Invalid option\n");
+ poptPrintHelp(pc, stderr, 0);
+ break;
+ }
+ }
+
+ /* We should now have the remaining command line arguments in
+ argv. The argc parameter should have been decremented to the
+ correct value in the above switch statement. */
+
+ argv = (const char **)poptGetArgs(pc);
+ argc = 0;
+ if (argv != NULL) {
+ while (argv[argc] != NULL) {
+ argc++;
+ }
+ }
+
+ if (argc <= 1)
+ usage(pc);
+
+ msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
+ if (msg_ctx == NULL) {
+ fprintf(stderr,
+ "Could not init messaging context, not root?\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ evt_ctx = global_event_context();
+
+ /* Need to invert sense of return code -- samba
+ * routines mostly return True==1 for success, but
+ * shell needs 0. */
+
+ ret = !do_command(evt_ctx, msg_ctx, argc, argv);
+
+ cmdline_messaging_context_free();
+ gfree_all();
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ return ret;
+}
diff --git a/source3/utils/smbcquotas.c b/source3/utils/smbcquotas.c
new file mode 100644
index 0000000..655da7d
--- /dev/null
+++ b/source3/utils/smbcquotas.c
@@ -0,0 +1,832 @@
+/*
+ Unix SMB/CIFS implementation.
+ QUOTA get/set utility
+
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jeremy Allison 2000
+ Copyright (C) Stefan (metze) Metzmacher 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "fake_file.h"
+#include "../libcli/security/security.h"
+#include "libsmb/libsmb.h"
+#include "lib/param/param.h"
+
+static char *server;
+
+/* numeric is set when the user wants numeric SIDs and ACEs rather
+ than going via LSA calls to resolve them */
+static bool numeric;
+static bool verbose;
+
+enum todo_values {NOOP_QUOTA=0,FS_QUOTA,USER_QUOTA,LIST_QUOTA,SET_QUOTA};
+enum exit_values {EXIT_OK, EXIT_FAILED, EXIT_PARSE_ERROR};
+
+static struct cli_state *cli_ipc;
+static struct rpc_pipe_client *global_pipe_hnd;
+static struct policy_handle pol;
+static bool got_policy_hnd;
+
+static struct cli_state *connect_one(const char *share);
+
+/* Open cli connection and policy handle */
+
+static bool cli_open_policy_hnd(void)
+{
+ /* Initialise cli LSA connection */
+
+ if (!cli_ipc) {
+ NTSTATUS ret;
+ cli_ipc = connect_one("IPC$");
+ ret = cli_rpc_pipe_open_noauth(cli_ipc,
+ &ndr_table_lsarpc,
+ &global_pipe_hnd);
+ if (!NT_STATUS_IS_OK(ret)) {
+ return False;
+ }
+ }
+
+ /* Open policy handle */
+
+ if (!got_policy_hnd) {
+
+ /* Some systems don't support SEC_FLAG_MAXIMUM_ALLOWED,
+ but NT sends 0x2000000 so we might as well do it too. */
+
+ if (!NT_STATUS_IS_OK(rpccli_lsa_open_policy(global_pipe_hnd, talloc_tos(), True,
+ GENERIC_EXECUTE_ACCESS, &pol))) {
+ return False;
+ }
+
+ got_policy_hnd = True;
+ }
+
+ return True;
+}
+
+/* convert a SID to a string, either numeric or username/group */
+static void SidToString(fstring str, struct dom_sid *sid, bool _numeric)
+{
+ char **domains = NULL;
+ char **names = NULL;
+ enum lsa_SidType *types = NULL;
+
+ sid_to_fstring(str, sid);
+
+ if (_numeric) return;
+
+ /* Ask LSA to convert the sid to a name */
+
+ if (!cli_open_policy_hnd() ||
+ !NT_STATUS_IS_OK(rpccli_lsa_lookup_sids(global_pipe_hnd, talloc_tos(),
+ &pol, 1, sid, &domains,
+ &names, &types)) ||
+ !domains || !domains[0] || !names || !names[0]) {
+ return;
+ }
+
+ /* Converted OK */
+
+ slprintf(str, sizeof(fstring) - 1, "%s%s%s",
+ domains[0], lp_winbind_separator(),
+ names[0]);
+}
+
+/* convert a string to a SID, either numeric or username/group */
+static bool StringToSid(struct dom_sid *sid, const char *str)
+{
+ enum lsa_SidType *types = NULL;
+ struct dom_sid *sids = NULL;
+ bool result = True;
+
+ if (string_to_sid(sid, str)) {
+ return true;
+ }
+
+ if (!cli_open_policy_hnd() ||
+ !NT_STATUS_IS_OK(rpccli_lsa_lookup_names(global_pipe_hnd, talloc_tos(),
+ &pol, 1, &str, NULL, 1, &sids,
+ &types))) {
+ result = False;
+ goto done;
+ }
+
+ sid_copy(sid, &sids[0]);
+ done:
+
+ return result;
+}
+
+#define QUOTA_GET 1
+#define QUOTA_SETLIM 2
+#define QUOTA_SETFLAGS 3
+#define QUOTA_LIST 4
+
+enum {PARSE_FLAGS,PARSE_LIM};
+
+static int parse_quota_set(TALLOC_CTX *ctx,
+ char *set_str,
+ char **pp_username_str,
+ enum SMB_QUOTA_TYPE *qtype,
+ int *cmd,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ char *p = set_str,*p2;
+ int todo;
+ bool stop = False;
+ bool enable = False;
+ bool deny = False;
+
+ *pp_username_str = NULL;
+ if (strnequal(set_str,"UQLIM:",6)) {
+ p += 6;
+ *qtype = SMB_USER_QUOTA_TYPE;
+ *cmd = QUOTA_SETLIM;
+ todo = PARSE_LIM;
+ if ((p2=strstr(p,":"))==NULL) {
+ return -1;
+ }
+
+ *p2 = '\0';
+ p2++;
+
+ *pp_username_str = talloc_strdup(ctx, p);
+ p = p2;
+ } else if (strnequal(set_str,"FSQLIM:",7)) {
+ p +=7;
+ *qtype = SMB_USER_FS_QUOTA_TYPE;
+ *cmd = QUOTA_SETLIM;
+ todo = PARSE_LIM;
+ } else if (strnequal(set_str,"FSQFLAGS:",9)) {
+ p +=9;
+ todo = PARSE_FLAGS;
+ *qtype = SMB_USER_FS_QUOTA_TYPE;
+ *cmd = QUOTA_SETFLAGS;
+ } else {
+ return -1;
+ }
+
+ switch (todo) {
+ case PARSE_LIM:
+ if (sscanf(p,"%"SCNu64"/%"SCNu64,&pqt->softlim,
+ &pqt->hardlim) != 2)
+ {
+ return -1;
+ }
+
+ break;
+ case PARSE_FLAGS:
+ while (!stop) {
+
+ if ((p2=strstr(p,"/"))==NULL) {
+ stop = True;
+ } else {
+ *p2 = '\0';
+ p2++;
+ }
+
+ if (strnequal(p,"QUOTA_ENABLED",13)) {
+ enable = True;
+ } else if (strnequal(p,"DENY_DISK",9)) {
+ deny = True;
+ } else if (strnequal(p,"LOG_SOFTLIMIT",13)) {
+ pqt->qflags |= QUOTAS_LOG_THRESHOLD;
+ } else if (strnequal(p,"LOG_HARDLIMIT",13)) {
+ pqt->qflags |= QUOTAS_LOG_LIMIT;
+ } else {
+ return -1;
+ }
+
+ p=p2;
+ }
+
+ if (deny) {
+ pqt->qflags |= QUOTAS_DENY_DISK;
+ } else if (enable) {
+ pqt->qflags |= QUOTAS_ENABLED;
+ }
+
+ break;
+ }
+
+ return 0;
+}
+
+
+static const char *quota_str_static(uint64_t val, bool special, bool _numeric)
+{
+ const char *result;
+
+ if (!_numeric && special && val == 0) {
+ return "NO LIMIT";
+ }
+ result = talloc_asprintf(talloc_tos(), "%"PRIu64, val);
+ SMB_ASSERT(result != NULL);
+ return result;
+}
+
+static void dump_ntquota(SMB_NTQUOTA_STRUCT *qt, bool _verbose,
+ bool _numeric,
+ void (*_sidtostring)(fstring str,
+ struct dom_sid *sid,
+ bool _numeric))
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (!qt) {
+ smb_panic("dump_ntquota() called with NULL pointer");
+ }
+
+ switch (qt->qtype) {
+ case SMB_USER_FS_QUOTA_TYPE:
+ {
+ d_printf("File System QUOTAS:\n");
+ d_printf("Limits:\n");
+ d_printf(" Default Soft Limit: %15s\n",
+ quota_str_static(qt->softlim,True,_numeric));
+ d_printf(" Default Hard Limit: %15s\n",
+ quota_str_static(qt->hardlim,True,_numeric));
+ d_printf("Quota Flags:\n");
+ d_printf(" Quotas Enabled: %s\n",
+ ((qt->qflags&QUOTAS_ENABLED)
+ ||(qt->qflags&QUOTAS_DENY_DISK))?"On":"Off");
+ d_printf(" Deny Disk: %s\n",
+ (qt->qflags&QUOTAS_DENY_DISK)?"On":"Off");
+ d_printf(" Log Soft Limit: %s\n",
+ (qt->qflags&QUOTAS_LOG_THRESHOLD)?"On":"Off");
+ d_printf(" Log Hard Limit: %s\n",
+ (qt->qflags&QUOTAS_LOG_LIMIT)?"On":"Off");
+ }
+ break;
+ case SMB_USER_QUOTA_TYPE:
+ {
+ fstring username_str = {0};
+
+ if (_sidtostring) {
+ _sidtostring(username_str,&qt->sid,_numeric);
+ } else {
+ sid_to_fstring(username_str, &qt->sid);
+ }
+
+ if (_verbose) {
+ d_printf("Quotas for User: %s\n",username_str);
+ d_printf("Used Space: %15s\n",
+ quota_str_static(qt->usedspace,False,
+ _numeric));
+ d_printf("Soft Limit: %15s\n",
+ quota_str_static(qt->softlim,True,
+ _numeric));
+ d_printf("Hard Limit: %15s\n",
+ quota_str_static(qt->hardlim,True,_numeric));
+ } else {
+ d_printf("%-30s: ",username_str);
+ d_printf("%15s/",quota_str_static(
+ qt->usedspace,False,_numeric));
+ d_printf("%15s/",quota_str_static(
+ qt->softlim,True,_numeric));
+ d_printf("%15s\n",quota_str_static(
+ qt->hardlim,True,_numeric));
+ }
+ }
+ break;
+ default:
+ d_printf("dump_ntquota() invalid qtype(%d)\n",qt->qtype);
+ }
+ TALLOC_FREE(frame);
+ return;
+}
+
+static void dump_ntquota_list(SMB_NTQUOTA_LIST **qtl, bool _verbose,
+ bool _numeric,
+ void (*_sidtostring)(fstring str,
+ struct dom_sid *sid,
+ bool _numeric))
+{
+ SMB_NTQUOTA_LIST *cur;
+
+ for (cur = *qtl;cur;cur = cur->next) {
+ if (cur->quotas)
+ dump_ntquota(cur->quotas,_verbose,_numeric,
+ _sidtostring);
+ }
+}
+
+static int do_quota(struct cli_state *cli,
+ enum SMB_QUOTA_TYPE qtype,
+ uint16_t cmd,
+ const char *username_str,
+ SMB_NTQUOTA_STRUCT *pqt)
+{
+ uint32_t fs_attrs = 0;
+ uint16_t quota_fnum = 0;
+ SMB_NTQUOTA_LIST *qtl = NULL;
+ TALLOC_CTX *qtl_ctx = NULL;
+ SMB_NTQUOTA_STRUCT qt;
+ NTSTATUS status;
+
+ ZERO_STRUCT(qt);
+
+ status = cli_get_fs_attr_info(cli, &fs_attrs);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Failed to get the filesystem attributes %s.\n",
+ nt_errstr(status));
+ return -1;
+ }
+
+ if (!(fs_attrs & FILE_VOLUME_QUOTAS)) {
+ d_printf("Quotas are not supported by the server.\n");
+ return 0;
+ }
+
+ status = cli_get_quota_handle(cli, &quota_fnum);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("Quotas are not enabled on this share.\n");
+ d_printf("Failed to open %s %s.\n",
+ FAKE_FILE_NAME_QUOTA_WIN32,
+ nt_errstr(status));
+ return -1;
+ }
+
+ switch(qtype) {
+ case SMB_USER_QUOTA_TYPE:
+ if (!StringToSid(&qt.sid, username_str)) {
+ d_printf("StringToSid() failed for [%s]\n",username_str);
+ return -1;
+ }
+
+ switch(cmd) {
+ case QUOTA_GET:
+ status = cli_get_user_quota(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_get_user_quota %s\n",
+ nt_errstr(status),
+ username_str);
+ return -1;
+ }
+ dump_ntquota(&qt,verbose,numeric,SidToString);
+ break;
+ case QUOTA_SETLIM:
+ pqt->sid = qt.sid;
+ if ((qtl_ctx = talloc_init(
+ "SMB_USER_QUOTA_SET")) ==
+ NULL) {
+ return -1;
+ }
+
+ if (!add_record_to_ntquota_list(
+ qtl_ctx, pqt, &qtl)) {
+ TALLOC_FREE(qtl_ctx);
+ return -1;
+ }
+
+ status = cli_set_user_quota(
+ cli, quota_fnum, qtl);
+ free_ntquota_list(&qtl);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_set_user_quota %s\n",
+ nt_errstr(status),
+ username_str);
+ return -1;
+ }
+ status = cli_get_user_quota(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_get_user_quota %s\n",
+ nt_errstr(status),
+ username_str);
+ return -1;
+ }
+ dump_ntquota(&qt,verbose,numeric,SidToString);
+ break;
+ case QUOTA_LIST:
+ status = cli_list_user_quota(
+ cli, quota_fnum, &qtl);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf(
+ "%s cli_list_user_quota\n",
+ nt_errstr(status));
+ return -1;
+ }
+ dump_ntquota_list(&qtl,verbose,numeric,SidToString);
+ free_ntquota_list(&qtl);
+ break;
+ default:
+ d_printf("Unknown Error\n");
+ return -1;
+ }
+ break;
+ case SMB_USER_FS_QUOTA_TYPE:
+ switch(cmd) {
+ case QUOTA_GET:
+ status = cli_get_fs_quota_info(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_get_fs_quota_info\n",
+ nt_errstr(status));
+ return -1;
+ }
+ dump_ntquota(&qt,True,numeric,NULL);
+ break;
+ case QUOTA_SETLIM:
+ status = cli_get_fs_quota_info(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_get_fs_quota_info\n",
+ nt_errstr(status));
+ return -1;
+ }
+ qt.softlim = pqt->softlim;
+ qt.hardlim = pqt->hardlim;
+ status = cli_set_fs_quota_info(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_set_fs_quota_info\n",
+ nt_errstr(status));
+ return -1;
+ }
+ status = cli_get_fs_quota_info(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_get_fs_quota_info\n",
+ nt_errstr(status));
+ return -1;
+ }
+ dump_ntquota(&qt,True,numeric,NULL);
+ break;
+ case QUOTA_SETFLAGS:
+ status = cli_get_fs_quota_info(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_get_fs_quota_info\n",
+ nt_errstr(status));
+ return -1;
+ }
+ qt.qflags = pqt->qflags;
+ status = cli_set_fs_quota_info(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_set_fs_quota_info\n",
+ nt_errstr(status));
+ return -1;
+ }
+ status = cli_get_fs_quota_info(
+ cli, quota_fnum, &qt);
+ if (!NT_STATUS_IS_OK(status)) {
+ d_printf("%s cli_get_fs_quota_info\n",
+ nt_errstr(status));
+ return -1;
+ }
+ dump_ntquota(&qt,True,numeric,NULL);
+ break;
+ default:
+ d_printf("Unknown Error\n");
+ return -1;
+ }
+ break;
+ default:
+ d_printf("Unknown Error\n");
+ return -1;
+ }
+
+ cli_close(cli, quota_fnum);
+
+ return 0;
+}
+
+/*****************************************************
+ Return a connection to a server.
+*******************************************************/
+
+static struct cli_state *connect_one(const char *share)
+{
+ struct cli_state *c;
+ NTSTATUS nt_status;
+ uint32_t flags = 0;
+
+ nt_status = cli_full_connection_creds(&c, lp_netbios_name(), server,
+ NULL, 0,
+ share, "?????",
+ samba_cmdline_get_creds(),
+ flags);
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ DEBUG(0,("cli_full_connection failed! (%s)\n", nt_errstr(nt_status)));
+ return NULL;
+ }
+
+ return c;
+}
+
+/****************************************************************************
+ main program
+****************************************************************************/
+int main(int argc, char *argv[])
+{
+ const char **argv_const = discard_const_p(const char *, argv);
+ char *share;
+ int opt;
+ int result;
+ int todo = 0;
+ char *username_str = NULL;
+ char *path = NULL;
+ char *set_str = NULL;
+ enum SMB_QUOTA_TYPE qtype = SMB_INVALID_QUOTA_TYPE;
+ int cmd = 0;
+ static bool test_args = False;
+ struct cli_state *cli;
+ bool fix_user = False;
+ SMB_NTQUOTA_STRUCT qt;
+ TALLOC_CTX *frame = talloc_stackframe();
+ poptContext pc;
+ struct cli_credentials *creds = NULL;
+ bool ok;
+ struct loadparm_context *lp_ctx = NULL;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "quota-user",
+ .shortName = 'u',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'u',
+ .descrip = "Show quotas for user",
+ .argDescrip = "USER",
+ },
+ {
+ .longName = "list",
+ .shortName = 'L',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'L',
+ .descrip = "List user quotas",
+ },
+ {
+ .longName = "fs",
+ .shortName = 'F',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'F',
+ .descrip = "Show filesystem quotas",
+ },
+ {
+ .longName = "set",
+ .shortName = 'S',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'S',
+ .descrip = "Set acls\n"
+ "SETSTRING:\n"
+ "UQLIM:<username>/<softlimit>/<hardlimit> for user quotas\n"
+ "FSQLIM:<softlimit>/<hardlimit> for filesystem defaults\n"
+ "FSQFLAGS:QUOTA_ENABLED/DENY_DISK/LOG_SOFTLIMIT/LOG_HARD_LIMIT",
+ .argDescrip = "SETSTRING",
+ },
+ {
+ .longName = "numeric",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'n',
+ .descrip = "Don't resolve sids or limits to names",
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'v',
+ .descrip = "be verbose",
+ },
+ {
+ .longName = "test-args",
+ .shortName = 't',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 't',
+ .descrip = "Test arguments"
+ },
+ {
+ .longName = "max-protocol",
+ .shortName = 'm',
+ .argInfo = POPT_ARG_STRING,
+ .arg = NULL,
+ .val = 'm',
+ .descrip = "Set the max protocol level",
+ .argDescrip = "LEVEL"
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S3
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ smb_init_locale();
+
+ ZERO_STRUCT(qt);
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ /* set default debug level to 1 regardless of what smb.conf sets */
+ lpcfg_set_cmdline(lp_ctx, "log level", "1");
+
+ setlinebuf(stdout);
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv_const,
+ long_options, 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ poptSetOtherOptionHelp(pc, "//server1/share1");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'n':
+ numeric = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 't':
+ test_args = true;
+ break;
+ case 'L':
+ if (todo != 0) {
+ d_printf("Please specify only one option of <-L|-F|-S|-u>\n");
+ exit(EXIT_PARSE_ERROR);
+ }
+ todo = LIST_QUOTA;
+ break;
+
+ case 'F':
+ if (todo != 0) {
+ d_printf("Please specify only one option of <-L|-F|-S|-u>\n");
+ exit(EXIT_PARSE_ERROR);
+ }
+ todo = FS_QUOTA;
+ break;
+
+ case 'u':
+ if (todo != 0) {
+ d_printf("Please specify only one option of <-L|-F|-S|-u>\n");
+ exit(EXIT_PARSE_ERROR);
+ }
+ username_str = talloc_strdup(frame, poptGetOptArg(pc));
+ if (!username_str) {
+ exit(EXIT_PARSE_ERROR);
+ }
+ todo = USER_QUOTA;
+ fix_user = True;
+ break;
+
+ case 'S':
+ if (todo != 0) {
+ d_printf("Please specify only one option of <-L|-F|-S|-u>\n");
+ exit(EXIT_PARSE_ERROR);
+ }
+ set_str = talloc_strdup(frame, poptGetOptArg(pc));
+ if (!set_str) {
+ exit(EXIT_PARSE_ERROR);
+ }
+ todo = SET_QUOTA;
+ break;
+ case 'm':
+ lpcfg_set_cmdline(lp_ctx,
+ "client max protocol",
+ poptGetOptArg(pc));
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ creds = samba_cmdline_get_creds();
+
+ if (todo == 0)
+ todo = USER_QUOTA;
+
+ if (!fix_user) {
+ const char *user = cli_credentials_get_username(creds);
+ if (user == NULL) {
+ exit(EXIT_PARSE_ERROR);
+ }
+
+ username_str = talloc_strdup(frame, user);
+ if (!username_str) {
+ exit(EXIT_PARSE_ERROR);
+ }
+ }
+
+ /* Make connection to server */
+ if(!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ exit(EXIT_PARSE_ERROR);
+ }
+
+ path = talloc_strdup(frame, poptGetArg(pc));
+ if (!path) {
+ printf("Out of memory\n");
+ exit(EXIT_PARSE_ERROR);
+ }
+
+ if (strncmp(path, "\\\\", 2) && strncmp(path, "//", 2)) {
+ printf("Invalid argument: %s\n", path);
+ return -1;
+ }
+
+ poptFreeContext(pc);
+ samba_cmdline_burn(argc, argv);
+
+ string_replace(path, '/', '\\');
+
+ server = SMB_STRDUP(path+2);
+ if (!server) {
+ printf("Out of memory\n");
+ exit(EXIT_PARSE_ERROR);
+ }
+ share = strchr_m(server,'\\');
+ if (share == NULL) {
+ printf("Invalid argument\n");
+ exit(EXIT_PARSE_ERROR);
+ }
+
+ *share = 0;
+ share++;
+
+ if (todo == SET_QUOTA) {
+ if (parse_quota_set(talloc_tos(), set_str, &username_str, &qtype, &cmd, &qt)) {
+ printf("Invalid argument: -S %s\n", set_str);
+ exit(EXIT_PARSE_ERROR);
+ }
+ }
+
+ if (!test_args) {
+ cli = connect_one(share);
+ if (!cli) {
+ exit(EXIT_FAILED);
+ }
+ } else {
+ exit(EXIT_OK);
+ }
+
+
+ /* Perform requested action */
+
+ switch (todo) {
+ case FS_QUOTA:
+ result = do_quota(cli,SMB_USER_FS_QUOTA_TYPE, QUOTA_GET, username_str, NULL);
+ break;
+ case LIST_QUOTA:
+ result = do_quota(cli,SMB_USER_QUOTA_TYPE, QUOTA_LIST, username_str, NULL);
+ break;
+ case USER_QUOTA:
+ result = do_quota(cli,SMB_USER_QUOTA_TYPE, QUOTA_GET, username_str, NULL);
+ break;
+ case SET_QUOTA:
+ result = do_quota(cli, qtype, cmd, username_str, &qt);
+ break;
+ default:
+ result = EXIT_FAILED;
+ break;
+ }
+
+ gfree_all();
+ talloc_free(frame);
+
+ return result;
+}
diff --git a/source3/utils/smbfilter.c b/source3/utils/smbfilter.c
new file mode 100644
index 0000000..ebf2809
--- /dev/null
+++ b/source3/utils/smbfilter.c
@@ -0,0 +1,356 @@
+/*
+ Unix SMB/CIFS implementation.
+ SMB filter/socket plugin
+ Copyright (C) Andrew Tridgell 1999
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "system/select.h"
+#include "libsmb/namequery.h"
+#include "../lib/util/select.h"
+#include "libsmb/nmblib.h"
+#include "lib/util/sys_rw_data.h"
+
+#define SECURITY_MASK 0
+#define SECURITY_SET 0
+
+/* this forces non-unicode */
+#define CAPABILITY_MASK 0
+#define CAPABILITY_SET 0
+
+/* and non-unicode for the client too */
+#define CLI_CAPABILITY_MASK 0
+#define CLI_CAPABILITY_SET 0
+
+static char *netbiosname;
+
+static void save_file(const char *fname, void *ppacket, size_t length)
+{
+ int fd;
+ fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+ if (fd == -1) {
+ perror(fname);
+ return;
+ }
+ if (write(fd, ppacket, length) != length) {
+ fprintf(stderr,"Failed to write %s\n", fname);
+ close(fd);
+ return;
+ }
+ close(fd);
+ printf("Wrote %ld bytes to %s\n", (unsigned long)length, fname);
+}
+
+static void filter_reply(char *buf)
+{
+ int msg_type = CVAL(buf,0);
+ int type = CVAL(buf,smb_com);
+ unsigned x;
+
+ if (msg_type) return;
+
+ switch (type) {
+
+ case SMBnegprot:
+ /* force the security bits */
+ x = CVAL(buf, smb_vwv1);
+ x = (x | SECURITY_SET) & ~SECURITY_MASK;
+ SCVAL(buf, smb_vwv1, x);
+
+ /* force the capabilities */
+ x = IVAL(buf,smb_vwv9+1);
+ x = (x | CAPABILITY_SET) & ~CAPABILITY_MASK;
+ SIVAL(buf, smb_vwv9+1, x);
+ break;
+
+ }
+}
+
+static void filter_request(char *buf, size_t buf_len)
+{
+ int msg_type = CVAL(buf,0);
+ int type = CVAL(buf,smb_com);
+ unsigned x;
+ fstring name1,name2;
+ int name_len1 = 0;
+ int name_len2;
+ int name_type1, name_type2;
+ int ret;
+
+ if (msg_type) {
+ /* it's a netbios special */
+ switch (msg_type)
+ case 0x81:
+ /* session request */
+ /* inbuf_size is guaranteed to be at least 4. */
+ name_len1 = name_len((unsigned char *)(buf+4),
+ buf_len - 4);
+ if (name_len1 <= 0 || name_len1 > buf_len - 4) {
+ DEBUG(0,("Invalid name length in session request\n"));
+ return;
+ }
+ name_len2 = name_len((unsigned char *)(buf+4+name_len1),
+ buf_len - 4 - name_len1);
+ if (name_len2 <= 0 || name_len2 > buf_len - 4 - name_len1) {
+ DEBUG(0,("Invalid name length in session request\n"));
+ return;
+ }
+
+ name_type1 = name_extract((unsigned char *)buf,
+ buf_len,(unsigned int)4,name1);
+ name_type2 = name_extract((unsigned char *)buf,
+ buf_len,(unsigned int)(4 + name_len1),name2);
+
+ if (name_type1 == -1 || name_type2 == -1) {
+ DEBUG(0,("Invalid name type in session request\n"));
+ return;
+ }
+
+ d_printf("session_request: %s -> %s\n",
+ name1, name2);
+ if (netbiosname) {
+ char *mangled = name_mangle(
+ talloc_tos(), netbiosname, 0x20);
+ if (mangled != NULL) {
+ /* replace the destination netbios
+ * name */
+ memcpy(buf+4, mangled,
+ name_len((unsigned char *)mangled,
+ talloc_get_size(mangled)));
+ TALLOC_FREE(mangled);
+ }
+ }
+ return;
+ }
+
+ /* it's an ordinary SMB request */
+ switch (type) {
+ case SMBsesssetupX:
+ /* force the client capabilities */
+ x = IVAL(buf,smb_vwv11);
+ d_printf("SMBsesssetupX cap=0x%08x\n", x);
+ d_printf("pwlen=%d/%d\n", SVAL(buf, smb_vwv7), SVAL(buf, smb_vwv8));
+ ret = system("mv sessionsetup.dat sessionsetup1.dat");
+ if (ret == -1) {
+ DBG_ERR("failed to call mv command\n");
+ }
+ save_file("sessionsetup.dat", smb_buf(buf), SVAL(buf, smb_vwv7));
+ x = (x | CLI_CAPABILITY_SET) & ~CLI_CAPABILITY_MASK;
+ SIVAL(buf, smb_vwv11, x);
+ break;
+ }
+}
+
+/****************************************************************************
+ Send an smb to a fd.
+****************************************************************************/
+
+static bool send_smb(int fd, char *buffer)
+{
+ size_t len;
+ size_t nwritten=0;
+ ssize_t ret;
+
+ len = smb_len(buffer) + 4;
+
+ while (nwritten < len) {
+ ret = write_data(fd,buffer+nwritten,len - nwritten);
+ if (ret <= 0) {
+ DEBUG(0,("Error writing %d bytes to client. %d. (%s)\n",
+ (int)len,(int)ret, strerror(errno) ));
+ return false;
+ }
+ nwritten += ret;
+ }
+
+ return true;
+}
+
+static void filter_child(int c, struct sockaddr_storage *dest_ss)
+{
+ NTSTATUS status;
+ int s = -1;
+ char packet[128*1024];
+
+ /* we have a connection from a new client, now connect to the server */
+ status = open_socket_out(dest_ss, TCP_SMB_PORT, LONG_CONNECT_TIMEOUT, &s);
+ if (!NT_STATUS_IS_OK(status)) {
+ char addr[INET6_ADDRSTRLEN];
+ if (dest_ss) {
+ print_sockaddr(addr, sizeof(addr), dest_ss);
+ }
+
+ d_printf("Unable to connect to %s (%s)\n",
+ dest_ss?addr:"NULL", nt_errstr(status));
+ exit(1);
+ }
+
+ while (c != -1 || s != -1) {
+ struct pollfd fds[2];
+ int num_fds, ret;
+
+ memset(fds, 0, sizeof(struct pollfd) * 2);
+ fds[0].fd = -1;
+ fds[1].fd = -1;
+ num_fds = 0;
+
+ if (s != -1) {
+ fds[num_fds].fd = s;
+ fds[num_fds].events = POLLIN|POLLHUP;
+ num_fds += 1;
+ }
+ if (c != -1) {
+ fds[num_fds].fd = c;
+ fds[num_fds].events = POLLIN|POLLHUP;
+ num_fds += 1;
+ }
+
+ ret = sys_poll_intr(fds, num_fds, -1);
+ if (ret <= 0) {
+ continue;
+ }
+
+ /*
+ * find c in fds and see if it's readable
+ */
+ if ((c != -1) &&
+ (((fds[0].fd == c)
+ && (fds[0].revents & (POLLIN|POLLHUP|POLLERR))) ||
+ ((fds[1].fd == c)
+ && (fds[1].revents & (POLLIN|POLLHUP|POLLERR))))) {
+ size_t len;
+ if (!NT_STATUS_IS_OK(receive_smb_raw(
+ c, packet, sizeof(packet),
+ 0, 0, &len))) {
+ d_printf("client closed connection\n");
+ exit(0);
+ }
+ filter_request(packet, len);
+ if (!send_smb(s, packet)) {
+ d_printf("server is dead\n");
+ exit(1);
+ }
+ }
+
+ /*
+ * find s in fds and see if it's readable
+ */
+ if ((s != -1) &&
+ (((fds[0].fd == s)
+ && (fds[0].revents & (POLLIN|POLLHUP|POLLERR))) ||
+ ((fds[1].fd == s)
+ && (fds[1].revents & (POLLIN|POLLHUP|POLLERR))))) {
+ size_t len;
+ if (!NT_STATUS_IS_OK(receive_smb_raw(
+ s, packet, sizeof(packet),
+ 0, 0, &len))) {
+ d_printf("server closed connection\n");
+ exit(0);
+ }
+ filter_reply(packet);
+ if (!send_smb(c, packet)) {
+ d_printf("client is dead\n");
+ exit(1);
+ }
+ }
+ }
+ d_printf("Connection closed\n");
+ exit(0);
+}
+
+
+static void start_filter(char *desthost)
+{
+ int s, c;
+ struct sockaddr_storage dest_ss;
+ struct sockaddr_storage my_ss;
+
+ CatchChild();
+
+ /* start listening on port 445 locally */
+
+ zero_sockaddr(&my_ss);
+ s = open_socket_in(SOCK_STREAM, &my_ss, TCP_SMB_PORT, true);
+
+ if (s < 0) {
+ d_printf("bind failed: %s\n", strerror(-s));
+ exit(1);
+ }
+
+ if (listen(s, 5) == -1) {
+ d_printf("listen failed\n");
+ }
+
+ if (!resolve_name(desthost, &dest_ss, 0x20, false)) {
+ d_printf("Unable to resolve host %s\n", desthost);
+ exit(1);
+ }
+
+ while (1) {
+ int num, revents;
+ struct sockaddr_storage ss;
+ socklen_t in_addrlen = sizeof(ss);
+
+ num = poll_intr_one_fd(s, POLLIN|POLLHUP, -1, &revents);
+ if ((num > 0) && (revents & (POLLIN|POLLHUP|POLLERR))) {
+ c = accept(s, (struct sockaddr *)&ss, &in_addrlen);
+ if (c != -1) {
+ smb_set_close_on_exec(c);
+ if (fork() == 0) {
+ close(s);
+ filter_child(c, &dest_ss);
+ exit(0);
+ } else {
+ close(c);
+ }
+ }
+ }
+ }
+}
+
+
+int main(int argc, char *argv[])
+{
+ char *desthost;
+ const char *configfile;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ smb_init_locale();
+
+ setup_logging(argv[0], DEBUG_STDOUT);
+
+ configfile = get_dyn_CONFIGFILE();
+
+ if (argc < 2) {
+ fprintf(stderr,"smbfilter <desthost> <netbiosname>\n");
+ exit(1);
+ }
+
+ desthost = argv[1];
+ if (argc > 2) {
+ netbiosname = argv[2];
+ }
+
+ if (!lp_load_global(configfile)) {
+ d_printf("Unable to load config file\n");
+ }
+
+ start_filter(desthost);
+ gfree_all();
+ TALLOC_FREE(frame);
+ return 0;
+}
diff --git a/source3/utils/smbget.c b/source3/utils/smbget.c
new file mode 100644
index 0000000..67ea259
--- /dev/null
+++ b/source3/utils/smbget.c
@@ -0,0 +1,1073 @@
+/*
+ smbget: a wget-like utility with support for recursive downloading of
+ smb:// urls
+ Copyright (C) 2003-2004 Jelmer Vernooij <jelmer@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/param/param.h"
+#include "libsmbclient.h"
+#include "cmdline_contexts.h"
+#include "auth/credentials/credentials.h"
+#include "auth/gensec/gensec.h"
+
+static int columns = 0;
+
+static time_t total_start_time = 0;
+static off_t total_bytes = 0;
+
+#define SMB_MAXPATHLEN MAXPATHLEN
+
+/*
+ * Number of bytes to read when checking whether local and remote file
+ * are really the same file
+ */
+#define RESUME_CHECK_SIZE 512
+#define RESUME_DOWNLOAD_OFFSET 1024
+#define RESUME_CHECK_OFFSET (RESUME_DOWNLOAD_OFFSET+RESUME_CHECK_SIZE)
+/* Number of bytes to read at once */
+#define SMB_DEFAULT_BLOCKSIZE 64000
+
+struct opt {
+ char *outputfile;
+ size_t blocksize;
+
+ int quiet;
+ int dots;
+ int verbose;
+ int send_stdout;
+ int update;
+ unsigned limit_rate;
+};
+static struct opt opt = { .blocksize = SMB_DEFAULT_BLOCKSIZE };
+
+static bool smb_download_file(const char *base, const char *name,
+ bool recursive, bool resume, bool toplevel,
+ char *outfile);
+
+static int get_num_cols(void)
+{
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) < 0) {
+ return 0;
+ }
+ return ws.ws_col;
+#else
+#warning No support for TIOCGWINSZ
+ char *cols = getenv("COLUMNS");
+ if (!cols) {
+ return 0;
+ }
+ return atoi(cols);
+#endif
+}
+
+static void change_columns(int sig)
+{
+ columns = get_num_cols();
+}
+
+static void human_readable(off_t s, char *buffer, int l)
+{
+ if (s > 1024 * 1024 * 1024) {
+ snprintf(buffer, l, "%.2fGB", 1.0 * s / (1024 * 1024 * 1024));
+ } else if (s > 1024 * 1024) {
+ snprintf(buffer, l, "%.2fMB", 1.0 * s / (1024 * 1024));
+ } else if (s > 1024) {
+ snprintf(buffer, l, "%.2fkB", 1.0 * s / 1024);
+ } else {
+ snprintf(buffer, l, "%jdb", (intmax_t)s);
+ }
+}
+
+/*
+ * Authentication callback for libsmbclient.
+ *
+ * The command line parser will take care asking for a password interactively!
+ */
+static void get_auth_data_with_context_fn(SMBCCTX *ctx,
+ const char *srv,
+ const char *shr,
+ char *dom,
+ int dom_len,
+ char *usr,
+ int usr_len,
+ char *pwd,
+ int pwd_len)
+{
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ const char *username = NULL;
+ const char *password = NULL;
+ const char *domain = NULL;
+ enum credentials_obtained obtained = CRED_UNINITIALISED;
+
+ domain = cli_credentials_get_domain_and_obtained(creds, &obtained);
+ if (domain != NULL) {
+ bool overwrite = false;
+ if (dom[0] == '\0') {
+ overwrite = true;
+ }
+ if (obtained >= CRED_CALLBACK_RESULT) {
+ overwrite = true;
+ }
+ if (overwrite) {
+ strncpy(dom, domain, dom_len - 1);
+ }
+ }
+ cli_credentials_set_domain(creds, dom, obtained);
+
+ username = cli_credentials_get_username_and_obtained(creds, &obtained);
+ if (username != NULL) {
+ bool overwrite = false;
+ if (usr[0] == '\0') {
+ overwrite = true;
+ }
+ if (obtained >= CRED_CALLBACK_RESULT) {
+ overwrite = true;
+ }
+ if (overwrite) {
+ strncpy(usr, username, usr_len - 1);
+ }
+ }
+ cli_credentials_set_username(creds, usr, obtained);
+
+ password = cli_credentials_get_password_and_obtained(creds, &obtained);
+ if (password != NULL) {
+ bool overwrite = false;
+ if (pwd[0] == '\0') {
+ overwrite = true;
+ }
+ if (obtained >= CRED_CALLBACK_RESULT) {
+ overwrite = true;
+ }
+ if (overwrite) {
+ strncpy(pwd, password, pwd_len - 1);
+ }
+ }
+ cli_credentials_set_password(creds, pwd, obtained);
+
+ smbc_set_credentials_with_fallback(ctx, dom, usr, pwd);
+
+ if (!opt.quiet) {
+ if (usr[0] == '\0') {
+ printf("Using guest user\n");
+ } else if (dom[0] == '\0') {
+ printf("Using user: %s\n", usr);
+ } else {
+ printf("Using domain: %s, user: %s\n", dom, usr);
+ }
+ }
+}
+
+static bool smb_download_dir(const char *base, const char *name, int resume)
+{
+ char path[SMB_MAXPATHLEN];
+ int dirhandle;
+ struct smbc_dirent *dirent;
+ const char *relname = name;
+ char *tmpname;
+ bool ok = false;
+
+ snprintf(path, SMB_MAXPATHLEN-1, "%s%s%s", base,
+ (base[0] && name[0] && name[0] != '/' &&
+ base[strlen(base)-1] != '/') ? "/" : "",
+ name);
+
+ /* List files in directory and call smb_download_file on them */
+ dirhandle = smbc_opendir(path);
+ if (dirhandle < 1) {
+ if (errno == ENOTDIR) {
+ return smb_download_file(base, name, true, resume,
+ false, NULL);
+ }
+ fprintf(stderr, "Can't open directory %s: %s\n", path,
+ strerror(errno));
+ return false;
+ }
+
+ while (*relname == '/') {
+ relname++;
+ }
+
+ if (strlen(relname) > 0) {
+ int rc = mkdir(relname, 0755);
+ if (rc == -1 && errno != EEXIST) {
+ fprintf(stderr, "Can't create directory %s: %s\n",
+ relname, strerror(errno));
+ return false;
+ }
+ }
+
+ tmpname = SMB_STRDUP(name);
+
+ while ((dirent = smbc_readdir(dirhandle))) {
+ char *newname;
+ if (!strcmp(dirent->name, ".") || !strcmp(dirent->name, "..")) {
+ ok = true;
+ continue;
+ }
+ if (asprintf(&newname, "%s/%s", tmpname, dirent->name) == -1) {
+ free(tmpname);
+ return false;
+ }
+ switch (dirent->smbc_type) {
+ case SMBC_DIR:
+ ok = smb_download_dir(base, newname, resume);
+ break;
+
+ case SMBC_WORKGROUP:
+ ok = smb_download_dir("smb://", dirent->name, resume);
+ break;
+
+ case SMBC_SERVER:
+ ok = smb_download_dir("smb://", dirent->name, resume);
+ break;
+
+ case SMBC_FILE:
+ ok = smb_download_file(base, newname, true, resume,
+ false, NULL);
+ break;
+
+ case SMBC_FILE_SHARE:
+ ok = smb_download_dir(base, newname, resume);
+ break;
+
+ case SMBC_PRINTER_SHARE:
+ if (!opt.quiet) {
+ printf("Ignoring printer share %s\n",
+ dirent->name);
+ }
+ break;
+
+ case SMBC_COMMS_SHARE:
+ if (!opt.quiet) {
+ printf("Ignoring comms share %s\n",
+ dirent->name);
+ }
+ break;
+
+ case SMBC_IPC_SHARE:
+ if (!opt.quiet) {
+ printf("Ignoring ipc$ share %s\n",
+ dirent->name);
+ }
+ break;
+
+ default:
+ fprintf(stderr, "Ignoring file '%s' of type '%d'\n",
+ newname, dirent->smbc_type);
+ break;
+ }
+
+ if (!ok) {
+ fprintf(stderr, "Failed to download %s: %s\n",
+ newname, strerror(errno));
+ free(newname);
+ free(tmpname);
+ return false;
+ }
+ free(newname);
+ }
+ free(tmpname);
+
+ smbc_closedir(dirhandle);
+ return ok;
+}
+
+static char *print_time(long t)
+{
+ static char buffer[100];
+ int secs, mins, hours;
+ if (t < -1) {
+ strncpy(buffer, "Unknown", sizeof(buffer));
+ return buffer;
+ }
+
+ secs = (int)t % 60;
+ mins = (int)t / 60 % 60;
+ hours = (int)t / (60 * 60);
+ snprintf(buffer, sizeof(buffer) - 1, "%02d:%02d:%02d", hours, mins,
+ secs);
+ return buffer;
+}
+
+static void print_progress(const char *name, time_t start, time_t now,
+ off_t start_pos, off_t pos, off_t total)
+{
+ double avg = 0.0;
+ long eta = -1;
+ double prcnt = 0.0;
+ char hpos[22], htotal[22], havg[22];
+ char *status, *filename;
+ int len;
+ if (now - start) {
+ avg = 1.0 * (pos - start_pos) / (now - start);
+ }
+ eta = (total - pos) / avg;
+ if (total) {
+ prcnt = 100.0 * pos / total;
+ }
+
+ human_readable(pos, hpos, sizeof(hpos));
+ human_readable(total, htotal, sizeof(htotal));
+ human_readable(avg, havg, sizeof(havg));
+
+ len = asprintf(&status, "%s of %s (%.2f%%) at %s/s ETA: %s", hpos,
+ htotal, prcnt, havg, print_time(eta));
+ if (len == -1) {
+ return;
+ }
+
+ if (columns) {
+ int required = strlen(name),
+ available = columns - len - strlen("[] ");
+ if (required > available) {
+ if (asprintf(&filename, "...%s",
+ name + required - available + 3) == -1) {
+ return;
+ }
+ } else {
+ filename = SMB_STRNDUP(name, available);
+ }
+ } else {
+ filename = SMB_STRDUP(name);
+ }
+
+ fprintf(stderr, "\r[%s] %s", filename, status);
+
+ free(filename);
+ free(status);
+}
+
+/* Return false on error, true on success. */
+
+static bool smb_download_file(const char *base, const char *name,
+ bool recursive, bool resume, bool toplevel,
+ char *outfile)
+{
+ int remotehandle, localhandle;
+ time_t start_time = time_mono(NULL);
+ const char *newpath;
+ char path[SMB_MAXPATHLEN];
+ char checkbuf[2][RESUME_CHECK_SIZE];
+ char *readbuf = NULL;
+ off_t offset_download = 0, offset_check = 0, curpos = 0,
+ start_offset = 0;
+ struct stat localstat, remotestat;
+ clock_t start_of_bucket_ticks = 0;
+ size_t bytes_in_bucket = 0;
+ size_t bucket_size = 0;
+ clock_t ticks_to_fill_bucket = 0;
+
+ snprintf(path, SMB_MAXPATHLEN-1, "%s%s%s", base,
+ (*base && *name && name[0] != '/' &&
+ base[strlen(base)-1] != '/') ? "/" : "",
+ name);
+
+ remotehandle = smbc_open(path, O_RDONLY, 0755);
+
+ if (remotehandle < 0) {
+ switch (errno) {
+ case EISDIR:
+ if (!recursive) {
+ fprintf(stderr,
+ "%s is a directory. Specify -R "
+ "to download recursively\n",
+ path);
+ return false;
+ }
+ return smb_download_dir(base, name, resume);
+
+ case ENOENT:
+ fprintf(stderr,
+ "%s can't be found on the remote server\n",
+ path);
+ return false;
+
+ case ENOMEM:
+ fprintf(stderr, "Not enough memory\n");
+ return false;
+
+ case ENODEV:
+ fprintf(stderr,
+ "The share name used in %s does not exist\n",
+ path);
+ return false;
+
+ case EACCES:
+ fprintf(stderr, "You don't have enough permissions "
+ "to access %s\n",
+ path);
+ return false;
+
+ default:
+ perror("smbc_open");
+ return false;
+ }
+ }
+
+ if (smbc_fstat(remotehandle, &remotestat) < 0) {
+ fprintf(stderr, "Can't stat %s: %s\n", path, strerror(errno));
+ return false;
+ }
+
+ if (outfile) {
+ newpath = outfile;
+ } else if (!name[0]) {
+ newpath = strrchr(base, '/');
+ if (newpath) {
+ newpath++;
+ } else {
+ newpath = base;
+ }
+ } else {
+ newpath = name;
+ }
+
+ if (!toplevel && (newpath[0] == '/')) {
+ newpath++;
+ }
+
+ /* Open local file according to the mode */
+ if (opt.update) {
+ /* if it is up-to-date, skip */
+ if (stat(newpath, &localstat) == 0 &&
+ localstat.st_mtime >= remotestat.st_mtime) {
+ if (opt.verbose) {
+ printf("%s is up-to-date, skipping\n", newpath);
+ }
+ smbc_close(remotehandle);
+ return true;
+ }
+ /* else open it for writing and truncate if it exists */
+ localhandle = open(
+ newpath, O_CREAT | O_NONBLOCK | O_RDWR | O_TRUNC, 0775);
+ if (localhandle < 0) {
+ fprintf(stderr, "Can't open %s : %s\n", newpath,
+ strerror(errno));
+ smbc_close(remotehandle);
+ return false;
+ }
+ /* no offset */
+ } else if (!opt.send_stdout) {
+ localhandle = open(newpath, O_CREAT | O_NONBLOCK | O_RDWR |
+ (!resume ? O_EXCL : 0),
+ 0755);
+ if (localhandle < 0) {
+ fprintf(stderr, "Can't open %s: %s\n", newpath,
+ strerror(errno));
+ smbc_close(remotehandle);
+ return false;
+ }
+
+ if (fstat(localhandle, &localstat) != 0) {
+ fprintf(stderr, "Can't fstat %s: %s\n", newpath,
+ strerror(errno));
+ smbc_close(remotehandle);
+ close(localhandle);
+ return false;
+ }
+
+ start_offset = localstat.st_size;
+
+ if (localstat.st_size &&
+ localstat.st_size == remotestat.st_size) {
+ if (opt.verbose) {
+ fprintf(stderr, "%s is already downloaded "
+ "completely.\n",
+ path);
+ } else if (!opt.quiet) {
+ fprintf(stderr, "%s\n", path);
+ }
+ smbc_close(remotehandle);
+ close(localhandle);
+ return true;
+ }
+
+ if (localstat.st_size > RESUME_CHECK_OFFSET &&
+ remotestat.st_size > RESUME_CHECK_OFFSET) {
+ offset_download =
+ localstat.st_size - RESUME_DOWNLOAD_OFFSET;
+ offset_check = localstat.st_size - RESUME_CHECK_OFFSET;
+ if (opt.verbose) {
+ printf("Trying to start resume of %s at %jd\n"
+ "At the moment %jd of %jd bytes have "
+ "been retrieved\n",
+ newpath, (intmax_t)offset_check,
+ (intmax_t)localstat.st_size,
+ (intmax_t)remotestat.st_size);
+ }
+ }
+
+ if (offset_check) {
+ off_t off1, off2;
+ /* First, check all bytes from offset_check to
+ * offset_download */
+ off1 = lseek(localhandle, offset_check, SEEK_SET);
+ if (off1 < 0) {
+ fprintf(stderr,
+ "Can't seek to %jd in local file %s\n",
+ (intmax_t)offset_check, newpath);
+ smbc_close(remotehandle);
+ close(localhandle);
+ return false;
+ }
+
+ off2 = smbc_lseek(remotehandle, offset_check, SEEK_SET);
+ if (off2 < 0) {
+ fprintf(stderr,
+ "Can't seek to %jd in remote file %s\n",
+ (intmax_t)offset_check, newpath);
+ smbc_close(remotehandle);
+ close(localhandle);
+ return false;
+ }
+
+ if (off1 != off2) {
+ fprintf(stderr, "Offset in local and remote "
+ "files are different "
+ "(local: %jd, remote: %jd)\n",
+ (intmax_t)off1, (intmax_t)off2);
+ smbc_close(remotehandle);
+ close(localhandle);
+ return false;
+ }
+
+ if (smbc_read(remotehandle, checkbuf[0],
+ RESUME_CHECK_SIZE) != RESUME_CHECK_SIZE) {
+ fprintf(stderr, "Can't read %d bytes from "
+ "remote file %s\n",
+ RESUME_CHECK_SIZE, path);
+ smbc_close(remotehandle);
+ close(localhandle);
+ return false;
+ }
+
+ if (read(localhandle, checkbuf[1], RESUME_CHECK_SIZE) !=
+ RESUME_CHECK_SIZE) {
+ fprintf(stderr, "Can't read %d bytes from "
+ "local file %s\n",
+ RESUME_CHECK_SIZE, name);
+ smbc_close(remotehandle);
+ close(localhandle);
+ return false;
+ }
+
+ if (memcmp(checkbuf[0], checkbuf[1],
+ RESUME_CHECK_SIZE) == 0) {
+ if (opt.verbose) {
+ printf("Current local and remote file "
+ "appear to be the same. "
+ "Starting download from "
+ "offset %jd\n",
+ (intmax_t)offset_download);
+ }
+ } else {
+ fprintf(stderr, "Local and remote file appear "
+ "to be different, not "
+ "doing resume for %s\n",
+ path);
+ smbc_close(remotehandle);
+ close(localhandle);
+ return false;
+ }
+ }
+ } else {
+ localhandle = STDOUT_FILENO;
+ start_offset = 0;
+ offset_download = 0;
+ offset_check = 0;
+ }
+
+ /* We implement rate limiting by filling up a bucket with bytes and
+ * checking, once the bucket is filled, if it was filled too fast.
+ * If so, we sleep for some time to get an average transfer rate that
+ * equals to the one set by the user.
+ *
+ * The bucket size directly affects the traffic characteristics.
+ * The smaller the bucket the more frequent the pause/resume cycle.
+ * A large bucket can result in burst of high speed traffic and large
+ * pauses. A cycle of 100ms looks like a good value. This value (in
+ * ticks) is held in `ticks_to_fill_bucket`. The `bucket_size` is
+ * calculated as:
+ * `limit_rate * 1024 * / (CLOCKS_PER_SEC / ticks_to_fill_bucket)`
+ *
+ * After selecting the bucket size we also need to check the blocksize
+ * of the transfer, since this is the minimum unit of traffic that we
+ * can observe. Achieving a ~10% precision requires a blocksize with a
+ * maximum size of `bucket_size / 10`.
+ */
+ if (opt.limit_rate > 0) {
+ unsigned max_block_size;
+ /* This is the time that the bucket should take to fill. */
+ ticks_to_fill_bucket = 100 /*ms*/ * CLOCKS_PER_SEC / 1000;
+ /* This is the size of the bucket in bytes.
+ * If we fill the bucket too quickly we should pause */
+ bucket_size = opt.limit_rate * 1024 / (CLOCKS_PER_SEC / ticks_to_fill_bucket);
+ max_block_size = bucket_size / 10;
+ max_block_size = max_block_size > 0 ? max_block_size : 1;
+ if (opt.blocksize > max_block_size) {
+ if (opt.blocksize != SMB_DEFAULT_BLOCKSIZE) {
+ fprintf(stderr,
+ "Warning: Overriding block size to %d "
+ "due to limit-rate", max_block_size);
+ }
+ opt.blocksize = max_block_size;
+ }
+ start_of_bucket_ticks = clock();
+ }
+
+ readbuf = (char *)SMB_MALLOC(opt.blocksize);
+ if (!readbuf) {
+ fprintf(stderr, "Failed to allocate %zu bytes for read "
+ "buffer (%s)", opt.blocksize, strerror(errno));
+ if (localhandle != STDOUT_FILENO) {
+ close(localhandle);
+ }
+ return false;
+ }
+
+ /* Now, download all bytes from offset_download to the end */
+ for (curpos = offset_download; curpos < remotestat.st_size;
+ curpos += opt.blocksize) {
+ ssize_t bytesread;
+ ssize_t byteswritten;
+
+ /* Rate limiting. This pauses the transfer to limit traffic. */
+ if (opt.limit_rate > 0) {
+ if (bytes_in_bucket > bucket_size) {
+ clock_t now_ticks = clock();
+ clock_t diff_ticks = now_ticks
+ - start_of_bucket_ticks;
+ /* Check if the bucket filled up too fast. */
+ if (diff_ticks < ticks_to_fill_bucket) {
+ /* Pause until `ticks_to_fill_bucket` */
+ double sleep_us
+ = (ticks_to_fill_bucket - diff_ticks)
+ * 1000000.0 / CLOCKS_PER_SEC;
+ usleep(sleep_us);
+ }
+ /* Reset the byte counter and the ticks. */
+ bytes_in_bucket = 0;
+ start_of_bucket_ticks = clock();
+ }
+ }
+
+ bytesread = smbc_read(remotehandle, readbuf, opt.blocksize);
+ if (opt.limit_rate > 0) {
+ bytes_in_bucket += bytesread;
+ }
+ if(bytesread < 0) {
+ fprintf(stderr,
+ "Can't read %zu bytes at offset %jd, file %s\n",
+ opt.blocksize, (intmax_t)curpos, path);
+ smbc_close(remotehandle);
+ if (localhandle != STDOUT_FILENO) {
+ close(localhandle);
+ }
+ free(readbuf);
+ return false;
+ }
+
+ total_bytes += bytesread;
+
+ byteswritten = write(localhandle, readbuf, bytesread);
+ if (byteswritten != bytesread) {
+ fprintf(stderr,
+ "Can't write %zd bytes to local file %s at "
+ "offset %jd\n", bytesread, path,
+ (intmax_t)curpos);
+ free(readbuf);
+ smbc_close(remotehandle);
+ if (localhandle != STDOUT_FILENO) {
+ close(localhandle);
+ }
+ return false;
+ }
+
+ if (opt.dots) {
+ fputc('.', stderr);
+ } else if (!opt.quiet) {
+ print_progress(newpath, start_time, time_mono(NULL),
+ start_offset, curpos,
+ remotestat.st_size);
+ }
+ }
+
+ free(readbuf);
+
+ if (opt.dots) {
+ fputc('\n', stderr);
+ printf("%s downloaded\n", path);
+ } else if (!opt.quiet) {
+ int i;
+ fprintf(stderr, "\r%s", path);
+ if (columns) {
+ for (i = strlen(path); i < columns; i++) {
+ fputc(' ', stderr);
+ }
+ }
+ fputc('\n', stderr);
+ }
+
+ smbc_close(remotehandle);
+ if (localhandle != STDOUT_FILENO) {
+ close(localhandle);
+ }
+ return true;
+}
+
+static void clean_exit(void)
+{
+ char bs[100];
+ human_readable(total_bytes, bs, sizeof(bs));
+ if (!opt.quiet) {
+ fprintf(stderr, "Downloaded %s in %lu seconds\n", bs,
+ (unsigned long)(time_mono(NULL) - total_start_time));
+ }
+ exit(0);
+}
+
+static void signal_quit(int v)
+{
+ clean_exit();
+}
+
+int main(int argc, char **argv)
+{
+ int c = 0;
+ const char *file = NULL;
+ int smb_encrypt = false;
+ int resume = 0, recursive = 0;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ok = false;
+ const char **argv_const = discard_const_p(const char *, argv);
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+
+ {
+ .longName = "guest",
+ .shortName = 'a',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'a',
+ .descrip = "Work as user guest"
+ },
+ {
+ .longName = "encrypt",
+ .shortName = 'e',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &smb_encrypt,
+ .val = 1,
+ .descrip = "Encrypt SMB transport"
+ },
+ {
+ .longName = "resume",
+ .shortName = 'r',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &resume,
+ .val = 1,
+ .descrip = "Automatically resume aborted files"
+ },
+ {
+ .longName = "update",
+ .shortName = 'u',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt.update,
+ .val = 1,
+ .descrip = "Download only when remote file is "
+ "newer than local file or local file "
+ "is missing"
+ },
+ {
+ .longName = "recursive",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &recursive,
+ .val = true,
+ .descrip = "Recursively download files"
+ },
+ {
+ .longName = "blocksize",
+ .shortName = 'b',
+ .argInfo = POPT_ARG_INT,
+ .arg = &opt.blocksize,
+ .val = 'b',
+ .descrip = "Change number of bytes in a block"
+ },
+
+ {
+ .longName = "outputfile",
+ .shortName = 'o',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &opt.outputfile,
+ .val = 'o',
+ .descrip = "Write downloaded data to specified file"
+ },
+ {
+ .longName = "stdout",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt.send_stdout,
+ .val = true,
+ .descrip = "Write data to stdout"
+ },
+ {
+ .longName = "dots",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt.dots,
+ .val = 1,
+ .descrip = "Show dots as progress indication"
+ },
+ {
+ .longName = "quiet",
+ .shortName = 'q',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt.quiet,
+ .val = 1,
+ .descrip = "Be quiet"
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &opt.verbose,
+ .val = 1,
+ .descrip = "Be verbose"
+ },
+ {
+ .longName = "limit-rate",
+ .shortName = 0,
+ .argInfo = POPT_ARG_INT,
+ .arg = &opt.limit_rate,
+ .val = 'l',
+ .descrip = "Limit download speed to this many KB/s"
+ },
+
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_LEGACY_S3
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ poptContext pc = NULL;
+ struct cli_credentials *creds = NULL;
+ enum smb_encryption_setting encryption_state = SMB_ENCRYPTION_DEFAULT;
+ enum credentials_use_kerberos use_kerberos = CRED_USE_KERBEROS_DESIRED;
+ smbc_smb_encrypt_level encrypt_level = SMBC_ENCRYPTLEVEL_DEFAULT;
+#if 0
+ enum smb_signing_setting signing_state = SMB_SIGNING_DEFAULT;
+ const char *use_signing = "auto";
+#endif
+ bool is_nt_hash = false;
+ uint32_t gensec_features;
+ bool use_wbccache = false;
+ SMBCCTX *smb_ctx = NULL;
+ int dbg_lvl = -1;
+ int rc;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false);
+ if (!ok) {
+ goto done;
+ }
+
+#ifdef SIGWINCH
+ signal(SIGWINCH, change_columns);
+#endif
+ signal(SIGINT, signal_quit);
+ signal(SIGTERM, signal_quit);
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv_const,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ ok = false;
+ goto done;
+ }
+
+ creds = samba_cmdline_get_creds();
+
+ while ((c = poptGetNextOpt(pc)) != -1) {
+ switch (c) {
+ case 'a':
+ cli_credentials_set_anonymous(creds);
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(c));
+ poptPrintUsage(pc, stderr, 0);
+ ok = false;
+ goto done;
+ }
+
+ if (c < -1) {
+ fprintf(stderr, "%s: %s\n",
+ poptBadOption(pc, POPT_BADOPTION_NOALIAS),
+ poptStrerror(c));
+ ok = false;
+ goto done;
+ }
+ }
+
+ if ((opt.send_stdout || resume || opt.outputfile) && opt.update) {
+ fprintf(stderr, "The -o, -R or -O and -U options can not be "
+ "used together.\n");
+ ok = true;
+ goto done;
+ }
+ if ((opt.send_stdout || opt.outputfile) && recursive) {
+ fprintf(stderr, "The -o or -O and -R options can not be "
+ "used together.\n");
+ ok = true;
+ goto done;
+ }
+
+ if (opt.outputfile && opt.send_stdout) {
+ fprintf(stderr, "The -o and -O options can not be "
+ "used together.\n");
+ ok = true;
+ goto done;
+ }
+
+ samba_cmdline_burn(argc, argv);
+
+ /* smbc_new_context() will set the log level to 0 */
+ dbg_lvl = debuglevel_get();
+
+ smb_ctx = smbc_new_context();
+ if (smb_ctx == NULL) {
+ fprintf(stderr, "Unable to initialize libsmbclient\n");
+ ok = false;
+ goto done;
+ }
+ smbc_setDebug(smb_ctx, dbg_lvl);
+
+ rc = smbc_setConfiguration(smb_ctx, lp_default_path());
+ if (rc < 0) {
+ ok = false;
+ goto done;
+ }
+
+ smbc_setFunctionAuthDataWithContext(smb_ctx,
+ get_auth_data_with_context_fn);
+
+ ok = smbc_init_context(smb_ctx);
+ if (!ok) {
+ goto done;
+ }
+ smbc_set_context(smb_ctx);
+
+ encryption_state = cli_credentials_get_smb_encryption(creds);
+ switch (encryption_state) {
+ case SMB_ENCRYPTION_REQUIRED:
+ encrypt_level = SMBC_ENCRYPTLEVEL_REQUIRE;
+ break;
+ case SMB_ENCRYPTION_DESIRED:
+ case SMB_ENCRYPTION_IF_REQUIRED:
+ encrypt_level = SMBC_ENCRYPTLEVEL_REQUEST;
+ break;
+ case SMB_ENCRYPTION_OFF:
+ encrypt_level = SMBC_ENCRYPTLEVEL_NONE;
+ break;
+ case SMB_ENCRYPTION_DEFAULT:
+ encrypt_level = SMBC_ENCRYPTLEVEL_DEFAULT;
+ break;
+ }
+ if (smb_encrypt) {
+ encrypt_level = SMBC_ENCRYPTLEVEL_REQUIRE;
+ }
+ smbc_setOptionSmbEncryptionLevel(smb_ctx, encrypt_level);
+
+#if 0
+ signing_state = cli_credentials_get_smb_signing(creds);
+ if (encryption_state >= SMB_ENCRYPTION_DESIRED) {
+ signing_state = SMB_SIGNING_REQUIRED;
+ }
+ switch (signing_state) {
+ case SMB_SIGNING_REQUIRED:
+ use_signing = "required";
+ break;
+ case SMB_SIGNING_DEFAULT:
+ case SMB_SIGNING_DESIRED:
+ case SMB_SIGNING_IF_REQUIRED:
+ use_signing = "yes";
+ break;
+ case SMB_SIGNING_OFF:
+ use_signing = "off";
+ break;
+ default:
+ use_signing = "auto";
+ break;
+ }
+ /* FIXME: There is no libsmbclient function to set signing state */
+#endif
+
+ use_kerberos = cli_credentials_get_kerberos_state(creds);
+ switch (use_kerberos) {
+ case CRED_USE_KERBEROS_REQUIRED:
+ smbc_setOptionUseKerberos(smb_ctx, true);
+ smbc_setOptionFallbackAfterKerberos(smb_ctx, false);
+ break;
+ case CRED_USE_KERBEROS_DESIRED:
+ smbc_setOptionUseKerberos(smb_ctx, true);
+ smbc_setOptionFallbackAfterKerberos(smb_ctx, true);
+ break;
+ case CRED_USE_KERBEROS_DISABLED:
+ smbc_setOptionUseKerberos(smb_ctx, false);
+ break;
+ }
+
+ /* Check if the password supplied is an NT hash */
+ is_nt_hash = cli_credentials_is_password_nt_hash(creds);
+ smbc_setOptionUseNTHash(smb_ctx, is_nt_hash);
+
+ /* Check if we should use the winbind ccache */
+ gensec_features = cli_credentials_get_gensec_features(creds);
+ use_wbccache = (gensec_features & GENSEC_FEATURE_NTLM_CCACHE);
+ smbc_setOptionUseCCache(smb_ctx, use_wbccache);
+
+ columns = get_num_cols();
+
+ total_start_time = time_mono(NULL);
+
+ while ((file = poptGetArg(pc))) {
+ if (!recursive) {
+ ok = smb_download_file(file, "", recursive, resume,
+ true, opt.outputfile);
+ } else {
+ ok = smb_download_dir(file, "", resume);
+ }
+ }
+
+done:
+ gfree_all();
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ if (ok) {
+ clean_exit();
+ }
+ return ok ? 0 : 1;
+}
diff --git a/source3/utils/smbpasswd.c b/source3/utils/smbpasswd.c
new file mode 100644
index 0000000..f2c5dfd
--- /dev/null
+++ b/source3/utils/smbpasswd.c
@@ -0,0 +1,678 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Copyright (C) Jeremy Allison 1995-1998
+ * Copyright (C) Tim Potter 2001
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, see <http://www.gnu.org/licenses/>. */
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "secrets.h"
+#include "../librpc/gen_ndr/samr.h"
+#include "../lib/util/util_pw.h"
+#include "libsmb/proto.h"
+#include "passdb.h"
+#include "cmdline_contexts.h"
+#include "passwd_proto.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/param/param.h"
+#include "lib/util/memcache.h"
+
+/*
+ * Next two lines needed for SunOS and don't
+ * hurt anything else...
+ */
+extern char *optarg;
+extern int optind;
+
+/* forced running in root-mode */
+static bool got_username = False;
+static bool stdin_passwd_get = False;
+static fstring user_name;
+static char *new_passwd = NULL;
+static const char *remote_machine = NULL;
+
+static fstring ldap_secret;
+
+
+/*********************************************************
+ Print command usage on stderr and die.
+**********************************************************/
+static void usage(void)
+{
+ printf("When run by root:\n");
+ printf(" smbpasswd [options] [username]\n");
+ printf("otherwise:\n");
+ printf(" smbpasswd [options]\n\n");
+
+ printf("options:\n");
+ printf(" -L local mode (must be first option)\n");
+ printf(" -h print this usage message\n");
+ printf(" -s use stdin for password prompt\n");
+ printf(" -c smb.conf file Use the given path to the smb.conf file\n");
+ printf(" -D LEVEL debug level\n");
+ printf(" -r MACHINE remote machine\n");
+ printf(" -U USER remote username (e.g. SAM/user)\n");
+
+ printf("extra options when run by root or in local mode:\n");
+ printf(" -a add user\n");
+ printf(" -d disable user\n");
+ printf(" -e enable user\n");
+ printf(" -i interdomain trust account\n");
+ printf(" -m machine trust account\n");
+ printf(" -n set no password\n");
+ printf(" -W use stdin ldap admin password\n");
+ printf(" -w PASSWORD ldap admin password\n");
+ printf(" -x delete user\n");
+ printf(" -R ORDER name resolve order\n");
+
+ exit(1);
+}
+
+static void set_line_buffering(FILE *f)
+{
+ setvbuf(f, NULL, _IOLBF, 0);
+}
+
+/*******************************************************************
+ Process command line options
+ ******************************************************************/
+
+static int process_options(int argc, char **argv, int local_flags,
+ struct loadparm_context *lp_ctx)
+{
+ int ch;
+ const char *configfile = get_dyn_CONFIGFILE();
+
+ local_flags |= LOCAL_SET_PASSWORD;
+
+ ZERO_STRUCT(user_name);
+
+ user_name[0] = '\0';
+
+ while ((ch = getopt(argc, argv, "c:axdehminjr:sw:R:D:U:LWS:")) != EOF) {
+ switch(ch) {
+ case 'L':
+ if (getuid() != 0) {
+ fprintf(stderr, "smbpasswd -L can only be used by root.\n");
+ exit(1);
+ }
+ local_flags |= LOCAL_AM_ROOT;
+ break;
+ case 'c':
+ configfile = optarg;
+ set_dyn_CONFIGFILE(optarg);
+ break;
+ case 'a':
+ local_flags |= LOCAL_ADD_USER;
+ break;
+ case 'x':
+ local_flags |= LOCAL_DELETE_USER;
+ local_flags &= ~LOCAL_SET_PASSWORD;
+ break;
+ case 'd':
+ local_flags |= LOCAL_DISABLE_USER;
+ local_flags &= ~LOCAL_SET_PASSWORD;
+ break;
+ case 'e':
+ local_flags |= LOCAL_ENABLE_USER;
+ local_flags &= ~LOCAL_SET_PASSWORD;
+ break;
+ case 'm':
+ local_flags |= LOCAL_TRUST_ACCOUNT;
+ break;
+ case 'i':
+ local_flags |= LOCAL_INTERDOM_ACCOUNT;
+ break;
+ case 'j':
+ d_printf("See 'net join' for this functionality\n");
+ exit(1);
+ break;
+ case 'n':
+ local_flags |= LOCAL_SET_NO_PASSWORD;
+ local_flags &= ~LOCAL_SET_PASSWORD;
+ SAFE_FREE(new_passwd);
+ new_passwd = smb_xstrdup("NO PASSWORD");
+ break;
+ case 'r':
+ remote_machine = optarg;
+ break;
+ case 's':
+ set_line_buffering(stdin);
+ set_line_buffering(stdout);
+ set_line_buffering(stderr);
+ stdin_passwd_get = True;
+ break;
+ case 'w':
+ local_flags |= LOCAL_SET_LDAP_ADMIN_PW;
+ fstrcpy(ldap_secret, optarg);
+ break;
+ case 'R':
+ lpcfg_set_cmdline(lp_ctx, "name resolve order", optarg);
+ break;
+ case 'D':
+ lpcfg_set_cmdline(lp_ctx, "log level", optarg);
+ break;
+ case 'U': {
+ got_username = True;
+ fstrcpy(user_name, optarg);
+ break;
+ case 'W':
+ local_flags |= LOCAL_SET_LDAP_ADMIN_PW;
+ *ldap_secret = '\0';
+ break;
+ }
+ case 'h':
+ default:
+ usage();
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ switch(argc) {
+ case 0:
+ if (!got_username)
+ fstrcpy(user_name, "");
+ break;
+ case 1:
+ if (!(local_flags & LOCAL_AM_ROOT)) {
+ usage();
+ } else {
+ if (got_username) {
+ usage();
+ } else {
+ fstrcpy(user_name, argv[0]);
+ }
+ }
+ break;
+ default:
+ usage();
+ }
+
+ if (!lp_load_global(configfile)) {
+ fprintf(stderr, "Can't load %s - run testparm to debug it\n",
+ configfile);
+ exit(1);
+ }
+
+ return local_flags;
+}
+
+/*************************************************************
+ Utility function to prompt for new password.
+*************************************************************/
+static char *prompt_for_new_password(bool stdin_get)
+{
+ char *p;
+ fstring new_pw;
+
+ ZERO_ARRAY(new_pw);
+
+ p = get_pass("New SMB password:", stdin_get);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ fstrcpy(new_pw, p);
+ SAFE_FREE(p);
+
+ p = get_pass("Retype new SMB password:", stdin_get);
+ if (p == NULL) {
+ return NULL;
+ }
+
+ if (strcmp(p, new_pw)) {
+ fprintf(stderr, "Mismatch - password unchanged.\n");
+ ZERO_ARRAY(new_pw);
+ SAFE_FREE(p);
+ return NULL;
+ }
+
+ return p;
+}
+
+
+/*************************************************************
+ Change a password either locally or remotely.
+*************************************************************/
+
+static NTSTATUS password_change(const char *remote_mach,
+ const char *domain, const char *username,
+ const char *old_passwd, const char *new_pw,
+ int local_flags)
+{
+ NTSTATUS ret;
+ char *err_str = NULL;
+ char *msg_str = NULL;
+
+ if (remote_mach != NULL) {
+ if (local_flags & (LOCAL_ADD_USER|LOCAL_DELETE_USER|
+ LOCAL_DISABLE_USER|LOCAL_ENABLE_USER|
+ LOCAL_TRUST_ACCOUNT|LOCAL_SET_NO_PASSWORD)) {
+ /* these things can't be done remotely yet */
+ fprintf(stderr, "Invalid remote operation!\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ ret = remote_password_change(remote_mach,
+ domain, username,
+ old_passwd, new_pw, &err_str);
+ } else {
+ ret = local_password_change(username, local_flags, new_pw,
+ &err_str, &msg_str);
+ }
+
+ if (msg_str) {
+ printf("%s", msg_str);
+ }
+ if (err_str) {
+ fprintf(stderr, "%s", err_str);
+ }
+ if (!NT_STATUS_IS_OK(ret) && !err_str) {
+ fprintf(stderr, "Failed to change password!\n");
+ }
+
+ SAFE_FREE(msg_str);
+ SAFE_FREE(err_str);
+ return ret;
+}
+
+/*******************************************************************
+ Store the LDAP admin password in secrets.tdb
+ ******************************************************************/
+static bool store_ldap_admin_pw (char* pw)
+{
+ if (!pw)
+ return False;
+
+ if (!secrets_init())
+ return False;
+
+ return secrets_store_ldap_pw(lp_ldap_admin_dn(), pw);
+}
+
+
+/*************************************************************
+ Handle password changing for root.
+*************************************************************/
+
+static int process_root(int local_flags)
+{
+ struct passwd *pwd;
+ int result = 0;
+ char *old_passwd = NULL;
+
+ if (local_flags & LOCAL_SET_LDAP_ADMIN_PW) {
+ const char *ldap_admin_dn = lp_ldap_admin_dn();
+ if ( ! *ldap_admin_dn ) {
+ DEBUG(0,("ERROR: 'ldap admin dn' not defined! Please check your smb.conf\n"));
+ goto done;
+ }
+
+ printf("Setting stored password for \"%s\" in secrets.tdb\n", ldap_admin_dn);
+ if ( ! *ldap_secret ) {
+ new_passwd = prompt_for_new_password(stdin_passwd_get);
+ if (new_passwd == NULL) {
+ fprintf(stderr, "Failed to read new password!\n");
+ exit(1);
+ }
+ fstrcpy(ldap_secret, new_passwd);
+ }
+ if (!store_ldap_admin_pw(ldap_secret)) {
+ DEBUG(0,("ERROR: Failed to store the ldap admin password!\n"));
+ }
+ goto done;
+ }
+
+ /* Ensure passdb startup(). */
+ if(!initialize_password_db(False, NULL)) {
+ DEBUG(0, ("Failed to open passdb!\n"));
+ exit(1);
+ }
+
+ /* Ensure we have a SAM sid. */
+ get_global_sam_sid();
+
+ /*
+ * Ensure both add/delete user are not set
+ * Ensure add/delete user and either remote machine or join domain are
+ * not both set.
+ */
+ if(((local_flags & (LOCAL_ADD_USER|LOCAL_DELETE_USER)) == (LOCAL_ADD_USER|LOCAL_DELETE_USER)) ||
+ ((local_flags & (LOCAL_ADD_USER|LOCAL_DELETE_USER)) &&
+ (remote_machine != NULL))) {
+ usage();
+ }
+
+ /* Only load interfaces if we are doing network operations. */
+
+ if (remote_machine) {
+ load_interfaces();
+ }
+
+ if (!user_name[0] && (pwd = getpwuid_alloc(talloc_tos(), geteuid()))) {
+ fstrcpy(user_name, pwd->pw_name);
+ TALLOC_FREE(pwd);
+ }
+
+ if (!user_name[0]) {
+ fprintf(stderr,"You must specify a username\n");
+ exit(1);
+ }
+
+ if (local_flags & LOCAL_TRUST_ACCOUNT) {
+ /* add the $ automatically */
+ size_t user_name_len = strlen(user_name);
+
+ if (user_name[user_name_len - 1] == '$') {
+ user_name_len--;
+ } else {
+ if (user_name_len + 2 > sizeof(user_name)) {
+ fprintf(stderr, "machine name too long\n");
+ exit(1);
+ }
+ user_name[user_name_len] = '$';
+ user_name[user_name_len + 1] = '\0';
+ }
+
+ if (local_flags & LOCAL_ADD_USER) {
+ SAFE_FREE(new_passwd);
+
+ /*
+ * Remove any trailing '$' before we
+ * generate the initial machine password.
+ */
+ new_passwd = smb_xstrndup(user_name, user_name_len);
+ if (!strlower_m(new_passwd)) {
+ fprintf(stderr, "strlower_m %s failed\n",
+ new_passwd);
+ exit(1);
+ }
+ }
+ } else if (local_flags & LOCAL_INTERDOM_ACCOUNT) {
+ size_t user_name_len = strlen(user_name);
+
+ if (user_name[user_name_len - 1] != '$') {
+ if (user_name_len + 2 > sizeof(user_name)) {
+ fprintf(stderr, "machine name too long\n");
+ exit(1);
+ }
+ user_name[user_name_len] = '$';
+ user_name[user_name_len + 1] = '\0';
+ }
+
+ if ((local_flags & LOCAL_ADD_USER) && (new_passwd == NULL)) {
+ /*
+ * Prompt for trusting domain's account password
+ */
+ new_passwd = prompt_for_new_password(stdin_passwd_get);
+ if(!new_passwd) {
+ fprintf(stderr, "Unable to get newpassword.\n");
+ exit(1);
+ }
+ }
+ } else {
+
+ if (remote_machine != NULL) {
+ old_passwd = get_pass("Old SMB password:",stdin_passwd_get);
+ if(!old_passwd) {
+ fprintf(stderr, "Unable to get old password.\n");
+ exit(1);
+ }
+ }
+
+ if (!(local_flags & LOCAL_SET_PASSWORD)) {
+
+ /*
+ * If we are trying to enable a user, first we need to find out
+ * if they are using a modern version of the smbpasswd file that
+ * disables a user by just writing a flag into the file. If so
+ * then we can re-enable a user without prompting for a new
+ * password. If not (ie. they have a no stored password in the
+ * smbpasswd file) then we need to prompt for a new password.
+ */
+
+ if(local_flags & LOCAL_ENABLE_USER) {
+ struct samu *sampass = NULL;
+
+ sampass = samu_new( NULL );
+ if (!sampass) {
+ fprintf(stderr, "talloc fail for struct samu.\n");
+ exit(1);
+ }
+ if (!pdb_getsampwnam(sampass, user_name)) {
+ fprintf(stderr, "Failed to find user %s in passdb backend.\n",
+ user_name );
+ exit(1);
+ }
+
+ if(pdb_get_nt_passwd(sampass) == NULL) {
+ local_flags |= LOCAL_SET_PASSWORD;
+ }
+ TALLOC_FREE(sampass);
+ }
+ }
+
+ if((local_flags & LOCAL_SET_PASSWORD) && (new_passwd == NULL)) {
+
+ new_passwd = prompt_for_new_password(stdin_passwd_get);
+ if(!new_passwd) {
+ fprintf(stderr, "Unable to get new password.\n");
+ exit(1);
+ }
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(password_change(remote_machine,
+ NULL, user_name,
+ old_passwd, new_passwd,
+ local_flags))) {
+ result = 1;
+ goto done;
+ }
+
+ if(remote_machine) {
+ printf("Password changed for user %s on %s.\n", user_name, remote_machine );
+ } else if(!(local_flags & (LOCAL_ADD_USER|LOCAL_DISABLE_USER|LOCAL_ENABLE_USER|LOCAL_DELETE_USER|LOCAL_SET_NO_PASSWORD|LOCAL_SET_PASSWORD))) {
+ struct samu *sampass = NULL;
+
+ sampass = samu_new( NULL );
+ if (!sampass) {
+ fprintf(stderr, "talloc fail for struct samu.\n");
+ exit(1);
+ }
+
+ if (!pdb_getsampwnam(sampass, user_name)) {
+ fprintf(stderr, "Failed to find user %s in passdb backend.\n",
+ user_name );
+ exit(1);
+ }
+
+ printf("Password changed for user %s.", user_name );
+ if(pdb_get_acct_ctrl(sampass)&ACB_DISABLED) {
+ printf(" User has disabled flag set.");
+ }
+ if(pdb_get_acct_ctrl(sampass) & ACB_PWNOTREQ) {
+ printf(" User has no password flag set.");
+ }
+ printf("\n");
+ TALLOC_FREE(sampass);
+ }
+
+ done:
+ SAFE_FREE(old_passwd);
+ SAFE_FREE(new_passwd);
+ return result;
+}
+
+
+/*************************************************************
+ Handle password changing for non-root.
+*************************************************************/
+
+static int process_nonroot(int local_flags)
+{
+ struct passwd *pwd = NULL;
+ int result = 0;
+ char *old_pw = NULL;
+ char *new_pw = NULL;
+ const char *username = user_name;
+ const char *domain = NULL;
+ char *p = NULL;
+
+ if (local_flags & ~(LOCAL_AM_ROOT | LOCAL_SET_PASSWORD)) {
+ /* Extra flags that we can't honor non-root */
+ usage();
+ }
+
+ if (!user_name[0]) {
+ pwd = getpwuid_alloc(talloc_tos(), getuid());
+ if (pwd) {
+ fstrcpy(user_name,pwd->pw_name);
+ TALLOC_FREE(pwd);
+ } else {
+ fprintf(stderr, "smbpasswd: cannot lookup user name for uid %u\n", (unsigned int)getuid());
+ exit(1);
+ }
+ }
+
+ /* Allow domain as part of the username */
+ if ((p = strchr_m(user_name, '\\')) ||
+ (p = strchr_m(user_name, '/')) ||
+ (p = strchr_m(user_name, *lp_winbind_separator()))) {
+ *p = '\0';
+ username = p + 1;
+ domain = user_name;
+ }
+
+ /*
+ * A non-root user is always setting a password
+ * via a remote machine (even if that machine is
+ * localhost).
+ */
+
+ load_interfaces(); /* Delayed from main() */
+
+ if (remote_machine != NULL) {
+ if (!is_ipaddress(remote_machine)) {
+ domain = remote_machine;
+ }
+ } else {
+ remote_machine = "127.0.0.1";
+
+ /*
+ * If we deal with a local user, change the password for the
+ * user in our SAM.
+ */
+ domain = get_global_sam_name();
+ }
+
+ old_pw = get_pass("Old SMB password:",stdin_passwd_get);
+ if (old_pw == NULL) {
+ fprintf(stderr, "Unable to get old password.\n");
+ exit(1);
+ }
+
+ if (!new_passwd) {
+ new_pw = prompt_for_new_password(stdin_passwd_get);
+ }
+ else
+ new_pw = smb_xstrdup(new_passwd);
+
+ if (!new_pw) {
+ fprintf(stderr, "Unable to get new password.\n");
+ exit(1);
+ }
+
+ if (!NT_STATUS_IS_OK(password_change(remote_machine,
+ domain, username,
+ old_pw, new_pw, 0))) {
+ result = 1;
+ goto done;
+ }
+
+ printf("Password changed for user %s\n", username);
+
+ done:
+ SAFE_FREE(old_pw);
+ SAFE_FREE(new_pw);
+
+ return result;
+}
+
+
+
+/*********************************************************
+ Start here.
+**********************************************************/
+int main(int argc, char **argv)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+ struct memcache *mcache = NULL;
+ int local_flags = 0;
+ int ret;
+
+ mcache = memcache_init(NULL, 0);
+ if (mcache == NULL) {
+ fprintf(stderr, "%s: memcache_init failed\n", __location__);
+ return 1;
+ }
+ memcache_set_global(mcache);
+
+#if defined(HAVE_SET_AUTH_PARAMETERS)
+ set_auth_parameters(argc, argv);
+#endif /* HAVE_SET_AUTH_PARAMETERS */
+
+ if (getuid() == 0) {
+ local_flags = LOCAL_AM_ROOT;
+ }
+
+ smb_init_locale();
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ fprintf(stderr,
+ "Failed to initialise the global parameter structure.\n");
+ return 1;
+ }
+
+ local_flags = process_options(argc, argv, local_flags, lp_ctx);
+
+ setup_logging("smbpasswd", DEBUG_STDERR);
+
+ /* Check the effective uid - make sure we are not setuid */
+ if (is_setuid_root()) {
+ fprintf(stderr, "smbpasswd must *NOT* be setuid root.\n");
+ exit(1);
+ }
+
+ if (local_flags & LOCAL_AM_ROOT) {
+ bool ok;
+
+ ok = secrets_init();
+ if (!ok) {
+ return 1;
+ }
+ ret = process_root(local_flags);
+ } else {
+ ret = process_nonroot(local_flags);
+ }
+
+ gfree_all();
+
+ TALLOC_FREE(frame);
+ return ret;
+}
diff --git a/source3/utils/smbtree.c b/source3/utils/smbtree.c
new file mode 100644
index 0000000..80dfa0a
--- /dev/null
+++ b/source3/utils/smbtree.c
@@ -0,0 +1,295 @@
+/*
+ Unix SMB/CIFS implementation.
+ Network neighbourhood browser.
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jelmer Vernooij 2003
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_srvsvc_c.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/namequery.h"
+#include "libsmb/clirap.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "nameserv.h"
+#include "libsmbclient.h"
+
+/* How low can we go? */
+
+enum tree_level {LEV_WORKGROUP, LEV_SERVER, LEV_SHARE};
+static enum tree_level level = LEV_SHARE;
+
+static void get_auth_data_with_context_fn(
+ SMBCCTX *context,
+ const char *server,
+ const char *share,
+ char *domain,
+ int domain_len,
+ char *user,
+ int user_len,
+ char *password,
+ int password_len)
+{
+ struct cli_credentials *creds = samba_cmdline_get_creds();
+ size_t len;
+
+ len = strlcpy(domain, cli_credentials_get_domain(creds), domain_len);
+ if ((int)len >= domain_len) {
+ return;
+ }
+ len = strlcpy(
+ user, cli_credentials_get_username(creds), user_len);
+ if ((int)len >= user_len) {
+ return;
+ }
+ len = strlcpy(
+ password, cli_credentials_get_password(creds), password_len);
+ if ((int)len >= password_len) {
+ /* pointless, but what can you do... */
+ return;
+ }
+}
+
+/****************************************************************************
+ main program
+****************************************************************************/
+int main(int argc, char *argv[])
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ const char **argv_const = discard_const_p(const char *, argv);
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "domains",
+ .shortName = 'D',
+ .argInfo = POPT_ARG_VAL,
+ .arg = &level,
+ .val = LEV_WORKGROUP,
+ .descrip = "List only domains (workgroups) of tree" ,
+ },
+ {
+ .longName = "servers",
+ .shortName = 'S',
+ .argInfo = POPT_ARG_VAL,
+ .arg = &level,
+ .val = LEV_SERVER,
+ .descrip = "List domains(workgroups) and servers of tree" ,
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CREDENTIALS
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ poptContext pc;
+ SMBCCTX *ctx = NULL;
+ SMBCFILE *workgroups = NULL;
+ struct smbc_dirent *dirent = NULL;
+ bool ok;
+ int ret, result = 1;
+ int opt;
+ int debuglevel;
+
+ /* Initialise samba stuff */
+ smb_init_locale();
+
+ setlinebuf(stdout);
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv_const,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ samba_cmdline_burn(argc, argv);
+
+ debuglevel = DEBUGLEVEL;
+
+ ctx = smbc_new_context();
+ if (ctx == NULL) {
+ perror("smbc_new_context");
+ goto fail;
+ }
+ ret = smbc_setConfiguration(ctx, get_dyn_CONFIGFILE());
+ if (ret == -1) {
+ perror("smbc_setConfiguration");
+ goto fail;
+ }
+ smbc_setDebug(ctx, debuglevel);
+ ok = smbc_setOptionProtocols(ctx, NULL, "NT1");
+ if (!ok) {
+ perror("smbc_setOptionProtocols");
+ goto fail;
+ }
+ smbc_setFunctionAuthDataWithContext(
+ ctx, get_auth_data_with_context_fn);
+
+ ok = smbc_init_context(ctx);
+ if (!ok) {
+ perror("smbc_init_context");
+ goto fail;
+ }
+
+ workgroups = smbc_getFunctionOpendir(ctx)(ctx, "smb://");
+ if (workgroups == NULL) {
+ DBG_ERR("This is utility doesn't work if netbios name "
+ "resolution is not configured.\n"
+ "If you are using SMB2 or SMB3, network browsing uses "
+ "WSD/LLMNR, which is not yet supported by Samba. SMB1 "
+ "is disabled by default on the latest Windows versions "
+ "for security reasons. It is still possible to access "
+ "the Samba resources directly via \\name or "
+ "\\ip.address.\n");
+ goto fail;
+ }
+
+ while ((dirent = smbc_getFunctionReaddir(ctx)(ctx, workgroups))
+ != NULL) {
+ char *url = NULL;
+ SMBCFILE *servers = NULL;
+
+ if (dirent->smbc_type != SMBC_WORKGROUP) {
+ continue;
+ }
+
+ printf("%s\n", dirent->name);
+
+ if (level == LEV_WORKGROUP) {
+ continue;
+ }
+
+ url = talloc_asprintf(
+ talloc_tos(), "smb://%s/", dirent->name);
+ if (url == NULL) {
+ perror("talloc_asprintf");
+ goto fail;
+ }
+
+ servers = smbc_getFunctionOpendir(ctx)(ctx, url);
+ if (servers == NULL) {
+ perror("smbc_opendir");
+ goto fail;
+ }
+ TALLOC_FREE(url);
+
+ while ((dirent = smbc_getFunctionReaddir(ctx)(ctx, servers))
+ != NULL) {
+ SMBCFILE *shares = NULL;
+ char *servername = NULL;
+
+ if (dirent->smbc_type != SMBC_SERVER) {
+ continue;
+ }
+
+ printf("\t\\\\%-15s\t\t%s\n",
+ dirent->name,
+ dirent->comment);
+
+ if (level == LEV_SERVER) {
+ continue;
+ }
+
+ /*
+ * The subsequent readdir for shares will
+ * overwrite the "server" readdir
+ */
+ servername = talloc_strdup(talloc_tos(), dirent->name);
+ if (servername == NULL) {
+ continue;
+ }
+
+ url = talloc_asprintf(
+ talloc_tos(), "smb://%s/", servername);
+ if (url == NULL) {
+ perror("talloc_asprintf");
+ goto fail;
+ }
+
+ shares = smbc_getFunctionOpendir(ctx)(ctx, url);
+ if (shares == NULL) {
+ perror("smbc_opendir");
+ goto fail;
+ }
+
+ while ((dirent = smbc_getFunctionReaddir(
+ ctx)(ctx, shares))
+ != NULL) {
+ printf("\t\t\\\\%s\\%-15s\t%s\n",
+ servername,
+ dirent->name,
+ dirent->comment);
+ }
+
+ ret = smbc_getFunctionClosedir(ctx)(ctx, shares);
+ if (ret == -1) {
+ perror("smbc_closedir");
+ goto fail;
+ }
+
+ TALLOC_FREE(servername);
+ TALLOC_FREE(url);
+ }
+
+ ret = smbc_getFunctionClosedir(ctx)(ctx, servers);
+ if (ret == -1) {
+ perror("smbc_closedir");
+ goto fail;
+ }
+ }
+
+ ret = smbc_getFunctionClosedir(ctx)(ctx, workgroups);
+ if (ret == -1) {
+ perror("smbc_closedir");
+ goto fail;
+ }
+
+ result = 0;
+fail:
+ if (ctx != NULL) {
+ smbc_free_context(ctx, 0);
+ ctx = NULL;
+ }
+ gfree_all();
+ poptFreeContext(pc);
+ TALLOC_FREE(frame);
+ return result;
+}
diff --git a/source3/utils/status.c b/source3/utils/status.c
new file mode 100644
index 0000000..4102b41
--- /dev/null
+++ b/source3/utils/status.c
@@ -0,0 +1,1241 @@
+/*
+ Unix SMB/CIFS implementation.
+ status reporting
+ Copyright (C) Andrew Tridgell 1994-1998
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ Revision History:
+
+ 12 aug 96: Erik.Devriendt@te6.siemens.be
+ added support for shared memory implementation of share mode locking
+
+ 21-Jul-1998: rsharpe@ns.aus.com (Richard Sharpe)
+ Added -L (locks only) -S (shares only) flags and code
+
+*/
+
+/*
+ * This program reports current SMB connections
+ */
+
+#include "includes.h"
+#include "lib/util/server_id.h"
+#include "smbd/globals.h"
+#include "system/filesys.h"
+#include "lib/cmdline/cmdline.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "session.h"
+#include "locking/share_mode_lock.h"
+#include "locking/proto.h"
+#include "messages.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "smbd/smbd.h"
+#include "librpc/gen_ndr/notify.h"
+#include "conn_tdb.h"
+#include "serverid.h"
+#include "status_profile.h"
+#include "status.h"
+#include "status_json.h"
+#include "smbd/notifyd/notifyd_db.h"
+#include "cmdline_contexts.h"
+#include "locking/leases_db.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/param/param.h"
+
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+#include "audit_logging.h" /* various JSON helpers */
+#include "auth/common_auth.h"
+#endif /* HAVE_JANSSON */
+
+#define SMB_MAXPIDS 2048
+static uid_t Ucrit_uid = 0; /* added by OH */
+static struct server_id Ucrit_pid[SMB_MAXPIDS]; /* Ugly !!! */ /* added by OH */
+static int Ucrit_MaxPid=0; /* added by OH */
+static unsigned int Ucrit_IsActive = 0; /* added by OH */
+
+static bool verbose, brief;
+static bool shares_only; /* Added by RJS */
+static bool locks_only; /* Added by RJS */
+static bool processes_only;
+static bool show_brl;
+static bool numeric_only;
+static bool do_checks = true;
+
+const char *username = NULL;
+
+/* added by OH */
+static void Ucrit_addUid(uid_t uid)
+{
+ Ucrit_uid = uid;
+ Ucrit_IsActive = 1;
+}
+
+static unsigned int Ucrit_checkUid(uid_t uid)
+{
+ if ( !Ucrit_IsActive )
+ return 1;
+
+ if ( uid == Ucrit_uid )
+ return 1;
+
+ return 0;
+}
+
+static unsigned int Ucrit_checkPid(struct server_id pid)
+{
+ int i;
+
+ if ( !Ucrit_IsActive )
+ return 1;
+
+ for (i=0;i<Ucrit_MaxPid;i++) {
+ if (server_id_equal(&pid, &Ucrit_pid[i])) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static bool Ucrit_addPid( struct server_id pid )
+{
+ if ( !Ucrit_IsActive )
+ return True;
+
+ if ( Ucrit_MaxPid >= SMB_MAXPIDS ) {
+ fprintf(stderr, "ERROR: More than %d pids for user %s!\n",
+ SMB_MAXPIDS, uidtoname(Ucrit_uid));
+
+ return False;
+ }
+
+ Ucrit_pid[Ucrit_MaxPid++] = pid;
+
+ return True;
+}
+
+static int print_share_mode_stdout(struct traverse_state *state,
+ const char *pid,
+ const char *user_name,
+ const char *denymode,
+ int access_mask,
+ const char *rw,
+ const char *oplock,
+ const char *servicepath,
+ const char *filename,
+ const char *timestr)
+{
+ if (state->first) {
+ d_printf("\nLocked files:\n");
+ d_printf("Pid User(ID) DenyMode Access R/W Oplock SharePath Name Time\n");
+ d_printf("--------------------------------------------------------------------------------------------------\n");
+
+ state->first = false;
+ }
+
+ d_printf("%-11s %-9s %-10s 0x%-8x %-10s %-14s %s %s %s",
+ pid, user_name, denymode, access_mask, rw, oplock,
+ servicepath, filename, timestr);
+ return 0;
+}
+
+static int prepare_share_mode(struct traverse_state *state)
+{
+ if (!state->json_output) {
+ /* only print header line if there are open files */
+ state->first = true;
+ } else {
+ add_section_to_json(state, "open_files");
+ }
+ return 0;
+}
+
+static uint32_t map_share_mode_to_deny_mode(
+ uint32_t share_access, uint32_t private_options)
+{
+ switch (share_access & ~FILE_SHARE_DELETE) {
+ case FILE_SHARE_NONE:
+ return DENY_ALL;
+ case FILE_SHARE_READ:
+ return DENY_WRITE;
+ case FILE_SHARE_WRITE:
+ return DENY_READ;
+ case FILE_SHARE_READ|FILE_SHARE_WRITE:
+ return DENY_NONE;
+ }
+ if (private_options & NTCREATEX_FLAG_DENY_DOS) {
+ return DENY_DOS;
+ } else if (private_options & NTCREATEX_FLAG_DENY_FCB) {
+ return DENY_FCB;
+ }
+
+ return (uint32_t)-1;
+}
+
+static int print_share_mode(struct file_id fid,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ void *private_data)
+{
+ const char *denymode = NULL;
+ uint denymode_int;
+ const char *oplock = NULL;
+ const char *pid = NULL;
+ const char *rw = NULL;
+ const char *filename = NULL;
+ const char *timestr = NULL;
+ const char *user_str = NULL;
+ uint32_t lstate;
+ struct traverse_state *state = (struct traverse_state *)private_data;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ if (do_checks && !is_valid_share_mode_entry(e)) {
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+ }
+
+ if (do_checks && !serverid_exists(&e->pid)) {
+ /* the process for this entry does not exist any more */
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+ }
+
+ if (Ucrit_checkPid(e->pid)) {
+ struct server_id_buf tmp;
+ pid = server_id_str_buf(e->pid, &tmp);
+ if (state->resolve_uids) {
+ user_str = talloc_asprintf(tmp_ctx, "%s", uidtoname(e->uid));
+ } else {
+ user_str = talloc_asprintf(tmp_ctx, "%u", (unsigned int)e->uid);
+ }
+ if (user_str == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ denymode_int = map_share_mode_to_deny_mode(e->share_access,
+ e->private_options);
+ switch (denymode_int) {
+ case DENY_NONE:
+ denymode = "DENY_NONE";
+ break;
+ case DENY_ALL:
+ denymode = "DENY_ALL";
+ break;
+ case DENY_DOS:
+ denymode = "DENY_DOS";
+ break;
+ case DENY_READ:
+ denymode = "DENY_READ";
+ break;
+ case DENY_WRITE:
+ denymode = "DENY_WRITE";
+ break;
+ case DENY_FCB:
+ denymode = "DENY_FCB";
+ break;
+ default: {
+ denymode = talloc_asprintf(tmp_ctx,
+ "UNKNOWN(0x%08x)",
+ denymode_int);
+ if (denymode == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+ fprintf(stderr,
+ "unknown-please report ! "
+ "e->share_access = 0x%x, "
+ "e->private_options = 0x%x\n",
+ (unsigned int)e->share_access,
+ (unsigned int)e->private_options);
+ break;
+ }
+ }
+ filename = talloc_asprintf(tmp_ctx,
+ "%s%s",
+ d->base_name,
+ (d->stream_name != NULL) ? d->stream_name : "");
+ if (filename == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+ if ((e->access_mask & (FILE_READ_DATA|FILE_WRITE_DATA))==
+ (FILE_READ_DATA|FILE_WRITE_DATA)) {
+ rw = "RDWR";
+ } else if (e->access_mask & FILE_WRITE_DATA) {
+ rw = "WRONLY";
+ } else {
+ rw = "RDONLY";
+ }
+
+ if (e->op_type & BATCH_OPLOCK) {
+ oplock = "BATCH";
+ } else if (e->op_type & EXCLUSIVE_OPLOCK) {
+ oplock = "EXCLUSIVE";
+ } else if (e->op_type & LEVEL_II_OPLOCK) {
+ oplock = "LEVEL_II";
+ } else if (e->op_type == LEASE_OPLOCK) {
+ NTSTATUS status;
+
+ status = leases_db_get(
+ &e->client_guid,
+ &e->lease_key,
+ &d->id,
+ &lstate, /* current_state */
+ NULL, /* breaking */
+ NULL, /* breaking_to_requested */
+ NULL, /* breaking_to_required */
+ NULL, /* lease_version */
+ NULL); /* epoch */
+
+ if (NT_STATUS_IS_OK(status)) {
+ oplock = talloc_asprintf(tmp_ctx, "LEASE(%s%s%s)%s%s%s",
+ (lstate & SMB2_LEASE_READ)?"R":"",
+ (lstate & SMB2_LEASE_WRITE)?"W":"",
+ (lstate & SMB2_LEASE_HANDLE)?"H":"",
+ (lstate & SMB2_LEASE_READ)?"":" ",
+ (lstate & SMB2_LEASE_WRITE)?"":" ",
+ (lstate & SMB2_LEASE_HANDLE)?"":" ");
+ } else {
+ oplock = "LEASE STATE UNKNOWN";
+ }
+ } else {
+ oplock = "NONE";
+ }
+
+ timestr = time_to_asc((time_t)e->time.tv_sec);
+
+ if (!state->json_output) {
+ print_share_mode_stdout(state,
+ pid,
+ user_str,
+ denymode,
+ (unsigned int)e->access_mask,
+ rw,
+ oplock,
+ d->servicepath,
+ filename,
+ timestr);
+ } else {
+ print_share_mode_json(state,
+ d,
+ e,
+ fid,
+ user_str,
+ oplock,
+ lstate,
+ filename);
+ }
+ }
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+}
+
+static void print_brl_stdout(struct traverse_state *state,
+ char *pid,
+ char *id,
+ const char *desc,
+ intmax_t start,
+ intmax_t size,
+ const char *sharepath,
+ char *fname)
+{
+ if (state->first) {
+ d_printf("Byte range locks:\n");
+ d_printf("Pid dev:inode R/W start size SharePath Name\n");
+ d_printf("--------------------------------------------------------------------------------\n");
+
+ state->first = false;
+ }
+ d_printf("%-10s %-15s %-4s %-9jd %-9jd %-24s %-24s\n",
+ pid, id, desc, start, size, sharepath, fname);
+}
+
+static int prepare_brl(struct traverse_state *state)
+{
+ if (!state->json_output) {
+ /* only print header line if there are locked files */
+ state->first = true;
+ } else {
+ add_section_to_json(state, "byte_range_locks");
+ }
+ return 0;
+}
+
+static void print_brl(struct file_id id,
+ struct server_id pid,
+ enum brl_type lock_type,
+ enum brl_flavour lock_flav,
+ br_off start,
+ br_off size,
+ void *private_data)
+{
+ unsigned int i;
+ static const struct {
+ enum brl_type lock_type;
+ const char *desc;
+ } lock_types[] = {
+ { READ_LOCK, "R" },
+ { WRITE_LOCK, "W" },
+ { UNLOCK_LOCK, "U" }
+ };
+ const char *desc="X";
+ const char *sharepath = "";
+ char *fname = NULL;
+ struct share_mode_lock *share_mode;
+ struct server_id_buf tmp;
+ struct file_id_buf ftmp;
+ struct traverse_state *state = (struct traverse_state *)private_data;
+
+ share_mode = fetch_share_mode_unlocked(NULL, id);
+ if (share_mode) {
+ fname = share_mode_filename(NULL, share_mode);
+ sharepath = share_mode_servicepath(share_mode);
+ } else {
+ fname = talloc_strdup(NULL, "");
+ if (fname == NULL) {
+ return;
+ }
+ }
+
+ for (i=0;i<ARRAY_SIZE(lock_types);i++) {
+ if (lock_type == lock_types[i].lock_type) {
+ desc = lock_types[i].desc;
+ }
+ }
+
+ if (!state->json_output) {
+ print_brl_stdout(state,
+ server_id_str_buf(pid, &tmp),
+ file_id_str_buf(id, &ftmp),
+ desc,
+ (intmax_t)start,
+ (intmax_t)size,
+ sharepath,
+ fname);
+ } else {
+ print_brl_json(state,
+ pid,
+ id,
+ desc,
+ lock_flav,
+ (intmax_t)start,
+ (intmax_t)size,
+ sharepath,
+ fname);
+
+ }
+
+ TALLOC_FREE(fname);
+ TALLOC_FREE(share_mode);
+}
+
+static const char *session_dialect_str(uint16_t dialect)
+{
+ static fstring unknown_dialect;
+
+ switch(dialect){
+ case SMB2_DIALECT_REVISION_000:
+ return "NT1";
+ case SMB2_DIALECT_REVISION_202:
+ return "SMB2_02";
+ case SMB2_DIALECT_REVISION_210:
+ return "SMB2_10";
+ case SMB2_DIALECT_REVISION_222:
+ return "SMB2_22";
+ case SMB2_DIALECT_REVISION_224:
+ return "SMB2_24";
+ case SMB3_DIALECT_REVISION_300:
+ return "SMB3_00";
+ case SMB3_DIALECT_REVISION_302:
+ return "SMB3_02";
+ case SMB3_DIALECT_REVISION_310:
+ return "SMB3_10";
+ case SMB3_DIALECT_REVISION_311:
+ return "SMB3_11";
+ }
+
+ fstr_sprintf(unknown_dialect, "Unknown (0x%04x)", dialect);
+ return unknown_dialect;
+}
+
+static int traverse_connections_stdout(struct traverse_state *state,
+ const char *servicename,
+ char *server_id,
+ const char *machine,
+ const char *timestr,
+ const char *encryption,
+ const char *signing)
+{
+ d_printf("%-12s %-7s %-13s %-32s %-12s %-12s\n",
+ servicename, server_id, machine, timestr, encryption, signing);
+
+ return 0;
+}
+
+static int prepare_connections(struct traverse_state *state)
+{
+ if (!state->json_output) {
+ /* always print header line */
+ d_printf("\n%-12s %-7s %-13s %-32s %-12s %-12s\n", "Service", "pid", "Machine", "Connected at", "Encryption", "Signing");
+ d_printf("---------------------------------------------------------------------------------------------\n");
+ } else {
+ add_section_to_json(state, "tcons");
+ }
+ return 0;
+}
+
+static int traverse_connections(const struct connections_data *crec,
+ void *private_data)
+{
+ struct server_id_buf tmp;
+ char *timestr = NULL;
+ int result = 0;
+ const char *encryption = "-";
+ enum crypto_degree encryption_degree = CRYPTO_DEGREE_NONE;
+ const char *signing = "-";
+ enum crypto_degree signing_degree = CRYPTO_DEGREE_NONE;
+ struct traverse_state *state = (struct traverse_state *)private_data;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ if (crec->cnum == TID_FIELD_INVALID) {
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+ }
+
+ if (do_checks &&
+ (!process_exists(crec->pid) || !Ucrit_checkUid(crec->uid))) {
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+ }
+
+ timestr = timestring(tmp_ctx, nt_time_to_unix(crec->start));
+ if (timestr == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (smbXsrv_is_encrypted(crec->encryption_flags)) {
+ switch (crec->cipher) {
+ case SMB_ENCRYPTION_GSSAPI:
+ encryption = "GSSAPI";
+ break;
+ case SMB2_ENCRYPTION_AES128_CCM:
+ encryption = "AES-128-CCM";
+ break;
+ case SMB2_ENCRYPTION_AES128_GCM:
+ encryption = "AES-128-GCM";
+ break;
+ default:
+ encryption = "???";
+ break;
+ }
+ encryption_degree = CRYPTO_DEGREE_FULL;
+ }
+
+ if (smbXsrv_is_signed(crec->signing_flags)) {
+ switch (crec->signing) {
+ case SMB2_SIGNING_MD5_SMB1:
+ signing = "HMAC-MD5";
+ break;
+ case SMB2_SIGNING_HMAC_SHA256:
+ signing = "HMAC-SHA256";
+ break;
+ case SMB2_SIGNING_AES128_CMAC:
+ signing = "AES-128-CMAC";
+ break;
+ case SMB2_SIGNING_AES128_GMAC:
+ signing = "AES-128-GMAC";
+ break;
+ default:
+ signing = "???";
+ break;
+ }
+ signing_degree = CRYPTO_DEGREE_FULL;
+ }
+
+ if (!state->json_output) {
+ result = traverse_connections_stdout(state,
+ crec->servicename,
+ server_id_str_buf(crec->pid, &tmp),
+ crec->machine,
+ timestr,
+ encryption,
+ signing);
+ } else {
+ result = traverse_connections_json(state,
+ crec,
+ encryption,
+ encryption_degree,
+ signing,
+ signing_degree);
+ }
+
+ TALLOC_FREE(timestr);
+ TALLOC_FREE(tmp_ctx);
+
+ return result;
+}
+
+static int traverse_sessionid_stdout(struct traverse_state *state,
+ char *server_id,
+ char *uid_gid_str,
+ char *machine_hostname,
+ const char *dialect,
+ const char *encryption_cipher,
+ enum crypto_degree encryption_degree,
+ const char *signing_cipher,
+ enum crypto_degree signing_degree)
+{
+ fstring encryption;
+ fstring signing;
+
+ if (encryption_degree == CRYPTO_DEGREE_FULL) {
+ fstr_sprintf(encryption, "%s", encryption_cipher);
+ } else if (encryption_degree == CRYPTO_DEGREE_PARTIAL) {
+ fstr_sprintf(encryption, "partial(%s)", encryption_cipher);
+ } else {
+ fstr_sprintf(encryption, "-");
+ }
+ if (signing_degree == CRYPTO_DEGREE_FULL) {
+ fstr_sprintf(signing, "%s", signing_cipher);
+ } else if (signing_degree == CRYPTO_DEGREE_PARTIAL) {
+ fstr_sprintf(signing, "partial(%s)", signing_cipher);
+ } else {
+ fstr_sprintf(signing, "-");
+ }
+
+ d_printf("%-7s %-25s %-41s %-17s %-20s %-21s\n",
+ server_id, uid_gid_str, machine_hostname, dialect, encryption,
+ signing);
+
+ return 0;
+}
+
+static int prepare_sessionid(struct traverse_state *state)
+{
+ if (!state->json_output) {
+ /* always print header line */
+ d_printf("\nSamba version %s\n",samba_version_string());
+ d_printf("%-7s %-12s %-12s %-41s %-17s %-20s %-21s\n", "PID", "Username", "Group", "Machine", "Protocol Version", "Encryption", "Signing");
+ d_printf("----------------------------------------------------------------------------------------------------------------------------------------\n");
+ } else {
+ add_section_to_json(state, "sessions");
+ }
+ return 0;
+
+}
+
+static int traverse_sessionid(const char *key, struct sessionid *session,
+ void *private_data)
+{
+ fstring uid_gid_str;
+ fstring uid_str;
+ fstring gid_str;
+ struct server_id_buf tmp;
+ char *machine_hostname = NULL;
+ int result = 0;
+ const char *encryption = "-";
+ enum crypto_degree encryption_degree = CRYPTO_DEGREE_NONE;
+ const char *signing = "-";
+ enum crypto_degree signing_degree = CRYPTO_DEGREE_NONE;
+ struct traverse_state *state = (struct traverse_state *)private_data;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ if (do_checks &&
+ (!process_exists(session->pid) ||
+ !Ucrit_checkUid(session->uid))) {
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+ }
+
+ Ucrit_addPid(session->pid);
+
+ if (numeric_only) {
+ fstr_sprintf(gid_str, "%u", (unsigned int)session->gid);
+ fstr_sprintf(uid_str, "%u", (unsigned int)session->uid);
+ fstr_sprintf(uid_gid_str, "%-12u %-12u",
+ (unsigned int)session->uid,
+ (unsigned int)session->gid);
+ } else {
+ if (session->uid == -1 && session->gid == -1) {
+ /*
+ * The session is not fully authenticated yet.
+ */
+ fstrcpy(uid_gid_str, "(auth in progress)");
+ fstrcpy(gid_str, "(auth in progress)");
+ fstrcpy(uid_str, "(auth in progress)");
+ } else {
+ /*
+ * In theory it should not happen that one of
+ * session->uid and session->gid is valid (ie != -1)
+ * while the other is not (ie = -1), so we a check for
+ * that case that bails out would be reasonable.
+ */
+ const char *uid_name = "-1";
+ const char *gid_name = "-1";
+
+ if (session->uid != -1) {
+ uid_name = uidtoname(session->uid);
+ if (uid_name == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+ }
+ if (session->gid != -1) {
+ gid_name = gidtoname(session->gid);
+ if (gid_name == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+ }
+ fstr_sprintf(gid_str, "%s", gid_name);
+ fstr_sprintf(uid_str, "%s", uid_name);
+ fstr_sprintf(uid_gid_str, "%-12s %-12s",
+ uid_name, gid_name);
+ }
+ }
+
+ machine_hostname = talloc_asprintf(tmp_ctx, "%s (%s)",
+ session->remote_machine,
+ session->hostname);
+ if (machine_hostname == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+ }
+
+ if (smbXsrv_is_encrypted(session->encryption_flags) ||
+ smbXsrv_is_partially_encrypted(session->encryption_flags)) {
+ switch (session->cipher) {
+ case SMB2_ENCRYPTION_AES128_CCM:
+ encryption = "AES-128-CCM";
+ break;
+ case SMB2_ENCRYPTION_AES128_GCM:
+ encryption = "AES-128-GCM";
+ break;
+ case SMB2_ENCRYPTION_AES256_CCM:
+ encryption = "AES-256-CCM";
+ break;
+ case SMB2_ENCRYPTION_AES256_GCM:
+ encryption = "AES-256-GCM";
+ break;
+ default:
+ encryption = "???";
+ result = -1;
+ break;
+ }
+ if (smbXsrv_is_encrypted(session->encryption_flags)) {
+ encryption_degree = CRYPTO_DEGREE_FULL;
+ } else if (smbXsrv_is_partially_encrypted(session->encryption_flags)) {
+ encryption_degree = CRYPTO_DEGREE_PARTIAL;
+ }
+ }
+
+ if (smbXsrv_is_signed(session->signing_flags) ||
+ smbXsrv_is_partially_signed(session->signing_flags)) {
+ switch (session->signing) {
+ case SMB2_SIGNING_MD5_SMB1:
+ signing = "HMAC-MD5";
+ break;
+ case SMB2_SIGNING_HMAC_SHA256:
+ signing = "HMAC-SHA256";
+ break;
+ case SMB2_SIGNING_AES128_CMAC:
+ signing = "AES-128-CMAC";
+ break;
+ case SMB2_SIGNING_AES128_GMAC:
+ signing = "AES-128-GMAC";
+ break;
+ default:
+ signing = "???";
+ result = -1;
+ break;
+ }
+ if (smbXsrv_is_signed(session->signing_flags)) {
+ signing_degree = CRYPTO_DEGREE_FULL;
+ } else if (smbXsrv_is_partially_signed(session->signing_flags)) {
+ signing_degree = CRYPTO_DEGREE_PARTIAL;
+ }
+ }
+
+
+ if (!state->json_output) {
+ traverse_sessionid_stdout(state,
+ server_id_str_buf(session->pid, &tmp),
+ uid_gid_str,
+ machine_hostname,
+ session_dialect_str(session->connection_dialect),
+ encryption,
+ encryption_degree,
+ signing,
+ signing_degree);
+ } else {
+ result = traverse_sessionid_json(state,
+ session,
+ uid_str,
+ gid_str,
+ encryption,
+ encryption_degree,
+ signing,
+ signing_degree,
+ session_dialect_str(session->connection_dialect));
+ }
+
+ TALLOC_FREE(machine_hostname);
+ TALLOC_FREE(tmp_ctx);
+
+ return result;
+}
+
+
+static bool print_notify_rec_stdout(struct traverse_state *state,
+ const char *path,
+ char *server_id_str,
+ unsigned filter,
+ unsigned subdir_filter)
+{
+ d_printf("%s\\%s\\%x\\%x\n", path, server_id_str,
+ filter, subdir_filter);
+
+ return true;
+}
+
+static int prepare_notify(struct traverse_state *state)
+{
+ if (!state->json_output) {
+ /* don't print header line */
+ } else {
+ add_section_to_json(state, "notifies");
+ }
+ return 0;
+}
+
+static bool print_notify_rec(const char *path, struct server_id server,
+ const struct notify_instance *instance,
+ void *private_data)
+{
+ struct server_id_buf idbuf;
+ struct traverse_state *state = (struct traverse_state *)private_data;
+ bool result;
+
+ if (!state->json_output) {
+ result = print_notify_rec_stdout(state,
+ path,
+ server_id_str_buf(server, &idbuf),
+ (unsigned)instance->filter,
+ (unsigned)instance->subdir_filter);
+
+ } else {
+ result = print_notify_rec_json(state,
+ instance,
+ server,
+ path);
+ }
+
+ return result;
+}
+
+enum {
+ OPT_RESOLVE_UIDS = 1000,
+};
+
+int main(int argc, const char *argv[])
+{
+ int c;
+ int profile_only = 0;
+ bool show_processes, show_locks, show_shares;
+ bool show_notify = false;
+ poptContext pc = NULL;
+ struct traverse_state state = {0};
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "processes",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'p',
+ .descrip = "Show processes only",
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'v',
+ .descrip = "Be verbose",
+ },
+ {
+ .longName = "locks",
+ .shortName = 'L',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'L',
+ .descrip = "Show locks only",
+ },
+ {
+ .longName = "shares",
+ .shortName = 'S',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'S',
+ .descrip = "Show shares only",
+ },
+ {
+ .longName = "notify",
+ .shortName = 'N',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'N',
+ .descrip = "Show notifies",
+ },
+ {
+ .longName = "user",
+ .shortName = 'u',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &username,
+ .val = 'u',
+ .descrip = "Switch to user",
+ },
+ {
+ .longName = "brief",
+ .shortName = 'b',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'b',
+ .descrip = "Be brief",
+ },
+ {
+ .longName = "profile",
+ .shortName = 'P',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'P',
+ .descrip = "Do profiling",
+ },
+ {
+ .longName = "profile-rates",
+ .shortName = 'R',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'R',
+ .descrip = "Show call rates",
+ },
+ {
+ .longName = "byterange",
+ .shortName = 'B',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'B',
+ .descrip = "Include byte range locks"
+ },
+ {
+ .longName = "numeric",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'n',
+ .descrip = "Numeric uid/gid"
+ },
+ {
+ .longName = "json",
+ .shortName = 'j',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'j',
+ .descrip = "JSON output"
+ },
+ {
+ .longName = "fast",
+ .shortName = 'f',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'f',
+ .descrip = "Skip checks if processes still exist"
+ },
+ {
+ .longName = "resolve-uids",
+ .shortName = 0,
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = OPT_RESOLVE_UIDS,
+ .descrip = "Try to resolve UIDs to usernames"
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+ int ret = 0;
+ struct messaging_context *msg_ctx = NULL;
+ char *db_path;
+ bool ok;
+ struct loadparm_context *lp_ctx = NULL;
+
+ state.first = true;
+ state.json_output = false;
+ state.resolve_uids = false;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+ lpcfg_set_cmdline(lp_ctx, "log level", "0");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ POPT_CONTEXT_KEEP_FIRST);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((c = poptGetNextOpt(pc)) != -1) {
+ switch (c) {
+ case 'p':
+ processes_only = true;
+ break;
+ case 'v':
+ verbose = true;
+ break;
+ case 'L':
+ locks_only = true;
+ break;
+ case 'S':
+ shares_only = true;
+ break;
+ case 'N':
+ show_notify = true;
+ break;
+ case 'b':
+ brief = true;
+ break;
+ case 'u':
+ Ucrit_addUid(nametouid(poptGetOptArg(pc)));
+ break;
+ case 'P':
+ case 'R':
+ profile_only = c;
+ break;
+ case 'B':
+ show_brl = true;
+ break;
+ case 'n':
+ numeric_only = true;
+ break;
+ case 'j':
+ state.json_output = true;
+ break;
+ case 'f':
+ do_checks = false;
+ break;
+ case OPT_RESOLVE_UIDS:
+ state.resolve_uids = true;
+ break;
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(c));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ sec_init();
+
+#ifdef HAVE_JANSSON
+ state.root_json = json_new_object();
+ if (!json_is_invalid(&state.root_json)) {
+ add_general_information_to_json(&state);
+ }
+#else /* HAVE_JANSSON */
+ if (state.json_output) {
+ fprintf(stderr, "JSON support not available, please install lib Jansson\n");
+ goto done;
+ }
+#endif /* HAVE_JANSSON */
+
+ if (getuid() != geteuid()) {
+ fprintf(stderr, "smbstatus should not be run setuid\n");
+ ret = 1;
+ goto done;
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr, "smbstatus only works as root!\n");
+ ret = 1;
+ goto done;
+ }
+
+ /* setup the flags based on the possible combincations */
+
+ show_processes = !(shares_only || locks_only || profile_only) || processes_only;
+ show_locks = !(shares_only || processes_only || profile_only) || locks_only;
+ show_shares = !(processes_only || locks_only || profile_only) || shares_only;
+
+ if ( username )
+ Ucrit_addUid( nametouid(username) );
+
+ if (verbose && !state.json_output) {
+ d_printf("using configfile = %s\n", get_dyn_CONFIGFILE());
+ }
+
+ msg_ctx = cmdline_messaging_context(get_dyn_CONFIGFILE());
+ if (msg_ctx == NULL) {
+ fprintf(stderr, "Could not initialize messaging, not root?\n");
+ ret = -1;
+ goto done;
+ }
+
+ switch (profile_only) {
+ case 'P':
+ /* Dump profile data */
+ ok = status_profile_dump(verbose, &state);
+ ret = ok ? 0 : 1;
+ goto done;
+ case 'R':
+ /* Continuously display rate-converted data */
+ if (!state.json_output) {
+ ok = status_profile_rates(verbose);
+ ret = ok ? 0 : 1;
+ } else {
+ fprintf(stderr, "Call rates not available in a json output.\n");
+ ret = 1;
+ }
+ goto done;
+ default:
+ break;
+ }
+
+ if ( show_processes ) {
+ prepare_sessionid(&state);
+ sessionid_traverse_read(traverse_sessionid, &state);
+
+ if (processes_only) {
+ goto done;
+ }
+ }
+
+ if ( show_shares ) {
+ if (brief) {
+ goto done;
+ }
+ prepare_connections(&state);
+ connections_forall_read(traverse_connections, &state);
+
+ if (!state.json_output) {
+ d_printf("\n");
+ }
+
+ if ( shares_only ) {
+ goto done;
+ }
+ }
+
+ if ( show_locks ) {
+ int result;
+ struct db_context *db;
+
+ db_path = lock_path(talloc_tos(), "locking.tdb");
+ if (db_path == NULL) {
+ fprintf(stderr, "Out of memory - exiting\n");
+ ret = -1;
+ goto done;
+ }
+
+ db = db_open(NULL, db_path, 0,
+ TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH, O_RDONLY, 0,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+
+ if (!db) {
+ fprintf(stderr, "%s not initialised\n", db_path);
+ fprintf(stderr, "This is normal if an SMB client has never "
+ "connected to your server.\n");
+ TALLOC_FREE(db_path);
+ ret = 0;
+ goto done;
+ } else {
+ TALLOC_FREE(db);
+ TALLOC_FREE(db_path);
+ }
+
+ if (!locking_init_readonly()) {
+ fprintf(stderr, "Can't initialise locking module - exiting\n");
+ ret = 1;
+ goto done;
+ }
+
+ prepare_share_mode(&state);
+ result = share_entry_forall(print_share_mode, &state);
+
+ if (result == 0 && !state.json_output) {
+ fprintf(stderr, "No locked files\n");
+ } else if (result < 0 && !state.json_output) {
+ fprintf(stderr, "locked file list truncated\n");
+ }
+
+ if (!state.json_output) {
+ d_printf("\n");
+ }
+
+ if (show_brl) {
+ prepare_brl(&state);
+ brl_forall(print_brl, &state);
+ }
+
+ locking_end();
+ }
+
+ if (show_notify) {
+ prepare_notify(&state);
+ notify_walk(msg_ctx, print_notify_rec, &state);
+ }
+
+done:
+ cmdline_messaging_context_free();
+ poptFreeContext(pc);
+#ifdef HAVE_JANSSON
+ if (state.json_output) {
+ d_printf("%s\n", json_to_string(frame, &state.root_json));
+ }
+ json_free(&state.root_json);
+#endif /* HAVE_JANSSON */
+ TALLOC_FREE(frame);
+ return ret;
+}
diff --git a/source3/utils/status.h b/source3/utils/status.h
new file mode 100644
index 0000000..c08aba4
--- /dev/null
+++ b/source3/utils/status.h
@@ -0,0 +1,44 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * State struct
+ * Copyright (C) Jule Anger 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_JANSSON
+#include <jansson.h>
+#include "audit_logging.h" /* various JSON helpers */
+#include "auth/common_auth.h"
+#endif /* HAVE_JANSSON */
+
+#ifndef STATUS_H
+#define STATUS_H
+
+struct traverse_state {
+ bool json_output;
+ bool first;
+ bool resolve_uids;
+#ifdef HAVE_JANSSON
+ struct json_object root_json;
+#endif /* HAVE_JANSSON */
+};
+
+enum crypto_degree {
+ CRYPTO_DEGREE_NONE,
+ CRYPTO_DEGREE_PARTIAL,
+ CRYPTO_DEGREE_FULL
+};
+
+#endif
diff --git a/source3/utils/status_json.c b/source3/utils/status_json.c
new file mode 100644
index 0000000..ee24a3b
--- /dev/null
+++ b/source3/utils/status_json.c
@@ -0,0 +1,1386 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Json output
+ * Copyright (C) Jule Anger 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbprofile.h"
+#include "lib/util/time_basic.h"
+#include "conn_tdb.h"
+#include "session.h"
+#include "librpc/gen_ndr/smbXsrv.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "status_json.h"
+#include "../libcli/security/security.h"
+#include "status.h"
+#include "lib/util/server_id.h"
+#include "lib/util/string_wrappers.h"
+
+#include <jansson.h>
+#include "audit_logging.h" /* various JSON helpers */
+#include "auth/common_auth.h"
+
+int add_general_information_to_json(struct traverse_state *state)
+{
+ int result;
+
+ result = json_add_timestamp(&state->root_json);
+ if (result < 0) {
+ return -1;
+ }
+
+ result = json_add_string(&state->root_json, "version", samba_version_string());
+ if (result < 0) {
+ return -1;
+ }
+
+ result = json_add_string(&state->root_json, "smb_conf", get_dyn_CONFIGFILE());
+ if (result < 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int add_server_id_to_json(struct json_object *parent_json,
+ const struct server_id server_id)
+{
+ struct json_object sub_json;
+ char *pid_str = NULL;
+ char *task_id_str = NULL;
+ char *vnn_str = NULL;
+ char *unique_id_str = NULL;
+ int result;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+
+ pid_str = talloc_asprintf(
+ tmp_ctx, "%lu", (unsigned long)server_id.pid);
+ result = json_add_string(&sub_json, "pid", pid_str);
+ if (result < 0) {
+ goto failure;
+ }
+ task_id_str = talloc_asprintf(tmp_ctx, "%u", server_id.task_id);
+ result = json_add_string(&sub_json, "task_id", task_id_str);
+ if (result < 0) {
+ goto failure;
+ }
+ vnn_str = talloc_asprintf(tmp_ctx, "%u", server_id.vnn);
+ result = json_add_string(&sub_json, "vnn", vnn_str);
+ if (result < 0) {
+ goto failure;
+ }
+ unique_id_str = talloc_asprintf(
+ tmp_ctx, "%"PRIu64, server_id.unique_id);
+ result = json_add_string(&sub_json, "unique_id", unique_id_str);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(parent_json, "server_id", &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&sub_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+struct mask2txt {
+ uint32_t mask;
+ const char *string_desc;
+};
+
+/*
+ * Convert a mask of some sort (access, oplock, leases),
+ * to key/value pairs in a JSON object.
+ */
+static int map_mask_to_json(struct json_object *root_json,
+ uint32_t tomap,
+ const struct mask2txt *table)
+{
+ const struct mask2txt *a = NULL;
+ int result = 0;
+
+ for (a = table; a->string_desc != 0; a++) {
+ result = json_add_bool(root_json, a->string_desc,
+ (tomap & a->mask) ? true : false);
+
+ if (result < 0) {
+ return result;
+ }
+ tomap &= ~a->mask;
+ }
+
+ /* Assert we know about all requested "tomap" values */
+ SMB_ASSERT(tomap == 0);
+
+ return 0;
+}
+
+static const struct mask2txt access_mask[] = {
+ {FILE_READ_DATA, "READ_DATA"},
+ {FILE_WRITE_DATA, "WRITE_DATA"},
+ {FILE_APPEND_DATA, "APPEND_DATA"},
+ {FILE_READ_EA, "READ_EA"},
+ {FILE_WRITE_EA, "WRITE_EA"},
+ {FILE_EXECUTE, "EXECUTE"},
+ {FILE_READ_ATTRIBUTES, "READ_ATTRIBUTES"},
+ {FILE_WRITE_ATTRIBUTES, "WRITE_ATTRIBUTES"},
+ {FILE_DELETE_CHILD, "DELETE_CHILD"},
+ {SEC_STD_DELETE, "DELETE"},
+ {SEC_STD_READ_CONTROL, "READ_CONTROL"},
+ {SEC_STD_WRITE_DAC, "WRITE_DAC"},
+ {SEC_STD_SYNCHRONIZE, "SYNCHRONIZE"},
+ {SEC_FLAG_SYSTEM_SECURITY, "ACCESS_SYSTEM_SECURITY"},
+ {0, NULL}
+};
+
+static const struct mask2txt oplock_mask[] = {
+ {EXCLUSIVE_OPLOCK, "EXCLUSIVE"},
+ {BATCH_OPLOCK, "BATCH"},
+ {LEVEL_II_OPLOCK, "LEVEL_II"},
+ {LEASE_OPLOCK, "LEASE"},
+ {0, NULL}
+};
+
+static const struct mask2txt sharemode_mask[] = {
+ {FILE_SHARE_READ, "READ"},
+ {FILE_SHARE_WRITE, "WRITE"},
+ {FILE_SHARE_DELETE, "DELETE"},
+ {0, NULL}
+};
+
+static const struct mask2txt lease_mask[] = {
+ {SMB2_LEASE_READ, "READ"},
+ {SMB2_LEASE_WRITE, "WRITE"},
+ {SMB2_LEASE_HANDLE, "HANDLE"},
+ {0, NULL}
+};
+
+int add_profile_item_to_json(struct traverse_state *state,
+ const char *section,
+ const char *subsection,
+ const char *key,
+ uintmax_t value)
+{
+ struct json_object section_json = {
+ .valid = false,
+ };
+ struct json_object subsection_json = {
+ .valid = false,
+ };
+ int result = 0;
+
+ section_json = json_get_object(&state->root_json, section);
+ if (json_is_invalid(&section_json)) {
+ goto failure;
+ }
+ subsection_json = json_get_object(&section_json, subsection);
+ if (json_is_invalid(&subsection_json)) {
+ goto failure;
+ }
+
+ result = json_add_int(&subsection_json, key, value);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_update_object(&section_json, subsection, &subsection_json);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_update_object(&state->root_json, section, &section_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ return 0;
+failure:
+ json_free(&section_json);
+ json_free(&subsection_json);
+ return -1;
+}
+
+int add_section_to_json(struct traverse_state *state,
+ const char *key)
+{
+ struct json_object empty_json;
+ int result;
+
+ empty_json = json_new_object();
+ if (json_is_invalid(&empty_json)) {
+ return -1;
+ }
+
+ result = json_add_object(&state->root_json, key, &empty_json);
+ if (result < 0) {
+ return -1;
+ }
+
+ return result;
+}
+
+static int add_crypto_to_json(struct json_object *parent_json,
+ const char *key,
+ const char *cipher,
+ enum crypto_degree degree)
+{
+ struct json_object sub_json;
+ const char *degree_str;
+ int result;
+
+ if (degree == CRYPTO_DEGREE_NONE) {
+ degree_str = "none";
+ } else if (degree == CRYPTO_DEGREE_PARTIAL) {
+ degree_str = "partial";
+ } else {
+ degree_str = "full";
+ }
+
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+
+ result = json_add_string(&sub_json, "cipher", cipher);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "degree", degree_str);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_object(parent_json, key, &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ return 0;
+failure:
+ json_free(&sub_json);
+ return -1;
+}
+
+static int add_channel_to_json(struct json_object *parent_json,
+ const struct smbXsrv_channel_global0 *channel)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct json_object sub_json;
+ char *id_str = NULL;
+ struct timeval tv;
+ struct timeval_buf tv_buf;
+ char *time_str = NULL;
+ int result;
+
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+
+ id_str = talloc_asprintf(frame, "%"PRIu64"", channel->channel_id);
+ if (id_str == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "channel_id", id_str);
+ if (result < 0) {
+ goto failure;
+ }
+ nttime_to_timeval(&tv, channel->creation_time);
+ time_str = timeval_str_buf(&tv, true, true, &tv_buf);
+ if (time_str == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "creation_time", time_str);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "local_address", channel->local_address);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "remote_address", channel->remote_address);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(parent_json, id_str, &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(frame);
+ return 0;
+failure:
+ json_free(&sub_json);
+ TALLOC_FREE(frame);
+ return -1;
+}
+
+static int add_channels_to_json(struct json_object *parent_json,
+ const struct smbXsrv_session_global0 *global)
+{
+ struct json_object sub_json;
+ uint32_t i;
+ int result;
+
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+
+ for (i = 0; i < global->num_channels; i++) {
+ const struct smbXsrv_channel_global0 *c = &global->channels[i];
+
+ result = add_channel_to_json(&sub_json, c);
+ if (result < 0) {
+ goto failure;
+ }
+ }
+
+ result = json_add_object(parent_json, "channels", &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ return 0;
+failure:
+ json_free(&sub_json);
+ return -1;
+}
+
+int traverse_connections_json(struct traverse_state *state,
+ const struct connections_data *crec,
+ const char *encryption_cipher,
+ enum crypto_degree encryption_degree,
+ const char *signing_cipher,
+ enum crypto_degree signing_degree)
+{
+ struct json_object sub_json;
+ struct json_object connections_json;
+ struct timeval tv;
+ struct timeval_buf tv_buf;
+ char *time = NULL;
+ int result = 0;
+ char *sess_id_str = NULL;
+ char *tcon_id_str = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+ connections_json = json_get_object(&state->root_json, "tcons");
+ if (json_is_invalid(&connections_json)) {
+ goto failure;
+ }
+
+ result = json_add_string(&sub_json, "service", crec->servicename);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_server_id_to_json(&sub_json, crec->pid);
+ if (result < 0) {
+ goto failure;
+ }
+ tcon_id_str = talloc_asprintf(tmp_ctx, "%u", crec->cnum);
+ if (tcon_id_str == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "tcon_id", tcon_id_str);
+ if (result < 0) {
+ goto failure;
+ }
+ sess_id_str = talloc_asprintf(tmp_ctx, "%u", crec->sess_id);
+ if (sess_id_str == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "session_id", sess_id_str);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "machine", crec->machine);
+ if (result < 0) {
+ goto failure;
+ }
+ nttime_to_timeval(&tv, crec->start);
+ time = timeval_str_buf(&tv, true, true, &tv_buf);
+ if (time == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "connected_at", time);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_crypto_to_json(&sub_json, "encryption",
+ encryption_cipher, encryption_degree);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_crypto_to_json(&sub_json, "signing",
+ signing_cipher, signing_degree);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(&connections_json, tcon_id_str, &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_update_object(&state->root_json, "tcons", &connections_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&sub_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+int traverse_sessionid_json(struct traverse_state *state,
+ struct sessionid *session,
+ char *uid_str,
+ char *gid_str,
+ const char *encryption_cipher,
+ enum crypto_degree encryption_degree,
+ const char *signing_cipher,
+ enum crypto_degree signing_degree,
+ const char *connection_dialect)
+{
+ struct json_object sub_json;
+ struct json_object session_json;
+ int result = 0;
+ char *id_str = NULL;
+ struct timeval tv;
+ struct timeval_buf tv_buf;
+ char *time_str = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+
+ session_json = json_get_object(&state->root_json, "sessions");
+ if (json_is_invalid(&session_json)) {
+ goto failure;
+ }
+
+ id_str = talloc_asprintf(tmp_ctx, "%u", session->id_num);
+ result = json_add_string(&sub_json, "session_id", id_str);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_server_id_to_json(&sub_json, session->pid);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_int(&sub_json, "uid", session->uid);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_int(&sub_json, "gid", session->gid);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "username", uid_str);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "groupname", gid_str);
+ if (result < 0) {
+ goto failure;
+ }
+
+ nttime_to_timeval(&tv, session->global->creation_time);
+ time_str = timeval_str_buf(&tv, true, true, &tv_buf);
+ if (time_str == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "creation_time", time_str);
+ if (result < 0) {
+ goto failure;
+ }
+
+ nttime_to_timeval(&tv, session->global->expiration_time);
+ time_str = timeval_str_buf(&tv, true, true, &tv_buf);
+ if (time_str == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "expiration_time", time_str);
+ if (result < 0) {
+ goto failure;
+ }
+
+ nttime_to_timeval(&tv, session->global->auth_time);
+ time_str = timeval_str_buf(&tv, true, true, &tv_buf);
+ if (time_str == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "auth_time", time_str);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_string(&sub_json, "remote_machine", session->remote_machine);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "hostname", session->hostname);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "session_dialect", connection_dialect);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_guid(&sub_json,
+ "client_guid",
+ &session->global->client_guid);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_crypto_to_json(&sub_json, "encryption",
+ encryption_cipher, encryption_degree);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_crypto_to_json(&sub_json, "signing",
+ signing_cipher, signing_degree);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = add_channels_to_json(&sub_json, session->global);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(&session_json, id_str, &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_update_object(&state->root_json, "sessions", &session_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&sub_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_access_mode_to_json(struct json_object *parent_json,
+ int access_int)
+{
+ struct json_object access_json;
+ char *access_hex = NULL;
+ const char *access_str = NULL;
+ int result;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ access_json = json_new_object();
+ if (json_is_invalid(&access_json)) {
+ goto failure;
+ }
+
+ access_hex = talloc_asprintf(tmp_ctx, "0x%08x", access_int);
+ result = json_add_string(&access_json, "hex", access_hex);
+ if (result < 0) {
+ goto failure;
+ }
+ result = map_mask_to_json(&access_json, access_int, access_mask);
+ if (result < 0) {
+ goto failure;
+ }
+
+ access_str = talloc_asprintf(tmp_ctx, "%s%s",
+ (access_int & FILE_READ_DATA)?"R":"",
+ (access_int & (FILE_WRITE_DATA|FILE_APPEND_DATA))?"W":"");
+ result = json_add_string(&access_json, "text", access_str);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(parent_json, "access_mask", &access_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&access_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_caching_to_json(struct json_object *parent_json,
+ int op_type,
+ int lease_type)
+{
+ struct json_object caching_json;
+ char *hex = NULL;
+ char *caching_text = NULL;
+ int caching_type = 0;
+ int result;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ caching_json = json_new_object();
+ if (json_is_invalid(&caching_json)) {
+ goto failure;
+ }
+
+ if (op_type & LEASE_OPLOCK) {
+ caching_type = lease_type;
+ } else {
+ if (op_type & LEVEL_II_OPLOCK) {
+ caching_type = SMB2_LEASE_READ;
+ } else if (op_type & EXCLUSIVE_OPLOCK) {
+ caching_type = SMB2_LEASE_READ + SMB2_LEASE_WRITE;
+ } else if (op_type & BATCH_OPLOCK) {
+ caching_type = SMB2_LEASE_READ + SMB2_LEASE_WRITE + SMB2_LEASE_HANDLE;
+ }
+ }
+ result = map_mask_to_json(&caching_json, caching_type, lease_mask);
+ if (result < 0) {
+ goto failure;
+ }
+
+ hex = talloc_asprintf(tmp_ctx, "0x%08x", caching_type);
+ if (hex == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&caching_json, "hex", hex);
+ if (result < 0) {
+ goto failure;
+ }
+
+ caching_text = talloc_asprintf(tmp_ctx, "%s%s%s",
+ (caching_type & SMB2_LEASE_READ)?"R":"",
+ (caching_type & SMB2_LEASE_WRITE)?"W":"",
+ (caching_type & SMB2_LEASE_HANDLE)?"H":"");
+ if (caching_text == NULL) {
+ return -1;
+ }
+
+ result = json_add_string(&caching_json, "text", caching_text);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(parent_json, "caching", &caching_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&caching_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_oplock_to_json(struct json_object *parent_json,
+ uint16_t op_type,
+ const char *op_str)
+{
+ struct json_object oplock_json;
+ int result;
+
+ oplock_json = json_new_object();
+ if (json_is_invalid(&oplock_json)) {
+ goto failure;
+ }
+
+ if (op_type != 0) {
+ result = map_mask_to_json(&oplock_json, op_type, oplock_mask);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&oplock_json, "text", op_str);
+ if (result < 0) {
+ goto failure;
+ }
+ }
+
+ result = json_add_object(parent_json, "oplock", &oplock_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ return 0;
+failure:
+ json_free(&oplock_json);
+ return -1;
+}
+
+static int lease_key_to_str(struct smb2_lease_key lease_key,
+ char *lease_str)
+{
+ uint8_t _buf[16] = {0};
+ DATA_BLOB blob = data_blob_const(_buf, sizeof(_buf));
+ struct GUID guid;
+ NTSTATUS status;
+ char *tmp = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ PUSH_LE_U64(_buf, 0, lease_key.data[0]);
+ PUSH_LE_U64(_buf, 8, lease_key.data[1]);
+
+ status = GUID_from_ndr_blob(&blob, &guid);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto failure;
+ }
+ tmp = GUID_string(tmp_ctx, &guid);
+ if (tmp == NULL) {
+ goto failure;
+ }
+ fstrcpy(lease_str, tmp);
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_lease_to_json(struct json_object *parent_json,
+ int lease_type,
+ struct smb2_lease_key lease_key,
+ bool add_lease)
+{
+ struct json_object lease_json;
+ char *lease_hex = NULL;
+ char *lease_text = NULL;
+ fstring lease_key_str;
+ int result;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ lease_json = json_new_object();
+ if (json_is_invalid(&lease_json)) {
+ goto failure;
+ }
+
+
+ if (add_lease) {
+ result = lease_key_to_str(lease_key, lease_key_str);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&lease_json, "lease_key", lease_key_str);
+ if (result < 0) {
+ goto failure;
+ }
+ lease_hex = talloc_asprintf(tmp_ctx, "0x%08x", lease_type);
+ result = json_add_string(&lease_json, "hex", lease_hex);
+ if (result < 0) {
+ goto failure;
+ }
+ if (lease_type > (SMB2_LEASE_WRITE + SMB2_LEASE_HANDLE + SMB2_LEASE_READ)) {
+ result = json_add_bool(&lease_json, "UNKNOWN", true);
+ if (result < 0) {
+ goto failure;
+ }
+ } else {
+ result = map_mask_to_json(&lease_json, lease_type, lease_mask);
+ if (result < 0) {
+ goto failure;
+ }
+ }
+ lease_text = talloc_asprintf(tmp_ctx, "%s%s%s",
+ (lease_type & SMB2_LEASE_READ)?"R":"",
+ (lease_type & SMB2_LEASE_WRITE)?"W":"",
+ (lease_type & SMB2_LEASE_HANDLE)?"H":"");
+
+ result = json_add_string(&lease_json, "text", lease_text);
+ if (result < 0) {
+ goto failure;
+ }
+ }
+
+ result = json_add_object(parent_json, "lease", &lease_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&lease_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_sharemode_to_json(struct json_object *parent_json,
+ int sharemode)
+{
+ struct json_object sharemode_json;
+ char *hex = NULL;
+ char *text = NULL;
+ int result;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ sharemode_json = json_new_object();
+ if (json_is_invalid(&sharemode_json)) {
+ goto failure;
+ }
+
+ hex = talloc_asprintf(tmp_ctx, "0x%08x", sharemode);
+ if (hex == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sharemode_json, "hex", hex);
+ if (result < 0) {
+ goto failure;
+ }
+ result = map_mask_to_json(&sharemode_json, sharemode, sharemode_mask);
+ if (result < 0) {
+ goto failure;
+ }
+
+ text = talloc_asprintf(tmp_ctx, "%s%s%s",
+ (sharemode & FILE_SHARE_READ)?"R":"",
+ (sharemode & FILE_SHARE_WRITE)?"W":"",
+ (sharemode & FILE_SHARE_DELETE)?"D":"");
+ if (text == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sharemode_json, "text", text);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(parent_json, "sharemode", &sharemode_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&sharemode_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_open_to_json(struct json_object *parent_json,
+ const struct share_mode_entry *e,
+ bool resolve_uids,
+ const char *op_str,
+ uint32_t lease_type,
+ const char *uid_str)
+{
+ struct json_object sub_json = {
+ .valid = false,
+ };
+ struct json_object opens_json = {
+ .valid = false,
+ };
+ struct timeval_buf tv_buf;
+ int result = 0;
+ char *timestr;
+ bool add_lease = false;
+ char *key = NULL;
+ char *share_file_id = NULL;
+ char *pid = NULL;
+ struct server_id_buf tmp;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ opens_json = json_get_object(parent_json, "opens");
+ if (json_is_invalid(&opens_json)) {
+ goto failure;
+ }
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+
+
+ result = add_server_id_to_json(&sub_json, e->pid);
+ if (result < 0) {
+ goto failure;
+ }
+ if (resolve_uids) {
+ result = json_add_string(&sub_json, "username", uid_str);
+ if (result < 0) {
+ goto failure;
+ }
+ }
+ result = json_add_int(&sub_json, "uid", e->uid);
+ if (result < 0) {
+ goto failure;
+ }
+ share_file_id = talloc_asprintf(tmp_ctx, "%"PRIu64, e->share_file_id);
+ result = json_add_string(&sub_json, "share_file_id", share_file_id);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_sharemode_to_json(&sub_json, e->share_access);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_access_mode_to_json(&sub_json, e->access_mask);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_caching_to_json(&sub_json, e->op_type, lease_type);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_oplock_to_json(&sub_json, e->op_type, op_str);
+ if (result < 0) {
+ goto failure;
+ }
+ add_lease = e->op_type & LEASE_OPLOCK;
+ result = add_lease_to_json(&sub_json, lease_type, e->lease_key, add_lease);
+ if (result < 0) {
+ goto failure;
+ }
+
+ timestr = timeval_str_buf(&e->time, true, true, &tv_buf);
+ if (timestr == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "opened_at", timestr);
+ if (result < 0) {
+ goto failure;
+ }
+
+ pid = server_id_str_buf(e->pid, &tmp);
+ key = talloc_asprintf(tmp_ctx, "%s/%"PRIu64, pid, e->share_file_id);
+ result = json_add_object(&opens_json, key, &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_update_object(parent_json, "opens", &opens_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&opens_json);
+ json_free(&sub_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_fileid_to_json(struct json_object *parent_json,
+ struct file_id fid)
+{
+ struct json_object fid_json;
+ int result;
+
+ fid_json = json_new_object();
+ if (json_is_invalid(&fid_json)) {
+ goto failure;
+ }
+
+ result = json_add_int(&fid_json, "devid", fid.devid);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_int(&fid_json, "inode", fid.inode);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_int(&fid_json, "extid", fid.extid);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(parent_json, "fileid", &fid_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ return 0;
+failure:
+ json_free(&fid_json);
+ return -1;
+}
+
+int print_share_mode_json(struct traverse_state *state,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ struct file_id fid,
+ const char *uid_str,
+ const char *op_str,
+ uint32_t lease_type,
+ const char *filename)
+{
+ struct json_object locks_json = {
+ .valid = false,
+ };
+ struct json_object file_json = {
+ .valid = false,
+ };
+ char *key = NULL;
+ int result = 0;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ if (d->servicepath[strlen(d->servicepath)-1] == '/') {
+ key = talloc_asprintf(tmp_ctx, "%s%s", d->servicepath, filename);
+ } else {
+ key = talloc_asprintf(tmp_ctx, "%s/%s", d->servicepath, filename);
+ }
+
+ locks_json = json_get_object(&state->root_json, "open_files");
+ if (json_is_invalid(&locks_json)) {
+ goto failure;
+ }
+ file_json = json_get_object(&locks_json, key);
+ if (json_is_invalid(&file_json)) {
+ goto failure;
+ }
+
+ result = json_add_string(&file_json, "service_path", d->servicepath);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&file_json, "filename", filename);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_fileid_to_json(&file_json, fid);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_int(&file_json, "num_pending_deletes", d->num_delete_tokens);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = add_open_to_json(&file_json,
+ e,
+ state->resolve_uids,
+ op_str,
+ lease_type,
+ uid_str);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_update_object(&locks_json, key, &file_json);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_update_object(&state->root_json, "open_files", &locks_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&file_json);
+ json_free(&locks_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+static int add_lock_to_json(struct json_object *parent_json,
+ struct server_id server_id,
+ const char *type,
+ enum brl_flavour flavour,
+ intmax_t start,
+ intmax_t size)
+{
+ struct json_object sub_json = {
+ .valid = false,
+ };
+ struct json_object locks_json = {
+ .valid = false,
+ };
+ const char *flavour_str;
+ int result = 0;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ locks_json = json_get_array(parent_json, "locks");
+ if (json_is_invalid(&locks_json)) {
+ goto failure;
+ }
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ goto failure;
+ }
+
+ result = add_server_id_to_json(&sub_json, server_id);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "type", type);
+ if (result < 0) {
+ goto failure;
+ }
+ flavour_str = talloc_asprintf(tmp_ctx, "%s%s",
+ (flavour == WINDOWS_LOCK)?"Windows":"",
+ (flavour == POSIX_LOCK)?"Posix":"");
+ result = json_add_string(&sub_json, "flavour", flavour_str);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_int(&sub_json, "start", start);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_int(&sub_json, "size", size);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(&locks_json, NULL, &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_update_object(parent_json, "locks", &locks_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&locks_json);
+ json_free(&sub_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+int print_brl_json(struct traverse_state *state,
+ const struct server_id server_id,
+ struct file_id fid,
+ const char *type,
+ enum brl_flavour flavour,
+ intmax_t start,
+ intmax_t size,
+ const char *sharepath,
+ const char *filename)
+{
+ struct json_object file_json = {
+ .valid = false,
+ };
+ struct json_object brl_json = {
+ .valid = false,
+ };
+ int result = 0;
+ char *key;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ if (sharepath[strlen(sharepath)-1] == '/') {
+ key = talloc_asprintf(tmp_ctx, "%s%s", sharepath, filename);
+ } else {
+ key = talloc_asprintf(tmp_ctx, "%s/%s", sharepath, filename);
+ }
+ if (key == NULL) {
+ goto failure;
+ }
+
+ brl_json = json_get_object(&state->root_json, "byte_range_locks");
+ if (json_is_invalid(&brl_json)) {
+ goto failure;
+ }
+ file_json = json_get_object(&brl_json, key);
+ if (json_is_invalid(&file_json)) {
+ goto failure;
+ }
+
+ result = add_fileid_to_json(&file_json, fid);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&file_json, "file_name", filename);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&file_json, "share_path", sharepath);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_server_id_to_json(&file_json, server_id);
+ if (result < 0) {
+ goto failure;
+ }
+ result = add_lock_to_json(&file_json, server_id, type, flavour, start, size);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_add_object(&brl_json, key, &file_json);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_update_object(&state->root_json, "byte_range_locks", &brl_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return 0;
+failure:
+ json_free(&file_json);
+ json_free(&brl_json);
+ TALLOC_FREE(tmp_ctx);
+ return -1;
+}
+
+bool print_notify_rec_json(struct traverse_state *state,
+ const struct notify_instance *instance,
+ const struct server_id server_id,
+ const char *path)
+{
+ struct json_object sub_json;
+ struct json_object notify_json;
+ char *filter = NULL;
+ char *subdir_filter = NULL;
+ struct timeval_buf tv_buf;
+ struct timeval val;
+ char *time = NULL;
+ char *pid = NULL;
+ struct server_id_buf tmp;
+ int result = 0;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return -1;
+ }
+
+ sub_json = json_new_object();
+ if (json_is_invalid(&sub_json)) {
+ return false;
+ }
+ notify_json = json_get_object(&state->root_json, "notifies");
+ if (json_is_invalid(&notify_json)) {
+ goto failure;
+ }
+
+ result = add_server_id_to_json(&sub_json, server_id);
+ if (result < 0) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "path", path);
+ if (result < 0) {
+ goto failure;
+ }
+ filter = talloc_asprintf(tmp_ctx, "%u", instance->filter);
+ if (filter == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "filter", filter);
+ if (result < 0) {
+ goto failure;
+ }
+ subdir_filter = talloc_asprintf(tmp_ctx, "%u", instance->subdir_filter);
+ if (subdir_filter == NULL) {
+ goto failure;
+ }
+ result = json_add_string(&sub_json, "subdir_filter", subdir_filter);
+ if (result < 0) {
+ goto failure;
+ }
+ val = convert_timespec_to_timeval(instance->creation_time);
+ time = timeval_str_buf(&val, true, true, &tv_buf);
+ result = json_add_string(&sub_json, "creation_time", time);
+ if (result < 0) {
+ goto failure;
+ }
+
+ pid = server_id_str_buf(server_id, &tmp);
+ result = json_add_object(&notify_json, pid, &sub_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ result = json_update_object(&state->root_json, "notifies", &notify_json);
+ if (result < 0) {
+ goto failure;
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return true;
+failure:
+ json_free(&sub_json);
+ TALLOC_FREE(tmp_ctx);
+ return false;
+}
diff --git a/source3/utils/status_json.h b/source3/utils/status_json.h
new file mode 100644
index 0000000..ef5d181
--- /dev/null
+++ b/source3/utils/status_json.h
@@ -0,0 +1,77 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Json output
+ * Copyright (C) Jule Anger 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "status.h"
+#include "smbd/notifyd/notifyd_db.h"
+
+#ifndef STATUS_JSON_H
+#define STATUS_JSON_H
+
+int add_section_to_json(struct traverse_state *state,
+ const char *key);
+
+int add_general_information_to_json(struct traverse_state *state);
+
+int add_profile_item_to_json(struct traverse_state *state,
+ const char *section,
+ const char *subsection,
+ const char *key,
+ uintmax_t value);
+
+int traverse_connections_json(struct traverse_state *state,
+ const struct connections_data *crec,
+ const char *encryption_cipher,
+ enum crypto_degree encryption_degree,
+ const char *signing_cipher,
+ enum crypto_degree signing_degree);
+
+int traverse_sessionid_json(struct traverse_state *state,
+ struct sessionid *session,
+ char *uid_str,
+ char *gid_str,
+ const char *encryption_cipher,
+ enum crypto_degree encryption_degree,
+ const char *signing_cipher,
+ enum crypto_degree signing_degree,
+ const char *connection_dialect);
+
+int print_share_mode_json(struct traverse_state *state,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ struct file_id fid,
+ const char *uid_str,
+ const char *op_str,
+ uint32_t lease_type,
+ const char *filename);
+
+int print_brl_json(struct traverse_state *state,
+ const struct server_id server_id,
+ struct file_id fid,
+ const char *type,
+ enum brl_flavour flavour,
+ intmax_t start,
+ intmax_t size,
+ const char *sharepath,
+ const char *filename);
+
+bool print_notify_rec_json(struct traverse_state *state,
+ const struct notify_instance *instance,
+ const struct server_id server_id,
+ const char *path);
+#endif
diff --git a/source3/utils/status_json_dummy.c b/source3/utils/status_json_dummy.c
new file mode 100644
index 0000000..3cd8531
--- /dev/null
+++ b/source3/utils/status_json_dummy.c
@@ -0,0 +1,101 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Json output
+ * Copyright (C) Jule Anger 2022
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbprofile.h"
+#include "../libcli/security/security.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "conn_tdb.h"
+#include "status_json.h"
+
+int add_section_to_json(struct traverse_state *state,
+ const char *key)
+{
+ return 0;
+}
+
+int add_general_information_to_json(struct traverse_state *state)
+{
+ return 0;
+}
+
+int add_profile_item_to_json(struct traverse_state *state,
+ const char *section,
+ const char *subsection,
+ const char *key,
+ uintmax_t value)
+{
+ return 0;
+}
+
+int traverse_connections_json(struct traverse_state *state,
+ const struct connections_data *crec,
+ const char *encryption_cipher,
+ enum crypto_degree encryption_degree,
+ const char *signing_cipher,
+ enum crypto_degree signing_degree)
+{
+ return 0;
+}
+
+int traverse_sessionid_json(struct traverse_state *state,
+ struct sessionid *session,
+ char *uid_str,
+ char *gid_str,
+ const char *encryption_cipher,
+ enum crypto_degree encryption_degree,
+ const char *signing_cipher,
+ enum crypto_degree signing_degree,
+ const char *connection_dialect)
+{
+ return 0;
+}
+
+int print_share_mode_json(struct traverse_state *state,
+ const struct share_mode_data *d,
+ const struct share_mode_entry *e,
+ struct file_id fid,
+ const char *uid_str,
+ const char *op_str,
+ uint32_t lease_type,
+ const char *filename)
+{
+ return 0;
+}
+
+int print_brl_json(struct traverse_state *state,
+ const struct server_id server_id,
+ struct file_id fid,
+ const char *type,
+ enum brl_flavour flavour,
+ intmax_t start,
+ intmax_t size,
+ const char *sharepath,
+ const char *filename)
+{
+ return 0;
+}
+
+bool print_notify_rec_json(struct traverse_state *state,
+ const struct notify_instance *instance,
+ const struct server_id server_id,
+ const char *path)
+{
+ return 0;
+}
diff --git a/source3/utils/status_profile.c b/source3/utils/status_profile.c
new file mode 100644
index 0000000..6e0916e
--- /dev/null
+++ b/source3/utils/status_profile.c
@@ -0,0 +1,381 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * status reporting
+ * Copyright (C) Andrew Tridgell 1994-1998
+ * Copyright (C) James Peach 2005-2006
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbprofile.h"
+#include "status_profile.h"
+#include "conn_tdb.h"
+#include "librpc/gen_ndr/open_files.h"
+#include "status_json.h"
+
+static void profile_separator(const char * title,
+ struct traverse_state *state)
+{
+ char line[79 + 1];
+ char * end;
+
+ if (state->json_output) {
+ return;
+ }
+
+ snprintf(line, sizeof(line), "**** %s ", title);
+
+ for (end = line + strlen(line); end < &line[sizeof(line) -1]; ++end) {
+ *end = '*';
+ }
+
+ line[sizeof(line) - 1] = '\0';
+ d_printf("%s\n", line);
+}
+
+/*******************************************************************
+ dump the elements of the profile structure
+ ******************************************************************/
+bool status_profile_dump(bool verbose,
+ struct traverse_state *state)
+{
+ struct profile_stats stats = {};
+ const char* latest_section = NULL;
+
+ if (!profile_setup(NULL, True)) {
+ fprintf(stderr,"Failed to initialise profile memory\n");
+ return False;
+ }
+
+ smbprofile_collect(&stats);
+
+#define __PRINT_FIELD_LINE(name, _stats, field) do { \
+ uintmax_t val = (uintmax_t)stats.values._stats.field; \
+ if (!state->json_output) { \
+ d_printf("%-59s%20ju\n", \
+ name "_" #field ":", \
+ val); \
+ } else { \
+ add_profile_item_to_json(state, latest_section, name, #field, val); \
+ } \
+} while(0);
+#define SMBPROFILE_STATS_START
+#define SMBPROFILE_STATS_SECTION_START(name, display) do { \
+ latest_section = display; \
+ profile_separator(display, state);\
+} while(0);
+#define SMBPROFILE_STATS_COUNT(name) do { \
+ __PRINT_FIELD_LINE(#name, name##_stats, count); \
+} while(0);
+#define SMBPROFILE_STATS_TIME(name) do { \
+ __PRINT_FIELD_LINE(#name, name##_stats, time); \
+} while(0);
+#define SMBPROFILE_STATS_BASIC(name) do { \
+ __PRINT_FIELD_LINE(#name, name##_stats, count); \
+ __PRINT_FIELD_LINE(#name, name##_stats, time); \
+} while(0);
+#define SMBPROFILE_STATS_BYTES(name) do { \
+ __PRINT_FIELD_LINE(#name, name##_stats, count); \
+ __PRINT_FIELD_LINE(#name, name##_stats, time); \
+ __PRINT_FIELD_LINE(#name, name##_stats, idle); \
+ __PRINT_FIELD_LINE(#name, name##_stats, bytes); \
+} while(0);
+#define SMBPROFILE_STATS_IOBYTES(name) do { \
+ __PRINT_FIELD_LINE(#name, name##_stats, count); \
+ __PRINT_FIELD_LINE(#name, name##_stats, time); \
+ __PRINT_FIELD_LINE(#name, name##_stats, idle); \
+ __PRINT_FIELD_LINE(#name, name##_stats, inbytes); \
+ __PRINT_FIELD_LINE(#name, name##_stats, outbytes); \
+} while(0);
+#define SMBPROFILE_STATS_SECTION_END
+#define SMBPROFILE_STATS_END
+ SMBPROFILE_STATS_ALL_SECTIONS
+#undef __PRINT_FIELD_LINE
+#undef SMBPROFILE_STATS_START
+#undef SMBPROFILE_STATS_SECTION_START
+#undef SMBPROFILE_STATS_COUNT
+#undef SMBPROFILE_STATS_TIME
+#undef SMBPROFILE_STATS_BASIC
+#undef SMBPROFILE_STATS_BYTES
+#undef SMBPROFILE_STATS_IOBYTES
+#undef SMBPROFILE_STATS_SECTION_END
+#undef SMBPROFILE_STATS_END
+
+ return True;
+}
+
+/* Convert microseconds to milliseconds. */
+#define usec_to_msec(s) ((s) / 1000)
+/* Convert microseconds to seconds. */
+#define usec_to_sec(s) ((s) / 1000000)
+/* One second in microseconds. */
+#define one_second_usec (1000000)
+
+#define sample_interval_usec one_second_usec
+
+#define percent_time(used, period) ((double)(used) / (double)(period) * 100.0 )
+
+static uint64_t print_count_count_samples(
+ char *buf, const size_t buflen,
+ const char *name,
+ const struct smbprofile_stats_count * const current,
+ const struct smbprofile_stats_count * const last,
+ uint64_t delta_usec)
+{
+ uint64_t step = current->count - last->count;
+ uint64_t count = 0;
+
+ if (step != 0) {
+ uint64_t delta_sec = usec_to_sec(delta_usec);
+
+ count++;
+
+ if (buf[0] == '\0') {
+ snprintf(buf, buflen,
+ "%-40s %ju/sec",
+ name, (uintmax_t)(step / delta_sec));
+ } else {
+ printf("%-40s %s %ju/sec\n",
+ buf, name, (uintmax_t)(step / delta_sec));
+ buf[0] = '\0';
+ }
+ }
+
+ return count;
+}
+
+static uint64_t print_basic_count_samples(
+ char *buf, const size_t buflen,
+ const char *name,
+ const struct smbprofile_stats_basic * const current,
+ const struct smbprofile_stats_basic * const last,
+ uint64_t delta_usec)
+{
+ uint64_t step = current->count - last->count;
+ uint64_t spent = current->time - last->time;
+ uint64_t count = 0;
+
+ if (step != 0) {
+ uint64_t delta_sec = usec_to_sec(delta_usec);
+
+ count++;
+
+ if (buf[0] == '\0') {
+ snprintf(buf, buflen,
+ "%s %ju/sec (%.2f%%)",
+ name, (uintmax_t)(step / delta_sec),
+ percent_time(spent, delta_usec));
+ } else {
+ printf("%-40s %s %ju/sec (%.2f%%)\n",
+ buf, name, (uintmax_t)(step / delta_sec),
+ percent_time(spent, delta_usec));
+ buf[0] = '\0';
+ }
+ }
+
+ return count;
+}
+
+static uint64_t print_bytes_count_samples(
+ char *buf, const size_t buflen,
+ const char *name,
+ const struct smbprofile_stats_bytes * const current,
+ const struct smbprofile_stats_bytes * const last,
+ uint64_t delta_usec)
+{
+ uint64_t step = current->count - last->count;
+ uint64_t spent = current->time - last->time;
+ uint64_t count = 0;
+
+ if (step != 0) {
+ uint64_t delta_sec = usec_to_sec(delta_usec);
+
+ count++;
+
+ if (buf[0] == '\0') {
+ snprintf(buf, buflen,
+ "%s %ju/sec (%.2f%%)",
+ name, (uintmax_t)(step / delta_sec),
+ percent_time(spent, delta_usec));
+ } else {
+ printf("%-40s %s %ju/sec (%.2f%%)\n",
+ buf, name, (uintmax_t)(step / delta_sec),
+ percent_time(spent, delta_usec));
+ buf[0] = '\0';
+ }
+ }
+
+ return count;
+}
+
+static uint64_t print_iobytes_count_samples(
+ char *buf, const size_t buflen,
+ const char *name,
+ const struct smbprofile_stats_iobytes * const current,
+ const struct smbprofile_stats_iobytes * const last,
+ uint64_t delta_usec)
+{
+ uint64_t step = current->count - last->count;
+ uint64_t spent = current->time - last->time;
+ uint64_t count = 0;
+
+ if (step != 0) {
+ uint64_t delta_sec = usec_to_sec(delta_usec);
+
+ count++;
+
+ if (buf[0] == '\0') {
+ snprintf(buf, buflen,
+ "%s %ju/sec (%.2f%%)",
+ name, (uintmax_t)(step / delta_sec),
+ percent_time(spent, delta_usec));
+ } else {
+ printf("%-40s %s %ju/sec (%.2f%%)\n",
+ buf, name, (uintmax_t)(step / delta_sec),
+ percent_time(spent, delta_usec));
+ buf[0] = '\0';
+ }
+ }
+
+ return count;
+}
+
+static uint64_t print_count_samples(
+ const struct profile_stats * const current,
+ const struct profile_stats * const last,
+ uint64_t delta_usec)
+{
+ uint64_t count = 0;
+ char buf[60] = { '\0', };
+
+ if (delta_usec == 0) {
+ return 0;
+ }
+
+#define SMBPROFILE_STATS_START
+#define SMBPROFILE_STATS_SECTION_START(name, display)
+#define SMBPROFILE_STATS_COUNT(name) do { \
+ count += print_count_count_samples(buf, sizeof(buf), \
+ #name, \
+ &current->values.name##_stats, \
+ &last->values.name##_stats, \
+ delta_usec); \
+} while(0);
+#define SMBPROFILE_STATS_TIME(name) do { \
+} while(0);
+#define SMBPROFILE_STATS_BASIC(name) do { \
+ count += print_basic_count_samples(buf, sizeof(buf), \
+ #name, \
+ &current->values.name##_stats, \
+ &last->values.name##_stats, \
+ delta_usec); \
+} while(0);
+#define SMBPROFILE_STATS_BYTES(name) do { \
+ count += print_bytes_count_samples(buf, sizeof(buf), \
+ #name, \
+ &current->values.name##_stats, \
+ &last->values.name##_stats, \
+ delta_usec); \
+} while(0);
+#define SMBPROFILE_STATS_IOBYTES(name) do { \
+ count += print_iobytes_count_samples(buf, sizeof(buf), \
+ #name, \
+ &current->values.name##_stats, \
+ &last->values.name##_stats, \
+ delta_usec); \
+} while(0);
+#define SMBPROFILE_STATS_SECTION_END
+#define SMBPROFILE_STATS_END
+ SMBPROFILE_STATS_ALL_SECTIONS
+#undef SMBPROFILE_STATS_START
+#undef SMBPROFILE_STATS_SECTION_START
+#undef SMBPROFILE_STATS_COUNT
+#undef SMBPROFILE_STATS_TIME
+#undef SMBPROFILE_STATS_BASIC
+#undef SMBPROFILE_STATS_BYTES
+#undef SMBPROFILE_STATS_IOBYTES
+#undef SMBPROFILE_STATS_SECTION_END
+#undef SMBPROFILE_STATS_END
+
+ if (buf[0] != '\0') {
+ printf("%-40s\n", buf);
+ buf[0] = '\0';
+ }
+
+ return count;
+}
+
+static struct profile_stats sample_data[2];
+static uint64_t sample_time[2];
+
+bool status_profile_rates(bool verbose)
+{
+ uint64_t remain_usec;
+ uint64_t next_usec;
+ uint64_t delta_usec;
+
+ int last = 0;
+ int current = 1;
+ int tmp;
+
+ if (verbose) {
+ fprintf(stderr, "Sampling stats at %d sec intervals\n",
+ usec_to_sec(sample_interval_usec));
+ }
+
+ if (!profile_setup(NULL, True)) {
+ fprintf(stderr,"Failed to initialise profile memory\n");
+ return False;
+ }
+
+ smbprofile_collect(&sample_data[last]);
+ for (;;) {
+ sample_time[current] = profile_timestamp();
+ next_usec = sample_time[current] + sample_interval_usec;
+
+ /* Take a sample. */
+ smbprofile_collect(&sample_data[current]);
+
+ /* Rate convert some values and print results. */
+ delta_usec = sample_time[current] - sample_time[last];
+
+ if (print_count_samples(&sample_data[current],
+ &sample_data[last], delta_usec)) {
+ printf("\n");
+ }
+
+ /* Swap sampling buffers. */
+ tmp = last;
+ last = current;
+ current = tmp;
+
+ /* Delay until next sample time. */
+ remain_usec = next_usec - profile_timestamp();
+ if (remain_usec > sample_interval_usec) {
+ fprintf(stderr, "eek! falling behind sampling rate!\n");
+ } else {
+ if (verbose) {
+ fprintf(stderr,
+ "delaying for %lu msec\n",
+ (unsigned long )usec_to_msec(remain_usec));
+ }
+
+ usleep(remain_usec);
+ }
+
+ }
+
+ return True;
+}
diff --git a/source3/utils/status_profile.h b/source3/utils/status_profile.h
new file mode 100644
index 0000000..eed54e0
--- /dev/null
+++ b/source3/utils/status_profile.h
@@ -0,0 +1,30 @@
+/*
+ * Samba Unix/Linux SMB client library
+ * Dump profiles
+ * Copyright (C) Volker Lendecke 2014
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __STATUS_PROFILE_H__
+#define __STATUS_PROFILE_H__
+
+#include "replace.h"
+#include "status.h"
+
+bool status_profile_dump(bool be_verbose,
+ struct traverse_state *state);
+bool status_profile_rates(bool be_verbose);
+
+#endif
diff --git a/source3/utils/status_profile_dummy.c b/source3/utils/status_profile_dummy.c
new file mode 100644
index 0000000..9083abf
--- /dev/null
+++ b/source3/utils/status_profile_dummy.c
@@ -0,0 +1,35 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Samba internal messaging functions
+ * Copyright (C) 2013 by Volker Lendecke
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "smbprofile.h"
+#include "status_profile.h"
+
+bool status_profile_dump(bool be_verbose,
+ struct traverse_state *state)
+{
+ fprintf(stderr, "Profile data unavailable\n");
+ return true;
+}
+
+bool status_profile_rates(bool be_verbose)
+{
+ fprintf(stderr, "Profile data unavailable\n");
+ return true;
+}
diff --git a/source3/utils/testparm.c b/source3/utils/testparm.c
new file mode 100644
index 0000000..fd90e8d
--- /dev/null
+++ b/source3/utils/testparm.c
@@ -0,0 +1,1060 @@
+/*
+ Unix SMB/CIFS implementation.
+ Test validity of smb.conf
+ Copyright (C) Karl Auer 1993, 1994-1998
+
+ Extensively modified by Andrew Tridgell, 1995
+ Converted to popt by Jelmer Vernooij (jelmer@nl.linux.org), 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * Testbed for loadparm.c/params.c
+ *
+ * This module simply loads a specified configuration file and
+ * if successful, dumps it's contents to stdout. Note that the
+ * operation is performed with DEBUGLEVEL at 3.
+ *
+ * Useful for a quick 'syntax check' of a configuration file.
+ *
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/param/loadparm.h"
+#include "lib/param/param.h"
+#include "lib/crypto/gnutls_helpers.h"
+#include "cmdline_contexts.h"
+
+#include <regex.h>
+
+/*******************************************************************
+ Check if a directory exists.
+********************************************************************/
+
+static bool directory_exist_stat(const char *dname,SMB_STRUCT_STAT *st)
+{
+ SMB_STRUCT_STAT st2;
+ bool ret;
+
+ if (!st)
+ st = &st2;
+
+ if (sys_stat(dname, st, false) != 0)
+ return(False);
+
+ ret = S_ISDIR(st->st_ex_mode);
+ if(!ret)
+ errno = ENOTDIR;
+ return ret;
+}
+
+struct idmap_config {
+ const char *domain_name;
+ const char *backend;
+ uint32_t high;
+ uint32_t low;
+};
+
+struct idmap_domains {
+ struct idmap_config *c;
+ uint32_t count;
+ uint32_t size;
+};
+
+static bool lp_scan_idmap_found_domain(const char *string,
+ regmatch_t matches[],
+ void *private_data)
+{
+ bool ok = false;
+
+ if (matches[1].rm_so == -1) {
+ fprintf(stderr, "Found match, but no name - invalid idmap config");
+ return false;
+ }
+ if (matches[1].rm_eo <= matches[1].rm_so) {
+ fprintf(stderr, "Invalid match - invalid idmap config");
+ return false;
+ }
+
+ {
+ struct idmap_domains *d = private_data;
+ struct idmap_config *c = &d->c[d->count];
+ regoff_t len = matches[1].rm_eo - matches[1].rm_so;
+ char domname[len + 1];
+
+ if (d->count >= d->size) {
+ return false;
+ }
+
+ memcpy(domname, string + matches[1].rm_so, len);
+ domname[len] = '\0';
+
+ c->domain_name = talloc_strdup_upper(d->c, domname);
+ if (c->domain_name == NULL) {
+ return false;
+ }
+ c->backend = talloc_strdup(d->c, lp_idmap_backend(domname));
+ if (c->backend == NULL) {
+ return false;
+ }
+
+ if (lp_server_role() != ROLE_ACTIVE_DIRECTORY_DC) {
+ ok = lp_idmap_range(domname, &c->low, &c->high);
+ if (!ok) {
+ fprintf(stderr,
+ "ERROR: Invalid idmap range for domain "
+ "%s!\n\n",
+ c->domain_name);
+ return false;
+ }
+ }
+
+ d->count++;
+ }
+
+ return false; /* Keep scanning */
+}
+
+static int idmap_config_int(const char *domname, const char *option, int def)
+{
+ int len = snprintf(NULL, 0, "idmap config %s", domname);
+
+ if (len == -1) {
+ return def;
+ }
+ {
+ char config_option[len+1];
+ snprintf(config_option, sizeof(config_option),
+ "idmap config %s", domname);
+ return lp_parm_int(-1, config_option, option, def);
+ }
+}
+
+static bool do_idmap_check(void)
+{
+ struct idmap_domains *d;
+ uint32_t i;
+ bool ok = false;
+ int rc;
+
+ d = talloc_zero(talloc_tos(), struct idmap_domains);
+ if (d == NULL) {
+ return false;
+ }
+ d->count = 0;
+ d->size = 32;
+
+ d->c = talloc_array(d, struct idmap_config, d->size);
+ if (d->c == NULL) {
+ goto done;
+ }
+
+ rc = lp_wi_scan_global_parametrics("idmapconfig\\(.*\\):backend",
+ 2,
+ lp_scan_idmap_found_domain,
+ d);
+ if (rc != 0) {
+ fprintf(stderr,
+ "FATAL: wi_scan_global_parametrics failed: %d",
+ rc);
+ }
+
+ /* Check autorid backend */
+ if (strequal(lp_idmap_default_backend(), "autorid")) {
+ struct idmap_config *c = NULL;
+ bool found = false;
+
+ for (i = 0; i < d->count; i++) {
+ c = &d->c[i];
+
+ if (strequal(c->backend, "autorid")) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ uint32_t rangesize =
+ idmap_config_int("*", "rangesize", 100000);
+ uint32_t maxranges =
+ (c->high - c->low + 1) / rangesize;
+
+ if (((c->high - c->low + 1) % rangesize) != 0) {
+ fprintf(stderr,
+ "WARNING: The idmap autorid range "
+ "[%u-%u] SHOULD be a multiple of "
+ "the rangesize [%u]!"
+ "\n\n",
+ c->low,
+ c->high,
+ rangesize);
+ }
+
+ if (maxranges < 2) {
+ fprintf(stderr,
+ "ERROR: The idmap autorid range "
+ "[%u-%u] needs to be at least twice as "
+ "big as the rangesize [%u]!"
+ "\n\n",
+ c->low,
+ c->high,
+ rangesize);
+ ok = false;
+ goto done;
+ }
+ }
+ }
+
+ /* Check for overlapping idmap ranges */
+ for (i = 0; i < d->count; i++) {
+ struct idmap_config *c = &d->c[i];
+ uint32_t j;
+
+ for (j = 0; j < d->count && j != i; j++) {
+ struct idmap_config *x = &d->c[j];
+
+ if ((c->low >= x->low && c->low <= x->high) ||
+ (c->high >= x->low && c->high <= x->high)) {
+ /*
+ * Allow overlapping ranges for idmap_ad
+ * and idmap_nss
+ */
+ ok = strequal(c->backend, x->backend);
+ if (ok) {
+ ok = strequal(c->backend, "ad") ||
+ strequal(c->backend, "nss");
+ if (ok) {
+ fprintf(stderr,
+ "NOTE: The idmap_%s "
+ "range for the domain "
+ "%s overlaps with the "
+ "range of %s.\n\n",
+ c->backend,
+ c->domain_name,
+ x->domain_name);
+ continue;
+ }
+ }
+
+ fprintf(stderr,
+ "ERROR: The idmap range for the domain "
+ "%s (%s) overlaps with the range of "
+ "%s (%s)!\n\n",
+ c->domain_name,
+ c->backend,
+ x->domain_name,
+ x->backend);
+ ok = false;
+ goto done;
+ }
+ }
+ }
+
+ ok = true;
+done:
+ TALLOC_FREE(d);
+ return ok;
+}
+
+/***********************************************
+ Here we do a set of 'hard coded' checks for bad
+ configuration settings.
+************************************************/
+
+static int do_global_checks(void)
+{
+ int ret = 0;
+ SMB_STRUCT_STAT st;
+ const char *socket_options;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ fprintf(stderr, "\n");
+
+ if (lp_security() >= SEC_DOMAIN && !lp_encrypt_passwords()) {
+ fprintf(stderr, "ERROR: in 'security=domain' mode the "
+ "'encrypt passwords' parameter must always be "
+ "set to 'true'.\n\n");
+ ret = 1;
+ }
+
+ if (lp_security() == SEC_ADS) {
+ const char *workgroup = lp_workgroup();
+ const char *realm = lp_realm();
+
+ if (workgroup == NULL || strlen(workgroup) == 0) {
+ fprintf(stderr,
+ "ERROR: The 'security=ADS' mode requires "
+ "'workgroup' parameter to be set!\n\n ");
+ ret = 1;
+ }
+
+ if (realm == NULL || strlen(realm) == 0) {
+ fprintf(stderr,
+ "ERROR: The 'security=ADS' mode requires "
+ "'realm' parameter to be set!\n\n ");
+ ret = 1;
+ }
+ }
+
+
+ if (lp_we_are_a_wins_server() && lp_wins_server_list()) {
+ fprintf(stderr, "ERROR: both 'wins support = true' and "
+ "'wins server = <server list>' cannot be set in "
+ "the smb.conf file. nmbd will abort with this "
+ "setting.\n\n");
+ ret = 1;
+ }
+
+ if (strequal(lp_workgroup(), lp_netbios_name())) {
+ fprintf(stderr, "WARNING: 'workgroup' and 'netbios name' "
+ "must differ.\n\n");
+ }
+
+ if (lp_client_ipc_signing() == SMB_SIGNING_IF_REQUIRED
+ || lp_client_ipc_signing() == SMB_SIGNING_OFF) {
+ fprintf(stderr, "WARNING: The 'client ipc signing' value "
+ "%s SMB signing is not used when contacting a "
+ "domain controller or other server. "
+ "This setting is not recommended; please be "
+ "aware of the security implications when using "
+ "this configuration setting.\n\n",
+ lp_client_ipc_signing() == SMB_SIGNING_OFF ?
+ "ensures" : "may mean");
+ }
+
+ if (strlen(lp_netbios_name()) > 15) {
+ fprintf(stderr, "WARNING: The 'netbios name' is too long "
+ "(max. 15 chars).\n\n");
+ }
+
+ if (!directory_exist_stat(lp_lock_directory(), &st)) {
+ fprintf(stderr, "ERROR: lock directory %s does not exist\n\n",
+ lp_lock_directory());
+ ret = 1;
+ } else if ((st.st_ex_mode & 0777) != 0755) {
+ fprintf(stderr, "WARNING: lock directory %s should have "
+ "permissions 0755 for browsing to work\n\n",
+ lp_lock_directory());
+ }
+
+ if (!directory_exist_stat(lp_state_directory(), &st)) {
+ fprintf(stderr, "ERROR: state directory %s does not exist\n\n",
+ lp_state_directory());
+ ret = 1;
+ } else if ((st.st_ex_mode & 0777) != 0755) {
+ fprintf(stderr, "WARNING: state directory %s should have "
+ "permissions 0755 for browsing to work\n\n",
+ lp_state_directory());
+ }
+
+ if (!directory_exist_stat(lp_cache_directory(), &st)) {
+ fprintf(stderr, "ERROR: cache directory %s does not exist\n\n",
+ lp_cache_directory());
+ ret = 1;
+ } else if ((st.st_ex_mode & 0777) != 0755) {
+ fprintf(stderr, "WARNING: cache directory %s should have "
+ "permissions 0755 for browsing to work\n\n",
+ lp_cache_directory());
+ }
+
+ if (!directory_exist_stat(lp_pid_directory(), &st)) {
+ fprintf(stderr, "ERROR: pid directory %s does not exist\n\n",
+ lp_pid_directory());
+ ret = 1;
+ }
+
+ if (lp_passdb_expand_explicit()) {
+ fprintf(stderr, "WARNING: passdb expand explicit = yes is "
+ "deprecated\n\n");
+ }
+
+ /*
+ * Socket options.
+ */
+ socket_options = lp_socket_options();
+ if (socket_options != NULL &&
+ (strstr(socket_options, "SO_SNDBUF") ||
+ strstr(socket_options, "SO_RCVBUF") ||
+ strstr(socket_options, "SO_SNDLOWAT") ||
+ strstr(socket_options, "SO_RCVLOWAT")))
+ {
+ fprintf(stderr,
+ "WARNING: socket options = %s\n"
+ "This warning is printed because you set one of the\n"
+ "following options: SO_SNDBUF, SO_RCVBUF, SO_SNDLOWAT,\n"
+ "SO_RCVLOWAT\n"
+ "Modern server operating systems are tuned for\n"
+ "high network performance in the majority of situations;\n"
+ "when you set 'socket options' you are overriding those\n"
+ "settings.\n"
+ "Linux in particular has an auto-tuning mechanism for\n"
+ "buffer sizes (SO_SNDBUF, SO_RCVBUF) that will be\n"
+ "disabled if you specify a socket buffer size. This can\n"
+ "potentially cripple your TCP/IP stack.\n\n"
+ "Getting the 'socket options' correct can make a big\n"
+ "difference to your performance, but getting them wrong\n"
+ "can degrade it by just as much. As with any other low\n"
+ "level setting, if you must make changes to it, make\n "
+ "small changes and test the effect before making any\n"
+ "large changes.\n\n",
+ socket_options);
+ }
+
+ /*
+ * Password server sanity checks.
+ */
+
+ if((lp_security() >= SEC_DOMAIN) && !*lp_password_server()) {
+ const char *sec_setting;
+ if(lp_security() == SEC_DOMAIN)
+ sec_setting = "domain";
+ else if(lp_security() == SEC_ADS)
+ sec_setting = "ads";
+ else
+ sec_setting = "";
+
+ fprintf(stderr, "ERROR: The setting 'security=%s' requires the "
+ "'password server' parameter be set to the "
+ "default value * or a valid password server.\n\n",
+ sec_setting );
+ ret = 1;
+ }
+
+ if((lp_security() >= SEC_DOMAIN) && (strcmp(lp_password_server(), "*") != 0)) {
+ const char *sec_setting;
+ if(lp_security() == SEC_DOMAIN)
+ sec_setting = "domain";
+ else if(lp_security() == SEC_ADS)
+ sec_setting = "ads";
+ else
+ sec_setting = "";
+
+ fprintf(stderr, "WARNING: The setting 'security=%s' should NOT "
+ "be combined with the 'password server' "
+ "parameter.\n"
+ "(by default Samba will discover the correct DC "
+ "to contact automatically).\n\n",
+ sec_setting );
+ }
+
+ /*
+ * Password chat sanity checks.
+ */
+
+ if(lp_security() == SEC_USER && lp_unix_password_sync()) {
+
+ /*
+ * Check that we have a valid lp_passwd_program() if not using pam.
+ */
+
+#ifdef WITH_PAM
+ if (!lp_pam_password_change()) {
+#endif
+
+ if((lp_passwd_program(talloc_tos(), lp_sub) == NULL) ||
+ (strlen(lp_passwd_program(talloc_tos(), lp_sub)) == 0))
+ {
+ fprintf(stderr,
+ "ERROR: the 'unix password sync' "
+ "parameter is set and there is no valid "
+ "'passwd program' parameter.\n\n");
+ ret = 1;
+ } else {
+ const char *passwd_prog;
+ char *truncated_prog = NULL;
+ const char *p;
+
+ passwd_prog = lp_passwd_program(talloc_tos(), lp_sub);
+ p = passwd_prog;
+ next_token_talloc(talloc_tos(),
+ &p,
+ &truncated_prog, NULL);
+ if (truncated_prog && access(truncated_prog, F_OK) == -1) {
+ fprintf(stderr,
+ "ERROR: the 'unix password sync' "
+ "parameter is set and the "
+ "'passwd program' (%s) cannot be "
+ "executed (error was %s).\n\n",
+ truncated_prog,
+ strerror(errno));
+ ret = 1;
+ }
+ }
+
+#ifdef WITH_PAM
+ }
+#endif
+
+ if(lp_passwd_chat(talloc_tos(), lp_sub) == NULL) {
+ fprintf(stderr,
+ "ERROR: the 'unix password sync' parameter is "
+ "set and there is no valid 'passwd chat' "
+ "parameter.\n\n");
+ ret = 1;
+ }
+
+ if ((lp_passwd_program(talloc_tos(), lp_sub) != NULL) &&
+ (strlen(lp_passwd_program(talloc_tos(), lp_sub)) > 0))
+ {
+ /* check if there's a %u parameter present */
+ if(strstr_m(lp_passwd_program(talloc_tos(), lp_sub), "%u") == NULL) {
+ fprintf(stderr,
+ "ERROR: the 'passwd program' (%s) "
+ "requires a '%%u' parameter.\n\n",
+ lp_passwd_program(talloc_tos(), lp_sub));
+ ret = 1;
+ }
+ }
+
+ /*
+ * Check that we have a valid script and that it hasn't
+ * been written to expect the old password.
+ */
+
+ if(lp_encrypt_passwords()) {
+ if(strstr_m( lp_passwd_chat(talloc_tos(), lp_sub), "%o")!=NULL) {
+ fprintf(stderr,
+ "ERROR: the 'passwd chat' script [%s] "
+ "expects to use the old plaintext "
+ "password via the %%o substitution. With "
+ "encrypted passwords this is not "
+ "possible.\n\n",
+ lp_passwd_chat(talloc_tos(), lp_sub) );
+ ret = 1;
+ }
+ }
+ }
+
+ if (strlen(lp_winbind_separator()) != 1) {
+ fprintf(stderr, "ERROR: the 'winbind separator' parameter must "
+ "be a single character.\n\n");
+ ret = 1;
+ }
+
+ if (*lp_winbind_separator() == '+') {
+ fprintf(stderr, "'winbind separator = +' might cause problems "
+ "with group membership.\n\n");
+ }
+
+ if (lp_algorithmic_rid_base() < BASE_RID) {
+ /* Try to prevent admin foot-shooting, we can't put algorithmic
+ rids below 1000, that's the 'well known RIDs' on NT */
+ fprintf(stderr, "'algorithmic rid base' must be equal to or "
+ "above %lu\n\n", BASE_RID);
+ }
+
+ if (lp_algorithmic_rid_base() & 1) {
+ fprintf(stderr, "'algorithmic rid base' must be even.\n\n");
+ }
+
+ if (lp_server_role() != ROLE_STANDALONE) {
+ const char *default_backends[] = {
+ "tdb", "tdb2", "ldap", "autorid", "hash"
+ };
+ const char *idmap_backend;
+ bool valid_backend = false;
+ uint32_t i;
+ bool ok;
+
+ idmap_backend = lp_idmap_default_backend();
+
+ for (i = 0; i < ARRAY_SIZE(default_backends); i++) {
+ ok = strequal(idmap_backend, default_backends[i]);
+ if (ok) {
+ valid_backend = true;
+ }
+ }
+
+ if (!valid_backend) {
+ ret = 1;
+ fprintf(stderr, "ERROR: Do not use the '%s' backend "
+ "as the default idmap backend!\n\n",
+ idmap_backend);
+ }
+
+ ok = do_idmap_check();
+ if (!ok) {
+ ret = 1;
+ }
+ }
+
+#ifndef HAVE_DLOPEN
+ if (lp_preload_modules()) {
+ fprintf(stderr, "WARNING: 'preload modules = ' set while loading "
+ "plugins not supported.\n\n");
+ }
+#endif
+
+ if (!lp_passdb_backend()) {
+ fprintf(stderr, "ERROR: passdb backend must have a value or be "
+ "left out\n\n");
+ }
+
+ if (lp_os_level() > 255) {
+ fprintf(stderr, "WARNING: Maximum value for 'os level' is "
+ "255!\n\n");
+ }
+
+ if (strequal(lp_dos_charset(), "UTF8") || strequal(lp_dos_charset(), "UTF-8")) {
+ fprintf(stderr, "ERROR: 'dos charset' must not be UTF8\n\n");
+ ret = 1;
+ }
+
+ if (lp_server_schannel() != true) { /* can be 'auto' */
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'server schannel = yes' (the default). "
+ "Your server is vulnerable to \"ZeroLogon\" "
+ "(CVE-2020-1472)\n"
+ "If required use individual "
+ "'server require schannel:COMPUTERACCOUNT$ = no' "
+ "options\n\n");
+ }
+ if (lp_allow_nt4_crypto()) {
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'allow nt4 crypto = no' (the default). "
+ "Your server is vulnerable to "
+ "CVE-2022-38023 and others!\n"
+ "If required use individual "
+ "'allow nt4 crypto:COMPUTERACCOUNT$ = yes' "
+ "options\n\n");
+ }
+ if (!lp_reject_md5_clients()) {
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'reject md5 clients = yes' (the default). "
+ "Your server is vulnerable to "
+ "CVE-2022-38023!\n"
+ "If required use individual "
+ "'server reject md5 schannel:COMPUTERACCOUNT$ = yes' "
+ "options\n\n");
+ }
+ if (!lp_server_schannel_require_seal()) {
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'server schannel require seal = yes' (the default). "
+ "Your server is vulnerable to "
+ "CVE-2022-38023!\n"
+ "If required use individual "
+ "'server schannel require seal:COMPUTERACCOUNT$ = no' "
+ "options\n\n");
+ }
+
+ if (lp_client_schannel() != true) { /* can be 'auto' */
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'client schannel = yes' (the default). "
+ "Your server is vulnerable to \"ZeroLogon\" "
+ "(CVE-2020-1472)\n"
+ "If required use individual "
+ "'client schannel:NETBIOSDOMAIN = no' "
+ "options\n\n");
+ }
+ if (!lp_reject_md5_servers()) {
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'reject md5 servers = yes' (the default). "
+ "Your server is vulnerable to "
+ "CVE-2022-38023\n"
+ "If required use individual "
+ "'reject md5 servers:NETBIOSDOMAIN = no' "
+ "options\n\n");
+ }
+ if (!lp_require_strong_key()) {
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'require strong key = yes' (the default). "
+ "Your server is vulnerable to "
+ "CVE-2022-38023\n"
+ "If required use individual "
+ "'require strong key:NETBIOSDOMAIN = no' "
+ "options\n\n");
+ }
+ if (!lp_winbind_sealed_pipes()) {
+ fprintf(stderr,
+ "WARNING: You have not configured "
+ "'winbind sealed pipes = yes' (the default). "
+ "Your server is vulnerable to "
+ "CVE-2022-38023\n"
+ "If required use individual "
+ "'winbind sealed pipes:NETBIOSDOMAIN = no' "
+ "options\n\n");
+ }
+
+ if (lp_kerberos_encryption_types() == KERBEROS_ETYPES_LEGACY) {
+ fprintf(stderr,
+ "WARNING: You have configured "
+ "'kerberos encryption types = legacy'. "
+ "Your server is vulnerable to "
+ "CVE-2022-37966\n\n");
+ }
+
+ return ret;
+}
+
+/**
+ * per-share logic tests
+ */
+static void do_per_share_checks(int s)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ const char **deny_list = lp_hosts_deny(s);
+ const char **allow_list = lp_hosts_allow(s);
+ const char **vfs_objects = NULL;
+ int i;
+ static bool uses_fruit;
+ static bool doesnt_use_fruit;
+ static bool fruit_mix_warned;
+
+ if(deny_list) {
+ for (i=0; deny_list[i]; i++) {
+ char *hasstar = strchr_m(deny_list[i], '*');
+ char *hasquery = strchr_m(deny_list[i], '?');
+ if(hasstar || hasquery) {
+ fprintf(stderr,
+ "Invalid character %c in hosts deny list "
+ "(%s) for service %s.\n\n",
+ hasstar ? *hasstar : *hasquery,
+ deny_list[i],
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+ }
+ }
+
+ if(allow_list) {
+ for (i=0; allow_list[i]; i++) {
+ char *hasstar = strchr_m(allow_list[i], '*');
+ char *hasquery = strchr_m(allow_list[i], '?');
+ if(hasstar || hasquery) {
+ fprintf(stderr,
+ "Invalid character %c in hosts allow "
+ "list (%s) for service %s.\n\n",
+ hasstar ? *hasstar : *hasquery,
+ allow_list[i],
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+ }
+ }
+
+ if(lp_level2_oplocks(s) && !lp_oplocks(s)) {
+ fprintf(stderr, "Invalid combination of parameters for service "
+ "%s. Level II oplocks can only be set if oplocks "
+ "are also set.\n\n",
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+
+ if (!lp_store_dos_attributes(s) && lp_map_hidden(s)
+ && !(lp_create_mask(s) & S_IXOTH))
+ {
+ fprintf(stderr,
+ "Invalid combination of parameters for service %s. Map "
+ "hidden can only work if create mask includes octal "
+ "01 (S_IXOTH).\n\n",
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+ if (!lp_store_dos_attributes(s) && lp_map_hidden(s)
+ && (lp_force_create_mode(s) & S_IXOTH))
+ {
+ fprintf(stderr,
+ "Invalid combination of parameters for service "
+ "%s. Map hidden can only work if force create mode "
+ "excludes octal 01 (S_IXOTH).\n\n",
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+ if (!lp_store_dos_attributes(s) && lp_map_system(s)
+ && !(lp_create_mask(s) & S_IXGRP))
+ {
+ fprintf(stderr,
+ "Invalid combination of parameters for service "
+ "%s. Map system can only work if create mask includes "
+ "octal 010 (S_IXGRP).\n\n",
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+ if (!lp_store_dos_attributes(s) && lp_map_system(s)
+ && (lp_force_create_mode(s) & S_IXGRP))
+ {
+ fprintf(stderr,
+ "Invalid combination of parameters for service "
+ "%s. Map system can only work if force create mode "
+ "excludes octal 010 (S_IXGRP).\n\n",
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+ if (lp_printing(s) == PRINT_CUPS && *(lp_print_command(s)) != '\0') {
+ fprintf(stderr,
+ "Warning: Service %s defines a print command, but "
+ "parameter is ignored when using CUPS libraries.\n\n",
+ lp_servicename(talloc_tos(), lp_sub, s));
+ }
+
+ vfs_objects = lp_vfs_objects(s);
+ if (vfs_objects && str_list_check(vfs_objects, "fruit")) {
+ uses_fruit = true;
+ } else {
+ doesnt_use_fruit = true;
+ }
+
+ if (uses_fruit && doesnt_use_fruit && !fruit_mix_warned) {
+ fruit_mix_warned = true;
+ fprintf(stderr,
+ "WARNING: some services use vfs_fruit, others don't. Mounting them "
+ "in conjunction on OS X clients results in undefined behaviour.\n\n");
+ }
+}
+
+ int main(int argc, const char *argv[])
+{
+ const char *config_file = NULL;
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ int opt;
+ int s;
+ static int silent_mode = False;
+ static int show_all_parameters = False;
+ int ret = 0;
+ poptContext pc;
+ static char *parameter_name = NULL;
+ static const char *section_name = NULL;
+ const char *cname;
+ const char *caddr;
+ static int show_defaults;
+ static int skip_logic_checks = 0;
+ bool ok;
+
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "suppress-prompt",
+ .shortName = 's',
+ .argInfo = POPT_ARG_VAL,
+ .arg = &silent_mode,
+ .val = 1,
+ .descrip = "Suppress prompt for enter",
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &show_defaults,
+ .val = 1,
+ .descrip = "Show default options too",
+ },
+ {
+ .longName = "skip-logic-checks",
+ .shortName = 'l',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &skip_logic_checks,
+ .val = 1,
+ .descrip = "Skip the global checks",
+ },
+ {
+ .longName = "show-all-parameters",
+ .shortName = '\0',
+ .argInfo = POPT_ARG_VAL,
+ .arg = &show_all_parameters,
+ .val = True,
+ .descrip = "Show the parameters, type, possible "
+ "values",
+ },
+ {
+ .longName = "parameter-name",
+ .shortName = '\0',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &parameter_name,
+ .val = 0,
+ .descrip = "Limit testparm to a named parameter",
+ },
+ {
+ .longName = "section-name",
+ .shortName = '\0',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &section_name,
+ .val = 0,
+ .descrip = "Limit testparm to a named section",
+ },
+ POPT_COMMON_DEBUG_ONLY
+ POPT_COMMON_OPTION_ONLY
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct loadparm_context *lp_ctx = NULL;
+
+ smb_init_locale();
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_NONE,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to init cmdline parser!\n");
+ ret = 1;
+ goto done;
+ }
+ lp_ctx = samba_cmdline_get_lp_ctx();
+
+ /*
+ * Set the default debug level to 1.
+ * Allow it to be overridden by the command line,
+ * not by smb.conf.
+ */
+ lpcfg_set_cmdline(lp_ctx, "log level", "1");
+
+ pc = samba_popt_get_context(getprogname(),
+ argc,
+ argv,
+ long_options,
+ 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt context!\n");
+ ret = 1;
+ goto done;
+ }
+
+ poptSetOtherOptionHelp(pc, "[OPTION...] <config-file> [host-name] [host-ip]");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case POPT_ERROR_BADOPT:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ if (show_all_parameters) {
+ show_parameter_list();
+ exit(0);
+ }
+
+ if (poptPeekArg(pc)) {
+ config_file = talloc_strdup(frame, poptGetArg(pc));
+ if (config_file == NULL) {
+ DBG_ERR("out of memory\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+ } else {
+ config_file = get_dyn_CONFIGFILE();
+ }
+
+ cname = talloc_strdup(frame, poptGetArg(pc));
+ caddr = talloc_strdup(frame, poptGetArg(pc));
+
+ poptFreeContext(pc);
+
+ if ( cname && ! caddr ) {
+ printf ( "ERROR: You must specify both a machine name and an IP address.\n" );
+ ret = 1;
+ goto done;
+ }
+
+ fprintf(stderr,"Load smb config files from %s\n",config_file);
+
+ if (!lp_load_with_registry_shares(config_file)) {
+ fprintf(stderr,"Error loading services.\n");
+ ret = 1;
+ goto done;
+ }
+
+ fprintf(stderr,"Loaded services file OK.\n");
+
+ fprintf(stderr,
+ "Weak crypto is %sallowed by GnuTLS "
+ "(e.g. NTLM as a compatibility fallback)\n",
+ samba_gnutls_weak_crypto_allowed() ? "" : "dis");
+
+ if (skip_logic_checks == 0) {
+ ret = do_global_checks();
+ }
+
+ for (s=0;s<1000;s++) {
+ if (VALID_SNUM(s) && (skip_logic_checks == 0)) {
+ do_per_share_checks(s);
+ }
+ }
+
+
+ if (!section_name && !parameter_name) {
+ fprintf(stderr,
+ "Server role: %s\n\n",
+ server_role_str(lp_server_role()));
+ }
+
+ if (!cname) {
+ if (!silent_mode) {
+ fprintf(stderr,"Press enter to see a dump of your service definitions\n");
+ fflush(stdout);
+ getc(stdin);
+ }
+ if (parameter_name || section_name) {
+ bool isGlobal = False;
+ s = GLOBAL_SECTION_SNUM;
+
+ if (!section_name) {
+ section_name = GLOBAL_NAME;
+ isGlobal = True;
+ } else if ((isGlobal=!strwicmp(section_name, GLOBAL_NAME)) == 0 &&
+ (s=lp_servicenumber(section_name)) == -1) {
+ fprintf(stderr,"Unknown section %s\n",
+ section_name);
+ ret = 1;
+ goto done;
+ }
+ if (parameter_name) {
+ if (!dump_a_parameter( s, parameter_name, stdout, isGlobal)) {
+ fprintf(stderr,"Parameter %s unknown for section %s\n",
+ parameter_name, section_name);
+ ret = 1;
+ goto done;
+ }
+ } else {
+ if (isGlobal == True)
+ lp_dump(stdout, show_defaults, 0);
+ else
+ lp_dump_one(stdout, show_defaults, s);
+ }
+ goto done;
+ }
+
+ lp_dump(stdout, show_defaults, lp_numservices());
+ }
+
+ if(cname && caddr){
+ /* this is totally ugly, a real `quick' hack */
+ for (s=0;s<1000;s++) {
+ if (VALID_SNUM(s)) {
+ if (allow_access(lp_hosts_deny(-1), lp_hosts_allow(-1), cname, caddr)
+ && allow_access(lp_hosts_deny(s), lp_hosts_allow(s), cname, caddr)) {
+ fprintf(stderr,"Allow connection from %s (%s) to %s\n",
+ cname,caddr,lp_servicename(talloc_tos(), lp_sub, s));
+ } else {
+ fprintf(stderr,"Deny connection from %s (%s) to %s\n",
+ cname,caddr,lp_servicename(talloc_tos(), lp_sub, s));
+ }
+ }
+ }
+ }
+
+done:
+ gfree_loadparm();
+ TALLOC_FREE(frame);
+ return ret;
+}
diff --git a/source3/utils/wscript_build b/source3/utils/wscript_build
new file mode 100644
index 0000000..ca57e80
--- /dev/null
+++ b/source3/utils/wscript_build
@@ -0,0 +1,364 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_SUBSYSTEM('PASSWD_UTIL',
+ source='passwd_util.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('CONN_TDB',
+ source='conn_tdb.c')
+
+bld.SAMBA3_SUBSYSTEM('DNS_UTIL',
+ source='net_dns.c net_ads_join_dns.c',
+ deps='addns')
+
+bld.SAMBA3_BINARY('profiles',
+ source='profiles.c',
+ deps='''
+ talloc
+ CMDLINE_S3
+ smbconf
+ REGFIO''')
+
+bld.SAMBA3_BINARY('smbcontrol',
+ source='smbcontrol.c',
+ deps='''
+ talloc
+ smbconf
+ CMDLINE_S3
+ cmdline_contexts
+ PRINTBASE''')
+
+bld.SAMBA3_BINARY('smbtree',
+ source='smbtree.c',
+ deps='''
+ talloc
+ smbconf
+ smbclient
+ msrpc3
+ CMDLINE_S3
+ RPC_NDR_SRVSVC''')
+
+bld.SAMBA3_BINARY('smbpasswd',
+ source='smbpasswd.c',
+ deps='''
+ talloc
+ smbconf
+ pdb
+ PASSWD_UTIL
+ PASSCHANGE
+ cmdline_contexts
+ ''')
+
+bld.SAMBA3_BINARY('pdbedit',
+ source='pdbedit.c',
+ deps='''
+ talloc
+ smbconf
+ CMDLINE_S3
+ cmdline_contexts
+ pdb
+ PASSWD_UTIL''')
+
+bld.SAMBA3_BINARY('smbget',
+ source='smbget.c',
+ deps='''
+ talloc
+ CMDLINE_S3
+ smbclient''')
+
+bld.SAMBA3_BINARY('nmblookup',
+ source='nmblookup.c',
+ deps='''
+ talloc
+ smbconf
+ CMDLINE_S3
+ LIBNMB''')
+
+bld.SAMBA3_BINARY('smbcacls',
+ source='smbcacls.c',
+ deps='''
+ talloc
+ CMDLINE_S3
+ msrpc3
+ libcli_lsa3
+ util_sd
+ krb5samba''')
+
+bld.SAMBA3_BINARY('smbcquotas',
+ source='smbcquotas.c',
+ deps='''
+ talloc
+ CMDLINE_S3
+ libsmb
+ msrpc3
+ libcli_lsa3''')
+
+bld.SAMBA3_BINARY('eventlogadm',
+ source='eventlogadm.c',
+ deps='''
+ talloc
+ smbconf
+ cmdline_contexts
+ LIBEVENTLOG''',
+ install_path='${SBINDIR}')
+
+bld.SAMBA3_BINARY('sharesec',
+ source='sharesec.c',
+ deps='''
+ talloc
+ msrpc3
+ libcli_lsa3
+ CMDLINE_S3
+ cmdline_contexts
+ util_sd
+ ''')
+
+bld.SAMBA3_BINARY('log2pcap',
+ source='log2pcaphex.c',
+ deps='''talloc popt''',
+ install=False)
+
+bld.SAMBA3_BINARY('smbfilter',
+ source='smbfilter.c',
+ deps='''
+ talloc
+ smbconf
+ LIBNMB''',
+ install=False)
+
+bld.SAMBA3_BINARY('ntlm_auth',
+ source='''ntlm_auth.c ntlm_auth_diagnostics.c''',
+ deps='''
+ talloc
+ krb5samba
+ tiniparser
+ libsmb
+ CMDLINE_S3
+ cmdline_contexts
+ wbclient
+ gse gensec''')
+
+bld.SAMBA3_BINARY('dbwrap_tool',
+ source='dbwrap_tool.c',
+ deps='''
+ talloc
+ CMDLINE_S3
+ cmdline_contexts
+ ''')
+
+bld.SAMBA3_BINARY('dbwrap_torture',
+ source='dbwrap_torture.c',
+ deps='''
+ talloc
+ CMDLINE_S3
+ smbconf
+ ''',
+ install=False)
+
+bld.SAMBA3_BINARY('samba-regedit',
+ source="""regedit.c regedit_samba3.c
+ regedit_wrap.c regedit_treeview.c
+ regedit_valuelist.c regedit_dialog.c
+ regedit_hexedit.c regedit_list.c""",
+ deps='''
+ ncurses
+ menu
+ panel
+ form
+ registry
+ smbconf
+ CMDLINE_S3
+ ''',
+ enabled=bld.env.build_regedit)
+
+bld.SAMBA3_BINARY('testparm',
+ source='testparm.c',
+ deps='''
+ talloc
+ smbconf
+ CMDLINE_S3
+ cmdline_contexts
+ GNUTLS_HELPERS
+ ''')
+
+bld.SAMBA3_BINARY('net',
+ source='''net.c
+ net_ads.c
+ net_help.c
+ clirap2.c
+ net_rap.c
+ net_rpc.c
+ net_rpc_samsync.c
+ net_time.c
+ net_lookup.c
+ net_cache.c
+ net_groupmap.c
+ net_idmap.c
+ net_idmap_check.c
+ interact.c
+ net_status.c
+ net_rpc_printer.c
+ net_rpc_rights.c
+ net_rpc_service.c
+ net_rpc_registry.c
+ net_usershare.c
+ netlookup.c
+ net_sam.c
+ net_rpc_shell.c
+ net_util.c
+ net_rpc_sh_acct.c
+ net_rpc_audit.c
+ net_ads_gpo.c
+ net_conf.c
+ net_conf_util.c
+ net_join.c
+ net_offlinejoin.c
+ net_user.c
+ net_group.c
+ net_file.c
+ net_registry.c
+ net_registry_check.c
+ net_dom.c
+ net_share.c
+ net_g_lock.c
+ net_serverid.c
+ net_eventlog.c
+ net_printing.c
+ net_rpc_trust.c
+ net_rpc_conf.c
+ net_afs.c
+ net_notify.c
+ net_tdb.c
+ net_witness.c
+ net_vfs.c
+ ../registry/reg_format.c
+ ../registry/reg_import.c
+ net_registry_util.c
+ net_help_common.c''',
+ deps='''
+ talloc
+ netapi
+ addns
+ samba_intl
+ CMDLINE_S3
+ cmdline_contexts
+ pdb
+ libsmb
+ smbconf
+ KRBCLIENT
+ ndr-standard
+ msrpc3
+ gpo
+ ads
+ smbd_base
+ LIBADS_SERVER
+ LIBADS_PRINTER
+ SMBREADLINE
+ PASSWD_UTIL
+ LIBNET
+ LIBNET_DSSYNC
+ LIBEVENTLOG
+ REGFIO
+ NDR_NTPRINTING
+ RPC_NDR_WINREG
+ RPC_CLIENT_SCHANNEL
+ LIBCLI_SAMR
+ libcli_lsa3
+ libcli_netlogon3
+ cli_spoolss
+ RPC_NDR_SRVSVC
+ RPC_NDR_SVCCTL
+ RPC_NDR_DSSETUP
+ RPC_NDR_INITSHUTDOWN
+ printing_migrate
+ trusts_util
+ IDMAP_AUTORID_TDB
+ CONN_TDB
+ jansson
+ common_auth
+ ADOUBLE
+ DNS_UTIL
+ util_sd
+ ''')
+
+bld.SAMBA3_BINARY('mvxattr',
+ source='mvxattr.c',
+ deps='''
+ talloc
+ popt
+ samba-util
+ ''',
+ enabled=bld.env.build_mvxattr)
+
+bld.SAMBA3_BINARY('destroy_netlogon_creds_cli',
+ source='destroy_netlogon_creds_cli.c',
+ deps = '''
+ talloc
+ smbconf
+ NETLOGON_CREDS_CLI
+ ''',
+ install=False)
+
+smbstatus_source = 'status.c'
+
+if bld.CONFIG_GET("WITH_PROFILE"):
+ smbstatus_source += ' status_profile.c'
+else:
+ smbstatus_source += ' status_profile_dummy.c'
+
+if bld.CONFIG_GET("HAVE_JANSSON"):
+ smbstatus_source += ' status_json.c'
+else:
+ smbstatus_source += ' status_json_dummy.c'
+
+bld.SAMBA3_BINARY('smbstatus',
+ source=smbstatus_source,
+ deps='''
+ talloc
+ smbconf
+ CMDLINE_S3
+ cmdline_contexts
+ smbd_base
+ LOCKING
+ PROFILE
+ CONN_TDB
+ ''')
+
+bld.SAMBA3_BINARY('mdsearch',
+ source='mdsearch.c',
+ deps='''
+ talloc
+ tevent
+ smbconf
+ CMDLINE_S3
+ cmdline_contexts
+ libsmb
+ msrpc3
+ RPCCLI_MDSSVC
+ mdssvc
+ ''')
+
+bld.SAMBA3_BINARY('wspsearch',
+ source='wspsearch.c',
+ deps='''
+ talloc
+ tevent
+ smbconf
+ CMDLINE_S3
+ cmdline_contexts
+ libsmb
+ msrpc3
+ LIBSAMBA_WSP
+ RPCCLI_WSP
+ WSP_UTIL
+ dcerpc
+ ''',
+ enabled=bld.env.with_wsp)
+
+pytalloc_util = bld.pyembed_libname('pytalloc-util')
+pyrpc_util = bld.pyembed_libname('pyrpc_util')
+bld.SAMBA3_PYTHON('python_net_s3',
+ source='py_net.c',
+ deps='LIBNET DNS_UTIL cmdline_contexts %s %s' % (pytalloc_util, pyrpc_util),
+ realname='samba/net_s3.so'
+ )
diff --git a/source3/utils/wspsearch.c b/source3/utils/wspsearch.c
new file mode 100644
index 0000000..063b952
--- /dev/null
+++ b/source3/utils/wspsearch.c
@@ -0,0 +1,842 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Window Search Service
+ *
+ * Copyright (c) Noel Power
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "includes.h"
+#include "lib/util/debug.h"
+#include "lib/cmdline/cmdline.h"
+#include "lib/cmdline_contexts.h"
+#include "param.h"
+#include "client.h"
+#include "libsmb/proto.h"
+#include "librpc/rpc/rpc_common.h"
+#include "librpc/wsp/wsp_util.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/wsp_cli.h"
+#include "libcli/wsp/wsp_aqs.h"
+#include "librpc/gen_ndr/ndr_wsp.h"
+#include "librpc/gen_ndr/ndr_wsp_data.h"
+#include "dcerpc.h"
+
+#define WIN_VERSION_64 0x10000
+
+/* send connectin message */
+static NTSTATUS wsp_connect(TALLOC_CTX *ctx,
+ struct wsp_client_ctx *wsp_ctx,
+ const char* clientmachine,
+ const char* clientuser,
+ const char* server,
+ bool *is_64bit)
+{
+ struct wsp_request *request = NULL;
+ struct wsp_response *response = NULL;
+ uint32_t client_ver;
+ uint32_t server_ver;
+ DATA_BLOB unread = data_blob_null;
+ NTSTATUS status;
+ TALLOC_CTX *local_ctx = talloc_new(ctx);
+
+
+ if (local_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ response = talloc_zero(local_ctx, struct wsp_response);
+ if (!response) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ request = talloc_zero(local_ctx, struct wsp_request);
+ if (!request) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (!init_connectin_request(local_ctx, request,
+ clientmachine, clientuser, server)) {
+ DBG_ERR("Failed in initialise connection message\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ status = wsp_request_response(local_ctx, wsp_ctx,
+ request, response, &unread);
+ if (NT_STATUS_IS_OK(status)) {
+ client_ver = request->message.cpmconnect.iclientversion;
+ server_ver = response->message.cpmconnect.server_version;
+ *is_64bit =
+ (server_ver & WIN_VERSION_64)
+ && (client_ver & WIN_VERSION_64);
+ }
+
+out:
+ data_blob_free(&unread);
+ TALLOC_FREE(local_ctx);
+ return status;
+}
+
+static NTSTATUS create_query(TALLOC_CTX *ctx,
+ struct wsp_client_ctx *wsp_ctx,
+ uint32_t limit,
+ t_select_stmt *select,
+ uint32_t *single_cursor)
+{
+ struct wsp_request *request = NULL;
+ struct wsp_response *response = NULL;
+ NTSTATUS status;
+ DATA_BLOB unread = data_blob_null;
+ TALLOC_CTX *local_ctx = talloc_new(ctx);
+
+ if (local_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ request = talloc_zero(local_ctx, struct wsp_request);
+ if (!request) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ response = talloc_zero(local_ctx, struct wsp_response);
+ if (!response) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;;
+ }
+
+ if (!create_querysearch_request(ctx, request, select)) {
+ DBG_ERR("error setting up query request message\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ request->message.cpmcreatequery.rowsetproperties.cmaxresults = limit;
+
+ status = wsp_request_response(local_ctx,
+ wsp_ctx,
+ request,
+ response,
+ &unread);
+ if (NT_STATUS_IS_OK(status)) {
+ if (unread.length == 4) {
+ *single_cursor = IVAL(unread.data, 0);
+ }
+ }
+
+out:
+ data_blob_free(&unread);
+ TALLOC_FREE(local_ctx);
+ return status;
+}
+
+static NTSTATUS create_bindings(TALLOC_CTX *ctx,
+ struct wsp_client_ctx *wsp_ctx,
+ t_select_stmt *select,
+ uint32_t cursor,
+ struct wsp_cpmsetbindingsin *bindings_out,
+ bool is_64bit)
+{
+ struct wsp_request *request = NULL;
+ struct wsp_response *response = NULL;
+ NTSTATUS status;
+ DATA_BLOB unread = data_blob_null;
+
+ request = talloc_zero(ctx, struct wsp_request);
+ if (!request) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ response = talloc_zero(ctx, struct wsp_response);
+ if (!response) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (!create_setbindings_request(ctx,
+ request,
+ select,
+ cursor,
+ is_64bit)) {
+ DBG_ERR("Failed to create setbindings message\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ status = wsp_request_response(ctx,
+ wsp_ctx,
+ request,
+ response,
+ &unread);
+ if (NT_STATUS_IS_OK(status)) {
+ *bindings_out = request->message.cpmsetbindings;
+ }
+
+out:
+ data_blob_free(&unread);
+ return status;
+}
+
+static NTSTATUS create_querystatusex(TALLOC_CTX *ctx,
+ struct wsp_client_ctx *wsp_ctx,
+ uint32_t cursor,
+ uint32_t *nrows)
+{
+ struct wsp_request *request = NULL;
+ struct wsp_response *response = NULL;
+ struct wsp_cpmgetquerystatusexin *statusexin = NULL;
+ NTSTATUS status;
+ DATA_BLOB unread = data_blob_null;
+ TALLOC_CTX *local_ctx = talloc_new(ctx);
+
+ if (local_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ request = talloc_zero(local_ctx, struct wsp_request);
+ if (!request) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ response = talloc_zero(local_ctx, struct wsp_response);
+ if (!response) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ statusexin = &request->message.cpmgetquerystatusex;
+
+ request->header.msg = CPMGETQUERYSTATUSEX;
+ statusexin->hcursor = cursor;
+ statusexin->bmk = 0xfffffffc;
+ status = wsp_request_response(local_ctx,
+ wsp_ctx,
+ request,
+ response,
+ &unread);
+ if (NT_STATUS_IS_OK(status)) {
+ *nrows = response->message.cpmgetquerystatusex.resultsfound;
+ }
+
+out:
+ data_blob_free(&unread);
+ TALLOC_FREE(local_ctx);
+ return status;
+}
+
+static NTSTATUS print_rowsreturned(
+ TALLOC_CTX *ctx,
+ DATA_BLOB *buffer,
+ bool is_64bit,
+ bool disp_all_cols,
+ struct wsp_cpmsetbindingsin *bindings,
+ uint32_t cbreserved,
+ uint64_t address,
+ uint32_t rowsreturned,
+ uint32_t *rows_processed)
+{
+ NTSTATUS status;
+ uint32_t row = 0;
+ TALLOC_CTX *local_ctx = NULL;
+ struct wsp_cbasestoragevariant **rowsarray = NULL;
+ enum ndr_err_code err;
+
+ local_ctx = talloc_init("results");
+ if (local_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ rowsarray = talloc_zero_array(local_ctx,
+ struct wsp_cbasestoragevariant*,
+ rowsreturned);
+ if (rowsarray == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ err = extract_rowsarray(rowsarray,
+ buffer,
+ is_64bit,
+ bindings,
+ cbreserved,
+ address,
+ rowsreturned,
+ rowsarray);
+ if (err) {
+ DBG_ERR("failed to extract rows from getrows response\n");
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ for(row = 0; row < rowsreturned; row++) {
+ TALLOC_CTX *row_ctx = NULL;
+ const char *col_str = NULL;
+
+ row_ctx = talloc_init("row");
+ if (row_ctx == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (disp_all_cols) {
+ int i;
+ for (i = 0; i < bindings->ccolumns; i++){
+ col_str =
+ variant_as_string(
+ row_ctx,
+ &rowsarray[row][i],
+ true);
+ if (col_str) {
+ printf("%s%s",
+ i ? ", " : "", col_str);
+ } else {
+ printf("%sN/A",
+ i ? ", " : "");
+ }
+ }
+ } else {
+ col_str = variant_as_string(
+ row_ctx,
+ &rowsarray[row][0],
+ true);
+ printf("%s", col_str);
+ }
+ printf("\n");
+ TALLOC_FREE(row_ctx);
+ }
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(local_ctx);
+ *rows_processed = row;
+ return status;
+}
+
+static NTSTATUS create_getrows(TALLOC_CTX *ctx,
+ struct wsp_client_ctx *wsp_ctx,
+ struct wsp_cpmsetbindingsin *bindings,
+ uint32_t cursor,
+ uint32_t nrows,
+ bool disp_all_cols,
+ bool is_64bit)
+{
+ struct wsp_request *request = NULL;
+ struct wsp_response *response = NULL;
+ NTSTATUS status;
+ DATA_BLOB unread = data_blob_null;
+ uint32_t bmk = 0xfffffffc;
+ uint32_t skip = 0;
+ uint32_t total_rows = 0;
+ uint32_t INITIAL_ROWS = 32;
+ uint32_t requested_rows = INITIAL_ROWS;
+ uint32_t rows_printed;
+ uint64_t baseaddress;
+ uint32_t offset_lowbits = 0xdeabd860;
+ uint32_t offset_hibits = 0xfeeddeaf;
+
+ TALLOC_CTX *row_ctx;
+ bool loop_again;
+
+ do {
+ row_ctx = talloc_new(NULL);
+ if (!row_ctx) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ request = talloc_zero(row_ctx, struct wsp_request);
+ if (!request) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ response = talloc_zero(row_ctx, struct wsp_response);
+ if (!response) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ create_seekat_getrows_request(request,
+ request,
+ cursor,
+ bmk,
+ skip,
+ requested_rows,
+ 40,
+ offset_lowbits,
+ bindings->brow,
+ 0);
+
+ if (is_64bit) {
+ /*
+ * MS-WSP 2.2.2
+ * ulreservered holds the high 32-bits part of
+ * a 64-bit offset if 64-bit offsets are being used.
+ */
+ request->header.ulreserved2 = offset_hibits;
+ baseaddress = request->header.ulreserved2;
+ baseaddress <<= 32;
+ baseaddress += offset_lowbits;
+ } else {
+ baseaddress = offset_lowbits;
+ }
+
+ status = wsp_request_response(request,
+ wsp_ctx,
+ request,
+ response,
+ &unread);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ total_rows += response->message.cpmgetrows.rowsreturned;
+ if (response->message.cpmgetrows.rowsreturned
+ != requested_rows) {
+ uint32_t rowsreturned =
+ response->message.cpmgetrows.rowsreturned;
+ if (response->message.cpmgetrows.etype == EROWSEEKAT) {
+ struct wsp_cpmgetrowsout *resp;
+ struct wsp_crowseekat *seekat;
+ resp = &response->message.cpmgetrows;
+ seekat =
+ &resp->seekdescription.crowseekat;
+ bmk = seekat->bmkoffset;
+ skip = seekat->cskip;
+ } else {
+ bmk = 0xfffffffc;
+ skip = total_rows;
+ }
+ requested_rows = requested_rows - rowsreturned;
+ } else {
+ requested_rows = INITIAL_ROWS;
+ bmk = 0xfffffffc;
+ skip = total_rows;
+ }
+
+ if (response->message.cpmgetrows.rowsreturned) {
+ status = print_rowsreturned(row_ctx, &unread,
+ is_64bit,
+ disp_all_cols,
+ bindings, 40,
+ baseaddress,
+ response->message.cpmgetrows.rowsreturned,
+ &rows_printed);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ data_blob_free(&unread);
+ }
+
+ /*
+ * response is a talloc child of row_ctx so we need to
+ * assign loop_again before we delete row_ctx
+ */
+ loop_again = response->message.cpmgetrows.rowsreturned;
+
+ TALLOC_FREE(row_ctx);
+ if (nrows && total_rows > nrows) {
+ DBG_ERR("Something is wrong, results returned %d "
+ "exceed expected number of results %d\n",
+ total_rows, nrows);
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+ } while (loop_again);
+out:
+ data_blob_free(&unread);
+ TALLOC_FREE(row_ctx);
+ return status;
+}
+
+const char *default_column = "System.ItemUrl";
+
+static bool is_valid_kind(const char *kind)
+{
+ const char* kinds[] = {"calendar",
+ "communication",
+ "contact",
+ "document",
+ "email",
+ "feed",
+ "folder",
+ "game",
+ "instantMessage",
+ "journal",
+ "link",
+ "movie",
+ "music",
+ "note",
+ "picture",
+ "program",
+ "recordedtv",
+ "searchfolder",
+ "task",
+ "video",
+ "webhistory"};
+ char* search_kind = NULL;
+ int i;
+ bool found = false;
+
+ search_kind = strlower_talloc(NULL, kind);
+ if (search_kind == NULL) {
+ DBG_ERR("couldn't convert %s to lower case\n",
+ kind);
+ return NULL;
+ }
+
+ for (i=0; i<ARRAY_SIZE(kinds); i++) {
+ if (strequal(search_kind, kinds[i])) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found == false) {
+ DBG_ERR("Invalid kind %s\n", kind);
+ }
+ TALLOC_FREE(search_kind);
+ return found;
+}
+
+static char * build_default_sql(TALLOC_CTX *ctx,
+ const char *kind,
+ const char *phrase,
+ const char *location)
+{
+ char *sql = NULL;
+ /* match what windows clients do */
+ sql = talloc_asprintf(ctx,
+ "Scope:\"%s\" AND NOT System.Shell.SFGAOFlagsStrings:hidden"
+ " AND NOT System.Shell.OmitFromView:true", location);
+
+ if (kind) {
+ if (!is_valid_kind(kind)) {
+ return NULL;
+ }
+ sql = talloc_asprintf(ctx, "System.Kind:%s AND %s",
+ kind, sql);
+ }
+
+ if (phrase) {
+ sql = talloc_asprintf(ctx,
+ "All:$=\"%s\" OR All:$<\"%s\""
+ " AND %s", phrase, phrase, sql);
+ }
+ sql = talloc_asprintf(ctx, "SELECT %s"
+ " WHERE %s", default_column, sql);
+ return sql;
+}
+
+int main(int argc, char **argv)
+{
+ int opt;
+ int result = 0;
+ NTSTATUS status = NT_STATUS_OK;
+ poptContext pc;
+ char* server = NULL;
+ char* share = NULL;
+ char* path = NULL;
+ char* location = NULL;
+ char* query = NULL;
+ bool custom_query = false;
+ const char* phrase = NULL;
+ const char* kind = NULL;
+ uint32_t limit = 500;
+ uint32_t nrows = 0;
+ struct wsp_cpmsetbindingsin bindings_used = {0};
+ bool is_64bit = false;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ { "limit",
+ 0,
+ POPT_ARG_INT,
+ &limit,
+ 0,
+ "limit results",
+ "default is 500, specifying 0 means unlimited" },
+ { "search",
+ 0,
+ POPT_ARG_STRING,
+ &phrase,
+ 0,
+ "Search phrase",
+ "phrase" },
+ { "kind", 0, POPT_ARG_STRING, &kind, 0,
+ "Kind of thing to search for [Calendar|Communication|"
+ "Contact|Document|Email|Feed|Folder|Game|"
+ "InstantMessage|Journal|Link|Movie|Music|Note|Picture|"
+ "Program|RecordedTV|SearchFolder|Task|Video"
+ "|WebHistory]",
+ "kind" },
+ { "query",
+ 0,
+ POPT_ARG_STRING,
+ &query,
+ 0,
+ "specify a more complex query",
+ "query" },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_CONNECTION
+ POPT_COMMON_CREDENTIALS
+ POPT_TABLEEND
+ };
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev_ctx
+ = samba_tevent_context_init(talloc_tos());
+ uint32_t cursor = 0;
+ struct wsp_client_ctx *wsp_ctx = NULL;
+ t_select_stmt *select_stmt = NULL;
+ const char **const_argv = discard_const_p(const char *, argv);
+ struct dcerpc_binding_handle *h = NULL;
+ struct cli_state *c = NULL;
+ uint32_t flags = CLI_FULL_CONNECTION_IPC;
+ bool ok;
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_CLIENT,
+ false /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to set up cmdline parser\n");
+ result = -1;
+ goto out;
+ }
+
+ pc = samba_popt_get_context("wspsearch",
+ argc,
+ const_argv,
+ long_options,
+ 0);
+ poptSetOtherOptionHelp(pc, "[OPTIONS] //server1/share1");
+
+ while ((opt = poptGetNextOpt(pc)) != -1) ;
+
+ if(!poptPeekArg(pc)) {
+ poptPrintUsage(pc, stderr, 0);
+ result = -1;
+ goto out;
+ }
+
+ path = talloc_strdup(talloc_tos(), poptGetArg(pc));
+ if (!path) {
+ DBG_ERR("Invalid argument\n");
+ result = -1;
+ goto out;
+ }
+
+ string_replace(path,'/','\\');
+ server = talloc_strdup(talloc_tos(), path+2);
+ if (!server) {
+ DBG_ERR("Invalid argument\n");
+ return -1;
+ }
+
+ if (server) {
+ /*
+ * if we specify --query then we don't need actually need the
+ * share part, if it is specified then we don't care as we
+ * expect the scope to be part of the query (and if it isn't
+ * then it will probably fail anyway)
+ */
+ share = strchr_m(server,'\\');
+ if (!query && !share) {
+ DBG_ERR("Invalid argument\n");
+ return -1;
+ }
+ if (share) {
+ *share = 0;
+ share++;
+ }
+ }
+
+ DBG_INFO("server name is %s\n", server ? server : "N/A");
+ DBG_INFO("share name is %s\n", share ? share : "N/A");
+ DBG_INFO("search phrase is %s\n", phrase ? phrase : "N/A");
+ DBG_INFO("search kind is %s\n", kind ? kind : "N/A");
+
+ if (!query && (kind == NULL && phrase == NULL)) {
+ poptPrintUsage(pc, stderr, 0);
+ result = -1;
+ goto out;
+ }
+
+ if (!query) {
+ location = talloc_asprintf(talloc_tos(),
+ "FILE://%s/%s", server, share);
+ query = build_default_sql(talloc_tos(), kind, phrase, location);
+ if (!query) {
+ result = -1;
+ goto out;
+ }
+ } else {
+ custom_query = true;
+ }
+
+ printf("custom_query %d\n", custom_query);
+ select_stmt = get_wsp_sql_tree(query);
+
+ poptFreeContext(pc);
+
+ if (select_stmt == NULL) {
+ DBG_ERR("query failed\n");
+ result = -1;
+ goto out;
+ }
+
+ if (select_stmt->cols == NULL) {
+ select_stmt->cols = talloc_zero(select_stmt, t_col_list);
+ if (select_stmt->cols == NULL) {
+ DBG_ERR("out of memory\n");
+ result = -1;
+ goto out;
+ }
+ select_stmt->cols->num_cols = 1;
+ select_stmt->cols->cols =
+ talloc_zero_array(select_stmt->cols, char*, 1);
+ if (select_stmt->cols->cols == NULL) {
+ DBG_ERR("out of memory\n");
+ result = -1;
+ goto out;
+ }
+ select_stmt->cols->cols[0] =
+ talloc_strdup(select_stmt->cols, default_column);
+ }
+
+ status = cli_full_connection_creds(&c,
+ lp_netbios_name(),
+ server,
+ NULL,
+ 0,
+ "IPC$",
+ "IPC",
+ samba_cmdline_get_creds(),
+ flags);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to connect to IPC$: %s\n",
+ nt_errstr(status));
+ result = -1;
+ goto out;
+ }
+
+ status = wsp_server_connect(talloc_tos(),
+ server,
+ ev_ctx,
+ samba_cmdline_get_lp_ctx(),
+ samba_cmdline_get_creds(),
+ c,
+ &wsp_ctx);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to connect to wsp: %s\n",
+ nt_errstr(status));
+ result = -1;
+ goto out;
+ }
+
+ h = get_wsp_pipe(wsp_ctx);
+ if (h == NULL) {
+ DBG_ERR("Failed to communicate with server, no pipe\n");
+ result = -1;
+ goto out;
+ }
+
+ dcerpc_binding_handle_set_timeout(h,
+ DCERPC_REQUEST_TIMEOUT * 1000);
+
+ /* connect */
+ DBG_INFO("sending connect\n");
+ status = wsp_connect(talloc_tos(),
+ wsp_ctx,
+ lpcfg_netbios_name(samba_cmdline_get_lp_ctx()),
+ cli_credentials_get_username(
+ samba_cmdline_get_creds()),
+ server,
+ &is_64bit);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to connect to wsp: %s\n",
+ nt_errstr(status));
+ result = -1;
+ goto out;
+ }
+
+ DBG_INFO("sending query\n");
+
+ status = create_query(talloc_tos(),
+ wsp_ctx,
+ limit,
+ select_stmt,
+ &cursor);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to send query: %s)\n",
+ nt_errstr(status));
+ result = -1;
+ goto out;
+ }
+
+ DBG_INFO("sending createbindings\n");
+ /* set bindings */
+ status = create_bindings(talloc_tos(),
+ wsp_ctx,
+ select_stmt,
+ cursor,
+ &bindings_used,
+ is_64bit);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("failed to setbindings: %s)\n",
+ nt_errstr(status));
+ result = -1;
+ goto out;
+ }
+
+ status = create_querystatusex(talloc_tos(),
+ wsp_ctx,
+ bindings_used.hcursor,
+ &nrows);
+ if (!nrows) {
+ result = 0;
+ DBG_ERR("no results found\n");
+ goto out;
+ }
+
+ printf("found %d results, returning %d \n",
+ nrows,
+ limit ? MIN(nrows, limit) : nrows);
+ status = create_getrows(talloc_tos(),
+ wsp_ctx,
+ &bindings_used,
+ bindings_used.hcursor,
+ limit ? MIN(nrows, limit) : nrows,
+ custom_query,
+ is_64bit);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to retrieve rows, error: %s\n",
+ nt_errstr(status));
+ result = -1;
+ goto out;
+ }
+ result = 0;
+out:
+ TALLOC_FREE(frame);
+ return result;
+}
diff --git a/source3/web/swat.c b/source3/web/swat.c
new file mode 100644
index 0000000..bbacdd1
--- /dev/null
+++ b/source3/web/swat.c
@@ -0,0 +1,58 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba Web Administration Tool
+ Version 3.0.0
+ Copyright (C) Andrew Tridgell 1997-2002
+ Copyright (C) John H Terpstra 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*
+ * 1997 - 2013 SWAT
+ * R.I.P. :,
+ * Finally swatted, you will bug us no more. @,@
+ * +#`@
+ * @`:@
+ * ,' :@ #'
+ * + @ @: @.
+ * @ + ,@ .@
+ * ;` ; @; #+
+ * @@` @ @ ;@ @`
+ * # :@ ' # @, ##
+ * @ +. # ` ## .@
+ * # # ' ;@ @;
+ *, ' # + ,@` @@
+ *' ` + : .@, #@
+ *# `@@#. : ;@: '@
+ *+. #` ,#@@@@@@@@@@@@@ #@` @@
+ * .@@. @ `#@+,` `##@+ + .@@ `@#
+ * +@. @ ;@;. `:;# '`:+@ `@@, +@:
+ * :@,#, +`:;;;#.@@.:' :@@: '@#
+ * + ,: '+@@@@@@@@@+ ;@@+ '@@
+ * .: :@@;. @: ;;+@@; ;@@'
+ * # ,#@', @# .`;#@ @
+ * # .@ '' ; @
+ * @ ,:@@@+;++..+,. @
+ * @ @+;+@+@, . ;` @'
+ * @ : . @,'@ '
+ * .@@+#+''''++#@@@+;.,
+ * .@ @'. : :++@@# .
+ * + @:#@;'+@@@@@#;
+ * ` :@; .@ '#
+ * ,: ,#
+ * , +.
+ * , :
+ * `
+ * +
+ */
diff --git a/source3/winbindd/idmap.c b/source3/winbindd/idmap.c
new file mode 100644
index 0000000..53b860b
--- /dev/null
+++ b/source3/winbindd/idmap.c
@@ -0,0 +1,632 @@
+/*
+ Unix SMB/CIFS implementation.
+ ID Mapping
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Simo Sorce 2003-2007
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Michael Adam 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "idmap.h"
+#include "lib/util_sid_passdb.h"
+#include "libcli/security/dom_sid.h"
+#include "passdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+static_decl_idmap;
+
+/**
+ * Pointer to the backend methods. Modules register themselves here via
+ * smb_register_idmap.
+ */
+
+struct idmap_backend {
+ const char *name;
+ const struct idmap_methods *methods;
+ struct idmap_backend *prev, *next;
+};
+static struct idmap_backend *backends = NULL;
+
+/**
+ * Default idmap domain configured via "idmap backend".
+ */
+static struct idmap_domain *default_idmap_domain;
+
+/**
+ * Passdb idmap domain, not configurable. winbind must always give passdb a
+ * chance to map ids.
+ */
+static struct idmap_domain *passdb_idmap_domain;
+
+/**
+ * List of specially configured idmap domains. This list is filled on demand
+ * in the winbind idmap child when the parent winbind figures out via the
+ * special range parameter or via the domain SID that a special "idmap config
+ * domain" configuration is present.
+ */
+static struct idmap_domain **idmap_domains = NULL;
+static int num_domains = 0;
+
+static struct idmap_domain *idmap_init_named_domain(TALLOC_CTX *mem_ctx,
+ const char *domname);
+static struct idmap_domain *idmap_init_domain(TALLOC_CTX *mem_ctx,
+ const char *domainname,
+ const char *modulename,
+ bool check_range);
+
+struct lp_scan_idmap_domains_state {
+ bool (*fn)(const char *domname, void *private_data);
+ void *private_data;
+};
+
+static bool lp_scan_idmap_found_domain(
+ const char *string, regmatch_t matches[], void *private_data);
+
+bool lp_scan_idmap_domains(bool (*fn)(const char *domname,
+ void *private_data),
+ void *private_data)
+{
+ struct lp_scan_idmap_domains_state state = {
+ .fn = fn, .private_data = private_data };
+ int ret;
+
+ ret = lp_wi_scan_global_parametrics(
+ "idmapconfig\\(.*\\):backend", 2,
+ lp_scan_idmap_found_domain, &state);
+ if (ret != 0) {
+ DBG_WARNING("wi_scan_global_parametrics returned %d\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+static bool lp_scan_idmap_found_domain(
+ const char *string, regmatch_t matches[], void *private_data)
+{
+ bool ok;
+
+ if (matches[1].rm_so == -1) {
+ DBG_WARNING("Found match, but no name??\n");
+ return false;
+ }
+ if (matches[1].rm_eo <= matches[1].rm_so) {
+ DBG_WARNING("Invalid match\n");
+ return false;
+ }
+
+ {
+ struct lp_scan_idmap_domains_state *state = private_data;
+ regoff_t len = matches[1].rm_eo - matches[1].rm_so;
+ char domname[len+1];
+
+ memcpy(domname, string + matches[1].rm_so, len);
+ domname[len] = '\0';
+
+ DBG_DEBUG("Found idmap domain \"%s\"\n", domname);
+
+ ok = state->fn(domname, state->private_data);
+ }
+
+ return ok;
+}
+
+static bool idmap_found_domain_backend(const char *domname,
+ void *private_data);
+
+static bool idmap_init(void)
+{
+ static bool initialized;
+ bool ok;
+
+ if (initialized) {
+ return true;
+ }
+
+ DEBUG(10, ("idmap_init(): calling static_init_idmap\n"));
+
+ static_init_idmap(NULL);
+
+ initialized = true;
+
+ if (!pdb_is_responsible_for_everything_else()) {
+ default_idmap_domain = idmap_init_named_domain(NULL, "*");
+ if (default_idmap_domain == NULL) {
+ return false;
+ }
+ }
+
+ passdb_idmap_domain = idmap_init_domain(
+ NULL, get_global_sam_name(), "passdb", false);
+ if (passdb_idmap_domain == NULL) {
+ TALLOC_FREE(default_idmap_domain);
+ return false;
+ }
+
+ idmap_domains = talloc_array(NULL, struct idmap_domain *, 0);
+ if (idmap_domains == NULL) {
+ TALLOC_FREE(passdb_idmap_domain);
+ TALLOC_FREE(default_idmap_domain);
+ return false;
+ }
+
+ ok = lp_scan_idmap_domains(idmap_found_domain_backend, NULL);
+ if (!ok) {
+ DBG_WARNING("lp_scan_idmap_domains failed\n");
+ return false;
+ }
+
+ return true;
+}
+
+static int idmap_config_name(const char *domname, char *buf, size_t buflen)
+{
+ int len = snprintf(buf, buflen, "idmap config %s", domname);
+ SMB_ASSERT(len > 0);
+ return len + 1;
+}
+
+const char *idmap_config_const_string(const char *domname, const char *option,
+ const char *def)
+{
+ int len = idmap_config_name(domname, NULL, 0);
+ char config_option[len];
+ idmap_config_name(domname, config_option, sizeof(config_option));
+
+ return lp_parm_const_string(-1, config_option, option, def);
+}
+
+bool idmap_config_bool(const char *domname, const char *option, bool def)
+{
+ int len = idmap_config_name(domname, NULL, 0);
+ char config_option[len];
+ idmap_config_name(domname, config_option, sizeof(config_option));
+
+ return lp_parm_bool(-1, config_option, option, def);
+}
+
+int idmap_config_int(const char *domname, const char *option, int def)
+{
+ int len = idmap_config_name(domname, NULL, 0);
+ char config_option[len];
+ idmap_config_name(domname, config_option, sizeof(config_option));
+
+ return lp_parm_int(-1, config_option, option, def);
+}
+
+const char **idmap_config_string_list(const char *domname,
+ const char *option,
+ const char **def)
+{
+ int len = idmap_config_name(domname, NULL, 0);
+ char config_option[len];
+ idmap_config_name(domname, config_option, sizeof(config_option));
+
+ return lp_parm_string_list(-1, config_option, option, def);
+}
+
+bool domain_has_idmap_config(const char *domname)
+{
+ int i;
+ const char *range = NULL;
+ const char *backend = NULL;
+ bool ok;
+
+ ok = idmap_init();
+ if (!ok) {
+ return false;
+ }
+
+ for (i=0; i<num_domains; i++) {
+ if (strequal(idmap_domains[i]->name, domname)) {
+ return true;
+ }
+ }
+
+ /* fallback: also check loadparm */
+
+ range = idmap_config_const_string(domname, "range", NULL);
+ backend = idmap_config_const_string(domname, "backend", NULL);
+ if (range != NULL && backend != NULL) {
+ DEBUG(5, ("idmap configuration specified for domain '%s'\n",
+ domname));
+ return true;
+ }
+
+ return false;
+}
+
+static bool idmap_found_domain_backend(const char *domname,
+ void *private_data)
+{
+ struct idmap_domain *dom, **tmp;
+
+ DBG_DEBUG("Found idmap domain \"%s\"\n", domname);
+
+ if (strcmp(domname, "*") == 0) {
+ return false;
+ }
+
+ dom = idmap_init_named_domain(idmap_domains, domname);
+ if (dom == NULL) {
+ DBG_NOTICE("Could not init idmap domain %s\n", domname);
+ return false;
+ }
+
+ tmp = talloc_realloc(idmap_domains, idmap_domains,
+ struct idmap_domain *, num_domains + 1);
+ if (tmp == NULL) {
+ DBG_WARNING("talloc_realloc failed\n");
+ TALLOC_FREE(dom);
+ return false;
+ }
+ idmap_domains = tmp;
+ idmap_domains[num_domains] = dom;
+ num_domains += 1;
+
+ return false;
+}
+
+static const struct idmap_methods *get_methods(const char *name)
+{
+ struct idmap_backend *b;
+
+ for (b = backends; b; b = b->next) {
+ if (strequal(b->name, name)) {
+ return b->methods;
+ }
+ }
+
+ return NULL;
+}
+
+bool idmap_is_offline(void)
+{
+ return ( lp_winbind_offline_logon() &&
+ get_global_winbindd_state_offline() );
+}
+
+/**********************************************************************
+ Allow a module to register itself as a method.
+**********************************************************************/
+
+NTSTATUS smb_register_idmap(int version, const char *name,
+ const struct idmap_methods *methods)
+{
+ struct idmap_backend *entry;
+
+ if ((version != SMB_IDMAP_INTERFACE_VERSION)) {
+ DEBUG(0, ("Failed to register idmap module.\n"
+ "The module was compiled against "
+ "SMB_IDMAP_INTERFACE_VERSION %d,\n"
+ "current SMB_IDMAP_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current version "
+ "of samba!\n",
+ version, SMB_IDMAP_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0] || !methods) {
+ DEBUG(0,("Called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ for (entry = backends; entry != NULL; entry = entry->next) {
+ if (strequal(entry->name, name)) {
+ DEBUG(5,("Idmap module %s already registered!\n",
+ name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+ }
+
+ entry = talloc(NULL, struct idmap_backend);
+ if ( ! entry) {
+ DEBUG(0,("Out of memory!\n"));
+ TALLOC_FREE(entry);
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry->name = talloc_strdup(entry, name);
+ if ( ! entry->name) {
+ DEBUG(0,("Out of memory!\n"));
+ TALLOC_FREE(entry);
+ return NT_STATUS_NO_MEMORY;
+ }
+ entry->methods = methods;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5, ("Successfully added idmap backend '%s'\n", name));
+ return NT_STATUS_OK;
+}
+
+/**
+ * Initialize a domain structure
+ * @param[in] mem_ctx memory context for the result
+ * @param[in] domainname which domain is this for
+ * @param[in] modulename which backend module
+ * @param[in] check_range whether range checking should be done
+ * @result The initialized structure
+ */
+static struct idmap_domain *idmap_init_domain(TALLOC_CTX *mem_ctx,
+ const char *domainname,
+ const char *modulename,
+ bool check_range)
+{
+ struct idmap_domain *result;
+ NTSTATUS status;
+ const char *range;
+ unsigned low_id = 0;
+ unsigned high_id = 0;
+
+ result = talloc_zero(mem_ctx, struct idmap_domain);
+ if (result == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NULL;
+ }
+
+ result->name = talloc_strdup(result, domainname);
+ if (result->name == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ goto fail;
+ }
+
+ /*
+ * Check whether the requested backend module exists and
+ * load the methods.
+ */
+
+ result->methods = get_methods(modulename);
+ if (result->methods == NULL) {
+ DEBUG(3, ("idmap backend %s not found\n", modulename));
+
+ status = smb_probe_module("idmap", modulename);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not probe idmap module %s\n",
+ modulename));
+ goto fail;
+ }
+
+ result->methods = get_methods(modulename);
+ }
+ if (result->methods == NULL) {
+ DEBUG(1, ("idmap backend %s not found\n", modulename));
+ goto fail;
+ }
+
+ /*
+ * load ranges and read only information from the config
+ */
+
+ result->read_only = idmap_config_bool(result->name, "read only", false);
+ range = idmap_config_const_string(result->name, "range", NULL);
+
+ if (range == NULL) {
+ if (check_range) {
+ DEBUG(1, ("idmap range not specified for domain %s\n",
+ result->name));
+ goto fail;
+ }
+ } else if (sscanf(range, "%u - %u", &low_id, &high_id) != 2)
+ {
+ DEBUG(1, ("invalid range '%s' specified for domain "
+ "'%s'\n", range, result->name));
+ if (check_range) {
+ goto fail;
+ }
+ } else if (low_id > high_id) {
+ DEBUG(1, ("Error: invalid idmap range detected: %u - %u\n",
+ low_id, high_id));
+ if (check_range) {
+ goto fail;
+ }
+ }
+
+ result->low_id = low_id;
+ result->high_id = high_id;
+
+ status = result->methods->init(result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("idmap initialization returned %s\n",
+ nt_errstr(status)));
+ goto fail;
+ }
+
+ return result;
+
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+/**
+ * Initialize a named domain structure
+ * @param[in] mem_ctx memory context for the result
+ * @param[in] domname the domain name
+ * @result The default domain structure
+ *
+ * This routine looks at the "idmap config <domname>" parameters to figure out
+ * the configuration.
+ */
+
+static struct idmap_domain *idmap_init_named_domain(TALLOC_CTX *mem_ctx,
+ const char *domname)
+{
+ struct idmap_domain *result = NULL;
+ const char *backend;
+ bool ok;
+
+ ok = idmap_init();
+ if (!ok) {
+ return NULL;
+ }
+
+ backend = idmap_config_const_string(domname, "backend", NULL);
+ if (backend == NULL) {
+ DEBUG(10, ("no idmap backend configured for domain '%s'\n",
+ domname));
+ goto fail;
+ }
+
+ result = idmap_init_domain(mem_ctx, domname, backend, true);
+ if (result == NULL) {
+ goto fail;
+ }
+
+ return result;
+
+fail:
+ TALLOC_FREE(result);
+ return NULL;
+}
+
+/**
+ * Find a domain struct according to a domain name
+ * @param[in] domname Domain name to get the config for
+ * @result The default domain structure that fits
+ *
+ * This is the central routine in the winbindd-idmap child to pick the correct
+ * domain for looking up IDs. If domname is NULL or empty, we use the default
+ * domain. If it contains something, we try to use idmap_init_named_domain()
+ * to fetch the correct backend.
+ *
+ * The choice about "domname" is being made by the winbind parent, look at the
+ * "have_idmap_config" of "struct winbindd_domain" which is set in
+ * add_trusted_domain.
+ */
+
+struct idmap_domain *idmap_find_domain(const char *domname)
+{
+ bool ok;
+ int i;
+
+ DEBUG(10, ("idmap_find_domain called for domain '%s'\n",
+ domname?domname:"NULL"));
+
+ ok = idmap_init();
+ if (!ok) {
+ return NULL;
+ }
+
+ if ((domname == NULL) || (domname[0] == '\0')) {
+ return default_idmap_domain;
+ }
+
+ for (i=0; i<num_domains; i++) {
+ if (strequal(idmap_domains[i]->name, domname)) {
+ return idmap_domains[i];
+ }
+ }
+
+ return default_idmap_domain;
+}
+
+struct idmap_domain *idmap_find_domain_with_sid(const char *domname,
+ const struct dom_sid *sid)
+{
+ bool ok;
+
+ ok = idmap_init();
+ if (!ok) {
+ return NULL;
+ }
+
+ if (sid_check_is_for_passdb(sid)) {
+ return passdb_idmap_domain;
+ }
+
+ return idmap_find_domain(domname);
+}
+
+void idmap_close(void)
+{
+ TALLOC_FREE(default_idmap_domain);
+ TALLOC_FREE(passdb_idmap_domain);
+ TALLOC_FREE(idmap_domains);
+ num_domains = 0;
+}
+
+/**************************************************************************
+ idmap allocator interface functions
+**************************************************************************/
+
+static NTSTATUS idmap_allocate_unixid(struct unixid *id)
+{
+ struct idmap_domain *dom;
+ NTSTATUS ret;
+
+ dom = idmap_find_domain(NULL);
+
+ if (dom == NULL) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ if (dom->methods->allocate_id == NULL) {
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ ret = dom->methods->allocate_id(dom, id);
+
+ return ret;
+}
+
+
+NTSTATUS idmap_allocate_uid(struct unixid *id)
+{
+ id->type = ID_TYPE_UID;
+ return idmap_allocate_unixid(id);
+}
+
+NTSTATUS idmap_allocate_gid(struct unixid *id)
+{
+ id->type = ID_TYPE_GID;
+ return idmap_allocate_unixid(id);
+}
+
+NTSTATUS idmap_backend_unixids_to_sids(struct id_map **maps,
+ const char *domain_name,
+ struct dom_sid domain_sid)
+{
+ struct idmap_domain *dom = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ ok = idmap_init();
+ if (!ok) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ if (strequal(domain_name, get_global_sam_name())) {
+ dom = passdb_idmap_domain;
+ }
+ if (dom == NULL) {
+ dom = idmap_find_domain(domain_name);
+ }
+ if (dom == NULL) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ dom->dom_sid = domain_sid;
+ status = dom->methods->unixids_to_sids(dom, maps);
+
+ DBG_DEBUG("unixid_to_sids for domain %s returned %s\n",
+ domain_name, nt_errstr(status));
+
+ return status;
+}
diff --git a/source3/winbindd/idmap_ad.c b/source3/winbindd/idmap_ad.c
new file mode 100644
index 0000000..5c9fe07
--- /dev/null
+++ b/source3/winbindd/idmap_ad.c
@@ -0,0 +1,1243 @@
+/*
+ * idmap_ad: map between Active Directory and RFC 2307 accounts
+ *
+ * Copyright (C) Volker Lendecke 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libsmb/namequery.h"
+#include "idmap.h"
+#include "tldap_gensec_bind.h"
+#include "tldap_util.h"
+#include "passdb.h"
+#include "lib/param/param.h"
+#include "auth/gensec/gensec.h"
+#include "librpc/gen_ndr/ndr_netlogon.h"
+#include "libads/ldap_schema_oids.h"
+#include "../libds/common/flags.h"
+#include "libcli/ldap/ldap_ndr.h"
+#include "libcli/security/dom_sid.h"
+#include "source3/libads/sitename_cache.h"
+#include "source3/libads/kerberos_proto.h"
+#include "source3/librpc/gen_ndr/ads.h"
+#include "source3/lib/global_contexts.h"
+#include <ldb.h>
+
+struct idmap_ad_schema_names;
+
+struct idmap_ad_context {
+ struct idmap_domain *dom;
+ struct tldap_context *ld;
+ struct idmap_ad_schema_names *schema;
+ const char *default_nc;
+
+ bool unix_primary_group;
+ bool unix_nss_info;
+
+ struct ldb_context *ldb;
+ struct ldb_dn **deny_ous;
+ struct ldb_dn **allow_ous;
+};
+
+static NTSTATUS idmap_ad_get_context(struct idmap_domain *dom,
+ struct idmap_ad_context **pctx);
+
+static char *get_schema_path(TALLOC_CTX *mem_ctx, struct tldap_context *ld)
+{
+ struct tldap_message *rootdse;
+
+ rootdse = tldap_rootdse(ld);
+ if (rootdse == NULL) {
+ return NULL;
+ }
+
+ return tldap_talloc_single_attribute(rootdse, "schemaNamingContext",
+ mem_ctx);
+}
+
+static char *get_default_nc(TALLOC_CTX *mem_ctx, struct tldap_context *ld)
+{
+ struct tldap_message *rootdse;
+
+ rootdse = tldap_rootdse(ld);
+ if (rootdse == NULL) {
+ return NULL;
+ }
+
+ return tldap_talloc_single_attribute(rootdse, "defaultNamingContext",
+ mem_ctx);
+}
+
+struct idmap_ad_schema_names {
+ char *name;
+ char *uid;
+ char *gid;
+ char *gecos;
+ char *dir;
+ char *shell;
+};
+
+static TLDAPRC get_attrnames_by_oids(struct tldap_context *ld,
+ TALLOC_CTX *mem_ctx,
+ const char *schema_path,
+ size_t num_oids,
+ const char **oids,
+ char **names)
+{
+ char *filter;
+ const char *attrs[] = { "lDAPDisplayName", "attributeId" };
+ size_t i;
+ TLDAPRC rc;
+ struct tldap_message **msgs;
+ size_t num_msgs;
+
+ filter = talloc_strdup(mem_ctx, "(|");
+
+ for (i=0; i<num_oids; i++) {
+ talloc_asprintf_addbuf(&filter, "(attributeId=%s)", oids[i]);
+ }
+ talloc_asprintf_addbuf(&filter, ")");
+
+ if (filter == NULL) {
+ return TLDAP_NO_MEMORY;
+ }
+
+ rc = tldap_search(ld, schema_path, TLDAP_SCOPE_SUB, filter,
+ attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0,
+ 0, 0, 0, mem_ctx, &msgs);;
+ TALLOC_FREE(filter);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ return rc;
+ }
+
+ for (i=0; i<num_oids; i++) {
+ names[i] = NULL;
+ }
+
+ num_msgs = talloc_array_length(msgs);
+
+ for (i=0; i<num_msgs; i++) {
+ struct tldap_message *msg = msgs[i];
+ char *oid;
+ size_t j;
+
+ if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) {
+ /* Could be a TLDAP_RES_SEARCH_REFERENCE */
+ continue;
+ }
+
+ oid = tldap_talloc_single_attribute(
+ msg, "attributeId", msg);
+ if (oid == NULL) {
+ continue;
+ }
+
+ for (j=0; j<num_oids; j++) {
+ if (strequal(oid, oids[j])) {
+ break;
+ }
+ }
+ TALLOC_FREE(oid);
+
+ if (j == num_oids) {
+ /* not found */
+ continue;
+ }
+
+ names[j] = tldap_talloc_single_attribute(
+ msg, "lDAPDisplayName", mem_ctx);
+ }
+
+ TALLOC_FREE(msgs);
+ for (i=0; i<num_oids; i++) {
+ if (names[i] == NULL) {
+ DBG_ERR("Failed to retrieve schema name for "
+ "oid [%s]. Schema mode is incorrect "
+ "for this domain.\n", oids[i]);
+ return TLDAP_FILTER_ERROR;
+ }
+ }
+
+ return TLDAP_SUCCESS;
+}
+
+static TLDAPRC get_posix_schema_names(struct tldap_context *ld,
+ const char *schema_mode,
+ TALLOC_CTX *mem_ctx,
+ struct idmap_ad_schema_names **pschema)
+{
+ char *schema_path;
+ struct idmap_ad_schema_names *schema;
+ char *names[6];
+ const char *oids_sfu[] = {
+ ADS_ATTR_SFU_UIDNUMBER_OID,
+ ADS_ATTR_SFU_GIDNUMBER_OID,
+ ADS_ATTR_SFU_HOMEDIR_OID,
+ ADS_ATTR_SFU_SHELL_OID,
+ ADS_ATTR_SFU_GECOS_OID,
+ ADS_ATTR_SFU_UID_OID
+ };
+ const char *oids_sfu20[] = {
+ ADS_ATTR_SFU20_UIDNUMBER_OID,
+ ADS_ATTR_SFU20_GIDNUMBER_OID,
+ ADS_ATTR_SFU20_HOMEDIR_OID,
+ ADS_ATTR_SFU20_SHELL_OID,
+ ADS_ATTR_SFU20_GECOS_OID,
+ ADS_ATTR_SFU20_UID_OID
+ };
+ const char *oids_rfc2307[] = {
+ ADS_ATTR_RFC2307_UIDNUMBER_OID,
+ ADS_ATTR_RFC2307_GIDNUMBER_OID,
+ ADS_ATTR_RFC2307_HOMEDIR_OID,
+ ADS_ATTR_RFC2307_SHELL_OID,
+ ADS_ATTR_RFC2307_GECOS_OID,
+ ADS_ATTR_RFC2307_UID_OID
+ };
+ const char **oids;
+
+ TLDAPRC rc;
+
+ schema = talloc(mem_ctx, struct idmap_ad_schema_names);
+ if (schema == NULL) {
+ return TLDAP_NO_MEMORY;
+ }
+
+ schema_path = get_schema_path(schema, ld);
+ if (schema_path == NULL) {
+ TALLOC_FREE(schema);
+ return TLDAP_NO_MEMORY;
+ }
+
+ oids = oids_rfc2307;
+
+ if ((schema_mode != NULL) && (schema_mode[0] != '\0')) {
+ if (strequal(schema_mode, "sfu")) {
+ oids = oids_sfu;
+ } else if (strequal(schema_mode, "sfu20")) {
+ oids = oids_sfu20;
+ } else if (strequal(schema_mode, "rfc2307" )) {
+ oids = oids_rfc2307;
+ } else {
+ DBG_WARNING("Unknown schema mode %s\n", schema_mode);
+ }
+ }
+
+ rc = get_attrnames_by_oids(ld, schema, schema_path, 6, oids, names);
+ TALLOC_FREE(schema_path);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ TALLOC_FREE(schema);
+ return rc;
+ }
+
+ schema->uid = names[0];
+ schema->gid = names[1];
+ schema->dir = names[2];
+ schema->shell = names[3];
+ schema->gecos = names[4];
+ schema->name = names[5];
+
+ *pschema = schema;
+
+ return TLDAP_SUCCESS;
+}
+
+static void PRINTF_ATTRIBUTE(3, 0) idmap_ad_tldap_debug(
+ void *log_private,
+ enum tldap_debug_level level,
+ const char *fmt,
+ va_list ap)
+{
+ int samba_level = -1;
+
+ switch (level) {
+ case TLDAP_DEBUG_FATAL:
+ samba_level = DBGLVL_ERR;
+ break;
+ case TLDAP_DEBUG_ERROR:
+ samba_level = DBGLVL_ERR;
+ break;
+ case TLDAP_DEBUG_WARNING:
+ samba_level = DBGLVL_WARNING;
+ break;
+ case TLDAP_DEBUG_TRACE:
+ samba_level = DBGLVL_DEBUG;
+ break;
+ }
+
+ if (CHECK_DEBUGLVL(samba_level)) {
+ char *s = NULL;
+ int ret;
+
+ ret = vasprintf(&s, fmt, ap);
+ if (ret == -1) {
+ return;
+ }
+ DEBUG(samba_level, ("idmap_ad_tldap: %s", s));
+ free(s);
+ }
+}
+
+static uint32_t gensec_features_from_ldap_sasl_wrapping(void)
+{
+ int wrap_flags;
+ uint32_t gensec_features = 0;
+
+ wrap_flags = lp_client_ldap_sasl_wrapping();
+ if (wrap_flags == -1) {
+ wrap_flags = 0;
+ }
+
+ if (wrap_flags & ADS_AUTH_SASL_SEAL) {
+ gensec_features |= GENSEC_FEATURE_SEAL;
+ }
+ if (wrap_flags & ADS_AUTH_SASL_SIGN) {
+ gensec_features |= GENSEC_FEATURE_SIGN;
+ }
+
+ if (gensec_features != 0) {
+ gensec_features |= GENSEC_FEATURE_LDAP_STYLE;
+ }
+
+ return gensec_features;
+}
+
+static NTSTATUS idmap_ad_get_tldap_ctx(TALLOC_CTX *mem_ctx,
+ const char *domname,
+ struct tldap_context **pld)
+{
+ struct netr_DsRGetDCNameInfo *dcinfo;
+ struct sockaddr_storage dcaddr;
+ struct cli_credentials *creds;
+ struct loadparm_context *lp_ctx;
+ struct tldap_context *ld;
+ uint32_t gensec_features = gensec_features_from_ldap_sasl_wrapping();
+ char *sitename = NULL;
+ int fd;
+ NTSTATUS status;
+ bool ok;
+ TLDAPRC rc;
+
+ status = wb_dsgetdcname_gencache_get(mem_ctx, domname, &dcinfo);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("Could not get dcinfo for %s: %s\n", domname,
+ nt_errstr(status));
+ return status;
+ }
+
+ if (dcinfo->dc_unc == NULL) {
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+ if (dcinfo->dc_unc[0] == '\\') {
+ dcinfo->dc_unc += 1;
+ }
+ if (dcinfo->dc_unc[0] == '\\') {
+ dcinfo->dc_unc += 1;
+ }
+
+ ok = resolve_name(dcinfo->dc_unc, &dcaddr, 0x20, true);
+ if (!ok) {
+ DBG_DEBUG("Could not resolve name %s\n", dcinfo->dc_unc);
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ sitename = sitename_fetch(talloc_tos(), lp_realm());
+
+ /*
+ * create_local_private_krb5_conf_for_domain() can deal with
+ * sitename==NULL
+ */
+
+ ok = create_local_private_krb5_conf_for_domain(
+ lp_realm(), lp_workgroup(), sitename, &dcaddr);
+ TALLOC_FREE(sitename);
+ if (!ok) {
+ DBG_DEBUG("Could not create private krb5.conf\n");
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ status = open_socket_out(&dcaddr, 389, 10000, &fd);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("open_socket_out failed: %s\n", nt_errstr(status));
+ TALLOC_FREE(dcinfo);
+ return status;
+ }
+
+ ld = tldap_context_create(dcinfo, fd);
+ if (ld == NULL) {
+ DBG_DEBUG("tldap_context_create failed\n");
+ close(fd);
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_NO_MEMORY;
+ }
+ tldap_set_debug(ld, idmap_ad_tldap_debug, NULL);
+
+ /*
+ * Here we use or own machine account as
+ * we run as domain member.
+ */
+ status = pdb_get_trust_credentials(lp_workgroup(),
+ lp_realm(),
+ dcinfo,
+ &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("pdb_get_trust_credentials() failed - %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(dcinfo);
+ return status;
+ }
+
+ lp_ctx = loadparm_init_s3(dcinfo, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DBG_DEBUG("loadparm_init_s3 failed\n");
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ rc = tldap_gensec_bind(ld, creds, "ldap", dcinfo->dc_unc, NULL, lp_ctx,
+ gensec_features);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ DBG_DEBUG("tldap_gensec_bind failed: %s\n",
+ tldap_errstr(dcinfo, ld, rc));
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_LDAP(TLDAP_RC_V(rc));
+ }
+
+ rc = tldap_fetch_rootdse(ld);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ DBG_DEBUG("tldap_fetch_rootdse failed: %s\n",
+ tldap_errstr(dcinfo, ld, rc));
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_LDAP(TLDAP_RC_V(rc));
+ }
+
+ *pld = talloc_move(mem_ctx, &ld);
+ TALLOC_FREE(dcinfo);
+ return NT_STATUS_OK;
+}
+
+static int idmap_ad_context_destructor(struct idmap_ad_context *ctx)
+{
+ if ((ctx->dom != NULL) && (ctx->dom->private_data == ctx)) {
+ ctx->dom->private_data = NULL;
+ }
+ return 0;
+}
+
+static struct ldb_dn **str_list_to_dns(TALLOC_CTX *mem_ctx,
+ const char *dbgmsg,
+ struct ldb_context *ldb,
+ const char **strlist)
+{
+ size_t i, num_dns = str_list_length(strlist);
+ char *dbgstr = NULL;
+ struct ldb_dn **dns = NULL;
+
+ dns = talloc_array(mem_ctx, struct ldb_dn *, num_dns);
+ if (dns == NULL) {
+ TALLOC_FREE(dbgstr);
+ return NULL;
+ }
+
+ dbgstr = talloc_strdup(talloc_tos(), "");
+
+ for (i = 0; i < num_dns; i++) {
+ dns[i] = ldb_dn_new(dns, ldb, strlist[i]);
+ if (dns[i] == NULL) {
+ DBG_WARNING("ldb_dn_new(%s) failed\n", strlist[i]);
+ TALLOC_FREE(dns);
+ return NULL;
+ }
+ talloc_asprintf_addbuf(
+ &dbgstr,
+ "%s ",
+ ldb_dn_get_extended_linearized(dbgstr, dns[i], 1));
+ }
+
+ DBG_DEBUG("%s %s\n", dbgmsg, dbgstr);
+ TALLOC_FREE(dbgstr);
+
+ return dns;
+}
+
+static NTSTATUS idmap_ad_context_create(TALLOC_CTX *mem_ctx,
+ struct idmap_domain *dom,
+ const char *domname,
+ struct idmap_ad_context **pctx)
+{
+ struct idmap_ad_context *ctx;
+ const char *schema_mode;
+ const char **allow = NULL;
+ const char **deny = NULL;
+ NTSTATUS status;
+ TLDAPRC rc;
+
+ ctx = talloc_zero(mem_ctx, struct idmap_ad_context);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ctx->dom = dom;
+
+ talloc_set_destructor(ctx, idmap_ad_context_destructor);
+
+ status = idmap_ad_get_tldap_ctx(ctx, domname, &ctx->ld);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("idmap_ad_get_tldap_ctx failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ ctx->default_nc = get_default_nc(ctx, ctx->ld);
+ if (ctx->default_nc == NULL) {
+ DBG_DEBUG("No default nc\n");
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ ctx->unix_primary_group = idmap_config_bool(
+ domname, "unix_primary_group", false);
+ ctx->unix_nss_info = idmap_config_bool(
+ domname, "unix_nss_info", false);
+
+ schema_mode = idmap_config_const_string(
+ domname, "schema_mode", "rfc2307");
+
+ rc = get_posix_schema_names(ctx->ld, schema_mode, ctx, &ctx->schema);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ DBG_DEBUG("get_posix_schema_names failed: %s\n",
+ tldap_errstr(ctx, ctx->ld, rc));
+ TALLOC_FREE(ctx);
+ return NT_STATUS_LDAP(TLDAP_RC_V(rc));
+ }
+
+ deny = idmap_config_string_list(domname, "deny ous", NULL);
+ allow = idmap_config_string_list(domname, "allow ous", NULL);
+
+ if ((deny != NULL) || (allow != NULL)) {
+ int ret = ldb_global_init();
+ if (ret == -1) {
+ status = map_nt_error_from_unix(errno);
+ DBG_WARNING("ldb_global_init() failed: %s\n",
+ strerror(errno));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+
+ ctx->ldb = ldb_init(ctx, global_event_context());
+ if (ctx->ldb == NULL) {
+ status = map_nt_error_from_unix(errno);
+ DBG_WARNING("ldb_init() failed: %s\n", strerror(errno));
+ TALLOC_FREE(ctx);
+ return status;
+ }
+ }
+
+ if (deny != NULL) {
+ ctx->deny_ous = str_list_to_dns(ctx, "Denying", ctx->ldb, deny);
+ if (ctx->deny_ous == NULL) {
+ DBG_DEBUG("str_list_to_dns failed\n");
+ TALLOC_FREE(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (allow != NULL) {
+ ctx->allow_ous =
+ str_list_to_dns(ctx, "Allowing", ctx->ldb, allow);
+ if (ctx->allow_ous == NULL) {
+ DBG_DEBUG("str_list_to_dns failed\n");
+ TALLOC_FREE(ctx);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *pctx = ctx;
+ return NT_STATUS_OK;
+}
+
+static bool check_dn(struct ldb_dn **dns, struct ldb_dn *dn)
+{
+ size_t i, num_dns = talloc_array_length(dns);
+
+ for (i = 0; i < num_dns; i++) {
+ struct ldb_dn *base = dns[i];
+ int ret = ldb_dn_compare_base(base, dn);
+ if (ret == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool idmap_ad_dn_filter(struct idmap_domain *dom, const char *dnstr)
+{
+ struct idmap_ad_context *ctx = NULL;
+ struct ldb_dn *dn = NULL;
+ NTSTATUS status;
+ bool result = false;
+
+ status = idmap_ad_get_context(dom, &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("idmap_ad_get_context failed: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if ((ctx->allow_ous == NULL) && (ctx->deny_ous == NULL)) {
+ /*
+ * Nothing to check
+ */
+ return true;
+ }
+
+ dn = ldb_dn_new(talloc_tos(), ctx->ldb, dnstr);
+ if (dn == NULL) {
+ DBG_DEBUG("ldb_dn_new(%s) failed\n", dnstr);
+ return false;
+ }
+
+ if (ctx->deny_ous != NULL) {
+ bool denied = check_dn(ctx->deny_ous, dn);
+ if (denied) {
+ DBG_WARNING("Denied %s\n", dnstr);
+ TALLOC_FREE(dn);
+ return false;
+ }
+
+ if (ctx->allow_ous == NULL) {
+ /*
+ * Only a few denied OUs around, allow by
+ * default
+ */
+ result = true;
+ }
+ }
+
+ if (ctx->allow_ous != NULL) {
+ bool allowed = check_dn(ctx->allow_ous, dn);
+ if (allowed) {
+ return true;
+ }
+ DBG_WARNING("Did not allow %s\n", dnstr);
+ }
+
+ return result;
+}
+
+static NTSTATUS idmap_ad_query_user(struct idmap_domain *domain,
+ struct wbint_userinfo *info)
+{
+ struct idmap_ad_context *ctx;
+ TLDAPRC rc;
+ NTSTATUS status;
+ char *sidstr, *filter;
+ const char *attrs[4];
+ size_t i, num_msgs;
+ struct tldap_message **msgs;
+
+ status = idmap_ad_get_context(domain, &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!(ctx->unix_primary_group || ctx->unix_nss_info)) {
+ return NT_STATUS_OK;
+ }
+
+ attrs[0] = ctx->schema->gid;
+ attrs[1] = ctx->schema->gecos;
+ attrs[2] = ctx->schema->dir;
+ attrs[3] = ctx->schema->shell;
+
+ sidstr = ldap_encode_ndr_dom_sid(talloc_tos(), &info->user_sid);
+ if (sidstr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ filter = talloc_asprintf(talloc_tos(), "(objectsid=%s)", sidstr);
+ TALLOC_FREE(sidstr);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("Filter: [%s]\n", filter);
+
+ rc = tldap_search(ctx->ld, ctx->default_nc, TLDAP_SCOPE_SUB, filter,
+ attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0,
+ 0, 0, 0, talloc_tos(), &msgs);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ return NT_STATUS_LDAP(TLDAP_RC_V(rc));
+ }
+
+ TALLOC_FREE(filter);
+
+ num_msgs = talloc_array_length(msgs);
+
+ for (i=0; i<num_msgs; i++) {
+ struct tldap_message *msg = msgs[i];
+ char *dn = NULL;
+ bool ok;
+
+ if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) {
+ continue;
+ }
+ ok = tldap_entry_dn(msg, &dn);
+ if (!ok) {
+ continue;
+ }
+ ok = idmap_ad_dn_filter(domain, dn);
+ if (!ok) {
+ DBG_DEBUG("%s filtered out\n", dn);
+ continue;
+ }
+
+ if (ctx->unix_primary_group) {
+ uint32_t gid;
+
+ ok = tldap_pull_uint32(msg, ctx->schema->gid, &gid);
+ if (ok) {
+ DBG_DEBUG("Setting primary group "
+ "to %"PRIu32" from attr %s\n",
+ gid, ctx->schema->gid);
+ info->primary_gid = gid;
+ }
+ }
+
+ if (ctx->unix_nss_info) {
+ char *attr;
+
+ attr = tldap_talloc_single_attribute(
+ msg, ctx->schema->dir, talloc_tos());
+ if (attr != NULL) {
+ info->homedir = talloc_move(info, &attr);
+ }
+ TALLOC_FREE(attr);
+
+ attr = tldap_talloc_single_attribute(
+ msg, ctx->schema->shell, talloc_tos());
+ if (attr != NULL) {
+ info->shell = talloc_move(info, &attr);
+ }
+ TALLOC_FREE(attr);
+
+ attr = tldap_talloc_single_attribute(
+ msg, ctx->schema->gecos, talloc_tos());
+ if (attr != NULL) {
+ info->full_name = talloc_move(info, &attr);
+ }
+ TALLOC_FREE(attr);
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_ad_query_user_retry(struct idmap_domain *domain,
+ struct wbint_userinfo *info)
+{
+ const NTSTATUS status_server_down =
+ NT_STATUS_LDAP(TLDAP_RC_V(TLDAP_SERVER_DOWN));
+ NTSTATUS status;
+
+ status = idmap_ad_query_user(domain, info);
+
+ if (NT_STATUS_EQUAL(status, status_server_down)) {
+ TALLOC_FREE(domain->private_data);
+ status = idmap_ad_query_user(domain, info);
+ }
+
+ return status;
+}
+
+static NTSTATUS idmap_ad_initialize(struct idmap_domain *dom)
+{
+ dom->query_user = idmap_ad_query_user_retry;
+ dom->private_data = NULL;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_ad_get_context(struct idmap_domain *dom,
+ struct idmap_ad_context **pctx)
+{
+ struct idmap_ad_context *ctx = NULL;
+ NTSTATUS status;
+
+ if (IS_AD_DC) {
+ /*
+ * Make sure we never try to use LDAP against
+ * a trusted domain as AD_DC.
+ *
+ * This shouldn't be called currently,
+ * but you never know what happens in future.
+ */
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ if (dom->private_data != NULL) {
+ *pctx = talloc_get_type_abort(dom->private_data,
+ struct idmap_ad_context);
+ return NT_STATUS_OK;
+ }
+
+ status = idmap_ad_context_create(dom, dom, dom->name, &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("idmap_ad_context_create failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ dom->private_data = ctx;
+ *pctx = ctx;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_ad_unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_ad_context *ctx;
+ TLDAPRC rc;
+ NTSTATUS status;
+ struct tldap_message **msgs;
+
+ size_t i, num_msgs;
+ char *u_filter, *g_filter, *filter;
+
+ const char *attrs[] = {
+ "sAMAccountType",
+ "objectSid",
+ NULL, /* attr_uidnumber */
+ NULL, /* attr_gidnumber */
+ };
+
+ status = idmap_ad_get_context(dom, &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ attrs[2] = ctx->schema->uid;
+ attrs[3] = ctx->schema->gid;
+
+ u_filter = talloc_strdup(talloc_tos(), "");
+ if (u_filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ g_filter = talloc_strdup(talloc_tos(), "");
+ if (g_filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; ids[i] != NULL; i++) {
+ struct id_map *id = ids[i];
+
+ id->status = ID_UNKNOWN;
+
+ switch (id->xid.type) {
+ case ID_TYPE_UID: {
+ u_filter = talloc_asprintf_append_buffer(
+ u_filter, "(%s=%ju)", ctx->schema->uid,
+ (uintmax_t)id->xid.id);
+ if (u_filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ }
+
+ case ID_TYPE_GID: {
+ g_filter = talloc_asprintf_append_buffer(
+ g_filter, "(%s=%ju)", ctx->schema->gid,
+ (uintmax_t)id->xid.id);
+ if (g_filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ break;
+ }
+
+ default:
+ DBG_WARNING("Unknown id type: %u\n",
+ (unsigned)id->xid.type);
+ break;
+ }
+ }
+
+ filter = talloc_strdup(talloc_tos(), "(|");
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (*u_filter != '\0') {
+ filter = talloc_asprintf_append_buffer(
+ filter,
+ "(&(|(sAMAccountType=%d)(sAMAccountType=%d)"
+ "(sAMAccountType=%d))(|%s))",
+ ATYPE_NORMAL_ACCOUNT, ATYPE_WORKSTATION_TRUST,
+ ATYPE_INTERDOMAIN_TRUST, u_filter);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ TALLOC_FREE(u_filter);
+
+ if (*g_filter != '\0') {
+ filter = talloc_asprintf_append_buffer(
+ filter,
+ "(&(|(sAMAccountType=%d)(sAMAccountType=%d))(|%s))",
+ ATYPE_SECURITY_GLOBAL_GROUP,
+ ATYPE_SECURITY_LOCAL_GROUP,
+ g_filter);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ TALLOC_FREE(g_filter);
+
+ filter = talloc_asprintf_append_buffer(filter, ")");
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("Filter: [%s]\n", filter);
+
+ rc = tldap_search(ctx->ld, ctx->default_nc, TLDAP_SCOPE_SUB, filter,
+ attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0,
+ 0, 0, 0, talloc_tos(), &msgs);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ return NT_STATUS_LDAP(TLDAP_RC_V(rc));
+ }
+
+ TALLOC_FREE(filter);
+
+ num_msgs = talloc_array_length(msgs);
+
+ for (i=0; i<num_msgs; i++) {
+ struct tldap_message *msg = msgs[i];
+ char *dn;
+ struct id_map *map;
+ struct dom_sid sid;
+ size_t j;
+ bool ok;
+ uint32_t atype, xid;
+ enum id_type type;
+ struct dom_sid_buf sidbuf;
+
+ if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) {
+ continue;
+ }
+
+ ok = tldap_entry_dn(msg, &dn);
+ if (!ok) {
+ DBG_DEBUG("No dn found in msg %zu\n", i);
+ continue;
+ }
+
+ ok = idmap_ad_dn_filter(dom, dn);
+ if (!ok) {
+ DBG_DEBUG("%s filtered out\n", dn);
+ continue;
+ }
+
+ ok = tldap_pull_uint32(msg, "sAMAccountType", &atype);
+ if (!ok) {
+ DBG_DEBUG("No atype in object %s\n", dn);
+ continue;
+ }
+
+ switch (atype & 0xF0000000) {
+ case ATYPE_SECURITY_GLOBAL_GROUP:
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ type = ID_TYPE_GID;
+ break;
+ case ATYPE_NORMAL_ACCOUNT:
+ case ATYPE_WORKSTATION_TRUST:
+ case ATYPE_INTERDOMAIN_TRUST:
+ type = ID_TYPE_UID;
+ break;
+ default:
+ DBG_WARNING("unrecognized SAM account type %08x\n",
+ atype);
+ continue;
+ }
+
+ ok = tldap_pull_uint32(msg, (type == ID_TYPE_UID) ?
+ ctx->schema->uid : ctx->schema->gid,
+ &xid);
+ if (!ok) {
+ DBG_WARNING("No unix id in object %s\n", dn);
+ continue;
+ }
+
+ ok = tldap_pull_binsid(msg, "objectSid", &sid);
+ if (!ok) {
+ DBG_DEBUG("No objectSid in object %s\n", dn);
+ continue;
+ }
+
+ map = NULL;
+ for (j=0; ids[j]; j++) {
+ if ((type == ids[j]->xid.type) &&
+ (xid == ids[j]->xid.id)) {
+ map = ids[j];
+ break;
+ }
+ }
+ if (map == NULL) {
+ DBG_DEBUG("Got unexpected sid %s from object %s\n",
+ dom_sid_str_buf(&sid, &sidbuf),
+ dn);
+ continue;
+ }
+
+ sid_copy(map->sid, &sid);
+ map->status = ID_MAPPED;
+
+ DBG_DEBUG("Mapped %s -> %ju (%d)\n",
+ dom_sid_str_buf(map->sid, &sidbuf),
+ (uintmax_t)map->xid.id, map->xid.type);
+ }
+
+ TALLOC_FREE(msgs);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_ad_sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_ad_context *ctx;
+ TLDAPRC rc;
+ NTSTATUS status;
+ struct tldap_message **msgs;
+
+ char *filter;
+ size_t i, num_msgs;
+
+ const char *attrs[] = {
+ "sAMAccountType",
+ "objectSid",
+ NULL, /* attr_uidnumber */
+ NULL, /* attr_gidnumber */
+ };
+
+ status = idmap_ad_get_context(dom, &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ attrs[2] = ctx->schema->uid;
+ attrs[3] = ctx->schema->gid;
+
+ filter = talloc_asprintf(
+ talloc_tos(),
+ "(&(|(sAMAccountType=%d)(sAMAccountType=%d)(sAMAccountType=%d)"
+ "(sAMAccountType=%d)(sAMAccountType=%d))(|",
+ ATYPE_NORMAL_ACCOUNT, ATYPE_WORKSTATION_TRUST,
+ ATYPE_INTERDOMAIN_TRUST, ATYPE_SECURITY_GLOBAL_GROUP,
+ ATYPE_SECURITY_LOCAL_GROUP);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; ids[i]; i++) {
+ char *sidstr;
+
+ ids[i]->status = ID_UNKNOWN;
+
+ sidstr = ldap_encode_ndr_dom_sid(talloc_tos(), ids[i]->sid);
+ if (sidstr == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ filter = talloc_asprintf_append_buffer(
+ filter, "(objectSid=%s)", sidstr);
+ TALLOC_FREE(sidstr);
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ filter = talloc_asprintf_append_buffer(filter, "))");
+ if (filter == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DBG_DEBUG("Filter: [%s]\n", filter);
+
+ rc = tldap_search(ctx->ld, ctx->default_nc, TLDAP_SCOPE_SUB, filter,
+ attrs, ARRAY_SIZE(attrs), 0, NULL, 0, NULL, 0,
+ 0, 0, 0, talloc_tos(), &msgs);
+ if (!TLDAP_RC_IS_SUCCESS(rc)) {
+ return NT_STATUS_LDAP(TLDAP_RC_V(rc));
+ }
+
+ TALLOC_FREE(filter);
+
+ num_msgs = talloc_array_length(msgs);
+
+ for (i=0; i<num_msgs; i++) {
+ struct tldap_message *msg = msgs[i];
+ char *dn;
+ struct id_map *map;
+ struct dom_sid sid;
+ size_t j;
+ bool ok;
+ uint64_t account_type, xid;
+ enum id_type type;
+ struct dom_sid_buf buf;
+
+ if (tldap_msg_type(msg) != TLDAP_RES_SEARCH_ENTRY) {
+ continue;
+ }
+
+ ok = tldap_entry_dn(msg, &dn);
+ if (!ok) {
+ DBG_DEBUG("No dn found in msg %zu\n", i);
+ continue;
+ }
+
+ ok = idmap_ad_dn_filter(dom, dn);
+ if (!ok) {
+ DBG_DEBUG("%s filtered out\n", dn);
+ continue;
+ }
+
+ ok = tldap_pull_binsid(msg, "objectSid", &sid);
+ if (!ok) {
+ DBG_DEBUG("No objectSid in object %s\n", dn);
+ continue;
+ }
+
+ map = NULL;
+ for (j=0; ids[j]; j++) {
+ if (dom_sid_equal(&sid, ids[j]->sid)) {
+ map = ids[j];
+ break;
+ }
+ }
+ if (map == NULL) {
+ DBG_DEBUG("Got unexpected sid %s from object %s\n",
+ dom_sid_str_buf(&sid, &buf),
+ dn);
+ continue;
+ }
+
+ ok = tldap_pull_uint64(msg, "sAMAccountType", &account_type);
+ if (!ok) {
+ DBG_DEBUG("No sAMAccountType in %s\n", dn);
+ continue;
+ }
+
+ switch (account_type & 0xF0000000) {
+ case ATYPE_SECURITY_GLOBAL_GROUP:
+ case ATYPE_SECURITY_LOCAL_GROUP:
+ type = ID_TYPE_GID;
+ break;
+ case ATYPE_NORMAL_ACCOUNT:
+ case ATYPE_WORKSTATION_TRUST:
+ case ATYPE_INTERDOMAIN_TRUST:
+ type = ID_TYPE_UID;
+ break;
+ default:
+ DBG_WARNING("unrecognized SAM account type %"PRIu64"\n",
+ account_type);
+ continue;
+ }
+
+ ok = tldap_pull_uint64(msg,
+ type == ID_TYPE_UID ?
+ ctx->schema->uid : ctx->schema->gid,
+ &xid);
+ if (!ok) {
+ DBG_DEBUG("No xid in %s\n", dn);
+ continue;
+ }
+
+ /* mapped */
+ map->xid.type = type;
+ map->xid.id = xid;
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Mapped %s -> %lu (%d)\n",
+ dom_sid_str_buf(map->sid, &buf),
+ (unsigned long)map->xid.id, map->xid.type));
+ }
+
+ TALLOC_FREE(msgs);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_ad_unixids_to_sids_retry(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ const NTSTATUS status_server_down =
+ NT_STATUS_LDAP(TLDAP_RC_V(TLDAP_SERVER_DOWN));
+ NTSTATUS status;
+
+ status = idmap_ad_unixids_to_sids(dom, ids);
+
+ if (NT_STATUS_EQUAL(status, status_server_down)) {
+ TALLOC_FREE(dom->private_data);
+ status = idmap_ad_unixids_to_sids(dom, ids);
+ }
+
+ return status;
+}
+
+static NTSTATUS idmap_ad_sids_to_unixids_retry(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ const NTSTATUS status_server_down =
+ NT_STATUS_LDAP(TLDAP_RC_V(TLDAP_SERVER_DOWN));
+ NTSTATUS status;
+
+ status = idmap_ad_sids_to_unixids(dom, ids);
+
+ if (NT_STATUS_EQUAL(status, status_server_down)) {
+ TALLOC_FREE(dom->private_data);
+ status = idmap_ad_sids_to_unixids(dom, ids);
+ }
+
+ return status;
+}
+
+static const struct idmap_methods ad_methods = {
+ .init = idmap_ad_initialize,
+ .unixids_to_sids = idmap_ad_unixids_to_sids_retry,
+ .sids_to_unixids = idmap_ad_sids_to_unixids_retry,
+};
+
+static_decl_idmap;
+NTSTATUS idmap_ad_init(TALLOC_CTX *ctx)
+{
+ NTSTATUS status;
+
+ status = smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION,
+ "ad", &ad_methods);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = idmap_ad_nss_init(ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/idmap_ad_nss.c b/source3/winbindd/idmap_ad_nss.c
new file mode 100644
index 0000000..3120280
--- /dev/null
+++ b/source3/winbindd/idmap_ad_nss.c
@@ -0,0 +1,418 @@
+/*
+ * idmap_ad: map between Active Directory and RFC 2307 or "Services for Unix" (SFU) Accounts
+ *
+ * Unix SMB/CIFS implementation.
+ *
+ * Winbind ADS backend functions
+ *
+ * Copyright (C) Andrew Tridgell 2001
+ * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+ * Copyright (C) Gerald (Jerry) Carter 2004-2007
+ * Copyright (C) Luke Howard 2001-2004
+ * Copyright (C) Michael Adam 2008,2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libds/common/flags.h"
+#include "winbindd_ads.h"
+#include "libads/ldap_schema.h"
+#include "nss_info.h"
+#include "idmap.h"
+#include "../libcli/ldap/ldap_ndr.h"
+#include "../libcli/security/security.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+#define CHECK_ALLOC_DONE(mem) do { \
+ if (!mem) { \
+ DEBUG(0, ("Out of memory!\n")); \
+ ret = NT_STATUS_NO_MEMORY; \
+ goto done; \
+ } \
+} while (0)
+
+struct idmap_ad_context {
+ ADS_STRUCT *ads;
+ struct posix_schema *ad_schema;
+ enum wb_posix_mapping ad_map_type; /* WB_POSIX_MAP_UNKNOWN */
+};
+
+/************************************************************************
+ ***********************************************************************/
+
+static ADS_STATUS ad_idmap_cached_connection(struct idmap_domain *dom)
+{
+ ADS_STATUS status;
+ struct idmap_ad_context * ctx;
+
+ DEBUG(10, ("ad_idmap_cached_connection: called for domain '%s'\n",
+ dom->name));
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ad_context);
+
+ status = ads_idmap_cached_connection(dom->name, ctx, &ctx->ads);
+ if (!ADS_ERR_OK(status)) {
+ return status;
+ }
+
+ /* if we have a valid ADS_STRUCT and the schema model is
+ defined, then we can return here. */
+
+ if ( ctx->ad_schema ) {
+ return ADS_SUCCESS;
+ }
+
+ /* Otherwise, set the schema model */
+
+ if ( (ctx->ad_map_type == WB_POSIX_MAP_SFU) ||
+ (ctx->ad_map_type == WB_POSIX_MAP_SFU20) ||
+ (ctx->ad_map_type == WB_POSIX_MAP_RFC2307) )
+ {
+ status = ads_check_posix_schema_mapping(
+ ctx, ctx->ads, ctx->ad_map_type, &ctx->ad_schema);
+ if ( !ADS_ERR_OK(status) ) {
+ DEBUG(2,("ad_idmap_cached_connection: Failed to obtain schema details!\n"));
+ }
+ }
+
+ return status;
+}
+
+/*
+ * nss_info_{sfu,sfu20,rfc2307}
+ */
+
+/************************************************************************
+ Initialize the {sfu,sfu20,rfc2307} state
+ ***********************************************************************/
+
+static const char *wb_posix_map_unknown_string = "WB_POSIX_MAP_UNKNOWN";
+static const char *wb_posix_map_template_string = "WB_POSIX_MAP_TEMPLATE";
+static const char *wb_posix_map_sfu_string = "WB_POSIX_MAP_SFU";
+static const char *wb_posix_map_sfu20_string = "WB_POSIX_MAP_SFU20";
+static const char *wb_posix_map_rfc2307_string = "WB_POSIX_MAP_RFC2307";
+static const char *wb_posix_map_unixinfo_string = "WB_POSIX_MAP_UNIXINFO";
+
+static const char *ad_map_type_string(enum wb_posix_mapping map_type)
+{
+ switch (map_type) {
+ case WB_POSIX_MAP_TEMPLATE:
+ return wb_posix_map_template_string;
+ case WB_POSIX_MAP_SFU:
+ return wb_posix_map_sfu_string;
+ case WB_POSIX_MAP_SFU20:
+ return wb_posix_map_sfu20_string;
+ case WB_POSIX_MAP_RFC2307:
+ return wb_posix_map_rfc2307_string;
+ case WB_POSIX_MAP_UNIXINFO:
+ return wb_posix_map_unixinfo_string;
+ default:
+ return wb_posix_map_unknown_string;
+ }
+}
+
+static NTSTATUS nss_ad_generic_init(struct nss_domain_entry *e,
+ enum wb_posix_mapping new_ad_map_type)
+{
+ struct idmap_domain *dom;
+ struct idmap_ad_context *ctx;
+
+ if (e->state != NULL) {
+ dom = talloc_get_type(e->state, struct idmap_domain);
+ } else {
+ dom = talloc_zero(e, struct idmap_domain);
+ if (dom == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ e->state = dom;
+ }
+
+ if (e->domain != NULL) {
+ dom->name = talloc_strdup(dom, e->domain);
+ if (dom->name == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (dom->private_data != NULL) {
+ ctx = talloc_get_type(dom->private_data,
+ struct idmap_ad_context);
+ } else {
+ ctx = talloc_zero(dom, struct idmap_ad_context);
+ if (ctx == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ ctx->ad_map_type = WB_POSIX_MAP_RFC2307;
+ dom->private_data = ctx;
+ }
+
+ if ((ctx->ad_map_type != WB_POSIX_MAP_UNKNOWN) &&
+ (ctx->ad_map_type != new_ad_map_type))
+ {
+ DEBUG(2, ("nss_ad_generic_init: "
+ "Warning: overriding previously set posix map type "
+ "%s for domain %s with map type %s.\n",
+ ad_map_type_string(ctx->ad_map_type),
+ dom->name,
+ ad_map_type_string(new_ad_map_type)));
+ }
+
+ ctx->ad_map_type = new_ad_map_type;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nss_sfu_init( struct nss_domain_entry *e )
+{
+ return nss_ad_generic_init(e, WB_POSIX_MAP_SFU);
+}
+
+static NTSTATUS nss_sfu20_init( struct nss_domain_entry *e )
+{
+ return nss_ad_generic_init(e, WB_POSIX_MAP_SFU20);
+}
+
+static NTSTATUS nss_rfc2307_init( struct nss_domain_entry *e )
+{
+ return nss_ad_generic_init(e, WB_POSIX_MAP_RFC2307);
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS nss_ad_map_to_alias(TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *name,
+ char **alias)
+{
+ const char *attrs[] = {NULL, /* attr_uid */
+ NULL };
+ char *filter = NULL;
+ LDAPMessage *msg = NULL;
+ ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct idmap_domain *dom;
+ struct idmap_ad_context *ctx = NULL;
+
+ /* Check incoming parameters */
+
+ if ( !e || !e->domain || !name || !*alias) {
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* Only do query if we are online */
+
+ if (idmap_is_offline()) {
+ nt_status = NT_STATUS_FILE_IS_OFFLINE;
+ goto done;
+ }
+
+ dom = talloc_get_type(e->state, struct idmap_domain);
+ ctx = talloc_get_type(dom->private_data, struct idmap_ad_context);
+
+ ads_status = ad_idmap_cached_connection(dom);
+ if (!ADS_ERR_OK(ads_status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!ctx->ad_schema) {
+ nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto done;
+ }
+
+ attrs[0] = ctx->ad_schema->posix_uid_attr;
+
+ filter = talloc_asprintf(mem_ctx,
+ "(sAMAccountName=%s)",
+ name);
+ if (!filter) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ads_status = ads_search_retry(ctx->ads, &msg, filter, attrs);
+ if (!ADS_ERR_OK(ads_status)) {
+ nt_status = ads_ntstatus(ads_status);
+ goto done;
+ }
+
+ *alias = ads_pull_string(ctx->ads, mem_ctx, msg, ctx->ad_schema->posix_uid_attr);
+
+ if (!*alias) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ if (filter) {
+ talloc_destroy(filter);
+ }
+ if (msg) {
+ ads_msgfree(ctx->ads, msg);
+ }
+
+ return nt_status;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS nss_ad_map_from_alias( TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *alias,
+ char **name )
+{
+ const char *attrs[] = {"sAMAccountName",
+ NULL };
+ char *filter = NULL;
+ LDAPMessage *msg = NULL;
+ ADS_STATUS ads_status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ char *username = NULL;
+ struct idmap_domain *dom;
+ struct idmap_ad_context *ctx = NULL;
+
+ /* Check incoming parameters */
+
+ if ( !alias || !name) {
+ nt_status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* Only do query if we are online */
+
+ if (idmap_is_offline()) {
+ nt_status = NT_STATUS_FILE_IS_OFFLINE;
+ goto done;
+ }
+
+ dom = talloc_get_type(e->state, struct idmap_domain);
+ ctx = talloc_get_type(dom->private_data, struct idmap_ad_context);
+
+ ads_status = ad_idmap_cached_connection(dom);
+ if (!ADS_ERR_OK(ads_status)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (!ctx->ad_schema) {
+ nt_status = NT_STATUS_OBJECT_PATH_NOT_FOUND;
+ goto done;
+ }
+
+ filter = talloc_asprintf(mem_ctx,
+ "(%s=%s)",
+ ctx->ad_schema->posix_uid_attr,
+ alias);
+ if (!filter) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ads_status = ads_search_retry(ctx->ads, &msg, filter, attrs);
+ if (!ADS_ERR_OK(ads_status)) {
+ nt_status = ads_ntstatus(ads_status);
+ goto done;
+ }
+
+ username = ads_pull_string(ctx->ads, mem_ctx, msg,
+ "sAMAccountName");
+ if (!username) {
+ nt_status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto done;
+ }
+
+ *name = talloc_asprintf(mem_ctx, "%s\\%s",
+ lp_workgroup(),
+ username);
+ if (!*name) {
+ nt_status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ nt_status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(username);
+ TALLOC_FREE(filter);
+ if (msg) {
+ ads_msgfree(ctx->ads, msg);
+ }
+
+ return nt_status;
+}
+
+/************************************************************************
+ Function dispatch tables for the idmap and nss plugins
+ ***********************************************************************/
+
+/* The SFU and RFC2307 NSS plugins share everything but the init
+ function which sets the intended schema model to use */
+
+static const struct nss_info_methods nss_rfc2307_methods = {
+ .init = nss_rfc2307_init,
+ .map_to_alias = nss_ad_map_to_alias,
+ .map_from_alias = nss_ad_map_from_alias,
+};
+
+static const struct nss_info_methods nss_sfu_methods = {
+ .init = nss_sfu_init,
+ .map_to_alias = nss_ad_map_to_alias,
+ .map_from_alias = nss_ad_map_from_alias,
+};
+
+static const struct nss_info_methods nss_sfu20_methods = {
+ .init = nss_sfu20_init,
+ .map_to_alias = nss_ad_map_to_alias,
+ .map_from_alias = nss_ad_map_from_alias,
+};
+
+
+
+/************************************************************************
+ Initialize the plugins
+ ***********************************************************************/
+
+NTSTATUS idmap_ad_nss_init(TALLOC_CTX *mem_ctx)
+{
+ NTSTATUS status;
+
+ status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "rfc2307", &nss_rfc2307_methods);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "sfu", &nss_sfu_methods);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "sfu20", &nss_sfu20_methods);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/idmap_autorid.c b/source3/winbindd/idmap_autorid.c
new file mode 100644
index 0000000..bf5947a
--- /dev/null
+++ b/source3/winbindd/idmap_autorid.c
@@ -0,0 +1,945 @@
+/*
+ * idmap_autorid: static map between Active Directory/NT RIDs
+ * and RFC 2307 accounts
+ *
+ * based on the idmap_rid module, but this module defines the ranges
+ * for the domains by automatically allocating a range for each domain
+ *
+ * Copyright (C) Christian Ambach, 2010-2012
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*
+ * This module allocates ranges for domains to be used in a
+ * algorithmic mode like idmap_rid. Multiple ranges are supported
+ * for a single domain: If a rid exceeds the range size, a matching
+ * range is allocated to hold the rid's id.
+ *
+ * Here are the formulas applied:
+ *
+ *
+ * For a sid of the form domain_sid-rid, we have
+ *
+ * rid = reduced_rid + domain_range_index * range_size
+ *
+ * with
+ * reduced_rid := rid % range_size
+ * domain_range_index := rid / range_size
+ *
+ * And reduced_rid fits into a range.
+ *
+ * In the database, we associate a range_number to
+ * the pair domain_sid,domain_range_index.
+ *
+ * Now the unix id for the given sid calculates as:
+ *
+ * id = reduced_rid + range_low_id
+ *
+ * with
+ *
+ * range_low_id = low_id + range_number * range_size
+ *
+ *
+ * The inverse calculation goes like this:
+ *
+ * Given a unix id, let
+ *
+ * normalized_id := id - low_id
+ * reduced_rid := normalized_id % range_size
+ * range_number = normalized_id / range_size
+ *
+ * Then we have
+ *
+ * id = reduced_rid + low_id + range_number * range_size
+ *
+ * From the database, get the domain_sid,domain_range_index pair
+ * belonging to the range_number (if there is already one).
+ *
+ * Then the rid for the unix id calculates as:
+ *
+ * rid = reduced_rid + domain_range_index * range_size
+ */
+
+#include "idmap_autorid_tdb.h"
+#include "winbindd.h"
+#include "idmap.h"
+#include "idmap_rw.h"
+#include "../libcli/security/dom_sid.h"
+#include "libsmb/samlogon_cache.h"
+#include "passdb/machine_sid.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+#define IDMAP_AUTORID_ALLOC_RESERVED 500
+
+/* handle to the tdb storing domain <-> range assignments */
+static struct db_context *autorid_db;
+
+static bool ignore_builtin = false;
+
+static NTSTATUS idmap_autorid_get_alloc_range(struct idmap_domain *dom,
+ struct autorid_range_config *range)
+{
+ NTSTATUS status;
+
+ ZERO_STRUCT(*range);
+
+ fstrcpy(range->domsid, ALLOC_RANGE);
+
+ status = idmap_autorid_get_domainrange(autorid_db,
+ range,
+ dom->read_only);
+
+ return status;
+}
+
+static NTSTATUS idmap_autorid_allocate_id(struct idmap_domain *dom,
+ struct unixid *xid) {
+
+ NTSTATUS ret;
+ struct autorid_range_config range;
+
+ if (dom->read_only) {
+ DEBUG(3, ("Backend is read-only, refusing "
+ "new allocation request\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* fetch the range for the allocation pool */
+
+ ret = idmap_autorid_get_alloc_range(dom, &range);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(3, ("Could not determine range for allocation pool, "
+ "check previous messages for reason\n"));
+ return ret;
+ }
+
+ ret = idmap_tdb_common_get_new_id(dom, xid);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while allocating new ID!\n"));
+ return ret;
+ }
+
+ xid->id = xid->id + range.low_id;
+
+ DEBUG(10, ("Returned new %s %d from allocation range\n",
+ (xid->type==ID_TYPE_UID)?"uid":"gid", xid->id));
+
+ return ret;
+}
+
+/*
+ * map a xid to SID using the idmap_tdb like pool
+ */
+static NTSTATUS idmap_autorid_id_to_sid_alloc(struct idmap_domain *dom,
+ struct id_map *map)
+{
+ NTSTATUS ret;
+
+ /* look out for the mapping */
+ ret = idmap_tdb_common_unixid_to_sid(dom, map);
+
+ if (NT_STATUS_IS_OK(ret)) {
+ map->status = ID_MAPPED;
+ return ret;
+ }
+
+ map->status = ID_UNKNOWN;
+
+ DEBUG(10, ("no ID->SID mapping for %d could be found\n", map->xid.id));
+
+ return ret;
+}
+
+static NTSTATUS idmap_autorid_id_to_sid(struct autorid_global_config *cfg,
+ struct idmap_domain *dom,
+ struct id_map *map)
+{
+ uint32_t range_number;
+ uint32_t domain_range_index;
+ uint32_t normalized_id;
+ uint32_t reduced_rid;
+ uint32_t rid;
+ TDB_DATA data = tdb_null;
+ char *keystr;
+ struct dom_sid domsid;
+ NTSTATUS status;
+ bool ok;
+ const char *q = NULL;
+
+ /* can this be one of our ids? */
+ if (map->xid.id < cfg->minvalue) {
+ DEBUG(10, ("id %d is lower than minimum value, "
+ "ignoring mapping request\n", map->xid.id));
+ map->status = ID_UNKNOWN;
+ return NT_STATUS_OK;
+ }
+
+ if (map->xid.id > (cfg->minvalue + cfg->rangesize * cfg->maxranges)) {
+ DEBUG(10, ("id %d is outside of maximum id value, "
+ "ignoring mapping request\n", map->xid.id));
+ map->status = ID_UNKNOWN;
+ return NT_STATUS_OK;
+ }
+
+ /* determine the range of this uid */
+
+ normalized_id = map->xid.id - cfg->minvalue;
+ range_number = normalized_id / cfg->rangesize;
+
+ keystr = talloc_asprintf(talloc_tos(), "%u", range_number);
+ if (!keystr) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dbwrap_fetch_bystring(autorid_db, talloc_tos(), keystr, &data);
+ TALLOC_FREE(keystr);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(4, ("id %d belongs to range %d which does not have "
+ "domain mapping, ignoring mapping request\n",
+ map->xid.id, range_number));
+ TALLOC_FREE(data.dptr);
+ map->status = ID_UNKNOWN;
+ return NT_STATUS_OK;
+ }
+
+ if ((data.dsize == 0) || (data.dptr[data.dsize-1] != '\0')) {
+ DBG_WARNING("Invalid range %"PRIu32"\n", range_number);
+ TALLOC_FREE(data.dptr);
+ map->status = ID_UNKNOWN;
+ return NT_STATUS_OK;
+ }
+
+ if (strncmp((const char *)data.dptr,
+ ALLOC_RANGE,
+ strlen(ALLOC_RANGE)) == 0) {
+ /*
+ * this is from the alloc range, check if there is a mapping
+ */
+ DEBUG(5, ("id %d belongs to allocation range, "
+ "checking for mapping\n",
+ map->xid.id));
+ TALLOC_FREE(data.dptr);
+ return idmap_autorid_id_to_sid_alloc(dom, map);
+ }
+
+ ok = dom_sid_parse_endp((const char *)data.dptr, &domsid, &q);
+ if (!ok) {
+ TALLOC_FREE(data.dptr);
+ map->status = ID_UNKNOWN;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Allow for sid#range_index, just sid is range index 0
+ */
+
+ switch (*q) {
+ case '\0':
+ domain_range_index = 0;
+ break;
+ case '#':
+ if (sscanf(q+1, "%"SCNu32, &domain_range_index) == 1) {
+ break;
+ }
+ /* If we end up here, something weird is in the record. */
+
+ FALL_THROUGH;
+ default:
+ DBG_DEBUG("SID/domain range: %s\n",
+ (const char *)data.dptr);
+ TALLOC_FREE(data.dptr);
+ map->status = ID_UNKNOWN;
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(data.dptr);
+
+ reduced_rid = normalized_id % cfg->rangesize;
+ rid = reduced_rid + domain_range_index * cfg->rangesize;
+
+ sid_compose(map->sid, &domsid, rid);
+
+ /* We **really** should have some way of validating
+ the SID exists and is the correct type here. But
+ that is a deficiency in the idmap_rid design. */
+
+ map->status = ID_MAPPED;
+ map->xid.type = ID_TYPE_BOTH;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Single sid to id lookup function.
+**********************************/
+
+static NTSTATUS idmap_autorid_sid_to_id_rid(
+ uint32_t rangesize,
+ uint32_t low_id,
+ struct id_map *map)
+{
+ uint32_t rid;
+ uint32_t reduced_rid;
+
+ sid_peek_rid(map->sid, &rid);
+
+ reduced_rid = rid % rangesize;
+
+ map->xid.id = reduced_rid + low_id;
+ map->xid.type = ID_TYPE_BOTH;
+ map->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_autorid_unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_tdb_common_context *commoncfg;
+ struct autorid_global_config *globalcfg;
+ NTSTATUS ret;
+ int i;
+ int num_tomap = 0;
+ int num_mapped = 0;
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ num_tomap++;
+ }
+
+ commoncfg =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ globalcfg = talloc_get_type(commoncfg->private_data,
+ struct autorid_global_config);
+
+ for (i = 0; ids[i]; i++) {
+
+ ret = idmap_autorid_id_to_sid(globalcfg, dom, ids[i]);
+
+ if ((!NT_STATUS_IS_OK(ret)) &&
+ (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+ /* some fatal error occurred, log it */
+ DBG_NOTICE("Unexpected error resolving an ID "
+ "(%d): %s\n", ids[i]->xid.id,
+ nt_errstr(ret));
+ goto failure;
+ }
+
+ if (NT_STATUS_IS_OK(ret) && ids[i]->status == ID_MAPPED) {
+ num_mapped++;
+ }
+
+ }
+
+ if (num_tomap == num_mapped) {
+ return NT_STATUS_OK;
+ }
+ if (num_mapped == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return STATUS_SOME_UNMAPPED;
+
+
+ failure:
+ return ret;
+}
+
+static bool idmap_autorid_sid_is_special(struct dom_sid *sid)
+{
+ bool match;
+
+ match = sid_check_is_in_wellknown_domain(sid);
+ if (match) {
+ return true;
+ }
+
+ return false;
+}
+
+static NTSTATUS idmap_autorid_sid_to_id_special(struct idmap_domain *dom,
+ struct id_map *map)
+{
+ struct idmap_tdb_common_context *common =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+ uint32_t count;
+ struct autorid_range_config range;
+ NTSTATUS status;
+ uint32_t free_id;
+
+ status = idmap_autorid_get_alloc_range(dom, &range);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* Take the next free ID, counting from the top */
+ free_id = 0;
+ for (count = 0; count < IDMAP_AUTORID_ALLOC_RESERVED; count++) {
+ struct id_map test_map;
+ struct dom_sid sid;
+
+ test_map.sid = &sid;
+ test_map.xid.type = map->xid.type;
+ test_map.xid.id = range.high_id - count;
+ test_map.status = ID_UNKNOWN;
+
+ status = idmap_tdb_common_unixid_to_sid(dom, &test_map);
+ if (NT_STATUS_EQUAL(NT_STATUS_NONE_MAPPED, status)) {
+ free_id = test_map.xid.id;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* error - get out */
+ return status;
+ }
+
+ /* mapping exists - try next ID */
+ }
+
+ if (free_id == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ map->status = ID_MAPPED;
+ map->xid.id = free_id;
+
+ status = common->rw_ops->set_mapping(dom, map);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("Error storing new mapping: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+struct idmap_autorid_sid_to_id_alloc_ctx {
+ struct idmap_domain *dom;
+ struct id_map *map;
+};
+
+static NTSTATUS idmap_autorid_sid_to_id_alloc_action(
+ struct db_context *db,
+ void *private_data)
+{
+ struct idmap_autorid_sid_to_id_alloc_ctx *ctx;
+
+ ctx = (struct idmap_autorid_sid_to_id_alloc_ctx *)private_data;
+
+ if (idmap_autorid_sid_is_special(ctx->map->sid)) {
+ struct dom_sid_buf buf;
+ NTSTATUS ret;
+
+ ret = idmap_autorid_sid_to_id_special(ctx->dom, ctx->map);
+ if (NT_STATUS_IS_OK(ret)) {
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(NT_STATUS_NONE_MAPPED, ret)) {
+ return ret;
+ }
+
+ DEBUG(10, ("Special sid %s not mapped. falling back to "
+ "regular allocation\n",
+ dom_sid_str_buf(ctx->map->sid, &buf)));
+ }
+
+ return idmap_tdb_common_new_mapping(ctx->dom, ctx->map);
+}
+
+/*
+ * map a SID to xid using the idmap_tdb like pool
+ */
+static NTSTATUS idmap_autorid_sid_to_id_alloc(
+ struct idmap_tdb_common_context *ctx,
+ struct idmap_domain *dom,
+ struct id_map *map)
+{
+ NTSTATUS ret;
+ struct idmap_autorid_sid_to_id_alloc_ctx alloc_ctx;
+ struct dom_sid_buf buf;
+
+ map->status = ID_UNKNOWN;
+
+ /* see if we already have a mapping */
+ ret = idmap_tdb_common_sid_to_unixid(dom, map);
+
+ if (NT_STATUS_IS_OK(ret)) {
+ map->status = ID_MAPPED;
+ return ret;
+ }
+
+ /* bad things happened */
+ if (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) {
+ DEBUG(1, ("Looking up SID->ID mapping for %s failed: %s\n",
+ dom_sid_str_buf(map->sid, &buf),
+ nt_errstr(ret)));
+ return ret;
+ }
+
+ if (dom->read_only) {
+ DEBUG(3, ("Not allocating new mapping for %s, because backend "
+ "is read-only\n",
+ dom_sid_str_buf(map->sid, &buf)));
+ map->status = ID_UNMAPPED;
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ DEBUG(10, ("Creating new mapping in pool for %s\n",
+ dom_sid_str_buf(map->sid, &buf)));
+
+ alloc_ctx.dom = dom;
+ alloc_ctx.map = map;
+
+ ret = dbwrap_trans_do(ctx->db, idmap_autorid_sid_to_id_alloc_action,
+ &alloc_ctx);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Failed to create a new mapping in alloc range: %s\n",
+ nt_errstr(ret)));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ map->status = ID_MAPPED;
+ return NT_STATUS_OK;
+}
+
+static bool idmap_autorid_domsid_is_for_alloc(struct dom_sid *sid)
+{
+ bool match;
+
+ match = sid_check_is_wellknown_domain(sid, NULL);
+ if (match) {
+ return true;
+ }
+
+ return false;
+}
+
+static NTSTATUS idmap_autorid_sid_to_id(struct idmap_tdb_common_context *common,
+ struct idmap_domain *dom,
+ struct id_map *map)
+{
+ struct autorid_global_config *global =
+ talloc_get_type_abort(common->private_data,
+ struct autorid_global_config);
+ struct autorid_range_config range;
+ uint32_t rid;
+ struct dom_sid domainsid;
+ struct dom_sid_buf buf;
+ NTSTATUS ret;
+
+ ZERO_STRUCT(range);
+ map->status = ID_UNKNOWN;
+
+ DEBUG(10, ("Trying to map %s\n", dom_sid_str_buf(map->sid, &buf)));
+
+ sid_copy(&domainsid, map->sid);
+ if (!sid_split_rid(&domainsid, &rid)) {
+ DEBUG(4, ("Could not determine domain SID from %s, "
+ "ignoring mapping request\n",
+ dom_sid_str_buf(map->sid, &buf)));
+ map->status = ID_UNMAPPED;
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ if (idmap_autorid_domsid_is_for_alloc(&domainsid)) {
+ DEBUG(10, ("SID %s is for ALLOC range.\n",
+ dom_sid_str_buf(map->sid, &buf)));
+
+ return idmap_autorid_sid_to_id_alloc(common, dom, map);
+ }
+
+ if (dom_sid_equal(&domainsid, &global_sid_Builtin) && ignore_builtin) {
+ DEBUG(10, ("Ignoring request for BUILTIN domain\n"));
+ map->status = ID_UNMAPPED;
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ sid_to_fstring(range.domsid, &domainsid);
+
+ range.domain_range_index = rid / (global->rangesize);
+
+ ret = idmap_autorid_getrange(autorid_db, range.domsid,
+ range.domain_range_index,
+ &range.rangenum, &range.low_id);
+ if (NT_STATUS_IS_OK(ret)) {
+ return idmap_autorid_sid_to_id_rid(
+ global->rangesize, range.low_id, map);
+ }
+
+ if (dom->read_only) {
+ DBG_DEBUG("read-only is enabled, did not allocate "
+ "new range for domain %s\n", range.domsid);
+ map->status = ID_UNMAPPED;
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ /*
+ * Check if we should allocate a domain range. We need to
+ * protect against unknown domains to not fill our ranges
+ * needlessly.
+ */
+
+ if (sid_check_is_builtin(&domainsid) ||
+ sid_check_is_our_sam(&domainsid)) {
+ goto allocate;
+ }
+
+ {
+ struct winbindd_domain *domain;
+
+ /*
+ * Deterministic check for domain members: We can be
+ * sure that the domain we are member of is worth to
+ * add a mapping for.
+ */
+
+ domain = find_our_domain();
+ if ((domain != NULL) &&
+ dom_sid_equal(&domain->sid, &domainsid)) {
+ goto allocate;
+ }
+ }
+
+ /*
+ * If we have already allocated range index 0, this domain is
+ * worth allocating for in higher ranges.
+ */
+ if (range.domain_range_index != 0) {
+ uint32_t zero_rangenum, zero_low_id;
+
+ ret = idmap_autorid_getrange(autorid_db, range.domsid, 0,
+ &zero_rangenum, &zero_low_id);
+ if (NT_STATUS_IS_OK(ret)) {
+ goto allocate;
+ }
+ }
+
+ /*
+ * If the caller already did a lookup sid and made sure the
+ * domain sid is valid, we can allocate a new range.
+ *
+ * Currently the winbindd parent already does a lookup sids
+ * first, but hopefully changes in future. If the
+ * caller knows the domain sid, ID_TYPE_BOTH should be
+ * passed instead of ID_TYPE_NOT_SPECIFIED.
+ */
+ if (map->xid.type != ID_TYPE_NOT_SPECIFIED) {
+ goto allocate;
+ }
+
+ /*
+ * Check of last resort: A domain is valid if a user from that
+ * domain has recently logged in. The samlogon_cache these
+ * days also stores the domain sid.
+ *
+ * We used to check the list of trusted domains we received
+ * from "our" dc, but this is not reliable enough.
+ */
+ if (netsamlogon_cache_have(&domainsid)) {
+ goto allocate;
+ }
+
+ /*
+ * Nobody knows this domain, so refuse to allocate a fresh
+ * range.
+ */
+
+ DBG_NOTICE("Allocating range for domain %s required type_hint\n", range.domsid);
+ map->status = ID_REQUIRE_TYPE;
+ return NT_STATUS_SOME_NOT_MAPPED;
+
+allocate:
+ ret = idmap_autorid_acquire_range(autorid_db, &range);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DBG_NOTICE("Could not determine range for domain: %s, "
+ "check previous messages for reason\n",
+ nt_errstr(ret));
+ return ret;
+ }
+
+ return idmap_autorid_sid_to_id_rid(global->rangesize, range.low_id,
+ map);
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_autorid_sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_tdb_common_context *commoncfg;
+ NTSTATUS ret;
+ size_t i;
+ size_t num_tomap = 0;
+ size_t num_mapped = 0;
+ size_t num_required = 0;
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ num_tomap++;
+ }
+
+ commoncfg =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ for (i = 0; ids[i]; i++) {
+ ret = idmap_autorid_sid_to_id(commoncfg, dom, ids[i]);
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_SOME_NOT_MAPPED) &&
+ ids[i]->status == ID_REQUIRE_TYPE)
+ {
+ num_required++;
+ continue;
+ }
+ if ((!NT_STATUS_IS_OK(ret)) &&
+ (!NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+ struct dom_sid_buf buf;
+ /* some fatal error occurred, log it */
+ DEBUG(3, ("Unexpected error resolving a SID (%s)\n",
+ dom_sid_str_buf(ids[i]->sid, &buf)));
+ return ret;
+ }
+
+ if (NT_STATUS_IS_OK(ret) && ids[i]->status == ID_MAPPED) {
+ num_mapped++;
+ }
+ }
+
+ if (num_tomap == num_mapped) {
+ return NT_STATUS_OK;
+ } else if (num_required > 0) {
+ return STATUS_SOME_UNMAPPED;
+ } else if (num_mapped == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return STATUS_SOME_UNMAPPED;
+}
+
+static NTSTATUS idmap_autorid_preallocate_wellknown(struct idmap_domain *dom)
+{
+ const char *groups[] = { "S-1-1-0", "S-1-2-0", "S-1-2-1",
+ "S-1-3-0", "S-1-3-1", "S-1-3-2", "S-1-3-3", "S-1-3-4",
+ "S-1-5-1", "S-1-5-2", "S-1-5-3", "S-1-5-4", "S-1-5-6",
+ "S-1-5-7", "S-1-5-8", "S-1-5-9", "S-1-5-10", "S-1-5-11",
+ "S-1-5-12", "S-1-5-13", "S-1-5-14", "S-1-5-15",
+ "S-1-5-17", "S-1-5-18", "S-1-5-19", "S-1-5-20"
+ };
+
+ struct id_map **maps;
+ int i, num;
+ NTSTATUS status;
+
+ if (dom->read_only) {
+ return NT_STATUS_OK;
+ }
+
+ num = ARRAY_SIZE(groups);
+
+ maps = talloc_array(talloc_tos(), struct id_map*, num+1);
+ if (!maps) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num; i++) {
+ maps[i] = talloc(maps, struct id_map);
+ if (maps[i] == NULL) {
+ talloc_free(maps);
+ return NT_STATUS_NO_MEMORY;
+ }
+ maps[i]->xid.type = ID_TYPE_GID;
+ maps[i]->sid = dom_sid_parse_talloc(maps, groups[i]);
+ }
+
+ maps[num] = NULL;
+
+ status = idmap_autorid_sids_to_unixids(dom, maps);
+
+ DEBUG(10,("Preallocation run finished with status %s\n",
+ nt_errstr(status)));
+
+ talloc_free(maps);
+
+ return NT_STATUS_IS_OK(status)?NT_STATUS_OK:NT_STATUS_UNSUCCESSFUL;
+}
+
+static NTSTATUS idmap_autorid_initialize_action(struct db_context *db,
+ void *private_data)
+{
+ struct idmap_domain *dom;
+ struct idmap_tdb_common_context *common;
+ struct autorid_global_config *config;
+ NTSTATUS status;
+
+ dom = (struct idmap_domain *)private_data;
+ common = (struct idmap_tdb_common_context *)dom->private_data;
+ config = (struct autorid_global_config *)common->private_data;
+
+ status = idmap_autorid_init_hwms(db);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = idmap_autorid_saveconfig(db, config);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to store configuration data!\n"));
+ return status;
+ }
+
+ status = idmap_autorid_preallocate_wellknown(dom);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to preallocate wellknown sids: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_autorid_initialize(struct idmap_domain *dom)
+{
+ struct idmap_tdb_common_context *commonconfig;
+ struct autorid_global_config *config;
+ NTSTATUS status;
+ char *db_path;
+
+ if (!strequal(dom->name, "*")) {
+ DEBUG(0, ("idmap_autorid_initialize: Error: autorid configured "
+ "for domain '%s'. But autorid can only be used for "
+ "the default idmap configuration.\n", dom->name));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ commonconfig = talloc_zero(dom, struct idmap_tdb_common_context);
+ if (!commonconfig) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ dom->private_data = commonconfig;
+
+ commonconfig->rw_ops = talloc_zero(commonconfig, struct idmap_rw_ops);
+ if (commonconfig->rw_ops == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ config = talloc_zero(commonconfig, struct autorid_global_config);
+ if (!config) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ commonconfig->private_data = config;
+
+ config->minvalue = dom->low_id;
+ config->rangesize = idmap_config_int("*", "rangesize", 100000);
+
+ config->maxranges = (dom->high_id - dom->low_id + 1) /
+ config->rangesize;
+
+ if (config->maxranges < 2) {
+ DBG_WARNING("Allowed idmap range is not a least double the "
+ "size of the rangesize. Please increase idmap "
+ "range.\n");
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto error;
+ }
+
+ /* check if the high-low limit is a multiple of the rangesize */
+ if ((dom->high_id - dom->low_id + 1) % config->rangesize != 0) {
+ DEBUG(5, ("High uid-low uid difference of %d "
+ "is not a multiple of the rangesize %d, "
+ "limiting ranges to lower boundary number of %d\n",
+ (dom->high_id - dom->low_id + 1), config->rangesize,
+ config->maxranges));
+ }
+
+ DEBUG(5, ("%d domain ranges with a size of %d are available\n",
+ config->maxranges, config->rangesize));
+
+ ignore_builtin = idmap_config_bool("*", "ignore builtin", false);
+
+ /* fill the TDB common configuration */
+
+ commonconfig->max_id = config->rangesize - 1
+ - IDMAP_AUTORID_ALLOC_RESERVED;
+ commonconfig->hwmkey_uid = ALLOC_HWM_UID;
+ commonconfig->hwmkey_gid = ALLOC_HWM_GID;
+ commonconfig->rw_ops->get_new_id = idmap_autorid_allocate_id;
+ commonconfig->rw_ops->set_mapping = idmap_tdb_common_set_mapping;
+
+ db_path = state_path(talloc_tos(), "autorid.tdb");
+ if (db_path == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ status = idmap_autorid_db_open(db_path,
+ NULL, /* TALLOC_CTX */
+ &autorid_db);
+ TALLOC_FREE(db_path);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ commonconfig->db = autorid_db;
+
+ status = dbwrap_trans_do(autorid_db,
+ idmap_autorid_initialize_action,
+ dom);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to init the idmap database: %s\n",
+ nt_errstr(status)));
+ goto error;
+ }
+
+ goto done;
+
+error:
+ talloc_free(config);
+
+done:
+ return status;
+}
+
+static const struct idmap_methods autorid_methods = {
+ .init = idmap_autorid_initialize,
+ .unixids_to_sids = idmap_autorid_unixids_to_sids,
+ .sids_to_unixids = idmap_autorid_sids_to_unixids,
+ .allocate_id = idmap_autorid_allocate_id
+};
+
+static_decl_idmap;
+NTSTATUS idmap_autorid_init(TALLOC_CTX *ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION,
+ "autorid", &autorid_methods);
+}
diff --git a/source3/winbindd/idmap_autorid_tdb.c b/source3/winbindd/idmap_autorid_tdb.c
new file mode 100644
index 0000000..6c76764
--- /dev/null
+++ b/source3/winbindd/idmap_autorid_tdb.c
@@ -0,0 +1,1269 @@
+/*
+ * idmap_autorid_tdb: This file contains common code used by
+ * idmap_autorid and net idmap autorid utilities. The common
+ * code provides functions for performing various operations
+ * on autorid.tdb
+ *
+ * Copyright (C) Christian Ambach, 2010-2012
+ * Copyright (C) Atul Kulkarni, 2013
+ * Copyright (C) Michael Adam, 2012-2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "idmap_autorid_tdb.h"
+#include "../libcli/security/dom_sid.h"
+#include "lib/util/string_wrappers.h"
+
+/**
+ * Build the database keystring for getting a range
+ * belonging to a domain sid and a range index.
+ */
+static void idmap_autorid_build_keystr(const char *domsid,
+ uint32_t domain_range_index,
+ fstring keystr)
+{
+ if (domain_range_index > 0) {
+ fstr_sprintf(keystr, "%s#%"PRIu32,
+ domsid, domain_range_index);
+ } else {
+ fstrcpy(keystr, domsid);
+ }
+}
+
+static char *idmap_autorid_build_keystr_talloc(TALLOC_CTX *mem_ctx,
+ const char *domsid,
+ uint32_t domain_range_index)
+{
+ char *keystr;
+
+ if (domain_range_index > 0) {
+ keystr = talloc_asprintf(mem_ctx, "%s#%"PRIu32, domsid,
+ domain_range_index);
+ } else {
+ keystr = talloc_strdup(mem_ctx, domsid);
+ }
+
+ return keystr;
+}
+
+
+static bool idmap_autorid_validate_sid(const char *sid)
+{
+ struct dom_sid ignore;
+ if (sid == NULL) {
+ return false;
+ }
+
+ if (strcmp(sid, ALLOC_RANGE) == 0) {
+ return true;
+ }
+
+ return dom_sid_parse(sid, &ignore);
+}
+
+struct idmap_autorid_addrange_ctx {
+ struct autorid_range_config *range;
+ bool acquire;
+};
+
+static NTSTATUS idmap_autorid_addrange_action(struct db_context *db,
+ void *private_data)
+{
+ struct idmap_autorid_addrange_ctx *ctx;
+ uint32_t requested_rangenum, stored_rangenum;
+ struct autorid_range_config *range;
+ bool acquire;
+ NTSTATUS ret;
+ uint32_t hwm;
+ char *numstr;
+ struct autorid_global_config globalcfg = {0};
+ fstring keystr;
+ uint32_t increment;
+ TALLOC_CTX *mem_ctx = NULL;
+
+ ctx = (struct idmap_autorid_addrange_ctx *)private_data;
+ range = ctx->range;
+ acquire = ctx->acquire;
+ requested_rangenum = range->rangenum;
+
+ if (db == NULL) {
+ DEBUG(3, ("Invalid database argument: NULL\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (range == NULL) {
+ DEBUG(3, ("Invalid range argument: NULL\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10, ("Adding new range for domain %s "
+ "(domain_range_index=%"PRIu32")\n",
+ range->domsid, range->domain_range_index));
+
+ if (!idmap_autorid_validate_sid(range->domsid)) {
+ DEBUG(3, ("Invalid SID: %s\n", range->domsid));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ idmap_autorid_build_keystr(range->domsid, range->domain_range_index,
+ keystr);
+
+ ret = dbwrap_fetch_uint32_bystring(db, keystr, &stored_rangenum);
+
+ if (NT_STATUS_IS_OK(ret)) {
+ /* entry is already present*/
+ if (acquire) {
+ DEBUG(10, ("domain range already allocated - "
+ "Not adding!\n"));
+
+ ret = idmap_autorid_loadconfig(db, &globalcfg);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while fetching "
+ "configuration: %s\n",
+ nt_errstr(ret)));
+ goto error;
+ }
+
+ range->rangenum = stored_rangenum;
+ range->low_id = globalcfg.minvalue
+ + range->rangenum * globalcfg.rangesize;
+ range->high_id =
+ range->low_id + globalcfg.rangesize - 1;
+
+ return NT_STATUS_OK;
+ }
+
+ if (stored_rangenum != requested_rangenum) {
+ DEBUG(1, ("Error: requested rangenumber (%u) differs "
+ "from stored one (%u).\n",
+ requested_rangenum, stored_rangenum));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DEBUG(10, ("Note: stored range agrees with requested "
+ "one - ok\n"));
+ return NT_STATUS_OK;
+ }
+
+ /* fetch the current HWM */
+ ret = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while fetching current "
+ "HWM value: %s\n", nt_errstr(ret)));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ mem_ctx = talloc_stackframe();
+
+ ret = idmap_autorid_loadconfig(db, &globalcfg);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while fetching configuration: %s\n",
+ nt_errstr(ret)));
+ goto error;
+ }
+
+ if (acquire) {
+ /*
+ * automatically acquire the next range
+ */
+ requested_rangenum = hwm;
+ }
+
+ if (requested_rangenum >= globalcfg.maxranges) {
+ DBG_WARNING("Not enough ranges available: New range %u can't "
+ "be allocated. Consider increasing the range "
+ "[%u-%u] by %u.\n",
+ requested_rangenum,
+ globalcfg.minvalue,
+ globalcfg.minvalue +
+ (globalcfg.maxranges * globalcfg.rangesize),
+ globalcfg.rangesize);
+ ret = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ /*
+ * Check that it is not yet taken.
+ * If the range is requested and < HWM, we need
+ * to check anyways, and otherwise, we also better
+ * check in order to prevent further corruption
+ * in case the db has been externally modified.
+ */
+
+ numstr = talloc_asprintf(mem_ctx, "%u", requested_rangenum);
+ if (!numstr) {
+ DEBUG(1, ("Talloc failed!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ if (dbwrap_exists(db, string_term_tdb_data(numstr))) {
+ DEBUG(1, ("Requested range '%s' is already in use.\n", numstr));
+
+ if (requested_rangenum < hwm) {
+ ret = NT_STATUS_INVALID_PARAMETER;
+ } else {
+ ret = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ goto error;
+ }
+
+ if (requested_rangenum >= hwm) {
+ /*
+ * requested or automatic range >= HWM:
+ * increment the HWM.
+ */
+
+ /* HWM always contains current max range + 1 */
+ increment = requested_rangenum + 1 - hwm;
+
+ /* increase the HWM */
+ ret = dbwrap_change_uint32_atomic_bystring(db, HWM, &hwm,
+ increment);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while incrementing the HWM "
+ "value in the database: %s\n",
+ nt_errstr(ret)));
+ goto error;
+ }
+ }
+
+ /*
+ * store away the new mapping in both directions
+ */
+
+ ret = dbwrap_store_uint32_bystring(db, keystr, requested_rangenum);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while storing new "
+ "domain->range assignment: %s\n", nt_errstr(ret)));
+ goto error;
+ }
+
+ numstr = talloc_asprintf(mem_ctx, "%u", requested_rangenum);
+ if (!numstr) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ ret = dbwrap_store_bystring(db, numstr,
+ string_term_tdb_data(keystr), TDB_INSERT);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while storing new "
+ "domain->range assignment: %s\n", nt_errstr(ret)));
+ goto error;
+ }
+
+ DEBUG(5, ("%s new range #%d for domain %s "
+ "(domain_range_index=%"PRIu32")\n",
+ (acquire?"Acquired":"Stored"),
+ requested_rangenum, keystr,
+ range->domain_range_index));
+
+ range->rangenum = requested_rangenum;
+
+ range->low_id = globalcfg.minvalue
+ + range->rangenum * globalcfg.rangesize;
+ range->high_id = range->low_id + globalcfg.rangesize - 1;
+
+ ret = NT_STATUS_OK;
+
+error:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static NTSTATUS idmap_autorid_addrange(struct db_context *db,
+ struct autorid_range_config *range,
+ bool acquire)
+{
+ NTSTATUS status;
+ struct idmap_autorid_addrange_ctx ctx;
+
+ ctx.acquire = acquire;
+ ctx.range = range;
+
+ status = dbwrap_trans_do(db, idmap_autorid_addrange_action, &ctx);
+ return status;
+}
+
+NTSTATUS idmap_autorid_setrange(struct db_context *db,
+ const char *domsid,
+ uint32_t domain_range_index,
+ uint32_t rangenum)
+{
+ NTSTATUS status;
+ struct autorid_range_config range;
+
+ ZERO_STRUCT(range);
+ fstrcpy(range.domsid, domsid);
+ range.domain_range_index = domain_range_index;
+ range.rangenum = rangenum;
+
+ status = idmap_autorid_addrange(db, &range, false);
+ return status;
+}
+
+NTSTATUS idmap_autorid_acquire_range(struct db_context *db,
+ struct autorid_range_config *range)
+{
+ return idmap_autorid_addrange(db, range, true);
+}
+
+static NTSTATUS idmap_autorid_getrange_int(struct db_context *db,
+ struct autorid_range_config *range)
+{
+ NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
+ struct autorid_global_config globalcfg = {0};
+ fstring keystr;
+
+ if (db == NULL || range == NULL) {
+ DEBUG(3, ("Invalid arguments received\n"));
+ goto done;
+ }
+
+ if (!idmap_autorid_validate_sid(range->domsid)) {
+ DEBUG(3, ("Invalid SID: '%s'\n", range->domsid));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ idmap_autorid_build_keystr(range->domsid, range->domain_range_index,
+ keystr);
+
+ DEBUG(10, ("reading domain range for key %s\n", keystr));
+ status = dbwrap_fetch_uint32_bystring(db, keystr, &(range->rangenum));
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to read database record for key '%s': %s\n",
+ keystr, nt_errstr(status)));
+ goto done;
+ }
+
+ status = idmap_autorid_loadconfig(db, &globalcfg);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Failed to read global configuration\n"));
+ goto done;
+ }
+ range->low_id = globalcfg.minvalue
+ + range->rangenum * globalcfg.rangesize;
+ range->high_id = range->low_id + globalcfg.rangesize - 1;
+done:
+ return status;
+}
+
+NTSTATUS idmap_autorid_getrange(struct db_context *db,
+ const char *domsid,
+ uint32_t domain_range_index,
+ uint32_t *rangenum,
+ uint32_t *low_id)
+{
+ NTSTATUS status;
+ struct autorid_range_config range;
+
+ if (rangenum == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ZERO_STRUCT(range);
+ fstrcpy(range.domsid, domsid);
+ range.domain_range_index = domain_range_index;
+
+ status = idmap_autorid_getrange_int(db, &range);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *rangenum = range.rangenum;
+
+ if (low_id != NULL) {
+ *low_id = range.low_id;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS idmap_autorid_get_domainrange(struct db_context *db,
+ struct autorid_range_config *range,
+ bool read_only)
+{
+ NTSTATUS ret;
+
+ ret = idmap_autorid_getrange_int(db, range);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(10, ("Failed to read range config for '%s': %s\n",
+ range->domsid, nt_errstr(ret)));
+ if (read_only) {
+ DEBUG(10, ("Not allocating new range for '%s' because "
+ "read-only is enabled.\n", range->domsid));
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ ret = idmap_autorid_acquire_range(db, range);
+ }
+
+ DEBUG(10, ("Using range #%d for domain %s "
+ "(domain_range_index=%"PRIu32", low_id=%"PRIu32")\n",
+ range->rangenum, range->domsid, range->domain_range_index,
+ range->low_id));
+
+ return ret;
+}
+
+/* initialize the given HWM to 0 if it does not exist yet */
+static NTSTATUS idmap_autorid_init_hwm_action(struct db_context *db,
+ void *private_data)
+{
+ NTSTATUS status;
+ uint32_t hwmval;
+ const char *hwm;
+
+ hwm = (char *)private_data;
+
+ status = dbwrap_fetch_uint32_bystring(db, hwm, &hwmval);
+ if (NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("HWM (%s) already initialized in autorid database "
+ "(value %"PRIu32").\n", hwm, hwmval));
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(0, ("Error fetching HWM (%s) from autorid "
+ "database: %s\n", hwm, nt_errstr(status)));
+ return status;
+ }
+
+ status = dbwrap_trans_store_uint32_bystring(db, hwm, 0);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Error storing HWM (%s) in autorid database: %s\n",
+ hwm, nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS idmap_autorid_init_hwm(struct db_context *db, const char *hwm)
+{
+ NTSTATUS status;
+ uint32_t hwmval;
+
+ status = dbwrap_fetch_uint32_bystring(db, hwm, &hwmval);
+ if (NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("HWM (%s) already initialized in autorid database "
+ "(value %"PRIu32").\n", hwm, hwmval));
+ return NT_STATUS_OK;
+ }
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(0, ("unable to fetch HWM (%s) from autorid "
+ "database: %s\n", hwm, nt_errstr(status)));
+ return status;
+ }
+
+ status = dbwrap_trans_do(db, idmap_autorid_init_hwm_action,
+ discard_const(hwm));
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Error initializing HWM (%s) in autorid database: "
+ "%s\n", hwm, nt_errstr(status)));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ DEBUG(1, ("Initialized HWM (%s) in autorid database.\n", hwm));
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Delete a domain#index <-> range mapping from the database.
+ * The mapping is specified by the sid and index.
+ * If force == true, invalid mapping records are deleted as far
+ * as possible, otherwise they are left untouched.
+ */
+
+struct idmap_autorid_delete_range_by_sid_ctx {
+ const char *domsid;
+ uint32_t domain_range_index;
+ bool force;
+};
+
+static NTSTATUS idmap_autorid_delete_range_by_sid_action(struct db_context *db,
+ void *private_data)
+{
+ struct idmap_autorid_delete_range_by_sid_ctx *ctx =
+ (struct idmap_autorid_delete_range_by_sid_ctx *)private_data;
+ const char *domsid;
+ uint32_t domain_range_index;
+ uint32_t rangenum;
+ char *keystr;
+ char *range_keystr;
+ TDB_DATA data;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool is_valid_range_mapping = true;
+ bool force;
+
+ domsid = ctx->domsid;
+ domain_range_index = ctx->domain_range_index;
+ force = ctx->force;
+
+ keystr = idmap_autorid_build_keystr_talloc(frame, domsid,
+ domain_range_index);
+ if (keystr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = dbwrap_fetch_uint32_bystring(db, keystr, &rangenum);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ range_keystr = talloc_asprintf(frame, "%"PRIu32, rangenum);
+ if (range_keystr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ status = dbwrap_fetch_bystring(db, frame, range_keystr, &data);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(1, ("Incomplete mapping %s -> %s: no backward mapping\n",
+ keystr, range_keystr));
+ is_valid_range_mapping = false;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Error fetching reverse mapping for %s -> %s: %s\n",
+ keystr, range_keystr, nt_errstr(status)));
+ goto done;
+ } else if (strncmp((const char *)data.dptr, keystr, strlen(keystr))
+ != 0)
+ {
+ DEBUG(1, ("Invalid mapping: %s -> %s -> %s\n",
+ keystr, range_keystr, (const char *)data.dptr));
+ is_valid_range_mapping = false;
+ }
+
+ if (!is_valid_range_mapping && !force) {
+ DEBUG(10, ("Not deleting invalid mapping, since not in force "
+ "mode.\n"));
+ status = NT_STATUS_FILE_INVALID;
+ goto done;
+ }
+
+ status = dbwrap_delete_bystring(db, keystr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Deletion of '%s' failed: %s\n",
+ keystr, nt_errstr(status)));
+ goto done;
+ }
+
+ if (!is_valid_range_mapping) {
+ goto done;
+ }
+
+ status = dbwrap_delete_bystring(db, range_keystr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Deletion of '%s' failed: %s\n",
+ range_keystr, nt_errstr(status)));
+ goto done;
+ }
+
+ DEBUG(10, ("Deleted range mapping %s <--> %s\n", keystr,
+ range_keystr));
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS idmap_autorid_delete_range_by_sid(struct db_context *db,
+ const char *domsid,
+ uint32_t domain_range_index,
+ bool force)
+{
+ NTSTATUS status;
+ struct idmap_autorid_delete_range_by_sid_ctx ctx;
+
+ ctx.domain_range_index = domain_range_index;
+ ctx.domsid = domsid;
+ ctx.force = force;
+
+ status = dbwrap_trans_do(db, idmap_autorid_delete_range_by_sid_action,
+ &ctx);
+ return status;
+}
+
+/*
+ * Delete a domain#index <-> range mapping from the database.
+ * The mapping is specified by the range number.
+ * If force == true, invalid mapping records are deleted as far
+ * as possible, otherwise they are left untouched.
+ */
+struct idmap_autorid_delete_range_by_num_ctx {
+ uint32_t rangenum;
+ bool force;
+};
+
+static NTSTATUS idmap_autorid_delete_range_by_num_action(struct db_context *db,
+ void *private_data)
+{
+ struct idmap_autorid_delete_range_by_num_ctx *ctx =
+ (struct idmap_autorid_delete_range_by_num_ctx *)private_data;
+ uint32_t rangenum;
+ char *keystr = NULL;
+ char *range_keystr;
+ TDB_DATA val;
+ NTSTATUS status;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool is_valid_range_mapping = true;
+ bool force;
+
+ rangenum = ctx->rangenum;
+ force = ctx->force;
+
+ range_keystr = talloc_asprintf(frame, "%"PRIu32, rangenum);
+ if (range_keystr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ZERO_STRUCT(val);
+
+ status = dbwrap_fetch_bystring(db, frame, range_keystr, &val);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(10, ("Did not find range '%s' in database.\n",
+ range_keystr));
+ goto done;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("Error fetching rang key: %s\n", nt_errstr(status)));
+ goto done;
+ }
+
+ if (val.dptr == NULL) {
+ DEBUG(1, ("Invalid mapping: %s -> empty value\n",
+ range_keystr));
+ is_valid_range_mapping = false;
+ } else {
+ uint32_t reverse_rangenum = 0;
+
+ keystr = (char *)val.dptr;
+
+ status = dbwrap_fetch_uint32_bystring(db, keystr,
+ &reverse_rangenum);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(1, ("Incomplete mapping %s -> %s: "
+ "no backward mapping\n",
+ range_keystr, keystr));
+ is_valid_range_mapping = false;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Error fetching reverse mapping for "
+ "%s -> %s: %s\n",
+ range_keystr, keystr, nt_errstr(status)));
+ goto done;
+ } else if (rangenum != reverse_rangenum) {
+ is_valid_range_mapping = false;
+ }
+ }
+
+ if (!is_valid_range_mapping && !force) {
+ DEBUG(10, ("Not deleting invalid mapping, since not in force "
+ "mode.\n"));
+ status = NT_STATUS_FILE_INVALID;
+ goto done;
+ }
+
+ status = dbwrap_delete_bystring(db, range_keystr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Deletion of '%s' failed: %s\n",
+ range_keystr, nt_errstr(status)));
+ goto done;
+ }
+
+ if (!is_valid_range_mapping) {
+ goto done;
+ }
+
+ status = dbwrap_delete_bystring(db, keystr);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Deletion of '%s' failed: %s\n",
+ keystr, nt_errstr(status)));
+ goto done;
+ }
+
+ DEBUG(10, ("Deleted range mapping %s <--> %s\n", range_keystr,
+ keystr));
+
+done:
+ talloc_free(frame);
+ return status;
+}
+
+NTSTATUS idmap_autorid_delete_range_by_num(struct db_context *db,
+ uint32_t rangenum,
+ bool force)
+{
+ NTSTATUS status;
+ struct idmap_autorid_delete_range_by_num_ctx ctx;
+
+ ctx.rangenum = rangenum;
+ ctx.force = force;
+
+ status = dbwrap_trans_do(db, idmap_autorid_delete_range_by_num_action,
+ &ctx);
+ return status;
+}
+
+/**
+ * Open and possibly create the database.
+ */
+NTSTATUS idmap_autorid_db_open(const char *path,
+ TALLOC_CTX *mem_ctx,
+ struct db_context **db)
+{
+ if (*db != NULL) {
+ /* its already open */
+ return NT_STATUS_OK;
+ }
+
+ /* Open idmap repository */
+ *db = db_open(mem_ctx, path, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+
+ if (*db == NULL) {
+ DEBUG(0, ("Unable to open idmap_autorid database '%s'\n", path));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**
+ * Initialize the high watermark records in the database.
+ */
+NTSTATUS idmap_autorid_init_hwms(struct db_context *db)
+{
+ NTSTATUS status;
+
+ status = idmap_autorid_init_hwm(db, HWM);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = idmap_autorid_init_hwm(db, ALLOC_HWM_UID);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = idmap_autorid_init_hwm(db, ALLOC_HWM_GID);
+
+ return status;
+}
+
+NTSTATUS idmap_autorid_db_init(const char *path,
+ TALLOC_CTX *mem_ctx,
+ struct db_context **db)
+{
+ NTSTATUS status;
+
+ status = idmap_autorid_db_open(path, mem_ctx, db);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = idmap_autorid_init_hwms(*db);
+ return status;
+}
+
+
+
+struct idmap_autorid_fetch_config_state {
+ TALLOC_CTX *mem_ctx;
+ char *configstr;
+};
+
+static void idmap_autorid_config_parser(TDB_DATA key, TDB_DATA value,
+ void *private_data)
+{
+ struct idmap_autorid_fetch_config_state *state;
+
+ state = (struct idmap_autorid_fetch_config_state *)private_data;
+
+ /*
+ * strndup because we have non-nullterminated strings in the db
+ */
+ state->configstr = talloc_strndup(
+ state->mem_ctx, (const char *)value.dptr, value.dsize);
+}
+
+NTSTATUS idmap_autorid_getconfigstr(struct db_context *db, TALLOC_CTX *mem_ctx,
+ char **result)
+{
+ TDB_DATA key;
+ NTSTATUS status;
+ struct idmap_autorid_fetch_config_state state;
+
+ if (result == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ key = string_term_tdb_data(CONFIGKEY);
+
+ state.mem_ctx = mem_ctx;
+ state.configstr = NULL;
+
+ status = dbwrap_parse_record(db, key, idmap_autorid_config_parser,
+ &state);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Error while retrieving config: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (state.configstr == NULL) {
+ DEBUG(1, ("Error while retrieving config\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(5, ("found CONFIG: %s\n", state.configstr));
+
+ *result = state.configstr;
+ return NT_STATUS_OK;
+}
+
+bool idmap_autorid_parse_configstr(const char *configstr,
+ struct autorid_global_config *cfg)
+{
+ unsigned long minvalue, rangesize, maxranges;
+
+ if (sscanf(configstr,
+ "minvalue:%lu rangesize:%lu maxranges:%lu",
+ &minvalue, &rangesize, &maxranges) != 3) {
+ DEBUG(1,
+ ("Found invalid configuration data. "
+ "Creating new config\n"));
+ return false;
+ }
+
+ cfg->minvalue = minvalue;
+ cfg->rangesize = rangesize;
+ cfg->maxranges = maxranges;
+
+ return true;
+}
+
+NTSTATUS idmap_autorid_loadconfig(struct db_context *db,
+ struct autorid_global_config *result)
+{
+ struct autorid_global_config cfg = {0};
+ NTSTATUS status;
+ bool ok;
+ char *configstr = NULL;
+
+ if (result == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = idmap_autorid_getconfigstr(db, db, &configstr);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ok = idmap_autorid_parse_configstr(configstr, &cfg);
+ TALLOC_FREE(configstr);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10, ("Loaded previously stored configuration "
+ "minvalue:%d rangesize:%d\n",
+ cfg.minvalue, cfg.rangesize));
+
+ *result = cfg;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS idmap_autorid_saveconfig(struct db_context *db,
+ struct autorid_global_config *cfg)
+{
+
+ struct autorid_global_config storedconfig = {0};
+ NTSTATUS status = NT_STATUS_INVALID_PARAMETER;
+ TDB_DATA data;
+ char *cfgstr;
+ uint32_t hwm;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ DEBUG(10, ("New configuration provided for storing is "
+ "minvalue:%d rangesize:%d maxranges:%d\n",
+ cfg->minvalue, cfg->rangesize, cfg->maxranges));
+
+ if (cfg->rangesize < 2000) {
+ DEBUG(1, ("autorid rangesize must be at least 2000\n"));
+ goto done;
+ }
+
+ if (cfg->maxranges == 0) {
+ DEBUG(1, ("An autorid maxranges value of 0 is invalid. "
+ "Must have at least one range available.\n"));
+ goto done;
+ }
+
+ status = idmap_autorid_loadconfig(db, &storedconfig);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ DEBUG(5, ("No configuration found. Storing initial "
+ "configuration.\n"));
+ storedconfig = *cfg;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Error loading configuration: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /* did the minimum value or rangesize change? */
+ if ((storedconfig.minvalue != cfg->minvalue) ||
+ (storedconfig.rangesize != cfg->rangesize))
+ {
+ DEBUG(1, ("New configuration values for rangesize or "
+ "minimum uid value conflict with previously "
+ "used values! Not storing new config.\n"));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ status = dbwrap_fetch_uint32_bystring(db, HWM, &hwm);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Fatal error while fetching current "
+ "HWM value: %s\n", nt_errstr(status)));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /*
+ * has the highest uid value been reduced to setting that is not
+ * sufficient any more for already existing ranges?
+ */
+ if (hwm > cfg->maxranges) {
+ DEBUG(1, ("New upper uid limit is too low to cover "
+ "existing mappings! Not storing new config.\n"));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ cfgstr =
+ talloc_asprintf(frame,
+ "minvalue:%u rangesize:%u maxranges:%u",
+ cfg->minvalue, cfg->rangesize, cfg->maxranges);
+
+ if (cfgstr == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ data = string_tdb_data(cfgstr);
+
+ status = dbwrap_trans_store_bystring(db, CONFIGKEY, data, TDB_REPLACE);
+
+done:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS idmap_autorid_saveconfigstr(struct db_context *db,
+ const char *configstr)
+{
+ bool ok;
+ NTSTATUS status;
+ struct autorid_global_config cfg;
+
+ ok = idmap_autorid_parse_configstr(configstr, &cfg);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = idmap_autorid_saveconfig(db, &cfg);
+ return status;
+}
+
+
+/*
+ * iteration: Work on all range mappings for a given domain
+ */
+
+struct domain_range_visitor_ctx {
+ const char *domsid;
+ NTSTATUS (*fn)(struct db_context *db,
+ const char *domsid,
+ uint32_t index,
+ uint32_t rangenum,
+ void *private_data);
+ void *private_data;
+ int count; /* number of records worked on */
+};
+
+static int idmap_autorid_visit_domain_range(struct db_record *rec,
+ void *private_data)
+{
+ struct domain_range_visitor_ctx *vi;
+ char *domsid;
+ char *sep;
+ uint32_t range_index = 0;
+ uint32_t rangenum = 0;
+ TDB_DATA key, value;
+ NTSTATUS status;
+ int ret = 0;
+ struct db_context *db;
+
+ vi = talloc_get_type_abort(private_data,
+ struct domain_range_visitor_ctx);
+
+ key = dbwrap_record_get_key(rec);
+
+ /*
+ * split string "<sid>[#<index>]" into sid string and index number
+ */
+
+ domsid = (char *)key.dptr;
+
+ DEBUG(10, ("idmap_autorid_visit_domain_range: visiting key '%s'\n",
+ domsid));
+
+ sep = strrchr(domsid, '#');
+ if (sep != NULL) {
+ char *index_str;
+ *sep = '\0';
+ index_str = sep+1;
+ if (sscanf(index_str, "%"SCNu32, &range_index) != 1) {
+ DEBUG(10, ("Found separator '#' but '%s' is not a "
+ "valid range index. Skipping record\n",
+ index_str));
+ goto done;
+ }
+ }
+
+ if (!idmap_autorid_validate_sid(domsid)) {
+ DEBUG(10, ("String '%s' is not a valid sid. "
+ "Skipping record.\n", domsid));
+ goto done;
+ }
+
+ if ((vi->domsid != NULL) && (strcmp(domsid, vi->domsid) != 0)) {
+ DEBUG(10, ("key sid '%s' does not match requested sid '%s'.\n",
+ domsid, vi->domsid));
+ goto done;
+ }
+
+ value = dbwrap_record_get_value(rec);
+
+ if (value.dsize != sizeof(uint32_t)) {
+ /* it might be a mapping of a well known sid */
+ DEBUG(10, ("value size %u != sizeof(uint32_t) for sid '%s', "
+ "skipping.\n", (unsigned)value.dsize, vi->domsid));
+ goto done;
+ }
+
+ rangenum = IVAL(value.dptr, 0);
+
+ db = dbwrap_record_get_db(rec);
+
+ status = vi->fn(db, domsid, range_index, rangenum, vi->private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ ret = -1;
+ goto done;
+ }
+
+ vi->count++;
+ ret = 0;
+
+done:
+ return ret;
+}
+
+static NTSTATUS idmap_autorid_iterate_domain_ranges_int(struct db_context *db,
+ const char *domsid,
+ NTSTATUS (*fn)(struct db_context *db,
+ const char *domsid,
+ uint32_t index,
+ uint32_t rangnum,
+ void *private_data),
+ void *private_data,
+ int *count,
+ NTSTATUS (*traverse)(struct db_context *db,
+ int (*f)(struct db_record *, void *),
+ void *private_data,
+ int *count))
+{
+ NTSTATUS status;
+ struct domain_range_visitor_ctx *vi;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ if (domsid == NULL) {
+ DEBUG(10, ("No sid provided, operating on all ranges\n"));
+ }
+
+ if (fn == NULL) {
+ DEBUG(1, ("Error: missing visitor callback\n"));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ vi = talloc_zero(frame, struct domain_range_visitor_ctx);
+ if (vi == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ vi->domsid = domsid;
+ vi->fn = fn;
+ vi->private_data = private_data;
+
+ status = traverse(db, idmap_autorid_visit_domain_range, vi, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (count != NULL) {
+ *count = vi->count;
+ }
+
+done:
+ talloc_free(frame);
+ return status;
+}
+
+NTSTATUS idmap_autorid_iterate_domain_ranges(struct db_context *db,
+ const char *domsid,
+ NTSTATUS (*fn)(struct db_context *db,
+ const char *domsid,
+ uint32_t index,
+ uint32_t rangenum,
+ void *private_data),
+ void *private_data,
+ int *count)
+{
+ NTSTATUS status;
+
+ status = idmap_autorid_iterate_domain_ranges_int(db,
+ domsid,
+ fn,
+ private_data,
+ count,
+ dbwrap_traverse);
+
+ return status;
+}
+
+
+NTSTATUS idmap_autorid_iterate_domain_ranges_read(struct db_context *db,
+ const char *domsid,
+ NTSTATUS (*fn)(struct db_context *db,
+ const char *domsid,
+ uint32_t index,
+ uint32_t rangenum,
+ void *count),
+ void *private_data,
+ int *count)
+{
+ NTSTATUS status;
+
+ status = idmap_autorid_iterate_domain_ranges_int(db,
+ domsid,
+ fn,
+ private_data,
+ count,
+ dbwrap_traverse_read);
+
+ return status;
+}
+
+
+/*
+ * Delete all ranges configured for a given domain
+ */
+
+struct delete_domain_ranges_visitor_ctx {
+ bool force;
+};
+
+static NTSTATUS idmap_autorid_delete_domain_ranges_visitor(
+ struct db_context *db,
+ const char *domsid,
+ uint32_t domain_range_index,
+ uint32_t rangenum,
+ void *private_data)
+{
+ struct delete_domain_ranges_visitor_ctx *ctx;
+ NTSTATUS status;
+
+ ctx = (struct delete_domain_ranges_visitor_ctx *)private_data;
+
+ status = idmap_autorid_delete_range_by_sid(
+ db, domsid, domain_range_index, ctx->force);
+ return status;
+}
+
+struct idmap_autorid_delete_domain_ranges_ctx {
+ const char *domsid;
+ bool force;
+ int count; /* output: count records operated on */
+};
+
+static NTSTATUS idmap_autorid_delete_domain_ranges_action(struct db_context *db,
+ void *private_data)
+{
+ struct idmap_autorid_delete_domain_ranges_ctx *ctx;
+ struct delete_domain_ranges_visitor_ctx visitor_ctx;
+ int count;
+ NTSTATUS status;
+
+ ctx = (struct idmap_autorid_delete_domain_ranges_ctx *)private_data;
+
+ ZERO_STRUCT(visitor_ctx);
+ visitor_ctx.force = ctx->force;
+
+ status = idmap_autorid_iterate_domain_ranges(db,
+ ctx->domsid,
+ idmap_autorid_delete_domain_ranges_visitor,
+ &visitor_ctx,
+ &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ ctx->count = count;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS idmap_autorid_delete_domain_ranges(struct db_context *db,
+ const char *domsid,
+ bool force,
+ int *count)
+{
+ NTSTATUS status;
+ struct idmap_autorid_delete_domain_ranges_ctx ctx;
+
+ ZERO_STRUCT(ctx);
+ ctx.domsid = domsid;
+ ctx.force = force;
+
+ status = dbwrap_trans_do(db, idmap_autorid_delete_domain_ranges_action,
+ &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *count = ctx.count;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/idmap_hash/idmap_hash.c b/source3/winbindd/idmap_hash/idmap_hash.c
new file mode 100644
index 0000000..c2e835c
--- /dev/null
+++ b/source3/winbindd/idmap_hash/idmap_hash.c
@@ -0,0 +1,504 @@
+/*
+ * idmap_hash.c
+ *
+ * Copyright (C) Gerald Carter <jerry@samba.org> 2007 - 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "winbindd/winbindd.h"
+#include "idmap.h"
+#include "idmap_hash.h"
+#include "ads.h"
+#include "nss_info.h"
+#include "../libcli/security/dom_sid.h"
+#include "libsmb/samlogon_cache.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+struct sid_hash_table {
+ struct dom_sid *sid;
+};
+
+/*********************************************************************
+ Hash a domain SID (S-1-5-12-aaa-bbb-ccc) to a 12bit number
+ ********************************************************************/
+
+static uint32_t hash_domain_sid(const struct dom_sid *sid)
+{
+ uint32_t hash;
+
+ if (sid->num_auths != 4)
+ return 0;
+
+ /* XOR the last three subauths */
+
+ hash = ((sid->sub_auths[1] ^ sid->sub_auths[2]) ^ sid->sub_auths[3]);
+
+ /* Take all 32-bits into account when generating the 12-bit
+ hash value */
+ hash = (((hash & 0xFFF00000) >> 20)
+ + ((hash & 0x000FFF00) >> 8)
+ + (hash & 0x000000FF)) & 0x00000FFF;
+
+ /* return a 12-bit hash value */
+
+ return hash;
+}
+
+/*********************************************************************
+ Hash a Relative ID to a 19 bit number
+ ********************************************************************/
+
+static uint32_t hash_rid(uint32_t rid)
+{
+ /*
+ * 19 bits for the rid which allows us to support
+ * the first 50K users/groups in a domain
+ *
+ */
+
+ return (rid & 0x0007FFFF);
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static uint32_t combine_hashes(uint32_t h_domain,
+ uint32_t h_rid)
+{
+ uint32_t return_id = 0;
+
+ /*
+ * shift the hash_domain 19 bits to the left and OR with the
+ * hash_rid
+ *
+ * This will generate a 31 bit number out of
+ * 12 bit domain and 19 bit rid.
+ */
+
+ return_id = ((h_domain<<19) | h_rid);
+
+ return return_id;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static void separate_hashes(uint32_t id,
+ uint32_t *h_domain,
+ uint32_t *h_rid)
+{
+ *h_rid = id & 0x0007FFFF;
+ *h_domain = (id & 0x7FF80000) >> 19;
+
+ return;
+}
+
+
+/*********************************************************************
+ ********************************************************************/
+
+static NTSTATUS idmap_hash_initialize(struct idmap_domain *dom)
+{
+ struct sid_hash_table *hashed_domains;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_domains = 0;
+ size_t i;
+
+ DBG_ERR("The idmap_hash module is deprecated and should not be used. "
+ "Please migrate to a different plugin. This module will be "
+ "removed in a future version of Samba\n");
+
+ if (!strequal(dom->name, "*")) {
+ DBG_ERR("Error: idmap_hash configured for domain '%s'. "
+ "But the hash module can only be used for the default "
+ "idmap configuration.\n", dom->name);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!wcache_tdc_fetch_list(&dom_list, &num_domains)) {
+ nt_status = NT_STATUS_TRUSTED_DOMAIN_FAILURE;
+ BAIL_ON_NTSTATUS_ERROR(nt_status);
+ }
+
+ /* Create the hash table of domain SIDs */
+
+ hashed_domains = talloc_zero_array(dom, struct sid_hash_table, 4096);
+ BAIL_ON_PTR_NT_ERROR(hashed_domains, nt_status);
+
+ /* create the hash table of domain SIDs */
+
+ for (i=0; i<num_domains; i++) {
+ struct dom_sid_buf buf;
+ uint32_t hash;
+
+ if (is_null_sid(&dom_list[i].sid))
+ continue;
+
+ /*
+ * Check if the domain from the list is not already configured
+ * to use another idmap backend. Not checking this makes the
+ * idmap_hash module map IDs for *all* domains implicitly. This
+ * is quite dangerous in setups that use multiple idmap
+ * configurations.
+ */
+
+ if (domain_has_idmap_config(dom_list[i].domain_name)) {
+ continue;
+ }
+
+ if ((hash = hash_domain_sid(&dom_list[i].sid)) == 0)
+ continue;
+
+ DBG_INFO("Adding %s (%s) -> %d\n",
+ dom_list[i].domain_name,
+ dom_sid_str_buf(&dom_list[i].sid, &buf),
+ hash);
+
+ hashed_domains[hash].sid = talloc(hashed_domains, struct dom_sid);
+ sid_copy(hashed_domains[hash].sid, &dom_list[i].sid);
+ }
+
+ dom->private_data = hashed_domains;
+
+done:
+ return nt_status;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static NTSTATUS idmap_hash_id_to_sid(struct sid_hash_table *hashed_domains,
+ struct idmap_domain *dom,
+ struct id_map *id)
+{
+ uint32_t h_domain = 0, h_rid = 0;
+
+ id->status = ID_UNMAPPED;
+
+ separate_hashes(id->xid.id, &h_domain, &h_rid);
+
+ /*
+ * If the domain hash doesn't find a SID in the table,
+ * skip it
+ */
+ if (hashed_domains[h_domain].sid == NULL) {
+ /* keep ID_UNMAPPED */
+ return NT_STATUS_OK;
+ }
+
+ id->xid.type = ID_TYPE_BOTH;
+ sid_compose(id->sid, hashed_domains[h_domain].sid, h_rid);
+ id->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct sid_hash_table *hashed_domains = talloc_get_type_abort(
+ dom->private_data, struct sid_hash_table);
+ size_t i;
+ size_t num_tomap = 0;
+ size_t num_mapped = 0;
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ num_tomap++;
+ }
+
+ for (i=0; ids[i]; i++) {
+ NTSTATUS ret;
+
+ ret = idmap_hash_id_to_sid(hashed_domains, dom, ids[i]);
+ if (!NT_STATUS_IS_OK(ret)) {
+ /* some fatal error occurred, log it */
+ DBG_NOTICE("Unexpected error resolving an ID "
+ "(%d): %s\n", ids[i]->xid.id,
+ nt_errstr(ret));
+ return ret;
+ }
+
+ if (ids[i]->status == ID_MAPPED) {
+ num_mapped++;
+ }
+ }
+
+ if (num_tomap == num_mapped) {
+ return NT_STATUS_OK;
+ } else if (num_mapped == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return STATUS_SOME_UNMAPPED;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static NTSTATUS idmap_hash_sid_to_id(struct sid_hash_table *hashed_domains,
+ struct idmap_domain *dom,
+ struct id_map *id)
+{
+ struct dom_sid sid;
+ uint32_t rid;
+ uint32_t h_domain, h_rid;
+
+ id->status = ID_UNMAPPED;
+
+ sid_copy(&sid, id->sid);
+ sid_split_rid(&sid, &rid);
+
+ h_domain = hash_domain_sid(&sid);
+ h_rid = hash_rid(rid);
+
+ /* Check that both hashes are non-zero*/
+ if (h_domain == 0) {
+ /* keep ID_UNMAPPED */
+ return NT_STATUS_OK;
+ }
+ if (h_rid == 0) {
+ /* keep ID_UNMAPPED */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * If the domain hash already exists find a SID in the table,
+ * just return the mapping.
+ */
+ if (hashed_domains[h_domain].sid != NULL) {
+ goto return_mapping;
+ }
+
+ /*
+ * Check of last resort: A domain is valid if a user from that
+ * domain has recently logged in. The samlogon_cache these
+ * days also stores the domain sid.
+ */
+ if (netsamlogon_cache_have(&sid)) {
+ /*
+ * The domain is valid, so we'll
+ * remember it in order to
+ * allow reverse mappings to work.
+ */
+ goto remember_domain;
+ }
+
+ if (id->xid.type == ID_TYPE_NOT_SPECIFIED) {
+ /*
+ * idmap_hash used to bounce back the requested type,
+ * which was ID_TYPE_UID, ID_TYPE_GID or
+ * ID_TYPE_NOT_SPECIFIED before as the winbindd parent
+ * always used a lookupsids. When the lookupsids
+ * failed because of an unknown domain, the idmap child
+ * weren't requested at all and the caller sees
+ * ID_TYPE_NOT_SPECIFIED.
+ *
+ * Now that the winbindd parent will pass ID_TYPE_BOTH
+ * in order to indicate that the domain exists.
+ * We should ask the parent to fallback to lookupsids
+ * if the domain is not known yet.
+ */
+ id->status = ID_REQUIRE_TYPE;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * Now we're sure the domain exist, remember
+ * the domain in order to return reverse mappings
+ * in future.
+ */
+remember_domain:
+ hashed_domains[h_domain].sid = dom_sid_dup(hashed_domains, &sid);
+ if (hashed_domains[h_domain].sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * idmap_hash used to bounce back the requested type,
+ * which was ID_TYPE_UID, ID_TYPE_GID or
+ * ID_TYPE_NOT_SPECIFIED before as the winbindd parent
+ * always used a lookupsids.
+ *
+ * This module should have supported ID_TYPE_BOTH since
+ * samba-4.1.0, similar to idmap_rid and idmap_autorid.
+ *
+ * Now that the winbindd parent will pass ID_TYPE_BOTH
+ * in order to indicate that the domain exists, it's
+ * better to always return ID_TYPE_BOTH instead of a
+ * random mix of ID_TYPE_UID, ID_TYPE_GID or
+ * ID_TYPE_BOTH.
+ */
+return_mapping:
+ id->xid.type = ID_TYPE_BOTH;
+ id->xid.id = combine_hashes(h_domain, h_rid);
+ id->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct sid_hash_table *hashed_domains = talloc_get_type_abort(
+ dom->private_data, struct sid_hash_table);
+ size_t i;
+ size_t num_tomap = 0;
+ size_t num_mapped = 0;
+ size_t num_required = 0;
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ num_tomap++;
+ }
+
+ for (i=0; ids[i]; i++) {
+ NTSTATUS ret;
+
+ ret = idmap_hash_sid_to_id(hashed_domains, dom, ids[i]);
+ if (!NT_STATUS_IS_OK(ret)) {
+ struct dom_sid_buf buf;
+ /* some fatal error occurred, log it */
+ DBG_NOTICE("Unexpected error resolving a SID "
+ "(%s): %s\n",
+ dom_sid_str_buf(ids[i]->sid, &buf),
+ nt_errstr(ret));
+ return ret;
+ }
+
+ if (ids[i]->status == ID_MAPPED) {
+ num_mapped++;
+ }
+ if (ids[i]->status == ID_REQUIRE_TYPE) {
+ num_required++;
+ }
+ }
+
+ if (num_tomap == num_mapped) {
+ return NT_STATUS_OK;
+ } else if (num_required > 0) {
+ return STATUS_SOME_UNMAPPED;
+ } else if (num_mapped == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return STATUS_SOME_UNMAPPED;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static NTSTATUS nss_hash_init(struct nss_domain_entry *e )
+{
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS nss_hash_map_to_alias(TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *name,
+ char **alias)
+{
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+ const char *value;
+
+ value = talloc_asprintf(mem_ctx, "%s\\%s", e->domain, name);
+ BAIL_ON_PTR_NT_ERROR(value, nt_status);
+
+ nt_status = mapfile_lookup_key(mem_ctx, value, alias);
+ BAIL_ON_NTSTATUS_ERROR(nt_status);
+
+done:
+ return nt_status;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS nss_hash_map_from_alias(TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *alias,
+ char **name)
+{
+ return mapfile_lookup_value(mem_ctx, alias, name);
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS nss_hash_close(void)
+{
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ Dispatch Tables for IDMap and NssInfo Methods
+********************************************************************/
+
+static const struct idmap_methods hash_idmap_methods = {
+ .init = idmap_hash_initialize,
+ .unixids_to_sids = unixids_to_sids,
+ .sids_to_unixids = sids_to_unixids,
+};
+
+static const struct nss_info_methods hash_nss_methods = {
+ .init = nss_hash_init,
+ .map_to_alias = nss_hash_map_to_alias,
+ .map_from_alias = nss_hash_map_from_alias,
+ .close_fn = nss_hash_close
+};
+
+/**********************************************************************
+ Register with the idmap and idmap_nss subsystems. We have to protect
+ against the idmap and nss_info interfaces being in a half-registered
+ state.
+ **********************************************************************/
+
+static_decl_idmap;
+NTSTATUS idmap_hash_init(TALLOC_CTX *ctx)
+{
+ static NTSTATUS idmap_status = NT_STATUS_UNSUCCESSFUL;
+ static NTSTATUS nss_status = NT_STATUS_UNSUCCESSFUL;
+
+ if ( !NT_STATUS_IS_OK(idmap_status) ) {
+ idmap_status = smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION,
+ "hash", &hash_idmap_methods);
+
+ if ( !NT_STATUS_IS_OK(idmap_status) ) {
+ DEBUG(0,("Failed to register hash idmap plugin.\n"));
+ return idmap_status;
+ }
+ }
+
+ if ( !NT_STATUS_IS_OK(nss_status) ) {
+ nss_status = smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "hash", &hash_nss_methods);
+ if ( !NT_STATUS_IS_OK(nss_status) ) {
+ DEBUG(0,("Failed to register hash idmap nss plugin.\n"));
+ return nss_status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/idmap_hash/idmap_hash.h b/source3/winbindd/idmap_hash/idmap_hash.h
new file mode 100644
index 0000000..621520e
--- /dev/null
+++ b/source3/winbindd/idmap_hash/idmap_hash.h
@@ -0,0 +1,60 @@
+/*
+ * lwopen.h
+ *
+ * Copyright (C) Gerald Carter <jerry@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef _LWOPEN_H
+#define _LWOPEN_H
+
+#define BAIL_ON_NTSTATUS_ERROR(x) \
+ do { \
+ if (!NT_STATUS_IS_OK(x)) { \
+ DEBUG(10,("Failed! (%s)\n", nt_errstr(x))); \
+ goto done; \
+ } \
+ } \
+ while (0); \
+
+#define BAIL_ON_PTR_NT_ERROR(p, x) \
+ do { \
+ if ((p) == NULL ) { \
+ DEBUG(10,("NULL pointer!\n")); \
+ x = NT_STATUS_NO_MEMORY; \
+ goto done; \
+ } else { \
+ x = NT_STATUS_OK; \
+ } \
+ } while (0);
+
+#define PRINT_NTSTATUS_ERROR(x, hdr, level) \
+ do { \
+ if (!NT_STATUS_IS_OK(x)) { \
+ DEBUG(level,("Likewise Open ("hdr"): %s\n", nt_errstr(x))); \
+ } \
+ } while(0);
+
+
+NTSTATUS mapfile_lookup_key(TALLOC_CTX *ctx,
+ const char *value,
+ char **key);
+
+NTSTATUS mapfile_lookup_value(TALLOC_CTX *ctx,
+ const char *key,
+ char **value);
+
+#endif /* _LWOPEN_H */
diff --git a/source3/winbindd/idmap_hash/mapfile.c b/source3/winbindd/idmap_hash/mapfile.c
new file mode 100644
index 0000000..82812a1
--- /dev/null
+++ b/source3/winbindd/idmap_hash/mapfile.c
@@ -0,0 +1,182 @@
+/*
+ * mapfile.c
+ *
+ * Copyright (C) Gerald Carter <jerry@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "winbindd/winbindd.h"
+#include "idmap.h"
+#include "idmap_hash.h"
+
+static FILE *lw_map_file = NULL;
+
+/*********************************************************************
+ ********************************************************************/
+
+static bool mapfile_open(void)
+{
+ const char *mapfile_name = NULL;
+
+ /* If we have an open handle, just reset it */
+
+ if (lw_map_file) {
+ return (fseek(lw_map_file, 0, SEEK_SET) == 0);
+ }
+
+ mapfile_name = lp_parm_const_string(-1, "idmap_hash", "name_map", NULL);
+ if (!mapfile_name) {
+ return false;
+ }
+
+ lw_map_file = fopen(mapfile_name, "r");
+ if (!lw_map_file) {
+ DEBUG(0,("can't open idmap_hash:name_map (%s). Error %s\n",
+ mapfile_name, strerror(errno) ));
+ return false;
+ }
+
+ return true;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static bool mapfile_read_line(fstring key, fstring value)
+{
+ char buffer[1024];
+ char *p;
+ int len;
+
+ if (!lw_map_file)
+ return false;
+
+ p = fgets(buffer, sizeof(buffer)-1, lw_map_file);
+ if (p == NULL) {
+ return false;
+ }
+
+ /* Strip newlines and carriage returns */
+
+ len = strlen_m(buffer);
+ if (len == 0) {
+ return false;
+ }
+ len -= 1;
+
+ while ((buffer[len] == '\n') || (buffer[len] == '\r')) {
+ buffer[len--] = '\0';
+ }
+
+
+ if ((p = strchr_m(buffer, '=')) == NULL ) {
+ DEBUG(0,("idmap_hash: Bad line in name_map (%s)\n", buffer));
+ return false;
+ }
+
+ *p = '\0';
+ p++;
+
+ strlcpy(key, buffer, sizeof(fstring));
+ strlcpy(value, p, sizeof(fstring));
+
+ /* Eat whitespace */
+
+ if (!trim_char(key, ' ', ' '))
+ return false;
+
+ if (!trim_char(value, ' ', ' '))
+ return false;
+
+ return true;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static bool mapfile_close(void)
+{
+ int ret = 0;
+ if (lw_map_file) {
+ ret = fclose(lw_map_file);
+ lw_map_file = NULL;
+ }
+
+ return (ret == 0);
+}
+
+
+/*********************************************************************
+ ********************************************************************/
+
+NTSTATUS mapfile_lookup_key(TALLOC_CTX *ctx, const char *value, char **key)
+{
+ fstring r_key, r_value;
+ NTSTATUS ret = NT_STATUS_NOT_FOUND;
+
+ if (!mapfile_open())
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+
+ while (mapfile_read_line(r_key, r_value))
+ {
+ if (strequal(r_value, value)) {
+ ret = NT_STATUS_OK;
+
+ /* We're done once finishing this block */
+ *key = talloc_strdup(ctx, r_key);
+ if (!*key) {
+ ret = NT_STATUS_NO_MEMORY;
+ }
+ break;
+ }
+ }
+
+ mapfile_close();
+
+ return ret;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+NTSTATUS mapfile_lookup_value(TALLOC_CTX *ctx, const char *key, char **value)
+{
+ fstring r_key, r_value;
+ NTSTATUS ret = NT_STATUS_NOT_FOUND;
+
+ if (!mapfile_open())
+ return NT_STATUS_OBJECT_PATH_NOT_FOUND;
+
+ while (mapfile_read_line(r_key, r_value))
+ {
+ if (strequal(r_key, key)) {
+ ret = NT_STATUS_OK;
+
+ /* We're done once finishing this block */
+ *value = talloc_strdup(ctx, r_value);
+ if (!*key) {
+ ret = NT_STATUS_NO_MEMORY;
+ }
+ break;
+ }
+ }
+
+ mapfile_close();
+
+ return ret;
+}
diff --git a/source3/winbindd/idmap_ldap.c b/source3/winbindd/idmap_ldap.c
new file mode 100644
index 0000000..0b0d82b
--- /dev/null
+++ b/source3/winbindd/idmap_ldap.c
@@ -0,0 +1,1140 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap LDAP backend
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Gerald Carter 2003
+ Copyright (C) Simo Sorce 2003-2007
+ Copyright (C) Michael Adam 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "secrets.h"
+#include "idmap.h"
+#include "idmap_rw.h"
+#include "../libcli/security/security.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+#include <lber.h>
+#include <ldap.h>
+
+#include "smbldap.h"
+#include "passdb/pdb_ldap_schema.h"
+
+struct idmap_ldap_context {
+ struct smbldap_state *smbldap_state;
+ char *url;
+ char *suffix;
+ char *user_dn;
+ bool anon;
+ struct idmap_rw_ops *rw_ops;
+};
+
+#define CHECK_ALLOC_DONE(mem) do { \
+ if (!mem) { \
+ DEBUG(0, ("Out of memory!\n")); \
+ ret = NT_STATUS_NO_MEMORY; \
+ goto done; \
+ } } while (0)
+
+/**********************************************************************
+ IDMAP ALLOC TDB BACKEND
+**********************************************************************/
+
+/*********************************************************************
+ ********************************************************************/
+
+static NTSTATUS get_credentials( TALLOC_CTX *mem_ctx,
+ struct smbldap_state *ldap_state,
+ struct idmap_domain *dom,
+ char **dn )
+{
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ char *secret = NULL;
+ const char *tmp = NULL;
+ char *user_dn = NULL;
+ bool anon = False;
+
+ /* assume anonymous if we don't have a specified user */
+
+ tmp = idmap_config_const_string(dom->name, "ldap_user_dn", NULL);
+
+ if ( tmp ) {
+ secret = idmap_fetch_secret("ldap", dom->name, tmp);
+ if (!secret) {
+ DEBUG(0, ("get_credentials: Unable to fetch "
+ "auth credentials for %s in %s\n",
+ tmp, (dom==NULL)?"ALLOC":dom->name));
+ ret = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ *dn = talloc_strdup(mem_ctx, tmp);
+ CHECK_ALLOC_DONE(*dn);
+ } else {
+ if (!fetch_ldap_pw(&user_dn, &secret)) {
+ DEBUG(2, ("get_credentials: Failed to lookup ldap "
+ "bind creds. Using anonymous connection.\n"));
+ anon = True;
+ *dn = NULL;
+ } else {
+ *dn = talloc_strdup(mem_ctx, user_dn);
+ SAFE_FREE( user_dn );
+ CHECK_ALLOC_DONE(*dn);
+ }
+ }
+
+ smbldap_set_creds(ldap_state, anon, *dn, secret);
+ ret = NT_STATUS_OK;
+
+done:
+ BURN_FREE_STR(secret);
+
+ return ret;
+}
+
+
+/**********************************************************************
+ Verify the sambaUnixIdPool entry in the directory.
+**********************************************************************/
+
+static NTSTATUS verify_idpool(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *mem_ctx;
+ LDAPMessage *result = NULL;
+ LDAPMod **mods = NULL;
+ const char **attr_list;
+ char *filter;
+ int count;
+ int rc;
+ struct idmap_ldap_context *ctx;
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ mem_ctx = talloc_new(ctx);
+ if (mem_ctx == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ filter = talloc_asprintf(mem_ctx, "(objectclass=%s)", LDAP_OBJ_IDPOOL);
+ CHECK_ALLOC_DONE(filter);
+
+ attr_list = get_attr_list(mem_ctx, idpool_attr_list);
+ CHECK_ALLOC_DONE(attr_list);
+
+ rc = smbldap_search(ctx->smbldap_state,
+ ctx->suffix,
+ LDAP_SCOPE_SUBTREE,
+ filter,
+ attr_list,
+ 0,
+ &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1, ("Unable to verify the idpool, "
+ "cannot continue initialization!\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state),
+ result);
+
+ ldap_msgfree(result);
+
+ if ( count > 1 ) {
+ DEBUG(0,("Multiple entries returned from %s (base == %s)\n",
+ filter, ctx->suffix));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ else if (count == 0) {
+ char *uid_str, *gid_str;
+
+ uid_str = talloc_asprintf(mem_ctx, "%lu",
+ (unsigned long)dom->low_id);
+ gid_str = talloc_asprintf(mem_ctx, "%lu",
+ (unsigned long)dom->low_id);
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ "objectClass", LDAP_OBJ_IDPOOL);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_UIDNUMBER),
+ uid_str);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_GIDNUMBER),
+ gid_str);
+ if (mods) {
+ rc = smbldap_modify(ctx->smbldap_state,
+ ctx->suffix,
+ mods);
+ ldap_mods_free(mods, True);
+ } else {
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ ret = (rc == LDAP_SUCCESS)?NT_STATUS_OK:NT_STATUS_UNSUCCESSFUL;
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/********************************
+ Allocate a new uid or gid
+********************************/
+
+static NTSTATUS idmap_ldap_allocate_id_internal(struct idmap_domain *dom,
+ struct unixid *xid)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS ret = NT_STATUS_UNSUCCESSFUL;
+ int rc = LDAP_SERVER_DOWN;
+ int count = 0;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ char *id_str;
+ char *new_id_str;
+ char *filter = NULL;
+ const char *dn = NULL;
+ const char **attr_list;
+ const char *type;
+ struct idmap_ldap_context *ctx;
+ int error = 0;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ mem_ctx = talloc_new(ctx);
+ if (!mem_ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* get type */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_UIDNUMBER);
+ break;
+
+ case ID_TYPE_GID:
+ type = get_attr_key2string(idpool_attr_list,
+ LDAP_ATTR_GIDNUMBER);
+ break;
+
+ case ID_TYPE_BOTH:
+ /*
+ * This is not supported here yet and
+ * already handled in idmap_rw_new_mapping()
+ */
+ FALL_THROUGH;
+ case ID_TYPE_NOT_SPECIFIED:
+ /*
+ * This is handled in idmap_rw_new_mapping()
+ */
+ FALL_THROUGH;
+ default:
+ DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ filter = talloc_asprintf(mem_ctx, "(objectClass=%s)", LDAP_OBJ_IDPOOL);
+ CHECK_ALLOC_DONE(filter);
+
+ attr_list = get_attr_list(mem_ctx, idpool_attr_list);
+ CHECK_ALLOC_DONE(attr_list);
+
+ DEBUG(10, ("Search of the id pool (filter: %s)\n", filter));
+
+ rc = smbldap_search(ctx->smbldap_state,
+ ctx->suffix,
+ LDAP_SCOPE_SUBTREE, filter,
+ attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(0,("%s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ smbldap_talloc_autofree_ldapmsg(mem_ctx, result);
+
+ count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state),
+ result);
+ if (count != 1) {
+ DEBUG(0,("Single %s object not found\n", LDAP_OBJ_IDPOOL));
+ goto done;
+ }
+
+ entry = ldap_first_entry(smbldap_get_ldap(ctx->smbldap_state), result);
+
+ dn = smbldap_talloc_dn(mem_ctx,
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry);
+ if ( ! dn) {
+ goto done;
+ }
+
+ id_str = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry, type, mem_ctx);
+ if (id_str == NULL) {
+ DEBUG(0,("%s attribute not found\n", type));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ xid->id = smb_strtoul(id_str, NULL, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* make sure we still have room to grow */
+
+ switch (xid->type) {
+ case ID_TYPE_UID:
+ if (xid->id > dom->high_id) {
+ DEBUG(0,("Cannot allocate uid above %lu!\n",
+ (unsigned long)dom->high_id));
+ goto done;
+ }
+ break;
+
+ case ID_TYPE_GID:
+ if (xid->id > dom->high_id) {
+ DEBUG(0,("Cannot allocate gid above %lu!\n",
+ (unsigned long)dom->high_id));
+ goto done;
+ }
+ break;
+
+ default:
+ /* impossible */
+ goto done;
+ }
+
+ new_id_str = talloc_asprintf(mem_ctx, "%lu", (unsigned long)xid->id + 1);
+ if ( ! new_id_str) {
+ DEBUG(0,("Out of memory\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ smbldap_set_mod(&mods, LDAP_MOD_DELETE, type, id_str);
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, type, new_id_str);
+
+ if (mods == NULL) {
+ DEBUG(0,("smbldap_set_mod() failed.\n"));
+ goto done;
+ }
+
+ DEBUG(10, ("Try to atomically increment the id (%s -> %s)\n",
+ id_str, new_id_str));
+
+ rc = smbldap_modify(ctx->smbldap_state, dn, mods);
+
+ ldap_mods_free(mods, True);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(1,("Failed to allocate new %s. "
+ "smbldap_modify() failed.\n", type));
+ goto done;
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/**
+ * Allocate a new unix-ID.
+ * For now this is for the default idmap domain only.
+ * Should be extended later on.
+ */
+static NTSTATUS idmap_ldap_allocate_id(struct idmap_domain *dom,
+ struct unixid *id)
+{
+ NTSTATUS ret;
+
+ if (!strequal(dom->name, "*")) {
+ DEBUG(3, ("idmap_ldap_allocate_id: "
+ "Refusing allocation of a new unixid for domain'%s'. "
+ "This is only supported for the default "
+ "domain \"*\".\n",
+ dom->name));
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ ret = idmap_ldap_allocate_id_internal(dom, id);
+
+ return ret;
+}
+
+
+/**********************************************************************
+ IDMAP MAPPING LDAP BACKEND
+**********************************************************************/
+
+static int idmap_ldap_close_destructor(struct idmap_ldap_context *ctx)
+{
+ smbldap_free_struct(&ctx->smbldap_state);
+ DEBUG(5,("The connection to the LDAP server was closed\n"));
+ /* maybe free the results here --metze */
+
+ return 0;
+}
+
+/********************************
+ Initialise idmap database.
+********************************/
+
+static NTSTATUS idmap_ldap_set_mapping(struct idmap_domain *dom,
+ const struct id_map *map);
+
+static NTSTATUS idmap_ldap_db_init(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ struct idmap_ldap_context *ctx = NULL;
+ const char *tmp = NULL;
+
+ /* Only do init if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ ctx = talloc_zero(dom, struct idmap_ldap_context);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ tmp = idmap_config_const_string(dom->name, "ldap_url", NULL);
+
+ if ( ! tmp) {
+ DEBUG(1, ("ERROR: missing idmap ldap url\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ ctx->url = talloc_strdup(ctx, tmp);
+
+ trim_char(ctx->url, '\"', '\"');
+
+ tmp = idmap_config_const_string(dom->name, "ldap_base_dn", NULL);
+ if ( ! tmp || ! *tmp) {
+ tmp = lp_ldap_idmap_suffix(talloc_tos());
+ if ( ! tmp) {
+ DEBUG(1, ("ERROR: missing idmap ldap suffix\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+ }
+
+ ctx->suffix = talloc_strdup(ctx, tmp);
+ CHECK_ALLOC_DONE(ctx->suffix);
+
+ ctx->rw_ops = talloc_zero(ctx, struct idmap_rw_ops);
+ CHECK_ALLOC_DONE(ctx->rw_ops);
+
+ ctx->rw_ops->get_new_id = idmap_ldap_allocate_id_internal;
+ ctx->rw_ops->set_mapping = idmap_ldap_set_mapping;
+
+ /* get_credentials deals with setting up creds */
+
+ ret = smbldap_init(ctx, global_event_context(), ctx->url,
+ false, NULL, NULL, &ctx->smbldap_state);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("ERROR: smbldap_init (%s) failed!\n", ctx->url));
+ goto done;
+ }
+
+ ret = get_credentials( ctx, ctx->smbldap_state,
+ dom, &ctx->user_dn );
+ if ( !NT_STATUS_IS_OK(ret) ) {
+ DEBUG(1,("idmap_ldap_db_init: Failed to get connection "
+ "credentials (%s)\n", nt_errstr(ret)));
+ goto done;
+ }
+
+ /*
+ * Set the destructor on the context, so that resources are
+ * properly freed when the context is released.
+ */
+ talloc_set_destructor(ctx, idmap_ldap_close_destructor);
+
+ dom->private_data = ctx;
+
+ ret = verify_idpool(dom);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("idmap_ldap_db_init: failed to verify ID pool (%s)\n",
+ nt_errstr(ret)));
+ goto done;
+ }
+
+ return NT_STATUS_OK;
+
+/*failed */
+done:
+ talloc_free(ctx);
+ return ret;
+}
+
+/**
+ * set a mapping.
+ */
+
+/* TODO: change this: This function cannot be called to modify a mapping,
+ * only set a new one */
+
+static NTSTATUS idmap_ldap_set_mapping(struct idmap_domain *dom,
+ const struct id_map *map)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ldap_context *ctx;
+ LDAPMessage *entry = NULL;
+ LDAPMod **mods = NULL;
+ const char *type;
+ char *id_str;
+ struct dom_sid_buf sid;
+ char *dn;
+ int rc = -1;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ switch(map->xid.type) {
+ case ID_TYPE_UID:
+ type = get_attr_key2string(sidmap_attr_list,
+ LDAP_ATTR_UIDNUMBER);
+ break;
+
+ case ID_TYPE_GID:
+ type = get_attr_key2string(sidmap_attr_list,
+ LDAP_ATTR_GIDNUMBER);
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ memctx = talloc_new(ctx);
+ if ( ! memctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ id_str = talloc_asprintf(memctx, "%lu", (unsigned long)map->xid.id);
+ CHECK_ALLOC_DONE(id_str);
+
+ dn = talloc_asprintf(memctx, "%s=%s,%s",
+ get_attr_key2string(sidmap_attr_list, LDAP_ATTR_SID),
+ dom_sid_str_buf(map->sid, &sid),
+ ctx->suffix);
+ CHECK_ALLOC_DONE(dn);
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD,
+ "objectClass", LDAP_OBJ_IDMAP_ENTRY);
+
+ smbldap_make_mod(smbldap_get_ldap(ctx->smbldap_state),
+ entry, &mods, type, id_str);
+
+ smbldap_make_mod(smbldap_get_ldap(ctx->smbldap_state), entry, &mods,
+ get_attr_key2string(sidmap_attr_list, LDAP_ATTR_SID),
+ sid.buf);
+
+ if ( ! mods) {
+ DEBUG(2, ("ERROR: No mods?\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* TODO: remove conflicting mappings! */
+
+ smbldap_set_mod(&mods, LDAP_MOD_ADD, "objectClass", LDAP_OBJ_SID_ENTRY);
+
+ DEBUG(10, ("Set DN %s (%s -> %s)\n", dn, sid.buf, id_str));
+
+ rc = smbldap_add(ctx->smbldap_state, dn, mods);
+ ldap_mods_free(mods, True);
+
+ if (rc != LDAP_SUCCESS) {
+ char *ld_error = NULL;
+ ldap_get_option(smbldap_get_ldap(ctx->smbldap_state),
+ LDAP_OPT_ERROR_STRING, &ld_error);
+ DEBUG(0,("ldap_set_mapping_internals: Failed to add %s to %lu "
+ "mapping [%s]\n", sid.buf,
+ (unsigned long)map->xid.id, type));
+ DEBUG(0, ("ldap_set_mapping_internals: Error was: %s (%s)\n",
+ ld_error ? ld_error : "(NULL)", ldap_err2string (rc)));
+ if (ld_error) {
+ ldap_memfree(ld_error);
+ }
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ DEBUG(10,("ldap_set_mapping: Successfully created mapping from %s to "
+ "%lu [%s]\n", sid.buf, (unsigned long)map->xid.id, type));
+
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/**
+ * Create a new mapping for an unmapped SID, also allocating a new ID.
+ * If possible, this should be run inside a transaction to make the
+ * action atomic.
+ */
+static NTSTATUS idmap_ldap_new_mapping(struct idmap_domain *dom, struct id_map *map)
+{
+ NTSTATUS ret;
+ struct idmap_ldap_context *ctx;
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ ret = idmap_rw_new_mapping(dom, ctx->rw_ops, map);
+
+ return ret;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_ldap_unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ldap_context *ctx;
+ LDAPMessage *result = NULL;
+ LDAPMessage *entry = NULL;
+ const char *uidNumber;
+ const char *gidNumber;
+ const char **attr_list;
+ char *filter = NULL;
+ bool multi = False;
+ int idx = 0;
+ int bidx = 0;
+ int count;
+ int rc;
+ int i;
+ int error = 0;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ memctx = talloc_new(ctx);
+ if ( ! memctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_UIDNUMBER);
+ gidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_GIDNUMBER);
+
+ attr_list = get_attr_list(memctx, sidmap_attr_list);
+
+ if ( ! ids[1]) {
+ /* if we are requested just one mapping use the simple filter */
+
+ filter = talloc_asprintf(memctx, "(&(objectClass=%s)(%s=%lu))",
+ LDAP_OBJ_IDMAP_ENTRY,
+ (ids[0]->xid.type==ID_TYPE_UID)?uidNumber:gidNumber,
+ (unsigned long)ids[0]->xid.id);
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+ } else {
+ /* multiple mappings */
+ multi = True;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+again:
+ if (multi) {
+
+ talloc_free(filter);
+ filter = talloc_asprintf(memctx,
+ "(&(objectClass=%s)(|",
+ LDAP_OBJ_IDMAP_ENTRY);
+ CHECK_ALLOC_DONE(filter);
+
+ bidx = idx;
+ for (i = 0; (i < IDMAP_LDAP_MAX_IDS) && ids[idx]; i++, idx++) {
+ filter = talloc_asprintf_append_buffer(filter, "(%s=%lu)",
+ (ids[idx]->xid.type==ID_TYPE_UID)?uidNumber:gidNumber,
+ (unsigned long)ids[idx]->xid.id);
+ CHECK_ALLOC_DONE(filter);
+ }
+ filter = talloc_asprintf_append_buffer(filter, "))");
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+ } else {
+ bidx = 0;
+ idx = 1;
+ }
+
+ rc = smbldap_search(ctx->smbldap_state, ctx->suffix, LDAP_SCOPE_SUBTREE,
+ filter, attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(3,("Failure looking up ids (%s)\n", ldap_err2string(rc)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state),
+ result);
+
+ if (count == 0) {
+ DEBUG(10, ("NO SIDs found\n"));
+ }
+
+ for (i = 0; i < count; i++) {
+ char *sidstr = NULL;
+ char *tmp = NULL;
+ enum id_type type;
+ struct id_map *map;
+ uint32_t id;
+ struct dom_sid_buf buf;
+
+ if (i == 0) { /* first entry */
+ entry = ldap_first_entry(
+ smbldap_get_ldap(ctx->smbldap_state), result);
+ } else { /* following ones */
+ entry = ldap_next_entry(
+ smbldap_get_ldap(ctx->smbldap_state), entry);
+ }
+ if ( ! entry) {
+ DEBUG(2, ("ERROR: Unable to fetch ldap entries "
+ "from results\n"));
+ break;
+ }
+
+ /* first check if the SID is present */
+ sidstr = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry, LDAP_ATTRIBUTE_SID, memctx);
+ if ( ! sidstr) { /* no sid, skip entry */
+ DEBUG(2, ("WARNING SID not found on entry\n"));
+ continue;
+ }
+
+ /* now try to see if it is a uid, if not try with a gid
+ * (gid is more common, but in case both uidNumber and
+ * gidNumber are returned the SID is mapped to the uid
+ *not the gid) */
+ type = ID_TYPE_UID;
+ tmp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry, uidNumber, memctx);
+ if ( ! tmp) {
+ type = ID_TYPE_GID;
+ tmp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry, gidNumber, memctx);
+ }
+ if ( ! tmp) { /* wow very strange entry, how did it match ? */
+ DEBUG(5, ("Improbable match on (%s), no uidNumber, "
+ "nor gidNumber returned\n", sidstr));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ id = smb_strtoul(tmp, NULL, 10, &error, SMB_STR_STANDARD);
+ TALLOC_FREE(tmp);
+ if (error != 0) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). "
+ "Filtered!\n", id,
+ dom->low_id, dom->high_id));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ if (!idmap_unix_id_is_in_range(id, dom)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). "
+ "Filtered!\n", id,
+ dom->low_id, dom->high_id));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ map = idmap_find_map_by_id(&ids[bidx], type, id);
+ if (!map) {
+ DEBUG(2, ("WARNING: couldn't match sid (%s) "
+ "with requested ids\n", sidstr));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ if ( ! string_to_sid(map->sid, sidstr)) {
+ DEBUG(2, ("ERROR: Invalid SID on entry\n"));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ if (map->status == ID_MAPPED) {
+ DEBUG(1, ("WARNING: duplicate %s mapping in LDAP. "
+ "overwriting mapping %u -> %s with %u -> %s\n",
+ (type == ID_TYPE_UID) ? "UID" : "GID",
+ id,
+ dom_sid_str_buf(map->sid, &buf),
+ id,
+ sidstr));
+ }
+
+ TALLOC_FREE(sidstr);
+
+ /* mapped */
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Mapped %s -> %lu (%d)\n",
+ dom_sid_str_buf(map->sid, &buf),
+ (unsigned long)map->xid.id, map->xid.type));
+ }
+
+ /* free the ldap results */
+ if (result) {
+ ldap_msgfree(result);
+ result = NULL;
+ }
+
+ if (multi && ids[idx]) { /* still some values to map */
+ goto again;
+ }
+
+ ret = NT_STATUS_OK;
+
+ /* mark all unknown/expired ones as unmapped */
+ for (i = 0; ids[i]; i++) {
+ if (ids[i]->status != ID_MAPPED)
+ ids[i]->status = ID_UNMAPPED;
+ }
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_ldap_sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ LDAPMessage *entry = NULL;
+ NTSTATUS ret;
+ TALLOC_CTX *memctx;
+ struct idmap_ldap_context *ctx;
+ LDAPMessage *result = NULL;
+ const char *uidNumber;
+ const char *gidNumber;
+ const char **attr_list;
+ char *filter = NULL;
+ bool multi = False;
+ size_t num_required = 0;
+ int idx = 0;
+ int bidx = 0;
+ int count;
+ int rc;
+ int i;
+
+ /* Only do query if we are online */
+ if (idmap_is_offline()) {
+ return NT_STATUS_FILE_IS_OFFLINE;
+ }
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_ldap_context);
+
+ memctx = talloc_new(ctx);
+ if ( ! memctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ uidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_UIDNUMBER);
+ gidNumber = get_attr_key2string(idpool_attr_list, LDAP_ATTR_GIDNUMBER);
+
+ attr_list = get_attr_list(memctx, sidmap_attr_list);
+
+ if ( ! ids[1]) {
+ struct dom_sid_buf buf;
+ /* if we are requested just one mapping use the simple filter */
+
+ filter = talloc_asprintf(memctx, "(&(objectClass=%s)(%s=%s))",
+ LDAP_OBJ_IDMAP_ENTRY,
+ LDAP_ATTRIBUTE_SID,
+ dom_sid_str_buf(ids[0]->sid, &buf));
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+ } else {
+ /* multiple mappings */
+ multi = True;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+again:
+ if (multi) {
+
+ TALLOC_FREE(filter);
+ filter = talloc_asprintf(memctx,
+ "(&(objectClass=%s)(|",
+ LDAP_OBJ_IDMAP_ENTRY);
+ CHECK_ALLOC_DONE(filter);
+
+ bidx = idx;
+ for (i = 0; (i < IDMAP_LDAP_MAX_IDS) && ids[idx]; i++, idx++) {
+ struct dom_sid_buf buf;
+ filter = talloc_asprintf_append_buffer(filter, "("LDAP_ATTRIBUTE_SID"=%s)",
+ dom_sid_str_buf(ids[idx]->sid, &buf));
+ CHECK_ALLOC_DONE(filter);
+ }
+ filter = talloc_asprintf_append_buffer(filter, "))");
+ CHECK_ALLOC_DONE(filter);
+ DEBUG(10, ("Filter: [%s]\n", filter));
+ } else {
+ bidx = 0;
+ idx = 1;
+ }
+
+ rc = smbldap_search(ctx->smbldap_state, ctx->suffix, LDAP_SCOPE_SUBTREE,
+ filter, attr_list, 0, &result);
+
+ if (rc != LDAP_SUCCESS) {
+ DEBUG(3,("Failure looking up sids (%s)\n",
+ ldap_err2string(rc)));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ count = ldap_count_entries(smbldap_get_ldap(ctx->smbldap_state),
+ result);
+
+ if (count == 0) {
+ DEBUG(10, ("NO SIDs found\n"));
+ }
+
+ for (i = 0; i < count; i++) {
+ char *sidstr = NULL;
+ char *tmp = NULL;
+ enum id_type type;
+ struct id_map *map;
+ struct dom_sid sid;
+ struct dom_sid_buf buf;
+ uint32_t id;
+ int error = 0;
+
+ if (i == 0) { /* first entry */
+ entry = ldap_first_entry(
+ smbldap_get_ldap(ctx->smbldap_state), result);
+ } else { /* following ones */
+ entry = ldap_next_entry(
+ smbldap_get_ldap(ctx->smbldap_state), entry);
+ }
+ if ( ! entry) {
+ DEBUG(2, ("ERROR: Unable to fetch ldap entries "
+ "from results\n"));
+ break;
+ }
+
+ /* first check if the SID is present */
+ sidstr = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry, LDAP_ATTRIBUTE_SID, memctx);
+ if ( ! sidstr) { /* no sid ??, skip entry */
+ DEBUG(2, ("WARNING SID not found on entry\n"));
+ continue;
+ }
+
+ if ( ! string_to_sid(&sid, sidstr)) {
+ DEBUG(2, ("ERROR: Invalid SID on entry\n"));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ map = idmap_find_map_by_sid(&ids[bidx], &sid);
+ if (!map) {
+ DEBUG(2, ("WARNING: couldn't find entry sid (%s) "
+ "in ids\n", sidstr));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ /* now try to see if it is a uid, if not try with a gid
+ * (gid is more common, but in case both uidNumber and
+ * gidNumber are returned the SID is mapped to the uid
+ * not the gid) */
+ type = ID_TYPE_UID;
+ tmp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry, uidNumber, memctx);
+ if ( ! tmp) {
+ type = ID_TYPE_GID;
+ tmp = smbldap_talloc_single_attribute(
+ smbldap_get_ldap(ctx->smbldap_state),
+ entry, gidNumber, memctx);
+ }
+ if ( ! tmp) { /* no ids ?? */
+ DEBUG(5, ("no uidNumber, "
+ "nor gidNumber attributes found\n"));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ id = smb_strtoul(tmp, NULL, 10, &error, SMB_STR_STANDARD);
+ TALLOC_FREE(tmp);
+ if (error != 0) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). "
+ "Filtered!\n", id,
+ dom->low_id, dom->high_id));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ if (error != 0 || !idmap_unix_id_is_in_range(id, dom)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). "
+ "Filtered!\n", id,
+ dom->low_id, dom->high_id));
+ TALLOC_FREE(sidstr);
+ continue;
+ }
+
+ if (map->status == ID_MAPPED) {
+ DEBUG(1, ("WARNING: duplicate %s mapping in LDAP. "
+ "overwriting mapping %s -> %u with %s -> %u\n",
+ (type == ID_TYPE_UID) ? "UID" : "GID",
+ sidstr, map->xid.id, sidstr, id));
+ }
+
+ TALLOC_FREE(sidstr);
+
+ /* mapped */
+ map->xid.type = type;
+ map->xid.id = id;
+ map->status = ID_MAPPED;
+
+ DEBUG(10, ("Mapped %s -> %lu (%d)\n",
+ dom_sid_str_buf(map->sid, &buf),
+ (unsigned long)map->xid.id,
+ map->xid.type));
+ }
+
+ /* free the ldap results */
+ if (result) {
+ ldap_msgfree(result);
+ result = NULL;
+ }
+
+ if (multi && ids[idx]) { /* still some values to map */
+ goto again;
+ }
+
+ /*
+ * try to create new mappings for unmapped sids
+ */
+ for (i = 0; ids[i]; i++) {
+ if (ids[i]->status != ID_MAPPED) {
+ ids[i]->status = ID_UNMAPPED;
+ if (ids[i]->sid != NULL) {
+ ret = idmap_ldap_new_mapping(dom, ids[i]);
+ DBG_DEBUG("idmap_ldap_new_mapping returned %s\n",
+ nt_errstr(ret));
+ if (NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED)) {
+ if (ids[i]->status == ID_REQUIRE_TYPE) {
+ num_required += 1;
+ continue;
+ }
+ }
+ if (!NT_STATUS_IS_OK(ret)) {
+ /*
+ * If we can't create
+ * a new mapping it's unlikely
+ * that it will work for the
+ * next entry.
+ */
+ goto done;
+ }
+ }
+ }
+ }
+
+ ret = NT_STATUS_OK;
+ if (num_required > 0) {
+ ret = STATUS_SOME_UNMAPPED;
+ }
+
+done:
+ talloc_free(memctx);
+ return ret;
+}
+
+/**********************************
+ Close the idmap ldap instance
+**********************************/
+
+static const struct idmap_methods idmap_ldap_methods = {
+
+ .init = idmap_ldap_db_init,
+ .unixids_to_sids = idmap_ldap_unixids_to_sids,
+ .sids_to_unixids = idmap_ldap_sids_to_unixids,
+ .allocate_id = idmap_ldap_allocate_id,
+};
+
+NTSTATUS idmap_ldap_init(TALLOC_CTX *);
+NTSTATUS idmap_ldap_init(TALLOC_CTX *ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "ldap",
+ &idmap_ldap_methods);
+}
+
diff --git a/source3/winbindd/idmap_nss.c b/source3/winbindd/idmap_nss.c
new file mode 100644
index 0000000..0af2536
--- /dev/null
+++ b/source3/winbindd/idmap_nss.c
@@ -0,0 +1,446 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap NSS backend
+
+ Copyright (C) Simo Sorce 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/passwd.h"
+#include "winbindd.h"
+#include "nsswitch/winbind_client.h"
+#include "idmap.h"
+#include "lib/winbind_util.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/global_contexts.h"
+#include "messages.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+struct idmap_nss_context {
+ struct idmap_domain *dom;
+ bool use_upn;
+};
+
+static int idmap_nss_context_destructor(struct idmap_nss_context *ctx)
+{
+ if ((ctx->dom != NULL) && (ctx->dom->private_data == ctx)) {
+ ctx->dom->private_data = NULL;
+ }
+ return 0;
+}
+
+static NTSTATUS idmap_nss_context_create(TALLOC_CTX *mem_ctx,
+ struct idmap_domain *dom,
+ struct idmap_nss_context **pctx)
+{
+ struct idmap_nss_context *ctx = NULL;
+
+ ctx = talloc_zero(mem_ctx, struct idmap_nss_context);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ ctx->dom = dom;
+
+ talloc_set_destructor(ctx, idmap_nss_context_destructor);
+
+ ctx->use_upn = idmap_config_bool(dom->name, "use_upn", false);
+
+ *pctx = ctx;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_nss_get_context(struct idmap_domain *dom,
+ struct idmap_nss_context **pctx)
+{
+ struct idmap_nss_context *ctx = NULL;
+ NTSTATUS status;
+
+ if (dom->private_data != NULL) {
+ *pctx = talloc_get_type_abort(dom->private_data,
+ struct idmap_nss_context);
+ return NT_STATUS_OK;
+ }
+
+ status = idmap_nss_context_create(dom, dom, &ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_WARNING("idmap_nss_context_create failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ dom->private_data = ctx;
+ *pctx = ctx;
+ return NT_STATUS_OK;
+}
+
+static bool idmap_nss_msg_filter(struct messaging_rec *rec, void *private_data)
+{
+ struct idmap_domain *dom = talloc_get_type_abort(private_data,
+ struct idmap_domain);
+ struct idmap_nss_context *ctx = NULL;
+ NTSTATUS status;
+ bool ret;
+
+ if (rec->msg_type == MSG_SMB_CONF_UPDATED) {
+ ret = lp_load_global(get_dyn_CONFIGFILE());
+ if (!ret) {
+ DBG_WARNING("Failed to reload configuration\n");
+ return false;
+ }
+
+ status = idmap_nss_get_context(dom, &ctx);
+ if (NT_STATUS_IS_ERR(status)) {
+ DBG_WARNING("Failed to get idmap nss context: %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ ctx->use_upn = idmap_config_bool(dom->name, "use_upn", false);
+ }
+
+ return false;
+}
+
+/*****************************
+ Initialise idmap database.
+*****************************/
+
+static NTSTATUS idmap_nss_int_init(struct idmap_domain *dom)
+{
+ struct idmap_nss_context *ctx = NULL;
+ NTSTATUS status;
+ struct messaging_context *msg_ctx = global_messaging_context();
+ struct tevent_req *req = NULL;
+
+ status = idmap_nss_context_create(dom, dom, &ctx);
+ if (NT_STATUS_IS_ERR(status)) {
+ return status;
+ }
+
+ dom->private_data = ctx;
+
+ req = messaging_filtered_read_send(
+ dom,
+ messaging_tevent_context(msg_ctx),
+ msg_ctx,
+ idmap_nss_msg_filter,
+ dom);
+ if (req == NULL) {
+ DBG_WARNING("messaging_filtered_read_send failed\n");
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ return status;
+}
+
+static NTSTATUS idmap_nss_lookup_name(const char *namespace,
+ const char *username,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ bool ret;
+
+ /*
+ * By default calls to winbindd are disabled
+ * the following call will not recurse so this is safe
+ */
+ (void)winbind_on();
+ ret = winbind_lookup_name(namespace, username, sid, type);
+ (void)winbind_off();
+
+ if (!ret) {
+ DBG_NOTICE("Failed to lookup name [%s] in namespace [%s]\n",
+ username, namespace);
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_nss_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ struct idmap_nss_context *ctx = NULL;
+ NTSTATUS status;
+ int i;
+
+ status = idmap_nss_get_context(dom, &ctx);
+ if (NT_STATUS_IS_ERR(status)) {
+ DBG_WARNING("Failed to get idmap nss context: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ struct passwd *pw;
+ struct group *gr;
+ const char *name;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+
+ switch (ids[i]->xid.type) {
+ case ID_TYPE_UID:
+ errno = 0;
+ pw = getpwuid((uid_t)ids[i]->xid.id);
+ if (!pw) {
+ DBG_DEBUG("getpwuid(%lu) failed: %s\n",
+ (unsigned long)ids[i]->xid.id,
+ errno != 0
+ ? strerror(errno)
+ : "not found");
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+ name = pw->pw_name;
+ break;
+ case ID_TYPE_GID:
+ errno = 0;
+ gr = getgrgid((gid_t)ids[i]->xid.id);
+ if (!gr) {
+ DBG_DEBUG("getgrgid(%lu) failed: %s\n",
+ (unsigned long)ids[i]->xid.id,
+ errno != 0
+ ? strerror(errno)
+ : "not found");
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+ name = gr->gr_name;
+ break;
+ default: /* ?? */
+ DBG_WARNING("Unexpected xid type %d\n",
+ ids[i]->xid.type);
+ ids[i]->status = ID_UNKNOWN;
+ continue;
+ }
+
+ /* Lookup name from PDC using lsa_lookup_names() */
+ if (ctx->use_upn) {
+ char *p = NULL;
+ const char *namespace = NULL;
+ const char *domname = NULL;
+ const char *domuser = NULL;
+
+ p = strstr(name, lp_winbind_separator());
+ if (p != NULL) {
+ *p = '\0';
+ domname = name;
+ namespace = domname;
+ domuser = p + 1;
+ } else {
+ p = strchr(name, '@');
+ if (p != NULL) {
+ *p = '\0';
+ namespace = p + 1;
+ domname = "";
+ domuser = name;
+ } else {
+ namespace = dom->name;
+ domuser = name;
+ }
+ }
+
+ DBG_DEBUG("Using namespace [%s] from UPN instead "
+ "of [%s] to lookup the name [%s]\n",
+ namespace, dom->name, domuser);
+
+ status = idmap_nss_lookup_name(namespace,
+ domuser,
+ &sid,
+ &type);
+ } else {
+ status = idmap_nss_lookup_name(dom->name,
+ name,
+ &sid,
+ &type);
+ }
+
+ if (NT_STATUS_IS_ERR(status)) {
+ /*
+ * TODO: how do we know if the name is really
+ * not mapped, or something just failed ?
+ */
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ switch (type) {
+ case SID_NAME_USER:
+ if (ids[i]->xid.type == ID_TYPE_UID) {
+ sid_copy(ids[i]->sid, &sid);
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ if (ids[i]->xid.type == ID_TYPE_GID) {
+ sid_copy(ids[i]->sid, &sid);
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+
+ default:
+ ids[i]->status = ID_UNKNOWN;
+ break;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_nss_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ struct idmap_nss_context *ctx = NULL;
+ NTSTATUS status;
+ int i;
+
+ status = idmap_nss_get_context(dom, &ctx);
+ if (NT_STATUS_IS_ERR(status)) {
+ DBG_WARNING("Failed to get idmap nss context: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ struct group *gr;
+ enum lsa_SidType type;
+ const char *_domain = NULL;
+ const char *_name = NULL;
+ char *domain = NULL;
+ char *name = NULL;
+ char *fqdn = NULL;
+ char *sname = NULL;
+ bool ret;
+
+ /* by default calls to winbindd are disabled
+ the following call will not recurse so this is safe */
+ (void)winbind_on();
+ ret = winbind_lookup_sid(talloc_tos(),
+ ids[i]->sid,
+ &_domain,
+ &_name,
+ &type);
+ (void)winbind_off();
+ if (!ret) {
+ /* TODO: how do we know if the name is really not mapped,
+ * or something just failed ? */
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ domain = discard_const_p(char, _domain);
+ name = discard_const_p(char, _name);
+
+ if (!strequal(domain, dom->name)) {
+ struct dom_sid_buf buf;
+ DBG_ERR("DOMAIN[%s] ignoring SID[%s] belongs to %s [%s\\%s]\n",
+ dom->name, dom_sid_str_buf(ids[i]->sid, &buf),
+ sid_type_lookup(type), domain, name);
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ if (ctx->use_upn) {
+ fqdn = talloc_asprintf(talloc_tos(),
+ "%s%s%s",
+ domain,
+ lp_winbind_separator(),
+ name);
+ if (fqdn == NULL) {
+ DBG_ERR("No memory\n");
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+ DBG_DEBUG("Using UPN [%s] instead of plain name [%s]\n",
+ fqdn, name);
+ sname = fqdn;
+ } else {
+ sname = name;
+ }
+
+ switch (type) {
+ case SID_NAME_USER: {
+ struct passwd *pw;
+
+ /* this will find also all lower case name and use username level */
+ pw = Get_Pwnam_alloc(talloc_tos(), sname);
+ if (pw) {
+ ids[i]->xid.id = pw->pw_uid;
+ ids[i]->xid.type = ID_TYPE_UID;
+ ids[i]->status = ID_MAPPED;
+ }
+ TALLOC_FREE(pw);
+ break;
+ }
+
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+
+ gr = getgrnam(sname);
+ if (gr) {
+ ids[i]->xid.id = gr->gr_gid;
+ ids[i]->xid.type = ID_TYPE_GID;
+ ids[i]->status = ID_MAPPED;
+ }
+ break;
+
+ default:
+ ids[i]->status = ID_UNKNOWN;
+ break;
+ }
+ TALLOC_FREE(domain);
+ TALLOC_FREE(name);
+ TALLOC_FREE(fqdn);
+ }
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Close the idmap tdb instance
+**********************************/
+
+static const struct idmap_methods nss_methods = {
+ .init = idmap_nss_int_init,
+ .unixids_to_sids = idmap_nss_unixids_to_sids,
+ .sids_to_unixids = idmap_nss_sids_to_unixids,
+};
+
+NTSTATUS idmap_nss_init(TALLOC_CTX *mem_ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "nss", &nss_methods);
+}
diff --git a/source3/winbindd/idmap_passdb.c b/source3/winbindd/idmap_passdb.c
new file mode 100644
index 0000000..ab02119
--- /dev/null
+++ b/source3/winbindd/idmap_passdb.c
@@ -0,0 +1,87 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap PASSDB backend
+
+ Copyright (C) Simo Sorce 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "idmap.h"
+#include "passdb.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+/*****************************
+ Initialise idmap database.
+*****************************/
+
+static NTSTATUS idmap_pdb_init(struct idmap_domain *dom)
+{
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_pdb_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ int i;
+
+ for (i = 0; ids[i]; i++) {
+ /* unmapped by default */
+ ids[i]->status = ID_UNMAPPED;
+
+ if (pdb_id_to_sid(&ids[i]->xid, ids[i]->sid)) {
+ ids[i]->status = ID_MAPPED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_pdb_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ int i;
+
+ for (i = 0; ids[i]; i++) {
+ if (pdb_sid_to_id(ids[i]->sid, &ids[i]->xid)) {
+ ids[i]->status = ID_MAPPED;
+ } else {
+ /* Query Failed */
+ ids[i]->status = ID_UNMAPPED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static const struct idmap_methods passdb_methods = {
+ .init = idmap_pdb_init,
+ .unixids_to_sids = idmap_pdb_unixids_to_sids,
+ .sids_to_unixids = idmap_pdb_sids_to_unixids,
+};
+
+NTSTATUS idmap_passdb_init(TALLOC_CTX *mem_ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "passdb", &passdb_methods);
+}
diff --git a/source3/winbindd/idmap_proto.h b/source3/winbindd/idmap_proto.h
new file mode 100644
index 0000000..adc0443
--- /dev/null
+++ b/source3/winbindd/idmap_proto.h
@@ -0,0 +1,69 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * ID Mapping
+ *
+ * Copyright (C) Tim Potter 2000
+ * Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ * Copyright (C) Simo Sorce 2003-2007
+ * Copyright (C) Jeremy Allison 2006
+ * Copyright (C) Michael Adam 2009-2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _WINBINDD_IDMAP_PROTO_H_
+#define _WINBINDD_IDMAP_PROTO_H_
+
+/* The following definitions come from winbindd/idmap.c */
+
+bool idmap_is_offline(void);
+NTSTATUS smb_register_idmap(int version, const char *name,
+ const struct idmap_methods *methods);
+void idmap_close(void);
+NTSTATUS idmap_allocate_uid(struct unixid *id);
+NTSTATUS idmap_allocate_gid(struct unixid *id);
+NTSTATUS idmap_backend_unixids_to_sids(struct id_map **maps,
+ const char *domain_name,
+ struct dom_sid domain_sid);
+struct idmap_domain *idmap_find_domain(const char *domname);
+
+/* The following definitions come from winbindd/idmap_nss.c */
+
+NTSTATUS idmap_nss_init(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from winbindd/idmap_passdb.c */
+
+NTSTATUS idmap_passdb_init(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from winbindd/idmap_tdb.c */
+
+NTSTATUS idmap_tdb_init(TALLOC_CTX *mem_ctx);
+
+/* The following definitions come from winbindd/idmap_util.c */
+
+bool idmap_unix_id_is_in_range(uint32_t id, struct idmap_domain *dom);
+struct id_map *idmap_find_map_by_id(struct id_map **maps, enum id_type type,
+ uint32_t id);
+struct id_map *idmap_find_map_by_sid(struct id_map **maps, struct dom_sid *sid);
+char *idmap_fetch_secret(const char *backend, const char *domain,
+ const char *identity);
+
+struct id_map **id_map_ptrs_init(TALLOC_CTX *mem_ctx, size_t num_ids);
+
+/* max number of ids requested per LDAP batch query */
+#define IDMAP_LDAP_MAX_IDS 30
+
+NTSTATUS idmap_ad_nss_init(TALLOC_CTX *mem_ctx);
+
+#endif /* _WINBINDD_IDMAP_PROTO_H_ */
diff --git a/source3/winbindd/idmap_rfc2307.c b/source3/winbindd/idmap_rfc2307.c
new file mode 100644
index 0000000..2b32238
--- /dev/null
+++ b/source3/winbindd/idmap_rfc2307.c
@@ -0,0 +1,848 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Id mapping using LDAP records as defined in RFC 2307
+ *
+ * The SID<->uid/gid mapping is performed in two steps: 1) Query the
+ * AD server for the name<->sid mapping. 2) Query an LDAP server
+ * according to RFC 2307 for the name<->uid/gid mapping.
+ *
+ * Copyright (C) Christof Schmitt 2012,2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "winbindd_ads.h"
+#include "idmap.h"
+#include "smbldap.h"
+#include "nsswitch/winbind_client.h"
+#include "lib/winbind_util.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/global_contexts.h"
+
+/*
+ * Config and connection info per domain.
+ */
+struct idmap_rfc2307_context {
+ const char *bind_path_user;
+ const char *bind_path_group;
+ const char *ldap_domain;
+ bool user_cn;
+ const char *realm;
+
+ /*
+ * Pointer to ldap struct in ads or smbldap_state, has to be
+ * updated after connecting to server
+ */
+ LDAP *ldap;
+
+ /* Optional function to check connection to server */
+ NTSTATUS (*check_connection)(struct idmap_domain *dom);
+
+ /* Issue ldap query */
+ NTSTATUS (*search)(struct idmap_rfc2307_context *ctx,
+ const char *bind_path, const char *expr,
+ const char **attrs, LDAPMessage **res);
+
+ /* Access to LDAP in AD server */
+ ADS_STRUCT *ads;
+
+ /* Access to stand-alone LDAP server */
+ struct smbldap_state *smbldap_state;
+};
+
+/*
+ * backend functions for LDAP queries through ADS
+ */
+
+static NTSTATUS idmap_rfc2307_ads_check_connection(struct idmap_domain *dom)
+{
+ struct idmap_rfc2307_context *ctx;
+ const char *dom_name = dom->name;
+ ADS_STATUS status;
+
+ DEBUG(10, ("ad_idmap_cached_connection: called for domain '%s'\n",
+ dom->name));
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_rfc2307_context);
+ dom_name = ctx->ldap_domain ? ctx->ldap_domain : dom->name;
+
+ status = ads_idmap_cached_connection(dom_name, ctx, &ctx->ads);
+ if (ADS_ERR_OK(status)) {
+ ctx->ldap = ctx->ads->ldap.ld;
+ } else {
+ DEBUG(1, ("Could not connect to domain %s: %s\n", dom->name,
+ ads_errstr(status)));
+ }
+
+ return ads_ntstatus(status);
+}
+
+static NTSTATUS idmap_rfc2307_ads_search(struct idmap_rfc2307_context *ctx,
+ const char *bind_path,
+ const char *expr,
+ const char **attrs,
+ LDAPMessage **result)
+{
+ ADS_STATUS status;
+
+ status = ads_do_search_retry(ctx->ads, bind_path,
+ LDAP_SCOPE_SUBTREE, expr, attrs, result);
+
+ if (!ADS_ERR_OK(status)) {
+ return ads_ntstatus(status);
+ }
+
+ ctx->ldap = ctx->ads->ldap.ld;
+ return ads_ntstatus(status);
+}
+
+static NTSTATUS idmap_rfc2307_init_ads(struct idmap_rfc2307_context *ctx,
+ const char *domain_name)
+{
+ const char *ldap_domain;
+
+ ctx->search = idmap_rfc2307_ads_search;
+ ctx->check_connection = idmap_rfc2307_ads_check_connection;
+
+ ldap_domain = idmap_config_const_string(domain_name, "ldap_domain",
+ NULL);
+ if (ldap_domain) {
+ ctx->ldap_domain = talloc_strdup(ctx, ldap_domain);
+ if (ctx->ldap_domain == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * backend function for LDAP queries through stand-alone LDAP server
+ */
+
+static NTSTATUS idmap_rfc2307_ldap_search(struct idmap_rfc2307_context *ctx,
+ const char *bind_path,
+ const char *expr,
+ const char **attrs,
+ LDAPMessage **result)
+{
+ int ret;
+
+ ret = smbldap_search(ctx->smbldap_state, bind_path, LDAP_SCOPE_SUBTREE,
+ expr, attrs, 0, result);
+ ctx->ldap = smbldap_get_ldap(ctx->smbldap_state);
+
+ if (ret == LDAP_SUCCESS) {
+ return NT_STATUS_OK;
+ }
+
+ return NT_STATUS_LDAP(ret);
+}
+
+static bool idmap_rfc2307_get_uint32(LDAP *ldap, LDAPMessage *entry,
+ const char *field, uint32_t *value)
+{
+ bool b;
+ char str[20];
+
+ b = smbldap_get_single_attribute(ldap, entry, field, str, sizeof(str));
+
+ if (b) {
+ *value = atoi(str);
+ }
+
+ return b;
+}
+
+static NTSTATUS idmap_rfc2307_init_ldap(struct idmap_rfc2307_context *ctx,
+ const char *domain_name)
+{
+ NTSTATUS ret;
+ char *url;
+ char *secret = NULL;
+ const char *ldap_url, *user_dn;
+ TALLOC_CTX *mem_ctx = ctx;
+
+ ldap_url = idmap_config_const_string(domain_name, "ldap_url", NULL);
+ if (!ldap_url) {
+ DEBUG(1, ("ERROR: missing idmap ldap url\n"));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ url = talloc_strdup(talloc_tos(), ldap_url);
+
+ user_dn = idmap_config_const_string(domain_name, "ldap_user_dn", NULL);
+ if (user_dn) {
+ secret = idmap_fetch_secret("ldap", domain_name, user_dn);
+ if (!secret) {
+ ret = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+ }
+
+ /* assume anonymous if we don't have a specified user */
+ ret = smbldap_init(mem_ctx, global_event_context(), url,
+ (user_dn == NULL), user_dn, secret,
+ &ctx->smbldap_state);
+ BURN_FREE_STR(secret);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("ERROR: smbldap_init (%s) failed!\n", url));
+ goto done;
+ }
+
+ ctx->search = idmap_rfc2307_ldap_search;
+
+done:
+ talloc_free(url);
+ return ret;
+}
+
+/*
+ * common code for stand-alone LDAP and ADS
+ */
+
+static void idmap_rfc2307_map_sid_results(struct idmap_rfc2307_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct id_map **ids,
+ LDAPMessage *result,
+ const char *dom_name,
+ const char **attrs, int type)
+{
+ int count, i;
+ LDAPMessage *entry;
+
+ count = ldap_count_entries(ctx->ldap, result);
+
+ for (i = 0; i < count; i++) {
+ char *name;
+ struct dom_sid sid;
+ enum lsa_SidType lsa_type;
+ struct id_map *map;
+ uint32_t id;
+ bool b;
+
+ if (i == 0) {
+ entry = ldap_first_entry(ctx->ldap, result);
+ } else {
+ entry = ldap_next_entry(ctx->ldap, entry);
+ }
+ if (!entry) {
+ DEBUG(2, ("Unable to fetch entry.\n"));
+ break;
+ }
+
+ name = smbldap_talloc_single_attribute(ctx->ldap, entry,
+ attrs[0], mem_ctx);
+ if (!name) {
+ DEBUG(1, ("Could not get user name\n"));
+ continue;
+ }
+
+ b = idmap_rfc2307_get_uint32(ctx->ldap, entry, attrs[1], &id);
+ if (!b) {
+ DEBUG(1, ("Could not pull id for record %s\n", name));
+ continue;
+ }
+
+ map = idmap_find_map_by_id(ids, type, id);
+ if (!map) {
+ DEBUG(1, ("Could not find id %d, name %s\n", id, name));
+ continue;
+ }
+
+ if (ctx->realm != NULL) {
+ /* Strip @realm from user or group name */
+ char *delim;
+
+ delim = strchr(name, '@');
+ if (delim) {
+ *delim = '\0';
+ }
+ }
+
+ /* by default calls to winbindd are disabled
+ the following call will not recurse so this is safe */
+ (void)winbind_on();
+ /* Lookup name from PDC using lsa_lookup_names() */
+ b = winbind_lookup_name(dom_name, name, &sid, &lsa_type);
+ (void)winbind_off();
+
+ if (!b) {
+ DEBUG(1, ("SID lookup failed for id %d, %s\n",
+ id, name));
+ continue;
+ }
+
+ if (type == ID_TYPE_UID && lsa_type != SID_NAME_USER) {
+ DEBUG(1, ("Wrong type %d for user name %s\n",
+ type, name));
+ continue;
+ }
+
+ if (type == ID_TYPE_GID && lsa_type != SID_NAME_DOM_GRP &&
+ lsa_type != SID_NAME_ALIAS &&
+ lsa_type != SID_NAME_WKN_GRP) {
+ DEBUG(1, ("Wrong type %d for group name %s\n",
+ type, name));
+ continue;
+ }
+
+ map->status = ID_MAPPED;
+ sid_copy(map->sid, &sid);
+ }
+}
+
+/*
+ * Map unixids to names and then to sids.
+ */
+static NTSTATUS idmap_rfc2307_unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_rfc2307_context *ctx;
+ char *fltr_usr = NULL, *fltr_grp = NULL;
+ TALLOC_CTX *mem_ctx;
+ int cnt_usr = 0, cnt_grp = 0, idx = 0, bidx = 0;
+ LDAPMessage *result = NULL;
+ NTSTATUS ret;
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_rfc2307_context);
+ mem_ctx = talloc_new(ctx);
+ if (!mem_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ctx->check_connection) {
+ ret = ctx->check_connection(dom);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto out;
+ }
+ }
+
+again:
+ bidx = idx;
+
+ if (!fltr_usr) {
+ /* prepare new user query, see getpwuid() in RFC2307 */
+ fltr_usr = talloc_asprintf(mem_ctx,
+ "(&(objectClass=posixAccount)(|");
+ }
+
+ if (!fltr_grp) {
+ /* prepare new group query, see getgrgid() in RFC2307 */
+ fltr_grp = talloc_asprintf(mem_ctx,
+ "(&(objectClass=posixGroup)(|");
+ }
+
+ if (!fltr_usr || !fltr_grp) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ while (cnt_usr < IDMAP_LDAP_MAX_IDS &&
+ cnt_grp < IDMAP_LDAP_MAX_IDS && ids[idx]) {
+
+ switch (ids[idx]->xid.type) {
+ case ID_TYPE_UID:
+ fltr_usr = talloc_asprintf_append_buffer(fltr_usr,
+ "(uidNumber=%d)", ids[idx]->xid.id);
+ cnt_usr++;
+ break;
+ case ID_TYPE_GID:
+ fltr_grp = talloc_asprintf_append_buffer(fltr_grp,
+ "(gidNumber=%d)", ids[idx]->xid.id);
+ cnt_grp++;
+ break;
+ default:
+ DEBUG(3, ("Error: unknown ID type %d\n",
+ ids[idx]->xid.type));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto out;
+ }
+
+ if (!fltr_usr || !fltr_grp) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ idx++;
+ }
+
+ if (cnt_usr == IDMAP_LDAP_MAX_IDS || (cnt_usr != 0 && !ids[idx])) {
+ const char *attrs[] = { NULL, /* uid or cn */
+ "uidNumber",
+ NULL };
+
+ fltr_usr = talloc_strdup_append(fltr_usr, "))");
+ if (!fltr_usr) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ attrs[0] = ctx->user_cn ? "cn" : "uid";
+ ret = ctx->search(ctx, ctx->bind_path_user, fltr_usr, attrs,
+ &result);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto out;
+ }
+
+ idmap_rfc2307_map_sid_results(ctx, mem_ctx, &ids[bidx], result,
+ dom->name, attrs, ID_TYPE_UID);
+ cnt_usr = 0;
+ TALLOC_FREE(fltr_usr);
+ }
+
+ if (cnt_grp == IDMAP_LDAP_MAX_IDS || (cnt_grp != 0 && !ids[idx])) {
+ const char *attrs[] = { "cn", "gidNumber", NULL };
+
+ fltr_grp = talloc_strdup_append(fltr_grp, "))");
+ if (!fltr_grp) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ ret = ctx->search(ctx, ctx->bind_path_group, fltr_grp, attrs,
+ &result);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto out;
+ }
+
+ idmap_rfc2307_map_sid_results(ctx, mem_ctx, &ids[bidx], result,
+ dom->name, attrs, ID_TYPE_GID);
+ cnt_grp = 0;
+ TALLOC_FREE(fltr_grp);
+ }
+
+ if (ids[idx]) {
+ goto again;
+ }
+
+ ret = NT_STATUS_OK;
+
+out:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+struct idmap_rfc2307_map {
+ struct id_map *map;
+ const char *name;
+ enum id_type type;
+};
+
+/*
+ * Lookup names for SIDS and store the data in the local mapping
+ * array.
+ */
+static NTSTATUS idmap_rfc_2307_sids_to_names(TALLOC_CTX *mem_ctx,
+ struct id_map **ids,
+ struct idmap_rfc2307_map *maps,
+ struct idmap_rfc2307_context *ctx)
+{
+ int i;
+
+ for (i = 0; ids[i]; i++) {
+ const char *domain, *name;
+ enum lsa_SidType lsa_type;
+ struct id_map *id = ids[i];
+ struct idmap_rfc2307_map *map = &maps[i];
+ struct dom_sid_buf buf;
+ bool b;
+
+ /* by default calls to winbindd are disabled
+ the following call will not recurse so this is safe */
+ (void)winbind_on();
+ b = winbind_lookup_sid(mem_ctx, ids[i]->sid, &domain, &name,
+ &lsa_type);
+ (void)winbind_off();
+
+ if (!b) {
+ DEBUG(1, ("Lookup sid %s failed.\n",
+ dom_sid_str_buf(ids[i]->sid, &buf)));
+ continue;
+ }
+
+ switch(lsa_type) {
+ case SID_NAME_USER:
+ id->xid.type = map->type = ID_TYPE_UID;
+ if (ctx->user_cn && ctx->realm != NULL) {
+ name = talloc_asprintf(mem_ctx, "%s@%s",
+ name, ctx->realm);
+ }
+ id->xid.type = map->type = ID_TYPE_UID;
+ break;
+
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ if (ctx->realm != NULL) {
+ name = talloc_asprintf(mem_ctx, "%s@%s",
+ name, ctx->realm);
+ }
+ id->xid.type = map->type = ID_TYPE_GID;
+ break;
+
+ default:
+ DEBUG(1, ("Unknown lsa type %d for sid %s\n",
+ lsa_type,
+ dom_sid_str_buf(id->sid, &buf)));
+ id->status = ID_UNMAPPED;
+ continue;
+ }
+
+ map->map = id;
+ id->status = ID_UNKNOWN;
+ map->name = strupper_talloc(mem_ctx, name);
+
+ if (!map->name) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Find id_map entry by looking up the name in the internal
+ * mapping array.
+ */
+static struct id_map* idmap_rfc2307_find_map(struct idmap_rfc2307_map *maps,
+ enum id_type type,
+ const char *name)
+{
+ int i;
+
+ DEBUG(10, ("Looking for name %s, type %d\n", name, type));
+
+ for (i = 0; maps[i].map != NULL; i++) {
+ DEBUG(10, ("Entry %d: name %s, type %d\n",
+ i, maps[i].name, maps[i].type));
+ if (type == maps[i].type && strcmp(name, maps[i].name) == 0) {
+ return maps[i].map;
+ }
+ }
+
+ return NULL;
+}
+
+static void idmap_rfc2307_map_xid_results(struct idmap_rfc2307_context *ctx,
+ TALLOC_CTX *mem_ctx,
+ struct idmap_rfc2307_map *maps,
+ LDAPMessage *result,
+ struct idmap_domain *dom,
+ const char **attrs, enum id_type type)
+{
+ int count, i;
+ LDAPMessage *entry;
+
+ count = ldap_count_entries(ctx->ldap, result);
+
+ for (i = 0; i < count; i++) {
+ uint32_t id;
+ char *name;
+ bool b;
+ struct id_map *id_map;
+
+ if (i == 0) {
+ entry = ldap_first_entry(ctx->ldap, result);
+ } else {
+ entry = ldap_next_entry(ctx->ldap, entry);
+ }
+ if (!entry) {
+ DEBUG(2, ("Unable to fetch entry.\n"));
+ break;
+ }
+
+ name = smbldap_talloc_single_attribute(ctx->ldap, entry,
+ attrs[0], mem_ctx);
+ if (!name) {
+ DEBUG(1, ("Could not get user name\n"));
+ continue;
+ }
+
+ b = idmap_rfc2307_get_uint32(ctx->ldap, entry, attrs[1], &id);
+ if (!b) {
+ DEBUG(5, ("Could not pull id for record %s\n", name));
+ continue;
+ }
+
+ if (!idmap_unix_id_is_in_range(id, dom)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u).\n",
+ id, dom->low_id, dom->high_id));
+ continue;
+ }
+
+ if (!strupper_m(name)) {
+ DEBUG(5, ("Could not convert %s to uppercase\n", name));
+ continue;
+ }
+ id_map = idmap_rfc2307_find_map(maps, type, name);
+ if (!id_map) {
+ DEBUG(0, ("Could not find mapping entry for name %s\n",
+ name));
+ continue;
+ }
+
+ id_map->xid.id = id;
+ id_map->status = ID_MAPPED;
+ }
+}
+
+/*
+ * Map sids to names and then to unixids.
+ */
+static NTSTATUS idmap_rfc2307_sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_rfc2307_context *ctx;
+ TALLOC_CTX *mem_ctx;
+ struct idmap_rfc2307_map *int_maps;
+ int cnt_usr = 0, cnt_grp = 0, idx = 0;
+ char *fltr_usr = NULL, *fltr_grp = NULL;
+ NTSTATUS ret;
+ int i;
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_rfc2307_context);
+ mem_ctx = talloc_new(talloc_tos());
+ if (!mem_ctx) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (ctx->check_connection) {
+ ret = ctx->check_connection(dom);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto out;
+ }
+ }
+
+ for (i = 0; ids[i]; i++);
+ int_maps = talloc_zero_array(mem_ctx, struct idmap_rfc2307_map, i);
+ if (!int_maps) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = idmap_rfc_2307_sids_to_names(mem_ctx, ids, int_maps, ctx);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto out;
+ }
+
+again:
+ if (!fltr_usr) {
+ /* prepare new user query, see getpwuid() in RFC2307 */
+ fltr_usr = talloc_asprintf(mem_ctx,
+ "(&(objectClass=posixAccount)(|");
+ }
+
+ if (!fltr_grp) {
+ /* prepare new group query, see getgrgid() in RFC2307 */
+ fltr_grp = talloc_asprintf(mem_ctx,
+ "(&(objectClass=posixGroup)(|");
+ }
+
+ if (!fltr_usr || !fltr_grp) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ while (cnt_usr < IDMAP_LDAP_MAX_IDS &&
+ cnt_grp < IDMAP_LDAP_MAX_IDS && ids[idx]) {
+ struct id_map *id = ids[idx];
+ struct idmap_rfc2307_map *map = &int_maps[idx];
+
+ switch(id->xid.type) {
+ case ID_TYPE_UID:
+ fltr_usr = talloc_asprintf_append_buffer(fltr_usr,
+ "(%s=%s)", (ctx->user_cn ? "cn" : "uid"),
+ map->name);
+ cnt_usr++;
+ break;
+
+ case ID_TYPE_GID:
+ fltr_grp = talloc_asprintf_append_buffer(fltr_grp,
+ "(cn=%s)", map->name);
+ cnt_grp++;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!fltr_usr || !fltr_grp) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ idx++;
+ }
+
+ if (cnt_usr == IDMAP_LDAP_MAX_IDS || (cnt_usr != 0 && !ids[idx])) {
+ const char *attrs[] = { NULL, /* uid or cn */
+ "uidNumber",
+ NULL };
+ LDAPMessage *result;
+
+ fltr_usr = talloc_strdup_append(fltr_usr, "))");
+ if (!fltr_usr) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ attrs[0] = ctx->user_cn ? "cn" : "uid";
+ ret = ctx->search(ctx, ctx->bind_path_user, fltr_usr, attrs,
+ &result);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto out;
+ }
+
+ idmap_rfc2307_map_xid_results(ctx, mem_ctx, int_maps,
+ result, dom, attrs, ID_TYPE_UID);
+
+ cnt_usr = 0;
+ TALLOC_FREE(fltr_usr);
+ }
+
+ if (cnt_grp == IDMAP_LDAP_MAX_IDS || (cnt_grp != 0 && !ids[idx])) {
+ const char *attrs[] = {"cn", "gidNumber", NULL };
+ LDAPMessage *result;
+
+ fltr_grp = talloc_strdup_append(fltr_grp, "))");
+ if (!fltr_grp) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ ret = ctx->search(ctx, ctx->bind_path_group, fltr_grp, attrs,
+ &result);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto out;
+ }
+
+ idmap_rfc2307_map_xid_results(ctx, mem_ctx, int_maps, result,
+ dom, attrs, ID_TYPE_GID);
+ cnt_grp = 0;
+ TALLOC_FREE(fltr_grp);
+ }
+
+ if (ids[idx]) {
+ goto again;
+ }
+
+ ret = NT_STATUS_OK;
+
+out:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+static int idmap_rfc2307_context_destructor(struct idmap_rfc2307_context *ctx)
+{
+ TALLOC_FREE(ctx->ads);
+
+ if (ctx->smbldap_state != NULL) {
+ smbldap_free_struct(&ctx->smbldap_state);
+ }
+
+ return 0;
+}
+
+static NTSTATUS idmap_rfc2307_initialize(struct idmap_domain *domain)
+{
+ struct idmap_rfc2307_context *ctx;
+ const char *bind_path_user, *bind_path_group, *ldap_server, *realm;
+ NTSTATUS status;
+
+ ctx = talloc_zero(domain, struct idmap_rfc2307_context);
+ if (ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ talloc_set_destructor(ctx, idmap_rfc2307_context_destructor);
+
+ bind_path_user = idmap_config_const_string(
+ domain->name, "bind_path_user", NULL);
+ if (bind_path_user == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err;
+ }
+ ctx->bind_path_user = talloc_strdup(ctx, bind_path_user);
+ if (ctx->bind_path_user == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ bind_path_group = idmap_config_const_string(
+ domain->name, "bind_path_group", NULL);
+ if (bind_path_group == NULL) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err;
+ }
+ ctx->bind_path_group = talloc_strdup(ctx, bind_path_group);
+ if (ctx->bind_path_group == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+
+ ldap_server = idmap_config_const_string(
+ domain->name, "ldap_server", NULL);
+ if (!ldap_server) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto err;
+ }
+
+ if (strcmp(ldap_server, "stand-alone") == 0) {
+ status = idmap_rfc2307_init_ldap(ctx, domain->name);
+
+ } else if (strcmp(ldap_server, "ad") == 0) {
+ status = idmap_rfc2307_init_ads(ctx, domain->name);
+
+ } else {
+ status = NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto err;
+ }
+
+ realm = idmap_config_const_string(domain->name, "realm", NULL);
+ if (realm) {
+ ctx->realm = talloc_strdup(ctx, realm);
+ if (ctx->realm == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto err;
+ }
+ }
+
+ ctx->user_cn = idmap_config_bool(domain->name, "user_cn", false);
+
+ domain->private_data = ctx;
+ return NT_STATUS_OK;
+
+err:
+ talloc_free(ctx);
+ return status;
+}
+
+static const struct idmap_methods rfc2307_methods = {
+ .init = idmap_rfc2307_initialize,
+ .unixids_to_sids = idmap_rfc2307_unixids_to_sids,
+ .sids_to_unixids = idmap_rfc2307_sids_to_unixids,
+};
+
+static_decl_idmap;
+NTSTATUS idmap_rfc2307_init(TALLOC_CTX *ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "rfc2307",
+ &rfc2307_methods);
+}
diff --git a/source3/winbindd/idmap_rid.c b/source3/winbindd/idmap_rid.c
new file mode 100644
index 0000000..4fecf22
--- /dev/null
+++ b/source3/winbindd/idmap_rid.c
@@ -0,0 +1,182 @@
+/*
+ * idmap_rid: static map between Active Directory/NT RIDs and RFC 2307 accounts
+ * Copyright (C) Guenther Deschner, 2004
+ * Copyright (C) Sumit Bose, 2004
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "idmap.h"
+#include "../libcli/security/dom_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+struct idmap_rid_context {
+ uint32_t base_rid;
+};
+
+/******************************************************************************
+ compat params can't be used because of the completely different way
+ we support multiple domains in the new idmap
+ *****************************************************************************/
+
+static NTSTATUS idmap_rid_initialize(struct idmap_domain *dom)
+{
+ struct idmap_rid_context *ctx;
+
+ ctx = talloc_zero(dom, struct idmap_rid_context);
+ if (ctx == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ctx->base_rid = idmap_config_int(dom->name, "base_rid", 0);
+
+ dom->private_data = ctx;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_rid_id_to_sid(struct idmap_domain *dom, struct id_map *map)
+{
+ struct idmap_rid_context *ctx;
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_rid_context);
+
+ /* apply filters before checking */
+ if (!idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ if (is_null_sid(&dom->dom_sid)) {
+ DBG_INFO("idmap domain '%s' without SID\n", dom->name);
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ sid_compose(map->sid, &dom->dom_sid,
+ map->xid.id - dom->low_id + ctx->base_rid);
+
+ map->status = ID_MAPPED;
+ map->xid.type = ID_TYPE_BOTH;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ Single sid to id lookup function.
+**********************************/
+
+static NTSTATUS idmap_rid_sid_to_id(struct idmap_domain *dom, struct id_map *map)
+{
+ uint32_t rid;
+ struct idmap_rid_context *ctx;
+
+ ctx = talloc_get_type(dom->private_data, struct idmap_rid_context);
+
+ sid_peek_rid(map->sid, &rid);
+ map->xid.id = rid - ctx->base_rid + dom->low_id;
+ map->xid.type = ID_TYPE_BOTH;
+
+ /* apply filters before returning result */
+
+ if (!idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id));
+ map->status = ID_UNMAPPED;
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ map->status = ID_MAPPED;
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of unix ids.
+**********************************/
+
+static NTSTATUS idmap_rid_unixids_to_sids(struct idmap_domain *dom, struct id_map **ids)
+{
+ NTSTATUS ret;
+ int i;
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+ for (i = 0; ids[i]; i++) {
+
+ ret = idmap_rid_id_to_sid(dom, ids[i]);
+
+ if (( ! NT_STATUS_IS_OK(ret)) &&
+ ( ! NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+ /* some fatal error occurred, log it */
+ DBG_NOTICE("Unexpected error resolving an ID "
+ "(%d): %s\n", ids[i]->xid.id,
+ nt_errstr(ret));
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+/**********************************
+ lookup a set of sids.
+**********************************/
+
+static NTSTATUS idmap_rid_sids_to_unixids(struct idmap_domain *dom, struct id_map **ids)
+{
+ NTSTATUS ret;
+ int i;
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+ for (i = 0; ids[i]; i++) {
+
+ ret = idmap_rid_sid_to_id(dom, ids[i]);
+
+ if (( ! NT_STATUS_IS_OK(ret)) &&
+ ( ! NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED))) {
+ struct dom_sid_buf buf;
+ /* some fatal error occurred, log it */
+ DEBUG(3, ("Unexpected error resolving a SID (%s)\n",
+ dom_sid_str_buf(ids[i]->sid, &buf)));
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static const struct idmap_methods rid_methods = {
+ .init = idmap_rid_initialize,
+ .unixids_to_sids = idmap_rid_unixids_to_sids,
+ .sids_to_unixids = idmap_rid_sids_to_unixids,
+};
+
+static_decl_idmap;
+NTSTATUS idmap_rid_init(TALLOC_CTX *ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "rid", &rid_methods);
+}
+
diff --git a/source3/winbindd/idmap_rw.c b/source3/winbindd/idmap_rw.c
new file mode 100644
index 0000000..71bfc14
--- /dev/null
+++ b/source3/winbindd/idmap_rw.c
@@ -0,0 +1,109 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * ID mapping: abstract r/w new-mapping mechanism
+ *
+ * Copyright (C) Michael Adam 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "idmap.h"
+#include "idmap_rw.h"
+#include "libcli/security/dom_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+NTSTATUS idmap_rw_new_mapping(struct idmap_domain *dom,
+ struct idmap_rw_ops *ops,
+ struct id_map *map)
+{
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ if (map == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (map->sid == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ switch (map->xid.type) {
+ case ID_TYPE_NOT_SPECIFIED:
+ /*
+ * We need to know if we need a user or group mapping.
+ * Ask the winbindd parent to provide a valid type hint.
+ */
+ DBG_INFO("%s ID_TYPE_NOT_SPECIFIED => ID_REQUIRE_TYPE\n",
+ dom_sid_str_buf(map->sid, &buf));
+ map->status = ID_REQUIRE_TYPE;
+ return NT_STATUS_SOME_NOT_MAPPED;
+
+ case ID_TYPE_BOTH:
+ /*
+ * For now we still require
+ * an explicit type as hint
+ * and don't support ID_TYPE_BOTH
+ */
+ DBG_INFO("%s ID_TYPE_BOTH => ID_REQUIRE_TYPE\n",
+ dom_sid_str_buf(map->sid, &buf));
+ map->status = ID_REQUIRE_TYPE;
+ return NT_STATUS_SOME_NOT_MAPPED;
+
+ case ID_TYPE_UID:
+ break;
+
+ case ID_TYPE_GID:
+ break;
+
+ default:
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = ops->get_new_id(dom, &map->xid);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not allocate id: %s\n", nt_errstr(status)));
+ return status;
+ }
+
+ DEBUG(10, ("Setting mapping: %s <-> %s %lu\n",
+ dom_sid_str_buf(map->sid, &buf),
+ (map->xid.type == ID_TYPE_UID) ? "UID" : "GID",
+ (unsigned long)map->xid.id));
+
+ map->status = ID_MAPPED;
+ status = ops->set_mapping(dom, map);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_COLLISION)) {
+ struct id_map *ids[2];
+ DEBUG(5, ("Mapping for %s exists - retrying to map sid\n",
+ dom_sid_str_buf(map->sid, &buf)));
+ ids[0] = map;
+ ids[1] = NULL;
+ status = dom->methods->sids_to_unixids(dom, ids);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("Could not store the new mapping: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/idmap_rw.h b/source3/winbindd/idmap_rw.h
new file mode 100644
index 0000000..72389e7
--- /dev/null
+++ b/source3/winbindd/idmap_rw.h
@@ -0,0 +1,56 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * ID mapping: abstract r/w new-mapping mechanism
+ *
+ * Copyright (C) Michael Adam 2010
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This module implements the abstract logic for creating a new
+ * SID<->Unix-ID mapping. It can be used by idmap backends that
+ * need to create mappings for unmapped SIDs upon request.
+ */
+
+#ifndef _IDMAP_RW_H_
+#define _IDMAP_RW_H_
+
+#include "includes.h"
+#include "idmap.h"
+
+struct idmap_rw_ops {
+ NTSTATUS (*get_new_id)(struct idmap_domain *dom, struct unixid *id);
+ NTSTATUS (*set_mapping)(struct idmap_domain *dom,
+ const struct id_map *map);
+};
+
+/**
+ * This is the abstract mechanism of creating a new mapping
+ * for a given SID. It is meant to be called called from an
+ * allocating backend from within idmap_<backend>_sids_to_unixids().
+ * It expects map->sid and map->xid.type to be set.
+ * Upon success, the new mapping is stored by the backend and
+ * map contains the new mapped xid.
+ *
+ * The caller has to take care of the necessary steps to
+ * guarantee atomicity of the operation, e.g. wrapping
+ * this call in a transaction if available.
+ */
+NTSTATUS idmap_rw_new_mapping(struct idmap_domain *dom,
+ struct idmap_rw_ops *ops,
+ struct id_map *map);
+
+#endif /* _IDMAP_RW_H_ */
diff --git a/source3/winbindd/idmap_script.c b/source3/winbindd/idmap_script.c
new file mode 100644
index 0000000..bb89175
--- /dev/null
+++ b/source3/winbindd/idmap_script.c
@@ -0,0 +1,650 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap script backend, used for Samba setups where you need to map SIDs to
+ specific UIDs/GIDs.
+
+ Copyright (C) Richard Sharpe 2014.
+
+ This is heavily based upon idmap_tdb2.c, which is:
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Simo Sorce 2003-2006
+ Copyright (C) Michael Adam 2009-2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "winbindd.h"
+#include "idmap.h"
+#include "idmap_rw.h"
+#include "../libcli/security/dom_sid.h"
+#include "lib/util_file.h"
+#include "lib/util/tevent_unix.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+struct idmap_script_context {
+ const char *script; /* script to provide idmaps */
+};
+
+/*
+ run a script to perform a mapping
+
+ The script should accept the following command lines:
+
+ SIDTOID S-1-xxxx -> XID:<id> | ERR:<str>
+ SIDTOID S-1-xxxx -> UID:<id> | ERR:<str>
+ SIDTOID S-1-xxxx -> GID:<id> | ERR:<str>
+ IDTOSID XID xxxx -> SID:<sid> | ERR:<str>
+ IDTOSID UID xxxx -> SID:<sid> | ERR:<str>
+ IDTOSID GID xxxx -> SID:<sid> | ERR:<str>
+
+ where XID means both a UID and a GID. This is the case for ID_TYPE_BOTH.
+
+ TODO: Needs more validation ... like that we got a UID when we asked for one.
+ */
+
+struct idmap_script_xid2sid_state {
+ char **argl;
+ size_t idx;
+ uint8_t *out;
+};
+
+static void idmap_script_xid2sid_done(struct tevent_req *subreq);
+
+static struct tevent_req *idmap_script_xid2sid_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct unixid xid, const char *script, size_t idx)
+{
+ struct tevent_req *req, *subreq;
+ struct idmap_script_xid2sid_state *state;
+ char key;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct idmap_script_xid2sid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->idx = idx;
+
+ switch (xid.type) {
+ case ID_TYPE_UID:
+ key = 'U';
+ break;
+ case ID_TYPE_GID:
+ key = 'G';
+ break;
+ case ID_TYPE_BOTH:
+ key = 'X';
+ break;
+ default:
+ DBG_WARNING("INVALID unix ID type: 0x02%x\n", xid.type);
+ tevent_req_error(req, EINVAL);
+ return tevent_req_post(req, ev);
+ }
+
+ state->argl = str_list_make_empty(state);
+ str_list_add_printf(&state->argl, "%s", script);
+ str_list_add_printf(&state->argl, "IDTOSID");
+ str_list_add_printf(&state->argl, "%cID", key);
+ str_list_add_printf(&state->argl, "%lu", (unsigned long)xid.id);
+ if (tevent_req_nomem(state->argl, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = file_ploadv_send(state, ev, state->argl, 1024);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, idmap_script_xid2sid_done, req);
+ return req;
+}
+
+static void idmap_script_xid2sid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct idmap_script_xid2sid_state *state = tevent_req_data(
+ req, struct idmap_script_xid2sid_state);
+ int ret;
+
+ ret = file_ploadv_recv(subreq, state, &state->out);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static int idmap_script_xid2sid_recv(struct tevent_req *req, size_t *idx,
+ enum id_mapping *status,
+ struct dom_sid *sid)
+{
+ struct idmap_script_xid2sid_state *state = tevent_req_data(
+ req, struct idmap_script_xid2sid_state);
+ char *out = (char *)state->out;
+ size_t out_size = talloc_get_size(out);
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+
+ if (out_size == 0) {
+ goto unmapped;
+ }
+ if (state->out[out_size-1] != '\0') {
+ goto unmapped;
+ }
+
+ *idx = state->idx;
+
+ if ((strncmp(out, "SID:S-", 6) != 0) ||
+ !dom_sid_parse(out+4, sid)) {
+ DBG_WARNING("Bad sid from script: %s\n", out);
+ goto unmapped;
+ }
+
+ *status = ID_MAPPED;
+ return 0;
+
+unmapped:
+ *sid = (struct dom_sid) {0};
+ *status = ID_UNMAPPED;
+ return 0;
+}
+
+struct idmap_script_xids2sids_state {
+ struct id_map **ids;
+ size_t num_ids;
+ size_t num_done;
+};
+
+static void idmap_script_xids2sids_done(struct tevent_req *subreq);
+
+static struct tevent_req *idmap_script_xids2sids_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct id_map **ids, size_t num_ids, const char *script)
+{
+ struct tevent_req *req;
+ struct idmap_script_xids2sids_state *state;
+ size_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct idmap_script_xids2sids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ids = ids;
+ state->num_ids = num_ids;
+
+ if (state->num_ids == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_ids; i++) {
+ struct tevent_req *subreq;
+
+ subreq = idmap_script_xid2sid_send(
+ state, ev, ids[i]->xid, script, i);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, idmap_script_xids2sids_done,
+ req);
+ }
+
+ return req;
+}
+
+static void idmap_script_xids2sids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct idmap_script_xids2sids_state *state = tevent_req_data(
+ req, struct idmap_script_xids2sids_state);
+ size_t idx = 0;
+ enum id_mapping status = ID_UNKNOWN;
+ struct dom_sid sid = {0};
+ int ret;
+
+ ret = idmap_script_xid2sid_recv(subreq, &idx, &status, &sid);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+
+ if (idx >= state->num_ids) {
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ state->ids[idx]->status = status;
+
+ state->ids[idx]->sid = dom_sid_dup(state->ids, &sid);
+ if (tevent_req_nomem(state->ids[idx]->sid, req)) {
+ return;
+ }
+
+ state->num_done += 1;
+
+ if (state->num_done >= state->num_ids) {
+ tevent_req_done(req);
+ }
+}
+
+static int idmap_script_xids2sids_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static int idmap_script_xids2sids(struct id_map **ids, size_t num_ids,
+ const char *script)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ int ret = ENOMEM;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = idmap_script_xids2sids_send(frame, ev, ids, num_ids, script);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ ret = errno;
+ goto fail;
+ }
+ ret = idmap_script_xids2sids_recv(req);
+fail:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static NTSTATUS idmap_script_unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_script_context *ctx = talloc_get_type_abort(
+ dom->private_data, struct idmap_script_context);
+ int ret;
+ size_t i, num_ids, num_mapped;
+
+ DEBUG(10, ("%s called ...\n", __func__));
+ /* Init status to avoid surprise ... */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+ num_ids = i;
+
+ ret = idmap_script_xids2sids(ids, num_ids, ctx->script);
+ if (ret != 0) {
+ DBG_DEBUG("idmap_script_xids2sids returned %s\n",
+ strerror(ret));
+ return map_nt_error_from_unix(ret);
+ }
+
+ num_mapped = 0;
+
+ for (i = 0; ids[i]; i++) {
+ if (ids[i]->status == ID_MAPPED) {
+ num_mapped += 1;
+ }
+ }
+
+ if (num_mapped == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+ if (num_mapped < num_ids) {
+ return STATUS_SOME_UNMAPPED;
+ }
+ return NT_STATUS_OK;
+}
+
+struct idmap_script_sid2xid_state {
+ char **argl;
+ size_t idx;
+ uint8_t *out;
+};
+
+static void idmap_script_sid2xid_done(struct tevent_req *subreq);
+
+static struct tevent_req *idmap_script_sid2xid_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const struct dom_sid *sid, const char *script, size_t idx)
+{
+ struct tevent_req *req, *subreq;
+ struct idmap_script_sid2xid_state *state;
+ struct dom_sid_buf sidbuf;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct idmap_script_sid2xid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->idx = idx;
+
+ state->argl = str_list_make_empty(state);
+ str_list_add_printf(&state->argl, "%s", script);
+ str_list_add_printf(&state->argl, "SIDTOID");
+ str_list_add_printf(
+ &state->argl, "%s", dom_sid_str_buf(sid, &sidbuf));
+ if (tevent_req_nomem(state->argl, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = file_ploadv_send(state, ev, state->argl, 1024);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, idmap_script_sid2xid_done, req);
+ return req;
+}
+
+static void idmap_script_sid2xid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct idmap_script_sid2xid_state *state = tevent_req_data(
+ req, struct idmap_script_sid2xid_state);
+ int ret;
+
+ ret = file_ploadv_recv(subreq, state, &state->out);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static int idmap_script_sid2xid_recv(struct tevent_req *req,
+ size_t *idx, enum id_mapping *status,
+ struct unixid *xid)
+{
+ struct idmap_script_sid2xid_state *state = tevent_req_data(
+ req, struct idmap_script_sid2xid_state);
+ char *out = (char *)state->out;
+ size_t out_size = talloc_get_size(out);
+ unsigned long v;
+ int err;
+
+ if (tevent_req_is_unix_error(req, &err)) {
+ return err;
+ }
+
+ if (out_size == 0) {
+ goto unmapped;
+ }
+ if (state->out[out_size-1] != '\0') {
+ goto unmapped;
+ }
+
+ *idx = state->idx;
+
+ if (sscanf(out, "XID:%lu\n", &v) == 1) {
+ *xid = (struct unixid) { .id = v, .type = ID_TYPE_BOTH };
+ } else if (sscanf(out, "UID:%lu\n", &v) == 1) {
+ *xid = (struct unixid) { .id = v, .type = ID_TYPE_UID };
+ } else if (sscanf(out, "GID:%lu\n", &v) == 1) {
+ *xid = (struct unixid) { .id = v, .type = ID_TYPE_GID };
+ } else {
+ goto unmapped;
+ }
+
+ *status = ID_MAPPED;
+ return 0;
+
+unmapped:
+ *xid = (struct unixid) { .id = UINT32_MAX,
+ .type = ID_TYPE_NOT_SPECIFIED };
+ *status = ID_UNMAPPED;
+ return 0;
+}
+
+struct idmap_script_sids2xids_state {
+ struct id_map **ids;
+ size_t num_ids;
+ size_t num_done;
+};
+
+static void idmap_script_sids2xids_done(struct tevent_req *subreq);
+
+static struct tevent_req *idmap_script_sids2xids_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ struct id_map **ids, size_t num_ids, const char *script)
+{
+ struct tevent_req *req;
+ struct idmap_script_sids2xids_state *state;
+ size_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct idmap_script_sids2xids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ids = ids;
+ state->num_ids = num_ids;
+
+ if (state->num_ids == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_ids; i++) {
+ struct tevent_req *subreq;
+
+ subreq = idmap_script_sid2xid_send(
+ state, ev, ids[i]->sid, script, i);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, idmap_script_sids2xids_done,
+ req);
+ }
+
+ return req;
+}
+
+static void idmap_script_sids2xids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct idmap_script_sids2xids_state *state = tevent_req_data(
+ req, struct idmap_script_sids2xids_state);
+ size_t idx = 0;
+ enum id_mapping status = ID_UNKNOWN;
+ struct unixid xid = { .id = UINT32_MAX,
+ .type = ID_TYPE_NOT_SPECIFIED };
+ int ret;
+
+ ret = idmap_script_sid2xid_recv(subreq, &idx, &status, &xid);
+ TALLOC_FREE(subreq);
+ if (tevent_req_error(req, ret)) {
+ return;
+ }
+
+ if (idx >= state->num_ids) {
+ tevent_req_error(req, EINVAL);
+ return;
+ }
+
+ state->ids[idx]->status = status;
+ state->ids[idx]->xid = xid;
+
+ state->num_done += 1;
+
+ if (state->num_done >= state->num_ids) {
+ tevent_req_done(req);
+ }
+}
+
+static int idmap_script_sids2xids_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_unix(req);
+}
+
+static int idmap_script_sids2xids(struct id_map **ids, size_t num_ids,
+ const char *script)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ struct tevent_context *ev;
+ struct tevent_req *req;
+ int ret = ENOMEM;
+
+ ev = samba_tevent_context_init(frame);
+ if (ev == NULL) {
+ goto fail;
+ }
+ req = idmap_script_sids2xids_send(frame, ev, ids, num_ids, script);
+ if (req == NULL) {
+ goto fail;
+ }
+ if (!tevent_req_poll(req, ev)) {
+ ret = errno;
+ goto fail;
+ }
+ ret = idmap_script_sids2xids_recv(req);
+fail:
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static NTSTATUS idmap_script_sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids)
+{
+ struct idmap_script_context *ctx = talloc_get_type_abort(
+ dom->private_data, struct idmap_script_context);
+ int ret;
+ size_t i, num_ids, num_mapped;
+
+ DEBUG(10, ("%s called ...\n", __func__));
+ /* Init status to avoid surprise ... */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+ num_ids = i;
+
+ ret = idmap_script_sids2xids(ids, num_ids, ctx->script);
+ if (ret != 0) {
+ DBG_DEBUG("idmap_script_sids2xids returned %s\n",
+ strerror(ret));
+ return map_nt_error_from_unix(ret);
+ }
+
+ num_mapped = 0;
+
+ for (i=0; i<num_ids; i++) {
+ struct id_map *map = ids[i];
+
+ if ((map->status == ID_MAPPED) &&
+ !idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DBG_INFO("Script returned id (%u) out of range "
+ "(%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id);
+ map->status = ID_UNMAPPED;
+ }
+
+ if (map->status == ID_MAPPED) {
+ num_mapped += 1;
+ }
+ }
+
+ if (num_mapped == 0) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+ if (num_mapped < num_ids) {
+ return STATUS_SOME_UNMAPPED;
+ }
+ return NT_STATUS_OK;
+}
+
+/*
+ * Initialise idmap_script database.
+ */
+static NTSTATUS idmap_script_db_init(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ struct idmap_script_context *ctx;
+ const char * idmap_script = NULL;
+ const char *ctx_script = NULL;
+
+ DEBUG(10, ("%s called ...\n", __func__));
+
+ ctx = talloc_zero(dom, struct idmap_script_context);
+ if (!ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ ctx_script = idmap_config_const_string(dom->name, "script", NULL);
+
+ /* Do we even need to handle this? */
+ idmap_script = lp_parm_const_string(-1, "idmap", "script", NULL);
+ if (idmap_script != NULL) {
+ DEBUG(0, ("Warning: 'idmap:script' is deprecated. "
+ " Please use 'idmap config * : script' instead!\n"));
+ }
+
+ if (strequal(dom->name, "*") && ctx_script == NULL) {
+ /* fall back to idmap:script for backwards compatibility */
+ ctx_script = idmap_script;
+ }
+
+ if (ctx_script) {
+ DEBUG(1, ("using idmap script '%s'\n", ctx->script));
+ /*
+ * We must ensure this memory is owned by ctx.
+ * The ctx_script const pointer is a pointer into
+ * the config file data and may become invalid
+ * on config file reload. BUG: 13956
+ */
+ ctx->script = talloc_strdup(ctx, ctx_script);
+ if (ctx->script == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+ }
+
+ dom->private_data = ctx;
+ dom->read_only = true; /* We do not allocate!*/
+
+ return NT_STATUS_OK;
+
+failed:
+ talloc_free(ctx);
+ return ret;
+}
+
+static const struct idmap_methods db_methods = {
+ .init = idmap_script_db_init,
+ .unixids_to_sids = idmap_script_unixids_to_sids,
+ .sids_to_unixids = idmap_script_sids_to_unixids,
+};
+
+static_decl_idmap;
+NTSTATUS idmap_script_init(TALLOC_CTX *ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "script", &db_methods);
+}
diff --git a/source3/winbindd/idmap_tdb.c b/source3/winbindd/idmap_tdb.c
new file mode 100644
index 0000000..77714d9
--- /dev/null
+++ b/source3/winbindd/idmap_tdb.c
@@ -0,0 +1,434 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap TDB backend
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Simo Sorce 2003-2006
+ Copyright (C) Michael Adam 2009-2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "winbindd.h"
+#include "idmap.h"
+#include "idmap_rw.h"
+#include "idmap_tdb_common.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/security.h"
+#include "util_tdb.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+/* idmap version determines auto-conversion - this is the database
+ structure version specifier. */
+
+#define IDMAP_VERSION 2
+
+/* High water mark keys */
+#define HWM_GROUP "GROUP HWM"
+#define HWM_USER "USER HWM"
+
+struct convert_fn_state {
+ struct db_context *db;
+ bool failed;
+};
+
+/*****************************************************************************
+ For idmap conversion: convert one record to new format
+ Ancient versions (eg 2.2.3a) of winbindd_idmap.tdb mapped DOMAINNAME/rid
+ instead of the SID.
+*****************************************************************************/
+static int convert_fn(struct db_record *rec, void *private_data)
+{
+ struct winbindd_domain *domain;
+ char *p;
+ NTSTATUS status;
+ struct dom_sid sid;
+ uint32_t rid;
+ struct dom_sid_buf keystr;
+ fstring dom_name;
+ TDB_DATA key;
+ TDB_DATA key2;
+ TDB_DATA value;
+ struct convert_fn_state *s = (struct convert_fn_state *)private_data;
+
+ key = dbwrap_record_get_key(rec);
+
+ DEBUG(10,("Converting %s\n", (const char *)key.dptr));
+
+ p = strchr((const char *)key.dptr, '/');
+ if (!p)
+ return 0;
+
+ *p = 0;
+ fstrcpy(dom_name, (const char *)key.dptr);
+ *p++ = '/';
+
+ domain = find_domain_from_name(dom_name);
+ if (domain == NULL) {
+ /* We must delete the old record. */
+ DEBUG(0,("Unable to find domain %s\n", dom_name ));
+ DEBUG(0,("deleting record %s\n", (const char *)key.dptr ));
+
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to delete record %s:%s\n",
+ (const char *)key.dptr,
+ nt_errstr(status)));
+ s->failed = true;
+ return -1;
+ }
+
+ return 0;
+ }
+
+ rid = atoi(p);
+
+ sid_compose(&sid, &domain->sid, rid);
+
+ key2 = string_term_tdb_data(dom_sid_str_buf(&sid, &keystr));
+
+ value = dbwrap_record_get_value(rec);
+
+ status = dbwrap_store(s->db, key2, value, TDB_INSERT);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to add record %s:%s\n",
+ (const char *)key2.dptr,
+ nt_errstr(status)));
+ s->failed = true;
+ return -1;
+ }
+
+ status = dbwrap_store(s->db, value, key2, TDB_REPLACE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to update record %s:%s\n",
+ (const char *)value.dptr,
+ nt_errstr(status)));
+ s->failed = true;
+ return -1;
+ }
+
+ status = dbwrap_record_delete(rec);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("Unable to delete record %s:%s\n",
+ (const char *)key.dptr,
+ nt_errstr(status)));
+ s->failed = true;
+ return -1;
+ }
+
+ return 0;
+}
+
+/*****************************************************************************
+ Convert the idmap database from an older version.
+*****************************************************************************/
+
+static bool idmap_tdb_upgrade(struct idmap_domain *dom, struct db_context *db)
+{
+ int32_t vers;
+ struct convert_fn_state s;
+ NTSTATUS status;
+
+ status = dbwrap_fetch_int32_bystring(db, "IDMAP_VERSION", &vers);
+ if (!NT_STATUS_IS_OK(status)) {
+ vers = -1;
+ }
+
+ if (IREV(vers) == IDMAP_VERSION) {
+ /* Arrggghh ! Bytereversed - make order independent ! */
+ /*
+ * high and low records were created on a
+ * big endian machine and will need byte-reversing.
+ */
+
+ int32_t wm;
+
+ status = dbwrap_fetch_int32_bystring(db, HWM_USER, &wm);
+ if (!NT_STATUS_IS_OK(status)) {
+ wm = -1;
+ }
+
+ if (wm != -1) {
+ wm = IREV(wm);
+ } else {
+ wm = dom->low_id;
+ }
+
+ status = dbwrap_store_int32_bystring(db, HWM_USER, wm);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to byteswap user hwm in idmap "
+ "database: %s\n", nt_errstr(status)));
+ return False;
+ }
+
+ status = dbwrap_fetch_int32_bystring(db, HWM_GROUP, &wm);
+ if (!NT_STATUS_IS_OK(status)) {
+ wm = -1;
+ }
+
+ if (wm != -1) {
+ wm = IREV(wm);
+ } else {
+ wm = dom->low_id;
+ }
+
+ status = dbwrap_store_int32_bystring(db, HWM_GROUP, wm);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to byteswap group hwm in idmap "
+ "database: %s\n", nt_errstr(status)));
+ return False;
+ }
+ }
+
+ s.db = db;
+ s.failed = false;
+
+ /* the old format stored as DOMAIN/rid - now we store the SID direct */
+ status = dbwrap_traverse(db, convert_fn, &s, NULL);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Database traverse failed during conversion\n"));
+ return false;
+ }
+
+ if (s.failed) {
+ DEBUG(0, ("Problem during conversion\n"));
+ return False;
+ }
+
+ status = dbwrap_store_int32_bystring(db, "IDMAP_VERSION",
+ IDMAP_VERSION);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to store idmap version in database: %s\n",
+ nt_errstr(status)));
+ return False;
+ }
+
+ return True;
+}
+
+static NTSTATUS idmap_tdb_init_hwm(struct idmap_domain *dom)
+{
+ uint32_t low_uid;
+ uint32_t low_gid;
+ bool update_uid = false;
+ bool update_gid = false;
+ struct idmap_tdb_common_context *ctx;
+ NTSTATUS status;
+
+ ctx = talloc_get_type(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_USER, &low_uid);
+ if (!NT_STATUS_IS_OK(status) || low_uid < dom->low_id) {
+ update_uid = true;
+ }
+
+ status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_GROUP, &low_gid);
+ if (!NT_STATUS_IS_OK(status) || low_gid < dom->low_id) {
+ update_gid = true;
+ }
+
+ if (!update_uid && !update_gid) {
+ return NT_STATUS_OK;
+ }
+
+ if (dbwrap_transaction_start(ctx->db) != 0) {
+ DEBUG(0, ("Unable to start upgrade transaction!\n"));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (update_uid) {
+ status = dbwrap_store_uint32_bystring(ctx->db, HWM_USER,
+ dom->low_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbwrap_transaction_cancel(ctx->db);
+ DEBUG(0, ("Unable to initialise user hwm in idmap "
+ "database: %s\n", nt_errstr(status)));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+ }
+
+ if (update_gid) {
+ status = dbwrap_store_uint32_bystring(ctx->db, HWM_GROUP,
+ dom->low_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ dbwrap_transaction_cancel(ctx->db);
+ DEBUG(0, ("Unable to initialise group hwm in idmap "
+ "database: %s\n", nt_errstr(status)));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+ }
+
+ if (dbwrap_transaction_commit(ctx->db) != 0) {
+ DEBUG(0, ("Unable to commit upgrade transaction!\n"));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS idmap_tdb_open_db(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ TALLOC_CTX *mem_ctx;
+ char *tdbfile = NULL;
+ struct db_context *db = NULL;
+ int32_t version;
+ struct idmap_tdb_common_context *ctx;
+
+ ctx = talloc_get_type(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ if (ctx->db) {
+ /* it is already open */
+ return NT_STATUS_OK;
+ }
+
+ /* use our own context here */
+ mem_ctx = talloc_stackframe();
+
+ /* use the old database if present */
+ tdbfile = state_path(talloc_tos(), "winbindd_idmap.tdb");
+ if (!tdbfile) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(10,("Opening tdbfile %s\n", tdbfile ));
+
+ /* Open idmap repository */
+ db = db_open(mem_ctx, tdbfile, 0, TDB_DEFAULT, O_RDWR | O_CREAT, 0644,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (!db) {
+ DEBUG(0, ("Unable to open idmap database\n"));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* check against earlier versions */
+ ret = dbwrap_fetch_int32_bystring(db, "IDMAP_VERSION", &version);
+ if (!NT_STATUS_IS_OK(ret)) {
+ version = -1;
+ }
+
+ if (version != IDMAP_VERSION) {
+ if (dbwrap_transaction_start(db) != 0) {
+ DEBUG(0, ("Unable to start upgrade transaction!\n"));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ if (!idmap_tdb_upgrade(dom, db)) {
+ dbwrap_transaction_cancel(db);
+ DEBUG(0, ("Unable to open idmap database, it's in an old format, and upgrade failed!\n"));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ if (dbwrap_transaction_commit(db) != 0) {
+ DEBUG(0, ("Unable to commit upgrade transaction!\n"));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+ }
+
+ ctx->db = talloc_move(ctx, &db);
+
+ ret = idmap_tdb_init_hwm(dom);
+
+done:
+ talloc_free(mem_ctx);
+ return ret;
+}
+
+/**********************************************************************
+ IDMAP MAPPING TDB BACKEND
+**********************************************************************/
+
+/*****************************
+ Initialise idmap database.
+*****************************/
+
+static NTSTATUS idmap_tdb_db_init(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ struct idmap_tdb_common_context *ctx;
+
+ DEBUG(10, ("idmap_tdb_db_init called for domain '%s'\n", dom->name));
+
+ ctx = talloc_zero(dom, struct idmap_tdb_common_context);
+ if ( ! ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* load backend specific configuration here: */
+#if 0
+ if (strequal(dom->name, "*")) {
+ } else {
+ }
+#endif
+
+ ctx->rw_ops = talloc_zero(ctx, struct idmap_rw_ops);
+ if (ctx->rw_ops == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ ctx->max_id = dom->high_id;
+ ctx->hwmkey_uid = HWM_USER;
+ ctx->hwmkey_gid = HWM_GROUP;
+
+ ctx->rw_ops->get_new_id = idmap_tdb_common_get_new_id;
+ ctx->rw_ops->set_mapping = idmap_tdb_common_set_mapping;
+
+ dom->private_data = ctx;
+
+ ret = idmap_tdb_open_db(dom);
+ if ( ! NT_STATUS_IS_OK(ret)) {
+ goto failed;
+ }
+
+ return NT_STATUS_OK;
+
+failed:
+ talloc_free(ctx);
+ return ret;
+}
+
+static const struct idmap_methods db_methods = {
+ .init = idmap_tdb_db_init,
+ .unixids_to_sids = idmap_tdb_common_unixids_to_sids,
+ .sids_to_unixids = idmap_tdb_common_sids_to_unixids,
+ .allocate_id = idmap_tdb_common_get_new_id,
+};
+
+NTSTATUS idmap_tdb_init(TALLOC_CTX *mem_ctx)
+{
+ DEBUG(10, ("calling idmap_tdb_init\n"));
+
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "tdb", &db_methods);
+}
diff --git a/source3/winbindd/idmap_tdb2.c b/source3/winbindd/idmap_tdb2.c
new file mode 100644
index 0000000..d843aee
--- /dev/null
+++ b/source3/winbindd/idmap_tdb2.c
@@ -0,0 +1,612 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ idmap TDB2 backend, used for clustered Samba setups.
+
+ This uses dbwrap to access tdb files. The location can be set
+ using tdb:idmap2.tdb =" in smb.conf
+
+ Copyright (C) Andrew Tridgell 2007
+
+ This is heavily based upon idmap_tdb.c, which is:
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Simo Sorce 2003-2006
+ Copyright (C) Michael Adam 2009-2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "winbindd.h"
+#include "idmap.h"
+#include "idmap_rw.h"
+#include "dbwrap/dbwrap.h"
+#include "dbwrap/dbwrap_open.h"
+#include "../libcli/security/dom_sid.h"
+#include "util_tdb.h"
+#include "idmap_tdb_common.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+struct idmap_tdb2_context {
+ const char *script; /* script to provide idmaps */
+};
+
+/* High water mark keys */
+#define HWM_GROUP "GROUP HWM"
+#define HWM_USER "USER HWM"
+
+/*
+ * check and initialize high/low water marks in the db
+ */
+static NTSTATUS idmap_tdb2_init_hwm(struct idmap_domain *dom)
+{
+ NTSTATUS status;
+ uint32_t low_id;
+ struct idmap_tdb_common_context *ctx;
+
+ ctx = talloc_get_type(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ /* Create high water marks for group and user id */
+
+ status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_USER, &low_id);
+ if (!NT_STATUS_IS_OK(status) || (low_id < dom->low_id)) {
+ status = dbwrap_trans_store_uint32_bystring(ctx->db, HWM_USER,
+ dom->low_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to initialise user hwm in idmap "
+ "database: %s\n", nt_errstr(status)));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+ }
+
+ status = dbwrap_fetch_uint32_bystring(ctx->db, HWM_GROUP, &low_id);
+ if (!NT_STATUS_IS_OK(status) || (low_id < dom->low_id)) {
+ status = dbwrap_trans_store_uint32_bystring(ctx->db, HWM_GROUP,
+ dom->low_id);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Unable to initialise group hwm in idmap "
+ "database: %s\n", nt_errstr(status)));
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+/*
+ open the permanent tdb
+ */
+static NTSTATUS idmap_tdb2_open_db(struct idmap_domain *dom)
+{
+ char *db_path;
+ struct idmap_tdb_common_context *ctx;
+
+ ctx = talloc_get_type(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ if (ctx->db) {
+ /* its already open */
+ return NT_STATUS_OK;
+ }
+
+ db_path = talloc_asprintf(NULL, "%s/idmap2.tdb", lp_private_dir());
+ NT_STATUS_HAVE_NO_MEMORY(db_path);
+
+ /* Open idmap repository */
+ ctx->db = db_open(ctx, db_path, 0, TDB_DEFAULT, O_RDWR|O_CREAT, 0644,
+ DBWRAP_LOCK_ORDER_1, DBWRAP_FLAG_NONE);
+ if (ctx->db == NULL) {
+ DEBUG(0, ("Unable to open idmap_tdb2 database '%s'\n",
+ db_path));
+ TALLOC_FREE(db_path);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ TALLOC_FREE(db_path);
+
+ return idmap_tdb2_init_hwm(dom);
+}
+
+/**
+ * store a mapping in the database.
+ */
+
+struct idmap_tdb2_set_mapping_context {
+ const char *ksidstr;
+ const char *kidstr;
+};
+
+static NTSTATUS idmap_tdb2_set_mapping_action(struct db_context *db,
+ void *private_data)
+{
+ TDB_DATA data;
+ NTSTATUS ret;
+ struct idmap_tdb2_set_mapping_context *state;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ state = (struct idmap_tdb2_set_mapping_context *)private_data;
+
+ DEBUG(10, ("Storing %s <-> %s map\n", state->ksidstr, state->kidstr));
+
+ /* check whether sid mapping is already present in db */
+ ret = dbwrap_fetch_bystring(db, tmp_ctx, state->ksidstr, &data);
+ if (NT_STATUS_IS_OK(ret)) {
+ ret = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto done;
+ }
+
+ ret = dbwrap_store_bystring(db, state->ksidstr,
+ string_term_tdb_data(state->kidstr),
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("Error storing SID -> ID: %s\n", nt_errstr(ret)));
+ goto done;
+ }
+
+ ret = dbwrap_store_bystring(db, state->kidstr,
+ string_term_tdb_data(state->ksidstr),
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("Error storing ID -> SID: %s\n", nt_errstr(ret)));
+ /* try to remove the previous stored SID -> ID map */
+ dbwrap_delete_bystring(db, state->ksidstr);
+ goto done;
+ }
+
+ DEBUG(10,("Stored %s <-> %s\n", state->ksidstr, state->kidstr));
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+static NTSTATUS idmap_tdb2_set_mapping(struct idmap_domain *dom, const struct id_map *map)
+{
+ struct idmap_tdb2_context *ctx;
+ NTSTATUS ret;
+ char *kidstr;
+ struct dom_sid_buf sid_str;
+ struct idmap_tdb_common_context *commonctx;
+ struct idmap_tdb2_set_mapping_context state;
+
+ if (!map || !map->sid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ kidstr = NULL;
+
+ /* TODO: should we filter a set_mapping using low/high filters ? */
+
+ commonctx = talloc_get_type(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ ctx = talloc_get_type(commonctx->private_data,
+ struct idmap_tdb2_context);
+
+ switch (map->xid.type) {
+
+ case ID_TYPE_UID:
+ kidstr = talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id);
+ break;
+
+ case ID_TYPE_GID:
+ kidstr = talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id);
+ break;
+
+ default:
+ DEBUG(2, ("INVALID unix ID type: 0x02%x\n", map->xid.type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (kidstr == NULL) {
+ DEBUG(0, ("ERROR: Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ state.ksidstr = dom_sid_str_buf(map->sid, &sid_str);
+ state.kidstr = kidstr;
+
+ ret = dbwrap_trans_do(commonctx->db, idmap_tdb2_set_mapping_action,
+ &state);
+
+done:
+ talloc_free(kidstr);
+ return ret;
+}
+
+/*
+ run a script to perform a mapping
+
+ The script should the following command lines:
+
+ SIDTOID S-1-xxxx
+ IDTOSID UID xxxx
+ IDTOSID GID xxxx
+
+ and should return one of the following as a single line of text
+ UID:xxxx
+ GID:xxxx
+ SID:xxxx
+ ERR:xxxx
+ */
+static NTSTATUS idmap_tdb2_script(struct idmap_tdb2_context *ctx,
+ struct id_map *map, const char *fmt, ...)
+ PRINTF_ATTRIBUTE(3,4);
+
+static NTSTATUS idmap_tdb2_script(struct idmap_tdb2_context *ctx, struct id_map *map,
+ const char *fmt, ...)
+{
+ va_list ap;
+ char *cmd;
+ FILE *p;
+ char line[64];
+ unsigned long v;
+
+ cmd = talloc_asprintf(ctx, "%s ", ctx->script);
+ NT_STATUS_HAVE_NO_MEMORY(cmd);
+
+ va_start(ap, fmt);
+ cmd = talloc_vasprintf_append(cmd, fmt, ap);
+ va_end(ap);
+ NT_STATUS_HAVE_NO_MEMORY(cmd);
+
+ p = popen(cmd, "r");
+ talloc_free(cmd);
+ if (p == NULL) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ if (fgets(line, sizeof(line)-1, p) == NULL) {
+ pclose(p);
+ return NT_STATUS_NONE_MAPPED;
+ }
+ pclose(p);
+
+ DEBUG(10,("idmap script gave: %s\n", line));
+
+ if (sscanf(line, "UID:%lu", &v) == 1) {
+ map->xid.id = v;
+ map->xid.type = ID_TYPE_UID;
+ } else if (sscanf(line, "GID:%lu", &v) == 1) {
+ map->xid.id = v;
+ map->xid.type = ID_TYPE_GID;
+ } else if (strncmp(line, "SID:S-", 6) == 0) {
+ if (!string_to_sid(map->sid, &line[4])) {
+ DEBUG(0,("Bad SID in '%s' from idmap script %s\n",
+ line, ctx->script));
+ return NT_STATUS_NONE_MAPPED;
+ }
+ } else {
+ DEBUG(0,("Bad reply '%s' from idmap script %s\n",
+ line, ctx->script));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+
+
+/*
+ Single id to sid lookup function.
+*/
+static NTSTATUS idmap_tdb2_id_to_sid(struct idmap_domain *dom, struct id_map *map)
+{
+ NTSTATUS ret;
+ TDB_DATA data;
+ char *keystr;
+ NTSTATUS status;
+ struct idmap_tdb_common_context *commonctx;
+ struct idmap_tdb2_context *ctx;
+
+
+ if (!dom || !map) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ status = idmap_tdb2_open_db(dom);
+ NT_STATUS_NOT_OK_RETURN(status);
+
+ commonctx = talloc_get_type(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ ctx = talloc_get_type(commonctx->private_data,
+ struct idmap_tdb2_context);
+
+ /* apply filters before checking */
+ if (!idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ switch (map->xid.type) {
+
+ case ID_TYPE_UID:
+ keystr = talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id);
+ break;
+
+ case ID_TYPE_GID:
+ keystr = talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id);
+ break;
+
+ default:
+ DEBUG(2, ("INVALID unix ID type: 0x02%x\n", map->xid.type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (keystr == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(10,("Fetching record %s\n", keystr));
+
+ /* Check if the mapping exists */
+ status = dbwrap_fetch_bystring(commonctx->db, keystr, keystr, &data);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ struct dom_sid_buf sidstr;
+ struct idmap_tdb2_set_mapping_context store_state;
+
+ DEBUG(10,("Record %s not found\n", keystr));
+ if (ctx->script == NULL) {
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ ret = idmap_tdb2_script(ctx, map, "IDTOSID %s", keystr);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto done;
+ }
+
+ store_state.ksidstr = dom_sid_str_buf(map->sid, &sidstr);
+ store_state.kidstr = keystr;
+
+ ret = dbwrap_trans_do(commonctx->db,
+ idmap_tdb2_set_mapping_action,
+ &store_state);
+ goto done;
+ }
+
+ if (!string_to_sid(map->sid, (const char *)data.dptr)) {
+ DEBUG(10,("INVALID SID (%s) in record %s\n",
+ (const char *)data.dptr, keystr));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ DEBUG(10,("Found record %s -> %s\n", keystr, (const char *)data.dptr));
+ ret = NT_STATUS_OK;
+
+done:
+ talloc_free(keystr);
+ return ret;
+}
+
+
+/*
+ Single sid to id lookup function.
+*/
+static NTSTATUS idmap_tdb2_sid_to_id(struct idmap_domain *dom, struct id_map *map)
+{
+ NTSTATUS ret;
+ TDB_DATA data;
+ struct dom_sid_buf keystr;
+ unsigned long rec_id = 0;
+ struct idmap_tdb_common_context *commonctx;
+ struct idmap_tdb2_context *ctx;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ ret = idmap_tdb2_open_db(dom);
+ NT_STATUS_NOT_OK_RETURN(ret);
+
+ commonctx = talloc_get_type(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ ctx = talloc_get_type(commonctx->private_data,
+ struct idmap_tdb2_context);
+
+ dom_sid_str_buf(map->sid, &keystr);
+
+ DEBUG(10, ("Fetching record %s\n", keystr.buf));
+
+ /* Check if sid is present in database */
+ ret = dbwrap_fetch_bystring(commonctx->db, tmp_ctx, keystr.buf, &data);
+ if (!NT_STATUS_IS_OK(ret)) {
+ char *idstr;
+ struct idmap_tdb2_set_mapping_context store_state;
+
+ DBG_DEBUG("Record %s not found\n", keystr.buf);
+
+ if (ctx->script == NULL) {
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ ret = idmap_tdb2_script(ctx, map, "SIDTOID %s", keystr.buf);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto done;
+ }
+
+ /* apply filters before returning result */
+ if (!idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DEBUG(5, ("Script returned id (%u) out of range "
+ "(%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id));
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ idstr = talloc_asprintf(tmp_ctx, "%cID %lu",
+ map->xid.type == ID_TYPE_UID?'U':'G',
+ (unsigned long)map->xid.id);
+ if (idstr == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ store_state.ksidstr = keystr.buf;
+ store_state.kidstr = idstr;
+
+ ret = dbwrap_trans_do(commonctx->db,
+ idmap_tdb2_set_mapping_action,
+ &store_state);
+ goto done;
+ }
+
+ /* What type of record is this ? */
+ if (sscanf((const char *)data.dptr, "UID %lu", &rec_id) == 1) { /* Try a UID record. */
+ map->xid.id = rec_id;
+ map->xid.type = ID_TYPE_UID;
+ DBG_DEBUG("Found uid record %s -> %s \n",
+ keystr.buf,
+ (const char *)data.dptr );
+ ret = NT_STATUS_OK;
+
+ } else if (sscanf((const char *)data.dptr, "GID %lu", &rec_id) == 1) { /* Try a GID record. */
+ map->xid.id = rec_id;
+ map->xid.type = ID_TYPE_GID;
+ DBG_DEBUG("Found gid record %s -> %s \n",
+ keystr.buf,
+ (const char *)data.dptr );
+ ret = NT_STATUS_OK;
+
+ } else { /* Unknown record type ! */
+ DBG_WARNING("Found INVALID record %s -> %s\n",
+ keystr.buf,
+ (const char *)data.dptr);
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ /* apply filters before returning result */
+ if (!idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DEBUG(5, ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id));
+ ret = NT_STATUS_NONE_MAPPED;
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/*
+ Initialise idmap database.
+*/
+static NTSTATUS idmap_tdb2_db_init(struct idmap_domain *dom)
+{
+ NTSTATUS ret;
+ struct idmap_tdb_common_context *commonctx;
+ struct idmap_tdb2_context *ctx;
+ const char * idmap_script = NULL;
+ const char *ctx_script = NULL;
+
+ commonctx = talloc_zero(dom, struct idmap_tdb_common_context);
+ if(!commonctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ commonctx->rw_ops = talloc_zero(commonctx, struct idmap_rw_ops);
+ if (commonctx->rw_ops == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ ctx = talloc_zero(commonctx, struct idmap_tdb2_context);
+ if (!ctx) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+
+ ctx_script = idmap_config_const_string(dom->name, "script", NULL);
+
+ idmap_script = lp_parm_const_string(-1, "idmap", "script", NULL);
+ if (idmap_script != NULL) {
+ DEBUG(0, ("Warning: 'idmap:script' is deprecated. "
+ " Please use 'idmap config * : script' instead!\n"));
+ }
+
+ if (strequal(dom->name, "*") && ctx_script == NULL) {
+ /* fall back to idmap:script for backwards compatibility */
+ ctx_script = idmap_script;
+ }
+
+ if (ctx_script) {
+ DEBUG(1, ("using idmap script '%s'\n", ctx_script));
+ /*
+ * We must ensure this memory is owned by ctx.
+ * The ctx_script const pointer is a pointer into
+ * the config file data and may become invalid
+ * on config file reload. BUG: 13956
+ */
+ ctx->script = talloc_strdup(ctx, ctx_script);
+ if (ctx->script == NULL) {
+ ret = NT_STATUS_NO_MEMORY;
+ goto failed;
+ }
+ }
+
+ commonctx->max_id = dom->high_id;
+ commonctx->hwmkey_uid = HWM_USER;
+ commonctx->hwmkey_gid = HWM_GROUP;
+
+ commonctx->sid_to_unixid_fn = idmap_tdb2_sid_to_id;
+ commonctx->unixid_to_sid_fn = idmap_tdb2_id_to_sid;
+
+ commonctx->rw_ops->get_new_id = idmap_tdb_common_get_new_id;
+ commonctx->rw_ops->set_mapping = idmap_tdb2_set_mapping;
+
+ commonctx->private_data = ctx;
+ dom->private_data = commonctx;
+
+ ret = idmap_tdb2_open_db(dom);
+ if (!NT_STATUS_IS_OK(ret)) {
+ goto failed;
+ }
+
+ return NT_STATUS_OK;
+
+failed:
+ talloc_free(commonctx);
+ return ret;
+}
+
+
+static const struct idmap_methods db_methods = {
+ .init = idmap_tdb2_db_init,
+ .unixids_to_sids = idmap_tdb_common_unixids_to_sids,
+ .sids_to_unixids = idmap_tdb_common_sids_to_unixids,
+ .allocate_id = idmap_tdb_common_get_new_id
+};
+
+static_decl_idmap;
+NTSTATUS idmap_tdb2_init(TALLOC_CTX *ctx)
+{
+ return smb_register_idmap(SMB_IDMAP_INTERFACE_VERSION, "tdb2", &db_methods);
+}
diff --git a/source3/winbindd/idmap_tdb_common.c b/source3/winbindd/idmap_tdb_common.c
new file mode 100644
index 0000000..0df8f2f
--- /dev/null
+++ b/source3/winbindd/idmap_tdb_common.c
@@ -0,0 +1,664 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ common functions for TDB based idmapping backends
+
+ Copyright (C) Christian Ambach 2012
+
+ These functions were initially copied over from idmap_tdb.c and idmap_tdb2.c
+ which are:
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Simo Sorce 2003-2006
+ Copyright (C) Michael Adam 2009-2010
+ Copyright (C) Andrew Tridgell 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "includes.h"
+#include "idmap_tdb_common.h"
+#include "dbwrap/dbwrap.h"
+#include "util_tdb.h"
+#include "idmap_rw.h"
+#include "../libcli/security/dom_sid.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+struct idmap_tdb_common_allocate_id_context {
+ const char *hwmkey;
+ const char *hwmtype;
+ uint32_t high_hwm;
+ uint32_t hwm;
+};
+
+static NTSTATUS idmap_tdb_common_allocate_id_action(struct db_context *db,
+ void *private_data)
+{
+ NTSTATUS ret;
+ struct idmap_tdb_common_allocate_id_context *state = private_data;
+ uint32_t hwm;
+
+ ret = dbwrap_fetch_uint32_bystring(db, state->hwmkey, &hwm);
+ if (!NT_STATUS_IS_OK(ret)) {
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ /* check it is in the range */
+ if (hwm > state->high_hwm) {
+ DEBUG(1, ("Fatal Error: %s range full!! (max: %lu)\n",
+ state->hwmtype, (unsigned long)state->high_hwm));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ /* fetch a new id and increment it */
+ ret = dbwrap_change_uint32_atomic_bystring(db, state->hwmkey, &hwm, 1);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(1, ("Fatal error while fetching a new %s value\n!",
+ state->hwmtype));
+ goto done;
+ }
+
+ /* recheck it is in the range */
+ if (hwm > state->high_hwm) {
+ DEBUG(1, ("Fatal Error: %s range full!! (max: %lu)\n",
+ state->hwmtype, (unsigned long)state->high_hwm));
+ ret = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ ret = NT_STATUS_OK;
+ state->hwm = hwm;
+
+ done:
+ return ret;
+}
+
+static NTSTATUS idmap_tdb_common_allocate_id(struct idmap_domain *dom,
+ struct unixid *xid)
+{
+ const char *hwmkey;
+ const char *hwmtype;
+ uint32_t hwm = 0;
+ NTSTATUS status;
+ struct idmap_tdb_common_allocate_id_context state;
+ struct idmap_tdb_common_context *ctx;
+
+ ctx =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ /* Get current high water mark */
+ switch (xid->type) {
+
+ case ID_TYPE_UID:
+ hwmkey = ctx->hwmkey_uid;
+ hwmtype = "UID";
+ break;
+
+ case ID_TYPE_GID:
+ hwmkey = ctx->hwmkey_gid;
+ hwmtype = "GID";
+ break;
+
+ case ID_TYPE_BOTH:
+ /*
+ * This is not supported here yet and
+ * already handled in idmap_rw_new_mapping()
+ */
+ FALL_THROUGH;
+ case ID_TYPE_NOT_SPECIFIED:
+ /*
+ * This is handled in idmap_rw_new_mapping()
+ */
+ FALL_THROUGH;
+ default:
+ DEBUG(2, ("Invalid ID type (0x%x)\n", xid->type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ state.hwm = hwm;
+ state.high_hwm = ctx->max_id;
+ state.hwmtype = hwmtype;
+ state.hwmkey = hwmkey;
+
+ status = dbwrap_trans_do(ctx->db, idmap_tdb_common_allocate_id_action,
+ &state);
+
+ if (NT_STATUS_IS_OK(status)) {
+ xid->id = state.hwm;
+ DEBUG(10, ("New %s = %d\n", hwmtype, state.hwm));
+ } else {
+ DEBUG(1, ("Error allocating a new %s\n", hwmtype));
+ }
+
+ return status;
+}
+
+/**
+ * Allocate a new unix-ID.
+ * For now this is for the default idmap domain only.
+ * Should be extended later on.
+ */
+NTSTATUS idmap_tdb_common_get_new_id(struct idmap_domain * dom,
+ struct unixid * id)
+{
+ NTSTATUS ret;
+
+ if (!strequal(dom->name, "*")) {
+ DEBUG(3, ("idmap_tdb_common_get_new_id: "
+ "Refusing allocation of a new unixid for domain'%s'. "
+ "Currently only supported for the default "
+ "domain \"*\".\n", dom->name));
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ ret = idmap_tdb_common_allocate_id(dom, id);
+
+ return ret;
+}
+
+/**
+ * store a mapping in the database.
+ */
+
+struct idmap_tdb_common_set_mapping_context {
+ const char *ksidstr;
+ const char *kidstr;
+};
+
+static NTSTATUS idmap_tdb_common_set_mapping_action(struct db_context *db,
+ void *private_data)
+{
+ TDB_DATA data;
+ NTSTATUS ret;
+ struct idmap_tdb_common_set_mapping_context *state = private_data;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ DEBUG(10, ("Storing %s <-> %s map\n", state->ksidstr, state->kidstr));
+
+ /* check whether sid mapping is already present in db */
+ ret = dbwrap_fetch_bystring(db, tmp_ctx, state->ksidstr, &data);
+ if (NT_STATUS_IS_OK(ret)) {
+ ret = NT_STATUS_OBJECT_NAME_COLLISION;
+ goto done;
+ }
+
+ ret = dbwrap_store_bystring(db, state->ksidstr,
+ string_term_tdb_data(state->kidstr),
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("Error storing SID -> ID: %s\n", nt_errstr(ret)));
+ goto done;
+ }
+
+ ret = dbwrap_store_bystring(db, state->kidstr,
+ string_term_tdb_data(state->ksidstr),
+ TDB_INSERT);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(0, ("Error storing ID -> SID: %s\n", nt_errstr(ret)));
+ /* try to remove the previous stored SID -> ID map */
+ dbwrap_delete_bystring(db, state->ksidstr);
+ goto done;
+ }
+
+ DEBUG(10, ("Stored %s <-> %s\n", state->ksidstr, state->kidstr));
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+NTSTATUS idmap_tdb_common_set_mapping(struct idmap_domain * dom,
+ const struct id_map * map)
+{
+ struct idmap_tdb_common_context *ctx;
+ struct idmap_tdb_common_set_mapping_context state;
+ NTSTATUS ret;
+ struct dom_sid_buf ksidstr;
+ char *kidstr = NULL;
+
+ if (!map || !map->sid) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* TODO: should we filter a set_mapping using low/high filters ? */
+
+ ctx =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ switch (map->xid.type) {
+
+ case ID_TYPE_UID:
+ kidstr =
+ talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id);
+ break;
+
+ case ID_TYPE_GID:
+ kidstr =
+ talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id);
+ break;
+
+ default:
+ DEBUG(2, ("INVALID unix ID type: 0x%02x\n", map->xid.type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (kidstr == NULL) {
+ DEBUG(0, ("ERROR: Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ state.ksidstr = dom_sid_str_buf(map->sid, &ksidstr);
+ state.kidstr = kidstr;
+
+ ret = dbwrap_trans_do(ctx->db, idmap_tdb_common_set_mapping_action,
+ &state);
+
+ done:
+ talloc_free(kidstr);
+ return ret;
+}
+
+/*
+ * Create a new mapping for an unmapped SID, also allocating a new ID.
+ * This should be run inside a transaction.
+ *
+ * TODO:
+ * Properly integrate this with multi domain idmap config:
+ * Currently, the allocator is default-config only.
+ */
+NTSTATUS idmap_tdb_common_new_mapping(struct idmap_domain * dom,
+ struct id_map * map)
+{
+ NTSTATUS ret;
+ struct idmap_tdb_common_context *ctx;
+
+ ctx =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ ret = idmap_rw_new_mapping(dom, ctx->rw_ops, map);
+
+ return ret;
+}
+
+/*
+ lookup a set of unix ids
+*/
+NTSTATUS idmap_tdb_common_unixids_to_sids(struct idmap_domain * dom,
+ struct id_map ** ids)
+{
+ NTSTATUS ret;
+ size_t i, num_mapped = 0;
+ struct idmap_tdb_common_context *ctx;
+
+ NTSTATUS(*unixid_to_sid_fn) (struct idmap_domain * dom,
+ struct id_map * map);
+ ctx =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ if (ctx->unixid_to_sid_fn == NULL) {
+ unixid_to_sid_fn = idmap_tdb_common_unixid_to_sid;
+ } else {
+ unixid_to_sid_fn = ctx->unixid_to_sid_fn;
+ }
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+ for (i = 0; ids[i]; i++) {
+ ret = unixid_to_sid_fn(dom, ids[i]);
+ if (!NT_STATUS_IS_OK(ret)) {
+
+ /* if it is just a failed mapping continue */
+ if (NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) {
+
+ /* make sure it is marked as unmapped */
+ ids[i]->status = ID_UNMAPPED;
+ continue;
+ }
+
+ /* some fatal error occurred, return immediately */
+ goto done;
+ }
+
+ /* all ok, id is mapped */
+ ids[i]->status = ID_MAPPED;
+ num_mapped += 1;
+ }
+
+ ret = NT_STATUS_OK;
+
+done:
+
+ if (NT_STATUS_IS_OK(ret)) {
+ if (i == 0 || num_mapped == 0) {
+ ret = NT_STATUS_NONE_MAPPED;
+ } else if (num_mapped < i) {
+ ret = STATUS_SOME_UNMAPPED;
+ } else {
+ ret = NT_STATUS_OK;
+ }
+ }
+
+ return ret;
+}
+
+/*
+ default single id to sid lookup function
+*/
+NTSTATUS idmap_tdb_common_unixid_to_sid(struct idmap_domain * dom,
+ struct id_map * map)
+{
+ NTSTATUS ret;
+ TDB_DATA data;
+ char *keystr;
+ struct idmap_tdb_common_context *ctx;
+
+ if (!dom || !map) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ /* apply filters before checking */
+ if (!idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DEBUG(5,
+ ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id));
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ switch (map->xid.type) {
+
+ case ID_TYPE_UID:
+ keystr =
+ talloc_asprintf(ctx, "UID %lu", (unsigned long)map->xid.id);
+ break;
+
+ case ID_TYPE_GID:
+ keystr =
+ talloc_asprintf(ctx, "GID %lu", (unsigned long)map->xid.id);
+ break;
+
+ default:
+ DEBUG(2, ("INVALID unix ID type: 0x%02x\n", map->xid.type));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (keystr == NULL) {
+ DEBUG(0, ("Out of memory!\n"));
+ ret = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ DEBUG(10, ("Fetching record %s\n", keystr));
+
+ /* Check if the mapping exists */
+ ret = dbwrap_fetch_bystring(ctx->db, keystr, keystr, &data);
+
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(10, ("Record %s not found\n", keystr));
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ if ((data.dsize == 0) || (data.dptr[data.dsize-1] != '\0')) {
+ DBG_DEBUG("Invalid record length %zu\n", data.dsize);
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ if (!string_to_sid(map->sid, (const char *)data.dptr)) {
+ DEBUG(10, ("INVALID SID (%s) in record %s\n",
+ (const char *)data.dptr, keystr));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ DEBUG(10, ("Found record %s -> %s\n", keystr, (const char *)data.dptr));
+ ret = NT_STATUS_OK;
+
+ done:
+ talloc_free(keystr);
+ return ret;
+}
+
+/**********************************
+ Single sid to id lookup function.
+**********************************/
+
+NTSTATUS idmap_tdb_common_sid_to_unixid(struct idmap_domain * dom,
+ struct id_map * map)
+{
+ NTSTATUS ret;
+ TDB_DATA data;
+ struct dom_sid_buf keystr;
+ unsigned long rec_id = 0;
+ struct idmap_tdb_common_context *ctx;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+
+ if (!dom || !map) {
+ talloc_free(tmp_ctx);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ ctx =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ dom_sid_str_buf(map->sid, &keystr);
+
+ DEBUG(10, ("Fetching record %s\n", keystr.buf));
+
+ /* Check if sid is present in database */
+ ret = dbwrap_fetch_bystring(ctx->db, tmp_ctx, keystr.buf, &data);
+ if (!NT_STATUS_IS_OK(ret)) {
+ DEBUG(10, ("Record %s not found\n", keystr.buf));
+ ret = NT_STATUS_NONE_MAPPED;
+ goto done;
+ }
+
+ /* What type of record is this ? */
+ if (sscanf((const char *)data.dptr, "UID %lu", &rec_id) == 1) {
+ /* Try a UID record. */
+ map->xid.id = rec_id;
+ map->xid.type = ID_TYPE_UID;
+ DEBUG(10,
+ ("Found uid record %s -> %s \n", keystr.buf,
+ (const char *)data.dptr));
+ ret = NT_STATUS_OK;
+
+ } else if (sscanf((const char *)data.dptr, "GID %lu", &rec_id) == 1) {
+ /* Try a GID record. */
+ map->xid.id = rec_id;
+ map->xid.type = ID_TYPE_GID;
+ DEBUG(10,
+ ("Found gid record %s -> %s \n", keystr.buf,
+ (const char *)data.dptr));
+ ret = NT_STATUS_OK;
+
+ } else { /* Unknown record type ! */
+ DEBUG(2,
+ ("Found INVALID record %s -> %s\n", keystr.buf,
+ (const char *)data.dptr));
+ ret = NT_STATUS_INTERNAL_DB_ERROR;
+ goto done;
+ }
+
+ /* apply filters before returning result */
+ if (!idmap_unix_id_is_in_range(map->xid.id, dom)) {
+ DEBUG(5,
+ ("Requested id (%u) out of range (%u - %u). Filtered!\n",
+ map->xid.id, dom->low_id, dom->high_id));
+ ret = NT_STATUS_NONE_MAPPED;
+ }
+
+ done:
+ talloc_free(tmp_ctx);
+ return ret;
+}
+
+/**********************************
+ lookup a set of sids
+**********************************/
+
+struct idmap_tdb_common_sids_to_unixids_context {
+ struct idmap_domain *dom;
+ struct id_map **ids;
+ bool allocate_unmapped;
+ NTSTATUS(*sid_to_unixid_fn) (struct idmap_domain * dom,
+ struct id_map * map);
+};
+
+static NTSTATUS idmap_tdb_common_sids_to_unixids_action(struct db_context *db,
+ void *private_data)
+{
+ struct idmap_tdb_common_sids_to_unixids_context *state = private_data;
+ size_t i, num_mapped = 0, num_required = 0;
+ NTSTATUS ret = NT_STATUS_OK;
+
+ DEBUG(10, ("idmap_tdb_common_sids_to_unixids: "
+ " domain: [%s], allocate: %s\n",
+ state->dom->name, state->allocate_unmapped ? "yes" : "no"));
+
+ for (i = 0; state->ids[i]; i++) {
+ if ((state->ids[i]->status == ID_UNKNOWN) ||
+ /* retry if we could not map in previous run: */
+ (state->ids[i]->status == ID_UNMAPPED)) {
+ NTSTATUS ret2;
+
+ ret2 = state->sid_to_unixid_fn(state->dom,
+ state->ids[i]);
+
+ if (!NT_STATUS_IS_OK(ret2)) {
+
+ /* if it is just a failed mapping, continue */
+ if (NT_STATUS_EQUAL
+ (ret2, NT_STATUS_NONE_MAPPED)) {
+
+ /* make sure it is marked as unmapped */
+ state->ids[i]->status = ID_UNMAPPED;
+ ret = STATUS_SOME_UNMAPPED;
+ } else {
+ /*
+ * some fatal error occurred,
+ * return immediately
+ */
+ ret = ret2;
+ goto done;
+ }
+ } else {
+ /* all ok, id is mapped */
+ state->ids[i]->status = ID_MAPPED;
+ }
+ }
+
+ if (state->ids[i]->status == ID_MAPPED) {
+ num_mapped += 1;
+ }
+
+ if ((state->ids[i]->status == ID_UNMAPPED) &&
+ state->allocate_unmapped) {
+ ret =
+ idmap_tdb_common_new_mapping(state->dom,
+ state->ids[i]);
+ DBG_DEBUG("idmap_tdb_common_new_mapping returned %s\n",
+ nt_errstr(ret));
+ if (NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED)) {
+ if (state->ids[i]->status == ID_REQUIRE_TYPE) {
+ num_required += 1;
+ continue;
+ }
+ }
+ if (!NT_STATUS_IS_OK(ret)) {
+ ret = STATUS_SOME_UNMAPPED;
+ continue;
+ }
+ num_mapped += 1;
+ }
+ }
+
+done:
+
+ if (NT_STATUS_IS_OK(ret) ||
+ NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED)) {
+ if (i == 0 || num_mapped == 0) {
+ ret = NT_STATUS_NONE_MAPPED;
+ } else if (num_mapped < i) {
+ ret = STATUS_SOME_UNMAPPED;
+ } else {
+ ret = NT_STATUS_OK;
+ }
+ if (num_required > 0) {
+ ret = STATUS_SOME_UNMAPPED;
+ }
+ }
+
+ return ret;
+}
+
+NTSTATUS idmap_tdb_common_sids_to_unixids(struct idmap_domain * dom,
+ struct id_map ** ids)
+{
+ NTSTATUS ret;
+ int i;
+ struct idmap_tdb_common_sids_to_unixids_context state;
+ struct idmap_tdb_common_context *ctx;
+
+ ctx =
+ talloc_get_type_abort(dom->private_data,
+ struct idmap_tdb_common_context);
+
+ /* initialize the status to avoid surprise */
+ for (i = 0; ids[i]; i++) {
+ ids[i]->status = ID_UNKNOWN;
+ }
+
+ state.dom = dom;
+ state.ids = ids;
+ state.allocate_unmapped = false;
+ if (ctx->sid_to_unixid_fn == NULL) {
+ state.sid_to_unixid_fn = idmap_tdb_common_sid_to_unixid;
+ } else {
+ state.sid_to_unixid_fn = ctx->sid_to_unixid_fn;
+ }
+
+ ret = idmap_tdb_common_sids_to_unixids_action(ctx->db, &state);
+
+ if ( (NT_STATUS_EQUAL(ret, STATUS_SOME_UNMAPPED) ||
+ NT_STATUS_EQUAL(ret, NT_STATUS_NONE_MAPPED)) &&
+ !dom->read_only) {
+ state.allocate_unmapped = true;
+ ret = dbwrap_trans_do(ctx->db,
+ idmap_tdb_common_sids_to_unixids_action,
+ &state);
+ }
+
+ return ret;
+}
diff --git a/source3/winbindd/idmap_tdb_common.h b/source3/winbindd/idmap_tdb_common.h
new file mode 100644
index 0000000..3343b58
--- /dev/null
+++ b/source3/winbindd/idmap_tdb_common.h
@@ -0,0 +1,137 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ common functions for TDB based idmapping backends
+
+ Copyright (C) Christian Ambach 2012
+
+ These functions were initially copied over from idmap_tdb.c and idmap_tdb2.c
+ which are:
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Simo Sorce 2003-2006
+ Copyright (C) Michael Adam 2009-2010
+ Copyright (C) Andrew Tridgell 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#ifndef _IDMAP_TDB_COMMON_H_
+#define _IDMAP_TDB_COMMON_H_
+
+#include "includes.h"
+#include "idmap.h"
+#include "dbwrap/dbwrap.h"
+
+/*
+ * this must be stored in idmap_domain->private_data
+ * when using idmap_tdb_common_get_new_id and the
+ * mapping functions idmap_tdb_common_unixid(s)_to_sids
+ *
+ * private_data can be used for backend specific
+ * configuration data (e.g. idmap script in idmap_tdb2)
+ *
+ */
+struct idmap_tdb_common_context {
+ struct db_context *db;
+ struct idmap_rw_ops *rw_ops;
+ /*
+ * what is the maximum xid to be allocated
+ * this is typically just dom->high_id
+ */
+ uint32_t max_id;
+ const char *hwmkey_uid;
+ const char *hwmkey_gid;
+ /**
+ * if not set, idmap_tdb_common_unixids_to_sid will be used by
+ * idmap_tdb_common_unixids_to_sids
+ */
+ NTSTATUS(*unixid_to_sid_fn) (struct idmap_domain *dom,
+ struct id_map * map);
+ /*
+ * if not set, idmap_tdb_common_sid_to_id will be used by
+ * idmap_tdb_common_sids_to_unixids
+ */
+ NTSTATUS(*sid_to_unixid_fn) (struct idmap_domain *dom,
+ struct id_map * map);
+ void *private_data;
+};
+
+/**
+ * Allocate a new unix-ID.
+ * For now this is for the default idmap domain only.
+ * Should be extended later on.
+ */
+NTSTATUS idmap_tdb_common_get_new_id(struct idmap_domain *dom,
+ struct unixid *id);
+
+/*
+ * store a mapping into the idmap database
+ *
+ * the entries that will be stored are
+ * UID map->xid.id => map->sid and map->sid => UID map->xid.id
+ * or
+ * GID map->xid.id => map->sid and map->sid => GID map->xid.id
+ *
+ * for example
+ * UID 12345 = S-1-5-21-297746067-1479432880-4056370663
+ * S-1-5-21-297746067-1479432880-4056370663 = UID 12345
+ *
+ */
+NTSTATUS idmap_tdb_common_set_mapping(struct idmap_domain *dom,
+ const struct id_map *map);
+
+/*
+ * Create a new mapping for an unmapped SID, also allocating a new ID.
+ * This should be run inside a transaction.
+ *
+ * TODO:
+ * Properly integrate this with multi domain idmap config:
+ * Currently, the allocator is default-config only.
+ */
+NTSTATUS idmap_tdb_common_new_mapping(struct idmap_domain *dom,
+ struct id_map *map);
+
+/*
+ * default multiple id to sid lookup function
+ *
+ * will call idmap_tdb_common_unixid_to_sid for each mapping
+ * if no other function to lookup unixid_to_sid was given in
+ * idmap_tdb_common_context
+ */
+NTSTATUS idmap_tdb_common_unixids_to_sids(struct idmap_domain *dom,
+ struct id_map **ids);
+
+/*
+ * default single id to sid lookup function
+ *
+ * will read the entries written by idmap_tdb_common_set_mapping
+ */
+NTSTATUS idmap_tdb_common_unixid_to_sid(struct idmap_domain *dom,
+ struct id_map *map);
+
+/**********************************
+ Single sid to id lookup function.
+**********************************/
+
+NTSTATUS idmap_tdb_common_sid_to_unixid(struct idmap_domain *dom,
+ struct id_map *map);
+
+NTSTATUS idmap_tdb_common_sids_to_unixids(struct idmap_domain *dom,
+ struct id_map **ids);
+
+#endif /* _IDMAP_TDB_COMMON_H_ */
diff --git a/source3/winbindd/idmap_util.c b/source3/winbindd/idmap_util.c
new file mode 100644
index 0000000..fd2ae4a
--- /dev/null
+++ b/source3/winbindd/idmap_util.c
@@ -0,0 +1,137 @@
+/*
+ Unix SMB/CIFS implementation.
+ ID Mapping
+ Copyright (C) Simo Sorce 2003
+ Copyright (C) Jeremy Allison 2006
+ Copyright (C) Michael Adam 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "winbindd_proto.h"
+#include "idmap.h"
+#include "idmap_cache.h"
+#include "../libcli/security/security.h"
+#include "secrets.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_IDMAP
+
+/**
+ * check whether a given unix id is inside the filter range of an idmap domain
+ */
+bool idmap_unix_id_is_in_range(uint32_t id, struct idmap_domain *dom)
+{
+ if ((dom->low_id && (id < dom->low_id)) ||
+ (dom->high_id && (id > dom->high_id)))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Helper for unixids_to_sids: find entry by id in mapping array,
+ * search up to IDMAP_AD_MAX_IDS entries
+ */
+struct id_map *idmap_find_map_by_id(struct id_map **maps, enum id_type type,
+ uint32_t id)
+{
+ int i;
+
+ for (i = 0; maps[i] != NULL; i++) {
+ if ((maps[i]->xid.type == type) && (maps[i]->xid.id == id)) {
+ return maps[i];
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Helper for sids_to_unix_ids: find entry by SID in mapping array,
+ * search up to IDMAP_AD_MAX_IDS entries
+ */
+struct id_map *idmap_find_map_by_sid(struct id_map **maps, struct dom_sid *sid)
+{
+ int i;
+
+ for (i = 0; i < IDMAP_LDAP_MAX_IDS; i++) {
+ if (maps[i] == NULL) { /* end of the run */
+ return NULL;
+ }
+ if (dom_sid_equal(maps[i]->sid, sid)) {
+ return maps[i];
+ }
+ }
+
+ return NULL;
+}
+
+char *idmap_fetch_secret(const char *backend, const char *domain,
+ const char *identity)
+{
+ char *tmp, *ret;
+ int r;
+
+ r = asprintf(&tmp, "IDMAP_%s_%s", backend, domain);
+
+ if (r < 0)
+ return NULL;
+
+ /* make sure the key is case insensitive */
+ if (!strupper_m(tmp)) {
+ SAFE_FREE(tmp);
+ return NULL;
+ }
+
+ ret = secrets_fetch_generic(tmp, identity);
+
+ SAFE_FREE(tmp);
+
+ return ret;
+}
+
+struct id_map **id_map_ptrs_init(TALLOC_CTX *mem_ctx, size_t num_ids)
+{
+ struct id_map **ptrs;
+ struct id_map *maps;
+ struct dom_sid *sids;
+ size_t i;
+
+ ptrs = talloc_array(mem_ctx, struct id_map *, num_ids+1);
+ if (ptrs == NULL) {
+ return NULL;
+ }
+ maps = talloc_array(ptrs, struct id_map, num_ids);
+ if (maps == NULL) {
+ TALLOC_FREE(ptrs);
+ return NULL;
+ }
+ sids = talloc_zero_array(ptrs, struct dom_sid, num_ids);
+ if (sids == NULL) {
+ TALLOC_FREE(ptrs);
+ return NULL;
+ }
+
+ for (i=0; i<num_ids; i++) {
+ maps[i] = (struct id_map) { .sid = &sids[i] };
+ ptrs[i] = &maps[i];
+ }
+ ptrs[num_ids] = NULL;
+
+ return ptrs;
+}
diff --git a/source3/winbindd/nss_info.c b/source3/winbindd/nss_info.c
new file mode 100644
index 0000000..9c502e8
--- /dev/null
+++ b/source3/winbindd/nss_info.c
@@ -0,0 +1,377 @@
+/*
+ Unix SMB/CIFS implementation.
+ Idmap NSS headers
+
+ Copyright (C) Gerald Carter 2006
+ Copyright (C) Michael Adam 2008
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "nss_info.h"
+
+static struct nss_function_entry *backends = NULL;
+static struct nss_function_entry *default_backend = NULL;
+static struct nss_domain_entry *nss_domain_list = NULL;
+
+/**********************************************************************
+ Get idmap nss methods.
+**********************************************************************/
+
+static struct nss_function_entry *nss_get_backend(const char *name )
+{
+ struct nss_function_entry *entry = backends;
+
+ for(entry = backends; entry; entry = entry->next) {
+ if ( strequal(entry->name, name) )
+ return entry;
+ }
+
+ return NULL;
+}
+
+/*********************************************************************
+ Allow a module to register itself as a backend.
+**********************************************************************/
+
+ NTSTATUS smb_register_idmap_nss(int version, const char *name,
+ const struct nss_info_methods *methods)
+{
+ struct nss_function_entry *entry;
+
+ if ((version != SMB_NSS_INFO_INTERFACE_VERSION)) {
+ DEBUG(0, ("smb_register_idmap_nss: Failed to register idmap_nss module.\n"
+ "The module was compiled against SMB_NSS_INFO_INTERFACE_VERSION %d,\n"
+ "current SMB_NSS_INFO_INTERFACE_VERSION is %d.\n"
+ "Please recompile against the current version of samba!\n",
+ version, SMB_NSS_INFO_INTERFACE_VERSION));
+ return NT_STATUS_OBJECT_TYPE_MISMATCH;
+ }
+
+ if (!name || !name[0] || !methods) {
+ DEBUG(0,("smb_register_idmap_nss: called with NULL pointer or empty name!\n"));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if ( nss_get_backend(name) ) {
+ DEBUG(5,("smb_register_idmap_nss: idmap module %s "
+ "already registered!\n", name));
+ return NT_STATUS_OBJECT_NAME_COLLISION;
+ }
+
+ entry = SMB_XMALLOC_P(struct nss_function_entry);
+ entry->name = smb_xstrdup(name);
+ entry->methods = methods;
+
+ DLIST_ADD(backends, entry);
+ DEBUG(5, ("smb_register_idmap_nss: Successfully added idmap "
+ "nss backend '%s'\n", name));
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ *******************************************************************/
+
+static bool parse_nss_parm(TALLOC_CTX *mem_ctx,
+ const char *config,
+ char **backend,
+ char **domain)
+{
+ char *p;
+
+ *backend = *domain = NULL;
+
+ if ( !config )
+ return False;
+
+ p = strchr( config, ':' );
+
+ /* if no : then the string must be the backend name only */
+
+ if ( !p ) {
+ *backend = talloc_strdup(mem_ctx, config);
+ return (*backend != NULL);
+ }
+
+ /* split the string and return the two parts */
+
+ if ( strlen(p+1) > 0 ) {
+ *domain = talloc_strdup(mem_ctx, p + 1);
+ }
+
+ *backend = talloc_strndup(mem_ctx, config, PTR_DIFF(p, config));
+ return (*backend != NULL);
+}
+
+static NTSTATUS nss_domain_list_add_domain(const char *domain,
+ struct nss_function_entry *nss_backend)
+{
+ struct nss_domain_entry *nss_domain;
+
+ nss_domain = talloc_zero(nss_domain_list, struct nss_domain_entry);
+ if (!nss_domain) {
+ DEBUG(0, ("nss_domain_list_add_domain: talloc() failure!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ nss_domain->backend = nss_backend;
+ if (domain) {
+ nss_domain->domain = talloc_strdup(nss_domain, domain);
+ if (!nss_domain->domain) {
+ DEBUG(0, ("nss_domain_list_add_domain: talloc() "
+ "failure!\n"));
+ TALLOC_FREE(nss_domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ nss_domain->init_status = nss_domain->backend->methods->init(nss_domain);
+ if (!NT_STATUS_IS_OK(nss_domain->init_status)) {
+ DEBUG(0, ("nss_init: Failed to init backend '%s' for domain "
+ "'%s'!\n", nss_backend->name, nss_domain->domain));
+ }
+
+ DLIST_ADD(nss_domain_list, nss_domain);
+
+ DEBUG(10, ("Added domain '%s' with backend '%s' to nss_domain_list.\n",
+ domain, nss_backend->name));
+
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ Each nss backend must not store global state, but rather be able
+ to initialize the state on a per domain basis.
+ *******************************************************************/
+
+static NTSTATUS nss_init(const char **nss_list)
+{
+ NTSTATUS status;
+ static bool nss_initialized = false;
+ int i;
+ char *backend = NULL, *domain = NULL;
+ struct nss_function_entry *nss_backend;
+ TALLOC_CTX *frame;
+
+ /* check for previous successful initializations */
+
+ if (nss_initialized) {
+ return NT_STATUS_OK;
+ }
+
+ frame = talloc_stackframe();
+
+ /* The "template" backend should always be registered as it
+ is a static module */
+
+ nss_backend = nss_get_backend("template");
+ if (nss_backend == NULL) {
+ static_init_nss_info(NULL);
+ }
+
+ /* Create the list of nss_domains (loading any shared plugins
+ as necessary) */
+
+ for ( i=0; nss_list && nss_list[i]; i++ ) {
+ bool ok;
+
+ ok = parse_nss_parm(frame, nss_list[i], &backend, &domain);
+ if (!ok) {
+ DEBUG(0,("nss_init: failed to parse \"%s\"!\n",
+ nss_list[i]));
+ continue;
+ }
+
+ DEBUG(10, ("parsed backend = '%s', domain = '%s'\n",
+ backend, domain));
+
+ /* validate the backend */
+
+ nss_backend = nss_get_backend(backend);
+ if (nss_backend == NULL) {
+ /*
+ * This is a freaking hack. We don't have proper
+ * modules for nss_info backends. Right now we have
+ * our standard nss_info backends in the ad backend.
+ */
+ status = smb_probe_module("idmap", "ad");
+ if ( !NT_STATUS_IS_OK(status) ) {
+ continue;
+ }
+ }
+
+ nss_backend = nss_get_backend(backend);
+ if (nss_backend == NULL) {
+ /* attempt to register the backend */
+ status = smb_probe_module( "nss_info", backend );
+ if ( !NT_STATUS_IS_OK(status) ) {
+ continue;
+ }
+ }
+
+ /* try again */
+ nss_backend = nss_get_backend(backend);
+ if (nss_backend == NULL) {
+ DEBUG(0, ("nss_init: unregistered backend %s!. "
+ "Skipping\n", backend));
+ continue;
+ }
+
+ /*
+ * The first config item of the list without an explicit domain
+ * is treated as the default nss info backend.
+ */
+ if ((domain == NULL) && (default_backend == NULL)) {
+ DEBUG(10, ("nss_init: using '%s' as default backend.\n",
+ backend));
+ default_backend = nss_backend;
+ }
+
+ status = nss_domain_list_add_domain(domain, nss_backend);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* cleanup */
+
+ TALLOC_FREE(domain);
+ TALLOC_FREE(backend);
+ }
+
+
+ if ( !nss_domain_list ) {
+ DEBUG(3,("nss_init: no nss backends configured. "
+ "Defaulting to \"template\".\n"));
+
+
+ /* we should default to use template here */
+ }
+
+ nss_initialized = true;
+
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+}
+
+/********************************************************************
+ *******************************************************************/
+
+static struct nss_domain_entry *find_nss_domain( const char *domain )
+{
+ NTSTATUS status;
+ struct nss_domain_entry *p;
+
+ status = nss_init( lp_winbind_nss_info() );
+ if ( !NT_STATUS_IS_OK(status) ) {
+ DEBUG(4,("find_nss_domain: Failed to init nss_info API "
+ "(%s)!\n", nt_errstr(status)));
+ return NULL;
+ }
+
+ for ( p=nss_domain_list; p; p=p->next ) {
+ if ( strequal( p->domain, domain ) )
+ break;
+ }
+
+ /* If we didn't find a match, then use the default nss backend */
+
+ if ( !p ) {
+ if (!default_backend) {
+ return NULL;
+ }
+
+ status = nss_domain_list_add_domain(domain, default_backend);
+ if (!NT_STATUS_IS_OK(status)) {
+ return NULL;
+ }
+
+ /*
+ * HACK ALERT:
+ * Here, we use the fact that the new domain was added at
+ * the beginning of the list...
+ */
+ p = nss_domain_list;
+ }
+
+ if ( !NT_STATUS_IS_OK( p->init_status ) ) {
+ p->init_status = p->backend->methods->init( p );
+ }
+
+ return p;
+}
+
+/********************************************************************
+ *******************************************************************/
+
+ NTSTATUS nss_map_to_alias( TALLOC_CTX *mem_ctx, const char *domain,
+ const char *name, char **alias )
+{
+ struct nss_domain_entry *p;
+ const struct nss_info_methods *m;
+
+ if ( (p = find_nss_domain( domain )) == NULL ) {
+ DEBUG(4,("nss_map_to_alias: Failed to find nss domain pointer for %s\n",
+ domain ));
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ m = p->backend->methods;
+
+ return m->map_to_alias(mem_ctx, p, name, alias);
+}
+
+
+/********************************************************************
+ *******************************************************************/
+
+ NTSTATUS nss_map_from_alias( TALLOC_CTX *mem_ctx, const char *domain,
+ const char *alias, char **name )
+{
+ struct nss_domain_entry *p;
+ const struct nss_info_methods *m;
+
+ if ( (p = find_nss_domain( domain )) == NULL ) {
+ DEBUG(4,("nss_map_from_alias: Failed to find nss domain pointer for %s\n",
+ domain ));
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ m = p->backend->methods;
+
+ return m->map_from_alias( mem_ctx, p, alias, name );
+}
+
+/********************************************************************
+ *******************************************************************/
+
+ NTSTATUS nss_close( const char *parameters )
+{
+ struct nss_domain_entry *p = nss_domain_list;
+ struct nss_domain_entry *q;
+
+ while ( p && p->backend && p->backend->methods ) {
+ /* close the backend */
+ p->backend->methods->close_fn();
+
+ /* free the memory */
+ q = p;
+ p = p->next;
+ TALLOC_FREE( q );
+ }
+
+ return NT_STATUS_OK;
+}
+
diff --git a/source3/winbindd/nss_info_template.c b/source3/winbindd/nss_info_template.c
new file mode 100644
index 0000000..c58a7fc
--- /dev/null
+++ b/source3/winbindd/nss_info_template.c
@@ -0,0 +1,80 @@
+/*
+ Unix SMB/CIFS implementation.
+ idMap nss template plugin
+
+ Copyright (C) Gerald Carter 2006
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ads.h"
+#include "nss_info.h"
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS nss_template_init( struct nss_domain_entry *e )
+{
+ return NT_STATUS_OK;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS nss_template_map_to_alias( TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *name,
+ char **alias )
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/**********************************************************************
+ *********************************************************************/
+
+static NTSTATUS nss_template_map_from_alias( TALLOC_CTX *mem_ctx,
+ struct nss_domain_entry *e,
+ const char *alias,
+ char **name )
+{
+ return NT_STATUS_NOT_IMPLEMENTED;
+}
+
+/************************************************************************
+ ***********************************************************************/
+
+static NTSTATUS nss_template_close( void )
+{
+ return NT_STATUS_OK;
+}
+
+
+/************************************************************************
+ ***********************************************************************/
+
+static struct nss_info_methods nss_template_methods = {
+ .init = nss_template_init,
+ .map_to_alias = nss_template_map_to_alias,
+ .map_from_alias = nss_template_map_from_alias,
+ .close_fn = nss_template_close
+};
+
+NTSTATUS nss_info_template_init(TALLOC_CTX *mem_ctx)
+{
+ return smb_register_idmap_nss(SMB_NSS_INFO_INTERFACE_VERSION,
+ "template",
+ &nss_template_methods);
+}
+
diff --git a/source3/winbindd/wb_alias_members.c b/source3/winbindd/wb_alias_members.c
new file mode 100644
index 0000000..06c2292
--- /dev/null
+++ b/source3/winbindd/wb_alias_members.c
@@ -0,0 +1,137 @@
+/*
+ Unix SMB/CIFS implementation.
+ async alias_members
+ Copyright (C) Pavel Filipenský 2023
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+
+struct wb_alias_members_state {
+ struct tevent_context *ev;
+ struct dom_sid sid;
+ struct wbint_SidArray sids;
+};
+
+static void wb_alias_members_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_alias_members_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid,
+ enum lsa_SidType type,
+ int max_nesting)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_alias_members_state *state;
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_alias_members_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_INFO("WB command alias_members start.\nLooking up SID %s.\n",
+ dom_sid_str_buf(sid, &buf));
+
+ if (max_nesting <= 0) {
+ D_DEBUG("Finished. The depth based on 'winbind expand groups' is %d.\n", max_nesting);
+ state->sids.num_sids = 0;
+ state->sids.sids = NULL;
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ sid_copy(&state->sid, sid);
+
+ status = lookup_usergroups_cached(state,
+ &state->sid,
+ &state->sids.num_sids,
+ &state->sids.sids);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ domain = find_domain_from_sid_noinit(&state->sid);
+ if (domain == NULL) {
+ DBG_WARNING("could not find domain entry for sid %s\n",
+ dom_sid_str_buf(&state->sid, &buf));
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_ALIAS);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_LookupAliasMembers_send(state,
+ ev,
+ dom_child_handle(domain),
+ &state->sid,
+ type,
+ &state->sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_alias_members_done, req);
+ return req;
+}
+
+static void wb_alias_members_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct wb_alias_members_state *state =
+ tevent_req_data(req, struct wb_alias_members_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupAliasMembers_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_alias_members_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_sids,
+ struct dom_sid **sids)
+{
+ struct wb_alias_members_state *state =
+ tevent_req_data(req, struct wb_alias_members_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *num_sids = state->sids.num_sids;
+ *sids = talloc_move(mem_ctx, &state->sids.sids);
+
+ D_INFO("WB command alias_members end.\nReceived %" PRIu32 " SID(s).\n",
+ *num_sids);
+ if (CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ for (i = 0; i < *num_sids; i++) {
+ struct dom_sid_buf buf;
+ D_INFO("%" PRIu32 ": %s\n",
+ i,
+ dom_sid_str_buf(&(*sids)[i], &buf));
+ }
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_dsgetdcname.c b/source3/winbindd/wb_dsgetdcname.c
new file mode 100644
index 0000000..0f6acaa
--- /dev/null
+++ b/source3/winbindd/wb_dsgetdcname.c
@@ -0,0 +1,255 @@
+/*
+ Unix SMB/CIFS implementation.
+ async dsgetdcname
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "librpc/gen_ndr/ndr_netlogon.h"
+#include "lib/gencache.h"
+
+struct wb_dsgetdcname_state {
+ const char *domain_name;
+ struct GUID domain_guid;
+ struct netr_DsRGetDCNameInfo *dcinfo;
+};
+
+static void wb_dsgetdcname_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_dsgetdcname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ const char *site_name,
+ uint32_t flags)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_dsgetdcname_state *state;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ struct GUID *guid_ptr = NULL;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_dsgetdcname_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command dsgetdcname start.\n"
+ "Search domain name %s and site name %s.\n",
+ domain_name,
+ site_name);
+ if (strequal(domain_name, "BUILTIN")) {
+ /*
+ * This makes no sense
+ */
+ tevent_req_nterror(req, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
+ return tevent_req_post(req, ev);
+ }
+
+ if (strequal(domain_name, get_global_sam_name())) {
+ int role = lp_server_role();
+ if ( role != ROLE_ACTIVE_DIRECTORY_DC ) {
+ /*
+ * Two options here: Give back our own address, or say there's
+ * nobody around. Right now opting for the latter, one measure
+ * to prevent the loopback connects. This might change if
+ * needed.
+ */
+ tevent_req_nterror(req, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ if (IS_DC) {
+ /*
+ * We have to figure out the DC ourselves
+ */
+ child_binding_handle = locator_child_handle();
+ } else {
+ struct winbindd_domain *domain = find_our_domain();
+ child_binding_handle = dom_child_handle(domain);
+ }
+
+ if (domain_guid != NULL) {
+ /* work around a const issue in rpccli_ autogenerated code */
+ state->domain_guid = *domain_guid;
+ guid_ptr = &state->domain_guid;
+ }
+
+ state->domain_name = talloc_strdup(state, domain_name);
+ if (tevent_req_nomem(state->domain_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_DsGetDcName_send(
+ state, ev, child_binding_handle, domain_name, guid_ptr, site_name,
+ flags, &state->dcinfo);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_dsgetdcname_done, req);
+ return req;
+}
+
+static void wb_dsgetdcname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_dsgetdcname_state *state = tevent_req_data(
+ req, struct wb_dsgetdcname_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_DsGetDcName_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_dsgetdcname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct netr_DsRGetDCNameInfo **pdcinfo)
+{
+ struct wb_dsgetdcname_state *state = tevent_req_data(
+ req, struct wb_dsgetdcname_state);
+ NTSTATUS status;
+
+ D_INFO("WB command dsgetdcname for %s end.\n",
+ state->domain_name);
+ if (tevent_req_is_nterror(req, &status)) {
+ D_NOTICE("Failed for %s with %s.\n",
+ state->domain_name,
+ nt_errstr(status));
+ return status;
+ }
+ *pdcinfo = talloc_move(mem_ctx, &state->dcinfo);
+ return NT_STATUS_OK;
+}
+
+NTSTATUS wb_dsgetdcname_gencache_set(const char *domname,
+ struct netr_DsRGetDCNameInfo *dcinfo)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+ char *key;
+ bool ok;
+
+ key = talloc_asprintf_strupper_m(talloc_tos(), "DCINFO/%s", domname);
+ if (key == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (DEBUGLEVEL >= DBGLVL_DEBUG) {
+ NDR_PRINT_DEBUG(netr_DsRGetDCNameInfo, dcinfo);
+ }
+
+ ndr_err = ndr_push_struct_blob(
+ &blob, key, dcinfo,
+ (ndr_push_flags_fn_t)ndr_push_netr_DsRGetDCNameInfo);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ NTSTATUS status = ndr_map_error2ntstatus(ndr_err);
+ DBG_WARNING("ndr_push_struct_blob failed: %s\n",
+ ndr_errstr(ndr_err));
+ TALLOC_FREE(key);
+ return status;
+ }
+
+ ok = gencache_set_data_blob(key, blob, time(NULL)+3600);
+
+ if (!ok) {
+ DBG_WARNING("gencache_set_data_blob for key %s failed\n", key);
+ TALLOC_FREE(key);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ TALLOC_FREE(key);
+ return NT_STATUS_OK;
+}
+
+struct dcinfo_parser_state {
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx;
+ struct netr_DsRGetDCNameInfo *dcinfo;
+};
+
+static void dcinfo_parser(const struct gencache_timeout *timeout,
+ DATA_BLOB blob,
+ void *private_data)
+{
+ struct dcinfo_parser_state *state = private_data;
+ enum ndr_err_code ndr_err;
+
+ if (gencache_timeout_expired(timeout)) {
+ return;
+ }
+
+ state->dcinfo = talloc(state->mem_ctx, struct netr_DsRGetDCNameInfo);
+ if (state->dcinfo == NULL) {
+ state->status = NT_STATUS_NO_MEMORY;
+ return;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &blob, state->dcinfo, state->dcinfo,
+ (ndr_pull_flags_fn_t)ndr_pull_netr_DsRGetDCNameInfo);
+
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_pull_struct_blob failed\n");
+ state->status = ndr_map_error2ntstatus(ndr_err);
+ TALLOC_FREE(state->dcinfo);
+ return;
+ }
+
+ state->status = NT_STATUS_OK;
+}
+
+NTSTATUS wb_dsgetdcname_gencache_get(TALLOC_CTX *mem_ctx,
+ const char *domname,
+ struct netr_DsRGetDCNameInfo **dcinfo)
+{
+ struct dcinfo_parser_state state;
+ char *key;
+ bool ok;
+
+ key = talloc_asprintf_strupper_m(mem_ctx, "DCINFO/%s", domname);
+ if (key == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state = (struct dcinfo_parser_state) {
+ .status = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND,
+ .mem_ctx = mem_ctx,
+ };
+
+ ok = gencache_parse(key, dcinfo_parser, &state);
+ TALLOC_FREE(key);
+ if (!ok) {
+ return NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ }
+
+ if (!NT_STATUS_IS_OK(state.status)) {
+ return state.status;
+ }
+
+ if (DEBUGLEVEL >= DBGLVL_DEBUG) {
+ NDR_PRINT_DEBUG(netr_DsRGetDCNameInfo, state.dcinfo);
+ }
+
+ *dcinfo = state.dcinfo;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_getgrsid.c b/source3/winbindd/wb_getgrsid.c
new file mode 100644
index 0000000..4fd696d
--- /dev/null
+++ b/source3/winbindd/wb_getgrsid.c
@@ -0,0 +1,403 @@
+/*
+ Unix SMB/CIFS implementation.
+ async getgrsid
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+#include "lib/dbwrap/dbwrap.h"
+
+struct wb_getgrsid_state {
+ struct tevent_context *ev;
+ struct dom_sid sid;
+ int max_nesting;
+ const char *domname;
+ const char *name;
+ enum lsa_SidType type;
+ gid_t gid;
+ struct db_context *members;
+ uint32_t num_sids;
+ struct dom_sid *sids;
+};
+
+static void wb_getgrsid_lookupsid_done(struct tevent_req *subreq);
+static void wb_getgrsid_sid2gid_done(struct tevent_req *subreq);
+static void wb_getgrsid_got_members(struct tevent_req *subreq);
+static void wb_getgrsid_got_alias_members(struct tevent_req *subreq);
+
+struct tevent_req *wb_getgrsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *group_sid,
+ int max_nesting)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_getgrsid_state *state;
+ struct dom_sid_buf buf;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_getgrsid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command getgrsid start.\nLooking up group SID %s.\n", dom_sid_str_buf(group_sid, &buf));
+
+ sid_copy(&state->sid, group_sid);
+ state->ev = ev;
+ state->max_nesting = max_nesting;
+
+ if (dom_sid_in_domain(&global_sid_Unix_Groups, group_sid)) {
+ /* unmapped Unix groups must be resolved locally */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_lookupsid_send(state, ev, &state->sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_getgrsid_lookupsid_done, req);
+ return req;
+}
+
+static void wb_getgrsid_lookupsid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_getgrsid_state *state = tevent_req_data(
+ req, struct wb_getgrsid_state);
+ NTSTATUS status;
+
+ status = wb_lookupsid_recv(subreq, state, &state->type,
+ &state->domname, &state->name);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ switch (state->type) {
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ /*
+ * also treat user-type SIDS (they might map to ID_TYPE_BOTH)
+ */
+ case SID_NAME_USER:
+ case SID_NAME_COMPUTER:
+ break;
+ default:
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP);
+ return;
+ }
+
+ subreq = wb_sids2xids_send(state, state->ev, &state->sid, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_getgrsid_sid2gid_done, req);
+}
+
+static void wb_getgrsid_sid2gid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_getgrsid_state *state = tevent_req_data(
+ req, struct wb_getgrsid_state);
+ NTSTATUS status;
+ struct unixid xids[1];
+
+ status = wb_sids2xids_recv(subreq, xids, ARRAY_SIZE(xids));
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * We are filtering further down in sids2xids, but that filtering
+ * depends on the actual type of the sid handed in (as determined
+ * by lookupsids). Here we need to filter for the type of object
+ * actually requested, in this case uid.
+ */
+ if (!(xids[0].type == ID_TYPE_GID || xids[0].type == ID_TYPE_BOTH)) {
+ tevent_req_nterror(req, NT_STATUS_NONE_MAPPED);
+ return;
+ }
+
+ state->gid = (gid_t)xids[0].id;
+
+ switch (state->type) {
+ case SID_NAME_USER:
+ case SID_NAME_COMPUTER: {
+ /*
+ * special treatment for a user sid that is
+ * mapped to ID_TYPE_BOTH:
+ * create a group with the sid/xid as only member
+ */
+ const char *name;
+
+ if (xids[0].type != ID_TYPE_BOTH) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP);
+ return;
+ }
+
+ state->members = db_open_rbt(state);
+ if (tevent_req_nomem(state->members, req)) {
+ return;
+ }
+
+ name = fill_domain_username_talloc(talloc_tos(),
+ state->domname,
+ state->name,
+ true /* can_assume */);
+ if (tevent_req_nomem(name, req)) {
+ return;
+ }
+
+ status = add_member_to_db(state->members, &state->sid, name);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ tevent_req_done(req);
+ return;
+ }
+ case SID_NAME_ALIAS:
+ subreq = wb_alias_members_send(state,
+ state->ev,
+ &state->sid,
+ state->type,
+ state->max_nesting);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ /* Decrement the depth based on 'winbind expand groups' */
+ state->max_nesting--;
+ tevent_req_set_callback(subreq,
+ wb_getgrsid_got_alias_members,
+ req);
+ break;
+ case SID_NAME_DOM_GRP:
+ subreq = wb_group_members_send(state,
+ state->ev,
+ &state->sid,
+ 1,
+ &state->type,
+ state->max_nesting);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_getgrsid_got_members, req);
+ break;
+ case SID_NAME_WKN_GRP:
+ state->members = db_open_rbt(state);
+ if (tevent_req_nomem(state->members, req)) {
+ return;
+ }
+ tevent_req_done(req);
+ return;
+ default:
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP);
+ break;
+ }
+}
+
+static void wb_getgrsid_got_alias_members_names(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct wb_getgrsid_state *state =
+ tevent_req_data(req, struct wb_getgrsid_state);
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransNameArray *names = NULL;
+ NTSTATUS status;
+ uint32_t li;
+ uint32_t num_sids = 0;
+ struct dom_sid *sids = NULL;
+ enum lsa_SidType *types = NULL;
+
+ status = wb_lookupsids_recv(subreq, state, &domains, &names);
+
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+
+ if (domains == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+
+ if (names == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+
+ state->members = db_open_rbt(state);
+ if (tevent_req_nomem(state->members, req)) {
+ return;
+ }
+
+ for (li = 0; li < state->num_sids; li++) {
+ struct lsa_TranslatedName *n = &names->names[li];
+
+ if (n->sid_type == SID_NAME_USER ||
+ n->sid_type == SID_NAME_COMPUTER) {
+ const char *name = fill_domain_username_talloc(
+ talloc_tos(),
+ domains->domains[n->sid_index].name.string,
+ n->name.string,
+ false /* can_assume */);
+ if (tevent_req_nomem(name, req)) {
+ return;
+ }
+
+ status = add_member_to_db(state->members,
+ &state->sids[li],
+ name);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ } else if (n->sid_type == SID_NAME_DOM_GRP) {
+ sids = talloc_realloc(talloc_tos(),
+ sids,
+ struct dom_sid,
+ num_sids + 1);
+ if (tevent_req_nomem(sids, req)) {
+ return;
+ }
+ sids[num_sids] = state->sids[li];
+ types = talloc_realloc(talloc_tos(),
+ types,
+ enum lsa_SidType,
+ num_sids + 1);
+ if (tevent_req_nomem(types, req)) {
+ return;
+ }
+ types[num_sids] = n->sid_type;
+ num_sids++;
+ } else {
+ struct dom_sid_buf buf;
+ D_DEBUG("SID %s with sid_type=%d is ignored!\n",
+ dom_sid_str_buf(&state->sids[li], &buf),
+ n->sid_type);
+ }
+ }
+
+ TALLOC_FREE(names);
+ TALLOC_FREE(domains);
+
+ if (num_sids == 0) {
+ tevent_req_done(req);
+ return;
+ }
+ subreq = wb_group_members_send(state,
+ state->ev,
+ sids,
+ num_sids,
+ types,
+ state->max_nesting);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_getgrsid_got_members, req);
+}
+
+static void wb_getgrsid_got_alias_members(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq, struct tevent_req);
+ struct wb_getgrsid_state *state =
+ tevent_req_data(req, struct wb_getgrsid_state);
+ NTSTATUS status;
+
+ status = wb_alias_members_recv(subreq,
+ state,
+ &state->num_sids,
+ &state->sids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = wb_lookupsids_send(state,
+ state->ev,
+ state->sids,
+ state->num_sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ wb_getgrsid_got_alias_members_names,
+ req);
+}
+
+static void wb_getgrsid_got_members(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_getgrsid_state *state = tevent_req_data(
+ req, struct wb_getgrsid_state);
+ NTSTATUS status;
+ struct db_context *members_prev = state->members;
+
+ status = wb_group_members_recv(subreq, state, &state->members);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ /*
+ * If we have called wb_alias_members_send(), members_prev
+ * might already contain users that are direct members of alias,
+ * add to them the users from nested groups.
+ */
+ if (members_prev != NULL) {
+ status = dbwrap_merge_dbs(state->members,
+ members_prev,
+ TDB_REPLACE);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_getgrsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ const char **domname, const char **name, gid_t *gid,
+ struct db_context **members)
+{
+ struct wb_getgrsid_state *state = tevent_req_data(
+ req, struct wb_getgrsid_state);
+ NTSTATUS status;
+
+ D_INFO("WB command getgrsid end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+ *domname = talloc_move(mem_ctx, &state->domname);
+ *name = talloc_move(mem_ctx, &state->name);
+ *gid = state->gid;
+ *members = talloc_move(mem_ctx, &state->members);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_getpwsid.c b/source3/winbindd/wb_getpwsid.c
new file mode 100644
index 0000000..7d04c39
--- /dev/null
+++ b/source3/winbindd/wb_getpwsid.c
@@ -0,0 +1,156 @@
+/*
+ Unix SMB/CIFS implementation.
+ async getpwsid
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+#include "lib/util/string_wrappers.h"
+#include "source3/lib/substitute.h"
+
+struct wb_getpwsid_state {
+ struct tevent_context *ev;
+ struct dom_sid sid;
+ struct wbint_userinfo *userinfo;
+ struct winbindd_pw *pw;
+};
+
+static void wb_getpwsid_queryuser_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_getpwsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *user_sid,
+ struct winbindd_pw *pw)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_getpwsid_state *state;
+ struct dom_sid_buf buf;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_getpwsid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_INFO("WB command getpwsid start.\nQuery user SID %s.\n", dom_sid_str_buf(user_sid, &buf));
+ sid_copy(&state->sid, user_sid);
+ state->ev = ev;
+ state->pw = pw;
+
+ if (dom_sid_in_domain(&global_sid_Unix_Users, user_sid)) {
+ /* unmapped Unix users must be resolved locally */
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_queryuser_send(state, ev, &state->sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_getpwsid_queryuser_done, req);
+ return req;
+}
+
+static void wb_getpwsid_queryuser_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_getpwsid_state *state = tevent_req_data(
+ req, struct wb_getpwsid_state);
+ struct winbindd_pw *pw = state->pw;
+ struct wbint_userinfo *info;
+ fstring acct_name;
+ const char *output_username = NULL;
+ char *mapped_name = NULL;
+ char *tmp;
+ NTSTATUS status;
+
+ status = wb_queryuser_recv(subreq, state, &state->userinfo);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ info = state->userinfo;
+
+ pw->pw_uid = info->uid;
+ pw->pw_gid = info->primary_gid;
+
+ fstrcpy(acct_name, info->acct_name);
+ if (!strlower_m(acct_name)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+
+ /*
+ * TODO:
+ * This function should be called in 'idmap winbind child'. It shouldn't
+ * be a blocking call, but for this we need to add a new function for
+ * winbind.idl. This is a fix which can be backported for now.
+ */
+ status = normalize_name_map(state,
+ info->domain_name,
+ acct_name,
+ &mapped_name);
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) {
+ fstrcpy(acct_name, mapped_name);
+ }
+ output_username = fill_domain_username_talloc(state,
+ info->domain_name,
+ acct_name,
+ true);
+ if (output_username == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+
+ strlcpy(pw->pw_name, output_username, sizeof(pw->pw_name));
+
+ strlcpy(pw->pw_gecos, info->full_name ? info->full_name : "",
+ sizeof(pw->pw_gecos));
+
+ tmp = talloc_sub_specified(
+ state, info->homedir, acct_name,
+ info->primary_group_name, info->domain_name,
+ pw->pw_uid, pw->pw_gid);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ strlcpy(pw->pw_dir, tmp, sizeof(pw->pw_dir));
+ TALLOC_FREE(tmp);
+
+ tmp = talloc_sub_specified(
+ state, info->shell, acct_name,
+ info->primary_group_name, info->domain_name,
+ pw->pw_uid, pw->pw_gid);
+ if (tevent_req_nomem(tmp, req)) {
+ return;
+ }
+ strlcpy(pw->pw_shell, tmp, sizeof(pw->pw_shell));
+ TALLOC_FREE(tmp);
+
+ strlcpy(pw->pw_passwd, "*", sizeof(pw->pw_passwd));
+
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_getpwsid_recv(struct tevent_req *req)
+{
+ NTSTATUS status = tevent_req_simple_recv_ntstatus(req);
+ D_INFO("WB command getpwsid end.\nReturn status %s.\n", nt_errstr(status));
+ return status;
+}
diff --git a/source3/winbindd/wb_gettoken.c b/source3/winbindd/wb_gettoken.c
new file mode 100644
index 0000000..3930f71
--- /dev/null
+++ b/source3/winbindd/wb_gettoken.c
@@ -0,0 +1,290 @@
+/*
+ Unix SMB/CIFS implementation.
+ async gettoken
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "util/debug.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+
+struct wb_gettoken_state {
+ struct tevent_context *ev;
+ struct dom_sid usersid;
+ bool expand_local_aliases;
+ uint32_t num_sids;
+ struct dom_sid *sids;
+};
+
+static NTSTATUS wb_add_rids_to_sids(TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids,
+ const struct dom_sid *domain_sid,
+ uint32_t num_rids, uint32_t *rids);
+
+static void wb_gettoken_gotuser(struct tevent_req *subreq);
+static void wb_gettoken_gotgroups(struct tevent_req *subreq);
+static void wb_gettoken_gotlocalgroups(struct tevent_req *subreq);
+static void wb_gettoken_gotbuiltins(struct tevent_req *subreq);
+
+struct tevent_req *wb_gettoken_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid,
+ bool expand_local_aliases)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_gettoken_state *state;
+ struct dom_sid_buf buf;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_gettoken_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ sid_copy(&state->usersid, sid);
+ state->ev = ev;
+ state->expand_local_aliases = expand_local_aliases;
+
+ D_INFO("WB command gettoken start.\n"
+ "Query user SID %s (expand local aliases is %d).\n",
+ dom_sid_str_buf(sid, &buf),
+ expand_local_aliases);
+ subreq = wb_queryuser_send(state, ev, &state->usersid);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_gettoken_gotuser, req);
+ return req;
+}
+
+static void wb_gettoken_gotuser(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_gettoken_state *state = tevent_req_data(
+ req, struct wb_gettoken_state);
+ struct wbint_userinfo *info;
+ NTSTATUS status;
+ struct dom_sid_buf buf0, buf1;
+
+ status = wb_queryuser_recv(subreq, state, &info);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->sids = talloc_array(state, struct dom_sid, 2);
+ if (tevent_req_nomem(state->sids, req)) {
+ return;
+ }
+ state->num_sids = 2;
+
+ D_DEBUG("Got user SID %s and group SID %s\n",
+ dom_sid_str_buf(&info->user_sid, &buf0),
+ dom_sid_str_buf(&info->group_sid, &buf1));
+ sid_copy(&state->sids[0], &info->user_sid);
+ sid_copy(&state->sids[1], &info->group_sid);
+
+ D_DEBUG("Looking up user groups for the user SID.\n");
+ subreq = wb_lookupusergroups_send(state, state->ev, &info->user_sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_gettoken_gotgroups, req);
+}
+
+static void wb_gettoken_gotgroups(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_gettoken_state *state = tevent_req_data(
+ req, struct wb_gettoken_state);
+ uint32_t i, num_groups;
+ struct dom_sid *groups;
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ status = wb_lookupusergroups_recv(subreq, state, &num_groups, &groups);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return;
+ }
+
+ D_DEBUG("Received %"PRIu32" group(s).\n", num_groups);
+ for (i = 0; i < num_groups; i++) {
+ D_DEBUG("Adding SID %s.\n", dom_sid_str_buf(&groups[i], &buf));
+ status = add_sid_to_array_unique(
+ state, &groups[i], &state->sids, &state->num_sids);
+
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ }
+
+ if (!state->expand_local_aliases) {
+ D_DEBUG("Done. Not asked to expand local aliases.\n");
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * Expand our domain's aliases
+ */
+ domain = find_domain_from_sid_noinit(get_global_sam_sid());
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ D_DEBUG("Expand domain's aliases for %"PRIu32" SID(s).\n",
+ state->num_sids);
+ subreq = wb_lookupuseraliases_send(state, state->ev, domain,
+ state->num_sids, state->sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_gettoken_gotlocalgroups, req);
+}
+
+static void wb_gettoken_gotlocalgroups(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_gettoken_state *state = tevent_req_data(
+ req, struct wb_gettoken_state);
+ uint32_t num_rids;
+ uint32_t *rids;
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+
+ status = wb_lookupuseraliases_recv(subreq, state, &num_rids, &rids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ D_DEBUG("Got %"PRIu32" RID(s).\n", num_rids);
+ status = wb_add_rids_to_sids(state, &state->num_sids, &state->sids,
+ get_global_sam_sid(), num_rids, rids);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ TALLOC_FREE(rids);
+
+ /*
+ * Now expand the builtin groups
+ */
+
+ D_DEBUG("Expand the builtin groups for %"PRIu32" SID(s).\n",
+ state->num_sids);
+ domain = find_domain_from_sid(&global_sid_Builtin);
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ subreq = wb_lookupuseraliases_send(state, state->ev, domain,
+ state->num_sids, state->sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_gettoken_gotbuiltins, req);
+}
+
+static void wb_gettoken_gotbuiltins(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_gettoken_state *state = tevent_req_data(
+ req, struct wb_gettoken_state);
+ uint32_t num_rids;
+ uint32_t *rids;
+ NTSTATUS status;
+
+ status = wb_lookupuseraliases_recv(subreq, state, &num_rids, &rids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ D_DEBUG("Got %"PRIu32" RID(s).\n", num_rids);
+ status = wb_add_rids_to_sids(state, &state->num_sids, &state->sids,
+ &global_sid_Builtin, num_rids, rids);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_gettoken_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_sids, struct dom_sid **sids)
+{
+ struct wb_gettoken_state *state = tevent_req_data(
+ req, struct wb_gettoken_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *num_sids = state->num_sids;
+ D_INFO("WB command gettoken end.\nReceived %"PRIu32" SID(s).\n",
+ state->num_sids);
+
+ if (CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ for (i = 0; i < state->num_sids; i++) {
+ struct dom_sid_buf sidbuf;
+ D_INFO("%"PRIu32": %s\n",
+ i,
+ dom_sid_str_buf(&state->sids[i],
+ &sidbuf));
+ }
+ }
+
+ *sids = talloc_move(mem_ctx, &state->sids);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS wb_add_rids_to_sids(TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids,
+ const struct dom_sid *domain_sid,
+ uint32_t num_rids, uint32_t *rids)
+{
+ uint32_t i;
+
+ D_DEBUG("%"PRIu32" SID(s) will be uniquely added to the SID array.\n"
+ "Before the addition the array has %"PRIu32" SID(s).\n",
+ num_rids, *pnum_sids);
+
+ for (i = 0; i < num_rids; i++) {
+ NTSTATUS status;
+ struct dom_sid sid;
+
+ sid_compose(&sid, domain_sid, rids[i]);
+ status = add_sid_to_array_unique(
+ mem_ctx, &sid, psids, pnum_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+ D_DEBUG("After the addition the array has %"PRIu32" SID(s).\n",
+ *pnum_sids);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_group_members.c b/source3/winbindd/wb_group_members.c
new file mode 100644
index 0000000..3fe7357
--- /dev/null
+++ b/source3/winbindd/wb_group_members.c
@@ -0,0 +1,489 @@
+/*
+ Unix SMB/CIFS implementation.
+ async lookupgroupmembers
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../libcli/security/security.h"
+#include "lib/util/util_tdb.h"
+#include "lib/dbwrap/dbwrap.h"
+#include "lib/dbwrap/dbwrap_rbt.h"
+
+/*
+ * We have 3 sets of routines here:
+ *
+ * wb_lookupgroupmem is the low-level one-group routine
+ *
+ * wb_groups_members walks a list of groups
+ *
+ * wb_group_members finally is the high-level routine expanding groups
+ * recursively
+ */
+
+/*
+ * TODO: fill_grent_mem_domusers must be re-added
+ */
+
+/*
+ * Look up members of a single group. Essentially a wrapper around the
+ * lookup_groupmem winbindd_methods routine.
+ */
+
+struct wb_lookupgroupmem_state {
+ struct dom_sid sid;
+ struct wbint_Principals members;
+};
+
+static void wb_lookupgroupmem_done(struct tevent_req *subreq);
+
+static struct tevent_req *wb_lookupgroupmem_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_lookupgroupmem_state *state;
+ struct winbindd_domain *domain;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_lookupgroupmem_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ sid_copy(&state->sid, group_sid);
+
+ domain = find_domain_from_sid_noinit(group_sid);
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_LookupGroupMembers_send(
+ state, ev, dom_child_handle(domain), &state->sid, type,
+ &state->members);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_lookupgroupmem_done, req);
+ return req;
+}
+
+static void wb_lookupgroupmem_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupgroupmem_state *state = tevent_req_data(
+ req, struct wb_lookupgroupmem_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupGroupMembers_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static NTSTATUS wb_lookupgroupmem_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_members,
+ struct wbint_Principal **members)
+{
+ struct wb_lookupgroupmem_state *state = tevent_req_data(
+ req, struct wb_lookupgroupmem_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ *num_members = state->members.num_principals;
+ *members = talloc_move(mem_ctx, &state->members.principals);
+ return NT_STATUS_OK;
+}
+
+/*
+ * Same as wb_lookupgroupmem for a list of groups
+ */
+
+struct wb_groups_members_state {
+ struct tevent_context *ev;
+ struct wbint_Principal *groups;
+ uint32_t num_groups;
+ uint32_t next_group;
+ struct wbint_Principal *all_members;
+};
+
+static NTSTATUS wb_groups_members_next_subreq(
+ struct wb_groups_members_state *state,
+ TALLOC_CTX *mem_ctx, struct tevent_req **psubreq);
+static void wb_groups_members_done(struct tevent_req *subreq);
+
+static struct tevent_req *wb_groups_members_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ uint32_t num_groups,
+ struct wbint_Principal *groups)
+{
+ struct tevent_req *req, *subreq = NULL;
+ struct wb_groups_members_state *state;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_groups_members_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->groups = groups;
+ state->num_groups = num_groups;
+ state->next_group = 0;
+ state->all_members = NULL;
+
+ D_DEBUG("Looking up %"PRIu32" group(s).\n", num_groups);
+ status = wb_groups_members_next_subreq(state, state, &subreq);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ if (subreq == NULL) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_groups_members_done, req);
+ return req;
+}
+
+static NTSTATUS wb_groups_members_next_subreq(
+ struct wb_groups_members_state *state,
+ TALLOC_CTX *mem_ctx, struct tevent_req **psubreq)
+{
+ struct tevent_req *subreq;
+ struct wbint_Principal *g;
+
+ if (state->next_group >= state->num_groups) {
+ *psubreq = NULL;
+ return NT_STATUS_OK;
+ }
+
+ g = &state->groups[state->next_group];
+ state->next_group += 1;
+
+ subreq = wb_lookupgroupmem_send(mem_ctx, state->ev, &g->sid, g->type);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *psubreq = subreq;
+ return NT_STATUS_OK;
+}
+
+static void wb_groups_members_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_groups_members_state *state = tevent_req_data(
+ req, struct wb_groups_members_state);
+ uint32_t i, num_all_members;
+ uint32_t num_members = 0;
+ struct wbint_Principal *members = NULL;
+ NTSTATUS status;
+
+ status = wb_lookupgroupmem_recv(subreq, state, &num_members, &members);
+ TALLOC_FREE(subreq);
+
+ /*
+ * In this error handling here we might have to be a bit more generous
+ * and just continue if an error occurred.
+ */
+
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!NT_STATUS_EQUAL(
+ status, NT_STATUS_TRUSTED_DOMAIN_FAILURE)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ num_members = 0;
+ }
+
+ num_all_members = talloc_array_length(state->all_members);
+
+ D_DEBUG("Adding %"PRIu32" new member(s) to existing %"PRIu32" member(s)\n",
+ num_members,
+ num_all_members);
+
+ state->all_members = talloc_realloc(
+ state, state->all_members, struct wbint_Principal,
+ num_all_members + num_members);
+ if ((num_all_members + num_members != 0)
+ && tevent_req_nomem(state->all_members, req)) {
+ return;
+ }
+ for (i=0; i<num_members; i++) {
+ struct wbint_Principal *src, *dst;
+ src = &members[i];
+ dst = &state->all_members[num_all_members + i];
+ sid_copy(&dst->sid, &src->sid);
+ dst->name = talloc_move(state->all_members, &src->name);
+ dst->type = src->type;
+ }
+ TALLOC_FREE(members);
+
+ status = wb_groups_members_next_subreq(state, state, &subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (subreq == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_groups_members_done, req);
+}
+
+static NTSTATUS wb_groups_members_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_members,
+ struct wbint_Principal **members)
+{
+ struct wb_groups_members_state *state = tevent_req_data(
+ req, struct wb_groups_members_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *num_members = talloc_array_length(state->all_members);
+ *members = talloc_move(mem_ctx, &state->all_members);
+ return NT_STATUS_OK;
+}
+
+
+/*
+ * This is the routine expanding a list of groups up to a certain level. We
+ * collect the users in a rbt database: We have to add them without duplicates,
+ * and the db is indexed by SID.
+ */
+
+struct wb_group_members_state {
+ struct tevent_context *ev;
+ int depth;
+ struct db_context *users;
+ struct wbint_Principal *groups;
+};
+
+static NTSTATUS wb_group_members_next_subreq(
+ struct wb_group_members_state *state,
+ TALLOC_CTX *mem_ctx, struct tevent_req **psubreq);
+static void wb_group_members_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_group_members_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid,
+ uint32_t num_sids,
+ enum lsa_SidType *type,
+ int max_depth)
+{
+ struct tevent_req *req, *subreq = NULL;
+ struct wb_group_members_state *state;
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+ uint32_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_group_members_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_INFO("WB command group_members start (max_depth=%d).\n", max_depth);
+ for (i = 0; i < num_sids; i++) {
+ D_INFO("Looking up members of group SID %s with SID type %d\n",
+ dom_sid_str_buf(&sid[i], &buf),
+ type[i]);
+ }
+
+ state->ev = ev;
+ state->depth = max_depth;
+ state->users = db_open_rbt(state);
+ if (tevent_req_nomem(state->users, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->groups = talloc_array(state, struct wbint_Principal, num_sids);
+ if (tevent_req_nomem(state->groups, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (i = 0; i < num_sids; i++) {
+ state->groups[i].name = NULL;
+ sid_copy(&state->groups[i].sid, &sid[i]);
+ state->groups[i].type = type[i];
+ }
+
+ status = wb_group_members_next_subreq(state, state, &subreq);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+ if (subreq == NULL) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_group_members_done, req);
+ return req;
+}
+
+static NTSTATUS wb_group_members_next_subreq(
+ struct wb_group_members_state *state,
+ TALLOC_CTX *mem_ctx, struct tevent_req **psubreq)
+{
+ struct tevent_req *subreq;
+
+ if ((talloc_array_length(state->groups) == 0)
+ || (state->depth <= 0)) {
+ *psubreq = NULL;
+ D_DEBUG("Finished. The depth is %d.\n", state->depth);
+ return NT_STATUS_OK;
+ }
+ state->depth -= 1;
+
+ D_DEBUG("The depth is decremented to %d.\n", state->depth);
+ subreq = wb_groups_members_send(
+ mem_ctx, state->ev, talloc_array_length(state->groups),
+ state->groups);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ *psubreq = subreq;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS add_member_to_db(struct db_context *db, struct dom_sid *sid,
+ const char *name)
+{
+ size_t len = ndr_size_dom_sid(sid, 0);
+ uint8_t sidbuf[len];
+ TDB_DATA key = { .dptr = sidbuf, .dsize = sizeof(sidbuf) };
+ NTSTATUS status;
+
+ sid_linearize(sidbuf, sizeof(sidbuf), sid);
+
+ status = dbwrap_store(db, key, string_term_tdb_data(name), 0);
+ return status;
+}
+
+static void wb_group_members_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_group_members_state *state = tevent_req_data(
+ req, struct wb_group_members_state);
+ uint32_t i, num_groups, new_groups;
+ uint32_t num_members = 0;
+ struct wbint_Principal *members = NULL;
+ NTSTATUS status;
+
+ status = wb_groups_members_recv(subreq, state, &num_members, &members);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ new_groups = 0;
+ for (i=0; i<num_members; i++) {
+ switch (members[i].type) {
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ new_groups += 1;
+ break;
+ default:
+ /* Ignore everything else */
+ break;
+ }
+ }
+
+ num_groups = 0;
+ TALLOC_FREE(state->groups);
+ state->groups = talloc_array(state, struct wbint_Principal,
+ new_groups);
+
+ /*
+ * Collect the users into state->users and the groups into
+ * state->groups for the next iteration.
+ */
+
+ for (i=0; i<num_members; i++) {
+ switch (members[i].type) {
+ case SID_NAME_USER:
+ case SID_NAME_COMPUTER: {
+ /*
+ * Add a copy of members[i] to state->users
+ */
+ status = add_member_to_db(state->users, &members[i].sid,
+ members[i].name);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ break;
+ }
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP: {
+ struct wbint_Principal *g;
+ /*
+ * Save members[i] for the next round
+ */
+ g = &state->groups[num_groups];
+ sid_copy(&g->sid, &members[i].sid);
+ g->name = talloc_move(state->groups, &members[i].name);
+ g->type = members[i].type;
+ num_groups += 1;
+ break;
+ }
+ default:
+ /* Ignore everything else */
+ break;
+ }
+ }
+
+ status = wb_group_members_next_subreq(state, state, &subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (subreq == NULL) {
+ tevent_req_done(req);
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_group_members_done, req);
+}
+
+NTSTATUS wb_group_members_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct db_context **members)
+{
+ struct wb_group_members_state *state = tevent_req_data(
+ req, struct wb_group_members_state);
+ NTSTATUS status;
+
+ D_INFO("WB command group_members end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+ *members = talloc_move(mem_ctx, &state->users);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_lookupname.c b/source3/winbindd/wb_lookupname.c
new file mode 100644
index 0000000..12dbfbe
--- /dev/null
+++ b/source3/winbindd/wb_lookupname.c
@@ -0,0 +1,123 @@
+/*
+ Unix SMB/CIFS implementation.
+ async lookupname
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+
+struct wb_lookupname_state {
+ struct tevent_context *ev;
+ const char *dom_name;
+ const char *name;
+ uint32_t flags;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+};
+
+static void wb_lookupname_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_lookupname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *namespace,
+ const char *dom_name,
+ const char *name,
+ uint32_t flags)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_lookupname_state *state;
+ struct winbindd_domain *domain;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_lookupname_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command lookupname start.\n"
+ "Search namespace '%s' and domain '%s' for name '%s'.\n",
+ namespace, dom_name, name);
+ state->ev = ev;
+ state->flags = flags;
+
+ /*
+ * Uppercase domain and name so that we become cache-friendly
+ */
+ state->dom_name = talloc_strdup_upper(state, dom_name);
+ if (tevent_req_nomem(state->dom_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->name = talloc_strdup_upper(state, name);
+ if (tevent_req_nomem(state->name, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ domain = find_lookup_domain_from_name(namespace);
+ if (domain == NULL) {
+ D_WARNING("Could not find domain for %s\n", namespace);
+ tevent_req_nterror(req, NT_STATUS_NONE_MAPPED);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_LookupName_send(
+ state, ev, dom_child_handle(domain),
+ state->dom_name, state->name,
+ flags, &state->type, &state->sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_lookupname_done, req);
+ return req;
+}
+
+static void wb_lookupname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupname_state *state = tevent_req_data(
+ req, struct wb_lookupname_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupName_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_lookupname_recv(struct tevent_req *req, struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ struct wb_lookupname_state *state = tevent_req_data(
+ req, struct wb_lookupname_state);
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ sid_copy(sid, &state->sid);
+ *type = state->type;
+ D_INFO("WB command lookupname end.\n"
+ "Found SID %s with SID type %d.\n",
+ dom_sid_str_buf(sid, &buf),
+ *type);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_lookupsid.c b/source3/winbindd/wb_lookupsid.c
new file mode 100644
index 0000000..31820f9
--- /dev/null
+++ b/source3/winbindd/wb_lookupsid.c
@@ -0,0 +1,114 @@
+/*
+ Unix SMB/CIFS implementation.
+ async lookupsid
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+
+struct wb_lookupsid_state {
+ struct tevent_context *ev;
+ struct winbindd_domain *lookup_domain;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *domname;
+ const char *name;
+};
+
+static void wb_lookupsid_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_lookupsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_lookupsid_state *state;
+ struct dom_sid_buf buf;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_lookupsid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command lookupsid start.\n");
+ sid_copy(&state->sid, sid);
+ state->ev = ev;
+
+ state->lookup_domain = find_lookup_domain_from_sid(sid);
+ if (state->lookup_domain == NULL) {
+ D_WARNING("Could not find domain for sid %s\n",
+ dom_sid_str_buf(sid, &buf));
+ tevent_req_nterror(req, NT_STATUS_NONE_MAPPED);
+ return tevent_req_post(req, ev);
+ }
+
+ D_DEBUG("Looking up SID %s in domain %s.\n",
+ dom_sid_str_buf(&state->sid, &buf),
+ state->lookup_domain->name);
+ subreq = dcerpc_wbint_LookupSid_send(
+ state, ev, dom_child_handle(state->lookup_domain),
+ &state->sid, &state->type, &state->domname, &state->name);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_lookupsid_done, req);
+ return req;
+}
+
+static void wb_lookupsid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupsid_state *state = tevent_req_data(
+ req, struct wb_lookupsid_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupSid_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_lookupsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ enum lsa_SidType *type, const char **domain,
+ const char **name)
+{
+ struct wb_lookupsid_state *state = tevent_req_data(
+ req, struct wb_lookupsid_state);
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ D_INFO("WB command lookupsid end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+ *type = state->type;
+ *domain = talloc_move(mem_ctx, &state->domname);
+ *name = talloc_move(mem_ctx, &state->name);
+ D_INFO("SID %s has name '%s' with type '%d' in domain '%s'.\n",
+ dom_sid_str_buf(&state->sid, &buf),
+ *name,
+ *type,
+ *domain);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_lookupsids.c b/source3/winbindd/wb_lookupsids.c
new file mode 100644
index 0000000..828e79e
--- /dev/null
+++ b/source3/winbindd/wb_lookupsids.c
@@ -0,0 +1,699 @@
+/*
+ Unix SMB/CIFS implementation.
+ async lookupsids
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "lib/util_unixsids.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+#include "lsa.h"
+
+struct wb_lookupsids_domain {
+ struct winbindd_domain *domain;
+
+ /*
+ * Array of sids to be passed into wbint_LookupSids. Preallocated with
+ * num_sids.
+ */
+ struct lsa_SidArray sids;
+
+ /*
+ * Indexes into wb_lookupsids_state->sids and thus
+ * wb_lookupsids_state->res_names. Preallocated with num_sids.
+ */
+ uint32_t *sid_indexes;
+};
+
+struct wb_translated_name {
+ const char *domain_name;
+ const char *name;
+ enum lsa_SidType type;
+};
+
+static struct wb_lookupsids_domain *wb_lookupsids_get_domain(
+ const struct dom_sid *sid, TALLOC_CTX *mem_ctx,
+ struct wb_lookupsids_domain **domains, uint32_t num_sids);
+
+struct wb_lookupsids_state {
+ struct tevent_context *ev;
+
+ /*
+ * SIDs passed in
+ */
+ struct dom_sid *sids;
+ uint32_t num_sids;
+
+ /*
+ * The domains we're using for bulk lookup via wbint_LookupRids or
+ * wbint_LookupSids. We expect very few domains, so we do a
+ * talloc_realloc and rely on talloc_array_length.
+ */
+ struct wb_lookupsids_domain *domains;
+ uint32_t domains_done;
+
+ /*
+ * These SIDs are looked up individually via
+ * wbint_LookupSid. Preallocated with num_sids.
+ */
+ uint32_t *single_sids;
+ uint32_t num_single_sids;
+ uint32_t single_sids_done;
+
+ /*
+ * Intermediate store for wbint_LookupRids to passdb. These are
+ * spliced into res_domains/res_names in wb_lookupsids_move_name.
+ */
+ struct wbint_RidArray rids;
+ const char *domain_name;
+ struct wbint_Principals rid_names;
+
+ /*
+ * Intermediate results for wbint_LookupSids. These results are
+ * spliced into res_domains/res_names in wb_lookupsids_move_name.
+ */
+ struct lsa_RefDomainList tmp_domains;
+ struct lsa_TransNameArray tmp_names;
+
+ /*
+ * Results
+ */
+ struct lsa_RefDomainList *res_domains;
+ /*
+ * Indexed as "sids" in this structure
+ */
+ struct lsa_TransNameArray *res_names;
+};
+
+static bool wb_lookupsids_next(struct tevent_req *req,
+ struct wb_lookupsids_state *state);
+static void wb_lookupsids_single_done(struct tevent_req *subreq);
+static void wb_lookupsids_lookuprids_done(struct tevent_req *subreq);
+static void wb_lookupsids_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_lookupsids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dom_sid *sids,
+ uint32_t num_sids)
+{
+ struct tevent_req *req;
+ struct wb_lookupsids_state *state;
+ uint32_t i;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_lookupsids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command lookupsids start.\nLooking up %"PRIu32" SID(s)\n",
+ num_sids);
+ if (CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ for (i = 0; i < num_sids; i++) {
+ struct dom_sid_buf buf;
+ D_INFO("%"PRIu32": %s\n",
+ i, dom_sid_str_buf(&sids[i], &buf));
+ }
+ }
+
+ state->ev = ev;
+ state->sids = sids;
+ state->num_sids = num_sids;
+
+ state->single_sids = talloc_zero_array(state, uint32_t, num_sids);
+ if (tevent_req_nomem(state->single_sids, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->res_domains = talloc_zero(state, struct lsa_RefDomainList);
+ if (tevent_req_nomem(state->res_domains, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->res_domains->domains = talloc_zero_array(
+ state->res_domains, struct lsa_DomainInfo, num_sids);
+ if (tevent_req_nomem(state->res_domains->domains, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->res_names = talloc_zero(state, struct lsa_TransNameArray);
+ if (tevent_req_nomem(state->res_names, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->res_names->names = talloc_zero_array(
+ state->res_names, struct lsa_TranslatedName, num_sids);
+ if (tevent_req_nomem(state->res_names->names, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (num_sids == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_sids; i++) {
+ struct wb_lookupsids_domain *d;
+
+ d = wb_lookupsids_get_domain(&sids[i], state, &state->domains,
+ num_sids);
+ if (d != NULL) {
+ d->sids.sids[d->sids.num_sids].sid = &sids[i];
+ d->sid_indexes[d->sids.num_sids] = i;
+ d->sids.num_sids += 1;
+ } else {
+ state->single_sids[state->num_single_sids] = i;
+ state->num_single_sids += 1;
+ }
+ }
+
+ if (!wb_lookupsids_next(req, state)) {
+ return tevent_req_post(req, ev);
+ }
+ return req;
+}
+
+static bool wb_lookupsids_next(struct tevent_req *req,
+ struct wb_lookupsids_state *state)
+{
+ struct tevent_req *subreq;
+
+ if (state->domains_done < talloc_array_length(state->domains)) {
+ struct wb_lookupsids_domain *d;
+ uint32_t i;
+
+ d = &state->domains[state->domains_done];
+
+ if (d->domain->internal) {
+ /*
+ * This is only our local SAM,
+ * see wb_lookupsids_bulk() and
+ * wb_lookupsids_get_domain().
+ */
+ state->rids.num_rids = d->sids.num_sids;
+ state->rids.rids = talloc_array(state, uint32_t,
+ state->rids.num_rids);
+ if (tevent_req_nomem(state->rids.rids, req)) {
+ return false;
+ }
+ for (i=0; i<state->rids.num_rids; i++) {
+ sid_peek_rid(d->sids.sids[i].sid,
+ &state->rids.rids[i]);
+ }
+ subreq = dcerpc_wbint_LookupRids_send(
+ state, state->ev, dom_child_handle(d->domain),
+ &d->domain->sid, &state->rids, &state->domain_name,
+ &state->rid_names);
+ if (tevent_req_nomem(subreq, req)) {
+ return false;
+ }
+ tevent_req_set_callback(
+ subreq, wb_lookupsids_lookuprids_done, req);
+ return true;
+ }
+
+ subreq = dcerpc_wbint_LookupSids_send(
+ state, state->ev, dom_child_handle(d->domain),
+ &d->sids, &state->tmp_domains, &state->tmp_names);
+ if (tevent_req_nomem(subreq, req)) {
+ return false;
+ }
+ tevent_req_set_callback(subreq, wb_lookupsids_done, req);
+ return true;
+ }
+
+ if (state->single_sids_done < state->num_single_sids) {
+ uint32_t sid_idx;
+ const struct dom_sid *sid;
+
+ sid_idx = state->single_sids[state->single_sids_done];
+ sid = &state->sids[sid_idx];
+
+ subreq = wb_lookupsid_send(state, state->ev, sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return false;
+ }
+ tevent_req_set_callback(subreq, wb_lookupsids_single_done,
+ req);
+ return true;
+ }
+
+ tevent_req_done(req);
+ return false;
+}
+
+/*
+ * Decide whether to do bulk lookupsids. We have optimizations for
+ * passdb via lookuprids and to remote DCs via lookupsids.
+ */
+
+static bool wb_lookupsids_bulk(const struct dom_sid *sid)
+{
+ struct dom_sid_buf sidbuf;
+
+ if (sid->num_auths != 5) {
+ /*
+ * Only do "S-1-5-21-x-y-z-rid" domains via bulk
+ * lookup
+ */
+ DBG_DEBUG("No bulk setup for SID %s with %"PRIi8" subauths\n",
+ dom_sid_str_buf(sid, &sidbuf),
+ sid->num_auths);
+ return false;
+ }
+
+ if (sid_check_is_in_our_sam(sid)) {
+ /*
+ * Passdb lookup via lookuprids
+ */
+ DBG_DEBUG("%s is in our domain\n",
+ dom_sid_str_buf(sid, &sidbuf));
+ return true;
+ }
+
+ if (IS_DC) {
+ /*
+ * Bulk lookups to trusted DCs
+ */
+ return (find_domain_from_sid_noinit(sid) != NULL);
+ }
+
+ if (lp_server_role() != ROLE_DOMAIN_MEMBER) {
+ /*
+ * Don't do bulk lookups as standalone, the only bulk
+ * lookup left is for domain members.
+ */
+ return false;
+ }
+
+ if (sid_check_is_in_unix_groups(sid) ||
+ sid_check_is_unix_groups(sid) ||
+ sid_check_is_in_unix_users(sid) ||
+ sid_check_is_unix_users(sid) ||
+ sid_check_is_in_builtin(sid) ||
+ sid_check_is_builtin(sid) ||
+ sid_check_is_wellknown_domain(sid, NULL) ||
+ sid_check_is_in_wellknown_domain(sid))
+ {
+ /*
+ * These are locally done piece by piece anyway, no
+ * need for bulk optimizations.
+ */
+ return false;
+ }
+
+ /*
+ * All other SIDs are sent to the DC we're connected to as
+ * member via a single lsa_lookupsids call.
+ */
+ return true;
+}
+
+static struct wb_lookupsids_domain *wb_lookupsids_get_domain(
+ const struct dom_sid *sid, TALLOC_CTX *mem_ctx,
+ struct wb_lookupsids_domain **pdomains, uint32_t num_sids)
+{
+ struct wb_lookupsids_domain *domains, *domain;
+ struct winbindd_domain *wb_domain;
+ uint32_t i, num_domains;
+
+ if (!wb_lookupsids_bulk(sid)) {
+ D_DEBUG("wb_lookupsids_bulk() is FALSE\n");
+ return NULL;
+ }
+ D_DEBUG("wb_lookupsids_bulk() is TRUE\n");
+
+ domains = *pdomains;
+ num_domains = talloc_array_length(domains);
+
+ wb_domain = find_lookup_domain_from_sid(sid);
+ if (wb_domain == NULL) {
+ return NULL;
+ }
+
+ D_DEBUG("Searching %"PRIu32" domain(s) for domain '%s'\n",
+ num_domains, wb_domain->name);
+ for (i=0; i<num_domains; i++) {
+ if (domains[i].domain != wb_domain) {
+ continue;
+ }
+
+ if (!domains[i].domain->internal) {
+ /*
+ * If it's not our local sam,
+ * we can re-use the domain without
+ * checking the sid.
+ *
+ * Note the wb_lookupsids_bulk() above
+ * already caught special SIDs,
+ * e.g. the unix and builtin domains.
+ */
+ return &domains[i];
+ }
+
+ if (dom_sid_compare_domain(sid, &domains[i].domain->sid) == 0) {
+ /*
+ * If it's out local sam we can also use it.
+ */
+ return &domains[i];
+ }
+
+ /*
+ * I'm not sure if this can be triggered,
+ * as wb_lookupsids_bulk() should also catch this,
+ * but we need to make sure that we don't use
+ * wbint_LookupRids() without a SID match.
+ */
+ return NULL;
+ }
+
+ domains = talloc_realloc(
+ mem_ctx, domains, struct wb_lookupsids_domain, num_domains+1);
+ if (domains == NULL) {
+ return NULL;
+ }
+ *pdomains = domains;
+
+ domain = &domains[num_domains];
+ domain->domain = wb_domain;
+
+ domain->sids.sids = talloc_zero_array(domains, struct lsa_SidPtr, num_sids);
+ if (domains->sids.sids == NULL) {
+ goto fail;
+ }
+ domain->sids.num_sids = 0;
+
+ domain->sid_indexes = talloc_zero_array(domains, uint32_t, num_sids);
+ if (domain->sid_indexes == NULL) {
+ TALLOC_FREE(domain->sids.sids);
+ goto fail;
+ }
+ return domain;
+
+fail:
+ /*
+ * Realloc to the state it was in before
+ */
+ *pdomains = talloc_realloc(
+ mem_ctx, domains, struct wb_lookupsids_domain, num_domains);
+ return NULL;
+}
+
+static bool wb_lookupsids_find_dom_idx(struct lsa_DomainInfo *domain,
+ struct lsa_RefDomainList *list,
+ uint32_t *idx)
+{
+ uint32_t i;
+ struct lsa_DomainInfo *new_domain;
+
+ for (i=0; i<list->count; i++) {
+ if (dom_sid_equal(domain->sid, list->domains[i].sid)) {
+ *idx = i;
+ return true;
+ }
+ }
+
+ new_domain = &list->domains[list->count];
+
+ new_domain->name.string = talloc_strdup(
+ list->domains, domain->name.string);
+ if (new_domain->name.string == NULL) {
+ return false;
+ }
+
+ new_domain->sid = dom_sid_dup(list->domains, domain->sid);
+ if (new_domain->sid == NULL) {
+ return false;
+ }
+
+ *idx = list->count;
+ list->count += 1;
+ return true;
+}
+
+static bool wb_lookupsids_move_name(struct lsa_RefDomainList *src_domains,
+ struct lsa_TranslatedName *src_name,
+ struct lsa_RefDomainList *dst_domains,
+ struct lsa_TransNameArray *dst_names,
+ uint32_t dst_name_index)
+{
+ struct lsa_TranslatedName *dst_name;
+ struct lsa_DomainInfo *src_domain;
+ uint32_t src_domain_index;
+ uint32_t dst_domain_index = UINT32_MAX;
+ bool ok;
+
+ src_domain_index = src_name->sid_index;
+ if ((src_domain_index != UINT32_MAX) && (src_domains != NULL)) {
+ if (src_domain_index >= src_domains->count) {
+ return false;
+ }
+ src_domain = &src_domains->domains[src_domain_index];
+
+ ok = wb_lookupsids_find_dom_idx(src_domain,
+ dst_domains,
+ &dst_domain_index);
+ if (!ok) {
+ return false;
+ }
+ }
+
+ dst_name = &dst_names->names[dst_name_index];
+
+ dst_name->sid_type = src_name->sid_type;
+ dst_name->name.string = talloc_move(dst_names->names,
+ &src_name->name.string);
+ dst_name->sid_index = dst_domain_index;
+ dst_names->count += 1;
+
+ return true;
+}
+
+static void wb_lookupsids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupsids_state *state = tevent_req_data(
+ req, struct wb_lookupsids_state);
+ struct wb_lookupsids_domain *d;
+ uint32_t i;
+
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupSids_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (NT_STATUS_LOOKUP_ERR(result)) {
+ tevent_req_nterror(req, result);
+ return;
+ }
+
+ /*
+ * Look at the individual states in the translated names.
+ */
+
+ d = &state->domains[state->domains_done];
+
+ for (i=0; i<state->tmp_names.count; i++) {
+ uint32_t res_sid_index = d->sid_indexes[i];
+
+ if (!wb_lookupsids_move_name(
+ &state->tmp_domains, &state->tmp_names.names[i],
+ state->res_domains, state->res_names,
+ res_sid_index)) {
+ tevent_req_oom(req);
+ return;
+ }
+ }
+ state->domains_done += 1;
+ wb_lookupsids_next(req, state);
+}
+
+static void wb_lookupsids_single_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupsids_state *state = tevent_req_data(
+ req, struct wb_lookupsids_state);
+ const char *domain_name = NULL;
+ const char *name = NULL;
+ enum lsa_SidType type = SID_NAME_UNKNOWN;
+ uint32_t res_sid_index;
+ uint32_t src_rid;
+
+ struct dom_sid src_domain_sid;
+ struct lsa_DomainInfo src_domain;
+ struct lsa_RefDomainList src_domains;
+ struct lsa_RefDomainList *psrc_domains = NULL;
+ struct lsa_TranslatedName src_name;
+
+ uint32_t domain_idx = UINT32_MAX;
+ NTSTATUS status;
+ bool ok;
+
+ status = wb_lookupsid_recv(subreq, talloc_tos(), &type,
+ &domain_name, &name);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_LOOKUP_ERR(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ res_sid_index = state->single_sids[state->single_sids_done];
+
+ if ((domain_name != NULL) && (domain_name[0] != '\0')) {
+ /*
+ * Build structs with the domain name for
+ * wb_lookupsids_move_name(). If we didn't get a name, we will
+ * pass NULL and UINT32_MAX.
+ */
+
+ sid_copy(&src_domain_sid, &state->sids[res_sid_index]);
+ if (type != SID_NAME_DOMAIN) {
+ sid_split_rid(&src_domain_sid, &src_rid);
+ }
+
+ src_domain.name.string = domain_name;
+ src_domain.sid = &src_domain_sid;
+
+ src_domains.count = 1;
+ src_domains.domains = &src_domain;
+ psrc_domains = &src_domains;
+
+ domain_idx = 0;
+ }
+
+ src_name.sid_type = type;
+ src_name.name.string = name;
+ src_name.sid_index = domain_idx;
+
+ ok = wb_lookupsids_move_name(psrc_domains,
+ &src_name,
+ state->res_domains,
+ state->res_names,
+ res_sid_index);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ state->single_sids_done += 1;
+ wb_lookupsids_next(req, state);
+}
+
+static void wb_lookupsids_lookuprids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupsids_state *state = tevent_req_data(
+ req, struct wb_lookupsids_state);
+ struct dom_sid src_domain_sid;
+ struct lsa_DomainInfo src_domain;
+ struct lsa_RefDomainList src_domains;
+ NTSTATUS status, result;
+ struct wb_lookupsids_domain *d;
+ uint32_t i;
+
+ status = dcerpc_wbint_LookupRids_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (NT_STATUS_LOOKUP_ERR(result)) {
+ tevent_req_nterror(req, result);
+ return;
+ }
+
+ /*
+ * Look at the individual states in the translated names.
+ */
+
+ d = &state->domains[state->domains_done];
+
+ sid_copy(&src_domain_sid, get_global_sam_sid());
+ src_domain.name.string = get_global_sam_name();
+ src_domain.sid = &src_domain_sid;
+ src_domains.count = 1;
+ src_domains.domains = &src_domain;
+
+ for (i=0; i<state->rid_names.num_principals; i++) {
+ struct lsa_TranslatedName src_name;
+ uint32_t res_sid_index;
+
+ /*
+ * Fake up structs for wb_lookupsids_move_name
+ */
+ res_sid_index = d->sid_indexes[i];
+
+ src_name.sid_type = state->rid_names.principals[i].type;
+ src_name.name.string = state->rid_names.principals[i].name;
+ src_name.sid_index = 0;
+
+ if (!wb_lookupsids_move_name(
+ &src_domains, &src_name,
+ state->res_domains, state->res_names,
+ res_sid_index)) {
+ tevent_req_oom(req);
+ return;
+ }
+ }
+
+ state->domains_done += 1;
+ wb_lookupsids_next(req, state);
+}
+
+NTSTATUS wb_lookupsids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct lsa_RefDomainList **domains,
+ struct lsa_TransNameArray **names)
+{
+ struct wb_lookupsids_state *state = tevent_req_data(
+ req, struct wb_lookupsids_state);
+ NTSTATUS status;
+
+ D_INFO("WB command lookupsids end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+
+ /*
+ * The returned names need to match the given sids,
+ * if not we have a bug in the code!
+ */
+ if (state->res_names->count != state->num_sids) {
+ D_WARNING("Got %"PRIu32" returned name(s), but expected %"PRIu32"!\n",
+ state->res_names->count, state->num_sids);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ /*
+ * Not strictly needed, but it might make debugging in the callers
+ * easier in future, if the talloc_array_length() returns the
+ * expected result...
+ */
+ state->res_domains->domains = talloc_realloc(state->res_domains,
+ state->res_domains->domains,
+ struct lsa_DomainInfo,
+ state->res_domains->count);
+
+ *domains = talloc_move(mem_ctx, &state->res_domains);
+ *names = talloc_move(mem_ctx, &state->res_names);
+ D_INFO("Returning %"PRIu32" domain(s) and %"PRIu32" name(s).\n",
+ (*domains)->count,
+ (*names)->count);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_lookupuseraliases.c b/source3/winbindd/wb_lookupuseraliases.c
new file mode 100644
index 0000000..a9ad7d4
--- /dev/null
+++ b/source3/winbindd/wb_lookupuseraliases.c
@@ -0,0 +1,108 @@
+/*
+ Unix SMB/CIFS implementation.
+ async lookupuseraliases
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "libcli/security/dom_sid.h"
+
+struct wb_lookupuseraliases_state {
+ struct tevent_context *ev;
+ struct wbint_SidArray sids;
+ struct wbint_RidArray rids;
+};
+
+static void wb_lookupuseraliases_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_lookupuseraliases_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain,
+ uint32_t num_sids,
+ const struct dom_sid *sids)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_lookupuseraliases_state *state;
+ uint32_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_lookupuseraliases_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command lookupuseraliases start.\n"
+ "Query domain %s for max %"PRIu32" SID(s).\n",
+ domain->name, num_sids);
+
+ for (i = 0; i < num_sids; i++) {
+ struct dom_sid_buf buf;
+ D_INFO("%"PRIu32": SID %s\n", i, dom_sid_str_buf(&sids[i], &buf));
+ }
+ state->sids.num_sids = num_sids;
+ state->sids.sids = discard_const_p(struct dom_sid, sids);
+
+ subreq = dcerpc_wbint_LookupUserAliases_send(
+ state, ev, dom_child_handle(domain), &state->sids, &state->rids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_lookupuseraliases_done, req);
+ return req;
+}
+
+static void wb_lookupuseraliases_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupuseraliases_state *state = tevent_req_data(
+ req, struct wb_lookupuseraliases_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupUserAliases_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ D_WARNING("LookupUserAliases failed with %s.\n",
+ nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_lookupuseraliases_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_aliases, uint32_t **aliases)
+{
+ struct wb_lookupuseraliases_state *state = tevent_req_data(
+ req, struct wb_lookupuseraliases_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *num_aliases = state->rids.num_rids;
+ D_INFO("WB command lookupuseraliases end.\nGot %"PRIu32" alias(es):\n",
+ *num_aliases);
+ for (i = 0; i < *num_aliases; i++) {
+ D_INFO("%"PRIu32": RID %"PRIu32"\n", i, state->rids.rids[i]);
+ }
+
+ *aliases = talloc_move(mem_ctx, &state->rids.rids);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_lookupusergroups.c b/source3/winbindd/wb_lookupusergroups.c
new file mode 100644
index 0000000..7f359ee
--- /dev/null
+++ b/source3/winbindd/wb_lookupusergroups.c
@@ -0,0 +1,120 @@
+/*
+ Unix SMB/CIFS implementation.
+ async lookupusergroups
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+
+struct wb_lookupusergroups_state {
+ struct tevent_context *ev;
+ struct dom_sid sid;
+ struct wbint_SidArray sids;
+};
+
+static void wb_lookupusergroups_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_lookupusergroups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_lookupusergroups_state *state;
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_lookupusergroups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_INFO("WB command lookupusergroups start.\nLooking up SID %s.\n",
+ dom_sid_str_buf(sid, &buf));
+ sid_copy(&state->sid, sid);
+
+ status = lookup_usergroups_cached(state,
+ &state->sid,
+ &state->sids.num_sids,
+ &state->sids.sids);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ domain = find_domain_from_sid_noinit(&state->sid);
+ if (domain == NULL) {
+ DBG_WARNING("could not find domain entry for sid %s\n",
+ dom_sid_str_buf(&state->sid, &buf));
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_LookupUserGroups_send(
+ state, ev, dom_child_handle(domain), &state->sid, &state->sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_lookupusergroups_done, req);
+ return req;
+}
+
+static void wb_lookupusergroups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_lookupusergroups_state *state = tevent_req_data(
+ req, struct wb_lookupusergroups_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupUserGroups_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_lookupusergroups_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_sids, struct dom_sid **sids)
+{
+ struct wb_lookupusergroups_state *state = tevent_req_data(
+ req, struct wb_lookupusergroups_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *num_sids = state->sids.num_sids;
+ *sids = talloc_move(mem_ctx, &state->sids.sids);
+
+ D_INFO("WB command lookupusergroups end.\nReceived %"PRIu32" SID(s).\n",
+ *num_sids);
+ if (CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ for (i = 0; i < *num_sids; i++) {
+ struct dom_sid_buf buf;
+ D_INFO("%"PRIu32": %s\n",
+ i, dom_sid_str_buf(&(*sids)[i], &buf));
+ }
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_next_grent.c b/source3/winbindd/wb_next_grent.c
new file mode 100644
index 0000000..5c2d447
--- /dev/null
+++ b/source3/winbindd/wb_next_grent.c
@@ -0,0 +1,169 @@
+/*
+ Unix SMB/CIFS implementation.
+ async next_grent
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "passdb/machine_sid.h"
+
+struct wb_next_grent_state {
+ struct tevent_context *ev;
+ int max_nesting;
+ struct getgrent_state *gstate;
+ struct winbindd_gr *gr;
+ struct db_context *members;
+};
+
+static void wb_next_grent_fetch_done(struct tevent_req *subreq);
+static void wb_next_grent_getgrsid_done(struct tevent_req *subreq);
+
+static void wb_next_grent_send_do(struct tevent_req *req,
+ struct wb_next_grent_state *state)
+{
+ struct tevent_req *subreq;
+
+ if (state->gstate->next_group >= state->gstate->num_groups) {
+ TALLOC_FREE(state->gstate->groups);
+
+ state->gstate->domain = wb_next_domain(state->gstate->domain);
+ if (state->gstate->domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES);
+ return;
+ }
+
+ subreq = wb_query_group_list_send(state, state->ev,
+ state->gstate->domain);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_next_grent_fetch_done, req);
+ return;
+ }
+
+ subreq = wb_getgrsid_send(
+ state, state->ev,
+ &state->gstate->groups[state->gstate->next_group].sid,
+ state->max_nesting);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_next_grent_getgrsid_done, req);
+}
+
+struct tevent_req *wb_next_grent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int max_nesting,
+ struct getgrent_state *gstate,
+ struct winbindd_gr *gr)
+{
+ struct tevent_req *req;
+ struct wb_next_grent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_next_grent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command next_grent start.\n");
+
+ state->ev = ev;
+ state->gstate = gstate;
+ state->gr = gr;
+ state->max_nesting = max_nesting;
+
+ wb_next_grent_send_do(req, state);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void wb_next_grent_fetch_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_next_grent_state *state = tevent_req_data(
+ req, struct wb_next_grent_state);
+ NTSTATUS status;
+
+ status = wb_query_group_list_recv(subreq, state->gstate,
+ &state->gstate->num_groups,
+ &state->gstate->groups);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* Ignore errors here, just log it */
+ D_DEBUG("query_group_list for domain %s returned %s\n",
+ state->gstate->domain->name, nt_errstr(status));
+ state->gstate->num_groups = 0;
+ }
+
+ state->gstate->next_group = 0;
+
+ wb_next_grent_send_do(req, state);
+}
+
+static void wb_next_grent_getgrsid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_next_grent_state *state = tevent_req_data(
+ req, struct wb_next_grent_state);
+ const char *domname, *name;
+ NTSTATUS status;
+
+ status = wb_getgrsid_recv(subreq, talloc_tos(), &domname, &name,
+ &state->gr->gr_gid, &state->members);
+ TALLOC_FREE(subreq);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ state->gstate->next_group += 1;
+
+ wb_next_grent_send_do(req, state);
+
+ return;
+ } else if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (!fill_grent(talloc_tos(), state->gr, domname, name,
+ state->gr->gr_gid)) {
+ D_WARNING("fill_grent failed\n");
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ state->gstate->next_group += 1;
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_next_grent_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct db_context **members)
+{
+ struct wb_next_grent_state *state = tevent_req_data(
+ req, struct wb_next_grent_state);
+ NTSTATUS status;
+
+ D_INFO("WB command next_grent end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+ *members = talloc_move(mem_ctx, &state->members);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_next_pwent.c b/source3/winbindd/wb_next_pwent.c
new file mode 100644
index 0000000..f000c64
--- /dev/null
+++ b/source3/winbindd/wb_next_pwent.c
@@ -0,0 +1,162 @@
+/*
+ Unix SMB/CIFS implementation.
+ async next_pwent
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "libcli/security/dom_sid.h"
+#include "passdb/machine_sid.h"
+
+struct wb_next_pwent_state {
+ struct tevent_context *ev;
+ struct getpwent_state *gstate;
+ struct dom_sid next_sid;
+ struct winbindd_pw *pw;
+};
+
+static void wb_next_pwent_fetch_done(struct tevent_req *subreq);
+static void wb_next_pwent_fill_done(struct tevent_req *subreq);
+
+static void wb_next_pwent_send_do(struct tevent_req *req,
+ struct wb_next_pwent_state *state)
+{
+ struct tevent_req *subreq;
+ struct dom_sid_buf buf, buf1;
+
+ if (state->gstate->next_user >= state->gstate->rids.num_rids) {
+ TALLOC_FREE(state->gstate->rids.rids);
+ state->gstate->rids.num_rids = 0;
+
+ state->gstate->domain = wb_next_domain(state->gstate->domain);
+ if (state->gstate->domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES);
+ return;
+ }
+
+ D_DEBUG("Query user RID list for domain %s.\n",
+ state->gstate->domain->name);
+ subreq = dcerpc_wbint_QueryUserRidList_send(
+ state, state->ev,
+ dom_child_handle(state->gstate->domain),
+ &state->gstate->rids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ tevent_req_set_callback(subreq, wb_next_pwent_fetch_done, req);
+ return;
+ }
+
+ sid_compose(&state->next_sid, &state->gstate->domain->sid,
+ state->gstate->rids.rids[state->gstate->next_user]);
+
+ D_DEBUG("Get pw for SID %s composed from domain SID %s and RID %"PRIu32".\n",
+ dom_sid_str_buf(&state->next_sid, &buf),
+ dom_sid_str_buf(&state->gstate->domain->sid, &buf1),
+ state->gstate->rids.rids[state->gstate->next_user]);
+ subreq = wb_getpwsid_send(state, state->ev, &state->next_sid,
+ state->pw);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ tevent_req_set_callback(subreq, wb_next_pwent_fill_done, req);
+}
+
+struct tevent_req *wb_next_pwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct getpwent_state *gstate,
+ struct winbindd_pw *pw)
+{
+ struct tevent_req *req;
+ struct wb_next_pwent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_next_pwent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_INFO("WB command next_pwent start.\n");
+ state->ev = ev;
+ state->gstate = gstate;
+ state->pw = pw;
+
+ wb_next_pwent_send_do(req, state);
+ if (!tevent_req_is_in_progress(req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void wb_next_pwent_fetch_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_next_pwent_state *state = tevent_req_data(
+ req, struct wb_next_pwent_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_QueryUserRidList_recv(subreq, state->gstate,
+ &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ /* Ignore errors here, just log it */
+ D_DEBUG("query_user_list for domain %s returned %s\n",
+ state->gstate->domain->name,
+ nt_errstr(status));
+ state->gstate->rids.num_rids = 0;
+ }
+
+ state->gstate->next_user = 0;
+
+ wb_next_pwent_send_do(req, state);
+}
+
+static void wb_next_pwent_fill_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_next_pwent_state *state = tevent_req_data(
+ req, struct wb_next_pwent_state);
+ NTSTATUS status;
+
+ status = wb_getpwsid_recv(subreq);
+ TALLOC_FREE(subreq);
+ /*
+ * When you try to enumerate users with 'getent passwd' and the user
+ * doesn't have a uid set we should just move on.
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ state->gstate->next_user += 1;
+
+ wb_next_pwent_send_do(req, state);
+
+ return;
+ } else if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->gstate->next_user += 1;
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_next_pwent_recv(struct tevent_req *req)
+{
+ D_INFO("WB command next_pwent end.\n");
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/winbindd/wb_query_group_list.c b/source3/winbindd/wb_query_group_list.c
new file mode 100644
index 0000000..a71e162
--- /dev/null
+++ b/source3/winbindd/wb_query_group_list.c
@@ -0,0 +1,94 @@
+/*
+ Unix SMB/CIFS implementation.
+ async query_group_list
+ Copyright (C) Volker Lendecke 2009
+ Copyright (C) Michael Adam 2015
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+
+struct wb_query_group_list_state {
+ struct wbint_Principals groups;
+};
+
+static void wb_query_group_list_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_query_group_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_query_group_list_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_query_group_list_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command group_list start.\nQuery domain %s\n", domain->name);
+ subreq = dcerpc_wbint_QueryGroupList_send(state, ev,
+ dom_child_handle(domain),
+ &state->groups);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, wb_query_group_list_done, req);
+ return req;
+}
+
+static void wb_query_group_list_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_query_group_list_state *state = tevent_req_data(
+ req, struct wb_query_group_list_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_QueryGroupList_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_query_group_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_groups,
+ struct wbint_Principal **groups)
+{
+ struct wb_query_group_list_state *state = tevent_req_data(
+ req, struct wb_query_group_list_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ *num_groups = state->groups.num_principals;
+ *groups = talloc_move(mem_ctx, &state->groups.principals);
+
+ D_INFO("WB command group_list end.\n"
+ "Returning %"PRIu32" group(s).\n", *num_groups);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_query_user_list.c b/source3/winbindd/wb_query_user_list.c
new file mode 100644
index 0000000..c3f52e5
--- /dev/null
+++ b/source3/winbindd/wb_query_user_list.c
@@ -0,0 +1,146 @@
+/*
+ Unix SMB/CIFS implementation.
+ async query_user_list
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/util/strv.h"
+
+struct wb_query_user_list_state {
+ struct tevent_context *ev;
+ struct winbindd_domain *domain;
+ struct wbint_RidArray rids;
+ const char *domain_name;
+ struct wbint_Principals names;
+ char *users;
+};
+
+static void wb_query_user_list_gotrids(struct tevent_req *subreq);
+static void wb_query_user_list_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_query_user_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_query_user_list_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_query_user_list_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command user_list start.\nQuery users in domain %s.\n",
+ domain->name);
+ state->ev = ev;
+ state->domain = domain;
+
+ subreq = dcerpc_wbint_QueryUserRidList_send(
+ state, ev, dom_child_handle(domain), &state->rids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_query_user_list_gotrids, req);
+ return req;
+}
+
+static void wb_query_user_list_gotrids(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_query_user_list_state *state = tevent_req_data(
+ req, struct wb_query_user_list_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_QueryUserRidList_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ D_DEBUG("dcerpc_wbint_QueryUserRidList returned %"PRIu32" users\n",
+ state->rids.num_rids);
+
+ subreq = dcerpc_wbint_LookupRids_send(
+ state, state->ev, dom_child_handle(state->domain),
+ &state->domain->sid, &state->rids,
+ &state->domain_name, &state->names);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_query_user_list_done, req);
+}
+
+static void wb_query_user_list_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_query_user_list_state *state = tevent_req_data(
+ req, struct wb_query_user_list_state);
+ NTSTATUS status, result;
+ uint32_t i;
+
+ status = dcerpc_wbint_LookupRids_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ D_DEBUG("Processing %"PRIu32" principal(s).\n",
+ state->names.num_principals);
+ for (i=0; i<state->names.num_principals; i++) {
+ struct wbint_Principal *p = &state->names.principals[i];
+ const char *name;
+ int ret;
+
+ name = fill_domain_username_talloc(state, state->domain_name, p->name, true);
+ if (name == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return;
+ }
+ ret = strv_add(state, &state->users, name);
+ if (ret != 0) {
+ tevent_req_nterror(req, map_nt_error_from_unix(ret));
+ return;
+ }
+ D_DEBUG("%"PRIu32": Adding user %s\n", i, name);
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_query_user_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **users)
+{
+ struct wb_query_user_list_state *state = tevent_req_data(
+ req, struct wb_query_user_list_state);
+ NTSTATUS status;
+
+ D_INFO("WB command user_list end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ *users = talloc_move(mem_ctx, &state->users);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_queryuser.c b/source3/winbindd/wb_queryuser.c
new file mode 100644
index 0000000..c2758f1
--- /dev/null
+++ b/source3/winbindd/wb_queryuser.c
@@ -0,0 +1,480 @@
+/*
+ Unix SMB/CIFS implementation.
+ async queryuser
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "util/debug.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+#include "libsmb/samlogon_cache.h"
+#include "librpc/gen_ndr/ndr_winbind.h"
+
+struct wb_queryuser_state {
+ struct tevent_context *ev;
+ struct wbint_userinfo *info;
+ const struct wb_parent_idmap_config *idmap_cfg;
+ bool tried_dclookup;
+};
+
+static void wb_queryuser_idmap_setup_done(struct tevent_req *subreq);
+static void wb_queryuser_got_uid(struct tevent_req *subreq);
+static void wb_queryuser_got_domain(struct tevent_req *subreq);
+static void wb_queryuser_got_dc(struct tevent_req *subreq);
+static void wb_queryuser_got_gid(struct tevent_req *subreq);
+static void wb_queryuser_got_group_name(struct tevent_req *subreq);
+static void wb_queryuser_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_queryuser_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *user_sid)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_queryuser_state *state;
+ struct wbint_userinfo *info;
+ struct dom_sid_buf buf;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_queryuser_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_INFO("WB command queryuser start.\nQuery user sid %s\n",
+ dom_sid_str_buf(user_sid, &buf));
+ state->ev = ev;
+
+ state->info = talloc_zero(state, struct wbint_userinfo);
+ if (tevent_req_nomem(state->info, req)) {
+ return tevent_req_post(req, ev);
+ }
+ info = state->info;
+
+ info->primary_gid = (gid_t)-1;
+
+ sid_copy(&info->user_sid, user_sid);
+
+ subreq = wb_parent_idmap_setup_send(state, state->ev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_idmap_setup_done, req);
+ return req;
+}
+
+static void wb_queryuser_idmap_setup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ NTSTATUS status;
+ struct dom_sid_buf buf;
+
+ status = wb_parent_idmap_setup_recv(subreq, &state->idmap_cfg);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_parent_idmap_setup_recv() failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+
+ D_DEBUG("Convert the user SID %s to XID.\n",
+ dom_sid_str_buf(&state->info->user_sid, &buf));
+ subreq = wb_sids2xids_send(
+ state, state->ev, &state->info->user_sid, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_got_uid, req);
+ return;
+}
+
+static void wb_queryuser_got_uid(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ struct wbint_userinfo *info = state->info;
+ struct netr_SamInfo3 *info3;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ struct unixid xid;
+ uint32_t user_rid = 0;
+ NTSTATUS status;
+ struct dom_sid_buf buf, buf1;
+
+ status = wb_sids2xids_recv(subreq, &xid, 1);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_sids2xids_recv() failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+
+ if ((xid.type != ID_TYPE_UID) && (xid.type != ID_TYPE_BOTH)) {
+ D_WARNING("XID type is %d, should be ID_TYPE_UID or ID_TYPE_BOTH.\n",
+ xid.type);
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return;
+ }
+
+ D_DEBUG("Received XID %"PRIu32" for SID %s.\n",
+ xid.id,
+ dom_sid_str_buf(&info->user_sid, &buf1));
+ info->uid = xid.id;
+
+ /*
+ * Default the group sid to "Domain Users" in the user's
+ * domain. The samlogon cache or the query_user call later on
+ * can override this.
+ * TODO: There is still missing functionality to set the correct group
+ * sid using samlogon cache (needs to use S4USelf).
+ * Once this is done, remove the workaround in test_membership_user() in
+ * source4/torture/local/nss_tests.c
+ */
+ sid_copy(&info->group_sid, &info->user_sid);
+ sid_split_rid(&info->group_sid, &user_rid);
+ sid_append_rid(&info->group_sid,
+ user_rid == DOMAIN_RID_GUEST ? DOMAIN_RID_GUESTS
+ : DOMAIN_RID_USERS);
+
+ D_DEBUG("Preconfigured 'Domain Users' RID %u was used to create group SID %s from user SID %s.\n",
+ DOMAIN_RID_USERS,
+ dom_sid_str_buf(&info->group_sid, &buf),
+ dom_sid_str_buf(&info->user_sid, &buf1));
+
+ info->homedir = talloc_strdup(info, lp_template_homedir());
+ D_DEBUG("Setting 'homedir' to the template '%s'.\n", info->homedir);
+ if (tevent_req_nomem(info->homedir, req)) {
+ return;
+ }
+
+ info->shell = talloc_strdup(info, lp_template_shell());
+ D_DEBUG("Setting 'shell' to the template '%s'.\n", info->shell);
+ if (tevent_req_nomem(info->shell, req)) {
+ return;
+ }
+
+ info3 = netsamlogon_cache_get(state, &info->user_sid);
+ if (info3 != NULL) {
+ D_DEBUG("Filling data received from netsamlogon_cache\n");
+ sid_compose(&info->group_sid, info3->base.domain_sid,
+ info3->base.primary_gid);
+ info->acct_name = talloc_move(
+ info, &info3->base.account_name.string);
+ info->full_name = talloc_move(
+ info, &info3->base.full_name.string);
+
+ info->domain_name = talloc_move(
+ state, &info3->base.logon_domain.string);
+
+ TALLOC_FREE(info3);
+ }
+
+ NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info);
+ if (info->domain_name == NULL) {
+ D_DEBUG("Domain name is empty, calling wb_lookupsid_send() to get it.\n");
+ subreq = wb_lookupsid_send(state, state->ev, &info->user_sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_got_domain, req);
+ return;
+ }
+
+ /*
+ * Note wb_sids2xids_send/recv was called before,
+ * so we're sure that wb_parent_idmap_setup_send/recv
+ * was already called.
+ */
+ child_binding_handle = idmap_child_handle();
+ D_DEBUG("Domain name is set, calling dcerpc_wbint_GetNssInfo_send()\n");
+ subreq = dcerpc_wbint_GetNssInfo_send(
+ state, state->ev, child_binding_handle, info);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_done, req);
+}
+
+static void wb_queryuser_got_domain(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ struct wbint_userinfo *info = state->info;
+ enum lsa_SidType type;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ NTSTATUS status;
+
+ status = wb_lookupsid_recv(subreq, state, &type,
+ &info->domain_name, &info->acct_name);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_lookupsid_recv failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+
+ NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info);
+ switch (type) {
+ case SID_NAME_USER:
+ case SID_NAME_COMPUTER:
+ /*
+ * user case: we only need the account name from lookup_sids
+ */
+ break;
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ /*
+ * also treat group-type SIDs (they might map to ID_TYPE_BOTH)
+ */
+ sid_copy(&info->group_sid, &info->user_sid);
+ break;
+ default:
+ D_WARNING("Unknown type:%d, return NT_STATUS_NO_SUCH_USER.\n",
+ type);
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return;
+ }
+
+ /*
+ * Note wb_sids2xids_send/recv was called before,
+ * so we're sure that wb_parent_idmap_setup_send/recv
+ * was already called.
+ */
+ child_binding_handle = idmap_child_handle();
+ D_DEBUG("About to call dcerpc_wbint_GetNssInfo_send()\n");
+ subreq = dcerpc_wbint_GetNssInfo_send(
+ state, state->ev, child_binding_handle, info);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_done, req);
+}
+
+static void wb_queryuser_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ struct wbint_userinfo *info = state->info;
+ NTSTATUS status, result;
+ bool need_group_name = false;
+ const char *tmpl = NULL;
+
+ status = dcerpc_wbint_GetNssInfo_recv(subreq, info, &result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("GetNssInfo failed with %s.\n", nt_errstr(status));
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) &&
+ !state->tried_dclookup) {
+ D_DEBUG("GetNssInfo got DOMAIN_CONTROLLER_NOT_FOUND, calling wb_dsgetdcname_send()\n");
+ subreq = wb_dsgetdcname_send(
+ state, state->ev, state->info->domain_name, NULL, NULL,
+ DS_RETURN_DNS_NAME);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_got_dc, req);
+ return;
+ }
+
+ /*
+ * Ignore failure in "result" here. We'll try to fill in stuff
+ * that misses further down.
+ */
+
+ if (state->info->primary_gid == (gid_t)-1) {
+ D_DEBUG("Calling wb_sids2xids_send() to resolve primary gid.\n");
+ subreq = wb_sids2xids_send(
+ state, state->ev, &info->group_sid, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_got_gid, req);
+ return;
+ }
+
+ tmpl = lp_template_homedir();
+ if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) {
+ need_group_name = true;
+ }
+ tmpl = lp_template_shell();
+ if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) {
+ need_group_name = true;
+ }
+
+ if (need_group_name && state->info->primary_group_name == NULL) {
+ D_DEBUG("Calling wb_lookupsid_send() to resolve primary group name.\n");
+ subreq = wb_lookupsid_send(state, state->ev, &info->group_sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_got_group_name,
+ req);
+ return;
+ }
+
+ NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info);
+ tevent_req_done(req);
+}
+
+static void wb_queryuser_got_dc(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ struct wbint_userinfo *info = state->info;
+ struct netr_DsRGetDCNameInfo *dcinfo;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ NTSTATUS status;
+
+ status = wb_dsgetdcname_recv(subreq, state, &dcinfo);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_dsgetdcname_recv() failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+
+ state->tried_dclookup = true;
+
+ D_DEBUG("Got DC name, calling wb_dsgetdcname_gencache_set().\n");
+ status = wb_dsgetdcname_gencache_set(info->domain_name, dcinfo);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_dsgetdcname_gencache_set() failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+ NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info);
+
+ /*
+ * Note wb_sids2xids_send/recv was called before,
+ * so we're sure that wb_parent_idmap_setup_send/recv
+ * was already called.
+ */
+ child_binding_handle = idmap_child_handle();
+ subreq = dcerpc_wbint_GetNssInfo_send(
+ state, state->ev, child_binding_handle, info);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_done, req);
+}
+
+static void wb_queryuser_got_gid(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ struct unixid xid;
+ NTSTATUS status;
+ bool need_group_name = false;
+ const char *tmpl = NULL;
+ struct dom_sid_buf buf;
+
+ status = wb_sids2xids_recv(subreq, &xid, 1);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_sids2xids_recv() failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+
+ D_DEBUG("Got XID %"PRIu32" with type %d.\n", xid.id, xid.type);
+ if ((xid.type != ID_TYPE_GID) && (xid.type != ID_TYPE_BOTH)) {
+ D_WARNING("Returning NT_STATUS_NO_SUCH_USER\n"
+ "xid.type must be ID_TYPE_UID or ID_TYPE_BOTH.\n");
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return;
+ }
+
+ state->info->primary_gid = xid.id;
+
+ tmpl = lp_template_homedir();
+ if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) {
+ need_group_name = true;
+ }
+ tmpl = lp_template_shell();
+ if(strstr_m(tmpl, "%g") || strstr_m(tmpl, "%G")) {
+ need_group_name = true;
+ }
+
+ NDR_PRINT_DEBUG_LEVEL(DBGLVL_DEBUG, wbint_userinfo, state->info);
+
+ if (need_group_name && state->info->primary_group_name == NULL) {
+ D_DEBUG("Calling wb_lookupsid_send for group SID %s.\n",
+ dom_sid_str_buf(&state->info->group_sid, &buf));
+ subreq = wb_lookupsid_send(state, state->ev,
+ &state->info->group_sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_queryuser_got_group_name,
+ req);
+ return;
+ }
+
+ D_DEBUG("No need to lookup primary group name. Request is done!\n");
+ tevent_req_done(req);
+}
+
+static void wb_queryuser_got_group_name(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ enum lsa_SidType type;
+ NTSTATUS status;
+ const char *domain_name;
+
+ status = wb_lookupsid_recv(subreq, state->info, &type, &domain_name,
+ &state->info->primary_group_name);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_lookupsid_recv() failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_queryuser_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct wbint_userinfo **pinfo)
+{
+ struct wb_queryuser_state *state = tevent_req_data(
+ req, struct wb_queryuser_state);
+ NTSTATUS status;
+
+ D_INFO("WB command queryuser end.\n");
+ NDR_PRINT_DEBUG_LEVEL(DBGLVL_INFO, wbint_userinfo, state->info);
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *pinfo = talloc_move(mem_ctx, &state->info);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_seqnum.c b/source3/winbindd/wb_seqnum.c
new file mode 100644
index 0000000..7affd76
--- /dev/null
+++ b/source3/winbindd/wb_seqnum.c
@@ -0,0 +1,78 @@
+/*
+ Unix SMB/CIFS implementation.
+ async seqnum
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct wb_seqnum_state {
+ uint32_t seqnum;
+};
+
+static void wb_seqnum_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_seqnum_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_seqnum_state *state;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_seqnum_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ subreq = dcerpc_wbint_QuerySequenceNumber_send(
+ state, ev, dom_child_handle(domain), &state->seqnum);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_seqnum_done, req);
+ return req;
+}
+
+static void wb_seqnum_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_seqnum_state *state = tevent_req_data(
+ req, struct wb_seqnum_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_QuerySequenceNumber_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS wb_seqnum_recv(struct tevent_req *req, uint32_t *seqnum)
+{
+ struct wb_seqnum_state *state = tevent_req_data(
+ req, struct wb_seqnum_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *seqnum = state->seqnum;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_seqnums.c b/source3/winbindd/wb_seqnums.c
new file mode 100644
index 0000000..24955ad
--- /dev/null
+++ b/source3/winbindd/wb_seqnums.c
@@ -0,0 +1,153 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ async seqnums, update the seqnums in winbindd_cache.c
+
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct wb_seqnums_state {
+ int num_domains;
+ int num_received;
+
+ struct tevent_req **subreqs;
+ struct winbindd_domain **domains;
+ NTSTATUS *statuses;
+ uint32_t *seqnums;
+};
+
+static void wb_seqnums_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_seqnums_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct tevent_req *req;
+ struct wb_seqnums_state *state;
+ struct winbindd_domain *domain;
+ int i;
+
+ req = tevent_req_create(mem_ctx, &state, struct wb_seqnums_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->num_received = 0;
+ state->num_domains = 0;
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ state->num_domains += 1;
+ }
+
+ state->subreqs = talloc_array(state, struct tevent_req *,
+ state->num_domains);
+ state->domains = talloc_zero_array(state, struct winbindd_domain *,
+ state->num_domains);
+ state->statuses = talloc_array(state, NTSTATUS, state->num_domains);
+ state->seqnums = talloc_array(state, uint32_t, state->num_domains);
+
+ if ((state->subreqs == NULL) || (state->domains == NULL) ||
+ (state->statuses == NULL) || (state->seqnums == NULL)) {
+ tevent_req_nterror(req, NT_STATUS_NO_MEMORY);
+ return tevent_req_post(req, ev);
+ }
+
+ i = 0;
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ state->domains[i] = domain;
+ state->subreqs[i] = wb_seqnum_send(state->subreqs, ev, domain);
+ if (tevent_req_nomem(state->subreqs[i], req)) {
+ /* Don't even start all the other requests */
+ TALLOC_FREE(state->subreqs);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(state->subreqs[i], wb_seqnums_done,
+ req);
+ i += 1;
+ }
+ return req;
+}
+
+static void wb_seqnums_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_seqnums_state *state = tevent_req_data(
+ req, struct wb_seqnums_state);
+ NTSTATUS status;
+ uint32_t seqnum;
+ int i;
+
+ status = wb_seqnum_recv(subreq, &seqnum);
+
+ for (i=0; i<state->num_domains; i++) {
+ if (subreq == state->subreqs[i]) {
+ break;
+ }
+ }
+ if (i < state->num_domains) {
+ /* found one */
+
+ state->subreqs[i] = NULL;
+ state->statuses[i] = status;
+ if (NT_STATUS_IS_OK(status)) {
+ state->seqnums[i] = seqnum;
+
+ /*
+ * This first assignment might be removed
+ * later
+ */
+ state->domains[i]->sequence_number = seqnum;
+
+ if (!wcache_store_seqnum(state->domains[i]->name,
+ state->seqnums[i],
+ time(NULL))) {
+ DEBUG(1, ("wcache_store_seqnum failed for "
+ "domain %s\n",
+ state->domains[i]->name));
+ }
+ }
+ }
+
+ TALLOC_FREE(subreq);
+
+ state->num_received += 1;
+
+ if (state->num_received >= state->num_domains) {
+ tevent_req_done(req);
+ }
+}
+
+NTSTATUS wb_seqnums_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ int *num_domains, struct winbindd_domain ***domains,
+ NTSTATUS **statuses, uint32_t **seqnums)
+{
+ struct wb_seqnums_state *state = tevent_req_data(
+ req, struct wb_seqnums_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ *num_domains = state->num_domains;
+ *domains = talloc_move(mem_ctx, &state->domains);
+ *statuses = talloc_move(mem_ctx, &state->statuses);
+ *seqnums = talloc_move(mem_ctx, &state->seqnums);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_sids2xids.c b/source3/winbindd/wb_sids2xids.c
new file mode 100644
index 0000000..f0f6c23
--- /dev/null
+++ b/source3/winbindd/wb_sids2xids.c
@@ -0,0 +1,786 @@
+/*
+ Unix SMB/CIFS implementation.
+ async sids2xids
+ Copyright (C) Volker Lendecke 2011
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+#include "idmap_cache.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "librpc/gen_ndr/ndr_netlogon.h"
+#include "lsa.h"
+
+struct wb_sids2xids_state {
+ struct tevent_context *ev;
+
+ const struct wb_parent_idmap_config *cfg;
+
+ struct dom_sid *sids;
+ uint32_t num_sids;
+
+ struct wbint_TransIDArray all_ids;
+
+ /* Used to translated the idx back into all_ids.ids[idx] */
+ uint32_t *tmp_idx;
+
+ uint32_t lookup_count;
+ struct dom_sid *lookup_sids;
+
+ struct wbint_TransIDArray map_ids_in;
+ struct wbint_TransIDArray map_ids_out;
+
+ /*
+ * Domain array to use for the idmap call. The output from
+ * lookupsids cannot be used directly since for migrated
+ * objects the returned domain SID can be different than the
+ * original one. The new domain SID cannot be combined with
+ * the RID from the previous domain.
+ *
+ * The proper way would be asking for the correct RID in the
+ * new domain, but this approach avoids id mappings for
+ * invalid SIDs.
+ */
+ struct lsa_RefDomainList idmap_doms;
+
+ uint32_t dom_index;
+ struct lsa_RefDomainList idmap_dom;
+ bool tried_dclookup;
+};
+
+static void wb_sids2xids_idmap_setup_done(struct tevent_req *subreq);
+static bool wb_sids2xids_in_cache(struct dom_sid *sid, struct id_map *map);
+static void wb_sids2xids_lookupsids_done(struct tevent_req *subreq);
+static void wb_sids2xids_done(struct tevent_req *subreq);
+static void wb_sids2xids_gotdc(struct tevent_req *subreq);
+static void wb_sids2xids_next_sids2unix(struct tevent_req *req);
+static enum id_type lsa_SidType_to_id_type(const enum lsa_SidType sid_type);
+
+struct tevent_req *wb_sids2xids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sids,
+ const uint32_t num_sids)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_sids2xids_state *state;
+ uint32_t i;
+ uint32_t num_valid = 0;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_sids2xids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command sids2xids start.\n"
+ "Resolving %"PRIu32" SID(s).\n", num_sids);
+
+ state->ev = ev;
+
+ state->num_sids = num_sids;
+
+ state->sids = talloc_zero_array(state, struct dom_sid, num_sids);
+ if (tevent_req_nomem(state->sids, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (i = 0; i < num_sids; i++) {
+ sid_copy(&state->sids[i], &sids[i]);
+ }
+
+ state->all_ids.num_ids = num_sids;
+ state->all_ids.ids = talloc_zero_array(state, struct wbint_TransID, num_sids);
+ if (tevent_req_nomem(state->all_ids.ids, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->tmp_idx = talloc_zero_array(state, uint32_t, num_sids);
+ if (tevent_req_nomem(state->tmp_idx, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->lookup_sids = talloc_zero_array(state, struct dom_sid, num_sids);
+ if (tevent_req_nomem(state->lookup_sids, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->map_ids_in.ids = talloc_zero_array(state, struct wbint_TransID, num_sids);
+ if (tevent_req_nomem(state->map_ids_in.ids, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Extract those sids that can not be resolved from cache
+ * into a separate list to be handed to id mapping, keeping
+ * the same index.
+ */
+ for (i=0; i<state->num_sids; i++) {
+ struct wbint_TransID *cur_id = &state->all_ids.ids[i];
+ struct dom_sid domain_sid;
+ struct dom_sid_buf buf;
+ struct id_map map = { .status = ID_UNMAPPED, };
+ uint32_t rid = 0;
+ bool in_cache;
+
+ sid_copy(&domain_sid, &state->sids[i]);
+ sid_split_rid(&domain_sid, &rid);
+
+ /*
+ * Start with an invalid entry.
+ */
+ *cur_id = (struct wbint_TransID) {
+ .type_hint = ID_TYPE_NOT_SPECIFIED,
+ .domain_index = UINT32_MAX - 1, /* invalid */
+ .rid = rid,
+ .xid = {
+ .id = UINT32_MAX,
+ .type = ID_TYPE_NOT_SPECIFIED,
+ },
+ };
+
+ D_DEBUG("%"PRIu32": SID %s\n",
+ i, dom_sid_str_buf(&state->sids[i], &buf));
+
+ in_cache = wb_sids2xids_in_cache(&state->sids[i], &map);
+ if (in_cache) {
+ /*
+ * We used to ignore map.status and just rely
+ * on map.xid.type.
+ *
+ * Lets keep this logic for now...
+ */
+
+ cur_id->xid = map.xid;
+ cur_id->domain_index = UINT32_MAX; /* this marks it as filled entry */
+ num_valid += 1;
+ continue;
+ }
+ }
+
+ D_DEBUG("Found %"PRIu32" (out of %"PRIu32") SID(s) in cache.\n",
+ num_valid, num_sids);
+ if (num_valid == num_sids) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_parent_idmap_setup_send(state, state->ev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_sids2xids_idmap_setup_done, req);
+ return req;
+}
+
+static void wb_sids2xids_idmap_setup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_sids2xids_state *state = tevent_req_data(
+ req, struct wb_sids2xids_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ status = wb_parent_idmap_setup_recv(subreq, &state->cfg);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+ SMB_ASSERT(state->cfg->num_doms > 0);
+ D_DEBUG("We will loop over %"PRIu32" SID(s) (skipping those already resolved via cache) and over %"PRIu32" domain(s).\n",
+ state->num_sids,
+ state->cfg->num_doms);
+
+ /*
+ * Now we build a list with all domain
+ * with non cached entries
+ */
+ for (i=0; i<state->num_sids; i++) {
+ struct wbint_TransID *t = &state->all_ids.ids[i];
+ struct dom_sid domain_sid;
+ const char *domain_name = NULL;
+ int domain_index;
+ uint32_t rid = 0;
+ uint32_t di;
+ struct dom_sid_buf buf0, buf1;
+
+ D_DEBUG("%"PRIu32": Processing SID %s\n",
+ i,
+ dom_sid_str_buf(&state->sids[i], &buf0));
+ if (t->domain_index == UINT32_MAX) {
+ /* ignore already filled entries */
+ D_DEBUG("%"PRIu32": Ignoring already resolved SID %s\n",
+ i,
+ dom_sid_str_buf(&state->sids[i], &buf0));
+ continue;
+ }
+
+ sid_copy(&domain_sid, &state->sids[i]);
+ sid_split_rid(&domain_sid, &rid);
+ D_DEBUG("%"PRIu32": Split SID %s into domain SID %s and RID %"PRIu32"\n",
+ i,
+ dom_sid_str_buf(&state->sids[i], &buf0),
+ dom_sid_str_buf(&domain_sid, &buf1),
+ rid);
+
+ if (t->type_hint == ID_TYPE_NOT_SPECIFIED) {
+ const char *tmp_name = NULL;
+ enum lsa_SidType sid_type = SID_NAME_USE_NONE;
+ const struct dom_sid *tmp_authority_sid = NULL;
+ const char *tmp_authority_name = NULL;
+
+ /*
+ * Try to get a type hint from for predefined sids
+ */
+ status = dom_sid_lookup_predefined_sid(&state->sids[i],
+ &tmp_name,
+ &sid_type,
+ &tmp_authority_sid,
+ &tmp_authority_name);
+ if (NT_STATUS_IS_OK(status)) {
+ t->type_hint = lsa_SidType_to_id_type(sid_type);
+ D_DEBUG("Got a type hint: %d from predefined SID.\n",
+ t->type_hint);
+ }
+ }
+
+ D_DEBUG("Looping over %"PRIu32" domain(s) to find domain SID %s.\n",
+ state->cfg->num_doms,
+ dom_sid_str_buf(&domain_sid, &buf0));
+ for (di = 0; di < state->cfg->num_doms; di++) {
+ struct wb_parent_idmap_config_dom *dom =
+ &state->cfg->doms[di];
+ bool match;
+
+ match = dom_sid_equal(&domain_sid, &dom->sid);
+ if (!match) {
+ continue;
+ }
+
+ domain_name = dom->name;
+ D_DEBUG("Found domain '%s'.\n", domain_name);
+ break;
+ }
+ if (domain_name == NULL) {
+ struct winbindd_domain *wb_domain = NULL;
+
+ D_DEBUG("Could not find a domain for domain SID %s. Trying to fill the domain name from list of known domains.\n",
+ dom_sid_str_buf(&domain_sid, &buf0));
+ /*
+ * Try to fill the name if we already know it
+ */
+ wb_domain = find_domain_from_sid_noinit(&state->sids[i]);
+ if (wb_domain != NULL) {
+ domain_name = wb_domain->name;
+ D_DEBUG("Found domain '%s' in list of known domains.\n", domain_name);
+ }
+ }
+ if (domain_name == NULL) {
+ domain_name = "";
+ D_DEBUG("Not found domain in list of known domains, setting empty domain name.\n");
+ }
+
+ if (t->type_hint == ID_TYPE_NOT_SPECIFIED) {
+ if (domain_name[0] != '\0') {
+ /*
+ * We know the domain, we indicate this
+ * by passing ID_TYPE_BOTH as a hint
+ *
+ * Maybe that's already enough for the backend
+ */
+ t->type_hint = ID_TYPE_BOTH;
+ D_DEBUG("Setting type hint ID_TYPE_BOTH for domain '%s'.\n", domain_name);
+ }
+ }
+
+ domain_index = init_lsa_ref_domain_list(state,
+ &state->idmap_doms,
+ domain_name,
+ &domain_sid);
+ if (domain_index == -1) {
+ tevent_req_oom(req);
+ return;
+ }
+ t->domain_index = domain_index;
+ }
+
+ /*
+ * We defer lookupsids because it requires domain controller
+ * interaction.
+ *
+ * First we ask the idmap child without explicit type hints.
+ * In most cases mappings already exist in the backend and
+ * a type_hint is not needed.
+ */
+ wb_sids2xids_next_sids2unix(req);
+}
+
+static bool wb_sids2xids_in_cache(struct dom_sid *sid, struct id_map *map)
+{
+ struct unixid id;
+ bool expired;
+
+ if (!winbindd_use_idmap_cache()) {
+ return false;
+ }
+ if (idmap_cache_find_sid2unixid(sid, &id, &expired)) {
+ if (expired && is_domain_online(find_our_domain())) {
+ return false;
+ }
+ map->sid = sid;
+ map->xid = id;
+ map->status = ID_MAPPED;
+ return true;
+ }
+ return false;
+}
+
+static void wb_sids2xids_lookupsids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_sids2xids_state *state = tevent_req_data(
+ req, struct wb_sids2xids_state);
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransNameArray *names = NULL;
+ NTSTATUS status;
+ uint32_t li;
+
+ status = wb_lookupsids_recv(subreq, state, &domains, &names);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+
+ if (domains == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+
+ if (names == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+
+ for (li = 0; li < state->lookup_count; li++) {
+ struct lsa_TranslatedName *n = &names->names[li];
+ uint32_t ai = state->tmp_idx[li];
+ struct wbint_TransID *t = &state->all_ids.ids[ai];
+ enum id_type type_hint;
+
+ type_hint = lsa_SidType_to_id_type(n->sid_type);
+ if (type_hint != ID_TYPE_NOT_SPECIFIED) {
+ /*
+ * We know it's a valid user or group.
+ */
+ t->type_hint = type_hint;
+ continue;
+ }
+
+ if (n->sid_index == UINT32_MAX) {
+ /*
+ * The domain is not known, there's
+ * no point to try mapping again.
+ * mark is done and add a negative cache
+ * entry.
+ */
+ t->domain_index = UINT32_MAX; /* mark as valid */
+ idmap_cache_set_sid2unixid(&state->sids[ai], &t->xid);
+ continue;
+ }
+
+ if (n->sid_index >= domains->count) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+
+ if (domains->domains[n->sid_index].name.string == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+ if (domains->domains[n->sid_index].sid == NULL) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Failed with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+
+ if (t->type_hint != ID_TYPE_NOT_SPECIFIED) {
+ /*
+ * We already tried with a type hint there's
+ * no point to try mapping again with ID_TYPE_BOTH.
+ *
+ * Mark is done and add a negative cache entry.
+ */
+ t->domain_index = UINT32_MAX; /* mark as valid */
+ idmap_cache_set_sid2unixid(&state->sids[ai], &t->xid);
+ continue;
+ }
+
+ /*
+ * We only know the domain exists, but the user doesn't
+ */
+ t->type_hint = ID_TYPE_BOTH;
+ }
+
+ TALLOC_FREE(names);
+ TALLOC_FREE(domains);
+
+ /*
+ * Now that we have type_hints for the remaining sids,
+ * we need to restart with the first domain.
+ */
+ state->dom_index = 0;
+ wb_sids2xids_next_sids2unix(req);
+}
+
+static void wb_sids2xids_next_sids2unix(struct tevent_req *req)
+{
+ struct wb_sids2xids_state *state = tevent_req_data(
+ req, struct wb_sids2xids_state);
+ struct tevent_req *subreq = NULL;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ const struct wbint_TransIDArray *src = NULL;
+ struct wbint_TransIDArray *dst = NULL;
+ uint32_t si;
+
+ next_domain:
+ state->tried_dclookup = false;
+
+ D_DEBUG("Processing next domain (dom_index=%"PRIu32", idmap_doms.count=%"PRIu32", lookup_count=%"PRIu32").\n",
+ state->dom_index,
+ state->idmap_doms.count,
+ state->lookup_count);
+ if (state->dom_index == state->idmap_doms.count) {
+ if (state->lookup_count != 0) {
+ /*
+ * We already called wb_lookupsids_send()
+ * before, so we're done.
+ */
+ D_DEBUG("We already called wb_lookupsids_send() before, so we're done.\n");
+ tevent_req_done(req);
+ return;
+ }
+
+ for (si=0; si < state->num_sids; si++) {
+ struct wbint_TransID *t = &state->all_ids.ids[si];
+
+ if (t->domain_index == UINT32_MAX) {
+ /* ignore already filled entries */
+ continue;
+ }
+
+ state->tmp_idx[state->lookup_count] = si;
+ sid_copy(&state->lookup_sids[state->lookup_count],
+ &state->sids[si]);
+ state->lookup_count += 1;
+ }
+
+ D_DEBUG("Prepared %"PRIu32" SID(s) for lookup wb_lookupsids_send().\n",
+ state->lookup_count);
+ if (state->lookup_count == 0) {
+ /*
+ * no wb_lookupsids_send() needed...
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ subreq = wb_lookupsids_send(state,
+ state->ev,
+ state->lookup_sids,
+ state->lookup_count);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_sids2xids_lookupsids_done, req);
+ return;
+ }
+
+ src = &state->all_ids;
+ dst = &state->map_ids_in;
+ dst->num_ids = 0;
+
+ for (si=0; si < src->num_ids; si++) {
+ if (src->ids[si].domain_index != state->dom_index) {
+ continue;
+ }
+
+ state->tmp_idx[dst->num_ids] = si;
+ dst->ids[dst->num_ids] = src->ids[si];
+ dst->ids[dst->num_ids].domain_index = 0;
+ dst->num_ids += 1;
+ }
+
+ if (dst->num_ids == 0) {
+ state->dom_index += 1;
+ D_DEBUG("Go to next domain.\n");
+ goto next_domain;
+ }
+
+ state->idmap_dom = (struct lsa_RefDomainList) {
+ .count = 1,
+ .domains = &state->idmap_doms.domains[state->dom_index],
+ .max_size = 1
+ };
+
+ /*
+ * dcerpc_wbint_Sids2UnixIDs_send/recv will
+ * allocate a new array for the response
+ * and overwrite _ids->ids pointer.
+ *
+ * So we better make a temporary copy
+ * of state->map_ids_in (which contains the request array)
+ * into state->map_ids_out.
+ *
+ * That makes it possible to reuse the pre-allocated
+ * state->map_ids_in.ids array.
+ */
+ state->map_ids_out = state->map_ids_in;
+ child_binding_handle = idmap_child_handle();
+ subreq = dcerpc_wbint_Sids2UnixIDs_send(
+ state, state->ev, child_binding_handle, &state->idmap_dom,
+ &state->map_ids_out);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_sids2xids_done, req);
+}
+
+static enum id_type lsa_SidType_to_id_type(const enum lsa_SidType sid_type)
+{
+ enum id_type type;
+
+ switch(sid_type) {
+ case SID_NAME_COMPUTER:
+ case SID_NAME_USER:
+ type = ID_TYPE_UID;
+ break;
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ type = ID_TYPE_GID;
+ break;
+ default:
+ type = ID_TYPE_NOT_SPECIFIED;
+ break;
+ }
+
+ return type;
+}
+
+static void wb_sids2xids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_sids2xids_state *state = tevent_req_data(
+ req, struct wb_sids2xids_state);
+ NTSTATUS status, result;
+ const struct wbint_TransIDArray *src = NULL;
+ struct wbint_TransIDArray *dst = NULL;
+ uint32_t si;
+
+ status = dcerpc_wbint_Sids2UnixIDs_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) &&
+ !state->tried_dclookup) {
+
+ struct lsa_DomainInfo *d;
+
+ D_DEBUG("Domain controller not found. Calling wb_dsgetdcname_send() to get it.\n");
+ d = &state->idmap_doms.domains[state->dom_index];
+
+ subreq = wb_dsgetdcname_send(
+ state, state->ev, d->name.string, NULL, NULL,
+ DS_RETURN_DNS_NAME);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_sids2xids_gotdc, req);
+ return;
+ }
+
+ src = &state->map_ids_out;
+ dst = &state->all_ids;
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ D_DEBUG("Either status %s or result %s is not ok. Report SIDs as not mapped.\n",
+ nt_errstr(status),
+ nt_errstr(result));
+ /*
+ * All we can do here is to report "not mapped"
+ */
+ src = &state->map_ids_in;
+ for (si=0; si < src->num_ids; si++) {
+ src->ids[si].xid.type = ID_TYPE_NOT_SPECIFIED;
+ }
+ }
+
+ if (src->num_ids != state->map_ids_in.num_ids) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ D_WARNING("Number of mapped SIDs does not match. Failing with NT_STATUS_INTERNAL_ERROR.\n");
+ return;
+ }
+
+ for (si=0; si < src->num_ids; si++) {
+ uint32_t di = state->tmp_idx[si];
+
+ if (src->ids[si].xid.type == ID_TYPE_WB_REQUIRE_TYPE) {
+ if (state->lookup_count == 0) {
+ D_DEBUG("The backend asks for more information (a type_hint), we'll do a lookupsids later.\n");
+ /*
+ * The backend asks for more information
+ * (a type_hint), we'll do a lookupsids
+ * later.
+ */
+ continue;
+ }
+
+ /*
+ * lookupsids was not able to provide a type_hint that
+ * satisfied the backend.
+ *
+ * Make sure we don't expose ID_TYPE_WB_REQUIRE_TYPE
+ * outside of winbindd!
+ */
+ D_DEBUG("lookupsids was not able to provide a type_hint that satisfied the backend. Make sure we don't expose ID_TYPE_WB_REQUIRE_TYPE outside of winbindd!\n");
+ src->ids[si].xid.type = ID_TYPE_NOT_SPECIFIED;
+ }
+
+ if (src->ids[si].xid.type != ID_TYPE_NOT_SPECIFIED) {
+ dst->ids[di].xid = src->ids[si].xid;
+ D_DEBUG("%"PRIu32": Setting XID %"PRIu32"\n",
+ si, src->ids[si].xid.id);
+ }
+ dst->ids[di].domain_index = UINT32_MAX; /* mark as valid */
+ idmap_cache_set_sid2unixid(&state->sids[di], &dst->ids[di].xid);
+ }
+
+ state->map_ids_in.num_ids = 0;
+ if (NT_STATUS_IS_OK(status)) {
+ /*
+ * If we got a valid response, we expect
+ * state->map_ids_out.ids to be a new allocated
+ * array, which we want to free early.
+ */
+ SMB_ASSERT(state->map_ids_out.ids != state->map_ids_in.ids);
+ TALLOC_FREE(state->map_ids_out.ids);
+ }
+ state->map_ids_out = (struct wbint_TransIDArray) { .num_ids = 0, };
+
+ state->dom_index += 1;
+
+ wb_sids2xids_next_sids2unix(req);
+}
+
+static void wb_sids2xids_gotdc(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_sids2xids_state *state = tevent_req_data(
+ req, struct wb_sids2xids_state);
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ struct netr_DsRGetDCNameInfo *dcinfo;
+ NTSTATUS status;
+
+ status = wb_dsgetdcname_recv(subreq, state, &dcinfo);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+
+ state->tried_dclookup = true;
+
+ {
+ struct lsa_DomainInfo *d =
+ &state->idmap_doms.domains[state->dom_index];
+ const char *dom_name = d->name.string;
+
+ status = wb_dsgetdcname_gencache_set(dom_name, dcinfo);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+ }
+
+ /*
+ * dcerpc_wbint_Sids2UnixIDs_send/recv will
+ * allocate a new array for the response
+ * and overwrite _ids->ids pointer.
+ *
+ * So we better make a temporary copy
+ * of state->map_ids_in (which contains the request array)
+ * into state->map_ids_out.
+ *
+ * That makes it possible to reuse the pre-allocated
+ * state->map_ids_in.ids array.
+ */
+ state->map_ids_out = state->map_ids_in;
+ child_binding_handle = idmap_child_handle();
+ subreq = dcerpc_wbint_Sids2UnixIDs_send(
+ state, state->ev, child_binding_handle, &state->idmap_dom,
+ &state->map_ids_out);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_sids2xids_done, req);
+}
+
+NTSTATUS wb_sids2xids_recv(struct tevent_req *req,
+ struct unixid xids[], uint32_t num_xids)
+{
+ struct wb_sids2xids_state *state = tevent_req_data(
+ req, struct wb_sids2xids_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+
+ if (num_xids != state->num_sids) {
+ D_WARNING("Error. We have resolved only %"PRIu32" XID(s), but caller asked for %"PRIu32".\n",
+ state->num_sids, num_xids);
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ D_INFO("WB command sids2xids end.\n");
+ for (i=0; i<state->num_sids; i++) {
+ struct dom_sid_buf buf;
+ xids[i] = state->all_ids.ids[i].xid;
+ D_INFO("%"PRIu32": Found XID %"PRIu32" for SID %s\n",
+ i,
+ xids[i].id,
+ dom_sid_str_buf(&state->sids[i], &buf));
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wb_xids2sids.c b/source3/winbindd/wb_xids2sids.c
new file mode 100644
index 0000000..86bd7f9
--- /dev/null
+++ b/source3/winbindd/wb_xids2sids.c
@@ -0,0 +1,422 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * async xids2sids
+ * Copyright (C) Volker Lendecke 2015
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+#include "idmap_cache.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "librpc/gen_ndr/ndr_netlogon.h"
+#include "passdb/lookup_sid.h"
+
+struct wb_xids2sids_dom_state {
+ struct tevent_context *ev;
+ struct unixid *all_xids;
+ const bool *cached;
+ size_t num_all_xids;
+ struct dom_sid *all_sids;
+ const struct wb_parent_idmap_config_dom *dom_map;
+ bool tried_dclookup;
+
+ size_t num_dom_xids;
+ struct unixid *dom_xids;
+ struct dom_sid *dom_sids;
+};
+
+static void wb_xids2sids_dom_done(struct tevent_req *subreq);
+static void wb_xids2sids_dom_gotdc(struct tevent_req *subreq);
+
+static struct tevent_req *wb_xids2sids_dom_send(
+ TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ const struct wb_parent_idmap_config_dom *dom_map,
+ struct unixid *xids,
+ const bool *cached,
+ size_t num_xids,
+ struct dom_sid *sids)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_xids2sids_dom_state *state;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ size_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_xids2sids_dom_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_DEBUG("Searching for %zu xid(s) in domain %s.\n",
+ num_xids,
+ dom_map->name);
+
+ state->ev = ev;
+ state->all_xids = xids;
+ state->cached = cached;
+ state->num_all_xids = num_xids;
+ state->all_sids = sids;
+ state->dom_map = dom_map;
+
+ state->dom_xids = talloc_array(state, struct unixid, num_xids);
+ if (tevent_req_nomem(state->dom_xids, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->dom_sids = talloc_array(state, struct dom_sid, num_xids);
+ if (tevent_req_nomem(state->dom_sids, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ for (i=0; i<num_xids; i++) {
+ struct unixid id = state->all_xids[i];
+
+ if ((id.id < dom_map->low_id) || (id.id > dom_map->high_id)) {
+ /* out of range */
+ D_DEBUG("%zu: XID %"PRIu32" is out of range.\n",
+ i, id.id);
+ continue;
+ }
+ if (state->cached[i]) {
+ /* already found in cache */
+ D_DEBUG("%zu: XID %"PRIu32" is already found in cache.\n",
+ i, id.id);
+ continue;
+ }
+ if (!is_null_sid(&state->all_sids[i])) {
+ /* already mapped in a previously asked domain */
+ D_DEBUG("%zu: XID %"PRIu32" is already mapped in a previously asked domain.\n",
+ i, id.id);
+ continue;
+ }
+ D_DEBUG("%zu: XID %"PRIu32" will be looked up via dcerpc_wbint_UnixIDs2Sids_send().\n",
+ i, id.id);
+ state->dom_xids[state->num_dom_xids++] = id;
+ }
+
+ if (state->num_dom_xids == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ child_binding_handle = idmap_child_handle();
+ subreq = dcerpc_wbint_UnixIDs2Sids_send(
+ state, ev, child_binding_handle, dom_map->name, dom_map->sid,
+ state->num_dom_xids, state->dom_xids, state->dom_sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_xids2sids_dom_done, req);
+ return req;
+}
+
+static void wb_xids2sids_dom_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_xids2sids_dom_state *state = tevent_req_data(
+ req, struct wb_xids2sids_dom_state);
+ const struct wb_parent_idmap_config_dom *dom_map = state->dom_map;
+ NTSTATUS status, result;
+ size_t i;
+ size_t dom_sid_idx;
+
+ status = dcerpc_wbint_UnixIDs2Sids_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) &&
+ !state->tried_dclookup) {
+
+ subreq = wb_dsgetdcname_send(
+ state, state->ev, state->dom_map->name, NULL, NULL,
+ DS_RETURN_DNS_NAME);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_xids2sids_dom_gotdc, req);
+ return;
+ }
+
+ if (!NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED) &&
+ tevent_req_nterror(req, result)) {
+ return;
+ }
+
+ dom_sid_idx = 0;
+
+ D_DEBUG("Processing response for %zu xid(s).\n", state->num_all_xids);
+ for (i=0; i<state->num_all_xids; i++) {
+ struct unixid *id = &state->all_xids[i];
+ struct dom_sid_buf buf;
+
+ if ((id->id < dom_map->low_id) || (id->id > dom_map->high_id)) {
+ /* out of range */
+ continue;
+ }
+ if (state->cached[i]) {
+ /* already found in cache */
+ continue;
+ }
+ if (!is_null_sid(&state->all_sids[i])) {
+ /* already mapped in a previously asked domain */
+ continue;
+ }
+
+ sid_copy(&state->all_sids[i], &state->dom_sids[dom_sid_idx]);
+ *id = state->dom_xids[dom_sid_idx];
+ D_DEBUG("%zu: XID %"PRIu32" mapped to SID %s.\n",
+ i,
+ id->id,
+ dom_sid_str_buf(&state->all_sids[i], &buf));
+
+ dom_sid_idx += 1;
+ }
+
+ tevent_req_done(req);
+}
+
+static void wb_xids2sids_dom_gotdc(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_xids2sids_dom_state *state = tevent_req_data(
+ req, struct wb_xids2sids_dom_state);
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ struct netr_DsRGetDCNameInfo *dcinfo;
+ NTSTATUS status;
+
+ status = wb_dsgetdcname_recv(subreq, state, &dcinfo);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->tried_dclookup = true;
+
+ status = wb_dsgetdcname_gencache_set(state->dom_map->name, dcinfo);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ child_binding_handle = idmap_child_handle();
+ subreq = dcerpc_wbint_UnixIDs2Sids_send(
+ state, state->ev, child_binding_handle, state->dom_map->name,
+ state->dom_map->sid, state->num_dom_xids,
+ state->dom_xids, state->dom_sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_xids2sids_dom_done, req);
+}
+
+static NTSTATUS wb_xids2sids_dom_recv(struct tevent_req *req)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
+
+struct wb_xids2sids_state {
+ struct tevent_context *ev;
+ struct unixid *xids;
+ size_t num_xids;
+ struct dom_sid *sids;
+ bool *cached;
+
+ size_t dom_idx;
+ const struct wb_parent_idmap_config *cfg;
+};
+
+static void wb_xids2sids_idmap_setup_done(struct tevent_req *subreq);
+static void wb_xids2sids_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_xids2sids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct unixid *xids,
+ uint32_t num_xids)
+{
+ struct tevent_req *req, *subreq;
+ struct wb_xids2sids_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_xids2sids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_INFO("WB command xids2sids start.\nLooking up %"PRIu32" XID(s).\n",
+ num_xids);
+
+ state->ev = ev;
+ state->num_xids = num_xids;
+
+ state->xids = talloc_array(state, struct unixid, num_xids);
+ if (tevent_req_nomem(state->xids, req)) {
+ return tevent_req_post(req, ev);
+ }
+ memcpy(state->xids, xids, num_xids * sizeof(struct unixid));
+
+ state->sids = talloc_zero_array(state, struct dom_sid, num_xids);
+ if (tevent_req_nomem(state->sids, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->cached = talloc_zero_array(state, bool, num_xids);
+ if (tevent_req_nomem(state->cached, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (winbindd_use_idmap_cache()) {
+ uint32_t i;
+
+ for (i=0; i<num_xids; i++) {
+ struct dom_sid sid = {0};
+ bool ok, expired = true;
+
+ ok = idmap_cache_find_xid2sid(
+ &xids[i], &sid, &expired);
+ if (ok && !expired) {
+ struct dom_sid_buf buf;
+ DBG_DEBUG("Found %cID in cache: %s\n",
+ xids[i].type == ID_TYPE_UID?'U':'G',
+ dom_sid_str_buf(&sid, &buf));
+
+ sid_copy(&state->sids[i], &sid);
+ state->cached[i] = true;
+ }
+ }
+ }
+
+ subreq = wb_parent_idmap_setup_send(state, state->ev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_xids2sids_idmap_setup_done, req);
+ return req;
+}
+
+static void wb_xids2sids_idmap_setup_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_xids2sids_state *state = tevent_req_data(
+ req, struct wb_xids2sids_state);
+ NTSTATUS status;
+
+ status = wb_parent_idmap_setup_recv(subreq, &state->cfg);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ SMB_ASSERT(state->cfg->num_doms > 0);
+
+ subreq = wb_xids2sids_dom_send(
+ state, state->ev,
+ &state->cfg->doms[state->dom_idx],
+ state->xids, state->cached, state->num_xids, state->sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_xids2sids_done, req);
+ return;
+}
+
+static void wb_xids2sids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_xids2sids_state *state = tevent_req_data(
+ req, struct wb_xids2sids_state);
+ size_t i;
+ NTSTATUS status;
+
+ status = wb_xids2sids_dom_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->dom_idx += 1;
+ if (state->dom_idx < state->cfg->num_doms) {
+ const struct wb_parent_idmap_config_dom *dom_map =
+ &state->cfg->doms[state->dom_idx];
+
+ subreq = wb_xids2sids_dom_send(state,
+ state->ev,
+ dom_map,
+ state->xids,
+ state->cached,
+ state->num_xids,
+ state->sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_xids2sids_done, req);
+ return;
+ }
+
+
+ for (i = 0; i < state->num_xids; i++) {
+ /*
+ * Prime the cache after an xid2sid call. It's important that we
+ * use the xid value returned from the backend for the xid value
+ * passed to idmap_cache_set_sid2unixid(), not the input to
+ * wb_xids2sids_send: the input carries what was asked for,
+ * e.g. a ID_TYPE_UID. The result from the backend something the
+ * idmap child possibly changed to ID_TYPE_BOTH.
+ *
+ * And of course If the value was from the cache don't update
+ * the cache.
+ */
+
+ if (state->cached[i]) {
+ continue;
+ }
+
+ idmap_cache_set_sid2unixid(&state->sids[i], &state->xids[i]);
+ }
+
+ tevent_req_done(req);
+ return;
+}
+
+NTSTATUS wb_xids2sids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct dom_sid **sids)
+{
+ struct wb_xids2sids_state *state = tevent_req_data(
+ req, struct wb_xids2sids_state);
+ NTSTATUS status;
+ size_t i;
+
+ D_INFO("WB command xids2sids end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("wb_sids_to_xids failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ *sids = talloc_move(mem_ctx, &state->sids);
+ if (CHECK_DEBUGLVL(DBGLVL_INFO)) {
+ for (i = 0; i < state->num_xids; i++) {
+ struct dom_sid_buf buf;
+ D_INFO("%zu: XID %"PRIu32" mapped to SID %s\n",
+ i,
+ state->xids[i].id,
+ dom_sid_str_buf(&((*sids)[i]), &buf));
+ }
+ }
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd.c b/source3/winbindd/winbindd.c
new file mode 100644
index 0000000..29a24a9
--- /dev/null
+++ b/source3/winbindd/winbindd.c
@@ -0,0 +1,1742 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) by Tim Potter 2000-2002
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Jelmer Vernooij 2003
+ Copyright (C) Volker Lendecke 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "lib/cmdline/cmdline.h"
+#include "winbindd.h"
+#include "nsswitch/winbind_client.h"
+#include "nsswitch/wb_reqtrans.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_lsa_scompat.h"
+#include "librpc/gen_ndr/ndr_samr_scompat.h"
+#include "librpc/gen_ndr/ndr_winbind_scompat.h"
+#include "secrets.h"
+#include "rpc_client/cli_netlogon.h"
+#include "idmap.h"
+#include "lib/addrchange.h"
+#include "auth.h"
+#include "messages.h"
+#include "../lib/util/pidfile.h"
+#include "util_cluster.h"
+#include "source4/lib/messaging/irpc.h"
+#include "source4/lib/messaging/messaging.h"
+#include "lib/param/param.h"
+#include "lib/async_req/async_sock.h"
+#include "libsmb/samlogon_cache.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "passdb.h"
+#include "lib/util/tevent_req_profile.h"
+#include "lib/gencache.h"
+#include "rpc_server/rpc_config.h"
+#include "lib/global_contexts.h"
+#include "source3/lib/substitute.h"
+#include "winbindd_traceid.h"
+#include "lib/util/util_process.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define SCRUB_CLIENTS_INTERVAL 5
+
+static bool client_is_idle(struct winbindd_cli_state *state);
+static void remove_client(struct winbindd_cli_state *state);
+
+static bool interactive = False;
+
+/* Reload configuration */
+
+
+
+static void winbindd_status(void)
+{
+ struct winbindd_cli_state *tmp;
+
+ DEBUG(0, ("winbindd status:\n"));
+
+ /* Print client state information */
+
+ DEBUG(0, ("\t%d clients currently active\n", winbindd_num_clients()));
+
+ if (DEBUGLEVEL >= 2 && winbindd_num_clients()) {
+ DEBUG(2, ("\tclient list:\n"));
+ for(tmp = winbindd_client_list(); tmp; tmp = tmp->next) {
+ DEBUGADD(2, ("\t\tpid %lu, sock %d (%s)\n",
+ (unsigned long)tmp->pid, tmp->sock,
+ client_is_idle(tmp) ? "idle" : "active"));
+ }
+ }
+}
+
+/*
+ handle stdin becoming readable when we are in --foreground mode
+ */
+static void winbindd_stdin_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ char c;
+ if (read(0, &c, 1) != 1) {
+ bool *is_parent = talloc_get_type_abort(private_data, bool);
+
+ /* we have reached EOF on stdin, which means the
+ parent has exited. Shutdown the server */
+ DEBUG(0,("EOF on stdin (is_parent=%d)\n",
+ (int)*is_parent));
+ winbindd_terminate(*is_parent);
+ }
+}
+
+bool winbindd_setup_stdin_handler(bool parent, bool foreground)
+{
+ bool *is_parent;
+
+ if (foreground) {
+ struct stat st;
+
+ is_parent = talloc(global_event_context(), bool);
+ if (!is_parent) {
+ return false;
+ }
+
+ *is_parent = parent;
+
+ /* if we are running in the foreground then look for
+ EOF on stdin, and exit if it happens. This allows
+ us to die if the parent process dies
+ Only do this on a pipe or socket, no other device.
+ */
+ if (fstat(0, &st) != 0) {
+ return false;
+ }
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode)) {
+ tevent_add_fd(global_event_context(),
+ is_parent,
+ 0,
+ TEVENT_FD_READ,
+ winbindd_stdin_handler,
+ is_parent);
+ }
+ }
+
+ return true;
+}
+
+static void winbindd_sig_chld_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ pid_t pid;
+
+ while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
+ winbind_child_died(pid);
+ }
+}
+
+static bool winbindd_setup_sig_chld_handler(void)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(global_event_context(),
+ global_event_context(),
+ SIGCHLD, 0,
+ winbindd_sig_chld_handler,
+ NULL);
+ if (!se) {
+ return false;
+ }
+
+ return true;
+}
+
+static void winbindd_sig_usr2_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ winbindd_status();
+}
+
+static bool winbindd_setup_sig_usr2_handler(void)
+{
+ struct tevent_signal *se;
+
+ se = tevent_add_signal(global_event_context(),
+ global_event_context(),
+ SIGUSR2, 0,
+ winbindd_sig_usr2_handler,
+ NULL);
+ if (!se) {
+ return false;
+ }
+
+ return true;
+}
+
+/* React on 'smbcontrol winbindd shutdown' in the same way as on SIGTERM*/
+static void msg_shutdown(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ /* only the parent waits for this message */
+ DEBUG(0,("Got shutdown message\n"));
+ winbindd_terminate(true);
+}
+
+
+static void winbind_msg_validate_cache(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ uint8_t ret;
+ pid_t child_pid;
+ NTSTATUS status;
+
+ DEBUG(10, ("winbindd_msg_validate_cache: got validate-cache "
+ "message.\n"));
+
+ /*
+ * call the validation code from a child:
+ * so we don't block the main winbindd and the validation
+ * code can safely use fork/waitpid...
+ */
+ child_pid = fork();
+
+ if (child_pid == -1) {
+ DEBUG(1, ("winbind_msg_validate_cache: Could not fork: %s\n",
+ strerror(errno)));
+ return;
+ }
+
+ if (child_pid != 0) {
+ /* parent */
+ DEBUG(5, ("winbind_msg_validate_cache: child created with "
+ "pid %d.\n", (int)child_pid));
+ return;
+ }
+
+ /* child */
+
+ status = winbindd_reinit_after_fork(NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("winbindd_reinit_after_fork failed: %s\n",
+ nt_errstr(status)));
+ _exit(0);
+ }
+
+ /* install default SIGCHLD handler: validation code uses fork/waitpid */
+ CatchSignal(SIGCHLD, SIG_DFL);
+
+ process_set_title("wb: check cache", "validate cache child");
+
+ ret = (uint8_t)winbindd_validate_cache_nobackup();
+ DEBUG(10, ("winbindd_msg_validata_cache: got return value %d\n", ret));
+ messaging_send_buf(msg_ctx, server_id, MSG_WINBIND_VALIDATE_CACHE, &ret,
+ (size_t)1);
+ _exit(0);
+}
+
+static struct winbindd_bool_dispatch_table {
+ enum winbindd_cmd cmd;
+ bool (*fn)(struct winbindd_cli_state *state);
+ const char *cmd_name;
+} bool_dispatch_table[] = {
+ { WINBINDD_INTERFACE_VERSION,
+ winbindd_interface_version,
+ "INTERFACE_VERSION" },
+ { WINBINDD_INFO,
+ winbindd_info,
+ "INFO" },
+ { WINBINDD_PING,
+ winbindd_ping,
+ "PING" },
+ { WINBINDD_DOMAIN_NAME,
+ winbindd_domain_name,
+ "DOMAIN_NAME" },
+ { WINBINDD_NETBIOS_NAME,
+ winbindd_netbios_name,
+ "NETBIOS_NAME" },
+ { WINBINDD_DC_INFO,
+ winbindd_dc_info,
+ "DC_INFO" },
+ { WINBINDD_CCACHE_NTLMAUTH,
+ winbindd_ccache_ntlm_auth,
+ "NTLMAUTH" },
+ { WINBINDD_CCACHE_SAVE,
+ winbindd_ccache_save,
+ "CCACHE_SAVE" },
+ { WINBINDD_PRIV_PIPE_DIR,
+ winbindd_priv_pipe_dir,
+ "WINBINDD_PRIV_PIPE_DIR" },
+ { WINBINDD_LIST_TRUSTDOM,
+ winbindd_list_trusted_domains,
+ "LIST_TRUSTDOM" },
+};
+
+struct winbindd_async_dispatch_table {
+ enum winbindd_cmd cmd;
+ const char *cmd_name;
+ struct tevent_req *(*send_req)(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+ NTSTATUS (*recv_req)(struct tevent_req *req,
+ struct winbindd_response *presp);
+};
+
+static struct winbindd_async_dispatch_table async_nonpriv_table[] = {
+ { WINBINDD_LOOKUPSID, "LOOKUPSID",
+ winbindd_lookupsid_send, winbindd_lookupsid_recv },
+ { WINBINDD_LOOKUPSIDS, "LOOKUPSIDS",
+ winbindd_lookupsids_send, winbindd_lookupsids_recv },
+ { WINBINDD_LOOKUPNAME, "LOOKUPNAME",
+ winbindd_lookupname_send, winbindd_lookupname_recv },
+ { WINBINDD_SIDS_TO_XIDS, "SIDS_TO_XIDS",
+ winbindd_sids_to_xids_send, winbindd_sids_to_xids_recv },
+ { WINBINDD_XIDS_TO_SIDS, "XIDS_TO_SIDS",
+ winbindd_xids_to_sids_send, winbindd_xids_to_sids_recv },
+ { WINBINDD_GETPWSID, "GETPWSID",
+ winbindd_getpwsid_send, winbindd_getpwsid_recv },
+ { WINBINDD_GETPWNAM, "GETPWNAM",
+ winbindd_getpwnam_send, winbindd_getpwnam_recv },
+ { WINBINDD_GETPWUID, "GETPWUID",
+ winbindd_getpwuid_send, winbindd_getpwuid_recv },
+ { WINBINDD_GETSIDALIASES, "GETSIDALIASES",
+ winbindd_getsidaliases_send, winbindd_getsidaliases_recv },
+ { WINBINDD_GETUSERDOMGROUPS, "GETUSERDOMGROUPS",
+ winbindd_getuserdomgroups_send, winbindd_getuserdomgroups_recv },
+ { WINBINDD_GETGROUPS, "GETGROUPS",
+ winbindd_getgroups_send, winbindd_getgroups_recv },
+ { WINBINDD_SHOW_SEQUENCE, "SHOW_SEQUENCE",
+ winbindd_show_sequence_send, winbindd_show_sequence_recv },
+ { WINBINDD_GETGRGID, "GETGRGID",
+ winbindd_getgrgid_send, winbindd_getgrgid_recv },
+ { WINBINDD_GETGRNAM, "GETGRNAM",
+ winbindd_getgrnam_send, winbindd_getgrnam_recv },
+ { WINBINDD_GETUSERSIDS, "GETUSERSIDS",
+ winbindd_getusersids_send, winbindd_getusersids_recv },
+ { WINBINDD_LOOKUPRIDS, "LOOKUPRIDS",
+ winbindd_lookuprids_send, winbindd_lookuprids_recv },
+ { WINBINDD_SETPWENT, "SETPWENT",
+ winbindd_setpwent_send, winbindd_setpwent_recv },
+ { WINBINDD_GETPWENT, "GETPWENT",
+ winbindd_getpwent_send, winbindd_getpwent_recv },
+ { WINBINDD_ENDPWENT, "ENDPWENT",
+ winbindd_endpwent_send, winbindd_endpwent_recv },
+ { WINBINDD_DSGETDCNAME, "DSGETDCNAME",
+ winbindd_dsgetdcname_send, winbindd_dsgetdcname_recv },
+ { WINBINDD_GETDCNAME, "GETDCNAME",
+ winbindd_getdcname_send, winbindd_getdcname_recv },
+ { WINBINDD_SETGRENT, "SETGRENT",
+ winbindd_setgrent_send, winbindd_setgrent_recv },
+ { WINBINDD_GETGRENT, "GETGRENT",
+ winbindd_getgrent_send, winbindd_getgrent_recv },
+ { WINBINDD_ENDGRENT, "ENDGRENT",
+ winbindd_endgrent_send, winbindd_endgrent_recv },
+ { WINBINDD_LIST_USERS, "LIST_USERS",
+ winbindd_list_users_send, winbindd_list_users_recv },
+ { WINBINDD_LIST_GROUPS, "LIST_GROUPS",
+ winbindd_list_groups_send, winbindd_list_groups_recv },
+ { WINBINDD_CHECK_MACHACC, "CHECK_MACHACC",
+ winbindd_check_machine_acct_send, winbindd_check_machine_acct_recv },
+ { WINBINDD_PING_DC, "PING_DC",
+ winbindd_ping_dc_send, winbindd_ping_dc_recv },
+ { WINBINDD_PAM_AUTH, "PAM_AUTH",
+ winbindd_pam_auth_send, winbindd_pam_auth_recv },
+ { WINBINDD_PAM_LOGOFF, "PAM_LOGOFF",
+ winbindd_pam_logoff_send, winbindd_pam_logoff_recv },
+ { WINBINDD_PAM_CHAUTHTOK, "PAM_CHAUTHTOK",
+ winbindd_pam_chauthtok_send, winbindd_pam_chauthtok_recv },
+ { WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP, "PAM_CHNG_PSWD_AUTH_CRAP",
+ winbindd_pam_chng_pswd_auth_crap_send,
+ winbindd_pam_chng_pswd_auth_crap_recv },
+ { WINBINDD_WINS_BYIP, "WINS_BYIP",
+ winbindd_wins_byip_send, winbindd_wins_byip_recv },
+ { WINBINDD_WINS_BYNAME, "WINS_BYNAME",
+ winbindd_wins_byname_send, winbindd_wins_byname_recv },
+ { WINBINDD_DOMAIN_INFO, "DOMAIN_INFO",
+ winbindd_domain_info_send, winbindd_domain_info_recv },
+
+ { 0, NULL, NULL, NULL }
+};
+
+static struct winbindd_async_dispatch_table async_priv_table[] = {
+ { WINBINDD_ALLOCATE_UID, "ALLOCATE_UID",
+ winbindd_allocate_uid_send, winbindd_allocate_uid_recv },
+ { WINBINDD_ALLOCATE_GID, "ALLOCATE_GID",
+ winbindd_allocate_gid_send, winbindd_allocate_gid_recv },
+ { WINBINDD_CHANGE_MACHACC, "CHANGE_MACHACC",
+ winbindd_change_machine_acct_send, winbindd_change_machine_acct_recv },
+ { WINBINDD_PAM_AUTH_CRAP, "PAM_AUTH_CRAP",
+ winbindd_pam_auth_crap_send, winbindd_pam_auth_crap_recv },
+
+ { 0, NULL, NULL, NULL }
+};
+
+struct process_request_state {
+ struct winbindd_cli_state *cli_state;
+ struct tevent_context *ev;
+};
+
+static void process_request_done(struct tevent_req *subreq);
+static void process_request_written(struct tevent_req *subreq);
+
+static struct tevent_req *process_request_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli_state)
+{
+ struct tevent_req *req, *subreq;
+ struct process_request_state *state;
+ struct winbindd_async_dispatch_table *atable;
+ enum winbindd_cmd cmd = cli_state->request->cmd;
+ size_t i;
+ bool ok;
+ static uint64_t request_index = 1;
+
+ /*
+ * debug traceid values:
+ * 0 .. inactive
+ * 1 .. not processing a winbind request, but in other code (timers)
+ * >=2 .. winbind request processing
+ */
+ if (debug_traceid_get() != 0) {
+ request_index = ++request_index == 0 ? 2 : request_index;
+ debug_traceid_set(request_index);
+ }
+ req = tevent_req_create(mem_ctx, &state,
+ struct process_request_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->cli_state = cli_state;
+ state->ev = ev;
+
+ ok = tevent_req_set_profile(req);
+ if (!ok) {
+ return tevent_req_post(req, ev);
+ }
+
+ SMB_ASSERT(cli_state->mem_ctx == NULL);
+ cli_state->mem_ctx = talloc_named(cli_state, 0, "winbind request");
+ if (tevent_req_nomem(cli_state->mem_ctx, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ cli_state->response = talloc_zero(
+ cli_state->mem_ctx,
+ struct winbindd_response);
+ if (tevent_req_nomem(cli_state->response, req)) {
+ return tevent_req_post(req, ev);
+ }
+ cli_state->response->result = WINBINDD_PENDING;
+ cli_state->response->length = sizeof(struct winbindd_response);
+
+ /* Remember who asked us. */
+ cli_state->pid = cli_state->request->pid;
+ memcpy(cli_state->client_name,
+ cli_state->request->client_name,
+ sizeof(cli_state->client_name));
+
+ cli_state->cmd_name = "unknown request";
+ cli_state->recv_fn = NULL;
+
+ /* client is newest */
+ winbindd_promote_client(cli_state);
+
+ for (atable = async_nonpriv_table; atable->send_req; atable += 1) {
+ if (cmd == atable->cmd) {
+ break;
+ }
+ }
+
+ if ((atable->send_req == NULL) && cli_state->privileged) {
+ for (atable = async_priv_table; atable->send_req;
+ atable += 1) {
+ if (cmd == atable->cmd) {
+ break;
+ }
+ }
+ }
+
+ if (atable->send_req != NULL) {
+ cli_state->cmd_name = atable->cmd_name;
+ cli_state->recv_fn = atable->recv_req;
+
+ DBG_NOTICE("[%s (%d)] Handling async request: %s\n",
+ cli_state->client_name,
+ (int)cli_state->pid,
+ cli_state->cmd_name);
+
+ subreq = atable->send_req(
+ state,
+ state->ev,
+ cli_state,
+ cli_state->request);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, process_request_done, req);
+ return req;
+ }
+
+ for (i=0; i<ARRAY_SIZE(bool_dispatch_table); i++) {
+ if (cmd == bool_dispatch_table[i].cmd) {
+ break;
+ }
+ }
+
+ ok = false;
+
+ if (i < ARRAY_SIZE(bool_dispatch_table)) {
+ cli_state->cmd_name = bool_dispatch_table[i].cmd_name;
+
+ DBG_DEBUG("process_request: request fn %s\n",
+ bool_dispatch_table[i].cmd_name);
+ ok = bool_dispatch_table[i].fn(cli_state);
+ }
+
+ cli_state->response->result = ok ? WINBINDD_OK : WINBINDD_ERROR;
+
+ TALLOC_FREE(cli_state->io_req);
+ TALLOC_FREE(cli_state->request);
+
+ subreq = wb_resp_write_send(
+ state,
+ state->ev,
+ cli_state->out_queue,
+ cli_state->sock,
+ cli_state->response);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, process_request_written, req);
+
+ cli_state->io_req = subreq;
+
+ return req;
+}
+
+static void process_request_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct process_request_state *state = tevent_req_data(
+ req, struct process_request_state);
+ struct winbindd_cli_state *cli_state = state->cli_state;
+ NTSTATUS status;
+ bool ok;
+
+ status = cli_state->recv_fn(subreq, cli_state->response);
+ TALLOC_FREE(subreq);
+
+ DBG_NOTICE("[%s(%d):%s]: %s\n",
+ cli_state->client_name,
+ (int)cli_state->pid,
+ cli_state->cmd_name,
+ nt_errstr(status));
+
+ ok = NT_STATUS_IS_OK(status);
+ cli_state->response->result = ok ? WINBINDD_OK : WINBINDD_ERROR;
+
+ TALLOC_FREE(cli_state->io_req);
+ TALLOC_FREE(cli_state->request);
+
+ subreq = wb_resp_write_send(
+ state,
+ state->ev,
+ cli_state->out_queue,
+ cli_state->sock,
+ cli_state->response);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, process_request_written, req);
+
+ cli_state->io_req = subreq;
+}
+
+static void process_request_written(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct process_request_state *state = tevent_req_data(
+ req, struct process_request_state);
+ struct winbindd_cli_state *cli_state = state->cli_state;
+ ssize_t ret;
+ int err;
+
+ cli_state->io_req = NULL;
+
+ ret = wb_resp_write_recv(subreq, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_nterror(req, map_nt_error_from_unix(err));
+ return;
+ }
+
+ DBG_DEBUG("[%s(%d):%s]: delivered response to client\n",
+ cli_state->client_name,
+ (int)cli_state->pid,
+ cli_state->cmd_name);
+
+ TALLOC_FREE(cli_state->mem_ctx);
+ cli_state->response = NULL;
+ cli_state->cmd_name = "no request";
+ cli_state->recv_fn = NULL;
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS process_request_recv(
+ struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ struct tevent_req_profile **profile)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *profile = tevent_req_move_profile(req, mem_ctx);
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+/*
+ * This is the main event loop of winbind requests. It goes through a
+ * state-machine of 3 read/write requests, 4 if you have extra data to send.
+ *
+ * An idle winbind client has a read request of 4 bytes outstanding,
+ * finalizing function is request_len_recv, checking the length. request_recv
+ * then processes the packet. The processing function then at some point has
+ * to call request_finished which schedules sending the response.
+ */
+
+static void winbind_client_request_read(struct tevent_req *req);
+static void winbind_client_activity(struct tevent_req *req);
+static void winbind_client_processed(struct tevent_req *req);
+
+/* Process a new connection by adding it to the client connection list */
+
+static void new_connection(int listen_sock, bool privileged)
+{
+ struct sockaddr_un sunaddr;
+ struct winbindd_cli_state *state;
+ struct tevent_req *req;
+ socklen_t len;
+ int sock;
+
+ /* Accept connection */
+
+ len = sizeof(sunaddr);
+
+ sock = accept(listen_sock, (struct sockaddr *)(void *)&sunaddr, &len);
+
+ if (sock == -1) {
+ if (errno != EINTR) {
+ D_ERR("Failed to accept socket: %s\n", strerror(errno));
+ }
+ return;
+ }
+ smb_set_close_on_exec(sock);
+
+ D_INFO("Accepted client socket %d\n", sock);
+
+ /* Create new connection structure */
+
+ if ((state = talloc_zero(NULL, struct winbindd_cli_state)) == NULL) {
+ close(sock);
+ return;
+ }
+
+ state->sock = sock;
+
+ state->out_queue = tevent_queue_create(state, "winbind client reply");
+ if (state->out_queue == NULL) {
+ close(sock);
+ TALLOC_FREE(state);
+ return;
+ }
+
+ state->privileged = privileged;
+
+ req = wb_req_read_send(state, global_event_context(), state->sock,
+ WINBINDD_MAX_EXTRA_DATA);
+ if (req == NULL) {
+ TALLOC_FREE(state);
+ close(sock);
+ return;
+ }
+ tevent_req_set_callback(req, winbind_client_request_read, state);
+ state->io_req = req;
+
+ /* Add to connection list */
+
+ winbindd_add_client(state);
+}
+
+static void winbind_client_request_read(struct tevent_req *req)
+{
+ struct winbindd_cli_state *state = tevent_req_callback_data(
+ req, struct winbindd_cli_state);
+ ssize_t ret;
+ int err;
+
+ state->io_req = NULL;
+
+ ret = wb_req_read_recv(req, state, &state->request, &err);
+ TALLOC_FREE(req);
+ if (ret == -1) {
+ if (err == EPIPE) {
+ DEBUG(6, ("closing socket %d, client exited\n",
+ state->sock));
+ } else {
+ DEBUG(2, ("Could not read client request from fd %d: "
+ "%s\n", state->sock, strerror(err)));
+ }
+ close(state->sock);
+ state->sock = -1;
+ remove_client(state);
+ return;
+ }
+
+ req = wait_for_read_send(state, global_event_context(), state->sock,
+ true);
+ if (req == NULL) {
+ DEBUG(0, ("winbind_client_request_read[%d:%s]:"
+ " wait_for_read_send failed - removing client\n",
+ (int)state->pid, state->cmd_name));
+ remove_client(state);
+ return;
+ }
+ tevent_req_set_callback(req, winbind_client_activity, state);
+ state->io_req = req;
+
+ req = process_request_send(state, global_event_context(), state);
+ if (req == NULL) {
+ DBG_ERR("process_request_send failed\n");
+ remove_client(state);
+ return;
+ }
+ tevent_req_set_callback(req, winbind_client_processed, state);
+}
+
+static void winbind_client_activity(struct tevent_req *req)
+{
+ struct winbindd_cli_state *state =
+ tevent_req_callback_data(req, struct winbindd_cli_state);
+ int err;
+ bool has_data;
+
+ has_data = wait_for_read_recv(req, &err);
+
+ if (has_data) {
+ DEBUG(0, ("winbind_client_activity[%d:%s]:"
+ "unexpected data from client - removing client\n",
+ (int)state->pid, state->cmd_name));
+ } else {
+ if (err == EPIPE) {
+ DEBUG(6, ("winbind_client_activity[%d:%s]: "
+ "client has closed connection - removing "
+ "client\n",
+ (int)state->pid, state->cmd_name));
+ } else {
+ DEBUG(2, ("winbind_client_activity[%d:%s]: "
+ "client socket error (%s) - removing "
+ "client\n",
+ (int)state->pid, state->cmd_name,
+ strerror(err)));
+ }
+ }
+
+ remove_client(state);
+}
+
+static void winbind_client_processed(struct tevent_req *req)
+{
+ struct winbindd_cli_state *cli_state = tevent_req_callback_data(
+ req, struct winbindd_cli_state);
+ struct tevent_req_profile *profile = NULL;
+ struct timeval start, stop, diff;
+ int threshold;
+ NTSTATUS status;
+
+ status = process_request_recv(req, cli_state, &profile);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("process_request failed: %s\n", nt_errstr(status));
+ remove_client(cli_state);
+ return;
+ }
+
+ tevent_req_profile_get_start(profile, NULL, &start);
+ tevent_req_profile_get_stop(profile, NULL, &stop);
+ diff = tevent_timeval_until(&start, &stop);
+
+ threshold = lp_parm_int(-1, "winbind", "request profile threshold", 60);
+
+ if (diff.tv_sec >= threshold) {
+ int depth;
+ char *str;
+
+ depth = lp_parm_int(
+ -1,
+ "winbind",
+ "request profile depth",
+ INT_MAX);
+
+ DBG_ERR("request took %u.%.6u seconds\n",
+ (unsigned)diff.tv_sec, (unsigned)diff.tv_usec);
+
+ str = tevent_req_profile_string(
+ talloc_tos(), profile, 0, depth);
+ if (str != NULL) {
+ /* No "\n", already contained in "str" */
+ DEBUGADD(0, ("%s", str));
+ }
+ TALLOC_FREE(str);
+ }
+
+ TALLOC_FREE(profile);
+
+ req = wb_req_read_send(
+ cli_state,
+ global_event_context(),
+ cli_state->sock,
+ WINBINDD_MAX_EXTRA_DATA);
+ if (req == NULL) {
+ remove_client(cli_state);
+ return;
+ }
+ tevent_req_set_callback(req, winbind_client_request_read, cli_state);
+ cli_state->io_req = req;
+}
+
+/* Remove a client connection from client connection list */
+
+static void remove_client(struct winbindd_cli_state *state)
+{
+ /* It's a dead client - hold a funeral */
+
+ if (state == NULL) {
+ return;
+ }
+
+ /*
+ * We need to remove a pending wb_req_read_*
+ * or wb_resp_write_* request before closing the
+ * socket.
+ *
+ * This is important as they might have used tevent_add_fd() and we
+ * use the epoll * backend on linux. So we must remove the tevent_fd
+ * before closing the fd.
+ *
+ * Otherwise we might hit a race with close_conns_after_fork() (via
+ * winbindd_reinit_after_fork()) where a file descriptor
+ * is still open in a child, which means it's still active in
+ * the parents epoll queue, but the related tevent_fd is already
+ * already gone in the parent.
+ *
+ * See bug #11141.
+ */
+ TALLOC_FREE(state->io_req);
+
+ if (state->sock != -1) {
+ char c = 0;
+ int nwritten;
+
+ /* tell client, we are closing ... */
+ nwritten = write(state->sock, &c, sizeof(c));
+ if (nwritten == -1) {
+ DEBUG(2, ("final write to client failed: %s\n",
+ strerror(errno)));
+ }
+
+ /* Close socket */
+
+ close(state->sock);
+ state->sock = -1;
+ }
+
+ TALLOC_FREE(state->mem_ctx);
+
+ /* Remove from list and free */
+
+ winbindd_remove_client(state);
+ TALLOC_FREE(state);
+}
+
+/* Is a client idle? */
+
+static bool client_is_idle(struct winbindd_cli_state *state) {
+ return (state->request == NULL &&
+ state->response == NULL &&
+ !state->pwent_state && !state->grent_state);
+}
+
+/* Shutdown client connection which has been idle for the longest time */
+
+static bool remove_idle_client(void)
+{
+ struct winbindd_cli_state *state, *remove_state = NULL;
+ int nidle = 0;
+
+ for (state = winbindd_client_list(); state; state = state->next) {
+ if (client_is_idle(state)) {
+ nidle++;
+ /* list is sorted by access time */
+ remove_state = state;
+ }
+ }
+
+ if (remove_state) {
+ DEBUG(5,("Found %d idle client connections, shutting down sock %d, pid %u\n",
+ nidle, remove_state->sock, (unsigned int)remove_state->pid));
+ remove_client(remove_state);
+ return True;
+ }
+
+ return False;
+}
+
+/*
+ * Terminate all clients whose requests have taken longer than
+ * "winbind request timeout" seconds to process, or have been
+ * idle for more than "winbind request timeout" seconds.
+ */
+
+static void remove_timed_out_clients(void)
+{
+ struct winbindd_cli_state *state, *prev = NULL;
+ time_t curr_time = time(NULL);
+ int timeout_val = lp_winbind_request_timeout();
+
+ for (state = winbindd_client_list_tail(); state; state = prev) {
+ time_t expiry_time;
+
+ prev = winbindd_client_list_prev(state);
+ expiry_time = state->last_access + timeout_val;
+
+ if (curr_time <= expiry_time) {
+ /* list is sorted, previous clients in
+ list are newer */
+ break;
+ }
+
+ if (client_is_idle(state)) {
+ DEBUG(5,("Idle client timed out, "
+ "shutting down sock %d, pid %u\n",
+ state->sock,
+ (unsigned int)state->pid));
+ } else {
+ DEBUG(5,("Client request timed out, "
+ "shutting down sock %d, pid %u\n",
+ state->sock,
+ (unsigned int)state->pid));
+ }
+
+ remove_client(state);
+ }
+}
+
+static void winbindd_scrub_clients_handler(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ remove_timed_out_clients();
+ if (tevent_add_timer(ev, ev,
+ timeval_current_ofs(SCRUB_CLIENTS_INTERVAL, 0),
+ winbindd_scrub_clients_handler, NULL) == NULL) {
+ DEBUG(0, ("winbindd: failed to reschedule client scrubber\n"));
+ exit(1);
+ }
+}
+
+struct winbindd_listen_state {
+ bool privileged;
+ int fd;
+};
+
+static void winbindd_listen_fde_handler(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct winbindd_listen_state *s = talloc_get_type_abort(private_data,
+ struct winbindd_listen_state);
+
+ while (winbindd_num_clients() > lp_winbind_max_clients() - 1) {
+ DEBUG(5,("winbindd: Exceeding %d client "
+ "connections, removing idle "
+ "connection.\n", lp_winbind_max_clients()));
+ if (!remove_idle_client()) {
+ DEBUG(0,("winbindd: Exceeding %d "
+ "client connections, no idle "
+ "connection found\n",
+ lp_winbind_max_clients()));
+ break;
+ }
+ }
+ remove_timed_out_clients();
+ new_connection(s->fd, s->privileged);
+}
+
+/*
+ * Winbindd socket accessor functions
+ */
+
+static bool winbindd_setup_listeners(void)
+{
+ struct winbindd_listen_state *pub_state = NULL;
+ struct winbindd_listen_state *priv_state = NULL;
+ struct tevent_fd *fde;
+ int rc;
+ char *socket_path;
+
+ pub_state = talloc(global_event_context(),
+ struct winbindd_listen_state);
+ if (!pub_state) {
+ goto failed;
+ }
+
+ pub_state->privileged = false;
+ pub_state->fd = create_pipe_sock(
+ lp_winbindd_socket_directory(), WINBINDD_SOCKET_NAME, 0755);
+ if (pub_state->fd == -1) {
+ goto failed;
+ }
+ rc = listen(pub_state->fd, 5);
+ if (rc < 0) {
+ goto failed;
+ }
+
+ fde = tevent_add_fd(global_event_context(), pub_state, pub_state->fd,
+ TEVENT_FD_READ, winbindd_listen_fde_handler,
+ pub_state);
+ if (fde == NULL) {
+ close(pub_state->fd);
+ goto failed;
+ }
+ tevent_fd_set_auto_close(fde);
+
+ priv_state = talloc(global_event_context(),
+ struct winbindd_listen_state);
+ if (!priv_state) {
+ goto failed;
+ }
+
+ socket_path = get_winbind_priv_pipe_dir();
+ if (socket_path == NULL) {
+ goto failed;
+ }
+
+ priv_state->privileged = true;
+ priv_state->fd = create_pipe_sock(
+ socket_path, WINBINDD_SOCKET_NAME, 0750);
+ TALLOC_FREE(socket_path);
+ if (priv_state->fd == -1) {
+ goto failed;
+ }
+ rc = listen(priv_state->fd, 5);
+ if (rc < 0) {
+ goto failed;
+ }
+
+ fde = tevent_add_fd(global_event_context(), priv_state,
+ priv_state->fd, TEVENT_FD_READ,
+ winbindd_listen_fde_handler, priv_state);
+ if (fde == NULL) {
+ close(priv_state->fd);
+ goto failed;
+ }
+ tevent_fd_set_auto_close(fde);
+
+ winbindd_scrub_clients_handler(global_event_context(), NULL,
+ timeval_current(), NULL);
+ return true;
+failed:
+ TALLOC_FREE(pub_state);
+ TALLOC_FREE(priv_state);
+ return false;
+}
+
+static void winbindd_register_handlers(struct messaging_context *msg_ctx,
+ bool foreground)
+{
+ bool scan_trusts = true;
+ NTSTATUS status;
+ struct tevent_timer *te = NULL;
+
+ /* Setup signal handlers */
+
+ if (!winbindd_setup_sig_term_handler(true))
+ exit(1);
+ if (!winbindd_setup_stdin_handler(true, foreground))
+ exit(1);
+ if (!winbindd_setup_sig_hup_handler(NULL))
+ exit(1);
+ if (!winbindd_setup_sig_chld_handler())
+ exit(1);
+ if (!winbindd_setup_sig_usr2_handler())
+ exit(1);
+
+ CatchSignal(SIGPIPE, SIG_IGN); /* Ignore sigpipe */
+
+ /*
+ * Ensure all cache and idmap caches are consistent
+ * and initialized before we startup.
+ */
+ if (!winbindd_cache_validate_and_initialize()) {
+ exit(1);
+ }
+
+ /* React on 'smbcontrol winbindd reload-config' in the same way
+ as to SIGHUP signal */
+ messaging_register(msg_ctx, NULL,
+ MSG_SMB_CONF_UPDATED,
+ winbindd_msg_reload_services_parent);
+ messaging_register(msg_ctx, NULL,
+ MSG_SHUTDOWN, msg_shutdown);
+
+ /* Handle online/offline messages. */
+ messaging_register(msg_ctx, NULL,
+ MSG_WINBIND_OFFLINE, winbind_msg_offline);
+ messaging_register(msg_ctx, NULL,
+ MSG_WINBIND_ONLINE, winbind_msg_online);
+ messaging_register(msg_ctx, NULL,
+ MSG_WINBIND_ONLINESTATUS, winbind_msg_onlinestatus);
+
+ /* Handle domain online/offline messages for domains */
+ messaging_register(global_messaging_context(), NULL,
+ MSG_WINBIND_DOMAIN_OFFLINE, winbind_msg_domain_offline);
+ messaging_register(global_messaging_context(), NULL,
+ MSG_WINBIND_DOMAIN_ONLINE, winbind_msg_domain_online);
+
+ messaging_register(msg_ctx, NULL,
+ MSG_WINBIND_VALIDATE_CACHE,
+ winbind_msg_validate_cache);
+
+ messaging_register(msg_ctx, NULL,
+ MSG_WINBIND_DUMP_DOMAIN_LIST,
+ winbind_msg_dump_domain_list);
+
+ messaging_register(msg_ctx, NULL,
+ MSG_WINBIND_IP_DROPPED,
+ winbind_msg_ip_dropped_parent);
+
+ /* Register handler for MSG_DEBUG. */
+ messaging_register(msg_ctx, NULL,
+ MSG_DEBUG,
+ winbind_msg_debug);
+
+ messaging_register(msg_ctx, NULL,
+ MSG_WINBIND_DISCONNECT_DC,
+ winbind_disconnect_dc_parent);
+
+ netsamlogon_cache_init(); /* Non-critical */
+
+ /* clear the cached list of trusted domains */
+
+ wcache_tdc_clear();
+
+ if (!init_domain_list()) {
+ DEBUG(0,("unable to initialize domain list\n"));
+ exit(1);
+ }
+
+ status = init_idmap_child(global_event_context());
+ if (NT_STATUS_IS_ERR(status)) {
+ DBG_ERR("Unable to start idmap child: %s\n", nt_errstr(status));
+ exit(1);
+ }
+
+ status = init_locator_child(global_event_context());
+ if (NT_STATUS_IS_ERR(status)) {
+ DBG_ERR("Unable to start locator child: %s\n", nt_errstr(status));
+ exit(1);
+ }
+
+ smb_nscd_flush_user_cache();
+ smb_nscd_flush_group_cache();
+
+ if (!lp_winbind_scan_trusted_domains()) {
+ scan_trusts = false;
+ }
+
+ if (!lp_allow_trusted_domains()) {
+ scan_trusts = false;
+ }
+
+ if (IS_DC) {
+ scan_trusts = false;
+ }
+
+ if (scan_trusts) {
+ if (tevent_add_timer(global_event_context(), NULL, timeval_zero(),
+ rescan_trusted_domains, NULL) == NULL) {
+ DEBUG(0, ("Could not trigger rescan_trusted_domains()\n"));
+ exit(1);
+ }
+ }
+
+ te = tevent_add_timer(global_event_context(),
+ NULL,
+ timeval_zero(),
+ winbindd_ping_offline_domains,
+ NULL);
+ if (te == NULL) {
+ DBG_ERR("Failed to schedule winbindd_ping_offline_domains()\n");
+ exit(1);
+ }
+
+ status = wb_irpc_register();
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not register IRPC handlers\n"));
+ exit(1);
+ }
+}
+
+struct winbindd_addrchanged_state {
+ struct addrchange_context *ctx;
+ struct tevent_context *ev;
+ struct messaging_context *msg_ctx;
+};
+
+static void winbindd_addr_changed(struct tevent_req *req);
+
+static void winbindd_init_addrchange(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct messaging_context *msg_ctx)
+{
+ struct winbindd_addrchanged_state *state;
+ struct tevent_req *req;
+ NTSTATUS status;
+
+ state = talloc(mem_ctx, struct winbindd_addrchanged_state);
+ if (state == NULL) {
+ DEBUG(10, ("talloc failed\n"));
+ return;
+ }
+ state->ev = ev;
+ state->msg_ctx = msg_ctx;
+
+ status = addrchange_context_create(state, &state->ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("addrchange_context_create failed: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(state);
+ return;
+ }
+ req = addrchange_send(state, ev, state->ctx);
+ if (req == NULL) {
+ DEBUG(0, ("addrchange_send failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ tevent_req_set_callback(req, winbindd_addr_changed, state);
+}
+
+static void winbindd_addr_changed(struct tevent_req *req)
+{
+ struct winbindd_addrchanged_state *state = tevent_req_callback_data(
+ req, struct winbindd_addrchanged_state);
+ enum addrchange_type type;
+ struct sockaddr_storage addr;
+ NTSTATUS status;
+
+ status = addrchange_recv(req, &type, &addr);
+ TALLOC_FREE(req);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("addrchange_recv failed: %s, stop listening\n",
+ nt_errstr(status)));
+ TALLOC_FREE(state);
+ return;
+ }
+ if (type == ADDRCHANGE_DEL) {
+ char addrstr[INET6_ADDRSTRLEN];
+ DATA_BLOB blob;
+
+ print_sockaddr(addrstr, sizeof(addrstr), &addr);
+
+ DEBUG(3, ("winbindd: kernel (AF_NETLINK) dropped ip %s\n",
+ addrstr));
+
+ blob = data_blob_const(addrstr, strlen(addrstr)+1);
+
+ status = messaging_send(state->msg_ctx,
+ messaging_server_id(state->msg_ctx),
+ MSG_WINBIND_IP_DROPPED, &blob);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("messaging_send failed: %s - ignoring\n",
+ nt_errstr(status)));
+ }
+ }
+ req = addrchange_send(state, state->ev, state->ctx);
+ if (req == NULL) {
+ DEBUG(0, ("addrchange_send failed\n"));
+ TALLOC_FREE(state);
+ return;
+ }
+ tevent_req_set_callback(req, winbindd_addr_changed, state);
+}
+
+/* Main function */
+
+int main(int argc, const char **argv)
+{
+ static bool log_stdout = False;
+ struct samba_cmdline_daemon_cfg *cmdline_daemon_cfg = NULL;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ {
+ .longName = "no-caching",
+ .shortName = 'n',
+ .argInfo = POPT_ARG_NONE,
+ .arg = NULL,
+ .val = 'n',
+ .descrip = "Disable caching",
+ },
+ POPT_COMMON_SAMBA
+ POPT_COMMON_DAEMON
+ POPT_COMMON_VERSION
+ POPT_TABLEEND
+ };
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ poptContext pc;
+ int opt;
+ TALLOC_CTX *frame;
+ NTSTATUS status;
+ bool ok;
+ const struct dcesrv_endpoint_server *ep_server = NULL;
+ struct dcesrv_context *dce_ctx = NULL;
+ size_t winbindd_socket_dir_len = 0;
+ char *winbindd_priv_socket_dir = NULL;
+ size_t winbindd_priv_socket_dir_len = 0;
+
+ setproctitle_init(argc, discard_const(argv), environ);
+
+ /*
+ * Do this before any other talloc operation
+ */
+ talloc_enable_null_tracking();
+ frame = talloc_stackframe();
+
+ /*
+ * We want total control over the permissions on created files,
+ * so set our umask to 0.
+ */
+ umask(0);
+
+ smb_init_locale();
+
+ /* glibc (?) likes to print "User defined signal 1" and exit if a
+ SIGUSR[12] is received before a handler is installed */
+
+ CatchSignal(SIGUSR1, SIG_IGN);
+ CatchSignal(SIGUSR2, SIG_IGN);
+
+ ok = samba_cmdline_init(frame,
+ SAMBA_CMDLINE_CONFIG_SERVER,
+ true /* require_smbconf */);
+ if (!ok) {
+ DBG_ERR("Failed to setup cmdline parser\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ cmdline_daemon_cfg = samba_cmdline_get_daemon_cfg();
+
+ pc = samba_popt_get_context(getprogname(), argc, argv, long_options, 0);
+ if (pc == NULL) {
+ DBG_ERR("Failed to setup popt parser!\n");
+ TALLOC_FREE(frame);
+ exit(1);
+ }
+
+ while ((opt = poptGetNextOpt(pc)) != -1) {
+ switch (opt) {
+ case 'n':
+ winbindd_set_use_cache(false);
+ break;
+ default:
+ d_fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+ }
+
+ /* Set environment variable so we don't recursively call ourselves.
+ This may also be useful interactively. */
+ if ( !winbind_off() ) {
+ DEBUG(0,("Failed to disable recursive winbindd calls. Exiting.\n"));
+ exit(1);
+ }
+
+ /* Initialise for running in non-root mode */
+ sec_init();
+
+ set_remote_machine_name("winbindd", False);
+
+ dump_core_setup("winbindd", lp_logfile(talloc_tos(), lp_sub));
+ if (cmdline_daemon_cfg->daemon && cmdline_daemon_cfg->interactive) {
+ d_fprintf(stderr,"\nERROR: "
+ "Option -i|--interactive is not allowed together with -D|--daemon\n\n");
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+
+ log_stdout = (debug_get_log_type() == DEBUG_STDOUT);
+ if (cmdline_daemon_cfg->interactive) {
+ /*
+ * libcmdline POPT_DAEMON callback sets "fork" to false if "-i"
+ * for interactive is passed on the commandline. Set it back to
+ * true. TODO: check if this is correct, smbd and nmbd don't do
+ * this.
+ */
+ cmdline_daemon_cfg->fork = true;
+ log_stdout = true;
+ }
+
+ if (log_stdout && cmdline_daemon_cfg->fork) {
+ d_fprintf(stderr, "\nERROR: "
+ "Can't log to stdout (-S) unless daemon is in "
+ "foreground (-F) or interactive (-i)\n\n");
+ poptPrintUsage(pc, stderr, 0);
+ exit(1);
+ }
+
+ poptFreeContext(pc);
+
+ reopen_logs();
+
+ DBG_STARTUP_NOTICE("winbindd version %s started.\n%s\n",
+ samba_version_string(),
+ samba_copyright_string());
+
+ /* After parsing the configuration file we setup the core path one more time
+ * as the log file might have been set in the configuration and cores's
+ * path is by default basename(lp_logfile()).
+ */
+ dump_core_setup("winbindd", lp_logfile(talloc_tos(), lp_sub));
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ if (!lp_parm_bool(-1, "server role check", "inhibit", false)) {
+ DBG_ERR("server role = 'active directory domain controller' not compatible with running the winbindd binary. \n");
+ DEBUGADD(0, ("You should start 'samba' instead, and it will control starting the internal AD DC winbindd implementation, which is not the same as this one\n"));
+ exit(1);
+ }
+ /* Main 'samba' daemon will notify */
+ daemon_sd_notifications(false);
+ }
+
+ if (lp_security() == SEC_ADS) {
+ const char *realm = lp_realm();
+ const char *workgroup = lp_workgroup();
+
+ if (workgroup == NULL || strlen(workgroup) == 0) {
+ DBG_ERR("For 'secuirity = ADS' mode, the 'workgroup' "
+ "parameter is required to be set!\n");
+ exit(1);
+ }
+
+ if (realm == NULL || strlen(realm) == 0) {
+ DBG_ERR("For 'secuirity = ADS' mode, the 'realm' "
+ "parameter is required to be set!\n");
+ exit(1);
+ }
+ }
+
+ winbindd_socket_dir_len = strlen(lp_winbindd_socket_directory());
+ if (winbindd_socket_dir_len > 0) {
+ size_t winbindd_socket_len =
+ winbindd_socket_dir_len + 1 +
+ strlen(WINBINDD_SOCKET_NAME);
+ struct sockaddr_un un = {
+ .sun_family = AF_UNIX,
+ };
+ size_t sun_path_len = sizeof(un.sun_path);
+
+ if (winbindd_socket_len >= sun_path_len) {
+ DBG_ERR("The winbind socket path [%s/%s] is too long "
+ "(%zu >= %zu)\n",
+ lp_winbindd_socket_directory(),
+ WINBINDD_SOCKET_NAME,
+ winbindd_socket_len,
+ sun_path_len);
+ exit(1);
+ }
+ } else {
+ DBG_ERR("'winbindd_socket_directory' parameter is empty\n");
+ exit(1);
+ }
+
+ winbindd_priv_socket_dir = get_winbind_priv_pipe_dir();
+ winbindd_priv_socket_dir_len = strlen(winbindd_priv_socket_dir);
+ if (winbindd_priv_socket_dir_len > 0) {
+ size_t winbindd_priv_socket_len =
+ winbindd_priv_socket_dir_len + 1 +
+ strlen(WINBINDD_SOCKET_NAME);
+ struct sockaddr_un un = {
+ .sun_family = AF_UNIX,
+ };
+ size_t sun_path_len = sizeof(un.sun_path);
+
+ if (winbindd_priv_socket_len >= sun_path_len) {
+ DBG_ERR("The winbind privileged socket path [%s/%s] is too long "
+ "(%zu >= %zu)\n",
+ winbindd_priv_socket_dir,
+ WINBINDD_SOCKET_NAME,
+ winbindd_priv_socket_len,
+ sun_path_len);
+ exit(1);
+ }
+ } else {
+ DBG_ERR("'winbindd_priv_socket_directory' parameter is empty\n");
+ exit(1);
+ }
+ TALLOC_FREE(winbindd_priv_socket_dir);
+
+ if (!cluster_probe_ok()) {
+ exit(1);
+ }
+
+ /* Initialise messaging system */
+
+ if (global_messaging_context() == NULL) {
+ exit(1);
+ }
+
+ if (!winbindd_reload_services_file(NULL)) {
+ DEBUG(0, ("error opening config file\n"));
+ exit(1);
+ }
+
+ {
+ size_t i;
+ const char *idmap_backend;
+ const char *invalid_backends[] = {
+ "ad", "rfc2307", "rid",
+ };
+
+ idmap_backend = lp_idmap_default_backend();
+ for (i = 0; i < ARRAY_SIZE(invalid_backends); i++) {
+ ok = strequal(idmap_backend, invalid_backends[i]);
+ if (ok) {
+ DBG_ERR("FATAL: Invalid idmap backend %s "
+ "configured as the default backend!\n",
+ idmap_backend);
+ exit(1);
+ }
+ }
+ }
+
+ ok = directory_create_or_exist(lp_lock_directory(), 0755);
+ if (!ok) {
+ DEBUG(0, ("Failed to create directory %s for lock files - %s\n",
+ lp_lock_directory(), strerror(errno)));
+ exit(1);
+ }
+
+ ok = directory_create_or_exist(lp_pid_directory(), 0755);
+ if (!ok) {
+ DEBUG(0, ("Failed to create directory %s for pid files - %s\n",
+ lp_pid_directory(), strerror(errno)));
+ exit(1);
+ }
+
+ load_interfaces();
+
+ if (!secrets_init()) {
+
+ DEBUG(0,("Could not initialize domain trust account secrets. Giving up\n"));
+ return False;
+ }
+
+ status = rpccli_pre_open_netlogon_creds();
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("rpccli_pre_open_netlogon_creds() - %s\n",
+ nt_errstr(status)));
+ exit(1);
+ }
+
+ /* Unblock all signals we are interested in as they may have been
+ blocked by the parent process. */
+
+ BlockSignals(False, SIGINT);
+ BlockSignals(False, SIGQUIT);
+ BlockSignals(False, SIGTERM);
+ BlockSignals(False, SIGUSR1);
+ BlockSignals(False, SIGUSR2);
+ BlockSignals(False, SIGHUP);
+ BlockSignals(False, SIGCHLD);
+
+ if (!interactive) {
+ become_daemon(cmdline_daemon_cfg->fork,
+ cmdline_daemon_cfg->no_process_group,
+ log_stdout);
+ } else {
+ daemon_status("winbindd", "Starting process ...");
+ }
+
+ pidfile_create(lp_pid_directory(), "winbindd");
+
+#ifdef HAVE_SETPGID
+ /*
+ * If we're interactive we want to set our own process group for
+ * signal management.
+ */
+ if (cmdline_daemon_cfg->interactive &&
+ !cmdline_daemon_cfg->no_process_group)
+ {
+ setpgid( (pid_t)0, (pid_t)0);
+ }
+#endif
+
+ TimeInit();
+
+ /* Don't use winbindd_reinit_after_fork here as
+ * we're just starting up and haven't created any
+ * winbindd-specific resources we must free yet. JRA.
+ */
+
+ status = reinit_after_fork(global_messaging_context(),
+ global_event_context(),
+ false);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Winbindd reinit_after_fork() failed", map_errno_from_nt_status(status));
+ }
+
+ if (lp_winbind_debug_traceid()) {
+ winbind_debug_traceid_setup(global_event_context());
+ winbind_debug_call_depth_setup(debug_call_depth_addr());
+ tevent_thread_call_depth_set_callback(
+ debuglevel_get() > 1 ? winbind_call_flow : NULL,
+ NULL);
+ }
+ ok = initialize_password_db(true, global_event_context());
+ if (!ok) {
+ exit_daemon("Failed to initialize passdb backend! "
+ "Check the 'passdb backend' variable in your "
+ "smb.conf file.", EINVAL);
+ }
+
+ /*
+ * Do not initialize the parent-child-pipe before becoming
+ * a daemon: this is used to detect a died parent in the child
+ * process.
+ */
+ status = init_before_fork();
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon(nt_errstr(status), map_errno_from_nt_status(status));
+ }
+
+ winbindd_register_handlers(global_messaging_context(),
+ !cmdline_daemon_cfg->fork);
+
+ if (!messaging_parent_dgm_cleanup_init(global_messaging_context())) {
+ exit(1);
+ }
+
+ status = init_system_session_info(NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit_daemon("Winbindd failed to setup system user info", map_errno_from_nt_status(status));
+ }
+
+ DBG_INFO("Registering DCE/RPC endpoint servers\n");
+
+ ep_server = winbind_get_ep_server();
+ if (ep_server == NULL) {
+ DBG_ERR("Failed to get 'winbind' endpoint server\n");
+ exit(1);
+ }
+ status = dcerpc_register_ep_server(ep_server);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to register 'winbind' endpoint "
+ "server: %s\n", nt_errstr(status));
+ exit(1);
+ }
+
+ dce_ctx = global_dcesrv_context();
+
+ DBG_INFO("Initializing DCE/RPC registered endpoint servers\n");
+
+ /* Init all registered ep servers */
+ status = dcesrv_init_registered_ep_servers(dce_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to init DCE/RPC endpoint servers: %s\n",
+ nt_errstr(status));
+ exit(1);
+ }
+
+ winbindd_init_addrchange(NULL, global_event_context(),
+ global_messaging_context());
+
+ /* setup listen sockets */
+
+ if (!winbindd_setup_listeners()) {
+ exit_daemon("Winbindd failed to setup listeners", EPIPE);
+ }
+
+ irpc_add_name(winbind_imessaging_context(), "winbind_server");
+
+ TALLOC_FREE(frame);
+
+ if (!interactive) {
+ daemon_ready("winbindd");
+ }
+
+ gpupdate_init();
+
+ /* Loop waiting for requests */
+ while (1) {
+ frame = talloc_stackframe();
+
+ if (tevent_loop_once(global_event_context()) == -1) {
+ DEBUG(1, ("tevent_loop_once() failed: %s\n",
+ strerror(errno)));
+ return 1;
+ }
+
+ TALLOC_FREE(frame);
+ }
+
+ return 0;
+}
diff --git a/source3/winbindd/winbindd.h b/source3/winbindd/winbindd.h
new file mode 100644
index 0000000..53430a6
--- /dev/null
+++ b/source3/winbindd/winbindd.h
@@ -0,0 +1,370 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _WINBINDD_H
+#define _WINBINDD_H
+
+#include "nsswitch/winbind_struct_protocol.h"
+#include "nsswitch/libwbclient/wbclient.h"
+#include "librpc/gen_ndr/dcerpc.h"
+#include "librpc/gen_ndr/winbind.h"
+
+#include "../lib/util/tevent_ntstatus.h"
+
+#ifdef HAVE_LIBNSCD
+#include <libnscd.h>
+#endif
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define WB_REPLACE_CHAR '_'
+
+struct winbind_internal_pipes;
+struct ads_struct;
+
+struct winbindd_cli_state {
+ struct winbindd_cli_state *prev, *next; /* Linked list pointers */
+ int sock; /* Open socket from client */
+ pid_t pid; /* pid of client */
+ char client_name[32]; /* The process name of the client */
+ time_t last_access; /* Time of last access (read or write) */
+ bool privileged; /* Is the client 'privileged' */
+
+ TALLOC_CTX *mem_ctx; /* memory per request */
+ const char *cmd_name;
+ NTSTATUS (*recv_fn)(struct tevent_req *req,
+ struct winbindd_response *presp);
+ struct winbindd_request *request; /* Request from client */
+ struct tevent_queue *out_queue;
+ struct winbindd_response *response; /* Response to client */
+ struct tevent_req *io_req; /* wb_req_read_* or wb_resp_write_* */
+
+ struct getpwent_state *pwent_state; /* State for getpwent() */
+ struct getgrent_state *grent_state; /* State for getgrent() */
+};
+
+struct getpwent_state {
+ struct winbindd_domain *domain;
+ uint32_t next_user;
+ struct wbint_RidArray rids;
+};
+
+struct getgrent_state {
+ struct winbindd_domain *domain;
+ uint32_t next_group;
+ uint32_t num_groups;
+ struct wbint_Principal *groups;
+};
+
+/* Our connection to the DC */
+
+struct winbindd_cm_conn {
+ struct cli_state *cli;
+
+ enum dcerpc_AuthLevel auth_level;
+
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle sam_connect_handle, sam_domain_handle;
+
+ struct rpc_pipe_client *lsa_pipe;
+ struct rpc_pipe_client *lsa_pipe_tcp;
+ struct policy_handle lsa_policy;
+
+ struct rpc_pipe_client *netlogon_pipe;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx;
+ bool netlogon_force_reauth;
+};
+
+/* Async child */
+
+struct winbindd_domain;
+
+struct winbindd_child {
+ pid_t pid;
+ struct winbindd_domain *domain;
+ char *logfilename;
+
+ int sock;
+ struct tevent_fd *monitor_fde; /* Watch for dead children/sockets */
+ struct tevent_queue *queue;
+ struct dcerpc_binding_handle *binding_handle;
+
+ struct tevent_timer *lockout_policy_event;
+ struct tevent_timer *machine_password_change_event;
+};
+
+/* Structures to hold per domain information */
+
+struct winbindd_domain {
+ char *name; /* Domain name (NetBIOS) */
+ char *alt_name; /* alt Domain name, if any (FQDN for ADS) */
+ char *forest_name; /* Name of the AD forest we're in */
+ struct dom_sid sid; /* SID for this domain */
+ enum netr_SchannelType secure_channel_type;
+ uint32_t domain_flags; /* Domain flags from netlogon.h */
+ uint32_t domain_type; /* Domain type from netlogon.h */
+ uint32_t domain_trust_attribs; /* Trust attribs from netlogon.h */
+ struct winbindd_domain *routing_domain;
+ bool initialized; /* Did we already ask for the domain mode? */
+ bool native_mode; /* is this a win2k domain in native mode ? */
+ bool active_directory; /* is this a win2k active directory ? */
+ bool primary; /* is this our primary domain ? */
+ bool internal; /* BUILTIN and member SAM */
+ bool rodc; /* Are we an RODC for this AD domain? (do some operations locally) */
+ bool online; /* is this domain available ? */
+ time_t startup_time; /* When we set "startup" true. monotonic clock */
+ bool startup; /* are we in the first 30 seconds after startup_time ? */
+
+ bool can_do_ncacn_ip_tcp;
+
+ /*
+ * Lookup methods for this domain (LDAP or RPC). The backend
+ * methods are used by the cache layer.
+ */
+ struct winbindd_methods *backend;
+
+ struct {
+ struct winbind_internal_pipes *samr_pipes;
+ struct ads_struct *ads_conn;
+ } backend_data;
+
+ /* A working DC */
+ bool force_dc;
+ char *dcname;
+ const char *ping_dcname;
+ struct sockaddr_storage dcaddr;
+
+ /* Sequence number stuff */
+
+ time_t last_seq_check;
+ uint32_t sequence_number;
+ NTSTATUS last_status;
+
+ /* The smb connection */
+
+ struct winbindd_cm_conn conn;
+
+ /* The child pid we're talking to */
+
+ struct winbindd_child *children;
+
+ struct tevent_queue *queue;
+ struct dcerpc_binding_handle *binding_handle;
+
+ struct tevent_req *check_online_event;
+
+ /* Linked list info */
+
+ struct winbindd_domain *prev, *next;
+};
+
+struct wb_parent_idmap_config_dom {
+ unsigned low_id;
+ unsigned high_id;
+ const char *name;
+ struct dom_sid sid;
+};
+
+struct wb_parent_idmap_config {
+ struct tevent_queue *queue;
+ uint32_t num_doms;
+ bool initialized;
+ struct wb_parent_idmap_config_dom *doms;
+};
+
+struct wb_acct_info {
+ const char *acct_name; /* account name */
+ const char *acct_desc; /* account name */
+ uint32_t rid; /* domain-relative RID */
+};
+
+/* per-domain methods. This is how LDAP vs RPC is selected
+ */
+struct winbindd_methods {
+ /* does this backend provide a consistent view of the data? (ie. is the primary group
+ always correct) */
+ bool consistent;
+
+ /* get a list of users, returning a wbint_userinfo for each one */
+ NTSTATUS (*query_user_list)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **rids);
+
+ /* get a list of domain groups */
+ NTSTATUS (*enum_dom_groups)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info);
+
+ /* get a list of domain local groups */
+ NTSTATUS (*enum_local_groups)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info);
+
+ /* convert one user or group name to a sid */
+ NTSTATUS (*name_to_sid)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ const char **pdom_name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type);
+
+ /* convert a sid to a user or group name */
+ NTSTATUS (*sid_to_name)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type);
+
+ NTSTATUS (*rids_to_names)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types);
+
+ /* lookup all groups that a user is a member of. The backend
+ can also choose to lookup by username or rid for this
+ function */
+ NTSTATUS (*lookup_usergroups)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *num_groups, struct dom_sid **user_gids);
+
+ /* Lookup all aliases that the sids delivered are member of. This is
+ * to implement 'domain local groups' correctly */
+ NTSTATUS (*lookup_useraliases)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *num_aliases,
+ uint32_t **alias_rids);
+
+ /* find all members of the group with the specified group_rid */
+ NTSTATUS (*lookup_groupmem)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem, char ***names,
+ uint32_t **name_types);
+
+ /* find all members of the alias with the specified alias_sid */
+ NTSTATUS (*lookup_aliasmem)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *alias_sid,
+ enum lsa_SidType type,
+ uint32_t *num_sids,
+ struct dom_sid **sid_mem);
+
+ /* return the lockout policy */
+ NTSTATUS (*lockout_policy)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *lockout_policy);
+
+ /* return the lockout policy */
+ NTSTATUS (*password_policy)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *password_policy);
+
+ /* enumerate trusted domains */
+ NTSTATUS (*trusted_domains)(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *trusts);
+};
+
+/* Filled out by IDMAP backends */
+struct winbindd_idmap_methods {
+ /* Called when backend is first loaded */
+ bool (*init)(void);
+
+ bool (*get_sid_from_uid)(uid_t uid, struct dom_sid *sid);
+ bool (*get_sid_from_gid)(gid_t gid, struct dom_sid *sid);
+
+ bool (*get_uid_from_sid)(struct dom_sid *sid, uid_t *uid);
+ bool (*get_gid_from_sid)(struct dom_sid *sid, gid_t *gid);
+
+ /* Called when backend is unloaded */
+ bool (*close)(void);
+ /* Called to dump backend status */
+ void (*status)(void);
+};
+
+/* Data structures for dealing with the trusted domain cache */
+
+struct winbindd_tdc_domain {
+ const char *domain_name;
+ const char *dns_name;
+ struct dom_sid sid;
+ uint32_t trust_flags;
+ uint32_t trust_attribs;
+ uint32_t trust_type;
+};
+
+struct WINBINDD_MEMORY_CREDS {
+ struct WINBINDD_MEMORY_CREDS *next, *prev;
+ const char *username; /* lookup key. */
+ uid_t uid;
+ int ref_count;
+ size_t len;
+ uint8_t *nt_hash; /* Base pointer for the following 2 */
+ uint8_t *lm_hash;
+ char *pass;
+};
+
+struct WINBINDD_CCACHE_ENTRY {
+ struct WINBINDD_CCACHE_ENTRY *next, *prev;
+ const char *principal_name;
+ const char *ccname;
+ const char *service;
+ const char *username;
+ const char *realm;
+ const char *canon_principal;
+ const char *canon_realm;
+ struct WINBINDD_MEMORY_CREDS *cred_ptr;
+ int ref_count;
+ uid_t uid;
+ time_t create_time;
+ time_t renew_until;
+ time_t refresh_time;
+ struct tevent_timer *event;
+};
+
+#include "winbindd/winbindd_proto.h"
+
+#define WINBINDD_ESTABLISH_LOOP 30
+#define WINBINDD_RESCAN_FREQ lp_winbind_cache_time()
+#define WINBINDD_PAM_AUTH_KRB5_RENEW_TIME 2592000 /* one month */
+#define DOM_SEQUENCE_NONE ((uint32_t)-1)
+
+#endif /* _WINBINDD_H */
diff --git a/source3/winbindd/winbindd_ads.c b/source3/winbindd/winbindd_ads.c
new file mode 100644
index 0000000..7e572e5
--- /dev/null
+++ b/source3/winbindd/winbindd_ads.c
@@ -0,0 +1,1604 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind ADS backend functions
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003
+ Copyright (C) Gerald (Jerry) Carter 2004
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "winbindd_ads.h"
+#include "libsmb/namequery.h"
+#include "rpc_client/rpc_client.h"
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "../libds/common/flags.h"
+#include "ads.h"
+#include "../libcli/ldap/ldap_ndr.h"
+#include "../libcli/security/security.h"
+#include "../libds/common/flag_mapping.h"
+#include "libsmb/samlogon_cache.h"
+#include "passdb.h"
+#include "auth/credentials/credentials.h"
+
+#ifdef HAVE_ADS
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+extern struct winbindd_methods reconnect_methods;
+extern struct winbindd_methods msrpc_methods;
+
+#define WINBIND_CCACHE_NAME "MEMORY:winbind_ccache"
+
+/**
+ * Check if cached connection can be reused. If the connection cannot
+ * be reused the ADS_STRUCT is freed and the pointer is set to NULL.
+ */
+static void ads_cached_connection_reuse(ADS_STRUCT **adsp)
+{
+
+ ADS_STRUCT *ads = *adsp;
+
+ if (ads != NULL) {
+ time_t expire;
+ time_t now = time(NULL);
+
+ expire = MIN(ads->auth.tgt_expire, ads->auth.tgs_expire);
+
+ DEBUG(7, ("Current tickets expire in %d seconds (at %d, time "
+ "is now %d)\n", (uint32_t)expire - (uint32_t)now,
+ (uint32_t) expire, (uint32_t) now));
+
+ if ( ads->config.realm && (expire > now)) {
+ return;
+ } else {
+ /* we own this ADS_STRUCT so make sure it goes away */
+ DEBUG(7,("Deleting expired krb5 credential cache\n"));
+ TALLOC_FREE(ads);
+ ads_kdestroy(WINBIND_CCACHE_NAME);
+ *adsp = NULL;
+ }
+ }
+}
+
+/**
+ * @brief Establish a connection to a DC
+ *
+ * @param[out] adsp ADS_STRUCT that will be created
+ * @param[in] target_realm Realm of domain to connect to
+ * @param[in] target_dom_name 'workgroup' name of domain to connect to
+ * @param[in] ldap_server DNS name of server to connect to
+ * @param[in] password Our machine account secret
+ * @param[in] auth_realm Realm of local domain for creating krb token
+ * @param[in] renewable Renewable ticket time
+ *
+ * @return ADS_STATUS
+ */
+static ADS_STATUS ads_cached_connection_connect(const char *target_realm,
+ const char *target_dom_name,
+ const char *ldap_server,
+ char *password,
+ char *auth_realm,
+ time_t renewable,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **adsp)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STRUCT *ads;
+ ADS_STATUS status;
+ struct sockaddr_storage dc_ss;
+ fstring dc_name;
+ enum credentials_use_kerberos krb5_state;
+
+ if (auth_realm == NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ }
+
+ /* we don't want this to affect the users ccache */
+ setenv("KRB5CCNAME", WINBIND_CCACHE_NAME, 1);
+
+ ads = ads_init(tmp_ctx,
+ target_realm,
+ target_dom_name,
+ ldap_server,
+ ADS_SASL_SEAL);
+ if (!ads) {
+ DEBUG(1,("ads_init for domain %s failed\n", target_dom_name));
+ status = ADS_ERROR(LDAP_NO_MEMORY);
+ goto out;
+ }
+
+ ADS_TALLOC_CONST_FREE(ads->auth.password);
+ ADS_TALLOC_CONST_FREE(ads->auth.realm);
+
+ ads->auth.renewable = renewable;
+ ads->auth.password = talloc_strdup(ads, password);
+ if (ads->auth.password == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ /* In FIPS mode, client use kerberos is forced to required. */
+ krb5_state = lp_client_use_kerberos();
+ switch (krb5_state) {
+ case CRED_USE_KERBEROS_REQUIRED:
+ ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ ads->auth.flags &= ~ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DESIRED:
+ ads->auth.flags &= ~ADS_AUTH_DISABLE_KERBEROS;
+ ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ case CRED_USE_KERBEROS_DISABLED:
+ ads->auth.flags |= ADS_AUTH_DISABLE_KERBEROS;
+ ads->auth.flags |= ADS_AUTH_ALLOW_NTLMSSP;
+ break;
+ }
+
+ ads->auth.realm = talloc_asprintf_strupper_m(ads, "%s", auth_realm);
+ if (ads->auth.realm == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
+ goto out;
+ }
+
+ /* Setup the server affinity cache. We don't reaally care
+ about the name. Just setup affinity and the KRB5_CONFIG
+ file. */
+ get_dc_name(ads->server.workgroup, ads->server.realm, dc_name, &dc_ss);
+
+ status = ads_connect(ads);
+ if (!ADS_ERR_OK(status)) {
+ DEBUG(1,("ads_connect for domain %s failed: %s\n",
+ target_dom_name, ads_errstr(status)));
+ goto out;
+ }
+
+ *adsp = talloc_move(mem_ctx, &ads);
+out:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+ADS_STATUS ads_idmap_cached_connection(const char *dom_name,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **adsp)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ char *ldap_server = NULL;
+ char *realm = NULL;
+ char *password = NULL;
+ struct winbindd_domain *wb_dom = NULL;
+ ADS_STATUS status;
+
+ if (IS_AD_DC) {
+ /*
+ * Make sure we never try to use LDAP against
+ * a trusted domain as AD DC.
+ */
+ TALLOC_FREE(tmp_ctx);
+ return ADS_ERROR_NT(NT_STATUS_REQUEST_NOT_ACCEPTED);
+ }
+
+ ads_cached_connection_reuse(adsp);
+ if (*adsp != NULL) {
+ TALLOC_FREE(tmp_ctx);
+ return ADS_SUCCESS;
+ }
+
+ /*
+ * At this point we only have the NetBIOS domain name.
+ * Check if we can get server name and realm from SAF cache
+ * and the domain list.
+ */
+ ldap_server = saf_fetch(tmp_ctx, dom_name);
+
+ DBG_DEBUG("ldap_server from saf cache: '%s'\n",
+ ldap_server ? ldap_server : "");
+
+ wb_dom = find_domain_from_name(dom_name);
+ if (wb_dom == NULL) {
+ DBG_DEBUG("could not find domain '%s'\n", dom_name);
+ status = ADS_ERROR_NT(NT_STATUS_UNSUCCESSFUL);
+ goto out;
+ }
+
+ DBG_DEBUG("find_domain_from_name found realm '%s' for "
+ " domain '%s'\n", wb_dom->alt_name, dom_name);
+
+ if (!get_trust_pw_clear(dom_name, &password, NULL, NULL)) {
+ status = ADS_ERROR_NT(NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
+ goto out;
+ }
+
+ if (IS_DC) {
+ SMB_ASSERT(wb_dom->alt_name != NULL);
+ realm = talloc_strdup(tmp_ctx, wb_dom->alt_name);
+ } else {
+ struct winbindd_domain *our_domain = wb_dom;
+
+ /* always give preference to the alt_name in our
+ primary domain if possible */
+
+ if (!wb_dom->primary) {
+ our_domain = find_our_domain();
+ }
+
+ if (our_domain->alt_name != NULL) {
+ realm = talloc_strdup(tmp_ctx, our_domain->alt_name);
+ } else {
+ realm = talloc_strdup(tmp_ctx, lp_realm());
+ }
+ }
+
+ if (realm == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ status = ads_cached_connection_connect(
+ wb_dom->alt_name, /* realm to connect to. */
+ dom_name, /* 'workgroup' name for ads_init */
+ ldap_server, /* DNS name to connect to. */
+ password, /* password for auth realm. */
+ realm, /* realm used for krb5 ticket. */
+ 0, /* renewable ticket time. */
+ mem_ctx, /* memory context for ads struct */
+ adsp); /* Returns ads struct. */
+
+out:
+ TALLOC_FREE(tmp_ctx);
+ SAFE_FREE(password);
+
+ return status;
+}
+
+/*
+ return our ads connections structure for a domain. We keep the connection
+ open to make things faster
+*/
+static ADS_STATUS ads_cached_connection(struct winbindd_domain *domain,
+ ADS_STRUCT **adsp)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ ADS_STATUS status;
+ char *password = NULL;
+ char *realm = NULL;
+
+ if (IS_AD_DC) {
+ /*
+ * Make sure we never try to use LDAP against
+ * a trusted domain as AD DC.
+ */
+ TALLOC_FREE(tmp_ctx);
+ return ADS_ERROR_NT(NT_STATUS_REQUEST_NOT_ACCEPTED);
+ }
+
+ DBG_DEBUG("ads_cached_connection\n");
+
+ ads_cached_connection_reuse(&domain->backend_data.ads_conn);
+ if (domain->backend_data.ads_conn != NULL) {
+ *adsp = domain->backend_data.ads_conn;
+ TALLOC_FREE(tmp_ctx);
+ return ADS_SUCCESS;
+ }
+
+ /* the machine acct password might have change - fetch it every time */
+
+ if (!get_trust_pw_clear(domain->name, &password, NULL, NULL)) {
+ status = ADS_ERROR_NT(NT_STATUS_CANT_ACCESS_DOMAIN_INFO);
+ goto out;
+ }
+
+ if ( IS_DC ) {
+ SMB_ASSERT(domain->alt_name != NULL);
+ realm = talloc_strdup(tmp_ctx, domain->alt_name);
+ } else {
+ struct winbindd_domain *our_domain = domain;
+
+
+ /* always give preference to the alt_name in our
+ primary domain if possible */
+
+ if ( !domain->primary )
+ our_domain = find_our_domain();
+
+ if (our_domain->alt_name != NULL) {
+ realm = talloc_strdup(tmp_ctx, our_domain->alt_name );
+ } else {
+ realm = talloc_strdup(tmp_ctx, lp_realm() );
+ }
+ }
+
+ if (realm == NULL) {
+ status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+
+ status = ads_cached_connection_connect(
+ domain->alt_name,
+ domain->name, NULL,
+ password,
+ realm,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ domain,
+ &domain->backend_data.ads_conn);
+ if (!ADS_ERR_OK(status)) {
+ /* if we get ECONNREFUSED then it might be a NT4
+ server, fall back to MSRPC */
+ if (status.error_type == ENUM_ADS_ERROR_SYSTEM &&
+ status.err.rc == ECONNREFUSED) {
+ /* 'reconnect_methods' is the MS-RPC backend. */
+ DBG_NOTICE("Trying MSRPC methods for domain '%s'\n",
+ domain->name);
+ domain->backend = &reconnect_methods;
+ }
+ goto out;
+ }
+
+ *adsp = domain->backend_data.ads_conn;
+out:
+ TALLOC_FREE(tmp_ctx);
+ SAFE_FREE(password);
+
+ return status;
+}
+
+/* Query display info for a realm. This is the basic user list fn */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **prids)
+{
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = { "sAMAccountType", "objectSid", NULL };
+ int count;
+ uint32_t *rids = NULL;
+ ADS_STATUS rc;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+ DEBUG(3,("ads: query_user_list\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("query_user_list: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ rc = ads_cached_connection(domain, &ads);
+ if (!ADS_ERR_OK(rc)) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ rc = ads_search_retry(ads, &res, "(objectCategory=user)", attrs);
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(1,("query_user_list ads_search: %s\n", ads_errstr(rc)));
+ status = ads_ntstatus(rc);
+ goto done;
+ } else if (!res) {
+ DEBUG(1,("query_user_list ads_search returned NULL res\n"));
+ goto done;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count == 0) {
+ DEBUG(1,("query_user_list: No users found\n"));
+ goto done;
+ }
+
+ rids = talloc_zero_array(mem_ctx, uint32_t, count);
+ if (rids == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ count = 0;
+
+ for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
+ struct dom_sid user_sid;
+ uint32_t atype;
+ bool ok;
+
+ ok = ads_pull_uint32(ads, msg, "sAMAccountType", &atype);
+ if (!ok) {
+ DBG_INFO("Object lacks sAMAccountType attribute\n");
+ continue;
+ }
+ if (ds_atype_map(atype) != SID_NAME_USER) {
+ DBG_INFO("Not a user account? atype=0x%x\n", atype);
+ continue;
+ }
+
+ if (!ads_pull_sid(ads, msg, "objectSid", &user_sid)) {
+ char *dn = ads_get_dn(ads, talloc_tos(), msg);
+ DBG_INFO("No sid for %s !?\n", dn);
+ TALLOC_FREE(dn);
+ continue;
+ }
+
+ if (!dom_sid_in_domain(&domain->sid, &user_sid)) {
+ struct dom_sid_buf sidstr, domstr;
+ DBG_WARNING("Got sid %s in domain %s\n",
+ dom_sid_str_buf(&user_sid, &sidstr),
+ dom_sid_str_buf(&domain->sid, &domstr));
+ continue;
+ }
+
+ sid_split_rid(&user_sid, &rids[count]);
+ count += 1;
+ }
+
+ rids = talloc_realloc(mem_ctx, rids, uint32_t, count);
+ if (prids != NULL) {
+ *prids = rids;
+ } else {
+ TALLOC_FREE(rids);
+ }
+
+ status = NT_STATUS_OK;
+
+ DBG_NOTICE("ads query_user_list gave %d entries\n", count);
+
+done:
+ ads_msgfree(ads, res);
+ return status;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = {"userPrincipalName", "sAMAccountName",
+ "name", "objectSid", NULL};
+ int i, count;
+ ADS_STATUS rc;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ const char *filter;
+ bool enum_dom_local_groups = False;
+
+ *num_entries = 0;
+
+ DEBUG(3,("ads: enum_dom_groups\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("enum_dom_groups: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ /* only grab domain local groups for our domain */
+ if ( domain->active_directory && strequal(lp_realm(), domain->alt_name) ) {
+ enum_dom_local_groups = True;
+ }
+
+ /* Workaround ADS LDAP bug present in MS W2K3 SP0 and W2K SP4 w/o
+ * rollup-fixes:
+ *
+ * According to Section 5.1(4) of RFC 2251 if a value of a type is it's
+ * default value, it MUST be absent. In case of extensible matching the
+ * "dnattr" boolean defaults to FALSE and so it must be only be present
+ * when set to TRUE.
+ *
+ * When it is set to FALSE and the OpenLDAP lib (correctly) encodes a
+ * filter using bitwise matching rule then the buggy AD fails to decode
+ * the extensible match. As a workaround set it to TRUE and thereby add
+ * the dnAttributes "dn" field to cope with those older AD versions.
+ * It should not harm and won't put any additional load on the AD since
+ * none of the dn components have a bitmask-attribute.
+ *
+ * Thanks to Ralf Haferkamp for input and testing - Guenther */
+
+ filter = talloc_asprintf(mem_ctx, "(&(objectCategory=group)"
+ "(&(groupType:dn:"ADS_LDAP_MATCHING_RULE_BIT_AND":=%d)"
+ "(!(groupType:dn:"ADS_LDAP_MATCHING_RULE_BIT_AND":=%d))))",
+ GROUP_TYPE_SECURITY_ENABLED,
+ enum_dom_local_groups ? GROUP_TYPE_BUILTIN_LOCAL_GROUP : GROUP_TYPE_RESOURCE_GROUP);
+
+ if (filter == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ rc = ads_cached_connection(domain, &ads);
+ if (!ADS_ERR_OK(rc)) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ rc = ads_search_retry(ads, &res, filter, attrs);
+ if (!ADS_ERR_OK(rc)) {
+ status = ads_ntstatus(rc);
+ DEBUG(1,("enum_dom_groups ads_search: %s\n", ads_errstr(rc)));
+ goto done;
+ } else if (!res) {
+ DEBUG(1,("enum_dom_groups ads_search returned NULL res\n"));
+ goto done;
+ }
+
+ count = ads_count_replies(ads, res);
+ if (count == 0) {
+ DEBUG(1,("enum_dom_groups: No groups found\n"));
+ goto done;
+ }
+
+ (*info) = talloc_zero_array(mem_ctx, struct wb_acct_info, count);
+ if (!*info) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ i = 0;
+
+ for (msg = ads_first_entry(ads, res); msg; msg = ads_next_entry(ads, msg)) {
+ char *name, *gecos;
+ struct dom_sid sid;
+ uint32_t rid;
+
+ name = ads_pull_username(ads, (*info), msg);
+ gecos = ads_pull_string(ads, (*info), msg, "name");
+ if (!ads_pull_sid(ads, msg, "objectSid", &sid)) {
+ DEBUG(1,("No sid for %s !?\n", name));
+ continue;
+ }
+
+ if (!sid_peek_check_rid(&domain->sid, &sid, &rid)) {
+ DEBUG(1,("No rid for %s !?\n", name));
+ continue;
+ }
+
+ (*info)[i].acct_name = name;
+ (*info)[i].acct_desc = gecos;
+ (*info)[i].rid = rid;
+ i++;
+ }
+
+ (*num_entries) = i;
+
+ status = NT_STATUS_OK;
+
+ DEBUG(3,("ads enum_dom_groups gave %d entries\n", (*num_entries)));
+
+done:
+ if (res)
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/* list all domain local groups */
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ /*
+ * This is a stub function only as we returned the domain
+ * local groups in enum_dom_groups() if the domain->native field
+ * was true. This is a simple performance optimization when
+ * using LDAP.
+ *
+ * if we ever need to enumerate domain local groups separately,
+ * then this optimization in enum_dom_groups() will need
+ * to be split out
+ */
+ *num_entries = 0;
+
+ return NT_STATUS_OK;
+}
+
+/* convert a single name to a sid in a domain - use rpc methods */
+static NTSTATUS name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ const char **pdom_name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ return msrpc_methods.name_to_sid(domain, mem_ctx, domain_name, name,
+ flags, pdom_name, sid, type);
+}
+
+/* convert a domain SID to a user or group name - use rpc methods */
+static NTSTATUS sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ return msrpc_methods.sid_to_name(domain, mem_ctx, sid,
+ domain_name, name, type);
+}
+
+/* convert a list of rids to names - use rpc methods */
+static NTSTATUS rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ return msrpc_methods.rids_to_names(domain, mem_ctx, sid,
+ rids, num_rids,
+ domain_name, names, types);
+}
+
+/* Lookup groups a user is a member of - alternate method, for when
+ tokenGroups are not available. */
+static NTSTATUS lookup_usergroups_member(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *user_dn,
+ struct dom_sid *primary_group,
+ uint32_t *p_num_groups, struct dom_sid **user_sids)
+{
+ ADS_STATUS rc;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ int count;
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg = NULL;
+ char *ldap_exp;
+ ADS_STRUCT *ads = NULL;
+ const char *group_attrs[] = {"objectSid", NULL};
+ char *escaped_dn;
+ uint32_t num_groups = 0;
+
+ DEBUG(3,("ads: lookup_usergroups_member\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups_members: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ rc = ads_cached_connection(domain, &ads);
+ if (!ADS_ERR_OK(rc)) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ if (!(escaped_dn = escape_ldap_string(talloc_tos(), user_dn))) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ ldap_exp = talloc_asprintf(mem_ctx,
+ "(&(member=%s)(objectCategory=group)"
+ "(groupType:dn:"ADS_LDAP_MATCHING_RULE_BIT_AND":=%d))",
+ escaped_dn,
+ GROUP_TYPE_SECURITY_ENABLED);
+ if (!ldap_exp) {
+ DEBUG(1,("lookup_usergroups(dn=%s) asprintf failed!\n", user_dn));
+ TALLOC_FREE(escaped_dn);
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ TALLOC_FREE(escaped_dn);
+
+ rc = ads_search_retry(ads, &res, ldap_exp, group_attrs);
+
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(1,("lookup_usergroups ads_search member=%s: %s\n", user_dn, ads_errstr(rc)));
+ return ads_ntstatus(rc);
+ } else if (!res) {
+ DEBUG(1,("lookup_usergroups ads_search returned NULL res\n"));
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+
+ count = ads_count_replies(ads, res);
+
+ *user_sids = NULL;
+ num_groups = 0;
+
+ /* always add the primary group to the sid array */
+ status = add_sid_to_array(mem_ctx, primary_group, user_sids,
+ &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (count > 0) {
+ for (msg = ads_first_entry(ads, res); msg;
+ msg = ads_next_entry(ads, msg)) {
+ struct dom_sid group_sid;
+
+ if (!ads_pull_sid(ads, msg, "objectSid", &group_sid)) {
+ DEBUG(1,("No sid for this group ?!?\n"));
+ continue;
+ }
+
+ /* ignore Builtin groups from ADS - Guenther */
+ if (sid_check_is_in_builtin(&group_sid)) {
+ continue;
+ }
+
+ status = add_sid_to_array(mem_ctx, &group_sid,
+ user_sids, &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ }
+
+ *p_num_groups = num_groups;
+ status = (user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,("ads lookup_usergroups (member) succeeded for dn=%s\n", user_dn));
+done:
+ if (res)
+ ads_msgfree(ads, res);
+
+ return status;
+}
+
+/* Lookup groups a user is a member of - alternate method, for when
+ tokenGroups are not available. */
+static NTSTATUS lookup_usergroups_memberof(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *user_dn,
+ struct dom_sid *primary_group,
+ uint32_t *p_num_groups,
+ struct dom_sid **user_sids)
+{
+ ADS_STATUS rc;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = {"memberOf", NULL};
+ uint32_t num_groups = 0;
+ struct dom_sid *group_sids = NULL;
+ size_t i;
+ char **strings = NULL;
+ size_t num_strings = 0, num_sids = 0;
+
+
+ DEBUG(3,("ads: lookup_usergroups_memberof\n"));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups_memberof: No incoming trust for "
+ "domain %s\n", domain->name));
+ return NT_STATUS_OK;
+ }
+
+ rc = ads_cached_connection(domain, &ads);
+ if (!ADS_ERR_OK(rc)) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ rc = ads_search_retry_extended_dn_ranged(ads, mem_ctx, user_dn, attrs,
+ ADS_EXTENDED_DN_HEX_STRING,
+ &strings, &num_strings);
+
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(1,("lookup_usergroups_memberof ads_search "
+ "member=%s: %s\n", user_dn, ads_errstr(rc)));
+ return ads_ntstatus(rc);
+ }
+
+ *user_sids = NULL;
+ num_groups = 0;
+
+ /* always add the primary group to the sid array */
+ status = add_sid_to_array(mem_ctx, primary_group, user_sids,
+ &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ group_sids = talloc_zero_array(mem_ctx, struct dom_sid, num_strings + 1);
+ if (!group_sids) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i<num_strings; i++) {
+ rc = ads_get_sid_from_extended_dn(mem_ctx, strings[i],
+ ADS_EXTENDED_DN_HEX_STRING,
+ &(group_sids)[i]);
+ if (!ADS_ERR_OK(rc)) {
+ /* ignore members without SIDs */
+ if (NT_STATUS_EQUAL(ads_ntstatus(rc),
+ NT_STATUS_NOT_FOUND)) {
+ continue;
+ }
+ else {
+ status = ads_ntstatus(rc);
+ goto done;
+ }
+ }
+ num_sids++;
+ }
+
+ if (i == 0) {
+ DEBUG(1,("No memberOf for this user?!?\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ for (i=0; i<num_sids; i++) {
+
+ /* ignore Builtin groups from ADS - Guenther */
+ if (sid_check_is_in_builtin(&group_sids[i])) {
+ continue;
+ }
+
+ status = add_sid_to_array(mem_ctx, &group_sids[i], user_sids,
+ &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ }
+
+ *p_num_groups = num_groups;
+ status = (*user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,("ads lookup_usergroups (memberof) succeeded for dn=%s\n",
+ user_dn));
+
+done:
+ TALLOC_FREE(strings);
+ TALLOC_FREE(group_sids);
+
+ return status;
+}
+
+
+/* Lookup groups a user is a member of. */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ uint32_t *p_num_groups, struct dom_sid **user_sids)
+{
+ ADS_STRUCT *ads = NULL;
+ const char *attrs[] = {"tokenGroups", "primaryGroupID", NULL};
+ ADS_STATUS rc;
+ int count;
+ LDAPMessage *msg = NULL;
+ char *user_dn = NULL;
+ struct dom_sid *sids;
+ int i;
+ struct dom_sid primary_group;
+ uint32_t primary_group_rid;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ uint32_t num_groups = 0;
+ struct dom_sid_buf buf;
+
+ DEBUG(3,("ads: lookup_usergroups\n"));
+ *p_num_groups = 0;
+
+ status = lookup_usergroups_cached(mem_ctx, sid,
+ p_num_groups, user_sids);
+ if (NT_STATUS_IS_OK(status)) {
+ return NT_STATUS_OK;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups: No incoming trust for domain %s\n",
+ domain->name));
+
+ /* Tell the cache manager not to remember this one */
+
+ return NT_STATUS_SYNCHRONIZATION_REQUIRED;
+ }
+
+ rc = ads_cached_connection(domain, &ads);
+ if (!ADS_ERR_OK(rc)) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ rc = ads_search_retry_sid(ads, &msg, sid, attrs);
+
+ if (!ADS_ERR_OK(rc)) {
+ status = ads_ntstatus(rc);
+ DEBUG(1, ("lookup_usergroups(sid=%s) ads_search tokenGroups: "
+ "%s\n",
+ dom_sid_str_buf(sid, &buf),
+ ads_errstr(rc)));
+ goto done;
+ }
+
+ count = ads_count_replies(ads, msg);
+ if (count != 1) {
+ status = NT_STATUS_UNSUCCESSFUL;
+ DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: "
+ "invalid number of results (count=%d)\n",
+ dom_sid_str_buf(sid, &buf),
+ count));
+ goto done;
+ }
+
+ if (!msg) {
+ DEBUG(1,("lookup_usergroups(sid=%s) ads_search tokenGroups: NULL msg\n",
+ dom_sid_str_buf(sid, &buf)));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ user_dn = ads_get_dn(ads, mem_ctx, msg);
+ if (user_dn == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (!ads_pull_uint32(ads, msg, "primaryGroupID", &primary_group_rid)) {
+ DEBUG(1,("%s: No primary group for sid=%s !?\n",
+ domain->name,
+ dom_sid_str_buf(sid, &buf)));
+ goto done;
+ }
+
+ sid_compose(&primary_group, &domain->sid, primary_group_rid);
+
+ count = ads_pull_sids(ads, mem_ctx, msg, "tokenGroups", &sids);
+
+ /* there must always be at least one group in the token,
+ unless we are talking to a buggy Win2k server */
+
+ /* actually this only happens when the machine account has no read
+ * permissions on the tokenGroup attribute - gd */
+
+ if (count == 0) {
+
+ /* no tokenGroups */
+
+ /* lookup what groups this user is a member of by DN search on
+ * "memberOf" */
+
+ status = lookup_usergroups_memberof(domain, mem_ctx, user_dn,
+ &primary_group,
+ &num_groups, user_sids);
+ *p_num_groups = num_groups;
+ if (NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ /* lookup what groups this user is a member of by DN search on
+ * "member" */
+
+ status = lookup_usergroups_member(domain, mem_ctx, user_dn,
+ &primary_group,
+ &num_groups, user_sids);
+ *p_num_groups = num_groups;
+ goto done;
+ }
+
+ *user_sids = NULL;
+ num_groups = 0;
+
+ status = add_sid_to_array(mem_ctx, &primary_group, user_sids,
+ &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ for (i=0;i<count;i++) {
+
+ /* ignore Builtin groups from ADS - Guenther */
+ if (sid_check_is_in_builtin(&sids[i])) {
+ continue;
+ }
+
+ status = add_sid_to_array_unique(mem_ctx, &sids[i],
+ user_sids, &num_groups);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ }
+
+ *p_num_groups = (uint32_t)num_groups;
+ status = (*user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,("ads lookup_usergroups (tokenGroups) succeeded for sid=%s\n",
+ dom_sid_str_buf(sid, &buf)));
+done:
+ TALLOC_FREE(user_dn);
+ ads_msgfree(ads, msg);
+ return status;
+}
+
+/* Lookup aliases a user is member of - use rpc methods */
+static NTSTATUS lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids, const struct dom_sid *sids,
+ uint32_t *num_aliases, uint32_t **alias_rids)
+{
+ return msrpc_methods.lookup_useraliases(domain, mem_ctx, num_sids, sids,
+ num_aliases, alias_rids);
+}
+
+static NTSTATUS add_primary_group_members(
+ ADS_STRUCT *ads, TALLOC_CTX *mem_ctx, uint32_t rid,
+ char ***all_members, size_t *num_all_members)
+{
+ char *filter;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ ADS_STATUS rc;
+ const char *attrs[] = { "dn", NULL };
+ LDAPMessage *res = NULL;
+ LDAPMessage *msg;
+ char **members;
+ size_t num_members;
+ ads_control args;
+
+ filter = talloc_asprintf(
+ mem_ctx, "(&(objectCategory=user)(primaryGroupID=%u))",
+ (unsigned)rid);
+ if (filter == NULL) {
+ goto done;
+ }
+
+ args.control = ADS_EXTENDED_DN_OID;
+ args.val = ADS_EXTENDED_DN_HEX_STRING;
+ args.critical = True;
+
+ rc = ads_do_search_all_args(ads, ads->config.bind_path,
+ LDAP_SCOPE_SUBTREE, filter, attrs, &args,
+ &res);
+
+ if (!ADS_ERR_OK(rc)) {
+ status = ads_ntstatus(rc);
+ DEBUG(1,("%s: ads_search: %s\n", __func__, ads_errstr(rc)));
+ goto done;
+ }
+ if (res == NULL) {
+ DEBUG(1,("%s: ads_search returned NULL res\n", __func__));
+ goto done;
+ }
+
+ num_members = ads_count_replies(ads, res);
+
+ DEBUG(10, ("%s: Got %ju primary group members\n", __func__,
+ (uintmax_t)num_members));
+
+ if (num_members == 0) {
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ members = talloc_realloc(mem_ctx, *all_members, char *,
+ *num_all_members + num_members);
+ if (members == NULL) {
+ DEBUG(1, ("%s: talloc_realloc failed\n", __func__));
+ goto done;
+ }
+ *all_members = members;
+
+ for (msg = ads_first_entry(ads, res); msg != NULL;
+ msg = ads_next_entry(ads, msg)) {
+ char *dn;
+
+ dn = ads_get_dn(ads, members, msg);
+ if (dn == NULL) {
+ DEBUG(1, ("%s: ads_get_dn failed\n", __func__));
+ continue;
+ }
+
+ members[*num_all_members] = dn;
+ *num_all_members += 1;
+ }
+
+ status = NT_STATUS_OK;
+done:
+ if (res != NULL) {
+ ads_msgfree(ads, res);
+ }
+ TALLOC_FREE(filter);
+ return status;
+}
+
+/*
+ find the members of a group, given a group rid and domain
+ */
+static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem, char ***names,
+ uint32_t **name_types)
+{
+ ADS_STATUS rc;
+ ADS_STRUCT *ads = NULL;
+ char *ldap_exp;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ char *sidbinstr;
+ char **members = NULL;
+ size_t i;
+ size_t num_members = 0;
+ ads_control args;
+ struct dom_sid *sid_mem_nocache = NULL;
+ char **names_nocache = NULL;
+ enum lsa_SidType *name_types_nocache = NULL;
+ char **domains_nocache = NULL; /* only needed for rpccli_lsa_lookup_sids */
+ uint32_t num_nocache = 0;
+ TALLOC_CTX *tmp_ctx = NULL;
+ uint32_t rid;
+ struct dom_sid_buf buf;
+
+ DEBUG(10,("ads: lookup_groupmem %s sid=%s\n", domain->name,
+ dom_sid_str_buf(group_sid, &buf)));
+
+ *num_names = 0;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (!tmp_ctx) {
+ DEBUG(1, ("ads: lookup_groupmem: talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (!sid_peek_rid(group_sid, &rid)) {
+ DEBUG(1, ("%s: sid_peek_rid failed\n", __func__));
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_groupmem: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ rc = ads_cached_connection(domain, &ads);
+ if (!ADS_ERR_OK(rc)) {
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+ goto done;
+ }
+
+ if ((sidbinstr = ldap_encode_ndr_dom_sid(talloc_tos(), group_sid)) == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ /* search for all members of the group */
+ ldap_exp = talloc_asprintf(tmp_ctx, "(objectSid=%s)", sidbinstr);
+ TALLOC_FREE(sidbinstr);
+ if (ldap_exp == NULL) {
+ DEBUG(1, ("ads: lookup_groupmem: talloc_asprintf for ldap_exp failed!\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ args.control = ADS_EXTENDED_DN_OID;
+ args.val = ADS_EXTENDED_DN_HEX_STRING;
+ args.critical = True;
+
+ rc = ads_ranged_search(ads, tmp_ctx, LDAP_SCOPE_SUBTREE, ads->config.bind_path,
+ ldap_exp, &args, "member", &members, &num_members);
+
+ if (!ADS_ERR_OK(rc)) {
+ DEBUG(0,("ads_ranged_search failed with: %s\n", ads_errstr(rc)));
+ status = NT_STATUS_UNSUCCESSFUL;
+ goto done;
+ }
+
+ DEBUG(10, ("ads lookup_groupmem: got %d sids via extended dn call\n", (int)num_members));
+
+ status = add_primary_group_members(ads, mem_ctx, rid,
+ &members, &num_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("%s: add_primary_group_members failed: %s\n",
+ __func__, nt_errstr(status)));
+ goto done;
+ }
+
+ DEBUG(10, ("%s: Got %d sids after adding primary group members\n",
+ __func__, (int)num_members));
+
+ /* Now that we have a list of sids, we need to get the
+ * lists of names and name_types belonging to these sids.
+ * even though conceptually not quite clean, we use the
+ * RPC call lsa_lookup_sids for this since it can handle a
+ * list of sids. ldap calls can just resolve one sid at a time.
+ *
+ * At this stage, the sids are still hidden in the exetended dn
+ * member output format. We actually do a little better than
+ * stated above: In extracting the sids from the member strings,
+ * we try to resolve as many sids as possible from the
+ * cache. Only the rest is passed to the lsa_lookup_sids call. */
+
+ if (num_members) {
+ (*sid_mem) = talloc_zero_array(mem_ctx, struct dom_sid, num_members);
+ (*names) = talloc_zero_array(mem_ctx, char *, num_members);
+ (*name_types) = talloc_zero_array(mem_ctx, uint32_t, num_members);
+ (sid_mem_nocache) = talloc_zero_array(tmp_ctx, struct dom_sid, num_members);
+
+ if ((members == NULL) || (*sid_mem == NULL) ||
+ (*names == NULL) || (*name_types == NULL) ||
+ (sid_mem_nocache == NULL))
+ {
+ DEBUG(1, ("ads: lookup_groupmem: talloc failed\n"));
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+ else {
+ (*sid_mem) = NULL;
+ (*names) = NULL;
+ (*name_types) = NULL;
+ }
+
+ for (i=0; i<num_members; i++) {
+ enum lsa_SidType name_type;
+ char *name, *domain_name;
+ struct dom_sid sid;
+
+ rc = ads_get_sid_from_extended_dn(tmp_ctx, members[i], args.val,
+ &sid);
+ if (!ADS_ERR_OK(rc)) {
+ if (NT_STATUS_EQUAL(ads_ntstatus(rc),
+ NT_STATUS_NOT_FOUND)) {
+ /* Group members can be objects, like Exchange
+ * Public Folders, that don't have a SID. Skip
+ * them. */
+ continue;
+ }
+ else {
+ status = ads_ntstatus(rc);
+ goto done;
+ }
+ }
+ if (lookup_cached_sid(mem_ctx, &sid, &domain_name, &name,
+ &name_type)) {
+ DEBUG(10,("ads: lookup_groupmem: got sid %s from "
+ "cache\n",
+ dom_sid_str_buf(&sid, &buf)));
+ sid_copy(&(*sid_mem)[*num_names], &sid);
+ (*names)[*num_names] = fill_domain_username_talloc(
+ *names,
+ domain_name,
+ name,
+ true);
+
+ (*name_types)[*num_names] = name_type;
+ (*num_names)++;
+ }
+ else {
+ DEBUG(10, ("ads: lookup_groupmem: sid %s not found in "
+ "cache\n",
+ dom_sid_str_buf(&sid, &buf)));
+ sid_copy(&(sid_mem_nocache)[num_nocache], &sid);
+ num_nocache++;
+ }
+ }
+
+ DEBUG(10, ("ads: lookup_groupmem: %d sids found in cache, "
+ "%d left for lsa_lookupsids\n", *num_names, num_nocache));
+
+ /* handle sids not resolved from cache by lsa_lookup_sids */
+ if (num_nocache > 0) {
+
+ status = winbindd_lookup_sids(tmp_ctx,
+ domain,
+ num_nocache,
+ sid_mem_nocache,
+ &domains_nocache,
+ &names_nocache,
+ &name_types_nocache);
+
+ if (!(NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)))
+ {
+ DEBUG(1, ("lsa_lookupsids call failed with %s "
+ "- retrying...\n", nt_errstr(status)));
+
+ status = winbindd_lookup_sids(tmp_ctx,
+ domain,
+ num_nocache,
+ sid_mem_nocache,
+ &domains_nocache,
+ &names_nocache,
+ &name_types_nocache);
+ }
+
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED))
+ {
+ /* Copy the entries over from the "_nocache" arrays
+ * to the result arrays, skipping the gaps the
+ * lookup_sids call left. */
+ for (i=0; i < num_nocache; i++) {
+ if (((names_nocache)[i] != NULL) &&
+ ((name_types_nocache)[i] != SID_NAME_UNKNOWN))
+ {
+ sid_copy(&(*sid_mem)[*num_names],
+ &sid_mem_nocache[i]);
+ (*names)[*num_names] =
+ fill_domain_username_talloc(
+ *names,
+ domains_nocache[i],
+ names_nocache[i],
+ true);
+ (*name_types)[*num_names] = name_types_nocache[i];
+ (*num_names)++;
+ }
+ }
+ }
+ else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ DEBUG(10, ("lookup_groupmem: lsa_lookup_sids could "
+ "not map any SIDs at all.\n"));
+ /* Don't handle this as an error here.
+ * There is nothing left to do with respect to the
+ * overall result... */
+ }
+ else if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("lookup_groupmem: Error looking up %d "
+ "sids via rpc_lsa_lookup_sids: %s\n",
+ (int)num_members, nt_errstr(status)));
+ goto done;
+ }
+ }
+
+ status = NT_STATUS_OK;
+ DEBUG(3,("ads lookup_groupmem for sid=%s succeeded\n",
+ dom_sid_str_buf(group_sid, &buf)));
+
+done:
+
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+static NTSTATUS lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ enum lsa_SidType type,
+ uint32_t *num_sids,
+ struct dom_sid **sids)
+{
+ char **names = NULL;
+ uint32_t *name_types = NULL;
+ struct dom_sid_buf buf;
+
+ DBG_DEBUG("ads: lookup_aliasmem %s sid=%s\n",
+ domain->name,
+ dom_sid_str_buf(sid, &buf));
+ /* Search for alias and group membership uses the same LDAP command. */
+ return lookup_groupmem(domain,
+ mem_ctx,
+ sid,
+ type,
+ num_sids,
+ sids,
+ &names,
+ &name_types);
+}
+
+/* find the lockout policy of a domain - use rpc methods */
+static NTSTATUS lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *policy)
+{
+ return msrpc_methods.lockout_policy(domain, mem_ctx, policy);
+}
+
+/* find the password policy of a domain - use rpc methods */
+static NTSTATUS password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *policy)
+{
+ return msrpc_methods.password_policy(domain, mem_ctx, policy);
+}
+
+/* get a list of trusted domains */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *trusts)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr;
+ uint32_t i;
+ uint32_t flags;
+ struct rpc_pipe_client *cli;
+ struct dcerpc_binding_handle *b;
+
+ DEBUG(3,("ads: trusted_domains\n"));
+
+ ZERO_STRUCTP(trusts);
+
+ /* If this is our primary domain or a root in our forest,
+ query for all trusts. If not, then just look for domain
+ trusts in the target forest */
+
+ if (domain->primary || domain_is_forest_root(domain)) {
+ flags = NETR_TRUST_FLAG_OUTBOUND |
+ NETR_TRUST_FLAG_INBOUND |
+ NETR_TRUST_FLAG_IN_FOREST;
+ } else {
+ flags = NETR_TRUST_FLAG_IN_FOREST;
+ }
+
+ result = cm_connect_netlogon(domain, &cli);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(5, ("trusted_domains: Could not open a connection to %s "
+ "for PIPE_NETLOGON (%s)\n",
+ domain->name, nt_errstr(result)));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ b = cli->binding_handle;
+
+ result = dcerpc_netr_DsrEnumerateDomainTrusts(b, mem_ctx,
+ cli->desthost,
+ flags,
+ trusts,
+ &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ return werror_to_ntstatus(werr);
+ }
+ if (trusts->count == 0) {
+ return NT_STATUS_OK;
+ }
+
+ /* Copy across names and sids */
+
+ for (i = 0; i < trusts->count; i++) {
+ struct netr_DomainTrust *trust = &trusts->array[i];
+ struct winbindd_domain d;
+
+ ZERO_STRUCT(d);
+
+ /*
+ * drop external trusts if this is not our primary
+ * domain. This means that the returned number of
+ * domains may be less that the ones actually trusted
+ * by the DC.
+ */
+
+ if ((trust->trust_attributes
+ & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) &&
+ !domain->primary )
+ {
+ DEBUG(10,("trusted_domains: Skipping external trusted "
+ "domain %s because it is outside of our "
+ "primary domain\n",
+ trust->netbios_name));
+ continue;
+ }
+
+ /* add to the trusted domain cache */
+
+ d.name = discard_const_p(char, trust->netbios_name);
+ d.alt_name = discard_const_p(char, trust->dns_name);
+
+ if (trust->sid) {
+ sid_copy(&d.sid, trust->sid);
+ } else {
+ sid_copy(&d.sid, &global_sid_NULL);
+ }
+
+ if ( domain->primary ) {
+ DEBUG(10,("trusted_domains(ads): Searching "
+ "trusted domain list of %s and storing "
+ "trust flags for domain %s\n",
+ domain->name, d.alt_name));
+
+ d.domain_flags = trust->trust_flags;
+ d.domain_type = trust->trust_type;
+ d.domain_trust_attribs = trust->trust_attributes;
+
+ wcache_tdc_add_domain( &d );
+ } else if (domain_is_forest_root(domain)) {
+ /* Check if we already have this record. If
+ * we are following our forest root that is not
+ * our primary domain, we want to keep trust
+ * flags from the perspective of our primary
+ * domain not our forest root. */
+ struct winbindd_tdc_domain *exist = NULL;
+
+ exist = wcache_tdc_fetch_domain(
+ talloc_tos(), trust->netbios_name);
+ if (!exist) {
+ DEBUG(10,("trusted_domains(ads): Searching "
+ "trusted domain list of %s and "
+ "storing trust flags for domain "
+ "%s\n", domain->name, d.alt_name));
+ d.domain_flags = trust->trust_flags;
+ d.domain_type = trust->trust_type;
+ d.domain_trust_attribs =
+ trust->trust_attributes;
+
+ wcache_tdc_add_domain( &d );
+ }
+ TALLOC_FREE(exist);
+ } else {
+ /* This gets a little tricky. If we are
+ following a transitive forest trust, then
+ innerit the flags, type, and attribs from
+ the domain we queried to make sure we don't
+ record the view of the trust from the wrong
+ side. Always view it from the side of our
+ primary domain. --jerry */
+ struct winbindd_tdc_domain *parent = NULL;
+
+ DEBUG(10,("trusted_domains(ads): Searching "
+ "trusted domain list of %s and inheriting "
+ "trust flags for domain %s\n",
+ domain->name, d.alt_name));
+
+ parent = wcache_tdc_fetch_domain(talloc_tos(),
+ domain->name);
+ if (parent) {
+ d.domain_flags = parent->trust_flags;
+ d.domain_type = parent->trust_type;
+ d.domain_trust_attribs = parent->trust_attribs;
+ } else {
+ d.domain_flags = domain->domain_flags;
+ d.domain_type = domain->domain_type;
+ d.domain_trust_attribs =
+ domain->domain_trust_attribs;
+ }
+ TALLOC_FREE(parent);
+
+ /*
+ * We need to pass the modified properties
+ * to the caller.
+ */
+ trust->trust_flags = d.domain_flags;
+ trust->trust_type = d.domain_type;
+ trust->trust_attributes = d.domain_trust_attribs;
+
+ wcache_tdc_add_domain( &d );
+ }
+ }
+ return result;
+}
+
+/* the ADS backend methods are exposed via this structure */
+struct winbindd_methods ads_methods = {
+ True,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ name_to_sid,
+ sid_to_name,
+ rids_to_names,
+ lookup_usergroups,
+ lookup_useraliases,
+ lookup_groupmem,
+ lookup_aliasmem,
+ lockout_policy,
+ password_policy,
+ trusted_domains,
+};
+
+#endif
diff --git a/source3/winbindd/winbindd_ads.h b/source3/winbindd/winbindd_ads.h
new file mode 100644
index 0000000..0fd9774
--- /dev/null
+++ b/source3/winbindd/winbindd_ads.h
@@ -0,0 +1,34 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind ADS backend functions
+
+ Copyright (C) Volker Lendecke 2017
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __WINBINDD_ADS_H__
+#define __WINBINDD_ADS_H__
+
+
+#include "ads.h"
+
+extern struct winbindd_methods ads_methods;
+
+ADS_STATUS ads_idmap_cached_connection(const char *dom_name,
+ TALLOC_CTX *mem_ctx,
+ ADS_STRUCT **adsp);
+
+#endif
diff --git a/source3/winbindd/winbindd_allocate_gid.c b/source3/winbindd/winbindd_allocate_gid.c
new file mode 100644
index 0000000..2841d96
--- /dev/null
+++ b/source3/winbindd/winbindd_allocate_gid.c
@@ -0,0 +1,121 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_ALLOCATE_GID
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_allocate_gid_state {
+ struct tevent_context *ev;
+ uint64_t gid;
+};
+
+static void winbindd_allocate_gid_initialized(struct tevent_req *subreq);
+static void winbindd_allocate_gid_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_allocate_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_allocate_gid_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_allocate_gid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ DEBUG(3, ("allocate_gid\n"));
+
+ subreq = wb_parent_idmap_setup_send(state, ev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, winbindd_allocate_gid_initialized, req);
+ return req;
+}
+
+static void winbindd_allocate_gid_initialized(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_allocate_gid_state *state = tevent_req_data(
+ req, struct winbindd_allocate_gid_state);
+ NTSTATUS status;
+ const struct wb_parent_idmap_config *cfg = NULL;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+
+ status = wb_parent_idmap_setup_recv(subreq, &cfg);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (cfg->num_doms == 0) {
+ /*
+ * idmap_tdb also returns UNSUCCESSFUL if a range is full
+ */
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+
+ child_binding_handle = idmap_child_handle();
+
+ subreq = dcerpc_wbint_AllocateGid_send(
+ state, state->ev, child_binding_handle, &state->gid);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_allocate_gid_done, req);
+}
+
+static void winbindd_allocate_gid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_allocate_gid_state *state = tevent_req_data(
+ req, struct winbindd_allocate_gid_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_AllocateGid_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_allocate_gid_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_allocate_gid_state *state = tevent_req_data(
+ req, struct winbindd_allocate_gid_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DEBUG(5, ("Could not allocate gid: %s\n", nt_errstr(status)));
+ return status;
+ }
+ response->data.gid = state->gid;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_allocate_uid.c b/source3/winbindd/winbindd_allocate_uid.c
new file mode 100644
index 0000000..64711f1
--- /dev/null
+++ b/source3/winbindd/winbindd_allocate_uid.c
@@ -0,0 +1,122 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_ALLOCATE_UID
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_allocate_uid_state {
+ struct tevent_context *ev;
+ uint64_t uid;
+};
+
+static void winbindd_allocate_uid_initialized(struct tevent_req *subreq);
+static void winbindd_allocate_uid_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_allocate_uid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_allocate_uid_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_allocate_uid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ DEBUG(3, ("allocate_uid\n"));
+
+ subreq = wb_parent_idmap_setup_send(state, ev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_allocate_uid_initialized, req);
+ return req;
+}
+
+static void winbindd_allocate_uid_initialized(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ struct winbindd_allocate_uid_state *state = tevent_req_data(
+ req, struct winbindd_allocate_uid_state);
+ const struct wb_parent_idmap_config *cfg = NULL;
+ NTSTATUS status;
+
+ status = wb_parent_idmap_setup_recv(subreq, &cfg);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (cfg->num_doms == 0) {
+ /*
+ * idmap_tdb also returns UNSUCCESSFUL if a range is full
+ */
+ tevent_req_nterror(req, NT_STATUS_UNSUCCESSFUL);
+ return;
+ }
+
+ child_binding_handle = idmap_child_handle();
+
+ subreq = dcerpc_wbint_AllocateUid_send(state,
+ state->ev,
+ child_binding_handle,
+ &state->uid);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_allocate_uid_done, req);
+}
+
+static void winbindd_allocate_uid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_allocate_uid_state *state = tevent_req_data(
+ req, struct winbindd_allocate_uid_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_AllocateUid_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_allocate_uid_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_allocate_uid_state *state = tevent_req_data(
+ req, struct winbindd_allocate_uid_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DEBUG(5, ("Could not allocate uid: %s\n", nt_errstr(status)));
+ return status;
+ }
+ response->data.uid = state->uid;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_cache.c b/source3/winbindd/winbindd_cache.c
new file mode 100644
index 0000000..ca2341e
--- /dev/null
+++ b/source3/winbindd/winbindd_cache.c
@@ -0,0 +1,4930 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind cache backend functions
+
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Gerald Carter 2003-2007
+ Copyright (C) Volker Lendecke 2005
+ Copyright (C) Guenther Deschner 2005
+ Copyright (C) Michael Adam 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "system/filesys.h"
+#include "winbindd.h"
+#include "tdb_validate.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_winbind.h"
+#include "ads.h"
+#include "nss_info.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+#include "util_tdb.h"
+#include "libsmb/samlogon_cache.h"
+#include "lib/namemap_cache.h"
+#include "lib/util/string_wrappers.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/crypto.h>
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define WINBINDD_CACHE_VER1 1 /* initial db version */
+#define WINBINDD_CACHE_VER2 2 /* second version with timeouts for NDR entries */
+
+#define WINBINDD_CACHE_VERSION WINBINDD_CACHE_VER2
+#define WINBINDD_CACHE_VERSION_KEYSTR "WINBINDD_CACHE_VERSION"
+
+extern struct winbindd_methods reconnect_methods;
+#ifdef HAVE_ADS
+extern struct winbindd_methods reconnect_ads_methods;
+#endif
+extern struct winbindd_methods builtin_passdb_methods;
+extern struct winbindd_methods sam_passdb_methods;
+
+static void wcache_flush_cache(void);
+
+static bool opt_nocache = False;
+
+/*
+ * JRA. KEEP THIS LIST UP TO DATE IF YOU ADD CACHE ENTRIES.
+ * Here are the list of entry types that are *not* stored
+ * as form struct cache_entry in the cache.
+ */
+
+static const char *non_centry_keys[] = {
+ "SEQNUM/",
+ "WINBINDD_OFFLINE",
+ WINBINDD_CACHE_VERSION_KEYSTR,
+ NULL
+};
+
+bool winbindd_use_idmap_cache(void)
+{
+ return !opt_nocache;
+}
+
+bool winbindd_use_cache(void)
+{
+ return !opt_nocache;
+}
+
+void winbindd_set_use_cache(bool use_cache)
+{
+ opt_nocache = !use_cache;
+}
+
+void winbindd_flush_caches(void)
+{
+ /* We need to invalidate cached user list entries on a SIGHUP
+ otherwise cached access denied errors due to restrict anonymous
+ hang around until the sequence number changes. */
+
+ if (!wcache_invalidate_cache()) {
+ DBG_ERR("invalidating the cache failed; revalidate the cache\n");
+ if (!winbindd_cache_validate_and_initialize()) {
+ exit(1);
+ }
+ }
+}
+
+/************************************************************************
+ Is this key a non-centry type ?
+************************************************************************/
+
+static bool is_non_centry_key(TDB_DATA kbuf)
+{
+ int i;
+
+ if (kbuf.dptr == NULL || kbuf.dsize == 0) {
+ return false;
+ }
+ for (i = 0; non_centry_keys[i] != NULL; i++) {
+ size_t namelen = strlen(non_centry_keys[i]);
+ if (kbuf.dsize < namelen) {
+ continue;
+ }
+ if (strncmp(non_centry_keys[i], (const char *)kbuf.dptr, namelen) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+struct winbind_cache {
+ TDB_CONTEXT *tdb;
+};
+
+struct cache_entry {
+ NTSTATUS status;
+ uint32_t sequence_number;
+ uint64_t timeout;
+ uint8_t *data;
+ uint32_t len, ofs;
+};
+
+void (*smb_panic_fn)(const char *const why) = smb_panic;
+
+static struct winbind_cache *wcache;
+
+static char *wcache_path(void)
+{
+ /*
+ * Data needs to be kept persistent in state directory for
+ * running with "winbindd offline logon".
+ */
+ return state_path(talloc_tos(), "winbindd_cache.tdb");
+}
+
+static void winbindd_domain_init_backend(struct winbindd_domain *domain)
+{
+ if (domain->backend != NULL) {
+ return;
+ }
+
+ if (domain->internal) {
+ domain->backend = &builtin_passdb_methods;
+ }
+
+ if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) {
+ domain->initialized = true;
+ }
+
+ if (strequal(domain->name, get_global_sam_name()) &&
+ sid_check_is_our_sam(&domain->sid))
+ {
+ domain->backend = &sam_passdb_methods;
+ }
+
+ if (!domain->initialized) {
+ /* We do not need a connection to an RW DC for cache operation */
+ init_dc_connection(domain, false);
+ }
+
+#ifdef HAVE_ADS
+ if (domain->backend == NULL) {
+ struct winbindd_domain *our_domain = domain;
+
+ /* find our domain first so we can figure out if we
+ are joined to a kerberized domain */
+
+ if (!domain->primary) {
+ our_domain = find_our_domain();
+ }
+
+ if ((our_domain->active_directory || IS_DC)
+ && domain->active_directory
+ && !lp_winbind_rpc_only())
+ {
+ DBG_INFO("Setting ADS methods for domain %s\n",
+ domain->name);
+ domain->backend = &reconnect_ads_methods;
+ }
+ }
+#endif /* HAVE_ADS */
+
+ if (domain->backend == NULL) {
+ DBG_INFO("Setting MS-RPC methods for domain %s\n", domain->name);
+ domain->backend = &reconnect_methods;
+ }
+}
+
+/* get the winbind_cache structure */
+static struct winbind_cache *get_cache(struct winbindd_domain *domain)
+{
+ struct winbind_cache *ret = wcache;
+
+ winbindd_domain_init_backend(domain);
+
+ if (ret != NULL) {
+ return ret;
+ }
+
+ ret = SMB_XMALLOC_P(struct winbind_cache);
+ ZERO_STRUCTP(ret);
+
+ wcache = ret;
+ wcache_flush_cache();
+
+ return ret;
+}
+
+/*
+ free a centry structure
+*/
+static void centry_free(struct cache_entry *centry)
+{
+ if (!centry)
+ return;
+ SAFE_FREE(centry->data);
+ free(centry);
+}
+
+static bool centry_check_bytes(struct cache_entry *centry, size_t nbytes)
+{
+ if (centry->len - centry->ofs < nbytes) {
+ DBG_ERR("centry corruption? needed %u bytes, have %d\n",
+ (unsigned int)nbytes,
+ centry->len - centry->ofs);
+ return false;
+ }
+ return true;
+}
+
+/*
+ pull a uint64_t from a cache entry
+*/
+static uint64_t centry_uint64_t(struct cache_entry *centry)
+{
+ uint64_t ret;
+
+ if (!centry_check_bytes(centry, 8)) {
+ smb_panic_fn("centry_uint64_t");
+ }
+ ret = BVAL(centry->data, centry->ofs);
+ centry->ofs += 8;
+ return ret;
+}
+
+/*
+ pull a uint32_t from a cache entry
+*/
+static uint32_t centry_uint32(struct cache_entry *centry)
+{
+ uint32_t ret;
+
+ if (!centry_check_bytes(centry, 4)) {
+ smb_panic_fn("centry_uint32");
+ }
+ ret = IVAL(centry->data, centry->ofs);
+ centry->ofs += 4;
+ return ret;
+}
+
+/*
+ pull a uint16_t from a cache entry
+*/
+static uint16_t centry_uint16(struct cache_entry *centry)
+{
+ uint16_t ret;
+ if (!centry_check_bytes(centry, 2)) {
+ smb_panic_fn("centry_uint16");
+ }
+ ret = SVAL(centry->data, centry->ofs);
+ centry->ofs += 2;
+ return ret;
+}
+
+/*
+ pull a uint8_t from a cache entry
+*/
+static uint8_t centry_uint8(struct cache_entry *centry)
+{
+ uint8_t ret;
+ if (!centry_check_bytes(centry, 1)) {
+ smb_panic_fn("centry_uint8");
+ }
+ ret = CVAL(centry->data, centry->ofs);
+ centry->ofs += 1;
+ return ret;
+}
+
+/*
+ pull a NTTIME from a cache entry
+*/
+static NTTIME centry_nttime(struct cache_entry *centry)
+{
+ NTTIME ret;
+ if (!centry_check_bytes(centry, 8)) {
+ smb_panic_fn("centry_nttime");
+ }
+ ret = IVAL(centry->data, centry->ofs);
+ centry->ofs += 4;
+ ret += (uint64_t)IVAL(centry->data, centry->ofs) << 32;
+ centry->ofs += 4;
+ return ret;
+}
+
+/*
+ pull a time_t from a cache entry. time_t stored portably as a 64-bit time.
+*/
+static time_t centry_time(struct cache_entry *centry)
+{
+ return (time_t)centry_nttime(centry);
+}
+
+/* pull a string from a cache entry, using the supplied
+ talloc context
+*/
+static char *centry_string(struct cache_entry *centry, TALLOC_CTX *mem_ctx)
+{
+ uint32_t len;
+ char *ret;
+
+ len = centry_uint8(centry);
+
+ if (len == 0xFF) {
+ /* a deliberate NULL string */
+ return NULL;
+ }
+
+ if (!centry_check_bytes(centry, (size_t)len)) {
+ smb_panic_fn("centry_string");
+ }
+
+ ret = talloc_array(mem_ctx, char, len+1);
+ if (!ret) {
+ smb_panic_fn("centry_string out of memory\n");
+ }
+ memcpy(ret,centry->data + centry->ofs, len);
+ ret[len] = 0;
+ centry->ofs += len;
+ return ret;
+}
+
+/* pull a hash16 from a cache entry, using the supplied
+ talloc context
+*/
+static char *centry_hash16(struct cache_entry *centry, TALLOC_CTX *mem_ctx)
+{
+ uint32_t len;
+ char *ret;
+
+ len = centry_uint8(centry);
+
+ if (len != 16) {
+ DBG_ERR("centry corruption? hash len (%u) != 16\n",
+ len );
+ return NULL;
+ }
+
+ if (!centry_check_bytes(centry, 16)) {
+ return NULL;
+ }
+
+ ret = talloc_array(mem_ctx, char, 16);
+ if (!ret) {
+ smb_panic_fn("centry_hash out of memory\n");
+ }
+ memcpy(ret,centry->data + centry->ofs, 16);
+ centry->ofs += 16;
+ return ret;
+}
+
+/* pull a sid from a cache entry, using the supplied
+ talloc context
+*/
+static bool centry_sid(struct cache_entry *centry, struct dom_sid *sid)
+{
+ char *sid_string;
+ bool ret;
+
+ sid_string = centry_string(centry, talloc_tos());
+ if (sid_string == NULL) {
+ return false;
+ }
+ ret = string_to_sid(sid, sid_string);
+ TALLOC_FREE(sid_string);
+ return ret;
+}
+
+
+/*
+ pull a NTSTATUS from a cache entry
+*/
+static NTSTATUS centry_ntstatus(struct cache_entry *centry)
+{
+ NTSTATUS status;
+
+ status = NT_STATUS(centry_uint32(centry));
+ return status;
+}
+
+
+/* the server is considered down if it can't give us a sequence number */
+static bool wcache_server_down(struct winbindd_domain *domain)
+{
+ bool ret;
+
+ if (!wcache->tdb)
+ return false;
+
+ ret = (domain->sequence_number == DOM_SEQUENCE_NONE);
+
+ if (ret)
+ DBG_DEBUG("wcache_server_down: server for Domain %s down\n",
+ domain->name );
+ return ret;
+}
+
+struct wcache_seqnum_state {
+ uint32_t *seqnum;
+ uint32_t *last_seq_check;
+};
+
+static int wcache_seqnum_parser(TDB_DATA key, TDB_DATA data,
+ void *private_data)
+{
+ struct wcache_seqnum_state *state = private_data;
+
+ if (data.dsize != 8) {
+ DBG_DEBUG("wcache_fetch_seqnum: invalid data size %d\n",
+ (int)data.dsize);
+ return -1;
+ }
+
+ *state->seqnum = IVAL(data.dptr, 0);
+ *state->last_seq_check = IVAL(data.dptr, 4);
+ return 0;
+}
+
+static bool wcache_fetch_seqnum(const char *domain_name, uint32_t *seqnum,
+ uint32_t *last_seq_check)
+{
+ struct wcache_seqnum_state state = {
+ .seqnum = seqnum, .last_seq_check = last_seq_check
+ };
+ size_t len = strlen(domain_name);
+ char keystr[len+8];
+ TDB_DATA key = { .dptr = (uint8_t *)keystr, .dsize = sizeof(keystr) };
+ int ret;
+
+ if (wcache->tdb == NULL) {
+ DBG_DEBUG("wcache_fetch_seqnum: tdb == NULL\n");
+ return false;
+ }
+
+ snprintf(keystr, sizeof(keystr), "SEQNUM/%s", domain_name);
+
+ ret = tdb_parse_record(wcache->tdb, key, wcache_seqnum_parser,
+ &state);
+ return (ret == 0);
+}
+
+static NTSTATUS fetch_cache_seqnum( struct winbindd_domain *domain, time_t now )
+{
+ uint32_t last_check, time_diff;
+
+ if (!wcache_fetch_seqnum(domain->name, &domain->sequence_number,
+ &last_check)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+ domain->last_seq_check = last_check;
+
+ /* have we expired? */
+
+ time_diff = now - domain->last_seq_check;
+ if ((int)time_diff > lp_winbind_cache_time()) {
+ DBG_DEBUG("fetch_cache_seqnum: timeout [%s][%u @ %u]\n",
+ domain->name, domain->sequence_number,
+ (uint32_t)domain->last_seq_check);
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ DBG_DEBUG("fetch_cache_seqnum: success [%s][%u @ %u]\n",
+ domain->name, domain->sequence_number,
+ (uint32_t)domain->last_seq_check);
+
+ return NT_STATUS_OK;
+}
+
+bool wcache_store_seqnum(const char *domain_name, uint32_t seqnum,
+ time_t last_seq_check)
+{
+ size_t len = strlen(domain_name);
+ char keystr[len+8];
+ TDB_DATA key = { .dptr = (uint8_t *)keystr, .dsize = sizeof(keystr) };
+ uint8_t buf[8];
+ int ret;
+
+ if (wcache->tdb == NULL) {
+ DBG_DEBUG("wcache_store_seqnum: wcache->tdb == NULL\n");
+ return false;
+ }
+
+ snprintf(keystr, sizeof(keystr), "SEQNUM/%s", domain_name);
+
+ SIVAL(buf, 0, seqnum);
+ SIVAL(buf, 4, last_seq_check);
+
+ ret = tdb_store(wcache->tdb, key, make_tdb_data(buf, sizeof(buf)),
+ TDB_REPLACE);
+ if (ret != 0) {
+ DBG_DEBUG("tdb_store_bystring failed: %s\n",
+ tdb_errorstr(wcache->tdb));
+ return false;
+ }
+
+ DBG_DEBUG("wcache_store_seqnum: success [%s][%u @ %u]\n",
+ domain_name, seqnum, (unsigned)last_seq_check);
+
+ return true;
+}
+
+static bool store_cache_seqnum( struct winbindd_domain *domain )
+{
+ return wcache_store_seqnum(domain->name, domain->sequence_number,
+ domain->last_seq_check);
+}
+
+/*
+ refresh the domain sequence number on timeout.
+*/
+
+static void refresh_sequence_number(struct winbindd_domain *domain)
+{
+ NTSTATUS status;
+ unsigned time_diff;
+ time_t t = time(NULL);
+ unsigned cache_time = lp_winbind_cache_time();
+
+ if (is_domain_offline(domain)) {
+ return;
+ }
+
+ get_cache( domain );
+
+ time_diff = t - domain->last_seq_check;
+
+ /* see if we have to refetch the domain sequence number */
+ if ((time_diff < cache_time) &&
+ (domain->sequence_number != DOM_SEQUENCE_NONE) &&
+ NT_STATUS_IS_OK(domain->last_status)) {
+ DBG_DEBUG("refresh_sequence_number: %s time ok\n", domain->name);
+ goto done;
+ }
+
+ /* try to get the sequence number from the tdb cache first */
+ /* this will update the timestamp as well */
+
+ status = fetch_cache_seqnum( domain, t );
+ if (NT_STATUS_IS_OK(status) &&
+ (domain->sequence_number != DOM_SEQUENCE_NONE) &&
+ NT_STATUS_IS_OK(domain->last_status)) {
+ goto done;
+ }
+
+ /* just use the current time */
+ domain->last_status = NT_STATUS_OK;
+ domain->sequence_number = time(NULL);
+ domain->last_seq_check = time(NULL);
+
+ /* save the new sequence number in the cache */
+ store_cache_seqnum( domain );
+
+done:
+ DBG_DEBUG("refresh_sequence_number: %s seq number is now %d\n",
+ domain->name, domain->sequence_number);
+
+ return;
+}
+
+/*
+ decide if a cache entry has expired
+*/
+static bool centry_expired(struct winbindd_domain *domain, const char *keystr, struct cache_entry *centry)
+{
+ /* If we've been told to be offline - stay in that state... */
+ if (lp_winbind_offline_logon() && get_global_winbindd_state_offline()) {
+ DBG_DEBUG("centry_expired: Key %s for domain %s valid as winbindd is globally offline.\n",
+ keystr, domain->name );
+ return false;
+ }
+
+ /* when the domain is offline return the cached entry.
+ * This deals with transient offline states... */
+
+ if (!domain->online) {
+ DBG_DEBUG("centry_expired: Key %s for domain %s valid as domain is offline.\n",
+ keystr, domain->name );
+ return false;
+ }
+
+ /* if the server is OK and our cache entry came from when it was down then
+ the entry is invalid */
+ if ((domain->sequence_number != DOM_SEQUENCE_NONE) &&
+ (centry->sequence_number == DOM_SEQUENCE_NONE)) {
+ DBG_DEBUG("centry_expired: Key %s for domain %s invalid sequence.\n",
+ keystr, domain->name );
+ return true;
+ }
+
+ /* if the server is down or the cache entry is not older than the
+ current sequence number or it did not timeout then it is OK */
+ if (wcache_server_down(domain)
+ || ((centry->sequence_number == domain->sequence_number)
+ && ((time_t)centry->timeout > time(NULL)))) {
+ DBG_DEBUG("centry_expired: Key %s for domain %s is good.\n",
+ keystr, domain->name );
+ return false;
+ }
+
+ DBG_DEBUG("centry_expired: Key %s for domain %s expired\n",
+ keystr, domain->name );
+
+ /* it's expired */
+ return true;
+}
+
+static struct cache_entry *wcache_fetch_raw(char *kstr)
+{
+ TDB_DATA data;
+ struct cache_entry *centry;
+ TDB_DATA key;
+
+ key = string_tdb_data(kstr);
+ data = tdb_fetch(wcache->tdb, key);
+ if (!data.dptr) {
+ /* a cache miss */
+ return NULL;
+ }
+
+ centry = SMB_XMALLOC_P(struct cache_entry);
+ centry->data = (unsigned char *)data.dptr;
+ centry->len = data.dsize;
+ centry->ofs = 0;
+
+ if (centry->len < 16) {
+ /* huh? corrupt cache? */
+ DBG_DEBUG("wcache_fetch_raw: Corrupt cache for key %s "
+ "(len < 16)?\n", kstr);
+ centry_free(centry);
+ return NULL;
+ }
+
+ centry->status = centry_ntstatus(centry);
+ centry->sequence_number = centry_uint32(centry);
+ centry->timeout = centry_uint64_t(centry);
+
+ return centry;
+}
+
+static bool is_my_own_sam_domain(struct winbindd_domain *domain)
+{
+ if (strequal(domain->name, get_global_sam_name()) &&
+ sid_check_is_our_sam(&domain->sid)) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool is_builtin_domain(struct winbindd_domain *domain)
+{
+ if (strequal(domain->name, "BUILTIN") &&
+ sid_check_is_builtin(&domain->sid)) {
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ fetch an entry from the cache, with a varargs key. auto-fetch the sequence
+ number and return status
+*/
+static struct cache_entry *wcache_fetch(struct winbind_cache *cache,
+ struct winbindd_domain *domain,
+ const char *format, ...) PRINTF_ATTRIBUTE(3,4);
+static struct cache_entry *wcache_fetch(struct winbind_cache *cache,
+ struct winbindd_domain *domain,
+ const char *format, ...)
+{
+ va_list ap;
+ char *kstr;
+ struct cache_entry *centry;
+ int ret;
+
+ if (!winbindd_use_cache() ||
+ is_my_own_sam_domain(domain) ||
+ is_builtin_domain(domain)) {
+ return NULL;
+ }
+
+ refresh_sequence_number(domain);
+
+ va_start(ap, format);
+ ret = vasprintf(&kstr, format, ap);
+ va_end(ap);
+
+ if (ret == -1) {
+ return NULL;
+ }
+
+ centry = wcache_fetch_raw(kstr);
+ if (centry == NULL) {
+ free(kstr);
+ return NULL;
+ }
+
+ if (centry_expired(domain, kstr, centry)) {
+
+ DBG_DEBUG("wcache_fetch: entry %s expired for domain %s\n",
+ kstr, domain->name );
+
+ centry_free(centry);
+ free(kstr);
+ return NULL;
+ }
+
+ DBG_DEBUG("wcache_fetch: returning entry %s for domain %s\n",
+ kstr, domain->name );
+
+ free(kstr);
+ return centry;
+}
+
+static void wcache_delete(const char *format, ...) PRINTF_ATTRIBUTE(1,2);
+static void wcache_delete(const char *format, ...)
+{
+ va_list ap;
+ char *kstr;
+ TDB_DATA key;
+ int ret;
+
+ va_start(ap, format);
+ ret = vasprintf(&kstr, format, ap);
+ va_end(ap);
+
+ if (ret == -1) {
+ return;
+ }
+
+ key = string_tdb_data(kstr);
+
+ tdb_delete(wcache->tdb, key);
+ free(kstr);
+}
+
+/*
+ make sure we have at least len bytes available in a centry
+*/
+static void centry_expand(struct cache_entry *centry, uint32_t len)
+{
+ if (centry->len - centry->ofs >= len)
+ return;
+ centry->len *= 2;
+ centry->data = SMB_REALLOC_ARRAY(centry->data, unsigned char,
+ centry->len);
+ if (!centry->data) {
+ DBG_ERR("out of memory: needed %d bytes in centry_expand\n", centry->len);
+ smb_panic_fn("out of memory in centry_expand");
+ }
+}
+
+/*
+ push a uint64_t into a centry
+*/
+static void centry_put_uint64_t(struct cache_entry *centry, uint64_t v)
+{
+ centry_expand(centry, 8);
+ SBVAL(centry->data, centry->ofs, v);
+ centry->ofs += 8;
+}
+
+/*
+ push a uint32_t into a centry
+*/
+static void centry_put_uint32(struct cache_entry *centry, uint32_t v)
+{
+ centry_expand(centry, 4);
+ SIVAL(centry->data, centry->ofs, v);
+ centry->ofs += 4;
+}
+
+/*
+ push a uint16_t into a centry
+*/
+static void centry_put_uint16(struct cache_entry *centry, uint16_t v)
+{
+ centry_expand(centry, 2);
+ SSVAL(centry->data, centry->ofs, v);
+ centry->ofs += 2;
+}
+
+/*
+ push a uint8_t into a centry
+*/
+static void centry_put_uint8(struct cache_entry *centry, uint8_t v)
+{
+ centry_expand(centry, 1);
+ SCVAL(centry->data, centry->ofs, v);
+ centry->ofs += 1;
+}
+
+/*
+ push a string into a centry
+ */
+static void centry_put_string(struct cache_entry *centry, const char *s)
+{
+ int len;
+
+ if (!s) {
+ /* null strings are marked as len 0xFFFF */
+ centry_put_uint8(centry, 0xFF);
+ return;
+ }
+
+ len = strlen(s);
+ /* can't handle more than 254 char strings. Truncating is probably best */
+ if (len > 254) {
+ DBG_DEBUG("centry_put_string: truncating len (%d) to: 254\n", len);
+ len = 254;
+ }
+ centry_put_uint8(centry, len);
+ centry_expand(centry, len);
+ memcpy(centry->data + centry->ofs, s, len);
+ centry->ofs += len;
+}
+
+/*
+ push a 16 byte hash into a centry - treat as 16 byte string.
+ */
+static void centry_put_hash16(struct cache_entry *centry, const uint8_t val[16])
+{
+ centry_put_uint8(centry, 16);
+ centry_expand(centry, 16);
+ memcpy(centry->data + centry->ofs, val, 16);
+ centry->ofs += 16;
+}
+
+static void centry_put_sid(struct cache_entry *centry, const struct dom_sid *sid)
+{
+ struct dom_sid_buf sid_string;
+ centry_put_string(centry, dom_sid_str_buf(sid, &sid_string));
+}
+
+
+/*
+ put NTSTATUS into a centry
+*/
+static void centry_put_ntstatus(struct cache_entry *centry, NTSTATUS status)
+{
+ uint32_t status_value = NT_STATUS_V(status);
+ centry_put_uint32(centry, status_value);
+}
+
+
+/*
+ push a NTTIME into a centry
+*/
+static void centry_put_nttime(struct cache_entry *centry, NTTIME nt)
+{
+ centry_expand(centry, 8);
+ SIVAL(centry->data, centry->ofs, nt & 0xFFFFFFFF);
+ centry->ofs += 4;
+ SIVAL(centry->data, centry->ofs, nt >> 32);
+ centry->ofs += 4;
+}
+
+/*
+ push a time_t into a centry - use a 64 bit size.
+ NTTIME here is being used as a convenient 64-bit size.
+*/
+static void centry_put_time(struct cache_entry *centry, time_t t)
+{
+ NTTIME nt = (NTTIME)t;
+ centry_put_nttime(centry, nt);
+}
+
+/*
+ start a centry for output. When finished, call centry_end()
+*/
+static struct cache_entry *centry_start(struct winbindd_domain *domain,
+ NTSTATUS status)
+{
+ struct cache_entry *centry;
+
+ if (!wcache->tdb)
+ return NULL;
+
+ centry = SMB_XMALLOC_P(struct cache_entry);
+
+ centry->len = 8192; /* reasonable default */
+ centry->data = SMB_XMALLOC_ARRAY(uint8_t, centry->len);
+ centry->ofs = 0;
+ centry->sequence_number = domain->sequence_number;
+ centry->timeout = lp_winbind_cache_time() + time(NULL);
+ centry_put_ntstatus(centry, status);
+ centry_put_uint32(centry, centry->sequence_number);
+ centry_put_uint64_t(centry, centry->timeout);
+ return centry;
+}
+
+/*
+ finish a centry and write it to the tdb
+*/
+static void centry_end(struct cache_entry *centry, const char *format, ...) PRINTF_ATTRIBUTE(2,3);
+static void centry_end(struct cache_entry *centry, const char *format, ...)
+{
+ va_list ap;
+ char *kstr;
+ TDB_DATA key, data;
+ int ret;
+
+ if (!winbindd_use_cache()) {
+ return;
+ }
+
+ va_start(ap, format);
+ ret = vasprintf(&kstr, format, ap);
+ va_end(ap);
+
+ if (ret == -1) {
+ return;
+ }
+
+ key = string_tdb_data(kstr);
+ data.dptr = centry->data;
+ data.dsize = centry->ofs;
+
+ tdb_store(wcache->tdb, key, data, TDB_REPLACE);
+ free(kstr);
+}
+
+static void wcache_save_name_to_sid(struct winbindd_domain *domain,
+ NTSTATUS status, const char *domain_name,
+ const char *name, const struct dom_sid *sid,
+ enum lsa_SidType type)
+{
+ bool ok;
+
+ ok = namemap_cache_set_name2sid(domain_name, name, sid, type,
+ time(NULL) + lp_winbind_cache_time());
+ if (!ok) {
+ DBG_DEBUG("namemap_cache_set_name2sid failed\n");
+ }
+
+ /*
+ * Don't store the reverse mapping. The name came from user
+ * input, and we might not have the correct capitalization,
+ * which is important for nsswitch.
+ */
+}
+
+static void wcache_save_sid_to_name(struct winbindd_domain *domain, NTSTATUS status,
+ const struct dom_sid *sid, const char *domain_name, const char *name, enum lsa_SidType type)
+{
+ bool ok;
+
+ ok = namemap_cache_set_sid2name(sid, domain_name, name, type,
+ time(NULL) + lp_winbind_cache_time());
+ if (!ok) {
+ DBG_DEBUG("namemap_cache_set_sid2name failed\n");
+ }
+
+ if (type != SID_NAME_UNKNOWN) {
+ ok = namemap_cache_set_name2sid(
+ domain_name, name, sid, type,
+ time(NULL) + lp_winbind_cache_time());
+ if (!ok) {
+ DBG_DEBUG("namemap_cache_set_name2sid failed\n");
+ }
+ }
+}
+
+static void wcache_save_lockout_policy(struct winbindd_domain *domain,
+ NTSTATUS status,
+ struct samr_DomInfo12 *lockout_policy)
+{
+ struct cache_entry *centry;
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ return;
+
+ centry_put_nttime(centry, lockout_policy->lockout_duration);
+ centry_put_nttime(centry, lockout_policy->lockout_window);
+ centry_put_uint16(centry, lockout_policy->lockout_threshold);
+
+ centry_end(centry, "LOC_POL/%s", domain->name);
+
+ DBG_DEBUG("wcache_save_lockout_policy: %s\n", domain->name);
+
+ centry_free(centry);
+}
+
+
+
+static void wcache_save_password_policy(struct winbindd_domain *domain,
+ NTSTATUS status,
+ struct samr_DomInfo1 *policy)
+{
+ struct cache_entry *centry;
+
+ centry = centry_start(domain, status);
+ if (!centry)
+ return;
+
+ centry_put_uint16(centry, policy->min_password_length);
+ centry_put_uint16(centry, policy->password_history_length);
+ centry_put_uint32(centry, policy->password_properties);
+ centry_put_nttime(centry, policy->max_password_age);
+ centry_put_nttime(centry, policy->min_password_age);
+
+ centry_end(centry, "PWD_POL/%s", domain->name);
+
+ DBG_DEBUG("wcache_save_password_policy: %s\n", domain->name);
+
+ centry_free(centry);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+
+static void wcache_save_username_alias(struct winbindd_domain *domain,
+ NTSTATUS status,
+ const char *name, const char *alias)
+{
+ struct cache_entry *centry;
+ fstring uname;
+
+ if ( (centry = centry_start(domain, status)) == NULL )
+ return;
+
+ centry_put_string( centry, alias );
+
+ fstrcpy(uname, name);
+ (void)strupper_m(uname);
+ centry_end(centry, "NSS/NA/%s", uname);
+
+ DBG_DEBUG("wcache_save_username_alias: %s -> %s\n", name, alias );
+
+ centry_free(centry);
+}
+
+static void wcache_save_alias_username(struct winbindd_domain *domain,
+ NTSTATUS status,
+ const char *alias, const char *name)
+{
+ struct cache_entry *centry;
+ fstring uname;
+
+ if ( (centry = centry_start(domain, status)) == NULL )
+ return;
+
+ centry_put_string( centry, name );
+
+ fstrcpy(uname, alias);
+ (void)strupper_m(uname);
+ centry_end(centry, "NSS/AN/%s", uname);
+
+ DBG_DEBUG("wcache_save_alias_username: %s -> %s\n", alias, name );
+
+ centry_free(centry);
+}
+
+/***************************************************************************
+ ***************************************************************************/
+
+NTSTATUS resolve_username_to_alias( TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ const char *name, char **alias )
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ char *upper_name;
+
+ if ( domain->internal )
+ return NT_STATUS_NOT_SUPPORTED;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ upper_name = talloc_strdup_upper(mem_ctx, name);
+ if (upper_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ centry = wcache_fetch(cache, domain, "NSS/NA/%s", upper_name);
+
+ talloc_free(upper_name);
+
+ if (!centry)
+ goto do_query;
+
+ status = centry->status;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ centry_free(centry);
+ return status;
+ }
+
+ *alias = centry_string( centry, mem_ctx );
+
+ centry_free(centry);
+
+ DBG_DEBUG("resolve_username_to_alias: [Cached] - mapped %s to %s\n",
+ name, *alias ? *alias : "(none)");
+
+ return (*alias) ? NT_STATUS_OK : NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+do_query:
+
+ /* If its not in cache and we are offline, then fail */
+
+ if (is_domain_offline(domain)) {
+ DBG_DEBUG("resolve_username_to_alias: rejecting query "
+ "in offline mode\n");
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ status = nss_map_to_alias( mem_ctx, domain->name, name, alias );
+
+ if ( NT_STATUS_IS_OK( status ) ) {
+ wcache_save_username_alias(domain, status, name, *alias);
+ }
+
+ if ( NT_STATUS_EQUAL( status, NT_STATUS_NONE_MAPPED ) ) {
+ wcache_save_username_alias(domain, status, name, "(NULL)");
+ }
+
+ DBG_INFO("resolve_username_to_alias: backend query returned %s\n",
+ nt_errstr(status));
+
+ if ( NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ) {
+ set_domain_offline( domain );
+ }
+
+ return status;
+}
+
+/***************************************************************************
+ ***************************************************************************/
+
+NTSTATUS resolve_alias_to_username( TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ const char *alias, char **name )
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ char *upper_name;
+
+ if ( domain->internal )
+ return NT_STATUS_NOT_SUPPORTED;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ upper_name = talloc_strdup(mem_ctx, alias);
+ if (upper_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ if (!strupper_m(upper_name)) {
+ talloc_free(upper_name);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ centry = wcache_fetch(cache, domain, "NSS/AN/%s", upper_name);
+
+ talloc_free(upper_name);
+
+ if (!centry)
+ goto do_query;
+
+ status = centry->status;
+
+ if (!NT_STATUS_IS_OK(status)) {
+ centry_free(centry);
+ return status;
+ }
+
+ *name = centry_string( centry, mem_ctx );
+
+ centry_free(centry);
+
+ DBG_DEBUG("resolve_alias_to_username: [Cached] - mapped %s to %s\n",
+ alias, *name ? *name : "(none)");
+
+ return (*name) ? NT_STATUS_OK : NT_STATUS_OBJECT_NAME_NOT_FOUND;
+
+do_query:
+
+ /* If its not in cache and we are offline, then fail */
+
+ if (is_domain_offline(domain)) {
+ DBG_DEBUG("resolve_alias_to_username: rejecting query "
+ "in offline mode\n");
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /* an alias cannot contain a domain prefix or '@' */
+
+ if (strchr(alias, '\\') || strchr(alias, '@')) {
+ DBG_DEBUG("resolve_alias_to_username: skipping fully "
+ "qualified name %s\n", alias);
+ return NT_STATUS_OBJECT_NAME_INVALID;
+ }
+
+ status = nss_map_from_alias( mem_ctx, domain->name, alias, name );
+
+ if ( NT_STATUS_IS_OK( status ) ) {
+ wcache_save_alias_username( domain, status, alias, *name );
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ wcache_save_alias_username(domain, status, alias, "(NULL)");
+ }
+
+ DBG_INFO("resolve_alias_to_username: backend query returned %s\n",
+ nt_errstr(status));
+
+ if ( NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ) {
+ set_domain_offline( domain );
+ }
+
+ return status;
+}
+
+NTSTATUS wcache_cached_creds_exist(struct winbindd_domain *domain, const struct dom_sid *sid)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ int ret;
+ struct dom_sid_buf tmp;
+ fstring key_str;
+ uint32_t rid;
+
+ if (!cache->tdb) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (is_null_sid(sid)) {
+ return NT_STATUS_INVALID_SID;
+ }
+
+ if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) {
+ return NT_STATUS_INVALID_SID;
+ }
+
+ fstr_sprintf(key_str, "CRED/%s", dom_sid_str_buf(sid, &tmp));
+
+ ret = tdb_exists(cache->tdb, string_tdb_data(key_str));
+ if (ret != 1) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* Lookup creds for a SID - copes with old (unsalted) creds as well
+ as new salted ones. */
+
+NTSTATUS wcache_get_creds(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ const uint8_t **cached_nt_pass,
+ const uint8_t **cached_salt)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ uint32_t rid;
+ struct dom_sid_buf sidstr;
+
+ if (!cache->tdb) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ if (is_null_sid(sid)) {
+ return NT_STATUS_INVALID_SID;
+ }
+
+ if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) {
+ return NT_STATUS_INVALID_SID;
+ }
+
+ /* Try and get a salted cred first. If we can't
+ fall back to an unsalted cred. */
+
+ centry = wcache_fetch(cache, domain, "CRED/%s",
+ dom_sid_str_buf(sid, &sidstr));
+ if (!centry) {
+ DBG_DEBUG("wcache_get_creds: entry for [CRED/%s] not found\n",
+ dom_sid_str_buf(sid, &sidstr));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * We don't use the time element at this moment,
+ * but we have to consume it, so that we don't
+ * need to change the disk format of the cache.
+ */
+ (void)centry_time(centry);
+
+ /* In the salted case this isn't actually the nt_hash itself,
+ but the MD5 of the salt + nt_hash. Let the caller
+ sort this out. It can tell as we only return the cached_salt
+ if we are returning a salted cred. */
+
+ *cached_nt_pass = (const uint8_t *)centry_hash16(centry, mem_ctx);
+ if (*cached_nt_pass == NULL) {
+
+ dom_sid_str_buf(sid, &sidstr);
+
+ /* Bad (old) cred cache. Delete and pretend we
+ don't have it. */
+ DBG_WARNING("wcache_get_creds: bad entry for [CRED/%s] - deleting\n",
+ sidstr.buf);
+ wcache_delete("CRED/%s", sidstr.buf);
+ centry_free(centry);
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /* We only have 17 bytes more data in the salted cred case. */
+ if (centry->len - centry->ofs == 17) {
+ *cached_salt = (const uint8_t *)centry_hash16(centry, mem_ctx);
+ } else {
+ *cached_salt = NULL;
+ }
+
+ dump_data_pw("cached_nt_pass", *cached_nt_pass, NT_HASH_LEN);
+ if (*cached_salt) {
+ dump_data_pw("cached_salt", *cached_salt, NT_HASH_LEN);
+ }
+
+ status = centry->status;
+
+ DBG_DEBUG("wcache_get_creds: [Cached] - cached creds for user %s status: %s\n",
+ dom_sid_str_buf(sid, &sidstr),
+ nt_errstr(status) );
+
+ centry_free(centry);
+ return status;
+}
+
+/* Store creds for a SID - only writes out new salted ones. */
+
+NTSTATUS wcache_save_creds(struct winbindd_domain *domain,
+ const struct dom_sid *sid,
+ const uint8_t nt_pass[NT_HASH_LEN])
+{
+ struct cache_entry *centry;
+ struct dom_sid_buf sid_str;
+ uint32_t rid;
+ uint8_t cred_salt[NT_HASH_LEN];
+ uint8_t salted_hash[NT_HASH_LEN];
+ gnutls_hash_hd_t hash_hnd = NULL;
+ int rc;
+
+ if (is_null_sid(sid)) {
+ return NT_STATUS_INVALID_SID;
+ }
+
+ if (!(sid_peek_rid(sid, &rid)) || (rid == 0)) {
+ return NT_STATUS_INVALID_SID;
+ }
+
+ centry = centry_start(domain, NT_STATUS_OK);
+ if (!centry) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ dump_data_pw("nt_pass", nt_pass, NT_HASH_LEN);
+
+ centry_put_time(centry, time(NULL));
+
+ /* Create a salt and then salt the hash. */
+ generate_random_buffer(cred_salt, NT_HASH_LEN);
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5);
+ if (rc < 0) {
+ centry_free(centry);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+
+ rc = gnutls_hash(hash_hnd, cred_salt, 16);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ centry_free(centry);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ rc = gnutls_hash(hash_hnd, nt_pass, 16);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ centry_free(centry);
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ }
+ gnutls_hash_deinit(hash_hnd, salted_hash);
+
+ centry_put_hash16(centry, salted_hash);
+ centry_put_hash16(centry, cred_salt);
+ centry_end(centry, "CRED/%s", dom_sid_str_buf(sid, &sid_str));
+
+ DBG_DEBUG("wcache_save_creds: %s\n", sid_str.buf);
+
+ centry_free(centry);
+
+ return NT_STATUS_OK;
+}
+
+
+/* Query display info. This is the basic user list fn */
+NTSTATUS wb_cache_query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **prids)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ uint32_t num_rids = 0;
+ uint32_t *rids = NULL;
+ NTSTATUS status;
+ unsigned int i, retry;
+ bool old_status = domain->online;
+
+ *prids = NULL;
+
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "UL/%s", domain->name);
+ if (!centry)
+ goto do_query;
+
+do_fetch_cache:
+ num_rids = centry_uint32(centry);
+
+ if (num_rids == 0) {
+ goto do_cached;
+ }
+
+ rids = talloc_array(mem_ctx, uint32_t, num_rids);
+ if (rids == NULL) {
+ centry_free(centry);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_rids; i++) {
+ rids[i] = centry_uint32(centry);
+ }
+
+do_cached:
+ status = centry->status;
+
+ DBG_DEBUG("query_user_list: [Cached] - cached list for domain %s status: %s\n",
+ domain->name, nt_errstr(status) );
+
+ centry_free(centry);
+ return status;
+
+do_query:
+
+ /* Put the query_user_list() in a retry loop. There appears to be
+ * some bug either with Windows 2000 or Samba's handling of large
+ * rpc replies. This manifests itself as sudden disconnection
+ * at a random point in the enumeration of a large (60k) user list.
+ * The retry loop simply tries the operation again. )-: It's not
+ * pretty but an acceptable workaround until we work out what the
+ * real problem is. */
+
+ retry = 0;
+ do {
+
+ DBG_DEBUG("query_user_list: [Cached] - doing backend query for list for domain %s\n",
+ domain->name );
+
+ rids = NULL;
+ status = domain->backend->query_user_list(domain, mem_ctx,
+ &rids);
+ num_rids = talloc_array_length(rids);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("query_user_list: returned 0x%08x, "
+ "retrying\n", NT_STATUS_V(status));
+ }
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_UNSUCCESSFUL)) {
+ DBG_NOTICE("query_user_list: flushing "
+ "connection cache\n");
+ invalidate_cm_connection(domain);
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ /* store partial response. */
+ if (num_rids > 0) {
+ /*
+ * humm, what about the status used for cache?
+ * Should it be NT_STATUS_OK?
+ */
+ break;
+ }
+ /*
+ * domain is offline now, and there is no user entries,
+ * try to fetch from cache again.
+ */
+ if (cache->tdb && !domain->online && !domain->internal && old_status) {
+ centry = wcache_fetch(cache, domain, "UL/%s", domain->name);
+ /* partial response... */
+ if (!centry) {
+ goto skip_save;
+ } else {
+ goto do_fetch_cache;
+ }
+ } else {
+ goto skip_save;
+ }
+ }
+
+ } while (NT_STATUS_V(status) == NT_STATUS_V(NT_STATUS_UNSUCCESSFUL) &&
+ (retry++ < 5));
+
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+ centry_put_uint32(centry, num_rids);
+ for (i=0; i<num_rids; i++) {
+ centry_put_uint32(centry, rids[i]);
+ }
+ centry_end(centry, "UL/%s", domain->name);
+ centry_free(centry);
+
+ *prids = rids;
+
+skip_save:
+ return status;
+}
+
+/* list all domain groups */
+NTSTATUS wb_cache_enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ bool old_status;
+
+ old_status = domain->online;
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name);
+ if (!centry)
+ goto do_query;
+
+do_fetch_cache:
+ *num_entries = centry_uint32(centry);
+
+ if (*num_entries == 0)
+ goto do_cached;
+
+ (*info) = talloc_array(mem_ctx, struct wb_acct_info, *num_entries);
+ if (! (*info)) {
+ smb_panic_fn("enum_dom_groups out of memory");
+ }
+ for (i=0; i<(*num_entries); i++) {
+ (*info)[i].acct_name = centry_string(centry, (*info));
+ (*info)[i].acct_desc = centry_string(centry, (*info));
+ (*info)[i].rid = centry_uint32(centry);
+ }
+
+do_cached:
+ status = centry->status;
+
+ DBG_DEBUG("enum_dom_groups: [Cached] - cached list for domain %s status: %s\n",
+ domain->name, nt_errstr(status) );
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ *num_entries = 0;
+ *info = NULL;
+
+ DBG_DEBUG("enum_dom_groups: [Cached] - doing backend query for list for domain %s\n",
+ domain->name );
+
+ status = domain->backend->enum_dom_groups(domain, mem_ctx, num_entries, info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (cache->tdb &&
+ !domain->online &&
+ !domain->internal &&
+ old_status) {
+ centry = wcache_fetch(cache, domain, "GL/%s/domain", domain->name);
+ if (centry) {
+ goto do_fetch_cache;
+ }
+ }
+ }
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+ centry_put_uint32(centry, *num_entries);
+ for (i=0; i<(*num_entries); i++) {
+ centry_put_string(centry, (*info)[i].acct_name);
+ centry_put_string(centry, (*info)[i].acct_desc);
+ centry_put_uint32(centry, (*info)[i].rid);
+ }
+ centry_end(centry, "GL/%s/domain", domain->name);
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+/* list all domain groups */
+NTSTATUS wb_cache_enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ bool old_status;
+
+ old_status = domain->online;
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name);
+ if (!centry)
+ goto do_query;
+
+do_fetch_cache:
+ *num_entries = centry_uint32(centry);
+
+ if (*num_entries == 0)
+ goto do_cached;
+
+ (*info) = talloc_array(mem_ctx, struct wb_acct_info, *num_entries);
+ if (! (*info)) {
+ smb_panic_fn("enum_dom_groups out of memory");
+ }
+ for (i=0; i<(*num_entries); i++) {
+ (*info)[i].acct_name = centry_string(centry, (*info));
+ (*info)[i].acct_desc = centry_string(centry, (*info));
+ (*info)[i].rid = centry_uint32(centry);
+ }
+
+do_cached:
+
+ /* If we are returning cached data and the domain controller
+ is down then we don't know whether the data is up to date
+ or not. Return NT_STATUS_MORE_PROCESSING_REQUIRED to
+ indicate this. */
+
+ if (wcache_server_down(domain)) {
+ DBG_DEBUG("enum_local_groups: returning cached user list and server was down\n");
+ status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ } else
+ status = centry->status;
+
+ DBG_DEBUG("enum_local_groups: [Cached] - cached list for domain %s status: %s\n",
+ domain->name, nt_errstr(status) );
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ *num_entries = 0;
+ *info = NULL;
+
+ DBG_DEBUG("enum_local_groups: [Cached] - doing backend query for list for domain %s\n",
+ domain->name );
+
+ status = domain->backend->enum_local_groups(domain, mem_ctx, num_entries, info);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (cache->tdb &&
+ !domain->internal &&
+ !domain->online &&
+ old_status) {
+ centry = wcache_fetch(cache, domain, "GL/%s/local", domain->name);
+ if (centry) {
+ goto do_fetch_cache;
+ }
+ }
+ }
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+ centry_put_uint32(centry, *num_entries);
+ for (i=0; i<(*num_entries); i++) {
+ centry_put_string(centry, (*info)[i].acct_name);
+ centry_put_string(centry, (*info)[i].acct_desc);
+ centry_put_uint32(centry, (*info)[i].rid);
+ }
+ centry_end(centry, "GL/%s/local", domain->name);
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+struct wcache_name_to_sid_state {
+ struct dom_sid *sid;
+ enum lsa_SidType *type;
+ bool offline;
+ bool found;
+};
+
+static void wcache_name_to_sid_fn(const struct dom_sid *sid,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ struct wcache_name_to_sid_state *state = private_data;
+
+ *state->sid = *sid;
+ *state->type = type;
+ state->found = (!expired || state->offline);
+}
+
+static NTSTATUS wcache_name_to_sid(struct winbindd_domain *domain,
+ const char *domain_name,
+ const char *name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ struct wcache_name_to_sid_state state = {
+ .sid = sid, .type = type, .found = false,
+ .offline = is_domain_offline(domain),
+ };
+ bool ok;
+
+ ok = namemap_cache_find_name(domain_name, name, wcache_name_to_sid_fn,
+ &state);
+ if (!ok) {
+ DBG_DEBUG("namemap_cache_find_name failed\n");
+ return NT_STATUS_NOT_FOUND;
+ }
+ if (!state.found) {
+ DBG_DEBUG("cache entry not found\n");
+ return NT_STATUS_NOT_FOUND;
+ }
+ if (*type == SID_NAME_UNKNOWN) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* convert a single name to a sid in a domain */
+NTSTATUS wb_cache_name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ NTSTATUS status;
+ bool old_status;
+ const char *dom_name;
+
+ old_status = domain->online;
+
+ status = wcache_name_to_sid(domain, domain_name, name, sid, type);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return status;
+ }
+
+ ZERO_STRUCTP(sid);
+
+ DBG_DEBUG("name_to_sid: [Cached] - doing backend query for name for domain %s\n",
+ domain->name );
+
+ winbindd_domain_init_backend(domain);
+ status = domain->backend->name_to_sid(domain, mem_ctx, domain_name,
+ name, flags, &dom_name, sid, type);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (!domain->internal &&
+ !domain->online &&
+ old_status) {
+ NTSTATUS cache_status;
+ cache_status = wcache_name_to_sid(domain, domain_name, name, sid, type);
+ return cache_status;
+ }
+ }
+ /* and save it */
+
+ if (domain->online &&
+ (NT_STATUS_IS_OK(status) || NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED))) {
+ enum lsa_SidType save_type = *type;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ save_type = SID_NAME_UNKNOWN;
+ }
+
+ wcache_save_name_to_sid(domain, status, domain_name, name, sid,
+ save_type);
+
+ /* Only save the reverse mapping if this was not a UPN */
+ if (!strchr(name, '@')) {
+ if (!strupper_m(discard_const_p(char, domain_name))) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ (void)strlower_m(discard_const_p(char, name));
+ wcache_save_sid_to_name(domain, status, sid,
+ dom_name, name, save_type);
+ }
+ }
+
+ return status;
+}
+
+struct wcache_sid_to_name_state {
+ TALLOC_CTX *mem_ctx;
+ char **domain_name;
+ char **name;
+ enum lsa_SidType *type;
+ bool offline;
+ bool found;
+};
+
+static void wcache_sid_to_name_fn(const char *domain,
+ const char *name,
+ enum lsa_SidType type,
+ bool expired,
+ void *private_data)
+{
+ struct wcache_sid_to_name_state *state = private_data;
+
+ *state->domain_name = talloc_strdup(state->mem_ctx, domain);
+ if (*state->domain_name == NULL) {
+ return;
+ }
+ *state->name = talloc_strdup(state->mem_ctx, name);
+ if (*state->name == NULL) {
+ return;
+ }
+ *state->type = type;
+ state->found = (!expired || state->offline);
+}
+
+static NTSTATUS wcache_sid_to_name(struct winbindd_domain *domain,
+ const struct dom_sid *sid,
+ TALLOC_CTX *mem_ctx,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ struct wcache_sid_to_name_state state = {
+ .mem_ctx = mem_ctx, .found = false,
+ .domain_name = domain_name, .name = name, .type = type,
+ .offline = is_domain_offline(domain)
+ };
+ bool ok;
+
+ ok = namemap_cache_find_sid(sid, wcache_sid_to_name_fn, &state);
+ if (!ok) {
+ DBG_DEBUG("namemap_cache_find_name failed\n");
+ return NT_STATUS_NOT_FOUND;
+ }
+ if (!state.found) {
+ DBG_DEBUG("cache entry not found\n");
+ return NT_STATUS_NOT_FOUND;
+ }
+ if (*type == SID_NAME_UNKNOWN) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* convert a sid to a user or group name. The sid is guaranteed to be in the domain
+ given */
+NTSTATUS wb_cache_sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ NTSTATUS status;
+ bool old_status;
+
+ old_status = domain->online;
+ status = wcache_sid_to_name(domain, sid, mem_ctx, domain_name, name,
+ type);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return status;
+ }
+
+ *name = NULL;
+ *domain_name = NULL;
+
+ DBG_DEBUG("sid_to_name: [Cached] - doing backend query for name for domain %s\n",
+ domain->name );
+
+ winbindd_domain_init_backend(domain);
+
+ status = domain->backend->sid_to_name(domain, mem_ctx, sid, domain_name, name, type);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (!domain->internal &&
+ !domain->online &&
+ old_status) {
+ NTSTATUS cache_status;
+ cache_status = wcache_sid_to_name(domain, sid, mem_ctx,
+ domain_name, name, type);
+ return cache_status;
+ }
+ }
+ /* and save it */
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ wcache_save_sid_to_name(domain, status, sid, *domain_name, *name, *type);
+
+ /* We can't save the name to sid mapping here, as with sid history a
+ * later name2sid would give the wrong sid. */
+
+ return status;
+}
+
+NTSTATUS wb_cache_rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ size_t i;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ bool have_mapped;
+ bool have_unmapped;
+ bool old_status;
+
+ old_status = domain->online;
+ *domain_name = NULL;
+ *names = NULL;
+ *types = NULL;
+
+ if (!cache->tdb) {
+ goto do_query;
+ }
+
+ if (num_rids == 0) {
+ return NT_STATUS_OK;
+ }
+
+ *names = talloc_array(mem_ctx, char *, num_rids);
+ *types = talloc_array(mem_ctx, enum lsa_SidType, num_rids);
+
+ if ((*names == NULL) || (*types == NULL)) {
+ result = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ have_mapped = have_unmapped = false;
+
+ for (i=0; i<num_rids; i++) {
+ struct dom_sid sid;
+ NTSTATUS status;
+ enum lsa_SidType type;
+ char *dom, *name;
+
+ if (!sid_compose(&sid, domain_sid, rids[i])) {
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ status = wcache_sid_to_name(domain, &sid, *names, &dom,
+ &name, &type);
+
+ (*types)[i] = SID_NAME_UNKNOWN;
+ (*names)[i] = talloc_strdup(*names, "");
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ /* not cached */
+ goto do_query;
+ }
+
+ if (NT_STATUS_IS_OK(status)) {
+ have_mapped = true;
+ (*types)[i] = type;
+
+ if (*domain_name == NULL) {
+ *domain_name = dom;
+ } else {
+ TALLOC_FREE(dom);
+ }
+
+ (*names)[i] = name;
+
+ } else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ have_unmapped = true;
+ } else {
+ /* something's definitely wrong */
+ result = status;
+ goto error;
+ }
+ }
+
+ if (!have_mapped) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+ if (!have_unmapped) {
+ return NT_STATUS_OK;
+ }
+ return STATUS_SOME_UNMAPPED;
+
+ do_query:
+
+ TALLOC_FREE(*names);
+ TALLOC_FREE(*types);
+
+ result = domain->backend->rids_to_names(domain, mem_ctx, domain_sid,
+ rids, num_rids, domain_name,
+ names, types);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (cache->tdb &&
+ !domain->internal &&
+ !domain->online &&
+ old_status) {
+ have_mapped = have_unmapped = false;
+
+ *names = talloc_array(mem_ctx, char *, num_rids);
+ if (*names == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ *types = talloc_array(mem_ctx, enum lsa_SidType,
+ num_rids);
+ if (*types == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto error;
+ }
+
+ for (i=0; i<num_rids; i++) {
+ struct dom_sid sid;
+ NTSTATUS status;
+ enum lsa_SidType type;
+ char *dom, *name;
+
+ if (!sid_compose(&sid, domain_sid, rids[i])) {
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ status = wcache_sid_to_name(domain, &sid,
+ *names, &dom,
+ &name, &type);
+
+ (*types)[i] = SID_NAME_UNKNOWN;
+ (*names)[i] = talloc_strdup(*names, "");
+
+ if (NT_STATUS_IS_OK(status)) {
+ have_mapped = true;
+ (*types)[i] = type;
+
+ if (*domain_name == NULL) {
+ *domain_name = dom;
+ } else {
+ TALLOC_FREE(dom);
+ }
+
+ (*names)[i] = name;
+
+ } else if (NT_STATUS_EQUAL(
+ status,
+ NT_STATUS_NONE_MAPPED)) {
+ have_unmapped = true;
+ } else {
+ /* something's definitely wrong */
+ result = status;
+ goto error;
+ }
+ }
+
+ if (!have_mapped) {
+ return NT_STATUS_NONE_MAPPED;
+ }
+ if (!have_unmapped) {
+ return NT_STATUS_OK;
+ }
+ return STATUS_SOME_UNMAPPED;
+ }
+ }
+ /*
+ None of the queried rids has been found so save all negative entries
+ */
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NONE_MAPPED)) {
+ for (i = 0; i < num_rids; i++) {
+ struct dom_sid sid;
+ const char *name = "";
+ const enum lsa_SidType type = SID_NAME_UNKNOWN;
+ NTSTATUS status = NT_STATUS_NONE_MAPPED;
+
+ if (!sid_compose(&sid, domain_sid, rids[i])) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ wcache_save_sid_to_name(domain, status, &sid, *domain_name,
+ name, type);
+ }
+
+ return result;
+ }
+
+ /*
+ Some or all of the queried rids have been found.
+ */
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) {
+ return result;
+ }
+
+ refresh_sequence_number(domain);
+
+ for (i=0; i<num_rids; i++) {
+ struct dom_sid sid;
+ NTSTATUS status;
+
+ if (!sid_compose(&sid, domain_sid, rids[i])) {
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto error;
+ }
+
+ status = (*types)[i] == SID_NAME_UNKNOWN ?
+ NT_STATUS_NONE_MAPPED : NT_STATUS_OK;
+
+ wcache_save_sid_to_name(domain, status, &sid, *domain_name,
+ (*names)[i], (*types)[i]);
+ }
+
+ return result;
+
+ error:
+ TALLOC_FREE(*names);
+ TALLOC_FREE(*types);
+ return result;
+}
+
+static NTSTATUS wcache_query_user(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ struct wbint_userinfo *info)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ struct dom_sid_buf sid_string;
+
+ if (cache->tdb == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ centry = wcache_fetch(
+ cache, domain, "U/%s", dom_sid_str_buf(user_sid, &sid_string));
+ if (centry == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ /* if status is not ok then this is a negative hit
+ and the rest of the data doesn't matter */
+ status = centry->status;
+ if (NT_STATUS_IS_OK(status)) {
+ info->domain_name = centry_string(centry, mem_ctx);
+ info->acct_name = centry_string(centry, mem_ctx);
+ info->full_name = centry_string(centry, mem_ctx);
+ info->homedir = centry_string(centry, mem_ctx);
+ info->shell = centry_string(centry, mem_ctx);
+ info->uid = centry_uint32(centry);
+ info->primary_gid = centry_uint32(centry);
+ info->primary_group_name = centry_string(centry, mem_ctx);
+ centry_sid(centry, &info->user_sid);
+ centry_sid(centry, &info->group_sid);
+ }
+
+ DBG_DEBUG("query_user: [Cached] - cached info for domain %s status: "
+ "%s\n", domain->name, nt_errstr(status) );
+
+ centry_free(centry);
+ return status;
+}
+
+
+/**
+* @brief Query a fullname from the username cache (for further gecos processing)
+*
+* @param domain A pointer to the winbindd_domain struct.
+* @param mem_ctx The talloc context.
+* @param user_sid The user sid.
+* @param full_name A pointer to the full_name string.
+*
+* @return NTSTATUS code
+*/
+NTSTATUS wcache_query_user_fullname(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ const char **full_name)
+{
+ NTSTATUS status;
+ struct wbint_userinfo info;
+
+ status = wcache_query_user(domain, mem_ctx, user_sid, &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (info.full_name != NULL) {
+ *full_name = talloc_strdup(mem_ctx, info.full_name);
+ if (*full_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS wcache_lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ uint32_t i, num_sids;
+ struct dom_sid *sids;
+ struct dom_sid_buf sid_string;
+
+ if (cache->tdb == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ centry = wcache_fetch(
+ cache,
+ domain,
+ "UG/%s",
+ dom_sid_str_buf(user_sid, &sid_string));
+ if (centry == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ num_sids = centry_uint32(centry);
+ sids = talloc_array(mem_ctx, struct dom_sid, num_sids);
+ if (sids == NULL) {
+ centry_free(centry);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_sids; i++) {
+ centry_sid(centry, &sids[i]);
+ }
+
+ status = centry->status;
+
+ DBG_DEBUG("lookup_usergroups: [Cached] - cached info for domain %s "
+ "status: %s\n", domain->name, nt_errstr(status));
+
+ centry_free(centry);
+
+ *pnum_sids = num_sids;
+ *psids = sids;
+ return status;
+}
+
+/* Lookup groups a user is a member of. */
+NTSTATUS wb_cache_lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *num_groups,
+ struct dom_sid **user_gids)
+{
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ struct dom_sid_buf sid_string;
+ bool old_status;
+
+ old_status = domain->online;
+ status = wcache_lookup_usergroups(domain, mem_ctx, user_sid,
+ num_groups, user_gids);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return status;
+ }
+
+ (*num_groups) = 0;
+ (*user_gids) = NULL;
+
+ DBG_DEBUG("lookup_usergroups: [Cached] - doing backend query for info for domain %s\n",
+ domain->name );
+
+ status = domain->backend->lookup_usergroups(domain, mem_ctx, user_sid, num_groups, user_gids);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (!domain->internal &&
+ !domain->online &&
+ old_status) {
+ NTSTATUS cache_status;
+ cache_status = wcache_lookup_usergroups(domain, mem_ctx, user_sid,
+ num_groups, user_gids);
+ return cache_status;
+ }
+ }
+ if ( NT_STATUS_EQUAL(status, NT_STATUS_SYNCHRONIZATION_REQUIRED) )
+ goto skip_save;
+
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+
+ centry_put_uint32(centry, *num_groups);
+ for (i=0; i<(*num_groups); i++) {
+ centry_put_sid(centry, &(*user_gids)[i]);
+ }
+
+ centry_end(centry, "UG/%s", dom_sid_str_buf(user_sid, &sid_string));
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+static char *wcache_make_sidlist(TALLOC_CTX *mem_ctx, uint32_t num_sids,
+ const struct dom_sid *sids)
+{
+ uint32_t i;
+ char *sidlist;
+
+ sidlist = talloc_strdup(mem_ctx, "");
+ if (sidlist == NULL) {
+ return NULL;
+ }
+ for (i=0; i<num_sids; i++) {
+ struct dom_sid_buf tmp;
+ sidlist = talloc_asprintf_append_buffer(
+ sidlist,
+ "/%s",
+ dom_sid_str_buf(&sids[i], &tmp));
+ if (sidlist == NULL) {
+ return NULL;
+ }
+ }
+ return sidlist;
+}
+
+static NTSTATUS wcache_lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *pnum_aliases,
+ uint32_t **paliases)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ uint32_t i, num_aliases;
+ uint32_t *aliases;
+ NTSTATUS status;
+ char *sidlist;
+
+ if (cache->tdb == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ if (num_sids == 0) {
+ *pnum_aliases = 0;
+ *paliases = NULL;
+ return NT_STATUS_OK;
+ }
+
+ /* We need to cache indexed by the whole list of SIDs, the aliases
+ * resulting might come from any of the SIDs. */
+
+ sidlist = wcache_make_sidlist(talloc_tos(), num_sids, sids);
+ if (sidlist == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ centry = wcache_fetch(cache, domain, "UA%s", sidlist);
+ TALLOC_FREE(sidlist);
+ if (centry == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ num_aliases = centry_uint32(centry);
+ aliases = talloc_array(mem_ctx, uint32_t, num_aliases);
+ if (aliases == NULL) {
+ centry_free(centry);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_aliases; i++) {
+ aliases[i] = centry_uint32(centry);
+ }
+
+ status = centry->status;
+
+ DBG_DEBUG("lookup_useraliases: [Cached] - cached info for domain: %s "
+ "status %s\n", domain->name, nt_errstr(status));
+
+ centry_free(centry);
+
+ *pnum_aliases = num_aliases;
+ *paliases = aliases;
+
+ return status;
+}
+
+NTSTATUS wb_cache_lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *num_aliases,
+ uint32_t **alias_rids)
+{
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ char *sidlist;
+ uint32_t i;
+ bool old_status;
+
+ old_status = domain->online;
+ status = wcache_lookup_useraliases(domain, mem_ctx, num_sids, sids,
+ num_aliases, alias_rids);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return status;
+ }
+
+ (*num_aliases) = 0;
+ (*alias_rids) = NULL;
+
+ DBG_DEBUG("lookup_usergroups: [Cached] - doing backend query for info "
+ "for domain %s\n", domain->name );
+
+ sidlist = wcache_make_sidlist(talloc_tos(), num_sids, sids);
+ if (sidlist == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = domain->backend->lookup_useraliases(domain, mem_ctx,
+ num_sids, sids,
+ num_aliases, alias_rids);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (!domain->internal &&
+ !domain->online &&
+ old_status) {
+ NTSTATUS cache_status;
+ cache_status = wcache_lookup_useraliases(domain, mem_ctx, num_sids,
+ sids, num_aliases, alias_rids);
+ return cache_status;
+ }
+ }
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+ centry_put_uint32(centry, *num_aliases);
+ for (i=0; i<(*num_aliases); i++)
+ centry_put_uint32(centry, (*alias_rids)[i]);
+ centry_end(centry, "UA%s", sidlist);
+ centry_free(centry);
+
+ skip_save:
+ return status;
+}
+
+static NTSTATUS wcache_lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ struct dom_sid_buf sid_string;
+
+ if (cache->tdb == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ centry = wcache_fetch(cache,
+ domain,
+ "AM/%s",
+ dom_sid_str_buf(group_sid, &sid_string));
+ if (centry == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ *sid_mem = NULL;
+
+ *num_names = centry_uint32(centry);
+ if (*num_names == 0) {
+ centry_free(centry);
+ return NT_STATUS_OK;
+ }
+
+ *sid_mem = talloc_array(mem_ctx, struct dom_sid, *num_names);
+ if (*sid_mem == NULL) {
+ TALLOC_FREE(*sid_mem);
+ centry_free(centry);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < (*num_names); i++) {
+ centry_sid(centry, &(*sid_mem)[i]);
+ }
+
+ status = centry->status;
+
+ D_DEBUG("[Cached] - cached info for domain %s "
+ "status: %s\n",
+ domain->name,
+ nt_errstr(status));
+
+ centry_free(centry);
+ return status;
+}
+
+NTSTATUS wb_cache_lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_sids,
+ struct dom_sid **sid_mem)
+{
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ struct dom_sid_buf sid_string;
+ bool old_status;
+
+ old_status = domain->online;
+ status = wcache_lookup_aliasmem(domain,
+ mem_ctx,
+ group_sid,
+ num_sids,
+ sid_mem);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return status;
+ }
+
+ (*num_sids) = 0;
+ (*sid_mem) = NULL;
+
+ D_DEBUG("[Cached] - doing backend query for info for domain %s\n",
+ domain->name);
+
+ status = domain->backend->lookup_aliasmem(domain,
+ mem_ctx,
+ group_sid,
+ type,
+ num_sids,
+ sid_mem);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (!domain->internal && !domain->online && old_status) {
+ NTSTATUS cache_status;
+ cache_status = wcache_lookup_aliasmem(domain,
+ mem_ctx,
+ group_sid,
+ num_sids,
+ sid_mem);
+ return cache_status;
+ }
+ }
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+ centry_put_uint32(centry, *num_sids);
+ for (i = 0; i < (*num_sids); i++) {
+ centry_put_sid(centry, &(*sid_mem)[i]);
+ }
+ centry_end(centry, "AM/%s", dom_sid_str_buf(group_sid, &sid_string));
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+static NTSTATUS wcache_lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem, char ***names,
+ uint32_t **name_types)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ struct dom_sid_buf sid_string;
+
+ if (cache->tdb == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ centry = wcache_fetch(
+ cache,
+ domain,
+ "GM/%s",
+ dom_sid_str_buf(group_sid, &sid_string));
+ if (centry == NULL) {
+ return NT_STATUS_NOT_FOUND;
+ }
+
+ *sid_mem = NULL;
+ *names = NULL;
+ *name_types = NULL;
+
+ *num_names = centry_uint32(centry);
+ if (*num_names == 0) {
+ centry_free(centry);
+ return NT_STATUS_OK;
+ }
+
+ *sid_mem = talloc_array(mem_ctx, struct dom_sid, *num_names);
+ *names = talloc_array(mem_ctx, char *, *num_names);
+ *name_types = talloc_array(mem_ctx, uint32_t, *num_names);
+
+ if ((*sid_mem == NULL) || (*names == NULL) || (*name_types == NULL)) {
+ TALLOC_FREE(*sid_mem);
+ TALLOC_FREE(*names);
+ TALLOC_FREE(*name_types);
+ centry_free(centry);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<(*num_names); i++) {
+ centry_sid(centry, &(*sid_mem)[i]);
+ (*names)[i] = centry_string(centry, mem_ctx);
+ (*name_types)[i] = centry_uint32(centry);
+ }
+
+ status = centry->status;
+
+ DBG_DEBUG("lookup_groupmem: [Cached] - cached info for domain %s "
+ "status: %s\n", domain->name, nt_errstr(status));
+
+ centry_free(centry);
+ return status;
+}
+
+NTSTATUS wb_cache_lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem,
+ char ***names,
+ uint32_t **name_types)
+{
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ unsigned int i;
+ struct dom_sid_buf sid_string;
+ bool old_status;
+
+ old_status = domain->online;
+ status = wcache_lookup_groupmem(domain, mem_ctx, group_sid, num_names,
+ sid_mem, names, name_types);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) {
+ return status;
+ }
+
+ (*num_names) = 0;
+ (*sid_mem) = NULL;
+ (*names) = NULL;
+ (*name_types) = NULL;
+
+ DBG_DEBUG("lookup_groupmem: [Cached] - doing backend query for info for domain %s\n",
+ domain->name );
+
+ status = domain->backend->lookup_groupmem(domain, mem_ctx, group_sid,
+ type, num_names,
+ sid_mem, names, name_types);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (!domain->internal &&
+ !domain->online &&
+ old_status) {
+ NTSTATUS cache_status;
+ cache_status = wcache_lookup_groupmem(domain, mem_ctx, group_sid,
+ num_names, sid_mem, names,
+ name_types);
+ return cache_status;
+ }
+ }
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ centry = centry_start(domain, status);
+ if (!centry)
+ goto skip_save;
+ centry_put_uint32(centry, *num_names);
+ for (i=0; i<(*num_names); i++) {
+ centry_put_sid(centry, &(*sid_mem)[i]);
+ centry_put_string(centry, (*names)[i]);
+ centry_put_uint32(centry, (*name_types)[i]);
+ }
+ centry_end(centry,
+ "GM/%s",
+ dom_sid_str_buf(group_sid, &sid_string));
+ centry_free(centry);
+
+skip_save:
+ return status;
+}
+
+/* find the sequence number for a domain */
+NTSTATUS wb_cache_sequence_number(struct winbindd_domain *domain,
+ uint32_t *seq)
+{
+ refresh_sequence_number(domain);
+
+ *seq = domain->sequence_number;
+
+ return NT_STATUS_OK;
+}
+
+/* enumerate trusted domains
+ * (we need to have the list of trustdoms in the cache when we go offline) -
+ * Guenther */
+NTSTATUS wb_cache_trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *trusts)
+{
+ NTSTATUS status;
+ struct winbind_cache *cache;
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_domains = 0;
+ bool retval = false;
+ size_t i;
+ bool old_status;
+
+ old_status = domain->online;
+ trusts->count = 0;
+ trusts->array = NULL;
+
+ cache = get_cache(domain);
+ if (!cache || !cache->tdb) {
+ goto do_query;
+ }
+
+ if (domain->online) {
+ goto do_query;
+ }
+
+ retval = wcache_tdc_fetch_list(&dom_list, &num_domains);
+ if (!retval || !num_domains || !dom_list) {
+ TALLOC_FREE(dom_list);
+ goto do_query;
+ }
+
+do_fetch_cache:
+ trusts->array = talloc_zero_array(mem_ctx, struct netr_DomainTrust, num_domains);
+ if (!trusts->array) {
+ TALLOC_FREE(dom_list);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < num_domains; i++) {
+ struct netr_DomainTrust *trust;
+ struct dom_sid *sid;
+ struct winbindd_domain *dom;
+
+ dom = find_domain_from_name_noinit(dom_list[i].domain_name);
+ if (dom && dom->internal) {
+ continue;
+ }
+
+ trust = &trusts->array[trusts->count];
+ trust->netbios_name = talloc_strdup(trusts->array, dom_list[i].domain_name);
+ trust->dns_name = talloc_strdup(trusts->array, dom_list[i].dns_name);
+ sid = talloc(trusts->array, struct dom_sid);
+ if (!trust->netbios_name || !trust->dns_name ||
+ !sid) {
+ TALLOC_FREE(dom_list);
+ TALLOC_FREE(trusts->array);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ trust->trust_flags = dom_list[i].trust_flags;
+ trust->trust_attributes = dom_list[i].trust_attribs;
+ trust->trust_type = dom_list[i].trust_type;
+ sid_copy(sid, &dom_list[i].sid);
+ trust->sid = sid;
+ trusts->count++;
+ }
+
+ TALLOC_FREE(dom_list);
+ return NT_STATUS_OK;
+
+do_query:
+ DBG_DEBUG("trusted_domains: [Cached] - doing backend query for info for domain %s\n",
+ domain->name );
+
+ status = domain->backend->trusted_domains(domain, mem_ctx, trusts);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (!domain->internal &&
+ !domain->online &&
+ old_status) {
+ retval = wcache_tdc_fetch_list(&dom_list, &num_domains);
+ if (retval && num_domains && dom_list) {
+ TALLOC_FREE(trusts->array);
+ trusts->count = 0;
+ goto do_fetch_cache;
+ }
+ }
+ }
+ /* no trusts gives NT_STATUS_NO_MORE_ENTRIES resetting to NT_STATUS_OK
+ * so that the generic centry handling still applies correctly -
+ * Guenther*/
+
+ if (!NT_STATUS_IS_ERR(status)) {
+ status = NT_STATUS_OK;
+ }
+ return status;
+}
+
+/* get lockout policy */
+NTSTATUS wb_cache_lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *policy)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ bool old_status;
+
+ old_status = domain->online;
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name);
+
+ if (!centry)
+ goto do_query;
+
+do_fetch_cache:
+ policy->lockout_duration = centry_nttime(centry);
+ policy->lockout_window = centry_nttime(centry);
+ policy->lockout_threshold = centry_uint16(centry);
+
+ status = centry->status;
+
+ DBG_DEBUG("lockout_policy: [Cached] - cached info for domain %s status: %s\n",
+ domain->name, nt_errstr(status) );
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ ZERO_STRUCTP(policy);
+
+ DBG_DEBUG("lockout_policy: [Cached] - doing backend query for info for domain %s\n",
+ domain->name );
+
+ status = domain->backend->lockout_policy(domain, mem_ctx, policy);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (cache->tdb &&
+ !domain->internal &&
+ !domain->online &&
+ old_status) {
+ centry = wcache_fetch(cache, domain, "LOC_POL/%s", domain->name);
+ if (centry) {
+ goto do_fetch_cache;
+ }
+ }
+ }
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ wcache_save_lockout_policy(domain, status, policy);
+
+ return status;
+}
+
+/* get password policy */
+NTSTATUS wb_cache_password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *policy)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ struct cache_entry *centry = NULL;
+ NTSTATUS status;
+ bool old_status;
+
+ old_status = domain->online;
+ if (!cache->tdb)
+ goto do_query;
+
+ centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name);
+
+ if (!centry)
+ goto do_query;
+
+do_fetch_cache:
+ policy->min_password_length = centry_uint16(centry);
+ policy->password_history_length = centry_uint16(centry);
+ policy->password_properties = centry_uint32(centry);
+ policy->max_password_age = centry_nttime(centry);
+ policy->min_password_age = centry_nttime(centry);
+
+ status = centry->status;
+
+ DBG_DEBUG("lockout_policy: [Cached] - cached info for domain %s status: %s\n",
+ domain->name, nt_errstr(status) );
+
+ centry_free(centry);
+ return status;
+
+do_query:
+ ZERO_STRUCTP(policy);
+
+ DBG_DEBUG("password_policy: [Cached] - doing backend query for info for domain %s\n",
+ domain->name );
+
+ status = domain->backend->password_policy(domain, mem_ctx, policy);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ if (!domain->internal && old_status) {
+ set_domain_offline(domain);
+ }
+ if (cache->tdb &&
+ !domain->internal &&
+ !domain->online &&
+ old_status) {
+ centry = wcache_fetch(cache, domain, "PWD_POL/%s", domain->name);
+ if (centry) {
+ goto do_fetch_cache;
+ }
+ }
+ }
+ /* and save it */
+ refresh_sequence_number(domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ wcache_save_password_policy(domain, status, policy);
+
+ return status;
+}
+
+
+/* Invalidate cached user and group lists coherently */
+
+static int traverse_fn(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf,
+ void *state)
+{
+ if (strncmp((const char *)kbuf.dptr, "UL/", 3) == 0 ||
+ strncmp((const char *)kbuf.dptr, "GL/", 3) == 0)
+ tdb_delete(the_tdb, kbuf);
+
+ return 0;
+}
+
+/* Invalidate the getpwnam and getgroups entries for a winbindd domain */
+
+void wcache_invalidate_samlogon(struct winbindd_domain *domain,
+ const struct dom_sid *sid)
+{
+ fstring key_str;
+ struct dom_sid_buf sid_string;
+ struct winbind_cache *cache;
+
+ /* don't clear cached U/SID and UG/SID entries when we want to logon
+ * offline - gd */
+
+ if (lp_winbind_offline_logon()) {
+ return;
+ }
+
+ if (!domain)
+ return;
+
+ cache = get_cache(domain);
+
+ if (!cache->tdb) {
+ return;
+ }
+
+ /* Clear U/SID cache entry */
+ fstr_sprintf(key_str, "U/%s", dom_sid_str_buf(sid, &sid_string));
+ DBG_DEBUG("wcache_invalidate_samlogon: clearing %s\n", key_str);
+ tdb_delete(cache->tdb, string_tdb_data(key_str));
+
+ /* Clear UG/SID cache entry */
+ fstr_sprintf(key_str, "UG/%s", dom_sid_str_buf(sid, &sid_string));
+ DBG_DEBUG("wcache_invalidate_samlogon: clearing %s\n", key_str);
+ tdb_delete(cache->tdb, string_tdb_data(key_str));
+
+ /* Samba/winbindd never needs this. */
+ netsamlogon_clear_cached_user(sid);
+}
+
+bool wcache_invalidate_cache(void)
+{
+ struct winbindd_domain *domain;
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ struct winbind_cache *cache = get_cache(domain);
+
+ DBG_DEBUG("wcache_invalidate_cache: invalidating cache "
+ "entries for %s\n", domain->name);
+ if (cache) {
+ if (cache->tdb) {
+ tdb_traverse(cache->tdb, traverse_fn, NULL);
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool wcache_invalidate_cache_noinit(void)
+{
+ struct winbindd_domain *domain;
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ struct winbind_cache *cache;
+
+ /* Skip uninitialized domains. */
+ if (!domain->initialized && !domain->internal) {
+ continue;
+ }
+
+ cache = get_cache(domain);
+
+ DBG_DEBUG("wcache_invalidate_cache: invalidating cache "
+ "entries for %s\n", domain->name);
+ if (cache) {
+ if (cache->tdb) {
+ tdb_traverse(cache->tdb, traverse_fn, NULL);
+ /*
+ * Flushing cache has nothing to with domains.
+ * return here if we successfully flushed once.
+ * To avoid unnecessary traversing the cache.
+ */
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static bool init_wcache(void)
+{
+ char *db_path;
+
+ if (wcache == NULL) {
+ wcache = SMB_XMALLOC_P(struct winbind_cache);
+ ZERO_STRUCTP(wcache);
+ }
+
+ if (wcache->tdb != NULL)
+ return true;
+
+ db_path = wcache_path();
+ if (db_path == NULL) {
+ return false;
+ }
+
+ /* when working offline we must not clear the cache on restart */
+ wcache->tdb = tdb_open_log(db_path,
+ WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE,
+ TDB_INCOMPATIBLE_HASH |
+ (lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST)),
+ O_RDWR|O_CREAT, 0600);
+ TALLOC_FREE(db_path);
+ if (wcache->tdb == NULL) {
+ DBG_ERR("Failed to open winbindd_cache.tdb!\n");
+ return false;
+ }
+
+ return true;
+}
+
+/************************************************************************
+ This is called by the parent to initialize the cache file.
+ We don't need sophisticated locking here as we know we're the
+ only opener.
+************************************************************************/
+
+bool initialize_winbindd_cache(void)
+{
+ bool cache_bad = false;
+ uint32_t vers = 0;
+ bool ok;
+
+ if (!init_wcache()) {
+ DBG_ERR("initialize_winbindd_cache: init_wcache failed.\n");
+ return false;
+ }
+
+ /* Check version number. */
+ ok = tdb_fetch_uint32(wcache->tdb, WINBINDD_CACHE_VERSION_KEYSTR, &vers);
+ if (!ok) {
+ DBG_DEBUG("Failed to get cache version\n");
+ cache_bad = true;
+ }
+ if (vers != WINBINDD_CACHE_VERSION) {
+ DBG_DEBUG("Invalid cache version %u != %u\n",
+ vers,
+ WINBINDD_CACHE_VERSION);
+ cache_bad = true;
+ }
+
+ if (cache_bad) {
+ char *db_path;
+
+ DBG_NOTICE("initialize_winbindd_cache: clearing cache "
+ "and re-creating with version number %d\n",
+ WINBINDD_CACHE_VERSION);
+
+ tdb_close(wcache->tdb);
+ wcache->tdb = NULL;
+
+ db_path = wcache_path();
+ if (db_path == NULL) {
+ return false;
+ }
+
+ if (unlink(db_path) == -1) {
+ DBG_ERR("initialize_winbindd_cache: unlink %s failed %s\n",
+ db_path,
+ strerror(errno) );
+ TALLOC_FREE(db_path);
+ return false;
+ }
+ TALLOC_FREE(db_path);
+ if (!init_wcache()) {
+ DBG_ERR("initialize_winbindd_cache: re-initialization "
+ "init_wcache failed.\n");
+ return false;
+ }
+
+ /* Write the version. */
+ if (!tdb_store_uint32(wcache->tdb, WINBINDD_CACHE_VERSION_KEYSTR, WINBINDD_CACHE_VERSION)) {
+ DBG_ERR("initialize_winbindd_cache: version number store failed %s\n",
+ tdb_errorstr(wcache->tdb) );
+ return false;
+ }
+ }
+
+ tdb_close(wcache->tdb);
+ wcache->tdb = NULL;
+ return true;
+}
+
+void close_winbindd_cache(void)
+{
+ if (!wcache) {
+ return;
+ }
+ if (wcache->tdb) {
+ tdb_close(wcache->tdb);
+ wcache->tdb = NULL;
+ }
+}
+
+bool lookup_cached_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ char **domain_name, char **name,
+ enum lsa_SidType *type)
+{
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+
+ domain = find_lookup_domain_from_sid(sid);
+ if (domain == NULL) {
+ return false;
+ }
+ status = wcache_sid_to_name(domain, sid, mem_ctx, domain_name, name,
+ type);
+ return NT_STATUS_IS_OK(status);
+}
+
+bool lookup_cached_name(const char *namespace,
+ const char *domain_name,
+ const char *name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ bool original_online_state;
+
+ domain = find_lookup_domain_from_name(namespace);
+ if (domain == NULL) {
+ return false;
+ }
+
+ /* If we are doing a cached logon, temporarily set the domain
+ offline so the cache won't expire the entry */
+
+ original_online_state = domain->online;
+ domain->online = false;
+ status = wcache_name_to_sid(domain, domain_name, name, sid, type);
+ domain->online = original_online_state;
+
+ return NT_STATUS_IS_OK(status);
+}
+
+/*
+ * Cache a name to sid without checking the sequence number.
+ * Used when caching from a trusted PAC.
+ */
+
+void cache_name2sid_trusted(struct winbindd_domain *domain,
+ const char *domain_name,
+ const char *name,
+ enum lsa_SidType type,
+ const struct dom_sid *sid)
+{
+ /*
+ * Ensure we store the mapping with the
+ * existing sequence number from the cache.
+ */
+ get_cache(domain);
+ (void)fetch_cache_seqnum(domain, time(NULL));
+ wcache_save_name_to_sid(domain,
+ NT_STATUS_OK,
+ domain_name,
+ name,
+ sid,
+ type);
+}
+
+void cache_name2sid(struct winbindd_domain *domain,
+ const char *domain_name, const char *name,
+ enum lsa_SidType type, const struct dom_sid *sid)
+{
+ refresh_sequence_number(domain);
+ wcache_save_name_to_sid(domain, NT_STATUS_OK, domain_name, name,
+ sid, type);
+}
+
+/*
+ * The original idea that this cache only contains centries has
+ * been blurred - now other stuff gets put in here. Ensure we
+ * ignore these things on cleanup.
+ */
+
+static int traverse_fn_cleanup(TDB_CONTEXT *the_tdb, TDB_DATA kbuf,
+ TDB_DATA dbuf, void *state)
+{
+ struct cache_entry *centry;
+
+ if (is_non_centry_key(kbuf)) {
+ return 0;
+ }
+
+ centry = wcache_fetch_raw((char *)kbuf.dptr);
+ if (!centry) {
+ return 0;
+ }
+
+ if (!NT_STATUS_IS_OK(centry->status)) {
+ DBG_DEBUG("deleting centry %s\n", (const char *)kbuf.dptr);
+ tdb_delete(the_tdb, kbuf);
+ }
+
+ centry_free(centry);
+ return 0;
+}
+
+/* flush the cache */
+static void wcache_flush_cache(void)
+{
+ char *db_path;
+
+ if (!wcache)
+ return;
+ if (wcache->tdb) {
+ tdb_close(wcache->tdb);
+ wcache->tdb = NULL;
+ }
+ if (!winbindd_use_cache()) {
+ return;
+ }
+
+ db_path = wcache_path();
+ if (db_path == NULL) {
+ return;
+ }
+
+ /* when working offline we must not clear the cache on restart */
+ wcache->tdb = tdb_open_log(db_path,
+ WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE,
+ TDB_INCOMPATIBLE_HASH |
+ (lp_winbind_offline_logon() ? TDB_DEFAULT : (TDB_DEFAULT | TDB_CLEAR_IF_FIRST)),
+ O_RDWR|O_CREAT, 0600);
+ TALLOC_FREE(db_path);
+ if (!wcache->tdb) {
+ DBG_ERR("Failed to open winbindd_cache.tdb!\n");
+ return;
+ }
+
+ tdb_traverse(wcache->tdb, traverse_fn_cleanup, NULL);
+
+ DBG_DEBUG("wcache_flush_cache success\n");
+}
+
+/* Count cached creds */
+
+static int traverse_fn_cached_creds(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf,
+ void *state)
+{
+ int *cred_count = (int*)state;
+
+ if (strncmp((const char *)kbuf.dptr, "CRED/", 5) == 0) {
+ (*cred_count)++;
+ }
+ return 0;
+}
+
+NTSTATUS wcache_count_cached_creds(struct winbindd_domain *domain, int *count)
+{
+ struct winbind_cache *cache = get_cache(domain);
+
+ *count = 0;
+
+ if (!cache->tdb) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ tdb_traverse(cache->tdb, traverse_fn_cached_creds, (void *)count);
+
+ return NT_STATUS_OK;
+}
+
+struct cred_list {
+ struct cred_list *prev, *next;
+ TDB_DATA key;
+ fstring name;
+ time_t created;
+};
+static struct cred_list *wcache_cred_list;
+
+static int traverse_fn_get_credlist(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf,
+ void *state)
+{
+ struct cred_list *cred;
+
+ if (strncmp((const char *)kbuf.dptr, "CRED/", 5) == 0) {
+
+ cred = SMB_MALLOC_P(struct cred_list);
+ if (cred == NULL) {
+ DBG_ERR("traverse_fn_remove_first_creds: failed to malloc new entry for list\n");
+ return -1;
+ }
+
+ ZERO_STRUCTP(cred);
+
+ /* save a copy of the key */
+
+ fstrcpy(cred->name, (const char *)kbuf.dptr);
+ DLIST_ADD(wcache_cred_list, cred);
+ }
+
+ return 0;
+}
+
+NTSTATUS wcache_remove_oldest_cached_creds(struct winbindd_domain *domain, const struct dom_sid *sid)
+{
+ struct winbind_cache *cache = get_cache(domain);
+ NTSTATUS status;
+ int ret;
+ struct cred_list *cred, *next, *oldest = NULL;
+
+ if (!cache->tdb) {
+ return NT_STATUS_INTERNAL_DB_ERROR;
+ }
+
+ /* we possibly already have an entry */
+ if (sid && NT_STATUS_IS_OK(wcache_cached_creds_exist(domain, sid))) {
+
+ fstring key_str;
+ struct dom_sid_buf tmp;
+
+ DBG_DEBUG("we already have an entry, deleting that\n");
+
+ fstr_sprintf(key_str, "CRED/%s", dom_sid_str_buf(sid, &tmp));
+
+ tdb_delete(cache->tdb, string_tdb_data(key_str));
+
+ return NT_STATUS_OK;
+ }
+
+ ret = tdb_traverse(cache->tdb, traverse_fn_get_credlist, NULL);
+ if (ret == 0) {
+ return NT_STATUS_OK;
+ } else if ((ret < 0) || (wcache_cred_list == NULL)) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ ZERO_STRUCTP(oldest);
+
+ for (cred = wcache_cred_list; cred; cred = cred->next) {
+
+ TDB_DATA data;
+ time_t t;
+
+ data = tdb_fetch(cache->tdb, string_tdb_data(cred->name));
+ if (!data.dptr) {
+ DBG_DEBUG("wcache_remove_oldest_cached_creds: entry for [%s] not found\n",
+ cred->name);
+ status = NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ goto done;
+ }
+
+ t = IVAL(data.dptr, 0);
+ SAFE_FREE(data.dptr);
+
+ if (!oldest) {
+ oldest = SMB_MALLOC_P(struct cred_list);
+ if (oldest == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ fstrcpy(oldest->name, cred->name);
+ oldest->created = t;
+ continue;
+ }
+
+ if (t < oldest->created) {
+ fstrcpy(oldest->name, cred->name);
+ oldest->created = t;
+ }
+ }
+
+ if (tdb_delete(cache->tdb, string_tdb_data(oldest->name)) == 0) {
+ status = NT_STATUS_OK;
+ } else {
+ status = NT_STATUS_UNSUCCESSFUL;
+ }
+done:
+ for (cred = wcache_cred_list; cred; cred = next) {
+ next = cred->next;
+ DLIST_REMOVE(wcache_cred_list, cred);
+ SAFE_FREE(cred);
+ }
+ SAFE_FREE(oldest);
+
+ return status;
+}
+
+/* Change the global online/offline state. */
+bool set_global_winbindd_state_offline(void)
+{
+ bool ok;
+ uint8_t buf[4] = {0};
+ TDB_DATA data = {
+ .dptr = buf,
+ .dsize = sizeof(buf)
+ };
+ int rc;
+
+ DBG_NOTICE("Offline requested\n");
+
+ if (wcache == NULL || wcache->tdb == NULL) {
+ DBG_NOTICE("Winbind cache doesn't exist yet\n");
+ return false;
+ }
+
+ if (!lp_winbind_offline_logon()) {
+ DBG_DEBUG("Rejecting request to set winbind offline, "
+ "offline logons are disabled in smb.conf\n");
+ return false;
+ }
+
+ ok = get_global_winbindd_state_offline();
+ if (ok) {
+ return true;
+ }
+
+ PUSH_LE_U32(buf, 0, time(NULL));
+
+ rc = tdb_store_bystring(wcache->tdb,
+ "WINBINDD_OFFLINE",
+ data,
+ TDB_INSERT);
+ if (rc != 0) {
+ return false;
+ }
+
+ return true;
+
+}
+
+void set_global_winbindd_state_online(void)
+{
+ DBG_DEBUG("set_global_winbindd_state_online: online requested.\n");
+
+ if (!lp_winbind_offline_logon()) {
+ DBG_DEBUG("Rejecting request to set winbind online, "
+ "offline logons are disabled in smb.conf.\n");
+ return;
+ }
+
+ if (!wcache->tdb) {
+ return;
+ }
+
+ /* Ensure there is no key "WINBINDD_OFFLINE" in the cache tdb. */
+ tdb_delete_bystring(wcache->tdb, "WINBINDD_OFFLINE");
+}
+
+bool get_global_winbindd_state_offline(void)
+{
+ TDB_DATA data;
+
+ data = tdb_fetch_bystring(wcache->tdb, "WINBINDD_OFFLINE");
+ if (data.dptr == NULL || data.dsize != 4) {
+ DBG_DEBUG("Offline state not set.\n");
+ SAFE_FREE(data.dptr);
+ return false;
+ }
+
+ return true;
+}
+
+/***********************************************************************
+ Validate functions for all possible cache tdb keys.
+***********************************************************************/
+
+static struct cache_entry *create_centry_validate(const char *kstr, TDB_DATA data,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry;
+
+ centry = SMB_XMALLOC_P(struct cache_entry);
+ centry->data = (unsigned char *)smb_memdup(data.dptr, data.dsize);
+ if (!centry->data) {
+ SAFE_FREE(centry);
+ return NULL;
+ }
+ centry->len = data.dsize;
+ centry->ofs = 0;
+
+ if (centry->len < 16) {
+ /* huh? corrupt cache? */
+ DBG_ERR("create_centry_validate: Corrupt cache for key %s "
+ "(len < 16) ?\n", kstr);
+ centry_free(centry);
+ state->bad_entry = true;
+ state->success = false;
+ return NULL;
+ }
+
+ centry->status = NT_STATUS(centry_uint32(centry));
+ centry->sequence_number = centry_uint32(centry);
+ centry->timeout = centry_uint64_t(centry);
+ return centry;
+}
+
+static int validate_seqnum(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ if (dbuf.dsize != 8) {
+ DBG_ERR("validate_seqnum: Corrupt cache for key %s (len %u != 8) ?\n",
+ keystr, (unsigned int)dbuf.dsize );
+ state->bad_entry = true;
+ return 1;
+ }
+ return 0;
+}
+
+static int validate_u(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+ struct dom_sid sid;
+
+ if (!centry) {
+ return 1;
+ }
+
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_uint32(centry);
+ (void)centry_uint32(centry);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_sid(centry, &sid);
+ (void)centry_sid(centry, &sid);
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_u: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_loc_pol(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+
+ if (!centry) {
+ return 1;
+ }
+
+ (void)centry_nttime(centry);
+ (void)centry_nttime(centry);
+ (void)centry_uint16(centry);
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_loc_pol: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_pwd_pol(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+
+ if (!centry) {
+ return 1;
+ }
+
+ (void)centry_uint16(centry);
+ (void)centry_uint16(centry);
+ (void)centry_uint32(centry);
+ (void)centry_nttime(centry);
+ (void)centry_nttime(centry);
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_pwd_pol: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_cred(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+
+ if (!centry) {
+ return 1;
+ }
+
+ (void)centry_time(centry);
+ (void)centry_hash16(centry, mem_ctx);
+
+ /* We only have 17 bytes more data in the salted cred case. */
+ if (centry->len - centry->ofs == 17) {
+ (void)centry_hash16(centry, mem_ctx);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_cred: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_ul(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+ int32_t num_entries, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_entries = (int32_t)centry_uint32(centry);
+
+ for (i=0; i< num_entries; i++) {
+ (void)centry_uint32(centry);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_ul: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_gl(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+ int32_t num_entries, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_entries = centry_uint32(centry);
+
+ for (i=0; i< num_entries; i++) {
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_uint32(centry);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_gl: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_ug(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+ int32_t num_groups, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_groups = centry_uint32(centry);
+
+ for (i=0; i< num_groups; i++) {
+ struct dom_sid sid;
+ centry_sid(centry, &sid);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_ug: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_ua(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+ int32_t num_aliases, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_aliases = centry_uint32(centry);
+
+ for (i=0; i < num_aliases; i++) {
+ (void)centry_uint32(centry);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_ua: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_gm(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+ int32_t num_names, i;
+
+ if (!centry) {
+ return 1;
+ }
+
+ num_names = centry_uint32(centry);
+
+ for (i=0; i< num_names; i++) {
+ struct dom_sid sid;
+ centry_sid(centry, &sid);
+ (void)centry_string(centry, mem_ctx);
+ (void)centry_uint32(centry);
+ }
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_gm: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_dr(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ /* Can't say anything about this other than must be nonzero. */
+ if (dbuf.dsize == 0) {
+ DBG_ERR("validate_dr: Corrupt cache for key %s (len == 0) ?\n",
+ keystr);
+ state->bad_entry = true;
+ state->success = false;
+ return 1;
+ }
+
+ DBG_DEBUG("validate_dr: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_de(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ /* Can't say anything about this other than must be nonzero. */
+ if (dbuf.dsize == 0) {
+ DBG_ERR("validate_de: Corrupt cache for key %s (len == 0) ?\n",
+ keystr);
+ state->bad_entry = true;
+ state->success = false;
+ return 1;
+ }
+
+ DBG_DEBUG("validate_de: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_nss_an(TALLOC_CTX *mem_ctx, const char *keystr,
+ TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+
+ if (!centry) {
+ return 1;
+ }
+
+ (void)centry_string( centry, mem_ctx );
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("validate_pwinfo: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_nss_na(TALLOC_CTX *mem_ctx, const char *keystr,
+ TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ struct cache_entry *centry = create_centry_validate(keystr, dbuf, state);
+
+ if (!centry) {
+ return 1;
+ }
+
+ (void)centry_string( centry, mem_ctx );
+
+ centry_free(centry);
+
+ if (!(state->success)) {
+ return 1;
+ }
+ DBG_DEBUG("%s ok\n", keystr);
+ return 0;
+}
+
+static int validate_trustdomcache(TALLOC_CTX *mem_ctx, const char *keystr,
+ TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ if (dbuf.dsize == 0) {
+ DBG_ERR("validate_trustdomcache: Corrupt cache for "
+ "key %s (len ==0) ?\n", keystr);
+ state->bad_entry = true;
+ state->success = false;
+ return 1;
+ }
+
+ DBG_DEBUG("validate_trustdomcache: %s ok\n"
+ " Don't trust me, I am a DUMMY!\n",
+ keystr);
+ return 0;
+}
+
+static int validate_offline(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ if (dbuf.dsize != 4) {
+ DBG_ERR("validate_offline: Corrupt cache for key %s (len %u != 4) ?\n",
+ keystr, (unsigned int)dbuf.dsize );
+ state->bad_entry = true;
+ state->success = false;
+ return 1;
+ }
+ DBG_DEBUG("validate_offline: %s ok\n", keystr);
+ return 0;
+}
+
+static int validate_ndr(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ /*
+ * Ignore validation for now. The proper way to do this is with a
+ * checksum. Just pure parsing does not really catch much.
+ */
+ return 0;
+}
+
+static int validate_cache_version(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf,
+ struct tdb_validation_status *state)
+{
+ if (dbuf.dsize != 4) {
+ DBG_ERR("validate_cache_version: Corrupt cache for "
+ "key %s (len %u != 4) ?\n",
+ keystr, (unsigned int)dbuf.dsize);
+ state->bad_entry = true;
+ state->success = false;
+ return 1;
+ }
+
+ DBG_DEBUG("validate_cache_version: %s ok\n", keystr);
+ return 0;
+}
+
+/***********************************************************************
+ A list of all possible cache tdb keys with associated validation
+ functions.
+***********************************************************************/
+
+struct key_val_struct {
+ const char *keyname;
+ int (*validate_data_fn)(TALLOC_CTX *mem_ctx, const char *keystr, TDB_DATA dbuf, struct tdb_validation_status* state);
+} key_val[] = {
+ {"SEQNUM/", validate_seqnum},
+ {"U/", validate_u},
+ {"LOC_POL/", validate_loc_pol},
+ {"PWD_POL/", validate_pwd_pol},
+ {"CRED/", validate_cred},
+ {"UL/", validate_ul},
+ {"GL/", validate_gl},
+ {"UG/", validate_ug},
+ {"UA", validate_ua},
+ {"GM/", validate_gm},
+ {"DR/", validate_dr},
+ {"DE/", validate_de},
+ {"TRUSTDOMCACHE/", validate_trustdomcache},
+ {"NSS/NA/", validate_nss_na},
+ {"NSS/AN/", validate_nss_an},
+ {"WINBINDD_OFFLINE", validate_offline},
+ {"NDR/", validate_ndr},
+ {WINBINDD_CACHE_VERSION_KEYSTR, validate_cache_version},
+ {NULL, NULL}
+};
+
+/***********************************************************************
+ Function to look at every entry in the tdb and validate it as far as
+ possible.
+***********************************************************************/
+
+static int cache_traverse_validate_fn(TDB_CONTEXT *the_tdb, TDB_DATA kbuf, TDB_DATA dbuf, void *state)
+{
+ int i;
+ unsigned int max_key_len = 1024;
+ struct tdb_validation_status *v_state = (struct tdb_validation_status *)state;
+
+ /* Paranoia check. */
+ if (strncmp("UA/", (const char *)kbuf.dptr, 3) == 0 ||
+ strncmp("NDR/", (const char *)kbuf.dptr, 4) == 0) {
+ max_key_len = 1024 * 1024;
+ }
+ if (kbuf.dsize > max_key_len) {
+ DBG_ERR("cache_traverse_validate_fn: key length too large: "
+ "(%u) > (%u)\n\n",
+ (unsigned int)kbuf.dsize, (unsigned int)max_key_len);
+ return 1;
+ }
+
+ for (i = 0; key_val[i].keyname; i++) {
+ size_t namelen = strlen(key_val[i].keyname);
+ if (kbuf.dsize >= namelen && (
+ strncmp(key_val[i].keyname, (const char *)kbuf.dptr, namelen)) == 0) {
+ TALLOC_CTX *mem_ctx;
+ char *keystr;
+ int ret;
+
+ keystr = SMB_MALLOC_ARRAY(char, kbuf.dsize+1);
+ if (!keystr) {
+ return 1;
+ }
+ memcpy(keystr, kbuf.dptr, kbuf.dsize);
+ keystr[kbuf.dsize] = '\0';
+
+ mem_ctx = talloc_init("validate_ctx");
+ if (!mem_ctx) {
+ SAFE_FREE(keystr);
+ return 1;
+ }
+
+ ret = key_val[i].validate_data_fn(mem_ctx, keystr, dbuf,
+ v_state);
+
+ SAFE_FREE(keystr);
+ talloc_destroy(mem_ctx);
+ return ret;
+ }
+ }
+
+ DBG_ERR("cache_traverse_validate_fn: unknown cache entry\nkey :\n");
+ dump_data(0, (uint8_t *)kbuf.dptr, kbuf.dsize);
+ DBG_ERR("data :\n");
+ dump_data(0, (uint8_t *)dbuf.dptr, dbuf.dsize);
+ v_state->unknown_key = true;
+ v_state->success = false;
+ return 1; /* terminate. */
+}
+
+static void validate_panic(const char *const why)
+{
+ DBG_ERR("validating cache: would panic %s\n"
+ "exiting instead (cache validation mode)\n", why );
+ exit(47);
+}
+
+static int wbcache_update_centry_fn(TDB_CONTEXT *tdb,
+ TDB_DATA key,
+ TDB_DATA data,
+ void *state)
+{
+ uint64_t ctimeout;
+ TDB_DATA blob;
+
+ if (is_non_centry_key(key)) {
+ return 0;
+ }
+
+ if (data.dptr == NULL || data.dsize == 0) {
+ if (tdb_delete(tdb, key) < 0) {
+ DBG_ERR("tdb_delete for [%s] failed!\n",
+ key.dptr);
+ return 1;
+ }
+ }
+
+ /* add timeout to blob (uint64_t) */
+ blob.dsize = data.dsize + 8;
+
+ blob.dptr = SMB_XMALLOC_ARRAY(uint8_t, blob.dsize);
+ if (blob.dptr == NULL) {
+ return 1;
+ }
+ memset(blob.dptr, 0, blob.dsize);
+
+ /* copy status and seqnum */
+ memcpy(blob.dptr, data.dptr, 8);
+
+ /* add timeout */
+ ctimeout = lp_winbind_cache_time() + time(NULL);
+ SBVAL(blob.dptr, 8, ctimeout);
+
+ /* copy the rest */
+ memcpy(blob.dptr + 16, data.dptr + 8, data.dsize - 8);
+
+ if (tdb_store(tdb, key, blob, TDB_REPLACE) < 0) {
+ DBG_ERR("tdb_store to update [%s] failed!\n",
+ key.dptr);
+ SAFE_FREE(blob.dptr);
+ return 1;
+ }
+
+ SAFE_FREE(blob.dptr);
+ return 0;
+}
+
+static bool wbcache_upgrade_v1_to_v2(TDB_CONTEXT *tdb)
+{
+ int rc;
+
+ DBG_NOTICE("Upgrade to version 2 of the winbindd_cache.tdb\n");
+
+ rc = tdb_traverse(tdb, wbcache_update_centry_fn, NULL);
+ if (rc < 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/***********************************************************************
+ Try and validate every entry in the winbindd cache. If we fail here,
+ delete the cache tdb and return non-zero.
+***********************************************************************/
+
+int winbindd_validate_cache(void)
+{
+ int ret = -1;
+ char *tdb_path = NULL;
+ TDB_CONTEXT *tdb = NULL;
+ uint32_t vers_id;
+ bool ok;
+
+ DBG_DEBUG("winbindd_validate_cache: replacing panic function\n");
+ smb_panic_fn = validate_panic;
+
+ tdb_path = wcache_path();
+ if (tdb_path == NULL) {
+ goto done;
+ }
+
+ tdb = tdb_open_log(tdb_path,
+ WINBINDD_CACHE_TDB_DEFAULT_HASH_SIZE,
+ TDB_INCOMPATIBLE_HASH |
+ ( lp_winbind_offline_logon()
+ ? TDB_DEFAULT
+ : TDB_DEFAULT | TDB_CLEAR_IF_FIRST ),
+ O_RDWR|O_CREAT,
+ 0600);
+ if (!tdb) {
+ DBG_ERR("winbindd_validate_cache: "
+ "error opening/initializing tdb\n");
+ goto done;
+ }
+
+ /* Version check and upgrade code. */
+ if (!tdb_fetch_uint32(tdb, WINBINDD_CACHE_VERSION_KEYSTR, &vers_id)) {
+ DBG_DEBUG("Fresh database\n");
+ tdb_store_uint32(tdb, WINBINDD_CACHE_VERSION_KEYSTR, WINBINDD_CACHE_VERSION);
+ vers_id = WINBINDD_CACHE_VERSION;
+ }
+
+ if (vers_id != WINBINDD_CACHE_VERSION) {
+ if (vers_id == WINBINDD_CACHE_VER1) {
+ ok = wbcache_upgrade_v1_to_v2(tdb);
+ if (!ok) {
+ DBG_DEBUG("winbindd_validate_cache: upgrade to version 2 failed.\n");
+ unlink(tdb_path);
+ goto done;
+ }
+
+ tdb_store_uint32(tdb,
+ WINBINDD_CACHE_VERSION_KEYSTR,
+ WINBINDD_CACHE_VERSION);
+ vers_id = WINBINDD_CACHE_VER2;
+ }
+ }
+
+ tdb_close(tdb);
+
+ ret = tdb_validate_and_backup(tdb_path, cache_traverse_validate_fn);
+
+ if (ret != 0) {
+ DBG_DEBUG("winbindd_validate_cache: validation not successful.\n"
+ "removing tdb %s.\n", tdb_path);
+ unlink(tdb_path);
+ }
+
+done:
+ TALLOC_FREE(tdb_path);
+ DBG_DEBUG("winbindd_validate_cache: restoring panic function\n");
+ smb_panic_fn = smb_panic;
+ return ret;
+}
+
+/***********************************************************************
+ Try and validate every entry in the winbindd cache.
+***********************************************************************/
+
+int winbindd_validate_cache_nobackup(void)
+{
+ int ret = -1;
+ char *tdb_path;
+
+ DBG_DEBUG("winbindd_validate_cache: replacing panic function\n");
+ smb_panic_fn = validate_panic;
+
+ tdb_path = wcache_path();
+ if (tdb_path == NULL) {
+ goto err_panic_restore;
+ }
+
+ if (wcache == NULL || wcache->tdb == NULL) {
+ ret = tdb_validate_open(tdb_path, cache_traverse_validate_fn);
+ } else {
+ ret = tdb_validate(wcache->tdb, cache_traverse_validate_fn);
+ }
+
+ if (ret != 0) {
+ DBG_DEBUG("winbindd_validate_cache_nobackup: validation not "
+ "successful.\n");
+ }
+
+ TALLOC_FREE(tdb_path);
+err_panic_restore:
+ DBG_DEBUG("winbindd_validate_cache_nobackup: restoring panic "
+ "function\n");
+ smb_panic_fn = smb_panic;
+ return ret;
+}
+
+bool winbindd_cache_validate_and_initialize(void)
+{
+ close_winbindd_cache();
+
+ if (lp_winbind_offline_logon()) {
+ if (winbindd_validate_cache() < 0) {
+ DBG_ERR("winbindd cache tdb corrupt and no backup "
+ "could be restored.\n");
+ }
+ }
+
+ return initialize_winbindd_cache();
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static bool add_wbdomain_to_tdc_array( struct winbindd_domain *new_dom,
+ struct winbindd_tdc_domain **domains,
+ size_t *num_domains )
+{
+ struct winbindd_tdc_domain *list = NULL;
+ size_t i, idx;
+ bool set_only = false;
+
+ /* don't allow duplicates */
+
+ idx = *num_domains;
+ list = *domains;
+
+ for ( i=0; i< (*num_domains); i++ ) {
+ if ( strequal( new_dom->name, list[i].domain_name ) ) {
+ DBG_DEBUG("add_wbdomain_to_tdc_array: Found existing record for %s\n",
+ new_dom->name);
+ idx = i;
+ set_only = true;
+
+ break;
+ }
+ }
+
+ if ( !set_only ) {
+ if ( !*domains ) {
+ list = talloc_array( NULL, struct winbindd_tdc_domain, 1 );
+ idx = 0;
+ } else {
+ list = talloc_realloc( *domains, *domains,
+ struct winbindd_tdc_domain,
+ (*num_domains)+1);
+ idx = *num_domains;
+ }
+
+ ZERO_STRUCT( list[idx] );
+ }
+
+ if ( !list )
+ return false;
+
+ list[idx].domain_name = talloc_strdup(list, new_dom->name);
+ if (list[idx].domain_name == NULL) {
+ return false;
+ }
+ if (new_dom->alt_name != NULL) {
+ list[idx].dns_name = talloc_strdup(list, new_dom->alt_name);
+ if (list[idx].dns_name == NULL) {
+ return false;
+ }
+ }
+
+ if ( !is_null_sid( &new_dom->sid ) ) {
+ sid_copy( &list[idx].sid, &new_dom->sid );
+ } else {
+ sid_copy(&list[idx].sid, &global_sid_NULL);
+ }
+
+ if ( new_dom->domain_flags != 0x0 )
+ list[idx].trust_flags = new_dom->domain_flags;
+
+ if ( new_dom->domain_type != 0x0 )
+ list[idx].trust_type = new_dom->domain_type;
+
+ if ( new_dom->domain_trust_attribs != 0x0 )
+ list[idx].trust_attribs = new_dom->domain_trust_attribs;
+
+ if ( !set_only ) {
+ *domains = list;
+ *num_domains = idx + 1;
+ }
+
+ return true;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static TDB_DATA make_tdc_key( const char *domain_name )
+{
+ char *keystr = NULL;
+ TDB_DATA key = { NULL, 0 };
+
+ if ( !domain_name ) {
+ DBG_INFO("make_tdc_key: Keyname workgroup is NULL!\n");
+ return key;
+ }
+
+ if (asprintf( &keystr, "TRUSTDOMCACHE/%s", domain_name ) == -1) {
+ return key;
+ }
+ key = string_term_tdb_data(keystr);
+
+ return key;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static int pack_tdc_domains( struct winbindd_tdc_domain *domains,
+ size_t num_domains,
+ unsigned char **buf )
+{
+ unsigned char *buffer = NULL;
+ int len = 0;
+ int buflen = 0;
+ size_t i = 0;
+
+ DBG_DEBUG("pack_tdc_domains: Packing %d trusted domains\n",
+ (int)num_domains);
+
+ buflen = 0;
+
+ again:
+ len = 0;
+
+ /* Store the number of array items first */
+ len += tdb_pack( buffer ? buffer+len : NULL,
+ buffer ? buflen-len : 0, "d",
+ num_domains );
+
+ /* now pack each domain trust record */
+ for ( i=0; i<num_domains; i++ ) {
+
+ struct dom_sid_buf tmp;
+
+ if ( buflen > 0 ) {
+ DBG_DEBUG("pack_tdc_domains: Packing domain %s (%s)\n",
+ domains[i].domain_name,
+ domains[i].dns_name ? domains[i].dns_name : "UNKNOWN" );
+ }
+
+ len += tdb_pack( buffer ? buffer+len : NULL,
+ buffer ? buflen-len : 0, "fffddd",
+ domains[i].domain_name,
+ domains[i].dns_name ? domains[i].dns_name : "",
+ dom_sid_str_buf(&domains[i].sid, &tmp),
+ domains[i].trust_flags,
+ domains[i].trust_attribs,
+ domains[i].trust_type );
+ }
+
+ if ( buflen < len ) {
+ SAFE_FREE(buffer);
+ if ( (buffer = SMB_MALLOC_ARRAY(unsigned char, len)) == NULL ) {
+ DBG_ERR("pack_tdc_domains: failed to alloc buffer!\n");
+ buflen = -1;
+ goto done;
+ }
+ buflen = len;
+ goto again;
+ }
+
+ *buf = buffer;
+
+ done:
+ return buflen;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static size_t unpack_tdc_domains( unsigned char *buf, int buflen,
+ struct winbindd_tdc_domain **domains )
+{
+ fstring domain_name, dns_name, sid_string;
+ uint32_t type, attribs, flags;
+ int num_domains;
+ int len = 0;
+ int i;
+ struct winbindd_tdc_domain *list = NULL;
+
+ /* get the number of domains */
+ len += tdb_unpack( buf+len, buflen-len, "d", &num_domains);
+ if ( len == -1 ) {
+ DBG_INFO("unpack_tdc_domains: Failed to unpack domain array\n");
+ return 0;
+ }
+
+ list = talloc_array( NULL, struct winbindd_tdc_domain, num_domains );
+ if ( !list ) {
+ DBG_ERR("unpack_tdc_domains: Failed to talloc() domain list!\n");
+ return 0;
+ }
+
+ for ( i=0; i<num_domains; i++ ) {
+ int this_len;
+
+ this_len = tdb_unpack( buf+len, buflen-len, "fffddd",
+ domain_name,
+ dns_name,
+ sid_string,
+ &flags,
+ &attribs,
+ &type );
+
+ if ( this_len == -1 ) {
+ DBG_INFO("unpack_tdc_domains: Failed to unpack domain array\n");
+ TALLOC_FREE( list );
+ return 0;
+ }
+ len += this_len;
+
+ DBG_DEBUG("unpack_tdc_domains: Unpacking domain %s (%s) "
+ "SID %s, flags = 0x%x, attribs = 0x%x, type = 0x%x\n",
+ domain_name, dns_name, sid_string,
+ flags, attribs, type);
+
+ list[i].domain_name = talloc_strdup( list, domain_name );
+ list[i].dns_name = NULL;
+ if (dns_name[0] != '\0') {
+ list[i].dns_name = talloc_strdup(list, dns_name);
+ }
+ if ( !string_to_sid( &(list[i].sid), sid_string ) ) {
+ DBG_DEBUG("unpack_tdc_domains: no SID for domain %s\n",
+ domain_name);
+ }
+ list[i].trust_flags = flags;
+ list[i].trust_attribs = attribs;
+ list[i].trust_type = type;
+ }
+
+ *domains = list;
+
+ return num_domains;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+static bool wcache_tdc_store_list( struct winbindd_tdc_domain *domains, size_t num_domains )
+{
+ TDB_DATA key = make_tdc_key( lp_workgroup() );
+ TDB_DATA data = { NULL, 0 };
+ int ret;
+
+ if ( !key.dptr )
+ return false;
+
+ /* See if we were asked to delete the cache entry */
+
+ if ( !domains ) {
+ ret = tdb_delete( wcache->tdb, key );
+ goto done;
+ }
+
+ data.dsize = pack_tdc_domains( domains, num_domains, &data.dptr );
+
+ if ( !data.dptr ) {
+ ret = -1;
+ goto done;
+ }
+
+ ret = tdb_store( wcache->tdb, key, data, 0 );
+
+ done:
+ SAFE_FREE( data.dptr );
+ SAFE_FREE( key.dptr );
+
+ return ( ret == 0 );
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+bool wcache_tdc_fetch_list( struct winbindd_tdc_domain **domains, size_t *num_domains )
+{
+ TDB_DATA key = make_tdc_key( lp_workgroup() );
+ TDB_DATA data = { NULL, 0 };
+
+ *domains = NULL;
+ *num_domains = 0;
+
+ if ( !key.dptr )
+ return false;
+
+ data = tdb_fetch( wcache->tdb, key );
+
+ SAFE_FREE( key.dptr );
+
+ if ( !data.dptr )
+ return false;
+
+ *num_domains = unpack_tdc_domains( data.dptr, data.dsize, domains );
+
+ SAFE_FREE( data.dptr );
+
+ if ( !*domains )
+ return false;
+
+ return true;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+bool wcache_tdc_add_domain( struct winbindd_domain *domain )
+{
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_domains = 0;
+ bool ret = false;
+ struct dom_sid_buf buf;
+
+ DBG_DEBUG("wcache_tdc_add_domain: Adding domain %s (%s), SID %s, "
+ "flags = 0x%x, attributes = 0x%x, type = 0x%x\n",
+ domain->name, domain->alt_name,
+ dom_sid_str_buf(&domain->sid, &buf),
+ domain->domain_flags,
+ domain->domain_trust_attribs,
+ domain->domain_type);
+
+ if ( !init_wcache() ) {
+ return false;
+ }
+
+ /* fetch the list */
+
+ wcache_tdc_fetch_list( &dom_list, &num_domains );
+
+ /* add the new domain */
+
+ if ( !add_wbdomain_to_tdc_array( domain, &dom_list, &num_domains ) ) {
+ goto done;
+ }
+
+ /* pack the domain */
+
+ if ( !wcache_tdc_store_list( dom_list, num_domains ) ) {
+ goto done;
+ }
+
+ /* Success */
+
+ ret = true;
+ done:
+ TALLOC_FREE( dom_list );
+
+ return ret;
+}
+
+static struct winbindd_tdc_domain *wcache_tdc_dup_domain(
+ TALLOC_CTX *mem_ctx, const struct winbindd_tdc_domain *src)
+{
+ struct winbindd_tdc_domain *dst;
+
+ dst = talloc(mem_ctx, struct winbindd_tdc_domain);
+ if (dst == NULL) {
+ goto fail;
+ }
+ dst->domain_name = talloc_strdup(dst, src->domain_name);
+ if (dst->domain_name == NULL) {
+ goto fail;
+ }
+
+ dst->dns_name = NULL;
+ if (src->dns_name != NULL) {
+ dst->dns_name = talloc_strdup(dst, src->dns_name);
+ if (dst->dns_name == NULL) {
+ goto fail;
+ }
+ }
+
+ sid_copy(&dst->sid, &src->sid);
+ dst->trust_flags = src->trust_flags;
+ dst->trust_type = src->trust_type;
+ dst->trust_attribs = src->trust_attribs;
+ return dst;
+fail:
+ TALLOC_FREE(dst);
+ return NULL;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+struct winbindd_tdc_domain * wcache_tdc_fetch_domain( TALLOC_CTX *ctx, const char *name )
+{
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_domains = 0;
+ size_t i;
+ struct winbindd_tdc_domain *d = NULL;
+
+ DBG_DEBUG("wcache_tdc_fetch_domain: Searching for domain %s\n", name);
+
+ if ( !init_wcache() ) {
+ return NULL;
+ }
+
+ /* fetch the list */
+
+ wcache_tdc_fetch_list( &dom_list, &num_domains );
+
+ for ( i=0; i<num_domains; i++ ) {
+ if ( strequal(name, dom_list[i].domain_name) ||
+ strequal(name, dom_list[i].dns_name) )
+ {
+ DBG_DEBUG("wcache_tdc_fetch_domain: Found domain %s\n",
+ name);
+
+ d = wcache_tdc_dup_domain(ctx, &dom_list[i]);
+ break;
+ }
+ }
+
+ TALLOC_FREE( dom_list );
+
+ return d;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+void wcache_tdc_clear( void )
+{
+ if ( !init_wcache() )
+ return;
+
+ wcache_tdc_store_list( NULL, 0 );
+
+ return;
+}
+
+static bool wcache_ndr_key(TALLOC_CTX *mem_ctx, const char *domain_name,
+ uint32_t opnum, const DATA_BLOB *req,
+ TDB_DATA *pkey)
+{
+ char *key;
+ size_t keylen;
+
+ key = talloc_asprintf(mem_ctx, "NDR/%s/%d/", domain_name, (int)opnum);
+ if (key == NULL) {
+ return false;
+ }
+ keylen = talloc_get_size(key) - 1;
+
+ key = talloc_realloc(mem_ctx, key, char, keylen + req->length);
+ if (key == NULL) {
+ return false;
+ }
+ memcpy(key + keylen, req->data, req->length);
+
+ pkey->dptr = (uint8_t *)key;
+ pkey->dsize = talloc_get_size(key);
+ return true;
+}
+
+static bool wcache_opnum_cacheable(uint32_t opnum)
+{
+ switch (opnum) {
+ case NDR_WBINT_LOOKUPSID:
+ case NDR_WBINT_LOOKUPSIDS:
+ case NDR_WBINT_LOOKUPNAME:
+ case NDR_WBINT_SIDS2UNIXIDS:
+ case NDR_WBINT_UNIXIDS2SIDS:
+ case NDR_WBINT_GETNSSINFO:
+ case NDR_WBINT_LOOKUPUSERALIASES:
+ case NDR_WBINT_LOOKUPUSERGROUPS:
+ case NDR_WBINT_LOOKUPGROUPMEMBERS:
+ case NDR_WBINT_QUERYGROUPLIST:
+ case NDR_WBINT_QUERYUSERRIDLIST:
+ case NDR_WBINT_DSGETDCNAME:
+ case NDR_WBINT_LOOKUPRIDS:
+ return true;
+ }
+ return false;
+}
+
+bool wcache_fetch_ndr(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
+ uint32_t opnum, const DATA_BLOB *req, DATA_BLOB *resp)
+{
+ TDB_DATA key, data;
+ bool ret = false;
+
+ if (!wcache_opnum_cacheable(opnum) ||
+ is_my_own_sam_domain(domain) ||
+ is_builtin_domain(domain)) {
+ return false;
+ }
+
+ if (wcache->tdb == NULL) {
+ return false;
+ }
+
+ if (!wcache_ndr_key(talloc_tos(), domain->name, opnum, req, &key)) {
+ return false;
+ }
+ data = tdb_fetch(wcache->tdb, key);
+ TALLOC_FREE(key.dptr);
+
+ if (data.dptr == NULL) {
+ return false;
+ }
+ if (data.dsize < 12) {
+ goto fail;
+ }
+
+ if (is_domain_online(domain)) {
+ uint32_t entry_seqnum, dom_seqnum, last_check;
+ uint64_t entry_timeout;
+
+ if (!wcache_fetch_seqnum(domain->name, &dom_seqnum,
+ &last_check)) {
+ goto fail;
+ }
+ entry_seqnum = IVAL(data.dptr, 0);
+ if (entry_seqnum != dom_seqnum) {
+ DBG_DEBUG("Entry has wrong sequence number: %d\n",
+ (int)entry_seqnum);
+ goto fail;
+ }
+ entry_timeout = BVAL(data.dptr, 4);
+ if (time(NULL) > (time_t)entry_timeout) {
+ DBG_DEBUG("Entry has timed out\n");
+ goto fail;
+ }
+ }
+
+ resp->data = (uint8_t *)talloc_memdup(mem_ctx, data.dptr + 12,
+ data.dsize - 12);
+ if (resp->data == NULL) {
+ DBG_DEBUG("talloc failed\n");
+ goto fail;
+ }
+ resp->length = data.dsize - 12;
+
+ ret = true;
+fail:
+ SAFE_FREE(data.dptr);
+ return ret;
+}
+
+void wcache_store_ndr(struct winbindd_domain *domain, uint32_t opnum,
+ const DATA_BLOB *req, const DATA_BLOB *resp)
+{
+ TDB_DATA key, data;
+ uint32_t dom_seqnum, last_check;
+ uint64_t timeout;
+
+ if (!wcache_opnum_cacheable(opnum) ||
+ is_my_own_sam_domain(domain) ||
+ is_builtin_domain(domain)) {
+ return;
+ }
+
+ if (wcache->tdb == NULL) {
+ return;
+ }
+
+ if (!wcache_fetch_seqnum(domain->name, &dom_seqnum, &last_check)) {
+ DBG_DEBUG("could not fetch seqnum for domain %s\n",
+ domain->name);
+ return;
+ }
+
+ if (!wcache_ndr_key(talloc_tos(), domain->name, opnum, req, &key)) {
+ return;
+ }
+
+ timeout = time(NULL) + lp_winbind_cache_time();
+
+ data.dsize = resp->length + 12;
+ data.dptr = talloc_array(key.dptr, uint8_t, data.dsize);
+ if (data.dptr == NULL) {
+ goto done;
+ }
+
+ SIVAL(data.dptr, 0, dom_seqnum);
+ SBVAL(data.dptr, 4, timeout);
+ memcpy(data.dptr + 12, resp->data, resp->length);
+
+ tdb_store(wcache->tdb, key, data, 0);
+
+done:
+ TALLOC_FREE(key.dptr);
+ return;
+}
diff --git a/source3/winbindd/winbindd_ccache_access.c b/source3/winbindd/winbindd_ccache_access.c
new file mode 100644
index 0000000..cc395ad
--- /dev/null
+++ b/source3/winbindd/winbindd_ccache_access.c
@@ -0,0 +1,397 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - cached credentials functions
+
+ Copyright (C) Robert O'Callahan 2006
+ Copyright (C) Jeremy Allison 2006 (minor fixes to fit into Samba and
+ protect against integer wrap).
+ Copyright (C) Andrew Bartlett 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "auth/gensec/gensec.h"
+#include "auth_generic.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static bool client_can_access_ccache_entry(uid_t client_uid,
+ struct WINBINDD_MEMORY_CREDS *entry)
+{
+ if (client_uid == entry->uid || client_uid == 0) {
+ DEBUG(10, ("Access granted to uid %u\n", (unsigned int)client_uid));
+ return True;
+ }
+
+ DEBUG(1, ("Access denied to uid %u (expected %u)\n",
+ (unsigned int)client_uid, (unsigned int)entry->uid));
+ return False;
+}
+
+static NTSTATUS do_ntlm_auth_with_stored_pw(const char *namespace,
+ const char *domain,
+ const char *username,
+ const char *password,
+ const DATA_BLOB initial_msg,
+ const DATA_BLOB challenge_msg,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *auth_msg,
+ uint8_t session_key[16],
+ uint8_t *new_spnego)
+{
+ NTSTATUS status;
+ struct auth_generic_state *auth_generic_state = NULL;
+ DATA_BLOB reply, session_key_blob;
+
+ status = auth_generic_client_prepare(mem_ctx, &auth_generic_state);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not start NTLMSSP client: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = auth_generic_set_username(auth_generic_state, username);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not set username: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = auth_generic_set_domain(auth_generic_state, domain);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not set domain: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ status = auth_generic_set_password(auth_generic_state, password);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not set password: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ if (initial_msg.length == 0) {
+ gensec_want_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_SESSION_KEY);
+ }
+
+ status = auth_generic_client_start_by_name(auth_generic_state,
+ "ntlmssp_resume_ccache");
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("Could not start NTLMSSP resume mech: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /*
+ * We inject the initial NEGOTIATE message our caller used
+ * in order to get the state machine into the correct position.
+ */
+ reply = data_blob_null;
+ status = gensec_update(auth_generic_state->gensec_security,
+ talloc_tos(), initial_msg, &reply);
+ data_blob_free(&reply);
+
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ DEBUG(1, ("Failed to create initial message! [%s]\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /* Now we are ready to handle the server's actual response. */
+ status = gensec_update(auth_generic_state->gensec_security,
+ mem_ctx, challenge_msg, &reply);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+ DEBUG(1, ("We didn't get a response to the challenge! [%s]\n",
+ nt_errstr(status)));
+ data_blob_free(&reply);
+ goto done;
+ }
+
+ status = gensec_session_key(auth_generic_state->gensec_security,
+ talloc_tos(), &session_key_blob);
+ if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
+ DEBUG(1, ("We didn't get the session key we requested! [%s]\n",
+ nt_errstr(status)));
+ data_blob_free(&reply);
+ goto done;
+ }
+
+ if (session_key_blob.length != 16) {
+ DEBUG(1, ("invalid session key length %d\n",
+ (int)session_key_blob.length));
+ data_blob_free(&reply);
+ goto done;
+ }
+ memcpy(session_key, session_key_blob.data, 16);
+ data_blob_free(&session_key_blob);
+ *auth_msg = reply;
+ *new_spnego = gensec_have_feature(auth_generic_state->gensec_security,
+ GENSEC_FEATURE_NEW_SPNEGO);
+ status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(auth_generic_state);
+ return status;
+}
+
+static bool check_client_uid(struct winbindd_cli_state *state, uid_t uid)
+{
+ int ret;
+ uid_t ret_uid;
+ gid_t ret_gid;
+
+ ret_uid = (uid_t)-1;
+
+ ret = getpeereid(state->sock, &ret_uid, &ret_gid);
+ if (ret != 0) {
+ DEBUG(1, ("check_client_uid: Could not get socket peer uid: %s; "
+ "denying access\n", strerror(errno)));
+ return False;
+ }
+
+ if (uid != ret_uid && ret_uid != sec_initial_uid()) {
+ DEBUG(1, ("check_client_uid: Client lied about its uid: said %u, "
+ "actually was %u; denying access\n",
+ (unsigned int)uid, (unsigned int)ret_uid));
+ return False;
+ }
+
+ return True;
+}
+
+bool winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *name_user = NULL;
+ char *auth_user = NULL;
+ NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
+ struct WINBINDD_MEMORY_CREDS *entry;
+ DATA_BLOB initial, challenge, auth;
+ uint32_t initial_blob_len, challenge_blob_len, extra_len;
+ bool ok;
+
+ /* Ensure null termination */
+ state->request->data.ccache_ntlm_auth.user[
+ sizeof(state->request->data.ccache_ntlm_auth.user)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: perform NTLM auth on behalf of user %s\n", (unsigned long)state->pid,
+ state->request->data.ccache_ntlm_auth.user));
+
+ /* Parse domain and username */
+
+ auth_user = state->request->data.ccache_ntlm_auth.user;
+ ok = canonicalize_username(state,
+ &auth_user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ DEBUG(5,("winbindd_ccache_ntlm_auth: cannot parse domain and user from name [%s]\n",
+ state->request->data.ccache_ntlm_auth.user));
+ return false;
+ }
+
+ fstrcpy(state->request->data.ccache_ntlm_auth.user, auth_user);
+ TALLOC_FREE(auth_user);
+
+ domain = find_auth_domain(state->request->flags, name_domain);
+
+ if (domain == NULL) {
+ DEBUG(5,("winbindd_ccache_ntlm_auth: can't get domain [%s]\n",
+ name_domain));
+ return false;
+ }
+
+ if (!check_client_uid(state, state->request->data.ccache_ntlm_auth.uid)) {
+ return false;
+ }
+
+ /* validate blob lengths */
+ initial_blob_len = state->request->data.ccache_ntlm_auth.initial_blob_len;
+ challenge_blob_len = state->request->data.ccache_ntlm_auth.challenge_blob_len;
+ extra_len = state->request->extra_len;
+
+ if (initial_blob_len > extra_len || challenge_blob_len > extra_len ||
+ initial_blob_len + challenge_blob_len > extra_len ||
+ initial_blob_len + challenge_blob_len < initial_blob_len ||
+ initial_blob_len + challenge_blob_len < challenge_blob_len) {
+
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: blob lengths overrun "
+ "or wrap. Buffer [%d+%d > %d]\n",
+ initial_blob_len,
+ challenge_blob_len,
+ extra_len));
+ goto process_result;
+ }
+
+ TALLOC_FREE(name_namespace);
+ TALLOC_FREE(name_domain);
+ TALLOC_FREE(name_user);
+ /* Parse domain and username */
+ ok = parse_domain_user(state,
+ state->request->data.ccache_ntlm_auth.user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: cannot parse "
+ "domain and user from name [%s]\n",
+ state->request->data.ccache_ntlm_auth.user));
+ goto process_result;
+ }
+
+ entry = find_memory_creds_by_name(state->request->data.ccache_ntlm_auth.user);
+ if (entry == NULL || entry->nt_hash == NULL || entry->lm_hash == NULL) {
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: could not find "
+ "credentials for user %s\n",
+ state->request->data.ccache_ntlm_auth.user));
+ goto process_result;
+ }
+
+ DEBUG(10,("winbindd_dual_ccache_ntlm_auth: found ccache [%s]\n", entry->username));
+
+ if (!client_can_access_ccache_entry(state->request->data.ccache_ntlm_auth.uid, entry)) {
+ goto process_result;
+ }
+
+ if (initial_blob_len == 0 && challenge_blob_len == 0) {
+ /* this is just a probe to see if credentials are available. */
+ result = NT_STATUS_OK;
+ state->response->data.ccache_ntlm_auth.auth_blob_len = 0;
+ goto process_result;
+ }
+
+ initial = data_blob_const(state->request->extra_data.data,
+ initial_blob_len);
+ challenge = data_blob_const(
+ state->request->extra_data.data + initial_blob_len,
+ state->request->data.ccache_ntlm_auth.challenge_blob_len);
+
+ result = do_ntlm_auth_with_stored_pw(
+ name_namespace,
+ name_domain,
+ name_user,
+ entry->pass,
+ initial,
+ challenge,
+ talloc_tos(),
+ &auth,
+ state->response->data.ccache_ntlm_auth.session_key,
+ &state->response->data.ccache_ntlm_auth.new_spnego);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ goto process_result;
+ }
+
+ state->response->extra_data.data = talloc_memdup(
+ state->mem_ctx, auth.data, auth.length);
+ if (!state->response->extra_data.data) {
+ result = NT_STATUS_NO_MEMORY;
+ goto process_result;
+ }
+ state->response->length += auth.length;
+ state->response->data.ccache_ntlm_auth.auth_blob_len = auth.length;
+
+ data_blob_free(&auth);
+
+ process_result:
+ TALLOC_FREE(name_namespace);
+ TALLOC_FREE(name_domain);
+ TALLOC_FREE(name_user);
+ return NT_STATUS_IS_OK(result);
+}
+
+bool winbindd_ccache_save(struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain;
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *name_user = NULL;
+ char *save_user = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ /* Ensure null termination */
+ state->request->data.ccache_save.user[
+ sizeof(state->request->data.ccache_save.user)-1]='\0';
+ state->request->data.ccache_save.pass[
+ sizeof(state->request->data.ccache_save.pass)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: save password of user %s\n",
+ (unsigned long)state->pid,
+ state->request->data.ccache_save.user));
+
+ /* Parse domain and username */
+
+
+ save_user = state->request->data.ccache_save.user;
+ ok = canonicalize_username(state,
+ &save_user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ DEBUG(5,("winbindd_ccache_save: cannot parse domain and user "
+ "from name [%s]\n",
+ state->request->data.ccache_save.user));
+ return false;
+ }
+
+ fstrcpy(state->request->data.ccache_save.user, save_user);
+
+ /*
+ * The domain is checked here only for compatibility
+ * reasons. We used to do the winbindd memory ccache for
+ * ntlm_auth in the domain child. With that code, we had to
+ * make sure that we do have a domain around to send this
+ * to. Now we do the memory cache in the parent winbindd,
+ * where it would not matter if we have a domain or not.
+ */
+
+ domain = find_auth_domain(state->request->flags, name_domain);
+ if (domain == NULL) {
+ DEBUG(5, ("winbindd_ccache_save: can't get domain [%s]\n",
+ name_domain));
+ return false;
+ }
+
+ if (!check_client_uid(state, state->request->data.ccache_save.uid)) {
+ return false;
+ }
+
+ status = winbindd_add_memory_creds(
+ state->request->data.ccache_save.user,
+ state->request->data.ccache_save.uid,
+ state->request->data.ccache_save.pass);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("winbindd_add_memory_creds failed %s\n",
+ nt_errstr(status)));
+ return false;
+ }
+ return true;
+}
diff --git a/source3/winbindd/winbindd_change_machine_acct.c b/source3/winbindd/winbindd_change_machine_acct.c
new file mode 100644
index 0000000..fe5b9bf
--- /dev/null
+++ b/source3/winbindd/winbindd_change_machine_acct.c
@@ -0,0 +1,99 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_CHANGE_MACHINE_ACCT
+ Copyright (C) Volker Lendecke 2009
+ Copyright (C) Guenther Deschner 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_change_machine_acct_state {
+ uint8_t dummy;
+};
+
+static void winbindd_change_machine_acct_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_change_machine_acct_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_change_machine_acct_state *state;
+ struct winbindd_domain *domain;
+ const char *dcname = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_change_machine_acct_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (request->data.init_conn.dcname[0] != '\0') {
+ dcname = request->data.init_conn.dcname;
+ }
+
+ domain = find_domain_from_name(request->domain_name);
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+ if (domain->internal) {
+ /*
+ * Internal domains are passdb based, we can always
+ * contact them.
+ *
+ * This also protects us from changing the password on
+ * the AD DC without updating all the right databases.
+ * Do not remove this until that code is fixed.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_ChangeMachineAccount_send(state, ev,
+ dom_child_handle(domain),
+ dcname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_change_machine_acct_done, req);
+ return req;
+}
+
+static void winbindd_change_machine_acct_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_change_machine_acct_state *state = tevent_req_data(
+ req, struct winbindd_change_machine_acct_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_ChangeMachineAccount_recv(subreq, state, &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_change_machine_acct_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ return tevent_req_simple_recv_ntstatus(req);
+}
diff --git a/source3/winbindd/winbindd_check_machine_acct.c b/source3/winbindd/winbindd_check_machine_acct.c
new file mode 100644
index 0000000..c657374
--- /dev/null
+++ b/source3/winbindd/winbindd_check_machine_acct.c
@@ -0,0 +1,96 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_CHECK_MACHINE_ACCT
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_check_machine_acct_state {
+ uint8_t dummy;
+};
+
+static void winbindd_check_machine_acct_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_check_machine_acct_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_check_machine_acct_state *state;
+ struct winbindd_domain *domain;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_check_machine_acct_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (request->domain_name[0] == '\0') {
+ /* preserve old behavior, when no domain name is given */
+ domain = find_our_domain();
+ } else {
+ domain = find_domain_from_name(request->domain_name);
+ }
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+ if (domain->internal) {
+ /*
+ * Internal domains are passdb based, we can always
+ * contact them.
+ */
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_CheckMachineAccount_send(state, ev,
+ dom_child_handle(domain));
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_check_machine_acct_done, req);
+ return req;
+}
+
+static void winbindd_check_machine_acct_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_check_machine_acct_state *state = tevent_req_data(
+ req, struct winbindd_check_machine_acct_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_CheckMachineAccount_recv(subreq, state, &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_check_machine_acct_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ NTSTATUS status = tevent_req_simple_recv_ntstatus(req);
+
+ set_auth_errors(presp, status);
+ return status;
+}
diff --git a/source3/winbindd/winbindd_cm.c b/source3/winbindd/winbindd_cm.c
new file mode 100644
index 0000000..1685edb
--- /dev/null
+++ b/source3/winbindd/winbindd_cm.c
@@ -0,0 +1,3434 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon connection manager
+
+ Copyright (C) Tim Potter 2001
+ Copyright (C) Andrew Bartlett 2002
+ Copyright (C) Gerald (Jerry) Carter 2003-2005.
+ Copyright (C) Volker Lendecke 2004-2005
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ We need to manage connections to domain controllers without having to
+ mess up the main winbindd code with other issues. The aim of the
+ connection manager is to:
+
+ - make connections to domain controllers and cache them
+ - re-establish connections when networks or servers go down
+ - centralise the policy on connection timeouts, domain controller
+ selection etc
+ - manage re-entrancy for when winbindd becomes able to handle
+ multiple outstanding rpc requests
+
+ Why not have connection management as part of the rpc layer like tng?
+ Good question. This code may morph into libsmb/rpc_cache.c or something
+ like that but at the moment it's simply staying as part of winbind. I
+ think the TNG architecture of forcing every user of the rpc layer to use
+ the connection caching system is a bad idea. It should be an optional
+ method of using the routines.
+
+ The TNG design is quite good but I disagree with some aspects of the
+ implementation. -tpot
+
+ */
+
+/*
+ TODO:
+
+ - I'm pretty annoyed by all the make_nmb_name() stuff. It should be
+ moved down into another function.
+
+ - Take care when destroying cli_structs as they can be shared between
+ various sam handles.
+
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libsmb/namequery.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_netlogon.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../librpc/gen_ndr/ndr_dssetup_c.h"
+#include "libads/sitename_cache.h"
+#include "libsmb/libsmb.h"
+#include "libsmb/clidgram.h"
+#include "ads.h"
+#include "secrets.h"
+#include "../libcli/security/security.h"
+#include "passdb.h"
+#include "messages.h"
+#include "auth/gensec/gensec.h"
+#include "../libcli/smb/smbXcli_base.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "auth.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "auth/credentials/credentials.h"
+#include "lib/param/param.h"
+#include "lib/gencache.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+struct dc_name_ip {
+ fstring name;
+ struct sockaddr_storage ss;
+};
+
+extern struct winbindd_methods reconnect_methods;
+
+static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool need_rw_dc);
+static void set_dc_type_and_flags( struct winbindd_domain *domain );
+static bool set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain );
+static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
+ struct dc_name_ip **dcs, int *num_dcs,
+ uint32_t request_flags);
+
+void winbind_msg_domain_offline(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ const char *domain_name = (const char *)data->data;
+ struct winbindd_domain *domain;
+
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ DBG_DEBUG("Domain %s not found!\n", domain_name);
+ return;
+ }
+
+ DBG_DEBUG("Domain %s was %s, change to offline now.\n",
+ domain_name,
+ domain->online ? "online" : "offline");
+
+ domain->online = false;
+}
+
+void winbind_msg_domain_online(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ const char *domain_name = (const char *)data->data;
+ struct winbindd_domain *domain;
+
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ return;
+ }
+
+ SMB_ASSERT(wb_child_domain() == NULL);
+
+ DBG_DEBUG("Domain %s was %s, marking as online now!\n",
+ domain_name,
+ domain->online ? "online" : "offline");
+
+ domain->online = true;
+}
+
+/****************************************************************
+ Set domain offline and also add handler to put us back online
+ if we detect a DC.
+****************************************************************/
+
+void set_domain_offline(struct winbindd_domain *domain)
+{
+ pid_t parent_pid = getppid();
+
+ DEBUG(10,("set_domain_offline: called for domain %s\n",
+ domain->name ));
+
+ if (domain->internal) {
+ DEBUG(3,("set_domain_offline: domain %s is internal - logic error.\n",
+ domain->name ));
+ return;
+ }
+
+ domain->online = False;
+
+ /* Offline domains are always initialized. They're
+ re-initialized when they go back online. */
+
+ domain->initialized = True;
+
+ /* Send a message to the parent that the domain is offline. */
+ if (parent_pid > 1 && !domain->internal) {
+ messaging_send_buf(global_messaging_context(),
+ pid_to_procid(parent_pid),
+ MSG_WINBIND_DOMAIN_OFFLINE,
+ (uint8_t *)domain->name,
+ strlen(domain->name) + 1);
+ }
+
+ /* Send an offline message to the idmap child when our
+ primary domain goes offline */
+ if ( domain->primary ) {
+ pid_t idmap_pid = idmap_child_pid();
+
+ if (idmap_pid != 0) {
+ messaging_send_buf(global_messaging_context(),
+ pid_to_procid(idmap_pid),
+ MSG_WINBIND_OFFLINE,
+ (const uint8_t *)domain->name,
+ strlen(domain->name)+1);
+ }
+ }
+
+ return;
+}
+
+/****************************************************************
+ Set domain online - if allowed.
+****************************************************************/
+
+static void set_domain_online(struct winbindd_domain *domain)
+{
+ pid_t parent_pid = getppid();
+
+ DEBUG(10,("set_domain_online: called for domain %s\n",
+ domain->name ));
+
+ if (domain->internal) {
+ DEBUG(3,("set_domain_online: domain %s is internal - logic error.\n",
+ domain->name ));
+ return;
+ }
+
+ if (get_global_winbindd_state_offline()) {
+ DEBUG(10,("set_domain_online: domain %s remaining globally offline\n",
+ domain->name ));
+ return;
+ }
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ /* If we are waiting to get a krb5 ticket, trigger immediately. */
+ ccache_regain_all_now();
+
+ /* Ok, we're out of any startup mode now... */
+ domain->startup = False;
+
+ if (domain->online == False) {
+ /* We were offline - now we're online. We default to
+ using the MS-RPC backend if we started offline,
+ and if we're going online for the first time we
+ should really re-initialize the backends and the
+ checks to see if we're talking to an AD or NT domain.
+ */
+
+ domain->initialized = False;
+
+ /* 'reconnect_methods' is the MS-RPC backend. */
+ if (domain->backend == &reconnect_methods) {
+ domain->backend = NULL;
+ }
+ }
+
+ domain->online = True;
+
+ /* Send a message to the parent that the domain is online. */
+ if (parent_pid > 1 && !domain->internal) {
+ messaging_send_buf(global_messaging_context(),
+ pid_to_procid(parent_pid),
+ MSG_WINBIND_DOMAIN_ONLINE,
+ (uint8_t *)domain->name,
+ strlen(domain->name) + 1);
+ }
+
+ /* Send an online message to the idmap child when our
+ primary domain comes online */
+
+ if ( domain->primary ) {
+ pid_t idmap_pid = idmap_child_pid();
+
+ if (idmap_pid != 0) {
+ messaging_send_buf(global_messaging_context(),
+ pid_to_procid(idmap_pid),
+ MSG_WINBIND_ONLINE,
+ (const uint8_t *)domain->name,
+ strlen(domain->name)+1);
+ }
+ }
+
+ return;
+}
+
+/****************************************************************
+ Requested to set a domain online.
+****************************************************************/
+
+void set_domain_online_request(struct winbindd_domain *domain)
+{
+ NTSTATUS status;
+
+ SMB_ASSERT(wb_child_domain() || idmap_child());
+
+ DEBUG(10,("set_domain_online_request: called for domain %s\n",
+ domain->name ));
+
+ if (get_global_winbindd_state_offline()) {
+ DEBUG(10,("set_domain_online_request: domain %s remaining globally offline\n",
+ domain->name ));
+ return;
+ }
+
+ if (domain->internal) {
+ DEBUG(10, ("set_domain_online_request: Internal domains are "
+ "always online\n"));
+ return;
+ }
+
+ /*
+ * This call takes care of setting the online flag to true if we
+ * connected, or tell the parent to ping us back if false. Bypasses
+ * online check so always does network calls.
+ */
+ status = init_dc_connection_network(domain, true);
+ DBG_DEBUG("init_dc_connection_network(), returned %s, called for "
+ "domain %s (online = %s)\n",
+ nt_errstr(status),
+ domain->name,
+ domain->online ? "true" : "false");
+}
+
+/****************************************************************
+ Add -ve connection cache entries for domain and realm.
+****************************************************************/
+
+static void winbind_add_failed_connection_entry(
+ const struct winbindd_domain *domain,
+ const char *server,
+ NTSTATUS result)
+{
+ add_failed_connection_entry(domain->name, server, result);
+ /* If this was the saf name for the last thing we talked to,
+ remove it. */
+ saf_delete(domain->name);
+ if (domain->alt_name != NULL) {
+ add_failed_connection_entry(domain->alt_name, server, result);
+ saf_delete(domain->alt_name);
+ }
+ winbindd_unset_locator_kdc_env(domain);
+}
+
+/* Choose between anonymous or authenticated connections. We need to use
+ an authenticated connection if DCs have the RestrictAnonymous registry
+ entry set > 0, or the "Additional restrictions for anonymous
+ connections" set in the win2k Local Security Policy.
+
+ Caller to free() result in domain, username, password
+*/
+
+static void cm_get_ipc_userpass(char **username, char **domain, char **password)
+{
+ *username = (char *)secrets_fetch(SECRETS_AUTH_USER, NULL);
+ *domain = (char *)secrets_fetch(SECRETS_AUTH_DOMAIN, NULL);
+ *password = (char *)secrets_fetch(SECRETS_AUTH_PASSWORD, NULL);
+
+ if (*username && **username) {
+
+ if (!*domain || !**domain)
+ *domain = smb_xstrdup(lp_workgroup());
+
+ if (!*password || !**password)
+ *password = smb_xstrdup("");
+
+ DEBUG(3, ("cm_get_ipc_userpass: Retrieved auth-user from secrets.tdb [%s\\%s]\n",
+ *domain, *username));
+
+ } else {
+ DEBUG(3, ("cm_get_ipc_userpass: No auth-user defined\n"));
+ *username = smb_xstrdup("");
+ *domain = smb_xstrdup("");
+ *password = smb_xstrdup("");
+ }
+}
+
+static NTSTATUS cm_get_ipc_credentials(TALLOC_CTX *mem_ctx,
+ struct cli_credentials **_creds)
+{
+
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_INTERNAL_ERROR;
+ struct loadparm_context *lp_ctx;
+ char *username = NULL;
+ char *netbios_domain = NULL;
+ char *password = NULL;
+ struct cli_credentials *creds = NULL;
+ bool ok;
+
+ cm_get_ipc_userpass(&username, &netbios_domain, &password);
+
+ lp_ctx = loadparm_init_s3(frame, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ DEBUG(1, ("loadparm_init_s3 failed\n"));
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ creds = cli_credentials_init(mem_ctx);
+ if (creds == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ ok = cli_credentials_set_conf(creds, lp_ctx);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+
+ cli_credentials_set_kerberos_state(creds,
+ CRED_USE_KERBEROS_DISABLED,
+ CRED_SPECIFIED);
+
+ ok = cli_credentials_set_domain(creds, netbios_domain, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ ok = cli_credentials_set_username(creds, username, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ ok = cli_credentials_set_password(creds, password, CRED_SPECIFIED);
+ if (!ok) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ *_creds = creds;
+ creds = NULL;
+ status = NT_STATUS_OK;
+ fail:
+ TALLOC_FREE(creds);
+ SAFE_FREE(username);
+ SAFE_FREE(netbios_domain);
+ SAFE_FREE(password);
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static bool cm_is_ipc_credentials(struct cli_credentials *creds)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *ipc_account = NULL;
+ char *ipc_domain = NULL;
+ char *ipc_password = NULL;
+ const char *creds_account = NULL;
+ const char *creds_domain = NULL;
+ const char *creds_password = NULL;
+ bool ret = false;
+
+ cm_get_ipc_userpass(&ipc_account, &ipc_domain, &ipc_password);
+
+ creds_account = cli_credentials_get_username(creds);
+ creds_domain = cli_credentials_get_domain(creds);
+ creds_password = cli_credentials_get_password(creds);
+
+ if (!strequal(ipc_domain, creds_domain)) {
+ goto done;
+ }
+
+ if (!strequal(ipc_account, creds_account)) {
+ goto done;
+ }
+
+ if (!strcsequal(ipc_password, creds_password)) {
+ goto done;
+ }
+
+ ret = true;
+ done:
+ SAFE_FREE(ipc_account);
+ SAFE_FREE(ipc_domain);
+ SAFE_FREE(ipc_password);
+ TALLOC_FREE(frame);
+ return ret;
+}
+
+static bool get_dc_name_via_netlogon(struct winbindd_domain *domain,
+ fstring dcname,
+ struct sockaddr_storage *dc_ss,
+ uint32_t request_flags)
+{
+ struct winbindd_domain *our_domain = NULL;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ NTSTATUS result;
+ WERROR werr;
+ TALLOC_CTX *mem_ctx;
+ unsigned int orig_timeout;
+ const char *tmp = NULL;
+ const char *p;
+ struct dcerpc_binding_handle *b;
+
+ /* Hmmmm. We can only open one connection to the NETLOGON pipe at the
+ * moment.... */
+
+ if (IS_DC) {
+ return False;
+ }
+
+ if (domain->primary) {
+ return False;
+ }
+
+ our_domain = find_our_domain();
+
+ if ((mem_ctx = talloc_init("get_dc_name_via_netlogon")) == NULL) {
+ return False;
+ }
+
+ result = cm_connect_netlogon(our_domain, &netlogon_pipe);
+ if (!NT_STATUS_IS_OK(result)) {
+ talloc_destroy(mem_ctx);
+ return False;
+ }
+
+ b = netlogon_pipe->binding_handle;
+
+ /* This call can take a long time - allow the server to time out.
+ 35 seconds should do it. */
+
+ orig_timeout = rpccli_set_timeout(netlogon_pipe, 35000);
+
+ if (our_domain->active_directory) {
+ struct netr_DsRGetDCNameInfo *domain_info = NULL;
+
+ /*
+ * TODO request flags are not respected in the server
+ * (and in some cases, like REQUIRE_PDC, causes an error)
+ */
+ result = dcerpc_netr_DsRGetDCName(b,
+ mem_ctx,
+ our_domain->dcname,
+ domain->name,
+ NULL,
+ NULL,
+ request_flags|DS_RETURN_DNS_NAME,
+ &domain_info,
+ &werr);
+ if (NT_STATUS_IS_OK(result) && W_ERROR_IS_OK(werr)) {
+ tmp = talloc_strdup(
+ mem_ctx, domain_info->dc_unc);
+ if (tmp == NULL) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ talloc_destroy(mem_ctx);
+ return false;
+ }
+ if (domain->alt_name == NULL) {
+ domain->alt_name = talloc_strdup(domain,
+ domain_info->domain_name);
+ if (domain->alt_name == NULL) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ talloc_destroy(mem_ctx);
+ return false;
+ }
+ }
+ if (domain->forest_name == NULL) {
+ domain->forest_name = talloc_strdup(domain,
+ domain_info->forest_name);
+ if (domain->forest_name == NULL) {
+ DEBUG(0, ("talloc_strdup failed\n"));
+ talloc_destroy(mem_ctx);
+ return false;
+ }
+ }
+ }
+ } else {
+ result = dcerpc_netr_GetAnyDCName(b, mem_ctx,
+ our_domain->dcname,
+ domain->name,
+ &tmp,
+ &werr);
+ }
+
+ /* And restore our original timeout. */
+ rpccli_set_timeout(netlogon_pipe, orig_timeout);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n",
+ nt_errstr(result)));
+ talloc_destroy(mem_ctx);
+ return false;
+ }
+
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(10,("dcerpc_netr_GetAnyDCName failed: %s\n",
+ win_errstr(werr)));
+ talloc_destroy(mem_ctx);
+ return false;
+ }
+
+ /* dcerpc_netr_GetAnyDCName gives us a name with \\ */
+ p = strip_hostname(tmp);
+
+ fstrcpy(dcname, p);
+
+ talloc_destroy(mem_ctx);
+
+ DEBUG(10,("dcerpc_netr_GetAnyDCName returned %s\n", dcname));
+
+ if (!resolve_name(dcname, dc_ss, 0x20, true)) {
+ return False;
+ }
+
+ return True;
+}
+
+/**
+ * Helper function to assemble trust password and account name
+ */
+static NTSTATUS get_trust_credentials(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ bool netlogon,
+ struct cli_credentials **_creds)
+{
+ const struct winbindd_domain *creds_domain = NULL;
+ struct cli_credentials *creds;
+ NTSTATUS status;
+ bool force_machine_account = false;
+
+ /* If we are a DC and this is not our own domain */
+
+ if (!domain->active_directory) {
+ if (!netlogon) {
+ /*
+ * For non active directory domains
+ * we can only use NTLMSSP for SMB.
+ *
+ * But the trust account is not allowed
+ * to use SMB with NTLMSSP.
+ */
+ force_machine_account = true;
+ }
+ }
+
+ if (IS_DC && !force_machine_account) {
+ creds_domain = domain;
+ } else {
+ creds_domain = find_our_domain();
+ if (creds_domain == NULL) {
+ return NT_STATUS_INVALID_SERVER_STATE;
+ }
+ }
+
+ status = pdb_get_trust_credentials(creds_domain->name,
+ creds_domain->alt_name,
+ mem_ctx,
+ &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto ipc_fallback;
+ }
+
+ if (creds_domain != domain) {
+ /*
+ * We can only use schannel against a direct trust
+ */
+ cli_credentials_set_secure_channel_type(creds,
+ SEC_CHAN_NULL);
+ }
+
+ *_creds = creds;
+ return NT_STATUS_OK;
+
+ ipc_fallback:
+ if (netlogon) {
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ status = cm_get_ipc_credentials(mem_ctx, &creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *_creds = creds;
+ return NT_STATUS_OK;
+}
+
+/************************************************************************
+ Given a fd with a just-connected TCP connection to a DC, open a connection
+ to the pipe.
+************************************************************************/
+
+static NTSTATUS cm_prepare_connection(struct winbindd_domain *domain,
+ const int sockfd,
+ const char *controller,
+ struct cli_state **cli,
+ bool *retry)
+{
+ bool try_ipc_auth = false;
+ const char *machine_principal = NULL;
+ const char *machine_realm = NULL;
+ const char *machine_account = NULL;
+ const char *machine_domain = NULL;
+ int flags = 0;
+ struct cli_credentials *creds = NULL;
+
+ struct named_mutex *mutex;
+
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS tmp_status;
+ NTSTATUS tcon_status = NT_STATUS_NETWORK_NAME_DELETED;
+
+ enum smb_signing_setting smb_sign_client_connections = lp_client_ipc_signing();
+
+ if (IS_DC) {
+ if (domain->secure_channel_type == SEC_CHAN_NULL) {
+ /*
+ * Make sure we don't even try to
+ * connect to a foreign domain
+ * without a direct outbound trust.
+ */
+ close(sockfd);
+ return NT_STATUS_NO_TRUST_LSA_SECRET;
+ }
+
+ /*
+ * As AD DC we only use netlogon and lsa
+ * using schannel over an anonymous transport
+ * (ncacn_ip_tcp or ncacn_np).
+ *
+ * Currently we always establish the SMB connection,
+ * even if we don't use it, because we later use ncacn_ip_tcp.
+ *
+ * As we won't use the SMB connection there's no
+ * need to try kerberos. And NT4 domains expect
+ * an anonymous IPC$ connection anyway.
+ */
+ smb_sign_client_connections = SMB_SIGNING_OFF;
+ }
+
+ if (smb_sign_client_connections == SMB_SIGNING_DEFAULT) {
+ /*
+ * If we are connecting to our own AD domain, require
+ * smb signing to disrupt MITM attacks
+ */
+ if (domain->primary && lp_security() == SEC_ADS) {
+ smb_sign_client_connections = SMB_SIGNING_REQUIRED;
+ /*
+ * If we are in or are an AD domain and connecting to another
+ * AD domain in our forest
+ * then require smb signing to disrupt MITM attacks
+ */
+ } else if ((lp_security() == SEC_ADS)
+ && domain->active_directory
+ && (domain->domain_trust_attribs
+ & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)) {
+ smb_sign_client_connections = SMB_SIGNING_REQUIRED;
+ }
+ }
+
+ DEBUG(10,("cm_prepare_connection: connecting to DC %s for domain %s\n",
+ controller, domain->name ));
+
+ *retry = True;
+
+ mutex = grab_named_mutex(talloc_tos(), controller,
+ WINBIND_SERVER_MUTEX_WAIT_TIME);
+ if (mutex == NULL) {
+ close(sockfd);
+ DEBUG(0,("cm_prepare_connection: mutex grab failed for %s\n",
+ controller));
+ result = NT_STATUS_POSSIBLE_DEADLOCK;
+ goto done;
+ }
+
+ /*
+ * cm_prepare_connection() is responsible that sockfd does not leak.
+ * Once cli_state_create() returns with success, the
+ * smbXcli_conn_destructor() makes sure that close(sockfd) is finally
+ * called. Till that, close(sockfd) must be called on every unsuccessful
+ * return.
+ */
+ *cli = cli_state_create(NULL, sockfd, controller,
+ smb_sign_client_connections, flags);
+ if (*cli == NULL) {
+ close(sockfd);
+ DEBUG(1, ("Could not cli_initialize\n"));
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ cli_set_timeout(*cli, 10000); /* 10 seconds */
+
+ set_socket_options(sockfd, lp_socket_options());
+
+ result = smbXcli_negprot((*cli)->conn,
+ (*cli)->timeout,
+ lp_client_ipc_min_protocol(),
+ lp_client_ipc_max_protocol(),
+ NULL,
+ NULL,
+ NULL);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("cli_negprot failed: %s\n", nt_errstr(result)));
+ goto done;
+ }
+
+ if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_NT1 &&
+ smb1cli_conn_capabilities((*cli)->conn) & CAP_EXTENDED_SECURITY) {
+ try_ipc_auth = true;
+ } else if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_SMB2_02) {
+ try_ipc_auth = true;
+ } else if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) {
+ /*
+ * If we are forcing on SMB signing, then we must
+ * require authentication unless this is a one-way
+ * trust, and we have no stored user/password
+ */
+ try_ipc_auth = true;
+ }
+
+ if (IS_DC) {
+ /*
+ * As AD DC we only use netlogon and lsa
+ * using schannel over an anonymous transport
+ * (ncacn_ip_tcp or ncacn_np).
+ *
+ * Currently we always establish the SMB connection,
+ * even if we don't use it, because we later use ncacn_ip_tcp.
+ *
+ * As we won't use the SMB connection there's no
+ * need to try kerberos. And NT4 domains expect
+ * an anonymous IPC$ connection anyway.
+ */
+ try_ipc_auth = false;
+ }
+
+ if (try_ipc_auth) {
+ result = get_trust_credentials(domain, talloc_tos(), false, &creds);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("get_trust_credentials(%s) failed: %s\n",
+ domain->name, nt_errstr(result)));
+ goto done;
+ }
+ } else {
+ /*
+ * Without SPNEGO or NTLMSSP (perhaps via SMB2) we
+ * would try and authentication with our machine
+ * account password and fail. This is very rare in
+ * the modern world however
+ */
+ creds = cli_credentials_init_anon(talloc_tos());
+ if (creds == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ DEBUG(1, ("cli_credentials_init_anon(%s) failed: %s\n",
+ domain->name, nt_errstr(result)));
+ goto done;
+ }
+ }
+
+ machine_principal = cli_credentials_get_principal(creds,
+ talloc_tos());
+ machine_realm = cli_credentials_get_realm(creds);
+ machine_account = cli_credentials_get_username(creds);
+ machine_domain = cli_credentials_get_domain(creds);
+
+ DEBUG(5, ("connecting to %s (%s, %s) with account [%s\\%s] principal "
+ "[%s] and realm [%s]\n",
+ controller, domain->name, domain->alt_name,
+ machine_domain, machine_account,
+ machine_principal, machine_realm));
+
+ if (cli_credentials_is_anonymous(creds)) {
+ goto anon_fallback;
+ }
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ result = cli_session_setup_creds(*cli, creds);
+ if (NT_STATUS_IS_OK(result)) {
+ goto session_setup_done;
+ }
+
+ DEBUG(1, ("authenticated session setup to %s using %s failed with %s\n",
+ controller,
+ cli_credentials_get_unparsed_name(creds, talloc_tos()),
+ nt_errstr(result)));
+
+ /*
+ * If we are not going to validate the connection
+ * with SMB signing, then allow us to fall back to
+ * anonymous
+ */
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT)
+ || NT_STATUS_EQUAL(result, NT_STATUS_TRUSTED_DOMAIN_FAILURE)
+ || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_ACCOUNT_NAME)
+ || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_COMPUTER_NAME)
+ || NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_DOMAIN)
+ || NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS)
+ || NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE))
+ {
+ if (!cm_is_ipc_credentials(creds)) {
+ goto ipc_fallback;
+ }
+
+ if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) {
+ goto done;
+ }
+
+ goto anon_fallback;
+ }
+
+ goto done;
+
+ ipc_fallback:
+ TALLOC_FREE(creds);
+ tmp_status = cm_get_ipc_credentials(talloc_tos(), &creds);
+ if (!NT_STATUS_IS_OK(tmp_status)) {
+ result = tmp_status;
+ goto done;
+ }
+
+ if (cli_credentials_is_anonymous(creds)) {
+ goto anon_fallback;
+ }
+
+ machine_account = cli_credentials_get_username(creds);
+ machine_domain = cli_credentials_get_domain(creds);
+
+ DEBUG(5, ("connecting to %s from %s using NTLMSSP with username "
+ "[%s]\\[%s]\n", controller, lp_netbios_name(),
+ machine_domain, machine_account));
+
+ result = cli_session_setup_creds(*cli, creds);
+ if (NT_STATUS_IS_OK(result)) {
+ goto session_setup_done;
+ }
+
+ DEBUG(1, ("authenticated session setup to %s using %s failed with %s\n",
+ controller,
+ cli_credentials_get_unparsed_name(creds, talloc_tos()),
+ nt_errstr(result)));
+
+ /*
+ * If we are not going to validate the connection
+ * with SMB signing, then allow us to fall back to
+ * anonymous
+ */
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT)
+ || NT_STATUS_EQUAL(result, NT_STATUS_TRUSTED_DOMAIN_FAILURE)
+ || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_ACCOUNT_NAME)
+ || NT_STATUS_EQUAL(result, NT_STATUS_INVALID_COMPUTER_NAME)
+ || NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_DOMAIN)
+ || NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS)
+ || NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE))
+ {
+ goto anon_fallback;
+ }
+
+ goto done;
+
+ anon_fallback:
+ TALLOC_FREE(creds);
+
+ if (smb_sign_client_connections == SMB_SIGNING_REQUIRED) {
+ goto done;
+ }
+
+ /* Fall back to anonymous connection, this might fail later */
+ DEBUG(5,("cm_prepare_connection: falling back to anonymous "
+ "connection for DC %s\n",
+ controller ));
+
+ result = cli_session_setup_anon(*cli);
+ if (NT_STATUS_IS_OK(result)) {
+ DEBUG(5, ("Connected anonymously\n"));
+ goto session_setup_done;
+ }
+
+ DEBUG(1, ("anonymous session setup to %s failed with %s\n",
+ controller, nt_errstr(result)));
+
+ /* We can't session setup */
+ goto done;
+
+ session_setup_done:
+ TALLOC_FREE(creds);
+
+ /*
+ * This should be a short term hack until
+ * dynamic re-authentication is implemented.
+ *
+ * See Bug 9175 - winbindd doesn't recover from
+ * NT_STATUS_NETWORK_SESSION_EXPIRED
+ */
+ if (smbXcli_conn_protocol((*cli)->conn) >= PROTOCOL_SMB2_02) {
+ smbXcli_session_set_disconnect_expired((*cli)->smb2.session);
+ }
+
+ result = cli_tree_connect(*cli, "IPC$", "IPC", NULL);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1,("failed tcon_X with %s\n", nt_errstr(result)));
+ goto done;
+ }
+ tcon_status = result;
+
+ /* cache the server name for later connections */
+
+ saf_store(domain->name, controller);
+ if (domain->alt_name) {
+ saf_store(domain->alt_name, controller);
+ }
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ TALLOC_FREE(mutex);
+ *retry = False;
+
+ result = NT_STATUS_OK;
+
+ done:
+ TALLOC_FREE(mutex);
+ TALLOC_FREE(creds);
+
+ if (NT_STATUS_IS_OK(result)) {
+ result = tcon_status;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("Failed to prepare SMB connection to %s: %s\n",
+ controller, nt_errstr(result)));
+ winbind_add_failed_connection_entry(domain, controller, result);
+ if ((*cli) != NULL) {
+ cli_shutdown(*cli);
+ *cli = NULL;
+ }
+ }
+
+ return result;
+}
+
+/*******************************************************************
+ Add a dcname and sockaddr_storage pair to the end of a dc_name_ip
+ array.
+
+ Keeps the list unique by not adding duplicate entries.
+
+ @param[in] mem_ctx talloc memory context to allocate from
+ @param[in] domain_name domain of the DC
+ @param[in] dcname name of the DC to add to the list
+ @param[in] pss Internet address and port pair to add to the list
+ @param[in,out] dcs array of dc_name_ip structures to add to
+ @param[in,out] num_dcs number of dcs returned in the dcs array
+ @return true if the list was added to, false otherwise
+*******************************************************************/
+
+static bool add_one_dc_unique(TALLOC_CTX *mem_ctx, const char *domain_name,
+ const char *dcname, struct sockaddr_storage *pss,
+ struct dc_name_ip **dcs, int *num)
+{
+ int i = 0;
+
+ if (!NT_STATUS_IS_OK(check_negative_conn_cache(domain_name, dcname))) {
+ DEBUG(10, ("DC %s was in the negative conn cache\n", dcname));
+ return False;
+ }
+
+ /* Make sure there's no duplicates in the list */
+ for (i=0; i<*num; i++)
+ if (sockaddr_equal(
+ (struct sockaddr *)(void *)&(*dcs)[i].ss,
+ (struct sockaddr *)(void *)pss))
+ return False;
+
+ *dcs = talloc_realloc(mem_ctx, *dcs, struct dc_name_ip, (*num)+1);
+
+ if (*dcs == NULL)
+ return False;
+
+ fstrcpy((*dcs)[*num].name, dcname);
+ (*dcs)[*num].ss = *pss;
+ *num += 1;
+ return True;
+}
+
+static bool add_sockaddr_to_array(TALLOC_CTX *mem_ctx,
+ struct sockaddr_storage *pss, uint16_t port,
+ struct sockaddr_storage **addrs, int *num)
+{
+ *addrs = talloc_realloc(mem_ctx, *addrs, struct sockaddr_storage, (*num)+1);
+
+ if (*addrs == NULL) {
+ *num = 0;
+ return False;
+ }
+
+ (*addrs)[*num] = *pss;
+ set_sockaddr_port((struct sockaddr *)&(*addrs)[*num], port);
+
+ *num += 1;
+ return True;
+}
+
+#ifdef HAVE_ADS
+static bool dcip_check_name_ads(const struct winbindd_domain *domain,
+ struct samba_sockaddr *sa,
+ uint32_t request_flags,
+ TALLOC_CTX *mem_ctx,
+ char **namep)
+{
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ char *name = NULL;
+ ADS_STRUCT *ads = NULL;
+ ADS_STATUS ads_status;
+ char addr[INET6_ADDRSTRLEN];
+
+ print_sockaddr(addr, sizeof(addr), &sa->u.ss);
+ D_DEBUG("Trying to figure out the DC name for domain '%s' at IP '%s'.\n",
+ domain->name,
+ addr);
+
+ ads = ads_init(tmp_ctx,
+ domain->alt_name,
+ domain->name,
+ addr,
+ ADS_SASL_PLAIN);
+ if (ads == NULL) {
+ ads_status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ ads->auth.flags |= ADS_AUTH_NO_BIND;
+ ads->config.flags |= request_flags;
+ ads->server.no_fallback = true;
+
+ ads_status = ads_connect(ads);
+ if (!ADS_ERR_OK(ads_status)) {
+ goto out;
+ }
+
+ /* We got a cldap packet. */
+ name = talloc_strdup(tmp_ctx, ads->config.ldap_server_name);
+ if (name == NULL) {
+ ads_status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
+ goto out;
+ }
+ namecache_store(name, 0x20, 1, sa);
+
+ DBG_DEBUG("CLDAP flags = 0x%"PRIx32"\n", ads->config.flags);
+
+ if (domain->primary && (ads->config.flags & NBT_SERVER_KDC)) {
+ if (ads_closest_dc(ads)) {
+ char *sitename = sitename_fetch(tmp_ctx,
+ ads->config.realm);
+
+ /* We're going to use this KDC for this realm/domain.
+ If we are using sites, then force the krb5 libs
+ to use this KDC. */
+
+ create_local_private_krb5_conf_for_domain(domain->alt_name,
+ domain->name,
+ sitename,
+ &sa->u.ss);
+
+ TALLOC_FREE(sitename);
+ } else {
+ /* use an off site KDC */
+ create_local_private_krb5_conf_for_domain(domain->alt_name,
+ domain->name,
+ NULL,
+ &sa->u.ss);
+ }
+ winbindd_set_locator_kdc_envs(domain);
+
+ /* Ensure we contact this DC also. */
+ saf_store(domain->name, name);
+ saf_store(domain->alt_name, name);
+ }
+
+ D_DEBUG("DC name for domain '%s' at IP '%s' is '%s'\n",
+ domain->name,
+ addr,
+ name);
+ *namep = talloc_move(mem_ctx, &name);
+
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return ADS_ERR_OK(ads_status) ? true : false;
+}
+#endif
+
+/*******************************************************************
+ convert an ip to a name
+ For an AD Domain, it checks the requirements of the request flags.
+*******************************************************************/
+
+static bool dcip_check_name(TALLOC_CTX *mem_ctx,
+ const struct winbindd_domain *domain,
+ struct sockaddr_storage *pss,
+ char **name, uint32_t request_flags)
+{
+ struct samba_sockaddr sa = {0};
+ uint32_t nt_version = NETLOGON_NT_VERSION_1;
+ NTSTATUS status;
+ const char *dc_name;
+ fstring nbtname;
+#ifdef HAVE_ADS
+ bool is_ad_domain = false;
+#endif
+ bool ok = sockaddr_storage_to_samba_sockaddr(&sa, pss);
+ if (!ok) {
+ return false;
+ }
+
+#ifdef HAVE_ADS
+ /* For active directory servers, try to get the ldap server name.
+ None of these failures should be considered critical for now */
+
+ if ((lp_security() == SEC_ADS) && (domain->alt_name != NULL)) {
+ is_ad_domain = true;
+ } else if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ is_ad_domain = domain->active_directory;
+ }
+
+ if (is_ad_domain) {
+ return dcip_check_name_ads(domain,
+ &sa,
+ request_flags,
+ mem_ctx,
+ name);
+ }
+#endif
+
+ {
+ size_t len = strlen(lp_netbios_name());
+ char my_acct_name[len+2];
+
+ snprintf(my_acct_name,
+ sizeof(my_acct_name),
+ "%s$",
+ lp_netbios_name());
+
+ status = nbt_getdc(global_messaging_context(), 10, &sa.u.ss,
+ domain->name, &domain->sid,
+ my_acct_name, ACB_WSTRUST,
+ nt_version, mem_ctx, &nt_version,
+ &dc_name, NULL);
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ *name = talloc_strdup(mem_ctx, dc_name);
+ if (*name == NULL) {
+ return false;
+ }
+ namecache_store(*name, 0x20, 1, &sa);
+ return True;
+ }
+
+ /* try node status request */
+
+ if (name_status_find(domain->name, 0x1c, 0x20, &sa.u.ss, nbtname) ) {
+ namecache_store(nbtname, 0x20, 1, &sa);
+
+ if (name != NULL) {
+ *name = talloc_strdup(mem_ctx, nbtname);
+ if (*name == NULL) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ return False;
+}
+
+/*******************************************************************
+ Retrieve a list of IP addresses for domain controllers.
+
+ The array is sorted in the preferred connection order.
+
+ @param[in] mem_ctx talloc memory context to allocate from
+ @param[in] domain domain to retrieve DCs for
+ @param[out] dcs array of dcs that will be returned
+ @param[out] num_dcs number of dcs returned in the dcs array
+ @return always true
+*******************************************************************/
+
+static bool get_dcs(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
+ struct dc_name_ip **dcs, int *num_dcs,
+ uint32_t request_flags)
+{
+ fstring dcname;
+ struct sockaddr_storage ss;
+ struct samba_sockaddr *sa_list = NULL;
+ size_t salist_size = 0;
+ size_t i;
+ bool is_our_domain;
+ enum security_types sec = (enum security_types)lp_security();
+
+ is_our_domain = strequal(domain->name, lp_workgroup());
+
+ /* If not our domain, get the preferred DC, by asking our primary DC */
+ if ( !is_our_domain
+ && get_dc_name_via_netlogon(domain, dcname, &ss, request_flags)
+ && add_one_dc_unique(mem_ctx, domain->name, dcname, &ss, dcs,
+ num_dcs) )
+ {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr), &ss);
+ DEBUG(10, ("Retrieved DC %s at %s via netlogon\n",
+ dcname, addr));
+ return True;
+ }
+
+ if ((sec == SEC_ADS) && (domain->alt_name != NULL)) {
+ char *sitename = NULL;
+
+ /* We need to make sure we know the local site before
+ doing any DNS queries, as this will restrict the
+ get_sorted_dc_list() call below to only fetching
+ DNS records for the correct site. */
+
+ /* Find any DC to get the site record.
+ We deliberately don't care about the
+ return here. */
+
+ get_dc_name(domain->name, domain->alt_name, dcname, &ss);
+
+ sitename = sitename_fetch(mem_ctx, domain->alt_name);
+ if (sitename) {
+
+ /* Do the site-specific AD dns lookup first. */
+ (void)get_sorted_dc_list(mem_ctx,
+ domain->alt_name,
+ sitename,
+ &sa_list,
+ &salist_size,
+ true);
+
+ /* Add ips to the DC array. We don't look up the name
+ of the DC in this function, but we fill in the char*
+ of the ip now to make the failed connection cache
+ work */
+ for ( i=0; i<salist_size; i++ ) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr),
+ &sa_list[i].u.ss);
+ add_one_dc_unique(mem_ctx,
+ domain->name,
+ addr,
+ &sa_list[i].u.ss,
+ dcs,
+ num_dcs);
+ }
+
+ TALLOC_FREE(sa_list);
+ TALLOC_FREE(sitename);
+ salist_size = 0;
+ }
+
+ /* Now we add DCs from the main AD DNS lookup. */
+ (void)get_sorted_dc_list(mem_ctx,
+ domain->alt_name,
+ NULL,
+ &sa_list,
+ &salist_size,
+ true);
+
+ for ( i=0; i<salist_size; i++ ) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr),
+ &sa_list[i].u.ss);
+ add_one_dc_unique(mem_ctx,
+ domain->name,
+ addr,
+ &sa_list[i].u.ss,
+ dcs,
+ num_dcs);
+ }
+
+ TALLOC_FREE(sa_list);
+ salist_size = 0;
+ }
+
+ /* Try standard netbios queries if no ADS and fall back to DNS queries
+ * if alt_name is available */
+ if (*num_dcs == 0) {
+ (void)get_sorted_dc_list(mem_ctx,
+ domain->name,
+ NULL,
+ &sa_list,
+ &salist_size,
+ false);
+ if (salist_size == 0) {
+ if (domain->alt_name != NULL) {
+ (void)get_sorted_dc_list(mem_ctx,
+ domain->alt_name,
+ NULL,
+ &sa_list,
+ &salist_size,
+ true);
+ }
+ }
+
+ for ( i=0; i<salist_size; i++ ) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr),
+ &sa_list[i].u.ss);
+ add_one_dc_unique(mem_ctx,
+ domain->name,
+ addr,
+ &sa_list[i].u.ss,
+ dcs,
+ num_dcs);
+ }
+
+ TALLOC_FREE(sa_list);
+ salist_size = 0;
+ }
+
+ return True;
+}
+
+static bool connect_preferred_dc(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ uint32_t request_flags,
+ int *fd)
+{
+ char *saf_servername = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * We have to check the server affinity cache here since later we select
+ * a DC based on response time and not preference.
+ */
+ if (domain->force_dc) {
+ saf_servername = domain->dcname;
+ } else {
+ saf_servername = saf_fetch(mem_ctx, domain->name);
+ }
+
+ /*
+ * Check the negative connection cache before talking to it. It going
+ * down may have triggered the reconnection.
+ */
+ if (saf_servername != NULL) {
+ status = check_negative_conn_cache(domain->name,
+ saf_servername);
+ if (!NT_STATUS_IS_OK(status)) {
+ saf_servername = NULL;
+ }
+ }
+
+ if (saf_servername != NULL) {
+ DBG_DEBUG("saf_servername is '%s' for domain %s\n",
+ saf_servername, domain->name);
+
+ /* convert an ip address to a name */
+ if (is_ipaddress(saf_servername)) {
+ ok = interpret_string_addr(&domain->dcaddr,
+ saf_servername,
+ AI_NUMERICHOST);
+ if (!ok) {
+ return false;
+ }
+ } else {
+ ok = resolve_name(saf_servername,
+ &domain->dcaddr,
+ 0x20,
+ true);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ TALLOC_FREE(domain->dcname);
+ ok = dcip_check_name(domain,
+ domain,
+ &domain->dcaddr,
+ &domain->dcname,
+ request_flags);
+ if (!ok) {
+ goto fail;
+ }
+ }
+
+ if (domain->dcname == NULL) {
+ return false;
+ }
+
+ status = check_negative_conn_cache(domain->name, domain->dcname);
+ if (!NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ status = smbsock_connect(&domain->dcaddr, 0,
+ NULL, -1, NULL, -1,
+ fd, NULL, 10);
+ if (!NT_STATUS_IS_OK(status)) {
+ winbind_add_failed_connection_entry(domain,
+ domain->dcname,
+ NT_STATUS_UNSUCCESSFUL);
+ return false;
+ }
+ return true;
+
+fail:
+ winbind_add_failed_connection_entry(domain,
+ saf_servername,
+ NT_STATUS_UNSUCCESSFUL);
+ return false;
+
+}
+
+/*******************************************************************
+ Find and make a connection to a DC in the given domain.
+
+ @param[in] mem_ctx talloc memory context to allocate from
+ @param[in] domain domain to find a dc in
+ @param[out] fd fd of the open socket connected to the newly found dc
+ @return true when a DC connection is made, false otherwise
+*******************************************************************/
+
+static bool find_dc(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ uint32_t request_flags,
+ int *fd)
+{
+ struct dc_name_ip *dcs = NULL;
+ int num_dcs = 0;
+
+ const char **dcnames = NULL;
+ size_t num_dcnames = 0;
+
+ struct sockaddr_storage *addrs = NULL;
+ int num_addrs = 0;
+
+ int i;
+ size_t fd_index;
+
+ NTSTATUS status;
+ bool ok;
+
+ *fd = -1;
+
+ D_NOTICE("First try to connect to the closest DC (using server "
+ "affinity cache). If this fails, try to lookup the DC using "
+ "DNS afterwards.\n");
+ ok = connect_preferred_dc(mem_ctx, domain, request_flags, fd);
+ if (ok) {
+ return true;
+ }
+
+ if (domain->force_dc) {
+ return false;
+ }
+
+ again:
+ D_DEBUG("Retrieving a list of IP addresses for DCs.\n");
+ if (!get_dcs(mem_ctx, domain, &dcs, &num_dcs, request_flags) || (num_dcs == 0))
+ return False;
+
+ D_DEBUG("Retrieved IP addresses for %d DCs.\n", num_dcs);
+ for (i=0; i<num_dcs; i++) {
+
+ if (!add_string_to_array(mem_ctx, dcs[i].name,
+ &dcnames, &num_dcnames)) {
+ return False;
+ }
+ if (!add_sockaddr_to_array(mem_ctx, &dcs[i].ss, TCP_SMB_PORT,
+ &addrs, &num_addrs)) {
+ return False;
+ }
+ }
+
+ if ((num_dcnames == 0) || (num_dcnames != num_addrs))
+ return False;
+
+ if ((addrs == NULL) || (dcnames == NULL))
+ return False;
+
+ D_DEBUG("Trying to establish a connection to one of the %d DCs "
+ "(timeout of 10 sec for each DC).\n",
+ num_dcs);
+ status = smbsock_any_connect(addrs, dcnames, NULL, NULL, NULL,
+ num_addrs, 0, 10, fd, &fd_index, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ for (i=0; i<num_dcs; i++) {
+ char ab[INET6_ADDRSTRLEN];
+ print_sockaddr(ab, sizeof(ab), &dcs[i].ss);
+ DBG_DEBUG("smbsock_any_connect failed for "
+ "domain %s address %s. Error was %s\n",
+ domain->name, ab, nt_errstr(status));
+ winbind_add_failed_connection_entry(domain,
+ dcs[i].name, NT_STATUS_UNSUCCESSFUL);
+ }
+ return False;
+ }
+ D_NOTICE("Successfully connected to DC '%s'.\n", dcs[fd_index].name);
+
+ domain->dcaddr = addrs[fd_index];
+
+ if (*dcnames[fd_index] != '\0' && !is_ipaddress(dcnames[fd_index])) {
+ /* Ok, we've got a name for the DC */
+ TALLOC_FREE(domain->dcname);
+ domain->dcname = talloc_strdup(domain, dcnames[fd_index]);
+ if (domain->dcname == NULL) {
+ return false;
+ }
+ return true;
+ }
+
+ /* Try to figure out the name */
+ TALLOC_FREE(domain->dcname);
+ ok = dcip_check_name(domain,
+ domain,
+ &domain->dcaddr,
+ &domain->dcname,
+ request_flags);
+ if (ok) {
+ return true;
+ }
+
+ /* We can not continue without the DC's name */
+ winbind_add_failed_connection_entry(domain, dcs[fd_index].name,
+ NT_STATUS_UNSUCCESSFUL);
+
+ /* Throw away all arrays as we're doing this again. */
+ TALLOC_FREE(dcs);
+ num_dcs = 0;
+
+ TALLOC_FREE(dcnames);
+ num_dcnames = 0;
+
+ TALLOC_FREE(addrs);
+ num_addrs = 0;
+
+ if (*fd != -1) {
+ close(*fd);
+ *fd = -1;
+ }
+
+ /*
+ * This should not be an infinite loop, since get_dcs() will not return
+ * the DC added to the negative connection cache in the above
+ * winbind_add_failed_connection_entry() call.
+ */
+ goto again;
+}
+
+static char *current_dc_key(TALLOC_CTX *mem_ctx, const char *domain_name)
+{
+ return talloc_asprintf_strupper_m(mem_ctx, "CURRENT_DCNAME/%s",
+ domain_name);
+}
+
+static void store_current_dc_in_gencache(const char *domain_name,
+ const char *dc_name,
+ struct cli_state *cli)
+{
+ char addr[INET6_ADDRSTRLEN];
+ char *key = NULL;
+ char *value = NULL;
+
+ if (!cli_state_is_connected(cli)) {
+ return;
+ }
+
+ print_sockaddr(addr, sizeof(addr),
+ smbXcli_conn_remote_sockaddr(cli->conn));
+
+ key = current_dc_key(talloc_tos(), domain_name);
+ if (key == NULL) {
+ goto done;
+ }
+
+ value = talloc_asprintf(talloc_tos(), "%s %s", addr, dc_name);
+ if (value == NULL) {
+ goto done;
+ }
+
+ gencache_set(key, value, 0x7fffffff);
+done:
+ TALLOC_FREE(value);
+ TALLOC_FREE(key);
+}
+
+bool fetch_current_dc_from_gencache(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ char **p_dc_name, char **p_dc_ip)
+{
+ char *key, *p;
+ char *value = NULL;
+ bool ret = false;
+ char *dc_name = NULL;
+ char *dc_ip = NULL;
+
+ key = current_dc_key(talloc_tos(), domain_name);
+ if (key == NULL) {
+ goto done;
+ }
+ if (!gencache_get(key, mem_ctx, &value, NULL)) {
+ goto done;
+ }
+ p = strchr(value, ' ');
+ if (p == NULL) {
+ goto done;
+ }
+ dc_ip = talloc_strndup(mem_ctx, value, p - value);
+ if (dc_ip == NULL) {
+ goto done;
+ }
+ dc_name = talloc_strdup(mem_ctx, p+1);
+ if (dc_name == NULL) {
+ goto done;
+ }
+
+ if (p_dc_ip != NULL) {
+ *p_dc_ip = dc_ip;
+ dc_ip = NULL;
+ }
+ if (p_dc_name != NULL) {
+ *p_dc_name = dc_name;
+ dc_name = NULL;
+ }
+ ret = true;
+done:
+ TALLOC_FREE(dc_name);
+ TALLOC_FREE(dc_ip);
+ TALLOC_FREE(key);
+ TALLOC_FREE(value);
+ return ret;
+}
+
+NTSTATUS wb_open_internal_pipe(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **ret_pipe)
+{
+ struct rpc_pipe_client *cli = NULL;
+ const struct auth_session_info *session_info = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+
+ session_info = get_session_info_system();
+ SMB_ASSERT(session_info != NULL);
+
+ status = rpc_pipe_open_local_np(
+ mem_ctx, table, NULL, NULL, NULL, NULL, session_info, &cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (ret_pipe) {
+ *ret_pipe = cli;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS cm_open_connection(struct winbindd_domain *domain,
+ struct winbindd_cm_conn *new_conn,
+ bool need_rw_dc)
+{
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ int retries;
+ uint32_t request_flags = need_rw_dc ? DS_WRITABLE_REQUIRED : 0;
+ int fd = -1;
+ bool retry = false;
+ bool seal_pipes = true;
+
+ if ((mem_ctx = talloc_init("cm_open_connection")) == NULL) {
+ set_domain_offline(domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ D_NOTICE("Creating connection to domain controller. This is a start of "
+ "a new connection or a DC failover. The failover only happens "
+ "if the domain has more than one DC. We will try to connect 3 "
+ "times at most.\n");
+ for (retries = 0; retries < 3; retries++) {
+ bool found_dc;
+
+ D_DEBUG("Attempt %d/3: DC '%s' of domain '%s'.\n",
+ retries,
+ domain->dcname ? domain->dcname : "",
+ domain->name);
+
+ found_dc = find_dc(mem_ctx, domain, request_flags, &fd);
+ if (!found_dc) {
+ /* This is the one place where we will
+ set the global winbindd offline state
+ to true, if a "WINBINDD_OFFLINE" entry
+ is found in the winbindd cache. */
+ set_global_winbindd_state_offline();
+ result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ break;
+ }
+
+ new_conn->cli = NULL;
+
+ result = cm_prepare_connection(domain, fd, domain->dcname,
+ &new_conn->cli, &retry);
+ if (NT_STATUS_IS_OK(result)) {
+ break;
+ }
+ if (!retry) {
+ break;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ /* Ensure we setup the retry handler. */
+ set_domain_offline(domain);
+ goto out;
+ }
+
+ winbindd_set_locator_kdc_envs(domain);
+
+ if (domain->online == False) {
+ /* We're changing state from offline to online. */
+ set_global_winbindd_state_online();
+ }
+ set_domain_online(domain);
+
+ /*
+ * Much as I hate global state, this seems to be the point
+ * where we can be certain that we have a proper connection to
+ * a DC. wbinfo --dc-info needs that information, store it in
+ * gencache with a looong timeout. This will need revisiting
+ * once we start to connect to multiple DCs, wbcDcInfo is
+ * already prepared for that.
+ */
+ store_current_dc_in_gencache(domain->name, domain->dcname,
+ new_conn->cli);
+
+ seal_pipes = lp_winbind_sealed_pipes();
+ seal_pipes = lp_parm_bool(-1, "winbind sealed pipes",
+ domain->name,
+ seal_pipes);
+
+ if (seal_pipes) {
+ new_conn->auth_level = DCERPC_AUTH_LEVEL_PRIVACY;
+ } else {
+ new_conn->auth_level = DCERPC_AUTH_LEVEL_INTEGRITY;
+ }
+
+out:
+ talloc_destroy(mem_ctx);
+ return result;
+}
+
+/* Close down all open pipes on a connection. */
+
+void invalidate_cm_connection(struct winbindd_domain *domain)
+{
+ NTSTATUS result;
+ struct winbindd_cm_conn *conn = &domain->conn;
+
+ domain->sequence_number = DOM_SEQUENCE_NONE;
+ domain->last_seq_check = 0;
+ domain->last_status = NT_STATUS_SERVER_DISABLED;
+
+ /* We're closing down a possibly dead
+ connection. Don't have impossibly long (10s) timeouts. */
+
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 1000); /* 1 second. */
+ }
+
+ if (conn->samr_pipe != NULL) {
+ if (is_valid_policy_hnd(&conn->sam_connect_handle)) {
+ dcerpc_samr_Close(conn->samr_pipe->binding_handle,
+ talloc_tos(),
+ &conn->sam_connect_handle,
+ &result);
+ }
+ TALLOC_FREE(conn->samr_pipe);
+ /* Ok, it must be dead. Drop timeout to 0.5 sec. */
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 500);
+ }
+ }
+
+ if (conn->lsa_pipe != NULL) {
+ if (is_valid_policy_hnd(&conn->lsa_policy)) {
+ dcerpc_lsa_Close(conn->lsa_pipe->binding_handle,
+ talloc_tos(),
+ &conn->lsa_policy,
+ &result);
+ }
+ TALLOC_FREE(conn->lsa_pipe);
+ /* Ok, it must be dead. Drop timeout to 0.5 sec. */
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 500);
+ }
+ }
+
+ if (conn->lsa_pipe_tcp != NULL) {
+ if (is_valid_policy_hnd(&conn->lsa_policy)) {
+ dcerpc_lsa_Close(conn->lsa_pipe_tcp->binding_handle,
+ talloc_tos(),
+ &conn->lsa_policy,
+ &result);
+ }
+ TALLOC_FREE(conn->lsa_pipe_tcp);
+ /* Ok, it must be dead. Drop timeout to 0.5 sec. */
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 500);
+ }
+ }
+
+ if (conn->netlogon_pipe != NULL) {
+ TALLOC_FREE(conn->netlogon_pipe);
+ /* Ok, it must be dead. Drop timeout to 0.5 sec. */
+ if (conn->cli) {
+ cli_set_timeout(conn->cli, 500);
+ }
+ }
+
+ conn->auth_level = DCERPC_AUTH_LEVEL_PRIVACY;
+ TALLOC_FREE(conn->netlogon_creds_ctx);
+
+ if (conn->cli) {
+ cli_shutdown(conn->cli);
+ }
+
+ conn->cli = NULL;
+}
+
+void close_conns_after_fork(void)
+{
+ struct winbindd_domain *domain;
+ struct winbindd_cli_state *cli_state;
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ /*
+ * first close the low level SMB TCP connection
+ * so that we don't generate any SMBclose
+ * requests in invalidate_cm_connection()
+ */
+ if (cli_state_is_connected(domain->conn.cli)) {
+ smbXcli_conn_disconnect(domain->conn.cli->conn, NT_STATUS_OK);
+ }
+
+ invalidate_cm_connection(domain);
+ }
+
+ for (cli_state = winbindd_client_list();
+ cli_state != NULL;
+ cli_state = cli_state->next) {
+ if (cli_state->sock >= 0) {
+ close(cli_state->sock);
+ cli_state->sock = -1;
+ }
+ }
+}
+
+static bool connection_ok(struct winbindd_domain *domain)
+{
+ bool ok;
+
+ ok = cli_state_is_connected(domain->conn.cli);
+ if (!ok) {
+ DEBUG(3, ("connection_ok: Connection to %s for domain %s is not connected\n",
+ domain->dcname, domain->name));
+ return False;
+ }
+
+ if (!domain->online) {
+ DEBUG(3, ("connection_ok: Domain %s is offline\n", domain->name));
+ return False;
+ }
+
+ return True;
+}
+
+/* Initialize a new connection up to the RPC BIND.
+ Bypass online status check so always does network calls. */
+
+static NTSTATUS init_dc_connection_network(struct winbindd_domain *domain, bool need_rw_dc)
+{
+ NTSTATUS result;
+ bool skip_connection = domain->internal;
+ if (need_rw_dc && domain->rodc) {
+ skip_connection = false;
+ }
+
+ /* Internal connections never use the network. */
+ if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) {
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ /* Still ask the internal LSA and SAMR server about the local domain */
+ if (skip_connection || connection_ok(domain)) {
+ if (!domain->initialized) {
+ set_dc_type_and_flags(domain);
+ }
+ return NT_STATUS_OK;
+ }
+
+ invalidate_cm_connection(domain);
+
+ if (!domain->primary && !domain->initialized) {
+ /*
+ * Before we connect to a trust, work out if it is an
+ * AD domain by asking our own domain.
+ */
+ set_dc_type_and_flags_trustinfo(domain);
+ }
+
+ result = cm_open_connection(domain, &domain->conn, need_rw_dc);
+
+ if (NT_STATUS_IS_OK(result) && !domain->initialized) {
+ set_dc_type_and_flags(domain);
+ }
+
+ return result;
+}
+
+NTSTATUS init_dc_connection(struct winbindd_domain *domain, bool need_rw_dc)
+{
+ if (dom_sid_equal(&domain->sid, &global_sid_Builtin)) {
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ SMB_ASSERT(wb_child_domain() || idmap_child());
+
+ return init_dc_connection_network(domain, need_rw_dc);
+}
+
+static NTSTATUS init_dc_connection_rpc(struct winbindd_domain *domain, bool need_rw_dc)
+{
+ NTSTATUS status;
+
+ status = init_dc_connection(domain, need_rw_dc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!domain->internal && domain->conn.cli == NULL) {
+ /* happens for trusted domains without inbound trust */
+ return NT_STATUS_TRUSTED_DOMAIN_FAILURE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/******************************************************************************
+ Set the trust flags (direction and forest location) for a domain
+******************************************************************************/
+
+static bool set_dc_type_and_flags_trustinfo( struct winbindd_domain *domain )
+{
+ struct winbindd_domain *our_domain;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr;
+ struct netr_DomainTrustList trusts;
+ int i;
+ uint32_t flags = (NETR_TRUST_FLAG_IN_FOREST |
+ NETR_TRUST_FLAG_OUTBOUND |
+ NETR_TRUST_FLAG_INBOUND);
+ struct rpc_pipe_client *cli;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct dcerpc_binding_handle *b;
+
+ if (IS_DC) {
+ /*
+ * On a DC we loaded all trusts
+ * from configuration and never learn
+ * new domains.
+ */
+ return true;
+ }
+
+ DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s\n", domain->name ));
+
+ /* Our primary domain doesn't need to worry about trust flags.
+ Force it to go through the network setup */
+ if ( domain->primary ) {
+ return False;
+ }
+
+ mem_ctx = talloc_stackframe();
+ our_domain = find_our_domain();
+ if (our_domain->internal) {
+ result = init_dc_connection(our_domain, false);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3,("set_dc_type_and_flags_trustinfo: "
+ "Not able to make a connection to our domain: %s\n",
+ nt_errstr(result)));
+ TALLOC_FREE(mem_ctx);
+ return false;
+ }
+ }
+
+ /* This won't work unless our domain is AD */
+ if ( !our_domain->active_directory ) {
+ TALLOC_FREE(mem_ctx);
+ return False;
+ }
+
+ if (our_domain->internal) {
+ result = wb_open_internal_pipe(mem_ctx, &ndr_table_netlogon, &cli);
+ } else if (!connection_ok(our_domain)) {
+ DEBUG(3,("set_dc_type_and_flags_trustinfo: "
+ "No connection to our domain!\n"));
+ TALLOC_FREE(mem_ctx);
+ return False;
+ } else {
+ result = cm_connect_netlogon(our_domain, &cli);
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(5, ("set_dc_type_and_flags_trustinfo: Could not open "
+ "a connection to %s for PIPE_NETLOGON (%s)\n",
+ domain->name, nt_errstr(result)));
+ TALLOC_FREE(mem_ctx);
+ return False;
+ }
+ b = cli->binding_handle;
+
+ /* Use DsEnumerateDomainTrusts to get us the trust direction and type. */
+ result = dcerpc_netr_DsrEnumerateDomainTrusts(b, mem_ctx,
+ cli->desthost,
+ flags,
+ &trusts,
+ &werr);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("set_dc_type_and_flags_trustinfo: "
+ "failed to query trusted domain list: %s\n",
+ nt_errstr(result)));
+ TALLOC_FREE(mem_ctx);
+ return false;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(0,("set_dc_type_and_flags_trustinfo: "
+ "failed to query trusted domain list: %s\n",
+ win_errstr(werr)));
+ TALLOC_FREE(mem_ctx);
+ return false;
+ }
+
+ /* Now find the domain name and get the flags */
+
+ for ( i=0; i<trusts.count; i++ ) {
+ if ( strequal( domain->name, trusts.array[i].netbios_name) ) {
+ domain->domain_flags = trusts.array[i].trust_flags;
+ domain->domain_type = trusts.array[i].trust_type;
+ domain->domain_trust_attribs = trusts.array[i].trust_attributes;
+
+ if ( domain->domain_type == LSA_TRUST_TYPE_UPLEVEL )
+ domain->active_directory = True;
+
+ /* This flag is only set if the domain is *our*
+ primary domain and the primary domain is in
+ native mode */
+
+ domain->native_mode = (domain->domain_flags & NETR_TRUST_FLAG_NATIVE);
+
+ DEBUG(5, ("set_dc_type_and_flags_trustinfo: domain %s is %sin "
+ "native mode.\n", domain->name,
+ domain->native_mode ? "" : "NOT "));
+
+ DEBUG(5,("set_dc_type_and_flags_trustinfo: domain %s is %s"
+ "running active directory.\n", domain->name,
+ domain->active_directory ? "" : "NOT "));
+
+ domain->can_do_ncacn_ip_tcp = domain->active_directory;
+
+ domain->initialized = True;
+
+ break;
+ }
+ }
+
+ TALLOC_FREE(mem_ctx);
+
+ return domain->initialized;
+}
+
+/******************************************************************************
+ We can 'sense' certain things about the DC by it's replies to certain
+ questions.
+
+ This tells us if this particular remote server is Active Directory, and if it
+ is native mode.
+******************************************************************************/
+
+static void set_dc_type_and_flags_connect( struct winbindd_domain *domain )
+{
+ NTSTATUS status, result;
+ NTSTATUS close_status = NT_STATUS_UNSUCCESSFUL;
+ WERROR werr;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct rpc_pipe_client *cli = NULL;
+ struct policy_handle pol = { .handle_type = 0 };
+ union dssetup_DsRoleInfo info;
+ union lsa_PolicyInformation *lsa_info = NULL;
+ union lsa_revision_info out_revision_info = {
+ .info1 = {
+ .revision = 0,
+ },
+ };
+ uint32_t out_version = 0;
+
+ if (!domain->internal && !connection_ok(domain)) {
+ return;
+ }
+
+ mem_ctx = talloc_init("set_dc_type_and_flags on domain %s\n",
+ domain->name);
+ if (!mem_ctx) {
+ DEBUG(1, ("set_dc_type_and_flags_connect: talloc_init() failed\n"));
+ return;
+ }
+
+ DEBUG(5, ("set_dc_type_and_flags_connect: domain %s\n", domain->name ));
+
+ if (domain->internal) {
+ status = wb_open_internal_pipe(mem_ctx,
+ &ndr_table_dssetup,
+ &cli);
+ } else {
+ status = cli_rpc_pipe_open_noauth(domain->conn.cli,
+ &ndr_table_dssetup,
+ &cli);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to "
+ "PI_DSSETUP on domain %s: (%s)\n",
+ domain->name, nt_errstr(status)));
+
+ /* if this is just a non-AD domain we need to continue
+ * identifying so that we can in the end return with
+ * domain->initialized = True - gd */
+
+ goto no_dssetup;
+ }
+
+ status = dcerpc_dssetup_DsRoleGetPrimaryDomainInformation(cli->binding_handle, mem_ctx,
+ DS_ROLE_BASIC_INFORMATION,
+ &info,
+ &werr);
+ TALLOC_FREE(cli);
+
+ if (NT_STATUS_IS_OK(status)) {
+ result = werror_to_ntstatus(werr);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("set_dc_type_and_flags_connect: rpccli_ds_getprimarydominfo "
+ "on domain %s failed: (%s)\n",
+ domain->name, nt_errstr(status)));
+
+ /* older samba3 DCs will return DCERPC_FAULT_OP_RNG_ERROR for
+ * every opcode on the DSSETUP pipe, continue with
+ * no_dssetup mode here as well to get domain->initialized
+ * set - gd */
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+ goto no_dssetup;
+ }
+
+ TALLOC_FREE(mem_ctx);
+ return;
+ }
+
+ if ((info.basic.flags & DS_ROLE_PRIMARY_DS_RUNNING) &&
+ !(info.basic.flags & DS_ROLE_PRIMARY_DS_MIXED_MODE)) {
+ domain->native_mode = True;
+ } else {
+ domain->native_mode = False;
+ }
+
+no_dssetup:
+ if (domain->internal) {
+ status = wb_open_internal_pipe(mem_ctx,
+ &ndr_table_lsarpc,
+ &cli);
+ } else {
+ status = cli_rpc_pipe_open_noauth(domain->conn.cli,
+ &ndr_table_lsarpc, &cli);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(5, ("set_dc_type_and_flags_connect: Could not bind to "
+ "PI_LSARPC on domain %s: (%s)\n",
+ domain->name, nt_errstr(status)));
+ TALLOC_FREE(cli);
+ TALLOC_FREE(mem_ctx);
+ return;
+ }
+
+ status = dcerpc_lsa_open_policy_fallback(cli->binding_handle,
+ mem_ctx,
+ cli->srv_name_slash,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &out_version,
+ &out_revision_info,
+ &pol,
+ &result);
+
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ /* This particular query is exactly what Win2k clients use
+ to determine that the DC is active directory */
+ status = dcerpc_lsa_QueryInfoPolicy2(cli->binding_handle, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_DNS,
+ &lsa_info,
+ &result);
+ }
+
+ /*
+ * If the status and result will not be OK we will fallback to
+ * OpenPolicy.
+ */
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ domain->active_directory = True;
+
+ if (lsa_info->dns.name.string) {
+ if (!strequal(domain->name, lsa_info->dns.name.string))
+ {
+ DEBUG(1, ("set_dc_type_and_flags_connect: DC "
+ "for domain %s claimed it was a DC "
+ "for domain %s, refusing to "
+ "initialize\n",
+ domain->name,
+ lsa_info->dns.name.string));
+ TALLOC_FREE(cli);
+ TALLOC_FREE(mem_ctx);
+ return;
+ }
+ talloc_free(domain->name);
+ domain->name = talloc_strdup(domain,
+ lsa_info->dns.name.string);
+ if (domain->name == NULL) {
+ goto done;
+ }
+ }
+
+ if (lsa_info->dns.dns_domain.string) {
+ if (domain->alt_name != NULL &&
+ !strequal(domain->alt_name,
+ lsa_info->dns.dns_domain.string))
+ {
+ DEBUG(1, ("set_dc_type_and_flags_connect: DC "
+ "for domain %s (%s) claimed it was "
+ "a DC for domain %s, refusing to "
+ "initialize\n",
+ domain->alt_name, domain->name,
+ lsa_info->dns.dns_domain.string));
+ TALLOC_FREE(cli);
+ TALLOC_FREE(mem_ctx);
+ return;
+ }
+ talloc_free(domain->alt_name);
+ domain->alt_name =
+ talloc_strdup(domain,
+ lsa_info->dns.dns_domain.string);
+ if (domain->alt_name == NULL) {
+ goto done;
+ }
+ }
+
+ /* See if we can set some domain trust flags about
+ ourself */
+
+ if (lsa_info->dns.dns_forest.string) {
+ talloc_free(domain->forest_name);
+ domain->forest_name =
+ talloc_strdup(domain,
+ lsa_info->dns.dns_forest.string);
+ if (domain->forest_name == NULL) {
+ goto done;
+ }
+
+ if (strequal(domain->forest_name, domain->alt_name)) {
+ domain->domain_flags |= NETR_TRUST_FLAG_TREEROOT;
+ }
+ }
+
+ if (lsa_info->dns.sid) {
+ if (!is_null_sid(&domain->sid) &&
+ !dom_sid_equal(&domain->sid,
+ lsa_info->dns.sid))
+ {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(1, ("set_dc_type_and_flags_connect: DC "
+ "for domain %s (%s) claimed it was "
+ "a DC for domain %s, refusing to "
+ "initialize\n",
+ dom_sid_str_buf(&domain->sid, &buf1),
+ domain->name,
+ dom_sid_str_buf(lsa_info->dns.sid,
+ &buf2)));
+ TALLOC_FREE(cli);
+ TALLOC_FREE(mem_ctx);
+ return;
+ }
+ sid_copy(&domain->sid, lsa_info->dns.sid);
+ }
+ } else {
+ domain->active_directory = False;
+
+ status = rpccli_lsa_open_policy(cli, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &pol);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_lsa_QueryInfoPolicy(cli->binding_handle, mem_ctx,
+ &pol,
+ LSA_POLICY_INFO_ACCOUNT_DOMAIN,
+ &lsa_info,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+
+ if (lsa_info->account_domain.name.string) {
+ if (!strequal(domain->name,
+ lsa_info->account_domain.name.string))
+ {
+ DEBUG(1,
+ ("set_dc_type_and_flags_connect: "
+ "DC for domain %s claimed it was"
+ " a DC for domain %s, refusing "
+ "to initialize\n", domain->name,
+ lsa_info->
+ account_domain.name.string));
+ TALLOC_FREE(cli);
+ TALLOC_FREE(mem_ctx);
+ return;
+ }
+ talloc_free(domain->name);
+ domain->name =
+ talloc_strdup(domain,
+ lsa_info->account_domain.name.string);
+ }
+
+ if (lsa_info->account_domain.sid) {
+ if (!is_null_sid(&domain->sid) &&
+ !dom_sid_equal(&domain->sid,
+ lsa_info->account_domain.sid))
+ {
+ struct dom_sid_buf buf1, buf2;
+ DEBUG(1,
+ ("set_dc_type_and_flags_connect: "
+ "DC for domain %s (%s) claimed "
+ "it was a DC for domain %s, "
+ "refusing to initialize\n",
+ dom_sid_str_buf(
+ &domain->sid, &buf1),
+ domain->name,
+ dom_sid_str_buf(
+ lsa_info->account_domain.sid,
+ &buf2)));
+ TALLOC_FREE(cli);
+ TALLOC_FREE(mem_ctx);
+ return;
+ }
+ sid_copy(&domain->sid, lsa_info->account_domain.sid);
+ }
+ }
+ }
+done:
+ if (is_valid_policy_hnd(&pol)) {
+ dcerpc_lsa_Close(cli->binding_handle,
+ mem_ctx,
+ &pol,
+ &close_status);
+ }
+
+ DEBUG(5, ("set_dc_type_and_flags_connect: domain %s is %sin native mode.\n",
+ domain->name, domain->native_mode ? "" : "NOT "));
+
+ DEBUG(5,("set_dc_type_and_flags_connect: domain %s is %srunning active directory.\n",
+ domain->name, domain->active_directory ? "" : "NOT "));
+
+ domain->can_do_ncacn_ip_tcp = domain->active_directory;
+
+ TALLOC_FREE(cli);
+
+ TALLOC_FREE(mem_ctx);
+
+ domain->initialized = True;
+}
+
+/**********************************************************************
+ Set the domain_flags (trust attributes, domain operating modes, etc...
+***********************************************************************/
+
+static void set_dc_type_and_flags( struct winbindd_domain *domain )
+{
+ if (IS_DC) {
+ /*
+ * On a DC we loaded all trusts
+ * from configuration and never learn
+ * new domains.
+ */
+ return;
+ }
+
+ /* we always have to contact our primary domain */
+
+ if ( domain->primary || domain->internal) {
+ DEBUG(10,("set_dc_type_and_flags: setting up flags for "
+ "primary or internal domain\n"));
+ set_dc_type_and_flags_connect( domain );
+ return;
+ }
+
+ /* Use our DC to get the information if possible */
+
+ if ( !set_dc_type_and_flags_trustinfo( domain ) ) {
+ /* Otherwise, fallback to contacting the
+ domain directly */
+ set_dc_type_and_flags_connect( domain );
+ }
+
+ return;
+}
+
+
+
+/**********************************************************************
+***********************************************************************/
+
+static NTSTATUS cm_get_schannel_creds(struct winbindd_domain *domain,
+ struct netlogon_creds_cli_context **ppdc)
+{
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ struct rpc_pipe_client *netlogon_pipe;
+
+ *ppdc = NULL;
+
+ if ((!IS_DC) && (!domain->primary)) {
+ return NT_STATUS_TRUSTED_DOMAIN_FAILURE;
+ }
+
+ if (domain->conn.netlogon_creds_ctx != NULL) {
+ *ppdc = domain->conn.netlogon_creds_ctx;
+ return NT_STATUS_OK;
+ }
+
+ result = cm_connect_netlogon_secure(domain, &netlogon_pipe, ppdc);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS cm_connect_sam(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx,
+ bool need_rw_dc,
+ struct rpc_pipe_client **cli, struct policy_handle *sam_handle)
+{
+ struct winbindd_cm_conn *conn;
+ NTSTATUS status, result;
+ struct netlogon_creds_cli_context *p_creds;
+ struct cli_credentials *creds = NULL;
+ bool retry = false; /* allow one retry attempt for expired session */
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+ bool sealed_pipes = true;
+ bool strong_key = true;
+
+ if (sid_check_is_our_sam(&domain->sid)) {
+ if (domain->rodc == false || need_rw_dc == false) {
+ return open_internal_samr_conn(mem_ctx, domain, cli, sam_handle);
+ }
+ }
+
+ if (IS_AD_DC) {
+ /*
+ * In theory we should not use SAMR within
+ * winbindd at all, but that's a larger task to
+ * remove this and avoid breaking existing
+ * setups.
+ *
+ * At least as AD DC we have the restriction
+ * to avoid SAMR against trusted domains,
+ * as there're no existing setups.
+ */
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+retry:
+ status = init_dc_connection_rpc(domain, need_rw_dc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ conn = &domain->conn;
+
+ if (rpccli_is_connected(conn->samr_pipe)) {
+ goto done;
+ }
+
+ TALLOC_FREE(conn->samr_pipe);
+
+ /*
+ * No SAMR pipe yet. Attempt to get an NTLMSSP SPNEGO authenticated
+ * sign and sealed pipe using the machine account password by
+ * preference. If we can't - try schannel, if that fails, try
+ * anonymous.
+ */
+
+ result = get_trust_credentials(domain, talloc_tos(), false, &creds);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("cm_connect_sam: No user available for "
+ "domain %s, trying schannel\n", domain->name));
+ goto schannel;
+ }
+
+ if (cli_credentials_is_anonymous(creds)) {
+ goto anonymous;
+ }
+
+ remote_name = smbXcli_conn_remote_name(conn->cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn);
+
+ /*
+ * We have an authenticated connection. Use a SPNEGO
+ * authenticated SAMR pipe with sign & seal.
+ */
+ status = cli_rpc_pipe_open_with_creds(conn->cli,
+ &ndr_table_samr,
+ NCACN_NP,
+ DCERPC_AUTH_TYPE_SPNEGO,
+ conn->auth_level,
+ remote_name,
+ remote_sockaddr,
+ creds,
+ &conn->samr_pipe);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)
+ && !retry) {
+ invalidate_cm_connection(domain);
+ retry = true;
+ goto retry;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("cm_connect_sam: failed to connect to SAMR "
+ "pipe for domain %s using NTLMSSP "
+ "authenticated pipe: user %s. Error was "
+ "%s\n", domain->name,
+ cli_credentials_get_unparsed_name(creds, talloc_tos()),
+ nt_errstr(status)));
+ goto schannel;
+ }
+
+ DEBUG(10,("cm_connect_sam: connected to SAMR pipe for "
+ "domain %s using NTLMSSP authenticated "
+ "pipe: user %s\n", domain->name,
+ cli_credentials_get_unparsed_name(creds, talloc_tos())));
+
+ status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx,
+ conn->samr_pipe->desthost,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &conn->sam_connect_handle,
+ &result);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) {
+ invalidate_cm_connection(domain);
+ TALLOC_FREE(conn->samr_pipe);
+ retry = true;
+ goto retry;
+ }
+
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ goto open_domain;
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ status = result;
+ }
+
+ DEBUG(10,("cm_connect_sam: ntlmssp-sealed dcerpc_samr_Connect2 "
+ "failed for domain %s, error was %s. Trying schannel\n",
+ domain->name, nt_errstr(status) ));
+ TALLOC_FREE(conn->samr_pipe);
+
+ schannel:
+
+ /* Fall back to schannel if it's a W2K pre-SP1 box. */
+
+ status = cm_get_schannel_creds(domain, &p_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ /* If this call fails - conn->cli can now be NULL ! */
+ DEBUG(10, ("cm_connect_sam: Could not get schannel auth info "
+ "for domain %s (error %s), trying anon\n",
+ domain->name,
+ nt_errstr(status) ));
+ goto anonymous;
+ }
+ TALLOC_FREE(creds);
+ status = cli_rpc_pipe_open_schannel_with_creds(
+ conn->cli, &ndr_table_samr, NCACN_NP, p_creds,
+ remote_name,
+ remote_sockaddr,
+ &conn->samr_pipe);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)
+ && !retry) {
+ invalidate_cm_connection(domain);
+ retry = true;
+ goto retry;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("cm_connect_sam: failed to connect to SAMR pipe for "
+ "domain %s using schannel. Error was %s\n",
+ domain->name, nt_errstr(status) ));
+ goto anonymous;
+ }
+ DEBUG(10,("cm_connect_sam: connected to SAMR pipe for domain %s using "
+ "schannel.\n", domain->name ));
+
+ status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx,
+ conn->samr_pipe->desthost,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &conn->sam_connect_handle,
+ &result);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) {
+ invalidate_cm_connection(domain);
+ TALLOC_FREE(conn->samr_pipe);
+ retry = true;
+ goto retry;
+ }
+
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ goto open_domain;
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ status = result;
+ }
+ DEBUG(10,("cm_connect_sam: schannel-sealed dcerpc_samr_Connect2 failed "
+ "for domain %s, error was %s. Trying anonymous\n",
+ domain->name, nt_errstr(status) ));
+ TALLOC_FREE(conn->samr_pipe);
+
+ anonymous:
+
+ sealed_pipes = lp_winbind_sealed_pipes();
+ sealed_pipes = lp_parm_bool(-1, "winbind sealed pipes",
+ domain->name,
+ sealed_pipes);
+ strong_key = lp_require_strong_key();
+ strong_key = lp_parm_bool(-1, "require strong key",
+ domain->name,
+ strong_key);
+
+ /* Finally fall back to anonymous. */
+ if (sealed_pipes || strong_key) {
+ status = NT_STATUS_DOWNGRADE_DETECTED;
+ DEBUG(1, ("Unwilling to make SAMR connection to domain %s "
+ "without connection level security, "
+ "must set 'winbind sealed pipes:%s = false' and "
+ "'require strong key:%s = false' to proceed: %s\n",
+ domain->name, domain->name, domain->name,
+ nt_errstr(status)));
+ goto done;
+ }
+ status = cli_rpc_pipe_open_noauth(conn->cli, &ndr_table_samr,
+ &conn->samr_pipe);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)
+ && !retry) {
+ invalidate_cm_connection(domain);
+ retry = true;
+ goto retry;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = dcerpc_samr_Connect2(conn->samr_pipe->binding_handle, mem_ctx,
+ conn->samr_pipe->desthost,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &conn->sam_connect_handle,
+ &result);
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR) && !retry) {
+ invalidate_cm_connection(domain);
+ TALLOC_FREE(conn->samr_pipe);
+ retry = true;
+ goto retry;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("cm_connect_sam: rpccli_samr_Connect2 failed "
+ "for domain %s Error was %s\n",
+ domain->name, nt_errstr(status) ));
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ DEBUG(10,("cm_connect_sam: dcerpc_samr_Connect2 failed "
+ "for domain %s Error was %s\n",
+ domain->name, nt_errstr(result)));
+ goto done;
+ }
+
+ open_domain:
+ status = dcerpc_samr_OpenDomain(conn->samr_pipe->binding_handle,
+ mem_ctx,
+ &conn->sam_connect_handle,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &domain->sid,
+ &conn->sam_domain_handle,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = result;
+ done:
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ /*
+ * if we got access denied, we might just have no access rights
+ * to talk to the remote samr server server (e.g. when we are a
+ * PDC and we are connecting a w2k8 pdc via an interdomain
+ * trust). In that case do not invalidate the whole connection
+ * stack
+ */
+ TALLOC_FREE(conn->samr_pipe);
+ ZERO_STRUCT(conn->sam_domain_handle);
+ return status;
+ } else if (!NT_STATUS_IS_OK(status)) {
+ invalidate_cm_connection(domain);
+ return status;
+ }
+
+ *cli = conn->samr_pipe;
+ *sam_handle = conn->sam_domain_handle;
+ return status;
+}
+
+/**********************************************************************
+ open an schanneld ncacn_ip_tcp connection to LSA
+***********************************************************************/
+
+static NTSTATUS cm_connect_lsa_tcp(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **cli)
+{
+ struct winbindd_cm_conn *conn;
+ struct netlogon_creds_cli_context *p_creds = NULL;
+ NTSTATUS status;
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+
+ DEBUG(10,("cm_connect_lsa_tcp\n"));
+
+ status = init_dc_connection_rpc(domain, false);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ conn = &domain->conn;
+
+ /*
+ * rpccli_is_connected handles more error cases
+ */
+ if (rpccli_is_connected(conn->lsa_pipe_tcp) &&
+ conn->lsa_pipe_tcp->transport->transport == NCACN_IP_TCP &&
+ conn->lsa_pipe_tcp->auth->auth_level >= DCERPC_AUTH_LEVEL_INTEGRITY) {
+ goto done;
+ }
+
+ TALLOC_FREE(conn->lsa_pipe_tcp);
+
+ status = cm_get_schannel_creds(domain, &p_creds);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ remote_name = smbXcli_conn_remote_name(conn->cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn);
+
+ status = cli_rpc_pipe_open_schannel_with_creds(
+ conn->cli,
+ &ndr_table_lsarpc,
+ NCACN_IP_TCP,
+ p_creds,
+ remote_name,
+ remote_sockaddr,
+ &conn->lsa_pipe_tcp);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("cli_rpc_pipe_open_schannel_with_key failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ done:
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(conn->lsa_pipe_tcp);
+ return status;
+ }
+
+ *cli = conn->lsa_pipe_tcp;
+
+ return status;
+}
+
+NTSTATUS cm_connect_lsa(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **cli, struct policy_handle *lsa_policy)
+{
+ struct winbindd_cm_conn *conn;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ struct netlogon_creds_cli_context *p_creds;
+ struct cli_credentials *creds = NULL;
+ bool retry = false; /* allow one retry attempt for expired session */
+ const char *remote_name = NULL;
+ const struct sockaddr_storage *remote_sockaddr = NULL;
+ bool sealed_pipes = true;
+ bool strong_key = true;
+
+retry:
+ result = init_dc_connection_rpc(domain, false);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ conn = &domain->conn;
+
+ if (rpccli_is_connected(conn->lsa_pipe)) {
+ goto done;
+ }
+
+ TALLOC_FREE(conn->lsa_pipe);
+
+ if (IS_DC) {
+ /*
+ * Make sure we only use schannel as AD DC.
+ */
+ goto schannel;
+ }
+
+ result = get_trust_credentials(domain, talloc_tos(), false, &creds);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("cm_connect_lsa: No user available for "
+ "domain %s, trying schannel\n", domain->name));
+ goto schannel;
+ }
+
+ if (cli_credentials_is_anonymous(creds)) {
+ goto anonymous;
+ }
+
+ remote_name = smbXcli_conn_remote_name(conn->cli->conn);
+ remote_sockaddr = smbXcli_conn_remote_sockaddr(conn->cli->conn);
+
+ /*
+ * We have an authenticated connection. Use a SPNEGO
+ * authenticated LSA pipe with sign & seal.
+ */
+ result = cli_rpc_pipe_open_with_creds
+ (conn->cli, &ndr_table_lsarpc, NCACN_NP,
+ DCERPC_AUTH_TYPE_SPNEGO,
+ conn->auth_level,
+ remote_name,
+ remote_sockaddr,
+ creds,
+ &conn->lsa_pipe);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED)
+ && !retry) {
+ invalidate_cm_connection(domain);
+ retry = true;
+ goto retry;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for "
+ "domain %s using NTLMSSP authenticated pipe: user "
+ "%s. Error was %s. Trying schannel.\n",
+ domain->name,
+ cli_credentials_get_unparsed_name(creds, talloc_tos()),
+ nt_errstr(result)));
+ goto schannel;
+ }
+
+ DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using "
+ "NTLMSSP authenticated pipe: user %s\n",
+ domain->name, cli_credentials_get_unparsed_name(creds, talloc_tos())));
+
+ result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &conn->lsa_policy);
+ if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) {
+ invalidate_cm_connection(domain);
+ TALLOC_FREE(conn->lsa_pipe);
+ retry = true;
+ goto retry;
+ }
+
+ if (NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying "
+ "schannel\n"));
+
+ TALLOC_FREE(conn->lsa_pipe);
+
+ schannel:
+
+ /* Fall back to schannel if it's a W2K pre-SP1 box. */
+
+ result = cm_get_schannel_creds(domain, &p_creds);
+ if (!NT_STATUS_IS_OK(result)) {
+ /* If this call fails - conn->cli can now be NULL ! */
+ DEBUG(10, ("cm_connect_lsa: Could not get schannel auth info "
+ "for domain %s (error %s), trying anon\n",
+ domain->name,
+ nt_errstr(result) ));
+ goto anonymous;
+ }
+
+ TALLOC_FREE(creds);
+ result = cli_rpc_pipe_open_schannel_with_creds(
+ conn->cli, &ndr_table_lsarpc, NCACN_NP, p_creds,
+ remote_name,
+ remote_sockaddr,
+ &conn->lsa_pipe);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED)
+ && !retry) {
+ invalidate_cm_connection(domain);
+ retry = true;
+ goto retry;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("cm_connect_lsa: failed to connect to LSA pipe for "
+ "domain %s using schannel. Error was %s\n",
+ domain->name, nt_errstr(result) ));
+ goto anonymous;
+ }
+ DEBUG(10,("cm_connect_lsa: connected to LSA pipe for domain %s using "
+ "schannel.\n", domain->name ));
+
+ result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &conn->lsa_policy);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) {
+ invalidate_cm_connection(domain);
+ TALLOC_FREE(conn->lsa_pipe);
+ retry = true;
+ goto retry;
+ }
+
+ if (NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ if (IS_DC) {
+ /*
+ * Make sure we only use schannel as AD DC.
+ */
+ goto done;
+ }
+
+ DEBUG(10,("cm_connect_lsa: rpccli_lsa_open_policy failed, trying "
+ "anonymous\n"));
+
+ TALLOC_FREE(conn->lsa_pipe);
+
+ anonymous:
+
+ if (IS_DC) {
+ /*
+ * Make sure we only use schannel as AD DC.
+ */
+ goto done;
+ }
+
+ sealed_pipes = lp_winbind_sealed_pipes();
+ sealed_pipes = lp_parm_bool(-1, "winbind sealed pipes",
+ domain->name,
+ sealed_pipes);
+ strong_key = lp_require_strong_key();
+ strong_key = lp_parm_bool(-1, "require strong key",
+ domain->name,
+ strong_key);
+
+ /* Finally fall back to anonymous. */
+ if (sealed_pipes || strong_key) {
+ result = NT_STATUS_DOWNGRADE_DETECTED;
+ DEBUG(1, ("Unwilling to make LSA connection to domain %s "
+ "without connection level security, "
+ "must set 'winbind sealed pipes:%s = false' and "
+ "'require strong key:%s = false' to proceed: %s\n",
+ domain->name, domain->name, domain->name,
+ nt_errstr(result)));
+ goto done;
+ }
+
+ result = cli_rpc_pipe_open_noauth(conn->cli,
+ &ndr_table_lsarpc,
+ &conn->lsa_pipe);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NETWORK_SESSION_EXPIRED)
+ && !retry) {
+ invalidate_cm_connection(domain);
+ retry = true;
+ goto retry;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ result = rpccli_lsa_open_policy(conn->lsa_pipe, mem_ctx, True,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &conn->lsa_policy);
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_IO_DEVICE_ERROR) && !retry) {
+ invalidate_cm_connection(domain);
+ TALLOC_FREE(conn->lsa_pipe);
+ retry = true;
+ goto retry;
+ }
+
+ done:
+ if (!NT_STATUS_IS_OK(result)) {
+ invalidate_cm_connection(domain);
+ return result;
+ }
+
+ *cli = conn->lsa_pipe;
+ *lsa_policy = conn->lsa_policy;
+ return result;
+}
+
+/****************************************************************************
+Open a LSA connection to a DC, suitable for LSA lookup calls.
+****************************************************************************/
+
+NTSTATUS cm_connect_lsat(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **cli,
+ struct policy_handle *lsa_policy)
+{
+ NTSTATUS status;
+
+ if (domain->can_do_ncacn_ip_tcp) {
+ status = cm_connect_lsa_tcp(domain, mem_ctx, cli);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ invalidate_cm_connection(domain);
+ status = cm_connect_lsa_tcp(domain, mem_ctx, cli);
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * we tried twice to connect via ncan_ip_tcp and schannel and
+ * failed - maybe it is a trusted domain we can't connect to ?
+ * do not try tcp next time - gd
+ *
+ * This also prevents NETLOGON over TCP
+ */
+ domain->can_do_ncacn_ip_tcp = false;
+ }
+
+ status = cm_connect_lsa(domain, mem_ctx, cli, lsa_policy);
+
+ return status;
+}
+
+/****************************************************************************
+ Open the netlogon pipe to this DC.
+****************************************************************************/
+
+static NTSTATUS cm_connect_netlogon_transport(struct winbindd_domain *domain,
+ enum dcerpc_transport_t transport,
+ struct rpc_pipe_client **cli)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ struct winbindd_cm_conn *conn;
+ NTSTATUS result;
+ enum netr_SchannelType sec_chan_type;
+ struct cli_credentials *creds = NULL;
+
+ *cli = NULL;
+
+ if (IS_DC) {
+ if (domain->secure_channel_type == SEC_CHAN_NULL) {
+ /*
+ * Make sure we don't even try to
+ * connect to a foreign domain
+ * without a direct outbound trust.
+ */
+ return NT_STATUS_NO_TRUST_LSA_SECRET;
+ }
+ }
+
+ result = init_dc_connection_rpc(domain, domain->rodc);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ conn = &domain->conn;
+
+ if (rpccli_is_connected(conn->netlogon_pipe)) {
+ *cli = conn->netlogon_pipe;
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(conn->netlogon_pipe);
+ TALLOC_FREE(conn->netlogon_creds_ctx);
+
+ result = get_trust_credentials(domain, talloc_tos(), true, &creds);
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_DEBUG("No user available for domain %s when trying "
+ "schannel\n", domain->name);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ if (cli_credentials_is_anonymous(creds)) {
+ DBG_WARNING("get_trust_credential only gave anonymous for %s, "
+ "unable to make get NETLOGON credentials\n",
+ domain->name);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ sec_chan_type = cli_credentials_get_secure_channel_type(creds);
+ if (sec_chan_type == SEC_CHAN_NULL) {
+ const char *remote_name =
+ smbXcli_conn_remote_name(conn->cli->conn);
+ const struct sockaddr_storage *remote_sockaddr =
+ smbXcli_conn_remote_sockaddr(conn->cli->conn);
+
+ if (transport == NCACN_IP_TCP) {
+ DBG_NOTICE("get_secure_channel_type gave SEC_CHAN_NULL "
+ "for %s, deny NCACN_IP_TCP and let the "
+ "caller fallback to NCACN_NP.\n",
+ domain->name);
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ DBG_NOTICE("get_secure_channel_type gave SEC_CHAN_NULL for %s, "
+ "fallback to noauth on NCACN_NP.\n",
+ domain->name);
+
+ result = cli_rpc_pipe_open_noauth_transport(
+ conn->cli,
+ transport,
+ &ndr_table_netlogon,
+ remote_name,
+ remote_sockaddr,
+ &conn->netlogon_pipe);
+ if (!NT_STATUS_IS_OK(result)) {
+ invalidate_cm_connection(domain);
+ return result;
+ }
+
+ *cli = conn->netlogon_pipe;
+ return NT_STATUS_OK;
+ }
+
+ result = rpccli_create_netlogon_creds_ctx(creds,
+ domain->dcname,
+ msg_ctx,
+ domain,
+ &conn->netlogon_creds_ctx);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("rpccli_create_netlogon_creds failed for %s, "
+ "unable to create NETLOGON credentials: %s\n",
+ domain->name, nt_errstr(result)));
+ return result;
+ }
+
+ result = rpccli_connect_netlogon(
+ conn->cli, transport,
+ conn->netlogon_creds_ctx, conn->netlogon_force_reauth, creds,
+ &conn->netlogon_pipe);
+ conn->netlogon_force_reauth = false;
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_DEBUG("rpccli_connect_netlogon failed: %s\n",
+ nt_errstr(result));
+ return result;
+ }
+
+ *cli = conn->netlogon_pipe;
+ return NT_STATUS_OK;
+}
+
+/****************************************************************************
+Open a NETLOGON connection to a DC, suitable for SamLogon calls.
+****************************************************************************/
+
+NTSTATUS cm_connect_netlogon(struct winbindd_domain *domain,
+ struct rpc_pipe_client **cli)
+{
+ NTSTATUS status;
+
+ status = init_dc_connection_rpc(domain, domain->rodc);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (domain->active_directory && domain->can_do_ncacn_ip_tcp) {
+ status = cm_connect_netlogon_transport(domain, NCACN_IP_TCP, cli);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ invalidate_cm_connection(domain);
+ status = cm_connect_netlogon_transport(domain, NCACN_IP_TCP, cli);
+ }
+ if (NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /*
+ * we tried twice to connect via ncan_ip_tcp and schannel and
+ * failed - maybe it is a trusted domain we can't connect to ?
+ * do not try tcp next time - gd
+ *
+ * This also prevents LSA over TCP
+ */
+ domain->can_do_ncacn_ip_tcp = false;
+ }
+
+ status = cm_connect_netlogon_transport(domain, NCACN_NP, cli);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_SESSION_EXPIRED)) {
+ /*
+ * SMB2 session expired, needs reauthentication. Drop
+ * connection and retry.
+ */
+ invalidate_cm_connection(domain);
+ status = cm_connect_netlogon_transport(domain, NCACN_NP, cli);
+ }
+
+ return status;
+}
+
+NTSTATUS cm_connect_netlogon_secure(struct winbindd_domain *domain,
+ struct rpc_pipe_client **cli,
+ struct netlogon_creds_cli_context **ppdc)
+{
+ NTSTATUS status;
+
+ if (domain->secure_channel_type == SEC_CHAN_NULL) {
+ return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
+ }
+
+ status = cm_connect_netlogon(domain, cli);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (domain->conn.netlogon_creds_ctx == NULL) {
+ return NT_STATUS_TRUSTED_DOMAIN_FAILURE;
+ }
+
+ *ppdc = domain->conn.netlogon_creds_ctx;
+ return NT_STATUS_OK;
+}
+
+void winbind_msg_ip_dropped(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+ char *freeit = NULL;
+ char *addr;
+
+ if ((data == NULL)
+ || (data->data == NULL)
+ || (data->length == 0)
+ || (data->data[data->length-1] != '\0')) {
+ DEBUG(1, ("invalid msg_ip_dropped message: not a valid "
+ "string\n"));
+ return;
+ }
+
+ addr = (char *)data->data;
+ DEBUG(10, ("IP %s dropped\n", addr));
+
+ if (!is_ipaddress(addr)) {
+ char *slash;
+ /*
+ * Some code sends us ip addresses with the /netmask
+ * suffix
+ */
+ slash = strchr(addr, '/');
+ if (slash == NULL) {
+ DEBUG(1, ("invalid msg_ip_dropped message: %s\n",
+ addr));
+ return;
+ }
+ freeit = talloc_strndup(talloc_tos(), addr, slash-addr);
+ if (freeit == NULL) {
+ DEBUG(1, ("talloc failed\n"));
+ return;
+ }
+ addr = freeit;
+ DEBUG(10, ("Stripped /netmask to IP %s\n", addr));
+ }
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ char sockaddr[INET6_ADDRSTRLEN];
+
+ if (!cli_state_is_connected(domain->conn.cli)) {
+ continue;
+ }
+
+ print_sockaddr(sockaddr, sizeof(sockaddr),
+ smbXcli_conn_local_sockaddr(domain->conn.cli->conn));
+
+ if (strequal(sockaddr, addr)) {
+ smbXcli_conn_disconnect(domain->conn.cli->conn, NT_STATUS_OK);
+ }
+ }
+ TALLOC_FREE(freeit);
+}
+
+void winbind_msg_disconnect_dc(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ invalidate_cm_connection(domain);
+ }
+}
diff --git a/source3/winbindd/winbindd_cred_cache.c b/source3/winbindd/winbindd_cred_cache.c
new file mode 100644
index 0000000..59daaff
--- /dev/null
+++ b/source3/winbindd/winbindd_cred_cache.c
@@ -0,0 +1,1061 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - krb5 credential cache functions
+ and in-memory cache functions.
+
+ Copyright (C) Guenther Deschner 2005-2006
+ Copyright (C) Jeremy Allison 2006
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "smb_krb5.h"
+#include "libads/kerberos_proto.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* uncomment this to do fast debugging on the krb5 ticket renewal event */
+#ifdef DEBUG_KRB5_TKT_RENEWAL
+#undef DEBUG_KRB5_TKT_RENEWAL
+#endif
+
+#define MAX_CCACHES 100
+
+static struct WINBINDD_CCACHE_ENTRY *ccache_list;
+static void krb5_ticket_gain_handler(struct tevent_context *,
+ struct tevent_timer *,
+ struct timeval,
+ void *);
+static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *,
+ struct timeval);
+
+/* The Krb5 ticket refresh handler should be scheduled
+ at one-half of the period from now till the tkt
+ expiration */
+
+static time_t krb5_event_refresh_time(time_t end_time)
+{
+ time_t rest = end_time - time(NULL);
+ return end_time - rest/2;
+}
+
+/****************************************************************
+ Find an entry by name.
+****************************************************************/
+
+static struct WINBINDD_CCACHE_ENTRY *get_ccache_by_username(const char *username)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry;
+
+ for (entry = ccache_list; entry; entry = entry->next) {
+ if (strequal(entry->username, username)) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+/****************************************************************
+ How many do we have ?
+****************************************************************/
+
+static int ccache_entry_count(void)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry;
+ int i = 0;
+
+ for (entry = ccache_list; entry; entry = entry->next) {
+ i++;
+ }
+ return i;
+}
+
+void ccache_remove_all_after_fork(void)
+{
+ struct WINBINDD_CCACHE_ENTRY *cur, *next;
+
+ for (cur = ccache_list; cur; cur = next) {
+ next = cur->next;
+ DLIST_REMOVE(ccache_list, cur);
+ TALLOC_FREE(cur->event);
+ TALLOC_FREE(cur);
+ }
+
+ return;
+}
+
+/****************************************************************
+ Do the work of refreshing the ticket.
+****************************************************************/
+
+static void krb5_ticket_refresh_handler(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry =
+ talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
+#ifdef HAVE_KRB5
+ int ret;
+ time_t new_start;
+ time_t expire_time = 0;
+ struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
+#endif
+
+ DBG_DEBUG("event called for: %s, %s\n",
+ entry->ccname, entry->username);
+
+ TALLOC_FREE(entry->event);
+
+#ifdef HAVE_KRB5
+
+ /* Kinit again if we have the user password and we can't renew the old
+ * tgt anymore
+ * NB
+ * This happens when machine are put to sleep for a very long time. */
+
+ if (entry->renew_until < time(NULL)) {
+rekinit:
+ if (cred_ptr && cred_ptr->pass) {
+
+ set_effective_uid(entry->uid);
+
+ ret = kerberos_kinit_password_ext(entry->principal_name,
+ cred_ptr->pass,
+ 0, /* hm, can we do time correction here ? */
+ &entry->refresh_time,
+ &entry->renew_until,
+ entry->ccname,
+ False, /* no PAC required anymore */
+ True,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ gain_root_privilege();
+
+ if (ret) {
+ DEBUG(3,("krb5_ticket_refresh_handler: "
+ "could not re-kinit: %s\n",
+ error_message(ret)));
+ /* destroy the ticket because we cannot rekinit
+ * it, ignore error here */
+ ads_kdestroy(entry->ccname);
+
+ /* Don't break the ticket refresh chain: retry
+ * refreshing ticket sometime later when KDC is
+ * unreachable -- BoYang. More error code handling
+ * here?
+ * */
+
+ if ((ret == KRB5_KDC_UNREACH)
+ || (ret == KRB5_REALM_CANT_RESOLVE)) {
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ new_start = time(NULL) +
+ MAX(30, lp_winbind_cache_time());
+#endif
+ add_krb5_ticket_gain_handler_event(entry,
+ timeval_set(new_start, 0));
+ return;
+ }
+ TALLOC_FREE(entry->event);
+ return;
+ }
+
+ DEBUG(10,("krb5_ticket_refresh_handler: successful re-kinit "
+ "for: %s in ccache: %s\n",
+ entry->principal_name, entry->ccname));
+
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ /* The tkt should be refreshed at one-half the period
+ from now to the expiration time */
+ expire_time = entry->refresh_time;
+ new_start = krb5_event_refresh_time(entry->refresh_time);
+#endif
+ goto done;
+ } else {
+ /* can this happen?
+ * No cached credentials
+ * destroy ticket and refresh chain
+ * */
+ ads_kdestroy(entry->ccname);
+ TALLOC_FREE(entry->event);
+ return;
+ }
+ }
+
+ set_effective_uid(entry->uid);
+
+ ret = smb_krb5_renew_ticket(entry->ccname,
+ entry->canon_principal,
+ entry->service,
+ &new_start);
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ expire_time = new_start;
+ new_start = krb5_event_refresh_time(new_start);
+#endif
+
+ gain_root_privilege();
+
+ if (ret) {
+ DEBUG(3,("krb5_ticket_refresh_handler: "
+ "could not renew tickets: %s\n",
+ error_message(ret)));
+ /* maybe we are beyond the renewing window */
+
+ /* evil rises here, we refresh ticket failed,
+ * but the ticket might be expired. Therefore,
+ * When we refresh ticket failed, destroy the
+ * ticket */
+
+ ads_kdestroy(entry->ccname);
+
+ /* avoid breaking the renewal chain: retry in
+ * lp_winbind_cache_time() seconds when the KDC was not
+ * available right now.
+ * the return code can be KRB5_REALM_CANT_RESOLVE.
+ * More error code handling here? */
+
+ if ((ret == KRB5_KDC_UNREACH)
+ || (ret == KRB5_REALM_CANT_RESOLVE)) {
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ new_start = time(NULL) + 30;
+#else
+ new_start = time(NULL) +
+ MAX(30, lp_winbind_cache_time());
+#endif
+ /* ticket is destroyed here, we have to regain it
+ * if it is possible */
+ add_krb5_ticket_gain_handler_event(entry,
+ timeval_set(new_start, 0));
+ return;
+ }
+
+ /* This is evil, if the ticket was already expired.
+ * renew ticket function returns KRB5KRB_AP_ERR_TKT_EXPIRED.
+ * But there is still a chance that we can rekinit it.
+ *
+ * This happens when user login in online mode, and then network
+ * down or something cause winbind goes offline for a very long time,
+ * and then goes online again. ticket expired, renew failed.
+ * This happens when machine are put to sleep for a long time,
+ * but shorter than entry->renew_util.
+ * NB
+ * Looks like the KDC is reachable, we want to rekinit as soon as
+ * possible instead of waiting some time later. */
+ if ((ret == KRB5KRB_AP_ERR_TKT_EXPIRED)
+ || (ret == KRB5_FCC_NOFILE)) goto rekinit;
+
+ return;
+ }
+
+done:
+ /* in cases that ticket will be unrenewable soon, we don't try to renew ticket
+ * but try to regain ticket if it is possible */
+ if (entry->renew_until && expire_time
+ && (entry->renew_until <= expire_time)) {
+ /* try to regain ticket 10 seconds before expiration */
+ expire_time -= 10;
+ add_krb5_ticket_gain_handler_event(entry,
+ timeval_set(expire_time, 0));
+ return;
+ }
+
+ if (entry->refresh_time == 0) {
+ entry->refresh_time = new_start;
+ }
+ entry->event = tevent_add_timer(global_event_context(), entry,
+ timeval_set(new_start, 0),
+ krb5_ticket_refresh_handler,
+ entry);
+
+#endif
+}
+
+/****************************************************************
+ Do the work of regaining a ticket when coming from offline auth.
+****************************************************************/
+
+static void krb5_ticket_gain_handler(struct tevent_context *event_ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry =
+ talloc_get_type_abort(private_data, struct WINBINDD_CCACHE_ENTRY);
+#ifdef HAVE_KRB5
+ int ret;
+ struct timeval t;
+ struct WINBINDD_MEMORY_CREDS *cred_ptr = entry->cred_ptr;
+ struct winbindd_domain *domain = NULL;
+#endif
+
+ DBG_DEBUG("event called for: %s, %s\n",
+ entry->ccname, entry->username);
+
+ TALLOC_FREE(entry->event);
+
+#ifdef HAVE_KRB5
+
+ if (!cred_ptr || !cred_ptr->pass) {
+ DEBUG(10,("krb5_ticket_gain_handler: no memory creds\n"));
+ return;
+ }
+
+ if ((domain = find_domain_from_name(entry->realm)) == NULL) {
+ DEBUG(0,("krb5_ticket_gain_handler: unknown domain\n"));
+ return;
+ }
+
+ if (!domain->online) {
+ goto retry_later;
+ }
+
+ set_effective_uid(entry->uid);
+
+ ret = kerberos_kinit_password_ext(entry->principal_name,
+ cred_ptr->pass,
+ 0, /* hm, can we do time correction here ? */
+ &entry->refresh_time,
+ &entry->renew_until,
+ entry->ccname,
+ False, /* no PAC required anymore */
+ True,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ NULL,
+ NULL,
+ NULL,
+ NULL);
+ gain_root_privilege();
+
+ if (ret) {
+ DEBUG(3,("krb5_ticket_gain_handler: "
+ "could not kinit: %s\n",
+ error_message(ret)));
+ /* evil. If we cannot do it, destroy any the __maybe__
+ * __existing__ ticket */
+ ads_kdestroy(entry->ccname);
+ goto retry_later;
+ }
+
+ DEBUG(10,("krb5_ticket_gain_handler: "
+ "successful kinit for: %s in ccache: %s\n",
+ entry->principal_name, entry->ccname));
+
+ goto got_ticket;
+
+ retry_later:
+
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ t = timeval_set(time(NULL) + 30, 0);
+#else
+ t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
+#endif
+
+ add_krb5_ticket_gain_handler_event(entry, t);
+ return;
+
+ got_ticket:
+
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ t = timeval_set(time(NULL) + 30, 0);
+#else
+ t = timeval_set(krb5_event_refresh_time(entry->refresh_time), 0);
+#endif
+
+ if (entry->refresh_time == 0) {
+ entry->refresh_time = t.tv_sec;
+ }
+ entry->event = tevent_add_timer(global_event_context(),
+ entry,
+ t,
+ krb5_ticket_refresh_handler,
+ entry);
+
+ return;
+#endif
+}
+
+/**************************************************************
+ The gain initial ticket case is recognised as entry->refresh_time
+ is always zero.
+**************************************************************/
+
+static void add_krb5_ticket_gain_handler_event(struct WINBINDD_CCACHE_ENTRY *entry,
+ struct timeval t)
+{
+ entry->refresh_time = 0;
+ entry->event = tevent_add_timer(global_event_context(),
+ entry,
+ t,
+ krb5_ticket_gain_handler,
+ entry);
+}
+
+void ccache_regain_all_now(void)
+{
+ struct WINBINDD_CCACHE_ENTRY *cur;
+ struct timeval t = timeval_current();
+
+ for (cur = ccache_list; cur; cur = cur->next) {
+ struct tevent_timer *new_event;
+
+ /*
+ * if refresh_time is 0, we know that the
+ * the event has the krb5_ticket_gain_handler
+ */
+ if (cur->refresh_time == 0) {
+ new_event = tevent_add_timer(global_event_context(),
+ cur,
+ t,
+ krb5_ticket_gain_handler,
+ cur);
+ } else {
+ new_event = tevent_add_timer(global_event_context(),
+ cur,
+ t,
+ krb5_ticket_refresh_handler,
+ cur);
+ }
+
+ if (!new_event) {
+ continue;
+ }
+
+ TALLOC_FREE(cur->event);
+ cur->event = new_event;
+ }
+
+ return;
+}
+
+/****************************************************************
+ Check if an ccache entry exists.
+****************************************************************/
+
+bool ccache_entry_exists(const char *username)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+ return (entry != NULL);
+}
+
+/****************************************************************
+ Ensure we're changing the correct entry.
+****************************************************************/
+
+bool ccache_entry_identical(const char *username,
+ uid_t uid,
+ const char *ccname)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+
+ if (!entry) {
+ return False;
+ }
+
+ if (entry->uid != uid) {
+ DEBUG(0,("cache_entry_identical: uid's differ: %u != %u\n",
+ (unsigned int)entry->uid, (unsigned int)uid));
+ return False;
+ }
+ if (!strcsequal(entry->ccname, ccname)) {
+ DEBUG(0,("cache_entry_identical: "
+ "ccnames differ: (cache) %s != (client) %s\n",
+ entry->ccname, ccname));
+ return False;
+ }
+ return True;
+}
+
+NTSTATUS add_ccache_to_list(const char *princ_name,
+ const char *ccname,
+ const char *username,
+ const char *pass,
+ const char *realm,
+ uid_t uid,
+ time_t create_time,
+ time_t ticket_end,
+ time_t renew_until,
+ bool postponed_request,
+ const char *canon_principal,
+ const char *canon_realm)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = NULL;
+ struct timeval t;
+ NTSTATUS ntret;
+
+ if ((username == NULL && princ_name == NULL) ||
+ ccname == NULL || uid == (uid_t)-1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (ccache_entry_count() + 1 > MAX_CCACHES) {
+ DEBUG(10,("add_ccache_to_list: "
+ "max number of ccaches reached\n"));
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ /* Reference count old entries */
+ entry = get_ccache_by_username(username);
+ if (entry) {
+ /* Check cached entries are identical. */
+ if (!ccache_entry_identical(username, uid, ccname)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ entry->ref_count++;
+ DEBUG(10,("add_ccache_to_list: "
+ "ref count on entry %s is now %d\n",
+ username, entry->ref_count));
+ /* FIXME: in this case we still might want to have a krb5 cred
+ * event handler created - gd
+ * Add ticket refresh handler here */
+
+ if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
+ return NT_STATUS_OK;
+ }
+
+ if (!entry->event) {
+ if (postponed_request) {
+ t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
+ add_krb5_ticket_gain_handler_event(entry, t);
+ } else {
+ /* Renew at 1/2 the ticket expiration time */
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ t = timeval_set(time(NULL)+30, 0);
+#else
+ t = timeval_set(krb5_event_refresh_time(ticket_end),
+ 0);
+#endif
+ if (!entry->refresh_time) {
+ entry->refresh_time = t.tv_sec;
+ }
+ entry->event = tevent_add_timer(global_event_context(),
+ entry,
+ t,
+ krb5_ticket_refresh_handler,
+ entry);
+ }
+
+ if (!entry->event) {
+ ntret = remove_ccache(username);
+ if (!NT_STATUS_IS_OK(ntret)) {
+ DEBUG(0, ("add_ccache_to_list: Failed to remove krb5 "
+ "ccache %s for user %s\n", entry->ccname,
+ entry->username));
+ DEBUG(0, ("add_ccache_to_list: error is %s\n",
+ nt_errstr(ntret)));
+ return ntret;
+ }
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
+
+ }
+
+ /*
+ * If we're set up to renew our krb5 tickets, we must
+ * cache the credentials in memory for the ticket
+ * renew function (or increase the reference count
+ * if we're logging in more than once). Fix inspired
+ * by patch from Ian Gordon <ian.gordon@strath.ac.uk>
+ * for bugid #9098.
+ */
+
+ ntret = winbindd_add_memory_creds(username, uid, pass);
+ DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
+ nt_errstr(ntret)));
+
+ return NT_STATUS_OK;
+ }
+
+ entry = talloc(NULL, struct WINBINDD_CCACHE_ENTRY);
+ if (!entry) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ZERO_STRUCTP(entry);
+
+ if (username) {
+ entry->username = talloc_strdup(entry, username);
+ if (!entry->username) {
+ goto no_mem;
+ }
+ }
+ if (princ_name) {
+ entry->principal_name = talloc_strdup(entry, princ_name);
+ if (!entry->principal_name) {
+ goto no_mem;
+ }
+ }
+ if (canon_principal != NULL) {
+ entry->canon_principal = talloc_strdup(entry, canon_principal);
+ if (entry->canon_principal == NULL) {
+ goto no_mem;
+ }
+ }
+ if (canon_realm != NULL) {
+ entry->canon_realm = talloc_strdup(entry, canon_realm);
+ if (entry->canon_realm == NULL) {
+ goto no_mem;
+ }
+ }
+
+ entry->ccname = talloc_strdup(entry, ccname);
+ if (!entry->ccname) {
+ goto no_mem;
+ }
+
+ entry->realm = talloc_strdup(entry, realm);
+ if (!entry->realm) {
+ goto no_mem;
+ }
+
+ entry->service = talloc_asprintf(entry,
+ "%s/%s@%s",
+ KRB5_TGS_NAME,
+ canon_realm,
+ canon_realm);
+ if (entry->service == NULL) {
+ goto no_mem;
+ }
+
+ entry->create_time = create_time;
+ entry->renew_until = renew_until;
+ entry->uid = uid;
+ entry->ref_count = 1;
+
+ if (!lp_winbind_refresh_tickets() || renew_until <= 0) {
+ goto add_entry;
+ }
+
+ if (postponed_request) {
+ t = timeval_current_ofs(MAX(30, lp_winbind_cache_time()), 0);
+ add_krb5_ticket_gain_handler_event(entry, t);
+ } else {
+ /* Renew at 1/2 the ticket expiration time */
+#if defined(DEBUG_KRB5_TKT_RENEWAL)
+ t = timeval_set(time(NULL)+30, 0);
+#else
+ t = timeval_set(krb5_event_refresh_time(ticket_end), 0);
+#endif
+ if (entry->refresh_time == 0) {
+ entry->refresh_time = t.tv_sec;
+ }
+ entry->event = tevent_add_timer(global_event_context(),
+ entry,
+ t,
+ krb5_ticket_refresh_handler,
+ entry);
+ }
+
+ if (!entry->event) {
+ goto no_mem;
+ }
+
+ DEBUG(10,("add_ccache_to_list: added krb5_ticket handler\n"));
+
+ add_entry:
+
+ DLIST_ADD(ccache_list, entry);
+
+ DBG_DEBUG("Added ccache [%s] for user [%s] and service [%s]\n",
+ entry->ccname, entry->username, entry->service);
+
+ if (entry->event) {
+ /*
+ * If we're set up to renew our krb5 tickets, we must
+ * cache the credentials in memory for the ticket
+ * renew function. Fix inspired by patch from
+ * Ian Gordon <ian.gordon@strath.ac.uk> for
+ * bugid #9098.
+ */
+
+ ntret = winbindd_add_memory_creds(username, uid, pass);
+ DEBUG(10, ("winbindd_add_memory_creds returned: %s\n",
+ nt_errstr(ntret)));
+ }
+
+ return NT_STATUS_OK;
+
+ no_mem:
+
+ TALLOC_FREE(entry);
+ return NT_STATUS_NO_MEMORY;
+}
+
+/*******************************************************************
+ Remove a WINBINDD_CCACHE_ENTRY entry and the krb5 ccache if no longer
+ referenced.
+ *******************************************************************/
+
+NTSTATUS remove_ccache(const char *username)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+ NTSTATUS status = NT_STATUS_OK;
+#ifdef HAVE_KRB5
+ krb5_error_code ret;
+#endif
+
+ if (!entry) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (entry->ref_count <= 0) {
+ DEBUG(0,("remove_ccache: logic error. "
+ "ref count for user %s = %d\n",
+ username, entry->ref_count));
+ return NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ entry->ref_count--;
+
+ if (entry->ref_count > 0) {
+ DEBUG(10,("remove_ccache: entry %s ref count now %d\n",
+ username, entry->ref_count));
+ return NT_STATUS_OK;
+ }
+
+ /* no references any more */
+
+ DLIST_REMOVE(ccache_list, entry);
+ TALLOC_FREE(entry->event); /* unregisters events */
+
+#ifdef HAVE_KRB5
+ ret = ads_kdestroy(entry->ccname);
+
+ /* we ignore the error when there has been no credential cache */
+ if (ret == KRB5_FCC_NOFILE) {
+ ret = 0;
+ } else if (ret) {
+ DEBUG(0,("remove_ccache: "
+ "failed to destroy user krb5 ccache %s with: %s\n",
+ entry->ccname, error_message(ret)));
+ } else {
+ DEBUG(10,("remove_ccache: "
+ "successfully destroyed krb5 ccache %s for user %s\n",
+ entry->ccname, username));
+ }
+ status = krb5_to_nt_status(ret);
+#endif
+
+ TALLOC_FREE(entry);
+ DEBUG(10,("remove_ccache: removed ccache for user %s\n", username));
+
+ return status;
+}
+
+/*******************************************************************
+ In memory credentials cache code.
+*******************************************************************/
+
+static struct WINBINDD_MEMORY_CREDS *memory_creds_list;
+
+/***********************************************************
+ Find an entry on the list by name.
+***********************************************************/
+
+struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username)
+{
+ struct WINBINDD_MEMORY_CREDS *p;
+
+ for (p = memory_creds_list; p; p = p->next) {
+ if (strequal(p->username, username)) {
+ return p;
+ }
+ }
+ return NULL;
+}
+
+/***********************************************************
+ Store the required creds and mlock them.
+***********************************************************/
+
+static NTSTATUS store_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp,
+ const char *pass)
+{
+#if !defined(HAVE_MLOCK)
+ return NT_STATUS_OK;
+#else
+ /* new_entry->nt_hash is the base pointer for the block
+ of memory pointed into by new_entry->lm_hash and
+ new_entry->pass (if we're storing plaintext). */
+
+ memcredp->len = NT_HASH_LEN + LM_HASH_LEN;
+ if (pass) {
+ memcredp->len += strlen(pass)+1;
+ }
+
+
+#if defined(LINUX)
+ /* aligning the memory on on x86_64 and compiling
+ with gcc 4.1 using -O2 causes a segv in the
+ next memset() --jerry */
+ memcredp->nt_hash = SMB_MALLOC_ARRAY(unsigned char, memcredp->len);
+#else
+ /* On non-linux platforms, mlock()'d memory must be aligned */
+ memcredp->nt_hash = SMB_MEMALIGN_ARRAY(unsigned char,
+ getpagesize(), memcredp->len);
+#endif
+ if (!memcredp->nt_hash) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memset(memcredp->nt_hash, 0x0, memcredp->len);
+
+ memcredp->lm_hash = memcredp->nt_hash + NT_HASH_LEN;
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(10,("mlocking memory: %p\n", memcredp->nt_hash));
+#endif
+ if ((mlock(memcredp->nt_hash, memcredp->len)) == -1) {
+ DEBUG(0,("failed to mlock memory: %s (%d)\n",
+ strerror(errno), errno));
+ SAFE_FREE(memcredp->nt_hash);
+ return map_nt_error_from_unix(errno);
+ }
+
+#ifdef DEBUG_PASSWORD
+ DEBUG(10,("mlocked memory: %p\n", memcredp->nt_hash));
+#endif
+
+ if (pass) {
+ /* Create and store the password hashes. */
+ E_md4hash(pass, memcredp->nt_hash);
+ E_deshash(pass, memcredp->lm_hash);
+
+ memcredp->pass = (char *)memcredp->lm_hash + LM_HASH_LEN;
+ memcpy(memcredp->pass, pass,
+ memcredp->len - NT_HASH_LEN - LM_HASH_LEN);
+ }
+
+ return NT_STATUS_OK;
+#endif
+}
+
+/***********************************************************
+ Destroy existing creds.
+***********************************************************/
+
+static NTSTATUS delete_memory_creds(struct WINBINDD_MEMORY_CREDS *memcredp)
+{
+#if !defined(HAVE_MUNLOCK)
+ return NT_STATUS_OK;
+#else
+ if (munlock(memcredp->nt_hash, memcredp->len) == -1) {
+ DEBUG(0,("failed to munlock memory: %s (%d)\n",
+ strerror(errno), errno));
+ return map_nt_error_from_unix(errno);
+ }
+ memset(memcredp->nt_hash, '\0', memcredp->len);
+ SAFE_FREE(memcredp->nt_hash);
+ memcredp->nt_hash = NULL;
+ memcredp->lm_hash = NULL;
+ memcredp->pass = NULL;
+ memcredp->len = 0;
+ return NT_STATUS_OK;
+#endif
+}
+
+/***********************************************************
+ Replace the required creds with new ones (password change).
+***********************************************************/
+
+static NTSTATUS winbindd_replace_memory_creds_internal(struct WINBINDD_MEMORY_CREDS *memcredp,
+ const char *pass)
+{
+ NTSTATUS status = delete_memory_creds(memcredp);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return store_memory_creds(memcredp, pass);
+}
+
+/*************************************************************
+ Store credentials in memory in a list.
+*************************************************************/
+
+static NTSTATUS winbindd_add_memory_creds_internal(const char *username,
+ uid_t uid,
+ const char *pass)
+{
+ /* Shortcut to ensure we don't store if no mlock. */
+#if !defined(HAVE_MLOCK) || !defined(HAVE_MUNLOCK)
+ return NT_STATUS_OK;
+#else
+ NTSTATUS status;
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+
+ memcredp = find_memory_creds_by_name(username);
+ if (uid == (uid_t)-1) {
+ DEBUG(0,("winbindd_add_memory_creds_internal: "
+ "invalid uid for user %s.\n", username));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (memcredp) {
+ /* Already exists. Increment the reference count and replace stored creds. */
+ if (uid != memcredp->uid) {
+ DEBUG(0,("winbindd_add_memory_creds_internal: "
+ "uid %u for user %s doesn't "
+ "match stored uid %u. Replacing.\n",
+ (unsigned int)uid, username,
+ (unsigned int)memcredp->uid));
+ memcredp->uid = uid;
+ }
+ memcredp->ref_count++;
+ DEBUG(10,("winbindd_add_memory_creds_internal: "
+ "ref count for user %s is now %d\n",
+ username, memcredp->ref_count));
+ return winbindd_replace_memory_creds_internal(memcredp, pass);
+ }
+
+ memcredp = talloc_zero(NULL, struct WINBINDD_MEMORY_CREDS);
+ if (!memcredp) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ memcredp->username = talloc_strdup(memcredp, username);
+ if (!memcredp->username) {
+ talloc_destroy(memcredp);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = store_memory_creds(memcredp, pass);
+ if (!NT_STATUS_IS_OK(status)) {
+ talloc_destroy(memcredp);
+ return status;
+ }
+
+ memcredp->uid = uid;
+ memcredp->ref_count = 1;
+ DLIST_ADD(memory_creds_list, memcredp);
+
+ DEBUG(10,("winbindd_add_memory_creds_internal: "
+ "added entry for user %s\n", username));
+
+ return NT_STATUS_OK;
+#endif
+}
+
+/*************************************************************
+ Store users credentials in memory. If we also have a
+ struct WINBINDD_CCACHE_ENTRY for this username with a
+ refresh timer, then store the plaintext of the password
+ and associate the new credentials with the struct WINBINDD_CCACHE_ENTRY.
+*************************************************************/
+
+NTSTATUS winbindd_add_memory_creds(const char *username,
+ uid_t uid,
+ const char *pass)
+{
+ struct WINBINDD_CCACHE_ENTRY *entry = get_ccache_by_username(username);
+ NTSTATUS status;
+
+ status = winbindd_add_memory_creds_internal(username, uid, pass);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (entry) {
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+ memcredp = find_memory_creds_by_name(username);
+ if (memcredp) {
+ entry->cred_ptr = memcredp;
+ }
+ }
+
+ return status;
+}
+
+/*************************************************************
+ Decrement the in-memory ref count - delete if zero.
+*************************************************************/
+
+NTSTATUS winbindd_delete_memory_creds(const char *username)
+{
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+ struct WINBINDD_CCACHE_ENTRY *entry = NULL;
+ NTSTATUS status = NT_STATUS_OK;
+
+ memcredp = find_memory_creds_by_name(username);
+ entry = get_ccache_by_username(username);
+
+ if (!memcredp) {
+ DEBUG(10,("winbindd_delete_memory_creds: unknown user %s\n",
+ username));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ if (memcredp->ref_count <= 0) {
+ DEBUG(0,("winbindd_delete_memory_creds: logic error. "
+ "ref count for user %s = %d\n",
+ username, memcredp->ref_count));
+ status = NT_STATUS_INTERNAL_DB_CORRUPTION;
+ }
+
+ memcredp->ref_count--;
+ if (memcredp->ref_count <= 0) {
+ delete_memory_creds(memcredp);
+ DLIST_REMOVE(memory_creds_list, memcredp);
+ talloc_destroy(memcredp);
+ DEBUG(10,("winbindd_delete_memory_creds: "
+ "deleted entry for user %s\n",
+ username));
+ } else {
+ DEBUG(10,("winbindd_delete_memory_creds: "
+ "entry for user %s ref_count now %d\n",
+ username, memcredp->ref_count));
+ }
+
+ if (entry) {
+ /* Ensure we have no dangling references to this. */
+ entry->cred_ptr = NULL;
+ }
+
+ return status;
+}
+
+/***********************************************************
+ Replace the required creds with new ones (password change).
+***********************************************************/
+
+NTSTATUS winbindd_replace_memory_creds(const char *username,
+ const char *pass)
+{
+ struct WINBINDD_MEMORY_CREDS *memcredp = NULL;
+
+ memcredp = find_memory_creds_by_name(username);
+ if (!memcredp) {
+ DEBUG(10,("winbindd_replace_memory_creds: unknown user %s\n",
+ username));
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ DEBUG(10,("winbindd_replace_memory_creds: replaced creds for user %s\n",
+ username));
+
+ return winbindd_replace_memory_creds_internal(memcredp, pass);
+}
diff --git a/source3/winbindd/winbindd_creds.c b/source3/winbindd/winbindd_creds.c
new file mode 100644
index 0000000..a0cce7e
--- /dev/null
+++ b/source3/winbindd/winbindd_creds.c
@@ -0,0 +1,147 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - cached credentials functions
+
+ Copyright (C) Guenther Deschner 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "../libcli/security/security.h"
+#include "libsmb/samlogon_cache.h"
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define MAX_CACHED_LOGINS 10
+
+NTSTATUS winbindd_get_creds(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ struct netr_SamInfo3 **info3,
+ const uint8_t **cached_nt_pass,
+ const uint8_t **cred_salt)
+{
+ struct netr_SamInfo3 *info;
+ NTSTATUS status;
+
+ status = wcache_get_creds(domain, mem_ctx, sid, cached_nt_pass, cred_salt);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ info = netsamlogon_cache_get(mem_ctx, sid);
+ if (info == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ *info3 = info;
+
+ return NT_STATUS_OK;
+}
+
+
+NTSTATUS winbindd_store_creds(struct winbindd_domain *domain,
+ const char *user,
+ const char *pass,
+ struct netr_SamInfo3 *info3)
+{
+ NTSTATUS status;
+ uchar nt_pass[NT_HASH_LEN];
+ struct dom_sid cred_sid;
+
+ if (info3 != NULL) {
+
+ sid_compose(&cred_sid, info3->base.domain_sid,
+ info3->base.rid);
+ info3->base.user_flags |= NETLOGON_CACHED_ACCOUNT;
+
+ } else if (user != NULL) {
+
+ /* do lookup ourself */
+
+ enum lsa_SidType type;
+
+ if (!lookup_cached_name(domain->name, /* namespace */
+ domain->name,
+ user,
+ &cred_sid,
+ &type)) {
+ return NT_STATUS_NO_SUCH_USER;
+ }
+ } else {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (pass) {
+
+ int count = 0;
+
+ status = wcache_count_cached_creds(domain, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ DEBUG(11,("we have %d cached creds\n", count));
+
+ if (count + 1 > MAX_CACHED_LOGINS) {
+
+ DEBUG(10,("need to delete the oldest cached login\n"));
+
+ status = wcache_remove_oldest_cached_creds(domain, &cred_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10,("failed to remove oldest cached cred: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+ }
+
+ E_md4hash(pass, nt_pass);
+
+ dump_data_pw("nt_pass", nt_pass, NT_HASH_LEN);
+
+ status = wcache_save_creds(domain, &cred_sid, nt_pass);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ }
+
+ if (info3 != NULL && user != NULL) {
+ if (!netsamlogon_cache_store(user, info3)) {
+ return NT_STATUS_ACCESS_DENIED;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS winbindd_update_creds_by_info3(struct winbindd_domain *domain,
+ const char *user,
+ const char *pass,
+ struct netr_SamInfo3 *info3)
+{
+ return winbindd_store_creds(domain, user, pass, info3);
+}
+
+NTSTATUS winbindd_update_creds_by_name(struct winbindd_domain *domain,
+ const char *user,
+ const char *pass)
+{
+ return winbindd_store_creds(domain, user, pass, NULL);
+}
+
+
diff --git a/source3/winbindd/winbindd_domain.c b/source3/winbindd/winbindd_domain.c
new file mode 100644
index 0000000..4c8aa9a
--- /dev/null
+++ b/source3/winbindd/winbindd_domain.c
@@ -0,0 +1,36 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind domain child functions
+
+ Copyright (C) Stefan Metzmacher 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+void setup_domain_child(struct winbindd_domain *domain)
+{
+ int i;
+
+ for (i=0; i<talloc_array_length(domain->children); i++) {
+ setup_child(domain, &domain->children[i],
+ "log.wb", domain->name);
+ }
+}
diff --git a/source3/winbindd/winbindd_domain_info.c b/source3/winbindd/winbindd_domain_info.c
new file mode 100644
index 0000000..c4364d9
--- /dev/null
+++ b/source3/winbindd/winbindd_domain_info.c
@@ -0,0 +1,141 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * async implementation of WINBINDD_DOMAIN_INFO
+ * Copyright (C) Volker Lendecke 2018
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_domain_info_state {
+ struct winbindd_domain *domain;
+ uint32_t in;
+ uint32_t out;
+};
+
+static void winbindd_domain_info_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_domain_info_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_domain_info_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_domain_info_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ DEBUG(3, ("[%5lu]: domain_info [%s]\n", (unsigned long)cli->pid,
+ cli->request->domain_name));
+
+ state->domain = find_domain_from_name_noinit(
+ cli->request->domain_name);
+
+ if (state->domain == NULL) {
+ DEBUG(3, ("Did not find domain [%s]\n",
+ cli->request->domain_name));
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->domain->initialized) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * Send a ping down. This implicitly initializes the domain.
+ */
+
+ state->in = cli->pid;
+ state->out = 0;
+ subreq = dcerpc_wbint_Ping_send(state,
+ global_event_context(),
+ dom_child_handle(state->domain),
+ state->in,
+ &state->out);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_domain_info_done, req);
+
+ return req;
+}
+
+static void winbindd_domain_info_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_domain_info_state *state = tevent_req_data(
+ req, struct winbindd_domain_info_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_Ping_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ DBG_NOTICE("dcerpc_wbint_Ping call failed: %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ if (tevent_req_nterror(req, result)) {
+ DBG_NOTICE("dcerpc_wbint_Ping failed: %s\n",
+ nt_errstr(result));
+ return;
+ }
+
+ if (!state->domain->initialized) {
+ DBG_INFO("dcerpc_wbint_Ping did not initialize domain %s\n",
+ state->domain->name);
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_domain_info_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_domain_info_state *state = tevent_req_data(
+ req, struct winbindd_domain_info_state);
+ struct winbindd_domain *domain = state->domain;
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DBG_NOTICE("winbindd_domain_info failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ fstrcpy(response->data.domain_info.name, domain->name);
+ fstrcpy(response->data.domain_info.alt_name, domain->alt_name);
+ sid_to_fstring(response->data.domain_info.sid, &domain->sid);
+
+ response->data.domain_info.native_mode = domain->native_mode;
+ response->data.domain_info.active_directory = domain->active_directory;
+ response->data.domain_info.primary = domain->primary;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_dsgetdcname.c b/source3/winbindd/winbindd_dsgetdcname.c
new file mode 100644
index 0000000..9dadeab
--- /dev/null
+++ b/source3/winbindd/winbindd_dsgetdcname.c
@@ -0,0 +1,200 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_DSGETDCNAME
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "util/debug.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_dsgetdcname_state {
+ struct GUID guid;
+ struct netr_DsRGetDCNameInfo *dc_info;
+};
+
+static uint32_t get_dsgetdc_flags(uint32_t wbc_flags);
+static void winbindd_dsgetdcname_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_dsgetdcname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct dcerpc_binding_handle *child_binding_handle = NULL;
+ struct winbindd_dsgetdcname_state *state;
+ struct GUID *guid_ptr = NULL;
+ uint32_t ds_flags = 0;
+ NTSTATUS status;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_dsgetdcname_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_NOTICE("[%s (%u)] Winbind external command DSGETDCNAME start.\n",
+ cli->client_name,
+ (unsigned int)cli->pid);
+
+ request->data.dsgetdcname.domain_name
+ [sizeof(request->data.dsgetdcname.domain_name)-1] = '\0';
+ request->data.dsgetdcname.site_name
+ [sizeof(request->data.dsgetdcname.site_name)-1] = '\0';
+ request->data.dsgetdcname.domain_guid
+ [sizeof(request->data.dsgetdcname.domain_guid)-1] = '\0';
+
+ D_NOTICE("Calling DsGetDcName for domain '%s'.\n",
+ request->data.dsgetdcname.domain_name);
+
+ ds_flags = get_dsgetdc_flags(request->data.dsgetdcname.flags);
+
+ status = GUID_from_string(request->data.dsgetdcname.domain_guid,
+ &state->guid);
+ if (NT_STATUS_IS_OK(status) && !GUID_all_zero(&state->guid)) {
+ guid_ptr = &state->guid;
+ }
+
+ child_binding_handle = locator_child_handle();
+
+ subreq = dcerpc_wbint_DsGetDcName_send(
+ state, ev, child_binding_handle,
+ request->data.dsgetdcname.domain_name, guid_ptr,
+ request->data.dsgetdcname.site_name,
+ ds_flags, &state->dc_info);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_dsgetdcname_done, req);
+ return req;
+}
+
+static void winbindd_dsgetdcname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_dsgetdcname_state *state = tevent_req_data(
+ req, struct winbindd_dsgetdcname_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_DsGetDcName_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_dsgetdcname_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_dsgetdcname_state *state = tevent_req_data(
+ req, struct winbindd_dsgetdcname_state);
+ struct GUID_txt_buf guid_str_buf;
+ char *guid_str;
+ NTSTATUS status;
+
+ D_NOTICE("Winbind external command DSGETDCNAME end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ fstrcpy(response->data.dsgetdcname.dc_unc,
+ state->dc_info->dc_unc);
+ fstrcpy(response->data.dsgetdcname.dc_address,
+ state->dc_info->dc_address);
+ response->data.dsgetdcname.dc_address_type =
+ state->dc_info->dc_address_type;
+
+ guid_str = GUID_buf_string(&state->dc_info->domain_guid,
+ &guid_str_buf);
+ fstrcpy(response->data.dsgetdcname.domain_guid, guid_str);
+
+ fstrcpy(response->data.dsgetdcname.domain_name,
+ state->dc_info->domain_name);
+ fstrcpy(response->data.dsgetdcname.forest_name,
+ state->dc_info->forest_name);
+ response->data.dsgetdcname.dc_flags = state->dc_info->dc_flags;
+ fstrcpy(response->data.dsgetdcname.dc_site_name,
+ state->dc_info->dc_site_name);
+ fstrcpy(response->data.dsgetdcname.client_site_name,
+ state->dc_info->client_site_name);
+
+ return NT_STATUS_OK;
+}
+
+static uint32_t get_dsgetdc_flags(uint32_t wbc_flags)
+{
+ struct wbc_flag_map {
+ uint32_t wbc_dc_flag;
+ uint32_t ds_dc_flags;
+ } lookup_dc_flags[] = {
+ { WBC_LOOKUP_DC_FORCE_REDISCOVERY,
+ DS_FORCE_REDISCOVERY },
+ { WBC_LOOKUP_DC_DS_REQUIRED,
+ DS_DIRECTORY_SERVICE_REQUIRED },
+ { WBC_LOOKUP_DC_DS_PREFERRED,
+ DS_DIRECTORY_SERVICE_PREFERRED},
+ { WBC_LOOKUP_DC_GC_SERVER_REQUIRED,
+ DS_GC_SERVER_REQUIRED },
+ { WBC_LOOKUP_DC_PDC_REQUIRED,
+ DS_PDC_REQUIRED},
+ { WBC_LOOKUP_DC_BACKGROUND_ONLY,
+ DS_BACKGROUND_ONLY },
+ { WBC_LOOKUP_DC_IP_REQUIRED,
+ DS_IP_REQUIRED },
+ { WBC_LOOKUP_DC_KDC_REQUIRED,
+ DS_KDC_REQUIRED },
+ { WBC_LOOKUP_DC_TIMESERV_REQUIRED,
+ DS_TIMESERV_REQUIRED },
+ { WBC_LOOKUP_DC_WRITABLE_REQUIRED,
+ DS_WRITABLE_REQUIRED },
+ { WBC_LOOKUP_DC_GOOD_TIMESERV_PREFERRED,
+ DS_GOOD_TIMESERV_PREFERRED },
+ { WBC_LOOKUP_DC_AVOID_SELF,
+ DS_AVOID_SELF },
+ { WBC_LOOKUP_DC_ONLY_LDAP_NEEDED,
+ DS_ONLY_LDAP_NEEDED },
+ { WBC_LOOKUP_DC_IS_FLAT_NAME,
+ DS_IS_FLAT_NAME },
+ { WBC_LOOKUP_DC_IS_DNS_NAME,
+ DS_IS_DNS_NAME },
+ { WBC_LOOKUP_DC_TRY_NEXTCLOSEST_SITE,
+ DS_TRY_NEXTCLOSEST_SITE },
+ { WBC_LOOKUP_DC_DS_6_REQUIRED,
+ DS_DIRECTORY_SERVICE_6_REQUIRED },
+ { WBC_LOOKUP_DC_RETURN_DNS_NAME,
+ DS_RETURN_DNS_NAME },
+ { WBC_LOOKUP_DC_RETURN_FLAT_NAME,
+ DS_RETURN_FLAT_NAME }
+ };
+
+ uint32_t ds_flags = 0;
+ size_t i = 0;
+
+ for (i=0; i<ARRAY_SIZE(lookup_dc_flags); i++) {
+ if (wbc_flags & lookup_dc_flags[i].wbc_dc_flag) {
+ ds_flags |= lookup_dc_flags[i].ds_dc_flags;
+ }
+ }
+
+ return ds_flags;
+}
diff --git a/source3/winbindd/winbindd_dual.c b/source3/winbindd/winbindd_dual.c
new file mode 100644
index 0000000..e63b405
--- /dev/null
+++ b/source3/winbindd/winbindd_dual.c
@@ -0,0 +1,2093 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind child daemons
+
+ Copyright (C) Andrew Tridgell 2002
+ Copyright (C) Volker Lendecke 2004,2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * We fork a child per domain to be able to act non-blocking in the main
+ * winbind daemon. A domain controller thousands of miles away being being
+ * slow replying with a 10.000 user list should not hold up netlogon calls
+ * that can be handled locally.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "rpc_client/rpc_client.h"
+#include "nsswitch/wb_reqtrans.h"
+#include "secrets.h"
+#include "../lib/util/select.h"
+#include "winbindd_traceid.h"
+#include "../libcli/security/security.h"
+#include "system/select.h"
+#include "messages.h"
+#include "../lib/util/tevent_unix.h"
+#include "lib/param/loadparm.h"
+#include "lib/util/sys_rw.h"
+#include "lib/util/sys_rw_data.h"
+#include "passdb.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "idmap.h"
+#include "libcli/auth/netlogon_creds_cli.h"
+#include "../lib/util/pidfile.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/util/util_process.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static void forall_domain_children(bool (*fn)(struct winbindd_child *c,
+ void *private_data),
+ void *private_data)
+{
+ struct winbindd_domain *d;
+
+ for (d = domain_list(); d != NULL; d = d->next) {
+ int i;
+
+ for (i = 0; i < talloc_array_length(d->children); i++) {
+ struct winbindd_child *c = &d->children[i];
+ bool ok;
+
+ if (c->pid == 0) {
+ continue;
+ }
+
+ ok = fn(c, private_data);
+ if (!ok) {
+ return;
+ }
+ }
+ }
+}
+
+static void forall_children(bool (*fn)(struct winbindd_child *c,
+ void *private_data),
+ void *private_data)
+{
+ struct winbindd_child *c;
+ bool ok;
+
+ c = idmap_child();
+ if (c->pid != 0) {
+ ok = fn(c, private_data);
+ if (!ok) {
+ return;
+ }
+ }
+
+ c = locator_child();
+ if (c->pid != 0) {
+ ok = fn(c, private_data);
+ if (!ok) {
+ return;
+ }
+ }
+
+ forall_domain_children(fn, private_data);
+}
+
+/* Read some data from a client connection */
+
+static NTSTATUS child_read_request(int sock, struct winbindd_request *wreq)
+{
+ NTSTATUS status;
+
+ status = read_data_ntstatus(sock, (char *)wreq, sizeof(*wreq));
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("child_read_request: read_data failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (wreq->extra_len == 0) {
+ wreq->extra_data.data = NULL;
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(10, ("Need to read %d extra bytes\n", (int)wreq->extra_len));
+
+ wreq->extra_data.data = SMB_MALLOC_ARRAY(char, wreq->extra_len + 1);
+ if (wreq->extra_data.data == NULL) {
+ DEBUG(0, ("malloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Ensure null termination */
+ wreq->extra_data.data[wreq->extra_len] = '\0';
+
+ status = read_data_ntstatus(sock, wreq->extra_data.data,
+ wreq->extra_len);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0, ("Could not read extra data: %s\n",
+ nt_errstr(status)));
+ }
+ return status;
+}
+
+static NTSTATUS child_write_response(int sock, struct winbindd_response *wrsp)
+{
+ struct iovec iov[2];
+ int iov_count;
+
+ iov[0].iov_base = (void *)wrsp;
+ iov[0].iov_len = sizeof(struct winbindd_response);
+ iov_count = 1;
+
+ if (wrsp->length > sizeof(struct winbindd_response)) {
+ iov[1].iov_base = (void *)wrsp->extra_data.data;
+ iov[1].iov_len = wrsp->length-iov[0].iov_len;
+ iov_count = 2;
+ }
+
+ DEBUG(10, ("Writing %d bytes to parent\n", (int)wrsp->length));
+
+ if (write_data_iov(sock, iov, iov_count) != wrsp->length) {
+ DEBUG(0, ("Could not write result\n"));
+ return NT_STATUS_INVALID_HANDLE;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Do winbind child async request. This is not simply wb_simple_trans. We have
+ * to do the queueing ourselves because while a request is queued, the child
+ * might have crashed, and we have to re-fork it in the _trigger function.
+ */
+
+struct wb_child_request_state {
+ struct tevent_context *ev;
+ struct tevent_req *queue_subreq;
+ struct tevent_req *subreq;
+ struct winbindd_child *child;
+ struct winbindd_request *request;
+ struct winbindd_response *response;
+};
+
+static bool fork_domain_child(struct winbindd_child *child);
+
+static void wb_child_request_waited(struct tevent_req *subreq);
+static void wb_child_request_done(struct tevent_req *subreq);
+static void wb_child_request_orphaned(struct tevent_req *subreq);
+
+static void wb_child_request_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state);
+
+struct tevent_req *wb_child_request_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_child *child,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct wb_child_request_state *state;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_child_request_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->ev = ev;
+ state->child = child;
+
+ /*
+ * We have to make a copy of "request", because our caller
+ * might drop us via talloc_free().
+ *
+ * The talloc_move() magic in wb_child_request_cleanup() keeps
+ * all the requests, but if we are sitting deep within
+ * writev_send() down to the client, we have given it the
+ * pointer to "request". As our caller lost interest, it will
+ * just free "request", while writev_send still references it.
+ */
+
+ state->request = talloc_memdup(state, request, sizeof(*request));
+ if (tevent_req_nomem(state->request, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->request->traceid = debug_traceid_get();
+
+ if (request->extra_data.data != NULL) {
+ state->request->extra_data.data = talloc_memdup(
+ state->request,
+ request->extra_data.data,
+ request->extra_len);
+ if (tevent_req_nomem(state->request->extra_data.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = tevent_queue_wait_send(state, ev, child->queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wb_child_request_waited, req);
+ state->queue_subreq = subreq;
+
+ tevent_req_set_cleanup_fn(req, wb_child_request_cleanup);
+
+ return req;
+}
+
+static void wb_child_request_waited(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_child_request_state *state = tevent_req_data(
+ req, struct wb_child_request_state);
+ bool ok;
+
+ ok = tevent_queue_wait_recv(subreq);
+ if (!ok) {
+ tevent_req_oom(req);
+ return;
+ }
+ /*
+ * We need to keep state->queue_subreq
+ * in order to block the queue.
+ */
+ subreq = NULL;
+
+ if ((state->child->sock == -1) && (!fork_domain_child(state->child))) {
+ tevent_req_error(req, errno);
+ return;
+ }
+
+ tevent_fd_set_flags(state->child->monitor_fde, 0);
+
+ subreq = wb_simple_trans_send(state, global_event_context(), NULL,
+ state->child->sock, state->request);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+
+ state->subreq = subreq;
+ tevent_req_set_callback(subreq, wb_child_request_done, req);
+ tevent_req_set_endtime(req, state->ev, timeval_current_ofs(300, 0));
+}
+
+static void wb_child_request_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_child_request_state *state = tevent_req_data(
+ req, struct wb_child_request_state);
+ int ret, err;
+
+ ret = wb_simple_trans_recv(subreq, state, &state->response, &err);
+ /* Freeing the subrequest is deferred until the cleanup function,
+ * which has to know whether a subrequest exists, and consequently
+ * decide whether to shut down the pipe to the child process.
+ */
+ if (ret == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void wb_child_request_orphaned(struct tevent_req *subreq)
+{
+ struct winbindd_child *child =
+ (struct winbindd_child *)tevent_req_callback_data_void(subreq);
+
+ DBG_WARNING("cleanup orphaned subreq[%p]\n", subreq);
+ TALLOC_FREE(subreq);
+
+ if (child->domain != NULL) {
+ /*
+ * If the child is attached to a domain,
+ * we need to make sure the domain queue
+ * can move forward, after the orphaned
+ * request is done.
+ */
+ tevent_queue_start(child->domain->queue);
+ }
+}
+
+int wb_child_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct winbindd_response **presponse, int *err)
+{
+ struct wb_child_request_state *state = tevent_req_data(
+ req, struct wb_child_request_state);
+
+ if (tevent_req_is_unix_error(req, err)) {
+ return -1;
+ }
+ *presponse = talloc_move(mem_ctx, &state->response);
+ return 0;
+}
+
+static void wb_child_request_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct wb_child_request_state *state =
+ tevent_req_data(req, struct wb_child_request_state);
+
+ if (state->subreq == NULL) {
+ /* nothing to cleanup */
+ return;
+ }
+
+ if (req_state == TEVENT_REQ_RECEIVED) {
+ struct tevent_req *subreq = NULL;
+
+ /*
+ * Our caller gave up, but we need to keep
+ * the low level request (wb_simple_trans)
+ * in order to maintain the parent child protocol.
+ *
+ * We also need to keep the child queue blocked
+ * until we got the response from the child.
+ */
+
+ subreq = talloc_move(state->child->queue, &state->subreq);
+ talloc_move(subreq, &state->queue_subreq);
+ talloc_move(subreq, &state->request);
+ tevent_req_set_callback(subreq,
+ wb_child_request_orphaned,
+ state->child);
+
+ DBG_WARNING("keep orphaned subreq[%p]\n", subreq);
+ return;
+ }
+
+ TALLOC_FREE(state->subreq);
+ TALLOC_FREE(state->queue_subreq);
+
+ tevent_fd_set_flags(state->child->monitor_fde, TEVENT_FD_READ);
+
+ if (state->child->domain != NULL) {
+ /*
+ * If the child is attached to a domain,
+ * we need to make sure the domain queue
+ * can move forward, after the request
+ * is done.
+ */
+ tevent_queue_start(state->child->domain->queue);
+ }
+
+ if (req_state == TEVENT_REQ_DONE) {
+ /* transmitted request and got response */
+ return;
+ }
+
+ /*
+ * Failed to transmit and receive response, or request
+ * cancelled while being serviced.
+ * The basic parent/child communication broke, close
+ * our socket
+ */
+ TALLOC_FREE(state->child->monitor_fde);
+ close(state->child->sock);
+ state->child->sock = -1;
+}
+
+static void child_socket_readable(struct tevent_context *ev,
+ struct tevent_fd *fde,
+ uint16_t flags,
+ void *private_data)
+{
+ struct winbindd_child *child = private_data;
+
+ if ((flags & TEVENT_FD_READ) == 0) {
+ return;
+ }
+
+ TALLOC_FREE(child->monitor_fde);
+
+ /*
+ * We're only active when there is no outstanding child
+ * request. Arriving here means the child closed its socket,
+ * it died. Do the same here.
+ */
+
+ SMB_ASSERT(child->sock != -1);
+
+ close(child->sock);
+ child->sock = -1;
+}
+
+static struct winbindd_child *choose_domain_child(struct winbindd_domain *domain)
+{
+ struct winbindd_child *shortest = &domain->children[0];
+ struct winbindd_child *current;
+ int i;
+
+ for (i=0; i<talloc_array_length(domain->children); i++) {
+ size_t shortest_len, current_len;
+
+ current = &domain->children[i];
+ current_len = tevent_queue_length(current->queue);
+
+ if (current_len == 0) {
+ /* idle child */
+ return current;
+ }
+
+ shortest_len = tevent_queue_length(shortest->queue);
+
+ if (current_len < shortest_len) {
+ shortest = current;
+ }
+ }
+
+ return shortest;
+}
+
+struct dcerpc_binding_handle *dom_child_handle(struct winbindd_domain *domain)
+{
+ return domain->binding_handle;
+}
+
+struct wb_domain_request_state {
+ struct tevent_context *ev;
+ struct tevent_queue_entry *queue_entry;
+ struct winbindd_domain *domain;
+ struct winbindd_child *child;
+ struct winbindd_request *request;
+ struct winbindd_request *init_req;
+ struct winbindd_response *response;
+ struct tevent_req *pending_subreq;
+ struct wbint_InitConnection r;
+};
+
+static void wb_domain_request_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct wb_domain_request_state *state = tevent_req_data(
+ req, struct wb_domain_request_state);
+
+ /*
+ * If we're completely done or got a failure.
+ * we should remove ourself from the domain queue,
+ * after removing the child subreq from the child queue
+ * and give the next one in the queue the chance
+ * to check for an idle child.
+ */
+ TALLOC_FREE(state->pending_subreq);
+ TALLOC_FREE(state->queue_entry);
+ tevent_queue_start(state->domain->queue);
+}
+
+static void wb_domain_request_trigger(struct tevent_req *req,
+ void *private_data);
+static void wb_domain_request_gotdc(struct tevent_req *subreq);
+static void wb_domain_request_initialized(struct tevent_req *subreq);
+static void wb_domain_request_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_domain_request_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct wb_domain_request_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_domain_request_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ state->domain = domain;
+ state->ev = ev;
+ state->request = request;
+
+ tevent_req_set_cleanup_fn(req, wb_domain_request_cleanup);
+
+ state->queue_entry = tevent_queue_add_entry(
+ domain->queue, state->ev, req,
+ wb_domain_request_trigger, NULL);
+ if (tevent_req_nomem(state->queue_entry, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ return req;
+}
+
+static void wb_domain_request_trigger(struct tevent_req *req,
+ void *private_data)
+{
+ struct wb_domain_request_state *state = tevent_req_data(
+ req, struct wb_domain_request_state);
+ struct winbindd_domain *domain = state->domain;
+ struct tevent_req *subreq = NULL;
+ size_t shortest_queue_length;
+
+ state->child = choose_domain_child(domain);
+ shortest_queue_length = tevent_queue_length(state->child->queue);
+ if (shortest_queue_length > 0) {
+ /*
+ * All children are busy, we need to stop
+ * the queue and untrigger our own queue
+ * entry. Once a pending request
+ * is done it calls tevent_queue_start
+ * and we get retriggered.
+ */
+ state->child = NULL;
+ tevent_queue_stop(state->domain->queue);
+ tevent_queue_entry_untrigger(state->queue_entry);
+ return;
+ }
+
+ if (domain->initialized) {
+ subreq = wb_child_request_send(state, state->ev, state->child,
+ state->request);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_domain_request_done, req);
+ state->pending_subreq = subreq;
+
+ /*
+ * Once the domain is initialized and
+ * once we placed our real request into the child queue,
+ * we can remove ourself from the domain queue
+ * and give the next one in the queue the chance
+ * to check for an idle child.
+ */
+ TALLOC_FREE(state->queue_entry);
+ return;
+ }
+
+ state->init_req = talloc_zero(state, struct winbindd_request);
+ if (tevent_req_nomem(state->init_req, req)) {
+ return;
+ }
+
+ if (IS_DC || domain->primary || domain->internal) {
+ /* The primary domain has to find the DC name itself */
+ state->r.in.dcname = talloc_strdup(state, "");
+ if (tevent_req_nomem(state->r.in.dcname, req)) {
+ return;
+ }
+
+ subreq = dcerpc_wbint_InitConnection_r_send(state,
+ state->ev,
+ state->child->binding_handle,
+ &state->r);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_domain_request_initialized,
+ req);
+ state->pending_subreq = subreq;
+ return;
+ }
+
+ /*
+ * This is *not* the primary domain,
+ * let's ask our DC about a DC name.
+ *
+ * We prefer getting a dns name in dc_unc,
+ * which is indicated by DS_RETURN_DNS_NAME.
+ * For NT4 domains we still get the netbios name.
+ */
+ subreq = wb_dsgetdcname_send(state, state->ev,
+ state->domain->name,
+ NULL, /* domain_guid */
+ NULL, /* site_name */
+ DS_RETURN_DNS_NAME); /* flags */
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_domain_request_gotdc, req);
+ state->pending_subreq = subreq;
+ return;
+}
+
+static void wb_domain_request_gotdc(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_domain_request_state *state = tevent_req_data(
+ req, struct wb_domain_request_state);
+ struct netr_DsRGetDCNameInfo *dcinfo = NULL;
+ NTSTATUS status;
+ const char *dcname = NULL;
+
+ state->pending_subreq = NULL;
+
+ status = wb_dsgetdcname_recv(subreq, state, &dcinfo);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ dcname = dcinfo->dc_unc;
+ while (dcname != NULL && *dcname == '\\') {
+ dcname++;
+ }
+
+ state->r.in.dcname = talloc_strdup(state, dcname);
+ if (tevent_req_nomem(state->r.in.dcname, req)) {
+ return;
+ }
+
+ TALLOC_FREE(dcinfo);
+
+ subreq = dcerpc_wbint_InitConnection_r_send(state,
+ state->ev,
+ state->child->binding_handle,
+ &state->r);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_domain_request_initialized, req);
+ state->pending_subreq = subreq;
+}
+
+static void wb_domain_request_initialized(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_domain_request_state *state = tevent_req_data(
+ req, struct wb_domain_request_state);
+ NTSTATUS status;
+
+ state->pending_subreq = NULL;
+
+ status = dcerpc_wbint_InitConnection_r_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_IS_ERR(status)) {
+ tevent_req_error(req, map_errno_from_nt_status(status));
+ return;
+ }
+
+ status = state->r.out.result;
+ if (NT_STATUS_IS_ERR(status)) {
+ tevent_req_error(req, map_errno_from_nt_status(status));
+ return;
+ }
+
+ state->domain->sid = *state->r.out.sid;
+
+ talloc_free(state->domain->name);
+ state->domain->name = talloc_strdup(state->domain, *state->r.out.name);
+ if (state->domain->name == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+
+ if (*state->r.out.alt_name != NULL &&
+ strlen(*state->r.out.alt_name) > 0) {
+ talloc_free(state->domain->alt_name);
+
+ state->domain->alt_name = talloc_strdup(state->domain,
+ *state->r.out.alt_name);
+ if (state->domain->alt_name == NULL) {
+ tevent_req_error(req, ENOMEM);
+ return;
+ }
+ }
+
+ state->domain->native_mode =
+ (*state->r.out.flags & WB_DOMINFO_DOMAIN_NATIVE);
+ state->domain->active_directory =
+ (*state->r.out.flags & WB_DOMINFO_DOMAIN_AD);
+ state->domain->initialized = true;
+
+ subreq = wb_child_request_send(state, state->ev, state->child,
+ state->request);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, wb_domain_request_done, req);
+ state->pending_subreq = subreq;
+
+ /*
+ * Once the domain is initialized and
+ * once we placed our real request into the child queue,
+ * we can remove ourself from the domain queue
+ * and give the next one in the queue the chance
+ * to check for an idle child.
+ */
+ TALLOC_FREE(state->queue_entry);
+}
+
+static void wb_domain_request_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct wb_domain_request_state *state = tevent_req_data(
+ req, struct wb_domain_request_state);
+ int ret, err;
+
+ state->pending_subreq = NULL;
+
+ ret = wb_child_request_recv(subreq, talloc_tos(), &state->response,
+ &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ tevent_req_error(req, err);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+int wb_domain_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct winbindd_response **presponse, int *err)
+{
+ struct wb_domain_request_state *state = tevent_req_data(
+ req, struct wb_domain_request_state);
+
+ if (tevent_req_is_unix_error(req, err)) {
+ return -1;
+ }
+ *presponse = talloc_move(mem_ctx, &state->response);
+ return 0;
+}
+
+static void child_process_request(struct winbindd_child *child,
+ struct winbindd_cli_state *state)
+{
+ struct winbindd_domain *domain = child->domain;
+
+ /* Free response data - we may be interrupted and receive another
+ command before being able to send this data off. */
+
+ state->response->result = WINBINDD_ERROR;
+ state->response->length = sizeof(struct winbindd_response);
+
+ /* as all requests in the child are sync, we can use talloc_tos() */
+ state->mem_ctx = talloc_tos();
+
+ /* Process command */
+ state->response->result = winbindd_dual_ndrcmd(domain, state);
+}
+
+void setup_child(struct winbindd_domain *domain, struct winbindd_child *child,
+ const char *logprefix,
+ const char *logname)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+
+ if (logprefix && logname) {
+ char *logbase = NULL;
+
+ if (*lp_logfile(talloc_tos(), lp_sub)) {
+ char *end = NULL;
+
+ if (asprintf(&logbase, "%s", lp_logfile(talloc_tos(), lp_sub)) < 0) {
+ smb_panic("Internal error: asprintf failed");
+ }
+
+ if ((end = strrchr_m(logbase, '/'))) {
+ *end = '\0';
+ }
+ } else {
+ if (asprintf(&logbase, "%s", get_dyn_LOGFILEBASE()) < 0) {
+ smb_panic("Internal error: asprintf failed");
+ }
+ }
+
+ if (asprintf(&child->logfilename, "%s/%s-%s",
+ logbase, logprefix, logname) < 0) {
+ SAFE_FREE(logbase);
+ smb_panic("Internal error: asprintf failed");
+ }
+
+ SAFE_FREE(logbase);
+ } else {
+ smb_panic("Internal error: logprefix == NULL && "
+ "logname == NULL");
+ }
+
+ child->pid = 0;
+ child->sock = -1;
+ child->domain = domain;
+ child->queue = tevent_queue_create(NULL, "winbind_child");
+ SMB_ASSERT(child->queue != NULL);
+
+ child->binding_handle = wbint_binding_handle(NULL, NULL, child);
+ SMB_ASSERT(child->binding_handle != NULL);
+}
+
+struct winbind_child_died_state {
+ pid_t pid;
+ struct winbindd_child *child;
+};
+
+static bool winbind_child_died_fn(struct winbindd_child *child,
+ void *private_data)
+{
+ struct winbind_child_died_state *state = private_data;
+
+ if (child->pid == state->pid) {
+ state->child = child;
+ return false;
+ }
+ return true;
+}
+
+void winbind_child_died(pid_t pid)
+{
+ struct winbind_child_died_state state = { .pid = pid };
+
+ forall_children(winbind_child_died_fn, &state);
+
+ if (state.child == NULL) {
+ DEBUG(5, ("Already reaped child %u died\n", (unsigned int)pid));
+ return;
+ }
+
+ state.child->pid = 0;
+}
+
+/* Ensure any negative cache entries with the netbios or realm names are removed. */
+
+void winbindd_flush_negative_conn_cache(struct winbindd_domain *domain)
+{
+ flush_negative_conn_cache_for_domain(domain->name);
+ if (domain->alt_name != NULL) {
+ flush_negative_conn_cache_for_domain(domain->alt_name);
+ }
+}
+
+/*
+ * Parent winbindd process sets its own debug level first and then
+ * sends a message to all the winbindd children to adjust their debug
+ * level to that of parents.
+ */
+
+struct winbind_msg_relay_state {
+ struct messaging_context *msg_ctx;
+ uint32_t msg_type;
+ DATA_BLOB *data;
+};
+
+static bool winbind_msg_relay_fn(struct winbindd_child *child,
+ void *private_data)
+{
+ struct winbind_msg_relay_state *state = private_data;
+
+ DBG_DEBUG("sending message to pid %u.\n",
+ (unsigned int)child->pid);
+
+ messaging_send(state->msg_ctx, pid_to_procid(child->pid),
+ state->msg_type, state->data);
+ return true;
+}
+
+void winbind_msg_debug(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbind_msg_relay_state state = {
+ .msg_ctx = msg_ctx, .msg_type = msg_type, .data = data
+ };
+
+ DEBUG(10,("winbind_msg_debug: got debug message.\n"));
+
+ debug_message(msg_ctx, private_data, MSG_DEBUG, server_id, data);
+
+ forall_children(winbind_msg_relay_fn, &state);
+}
+
+void winbind_disconnect_dc_parent(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbind_msg_relay_state state = {
+ .msg_ctx = msg_ctx, .msg_type = msg_type, .data = data
+ };
+
+ DBG_DEBUG("Got disconnect_dc message\n");
+
+ forall_children(winbind_msg_relay_fn, &state);
+}
+
+static bool winbindd_child_msg_filter(struct messaging_rec *rec,
+ void *private_data)
+{
+ struct winbindd_child *child = talloc_get_type_abort(private_data,
+ struct winbindd_child);
+
+ if (rec->msg_type == MSG_SMB_CONF_UPDATED) {
+ DBG_DEBUG("Got reload-config message\n");
+ winbindd_reload_services_file(child->logfilename);
+ }
+
+ return false;
+}
+
+/* React on 'smbcontrol winbindd reload-config' in the same way as on SIGHUP*/
+void winbindd_msg_reload_services_parent(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbind_msg_relay_state state = {
+ .msg_ctx = msg,
+ .msg_type = msg_type,
+ .data = data,
+ };
+ bool ok;
+
+ DBG_DEBUG("Got reload-config message\n");
+
+ /* Flush various caches */
+ winbindd_flush_caches();
+
+ winbindd_reload_services_file((const char *)private_data);
+
+ /* Set tevent_thread_call_depth_set_callback according to debug level */
+ if (lp_winbind_debug_traceid() && debuglevel_get() > 1) {
+ tevent_thread_call_depth_set_callback(winbind_call_flow, NULL);
+ } else {
+ tevent_thread_call_depth_set_callback(NULL, NULL);
+ }
+
+ ok = add_trusted_domains_dc();
+ if (!ok) {
+ DBG_ERR("add_trusted_domains_dc() failed\n");
+ }
+
+ forall_children(winbind_msg_relay_fn, &state);
+}
+
+/* Set our domains as offline and forward the offline message to our children. */
+
+struct winbind_msg_on_offline_state {
+ struct messaging_context *msg_ctx;
+ uint32_t msg_type;
+};
+
+static bool winbind_msg_on_offline_fn(struct winbindd_child *child,
+ void *private_data)
+{
+ struct winbind_msg_on_offline_state *state = private_data;
+
+ if (child->domain->internal) {
+ return true;
+ }
+
+ /*
+ * Each winbindd child should only process requests for one
+ * domain - make sure we only set it online / offline for that
+ * domain.
+ */
+ DBG_DEBUG("sending message to pid %u for domain %s.\n",
+ (unsigned int)child->pid, child->domain->name);
+
+ messaging_send_buf(state->msg_ctx,
+ pid_to_procid(child->pid),
+ state->msg_type,
+ (const uint8_t *)child->domain->name,
+ strlen(child->domain->name)+1);
+
+ return true;
+}
+
+void winbind_msg_offline(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbind_msg_on_offline_state state = {
+ .msg_ctx = msg_ctx,
+ .msg_type = MSG_WINBIND_OFFLINE,
+ };
+ struct winbindd_domain *domain;
+
+ DEBUG(10,("winbind_msg_offline: got offline message.\n"));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("winbind_msg_offline: rejecting offline message.\n"));
+ return;
+ }
+
+ /* Set our global state as offline. */
+ if (!set_global_winbindd_state_offline()) {
+ DEBUG(10,("winbind_msg_offline: offline request failed.\n"));
+ return;
+ }
+
+ /* Set all our domains as offline. */
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ DEBUG(5,("winbind_msg_offline: marking %s offline.\n", domain->name));
+ domain->online = false;
+ }
+
+ forall_domain_children(winbind_msg_on_offline_fn, &state);
+}
+
+/* Set our domains as online and forward the online message to our children. */
+
+void winbind_msg_online(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbind_msg_on_offline_state state = {
+ .msg_ctx = msg_ctx,
+ .msg_type = MSG_WINBIND_ONLINE,
+ };
+
+ DEBUG(10,("winbind_msg_online: got online message.\n"));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("winbind_msg_online: rejecting online message.\n"));
+ return;
+ }
+
+ /* Set our global state as online. */
+ set_global_winbindd_state_online();
+
+ smb_nscd_flush_user_cache();
+ smb_nscd_flush_group_cache();
+
+ /* Tell all our child domains to go online online. */
+ forall_domain_children(winbind_msg_on_offline_fn, &state);
+}
+
+static const char *collect_onlinestatus(TALLOC_CTX *mem_ctx)
+{
+ struct winbindd_domain *domain;
+ char *buf = NULL;
+
+ if ((buf = talloc_asprintf(mem_ctx, "global:%s ",
+ get_global_winbindd_state_offline() ?
+ "Offline":"Online")) == NULL) {
+ return NULL;
+ }
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if ((buf = talloc_asprintf_append_buffer(buf, "%s:%s ",
+ domain->name,
+ domain->online ?
+ "Online":"Offline")) == NULL) {
+ return NULL;
+ }
+ }
+
+ buf = talloc_asprintf_append_buffer(buf, "\n");
+
+ DEBUG(5,("collect_onlinestatus: %s", buf));
+
+ return buf;
+}
+
+void winbind_msg_onlinestatus(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ TALLOC_CTX *mem_ctx;
+ const char *message;
+
+ DEBUG(5,("winbind_msg_onlinestatus received.\n"));
+
+ mem_ctx = talloc_init("winbind_msg_onlinestatus");
+ if (mem_ctx == NULL) {
+ return;
+ }
+
+ message = collect_onlinestatus(mem_ctx);
+ if (message == NULL) {
+ talloc_destroy(mem_ctx);
+ return;
+ }
+
+ messaging_send_buf(msg_ctx, server_id, MSG_WINBIND_ONLINESTATUS,
+ (const uint8_t *)message, strlen(message) + 1);
+
+ talloc_destroy(mem_ctx);
+}
+
+void winbind_msg_dump_domain_list(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ TALLOC_CTX *mem_ctx;
+ const char *message = NULL;
+ const char *domain = NULL;
+ char *s = NULL;
+ NTSTATUS status;
+ struct winbindd_domain *dom = NULL;
+
+ DEBUG(5,("winbind_msg_dump_domain_list received.\n"));
+
+ mem_ctx = talloc_init("winbind_msg_dump_domain_list");
+ if (!mem_ctx) {
+ return;
+ }
+
+ if (data->length > 0) {
+ domain = (const char *)data->data;
+ }
+
+ if (domain) {
+
+ DEBUG(5,("winbind_msg_dump_domain_list for domain: %s\n",
+ domain));
+
+ message = NDR_PRINT_STRUCT_STRING(mem_ctx, winbindd_domain,
+ find_domain_from_name_noinit(domain));
+ if (!message) {
+ talloc_destroy(mem_ctx);
+ return;
+ }
+
+ messaging_send_buf(msg_ctx, server_id,
+ MSG_WINBIND_DUMP_DOMAIN_LIST,
+ (const uint8_t *)message, strlen(message) + 1);
+
+ talloc_destroy(mem_ctx);
+
+ return;
+ }
+
+ DEBUG(5,("winbind_msg_dump_domain_list all domains\n"));
+
+ for (dom = domain_list(); dom; dom=dom->next) {
+ message = NDR_PRINT_STRUCT_STRING(mem_ctx, winbindd_domain, dom);
+ if (!message) {
+ talloc_destroy(mem_ctx);
+ return;
+ }
+
+ s = talloc_asprintf_append(s, "%s\n", message);
+ if (!s) {
+ talloc_destroy(mem_ctx);
+ return;
+ }
+ }
+
+ status = messaging_send_buf(msg_ctx, server_id,
+ MSG_WINBIND_DUMP_DOMAIN_LIST,
+ (uint8_t *)s, strlen(s) + 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("failed to send message: %s\n",
+ nt_errstr(status)));
+ }
+
+ talloc_destroy(mem_ctx);
+}
+
+static void account_lockout_policy_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct winbindd_child *child =
+ (struct winbindd_child *)private_data;
+ TALLOC_CTX *mem_ctx = NULL;
+ struct samr_DomInfo12 lockout_policy;
+ NTSTATUS result;
+
+ DEBUG(10,("account_lockout_policy_handler called\n"));
+
+ TALLOC_FREE(child->lockout_policy_event);
+
+ if ( !winbindd_can_contact_domain( child->domain ) ) {
+ DEBUG(10,("account_lockout_policy_handler: Removing myself since I "
+ "do not have an incoming trust to domain %s\n",
+ child->domain->name));
+
+ return;
+ }
+
+ mem_ctx = talloc_init("account_lockout_policy_handler ctx");
+ if (!mem_ctx) {
+ result = NT_STATUS_NO_MEMORY;
+ } else {
+ result = wb_cache_lockout_policy(child->domain, mem_ctx,
+ &lockout_policy);
+ }
+ TALLOC_FREE(mem_ctx);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("account_lockout_policy_handler: lockout_policy failed error %s\n",
+ nt_errstr(result)));
+ }
+
+ child->lockout_policy_event = tevent_add_timer(global_event_context(), NULL,
+ timeval_current_ofs(3600, 0),
+ account_lockout_policy_handler,
+ child);
+}
+
+static time_t get_machine_password_timeout(void)
+{
+ /* until we have gpo support use lp setting */
+ return lp_machine_password_timeout();
+}
+
+static bool calculate_next_machine_pwd_change(const char *domain,
+ struct timeval *t)
+{
+ time_t pass_last_set_time;
+ time_t timeout;
+ time_t next_change;
+ struct timeval tv;
+ char *pw;
+
+ pw = secrets_fetch_machine_password(domain,
+ &pass_last_set_time,
+ NULL);
+
+ if (pw == NULL) {
+ DEBUG(0,("cannot fetch own machine password ????\n"));
+ return false;
+ }
+
+ SAFE_FREE(pw);
+
+ timeout = get_machine_password_timeout();
+ if (timeout == 0) {
+ DEBUG(10,("machine password never expires\n"));
+ return false;
+ }
+
+ tv.tv_sec = pass_last_set_time;
+ DEBUG(10, ("password last changed %s\n",
+ timeval_string(talloc_tos(), &tv, false)));
+ tv.tv_sec += timeout;
+ DEBUGADD(10, ("password valid until %s\n",
+ timeval_string(talloc_tos(), &tv, false)));
+
+ if (time(NULL) < (pass_last_set_time + timeout)) {
+ next_change = pass_last_set_time + timeout;
+ DEBUG(10,("machine password still valid until: %s\n",
+ http_timestring(talloc_tos(), next_change)));
+ *t = timeval_set(next_change, 0);
+
+ if (lp_clustering()) {
+ uint8_t randbuf;
+ /*
+ * When having a cluster, we have several
+ * winbinds racing for the password change. In
+ * the machine_password_change_handler()
+ * function we check if someone else was
+ * faster when the event triggers. We add a
+ * 255-second random delay here, so that we
+ * don't run to change the password at the
+ * exact same moment.
+ */
+ generate_random_buffer(&randbuf, sizeof(randbuf));
+ DEBUG(10, ("adding %d seconds randomness\n",
+ (int)randbuf));
+ t->tv_sec += randbuf;
+ }
+ return true;
+ }
+
+ DEBUG(10,("machine password expired, needs immediate change\n"));
+
+ *t = timeval_zero();
+
+ return true;
+}
+
+static void machine_password_change_handler(struct tevent_context *ctx,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ struct winbindd_child *child =
+ (struct winbindd_child *)private_data;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ NTSTATUS result;
+ struct timeval next_change;
+
+ DEBUG(10,("machine_password_change_handler called\n"));
+
+ TALLOC_FREE(child->machine_password_change_event);
+
+ if (!calculate_next_machine_pwd_change(child->domain->name,
+ &next_change)) {
+ DEBUG(10, ("calculate_next_machine_pwd_change failed\n"));
+ return;
+ }
+
+ DEBUG(10, ("calculate_next_machine_pwd_change returned %s\n",
+ timeval_string(talloc_tos(), &next_change, false)));
+
+ if (!timeval_expired(&next_change)) {
+ DEBUG(10, ("Someone else has already changed the pw\n"));
+ goto done;
+ }
+
+ if (!winbindd_can_contact_domain(child->domain)) {
+ DEBUG(10,("machine_password_change_handler: Removing myself since I "
+ "do not have an incoming trust to domain %s\n",
+ child->domain->name));
+ return;
+ }
+
+ result = cm_connect_netlogon_secure(child->domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("machine_password_change_handler: "
+ "failed to connect netlogon pipe: %s\n",
+ nt_errstr(result)));
+ return;
+ }
+
+ result = trust_pw_change(netlogon_creds_ctx,
+ msg_ctx,
+ netlogon_pipe->binding_handle,
+ child->domain->name,
+ child->domain->dcname,
+ false); /* force */
+
+ DEBUG(10, ("machine_password_change_handler: "
+ "trust_pw_change returned %s\n",
+ nt_errstr(result)));
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED) ) {
+ DEBUG(3,("machine_password_change_handler: password set returned "
+ "ACCESS_DENIED. Maybe the trust account "
+ "password was changed and we didn't know it. "
+ "Killing connections to domain %s\n",
+ child->domain->name));
+ invalidate_cm_connection(child->domain);
+ }
+
+ if (!calculate_next_machine_pwd_change(child->domain->name,
+ &next_change)) {
+ DEBUG(10, ("calculate_next_machine_pwd_change failed\n"));
+ return;
+ }
+
+ DEBUG(10, ("calculate_next_machine_pwd_change returned %s\n",
+ timeval_string(talloc_tos(), &next_change, false)));
+
+ if (!NT_STATUS_IS_OK(result)) {
+ struct timeval tmp;
+ /*
+ * In case of failure, give the DC a minute to recover
+ */
+ tmp = timeval_current_ofs(60, 0);
+ next_change = timeval_max(&next_change, &tmp);
+ }
+
+done:
+ child->machine_password_change_event = tevent_add_timer(global_event_context(), NULL,
+ next_change,
+ machine_password_change_handler,
+ child);
+}
+
+/* Deal with a request to go offline. */
+
+static void child_msg_offline(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+ struct winbindd_domain *primary_domain = NULL;
+ const char *domainname = (const char *)data->data;
+
+ if (data->data == NULL || data->length == 0) {
+ return;
+ }
+
+ DEBUG(5,("child_msg_offline received for domain %s.\n", domainname));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("child_msg_offline: rejecting offline message.\n"));
+ return;
+ }
+
+ primary_domain = find_our_domain();
+
+ /* Mark the requested domain offline. */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ if (strequal(domain->name, domainname)) {
+ DEBUG(5,("child_msg_offline: marking %s offline.\n", domain->name));
+ set_domain_offline(domain);
+ /* we are in the trusted domain, set the primary domain
+ * offline too */
+ if (domain != primary_domain) {
+ set_domain_offline(primary_domain);
+ }
+ }
+ }
+}
+
+/* Deal with a request to go online. */
+
+static void child_msg_online(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbindd_domain *domain;
+ struct winbindd_domain *primary_domain = NULL;
+ const char *domainname = (const char *)data->data;
+
+ if (data->data == NULL || data->length == 0) {
+ return;
+ }
+
+ DEBUG(5,("child_msg_online received for domain %s.\n", domainname));
+
+ if (!lp_winbind_offline_logon()) {
+ DEBUG(10,("child_msg_online: rejecting online message.\n"));
+ return;
+ }
+
+ primary_domain = find_our_domain();
+
+ /* Set our global state as online. */
+ set_global_winbindd_state_online();
+
+ /* Try and mark everything online - delete any negative cache entries
+ to force a reconnect now. */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ if (domain->internal) {
+ continue;
+ }
+ if (strequal(domain->name, domainname)) {
+ DEBUG(5,("child_msg_online: requesting %s to go online.\n", domain->name));
+ winbindd_flush_negative_conn_cache(domain);
+ set_domain_online_request(domain);
+
+ /* we can be in trusted domain, which will contact primary domain
+ * we have to bring primary domain online in trusted domain process
+ * see, winbindd_dual_pam_auth() --> winbindd_dual_pam_auth_samlogon()
+ * --> contact_domain = find_our_domain()
+ * */
+ if (domain != primary_domain) {
+ winbindd_flush_negative_conn_cache(primary_domain);
+ set_domain_online_request(primary_domain);
+ }
+ }
+ }
+}
+
+struct winbindd_reinit_after_fork_state {
+ const struct winbindd_child *myself;
+};
+
+static bool winbindd_reinit_after_fork_fn(struct winbindd_child *child,
+ void *private_data)
+{
+ struct winbindd_reinit_after_fork_state *state = private_data;
+
+ if (child == state->myself) {
+ return true;
+ }
+
+ /* Destroy all possible events in child list. */
+ TALLOC_FREE(child->lockout_policy_event);
+ TALLOC_FREE(child->machine_password_change_event);
+
+ /*
+ * Children should never be able to send each other messages,
+ * all messages must go through the parent.
+ */
+ child->pid = (pid_t)0;
+
+ /*
+ * Close service sockets to all other children
+ */
+ if (child->sock != -1) {
+ close(child->sock);
+ child->sock = -1;
+ }
+
+ return true;
+}
+
+NTSTATUS winbindd_reinit_after_fork(const struct winbindd_child *myself,
+ const char *logfilename)
+{
+ struct winbindd_reinit_after_fork_state state = { .myself = myself };
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+
+ status = reinit_after_fork(
+ global_messaging_context(),
+ global_event_context(),
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("reinit_after_fork() failed\n"));
+ return status;
+ }
+ initialize_password_db(true, global_event_context());
+
+ close_conns_after_fork();
+
+ if (logfilename != NULL) {
+ lp_set_logfile(logfilename);
+ reopen_logs();
+ }
+
+ if (!winbindd_setup_sig_term_handler(false)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!winbindd_setup_sig_hup_handler(logfilename)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Stop zombies in children */
+ CatchChild();
+
+ /* Don't handle the same messages as our parent. */
+ messaging_deregister(global_messaging_context(),
+ MSG_SMB_CONF_UPDATED, NULL);
+ messaging_deregister(global_messaging_context(),
+ MSG_SHUTDOWN, NULL);
+ messaging_deregister(global_messaging_context(),
+ MSG_WINBIND_OFFLINE, NULL);
+ messaging_deregister(global_messaging_context(),
+ MSG_WINBIND_ONLINE, NULL);
+ messaging_deregister(global_messaging_context(),
+ MSG_WINBIND_ONLINESTATUS, NULL);
+ messaging_deregister(global_messaging_context(),
+ MSG_WINBIND_DUMP_DOMAIN_LIST, NULL);
+ messaging_deregister(global_messaging_context(),
+ MSG_DEBUG, NULL);
+
+ messaging_deregister(global_messaging_context(),
+ MSG_WINBIND_DOMAIN_OFFLINE, NULL);
+ messaging_deregister(global_messaging_context(),
+ MSG_WINBIND_DOMAIN_ONLINE, NULL);
+
+ /* We have destroyed all events in the winbindd_event_context
+ * in reinit_after_fork(), so clean out all possible pending
+ * event pointers. */
+
+ /* Deal with check_online_events. */
+
+ for (domain = domain_list(); domain; domain = domain->next) {
+ TALLOC_FREE(domain->check_online_event);
+ }
+
+ /* Ensure we're not handling a credential cache event inherited
+ * from our parent. */
+
+ ccache_remove_all_after_fork();
+
+ forall_children(winbindd_reinit_after_fork_fn, &state);
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * In a child there will be only one domain, reference that here.
+ */
+static struct winbindd_domain *child_domain;
+
+struct winbindd_domain *wb_child_domain(void)
+{
+ return child_domain;
+}
+
+struct child_handler_state {
+ struct winbindd_child *child;
+ struct winbindd_cli_state cli;
+};
+
+static void child_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *private_data)
+{
+ struct child_handler_state *state =
+ (struct child_handler_state *)private_data;
+ NTSTATUS status;
+ uint64_t parent_traceid;
+
+ /* fetch a request from the main daemon */
+ status = child_read_request(state->cli.sock, state->cli.request);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ /* we lost contact with our parent */
+ _exit(0);
+ }
+
+ /* read traceid from request */
+ parent_traceid = state->cli.request->traceid;
+ debug_traceid_set(parent_traceid);
+
+ DEBUG(4,("child daemon request %d\n",
+ (int)state->cli.request->cmd));
+
+ ZERO_STRUCTP(state->cli.response);
+ state->cli.request->null_term = '\0';
+ state->cli.mem_ctx = talloc_tos();
+ child_process_request(state->child, &state->cli);
+
+ DEBUG(4, ("Finished processing child request %d\n",
+ (int)state->cli.request->cmd));
+
+ SAFE_FREE(state->cli.request->extra_data.data);
+
+ status = child_write_response(state->cli.sock, state->cli.response);
+ if (!NT_STATUS_IS_OK(status)) {
+ exit(1);
+ }
+}
+
+static bool fork_domain_child(struct winbindd_child *child)
+{
+ int fdpair[2];
+ struct child_handler_state state;
+ struct winbindd_request request;
+ struct winbindd_response response;
+ struct winbindd_domain *primary_domain = NULL;
+ NTSTATUS status;
+ ssize_t nwritten;
+ struct tevent_fd *fde;
+ struct tevent_req *req = NULL;
+
+ if (child->domain) {
+ DEBUG(10, ("fork_domain_child called for domain '%s'\n",
+ child->domain->name));
+ } else {
+ DEBUG(10, ("fork_domain_child called without domain.\n"));
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fdpair) != 0) {
+ DEBUG(0, ("Could not open child pipe: %s\n",
+ strerror(errno)));
+ return False;
+ }
+
+ ZERO_STRUCT(state);
+ state.child = child;
+ state.cli.pid = getpid();
+ state.cli.request = &request;
+ state.cli.response = &response;
+
+ child->pid = fork();
+
+ if (child->pid == -1) {
+ DEBUG(0, ("Could not fork: %s\n", strerror(errno)));
+ close(fdpair[0]);
+ close(fdpair[1]);
+ return False;
+ }
+
+ if (child->pid != 0) {
+ /* Parent */
+ ssize_t nread;
+ int rc;
+
+ close(fdpair[0]);
+
+ nread = sys_read(fdpair[1], &status, sizeof(status));
+ if (nread != sizeof(status)) {
+ DEBUG(1, ("fork_domain_child: Could not read child status: "
+ "nread=%d, error=%s\n", (int)nread,
+ strerror(errno)));
+ close(fdpair[1]);
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("fork_domain_child: Child status is %s\n",
+ nt_errstr(status)));
+ close(fdpair[1]);
+ return false;
+ }
+
+ child->monitor_fde = tevent_add_fd(global_event_context(),
+ global_event_context(),
+ fdpair[1],
+ TEVENT_FD_READ,
+ child_socket_readable,
+ child);
+ if (child->monitor_fde == NULL) {
+ DBG_WARNING("tevent_add_fd failed\n");
+ close(fdpair[1]);
+ return false;
+ }
+
+ rc = set_blocking(fdpair[1], false);
+ if (rc < 0) {
+ close(fdpair[1]);
+ return false;
+ }
+
+ child->sock = fdpair[1];
+
+ return true;
+ }
+
+ /* Child */
+ child_domain = child->domain;
+
+ DEBUG(10, ("Child process %d\n", (int)getpid()));
+
+ state.cli.sock = fdpair[0];
+ close(fdpair[1]);
+
+ /* Reset traceid and deactivate call_depth tracking */
+ if (lp_winbind_debug_traceid()) {
+ debug_traceid_set(1);
+ tevent_thread_call_depth_set_callback(NULL, NULL);
+ }
+
+ status = winbindd_reinit_after_fork(child, child->logfilename);
+
+ /* setup callbacks again, one of them is removed in reinit_after_fork */
+ if (lp_winbind_debug_traceid()) {
+ winbind_debug_traceid_setup(global_event_context());
+ }
+
+ nwritten = sys_write(state.cli.sock, &status, sizeof(status));
+ if (nwritten != sizeof(status)) {
+ DEBUG(1, ("fork_domain_child: Could not write status: "
+ "nwritten=%d, error=%s\n", (int)nwritten,
+ strerror(errno)));
+ _exit(0);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(1, ("winbindd_reinit_after_fork failed: %s\n",
+ nt_errstr(status)));
+ _exit(0);
+ }
+
+ if (child_domain != NULL) {
+ process_set_title("wb[%s]", "domain child [%s]", child_domain->name);
+ } else if (is_idmap_child(child)) {
+ process_set_title("wb-idmap", "idmap child");
+ }
+
+ /* Handle online/offline messages. */
+ messaging_register(global_messaging_context(), NULL,
+ MSG_WINBIND_OFFLINE, child_msg_offline);
+ messaging_register(global_messaging_context(), NULL,
+ MSG_WINBIND_ONLINE, child_msg_online);
+ messaging_register(global_messaging_context(), NULL,
+ MSG_DEBUG, debug_message);
+ messaging_register(global_messaging_context(), NULL,
+ MSG_WINBIND_IP_DROPPED,
+ winbind_msg_ip_dropped);
+ messaging_register(global_messaging_context(), NULL,
+ MSG_WINBIND_DISCONNECT_DC,
+ winbind_msg_disconnect_dc);
+
+ req = messaging_filtered_read_send(global_event_context(),
+ global_event_context(),
+ global_messaging_context(),
+ winbindd_child_msg_filter,
+ child);
+ if (req == NULL) {
+ DBG_ERR("messaging_filtered_read_send failed\n");
+ _exit(1);
+ }
+
+ primary_domain = find_our_domain();
+
+ if (primary_domain == NULL) {
+ smb_panic("no primary domain found");
+ }
+
+ /* It doesn't matter if we allow cache login,
+ * try to bring domain online after fork. */
+ if ( child->domain ) {
+ child->domain->startup = True;
+ child->domain->startup_time = time_mono(NULL);
+ /* we can be in primary domain or in trusted domain
+ * If we are in trusted domain, set the primary domain
+ * in start-up mode */
+ if (!(child->domain->internal)) {
+ set_domain_online_request(child->domain);
+ if (!(child->domain->primary)) {
+ primary_domain->startup = True;
+ primary_domain->startup_time = time_mono(NULL);
+ set_domain_online_request(primary_domain);
+ }
+ }
+ }
+
+ /* We might be in the idmap child...*/
+ if (child->domain && !(child->domain->internal) &&
+ lp_winbind_offline_logon()) {
+
+ set_domain_online_request(child->domain);
+
+ if (primary_domain && (primary_domain != child->domain)) {
+ /* We need to talk to the primary
+ * domain as well as the trusted
+ * domain inside a trusted domain
+ * child.
+ * See the code in :
+ * set_dc_type_and_flags_trustinfo()
+ * for details.
+ */
+ set_domain_online_request(primary_domain);
+ }
+
+ child->lockout_policy_event = tevent_add_timer(
+ global_event_context(), NULL, timeval_zero(),
+ account_lockout_policy_handler,
+ child);
+ }
+
+ if (child->domain && child->domain->primary &&
+ !USE_KERBEROS_KEYTAB &&
+ lp_server_role() == ROLE_DOMAIN_MEMBER) {
+
+ struct timeval next_change;
+
+ if (calculate_next_machine_pwd_change(child->domain->name,
+ &next_change)) {
+ child->machine_password_change_event = tevent_add_timer(
+ global_event_context(), NULL, next_change,
+ machine_password_change_handler,
+ child);
+ }
+ }
+
+ fde = tevent_add_fd(global_event_context(), NULL, state.cli.sock,
+ TEVENT_FD_READ, child_handler, &state);
+ if (fde == NULL) {
+ DEBUG(1, ("tevent_add_fd failed\n"));
+ _exit(1);
+ }
+
+ while (1) {
+
+ int ret;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ ret = tevent_loop_once(global_event_context());
+ if (ret != 0) {
+ DEBUG(1, ("tevent_loop_once failed: %s\n",
+ strerror(errno)));
+ _exit(1);
+ }
+
+ if (child->domain && child->domain->startup &&
+ (time_mono(NULL) > child->domain->startup_time + 30)) {
+ /* No longer in "startup" mode. */
+ DEBUG(10,("fork_domain_child: domain %s no longer in 'startup' mode.\n",
+ child->domain->name ));
+ child->domain->startup = False;
+ }
+
+ TALLOC_FREE(frame);
+ }
+}
+
+void winbind_msg_ip_dropped_parent(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data)
+{
+ struct winbind_msg_relay_state state = {
+ .msg_ctx = msg_ctx,
+ .msg_type = msg_type,
+ .data = data,
+ };
+
+ winbind_msg_ip_dropped(msg_ctx, private_data, msg_type,
+ server_id, data);
+
+ forall_children(winbind_msg_relay_fn, &state);
+}
+
+void winbindd_terminate(bool is_parent)
+{
+ if (is_parent) {
+ /* When parent goes away we should
+ * remove the socket file. Not so
+ * when children terminate.
+ */
+ char *path = NULL;
+
+ if (asprintf(&path, "%s/%s",
+ lp_winbindd_socket_directory(), WINBINDD_SOCKET_NAME) > 0) {
+ unlink(path);
+ SAFE_FREE(path);
+ }
+ }
+
+ idmap_close();
+
+ netlogon_creds_cli_close_global_db();
+
+#if 0
+ if (interactive) {
+ TALLOC_CTX *mem_ctx = talloc_init("end_description");
+ char *description = talloc_describe_all(mem_ctx);
+
+ DEBUG(3, ("tallocs left:\n%s\n", description));
+ talloc_destroy(mem_ctx);
+ }
+#endif
+
+ if (is_parent) {
+ pidfile_unlink(lp_pid_directory(), "winbindd");
+ }
+
+ exit(0);
+}
+
+static void winbindd_sig_term_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ bool *p = talloc_get_type_abort(private_data, bool);
+ bool is_parent = *p;
+
+ TALLOC_FREE(p);
+
+ DEBUG(0,("Got sig[%d] terminate (is_parent=%d)\n",
+ signum, is_parent));
+ winbindd_terminate(is_parent);
+}
+
+bool winbindd_setup_sig_term_handler(bool parent)
+{
+ struct tevent_signal *se;
+ bool *is_parent;
+
+ is_parent = talloc(global_event_context(), bool);
+ if (!is_parent) {
+ return false;
+ }
+
+ *is_parent = parent;
+
+ se = tevent_add_signal(global_event_context(),
+ is_parent,
+ SIGTERM, 0,
+ winbindd_sig_term_handler,
+ is_parent);
+ if (!se) {
+ DEBUG(0,("failed to setup SIGTERM handler\n"));
+ talloc_free(is_parent);
+ return false;
+ }
+
+ se = tevent_add_signal(global_event_context(),
+ is_parent,
+ SIGINT, 0,
+ winbindd_sig_term_handler,
+ is_parent);
+ if (!se) {
+ DEBUG(0,("failed to setup SIGINT handler\n"));
+ talloc_free(is_parent);
+ return false;
+ }
+
+ se = tevent_add_signal(global_event_context(),
+ is_parent,
+ SIGQUIT, 0,
+ winbindd_sig_term_handler,
+ is_parent);
+ if (!se) {
+ DEBUG(0,("failed to setup SIGINT handler\n"));
+ talloc_free(is_parent);
+ return false;
+ }
+
+ return true;
+}
+
+static void flush_caches_noinit(void)
+{
+ /*
+ * We need to invalidate cached user list entries on a SIGHUP
+ * otherwise cached access denied errors due to restrict anonymous
+ * hang around until the sequence number changes.
+ * NB
+ * Skip uninitialized domains when flush cache.
+ * If domain is not initialized, it means it is never
+ * used or never become online. look, wcache_invalidate_cache()
+ * -> get_cache() -> init_dc_connection(). It causes a lot of traffic
+ * for unused domains and large traffic for primary domain's DC if there
+ * are many domains..
+ */
+
+ if (!wcache_invalidate_cache_noinit()) {
+ DEBUG(0, ("invalidating the cache failed; revalidate the cache\n"));
+ if (!winbindd_cache_validate_and_initialize()) {
+ exit(1);
+ }
+ }
+}
+
+static void winbindd_sig_hup_handler(struct tevent_context *ev,
+ struct tevent_signal *se,
+ int signum,
+ int count,
+ void *siginfo,
+ void *private_data)
+{
+ const char *file = (const char *)private_data;
+
+ DEBUG(1,("Reloading services after SIGHUP\n"));
+ flush_caches_noinit();
+ winbindd_reload_services_file(file);
+}
+
+bool winbindd_setup_sig_hup_handler(const char *lfile)
+{
+ struct tevent_signal *se;
+ char *file = NULL;
+
+ if (lfile) {
+ file = talloc_strdup(global_event_context(),
+ lfile);
+ if (!file) {
+ return false;
+ }
+ }
+
+ se = tevent_add_signal(global_event_context(),
+ global_event_context(),
+ SIGHUP, 0,
+ winbindd_sig_hup_handler,
+ file);
+ if (!se) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/source3/winbindd/winbindd_dual_ndr.c b/source3/winbindd/winbindd_dual_ndr.c
new file mode 100644
index 0000000..7835bd3
--- /dev/null
+++ b/source3/winbindd/winbindd_dual_ndr.c
@@ -0,0 +1,615 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Provide parent->child communication based on NDR marshalling
+
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ * This file implements an RPC between winbind parent and child processes,
+ * leveraging the autogenerated marshalling routines for MSRPC. This is not
+ * MSRPC, as it does not go through the whole DCERPC fragmentation, we just
+ * leverage much the same infrastructure we already have for it.
+ */
+
+#include "includes.h"
+#include "winbindd/winbindd.h"
+#include "winbindd/winbindd_proto.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_winbind.h"
+#include "rpc_server/rpc_config.h"
+#include "rpc_server/rpc_server.h"
+#include "rpc_dce.h"
+#include "lib/tsocket/tsocket.h"
+
+struct wbint_bh_state {
+ struct winbindd_domain *domain;
+ struct winbindd_child *child;
+};
+
+static bool wbint_bh_is_connected(struct dcerpc_binding_handle *h)
+{
+ struct wbint_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct wbint_bh_state);
+
+ if ((hs->domain == NULL) && (hs->child == NULL)) {
+ return false;
+ }
+
+ return true;
+}
+
+static uint32_t wbint_bh_set_timeout(struct dcerpc_binding_handle *h,
+ uint32_t timeout)
+{
+ /* TODO: implement timeouts */
+ return UINT32_MAX;
+}
+
+struct wbint_bh_raw_call_state {
+ struct winbindd_domain *domain;
+ uint32_t opnum;
+ DATA_BLOB in_data;
+ struct winbindd_request request;
+ struct winbindd_response *response;
+ DATA_BLOB out_data;
+};
+
+static void wbint_bh_raw_call_child_done(struct tevent_req *subreq);
+static void wbint_bh_raw_call_domain_done(struct tevent_req *subreq);
+
+static struct tevent_req *wbint_bh_raw_call_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *h,
+ const struct GUID *object,
+ uint32_t opnum,
+ uint32_t in_flags,
+ const uint8_t *in_data,
+ size_t in_length)
+{
+ struct wbint_bh_state *hs =
+ dcerpc_binding_handle_data(h,
+ struct wbint_bh_state);
+ struct tevent_req *req;
+ struct wbint_bh_raw_call_state *state;
+ bool ok;
+ struct tevent_req *subreq;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wbint_bh_raw_call_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->domain = hs->domain;
+ state->opnum = opnum;
+ state->in_data.data = discard_const_p(uint8_t, in_data);
+ state->in_data.length = in_length;
+
+ ok = wbint_bh_is_connected(h);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
+ return tevent_req_post(req, ev);
+ }
+
+ if ((state->domain != NULL)
+ && wcache_fetch_ndr(state, state->domain, state->opnum,
+ &state->in_data, &state->out_data)) {
+ DBG_DEBUG("Got opnum %"PRIu32" for domain %s from cache\n",
+ state->opnum, state->domain->name);
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ state->request.cmd = WINBINDD_DUAL_NDRCMD;
+ state->request.data.ndrcmd = state->opnum;
+ state->request.extra_data.data = (char *)state->in_data.data;
+ state->request.extra_len = state->in_data.length;
+
+ if (hs->child != NULL) {
+ subreq = wb_child_request_send(state, ev, hs->child,
+ &state->request);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, wbint_bh_raw_call_child_done, req);
+ return req;
+ }
+
+ subreq = wb_domain_request_send(state, ev, hs->domain,
+ &state->request);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, wbint_bh_raw_call_domain_done, req);
+
+ return req;
+}
+
+static void wbint_bh_raw_call_child_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct wbint_bh_raw_call_state *state =
+ tevent_req_data(req,
+ struct wbint_bh_raw_call_state);
+ int ret, err;
+
+ ret = wb_child_request_recv(subreq, state, &state->response, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ NTSTATUS status = map_nt_error_from_unix(err);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->out_data = data_blob_talloc(state,
+ state->response->extra_data.data,
+ state->response->length - sizeof(struct winbindd_response));
+ if (state->response->extra_data.data && !state->out_data.data) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ if (state->domain != NULL) {
+ wcache_store_ndr(state->domain, state->opnum,
+ &state->in_data, &state->out_data);
+ }
+
+ tevent_req_done(req);
+}
+
+static void wbint_bh_raw_call_domain_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct wbint_bh_raw_call_state *state =
+ tevent_req_data(req,
+ struct wbint_bh_raw_call_state);
+ int ret, err;
+
+ ret = wb_domain_request_recv(subreq, state, &state->response, &err);
+ TALLOC_FREE(subreq);
+ if (ret == -1) {
+ NTSTATUS status = map_nt_error_from_unix(err);
+ tevent_req_nterror(req, status);
+ return;
+ }
+
+ state->out_data = data_blob_talloc(state,
+ state->response->extra_data.data,
+ state->response->length - sizeof(struct winbindd_response));
+ if (state->response->extra_data.data && !state->out_data.data) {
+ tevent_req_oom(req);
+ return;
+ }
+
+ if (state->domain != NULL) {
+ wcache_store_ndr(state->domain, state->opnum,
+ &state->in_data, &state->out_data);
+ }
+
+ tevent_req_done(req);
+}
+
+static NTSTATUS wbint_bh_raw_call_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint8_t **out_data,
+ size_t *out_length,
+ uint32_t *out_flags)
+{
+ struct wbint_bh_raw_call_state *state =
+ tevent_req_data(req,
+ struct wbint_bh_raw_call_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ *out_data = talloc_move(mem_ctx, &state->out_data.data);
+ *out_length = state->out_data.length;
+ *out_flags = 0;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+struct wbint_bh_disconnect_state {
+ uint8_t _dummy;
+};
+
+static struct tevent_req *wbint_bh_disconnect_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dcerpc_binding_handle *h)
+{
+ struct wbint_bh_state *hs = dcerpc_binding_handle_data(h,
+ struct wbint_bh_state);
+ struct tevent_req *req;
+ struct wbint_bh_disconnect_state *state;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wbint_bh_disconnect_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ ok = wbint_bh_is_connected(h);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_CONNECTION_DISCONNECTED);
+ return tevent_req_post(req, ev);
+ }
+
+ /*
+ * TODO: do a real async disconnect ...
+ */
+ hs->domain = NULL;
+ hs->child = NULL;
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+static NTSTATUS wbint_bh_disconnect_recv(struct tevent_req *req)
+{
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
+
+static bool wbint_bh_ref_alloc(struct dcerpc_binding_handle *h)
+{
+ return true;
+}
+
+static void wbint_bh_do_ndr_print(struct dcerpc_binding_handle *h,
+ ndr_flags_type ndr_flags,
+ const void *_struct_ptr,
+ const struct ndr_interface_call *call)
+{
+ void *struct_ptr = discard_const(_struct_ptr);
+
+ if (DEBUGLEVEL < 10) {
+ return;
+ }
+
+ if (ndr_flags & NDR_IN) {
+ ndr_print_function_debug(call->ndr_print,
+ call->name,
+ ndr_flags,
+ struct_ptr);
+ }
+ if (ndr_flags & NDR_OUT) {
+ ndr_print_function_debug(call->ndr_print,
+ call->name,
+ ndr_flags,
+ struct_ptr);
+ }
+}
+
+static const struct dcerpc_binding_handle_ops wbint_bh_ops = {
+ .name = "wbint",
+ .is_connected = wbint_bh_is_connected,
+ .set_timeout = wbint_bh_set_timeout,
+ .raw_call_send = wbint_bh_raw_call_send,
+ .raw_call_recv = wbint_bh_raw_call_recv,
+ .disconnect_send = wbint_bh_disconnect_send,
+ .disconnect_recv = wbint_bh_disconnect_recv,
+
+ .ref_alloc = wbint_bh_ref_alloc,
+ .do_ndr_print = wbint_bh_do_ndr_print,
+};
+
+static NTSTATUS make_internal_ncacn_conn(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ struct dcerpc_ncacn_conn **_out)
+{
+ struct dcerpc_ncacn_conn *ncacn_conn = NULL;
+
+ ncacn_conn = talloc_zero(mem_ctx, struct dcerpc_ncacn_conn);
+ if (ncacn_conn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ ncacn_conn->p.mem_ctx = mem_ctx;
+
+ *_out = ncacn_conn;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS find_ncalrpc_default_endpoint(struct dcesrv_context *dce_ctx,
+ struct dcesrv_endpoint **ep)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct dcerpc_binding *binding = NULL;
+ NTSTATUS status;
+
+ tmp_ctx = talloc_new(dce_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Some services use a rpcint binding handle in their initialization,
+ * before the server is fully initialized. Search the NCALRPC endpoint
+ * with and without endpoint
+ */
+ status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:", &binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = dcesrv_find_endpoint(dce_ctx, binding, ep);
+ if (NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:[WINBIND]", &binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = dcesrv_find_endpoint(dce_ctx, binding, ep);
+ if (NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = dcerpc_parse_binding(tmp_ctx, "ncalrpc:[DEFAULT]", &binding);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = dcesrv_find_endpoint(dce_ctx, binding, ep);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+out:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS make_internal_dcesrv_connection(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *ndr_table,
+ struct dcerpc_ncacn_conn *ncacn_conn,
+ struct dcesrv_connection **_out)
+{
+ struct dcesrv_connection *conn = NULL;
+ struct dcesrv_connection_context *context = NULL;
+ struct dcesrv_endpoint *endpoint = NULL;
+ NTSTATUS status;
+
+ conn = talloc_zero(mem_ctx, struct dcesrv_connection);
+ if (conn == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ conn->dce_ctx = global_dcesrv_context();
+ conn->preferred_transfer = &ndr_transfer_syntax_ndr;
+ conn->transport.private_data = ncacn_conn;
+
+ status = find_ncalrpc_default_endpoint(conn->dce_ctx, &endpoint);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ conn->endpoint = endpoint;
+
+ conn->default_auth_state = talloc_zero(conn, struct dcesrv_auth);
+ if (conn->default_auth_state == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ conn->default_auth_state->auth_finished = true;
+
+ context = talloc_zero(conn, struct dcesrv_connection_context);
+ if (context == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ context->conn = conn;
+ context->context_id = 0;
+ context->transfer_syntax = *(conn->preferred_transfer);
+ context->iface = find_interface_by_syntax_id(
+ conn->endpoint, &ndr_table->syntax_id);
+ if (context->iface == NULL) {
+ status = NT_STATUS_RPC_INTERFACE_NOT_FOUND;
+ goto fail;
+ }
+
+ DLIST_ADD(conn->contexts, context);
+
+ *_out = conn;
+
+ return NT_STATUS_OK;
+fail:
+ talloc_free(conn);
+ return status;
+}
+
+static NTSTATUS set_remote_addresses(struct dcesrv_connection *conn,
+ int sock)
+{
+ struct sockaddr_storage st = { 0 };
+ struct sockaddr *sar = (struct sockaddr *)&st;
+ struct tsocket_address *remote = NULL;
+ struct tsocket_address *local = NULL;
+ socklen_t sa_len = sizeof(st);
+ NTSTATUS status;
+ int ret;
+
+ ZERO_STRUCT(st);
+ ret = getpeername(sock, sar, &sa_len);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(ret);
+ DBG_ERR("getpeername failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ ret = tsocket_address_bsd_from_sockaddr(conn, sar, sa_len, &remote);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(ret);
+ DBG_ERR("tsocket_address_bsd_from_sockaddr failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ ZERO_STRUCT(st);
+ ret = getsockname(sock, sar, &sa_len);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(ret);
+ DBG_ERR("getsockname failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ ret = tsocket_address_bsd_from_sockaddr(conn, sar, sa_len, &local);
+ if (ret != 0) {
+ status = map_nt_error_from_unix(ret);
+ DBG_ERR("tsocket_address_bsd_from_sockaddr failed: %s\n",
+ nt_errstr(status));
+ return status;
+ }
+
+ conn->local_address = talloc_move(conn, &local);
+ conn->remote_address = talloc_move(conn, &remote);
+
+ return NT_STATUS_OK;
+}
+
+/* initialise a wbint binding handle */
+struct dcerpc_binding_handle *wbint_binding_handle(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct winbindd_child *child)
+{
+ struct dcerpc_binding_handle *h;
+ struct wbint_bh_state *hs;
+
+ h = dcerpc_binding_handle_create(mem_ctx,
+ &wbint_bh_ops,
+ NULL,
+ &ndr_table_winbind,
+ &hs,
+ struct wbint_bh_state,
+ __location__);
+ if (h == NULL) {
+ return NULL;
+ }
+ hs->domain = domain;
+ hs->child = child;
+
+ return h;
+}
+
+enum winbindd_result winbindd_dual_ndrcmd(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state)
+{
+ struct dcerpc_ncacn_conn *ncacn_conn = NULL;
+ struct dcesrv_connection *dcesrv_conn = NULL;
+ struct dcesrv_call_state *dcesrv_call = NULL;
+ struct data_blob_list_item *rep = NULL;
+ struct dcesrv_context_callbacks *cb = NULL;
+ uint32_t opnum = state->request->data.ndrcmd;
+ TALLOC_CTX *mem_ctx;
+ NTSTATUS status;
+
+ DBG_DEBUG("Running command %s (domain '%s')\n",
+ ndr_table_winbind.calls[opnum].name,
+ domain ? domain->name : "(null)");
+
+ mem_ctx = talloc_stackframe();
+ if (mem_ctx == NULL) {
+ DBG_ERR("No memory\n");
+ return WINBINDD_ERROR;
+ }
+
+ status = make_internal_ncacn_conn(mem_ctx,
+ &ndr_table_winbind,
+ &ncacn_conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = make_internal_dcesrv_connection(ncacn_conn,
+ &ndr_table_winbind,
+ ncacn_conn,
+ &dcesrv_conn);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ status = set_remote_addresses(dcesrv_conn, state->sock);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ dcesrv_call = talloc_zero(dcesrv_conn, struct dcesrv_call_state);
+ if (dcesrv_call == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ dcesrv_call->conn = dcesrv_conn;
+ dcesrv_call->context = dcesrv_conn->contexts;
+ dcesrv_call->auth_state = dcesrv_conn->default_auth_state;
+
+ ZERO_STRUCT(dcesrv_call->pkt);
+ dcesrv_call->pkt.u.bind.assoc_group_id = 0;
+
+ cb = dcesrv_call->conn->dce_ctx->callbacks;
+ status = cb->assoc_group.find(
+ dcesrv_call, cb->assoc_group.private_data);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ ZERO_STRUCT(dcesrv_call->pkt);
+ dcesrv_call->pkt.u.request.opnum = opnum;
+ dcesrv_call->pkt.u.request.context_id = 0;
+ dcesrv_call->pkt.u.request.stub_and_verifier =
+ data_blob_const(state->request->extra_data.data,
+ state->request->extra_len);
+
+ status = dcesrv_call_dispatch_local(dcesrv_call);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ rep = dcesrv_call->replies;
+ DLIST_REMOVE(dcesrv_call->replies, rep);
+
+ state->response->extra_data.data = talloc_steal(state->mem_ctx,
+ rep->blob.data);
+ state->response->length += rep->blob.length;
+
+ talloc_free(rep);
+
+out:
+ talloc_free(mem_ctx);
+ if (NT_STATUS_IS_OK(status)) {
+ return WINBINDD_OK;
+ }
+ return WINBINDD_ERROR;
+}
diff --git a/source3/winbindd/winbindd_dual_srv.c b/source3/winbindd/winbindd_dual_srv.c
new file mode 100644
index 0000000..bbdaf6e
--- /dev/null
+++ b/source3/winbindd/winbindd_dual_srv.c
@@ -0,0 +1,2127 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ In-Child server implementation of the routines defined in wbint.idl
+
+ Copyright (C) Volker Lendecke 2009
+ Copyright (C) Guenther Deschner 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd/winbindd.h"
+#include "winbindd/winbindd_proto.h"
+#include "rpc_client/cli_pipe.h"
+#include "ntdomain.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_winbind.h"
+#include "librpc/gen_ndr/ndr_winbind_scompat.h"
+#include "../librpc/gen_ndr/ndr_netlogon_c.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "idmap.h"
+#include "../libcli/security/security.h"
+#include "../libcli/auth/netlogon_creds_cli.h"
+#include "passdb.h"
+#include "../source4/dsdb/samdb/samdb.h"
+#include "rpc_client/cli_netlogon.h"
+#include "rpc_client/util_netlogon.h"
+#include "libsmb/dsgetdcname.h"
+#include "lib/global_contexts.h"
+
+NTSTATUS _wbint_Ping(struct pipes_struct *p, struct wbint_Ping *r)
+{
+ *r->out.out_data = r->in.in_data;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_InitConnection(struct pipes_struct *p,
+ struct wbint_InitConnection *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+
+ if (r->in.dcname != NULL && strlen(r->in.dcname) > 0) {
+ TALLOC_FREE(domain->dcname);
+ domain->dcname = talloc_strdup(domain, r->in.dcname);
+ if (domain->dcname == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ init_dc_connection(domain, false);
+
+ if (!domain->initialized) {
+ /*
+ * If we return error here we can't do any cached
+ * authentication, but we may be in disconnected mode and can't
+ * initialize correctly. Do what the previous code did and just
+ * return without initialization, once we go online we'll
+ * re-initialize.
+ */
+ DBG_INFO("%s returning without initialization online = %d\n",
+ domain->name, (int)domain->online);
+ }
+
+ *r->out.name = talloc_strdup(p->mem_ctx, domain->name);
+ if (*r->out.name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (domain->alt_name != NULL) {
+ *r->out.alt_name = talloc_strdup(p->mem_ctx, domain->alt_name);
+ if (*r->out.alt_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ r->out.sid = dom_sid_dup(p->mem_ctx, &domain->sid);
+ if (r->out.sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *r->out.flags = 0;
+ if (domain->native_mode) {
+ *r->out.flags |= WB_DOMINFO_DOMAIN_NATIVE;
+ }
+ if (domain->active_directory) {
+ *r->out.flags |= WB_DOMINFO_DOMAIN_AD;
+ }
+ if (domain->primary) {
+ *r->out.flags |= WB_DOMINFO_DOMAIN_PRIMARY;
+ }
+
+ return NT_STATUS_OK;
+}
+
+bool reset_cm_connection_on_error(struct winbindd_domain *domain,
+ struct dcerpc_binding_handle *b,
+ NTSTATUS status)
+{
+ if (NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED)) {
+ invalidate_cm_connection(domain);
+ domain->conn.netlogon_force_reauth = true;
+ return true;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR))
+ {
+ invalidate_cm_connection(domain);
+ /* We invalidated the connection. */
+ return true;
+ }
+
+ if (b != NULL && !dcerpc_binding_handle_is_connected(b)) {
+ invalidate_cm_connection(domain);
+ return true;
+ }
+
+ return false;
+}
+
+NTSTATUS _wbint_LookupSid(struct pipes_struct *p, struct wbint_LookupSid *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ char *dom_name;
+ char *name;
+ enum lsa_SidType type;
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = wb_cache_sid_to_name(domain, p->mem_ctx, r->in.sid,
+ &dom_name, &name, &type);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ *r->out.domain = dom_name;
+ *r->out.name = name;
+ *r->out.type = type;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_LookupSids(struct pipes_struct *p, struct wbint_LookupSids *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ struct lsa_RefDomainList *domains = r->out.domains;
+ NTSTATUS status;
+ bool retry = false;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /*
+ * This breaks the winbindd_domain->methods abstraction: This
+ * is only called for remote domains, and both winbindd_msrpc
+ * and winbindd_ad call into lsa_lookupsids anyway. Caching is
+ * done at the wbint RPC layer.
+ */
+again:
+ status = rpc_lookup_sids(p->mem_ctx, domain, r->in.sids,
+ &domains, &r->out.names);
+
+ if (domains != NULL) {
+ r->out.domains = domains;
+ }
+
+ if (!retry && reset_cm_connection_on_error(domain, NULL, status)) {
+ retry = true;
+ goto again;
+ }
+
+ return status;
+}
+
+NTSTATUS _wbint_LookupName(struct pipes_struct *p, struct wbint_LookupName *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = wb_cache_name_to_sid(domain, p->mem_ctx, r->in.domain,
+ r->in.name, r->in.flags,
+ r->out.sid, r->out.type);
+ reset_cm_connection_on_error(domain, NULL, status);
+ return status;
+}
+
+NTSTATUS _wbint_Sids2UnixIDs(struct pipes_struct *p,
+ struct wbint_Sids2UnixIDs *r)
+{
+ uint32_t i;
+
+ struct lsa_DomainInfo *d;
+ struct wbint_TransID *ids;
+ uint32_t num_ids;
+
+ struct id_map **id_map_ptrs = NULL;
+ struct idmap_domain *dom;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+
+ if (r->in.domains->count != 1) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ d = &r->in.domains->domains[0];
+ ids = r->in.ids->ids;
+ num_ids = r->in.ids->num_ids;
+
+ dom = idmap_find_domain_with_sid(d->name.string, d->sid);
+ if (dom == NULL) {
+ struct dom_sid_buf buf;
+ DEBUG(10, ("idmap domain %s:%s not found\n",
+ d->name.string,
+ dom_sid_str_buf(d->sid, &buf)));
+
+ for (i=0; i<num_ids; i++) {
+
+ ids[i].xid = (struct unixid) {
+ .id = UINT32_MAX,
+ .type = ID_TYPE_NOT_SPECIFIED
+ };
+ }
+
+ return NT_STATUS_OK;
+ }
+
+ id_map_ptrs = id_map_ptrs_init(talloc_tos(), num_ids);
+ if (id_map_ptrs == NULL) {
+ goto nomem;
+ }
+
+ /*
+ * Convert the input data into a list of id_map structs
+ * suitable for handing in to the idmap sids_to_unixids
+ * method.
+ */
+
+ for (i=0; i<num_ids; i++) {
+ struct id_map *m = id_map_ptrs[i];
+
+ sid_compose(m->sid, d->sid, ids[i].rid);
+ m->status = ID_UNKNOWN;
+ m->xid = (struct unixid) { .type = ids[i].type_hint };
+ }
+
+ status = dom->methods->sids_to_unixids(dom, id_map_ptrs);
+
+ if (NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) {
+ /*
+ * This is okay. We need to transfer the mapped ones
+ * up to our caller. The individual mappings carry the
+ * information whether they are mapped or not.
+ */
+ status = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("sids_to_unixids returned %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+
+ /*
+ * Extract the results for handing them back to the caller.
+ */
+
+ for (i=0; i<num_ids; i++) {
+ struct id_map *m = id_map_ptrs[i];
+
+ if (m->status == ID_REQUIRE_TYPE) {
+ ids[i].xid.id = UINT32_MAX;
+ ids[i].xid.type = ID_TYPE_WB_REQUIRE_TYPE;
+ continue;
+ }
+
+ if (!idmap_unix_id_is_in_range(m->xid.id, dom)) {
+ DBG_DEBUG("id %"PRIu32" is out of range "
+ "%"PRIu32"-%"PRIu32" for domain %s\n",
+ m->xid.id, dom->low_id, dom->high_id,
+ dom->name);
+ m->status = ID_UNMAPPED;
+ }
+
+ if (m->status == ID_MAPPED) {
+ ids[i].xid = m->xid;
+ } else {
+ ids[i].xid.id = UINT32_MAX;
+ ids[i].xid.type = ID_TYPE_NOT_SPECIFIED;
+ }
+ }
+
+ goto done;
+nomem:
+ status = NT_STATUS_NO_MEMORY;
+done:
+ TALLOC_FREE(id_map_ptrs);
+ return status;
+}
+
+NTSTATUS _wbint_UnixIDs2Sids(struct pipes_struct *p,
+ struct wbint_UnixIDs2Sids *r)
+{
+ struct id_map **maps;
+ NTSTATUS status;
+ uint32_t i;
+
+ maps = id_map_ptrs_init(talloc_tos(), r->in.num_ids);
+ if (maps == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<r->in.num_ids; i++) {
+ maps[i]->status = ID_UNKNOWN;
+ maps[i]->xid = r->in.xids[i];
+ }
+
+ status = idmap_backend_unixids_to_sids(maps, r->in.domain_name,
+ r->in.domain_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(maps);
+ return status;
+ }
+
+ for (i=0; i<r->in.num_ids; i++) {
+ if (maps[i]->status == ID_MAPPED) {
+ r->out.xids[i] = maps[i]->xid;
+ sid_copy(&r->out.sids[i], maps[i]->sid);
+ } else {
+ r->out.sids[i] = (struct dom_sid) { 0 };
+ }
+ }
+
+ TALLOC_FREE(maps);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_AllocateUid(struct pipes_struct *p, struct wbint_AllocateUid *r)
+{
+ struct unixid xid;
+ NTSTATUS status;
+
+ status = idmap_allocate_uid(&xid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *r->out.uid = xid.id;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_AllocateGid(struct pipes_struct *p, struct wbint_AllocateGid *r)
+{
+ struct unixid xid;
+ NTSTATUS status;
+
+ status = idmap_allocate_gid(&xid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ *r->out.gid = xid.id;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_GetNssInfo(struct pipes_struct *p, struct wbint_GetNssInfo *r)
+{
+ struct idmap_domain *domain;
+ NTSTATUS status;
+
+ domain = idmap_find_domain(r->in.info->domain_name);
+ if ((domain == NULL) || (domain->query_user == NULL)) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = domain->query_user(domain, r->in.info);
+ return status;
+}
+
+NTSTATUS _wbint_LookupUserAliases(struct pipes_struct *p,
+ struct wbint_LookupUserAliases *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = wb_cache_lookup_useraliases(domain, p->mem_ctx,
+ r->in.sids->num_sids,
+ r->in.sids->sids,
+ &r->out.rids->num_rids,
+ &r->out.rids->rids);
+ reset_cm_connection_on_error(domain, NULL, status);
+ return status;
+}
+
+NTSTATUS _wbint_LookupUserGroups(struct pipes_struct *p,
+ struct wbint_LookupUserGroups *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = wb_cache_lookup_usergroups(domain, p->mem_ctx, r->in.sid,
+ &r->out.sids->num_sids,
+ &r->out.sids->sids);
+ reset_cm_connection_on_error(domain, NULL, status);
+ return status;
+}
+
+NTSTATUS _wbint_QuerySequenceNumber(struct pipes_struct *p,
+ struct wbint_QuerySequenceNumber *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = wb_cache_sequence_number(domain, r->out.sequence);
+ reset_cm_connection_on_error(domain, NULL, status);
+ return status;
+}
+
+NTSTATUS _wbint_LookupGroupMembers(struct pipes_struct *p,
+ struct wbint_LookupGroupMembers *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ uint32_t i, num_names;
+ struct dom_sid *sid_mem;
+ char **names;
+ uint32_t *name_types;
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = wb_cache_lookup_groupmem(domain, p->mem_ctx, r->in.sid,
+ r->in.type, &num_names, &sid_mem,
+ &names, &name_types);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->out.members->num_principals = num_names;
+ r->out.members->principals = talloc_array(
+ r->out.members, struct wbint_Principal, num_names);
+ if (r->out.members->principals == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<num_names; i++) {
+ struct wbint_Principal *m = &r->out.members->principals[i];
+ sid_copy(&m->sid, &sid_mem[i]);
+ m->name = talloc_move(r->out.members->principals, &names[i]);
+ m->type = (enum lsa_SidType)name_types[i];
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_LookupAliasMembers(struct pipes_struct *p,
+ struct wbint_LookupAliasMembers *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+ status = wb_cache_lookup_aliasmem(domain,
+ p->mem_ctx,
+ r->in.sid,
+ r->in.type,
+ &r->out.sids->num_sids,
+ &r->out.sids->sids);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_QueryGroupList(struct pipes_struct *p,
+ struct wbint_QueryGroupList *r)
+{
+ TALLOC_CTX *frame = NULL;
+ struct winbindd_domain *domain = wb_child_domain();
+ uint32_t i;
+ uint32_t num_local_groups = 0;
+ struct wb_acct_info *local_groups = NULL;
+ uint32_t num_dom_groups = 0;
+ struct wb_acct_info *dom_groups = NULL;
+ uint32_t ti = 0;
+ uint64_t num_total = 0;
+ struct wbint_Principal *result;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ bool include_local_groups = false;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ frame = talloc_stackframe();
+
+ switch (lp_server_role()) {
+ case ROLE_ACTIVE_DIRECTORY_DC:
+ if (domain->internal) {
+ /*
+ * we want to include local groups
+ * for BUILTIN and WORKGROUP
+ */
+ include_local_groups = true;
+ }
+ break;
+ case ROLE_DOMAIN_MEMBER:
+ /*
+ * This is needed for GETGRENT to show also e.g. BUILTIN/users.
+ * Otherwise the test_membership_user (smbtorture
+ * local.nss.membership) would fail (getgrouplist() would
+ * reports BUILTIN/users).
+ */
+ if (domain->internal) {
+ /*
+ * we want to include local groups
+ * for BUILTIN and LOCALSAM
+ */
+ include_local_groups = true;
+ }
+ break;
+ default:
+ /*
+ * We might include local groups in more
+ * setups later, but that requires more work
+ * elsewhere.
+ */
+ break;
+ }
+
+ if (include_local_groups) {
+ status = wb_cache_enum_local_groups(domain, frame,
+ &num_local_groups,
+ &local_groups);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+ }
+
+ status = wb_cache_enum_dom_groups(domain, frame,
+ &num_dom_groups,
+ &dom_groups);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ num_total = num_local_groups + num_dom_groups;
+ if (num_total > UINT32_MAX) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ result = talloc_array(frame, struct wbint_Principal, num_total);
+ if (result == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ for (i = 0; i < num_local_groups; i++) {
+ struct wb_acct_info *lg = &local_groups[i];
+ struct wbint_Principal *rg = &result[ti++];
+
+ sid_compose(&rg->sid, &domain->sid, lg->rid);
+ rg->type = SID_NAME_ALIAS;
+ rg->name = talloc_strdup(result, lg->acct_name);
+ if (rg->name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ num_local_groups = 0;
+
+ for (i = 0; i < num_dom_groups; i++) {
+ struct wb_acct_info *dg = &dom_groups[i];
+ struct wbint_Principal *rg = &result[ti++];
+
+ sid_compose(&rg->sid, &domain->sid, dg->rid);
+ rg->type = SID_NAME_DOM_GRP;
+ rg->name = talloc_strdup(result, dg->acct_name);
+ if (rg->name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+ num_dom_groups = 0;
+
+ r->out.groups->num_principals = ti;
+ r->out.groups->principals = talloc_move(r->out.groups, &result);
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+NTSTATUS _wbint_QueryUserRidList(struct pipes_struct *p,
+ struct wbint_QueryUserRidList *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS status;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /*
+ * Right now this is overkill. We should add a backend call
+ * just querying the rids.
+ */
+
+ status = wb_cache_query_user_list(domain, p->mem_ctx,
+ &r->out.rids->rids);
+ reset_cm_connection_on_error(domain, NULL, status);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ r->out.rids->num_rids = talloc_array_length(r->out.rids->rids);
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_DsGetDcName(struct pipes_struct *p, struct wbint_DsGetDcName *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ struct rpc_pipe_client *netlogon_pipe;
+ struct netr_DsRGetDCNameInfo *dc_info;
+ NTSTATUS status;
+ WERROR werr;
+ unsigned int orig_timeout;
+ struct dcerpc_binding_handle *b;
+ bool retry = false;
+ bool try_dsrgetdcname = false;
+
+ if (domain == NULL) {
+ return dsgetdcname(p->mem_ctx, global_messaging_context(),
+ r->in.domain_name, r->in.domain_guid,
+ r->in.site_name ? r->in.site_name : "",
+ r->in.flags,
+ r->out.dc_info);
+ }
+
+ if (domain->active_directory) {
+ try_dsrgetdcname = true;
+ }
+
+reconnect:
+ status = cm_connect_netlogon(domain, &netlogon_pipe);
+
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("Can't contact the NETLOGON pipe\n"));
+ return status;
+ }
+
+ b = netlogon_pipe->binding_handle;
+
+ /* This call can take a long time - allow the server to time out.
+ 35 seconds should do it. */
+
+ orig_timeout = rpccli_set_timeout(netlogon_pipe, 35000);
+
+ if (try_dsrgetdcname) {
+ status = dcerpc_netr_DsRGetDCName(b,
+ p->mem_ctx, domain->dcname,
+ r->in.domain_name, NULL, r->in.domain_guid,
+ r->in.flags, r->out.dc_info, &werr);
+ if (NT_STATUS_IS_OK(status) && W_ERROR_IS_OK(werr)) {
+ goto done;
+ }
+ if (!retry &&
+ reset_cm_connection_on_error(domain, NULL, status))
+ {
+ retry = true;
+ goto reconnect;
+ }
+ try_dsrgetdcname = false;
+ retry = false;
+ }
+
+ /*
+ * Fallback to less capable methods
+ */
+
+ dc_info = talloc_zero(r->out.dc_info, struct netr_DsRGetDCNameInfo);
+ if (dc_info == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ if (r->in.flags & DS_PDC_REQUIRED) {
+ status = dcerpc_netr_GetDcName(b,
+ p->mem_ctx, domain->dcname,
+ r->in.domain_name, &dc_info->dc_unc, &werr);
+ } else {
+ status = dcerpc_netr_GetAnyDCName(b,
+ p->mem_ctx, domain->dcname,
+ r->in.domain_name, &dc_info->dc_unc, &werr);
+ }
+
+ if (!retry && reset_cm_connection_on_error(domain, b, status)) {
+ retry = true;
+ goto reconnect;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("dcerpc_netr_Get[Any]DCName failed: %s\n",
+ nt_errstr(status)));
+ goto done;
+ }
+ if (!W_ERROR_IS_OK(werr)) {
+ DEBUG(10, ("dcerpc_netr_Get[Any]DCName failed: %s\n",
+ win_errstr(werr)));
+ status = werror_to_ntstatus(werr);
+ goto done;
+ }
+
+ *r->out.dc_info = dc_info;
+ status = NT_STATUS_OK;
+
+done:
+ /* And restore our original timeout. */
+ rpccli_set_timeout(netlogon_pipe, orig_timeout);
+
+ return status;
+}
+
+NTSTATUS _wbint_LookupRids(struct pipes_struct *p, struct wbint_LookupRids *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ char *domain_name;
+ char **names;
+ enum lsa_SidType *types;
+ struct wbint_Principal *result;
+ NTSTATUS status;
+ uint32_t i;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = wb_cache_rids_to_names(domain, talloc_tos(), r->in.domain_sid,
+ r->in.rids->rids, r->in.rids->num_rids,
+ &domain_name, &names, &types);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) {
+ return status;
+ }
+
+ *r->out.domain_name = talloc_move(r->out.domain_name, &domain_name);
+
+ result = talloc_array(p->mem_ctx, struct wbint_Principal,
+ r->in.rids->num_rids);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<r->in.rids->num_rids; i++) {
+ sid_compose(&result[i].sid, r->in.domain_sid,
+ r->in.rids->rids[i]);
+ result[i].type = types[i];
+ result[i].name = talloc_move(result, &names[i]);
+ }
+ TALLOC_FREE(types);
+ TALLOC_FREE(names);
+
+ r->out.names->num_principals = r->in.rids->num_rids;
+ r->out.names->principals = result;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_CheckMachineAccount(struct pipes_struct *p,
+ struct wbint_CheckMachineAccount *r)
+{
+ struct winbindd_domain *domain;
+ int num_retries = 0;
+ NTSTATUS status;
+
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+again:
+ invalidate_cm_connection(domain);
+ domain->conn.netlogon_force_reauth = true;
+
+ {
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ }
+
+ /* There is a race condition between fetching the trust account
+ password and the periodic machine password change. So it's
+ possible that the trust account password has been changed on us.
+ We are returned NT_STATUS_ACCESS_DENIED if this happens. */
+
+#define MAX_RETRIES 3
+
+ if ((num_retries < MAX_RETRIES)
+ && NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED)) {
+ num_retries++;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
+ goto done;
+ }
+
+ /* Pass back result code - zero for success, other values for
+ specific failures. */
+
+ DEBUG(3,("domain %s secret is %s\n", domain->name,
+ NT_STATUS_IS_OK(status) ? "good" : "bad"));
+
+ done:
+ DEBUG(NT_STATUS_IS_OK(status) ? 5 : 2,
+ ("Checking the trust account password for domain %s returned %s\n",
+ domain->name, nt_errstr(status)));
+
+ return status;
+}
+
+NTSTATUS _wbint_ChangeMachineAccount(struct pipes_struct *p,
+ struct wbint_ChangeMachineAccount *r)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ if (r->in.dcname != NULL && r->in.dcname[0] != '\0') {
+ invalidate_cm_connection(domain);
+ TALLOC_FREE(domain->dcname);
+
+ domain->dcname = talloc_strdup(domain, r->in.dcname);
+ if (domain->dcname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ domain->force_dc = true;
+
+ DBG_NOTICE("attempt connection to change trust account "
+ "password for %s at %s\n",
+ domain->name, domain->dcname);
+ }
+
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
+ goto done;
+ }
+
+ status = trust_pw_change(netlogon_creds_ctx,
+ msg_ctx,
+ netlogon_pipe->binding_handle,
+ domain->name,
+ domain->dcname,
+ true); /* force */
+
+ /* Pass back result code - zero for success, other values for
+ specific failures. */
+
+ DEBUG(3,("domain %s secret %s\n", domain->name,
+ NT_STATUS_IS_OK(status) ? "changed" : "unchanged"));
+
+ done:
+ DEBUG(NT_STATUS_IS_OK(status) ? 5 :
+ domain->force_dc ? 0 : 2,
+ ("Changing the trust account password for domain %s at %s "
+ "(forced: %s) returned %s\n",
+ domain->name, domain->dcname, domain->force_dc ? "yes" : "no",
+ nt_errstr(status)));
+ domain->force_dc = false;
+
+ return status;
+}
+
+NTSTATUS _wbint_PingDc(struct pipes_struct *p, struct wbint_PingDc *r)
+{
+ NTSTATUS status;
+ struct winbindd_domain *domain;
+ struct rpc_pipe_client *netlogon_pipe;
+ union netr_CONTROL_QUERY_INFORMATION info;
+ WERROR werr;
+ fstring logon_server;
+ struct dcerpc_binding_handle *b;
+ bool retry = false;
+
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+reconnect:
+ status = cm_connect_netlogon(domain, &netlogon_pipe);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ b = netlogon_pipe->binding_handle;
+
+ fstr_sprintf(logon_server, "\\\\%s", domain->dcname);
+ *r->out.dcname = talloc_strdup(p->mem_ctx, domain->dcname);
+ if (*r->out.dcname == NULL) {
+ DEBUG(2, ("Could not allocate memory\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * This provokes a WERR_NOT_SUPPORTED error message. This is
+ * documented in the wspp docs. I could not get a successful
+ * call to work, but the main point here is testing that the
+ * netlogon pipe works.
+ */
+ status = dcerpc_netr_LogonControl(b, p->mem_ctx,
+ logon_server, NETLOGON_CONTROL_QUERY,
+ 2, &info, &werr);
+
+ if (!retry && reset_cm_connection_on_error(domain, b, status)) {
+ retry = true;
+ goto reconnect;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("dcerpc_netr_LogonControl failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ if (!W_ERROR_EQUAL(werr, WERR_NOT_SUPPORTED)) {
+ DEBUG(2, ("dcerpc_netr_LogonControl returned %s, expected "
+ "WERR_NOT_SUPPORTED\n",
+ win_errstr(werr)));
+ return werror_to_ntstatus(werr);
+ }
+
+ DEBUG(5, ("winbindd_dual_ping_dc succeeded\n"));
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _winbind_DsrUpdateReadOnlyServerDnsRecords(struct pipes_struct *p,
+ struct winbind_DsrUpdateReadOnlyServerDnsRecords *r)
+{
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ bool retry = false;
+
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+reconnect:
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
+ goto done;
+ }
+
+ b = netlogon_pipe->binding_handle;
+
+ status = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords(netlogon_creds_ctx,
+ netlogon_pipe->binding_handle,
+ r->in.site_name,
+ r->in.dns_ttl,
+ r->in.dns_names);
+
+ if (!retry && reset_cm_connection_on_error(domain, b, status)) {
+ retry = true;
+ goto reconnect;
+ }
+
+ /* Pass back result code - zero for success, other values for
+ specific failures. */
+
+ DEBUG(3,("DNS records for domain %s %s\n", domain->name,
+ NT_STATUS_IS_OK(status) ? "changed" : "unchanged"));
+
+ done:
+ DEBUG(NT_STATUS_IS_OK(status) ? 5 : 2,
+ ("Update of DNS records via RW DC %s returned %s\n",
+ domain->name, nt_errstr(status)));
+
+ return status;
+}
+
+NTSTATUS _winbind_SamLogon(struct pipes_struct *p,
+ struct winbind_SamLogon *r)
+{
+ struct dcesrv_call_state *dce_call = p->dce_call;
+ struct dcesrv_connection *dcesrv_conn = dce_call->conn;
+ const struct tsocket_address *local_address =
+ dcesrv_connection_get_local_address(dcesrv_conn);
+ const struct tsocket_address *remote_address =
+ dcesrv_connection_get_remote_address(dcesrv_conn);
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ struct netr_IdentityInfo *identity_info = NULL;
+ DATA_BLOB lm_response, nt_response;
+ DATA_BLOB challenge = data_blob_null;
+ uint32_t flags = 0;
+ uint16_t validation_level;
+ union netr_Validation *validation = NULL;
+ bool interactive = false;
+
+ /*
+ * Make sure we start with authoritative=true,
+ * it will only set to false if we don't know the
+ * domain.
+ */
+ r->out.authoritative = true;
+
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ switch (r->in.validation_level) {
+ case 3:
+ case 6:
+ break;
+ default:
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ switch (r->in.logon_level) {
+ case NetlogonInteractiveInformation:
+ case NetlogonServiceInformation:
+ case NetlogonInteractiveTransitiveInformation:
+ case NetlogonServiceTransitiveInformation:
+ if (r->in.logon.password == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ interactive = true;
+ identity_info = &r->in.logon.password->identity_info;
+
+ challenge = data_blob_null;
+ lm_response = data_blob_talloc(p->mem_ctx,
+ r->in.logon.password->lmpassword.hash,
+ sizeof(r->in.logon.password->lmpassword.hash));
+ nt_response = data_blob_talloc(p->mem_ctx,
+ r->in.logon.password->ntpassword.hash,
+ sizeof(r->in.logon.password->ntpassword.hash));
+ break;
+
+ case NetlogonNetworkInformation:
+ case NetlogonNetworkTransitiveInformation:
+ if (r->in.logon.network == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ interactive = false;
+ identity_info = &r->in.logon.network->identity_info;
+
+ challenge = data_blob_talloc(p->mem_ctx,
+ r->in.logon.network->challenge,
+ 8);
+ lm_response = data_blob_talloc(p->mem_ctx,
+ r->in.logon.network->lm.data,
+ r->in.logon.network->lm.length);
+ nt_response = data_blob_talloc(p->mem_ctx,
+ r->in.logon.network->nt.data,
+ r->in.logon.network->nt.length);
+ break;
+
+ case NetlogonGenericInformation:
+ if (r->in.logon.generic == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ identity_info = &r->in.logon.generic->identity_info;
+ /*
+ * Not implemented here...
+ */
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+
+ default:
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ status = winbind_dual_SamLogon(domain, p->mem_ctx,
+ interactive,
+ identity_info->parameter_control,
+ identity_info->account_name.string,
+ identity_info->domain_name.string,
+ identity_info->workstation.string,
+ identity_info->logon_id,
+ "SamLogon",
+ 0,
+ challenge,
+ lm_response, nt_response,
+ remote_address,
+ local_address,
+ &r->out.authoritative,
+ true, /* skip_sam */
+ &flags,
+ &validation_level,
+ &validation);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ switch (r->in.validation_level) {
+ case 3:
+ status = map_validation_to_info3(p->mem_ctx,
+ validation_level,
+ validation,
+ &r->out.validation.sam3);
+ TALLOC_FREE(validation);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+ case 6:
+ status = map_validation_to_info6(p->mem_ctx,
+ validation_level,
+ validation,
+ &r->out.validation.sam6);
+ TALLOC_FREE(validation);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ return NT_STATUS_OK;
+ }
+
+ smb_panic(__location__);
+ return NT_STATUS_INTERNAL_ERROR;
+}
+
+static WERROR _winbind_LogonControl_REDISCOVER(struct pipes_struct *p,
+ struct winbindd_domain *domain,
+ struct winbind_LogonControl *r)
+{
+ NTSTATUS status;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ struct netr_NETLOGON_INFO_2 *info2 = NULL;
+ WERROR check_result = WERR_INTERNAL_ERROR;
+
+ info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2);
+ if (info2 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (domain->internal) {
+ check_result = WERR_OK;
+ goto check_return;
+ }
+
+ /*
+ * For now we just force a reconnect
+ *
+ * TODO: take care of the optional '\dcname'
+ */
+ invalidate_cm_connection(domain);
+ domain->conn.netlogon_force_reauth = true;
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("%s: domain[%s/%s] cm_connect_netlogon() returned %s\n",
+ __func__, domain->name, domain->alt_name,
+ nt_errstr(status)));
+ /*
+ * Here we return a top level error!
+ * This is different than TC_QUERY or TC_VERIFY.
+ */
+ return ntstatus_to_werror(status);
+ }
+ check_result = WERR_OK;
+
+check_return:
+ info2->pdc_connection_status = WERR_OK;
+ if (domain->dcname != NULL) {
+ info2->flags |= NETLOGON_HAS_IP;
+ info2->flags |= NETLOGON_HAS_TIMESERV;
+ info2->trusted_dc_name = talloc_asprintf(info2, "\\\\%s",
+ domain->dcname);
+ if (info2->trusted_dc_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ info2->trusted_dc_name = talloc_strdup(info2, "");
+ if (info2->trusted_dc_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+ info2->tc_connection_status = check_result;
+
+ if (!W_ERROR_IS_OK(info2->pdc_connection_status) ||
+ !W_ERROR_IS_OK(info2->tc_connection_status))
+ {
+ DEBUG(2, ("%s: domain[%s/%s] dcname[%s] "
+ "pdc_connection[%s] tc_connection[%s]\n",
+ __func__, domain->name, domain->alt_name,
+ domain->dcname,
+ win_errstr(info2->pdc_connection_status),
+ win_errstr(info2->tc_connection_status)));
+ }
+
+ r->out.query->info2 = info2;
+
+ DEBUG(5, ("%s: succeeded.\n", __func__));
+ return WERR_OK;
+}
+
+static WERROR _winbind_LogonControl_TC_QUERY(struct pipes_struct *p,
+ struct winbindd_domain *domain,
+ struct winbind_LogonControl *r)
+{
+ NTSTATUS status;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ struct netr_NETLOGON_INFO_2 *info2 = NULL;
+ WERROR check_result = WERR_INTERNAL_ERROR;
+
+ info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2);
+ if (info2 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (domain->internal) {
+ check_result = WERR_OK;
+ goto check_return;
+ }
+
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n",
+ nt_errstr(status)));
+ check_result = ntstatus_to_werror(status);
+ goto check_return;
+ }
+ check_result = WERR_OK;
+
+check_return:
+ info2->pdc_connection_status = WERR_OK;
+ if (domain->dcname != NULL) {
+ info2->flags |= NETLOGON_HAS_IP;
+ info2->flags |= NETLOGON_HAS_TIMESERV;
+ info2->trusted_dc_name = talloc_asprintf(info2, "\\\\%s",
+ domain->dcname);
+ if (info2->trusted_dc_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ info2->trusted_dc_name = talloc_strdup(info2, "");
+ if (info2->trusted_dc_name == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+ info2->tc_connection_status = check_result;
+
+ if (!W_ERROR_IS_OK(info2->pdc_connection_status) ||
+ !W_ERROR_IS_OK(info2->tc_connection_status))
+ {
+ DEBUG(2, ("%s: domain[%s/%s] dcname[%s] "
+ "pdc_connection[%s] tc_connection[%s]\n",
+ __func__, domain->name, domain->alt_name,
+ domain->dcname,
+ win_errstr(info2->pdc_connection_status),
+ win_errstr(info2->tc_connection_status)));
+ }
+
+ r->out.query->info2 = info2;
+
+ DEBUG(5, ("%s: succeeded.\n", __func__));
+ return WERR_OK;
+}
+
+static WERROR _winbind_LogonControl_TC_VERIFY(struct pipes_struct *p,
+ struct winbindd_domain *domain,
+ struct winbind_LogonControl *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+ NTSTATUS result;
+ struct lsa_String trusted_domain_name = {};
+ struct lsa_StringLarge trusted_domain_name_l = {};
+ struct rpc_pipe_client *local_lsa_pipe = NULL;
+ struct policy_handle local_lsa_policy = {};
+ struct dcerpc_binding_handle *local_lsa = NULL;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ struct cli_credentials *creds = NULL;
+ struct samr_Password *cur_nt_hash = NULL;
+ uint32_t trust_attributes = 0;
+ struct samr_Password new_owf_password = {};
+ bool cmp_new = false;
+ struct samr_Password old_owf_password = {};
+ bool cmp_old = false;
+ const struct lsa_TrustDomainInfoInfoEx *local_tdo = NULL;
+ bool fetch_fti = false;
+ struct lsa_ForestTrustInformation *new_fti = NULL;
+ struct netr_TrustInfo *trust_info = NULL;
+ struct netr_NETLOGON_INFO_2 *info2 = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ WERROR check_result = WERR_INTERNAL_ERROR;
+ WERROR verify_result = WERR_INTERNAL_ERROR;
+ bool retry = false;
+
+ trusted_domain_name.string = domain->name;
+ trusted_domain_name_l.string = domain->name;
+
+ info2 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_2);
+ if (info2 == NULL) {
+ TALLOC_FREE(frame);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (domain->internal) {
+ check_result = WERR_OK;
+ goto check_return;
+ }
+
+ status = pdb_get_trust_credentials(domain->name,
+ domain->alt_name,
+ frame,
+ &creds);
+ if (NT_STATUS_IS_OK(status)) {
+ cur_nt_hash = cli_credentials_get_nt_hash(creds, frame);
+ TALLOC_FREE(creds);
+ }
+
+ if (!domain->primary) {
+ union lsa_TrustedDomainInfo *tdi = NULL;
+
+ status = open_internal_lsa_conn(frame, &local_lsa_pipe,
+ &local_lsa_policy);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: open_internal_lsa_conn() failed - %s\n",
+ __location__, __func__, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ local_lsa = local_lsa_pipe->binding_handle;
+
+ status = dcerpc_lsa_QueryTrustedDomainInfoByName(local_lsa, frame,
+ &local_lsa_policy,
+ &trusted_domain_name,
+ LSA_TRUSTED_DOMAIN_INFO_INFO_EX,
+ &tdi, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) failed - %s\n",
+ __location__, __func__, domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DEBUG(1,("%s:%s: domain[%s] not found via LSA, might be removed already.\n",
+ __location__, __func__, domain->name));
+ TALLOC_FREE(frame);
+ return WERR_NO_SUCH_DOMAIN;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) returned %s\n",
+ __location__, __func__, domain->name, nt_errstr(result)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (tdi == NULL) {
+ DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName() "
+ "returned no trusted domain information\n",
+ __location__, __func__));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ local_tdo = &tdi->info_ex;
+ trust_attributes = local_tdo->trust_attributes;
+ }
+
+ if (trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
+ struct lsa_ForestTrustInformation *old_fti = NULL;
+
+ status = dcerpc_lsa_lsaRQueryForestTrustInformation(local_lsa, frame,
+ &local_lsa_policy,
+ &trusted_domain_name,
+ LSA_FOREST_TRUST_DOMAIN_INFO,
+ &old_fti, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) failed %s\n",
+ __location__, __func__, domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_FOUND)) {
+ DEBUG(2,("%s: no forest trust information available for domain[%s] yet.\n",
+ __func__, domain->name));
+ old_fti = NULL;
+ fetch_fti = true;
+ result = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) returned %s\n",
+ __location__, __func__, domain->name, nt_errstr(result)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ TALLOC_FREE(old_fti);
+ }
+
+reconnect:
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n",
+ nt_errstr(status)));
+ check_result = ntstatus_to_werror(status);
+ goto check_return;
+ }
+ check_result = WERR_OK;
+ b = netlogon_pipe->binding_handle;
+
+ if (cur_nt_hash == NULL) {
+ verify_result = WERR_NO_TRUST_LSA_SECRET;
+ goto verify_return;
+ }
+
+ if (fetch_fti) {
+ status = netlogon_creds_cli_GetForestTrustInformation(netlogon_creds_ctx,
+ b, frame,
+ &new_fti);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ new_fti = NULL;
+ status = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!retry &&
+ reset_cm_connection_on_error(domain, b, status))
+ {
+ retry = true;
+ goto reconnect;
+ }
+ DEBUG(2, ("netlogon_creds_cli_GetForestTrustInformation(%s)"
+ "failed: %s\n",
+ domain->name, nt_errstr(status)));
+ check_result = ntstatus_to_werror(status);
+ goto check_return;
+ }
+ }
+
+ if (new_fti != NULL) {
+ struct lsa_ForestTrustInformation old_fti = {};
+ struct lsa_ForestTrustInformation *merged_fti = NULL;
+ struct lsa_ForestTrustCollisionInfo *collision_info = NULL;
+
+ status = dsdb_trust_merge_forest_info(frame, local_tdo,
+ &old_fti, new_fti,
+ &merged_fti);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: dsdb_trust_merge_forest_info(%s) failed %s\n",
+ __location__, __func__,
+ domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return ntstatus_to_werror(status);
+ }
+
+ status = dcerpc_lsa_lsaRSetForestTrustInformation(local_lsa, frame,
+ &local_lsa_policy,
+ &trusted_domain_name_l,
+ LSA_FOREST_TRUST_DOMAIN_INFO,
+ merged_fti,
+ 0, /* check_only=0 => store it! */
+ &collision_info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) failed %s\n",
+ __location__, __func__, domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) returned %s\n",
+ __location__, __func__, domain->name, nt_errstr(result)));
+ TALLOC_FREE(frame);
+ return ntstatus_to_werror(result);
+ }
+ }
+
+ status = netlogon_creds_cli_ServerGetTrustInfo(netlogon_creds_ctx,
+ b, frame,
+ &new_owf_password,
+ &old_owf_password,
+ &trust_info);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+ status = NT_STATUS_NOT_SUPPORTED;
+ }
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED)) {
+ DEBUG(5, ("netlogon_creds_cli_ServerGetTrustInfo failed: %s\n",
+ nt_errstr(status)));
+ verify_result = WERR_OK;
+ goto verify_return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!retry && reset_cm_connection_on_error(domain, b, status)) {
+ retry = true;
+ goto reconnect;
+ }
+ DEBUG(2, ("netlogon_creds_cli_ServerGetTrustInfo failed: %s\n",
+ nt_errstr(status)));
+
+ if (!dcerpc_binding_handle_is_connected(b)) {
+ check_result = ntstatus_to_werror(status);
+ goto check_return;
+ } else {
+ verify_result = ntstatus_to_werror(status);
+ goto verify_return;
+ }
+ }
+
+ if (trust_info != NULL && trust_info->count >= 1) {
+ uint32_t diff = trust_info->data[0] ^ trust_attributes;
+
+ if (diff & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
+ verify_result = WERR_DOMAIN_TRUST_INCONSISTENT;
+ goto verify_return;
+ }
+ }
+
+ cmp_new = mem_equal_const_time(new_owf_password.hash,
+ cur_nt_hash->hash,
+ sizeof(cur_nt_hash->hash));
+ cmp_old = mem_equal_const_time(old_owf_password.hash,
+ cur_nt_hash->hash,
+ sizeof(cur_nt_hash->hash));
+ if (!cmp_new && !cmp_old) {
+ DEBUG(1,("%s:Error: credentials for domain[%s/%s] doesn't match "
+ "any password known to dcname[%s]\n",
+ __func__, domain->name, domain->alt_name,
+ domain->dcname));
+ verify_result = WERR_WRONG_PASSWORD;
+ goto verify_return;
+ }
+
+ if (!cmp_new) {
+ DEBUG(2,("%s:Warning: credentials for domain[%s/%s] only match "
+ "against the old password known to dcname[%s]\n",
+ __func__, domain->name, domain->alt_name,
+ domain->dcname));
+ }
+
+ verify_result = WERR_OK;
+ goto verify_return;
+
+check_return:
+ verify_result = check_result;
+verify_return:
+ info2->flags |= NETLOGON_VERIFY_STATUS_RETURNED;
+ info2->pdc_connection_status = verify_result;
+ if (domain->dcname != NULL) {
+ info2->flags |= NETLOGON_HAS_IP;
+ info2->flags |= NETLOGON_HAS_TIMESERV;
+ info2->trusted_dc_name = talloc_asprintf(info2, "\\\\%s",
+ domain->dcname);
+ if (info2->trusted_dc_name == NULL) {
+ TALLOC_FREE(frame);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ } else {
+ info2->trusted_dc_name = talloc_strdup(info2, "");
+ if (info2->trusted_dc_name == NULL) {
+ TALLOC_FREE(frame);
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+ }
+ info2->tc_connection_status = check_result;
+
+ if (!W_ERROR_IS_OK(info2->pdc_connection_status) ||
+ !W_ERROR_IS_OK(info2->tc_connection_status))
+ {
+ DEBUG(2, ("%s: domain[%s/%s] dcname[%s] "
+ "pdc_connection[%s] tc_connection[%s]\n",
+ __func__, domain->name, domain->alt_name,
+ domain->dcname,
+ win_errstr(info2->pdc_connection_status),
+ win_errstr(info2->tc_connection_status)));
+ }
+
+ r->out.query->info2 = info2;
+
+ DEBUG(5, ("%s: succeeded.\n", __func__));
+ TALLOC_FREE(frame);
+ return WERR_OK;
+}
+
+static WERROR _winbind_LogonControl_CHANGE_PASSWORD(struct pipes_struct *p,
+ struct winbindd_domain *domain,
+ struct winbind_LogonControl *r)
+{
+ struct messaging_context *msg_ctx = global_messaging_context();
+ NTSTATUS status;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ struct cli_credentials *creds = NULL;
+ struct samr_Password *cur_nt_hash = NULL;
+ struct netr_NETLOGON_INFO_1 *info1 = NULL;
+ struct dcerpc_binding_handle *b;
+ WERROR change_result = WERR_OK;
+ bool retry = false;
+
+ info1 = talloc_zero(p->mem_ctx, struct netr_NETLOGON_INFO_1);
+ if (info1 == NULL) {
+ return WERR_NOT_ENOUGH_MEMORY;
+ }
+
+ if (domain->internal) {
+ return WERR_NOT_SUPPORTED;
+ }
+
+ status = pdb_get_trust_credentials(domain->name,
+ domain->alt_name,
+ p->mem_ctx,
+ &creds);
+ if (NT_STATUS_IS_OK(status)) {
+ cur_nt_hash = cli_credentials_get_nt_hash(creds, p->mem_ctx);
+ TALLOC_FREE(creds);
+ }
+
+reconnect:
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(2, ("%s: domain[%s/%s] cm_connect_netlogon() returned %s\n",
+ __func__, domain->name, domain->alt_name,
+ nt_errstr(status)));
+ /*
+ * Here we return a top level error!
+ * This is different than TC_QUERY or TC_VERIFY.
+ */
+ return ntstatus_to_werror(status);
+ }
+ b = netlogon_pipe->binding_handle;
+
+ if (cur_nt_hash == NULL) {
+ change_result = WERR_NO_TRUST_LSA_SECRET;
+ goto change_return;
+ }
+ TALLOC_FREE(cur_nt_hash);
+
+ status = trust_pw_change(netlogon_creds_ctx,
+ msg_ctx, b, domain->name,
+ domain->dcname,
+ true); /* force */
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!retry && reset_cm_connection_on_error(domain, b, status)) {
+ retry = true;
+ goto reconnect;
+ }
+
+ DEBUG(1, ("trust_pw_change(%s): %s\n",
+ domain->name, nt_errstr(status)));
+
+ change_result = ntstatus_to_werror(status);
+ goto change_return;
+ }
+
+ change_result = WERR_OK;
+
+change_return:
+ info1->pdc_connection_status = change_result;
+
+ if (!W_ERROR_IS_OK(info1->pdc_connection_status)) {
+ DEBUG(2, ("%s: domain[%s/%s] dcname[%s] "
+ "pdc_connection[%s]\n",
+ __func__, domain->name, domain->alt_name,
+ domain->dcname,
+ win_errstr(info1->pdc_connection_status)));
+ }
+
+ r->out.query->info1 = info1;
+
+ DEBUG(5, ("%s: succeeded.\n", __func__));
+ return WERR_OK;
+}
+
+WERROR _winbind_LogonControl(struct pipes_struct *p,
+ struct winbind_LogonControl *r)
+{
+ struct winbindd_domain *domain;
+
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ return WERR_NO_SUCH_DOMAIN;
+ }
+
+ switch (r->in.function_code) {
+ case NETLOGON_CONTROL_REDISCOVER:
+ if (r->in.level != 2) {
+ return WERR_INVALID_PARAMETER;
+ }
+ return _winbind_LogonControl_REDISCOVER(p, domain, r);
+ case NETLOGON_CONTROL_TC_QUERY:
+ if (r->in.level != 2) {
+ return WERR_INVALID_PARAMETER;
+ }
+ return _winbind_LogonControl_TC_QUERY(p, domain, r);
+ case NETLOGON_CONTROL_TC_VERIFY:
+ if (r->in.level != 2) {
+ return WERR_INVALID_PARAMETER;
+ }
+ return _winbind_LogonControl_TC_VERIFY(p, domain, r);
+ case NETLOGON_CONTROL_CHANGE_PASSWORD:
+ if (r->in.level != 1) {
+ return WERR_INVALID_PARAMETER;
+ }
+ return _winbind_LogonControl_CHANGE_PASSWORD(p, domain, r);
+ default:
+ break;
+ }
+
+ DEBUG(4, ("%s: function_code[0x%x] not supported\n",
+ __func__, r->in.function_code));
+ return WERR_NOT_SUPPORTED;
+}
+
+WERROR _winbind_GetForestTrustInformation(struct pipes_struct *p,
+ struct winbind_GetForestTrustInformation *r)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status, result;
+ struct winbindd_domain *domain;
+ struct rpc_pipe_client *netlogon_pipe = NULL;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ struct dcerpc_binding_handle *b;
+ bool retry = false;
+ struct lsa_String trusted_domain_name = {};
+ struct lsa_StringLarge trusted_domain_name_l = {};
+ union lsa_TrustedDomainInfo *tdi = NULL;
+ const struct lsa_TrustDomainInfoInfoEx *tdo = NULL;
+ struct lsa_ForestTrustInformation _old_fti = {};
+ struct lsa_ForestTrustInformation *old_fti = NULL;
+ struct lsa_ForestTrustInformation *new_fti = NULL;
+ struct lsa_ForestTrustInformation *merged_fti = NULL;
+ struct lsa_ForestTrustCollisionInfo *collision_info = NULL;
+ bool update_fti = false;
+ struct rpc_pipe_client *local_lsa_pipe;
+ struct policy_handle local_lsa_policy;
+ struct dcerpc_binding_handle *local_lsa = NULL;
+
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ TALLOC_FREE(frame);
+ return WERR_NO_SUCH_DOMAIN;
+ }
+
+ /*
+ * checking for domain->internal and domain->primary
+ * makes sure we only do some work when running as DC.
+ */
+
+ if (domain->internal) {
+ TALLOC_FREE(frame);
+ return WERR_NO_SUCH_DOMAIN;
+ }
+
+ if (domain->primary) {
+ TALLOC_FREE(frame);
+ return WERR_NO_SUCH_DOMAIN;
+ }
+
+ trusted_domain_name.string = domain->name;
+ trusted_domain_name_l.string = domain->name;
+
+ status = open_internal_lsa_conn(frame, &local_lsa_pipe,
+ &local_lsa_policy);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: open_internal_lsa_conn() failed - %s\n",
+ __location__, __func__, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ local_lsa = local_lsa_pipe->binding_handle;
+
+ status = dcerpc_lsa_QueryTrustedDomainInfoByName(local_lsa, frame,
+ &local_lsa_policy,
+ &trusted_domain_name,
+ LSA_TRUSTED_DOMAIN_INFO_INFO_EX,
+ &tdi, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) failed - %s\n",
+ __location__, __func__, domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ DEBUG(1,("%s:%s: domain[%s] not found via LSA, might be removed already.\n",
+ __location__, __func__, domain->name));
+ TALLOC_FREE(frame);
+ return WERR_NO_SUCH_DOMAIN;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName(%s) returned %s\n",
+ __location__, __func__, domain->name, nt_errstr(result)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (tdi == NULL) {
+ DEBUG(0,("%s:%s: local_lsa.QueryTrustedDomainInfoByName() "
+ "returned no trusted domain information\n",
+ __location__, __func__));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ tdo = &tdi->info_ex;
+
+ if (!(tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) {
+ DEBUG(2,("%s: tdo[%s/%s] is no forest trust attributes[0x%08X]\n",
+ __func__, tdo->netbios_name.string,
+ tdo->domain_name.string,
+ (unsigned)tdo->trust_attributes));
+ TALLOC_FREE(frame);
+ return WERR_NO_SUCH_DOMAIN;
+ }
+
+ if (r->in.flags & ~DS_GFTI_UPDATE_TDO) {
+ TALLOC_FREE(frame);
+ return WERR_INVALID_FLAGS;
+ }
+
+reconnect:
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ status = NT_STATUS_NO_LOGON_SERVERS;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe: %s\n",
+ nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return ntstatus_to_werror(status);
+ }
+ b = netlogon_pipe->binding_handle;
+
+ status = netlogon_creds_cli_GetForestTrustInformation(netlogon_creds_ctx,
+ b, p->mem_ctx,
+ &new_fti);
+ if (!NT_STATUS_IS_OK(status)) {
+ if (!retry && reset_cm_connection_on_error(domain, b, status)) {
+ retry = true;
+ goto reconnect;
+ }
+ DEBUG(2, ("netlogon_creds_cli_GetForestTrustInformation(%s) failed: %s\n",
+ domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return ntstatus_to_werror(status);
+ }
+
+ *r->out.forest_trust_info = new_fti;
+
+ if (r->in.flags & DS_GFTI_UPDATE_TDO) {
+ update_fti = true;
+ }
+
+ status = dcerpc_lsa_lsaRQueryForestTrustInformation(local_lsa, frame,
+ &local_lsa_policy,
+ &trusted_domain_name,
+ LSA_FOREST_TRUST_DOMAIN_INFO,
+ &old_fti, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) failed %s\n",
+ __location__, __func__, domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_FOUND)) {
+ DEBUG(2,("%s: no forest trust information available for domain[%s] yet.\n",
+ __func__, domain->name));
+ update_fti = true;
+ old_fti = &_old_fti;
+ result = NT_STATUS_OK;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation(%s) returned %s\n",
+ __location__, __func__, domain->name, nt_errstr(result)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ if (old_fti == NULL) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRQueryForestTrustInformation() "
+ "returned success without returning forest trust information\n",
+ __location__, __func__));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+
+ if (!update_fti) {
+ goto done;
+ }
+
+ status = dsdb_trust_merge_forest_info(frame, tdo, old_fti, new_fti,
+ &merged_fti);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: dsdb_trust_merge_forest_info(%s) failed %s\n",
+ __location__, __func__, domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return ntstatus_to_werror(status);
+ }
+
+ status = dcerpc_lsa_lsaRSetForestTrustInformation(local_lsa, frame,
+ &local_lsa_policy,
+ &trusted_domain_name_l,
+ LSA_FOREST_TRUST_DOMAIN_INFO,
+ merged_fti,
+ 0, /* check_only=0 => store it! */
+ &collision_info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) failed %s\n",
+ __location__, __func__, domain->name, nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return WERR_INTERNAL_ERROR;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("%s:%s: local_lsa.lsaRSetForestTrustInformation(%s) returned %s\n",
+ __location__, __func__, domain->name, nt_errstr(result)));
+ TALLOC_FREE(frame);
+ return ntstatus_to_werror(result);
+ }
+
+done:
+ DEBUG(5, ("_winbind_GetForestTrustInformation succeeded\n"));
+ TALLOC_FREE(frame);
+ return WERR_OK;
+}
+
+NTSTATUS _winbind_SendToSam(struct pipes_struct *p, struct winbind_SendToSam *r)
+{
+ struct winbindd_domain *domain;
+ NTSTATUS status;
+ struct rpc_pipe_client *netlogon_pipe;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ bool retry = false;
+
+ DEBUG(5, ("_winbind_SendToSam received\n"));
+ domain = wb_child_domain();
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+reconnect:
+ status = cm_connect_netlogon_secure(domain,
+ &netlogon_pipe,
+ &netlogon_creds_ctx);
+ reset_cm_connection_on_error(domain, NULL, status);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(3, ("could not open handle to NETLOGON pipe\n"));
+ return status;
+ }
+
+ b = netlogon_pipe->binding_handle;
+
+ status = netlogon_creds_cli_SendToSam(netlogon_creds_ctx,
+ b,
+ &r->in.message);
+ if (!retry && reset_cm_connection_on_error(domain, b, status)) {
+ retry = true;
+ goto reconnect;
+ }
+
+ return status;
+}
+
+NTSTATUS _wbint_ListTrustedDomains(struct pipes_struct *p,
+ struct wbint_ListTrustedDomains *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ uint32_t i;
+ NTSTATUS result;
+ struct netr_DomainTrustList trusts;
+ uint32_t count = 0;
+ struct netr_DomainTrust *array = NULL;
+ pid_t client_pid;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /* Cut client_pid to 32bit */
+ client_pid = r->in.client_pid;
+ if ((uint64_t)client_pid != r->in.client_pid) {
+ DBG_DEBUG("pid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DBG_NOTICE("[%s %"PRIu32"]: list trusted domains\n",
+ r->in.client_name, client_pid);
+
+ result = wb_cache_trusted_domains(domain, p->mem_ctx, &trusts);
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_NOTICE("wb_cache_trusted_domains returned %s\n",
+ nt_errstr(result));
+ return result;
+ }
+
+ for (i=0; i<trusts.count; i++) {
+ struct netr_DomainTrust *st = &trusts.array[i];
+ struct netr_DomainTrust *dt = NULL;
+
+ if (st->sid == NULL) {
+ continue;
+ }
+ if (dom_sid_equal(st->sid, &global_sid_NULL)) {
+ continue;
+ }
+
+ array = talloc_realloc(r->out.domains, array,
+ struct netr_DomainTrust,
+ count + 1);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ dt = &array[count];
+
+ *dt = (struct netr_DomainTrust) {
+ .trust_flags = st->trust_flags,
+ .trust_type = st->trust_type,
+ .trust_attributes = st->trust_attributes,
+ .netbios_name = talloc_move(array, &st->netbios_name),
+ .dns_name = talloc_move(array, &st->dns_name),
+ };
+
+ dt->sid = dom_sid_dup(array, st->sid);
+ if (dt->sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ count++;
+ }
+
+ r->out.domains->array = array;
+ r->out.domains->count = count;
+ return NT_STATUS_OK;
+}
+
+#include "librpc/gen_ndr/ndr_winbind_scompat.c"
diff --git a/source3/winbindd/winbindd_endgrent.c b/source3/winbindd/winbindd_endgrent.c
new file mode 100644
index 0000000..6fdeb8f
--- /dev/null
+++ b/source3/winbindd/winbindd_endgrent.c
@@ -0,0 +1,54 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_ENDGRENT
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+struct winbindd_endgrent_state {
+ uint8_t dummy;
+};
+
+struct tevent_req *winbindd_endgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct winbindd_endgrent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_endgrent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_NOTICE("[%s (%u)] Winbind external command ENDGRENT start.\n",
+ cli->client_name,
+ (unsigned int)cli->pid);
+
+ TALLOC_FREE(cli->grent_state);
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+NTSTATUS winbindd_endgrent_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ D_NOTICE("Winbind external command ENDGRENT end.\n");
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_endpwent.c b/source3/winbindd/winbindd_endpwent.c
new file mode 100644
index 0000000..a7c14cb
--- /dev/null
+++ b/source3/winbindd/winbindd_endpwent.c
@@ -0,0 +1,55 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_ENDPWENT
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+struct winbindd_endpwent_state {
+ uint8_t dummy;
+};
+
+struct tevent_req *winbindd_endpwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct winbindd_endpwent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_endpwent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_NOTICE("[%s (%u)] Winbind external command ENDPWENT start.\n",
+ cli->client_name,
+ (unsigned int)cli->pid);
+
+ TALLOC_FREE(cli->pwent_state);
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+NTSTATUS winbindd_endpwent_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ D_NOTICE("Winbind external command ENDPWENT end.\n");
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getdcname.c b/source3/winbindd/winbindd_getdcname.c
new file mode 100644
index 0000000..690e840
--- /dev/null
+++ b/source3/winbindd/winbindd_getdcname.c
@@ -0,0 +1,95 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETDCNAME
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_getdcname_state {
+ struct netr_DsRGetDCNameInfo *dcinfo;
+};
+
+static void winbindd_getdcname_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getdcname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getdcname_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getdcname_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ request->domain_name[sizeof(request->domain_name)-1] = '\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command GETDCNAME start.\n"
+ "Search DCNAME for domain %s.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->domain_name);
+
+ subreq = wb_dsgetdcname_send(state, ev, request->domain_name, NULL,
+ NULL, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getdcname_done, req);
+ return req;
+}
+
+static void winbindd_getdcname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getdcname_state *state = tevent_req_data(
+ req, struct winbindd_getdcname_state);
+ NTSTATUS status;
+
+ status = wb_dsgetdcname_recv(subreq, state, &state->dcinfo);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getdcname_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getdcname_state *state = tevent_req_data(
+ req, struct winbindd_getdcname_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("getdcname failed: %s\n", nt_errstr(status));
+ return status;
+ }
+ fstrcpy(response->data.dc_name, strip_hostname(state->dcinfo->dc_unc));
+
+ D_NOTICE("Winbind external command GETDCNAME end.\n"
+ "Got DCNAME '%s'.\n",
+ response->data.dc_name);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getgrent.c b/source3/winbindd/winbindd_getgrent.c
new file mode 100644
index 0000000..e8e2b66
--- /dev/null
+++ b/source3/winbindd/winbindd_getgrent.c
@@ -0,0 +1,213 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETGRENT
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+struct winbindd_getgrent_state {
+ struct tevent_context *ev;
+ struct winbindd_cli_state *cli;
+ uint32_t max_groups;
+ uint32_t num_groups;
+ struct winbindd_gr *groups;
+ struct db_context **members;
+};
+
+static void winbindd_getgrent_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getgrent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getgrent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->num_groups = 0;
+ state->cli = cli;
+
+ D_NOTICE("[%s (%u)] Winbind external command GETGRENT start.\n"
+ "The caller (%s) provided room for %"PRIu32" entries.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ cli->client_name,
+ request->data.num_entries);
+
+ if (cli->grent_state == NULL) {
+ D_NOTICE("The grent state from winbindd client state is NULL.\n");
+ tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES);
+ return tevent_req_post(req, ev);
+ }
+
+ state->max_groups = MIN(500, request->data.num_entries);
+ if (state->max_groups == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->groups = talloc_zero_array(state, struct winbindd_gr,
+ state->max_groups);
+ if (tevent_req_nomem(state->groups, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->members = talloc_array(state, struct db_context *,
+ state->max_groups);
+ if (tevent_req_nomem(state->members, req)) {
+ TALLOC_FREE(state->groups);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_next_grent_send(state, ev, lp_winbind_expand_groups(),
+ cli->grent_state,
+ &state->groups[state->num_groups]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getgrent_done, req);
+ return req;
+}
+
+static void winbindd_getgrent_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgrent_state *state = tevent_req_data(
+ req, struct winbindd_getgrent_state);
+ NTSTATUS status;
+
+ status = wb_next_grent_recv(subreq, state,
+ &state->members[state->num_groups]);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) {
+ D_WARNING("winbindd_getgrent_done: done with %"PRIu32" groups\n",
+ state->num_groups);
+ TALLOC_FREE(state->cli->grent_state);
+ tevent_req_done(req);
+ return;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ state->num_groups += 1;
+ if (state->num_groups >= state->max_groups) {
+ D_DEBUG("winbindd_getgrent_done: Got enough groups: %"PRIu32"\n",
+ state->num_groups);
+ tevent_req_done(req);
+ return;
+ }
+ if (state->cli->grent_state == NULL) {
+ D_DEBUG("winbindd_getgrent_done: endgrent called in between\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ subreq = wb_next_grent_send(state, state->ev,
+ lp_winbind_expand_groups(),
+ state->cli->grent_state,
+ &state->groups[state->num_groups]);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getgrent_done, req);
+}
+
+NTSTATUS winbindd_getgrent_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getgrent_state *state = tevent_req_data(
+ req, struct winbindd_getgrent_state);
+ NTSTATUS status;
+ char **memberstrings;
+ char *result;
+ size_t base_memberofs, total_memberlen;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ TALLOC_FREE(state->cli->grent_state);
+ D_WARNING("getgrent failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ if (state->num_groups == 0) {
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ memberstrings = talloc_array(talloc_tos(), char *, state->num_groups);
+ if (memberstrings == NULL) {
+ TALLOC_FREE(state->cli->grent_state);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ total_memberlen = 0;
+
+ for (i=0; i<state->num_groups; i++) {
+ int num_members;
+
+ status = winbindd_print_groupmembers(
+ state->members[i], memberstrings, &num_members,
+ &memberstrings[i]);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(memberstrings);
+ TALLOC_FREE(state->cli->grent_state);
+ return status;
+ }
+ TALLOC_FREE(state->members[i]);
+
+ state->groups[i].num_gr_mem = num_members;
+ state->groups[i].gr_mem_ofs = total_memberlen;
+
+ total_memberlen += talloc_get_size(memberstrings[i]);
+ }
+
+ base_memberofs = state->num_groups * sizeof(struct winbindd_gr);
+
+ result = talloc_realloc(state, state->groups, char,
+ base_memberofs + total_memberlen);
+ if (result == NULL) {
+ TALLOC_FREE(state->cli->grent_state);
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->groups = (struct winbindd_gr *)result;
+
+ for (i=0; i<state->num_groups; i++) {
+ memcpy(result + base_memberofs + state->groups[i].gr_mem_ofs,
+ memberstrings[i], talloc_get_size(memberstrings[i]));
+ TALLOC_FREE(memberstrings[i]);
+ }
+
+ TALLOC_FREE(memberstrings);
+
+ response->data.num_entries = state->num_groups;
+ response->length += talloc_get_size(result);
+ response->extra_data.data = talloc_move(response, &result);
+
+ D_NOTICE("Winbind external command GETGRENT end.\n"
+ "Received %"PRIu32" entries.\n",
+ response->data.num_entries);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getgrgid.c b/source3/winbindd/winbindd_getgrgid.c
new file mode 100644
index 0000000..4edd81b
--- /dev/null
+++ b/source3/winbindd/winbindd_getgrgid.c
@@ -0,0 +1,156 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETGRGID
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libcli/security/dom_sid.h"
+
+struct winbindd_getgrgid_state {
+ struct tevent_context *ev;
+ struct unixid xid;
+ struct dom_sid *sid;
+ const char *domname;
+ const char *name;
+ gid_t gid;
+ struct db_context *members;
+};
+
+static void winbindd_getgrgid_gid2sid_done(struct tevent_req *subreq);
+static void winbindd_getgrgid_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getgrgid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getgrgid_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getgrgid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ D_NOTICE("[%s (%u)] Winbind external command GETGRGID start.\n"
+ "gid=%u\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ (int)request->data.gid);
+
+ state->xid = (struct unixid) {
+ .id = request->data.uid, .type = ID_TYPE_GID };
+
+ subreq = wb_xids2sids_send(state, ev, &state->xid, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getgrgid_gid2sid_done,
+ req);
+ return req;
+}
+
+static void winbindd_getgrgid_gid2sid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgrgid_state *state = tevent_req_data(
+ req, struct winbindd_getgrgid_state);
+ NTSTATUS status;
+
+ status = wb_xids2sids_recv(subreq, state, &state->sid);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ if (is_null_sid(state->sid)) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP);
+ return;
+ }
+
+ subreq = wb_getgrsid_send(state, state->ev, state->sid,
+ lp_winbind_expand_groups());
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getgrgid_done, req);
+}
+
+static void winbindd_getgrgid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgrgid_state *state = tevent_req_data(
+ req, struct winbindd_getgrgid_state);
+ NTSTATUS status;
+
+ status = wb_getgrsid_recv(subreq, state, &state->domname, &state->name,
+ &state->gid, &state->members);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getgrgid_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getgrgid_state *state = tevent_req_data(
+ req, struct winbindd_getgrgid_state);
+ NTSTATUS status;
+ int num_members;
+ char *buf;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct dom_sid_buf sidbuf;
+ D_WARNING("Could not convert sid %s: %s\n",
+ dom_sid_str_buf(state->sid, &sidbuf),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!fill_grent(talloc_tos(), &response->data.gr, state->domname,
+ state->name, state->gid)) {
+ D_WARNING("fill_grent failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = winbindd_print_groupmembers(state->members, response,
+ &num_members, &buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+
+ response->data.gr.num_gr_mem = (uint32_t)num_members;
+
+ /* Group membership lives at start of extra data */
+
+ response->data.gr.gr_mem_ofs = 0;
+ response->extra_data.data = buf;
+ response->length += talloc_get_size(response->extra_data.data);
+
+ D_NOTICE("Winbind external command GETGRGID end.\n"
+ "Returning %"PRIu32" group member(s).\n",
+ response->data.gr.num_gr_mem);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getgrnam.c b/source3/winbindd/winbindd_getgrnam.c
new file mode 100644
index 0000000..6b277c2
--- /dev/null
+++ b/source3/winbindd/winbindd_getgrnam.c
@@ -0,0 +1,213 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETGRNAM
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_getgrnam_state {
+ struct tevent_context *ev;
+ char *name_namespace;
+ char *name_domain;
+ char *name_group;
+ struct dom_sid sid;
+ const char *domname;
+ const char *name;
+ gid_t gid;
+ struct db_context *members;
+};
+
+static void winbindd_getgrnam_lookupname_done(struct tevent_req *subreq);
+static void winbindd_getgrnam_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getgrnam_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getgrnam_state *state;
+ char *tmp;
+ NTSTATUS nt_status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getgrnam_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ /* Ensure null termination */
+ request->data.groupname[sizeof(request->data.groupname)-1]='\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command GETGRNAM start.\n"
+ "Searching group name '%s'.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.groupname);
+
+ nt_status = normalize_name_unmap(state, request->data.groupname, &tmp);
+ /* If we didn't map anything in the above call, just reset the
+ tmp pointer to the original string */
+ if (!NT_STATUS_IS_OK(nt_status) &&
+ !NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED))
+ {
+ tmp = request->data.groupname;
+ }
+
+ /* Parse domain and groupname */
+
+ ok = parse_domain_user(state, tmp,
+ &state->name_namespace,
+ &state->name_domain,
+ &state->name_group);
+ if (!ok) {
+ DBG_INFO("Could not parse domain user: %s\n", tmp);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ /* if no domain or our local domain and no local tdb group, default to
+ * our local domain for aliases */
+
+ if ( !*(state->name_domain) || strequal(state->name_domain,
+ get_global_sam_name()) ) {
+ TALLOC_FREE(state->name_domain);
+ state->name_domain = talloc_strdup(state,
+ get_global_sam_name());
+ if (tevent_req_nomem(state->name_domain, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = wb_lookupname_send(state, ev,
+ state->name_namespace,
+ state->name_domain,
+ state->name_group,
+ 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getgrnam_lookupname_done,
+ req);
+ return req;
+}
+
+static void winbindd_getgrnam_lookupname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgrnam_state *state = tevent_req_data(
+ req, struct winbindd_getgrnam_state);
+ enum lsa_SidType type;
+ NTSTATUS status;
+
+ status = wb_lookupname_recv(subreq, &state->sid, &type);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ switch (type) {
+ case SID_NAME_DOM_GRP:
+ case SID_NAME_ALIAS:
+ case SID_NAME_WKN_GRP:
+ /*
+ * Also give user types a chance:
+ * These might be user sids mapped to the ID_TYPE_BOTH,
+ * and in that case we should construct a group struct.
+ */
+ case SID_NAME_USER:
+ case SID_NAME_COMPUTER:
+ break;
+ default:
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_GROUP);
+ return;
+ }
+
+ subreq = wb_getgrsid_send(state, state->ev, &state->sid,
+ lp_winbind_expand_groups());
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getgrnam_done, req);
+}
+
+static void winbindd_getgrnam_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgrnam_state *state = tevent_req_data(
+ req, struct winbindd_getgrnam_state);
+ NTSTATUS status;
+
+ status = wb_getgrsid_recv(subreq, state, &state->domname, &state->name,
+ &state->gid, &state->members);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getgrnam_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getgrnam_state *state = tevent_req_data(
+ req, struct winbindd_getgrnam_state);
+ NTSTATUS status;
+ int num_members;
+ char *buf;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct dom_sid_buf sidbuf;
+ D_WARNING("Could not convert sid %s: %s\n",
+ dom_sid_str_buf(&state->sid, &sidbuf),
+ nt_errstr(status));
+ return status;
+ }
+
+ if (!fill_grent(talloc_tos(), &response->data.gr, state->domname,
+ state->name, state->gid)) {
+ D_WARNING("fill_grent failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = winbindd_print_groupmembers(state->members, response,
+ &num_members, &buf);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ response->data.gr.num_gr_mem = (uint32_t)num_members;
+
+ /* Group membership lives at start of extra data */
+
+ response->data.gr.gr_mem_ofs = 0;
+ response->extra_data.data = buf;
+ response->length += talloc_get_size(response->extra_data.data);
+
+ D_NOTICE("Winbind external command GETGRNAM end.\n"
+ "Returning %"PRIu32" member(s).\n",
+ response->data.gr.num_gr_mem);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getgroups.c b/source3/winbindd/winbindd_getgroups.c
new file mode 100644
index 0000000..c1c108e
--- /dev/null
+++ b/source3/winbindd/winbindd_getgroups.c
@@ -0,0 +1,283 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETGROUPS
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "passdb/lookup_sid.h" /* only for LOOKUP_NAME_NO_NSS flag */
+#include "libcli/security/dom_sid.h"
+
+struct winbindd_getgroups_state {
+ struct tevent_context *ev;
+ char *namespace;
+ char *domname;
+ char *username;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ uint32_t num_sids;
+ struct dom_sid *sids;
+ uint32_t num_gids;
+ gid_t *gids;
+};
+
+static void winbindd_getgroups_lookupname_done(struct tevent_req *subreq);
+static void winbindd_getgroups_gettoken_done(struct tevent_req *subreq);
+static void winbindd_getgroups_sid2gid_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getgroups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getgroups_state *state;
+ char *domuser, *mapped_user;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getgroups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ /* Ensure null termination */
+ request->data.username[sizeof(request->data.username)-1]='\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command GETGROUPS start.\n"
+ "Searching groups for username '%s'.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.username);
+
+ domuser = request->data.username;
+
+ status = normalize_name_unmap(state, domuser, &mapped_user);
+
+ if (NT_STATUS_IS_OK(status)
+ || NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) {
+ /* normalize_name_unmapped did something */
+ domuser = mapped_user;
+ }
+
+ ok = parse_domain_user(state, domuser,
+ &state->namespace,
+ &state->domname,
+ &state->username);
+ if (!ok) {
+ D_WARNING("Could not parse domain user: %s\n", domuser);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_lookupname_send(state, ev,
+ state->namespace,
+ state->domname,
+ state->username,
+ LOOKUP_NAME_NO_NSS);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getgroups_lookupname_done,
+ req);
+ return req;
+}
+
+static void winbindd_getgroups_lookupname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgroups_state *state = tevent_req_data(
+ req, struct winbindd_getgroups_state);
+ NTSTATUS status;
+
+ status = wb_lookupname_recv(subreq, &state->sid, &state->type);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = wb_gettoken_send(state, state->ev, &state->sid, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getgroups_gettoken_done, req);
+}
+
+static void winbindd_getgroups_gettoken_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgroups_state *state = tevent_req_data(
+ req, struct winbindd_getgroups_state);
+ NTSTATUS status;
+
+ status = wb_gettoken_recv(subreq, state, &state->num_sids,
+ &state->sids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ /*
+ * Convert the group SIDs to gids. state->sids[0] contains the user
+ * sid. If the idmap backend uses ID_TYPE_BOTH, we might need the
+ * the id of the user sid in the list of group sids, so map the
+ * complete token.
+ */
+
+ subreq = wb_sids2xids_send(state, state->ev,
+ state->sids, state->num_sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getgroups_sid2gid_done, req);
+}
+
+static void winbindd_getgroups_sid2gid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getgroups_state *state = tevent_req_data(
+ req, struct winbindd_getgroups_state);
+ NTSTATUS status;
+ struct unixid *xids;
+ uint32_t i;
+
+ xids = talloc_array(state, struct unixid, state->num_sids);
+ if (tevent_req_nomem(xids, req)) {
+ return;
+ }
+ for (i=0; i < state->num_sids; i++) {
+ xids[i].type = ID_TYPE_NOT_SPECIFIED;
+ xids[i].id = UINT32_MAX;
+ }
+
+ status = wb_sids2xids_recv(subreq, xids, state->num_sids);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED) ||
+ NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED))
+ {
+ status = NT_STATUS_OK;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ state->gids = talloc_array(state, gid_t, state->num_sids);
+ if (tevent_req_nomem(state->gids, req)) {
+ return;
+ }
+ state->num_gids = 0;
+
+ for (i=0; i < state->num_sids; i++) {
+ bool include_gid = false;
+ const char *debug_missing = NULL;
+
+ switch (xids[i].type) {
+ case ID_TYPE_NOT_SPECIFIED:
+ debug_missing = "not specified";
+ break;
+ case ID_TYPE_UID:
+ if (i != 0) {
+ debug_missing = "uid";
+ }
+ break;
+ case ID_TYPE_GID:
+ case ID_TYPE_BOTH:
+ include_gid = true;
+ break;
+ case ID_TYPE_WB_REQUIRE_TYPE:
+ /*
+ * these are internal between winbindd
+ * parent and child.
+ */
+ smb_panic(__location__);
+ break;
+ }
+
+ if (!include_gid) {
+ struct dom_sid_buf sidbuf;
+
+ if (debug_missing == NULL) {
+ continue;
+ }
+
+ D_WARNING("WARNING: skipping unix id (%"PRIu32") for sid %s "
+ "from group list because the idmap type "
+ "is %s. "
+ "This might be a security problem when ACLs "
+ "contain DENY ACEs!\n",
+ (unsigned)xids[i].id,
+ dom_sid_str_buf(&state->sids[i], &sidbuf),
+ debug_missing);
+ continue;
+ }
+
+ state->gids[state->num_gids] = (gid_t)xids[i].id;
+ state->num_gids += 1;
+ }
+
+ /*
+ * This should not fail, as it does not do any reallocation,
+ * just updating the talloc size.
+ */
+ state->gids = talloc_realloc(state, state->gids, gid_t, state->num_gids);
+ if (tevent_req_nomem(state->gids, req)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getgroups_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getgroups_state *state = tevent_req_data(
+ req, struct winbindd_getgroups_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct dom_sid_buf buf;
+ D_WARNING("Could not convert sid %s: %s\n",
+ dom_sid_str_buf(&state->sid, &buf),
+ nt_errstr(status));
+ return status;
+ }
+
+ response->data.num_entries = state->num_gids;
+
+ D_NOTICE("Winbind external command GETGROUPS end.\n"
+ "Received %"PRIu32" entries.\n",
+ response->data.num_entries);
+ if (CHECK_DEBUGLVL(DBGLVL_NOTICE)) {
+ for (i = 0; i < state->num_gids; i++) {
+ D_NOTICE("%"PRIu32": GID %u\n", i, state->gids[i]);
+ }
+ }
+
+ if (state->num_gids > 0) {
+ response->extra_data.data = talloc_move(response,
+ &state->gids);
+ response->length += state->num_gids * sizeof(gid_t);
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getpwent.c b/source3/winbindd/winbindd_getpwent.c
new file mode 100644
index 0000000..5a78883
--- /dev/null
+++ b/source3/winbindd/winbindd_getpwent.c
@@ -0,0 +1,162 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETPWENT
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+struct winbindd_getpwent_state {
+ struct tevent_context *ev;
+ struct winbindd_cli_state *cli;
+ uint32_t max_users;
+ uint32_t num_users;
+ struct winbindd_pw *users;
+};
+
+static void winbindd_getpwent_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getpwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getpwent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getpwent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->num_users = 0;
+ state->cli = cli;
+
+ D_NOTICE("[%s (%u)] Winbind external command GETPWENT start.\n"
+ "The caller (%s) provided room for %d entries.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ cli->client_name,
+ request->data.num_entries);
+
+ if (cli->pwent_state == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_MORE_ENTRIES);
+ return tevent_req_post(req, ev);
+ }
+
+ state->max_users = MIN(500, request->data.num_entries);
+ if (state->max_users == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->users = talloc_zero_array(state, struct winbindd_pw,
+ state->max_users);
+ if (tevent_req_nomem(state->users, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_next_pwent_send(state, ev, cli->pwent_state,
+ &state->users[state->num_users]);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getpwent_done, req);
+ return req;
+}
+
+static void winbindd_getpwent_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getpwent_state *state = tevent_req_data(
+ req, struct winbindd_getpwent_state);
+ NTSTATUS status;
+
+ status = wb_next_pwent_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) {
+ D_DEBUG("winbindd_getpwent_done: done with %"PRIu32" users\n",
+ state->num_users);
+ TALLOC_FREE(state->cli->pwent_state);
+ tevent_req_done(req);
+ return;
+ }
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ state->num_users += 1;
+ if (state->num_users >= state->max_users) {
+ D_DEBUG("winbindd_getpwent_done: Got enough users: %"PRIu32"\n",
+ state->num_users);
+ tevent_req_done(req);
+ return;
+ }
+ if (state->cli->pwent_state == NULL) {
+ D_DEBUG("winbindd_getpwent_done: endpwent called in between\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return;
+ }
+ subreq = wb_next_pwent_send(state, state->ev, state->cli->pwent_state,
+ &state->users[state->num_users]);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getpwent_done, req);
+}
+
+NTSTATUS winbindd_getpwent_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getpwent_state *state = tevent_req_data(
+ req, struct winbindd_getpwent_state);
+ NTSTATUS status;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ TALLOC_FREE(state->cli->pwent_state);
+ D_WARNING("getpwent failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ D_NOTICE("Winbind external command GETPWENT end.\n"
+ "Received %"PRIu32" entries.\n"
+ "(name:passwd:uid:gid:gecos:dir:shell)\n",
+ state->num_users);
+
+ if (state->num_users == 0) {
+ return NT_STATUS_NO_MORE_ENTRIES;
+ }
+
+ for (i = 0; i < state->num_users; i++) {
+ D_NOTICE("%"PRIu32": %s:%s:%u:%u:%s:%s:%s\n",
+ i,
+ state->users[i].pw_name,
+ state->users[i].pw_passwd,
+ (unsigned int)state->users[i].pw_uid,
+ (unsigned int)state->users[i].pw_gid,
+ state->users[i].pw_gecos,
+ state->users[i].pw_dir,
+ state->users[i].pw_shell
+ );
+ }
+ response->data.num_entries = state->num_users;
+ response->extra_data.data = talloc_move(response, &state->users);
+ response->length += state->num_users * sizeof(struct winbindd_pw);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getpwnam.c b/source3/winbindd/winbindd_getpwnam.c
new file mode 100644
index 0000000..2bf15c0
--- /dev/null
+++ b/source3/winbindd/winbindd_getpwnam.c
@@ -0,0 +1,163 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETPWNAM
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "passdb/lookup_sid.h" /* only for LOOKUP_NAME_NO_NSS flag */
+#include "libcli/security/dom_sid.h"
+
+struct winbindd_getpwnam_state {
+ struct tevent_context *ev;
+ char *namespace;
+ char *domname;
+ char *username;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ struct winbindd_pw pw;
+};
+
+static void winbindd_getpwnam_lookupname_done(struct tevent_req *subreq);
+static void winbindd_getpwnam_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getpwnam_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getpwnam_state *state;
+ char *domuser, *mapped_user;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getpwnam_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ /* Ensure null termination */
+ request->data.username[sizeof(request->data.username)-1]='\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command GETPWNAM start.\n"
+ "Query username '%s'.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.username);
+
+ domuser = request->data.username;
+
+ status = normalize_name_unmap(state, domuser, &mapped_user);
+
+ if (NT_STATUS_IS_OK(status)
+ || NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) {
+ /* normalize_name_unmapped did something */
+ domuser = mapped_user;
+ }
+
+ ok = parse_domain_user(state,
+ domuser,
+ &state->namespace,
+ &state->domname,
+ &state->username);
+ if (!ok) {
+ D_WARNING("Could not parse domain user: %s\n", domuser);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_lookupname_send(state, ev,
+ state->namespace,
+ state->domname,
+ state->username,
+ LOOKUP_NAME_NO_NSS);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getpwnam_lookupname_done,
+ req);
+ return req;
+}
+
+static void winbindd_getpwnam_lookupname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getpwnam_state *state = tevent_req_data(
+ req, struct winbindd_getpwnam_state);
+ NTSTATUS status;
+
+ status = wb_lookupname_recv(subreq, &state->sid, &state->type);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ subreq = wb_getpwsid_send(state, state->ev, &state->sid, &state->pw);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getpwnam_done, req);
+}
+
+static void winbindd_getpwnam_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = wb_getpwsid_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getpwnam_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getpwnam_state *state = tevent_req_data(
+ req, struct winbindd_getpwnam_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct dom_sid_buf buf;
+ D_WARNING("Could not convert sid %s: %s\n",
+ dom_sid_str_buf(&state->sid, &buf),
+ nt_errstr(status));
+ return status;
+ }
+ response->data.pw = state->pw;
+
+ D_NOTICE("Winbind external command GETPWNAM end.\n"
+ "(name:passwd:uid:gid:gecos:dir:shell)\n"
+ "%s:%s:%u:%u:%s:%s:%s\n",
+ state->pw.pw_name,
+ state->pw.pw_passwd,
+ (unsigned int)state->pw.pw_uid,
+ (unsigned int)state->pw.pw_gid,
+ state->pw.pw_gecos,
+ state->pw.pw_dir,
+ state->pw.pw_shell
+ );
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getpwsid.c b/source3/winbindd/winbindd_getpwsid.c
new file mode 100644
index 0000000..8e0a3e3
--- /dev/null
+++ b/source3/winbindd/winbindd_getpwsid.c
@@ -0,0 +1,109 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETPWSID
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+
+struct winbindd_getpwsid_state {
+ struct dom_sid sid;
+ struct winbindd_pw pw;
+};
+
+static void winbindd_getpwsid_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getpwsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getpwsid_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getpwsid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Ensure null termination */
+ request->data.sid[sizeof(request->data.sid)-1]='\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command GETPWSID start.\n"
+ "sid=%s\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.sid);
+
+ if (!string_to_sid(&state->sid, request->data.sid)) {
+ D_WARNING("Could not get convert sid %s from string\n",
+ request->data.sid);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_getpwsid_send(state, ev, &state->sid, &state->pw);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getpwsid_done, req);
+ return req;
+}
+
+static void winbindd_getpwsid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = wb_getpwsid_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getpwsid_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getpwsid_state *state = tevent_req_data(
+ req, struct winbindd_getpwsid_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+ response->data.pw = state->pw;
+
+ D_NOTICE("Winbind external command GETPWSID end.\n"
+ "(name:passwd:uid:gid:gecos:dir:shell)\n"
+ "%s:%s:%u:%u:%s:%s:%s\n",
+ state->pw.pw_name,
+ state->pw.pw_passwd,
+ (unsigned int)state->pw.pw_uid,
+ (unsigned int)state->pw.pw_gid,
+ state->pw.pw_gecos,
+ state->pw.pw_dir,
+ state->pw.pw_shell
+ );
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getpwuid.c b/source3/winbindd/winbindd_getpwuid.c
new file mode 100644
index 0000000..509e013
--- /dev/null
+++ b/source3/winbindd/winbindd_getpwuid.c
@@ -0,0 +1,137 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETPWUID
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libcli/security/dom_sid.h"
+
+struct winbindd_getpwuid_state {
+ struct tevent_context *ev;
+ struct unixid xid;
+ struct dom_sid *sid;
+ struct winbindd_pw pw;
+};
+
+static void winbindd_getpwuid_uid2sid_done(struct tevent_req *subreq);
+static void winbindd_getpwuid_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getpwuid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getpwuid_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getpwuid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ D_NOTICE("[%s (%u)] Winbind external command GETPWUID start.\n"
+ "Search UID %u.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ (int)request->data.uid);
+
+ state->xid = (struct unixid) {
+ .id = request->data.uid, .type = ID_TYPE_UID };
+
+ subreq = wb_xids2sids_send(state, ev, &state->xid, 1);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getpwuid_uid2sid_done,
+ req);
+ return req;
+}
+
+static void winbindd_getpwuid_uid2sid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getpwuid_state *state = tevent_req_data(
+ req, struct winbindd_getpwuid_state);
+ NTSTATUS status;
+
+ status = wb_xids2sids_recv(subreq, state, &state->sid);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+ if (is_null_sid(state->sid)) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ D_WARNING("Failed with NT_STATUS_NO_SUCH_USER.\n");
+ return;
+ }
+
+ subreq = wb_getpwsid_send(state, state->ev, state->sid, &state->pw);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_getpwuid_done, req);
+}
+
+static void winbindd_getpwuid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ NTSTATUS status;
+
+ status = wb_getpwsid_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getpwuid_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getpwuid_state *state = tevent_req_data(
+ req, struct winbindd_getpwuid_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct dom_sid_buf buf;
+ D_WARNING("Could not convert sid %s: %s\n",
+ dom_sid_str_buf(state->sid, &buf),
+ nt_errstr(status));
+ return status;
+ }
+ response->data.pw = state->pw;
+ D_NOTICE("Winbind external command GETPWUID end.\n"
+ "(name:passwd:uid:gid:gecos:dir:shell)\n"
+ "%s:%s:%u:%u:%s:%s:%s\n",
+ state->pw.pw_name,
+ state->pw.pw_passwd,
+ (unsigned int)state->pw.pw_uid,
+ (unsigned int)state->pw.pw_gid,
+ state->pw.pw_gecos,
+ state->pw.pw_dir,
+ state->pw.pw_shell
+ );
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getsidaliases.c b/source3/winbindd/winbindd_getsidaliases.c
new file mode 100644
index 0000000..bf8fcdd
--- /dev/null
+++ b/source3/winbindd/winbindd_getsidaliases.c
@@ -0,0 +1,160 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETSIDALIASES
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+
+struct winbindd_getsidaliases_state {
+ struct dom_sid sid;
+ uint32_t num_aliases;
+ uint32_t *aliases;
+};
+
+static void winbindd_getsidaliases_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getsidaliases_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getsidaliases_state *state;
+ struct winbindd_domain *domain;
+ uint32_t num_sids, i;
+ struct dom_sid *sids;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getsidaliases_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Ensure null termination */
+ request->data.sid[sizeof(request->data.sid)-1]='\0';
+
+ if (!string_to_sid(&state->sid, request->data.sid)) {
+ D_WARNING("Could not get convert sid %s from string\n",
+ request->data.sid);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ domain = find_domain_from_sid_noinit(&state->sid);
+ if (domain == NULL) {
+ D_WARNING("could not find domain entry for sid %s\n",
+ request->data.sid);
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+
+ num_sids = 0;
+ sids = NULL;
+
+ if (request->extra_data.data != NULL) {
+ if (request->extra_data.data[request->extra_len-1] != '\0') {
+ D_WARNING("Got non-NULL terminated sidlist\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (!parse_sidlist(state, request->extra_data.data,
+ &sids, &num_sids)) {
+ D_WARNING("Could not parse SID list: %s\n",
+ request->extra_data.data);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ D_NOTICE("[%s (%u)] Winbind external command GETSIDALIASES start.\n"
+ "sid=%s\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.sid);
+ if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) {
+ for (i = 0; i < num_sids; i++) {
+ struct dom_sid_buf sidstr;
+ D_NOTICE("%"PRIu32": %s\n",
+ i, dom_sid_str_buf(&sids[i], &sidstr));
+ }
+ }
+
+ subreq = wb_lookupuseraliases_send(state, ev, domain, num_sids, sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getsidaliases_done, req);
+ return req;
+}
+
+static void winbindd_getsidaliases_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getsidaliases_state *state = tevent_req_data(
+ req, struct winbindd_getsidaliases_state);
+ NTSTATUS status;
+
+ status = wb_lookupuseraliases_recv(subreq, state, &state->num_aliases,
+ &state->aliases);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getsidaliases_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getsidaliases_state *state = tevent_req_data(
+ req, struct winbindd_getsidaliases_state);
+ NTSTATUS status;
+ uint32_t i;
+ char *sidlist;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+
+ sidlist = talloc_strdup(response, "");
+
+ D_NOTICE("Winbind external command GETSIDALIASES end.\n"
+ "Received %"PRIu32" alias(es).\n",
+ state->num_aliases);
+ for (i=0; i<state->num_aliases; i++) {
+ struct dom_sid sid;
+ struct dom_sid_buf tmp;
+ sid_compose(&sid, &state->sid, state->aliases[i]);
+
+ talloc_asprintf_addbuf(
+ &sidlist, "%s\n", dom_sid_str_buf(&sid, &tmp));
+ D_NOTICE("%"PRIu32": %s\n", i, dom_sid_str_buf(&sid, &tmp));
+ }
+
+ if (sidlist == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ response->extra_data.data = sidlist;
+ response->length += talloc_get_size(sidlist);
+ response->data.num_entries = state->num_aliases;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getuserdomgroups.c b/source3/winbindd/winbindd_getuserdomgroups.c
new file mode 100644
index 0000000..75eb437
--- /dev/null
+++ b/source3/winbindd/winbindd_getuserdomgroups.c
@@ -0,0 +1,123 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETUSERDOMGROUPS
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+
+struct winbindd_getuserdomgroups_state {
+ struct dom_sid sid;
+ uint32_t num_sids;
+ struct dom_sid *sids;
+};
+
+static void winbindd_getuserdomgroups_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getuserdomgroups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getuserdomgroups_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getuserdomgroups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Ensure null termination */
+ request->data.sid[sizeof(request->data.sid)-1]='\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command GETUSERDOMGROUPS start.\n"
+ "sid=%s\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.sid);
+
+ if (!string_to_sid(&state->sid, request->data.sid)) {
+ D_WARNING("Could not get convert sid %s from string\n",
+ request->data.sid);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_gettoken_send(state, ev, &state->sid, false);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getuserdomgroups_done, req);
+ return req;
+}
+
+static void winbindd_getuserdomgroups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getuserdomgroups_state *state = tevent_req_data(
+ req, struct winbindd_getuserdomgroups_state);
+ NTSTATUS status;
+
+ status = wb_gettoken_recv(subreq, state, &state->num_sids,
+ &state->sids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getuserdomgroups_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getuserdomgroups_state *state = tevent_req_data(
+ req, struct winbindd_getuserdomgroups_state);
+ NTSTATUS status;
+ uint32_t i;
+ char *sidlist;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+
+ sidlist = talloc_strdup(response, "");
+ if (sidlist == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ D_NOTICE("Winbind external command GETUSERDOMGROUPS end.\n"
+ "Received %"PRIu32" entries.\n",
+ state->num_sids);
+ for (i=0; i<state->num_sids; i++) {
+ struct dom_sid_buf tmp;
+ sidlist = talloc_asprintf_append_buffer(
+ sidlist, "%s\n",
+ dom_sid_str_buf(&state->sids[i], &tmp));
+ if (sidlist == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ D_NOTICE("%"PRIu32": %s\n",
+ i, dom_sid_str_buf(&state->sids[i], &tmp));
+ }
+ response->extra_data.data = sidlist;
+ response->length += talloc_get_size(sidlist);
+ response->data.num_entries = state->num_sids;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_getusersids.c b/source3/winbindd/winbindd_getusersids.c
new file mode 100644
index 0000000..a285c1f
--- /dev/null
+++ b/source3/winbindd/winbindd_getusersids.c
@@ -0,0 +1,128 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_GETUSERSIDS
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+
+struct winbindd_getusersids_state {
+ struct dom_sid sid;
+ uint32_t num_sids;
+ struct dom_sid *sids;
+};
+
+static void winbindd_getusersids_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_getusersids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_getusersids_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_getusersids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Ensure null termination */
+ request->data.sid[sizeof(request->data.sid)-1]='\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command GETUSERSIDS start.\n"
+ "sid=%s\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.sid);
+
+ if (!string_to_sid(&state->sid, request->data.sid)) {
+ D_WARNING("Returning NT_STATUS_INVALID_PARAMETER.\n"
+ "Could not get convert sid %s from string\n",
+ request->data.sid);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_gettoken_send(state, ev, &state->sid, true);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_getusersids_done, req);
+ return req;
+}
+
+static void winbindd_getusersids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_getusersids_state *state = tevent_req_data(
+ req, struct winbindd_getusersids_state);
+ NTSTATUS status;
+
+ status = wb_gettoken_recv(subreq, state, &state->num_sids,
+ &state->sids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ D_WARNING("wb_gettoken_recv failed with %s.\n",
+ nt_errstr(status));
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_getusersids_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_getusersids_state *state = tevent_req_data(
+ req, struct winbindd_getusersids_state);
+ struct dom_sid_buf sidbuf;
+ NTSTATUS status;
+ uint32_t i;
+ char *result;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Could not convert sid %s: %s\n",
+ dom_sid_str_buf(&state->sid, &sidbuf),
+ nt_errstr(status));
+ return status;
+ }
+
+ result = talloc_strdup(response, "");
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ D_NOTICE("Winbind external command GETUSERSIDS end.\n"
+ "Got %"PRIu32" SID(s).\n", state->num_sids);
+ for (i=0; i<state->num_sids; i++) {
+ D_NOTICE("%"PRIu32": %s\n",
+ i,
+ dom_sid_str_buf(&state->sids[i], &sidbuf));
+ talloc_asprintf_addbuf(
+ &result,
+ "%s\n",
+ dom_sid_str_buf(&state->sids[i], &sidbuf));
+ }
+
+ response->data.num_entries = state->num_sids;
+ response->extra_data.data = result;
+ response->length += talloc_get_size(result);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_gpupdate.c b/source3/winbindd/winbindd_gpupdate.c
new file mode 100644
index 0000000..1ab20fb
--- /dev/null
+++ b/source3/winbindd/winbindd_gpupdate.c
@@ -0,0 +1,184 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * Group Policy Update event for winbindd
+ * Copyright (C) David Mulder 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "includes.h"
+#include "param/param.h"
+#include "param/loadparm.h"
+#include "winbindd.h"
+#include "lib/global_contexts.h"
+
+/*
+ * gpupdate_interval()
+ * return Random integer between 5400 and 7200, the group policy update
+ * interval in seconds
+ *
+ * Group Policy should be updated every 90 minutes in the background,
+ * with a random offset between 0 and 30 minutes. This ensures multiple
+ * clients will not update at the same time.
+ */
+#define GPUPDATE_INTERVAL (90*60)
+#define GPUPDATE_RAND_OFFSET (30*60)
+static uint32_t gpupdate_interval(void)
+{
+ int rand_int_offset = generate_random() % GPUPDATE_RAND_OFFSET;
+ return GPUPDATE_INTERVAL+rand_int_offset;
+}
+
+struct gpupdate_state {
+ TALLOC_CTX *ctx;
+ struct loadparm_context *lp_ctx;
+};
+
+static void gpupdate_cmd_done(struct tevent_req *subreq);
+
+static void gpupdate_callback(struct tevent_context *ev,
+ struct tevent_timer *tim,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct tevent_timer *time_event;
+ struct timeval schedule;
+ struct tevent_req *req = NULL;
+ struct gpupdate_state *data =
+ talloc_get_type_abort(private_data, struct gpupdate_state);
+ const char *const *gpupdate_cmd =
+ lpcfg_gpo_update_command(data->lp_ctx);
+ const char *smbconf = lpcfg_configfile(data->lp_ctx);
+ if (smbconf == NULL) {
+ smbconf = lp_default_path();
+ }
+
+ /* Execute gpupdate */
+ req = samba_runcmd_send(data->ctx, ev, timeval_zero(), 2, 0,
+ gpupdate_cmd,
+ "-s",
+ smbconf,
+ "--target=Computer",
+ "--machine-pass",
+ NULL);
+ if (req == NULL) {
+ DEBUG(0, ("Failed to execute the gpupdate command\n"));
+ return;
+ }
+
+ tevent_req_set_callback(req, gpupdate_cmd_done, NULL);
+
+ /* Schedule the next event */
+ schedule = tevent_timeval_current_ofs(gpupdate_interval(), 0);
+ time_event = tevent_add_timer(ev, data->ctx, schedule,
+ gpupdate_callback, data);
+ if (time_event == NULL) {
+ DEBUG(0, ("Failed scheduling the next gpupdate event\n"));
+ }
+}
+
+void gpupdate_init(void)
+{
+ struct tevent_timer *time_event;
+ struct timeval schedule;
+ TALLOC_CTX * ctx = talloc_new(global_event_context());
+ struct gpupdate_state *data = talloc(ctx, struct gpupdate_state);
+ struct loadparm_context *lp_ctx =
+ loadparm_init_s3(NULL, loadparm_s3_helpers());
+
+ /*
+ * Check if gpupdate is enabled for winbind, if not
+ * return without scheduling any events.
+ */
+ if (!lpcfg_apply_group_policies(lp_ctx)) {
+ return;
+ }
+
+ /*
+ * Execute the first event immediately, future events
+ * will execute on the gpupdate interval, which is every
+ * 90 to 120 minutes (at random).
+ */
+ schedule = tevent_timeval_current_ofs(0, 0);
+ data->ctx = ctx;
+ data->lp_ctx = lp_ctx;
+ if (data->lp_ctx == NULL) {
+ smb_panic("Could not load smb.conf\n");
+ }
+ time_event = tevent_add_timer(global_event_context(), data->ctx,
+ schedule, gpupdate_callback, data);
+ if (time_event == NULL) {
+ DEBUG(0, ("Failed scheduling the gpupdate event\n"));
+ }
+}
+
+void gpupdate_user_init(const char *user)
+{
+ struct tevent_req *req = NULL;
+ TALLOC_CTX *ctx = talloc_new(global_event_context());
+ struct loadparm_context *lp_ctx =
+ loadparm_init_s3(NULL, loadparm_s3_helpers());
+ const char *const *gpupdate_cmd = lpcfg_gpo_update_command(lp_ctx);
+ const char *smbconf = lpcfg_configfile(lp_ctx);
+ if (smbconf == NULL) {
+ smbconf = lp_default_path();
+ }
+
+ if (ctx == NULL) {
+ DBG_ERR("talloc_new failed\n");
+ return;
+ }
+
+ /*
+ * Check if gpupdate is enabled for winbind, if not
+ * return without applying user policy.
+ */
+ if (!lpcfg_apply_group_policies(lp_ctx)) {
+ return;
+ }
+
+ /*
+ * Execute gpupdate for the user immediately.
+ * TODO: This should be scheduled to reapply every 90 to 120 minutes.
+ * Logoff will need to handle cancelling these events though, and
+ * multiple timers cannot be run for the same user, even if there are
+ * multiple active sessions.
+ */
+ req = samba_runcmd_send(ctx, global_event_context(),
+ timeval_zero(), 2, 0,
+ gpupdate_cmd,
+ "-s",
+ smbconf,
+ "--target=User",
+ "-U",
+ user,
+ NULL);
+ if (req == NULL) {
+ DBG_ERR("Failed to execute the gpupdate command\n");
+ return;
+ }
+
+ tevent_req_set_callback(req, gpupdate_cmd_done, NULL);
+}
+
+static void gpupdate_cmd_done(struct tevent_req *subreq)
+{
+ int sys_errno;
+ int ret;
+
+ ret = samba_runcmd_recv(subreq, &sys_errno);
+ TALLOC_FREE(subreq);
+ if (ret != 0) {
+ DBG_ERR("gpupdate failed with exit status %d\n", sys_errno);
+ }
+}
diff --git a/source3/winbindd/winbindd_group.c b/source3/winbindd/winbindd_group.c
new file mode 100644
index 0000000..b233c8e
--- /dev/null
+++ b/source3/winbindd/winbindd_group.c
@@ -0,0 +1,156 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Jeremy Allison 2001.
+ Copyright (C) Gerald (Jerry) Carter 2003.
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "lib/dbwrap/dbwrap.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/* Fill a grent structure from various other information */
+
+bool fill_grent(TALLOC_CTX *mem_ctx, struct winbindd_gr *gr,
+ const char *dom_name, const char *gr_name, gid_t unix_gid)
+{
+ const char *full_group_name;
+ char *mapped_name = NULL;
+ NTSTATUS nt_status = NT_STATUS_UNSUCCESSFUL;
+
+ nt_status = normalize_name_map(mem_ctx, dom_name, gr_name,
+ &mapped_name);
+
+ D_DEBUG("Filling domain '%s' and group '%s'.\n", dom_name, gr_name);
+ /* Basic whitespace replacement */
+ if (NT_STATUS_IS_OK(nt_status)) {
+ full_group_name = fill_domain_username_talloc(mem_ctx, dom_name,
+ mapped_name, true);
+ }
+ /* Mapped to an alias */
+ else if (NT_STATUS_EQUAL(nt_status, NT_STATUS_FILE_RENAMED)) {
+ full_group_name = mapped_name;
+ }
+ /* no change */
+ else {
+ full_group_name = fill_domain_username_talloc(mem_ctx, dom_name,
+ gr_name, True );
+ }
+
+ if (full_group_name == NULL) {
+ D_DEBUG("Returning false, since there is no full group name.\n");
+ return false;
+ }
+
+ gr->gr_gid = unix_gid;
+
+ /* Group name and password */
+
+ strlcpy(gr->gr_name, full_group_name, sizeof(gr->gr_name));
+ strlcpy(gr->gr_passwd, "x", sizeof(gr->gr_passwd));
+
+ D_DEBUG("Returning true. Full group name is '%s'.\n", gr_name);
+ return True;
+}
+
+struct getgr_countmem {
+ int num;
+ size_t len;
+};
+
+static int getgr_calc_memberlen(struct db_record *rec, void *private_data)
+{
+ struct getgr_countmem *buf = private_data;
+ TDB_DATA data = dbwrap_record_get_value(rec);
+ size_t len;
+
+ buf->num += 1;
+
+ len = buf->len + data.dsize;
+ if (len < buf->len) {
+ return 0;
+ }
+ buf->len = len;
+ return 0;
+}
+
+struct getgr_stringmem {
+ size_t ofs;
+ char *buf;
+};
+
+static int getgr_unparse_members(struct db_record *rec, void *private_data)
+{
+ struct getgr_stringmem *buf = private_data;
+ TDB_DATA data = dbwrap_record_get_value(rec);
+ int len;
+
+ len = data.dsize-1;
+
+ memcpy(buf->buf + buf->ofs, data.dptr, len);
+ buf->ofs += len;
+ buf->buf[buf->ofs] = ',';
+ buf->ofs += 1;
+ return 0;
+}
+
+NTSTATUS winbindd_print_groupmembers(struct db_context *members,
+ TALLOC_CTX *mem_ctx,
+ int *num_members, char **result)
+{
+ struct getgr_countmem c;
+ struct getgr_stringmem m;
+ int count;
+ NTSTATUS status;
+
+ c.num = 0;
+ c.len = 0;
+
+ status = dbwrap_traverse(members, getgr_calc_memberlen, &c, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("dbwrap_traverse failed: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ m.ofs = 0;
+ m.buf = talloc_array(mem_ctx, char, c.len);
+ if (m.buf == NULL) {
+ D_WARNING("talloc failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = dbwrap_traverse(members, getgr_unparse_members, &m, &count);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(m.buf);
+ DBG_NOTICE("dbwrap_traverse failed: %s\n", nt_errstr(status));
+ return status;
+ }
+ if (c.len > 0) {
+ m.buf[c.len - 1] = '\0';
+ }
+
+ *num_members = c.num;
+ *result = m.buf;
+ D_DEBUG("Returning %d member(s).\n", *num_members);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_idmap.c b/source3/winbindd/winbindd_idmap.c
new file mode 100644
index 0000000..3622112
--- /dev/null
+++ b/source3/winbindd/winbindd_idmap.c
@@ -0,0 +1,436 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Async helpers for blocking functions
+
+ Copyright (C) Volker Lendecke 2005
+ Copyright (C) Gerald Carter 2006
+ Copyright (C) Simo Sorce 2007
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+#include "passdb/lookup_sid.h"
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static struct winbindd_child *static_idmap_child = NULL;
+
+/*
+ * Map idmap ranges to domain names, taken from smb.conf. This is
+ * stored in the parent winbind and used to assemble xids2sids/sids2xids calls
+ * into per-idmap-domain chunks.
+ */
+static struct wb_parent_idmap_config static_parent_idmap_config;
+
+struct winbindd_child *idmap_child(void)
+{
+ return static_idmap_child;
+}
+
+bool is_idmap_child(const struct winbindd_child *child)
+{
+ if (child == static_idmap_child) {
+ return true;
+ }
+
+ return false;
+}
+
+pid_t idmap_child_pid(void)
+{
+ return static_idmap_child->pid;
+}
+
+struct dcerpc_binding_handle *idmap_child_handle(void)
+{
+ /*
+ * The caller needs to use wb_parent_idmap_setup_send/recv
+ * before talking to the idmap child!
+ */
+ SMB_ASSERT(static_parent_idmap_config.num_doms > 0);
+ return static_idmap_child->binding_handle;
+}
+
+static void init_idmap_child_done(struct tevent_req *subreq);
+
+NTSTATUS init_idmap_child(TALLOC_CTX *mem_ctx)
+{
+ struct tevent_req *subreq = NULL;
+
+ if (static_idmap_child != NULL) {
+ DBG_ERR("idmap child already allocated\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ static_idmap_child = talloc_zero(mem_ctx, struct winbindd_child);
+ if (static_idmap_child == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ subreq = wb_parent_idmap_setup_send(static_idmap_child,
+ global_event_context());
+ if (subreq == NULL) {
+ /*
+ * This is only an optimization, so we're free to
+ * to ignore errors
+ */
+ DBG_ERR("wb_parent_idmap_setup_send() failed\n");
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, init_idmap_child_done, NULL);
+ DBG_DEBUG("wb_parent_idmap_setup_send() started\n");
+ return NT_STATUS_OK;
+}
+
+static void init_idmap_child_done(struct tevent_req *subreq)
+{
+ const struct wb_parent_idmap_config *cfg = NULL;
+ NTSTATUS status;
+
+ status = wb_parent_idmap_setup_recv(subreq, &cfg);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * This is only an optimization, so we're free to
+ * to ignore errors
+ */
+ DBG_ERR("wb_parent_idmap_setup_recv() failed %s\n",
+ nt_errstr(status));
+ return;
+ }
+
+ DBG_DEBUG("wb_parent_idmap_setup_recv() finished\n");
+}
+
+struct wb_parent_idmap_setup_state {
+ struct tevent_context *ev;
+ struct wb_parent_idmap_config *cfg;
+ size_t dom_idx;
+};
+
+static void wb_parent_idmap_setup_cleanup(struct tevent_req *req,
+ enum tevent_req_state req_state)
+{
+ struct wb_parent_idmap_setup_state *state =
+ tevent_req_data(req,
+ struct wb_parent_idmap_setup_state);
+
+ if (req_state == TEVENT_REQ_DONE) {
+ state->cfg = NULL;
+ return;
+ }
+
+ if (state->cfg == NULL) {
+ return;
+ }
+
+ state->cfg->num_doms = 0;
+ state->cfg->initialized = false;
+ TALLOC_FREE(state->cfg->doms);
+ state->cfg = NULL;
+}
+
+static void wb_parent_idmap_setup_queue_wait_done(struct tevent_req *subreq);
+static bool wb_parent_idmap_setup_scan_config(const char *domname,
+ void *private_data);
+static void wb_parent_idmap_setup_lookupname_next(struct tevent_req *req);
+static void wb_parent_idmap_setup_lookupname_done(struct tevent_req *subreq);
+
+struct tevent_req *wb_parent_idmap_setup_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev)
+{
+ struct tevent_req *req = NULL;
+ struct wb_parent_idmap_setup_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct wb_parent_idmap_setup_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ *state = (struct wb_parent_idmap_setup_state) {
+ .ev = ev,
+ .cfg = &static_parent_idmap_config,
+ .dom_idx = 0,
+ };
+
+ if (state->cfg->initialized) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ if (state->cfg->queue == NULL) {
+ state->cfg->queue = tevent_queue_create(NULL,
+ "wb_parent_idmap_config_queue");
+ if (tevent_req_nomem(state->cfg->queue, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ subreq = tevent_queue_wait_send(state, state->ev, state->cfg->queue);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq,
+ wb_parent_idmap_setup_queue_wait_done,
+ req);
+
+ return req;
+}
+
+static void wb_parent_idmap_setup_queue_wait_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct wb_parent_idmap_setup_state *state =
+ tevent_req_data(req,
+ struct wb_parent_idmap_setup_state);
+ bool ok;
+
+ /*
+ * Note we don't call TALLOC_FREE(subreq) here in order to block the
+ * queue until tevent_req_received() in wb_parent_idmap_setup_recv()
+ * will destroy it implicitly.
+ */
+ ok = tevent_queue_wait_recv(subreq);
+ if (!ok) {
+ DBG_ERR("tevent_queue_wait_recv() failed\n");
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ if (state->cfg->num_doms != 0) {
+ /*
+ * If we're not the first one we're done.
+ */
+ tevent_req_done(req);
+ return;
+ }
+
+ /*
+ * From this point we start changing state->cfg,
+ * which is &static_parent_idmap_config,
+ * so we better setup a cleanup function
+ * to undo the changes on failure.
+ */
+ tevent_req_set_cleanup_fn(req, wb_parent_idmap_setup_cleanup);
+
+ /*
+ * Put the passdb idmap domain first. We always need to try
+ * there first.
+ */
+ state->cfg->doms = talloc_zero_array(NULL,
+ struct wb_parent_idmap_config_dom,
+ 1);
+ if (tevent_req_nomem(state->cfg->doms, req)) {
+ return;
+ }
+ state->cfg->doms[0].low_id = 0;
+ state->cfg->doms[0].high_id = UINT_MAX;
+ state->cfg->doms[0].name = talloc_strdup(state->cfg->doms,
+ get_global_sam_name());
+ if (tevent_req_nomem(state->cfg->doms[0].name, req)) {
+ return;
+ }
+ state->cfg->num_doms += 1;
+
+ lp_scan_idmap_domains(wb_parent_idmap_setup_scan_config, req);
+ if (!tevent_req_is_in_progress(req)) {
+ return;
+ }
+
+ wb_parent_idmap_setup_lookupname_next(req);
+}
+
+static bool wb_parent_idmap_setup_scan_config(const char *domname,
+ void *private_data)
+{
+ struct tevent_req *req =
+ talloc_get_type_abort(private_data,
+ struct tevent_req);
+ struct wb_parent_idmap_setup_state *state =
+ tevent_req_data(req,
+ struct wb_parent_idmap_setup_state);
+ struct wb_parent_idmap_config_dom *map = NULL;
+ size_t i;
+ const char *range;
+ unsigned low_id, high_id;
+ int ret;
+
+ range = idmap_config_const_string(domname, "range", NULL);
+ if (range == NULL) {
+ DBG_DEBUG("No range for domain %s found\n", domname);
+ return false;
+ }
+
+ ret = sscanf(range, "%u - %u", &low_id, &high_id);
+ if (ret != 2) {
+ DBG_DEBUG("Invalid range spec \"%s\" for domain %s\n",
+ range, domname);
+ return false;
+ }
+
+ if (low_id > high_id) {
+ DBG_DEBUG("Invalid range %u - %u for domain %s\n",
+ low_id, high_id, domname);
+ return false;
+ }
+
+ for (i=0; i<state->cfg->num_doms; i++) {
+ if (strequal(domname, state->cfg->doms[i].name)) {
+ map = &state->cfg->doms[i];
+ break;
+ }
+ }
+
+ if (map == NULL) {
+ struct wb_parent_idmap_config_dom *tmp;
+ char *name;
+
+ name = talloc_strdup(state, domname);
+ if (name == NULL) {
+ DBG_ERR("talloc failed\n");
+ return false;
+ }
+
+ tmp = talloc_realloc(
+ NULL, state->cfg->doms, struct wb_parent_idmap_config_dom,
+ state->cfg->num_doms+1);
+ if (tmp == NULL) {
+ DBG_ERR("talloc failed\n");
+ return false;
+ }
+ state->cfg->doms = tmp;
+
+ map = &state->cfg->doms[state->cfg->num_doms];
+ state->cfg->num_doms += 1;
+ ZERO_STRUCTP(map);
+ map->name = talloc_move(state->cfg->doms, &name);
+ }
+
+ map->low_id = low_id;
+ map->high_id = high_id;
+
+ return false;
+}
+
+static void wb_parent_idmap_setup_lookupname_next(struct tevent_req *req)
+{
+ struct wb_parent_idmap_setup_state *state =
+ tevent_req_data(req,
+ struct wb_parent_idmap_setup_state);
+ struct wb_parent_idmap_config_dom *dom =
+ &state->cfg->doms[state->dom_idx];
+ struct tevent_req *subreq = NULL;
+
+ next_domain:
+ if (state->dom_idx == state->cfg->num_doms) {
+ /*
+ * We're done, so start the idmap child
+ */
+ setup_child(NULL, static_idmap_child, "log.winbindd", "idmap");
+ static_parent_idmap_config.initialized = true;
+ tevent_req_done(req);
+ return;
+ }
+
+ if (strequal(dom->name, "*")) {
+ state->dom_idx++;
+ goto next_domain;
+ }
+
+ subreq = wb_lookupname_send(state,
+ state->ev,
+ dom->name,
+ dom->name,
+ "",
+ LOOKUP_NAME_NO_NSS);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ wb_parent_idmap_setup_lookupname_done,
+ req);
+}
+
+static void wb_parent_idmap_setup_lookupname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req =
+ tevent_req_callback_data(subreq,
+ struct tevent_req);
+ struct wb_parent_idmap_setup_state *state =
+ tevent_req_data(req,
+ struct wb_parent_idmap_setup_state);
+ struct wb_parent_idmap_config_dom *dom =
+ &state->cfg->doms[state->dom_idx];
+ enum lsa_SidType type;
+ NTSTATUS status;
+
+ status = wb_lookupname_recv(subreq, &dom->sid, &type);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Lookup domain name '%s' failed '%s'\n",
+ dom->name,
+ nt_errstr(status));
+
+ state->dom_idx++;
+ wb_parent_idmap_setup_lookupname_next(req);
+ return;
+ }
+
+ if (type != SID_NAME_DOMAIN) {
+ struct dom_sid_buf buf;
+
+ DBG_ERR("SID %s for idmap domain name '%s' "
+ "not a domain SID\n",
+ dom_sid_str_buf(&dom->sid, &buf),
+ dom->name);
+
+ ZERO_STRUCT(dom->sid);
+ }
+
+ state->dom_idx++;
+ wb_parent_idmap_setup_lookupname_next(req);
+
+ return;
+}
+
+NTSTATUS wb_parent_idmap_setup_recv(struct tevent_req *req,
+ const struct wb_parent_idmap_config **_cfg)
+{
+ const struct wb_parent_idmap_config *cfg = &static_parent_idmap_config;
+ NTSTATUS status;
+
+ *_cfg = NULL;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ tevent_req_received(req);
+ return status;
+ }
+
+ /*
+ * Note state->cfg is already set to NULL by
+ * wb_parent_idmap_setup_cleanup()
+ */
+ *_cfg = cfg;
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_irpc.c b/source3/winbindd/winbindd_irpc.c
new file mode 100644
index 0000000..f66d797
--- /dev/null
+++ b/source3/winbindd/winbindd_irpc.c
@@ -0,0 +1,891 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of commands submitted over IRPC
+ Copyright (C) Volker Lendecke 2009
+ Copyright (C) Guenther Deschner 2009
+ Copyright (C) Andrew Bartlett 2014
+ Copyright (C) Andrew Tridgell 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "source4/lib/messaging/irpc.h"
+#include "librpc/gen_ndr/ndr_winbind.h"
+#include "librpc/gen_ndr/ndr_lsa.h"
+#include "librpc/gen_ndr/ndr_lsa_c.h"
+#include "libcli/security/dom_sid.h"
+#include "passdb/lookup_sid.h" /* only for LOOKUP_NAME_NO_NSS flag */
+#include "librpc/gen_ndr/ndr_irpc.h"
+#include "librpc/gen_ndr/ndr_netlogon.h"
+#include "lib/global_contexts.h"
+#include "lib/param/param.h"
+#include "messages.h"
+
+struct imessaging_context *winbind_imessaging_context(void)
+{
+ static struct imessaging_context *msg = NULL;
+ struct messaging_context *msg_ctx;
+ struct server_id myself;
+ struct loadparm_context *lp_ctx;
+
+ if (msg != NULL) {
+ return msg;
+ }
+
+ msg_ctx = global_messaging_context();
+ if (msg_ctx == NULL) {
+ smb_panic("global_messaging_context failed\n");
+ }
+ myself = messaging_server_id(msg_ctx);
+
+ lp_ctx = loadparm_init_s3(NULL, loadparm_s3_helpers());
+ if (lp_ctx == NULL) {
+ smb_panic("Could not load smb.conf to init winbindd's imessaging context.\n");
+ }
+
+ /*
+ * Note we MUST use the NULL context here, not the autofree context,
+ * to avoid side effects in forked children exiting.
+ */
+ msg = imessaging_init(NULL, lp_ctx, myself, global_event_context());
+ talloc_unlink(NULL, lp_ctx);
+
+ if (msg == NULL) {
+ smb_panic("Could not init winbindd's messaging context.\n");
+ }
+ return msg;
+}
+
+struct wb_irpc_forward_state {
+ struct irpc_message *msg;
+ const char *opname;
+ struct dcesrv_call_state *dce_call;
+};
+
+/*
+ called when the forwarded rpc request is finished
+ */
+static void wb_irpc_forward_callback(struct tevent_req *subreq)
+{
+ struct wb_irpc_forward_state *st =
+ tevent_req_callback_data(subreq,
+ struct wb_irpc_forward_state);
+ const char *opname = st->opname;
+ NTSTATUS status;
+
+ status = dcerpc_binding_handle_call_recv(subreq);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RPC callback failed for %s - %s\n",
+ opname, nt_errstr(status)));
+ irpc_send_reply(st->msg, status);
+ return;
+ }
+
+ irpc_send_reply(st->msg, status);
+}
+
+
+
+/**
+ * Forward a RPC call using IRPC to another task
+ */
+
+static NTSTATUS wb_irpc_forward_rpc_call(struct irpc_message *msg, TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ void *r, uint32_t callid,
+ const char *opname,
+ struct winbindd_domain *domain,
+ uint32_t timeout)
+{
+ struct wb_irpc_forward_state *st;
+ struct dcerpc_binding_handle *binding_handle;
+ struct tevent_req *subreq;
+
+ st = talloc(mem_ctx, struct wb_irpc_forward_state);
+ if (st == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ st->msg = msg;
+ st->opname = opname;
+
+ binding_handle = dom_child_handle(domain);
+ if (binding_handle == NULL) {
+ DEBUG(0,("%s: Failed to forward request to winbind handler for %s\n",
+ opname, domain->name));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* reset timeout for the handle */
+ dcerpc_binding_handle_set_timeout(binding_handle, timeout);
+
+ /* forward the call */
+ subreq = dcerpc_binding_handle_call_send(st, ev,
+ binding_handle,
+ NULL, &ndr_table_winbind,
+ callid,
+ msg, r);
+ if (subreq == NULL) {
+ DEBUG(0,("%s: Failed to forward request to winbind handler for %s\n",
+ opname, domain->name));
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* mark the request as replied async */
+ msg->defer_reply = true;
+
+ /* setup the callback */
+ tevent_req_set_callback(subreq, wb_irpc_forward_callback, st);
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS wb_irpc_DsrUpdateReadOnlyServerDnsRecords(struct irpc_message *msg,
+ struct winbind_DsrUpdateReadOnlyServerDnsRecords *req)
+{
+ struct winbindd_domain *domain = find_our_domain();
+ if (domain == NULL) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ DEBUG(5, ("wb_irpc_DsrUpdateReadOnlyServerDnsRecords called\n"));
+
+ return wb_irpc_forward_rpc_call(msg, msg,
+ global_event_context(),
+ req, NDR_WINBIND_DSRUPDATEREADONLYSERVERDNSRECORDS,
+ "winbind_DsrUpdateReadOnlyServerDnsRecords",
+ domain, IRPC_CALL_TIMEOUT);
+}
+
+static NTSTATUS wb_irpc_SamLogon(struct irpc_message *msg,
+ struct winbind_SamLogon *req)
+{
+ struct winbindd_domain *domain;
+ struct netr_IdentityInfo *identity_info;
+ const char *target_domain_name = NULL;
+ const char *account_name = NULL;
+
+ /*
+ * Make sure we start with authoritative=true,
+ * it will only set to false if we don't know the
+ * domain.
+ */
+ req->out.authoritative = true;
+
+ switch (req->in.logon_level) {
+ case NetlogonInteractiveInformation:
+ case NetlogonServiceInformation:
+ case NetlogonInteractiveTransitiveInformation:
+ case NetlogonServiceTransitiveInformation:
+ if (req->in.logon.password == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+ identity_info = &req->in.logon.password->identity_info;
+ break;
+
+ case NetlogonNetworkInformation:
+ case NetlogonNetworkTransitiveInformation:
+ if (req->in.logon.network == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ identity_info = &req->in.logon.network->identity_info;
+ break;
+
+ case NetlogonGenericInformation:
+ if (req->in.logon.generic == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ identity_info = &req->in.logon.generic->identity_info;
+ break;
+
+ default:
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ target_domain_name = identity_info->domain_name.string;
+ if (target_domain_name == NULL) {
+ target_domain_name = "";
+ }
+
+ account_name = identity_info->account_name.string;
+ if (account_name == NULL) {
+ account_name = "";
+ }
+
+ if (IS_DC && target_domain_name[0] == '\0') {
+ const char *p = NULL;
+
+ p = strchr_m(account_name, '@');
+ if (p != NULL) {
+ target_domain_name = p + 1;
+ }
+ }
+
+ if (IS_DC && target_domain_name[0] == '\0') {
+ DBG_ERR("target_domain[%s] account[%s]\n",
+ target_domain_name, account_name);
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ domain = find_auth_domain(0, target_domain_name);
+ if (domain == NULL) {
+ DBG_INFO("target_domain[%s] for account[%s] not known\n",
+ target_domain_name, account_name);
+ req->out.result = NT_STATUS_NO_SUCH_USER;
+ req->out.authoritative = 0;
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(5, ("wb_irpc_SamLogon called\n"));
+
+ return wb_irpc_forward_rpc_call(msg, msg,
+ global_event_context(),
+ req, NDR_WINBIND_SAMLOGON,
+ "winbind_SamLogon",
+ domain, IRPC_CALL_TIMEOUT);
+}
+
+static NTSTATUS wb_irpc_LogonControl(struct irpc_message *msg,
+ struct winbind_LogonControl *req)
+{
+ TALLOC_CTX *frame = talloc_stackframe();
+ char *domain_name = NULL;
+ struct winbindd_domain *domain = NULL;
+
+ DEBUG(5, ("wb_irpc_LogonControl called\n"));
+
+ switch (req->in.function_code) {
+ case NETLOGON_CONTROL_REDISCOVER:
+ case NETLOGON_CONTROL_TC_QUERY:
+ case NETLOGON_CONTROL_CHANGE_PASSWORD:
+ case NETLOGON_CONTROL_TC_VERIFY:
+ if (req->in.data->domain == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ domain_name = talloc_strdup(frame, req->in.data->domain);
+ if (domain_name == NULL) {
+ req->out.result = WERR_NOT_ENOUGH_MEMORY;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ break;
+ default:
+ TALLOC_FREE(frame);
+ return NT_STATUS_NOT_IMPLEMENTED;
+ }
+
+ if (req->in.function_code == NETLOGON_CONTROL_REDISCOVER) {
+ char *p = NULL;
+
+ /*
+ * NETLOGON_CONTROL_REDISCOVER
+ * gets an optional \dcname appended to the domain name
+ */
+ p = strchr_m(domain_name, '\\');
+ if (p != NULL) {
+ *p = '\0';
+ }
+ }
+
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ req->out.result = WERR_NO_SUCH_DOMAIN;
+ TALLOC_FREE(frame);
+ return NT_STATUS_OK;
+ }
+
+ TALLOC_FREE(frame);
+ return wb_irpc_forward_rpc_call(msg, msg,
+ global_event_context(),
+ req, NDR_WINBIND_LOGONCONTROL,
+ "winbind_LogonControl",
+ domain, 45 /* timeout */);
+}
+
+static NTSTATUS wb_irpc_GetForestTrustInformation(struct irpc_message *msg,
+ struct winbind_GetForestTrustInformation *req)
+{
+ struct winbindd_domain *domain = NULL;
+
+ if (req->in.trusted_domain_name == NULL) {
+ req->out.result = WERR_NO_SUCH_DOMAIN;
+ return NT_STATUS_OK;
+ }
+
+ domain = find_trust_from_name_noinit(req->in.trusted_domain_name);
+ if (domain == NULL) {
+ req->out.result = WERR_NO_SUCH_DOMAIN;
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * checking for domain->internal and domain->primary
+ * makes sure we only do some work when running as DC.
+ */
+
+ if (domain->internal) {
+ req->out.result = WERR_NO_SUCH_DOMAIN;
+ return NT_STATUS_OK;
+ }
+
+ if (domain->primary) {
+ req->out.result = WERR_NO_SUCH_DOMAIN;
+ return NT_STATUS_OK;
+ }
+
+ DEBUG(5, ("wb_irpc_GetForestTrustInformation called\n"));
+
+ return wb_irpc_forward_rpc_call(msg, msg,
+ global_event_context(),
+ req, NDR_WINBIND_GETFORESTTRUSTINFORMATION,
+ "winbind_GetForestTrustInformation",
+ domain, 45 /* timeout */);
+}
+
+static NTSTATUS wb_irpc_SendToSam(struct irpc_message *msg,
+ struct winbind_SendToSam *req)
+{
+ /* TODO make sure that it is RWDC */
+ struct winbindd_domain *domain = find_our_domain();
+ if (domain == NULL) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ DEBUG(5, ("wb_irpc_SendToSam called\n"));
+
+ return wb_irpc_forward_rpc_call(msg, msg,
+ global_event_context(),
+ req, NDR_WINBIND_SENDTOSAM,
+ "winbind_SendToSam",
+ domain, IRPC_CALL_TIMEOUT);
+}
+
+struct wb_irpc_lsa_LookupSids3_state {
+ struct irpc_message *msg;
+ struct lsa_LookupSids3 *req;
+};
+
+static void wb_irpc_lsa_LookupSids3_done(struct tevent_req *subreq);
+
+static NTSTATUS wb_irpc_lsa_LookupSids3_call(struct irpc_message *msg,
+ struct lsa_LookupSids3 *req)
+{
+ struct wb_irpc_lsa_LookupSids3_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ struct dom_sid *sids = NULL;
+ uint32_t i;
+
+ state = talloc_zero(msg, struct wb_irpc_lsa_LookupSids3_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->msg = msg;
+ state->req = req;
+
+ state->req->out.domains = talloc_zero(state->msg,
+ struct lsa_RefDomainList *);
+ if (state->req->out.domains == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->req->out.names = talloc_zero(state->msg,
+ struct lsa_TransNameArray2);
+ if (state->req->out.names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->req->out.count = talloc_zero(state->msg, uint32_t);
+ if (state->req->out.count == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->req->out.names->names = talloc_zero_array(state->msg,
+ struct lsa_TranslatedName2,
+ req->in.sids->num_sids);
+ if (state->req->out.names->names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sids = talloc_zero_array(state, struct dom_sid,
+ req->in.sids->num_sids);
+ if (sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < req->in.sids->num_sids; i++) {
+ if (req->in.sids->sids[i].sid == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ sids[i] = *req->in.sids->sids[i].sid;
+ }
+
+ subreq = wb_lookupsids_send(msg,
+ global_event_context(),
+ sids, req->in.sids->num_sids);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq, wb_irpc_lsa_LookupSids3_done, state);
+ msg->defer_reply = true;
+
+ return NT_STATUS_OK;
+}
+
+static void wb_irpc_lsa_LookupSids3_done(struct tevent_req *subreq)
+{
+ struct wb_irpc_lsa_LookupSids3_state *state =
+ tevent_req_callback_data(subreq,
+ struct wb_irpc_lsa_LookupSids3_state);
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransNameArray *names = NULL;
+ NTSTATUS status;
+ uint32_t i;
+
+ status = wb_lookupsids_recv(subreq, state->msg,
+ &domains, &names);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RPC callback failed for %s - %s\n",
+ __func__, nt_errstr(status)));
+ irpc_send_reply(state->msg, status);
+ return;
+ }
+
+ if (names->count > state->req->in.sids->num_sids) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ DEBUG(0,("RPC callback failed for %s - %s\n",
+ __func__, nt_errstr(status)));
+ irpc_send_reply(state->msg, status);
+ return;
+ }
+
+ *state->req->out.domains = domains;
+ for (i = 0; i < names->count; i++) {
+ struct lsa_TranslatedName2 *n2 =
+ &state->req->out.names->names[i];
+
+ n2->sid_type = names->names[i].sid_type;
+ n2->name = names->names[i].name;
+ n2->sid_index = names->names[i].sid_index;
+ n2->unknown = 0;
+
+ if (n2->sid_type != SID_NAME_UNKNOWN) {
+ (*state->req->out.count)++;
+ }
+ }
+ state->req->out.names->count = names->count;
+
+ if (*state->req->out.count == 0) {
+ state->req->out.result = NT_STATUS_NONE_MAPPED;
+ } else if (*state->req->out.count != names->count) {
+ state->req->out.result = NT_STATUS_SOME_NOT_MAPPED;
+ } else {
+ state->req->out.result = NT_STATUS_OK;
+ }
+
+ irpc_send_reply(state->msg, NT_STATUS_OK);
+ return;
+}
+
+struct wb_irpc_lsa_LookupNames4_name {
+ void *state;
+ uint32_t idx;
+ const char *namespace;
+ const char *domain;
+ char *name;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ struct dom_sid *authority_sid;
+};
+
+struct wb_irpc_lsa_LookupNames4_state {
+ struct irpc_message *msg;
+ struct lsa_LookupNames4 *req;
+ struct wb_irpc_lsa_LookupNames4_name *names;
+ uint32_t num_pending;
+ uint32_t num_domain_sids;
+ struct dom_sid *domain_sids;
+};
+
+static void wb_irpc_lsa_LookupNames4_done(struct tevent_req *subreq);
+
+static NTSTATUS wb_irpc_lsa_LookupNames4_call(struct irpc_message *msg,
+ struct lsa_LookupNames4 *req)
+{
+ struct wb_irpc_lsa_LookupNames4_state *state = NULL;
+ struct tevent_req *subreq = NULL;
+ uint32_t i;
+
+
+ state = talloc_zero(msg, struct wb_irpc_lsa_LookupNames4_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->msg = msg;
+ state->req = req;
+
+ state->req->out.domains = talloc_zero(state->msg,
+ struct lsa_RefDomainList *);
+ if (state->req->out.domains == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->req->out.sids = talloc_zero(state->msg,
+ struct lsa_TransSidArray3);
+ if (state->req->out.sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ state->req->out.count = talloc_zero(state->msg, uint32_t);
+ if (state->req->out.count == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->req->out.sids->sids = talloc_zero_array(state->msg,
+ struct lsa_TranslatedSid3,
+ req->in.num_names);
+ if (state->req->out.sids->sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->names = talloc_zero_array(state,
+ struct wb_irpc_lsa_LookupNames4_name,
+ req->in.num_names);
+ if (state->names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < req->in.num_names; i++) {
+ struct wb_irpc_lsa_LookupNames4_name *nstate =
+ &state->names[i];
+ char *p = NULL;
+
+ if (req->in.names[i].string == NULL) {
+ DBG_ERR("%s: name[%s] NT_STATUS_REQUEST_NOT_ACCEPTED.\n",
+ __location__, req->in.names[i].string);
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+ nstate->state = state;
+ nstate->idx = i;
+ nstate->name = talloc_strdup(state->names,
+ req->in.names[i].string);
+ if (nstate->name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ nstate->type = SID_NAME_UNKNOWN;
+
+ /* cope with the name being a fully qualified name */
+ p = strchr(nstate->name, '\\');
+ if (p != NULL) {
+ *p = 0;
+ nstate->domain = nstate->name;
+ nstate->namespace = nstate->domain;
+ nstate->name = p+1;
+ } else if ((p = strchr(nstate->name, '@')) != NULL) {
+ /* upn */
+ nstate->domain = "";
+ nstate->namespace = p + 1;
+ } else {
+ /*
+ * TODO: select the domain based on
+ * req->in.level and req->in.client_revision
+ *
+ * For now we don't allow this.
+ */
+ DBG_ERR("%s: name[%s] NT_STATUS_REQUEST_NOT_ACCEPTED.\n",
+ __location__, nstate->name);
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ subreq = wb_lookupname_send(msg,
+ global_event_context(),
+ nstate->namespace,
+ nstate->domain,
+ nstate->name,
+ LOOKUP_NAME_NO_NSS);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ tevent_req_set_callback(subreq,
+ wb_irpc_lsa_LookupNames4_done,
+ nstate);
+ state->num_pending++;
+ }
+
+ msg->defer_reply = true;
+
+ return NT_STATUS_OK;
+}
+
+static void wb_irpc_lsa_LookupNames4_domains_done(struct tevent_req *subreq);
+
+static void wb_irpc_lsa_LookupNames4_done(struct tevent_req *subreq)
+{
+ struct wb_irpc_lsa_LookupNames4_name *nstate =
+ (struct wb_irpc_lsa_LookupNames4_name *)
+ tevent_req_callback_data_void(subreq);
+ struct wb_irpc_lsa_LookupNames4_state *state =
+ talloc_get_type_abort(nstate->state,
+ struct wb_irpc_lsa_LookupNames4_state);
+ struct dom_sid_buf buf;
+ NTSTATUS status;
+
+ SMB_ASSERT(state->num_pending > 0);
+ state->num_pending--;
+ status = wb_lookupname_recv(subreq, &nstate->sid, &nstate->type);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RPC callback failed for %s - %s\n",
+ __func__, nt_errstr(status)));
+ irpc_send_reply(state->msg, status);
+ return;
+ }
+
+ status = dom_sid_split_rid(state, &nstate->sid,
+ &nstate->authority_sid, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("dom_sid_split_rid(%s) failed - %s\n",
+ dom_sid_str_buf(&nstate->sid, &buf),
+ nt_errstr(status));
+ irpc_send_reply(state->msg, status);
+ return;
+ }
+
+ status = add_sid_to_array_unique(state,
+ nstate->authority_sid,
+ &state->domain_sids,
+ &state->num_domain_sids);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("add_sid_to_array_unique(%s) failed - %s\n",
+ dom_sid_str_buf(nstate->authority_sid, &buf),
+ nt_errstr(status));
+ irpc_send_reply(state->msg, status);
+ return;
+ }
+
+ if (state->num_pending > 0) {
+ /*
+ * wait for more...
+ */
+ return;
+ }
+
+ /*
+ * Now resolve all domains back to a name
+ * to get a good lsa_RefDomainList
+ */
+ subreq = wb_lookupsids_send(state,
+ global_event_context(),
+ state->domain_sids,
+ state->num_domain_sids);
+ if (subreq == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ DBG_ERR("wb_lookupsids_send - %s\n",
+ nt_errstr(status));
+ irpc_send_reply(state->msg, status);
+ return;
+ }
+ tevent_req_set_callback(subreq,
+ wb_irpc_lsa_LookupNames4_domains_done,
+ state);
+
+ return;
+}
+
+static void wb_irpc_lsa_LookupNames4_domains_done(struct tevent_req *subreq)
+{
+ struct wb_irpc_lsa_LookupNames4_state *state =
+ tevent_req_callback_data(subreq,
+ struct wb_irpc_lsa_LookupNames4_state);
+ struct lsa_RefDomainList *domains = NULL;
+ struct lsa_TransNameArray *names = NULL;
+ NTSTATUS status;
+ uint32_t i;
+
+ status = wb_lookupsids_recv(subreq, state->msg,
+ &domains, &names);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(0,("RPC callback failed for %s - %s\n",
+ __func__, nt_errstr(status)));
+ irpc_send_reply(state->msg, status);
+ return;
+ }
+
+ *state->req->out.domains = domains;
+ for (i = 0; i < state->req->in.num_names; i++) {
+ struct wb_irpc_lsa_LookupNames4_name *nstate =
+ &state->names[i];
+ struct lsa_TranslatedSid3 *s3 =
+ &state->req->out.sids->sids[i];
+ uint32_t di;
+
+ s3->sid_type = nstate->type;
+ if (s3->sid_type != SID_NAME_UNKNOWN) {
+ s3->sid = &nstate->sid;
+ } else {
+ s3->sid = NULL;
+ }
+ s3->sid_index = UINT32_MAX;
+ for (di = 0; di < domains->count; di++) {
+ bool match;
+
+ if (domains->domains[di].sid == NULL) {
+ continue;
+ }
+
+ match = dom_sid_equal(nstate->authority_sid,
+ domains->domains[di].sid);
+ if (match) {
+ s3->sid_index = di;
+ break;
+ }
+ }
+ if (s3->sid_type != SID_NAME_UNKNOWN) {
+ (*state->req->out.count)++;
+ }
+ }
+ state->req->out.sids->count = state->req->in.num_names;
+
+ if (*state->req->out.count == 0) {
+ state->req->out.result = NT_STATUS_NONE_MAPPED;
+ } else if (*state->req->out.count != state->req->in.num_names) {
+ state->req->out.result = NT_STATUS_SOME_NOT_MAPPED;
+ } else {
+ state->req->out.result = NT_STATUS_OK;
+ }
+
+ irpc_send_reply(state->msg, NT_STATUS_OK);
+ return;
+}
+
+struct wb_irpc_GetDCName_state {
+ struct irpc_message *msg;
+ struct wbint_DsGetDcName *req;
+};
+
+static void wb_irpc_GetDCName_done(struct tevent_req *subreq);
+
+static NTSTATUS wb_irpc_GetDCName(struct irpc_message *msg,
+ struct wbint_DsGetDcName *req)
+{
+
+ struct tevent_req *subreq = NULL;
+ struct wb_irpc_GetDCName_state *state = NULL;
+
+ state = talloc_zero(msg, struct wb_irpc_GetDCName_state);
+ if (state == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ state->msg = msg;
+ state->req = req;
+
+ subreq = wb_dsgetdcname_send(msg,
+ global_event_context(),
+ req->in.domain_name,
+ req->in.domain_guid,
+ req->in.site_name,
+ req->in.flags);
+ if (subreq == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ tevent_req_set_callback(subreq,
+ wb_irpc_GetDCName_done,
+ state);
+
+ msg->defer_reply = true;
+
+ return NT_STATUS_OK;
+}
+
+static void wb_irpc_GetDCName_done(struct tevent_req *subreq)
+{
+ struct wb_irpc_GetDCName_state *state = tevent_req_callback_data(
+ subreq, struct wb_irpc_GetDCName_state);
+ NTSTATUS status;
+
+ status = wb_dsgetdcname_recv(subreq, state->msg,
+ state->req->out.dc_info);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_INFO("RPC callback failed for %s - %s\n", "DSGETDCNAME",
+ nt_errstr(status));
+ }
+
+ state->req->out.result = status;
+
+ irpc_send_reply(state->msg, NT_STATUS_OK);
+}
+
+NTSTATUS wb_irpc_register(void)
+{
+ NTSTATUS status;
+
+ status = IRPC_REGISTER(winbind_imessaging_context(), winbind, WINBIND_DSRUPDATEREADONLYSERVERDNSRECORDS,
+ wb_irpc_DsrUpdateReadOnlyServerDnsRecords, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = IRPC_REGISTER(winbind_imessaging_context(), winbind, WINBIND_SAMLOGON,
+ wb_irpc_SamLogon, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = IRPC_REGISTER(winbind_imessaging_context(), winbind,
+ WINBIND_LOGONCONTROL,
+ wb_irpc_LogonControl, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = IRPC_REGISTER(winbind_imessaging_context(), winbind,
+ WINBIND_GETFORESTTRUSTINFORMATION,
+ wb_irpc_GetForestTrustInformation, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = IRPC_REGISTER(winbind_imessaging_context(), winbind, WINBIND_SENDTOSAM,
+ wb_irpc_SendToSam, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = IRPC_REGISTER(winbind_imessaging_context(),
+ lsarpc, LSA_LOOKUPSIDS3,
+ wb_irpc_lsa_LookupSids3_call, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = IRPC_REGISTER(winbind_imessaging_context(),
+ lsarpc, LSA_LOOKUPNAMES4,
+ wb_irpc_lsa_LookupNames4_call, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ status = IRPC_REGISTER(winbind_imessaging_context(),
+ winbind, WBINT_DSGETDCNAME,
+ wb_irpc_GetDCName, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_list_groups.c b/source3/winbindd/winbindd_list_groups.c
new file mode 100644
index 0000000..272c638
--- /dev/null
+++ b/source3/winbindd/winbindd_list_groups.c
@@ -0,0 +1,233 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_LIST_GROUPS
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "util/debug.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_list_groups_domstate {
+ struct tevent_req *subreq;
+ struct winbindd_domain *domain;
+ struct wbint_Principals groups;
+};
+
+struct winbindd_list_groups_state {
+ uint32_t num_received;
+ /* All domains */
+ uint32_t num_domains;
+ struct winbindd_list_groups_domstate *domains;
+};
+
+static void winbindd_list_groups_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_list_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct winbindd_list_groups_state *state;
+ struct winbindd_domain *domain;
+ uint32_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_list_groups_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_NOTICE("[%s (%u)] Winbind external command LIST_GROUPS start.\n"
+ "WBFLAG_FROM_NSS is %s, winbind enum groups is %d.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->wb_flags & WBFLAG_FROM_NSS ? "Set" : "Unset",
+ lp_winbind_enum_groups());
+
+ if (request->wb_flags & WBFLAG_FROM_NSS && !lp_winbind_enum_groups()) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Ensure null termination */
+ request->domain_name[sizeof(request->domain_name)-1]='\0';
+
+ if (request->domain_name[0] != '\0') {
+ state->num_domains = 1;
+ D_DEBUG("List groups for domain %s.\n", request->domain_name);
+ } else {
+ state->num_domains = 0;
+ for (domain = domain_list(); domain; domain = domain->next) {
+ state->num_domains += 1;
+ }
+ D_DEBUG("List groups for %"PRIu32" domain(s).\n", state->num_domains);
+ }
+
+ state->domains = talloc_array(state,
+ struct winbindd_list_groups_domstate,
+ state->num_domains);
+ if (tevent_req_nomem(state->domains, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (request->domain_name[0] != '\0') {
+ ZERO_STRUCT(state->domains[0].groups);
+
+ state->domains[0].domain = find_domain_from_name_noinit(
+ request->domain_name);
+ if (state->domains[0].domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ i = 0;
+ for (domain = domain_list(); domain; domain = domain->next) {
+ ZERO_STRUCT(state->domains[i].groups);
+
+ state->domains[i].domain = domain;
+ i++;
+ }
+ }
+
+ for (i=0; i<state->num_domains; i++) {
+ struct winbindd_list_groups_domstate *d = &state->domains[i];
+
+ d->subreq = dcerpc_wbint_QueryGroupList_send(
+ state->domains, ev, dom_child_handle(d->domain),
+ &d->groups);
+ if (tevent_req_nomem(d->subreq, req)) {
+ TALLOC_FREE(state->domains);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(d->subreq, winbindd_list_groups_done,
+ req);
+ }
+ state->num_received = 0;
+ return req;
+}
+
+static void winbindd_list_groups_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_list_groups_state *state = tevent_req_data(
+ req, struct winbindd_list_groups_state);
+ NTSTATUS status, result;
+ uint32_t i;
+
+ status = dcerpc_wbint_QueryGroupList_recv(subreq, state->domains,
+ &result);
+
+ for (i=0; i<state->num_domains; i++) {
+ if (subreq == state->domains[i].subreq) {
+ break;
+ }
+ }
+ if (i < state->num_domains) {
+ struct winbindd_list_groups_domstate *d = &state->domains[i];
+
+ D_DEBUG("Domain %s returned %"PRIu32" groups\n", d->domain->name,
+ d->groups.num_principals);
+
+ d->subreq = NULL;
+
+ if (!NT_STATUS_IS_OK(status) || !NT_STATUS_IS_OK(result)) {
+ D_WARNING("list_groups for domain %s failed\n",
+ d->domain->name);
+ d->groups.num_principals = 0;
+ }
+ }
+
+ TALLOC_FREE(subreq);
+
+ state->num_received += 1;
+
+ if (state->num_received >= state->num_domains) {
+ tevent_req_done(req);
+ }
+}
+
+NTSTATUS winbindd_list_groups_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_list_groups_state *state = tevent_req_data(
+ req, struct winbindd_list_groups_state);
+ NTSTATUS status;
+ char *result;
+ uint32_t i, j, num_entries = 0;
+ size_t len;
+
+ D_NOTICE("Winbind external command LIST_GROUPS end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+
+ len = 0;
+ response->data.num_entries = 0;
+ for (i=0; i<state->num_domains; i++) {
+ struct winbindd_list_groups_domstate *d = &state->domains[i];
+
+ for (j=0; j<d->groups.num_principals; j++) {
+ const char *name;
+ name = fill_domain_username_talloc(response, d->domain->name,
+ d->groups.principals[j].name,
+ True);
+ if (name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ len += strlen(name)+1;
+ }
+ response->data.num_entries += d->groups.num_principals;
+ }
+
+ result = talloc_array(response, char, len+1);
+ if (result == 0) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ len = 0;
+ for (i=0; i<state->num_domains; i++) {
+ struct winbindd_list_groups_domstate *d = &state->domains[i];
+
+ for (j=0; j<d->groups.num_principals; j++) {
+ const char *name;
+ size_t this_len;
+ name = fill_domain_username_talloc(response, d->domain->name,
+ d->groups.principals[j].name,
+ True);
+ if (name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ this_len = strlen(name);
+ memcpy(result+len, name, this_len);
+ len += this_len;
+ result[len] = ',';
+ len += 1;
+ num_entries++;
+ }
+ }
+ result[len-1] = '\0';
+
+ response->data.num_entries = num_entries;
+ response->extra_data.data = result;
+ response->length += len;
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_list_users.c b/source3/winbindd/winbindd_list_users.c
new file mode 100644
index 0000000..8630672
--- /dev/null
+++ b/source3/winbindd/winbindd_list_users.c
@@ -0,0 +1,216 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_LIST_USERS
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/util/strv.h"
+
+struct winbindd_list_users_domstate {
+ struct tevent_req *subreq;
+ struct winbindd_domain *domain;
+ char *users;
+};
+
+struct winbindd_list_users_state {
+ size_t num_received;
+ /* All domains */
+ size_t num_domains;
+ struct winbindd_list_users_domstate *domains;
+};
+
+static void winbindd_list_users_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_list_users_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct winbindd_list_users_state *state;
+ struct winbindd_domain *domain;
+ size_t i;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_list_users_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_NOTICE("[%s (%u)] Winbind external command LIST_USERS start.\n"
+ "WBFLAG_FROM_NSS is %s, winbind enum users is %d.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->wb_flags & WBFLAG_FROM_NSS ? "Set" : "Unset",
+ lp_winbind_enum_users());
+
+ if (request->wb_flags & WBFLAG_FROM_NSS && !lp_winbind_enum_users()) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Ensure null termination */
+ request->domain_name[sizeof(request->domain_name)-1]='\0';
+
+ D_NOTICE("Listing users for domain %s\n", request->domain_name);
+ if (request->domain_name[0] != '\0') {
+ state->num_domains = 1;
+ } else {
+ state->num_domains = 0;
+ for (domain = domain_list(); domain; domain = domain->next) {
+ state->num_domains += 1;
+ }
+ }
+
+ state->domains = talloc_array(state,
+ struct winbindd_list_users_domstate,
+ state->num_domains);
+ if (tevent_req_nomem(state->domains, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (request->domain_name[0] != '\0') {
+ state->domains[0].domain = find_domain_from_name_noinit(
+ request->domain_name);
+ if (state->domains[0].domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ i = 0;
+ for (domain = domain_list(); domain; domain = domain->next) {
+ state->domains[i++].domain = domain;
+ }
+ }
+
+ for (i=0; i<state->num_domains; i++) {
+ struct winbindd_list_users_domstate *d = &state->domains[i];
+ /*
+ * Use "state" as a talloc memory context since it has type
+ * "struct tevent_req". This is needed to make tevent call depth
+ * tracking working as expected.
+ * After calling wb_query_user_list_send(), re-parent back to
+ * "state->domains" to make TALLOC_FREE(state->domains) working.
+ */
+ d->subreq = wb_query_user_list_send(state, ev, d->domain);
+ d->subreq = talloc_reparent(state, state->domains, d->subreq);
+ if (tevent_req_nomem(d->subreq, req)) {
+ TALLOC_FREE(state->domains);
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(d->subreq, winbindd_list_users_done,
+ req);
+ }
+ state->num_received = 0;
+ return req;
+}
+
+static void winbindd_list_users_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_list_users_state *state = tevent_req_data(
+ req, struct winbindd_list_users_state);
+ struct winbindd_list_users_domstate *d;
+ NTSTATUS status;
+ size_t i;
+
+ for (i=0; i<state->num_domains; i++) {
+ if (subreq == state->domains[i].subreq) {
+ break;
+ }
+ }
+ if (i == state->num_domains) {
+ tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
+ return;
+ }
+
+ d = &state->domains[i];
+
+ status = wb_query_user_list_recv(subreq, state->domains,
+ &d->users);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ /*
+ * Just skip this domain
+ */
+ d->users = NULL;
+ }
+
+ state->num_received += 1;
+
+ if (state->num_received >= state->num_domains) {
+ tevent_req_done(req);
+ }
+}
+
+NTSTATUS winbindd_list_users_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_list_users_state *state = tevent_req_data(
+ req, struct winbindd_list_users_state);
+ NTSTATUS status;
+ char *result;
+ size_t i, len;
+
+ D_NOTICE("Winbind external command LIST_USERS end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Failed with %s.\n", nt_errstr(status));
+ return status;
+ }
+
+ result = NULL;
+
+ for (i=0; i<state->num_domains; i++) {
+ struct winbindd_list_users_domstate *d = &state->domains[i];
+ int ret;
+
+ if (d->users == NULL) {
+ continue;
+ }
+
+ ret = strv_append(state, &result, d->users);
+ if (ret != 0) {
+ return map_nt_error_from_unix(ret);
+ }
+ }
+
+ len = talloc_get_size(result);
+
+ response->extra_data.data = talloc_steal(response, result);
+ response->length += len;
+ response->data.num_entries = 0;
+
+ if (result != NULL && len >= 1) {
+ len -= 1;
+ response->data.num_entries = 1;
+
+ for (i=0; i<len; i++) {
+ if (result[i] == '\0') {
+ result[i] = ',';
+ response->data.num_entries += 1;
+ }
+ }
+ }
+
+ D_NOTICE("Got %"PRIu32" user(s):\n%s\n",
+ response->data.num_entries,
+ result);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_locator.c b/source3/winbindd/winbindd_locator.c
new file mode 100644
index 0000000..c915bf2
--- /dev/null
+++ b/source3/winbindd/winbindd_locator.c
@@ -0,0 +1,56 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - miscellaneous other functions
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Andrew Bartlett 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+
+static struct winbindd_child *static_locator_child = NULL;
+
+struct winbindd_child *locator_child(void)
+{
+ return static_locator_child;
+}
+
+struct dcerpc_binding_handle *locator_child_handle(void)
+{
+ return static_locator_child->binding_handle;
+}
+
+NTSTATUS init_locator_child(TALLOC_CTX *mem_ctx)
+{
+ if (static_locator_child != NULL) {
+ DBG_ERR("locator child already allocated\n");
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ static_locator_child = talloc_zero(mem_ctx, struct winbindd_child);
+ if (static_locator_child == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ setup_child(NULL, static_locator_child, "log.winbindd", "locator");
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_lookupname.c b/source3/winbindd/winbindd_lookupname.c
new file mode 100644
index 0000000..f7af104
--- /dev/null
+++ b/source3/winbindd/winbindd_lookupname.c
@@ -0,0 +1,130 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_LOOKUPNAME
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libcli/security/dom_sid.h"
+
+struct winbindd_lookupname_state {
+ struct tevent_context *ev;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+};
+
+static void winbindd_lookupname_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_lookupname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_lookupname_state *state;
+ char *p = NULL;
+ const char *domname = NULL;
+ const char *name = NULL;
+ const char *namespace = NULL;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_lookupname_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_NOTICE("[%s (%u)] Winbind external command LOOKUPNAME start.\n",
+ cli->client_name,
+ (unsigned int)cli->pid);
+
+ state->ev = ev;
+
+ /* Ensure null termination */
+ request->data.name.dom_name[
+ sizeof(request->data.name.dom_name)-1]='\0';
+ request->data.name.name[sizeof(request->data.name.name)-1]='\0';
+
+ if (strlen(request->data.name.dom_name) == 0) {
+ /* cope with the name being a fully qualified name */
+ p = strstr(request->data.name.name, lp_winbind_separator());
+ if (p != NULL) {
+ *p = '\0';
+ domname = request->data.name.name;
+ namespace = domname;
+ name = p + 1;
+ } else {
+ p = strchr(request->data.name.name, '@');
+ if (p != NULL) {
+ /* upn */
+ namespace = p + 1;
+ } else {
+ namespace = "";
+ }
+ domname = "";
+ name = request->data.name.name;
+ }
+ } else {
+ domname = request->data.name.dom_name;
+ namespace = domname;
+ name = request->data.name.name;
+ }
+
+ D_NOTICE("lookupname %s%s%s\n", domname, lp_winbind_separator(), name);
+
+ subreq = wb_lookupname_send(state, ev, namespace, domname, name, 0);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_lookupname_done, req);
+ return req;
+}
+
+static void winbindd_lookupname_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_lookupname_state *state = tevent_req_data(
+ req, struct winbindd_lookupname_state);
+ NTSTATUS status;
+
+ status = wb_lookupname_recv(subreq, &state->sid, &state->type);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_lookupname_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_lookupname_state *state = tevent_req_data(
+ req, struct winbindd_lookupname_state);
+ NTSTATUS status;
+
+ D_NOTICE("Winbind external command LOOKUPNAME end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ struct dom_sid_buf buf;
+ D_WARNING("Could not convert SID %s, error is %s\n",
+ dom_sid_str_buf(&state->sid, &buf),
+ nt_errstr(status));
+ return status;
+ }
+ sid_to_fstring(response->data.sid.sid, &state->sid);
+ response->data.sid.type = state->type;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_lookuprids.c b/source3/winbindd/winbindd_lookuprids.c
new file mode 100644
index 0000000..fc8fa46
--- /dev/null
+++ b/source3/winbindd/winbindd_lookuprids.c
@@ -0,0 +1,200 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_LOOKUPRIDS
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "../libcli/security/security.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_lookuprids_state {
+ struct tevent_context *ev;
+ struct dom_sid domain_sid;
+ const char *domain_name;
+ struct wbint_RidArray rids;
+ struct wbint_Principals names;
+};
+
+static bool parse_ridlist(TALLOC_CTX *mem_ctx, char *ridstr,
+ uint32_t **prids, uint32_t *pnum_rids);
+
+static void winbindd_lookuprids_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_lookuprids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_lookuprids_state *state;
+ struct winbindd_domain *domain;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_lookuprids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ /* Ensure null termination */
+ request->data.sid[sizeof(request->data.sid)-1]='\0';
+
+ DEBUG(3, ("lookuprids (%s)\n", request->data.sid));
+
+ if (!string_to_sid(&state->domain_sid, request->data.sid)) {
+ DEBUG(5, ("%s not a SID\n", request->data.sid));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ domain = find_lookup_domain_from_sid(&state->domain_sid);
+ if (domain == NULL) {
+ struct dom_sid_buf buf;
+ DEBUG(5, ("Domain for sid %s not found\n",
+ dom_sid_str_buf(&state->domain_sid, &buf)));
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+
+ if (request->extra_data.data[request->extra_len-1] != '\0') {
+ DEBUG(5, ("extra_data not 0-terminated\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (!parse_ridlist(state, request->extra_data.data,
+ &state->rids.rids, &state->rids.num_rids)) {
+ DEBUG(5, ("parse_ridlist failed\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_LookupRids_send(
+ state, ev, dom_child_handle(domain), &state->domain_sid,
+ &state->rids, &state->domain_name, &state->names);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_lookuprids_done, req);
+ return req;
+}
+
+static void winbindd_lookuprids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_lookuprids_state *state = tevent_req_data(
+ req, struct winbindd_lookuprids_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_LookupRids_recv(subreq, state, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_lookuprids_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_lookuprids_state *state = tevent_req_data(
+ req, struct winbindd_lookuprids_state);
+ NTSTATUS status;
+ char *result;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DEBUG(5, ("Lookuprids failed: %s\n",nt_errstr(status)));
+ return status;
+ }
+
+ result = talloc_strdup(response, "");
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<state->names.num_principals; i++) {
+ struct wbint_Principal *p = &state->names.principals[i];
+
+ result = talloc_asprintf_append_buffer(
+ result, "%d %s\n", (int)p->type, p->name);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ fstrcpy(response->data.domain_name, state->domain_name);
+ response->extra_data.data = result;
+ response->length += talloc_get_size(result);
+ return NT_STATUS_OK;
+}
+
+static bool parse_ridlist(TALLOC_CTX *mem_ctx, char *ridstr,
+ uint32_t **prids, uint32_t *pnum_rids)
+{
+ uint32_t i, num_rids;
+ uint32_t *rids;
+ char *p;
+
+ if (ridstr == NULL) {
+ return false;
+ }
+
+ p = ridstr;
+ num_rids = 0;
+
+ /* count rids */
+
+ while ((p = strchr(p, '\n')) != NULL) {
+ p += 1;
+ num_rids += 1;
+ }
+
+ if (num_rids == 0) {
+ *pnum_rids = 0;
+ *prids = NULL;
+ return true;
+ }
+
+ rids = talloc_array(mem_ctx, uint32_t, num_rids);
+ if (rids == NULL) {
+ return false;
+ }
+
+ p = ridstr;
+
+ for (i=0; i<num_rids; i++) {
+ char *q;
+ int error = 0;
+
+ rids[i] = smb_strtoul(p, &q, 10, &error, SMB_STR_STANDARD);
+ if (error != 0 || *q != '\n') {
+ DEBUG(0, ("Got invalid ridstr: %s\n", p));
+ return false;
+ }
+ p = q+1;
+ }
+
+ *pnum_rids = num_rids;
+ *prids = rids;
+ return true;
+}
diff --git a/source3/winbindd/winbindd_lookupsid.c b/source3/winbindd/winbindd_lookupsid.c
new file mode 100644
index 0000000..e20966b
--- /dev/null
+++ b/source3/winbindd/winbindd_lookupsid.c
@@ -0,0 +1,104 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_LOOKUPSID
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_lookupsid_state {
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ const char *domname;
+ const char *name;
+};
+
+static void winbindd_lookupsid_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_lookupsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_lookupsid_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_lookupsid_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Ensure null termination */
+ request->data.sid[sizeof(request->data.sid)-1]='\0';
+
+ DEBUG(3, ("lookupsid %s\n", request->data.sid));
+
+ if (!string_to_sid(&state->sid, request->data.sid)) {
+ DEBUG(5, ("%s not a SID\n", request->data.sid));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_lookupsid_send(state, ev, &state->sid);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_lookupsid_done, req);
+ return req;
+}
+
+static void winbindd_lookupsid_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_lookupsid_state *state = tevent_req_data(
+ req, struct winbindd_lookupsid_state);
+ NTSTATUS status;
+
+ status = wb_lookupsid_recv(subreq, state, &state->type,
+ &state->domname, &state->name);
+ TALLOC_FREE(subreq);
+ if (!NT_STATUS_IS_OK(status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_lookupsid_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_lookupsid_state *state = tevent_req_data(
+ req, struct winbindd_lookupsid_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ struct dom_sid_buf buf;
+ DEBUG(5, ("Could not lookup sid %s: %s\n",
+ dom_sid_str_buf(&state->sid, &buf),
+ nt_errstr(status)));
+ return status;
+ }
+
+ fstrcpy(response->data.name.dom_name, state->domname);
+ fstrcpy(response->data.name.name, state->name);
+ response->data.name.type = state->type;
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_lookupsids.c b/source3/winbindd/winbindd_lookupsids.c
new file mode 100644
index 0000000..a289fd8
--- /dev/null
+++ b/source3/winbindd/winbindd_lookupsids.c
@@ -0,0 +1,144 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_LOOKUPSIDS
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+
+struct winbindd_lookupsids_state {
+ struct dom_sid *sids;
+ uint32_t num_sids;
+ struct lsa_RefDomainList *domains;
+ struct lsa_TransNameArray *names;
+};
+
+static void winbindd_lookupsids_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_lookupsids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_lookupsids_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_lookupsids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ DEBUG(3, ("lookupsids\n"));
+
+ if (request->extra_len == 0) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (request->extra_data.data[request->extra_len-1] != '\0') {
+ DEBUG(10, ("Got invalid sids list\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (!parse_sidlist(state, request->extra_data.data,
+ &state->sids, &state->num_sids)) {
+ DEBUG(10, ("parse_sidlist failed\n"));
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ subreq = wb_lookupsids_send(state, ev, state->sids, state->num_sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_lookupsids_done, req);
+ return req;
+}
+
+static void winbindd_lookupsids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_lookupsids_state *state = tevent_req_data(
+ req, struct winbindd_lookupsids_state);
+ NTSTATUS status;
+
+ status = wb_lookupsids_recv(subreq, state, &state->domains,
+ &state->names);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_lookupsids_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_lookupsids_state *state = tevent_req_data(
+ req, struct winbindd_lookupsids_state);
+ NTSTATUS status;
+ char *result;
+ uint32_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ DEBUG(5, ("wb_lookupsids failed: %s\n", nt_errstr(status)));
+ return status;
+ }
+
+ result = talloc_asprintf(response, "%d\n", (int)state->domains->count);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<state->domains->count; i++) {
+ struct dom_sid_buf sid_str;
+
+ result = talloc_asprintf_append_buffer(
+ result, "%s %s\n",
+ dom_sid_str_buf(state->domains->domains[i].sid,
+ &sid_str),
+ state->domains->domains[i].name.string);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ result = talloc_asprintf_append_buffer(
+ result, "%d\n", (int)state->names->count);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<state->names->count; i++) {
+ struct lsa_TranslatedName *name;
+
+ name = &state->names->names[i];
+
+ result = talloc_asprintf_append_buffer(
+ result, "%d %d %s\n",
+ (int)name->sid_index, (int)name->sid_type,
+ name->name.string);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ response->extra_data.data = result;
+ response->length += talloc_get_size(result);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_misc.c b/source3/winbindd/winbindd_misc.c
new file mode 100644
index 0000000..3dbbc2f
--- /dev/null
+++ b/source3/winbindd/winbindd_misc.c
@@ -0,0 +1,513 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - miscellaneous other functions
+
+ Copyright (C) Tim Potter 2000
+ Copyright (C) Andrew Bartlett 2002
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/util/string_wrappers.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static char *get_trust_type_string(TALLOC_CTX *mem_ctx,
+ struct winbindd_tdc_domain *tdc,
+ struct winbindd_domain *domain)
+{
+ enum netr_SchannelType secure_channel_type = SEC_CHAN_NULL;
+ char *s = NULL;
+
+ if (domain != NULL) {
+ secure_channel_type = domain->secure_channel_type;
+ }
+
+ switch (secure_channel_type) {
+ case SEC_CHAN_NULL: {
+ if (domain == NULL) {
+ DBG_ERR("Missing domain [%s]\n",
+ tdc->domain_name);
+ return NULL;
+ }
+ if (domain->routing_domain == NULL) {
+ DBG_ERR("Missing routing for domain [%s]\n",
+ tdc->domain_name);
+ return NULL;
+ }
+ s = talloc_asprintf(mem_ctx, "Routed (via %s)",
+ domain->routing_domain->name);
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+
+ case SEC_CHAN_LOCAL:
+ s = talloc_strdup(mem_ctx, "Local");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+
+ case SEC_CHAN_WKSTA:
+ s = talloc_strdup(mem_ctx, "Workstation");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+
+ case SEC_CHAN_BDC: {
+ int role = lp_server_role();
+
+ if (role == ROLE_DOMAIN_PDC || role == ROLE_IPA_DC) {
+ s = talloc_strdup(mem_ctx, "PDC");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+
+ if (role == ROLE_DOMAIN_BDC) {
+ s = talloc_strdup(mem_ctx, "BDC");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+
+ s = talloc_strdup(mem_ctx, "RWDC");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+
+ case SEC_CHAN_RODC:
+ s = talloc_strdup(mem_ctx, "RODC");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+
+ case SEC_CHAN_DNS_DOMAIN:
+ if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) {
+ s = talloc_strdup(mem_ctx, "External");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+ if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
+ s = talloc_strdup(mem_ctx, "In Forest");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+ if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL) {
+ s = talloc_strdup(mem_ctx, "External");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+ if (tdc->trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
+ s = talloc_strdup(mem_ctx, "Forest");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+ }
+ s = talloc_strdup(mem_ctx, "External");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+
+ case SEC_CHAN_DOMAIN:
+ s = talloc_strdup(mem_ctx, "External");
+ if (s == NULL) {
+ return NULL;
+ }
+ break;
+
+ default:
+ DBG_ERR("Unhandled secure_channel_type %d for domain[%s]\n",
+ secure_channel_type, tdc->domain_name);
+ return NULL;
+ }
+
+ return s;
+}
+
+static bool trust_is_inbound(struct winbindd_tdc_domain *domain)
+{
+ if (domain->trust_flags & NETR_TRUST_FLAG_INBOUND) {
+ return true;
+ }
+ return false;
+}
+
+static bool trust_is_outbound(struct winbindd_tdc_domain *domain)
+{
+ if (domain->trust_flags & NETR_TRUST_FLAG_OUTBOUND) {
+ return true;
+ }
+ return false;
+}
+
+static bool trust_is_transitive(struct winbindd_tdc_domain *domain)
+{
+ bool transitive = false;
+
+ /*
+ * Beware: order matters
+ */
+
+ if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
+ transitive = true;
+ }
+
+ if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
+ transitive = true;
+ }
+
+ if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) {
+ transitive = false;
+ }
+
+ if (domain->trust_attribs & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) {
+ transitive = false;
+ }
+
+ if (domain->trust_flags & NETR_TRUST_FLAG_PRIMARY) {
+ transitive = true;
+ }
+
+ return transitive;
+}
+
+bool winbindd_list_trusted_domains(struct winbindd_cli_state *state)
+{
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_domains = 0;
+ int extra_data_len = 0;
+ char *extra_data = NULL;
+ size_t i = 0;
+ bool ret = false;
+
+ DBG_NOTICE("[%s (%u)]: list trusted domains\n",
+ state->client_name,
+ (unsigned int)state->pid);
+
+ if( !wcache_tdc_fetch_list( &dom_list, &num_domains )) {
+ goto done;
+ }
+
+ extra_data = talloc_strdup(state->mem_ctx, "");
+ if (extra_data == NULL) {
+ goto done;
+ }
+
+ for ( i = 0; i < num_domains; i++ ) {
+ struct winbindd_domain *domain;
+ bool is_online = true;
+ struct winbindd_tdc_domain *d = NULL;
+ char *trust_type = NULL;
+ struct dom_sid_buf buf;
+
+ d = &dom_list[i];
+ domain = find_domain_from_name_noinit(d->domain_name);
+ if (domain) {
+ is_online = domain->online;
+ }
+
+ trust_type = get_trust_type_string(talloc_tos(), d, domain);
+ if (trust_type == NULL) {
+ continue;
+ }
+
+ extra_data = talloc_asprintf_append_buffer(
+ extra_data,
+ "%s\\%s\\%s\\%s\\%s\\%s\\%s\\%s\n",
+ d->domain_name,
+ d->dns_name ? d->dns_name : "",
+ dom_sid_str_buf(&d->sid, &buf),
+ trust_type,
+ trust_is_transitive(d) ? "Yes" : "No",
+ trust_is_inbound(d) ? "Yes" : "No",
+ trust_is_outbound(d) ? "Yes" : "No",
+ is_online ? "Online" : "Offline" );
+
+ TALLOC_FREE(trust_type);
+ }
+
+ state->response->data.num_entries = num_domains;
+
+ extra_data_len = strlen(extra_data);
+ if (extra_data_len > 0) {
+
+ /* Strip the last \n */
+ extra_data[extra_data_len-1] = '\0';
+
+ state->response->extra_data.data = extra_data;
+ state->response->length += extra_data_len;
+ }
+
+ ret = true;
+done:
+ TALLOC_FREE( dom_list );
+ return ret;
+}
+
+bool winbindd_dc_info(struct winbindd_cli_state *cli)
+{
+ struct winbindd_domain *domain;
+ char *dc_name, *dc_ip;
+
+ cli->request->domain_name[sizeof(cli->request->domain_name)-1] = '\0';
+
+ DBG_NOTICE("[%s (%u)]: domain_info [%s]\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ cli->request->domain_name);
+
+ if (cli->request->domain_name[0] != '\0') {
+ domain = find_trust_from_name_noinit(
+ cli->request->domain_name);
+ if (domain == NULL) {
+ DEBUG(10, ("Could not find domain %s\n",
+ cli->request->domain_name));
+ return false;
+ }
+ } else {
+ domain = find_our_domain();
+ }
+
+ if (!fetch_current_dc_from_gencache(
+ talloc_tos(), domain->name, &dc_name, &dc_ip)) {
+ DEBUG(10, ("fetch_current_dc_from_gencache(%s) failed\n",
+ domain->name));
+ return false;
+ }
+
+ cli->response->data.num_entries = 1;
+ cli->response->extra_data.data = talloc_asprintf(
+ cli->mem_ctx, "%s\n%s\n", dc_name, dc_ip);
+
+ TALLOC_FREE(dc_name);
+ TALLOC_FREE(dc_ip);
+
+ if (cli->response->extra_data.data == NULL) {
+ return false;
+ }
+
+ /* must add one to length to copy the 0 for string termination */
+ cli->response->length +=
+ strlen((char *)cli->response->extra_data.data) + 1;
+
+ return true;
+}
+
+bool winbindd_ping(struct winbindd_cli_state *state)
+{
+ DBG_NOTICE("[%s (%u)]: ping\n",
+ state->client_name,
+ (unsigned int)state->pid);
+ return true;
+}
+
+/* List various tidbits of information */
+
+bool winbindd_info(struct winbindd_cli_state *state)
+{
+
+ DBG_NOTICE("[%s (%u)]: request misc info\n",
+ state->client_name,
+ (unsigned int)state->pid);
+
+ state->response->data.info.winbind_separator = *lp_winbind_separator();
+ fstrcpy(state->response->data.info.samba_version, samba_version_string());
+ return true;
+}
+
+/* Tell the client the current interface version */
+
+bool winbindd_interface_version(struct winbindd_cli_state *state)
+{
+ DBG_NOTICE("[%s (%u)]: request interface version (version = %d)\n",
+ state->client_name,
+ (unsigned int)state->pid,
+ WINBIND_INTERFACE_VERSION);
+
+ state->response->data.interface_version = WINBIND_INTERFACE_VERSION;
+ return true;
+}
+
+/* What domain are we a member of? */
+
+bool winbindd_domain_name(struct winbindd_cli_state *state)
+{
+ DBG_NOTICE("[%s (%u)]: request domain name\n",
+ state->client_name,
+ (unsigned int)state->pid);
+
+ fstrcpy(state->response->data.domain_name, lp_workgroup());
+ return true;
+}
+
+/* What's my name again? */
+
+bool winbindd_netbios_name(struct winbindd_cli_state *state)
+{
+ DBG_NOTICE("[%s (%u)]: request netbios name\n",
+ state->client_name,
+ (unsigned int)state->pid);
+
+ fstrcpy(state->response->data.netbios_name, lp_netbios_name());
+ return true;
+}
+
+/* Where can I find the privileged pipe? */
+
+char *get_winbind_priv_pipe_dir(void)
+{
+ return state_path(talloc_tos(), WINBINDD_PRIV_SOCKET_SUBDIR);
+}
+
+bool winbindd_priv_pipe_dir(struct winbindd_cli_state *state)
+{
+ char *priv_dir;
+
+ DBG_NOTICE("[%s (%u)]: request location of privileged pipe\n",
+ state->client_name,
+ (unsigned int)state->pid);
+
+ priv_dir = get_winbind_priv_pipe_dir();
+ state->response->extra_data.data = talloc_move(state->mem_ctx,
+ &priv_dir);
+
+ /* must add one to length to copy the 0 for string termination */
+ state->response->length +=
+ strlen((char *)state->response->extra_data.data) + 1;
+
+ DBG_NOTICE("[%s (%u)]: response location of privileged pipe: %s\n",
+ state->client_name,
+ (unsigned int)state->pid,
+ priv_dir);
+
+ return true;
+}
+
+static void winbindd_setup_max_fds(void)
+{
+ int num_fds = MAX_OPEN_FUDGEFACTOR;
+ int actual_fds;
+
+ num_fds += lp_winbind_max_clients();
+ /* Add some more to account for 2 sockets open
+ when the client transitions from unprivileged
+ to privileged socket
+ */
+ num_fds += lp_winbind_max_clients() / 10;
+
+ /* Add one socket per child process
+ (yeah there are child processes other than the
+ domain children but only domain children can vary
+ with configuration
+ */
+ num_fds += lp_winbind_max_domain_connections() *
+ (lp_allow_trusted_domains() ? WINBIND_MAX_DOMAINS_HINT : 1);
+
+ actual_fds = set_maxfiles(num_fds);
+
+ if (actual_fds < num_fds) {
+ DEBUG(1, ("winbindd_setup_max_fds: Information only: "
+ "requested %d open files, %d are available.\n",
+ num_fds, actual_fds));
+ }
+}
+
+bool winbindd_reload_services_file(const char *lfile)
+{
+ const struct loadparm_substitution *lp_sub =
+ loadparm_s3_global_substitution();
+ bool ret;
+
+ if (lp_loaded()) {
+ char *fname = lp_next_configfile(talloc_tos(), lp_sub);
+
+ if (file_exist(fname) && !strcsequal(fname,get_dyn_CONFIGFILE())) {
+ set_dyn_CONFIGFILE(fname);
+ }
+ TALLOC_FREE(fname);
+ }
+
+ reopen_logs();
+ ret = lp_load_global(get_dyn_CONFIGFILE());
+
+ /* if this is a child, restore the logfile to the special
+ name - <domain>, idmap, etc. */
+ if (lfile && *lfile) {
+ lp_set_logfile(lfile);
+ }
+
+ reopen_logs();
+ load_interfaces();
+ winbindd_setup_max_fds();
+
+ return(ret);
+}
+
+static size_t *debug_call_depth = NULL;
+
+void winbind_debug_call_depth_setup(size_t *depth)
+{
+ debug_call_depth = depth;
+}
+
+void winbind_call_flow(void *private_data,
+ enum tevent_thread_call_depth_cmd cmd,
+ struct tevent_req *req,
+ size_t depth,
+ const char *fname)
+{
+ switch (cmd) {
+ case TEVENT_CALL_FLOW_REQ_CREATE:
+ *debug_call_depth = depth;
+ DEBUG(20, ("flow: -> %s\n", fname));
+ break;
+ case TEVENT_CALL_FLOW_REQ_NOTIFY_CB:
+ *debug_call_depth = depth;
+ DEBUG(20, ("flow: <- %s\n", fname));
+ break;
+ case TEVENT_CALL_FLOW_REQ_QUEUE_TRIGGER:
+ *debug_call_depth = depth;
+ break;
+ case TEVENT_CALL_FLOW_REQ_RESET:
+ *debug_call_depth = depth;
+ break;
+ case TEVENT_CALL_FLOW_REQ_CANCEL:
+ case TEVENT_CALL_FLOW_REQ_CLEANUP:
+ case TEVENT_CALL_FLOW_REQ_QUEUE_ENTER:
+ case TEVENT_CALL_FLOW_REQ_QUEUE_LEAVE:
+ break;
+ }
+}
diff --git a/source3/winbindd/winbindd_msrpc.c b/source3/winbindd/winbindd_msrpc.c
new file mode 100644
index 0000000..a7bd9be
--- /dev/null
+++ b/source3/winbindd/winbindd_msrpc.c
@@ -0,0 +1,1124 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind rpc backend functions
+
+ Copyright (C) Tim Potter 2000-2001,2003
+ Copyright (C) Andrew Tridgell 2001
+ Copyright (C) Volker Lendecke 2005
+ Copyright (C) Guenther Deschner 2008 (pidl conversion)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "winbindd_rpc.h"
+
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_samr.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../libcli/security/security.h"
+#include "libsmb/samlogon_cache.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+static NTSTATUS winbindd_lookup_names(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ uint32_t num_names,
+ const char **names,
+ const char ***domains,
+ struct dom_sid **sids,
+ enum lsa_SidType **types);
+
+/* Query display info for a domain. This returns enough information plus a
+ bit extra to give an overview of domain users for the User Manager
+ application. */
+static NTSTATUS msrpc_query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **prids)
+{
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle dom_pol;
+ uint32_t *rids = NULL;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ DEBUG(3, ("msrpc_query_user_list\n"));
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("query_user_list: No incoming trust for domain %s\n",
+ domain->name));
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_query_user_list(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &domain->sid,
+ &rids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (prids) {
+ *prids = talloc_move(mem_ctx, &rids);
+ }
+
+done:
+ TALLOC_FREE(rids);
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* list all domain groups */
+static NTSTATUS msrpc_enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol;
+ struct wb_acct_info *info = NULL;
+ uint32_t num_info = 0;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ DEBUG(3,("msrpc_enum_dom_groups\n"));
+
+ if (pnum_info) {
+ *pnum_info = 0;
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("enum_domain_groups: No incoming trust for domain %s\n",
+ domain->name));
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_enum_dom_groups(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &num_info,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pnum_info) {
+ *pnum_info = num_info;
+ }
+
+ if (pinfo) {
+ *pinfo = talloc_move(mem_ctx, &info);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* List all domain groups */
+
+static NTSTATUS msrpc_enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol;
+ struct wb_acct_info *info = NULL;
+ uint32_t num_info = 0;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ DEBUG(3,("msrpc_enum_local_groups\n"));
+
+ if (pnum_info) {
+ *pnum_info = 0;
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("enum_local_groups: No incoming trust for domain %s\n",
+ domain->name));
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_enum_local_groups(mem_ctx,
+ samr_pipe,
+ &dom_pol,
+ &num_info,
+ &info);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pnum_info) {
+ *pnum_info = num_info;
+ }
+
+ if (pinfo) {
+ *pinfo = talloc_move(mem_ctx, &info);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* convert a single name to a sid in a domain */
+static NTSTATUS msrpc_name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ const char **pdom_name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+ struct dom_sid *sids = NULL;
+ enum lsa_SidType *types = NULL;
+ char *full_name = NULL;
+ const char *names[1];
+ const char **domains;
+ NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL;
+ char *mapped_name = NULL;
+
+ if (name == NULL || *name=='\0') {
+ full_name = talloc_asprintf(mem_ctx, "%s", domain_name);
+ } else if (domain_name == NULL || *domain_name == '\0') {
+ full_name = talloc_asprintf(mem_ctx, "%s", name);
+ } else {
+ full_name = talloc_asprintf(mem_ctx, "%s\\%s", domain_name, name);
+ }
+ if (!full_name) {
+ DEBUG(0, ("talloc_asprintf failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ DEBUG(3, ("msrpc_name_to_sid: name=%s\n", full_name));
+
+ name_map_status = normalize_name_unmap(mem_ctx, full_name,
+ &mapped_name);
+
+ /* Reset the full_name pointer if we mapped anything */
+
+ if (NT_STATUS_IS_OK(name_map_status) ||
+ NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED))
+ {
+ full_name = mapped_name;
+ }
+
+ DEBUG(3,("name_to_sid [rpc] %s for domain %s\n",
+ full_name?full_name:"", domain_name ));
+
+ names[0] = full_name;
+
+ result = winbindd_lookup_names(mem_ctx, domain, 1,
+ names, &domains,
+ &sids, &types);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ /* Return rid and type if lookup successful */
+
+ if (pdom_name != NULL) {
+ const char *dom_name;
+
+ dom_name = talloc_strdup(mem_ctx, domains[0]);
+ if (dom_name == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *pdom_name = dom_name;
+ }
+
+ sid_copy(sid, &sids[0]);
+ *type = types[0];
+
+ return NT_STATUS_OK;
+}
+
+/*
+ convert a domain SID to a user or group name
+*/
+static NTSTATUS msrpc_sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ char **domains;
+ char **names;
+ enum lsa_SidType *types = NULL;
+ NTSTATUS result;
+ NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL;
+ char *mapped_name = NULL;
+ struct dom_sid_buf buf;
+
+ DEBUG(3, ("msrpc_sid_to_name: %s for domain %s\n",
+ dom_sid_str_buf(sid, &buf),
+ domain->name));
+
+ result = winbindd_lookup_sids(mem_ctx,
+ domain,
+ 1,
+ sid,
+ &domains,
+ &names,
+ &types);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(2,("msrpc_sid_to_name: failed to lookup sids: %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+
+
+ *type = (enum lsa_SidType)types[0];
+ *domain_name = domains[0];
+ *name = names[0];
+
+ DEBUG(5,("Mapped sid to [%s]\\[%s]\n", domains[0], *name));
+
+ name_map_status = normalize_name_map(mem_ctx, domain->name, *name,
+ &mapped_name);
+ if (NT_STATUS_IS_OK(name_map_status) ||
+ NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED))
+ {
+ *name = mapped_name;
+ DEBUG(5,("returning mapped name -- %s\n", *name));
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS msrpc_rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ char **domains;
+ NTSTATUS result;
+ struct dom_sid *sids;
+ size_t i;
+ char **ret_names;
+
+ DEBUG(3, ("msrpc_rids_to_names: domain %s\n", domain->name ));
+
+ if (num_rids) {
+ sids = talloc_array(mem_ctx, struct dom_sid, num_rids);
+ if (sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ sids = NULL;
+ }
+
+ for (i=0; i<num_rids; i++) {
+ if (!sid_compose(&sids[i], sid, rids[i])) {
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+ }
+
+ result = winbindd_lookup_sids(mem_ctx,
+ domain,
+ num_rids,
+ sids,
+ &domains,
+ names,
+ types);
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) {
+ return result;
+ }
+
+ ret_names = *names;
+ for (i=0; i<num_rids; i++) {
+ NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL;
+ char *mapped_name = NULL;
+
+ if ((*types)[i] != SID_NAME_UNKNOWN) {
+ name_map_status = normalize_name_map(mem_ctx,
+ domain->name,
+ ret_names[i],
+ &mapped_name);
+ if (NT_STATUS_IS_OK(name_map_status) ||
+ NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED))
+ {
+ ret_names[i] = mapped_name;
+ }
+
+ *domain_name = domains[i];
+ }
+ }
+
+ return result;
+}
+
+/* Lookup groups a user is a member of. I wish Unix had a call like this! */
+static NTSTATUS msrpc_lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *pnum_groups,
+ struct dom_sid **puser_grpsids)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol;
+ struct dom_sid *user_grpsids = NULL;
+ struct dom_sid_buf buf;
+ uint32_t num_groups = 0;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ DEBUG(3,("msrpc_lookup_usergroups sid=%s\n",
+ dom_sid_str_buf(user_sid, &buf)));
+
+ *pnum_groups = 0;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Check if we have a cached user_info_3 */
+ status = lookup_usergroups_cached(tmp_ctx,
+ user_sid,
+ &num_groups,
+ &user_grpsids);
+ if (NT_STATUS_IS_OK(status)) {
+ goto cached;
+ }
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_usergroups: No incoming trust for domain %s\n",
+ domain->name));
+
+ /* Tell the cache manager not to remember this one */
+ status = NT_STATUS_SYNCHRONIZATION_REQUIRED;
+ goto done;
+ }
+
+ /* no cache; hit the wire */
+ status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_lookup_usergroups(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &domain->sid,
+ user_sid,
+ &num_groups,
+ &user_grpsids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+cached:
+ *pnum_groups = num_groups;
+
+ if (puser_grpsids) {
+ *puser_grpsids = talloc_move(mem_ctx, &user_grpsids);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ return NT_STATUS_OK;
+}
+
+#define MAX_SAM_ENTRIES_W2K 0x400 /* 1024 */
+
+static NTSTATUS msrpc_lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids, const struct dom_sid *sids,
+ uint32_t *pnum_aliases,
+ uint32_t **palias_rids)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol;
+ uint32_t num_aliases = 0;
+ uint32_t *alias_rids = NULL;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ DEBUG(3,("msrpc_lookup_useraliases\n"));
+
+ if (pnum_aliases) {
+ *pnum_aliases = 0;
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!winbindd_can_contact_domain(domain)) {
+ DEBUG(10,("msrpc_lookup_useraliases: No incoming trust for domain %s\n",
+ domain->name));
+ /* Tell the cache manager not to remember this one */
+ status = NT_STATUS_SYNCHRONIZATION_REQUIRED;
+ goto done;
+ }
+
+ status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_lookup_useraliases(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ num_sids,
+ sids,
+ &num_aliases,
+ &alias_rids);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pnum_aliases) {
+ *pnum_aliases = num_aliases;
+ }
+
+ if (palias_rids) {
+ *palias_rids = talloc_move(mem_ctx, &alias_rids);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* lookup alias membership */
+static NTSTATUS msrpc_lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *alias_sid,
+ enum lsa_SidType type,
+ uint32_t *pnum_sids,
+ struct dom_sid **sid_mem)
+{
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle dom_pol;
+ struct dom_sid *alias_members = NULL;
+ struct dom_sid_buf buf;
+ uint32_t num_groups = 0;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+
+ D_INFO("Lookup alias members in domain=%s for sid=%s.\n",
+ domain->name,
+ dom_sid_str_buf(alias_sid, &buf));
+
+ *pnum_sids = 0;
+
+ if (!winbindd_can_contact_domain(domain)) {
+ D_DEBUG("No incoming trust for domain %s\n", domain->name);
+ status = NT_STATUS_OK;
+ goto done;
+ }
+
+ status = cm_connect_sam(domain, tmp_ctx, false, &samr_pipe, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_lookup_aliasmem(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &domain->sid,
+ alias_sid,
+ type,
+ &num_groups,
+ &alias_members);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ *pnum_sids = num_groups;
+ if (sid_mem) {
+ *sid_mem = talloc_move(mem_ctx, &alias_members);
+ }
+
+done:
+ talloc_free(tmp_ctx);
+ return status;
+}
+
+/* Lookup group membership given a rid. */
+static NTSTATUS msrpc_lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem,
+ char ***names,
+ uint32_t **name_types)
+{
+ NTSTATUS status, result;
+ uint32_t i, total_names = 0;
+ struct policy_handle dom_pol, group_pol;
+ uint32_t des_access = SEC_FLAG_MAXIMUM_ALLOWED;
+ uint32_t *rid_mem = NULL;
+ uint32_t group_rid;
+ unsigned int j, r;
+ struct rpc_pipe_client *cli;
+ unsigned int orig_timeout;
+ struct samr_RidAttrArray *rids = NULL;
+ struct dcerpc_binding_handle *b;
+ struct dom_sid_buf buf;
+
+ DEBUG(3,("msrpc_lookup_groupmem: %s sid=%s\n", domain->name,
+ dom_sid_str_buf(group_sid, &buf)));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("lookup_groupmem: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_OK;
+ }
+
+ if (!sid_peek_check_rid(&domain->sid, group_sid, &group_rid))
+ return NT_STATUS_UNSUCCESSFUL;
+
+ *num_names = 0;
+
+ result = cm_connect_sam(domain, mem_ctx, false, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(result))
+ return result;
+
+ b = cli->binding_handle;
+
+ status = dcerpc_samr_OpenGroup(b, mem_ctx,
+ &dom_pol,
+ des_access,
+ group_rid,
+ &group_pol,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ /* Step #1: Get a list of user rids that are the members of the
+ group. */
+
+ /* This call can take a long time - allow the server to time out.
+ 35 seconds should do it. */
+
+ orig_timeout = rpccli_set_timeout(cli, 35000);
+
+ status = dcerpc_samr_QueryGroupMember(b, mem_ctx,
+ &group_pol,
+ &rids,
+ &result);
+
+ /* And restore our original timeout. */
+ rpccli_set_timeout(cli, orig_timeout);
+
+ {
+ NTSTATUS _result;
+ dcerpc_samr_Close(b, mem_ctx, &group_pol, &_result);
+ }
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ if (!rids || !rids->count) {
+ names = NULL;
+ name_types = NULL;
+ sid_mem = NULL;
+ return NT_STATUS_OK;
+ }
+
+ *num_names = rids->count;
+ rid_mem = rids->rids;
+
+ /* Step #2: Convert list of rids into list of usernames. Do this
+ in bunches of ~1000 to avoid crashing NT4. It looks like there
+ is a buffer overflow or something like that lurking around
+ somewhere. */
+
+#define MAX_LOOKUP_RIDS 900
+
+ *names = talloc_zero_array(mem_ctx, char *, *num_names);
+ *name_types = talloc_zero_array(mem_ctx, uint32_t, *num_names);
+ *sid_mem = talloc_zero_array(mem_ctx, struct dom_sid, *num_names);
+
+ for (j=0;j<(*num_names);j++)
+ sid_compose(&(*sid_mem)[j], &domain->sid, rid_mem[j]);
+
+ if (*num_names>0 && (!*names || !*name_types))
+ return NT_STATUS_NO_MEMORY;
+
+ for (i = 0; i < *num_names; i += MAX_LOOKUP_RIDS) {
+ int num_lookup_rids = MIN(*num_names - i, MAX_LOOKUP_RIDS);
+ struct lsa_Strings tmp_names;
+ struct samr_Ids tmp_types;
+
+ /* Lookup a chunk of rids */
+
+ status = dcerpc_samr_LookupRids(b, mem_ctx,
+ &dom_pol,
+ num_lookup_rids,
+ &rid_mem[i],
+ &tmp_names,
+ &tmp_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ /* see if we have a real error (and yes the
+ STATUS_SOME_UNMAPPED is the one returned from 2k) */
+
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED))
+ return result;
+
+ /* Copy result into array. The talloc system will take
+ care of freeing the temporary arrays later on. */
+
+ if (tmp_names.count != num_lookup_rids) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (tmp_types.count != num_lookup_rids) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ for (r=0; r<tmp_names.count; r++) {
+ if (tmp_types.ids[r] == SID_NAME_UNKNOWN) {
+ continue;
+ }
+ if (total_names >= *num_names) {
+ break;
+ }
+ (*names)[total_names] = fill_domain_username_talloc(
+ mem_ctx, domain->name,
+ tmp_names.names[r].string, true);
+ (*name_types)[total_names] = tmp_types.ids[r];
+ total_names += 1;
+ }
+ }
+
+ *num_names = total_names;
+
+ return NT_STATUS_OK;
+}
+
+/* get a list of trusted domains */
+static NTSTATUS msrpc_trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *ptrust_list)
+{
+ struct rpc_pipe_client *lsa_pipe;
+ struct policy_handle lsa_policy;
+ struct netr_DomainTrust *trusts = NULL;
+ uint32_t num_trusts = 0;
+ TALLOC_CTX *tmp_ctx;
+ NTSTATUS status;
+
+ DEBUG(3,("msrpc_trusted_domains\n"));
+
+ if (ptrust_list) {
+ ZERO_STRUCTP(ptrust_list);
+ }
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = cm_connect_lsa(domain, tmp_ctx, &lsa_pipe, &lsa_policy);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_trusted_domains(tmp_ctx,
+ lsa_pipe,
+ &lsa_policy,
+ &num_trusts,
+ &trusts);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (ptrust_list) {
+ ptrust_list->count = num_trusts;
+ ptrust_list->array = talloc_move(mem_ctx, &trusts);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* find the lockout policy for a domain */
+static NTSTATUS msrpc_lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *lockout_policy)
+{
+ NTSTATUS status, result;
+ struct rpc_pipe_client *cli;
+ struct policy_handle dom_pol;
+ union samr_DomainInfo *info = NULL;
+ struct dcerpc_binding_handle *b;
+
+ DEBUG(3, ("msrpc_lockout_policy: fetch lockout policy for %s\n", domain->name));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("msrpc_lockout_policy: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = cm_connect_sam(domain, mem_ctx, false, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ b = cli->binding_handle;
+
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ &dom_pol,
+ DomainLockoutInformation,
+ &info,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ *lockout_policy = info->info12;
+
+ DEBUG(10,("msrpc_lockout_policy: lockout_threshold %d\n",
+ info->info12.lockout_threshold));
+
+ done:
+
+ return status;
+}
+
+/* find the password policy for a domain */
+static NTSTATUS msrpc_password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *password_policy)
+{
+ NTSTATUS status, result;
+ struct rpc_pipe_client *cli;
+ struct policy_handle dom_pol;
+ union samr_DomainInfo *info = NULL;
+ struct dcerpc_binding_handle *b;
+
+ DEBUG(3, ("msrpc_password_policy: fetch password policy for %s\n",
+ domain->name));
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DEBUG(10,("msrpc_password_policy: No incoming trust for domain %s\n",
+ domain->name));
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ status = cm_connect_sam(domain, mem_ctx, false, &cli, &dom_pol);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ b = cli->binding_handle;
+
+ status = dcerpc_samr_QueryDomainInfo(b, mem_ctx,
+ &dom_pol,
+ DomainPasswordInformation,
+ &info,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ *password_policy = info->info1;
+
+ DEBUG(10,("msrpc_password_policy: min_length_password %d\n",
+ info->info1.min_password_length));
+
+ done:
+
+ return status;
+}
+
+static enum lsa_LookupNamesLevel winbindd_lookup_level(
+ struct winbindd_domain *domain)
+{
+ enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_DOMAINS_ONLY;
+
+ if (domain->internal) {
+ level = LSA_LOOKUP_NAMES_ALL;
+ } else if (domain->secure_channel_type == SEC_CHAN_DNS_DOMAIN) {
+ if (domain->domain_flags & NETR_TRUST_FLAG_IN_FOREST) {
+ /*
+ * TODO:
+ *
+ * Depending on what we want to resolve. We need to use:
+ * 1. LsapLookupXForestReferral(5)/LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY
+ * if we want to pass the request into the direction of the forest
+ * root domain. The forest root domain uses
+ * LsapLookupXForestResolve(6)/LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2
+ * when passing the request to trusted forests.
+ * 2. LsapLookupGC(4)/LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY
+ * if we're not a GC and want to resolve a name within our own forest.
+ *
+ * As we don't support more than one domain in our own forest
+ * and always try to be a GC for now, we just set
+ * LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY.
+ */
+ level = LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY;
+ } else if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
+ /*
+ * This is LsapLookupXForestResolve(6)/LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2
+ */
+ level = LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2;
+ } else {
+ /*
+ * This is LsapLookupTDL(3)/LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY
+ */
+ level = LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY;
+ }
+ } else if (domain->secure_channel_type == SEC_CHAN_DOMAIN) {
+ /*
+ * This is LsapLookupTDL(3)/LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY
+ */
+ level = LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY;
+ } else if (domain->rodc) {
+ level = LSA_LOOKUP_NAMES_RODC_REFERRAL_TO_FULL_DC;
+ } else {
+ /*
+ * This is LsapLookupPDC(2)/LSA_LOOKUP_NAMES_DOMAINS_ONLY
+ */
+ level = LSA_LOOKUP_NAMES_DOMAINS_ONLY;
+ }
+
+ return level;
+}
+
+NTSTATUS winbindd_lookup_sids(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ char ***domains,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ NTSTATUS status;
+ NTSTATUS result;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle lsa_policy;
+ unsigned int orig_timeout;
+ bool use_lookupsids3 = false;
+ bool retried = false;
+ enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_ALL;
+
+ connect:
+ status = cm_connect_lsat(domain, mem_ctx, &cli, &lsa_policy);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ b = cli->binding_handle;
+
+ if (cli->transport->transport == NCACN_IP_TCP) {
+ use_lookupsids3 = true;
+ }
+
+ level = winbindd_lookup_level(domain);
+
+ /*
+ * This call can take a long time
+ * allow the server to time out.
+ * 35 seconds should do it.
+ */
+ orig_timeout = dcerpc_binding_handle_set_timeout(b, 35000);
+
+ status = dcerpc_lsa_lookup_sids_generic(b,
+ mem_ctx,
+ &lsa_policy,
+ num_sids,
+ sids,
+ level,
+ domains,
+ names,
+ types,
+ use_lookupsids3,
+ &result);
+
+ /* And restore our original timeout. */
+ dcerpc_binding_handle_set_timeout(b, orig_timeout);
+
+ if (reset_cm_connection_on_error(domain, b, status)) {
+ /*
+ * This can happen if the schannel key is not
+ * valid anymore, we need to invalidate the
+ * all connections to the dc and reestablish
+ * a netlogon connection first.
+ */
+ domain->can_do_ncacn_ip_tcp = domain->active_directory;
+ if (!retried) {
+ retried = true;
+ goto connect;
+ }
+ status = NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS winbindd_lookup_names(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ uint32_t num_names,
+ const char **names,
+ const char ***domains,
+ struct dom_sid **sids,
+ enum lsa_SidType **types)
+{
+ NTSTATUS status;
+ NTSTATUS result;
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ struct policy_handle lsa_policy;
+ unsigned int orig_timeout = 0;
+ bool use_lookupnames4 = false;
+ bool retried = false;
+ enum lsa_LookupNamesLevel level = LSA_LOOKUP_NAMES_ALL;
+
+ connect:
+ status = cm_connect_lsat(domain, mem_ctx, &cli, &lsa_policy);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ b = cli->binding_handle;
+
+ if (cli->transport->transport == NCACN_IP_TCP) {
+ use_lookupnames4 = true;
+ }
+
+ level = winbindd_lookup_level(domain);
+
+ /*
+ * This call can take a long time
+ * allow the server to time out.
+ * 35 seconds should do it.
+ */
+ orig_timeout = dcerpc_binding_handle_set_timeout(b, 35000);
+
+ status = dcerpc_lsa_lookup_names_generic(b,
+ mem_ctx,
+ &lsa_policy,
+ num_names,
+ (const char **) names,
+ domains,
+ level,
+ sids,
+ types,
+ use_lookupnames4,
+ &result);
+
+ /* And restore our original timeout. */
+ dcerpc_binding_handle_set_timeout(b, orig_timeout);
+
+ if (reset_cm_connection_on_error(domain, b, status)) {
+ /*
+ * This can happen if the schannel key is not
+ * valid anymore, we need to invalidate the
+ * all connections to the dc and reestablish
+ * a netlogon connection first.
+ */
+ if (!retried) {
+ retried = true;
+ goto connect;
+ }
+ status = NT_STATUS_ACCESS_DENIED;
+ }
+
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ return NT_STATUS_OK;
+}
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods msrpc_methods = {
+ False,
+ msrpc_query_user_list,
+ msrpc_enum_dom_groups,
+ msrpc_enum_local_groups,
+ msrpc_name_to_sid,
+ msrpc_sid_to_name,
+ msrpc_rids_to_names,
+ msrpc_lookup_usergroups,
+ msrpc_lookup_useraliases,
+ msrpc_lookup_groupmem,
+ msrpc_lookup_aliasmem,
+ msrpc_lockout_policy,
+ msrpc_password_policy,
+ msrpc_trusted_domains,
+};
diff --git a/source3/winbindd/winbindd_ndr.c b/source3/winbindd/winbindd_ndr.c
new file mode 100644
index 0000000..a52a704
--- /dev/null
+++ b/source3/winbindd/winbindd_ndr.c
@@ -0,0 +1,162 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * winbindd debug helper
+ * Copyright (C) Guenther Deschner 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "../librpc/gen_ndr/ndr_security.h"
+#include "../librpc/gen_ndr/ndr_lsa.h"
+#include "../librpc/ndr/libndr.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/****************************************************************
+****************************************************************/
+
+void ndr_print_winbindd_child(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_child *r)
+{
+ ndr_print_struct(ndr, name, "winbindd_child");
+ ndr->depth++;
+ ndr_print_uint32(ndr, "pid", (uint32_t)r->pid);
+#if 0
+ ndr_print_winbindd_domain(ndr, "domain", r->domain);
+#else
+ ndr_print_ptr(ndr, "domain", r->domain);
+#endif
+ ndr_print_string(ndr, "logfilename", r->logfilename);
+ /* struct fd_event event; */
+ ndr_print_ptr(ndr, "lockout_policy_event", r->lockout_policy_event);
+ ndr->depth--;
+}
+
+/****************************************************************
+****************************************************************/
+
+void ndr_print_winbindd_cm_conn(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_cm_conn *r)
+{
+ ndr_print_struct(ndr, name, "winbindd_cm_conn");
+ ndr->depth++;
+ ndr_print_ptr(ndr, "cli", r->cli);
+ ndr_print_ptr(ndr, "samr_pipe", r->samr_pipe);
+ ndr_print_policy_handle(ndr, "sam_connect_handle", &r->sam_connect_handle);
+ ndr_print_policy_handle(ndr, "sam_domain_handle", &r->sam_domain_handle);
+ ndr_print_ptr(ndr, "lsa_pipe", r->lsa_pipe);
+ ndr_print_policy_handle(ndr, "lsa_policy", &r->lsa_policy);
+ ndr_print_ptr(ndr, "netlogon_pipe", r->netlogon_pipe);
+ ndr->depth--;
+}
+
+/****************************************************************
+****************************************************************/
+
+#ifdef HAVE_ADS
+extern struct winbindd_methods ads_methods;
+extern struct winbindd_methods reconnect_ads_methods;
+#endif
+extern struct winbindd_methods msrpc_methods;
+extern struct winbindd_methods builtin_passdb_methods;
+extern struct winbindd_methods sam_passdb_methods;
+extern struct winbindd_methods reconnect_methods;
+
+void ndr_print_winbindd_methods(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_methods *r)
+{
+ ndr_print_struct(ndr, name, "winbindd_methods");
+ ndr->depth++;
+
+ if (r == NULL) {
+ ndr_print_string(ndr, name, "(NULL)");
+ ndr->depth--;
+ return;
+ }
+
+ if (r == &msrpc_methods) {
+ ndr_print_string(ndr, name, "msrpc_methods");
+#ifdef HAVE_ADS
+ } else if (r == &ads_methods) {
+ ndr_print_string(ndr, name, "ads_methods");
+ } else if (r == &reconnect_ads_methods) {
+ ndr_print_string(ndr, name, "reconnect_ads_methods");
+#endif
+ } else if (r == &builtin_passdb_methods) {
+ ndr_print_string(ndr, name, "builtin_passdb_methods");
+ } else if (r == &sam_passdb_methods) {
+ ndr_print_string(ndr, name, "sam_passdb_methods");
+ } else if (r == &reconnect_methods) {
+ ndr_print_string(ndr, name, "reconnect_methods");
+ } else {
+ ndr_print_string(ndr, name, "UNKNOWN");
+ }
+ ndr->depth--;
+}
+
+/****************************************************************
+****************************************************************/
+
+void ndr_print_winbindd_domain(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_domain *r)
+{
+ int i;
+ if (!r) {
+ return;
+ }
+
+ ndr_print_struct(ndr, name, "winbindd_domain");
+ ndr->depth++;
+ ndr_print_string(ndr, "name", r->name);
+ ndr_print_string(ndr, "alt_name", r->alt_name);
+ ndr_print_string(ndr, "forest_name", r->forest_name);
+ ndr_print_dom_sid(ndr, "sid", &r->sid);
+ ndr_print_netr_TrustFlags(ndr, "domain_flags", r->domain_flags);
+ ndr_print_lsa_TrustType(ndr, "domain_type", r->domain_type);
+ ndr_print_lsa_TrustAttributes(ndr, "domain_trust_attribs", r->domain_trust_attribs);
+ ndr_print_bool(ndr, "initialized", r->initialized);
+ ndr_print_bool(ndr, "native_mode", r->native_mode);
+ ndr_print_bool(ndr, "active_directory", r->active_directory);
+ ndr_print_bool(ndr, "primary", r->primary);
+ ndr_print_bool(ndr, "internal", r->internal);
+ ndr_print_bool(ndr, "online", r->online);
+ ndr_print_time_t(ndr, "startup_time", r->startup_time);
+ ndr_print_bool(ndr, "startup", r->startup);
+ ndr_print_winbindd_methods(ndr, "backend", r->backend);
+ ndr_print_ptr(ndr,
+ "backend_data.samr_pipes",
+ r->backend_data.samr_pipes);
+ ndr_print_ptr(ndr,
+ "backend_data.ads_conn",
+ r->backend_data.ads_conn);
+ ndr_print_string(ndr, "dcname", r->dcname);
+ ndr_print_sockaddr_storage(ndr, "dcaddr", &r->dcaddr);
+ ndr_print_time_t(ndr, "last_seq_check", r->last_seq_check);
+ ndr_print_uint32(ndr, "sequence_number", r->sequence_number);
+ ndr_print_NTSTATUS(ndr, "last_status", r->last_status);
+ ndr_print_winbindd_cm_conn(ndr, "conn", &r->conn);
+ for (i=0; i<talloc_array_length(r->children); i++) {
+ ndr_print_winbindd_child(ndr, "children", &r->children[i]);
+ }
+ ndr_print_ptr(ndr, "check_online_event", r->check_online_event);
+ ndr->depth--;
+}
diff --git a/source3/winbindd/winbindd_pam.c b/source3/winbindd/winbindd_pam.c
new file mode 100644
index 0000000..6c890c8
--- /dev/null
+++ b/source3/winbindd/winbindd_pam.c
@@ -0,0 +1,3616 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon - pam auth functions
+
+ Copyright (C) Andrew Tridgell 2000
+ Copyright (C) Tim Potter 2001
+ Copyright (C) Andrew Bartlett 2001-2002
+ Copyright (C) Guenther Deschner 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "ntdomain.h"
+#include "winbindd.h"
+#include "libsmb/namequery.h"
+#include "../libcli/auth/libcli_auth.h"
+#include "libcli/auth/pam_errors.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "librpc/rpc/dcesrv_core.h"
+#include "librpc/gen_ndr/ndr_winbind.h"
+#include "rpc_client/cli_pipe.h"
+#include "rpc_client/cli_samr.h"
+#include "../librpc/gen_ndr/ndr_netlogon.h"
+#include "rpc_client/cli_netlogon.h"
+#include "smb_krb5.h"
+#include "../libcli/security/security.h"
+#include "ads.h"
+#include "../librpc/gen_ndr/krb5pac.h"
+#include "passdb/machine_sid.h"
+#include "auth.h"
+#include "../lib/tsocket/tsocket.h"
+#include "auth/kerberos/pac_utils.h"
+#include "auth/gensec/gensec.h"
+#include "librpc/crypto/gse_krb5.h"
+#include "lib/afs/afs_funcs.h"
+#include "libsmb/samlogon_cache.h"
+#include "rpc_client/util_netlogon.h"
+#include "param/param.h"
+#include "messaging/messaging.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/crypto/gnutls_helpers.h"
+
+#include "lib/crypto/gnutls_helpers.h"
+#include <gnutls/crypto.h>
+#include "lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+#define LOGON_KRB5_FAIL_CLOCK_SKEW 0x02000000
+
+static NTSTATUS append_info3_as_txt(TALLOC_CTX *mem_ctx,
+ struct winbindd_response *resp,
+ uint16_t validation_level,
+ union netr_Validation *validation)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ char *ex = NULL;
+ uint32_t i;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ status = map_validation_to_info3(frame,
+ validation_level,
+ validation,
+ &info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto out;
+ }
+
+ resp->data.auth.info3.logon_time =
+ nt_time_to_unix(info3->base.logon_time);
+ resp->data.auth.info3.logoff_time =
+ nt_time_to_unix(info3->base.logoff_time);
+ resp->data.auth.info3.kickoff_time =
+ nt_time_to_unix(info3->base.kickoff_time);
+ resp->data.auth.info3.pass_last_set_time =
+ nt_time_to_unix(info3->base.last_password_change);
+ resp->data.auth.info3.pass_can_change_time =
+ nt_time_to_unix(info3->base.allow_password_change);
+ resp->data.auth.info3.pass_must_change_time =
+ nt_time_to_unix(info3->base.force_password_change);
+
+ resp->data.auth.info3.logon_count = info3->base.logon_count;
+ resp->data.auth.info3.bad_pw_count = info3->base.bad_password_count;
+
+ resp->data.auth.info3.user_rid = info3->base.rid;
+ resp->data.auth.info3.group_rid = info3->base.primary_gid;
+ sid_to_fstring(resp->data.auth.info3.dom_sid, info3->base.domain_sid);
+
+ resp->data.auth.info3.num_groups = info3->base.groups.count;
+ resp->data.auth.info3.user_flgs = info3->base.user_flags;
+
+ resp->data.auth.info3.acct_flags = info3->base.acct_flags;
+ resp->data.auth.info3.num_other_sids = info3->sidcount;
+
+ fstrcpy(resp->data.auth.info3.user_name,
+ info3->base.account_name.string);
+ fstrcpy(resp->data.auth.info3.full_name,
+ info3->base.full_name.string);
+ fstrcpy(resp->data.auth.info3.logon_script,
+ info3->base.logon_script.string);
+ fstrcpy(resp->data.auth.info3.profile_path,
+ info3->base.profile_path.string);
+ fstrcpy(resp->data.auth.info3.home_dir,
+ info3->base.home_directory.string);
+ fstrcpy(resp->data.auth.info3.dir_drive,
+ info3->base.home_drive.string);
+
+ fstrcpy(resp->data.auth.info3.logon_srv,
+ info3->base.logon_server.string);
+ fstrcpy(resp->data.auth.info3.logon_dom,
+ info3->base.logon_domain.string);
+
+ resp->data.auth.validation_level = validation_level;
+ if (validation_level == 6) {
+ fstrcpy(resp->data.auth.info6.dns_domainname,
+ validation->sam6->dns_domainname.string);
+ fstrcpy(resp->data.auth.info6.principal_name,
+ validation->sam6->principal_name.string);
+ }
+
+ ex = talloc_strdup(frame, "");
+ if (ex == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ for (i=0; i < info3->base.groups.count; i++) {
+ ex = talloc_asprintf_append_buffer(ex, "0x%08X:0x%08X\n",
+ info3->base.groups.rids[i].rid,
+ info3->base.groups.rids[i].attributes);
+ if (ex == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ for (i=0; i < info3->sidcount; i++) {
+ struct dom_sid_buf sidbuf;
+
+ ex = talloc_asprintf_append_buffer(
+ ex,
+ "%s:0x%08X\n",
+ dom_sid_str_buf(info3->sids[i].sid, &sidbuf),
+ info3->sids[i].attributes);
+ if (ex == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+ }
+
+ resp->length += talloc_get_size(ex);
+ resp->extra_data.data = talloc_move(mem_ctx, &ex);
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS append_info3_as_ndr(TALLOC_CTX *mem_ctx,
+ struct winbindd_response *resp,
+ struct netr_SamInfo3 *info3)
+{
+ DATA_BLOB blob;
+ enum ndr_err_code ndr_err;
+
+ ndr_err = ndr_push_struct_blob(&blob, mem_ctx, info3,
+ (ndr_push_flags_fn_t)ndr_push_netr_SamInfo3);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DEBUG(0,("append_info3_as_ndr: failed to append\n"));
+ return ndr_map_error2ntstatus(ndr_err);
+ }
+
+ resp->extra_data.data = blob.data;
+ resp->length += blob.length;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS append_unix_username(uint16_t validation_level,
+ union netr_Validation *validation,
+ const char *name_domain,
+ const char *name_user,
+ TALLOC_CTX *mem_ctx,
+ char **_unix_username)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ const char *nt_username = NULL;
+ const char *nt_domain = NULL;
+ char *unix_username = NULL;
+ struct netr_SamBaseInfo *base_info = NULL;
+ NTSTATUS status;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* We've been asked to return the unix username, per
+ 'winbind use default domain' settings and the like */
+
+ switch (validation_level) {
+ case 3:
+ base_info = &validation->sam3->base;
+ break;
+ case 6:
+ base_info = &validation->sam6->base;
+ break;
+ default:
+ DBG_ERR("Invalid validation level %d\n", validation_level);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ nt_domain = talloc_strdup(tmp_ctx, base_info->logon_domain.string);
+ if (!nt_domain) {
+ /* If the server didn't give us one, just use the one
+ * we sent them */
+ nt_domain = name_domain;
+ }
+
+ nt_username = talloc_strdup(tmp_ctx, base_info->account_name.string);
+ if (!nt_username) {
+ /* If the server didn't give us one, just use the one
+ * we sent them */
+ nt_username = name_user;
+ }
+
+ unix_username = fill_domain_username_talloc(tmp_ctx,
+ nt_domain,
+ nt_username,
+ true);
+ if (unix_username == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ DBG_INFO("Setting unix username to [%s]\n", unix_username);
+
+ *_unix_username = talloc_move(mem_ctx, &unix_username);
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+static NTSTATUS append_afs_token(uint16_t validation_level,
+ union netr_Validation *validation,
+ const char *name_domain,
+ const char *name_user,
+ TALLOC_CTX *mem_ctx,
+ DATA_BLOB *_blob)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ char *afsname = NULL;
+ char *cell;
+ char *token;
+ struct netr_SamBaseInfo *base_info = NULL;
+ NTSTATUS status;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ switch (validation_level) {
+ case 3:
+ base_info = &validation->sam3->base;
+ break;
+ case 6:
+ base_info = &validation->sam6->base;
+ break;
+ default:
+ DBG_ERR("Invalid validation level %d\n", validation_level);
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto out;
+ }
+
+ afsname = talloc_strdup(tmp_ctx, lp_afs_username_map());
+ if (afsname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ afsname = talloc_string_sub(tmp_ctx,
+ lp_afs_username_map(),
+ "%D", name_domain);
+ afsname = talloc_string_sub(tmp_ctx, afsname,
+ "%u", name_user);
+ afsname = talloc_string_sub(tmp_ctx, afsname,
+ "%U", name_user);
+
+ {
+ struct dom_sid user_sid;
+ struct dom_sid_buf sidstr;
+
+ sid_compose(&user_sid, base_info->domain_sid, base_info->rid);
+ afsname = talloc_string_sub(
+ tmp_ctx,
+ afsname,
+ "%s",
+ dom_sid_str_buf(&user_sid, &sidstr));
+ }
+
+ if (afsname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (!strlower_m(afsname)) {
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ DEBUG(10, ("Generating token for user %s\n", afsname));
+
+ cell = strchr(afsname, '@');
+
+ if (cell == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ *cell = '\0';
+ cell += 1;
+
+ token = afs_createtoken_str(afsname, cell);
+ if (token == NULL) {
+ status = NT_STATUS_OK;
+ goto out;
+ }
+
+ talloc_steal(mem_ctx, token);
+ *_blob = data_blob_string_const_null(token);
+
+ status = NT_STATUS_OK;
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+NTSTATUS extra_data_to_sid_array(const char *group_sid,
+ TALLOC_CTX *mem_ctx,
+ struct wbint_SidArray **_sid_array)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ struct wbint_SidArray *sid_array = NULL;
+ struct dom_sid *require_membership_of_sid = NULL;
+ uint32_t num_require_membership_of_sid = 0;
+ char *req_sid = NULL;
+ const char *p = NULL;
+ NTSTATUS status;
+
+ if (_sid_array == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ *_sid_array = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ sid_array = talloc_zero(tmp_ctx, struct wbint_SidArray);
+ if (sid_array == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+
+ if (!group_sid || !group_sid[0]) {
+ /* NO sid supplied, all users may access */
+ status = NT_STATUS_OK;
+ /*
+ * Always return an allocated wbint_SidArray,
+ * even if the array is empty.
+ */
+ goto out;
+ }
+
+ num_require_membership_of_sid = 0;
+ require_membership_of_sid = NULL;
+ p = group_sid;
+
+ while (next_token_talloc(tmp_ctx, &p, &req_sid, ",")) {
+ struct dom_sid sid;
+
+ if (!string_to_sid(&sid, req_sid)) {
+ DBG_ERR("check_info3_in_group: could not parse %s "
+ "as a SID!\n", req_sid);
+ status = NT_STATUS_INVALID_PARAMETER;
+ goto fail;
+ }
+
+ status = add_sid_to_array(tmp_ctx, &sid,
+ &require_membership_of_sid,
+ &num_require_membership_of_sid);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("add_sid_to_array failed\n");
+ goto fail;
+ }
+ }
+
+ sid_array->num_sids = num_require_membership_of_sid;
+ sid_array->sids = talloc_move(sid_array, &require_membership_of_sid);
+
+ status = NT_STATUS_OK;
+out:
+ *_sid_array = talloc_move(mem_ctx, &sid_array);
+
+fail:
+ TALLOC_FREE(tmp_ctx);
+
+ return status;
+}
+
+static NTSTATUS check_info3_in_group(struct netr_SamInfo3 *info3,
+ struct wbint_SidArray *sid_array)
+/**
+ * Check whether a user belongs to a group or list of groups.
+ *
+ * @param mem_ctx talloc memory context.
+ * @param info3 user information, including group membership info.
+ * @param group_sid One or more groups , separated by commas.
+ *
+ * @return NT_STATUS_OK on success,
+ * NT_STATUS_LOGON_FAILURE if the user does not belong,
+ * or other NT_STATUS_IS_ERR(status) for other kinds of failure.
+ */
+{
+ size_t i;
+ struct security_token *token;
+ NTSTATUS status;
+
+ /* Parse the 'required group' SID */
+
+ if (sid_array == NULL || sid_array->num_sids == 0) {
+ /* NO sid supplied, all users may access */
+ return NT_STATUS_OK;
+ }
+
+ /*
+ * This is a limited-use security_token for the purpose of
+ * checking the SID list below, so no claims need to be added
+ * and se_access_check() will never run.
+ */
+ token = security_token_initialise(talloc_tos(),
+ CLAIMS_EVALUATION_INVALID_STATE);
+ if (token == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = sid_array_from_info3(token, info3,
+ &token->sids,
+ &token->num_sids,
+ true);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(status = add_aliases(get_global_sam_sid(),
+ token))
+ || !NT_STATUS_IS_OK(status = add_aliases(&global_sid_Builtin,
+ token))) {
+ DEBUG(3, ("could not add aliases: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ security_token_debug(DBGC_CLASS, 10, token);
+
+ for (i=0; i<sid_array->num_sids; i++) {
+ struct dom_sid_buf buf;
+ DEBUG(10, ("Checking SID %s\n",
+ dom_sid_str_buf(&sid_array->sids[i],
+ &buf)));
+ if (nt_token_check_sid(&sid_array->sids[i],
+ token)) {
+ DEBUG(10, ("Access ok\n"));
+ return NT_STATUS_OK;
+ }
+ }
+
+ /* Do not distinguish this error from a wrong username/pw */
+
+ return NT_STATUS_LOGON_FAILURE;
+}
+
+struct winbindd_domain *find_auth_domain(uint8_t flags,
+ const char *domain_name)
+{
+ struct winbindd_domain *domain;
+
+ if (IS_DC) {
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ DEBUG(3, ("Authentication for domain [%s] refused "
+ "as it is not a trusted domain\n",
+ domain_name));
+ return NULL;
+ }
+
+ if (domain->secure_channel_type != SEC_CHAN_NULL) {
+ return domain;
+ }
+
+ return domain->routing_domain;
+ }
+
+ if (strequal(domain_name, get_global_sam_name())) {
+ return find_domain_from_name_noinit(domain_name);
+ }
+
+ if (lp_winbind_use_krb5_enterprise_principals()) {
+ /*
+ * If we use enterprise principals
+ * we always go through our primary domain
+ * and follow the WRONG_REALM replies.
+ */
+ flags &= ~WBFLAG_PAM_CONTACT_TRUSTDOM;
+ }
+
+ /* we can auth against trusted domains */
+ if (flags & WBFLAG_PAM_CONTACT_TRUSTDOM) {
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ DEBUG(3, ("Authentication for domain [%s] skipped "
+ "as it is not a trusted domain\n",
+ domain_name));
+ } else {
+ return domain;
+ }
+ }
+
+ return find_our_domain();
+}
+
+static NTSTATUS get_password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 **_policy)
+{
+ NTSTATUS status;
+ struct samr_DomInfo1 *policy = NULL;
+
+ if ( !winbindd_can_contact_domain( domain ) ) {
+ DBG_INFO("No inbound trust to contact domain %s\n",
+ domain->name);
+ return NT_STATUS_NOT_SUPPORTED;
+ }
+
+ policy = talloc_zero(mem_ctx, struct samr_DomInfo1);
+ if (policy == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = wb_cache_password_policy(domain, mem_ctx, policy);
+ if (NT_STATUS_IS_ERR(status)) {
+ TALLOC_FREE(policy);
+ return status;
+ }
+
+ *_policy = talloc_move(mem_ctx, &policy);
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS get_max_bad_attempts_from_lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint16_t *lockout_threshold)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ struct samr_DomInfo12 lockout_policy;
+
+ *lockout_threshold = 0;
+
+ status = wb_cache_lockout_policy(domain, mem_ctx, &lockout_policy);
+ if (NT_STATUS_IS_ERR(status)) {
+ return status;
+ }
+
+ *lockout_threshold = lockout_policy.lockout_threshold;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS get_pwd_properties(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *password_properties)
+{
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ struct samr_DomInfo1 password_policy;
+
+ *password_properties = 0;
+
+ status = wb_cache_password_policy(domain, mem_ctx, &password_policy);
+ if (NT_STATUS_IS_ERR(status)) {
+ return status;
+ }
+
+ *password_properties = password_policy.password_properties;
+
+ return NT_STATUS_OK;
+}
+
+#ifdef HAVE_KRB5
+
+static const char *generate_krb5_ccache(TALLOC_CTX *mem_ctx,
+ const char *type,
+ uid_t uid,
+ const char **user_ccache_file)
+{
+ /* accept FILE and WRFILE as krb5_cc_type from the client and then
+ * build the full ccname string based on the user's uid here -
+ * Guenther*/
+
+ const char *gen_cc = NULL;
+
+ if (uid != -1) {
+ if (strequal(type, "FILE")) {
+ gen_cc = talloc_asprintf(
+ mem_ctx, "FILE:/tmp/krb5cc_%d", uid);
+ }
+ if (strequal(type, "WRFILE")) {
+ gen_cc = talloc_asprintf(
+ mem_ctx, "WRFILE:/tmp/krb5cc_%d", uid);
+ }
+ if (strequal(type, "KEYRING")) {
+ gen_cc = talloc_asprintf(
+ mem_ctx, "KEYRING:persistent:%d", uid);
+ }
+ if (strequal(type, "KCM")) {
+ gen_cc = talloc_asprintf(mem_ctx,
+ "KCM:%d",
+ uid);
+ }
+
+ if (strnequal(type, "FILE:/", 6) ||
+ strnequal(type, "WRFILE:/", 8) ||
+ strnequal(type, "DIR:/", 5)) {
+
+ /* we allow only one "%u" substitution */
+
+ char *p;
+
+ p = strchr(type, '%');
+ if (p != NULL) {
+
+ p++;
+
+ if (p != NULL && *p == 'u' && strchr(p, '%') == NULL) {
+ char uid_str[sizeof("18446744073709551615")];
+
+ snprintf(uid_str, sizeof(uid_str), "%u", uid);
+
+ gen_cc = talloc_string_sub2(mem_ctx,
+ type,
+ "%u",
+ uid_str,
+ /* remove_unsafe_characters */
+ false,
+ /* replace_once */
+ true,
+ /* allow_trailing_dollar */
+ false);
+ }
+ }
+ }
+ }
+
+ *user_ccache_file = gen_cc;
+
+ if (gen_cc == NULL) {
+ gen_cc = talloc_strdup(mem_ctx, "MEMORY:winbindd_pam_ccache");
+ }
+ if (gen_cc == NULL) {
+ DEBUG(0,("out of memory\n"));
+ return NULL;
+ }
+
+ DEBUG(10, ("using ccache: %s%s\n", gen_cc,
+ (*user_ccache_file == NULL) ? " (internal)":""));
+
+ return gen_cc;
+}
+
+#endif
+
+uid_t get_uid_from_request(struct winbindd_request *request)
+{
+ uid_t uid;
+
+ uid = request->data.auth.uid;
+
+ if (uid == (uid_t)-1) {
+ DEBUG(1,("invalid uid: '%u'\n", (unsigned int)uid));
+ return -1;
+ }
+ return uid;
+}
+
+/**********************************************************************
+ Authenticate a user with a clear text password using Kerberos and fill up
+ ccache if required
+ **********************************************************************/
+
+static NTSTATUS winbindd_raw_kerberos_login(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ const char *user,
+ const char *pass,
+ const char *krb5_cc_type,
+ uid_t uid,
+ struct netr_SamInfo6 **info6,
+ const char **_krb5ccname)
+{
+#ifdef HAVE_KRB5
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ krb5_error_code krb5_ret;
+ const char *cc = NULL;
+ const char *principal_s = NULL;
+ char *realm = NULL;
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *name_user = NULL;
+ time_t ticket_lifetime = 0;
+ time_t renewal_until = 0;
+ time_t time_offset = 0;
+ const char *user_ccache_file;
+ struct PAC_LOGON_INFO *logon_info = NULL;
+ struct PAC_UPN_DNS_INFO *upn_dns_info = NULL;
+ struct PAC_DATA *pac_data = NULL;
+ struct PAC_DATA_CTR *pac_data_ctr = NULL;
+ const char *local_service;
+ uint32_t i;
+ struct netr_SamInfo6 *info6_copy = NULL;
+ char *canon_principal = NULL;
+ char *canon_realm = NULL;
+ bool ok;
+
+ *info6 = NULL;
+
+ if (domain->alt_name == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (_krb5ccname != NULL) {
+ *_krb5ccname = NULL;
+ }
+
+ /* 1st step:
+ * prepare a krb5_cc_cache string for the user */
+
+ if (uid == -1) {
+ DEBUG(0,("no valid uid\n"));
+ }
+
+ cc = generate_krb5_ccache(mem_ctx,
+ krb5_cc_type,
+ uid,
+ &user_ccache_file);
+ if (cc == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+
+ /* 2nd step:
+ * get kerberos properties */
+
+ if (domain->backend_data.ads_conn != NULL) {
+ time_offset = domain->backend_data.ads_conn->auth.time_offset;
+ }
+
+
+ /* 3rd step:
+ * do kerberos auth and setup ccache as the user */
+
+ ok = parse_domain_user(mem_ctx,
+ user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ realm = talloc_strdup(mem_ctx, domain->alt_name);
+ if (realm == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (!strupper_m(realm)) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (lp_winbind_use_krb5_enterprise_principals() &&
+ name_namespace[0] != '\0')
+ {
+ principal_s = talloc_asprintf(mem_ctx,
+ "%s@%s@%s",
+ name_user,
+ name_namespace,
+ realm);
+ } else {
+ principal_s = talloc_asprintf(mem_ctx,
+ "%s@%s",
+ name_user,
+ realm);
+ }
+ if (principal_s == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ local_service = talloc_asprintf(mem_ctx, "%s$@%s",
+ lp_netbios_name(), lp_realm());
+ if (local_service == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+
+ /* if this is a user ccache, we need to act as the user to let the krb5
+ * library handle the chown, etc. */
+
+ /************************ ENTERING NON-ROOT **********************/
+
+ if (user_ccache_file != NULL) {
+ set_effective_uid(uid);
+ DEBUG(10,("winbindd_raw_kerberos_login: uid is %d\n", uid));
+ }
+
+ result = kerberos_return_pac(mem_ctx,
+ principal_s,
+ pass,
+ time_offset,
+ &ticket_lifetime,
+ &renewal_until,
+ cc,
+ true,
+ true,
+ WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ NULL,
+ local_service,
+ &canon_principal,
+ &canon_realm,
+ &pac_data_ctr);
+ if (user_ccache_file != NULL) {
+ gain_root_privilege();
+ }
+
+ /************************ RETURNED TO ROOT **********************/
+
+ if (!NT_STATUS_IS_OK(result)) {
+ goto failed;
+ }
+
+ if (pac_data_ctr == NULL) {
+ goto failed;
+ }
+
+ pac_data = pac_data_ctr->pac_data;
+ if (pac_data == NULL) {
+ goto failed;
+ }
+
+ for (i=0; i < pac_data->num_buffers; i++) {
+
+ if (pac_data->buffers[i].type == PAC_TYPE_LOGON_INFO) {
+ logon_info = pac_data->buffers[i].info->logon_info.info;
+ continue;
+ }
+
+ if (pac_data->buffers[i].type == PAC_TYPE_UPN_DNS_INFO) {
+ upn_dns_info = &pac_data->buffers[i].info->upn_dns_info;
+ continue;
+ }
+ }
+
+ if (logon_info == NULL) {
+ DEBUG(10,("Missing logon_info in ticket of %s\n",
+ principal_s));
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DEBUG(10,("winbindd_raw_kerberos_login: winbindd validated ticket of %s\n",
+ principal_s));
+
+ result = create_info6_from_pac(mem_ctx, logon_info,
+ upn_dns_info, &info6_copy);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto failed;
+ }
+
+ /* if we had a user's ccache then return that string for the pam
+ * environment */
+
+ if (user_ccache_file != NULL) {
+
+ if (_krb5ccname != NULL) {
+ *_krb5ccname = talloc_steal(mem_ctx, user_ccache_file);
+ }
+
+ result = add_ccache_to_list(principal_s,
+ cc,
+ user,
+ pass,
+ realm,
+ uid,
+ time(NULL),
+ ticket_lifetime,
+ renewal_until,
+ false,
+ canon_principal,
+ canon_realm);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_raw_kerberos_login: failed to add ccache to list: %s\n",
+ nt_errstr(result)));
+ }
+ } else {
+
+ /* need to delete the memory cred cache, it is not used anymore */
+
+ krb5_ret = ads_kdestroy(cc);
+ if (krb5_ret) {
+ DEBUG(3,("winbindd_raw_kerberos_login: "
+ "could not destroy krb5 credential cache: "
+ "%s\n", error_message(krb5_ret)));
+ }
+
+ }
+ *info6 = info6_copy;
+ return NT_STATUS_OK;
+
+failed:
+ /*
+ * Do not delete an existing valid credential cache, if the user
+ * e.g. enters a wrong password
+ */
+ if ((strequal(krb5_cc_type, "FILE") || strequal(krb5_cc_type, "WRFILE"))
+ && user_ccache_file != NULL) {
+ return result;
+ }
+
+ /* we could have created a new credential cache with a valid tgt in it
+ * but we weren't able to get or verify the service ticket for this
+ * local host and therefore didn't get the PAC, we need to remove that
+ * cache entirely now */
+
+ krb5_ret = ads_kdestroy(cc);
+ if (krb5_ret) {
+ DEBUG(3,("winbindd_raw_kerberos_login: "
+ "could not destroy krb5 credential cache: "
+ "%s\n", error_message(krb5_ret)));
+ }
+
+ if (!NT_STATUS_IS_OK(remove_ccache(user))) {
+ DEBUG(3,("winbindd_raw_kerberos_login: "
+ "could not remove ccache for user %s\n",
+ user));
+ }
+
+ return result;
+#else
+ return NT_STATUS_NOT_SUPPORTED;
+#endif /* HAVE_KRB5 */
+}
+
+/****************************************************************
+****************************************************************/
+
+bool check_request_flags(uint32_t flags)
+{
+ uint32_t flags_edata = WBFLAG_PAM_AFS_TOKEN |
+ WBFLAG_PAM_INFO3_TEXT |
+ WBFLAG_PAM_INFO3_NDR;
+
+ if ( ( (flags & flags_edata) == WBFLAG_PAM_AFS_TOKEN) ||
+ ( (flags & flags_edata) == WBFLAG_PAM_INFO3_NDR) ||
+ ( (flags & flags_edata) == WBFLAG_PAM_INFO3_TEXT)||
+ !(flags & flags_edata) ) {
+ return true;
+ }
+
+ DEBUG(1, ("check_request_flags: invalid request flags[0x%08X]\n",
+ flags));
+
+ return false;
+}
+
+/****************************************************************
+****************************************************************/
+
+NTSTATUS append_auth_data(TALLOC_CTX *mem_ctx,
+ struct winbindd_response *resp,
+ uint32_t request_flags,
+ uint16_t validation_level,
+ union netr_Validation *validation,
+ const char *name_domain,
+ const char *name_user)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+
+ result = map_validation_to_info3(talloc_tos(),
+ validation_level,
+ validation,
+ &info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto out;
+ }
+
+ if (request_flags & WBFLAG_PAM_USER_SESSION_KEY) {
+ memcpy(resp->data.auth.user_session_key,
+ info3->base.key.key,
+ sizeof(resp->data.auth.user_session_key)
+ /* 16 */);
+ }
+
+ if (request_flags & WBFLAG_PAM_LMKEY) {
+ memcpy(resp->data.auth.first_8_lm_hash,
+ info3->base.LMSessKey.key,
+ sizeof(resp->data.auth.first_8_lm_hash)
+ /* 8 */);
+ }
+
+ if (request_flags & WBFLAG_PAM_UNIX_NAME) {
+ char *unix_username = NULL;
+ result = append_unix_username(validation_level,
+ validation,
+ name_domain,
+ name_user,
+ mem_ctx,
+ &unix_username);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append Unix Username: %s\n",
+ nt_errstr(result)));
+ goto out;
+ }
+ fstrcpy(resp->data.auth.unix_username, unix_username);
+ TALLOC_FREE(unix_username);
+ }
+
+ /* currently, anything from here on potentially overwrites extra_data. */
+
+ if (request_flags & WBFLAG_PAM_INFO3_NDR) {
+ result = append_info3_as_ndr(mem_ctx, resp, info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append INFO3 (NDR): %s\n",
+ nt_errstr(result)));
+ goto out;
+ }
+ }
+
+ if (request_flags & WBFLAG_PAM_INFO3_TEXT) {
+ result = append_info3_as_txt(mem_ctx, resp,
+ validation_level,
+ validation);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append INFO3 (TXT): %s\n",
+ nt_errstr(result)));
+ goto out;
+ }
+ }
+
+ if (request_flags & WBFLAG_PAM_AFS_TOKEN) {
+ DATA_BLOB blob = data_blob_null;
+ result = append_afs_token(validation_level,
+ validation,
+ name_domain,
+ name_user,
+ mem_ctx,
+ &blob);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("Failed to append AFS token: %s\n",
+ nt_errstr(result)));
+ goto out;
+ }
+ resp->extra_data.data = blob.data;
+ resp->length += blob.length;
+ }
+
+ result = NT_STATUS_OK;
+out:
+ TALLOC_FREE(info3);
+ return result;
+}
+
+static NTSTATUS winbindd_dual_pam_auth_cached(struct winbindd_domain *domain,
+ bool krb5_auth,
+ const char *user,
+ const char *pass,
+ const char *krb5_cc_type,
+ uid_t uid,
+ TALLOC_CTX *mem_ctx,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation,
+ const char **_krb5ccname)
+{
+ TALLOC_CTX *tmp_ctx = NULL;
+ NTSTATUS result = NT_STATUS_LOGON_FAILURE;
+ uint16_t max_allowed_bad_attempts;
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *name_user = NULL;
+ struct dom_sid sid;
+ enum lsa_SidType type;
+ uchar new_nt_pass[NT_HASH_LEN];
+ const uint8_t *cached_nt_pass;
+ const uint8_t *cached_salt;
+ struct netr_SamInfo3 *my_info3;
+ time_t kickoff_time, must_change_time;
+ bool password_good = false;
+ bool ok;
+#ifdef HAVE_KRB5
+ struct winbindd_tdc_domain *tdc_domain = NULL;
+#endif
+
+ if (_validation == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ *_validation = NULL;
+
+ if (_krb5ccname != NULL) {
+ *_krb5ccname = NULL;
+ }
+
+ DEBUG(10,("winbindd_dual_pam_auth_cached\n"));
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* Parse domain and username */
+
+ ok = parse_domain_user(tmp_ctx,
+ user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ DBG_DEBUG("parse_domain_user failed\n");
+ result = NT_STATUS_NO_SUCH_USER;
+ goto out;
+ }
+
+ if (!lookup_cached_name(name_namespace,
+ name_domain,
+ name_user,
+ &sid,
+ &type)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: no such user in the cache\n"));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto out;
+ }
+
+ if (type != SID_NAME_USER) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: not a user (%s)\n", sid_type_lookup(type)));
+ result = NT_STATUS_LOGON_FAILURE;
+ goto out;
+ }
+
+ result = winbindd_get_creds(domain,
+ tmp_ctx,
+ &sid,
+ &my_info3,
+ &cached_nt_pass,
+ &cached_salt);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get creds: %s\n", nt_errstr(result)));
+ goto out;
+ }
+
+ E_md4hash(pass, new_nt_pass);
+
+ dump_data_pw("new_nt_pass", new_nt_pass, NT_HASH_LEN);
+ dump_data_pw("cached_nt_pass", cached_nt_pass, NT_HASH_LEN);
+ if (cached_salt) {
+ dump_data_pw("cached_salt", cached_salt, NT_HASH_LEN);
+ }
+
+ if (cached_salt) {
+ /* In this case we didn't store the nt_hash itself,
+ but the MD5 combination of salt + nt_hash. */
+ uchar salted_hash[NT_HASH_LEN];
+ gnutls_hash_hd_t hash_hnd = NULL;
+ int rc;
+
+ rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5);
+ if (rc < 0) {
+ result = gnutls_error_to_ntstatus(
+ rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ goto out;
+ }
+
+ rc = gnutls_hash(hash_hnd, cached_salt, 16);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ result = gnutls_error_to_ntstatus(
+ rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ goto out;
+ }
+ rc = gnutls_hash(hash_hnd, new_nt_pass, 16);
+ if (rc < 0) {
+ gnutls_hash_deinit(hash_hnd, NULL);
+ result = gnutls_error_to_ntstatus(
+ rc, NT_STATUS_HASH_NOT_SUPPORTED);
+ goto out;
+ }
+ gnutls_hash_deinit(hash_hnd, salted_hash);
+
+ password_good = mem_equal_const_time(cached_nt_pass, salted_hash,
+ NT_HASH_LEN);
+ } else {
+ /* Old cached cred - direct store of nt_hash (bad bad bad !). */
+ password_good = mem_equal_const_time(cached_nt_pass, new_nt_pass,
+ NT_HASH_LEN);
+ }
+
+ if (password_good) {
+
+ /* User *DOES* know the password, update logon_time and reset
+ * bad_pw_count */
+
+ my_info3->base.user_flags |= NETLOGON_CACHED_ACCOUNT;
+
+ if (my_info3->base.acct_flags & ACB_AUTOLOCK) {
+ result = NT_STATUS_ACCOUNT_LOCKED_OUT;
+ goto out;
+ }
+
+ if (my_info3->base.acct_flags & ACB_DISABLED) {
+ result = NT_STATUS_ACCOUNT_DISABLED;
+ goto out;
+ }
+
+ if (my_info3->base.acct_flags & ACB_WSTRUST) {
+ result = NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT;
+ goto out;
+ }
+
+ if (my_info3->base.acct_flags & ACB_SVRTRUST) {
+ result = NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT;
+ goto out;
+ }
+
+ if (my_info3->base.acct_flags & ACB_DOMTRUST) {
+ result = NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT;
+ goto out;
+ }
+
+ if (!(my_info3->base.acct_flags & ACB_NORMAL)) {
+ DEBUG(0,("winbindd_dual_pam_auth_cached: what's wrong with that one?: 0x%08x\n",
+ my_info3->base.acct_flags));
+ result = NT_STATUS_LOGON_FAILURE;
+ goto out;
+ }
+
+ kickoff_time = nt_time_to_unix(my_info3->base.kickoff_time);
+ if (kickoff_time != 0 && time(NULL) > kickoff_time) {
+ result = NT_STATUS_ACCOUNT_EXPIRED;
+ goto out;
+ }
+
+ must_change_time = nt_time_to_unix(my_info3->base.force_password_change);
+ if (must_change_time != 0 && must_change_time < time(NULL)) {
+ /* we allow grace logons when the password has expired */
+ my_info3->base.user_flags |= NETLOGON_GRACE_LOGON;
+ /* return NT_STATUS_PASSWORD_EXPIRED; */
+ goto success;
+ }
+
+#ifdef HAVE_KRB5
+ if ((krb5_auth) &&
+ ((tdc_domain = wcache_tdc_fetch_domain(tmp_ctx, name_domain)) != NULL) &&
+ ((tdc_domain->trust_type & LSA_TRUST_TYPE_UPLEVEL) ||
+ /* used to cope with the case winbindd starting without network. */
+ !strequal(tdc_domain->domain_name, tdc_domain->dns_name))) {
+ const char *cc = NULL;
+ char *realm = NULL;
+ const char *principal_s = NULL;
+ const char *user_ccache_file;
+
+ if (domain->alt_name == NULL) {
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ if (uid == -1) {
+ DEBUG(0,("winbindd_dual_pam_auth_cached: invalid uid\n"));
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ cc = generate_krb5_ccache(tmp_ctx,
+ krb5_cc_type,
+ uid,
+ &user_ccache_file);
+ if (cc == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ realm = talloc_strdup(tmp_ctx, domain->alt_name);
+ if (realm == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (!strupper_m(realm)) {
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto out;
+ }
+
+ principal_s = talloc_asprintf(tmp_ctx, "%s@%s", name_user, realm);
+ if (principal_s == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto out;
+ }
+
+ if (user_ccache_file != NULL) {
+
+ if (_krb5ccname != NULL) {
+ *_krb5ccname = talloc_move(mem_ctx,
+ &user_ccache_file);
+ }
+
+ result = add_ccache_to_list(principal_s,
+ cc,
+ user,
+ pass,
+ realm,
+ uid,
+ time(NULL),
+ time(NULL) + lp_winbind_cache_time(),
+ time(NULL) + WINBINDD_PAM_AUTH_KRB5_RENEW_TIME,
+ true,
+ principal_s,
+ realm);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed "
+ "to add ccache to list: %s\n",
+ nt_errstr(result)));
+ }
+ }
+ }
+#endif /* HAVE_KRB5 */
+ success:
+ /* FIXME: we possibly should handle logon hours as well (does xp when
+ * offline?) see auth/auth_sam.c:sam_account_ok for details */
+
+ unix_to_nt_time(&my_info3->base.logon_time, time(NULL));
+ my_info3->base.bad_password_count = 0;
+
+ result = winbindd_update_creds_by_info3(domain,
+ user,
+ pass,
+ my_info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1,("winbindd_dual_pam_auth_cached: failed to update creds: %s\n",
+ nt_errstr(result)));
+ goto out;
+ }
+
+ result = map_info3_to_validation(mem_ctx,
+ my_info3,
+ _validation_level,
+ _validation);
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_ERR("map_info3_to_validation failed: %s\n",
+ nt_errstr(result));
+ goto out;
+ }
+
+ result = NT_STATUS_OK;
+ goto out;
+ }
+
+ /* User does *NOT* know the correct password, modify info3 accordingly, but only if online */
+ if (domain->online == false) {
+ goto failed;
+ }
+
+ /* failure of this is not critical */
+ result = get_max_bad_attempts_from_lockout_policy(domain, tmp_ctx, &max_allowed_bad_attempts);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get max_allowed_bad_attempts. "
+ "Won't be able to honour account lockout policies\n"));
+ }
+
+ /* increase counter */
+ my_info3->base.bad_password_count++;
+
+ if (max_allowed_bad_attempts == 0) {
+ goto failed;
+ }
+
+ /* lockout user */
+ if (my_info3->base.bad_password_count >= max_allowed_bad_attempts) {
+
+ uint32_t password_properties;
+
+ result = get_pwd_properties(domain, tmp_ctx, &password_properties);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached: failed to get password properties.\n"));
+ }
+
+ if ((my_info3->base.rid != DOMAIN_RID_ADMINISTRATOR) ||
+ (password_properties & DOMAIN_PASSWORD_LOCKOUT_ADMINS)) {
+ my_info3->base.acct_flags |= ACB_AUTOLOCK;
+ }
+ }
+
+failed:
+ result = winbindd_update_creds_by_info3(domain, user, NULL, my_info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(0,("winbindd_dual_pam_auth_cached: failed to update creds %s\n",
+ nt_errstr(result)));
+ }
+
+ result = NT_STATUS_LOGON_FAILURE;
+
+out:
+ TALLOC_FREE(tmp_ctx);
+
+ return result;
+}
+
+static NTSTATUS winbindd_dual_pam_auth_kerberos(struct winbindd_domain *domain,
+ const char *user,
+ const char *pass,
+ const char *krb5_cc_type,
+ uid_t uid,
+ TALLOC_CTX *mem_ctx,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation,
+ const char **_krb5ccname)
+{
+ struct netr_SamInfo6 *info6 = NULL;
+ struct winbindd_domain *contact_domain;
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *name_user = NULL;
+ NTSTATUS result;
+ bool ok;
+
+ DEBUG(10,("winbindd_dual_pam_auth_kerberos\n"));
+
+ /* Parse domain and username */
+
+ ok = parse_domain_user(mem_ctx,
+ user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ /* what domain should we contact? */
+
+ if (lp_winbind_use_krb5_enterprise_principals()) {
+ contact_domain = find_auth_domain(0, name_namespace);
+ } else {
+ contact_domain = find_domain_from_name(name_namespace);
+ }
+ if (contact_domain == NULL) {
+ DEBUG(3, ("Authentication for domain for [%s] -> [%s]\\[%s] failed as %s is not a trusted domain\n",
+ user, name_domain, name_user, name_namespace));
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+
+ if (contact_domain->initialized &&
+ contact_domain->active_directory) {
+ goto try_login;
+ }
+
+ if (!contact_domain->initialized) {
+ init_dc_connection(contact_domain, false);
+ }
+
+ if (!contact_domain->active_directory) {
+ DEBUG(3,("krb5 auth requested but domain (%s) is not Active Directory\n",
+ contact_domain->name));
+ return NT_STATUS_INVALID_LOGON_TYPE;
+ }
+try_login:
+ result = winbindd_raw_kerberos_login(
+ mem_ctx,
+ contact_domain,
+ user,
+ pass,
+ krb5_cc_type,
+ uid,
+ &info6,
+ _krb5ccname);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ result = map_info6_to_validation(mem_ctx,
+ info6,
+ _validation_level,
+ _validation);
+ TALLOC_FREE(info6);
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_ERR("map_info6_to_validation failed: %s\n",
+ nt_errstr(result));
+ }
+
+done:
+ return result;
+}
+
+static NTSTATUS winbindd_dual_auth_passdb(TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *domain,
+ const char *user,
+ const uint64_t logon_id,
+ const char *client_name,
+ const int client_pid,
+ const DATA_BLOB *challenge,
+ const DATA_BLOB *lm_resp,
+ const DATA_BLOB *nt_resp,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ bool interactive,
+ uint8_t *pauthoritative,
+ struct netr_SamInfo3 **pinfo3)
+{
+ struct auth_context *auth_context;
+ struct auth_serversupplied_info *server_info;
+ struct auth_usersupplied_info *user_info = NULL;
+ struct netr_SamInfo3 *info3;
+ NTSTATUS status;
+ bool ok;
+ TALLOC_CTX *frame = talloc_stackframe();
+
+ /*
+ * We are authoritative by default
+ */
+ *pauthoritative = 1;
+
+ status = make_user_info(frame, &user_info, user, user, domain, domain,
+ lp_netbios_name(), remote, local,
+ "winbind",
+ lm_resp, nt_resp, NULL, NULL,
+ NULL, AUTH_PASSWORD_RESPONSE);
+ if (!NT_STATUS_IS_OK(status)) {
+ DEBUG(10, ("make_user_info failed: %s\n", nt_errstr(status)));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ user_info->logon_parameters = logon_parameters;
+ user_info->logon_id = logon_id;
+ user_info->auth_description = talloc_asprintf(
+ frame, "PASSDB, %s, %d", client_name, client_pid);
+ if (user_info->auth_description == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* We don't want to come back to winbindd or to do PAM account checks */
+ user_info->flags |= USER_INFO_INFO3_AND_NO_AUTHZ;
+
+ if (interactive) {
+ user_info->flags |= USER_INFO_INTERACTIVE_LOGON;
+ }
+
+ status = make_auth3_context_for_winbind(frame, &auth_context);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("make_auth3_context_for_winbind failed: %s\n",
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ ok = auth3_context_set_challenge(auth_context,
+ challenge->data, "fixed");
+ if (!ok) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = auth_check_ntlm_password(mem_ctx,
+ auth_context,
+ user_info,
+ &server_info,
+ pauthoritative);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ info3 = talloc_zero(mem_ctx, struct netr_SamInfo3);
+ if (info3 == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ status = serverinfo_to_SamInfo3(server_info, info3);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ TALLOC_FREE(info3);
+ DEBUG(0, ("serverinfo_to_SamInfo3 failed: %s\n",
+ nt_errstr(status)));
+ return status;
+ }
+
+ *pinfo3 = info3;
+ DBG_DEBUG("Authenticating user %s\\%s returned %s\n",
+ domain,
+ user,
+ nt_errstr(status));
+ TALLOC_FREE(frame);
+ return status;
+}
+
+static NTSTATUS winbind_samlogon_retry_loop(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t logon_parameters,
+ const char *username,
+ const char *password,
+ const char *domainname,
+ const char *workstation,
+ const uint64_t logon_id,
+ bool plaintext_given,
+ DATA_BLOB chal,
+ DATA_BLOB lm_response,
+ DATA_BLOB nt_response,
+ bool interactive,
+ uint8_t *authoritative,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ int attempts = 0;
+ int netr_attempts = 0;
+ bool retry = false;
+ bool valid_result = false;
+ NTSTATUS result;
+ enum netr_LogonInfoClass logon_type_i;
+ enum netr_LogonInfoClass logon_type_n;
+ uint16_t validation_level = UINT16_MAX;
+ union netr_Validation *validation = NULL;
+ TALLOC_CTX *base_ctx = NULL;
+ struct netr_SamBaseInfo *base_info = NULL;
+
+ do {
+ struct rpc_pipe_client *netlogon_pipe;
+ struct netlogon_creds_cli_context *netlogon_creds_ctx = NULL;
+
+ /*
+ * We should always reset authoritative to 1
+ * before calling a server again.
+ *
+ * Otherwise we could treat a local problem as
+ * non-authoritative.
+ */
+ *authoritative = 1;
+
+ retry = false;
+
+ D_DEBUG("Creating a DCERPC netlogon connection for SAM logon. "
+ "netlogon attempt: %d, samlogon attempt: %d.\n",
+ netr_attempts,
+ attempts);
+ result = cm_connect_netlogon_secure(domain, &netlogon_pipe,
+ &netlogon_creds_ctx);
+
+ if (NT_STATUS_EQUAL(result,
+ NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
+ /*
+ * This means we don't have a trust account.
+ */
+ *authoritative = 0;
+ result = NT_STATUS_NO_SUCH_USER;
+ break;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(3,("Could not open handle to NETLOGON pipe "
+ "(error: %s, attempts: %d)\n",
+ nt_errstr(result), netr_attempts));
+
+ reset_cm_connection_on_error(domain, NULL, result);
+
+ /* After the first retry always close the connection */
+ if (netr_attempts > 0) {
+ DEBUG(3, ("This is again a problem for this "
+ "particular call, forcing the close "
+ "of this connection\n"));
+ invalidate_cm_connection(domain);
+ }
+
+ /* After the second retry failover to the next DC */
+ if (netr_attempts > 1) {
+ /*
+ * If the netlogon server is not reachable then
+ * it is possible that the DC is rebuilding
+ * sysvol and shutdown netlogon for that time.
+ * We should failover to the next dc.
+ */
+ DEBUG(3, ("This is the third problem for this "
+ "particular call, adding DC to the "
+ "negative cache list: %s %s\n", domain->name, domain->dcname));
+ add_failed_connection_entry(domain->name,
+ domain->dcname,
+ result);
+ saf_delete(domain->name);
+ }
+
+ /* Only allow 3 retries */
+ if (netr_attempts < 3) {
+ DEBUG(3, ("The connection to netlogon "
+ "failed, retrying\n"));
+ netr_attempts++;
+ retry = true;
+ continue;
+ }
+ return result;
+ }
+
+ logon_type_i = NetlogonInteractiveInformation;
+ logon_type_n = NetlogonNetworkInformation;
+ if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
+ logon_type_i = NetlogonInteractiveTransitiveInformation;
+ logon_type_n = NetlogonNetworkTransitiveInformation;
+ }
+
+ if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) {
+ logon_type_i = NetlogonInteractiveTransitiveInformation;
+ logon_type_n = NetlogonNetworkTransitiveInformation;
+ }
+
+ if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE) {
+ logon_type_i = NetlogonInteractiveInformation;
+ logon_type_n = NetlogonNetworkInformation;
+ }
+
+ if (domain->domain_trust_attribs & LSA_TRUST_ATTRIBUTE_QUARANTINED_DOMAIN) {
+ logon_type_i = NetlogonInteractiveInformation;
+ logon_type_n = NetlogonNetworkInformation;
+ }
+
+ netr_attempts = 0;
+ if (plaintext_given) {
+ result = rpccli_netlogon_password_logon(
+ netlogon_creds_ctx,
+ netlogon_pipe->binding_handle,
+ mem_ctx,
+ logon_parameters,
+ domainname,
+ username,
+ password,
+ workstation,
+ logon_id,
+ logon_type_i,
+ authoritative,
+ flags,
+ &validation_level,
+ &validation);
+ } else if (interactive) {
+ result = rpccli_netlogon_interactive_logon(
+ netlogon_creds_ctx,
+ netlogon_pipe->binding_handle,
+ mem_ctx,
+ logon_parameters,
+ username,
+ domainname,
+ workstation,
+ logon_id,
+ lm_response,
+ nt_response,
+ logon_type_i,
+ authoritative,
+ flags,
+ &validation_level,
+ &validation);
+ } else {
+ result = rpccli_netlogon_network_logon(
+ netlogon_creds_ctx,
+ netlogon_pipe->binding_handle,
+ mem_ctx,
+ logon_parameters,
+ username,
+ domainname,
+ workstation,
+ logon_id,
+ chal,
+ lm_response,
+ nt_response,
+ logon_type_n,
+ authoritative,
+ flags,
+ &validation_level,
+ &validation);
+ }
+
+ /*
+ * we increment this after the "feature negotiation"
+ * for can_do_samlogon_ex and can_do_validation6
+ */
+ attempts += 1;
+
+ /* We have to try a second time as cm_connect_netlogon
+ might not yet have noticed that the DC has killed
+ our connection. */
+
+ retry = reset_cm_connection_on_error(domain,
+ netlogon_pipe->binding_handle,
+ result);
+ if (retry) {
+ DBG_PREFIX(attempts > 1 ? DBGLVL_NOTICE : DBGLVL_INFO, (
+ "This is problem %d for this "
+ "particular call,"
+ "DOMAIN[%s] DC[%s] - %s\n",
+ attempts,
+ domain->name,
+ domain->dcname,
+ nt_errstr(result)));
+ continue;
+ }
+
+ valid_result = true;
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) {
+ /*
+ * Got DCERPC_FAULT_OP_RNG_ERROR for SamLogon
+ * (no Ex). This happens against old Samba
+ * DCs, if LogonSamLogonEx() fails with an error
+ * e.g. NT_STATUS_NO_SUCH_USER or NT_STATUS_WRONG_PASSWORD.
+ *
+ * The server will log something like this:
+ * api_net_sam_logon_ex: Failed to marshall NET_R_SAM_LOGON_EX.
+ *
+ * This sets the whole connection into a fault_state mode
+ * and all following request get NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE.
+ *
+ * This also happens to our retry with LogonSamLogonWithFlags()
+ * and LogonSamLogon().
+ *
+ * In order to recover from this situation, we need to
+ * drop the connection.
+ */
+ invalidate_cm_connection(domain);
+ result = NT_STATUS_LOGON_FAILURE;
+ break;
+ }
+
+ } while ( (attempts < 3) && retry );
+
+ if (!valid_result) {
+ /*
+ * This matches what windows does. In a chain of transitive
+ * trusts the ACCESS_DENIED/authoritative=0 is not propagated
+ * instead of NT_STATUS_NO_LOGON_SERVERS/authoritative=1 is
+ * passed along the chain if there's no other DC is available.
+ */
+ DBG_WARNING("Mapping %s/authoritative=%u to "
+ "NT_STATUS_NO_LOGON_SERVERS/authoritative=1 for"
+ "USERNAME[%s] USERDOMAIN[%s] REMOTE-DOMAIN[%s] \n",
+ nt_errstr(result),
+ *authoritative,
+ username,
+ domainname,
+ domain->name);
+ *authoritative = 1;
+ return NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ switch (validation_level) {
+ case 3:
+ base_ctx = validation->sam3;
+ base_info = &validation->sam3->base;
+ break;
+ case 6:
+ base_ctx = validation->sam6;
+ base_info = &validation->sam6->base;
+ break;
+ default:
+ smb_panic(__location__);
+ }
+
+ if (base_info->acct_flags == 0 || base_info->account_name.string == NULL) {
+ struct dom_sid user_sid;
+ struct dom_sid_buf sid_buf;
+ const char *acct_flags_src = "server";
+ const char *acct_name_src = "server";
+
+ /*
+ * Handle the case where a NT4 DC does not fill in the acct_flags in
+ * the samlogon reply info3. Yes, in 2021, there are still admins
+ * around with real NT4 DCs.
+ *
+ * We used to call dcerpc_samr_QueryUserInfo(level=16) to fetch
+ * acct_flags, but as NT4 DCs reject authentication with workstation
+ * accounts with NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT, even if
+ * MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT is specified, we only ever got
+ * ACB_NORMAL back (maybe with ACB_PWNOEXP in addition).
+ *
+ * For network logons NT4 DCs also skip the
+ * account_name, so we have to fallback to the
+ * one given by the client.
+ */
+
+ if (base_info->acct_flags == 0) {
+ base_info->acct_flags = ACB_NORMAL;
+ if (base_info->force_password_change == NTTIME_MAX) {
+ base_info->acct_flags |= ACB_PWNOEXP;
+ }
+ acct_flags_src = "calculated";
+ }
+
+ if (base_info->account_name.string == NULL) {
+ base_info->account_name.string = talloc_strdup(base_ctx,
+ username);
+ if (base_info->account_name.string == NULL) {
+ TALLOC_FREE(validation);
+ return NT_STATUS_NO_MEMORY;
+ }
+ acct_name_src = "client";
+ }
+
+ sid_compose(&user_sid, base_info->domain_sid, base_info->rid);
+
+ DBG_DEBUG("Fallback to %s_acct_flags[0x%x] %s_acct_name[%s] for %s\n",
+ acct_flags_src,
+ base_info->acct_flags,
+ acct_name_src,
+ base_info->account_name.string,
+ dom_sid_str_buf(&user_sid, &sid_buf));
+ }
+
+ *_validation_level = validation_level;
+ *_validation = validation;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS nt_dual_auth_passdb(TALLOC_CTX *mem_ctx,
+ fstring name_user,
+ fstring name_domain,
+ const char *pass,
+ uint64_t logon_id,
+ const char *client_name,
+ const int client_pid,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ uint8_t *authoritative,
+ struct netr_SamInfo3 **info3)
+{
+ unsigned char local_nt_response[24];
+ uchar chal[8];
+ DATA_BLOB chal_blob;
+ DATA_BLOB lm_resp;
+ DATA_BLOB nt_resp;
+
+ /* do password magic */
+
+ generate_random_buffer(chal, sizeof(chal));
+ chal_blob = data_blob_const(chal, sizeof(chal));
+
+ if (lp_client_ntlmv2_auth()) {
+ DATA_BLOB server_chal;
+ DATA_BLOB names_blob;
+ server_chal = data_blob_const(chal, 8);
+
+ /* note that the 'workgroup' here is for the local
+ machine. The 'server name' must match the
+ 'workstation' passed to the actual SamLogon call.
+ */
+ names_blob = NTLMv2_generate_names_blob(mem_ctx,
+ lp_netbios_name(),
+ lp_workgroup());
+
+ if (!SMBNTLMv2encrypt(mem_ctx, name_user, name_domain,
+ pass, &server_chal, &names_blob,
+ &lm_resp, &nt_resp, NULL, NULL)) {
+ data_blob_free(&names_blob);
+ DEBUG(0, ("SMBNTLMv2encrypt() failed!\n"));
+ return NT_STATUS_NO_MEMORY;
+ }
+ data_blob_free(&names_blob);
+ } else {
+ int rc;
+ lm_resp = data_blob_null;
+
+ rc = SMBNTencrypt(pass, chal, local_nt_response);
+ if (rc != 0) {
+ DEBUG(0, ("SMBNTencrypt() failed!\n"));
+ return gnutls_error_to_ntstatus(rc,
+ NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER);
+ }
+
+ nt_resp = data_blob_talloc(mem_ctx, local_nt_response,
+ sizeof(local_nt_response));
+ }
+
+ return winbindd_dual_auth_passdb(talloc_tos(), 0, name_domain,
+ name_user, logon_id, client_name,
+ client_pid, &chal_blob, &lm_resp,
+ &nt_resp, remote, local,
+ true, /* interactive */
+ authoritative, info3);
+}
+
+static NTSTATUS winbindd_dual_pam_auth_samlogon(
+ TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ const char *user,
+ const char *pass,
+ uint64_t logon_id,
+ const char *client_name,
+ const int client_pid,
+ uint32_t request_flags,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *name_user = NULL;
+ NTSTATUS result;
+ uint8_t authoritative = 1;
+ uint32_t flags = 0;
+ uint16_t validation_level = 0;
+ union netr_Validation *validation = NULL;
+ bool ok;
+
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon\n"));
+
+ /* Parse domain and username */
+
+ ok = parse_domain_user(mem_ctx,
+ user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * We check against domain->name instead of
+ * name_domain, as find_auth_domain() ->
+ * find_domain_from_name_noinit() already decided
+ * that we are in a child for the correct domain.
+ *
+ * name_domain can also be lp_realm()
+ * we need to check against domain->name.
+ */
+ if (strequal(domain->name, get_global_sam_name())) {
+ struct netr_SamInfo3 *info3 = NULL;
+
+ result = nt_dual_auth_passdb(mem_ctx, name_user, name_domain,
+ pass, logon_id, client_name,
+ client_pid, remote, local,
+ &authoritative, &info3);
+
+ /*
+ * We need to try the remote NETLOGON server if this is
+ * not authoritative (for example on the RODC).
+ */
+ if (authoritative != 0) {
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+ result = map_info3_to_validation(mem_ctx,
+ info3,
+ &validation_level,
+ &validation);
+ TALLOC_FREE(info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ goto done;
+ }
+ }
+
+ /* check authentication loop */
+
+ result = winbind_samlogon_retry_loop(domain,
+ mem_ctx,
+ 0,
+ name_user,
+ pass,
+ name_domain,
+ lp_netbios_name(),
+ logon_id,
+ true, /* plaintext_given */
+ data_blob_null,
+ data_blob_null, data_blob_null,
+ true, /* interactive */
+ &authoritative,
+ &flags,
+ &validation_level,
+ &validation);
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+done:
+ *_validation_level = validation_level;
+ *_validation = validation;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * @brief generate an authentication message in the logs.
+ *
+ */
+static void log_authentication(
+ TALLOC_CTX *mem_ctx,
+ const struct winbindd_domain *domain,
+ const char *client_name,
+ pid_t client_pid,
+ uint16_t validation_level,
+ union netr_Validation *validation,
+ const struct timeval start_time,
+ const uint64_t logon_id,
+ const char *command,
+ const char *user_name,
+ const char *domain_name,
+ const char *workstation,
+ const DATA_BLOB lm_resp,
+ const DATA_BLOB nt_resp,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ NTSTATUS result)
+{
+ struct auth_usersupplied_info *ui = NULL;
+ struct dom_sid *sid = NULL;
+ struct loadparm_context *lp_ctx = NULL;
+ struct imessaging_context *msg_ctx = NULL;
+ struct netr_SamBaseInfo *base_info = NULL;
+
+ if (validation != NULL) {
+ switch (validation_level) {
+ case 3:
+ base_info = &validation->sam3->base;
+ break;
+ case 6:
+ base_info = &validation->sam6->base;
+ break;
+ default:
+ DBG_WARNING("Unexpected validation level '%d'\n",
+ validation_level);
+ break;
+ }
+ }
+
+ ui = talloc_zero(mem_ctx, struct auth_usersupplied_info);
+ ui->logon_id = logon_id;
+ ui->service_description = "winbind";
+ ui->password.response.nt.length = nt_resp.length;
+ ui->password.response.nt.data = nt_resp.data;
+ ui->password.response.lanman.length = lm_resp.length;
+ ui->password.response.lanman.data = lm_resp.data;
+ if (nt_resp.length == 0 && lm_resp.length == 0) {
+ ui->password_state = AUTH_PASSWORD_PLAIN;
+ } else {
+ ui->password_state = AUTH_PASSWORD_RESPONSE;
+ }
+ /*
+ * In the event of a failure ui->auth_description will be null,
+ * the logging code handles this correctly so it can be ignored.
+ */
+ ui->auth_description = talloc_asprintf(
+ ui,
+ "%s, %s, %d",
+ command,
+ client_name,
+ client_pid);
+ if (ui->auth_description == NULL) {
+ DBG_ERR("OOM Unable to create auth_description\n");
+ }
+ ui->client.account_name = user_name;
+ ui->client.domain_name = domain_name;
+ ui->workstation_name = workstation;
+ ui->remote_host = remote;
+ ui->local_host = local;
+
+ if (base_info != NULL) {
+ sid = dom_sid_dup(ui, base_info->domain_sid);
+ if (sid != NULL) {
+ sid_append_rid(sid, base_info->rid);
+ }
+ }
+
+ if (lp_auth_event_notification()) {
+ lp_ctx = loadparm_init_s3(ui, loadparm_s3_helpers());
+ msg_ctx = imessaging_client_init(
+ ui, lp_ctx, global_event_context());
+ }
+ log_authentication_event(
+ msg_ctx,
+ lp_ctx,
+ &start_time,
+ ui,
+ result,
+ base_info != NULL ? base_info->logon_domain.string : "",
+ base_info != NULL ? base_info->account_name.string : "",
+ sid,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_info */);
+ TALLOC_FREE(ui);
+}
+
+NTSTATUS _wbint_PamAuth(struct pipes_struct *p,
+ struct wbint_PamAuth *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS result = NT_STATUS_LOGON_FAILURE;
+ NTSTATUS krb5_result = NT_STATUS_OK;
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *name_user = NULL;
+ char *mapped_user = NULL;
+ const char *domain_user = NULL;
+ uint16_t validation_level = UINT16_MAX;
+ union netr_Validation *validation = NULL;
+ struct netr_SamBaseInfo *base_info = NULL;
+ NTSTATUS name_map_status = NT_STATUS_UNSUCCESSFUL;
+ bool ok;
+ uint64_t logon_id = 0;
+ const struct timeval start_time = timeval_current();
+ const struct tsocket_address *remote = NULL;
+ const struct tsocket_address *local = NULL;
+ const char *krb5ccname = NULL;
+ uid_t uid;
+ pid_t client_pid;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /* Cut client_pid to 32bit */
+ client_pid = r->in.client_pid;
+ if ((uint64_t)client_pid != r->in.client_pid) {
+ DBG_DEBUG("pid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Cut uid to 32bit */
+ uid = r->in.info->uid;
+ if ((uint64_t)uid != r->in.info->uid) {
+ DBG_DEBUG("uid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /*
+ * Generate a logon_id for this session.
+ */
+ logon_id = generate_random_u64();
+ remote = dcesrv_connection_get_remote_address(p->dce_call->conn);
+ local = dcesrv_connection_get_local_address(p->dce_call->conn);
+ DEBUG(3, ("[%"PRIu32"]: dual pam auth %s\n", client_pid,
+ r->in.info->username));
+
+ /* Parse domain and username */
+
+ name_map_status = normalize_name_unmap(p->mem_ctx,
+ r->in.info->username,
+ &mapped_user);
+
+ /* If the name normalization didn't actually do anything,
+ just use the original name */
+
+ if (!NT_STATUS_IS_OK(name_map_status) &&
+ !NT_STATUS_EQUAL(name_map_status, NT_STATUS_FILE_RENAMED))
+ {
+ mapped_user = discard_const(r->in.info->username);
+ }
+
+ ok = parse_domain_user(p->mem_ctx,
+ mapped_user,
+ &name_namespace,
+ &name_domain,
+ &name_user);
+ if (!ok) {
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (mapped_user != r->in.info->username) {
+ domain_user = talloc_asprintf(talloc_tos(),
+ "%s%c%s",
+ name_domain,
+ *lp_winbind_separator(),
+ name_user);
+ if (domain_user == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ r->in.info->username = domain_user;
+ }
+
+ if (!domain->online) {
+ result = NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND;
+ if (domain->startup) {
+ /* Logons are very important to users. If we're offline and
+ we get a request within the first 30 seconds of startup,
+ try very hard to find a DC and go online. */
+
+ DEBUG(10,("winbindd_dual_pam_auth: domain: %s offline and auth "
+ "request in startup mode.\n", domain->name ));
+
+ winbindd_flush_negative_conn_cache(domain);
+ result = init_dc_connection(domain, false);
+ }
+ }
+
+ DEBUG(10,("winbindd_dual_pam_auth: domain: %s last was %s\n", domain->name, domain->online ? "online":"offline"));
+
+ /* Check for Kerberos authentication */
+ if (domain->online && (r->in.flags & WBFLAG_PAM_KRB5)) {
+ result = winbindd_dual_pam_auth_kerberos(
+ domain,
+ r->in.info->username,
+ r->in.info->password,
+ r->in.info->krb5_cc_type,
+ uid,
+ p->mem_ctx,
+ &validation_level,
+ &validation,
+ &krb5ccname);
+
+ /* save for later */
+ krb5_result = result;
+
+ if (NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_kerberos succeeded\n"));
+ goto process_result;
+ }
+
+ DBG_DEBUG("winbindd_dual_pam_auth_kerberos failed: %s\n",
+ nt_errstr(result));
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ DEBUG(10,("winbindd_dual_pam_auth_kerberos setting domain to offline\n"));
+ set_domain_offline( domain );
+ goto cached_logon;
+ }
+
+ /* there are quite some NT_STATUS errors where there is no
+ * point in retrying with a samlogon, we explicitly have to take
+ * care not to increase the bad logon counter on the DC */
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_DISABLED) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_EXPIRED) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_ACCOUNT_LOCKED_OUT) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_INVALID_LOGON_HOURS) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_INVALID_WORKSTATION) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_LOGON_FAILURE) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_EXPIRED) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_MUST_CHANGE) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_WRONG_PASSWORD)) {
+ goto done;
+ }
+
+ if (r->in.flags & WBFLAG_PAM_FALLBACK_AFTER_KRB5) {
+ DEBUG(3,("falling back to samlogon\n"));
+ goto sam_logon;
+ } else {
+ goto cached_logon;
+ }
+ }
+
+sam_logon:
+ /* Check for Samlogon authentication */
+ if (domain->online) {
+ result = winbindd_dual_pam_auth_samlogon(
+ p->mem_ctx,
+ domain,
+ r->in.info->username,
+ r->in.info->password,
+ logon_id,
+ r->in.client_name,
+ client_pid,
+ r->in.flags,
+ remote,
+ local,
+ &validation_level,
+ &validation);
+
+ if (NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon succeeded\n"));
+
+ switch (validation_level) {
+ case 3:
+ base_info = &validation->sam3->base;
+ break;
+ case 6:
+ base_info = &validation->sam6->base;
+ break;
+ default:
+ DBG_ERR("Bad validation level %d\n",
+ validation_level);
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ /* add the Krb5 err if we have one */
+ if ( NT_STATUS_EQUAL(krb5_result, NT_STATUS_TIME_DIFFERENCE_AT_DC ) ) {
+ base_info->user_flags |= LOGON_KRB5_FAIL_CLOCK_SKEW;
+ }
+
+ goto process_result;
+ }
+
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon failed: %s\n",
+ nt_errstr(result)));
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_LOGON_SERVERS) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND))
+ {
+ DEBUG(10,("winbindd_dual_pam_auth_samlogon setting domain to offline\n"));
+ set_domain_offline( domain );
+ goto cached_logon;
+ }
+
+ if (domain->online) {
+ /* We're still online - fail. */
+ goto done;
+ }
+ }
+
+cached_logon:
+ /* Check for Cached logons */
+ if (!domain->online && (r->in.flags & WBFLAG_PAM_CACHED_LOGIN) &&
+ lp_winbind_offline_logon()) {
+ result = winbindd_dual_pam_auth_cached(domain,
+ (r->in.flags & WBFLAG_PAM_KRB5),
+ r->in.info->username,
+ r->in.info->password,
+ r->in.info->krb5_cc_type,
+ uid,
+ p->mem_ctx,
+ &validation_level,
+ &validation,
+ &krb5ccname);
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10,("winbindd_dual_pam_auth_cached failed: %s\n", nt_errstr(result)));
+ goto done;
+ }
+ DEBUG(10,("winbindd_dual_pam_auth_cached succeeded\n"));
+ }
+
+process_result:
+
+ if (NT_STATUS_IS_OK(result)) {
+ struct dom_sid user_sid;
+ TALLOC_CTX *base_ctx = NULL;
+ struct netr_SamInfo3 *info3 = NULL;
+
+ switch (validation_level) {
+ case 3:
+ base_ctx = validation->sam3;
+ base_info = &validation->sam3->base;
+ break;
+ case 6:
+ base_ctx = validation->sam6;
+ base_info = &validation->sam6->base;
+ break;
+ default:
+ DBG_ERR("Bad validation level %d\n", validation_level);
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ sid_compose(&user_sid, base_info->domain_sid, base_info->rid);
+
+ if (base_info->full_name.string == NULL) {
+ struct netr_SamInfo3 *cached_info3;
+
+ cached_info3 = netsamlogon_cache_get(p->mem_ctx,
+ &user_sid);
+ if (cached_info3 != NULL &&
+ cached_info3->base.full_name.string != NULL) {
+ base_info->full_name.string = talloc_strdup(
+ base_ctx,
+ cached_info3->base.full_name.string);
+ if (base_info->full_name.string == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ } else {
+
+ /* this might fail so we don't check the return code */
+ wcache_query_user_fullname(domain,
+ base_ctx,
+ &user_sid,
+ &base_info->full_name.string);
+ }
+ }
+
+ result = map_validation_to_info3(talloc_tos(),
+ validation_level,
+ validation,
+ &info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ wcache_invalidate_samlogon(find_domain_from_name(name_domain),
+ &user_sid);
+ netsamlogon_cache_store(name_user, info3);
+
+ /* save name_to_sid info as early as possible (only if
+ this is our primary domain so we don't invalidate
+ the cache entry by storing the seq_num for the wrong
+ domain). */
+ if ( domain->primary ) {
+ cache_name2sid(domain, name_domain, name_user,
+ SID_NAME_USER, &user_sid);
+ }
+
+ /* Check if the user is in the right group */
+
+ result = check_info3_in_group(info3,
+ r->in.require_membership_of_sid);
+ if (!NT_STATUS_IS_OK(result)) {
+ char *s = NDR_PRINT_STRUCT_STRING(p->mem_ctx,
+ wbint_SidArray,
+ r->in.require_membership_of_sid);
+ DBG_NOTICE("User %s is not in the required groups:\n",
+ r->in.info->username);
+ DEBUGADD(DBGLVL_NOTICE, ("%s", s));
+ DEBUGADD(DBGLVL_NOTICE,
+ ("Plaintext authentication is rejected\n"));
+ goto done;
+ }
+
+ if (!is_allowed_domain(info3->base.logon_domain.string)) {
+ DBG_NOTICE("Authentication failed for user [%s] "
+ "from firewalled domain [%s]\n",
+ info3->base.account_name.string,
+ info3->base.logon_domain.string);
+ result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED;
+ goto done;
+ }
+
+ r->out.validation = talloc_zero(p->mem_ctx,
+ struct wbint_Validation);
+ if (r->out.validation == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ r->out.validation->level = validation_level;
+ r->out.validation->validation = talloc_steal(r->out.validation,
+ validation);
+ r->out.validation->krb5ccname = talloc_steal(r->out.validation,
+ krb5ccname);
+ if ((r->in.flags & WBFLAG_PAM_CACHED_LOGIN)
+ && lp_winbind_offline_logon()) {
+
+ result = winbindd_store_creds(domain,
+ r->in.info->username,
+ r->in.info->password,
+ info3);
+ }
+
+ result = NT_STATUS_OK;
+ }
+
+done:
+ /* give us a more useful (more correct?) error code */
+ if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ||
+ (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
+ result = NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ DBG_PREFIX(NT_STATUS_IS_OK(result) ? 5 : 2,
+ ("Plain-text authentication for user %s returned %s"
+ " (PAM: %d)\n",
+ r->in.info->username,
+ nt_errstr(result),
+ nt_status_to_pam(result)));
+
+ /*
+ * Log the winbind pam authentication, the logon_id will tie this to
+ * any of the logons invoked from this request.
+ */
+
+ log_authentication(
+ p->mem_ctx,
+ domain,
+ r->in.client_name,
+ client_pid,
+ validation_level,
+ validation,
+ start_time,
+ logon_id,
+ "PAM_AUTH",
+ name_user,
+ name_domain,
+ NULL,
+ data_blob_null,
+ data_blob_null,
+ remote,
+ local,
+ result);
+
+ if (NT_STATUS_IS_OK(result)) {
+ gpupdate_user_init(r->in.info->username);
+ }
+
+ return result;
+}
+
+NTSTATUS winbind_dual_SamLogon(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ bool interactive,
+ uint32_t logon_parameters,
+ const char *name_user,
+ const char *name_domain,
+ const char *workstation,
+ const uint64_t logon_id,
+ const char* client_name,
+ const int client_pid,
+ DATA_BLOB chal_blob,
+ DATA_BLOB lm_response,
+ DATA_BLOB nt_response,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ uint8_t *authoritative,
+ bool skip_sam,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation)
+{
+ uint16_t validation_level = 0;
+ union netr_Validation *validation = NULL;
+ NTSTATUS result;
+
+ /*
+ * We check against domain->name instead of
+ * name_domain, as find_auth_domain() ->
+ * find_domain_from_name_noinit() already decided
+ * that we are in a child for the correct domain.
+ *
+ * name_domain can also be lp_realm()
+ * we need to check against domain->name.
+ */
+ if (!skip_sam && strequal(domain->name, get_global_sam_name())) {
+ struct netr_SamInfo3 *info3 = NULL;
+
+ result = winbindd_dual_auth_passdb(
+ talloc_tos(),
+ logon_parameters,
+ name_domain, name_user,
+ logon_id,
+ client_name,
+ client_pid,
+ &chal_blob, &lm_response, &nt_response,
+ remote,
+ local,
+ interactive,
+ authoritative,
+ &info3);
+ if (NT_STATUS_IS_OK(result)) {
+ result = map_info3_to_validation(mem_ctx,
+ info3,
+ &validation_level,
+ &validation);
+ TALLOC_FREE(info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ /*
+ * We need to try the remote NETLOGON server if this is
+ * not authoritative.
+ */
+ if (*authoritative != 0) {
+ *flags = 0;
+ goto process_result;
+ }
+ }
+
+ result = winbind_samlogon_retry_loop(domain,
+ mem_ctx,
+ logon_parameters,
+ name_user,
+ NULL, /* password */
+ name_domain,
+ /* Bug #3248 - found by Stefan Burkei. */
+ workstation, /* We carefully set this above so use it... */
+ logon_id,
+ false, /* plaintext_given */
+ chal_blob,
+ lm_response,
+ nt_response,
+ interactive,
+ authoritative,
+ flags,
+ &validation_level,
+ &validation);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+process_result:
+
+ if (NT_STATUS_IS_OK(result)) {
+ struct dom_sid user_sid;
+ TALLOC_CTX *base_ctx = NULL;
+ struct netr_SamBaseInfo *base_info = NULL;
+ struct netr_SamInfo3 *info3 = NULL;
+
+ switch (validation_level) {
+ case 3:
+ base_ctx = validation->sam3;
+ base_info = &validation->sam3->base;
+ break;
+ case 6:
+ base_ctx = validation->sam6;
+ base_info = &validation->sam6->base;
+ break;
+ default:
+ result = NT_STATUS_INTERNAL_ERROR;
+ goto done;
+ }
+
+ sid_compose(&user_sid, base_info->domain_sid, base_info->rid);
+
+ if (base_info->full_name.string == NULL) {
+ struct netr_SamInfo3 *cached_info3;
+
+ cached_info3 = netsamlogon_cache_get(mem_ctx,
+ &user_sid);
+ if (cached_info3 != NULL &&
+ cached_info3->base.full_name.string != NULL)
+ {
+ base_info->full_name.string = talloc_strdup(
+ base_ctx,
+ cached_info3->base.full_name.string);
+ } else {
+
+ /* this might fail so we don't check the return code */
+ wcache_query_user_fullname(domain,
+ base_ctx,
+ &user_sid,
+ &base_info->full_name.string);
+ }
+ }
+
+ result = map_validation_to_info3(talloc_tos(),
+ validation_level,
+ validation,
+ &info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+ wcache_invalidate_samlogon(find_domain_from_name(name_domain),
+ &user_sid);
+ netsamlogon_cache_store(name_user, info3);
+ TALLOC_FREE(info3);
+ }
+
+done:
+
+ /* give us a more useful (more correct?) error code */
+ if ((NT_STATUS_EQUAL(result, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND) ||
+ (NT_STATUS_EQUAL(result, NT_STATUS_UNSUCCESSFUL)))) {
+ result = NT_STATUS_NO_LOGON_SERVERS;
+ }
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
+ ("NTLM CRAP authentication for user [%s]\\[%s] returned %s\n",
+ name_domain,
+ name_user,
+ nt_errstr(result)));
+
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ *_validation_level = validation_level;
+ *_validation = validation;
+ return NT_STATUS_OK;
+}
+
+NTSTATUS _wbint_PamAuthCrap(struct pipes_struct *p, struct wbint_PamAuthCrap *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS result;
+ uint64_t logon_id = 0;
+ uint8_t authoritative = 1;
+ uint32_t flags = 0;
+ uint16_t validation_level = UINT16_MAX;
+ union netr_Validation *validation = NULL;
+ const struct timeval start_time = timeval_current();
+ const struct tsocket_address *remote = NULL;
+ const struct tsocket_address *local = NULL;
+ struct netr_SamInfo3 *info3 = NULL;
+ pid_t client_pid;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /* Cut client_pid to 32bit */
+ client_pid = r->in.client_pid;
+ if ((uint64_t)client_pid != r->in.client_pid) {
+ DBG_DEBUG("pid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ logon_id = generate_random_u64();
+ remote = dcesrv_connection_get_remote_address(p->dce_call->conn);
+ local = dcesrv_connection_get_local_address(p->dce_call->conn);
+
+ DBG_NOTICE("[%"PRIu32"]: pam auth crap domain: %s user: %s\n",
+ client_pid, r->in.domain, r->in.user);
+
+ result = winbind_dual_SamLogon(domain,
+ p->mem_ctx,
+ false, /* interactive */
+ r->in.logon_parameters,
+ r->in.user,
+ r->in.domain,
+ r->in.workstation,
+ logon_id,
+ r->in.client_name,
+ client_pid,
+ r->in.chal,
+ r->in.lm_resp,
+ r->in.nt_resp,
+ remote,
+ local,
+ &authoritative,
+ false,
+ &flags,
+ &validation_level,
+ &validation);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ result = map_validation_to_info3(p->mem_ctx,
+ validation_level,
+ validation,
+ &info3);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+
+ /* Check if the user is in the right group */
+ result = check_info3_in_group(info3, r->in.require_membership_of_sid);
+ if (!NT_STATUS_IS_OK(result)) {
+ char *s = NDR_PRINT_STRUCT_STRING(p->mem_ctx,
+ wbint_SidArray,
+ r->in.require_membership_of_sid);
+ DBG_NOTICE("User %s is not in the required groups:\n",
+ r->in.user);
+ DEBUGADD(DBGLVL_NOTICE, ("%s", s));
+ DEBUGADD(DBGLVL_NOTICE,
+ ("CRAP authentication is rejected\n"));
+ goto done;
+ }
+
+ if (!is_allowed_domain(info3->base.logon_domain.string)) {
+ DBG_NOTICE("Authentication failed for user [%s] "
+ "from firewalled domain [%s]\n",
+ info3->base.account_name.string,
+ info3->base.logon_domain.string);
+ result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED;
+ goto done;
+ }
+
+ r->out.validation = talloc_zero(p->mem_ctx,
+ struct wbint_PamAuthCrapValidation);
+ if (r->out.validation == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ r->out.validation->level = validation_level;
+ r->out.validation->validation = talloc_move(r->out.validation,
+ &validation);
+done:
+
+ if (r->in.flags & WBFLAG_PAM_NT_STATUS_SQUASH) {
+ result = nt_status_squash(result);
+ }
+
+ *r->out.authoritative = authoritative;
+
+ /*
+ * Log the winbind pam authentication, the logon_id will tie this to
+ * any of the logons invoked from this request.
+ */
+ log_authentication(
+ p->mem_ctx,
+ domain,
+ r->in.client_name,
+ client_pid,
+ r->out.validation->level,
+ r->out.validation->validation,
+ start_time,
+ logon_id,
+ "NTLM_AUTH",
+ r->in.user,
+ r->in.domain,
+ r->in.workstation,
+ r->in.lm_resp,
+ r->in.nt_resp,
+ remote,
+ local,
+ result);
+
+ return result;
+}
+
+NTSTATUS _wbint_PamAuthChangePassword(struct pipes_struct *p,
+ struct wbint_PamAuthChangePassword *r)
+{
+ struct winbindd_domain *contact_domain = wb_child_domain();
+ struct policy_handle dom_pol;
+ struct rpc_pipe_client *cli = NULL;
+ bool got_info = false;
+ struct samr_DomInfo1 *info = NULL;
+ struct userPwdChangeFailureInformation *reject = NULL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ char *namespace = NULL;
+ char *domain = NULL;
+ char *user = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ bool ok;
+ pid_t client_pid;
+
+ ZERO_STRUCT(dom_pol);
+
+ if (contact_domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /* Cut client_pid to 32bit */
+ client_pid = r->in.client_pid;
+ if ((uint64_t)client_pid != r->in.client_pid) {
+ DBG_DEBUG("pid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DBG_NOTICE("[%"PRIu32"]: dual pam chauthtok %s\n",
+ client_pid, r->in.user);
+
+ ok = parse_domain_user(p->mem_ctx,
+ r->in.user,
+ &namespace,
+ &domain,
+ &user);
+ if (!ok) {
+ goto done;
+ }
+
+ if (!is_allowed_domain(domain)) {
+ DBG_NOTICE("Authentication failed for user [%s] "
+ "from firewalled domain [%s]\n",
+ user, domain);
+ result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED;
+ goto done;
+ }
+
+ /* Initialize reject reason */
+ *r->out.reject_reason = Undefined;
+
+ /* Get sam handle */
+
+ result = cm_connect_sam(contact_domain,
+ p->mem_ctx,
+ true,
+ &cli,
+ &dom_pol);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
+ goto done;
+ }
+
+ b = cli->binding_handle;
+
+ status = dcerpc_samr_chgpasswd_user4(cli->binding_handle,
+ p->mem_ctx,
+ cli->srv_name_slash,
+ user,
+ r->in.old_password,
+ r->in.new_password,
+ &result);
+ if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(result)) {
+ /* Password successfully changed. */
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_SUPPORTED) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_NOT_IMPLEMENTED)) {
+ /* DO NOT FALLBACK TO RC4 */
+ if (lp_weak_crypto() == SAMBA_WEAK_CRYPTO_DISALLOWED) {
+ result = NT_STATUS_STRONG_CRYPTO_NOT_SUPPORTED;
+ goto process_result;
+ }
+ }
+ } else {
+ /* Password change was unsuccessful. */
+ if (!NT_STATUS_IS_OK(result)) {
+ goto done;
+ }
+ }
+
+ result = rpccli_samr_chgpasswd_user3(cli,
+ p->mem_ctx,
+ user,
+ r->in.new_password,
+ r->in.old_password,
+ &info,
+ &reject);
+
+ /* Windows 2003 returns NT_STATUS_PASSWORD_RESTRICTION */
+
+ if (NT_STATUS_EQUAL(result, NT_STATUS_PASSWORD_RESTRICTION) ) {
+
+ *r->out.dominfo = talloc_steal(p->mem_ctx, info);
+ *r->out.reject_reason = reject->extendedFailureReason;
+
+ got_info = true;
+ }
+
+ /* atm the pidl generated rpccli_samr_ChangePasswordUser3 function will
+ * return with NT_STATUS_BUFFER_TOO_SMALL for w2k dcs as w2k just
+ * returns with 4byte error code (NT_STATUS_NOT_SUPPORTED) which is too
+ * short to comply with the samr_ChangePasswordUser3 idl - gd */
+
+ /* only fallback when the chgpasswd_user3 call is not supported */
+ if (NT_STATUS_EQUAL(result, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_NOT_SUPPORTED) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_BUFFER_TOO_SMALL) ||
+ NT_STATUS_EQUAL(result, NT_STATUS_NOT_IMPLEMENTED)) {
+
+ DEBUG(10,("Password change with chgpasswd_user3 failed with: %s, retrying chgpasswd_user2\n",
+ nt_errstr(result)));
+
+ result = rpccli_samr_chgpasswd_user2(cli,
+ p->mem_ctx,
+ user,
+ r->in.new_password,
+ r->in.old_password);
+
+ /* Windows 2000 returns NT_STATUS_ACCOUNT_RESTRICTION.
+ Map to the same status code as Windows 2003. */
+
+ if ( NT_STATUS_EQUAL(NT_STATUS_ACCOUNT_RESTRICTION, result ) ) {
+ result = NT_STATUS_PASSWORD_RESTRICTION;
+ }
+ }
+
+done:
+
+ if (NT_STATUS_IS_OK(result)
+ && (r->in.flags & WBFLAG_PAM_CACHED_LOGIN)
+ && lp_winbind_offline_logon()) {
+ result = winbindd_update_creds_by_name(contact_domain, user,
+ r->in.new_password);
+ /* Again, this happens when we login from gdm or xdm
+ * and the password expires, *BUT* cached credentials
+ * don't exist. winbindd_update_creds_by_name()
+ * returns NT_STATUS_NO_SUCH_USER.
+ * This is not a failure.
+ * --- BoYang
+ * */
+ if (NT_STATUS_EQUAL(result, NT_STATUS_NO_SUCH_USER)) {
+ result = NT_STATUS_OK;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(10, ("Failed to store creds: %s\n",
+ nt_errstr(result)));
+ goto process_result;
+ }
+ }
+
+ if (!NT_STATUS_IS_OK(result) && !got_info && contact_domain) {
+
+ NTSTATUS policy_ret;
+
+ policy_ret = get_password_policy(contact_domain,
+ p->mem_ctx,
+ &info);
+
+ /* failure of this is non critical, it will just provide no
+ * additional information to the client why the change has
+ * failed - Guenther */
+
+ if (!NT_STATUS_IS_OK(policy_ret)) {
+ DEBUG(10,("Failed to get password policies: %s\n", nt_errstr(policy_ret)));
+ goto process_result;
+ }
+
+ *r->out.dominfo = talloc_steal(p->mem_ctx, info);
+ }
+
+process_result:
+
+ if (strequal(contact_domain->name, get_global_sam_name())) {
+ /* FIXME: internal rpc pipe does not cache handles yet */
+ if (b) {
+ if (is_valid_policy_hnd(&dom_pol)) {
+ NTSTATUS _result;
+ dcerpc_samr_Close(b,
+ p->mem_ctx,
+ &dom_pol,
+ &_result);
+ }
+ TALLOC_FREE(cli);
+ }
+ }
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
+ ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n",
+ domain,
+ user,
+ nt_errstr(result),
+ nt_status_to_pam(result)));
+
+ return result;
+}
+
+NTSTATUS _wbint_PamLogOff(struct pipes_struct *p, struct wbint_PamLogOff *r)
+{
+ struct winbindd_domain *domain = wb_child_domain();
+ NTSTATUS result = NT_STATUS_NOT_SUPPORTED;
+ pid_t client_pid;
+ uid_t user_uid;
+
+ if (domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /* Cut client_pid to 32bit */
+ client_pid = r->in.client_pid;
+ if ((uint64_t)client_pid != r->in.client_pid) {
+ DBG_DEBUG("pid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Cut uid to 32bit */
+ user_uid = r->in.uid;
+ if ((uint64_t)user_uid != r->in.uid) {
+ DBG_DEBUG("uid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DBG_NOTICE("[%"PRIu32"]: pam dual logoff %s\n", client_pid, r->in.user);
+
+ if (!(r->in.flags & WBFLAG_PAM_KRB5)) {
+ result = NT_STATUS_OK;
+ goto process_result;
+ }
+
+ if ((r->in.krb5ccname == NULL) || (strlen(r->in.krb5ccname) == 0)) {
+ result = NT_STATUS_OK;
+ goto process_result;
+ }
+
+#ifdef HAVE_KRB5
+
+ if (user_uid == (uid_t)-1) {
+ DBG_DEBUG("Invalid uid for user '%s'\n", r->in.user);
+ goto process_result;
+ }
+
+ /* what we need here is to find the corresponding krb5 ccache name *we*
+ * created for a given username and destroy it */
+
+ if (!ccache_entry_exists(r->in.user)) {
+ result = NT_STATUS_OK;
+ DBG_DEBUG("No entry found for user '%s'.\n", r->in.user);
+ goto process_result;
+ }
+
+ if (!ccache_entry_identical(r->in.user, user_uid, r->in.krb5ccname)) {
+ DBG_DEBUG("Cached entry differs for user '%s'\n", r->in.user);
+ goto process_result;
+ }
+
+ result = remove_ccache(r->in.user);
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_DEBUG("Failed to remove ccache for user '%s': %s\n",
+ r->in.user, nt_errstr(result));
+ goto process_result;
+ }
+
+ /*
+ * Remove any mlock'ed memory creds in the child
+ * we might be using for krb5 ticket renewal.
+ */
+
+ winbindd_delete_memory_creds(r->in.user);
+
+#else
+ result = NT_STATUS_NOT_SUPPORTED;
+#endif
+
+process_result:
+
+ return result;
+}
+
+/* Change user password with auth crap*/
+
+NTSTATUS _wbint_PamAuthCrapChangePassword(struct pipes_struct *p,
+ struct wbint_PamAuthCrapChangePassword *r)
+{
+ NTSTATUS result;
+ char *namespace = NULL;
+ char *domain = NULL;
+ char *user = NULL;
+ struct policy_handle dom_pol;
+ struct winbindd_domain *contact_domain = wb_child_domain();
+ struct rpc_pipe_client *cli = NULL;
+ struct dcerpc_binding_handle *b = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ pid_t client_pid;
+
+ ZERO_STRUCT(dom_pol);
+
+ if (contact_domain == NULL) {
+ return NT_STATUS_REQUEST_NOT_ACCEPTED;
+ }
+
+ /* Cut client_pid to 32bit */
+ client_pid = r->in.client_pid;
+ if ((uint64_t)client_pid != r->in.client_pid) {
+ DBG_DEBUG("pid out of range\n");
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ DBG_NOTICE("[%"PRIu32"]: pam change pswd auth crap domain: %s "
+ "user: %s\n", client_pid, r->in.domain, r->in.user);
+
+ if (lp_winbind_offline_logon()) {
+ DEBUG(0,("Refusing password change as winbind offline logons are enabled. "));
+ DEBUGADD(0,("Changing passwords here would risk inconsistent logons\n"));
+ result = NT_STATUS_ACCESS_DENIED;
+ goto done;
+ }
+
+ if (r->in.domain != NULL && strlen(r->in.domain) > 0) {
+ user = talloc_strdup(frame, "");
+ namespace = talloc_strdup(frame, "");
+ domain = talloc_strdup(frame, r->in.domain);
+ if (domain == NULL || user == NULL || namespace == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+
+ } else {
+ bool ok;
+
+ ok = parse_domain_user(frame,
+ r->in.user,
+ &namespace,
+ &domain,
+ &user);
+ if (!ok) {
+ result = NT_STATUS_INVALID_PARAMETER;
+ goto done;
+ }
+
+ if (strlen(domain) == 0) {
+ DBG_NOTICE("no domain specified with username (%s) - "
+ "failing auth\n", r->in.user);
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+ }
+
+ if (!*domain && lp_winbind_use_default_domain()) {
+ TALLOC_FREE(domain);
+ domain = talloc_strdup(frame, lp_workgroup());
+ if (domain == NULL) {
+ result = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ }
+
+ if (!is_allowed_domain(domain)) {
+ DBG_NOTICE("Authentication failed for user [%s] "
+ "from firewalled domain [%s]\n",
+ r->in.user,
+ domain);
+ result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED;
+ goto done;
+ }
+
+ if(!*user) {
+ TALLOC_FREE(user);
+ user = talloc_strdup(frame, r->in.user);
+ if (user == NULL) {
+ result = NT_STATUS_NO_SUCH_USER;
+ goto done;
+ }
+ }
+
+ /* Get sam handle */
+
+ result = cm_connect_sam(contact_domain,
+ p->mem_ctx,
+ true,
+ &cli,
+ &dom_pol);
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("could not get SAM handle on DC for %s\n", domain));
+ goto done;
+ }
+
+ b = cli->binding_handle;
+
+ result = rpccli_samr_chng_pswd_auth_crap(cli,
+ p->mem_ctx,
+ user,
+ r->in.new_nt_pswd,
+ r->in.old_nt_hash_enc,
+ r->in.new_lm_pswd,
+ r->in.old_lm_hash_enc);
+
+ done:
+
+ if (strequal(contact_domain->name, get_global_sam_name())) {
+ /* FIXME: internal rpc pipe does not cache handles yet */
+ if (b) {
+ if (is_valid_policy_hnd(&dom_pol)) {
+ NTSTATUS _result;
+ dcerpc_samr_Close(b,
+ p->mem_ctx,
+ &dom_pol,
+ &_result);
+ }
+ TALLOC_FREE(cli);
+ }
+ }
+
+ DEBUG(NT_STATUS_IS_OK(result) ? 5 : 2,
+ ("Password change for user [%s]\\[%s] returned %s (PAM: %d)\n",
+ domain, user,
+ nt_errstr(result),
+ nt_status_to_pam(result)));
+ TALLOC_FREE(frame);
+ return result;
+}
+
+#ifdef HAVE_KRB5
+static NTSTATUS extract_pac_vrfy_sigs(TALLOC_CTX *mem_ctx, DATA_BLOB pac_blob,
+ struct PAC_DATA **p_pac_data)
+{
+ krb5_context krbctx = NULL;
+ krb5_error_code k5ret;
+ krb5_keytab keytab;
+ krb5_kt_cursor cursor;
+ krb5_keytab_entry entry;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+
+ ZERO_STRUCT(entry);
+ ZERO_STRUCT(cursor);
+
+ k5ret = smb_krb5_init_context_common(&krbctx);
+ if (k5ret) {
+ DBG_ERR("kerberos init context failed (%s)\n",
+ error_message(k5ret));
+ status = krb5_to_nt_status(k5ret);
+ goto out;
+ }
+
+ k5ret = gse_krb5_get_server_keytab(krbctx, &keytab);
+ if (k5ret) {
+ DEBUG(1, ("Failed to get keytab: %s\n",
+ error_message(k5ret)));
+ status = krb5_to_nt_status(k5ret);
+ goto out_free;
+ }
+
+ k5ret = krb5_kt_start_seq_get(krbctx, keytab, &cursor);
+ if (k5ret) {
+ DEBUG(1, ("Failed to start seq: %s\n",
+ error_message(k5ret)));
+ status = krb5_to_nt_status(k5ret);
+ goto out_keytab;
+ }
+
+ k5ret = krb5_kt_next_entry(krbctx, keytab, &entry, &cursor);
+ while (k5ret == 0) {
+ status = kerberos_decode_pac(mem_ctx,
+ pac_blob,
+ krbctx,
+ NULL, /* krbtgt_keyblock */
+ KRB5_KT_KEY(&entry), /* service_keyblock */
+ NULL, /* client_principal */
+ 0, /* tgs_authtime */
+ p_pac_data);
+ if (NT_STATUS_IS_OK(status)) {
+ break;
+ }
+ k5ret = smb_krb5_kt_free_entry(krbctx, &entry);
+ k5ret = krb5_kt_next_entry(krbctx, keytab, &entry, &cursor);
+ }
+
+ k5ret = krb5_kt_end_seq_get(krbctx, keytab, &cursor);
+ if (k5ret) {
+ DEBUG(1, ("Failed to end seq: %s\n",
+ error_message(k5ret)));
+ }
+out_keytab:
+ k5ret = krb5_kt_close(krbctx, keytab);
+ if (k5ret) {
+ DEBUG(1, ("Failed to close keytab: %s\n",
+ error_message(k5ret)));
+ }
+out_free:
+ krb5_free_context(krbctx);
+out:
+ return status;
+}
+
+NTSTATUS winbindd_pam_auth_pac_verify(struct winbindd_cli_state *state,
+ TALLOC_CTX *mem_ctx,
+ bool *p_is_trusted,
+ uint16_t *p_validation_level,
+ union netr_Validation **p_validation)
+{
+ struct winbindd_request *req = state->request;
+ DATA_BLOB pac_blob;
+ struct PAC_DATA *pac_data = NULL;
+ struct PAC_LOGON_INFO *logon_info = NULL;
+ struct PAC_UPN_DNS_INFO *upn_dns_info = NULL;
+ struct netr_SamInfo6 *info6 = NULL;
+ uint16_t validation_level = 0;
+ union netr_Validation *validation = NULL;
+ struct netr_SamInfo3 *info3_copy = NULL;
+ NTSTATUS result;
+ bool is_trusted = false;
+ uint32_t i;
+ TALLOC_CTX *tmp_ctx = NULL;
+
+ tmp_ctx = talloc_new(mem_ctx);
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ *p_is_trusted = false;
+ *p_validation_level = 0;
+ *p_validation = NULL;
+
+ pac_blob = data_blob_const(req->extra_data.data, req->extra_len);
+ result = extract_pac_vrfy_sigs(tmp_ctx, pac_blob, &pac_data);
+ if (NT_STATUS_IS_OK(result)) {
+ is_trusted = true;
+ }
+ if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) {
+ /* Try without signature verification */
+ result = kerberos_decode_pac(tmp_ctx,
+ pac_blob,
+ NULL, /* krb5_context */
+ NULL, /* krbtgt_keyblock */
+ NULL, /* service_keyblock */
+ NULL, /* client_principal */
+ 0, /* tgs_authtime */
+ &pac_data);
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DEBUG(1, ("Error during PAC signature verification: %s\n",
+ nt_errstr(result)));
+ goto out;
+ }
+
+ for (i=0; i < pac_data->num_buffers; i++) {
+ if (pac_data->buffers[i].type == PAC_TYPE_LOGON_INFO) {
+ logon_info = pac_data->buffers[i].info->logon_info.info;
+ continue;
+ }
+ if (pac_data->buffers[i].type == PAC_TYPE_UPN_DNS_INFO) {
+ upn_dns_info = &pac_data->buffers[i].info->upn_dns_info;
+ continue;
+ }
+ }
+
+ result = create_info6_from_pac(tmp_ctx,
+ logon_info,
+ upn_dns_info,
+ &info6);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto out;
+ }
+
+ if (!is_allowed_domain(info6->base.logon_domain.string)) {
+ DBG_NOTICE("Authentication failed for user [%s] "
+ "from firewalled domain [%s]\n",
+ info6->base.account_name.string,
+ info6->base.logon_domain.string);
+ result = NT_STATUS_AUTHENTICATION_FIREWALL_FAILED;
+ goto out;
+ }
+
+ result = map_info6_to_validation(tmp_ctx,
+ info6,
+ &validation_level,
+ &validation);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto out;
+ }
+
+ result = map_validation_to_info3(tmp_ctx,
+ validation_level,
+ validation,
+ &info3_copy);
+ if (!NT_STATUS_IS_OK(result)) {
+ goto out;
+ }
+
+ if (is_trusted) {
+ /*
+ * Signature verification succeeded, we can
+ * trust the PAC and prime the netsamlogon
+ * and name2sid caches. DO NOT DO THIS
+ * in the signature verification failed
+ * code path.
+ */
+ struct winbindd_domain *domain = NULL;
+
+ netsamlogon_cache_store(NULL, info3_copy);
+
+ /*
+ * We're in the parent here, so find the child
+ * pointer from the PAC domain name.
+ */
+ domain = find_lookup_domain_from_name(
+ info3_copy->base.logon_domain.string);
+ if (domain && domain->primary ) {
+ struct dom_sid user_sid;
+ struct dom_sid_buf buf;
+
+ sid_compose(&user_sid,
+ info3_copy->base.domain_sid,
+ info3_copy->base.rid);
+
+ cache_name2sid_trusted(domain,
+ info3_copy->base.logon_domain.string,
+ info3_copy->base.account_name.string,
+ SID_NAME_USER,
+ &user_sid);
+
+ DBG_INFO("PAC for user %s\\%s SID %s primed cache\n",
+ info3_copy->base.logon_domain.string,
+ info3_copy->base.account_name.string,
+ dom_sid_str_buf(&user_sid, &buf));
+ }
+ }
+
+ *p_is_trusted = is_trusted;
+ *p_validation_level = validation_level;
+ *p_validation = talloc_move(mem_ctx, &validation);
+
+ result = NT_STATUS_OK;
+out:
+ TALLOC_FREE(tmp_ctx);
+ return result;
+}
+#else /* HAVE_KRB5 */
+NTSTATUS winbindd_pam_auth_pac_verify(struct winbindd_cli_state *state,
+ TALLOC_CTX *mem_ctx,
+ bool *p_is_trusted,
+ uint16_t *p_validation_level,
+ union netr_Validation **p_validation);
+{
+
+ *p_is_trusted = false;
+ *p_validation_level = 0;
+ *p_validation = NULL;
+ return NT_STATUS_NO_SUCH_USER;
+}
+#endif /* HAVE_KRB5 */
diff --git a/source3/winbindd/winbindd_pam_auth.c b/source3/winbindd/winbindd_pam_auth.c
new file mode 100644
index 0000000..431da09
--- /dev/null
+++ b/source3/winbindd/winbindd_pam_auth.c
@@ -0,0 +1,291 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_PAM_AUTH
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+static NTSTATUS fake_password_policy(struct winbindd_response *r,
+ uint16_t validation_level,
+ union netr_Validation *validation)
+{
+ const struct netr_SamBaseInfo *bi = NULL;
+ NTTIME min_password_age;
+ NTTIME max_password_age;
+
+ switch (validation_level) {
+ case 3:
+ bi = &validation->sam3->base;
+ break;
+ case 6:
+ bi = &validation->sam6->base;
+ break;
+ default:
+ return NT_STATUS_INTERNAL_ERROR;
+ }
+
+ if (bi->allow_password_change > bi->last_password_change) {
+ min_password_age = bi->allow_password_change -
+ bi->last_password_change;
+ } else {
+ min_password_age = 0;
+ }
+
+ if (bi->force_password_change > bi->last_password_change) {
+ max_password_age = bi->force_password_change -
+ bi->last_password_change;
+ } else {
+ max_password_age = 0;
+ }
+
+ r->data.auth.policy.min_length_password = 0;
+ r->data.auth.policy.password_history = 0;
+ r->data.auth.policy.password_properties = 0;
+ r->data.auth.policy.expire =
+ nt_time_to_unix_abs(&max_password_age);
+ r->data.auth.policy.min_passwordage =
+ nt_time_to_unix_abs(&min_password_age);
+
+ return NT_STATUS_OK;
+}
+
+struct winbindd_pam_auth_state {
+ struct wbint_PamAuth *r;
+ char *name_namespace;
+ char *name_domain;
+ char *name_user;
+};
+
+static void winbindd_pam_auth_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_pam_auth_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_pam_auth_state *state;
+ struct winbindd_domain *domain;
+ char *mapped = NULL;
+ char *auth_user = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_pam_auth_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ D_NOTICE("[%s (%u)] Winbind external command PAM_AUTH start.\n"
+ "Authenticating user '%s'.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.auth.user);
+
+ if (!check_request_flags(request->flags)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Parse domain and username */
+
+ status = normalize_name_unmap(state, request->data.auth.user, &mapped);
+
+ /* If the name normalization changed something, copy it over the given
+ name */
+
+ if (NT_STATUS_IS_OK(status)
+ || NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) {
+ fstrcpy(request->data.auth.user, mapped);
+ }
+
+ auth_user = request->data.auth.user;
+ ok = canonicalize_username(state,
+ &auth_user,
+ &state->name_namespace,
+ &state->name_domain,
+ &state->name_user);
+ if (!ok) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return tevent_req_post(req, ev);
+ }
+
+ fstrcpy(request->data.auth.user, auth_user);
+
+ domain = find_auth_domain(request->flags, state->name_namespace);
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->r = talloc_zero(state, struct wbint_PamAuth);
+ if (tevent_req_nomem(state->r, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r->in.client_name = talloc_strdup(
+ state->r, request->client_name);
+ if (tevent_req_nomem(state->r, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r->in.client_pid = request->pid;
+ state->r->in.flags = request->flags;
+
+ state->r->in.info = talloc_zero(state->r, struct wbint_AuthUserInfo);
+ if (tevent_req_nomem(state->r, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r->in.info->krb5_cc_type = talloc_strdup(
+ state->r, request->data.auth.krb5_cc_type);
+ if (tevent_req_nomem(state->r, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r->in.info->password = talloc_strdup(
+ state->r, request->data.auth.pass);
+ if (tevent_req_nomem(state->r, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r->in.info->username = talloc_strdup(
+ state->r, request->data.auth.user);
+ if (tevent_req_nomem(state->r, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r->in.info->uid = request->data.auth.uid;
+
+ status = extra_data_to_sid_array(
+ request->data.auth.require_membership_of_sid,
+ state->r,
+ &state->r->in.require_membership_of_sid);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_PamAuth_r_send(state,
+ global_event_context(),
+ dom_child_handle(domain),
+ state->r);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_pam_auth_done, req);
+ return req;
+}
+
+static void winbindd_pam_auth_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_pam_auth_state *state = tevent_req_data(
+ req, struct winbindd_pam_auth_state);
+ NTSTATUS status;
+
+ status = dcerpc_wbint_PamAuth_r_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ if (tevent_req_nterror(req, state->r->out.result)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_pam_auth_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_pam_auth_state *state = tevent_req_data(
+ req, struct winbindd_pam_auth_state);
+ NTSTATUS status;
+
+ D_NOTICE("Winbind external command PAM_AUTH end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ set_auth_errors(response, status);
+ return status;
+ }
+
+ response->result = WINBINDD_PENDING;
+
+ status = append_auth_data(response,
+ response,
+ state->r->in.flags,
+ state->r->out.validation->level,
+ state->r->out.validation->validation,
+ state->name_domain,
+ state->name_user);
+ fstrcpy(response->data.auth.krb5ccname,
+ state->r->out.validation->krb5ccname);
+
+ if (state->r->in.flags & WBFLAG_PAM_INFO3_TEXT) {
+ bool ok;
+
+ ok = add_trusted_domain_from_auth(
+ state->r->out.validation->level,
+ &response->data.auth.info3,
+ &response->data.auth.info6);
+ if (!ok) {
+ DBG_ERR("add_trusted_domain_from_auth failed\n");
+ set_auth_errors(response, NT_STATUS_LOGON_FAILURE);
+ return NT_STATUS_LOGON_FAILURE;
+ }
+ }
+
+ if (state->r->in.flags & WBFLAG_PAM_CACHED_LOGIN) {
+
+ /* Store in-memory creds for single-signon using ntlm_auth. */
+
+ status = winbindd_add_memory_creds(
+ state->r->in.info->username,
+ state->r->in.info->uid,
+ state->r->in.info->password);
+ D_DEBUG("winbindd_add_memory_creds returned: %s\n",
+ nt_errstr(status));
+ }
+
+ if (state->r->in.flags & WBFLAG_PAM_GET_PWD_POLICY) {
+ /*
+ * WBFLAG_PAM_GET_PWD_POLICY is not used within
+ * any Samba caller anymore.
+ *
+ * We just fake this based on the effective values
+ * for the user, for legacy callers.
+ */
+ status = fake_password_policy(response,
+ state->r->out.validation->level,
+ state->r->out.validation->validation);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to fake password policy: %s\n",
+ nt_errstr(status));
+ set_auth_errors(response, status);
+ return status;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_pam_auth_crap.c b/source3/winbindd/winbindd_pam_auth_crap.c
new file mode 100644
index 0000000..e6a32c7
--- /dev/null
+++ b/source3/winbindd/winbindd_pam_auth_crap.c
@@ -0,0 +1,285 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_PAM_AUTH_CRAP
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "rpc_client/util_netlogon.h"
+#include "libcli/security/dom_sid.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_pam_auth_crap_state {
+ uint8_t authoritative;
+ uint32_t flags;
+ bool pac_is_trusted;
+ char *domain;
+ char *user;
+ struct wbint_PamAuthCrapValidation validation;
+ NTSTATUS result;
+};
+
+static void winbindd_pam_auth_crap_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_pam_auth_crap_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_pam_auth_crap_state *state;
+ struct winbindd_domain *domain;
+ const char *auth_domain = NULL;
+ DATA_BLOB lm_resp = data_blob_null;
+ DATA_BLOB nt_resp = data_blob_null;
+ DATA_BLOB chal = data_blob_null;
+ struct wbint_SidArray *require_membership_of_sid = NULL;
+ NTSTATUS status;
+ bool lmlength_ok = false;
+ bool ntlength_ok = false;
+ bool pwlength_ok = false;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_pam_auth_crap_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->authoritative = 1;
+ state->flags = request->flags;
+
+ if (state->flags & WBFLAG_PAM_AUTH_PAC) {
+ state->result = winbindd_pam_auth_pac_verify(cli,
+ state,
+ &state->pac_is_trusted,
+ &state->validation.level,
+ &state->validation.validation);
+ if (tevent_req_nterror(req, state->result)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ /* Ensure null termination */
+ request->data.auth_crap.user[
+ sizeof(request->data.auth_crap.user)-1] = '\0';
+ request->data.auth_crap.domain[
+ sizeof(request->data.auth_crap.domain)-1] = '\0';
+ request->data.auth_crap.workstation[
+ sizeof(request->data.auth_crap.workstation)-1] = '\0';
+
+ DBG_NOTICE("[%5lu]: pam auth crap domain: [%s] user: [%s] "
+ "workstation: [%s]\n",
+ (unsigned long)cli->pid,
+ request->data.auth_crap.domain,
+ request->data.auth_crap.user,
+ request->data.auth_crap.workstation);
+
+ if (!check_request_flags(request->flags)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX);
+ return tevent_req_post(req, ev);
+ }
+
+ auth_domain = request->data.auth_crap.domain;
+ if (auth_domain[0] == '\0') {
+ auth_domain = lp_workgroup();
+ }
+
+ domain = find_auth_domain(request->flags, auth_domain);
+ if (domain == NULL) {
+ /*
+ * We don't know the domain so
+ * we're not authoritative
+ */
+ state->authoritative = 0;
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return tevent_req_post(req, ev);
+ }
+
+ if (request->data.auth_crap.workstation[0] == '\0') {
+ fstrcpy(request->data.auth_crap.workstation, lp_netbios_name());
+ }
+
+ lmlength_ok = (request->data.auth_crap.lm_resp_len <=
+ sizeof(request->data.auth_crap.lm_resp));
+
+ ntlength_ok = (request->data.auth_crap.nt_resp_len <=
+ sizeof(request->data.auth_crap.nt_resp));
+
+ ntlength_ok |=
+ ((request->flags & WBFLAG_BIG_NTLMV2_BLOB) &&
+ (request->extra_len == request->data.auth_crap.nt_resp_len));
+
+ pwlength_ok = lmlength_ok && ntlength_ok;
+
+ if (!pwlength_ok) {
+ DBG_ERR("Invalid password length %u/%u\n",
+ request->data.auth_crap.lm_resp_len,
+ request->data.auth_crap.nt_resp_len);
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->domain = talloc_strdup(state, request->data.auth_crap.domain);
+ if (tevent_req_nomem(state->domain, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->user = talloc_strdup(state, request->data.auth_crap.user);
+ if (tevent_req_nomem(state->user, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ status = extra_data_to_sid_array(
+ request->data.auth_crap.require_membership_of_sid,
+ state,
+ &require_membership_of_sid);
+ if (tevent_req_nterror(req, status)) {
+ return tevent_req_post(req, ev);
+ }
+
+ lm_resp = data_blob_talloc(state,
+ request->data.auth_crap.lm_resp,
+ request->data.auth_crap.lm_resp_len);
+ if (tevent_req_nomem(lm_resp.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (request->flags & WBFLAG_BIG_NTLMV2_BLOB) {
+ nt_resp = data_blob_talloc(state,
+ request->extra_data.data,
+ request->data.auth_crap.nt_resp_len);
+ } else {
+ nt_resp = data_blob_talloc(state,
+ request->data.auth_crap.nt_resp,
+ request->data.auth_crap.nt_resp_len);
+ }
+ if (tevent_req_nomem(nt_resp.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ chal = data_blob_talloc(state,
+ request->data.auth_crap.chal,
+ 8);
+ if (tevent_req_nomem(chal.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_PamAuthCrap_send(state,
+ global_event_context(),
+ dom_child_handle(domain),
+ request->client_name,
+ request->pid,
+ request->flags,
+ request->data.auth_crap.user,
+ request->data.auth_crap.domain,
+ request->data.auth_crap.workstation,
+ lm_resp,
+ nt_resp,
+ chal,
+ request->data.auth_crap.logon_parameters,
+ require_membership_of_sid,
+ &state->authoritative,
+ &state->validation);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_pam_auth_crap_done, req);
+ return req;
+}
+
+static void winbindd_pam_auth_crap_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_pam_auth_crap_state *state = tevent_req_data(
+ req, struct winbindd_pam_auth_crap_state);
+ NTSTATUS status;
+
+ status = dcerpc_wbint_PamAuthCrap_recv(subreq, state, &state->result);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_pam_auth_crap_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_pam_auth_crap_state *state = tevent_req_data(
+ req, struct winbindd_pam_auth_crap_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ goto out;
+ }
+
+ if (NT_STATUS_IS_ERR(state->result)) {
+ status = state->result;
+ goto out;
+ }
+
+ status = append_auth_data(response,
+ response,
+ state->flags,
+ state->validation.level,
+ state->validation.validation,
+ state->domain,
+ state->user);
+ if (NT_STATUS_IS_ERR(status)) {
+ goto out;
+ }
+
+ if (state->flags & WBFLAG_PAM_AUTH_PAC && !state->pac_is_trusted) {
+ /*
+ * Clear the flag just in state to do no add the domain
+ * from auth below.
+ */
+ state->flags &= ~WBFLAG_PAM_INFO3_TEXT;
+ }
+
+ if (state->flags & WBFLAG_PAM_INFO3_TEXT) {
+ bool ok;
+
+ ok = add_trusted_domain_from_auth(
+ response->data.auth.validation_level,
+ &response->data.auth.info3,
+ &response->data.auth.info6);
+ if (!ok) {
+ status = NT_STATUS_LOGON_FAILURE;
+ DBG_ERR("add_trusted_domain_from_auth failed\n");
+ set_auth_errors(response, status);
+ response->data.auth.authoritative =
+ state->authoritative;
+ return status;
+ }
+ }
+
+ status = NT_STATUS_OK;
+
+out:
+ set_auth_errors(response, status);
+ response->data.auth.authoritative = state->authoritative;
+ response->result = WINBINDD_PENDING;
+ return NT_STATUS(response->data.auth.nt_status);
+}
diff --git a/source3/winbindd/winbindd_pam_chauthtok.c b/source3/winbindd/winbindd_pam_chauthtok.c
new file mode 100644
index 0000000..e778df8
--- /dev/null
+++ b/source3/winbindd/winbindd_pam_chauthtok.c
@@ -0,0 +1,204 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_PAM_CHAUTHTOK
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+static void fill_in_password_policy(struct winbindd_response *r,
+ const struct samr_DomInfo1 *p)
+{
+ r->data.auth.policy.min_length_password =
+ p->min_password_length;
+ r->data.auth.policy.password_history =
+ p->password_history_length;
+ r->data.auth.policy.password_properties =
+ p->password_properties;
+ r->data.auth.policy.expire =
+ nt_time_to_unix_abs((const NTTIME *)&(p->max_password_age));
+ r->data.auth.policy.min_passwordage =
+ nt_time_to_unix_abs((const NTTIME *)&(p->min_password_age));
+}
+
+struct winbindd_pam_chauthtok_state {
+ struct wbint_PamAuthChangePassword r;
+};
+
+static void winbindd_pam_chauthtok_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_pam_chauthtok_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_pam_chauthtok_state *state;
+ struct winbindd_domain *contact_domain;
+ char *namespace = NULL;
+ char *domain = NULL;
+ char *user = NULL;
+ char *chauthtok_user = NULL;
+ char *mapped_user;
+ NTSTATUS status;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_pam_chauthtok_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Ensure null termination */
+ request->data.chauthtok.user[
+ sizeof(request->data.chauthtok.user)-1]='\0';
+
+ DEBUG(3, ("[%5lu]: pam chauthtok %s\n", (unsigned long)cli->pid,
+ request->data.chauthtok.user));
+
+ status = normalize_name_unmap(state, request->data.chauthtok.user,
+ &mapped_user);
+
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_FILE_RENAMED)) {
+ fstrcpy(request->data.chauthtok.user, mapped_user);
+ }
+
+ chauthtok_user = request->data.chauthtok.user;
+ ok = canonicalize_username(req,
+ &chauthtok_user,
+ &namespace,
+ &domain,
+ &user);
+ if (!ok) {
+ DEBUG(10, ("winbindd_pam_chauthtok: canonicalize_username %s "
+ "failed with\n", request->data.chauthtok.user));
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return tevent_req_post(req, ev);
+ }
+
+ fstrcpy(request->data.chauthtok.user, chauthtok_user);
+
+ contact_domain = find_domain_from_name(namespace);
+ if (contact_domain == NULL) {
+ DEBUG(3, ("Cannot change password for [%s] -> [%s]\\[%s] "
+ "as %s is not a trusted domain\n",
+ request->data.chauthtok.user, domain, user, domain));
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.client_pid = request->pid;
+ state->r.in.flags = request->flags;
+
+ state->r.in.client_name = talloc_strdup(state, request->client_name);
+ if (tevent_req_nomem(state->r.in.client_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.user = talloc_strdup(state, request->data.chauthtok.user);
+ if (tevent_req_nomem(state->r.in.user, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.old_password = talloc_strdup(state,
+ request->data.chauthtok.oldpass);
+ if (tevent_req_nomem(state->r.in.old_password, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.new_password = talloc_strdup(state,
+ request->data.chauthtok.newpass);
+ if (tevent_req_nomem(state->r.in.new_password, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_PamAuthChangePassword_r_send(state,
+ global_event_context(),
+ dom_child_handle(contact_domain),
+ &state->r);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_pam_chauthtok_done, req);
+ return req;
+}
+
+static void winbindd_pam_chauthtok_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_pam_chauthtok_state *state = tevent_req_data(
+ req, struct winbindd_pam_chauthtok_state);
+ NTSTATUS status;
+
+ status = dcerpc_wbint_PamAuthChangePassword_r_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_pam_chauthtok_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_pam_chauthtok_state *state = tevent_req_data(
+ req, struct winbindd_pam_chauthtok_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ set_auth_errors(response, status);
+ return status;
+ }
+
+ response->result = WINBINDD_PENDING;
+
+ set_auth_errors(response, state->r.out.result);
+ if (*state->r.out.dominfo != NULL) {
+ fill_in_password_policy(response, *state->r.out.dominfo);
+ }
+ response->data.auth.reject_reason = *state->r.out.reject_reason;
+
+ if (state->r.in.flags & WBFLAG_PAM_CACHED_LOGIN) {
+
+ /* Update the single sign-on memory creds. */
+ status = winbindd_replace_memory_creds(
+ state->r.in.user, state->r.in.new_password);
+
+ DEBUG(10, ("winbindd_replace_memory_creds returned %s\n",
+ nt_errstr(status)));
+
+ /*
+ * When we login from gdm or xdm and password expires,
+ * we change password, but there are no memory
+ * credentials. So, winbindd_replace_memory_creds()
+ * returns NT_STATUS_OBJECT_NAME_NOT_FOUND. This is
+ * not a failure. --- BoYang
+ */
+ if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) {
+ status = NT_STATUS_OK;
+ }
+ }
+
+ return NT_STATUS(response->data.auth.nt_status);
+}
diff --git a/source3/winbindd/winbindd_pam_chng_pswd_auth_crap.c b/source3/winbindd/winbindd_pam_chng_pswd_auth_crap.c
new file mode 100644
index 0000000..8b69f02
--- /dev/null
+++ b/source3/winbindd/winbindd_pam_chng_pswd_auth_crap.c
@@ -0,0 +1,171 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_PAM_CHNG_PSWD_AUTH_CRAP
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_pam_chng_pswd_auth_crap_state {
+ struct wbint_PamAuthCrapChangePassword r;
+};
+
+static void winbindd_pam_chng_pswd_auth_crap_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_pam_chng_pswd_auth_crap_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_pam_chng_pswd_auth_crap_state *state;
+ struct winbindd_domain *domain;
+ const char *domain_name;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_pam_chng_pswd_auth_crap_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ /* Ensure null termination */
+ request->data.chng_pswd_auth_crap.user[
+ sizeof(request->data.chng_pswd_auth_crap.user)-1]='\0';
+ request->data.chng_pswd_auth_crap.domain[
+ sizeof(request->data.chng_pswd_auth_crap.domain)-1]=0;
+
+ DEBUG(3, ("[%5lu]: pam change pswd auth crap domain: %s user: %s\n",
+ (unsigned long)cli->pid,
+ request->data.chng_pswd_auth_crap.domain,
+ request->data.chng_pswd_auth_crap.user));
+
+ domain_name = NULL;
+ if (*request->data.chng_pswd_auth_crap.domain != '\0') {
+ domain_name = request->data.chng_pswd_auth_crap.domain;
+ } else if (lp_winbind_use_default_domain()) {
+ domain_name = lp_workgroup();
+ }
+
+ domain = NULL;
+ if (domain_name != NULL) {
+ domain = find_domain_from_name(domain_name);
+ }
+
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.client_pid = request->pid;
+ state->r.in.client_name = talloc_strdup(state, request->client_name);
+ if (tevent_req_nomem(state->r.in.client_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.domain = talloc_strdup(state, domain_name);
+ if (tevent_req_nomem(state->r.in.domain, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->r.in.user = talloc_strdup(state,
+ request->data.chng_pswd_auth_crap.user);
+ if (tevent_req_nomem(state->r.in.user, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.new_nt_pswd = data_blob_talloc(state,
+ request->data.chng_pswd_auth_crap.new_nt_pswd,
+ request->data.chng_pswd_auth_crap.new_nt_pswd_len);
+ if (tevent_req_nomem(state->r.in.new_nt_pswd.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.old_nt_hash_enc = data_blob_talloc(state,
+ request->data.chng_pswd_auth_crap.old_nt_hash_enc,
+ request->data.chng_pswd_auth_crap.old_nt_hash_enc_len);
+ if (tevent_req_nomem(state->r.in.old_nt_hash_enc.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ if (request->data.chng_pswd_auth_crap.new_lm_pswd_len > 0) {
+ state->r.in.new_lm_pswd = data_blob_talloc(state,
+ request->data.chng_pswd_auth_crap.new_lm_pswd,
+ request->data.chng_pswd_auth_crap.new_lm_pswd_len);
+ if (tevent_req_nomem(state->r.in.new_lm_pswd.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->r.in.old_lm_hash_enc = data_blob_talloc(state,
+ request->data.chng_pswd_auth_crap.old_lm_hash_enc,
+ request->data.chng_pswd_auth_crap.old_lm_hash_enc_len);
+ if (tevent_req_nomem(state->r.in.old_lm_hash_enc.data, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ state->r.in.new_lm_pswd = data_blob_null;
+ state->r.in.old_lm_hash_enc = data_blob_null;
+ }
+
+ subreq = dcerpc_wbint_PamAuthCrapChangePassword_r_send(state,
+ global_event_context(),
+ dom_child_handle(domain),
+ &state->r);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_pam_chng_pswd_auth_crap_done,
+ req);
+ return req;
+}
+
+static void winbindd_pam_chng_pswd_auth_crap_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_pam_chng_pswd_auth_crap_state *state = tevent_req_data(
+ req, struct winbindd_pam_chng_pswd_auth_crap_state);
+ NTSTATUS status;
+
+ status = dcerpc_wbint_PamAuthCrapChangePassword_r_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_pam_chng_pswd_auth_crap_recv(
+ struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_pam_chng_pswd_auth_crap_state *state = tevent_req_data(
+ req, struct winbindd_pam_chng_pswd_auth_crap_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ set_auth_errors(response, status);
+ return status;
+ }
+
+ response->result = WINBINDD_PENDING;
+ set_auth_errors(response, state->r.out.result);
+
+ return NT_STATUS(response->data.auth.nt_status);
+}
diff --git a/source3/winbindd/winbindd_pam_logoff.c b/source3/winbindd/winbindd_pam_logoff.c
new file mode 100644
index 0000000..c799eb5
--- /dev/null
+++ b/source3/winbindd/winbindd_pam_logoff.c
@@ -0,0 +1,183 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_PAM_LOGOFF
+ Copyright (C) Volker Lendecke 2010
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "util/debug.h"
+#include "winbindd.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_pam_logoff_state {
+ struct wbint_PamLogOff r;
+};
+
+static void winbindd_pam_logoff_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_pam_logoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_pam_logoff_state *state;
+ struct winbindd_domain *domain;
+ char *name_namespace = NULL;
+ char *name_domain = NULL;
+ char *user = NULL;
+ char *logoff_user = NULL;
+
+ uid_t caller_uid;
+ gid_t caller_gid;
+ int res;
+ bool ok;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_pam_logoff_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ D_NOTICE("[%s (%u)] Winbind external command PAM_LOGOFF start.\n"
+ "Username '%s' is used during logoff.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.auth.user);
+ /* Ensure null termination */
+ /* Ensure null termination */
+ request->data.logoff.user[sizeof(request->data.logoff.user)-1]='\0';
+ request->data.logoff.krb5ccname[
+ sizeof(request->data.logoff.krb5ccname)-1]='\0';
+
+ if (request->data.logoff.uid == (uid_t)-1) {
+ goto failed;
+ }
+
+ logoff_user = request->data.logoff.user;
+
+ ok = canonicalize_username(req,
+ &logoff_user,
+ &name_namespace,
+ &name_domain,
+ &user);
+ if (!ok) {
+ goto failed;
+ }
+
+ fstrcpy(request->data.logoff.user, logoff_user);
+
+ domain = find_auth_domain(request->flags, name_namespace);
+ if (domain == NULL) {
+ goto failed;
+ }
+
+ caller_uid = (uid_t)-1;
+
+ res = getpeereid(cli->sock, &caller_uid, &caller_gid);
+ if (res != 0) {
+ D_WARNING("winbindd_pam_logoff: failed to check peerid: %s\n",
+ strerror(errno));
+ goto failed;
+ }
+
+ switch (caller_uid) {
+ case -1:
+ goto failed;
+ case 0:
+ /* root must be able to logoff any user - gd */
+ break;
+ default:
+ if (caller_uid != request->data.logoff.uid) {
+ D_WARNING("caller requested invalid uid\n");
+ goto failed;
+ }
+ break;
+ }
+
+ state->r.in.client_name = talloc_strdup(state, request->client_name);
+ if (tevent_req_nomem(state->r.in.client_name, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->r.in.client_pid = request->pid;
+
+ state->r.in.flags = request->flags;
+ state->r.in.user = talloc_strdup(state, request->data.logoff.user);
+ if (tevent_req_nomem(state->r.in.user, req)) {
+ return tevent_req_post(req, ev);
+ }
+ state->r.in.uid = request->data.logoff.uid;
+ state->r.in.krb5ccname = talloc_strdup(state,
+ request->data.logoff.krb5ccname);
+ if (tevent_req_nomem(state->r.in.krb5ccname, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_PamLogOff_r_send(state,
+ global_event_context(),
+ dom_child_handle(domain),
+ &state->r);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_pam_logoff_done, req);
+ return req;
+
+failed:
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_USER);
+ return tevent_req_post(req, ev);
+}
+
+static void winbindd_pam_logoff_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_pam_logoff_state *state = tevent_req_data(
+ req, struct winbindd_pam_logoff_state);
+ NTSTATUS status;
+
+ status = dcerpc_wbint_PamLogOff_r_recv(subreq, state);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_pam_logoff_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_pam_logoff_state *state = tevent_req_data(
+ req, struct winbindd_pam_logoff_state);
+ NTSTATUS status = NT_STATUS_OK;
+
+ D_NOTICE("Winbind external command PAM_LOGOFF end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ set_auth_errors(response, status);
+ return status;
+ }
+
+ response->result = WINBINDD_PENDING;
+ set_auth_errors(response, state->r.out.result);
+
+ if (NT_STATUS_IS_OK(state->r.out.result)) {
+ winbindd_delete_memory_creds(state->r.in.user);
+ }
+
+ return NT_STATUS(response->data.auth.nt_status);
+}
diff --git a/source3/winbindd/winbindd_ping_dc.c b/source3/winbindd/winbindd_ping_dc.c
new file mode 100644
index 0000000..8f56a9e
--- /dev/null
+++ b/source3/winbindd/winbindd_ping_dc.c
@@ -0,0 +1,140 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_PING_DC
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+struct winbindd_ping_dc_state {
+ const char *dcname;
+ NTSTATUS result;
+};
+
+static void winbindd_ping_dc_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_ping_dc_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_ping_dc_state *state;
+ struct winbindd_domain *domain;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_ping_dc_state);
+ if (req == NULL) {
+ return NULL;
+ }
+
+ if (request->domain_name[0] == '\0') {
+ /* preserve old behavior, when no domain name is given */
+ domain = find_our_domain();
+ } else {
+ domain = find_trust_from_name_noinit(request->domain_name);
+ }
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+ if (domain->internal) {
+ const char *d = lp_dnsdomain();
+ const char *n = lp_netbios_name();
+
+ /*
+ * Internal domains are passdb based, we can always
+ * contact them.
+ */
+
+ if (d != NULL) {
+ char *h;
+ h = strlower_talloc(mem_ctx, n);
+ if (tevent_req_nomem(h, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ state->dcname = talloc_asprintf(state, "%s.%s", h, d);
+ TALLOC_FREE(h);
+
+ if (tevent_req_nomem(state->dcname, req)) {
+ return tevent_req_post(req, ev);
+ }
+ } else {
+ state->dcname = talloc_strdup(state, n);
+ if (tevent_req_nomem(state->dcname, req)) {
+ return tevent_req_post(req, ev);
+ }
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = dcerpc_wbint_PingDc_send(state, ev, dom_child_handle(domain),
+ &state->dcname);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_ping_dc_done, req);
+ return req;
+}
+
+static void winbindd_ping_dc_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_ping_dc_state *state = tevent_req_data(
+ req, struct winbindd_ping_dc_state);
+ NTSTATUS status, result;
+
+ status = dcerpc_wbint_PingDc_recv(subreq, state, &result);
+ state->result = result;
+ if (any_nt_status_not_ok(status, result, &status)) {
+ tevent_req_nterror(req, status);
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_ping_dc_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ struct winbindd_ping_dc_state *state = tevent_req_data(
+ req, struct winbindd_ping_dc_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(state->result)) {
+ set_auth_errors(presp, state->result);
+ }
+
+ if (state->dcname) {
+ presp->extra_data.data = talloc_strdup(presp, state->dcname);
+ if (presp->extra_data.data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ presp->length += strlen((char *)presp->extra_data.data) + 1;
+ }
+
+ tevent_req_received(req);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_proto.h b/source3/winbindd/winbindd_proto.h
new file mode 100644
index 0000000..a5e8c8b
--- /dev/null
+++ b/source3/winbindd/winbindd_proto.h
@@ -0,0 +1,1059 @@
+/*
+ * Unix SMB/CIFS implementation.
+ * collected prototypes header
+ *
+ * frozen from "make proto" in May 2008
+ *
+ * Copyright (C) Michael Adam 2008
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _WINBINDD_PROTO_H_
+#define _WINBINDD_PROTO_H_
+
+/* The following definitions come from winbindd/winbindd.c */
+struct imessaging_context *winbind_imessaging_context(void);
+void winbindd_terminate(bool is_parent);
+bool winbindd_setup_sig_term_handler(bool parent);
+bool winbindd_setup_stdin_handler(bool parent, bool foreground);
+bool winbindd_setup_sig_hup_handler(const char *lfile);
+bool winbindd_use_idmap_cache(void);
+bool winbindd_use_cache(void);
+void winbindd_set_use_cache(bool use_cache);
+char *get_winbind_priv_pipe_dir(void);
+void winbindd_flush_caches(void);
+void winbind_debug_call_depth_setup(size_t *depth);
+void winbind_call_flow(void *private_data,
+ enum tevent_thread_call_depth_cmd cmd,
+ struct tevent_req *req,
+ size_t depth,
+ const char *fname);
+bool winbindd_reload_services_file(const char *lfile);
+
+/* The following definitions come from winbindd/winbindd_ads.c */
+
+/* The following definitions come from winbindd/winbindd_rpc.c */
+
+NTSTATUS winbindd_lookup_sids(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ char ***domains,
+ char ***names,
+ enum lsa_SidType **types);
+NTSTATUS rpc_lookup_sids(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct lsa_SidArray *sids,
+ struct lsa_RefDomainList **pdomains,
+ struct lsa_TransNameArray **pnames);
+
+/* The following definitions come from winbindd/winbindd_cache.c */
+
+NTSTATUS wb_cache_query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **prids);
+NTSTATUS wb_cache_enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info);
+NTSTATUS wb_cache_enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info);
+NTSTATUS wb_cache_name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ struct dom_sid *sid,
+ enum lsa_SidType *type);
+NTSTATUS wb_cache_sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type);
+NTSTATUS wb_cache_rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types);
+NTSTATUS wb_cache_lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids);
+NTSTATUS wb_cache_lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *num_aliases,
+ uint32_t **alias_rids);
+NTSTATUS wb_cache_lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem,
+ char ***names,
+ uint32_t **name_types);
+NTSTATUS wb_cache_lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem);
+NTSTATUS wb_cache_sequence_number(struct winbindd_domain *domain,
+ uint32_t *seq);
+NTSTATUS wb_cache_lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *policy);
+NTSTATUS wb_cache_password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *policy);
+NTSTATUS wb_cache_trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *trusts);
+
+NTSTATUS wcache_cached_creds_exist(struct winbindd_domain *domain, const struct dom_sid *sid);
+NTSTATUS wcache_get_creds(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ const uint8_t **cached_nt_pass,
+ const uint8_t **cached_salt);
+NTSTATUS wcache_save_creds(struct winbindd_domain *domain,
+ const struct dom_sid *sid,
+ const uint8_t nt_pass[NT_HASH_LEN]);
+void wcache_invalidate_samlogon(struct winbindd_domain *domain,
+ const struct dom_sid *user_sid);
+bool wcache_invalidate_cache(void);
+bool wcache_invalidate_cache_noinit(void);
+bool initialize_winbindd_cache(void);
+void close_winbindd_cache(void);
+bool lookup_cached_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid,
+ char **domain_name, char **name,
+ enum lsa_SidType *type);
+bool lookup_cached_name(const char *namespace,
+ const char *domain_name,
+ const char *name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type);
+void cache_name2sid_trusted(struct winbindd_domain *domain,
+ const char *domain_name,
+ const char *name,
+ enum lsa_SidType type,
+ const struct dom_sid *sid);
+void cache_name2sid(struct winbindd_domain *domain,
+ const char *domain_name, const char *name,
+ enum lsa_SidType type, const struct dom_sid *sid);
+NTSTATUS wcache_query_user_fullname(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ const char **full_name);
+
+NTSTATUS wcache_count_cached_creds(struct winbindd_domain *domain, int *count);
+NTSTATUS wcache_remove_oldest_cached_creds(struct winbindd_domain *domain, const struct dom_sid *sid) ;
+bool set_global_winbindd_state_offline(void);
+void set_global_winbindd_state_online(void);
+bool get_global_winbindd_state_offline(void);
+int winbindd_validate_cache(void);
+int winbindd_validate_cache_nobackup(void);
+bool winbindd_cache_validate_and_initialize(void);
+bool wcache_tdc_fetch_list( struct winbindd_tdc_domain **domains, size_t *num_domains );
+bool wcache_tdc_add_domain( struct winbindd_domain *domain );
+struct winbindd_tdc_domain * wcache_tdc_fetch_domain( TALLOC_CTX *ctx, const char *name );
+void wcache_tdc_clear( void );
+bool wcache_store_seqnum(const char *domain_name, uint32_t seqnum,
+ time_t last_seq_check);
+bool wcache_fetch_ndr(TALLOC_CTX *mem_ctx, struct winbindd_domain *domain,
+ uint32_t opnum, const DATA_BLOB *req, DATA_BLOB *resp);
+void wcache_store_ndr(struct winbindd_domain *domain, uint32_t opnum,
+ const DATA_BLOB *req, const DATA_BLOB *resp);
+
+/* The following definitions come from winbindd/winbindd_ccache_access.c */
+
+bool winbindd_ccache_ntlm_auth(struct winbindd_cli_state *state);
+enum winbindd_result winbindd_dual_ccache_ntlm_auth(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state);
+bool winbindd_ccache_save(struct winbindd_cli_state *state);
+
+/* The following definitions come from winbindd/winbindd_cm.c */
+void winbind_msg_domain_offline(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_domain_online(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+
+void set_domain_offline(struct winbindd_domain *domain);
+void set_domain_online_request(struct winbindd_domain *domain);
+
+struct ndr_interface_table;
+NTSTATUS wb_open_internal_pipe(TALLOC_CTX *mem_ctx,
+ const struct ndr_interface_table *table,
+ struct rpc_pipe_client **ret_pipe);
+void invalidate_cm_connection(struct winbindd_domain *domain);
+void close_conns_after_fork(void);
+NTSTATUS init_dc_connection(struct winbindd_domain *domain, bool need_rw_dc);
+NTSTATUS cm_connect_sam(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx,
+ bool need_rw_dc,
+ struct rpc_pipe_client **cli, struct policy_handle *sam_handle);
+NTSTATUS cm_connect_lsa(struct winbindd_domain *domain, TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **cli, struct policy_handle *lsa_policy);
+NTSTATUS cm_connect_lsat(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **cli,
+ struct policy_handle *lsa_policy);
+NTSTATUS cm_connect_netlogon(struct winbindd_domain *domain,
+ struct rpc_pipe_client **cli);
+NTSTATUS cm_connect_netlogon_secure(struct winbindd_domain *domain,
+ struct rpc_pipe_client **cli,
+ struct netlogon_creds_cli_context **ppdc);
+bool fetch_current_dc_from_gencache(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ char **p_dc_name, char **p_dc_ip);
+
+/* The following definitions come from winbindd/winbindd_cred_cache.c */
+
+bool ccache_entry_exists(const char *username);
+bool ccache_entry_identical(const char *username,
+ uid_t uid,
+ const char *ccname);
+void ccache_remove_all_after_fork(void);
+void ccache_regain_all_now(void);
+NTSTATUS add_ccache_to_list(const char *princ_name,
+ const char *ccname,
+ const char *username,
+ const char *password,
+ const char *realm,
+ uid_t uid,
+ time_t create_time,
+ time_t ticket_end,
+ time_t renew_until,
+ bool postponed_request,
+ const char *canon_principal,
+ const char *canon_realm);
+NTSTATUS remove_ccache(const char *username);
+struct WINBINDD_MEMORY_CREDS *find_memory_creds_by_name(const char *username);
+NTSTATUS winbindd_add_memory_creds(const char *username,
+ uid_t uid,
+ const char *pass);
+NTSTATUS winbindd_delete_memory_creds(const char *username);
+NTSTATUS winbindd_replace_memory_creds(const char *username,
+ const char *pass);
+
+/* The following definitions come from winbindd/winbindd_creds.c */
+
+NTSTATUS winbindd_get_creds(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ struct netr_SamInfo3 **info3,
+ const uint8_t **cached_nt_pass,
+ const uint8_t **cred_salt);
+NTSTATUS winbindd_store_creds(struct winbindd_domain *domain,
+ const char *user,
+ const char *pass,
+ struct netr_SamInfo3 *info3);
+NTSTATUS winbindd_update_creds_by_info3(struct winbindd_domain *domain,
+ const char *user,
+ const char *pass,
+ struct netr_SamInfo3 *info3);
+NTSTATUS winbindd_update_creds_by_name(struct winbindd_domain *domain,
+ const char *user,
+ const char *pass);
+
+/* The following definitions come from winbindd/winbindd_domain.c */
+
+void setup_domain_child(struct winbindd_domain *domain);
+
+/* The following definitions come from winbindd/winbindd_dual.c */
+
+struct dcerpc_binding_handle *dom_child_handle(struct winbindd_domain *domain);
+
+struct tevent_req *wb_child_request_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_child *child,
+ struct winbindd_request *request);
+int wb_child_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct winbindd_response **presponse, int *err);
+struct tevent_req *wb_domain_request_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain,
+ struct winbindd_request *request);
+int wb_domain_request_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct winbindd_response **presponse, int *err);
+
+void setup_child(struct winbindd_domain *domain, struct winbindd_child *child,
+ const char *logprefix,
+ const char *logname);
+void winbind_child_died(pid_t pid);
+void winbindd_flush_negative_conn_cache(struct winbindd_domain *domain);
+void winbind_msg_debug(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_disconnect_dc_parent(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_offline(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_online(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_onlinestatus(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_dump_event_list(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_dump_domain_list(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_ip_dropped(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_disconnect_dc(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbind_msg_ip_dropped_parent(struct messaging_context *msg_ctx,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+void winbindd_msg_reload_services_parent(struct messaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ DATA_BLOB *data);
+NTSTATUS winbindd_reinit_after_fork(const struct winbindd_child *myself,
+ const char *logfilename);
+struct winbindd_domain *wb_child_domain(void);
+bool add_trusted_domains_dc(void);
+
+/* The following definitions come from winbindd/winbindd_group.c */
+bool fill_grent(TALLOC_CTX *mem_ctx, struct winbindd_gr *gr,
+ const char *dom_name, const char *gr_name, gid_t unix_gid);
+
+struct db_context;
+NTSTATUS winbindd_print_groupmembers(struct db_context *members,
+ TALLOC_CTX *mem_ctx,
+ int *num_members, char **result);
+
+
+/* The following definitions come from winbindd/winbindd_idmap.c */
+
+struct tevent_req *wb_parent_idmap_setup_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev);
+NTSTATUS wb_parent_idmap_setup_recv(struct tevent_req *req,
+ const struct wb_parent_idmap_config **_cfg);
+
+NTSTATUS init_idmap_child(TALLOC_CTX *mem_ctx);
+struct winbindd_child *idmap_child(void);
+bool is_idmap_child(const struct winbindd_child *child);
+pid_t idmap_child_pid(void);
+struct dcerpc_binding_handle *idmap_child_handle(void);
+struct idmap_domain *idmap_find_domain_with_sid(const char *domname,
+ const struct dom_sid *sid);
+const char *idmap_config_const_string(const char *domname, const char *option,
+ const char *def);
+bool idmap_config_bool(const char *domname, const char *option, bool def);
+int idmap_config_int(const char *domname, const char *option, int def);
+const char **idmap_config_string_list(const char *domname,
+ const char *option,
+ const char **def);
+bool domain_has_idmap_config(const char *domname);
+bool lp_scan_idmap_domains(bool (*fn)(const char *domname,
+ void *private_data),
+ void *private_data);
+
+/* The following definitions come from winbindd/winbindd_locator.c */
+
+NTSTATUS init_locator_child(TALLOC_CTX *mem_ctx);
+struct winbindd_child *locator_child(void);
+struct dcerpc_binding_handle *locator_child_handle(void);
+
+/* The following definitions come from winbindd/winbindd_misc.c */
+
+bool winbindd_list_trusted_domains(struct winbindd_cli_state *state);
+bool winbindd_dc_info(struct winbindd_cli_state *state);
+bool winbindd_ping(struct winbindd_cli_state *state);
+bool winbindd_info(struct winbindd_cli_state *state);
+bool winbindd_interface_version(struct winbindd_cli_state *state);
+bool winbindd_domain_name(struct winbindd_cli_state *state);
+bool winbindd_netbios_name(struct winbindd_cli_state *state);
+bool winbindd_priv_pipe_dir(struct winbindd_cli_state *state);
+
+/* The following definitions come from winbindd/winbindd_ndr.c */
+struct ndr_print;
+void ndr_print_winbindd_child(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_child *r);
+void ndr_print_winbindd_cm_conn(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_cm_conn *r);
+void ndr_print_winbindd_methods(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_methods *r);
+void ndr_print_winbindd_domain(struct ndr_print *ndr,
+ const char *name,
+ const struct winbindd_domain *r);
+
+/* The following definitions come from winbindd/winbindd_pam.c */
+
+bool check_request_flags(uint32_t flags);
+NTSTATUS append_auth_data(TALLOC_CTX *mem_ctx,
+ struct winbindd_response *resp,
+ uint32_t request_flags,
+ uint16_t validation_level,
+ union netr_Validation *validation,
+ const char *name_domain,
+ const char *name_user);
+NTSTATUS extra_data_to_sid_array(const char *group_sid,
+ TALLOC_CTX *mem_ctx,
+ struct wbint_SidArray **_sid_array);
+uid_t get_uid_from_request(struct winbindd_request *request);
+struct winbindd_domain *find_auth_domain(uint8_t flags,
+ const char *domain_name);
+struct pipes_struct;
+struct wbint_PamAuth;
+NTSTATUS _wbint_PamAuth(struct pipes_struct *p,
+ struct wbint_PamAuth *r);
+NTSTATUS _wbint_PamAuthCrap(struct pipes_struct *p,
+ struct wbint_PamAuthCrap *r);
+NTSTATUS _wbint_PamAuthChangePassword(struct pipes_struct *p,
+ struct wbint_PamAuthChangePassword *r);
+NTSTATUS _wbint_PamLogOff(struct pipes_struct *p,
+ struct wbint_PamLogOff *r);
+NTSTATUS _wbint_PamAuthCrapChangePassword(struct pipes_struct *p,
+ struct wbint_PamAuthCrapChangePassword *r);
+NTSTATUS winbindd_pam_auth_pac_verify(struct winbindd_cli_state *state,
+ TALLOC_CTX *mem_ctx,
+ bool *p_is_trusted,
+ uint16_t *p_validation_level,
+ union netr_Validation **p_validation);
+
+NTSTATUS winbind_dual_SamLogon(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ bool interactive,
+ uint32_t logon_parameters,
+ const char *name_user,
+ const char *name_domain,
+ const char *workstation,
+ const uint64_t logon_id,
+ const char *client_name,
+ const int pid,
+ DATA_BLOB chal,
+ DATA_BLOB lm_response,
+ DATA_BLOB nt_response,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ uint8_t *authoritative,
+ bool skip_sam,
+ uint32_t *flags,
+ uint16_t *_validation_level,
+ union netr_Validation **_validation);
+
+/* The following definitions come from winbindd/winbindd_util.c */
+
+struct winbindd_domain *domain_list(void);
+struct winbindd_domain *wb_next_domain(struct winbindd_domain *domain);
+bool set_routing_domain(struct winbindd_domain *domain,
+ struct winbindd_domain *routing_domain);
+bool add_trusted_domain_from_auth(uint16_t validation_level,
+ struct info3_text *info3,
+ struct info6_text *info6);
+bool domain_is_forest_root(const struct winbindd_domain *domain);
+void rescan_trusted_domains(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval now, void *private_data);
+void winbindd_ping_offline_domains(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data);
+bool init_domain_list(void);
+struct winbindd_domain *find_domain_from_name_noinit(const char *domain_name);
+struct winbindd_domain *find_trust_from_name_noinit(const char *domain_name);
+struct winbindd_domain *find_domain_from_name(const char *domain_name);
+struct winbindd_domain *find_domain_from_sid_noinit(const struct dom_sid *sid);
+struct winbindd_domain *find_trust_from_sid_noinit(const struct dom_sid *sid);
+struct winbindd_domain *find_domain_from_sid(const struct dom_sid *sid);
+struct winbindd_domain *find_our_domain(void);
+struct winbindd_domain *find_default_route_domain(void);
+struct winbindd_domain *find_lookup_domain_from_sid(const struct dom_sid *sid);
+struct winbindd_domain *find_lookup_domain_from_name(const char *domain_name);
+/**
+ * Parse a DOMAIN\user or UPN string into a domain, namespace and a user
+ *
+ * @param[in] ctx talloc context
+ * @param[in] domuser a DOMAIN\user or UPN string
+ * @param[out] namespace
+ * @param[out] domain
+ * @param[out] user
+ * @return bool indicating success or failure
+ */
+bool parse_domain_user(TALLOC_CTX *ctx,
+ const char *domuser,
+ char **namespace,
+ char **domain,
+ char **user);
+/**
+ * Ensure an incoming username from NSS is fully qualified. Replace the
+ * incoming username with DOMAIN <separator> user. Additionally returns
+ * the same values as parse_domain_user() as out params.
+ * Used to ensure all names are fully qualified within winbindd.
+ * Used by the NSS protocols of auth, chauthtok, logoff and ccache_ntlm_auth.
+ * The protocol definitions of auth_crap, chng_pswd_auth_crap
+ * really should be changed to use this instead of doing things
+ * by hand. JRA.
+ *
+ * @param[in] mem_ctx talloc context
+ * @param[in,out] username_inout populated with fully qualified name
+ with format 'DOMAIN <separator> user' where DOMAIN and
+ user are determined by the output of parse_domain_user()
+ * @param[out] namespace populated with namespace returned from
+ parse_domain_user()
+ * @param[out] domain populated with domain returned from
+ parse_domain_user()
+ * @param[out] populated with user returned from
+ parse_domain_user()
+ * @return bool indicating success or failure
+ */
+bool canonicalize_username(TALLOC_CTX *mem_ctx,
+ char **username_inout,
+ char **namespace,
+ char **domain,
+ char **user);
+char *fill_domain_username_talloc(TALLOC_CTX *ctx,
+ const char *domain,
+ const char *user,
+ bool can_assume);
+struct winbindd_cli_state *winbindd_client_list(void);
+struct winbindd_cli_state *winbindd_client_list_tail(void);
+struct winbindd_cli_state *
+winbindd_client_list_prev(struct winbindd_cli_state *cli);
+void winbindd_add_client(struct winbindd_cli_state *cli);
+void winbindd_remove_client(struct winbindd_cli_state *cli);
+void winbindd_promote_client(struct winbindd_cli_state *cli);
+int winbindd_num_clients(void);
+NTSTATUS lookup_usergroups_cached(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *p_num_groups, struct dom_sid **user_sids);
+
+NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ char **normalized);
+NTSTATUS normalize_name_unmap(TALLOC_CTX *mem_ctx,
+ const char *name,
+ char **normalized);
+
+NTSTATUS resolve_username_to_alias(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ const char *name, char **alias);
+NTSTATUS resolve_alias_to_username(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ const char *alias, char **name);
+
+bool winbindd_can_contact_domain(struct winbindd_domain *domain);
+void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain);
+void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain);
+void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain);
+void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain);
+void set_auth_errors(struct winbindd_response *resp, NTSTATUS result);
+bool is_domain_offline(const struct winbindd_domain *domain);
+bool is_domain_online(const struct winbindd_domain *domain);
+bool parse_sidlist(TALLOC_CTX *mem_ctx, const char *sidstr,
+ struct dom_sid **sids, uint32_t *num_sids);
+bool parse_xidlist(TALLOC_CTX *mem_ctx, const char *xidstr,
+ struct unixid **pxids, uint32_t *pnum_xids);
+
+/* The following definitions come from winbindd/winbindd_wins.c */
+
+void winbindd_wins_byname(struct winbindd_cli_state *state);
+
+struct dcerpc_binding_handle *wbint_binding_handle(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct winbindd_child *child);
+enum winbindd_result winbindd_dual_ndrcmd(struct winbindd_domain *domain,
+ struct winbindd_cli_state *state);
+
+struct tevent_req *wb_lookupsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid);
+NTSTATUS wb_lookupsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ enum lsa_SidType *type, const char **domain,
+ const char **name);
+
+struct tevent_req *winbindd_lookupsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_lookupsid_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_lookupsids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_lookupsids_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_lookupname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *namespace,
+ const char *dom_name,
+ const char *name,
+ uint32_t flags);
+NTSTATUS wb_lookupname_recv(struct tevent_req *req, struct dom_sid *sid,
+ enum lsa_SidType *type);
+
+struct tevent_req *winbindd_lookupname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_lookupname_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_allocate_uid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_allocate_uid_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_allocate_gid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_allocate_gid_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_queryuser_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *user_sid);
+NTSTATUS wb_queryuser_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct wbint_userinfo **pinfo);
+
+struct tevent_req *wb_getpwsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *user_sid,
+ struct winbindd_pw *pw);
+NTSTATUS wb_getpwsid_recv(struct tevent_req *req);
+
+struct tevent_req *winbindd_getpwsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getpwsid_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_getpwnam_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getpwnam_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_getpwuid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getpwuid_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+struct tevent_req *wb_lookupuseraliases_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain,
+ uint32_t num_sids,
+ const struct dom_sid *sids);
+NTSTATUS wb_lookupuseraliases_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_aliases, uint32_t **aliases);
+struct tevent_req *winbindd_getsidaliases_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getsidaliases_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+struct tevent_req *wb_lookupusergroups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid);
+NTSTATUS wb_lookupusergroups_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_sids, struct dom_sid **sids);
+
+struct tevent_req *winbindd_getuserdomgroups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getuserdomgroups_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+struct tevent_req *wb_gettoken_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid,
+ bool expand_local_aliases);
+NTSTATUS wb_gettoken_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_sids, struct dom_sid **sids);
+struct tevent_req *winbindd_getgroups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getgroups_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_seqnum_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain);
+NTSTATUS wb_seqnum_recv(struct tevent_req *req, uint32_t *seqnum);
+
+struct tevent_req *wb_seqnums_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev);
+NTSTATUS wb_seqnums_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ int *num_domains, struct winbindd_domain ***domains,
+ NTSTATUS **statuses, uint32_t **seqnums);
+
+struct tevent_req *winbindd_show_sequence_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_show_sequence_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_group_members_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid,
+ uint32_t num_sids,
+ enum lsa_SidType *type,
+ int max_depth);
+NTSTATUS wb_group_members_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct db_context **members);
+
+struct tevent_req *wb_alias_members_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sid,
+ enum lsa_SidType type,
+ int max_nesting);
+NTSTATUS wb_alias_members_recv(struct tevent_req *req,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_sids,
+ struct dom_sid **sids);
+
+NTSTATUS add_member_to_db(struct db_context *db, struct dom_sid *sid,
+ const char *name);
+
+struct tevent_req *wb_getgrsid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *group_sid,
+ int max_nesting);
+NTSTATUS wb_getgrsid_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ const char **domname, const char **name, gid_t *gid,
+ struct db_context **members);
+
+struct tevent_req *winbindd_getgrgid_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getgrgid_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_getgrnam_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getgrnam_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_getusersids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getusersids_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_lookuprids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_lookuprids_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_query_user_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain);
+NTSTATUS wb_query_user_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ char **users);
+
+struct tevent_req *wb_query_group_list_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_domain *domain);
+NTSTATUS wb_query_group_list_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ uint32_t *num_users,
+ struct wbint_Principal **groups);
+
+struct tevent_req *wb_next_pwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct getpwent_state *gstate,
+ struct winbindd_pw *pw);
+NTSTATUS wb_next_pwent_recv(struct tevent_req *req);
+
+struct tevent_req *winbindd_setpwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_setpwent_recv(struct tevent_req *req,
+ struct winbindd_response *presp);
+
+struct tevent_req *winbindd_getpwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getpwent_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_endpwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_endpwent_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_dsgetdcname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_dsgetdcname_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_dsgetdcname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const char *domain_name,
+ const struct GUID *domain_guid,
+ const char *site_name,
+ uint32_t flags);
+NTSTATUS wb_dsgetdcname_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct netr_DsRGetDCNameInfo **pdcinfo);
+NTSTATUS wb_dsgetdcname_gencache_set(const char *domname,
+ struct netr_DsRGetDCNameInfo *dcinfo);
+NTSTATUS wb_dsgetdcname_gencache_get(TALLOC_CTX *mem_ctx,
+ const char *domname,
+ struct netr_DsRGetDCNameInfo **dcinfo);
+
+struct tevent_req *winbindd_getdcname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getdcname_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_next_grent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ int max_nesting,
+ struct getgrent_state *gstate,
+ struct winbindd_gr *gr);
+NTSTATUS wb_next_grent_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct db_context **members);
+
+struct tevent_req *winbindd_setgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_setgrent_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+struct tevent_req *winbindd_getgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_getgrent_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+struct tevent_req *winbindd_endgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_endgrent_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_list_users_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_list_users_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_list_groups_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_list_groups_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_check_machine_acct_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_check_machine_acct_recv(struct tevent_req *req,
+ struct winbindd_response *presp);
+
+struct tevent_req *winbindd_ping_dc_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_ping_dc_recv(struct tevent_req *req,
+ struct winbindd_response *presp);
+
+struct tevent_req *winbindd_change_machine_acct_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_change_machine_acct_recv(struct tevent_req *req,
+ struct winbindd_response *presp);
+
+struct tevent_req *winbindd_pam_auth_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_pam_auth_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_pam_auth_crap_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_pam_auth_crap_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_pam_chauthtok_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_pam_chauthtok_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_pam_logoff_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_pam_logoff_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *winbindd_pam_chng_pswd_auth_crap_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_pam_chng_pswd_auth_crap_recv(
+ struct tevent_req *req,
+ struct winbindd_response *response);
+
+struct tevent_req *wb_lookupsids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct dom_sid *sids,
+ uint32_t num_sids);
+NTSTATUS wb_lookupsids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct lsa_RefDomainList **domains,
+ struct lsa_TransNameArray **names);
+
+struct tevent_req *wb_sids2xids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct dom_sid *sids,
+ const uint32_t num_sids);
+NTSTATUS wb_sids2xids_recv(struct tevent_req *req,
+ struct unixid xids[], uint32_t num_xids);
+struct tevent_req *winbindd_sids_to_xids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_sids_to_xids_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+struct tevent_req *wb_xids2sids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ const struct unixid *xids,
+ uint32_t num_xids);
+NTSTATUS wb_xids2sids_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
+ struct dom_sid **sids);
+struct tevent_req *winbindd_xids_to_sids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_xids_to_sids_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+struct tevent_req *winbindd_wins_byip_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_wins_byip_recv(struct tevent_req *req,
+ struct winbindd_response *presp);
+struct tevent_req *winbindd_wins_byname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_wins_byname_recv(struct tevent_req *req,
+ struct winbindd_response *presp);
+struct tevent_req *winbindd_domain_info_send(
+ TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request);
+NTSTATUS winbindd_domain_info_recv(struct tevent_req *req,
+ struct winbindd_response *response);
+
+/* The following definitions come from winbindd/winbindd_samr.c */
+
+NTSTATUS open_internal_samr_conn(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct rpc_pipe_client **samr_pipe,
+ struct policy_handle *samr_domain_hnd);
+NTSTATUS open_internal_lsa_conn(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **lsa_pipe,
+ struct policy_handle *lsa_hnd);
+
+/* The following definitions come from winbindd/winbindd_irpc.c */
+NTSTATUS wb_irpc_register(void);
+
+/* The following definitions come from winbindd/winbindd_reconnect.c */
+bool reconnect_need_retry(NTSTATUS status, struct winbindd_domain *domain);
+
+/* The following definitions come from winbindd/winbindd_gpupdate.c */
+void gpupdate_init(void);
+void gpupdate_user_init(const char *user);
+
+/* The following comes from winbindd/winbindd_dual_srv.c */
+bool reset_cm_connection_on_error(struct winbindd_domain *domain,
+ struct dcerpc_binding_handle *b,
+ NTSTATUS status);
+
+#endif /* _WINBINDD_PROTO_H_ */
diff --git a/source3/winbindd/winbindd_reconnect.c b/source3/winbindd/winbindd_reconnect.c
new file mode 100644
index 0000000..c49831b
--- /dev/null
+++ b/source3/winbindd/winbindd_reconnect.c
@@ -0,0 +1,354 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Wrapper around winbindd_rpc.c to centralize retry logic.
+
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+extern struct winbindd_methods msrpc_methods;
+
+bool reconnect_need_retry(NTSTATUS status, struct winbindd_domain *domain)
+{
+ if (NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (!NT_STATUS_IS_ERR(status)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_GROUP)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_ALIAS)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_MEMBER)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_PRIVILEGE)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
+ return false;
+ }
+
+ reset_cm_connection_on_error(domain, NULL, status);
+
+ return true;
+}
+
+/* List all users */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **rids)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.query_user_list(domain, mem_ctx, rids);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.query_user_list(domain, mem_ctx, rids);
+
+ return result;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.enum_dom_groups(domain, mem_ctx,
+ num_entries, info);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.enum_dom_groups(domain, mem_ctx,
+ num_entries, info);
+ return result;
+}
+
+/* List all domain groups */
+
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.enum_local_groups(domain, mem_ctx,
+ num_entries, info);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.enum_local_groups(domain, mem_ctx,
+ num_entries, info);
+
+ return result;
+}
+
+/* convert a single name to a sid in a domain */
+static NTSTATUS name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ const char **pdom_name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.name_to_sid(domain, mem_ctx, domain_name, name,
+ flags, pdom_name, sid, type);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.name_to_sid(domain, mem_ctx,
+ domain_name, name, flags,
+ pdom_name, sid, type);
+
+ return result;
+}
+
+/*
+ convert a domain SID to a user or group name
+*/
+static NTSTATUS sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.sid_to_name(domain, mem_ctx, sid,
+ domain_name, name, type);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.sid_to_name(domain, mem_ctx, sid,
+ domain_name, name, type);
+
+ return result;
+}
+
+static NTSTATUS rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.rids_to_names(domain, mem_ctx, sid,
+ rids, num_rids,
+ domain_name, names, types);
+ if (reconnect_need_retry(result, domain)) {
+ result = msrpc_methods.rids_to_names(domain, mem_ctx, sid,
+ rids, num_rids,
+ domain_name, names,
+ types);
+ }
+
+ return result;
+}
+
+/* Lookup groups a user is a member of. I wish Unix had a call like this! */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *num_groups, struct dom_sid **user_gids)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lookup_usergroups(domain, mem_ctx,
+ user_sid, num_groups,
+ user_gids);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.lookup_usergroups(domain, mem_ctx,
+ user_sid, num_groups,
+ user_gids);
+
+ return result;
+}
+
+static NTSTATUS lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids, const struct dom_sid *sids,
+ uint32_t *num_aliases, uint32_t **alias_rids)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lookup_useraliases(domain, mem_ctx,
+ num_sids, sids,
+ num_aliases,
+ alias_rids);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.lookup_useraliases(domain, mem_ctx,
+ num_sids, sids,
+ num_aliases,
+ alias_rids);
+
+ return result;
+}
+
+/* Lookup alias membership given */
+static NTSTATUS lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ enum lsa_SidType type,
+ uint32_t *num_sids,
+ struct dom_sid **sids)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lookup_aliasmem(domain,
+ mem_ctx,
+ sid,
+ type,
+ num_sids,
+ sids);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.lookup_aliasmem(domain,
+ mem_ctx,
+ sid,
+ type,
+ num_sids,
+ sids);
+
+ return result;
+}
+
+/* Lookup group membership given a rid. */
+static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem, char ***names,
+ uint32_t **name_types)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lookup_groupmem(domain, mem_ctx,
+ group_sid, type, num_names,
+ sid_mem, names,
+ name_types);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.lookup_groupmem(domain, mem_ctx,
+ group_sid, type,
+ num_names,
+ sid_mem, names,
+ name_types);
+
+ return result;
+}
+
+/* find the lockout policy of a domain */
+static NTSTATUS lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *policy)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.lockout_policy(domain, mem_ctx, policy);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.lockout_policy(domain, mem_ctx, policy);
+
+ return result;
+}
+
+/* find the password policy of a domain */
+static NTSTATUS password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *policy)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.password_policy(domain, mem_ctx, policy);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.password_policy(domain, mem_ctx, policy);
+
+ return result;
+}
+
+/* get a list of trusted domains */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *trusts)
+{
+ NTSTATUS result;
+
+ result = msrpc_methods.trusted_domains(domain, mem_ctx, trusts);
+
+ if (reconnect_need_retry(result, domain))
+ result = msrpc_methods.trusted_domains(domain, mem_ctx,
+ trusts);
+
+ return result;
+}
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods reconnect_methods = {
+ False,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ name_to_sid,
+ sid_to_name,
+ rids_to_names,
+ lookup_usergroups,
+ lookup_useraliases,
+ lookup_groupmem,
+ lookup_aliasmem,
+ lockout_policy,
+ password_policy,
+ trusted_domains,
+};
diff --git a/source3/winbindd/winbindd_reconnect_ads.c b/source3/winbindd/winbindd_reconnect_ads.c
new file mode 100644
index 0000000..367f4c6
--- /dev/null
+++ b/source3/winbindd/winbindd_reconnect_ads.c
@@ -0,0 +1,362 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Wrapper around winbindd_ads.c to centralize retry logic.
+ Copyright (C) Christof Schmitt 2016
+
+ Based on winbindd_reconnect.c
+ Copyright (C) Volker Lendecke 2005
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+#ifdef HAVE_ADS
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+extern struct winbindd_methods ads_methods;
+
+static bool ldap_reconnect_need_retry(NTSTATUS status,
+ struct winbindd_domain *domain)
+{
+ if (NT_STATUS_IS_OK(status)) {
+ return false;
+ }
+
+ if (!NT_STATUS_IS_ERR(status)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_GROUP)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_ALIAS)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_MEMBER)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_PRIVILEGE)) {
+ return false;
+ }
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MEMORY)) {
+ return false;
+ }
+
+ return true;
+}
+
+/* List all users */
+static NTSTATUS query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **rids)
+{
+ NTSTATUS result;
+
+ result = ads_methods.query_user_list(domain, mem_ctx, rids);
+
+ if (ldap_reconnect_need_retry(result, domain)) {
+ result = ads_methods.query_user_list(domain, mem_ctx, rids);
+ }
+
+ return result;
+}
+
+/* list all domain groups */
+static NTSTATUS enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ NTSTATUS result;
+
+ result = ads_methods.enum_dom_groups(domain, mem_ctx,
+ num_entries, info);
+
+ if (ldap_reconnect_need_retry(result, domain)) {
+ result = ads_methods.enum_dom_groups(domain, mem_ctx,
+ num_entries, info);
+ }
+
+ return result;
+}
+
+/* List all domain groups */
+static NTSTATUS enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ NTSTATUS result;
+
+ result = ads_methods.enum_local_groups(domain, mem_ctx,
+ num_entries, info);
+
+ if (ldap_reconnect_need_retry(result, domain)) {
+ result = ads_methods.enum_local_groups(domain, mem_ctx,
+ num_entries, info);
+ }
+
+ return result;
+}
+
+/* convert a single name to a sid in a domain */
+static NTSTATUS name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ const char **pdom_name,
+ struct dom_sid *sid,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ result = ads_methods.name_to_sid(domain, mem_ctx, domain_name, name,
+ flags, pdom_name, sid, type);
+
+ if (reconnect_need_retry(result, domain)) {
+ result = ads_methods.name_to_sid(domain, mem_ctx,
+ domain_name, name, flags,
+ pdom_name, sid, type);
+ }
+
+ return result;
+}
+
+/*
+ convert a domain SID to a user or group name
+*/
+static NTSTATUS sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **domain_name,
+ char **name,
+ enum lsa_SidType *type)
+{
+ NTSTATUS result;
+
+ result = ads_methods.sid_to_name(domain, mem_ctx, sid,
+ domain_name, name, type);
+
+ if (reconnect_need_retry(result, domain))
+ result = ads_methods.sid_to_name(domain, mem_ctx, sid,
+ domain_name, name, type);
+
+ return result;
+}
+
+static NTSTATUS rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **domain_name,
+ char ***names,
+ enum lsa_SidType **types)
+{
+ NTSTATUS result;
+
+ result = ads_methods.rids_to_names(domain, mem_ctx, sid,
+ rids, num_rids,
+ domain_name, names, types);
+ if (reconnect_need_retry(result, domain)) {
+ result = ads_methods.rids_to_names(domain, mem_ctx, sid,
+ rids, num_rids, domain_name,
+ names, types);
+ }
+
+ return result;
+}
+
+/* Lookup groups a user is a member of. I wish Unix had a call like this! */
+static NTSTATUS lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *num_groups,
+ struct dom_sid **user_gids)
+{
+ NTSTATUS result;
+
+ result = ads_methods.lookup_usergroups(domain, mem_ctx, user_sid,
+ num_groups, user_gids);
+
+ if (ldap_reconnect_need_retry(result, domain)) {
+ result = ads_methods.lookup_usergroups(domain, mem_ctx,
+ user_sid, num_groups,
+ user_gids);
+ }
+
+ return result;
+}
+
+static NTSTATUS lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *num_aliases, uint32_t **alias_rids)
+{
+ NTSTATUS result;
+
+ result = ads_methods.lookup_useraliases(domain, mem_ctx, num_sids, sids,
+ num_aliases, alias_rids);
+
+ if (reconnect_need_retry(result, domain)) {
+ result = ads_methods.lookup_useraliases(domain, mem_ctx,
+ num_sids, sids,
+ num_aliases,
+ alias_rids);
+ }
+
+ return result;
+}
+
+/* Lookup group membership given a rid. */
+static NTSTATUS lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem, char ***names,
+ uint32_t **name_types)
+{
+ NTSTATUS result;
+
+ result = ads_methods.lookup_groupmem(domain, mem_ctx, group_sid, type,
+ num_names, sid_mem, names,
+ name_types);
+
+ if (ldap_reconnect_need_retry(result, domain)) {
+ result = ads_methods.lookup_groupmem(domain, mem_ctx, group_sid,
+ type, num_names, sid_mem,
+ names, name_types);
+ }
+
+ return result;
+}
+
+static NTSTATUS lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *num_names,
+ struct dom_sid **sid_mem)
+{
+ NTSTATUS result = NT_STATUS_OK;
+
+ result = ads_methods.lookup_aliasmem(domain,
+ mem_ctx,
+ group_sid,
+ type,
+ num_names,
+ sid_mem);
+
+ if (ldap_reconnect_need_retry(result, domain)) {
+ result = ads_methods.lookup_aliasmem(domain,
+ mem_ctx,
+ group_sid,
+ type,
+ num_names,
+ sid_mem);
+ }
+ return result;
+}
+
+/* find the lockout policy of a domain */
+static NTSTATUS lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *policy)
+{
+ NTSTATUS result;
+
+ result = ads_methods.lockout_policy(domain, mem_ctx, policy);
+
+ if (reconnect_need_retry(result, domain)) {
+ result = ads_methods.lockout_policy(domain, mem_ctx, policy);
+ }
+
+ return result;
+}
+
+/* find the password policy of a domain */
+static NTSTATUS password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *policy)
+{
+ NTSTATUS result;
+
+ result = ads_methods.password_policy(domain, mem_ctx, policy);
+
+ if (reconnect_need_retry(result, domain)) {
+ result = ads_methods.password_policy(domain, mem_ctx, policy);
+ }
+
+ return result;
+}
+
+/* get a list of trusted domains */
+static NTSTATUS trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *trusts)
+{
+ NTSTATUS result;
+
+ result = ads_methods.trusted_domains(domain, mem_ctx, trusts);
+
+ if (reconnect_need_retry(result, domain)) {
+ result = ads_methods.trusted_domains(domain, mem_ctx, trusts);
+ }
+
+ return result;
+}
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods reconnect_ads_methods = {
+ true,
+ query_user_list,
+ enum_dom_groups,
+ enum_local_groups,
+ name_to_sid,
+ sid_to_name,
+ rids_to_names,
+ lookup_usergroups,
+ lookup_useraliases,
+ lookup_groupmem,
+ lookup_aliasmem,
+ lockout_policy,
+ password_policy,
+ trusted_domains,
+};
+
+#endif
diff --git a/source3/winbindd/winbindd_rpc.c b/source3/winbindd/winbindd_rpc.c
new file mode 100644
index 0000000..2b4a47e
--- /dev/null
+++ b/source3/winbindd/winbindd_rpc.c
@@ -0,0 +1,855 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Winbind rpc backend functions
+ *
+ * Copyright (c) 2000-2003 Tim Potter
+ * Copyright (c) 2001 Andrew Tridgell
+ * Copyright (c) 2005 Volker Lendecke
+ * Copyright (c) 2008 Guenther Deschner (pidl conversion)
+ * Copyright (c) 2010 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "winbindd_rpc.h"
+#include "rpc_client/rpc_client.h"
+#include "librpc/gen_ndr/ndr_samr_c.h"
+#include "librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_samr.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "../libcli/security/security.h"
+#include "lsa.h"
+
+/* Query display info for a domain */
+NTSTATUS rpc_query_user_list(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const struct dom_sid *domain_sid,
+ uint32_t **prids)
+{
+ struct dcerpc_binding_handle *b = samr_pipe->binding_handle;
+ uint32_t *rids = NULL;
+ uint32_t num_rids = 0;
+ uint32_t i = 0;
+ uint32_t resume_handle = 0;
+ NTSTATUS result = NT_STATUS_UNSUCCESSFUL;
+ NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
+ TALLOC_CTX *tmp_ctx;
+
+ *prids = NULL;
+
+ tmp_ctx = talloc_stackframe();
+ if (tmp_ctx == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ do {
+ struct samr_SamArray *sam_array = NULL;
+ uint32_t count = 0;
+ uint32_t *tmp;
+
+ status = dcerpc_samr_EnumDomainUsers(
+ b, tmp_ctx, samr_policy, &resume_handle,
+ ACB_NORMAL, &sam_array, 0xffff, &count, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ DBG_WARNING("EnumDomainUsers failed: %s\n",
+ nt_errstr(result));
+ status = result;
+ goto done;
+ }
+ }
+
+ if (num_rids + count < num_rids) {
+ status = NT_STATUS_INTEGER_OVERFLOW;
+ goto done;
+ }
+
+ tmp = talloc_realloc(tmp_ctx, rids, uint32_t, num_rids+count);
+ if (tmp == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto done;
+ }
+ rids = tmp;
+
+ for (i=0; i<count; i++) {
+ rids[num_rids++] = sam_array->entries[i].idx;
+ }
+
+ TALLOC_FREE(sam_array);
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ *prids = talloc_steal(mem_ctx, rids);
+ status = NT_STATUS_OK;
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* List all domain groups */
+NTSTATUS rpc_enum_dom_groups(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo)
+{
+ struct wb_acct_info *info = NULL;
+ uint32_t start = 0;
+ uint32_t num_info = 0;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = samr_pipe->binding_handle;
+
+ *pnum_info = 0;
+
+ do {
+ struct samr_SamArray *sam_array = NULL;
+ uint32_t count = 0;
+ uint32_t g;
+
+ /* start is updated by this call. */
+ status = dcerpc_samr_EnumDomainGroups(b,
+ mem_ctx,
+ samr_policy,
+ &start,
+ &sam_array,
+ 0xFFFF, /* buffer size? */
+ &count,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ DEBUG(2,("query_user_list: failed to enum domain groups: %s\n",
+ nt_errstr(result)));
+ return result;
+ }
+ }
+
+ info = talloc_realloc(mem_ctx,
+ info,
+ struct wb_acct_info,
+ num_info + count);
+ if (info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (g = 0; g < count; g++) {
+ struct wb_acct_info *i = &info[num_info + g];
+
+ i->acct_name = talloc_strdup(info,
+ sam_array->entries[g].name.string);
+ if (i->acct_name == NULL) {
+ TALLOC_FREE(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ i->acct_desc = NULL;
+ i->rid = sam_array->entries[g].idx;
+ }
+
+ num_info += count;
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ *pnum_info = num_info;
+ *pinfo = info;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpc_enum_local_groups(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo)
+{
+ struct wb_acct_info *info = NULL;
+ uint32_t num_info = 0;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = samr_pipe->binding_handle;
+
+ *pnum_info = 0;
+
+ do {
+ struct samr_SamArray *sam_array = NULL;
+ uint32_t count = 0;
+ uint32_t start = num_info;
+ uint32_t g;
+
+ status = dcerpc_samr_EnumDomainAliases(b,
+ mem_ctx,
+ samr_policy,
+ &start,
+ &sam_array,
+ 0xFFFF, /* buffer size? */
+ &count,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ return result;
+ }
+ }
+
+ info = talloc_realloc(mem_ctx,
+ info,
+ struct wb_acct_info,
+ num_info + count);
+ if (info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (g = 0; g < count; g++) {
+ struct wb_acct_info *i = &info[num_info + g];
+
+ i->acct_name = talloc_strdup(info,
+ sam_array->entries[g].name.string);
+ if (i->acct_name == NULL) {
+ TALLOC_FREE(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ i->acct_desc = NULL;
+ i->rid = sam_array->entries[g].idx;
+ }
+
+ num_info += count;
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ *pnum_info = num_info;
+ *pinfo = info;
+
+ return NT_STATUS_OK;
+}
+
+/* Lookup groups a user is a member of. */
+NTSTATUS rpc_lookup_usergroups(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *user_sid,
+ uint32_t *pnum_groups,
+ struct dom_sid **puser_grpsids)
+{
+ struct policy_handle user_policy;
+ struct samr_RidWithAttributeArray *rid_array = NULL;
+ struct dom_sid *user_grpsids = NULL;
+ uint32_t num_groups = 0, i;
+ uint32_t user_rid;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = samr_pipe->binding_handle;
+
+ if (!sid_peek_check_rid(domain_sid, user_sid, &user_rid)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /* Get user handle */
+ status = dcerpc_samr_OpenUser(b,
+ mem_ctx,
+ samr_policy,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ user_rid,
+ &user_policy,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ /* Query user rids */
+ status = dcerpc_samr_GetGroupsForUser(b,
+ mem_ctx,
+ &user_policy,
+ &rid_array,
+ &result);
+ {
+ NTSTATUS _result;
+ dcerpc_samr_Close(b, mem_ctx, &user_policy, &_result);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ num_groups = rid_array->count;
+
+ user_grpsids = talloc_array(mem_ctx, struct dom_sid, num_groups);
+ if (user_grpsids == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ return status;
+ }
+
+ for (i = 0; i < num_groups; i++) {
+ sid_compose(&(user_grpsids[i]), domain_sid,
+ rid_array->rids[i].rid);
+ }
+
+ *pnum_groups = num_groups;
+
+ *puser_grpsids = user_grpsids;
+
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpc_lookup_useraliases(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *pnum_aliases,
+ uint32_t **palias_rids)
+{
+#define MAX_SAM_ENTRIES_W2K 0x400 /* 1024 */
+ uint32_t num_query_sids = 0;
+ uint32_t num_queries = 1;
+ uint32_t num_aliases = 0;
+ uint32_t total_sids = 0;
+ uint32_t *alias_rids = NULL;
+ uint32_t rangesize = MAX_SAM_ENTRIES_W2K;
+ uint32_t i;
+ struct samr_Ids alias_rids_query;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = samr_pipe->binding_handle;
+
+ do {
+ /* prepare query */
+ struct lsa_SidArray sid_array;
+
+ ZERO_STRUCT(sid_array);
+
+ num_query_sids = MIN(num_sids - total_sids, rangesize);
+
+ DEBUG(10,("rpc: lookup_useraliases: entering query %d for %d sids\n",
+ num_queries, num_query_sids));
+
+ if (num_query_sids) {
+ sid_array.sids = talloc_zero_array(mem_ctx, struct lsa_SidPtr, num_query_sids);
+ if (sid_array.sids == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ sid_array.sids = NULL;
+ }
+
+ for (i = 0; i < num_query_sids; i++) {
+ sid_array.sids[i].sid = dom_sid_dup(mem_ctx, &sids[total_sids++]);
+ if (sid_array.sids[i].sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ sid_array.num_sids = num_query_sids;
+
+ /* do request */
+ status = dcerpc_samr_GetAliasMembership(b,
+ mem_ctx,
+ samr_policy,
+ &sid_array,
+ &alias_rids_query,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ /* process output */
+ for (i = 0; i < alias_rids_query.count; i++) {
+ size_t na = num_aliases;
+
+ if (!add_rid_to_array_unique(mem_ctx,
+ alias_rids_query.ids[i],
+ &alias_rids,
+ &na)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ num_aliases = na;
+ }
+
+ num_queries++;
+
+ } while (total_sids < num_sids);
+
+ DEBUG(10,("rpc: rpc_lookup_useraliases: got %d aliases in %d queries "
+ "(rangesize: %d)\n", num_aliases, num_queries, rangesize));
+
+ *pnum_aliases = num_aliases;
+ *palias_rids = alias_rids;
+
+ return NT_STATUS_OK;
+#undef MAX_SAM_ENTRIES_W2K
+}
+
+/* Lookup group membership given a rid. */
+NTSTATUS rpc_lookup_groupmem(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const char *domain_name,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *pnum_names,
+ struct dom_sid **psid_mem,
+ char ***pnames,
+ uint32_t **pname_types)
+{
+ struct policy_handle group_policy;
+ uint32_t group_rid;
+ uint32_t *rid_mem = NULL;
+
+ uint32_t num_names = 0;
+ uint32_t total_names = 0;
+ struct dom_sid *sid_mem = NULL;
+ char **names = NULL;
+ uint32_t *name_types = NULL;
+
+ struct lsa_Strings tmp_names;
+ struct samr_Ids tmp_types;
+
+ uint32_t j, r;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = samr_pipe->binding_handle;
+
+ if (!sid_peek_check_rid(domain_sid, group_sid, &group_rid)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ switch(type) {
+ case SID_NAME_DOM_GRP:
+ {
+ struct samr_RidAttrArray *rids = NULL;
+
+ status = dcerpc_samr_OpenGroup(b,
+ mem_ctx,
+ samr_policy,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ group_rid,
+ &group_policy,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ /*
+ * Step #1: Get a list of user rids that are the members of the group.
+ */
+ status = dcerpc_samr_QueryGroupMember(b,
+ mem_ctx,
+ &group_policy,
+ &rids,
+ &result);
+ {
+ NTSTATUS _result;
+ dcerpc_samr_Close(b, mem_ctx, &group_policy, &_result);
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+
+ if (rids == NULL || rids->count == 0) {
+ pnum_names = 0;
+ pnames = NULL;
+ pname_types = NULL;
+ psid_mem = NULL;
+
+ return NT_STATUS_OK;
+ }
+
+ num_names = rids->count;
+ rid_mem = rids->rids;
+
+ break;
+ }
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ /*
+ * Step #2: Convert list of rids into list of usernames.
+ */
+ if (num_names > 0) {
+ names = talloc_zero_array(mem_ctx, char *, num_names);
+ name_types = talloc_zero_array(mem_ctx, uint32_t, num_names);
+ sid_mem = talloc_zero_array(mem_ctx, struct dom_sid, num_names);
+ if (names == NULL || name_types == NULL || sid_mem == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ for (j = 0; j < num_names; j++) {
+ sid_compose(&sid_mem[j], domain_sid, rid_mem[j]);
+ }
+
+ status = dcerpc_samr_LookupRids(b,
+ mem_ctx,
+ samr_policy,
+ num_names,
+ rid_mem,
+ &tmp_names,
+ &tmp_types,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (!NT_STATUS_IS_OK(result)) {
+ if (!NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) {
+ return result;
+ }
+ }
+
+ /* Copy result into array. The talloc system will take
+ care of freeing the temporary arrays later on. */
+ if (tmp_names.count != num_names) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (tmp_types.count != num_names) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ for (r = 0; r < tmp_names.count; r++) {
+ if (tmp_types.ids[r] == SID_NAME_UNKNOWN) {
+ continue;
+ }
+ if (total_names >= num_names) {
+ break;
+ }
+ names[total_names] = fill_domain_username_talloc(names,
+ domain_name,
+ tmp_names.names[r].string,
+ true);
+ if (names[total_names] == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ name_types[total_names] = tmp_types.ids[r];
+ total_names++;
+ }
+
+ *pnum_names = total_names;
+ *pnames = names;
+ *pname_types = name_types;
+ *psid_mem = sid_mem;
+
+ return NT_STATUS_OK;
+}
+
+/* Lookup alias membership using a rid taken from alias_sid. */
+NTSTATUS rpc_lookup_aliasmem(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *alias_sid,
+ enum lsa_SidType type,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids)
+{
+ uint32_t alias_rid;
+ struct dom_sid *sid_mem = NULL;
+ struct lsa_SidArray sid_array;
+ uint32_t i;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = samr_pipe->binding_handle;
+
+ if (!sid_peek_check_rid(domain_sid, alias_sid, &alias_rid)) {
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+
+ switch (type) {
+ case SID_NAME_ALIAS: {
+ struct policy_handle alias_policy;
+
+ status = dcerpc_samr_OpenAlias(b,
+ mem_ctx,
+ samr_policy,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ alias_rid,
+ &alias_policy,
+ &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ status = dcerpc_samr_GetMembersInAlias(b,
+ mem_ctx,
+ &alias_policy,
+ &sid_array,
+ &result);
+ {
+ NTSTATUS _result;
+ dcerpc_samr_Close(b, mem_ctx, &alias_policy, &_result);
+ }
+ if (any_nt_status_not_ok(status, result, &status)) {
+ return status;
+ }
+
+ sid_mem = talloc_zero_array(mem_ctx,
+ struct dom_sid,
+ sid_array.num_sids);
+ if (sid_mem == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * We cannot just simply assign '*psids = sid_array.sids;'
+ * we need to copy every sid since these are incompatible types:
+ * 'struct dom_sid *' vs 'struct lsa_SidPtr *'
+ */
+ for (i = 0; i < sid_array.num_sids; i++) {
+ sid_copy(&sid_mem[i], sid_array.sids[i].sid);
+ }
+
+ *pnum_sids = sid_array.num_sids;
+ *psids = sid_mem;
+
+ return NT_STATUS_OK;
+ }
+ default:
+ return NT_STATUS_UNSUCCESSFUL;
+ }
+}
+
+/* Get a list of trusted domains */
+NTSTATUS rpc_trusted_domains(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *lsa_pipe,
+ struct policy_handle *lsa_policy,
+ uint32_t *pnum_trusts,
+ struct netr_DomainTrust **ptrusts)
+{
+ struct netr_DomainTrust *array = NULL;
+ uint32_t enum_ctx = 0;
+ uint32_t count = 0;
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = lsa_pipe->binding_handle;
+
+ do {
+ struct lsa_DomainList dom_list;
+ struct lsa_DomainListEx dom_list_ex;
+ bool has_ex = false;
+ uint32_t i;
+
+ /*
+ * We don't run into deadlocks here, cause winbind_off() is
+ * called in the main function.
+ */
+ status = dcerpc_lsa_EnumTrustedDomainsEx(b,
+ mem_ctx,
+ lsa_policy,
+ &enum_ctx,
+ &dom_list_ex,
+ (uint32_t) -1,
+ &result);
+ if (NT_STATUS_IS_OK(status) && !NT_STATUS_IS_ERR(result) &&
+ dom_list_ex.count > 0) {
+ count += dom_list_ex.count;
+ has_ex = true;
+ } else {
+ status = dcerpc_lsa_EnumTrustDom(b,
+ mem_ctx,
+ lsa_policy,
+ &enum_ctx,
+ &dom_list,
+ (uint32_t) -1,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ if (!NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES)) {
+ return result;
+ }
+ }
+
+ count += dom_list.count;
+ }
+
+ array = talloc_realloc(mem_ctx,
+ array,
+ struct netr_DomainTrust,
+ count);
+ if (array == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < count; i++) {
+ struct netr_DomainTrust *trust = &array[i];
+ struct dom_sid *sid;
+
+ ZERO_STRUCTP(trust);
+
+ sid = talloc(array, struct dom_sid);
+ if (sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (dom_list_ex.domains[i].sid == NULL) {
+ DBG_ERR("Trusted domain %s has no SID, "
+ "skipping!\n",
+ trust->dns_name);
+ continue;
+ }
+
+ if (has_ex) {
+ trust->netbios_name = talloc_move(array,
+ &dom_list_ex.domains[i].netbios_name.string);
+ trust->dns_name = talloc_move(array,
+ &dom_list_ex.domains[i].domain_name.string);
+ sid_copy(sid, dom_list_ex.domains[i].sid);
+ } else {
+ trust->netbios_name = talloc_move(array,
+ &dom_list.domains[i].name.string);
+ trust->dns_name = NULL;
+
+ sid_copy(sid, dom_list.domains[i].sid);
+ }
+
+ trust->sid = sid;
+ }
+ } while (NT_STATUS_EQUAL(result, STATUS_MORE_ENTRIES));
+
+ *pnum_trusts = count;
+ *ptrusts = array;
+
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS rpc_try_lookup_sids3(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct rpc_pipe_client *cli,
+ struct lsa_SidArray *sids,
+ struct lsa_RefDomainList **pdomains,
+ struct lsa_TransNameArray **pnames)
+{
+ struct lsa_TransNameArray2 lsa_names2;
+ struct lsa_TransNameArray *names = *pnames;
+ uint32_t i, count = 0;
+ NTSTATUS status, result;
+
+ ZERO_STRUCT(lsa_names2);
+ status = dcerpc_lsa_LookupSids3(cli->binding_handle,
+ mem_ctx,
+ sids,
+ pdomains,
+ &lsa_names2,
+ LSA_LOOKUP_NAMES_ALL,
+ &count,
+ LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES,
+ LSA_CLIENT_REVISION_2,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (NT_STATUS_LOOKUP_ERR(result)) {
+ return result;
+ }
+ if (sids->num_sids != lsa_names2.count) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ names->count = lsa_names2.count;
+ names->names = talloc_array(names, struct lsa_TranslatedName,
+ names->count);
+ if (names->names == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ for (i=0; i<names->count; i++) {
+ names->names[i].sid_type = lsa_names2.names[i].sid_type;
+ names->names[i].name.string = talloc_move(
+ names->names, &lsa_names2.names[i].name.string);
+ names->names[i].sid_index = lsa_names2.names[i].sid_index;
+
+ if (names->names[i].sid_index == UINT32_MAX) {
+ continue;
+ }
+ if ((*pdomains) == NULL) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (names->names[i].sid_index >= (*pdomains)->count) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ }
+ return NT_STATUS_OK;
+}
+
+NTSTATUS rpc_lookup_sids(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct lsa_SidArray *sids,
+ struct lsa_RefDomainList **pdomains,
+ struct lsa_TransNameArray **pnames)
+{
+ struct lsa_TransNameArray *names = *pnames;
+ struct rpc_pipe_client *cli = NULL;
+ struct policy_handle lsa_policy;
+ uint32_t count;
+ uint32_t i;
+ NTSTATUS status, result;
+
+ status = cm_connect_lsat(domain, mem_ctx, &cli, &lsa_policy);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (cli->transport->transport == NCACN_IP_TCP) {
+ return rpc_try_lookup_sids3(mem_ctx, domain, cli, sids,
+ pdomains, pnames);
+ }
+
+ status = dcerpc_lsa_LookupSids(cli->binding_handle, mem_ctx,
+ &lsa_policy, sids, pdomains,
+ names, LSA_LOOKUP_NAMES_ALL,
+ &count, &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (NT_STATUS_LOOKUP_ERR(result)) {
+ return result;
+ }
+
+ if (sids->num_sids != names->count) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+
+ for (i=0; i < names->count; i++) {
+ if (names->names[i].sid_index == UINT32_MAX) {
+ continue;
+ }
+ if ((*pdomains) == NULL) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ if (names->names[i].sid_index >= (*pdomains)->count) {
+ return NT_STATUS_INVALID_NETWORK_RESPONSE;
+ }
+ }
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_rpc.h b/source3/winbindd/winbindd_rpc.h
new file mode 100644
index 0000000..020958f
--- /dev/null
+++ b/source3/winbindd/winbindd_rpc.h
@@ -0,0 +1,95 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Winbind rpc backend functions
+ *
+ * Copyright (c) 2000-2003 Tim Potter
+ * Copyright (c) 2001 Andrew Tridgell
+ * Copyright (c) 2005 Volker Lendecke
+ * Copyright (c) 2008 Guenther Deschner (pidl conversion)
+ * Copyright (c) 2010 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _WINBINDD_RPC_H_
+#define _WINBINDD_RPC_H_
+
+/* Query display info for a domain */
+NTSTATUS rpc_query_user_list(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const struct dom_sid *domain_sid,
+ uint32_t **prids);
+
+NTSTATUS rpc_enum_dom_groups(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *sam_policy,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo);
+
+/* List all domain groups */
+NTSTATUS rpc_enum_local_groups(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo);
+
+/* Lookup groups a user is a member of. */
+NTSTATUS rpc_lookup_usergroups(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *user_sid,
+ uint32_t *pnum_groups,
+ struct dom_sid **puser_grpsids);
+
+NTSTATUS rpc_lookup_useraliases(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *pnum_aliases,
+ uint32_t **palias_rids);
+
+/* Lookup group membership given a rid. */
+NTSTATUS rpc_lookup_groupmem(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const char *domain_name,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *pnum_names,
+ struct dom_sid **psid_mem,
+ char ***pnames,
+ uint32_t **pname_types);
+
+NTSTATUS rpc_lookup_aliasmem(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *samr_pipe,
+ struct policy_handle *samr_policy,
+ const struct dom_sid *domain_sid,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *pnum_sids,
+ struct dom_sid **psids);
+
+/* Get a list of trusted domains */
+NTSTATUS rpc_trusted_domains(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client *lsa_pipe,
+ struct policy_handle *lsa_policy,
+ uint32_t *pnum_trusts,
+ struct netr_DomainTrust **ptrusts);
+
+#endif /* _WINBINDD_RPC_H_ */
diff --git a/source3/winbindd/winbindd_samr.c b/source3/winbindd/winbindd_samr.c
new file mode 100644
index 0000000..65e9932
--- /dev/null
+++ b/source3/winbindd/winbindd_samr.c
@@ -0,0 +1,1424 @@
+/*
+ * Unix SMB/CIFS implementation.
+ *
+ * Winbind rpc backend functions
+ *
+ * Copyright (c) 2000-2003 Tim Potter
+ * Copyright (c) 2001 Andrew Tridgell
+ * Copyright (c) 2005 Volker Lendecke
+ * Copyright (c) 2008 Guenther Deschner (pidl conversion)
+ * Copyright (c) 2010 Andreas Schneider <asn@samba.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "includes.h"
+#include "winbindd.h"
+#include "winbindd_rpc.h"
+#include "lib/util_unixsids.h"
+#include "rpc_client/rpc_client.h"
+#include "rpc_client/cli_pipe.h"
+#include "../librpc/gen_ndr/ndr_samr_c.h"
+#include "rpc_client/cli_samr.h"
+#include "../librpc/gen_ndr/ndr_lsa_c.h"
+#include "rpc_client/cli_lsarpc.h"
+#include "rpc_server/rpc_ncacn_np.h"
+#include "../libcli/security/security.h"
+#include "passdb/machine_sid.h"
+#include "auth.h"
+#include "source3/lib/global_contexts.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/*
+ * The other end of this won't go away easily, so we can trust it
+ *
+ * It is either a long-lived process with the same lifetime as
+ * winbindd or a part of this process
+ */
+struct winbind_internal_pipes {
+ struct tevent_timer *shutdown_timer;
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle samr_domain_hnd;
+ struct rpc_pipe_client *lsa_pipe;
+ struct policy_handle lsa_hnd;
+};
+
+
+NTSTATUS open_internal_samr_conn(TALLOC_CTX *mem_ctx,
+ struct winbindd_domain *domain,
+ struct rpc_pipe_client **samr_pipe,
+ struct policy_handle *samr_domain_hnd)
+{
+ NTSTATUS status, result;
+ struct policy_handle samr_connect_hnd;
+ struct dcerpc_binding_handle *b;
+
+ status = wb_open_internal_pipe(mem_ctx, &ndr_table_samr, samr_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Could not connect to %s pipe: %s\n",
+ ndr_table_samr.name, nt_errstr(status));
+ return status;
+ }
+
+ b = (*samr_pipe)->binding_handle;
+
+ status = dcerpc_samr_Connect2(b, mem_ctx,
+ (*samr_pipe)->desthost,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &samr_connect_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ return result;
+ }
+
+ status = dcerpc_samr_OpenDomain(b, mem_ctx,
+ &samr_connect_hnd,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ &domain->sid,
+ samr_domain_hnd,
+ &result);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ return result;
+}
+
+NTSTATUS open_internal_lsa_conn(TALLOC_CTX *mem_ctx,
+ struct rpc_pipe_client **lsa_pipe,
+ struct policy_handle *lsa_hnd)
+{
+ NTSTATUS status;
+
+ status = wb_open_internal_pipe(mem_ctx, &ndr_table_lsarpc, lsa_pipe);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Could not connect to %s pipe: %s\n",
+ ndr_table_lsarpc.name, nt_errstr(status));
+ return status;
+ }
+
+ status = rpccli_lsa_open_policy((*lsa_pipe),
+ mem_ctx,
+ true,
+ SEC_FLAG_MAXIMUM_ALLOWED,
+ lsa_hnd);
+
+ return status;
+}
+
+static void cached_internal_pipe_close(
+ struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval current_time,
+ void *private_data)
+{
+ struct winbindd_domain *domain = talloc_get_type_abort(
+ private_data, struct winbindd_domain);
+ /*
+ * Freeing samr_pipes closes the cached pipes.
+ *
+ * We can do a hard close because at the time of this commit
+ * we only use synchronous calls to external pipes. So we can't
+ * have any outstanding requests. Also, we don't set
+ * dcerpc_binding_handle_set_sync_ev in winbind, so we don't
+ * get nested event loops. Once we start to get async in
+ * winbind children, we need to check for outstanding calls
+ */
+ TALLOC_FREE(domain->backend_data.samr_pipes);
+}
+
+static NTSTATUS open_cached_internal_pipe_conn(
+ struct winbindd_domain *domain,
+ struct rpc_pipe_client **samr_pipe,
+ struct policy_handle *samr_domain_hnd,
+ struct rpc_pipe_client **lsa_pipe,
+ struct policy_handle *lsa_hnd)
+{
+ struct winbind_internal_pipes *internal_pipes =
+ domain->backend_data.samr_pipes;
+
+ if (internal_pipes == NULL) {
+ TALLOC_CTX *frame = talloc_stackframe();
+ NTSTATUS status;
+
+ internal_pipes = talloc_zero(frame,
+ struct winbind_internal_pipes);
+
+ status = open_internal_samr_conn(
+ internal_pipes,
+ domain,
+ &internal_pipes->samr_pipe,
+ &internal_pipes->samr_domain_hnd);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ status = open_internal_lsa_conn(internal_pipes,
+ &internal_pipes->lsa_pipe,
+ &internal_pipes->lsa_hnd);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(frame);
+ return status;
+ }
+
+ internal_pipes->shutdown_timer = tevent_add_timer(
+ global_event_context(),
+ internal_pipes,
+ timeval_current_ofs(5, 0),
+ cached_internal_pipe_close,
+ domain);
+ if (internal_pipes->shutdown_timer == NULL) {
+ TALLOC_FREE(frame);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ domain->backend_data.samr_pipes =
+ talloc_steal(domain, internal_pipes);
+
+ TALLOC_FREE(frame);
+ }
+
+ if (samr_domain_hnd) {
+ *samr_domain_hnd = internal_pipes->samr_domain_hnd;
+ }
+
+ if (samr_pipe) {
+ *samr_pipe = internal_pipes->samr_pipe;
+ }
+
+ if (lsa_hnd) {
+ *lsa_hnd = internal_pipes->lsa_hnd;
+ }
+
+ if (lsa_pipe) {
+ *lsa_pipe = internal_pipes->lsa_pipe;
+ }
+
+ tevent_update_timer(
+ internal_pipes->shutdown_timer,
+ timeval_current_ofs(5, 0));
+
+ return NT_STATUS_OK;
+}
+
+static bool reset_connection_on_error(struct winbindd_domain *domain,
+ struct rpc_pipe_client *p,
+ NTSTATUS status)
+{
+ struct dcerpc_binding_handle *b = p->binding_handle;
+
+ if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) ||
+ NT_STATUS_EQUAL(status, NT_STATUS_IO_DEVICE_ERROR))
+ {
+ TALLOC_FREE(domain->backend_data.samr_pipes);
+ return true;
+ }
+
+ if (!dcerpc_binding_handle_is_connected(b)) {
+ TALLOC_FREE(domain->backend_data.samr_pipes);
+ return true;
+ }
+
+ return false;
+}
+
+/*********************************************************************
+ SAM specific functions.
+*********************************************************************/
+
+/* List all domain groups */
+static NTSTATUS sam_enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol = { 0 };
+ struct wb_acct_info *info = NULL;
+ uint32_t num_info = 0;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DEBUG(3,("sam_enum_dom_groups\n"));
+
+ if (pnum_info) {
+ *pnum_info = 0;
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ status = rpc_enum_dom_groups(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &num_info,
+ &info);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(tmp_ctx);
+ return status;
+ }
+
+ if (pnum_info) {
+ *pnum_info = num_info;
+ }
+
+ if (pinfo) {
+ *pinfo = talloc_move(mem_ctx, &info);
+ }
+
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* Query display info for a domain */
+static NTSTATUS sam_query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **prids)
+{
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct policy_handle dom_pol = { 0 };
+ uint32_t *rids = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DEBUG(3,("samr_query_user_list\n"));
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_query_user_list(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &domain->sid,
+ &rids);
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (prids != NULL) {
+ *prids = talloc_move(mem_ctx, &rids);
+ }
+
+done:
+ TALLOC_FREE(rids);
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* get a list of trusted domains - builtin domain */
+static NTSTATUS sam_trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *ptrust_list)
+{
+ struct rpc_pipe_client *lsa_pipe;
+ struct policy_handle lsa_policy = { 0 };
+ struct netr_DomainTrust *trusts = NULL;
+ uint32_t num_trusts = 0;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DEBUG(3,("samr: trusted domains\n"));
+
+ if (ptrust_list) {
+ ZERO_STRUCTP(ptrust_list);
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ NULL,
+ NULL,
+ &lsa_pipe,
+ &lsa_policy);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_trusted_domains(tmp_ctx,
+ lsa_pipe,
+ &lsa_policy,
+ &num_trusts,
+ &trusts);
+
+ if (!retry && reset_connection_on_error(domain, lsa_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (ptrust_list) {
+ ptrust_list->count = num_trusts;
+ ptrust_list->array = talloc_move(mem_ctx, &trusts);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* Lookup group membership given a rid. */
+static NTSTATUS sam_lookup_groupmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *pnum_names,
+ struct dom_sid **psid_mem,
+ char ***pnames,
+ uint32_t **pname_types)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol = { 0 };
+
+ uint32_t num_names = 0;
+ struct dom_sid *sid_mem = NULL;
+ char **names = NULL;
+ uint32_t *name_types = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DEBUG(3,("sam_lookup_groupmem\n"));
+
+ /* Paranoia check */
+ if (sid_check_is_in_builtin(group_sid) && (type != SID_NAME_ALIAS)) {
+ /* There's no groups, only aliases in BUILTIN */
+ status = NT_STATUS_NO_SUCH_GROUP;
+ goto done;
+ }
+
+ if (pnum_names) {
+ *pnum_names = 0;
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_lookup_groupmem(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ domain->name,
+ &domain->sid,
+ group_sid,
+ type,
+ &num_names,
+ &sid_mem,
+ &names,
+ &name_types);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (pnum_names) {
+ *pnum_names = num_names;
+ }
+
+ if (pnames) {
+ *pnames = talloc_move(mem_ctx, &names);
+ }
+
+ if (pname_types) {
+ *pname_types = talloc_move(mem_ctx, &name_types);
+ }
+
+ if (psid_mem) {
+ *psid_mem = talloc_move(mem_ctx, &sid_mem);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* Lookup alias membership */
+static NTSTATUS sam_lookup_aliasmem(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *group_sid,
+ enum lsa_SidType type,
+ uint32_t *pnum_sids,
+ struct dom_sid **psid_mem)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol = {0};
+
+ uint32_t num_sids = 0;
+ struct dom_sid *sid_mem = NULL;
+
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DBG_INFO("sam_lookup_aliasmem\n");
+
+ /* Paranoia check */
+ if (type != SID_NAME_ALIAS) {
+ status = NT_STATUS_NO_SUCH_ALIAS;
+ goto done;
+ }
+
+ if (pnum_sids) {
+ *pnum_sids = 0;
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_lookup_aliasmem(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &domain->sid,
+ group_sid,
+ type,
+ &num_sids,
+ &sid_mem);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (pnum_sids) {
+ *pnum_sids = num_sids;
+ }
+
+ if (psid_mem) {
+ *psid_mem = talloc_move(mem_ctx, &sid_mem);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/*********************************************************************
+ BUILTIN specific functions.
+*********************************************************************/
+
+/* List all domain groups */
+static NTSTATUS builtin_enum_dom_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *num_entries,
+ struct wb_acct_info **info)
+{
+ /* BUILTIN doesn't have domain groups */
+ *num_entries = 0;
+ *info = NULL;
+ return NT_STATUS_OK;
+}
+
+/* Query display info for a domain */
+static NTSTATUS builtin_query_user_list(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t **rids)
+{
+ /* We don't have users */
+ *rids = NULL;
+ return NT_STATUS_OK;
+}
+
+/* get a list of trusted domains - builtin domain */
+static NTSTATUS builtin_trusted_domains(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct netr_DomainTrustList *trusts)
+{
+ ZERO_STRUCTP(trusts);
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ COMMON functions.
+*********************************************************************/
+
+/* List all local groups (aliases) */
+static NTSTATUS sam_enum_local_groups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t *pnum_info,
+ struct wb_acct_info **pinfo)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol = { 0 };
+ struct wb_acct_info *info = NULL;
+ uint32_t num_info = 0;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DEBUG(3,("samr: enum local groups\n"));
+
+ if (pnum_info) {
+ *pnum_info = 0;
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_enum_local_groups(mem_ctx,
+ samr_pipe,
+ &dom_pol,
+ &num_info,
+
+ &info);
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pnum_info) {
+ *pnum_info = num_info;
+ }
+
+ if (pinfo) {
+ *pinfo = talloc_move(mem_ctx, &info);
+ }
+
+done:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* convert a single name to a sid in a domain */
+static NTSTATUS sam_name_to_sid(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ uint32_t flags,
+ const char **pdom_name,
+ struct dom_sid *psid,
+ enum lsa_SidType *ptype)
+{
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct dcerpc_binding_handle *h = NULL;
+ struct policy_handle dom_pol = { .handle_type = 0, };
+ struct dom_sid sid;
+ const char *dom_name = domain_name;
+ struct lsa_String lsa_name = { .string = name };
+ struct samr_Ids rids = { .count = 0 };
+ struct samr_Ids types = { .count = 0 };
+ enum lsa_SidType type;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_NONE_MAPPED;
+ NTSTATUS result;
+ bool retry = false;
+ bool ok;
+
+ DBG_NOTICE("%s\\%s\n", domain_name, name);
+
+ if (strequal(domain_name, unix_users_domain_name())) {
+ struct passwd *pwd = NULL;
+
+ if (name[0] == '\0') {
+ sid_copy(&sid, &global_sid_Unix_Users);
+ type = SID_NAME_DOMAIN;
+ goto done;
+ }
+
+ pwd = Get_Pwnam_alloc(tmp_ctx, name);
+ if (pwd == NULL) {
+ goto fail;
+ }
+ ok = sid_compose(&sid, &global_sid_Unix_Users, pwd->pw_uid);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+ type = SID_NAME_USER;
+ goto done;
+ }
+
+ if (strequal(domain_name, unix_groups_domain_name())) {
+ struct group *grp = NULL;
+
+ if (name[0] == '\0') {
+ sid_copy(&sid, &global_sid_Unix_Groups);
+ type = SID_NAME_DOMAIN;
+ goto done;
+ }
+
+ grp = getgrnam(name);
+ if (grp == NULL) {
+ goto fail;
+ }
+ ok = sid_compose(&sid, &global_sid_Unix_Groups, grp->gr_gid);
+ if (!ok) {
+ status = NT_STATUS_INTERNAL_ERROR;
+ goto fail;
+ }
+ type = SID_NAME_DOM_GRP;
+ goto done;
+ }
+
+ if (name[0] == '\0') {
+ sid_copy(&sid, &domain->sid);
+ type = SID_NAME_DOMAIN;
+ goto done;
+ }
+
+ ok = lookup_wellknown_name(tmp_ctx, name, &sid, &dom_name);
+ if (ok) {
+ type = SID_NAME_WKN_GRP;
+ goto done;
+ }
+
+ {
+ char *normalized = NULL;
+ NTSTATUS nstatus = normalize_name_unmap(
+ tmp_ctx, name, &normalized);
+ if (NT_STATUS_IS_OK(nstatus) ||
+ NT_STATUS_EQUAL(nstatus, NT_STATUS_FILE_RENAMED)) {
+ lsa_name.string = normalized;
+ }
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(
+ domain, &samr_pipe, &dom_pol, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ h = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_LookupNames(
+ h, tmp_ctx, &dom_pol, 1, &lsa_name, &rids, &types, &result);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_samr_LookupNames returned %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_DEBUG("dcerpc_samr_LookupNames resulted in %s\n",
+ nt_errstr(status));
+ status = result;
+ goto fail;
+ }
+
+ sid_compose(&sid, &domain->sid, rids.ids[0]);
+ type = types.ids[0];
+
+done:
+ if (pdom_name != NULL) {
+ *pdom_name = talloc_strdup(mem_ctx, dom_name);
+ if (*pdom_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ if (psid) {
+ sid_copy(psid, &sid);
+ }
+ if (ptype) {
+ *ptype = type;
+ }
+
+ status = NT_STATUS_OK;
+fail:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* convert a domain SID to a user or group name */
+static NTSTATUS sam_sid_to_name(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *sid,
+ char **pdomain_name,
+ char **pname,
+ enum lsa_SidType *ptype)
+{
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct dcerpc_binding_handle *h = NULL;
+ struct policy_handle dom_pol = { .handle_type = 0, };
+ const char *domain_name = "";
+ const char *name = "";
+ enum lsa_SidType type = SID_NAME_USE_NONE;
+ struct lsa_Strings names = { .count = 0, };
+ struct samr_Ids types = { .count = 0 };
+ struct dom_sid domain_sid;
+ uint32_t rid;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_NONE_MAPPED;
+ NTSTATUS result;
+ bool retry = false;
+ bool ok;
+
+ DEBUG(3,("sam_sid_to_name\n"));
+
+ if (sid_check_is_unix_users(sid)) {
+ domain_name = unix_users_domain_name();
+ type = SID_NAME_DOMAIN;
+ goto done;
+ }
+ if (sid_check_is_in_unix_users(sid)) {
+ struct passwd *pwd = NULL;
+
+ ok = sid_peek_rid(sid, &rid);
+ if (!ok) {
+ goto fail;
+ }
+ pwd = getpwuid(rid);
+ if (pwd == NULL) {
+ goto fail;
+ }
+
+ domain_name = unix_users_domain_name();
+ name = talloc_strdup(tmp_ctx, pwd->pw_name);
+ if (name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ type = SID_NAME_USER;
+ goto done;
+ }
+
+ if (sid_check_is_unix_groups(sid)) {
+ domain_name = unix_groups_domain_name();
+ type = SID_NAME_DOMAIN;
+ goto done;
+ }
+ if (sid_check_is_in_unix_groups(sid)) {
+ struct group *grp = NULL;
+
+ ok = sid_peek_rid(sid, &rid);
+ if (!ok) {
+ goto fail;
+ }
+ grp = getgrgid(rid);
+ if (grp == NULL) {
+ goto fail;
+ }
+
+ domain_name = unix_groups_domain_name();
+ name = talloc_strdup(tmp_ctx, grp->gr_name);
+ if (name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ type = SID_NAME_DOM_GRP;
+ goto done;
+ }
+
+ ok = lookup_wellknown_sid(tmp_ctx, sid, &domain_name, &name);
+ if (ok) {
+ type = SID_NAME_WKN_GRP;
+ goto done;
+ }
+
+ if (dom_sid_equal(sid, &domain->sid)) {
+ domain_name = domain->name;
+ type = SID_NAME_DOMAIN;
+ goto done;
+ }
+
+ sid_copy(&domain_sid, sid);
+ ok = sid_split_rid(&domain_sid, &rid);
+ if (!ok) {
+ goto fail;
+ }
+
+ if (!dom_sid_equal(&domain_sid, &domain->sid)) {
+ goto fail;
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(
+ domain, &samr_pipe, &dom_pol, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto fail;
+ }
+ h = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_LookupRids(
+ h, tmp_ctx, &dom_pol, 1, &rid, &names, &types, &result);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_samr_LookupRids failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ DBG_DEBUG("dcerpc_samr_LookupRids resulted in %s\n",
+ nt_errstr(result));
+ status = result;
+ goto fail;
+ }
+
+ domain_name = domain->name;
+ name = names.names[0].string;
+ type = types.ids[0];
+
+ if (name != NULL) {
+ char *normalized = NULL;
+ NTSTATUS nstatus = normalize_name_map(
+ tmp_ctx, domain_name, name, &normalized);
+ if (NT_STATUS_IS_OK(nstatus) ||
+ NT_STATUS_EQUAL(nstatus, NT_STATUS_FILE_RENAMED)) {
+ name = normalized;
+ }
+ }
+
+done:
+ if (ptype) {
+ *ptype = type;
+ }
+
+ if (pname) {
+ *pname = talloc_strdup(mem_ctx, name);
+ if (*pname == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ if (pdomain_name) {
+ *pdomain_name = talloc_strdup(mem_ctx, domain_name);
+ if (*pdomain_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ status = NT_STATUS_OK;
+fail:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS sam_rids_to_names(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *domain_sid,
+ uint32_t *rids,
+ size_t num_rids,
+ char **pdomain_name,
+ char ***pnames,
+ enum lsa_SidType **ptypes)
+{
+ struct rpc_pipe_client *samr_pipe = NULL;
+ struct dcerpc_binding_handle *h = NULL;
+ struct policy_handle dom_pol = { .handle_type = 0, };
+ enum lsa_SidType *types = NULL;
+ char **names = NULL;
+ const char *domain_name = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ NTSTATUS result;
+ bool retry = false;
+ uint32_t i;
+
+ DEBUG(3,("sam_rids_to_names for %s\n", domain->name));
+
+ types = talloc_array(tmp_ctx, enum lsa_SidType, num_rids);
+ if (types == NULL) {
+ goto fail;
+ }
+
+ names = talloc_array(tmp_ctx, char *, num_rids);
+ if (names == NULL) {
+ goto fail;
+ }
+
+ if (sid_check_is_unix_users(domain_sid)) {
+ domain_name = unix_users_domain_name();
+ domain_sid = &global_sid_Unix_Users;
+ }
+ if (sid_check_is_unix_groups(domain_sid)) {
+ domain_name = unix_groups_domain_name();
+ domain_sid = &global_sid_Unix_Groups;
+ }
+
+ /* Here we're only interested in the domain name being set */
+ sid_check_is_wellknown_domain(domain_sid, &domain_name);
+
+ if (domain_name != NULL) {
+ uint32_t num_mapped = 0;
+
+ /*
+ * Do unix users/groups and wkn in a loop. There is no
+ * getpwuids() call & friends anyway
+ */
+
+ for (i=0; i<num_rids; i++) {
+ struct dom_sid sid;
+ char *name = NULL;
+
+ sid_compose(&sid, domain_sid, rids[i]);
+
+ types[i] = SID_NAME_UNKNOWN;
+ names[i] = NULL;
+
+ status = sam_sid_to_name(
+ domain,
+ tmp_ctx,
+ &sid,
+ NULL,
+ &name,
+ &types[i]);
+ if (NT_STATUS_IS_OK(status)) {
+ names[i] = talloc_move(names, &name);
+ num_mapped += 1;
+ }
+ }
+
+ status = NT_STATUS_NONE_MAPPED;
+ if (num_mapped > 0) {
+ status = (num_mapped == num_rids) ?
+ NT_STATUS_OK : STATUS_SOME_UNMAPPED;
+ }
+ goto done;
+ }
+
+ domain_name = domain->name;
+
+again:
+ status = open_cached_internal_pipe_conn(
+ domain, &samr_pipe, &dom_pol, NULL, NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+ h = samr_pipe->binding_handle;
+
+ /*
+ * Magic number 1000 comes from samr.idl
+ */
+
+ for (i = 0; i < num_rids; i += 1000) {
+ uint32_t num_lookup_rids = MIN(num_rids - i, 1000);
+ struct lsa_Strings lsa_names = {
+ .count = 0,
+ };
+ struct samr_Ids samr_types = {
+ .count = 0,
+ };
+ uint32_t j;
+
+ status = dcerpc_samr_LookupRids(h,
+ tmp_ctx,
+ &dom_pol,
+ num_lookup_rids,
+ &rids[i],
+ &lsa_names,
+ &samr_types,
+ &result);
+
+ if (!retry &&
+ reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_DEBUG("dcerpc_samr_LookupRids failed: %s\n",
+ nt_errstr(status));
+ goto fail;
+ }
+ if (!NT_STATUS_IS_OK(result) &&
+ !NT_STATUS_EQUAL(result, STATUS_SOME_UNMAPPED)) {
+ DBG_DEBUG("dcerpc_samr_LookupRids resulted in %s\n",
+ nt_errstr(result));
+ status = result;
+ goto fail;
+ }
+
+ for (j = 0; j < num_lookup_rids; j++) {
+ uint32_t dst = i + j;
+
+ types[dst] = samr_types.ids[j];
+ names[dst] = talloc_move(
+ names,
+ discard_const_p(char *,
+ &lsa_names.names[j].string));
+ if (names[dst] != NULL) {
+ char *normalized = NULL;
+ NTSTATUS nstatus =
+ normalize_name_map(names,
+ domain_name,
+ names[dst],
+ &normalized);
+ if (NT_STATUS_IS_OK(nstatus) ||
+ NT_STATUS_EQUAL(nstatus,
+ NT_STATUS_FILE_RENAMED)) {
+ names[dst] = normalized;
+ }
+ }
+ }
+
+ TALLOC_FREE(samr_types.ids);
+ TALLOC_FREE(lsa_names.names);
+ }
+
+done:
+ if (pdomain_name) {
+ *pdomain_name = talloc_strdup(mem_ctx, domain_name);
+ if (*pdomain_name == NULL) {
+ status = NT_STATUS_NO_MEMORY;
+ goto fail;
+ }
+ }
+
+ if (ptypes) {
+ *ptypes = talloc_move(mem_ctx, &types);
+ }
+
+ if (pnames) {
+ *pnames = talloc_move(mem_ctx, &names);
+ }
+
+fail:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS sam_lockout_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo12 *lockout_policy)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol = { 0 };
+ union samr_DomainInfo *info = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = NULL;
+ bool retry = false;
+
+ DEBUG(3,("sam_lockout_policy\n"));
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_QueryDomainInfo(b,
+ mem_ctx,
+ &dom_pol,
+ DomainLockoutInformation,
+ &info,
+ &result);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto error;
+ }
+
+ *lockout_policy = info->info12;
+
+error:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS sam_password_policy(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ struct samr_DomInfo1 *passwd_policy)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol = { 0 };
+ union samr_DomainInfo *info = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status, result;
+ struct dcerpc_binding_handle *b = NULL;
+ bool retry = false;
+
+ DEBUG(3,("sam_password_policy\n"));
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+
+ b = samr_pipe->binding_handle;
+
+ status = dcerpc_samr_QueryDomainInfo(b,
+ mem_ctx,
+ &dom_pol,
+ DomainPasswordInformation,
+ &info,
+ &result);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto error;
+ }
+ if (!NT_STATUS_IS_OK(result)) {
+ status = result;
+ goto error;
+ }
+
+ *passwd_policy = info->info1;
+
+error:
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* Lookup groups a user is a member of. */
+static NTSTATUS sam_lookup_usergroups(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *pnum_groups,
+ struct dom_sid **puser_grpsids)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol;
+ struct dom_sid *user_grpsids = NULL;
+ uint32_t num_groups = 0;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DEBUG(3,("sam_lookup_usergroups\n"));
+
+ ZERO_STRUCT(dom_pol);
+
+ if (pnum_groups) {
+ *pnum_groups = 0;
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_lookup_usergroups(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ &domain->sid,
+ user_sid,
+ &num_groups,
+ &user_grpsids);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pnum_groups) {
+ *pnum_groups = num_groups;
+ }
+
+ if (puser_grpsids) {
+ *puser_grpsids = talloc_move(mem_ctx, &user_grpsids);
+ }
+
+done:
+
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+static NTSTATUS sam_lookup_useraliases(struct winbindd_domain *domain,
+ TALLOC_CTX *mem_ctx,
+ uint32_t num_sids,
+ const struct dom_sid *sids,
+ uint32_t *pnum_aliases,
+ uint32_t **palias_rids)
+{
+ struct rpc_pipe_client *samr_pipe;
+ struct policy_handle dom_pol = { 0 };
+ uint32_t num_aliases = 0;
+ uint32_t *alias_rids = NULL;
+ TALLOC_CTX *tmp_ctx = talloc_stackframe();
+ NTSTATUS status;
+ bool retry = false;
+
+ DEBUG(3,("sam_lookup_useraliases\n"));
+
+ if (pnum_aliases) {
+ *pnum_aliases = 0;
+ }
+
+again:
+ status = open_cached_internal_pipe_conn(domain,
+ &samr_pipe,
+ &dom_pol,
+ NULL,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ status = rpc_lookup_useraliases(tmp_ctx,
+ samr_pipe,
+ &dom_pol,
+ num_sids,
+ sids,
+ &num_aliases,
+ &alias_rids);
+
+ if (!retry && reset_connection_on_error(domain, samr_pipe, status)) {
+ retry = true;
+ goto again;
+ }
+
+ if (!NT_STATUS_IS_OK(status)) {
+ goto done;
+ }
+
+ if (pnum_aliases) {
+ *pnum_aliases = num_aliases;
+ }
+
+ if (palias_rids) {
+ *palias_rids = talloc_move(mem_ctx, &alias_rids);
+ }
+
+done:
+
+ TALLOC_FREE(tmp_ctx);
+ return status;
+}
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods builtin_passdb_methods = {
+ .consistent = false,
+
+ .query_user_list = builtin_query_user_list,
+ .enum_dom_groups = builtin_enum_dom_groups,
+ .enum_local_groups = sam_enum_local_groups,
+ .name_to_sid = sam_name_to_sid,
+ .sid_to_name = sam_sid_to_name,
+ .rids_to_names = sam_rids_to_names,
+ .lookup_usergroups = sam_lookup_usergroups,
+ .lookup_useraliases = sam_lookup_useraliases,
+ .lookup_groupmem = sam_lookup_groupmem,
+ .lookup_aliasmem = sam_lookup_aliasmem,
+ .lockout_policy = sam_lockout_policy,
+ .password_policy = sam_password_policy,
+ .trusted_domains = builtin_trusted_domains
+};
+
+/* the rpc backend methods are exposed via this structure */
+struct winbindd_methods sam_passdb_methods = {
+ .consistent = false,
+
+ .query_user_list = sam_query_user_list,
+ .enum_dom_groups = sam_enum_dom_groups,
+ .enum_local_groups = sam_enum_local_groups,
+ .name_to_sid = sam_name_to_sid,
+ .sid_to_name = sam_sid_to_name,
+ .rids_to_names = sam_rids_to_names,
+ .lookup_usergroups = sam_lookup_usergroups,
+ .lookup_useraliases = sam_lookup_useraliases,
+ .lookup_groupmem = sam_lookup_groupmem,
+ .lookup_aliasmem = sam_lookup_aliasmem,
+ .lockout_policy = sam_lockout_policy,
+ .password_policy = sam_password_policy,
+ .trusted_domains = sam_trusted_domains
+};
diff --git a/source3/winbindd/winbindd_setgrent.c b/source3/winbindd/winbindd_setgrent.c
new file mode 100644
index 0000000..f4f2498
--- /dev/null
+++ b/source3/winbindd/winbindd_setgrent.c
@@ -0,0 +1,67 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_SETGRENT
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+struct winbindd_setgrent_state {
+ uint8_t dummy;
+};
+
+struct tevent_req *winbindd_setgrent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct winbindd_setgrent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_setgrent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ TALLOC_FREE(cli->grent_state);
+
+ D_NOTICE("[%s (%u)] Winbind external command SETGRENT start.\n"
+ "winbind enum groups = %d\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ lp_winbind_enum_groups());
+
+ if (!lp_winbind_enum_groups()) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ cli->grent_state = talloc_zero(cli, struct getgrent_state);
+ if (tevent_req_nomem(cli->grent_state, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+NTSTATUS winbindd_setgrent_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ D_NOTICE("Winbind external command SETGRENT end.\n");
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_setpwent.c b/source3/winbindd/winbindd_setpwent.c
new file mode 100644
index 0000000..3c98e5a
--- /dev/null
+++ b/source3/winbindd/winbindd_setpwent.c
@@ -0,0 +1,67 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_SETPWENT
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+struct winbindd_setpwent_state {
+ uint8_t dummy;
+};
+
+struct tevent_req *winbindd_setpwent_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req;
+ struct winbindd_setpwent_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_setpwent_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ TALLOC_FREE(cli->pwent_state);
+
+ D_NOTICE("[%s (%u)] Winbind external command SETPWENT start.\n"
+ "winbind enum users = %d\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ lp_winbind_enum_users());
+
+ if (!lp_winbind_enum_users()) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+
+ cli->pwent_state = talloc_zero(cli, struct getpwent_state);
+ if (tevent_req_nomem(cli->pwent_state, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+}
+
+NTSTATUS winbindd_setpwent_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ D_NOTICE("Winbind external command SETPWENT end.\n");
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_show_sequence.c b/source3/winbindd/winbindd_show_sequence.c
new file mode 100644
index 0000000..e12f476
--- /dev/null
+++ b/source3/winbindd/winbindd_show_sequence.c
@@ -0,0 +1,167 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_SHOW_SEQUENCE
+ Copyright (C) Volker Lendecke 2009
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+
+struct winbindd_show_sequence_state {
+ bool one_domain;
+ /* One domain */
+ uint32_t seqnum;
+
+ /* All domains */
+ int num_domains;
+ NTSTATUS *statuses;
+ struct winbindd_domain **domains;
+ uint32_t *seqnums;
+};
+
+static void winbindd_show_sequence_done_one(struct tevent_req *subreq);
+static void winbindd_show_sequence_done_all(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_show_sequence_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_show_sequence_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_show_sequence_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->one_domain = false;
+ state->domains = NULL;
+ state->statuses = NULL;
+ state->seqnums = NULL;
+
+ /* Ensure null termination */
+ request->domain_name[sizeof(request->domain_name)-1]='\0';
+
+ DEBUG(3, ("show_sequence %s\n", request->domain_name));
+
+ if (request->domain_name[0] != '\0') {
+ struct winbindd_domain *domain;
+
+ state->one_domain = true;
+
+ domain = find_domain_from_name_noinit(
+ request->domain_name);
+ if (domain == NULL) {
+ tevent_req_nterror(req, NT_STATUS_NO_SUCH_DOMAIN);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = wb_seqnum_send(state, ev, domain);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(
+ subreq, winbindd_show_sequence_done_one, req);
+ return req;
+ }
+
+ subreq = wb_seqnums_send(state, ev);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_show_sequence_done_all, req);
+ return req;
+}
+
+static void winbindd_show_sequence_done_one(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_show_sequence_state *state = tevent_req_data(
+ req, struct winbindd_show_sequence_state);
+ NTSTATUS status;
+
+ status = wb_seqnum_recv(subreq, &state->seqnum);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+static void winbindd_show_sequence_done_all(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_show_sequence_state *state = tevent_req_data(
+ req, struct winbindd_show_sequence_state);
+ NTSTATUS status;
+
+ status = wb_seqnums_recv(subreq, state, &state->num_domains,
+ &state->domains, &state->statuses,
+ &state->seqnums);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_show_sequence_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_show_sequence_state *state = tevent_req_data(
+ req, struct winbindd_show_sequence_state);
+ NTSTATUS status;
+ char *extra_data;
+ int i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ if (state->one_domain) {
+ response->data.sequence_number = state->seqnum;
+ return NT_STATUS_OK;
+ }
+
+ extra_data = talloc_strdup(response, "");
+ if (extra_data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<state->num_domains; i++) {
+ if (!NT_STATUS_IS_OK(state->statuses[i])
+ || (state->seqnums[i] == DOM_SEQUENCE_NONE)) {
+ extra_data = talloc_asprintf_append_buffer(
+ extra_data, "%s : DISCONNECTED\n",
+ state->domains[i]->name);
+ } else {
+ extra_data = talloc_asprintf_append_buffer(
+ extra_data, "%s : %d\n",
+ state->domains[i]->name,
+ (int)state->seqnums[i]);
+ }
+ if (extra_data == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ response->extra_data.data = extra_data;
+ response->length += talloc_get_size(extra_data);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_sids_to_xids.c b/source3/winbindd/winbindd_sids_to_xids.c
new file mode 100644
index 0000000..187b299
--- /dev/null
+++ b/source3/winbindd/winbindd_sids_to_xids.c
@@ -0,0 +1,164 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_SIDS_TO_XIDS
+ Copyright (C) Volker Lendecke 2011
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+
+
+struct winbindd_sids_to_xids_state {
+ struct tevent_context *ev;
+ struct dom_sid *sids;
+ uint32_t num_sids;
+ struct unixid *xids;
+};
+
+static void winbindd_sids_to_xids_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_sids_to_xids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_sids_to_xids_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_sids_to_xids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ D_NOTICE("[%s (%u)] Winbind external command SIDS_TO_XIDS start.\n",
+ cli->client_name,
+ (unsigned int)cli->pid);
+
+ if (request->extra_len == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (request->extra_data.data[request->extra_len-1] != '\0') {
+ D_DEBUG("Got invalid sids list\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (!parse_sidlist(state, request->extra_data.data,
+ &state->sids, &state->num_sids)) {
+ D_DEBUG("parse_sidlist failed\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ D_DEBUG("Resolving %"PRIu32" SID(s).\n", state->num_sids);
+
+ subreq = wb_sids2xids_send(state, ev, state->sids, state->num_sids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+
+ tevent_req_set_callback(subreq, winbindd_sids_to_xids_done, req);
+ return req;
+}
+
+static void winbindd_sids_to_xids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_sids_to_xids_state *state = tevent_req_data(
+ req, struct winbindd_sids_to_xids_state);
+ NTSTATUS status;
+
+ state->xids = talloc_zero_array(state, struct unixid, state->num_sids);
+ if (tevent_req_nomem(state->xids, req)) {
+ return;
+ }
+
+ status = wb_sids2xids_recv(subreq, state->xids, state->num_sids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_sids_to_xids_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_sids_to_xids_state *state = tevent_req_data(
+ req, struct winbindd_sids_to_xids_state);
+ NTSTATUS status;
+ char *result = NULL;
+ uint32_t i;
+
+ D_NOTICE("Winbind external command SIDS_TO_XIDS end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Could not convert sids: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ result = talloc_strdup(response, "");
+
+ for (i=0; i<state->num_sids; i++) {
+ char type = '\0';
+ bool found = true;
+ struct unixid xid;
+
+ xid = state->xids[i];
+
+ switch (xid.type) {
+ case ID_TYPE_UID:
+ type = 'U';
+ break;
+ case ID_TYPE_GID:
+ type = 'G';
+ break;
+ case ID_TYPE_BOTH:
+ type = 'B';
+ break;
+ default:
+ found = false;
+ break;
+ }
+
+ if (xid.id == UINT32_MAX) {
+ found = false;
+ }
+
+ if (found) {
+ talloc_asprintf_addbuf(
+ &result,
+ "%c%lu\n",
+ type,
+ (unsigned long)xid.id);
+ } else {
+ talloc_asprintf_addbuf(&result, "\n");
+ }
+ }
+
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ response->extra_data.data = result;
+ response->length += talloc_get_size(result);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_traceid.c b/source3/winbindd/winbindd_traceid.c
new file mode 100644
index 0000000..acf16be
--- /dev/null
+++ b/source3/winbindd/winbindd_traceid.c
@@ -0,0 +1,147 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2021 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lib/util/debug.h"
+#include "winbindd_traceid.h"
+#include "tevent.h"
+
+static void debug_traceid_trace_fde(struct tevent_fd *fde,
+ enum tevent_event_trace_point point,
+ void *private_data)
+{
+ switch (point) {
+ case TEVENT_EVENT_TRACE_ATTACH:
+ /* Assign the current traceid id when the event is created. */
+ tevent_fd_set_tag(fde, debug_traceid_get());
+ break;
+ case TEVENT_EVENT_TRACE_BEFORE_HANDLER:
+ /* Set the traceid id when a handler is being called. */
+ debug_traceid_set(tevent_fd_get_tag(fde));
+ break;
+ default:
+ /* Do nothing. */
+ break;
+ }
+}
+
+static void debug_traceid_trace_signal(struct tevent_signal *se,
+ enum tevent_event_trace_point point,
+ void *private_data)
+{
+ switch (point) {
+ case TEVENT_EVENT_TRACE_ATTACH:
+ /* Assign the current traceid id when the event is created. */
+ tevent_signal_set_tag(se, debug_traceid_get());
+ break;
+ case TEVENT_EVENT_TRACE_BEFORE_HANDLER:
+ /* Set the traceid id when a handler is being called. */
+ debug_traceid_set(tevent_signal_get_tag(se));
+ break;
+ default:
+ /* Do nothing. */
+ break;
+ }
+}
+
+static void debug_traceid_trace_timer(struct tevent_timer *timer,
+ enum tevent_event_trace_point point,
+ void *private_data)
+{
+ switch (point) {
+ case TEVENT_EVENT_TRACE_ATTACH:
+ /* Assign the current traceid id when the event is created. */
+ tevent_timer_set_tag(timer, debug_traceid_get());
+ break;
+ case TEVENT_EVENT_TRACE_BEFORE_HANDLER:
+ /* Set the traceid id when a handler is being called. */
+ debug_traceid_set(tevent_timer_get_tag(timer));
+ break;
+ default:
+ /* Do nothing. */
+ break;
+ }
+}
+
+static void debug_traceid_trace_immediate(struct tevent_immediate *im,
+ enum tevent_event_trace_point point,
+ void *private_data)
+{
+ switch (point) {
+ case TEVENT_EVENT_TRACE_ATTACH:
+ /* Assign the current traceid id when the event is created. */
+ tevent_immediate_set_tag(im, debug_traceid_get());
+ break;
+ case TEVENT_EVENT_TRACE_BEFORE_HANDLER:
+ /* Set the traceid id when a handler is being called. */
+ debug_traceid_set(tevent_immediate_get_tag(im));
+ break;
+ default:
+ /* Do nothing. */
+ break;
+ }
+}
+
+static void debug_traceid_trace_queue(struct tevent_queue_entry *qe,
+ enum tevent_event_trace_point point,
+ void *private_data)
+{
+ switch (point) {
+ case TEVENT_EVENT_TRACE_ATTACH:
+ /* Assign the current traceid id when the event is created. */
+ tevent_queue_entry_set_tag(qe, debug_traceid_get());
+ break;
+ case TEVENT_EVENT_TRACE_BEFORE_HANDLER:
+ /* Set the traceid id when a handler is being called. */
+ debug_traceid_set(tevent_queue_entry_get_tag(qe));
+ break;
+ default:
+ /* Do nothing. */
+ break;
+ }
+}
+
+static void debug_traceid_trace_loop(enum tevent_trace_point point,
+ void *private_data)
+{
+ switch (point) {
+ case TEVENT_TRACE_AFTER_LOOP_ONCE:
+ /* Reset traceid id when we got back to the loop. An event handler
+ * that set traceid id was fired. This tracepoint represents a place
+ * after the event handler was finished, we need to restore traceid
+ * id to 1 (out of request). 0 means not initialized.
+ */
+ debug_traceid_set(1);
+ break;
+ default:
+ /* Do nothing. */
+ break;
+ }
+}
+
+void winbind_debug_traceid_setup(struct tevent_context *ev)
+{
+ tevent_set_trace_callback(ev, debug_traceid_trace_loop, NULL);
+ tevent_set_trace_fd_callback(ev, debug_traceid_trace_fde, NULL);
+ tevent_set_trace_signal_callback(ev, debug_traceid_trace_signal, NULL);
+ tevent_set_trace_timer_callback(ev, debug_traceid_trace_timer, NULL);
+ tevent_set_trace_immediate_callback(ev, debug_traceid_trace_immediate, NULL);
+ tevent_set_trace_queue_callback(ev, debug_traceid_trace_queue, NULL);
+ debug_traceid_set(1);
+}
diff --git a/source3/winbindd/winbindd_traceid.h b/source3/winbindd/winbindd_traceid.h
new file mode 100644
index 0000000..d06e3ef
--- /dev/null
+++ b/source3/winbindd/winbindd_traceid.h
@@ -0,0 +1,29 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2021 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _DEBUG_TRACE_ID_
+#define _DEBUG_TRACE_ID_
+
+#include <tevent.h>
+
+/* Setup traceid tracking on tevent context. */
+void winbind_debug_traceid_setup(struct tevent_context *ev);
+
+#endif /* _DEBUG_TRACE_ID_ */
diff --git a/source3/winbindd/winbindd_util.c b/source3/winbindd/winbindd_util.c
new file mode 100644
index 0000000..7527a78
--- /dev/null
+++ b/source3/winbindd/winbindd_util.c
@@ -0,0 +1,2243 @@
+/*
+ Unix SMB/CIFS implementation.
+
+ Winbind daemon for ntdom nss module
+
+ Copyright (C) Tim Potter 2000-2001
+ Copyright (C) 2001 by Martin Pool <mbp@samba.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "lib/util_unixsids.h"
+#include "secrets.h"
+#include "../libcli/security/security.h"
+#include "../libcli/auth/pam_errors.h"
+#include "passdb/machine_sid.h"
+#include "passdb.h"
+#include "source4/lib/messaging/messaging.h"
+#include "librpc/gen_ndr/ndr_lsa.h"
+#include "librpc/gen_ndr/ndr_drsblobs.h"
+#include "auth/credentials/credentials.h"
+#include "libsmb/samlogon_cache.h"
+#include "lib/util/smb_strtox.h"
+#include "lib/util/string_wrappers.h"
+#include "lib/global_contexts.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+
+#undef DBGC_CLASS
+#define DBGC_CLASS DBGC_WINBIND
+
+/**
+ * @file winbindd_util.c
+ *
+ * Winbind daemon for NT domain authentication nss module.
+ **/
+
+/* The list of trusted domains. Note that the list can be deleted and
+ recreated using the init_domain_list() function so pointers to
+ individual winbindd_domain structures cannot be made. Keep a copy of
+ the domain name instead. */
+
+static struct winbindd_domain *_domain_list = NULL;
+
+struct winbindd_domain *domain_list(void)
+{
+ /* Initialise list */
+
+ if ((!_domain_list) && (!init_domain_list())) {
+ smb_panic("Init_domain_list failed");
+ }
+
+ return _domain_list;
+}
+
+/* Free all entries in the trusted domain list */
+
+static void free_domain_list(void)
+{
+ struct winbindd_domain *domain = _domain_list;
+
+ while(domain) {
+ struct winbindd_domain *next = domain->next;
+
+ DLIST_REMOVE(_domain_list, domain);
+ TALLOC_FREE(domain);
+ domain = next;
+ }
+}
+
+/**
+ * Iterator for winbindd's domain list.
+ * To be used (e.g.) in tevent based loops.
+ */
+struct winbindd_domain *wb_next_domain(struct winbindd_domain *domain)
+{
+ if (domain == NULL) {
+ domain = domain_list();
+ } else {
+ domain = domain->next;
+ }
+
+ if ((domain != NULL) &&
+ (lp_server_role() != ROLE_ACTIVE_DIRECTORY_DC) &&
+ sid_check_is_our_sam(&domain->sid))
+ {
+ domain = domain->next;
+ }
+
+ return domain;
+}
+
+static bool is_internal_domain(const struct dom_sid *sid)
+{
+ if (sid == NULL)
+ return False;
+
+ return (sid_check_is_our_sam(sid) || sid_check_is_builtin(sid));
+}
+
+/* Add a trusted domain to our list of domains.
+ If the domain already exists in the list,
+ return it and don't re-initialize. */
+
+static NTSTATUS add_trusted_domain(const char *domain_name,
+ const char *dns_name,
+ const struct dom_sid *sid,
+ uint32_t trust_type,
+ uint32_t trust_flags,
+ uint32_t trust_attribs,
+ enum netr_SchannelType secure_channel_type,
+ struct winbindd_domain *routing_domain,
+ struct winbindd_domain **_d)
+{
+ struct winbindd_domain *domain = NULL;
+ int role = lp_server_role();
+ struct dom_sid_buf buf;
+
+ if (is_null_sid(sid)) {
+ DBG_ERR("Got null SID for domain [%s]\n", domain_name);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (secure_channel_type == SEC_CHAN_NULL && !is_allowed_domain(domain_name)) {
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ /*
+ * We can't call domain_list() as this function is called from
+ * init_domain_list() and we'll get stuck in a loop.
+ */
+ for (domain = _domain_list; domain; domain = domain->next) {
+ if (strequal(domain_name, domain->name)) {
+ break;
+ }
+ }
+
+ if (domain != NULL) {
+ struct winbindd_domain *check_domain = NULL;
+
+ for (check_domain = _domain_list;
+ check_domain != NULL;
+ check_domain = check_domain->next)
+ {
+ if (check_domain == domain) {
+ continue;
+ }
+
+ if (dom_sid_equal(&check_domain->sid, sid)) {
+ break;
+ }
+ }
+
+ if (check_domain != NULL) {
+ DBG_ERR("SID [%s] already used by domain [%s], "
+ "expected [%s]\n",
+ dom_sid_str_buf(sid, &buf),
+ check_domain->name,
+ domain->name);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ if ((domain != NULL) && (dns_name != NULL)) {
+ struct winbindd_domain *check_domain = NULL;
+
+ for (check_domain = _domain_list;
+ check_domain != NULL;
+ check_domain = check_domain->next)
+ {
+ if (check_domain == domain) {
+ continue;
+ }
+
+ if (strequal(check_domain->alt_name, dns_name)) {
+ break;
+ }
+ }
+
+ if (check_domain != NULL) {
+ DBG_ERR("DNS name [%s] used by domain [%s], "
+ "expected [%s]\n",
+ dns_name, check_domain->name,
+ domain->name);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ if (domain != NULL) {
+ *_d = domain;
+ return NT_STATUS_OK;
+ }
+
+ /* Create new domain entry */
+ domain = talloc_zero(NULL, struct winbindd_domain);
+ if (domain == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ domain->children = talloc_zero_array(domain,
+ struct winbindd_child,
+ lp_winbind_max_domain_connections());
+ if (domain->children == NULL) {
+ TALLOC_FREE(domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ domain->queue = tevent_queue_create(domain, "winbind_domain");
+ if (domain->queue == NULL) {
+ TALLOC_FREE(domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ domain->binding_handle = wbint_binding_handle(domain, domain, NULL);
+ if (domain->binding_handle == NULL) {
+ TALLOC_FREE(domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ domain->name = talloc_strdup(domain, domain_name);
+ if (domain->name == NULL) {
+ TALLOC_FREE(domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (dns_name != NULL) {
+ domain->alt_name = talloc_strdup(domain, dns_name);
+ if (domain->alt_name == NULL) {
+ TALLOC_FREE(domain);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ domain->backend = NULL;
+ domain->internal = is_internal_domain(sid);
+ domain->secure_channel_type = secure_channel_type;
+ domain->sequence_number = DOM_SEQUENCE_NONE;
+ domain->last_seq_check = 0;
+ domain->initialized = false;
+ domain->online = is_internal_domain(sid);
+ domain->domain_flags = trust_flags;
+ domain->domain_type = trust_type;
+ domain->domain_trust_attribs = trust_attribs;
+ domain->secure_channel_type = secure_channel_type;
+ domain->routing_domain = routing_domain;
+ sid_copy(&domain->sid, sid);
+
+ /* Is this our primary domain ? */
+ if (role == ROLE_DOMAIN_MEMBER) {
+ domain->primary = strequal(domain_name, lp_workgroup());
+ } else {
+ domain->primary = strequal(domain_name, get_global_sam_name());
+ }
+
+ if (domain->primary) {
+ if (role == ROLE_ACTIVE_DIRECTORY_DC) {
+ domain->active_directory = true;
+ }
+ if (lp_security() == SEC_ADS) {
+ domain->active_directory = true;
+ }
+ } else if (!domain->internal) {
+ if (domain->domain_type == LSA_TRUST_TYPE_UPLEVEL) {
+ domain->active_directory = true;
+ }
+ }
+
+ domain->can_do_ncacn_ip_tcp = domain->active_directory;
+
+ /* Link to domain list */
+ DLIST_ADD_END(_domain_list, domain);
+
+ wcache_tdc_add_domain( domain );
+
+ setup_domain_child(domain);
+
+ DBG_NOTICE("Added domain [%s] [%s] [%s]\n",
+ domain->name, domain->alt_name,
+ dom_sid_str_buf(&domain->sid, &buf));
+
+ *_d = domain;
+ return NT_STATUS_OK;
+}
+
+bool set_routing_domain(struct winbindd_domain *domain,
+ struct winbindd_domain *routing_domain)
+{
+ if (domain->routing_domain == NULL) {
+ domain->routing_domain = routing_domain;
+ return true;
+ }
+ if (domain->routing_domain != routing_domain) {
+ return false;
+ }
+ return true;
+}
+
+bool add_trusted_domain_from_auth(uint16_t validation_level,
+ struct info3_text *info3,
+ struct info6_text *info6)
+{
+ struct winbindd_domain *domain = NULL;
+ struct dom_sid domain_sid;
+ const char *dns_domainname = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ /*
+ * We got a successful auth from a domain that might not yet be in our
+ * domain list. If we're a member we trust our DC who authenticated the
+ * user from that domain and add the domain to our list on-the-fly. If
+ * we're a DC we rely on configured trusts and don't add on-the-fly.
+ */
+
+ if (IS_DC) {
+ return true;
+ }
+
+ ok = dom_sid_parse(info3->dom_sid, &domain_sid);
+ if (!ok) {
+ DBG_NOTICE("dom_sid_parse [%s] failed\n", info3->dom_sid);
+ return false;
+ }
+
+ if (validation_level == 6) {
+ if (!strequal(info6->dns_domainname, "")) {
+ dns_domainname = info6->dns_domainname;
+ }
+ }
+
+ status = add_trusted_domain(info3->logon_dom,
+ dns_domainname,
+ &domain_sid,
+ 0,
+ NETR_TRUST_FLAG_OUTBOUND,
+ 0,
+ SEC_CHAN_NULL,
+ find_default_route_domain(),
+ &domain);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN))
+ {
+ DBG_DEBUG("Adding domain [%s] with sid [%s] failed\n",
+ info3->logon_dom, info3->dom_sid);
+ return false;
+ }
+
+ return true;
+}
+
+bool domain_is_forest_root(const struct winbindd_domain *domain)
+{
+ const uint32_t fr_flags =
+ (NETR_TRUST_FLAG_TREEROOT|NETR_TRUST_FLAG_IN_FOREST);
+
+ return ((domain->domain_flags & fr_flags) == fr_flags);
+}
+
+/********************************************************************
+ rescan our domains looking for new trusted domains
+********************************************************************/
+
+struct trustdom_state {
+ struct winbindd_domain *domain;
+ struct netr_DomainTrustList trusts;
+};
+
+static void trustdom_list_done(struct tevent_req *req);
+static void rescan_forest_root_trusts( void );
+static void rescan_forest_trusts( void );
+
+static void add_trusted_domains( struct winbindd_domain *domain )
+{
+ struct tevent_context *ev = global_event_context();
+ struct trustdom_state *state;
+ struct tevent_req *req;
+ const char *client_name = NULL;
+ pid_t client_pid;
+
+ state = talloc_zero(NULL, struct trustdom_state);
+ if (state == NULL) {
+ DEBUG(0, ("talloc failed\n"));
+ return;
+ }
+ state->domain = domain;
+
+ /* Called from timer, not from a real client */
+ client_name = getprogname();
+ client_pid = getpid();
+
+ req = dcerpc_wbint_ListTrustedDomains_send(state,
+ ev,
+ dom_child_handle(domain),
+ client_name,
+ client_pid,
+ &state->trusts);
+ if (req == NULL) {
+ DBG_ERR("dcerpc_wbint_ListTrustedDomains_send failed\n");
+ TALLOC_FREE(state);
+ return;
+ }
+ tevent_req_set_callback(req, trustdom_list_done, state);
+}
+
+static void trustdom_list_done(struct tevent_req *req)
+{
+ struct trustdom_state *state = tevent_req_callback_data(
+ req, struct trustdom_state);
+ bool within_forest = false;
+ NTSTATUS status, result;
+ uint32_t i;
+
+ /*
+ * Only when we enumerate our primary domain
+ * or our forest root domain, we should keep
+ * the NETR_TRUST_FLAG_IN_FOREST flag, in
+ * all other cases we need to clear it as the domain
+ * is not part of our forest.
+ */
+ if (state->domain->primary) {
+ within_forest = true;
+ } else if (domain_is_forest_root(state->domain)) {
+ within_forest = true;
+ }
+
+ status = dcerpc_wbint_ListTrustedDomains_recv(req, state, &result);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ DBG_WARNING("Could not receive trusts for domain %s: %s-%s\n",
+ state->domain->name, nt_errstr(status),
+ nt_errstr(result));
+ TALLOC_FREE(state);
+ return;
+ }
+
+ for (i=0; i<state->trusts.count; i++) {
+ struct netr_DomainTrust *trust = &state->trusts.array[i];
+ struct winbindd_domain *domain = NULL;
+
+ if (!within_forest) {
+ trust->trust_flags &= ~NETR_TRUST_FLAG_IN_FOREST;
+ }
+
+ if (!state->domain->primary) {
+ trust->trust_flags &= ~NETR_TRUST_FLAG_PRIMARY;
+ }
+
+ /*
+ * We always call add_trusted_domain() cause on an existing
+ * domain structure, it will update the SID if necessary.
+ * This is important because we need the SID for sibling
+ * domains.
+ */
+ status = add_trusted_domain(trust->netbios_name,
+ trust->dns_name,
+ trust->sid,
+ trust->trust_type,
+ trust->trust_flags,
+ trust->trust_attributes,
+ SEC_CHAN_NULL,
+ find_default_route_domain(),
+ &domain);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN))
+ {
+ DBG_NOTICE("add_trusted_domain returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+ }
+
+ /*
+ Cases to consider when scanning trusts:
+ (a) we are calling from a child domain (primary && !forest_root)
+ (b) we are calling from the root of the forest (primary && forest_root)
+ (c) we are calling from a trusted forest domain (!primary
+ && !forest_root)
+ */
+
+ if (state->domain->primary) {
+ /* If this is our primary domain and we are not in the
+ forest root, we have to scan the root trusts first */
+
+ if (!domain_is_forest_root(state->domain))
+ rescan_forest_root_trusts();
+ else
+ rescan_forest_trusts();
+
+ } else if (domain_is_forest_root(state->domain)) {
+ /* Once we have done root forest trust search, we can
+ go on to search the trusted forests */
+
+ rescan_forest_trusts();
+ }
+
+ TALLOC_FREE(state);
+
+ return;
+}
+
+/********************************************************************
+ Scan the trusts of our forest root
+********************************************************************/
+
+static void rescan_forest_root_trusts( void )
+{
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_trusts = 0;
+ size_t i;
+ NTSTATUS status;
+
+ /* The only transitive trusts supported by Windows 2003 AD are
+ (a) Parent-Child, (b) Tree-Root, and (c) Forest. The
+ first two are handled in forest and listed by
+ DsEnumerateDomainTrusts(). Forest trusts are not so we
+ have to do that ourselves. */
+
+ if ( !wcache_tdc_fetch_list( &dom_list, &num_trusts ) )
+ return;
+
+ for ( i=0; i<num_trusts; i++ ) {
+ struct winbindd_domain *d = NULL;
+
+ /* Find the forest root. Don't necessarily trust
+ the domain_list() as our primary domain may not
+ have been initialized. */
+
+ if ( !(dom_list[i].trust_flags & NETR_TRUST_FLAG_TREEROOT) ) {
+ continue;
+ }
+
+ /* Here's the forest root */
+
+ d = find_domain_from_name_noinit( dom_list[i].domain_name );
+ if (d == NULL) {
+ status = add_trusted_domain(dom_list[i].domain_name,
+ dom_list[i].dns_name,
+ &dom_list[i].sid,
+ dom_list[i].trust_type,
+ dom_list[i].trust_flags,
+ dom_list[i].trust_attribs,
+ SEC_CHAN_NULL,
+ find_default_route_domain(),
+ &d);
+
+ if (!NT_STATUS_IS_OK(status) &&
+ NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN))
+ {
+ DBG_ERR("add_trusted_domain returned %s\n",
+ nt_errstr(status));
+ return;
+ }
+ }
+ if (d == NULL) {
+ continue;
+ }
+
+ DEBUG(10,("rescan_forest_root_trusts: Following trust path "
+ "for domain tree root %s (%s)\n",
+ d->name, d->alt_name ));
+
+ d->domain_flags = dom_list[i].trust_flags;
+ d->domain_type = dom_list[i].trust_type;
+ d->domain_trust_attribs = dom_list[i].trust_attribs;
+
+ add_trusted_domains( d );
+
+ break;
+ }
+
+ TALLOC_FREE( dom_list );
+
+ return;
+}
+
+/********************************************************************
+ scan the transitive forest trusts (not our own)
+********************************************************************/
+
+
+static void rescan_forest_trusts( void )
+{
+ struct winbindd_domain *d = NULL;
+ struct winbindd_tdc_domain *dom_list = NULL;
+ size_t num_trusts = 0;
+ size_t i;
+ NTSTATUS status;
+
+ /* The only transitive trusts supported by Windows 2003 AD are
+ (a) Parent-Child, (b) Tree-Root, and (c) Forest. The
+ first two are handled in forest and listed by
+ DsEnumerateDomainTrusts(). Forest trusts are not so we
+ have to do that ourselves. */
+
+ if ( !wcache_tdc_fetch_list( &dom_list, &num_trusts ) )
+ return;
+
+ for ( i=0; i<num_trusts; i++ ) {
+ uint32_t flags = dom_list[i].trust_flags;
+ uint32_t type = dom_list[i].trust_type;
+ uint32_t attribs = dom_list[i].trust_attribs;
+
+ d = find_domain_from_name_noinit( dom_list[i].domain_name );
+
+ /* ignore our primary and internal domains */
+
+ if ( d && (d->internal || d->primary ) )
+ continue;
+
+ if ( (flags & NETR_TRUST_FLAG_INBOUND) &&
+ (type == LSA_TRUST_TYPE_UPLEVEL) &&
+ (attribs & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) )
+ {
+ /* add the trusted domain if we don't know
+ about it */
+
+ if (d == NULL) {
+ status = add_trusted_domain(
+ dom_list[i].domain_name,
+ dom_list[i].dns_name,
+ &dom_list[i].sid,
+ type,
+ flags,
+ attribs,
+ SEC_CHAN_NULL,
+ find_default_route_domain(),
+ &d);
+ if (!NT_STATUS_IS_OK(status) &&
+ NT_STATUS_EQUAL(status,
+ NT_STATUS_NO_SUCH_DOMAIN))
+ {
+ DBG_ERR("add_trusted_domain: %s\n",
+ nt_errstr(status));
+ return;
+ }
+ }
+
+ if (d == NULL) {
+ continue;
+ }
+
+ DEBUG(10,("Following trust path for domain %s (%s)\n",
+ d->name, d->alt_name ));
+ add_trusted_domains( d );
+ }
+ }
+
+ TALLOC_FREE( dom_list );
+
+ return;
+}
+
+/*********************************************************************
+ The process of updating the trusted domain list is a three step
+ async process:
+ (a) ask our domain
+ (b) ask the root domain in our forest
+ (c) ask a DC in any Win2003 trusted forests
+*********************************************************************/
+
+void rescan_trusted_domains(struct tevent_context *ev, struct tevent_timer *te,
+ struct timeval now, void *private_data)
+{
+ TALLOC_FREE(te);
+
+ /* I used to clear the cache here and start over but that
+ caused problems in child processes that needed the
+ trust dom list early on. Removing it means we
+ could have some trusted domains listed that have been
+ removed from our primary domain's DC until a full
+ restart. This should be ok since I think this is what
+ Windows does as well. */
+
+ /* this will only add new domains we didn't already know about
+ in the domain_list()*/
+
+ add_trusted_domains( find_our_domain() );
+
+ te = tevent_add_timer(
+ ev, NULL, timeval_current_ofs(WINBINDD_RESCAN_FREQ, 0),
+ rescan_trusted_domains, NULL);
+ /*
+ * If te == NULL, there's not much we can do here. Don't fail, the
+ * only thing we miss is new trusted domains.
+ */
+
+ return;
+}
+
+static void wbd_ping_dc_done(struct tevent_req *subreq);
+
+void winbindd_ping_offline_domains(struct tevent_context *ev,
+ struct tevent_timer *te,
+ struct timeval now,
+ void *private_data)
+{
+ struct winbindd_domain *domain = NULL;
+
+ TALLOC_FREE(te);
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ DBG_DEBUG("Domain %s is %s\n",
+ domain->name,
+ domain->online ? "online" : "offline");
+
+ if (get_global_winbindd_state_offline()) {
+ DBG_DEBUG("We are globally offline, do nothing.\n");
+ break;
+ }
+
+ if (domain->online ||
+ domain->check_online_event != NULL ||
+ domain->secure_channel_type == SEC_CHAN_NULL) {
+ continue;
+ }
+
+ winbindd_flush_negative_conn_cache(domain);
+
+ domain->check_online_event =
+ dcerpc_wbint_PingDc_send(domain,
+ ev,
+ dom_child_handle(domain),
+ &domain->ping_dcname);
+ if (domain->check_online_event == NULL) {
+ DBG_WARNING("Failed to schedule ping, no-memory\n");
+ continue;
+ }
+
+ tevent_req_set_callback(domain->check_online_event,
+ wbd_ping_dc_done, domain);
+ }
+
+ te = tevent_add_timer(ev,
+ NULL,
+ timeval_current_ofs(lp_winbind_reconnect_delay(),
+ 0),
+ winbindd_ping_offline_domains,
+ NULL);
+ if (te == NULL) {
+ DBG_ERR("Failed to schedule winbindd_ping_offline_domains()\n");
+ }
+
+ return;
+}
+
+static void wbd_ping_dc_done(struct tevent_req *subreq)
+{
+ struct winbindd_domain *domain =
+ tevent_req_callback_data(subreq,
+ struct winbindd_domain);
+ NTSTATUS status, result;
+
+ SMB_ASSERT(subreq == domain->check_online_event);
+ domain->check_online_event = NULL;
+
+ status = dcerpc_wbint_PingDc_recv(subreq, domain, &result);
+ TALLOC_FREE(subreq);
+ if (any_nt_status_not_ok(status, result, &status)) {
+ DBG_WARNING("dcerpc_wbint_PingDc_recv failed for domain: "
+ "%s - %s\n",
+ domain->name,
+ nt_errstr(status));
+ return;
+ }
+
+ DBG_DEBUG("dcerpc_wbint_PingDc_recv() succeeded, "
+ "domain: %s, dc-name: %s\n",
+ domain->name,
+ domain->ping_dcname);
+
+ talloc_free(discard_const(domain->ping_dcname));
+ domain->ping_dcname = NULL;
+
+ return;
+}
+
+static void wb_imsg_new_trusted_domain(struct imessaging_context *msg,
+ void *private_data,
+ uint32_t msg_type,
+ struct server_id server_id,
+ size_t num_fds,
+ int *fds,
+ DATA_BLOB *data)
+{
+ bool ok;
+
+ if (num_fds != 0) {
+ DBG_WARNING("Received %zu fds, ignoring message\n", num_fds);
+ return;
+ }
+
+ DBG_NOTICE("Rescanning trusted domains\n");
+
+ ok = add_trusted_domains_dc();
+ if (!ok) {
+ DBG_ERR("Failed to reload trusted domains\n");
+ }
+}
+
+/*
+ * We did not get the secret when we queried secrets.tdb, so read it
+ * from secrets.tdb and re-sync the databases
+ */
+static bool migrate_secrets_tdb_to_ldb(struct winbindd_domain *domain)
+{
+ bool ok;
+ struct cli_credentials *creds;
+ NTSTATUS can_migrate = pdb_get_trust_credentials(domain->name,
+ NULL, domain, &creds);
+ if (!NT_STATUS_IS_OK(can_migrate)) {
+ DEBUG(0, ("Failed to fetch our own local AD domain join "
+ "password for winbindd's internal use, both from "
+ "secrets.tdb and secrets.ldb: %s\n",
+ nt_errstr(can_migrate)));
+ return false;
+ }
+
+ /*
+ * NOTE: It is very unlikely we end up here if there is an
+ * oldpass, because a new password is created at
+ * classicupgrade, so this is not a concern.
+ */
+ ok = secrets_store_machine_pw_sync(cli_credentials_get_password(creds),
+ NULL /* oldpass */,
+ cli_credentials_get_domain(creds),
+ cli_credentials_get_realm(creds),
+ cli_credentials_get_salt_principal(creds),
+ 0, /* Supported enc types, unused */
+ &domain->sid,
+ cli_credentials_get_password_last_changed_time(creds),
+ cli_credentials_get_secure_channel_type(creds),
+ false /* do_delete: Do not delete */);
+ TALLOC_FREE(creds);
+ if (ok == false) {
+ DEBUG(0, ("Failed to write our own "
+ "local AD domain join password for "
+ "winbindd's internal use into secrets.tdb\n"));
+ return false;
+ }
+ return true;
+}
+
+bool add_trusted_domains_dc(void)
+{
+ struct winbindd_domain *domain = NULL;
+ struct pdb_trusted_domain **domains = NULL;
+ uint32_t num_domains = 0;
+ uint32_t i;
+ NTSTATUS status;
+
+ if (!(pdb_capabilities() & PDB_CAP_TRUSTED_DOMAINS_EX)) {
+ struct trustdom_info **ti = NULL;
+
+ status = pdb_enum_trusteddoms(talloc_tos(), &num_domains, &ti);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("pdb_enum_trusteddoms() failed - %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ for (i = 0; i < num_domains; i++) {
+ status = add_trusted_domain(ti[i]->name,
+ NULL,
+ &ti[i]->sid,
+ LSA_TRUST_TYPE_DOWNLEVEL,
+ NETR_TRUST_FLAG_OUTBOUND,
+ 0,
+ SEC_CHAN_DOMAIN,
+ NULL,
+ &domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("add_trusted_domain returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ status = pdb_enum_trusted_domains(talloc_tos(), &num_domains, &domains);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("pdb_enum_trusted_domains() failed - %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ for (i = 0; i < num_domains; i++) {
+ enum netr_SchannelType sec_chan_type = SEC_CHAN_DOMAIN;
+ uint32_t trust_flags = 0;
+
+ if (domains[i]->trust_type == LSA_TRUST_TYPE_UPLEVEL) {
+ sec_chan_type = SEC_CHAN_DNS_DOMAIN;
+ }
+
+ if (!(domains[i]->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND)) {
+ sec_chan_type = SEC_CHAN_NULL;
+ }
+
+ if (domains[i]->trust_direction & LSA_TRUST_DIRECTION_INBOUND) {
+ trust_flags |= NETR_TRUST_FLAG_INBOUND;
+ }
+ if (domains[i]->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) {
+ trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
+ }
+ if (domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) {
+ trust_flags |= NETR_TRUST_FLAG_IN_FOREST;
+ }
+
+ if (domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_CROSS_ORGANIZATION) {
+ /*
+ * We don't support selective authentication yet.
+ */
+ DBG_WARNING("Ignoring CROSS_ORGANIZATION trust to "
+ "domain[%s/%s]\n",
+ domains[i]->netbios_name,
+ domains[i]->domain_name);
+ continue;
+ }
+
+ status = add_trusted_domain(domains[i]->netbios_name,
+ domains[i]->domain_name,
+ &domains[i]->security_identifier,
+ domains[i]->trust_type,
+ trust_flags,
+ domains[i]->trust_attributes,
+ sec_chan_type,
+ NULL,
+ &domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("add_trusted_domain returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ if (domains[i]->trust_type == LSA_TRUST_TYPE_UPLEVEL) {
+ domain->active_directory = true;
+ }
+ domain->domain_type = domains[i]->trust_type;
+ domain->domain_trust_attribs = domains[i]->trust_attributes;
+ }
+
+ for (i = 0; i < num_domains; i++) {
+ struct ForestTrustInfo fti;
+ uint32_t fi;
+ enum ndr_err_code ndr_err;
+ struct winbindd_domain *routing_domain = NULL;
+
+ if (domains[i]->trust_type != LSA_TRUST_TYPE_UPLEVEL) {
+ continue;
+ }
+
+ if (!(domains[i]->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) {
+ continue;
+ }
+
+ if (domains[i]->trust_forest_trust_info.length == 0) {
+ continue;
+ }
+
+ routing_domain = find_domain_from_name_noinit(
+ domains[i]->netbios_name);
+ if (routing_domain == NULL) {
+ DBG_ERR("Can't find winbindd domain [%s]\n",
+ domains[i]->netbios_name);
+ return false;
+ }
+
+ ndr_err = ndr_pull_struct_blob_all(
+ &domains[i]->trust_forest_trust_info,
+ talloc_tos(), &fti,
+ (ndr_pull_flags_fn_t)ndr_pull_ForestTrustInfo);
+ if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+ DBG_ERR("ndr_pull_ForestTrustInfo(%s) - %s\n",
+ domains[i]->netbios_name,
+ ndr_map_error2string(ndr_err));
+ return false;
+ }
+
+ for (fi = 0; fi < fti.count; fi++) {
+ struct ForestTrustInfoRecord *rec =
+ &fti.records[fi].record;
+ struct ForestTrustDataDomainInfo *drec = NULL;
+
+ if (rec->type != FOREST_TRUST_DOMAIN_INFO) {
+ continue;
+ }
+ drec = &rec->data.info;
+
+ if (rec->flags & LSA_NB_DISABLED_MASK) {
+ continue;
+ }
+
+ if (rec->flags & LSA_SID_DISABLED_MASK) {
+ continue;
+ }
+
+ /*
+ * TODO:
+ * also try to find a matching
+ * LSA_TLN_DISABLED_MASK ???
+ */
+
+ domain = find_domain_from_name_noinit(drec->netbios_name.string);
+ if (domain != NULL) {
+ continue;
+ }
+
+ status = add_trusted_domain(drec->netbios_name.string,
+ drec->dns_name.string,
+ &drec->sid,
+ LSA_TRUST_TYPE_UPLEVEL,
+ NETR_TRUST_FLAG_OUTBOUND,
+ 0,
+ SEC_CHAN_NULL,
+ routing_domain,
+ &domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_NOTICE("add_trusted_domain returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+ if (domain == NULL) {
+ continue;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+/* Look up global info for the winbind daemon */
+bool init_domain_list(void)
+{
+ int role = lp_server_role();
+ struct pdb_domain_info *pdb_domain_info = NULL;
+ struct winbindd_domain *domain = NULL;
+ NTSTATUS status;
+ bool ok;
+
+ /* Free existing list */
+ free_domain_list();
+
+ /* BUILTIN domain */
+
+ status = add_trusted_domain("BUILTIN",
+ NULL,
+ &global_sid_Builtin,
+ LSA_TRUST_TYPE_DOWNLEVEL,
+ 0, /* trust_flags */
+ 0, /* trust_attribs */
+ SEC_CHAN_LOCAL,
+ NULL,
+ &domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("add_trusted_domain BUILTIN returned %s\n",
+ nt_errstr(status));
+ return false;
+ }
+
+ /* Local SAM */
+
+ /*
+ * In case the passdb backend is passdb_dsdb the domain SID comes from
+ * dsdb, not from secrets.tdb. As we use the domain SID in various
+ * places, we must ensure the domain SID is migrated from dsdb to
+ * secrets.tdb before get_global_sam_sid() is called the first time.
+ *
+ * The migration is done as part of the passdb_dsdb initialisation,
+ * calling pdb_get_domain_info() triggers it.
+ */
+ pdb_domain_info = pdb_get_domain_info(talloc_tos());
+
+ if ( role == ROLE_ACTIVE_DIRECTORY_DC ) {
+ uint32_t trust_flags;
+ bool is_root;
+ enum netr_SchannelType sec_chan_type;
+ const char *account_name;
+ struct samr_Password current_nt_hash;
+
+ if (pdb_domain_info == NULL) {
+ DEBUG(0, ("Failed to fetch our own local AD "
+ "domain info from sam.ldb\n"));
+ return false;
+ }
+
+ trust_flags = NETR_TRUST_FLAG_PRIMARY;
+ trust_flags |= NETR_TRUST_FLAG_IN_FOREST;
+ trust_flags |= NETR_TRUST_FLAG_NATIVE;
+ trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
+
+ is_root = strequal(pdb_domain_info->dns_domain,
+ pdb_domain_info->dns_forest);
+ if (is_root) {
+ trust_flags |= NETR_TRUST_FLAG_TREEROOT;
+ }
+
+ status = add_trusted_domain(pdb_domain_info->name,
+ pdb_domain_info->dns_domain,
+ &pdb_domain_info->sid,
+ LSA_TRUST_TYPE_UPLEVEL,
+ trust_flags,
+ LSA_TRUST_ATTRIBUTE_WITHIN_FOREST,
+ SEC_CHAN_BDC,
+ NULL,
+ &domain);
+ TALLOC_FREE(pdb_domain_info);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to add our own local AD "
+ "domain to winbindd's internal list\n");
+ return false;
+ }
+
+ /*
+ * We need to call this to find out if we are an RODC
+ */
+ ok = get_trust_pw_hash(domain->name,
+ current_nt_hash.hash,
+ &account_name,
+ &sec_chan_type);
+ if (!ok) {
+ /*
+ * If get_trust_pw_hash() fails, then try and
+ * fetch the password from the more recent of
+ * secrets.{ldb,tdb} using the
+ * pdb_get_trust_credentials()
+ */
+ ok = migrate_secrets_tdb_to_ldb(domain);
+
+ if (!ok) {
+ DEBUG(0, ("Failed to migrate our own "
+ "local AD domain join password for "
+ "winbindd's internal use into "
+ "secrets.tdb\n"));
+ return false;
+ }
+ ok = get_trust_pw_hash(domain->name,
+ current_nt_hash.hash,
+ &account_name,
+ &sec_chan_type);
+ if (!ok) {
+ DEBUG(0, ("Failed to find our own just "
+ "written local AD domain join "
+ "password for winbindd's internal "
+ "use in secrets.tdb\n"));
+ return false;
+ }
+ }
+
+ domain->secure_channel_type = sec_chan_type;
+ if (sec_chan_type == SEC_CHAN_RODC) {
+ domain->rodc = true;
+ }
+
+ } else {
+ uint32_t trust_flags;
+ enum netr_SchannelType secure_channel_type;
+
+ trust_flags = NETR_TRUST_FLAG_OUTBOUND;
+ if (role != ROLE_DOMAIN_MEMBER) {
+ trust_flags |= NETR_TRUST_FLAG_PRIMARY;
+ }
+
+ if (role > ROLE_DOMAIN_MEMBER) {
+ secure_channel_type = SEC_CHAN_BDC;
+ } else {
+ secure_channel_type = SEC_CHAN_LOCAL;
+ }
+
+ if ((pdb_domain_info != NULL) && (role == ROLE_IPA_DC)) {
+ /* This is IPA DC that presents itself as
+ * an Active Directory domain controller to trusted AD
+ * forests but in fact is a classic domain controller.
+ */
+ trust_flags = NETR_TRUST_FLAG_PRIMARY;
+ trust_flags |= NETR_TRUST_FLAG_IN_FOREST;
+ trust_flags |= NETR_TRUST_FLAG_NATIVE;
+ trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
+ trust_flags |= NETR_TRUST_FLAG_TREEROOT;
+ status = add_trusted_domain(pdb_domain_info->name,
+ pdb_domain_info->dns_domain,
+ &pdb_domain_info->sid,
+ LSA_TRUST_TYPE_UPLEVEL,
+ trust_flags,
+ LSA_TRUST_ATTRIBUTE_WITHIN_FOREST,
+ secure_channel_type,
+ NULL,
+ &domain);
+ TALLOC_FREE(pdb_domain_info);
+ } else {
+ status = add_trusted_domain(get_global_sam_name(),
+ NULL,
+ get_global_sam_sid(),
+ LSA_TRUST_TYPE_DOWNLEVEL,
+ trust_flags,
+ 0, /* trust_attribs */
+ secure_channel_type,
+ NULL,
+ &domain);
+ }
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to add local SAM to "
+ "domain to winbindd's internal list\n");
+ return false;
+ }
+ }
+
+ if (IS_DC) {
+ ok = add_trusted_domains_dc();
+ if (!ok) {
+ DBG_ERR("init_domain_list_dc failed\n");
+ return false;
+ }
+ }
+
+ if ( role == ROLE_DOMAIN_MEMBER ) {
+ struct dom_sid our_sid;
+ uint32_t trust_type;
+
+ if (!secrets_fetch_domain_sid(lp_workgroup(), &our_sid)) {
+ DEBUG(0, ("Could not fetch our SID - did we join?\n"));
+ return False;
+ }
+
+ if (lp_realm() != NULL) {
+ trust_type = LSA_TRUST_TYPE_UPLEVEL;
+ } else {
+ trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
+ }
+
+ status = add_trusted_domain(lp_workgroup(),
+ lp_realm(),
+ &our_sid,
+ trust_type,
+ NETR_TRUST_FLAG_PRIMARY|
+ NETR_TRUST_FLAG_OUTBOUND,
+ 0, /* trust_attribs */
+ SEC_CHAN_WKSTA,
+ NULL,
+ &domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("Failed to add local SAM to "
+ "domain to winbindd's internal list\n");
+ return false;
+ }
+ }
+
+ status = imessaging_register(winbind_imessaging_context(), NULL,
+ MSG_WINBIND_RELOAD_TRUSTED_DOMAINS,
+ wb_imsg_new_trusted_domain);
+ if (!NT_STATUS_IS_OK(status)) {
+ DBG_ERR("imessaging_register failed %s\n", nt_errstr(status));
+ return false;
+ }
+
+ return True;
+}
+
+/**
+ * Given a domain name, return the struct winbindd domain info for it
+ *
+ * @note Do *not* pass lp_workgroup() to this function. domain_list
+ * may modify it's value, and free that pointer. Instead, our local
+ * domain may be found by calling find_our_domain().
+ * directly.
+ *
+ *
+ * @return The domain structure for the named domain, if it is working.
+ */
+
+struct winbindd_domain *find_domain_from_name_noinit(const char *domain_name)
+{
+ struct winbindd_domain *domain;
+
+ /* Search through list */
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ if (strequal(domain_name, domain->name)) {
+ return domain;
+ }
+ if (domain->alt_name == NULL) {
+ continue;
+ }
+ if (strequal(domain_name, domain->alt_name)) {
+ return domain;
+ }
+ }
+
+ /* Not found */
+
+ return NULL;
+}
+
+/**
+ * Given a domain name, return the struct winbindd domain if it's a direct
+ * outgoing trust
+ *
+ * @return The domain structure for the named domain, if it is a direct outgoing trust
+ */
+struct winbindd_domain *find_trust_from_name_noinit(const char *domain_name)
+{
+ struct winbindd_domain *domain = NULL;
+
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ return NULL;
+ }
+
+ if (domain->secure_channel_type != SEC_CHAN_NULL) {
+ return domain;
+ }
+
+ return NULL;
+}
+
+struct winbindd_domain *find_domain_from_name(const char *domain_name)
+{
+ struct winbindd_domain *domain;
+
+ domain = find_domain_from_name_noinit(domain_name);
+
+ if (domain == NULL)
+ return NULL;
+
+ if (!domain->initialized)
+ init_dc_connection(domain, false);
+
+ return domain;
+}
+
+/* Given a domain sid, return the struct winbindd domain info for it */
+
+struct winbindd_domain *find_domain_from_sid_noinit(const struct dom_sid *sid)
+{
+ struct winbindd_domain *domain;
+
+ /* Search through list */
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ if (dom_sid_compare_domain(sid, &domain->sid) == 0)
+ return domain;
+ }
+
+ /* Not found */
+
+ return NULL;
+}
+
+/**
+ * Given a domain sid, return the struct winbindd domain if it's a direct
+ * outgoing trust
+ *
+ * @return The domain structure for the specified domain, if it is a direct outgoing trust
+ */
+struct winbindd_domain *find_trust_from_sid_noinit(const struct dom_sid *sid)
+{
+ struct winbindd_domain *domain = NULL;
+
+ domain = find_domain_from_sid_noinit(sid);
+ if (domain == NULL) {
+ return NULL;
+ }
+
+ if (domain->secure_channel_type != SEC_CHAN_NULL) {
+ return domain;
+ }
+
+ return NULL;
+}
+
+/* Given a domain sid, return the struct winbindd domain info for it */
+
+struct winbindd_domain *find_domain_from_sid(const struct dom_sid *sid)
+{
+ struct winbindd_domain *domain;
+
+ domain = find_domain_from_sid_noinit(sid);
+
+ if (domain == NULL)
+ return NULL;
+
+ if (!domain->initialized)
+ init_dc_connection(domain, false);
+
+ return domain;
+}
+
+struct winbindd_domain *find_our_domain(void)
+{
+ struct winbindd_domain *domain;
+
+ /* Search through list */
+
+ for (domain = domain_list(); domain != NULL; domain = domain->next) {
+ if (domain->primary)
+ return domain;
+ }
+
+ smb_panic("Could not find our domain");
+ return NULL;
+}
+
+struct winbindd_domain *find_default_route_domain(void)
+{
+ if (!IS_DC) {
+ return find_our_domain();
+ }
+ DBG_DEBUG("Routing logic not yet implemented on a DC\n");
+ return NULL;
+}
+
+/* Find the appropriate domain to lookup a name or SID */
+
+struct winbindd_domain *find_lookup_domain_from_sid(const struct dom_sid *sid)
+{
+ struct dom_sid_buf buf;
+
+ DBG_DEBUG("SID [%s]\n", dom_sid_str_buf(sid, &buf));
+
+ /*
+ * SIDs in the S-1-22-{1,2} domain and well-known SIDs should be handled
+ * by our passdb.
+ */
+
+ if ( sid_check_is_in_unix_groups(sid) ||
+ sid_check_is_unix_groups(sid) ||
+ sid_check_is_in_unix_users(sid) ||
+ sid_check_is_unix_users(sid) ||
+ sid_check_is_our_sam(sid) ||
+ sid_check_is_in_our_sam(sid) )
+ {
+ return find_domain_from_sid(get_global_sam_sid());
+ }
+
+ if ( sid_check_is_builtin(sid) ||
+ sid_check_is_in_builtin(sid) ||
+ sid_check_is_wellknown_domain(sid, NULL) ||
+ sid_check_is_in_wellknown_domain(sid) )
+ {
+ return find_domain_from_sid(&global_sid_Builtin);
+ }
+
+ if (IS_DC) {
+ struct winbindd_domain *domain = NULL;
+
+ domain = find_domain_from_sid_noinit(sid);
+ if (domain == NULL) {
+ return NULL;
+ }
+
+ if (domain->secure_channel_type != SEC_CHAN_NULL) {
+ return domain;
+ }
+
+ return domain->routing_domain;
+ }
+
+ /* On a member server a query for SID or name can always go to our
+ * primary DC. */
+
+ DEBUG(10, ("calling find_our_domain\n"));
+ return find_our_domain();
+}
+
+struct winbindd_domain *find_lookup_domain_from_name(const char *domain_name)
+{
+ bool predefined;
+
+ if ( strequal(domain_name, unix_users_domain_name() ) ||
+ strequal(domain_name, unix_groups_domain_name() ) )
+ {
+ /*
+ * The "Unix User" and "Unix Group" domain are handled by
+ * passdb
+ */
+ return find_domain_from_name_noinit( get_global_sam_name() );
+ }
+
+ if (strequal(domain_name, "BUILTIN") ||
+ strequal(domain_name, get_global_sam_name())) {
+ return find_domain_from_name_noinit(domain_name);
+ }
+
+ predefined = dom_sid_lookup_is_predefined_domain(domain_name);
+ if (predefined) {
+ return find_domain_from_name_noinit(builtin_domain_name());
+ }
+
+ if (IS_DC) {
+ struct winbindd_domain *domain = NULL;
+
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ return NULL;
+ }
+
+ if (domain->secure_channel_type != SEC_CHAN_NULL) {
+ return domain;
+ }
+
+ return domain->routing_domain;
+ }
+
+ return find_our_domain();
+}
+
+/* Is this a domain which we may assume no DOMAIN\ prefix? */
+
+static bool assume_domain(const char *domain)
+{
+ /* never assume the domain on a standalone server */
+
+ if ( lp_server_role() == ROLE_STANDALONE )
+ return False;
+
+ /* domain member servers may possibly assume for the domain name */
+
+ if ( lp_server_role() == ROLE_DOMAIN_MEMBER ) {
+ if ( !strequal(lp_workgroup(), domain) )
+ return False;
+
+ if ( lp_winbind_use_default_domain() )
+ return True;
+ }
+
+ /* only left with a domain controller */
+
+ if ( strequal(get_global_sam_name(), domain) ) {
+ return True;
+ }
+
+ return False;
+}
+
+/* Parse a DOMAIN\user or UPN string into a domain, namespace and a user */
+bool parse_domain_user(TALLOC_CTX *ctx,
+ const char *domuser,
+ char **pnamespace,
+ char **pdomain,
+ char **puser)
+{
+ char *p = NULL;
+ char *namespace = NULL;
+ char *domain = NULL;
+ char *user = NULL;
+
+ if (strlen(domuser) == 0) {
+ return false;
+ }
+
+ p = strchr(domuser, *lp_winbind_separator());
+ if (p != NULL) {
+ user = talloc_strdup(ctx, p + 1);
+ if (user == NULL) {
+ goto fail;
+ }
+ domain = talloc_strdup(ctx,
+ domuser);
+ if (domain == NULL) {
+ goto fail;
+ }
+ domain[PTR_DIFF(p, domuser)] = '\0';
+ namespace = talloc_strdup(ctx, domain);
+ if (namespace == NULL) {
+ goto fail;
+ }
+ } else {
+ user = talloc_strdup(ctx, domuser);
+ if (user == NULL) {
+ goto fail;
+ }
+ p = strchr(domuser, '@');
+ if (p != NULL) {
+ /* upn */
+ namespace = talloc_strdup(ctx, p + 1);
+ if (namespace == NULL) {
+ goto fail;
+ }
+ domain = talloc_strdup(ctx, "");
+ if (domain == NULL) {
+ goto fail;
+ }
+
+ } else if (assume_domain(lp_workgroup())) {
+ domain = talloc_strdup(ctx, lp_workgroup());
+ if (domain == NULL) {
+ goto fail;
+ }
+ namespace = talloc_strdup(ctx, domain);
+ if (namespace == NULL) {
+ goto fail;
+ }
+ } else {
+ namespace = talloc_strdup(ctx, lp_netbios_name());
+ if (namespace == NULL) {
+ goto fail;
+ }
+ domain = talloc_strdup(ctx, "");
+ if (domain == NULL) {
+ goto fail;
+ }
+ }
+ }
+
+ if (!strupper_m(domain)) {
+ goto fail;
+ }
+
+ *pnamespace = namespace;
+ *pdomain = domain;
+ *puser = user;
+ return true;
+fail:
+ TALLOC_FREE(user);
+ TALLOC_FREE(domain);
+ TALLOC_FREE(namespace);
+ return false;
+}
+
+bool canonicalize_username(TALLOC_CTX *mem_ctx,
+ char **pusername_inout,
+ char **pnamespace,
+ char **pdomain,
+ char **puser)
+{
+ bool ok;
+ char *namespace = NULL;
+ char *domain = NULL;
+ char *user = NULL;
+ char *username_inout = NULL;
+
+ ok = parse_domain_user(mem_ctx,
+ *pusername_inout,
+ &namespace, &domain, &user);
+
+ if (!ok) {
+ return False;
+ }
+
+ username_inout = talloc_asprintf(mem_ctx, "%s%c%s",
+ domain, *lp_winbind_separator(),
+ user);
+
+ if (username_inout == NULL) {
+ goto fail;
+ }
+
+ *pnamespace = namespace;
+ *puser = user;
+ *pdomain = domain;
+ *pusername_inout = username_inout;
+ return True;
+fail:
+ TALLOC_FREE(username_inout);
+ TALLOC_FREE(namespace);
+ TALLOC_FREE(domain);
+ TALLOC_FREE(user);
+ return false;
+}
+
+/*
+ Fill DOMAIN\\USERNAME entry accounting 'winbind use default domain' and
+ 'winbind separator' options.
+ This means:
+ - omit DOMAIN when 'winbind use default domain = true' and DOMAIN is
+ lp_workgroup()
+
+ If we are a PDC or BDC, and this is for our domain, do likewise.
+
+ On an AD DC we always fill DOMAIN\\USERNAME.
+
+ We always canonicalize as UPPERCASE DOMAIN, lowercase username.
+*/
+/**
+ * talloc version of fill_domain_username()
+ * return NULL on talloc failure.
+ */
+char *fill_domain_username_talloc(TALLOC_CTX *mem_ctx,
+ const char *domain,
+ const char *user,
+ bool can_assume)
+{
+ char *tmp_user, *name;
+
+ if (lp_server_role() == ROLE_ACTIVE_DIRECTORY_DC) {
+ can_assume = false;
+ }
+
+ if (user == NULL) {
+ return NULL;
+ }
+
+ tmp_user = talloc_strdup(mem_ctx, user);
+ if (tmp_user == NULL) {
+ return NULL;
+ }
+ if (!strlower_m(tmp_user)) {
+ TALLOC_FREE(tmp_user);
+ return NULL;
+ }
+
+ if (can_assume && assume_domain(domain)) {
+ name = tmp_user;
+ } else {
+ name = talloc_asprintf(mem_ctx, "%s%c%s",
+ domain,
+ *lp_winbind_separator(),
+ tmp_user);
+ TALLOC_FREE(tmp_user);
+ }
+
+ return name;
+}
+
+/*
+ * Client list accessor functions
+ */
+
+static struct winbindd_cli_state *_client_list;
+static int _num_clients;
+
+/* Return list of all connected clients */
+
+struct winbindd_cli_state *winbindd_client_list(void)
+{
+ return _client_list;
+}
+
+/* Return list-tail of all connected clients */
+
+struct winbindd_cli_state *winbindd_client_list_tail(void)
+{
+ return DLIST_TAIL(_client_list);
+}
+
+/* Return previous (read:newer) client in list */
+
+struct winbindd_cli_state *
+winbindd_client_list_prev(struct winbindd_cli_state *cli)
+{
+ return DLIST_PREV(cli);
+}
+
+/* Add a connection to the list */
+
+void winbindd_add_client(struct winbindd_cli_state *cli)
+{
+ cli->last_access = time(NULL);
+ DLIST_ADD(_client_list, cli);
+ _num_clients++;
+}
+
+/* Remove a client from the list */
+
+void winbindd_remove_client(struct winbindd_cli_state *cli)
+{
+ DLIST_REMOVE(_client_list, cli);
+ _num_clients--;
+}
+
+/* Move a client to head or list */
+
+void winbindd_promote_client(struct winbindd_cli_state *cli)
+{
+ cli->last_access = time(NULL);
+ DLIST_PROMOTE(_client_list, cli);
+}
+
+/* Return number of open clients */
+
+int winbindd_num_clients(void)
+{
+ return _num_clients;
+}
+
+NTSTATUS lookup_usergroups_cached(TALLOC_CTX *mem_ctx,
+ const struct dom_sid *user_sid,
+ uint32_t *p_num_groups, struct dom_sid **user_sids)
+{
+ struct netr_SamInfo3 *info3 = NULL;
+ NTSTATUS status = NT_STATUS_NO_MEMORY;
+ uint32_t num_groups = 0;
+
+ DEBUG(3,(": lookup_usergroups_cached\n"));
+
+ *user_sids = NULL;
+ *p_num_groups = 0;
+
+ info3 = netsamlogon_cache_get(mem_ctx, user_sid);
+
+ if (info3 == NULL) {
+ return NT_STATUS_OBJECT_NAME_NOT_FOUND;
+ }
+
+ /*
+ * Before bug #7843 the "Domain Local" groups were added with a
+ * lookupuseraliases call, but this isn't done anymore for our domain
+ * so we need to resolve resource groups here.
+ *
+ * When to use Resource Groups:
+ * http://technet.microsoft.com/en-us/library/cc753670%28v=WS.10%29.aspx
+ */
+ status = sid_array_from_info3(mem_ctx, info3,
+ user_sids,
+ &num_groups,
+ false);
+
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info3);
+ return status;
+ }
+
+ TALLOC_FREE(info3);
+ *p_num_groups = num_groups;
+ status = (user_sids != NULL) ? NT_STATUS_OK : NT_STATUS_NO_MEMORY;
+
+ DEBUG(3,(": lookup_usergroups_cached succeeded\n"));
+
+ return status;
+}
+
+/*********************************************************************
+ We use this to remove spaces from user and group names
+********************************************************************/
+
+NTSTATUS normalize_name_map(TALLOC_CTX *mem_ctx,
+ const char *domain_name,
+ const char *name,
+ char **normalized)
+{
+ struct winbindd_domain *domain = NULL;
+ NTSTATUS nt_status;
+
+ if (!name || !normalized) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_winbind_normalize_names()) {
+ return NT_STATUS_PROCEDURE_NOT_FOUND;
+ }
+
+ domain = find_domain_from_name_noinit(domain_name);
+ if (domain == NULL) {
+ DBG_ERR("Failed to find domain '%s'\n", domain_name);
+ return NT_STATUS_NO_SUCH_DOMAIN;
+ }
+
+ /* Alias support and whitespace replacement are mutually
+ exclusive */
+
+ nt_status = resolve_username_to_alias(mem_ctx, domain,
+ name, normalized );
+ if (NT_STATUS_IS_OK(nt_status)) {
+ /* special return code to let the caller know we
+ mapped to an alias */
+ return NT_STATUS_FILE_RENAMED;
+ }
+
+ /* check for an unreachable domain */
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ DEBUG(5,("normalize_name_map: Setting domain %s offline\n",
+ domain->name));
+ set_domain_offline(domain);
+ return nt_status;
+ }
+
+ /* deal with whitespace */
+
+ *normalized = talloc_strdup(mem_ctx, name);
+ if (!(*normalized)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ all_string_sub( *normalized, " ", "_", 0 );
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ We use this to do the inverse of normalize_name_map()
+********************************************************************/
+
+NTSTATUS normalize_name_unmap(TALLOC_CTX *mem_ctx,
+ const char *name,
+ char **normalized)
+{
+ NTSTATUS nt_status;
+ struct winbindd_domain *domain = find_our_domain();
+
+ if (!name || !normalized) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (!lp_winbind_normalize_names()) {
+ return NT_STATUS_PROCEDURE_NOT_FOUND;
+ }
+
+ /* Alias support and whitespace replacement are mutally
+ exclusive */
+
+ /* When mapping from an alias to a username, we don't know the
+ domain. But we only need a domain structure to cache
+ a successful lookup , so just our own domain structure for
+ the seqnum. */
+
+ nt_status = resolve_alias_to_username(mem_ctx, domain,
+ name, normalized);
+ if (NT_STATUS_IS_OK(nt_status)) {
+ /* Special return code to let the caller know we mapped
+ from an alias */
+ return NT_STATUS_FILE_RENAMED;
+ }
+
+ /* check for an unreachable domain */
+
+ if (NT_STATUS_EQUAL(nt_status, NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND)) {
+ DEBUG(5,("normalize_name_unmap: Setting domain %s offline\n",
+ domain->name));
+ set_domain_offline(domain);
+ return nt_status;
+ }
+
+ /* deal with whitespace */
+
+ *normalized = talloc_strdup(mem_ctx, name);
+ if (!(*normalized)) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ all_string_sub(*normalized, "_", " ", 0);
+
+ return NT_STATUS_OK;
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+bool winbindd_can_contact_domain(struct winbindd_domain *domain)
+{
+ struct winbindd_tdc_domain *tdc = NULL;
+ TALLOC_CTX *frame = talloc_stackframe();
+ bool ret = false;
+
+ /* We can contact the domain if it is our primary domain */
+
+ if (domain->primary) {
+ ret = true;
+ goto done;
+ }
+
+ /* Trust the TDC cache and not the winbindd_domain flags */
+
+ if ((tdc = wcache_tdc_fetch_domain(frame, domain->name)) == NULL) {
+ DEBUG(10,("winbindd_can_contact_domain: %s not found in cache\n",
+ domain->name));
+ ret = false;
+ goto done;
+ }
+
+ /* Can always contact a domain that is in out forest */
+
+ if (tdc->trust_flags & NETR_TRUST_FLAG_IN_FOREST) {
+ ret = true;
+ goto done;
+ }
+
+ /*
+ * On a _member_ server, we cannot contact the domain if it
+ * is running AD and we have no inbound trust.
+ */
+
+ if (!IS_DC &&
+ domain->active_directory &&
+ ((tdc->trust_flags & NETR_TRUST_FLAG_INBOUND) != NETR_TRUST_FLAG_INBOUND))
+ {
+ DEBUG(10, ("winbindd_can_contact_domain: %s is an AD domain "
+ "and we have no inbound trust.\n", domain->name));
+ goto done;
+ }
+
+ /* Assume everything else is ok (probably not true but what
+ can you do?) */
+
+ ret = true;
+
+done:
+ talloc_destroy(frame);
+
+ return ret;
+}
+
+#ifdef HAVE_KRB5_LOCATE_PLUGIN_H
+
+/*********************************************************************
+ ********************************************************************/
+
+static void winbindd_set_locator_kdc_env(const struct winbindd_domain *domain)
+{
+ char *var = NULL;
+ char addr[INET6_ADDRSTRLEN];
+ const char *kdc = NULL;
+ int lvl = 11;
+
+ if (!domain || !domain->alt_name || !*domain->alt_name) {
+ return;
+ }
+
+ if (domain->initialized && !domain->active_directory) {
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: %s not AD\n",
+ domain->alt_name));
+ return;
+ }
+
+ print_sockaddr(addr, sizeof(addr), &domain->dcaddr);
+ kdc = addr;
+ if (!*kdc) {
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: %s no DC IP\n",
+ domain->alt_name));
+ kdc = domain->dcname;
+ }
+
+ if (!kdc || !*kdc) {
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: %s no DC at all\n",
+ domain->alt_name));
+ return;
+ }
+
+ var = talloc_asprintf_strupper_m(
+ talloc_tos(),
+ "%s_%s",
+ WINBINDD_LOCATOR_KDC_ADDRESS,
+ domain->alt_name);
+ if (var == NULL) {
+ return;
+ }
+
+ DEBUG(lvl,("winbindd_set_locator_kdc_env: setting var: %s to: %s\n",
+ var, kdc));
+
+ setenv(var, kdc, 1);
+ TALLOC_FREE(var);
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain)
+{
+ struct winbindd_domain *our_dom = find_our_domain();
+
+ winbindd_set_locator_kdc_env(domain);
+
+ if (domain != our_dom) {
+ winbindd_set_locator_kdc_env(our_dom);
+ }
+}
+
+/*********************************************************************
+ ********************************************************************/
+
+void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain)
+{
+ char *var = NULL;
+
+ if (!domain || !domain->alt_name || !*domain->alt_name) {
+ return;
+ }
+
+ var = talloc_asprintf_strupper_m(
+ talloc_tos(),
+ "%s_%s",
+ WINBINDD_LOCATOR_KDC_ADDRESS,
+ domain->alt_name);
+ if (var == NULL) {
+ return;
+ }
+
+ unsetenv(var);
+ TALLOC_FREE(var);
+}
+#else
+
+void winbindd_set_locator_kdc_envs(const struct winbindd_domain *domain)
+{
+ return;
+}
+
+void winbindd_unset_locator_kdc_env(const struct winbindd_domain *domain)
+{
+ return;
+}
+
+#endif /* HAVE_KRB5_LOCATE_PLUGIN_H */
+
+void set_auth_errors(struct winbindd_response *resp, NTSTATUS result)
+{
+ /*
+ * Make sure we start with authoritative=true,
+ * it will only set to false if we don't know the
+ * domain.
+ */
+ resp->data.auth.authoritative = true;
+
+ resp->data.auth.nt_status = NT_STATUS_V(result);
+ fstrcpy(resp->data.auth.nt_status_string, nt_errstr(result));
+
+ /* we might have given a more useful error above */
+ if (*resp->data.auth.error_string == '\0')
+ fstrcpy(resp->data.auth.error_string,
+ get_friendly_nt_error_msg(result));
+ resp->data.auth.pam_error = nt_status_to_pam(result);
+}
+
+bool is_domain_offline(const struct winbindd_domain *domain)
+{
+ if (get_global_winbindd_state_offline()) {
+ return true;
+ }
+ return !domain->online;
+}
+
+bool is_domain_online(const struct winbindd_domain *domain)
+{
+ return !is_domain_offline(domain);
+}
+
+/**
+ * Parse an char array into a list of sids.
+ *
+ * The input sidstr should consist of 0-terminated strings
+ * representing sids, separated by newline characters '\n'.
+ * The list is terminated by an empty string, i.e.
+ * character '\0' directly following a character '\n'
+ * (or '\0' right at the start of sidstr).
+ */
+bool parse_sidlist(TALLOC_CTX *mem_ctx, const char *sidstr,
+ struct dom_sid **sids, uint32_t *num_sids)
+{
+ const char *p;
+
+ p = sidstr;
+ if (p == NULL)
+ return False;
+
+ while (p[0] != '\0') {
+ struct dom_sid sid;
+ const char *q = NULL;
+
+ if (!dom_sid_parse_endp(p, &sid, &q)) {
+ DEBUG(1, ("Could not parse sid %s\n", p));
+ return false;
+ }
+ if (q[0] != '\n') {
+ DEBUG(1, ("Got invalid sidstr: %s\n", p));
+ return false;
+ }
+ if (!NT_STATUS_IS_OK(add_sid_to_array(mem_ctx, &sid, sids,
+ num_sids)))
+ {
+ return False;
+ }
+ p = q+1;
+ }
+ return True;
+}
+
+bool parse_xidlist(TALLOC_CTX *mem_ctx, const char *xidstr,
+ struct unixid **pxids, uint32_t *pnum_xids)
+{
+ const char *p;
+ struct unixid *xids = NULL;
+ uint32_t num_xids = 0;
+
+ p = xidstr;
+ if (p == NULL) {
+ return false;
+ }
+
+ while (p[0] != '\0') {
+ struct unixid *tmp;
+ struct unixid xid;
+ unsigned long long id;
+ char *endp;
+ int error = 0;
+
+ switch (p[0]) {
+ case 'U':
+ xid = (struct unixid) { .type = ID_TYPE_UID };
+ break;
+ case 'G':
+ xid = (struct unixid) { .type = ID_TYPE_GID };
+ break;
+ default:
+ return false;
+ }
+
+ p += 1;
+
+ id = smb_strtoull(p, &endp, 10, &error, SMB_STR_STANDARD);
+ if (error != 0) {
+ goto fail;
+ }
+ if (*endp != '\n') {
+ goto fail;
+ }
+ p = endp+1;
+
+ xid.id = id;
+ if ((unsigned long long)xid.id != id) {
+ goto fail;
+ }
+
+ tmp = talloc_realloc(mem_ctx, xids, struct unixid, num_xids+1);
+ if (tmp == NULL) {
+ return 0;
+ }
+ xids = tmp;
+
+ xids[num_xids] = xid;
+ num_xids += 1;
+ }
+
+ *pxids = xids;
+ *pnum_xids = num_xids;
+ return true;
+
+fail:
+ TALLOC_FREE(xids);
+ return false;
+}
diff --git a/source3/winbindd/winbindd_wins_byip.c b/source3/winbindd/winbindd_wins_byip.c
new file mode 100644
index 0000000..1b9cdbc
--- /dev/null
+++ b/source3/winbindd/winbindd_wins_byip.c
@@ -0,0 +1,142 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_WINS_BYIP
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libsmb/namequery.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "libsmb/nmblib.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_wins_byip_state {
+ struct nmb_name star;
+ struct sockaddr_storage addr;
+ fstring response;
+};
+
+static void winbindd_wins_byip_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_wins_byip_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_wins_byip_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_wins_byip_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ /* Ensure null termination */
+ request->data.winsreq[sizeof(request->data.winsreq)-1]='\0';
+
+ fstr_sprintf(state->response, "%s\t", request->data.winsreq);
+
+ D_NOTICE("[%s (%u)] Winbind external command WINS_BYIP start.\n"
+ "Resolving wins byip for %s.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.winsreq);
+
+ make_nmb_name(&state->star, "*", 0);
+
+ if (!interpret_string_addr(&state->addr, request->data.winsreq,
+ AI_NUMERICHOST)) {
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ subreq = node_status_query_send(state, ev, &state->star,
+ &state->addr);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_wins_byip_done, req);
+ return req;
+}
+
+static void winbindd_wins_byip_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_wins_byip_state *state = tevent_req_data(
+ req, struct winbindd_wins_byip_state);
+ struct node_status *names;
+ size_t i;
+ size_t num_names = 0;
+ NTSTATUS status;
+
+ status = node_status_query_recv(subreq, talloc_tos(), &names,
+ &num_names, NULL);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+
+ for (i=0; i<num_names; i++) {
+ size_t size;
+ /*
+ * ignore group names
+ */
+ if (names[i].flags & 0x80) {
+ continue;
+ }
+ /*
+ * Only report 0x20
+ */
+ if (names[i].type != 0x20) {
+ continue;
+ }
+
+ D_DEBUG("Got name '%s'.\n", names[i].name);
+
+ size = strlen(names[i].name + strlen(state->response));
+ if (size > sizeof(state->response) - 1) {
+ D_WARNING("Too much data!\n");
+ tevent_req_nterror(req, STATUS_BUFFER_OVERFLOW);
+ return;
+ }
+ fstrcat(state->response, names[i].name);
+ fstrcat(state->response, " ");
+ }
+ state->response[strlen(state->response)-1] = '\n';
+
+
+ TALLOC_FREE(names);
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_wins_byip_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ struct winbindd_wins_byip_state *state = tevent_req_data(
+ req, struct winbindd_wins_byip_state);
+ NTSTATUS status;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+ D_NOTICE("Winbind external command WINS_BYIP end.\n"
+ "Response: %s",
+ state->response);
+ fstrcpy(presp->data.winsresp, state->response);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_wins_byname.c b/source3/winbindd/winbindd_wins_byname.c
new file mode 100644
index 0000000..ae170b2
--- /dev/null
+++ b/source3/winbindd/winbindd_wins_byname.c
@@ -0,0 +1,154 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_WINS_BYNAME
+ Copyright (C) Volker Lendecke 2011
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "libsmb/namequery.h"
+#include "librpc/gen_ndr/ndr_winbind_c.h"
+#include "libsmb/nmblib.h"
+#include "lib/util/string_wrappers.h"
+
+struct winbindd_wins_byname_state {
+ struct tevent_context *ev;
+ struct winbindd_request *request;
+ struct sockaddr_storage *addrs;
+ size_t num_addrs;
+};
+
+static void winbindd_wins_byname_wins_done(struct tevent_req *subreq);
+static void winbindd_wins_byname_bcast_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_wins_byname_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_wins_byname_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_wins_byname_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+ state->request = request;
+
+ /* Ensure null termination */
+ request->data.winsreq[sizeof(request->data.winsreq)-1]='\0';
+
+ D_NOTICE("[%s (%u)] Winbind external command WINS_BYNAME start.\n"
+ "Resolving wins byname for '%s'.\n",
+ cli->client_name,
+ (unsigned int)cli->pid,
+ request->data.winsreq);
+
+ subreq = resolve_wins_send(state, ev, state->request->data.winsreq,
+ 0x20);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_wins_byname_wins_done, req);
+ return req;
+}
+
+static void winbindd_wins_byname_wins_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_wins_byname_state *state = tevent_req_data(
+ req, struct winbindd_wins_byname_state);
+ NTSTATUS status;
+
+ status = resolve_wins_recv(subreq, talloc_tos(), &state->addrs,
+ &state->num_addrs, NULL);
+ TALLOC_FREE(subreq);
+ if (NT_STATUS_IS_OK(status)) {
+ tevent_req_done(req);
+ return;
+ }
+ subreq = name_resolve_bcast_send(state, state->ev,
+ state->request->data.winsreq, 0x20);
+ if (tevent_req_nomem(subreq, req)) {
+ return;
+ }
+ tevent_req_set_callback(subreq, winbindd_wins_byname_bcast_done, req);
+}
+
+static void winbindd_wins_byname_bcast_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_wins_byname_state *state = tevent_req_data(
+ req, struct winbindd_wins_byname_state);
+ NTSTATUS status;
+
+ status = name_resolve_bcast_recv(subreq, talloc_tos(), &state->addrs,
+ &state->num_addrs);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_wins_byname_recv(struct tevent_req *req,
+ struct winbindd_response *presp)
+{
+ struct winbindd_wins_byname_state *state = tevent_req_data(
+ req, struct winbindd_wins_byname_state);
+ char *response;
+ NTSTATUS status;
+ size_t i;
+
+ if (tevent_req_is_nterror(req, &status)) {
+ return status;
+ }
+
+ response = talloc_strdup(talloc_tos(), "");
+ if (response == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ D_NOTICE("Winbind external command WINS_BYNAME end.\n"
+ "Received %zu address(es).\n",
+ state->num_addrs);
+ for (i=0; i<state->num_addrs; i++) {
+ char addr[INET6_ADDRSTRLEN];
+ print_sockaddr(addr, sizeof(addr), &state->addrs[i]);
+ D_NOTICE("%zu: %s\n", i, addr);
+ talloc_asprintf_addbuf(
+ &response, "%s%s", addr,
+ i < (state->num_addrs-1) ? " " : "");
+ }
+
+ talloc_asprintf_addbuf(
+ &response, "\t%s\n", state->request->data.winsreq);
+ if (response == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (talloc_get_size(response) > sizeof(presp->data.winsresp)) {
+ TALLOC_FREE(response);
+ return NT_STATUS_MARSHALL_OVERFLOW;
+ }
+ fstrcpy(presp->data.winsresp, response);
+ TALLOC_FREE(response);
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/winbindd_xids_to_sids.c b/source3/winbindd/winbindd_xids_to_sids.c
new file mode 100644
index 0000000..3420377
--- /dev/null
+++ b/source3/winbindd/winbindd_xids_to_sids.c
@@ -0,0 +1,142 @@
+/*
+ Unix SMB/CIFS implementation.
+ async implementation of WINBINDD_SIDS_TO_XIDS
+ Copyright (C) Volker Lendecke 2011
+ Copyright (C) Michael Adam 2012
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "includes.h"
+#include "winbindd.h"
+#include "../libcli/security/security.h"
+
+
+struct winbindd_xids_to_sids_state {
+ struct tevent_context *ev;
+
+ struct unixid *xids;
+ uint32_t num_xids;
+
+ struct dom_sid *sids;
+};
+
+static void winbindd_xids_to_sids_done(struct tevent_req *subreq);
+
+struct tevent_req *winbindd_xids_to_sids_send(TALLOC_CTX *mem_ctx,
+ struct tevent_context *ev,
+ struct winbindd_cli_state *cli,
+ struct winbindd_request *request)
+{
+ struct tevent_req *req, *subreq;
+ struct winbindd_xids_to_sids_state *state;
+
+ req = tevent_req_create(mem_ctx, &state,
+ struct winbindd_xids_to_sids_state);
+ if (req == NULL) {
+ return NULL;
+ }
+ state->ev = ev;
+
+ D_NOTICE("[%s (%u)] Winbind external command XIDS_TO_SIDS start.\n",
+ cli->client_name,
+ (unsigned int)cli->pid);
+
+ if (request->extra_len == 0) {
+ tevent_req_done(req);
+ return tevent_req_post(req, ev);
+ }
+ if (request->extra_data.data[request->extra_len-1] != '\0') {
+ D_DEBUG("Got invalid XID list.\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+ if (!parse_xidlist(state, request->extra_data.data,
+ &state->xids, &state->num_xids)) {
+ D_DEBUG("parse_sidlist failed\n");
+ tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
+ return tevent_req_post(req, ev);
+ }
+
+ D_DEBUG("Resolving %"PRIu32" XID(s):\n%s\n",
+ state->num_xids,
+ (char *)request->extra_data.data);
+
+ subreq = wb_xids2sids_send(state, ev, state->xids, state->num_xids);
+ if (tevent_req_nomem(subreq, req)) {
+ return tevent_req_post(req, ev);
+ }
+ tevent_req_set_callback(subreq, winbindd_xids_to_sids_done, req);
+ return req;
+}
+
+static void winbindd_xids_to_sids_done(struct tevent_req *subreq)
+{
+ struct tevent_req *req = tevent_req_callback_data(
+ subreq, struct tevent_req);
+ struct winbindd_xids_to_sids_state *state = tevent_req_data(
+ req, struct winbindd_xids_to_sids_state);
+ NTSTATUS status;
+
+ status = wb_xids2sids_recv(subreq, state, &state->sids);
+ TALLOC_FREE(subreq);
+ if (tevent_req_nterror(req, status)) {
+ return;
+ }
+ tevent_req_done(req);
+}
+
+NTSTATUS winbindd_xids_to_sids_recv(struct tevent_req *req,
+ struct winbindd_response *response)
+{
+ struct winbindd_xids_to_sids_state *state = tevent_req_data(
+ req, struct winbindd_xids_to_sids_state);
+ NTSTATUS status;
+ char *result = NULL;
+ uint32_t i;
+
+ D_NOTICE("Winbind external command XIDS_TO_SIDS end.\n");
+ if (tevent_req_is_nterror(req, &status)) {
+ D_WARNING("Could not convert xids: %s\n", nt_errstr(status));
+ return status;
+ }
+
+ result = talloc_strdup(response, "");
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i=0; i<state->num_xids; i++) {
+ struct dom_sid_buf sid_buf;
+ const char *str = "-";
+
+ if (!is_null_sid(&state->sids[i])) {
+ dom_sid_str_buf(&state->sids[i], &sid_buf);
+ str = sid_buf.buf;
+ }
+
+ D_NOTICE("%"PRIu32": XID %"PRIu32" mapped to SID %s.\n",
+ i, state->xids[i].id, str);
+ result = talloc_asprintf_append_buffer(
+ result, "%s\n", str);
+ if (result == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ response->extra_data.data = result;
+ response->length += talloc_get_size(result);
+
+ return NT_STATUS_OK;
+}
diff --git a/source3/winbindd/wscript_build b/source3/winbindd/wscript_build
new file mode 100644
index 0000000..4c85876
--- /dev/null
+++ b/source3/winbindd/wscript_build
@@ -0,0 +1,291 @@
+#!/usr/bin/env python
+
+bld.SAMBA3_LIBRARY('idmap',
+ source='idmap.c idmap_util.c',
+ deps='samba-util pdb',
+ allow_undefined_symbols=True,
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('IDMAP_RW',
+ source='idmap_rw.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('IDMAP_TDB_COMMON',
+ source='idmap_tdb_common.c',
+ deps='tdb IDMAP_RW')
+
+bld.SAMBA3_SUBSYSTEM('IDMAP_HASH',
+ source='idmap_hash/idmap_hash.c idmap_hash/mapfile.c',
+ deps='samba-util krb5samba',
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_hash'))
+
+bld.SAMBA3_SUBSYSTEM('IDMAP_AD',
+ source='idmap_ad.c idmap_ad_nss.c',
+ deps='ads nss_info',
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad'))
+
+bld.SAMBA3_MODULE('idmap_ad',
+ subsystem='idmap',
+ allow_undefined_symbols=True,
+ source='',
+ deps='IDMAP_AD TLDAP LIBNMB',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad'))
+
+bld.SAMBA3_MODULE('idmap_rfc2307',
+ subsystem='idmap',
+ allow_undefined_symbols=True,
+ source='idmap_rfc2307.c',
+ init_function='',
+ deps='ads',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_rfc2307'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_rfc2307'))
+
+bld.SAMBA3_MODULE('idmap_rid',
+ subsystem='idmap',
+ allow_undefined_symbols=True,
+ source='idmap_rid.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_rid'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_rid'))
+
+bld.SAMBA3_MODULE('idmap_passdb',
+ subsystem='idmap',
+ source='idmap_passdb.c',
+ deps='samba-util samba-passdb',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_passdb'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_passdb'))
+
+bld.SAMBA3_MODULE('idmap_ldap',
+ subsystem='idmap',
+ source='idmap_ldap.c',
+ deps='smbldap smbldaphelper pdb IDMAP_RW',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ldap'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ldap') and bld.CONFIG_SET("HAVE_LDAP"),
+ allow_undefined_symbols=True)
+
+bld.SAMBA3_MODULE('idmap_nss',
+ subsystem='idmap',
+ source='idmap_nss.c',
+ deps='samba-util',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_nss'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_nss'))
+
+bld.SAMBA3_MODULE('idmap_tdb',
+ subsystem='idmap',
+ source='idmap_tdb.c',
+ deps='samba-util tdb IDMAP_TDB_COMMON',
+ init_function='',
+ allow_undefined_symbols=True,
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_tdb'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_tdb'))
+
+bld.SAMBA3_MODULE('idmap_tdb2',
+ subsystem='idmap',
+ source='idmap_tdb2.c',
+ deps='samba-util tdb IDMAP_TDB_COMMON',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_tdb2'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_tdb2'))
+
+bld.SAMBA3_MODULE('idmap_hash',
+ subsystem='idmap',
+ source='',
+ deps='IDMAP_HASH',
+ allow_undefined_symbols=True,
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_hash'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_hash'))
+
+bld.SAMBA3_SUBSYSTEM('IDMAP_AUTORID_TDB',
+ source='idmap_autorid_tdb.c',
+ deps='tdb')
+
+bld.SAMBA3_MODULE('idmap_autorid',
+ subsystem='idmap',
+ source='idmap_autorid.c',
+ deps='samba-util tdb IDMAP_TDB_COMMON IDMAP_AUTORID_TDB',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_autorid'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_autorid'),
+ allow_undefined_symbols=True)
+
+bld.SAMBA3_LIBRARY('nss_info',
+ source='nss_info.c',
+ deps='samba-util smbconf samba-modules',
+ private_library=True)
+
+bld.SAMBA3_MODULE('nss_info_template',
+ subsystem='nss_info',
+ source='nss_info_template.c',
+ deps='samba-util krb5samba',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('nss_info_template'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('nss_info_template'))
+
+bld.SAMBA3_MODULE('nss_info_hash',
+ subsystem='nss_info',
+ source='',
+ deps='IDMAP_HASH',
+ allow_undefined_symbols=True,
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_hash'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_hash'))
+
+bld.SAMBA3_MODULE('nss_info_rfc2307',
+ subsystem='nss_info',
+ source='',
+ deps='IDMAP_AD',
+ allow_undefined_symbols=True,
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad'))
+
+bld.SAMBA3_MODULE('nss_info_sfu20',
+ subsystem='nss_info',
+ source='',
+ deps='IDMAP_AD',
+ allow_undefined_symbols=True,
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad'))
+
+bld.SAMBA3_MODULE('nss_info_sfu',
+ subsystem='nss_info',
+ source='',
+ deps='IDMAP_AD',
+ allow_undefined_symbols=True,
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_ad'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_ad') and bld.CONFIG_SET("HAVE_LDAP"))
+
+bld.SAMBA3_MODULE('idmap_script',
+ subsystem='idmap',
+ allow_undefined_symbols=True,
+ source='idmap_script.c',
+ init_function='',
+ internal_module=bld.SAMBA3_IS_STATIC_MODULE('idmap_script'),
+ enabled=bld.SAMBA3_IS_ENABLED_MODULE('idmap_script'))
+
+bld.SAMBA3_SUBSYSTEM('winbindd-lib',
+ source='''
+ winbindd_group.c
+ winbindd_util.c
+ winbindd_cache.c
+ winbindd_pam.c
+ winbindd_misc.c
+ winbindd_cm.c
+ winbindd_wins_byip.c
+ winbindd_wins_byname.c
+ winbindd_msrpc.c
+ winbindd_rpc.c
+ winbindd_reconnect.c
+ winbindd_reconnect_ads.c
+ winbindd_ads.c
+ winbindd_samr.c
+ winbindd_dual.c
+ winbindd_dual_ndr.c
+ winbindd_dual_srv.c
+ winbindd_creds.c
+ winbindd_cred_cache.c
+ winbindd_ccache_access.c
+ winbindd_domain.c
+ winbindd_idmap.c
+ winbindd_locator.c
+ winbindd_ndr.c
+ winbindd_traceid.c
+ wb_lookupsid.c
+ wb_lookupsids.c
+ wb_lookupname.c
+ wb_sids2xids.c
+ wb_xids2sids.c
+ wb_queryuser.c
+ wb_lookupuseraliases.c
+ wb_lookupusergroups.c
+ wb_getpwsid.c
+ wb_gettoken.c
+ wb_seqnum.c
+ wb_seqnums.c
+ wb_group_members.c
+ wb_alias_members.c
+ wb_getgrsid.c
+ wb_query_user_list.c
+ wb_query_group_list.c
+ wb_next_pwent.c
+ wb_next_grent.c
+ wb_dsgetdcname.c
+ winbindd_lookupsid.c
+ winbindd_lookupsids.c
+ winbindd_lookupname.c
+ winbindd_sids_to_xids.c
+ winbindd_xids_to_sids.c
+ winbindd_allocate_uid.c
+ winbindd_allocate_gid.c
+ winbindd_getpwsid.c
+ winbindd_getpwnam.c
+ winbindd_getpwuid.c
+ winbindd_getsidaliases.c
+ winbindd_getuserdomgroups.c
+ winbindd_getgroups.c
+ winbindd_show_sequence.c
+ winbindd_getgrgid.c
+ winbindd_getgrnam.c
+ winbindd_getusersids.c
+ winbindd_lookuprids.c
+ winbindd_setpwent.c
+ winbindd_getpwent.c
+ winbindd_endpwent.c
+ winbindd_setgrent.c
+ winbindd_getgrent.c
+ winbindd_endgrent.c
+ winbindd_dsgetdcname.c
+ winbindd_getdcname.c
+ winbindd_list_users.c
+ winbindd_list_groups.c
+ winbindd_check_machine_acct.c
+ winbindd_change_machine_acct.c
+ winbindd_irpc.c
+ winbindd_ping_dc.c
+ winbindd_domain_info.c
+ winbindd_pam_auth.c
+ winbindd_pam_logoff.c
+ winbindd_pam_chauthtok.c
+ winbindd_pam_auth_crap.c
+ winbindd_pam_chng_pswd_auth_crap.c
+ winbindd_gpupdate.c''',
+ deps='''
+ talloc
+ tevent
+ pdb
+ idmap
+ ads
+ msrpc3
+ nss_info
+ LIBAFS
+ LIBADS_SERVER
+ LIBCLI_SAMR
+ SLCACHE
+ RPC_NDR_DSSETUP
+ RPC_NDR_WINBIND
+ TOKEN_UTIL
+ RPC_SERVER
+ WB_REQTRANS
+ TDB_VALIDATE
+ MESSAGING
+ LIBLSA
+ ''')
+
+bld.SAMBA3_BINARY('winbindd',
+ source='''
+ winbindd.c
+ ''',
+ deps='''
+ CMDLINE_S3
+ winbindd-lib
+ ''',
+ enabled=bld.env.build_winbind,
+ install_path='${SBINDIR}')
diff --git a/source3/wscript b/source3/wscript
new file mode 100644
index 0000000..83aeb76
--- /dev/null
+++ b/source3/wscript
@@ -0,0 +1,2109 @@
+#!/usr/bin/env python
+
+srcdir = ".."
+
+import sys, os
+from optparse import SUPPRESS_HELP
+sys.path.insert(0, srcdir + "/buildtools/wafsamba")
+sys.path.insert(0, "source3")
+
+from waflib import Options, Logs, Errors
+import wafsamba
+import build.charset
+from wafsamba import samba_utils
+from samba_utils import TO_LIST
+import samba3
+
+default_prefix = Options.default_prefix = '/usr/local/samba'
+
+def options(opt):
+
+ opt.add_option('--with-static-modules',
+ help=("Comma-separated list of names of modules to statically link in. "+
+ "May include !module to disable 'module'. "+
+ "Can be '!FORCED' to disable all non-required static only modules. "+
+ "Can be '!DEFAULT' to disable all modules defaulting to a static build. "+
+ "Can be 'ALL' to build all default shared modules static. "+
+ "The most specific one wins, while the order is ignored "+
+ "and --with-static-modules is evaluated before "+
+ "--with-shared-modules"),
+ action="store", dest='static_modules', default=None)
+ opt.add_option('--with-shared-modules',
+ help=("Comma-separated list of names of modules to build shared. "+
+ "May include !module to disable 'module'. "+
+ "Can be '!FORCED' to disable all non-required shared only modules. "+
+ "Can be '!DEFAULT' to disable all modules defaulting to a shared build. "+
+ "Can be 'ALL' to build all default static modules shared. "+
+ "The most specific one wins, while the order is ignored "+
+ "and --with-static-modules is evaluated before "+
+ "--with-shared-modules"),
+ action="store", dest='shared_modules', default=None)
+
+# Optional Libraries
+# ------------------
+#
+# Most of the calls to opt.samba_add_onoff_option() implicitly
+# or explicitly use default=True
+#
+# To assist users and distributors to build Samba with the full feature
+# set, the build system will abort if our dependent libraries and their
+# header files are not found on the target system. This will mean for
+# example, that xattr, acl and ldap headers must be installed for the
+# default build to complete. The configure system will check for these
+# headers, and the error message will indicate the option (such as
+# --without-acl-support) that can be specified to skip this requirement.
+#
+# This will assist users and in particular distributors in building fully
+# functional packages, while allowing those on systems truly without these
+# facilities to continue to build Samba after careful consideration.
+#
+# It also ensures our container image generation in bootstrap/ is correct
+# as otherwise a missing package there would just silently work
+
+ opt.samba_add_onoff_option('winbind')
+ opt.samba_add_onoff_option('ads')
+ opt.samba_add_onoff_option('ldap')
+ opt.samba_add_onoff_option('cups', with_name="enable", without_name="disable")
+ opt.samba_add_onoff_option('iprint', with_name="enable", without_name="disable")
+ opt.samba_add_onoff_option('pam')
+ opt.samba_add_onoff_option('quotas', default=None)
+ opt.samba_add_onoff_option('sendfile-support', default=None)
+ opt.samba_add_onoff_option('utmp')
+ opt.samba_add_onoff_option('avahi', with_name="enable", without_name="disable")
+ opt.samba_add_onoff_option('iconv')
+ opt.samba_add_onoff_option('acl-support')
+ opt.samba_add_onoff_option('syslog')
+ opt.samba_add_onoff_option('automount')
+ opt.samba_add_onoff_option('dmapi', default=None) # None means autodetection
+ opt.samba_add_onoff_option('fam', default=None) # None means autodetection
+ opt.samba_add_onoff_option('profiling-data', default=False)
+ opt.samba_add_onoff_option('libarchive', default=True)
+
+ opt.samba_add_onoff_option('cluster-support', default=False)
+
+ opt.samba_add_onoff_option('regedit', default=None)
+ opt.samba_add_onoff_option('winexe', default=None)
+
+ opt.samba_add_onoff_option('fake-kaserver',
+ help=("Include AFS fake-kaserver support"), default=False)
+
+ opt.add_option('--with-libcephfs',
+ help=("Directory under which libcephfs is installed"),
+ action="store", dest='libcephfs_dir', default=None)
+
+ opt.samba_add_onoff_option('glusterfs', with_name="enable", without_name="disable", default=True)
+ opt.samba_add_onoff_option('cephfs', with_name="enable", without_name="disable", default=True)
+
+ opt.add_option('--enable-vxfs',
+ help=("enable support for VxFS (default=no)"),
+ action="store_true", dest='enable_vxfs', default=False)
+
+ # default = None means autodetection
+ opt.samba_add_onoff_option('spotlight', with_name="enable", without_name="disable", default=None)
+ opt.samba_add_onoff_option('wsp', with_name="enable", without_name="disable", default=True)
+
+def configure(conf):
+ default_static_modules = []
+ default_shared_modules = []
+ required_static_modules = []
+ forced_static_modules = []
+ forced_shared_modules = []
+
+ if sys.platform != 'openbsd5':
+ conf.ADD_LDFLAGS("-Wl,--export-dynamic", testflags=True)
+
+ # We crash without vfs_default
+ # and vfs_not_implemented provides helper function
+ # for other modules
+ required_static_modules.extend(['vfs_default', 'vfs_not_implemented'])
+
+ conf.CHECK_HEADERS('netdb.h')
+ conf.CHECK_HEADERS('linux/falloc.h linux/ioctl.h')
+
+ conf.CHECK_FUNCS('getcwd fchown chmod fchmod mknod mknodat')
+ conf.CHECK_FUNCS('strtol strchr strupr chflags fchflags')
+ conf.CHECK_FUNCS('getrlimit fsync setpgid')
+ conf.CHECK_FUNCS('setsid glob strpbrk crypt16 getauthuid')
+ conf.CHECK_FUNCS('innetgr')
+ conf.CHECK_FUNCS('initgroups select poll rdchk getgrnam getgrent pathconf')
+ conf.CHECK_FUNCS('setpriv setgidx setuidx setgroups syscall sysconf')
+ conf.CHECK_FUNCS('atexit grantpt posix_openpt fallocate')
+ conf.CHECK_FUNCS('fseeko setluid')
+ conf.CHECK_FUNCS('getpwnam', headers='sys/types.h pwd.h')
+ conf.CHECK_FUNCS('fdopendir')
+ conf.CHECK_FUNCS('getpwent_r setenv clearenv strcasecmp')
+ conf.CHECK_FUNCS('syslog vsyslog timegm setlocale')
+ conf.CHECK_FUNCS('lutimes utimensat futimens')
+ conf.CHECK_FUNCS('mlock munlock mlockall munlockall')
+ conf.CHECK_FUNCS('memalign posix_memalign hstrerror')
+ conf.CHECK_FUNCS('getdomainname')
+ conf.CHECK_FUNCS_IN('dn_expand _dn_expand __dn_expand', 'resolv')
+ conf.CHECK_FUNCS_IN('dn_expand', 'inet')
+ conf.CHECK_DECLS('readahead', reverse=True, headers='fcntl.h')
+
+ if conf.CHECK_CODE('''
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+long ret = splice(0,0,1,0,400,SPLICE_F_MOVE);
+''',
+ 'HAVE_LINUX_SPLICE',
+ headers='fcntl.h'):
+ conf.CHECK_DECLS('splice', reverse=True, headers='fcntl.h')
+
+ # Check for inotify support (Skip if we are SunOS)
+ #NOTE: illumos provides sys/inotify.h but is not an exact match for linux
+ host_os = sys.platform
+ if host_os.rfind('sunos') == -1:
+ conf.CHECK_HEADERS('sys/inotify.h')
+ if conf.env.HAVE_SYS_INOTIFY_H:
+ conf.DEFINE('HAVE_INOTIFY', 1)
+
+ # Check for Linux kernel oplocks
+ if conf.CHECK_DECLS('F_SETLEASE', headers='linux/fcntl.h', reverse=True):
+ conf.DEFINE('HAVE_KERNEL_OPLOCKS_LINUX', 1)
+
+ # check for fam libs
+ samba_fam_libs=None
+ check_for_fam=False
+ if Options.options.with_fam is None:
+ check_for_fam=True
+ elif Options.options.with_fam == True:
+ check_for_fam=True
+
+ if check_for_fam and conf.CHECK_HEADERS('fam.h'):
+ if conf.CHECK_FUNCS_IN('FAMOpen2', 'fam'):
+ samba_fam_libs='fam'
+ elif conf.CHECK_FUNCS_IN('FAMOpen2', 'fam C'):
+ samba_fam_libs='fam C'
+ conf.CHECK_TYPE('enum FAMCodes', headers='fam.h',
+ define='HAVE_FAM_H_FAMCODES_TYPEDEF',
+ msg='Checking whether enum FAMCodes is available')
+ conf.CHECK_FUNCS_IN('FAMNoExists', 'fam')
+
+ if samba_fam_libs is not None:
+ conf.DEFINE('SAMBA_FAM_LIBS', samba_fam_libs)
+ conf.DEFINE('HAVE_FAM', 1)
+ else:
+ if Options.options.with_fam == True:
+ conf.fatal('FAM support requested, but no suitable FAM library found')
+ elif check_for_fam:
+ Logs.warn('no suitable FAM library found')
+
+ # check for libarchive (tar command in smbclient)
+ # None means autodetect, True/False means enable/disable
+ conf.SET_TARGET_TYPE('archive', 'EMPTY')
+ if Options.options.with_libarchive is not False:
+ Logs.info("Checking for libarchive existence")
+ if conf.CHECK_HEADERS('archive.h') and conf.CHECK_LIB('archive', shlib=True):
+ conf.CHECK_FUNCS_IN('archive_read_support_filter_all archive_read_free', 'archive')
+ else:
+ conf.fatal("libarchive support not found. "
+ "Try installing libarchive-dev or libarchive-devel. "
+ "Otherwise, use --without-libarchive to "
+ "build without libarchive support. "
+ "libarchive support is required for the smbclient "
+ "tar-file mode")
+ elif conf.CONFIG_GET('ENABLE_SELFTEST'):
+ raise Errors.WafError('libarchive library required for '
+ '--enable-selftest')
+
+
+ # check for DMAPI libs
+ if Options.options.with_dmapi == False:
+ have_dmapi = False
+ else:
+ have_dmapi = True
+ Logs.info("Checking for DMAPI library existence")
+ samba_dmapi_lib = ''
+ if conf.CHECK_FUNCS_IN('dm_get_eventlist', 'dm'):
+ samba_dmapi_lib = 'dm'
+ else:
+ if conf.CHECK_FUNCS_IN('dm_get_eventlist', 'jfsdm'):
+ samba_dmapi_lib = 'jfsdm'
+ else:
+ if conf.CHECK_FUNCS_IN('dm_get_eventlist', 'dmapi'):
+ samba_dmapi_lib = 'dmapi'
+ else:
+ if conf.CHECK_FUNCS_IN('dm_get_eventlist', 'xdsm'):
+ samba_dmapi_lib = 'xdsm'
+ # only bother to test headers and compilation when a candidate
+ # library has been found
+ if samba_dmapi_lib == '':
+ have_dmapi = False
+ broken_dmapi = "no suitable DMAPI library found"
+
+ if have_dmapi:
+ conf.CHECK_HEADERS('sys/dmi.h xfs/dmapi.h sys/jfsdmapi.h sys/dmapi.h dmapi.h')
+ conf.CHECK_CODE('''
+#include <time.h> /* needed by Tru64 */
+#include <sys/types.h> /* needed by AIX */
+#ifdef HAVE_XFS_DMAPI_H
+#include <xfs/dmapi.h>
+#elif defined(HAVE_SYS_DMI_H)
+#include <sys/dmi.h>
+#elif defined(HAVE_SYS_JFSDMAPI_H)
+#include <sys/jfsdmapi.h>
+#elif defined(HAVE_SYS_DMAPI_H)
+#include <sys/dmapi.h>
+#elif defined(HAVE_DMAPI_H)
+#include <dmapi.h>
+#endif
+
+/* This link test is designed to fail on IRI 6.4, but should
+ * succeed on Linux, IRIX 6.5 and AIX.
+ */
+int main(int argc, char **argv)
+{
+ char * version;
+ dm_eventset_t events;
+ /* This doesn't take an argument on IRIX 6.4. */
+ dm_init_service(&version);
+ /* IRIX 6.4 expects events to be a pointer. */
+ DMEV_ISSET(DM_EVENT_READ, events);
+
+ return 0;
+}
+''',
+ 'USEABLE_DMAPI_LIBRARY',
+ addmain=False,
+ execute=False,
+ lib=samba_dmapi_lib,
+ msg='Checking whether DMAPI lib '+samba_dmapi_lib+' can be used')
+ if not conf.CONFIG_SET('USEABLE_DMAPI_LIBRARY'):
+ have_dmapi = False
+ broken_dmapi = "no usable DMAPI library found"
+
+ if have_dmapi:
+ Logs.info("Building with DMAPI support.")
+ conf.env['dmapi_lib'] = samba_dmapi_lib
+ conf.DEFINE('USE_DMAPI', 1)
+ else:
+ if Options.options.with_dmapi == False:
+ Logs.info("Building without DMAPI support (--without-dmapi).")
+ elif Options.options.with_dmapi == True:
+ Logs.error("DMAPI support not available: " + broken_dmapi)
+ conf.fatal('DMAPI support requested but not found.');
+ else:
+ Logs.warn("Building without DMAPI support: " + broken_dmapi)
+ conf.env['dmapi_lib'] = ''
+
+ # Check for various members of the stat structure
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_blocks', define='HAVE_STAT_ST_BLOCKS',
+ headers='sys/stat.h')
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_blksize', define='HAVE_STAT_ST_BLKSIZE',
+ headers='sys/stat.h')
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_flags', define='HAVE_STAT_ST_FLAGS',
+ headers='sys/types.h sys/stat.h unistd.h')
+
+ if conf.env.HAVE_BLKCNT_T:
+ conf.CHECK_CODE('''
+ static int test_array[1 - 2 * !(((long int)(sizeof(blkcnt_t))) <= 4)];''',
+ 'SIZEOF_BLKCNT_T_4',
+ headers='replace.h sys/types.h sys/stat.h unistd.h',
+ msg="Checking whether blkcnt_t is 32 bit")
+
+ # If sizeof is 4 it can't be 8
+ if conf.env.HAVE_BLKCNT_T:
+ if not conf.CONFIG_SET('SIZEOF_BLKCNT_T_4'):
+ conf.CHECK_CODE('''
+ static int test_array[1 - 2 * !(((long int)(sizeof(blkcnt_t))) <= 8)];''',
+ 'SIZEOF_BLKCNT_T_8',
+ headers='replace.h sys/types.h sys/stat.h unistd.h',
+ msg="Checking whether blkcnt_t is 64 bit")
+
+ # Check for POSIX capability support
+ conf.CHECK_FUNCS_IN('cap_get_proc', 'cap', headers='sys/capability.h')
+
+ if conf.env.HAVE_SYS_CAPABILITY_H:
+ conf.CHECK_CODE('''
+ cap_t cap;
+ cap_value_t vals[1];
+ if (!(cap = cap_get_proc())) exit(1);
+ vals[0] = CAP_CHOWN;
+ cap_set_flag(cap, CAP_INHERITABLE, 1, vals, CAP_CLEAR);
+ cap_set_proc(cap);''',
+ 'HAVE_POSIX_CAPABILITIES', execute=True, lib="cap",
+ headers='sys/capability.h',
+ msg="Checking whether POSIX capabilities are available")
+
+ conf.CHECK_CODE('int i;', 'BROKEN_NISPLUS_INCLUDE_FILES',
+ headers='sys/types.h sys/acl.h rpcsvc/nis.h',
+ msg="Checking for broken nisplus include files")
+
+ # Check if the compiler will optimize out functions
+ conf.CHECK_CODE('''
+#include <sys/types.h>
+size_t __unsafe_string_function_usage_here_size_t__(void);
+#define CHECK_STRING_SIZE(d, len) (sizeof(d) != (len) && sizeof(d) != sizeof(char *))
+static size_t push_string_check_fn(void *dest, const char *src, size_t dest_len) {
+ return 0;
+}
+
+#define push_string_check(dest, src, dest_len) \
+ (CHECK_STRING_SIZE(dest, dest_len) \
+ ? __unsafe_string_function_usage_here_size_t__() \
+ : push_string_check_fn(dest, src, dest_len))
+
+int main(int argc, char **argv) {
+ char outbuf[1024];
+ char *p = outbuf;
+ const char *foo = "bar";
+ p += 31 + push_string_check(p + 31, foo, sizeof(outbuf) - (p + 31 - outbuf));
+ return 0;
+}''', 'HAVE_COMPILER_WILL_OPTIMIZE_OUT_FNS',
+ addmain=False,
+ add_headers=False,
+ msg="Checking if the compiler will optimize out functions")
+
+ # Check if the compiler supports the LL suffix on long long integers
+ # AIX needs this
+ conf.CHECK_CODE('long long i = 0x8000000000LL', 'COMPILER_SUPPORTS_LL',
+ headers='stdio.h',
+ msg="Checking for LL suffix on long long integers")
+
+ conf.CHECK_FUNCS('''
+DNSServiceRegister
+atexit
+chflags
+fchflags
+chmod
+crypt16
+devnm
+endmntent
+execl
+fchmod
+fchown
+fseeko
+fsync
+futimens
+getauthuid
+getcwd
+getgrent
+getgrnam
+getgrouplist
+getgrset
+getmntent
+getpagesize
+getpwanam
+getpwent_r
+getrlimit
+glob
+grantpt
+hstrerror
+initgroups
+innetgr
+llseek
+lutimes
+memalign
+mknod
+mlock
+mlockall
+munlock
+munlockall
+pathconf poll
+posix_memalign
+pread
+pwrite
+rdchk
+select
+setenv
+setgidx
+setgroups
+setlocale
+setluid
+setmntent
+setpgid
+setpriv
+setsid
+setuidx
+statvfs
+strcasecmp
+strchr
+strpbrk
+strsignal
+strtol
+strupr
+sysconf
+sysctl
+sysctlbyname
+syslog
+timegm
+utimensat
+vsyslog
+''')
+
+ conf.CHECK_SAMBA3_CHARSET() # see build/charset.py
+
+ # FIXME: these should be tests for features, but the old build system just
+ # checks for OSes.
+ host_os = sys.platform
+ Logs.info("building on %s" % host_os)
+
+ # Python doesn't have case switches... :/
+ # FIXME: original was *linux* | gnu* | k*bsd*-gnu | kopensolaris*-gnu | *qnx*)
+ # the search for .rfind('gnu') covers gnu* and *-gnu is that too broad?
+
+ conf.SET_TARGET_TYPE('sunacl', 'EMPTY')
+ if (host_os.rfind('linux') > -1) or (host_os.rfind('gnu') > -1) or (host_os.rfind('qnx') > -1):
+ if host_os.rfind('linux') > -1:
+ conf.DEFINE('LINUX', '1')
+ elif host_os.rfind('qnx') > -1:
+ conf.DEFINE('QNX', '1')
+ conf.DEFINE('STAT_ST_BLOCKSIZE', '512')
+ elif (host_os.rfind('darwin') > -1):
+ conf.DEFINE('DARWINOS', 1)
+ conf.ADD_CFLAGS('-fno-common')
+ conf.DEFINE('STAT_ST_BLOCKSIZE', '512')
+ elif (host_os.rfind('freebsd') > -1):
+ conf.DEFINE('FREEBSD', 1)
+ if conf.CHECK_HEADERS('sunacl.h'):
+ conf.DEFINE('HAVE_FREEBSD_SUNACL_H', '1')
+ conf.CHECK_FUNCS_IN(['acl'], 'sunacl')
+ conf.DEFINE('STAT_ST_BLOCKSIZE', '512')
+ elif (host_os.rfind('irix') > -1):
+ conf.DEFINE('IRIX', 1)
+ conf.DEFINE('STAT_ST_BLOCKSIZE', '512')
+ elif (host_os.rfind('aix') > -1):
+ conf.DEFINE('AIX', 1)
+ conf.DEFINE('STAT_ST_BLOCKSIZE', 'DEV_BSIZE')
+ elif (host_os.rfind('hpux') > -1):
+ conf.DEFINE('HPUX', 1)
+ conf.DEFINE('STAT_ST_BLOCKSIZE', '8192')
+ elif (host_os.rfind('osf') > -1):
+ conf.DEFINE('OSF1', 1)
+ conf.DEFINE('STAT_ST_BLOCKSIZE', '512')
+
+ # FIXME: Add more checks here.
+ else:
+ conf.DEFINE('STAT_ST_BLOCKSIZE', '512')
+
+ if Options.options.with_acl_support:
+ if (host_os.rfind('hpux') > -1):
+ Logs.info('Using HPUX ACLs')
+ conf.DEFINE('HAVE_HPUX_ACLS',1)
+ conf.DEFINE('POSIX_ACL_NEEDS_MASK',1)
+ required_static_modules.extend(['vfs_hpuxacl'])
+ elif (host_os.rfind('aix') > -1):
+ Logs.info('Using AIX ACLs')
+ conf.DEFINE('HAVE_AIX_ACLS',1)
+ required_static_modules.extend(['vfs_aixacl', 'vfs_aixacl2'])
+ elif (host_os.rfind('darwin') > -1):
+ Logs.warn('ACLs on Darwin currently not supported')
+ conf.fatal("ACL support not available on Darwin/MacOS. "
+ "Use --without-acl-support for building without "
+ "ACL support. "
+ "ACL support is required to change permissions "
+ "from Windows clients.")
+ else:
+ conf.CHECK_FUNCS_IN(['acl_get_file'], 'acl')
+ if conf.CHECK_CODE('''
+acl_t acl;
+int entry_id;
+acl_entry_t *entry_p;
+return acl_get_entry(acl, entry_id, entry_p);
+''',
+ 'HAVE_POSIX_ACLS',
+ headers='sys/types.h sys/acl.h', link=False,
+ msg="Checking for POSIX ACL support") :
+ conf.CHECK_CODE('''
+acl_permset_t permset_d;
+acl_perm_t perm;
+return acl_get_perm_np(permset_d, perm);
+''',
+ 'HAVE_ACL_GET_PERM_NP',
+ headers='sys/types.h sys/acl.h', link=True,
+ msg="Checking whether acl_get_perm_np() is available")
+ # source3/lib/sysacls.c calls posixacl_sys_acl_get_file()
+ required_static_modules.extend(['vfs_posixacl'])
+ conf.CHECK_VARIABLE('ACL_EVERYONE', headers='sys/acl.h')
+ elif conf.CHECK_FUNCS_IN(['facl'], 'sec'):
+ Logs.info('Using solaris or UnixWare ACLs')
+ conf.DEFINE('HAVE_SOLARIS_UNIXWARE_ACLS',1)
+ required_static_modules.extend(['vfs_solarisacl'])
+ else:
+ conf.fatal("ACL support not found. Try installing libacl1-dev "
+ "or libacl-devel. "
+ "Otherwise, use --without-acl-support to build "
+ "without ACL support. "
+ "ACL support is required to change permissions from "
+ "Windows clients.")
+
+ if conf.CHECK_FUNCS('dirfd'):
+ conf.DEFINE('HAVE_DIRFD_DECL', 1)
+
+ conf.CHECK_CODE('struct statfs fsd; fsid_t fsid = fsd.f_fsid; return statfs(".", &fsd);',
+ 'HAVE_STATFS_F_FSID',
+ msg="vfs_fileid checking for statfs() and struct statfs.f_fsid",
+ headers='sys/types.h sys/statfs.h',
+ execute=True)
+
+ if conf.CONFIG_SET('HAVE_FALLOCATE'):
+ conf.CHECK_CODE('''
+ int ret = fallocate(0, FALLOC_FL_KEEP_SIZE, 0, 10);''',
+ 'HAVE_LINUX_FALLOCATE',
+ msg="Checking whether the Linux 'fallocate' function is available",
+ headers='unistd.h sys/types.h fcntl.h linux/falloc.h')
+ conf.CHECK_CODE('''
+ int ret = fallocate(0, FALLOC_FL_PUNCH_HOLE, 0, 10);''',
+ 'HAVE_FALLOC_FL_PUNCH_HOLE',
+ msg="Checking whether Linux 'fallocate' supports hole-punching",
+ headers='unistd.h sys/types.h fcntl.h linux/falloc.h')
+
+ conf.CHECK_CODE('''
+ int ret = lseek(0, 0, SEEK_HOLE);
+ ret = lseek(0, 0, SEEK_DATA);''',
+ 'HAVE_LSEEK_HOLE_DATA',
+ msg="Checking whether lseek supports hole/data seeking",
+ headers='unistd.h sys/types.h')
+
+ conf.CHECK_CODE('''
+ ssize_t err = readahead(0,0,0x80000);''',
+ 'HAVE_LINUX_READAHEAD',
+ msg="Checking whether Linux readahead is available",
+ headers='unistd.h fcntl.h')
+ conf.CHECK_DECLS('readahead', headers='fcntl.h', always=True)
+
+ conf.CHECK_CODE('int fd = openat(AT_FDCWD, ".", O_RDONLY);',
+ 'HAVE_OPENAT',
+ msg='Checking for openat',
+ headers='fcntl.h')
+
+ conf.CHECK_CODE('''
+struct msghdr msg;
+union {
+ struct cmsghdr cm;
+ char control[CMSG_SPACE(sizeof(int))];
+} control_un;
+msg.msg_control = control_un.control;
+msg.msg_controllen = sizeof(control_un.control);
+''',
+ 'HAVE_STRUCT_MSGHDR_MSG_CONTROL',
+ msg='Checking if we can use msg_control for passing file descriptors',
+ headers='sys/types.h stdlib.h stddef.h sys/socket.h sys/un.h')
+ conf.CHECK_CODE('''
+struct msghdr msg;
+int fd;
+msg.msg_accrights = (caddr_t) &fd;
+msg.msg_accrightslen = sizeof(fd);
+''',
+ 'HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS',
+ msg='Checking if we can use msg_accrights for passing file descriptors',
+ headers='sys/types.h stdlib.h stddef.h sys/socket.h sys/un.h')
+
+ if Options.options.with_winbind:
+ conf.env.build_winbind = True
+ conf.DEFINE('WITH_WINBIND', '1')
+
+ conf.find_program('awk', var='AWK')
+
+ conf.CHECK_HEADERS('asm/types.h')
+
+ conf.CHECK_CODE('dev_t dev; int i = major(dev); return 0', "HAVE_DEVICE_MAJOR_FN",
+ headers='sys/sysmacros.h unistd.h sys/types.h',
+ msg="Checking for major macro")
+
+ conf.CHECK_CODE('dev_t dev; int i = minor(dev); return 0', "HAVE_DEVICE_MINOR_FN",
+ headers='sys/sysmacros.h unistd.h sys/types.h',
+ msg="Checking for minor macro")
+
+ conf.CHECK_STRUCTURE_MEMBER('struct dirent', 'd_off',
+ headers='unistd.h sys/types.h dirent.h',
+ define='HAVE_DIRENT_D_OFF')
+
+ if (conf.CONFIG_SET('HAVE_GETDOMAINNAME')):
+ conf.DEFINE('HAVE_NETGROUP', '1')
+
+ # Look for CUPS
+ if Options.options.with_cups:
+ conf.find_program('cups-config', var='CUPS_CONFIG')
+ if conf.env.CUPS_CONFIG:
+ # we would normally use --libs here, but cups-config incorrectly adds
+ # gssapi_krb5 and other libraries to its --libs output. That breaks the use
+ # of an in-tree heimdal kerberos
+ conf.CHECK_CFG(path=conf.env.CUPS_CONFIG, args="--cflags --ldflags",
+ package="", uselib_store="CUPS")
+ conf.CHECK_HEADERS('cups/cups.h cups/language.h', lib='cups')
+ conf.CHECK_FUNCS_IN('httpConnect httpConnect2 httpConnectEncrypt', 'cups')
+ if conf.CONFIG_SET('HAVE_CUPS_CUPS_H') and conf.CONFIG_SET('HAVE_CUPS_LANGUAGE_H'):
+ conf.DEFINE('HAVE_CUPS', '1')
+ else:
+ conf.undefine('HAVE_CUPS')
+ conf.SET_TARGET_TYPE('cups', 'EMPTY')
+ else:
+ # define an empty subsystem for cups, to allow it to be used as an empty dependency
+ conf.SET_TARGET_TYPE('cups', 'EMPTY')
+
+ if Options.options.with_iprint:
+ if conf.CONFIG_SET('HAVE_CUPS'):
+ conf.DEFINE('HAVE_IPRINT', '1')
+ else:
+ Logs.warn("--enable-iprint=yes but cups support not sufficient")
+ if Options.options.with_syslog:
+ conf.DEFINE('WITH_SYSLOG', '1')
+ if Options.options.with_automount:
+ conf.DEFINE('WITH_AUTOMOUNT', '1')
+
+ # Check for LDAP
+ if Options.options.with_ldap:
+ conf.CHECK_HEADERS('ldap.h lber.h ldap_pvt.h')
+ conf.CHECK_TYPE('ber_tag_t', 'unsigned int', headers='ldap.h lber.h')
+ conf.CHECK_FUNCS_IN('ber_scanf ber_sockbuf_add_io', 'lber')
+ conf.CHECK_VARIABLE('LDAP_OPT_SOCKBUF', headers='ldap.h')
+
+ # if we LBER_OPT_LOG_PRINT_FN we can intercept ldap logging and print it out
+ # for the samba logs
+ conf.CHECK_VARIABLE('LBER_OPT_LOG_PRINT_FN',
+ define='HAVE_LBER_LOG_PRINT_FN', headers='lber.h')
+
+ conf.CHECK_FUNCS_IN('ldap_init ldap_init_fd ldap_initialize ldap_set_rebind_proc', 'ldap')
+ conf.CHECK_FUNCS_IN('ldap_add_result_entry', 'ldap')
+
+ # Check if ldap_set_rebind_proc() takes three arguments
+ if conf.CHECK_CODE('ldap_set_rebind_proc(0, 0, 0)',
+ 'LDAP_SET_REBIND_PROC_ARGS',
+ msg="Checking whether ldap_set_rebind_proc takes 3 arguments",
+ headers='ldap.h lber.h', link=False):
+ conf.DEFINE('LDAP_SET_REBIND_PROC_ARGS', '3')
+ else:
+ conf.DEFINE('LDAP_SET_REBIND_PROC_ARGS', '2')
+
+ # last but not least, if ldap_init() exists, we want to use ldap
+ if conf.CONFIG_SET('HAVE_LDAP_INIT') and conf.CONFIG_SET('HAVE_LDAP_H'):
+ conf.DEFINE('HAVE_LDAP', '1')
+ conf.DEFINE('LDAP_DEPRECATED', '1')
+ conf.env['HAVE_LDAP'] = '1'
+ # if ber_sockbuf_add_io() and LDAP_OPT_SOCKBUF are available, we can add
+ # SASL wrapping hooks
+ if conf.CONFIG_SET('HAVE_BER_SOCKBUF_ADD_IO') and \
+ conf.CONFIG_SET('HAVE_LDAP_OPT_SOCKBUF'):
+ conf.DEFINE('HAVE_LDAP_SASL_WRAPPING', '1')
+ else:
+ conf.fatal("LDAP support not found. "
+ "Try installing libldap2-dev or openldap-devel. "
+ "Otherwise, use --without-ldap to build without "
+ "LDAP support. "
+ "LDAP support is required for the LDAP passdb backend, "
+ "LDAP idmap backends and ADS. "
+ "ADS support improves communication with "
+ "Active Directory domain controllers.")
+ else:
+ conf.SET_TARGET_TYPE('ldap', 'EMPTY')
+ conf.SET_TARGET_TYPE('lber', 'EMPTY')
+
+ if Options.options.with_ads == False:
+ use_ads = False
+ use_ads_krb5 = False
+ use_ads_ldap = False
+ else:
+ use_ads = True
+ use_ads_krb5 = True
+ use_ads_ldap = True
+ if not conf.CONFIG_SET('HAVE_ENCTYPE_ARCFOUR_HMAC_MD5') and \
+ not conf.CONFIG_SET('HAVE_ENCTYPE_ARCFOUR_HMAC'):
+ Logs.warn("arcfour-hmac-md5 encryption type not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_MK_REQ_EXTENDED'):
+ Logs.warn("krb5_mk_req_extended not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_GET_HOST_REALM'):
+ Logs.warn("krb5_get_host_realm not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_FREE_HOST_REALM'):
+ Logs.warn("krb5_free_host_realm not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_FWD_TGT_CREDS'):
+ Logs.warn("krb5_fwd_tgt_creds found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC'):
+ Logs.warn("krb5_get_init_creds_opt_alloc not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('KRB5_CREDS_OPT_FREE_REQUIRES_CONTEXT'):
+ Logs.warn("krb5_get_init_creds_opt_free was not found or was too old in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_GET_RENEWED_CREDS'):
+ Logs.warn("krb5_get_renewed_creds not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_PRINCIPAL_COMPARE_ANY_REALM'):
+ Logs.warn("krb5_principal_compare_any_realm not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_C_STRING_TO_KEY') and \
+ not conf.CONFIG_SET('HAVE_KRB5_STRING_TO_KEY_SALT'):
+ Logs.warn("krb5_c_string_to_key not found in -lkrb5")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_PRINCIPAL2SALT') and \
+ not conf.CONFIG_SET('HAVE_KRB5_GET_PW_SALT'):
+ Logs.warn("no CREATE_KEY_FUNCTIONS detected")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_GET_PERMITTED_ENCTYPES') and \
+ not conf.CONFIG_SET('HAVE_KRB5_GET_DEFAULT_IN_TKT_ETYPES'):
+ Logs.warn("no GET_ENCTYPES_FUNCTIONS detected")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_KT_FREE_ENTRY') and \
+ not conf.CONFIG_SET('HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS'):
+ Logs.warn("no KT_FREE_FUNCTION detected")
+ use_ads_krb5 = False
+ if not conf.CONFIG_SET('HAVE_KRB5_C_VERIFY_CHECKSUM'):
+ Logs.warn("krb5_c_verify_checksum_compare not found in -lkrb5")
+ use_ads_krb5 = False
+
+ # We don't actually use
+ # gsskrb5_extract_authz_data_from_sec_context, but it is a
+ # clue that this Heimdal, which does the PAC processing we
+ # need on the standard gss_inquire_sec_context_by_oid
+ if not conf.CONFIG_SET('HAVE_GSS_GET_NAME_ATTRIBUTE') and \
+ not (conf.CONFIG_SET('HAVE_GSSKRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT') and \
+ conf.CONFIG_SET('HAVE_GSS_INQUIRE_SEC_CONTEXT_BY_OID')):
+ Logs.warn("need either gss_get_name_attribute or gsskrb5_extract_authz_data_from_sec_context and gss_inquire_sec_context_by_oid in -lgssapi for PAC support")
+ use_ads_krb5 = False
+
+ if not conf.CONFIG_SET('HAVE_GSS_KRB5_EXPORT_LUCID_SEC_CONTEXT'):
+ Logs.warn("need gss_krb5_export_lucid_sec_context for SPNEGO and gss_wrap support")
+ use_ads_krb5 = False
+
+ if use_ads_krb5:
+ conf.DEFINE('HAVE_KRB5', '1')
+ conf.env['HAVE_KRB5'] = '1'
+ else:
+ conf.undefine('HAVE_KRB5_H')
+ conf.undefine('HAVE_GSSAPI_H')
+ conf.undefine('HAVE_GSSAPI_GSSAPI_GENERIC_H')
+ conf.undefine('HAVE_GSSAPI_GSSAPI_H')
+ use_ads = False
+
+ if not conf.CONFIG_SET('HAVE_LDAP'):
+ use_ads = False
+ use_ads_ldap = False
+
+ if use_ads:
+ conf.DEFINE('WITH_ADS', '1')
+ conf.env['HAVE_ADS'] = '1'
+ Logs.info("Building with Active Directory support.")
+ # these have broken dependencies
+ forced_shared_modules.extend(['idmap_ad', 'idmap_rfc2307'])
+ elif Options.options.with_ads == False:
+ Logs.info("Building without Active Directory support (--without-ads).")
+ if not Options.options.without_ad_dc:
+ conf.fatal("Building --without-ads requires also "
+ "building --without-ad-dc.")
+ else:
+ if not use_ads_krb5:
+ Logs.warn("Active Directory support not available: krb5 libs don't have all required features")
+ if not use_ads_ldap:
+ Logs.warn("Active Directory support not available: LDAP support is not available.")
+ if Options.options.with_ads:
+ conf.fatal("Active Directory support not found. Use --without-ads "
+ "for building without Active Directory support. "
+ "ADS support improves communication with "
+ "Active Directory domain controllers.")
+ else:
+ # this is the auto-mode case
+ Logs.warn("Building without Active Directory support.")
+
+
+ if Options.options.with_utmp:
+ conf.env.with_utmp = True
+ if not conf.CHECK_HEADERS('utmp.h'): conf.env.with_utmp = False
+ conf.CHECK_FUNCS('pututline pututxline updwtmp updwtmpx getutmpx')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_name', headers='utmp.h',
+ define='HAVE_UT_UT_NAME')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_user', headers='utmp.h',
+ define='HAVE_UT_UT_USER')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_id', headers='utmp.h',
+ define='HAVE_UT_UT_ID')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_host', headers='utmp.h',
+ define='HAVE_UT_UT_HOST')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_time', headers='utmp.h',
+ define='HAVE_UT_UT_TIME')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_tv', headers='utmp.h',
+ define='HAVE_UT_UT_TV')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_type', headers='utmp.h',
+ define='HAVE_UT_UT_TYPE')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_pid', headers='utmp.h',
+ define='HAVE_UT_UT_PID')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmp', 'ut_exit.e_exit', headers='utmp.h',
+ define='HAVE_UT_UT_EXIT')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmpx', 'ut_syslen', headers='utmpx.h',
+ define='HAVE_UX_UT_SYSLEN')
+ conf.CHECK_STRUCTURE_MEMBER('struct utmpx', 'ut_host', headers='utmpx.h',
+ define='HAVE_UX_UT_HOST')
+ conf.CHECK_CODE('struct utmp utarg; struct utmp *utreturn; utreturn = pututline(&utarg);',
+ 'PUTUTLINE_RETURNS_UTMP', headers='utmp.h',
+ msg="Checking whether pututline returns pointer")
+ conf.CHECK_SIZEOF(['((struct utmp *)NULL)->ut_line'], headers='utmp.h',
+ define='SIZEOF_UTMP_UT_LINE', critical=False)
+ if not conf.CONFIG_SET('SIZEOF_UTMP_UT_LINE'):
+ conf.env.with_utmp = False
+ elif int(conf.env.SIZEOF_UTMP_UT_LINE) < 15:
+ conf.env.with_utmp = False
+ if conf.env.with_utmp:
+ conf.DEFINE('WITH_UTMP', 1)
+ else:
+ Logs.warn("--with-utmp but utmp support not sufficient")
+
+ if Options.options.with_avahi:
+ conf.env.with_avahi = True
+ if not conf.CHECK_HEADERS('avahi-common/watch.h avahi-client/client.h'): conf.env.with_avahi = False
+ if not conf.CHECK_FUNCS_IN('avahi_client_new', 'avahi-client'): conf.env.with_avahi = False
+ if not conf.CHECK_FUNCS_IN('avahi_strerror', 'avahi-common'): conf.env.with_avahi = False
+ if conf.env.with_avahi:
+ conf.DEFINE('WITH_AVAHI_SUPPORT', 1)
+ else:
+ conf.SET_TARGET_TYPE('avahi-common', 'EMPTY')
+ conf.SET_TARGET_TYPE('avahi-client', 'EMPTY')
+
+ if Options.options.with_iconv:
+ conf.env.with_iconv = True
+ if not conf.CHECK_FUNCS_IN('iconv_open', 'iconv', headers='iconv.h'):
+ conf.env.with_iconv = False
+ if conf.env.with_iconv:
+ conf.DEFINE('HAVE_ICONV', 1)
+
+ if Options.options.with_pam:
+ use_pam=True
+ conf.CHECK_HEADERS('security/pam_appl.h pam/pam_appl.h')
+ if not conf.CONFIG_SET('HAVE_SECURITY_PAM_APPL_H') and not conf.CONFIG_SET('HAVE_PAM_PAM_APPL_H'):
+ Logs.warn("--with-pam=yes but pam_appl.h not found")
+ use_pam=False
+ conf.CHECK_FUNCS_IN('pam_get_data', 'pam')
+ conf.CHECK_HEADERS('security/pam_modules.h pam/pam_modules.h')
+ if not conf.CONFIG_SET('HAVE_SECURITY_PAM_MODULES_H') and not conf.CONFIG_SET('HAVE_PAM_PAM_MODULES_H'):
+ Logs.warn("--with-pam=yes but pam_modules.h not found")
+ use_pam=False
+ conf.CHECK_HEADERS('security/pam_ext.h security/_pam_macros.h')
+ conf.CHECK_HEADERS('pam/pam_ext.h pam/_pam_macros.h')
+ conf.CHECK_FUNCS_IN('pam_vsyslog', 'pam')
+ conf.CHECK_CODE('''
+#if defined(HAVE_SECURITY_PAM_APPL_H)
+#include <security/pam_appl.h>
+#elif defined(HAVE_PAM_PAM_APPL_H)
+#include <pam/pam_appl.h>
+#endif
+pam_set_item(0, PAM_RHOST, 0);
+''',
+ 'HAVE_PAM_RHOST',
+ lib='pam',
+ msg="Checking whether PAM_RHOST is available");
+ conf.CHECK_CODE('''
+#if defined(HAVE_SECURITY_PAM_APPL_H)
+#include <security/pam_appl.h>
+#elif defined(HAVE_PAM_PAM_APPL_H)
+#include <pam/pam_appl.h>
+#endif
+pam_set_item(0, PAM_TTY, 0);
+''',
+ 'HAVE_PAM_TTY',
+ lib='pam',
+ msg="Checking whether PAM_TTY is available");
+ conf.CHECK_CODE('''
+#if (!defined(LINUX))
+
+#define PAM_EXTERN extern
+#if defined(HAVE_SECURITY_PAM_APPL_H)
+#include <security/pam_appl.h>
+#elif defined(HAVE_PAM_PAM_APPL_H)
+#include <pam/pam_appl.h>
+#endif
+
+#endif
+
+#if defined(HAVE_SECURITY_PAM_MODULES_H)
+#include <security/pam_modules.h>
+#elif defined(HAVE_PAM_PAM_MODULES_H)
+#include <pam/pam_modules.h>
+#endif
+
+#if defined(HAVE_SECURITY__PAM_MACROS_H)
+#include <security/_pam_macros.h>
+#elif defined(HAVE_PAM__PAM_MACROS_H)
+#include <pam/_pam_macros.h>
+#endif
+
+#ifdef HAVE_SECURITY_PAM_EXT_H
+#include <security/pam_ext.h>
+#endif
+
+int i; i = PAM_RADIO_TYPE;
+''',
+ 'HAVE_PAM_RADIO_TYPE',
+ lib='pam',
+ msg="Checking whether PAM_RADIO_TYPE is available");
+ if use_pam:
+ conf.DEFINE('WITH_PAM', 1)
+ conf.DEFINE('WITH_PAM_MODULES', 1)
+ else:
+ conf.fatal("PAM support is enabled but prerequisite libraries "
+ "or headers not found. Use --without-pam to disable "
+ "PAM support.");
+
+ seteuid = False
+
+#
+# Ensure we select the correct set of system calls on Linux.
+#
+ if (host_os.rfind('linux') > -1):
+ conf.CHECK_CODE('''
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#ifdef HAVE_SYS_PRIV_H
+#include <sys/priv.h>
+#endif
+#ifdef HAVE_SYS_ID_H
+#include <sys/id.h>
+#endif
+
+#if defined(HAVE_SYSCALL_H)
+#include <syscall.h>
+#endif
+
+#if defined(HAVE_SYS_SYSCALL_H)
+#include <sys/syscall.h>
+#endif
+
+syscall(SYS_setresuid32, -1, -1, -1);
+syscall(SYS_setresgid32, -1, -1, -1);
+syscall(SYS_setreuid32, -1, -1);
+syscall(SYS_setregid32, -1, -1);
+syscall(SYS_setuid32, -1);
+syscall(SYS_setgid32, -1);
+syscall(SYS_setgroups32, 0, NULL);
+''',
+ 'USE_LINUX_32BIT_SYSCALLS',
+ msg="Checking whether Linux should use 32-bit credential calls");
+
+ if (conf.CONFIG_SET('USE_LINUX_32BIT_SYSCALLS')):
+ seteuid = conf.CHECK_CODE('''
+ #define AUTOCONF_TEST 1
+ #define HAVE_LINUX_THREAD_CREDENTIALS 1
+ #define USE_LINUX_32BIT_SYSCALLS 1
+ #include "../lib/util/setid.c"
+ #include "./lib/util_sec.c"
+ ''',
+ 'HAVE_LINUX_THREAD_CREDENTIALS',
+ addmain=False,
+ execute=True,
+ msg="Checking whether we can use Linux thread-specific credentials with 32-bit system calls")
+ else:
+ seteuid = conf.CHECK_CODE('''
+ #define AUTOCONF_TEST 1
+ #define HAVE_LINUX_THREAD_CREDENTIALS 1
+ #include "../lib/util/setid.c"
+ #include "./lib/util_sec.c"
+ ''',
+ 'HAVE_LINUX_THREAD_CREDENTIALS',
+ addmain=False,
+ execute=True,
+ msg="Checking whether we can use Linux thread-specific credentials")
+ if not seteuid:
+ seteuid = conf.CHECK_CODE('''
+ #define AUTOCONF_TEST 1
+ #define USE_SETREUID 1
+ #include "../lib/util/setid.c"
+ #include "./lib/util_sec.c"
+ ''',
+ 'USE_SETREUID',
+ addmain=False,
+ execute=True,
+ msg="Checking whether setreuid is available")
+ if not seteuid:
+ seteuid = conf.CHECK_CODE('''
+ #define AUTOCONF_TEST 1
+ #define USE_SETRESUID 1
+ #include "../lib/util/setid.c"
+ #include "./lib/util_sec.c"
+ ''',
+ 'USE_SETRESUID',
+ addmain=False,
+ execute=True,
+ msg="Checking whether setresuid is available")
+ if not seteuid:
+ seteuid = conf.CHECK_CODE('''
+ #define AUTOCONF_TEST 1
+ #define USE_SETEUID 1
+ #include "../lib/util/setid.c"
+ #include "./lib/util_sec.c"
+ ''',
+ 'USE_SETEUID',
+ addmain=False,
+ execute=True,
+ msg="Checking whether seteuid is available")
+ if not seteuid:
+ seteuid = conf.CHECK_CODE('''
+ #define AUTOCONF_TEST 1
+ #define USE_SETUIDX 1
+ #include "../lib/util/setid.c"
+ #include "./lib/util_sec.c"
+ ''',
+ 'USE_SETUIDX',
+ addmain=False,
+ execute=True,
+ mandatory=True,
+ msg="Checking whether setuidx is available")
+ # valgrind.h or valgrind/valgrind.h is checked in lib/replace/wscript
+ if Options.options.developer:
+ if conf.CONFIG_SET('HAVE_VALGRIND_H') or conf.CONFIG_SET('HAVE_VALGRIND_VALGRIND_H'):
+ conf.DEFINE('VALGRIND', '1')
+
+ if conf.CHECK_CODE('''
+#include <bits/sockaddr.h>
+#include <linux/netlink.h>
+''',
+ 'HAVE_LINUX_NETLINK_H',
+ msg="Checking whether Linux netlink is available"):
+
+ conf.CHECK_CODE('''
+#include <bits/sockaddr.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+''',
+ 'HAVE_LINUX_RTNETLINK_H',
+ msg='Checking whether Linux rtnetlink is available')
+
+ conf.CHECK_CODE('''
+#include "../tests/fcntl_lock.c"
+''',
+ 'HAVE_FCNTL_LOCK',
+ addmain=False,
+ execute=True,
+ msg='Checking whether fcntl locking is available')
+
+ conf.CHECK_CODE('''
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#define DATA "ofdtest.fcntl"
+
+int main(void) {
+ struct flock lck = {
+ .l_whence = SEEK_SET,
+ .l_type = F_WRLCK,
+ .l_start = 0,
+ .l_len = 1,
+ .l_pid = 0,
+ };
+ int ret;
+ int fd1;
+ int fd2;
+ char *testdir = getenv("TESTDIR");
+
+ if (testdir) {
+ if (chdir(testdir) != 0) {
+ goto err;
+ }
+ }
+
+ unlink(DATA);
+ fd1 = open(DATA, O_RDWR|O_CREAT|O_EXCL, 0600);
+ fd2 = open(DATA, O_RDWR);
+ if (fd1 == -1 || fd2 == -1) {
+ goto err;
+ }
+ ret = fcntl(fd1,F_OFD_SETLKW,&lck);
+ if (ret == -1) {
+ goto err;
+ }
+ ret = fcntl(fd2,F_OFD_SETLK,&lck);
+ if (ret != -1) {
+ goto err;
+ }
+ if (errno != EAGAIN) {
+ goto err;
+ }
+ ret = fcntl(fd2,F_OFD_GETLK,&lck);
+ if (ret == -1) {
+ goto err;
+ }
+ unlink(DATA);
+ exit(0);
+err:
+ unlink(DATA);
+ exit(1);
+}''',
+ 'HAVE_OFD_LOCKS',
+ addmain=False,
+ execute=True,
+ msg="Checking whether fcntl lock supports open file description locks")
+
+ conf.CHECK_CODE('''
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+
+int main(void)
+{
+ int sockfd, ret;
+ struct f_owner_ex owner, get_owner;
+
+ sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sockfd == -1) {
+ goto err;
+ }
+
+ owner.type = F_OWNER_PID;
+ owner.pid = getpid();
+
+ ret = fcntl(sockfd, F_SETOWN_EX, &owner);
+ if (ret == -1) {
+ goto err;
+ }
+
+ ret = fcntl(sockfd, F_GETOWN_EX, &get_owner);
+ if (ret == -1) {
+ goto err;
+ }
+
+ if (get_owner.type != F_OWNER_PID) {
+ goto err;
+ }
+
+ if (get_owner.pid != getpid()) {
+ goto err;
+ }
+
+ close(sockfd);
+ exit(0);
+err:
+ close(sockfd);
+ exit(1);
+}''',
+ 'HAVE_F_OWNER_EX',
+ addmain=False,
+ execute=True,
+ msg="Checking whether fcntl supports flags to send direct I/O availability signals")
+
+ conf.CHECK_CODE('''
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#define DATA "hinttest.fcntl"
+
+int main(void)
+{
+ uint64_t hint, get_hint;
+ int fd;
+
+ fd = open(DATA, O_RDONLY | O_CREAT | O_EXCL);
+ if (fd == -1) {
+ goto err;
+ }
+
+ hint = RWH_WRITE_LIFE_SHORT;
+ int ret = fcntl(fd, F_SET_RW_HINT, &hint);
+ if (ret == -1) {
+ goto err;
+ }
+
+ ret = fcntl(fd, F_GET_RW_HINT, &get_hint);
+ if (ret == -1) {
+ goto err;
+ }
+
+ if (get_hint != RWH_WRITE_LIFE_SHORT) {
+ goto err;
+ }
+
+ hint = RWH_WRITE_LIFE_EXTREME;
+ ret = fcntl(fd, F_SET_FILE_RW_HINT, &hint);
+ if (ret == -1) {
+ goto err;
+ }
+
+ ret = fcntl(fd, F_GET_FILE_RW_HINT, &get_hint);
+ if (ret == -1) {
+ goto err;
+ }
+
+ if (get_hint != RWH_WRITE_LIFE_EXTREME) {
+ goto err;
+ }
+
+ close(fd);
+ unlink(DATA);
+ exit(0);
+err:
+ close(fd);
+ unlink(DATA);
+ exit(1);
+}''',
+ 'HAVE_RW_HINTS',
+ addmain=False,
+ execute=True,
+ msg="Checking whether fcntl supports setting/getting hints")
+
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_mtim.tv_nsec',
+ define='HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC') # Linux, Solaris
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_mtimensec',
+ define='HAVE_STRUCT_STAT_ST_MTIMENSEC') # BSD, if defined _POSIX_SOURCE
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_mtimespec.tv_nsec',
+ define='HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC') # BSD, if not defined _POSIX_SOURCE
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_mtime_n',
+ define='HAVE_STRUCT_STAT_ST_MTIME_N') # AIX
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_umtime',
+ define='HAVE_STRUCT_STAT_ST_UMTIME') # Tru64
+ if conf.CONFIG_SET('HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC') or \
+ conf.CONFIG_SET('HAVE_STRUCT_STAT_ST_MTIMENSEC') or \
+ conf.CONFIG_SET('HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC') or \
+ conf.CONFIG_SET('HAVE_STRUCT_STAT_ST_MTIME_N') or \
+ conf.CONFIG_SET('HAVE_STRUCT_STAT_ST_UMTIME'):
+ conf.DEFINE('HAVE_STAT_HIRES_TIMESTAMPS', '1')
+
+ # recent FreeBSD, NetBSD have creation timestamps called birthtime:
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_birthtime',
+ define='HAVE_STRUCT_STAT_ST_BIRTHTIME')
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_birthtimespec.tv_nsec',
+ define='HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC')
+ conf.CHECK_STRUCTURE_MEMBER('struct stat', 'st_birthtimensec',
+ define='HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC')
+
+ conf.CHECK_CODE('''
+ssize_t err = posix_fadvise(0,0,0x80000,POSIX_FADV_WILLNEED);
+''',
+ 'HAVE_POSIX_FADVISE',
+ msg='Checking whether posix_fadvise is available',
+ headers='unistd.h fcntl.h')
+
+ for v in ['_SC_NGROUPS_MAX', '_SC_NPROC_ONLN', '_SC_NPROCESSORS_ONLN', '_SC_PAGESIZE' ]:
+ conf.CHECK_CODE('''
+ #include <unistd.h>
+ return sysconf(%s) == -1 ? 1 : 0;
+ ''' % v,
+ 'SYSCONF%s' % v,
+ msg='Checking whether sysconf(%s) is available' % v)
+
+ conf.CHECK_CODE('''
+#include <sys/syscall.h>
+#include <unistd.h>
+syscall(SYS_initgroups, 16, NULL, NULL, 0);
+ ''',
+ 'HAVE_DARWIN_INITGROUPS',
+ msg='Checking whether to use the Darwin-specific initgroups system call')
+
+ conf.CHECK_CODE('''struct utimbuf tbuf; tbuf.actime = 0; tbuf.modtime = 1; exit(utime("foo.c",&tbuf));''',
+ 'HAVE_UTIMBUF',
+ headers='sys/types.h utime.h',
+ msg='Checking whether struct utimbuf is available')
+
+ if conf.CHECK_CODE('''struct sigevent s;''',
+ 'HAVE_STRUCT_SIGEVENT',
+ headers='sys/types.h stdlib.h stddef.h signal.h',
+ msg='Checking whether we have the struct sigevent'):
+ conf.CHECK_STRUCTURE_MEMBER('struct sigevent', 'sigev_value.sival_ptr',
+ define='HAVE_STRUCT_SIGEVENT_SIGEV_VALUE_SIVAL_PTR',
+ headers='signal.h');
+ conf.CHECK_STRUCTURE_MEMBER('struct sigevent', 'sigev_value.sigval_ptr',
+ define='HAVE_STRUCT_SIGEVENT_SIGEV_VALUE_SIGVAL_PTR',
+ headers='signal.h');
+
+ if os.path.exists('/proc/sys/kernel/core_pattern'):
+ conf.DEFINE('HAVE_SYS_KERNEL_PROC_CORE_PATTERN', '1')
+
+ if conf.CHECK_CODE('''
+#include <time.h>
+int main(void) {
+ struct tm *tm;
+ if (sizeof(time_t) == 8) {
+ time_t max_time = 0x7fffffffffffffffll;
+ tm = gmtime(&max_time);
+ /* This should fail with 32-bit tm_year. */
+ if (tm == NULL) {
+ /* Max time_t that works with 32-bit int tm_year in struct tm. */
+ max_time = 67768036191676799ll;
+ tm = gmtime(&max_time);
+ if (tm) {
+ exit(0);
+ }
+ }
+ }
+ exit(1);
+}''',
+ '__TIME_T_MAX',
+ addmain=False,
+ execute=True,
+ msg="Checking for the maximum value of the 'time_t' type"):
+ conf.DEFINE('TIME_T_MAX', '67768036191676799ll')
+
+ conf.CHECK_CODE('''
+#if defined(HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+#include <sys/types.h>
+#if defined(HAVE_SYS_SYSMACROS_H)
+#include <sys/sysmacros.h>
+#endif
+int main(void) { dev_t dev = makedev(1,2); return 0; }
+''',
+ 'HAVE_MAKEDEV',
+ addmain=False,
+ msg='Checking whether the macro for makedev is available')
+
+ conf.CHECK_CODE('''
+#include <stdio.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdlib.h>
+
+void exit_on_core(int ignored) {
+ exit(1);
+}
+
+int main(void) {
+ char *newpath;
+ signal(SIGSEGV, exit_on_core);
+ newpath = realpath("/tmp", NULL);
+ exit((newpath != NULL) ? 0 : 1);
+}
+''',
+ 'REALPATH_TAKES_NULL',
+ addmain=False,
+ execute=True,
+ msg='Checking whether the realpath function allows a NULL argument')
+
+ conf.CHECK_CODE('''#include "../tests/ftruncate.c"''',
+ 'HAVE_FTRUNCATE_EXTEND',
+ msg='Checking for ftruncate extend',
+ addmain=False,
+ execute=True)
+
+ conf.CHECK_CODE('''#include "../tests/readlink.c"''',
+ 'HAVE_BROKEN_READLINK',
+ msg='Checking for readlink breakage',
+ addmain=False,
+ execute=True)
+
+ conf.SET_TARGET_TYPE('sendfile', 'EMPTY')
+ conf.CHECK_LIB('sendfile')
+ if not Options.options.with_sendfile_support == False:
+ if (host_os.rfind('linux') > -1) or (host_os.rfind('gnu') > -1) or (host_os.rfind('k*bsd*-gnu') > -1) or (host_os.rfind('kopensolaris*-gnu') > -1):
+ conf.CHECK_CODE('''
+ int tofd, fromfd;
+ off_t offset;
+ size_t total;
+ ssize_t nwritten = sendfile(tofd, fromfd, &offset, total);
+ ''',
+ '_HAVE_SENDFILE',
+ headers='sys/sendfile.h',
+ msg='Checking for linux sendfile support')
+
+ if conf.CONFIG_SET('_HAVE_SENDFILE'):
+ conf.DEFINE('HAVE_SENDFILE', '1')
+ conf.DEFINE('LINUX_SENDFILE_API', '1')
+ elif (host_os.rfind('freebsd') > -1) or (host_os.rfind('dragonfly') > -1):
+ conf.CHECK_CODE('''
+ #include <sys/types.h>
+ #include <unistd.h>
+ #include <sys/socket.h>
+ #include <sys/uio.h>
+ int fromfd, tofd, ret, total=0;
+ off_t offset, nwritten;
+ struct sf_hdtr hdr;
+ struct iovec hdtrl;
+ hdr.headers = &hdtrl;
+ hdr.hdr_cnt = 1;
+ hdr.trailers = NULL;
+ hdr.trl_cnt = 0;
+ hdtrl.iov_base = NULL;
+ hdtrl.iov_len = 0;
+ ret = sendfile(fromfd, tofd, offset, total, &hdr, &nwritten, 0)
+ ''',
+ '_HAVE_SENDFILE',
+ msg='Checking for freebsd sendfile support')
+ if conf.CONFIG_SET('_HAVE_SENDFILE'):
+ conf.DEFINE('HAVE_SENDFILE', '1')
+ conf.DEFINE('FREEBSD_SENDFILE_API', '1')
+ elif (host_os.rfind('darwin') > -1):
+ conf.CHECK_CODE('''
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <sys/uio.h>
+ int fromfd, tofd, ret;
+ off_t offset, nwritten;
+ struct sf_hdtr hdr;
+ struct iovec hdtrl;
+ hdr.headers = &hdtrl;
+ hdr.hdr_cnt = 1;
+ hdr.trailers = (void *)0;
+ hdr.trl_cnt = 0;
+ hdtrl.iov_base = (void *)0;
+ hdtrl.iov_len = 0;
+ ret = sendfile(fromfd, tofd, offset, &nwritten, &hdr, 0);
+ ''',
+ '_HAVE_SENDFILE',
+ msg='Checking for darwin sendfile support')
+ if conf.CONFIG_SET('_HAVE_SENDFILE'):
+ conf.DEFINE('HAVE_SENDFILE', '1')
+ conf.DEFINE('DARWIN_SENDFILE_API', '1')
+ elif (host_os.rfind('hpux') > -1) or (host_os.rfind('osf') > -1):
+ conf.CHECK_CODE('''
+ #include <sys/socket.h>
+ #include <sys/uio.h>
+ int fromfd, tofd;
+ size_t total=0;
+ struct iovec hdtrl[2];
+ ssize_t nwritten;
+ off_t offset;
+ hdtrl[0].iov_base = 0;
+ hdtrl[0].iov_len = 0;
+ nwritten = sendfile(tofd, fromfd, offset, total, &hdtrl[0], 0);
+ ''',
+ '_HAVE_SENDFILE',
+ msg='Checking for osf/hpux sendfile support')
+ if conf.CONFIG_SET('_HAVE_SENDFILE'):
+ conf.DEFINE('HAVE_SENDFILE', '1')
+ conf.DEFINE('HPUX_SENDFILE_API', '1')
+ elif (host_os.rfind('sunos') > -1):
+ conf.CHECK_FUNCS_IN('sendfilev', 'sendfile')
+ conf.CHECK_CODE('''
+ #include <sys/sendfile.h>,
+ int sfvcnt;
+ size_t xferred;
+ struct sendfilevec vec[2];
+ ssize_t nwritten;
+ int tofd;
+ sfvcnt = 2;
+ vec[0].sfv_fd = SFV_FD_SELF;
+ vec[0].sfv_flag = 0;
+ vec[0].sfv_off = 0;
+ vec[0].sfv_len = 0;
+ vec[1].sfv_fd = 0;
+ vec[1].sfv_flag = 0;
+ vec[1].sfv_off = 0;
+ vec[1].sfv_len = 0;
+ nwritten = sendfilev(tofd, vec, sfvcnt, &xferred);
+ ''',
+ '_HAVE_SENDFILEV',
+ msg='Checking for solaris sendfilev support',
+ lib='sendfile')
+ if conf.CONFIG_SET('_HAVE_SENDFILEV'):
+ conf.DEFINE('HAVE_SENDFILEV', '1')
+ conf.DEFINE('SOLARIS_SENDFILE_API', '1')
+ elif (host_os.rfind('aix') > -1):
+ conf.CHECK_CODE('''
+ #include <sys/socket.h>
+ int fromfd, tofd;
+ size_t total=0;
+ struct sf_parms hdtrl;
+ ssize_t nwritten;
+ hdtrl.header_data = 0;
+ hdtrl.header_length = 0;
+ hdtrl.file_descriptor = fromfd;
+ hdtrl.file_offset = 0;
+ hdtrl.file_bytes = 0;
+ hdtrl.trailer_data = 0;
+ hdtrl.trailer_length = 0;
+ nwritten = send_file(&tofd, &hdtrl, 0);
+ ''',
+ '_HAVE_SENDFILE',
+ msg='Checking for AIX send_file support')
+ if conf.CONFIG_SET('_HAVE_SENDFILE'):
+ conf.DEFINE('HAVE_SENDFILE', '1')
+ conf.DEFINE('AIX_SENDFILE_API', '1')
+
+ if Options.options.with_sendfile_support == True and not conf.CONFIG_SET('HAVE_SENDFILE'):
+ conf.fatal('sendfile support not found but it was requested !')
+ # Check for getcwd allowing a NULL arg.
+ conf.CHECK_CODE('''
+#include <unistd.h>
+int main(void) {
+ char *s = getcwd(NULL,0);
+ return s != NULL ? 0 : 1;
+}''', 'GETCWD_TAKES_NULL', addmain=False, execute=True,
+ msg="getcwd takes a NULL argument")
+
+
+ # UnixWare 7.x has its getspnam in -lgen
+ conf.CHECK_FUNCS_IN('getspnam', 'gen')
+ conf.CHECK_FUNCS_IN('getspnam', 'security')
+ conf.CHECK_FUNCS_IN('getspnam', 'sec')
+
+ legacy_quota_libs = ''
+ if not Options.options.with_quotas == False:
+ # For quotas on Veritas VxFS filesystems
+ conf.CHECK_HEADERS('sys/fs/vx_quota.h')
+ # For sys/quota.h and linux/quota.h
+ conf.CHECK_HEADERS('sys/quota.h')
+ # For quotas on BSD systems
+ conf.CHECK_HEADERS('ufs/ufs/quota.h')
+ # For quotas on AIX systems
+ conf.CHECK_HEADERS('jfs/quota.h')
+ # For quotas on Linux XFS filesystems
+ if conf.CHECK_HEADERS('xfs/xqm.h'):
+ conf.DEFINE('HAVE_XFS_QUOTAS', '1')
+ else:
+ # For Irix XFS
+ conf.CHECK_CODE('''
+ #include "confdefs.h"
+ #ifdef HAVE_SYS_TYPES_H
+ #include <sys/types.h>
+ #endif
+ #ifdef HAVE_ASM_TYPES_H
+ #include <asm/types.h>
+ #endif
+ #include <sys/quota.h>
+ int i = Q_XGETQUOTA;''',
+ define='HAVE_XFS_QUOTAS',
+ msg='for XFS QUOTA in <sys/quota.h>',
+ execute=False,
+ local_include=False)
+
+ # For IRIX like dqb_isoftlimit instead of dqb_fsoftlimit in struct dqblk
+ conf.CHECK_STRUCTURE_MEMBER('struct dqblk', 'dqb_fsoftlimit', define='HAVE_DQB_FSOFTLIMIT',
+ headers='sys/quota.h')
+ #darwin style quota bytecount
+ conf.CHECK_STRUCTURE_MEMBER('struct dqblk', 'dqb_curbytes', define='HAVE_STRUCT_DQBLK_DQB_CURBYTES',
+ headers='sys/quota.h')
+ conf.CHECK_HEADERS('rpc/types.h rpc/xdr.h', together=True)
+ if conf.CHECK_HEADERS('rpcsvc/rquota.h', lib='tirpc'):
+ # Optional structure member
+ conf.CHECK_STRUCTURE_MEMBER('struct getquota_rslt', 'getquota_rslt_u',
+ define='HAVE_GETQUOTA_RSLT_GETQUOTA_RSLT_U',
+ headers='rpcsvc/rquota.h',
+ lib='tirpc')
+
+ # Required function for NFS quota support
+ conf.CHECK_CODE('''
+ clnt_create("", RQUOTAPROG, RQUOTAVERS, "udp");
+ ''',
+ headers="rpc/rpc.h rpc/types.h rpcsvc/rquota.h rpc/nettype.h rpc/xdr.h",
+ define='HAVE_NFS_QUOTAS',
+ msg='checking for clnt_create()',
+ execute=True,
+ local_include=False,
+ lib='tirpc')
+
+ if (host_os.rfind('linux') > -1):
+ conf.DEFINE('HAVE_QUOTACTL_LINUX', '1')
+ elif not conf.CONFIG_SET("HAVE_XFS_QUOTAS"):
+ if not conf.CHECK_CODE('''
+ #define HAVE_QUOTACTL_4A 1
+ #define AUTOCONF_TEST 1
+ #include "../tests/sysquotas.c"
+ ''',
+ cflags=conf.env['WERROR_CFLAGS'],
+ define='HAVE_QUOTACTL_4A',
+ msg='for QUOTACTL_4A: long quotactl(int cmd, char *special, qid_t id, caddr_t addr)',
+ execute=True,
+ addmain=False):
+
+ conf.CHECK_CODE('''
+ #define HAVE_QUOTACTL_4B 1
+ #define AUTOCONF_TEST 1
+ #include "../tests/sysquotas.c"
+ ''',
+ cflags=conf.env['WERROR_CFLAGS'],
+ define='HAVE_QUOTACTL_4B',
+ msg='for QUOTACTL_4B: int quotactl(const char *path, int cmd, int id, char *addr)',
+ execute=True,
+ addmain=False)
+
+ if conf.CONFIG_SET('HAVE_QUOTACTL_LINUX') or \
+ conf.CONFIG_SET('HAVE_QUOTACTL_4A') or \
+ conf.CONFIG_SET('HAVE_QUOTACTL_4B') or \
+ conf.CONFIG_SET('HAVE_XFS_QUOTAS'):
+ conf.DEFINE('HAVE_SYS_QUOTAS', '1')
+ conf.DEFINE('WITH_QUOTAS', '1')
+
+ #
+ # check if Legacy quota code can be brought in
+ # if standard interfaces are not supported
+ #
+ if not conf.CONFIG_SET('WITH_QUOTAS'):
+ if host_os.rfind('sunos5') > -1:
+ conf.DEFINE('SUNOS5', '1')
+ legacy_quota_libs = 'nsl'
+ conf.CHECK_CODE('''
+ #define WITH_QUOTAS 1
+ #define AUTOCONF_TEST 1
+ #include "../tests/oldquotas.c"
+ ''',
+ cflags=conf.env['WERROR_CFLAGS'],
+ define='WITH_QUOTAS',
+ lib=legacy_quota_libs,
+ msg='Checking whether legacy quota code can be used',
+ execute=False,
+ addmain=False)
+ if not conf.CONFIG_SET('WITH_QUOTAS'):
+ legacy_quota_libs = ''
+ conf.env['legacy_quota_libs'] = legacy_quota_libs
+
+ if Options.options.with_quotas == True and not conf.CONFIG_SET('WITH_QUOTAS'):
+ conf.fatal('quota support not found but it was requested !')
+
+ conf.CHECK_CODE('(void)unshare(CLONE_FS);',
+ headers='sched.h',
+ define='HAVE_UNSHARE_CLONE_FS',
+ msg='for Linux unshare(CLONE_FS)')
+
+ #
+ # cluster support (CTDB)
+ #
+ if not Options.options.with_cluster_support:
+ Logs.info("building without cluster support (--without-cluster-support)")
+ conf.env.with_ctdb = False
+ else:
+ Logs.info("building with cluster support")
+ conf.env.with_ctdb = True
+ conf.DEFINE('CLUSTER_SUPPORT', 1)
+
+ if Options.options.with_profiling_data:
+ conf.DEFINE('WITH_PROFILE', 1);
+ conf.CHECK_FUNCS('getrusage', headers="sys/time.h sys/resource.h")
+
+ if (conf.CHECK_HEADERS('linux/ioctl.h sys/ioctl.h linux/fs.h') and
+ conf.CHECK_DECLS('FS_IOC_GETFLAGS FS_COMPR_FL', headers='linux/fs.h')):
+ conf.DEFINE('HAVE_LINUX_IOCTL', '1')
+
+ conf.env['CFLAGS_CEPHFS'] = "-D_FILE_OFFSET_BITS=64"
+ if Options.options.libcephfs_dir:
+ Logs.error('''--with-libcephfs no longer supported, please use compiler
+ flags instead, e.g. GCC LIBRARY_PATH and C_INCLUDE_PATH''')
+ sys.exit(1)
+
+ if (Options.options.with_cephfs and
+ conf.CHECK_HEADERS('cephfs/libcephfs.h', False, False, 'cephfs') and
+ conf.CHECK_LIB('cephfs', shlib=True)):
+ if (Options.options.with_acl_support and
+ conf.CHECK_FUNCS_IN('ceph_statx', 'cephfs',
+ headers='cephfs/libcephfs.h')):
+ conf.DEFINE('HAVE_CEPH', '1')
+ conf.CHECK_FUNCS_IN('ceph_select_filesystem', 'cephfs',
+ headers='cephfs/libcephfs.h')
+ conf.DEFINE('HAVE_MANDATORY_CEPH_API', '1')
+ for api in ['ceph_mkdirat', 'ceph_openat', 'ceph_unlinkat',
+ 'ceph_symlinkat', 'ceph_readlinkat', 'ceph_statxat',
+ 'ceph_fdopendir']:
+ if not conf.CHECK_FUNCS_IN(api, 'cephfs',
+ headers='cephfs/libcephfs.h'):
+ conf.undefine('HAVE_MANDATORY_CEPH_API')
+ if not conf.env.HAVE_MANDATORY_CEPH_API:
+ Logs.warn("Installed Ceph version support is at the verge of "
+ "deprecation due to the absence of some mandatory "
+ "libcephfs APIs. This warning there shall result in "
+ "disabling the Ceph VFS module altogether with the "
+ "next major Samba version. It is highly recommended "
+ "to update to a more recent/active version of Ceph.")
+ else:
+ Logs.warn('''Ceph support disabled due to --without-acl-support
+ or lack of ceph_statx support''')
+ conf.undefine('HAVE_CEPH')
+
+ if Options.options.with_glusterfs:
+ conf.CHECK_CFG(package='glusterfs-api', args='"glusterfs-api >= 4" --cflags --libs',
+ msg='Checking for glusterfs-api >= 4', uselib_store="GFAPI")
+ conf.CHECK_HEADERS('glusterfs/api/glfs.h', lib='gfapi')
+ conf.CHECK_LIB('gfapi', shlib=True)
+
+ if conf.CONFIG_SET('HAVE_GLUSTERFS_API_GLFS_H'):
+ if Options.options.with_acl_support:
+ conf.DEFINE('HAVE_GLUSTERFS', '1')
+ else:
+ Logs.warn("GlusterFS support disabled due to --without-acl-support")
+ conf.undefine('HAVE_GLUSTERFS')
+ else:
+ conf.undefine('HAVE_GLUSTERFS')
+
+ conf.CHECK_CFG(package='glusterfs-api', args='"glusterfs-api >= 6" --cflags --libs',
+ msg='Checking for glusterfs-api >= 6',
+ uselib_store="GFAPI_VER_6")
+ conf.CHECK_CFG(package='glusterfs-api', args='"glusterfs-api >= 7.6" --cflags --libs',
+ msg='Checking for glusterfs-api >= 7.6',
+ uselib_store="GFAPI_VER_7_6")
+ conf.CHECK_CFG(package='glusterfs-api', args='"glusterfs-api >= 7.9" --cflags --libs',
+ msg='Checking for glusterfs-api >= 7.9',
+ uselib_store="GFAPI_VER_7_9")
+ conf.CHECK_CFG(package='glusterfs-api', args='"glusterfs-api >= 7.11" --cflags --libs',
+ msg='Checking for glusterfs-api >= 7.11',
+ uselib_store="GFAPI_VER_7_11")
+
+ else:
+ conf.SET_TARGET_TYPE('gfapi', 'EMPTY')
+ conf.undefine('HAVE_GLUSTERFS')
+
+ if Options.options.enable_vxfs:
+ conf.DEFINE('HAVE_VXFS', '1')
+
+ if conf.CHECK_CFG(package='liburing', args='--cflags --libs',
+ msg='Checking for liburing package', uselib_store="URING"):
+ if (conf.CHECK_HEADERS('liburing.h', lib='uring')
+ and conf.CHECK_LIB('uring', shlib=True)):
+ conf.CHECK_FUNCS_IN('io_uring_ring_dontfork', 'uring',
+ headers='liburing.h')
+ # There are a few distributions, which
+ # don't seem to have linux/openat2.h available
+ # during the liburing build, which means liburing/compat.h
+ # defines struct open_how directly instead of including
+ # linux/openat2.h, while linux/openat2.h is
+ # available on the installed system, which cause problems
+ # when we try to include both files.
+ #
+ # check if struct open_how is defined in liburing/compat.h
+ # itself and not via including linux/openat2.h
+ conf.CHECK_TYPE_IN('struct open_how', 'liburing/compat.h',
+ cflags='-D_LINUX_OPENAT2_H',
+ define='HAVE_STRUCT_OPEN_HOW_LIBURING_COMPAT_H')
+ conf.DEFINE('HAVE_LIBURING', '1')
+
+ conf.env.build_regedit = False
+ if not Options.options.with_regedit == False:
+ conf.PROCESS_SEPARATE_RULE('system_ncurses')
+ if conf.CONFIG_SET('HAVE_NCURSES'):
+ conf.env.build_regedit = True
+
+ if conf.env.build_regedit:
+ Logs.info("building regedit")
+ else:
+ if Options.options.with_regedit == False:
+ Logs.info("not building regedit (--without-regedit)")
+ elif Options.options.with_regedit == True:
+ Logs.error("ncurses not available, cannot build regedit")
+ conf.fatal("ncurses not available, but --with-regedit was specified")
+ else:
+ Logs.info("ncurses not available, not building regedit")
+
+ if conf.CHECK_HEADERS('ftw.h') and conf.CHECK_FUNCS('nftw'):
+ conf.env.build_mvxattr = True
+
+ conf.env.build_winexe = False
+ if not Options.options.with_winexe == False:
+ if conf.CONFIG_SET('HAVE_WINEXE_CC_WIN32') or conf.CONFIG_SET('HAVE_WINEXE_CC_WIN64'):
+ conf.env.build_winexe = True
+
+ if conf.env.build_winexe:
+ Logs.info("building winexe")
+ else:
+ if Options.options.with_winexe == False:
+ Logs.info("not building winexe (--without-winexe)")
+ elif Options.options.with_winexe == True:
+ Logs.error("mingw not available, cannot build winexe")
+ conf.fatal("mingw not available, but --with-winexe was specified")
+ else:
+ Logs.info("mingw not available, not building winexe")
+
+ conf.CHECK_FUNCS_IN('DES_pcbc_encrypt', 'crypto')
+ if Options.options.with_fake_kaserver == True:
+ conf.CHECK_HEADERS('afs/param.h afs/stds.h', together=True)
+ conf.CHECK_HEADERS('afs/param.h afs/stds.h', together=True)
+ if (conf.CONFIG_SET('HAVE_AFS_PARAM_H') and conf.CONFIG_SET('HAVE_AFS_STDS_H') and conf.CONFIG_SET('HAVE_DES_PCBC_ENCRYPT')):
+ conf.DEFINE('WITH_FAKE_KASERVER', '1')
+ else:
+ conf.fatal('AFS headers not available, but --with-fake-kaserver was specified')
+
+ if conf.CHECK_CFG(package='glib-2.0',
+ args='--cflags --libs',
+ msg='Checking for glib-2.0',
+ uselib_store="GLIB-2.0"):
+ if (conf.CHECK_HEADERS('glib.h', lib='glib-2.0') and conf.CHECK_LIB('glib-2.0', shlib=True)):
+ conf.DEFINE('HAVE_GLIB', 1)
+
+ if conf.CONFIG_SET('HAVE_GLIB'):
+ conf.DEFINE('WITH_TEVENT_GLIB_GLUE', '1')
+
+ conf.env['libtracker']=''
+ tracker_versions = ['2.0', '1.0', '0.16', '0.14']
+
+ for version in tracker_versions:
+ testlib = 'tracker-sparql-' + version
+ if conf.CHECK_CFG(package=testlib,
+ args='--cflags --libs',
+ mandatory=False):
+ conf.SET_TARGET_TYPE(testlib, 'SYSLIB')
+ conf.env['libtracker'] = testlib
+ conf.DEFINE('HAVE_TRACKER', '1')
+ break
+
+ with_spotlight_tracker_backend = (
+ conf.CONFIG_SET('HAVE_TRACKER')
+ and conf.CONFIG_SET('HAVE_GLIB')
+ and conf.env['BISON']
+ and conf.env['FLEX']
+ and conf.CONFIG_GET('HAVE_UTF8_NORMALISATION')
+ )
+
+ with_spotlight_es_backend = (
+ conf.CONFIG_SET('HAVE_JSON_OBJECT')
+ and conf.env['BISON']
+ and conf.env['FLEX']
+ and conf.CONFIG_GET('HAVE_UTF8_NORMALISATION')
+ )
+
+ conf.env.with_wsp = False
+ if conf.CONFIG_GET('ENABLE_SELFTEST'):
+ Options.options.with_wsp = True
+ if Options.options.with_wsp:
+ Logs.info("building with WSP support")
+ conf.DEFINE('WITH_WSP', '1')
+ conf.env.with_wsp = True
+
+ conf.env.with_spotlight = False
+ if Options.options.with_spotlight is not False:
+ backends = ['noindex']
+
+ if not conf.env['BISON']:
+ Logs.warn("Spotlight support requested but bison missing")
+ if not conf.env['FLEX']:
+ Logs.warn("Spotlight support requested but flex missing")
+ if not conf.CONFIG_GET('HAVE_UTF8_NORMALISATION'):
+ Logs.warn("Missing support for Unicode normalisation. "
+ "Try installing icu-dev or libicu-devel.")
+ if not conf.CONFIG_SET('HAVE_TRACKER'):
+ Logs.warn('Missing libtracker-sparql development files for Spotlight backend "tracker"')
+ if not conf.CONFIG_SET('HAVE_GLIB'):
+ Logs.warn('Missing glib-2.0 development files for Spotlight backend "tracker"')
+ if not conf.CONFIG_GET('HAVE_JSON_OBJECT'):
+ Logs.warn('Missing libjansson development files for Spotlight backend "elasticsearch"')
+
+ if with_spotlight_tracker_backend:
+ conf.env.spotlight_backend_tracker = True
+ backends.append('tracker')
+ conf.DEFINE('HAVE_SPOTLIGHT_BACKEND_TRACKER', '1')
+
+ if with_spotlight_es_backend:
+ conf.env.spotlight_backend_es = True
+ backends.append('elasticsearch')
+ conf.DEFINE('HAVE_SPOTLIGHT_BACKEND_ES', '1')
+
+ if (Options.options.with_spotlight is True
+ and not conf.env.spotlight_backend_tracker
+ and not conf.env.spotlight_backend_es):
+ conf.fatal("Unmet dependencies for Spotlight backends")
+
+ Logs.info("Building with Spotlight support, available backends: %s" % ', '.join(backends))
+ conf.DEFINE('WITH_SPOTLIGHT', '1')
+ conf.env.with_spotlight = True
+
+ if not conf.CONFIG_SET('HAVE_RPC_XDR_H'):
+ conf.CHECK_HEADERS('rpc/types.h rpc/xdr.h', together=True, lib='tirpc')
+
+ if conf.CHECK_FUNCS_IN('nscd_flush_cache', 'nscd', headers='libnscd.h'):
+ conf.DEFINE('HAVE_NSCD_FLUSH_CACHE', '1')
+
+ forced_static_modules.extend(['auth_builtin', 'auth_sam', 'auth_winbind'])
+ default_static_modules.extend(['pdb_smbpasswd', 'pdb_tdbsam',
+ 'auth_unix',
+ 'nss_info_template', 'idmap_tdb', 'idmap_passdb',
+ 'idmap_nss'])
+
+ default_shared_modules.extend(['vfs_recycle', 'vfs_audit', 'vfs_extd_audit', 'vfs_full_audit',
+ 'vfs_fake_perms', 'vfs_default_quota', 'vfs_readonly', 'vfs_cap',
+ 'vfs_expand_msdfs', 'vfs_shadow_copy', 'vfs_shadow_copy2',
+ 'vfs_readahead', 'vfs_xattr_tdb',
+ 'vfs_streams_xattr', 'vfs_streams_depot', 'vfs_acl_xattr', 'vfs_acl_tdb',
+ 'vfs_preopen', 'vfs_catia',
+ 'vfs_media_harmony', 'vfs_unityed_media', 'vfs_fruit', 'vfs_shell_snap',
+ 'vfs_commit', 'vfs_worm', 'vfs_crossrename', 'vfs_linux_xfs_sgid',
+ 'vfs_time_audit', 'vfs_offline', 'vfs_virusfilter', 'vfs_widelinks'])
+ if host_os.rfind('linux') > -1:
+ default_shared_modules.extend(['vfs_snapper'])
+
+ default_shared_modules.extend(['idmap_tdb2', 'idmap_script'])
+ # these have broken dependencies
+ forced_shared_modules.extend(['idmap_autorid', 'idmap_rid', 'idmap_hash'])
+
+ if Options.options.developer:
+ default_static_modules.extend(['charset_weird'])
+ default_shared_modules.extend(['vfs_skel_opaque',
+ 'vfs_skel_transparent',
+ 'vfs_shadow_copy_test',
+ 'pdb_test',
+ 'vfs_fake_dfq',
+ 'gpext_security', 'gpext_registry', 'gpext_scripts'])
+
+ if Options.options.enable_selftest or Options.options.developer:
+ default_shared_modules.extend(['vfs_fake_acls', 'vfs_nfs4acl_xattr',
+ 'vfs_error_inject',
+ 'vfs_delay_inject'])
+
+ if conf.CONFIG_SET('AD_DC_BUILD_IS_ENABLED'):
+ default_static_modules.extend(['pdb_samba_dsdb', 'auth_samba4', 'vfs_dfs_samba4'])
+ default_shared_modules.extend(['vfs_posix_eadb'])
+
+ if conf.CONFIG_SET('HAVE_FREEBSD_SUNACL_H'):
+ default_shared_modules.extend(['vfs_zfsacl'])
+
+ if conf.CONFIG_SET('HAVE_DIRFD_DECL'):
+ default_shared_modules.extend(['vfs_syncops', 'vfs_dirsort'])
+
+ if conf.CONFIG_SET('HAVE_STATFS_F_FSID'):
+ default_shared_modules.extend(['vfs_fileid'])
+
+ if (conf.CONFIG_SET('HAVE_STRUCT_MSGHDR_MSG_CONTROL') or conf.CONFIG_SET('HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS')):
+ default_shared_modules.extend(['vfs_aio_fork'])
+
+ if conf.CONFIG_SET('HAVE_LIBURING'):
+ default_shared_modules.extend(['vfs_io_uring'])
+
+ if Options.options.with_pthreadpool:
+ default_shared_modules.extend(['vfs_aio_pthread'])
+
+ if conf.CONFIG_SET('HAVE_LDAP'):
+ default_static_modules.extend(['pdb_ldapsam', 'idmap_ldap'])
+
+ if conf.CONFIG_SET('DARWINOS'):
+ default_static_modules.extend(['charset_macosxfs'])
+
+ if conf.CONFIG_SET('HAVE_GPFS') and conf.CONFIG_SET('HAVE_KERNEL_OPLOCKS_LINUX'):
+ default_shared_modules.extend(['vfs_gpfs'])
+
+ if (conf.CONFIG_SET('HAVE_LINUX_IOCTL')
+ and conf.CONFIG_SET('HAVE_BASENAME') and conf.CONFIG_SET('HAVE_DIRNAME')):
+ default_shared_modules.extend(['vfs_btrfs'])
+
+ if conf.CONFIG_SET("HAVE_CEPH"):
+ default_shared_modules.extend(['vfs_ceph'])
+ # Unlike vfs_ceph, vfs_ceph_snapshots doesn't depend on libcephfs, so
+ # can be enabled atop a kernel CephFS share (with vfs_default) in
+ # addition to vfs_ceph. Still, only enable vfs_ceph_snapshots builds
+ # if we're building with libcephfs for now.
+ default_shared_modules.extend(['vfs_ceph_snapshots'])
+
+ if conf.CONFIG_SET('HAVE_GLUSTERFS'):
+ default_shared_modules.extend(['vfs_glusterfs'])
+
+ if conf.CONFIG_SET('HAVE_SETMNTENT'):
+ default_shared_modules.extend(['vfs_glusterfs_fuse'])
+
+ if conf.CONFIG_SET('HAVE_VXFS'):
+ default_shared_modules.extend(['vfs_vxfs'])
+
+ explicit_shared_modules = TO_LIST(Options.options.shared_modules, delimiter=',')
+ explicit_static_modules = TO_LIST(Options.options.static_modules, delimiter=',')
+
+ def replace_list_item(lst, item, value):
+ try:
+ idx = lst.index(item)
+ lst[idx] = value
+ except:
+ pass
+ # PDB module file name should have the same name as module registers itself
+ # In Autoconf build we export LDAP passdb module as ldapsam but WAF build
+ # was always exporting pdb_ldap. In order to support existing packages
+ # allow referring to pdb_ldapsam as pdb_ldap but use proper name internally.
+ replace_list_item(explicit_shared_modules, 'pdb_ldap', 'pdb_ldapsam')
+ replace_list_item(explicit_static_modules, 'pdb_ldap', 'pdb_ldapsam')
+
+ final_static_modules = []
+ final_static_modules.extend(TO_LIST(required_static_modules))
+ final_shared_modules = []
+
+ if '!FORCED' not in explicit_static_modules:
+ final_static_modules.extend(TO_LIST(forced_static_modules))
+ if '!FORCED' not in explicit_shared_modules:
+ final_shared_modules.extend(TO_LIST(forced_shared_modules))
+ if '!DEFAULT' not in explicit_static_modules:
+ final_static_modules.extend(TO_LIST(default_static_modules))
+ if '!DEFAULT' not in explicit_shared_modules:
+ final_shared_modules.extend(TO_LIST(default_shared_modules))
+
+ if 'ALL' in explicit_static_modules:
+ for m in default_shared_modules:
+ if m in final_shared_modules:
+ final_shared_modules.remove(m)
+ final_static_modules.append(m)
+ if 'ALL' in explicit_shared_modules:
+ for m in default_static_modules:
+ if m in final_static_modules:
+ final_static_modules.remove(m)
+ final_shared_modules.append(m)
+
+ for m in explicit_static_modules:
+ if m in ['ALL','!DEFAULT','!FORCED']:
+ continue
+ if m.startswith('!'):
+ m = m[1:]
+ if m in required_static_modules:
+ raise Errors.WafError('These modules are REQUIRED as static modules: %s' %
+ ' '.join(required_static_modules))
+ if m in final_static_modules:
+ final_static_modules.remove(m)
+ continue
+ if m in forced_shared_modules:
+ raise Errors.WafError('These modules MUST be configured as shared modules: %s' %
+ ' '.join(forced_shared_modules))
+ if m in final_shared_modules:
+ final_shared_modules.remove(m)
+ if m not in final_static_modules:
+ final_static_modules.append(m)
+ for m in explicit_shared_modules:
+ if m in ['ALL','!DEFAULT','!FORCED']:
+ continue
+ if m.startswith('!'):
+ m = m[1:]
+ if m in final_shared_modules:
+ final_shared_modules.remove(m)
+ continue
+ if m in required_static_modules:
+ raise Errors.WafError('These modules are REQUIRED as static modules: %s' %
+ ' '.join(required_static_modules))
+ if m in forced_static_modules:
+ raise Errors.WafError('These module MUST be configured as static modules: %s' %
+ ' '.join(forced_static_modules))
+ if m in final_static_modules:
+ final_static_modules.remove(m)
+ if m not in final_shared_modules:
+ final_shared_modules.append(m)
+
+ conf.env['static_modules'] = final_static_modules
+ conf.env['shared_modules'] = final_shared_modules
+
+ conf.DEFINE('STRING_STATIC_MODULES', ' '.join(final_static_modules), quote=True)
+ conf.DEFINE('STRING_SHARED_MODULES', ' '.join(final_shared_modules), quote=True)
+
+ static_list = {}
+ shared_list = {}
+
+ prefixes = ['vfs', 'pdb', 'auth', 'nss_info', 'charset', 'idmap', 'gpext']
+ conf.env['MODULE_PREFIXES'] = prefixes
+ for p in prefixes:
+ for m in final_static_modules:
+ if m.find(p) == 0:
+ if not p in static_list:
+ static_list[p] = []
+ static_list[p].append(m)
+ for m in final_shared_modules:
+ if m.find(p) == 0:
+ if not p in shared_list:
+ shared_list[p] = []
+ shared_list[p].append(m)
+
+ for p in prefixes:
+ static_env = "%s_STATIC" % p.upper()
+ shared_env = "%s_SHARED" % p.upper()
+ conf.env[static_env] = []
+ conf.env[shared_env] = []
+ if p in static_list:
+ decl_list = " ".join("extern NTSTATUS %s_init(TALLOC_CTX *mem_ctx); " % entry for entry in static_list[p])
+ for entry in static_list[p]:
+ conf.env[static_env].append('%s' % entry)
+ conf.DEFINE('static_decl_%s' % p, decl_list)
+ conf.DEFINE('static_init_%s(mem_ctx)' % p, '{ %s_init((mem_ctx)); }' % '_init((mem_ctx)); '.join(static_list[p]))
+ else:
+ conf.DEFINE('static_decl_%s' % p, '')
+ conf.DEFINE('static_init_%s(mem_ctx)' % p, '{}')
+ if p in shared_list:
+ for entry in shared_list[p]:
+ conf.DEFINE('%s_init' % entry, 'samba_init_module')
+ conf.env[shared_env].append('%s' % entry)
+ Logs.info("%s: %s" % (static_env, ','.join(conf.env[static_env])))
+ Logs.info("%s: %s" % (shared_env, ','.join(conf.env[shared_env])))
+
+ if (('vfs_snapper' in shared_list.get('vfs', []) or 'vfs_snapper' in static_list.get('vfs', []))
+ and not (conf.CHECK_CFG(package='dbus-1', args='--cflags --libs',
+ msg='Checking for dbus', uselib_store="DBUS-1")
+ and conf.CHECK_HEADERS('dbus/dbus.h', lib='dbus-1')
+ and conf.CHECK_LIB('dbus-1', shlib=True))):
+ conf.fatal("vfs_snapper is enabled but prerequisite dbus-1 package not "
+ "found. Use --with-shared-modules='!vfs_snapper' to disable "
+ "vfs_snapper support.")
+
+ conf.SAMBA_CONFIG_H('include/config.h')
diff --git a/source3/wscript_build b/source3/wscript_build
new file mode 100644
index 0000000..ff8de1e
--- /dev/null
+++ b/source3/wscript_build
@@ -0,0 +1,1323 @@
+#!/usr/bin/env python
+
+LIBS='ICONV'
+
+config_h = "../include/config.h"
+
+bld.SAMBA_BLDOPTIONS('smbd/build_options.c')
+
+bld.SETUP_BUILD_GROUPS()
+
+######################## SUBSYSTEMS #################################
+
+bld.SAMBA3_LIBRARY('netapi',
+ source='''lib/netapi/netapi.c
+ lib/netapi/cm.c
+ lib/netapi/libnetapi.c
+ lib/netapi/joindomain.c
+ lib/netapi/serverinfo.c
+ lib/netapi/wkstainfo.c
+ lib/netapi/getdc.c
+ lib/netapi/user.c
+ lib/netapi/group.c
+ lib/netapi/localgroup.c
+ lib/netapi/samr.c
+ lib/netapi/sid.c
+ lib/netapi/share.c
+ lib/netapi/file.c
+ lib/netapi/shutdown.c
+ lib/netapi/netlogon.c''',
+ deps='CREDENTIALS_CMDLINE',
+ public_deps='''
+ talloc
+ msrpc3
+ ads
+ NDR_LIBNETAPI
+ LIBNET
+ RPC_CLIENT_SCHANNEL
+ libcli_netlogon3
+ LIBCLI_SAMR
+ INIT_SAMR
+ auth
+ ''',
+ public_headers='../source3/lib/netapi/netapi.h',
+ pc_files='libnet/netapi.pc',
+ vnum='1.0.0')
+
+bld.SAMBA3_LIBRARY('gse',
+ source='librpc/crypto/gse_krb5.c librpc/crypto/gse.c',
+ deps='krb5samba gensec smbconf KRBCLIENT secrets3',
+ private_library=True)
+
+bld.SAMBA3_LIBRARY('msrpc3',
+ source='''
+ rpc_client/cli_pipe.c
+ rpc_client/rpc_transport_np.c
+ rpc_client/rpc_transport_sock.c
+ rpc_client/rpc_transport_tstream.c
+ rpc_client/local_np.c
+ librpc/rpc/dcerpc_helpers.c
+ ''',
+ deps='''
+ ndr
+ ndr-standard
+ RPC_NDR_EPMAPPER
+ NTLMSSP_COMMON
+ COMMON_SCHANNEL
+ LIBCLI_AUTH
+ LIBTSOCKET
+ npa_tstream
+ gse
+ dcerpc-binding
+ libsmb
+ ndr-table
+ NETLOGON_CREDS_CLI
+ ''',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('AVAHI',
+ source='''
+ lib/avahi.c
+ smbd/avahi_register.c
+ ''',
+ deps='''
+ avahi-common
+ avahi-client
+ ''',
+ enabled=bld.env.with_avahi)
+
+bld.SAMBA3_SUBSYSTEM('GROUPDB',
+ source='''
+ groupdb/mapping.c
+ groupdb/mapping_tdb.c
+ ''',
+ deps='tdb')
+
+bld.SAMBA3_SUBSYSTEM('TLDAP',
+ source='''
+ lib/tldap.c
+ lib/tldap_util.c
+ lib/tldap_gensec_bind.c
+ ''',
+ deps='''
+ asn1util
+ LIBTSOCKET
+ samba3util
+ ''')
+
+bld.SAMBA3_BINARY('test_tldap',
+ source='lib/test_tldap.c',
+ deps='''
+ asn1util
+ LIBTSOCKET
+ samba3util
+ smbconf
+ cmocka
+ ''',
+ for_selftest=True)
+
+# libpdb.so should not expose internal symbols that are only usable
+# to the statically linked modules that are merged into libpdb.
+# Note that we always filter these symbols out in libpdb, even
+# when modules are not linked statically. In the latter case
+# symbols will not be present in the libpdb anyway so no hurt is
+# done to the version script.
+private_pdb_match = []
+private_pdb_match.append('!idmap_init')
+private_pdb_match.append('!idmap_sids_to_xids')
+private_pdb_match.append('!idmap_xids_to_sids')
+
+ldapsam_pdb_match = ['!priv2ld', '!smbldap_search_domain_info',
+ '!ldapsam_*', '!groupmap_attr_list*', '!get_userattr_list',
+ '!dominfo_attr_list', '!get_attr_key2string',
+ '!sidmap_attr_list', '!attrib_map_*', '!idpool_attr_list',
+ '!get_attr_list']
+private_pdb_match.append('!pdb_nds_*')
+private_pdb_match.append('!pdb_ldapsam_init*')
+private_pdb_match.append('!pdb_*_init')
+private_pdb_match = private_pdb_match + ldapsam_pdb_match
+
+bld.SAMBA3_LIBRARY('samba-passdb',
+ source='',
+ deps='pdb',
+ private_library=False,
+ grouping_library=True,
+ pc_files=[],
+ public_headers_install=True,
+ public_headers='''
+ include/passdb.h
+ passdb/machine_sid.h
+ passdb/lookup_sid.h
+ ''',
+ abi_match=private_pdb_match,
+ abi_directory='passdb/ABI',
+ vnum='0.28.0')
+
+bld.SAMBA3_SUBSYSTEM('pdb',
+ source='''
+ passdb/pdb_get_set.c
+ passdb/passdb.c
+ lib/util_wellknown.c
+ lib/util_builtin.c
+ passdb/pdb_compat.c
+ lib/util_sid_passdb.c
+ lib/util_unixsids.c
+ passdb/lookup_sid.c
+ passdb/login_cache.c
+ passdb/account_pol.c
+ lib/privileges.c
+ lib/util_nscd.c
+ lib/winbind_util.c
+ passdb/pdb_util.c
+ passdb/pdb_interface.c
+ passdb/pdb_secrets.c''',
+ deps='''
+ secrets3
+ GROUPDB
+ SERVER_MUTEX
+ wbclient
+ LIBCLI_AUTH
+ flag_mapping
+ samba-credentials
+ samba-modules
+ nscd
+ ''')
+
+bld.SAMBA3_LIBRARY('smbldaphelper',
+ source='''
+ passdb/pdb_ldap_schema.c
+ passdb/pdb_ldap_util.c
+ ''',
+ deps='smbldap secrets3',
+ allow_undefined_symbols=True,
+ enabled=bld.CONFIG_SET('HAVE_LDAP'),
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('SERVER_MUTEX',
+ source='lib/server_mutex.c',
+ deps='talloc')
+
+# this includes only the low level parse code, not stuff
+# that requires knowledge of security contexts
+bld.SAMBA3_SUBSYSTEM('REG_PARSE_PRS',
+ source='registry/reg_parse_prs.c')
+
+bld.SAMBA3_SUBSYSTEM('REGFIO',
+ source='registry/regfio.c',
+ deps='samba-util REG_PARSE_PRS')
+
+bld.SAMBA_BINARY('test_registry_regfio',
+ source='registry/tests/test_regfio.c',
+ deps='cmocka samba3-util smbconf REGFIO',
+ local_include=False,
+ for_selftest=True)
+
+# Do not link against this use 'smbconf'
+bld.SAMBA3_SUBSYSTEM('SMBREGISTRY',
+ source='''
+ registry/reg_api.c
+ registry/reg_dispatcher.c
+ registry/reg_cachehook.c
+ registry/reg_objects.c
+ registry/reg_util_internal.c
+ lib/util_nttoken.c
+ registry/reg_backend_db.c
+ registry/reg_parse_internal.c
+ registry/reg_parse.c
+ lib/srprs.c
+ registry/reg_init_basic.c
+ ''',
+ deps='''
+ smbd_shim
+ tdb-wrap3
+ NDR_SECURITY
+ util_tdb
+ talloc
+ replace
+ util_reg
+ samba-util
+ samba-security
+ errors3
+ dbwrap
+ samba3-util
+ ''')
+
+# Do not link against this use 'smbconf'
+bld.SAMBA3_SUBSYSTEM('REG_SMBCONF',
+ source='''
+ registry/reg_backend_smbconf.c
+ registry/reg_init_smbconf.c
+ registry/reg_util_token.c
+ registry/reg_api_util.c
+ ''',
+ deps='SMBREGISTRY')
+
+bld.SAMBA3_LIBRARY('REG_FULL',private_library=True,
+ source='''
+ registry/reg_backend_printing.c
+ registry/reg_backend_shares.c
+ registry/reg_backend_netlogon_params.c
+ registry/reg_backend_prod_options.c
+ registry/reg_backend_tcpip_params.c
+ registry/reg_backend_hkpt_params.c
+ registry/reg_backend_current_version.c
+ registry/reg_backend_perflib.c
+ registry/reg_init_full.c
+ registry/reg_perfcount.c''',
+ deps='''
+ smbconf
+ tdb-wrap3
+ REG_PARSE_PRS
+ pdb
+ ''')
+
+bld.SAMBA3_LIBRARY('cmdline_contexts',
+ source='lib/cmdline_contexts.c',
+ deps='samba3core',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('KRBCLIENT',
+ source='libads/kerberos.c libads/ads_status.c',
+ public_deps='krb5samba asn1util k5crypto gssapi LIBTSOCKET CLDAP LIBNMB')
+
+bld.SAMBA3_SUBSYSTEM('samba3util',
+ source='''
+ lib/system.c
+ lib/sendfile.c
+ lib/recvfile.c
+ lib/time.c
+ lib/util_sid.c
+ lib/util_specialsids.c
+ lib/util_file.c
+ lib/util.c
+ lib/util_path.c
+ lib/util_matching.c
+ lib/util_procid.c
+ lib/util_sock.c
+ lib/util_tsock.c
+ lib/util_transfer_file.c
+ lib/util_macstreams.c
+ ''',
+ deps='''
+ CHARSET3
+ ndr
+ LIBTSOCKET
+ samba-security
+ NDR_SECURITY
+ samba-util
+ util_tdb
+ sys_rw
+ iov_buf
+ ''')
+
+if bld.env.with_ctdb:
+ SAMBA_CLUSTER_SUPPORT_SOURCES='''
+ lib/cluster_support.c
+ lib/dbwrap/dbwrap_ctdb.c
+ lib/messages_ctdb.c
+ lib/messages_ctdb_ref.c
+ lib/ctdbd_conn.c
+ '''
+ SAMBA_CLUSTER_SUPPORT_DEPS='''
+ talloc
+ tevent
+ tdb
+ '''
+else:
+ SAMBA_CLUSTER_SUPPORT_SOURCES='''
+ lib/cluster_support.c
+ lib/ctdb_dummy.c
+ '''
+ SAMBA_CLUSTER_SUPPORT_DEPS='''
+ talloc
+ tevent
+ '''
+
+bld.SAMBA3_LIBRARY('samba-cluster-support',
+ source=SAMBA_CLUSTER_SUPPORT_SOURCES,
+ deps=SAMBA_CLUSTER_SUPPORT_DEPS,
+ allow_undefined_symbols=True,
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('TDB_LIB',
+ source='''
+ lib/dbwrap/dbwrap_open.c
+ lib/dbwrap/dbwrap_watch.c
+ lib/g_lock.c
+ ''',
+ deps='''
+ dbwrap
+ samba-cluster-support
+ ''')
+
+bld.SAMBA3_LIBRARY('messages_util',
+ source='''lib/messages_util.c''',
+ deps='samba-util',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('samba3core',
+ source='''
+ lib/messages.c
+ lib/util_cluster.c
+ lib/id_cache.c
+ lib/serverid.c
+ lib/server_id_watch.c
+ lib/server_id_db_util.c
+ lib/addrchange.c
+ ../lib/util/debug_s3.c
+ lib/dumpcore.c
+ lib/interface.c
+ lib/username.c
+ lib/smbrun.c
+ lib/wins_srv.c
+ lib/substitute.c
+ lib/substitute_generic.c
+ lib/ms_fnmatch.c
+ lib/tallocmsg.c
+ lib/dmallocmsg.c
+ lib/gencache.c
+ lib/util_event.c
+ lib/global_contexts.c
+ lib/ldap_escape.c
+ lib/system_smbd.c
+ lib/audit.c
+ lib/idmap_cache.c
+ lib/namemap_cache.c
+ lib/util_ea.c
+ lib/background.c
+ ''',
+ deps='''
+ samba3util
+ LIBTSOCKET
+ NDR_MESSAGING
+ LIBASYNC_REQ
+ UTIL_PW
+ SAMBA_VERSION
+ PTHREADPOOL
+ interfaces
+ smbconf
+ dbwrap
+ samba3-util
+ errors3
+ server_id_db
+ messages_util
+ messages_dgm
+ talloc_report_printf
+ access
+ TDB_LIB
+ z
+ ''')
+
+bld.SAMBA3_LIBRARY('smbd_shim',
+ source='''lib/smbd_shim.c''',
+ deps='talloc',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('auth_generic',
+ source='libsmb/auth_generic.c',
+ deps='gse gensec')
+
+bld.SAMBA3_LIBRARY('libsmb',
+ source='''
+ libsmb/clientgen.c
+ libsmb/cliconnect.c
+ libsmb/clifile.c
+ libsmb/clirap.c
+ libsmb/clierror.c
+ libsmb/climessage.c
+ libsmb/clireadwrite.c
+ libsmb/clilist.c
+ libsmb/cliprint.c
+ libsmb/clitrans.c
+ libsmb/clisecdesc.c
+ libsmb/cliquota.c
+ libsmb/clifsinfo.c
+ libsmb/clidfs.c
+ libsmb/clioplock.c
+ libsmb/async_smb.c
+ libsmb/clisymlink.c
+ libsmb/smbsock_connect.c
+ libsmb/cli_smb2_fnum.c
+ ''',
+ deps='''
+ auth_generic
+ CLDAP
+ LIBNMB
+ SPNEGO_PARSE
+ LIBTSOCKET
+ KRBCLIENT
+ NDR_IOCTL
+ NDR_QUOTA
+ cli_smb_common
+ tevent
+ ''',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('CLDAP',
+ source='libads/cldap.c',
+ deps='cli-ldap-common cli_cldap LIBTSOCKET')
+
+# NOTE: The secrets3 library is a low level library used by several subsystems.
+# PLEASE DO NOT make it depend on high level libraries like PDB, if you are
+# doing that your design is wrong and needs changing. -SSS
+bld.SAMBA3_LIBRARY('secrets3',
+ source='''
+ passdb/secrets.c
+ passdb/machine_account_secrets.c
+ passdb/machine_sid.c
+ passdb/secrets_lsa.c
+ ''',
+ deps='''
+ ndr-samba4
+ smbconf
+ samba3util
+ dbwrap
+ krb5samba
+ LIBCLI_AUTH
+ samba-credentials
+ ''',
+ private_library=True)
+
+bld.SAMBA3_LIBRARY('smbldap',
+ source='lib/smbldap.c',
+ deps='ldap lber samba-util smbconf',
+ enabled=bld.CONFIG_SET("HAVE_LDAP"),
+ private_library=False,
+ abi_directory='lib/ABI',
+ abi_match='smbldap_*',
+ pc_files=[],
+ vnum='2.1.0',
+ public_headers='include/smbldap.h include/smb_ldap.h')
+
+bld.SAMBA3_LIBRARY('ads',
+ source='''
+ libads/ldap.c
+ libads/sasl.c
+ libads/sasl_wrapping.c
+ libads/krb5_setpw.c
+ libads/kerberos_util.c
+ libads/ldap_user.c
+ libads/ads_struct.c
+ libads/kerberos_keytab.c
+ libads/disp_sec.c
+ libads/ldap_utils.c
+ libads/ldap_schema.c
+ libads/util.c
+ libads/net_ads_setspn.c
+ ''',
+ deps='''
+ cli-ldap-common
+ krb5samba
+ krb5
+ ldap
+ lber
+ KRBCLIENT
+ smbconf
+ LIBNMB
+ libsmb
+ DCUTIL
+ smbldap
+ trusts_util
+ NDR_ADS
+ ''',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('LIBADS_SERVER',
+ source='libads/authdata.c',
+ deps='SERVER_MUTEX ndr-krb5pac krb5samba gssapi')
+
+bld.SAMBA3_SUBSYSTEM('LIBADS_PRINTER',
+ source='libads/ldap_printer.c',
+ deps='samba-util krb5samba')
+
+# Only smbconf should link against this subsystem, else we will create
+# a circular dependency.
+bld.SAMBA3_SUBSYSTEM('SMBCONF_PARAM',
+ source='''
+ param/loadparm.c
+ lib/sharesec.c
+ lib/ldap_debug_handler.c
+ lib/util_names.c
+ ''',
+ deps='''
+ samba-util
+ PARAM_UTIL
+ ldap
+ lber
+ LOADPARM_CTX
+ samba3core
+ param_local.h
+ param_global.h
+ cups
+ ''')
+
+bld.SAMBA3_LIBRARY('smbconf',
+ source='''
+ lib/smbconf/smbconf_init.c
+ lib/smbconf/smbconf_reg.c
+ ''',
+ deps='''
+ LIBSMBCONF
+ REG_SMBCONF
+ SAMBA_VERSION
+ cap
+ charset
+ samba-hostconfig
+ SMBCONF_PARAM
+ samba-util
+ talloc
+ util_reg
+ ''',
+ public_headers='../lib/smbconf/smbconf.h',
+ pc_files=[],
+ vnum='0.0.1')
+
+bld.SAMBA3_SUBSYSTEM('sysquotas',
+ source='''
+ lib/sysquotas.c
+ lib/sysquotas_linux.c
+ lib/sysquotas_xfs.c
+ lib/sysquotas_4A.c
+ lib/sysquotas_4B.c
+ lib/sysquotas_jfs2.c
+ lib/sysquotas_nfs.c
+ ''',
+ allow_warnings=True,
+ deps='samba3-util samba-util tirpc')
+
+NOTIFY_SOURCES=''
+NOTIFY_DEPS=''
+
+if bld.CONFIG_SET("HAVE_INOTIFY"):
+ NOTIFY_SOURCES += ' smbd/notify_inotify.c'
+
+if bld.CONFIG_SET('SAMBA_FAM_LIBS'):
+ NOTIFY_SOURCES += ' smbd/notify_fam.c'
+ NOTIFY_DEPS += ' ' + bld.CONFIG_GET('SAMBA_FAM_LIBS')
+
+if bld.CONFIG_SET('WITH_SMB1SERVER'):
+ SMB1_SOURCES = '''
+ smbd/smb1_message.c
+ smbd/smb1_sesssetup.c
+ smbd/smb1_lanman.c
+ smbd/smb1_utils.c
+ smbd/smb1_aio.c
+ smbd/smb1_ipc.c
+ smbd/smb1_negprot.c
+ smbd/smb1_nttrans.c
+ smbd/smb1_oplock.c
+ smbd/smb1_pipes.c
+ smbd/smb1_reply.c
+ smbd/smb1_service.c
+ smbd/smb1_signing.c
+ smbd/smb1_process.c
+ smbd/smb1_trans2.c
+ '''
+else:
+ SMB1_SOURCES = ''
+
+bld.SAMBA3_LIBRARY('smbd_base',
+ source='''
+ smbd/server_reload.c
+ smbd/files.c
+ smbd/connection.c
+ smbd/utmp.c
+ smbd/session.c
+ smbd/dfree.c
+ smbd/dir.c
+ smbd/password.c
+ smbd/conn_msg.c
+ smbd/conn_idle.c
+ smbd/share_access.c
+ smbd/fileio.c
+ smbd/smb2_ipc.c
+ smbd/smb2_nttrans.c
+ smbd/smb2_pipes.c
+ smbd/smb2_reply.c
+ smbd/smb2_trans2.c
+ smbd/uid.c
+ smbd/dosmode.c
+ smbd/filename.c
+ smbd/open.c
+ smbd/close.c
+ smbd/blocking.c
+ smbd/sec_ctx.c
+ smbd/srvstr.c
+ smbd/vfs.c
+ smbd/seal.c
+ smbd/posix_acls.c
+ lib/sysacls.c
+ smbd/smb2_process.c
+ smbd/smb2_service.c
+ smbd/error.c
+ printing/printspoolss.c
+ printing/rap_jobid.c
+ printing/load.c
+ printing/printer_list.c
+ lib/sessionid_tdb.c
+ lib/cleanupdb.c
+ smbd/fake_file.c
+ smbd/quotas.c
+ smbd/ntquotas.c
+ smbd/msdfs.c
+ smbd/smb2_aio.c smbd/statvfs.c
+ smbd/dmapi.c
+ smbd/smb2_signing.c
+ smbd/file_access.c
+ smbd/dnsregister.c smbd/globals.c
+ smbd/smb2_server.c
+ smbd/smb2_glue.c
+ smbd/smb2_negprot.c
+ smbd/smb2_sesssetup.c
+ smbd/smb2_tcon.c
+ smbd/smb2_create.c
+ smbd/smb2_close.c
+ smbd/smb2_flush.c
+ smbd/smb2_read.c
+ smbd/smb2_write.c
+ smbd/smb2_lock.c
+ smbd/smb2_ioctl.c
+ smbd/smb2_ioctl_dfs.c
+ smbd/smb2_ioctl_filesys.c
+ smbd/smb2_ioctl_named_pipe.c
+ smbd/smb2_ioctl_network_fs.c
+ smbd/smb2_ioctl_smbtorture.c
+ smbd/smb2_keepalive.c
+ smbd/smb2_query_directory.c
+ smbd/smb2_notify.c
+ smbd/smb2_getinfo.c
+ smbd/smb2_setinfo.c
+ smbd/smb2_break.c
+ smbd/smb2_posix.c
+ smbd/smbXsrv_version.c
+ smbd/smbXsrv_client.c
+ smbd/smbXsrv_session.c
+ smbd/smbXsrv_tcon.c
+ smbd/smbXsrv_open.c
+ smbd/server_exit.c
+ smbd/durable.c
+ smbd/scavenger.c
+ smbd/mangle.c
+ smbd/mangle_hash.c
+ smbd/mangle_hash2.c
+ smbd/smb2_oplock.c
+ smbd/oplock_linux.c
+ smbd/notify.c
+ smbd/notify_msg.c
+ smbd/build_options.c
+ smbd/conn.c
+ rpc_server/srv_pipe_hnd.c
+ rpc_server/rpc_ncacn_np.c
+ ''' + NOTIFY_SOURCES + SMB1_SOURCES,
+ deps='''
+ talloc
+ tevent
+ pdb
+ libsmb
+ msrpc3
+ vfs
+ vfs_default
+ vfs_posixacl
+ inotify
+ samba3core
+ param_service
+ AVAHI
+ PROFILE
+ LOCKING
+ LIBADS_SERVER
+ LIBAFS
+ NDR_SMBXSRV
+ LEASES_DB
+ LEASES_UTIL
+ sysquotas
+ NDR_SMB_ACL
+ netapi
+ NDR_IOCTL
+ notifyd
+ vfs_acl_common
+ NDR_QUOTA
+ GNUTLS_HELPERS
+ fd_handle
+ cli_spoolss
+ ''' +
+ bld.env['dmapi_lib'] +
+ bld.env['legacy_quota_libs'] +
+ NOTIFY_DEPS,
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('LOCKING',
+ source='''
+ locking/locking.c
+ locking/brlock.c
+ locking/posix.c
+ locking/share_mode_lock.c
+ ''',
+ deps='''
+ tdb
+ talloc
+ vfs
+ LEASES_DB
+ LEASES_UTIL
+ NDR_OPEN_FILES
+ FNAME_UTIL
+ fd_handle
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('LEASES_DB',
+ source='locking/leases_db.c',
+ deps='NDR_LEASES_DB')
+
+bld.SAMBA3_SUBSYSTEM('LEASES_UTIL',
+ source='locking/leases_util.c',
+ deps='NDR_OPEN_FILES')
+
+if bld.CONFIG_GET("WITH_PROFILE"):
+ bld.SAMBA_SUBSYSTEM('PROFILE_READ',
+ source='profile/profile_read.c',
+ deps='gnutls talloc tdb')
+ bld.SAMBA3_SUBSYSTEM('PROFILE',
+ source='profile/profile.c',
+ deps='''
+ samba-util
+ PROFILE_READ
+ ''')
+else:
+ bld.SAMBA3_SUBSYSTEM('PROFILE',
+ source='profile/profile_dummy.c',
+ deps='')
+
+bld.SAMBA3_SUBSYSTEM('PRINTBASE',
+ source='''
+ printing/notify.c
+ printing/printing_db.c
+ ''',
+ deps='''
+ samba-util
+ tdb
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('PRINTBACKEND',
+ source='''
+ printing/printing.c
+ printing/nt_printing.c
+ printing/nt_printing_tdb.c
+ printing/nt_printing_migrate_internal.c
+ printing/nt_printing_ads.c
+ printing/queue_process.c
+ ''',
+ deps='''
+ PRINTBASE
+ LIBADS_PRINTER
+ tdb
+ printing_migrate
+ ''')
+
+bld.SAMBA3_LIBRARY('printing_migrate',
+ source='''
+ printing/nt_printing_migrate.c
+ rpc_client/cli_winreg_spoolss.c
+ printing/nt_printing_os2.c
+ ''',
+ deps='''
+ NDR_NTPRINTING
+ cli_spoolss
+ RPC_NDR_WINREG
+ LIBCLI_WINREG
+ smbconf
+ ''',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('PRINTING',
+ source='''
+ printing/pcap.c
+ printing/print_svid.c
+ printing/print_aix.c
+ printing/print_cups.c
+ printing/print_generic.c
+ printing/lpq_parse.c
+ printing/print_standard.c
+ printing/print_iprint.c
+ ''',
+ deps='''
+ NDR_PRINTCAP
+ dbwrap
+ tdb-wrap3
+ SMBCONF_PARAM
+ PRINTBASE
+ smbd_base
+ cups
+ ''')
+
+bld.SAMBA_BINARY('samba-bgqd',
+ source='printing/samba-bgqd.c',
+ deps='''
+ samba3core
+ CMDLINE_S3
+ AUTH_COMMON
+ RPC_SPOOLSS
+ ''',
+ install_path='${LIBEXECDIR}/samba')
+
+bld.SAMBA3_SUBSYSTEM('FNAME_UTIL',
+ source='lib/filename_util.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('LIBNET',
+ source='libnet/libnet_join.c libnet/libnet_join_offline.c',
+ deps='NDR_LIBNET_JOIN INIT_SAMR net_keytab pdb')
+
+bld.SAMBA3_LIBRARY('net_keytab',
+ source='libnet/libnet_keytab.c',
+ deps='krb5samba ads',
+ private_library=True)
+
+
+bld.SAMBA3_SUBSYSTEM('LIBNET_DSSYNC',
+ source='''
+ libnet/libnet_dssync.c
+ libnet/libnet_dssync_passdb.c
+ libnet/libnet_dssync_keytab.c
+ ''',
+ deps='''
+ LIBNET
+ RPC_NDR_DRSUAPI
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('LIBEVENTLOG',
+ source='lib/eventlog/eventlog.c',
+ deps='NDR_EVENTLOG tdb')
+
+bld.SAMBA3_SUBSYSTEM('LIBNMB',
+ source='''
+ libsmb/unexpected.c
+ libsmb/namecache.c
+ libsmb/nmblib.c
+ libsmb/clidgram.c
+ libsmb/namequery.c
+ libsmb/conncache.c
+ libads/sitename_cache.c
+ ''',
+ deps='''
+ LIBTSOCKET
+ LIBCLI_NETLOGON
+ samba3util
+ addns
+ lmhosts
+ resolv
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('SERVICES',
+ source='''
+ services/svc_spoolss.c
+ services/svc_rcinit.c
+ services/svc_winreg_glue.c
+ services/svc_netlogon.c
+ services/svc_winreg.c
+ services/svc_wins.c
+ ''',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('PLAINTEXT_AUTH',
+ source='''
+ auth/pampass.c
+ auth/pass_check.c
+ ''',
+ deps='''
+ crypt
+ pam
+ PAM_ERRORS
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('PASSCHANGE',
+ source='libsmb/passchange.c',
+ deps='''
+ LIBCLI_SAMR
+ INIT_LSA
+ msrpc3
+ krb5samba
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('SAMBA_VERSION',
+ source='lib/version.c')
+
+bld.SAMBA3_SUBSYSTEM('SLCACHE',
+ source='libsmb/samlogon_cache.c',
+ deps='''
+ samba-util
+ tdb
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('dsgetdcname',
+ source='''
+ libsmb/dsgetdcname.c
+ ''',
+ deps='''
+ CLDAP
+ LIBNMB
+ ''')
+
+bld.SAMBA3_SUBSYSTEM('DCUTIL',
+ source='''
+ libsmb/namequery_dc.c
+ ''',
+ deps='''
+ ads
+ dsgetdcname
+ msrpc3
+ libcli_lsa3
+ ''')
+
+bld.SAMBA3_LIBRARY('trusts_util',
+ source='libsmb/trusts_util.c',
+ deps='''
+ libcli_netlogon3
+ msrpc3
+ samba-passdb
+ ''',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('tdb-wrap3',
+ source='lib/util_tdb.c',
+ deps='talloc samba3-util')
+
+bld.SAMBA3_LIBRARY('samba3-util',
+ source='''
+ lib/util_sec.c
+ lib/util_str.c
+ lib/adt_tree.c
+ lib/util_malloc.c
+ lib/namearray.c
+ lib/file_id.c
+ lib/cbuf.c
+ lib/per_thread_cwd.c
+ ''',
+ deps='''
+ samba-util
+ charset
+ ''',
+ private_library=True)
+
+bld.SAMBA_LIBRARY('xattr_tdb',
+ source='lib/xattr_tdb.c',
+ deps='NDR_XATTR dbwrap samba3-util',
+ private_library=True)
+
+bld.SAMBA3_LIBRARY('CHARSET3',
+ source='''
+ lib/charcnv.c
+ lib/fstring.c
+ ''',
+ public_deps='''
+ ICONV_WRAPPER
+ charset
+ ''',
+ deps='''
+ samba-util
+ samba3-util
+ ''',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('errors3',
+ source='''
+ libsmb/errormap.c
+ libsmb/smberr.c
+ lib/errmap_unix.c
+ ''',
+ deps='samba-errors')
+
+bld.SAMBA3_SUBSYSTEM('LIBCLI_SAMR',
+ source='rpc_client/cli_samr.c',
+ deps='RPC_NDR_SAMR INIT_SAMR')
+
+bld.SAMBA3_LIBRARY('libcli_lsa3',
+ source='rpc_client/cli_lsarpc.c',
+ deps='RPC_NDR_LSA INIT_LSA',
+ private_library=True)
+
+bld.SAMBA3_LIBRARY('libcli_netlogon3',
+ source='''
+ rpc_client/cli_netlogon.c
+ rpc_client/util_netlogon.c
+ ''',
+ deps='''
+ msrpc3
+ RPC_NDR_NETLOGON
+ cliauth
+ smbconf
+ NETLOGON_CREDS_CLI''',
+ private_library=True)
+
+bld.SAMBA3_LIBRARY('cli_spoolss',
+ source='''
+ rpc_client/cli_spoolss.c
+ rpc_client/init_spoolss.c
+ ''',
+ deps='''
+ RPC_NDR_SPOOLSS
+ smbconf
+ secrets3
+ gensec
+ ''',
+ private_library=True)
+
+bld.SAMBA3_SUBSYSTEM('LIBCLI_WINREG',
+ source='rpc_client/cli_winreg.c',
+ deps='RPC_NDR_WINREG')
+
+bld.SAMBA3_SUBSYSTEM('LIBCLI_WINREG_INTERNAL',
+ source='rpc_client/cli_winreg_int.c',
+ deps='LIBCLI_WINREG RPC_SERVER')
+
+bld.SAMBA3_SUBSYSTEM('RPC_CLIENT_SCHANNEL',
+ source='rpc_client/cli_pipe_schannel.c',
+ deps='samba-util krb5samba')
+
+bld.SAMBA3_SUBSYSTEM('RPCCLI_MDSSVC',
+ source='''
+ rpc_client/cli_mdssvc.c
+ rpc_client/cli_mdssvc_util.c
+ ''',
+ deps='mdssvc RPC_NDR_MDSSVC')
+
+bld.SAMBA3_SUBSYSTEM('RPCCLI_WSP',
+ source='''
+ rpc_client/wsp_cli.c
+ ''',
+ deps='tevent-util tstream_binding_handle')
+
+bld.SAMBA3_SUBSYSTEM('INIT_LSA',
+ source='rpc_client/init_lsa.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('INIT_SAMR',
+ source='rpc_client/init_samr.c',
+ deps='samba-util GNUTLS_HELPERS')
+
+bld.SAMBA3_SUBSYSTEM('LIBLSA',
+ source='lib/lsa.c')
+
+bld.SAMBA3_SUBSYSTEM('tevent-glib-glue',
+ source='lib/tevent_glib_glue.c',
+ deps='glib-2.0',
+ enabled=bld.CONFIG_SET('WITH_TEVENT_GLIB_GLUE'),
+)
+
+bld.SAMBA3_SUBSYSTEM('ADOUBLE',
+ source='lib/adouble.c',
+ deps='STRING_REPLACE')
+
+bld.SAMBA3_BINARY('test_adouble',
+ source='lib/test_adouble.c',
+ deps='smbd_base STRING_REPLACE cmocka',
+ for_selftest=True)
+
+bld.SAMBA3_SUBSYSTEM('STRING_REPLACE',
+ source='lib/string_replace.c')
+
+bld.SAMBA3_SUBSYSTEM('fd_handle',
+ source='smbd/fd_handle.c')
+
+########################## BINARIES #################################
+
+bld.SAMBA3_BINARY('smbd/smbd',
+ source='smbd/server.c smbd/smbd_cleanupd.c',
+ deps='''
+ CMDLINE_S3
+ smbd_base
+ REG_FULL
+ ''',
+ install_path='${SBINDIR}')
+
+
+bld.SAMBA3_SUBSYSTEM('TDB_VALIDATE',
+ source='lib/tdb_validate.c',
+ deps='samba-util')
+
+bld.SAMBA3_SUBSYSTEM('util_sd',
+ deps='smbclient',
+ source='lib/util_sd.c')
+
+bld.SAMBA3_BINARY('client/smbclient',
+ source='''
+ client/client.c
+ client/clitar.c
+ client/dnsbrowse.c
+ ''',
+ deps='''
+ talloc
+ CMDLINE_S3
+ smbconf
+ ndr-standard
+ SMBREADLINE
+ libsmb
+ msrpc3
+ RPC_NDR_SRVSVC
+ cli_smb_common
+ archive
+ ''')
+
+bld.SAMBA3_BINARY('smbspool',
+ source='client/smbspool.c',
+ deps='''
+ talloc
+ smbconf
+ libsmb
+ samba3core
+ ''')
+
+bld.SAMBA3_BINARY('smbspool_krb5_wrapper',
+ source='client/smbspool_krb5_wrapper.c',
+ deps='''
+ DYNCONFIG
+ cups
+ krb5samba
+ ''',
+ install_path='${LIBEXECDIR}/samba',
+ enabled=bld.CONFIG_SET('HAVE_CUPS'))
+
+bld.SAMBA3_BINARY('smbspool_argv_wrapper',
+ source='script/tests/smbspool_argv_wrapper.c',
+ for_selftest=True)
+
+bld.SAMBA3_BINARY('smbconftort',
+ source='lib/smbconf/testsuite.c',
+ deps='''
+ talloc
+ smbconf
+ CMDLINE_S3
+ ''',
+ for_selftest=True)
+
+bld.SAMBA3_BINARY('test_mdsparser_es',
+ source='''
+ rpc_server/mdssvc/test_mdsparser_es.c
+ rpc_server/mdssvc/es_parser.y
+ rpc_server/mdssvc/es_lexer.l
+ rpc_server/mdssvc/es_mapping.c
+ ''',
+ deps='''
+ samba3-util
+ talloc
+ smbconf
+ jansson
+ cmocka
+ CMDLINE_S3
+ ''',
+ enabled=bld.env.spotlight_backend_es,
+ for_selftest=True)
+
+bld.SAMBA3_BINARY('versiontest',
+ source='lib/version_test.c',
+ deps='''
+ SAMBA_VERSION
+ smbconf
+ ''',
+ install=False)
+
+bld.SAMBA3_BINARY('timelimit',
+ source='script/tests/timelimit.c',
+ for_selftest=True)
+
+bld.SAMBA3_BINARY('vlp',
+ source='printing/tests/vlp.c',
+ deps='''
+ talloc
+ smbconf
+ ''',
+ for_selftest=True)
+
+
+pyrpc_util = bld.pyembed_libname('pyrpc_util')
+pytalloc_util = bld.pyembed_libname('pytalloc-util')
+bld.SAMBA3_PYTHON('pysmbd',
+ source='smbd/pysmbd.c',
+ deps=' '.join(['smbd_base', pyrpc_util, pytalloc_util]),
+ realname='samba/samba3/smbd.so'
+ )
+
+pycredentials = 'pycredentials'
+bld.SAMBA3_PYTHON('pylibsmb',
+ source='libsmb/pylibsmb.c',
+ deps='smbclient samba-credentials %s' % pycredentials,
+ realname='samba/samba3/libsmb_samba_cwrapper.so'
+ )
+
+bld.SAMBA3_PYTHON('pymdscli',
+ source='rpc_client/py_mdscli.c',
+ deps=' '.join(['RPCCLI_MDSSVC', pytalloc_util, pyrpc_util]),
+ realname='samba/samba3/mdscli.so')
+
+bld.SAMBA3_PYTHON('pys3smbconf',
+ source='lib/smbconf/pys3smbconf.c',
+ deps='smbconf',
+ realname='samba/samba3/smbconf.so')
+
+bld.SAMBA3_BINARY('spotlight2sparql',
+ source='''
+ rpc_server/mdssvc/sparql_parser_test.c
+ rpc_server/mdssvc/sparql_parser.y
+ rpc_server/mdssvc/sparql_lexer.l
+ rpc_server/mdssvc/sparql_mapping.c''',
+ deps='samba3-util talloc ' + bld.env['libtracker'],
+ enabled=bld.env.spotlight_backend_tracker,
+ install=False)
+
+bld.SAMBA3_BINARY('spotlight2es',
+ source='''
+ rpc_server/mdssvc/es_parser_test.c
+ rpc_server/mdssvc/es_parser.y
+ rpc_server/mdssvc/es_lexer.l
+ rpc_server/mdssvc/es_mapping.c''',
+ deps='samba3-util talloc jansson smbconf',
+ enabled=bld.env.spotlight_backend_es,
+ install=False)
+
+bld.SAMBA3_BINARY('tevent_glib_glue_test',
+ source='lib/tevent_glib_glue_tests.c',
+ deps='''
+ talloc
+ libsmb
+ tevent-glib-glue''',
+ enabled=bld.CONFIG_SET('WITH_TEVENT_GLIB_GLUE'),
+ for_selftest=True)
+
+bld.SAMBA3_BINARY('tevent_glib_tracker',
+ source='utils/async-tracker.c',
+ deps='''
+ talloc
+ libsmb
+ CMDLINE_S3
+ tevent-glib-glue ''' + bld.env['libtracker'],
+ enabled=bld.CONFIG_SET('HAVE_TRACKER') and bld.CONFIG_SET('WITH_TEVENT_GLIB_GLUE'),
+ install=False)
+
+########################## INCLUDES #################################
+
+bld.RECURSE('auth')
+bld.RECURSE('libgpo/gpext')
+bld.RECURSE('librpc')
+bld.RECURSE('librpc/idl')
+bld.RECURSE('libsmb')
+bld.RECURSE('modules')
+bld.RECURSE('param')
+bld.RECURSE('passdb')
+bld.RECURSE('rpc_server')
+bld.RECURSE('script')
+bld.RECURSE('winbindd')
+bld.RECURSE('../examples/libsmbclient')
+bld.RECURSE('../examples/pdb')
+bld.RECURSE('../examples/VFS')
+bld.RECURSE('../examples/fuse')
+bld.RECURSE('../examples/winexe')
+bld.RECURSE('lib/netapi/tests')
+bld.RECURSE('lib/netapi/examples')
+bld.RECURSE('smbd/notifyd')
+bld.RECURSE('rpcclient')
+bld.RECURSE('utils')
+bld.RECURSE('nmbd')
+bld.RECURSE('lib/util')
+bld.RECURSE('torture')
+
+bld.ENFORCE_GROUP_ORDERING()
+bld.CHECK_PROJECT_RULES()
+
diff --git a/source3/wscript_configure_system_ncurses b/source3/wscript_configure_system_ncurses
new file mode 100644
index 0000000..133a5b5
--- /dev/null
+++ b/source3/wscript_configure_system_ncurses
@@ -0,0 +1,27 @@
+import sys
+from waflib import Logs, Options
+
+Logs.info("Looking for ncurses features")
+
+conf.find_program('ncurses5-config', var='NCURSES_CONFIG')
+if not conf.env.NCURSES_CONFIG:
+ conf.find_program('ncurses6-config', var='NCURSES_CONFIG')
+
+if conf.env.NCURSES_CONFIG:
+ conf.CHECK_CFG(path=conf.env.NCURSES_CONFIG, args="--cflags --libs",
+ package="", uselib_store="NCURSES")
+
+conf.CHECK_HEADERS('ncurses.h menu.h panel.h form.h', lib='ncurses')
+
+conf.CHECK_FUNCS_IN('initscr', 'ncurses')
+conf.CHECK_FUNCS_IN('set_menu_items item_count', 'menu')
+conf.CHECK_FUNCS_IN('new_panel show_panel', 'panel')
+conf.CHECK_FUNCS_IN('new_field new_form', 'form')
+
+if conf.CONFIG_SET('HAVE_NCURSES_H') and \
+ conf.CONFIG_SET('HAVE_MENU_H') and \
+ conf.CONFIG_SET('HAVE_PANEL_H') and \
+ conf.CONFIG_SET('HAVE_FORM_H'):
+ conf.DEFINE('HAVE_NCURSES', '1')
+else:
+ conf.undefine('HAVE_NCURSES')